From 40cdd07599b382b91ee82298dc0bcfb379296bea Mon Sep 17 00:00:00 2001 From: Lewis Tunstall Date: Fri, 18 Mar 2022 17:16:23 +0100 Subject: [PATCH 01/51] Add note about `remove_unused_columns` for whole word masking --- chapters/en/chapter7/section3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/en/chapter7/section3.mdx b/chapters/en/chapter7/section3.mdx index 0848222b3..54308d983 100644 --- a/chapters/en/chapter7/section3.mdx +++ b/chapters/en/chapter7/section3.mdx @@ -696,7 +696,7 @@ training_args = TrainingArguments( ) ``` -Here we tweaked a few of the default options, including `logging_steps` to ensure we track the training loss with each epoch. We've also used `fp16=True` to enable mixed-precision training, which gives us another boost in speed. +Here we tweaked a few of the default options, including `logging_steps` to ensure we track the training loss with each epoch. We've also used `fp16=True` to enable mixed-precision training, which gives us another boost in speed. By default, the `Trainer` will remove any columns that are not part of the model's `forward()` method. This means that if you're using the whole word masking collator, you'll also need to set `remove_unused_columns=False` to ensure we don't lose the `word_ids` column during training.conda a Note that you can specify the name of the repository you want to push to with the `hub_model_id` argument (in particular, you will have to use this argument to push to an organization). For instance, when we pushed the model to the [`huggingface-course` organization](https://huggingface.co/huggingface-course), we added `hub_model_id="huggingface-course/distilbert-finetuned-imdb"` to `TrainingArguments`. By default, the repository used will be in your namespace and named after the output directory you set, so in our case it will be `"lewtun/distilbert-finetuned-imdb"`. From 82c57b17c4e11fc33f32febeaf1f6ad46038794f Mon Sep 17 00:00:00 2001 From: lewtun Date: Fri, 18 Mar 2022 17:27:46 +0100 Subject: [PATCH 02/51] Merge pull request #24 from huggingface/fix-typo Fix typo --- chapters/en/chapter7/section3.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/en/chapter7/section3.mdx b/chapters/en/chapter7/section3.mdx index 54308d983..046780d3e 100644 --- a/chapters/en/chapter7/section3.mdx +++ b/chapters/en/chapter7/section3.mdx @@ -696,7 +696,7 @@ training_args = TrainingArguments( ) ``` -Here we tweaked a few of the default options, including `logging_steps` to ensure we track the training loss with each epoch. We've also used `fp16=True` to enable mixed-precision training, which gives us another boost in speed. By default, the `Trainer` will remove any columns that are not part of the model's `forward()` method. This means that if you're using the whole word masking collator, you'll also need to set `remove_unused_columns=False` to ensure we don't lose the `word_ids` column during training.conda a +Here we tweaked a few of the default options, including `logging_steps` to ensure we track the training loss with each epoch. We've also used `fp16=True` to enable mixed-precision training, which gives us another boost in speed. By default, the `Trainer` will remove any columns that are not part of the model's `forward()` method. This means that if you're using the whole word masking collator, you'll also need to set `remove_unused_columns=False` to ensure we don't lose the `word_ids` column during training. Note that you can specify the name of the repository you want to push to with the `hub_model_id` argument (in particular, you will have to use this argument to push to an organization). For instance, when we pushed the model to the [`huggingface-course` organization](https://huggingface.co/huggingface-course), we added `hub_model_id="huggingface-course/distilbert-finetuned-imdb"` to `TrainingArguments`. By default, the repository used will be in your namespace and named after the output directory you set, so in our case it will be `"lewtun/distilbert-finetuned-imdb"`. From 1a8221fc091acd3f9e2ee6f6b4fff5fd0e99f09b Mon Sep 17 00:00:00 2001 From: lewtun Date: Mon, 21 Mar 2022 13:24:30 +0100 Subject: [PATCH 03/51] Merge pull request #26 from huggingface/fix-qa-offsets Fix inequalities in answer spans for QA chapter --- chapters/en/chapter7/section7.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapters/en/chapter7/section7.mdx b/chapters/en/chapter7/section7.mdx index f71c25bbd..7c90a923c 100644 --- a/chapters/en/chapter7/section7.mdx +++ b/chapters/en/chapter7/section7.mdx @@ -291,7 +291,7 @@ for i, offset in enumerate(inputs["offset_mapping"]): context_end = idx - 1 # If the answer is not fully inside the context, label is (0, 0) - if offset[context_start][0] > end_char or offset[context_end][1] < start_char: + if offset[context_start][0] > start_char or offset[context_end][1] < end_char: start_positions.append(0) end_positions.append(0) else: @@ -398,7 +398,7 @@ def preprocess_training_examples(examples): context_end = idx - 1 # If the answer is not fully inside the context, label is (0, 0) - if offset[context_start][0] > end_char or offset[context_end][1] < start_char: + if offset[context_start][0] > start_char or offset[context_end][1] < end_char: start_positions.append(0) end_positions.append(0) else: From 5459ef14cbe2a036374fc4c65e784a7934bc5604 Mon Sep 17 00:00:00 2001 From: lewtun Date: Tue, 22 Mar 2022 14:52:48 +0100 Subject: [PATCH 04/51] Merge pull request #30 from huggingface/fix-modelcard-url Update model card URL --- chapters/en/chapter4/section4.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chapters/en/chapter4/section4.mdx b/chapters/en/chapter4/section4.mdx index cc6bb02cf..9863aa24b 100644 --- a/chapters/en/chapter4/section4.mdx +++ b/chapters/en/chapter4/section4.mdx @@ -85,4 +85,4 @@ datasets: This metadata is parsed by the Hugging Face Hub, which then identifies this model as being a French model, with an MIT license, trained on the Oscar dataset. -The [full model card specification](https://raw.githubusercontent.com/huggingface/huggingface_hub/main/modelcard.md) allows specifying languages, licenses, tags, datasets, metrics, as well as the evaluation results the model obtained when training. +The [full model card specification](https://github.com/huggingface/hub-docs/blame/main/modelcard.md) allows specifying languages, licenses, tags, datasets, metrics, as well as the evaluation results the model obtained when training. From 9744ac4fca243f348a0eaa9495c2728940029408 Mon Sep 17 00:00:00 2001 From: lewtun Date: Thu, 31 Mar 2022 15:39:10 +0200 Subject: [PATCH 05/51] Merge pull request #69 from yulonglin/patch-1 Correct typo mixing up space and newline symbols --- chapters/en/chapter6/2.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chapters/en/chapter6/2.mdx b/chapters/en/chapter6/2.mdx index 8c46a5716..7eba91c92 100644 --- a/chapters/en/chapter6/2.mdx +++ b/chapters/en/chapter6/2.mdx @@ -157,7 +157,7 @@ tokens 'Ġnumbers', 'Ġ`', 'a', '`', 'Ġand', 'Ġ`', 'b', '`', '."', '""', 'Ċ', 'Ġ', 'Ġ', 'Ġ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb'] ``` -This tokenizer has a few special symbols, like `Ċ` and `Ġ`, which denote spaces and newlines, respectively. As we can see, this is not too efficient: the tokenizer returns individual tokens for each space, when it could group together indentation levels (since having sets of four or eight spaces is going to be very common in code). It also split the function name a bit weirdly, not being used to seeing words with the `_` character. +This tokenizer has a few special symbols, like `Ġ` and `Ċ`, which denote spaces and newlines, respectively. As we can see, this is not too efficient: the tokenizer returns individual tokens for each space, when it could group together indentation levels (since having sets of four or eight spaces is going to be very common in code). It also split the function name a bit weirdly, not being used to seeing words with the `_` character. Let's train a new tokenizer and see if it solves those issues. For this, we'll use the method `train_new_from_iterator()`: @@ -183,7 +183,7 @@ tokens 'a', '`', 'Ġand', 'Ġ`', 'b', '`."""', 'ĊĠĠĠ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb'] ``` -Here we again see the special symbols `Ċ` and `Ġ` that denote spaces and newlines, but we can also see that our tokenizer learned some tokens that are highly specific to a corpus of Python functions: for example, there is a `ĊĠĠĠ` token that represents an indentation, and a `Ġ"""` token that represents the three quotes that start a docstring. The tokenizer also correctly split the function name on `_`. This is quite a compact representation; comparatively, using the plain English tokenizer on the same example will give us a longer sentence: +Here we again see the special symbols `Ġ` and `Ċ` that denote spaces and newlines, but we can also see that our tokenizer learned some tokens that are highly specific to a corpus of Python functions: for example, there is a `ĊĠĠĠ` token that represents an indentation, and a `Ġ"""` token that represents the three quotes that start a docstring. The tokenizer also correctly split the function name on `_`. This is quite a compact representation; comparatively, using the plain English tokenizer on the same example will give us a longer sentence: ```py print(len(tokens)) From 74e95ed00d07d31b3c2a54a89342ee46239c543a Mon Sep 17 00:00:00 2001 From: lewtun Date: Mon, 11 Apr 2022 11:08:32 +0200 Subject: [PATCH 06/51] Bump release (#99) * Bump release * Update author list Co-authored-by: DOOHAE JUNG Co-authored-by: m_khandaker Co-authored-by: Md. Al-Amin Khandaker Co-authored-by: ftarlaci <18291571+ftarlaci@users.noreply.github.com> Co-authored-by: Doohae Jung <80743307+Doohae@users.noreply.github.com> Co-authored-by: melaniedrevet --- .github/workflows/build_documentation.yml | 2 +- .github/workflows/build_pr_documentation.yml | 2 +- README.md | 4 +- chapters/bn/_toctree.yml | 4 + chapters/bn/chapter0/1.mdx | 112 +++ chapters/fr/_toctree.yml | 22 +- chapters/fr/chapter5/1.mdx | 17 + chapters/fr/chapter5/2.mdx | 167 +++++ chapters/fr/chapter5/3.mdx | 744 +++++++++++++++++++ chapters/fr/chapter5/4.mdx | 287 +++++++ chapters/fr/chapter5/5.mdx | 469 ++++++++++++ chapters/fr/chapter5/6.mdx | 530 +++++++++++++ chapters/fr/chapter5/7.mdx | 11 + chapters/fr/chapter5/8.mdx | 226 ++++++ chapters/ko/_toctree.yml | 29 + chapters/ko/chapter0/1.mdx | 110 +++ chapters/ko/chapter1/1.mdx | 52 ++ chapters/ko/chapter1/10.mdx | 254 +++++++ chapters/ko/chapter1/2.mdx | 21 + chapters/ko/chapter1/3.mdx | 328 ++++++++ chapters/ko/chapter1/4.mdx | 171 +++++ chapters/ko/chapter1/5.mdx | 17 + chapters/ko/chapter1/6.mdx | 16 + chapters/ko/chapter1/7.mdx | 16 + chapters/ko/chapter1/8.mdx | 32 + chapters/ko/chapter1/9.mdx | 11 + chapters/tr/_toctree.yml | 6 + chapters/tr/chapter1/2.mdx | 21 + 28 files changed, 3677 insertions(+), 4 deletions(-) create mode 100644 chapters/bn/_toctree.yml create mode 100644 chapters/bn/chapter0/1.mdx create mode 100644 chapters/fr/chapter5/1.mdx create mode 100644 chapters/fr/chapter5/2.mdx create mode 100644 chapters/fr/chapter5/3.mdx create mode 100644 chapters/fr/chapter5/4.mdx create mode 100644 chapters/fr/chapter5/5.mdx create mode 100644 chapters/fr/chapter5/6.mdx create mode 100644 chapters/fr/chapter5/7.mdx create mode 100644 chapters/fr/chapter5/8.mdx create mode 100644 chapters/ko/_toctree.yml create mode 100644 chapters/ko/chapter0/1.mdx create mode 100644 chapters/ko/chapter1/1.mdx create mode 100644 chapters/ko/chapter1/10.mdx create mode 100644 chapters/ko/chapter1/2.mdx create mode 100644 chapters/ko/chapter1/3.mdx create mode 100644 chapters/ko/chapter1/4.mdx create mode 100644 chapters/ko/chapter1/5.mdx create mode 100644 chapters/ko/chapter1/6.mdx create mode 100644 chapters/ko/chapter1/7.mdx create mode 100644 chapters/ko/chapter1/8.mdx create mode 100644 chapters/ko/chapter1/9.mdx create mode 100644 chapters/tr/chapter1/2.mdx diff --git a/.github/workflows/build_documentation.yml b/.github/workflows/build_documentation.yml index c29476fa2..a7714c94a 100644 --- a/.github/workflows/build_documentation.yml +++ b/.github/workflows/build_documentation.yml @@ -14,6 +14,6 @@ jobs: package: course path_to_docs: course/chapters/ additional_args: --not_python_module - languages: en es fa fr he pt ru th tr + languages: bn en es fa fr he ko pt ru th tr secrets: token: ${{ secrets.HUGGINGFACE_PUSH }} \ No newline at end of file diff --git a/.github/workflows/build_pr_documentation.yml b/.github/workflows/build_pr_documentation.yml index b83457820..f7b92f51d 100644 --- a/.github/workflows/build_pr_documentation.yml +++ b/.github/workflows/build_pr_documentation.yml @@ -16,5 +16,5 @@ jobs: package: course path_to_docs: course/chapters/ additional_args: --not_python_module - languages: en es fa fr he pt ru th tr + languages: bn en es fa fr he ko pt ru th tr hub_base_path: https://moon-ci-docs.huggingface.co/course \ No newline at end of file diff --git a/README.md b/README.md index 32e7d7b11..2b70941d7 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,10 @@ This repo contains the content that's used to create the **[Hugging Face course] | Language | Source | Authors | |:-------------------------------------------------------|:-----------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | [English](https://huggingface.co/course/en/chapter1/1) | [`chapters/en`](https://github.com/huggingface/course/tree/main/chapters/en) | [@sgugger](https://github.com/sgugger), [@lewtun](https://github.com/lewtun), [@LysandreJik](https://github.com/LysandreJik), [@Rocketknight1](https://github.com/Rocketknight1), [@sashavor](https://github.com/sashavor), [@osanseviero](https://github.com/osanseviero), [@SaulLu](https://github.com/SaulLu), [@lvwerra](https://github.com/lvwerra) | +| [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) | +| [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) | | [Spanish](https://huggingface.co/course/es/chapter1/1) (WIP) | [`chapters/es`](https://github.com/huggingface/course/tree/main/chapters/es) | [@camartinezbu](https://github.com/camartinezbu) | -| [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) | +| [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) | ### Translating the course into your language diff --git a/chapters/bn/_toctree.yml b/chapters/bn/_toctree.yml new file mode 100644 index 000000000..6be05c074 --- /dev/null +++ b/chapters/bn/_toctree.yml @@ -0,0 +1,4 @@ +- title: 0. সেটআপ + sections: + - local: chapter0/1 + title: ভূমিকা diff --git a/chapters/bn/chapter0/1.mdx b/chapters/bn/chapter0/1.mdx new file mode 100644 index 000000000..ab323044e --- /dev/null +++ b/chapters/bn/chapter0/1.mdx @@ -0,0 +1,112 @@ +# ভূমিকা + +হাগিং ফেস কোর্সে স্বাগতম! এই অধ্যায়টি একটি "ওয়ার্কিং এনভায়রনমেন্ট" সেট আপ করতে আপনাকে গাইড করবে। আপনি যদি এইপ্রথম কোর্সটি শুরু করে থাকেন, আমরা আপনাকে প্রথমে [অধ্যায় 1](/course/chapter1) একবার দেখে পড়ে আসার পরামর্শ দিচ্ছি, ফিরে এসে "ওয়ার্কিং এনভায়রনমেন্ট" সেট আপ করুন যাতে আপনি নিজেই কোডটি চেষ্টা করতে পারেন। + +এই কোর্সে আমরা যে সমস্ত লাইব্রেরিগুলি ব্যবহার করব সেগুলি পাইথন প্যাকেজ হিসাবে পাওয়া যাবে, তাই এখানে আমরা আপনাকে দেখাব কিভাবে একটি পাইথন এনভায়রনমেন্ট সেট আপ করতে হয় এবং আপনার প্রয়োজনীয় নির্দিষ্ট লাইব্রেরিগুলি ইনস্টল করতে হয়৷ + +Colab নোটবুক বা পাইথন virtual environment ব্যবহার করে আমরা "ওয়ার্কিং এনভায়রনমেন্ট" সেট-আপ করার দুটি উপায় কভার করব। যে পদ্ধতিটি আপনার কাছে সহজ সেটি আপনি বেছে নিতে পাড়েন। যারা নতুন শুরু করছেন তাদের জন্য আমরা Colab নোটবুক ব্যবহার করে শুরু করতে জোরালোভাবে রিকমেন্ড করি। + +মনে রাখবেন যে এখানে উইন্ডোজ সিস্টেম কভার করা হবে না। আপনি যদি উইন্ডোজ চালান, তাহলে আমরা Colab নোটবুক ব্যবহার করে ফলো করার পরামর্শ দিচ্ছি। আর আপনি যদি লিনাক্স ডিস্ট্রিবিউশন বা ম্যাকওএস ব্যবহার করেন তবে এখানে বর্ণিত পদ্ধতির যেকোনো একটি ব্যবহার করতে পারেন। + +কোর্সের অনেকটাই হাগিং ফেস অ্যাকাউন্ট উপর নির্ভর করবে। তাই আমরা একটি একাউন্ট ওপেন করার করার পরামর্শ দিচ্ছি: [একটি অ্যাকাউন্ট তৈরি করুন](https://huggingface.co/join)। + +## Google Colab নোটবুক ব্যবহার করার পদ্ধতি + +Colab নোটবুক ব্যবহার করার সবচেয়ে সহজ সেটআপ হচ্ছে ব্রাউজারে একটি নোটবুক ওপেন করুন এবং সরাসরি কোডিং এ যান! + +আপনি যদি Colab-এর সাথে পরিচিত না হন তাহলে আমরা আপনাকে [Colab পরিচয়](https://colab.research.google.com/notebooks/intro.ipynb) অনুসরণ করে শুরু করার পরামর্শ দিচ্ছি। Colab আপনাকে কিছু এক্সেলারেসন হার্ডওয়্যার ব্যবহার করতে দেয়, যেমন GPUs বা TPUs যা ছোট ওয়ার্ক লোডের জন্য ফ্রি। + +Colab-এর উপর আপানার হাত চলে আসলে একটি নতুন নোটবুক ওপেন করে সেট-আপ শুরু করুন: + +
+An empty colab notebook +
+ +পরবর্তী ধাপে আমরা এই কোর্সে ব্যবহার হবে এমন লাইব্রেরিগুলি ইনস্টল করা দেখাবো। আমরা ইনস্টলেশনের জন্য পাইথনের প্যাকেজ ম্যানেজার `pip` ব্যবহার করব। নোটবুকগুলিতে, আপনি `!` অক্ষর দিয়ে আগে সিস্টেম কমান্ড চালাতে পারবেন। যেমন ধরুন, নিচের কমান্ডটি দিয়ে 🤗 Transformers লাইব্রেরি ইনস্টল করতে পারবেন: + +``` +!pip install transformers +``` + +প্যাকেজটি আপনার পাইথন রানটাইমের মধ্যে সঠিকভাবে ইনস্টল করা হয়েছে কিনা তা import করে নিশ্চিত হতে পাড়েন। + +``` +import transformers +``` + +
+একটি gif উপরের দুটি কমান্ডের ফলাফল দেখাচ্ছে: installation and import +
+ +এটি 🤗 ট্রান্সফরমারের একটি খুব লাইট ভার্সন ইনস্টল করে। বিশেষ করে, যদিনা নির্দিষ্ট মেশিন লার্নিং ফ্রেমওয়ার্ক (যেমন PyTorch বা TensorFlow) ইনস্টল করা থাকে। যেহেতু আমরা লাইব্রেরির বিভিন্ন ফিচার ব্যবহার করব, তাই আমরা ডেভেলপমেন্ট ভার্সন ইনস্টল করার পরামর্শ দিচ্ছি, যতে ধারানা করার এমন সব ইউজ কেসে কাজ করবে: + +``` +!pip install transformers[sentencepiece] +``` + +ইনস্টল হতে কিছুটা সময় লাগবে, কিন্তু এরপর আপনি বাকি কোর্সের জন্য প্রস্তুত হয়ে যাবেন! + +## একটি পাইথন virtual environment ব্যবহার করা + +আপনি যদি পাইথন virtual environment ব্যবহার করতে পছন্দ করেন, প্রথম ধাপ হল আপনার সিস্টেমে পাইথন ইনস্টল করা। শুরু করার জন্য আমরা [এই নির্দেশিকা](https://realpython.com/installing-python/) অনুসরণ করার পরামর্শ দিচ্ছি। + +একবার আপনি পাইথন ইনস্টল করলে, আপনি আপনার টার্মিনালে পাইথন কমান্ড চালাতে সক্ষম হবেন। পরবর্তী ধাপে যাওয়ার আগে এটি সঠিকভাবে ইনস্টল করা হয়েছে তা নিশ্চিত করতে আপনি নিম্নলিখিত কমান্ডটি চালিয়ে শুরু করতে পারেন: `python --version`। এটি আপনার সিস্টেমে ইনস্টল হওয়া পাইথন সংস্করণটি প্রিন্ট করা উচিত। + +আপনার টার্মিনালে পাইথন কমান্ড চালানোর সময়, যেমন `python --version`, আপানাকে ভাবতে হবে যে এটি "main" পাইথন প্রোগ্রাম যা আপানার কমান্ড টিকে রান করছে। আমরা এই মূল ইনস্টলেশনটিকে যেকোন প্যাকেজ ইনস্টল থেকে মুক্ত রাখার সুপারিশ করি। এ আপনি যখন আলাদা অ্যাপ্লিকেশনে কাজ করবেন তখন তার জন্য আলাদা virtual environment তৈরি করতে এই পাইথন ইনস্টলেশনটিকে ব্যবহার করবেন। এতে করে প্রতিটি অ্যাপ্লিকেশনের নিজস্ব ডিপেন্ডেন্সি এবং প্যাকেজ আলাদা থাকবে এবং অন্যান্য অ্যাপ্লিকেশনের সাথে এর সম্ভাব্য কম্পাটিবিলটি নিয়ে আপানকে সমস্যায় করতে হবে না। + +পাইথনে এটি [*virtual environments*](https://docs.python.org/3/tutorial/venv.html) দিয়ে করা হয়, যেটি স্বয়ংসম্পূর্ণ ডিরেক্টরি ট্রি। যার প্রত্যেকটিতে এপ্লিকেশনের প্রয়োজনীয় সমস্ত প্যাকেজের পাশাপাশি একটি নির্দিষ্ট পাইথন ভার্শনের পাইথন ইনস্টলেশন আছে। এই ধরনের একটি virtual environments বিভিন্ন ভাবে তৈরি করা যেতে পারে। তবে আমরা এর জন্য অফিসিয়াল পাইথন প্যাকেজ ব্যবহার করব, যাকে বলা হয় [`venv`](https://docs.python.org/3/library) /venv.html#module-venv)। + +প্রথমে, আপনি যে ডিরেক্টরিটি আপনার অ্যাপ্লিকেশনটিতে রাখতে চান তা তৈরি করুন — উদাহরণস্বরূপ, আপনি আপনার হোম ডিরেক্টরির বা ফোল্ডার ভেতর *transformers-course* নামে একটি নতুন ডিরেক্টরি তৈরি করতে চাইতে পারেন: + +``` +mkdir ~/transformers-course +cd ~/transformers-course +``` + +এই ডিরেক্টরির ভিতর থেকে, পাইথন `venv` মডিউল ব্যবহার করে একটি virtual environment তৈরি করুন: + +``` +python -m venv .env +``` + +আপনার এখন *.env* নামে একটি ফোল্ডার থাকা উচিত, অন্যথায় খালি ফোল্ডার : + +``` +ls -a +``` + +```out +. .. .env +``` + + +আপনি এখন virtual environment টি `activate` করতে বা `deactivate` নিচের কমান্ড গুলো ব্যবহার করতে পারেন। + +``` +# virtual environment টি activate করার কমান্ড +source .env/bin/activate + +# virtual environment টি deactivate করার কমান্ড +source .env/bin/deactivate +``` + +`which python` কমান্ড চালিয়ে নিশ্চিত করতে পারেন যে virtual environment টি activate হয়েছে কিনা। +যদি এটি virtual environment টি কে পয়েন্ট করে করে, তাহলে আপনি সফলভাবে এটি সক্রিয় করেছেন! + +``` +which python +``` + +```out +/home//transformers-course/.env/bin/python +``` + +### ডিপেন্ডেন্সি ইনস্টল করা + +আগের সেকশনে Google Colab এ যেভাবে প্যাকেজ ইনস্টল করা হয়েছে একই ভাবে এখানেও `pip` প্যাকেজ ম্যানেজার ব্যবহার করে 🤗 Transformer এর development সংস্করণ ইনস্টল করতে পারেন: + +```` +pip install "transformers[sentencepiece]" +```` + +আপনি এখন শুরু করা জন্য সম্পূর্ণ প্রস্তুত! diff --git a/chapters/fr/_toctree.yml b/chapters/fr/_toctree.yml index 4a4973f75..ad9953b2d 100644 --- a/chapters/fr/_toctree.yml +++ b/chapters/fr/_toctree.yml @@ -1,4 +1,24 @@ - title: 0. Configuration sections: - local: chapter0/1 - title: Introduction \ No newline at end of file + title: Introduction + +- title: 5. La bibliothèque 🤗 Datasets + sections: + - local: chapter5/1 + title: Introduction + - local: chapter5/2 + title: Que faire si mon ensemble de données n'est pas sur le Hub ? + - local: chapter5/3 + title: Il est temps de trancher et de découper + - local: chapter5/4 + title: Big Data? 🤗 Des jeux de données à la rescousse ! + - local: chapter5/5 + title: Création de votre propre jeu de données + - local: chapter5/6 + title: Recherche sémantique avec FAISS + - local: chapter5/7 + title: 🤗 Datasets, vérifié ! + - local: chapter5/8 + title: Quiz de fin de chapitre + quiz: 5 \ No newline at end of file diff --git a/chapters/fr/chapter5/1.mdx b/chapters/fr/chapter5/1.mdx new file mode 100644 index 000000000..8e76cea5e --- /dev/null +++ b/chapters/fr/chapter5/1.mdx @@ -0,0 +1,17 @@ +# Introduction + +Dans le [Chapitre 3](/course/chapter3) vous avez eu un premier aperçu de la bibliothèque 🤗 Datasets et vous avez vu qu'il y avait trois étapes principales pour affiner un modèle: + +1. Chargez un jeu de données à partir de Hugging Face Hub. +2. Prétraitez les données avec `Dataset.map()`. +3. Charger et calculer des métriques. + +Mais ce n'est qu'effleurer la surface de ce que 🤗 Datasets peut faire ! Dans ce chapitre, nous allons plonger profondément dans la bibliothèque. En cours de route, nous trouverons des réponses aux questions suivantes: + +* Que faites-vous lorsque votre jeu de données n'est pas sur le Hub ? +* Comment pouvez-vous découper et trancher un ensemble de données ? (Et si vous avez _vraiment_ besoin d'utiliser Pandas ?) +* Que faites-vous lorsque votre ensemble de données est énorme et va faire fondre la RAM de votre ordinateur portable ? +* Qu'est-ce que c'est que le "mappage de la mémoire" et Apache Arrow ? +* Comment pouvez-vous créer votre propre ensemble de données et le pousser vers le Hub ? + +Les techniques que vous apprenez ici vous prépareront aux tâches avancées de tokenisation et de réglage fin du [Chapitre 6](/course/chapter6) et du [Chapitre 7](/course/chapter7) -- alors prenez un café et commençons ! \ No newline at end of file diff --git a/chapters/fr/chapter5/2.mdx b/chapters/fr/chapter5/2.mdx new file mode 100644 index 000000000..896dc8ca2 --- /dev/null +++ b/chapters/fr/chapter5/2.mdx @@ -0,0 +1,167 @@ +# Que faire si mon ensemble de données n'est pas sur le Hub ? + + + +Vous savez comment utiliser le [Hugging Face Hub](https://huggingface.co/datasets) pour télécharger des ensembles de données, mais vous vous retrouverez souvent à travailler avec des données stockées sur votre ordinateur portable ou sur un serveur distant. Dans cette section, nous allons vous montrer comment 🤗 Datasets peut être utilisé pour charger des ensembles de données qui ne sont pas disponibles sur le Hugging Face Hub. + + + +## Travailler avec des ensembles de données locaux et distants + +🤗 Datasets fournit des scripts de chargement pour gérer le chargement des ensembles de données locaux et distants. Il prend en charge plusieurs formats de données courants, tels que : + +| Format de données | Chargement du script | Exemple | +| :----------------: | :------------------: | :-----------------------------------------------------: | +| CSV & TSV | `csv` | `load_dataset("csv", data_files="my_file.csv")` | +| Fichiers texte | `text` | `load_dataset("text", data_files="my_file.txt")` | +| JSON & Lignes JSON | `json` | `load_dataset("json", data_files="my_file.jsonl")` | +| DataFrames marinés | `pandas` | `load_dataset("pandas", data_files="my_dataframe.pkl")` | + +Comme indiqué dans le tableau, pour chaque format de données, nous avons juste besoin de spécifier le type de script de chargement dans la fonction `load_dataset()`, ainsi qu'un argument `data_files` qui spécifie le chemin vers un ou plusieurs fichiers. Commençons par charger un jeu de données à partir de fichiers locaux ; plus tard, nous verrons comment faire la même chose avec des fichiers distants. + +## Charger un jeu de données local + +Pour cet exemple, nous utiliserons l'ensemble de données [SQuAD-it](https://github.com/crux82/squad-it/), qui est un ensemble de données à grande échelle pour répondre aux questions en Italien. + +Les fractionnements de formation et de test sont hébergés sur GitHub, nous pouvons donc les télécharger avec une simple commande `wget` : + +```python +!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-train.json.gz +!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-test.json.gz +``` + +Cela téléchargera deux fichiers compressés appelés *SQuAD_it-train.json.gz* et *SQuAD_it-test.json.gz*, que nous pouvons décompresser avec la commande Linux `gzip` : + +```python +!gzip -dkv SQuAD_it-*.json.gz +``` + +```bash +SQuAD_it-test.json.gz: 87.4% -- replaced with SQuAD_it-test.json +SQuAD_it-train.json.gz: 82.2% -- replaced with SQuAD_it-train.json +``` + +Nous pouvons voir que les fichiers compressés ont été remplacés par _SQuAD_it-train.json_ et _SQuAD_it-text.json_, et que les données sont stockées au format JSON. + + + +✎ Si vous vous demandez pourquoi il y a un caractère `!` dans les commandes shell ci-dessus, c'est parce que nous les exécutons dans un cahier Jupyter. Supprimez simplement le préfixe si vous souhaitez télécharger et décompresser l'ensemble de données dans un terminal. + + + +Pour charger un fichier JSON avec la fonction `load_dataset()`, nous avons juste besoin de savoir si nous avons affaire à du JSON ordinaire (similaire à un dictionnaire imbriqué) ou à des lignes JSON (JSON séparé par des lignes). Comme de nombreux ensembles de données de questions-réponses, SQuAD-it utilise le format imbriqué, avec tout le texte stocké dans un champ "données". Cela signifie que nous pouvons charger le jeu de données en spécifiant l'argument `field` comme suit : + +```py +from datasets import load_dataset + +squad_it_dataset = load_dataset("json", data_files="SQuAD_it-train.json", field="data") +``` + +Par défaut, le chargement de fichiers locaux crée un objet `DatasetDict` avec une division `train`. Nous pouvons le voir en inspectant l'objet `squad_it_dataset` : + +```py +squad_it_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 442 + }) +}) +``` + +Cela nous montre le nombre de lignes et les noms de colonnes associés à l'ensemble d'apprentissage. Nous pouvons afficher l'un des exemples en indexant la division "train" comme suit : + +```py +squad_it_dataset["train"][0] +``` + +```python out +{ + "title": "Terremoto del Sichuan del 2008", + "paragraphs": [ + { + "context": "Il terremoto del Sichuan del 2008 o il terremoto...", + "qas": [ + { + "answers": [{"answer_start": 29, "text": "2008"}], + "id": "56cdca7862d2951400fa6826", + "question": "In quale anno si è verificato il terremoto nel Sichuan?", + }, + ... + ], + }, + ... + ], +} +``` + +Super, nous avons chargé notre premier jeu de données local ! Mais bien que cela ait fonctionné pour l'ensemble d'entraînement, ce que nous voulons vraiment, c'est inclure à la fois les divisions `train` et `test` dans un seul objet `DatasetDict` afin que nous puissions appliquer les fonctions `Dataset.map()` sur les deux divisions à la fois . Pour ce faire, nous pouvons fournir un dictionnaire à l'argument `data_files` qui associe chaque nom de division à un fichier associé à cette division : + +```py +data_files = {"train": "SQuAD_it-train.json", "test": "SQuAD_it-test.json"} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +squad_it_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 442 + }) + test: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 48 + }) +}) +``` + +C'est exactement ce que nous voulions. Désormais, nous pouvons appliquer diverses techniques de prétraitement pour nettoyer les données, tokeniser les avis, etc. + + + +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 group files matching a specified pattern according to the rules used by the Unix shell (for example, you can group all JSON files in a directory into a single division by setting `data_files="*.json"` ). See 🤗 Datasets [documentation](https://huggingface.co/docs/datasets/loading.html#local-and-remote-files) for details. + + + +Les scripts de chargement dans 🤗 Datasets prend en charge la décompression automatique des fichiers d'entrée, nous aurions donc pu ignorer l'utilisation de `gzip` en pointant l'argument `data_files` directement sur les fichiers compressés : + +```py +data_files = {"train": "SQuAD_it-train.json.gz", "test": "SQuAD_it-test.json.gz"} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +``` + +Cela peut être utile si vous ne souhaitez pas décompresser manuellement de nombreux fichiers GZIP. La décompression automatique s'applique également à d'autres formats courants tels que ZIP et TAR, il vous suffit donc de pointer `data_files` vers les fichiers compressés et vous êtes prêt à partir ! + +Maintenant que vous savez comment charger des fichiers locaux sur votre ordinateur portable ou de bureau, examinons le chargement de fichiers distants. + +## Charger un jeu de données distant + +Si vous travaillez en tant que data scientist ou codeur dans une entreprise, il y a de fortes chances que les ensembles de données que vous souhaitez analyser soient stockés sur un serveur distant. Heureusement, charger des fichiers distants est aussi simple que de charger des fichiers locaux ! Au lieu de fournir un chemin vers les fichiers locaux, nous pointons l'argument `data_files` de `load_dataset()` vers une ou plusieurs URL où les fichiers distants sont stockés. Par exemple, pour l'ensemble de données SQuAD-it hébergé sur GitHub, nous pouvons simplement faire pointer `data_files` vers les URL _SQuAD_it-*.json.gz_ comme suit : + +```py +url = "https://github.com/crux82/squad-it/raw/master/" +data_files = { + "train": url + "SQuAD_it-train.json.gz", + "test": url + "SQuAD_it-test.json.gz", +} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +``` + +Cela renvoie le même objet `DatasetDict` obtenu ci-dessus, mais nous évite de télécharger et de décompresser manuellement les fichiers _SQuAD_it-*.json.gz_. Ceci conclut notre incursion dans les différentes façons de charger des ensembles de données qui ne sont pas hébergés sur le Hugging Face Hub. Maintenant que nous avons un ensemble de données avec lequel jouer, mettons-nous la main à la pâte avec diverses techniques de gestion des données ! + + + +✏️ **Essayez-le !** Choisissez un autre ensemble de données hébergé sur GitHub ou 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 ensemble 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). + + + + diff --git a/chapters/fr/chapter5/3.mdx b/chapters/fr/chapter5/3.mdx new file mode 100644 index 000000000..9d6a57959 --- /dev/null +++ b/chapters/fr/chapter5/3.mdx @@ -0,0 +1,744 @@ +# Il est temps de trancher et de découper + + + +La plupart du temps, les données avec lesquelles vous travaillez ne seront pas parfaitement préparées pour les modèles de formation. Dans cette section, nous allons explorer les différentes fonctionnalités fournies par 🤗 Datasets pour nettoyer vos ensembles de données. + + + +## Trancher et découper nos données + +Semblable à Pandas, 🤗 Datasets fournit plusieurs fonctions pour manipuler le contenu des objets `Dataset` et `DatasetDict`. Nous avons déjà rencontré la méthode `Dataset.map()` dans le [Chapitre 3](/course/chapter3), et dans cette section nous allons explorer certaines des autres fonctions à notre disposition. + +Pour cet exemple, nous utiliserons le [Drug Review Dataset](https://archive.ics.uci.edu/ml/datasets/Drug+Review+Dataset+%28Drugs.com%29) qui est hébergé sur [UC Irvine Machine Learning Repository] (https://archive.ics.uci.edu/ml/index.php), qui contient des avis de patients sur divers médicaments, ainsi que la condition traitée et une note de 10 étoiles sur la satisfaction du patient. + +Nous devons d'abord télécharger et extraire les données, ce qui peut être fait avec les commandes `wget` et `unzip` : + +```py +!wget "https://archive.ics.uci.edu/ml/machine-learning-databases/00462/drugsCom_raw.zip" +!unzip drugsCom_raw.zip +``` + +Étant donné que TSV n'est qu'une variante de CSV qui utilise des tabulations au lieu de virgules comme séparateurs, nous pouvons charger ces fichiers en utilisant le script de chargement `csv` et en spécifiant l'argument `delimiter` dans la fonction `load_dataset()` comme suit : + +```py +from datasets import load_dataset + +data_files = {"train": "drugsComTrain_raw.tsv", "test": "drugsComTest_raw.tsv"} +# \t is the tab character in Python +drug_dataset = load_dataset("csv", data_files=data_files, delimiter="\t") +``` + +Une bonne pratique lors de toute sorte d'analyse de données consiste à prélever un petit échantillon aléatoire pour avoir une idée rapide du type de données avec lesquelles vous travaillez. Dans 🤗 Datasets, nous pouvons créer un échantillon aléatoire en enchaînant les fonctions `Dataset.shuffle()` et `Dataset.select()` : + +```py +drug_sample = drug_dataset["train"].shuffle(seed=42).select(range(1000)) +# Peek at the first few examples +drug_sample[:3] +``` + +```python out +{'Unnamed: 0': [87571, 178045, 80482], + 'drugName': ['Naproxen', 'Duloxetine', 'Mobic'], + 'condition': ['Gout, Acute', 'ibromyalgia', 'Inflammatory Conditions'], + 'review': ['"like the previous person mention, I'm a strong believer of aleve, it works faster for my gout than the prescription meds I take. No more going to the doctor for refills.....Aleve works!"', + '"I have taken Cymbalta for about a year and a half for fibromyalgia pain. It is great\r\nas a pain reducer and an anti-depressant, however, the side effects outweighed \r\nany benefit I got from it. I had trouble with restlessness, being tired constantly,\r\ndizziness, dry mouth, numbness and tingling in my feet, and horrible sweating. I am\r\nbeing weaned off of it now. Went from 60 mg to 30mg and now to 15 mg. I will be\r\noff completely in about a week. The fibro pain is coming back, but I would rather deal with it than the side effects."', + '"I have been taking Mobic for over a year with no side effects other than an elevated blood pressure. I had severe knee and ankle pain which completely went away after taking Mobic. I attempted to stop the medication however pain returned after a few days."'], + 'rating': [9.0, 3.0, 10.0], + 'date': ['September 2, 2015', 'November 7, 2011', 'June 5, 2013'], + 'usefulCount': [36, 13, 128]} +``` + +Notez que nous avons corrigé la graine dans `Dataset.shuffle()` à des fins de reproductibilité. `Dataset.select()` attend un itérable d'indices, nous avons donc passé `range(1000)` pour récupérer les 1 000 premiers exemples de l'ensemble de données mélangé. À partir de cet échantillon, nous pouvons déjà voir quelques bizarreries dans notre ensemble de données : + +* La colonne "Sans nom : 0" ressemble étrangement à un identifiant anonyme pour chaque patient. +* La colonne "condition" comprend un mélange d'étiquettes en majuscules et en minuscules. +* Les avis sont de longueur variable et contiennent un mélange de séparateurs de lignes Python (`\r\n`) ainsi que des codes de caractères HTML comme `&\#039;`. + +Voyons comment nous pouvons utiliser 🤗 Datasets pour traiter chacun de ces problèmes. Pour tester l'hypothèse de l'ID patient pour la colonne `Unnamed : 0`, nous pouvons utiliser la fonction `Dataset.unique()` pour vérifier que le nombre d'ID correspond au nombre de lignes dans chaque division : + +```py +for split in drug_dataset.keys(): + assert len(drug_dataset[split]) == len(drug_dataset[split].unique("Unnamed: 0")) +``` + +Cela semble confirmer notre hypothèse, alors nettoyons un peu l'ensemble de données en renommant la colonne `Unnamed: 0` en quelque chose d'un peu plus interprétable. Nous pouvons utiliser la fonction `DatasetDict.rename_column()` pour renommer la colonne sur les deux divisions en une seule fois : + +```py +drug_dataset = drug_dataset.rename_column( + original_column_name="Unnamed: 0", new_column_name="patient_id" +) +drug_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'], + num_rows: 161297 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'], + num_rows: 53766 + }) +}) +``` + + + +✏️ **Essayez-le !** Utilisez la fonction "Dataset.unique()" pour trouver le nombre de médicaments et de conditions uniques dans les ensembles d'entraînement et de test. + + + +Ensuite, normalisons toutes les étiquettes `condition` en utilisant `Dataset.map()`. Comme nous l'avons fait avec la tokenisation dans le [chapitre 3](/course/chapter3), nous pouvons définir une fonction simple qui peut être appliquée sur toutes les lignes de chaque division dans `drug_dataset` : + +```py +def lowercase_condition(example): + return {"condition": example["condition"].lower()} + + +drug_dataset.map(lowercase_condition) +``` + +```python out +AttributeError: 'NoneType' object has no attribute 'lower' +``` + +Oh non, nous avons rencontré un problème avec notre fonction de carte ! À partir de l'erreur, nous pouvons déduire que certaines des entrées de la colonne "condition" sont "Aucune", qui ne peuvent pas être mises en minuscules car ce ne sont pas des chaînes. Supprimons ces lignes en utilisant `Dataset.filter()`, qui fonctionne de manière similaire à `Dataset.map()` et attend une fonction qui reçoit un seul exemple de l'ensemble de données. Au lieu d'écrire une fonction explicite comme : + +```py +def filter_nones(x): + return x["condition"] is not None +``` + +puis en exécutant `drug_dataset.filter(filter_nones)`, nous pouvons le faire en une seule ligne en utilisant une _lambda function_. En Python, les fonctions lambda sont de petites fonctions que vous pouvez définir sans les nommer explicitement. Ils prennent la forme générale : + +``` +lambda : +``` + +où `lambda` est l'un des [mots clés] spéciaux de Python (https://docs.python.org/3/reference/lexical_analysis.html#keywords), `` est une liste/ensemble de valeurs séparées par des virgules qui définissent les entrées de la fonction et `` représente les opérations que vous souhaitez exécuter. Par exemple, nous pouvons définir une simple fonction lambda qui met au carré un nombre comme suit : + +``` +lambda x : x * x +``` + +Pour appliquer cette fonction à une entrée, nous devons l'envelopper ainsi que l'entrée entre parenthèses : + +```py +(lambda x: x * x)(3) +``` + +```python out +9 +``` + +De même, nous pouvons définir des fonctions lambda avec plusieurs arguments en les séparant par des virgules. Par exemple, nous pouvons calculer l'aire d'un triangle comme suit : + +```py +(lambda base, height: 0.5 * base * height)(4, 8) +``` + +```python out +16.0 +``` + +Les fonctions Lambda sont pratiques lorsque vous souhaitez définir de petites fonctions à usage unique (pour plus d'informations à leur sujet, nous vous recommandons de lire l'excellent [tutoriel Real Python](https://realpython.com/python-lambda/) d'André Burgaud) . Dans le contexte 🤗 Datasets, nous pouvons utiliser des fonctions lambda pour définir des opérations simples de mappage et de filtrage, alors utilisons cette astuce pour éliminer les entrées "None" dans notre jeu de données : + +```py +drug_dataset = drug_dataset.filter(lambda x: x["condition"] is not None) +``` + +Avec les entrées "None" supprimées, nous pouvons normaliser notre colonne "condition" : + +```py +drug_dataset = drug_dataset.map(lowercase_condition) +# Check that lowercasing worked +drug_dataset["train"]["condition"][:3] +``` + +```python out +['left ventricular dysfunction', 'adhd', 'birth control'] +``` + +Ça marche ! Maintenant que nous avons nettoyé les étiquettes, examinons le nettoyage des avis eux-mêmes. + +## Création de nouvelles colonnes + +Chaque fois que vous avez affaire à des avis de clients, une bonne pratique consiste à vérifier le nombre de mots dans chaque avis. Une critique peut être un simple mot comme "Génial !" ou un essai complet avec des milliers de mots, et selon le cas d'utilisation, vous devrez gérer ces extrêmes différemment. Pour calculer le nombre de mots dans chaque révision, nous utiliserons une heuristique approximative basée sur la division de chaque texte par des espaces. + +Définissons une fonction simple qui compte le nombre de mots dans chaque avis : + +```py +def compute_review_length(example): + return {"review_length": len(example["review"].split())} +``` + +Contrairement à notre fonction `lowercase_condition()`, `compute_review_length()` renvoie un dictionnaire dont la clé ne correspond pas à l'un des noms de colonne de l'ensemble de données. Dans ce cas, lorsque `compute_review_length()` est passé à `Dataset.map()`, il sera appliqué à toutes les lignes du jeu de données pour créer une nouvelle colonne `review_length` : + +```py +drug_dataset = drug_dataset.map(compute_review_length) +# Inspect the first training example +drug_dataset["train"][0] +``` + +```python out +{'patient_id': 206461, + 'drugName': 'Valsartan', + 'condition': 'left ventricular dysfunction', + 'review': '"It has no side effect, I take it in combination of Bystolic 5 Mg and Fish Oil"', + 'rating': 9.0, + 'date': 'May 20, 2012', + 'usefulCount': 27, + 'review_length': 17} +``` + +Comme prévu, nous pouvons voir qu'une colonne "review_length" a été ajoutée à notre ensemble d'entraînement. Nous pouvons trier cette nouvelle colonne avec `Dataset.sort()` pour voir à quoi ressemblent les valeurs extrêmes : + +```py +drug_dataset["train"].sort("review_length")[:3] +``` + +```python out +{'patient_id': [103488, 23627, 20558], + 'drugName': ['Loestrin 21 1 / 20', 'Chlorzoxazone', 'Nucynta'], + 'condition': ['birth control', 'muscle spasm', 'pain'], + 'review': ['"Excellent."', '"useless"', '"ok"'], + 'rating': [10.0, 1.0, 6.0], + 'date': ['November 4, 2008', 'March 24, 2017', 'August 20, 2016'], + 'usefulCount': [5, 2, 10], + 'review_length': [1, 1, 1]} +``` + +Comme nous le soupçonnions, certaines critiques ne contiennent qu'un seul mot, ce qui, bien que cela puisse convenir à l'analyse des sentiments, ne serait pas informatif si nous voulons prédire la condition. + + + +🙋 Une autre façon d'ajouter de nouvelles colonnes à un ensemble de données consiste à utiliser la fonction `Dataset.add_column()`. Cela vous permet de fournir la colonne sous forme de liste Python ou de tableau NumPy et peut être utile dans les situations où `Dataset.map()` n'est pas bien adapté à votre analyse. + + + +Utilisons la fonction `Dataset.filter()` pour supprimer les avis contenant moins de 30 mots. De la même manière que nous l'avons fait avec la colonne "condition", nous pouvons filtrer les avis très courts en exigeant que les avis aient une longueur supérieure à ce seuil : + +```py +drug_dataset = drug_dataset.filter(lambda x: x["review_length"] > 30) +print(drug_dataset.num_rows) +``` + +```python out +{'train': 138514, 'test': 46108} +``` + +Comme vous pouvez le constater, cela a supprimé environ 15 % des avis de nos ensembles d'entraînement et de test d'origine. + + + +✏️ **Essayez-le !** 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. + + + +La dernière chose à laquelle nous devons faire face est la présence de codes de caractères HTML dans nos avis. Nous pouvons utiliser le module `html` de Python pour supprimer ces caractères, comme ceci : + +```py +import html + +text = "I'm a transformer called BERT" +html.unescape(text) +``` + +```python out +"I'm a transformer called BERT" +``` + +Nous utiliserons `Dataset.map()` pour démasquer tous les caractères HTML de notre corpus : + +```python +drug_dataset = drug_dataset.map(lambda x: {"review": html.unescape(x["review"])}) +``` + +Comme vous pouvez le voir, la méthode `Dataset.map()` est très utile pour le traitement des données -- et nous n'avons même pas effleuré la surface de tout ce qu'elle peut faire ! + +## Les superpouvoirs de la méthode `map()` + +La méthode `Dataset.map()` prend un argument `batched` qui, s'il est défini sur `True`, l'amène à envoyer un lot d'exemples à la fonction map en une seule fois (la taille du lot est configurable mais par défaut à 1 000). Par exemple, la fonction de carte précédente qui dégageait tout le code HTML prenait un peu de temps à s'exécuter (vous pouvez lire le temps pris dans les barres de progression). On peut accélérer cela en traitant plusieurs éléments en même temps à l'aide d'une liste en compréhension. + +Lorsque vous spécifiez `batched=True`, la fonction reçoit un dictionnaire avec les champs de l'ensemble de données, mais chaque valeur est maintenant une _liste de valeurs_, et non plus une seule valeur. La valeur de retour de `Dataset.map()` devrait être la même : un dictionnaire avec les champs que nous voulons mettre à jour ou ajouter à notre ensemble de données, et une liste de valeurs. Par exemple, voici une autre façon de supprimer tous les caractères HTML, mais en utilisant `batched=True` : + +```python +new_drug_dataset = drug_dataset.map( + lambda x: {"review": [html.unescape(o) for o in x["review"]]}, batched=True +) +``` + +Si vous exécutez ce code dans un notebook, vous verrez que cette commande s'exécute beaucoup plus rapidement que la précédente. Et ce n'est pas parce que nos critiques ont déjà été sans échappement HTML -- si vous ré-exécutez l'instruction de la section précédente (sans `batched=True`), cela prendra le même temps qu'avant. En effet, les compréhensions de liste sont généralement plus rapides que l'exécution du même code dans une boucle "for", et nous gagnons également en performances en accédant à de nombreux éléments en même temps au lieu d'un par un. + +L'utilisation de `Dataset.map()` avec `batched=True` sera essentielle pour débloquer la vitesse des tokenizers "rapides" que nous rencontrerons dans [Chapitre 6](/course/chapter6), qui peuvent rapidement tokeniser de grandes listes de textes. Par exemple, pour tokeniser toutes les revues de médicaments avec un tokenizer rapide, nous pourrions utiliser une fonction comme celle-ci : + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") + + +def tokenize_function(examples): + return tokenizer(examples["review"], truncation=True) +``` + +Comme vous l'avez vu dans le [Chapitre 3](/course/chapter3), nous pouvons passer un ou plusieurs exemples au tokenizer, nous pouvons donc utiliser cette fonction avec ou sans `batched=True`. Profitons-en pour comparer les performances des différentes options. Dans un cahier, vous pouvez chronométrer une instruction d'une ligne en ajoutant `%time` avant la ligne de code que vous souhaitez mesurer : + +```python no-format +%time tokenized_dataset = drug_dataset.map(tokenize_function, batched=True) +``` + +Vous pouvez également chronométrer une cellule entière en mettant `%%time` au début de la cellule. Sur le matériel sur lequel nous avons exécuté cela, il affichait 10,8 s pour cette instruction (c'est le nombre écrit après "Wall time"). + + + +✏️ **Essayez-le !** Exécutez la même instruction avec et sans `batched=True`, puis essayez-le avec un tokenizer lent (ajoutez `use_fast=False` dans la méthode `AutoTokenizer.from_pretrained()`) afin que vous puissiez voir quels numéros vous obtenez sur votre matériel. + + + +Voici les résultats que nous avons obtenus avec et sans batching, avec un tokenizer rapide et un lent : + +Options | Tokenizer rapide | Tokenizer lent +:--------------:|:----------------:|:-----------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s + +Cela signifie que l'utilisation d'un tokenizer rapide avec l'option `batched=True` est 30 fois plus rapide que son homologue lent sans traitement par lot -- c'est vraiment incroyable ! C'est la raison principale pour laquelle les tokenizers rapides sont la valeur par défaut lors de l'utilisation de `AutoTokenizer` (et pourquoi ils sont appelés "rapides"). Ils sont capables d'atteindre une telle accélération car dans les coulisses, le code de tokenisation est exécuté dans Rust, qui est un langage qui facilite la parallélisation de l'exécution du code. + +La parallélisation est également la raison de l'accélération de près de 6 fois obtenue par le fast tokenizer avec le traitement par lots : vous ne pouvez pas paralléliser une seule opération de tokenisation, mais lorsque vous souhaitez tokeniser de nombreux textes en même temps, vous pouvez simplement répartir l'exécution sur plusieurs processus. chacun responsable de ses propres textes. + +`Dataset.map()` possède également ses propres capacités de parallélisation. Comme ils ne sont pas soutenus par Rust, ils ne laisseront pas un tokenizer lent rattraper un rapide, mais ils peuvent toujours être utiles (surtout si vous utilisez un tokenizer qui n'a pas de version rapide). Pour activer le multitraitement, utilisez l'argument `num_proc` et spécifiez le nombre de processus à utiliser dans votre appel à `Dataset.map()` : + +```py +slow_tokenizer = AutoTokenizer.from_pretrained("bert-base-cased", use_fast=False) + + +def slow_tokenize_function(examples): + return slow_tokenizer(examples["review"], truncation=True) + + +tokenized_dataset = drug_dataset.map(slow_tokenize_function, batched=True, num_proc=8) +``` + +Vous pouvez expérimenter un peu le timing pour déterminer le nombre optimal de processus à utiliser ; dans notre cas 8 semblait produire le meilleur gain de vitesse. Voici les chiffres que nous avons obtenus avec et sans multitraitement : + +Options | Tokenizer rapide | Tokenizer lent +:----------------------------:|:----------------:|:---------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s +`batched=True`, `num_proc=8` | 6.52s | 41.3s +`batched=False`, `num_proc=8` | 9.49s | 45.2s + +Ce sont des résultats beaucoup plus raisonnables pour le tokenizer lent, mais les performances du tokenizer rapide ont également été considérablement améliorées. Notez, cependant, que ce ne sera pas toujours le cas - pour les valeurs de `num_proc` autres que 8, nos tests ont montré qu'il était plus rapide d'utiliser `batched=True` sans cette option. En général, nous ne recommandons pas d'utiliser le multitraitement Python pour les tokenizers rapides avec `batched=True`. + + + +Utiliser `num_proc` pour accélérer votre traitement est généralement une bonne idée, tant que la fonction que vous utilisez n'effectue pas déjà une sorte de multitraitement. + + + +Toutes ces fonctionnalités condensées en une seule méthode sont déjà assez étonnantes, mais il y a plus ! Avec `Dataset.map()` et `batched=True` vous pouvez modifier le nombre d'éléments dans votre jeu de données. Ceci est très utile dans de nombreuses situations où vous souhaitez créer plusieurs fonctionnalités d'entraînement à partir d'un exemple, et nous devrons le faire dans le cadre du prétraitement de plusieurs des tâches NLP que nous entreprendrons dans [Chapitre 7](/course/ Chapitre 7). + + + +💡 En machine learning, un _example_ est généralement défini comme l'ensemble de _features_ que nous alimentons au modèle. Dans certains contextes, ces caractéristiques seront l'ensemble des colonnes d'un `Dataset`, mais dans d'autres (comme ici et pour la réponse aux questions), plusieurs caractéristiques peuvent être extraites d'un seul exemple et appartenir à une seule colonne. + + + +Voyons comment cela fonctionne ! Ici, nous allons tokeniser nos exemples et les tronquer à une longueur maximale de 128, mais nous demanderons au tokenizer de renvoyer *tous* les morceaux des textes au lieu du premier. Cela peut être fait avec `return_overflowing_tokens=True` : + +```py +def tokenize_and_split(examples): + return tokenizer( + examples["review"], + truncation=True, + max_length=128, + return_overflowing_tokens=True, + ) +``` + +Testons cela sur un exemple avant d'utiliser `Dataset.map()` sur l'ensemble de données : + +```py +result = tokenize_and_split(drug_dataset["train"][0]) +[len(inp) for inp in result["input_ids"]] +``` + +```python out +[128, 49] +``` + +Ainsi, notre premier exemple dans l'ensemble de formation est devenu deux fonctionnalités car il a été segmenté à plus que le nombre maximum de jetons que nous avons spécifié : le premier de longueur 128 et le second de longueur 49. Faisons maintenant cela pour tous les éléments du base de données! + +```py +tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) +``` + +```python out +ArrowInvalid: Column 1 named condition expected length 1463 but got length 1000 +``` + +Oh non! Cela n'a pas fonctionné ! Pourquoi pas? L'examen du message d'erreur nous donnera 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] `Dataset.map()`(https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map), vous vous souviendrez 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 fonctionnalités, entraînant une erreur de forme. + +Le problème est que nous essayons de mélanger deux ensembles 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 le premier avec l'argument `remove_columns` : + +```py +tokenized_dataset = drug_dataset.map( + tokenize_and_split, batched=True, remove_columns=drug_dataset["train"].column_names +) +``` + +Maintenant, cela fonctionne sans erreur. Nous pouvons vérifier que notre nouveau jeu de données contient beaucoup plus d'éléments que le jeu de données d'origine en comparant les longueurs : + +```py +len(tokenized_dataset["train"]), len(drug_dataset["train"]) +``` + +```python out +(206772, 138514) +``` + +Nous avons mentionné que nous pouvions également résoudre le problème de longueur non concordante en donnant aux anciennes colonnes la même taille que les nouvelles. Pour ce faire, nous aurons besoin du champ `overflow_to_sample_mapping` que le tokenizer renvoie lorsque nous définissons `return_overflowing_tokens=True`. Il nous donne une correspondance entre un nouvel index de fonctionnalité et l'index de l'échantillon dont il est issu. Grâce à cela, nous pouvons associer chaque clé présente dans notre jeu de données d'origine à une liste de valeurs de la bonne taille en répétant les valeurs de chaque exemple autant de fois qu'il génère de nouvelles fonctionnalités : + +```py +def tokenize_and_split(examples): + result = tokenizer( + examples["review"], + truncation=True, + max_length=128, + return_overflowing_tokens=True, + ) + # Extract mapping between new and old indices + sample_map = result.pop("overflow_to_sample_mapping") + for key, values in examples.items(): + result[key] = [values[i] for i in sample_map] + return result +``` + +Nous pouvons voir que cela fonctionne avec `Dataset.map()` sans que nous ayons besoin de supprimer les anciennes colonnes : + +```py +tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) +tokenized_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'], + num_rows: 206772 + }) + test: Dataset({ + features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'], + num_rows: 68876 + }) +}) +``` + +Nous obtenons le même nombre de fonctionnalités d'entraînement qu'auparavant, mais ici nous avons conservé tous les anciens champs. Si vous en avez besoin pour un post-traitement après l'application de votre modèle, vous pouvez utiliser cette approche. + +Vous avez maintenant vu comment 🤗 Datasets peut être utilisé pour prétraiter un ensemble de données de différentes manières. Bien que les fonctions de traitement de 🤗 Datasets couvrent la plupart de vos besoins de formation de modèles, +il peut arriver que vous deviez passer à Pandas pour accéder à des fonctionnalités plus puissantes, telles que `DataFrame.groupby()` ou des API de haut niveau pour la visualisation. Heureusement, 🤗 Datasets est conçu pour être interopérable avec des bibliothèques telles que Pandas, NumPy, PyTorch, TensorFlow et JAX. Voyons comment cela fonctionne. + +## De `Dataset`s à `DataFrame`s et vice versa + + + +Pour permettre la conversion entre diverses bibliothèques tierces, 🤗 Datasets fournit une fonction `Dataset.set_format()`. Cette fonction ne modifie que le _format de sortie_ de l'ensemble de données, vous pouvez donc facilement passer à un autre format sans affecter le _format de données_ sous-jacent, qui est Apache Arrow. Le formatage se fait sur place. Pour démontrer, convertissons notre jeu de données en Pandas : + +```py +drug_dataset.set_format("pandas") +``` + +Maintenant, lorsque nous accédons aux éléments du jeu de données, nous obtenons un `pandas.DataFrame` au lieu d'un dictionnaire : + +```py +drug_dataset["train"][:3] +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
patient_iddrugNameconditionreviewratingdateusefulCountreview_length
095260Guanfacineadhd"My son is halfway through his fourth week of Intuniv..."8.0April 27, 2010192141
192703Lybrelbirth control"I used to take another oral contraceptive, which had 21 pill cycle, and was very happy- very light periods, max 5 days, no other side effects..."5.0December 14, 200917134
2138000Ortho Evrabirth control"This is my first time using any form of birth control..."8.0November 3, 20151089
+ +Créons un `pandas.DataFrame` pour l'ensemble d'entraînement en sélectionnant tous les éléments de `drug_dataset["train"]` : + +```py +train_df = drug_dataset["train"][:] +``` + + + +🚨 Sous le capot, `Dataset.set_format()` change le format de retour pour la méthode dunder `__getitem__()` de l'ensemble de données. Cela signifie que lorsque nous voulons créer un nouvel objet comme `train_df` à partir d'un `Dataset` au format `"pandas"`, nous devons découper tout l'ensemble de données pour obtenir un `pandas.DataFrame`. Vous pouvez vérifier par vous-même que le type de `drug_dataset["train"]` est `Dataset`, quel que soit le format de sortie. + + + + +De là, nous pouvons utiliser toutes les fonctionnalités Pandas que nous voulons. Par exemple, nous pouvons faire un chaînage sophistiqué pour calculer la distribution de classe parmi les entrées `condition` : + +```py +frequencies = ( + train_df["condition"] + .value_counts() + .to_frame() + .reset_index() + .rename(columns={"index": "condition", "condition": "frequency"}) +) +frequencies.head() +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
conditionfrequency
0birth control27655
1depression8023
2acne5209
3anxiety4991
4pain4744
+ + +Et une fois que nous avons terminé notre analyse Pandas, nous pouvons toujours créer un nouvel objet `Dataset` en utilisant la fonction `Dataset.from_pandas()` comme suit : + + +```py +from datasets import Dataset + +freq_dataset = Dataset.from_pandas(frequencies) +freq_dataset +``` + +```python out +Dataset({ + features: ['condition', 'frequency'], + num_rows: 819 +}) +``` + + + +✏️ **Essayez-le !** Calculez la note moyenne par médicament et stockez le résultat dans un nouvel ensemble de données. + + + +Ceci conclut notre visite des différentes techniques de prétraitement disponibles dans 🤗 Datasets. Pour compléter la section, créons un ensemble de validation pour préparer l'ensemble de données pour la formation d'un classificateur. Avant cela, nous allons réinitialiser le format de sortie de `drug_dataset` de `"pandas"` à `"arrow"` : + +```python +drug_dataset.reset_format() +``` + +## Création d'un ensemble de validation + +Bien que nous ayons un jeu de test que nous pourrions utiliser pour l'évaluation, il est recommandé de ne pas toucher au jeu de test et de créer un jeu de validation séparé pendant le développement. Une fois que vous êtes satisfait des performances de vos modèles sur l'ensemble de validation, vous pouvez effectuer une dernière vérification d'intégrité sur l'ensemble de test. Ce processus permet d'atténuer le risque de dépassement de l'ensemble de test et de déploiement d'un modèle qui échoue sur des données du monde réel. + +🤗 Datasets fournit une fonction `Dataset.train_test_split()` basée sur la célèbre fonctionnalité de `scikit-learn`. Utilisons-le pour diviser notre ensemble d'entraînement en divisions "train" et "validation" (nous définissons l'argument "seed" pour la reproductibilité) : + +```py +drug_dataset_clean = drug_dataset["train"].train_test_split(train_size=0.8, seed=42) +# Rename the default "test" split to "validation" +drug_dataset_clean["validation"] = drug_dataset_clean.pop("test") +# Add the "test" set to our `DatasetDict` +drug_dataset_clean["test"] = drug_dataset["test"] +drug_dataset_clean +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 110811 + }) + validation: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 27703 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 46108 + }) +}) +``` + +Génial, nous avons maintenant préparé un jeu de données prêt pour l'entraînement de certains modèles ! Dans la [section 5](/course/chapter5/5), nous vous montrerons comment télécharger des ensembles de données sur le Hugging Face Hub, mais pour l'instant, terminons notre analyse en examinant quelques façons d'enregistrer des ensembles de données sur votre ordinateur local. + +## Enregistrer un jeu de données + + + +Bien que 🤗 Datasets mette en cache chaque jeu de données téléchargé et les opérations qui y sont effectuées, il y a des moments où vous voudrez enregistrer un jeu de données sur le disque (par exemple, au cas où le cache serait supprimé). Comme indiqué dans le tableau ci-dessous, 🤗 Datasets fournit trois fonctions principales pour enregistrer votre jeu de données dans différents formats : + +| Format de données | Fonction | +| :---------------: | :----------------------: | +| Flèche | `Dataset.save_to_disk()` | +| CSV | `Dataset.to_csv()` | +| JSON | `Dataset.to_json()` | + +Par exemple, enregistrons notre jeu de données nettoyé au format Arrow : + +```py +drug_dataset_clean.save_to_disk("drug-reviews") +``` + +Cela créera un répertoire avec la structure suivante : + +``` +drug-reviews/ +├── dataset_dict.json +├── test +│ ├── dataset.arrow +│ ├── dataset_info.json +│ └── state.json +├── train +│ ├── dataset.arrow +│ ├── dataset_info.json +│ ├── indices.arrow +│ └── state.json +└── validation + ├── dataset.arrow + ├── dataset_info.json + ├── indices.arrow + └── state.json +``` + +où nous pouvons voir que chaque division est associée à sa propre table * dataset.arrow * et à certaines métadonnées dans * dataset_info.json * et * state.json *. Vous pouvez considérer le format Arrow comme un tableau sophistiqué de colonnes et de lignes optimisé pour la création d'applications hautes performances qui traitent et transportent de grands ensembles de données. + +Une fois le jeu de données enregistré, nous pouvons le charger en utilisant la fonction `load_from_disk()` comme suit : + +```py +from datasets import load_from_disk + +drug_dataset_reloaded = load_from_disk("drug-reviews") +drug_dataset_reloaded +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 110811 + }) + validation: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 27703 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 46108 + }) +}) +``` + +Pour les formats CSV et JSON, nous devons stocker chaque fractionnement dans un fichier séparé. Pour ce faire, vous pouvez parcourir les clés et les valeurs de l'objet "DatasetDict" : + +```py +for split, dataset in drug_dataset_clean.items(): + dataset.to_json(f"drug-reviews-{split}.jsonl") +``` + +Cela enregistre chaque fractionnement au [format de lignes JSON] (https://jsonlines.org), où chaque ligne de l'ensemble de données est stockée sous la forme d'une seule ligne de JSON. Voici à quoi ressemble le premier exemple : + +```py +!head -n 1 drug-reviews-train.jsonl +``` + +```python out +{"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} +``` + +Nous pouvons ensuite utiliser les techniques de [section 2](/course/chapter5/2) pour charger les fichiers JSON comme suit : + +```py +data_files = { + "train": "drug-reviews-train.jsonl", + "validation": "drug-reviews-validation.jsonl", + "test": "drug-reviews-test.jsonl", +} +drug_dataset_reloaded = load_dataset("json", data_files=data_files) +``` + +Et c'est tout pour notre excursion dans le data wrangling avec 🤗 Datasets ! Maintenant que nous disposons d'un ensemble de données nettoyé pour entraîner un modèle, voici quelques idées que vous pouvez essayer : + +1. Utilisez les techniques du [Chapitre 3](/course/chapter3) pour former un classificateur capable de prédire l'état du patient en fonction de l'examen du médicament. +2. Utilisez le pipeline `summarization` du [Chapitre 1](/course/chapter1) pour générer des résumés des révisions. + +Ensuite, nous verrons comment 🤗 Datasets peut vous permettre de travailler avec d'énormes ensembles de données sans faire exploser votre ordinateur portable ! diff --git a/chapters/fr/chapter5/4.mdx b/chapters/fr/chapter5/4.mdx new file mode 100644 index 000000000..63ed0be44 --- /dev/null +++ b/chapters/fr/chapter5/4.mdx @@ -0,0 +1,287 @@ +# Big Data? 🤗 Datasets à la rescousse ! + + + + +De nos jours, il n'est pas rare de travailler avec des ensembles de données de plusieurs gigaoctets, surtout si vous envisagez de pré-entraîner un transformateur comme BERT ou GPT-2 à partir de zéro. Dans ces cas, même _charger_ les données peut être un défi. Par exemple, le corpus WebText utilisé pour pré-entraîner GPT-2 se compose de plus de 8 millions de documents et de 40 Go de texte - le charger dans la RAM de votre ordinateur portable est susceptible de lui donner une crise cardiaque ! + +Heureusement, 🤗 Datasets a été conçu pour surmonter ces limitations. Il vous libère des problèmes de gestion de la mémoire en traitant les ensembles de données comme des fichiers _mappés en mémoire_, et des limites du disque dur en _streaming_ les entrées dans un corpus. + + + +Dans cette section, nous allons explorer ces fonctionnalités de 🤗 Datasets avec un énorme corpus de 825 Go connu sous le nom de [the Pile](https://pile.eleuther.ai). Commençons! + +## Qu'est-ce que The Pile ? + +The Pile est un corpus de texte en anglais créé par [EleutherAI](https://www.eleuther.ai) pour entraîner des modèles de langage à grande échelle. Il comprend une gamme variée d'ensembles de données, couvrant des articles scientifiques, des référentiels de code GitHub et du texte Web filtré. Le corpus de formation est disponible en [morceaux de 14 Go](https://mystic.the-eye.eu/public/AI/pile/), et vous pouvez également télécharger plusieurs des [composants individuels](https://mystic .the-eye.eu/public/AI/pile_preliminary_components/). Commençons par jeter un coup d'œil à l'ensemble de données PubMed Abstracts, qui est un corpus de résumés de 15 millions de publications biomédicales sur [PubMed](https://pubmed.ncbi.nlm.nih.gov/). L'ensemble de données est au [format JSON Lines](https://jsonlines.org) et est compressé à l'aide de la bibliothèque `zstandard`, nous devons donc d'abord l'installer : + +```py +!pip install zstandard +``` + +Ensuite, nous pouvons charger le jeu de données en utilisant la méthode pour les fichiers distants que nous avons apprise dans [section 2](/course/chapter5/2) : + +```py +from datasets import load_dataset + +# Cela prend quelques minutes à exécuter, alors allez prendre un thé ou un café en attendant :) +data_files = "https://mystic.the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst" +pubmed_dataset = load_dataset("json", data_files=data_files, split="train") +pubmed_dataset +``` + +```python out +Dataset({ + features: ['meta', 'text'], + num_rows: 15518009 +}) +``` + +Nous pouvons voir qu'il y a 15 518 009 lignes et 2 colonnes dans notre ensemble de données -- c'est beaucoup ! + + + +✎ Par défaut, 🤗 Datasets décompressera 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. + + + +Inspectons le contenu du premier exemple : + +```py +pubmed_dataset[0] +``` + +```python out +{'meta': {'pmid': 11409574, 'language': 'eng'}, + '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 ...'} +``` + +OK, ça ressemble au résumé d'un article médical. Voyons maintenant combien de RAM nous avons utilisé pour charger le jeu de données ! + +## La magie de la cartographie mémoire + +Un moyen simple de mesurer l'utilisation de la mémoire dans Python consiste à utiliser la bibliothèque [`psutil`](https://psutil.readthedocs.io/en/latest/), qui peut être installée avec `pip` comme suit : + +```python +!pip install psutil +``` + +Il fournit une classe `Process` qui nous permet de vérifier l'utilisation de la mémoire du processus en cours comme suit : + +```py +import psutil + +# Process.memory_info is expressed in bytes, so convert to megabytes +print(f"RAM used: {psutil.Process().memory_info().rss / (1024 * 1024):.2f} MB") +``` + +```python out +RAM used: 5678.33 MB +``` + +Ici, l'attribut `rss` fait référence à la _taille de l'ensemble résident_, qui est la fraction de mémoire qu'un processus occupe dans la RAM. Cette mesure inclut également la mémoire utilisée par l'interpréteur Python et les bibliothèques que nous avons chargées, de sorte que la quantité réelle de mémoire utilisée pour charger l'ensemble de données est un peu plus petite. À titre de comparaison, voyons la taille de l'ensemble de données sur le disque, en utilisant l'attribut `dataset_size`. Comme le résultat est exprimé en octets comme précédemment, nous devons le convertir manuellement en gigaoctets : + +```py +print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") +size_gb = pubmed_dataset.dataset_size / (1024**3) +print(f"Dataset size (cache file) : {size_gb:.2f} GB") +``` + +```python out +Number of files in dataset : 20979437051 +Dataset size (cache file) : 19.54 GB +``` + +Bien - malgré sa taille de près de 20 Go, nous pouvons charger et accéder à l'ensemble de données avec beaucoup moins de RAM ! + + + +✏️ **Essayez-le !** Choisissez l'un des [sous-ensembles](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/) de la Pile qui est plus grand que la RAM de votre ordinateur portable ou de bureau, chargez avec 🤗 Datasets, et mesurez la quantité de RAM utilisée. Notez que pour obtenir une mesure précise, vous devrez le faire dans un nouveau processus. Vous pouvez trouver les tailles décompressées de chaque sous-ensemble dans le tableau 1 de [the Pile paper](https://arxiv.org/abs/2101.00027). + + + +Si vous êtes familier avec les pandas, ce résultat pourrait surprendre en raison de la célèbre [règle d'or](https://wesmckinney.com/blog/apache-arrow-pandas-internals/) de Wes Kinney selon laquelle vous avez généralement besoin de 5 à 10 fois plus de RAM que la taille de votre jeu de données. Alors, comment 🤗 Datasets résout-il ce problème de gestion de la mémoire ? 🤗 Datasets traite chaque ensemble de données comme un [fichier mappé en mémoire] (https://en.wikipedia.org/wiki/Memory-mapped_file), qui fournit un mappage entre la RAM et le stockage du système de fichiers qui permet à la bibliothèque d'accéder et d'opérer sur des éléments du jeu de données sans avoir besoin de le charger entièrement en mémoire. + +Les fichiers mappés en mémoire peuvent également être partagés entre plusieurs processus, ce qui permet de paralléliser des méthodes telles que `Dataset.map()` sans avoir à déplacer ou copier l'ensemble de données. Sous le capot, ces capacités sont toutes réalisées par le format de mémoire [Apache Arrow](https://arrow.apache.org) et [`pyarrow`](https://arrow.apache.org/docs/python/index .html), qui accélèrent le chargement et le traitement des données. (Pour plus de détails sur Apache Arrow et les comparaisons avec Pandas, consultez [l'article de blog de Dejan Simic](https://towardsdatascience.com/apache-arrow-read-dataframe-with-zero-memory-69634092b1a).) Pour voir ceci en action, effectuons un petit test de vitesse en itérant sur tous les éléments du jeu de données PubMed Abstracts : + +```py +import timeit + +code_snippet = """batch_size = 1000 + +for idx in range(0, len(pubmed_dataset), batch_size): + _ = pubmed_dataset[idx:idx + batch_size] +""" + +time = timeit.timeit(stmt=code_snippet, number=1, globals=globals()) +print( + f"Iterated over {len(pubmed_dataset)} examples (about {size_gb:.1f} GB) in " + f"{time:.1f}s, i.e. {size_gb/time:.3f} GB/s" +) +``` + +```python out +'Iterated over 15518009 examples (about 19.5 GB) in 64.2s, i.e. 0.304 GB/s' +``` + +Ici, nous avons utilisé le module `timeit` de Python pour mesurer le temps d'exécution pris par `code_snippet`. Vous pourrez généralement itérer sur un ensemble de données à une vitesse de quelques dixièmes de Go/s à plusieurs Go/s. Cela fonctionne très bien pour la grande majorité des applications, mais vous devrez parfois travailler avec un ensemble de données trop volumineux pour être même stocké sur le disque dur de votre ordinateur portable. Par exemple, si nous essayions de télécharger la Pile dans son intégralité, nous aurions besoin de 825 Go d'espace disque libre ! Pour gérer ces cas, 🤗 Datasets fournit une fonctionnalité de streaming qui nous permet de télécharger et d'accéder aux éléments à la volée, sans avoir besoin de télécharger l'intégralité du jeu de données. Voyons comment cela fonctionne. + + + +💡 Dans les notebooks Jupyter, vous pouvez également chronométrer les cellules à l'aide de la fonction magique [`%%timeit`](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit). + + + +## Ensembles de données en continu + +Pour activer le streaming de l'ensemble de données, il vous suffit de passer l'argument `streaming=True` à la fonction `load_dataset()`. Par exemple, chargeons à nouveau le jeu de données PubMed Abstracts, mais en mode streaming : + +```py +pubmed_dataset_streamed = load_dataset( + "json", data_files=data_files, split="train", streaming=True +) +``` + +Au lieu du familier `Dataset` que nous avons rencontré ailleurs dans ce chapitre, l'objet retourné avec `streaming=True` est un `IterableDataset`. Comme son nom l'indique, pour accéder aux éléments d'un `IterableDataset`, nous devons parcourir celui-ci. Nous pouvons accéder au premier élément de notre jeu de données diffusé comme suit : + + +```py +next(iter(pubmed_dataset_streamed)) +``` + +```python out +{'meta': {'pmid': 11409574, 'language': 'eng'}, + '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 ...'} +``` + +Les éléments d'un ensemble de données diffusé en continu peuvent être traités à la volée à l'aide de `IterableDataset.map()`, ce qui est utile pendant la formation si vous avez besoin de tokeniser les entrées. Le processus est exactement le même que celui que nous avons utilisé pour tokeniser notre jeu de données dans [Chapitre 3](/course/chapter3), à la seule différence que les sorties sont renvoyées une par une : + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased") +tokenized_dataset = pubmed_dataset_streamed.map(lambda x: tokenizer(x["text"])) +next(iter(tokenized_dataset)) +``` + +```python out +{'input_ids': [101, 4958, 5178, 4328, 6779, ...], 'attention_mask': [1, 1, 1, 1, 1, ...]} +``` + + + +💡 Pour accélérer la tokenisation avec le streaming, vous pouvez passer `batched=True`, comme nous l'avons vu dans la dernière section. Il traitera les exemples lot par lot ; la taille de lot par défaut est de 1 000 et peut être spécifiée avec l'argument `batch_size`. + + + +Vous pouvez également mélanger un ensemble de données diffusé en continu à l'aide de `IterableDataset.shuffle()`, mais contrairement à `Dataset.shuffle()`, cela ne mélange que les éléments dans un `buffer_size` prédéfini : + +```py +shuffled_dataset = pubmed_dataset_streamed.shuffle(buffer_size=10_000, seed=42) +next(iter(shuffled_dataset)) +``` + +```python out +{'meta': {'pmid': 11410799, 'language': 'eng'}, + 'text': 'Randomized study of dose or schedule modification of granulocyte colony-stimulating factor in platinum-based chemotherapy for elderly patients with lung cancer ...'} +``` + +Dans cet exemple, nous avons sélectionné un exemple aléatoire parmi les 10 000 premiers exemples du tampon. Une fois qu'un exemple est accédé, sa place dans le tampon est remplie avec l'exemple suivant dans le corpus (c'est-à-dire le 10 001e exemple dans le cas ci-dessus). Vous pouvez également sélectionner des éléments d'un ensemble de données diffusé en continu à l'aide des fonctions `IterableDataset.take()` et `IterableDataset.skip()`, qui agissent de la même manière que `Dataset.select()`. Par exemple, pour sélectionner les 5 premiers exemples dans l'ensemble de données PubMed Abstracts, nous pouvons procéder comme suit : + +```py +dataset_head = pubmed_dataset_streamed.take(5) +list(dataset_head) +``` + +```python out +[{'meta': {'pmid': 11409574, 'language': 'eng'}, + 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'}, + {'meta': {'pmid': 11409575, 'language': 'eng'}, + 'text': 'Clinical signs of hypoxaemia in children with acute lower respiratory infection: indicators of oxygen therapy ...'}, + {'meta': {'pmid': 11409576, 'language': 'eng'}, + 'text': "Hypoxaemia in children with severe pneumonia in Papua New Guinea ..."}, + {'meta': {'pmid': 11409577, 'language': 'eng'}, + 'text': 'Oxygen concentrators and cylinders ...'}, + {'meta': {'pmid': 11409578, 'language': 'eng'}, + 'text': 'Oxygen supply in rural africa: a personal experience ...'}] +``` + +De même, vous pouvez utiliser la fonction `IterableDataset.skip()` pour créer des fractionnements d'entraînement et de validation à partir d'un ensemble de données mélangé comme suit : + +```py +# Skip the first 1,000 examples and include the rest in the training set +train_dataset = shuffled_dataset.skip(1000) +# Take the first 1,000 examples for the validation set +validation_dataset = shuffled_dataset.take(1000) +``` + +Terminons notre exploration du streaming d'ensembles de données avec une application commune : combiner plusieurs ensembles de données pour créer un seul corpus. 🤗 Datasets fournit une fonction `interleave_datasets()` qui convertit une liste d'objets `IterableDataset` en un seul `IterableDataset`, où les éléments du nouveau jeu de données sont obtenus en alternant entre les exemples source. Cette fonction est particulièrement utile lorsque vous essayez de combiner de grands ensembles de données. Par exemple, diffusons le sous-ensemble FreeLaw de la pile, qui est un ensemble de données de 51 Go d'avis juridiques de tribunaux américains : + +```py +law_dataset_streamed = load_dataset( + "json", + data_files="https://mystic.the-eye.eu/public/AI/pile_preliminary_components/FreeLaw_Opinions.jsonl.zst", + split="train", + streaming=True, +) +next(iter(law_dataset_streamed)) +``` + +```python out +{'meta': {'case_ID': '110921.json', + 'case_jurisdiction': 'scotus.tar.gz', + 'date_created': '2010-04-28T17:12:49Z'}, + 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'} +``` + +Cet ensemble de données est suffisamment volumineux pour solliciter la RAM de la plupart des ordinateurs portables, mais nous avons pu le charger et y accéder sans transpirer ! Combinons maintenant les exemples des jeux de données FreeLaw et PubMed Abstracts avec la fonction `interleave_datasets()` : + +```py +from itertools import islice +from datasets import interleave_datasets + +combined_dataset = interleave_datasets([pubmed_dataset_streamed, law_dataset_streamed]) +list(islice(combined_dataset, 2)) +``` + +```python out +[{'meta': {'pmid': 11409574, 'language': 'eng'}, + 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'}, + {'meta': {'case_ID': '110921.json', + 'case_jurisdiction': 'scotus.tar.gz', + 'date_created': '2010-04-28T17:12:49Z'}, + 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'}] +``` + +Ici, nous avons utilisé la fonction `islice()` du module `itertools` de Python pour sélectionner les deux premiers exemples de l'ensemble de données combiné, et nous pouvons voir qu'ils correspondent aux premiers exemples de chacun des deux ensembles de données source. + +Enfin, si vous souhaitez diffuser le Pile dans son intégralité de 825 Go, vous pouvez récupérer tous les fichiers préparés comme suit : + +```py +base_url = "https://mystic.the-eye.eu/public/AI/pile/" +data_files = { + "train": [base_url + "train/" + f"{idx:02d}.jsonl.zst" for idx in range(30)], + "validation": base_url + "val.jsonl.zst", + "test": base_url + "test.jsonl.zst", +} +pile_dataset = load_dataset("json", data_files=data_files, streaming=True) +next(iter(pile_dataset["train"])) +``` + +```python out +{'meta': {'pile_set_name': 'Pile-CC'}, + 'text': 'It is done, and submitted. You can play “Survival of the Tastiest” on Android, and on the web...'} +``` + + + +✏️ **Essayez-le !** Utilisez l'un des grands corpus Common Crawl comme [`mc4`](https://huggingface.co/datasets/mc4) ou [`oscar`](https://huggingface.co /datasets/oscar) pour créer un jeu de données multilingue en continu qui représente les proportions de langues parlées dans un pays de votre choix. Par exemple, les quatre langues nationales en Suisse sont l'allemand, le français, l'italien et le romanche, vous pouvez donc essayer de créer un corpus suisse en échantillonnant les sous-ensembles Oscar en fonction de leur proportion parlée. + + + +Vous disposez maintenant de tous les outils dont vous avez besoin pour charger et traiter des ensembles de données de toutes formes et tailles - mais à moins que vous ne soyez exceptionnellement chanceux, il arrivera un moment dans votre parcours PNL où vous devrez réellement créer un ensemble de données pour résoudre le problème à portée de main. C'est le sujet de la section suivante ! diff --git a/chapters/fr/chapter5/5.mdx b/chapters/fr/chapter5/5.mdx new file mode 100644 index 000000000..298f69200 --- /dev/null +++ b/chapters/fr/chapter5/5.mdx @@ -0,0 +1,469 @@ +# Création de votre propre jeu de données + + + +Parfois, l'ensemble de données dont vous avez besoin pour créer une application NLP n'existe pas, vous devrez donc le créer vous-même. Dans cette section, nous allons vous montrer comment créer un corpus de [problèmes GitHub](https://github.com/features/issues/), qui sont couramment utilisés pour suivre les bogues ou les fonctionnalités dans les référentiels GitHub. Ce corpus pourrait être utilisé à diverses fins, notamment : + +* Explorer combien de temps il faut pour fermer les problèmes ouverts ou les demandes d'extraction +* Entraînement d'un _classificateur multilabel_ capable d'étiqueter les problèmes avec des métadonnées basées sur la description du problème (par exemple, "bogue", "amélioration" ou "question") +* Création d'un moteur de recherche sémantique pour trouver les problèmes correspondant à la requête d'un utilisateur + +Ici, nous nous concentrerons sur la création du corpus, et dans la section suivante, nous aborderons l'application de recherche sémantique. Pour garder les choses méta, nous utiliserons les problèmes GitHub associés à un projet open source populaire : 🤗 Datasets ! Voyons comment obtenir les données et explorons les informations contenues dans ces problèmes. + +## Obtenir les données + +Vous pouvez trouver tous les problèmes dans 🤗 Datasets en accédant à l'[onglet Problèmes] du référentiel (https://github.com/huggingface/datasets/issues). Comme le montre la capture d'écran suivante, au moment de la rédaction, il y avait 331 problèmes ouverts et 668 problèmes fermés. + +
+Les problèmes GitHub associés aux 🤗 Datasets. +
+ +Si vous cliquez sur l'un de ces problèmes, vous constaterez qu'il contient un titre, une description et un ensemble d'étiquettes qui caractérisent le problème. Un exemple est montré dans la capture d'écran ci-dessous. + +
+Un problème GitHub typique dans le référentiel 🤗 Datasets. +
+ +Pour télécharger tous les problèmes du référentiel, nous utiliserons l'[API REST GitHub](https://docs.github.com/en/rest) pour interroger le point de terminaison [`Issues`](https://docs.github. com/en/rest/reference/issues#list-repository-issues). Ce point de terminaison renvoie une liste d'objets JSON, chaque objet contenant un grand nombre de champs qui incluent le titre et la description ainsi que des métadonnées sur l'état du problème, etc. + +Un moyen pratique de télécharger les problèmes consiste à utiliser la bibliothèque "requests", qui est la méthode standard pour effectuer des requêtes HTTP en Python. Vous pouvez installer la bibliothèque en exécutant : + +```python +!pip install requests +``` + +Une fois la bibliothèque installée, vous pouvez envoyer des requêtes GET au point de terminaison `Issues` en appelant la fonction `requests.get()`. Par exemple, vous pouvez exécuter la commande suivante pour récupérer le premier numéro sur la première page : + +```py +import requests + +url = "https://api.github.com/repos/huggingface/datasets/issues?page=1&per_page=1" +response = requests.get(url) +``` + +L'objet `response` contient de nombreuses informations utiles sur la requête, y compris le code d'état HTTP : + +```py +response.status_code +``` + +```python out +200 +``` + +où un statut "200" signifie que la requête a réussi (vous pouvez trouver une liste des codes de statut HTTP possibles [ici](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes)). Ce qui nous intéresse vraiment, cependant, c'est le _payload_, qui peut être consulté dans différents formats comme les octets, les chaînes ou JSON. Comme nous savons que nos problèmes sont au format JSON, examinons la charge utile comme suit : + +```py +response.json() +``` + +```python out +[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/2792', + 'repository_url': 'https://api.github.com/repos/huggingface/datasets', + 'labels_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/labels{/name}', + 'comments_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/comments', + 'events_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/events', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792', + 'id': 968650274, + 'node_id': 'MDExOlB1bGxSZXF1ZXN0NzEwNzUyMjc0', + 'number': 2792, + 'title': 'Update GooAQ', + 'user': {'login': 'bhavitvyamalik', + 'id': 19718818, + 'node_id': 'MDQ6VXNlcjE5NzE4ODE4', + 'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4', + 'gravatar_id': '', + 'url': 'https://api.github.com/users/bhavitvyamalik', + 'html_url': 'https://github.com/bhavitvyamalik', + 'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers', + 'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}', + 'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}', + 'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}', + 'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions', + 'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs', + 'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos', + 'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}', + 'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events', + 'type': 'User', + 'site_admin': False}, + 'labels': [], + 'state': 'open', + 'locked': False, + 'assignee': None, + 'assignees': [], + 'milestone': None, + 'comments': 1, + 'created_at': '2021-08-12T11:40:18Z', + 'updated_at': '2021-08-12T12:31:17Z', + 'closed_at': None, + 'author_association': 'CONTRIBUTOR', + 'active_lock_reason': None, + 'pull_request': {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/2792', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792', + 'diff_url': 'https://github.com/huggingface/datasets/pull/2792.diff', + 'patch_url': 'https://github.com/huggingface/datasets/pull/2792.patch'}, + 'body': '[GooAQ](https://github.com/allenai/gooaq) dataset was recently updated after splits were added for the same. This PR contains new updated GooAQ with train/val/test splits and updated README as well.', + 'performed_via_github_app': None}] +``` + +Waouh, ça fait beaucoup d'informations ! Nous pouvons voir des champs utiles comme `title`, `body` et `number` qui décrivent le problème, ainsi que des informations sur l'utilisateur GitHub qui a ouvert le problème. + + + +✏️ **Essayez-le !** Cliquez sur quelques-unes des URL dans la charge utile JSON ci-dessus pour avoir une idée du type d'informations auxquelles chaque problème GitHub est lié. + + + +Comme décrit dans la [documentation] GitHub(https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting), les requêtes non authentifiées sont limitées à 60 requêtes par heure. Bien que vous puissiez augmenter le paramètre de requête `per_page` pour réduire le nombre de requêtes que vous effectuez, vous atteindrez toujours la limite de débit sur tout référentiel contenant plus de quelques milliers de problèmes. Donc, à la place, vous devez suivre les [instructions] de GitHub (https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) sur la création d'un _jeton d'accès personnel_ afin que vous peut augmenter la limite de débit à 5 000 requêtes par heure. Une fois que vous avez votre jeton, vous pouvez l'inclure dans l'en-tête de la requête : + +```py +GITHUB_TOKEN = xxx # Copy your GitHub token here +headers = {"Authorization": f"token {GITHUB_TOKEN}"} +``` + + + +⚠️ Ne partagez pas un notebook avec votre `GITHUB_TOKEN` collé dedans. Nous vous recommandons de supprimer la dernière cellule une fois que vous l'avez exécutée pour éviter de divulguer accidentellement ces informations. Mieux encore, stockez le jeton dans un fichier *.env* et utilisez la [bibliothèque `python-dotenv`](https://github.com/theskumar/python-dotenv) pour le charger automatiquement pour vous en tant que variable d'environnement. + + + +Maintenant que nous avons notre jeton d'accès, créons une fonction qui peut télécharger tous les problèmes depuis un référentiel GitHub : + +```py +import time +import math +from pathlib import Path +import pandas as pd +from tqdm.notebook import tqdm + + +def fetch_issues( + owner="huggingface", + repo="datasets", + num_issues=10_000, + rate_limit=5_000, + issues_path=Path("."), +): + if not issues_path.is_dir(): + issues_path.mkdir(exist_ok=True) + + batch = [] + all_issues = [] + per_page = 100 # Number of issues to return per page + num_pages = math.ceil(num_issues / per_page) + base_url = "https://api.github.com/repos" + + for page in tqdm(range(num_pages)): + # Query with state=all to get both open and closed issues + query = f"issues?page={page}&per_page={per_page}&state=all" + issues = requests.get(f"{base_url}/{owner}/{repo}/{query}", headers=headers) + batch.extend(issues.json()) + + if len(batch) > rate_limit and len(all_issues) < num_issues: + all_issues.extend(batch) + batch = [] # Flush batch for next time period + print(f"Reached GitHub rate limit. Sleeping for one hour ...") + time.sleep(60 * 60 + 1) + + all_issues.extend(batch) + df = pd.DataFrame.from_records(all_issues) + df.to_json(f"{issues_path}/{repo}-issues.jsonl", orient="records", lines=True) + print( + f"Downloaded all the issues for {repo}! Dataset stored at {issues_path}/{repo}-issues.jsonl" + ) +``` + +Désormais, lorsque nous appellerons `fetch_issues()`, tous les problèmes seront téléchargés par lots pour éviter de dépasser la limite de GitHub sur le nombre de requêtes par heure ; le résultat sera stocké dans un fichier _repository_name-issues.jsonl_, où chaque ligne est un objet JSON qui représente un problème. Utilisons cette fonction pour saisir tous les problèmes de 🤗 Datasets : + +```py +# Depending on your internet connection, this can take several minutes to run... +fetch_issues() +``` + +Une fois les problèmes téléchargés, nous pouvons les charger localement en utilisant nos nouvelles compétences de [section 2](/course/chaper5/2) : + +```py +issues_dataset = load_dataset("json", data_files="datasets-issues.jsonl", split="train") +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'timeline_url', 'performed_via_github_app'], + num_rows: 3019 +}) +``` + +Génial, nous avons créé notre premier ensemble de données à partir de rien ! Mais pourquoi y a-t-il plusieurs milliers de problèmes alors que l'[onglet Problèmes](https://github.com/huggingface/datasets/issues) du 🤗 Datasets n'affiche qu'environ 1 000 problèmes au total 🤔 ? Comme décrit dans la [documentation] GitHub(https://docs.github.com/en/rest/reference/issues#list-issues-assigned-to-the-authenticated-user), c'est parce que nous avons téléchargé tous les les demandes d'extraction également : + +> L'API REST v3 de GitHub considère chaque demande d'extraction comme un problème, mais chaque problème n'est pas une demande d'extraction. Pour cette raison, les points de terminaison "Problèmes" peuvent renvoyer à la fois des problèmes et des demandes d'extraction dans la réponse. Vous pouvez identifier les demandes d'extraction par la clé `pull_request`. Sachez que l'identifiant d'une demande d'extraction renvoyée par les points de terminaison "Problèmes" sera un identifiant de problème. + +Étant donné que le contenu des problèmes et des demandes d'extraction est assez différent, procédons à un prétraitement mineur pour nous permettre de les distinguer. + +## Nettoyer les données + +L'extrait ci-dessus de la documentation de GitHub nous indique que la colonne "pull_request" peut être utilisée pour différencier les problèmes et les demandes d'extraction. Regardons un échantillon aléatoire pour voir quelle est la différence. Comme nous l'avons fait dans [section 3](/course/chapter5/3), nous allons enchaîner `Dataset.shuffle()` et `Dataset.select()` pour créer un échantillon aléatoire, puis compresser `html_url` et ` pull_request` afin que nous puissions comparer les différentes URL : + +```py +sample = issues_dataset.shuffle(seed=666).select(range(3)) + +# Print out the URL and pull request entries +for url, pr in zip(sample["html_url"], sample["pull_request"]): + print(f">> URL: {url}") + print(f">> Pull request: {pr}\n") +``` + +```python out +>> URL: https://github.com/huggingface/datasets/pull/850 +>> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/850', 'html_url': 'https://github.com/huggingface/datasets/pull/850', 'diff_url': 'https://github.com/huggingface/datasets/pull/850.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/850.patch'} + +>> URL: https://github.com/huggingface/datasets/issues/2773 +>> Pull request: None + +>> URL: https://github.com/huggingface/datasets/pull/783 +>> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/783', 'html_url': 'https://github.com/huggingface/datasets/pull/783', 'diff_url': 'https://github.com/huggingface/datasets/pull/783.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/783.patch'} +``` + +Ici, nous pouvons voir que chaque demande d'extraction est associée à diverses URL, tandis que les problèmes ordinaires ont une entrée "Aucun". Nous pouvons utiliser cette distinction pour créer une nouvelle colonne `is_pull_request` qui vérifie si le champ `pull_request` est `None` ou non : + +```py +issues_dataset = issues_dataset.map( + lambda x: {"is_pull_request": False if x["pull_request"] is None else True} +) +``` + + + +✏️ **Essayez-le !** Calculez le temps moyen nécessaire pour résoudre les problèmes dans 🤗 Datasets. Vous pouvez trouver la fonction `Dataset.filter()` utile pour filtrer les demandes d'extraction et les problèmes ouverts, et vous pouvez utiliser la fonction `Dataset.set_format()` pour convertir l'ensemble de données en un `DataFrame` afin que vous puissiez facilement manipuler les horodatages `created_at` et `closed_at`. Pour les points bonus, calculez le temps moyen nécessaire pour fermer les demandes d'extraction. + + + +Bien que nous puissions continuer à nettoyer davantage l'ensemble de données en supprimant ou en renommant certaines colonnes, il est généralement recommandé de conserver l'ensemble de données aussi "brut" que possible à ce stade afin qu'il puisse être facilement utilisé dans plusieurs applications. + +Avant de pousser notre ensemble de données vers le Hugging Face Hub, traitons d'une chose qui lui manque : les commentaires associés à chaque problème et pull request. Nous les ajouterons ensuite avec - vous l'avez deviné - l'API GitHub REST ! + +## Enrichir le jeu de données + +Comme le montre la capture d'écran suivante, les commentaires associés à un problème ou à une demande d'extraction fournissent une riche source d'informations, en particulier si nous souhaitons créer un moteur de recherche pour répondre aux requêtes des utilisateurs sur la bibliothèque. + +
+Commentaires associés à un problème concernant 🤗 Datasets. +
+ +L'API REST GitHub fournit un point de terminaison [`Comments`](https://docs.github.com/en/rest/reference/issues#list-issue-comments) qui renvoie tous les commentaires associés à un numéro de problème. Testons le point de terminaison pour voir ce qu'il renvoie : + +```py +issue_number = 2792 +url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments" +response = requests.get(url, headers=headers) +response.json() +``` + +```python out +[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/comments/897594128', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792#issuecomment-897594128', + 'issue_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792', + 'id': 897594128, + 'node_id': 'IC_kwDODunzps41gDMQ', + 'user': {'login': 'bhavitvyamalik', + 'id': 19718818, + 'node_id': 'MDQ6VXNlcjE5NzE4ODE4', + 'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4', + 'gravatar_id': '', + 'url': 'https://api.github.com/users/bhavitvyamalik', + 'html_url': 'https://github.com/bhavitvyamalik', + 'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers', + 'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}', + 'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}', + 'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}', + 'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions', + 'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs', + 'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos', + 'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}', + 'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events', + 'type': 'User', + 'site_admin': False}, + 'created_at': '2021-08-12T12:21:52Z', + 'updated_at': '2021-08-12T12:31:17Z', + 'author_association': 'CONTRIBUTOR', + 'body': "@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?", + 'performed_via_github_app': None}] +``` + +Nous pouvons voir que le commentaire est stocké dans le champ `body`, écrivons donc une fonction simple qui renvoie tous les commentaires associés à un problème en sélectionnant le contenu `body` pour chaque élément dans `response.json()` : + +```py +def get_comments(issue_number): + url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments" + response = requests.get(url, headers=headers) + return [r["body"] for r in response.json()] + + +# Testez notre fonction fonctionne comme prévu +get_comments(2792) +``` + +```python out +["@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?"] +``` + +Cela a l'air bien, alors utilisons `Dataset.map()` pour ajouter une nouvelle colonne `commentaires` à chaque problème de notre ensemble de données : + +```py +# Selon votre connexion internet, cela peut prendre quelques minutes... +issues_with_comments_dataset = issues_dataset.map( + lambda x: {"comments": get_comments(x["number"])} +) +``` + +La dernière étape consiste à enregistrer l'ensemble de données augmentées avec nos données brutes afin que nous puissions les pousser toutes les deux vers le Hub : + +```py +issues_with_comments_dataset.to_json("issues-datasets-with-comments.jsonl") +``` + +## Téléchargement de l'ensemble de données sur le Hugging Face Hub + + + +Maintenant que nous avons notre jeu de données augmenté, il est temps de le pousser vers le Hub afin que nous puissions le partager avec la communauté ! Pour télécharger l'ensemble de données, nous utiliserons la [bibliothèque Hub 🤗](https://github.com/huggingface/huggingface_hub), qui nous permet d'interagir avec le hub Hugging Face via une API Python. 🤗 Hub est préinstallé avec 🤗 Transformers, nous pouvons donc l'utiliser directement. Par exemple, nous pouvons utiliser la fonction `list_datasets()` pour obtenir des informations sur tous les ensembles de données publics actuellement hébergés sur le Hub : + +```py +from huggingface_hub import list_datasets + +all_datasets = list_datasets() +print(f"Number of datasets on Hub: {len(all_datasets)}") +print(all_datasets[0]) +``` + +```python out +Number of datasets on Hub: 1487 +Dataset Name: acronym_identification, Tags: ['annotations_creators:expert-generated', 'language_creators:found', 'languages:en', 'licenses:mit', 'multilinguality:monolingual', 'size_categories:10K + +✏️ **Essayez-le !** Utilisez votre nom d'utilisateur et votre mot de passe Hugging Face Hub pour obtenir un jeton et créer un référentiel vide appelé "github-issues". N'oubliez pas de **n'enregistrez jamais vos informations d'identification** dans Colab ou tout autre référentiel, car ces informations peuvent être exploitées par de mauvais acteurs. + + + +Ensuite, clonons le référentiel du Hub sur notre machine locale et copions-y notre fichier d'ensemble de données. 🤗 Hub fournit une classe `Repository` pratique qui encapsule de nombreuses commandes Git courantes, donc pour cloner le référentiel distant, nous devons simplement fournir l'URL et le chemin local vers lesquels nous souhaitons cloner : + +```py +from huggingface_hub import Repository + +repo = Repository(local_dir="github-issues", clone_from=repo_url) +!cp datasets-issues-with-comments.jsonl github-issues/ +``` + +Par défaut, diverses extensions de fichiers (telles que *.bin*, *.gz* et *.zip*) sont suivies avec Git LFS afin que les fichiers volumineux puissent être versionnés dans le même workflow Git. Vous pouvez trouver une liste des extensions de fichiers suivis dans le fichier *.gitattributes* du référentiel. Pour inclure le format JSON Lines dans la liste, nous pouvons exécuter la commande suivante : + +```py +repo.lfs_track("*.jsonl") +``` + +Ensuite, nous pouvons utiliser `Repository.push_to_hub()` pour pousser l'ensemble de données vers le Hub : + +```py +repo.push_to_hub() +``` + +Si nous naviguons vers l'URL contenue dans `repo_url`, nous devrions maintenant voir que notre fichier de jeu de données a été téléchargé. + +
+Notre référentiel d'ensembles de données sur le Hugging Face Hub. +
+ +À partir de là, n'importe qui peut télécharger l'ensemble de données en fournissant simplement `load_dataset()` avec l'ID du référentiel comme argument `path` : + +```py +remote_dataset = load_dataset("lewtun/github-issues", split="train") +remote_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 2855 +}) +``` + +Cool, nous avons poussé notre jeu de données vers le Hub et il est disponible pour que d'autres puissent l'utiliser ! Il ne reste plus qu'une chose importante à faire : ajouter une _carte de jeu de données_ qui explique comment le corpus a été créé et fournit d'autres informations utiles à la communauté. + + + +💡 Vous pouvez également télécharger un ensemble de données sur le Hugging Face Hub directement depuis le terminal en utilisant `huggingface-cli` et un peu de magie Git. Consultez le [🤗 Datasets guide](https://huggingface.co/docs/datasets/share.html#add-a-community-dataset) pour savoir comment procéder. + + + +## Création d'une fiche de jeu de données + +Des ensembles de données bien documentés sont plus susceptibles d'être utiles aux autres (y compris à votre futur moi !), car ils fournissent le contexte permettant aux utilisateurs de décider si l'ensemble de données est pertinent pour leur tâche et d'évaluer les biais potentiels ou les risques associés à l'utilisation l'ensemble de données. + +Sur le Hugging Face Hub, ces informations sont stockées dans le fichier *README.md* de chaque référentiel d'ensembles de données. Il y a deux étapes principales que vous devez suivre avant de créer ce fichier : + +1. Utilisez l'[application `datasets-tagging`](https://huggingface.co/datasets/tagging/) pour créer des balises de métadonnées au format YAML. Ces balises sont utilisées pour une variété de fonctionnalités de recherche sur le Hugging Face Hub et garantissent que votre ensemble de données peut être facilement trouvé par les membres de la communauté. Puisque nous avons créé un ensemble de données personnalisé ici, vous devrez cloner le référentiel `datasets-tagging` et exécuter l'application localement. Voici à quoi ressemble l'interface : + +
+L'interface `datasets-tagging`. +
+ +2. Lisez le [🤗 Datasets guide](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) sur la création de cartes d'ensemble de données informatives et utilisez-le comme modèle. + +Vous pouvez créer le fichier *README.md* directement sur le Hub, et vous pouvez trouver un modèle de carte d'ensemble de données dans le référentiel d'ensemble de données `lewtun/github-issues`. Une capture d'écran de la carte de jeu de données remplie est illustrée ci-dessous. + +
+Une carte de jeu de données. +
+ + + +* Fichier README.md* pour votre ensemble de données de problèmes GitHub. + + + +C'est ça! Nous avons vu dans cette section que la création d'un bon ensemble de données peut être assez complexe, mais heureusement, le télécharger et le partager avec la communauté ne l'est pas. Dans la section suivante, nous utiliserons notre nouvel ensemble de données pour créer un moteur de recherche sémantique avec 🤗 Deatasets qui peut faire correspondre les questions aux problèmes et commentaires les plus pertinents. + + + +✏️ **Essayez-le !** Suivez les étapes que nous avons suivies dans cette section pour créer un ensemble de données de problèmes GitHub pour votre bibliothèque open source préférée (choisissez autre chose que 🤗 Datasets, bien sûr !). Pour obtenir des points bonus, ajustez un classificateur multilabel pour prédire les balises présentes dans le champ "labels". + + + + diff --git a/chapters/fr/chapter5/6.mdx b/chapters/fr/chapter5/6.mdx new file mode 100644 index 000000000..fb055ccb8 --- /dev/null +++ b/chapters/fr/chapter5/6.mdx @@ -0,0 +1,530 @@ + + +# Recherche sémantique avec FAISS + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Dans [section 5](/course/chapter5/5), nous avons créé un ensemble de données de problèmes et de commentaires GitHub à partir du référentiel 🤗 Datasets. Dans cette section, nous utiliserons ces informations pour créer un moteur de recherche qui peut nous aider à trouver des réponses à nos questions les plus urgentes sur la bibliothèque ! + + + +## Utilisation des représentations vectorielles continues pour la recherche sémantique + +Comme nous l'avons vu dans le [Chapitre 1](/course/chapter1), les modèles de langage basés sur Transformer représentent chaque jeton dans une étendue de texte sous la forme d'un _vecteur d'intégration_. Il s'avère que l'on peut "regrouper" les incorporations individuelles pour créer une représentation vectorielle pour des phrases entières, des paragraphes ou (dans certains cas) des documents. Ces intégrations peuvent ensuite être utilisées pour trouver des documents similaires dans le corpus en calculant la similarité du produit scalaire (ou une autre métrique de similarité) entre chaque intégration et en renvoyant les documents avec le plus grand chevauchement. + +Dans cette section, nous utiliserons les incorporations pour développer un moteur de recherche sémantique. Ces moteurs de recherche offrent plusieurs avantages par rapport aux approches conventionnelles basées sur la correspondance des mots-clés dans une requête avec les documents. + +
+Recherche sémantique. + +
+ +## Chargement et préparation du jeu de données + +La première chose que nous devons faire est de télécharger notre ensemble de données de problèmes GitHub, alors utilisons la bibliothèque 🤗 Hub pour résoudre l'URL où notre fichier est stocké sur le Hugging Face Hub : + +```py +from huggingface_hub import hf_hub_url + +data_files = hf_hub_url( + repo_id="lewtun/github-issues", + filename="datasets-issues-with-comments.jsonl", + repo_type="dataset", +) +``` + +Avec l'URL stockée dans `data_files`, nous pouvons ensuite charger le jeu de données distant en utilisant la méthode introduite dans [section 2](/course/chapter5/2) : + +```py +from datasets import load_dataset + +issues_dataset = load_dataset("json", data_files=data_files, split="train") +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 2855 +}) +``` + +Ici, nous avons spécifié la division `train` par défaut dans `load_dataset()`, de sorte qu'elle renvoie un `Dataset` au lieu d'un `DatasetDict`. La première chose à faire est de filtrer les demandes d'extraction, car celles-ci ont tendance à être rarement utilisées pour répondre aux requêtes des utilisateurs et introduiront du bruit dans notre moteur de recherche. Comme cela devrait être familier maintenant, nous pouvons utiliser la fonction `Dataset.filter()` pour exclure ces lignes de notre ensemble de données. Pendant que nous y sommes, filtrons également les lignes sans commentaires, car celles-ci ne fournissent aucune réponse aux requêtes des utilisateurs : + +```py +issues_dataset = issues_dataset.filter( + lambda x: (x["is_pull_request"] == False and len(x["comments"]) > 0) +) +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 771 +}) +``` + +Nous pouvons voir qu'il y a beaucoup de colonnes dans notre ensemble de données, dont la plupart n'ont pas besoin de construire notre moteur de recherche. Du point de vue de la recherche, les colonnes les plus informatives sont `title`, `body` et `comments`, tandis que `html_url` nous fournit un lien vers le problème source. Utilisons la fonction `Dataset.remove_columns()` pour supprimer le reste : + +```py +columns = issues_dataset.column_names +columns_to_keep = ["title", "body", "html_url", "comments"] +columns_to_remove = set(columns_to_keep).symmetric_difference(columns) +issues_dataset = issues_dataset.remove_columns(columns_to_remove) +issues_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body'], + num_rows: 771 +}) +``` + +Pour créer nos intégrations, nous ajouterons à chaque commentaire le titre et le corps du problème, car ces champs contiennent souvent des informations contextuelles utiles. Étant donné que notre colonne `comments` est actuellement une liste de commentaires pour chaque problème, nous devons "éclater" la colonne afin que chaque ligne se compose d'un tuple `(html_url, title, body, comment)`. Dans Pandas, nous pouvons le faire avec la fonction [`DataFrame.explode()`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html), qui crée une nouvelle ligne pour chaque élément dans une colonne de type liste, tout en répliquant toutes les autres valeurs de colonne. Pour voir cela en action, passons d'abord au format "DataFrame" de Pandas : + +```py +issues_dataset.set_format("pandas") +df = issues_dataset[:] +``` + +Si nous inspectons la première ligne de ce `DataFrame`, nous pouvons voir qu'il y a quatre commentaires associés à ce problème : + +```py +df["comments"][0].tolist() +``` + +```python out +['the bug code locate in :\r\n if data_args.task_name is not None:\r\n # Downloading and loading a dataset from the hub.\r\n datasets = load_dataset("glue", data_args.task_name, cache_dir=model_args.cache_dir)', + 'Hi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com\r\n\r\nNormally, it should work if you wait a little and then retry.\r\n\r\nCould you please confirm if the problem persists?', + 'cannot connect,even by Web browser,please check that there is some problems。', + 'I can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...'] +``` + +Lorsque nous décomposons `df`, nous nous attendons à obtenir une ligne pour chacun de ces commentaires. Vérifions si c'est le cas : + +```py +comments_df = df.explode("comments", ignore_index=True) +comments_df.head(4) +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
html_urltitlecommentsbody
0https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comthe bug code locate in :\r\n if data_args.task_name is not None...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
1https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comHi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
2https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comcannot connect,even by Web browser,please check that there is some problems。Hello,\r\nI am trying to run run_glue.py and it gives me this error...
3https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comI can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
+ +Génial, nous pouvons voir que les lignes ont été répliquées, avec la colonne "commentaires" contenant les commentaires individuels ! Maintenant que nous en avons fini avec Pandas, nous pouvons rapidement revenir à un `Dataset` en chargeant le `DataFrame` en mémoire : + +```py +from datasets import Dataset + +comments_dataset = Dataset.from_pandas(comments_df) +comments_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body'], + num_rows: 2842 +}) +``` + +D'accord, cela nous a donné quelques milliers de commentaires avec lesquels travailler ! + + + + +✏️ **Essayez-le !** 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; vous pourriez trouver la section ["Batch mapping"](https://huggingface.co/docs/datasets/v1.12.1/about_map_batch.html?batch-mapping#batch-mapping) de la documentation 🤗 Datasets utile pour cette tâche. + + + +Maintenant que nous avons un commentaire par ligne, créons une nouvelle colonne `comments_length` contenant le nombre de mots par commentaire : + +```py +comments_dataset = comments_dataset.map( + lambda x: {"comment_length": len(x["comments"].split())} +) +``` + +Nous pouvons utiliser cette nouvelle colonne pour filtrer les commentaires courts, qui incluent généralement des éléments tels que "cc @lewtun" ou "Merci !" qui ne sont pas pertinents pour notre moteur de recherche. Il n'y a pas de nombre précis à sélectionner pour le filtre, mais environ 15 mots semblent être un bon début : + +```py +comments_dataset = comments_dataset.filter(lambda x: x["comment_length"] > 15) +comments_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body', 'comment_length'], + num_rows: 2098 +}) +``` + +Après avoir un peu nettoyé notre ensemble de données, concaténons le titre, la description et les commentaires du problème dans une nouvelle colonne "texte". Comme d'habitude, nous allons écrire une fonction simple que nous pouvons passer à `Dataset.map()` : + +```py +def concatenate_text(examples): + return { + "text": examples["title"] + + " \n " + + examples["body"] + + " \n " + + examples["comments"] + } + + +comments_dataset = comments_dataset.map(concatenate_text) +``` + +Nous sommes enfin prêts à créer des embeddings ! Nous allons jeter un coup d'oeil. + +## Création d'incorporations de texte + +Nous avons vu dans [Chapitre 2](/course/chapter2) que nous pouvons obtenir des incorporations de jetons en utilisant la classe `AutoModel`. Tout ce que nous avons à faire est de choisir un point de contrôle approprié à partir duquel charger le modèle. Heureusement, il existe une bibliothèque appelée `sentence-transformers` dédiée à la création d'incorporations. Comme décrit dans la [documentation] de la bibliothèque (https://www.sbert.net/examples/applications/semantic-search/README.html#symmetric-vs-asymmetric-semantic-search), notre cas d'utilisation est un exemple de _asymmetric recherche sémantique_ car nous avons une requête courte dont nous aimerions trouver la réponse dans un document plus long, comme un commentaire sur un problème. Le [tableau de présentation des modèles] (https://www.sbert.net/docs/pretrained_models.html#model-overview) pratique de la documentation indique que le point de contrôle `multi-qa-mpnet-base-dot-v1` a le meilleures performances pour la recherche sémantique, nous l'utiliserons donc pour notre application. Nous allons également charger le tokenizer en utilisant le même point de contrôle : + +{#if fw === 'pt'} + +```py +from transformers import AutoTokenizer, AutoModel + +model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1" +tokenizer = AutoTokenizer.from_pretrained(model_ckpt) +model = AutoModel.from_pretrained(model_ckpt) +``` + +Pour accélérer le processus d'intégration, il est utile de placer le modèle et les entrées sur un périphérique GPU, alors faisons-le maintenant : + +```py +import torch + +device = torch.device("cuda") +model.to(device) +``` + +{:else} + +```py +from transformers import AutoTokenizer, TFAutoModel + +model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1" +tokenizer = AutoTokenizer.from_pretrained(model_ckpt) +model = TFAutoModel.from_pretrained(model_ckpt, from_pt=True) +``` + +Notez que nous avons défini `from_pt=True` comme argument de la méthode `from_pretrained()`. C'est parce que le point de contrôle `multi-qa-mpnet-base-dot-v1` n'a que des poids PyTorch, donc définir `from_pt=True` les convertira automatiquement au format TensorFlow pour nous. Comme vous pouvez le voir, il est très simple de passer d'un framework à l'autre dans 🤗 Transformers ! + +{/if} + +Comme nous l'avons mentionné précédemment, nous aimerions représenter chaque entrée dans notre corpus de problèmes GitHub comme un vecteur unique, nous devons donc "regrouper" ou faire la moyenne de nos incorporations de jetons d'une manière ou d'une autre. Une approche populaire consiste à effectuer un * regroupement CLS * sur les sorties de notre modèle, où nous collectons simplement le dernier état caché pour le jeton spécial `[CLS]`. La fonction suivante fait l'affaire pour nous : + +```py +def cls_pooling(model_output): + return model_output.last_hidden_state[:, 0] +``` + +Ensuite, nous allons créer une fonction d'assistance qui va tokeniser une liste de documents, placer les tenseurs sur le GPU, les alimenter au modèle et enfin appliquer le regroupement CLS aux sorties : + +{#if fw === 'pt'} + +```py +def get_embeddings(text_list): + encoded_input = tokenizer( + text_list, padding=True, truncation=True, return_tensors="pt" + ) + encoded_input = {k: v.to(device) for k, v in encoded_input.items()} + model_output = model(**encoded_input) + return cls_pooling(model_output) +``` + +Nous pouvons tester le fonctionnement de la fonction en lui fournissant la première entrée de texte dans notre corpus et en inspectant la forme de sortie : + +```py +embedding = get_embeddings(comments_dataset["text"][0]) +embedding.shape +``` + +```python out +torch.Size([1, 768]) +``` + +Super, nous avons converti la première entrée de notre corpus en un vecteur à 768 dimensions ! Nous pouvons utiliser `Dataset.map()` pour appliquer notre fonction `get_embeddings()` à chaque ligne de notre corpus, créons donc une nouvelle colonne `embeddings` comme suit : + +```py +embeddings_dataset = comments_dataset.map( + lambda x: {"embeddings": get_embeddings(x["text"]).detach().cpu().numpy()[0]} +) +``` + +{:else} + +```py +def get_embeddings(text_list): + encoded_input = tokenizer( + text_list, padding=True, truncation=True, return_tensors="tf" + ) + encoded_input = {k: v for k, v in encoded_input.items()} + model_output = model(**encoded_input) + return cls_pooling(model_output) +``` + +Nous pouvons tester le fonctionnement de la fonction en lui fournissant la première entrée de texte dans notre corpus et en inspectant la forme de sortie : + +```py +embedding = get_embeddings(comments_dataset["text"][0]) +embedding.shape +``` + +```python out +TensorShape([1, 768]) +``` + +Super, nous avons converti la première entrée de notre corpus en un vecteur à 768 dimensions ! Nous pouvons utiliser `Dataset.map()` pour appliquer notre fonction `get_embeddings()` à chaque ligne de notre corpus, créons donc une nouvelle colonne `embeddings` comme suit : + +```py +embeddings_dataset = comments_dataset.map( + lambda x: {"embeddings": get_embeddings(x["text"]).numpy()[0]} +) +``` + +{/if} + + +Notez que nous avons converti les intégrations en tableaux NumPy -- c'est parce que 🤗 Datasets nécessite ce format lorsque nous essayons de les indexer avec FAISS, ce que nous ferons ensuite. + +## Utilisation de FAISS pour une recherche de similarité efficace + +Maintenant que nous avons un ensemble de données d'incorporations, nous avons besoin d'un moyen de les rechercher. Pour ce faire, nous utiliserons une structure de données spéciale dans 🤗 Datasets appelée _FAISS index_. [FAISS](https://faiss.ai/) (abréviation de Facebook AI Similarity Search) est une bibliothèque qui fournit des algorithmes efficaces pour rechercher et regrouper rapidement des vecteurs d'intégration. + +L'idée de base derrière FAISS est de créer une structure de données spéciale appelée un _index_ qui permet de trouver quels plongements sont similaires à un plongement d'entrée. Créer un index FAISS dans 🤗 Datasets est simple -- nous utilisons la fonction `Dataset.add_faiss_index()` et spécifions quelle colonne de notre jeu de données nous aimerions indexer : + +```py +embeddings_dataset.add_faiss_index(column="embeddings") +``` + +Nous pouvons maintenant effectuer des requêtes sur cet index en effectuant une recherche de voisin le plus proche avec la fonction `Dataset.get_nearest_examples()`. Testons cela en intégrant d'abord une question comme suit : + +{#if fw === 'pt'} + +```py +question = "How can I load a dataset offline?" +question_embedding = get_embeddings([question]).cpu().detach().numpy() +question_embedding.shape +``` + +```python out +torch.Size([1, 768]) +``` + +{:else} + +```py +question = "How can I load a dataset offline?" +question_embedding = get_embeddings([question]).numpy() +question_embedding.shape +``` + +```python out +(1, 768) +``` + +{/if} + +Tout comme avec les documents, nous avons maintenant un vecteur de 768 dimensions représentant la requête, que nous pouvons comparer à l'ensemble du corpus pour trouver les plongements les plus similaires : + +```py +scores, samples = embeddings_dataset.get_nearest_examples( + "embeddings", question_embedding, k=5 +) +``` + +La fonction `Dataset.get_nearest_examples()` renvoie un tuple de scores qui classent le chevauchement entre la requête et le document, et un ensemble correspondant d'échantillons (ici, les 5 meilleures correspondances). Collectons-les dans un `pandas.DataFrame` afin de pouvoir les trier facilement : + +```py +import pandas as pd + +samples_df = pd.DataFrame.from_dict(samples) +samples_df["scores"] = scores +samples_df.sort_values("scores", ascending=False, inplace=True) +``` + +Nous pouvons maintenant parcourir les premières lignes pour voir dans quelle mesure notre requête correspond aux commentaires disponibles : + +```py +for _, row in samples_df.iterrows(): + print(f"COMMENT: {row.comments}") + print(f"SCORE: {row.scores}") + print(f"TITLE: {row.title}") + print(f"URL: {row.html_url}") + print("=" * 50) + print() +``` + +```python out +""" +COMMENT: Requiring online connection is a deal breaker in some cases unfortunately so it'd be great if offline mode is added similar to how `transformers` loads models offline fine. + +@mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like? +SCORE: 25.505046844482422 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: The local dataset builders (csv, text , json and pandas) are now part of the `datasets` package since #1726 :) +You can now use them offline +\`\`\`python +datasets = load_dataset("text", data_files=data_files) +\`\`\` + +We'll do a new release soon +SCORE: 24.555509567260742 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: I opened a PR that allows to reload modules that have already been loaded once even if there's no internet. + +Let me know if you know other ways that can make the offline mode experience better. I'd be happy to add them :) + +I already note the "freeze" modules option, to prevent local modules updates. It would be a cool feature. + +---------- + +> @mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like? + +Indeed `load_dataset` allows to load remote dataset script (squad, glue, etc.) but also you own local ones. +For example if you have a dataset script at `./my_dataset/my_dataset.py` then you can do +\`\`\`python +load_dataset("./my_dataset") +\`\`\` +and the dataset script will generate your dataset once and for all. + +---------- + +About I'm looking into having `csv`, `json`, `text`, `pandas` dataset builders already included in the `datasets` package, so that they are available offline by default, as opposed to the other datasets that require the script to be downloaded. +cf #1724 +SCORE: 24.14896583557129 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: > here is my way to load a dataset offline, but it **requires** an online machine +> +> 1. (online machine) +> +> ``` +> +> import datasets +> +> data = datasets.load_dataset(...) +> +> data.save_to_disk(/YOUR/DATASET/DIR) +> +> ``` +> +> 2. copy the dir from online to the offline machine +> +> 3. (offline machine) +> +> ``` +> +> import datasets +> +> data = datasets.load_from_disk(/SAVED/DATA/DIR) +> +> ``` +> +> +> +> HTH. + + +SCORE: 22.893993377685547 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: here is my way to load a dataset offline, but it **requires** an online machine +1. (online machine) +\`\`\` +import datasets +data = datasets.load_dataset(...) +data.save_to_disk(/YOUR/DATASET/DIR) +\`\`\` +2. copy the dir from online to the offline machine +3. (offline machine) +\`\`\` +import datasets +data = datasets.load_from_disk(/SAVED/DATA/DIR) +\`\`\` + +HTH. +SCORE: 22.406635284423828 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== +""" +``` + +Pas mal! Notre deuxième résultat semble correspondre à la requête. + + + +✏️ **Essayez-le !** Créez votre propre requête et voyez si vous pouvez trouver une réponse dans les documents récupérés. Vous devrez peut-être augmenter le paramètre `k` dans `Dataset.get_nearest_examples()` pour élargir la recherche. + + \ No newline at end of file diff --git a/chapters/fr/chapter5/7.mdx b/chapters/fr/chapter5/7.mdx new file mode 100644 index 000000000..a4da397e5 --- /dev/null +++ b/chapters/fr/chapter5/7.mdx @@ -0,0 +1,11 @@ +# 🤗 Datasets, vérifié ! + +Eh bien, ce fut toute une visite de la 🤗 Datasets -- félicitations pour être arrivé jusqu'ici ! Avec les connaissances que vous avez acquises dans ce chapitre, vous devriez être en mesure de : + +- Chargez des ensembles de données de n'importe où, que ce soit le Hugging Face Hub, votre ordinateur portable ou un serveur distant de votre entreprise. +- Démêlez vos données en utilisant un mélange des fonctions `Dataset.map()` et `Dataset.filter()`. +- Basculez rapidement entre les formats de données comme Pandas et NumPy en utilisant `Dataset.set_format()`. +- Créez votre propre ensemble de données et transférez-le vers le Hugging Face Hub. +- Intégrez vos documents à l'aide d'un modèle Transformer et créez un moteur de recherche sémantique à l'aide de FAISS. + +Dans le [Chapitre 7](/course/chapter7), nous mettrons tout cela à profit en approfondissant les principales tâches de la PNL pour lesquelles les modèles Transformer sont parfaits. Avant de vous lancer, mettez à l'épreuve vos connaissances sur 🤗 Datasets avec un quiz rapide ! diff --git a/chapters/fr/chapter5/8.mdx b/chapters/fr/chapter5/8.mdx new file mode 100644 index 000000000..19bb1d08a --- /dev/null +++ b/chapters/fr/chapter5/8.mdx @@ -0,0 +1,226 @@ + + +# Quiz de fin de chapitre + +Ce chapitre a couvert beaucoup de terrain! Ne vous inquiétez pas si vous n'avez pas saisi tous les détails ; les prochains chapitres vous aideront à comprendre comment les choses fonctionnent sous le capot. + +Avant de poursuivre, testons ce que vous avez appris dans ce chapitre. + +### 1. La fonction `load_dataset()` dans 🤗 Datasets vous permet de charger un jeu de données depuis lequel des emplacements suivants ? + +data_files de load_dataset() pour charger les jeux de données locaux.", + correct: true + }, + { + text: "Le hub du visage étreignant", + explain: "Correct! Vous pouvez charger des ensembles de données sur le Hub en fournissant l'ID de l'ensemble de données, par ex. load_dataset('emotion').", + correct: true + }, + { + text: "Un serveur distant", + explain: "Correct! Vous pouvez passer des URL à l'argument data_files de load_dataset() pour charger des fichiers distants.", + correct: true + }, + ]} +/> + +### 2. Supposons que vous chargiez l'une des tâches GLUE comme suit : + +```py +from datasets import load_dataset + +dataset = load_dataset("glue", "mrpc", split="train") +``` + +Laquelle des commandes suivantes produira un échantillon aléatoire de 50 éléments à partir de `dataset` ? + +dataset.sample(50)", + explain: "Ceci est incorrect -- il n'y a pas de méthode Dataset.sample()." + }, + { + text: "dataset.shuffle().select(range(50))", + explain: "Correct! Comme vous l'avez vu dans ce chapitre, vous mélangez d'abord l'ensemble de données, puis sélectionnez les échantillons à partir de celui-ci.", + correct: true + }, + { + text: "dataset.select(range(50)).shuffle()", + explain: "Ceci est incorrect - bien que le code s'exécute, il ne mélange que les 50 premiers éléments de l'ensemble de données." + } + ]} +/> + +### 3. Supposons que vous disposiez d'un ensemble de données sur les animaux domestiques appelé "pets_dataset", qui comporte une colonne "name" indiquant le nom de chaque animal. Parmi les approches suivantes, laquelle vous permettrait de filtrer l'ensemble de données pour tous les animaux dont le nom commence par la lettre "L" ? + +pets_dataset.filter(lambda x : x['name'].startswith('L'))", + explain: "Correct! L'utilisation d'une fonction Python lambda pour ces filtres rapides est une excellente idée. Pouvez-vous penser à une autre solution?", + correct: true + }, + { + text: "pets_dataset.filter(lambda x['name'].startswith('L'))", + explain: "Ceci est incorrect -- une fonction lambda prend la forme générale lambda *arguments* : *expression*, vous devez donc fournir des arguments dans ce cas." + }, + { + text: "Créez une fonction comme def filter_names(x): return x['name'].startswith('L') et exécutez pets_dataset.filter(filter_names).", + explain: "Correct! Tout comme avec Dataset.map(), vous pouvez passer des fonctions explicites à Dataset.filter(). Ceci est utile lorsque vous avez une logique complexe qui ne convient pas à une fonction lambda courte. Parmi les autres solutions, laquelle fonctionnerait ?", + correct: true + } + ]} +/> + +### 4. Qu'est-ce que la cartographie mémoire ? + + + +### 5. Parmi les éléments suivants, lesquels sont les principaux avantages du mappage mémoire ? + + + +### 6. Pourquoi le code suivant échoue-t-il ? + +```py +from datasets import load_dataset + +dataset = load_dataset("allocine", streaming=True, split="train") +dataset[0] +``` + +IterableDataset.", + explain: "Correct! Un IterableDataset est un générateur, pas un conteneur, vous devez donc accéder à ses éléments en utilisant next(iter(dataset)).", + correct: true + }, + { + text: "L'ensemble de données allocine n'a pas de division train.", + explain: "Ceci est incorrect - consultez la [fiche de l'ensemble de données allocine](https://huggingface.co/datasets/allocine) sur le Hub pour voir quelles divisions elle contient." + } + ]} +/> + +### 7. Parmi les avantages suivants, lesquels sont les principaux avantages de la création d'une fiche d'ensemble de données ? + + + + +### 8. Qu'est-ce que la recherche sémantique ? + + + +### 9. Pour la recherche sémantique asymétrique, vous avez généralement : + + + +### 10. Puis-je utiliser 🤗 Datasets pour charger des données à utiliser dans d'autres domaines, comme le traitement de la parole ? + +MNIST dataset sur le Hub pour un exemple de vision par ordinateur." + }, + { + text: "Oui", + explain: "Correct! Découvrez les développements passionnants avec la parole et la vision dans la bibliothèque 🤗 Transformers pour voir comment 🤗 Datasets est utilisé dans ces domaines.", + correct : true + }, + ]} +/> diff --git a/chapters/ko/_toctree.yml b/chapters/ko/_toctree.yml new file mode 100644 index 000000000..fe4e110ef --- /dev/null +++ b/chapters/ko/_toctree.yml @@ -0,0 +1,29 @@ +- title: 0. 초기 설정 + sections: + - local: chapter0/1 + title: 강의 소개 + +- title: 1. 트랜스포머 모델 + sections: + - local: chapter1/1 + title: 단원 소개 + - local: chapter1/2 + title: 자연어 처리 + - local: chapter1/3 + title: 트랜스포머로 무엇을 할 수 있나요? + - local: chapter1/4 + title: 트랜스포머는 어떻게 동작하나요? + - local: chapter1/5 + title: 인코더 모델 + - local: chapter1/6 + title: 디코더 모델 + - local: chapter1/7 + title: 시퀀스-투-시퀀스 모델 + - local: chapter1/8 + title: 편향과 한계 + - local: chapter1/9 + title: 단원 정리 + - local: chapter1/10 + title: 단원 마무리 퀴즈 + quiz: 1 + \ No newline at end of file diff --git a/chapters/ko/chapter0/1.mdx b/chapters/ko/chapter0/1.mdx new file mode 100644 index 000000000..0a8dd7735 --- /dev/null +++ b/chapters/ko/chapter0/1.mdx @@ -0,0 +1,110 @@ +# 강의 소개 + +Hugging Face 강의에 오신 여러분들 환영합니다! 이번 강의 소개에서는 작업 환경 설정에 대해 안내드리겠습니다. 방금 막 이번 과정을 시작하셨다면 먼저 [Chapter 1](/course/chapter1) 내용을 살펴보고 돌아오신 뒤, 환경을 설정하여 코드를 직접 실행해보시길 추천드립니다. + +이번 과정에서 사용할 모든 라이브러리는 파이썬 패키지를 통해 사용할 수 있으므로 여기서는 파이썬 환경 설정 방법 및 필요한 라이브러리 설치 방법을 보여드리겠습니다. + +작업 환경 설정 방법으로 Colab 노트북을 이용한 방법과 파이썬 가상 환경을 이용한 방법, 두 가지를 다룰 것이고 둘 중 더 마음이 가는 방식을 자유롭게 선택하셔도 됩니다. 입문자의 경우 Colab 노트북을 이용하시길 강력하게 추천합니다. + +여기서 Windows 환경에 대해서는 다루지 않기 때문에 Windows에서 실행 중이시면 Colab 노트북을 이용해 아래 과정을 따라가 주시길 권장드립니다. Linux 혹은 macOS를 실행 중이시라면 어떤 방식을 택해도 무방합니다. + +대부분의 강의는 여러분이 Hugging Face 계정이 있다는 것을 전제로 하기 때문에 지금 바로 계정을 생성하시길 추천드립니다: [계정 생성하기](https://huggingface.co/join) + +## Google Colab 노트북 사용하기 + +Colab 노트북은 가장 쉬운 설정 방식입니다. 브라우저에 Colab 노트북을 켜고 바로 코딩을 시작하시면 됩니다! + +Colab에 익숙하지 않으시다면 [introduction](https://colab.research.google.com/notebooks/intro.ipynb) 링크를 따라 시작하시길 권장드립니다. Colab에서는 GPU, TPU와 같은 가속 하드웨어를 사용할 수 있으며 적은 양의 워크로드에 대해서는 무료입니다. + +Colab과 친숙해 지셨다면 새로운 노트북을 생성하여 아래와 같이 시작합니다: + +
+An empty colab notebook +
+ +다음으로, 이번 강의에서 사용할 라이브러리를 설치합니다. 설치에는 파이썬 패키지 관리자인 `pip` 를 사용하도록 하겠습니다. 노트북 파일에서는 시스템 명령어 앞에 `!` 를 붙여 실행시킬 수 있으므로, 아래와 같이 🤗 Transformers 라이브러리를 설치할 수 있습니다: + +``` +!pip install transformers +``` + +이제 파이썬 런타임에 패키지를 가져와 패키지가 제대로 설치되었는지 확인해보겠습니다: + +``` +import transformers +``` + +
+A gif showing the result of the two commands above: installation and import +
+ +위의 방식으로는 아주 가벼운 버전의 🤗 Transformers가 설치되고, 이는 PyTorch나 TensorFlow와 같은 특정 기계학습 프레임워크를 포함하지 않습니다. 하지만 본 강의에서는 이 라이브러리의 아주 다양한 기능들을 사용할 예정이므로, 아래의 명령어를 통해 대부분의 예제에 필요한 종속성(dependency)을 제공하는 개발 버전을 설치하시길 바랍니다: + +``` +!pip install transformers[sentencepiece] +``` + +설치에 시간이 조금 걸리지만 곧 강의를 위한 준비가 모두 끝납니다! + +## 파이썬 가상 환경 사용하기 + +파이썬 가상 환경 사용을 원하신다면 먼저 파이썬을 설치해야 합니다. 이 [가이드](https://realpython.com/installing-python/)를 따라 설치를 진행하실 수 있습니다. + +파이썬 설치가 완료되면 터미널에서 파이썬 명령어를 실행할 수 있습니다. 다음 단계로 넘어가기 전에, 다음과 같은 명령어를 실행하여 설치가 잘 되었는지 확인하세요: `python --version`. 이 때 시스템에 사용할 수 있는 파이썬 버전을 출력되어야 합니다. + +터미널에서 `python --version` 과 같은 파이썬 명령어를 실행하면, 명령어를 실행하는 프로그램을 시스템의 “메인(main)” 파이썬으로 생각해야 합니다. 이 메인 파이썬은 어떤 패키지도 설치하지 않은 상태로 유지하면서, 작업 중인 각 어플리케이션마다 별도의 환경을 생성하여 이용하는 것을 권장합니다. 이렇게 하면, 각 어플리케이션은 각각의 의존성 및 패키지를 갖게 되어 다른 어플리케이션과의 잠재적 호환성 문제를 피할 수 있습니다. + +파이썬에서 이는 [*가상 환경*](https://docs.python.org/3/tutorial/venv.html)을 통해 완수됩니다. 가상 환경은 자체 포함 디렉토리 트리로, 각 트리는 어플리케이션에게 필요한 모든 패키지와 함께 특정 파이썬 버전에 대한 파이썬 설치를 포함합니다. 이러한 가상 환경을 생성하는 방법은 여러 툴을 통해 할 수 있지만, 여기서는 공식 파이썬 패키지인 `[venv](https://docs.python.org/3/library/venv.html#module-venv)` 를 통해 생성해 보겠습니다. + +먼저, 어플리케이션을 넣어줄 디렉토리를 생성합니다. 예를 들어, 홈 디렉토리의 *transformers-course*와 같은 이름의 디렉토리를 만들어 봅시다: + +``` +mkdir ~/transformers-course +cd ~/transformers-course +``` + +디렉토리 내부에서, 파이썬 `venv` 모듈을 사용하여 가상 환경을 생성합니다: + +``` +python -m venv .env +``` + +원래 아무것도 없던 빈 폴더에 *.env*라는 디렉토리가 생기게 됩니다: + +``` +ls -a +``` + +```out +. .. .env +``` + +`activate` 스크립트를 통해 가상 환경으로 접속할 수 있고, `deactivate` 를 통해 가상 환경 밖으로 나올 수 있습니다: + +``` +# Activate the virtual environment +source .env/bin/activate + +# Deactivate the virtual environment +source .env/bin/deactivate +``` + +환경이 제대로 활성화 되었는지 `which python` 명령어를 실행하여 확인해 봅시다. 아래와 같이 가상 환경을 보여준다면 제대로 활성화가 것입니다! + +``` +which python +``` + +```out +/home//transformers-course/.env/bin/python +``` + +### 의존성(dependencies) 설치하기 + +Google Colab 사용법에서와 마찬가지로 다음 단계로 넘어가기 위해 패키지를 설치해야 합니다. 여기서도, `pip` 패키지 관리자를 통해 🤗 Transformers 개발 버전을 설치할 수 있습니다: + +``` +pip install "transformers[sentencepiece]" +``` + +이제 모든 환경 설정을 마치고 시작할 준비가 되었습니다! diff --git a/chapters/ko/chapter1/1.mdx b/chapters/ko/chapter1/1.mdx new file mode 100644 index 000000000..3bfb81f49 --- /dev/null +++ b/chapters/ko/chapter1/1.mdx @@ -0,0 +1,52 @@ +# 단원 소개 + +## 🤗 강의 수강생 여러분 환영합니다! + + + +이번 강의에서는 [Hugging Face](https://huggingface.co/) 환경의 라이브러리([🤗 Transformers](https://github.com/huggingface/transformers), [🤗 Datasets](https://github.com/huggingface/datasets), [🤗 Tokenizers](https://github.com/huggingface/tokenizers), [🤗 Accelerate](https://github.com/huggingface/accelerate))와 [Hugging Face Hub](https://huggingface.co/models) 를 이용해 자연어 처리(NLP)에 대해 배워보겠습니다. (무료 강의에 광고도 없는건 비밀입니다!) + +## 무엇을 배우나요? + +강의 개요 훑어보기: + +
+Brief overview of the chapters of the course. + +
+ +- 챕터 1~4에서는 🤗 Transformers 라이브러리의 핵심 개념에 대해 소개합니다. 이 부분을 마치면 트랜스포머 모델의 동작 원리를 이해하실 수 있고, [Hugging Face Hub](https://huggingface.co/models)에서 모델을 사용하여 데이터셋으로 미세 조정(fine-tune)한 후 Hub에 모델을 공유하는 방법까지 터득하게 될 것입니다! +- 챕터 5~8은 본격적으로 고전 NLP 업무를 수행하기 앞서, 🤗 Datasets와 🤗 Tokenizers의 기초에 대해 알아봅니다. 이 부분을 모두 학습하시면 일반적인 NLP 문제를 스스로 해낼 수 있게 됩니다. +- 챕터 9~12에서는 트랜스포머 모델이 NLP 문제를 넘어, 음성 처리 및 컴퓨터 비전에 어떻게 활용되는지 탐구합니다. 이 과정에서 모델 데모를 구축하고 공유하는 방법과 이를 프로덕션 환경에 최적화하는 방법을 공부합니다. 이러한 과정을 거쳐서, 여러분들은 거의 모든 기계 학습(머신 러닝) 문제에 🤗 Transformers를 적용할 준비를 갖추게 됩니다! + +이번 강의는: + +* 파이썬에 대한 기초 지식이 필요합니다 +* [DeepLearning.AI](https://www.deeplearning.ai/) 의 프로그램이나 [fast.ai's](https://www.fast.ai/) [Practical Deep Learning for Coders](https://course.fast.ai/) 와 같은 딥러닝에 대한 기초 강의를 듣고 수강하면 더욱 효과적입니다 +* [PyTorch](https://pytorch.org/) , [TensorFlow](https://www.tensorflow.org/) 에 대한 선수 지식이 필요하지는 않지만, 이에 익숙하시다면 도움이 될 것입니다 + +본 강의를 모두 수강한 후, DeepLearning.AI의 [Natural Language Processing Specialization](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh)을 학습하시길 권장드립니다. 해당 과정에서는 Naive Bayes, LSTM과 같은 알아두면 너무나 유용한 더 넓은 범위의 전통 NLP 모델에 대해 학습할 수 있습니다! + +## 우리가 누구일까요? + +저자 소개: + +**Matthew Carrigan**은 Hugging Face의 머신 러닝 엔지니어입니다. 현재 아일랜드 더블린에 살고 있으며, 이전에는 [Parse.ly](http://parse.ly/) 에서 ML 엔지니어로, 그 전에는 Trinity Collge Dublin에서 박사 과정 이후 연구원으로 근무했습니다. 사람이 기존 인공지능 아키텍쳐를 확장하여 사람 수준에는 도달하지 못할거라고 생각하지만, 그럼에도 불멸 로봇(immortality robot)에 대해 큰 기대를 갖고 있습니다. + +**Lysandre Debut**는 Hugging Face의 머신 러닝 엔지니어이며 초창기부터 🤗 Transformers 라이브러리 작업을 함께 했습니다. 아주 사용하기 쉬운 API를 개발하여 모두가 NLP를 쉽게 사용할 수 있도록 하는 목표를 갖고 있습니다. + +**Sylvain Gugger**는 Hugging Face의 리서치 엔지니어로 🤗 Transformers 라이브러리의 주요 관리자 중 한명입니다. 이전에 [fast.ai](http://fast.ai/) 에서 리서치 사이언티스트로 있었으며 Jeremy Howard와 함께 *[Deep Learning for Coders with fastai and PyTorch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)* 를 저술했습니다. 적은 리소스에서도 모델이 빠르게 학습되도록 기술을 디자인하고 개선하여 딥러닝에 보다 쉽게 접근할 수 있도록 하는 것을 리서치의 가장 큰 목표로 삼고 있습니다. + +**Merve Noyan**은 Hugging Face의 개발자 애드보케이트로, 모두에게 평등한 민주적인 머신 러닝 생태계를 만드는 목표를 갖고 있으며, 개발툴 작업 및 주변 컨텐츠 구축 작업을 담당하고 있습니다. + +**Lucile Saulnier**은 Hugging Face의 ML 엔지니어로 오픈 소스 툴 사용에 대한 개발 및 지원을 담당합니다. 자연어 처리 분야에서 협업 학습, BigScience등과 같은 다양한 리서치 프로젝트에도 활발히 참여하고 있습니다. + +**Lewis Tunstall**는 Hugging Face의 ML 엔지니어로 오픈 소스 툴을 개발하여 더 많은 커뮤니티에 상용화되도록 하는 데에 초점을 맞추고 있습니다. 곧 출간되는 [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/)의 공저자이기도 합니다. + +**Leandro von Werra**는 Hugging Face 오픈소스 팀의 머신 러닝 엔지니어이자 곧 출간될 [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/)의 공동 저자입니다. 모든 머신 러닝 스택에서의 작업을 통해 수 년간 NLP 프로젝트를 프로덕션으로 들여온 경력자입니다. + +시작할 준비가 되셨나요? 이번 챕터에서 다룰 내용은 다음과 같습니다: + +- 텍스트 생성 및 분류와 같은 NLP 문제를 푸는 `pipeline()` 함수 사용법 +- 트랜스포머 모델 구조 +- 인코더(encoder), 디코더(decoder), 인코더-디코더(encoder-decoder)의 구조와 용례 \ No newline at end of file diff --git a/chapters/ko/chapter1/10.mdx b/chapters/ko/chapter1/10.mdx new file mode 100644 index 000000000..a05bd4c3b --- /dev/null +++ b/chapters/ko/chapter1/10.mdx @@ -0,0 +1,254 @@ + + +# 단원 마무리 퀴즈 + +이번 챕터에서는 정말 많은 내용들을 다뤘습니다! 그러니 모든 세부 사항을 다 이해하지 못했다고 해서 좌절하지 마세요. 다음 챕터에서 다루는 내용은 내부 작동 방식을 이해하는 데에 도움이 될거에요. + +그래도 우선, 이번 챕터에서 배운 내용에 대해 확인해보는 시간을 갖도록 하겠습니다! + + +### 1. Hub에서 `roberta-large-mnli` 체크포인트를 검색해 보세요. 이 모델은 어떤 작업을 수행하나요? + + +roberta-large-mnli page." + }, + { + text: "텍스트 분류", + explain: "더 정확하게 말하면, 이 모델은 두 문장이 논리적으로 타당한지 세 가지 레이블(모순, 함의, 중립)로 분류합니다. 이러한 문제를 자연어 추론(natural language inference)이라고 부릅니다.", + correct: true + }, + { + text: "텍스트 생성", + explain: "이 페이지를 다시 확인하세요 roberta-large-mnli page." + } + ]} +/> + +### 2. 다음 코드는 무엇을 반환하나요? + +```py +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +sentiment-analysis 파이프라인에 대한 설명입니다." + }, + { + text: "이 문장을 완성할, 생성 텍스트를 반환합니다.", + explain: "오답입니다 — 이는 text-generation 파이프라인에 대한 설명입니다.", + }, + { + text: "사람, 기관, 장소 등을 나타내는 단어들을 반환합니다.", + explain: "이 뿐만 아니라, grouped_entities=True를 사용해 \"Hugging Face\"와 같이 같은 개체에 해당하는 단어들을 그룹화해줍니다.", + correct: true + } + ]} +/> + +### 3. 다음 예제 코드에서 ... 대신 무엇이 들어가야 할까요? + +```py +from transformers import pipeline + +filler = pipeline("fill-mask", model="bert-base-cased") +result = filler("...") +``` + +", + explain: "오답입니다. 여기 bert-base-cased 모델 카드를 보시고 다시 도전해보세요." + }, + { + text: "[MASK]", + explain: "정답! 이 모델의 마스크 토큰은 [MASK]입니다.", + correct: true + }, + { + text: "man", + explain: "오답입니다. 이 파이프라인은 마스킹된 단어를 채워야하기니까 어딘가에는 마스크 토큰이 있어야겠죠?" + } + ]} +/> + +### 4. 다음 코드가 실행되지 않는 이유는 무엇일까요? + +```py +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +result = classifier("This is a course about the Transformers library") +``` + +candidate_labels=[...].", + correct: true + }, + { + text: "한 문장이 아니라, 여러 문장을 파이프라인에 넣어주어야 합니다.", + explain: "틀렸지만, 다른 파이프라인과 마찬가지로 제대로 사용한다면 물론 여러 리스트의 문장도 입력으로 넣어줄 수 있습니다." + }, + { + text: "늘 그렇듯 🤗 Transformers 라이브러리가 또 고장난거 아닌가요?", + explain: "못 들은 걸로 하겠습니다!" + }, + { + text: "위의 문장은 너무 짧아서, 더 긴 문장을 입력해야 합니다.", + explain: "오답입니다. 매우 긴 텍스트는 파이프라인에서 처리할 때 잘리게 되는 것을 명심하세요." + } + ]} +/> + +### 5. "전이 학습(transfer learning)"이란 무엇을 의미하나요? + + + +### 6. 언어 모델은 일반적으로 사전 학습시에 레이블을 필요로 하지 않습니다. 이 문장은 참일까요 거짓일까요? + + +자가 지도(self-supervised) 방식입니다. 이는 다음 단어 예측 혹은 마스킹 된 단어 채우기 등과 같이 입력으로부터 자동으로 레이블을 생성하는 것을 의미합니다.", + correct: true + }, + { + text: "거짓", + explain: "정답이 아닙니다." + } + ]} +/> + +### 7. 다음 중 “모델(model)”, “구조(architecture)”, “가중치(weights)”에 대해 가장 잘 설명한 것을 고르세요. + + + + +### 8. 다음 중 어떤 모델이 텍스트를 생성하여 프롬프트(prompt)를 완성시키는 데에 가장 적합할까요? + + + +### 9. 다음 중 어떤 모델이 텍스트 요약에 가장 적합할까요? + + + +### 10. 다음 중 어떤 모델이 입력 텍스트를 특정 레이블로 분류하는 데에 가장 적합할까요? + + + +### 11. 다음 중 모델이 편향성(bias)을 갖게 되는 데에 가장 가능성 있는 원인을 모두 고르세요. + + diff --git a/chapters/ko/chapter1/2.mdx b/chapters/ko/chapter1/2.mdx new file mode 100644 index 000000000..ca5d62dc8 --- /dev/null +++ b/chapters/ko/chapter1/2.mdx @@ -0,0 +1,21 @@ +# 자연어 처리(Natural Language Processing) + +트랜스포머 모델을 공부하기에 앞서 자연어 처리(NLP)가 무엇인지, 그리고 왜 NLP가 중요한지 빠르고 간단하게 살펴보겠습니다. + +## NLP가 무엇인가요? + +NLP(Natural Language Processing)란 사람의 언어와 관련된 모든 것을 이해하는 데에 중점을 둔 언어학 및 기계 학습(머신 러닝) 분야를 말합니다. NLP의 목적은 단순히 하나의 개별 단어를 이해하는 것을 넘어, 해당 단어들의 문맥을 이해하는 것입니다. + +아래는 가장 일반적인 NLP 작업과 그 예시입니다: + +- **전체 문장 분류**: 리뷰에 드러난 감정 파악하기, 스팸 메일 분류하기, 문장이 문법적으로 올바른지 혹은 문장 쌍이 논리적으로 관련이 있는지 없는지 결정하기 +- **문장 내 단어 분류**: 문장 구성 성분(명사, 동사, 형용사 등) 혹은 개체명(사람, 장소, 기관) 식별하기 +- **텍스트 컨텐츠 생성**: 자동 생성 텍스트로 프롬프트 작성하기, 텍스트 내 마스킹 된 단어의 빈칸 채우기 +- **텍스트 안에서 정답 추출하기**: 지문과 질의가 주어질 때 지문에 주어진 정보를 이용해 질의에 대한 정답 추출하기 +- **입력 텍스트로부터 새로운 문장 생성하기**: 입력 텍스트를 다른 언어로 번역하거나, 요약하기 + +하지만 NLP는 위와 같은 텍스트 처리에만 제한되지 않습니다. NLP에서는 오디오 샘플의 스크립트 생성 및 이미지의 설명문 생성과 같이 음성 인식과 컴퓨터 비전 분야에서의 까다로운 문제 또한 다룹니다. + +## 왜 NLP가 어렵나요? + +컴퓨터와 사람은 서로 정보를 처리하는 방식이 다릅니다. 이를테면, “나는 배고파”라는 문장을 읽을 때 우리는 바로 그 의미를 이해할 수 있습니다. 마찬가지로 사람은 “나는 배고파”, “나 슬퍼”와 같은 문장 쌍이 주어질 때, 두 문장이 얼마나 유사한지 쉽게 판단할 수 있습니다. 그러나, 기계 학습 모델은 사람만큼 이를 쉽게 할 수 없습니다. 우선 모델이 텍스트를 학습할 수 있도록 텍스트가 처리 과정을 거쳐야 하는데, 사람의 언어 체계는 매우 복잡하기 때문에 이러한 처리가 어떻게 이루어져야 하는지 면밀히 고민해야 합니다. 따라서 텍스트 표현 방법과 관련한 수많은 연구가 진행되어 왔고, 다음 챕터에서 그 중 몇 가지 방법들을 소개해드리겠습니다. \ No newline at end of file diff --git a/chapters/ko/chapter1/3.mdx b/chapters/ko/chapter1/3.mdx new file mode 100644 index 000000000..f32892430 --- /dev/null +++ b/chapters/ko/chapter1/3.mdx @@ -0,0 +1,328 @@ +# 트랜스포머로 무엇을 할 수 있나요? + + + +이번 장에서는 트랜스포머(Transformer) 모델을 사용해 무엇을 할 수 있는지 같이 살펴보고, 🤗 Transformers 라이브러리 툴의 첫 사용을 `pipeline()` 함수와 함께 시작하겠습니다. + + +👀 오른쪽 상단에 Open in Colab 버튼이 보이시나요? 버튼을 클릭하면 이번 장에서 사용한 모든 코드 샘플들을 Google Colab notebook을 통해 열 수 있습니다. 이런 버튼을 예제 코드를 포함하는 모든 단원에서 발견하실 수 있습니다. + +로컬 환경에서 예제 코드를 실행하려면 setup을 살펴보세요. + + +## 트랜스포머는 어디에나 있어요! + +트랜스포머 모델은 이전 단원에서 언급한 작업과 같은 모든 NLP 문제를 해결하기 위해 사용됩니다. 아래와 같이 Hugging Face와 트랜스포머 모델을 이용하고 다시 모델을 공유하여 커뮤니티에 기여하는 많은 기업과 기관이 있습니다: + +Companies using Hugging Face + +[🤗 Transformers 라이브러리](https://github.com/huggingface/transformers)는 이렇게 공유한 모델을 사용하고 구축하는 기능들을 제공합니다. [Model Hub](https://huggingface.co/models)에서는 모두가 다운로드 받아 쓸 수 있는 수 천 개의 사전 학습된 모델들이 여러분을 기다리고 있습니다. 여러분만의 모델을 Hub에 업로드하는 것 또한 가능합니다! + + +⚠️ The Hugging Face Hub에는 트랜스포머 모델만 있지 않아요. 누구든지 어떠한 종류의 모델이나 데이터를 공유할 수 있습니다! Create a huggingface.co 링크에서 계정을 만들고 모든 기능을 사용해보세요! + + +트랜스포머 모델 안에서 무슨 일이 벌어지는지 알아보기 전에, 트랜스포머가 NLP 문제 해결에 어떻게 사용되는지 몇 가지 흥미로운 예시들을 살펴보겠습니다. + +## 파이프라인으로 작업하기 + + + +🤗 Transformers 라이브러리의 가장 기본 객체는 `pipeline()` 함수입니다. 이 함수는 모델에 있어서 필수 과정인 전처리와 후처리 과정을 모델과 연결하고, 우리가 바로 어떠한 텍스트 입력을 넣든 원하는 답을 얻을 수 있도록 합니다: + +```python +from transformers import pipeline + +classifier = pipeline("sentiment-analysis") +classifier("I've been waiting for a HuggingFace course my whole life.") +``` + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}] +``` + +아래와 같이 여러 문장을 함께 넣을 수도 있습니다! + +```python +classifier( + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!"] +) +``` + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}, + {'label': 'NEGATIVE', 'score': 0.9994558095932007}] +``` + +기본적으로 이 파이프라인은 영어 감정 분석에 미세 조정(fine-tune)된 사전 학습 모델을 선택하여 넣게 됩니다. 여기서 `classifier` 객체를 생성할 때 모델이 다운로드 되며 캐싱(caching)이 이루어지때문에, 재실행 시에는 캐싱된 모델을 사용하게 되어 모델을 다시 다운로드 하지 않습니다. + +텍스트를 파이프라인에 넣을 때 다음과 같은 세 가지 주요 과정을 거칩니다: + +1. 텍스트가 모델이 이해할 수 있는 형태로 전처리 과정을 거칩니다. +2. 전처리된 입력이 모델 입력으로 들어갑니다. +3. 모델의 예측값이 후처리를 거쳐, 사람이 이해할 수 있는 형태로 반환됩니다. + + +현재까지 사용할 수 있는 파이프라인([available pipelines](https://huggingface.co/transformers/main_classes/pipelines.html))은 다음과 같습니다: + +- `feature-extraction` : 특징 추출 (텍스트에 대한 벡터 표현 추출) +- `fill-mask` : 마스크 채우기 +- `ner` : 개체명 인식 (named entity recognition) +- `question-answering` : 질의 응답 +- `sentiment-analysis` : 감정 분석 +- `summarization` : 요약 +- `text-generation` : 텍스트 생성 +- `translation` : 번역 +- `zero-shot-classification` : 제로샷 분류 + +이 중 몇 가지를 같이 살펴보도록 하겠습니다! + +## 제로샷 분류(Zero-shot classification) + +레이블이 없는 텍스트를 분류하는 더 까다로운 과제부터 시작하겠습니다. 텍스트에 레이블을 다는 것은 시간이 많이 소요되고 도메인 지식이 필요하기 때문에 이러한 작업은 실제 프로젝트에서 아주 흔한 상황입니다. 이러한 상황에서 `zero-shot-classification` 파이프라인은 매우 유용합니다. 제로샷 파이프라인은 사전 학습된 모델에 의존하지 않고도 분류 작업에 사용할 레이블을 특정할 수 있도록 합니다. 위의 예시에서 모델이 긍정(positive)과 부정(negative)의 두 레이블을 분류하는 샘플을 살펴보았는데, 제로샷 파이프라인을 통해서는 어떠한 레이블 세트에 대해서도 분류 작업을 수행할 수 있습니다. + +```python +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +classifier( + "This is a course about the Transformers library", + candidate_labels=["education", "politics", "business"], +) +``` + +```python out +{'sequence': 'This is a course about the Transformers library', + 'labels': ['education', 'business', 'politics'], + 'scores': [0.8445963859558105, 0.111976258456707, 0.043427448719739914]} +``` + +이러한 파이프라인이 제로샷(zero-shot)이라 불리는 이유는 여러분의 데이터에 맞춰 미세 조정(fine-tune)하지 않고도 바로 작업에 사용할 수 있기 때문입니다. 제로샷은 여러분이 원하는 어떠한 분류 레이블에 대해서도 확률 점수를 즉시 반환합니다. + + + +✏️ **직접 해보기!** 여러분이 직접 작성한 시퀀스와 레이블을 사용해 모델이 어떻게 동작하는지 확인해보세요. + + + + +## 텍스트 생성(Text generation) + +지금부터 파이프라인을 사용해 텍스트를 생성하는 방법을 알아보겠습니다. 여기서의 핵심은 프롬트를 모델에 제공하면 모델이 나머지 텍스트를 생성하여 이를 자동으로 완성하는 것입니다. 이는 스마트폰의 텍스트 자동 완성 기능과 유사합니다. 텍스트 생성에는 랜덤하게 결과를 생성하는 과정이 포함되어 있어서 여러분이 아래와 같이 동일하게 입력을 넣어도 매번 다른 결과가 나올 수 있습니다. + +```python +from transformers import pipeline + +generator = pipeline("text-generation") +generator("In this course, we will teach you how to") +``` + +```python out +[{'generated_text': 'In this course, we will teach you how to understand and use ' + 'data flow and data interchange when handling user data. We ' + 'will be working with one or more of the most commonly used ' + 'data flows — data flows of various types, as seen by the ' + 'HTTP'}] +``` + +`num_return_sequences`라는 인자(argument)를 통해 몇 개의 서로 다른 출력 결과를 생성할지 정할 수 있고, `max_length` 인자를 통해 출력 텍스트의 총 길이를 설정할 수 있습니다. + + + +✏️ **직접 해보기!** `num_return_sequences` 와 `max_length` 인자를 설정해 15개의 단어를 가진 서로 다른 두 개의 문장을 출력해보세요. + + + + +## 파이프라인에 Hub의 모델 적용하기 + +지금까지 예제들은 해당 작업에 대해 기본 모델들을 사용했지만, 특정 모델을 Hub에서 선택해 텍스트 생성과 같은 특정 작업에 대한 파이프라인에서도 사용할 수 있습니다. [Model Hub](https://huggingface.co/models) 페이지의 화면 왼쪽에 태그를 클릭하여 태그명에 해당하는 작업을 지원하는 모델을 확인할 수 있습니다. 이 때, [다음](https://huggingface.co/models?pipeline_tag=text-generation)과 같은 페이지로 이동하게 됩니다. + +함께 [`distilgpt2`](https://huggingface.co/distilgpt2) 모델을 사용해보겠습니다! 위 예제에서 사용한 파이프라인에서 모델을 아래와 같이 로드 할 수 있습니다: + +```python +from transformers import pipeline + +generator = pipeline("text-generation", model="distilgpt2") +generator( + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, +) +``` + +```python out +[{'generated_text': 'In this course, we will teach you how to manipulate the world and ' + 'move your mental and physical capabilities to your advantage.'}, + {'generated_text': 'In this course, we will teach you how to become an expert and ' + 'practice realtime, and with a hands on experience on both real ' + 'time and real'}] +``` + +언어 태그를 클릭하여 해당 언어를 지원하고 생성하는 모델을 보다 구체적으로 검색할 수 있습니다. Model Hub에는 다양한 언어를 처리하는 다국어 모델의 체크포인트(모델의 파라미터 값) 또한 포함하고 있습니다. + +모델을 클릭하면 온라인상에서 바로 사용 가능한 위젯을 확인할 수 있고, 이를 통해 모델을 직접 다운로드 받기 전에 모델의 기능을 빠르게 테스트 해볼 수 있습니다. + + + +✏️ **직접 해보기!** 영어를 제외한 다른 언어를 생성하는 모델을 검색해보세요. 위젯을 자유롭게 다뤄 보시고 파이프라인을 사용해보세요! + + + +### 추론(Inference) API + +모든 모델들은 [Hugging Face 웹사이트](https://huggingface.co/)에서 제공하는 추론 API를 통해 여러분의 브라우저상에서 직접 테스트할 수 있습니다. 이 페이지 링크로 접속해 직접 작성하신 텍스트를 입력하시면 모델의 입력 데이터를 처리 결과를 확인할 수 있습니다. + +위젯을 구동하는 추론 API는 간편한 워크플로우를 가능하게 하는 유료 버전의 제품으로도 이용 가능합니다. 자세한 사항은 [가격 정책 페이지](https://huggingface.co/pricing)를 참고해주세요. + +## 마스크 채우기(Mask filling) + +다음으로 사용해볼 파이프라인은 마스크 채우기(`fill-mask`)입니다. 이 작업의 핵심 아이디어는 주어진 텍스트의 빈칸을 채우기입니다: + +```python +from transformers import pipeline + +unmasker = pipeline("fill-mask") +unmasker("This course will teach you all about models.", top_k=2) +``` + +```python out +[{'sequence': 'This course will teach you all about mathematical models.', + 'score': 0.19619831442832947, + 'token': 30412, + 'token_str': ' mathematical'}, + {'sequence': 'This course will teach you all about computational models.', + 'score': 0.04052725434303284, + 'token': 38163, + 'token_str': ' computational'}] +``` + +상위 몇 개의 높은 확률을 띠는 토큰을 출력할지 `top_k` 인자를 통해 조절합니다. 여기서 모델이 특이한 `` 단어를 채우는 것을 주목하세요. 이를 마스크 토큰(mask token)이라고 부릅니다. 다른 마스크 채우기 모델들은 다른 형태의 마스크 토큰을 사용할 수 있기 때문에 다른 모델을 탐색할 때 항상 해당 모델의 마스크 단어가 무엇인지 확인해야 합니다. 위젯에서 사용되는 마스크 단어를 보고 이를 확인할 수 있습니다. + + + +✏️ **직접 해보기!** Hub에서 `bert-base-cased`를 검색해 보고 추론 API 위젯을 통해 모델의 마스크 단어가 무엇인지 확인해 보세요. 이 모델이 위의 `pipeline` 예제에서 사용한 문장에 대해 어떻게 예측하나요? + + + +## 개체명 인식(Named entity recognition) + +모델이 입력 텍스트의 어느 부분이 사람, 장소, 기관 등과 같은 개체에 해당하는지 찾는 작업을 개체명 인식(NER)이라고 합니다. 예제를 통해 확인해 봅시다: + +```python +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +```python out +[{'entity_group': 'PER', 'score': 0.99816, 'word': 'Sylvain', 'start': 11, 'end': 18}, + {'entity_group': 'ORG', 'score': 0.97960, 'word': 'Hugging Face', 'start': 33, 'end': 45}, + {'entity_group': 'LOC', 'score': 0.99321, 'word': 'Brooklyn', 'start': 49, 'end': 57} +] +``` + +모델이 정확하게 Sylvain을 사람(PER)으로, Hugging Face를 기관(ORG)으로, Brooklyn을 장소(LOC)으로 예측했네요! + +파이프라인을 생성하는 함수에 `grouped_entities=True` 옵션을 전달하면 파이프라인이 같은 개체에 해당하는 문장 부분을 다시 그룹화합니다. 이 옵션을 설정하면 모델은 여러 단어로 구성된 단어임에도 “Hugging”과 “Face”를 하나의 기관으로 정확히 분류하게 됩니다. 다음 챕터에서도 확인하겠지만, 놀랍게도 전처리 과정에서 각 단어들은 더 작은 부분으로 쪼개집니다. 예를 들어 `Sylvain` 이라는 단어는 `S`, `##yl`, `##va`, `##in` 이렇게 네 조각으로 쪼개집니다. 후처리 단계에서 파이프라인은 이 조각들을 멋지게 재그룹화합니다. + + + +✏️ **직접 해보기!** Model Hub에서 영어 품사 태깅(part-of-speech tagging, 줄여서 POS)이 가능한 모델을 찾아보세요. 이 모델이 위의 예시 문장으로 무엇을 예측하나요? + + + +## 질의 응답(Question-answering) + +질의 응답(`question-answering`) 파이프라인은 주어진 지문(context)의 정보를 활용하여 질문에 대한 답을 하는 태스크입니다: + +```python +from transformers import pipeline + +question_answerer = pipeline("question-answering") +question_answerer( + question="Where do I work?", + context="My name is Sylvain and I work at Hugging Face in Brooklyn", +) +``` + +```python out +{'score': 0.6385916471481323, 'start': 33, 'end': 45, 'answer': 'Hugging Face'} +``` + +본 파이프라인은 답을 새롭게 생성하는 방식이 아닌, 주어진 지문 내에서 정답을 추출하는 방식임을 잘 기억하세요. + +## 요약(Summarization) + +요약(Summarization)은 참조 텍스트의 모든(혹은 대부분의) 중요한 특징을 그대로 유지한 채 텍스트를 짧게 줄이는 작업입니다. 아래 예제를 확인하세요: + +```python +from transformers import pipeline + +summarizer = pipeline("summarization") +summarizer( + """ + America has changed dramatically during recent years. Not only has the number of + graduates in traditional engineering disciplines such as mechanical, civil, + electrical, chemical, and aeronautical engineering declined, but in most of + the premier American universities engineering curricula now concentrate on + and encourage largely the study of engineering science. As a result, there + are declining offerings in engineering subjects dealing with infrastructure, + the environment, and related issues, and greater concentration on high + technology subjects, largely supporting increasingly complex scientific + developments. While the latter is important, it should not be at the expense + of more traditional engineering. + + Rapidly developing economies such as China and India, as well as other + industrial countries in Europe and Asia, continue to encourage and advance + the teaching of engineering. Both China and India, respectively, graduate + six and eight times as many traditional engineers as does the United States. + Other industrial countries at minimum maintain their output, while America + suffers an increasingly serious decline in the number of engineering graduates + and a lack of well-educated engineers. +""" +) +``` + +```python out +[{'summary_text': ' America has changed dramatically during recent years . The ' + 'number of engineering graduates in the U.S. has declined in ' + 'traditional engineering disciplines such as mechanical, civil ' + ', electrical, chemical, and aeronautical engineering . Rapidly ' + 'developing economies such as China and India, as well as other ' + 'industrial countries in Europe and Asia, continue to encourage ' + 'and advance engineering .'}] +``` + +텍스트 생성에서와 마찬가지로 `max_length` 와 `min_length` 를 미리 설정할 수 있습니다. + +## 번역(Translation) + +번역(Translation)의 경우 `"translation_en_to_fr"` 와 같이 태스크명에 해당하는 언어 쌍을 넣어준다면 기본 모델을 사용할 수 있지만, 더 간단하게 [Model Hub](https://huggingface.co/models)에서 원하는 모델을 선택해 사용하는 방법이 있습니다. 아래에서는 프랑스어에서 영어로 번역을 시도해 보겠습니다: + +```python +from transformers import pipeline + +translator = pipeline("translation", model="Helsinki-NLP/opus-mt-fr-en") +translator("Ce cours est produit par Hugging Face.") +``` + +```python out +[{'translation_text': 'This course is produced by Hugging Face.'}] +``` + +텍스트 생성 및 요약에서와 마찬가지로, `max_length` 혹은 `min_length` 를 지정하여 결과를 출력할 수 있습니다. + + + +✏️ **직접 해보기!** 다른 언어를 지원하는 번역 모델을 검색해보고 위의 문장을 몇 가지 다른 언어들로 번역해 봅시다. + + + +지금까지 보여드린 파이프라인들은 대부분 특정 작업을 위해 프로그래밍된 데모용 파이프라인으로, 여러 태스크를 동시에 지원하지는 않습니다. 다음 단원에서는 `pipeline()` 함수 내부를 살펴보고 그 동작 방식을 직접 설계하는 방법에 대해 다루겠습니다. \ No newline at end of file diff --git a/chapters/ko/chapter1/4.mdx b/chapters/ko/chapter1/4.mdx new file mode 100644 index 000000000..96456fab5 --- /dev/null +++ b/chapters/ko/chapter1/4.mdx @@ -0,0 +1,171 @@ +# 트랜스포머는 어떻게 동작하나요? + +이번 단원에서는 트랜스포머(Transformer) 모델의 대략적인 구조를 알아보겠습니다. + +## 트랜스포머 역사 훑어보기 + +아래에 트랜스포머 모델의 (짧은) 역사 중 주요한 지점을 나타냈습니다: + +
+A brief chronology of Transformers models. + +
+ +[Transformer architecture](https://arxiv.org/abs/1706.03762)는 2017년 6월에 처음 소개되었습니다. 처음 연구 목적은 번역 작업 수행이었습니다. 이후로 다음과 같이 줄줄이 막강한 모델들이 세상에 등장했습니다: + +- **2018년 6월**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), 최초로 사전 학습된 트랜스포머 모델로 다양한 NLP 작업에 미세 조정(fine-tune)되도록 사용되었으며 SOTA(state-of-the-art) 성능 달성 + +- **2018년 10월**: [BERT](https://arxiv.org/abs/1810.04805), 또 다른 거대 사전 학습 언어 모델로, 더 좋은 문장 요약을 위해 설계 (이번 단원과 다음 단원에서 더 자세히 알아봐요!) + +- **2019년 2월**: [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), 더 좋은 성능(그리고 더 큰) 버전의 GPT로, 윤리적 문제로 인해 즉시 공개되지 못하였음 + +- **2019년 10월**: [DistilBERT](https://arxiv.org/abs/1910.01108), BERT의 60% 빠른 속도에 메모리 측면에서 40% 가볍지만 BERT 성능의 97%를 재현하는 경량화 버전의 BERT + +- **2019년 10월**: [BART](https://arxiv.org/abs/1910.13461) 와 [T5](https://arxiv.org/abs/1910.10683), 동일한 구조의 (처음으로) 원본 트랜스포머 모델의 구조를 그대로 따른 두 거대 사전학습 언어 모델 + +- **2020년 5월**, [GPT-3](https://arxiv.org/abs/2005.14165), 미세 조정 없이도 (zero-shot learning이라 부름) 다양한 작업을 훌륭하게 수행하는 GPT-2의 더 큰 버전 + +위 리스트는 단순히 여러 종류의 트랜스포머 모델들 중 몇몇을 강조하기 위한 목록입니다. 넓은 관점에서 트랜스포머 모델은 아래와 같이 세 개의 카테고리로 묶을 수 있습니다: + +- GPT-계열 (*Auto-regressive* 트랜스포머 모델로도 불림) +- BERT-계열 (*Auto-encoding* 트랜스포머 모델로도 불림) +- BART/T5-계열 (*Sequence-to-sequence* 트랜스포머 모델로도 불림) + +추후에 각 계열들에 대해 더 자세히 살펴보도록 하겠습니다. + +## 트랜스포머는 언어 모델입니다 + +위에 언급한 모델(GPT, BERT, BART, T5 등)들은 *언어 모델(language model)*로서 학습 되었습니다. 다르게 말하면 이 모델들은 스스로 지도하는 방식으로 수많은 텍스트에 대해 학습된 모델들입니다. 이러한 자가 지도 학습(self-supervised learning)은 학습의 목적이 모델 입력으로부터 자동으로 계산되는 방식을 말합니다. 결국 사람이 데이터에 레이블을 달지 않아도 학습이 가능한 것입니다! + +이러한 종류의 모델은 학습한 언어에 대해 통계 기반의 방식으로 이해를 하지만, 이는 몇몇 실생활 문제에 적합하지 않습니다. 그렇기 때문에 사전 학습된 모델은 *전이 학습(transfer learning)*이라 불리는 과정을 거칩니다. 이 과정에서 모델은 특정 작업에 맞춰 지도적(supervised)인 방법, 즉 사람이 레이블을 추가한 데이터를 사용하는 방법으로 미세 조정(fine-tune)이 이루어지는 단계를 거칩니다. + +하나의 예시로 문장 내에서 이전 *n*개의 단어를 읽고 다음에 올 단어를 에측하는 문제를 들 수 있습니다. 이를 과거와 현재의 입력 정보를 이용하는 방식(미래에 올 입력 정보는 이용하지 않습니다)이기 때문에 *인과적 언어 모델링(causal language modeling)*이라고 부릅니다. + +
+Example of causal language modeling in which the next word from a sentence is predicted. + +
+ +다른 예시로 *마스크 언어 모델링(masked language modeling)*을 들 수 있습니다. 여기서 모델은 문장 내에 마스킹 된 단어를 예측합니다. + +
+Example of masked language modeling in which a masked word from a sentence is predicted. + +
+ +## 트랜스포머는 거대 모델입니다 + +(DistilBERT와 같은 몇몇 예외를 제외하고) 모델의 성능을 향상시키는 일반적인 전략은 사전 학습에 사용하는 "모델과 데이터의 크기를 늘리기" 입니다. + +
+Number of parameters of recent Transformers models +
+ +불행히도 아주 아주 큰 모델의 경우, 학습을 위해 어마무시한 양의 데이터를 필요로 하여 시간과 컴퓨팅 리소스 관점에서 비용이 매우 많이 듭니다. 아래 그래프에서 볼 수 있듯이 이는 환경 오염 문제로 이어지기도 합니다. + +
+The carbon footprint of a large language model. + +
+ + + +위 비디오는 사전 학습 과정이 환경에 끼치는 부정적 영향을 꾸준히 줄이기 위해 노력하는 한 팀의 (초거대) 언어 모델 프로젝트를 소개합니다. 최적의 하이퍼파라미터를 찾기 위한 수많은 시도에는 아직 많은 여정이 남아 있습니다. + +매번 리서치 팀, 학생 기관, 기업 등등이 밑바닥부터 모델을 학습시킨다고 생각해보세요. 이는 결국 정말 어마어마한 양의 글로벌 비용으로 이어질 것입니다! + +이제 왜 언어 모델을 공유하는 것이 중요한지 아시겠나요? 학습 가중치를 공유하고, 이미 학습시킨 가중치에 이를 차곡차곡 쌓아 올리는 방식으로 커뮤니티의 컴퓨팅 비용과 탄소 발자국을 줄일 수 있기 때문입니다. + + +## 전이 학습(Transfer Learning) + + + +*사전 학습(Pretraining)*시에는 모델을 밑바닥부터 학습시키게 됩니다. 모델 가중치를 랜덤하게 초기화하고 사전 지식 없이 학습을 시작합니다. + +
+The pretraining of a language model is costly in both time and money. + +
+ +이러한 사전 학습 과정은 엄청난 양의 데이터로 이루어지기 때문에 방대한 양의 코퍼스 데이터와 수 주 씩 걸리는 학습 시간을 필요로 하기도 합니다. + +반면에 *미세 조정(Fine-tuning)*이란 모델이 모두 사전 학습을 마친 **이후에** 하는 학습을 의미합니다. 미세 조정을 하기 위해서 우선 사전 학습된 언어 모델을 가져오고, 여러분이 할 작업에 특화된 데이터셋을 이용해 추가 학습을 수행합니다. 잠깐만요, 그냥 한번에 최종 태스크에 맞춰 학습시키면 안될까요? 이렇게 하는 데에는 몇 가지 이유가 있습니다: + +- 사전 학습된 모델은 이미 미세 조정 데이터셋과 유사한 데이터셋으로 학습이 이루어진 상태입니다. 결국 모델이 사전 학습시에 얻은 지식(이를테면, NLP 문제에서 사전 학습된 모델이 얻게 되는 언어의 통계적 이해)을 십분 활용해 미세 조정에 활용할 수 있게 됩니다. +- 사전 학습된 모델은 이미 방대한 데이터로 학습되었기 떄문에 미세 조정에서는 원하는 성능을 얻기까지 적은 양의 데이터만 필요로 하게 됩니다. +- 위와 같은 이유로, 원하는 성능을 얻기까지 적은 시간과 리소스만 필요하게 됩니다. + +예시로, 영어로 사전 학습된 모델을 활용해 arXiv 코퍼스로 미세 조정하여 과학/연구 기반 모델을 만들 수 있습니다. 이 때 미세 조정에는 적은 양의 데이터만 필요할 것입니다. 여기서 사전 학습 과정에서 얻은 지식이 “전이”되었다고 하여 *전이 학습(transfer learning)*이라 부릅니다. + +
+The fine-tuning of a language model is cheaper than pretraining in both time and money. + +
+ +모델의 미세 조정은 이렇게 시간, 데이터, 경제, 그리고 환경 비용 측면에서 훨씬 저렴합니다. 뿐만 아니라 전체 사전 학습 과정보다 제약이 적기 때문에 다양한 방식의 미세 조정을 쉽고 빠르게 반복할 수 있습니다. + +이렇듯 엄청나게 많은 데이터를 확보하지 않은 이상 미세 조정 방식이 밑바닥부터 학습시키는 방식보다 더 좋은 결과를 낼 수 있습니다. 따라서 항상 여러분의 지금 작업 내용과 최대한 비슷한 사전 학습 모델을 불러와 미세 조정하세요! + +## 일반적인 구조 + +이번 섹션에서는 전반적인 트랜스포머 모델의 구조에 대해 알아보겠습니다. 설령 몇몇 개념을 이해하지 못하게 되더라도 걱정하지 마세요. 추후에 각 구성 요소를 자세하게 다루는 섹션이 있으니까요. + + + +## 구조 소개 + +모델은 기본적으로 두 개의 블럭으로 이루어져 있습니다: + +* **인코더 (왼쪽)**: 인코더는 입력을 받아 입력(혹은 입력의 특징)을 구축합니다. 이는 인코더 모델이 입력을 이해하는 데에 최적화되어 있음을 의미합니다. +* **디코더 (오른쪽)**: 디코더는 인코더의 (특징) 표현과 또 다른 입력을 활용해 타겟 시퀀스를 생성합니다. 이는 디코더 모델이 출력을 생성하는 데에 최적화되어 있음을 의미합니다. + +
+Architecture of a Transformers models + +
+ +트랜스포머 모델의 각 파트는 태스크에 따라 다음과 같이 개별적으로 쓰일 수도 있습니다: + +* **인코더 모델(Encoder-only models)**: 문장 분류, 개체명 인식과 같이 입력에 대한 높은 이해를 요구하는 작업에 특화 +* **디코더 모델(Decoder-only models)**: 텍스트 생성과 같이 생성 관련 작업에 특화 +* **인코더-디코더(Encoder-decoder) 모델 또는 시퀀스-투-시퀀스 (Sequence-to-sequence) 모델**: 번역, 요약과 같이 입력을 필요로 하는 생성 관련 작업에 특화 + +다음 단원들에서 각각의 구조에 대해 더 깊게 살펴보도록 하겠습니다. + +## 어텐션 레이어(Attention layers) + +트랜스포머 모델의 중요한 특징은 *어텐션 레이어(attention layers)*라 불리는 특수한 레이어로 구성되었다는 점입니다. 사실, 트랜스포머 구조를 처음 소개한 논문 제목마저 ["Attention Is All You Need"](https://arxiv.org/abs/1706.03762)입니다! 어텐션 레이어의 디테일에 대해서는 추후 강의에서 자세히 다루겠지만, 우선은 이 레이어가 단어의 표현을 다룰 때 입력으로 넣어준 문장의 특정 단어에 어텐션(주의)을 기울이고 특정 단어는 무시하도록 알려준다는 사실을 기억하셔야 합니다. + +이러한 상황을 설명하기 위해 영어 텍스트를 프랑스어로 번역하는 작업을 생각해 보겠습니다. “You like this course”라는 입력이 주어지면 번역 모델은 “like”라는 단어를 알맞게 번역하기 위해 인접한 단어 “You”에도 주의를 기울입니다. 이는 프랑스어의 동사 “like”가 문맥에 따라 다른 의미로 해석되기 때문입니다. 그러나 문장의 나머지 단어들은 이 단어를 번역하는 데에 크게 유용하지 않습니다. 같은 맥락에서 “this”를 번역할 때, 이 단어는 연결 명사가 남성형이거나 여성형이냐에 따라 다르게 해석될 수 있기 때문에 모델은 “course”라는 단어에 주의를 기울입니다. 여기서도 문장의 나머지 다른 단어들은 “this”의 의미를 해석하는 데에 크게 중요하지 않습니다. 더 복잡한 문법의 문장에서 모델은 문장의 각 단어를 적절히 번역하기 위해 더 멀리 떨어진 단어들에 대해서도 주의를 기울여야 합니다. + +이와 동일한 개념이 자연어의 모든 문제에 적용됩니다. 단어는 그 자체로도 의미를 갖지만 문맥상 앞뒤의 다른 단어정보를 알게 되면 의미가 달라지기도 하죠. + +지금까지 어텐션 레이어가 무엇인지 알아보았으니 트랜스포머 구조에 대해 살펴보겠습니다. + +## 원본 구조 + +트랜스포머 구조는 처음에 번역을 위해 만들어졌습니다. 학습시에 인코더는 특정 언어의 입력 문장을 받고, 동시에 디코더는 타겟 언어로된 동일한 의미의 문장을 받습니다. 인코더에서 어텐션 레이어는 문장 내의 모든 단어를 활요할 수 있습니다(방금 보았듯이 주어진 단어의 번역은 문장의 전후를 살펴보아야 하니까요). 반면, 디코더는 순차적으로 작동하기 때문에 문장 내에서 이미 번역이 이루어진 부분에만 주의를 기울일 수 밖에 없습니다. 이로 인해 현재 생성(번역)되고 있는 단어의 앞에 단어들만 이용할 수 있죠. 예시로, 번역된 타겟의 처음 세 단어를 예측해 놨을 때, 이 결과를 디코더로 넘기면 디코더는 인코더로부터 받은 모든 입력 정보를 함께 이용해 네 번째 올 단어를 예측하는 것입니다. + +모델이 타겟 문장에 대한 액세스(access)가 있는 상황에서, 훈련 속도를 높이기 위해 디코더는 전체 타겟을 제공하지만 뒤에 올 단어들을 사용할 수 없습니다. (모델이 두 번째 올 단어를 예측하기 위해 두 번째 위치 단어를 접근할 수 있다면 예측이 의미없어지겠죠?) 예를 들어, 네 번째 단어를 예측할 때 어텐션 레이어는 1~3 번째 단어에만 액세스하도록 합니다. + +원래 처음 트랜스포머의 구조는 아래와 같이 왼쪽에는 인코더, 오른쪽에는 디코더가 있는 형태를 지닙니다: + +
+Architecture of a Transformers models + +
+ +디코더 블럭의 첫 번째 어텐션 레이어는 모든 이전의 디코더 입력에 대해 주의를 기울이지만, 두 번째 어텐션 레이어는 인코더의 출력만 사용하는 점을 주목하세요. 이로써 디코더는 전체 입력 문장에 액세스하여 현재 올 단어를 잘 예측하게 되는 것입니다. 이는 서로 다른 언어는 서로 다른 어순을 갖거나 문장의 뒷부분에 등장하는 문맥이 주어진 단어의 가장 적합한 번역을 결정할 수 있기 때문에 매우 유용합니다. + +*어텐션 마스크(attention mask)*는 또한 모델이 몇몇 특수 단어들에 어텐션을 주는 것을 막는 데에 사용됩니다. 그 예로, 여러 다른 길이의 문장들을 모아 배치를 구성할 때, 패딩이라고 불리는 특수한 단어를 넣어 문장들의 길이를 맞춰주는데 사용하는 경우 있습니다. + +## 구조(Architectures) vs. 체크포인트(Checkpoints) + +트랜스포머 모델을 본격적으로 공부하기 앞서, 모델(models)과 함께 *구조(architectures)*와 *체크포인트(checkpoints)*라는 단어를 들으시게 될겁니다. 이 셋은 아래와 같이 조금 다른 의미를 갖고 있습니다: + +* **구조(Architecture)**: 모델의 뼈대를 의미하는 용어로, 모델 내부의 각 레이어와 각 연산 작용들을 의미합니다. +* **체크포인트(Checkpoints)**: 주어진 구조(architecture)에 적용될 가중치들을 의미합니다. +* **모델(Model)**: 사실 모델은 “구조”나 “가중치”만큼 구체적이지 않은, 다소 뭉뚱그려 사용되는 용어입니다. 이 강의에서는 모호함을 피하기 위해 *구조(architecture)*와 *체크포인트(checkpoint)*를 구분해서 사용하도록 하겠습니다. + +예를 들면, BERT는 구조에 해당하고, Google 팀이 최초 공개에서 내놓은 학습 가중치 셋인 `bert-base-cased`는 체크포인에 해당합니다. 그렇지만 “BERT 모델”, “`bert-base-cased` 모델” 등과 같이 구분하지 않고 사용하기도 합니다. diff --git a/chapters/ko/chapter1/5.mdx b/chapters/ko/chapter1/5.mdx new file mode 100644 index 000000000..3c133aea8 --- /dev/null +++ b/chapters/ko/chapter1/5.mdx @@ -0,0 +1,17 @@ +# 인코더 모델 + + + +인코더 모델(Encoder models)은 트랜스포머 모델의 인코더만 사용합니다. 각각의 단계에서 어텐션 레이어는 초기 문장의 모든 단어에 액세스 할 수 있습니다. 이러한 모델은 “양방향성(bi-directional)” 어텐션을 지닌 특성이 있다고도 하며 *자동 인코딩(auto-enoding) 모델*이라고 부릅니다. + +이러한 모델은 주어진 문장을 훼손시킨 후(랜덤으로 단어에 마스킹을 하는 방식 등으로) 모델이 원본 문장을 찾아 재구성하게끔 하는 과정을 반복시키는 방식으로 사전 학습을 진행 합니다. + +인코더 모델은 문장 분류, 개체명 인식(더 넓은 범위에서 단어 분류), 추출 질의 응답 등과 같이 전체 문장에 대한 이해를 요구하는 작업에 특화되어 있습니다. + +이 계열을 대표하는 모델들은 아래와 같습니다: + +- [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) diff --git a/chapters/ko/chapter1/6.mdx b/chapters/ko/chapter1/6.mdx new file mode 100644 index 000000000..121403b4d --- /dev/null +++ b/chapters/ko/chapter1/6.mdx @@ -0,0 +1,16 @@ +# 디코더 모델 + + + +디코더 모델(Decoder models)은 트랜스포머 모델의 디코더만 사용합니다. 각각의 단계마다, 어텐션 레이어는 주어진 단어에 대해 문장 내에서 해당 단어 앞에 위치한 단어들에 대해서만 액세스 할 수 있습니다. 이러한 모델을 *자동 회귀(auto-regressive) 모델*이라고 부릅니다. + +디코더 모델의 사전 학습은 보통 문장 내 다음 단어 예측을 반복하는 방식으로 이루어집니다. + +이러한 모델은 텍스트 생성에 특화되어 있습니다. + +디코더 모델 계열의 대표 주자들은 다음과 같습니다: + +- [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) +- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) +- [GPT-2](https://huggingface.co/transformers/model_doc/gpt2.html) +- [Transformer XL](https://huggingface.co/transformers/model_doc/transformerxl.html) diff --git a/chapters/ko/chapter1/7.mdx b/chapters/ko/chapter1/7.mdx new file mode 100644 index 000000000..98aad86e9 --- /dev/null +++ b/chapters/ko/chapter1/7.mdx @@ -0,0 +1,16 @@ +# 시퀀스-투-시퀀스 모델 + + + +인코더-디코더 모델(Encoder-decoder models) (*시퀀스-투-시퀀스 모델(sequence-to-sequence models)*로 부르기도 합니다)은 트랜스포머 구조의 인코더, 디코더 둘을 모두 사용합니다. 각 단계마다, 인코더의 어텐션 레이어는 초기 문장의 모든 단어에 액세스 할 수 있는 반면, 디코더의 어텐션 레이어는 주어진 단어 앞에 위치한 단어들에만 액세스 할 수 있습니다. + +이러한 모델의 사전 학습은 인코더 혹은 디코더 모델의 방식을 모두 사용할 수 있지만 조금 더 복잡합니다. 이를테면, [T5](https://huggingface.co/t5-base)는 (여러 단어를 포함하기도 하는) 임의의 텍스트 범위를 하나의 특수 마스크 토큰으로 바꾸고 마스크 단어를 대체할 텍스트를 예측하는 방식으로 사전 학습 되었습니다. + +시퀀스-투-시퀀스 모델은 요약, 번역, 생성 질의 응답과 같이 주어진 입력을 기반으로 새로운 문장을 생성하는 작업에 가장 적합합니다. + +이 계열을 대표하는 모델은 다음과 같습니다: + +- [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) \ No newline at end of file diff --git a/chapters/ko/chapter1/8.mdx b/chapters/ko/chapter1/8.mdx new file mode 100644 index 000000000..edbd32548 --- /dev/null +++ b/chapters/ko/chapter1/8.mdx @@ -0,0 +1,32 @@ +# 편향과 한계 + + + +사전 학습된 혹은 미세 조정된 모델을 프로덕션 단계에서 사용하실 계획이라면, 이러한 모델들은 강력한 툴이지만 한계가 있음을 반드시 명심하셔야 합니다. 가장 큰 한계점은 리서처들이 무수히 많은 양의 데이터를 사전 학습에 사용하기 위해, 인터넷상에서 모을 수 있는 양질의 데이터와 함께 그렇지 않은 데이터까지 수집했을 가능성이 있다는 것입니다. + +이를 빠르게 보여드리기 위해 `fill-mask` 파이프라인에 BERT 모델을 연결한 예제를 다시 살펴보겠습니다: + +```python +from transformers import pipeline + +unmasker = pipeline("fill-mask", model="bert-base-uncased") +result = unmasker("This man works as a [MASK].") +print([r["token_str"] for r in result]) + +result = unmasker("This woman works as a [MASK].") +print([r["token_str"] for r in result]) +``` + +```python out +['lawyer', 'carpenter', 'doctor', 'waiter', 'mechanic'] +['nurse', 'waitress', 'teacher', 'maid', 'prostitute'] +``` + +주어의 성별만 바꾼 두 문장에서 빠진 단어를 채울 때, 모델은 성별과 관계 없는 공통 답변(waiter/waitress)을 하나만 내놓았습니다. 다른 답변들은 일반적으로 특정 성별에 편향된 답변이었습니다. 이를테면, 모델은 매춘이라는 단어와 연관된 상위 5개의 단어에 “여성”과 “일”을 포함시켰습니다. BERT가 전체 인터넷 상의 텍스트를 이용하여 사전 학습된 것이 아니라 [English Wikipedia](https://huggingface.co/datasets/wikipedia) 와 [BookCorpus](https://huggingface.co/datasets/bookcorpus) 같이 상당히 중립적인 데이터를 이용해 사전 학습 되었음에도 불구하고 이러한 현상이 일어납니다. + +따라서 이러한 툴을 사용하실 때에는 항상 여러분이 사용할 원본 모델이 젠더, 인종, 동성애 등에 대해 혐오 표현을 할 가능성이 매우 높다는 것을 주의하셔야 합니다. 이러한 모델은 미세 조정을 거쳐도 내제된 편향성을 없애지 못합니다. \ No newline at end of file diff --git a/chapters/ko/chapter1/9.mdx b/chapters/ko/chapter1/9.mdx new file mode 100644 index 000000000..191b5654d --- /dev/null +++ b/chapters/ko/chapter1/9.mdx @@ -0,0 +1,11 @@ +# 단원 정리 + +이번 단원에서는 🤗 Transformers의 하이레벨 함수인 `pipeline()` 를 사용하여 다양한 NLP 문제에 대한 접근 방식을 배웠습니다. 그리고 Hub에서 모델을 검색하여 사용하는 방법, 추론 API를 이용해 브라우저 상에서 바로 모델을 테스트 하는 방법 또한 알아보았습니다. + +지금까지 트랜스포머 모델의 대략작인 동작 방식과, 전이 학습(transfer learning) 및 미세 조정(fine-tuning)의 중요성에 알아보았습니다. 핵심은 어떤 문제를 풀고싶냐에 따라 전체 모델 구조를 다 사용하거나 인코더, 디코더만 사용할 수도 있다는 것입니다. 아래 표는 이를 요약해서 보여주고 있습니다: + +| Model | Examples | Tasks | +| --- | --- | --- | +| 인코더 | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa | 문장 분류, 개체명 인식, 추출 질의 응답 | +| 디코더 | CTRL, GPT, GPT-2, Transformer XL | 텍스트 생성 | +| 인코더-디코더 | BART, T5, Marian, mBART | 오약, 번역, 생성 질의 응답 | \ No newline at end of file diff --git a/chapters/tr/_toctree.yml b/chapters/tr/_toctree.yml index 41c6d6420..7c06284a4 100644 --- a/chapters/tr/_toctree.yml +++ b/chapters/tr/_toctree.yml @@ -2,3 +2,9 @@ sections: - local: chapter0/1 title: Giriş + + +- title: 1. Transformer modelleri + sections: + - local: chapter1/2 + title: Doğal Dil İşleme diff --git a/chapters/tr/chapter1/2.mdx b/chapters/tr/chapter1/2.mdx new file mode 100644 index 000000000..a9536d43a --- /dev/null +++ b/chapters/tr/chapter1/2.mdx @@ -0,0 +1,21 @@ +# Doğal Dil İşleme (NLP) + +Transformer modellerine geçiş yapmadan önce, doğal dil işlemenin tanımına ve neden önemli olduğuna kısaca bir göz atalım. + + +## NLP Nedir? + +NLP, dil bilim ve makina öğrenmesi alanlarini kapsayan, dogal dil üzerine olan herşeyi anlamaya yönelik bir alandır. NLP her bir kelimeyi anlamanın yanısıra, kelimelerin bağlamını anlayabilmeyi amaçlar. Aşağıda NLP görevlerinin genel bir listesini, her bir göreve dair bir kaç örnekle birlikte görülebilir: + +- **Cümle Sınıflandırması: Değerlendirmeler üzerine duygu analizi, spam e-posta saptaması, bir cümlenin dilbilgisel doğruluğunu, ya da iki cümlenin mantıksal olarak bağlı olup olmadığını anlama +- **Cümle içerisindeki her bir kelimenin sınıflandırması: Bir cümlenin dilbilgisel öğelerinin (isim, fiil, sıfat), ya da isim verilmiş varlıkların tanımlanması +- **Metin üretme: Verilen bir ipucunu kullanarak, otomatik olarak metni tamamlama, maskeli kelimeler ile metindeki boşlukları doldurma +- **Metinden cevap çıkarımı: Verilen bir soru ve bağlamı kullanarak, bağlamda verilmi olan bilgiye dayanarak verilen soruyu cevaplama +- **Verilen metni kullanarak yeni bir cümle üretme: Metni başka bir dile çevirme, metin özetleme. + +Metin işlemenin yanısıra, NLP, konuşma tanıma ve bilgisayar görmesi alanlarında gorülen, bir ses örneğini yazıya dökme ya da bir fotoğrafı betimleme gibi komplike işlemleri çözümlemek için de kullanılabilir. + +## NLP Neden Zor? + +Bilgisayarlar, bilgiyi biz insanların yaptığı gibi işlemiyor. Örneğin, insan beyni “Acıktım” gibi basit bir cümlenin anlamını kolaylıkla anlayabilir. Benzer bir şekilde, “acıktım” ve “üzgünüm” cümlelerinin dilbilgisel açıdan benzerliğini kolayca görebiliyoruz. Ancak, makine öğrenmesi modelleri için bu gibi işlemler hiç de kolay değil. Bir modelin, verilen metnin anlamını öğrenebilmesi için metnin belirli bir yöntem ile işlenmesi gerekir. İnsan dilinin bileşikliği nedeniyle, bu işlemenin nasıl yapılacağı konsunu dikkatlice düşünmeliyiz. Bu güne kadar NLP alanında yazının nasıl temsil edileceğine dair bir çok çalışma yapıldı. Bir sonraki bölümde, bu çalışmalarda geliştirilen bazı yöntemlere göz atacağız. + From ccfb1854c81a2c0cf89bafca76f52ed6066c8f20 Mon Sep 17 00:00:00 2001 From: lewtun Date: Thu, 14 Apr 2022 09:06:28 +0200 Subject: [PATCH 07/51] Bump release (#115) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ko-chapter1/1 * ko _toctree.yml created * Fix the issue #80 * Single expression changed * ko/chapter1 finished * ko/chapter0 finished * ko/chapter0 finished * reviewed by @bzantium ko/chapter0 * reviewed by @bzantium chapter0 & fixed typo * reviewed by @rainmaker712 * maximize Korean expressions * [Chapter 1] bangla traslation initial commit * Update 1.mdx update and fix formating * Fix formating and typos * translate _toctree.yml 0-1 chapter * Add Korean to CI * [tr] Translated chapter1/2.mdx * remove translation from sec titles not yet translated * Add authors [th ru] * [FIX] _toctree.yml * Update chapters/bn/chapter0/1.mdx [FIX] syntax formatting Co-authored-by: lewtun * tag typos & indentation & unnatural expressions * modified toctree.yml for chapter1/2 * modified toctree.yml for chapter1/2 & fix typo * French Translation - Chapter 5 * Add Bengali to CI * Update author list * Adding translations for 2/4 and 2/5 🚀 (#74) * Adding translations for 2/4 and 2/5 🚀 * Remove English content Co-authored-by: lewtun * Translation to Russian (#97) * translation of chapter 2/section 1 * add section 1 / chapter 2 to _toctree.yml * Translation of Chapter0 to Hindi (#86) * Hindi?Chapter0-Part_1 * Hindi/Chapter0-Part_2 * Chapter 0 Persian Translation First Draft (#95) * merged branch0 into main. no toctree yet. * updated toctree. * Updated the glossary with terms from chapter0. * Second draft in collab w/ @schoobani. Added empty chapter1 for preview. * Glossary typo fix. * Translation of Chapter0 (setup) to Arabic (#104) * Add AR translation for `introduction` * Fix alignment & format * Add Arabic to CI build * Russian - Chapter 1 finished (#98) * 01/4 start * 1/4 finished * 1/5 finished * 1/5 update toc * 1/6 finished * 1/7 finished * 1/8 finished * 1/9 finished * 1/4 fix * toc update * Chinese - Chapter 1 finished (#113) * Chinese - Chapter 1 finished * Add zh to the languages field Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> * [PT] Translation of chapter 2 (#107) * add PT translate to 2.1 * add PT translate to 2.2 * add portuguese translation to 2.3 * WIP portuguese translation to 2.4 * add portuguese translation to 2.4 * add portuguese translation to 2.5 * add portuguese translation to 2.6 * add _toctree infos Co-authored-by: lewtun * [FR] Translation of chapter 2 & event + Review of chapters 0 & 5 (#106) * Update _toctree.yml Add chapter 2 + little fix of chapter 5 * Update 1.mdx Review of chapter 0 * Create 1.mdx * Create 2.mdx * Create 3.mdx * Create 4.mdx * Create 5.mdx * Create 6.mdx * Create 7.mdx * Create 8.mdx * Update 8.mdx Since AutoNLP has recently been renamed to AutoTrain, let me make the correction on the English file * Update 1.mdx Review of chapter 5/1 * Update 2.mdx Review of chapter 5/2 * Update 3.mdx Review of chapter 5/3 * Update 4.mdx Review of chapter 5/4 * Update 5.mdx Review of chapter 5/5 * Update 6.mdx Review of chapter 5/6 * Update 7.mdx Review of chapter 5/7 * Update 8.mdx Review of chapter 5/8 * Create 1.mdx event's translation * Update _toctree.yml add event to the tree * Update _toctree.yml deletion of the files that pose a problem to pass the checks, will be resubmitted in another PR * Delete 1.mdx deletion of the files that pose a problem to pass the checks, will be resubmitted in another PR * make style correction * Update _toctree.yml the - * Fix spacing Co-authored-by: Lewis Tunstall * [th] Translated Chapter2/1 (#83) * Finish chapter2/1 * Update _toctree.yml * ko-chapter1/1 * ko _toctree.yml created * Fix the issue #80 * Single expression changed * ko/chapter1 finished * ko/chapter0 finished * ko/chapter0 finished * reviewed by @bzantium ko/chapter0 * reviewed by @bzantium chapter0 & fixed typo * reviewed by @rainmaker712 * maximize Korean expressions * [Chapter 1] bangla traslation initial commit * Update 1.mdx update and fix formating * Fix formating and typos * translate _toctree.yml 0-1 chapter * Add Korean to CI * remove translation from sec titles not yet translated * Add authors [th ru] * [FIX] _toctree.yml * Update chapters/bn/chapter0/1.mdx [FIX] syntax formatting Co-authored-by: lewtun * tag typos & indentation & unnatural expressions * modified toctree.yml for chapter1/2 * modified toctree.yml for chapter1/2 & fix typo * Add Bengali to CI * Update author list * Adding translations for 2/4 and 2/5 🚀 (#74) * Adding translations for 2/4 and 2/5 🚀 * Remove English content Co-authored-by: lewtun * Translation to Russian (#97) * translation of chapter 2/section 1 * add section 1 / chapter 2 to _toctree.yml * Translation of Chapter0 to Hindi (#86) * Hindi?Chapter0-Part_1 * Hindi/Chapter0-Part_2 * Chapter 0 Persian Translation First Draft (#95) * merged branch0 into main. no toctree yet. * updated toctree. * Updated the glossary with terms from chapter0. * Second draft in collab w/ @schoobani. Added empty chapter1 for preview. * Glossary typo fix. * Translation of Chapter0 (setup) to Arabic (#104) * Add AR translation for `introduction` * Fix alignment & format * Add Arabic to CI build * Russian - Chapter 1 finished (#98) * 01/4 start * 1/4 finished * 1/5 finished * 1/5 update toc * 1/6 finished * 1/7 finished * 1/8 finished * 1/9 finished * 1/4 fix * toc update * Chinese - Chapter 1 finished (#113) * Chinese - Chapter 1 finished * Add zh to the languages field Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> * [PT] Translation of chapter 2 (#107) * add PT translate to 2.1 * add PT translate to 2.2 * add portuguese translation to 2.3 * WIP portuguese translation to 2.4 * add portuguese translation to 2.4 * add portuguese translation to 2.5 * add portuguese translation to 2.6 * add _toctree infos Co-authored-by: lewtun * [FR] Translation of chapter 2 & event + Review of chapters 0 & 5 (#106) * Update _toctree.yml Add chapter 2 + little fix of chapter 5 * Update 1.mdx Review of chapter 0 * Create 1.mdx * Create 2.mdx * Create 3.mdx * Create 4.mdx * Create 5.mdx * Create 6.mdx * Create 7.mdx * Create 8.mdx * Update 8.mdx Since AutoNLP has recently been renamed to AutoTrain, let me make the correction on the English file * Update 1.mdx Review of chapter 5/1 * Update 2.mdx Review of chapter 5/2 * Update 3.mdx Review of chapter 5/3 * Update 4.mdx Review of chapter 5/4 * Update 5.mdx Review of chapter 5/5 * Update 6.mdx Review of chapter 5/6 * Update 7.mdx Review of chapter 5/7 * Update 8.mdx Review of chapter 5/8 * Create 1.mdx event's translation * Update _toctree.yml add event to the tree * Update _toctree.yml deletion of the files that pose a problem to pass the checks, will be resubmitted in another PR * Delete 1.mdx deletion of the files that pose a problem to pass the checks, will be resubmitted in another PR * make style correction * Update _toctree.yml the - * Fix spacing Co-authored-by: Lewis Tunstall * [th] Translated Chapter2/1 (#83) * Finish chapter2/1 * Update _toctree.yml * Add Hindi to CI (#116) Co-authored-by: DOOHAE JUNG Co-authored-by: m_khandaker Co-authored-by: Md. Al-Amin Khandaker Co-authored-by: ftarlaci <18291571+ftarlaci@users.noreply.github.com> Co-authored-by: Doohae Jung <80743307+Doohae@users.noreply.github.com> Co-authored-by: melaniedrevet Co-authored-by: Jose M Munoz Co-authored-by: svv73 <88366711+svv73@users.noreply.github.com> Co-authored-by: Vedant Pandya Co-authored-by: Bahram Shamshiri Co-authored-by: Giyaseddin Bayrak <34009210+giyaseddin@users.noreply.github.com> Co-authored-by: Pavel <60391448+pdumin@users.noreply.github.com> Co-authored-by: 1375626371 <40328311+1375626371@users.noreply.github.com> Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> Co-authored-by: João Gustavo A. Amorim Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Cherdsak Kingkan --- .github/workflows/build_documentation.yml | 2 +- .github/workflows/build_pr_documentation.yml | 4 +- chapters/ar/_toctree.yml | 4 + chapters/ar/chapter0/1.mdx | 136 +++++++ chapters/en/chapter2/8.mdx | 2 +- chapters/es/_toctree.yml | 7 + chapters/es/chapter2/4.mdx | 235 ++++++++++++ chapters/es/chapter2/5.mdx | 332 +++++++++++++++++ chapters/fa/_toctree.yml | 10 + chapters/fa/chapter0/1.mdx | 149 ++++++++ chapters/fa/chapter1/1.mdx | 7 + chapters/fa/glossary/1.mdx | 85 ++++- chapters/fr/_toctree.yml | 26 +- chapters/fr/chapter0/1.mdx | 35 +- chapters/fr/chapter2/1.mdx | 24 ++ chapters/fr/chapter2/2.mdx | 344 ++++++++++++++++++ chapters/fr/chapter2/3.mdx | 231 ++++++++++++ chapters/fr/chapter2/4.mdx | 253 +++++++++++++ chapters/fr/chapter2/5.mdx | 351 ++++++++++++++++++ chapters/fr/chapter2/6.mdx | 186 ++++++++++ chapters/fr/chapter2/7.mdx | 12 + chapters/fr/chapter2/8.mdx | 307 ++++++++++++++++ chapters/fr/chapter5/1.mdx | 20 +- chapters/fr/chapter5/2.mdx | 50 ++- chapters/fr/chapter5/3.mdx | 197 +++++------ chapters/fr/chapter5/4.mdx | 99 +++--- chapters/fr/chapter5/5.mdx | 124 ++++--- chapters/fr/chapter5/6.mdx | 74 ++-- chapters/fr/chapter5/7.mdx | 17 +- chapters/fr/chapter5/8.mdx | 104 +++--- chapters/hi/_toctree.yml | 9 + chapters/hi/chapter0/1.mdx | 110 ++++++ chapters/hi/chapter1/1.mdx | 51 +++ chapters/pt/_toctree.yml | 19 + chapters/pt/chapter2/1.mdx | 20 ++ chapters/pt/chapter2/2.mdx | 354 +++++++++++++++++++ chapters/pt/chapter2/3.mdx | 229 ++++++++++++ chapters/pt/chapter2/4.mdx | 248 +++++++++++++ chapters/pt/chapter2/5.mdx | 340 ++++++++++++++++++ chapters/pt/chapter2/6.mdx | 167 +++++++++ chapters/pt/chapter2/7.mdx | 13 + chapters/pt/chapter2/8.mdx | 305 ++++++++++++++++ chapters/ru/_toctree.yml | 21 +- chapters/ru/chapter1/4.mdx | 173 +++++++++ chapters/ru/chapter1/5.mdx | 17 + chapters/ru/chapter1/6.mdx | 16 + chapters/ru/chapter1/7.mdx | 16 + chapters/ru/chapter1/8.mdx | 33 ++ chapters/ru/chapter1/9.mdx | 12 + chapters/ru/chapter2/1.mdx | 19 + chapters/th/_toctree.yml | 7 +- chapters/th/chapter2/1.mdx | 19 + chapters/zh/_toctree.yml | 23 ++ chapters/zh/chapter1/1.mdx | 52 +++ chapters/zh/chapter1/10.mdx | 253 +++++++++++++ chapters/zh/chapter1/2.mdx | 20 ++ chapters/zh/chapter1/3.mdx | 287 +++++++++++++++ chapters/zh/chapter1/4.mdx | 172 +++++++++ chapters/zh/chapter1/5.mdx | 17 + chapters/zh/chapter1/6.mdx | 17 + chapters/zh/chapter1/7.mdx | 16 + chapters/zh/chapter1/8.mdx | 31 ++ chapters/zh/chapter1/9.mdx | 11 + 63 files changed, 6148 insertions(+), 376 deletions(-) create mode 100644 chapters/ar/_toctree.yml create mode 100644 chapters/ar/chapter0/1.mdx create mode 100644 chapters/es/chapter2/4.mdx create mode 100644 chapters/es/chapter2/5.mdx create mode 100644 chapters/fa/chapter0/1.mdx create mode 100644 chapters/fa/chapter1/1.mdx create mode 100644 chapters/fr/chapter2/1.mdx create mode 100644 chapters/fr/chapter2/2.mdx create mode 100644 chapters/fr/chapter2/3.mdx create mode 100644 chapters/fr/chapter2/4.mdx create mode 100644 chapters/fr/chapter2/5.mdx create mode 100644 chapters/fr/chapter2/6.mdx create mode 100644 chapters/fr/chapter2/7.mdx create mode 100644 chapters/fr/chapter2/8.mdx create mode 100644 chapters/hi/_toctree.yml create mode 100644 chapters/hi/chapter0/1.mdx create mode 100644 chapters/hi/chapter1/1.mdx create mode 100644 chapters/pt/chapter2/1.mdx create mode 100644 chapters/pt/chapter2/2.mdx create mode 100644 chapters/pt/chapter2/3.mdx create mode 100644 chapters/pt/chapter2/4.mdx create mode 100644 chapters/pt/chapter2/5.mdx create mode 100644 chapters/pt/chapter2/6.mdx create mode 100644 chapters/pt/chapter2/7.mdx create mode 100644 chapters/pt/chapter2/8.mdx create mode 100644 chapters/ru/chapter1/4.mdx create mode 100644 chapters/ru/chapter1/5.mdx create mode 100644 chapters/ru/chapter1/6.mdx create mode 100644 chapters/ru/chapter1/7.mdx create mode 100644 chapters/ru/chapter1/8.mdx create mode 100644 chapters/ru/chapter1/9.mdx create mode 100644 chapters/ru/chapter2/1.mdx create mode 100644 chapters/th/chapter2/1.mdx create mode 100644 chapters/zh/_toctree.yml create mode 100644 chapters/zh/chapter1/1.mdx create mode 100644 chapters/zh/chapter1/10.mdx create mode 100644 chapters/zh/chapter1/2.mdx create mode 100644 chapters/zh/chapter1/3.mdx create mode 100644 chapters/zh/chapter1/4.mdx create mode 100644 chapters/zh/chapter1/5.mdx create mode 100644 chapters/zh/chapter1/6.mdx create mode 100644 chapters/zh/chapter1/7.mdx create mode 100644 chapters/zh/chapter1/8.mdx create mode 100644 chapters/zh/chapter1/9.mdx diff --git a/.github/workflows/build_documentation.yml b/.github/workflows/build_documentation.yml index a7714c94a..1b7f31778 100644 --- a/.github/workflows/build_documentation.yml +++ b/.github/workflows/build_documentation.yml @@ -14,6 +14,6 @@ jobs: package: course path_to_docs: course/chapters/ additional_args: --not_python_module - languages: bn en es fa fr he ko pt ru th tr + languages: ar bn en es fa fr he hi ko pt ru th tr zh secrets: token: ${{ secrets.HUGGINGFACE_PUSH }} \ No newline at end of file diff --git a/.github/workflows/build_pr_documentation.yml b/.github/workflows/build_pr_documentation.yml index f7b92f51d..2e3c06441 100644 --- a/.github/workflows/build_pr_documentation.yml +++ b/.github/workflows/build_pr_documentation.yml @@ -16,5 +16,5 @@ jobs: package: course path_to_docs: course/chapters/ additional_args: --not_python_module - languages: bn en es fa fr he ko pt ru th tr - hub_base_path: https://moon-ci-docs.huggingface.co/course \ No newline at end of file + languages: ar bn en es fa fr he hi ko pt ru th tr zh + hub_base_path: https://moon-ci-docs.huggingface.co/course diff --git a/chapters/ar/_toctree.yml b/chapters/ar/_toctree.yml new file mode 100644 index 000000000..b9e6467fd --- /dev/null +++ b/chapters/ar/_toctree.yml @@ -0,0 +1,4 @@ +- title: 0.الإعداد + sections: + - local: chapter0/1 + title: مقدمة diff --git a/chapters/ar/chapter0/1.mdx b/chapters/ar/chapter0/1.mdx new file mode 100644 index 000000000..7951b9d94 --- /dev/null +++ b/chapters/ar/chapter0/1.mdx @@ -0,0 +1,136 @@ +
+ +# مقدمة + +مرحبًا بك في دورة Hugging Face! ستساعدك هذه المقدمة خلال إعداد بيئة العمل. إذا كنت قد بدأت الدورة للتو، فننصحك أولاً بإلقاء نظرة على [الفصل 1](/course/chapter1)، ثم العودة وإعداد بيئتك حتى تتمكن من تجربة الكود بنفسك. + +تتوفر جميع المكتبات التي سنستخدمها في هذه الدورة التدريبية على شكل حزم (Package) Python، لذلك سنوضح لك هنا كيفية إعداد بيئة Python وتثبيت المكتبات المحددة التي ستحتاج إليها. + +سنغطي طريقتين لإعداد بيئة العمل الخاصة بك، باستخدام دفتر Colab أو بيئة Python الافتراضية. لا تتردد في اختيار البيئة التي تناسبك أكثر.نوصي المبتدئين بشدة أن يبدأوا باستخدام دفتر Colab. + +لاحظ أننا لن نغطي نظام Windows. إذا كنت تعمل على نظام Windows، فإننا نوصي بمتابعة استخدام دفتر Colab. إذا كنت تستخدم توزيعة Linux أو macOS، فيمكنك استخدام أي من الطريقتين الموضحتين هنا. + +تعتمد معظم الدورة على امتلاكك لحساب Hugging Face. نوصي بإنشاء حساب الآن: [إنشاء حساب](https://huggingface.co/join). + +## استخدام دفتر Google Colab + +يعد استخدام دفتر Colab أبسط إعداد ممكن؛ فقط قم بتشغيل دفتر Colab في متصفحك ابدأ مباشرة بالبرمجة! + +إذا لم تكن معتادًا على Colab، نوصيك بالبدء باتباع [المقدمة](https://colab.research.google.com/notebooks/intro.ipynb). يتيح لك Colab استخدام بعض أجهزة التسريع، مثل GPUs أو TPUs، وهو مجاني في حال تشغيل مهمات خفيفة. + +بمجرد أن تشعر بالأريحية في التنقل في Colab، أنشئ دفتر ملاحظات جديدًا وابدأ في الإعداد: + +
+An empty colab notebook +
+ +الخطوة التالية هي تثبيت المكتبات التي سنستخدمها في هذه الدورة. سنستخدم `pip` للتثبيت، وهو مدير الحزم لPython. حتى تتمكن من تثبيت مكتبة 🤗 Transformers يمكنك تشغيل أوامر النظام عن طريق تسبقها بالحرف `!` في دفتر Colab, على النحو التالي: + +
+ +``` +!pip install transformers +``` + +
+يمكنك التأكد من تثبيت الحزمة بشكل صحيح عن طريق استيرادها (import) خلال وقت تشغيل Python: +
+ +``` +import transformers +``` + +
+A gif showing the result of the two commands above: installation and import +
+ +
+هذا يثبت نسخة خفيفة جدا من مكتبة 🤗 Transformers. أي أنه لم يتم تثبيت أي إطارات عمل محددة للتعلم الآلي (مثل PyTorch أو TensorFlow). نوصي بتثبيت "إصدار التطوير" للمكتبة لأننا سوف نستخدم الكثير من الميزات المختلفة, و هذا الإصدار يأتي مع جميع التبعيات المطلوبة تقريباً لأي حالة استخدام يمكن تخيلها: + +
+ +``` +!pip install transformers[sentencepiece] +``` + +
+سيستغرق هذا بعض الوقت، لكنك ستكون جاهزًا بعد ذلك لبقية الدورة! + +## استخدام بيئة Python افتراضية + +إذا كنت تفضل استخدام بيئة Python الافتراضية، فإن الخطوة الأولى هي تثبيت Python على نظامك. للبدء, نوصي باتباع [دليل الإرشادات هذا](https://realpython.com/installing-python/). + +بمجرد تثبيت Python، يجب أن تكون قادرًا على تشغيل أوامر Python في الجهاز المستخدم. للتأكد من تثبيته بشكل صحيح قبل المتابعة إلى الخطوات التالية يمكنك البدء بتشغيل الأمر التالي: `python --version`. يجب أن يطبع هذا إصدار Python المتاح الآن على نظامك. + +عند تشغيل أمر Python في الجهاز المستخدم، مثل `python --version`، يجب أن تفكر في البرنامج الذي يقوم بتشغيل الأمر الخاص بك باعتباره Python "الرئيسي" على نظامك. نوصي بالحفاظ على هذا التثبيت الرئيسي خاليًا من أي حزم، واستخدامه لإنشاء بيئات منفصلة لكل تطبيق تعمل عليه, وبهذه الطريقة، يمكن لكل تطبيق أن يكون له تبعيات وحزم خاصة به، ولن تقلق بشأن مشكلات التوافق المحتملة مع تطبيقات أخرى. + +في Python، يتم ذلك باستخدام [* البيئات الافتراضية *](https://docs.python.org/3/tutorial/venv.html)، وهي عبارة عن تفرعات من المجلدات كل منها قائم بحد ذاته, ويحتوي كل منها على Python مثبت بإصدار معين بالإضافة إلى جميع الحزم التي يحتاجها التطبيق. يمكن إنشاء مثل هذه البيئة الافتراضية باستخدام عدد من الأدوات المختلفة ، لكننا سنستخدم حزمة Python الرسمية لهذا الغرض، والتي تسمى [`venv`](https://docs.python.org/3/library/venv.html#module-venv). + +أولاً، قم بإنشاء المجلد الذي تريد أن يتواجد فيه التطبيق الخاص بك -على سبيل المثال، قد ترغب في إنشاء مجلد جديد يسمى *transformers-course* في المجلد الرئيسي للدورة: +
+ +``` +mkdir ~/transformers-course +cd ~/transformers-course +``` + +
+ +من داخل هذا المجلد، أنشئ بيئة افتراضية باستخدام وحدة Python `venv`: + +
+ +``` +python -m venv .env +``` + +
+يجب أن يكون لديك الآن مجلد يسمى *.env* في المجلد الفارغ الخاص بك: +
+ +``` +ls -a +``` + +```out +. .. .env +``` + +
+يمكنك الدخول والخروج من بيئتك الافتراضية باستخدام أوامر "التنشيط" و "إلغاء التنشيط": +
+ +``` +# Activate the virtual environment +source .env/bin/activate + +# Deactivate the virtual environment +source .env/bin/deactivate +``` + +
+يمكنك التأكد من تنشيط البيئة عن طريق تشغيل الأمر `which python`: إذا كان يشير إلى البيئة الافتراضية، فقد قمت بتنشيطها بنجاح! +
+ +``` +which python +``` + +```out +/home//transformers-course/.env/bin/python +``` + +
+ +### تثبيت التبعيات + +كما في القسم السابق حول استخدام مثيلات Google Colab، ستحتاج الآن إلى تثبيت الحزم المطلوبة للمتابعة. مرة أخرى، يمكنك تثبيت إصدار التطوير من 🤗 Transformers باستخدام مدير الحزم `pip`: +
+ +``` +pip install "transformers[sentencepiece]" +``` + +
+أنت الآن جاهز تمامًا للانطلاق! +
diff --git a/chapters/en/chapter2/8.mdx b/chapters/en/chapter2/8.mdx index 43f0a8c9c..ef07b0b31 100644 --- a/chapters/en/chapter2/8.mdx +++ b/chapters/en/chapter2/8.mdx @@ -105,7 +105,7 @@ choices={[ { text: "A model that automatically trains on your data", - explain: "Incorrect. Are you mistaking this with our AutoNLP product?" + explain: "Incorrect. Are you mistaking this with our AutoTrain product?" }, { text: "An object that returns the correct architecture based on the checkpoint", diff --git a/chapters/es/_toctree.yml b/chapters/es/_toctree.yml index 5ee96568f..2cdc73523 100644 --- a/chapters/es/_toctree.yml +++ b/chapters/es/_toctree.yml @@ -26,3 +26,10 @@ - local: chapter1/10 title: Quiz de final de capítulo quiz: 1 + +- title: 2. Usando Transformers 🤗 + sections: + - local: chapter2/4 + title: Tokenizadores + - local: chapter2/5 + title: Manejando Secuencias Múltiples diff --git a/chapters/es/chapter2/4.mdx b/chapters/es/chapter2/4.mdx new file mode 100644 index 000000000..7a3b40160 --- /dev/null +++ b/chapters/es/chapter2/4.mdx @@ -0,0 +1,235 @@ + + +# Tokenizadores + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + + +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. + +Veamos algunos algoritmos de tokenización, e intentemos atacar algunas preguntas que puedas tener. + +## Tokenización Word-based + + + +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()`. + +```py +tokenized_text = "Jim Henson era un titiritero".split() +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. + +Si queremos cubrir completamente un idioma con un tokenizador basado en palabras, necesitaremos tener un identificador para cada palabra del idioma, lo que generará una enorme cantidad de tokens. Por ejemplo, hay más de 500.000 palabras en el idioma inglés, por lo que para construir un mapa de cada palabra a un identificador de entrada necesitaríamos hacer un seguimiento de esa cantidad de identificadores. Además, palabras como "perro" se representan de forma diferente a palabras como "perros", y el modelo no tendrá forma de saber que "perro" y "perros" son similares: identificará las dos palabras como no relacionadas. Lo mismo ocurre con otras palabras similares, como "correr" y "corriendo", que el modelo no verá inicialmente como similares. + +Por último, necesitamos un token personalizado para representar palabras que no están en nuestro vocabulario. Esto se conoce como el token "desconocido", a menudo representado como "[UNK]" o "<unk>". Generalmente, si el tokenizador está produciendo muchos de estos tokens es una mala señal, ya que no fue capaz de recuperar una representación de alguna palabra y está perdiendo información en el proceso. El objetivo al elaborar el vocabulario es hacerlo de tal manera que el tokenizador tokenice el menor número de palabras posibles en tokens desconocidos. + +Una forma de reducir la cantidad de tokens desconocidos es ir un poco más allá, utilizando un tokenizador _word-based_. + +## Tokenización Character-based + + + +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: + +- Obtenemos un vocabulario mucho más corto. +- Habrá muchos menos tokens por fuera del vocabulatio conocido. + +No obstante, pueden surgir incovenientes por los espacios en blanco y signos de puntuación. + +
+ Ejemplo de tokenizador basado en palabras. + +
+ +Así, este método tampoco es perfecto. Dada que la representación se construyó con caracteres, uno podría pensar intuitivamente que resulta menos significativo: Cada una de las palabras no significa mucho por separado, mientras que las palabras sí. Sin embargo, eso es dependiente del idioma. Por ejemplo en Chino, cada uno de los caracteres conlleva más información que en un idioma latino. + +Otro aspecto a considerar es que terminamos con una gran cantidad de tokens que el modelo debe procesar, mientras que en el caso del tokenizador _word-based_, un token representa una palabra, en la representación de caracteres fácilmente puede necesitar más de 10 tokens. + +Para obtener lo mejor de ambos mundos, podemos usar una combinación de las técnicas: la tokenización por *subword tokenization*. + +## Tokenización por Subword + + + +Los algoritmos de tokenización de subpalabras se basan en el principio de que las palabras de uso frecuente no deben dividirse, mientras que las palabras raras deben descomponerse en subpalabras significativas. + +Por ejemplo, "extrañamente" podría considerarse una palabra rara y podría descomponerse en "extraña" y "mente". Es probable que ambas aparezcan con más frecuencia como subpalabras independientes, mientras que al mismo tiempo el significado de "extrañamente" se mantiene por el significado compuesto de "extraña" y "mente". + +Este es un ejemplo que muestra cómo un algoritmo de tokenización de subpalabras tokenizaría la secuencia "Let's do tokenization!": + +
+ Un tokenizador basado en subpalabras. + +
+ +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. + +### Y más! + +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 + +A este punto, deberías tener conocimientos suficientes sobre el funcionamiento de los tokenizadores para empezar a utilizar la API. + +## Cargando y guardando + +Cargar y guardar tokenizadores es tan sencillo como lo es con los modelos. En realidad, se basa en los mismos dos métodos: `from_pretrained()` y `save_pretrained()`. Estos métodos cargarán o guardarán el algoritmo utilizado por el tokenizador (un poco como la *arquitectura* del modelo) así como su vocabulario (un poco como los *pesos* del modelo). + +La carga del tokenizador BERT entrenado con el mismo punto de control que BERT se realiza de la misma manera que la carga del modelo, excepto que utilizamos la clase `BertTokenizer`: + +```py +from transformers import BertTokenizer + +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: + +{: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: + +{/if} + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +``` + +Ahora podemos utilizar el tokenizador como se muestra en la sección anterior: + +```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]} +``` +Guardar un tokenizador es idéntico a guardar un modelo: + +```py +tokenizer.save_pretrained("directorio_en_mi_computador") +``` + +Hablaremos más sobre `token_type_ids` en el [Capítulo 3](/course/chapter3), y explicaremos la clave `attention_mask` un poco más tarde. Primero, veamos cómo se generan los `input_ids`. Para ello, tendremos que ver los métodos intermedios del tokenizador. + +## Encoding + + + +La traducción de texto a números se conoce como _codificación_. La codificación se realiza en un proceso de dos pasos: la tokenización, seguida de la conversión a IDs de entrada. + +Como hemos visto, el primer paso es dividir el texto en palabras (o partes de palabras, símbolos de puntuación, etc.), normalmente llamadas *tokens*. Hay múltiples reglas que pueden gobernar ese proceso, por lo que necesitamos instanciar el tokenizador usando el nombre del modelo, para asegurarnos de que usamos las mismas reglas que se usaron cuando se preentrenó el modelo. + +El segundo paso es convertir esos tokens en números, para poder construir un tensor con ellos y alimentar el modelo. Para ello, el tokenizador tiene un *vocabulario*, que es la parte que descargamos cuando lo instanciamos con el método `from_pretrained()`. De nuevo, necesitamos usar el mismo vocabulario que se usó cuando el modelo fue preentrenado. + +Para entender mejor los dos pasos, los exploraremos por separado. Ten en cuenta que utilizaremos algunos métodos que realizan partes del proceso de tokenización por separado para mostrarte los resultados intermedios de esos pasos, pero en la práctica, deberías llamar al tokenizador directamente en tus _inputs_ (como se muestra en la sección 2). + +### Tokenization + +El proceso de tokenización se realiza mediante el método `tokenize()` del tokenizador: + +```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) +``` + +La salida de este método es una lista de cadenas, o tokens: + +```python out +['Using', 'a', 'transform', '##er', 'network', 'is', 'simple'] +``` + +Este tokenizador es un tokenizador de subpalabras: divide las palabras hasta obtener tokens que puedan ser representados por su vocabulario. Este es el caso de `transformer`, que se divide en dos tokens: `transform` y `##er`. + +### 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) + +print(ids) +``` + +```python out +[7993, 170, 11303, 1200, 2443, 1110, 3014] +``` + +Estos resultados, una vez convertidos en el tensor del marco apropiado, pueden utilizarse como entradas de un modelo, como se ha visto anteriormente en este capítulo. + + + +✏️ **Try it out!** Replica los dos últimos pasos (tokenización y conversión a IDs de entrada) en las frases de entrada que utilizamos en la sección 2 ("Llevo toda la vida esperando un curso de HuggingFace" y "¡Odio tanto esto!"). Comprueba que obtienes los mismos ID de entrada que obtuvimos antes! + + + +## Decodificación + +La *decodificación* va al revés: a partir de los índices del vocabulario, queremos obtener una cadena. Esto se puede hacer con el método `decode()` de la siguiente manera: + +```py +decoded_string = tokenizer.decode([7993, 170, 11303, 1200, 2443, 1110, 3014]) +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 diff --git a/chapters/es/chapter2/5.mdx b/chapters/es/chapter2/5.mdx new file mode 100644 index 000000000..606f9bc89 --- /dev/null +++ b/chapters/es/chapter2/5.mdx @@ -0,0 +1,332 @@ + + +# Manejando Secuencias Múltiples + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + +{:else} + +{/if} + +En la sección anterior, hemos explorado el caso de uso más sencillo: hacer inferencia sobre una única secuencia de poca longitud. Sin embargo, surgen algunas preguntas: + +- ¿Cómo manejamos las secuencias múltiples? +- ¿Cómo manejamos las secuencias múltiples *de diferentes longitudes*? +- ¿Son los índices de vocabulario las únicas entradas que permiten que un modelo funcione bien? +- ¿Existe una secuencia demasiado larga? + +Veamos qué tipo de problemas plantean estas preguntas, y cómo podemos resolverlos utilizando la API de Transformers 🤗. + +## Los modelos esperan Baches de entrada + +En el ejercicio anterior has visto cómo las secuencias se traducen en listas de números. Convirtamos esta lista de números en un tensor y enviémoslo al modelo: + +{#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) +# Esta línea va a fallar: +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) +# Esta línea va a fallar: +model(input_ids) +``` + +```py out +InvalidArgumentError: Input to reshape is a tensor with 14 values, but the requested shape has 196 [Op:Reshape] +``` +{/if} +¡Oh, no! ¿Por qué ha fallado esto? "Hemos seguido los pasos de la tubería en la sección 2. + +El problema es que enviamos una sola secuencia al modelo, mientras que los modelos de 🤗 Transformers esperan múltiples frases por defecto. Aquí tratamos de hacer todo lo que el tokenizador hizo detrás de escena cuando lo aplicamos a una `secuencia`, pero si te fijas bien, verás que no sólo convirtió la lista de IDs de entrada en un tensor, sino que le agregó una dimensión encima: + +{#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} +Intentémoslo de nuevo y añadamos una nueva dimensión encima: + + +{#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} +Imprimimos los IDs de entrada así como los logits resultantes - aquí está la salida: + +{#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} +*El "batching"* es el acto de enviar varias frases a través del modelo, todas a la vez. Si sólo tienes una frase, puedes construir un lote con una sola secuencia: + +``` +batched_ids = [ids, ids] +``` + +Se trata de un lote de dos secuencias idénticas. + + + +✏️ **Try it out!** Convierte esta lista `batched_ids` en un tensor y pásalo por tu modelo. Comprueba que obtienes los mismos logits que antes (¡pero dos veces!). + + + +La creación de lotes permite que el modelo funcione cuando lo alimentas con múltiples sentencias. Utilizar varias secuencias es tan sencillo como crear un lote con una sola secuencia. Sin embargo, hay un segundo problema. Cuando se trata de agrupar dos (o más) frases, éstas pueden ser de diferente longitud. Si alguna vez ha trabajado con tensores, sabrá que deben tener forma rectangular, por lo que no podrá convertir la lista de IDs de entrada en un tensor directamente. Para evitar este problema, usamos el *padding* para las entradas. + +## Padding a las entradas + +La siguiente lista de listas no se puede convertir en un tensor: + +```py no-format +batched_ids = [ + [200, 200, 200], + [200, 200] +] +``` +Para solucionar esto, utilizaremos *padding* para que nuestros tensores tengan una forma rectangular. El acolchado asegura que todas nuestras sentencias tengan la misma longitud añadiendo una palabra especial llamada *padding token* a las sentencias con menos valores. Por ejemplo, si tienes 10 frases con 10 palabras y 1 frase con 20 palabras, el relleno asegurará que todas las frases tengan 20 palabras. En nuestro ejemplo, el tensor resultante tiene este aspecto: + +```py no-format +padding_id = 100 + +batched_ids = [ + [200, 200, 200], + [200, 200, padding_id], +] +``` +El ID del *padding token* se puede encontrar en `tokenizer.pad_token_id`. Usémoslo y enviemos nuestras dos sentencias a través del modelo de forma individual y por lotes: + + +{#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} + +Hay un problema con los logits en nuestras predicciones por lotes: la segunda fila debería ser la misma que los logits de la segunda frase, ¡pero tenemos valores completamente diferentes! + +Esto se debe a que la característica clave de los modelos Transformer son las capas de atención que *contextualizan* cada token. Éstas tendrán en cuenta los tokens de relleno, ya que atienden a todos los tokens de una secuencia. Para obtener el mismo resultado al pasar oraciones individuales de diferente longitud por el modelo o al pasar un lote con las mismas oraciones y el padding aplicado, tenemos que decirles a esas capas de atención que ignoren los tokens de padding. Esto se hace utilizando una máscara de atención. + +## Máscaras de atención + +*Las máscaras de atención* son tensores con la misma forma que el tensor de IDs de entrada, rellenados con 0s y 1s: los 1s indican que los tokens correspondientes deben ser atendidos, y los 0s indican que los tokens correspondientes no deben ser atendidos (es decir, deben ser ignorados por las capas de atención del modelo). + +{#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} + +Ahora obtenemos los mismos logits para la segunda frase del lote. + +Podemos ver que el último valor de la segunda secuencia es un ID de relleno, que es un valor 0 en la máscara de atención. + + + +✏️ **Try it out!** Aplique la tokenización manualmente a las dos frases utilizadas en la sección 2 ("Llevo toda la vida esperando un curso de HuggingFace" y "¡Odio tanto esto!"). Páselas por el modelo y compruebe que obtiene los mismos logits que en la sección 2. Ahora júntalos usando el token de relleno, y luego crea la máscara de atención adecuada. Comprueba que obtienes los mismos resultados al pasarlos por el modelo. + + + +## Secuencias largas + +Con los modelos Transformer, hay un límite en la longitud de las secuencias que podemos pasar a los modelos. La mayoría de los modelos manejan secuencias de hasta 512 o 1024 tokens, y se bloquean cuando se les pide que procesen secuencias más largas. Hay dos soluciones a este problema: + +- Usar un modelo que soporte secuencias largas +- Truncar tus secuencias + +Los modelos tienen diferentes longitudes de secuencia soportadas, y algunos se especializan en el manejo de secuencias muy largas. Un ejemplo es [Longformer](https://huggingface.co/transformers/model_doc/longformer.html) y otro es [LED](https://huggingface.co/transformers/model_doc/led.html). Si estás trabajando en una tarea que requiere secuencias muy largas, te recomendamos que eches un vistazo a esos modelos. + +En caso contrario, le recomendamos que trunque sus secuencias especificando el parámetro `max_sequence_length`: + +```py +sequence = sequence[:max_sequence_length] +``` diff --git a/chapters/fa/_toctree.yml b/chapters/fa/_toctree.yml index c3b653696..179c9d87a 100644 --- a/chapters/fa/_toctree.yml +++ b/chapters/fa/_toctree.yml @@ -1,3 +1,13 @@ +- title: راه‌اندازی + sections: + - local: chapter0/1 + title: مقدمه + +- title: ۱- مدل‌های ترنسفورمر + sections: + - local: chapter1/1 + title: مقدمه + - title: ۲- بکارگیری ترنسفورمرهای هاگینگ‌فیس sections: - local: chapter2/1 diff --git a/chapters/fa/chapter0/1.mdx b/chapters/fa/chapter0/1.mdx new file mode 100644 index 000000000..d46295940 --- /dev/null +++ b/chapters/fa/chapter0/1.mdx @@ -0,0 +1,149 @@ +
+# مقدمه + + +به دوره‌ی آموزشی هاگینگ‌فیس خوش آمدید! این مقدمه شما را در طی مراحل راه‌اندازی محیط کار راهنمایی می‌کند. اگر تازه این دوره را شروع کرده‌اید، پیشنهاد می‌کنیم ابتدا نگاهی به [فصل اول](/course/chapter1) بیاندازید و سپس به این بخش بازگشته تا محیط کاری را راه‌اندازی کنید و بتوانید خودتان کد را اجرا کنید. + +همه کتابخانه‌هایی که در این دوره‌ی آموزشی استفاده خواهیم کرد، پکیج‌های پایتون هستند. در این بخش می‌بینیم که چگونه باید محیط کار پایتون را راه‌اندازی نموده و کتابخانه‌های مورد نیاز را نصب کنید. + +ما دو شیوه راه‌اندازی محیط کار، یکی استفاده از نوت‌بوک کولَب و دیگری استفاده از محیط مجازی پایتون را نشان خواهیم داد. می‌توانید هرکدام را که می‌خواهید انتخاب کنید. اگر تازه‌کارها هستید، توصیه مؤکد داریم که از نوت‌بوک کولَب استفاده کنید. + +توجه کنید که به محیط ویندوز نخواهیم پرداخت. اگر از ویندوز استفاده می‌کنید توصیه می‌کنیم از نوت‌بوک‌های کولَب استفاده کنید. اگر از سیستم‌عامل مک یا یکی از توزیع‌های لینوکس استفاده می‌کنید می‌توانید هر‌کدام از روش‌هایی که در اینجا ارائه می‌کنیم را دنبال کنید. + +برای طی بخش زیادی از این دوره نیاز به حساب کاربری ‌هاگینگ‌فیس‌ دارید. پیشنهاد می‌کنیم همین الان [حساب خود را بسازید](https://huggingface.co/join). + +

استفاده از نوت‌بوک‌ کولَب گوگل

+ +استفاده از نوت‌بوک کولَب ساده‌ترین راه شروع است. در مرورگر خود نوت‌بوکی جدید باز کرده و بلافاصله شروع به کد زدن کنید! + +اگر با کولَب آشنایی ندارید پیشنهاد می‌کنیم از این [راهنما](https://colab.research.google.com/notebooks/intro.ipynb) استفاده کنید. کولَب به شما امکان استفاده از سخت‌افزار‌‌‌های شتاب‌دهنده مانند GPU یا TPU می‌‌دهد و استفاده از آن برای محاسبات سبک رایگان است. + +وقتی که با محیط کاربری کولَب آشنا شدید، نوت‌بوکی جدید بسازید و مراحل راه‌اندازی را شروع کنید. +
+ +
+An empty colab notebook +
+
+ +قدم اول نصب کتابخانه‌هایی است که در این دوره استفاده خواهیم کرد. برای نصب کتابخانه‌ها از `pip` استفاده می‌کنیم که پکیج‌منیجر پایتون است. در فضای نوت‌بوک، برای اجرای دستورهای سیستمی، کافی است علامت `!` را به ابتدای خط اضافه کنید. برای نصب کتابخانه ترنسفورمرهای هاگینگ‌فیس این دستور را اجرا کنید: + +
+ + ``` + !pip install transformers + ``` + +
+ +برای اطمینان از نصب صحیح این پکیج، آن را ایمپورت کنید: + +
+ + ``` + import transformers + ``` + +
+ +
+
+A gif showing the result of the two commands above: installation and import +
+
+ +این دستور نسخه‌ای بسیار کم حجم از ترنسفورمرهای هاگینگ‌فیس را نصب می‌کند بدون آنکه فریمورک‌ یادگیری ماشین مشخصی مانند پایتورچ یا تنسورفلو را اضافه کند. با توجه به اینکه ما از بسیاری از قابلیت‌های مختلف این کتابخانه استفاده خواهیم کرد، پیشنهاد می‌کنیم نسخه توسعه‌ی این کتابخانه، که حاوی تمام پکیج‌های وابسته برای تقریبا همه مسائل قابل تصور است، را نصب کنید: + +
+ + ``` + !pip install transformers[sentencepiece] + ``` + +
+ +اجرای این فرمان کمی بیشتر طول می‌کشد ولی برای طی بقیه دوره نیازی به نصب پکیج دیگری نخواهید داشت! + +

استفاده از محیط مجازی پایتون

+ +اگر ترجیح می‌دهید از یکی از محیط‌های مجازی پایتون استفاده کنید، اولین مرحله نصب پایتون روی سیستم‌تان است. پیشنهاد می‌کنیم از این [راهنما](https://realpython.com/installing-python/) استفاده کنید. +اگر پایتون را نصب کردید، می‌توانید فرمان‌های پایتون را در ترمینال اجرا کنید. قبل از اینکه به سراغ مراحل بعدی بروید، با اجرای دستور `python --version` از نصب صحیح پایتون مطمئن شوید. این دستور، نسخه‌ی پایتون نصب شده روی سیستم‌تان را نمایش می‌دهد. + + +زمانی که فرمان‌های پایتون را در ترمینال اجرا می کنید، نسخه "اصلی” پایتون روی سیستم خود را درگیر می کنید. توصیه می کنیم این نسخه‌ را تمیز نگه دارید و هیچ پکیج اضافه‌ای روی آن نصب نکنید، بلکه از آن صرفا برای ایجاد محیط‌های مجازی دیگر و برای پروژه‌های مختلف استفاده کنید. با این روش هر پروژه می‌تواند وابستگی‌های مخصوص به خود را داشته‌باشد و دیگر نیازی نیست نگران ناسازگاری‌‌‌های احتمالی میان پکیج‌های نصب شده برای پروژه‌های مختلف باشید. + +این کار در پایتون با استفاده از [محیط‌های مجازی](https://docs.python.org/3/tutorial/venv.html) انجام می‌شود. محیط مجازی، پوشه ای قائم به خود روی فایل‌سیستم است که محتوی نسخه‌ای مشخص از پایتون به همراه تمام پکیج‌های مورد استفاده در پروژه‌ای خاص است. ساخت این پوشه با ابزارهای مختلفی امکان‌پذیر است. ما در اینجا از ابزار رسمی پایتون به نام [`venv`](https://docs.python.org/3/library/venv.html#module-venv) استفاده می‌کنیم. + +ابتدا پوشه‌ای جدید برای پروژه خود ایجاد کنید. برای مثال پوشه‌ای به نام transformers-course زیر پوشه‌ی خانه خودتان در فایل‌سیستم بسازید: + +
+ + ``` + mkdir ~/transformers-course + cd ~/transformers-course + ``` + +
+ +درون این پوشه، با استفاده از ماژول `venv` پایتون، محیط مجازی خود را بسازید: + +
+ + ``` + python -m venv .env + ``` + +
+ +حالا می‌بایست زیر پوشه پروژه شما تنها یک پوشه دیگر به نام .env وجود داشته باشد. + +
+ + ``` + ls -a + . .. .env + ``` + +
+ +برای ورود و خروج از محیط مجازی پروژه خود از اسکریپت‌های activate و deactivate استفاده کنید: + +
+ + ``` + \# Activate the virtual environment + source .env/bin/activate + + \# Deactivate the virtual environment + source .env/bin/deactivate + ``` + +
+ + +‌با اجرای دستور `which python` از فعال شدن محیط مجازی خود اطمینان حاصل کنید. اگر این دستور به آدرس محیط مجازی جدید اشاره کند، با موفقیت این محیط را فعال کرده‌اید. + +
+ + ``` + which python + /home/<user>/transformers-course/.env/bin/python + ``` + +
+ +

نصب وابستگی‌ها

+ +مانند آنچه در بخش استفاده از گوگل کولَب گفتیم، اکنون باید پکیج‌های موردنیاز برای ادامه دوره را نصب کنید. می‌توانید نسخه توسعه‌ی پکیج ترنسفورمرهای هاگینگ‌فیس را با استفاده از پکیج‌منیجر `pip` نصب کنید: + +
+ + ``` + pip install "transformers[sentencepiece]" + ``` + +
+ +شما تمام مراحل راه‌اندازی را طی کرده و آماده شروع دوره هستید! + +
diff --git a/chapters/fa/chapter1/1.mdx b/chapters/fa/chapter1/1.mdx new file mode 100644 index 000000000..03e429b99 --- /dev/null +++ b/chapters/fa/chapter1/1.mdx @@ -0,0 +1,7 @@ +
+# مقدمه + + +به دوره‌ی آموزشی هاگینگ‌فیس خوش آمدید! + +
diff --git a/chapters/fa/glossary/1.mdx b/chapters/fa/glossary/1.mdx index 8f5229155..8587b671b 100644 --- a/chapters/fa/glossary/1.mdx +++ b/chapters/fa/glossary/1.mdx @@ -27,30 +27,95 @@ | Configuration | تنظیمات | | Batch | بتچ | | Model Hub | Model Hub | +| Course | دوره‌ی آموزشی | +| Working Environment | محیط کار | +| Workspace | فضای کار | +| Setup | راه‌اندازی | +| Create | ایجاد یا ساختن | +| Code | کد | +| Package | پکیج‌ | +| Python | پایتون | +| Colab Notebook | نوت‌بوک کولَب | +| Google | گوگل | +| Windows | ویندوز | +| macOS | سیستم‌عامل مک | +| Distribution | توزیع | +| Linux | لینوکس | +| Workload | محاسبه، محاسبات | +| Package Manager | پکیج‌منیجر | +| Command | دستور یا فرمان | +| Feature | قابلیت‌ | +| Development | توسعه نرم‌افزار | +| Dependency | وابسته، وابستگی | +| Virtual Environment | محیط مجازی | +| Terminal | ترمینال | +| Incompatibility | ناسازگاری | +| Self-Contained | قائم به خود یا بدون وابستگی خارجی؟ | +| Script | اسکریپت‌ | +| Feature | قابلیت | +| Folder | پوشه | -Acronyms: +معادل‌هایی که استفاده نمی‌کنیم: -| معادل در منبع | معادل در ترجمه | +| معادل در منبع | معادل اشتباه در ترجمه | |-----------------------|------------------| -| NLP | NLP | -| API | API | +| Application, as in an app and not as in apply | کاربرد | -Convenience Acronyms: +کلمات مخفف: | معادل در منبع | معادل در ترجمه | |-----------------------|------------------| +| NLP | NLP | +| API | API | +| GPU | GPU | +| TPU | TPU | | ML | یادگیری ماشین | + +املای مورد استفاده کلمات فارسی: + +|ارائه| + - * Used in chapter1/page2 to loosely mean 'running' the model. If used anywhere else to exact technical meaning we could go with 'استنتاج' probably? But that sounds archaic to me. -- Don't translate industry-accepted acronyms. -- Translate acronyms used for convenience in English to full Persian form. -- Transliterate brand names to minimize mixing of Persian and English - characters, which makes for a fragmentary text. This should be a Persian text - with some English words sprinkled in, only when really necessary. +- Don't translate industry-accepted acronyms. e.g. TPU or GPU. + +- Translate acronyms used for convenience in English to full Persian form. e.g. ML + +- Keep voice active and consistent. Don't overdo it but try to avoid a passive voice. + +- 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. + +- Keep POV consistent. + +- Smaller sentences are better sentences. Apply with nuance. + +- If translating a technical word, keep the choice of Persian equivalent consistent. + If workspace is mohit-e-kari don't just switch to faza-ye-kari. This ofc + does not apply for non-technical choices, as in those cases variety actually + helps keep the text engaging. + +- This is merely a translation. Don't add any technical/contextual information + not present in the original text. Also don't leave stuff out. The creative + choices in composing this information were the original authors' to make. + Our creative choices are in doing a quality translation. + +- Note on translating indefinite articles(a, an) to Persian. Persian syntax does + not have these. Common problem in translations is using + the equivalent of 'One'("Yek", "Yeki") as a direct word-for-word substitute + which makes for ugly Persian. Be creative with Persian word order to avoid this. + + For example, the sentence "we use a library" should be translated as + "ما از کتابخانه‌ای استفاده می‌کنیم" and not "ما از یک کتابخانه استفاده می‌کنیم". + "Yek" here implies a number(1) to the Persian reader, but the original text + was emphasizing the indefinite nature of the library and not its count. + +- Be exact when choosing equivalents for technical words. Package is package. + Library is library. Don't mix and match. diff --git a/chapters/fr/_toctree.yml b/chapters/fr/_toctree.yml index ad9953b2d..ab363315c 100644 --- a/chapters/fr/_toctree.yml +++ b/chapters/fr/_toctree.yml @@ -3,16 +3,36 @@ - local: chapter0/1 title: Introduction +- title: 2. Utilisation de 🤗 Transformers + sections: + - local: chapter2/1 + title: Introduction + - local: chapter2/2 + title: Derrière le pipeline + - local: chapter2/3 + title: Modèles + - local: chapter2/4 + title: Tokenizers + - local: chapter2/5 + title: Manipulation de plusieurs séquences + - local: chapter2/6 + title: Tout assembler + - local: chapter2/7 + title: Utilisation de base terminée ! + - local: chapter2/8 + title: Quiz de fin de chapitre + quiz: 2 + - title: 5. La bibliothèque 🤗 Datasets sections: - local: chapter5/1 title: Introduction - local: chapter5/2 - title: Que faire si mon ensemble de données n'est pas sur le Hub ? + title: Que faire si mon jeu de données n'est pas sur le Hub ? - local: chapter5/3 title: Il est temps de trancher et de découper - local: chapter5/4 - title: Big Data? 🤗 Des jeux de données à la rescousse ! + title: Données massives ? 🤗 Des jeux de données à la rescousse ! - local: chapter5/5 title: Création de votre propre jeu de données - local: chapter5/6 @@ -21,4 +41,4 @@ title: 🤗 Datasets, vérifié ! - local: chapter5/8 title: Quiz de fin de chapitre - quiz: 5 \ No newline at end of file + quiz: 5 diff --git a/chapters/fr/chapter0/1.mdx b/chapters/fr/chapter0/1.mdx index 14aa13313..3dd008c1a 100644 --- a/chapters/fr/chapter0/1.mdx +++ b/chapters/fr/chapter0/1.mdx @@ -1,28 +1,29 @@ # Introduction -Bienvenue au cours Hugging Face ! Cette introduction vous guidera dans la mise en place d'un environnement de travail. Si vous venez de commencer le cours, nous vous recommandons de consulter d'abord le [Chapitre 1](/course/chapter1), puis de revenir et de configurer votre environnement afin de pouvoir essayer le code vous-même. +Bienvenue au cours d'Hugging Face ! Cette introduction est là pour vous guider dans la mise en place d'un environnement de travail. Si vous venez de commencer le cours, nous vous recommandons de consulter d'abord le [Chapitre 1](/course/fr/chapter1) puis de revenir et de configurer votre environnement afin de pouvoir essayer le code vous-même. -Toutes les bibliothèques que nous utiliserons dans ce cours sont disponibles sous forme de packages Python. Nous allons donc vous montrer comment configurer un environnement Python et installer les bibliothèques spécifiques dont vous aurez besoin. +Toutes les bibliothèques que nous utiliserons dans ce cours sont disponibles sous forme de *packages* Python. Nous allons donc vous montrer comment configurer un environnement Python et installer les bibliothèques spécifiques dont vous aurez besoin. -Nous aborderons deux façons de configurer votre environnement de travail, en utilisant un notebook google Colab ou un environnement virtuel Python. N'hésitez pas à choisir celle qui vous convient le mieux. Pour les débutants, nous vous recommandons vivement de commencer par utiliser un notebook google colab. +Nous aborderons deux façons de configurer votre environnement de travail : soit en utilisant un *notebook* Google Colab, soit en utilisant un environnement virtuel Python. N'hésitez pas à choisir celle qui vous convient le mieux. Pour les débutants, nous vous recommandons vivement de commencer en utilisant un *notebook* Google Colab. -Notez que nous ne couvrirons pas le système Windows. Si vous travaillez sous Windows, nous vous recommandons de suivre le cours en utilisant un cahier Colab. Si vous utilisez une distribution Linux ou macOS, vous pouvez utiliser l'une des deux approches décrites ici. +Notez que nous ne couvrirons pas le système Windows. Si vous travaillez sous Windows, nous vous recommandons de suivre le cours en utilisant un *notebook* Google Colab. Si vous utilisez une distribution Linux ou macOS, vous pouvez utiliser l'une des deux approches décrites ci-dessous. -La plupart du cours repose sur le fait que vous ayez un compte Hugging Face. Nous vous recommandons d'en créer un dès maintenant :[créer un compte](https://huggingface.co/join). +La plupart du cours repose sur le fait que vous ayez un compte Hugging Face. Si vous n'en disposez pas d'un, nous vous recommandons d'en créer un dès maintenant : [créer un compte](https://huggingface.co/join). -## Utilisatoin d'un notebook Google Colab +## Utilisatoin d'un *notebook* Google Colab -L'utilisation d'un notebook google Colab est la configuration la plus simple possible ; démarrez un notebook colab dans votre navigateur et passez directement au codage ! +L'utilisation d'un *notebook* Google Colab est la configuration la plus simple possible. Démarrez un *notebook* dans votre navigateur et passez directement au codage ! -Si vous n'êtes pas familier avec Colab, nous vous recommandons de commencer par suivre les [introduction](https://colab.research.google.com/notebooks/intro.ipynb).Colab vous permet d'utiliser du matériel d'accélération, comme les GPU ou les TPU, et il est gratuit pour les petites charges de travail. +Si vous n'êtes pas familier avec Colab, nous vous recommandons de commencer par suivre l'[introduction](https://colab.research.google.com/notebooks/intro.ipynb). Colab vous permet d'utiliser du matériel comme les GPUs ou les TPUs et est gratuit pour les petites charges de travail. -Une fois que vous vous sentez à l'aise dans Colab, créez un nouveau notebook et commencez à le configurer : +Une fois que vous vous sentez suffisamment à l'aise avec Colab, créez un nouveau *notebook* et commencez à le configurer :
An empty colab notebook
-L'étape suivante consiste à installer les bibliothèques que nous allons utiliser dans ce cours. Nous utiliserons `pip` pour l'installation, qui est le gestionnaire de paquets pour Python. Dans les notebooks, vous pouvez exécuter des commandes système en les faisant précéder du caractère `!`, vous pouvez donc installer la bibliothèque 🤗 Transformers comme suit : +L'étape suivante consiste à installer les bibliothèques que nous allons utiliser dans ce cours. Nous utiliserons `pip` pour l'installation qui est le gestionnaire de *packages* pour Python. Dans les *notebooks*, vous pouvez exécuter des commandes système en les faisant précéder du caractère `!`. Vous pouvez donc installer la bibliothèque 🤗 *Transformers* comme suit : + ``` !pip install transformers ``` @@ -37,22 +38,24 @@ import transformers A gif showing the result of the two commands above: installation and import -Cela installe une version très légère de 🤗 Transformers. En particulier, aucun framework d'apprentissage automatique spécifique (comme PyTorch ou TensorFlow) n'est installé. Comme nous utiliserons de nombreuses fonctionnalités différentes de la bibliothèque, nous recommandons d'installer la version de développement, qui est livrée avec toutes les dépendances requises pour à peu près tous les cas d'utilisation imaginables : +Cela installe une version très légère de 🤗 *Transformers*. En particulier, aucun *framework* d'apprentissage automatique spécifique (comme PyTorch ou TensorFlow) n'est installé. Comme nous utiliserons de nombreuses fonctionnalités différentes de la bibliothèque, nous recommandons d'installer la version de développement qui est livrée avec toutes les dépendances requises pour à peu près tous les cas d'utilisation imaginables : ``` !pip install transformers[sentencepiece] ``` + Cela prendra un peu de temps, mais vous serez alors prêt pour le reste du cours ! + ## Utilisation d'un environnement virtuel Python Si vous préférez utiliser un environnement virtuel Python, la première étape consiste à installer Python sur votre système. Nous vous recommandons de suivre [ce guide](https://realpython.com/installing-python/) pour commencer. -Une fois que Python est installé, vous devriez être en mesure d'exécuter des commandes Python dans votre terminal. Vous pouvez commencer par exécuter la commande suivante pour vous assurer qu'il est correctement installé avant de passer aux étapes suivantes : `python --version`. Cette commande devrait vous indiquer la version de Python disponible sur votre système. +Une fois Python installé, vous devriez être en mesure d'exécuter des commandes Python dans votre terminal. Vous pouvez commencer par exécuter la commande suivante pour vous assurer qu'il est correctement installé avant de passer aux étapes suivantes : `python --version`. Cette commande devrait vous indiquer la version de Python disponible sur votre système. -Lorsque vous exécutez une commande Python dans votre terminal, comme `python --version`, vous devez considérer le programme qui exécute votre commande comme la fonction "main" Python sur votre système. Nous vous recommandons de garder cette installation principale libre de tout paquet, et de l'utiliser pour créer des environnements séparés pour chaque application sur laquelle vous travaillez - de cette façon, chaque application peut avoir ses propres dépendances et paquets, et vous n'aurez pas à vous soucier de problèmes potentiels de compatibilité avec d'autres applications. +Lorsque vous exécutez une commande Python dans votre terminal, comme `python --version`, vous devez considérer le programme qui exécute votre commande comme la fonction « main » Python sur votre système. Nous vous recommandons de garder cette installation principale libre de tout *package* et de l'utiliser pour créer des environnements séparés pour chaque application sur laquelle vous travaillez. De cette façon, chaque application peut avoir ses propres dépendances et *packages*, et vous n'aurez pas à vous soucier de problèmes potentiels de compatibilité avec d'autres applications. -En Python, cela se fait avec les [*environnements virtuels*](https://docs.python.org/3/tutorial/venv.html), qui sont des arbres de répertoires autonomes contenant chacun une installation Python avec une version particulière de Python ainsi que tous les paquets dont l'application a besoin. La création d'un tel environnement virtuel peut se faire à l'aide d'un certain nombre d'outils différents, mais nous utiliserons le paquetage officiel de Python à cette fin, qui s'appelle [`venv`](https://docs.python.org/3/library/venv.html#module-venv). +En Python, cela se fait avec les [*environnements virtuels*](https://docs.python.org/3/tutorial/venv.html), qui sont des arbres de répertoires autonomes contenant chacun une installation Python avec une version particulière de Python ainsi que tous les *packages* dont l'application a besoin. La création d'un tel environnement virtuel peut se faire à l'aide d'un certain nombre d'outils différents, mais nous utiliserons le *package* officiel de Python : [`venv`](https://docs.python.org/3/library/venv.html#module-venv). Tout d'abord, créez le répertoire dans lequel vous souhaitez que votre application se trouve. Par exemple, vous pouvez créer un nouveau répertoire appelé *transformers-course* à la racine de votre répertoire personnel : ``` @@ -98,10 +101,10 @@ which python ### Installation des dépendances -Comme dans la section précédente sur l'utilisation des instances Google Colab, vous devez maintenant installer les packages requis pour continuer. Encore une fois, vous pouvez installer la version de développement de 🤗 Transformers à l'aide du gestionnaire de packages `pip` : +Comme dans la section précédente sur l'utilisation des instances Google Colab, vous devez maintenant installer les *packages* requis pour continuer. Encore une fois, vous pouvez installer la version de développement de 🤗 *Transformers* à l'aide du gestionnaire de packages `pip` : ``` pip install "transformers[sentencepiece]" ``` -Vous êtes maintenant prêt ! \ No newline at end of file +Vous êtes maintenant prêt ! diff --git a/chapters/fr/chapter2/1.mdx b/chapters/fr/chapter2/1.mdx new file mode 100644 index 000000000..bf0c05190 --- /dev/null +++ b/chapters/fr/chapter2/1.mdx @@ -0,0 +1,24 @@ +# Introduction + +Comme vous l'avez vu dans le [Chapitre 1](/course/fr/chapter1), les *transformers* sont généralement très grands. Pouvant aller de plusieurs millions à des dizaines de milliards de paramètres, l'entraînement et le déploiement de ces modèles est une entreprise compliquée. De plus, avec de nouveaux modèles publiés presque quotidiennement et ayant chacun sa propre implémentation, les essayer tous n'est pas une tâche facile. + +La bibliothèque 🤗 *Transformers* a été créée pour résoudre ce problème. Son objectif est de fournir une API unique à travers laquelle tout modèle de *transformers* peut être chargé, entraîné et sauvegardé. Les principales caractéristiques de la bibliothèque sont : + +- **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. + +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. +En plus de rendre les modèles plus accessibles et compréhensibles, cela vous permet d'expérimenter des choses facilement sur un modèle sans affecter les autres. + +Ce chapitre commence par un exemple de bout en bout où nous utilisons un modèle et un *tokenizer* ensemble pour reproduire la fonction `pipeline()` introduite dans le [Chapitre 1](/course/chapter1). +Ensuite, nous aborderons l'API *model* : nous nous plongerons dans les classes de modèle et de configuration, nous verrons comment charger un modèle et enfin comment il traite les entrées numériques pour produire des prédictions. + +Nous examinerons ensuite l'API *tokenizer* qui est l'autre composant principal de la fonction `pipeline()`. +Les *tokenizers* s'occupent de la première et de la dernière étape du traitement en gérant la conversion du texte en entrées numériques pour le réseau neuronal et la reconversion en texte lorsqu'elle est nécessaire. +Enfin, nous montrerons comment gérer l'envoi de plusieurs phrases à travers un modèle dans un batch préparé et nous conclurons le tout en examinant de plus près la fonction `tokenizer()`. + + +⚠️ Afin de bénéficier de toutes les fonctionnalités disponibles avec le Model Hub et le 🤗 *Transformers*, nous vous recommandons de créer un compte. + diff --git a/chapters/fr/chapter2/2.mdx b/chapters/fr/chapter2/2.mdx new file mode 100644 index 000000000..f53b1e891 --- /dev/null +++ b/chapters/fr/chapter2/2.mdx @@ -0,0 +1,344 @@ + + +# Derrière le pipeline + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + +Il s'agit de la première section dont le contenu est légèrement différent selon que vous utilisez PyTorch ou TensorFlow. Cliquez sur le bouton situé au-dessus du titre pour sélectionner la plateforme que vous préférez ! + + +{#if fw === 'pt'} + +{:else} + +{/if} + +Commençons par un exemple complet en regardant ce qui s'est passé en coulisses lorsque nous avons exécuté le code suivant dans le [Chapitre 1](/course/chapter1) : + +```python +from transformers import pipeline + +classifier = pipeline("sentiment-analysis") +classifier( + [ + "I've been waiting for a HuggingFace course my whole life.", # J'ai attendu un cours de HuggingFace toute ma vie. + "I hate this so much!", # Je déteste tellement ça ! + ] +) +``` + +la sortie : + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}, + {'label': 'NEGATIVE', 'score': 0.9994558095932007}] +``` + +Comme nous l'avons vu dans le [Chapitre 1](/course/fr/chapter1), ce pipeline regroupe trois étapes : le prétraitement, le passage des entrées dans le modèle et le post-traitement : + +
+The full NLP pipeline: tokenization of text, conversion to IDs, and inference through the Transformer model and the model head. + +
+ +Passons rapidement en revue chacun de ces éléments. + +## Prétraitement avec un *tokenizer* + +Comme d'autres réseaux de neurones, les *transformers* ne peuvent pas traiter directement le texte brut, donc la première étape de notre pipeline est de convertir les entrées textuelles en nombres afin que le modèle puisse les comprendre. Pour ce faire, nous utilisons un *tokenizer*, qui sera responsable de : +- diviser l'entrée en mots, sous-mots, ou symboles (comme la ponctuation) qui sont appelés *tokens*, +- associer chaque *token* à un nombre entier, +- ajouter des entrées supplémentaires qui peuvent être utiles au modèle. + +Tout ce prétraitement doit être effectué exactement de la même manière que celui appliqué lors du pré-entraînement du modèle. Nous devons donc d'abord télécharger ces informations depuis le [*Model Hub*](https://huggingface.co/models). Pour ce faire, nous utilisons la classe `AutoTokenizer` et sa méthode `from_pretrained()`. En utilisant le nom du *checkpoint* de notre modèle, elle va automatiquement récupérer les données associées au *tokenizer* du modèle et les mettre en cache (afin qu'elles ne soient téléchargées que la première fois que vous exécutez le code ci-dessous). + +Puisque le *checkpoint* par défaut du pipeline `sentiment-analysis` (analyse de sentiment) est `distilbert-base-uncased-finetuned-sst-2-english` (vous pouvez voir la carte de ce modèle [ici](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english)), nous exécutons ce qui suit : + +```python +from transformers import AutoTokenizer + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` + +Une fois que nous avons le *tokenizer* nous pouvons lui passer directement nos phrases et obtenir un dictionnaire prêt à être donné à notre modèle ! La seule chose qui reste à faire est de convertir en tenseurs la liste des identifiants d'entrée. + +Vous pouvez utiliser 🤗 *Transformers* sans avoir à vous soucier du *framework* utilisé comme *backend*. Il peut s'agir de PyTorch, de TensorFlow ou de Flax pour certains modèles. Cependant, les *transformers* n'acceptent que les *tenseurs* en entrée. Si c'est la première fois que vous entendez parler de tenseurs, vous pouvez les considérer comme des tableaux NumPy. Un tableau NumPy peut être un scalaire (0D), un vecteur (1D), une matrice (2D), ou avoir davantage de dimensions. Les tenseurs des autres *frameworks* d'apprentissage machine se comportent de manière similaire et sont généralement aussi simples à instancier que les tableaux NumPy. + +Pour spécifier le type de tenseurs que nous voulons récupérer (PyTorch, TensorFlow, ou simplement NumPy), nous utilisons l'argument `return_tensors` : + +{#if fw === 'pt'} +```python +raw_inputs = [ + "I've been waiting for a HuggingFace course my whole life.", # J'ai attendu un cours de HuggingFace toute ma vie. + "I hate this so much!", # Je déteste tellement ça ! +] +inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="pt") +print(inputs) +``` +{:else} +```python +raw_inputs = [ + "I've been waiting for a HuggingFace course my whole life.", # J'ai attendu un cours de HuggingFace toute ma vie. + "I hate this so much!", # Je déteste tellement ça ! +] +inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="tf") +print(inputs) +``` +{/if} + +Ne vous préoccupez pas encore du remplissage (*padding*) et de la troncature, nous les expliquerons plus tard. Les principales choses à retenir ici sont que vous pouvez passer une phrase ou une liste de phrases, ainsi que spécifier le type de tenseurs que vous voulez récupérer (si aucun type n'est passé, par défaut vous obtiendrez une liste de listes comme résultat). + +{#if fw === 'pt'} + +Voici à quoi ressemblent les résultats sous forme de tenseurs PyTorch : + +```python out +{ + 'input_ids': tensor([ + [ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012, 102], + [ 101, 1045, 5223, 2023, 2061, 2172, 999, 102, 0, 0, 0, 0, 0, 0, 0, 0] + ]), + '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] + ]) +} +``` +{:else} + +Voici à quoi ressemblent les résultats sous forme de tenseurs TensorFlow : + +```python out +{ + 'input_ids': , + 'attention_mask': +} +``` +{/if} + +La sortie elle-même est un dictionnaire contenant deux clés : `input_ids` et `attention_mask`. `input_ids` contient deux lignes d'entiers (une pour chaque phrase) qui sont les identifiants uniques des *tokens* dans chaque phrase. Nous expliquerons ce qu'est l'`attention_mask` plus tard dans ce chapitre. + +## Passage au modèle + +{#if fw === 'pt'} +Nous pouvons télécharger notre modèle prétraîné de la même manière que nous l'avons fait avec notre *tokenizer*. 🤗 *Transformers* fournit une classe `AutoModel` qui possède également une méthode `from_pretrained()` : + +```python +from transformers import AutoModel + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = AutoModel.from_pretrained(checkpoint) +``` +{:else} +Nous pouvons télécharger notre modèle prétraîné de la même manière que nous l'avons fait avec notre *tokenizer*. 🤗 *Transformers* fournit une classe `TFAutoModel` qui possède également une méthode `from_pretrained()` : + +```python +from transformers import TFAutoModel + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = TFAutoModel.from_pretrained(checkpoint) +``` +{/if} + +Dans cet extrait de code, nous avons téléchargé le même *checkpoint* que nous avons utilisé dans notre pipeline auparavant (il devrait en fait avoir déjà été mis en cache) et instancié un modèle avec lui. + +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. + +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. + +### Un vecteur de grande dimension ? + +Le vecteur produit en sortie par le *transformer* est généralement de grande dimension. Il a généralement trois dimensions : + +- **la taille du lot** : le nombre de séquences traitées à la fois (2 dans notre exemple), +- **la longueur de la séquence** : la longueur de la représentation numérique de la séquence (16 dans notre exemple), +- **la taille cachée** : la dimension du vecteur de chaque entrée du modèle. + +On dit qu'il est de « grande dimension » en raison de la dernière valeur. La taille cachée peut être très grande (généralement 768 pour les petits modèles et pour les grands modèles cela peut atteindre 3072 voire plus). + +Nous pouvons le constater si nous alimentons notre modèle avec les entrées que nous avons prétraitées : + + +{#if fw === 'pt'} +```python +outputs = model(**inputs) +print(outputs.last_hidden_state.shape) +``` + +```python out +torch.Size([2, 16, 768]) +``` +{:else} +```py +outputs = model(inputs) +print(outputs.last_hidden_state.shape) +``` + +```python out +(2, 16, 768) +``` +{/if} + +Notez que les sorties des modèles de la bibliothèque 🤗 Transformers se comportent comme des `namedtuples` ou des dictionnaires. Vous pouvez accéder aux éléments par attributs (comme nous l'avons fait), par clé (`outputs["last_hidden_state"]`), ou même par l’index si vous savez exactement où se trouve la chose que vous cherchez (`outputs[0]`). + +### Les têtes des modèles : donner du sens aux chiffres +Les têtes des modèles prennent en entrée le vecteur de grande dimension des états cachés et le projettent sur une autre dimension. Elles sont généralement composées d'une ou de quelques couches linéaires : +
+A Transformer network alongside its head. + +
+ +La sortie du *transformer* est envoyée directement à la tête du modèle pour être traitée. +Dans ce diagramme, le modèle est représenté par sa couche d’enchâssement et les couches suivantes. La couche d’enchâssement convertit chaque identifiant d'entrée dans l'entrée tokenisée en un vecteur qui représente le *token* associé. Les couches suivantes manipulent ces vecteurs en utilisant le mécanisme d'attention pour produire la représentation finale des phrases. +Il existe de nombreuses architectures différentes disponibles dans la bibliothèque 🤗 *Transformers*, chacune étant conçue autour de la prise en charge d'une tâche spécifique. En voici une liste non exhaustive : +- `*Model` (récupérer les états cachés) +- `*ForCausalLM` +- `*ForMaskedLM` +- `*ForMultipleChoice` +- `*ForQuestionAnswering` +- `*ForSequenceClassification` +- `*ForTokenClassification` +- et autres 🤗 + +{#if fw === 'pt'} +Pour notre exemple, nous avons besoin d'un modèle avec une tête de classification de séquence (pour pouvoir classer les phrases comme positives ou négatives). Donc, nous n'utilisons pas réellement la classe `AutoModel` mais plutôt `AutoModelForSequenceClassification` : +```python +from transformers import AutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +outputs = model(**inputs) +``` +{:else} +Pour notre exemple, nous avons besoin d'un modèle avec une tête de classification de séquence (pour pouvoir classer les phrases comme positives ou négatives). Donc, nous n'utilisons pas réellement la classe ` TFAutoModel` mais plutôt ` TFAutoModelForSequenceClassification` : +```python +from transformers import TFAutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +outputs = model(inputs) +``` +{/if} + +Maintenant, si nous examinons la forme de nos entrées, la dimensionnalité est beaucoup plus faible. La tête du modèle prend en entrée les vecteurs de grande dimension que nous avons vus précédemment et elle produit des vecteurs contenant deux valeurs (une par étiquette) : +```python +print(outputs.logits.shape) +``` + +{#if fw === 'pt'} +```python out +torch.Size([2, 2]) +``` +{:else} +```python out +(2, 2) +``` +{/if} + +Comme nous n'avons que deux phrases et deux étiquettes, le résultat que nous obtenons est de forme 2 x 2 + +## Post-traitement de la sortie + +Les valeurs que nous obtenons en sortie de notre modèle n'ont pas nécessairement de sens en elles-mêmes. Jetons-y un coup d'oeil : + +```python +print(outputs.logits) +``` + +{#if fw === 'pt'} +```python out +tensor([[-1.5607, 1.6123], + [ 4.1692, -3.3464]], grad_fn=) +``` +{:else} +```python out + +``` +{/if} + +Notre modèle a prédit `[-1.5607, 1.6123]` pour la première phrase et `[ 4.1692, -3.3464]` pour la seconde. Ce ne sont pas des probabilités mais des *logits*, les scores bruts, non normalisés, produits par la dernière couche du modèle. Pour être convertis en probabilités, ils doivent passer par une couche [SoftMax](https://fr.wikipedia.org/wiki/Fonction_softmax) (tous les modèles de la bibliotèque 🤗 Transformers sortent les logits car la fonction de perte de l'entraînement fusionne généralement la dernière fonction d'activation, comme la SoftMax, avec la fonction de perte réelle, comme l'entropie croisée) : + +{#if fw === 'pt'} +```py +import torch + +predictions = torch.nn.functional.softmax(outputs.logits, dim=-1) +print(predictions) +``` +{:else} +```py +import tensorflow as tf + +predictions = tf.math.softmax(outputs.logits, axis=-1) +print(predictions) +``` +{/if} + +{#if fw === 'pt'} +```python out +tensor([[4.0195e-02, 9.5980e-01], + [9.9946e-01, 5.4418e-04]], grad_fn=) +``` +{:else} +```python out +tf.Tensor( +[[4.01951671e-02 9.59804833e-01] + [9.9945587e-01 5.4418424e-04]], shape=(2, 2), dtype=float32) +``` +{/if} + +Maintenant nous pouvons voir que le modèle a prédit `[0.0402, 0.9598]` pour la première phrase et `[0.9995, 0.0005]` pour la seconde. Ce sont des scores de probabilité reconnaissables. + +Pour obtenir les étiquettes correspondant à chaque position, nous pouvons inspecter l'attribut `id2label` de la configuration du modèle (plus de détails dans la section suivante) : + +```python +model.config.id2label +``` + +```python out +{0: 'NEGATIVE', 1: 'POSITIVE'} +``` + +Nous pouvons maintenant conclure que le modèle a prédit ce qui suit : + +- première phrase : NEGATIVE: 0.0402, POSITIVE: 0.9598 +- deuxième phrase : NEGATIVE: 0.9995, POSITIVE: 0.0005 + +Nous avons reproduit avec succès les trois étapes du pipeline : prétraitement avec les *tokenizers*, passage des entrées dans le modèle et post-traitement ! Prenons maintenant le temps de nous plonger plus profondément dans chacune de ces étapes. + + +✏️ **Essayez !** Choisissez deux (ou plus) textes de votre choix (en anglais) et faites-les passer par le pipeline `sentiment-analysis`. Reproduisez ensuite vous-même les étapes vues ici et vérifiez que vous obtenez les mêmes résultats ! + diff --git a/chapters/fr/chapter2/3.mdx b/chapters/fr/chapter2/3.mdx new file mode 100644 index 000000000..96e555191 --- /dev/null +++ b/chapters/fr/chapter2/3.mdx @@ -0,0 +1,231 @@ + + +# Les modèles + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + +{:else} + +{/if} + +{#if fw === 'pt'} +Dans cette section, nous allons examiner de plus près la création et l'utilisation d'un modèle. Nous utiliserons la classe `AutoModel` qui est pratique lorsque vous voulez instancier n'importe quel modèle à partir d'un checkpoint. + +La classe `AutoModel` et toutes les classes apparentées sont en fait de simples *wrappers* sur la grande variété de modèles disponibles dans la bibliothèque. C'est une enveloppe intelligente car elle peut automatiquement deviner l'architecture appropriée pour votre *checkpoint* et ensuite instancier un modèle avec cette architecture. + +{:else} +Dans cette section, nous allons examiner de plus près la création et l'utilisation d'un modèle. Nous utiliserons la classe `TFAutoModel` qui est pratique lorsque vous voulez instancier n'importe quel modèle à partir d'un checkpoint. + +La classe `TFAutoModel` et toutes les classes apparentées sont en fait de simples *wrappers* sur la grande variété de modèles disponibles dans la bibliothèque. C'est une enveloppe intelligente car elle peut automatiquement deviner l'architecture appropriée pour votre *checkpoint* et ensuite instancier un modèle avec cette architecture. + +{/if} + +Cependant, si vous connaissez le type de modèle que vous voulez utiliser, vous pouvez utiliser directement la classe qui définit son architecture. Voyons comment cela fonctionne avec un modèle BERT. + +## Création d’un *transformer* + +La première chose que nous devons faire pour initialiser un modèle BERT est de charger un objet configuration : + +{#if fw === 'pt'} +```py +from transformers import BertConfig, BertModel + +# Construire la configuration +config = BertConfig() + +# Construire le modèle à partir de la configuration +model = BertModel(config) +``` +{:else} +```py +from transformers import BertConfig, TFBertModel + +# Construire la configuration +config = BertConfig() + +# Construire le modèle à partir de la configuration +model = TFBertModel(config) +``` +{/if} + +La configuration contient de nombreux attributs qui sont utilisés pour construire le modèle : +```py +print(config) +``` + +```python out +BertConfig { + [...] + "hidden_size": 768, + "intermediate_size": 3072, + "max_position_embeddings": 512, + "num_attention_heads": 12, + "num_hidden_layers": 12, + [...] +} +``` + +Bien que vous n'ayez pas encore vu ce que font tous ces attributs, vous devriez en reconnaître certains : l'attribut `hidden_size` définit la taille du vecteur `hidden_states`, et `num_hidden_layers` définit le nombre de couches que le *transformer* possède. + +### Différentes méthodes de chargement + +Un modèle créé à partir de la configuration par défaut est initialisé avec des valeurs aléatoires : + + +{#if fw === 'pt'} +```py +from transformers import BertConfig, BertModel + +config = BertConfig() +model = BertModel(config) + +# Le modèle est initialisé de façon aléatoire ! +``` +{:else} +```py +from transformers import BertConfig, TFBertModel + +config = BertConfig() +model = TFBertModel(config) + +# Le modèle est initialisé de façon aléatoire ! +``` +{/if} + +Le modèle peut être utilisé tel quel mais il produira du charabia. En effet, il doit d'abord être entraîné. Nous pourrions entraîner le modèle à partir de zéro sur la tâche qui nous intéresse mais comme vous l'avez vu dans le [Chapitre 1](/course/fr/chapter1) cela nécessiterait beaucoup de temps et de données. De plus cela aurait un impact environnemental non négligeable. Pour éviter les efforts inutiles et redondants, il est impératif de pouvoir partager et réutiliser les modèles qui ont déjà été entraînés. + +Charger un *transformer* qui a déjà été entraîné est simple : nous pouvons le faire en utilisant la méthode `from_pretrained()` : + + +{#if fw === 'pt'} +```py +from transformers import BertModel + +model = BertModel.from_pretrained("bert-base-cased") +``` + +Comme vu précédemment, nous pouvons remplacer `BertModel` par la classe équivalente `AutoModel`. A partir de maintenant nous ferons cela car cela produit un code agnostique *checkpoint*, c’est-à-dire que si votre code fonctionne pour un *checkpoint* donné, il devrait fonctionner sans problème avec un autre. Cela s'applique même si l'architecture est différente du moment que le *checkpoint* a été entraîné pour une tâche similaire (par exemple, une tâche d'analyse de sentiments). + +{:else} +```py +from transformers import TFBertModel + +model = TFBertModel.from_pretrained("bert-base-cased") +``` + +Comme vu précédemment, nous pouvons remplacer `TFBertModel` par la classe équivalente `TFAutoModel`. A partir de maintenant nous ferons cela car cela produit un code agnostique *checkpoint*, c’est-à-dire que si votre code fonctionne pour un *checkpoint* donné, il devrait fonctionner sans problème avec un autre. Cela s'applique même si l'architecture est différente du moment que le *checkpoint* a été entraîné pour une tâche similaire (par exemple, une tâche d'analyse de sentiments). + +{/if} + +Dans l'exemple de code ci-dessus, nous n'avons pas utilisé `BertConfig` et avons à la place chargé un modèle pré-entraîné via l'identifiant `bert-base-cased`. Il s'agit d'un *checkpoint* qui a été entraîné par les auteurs de BERT eux-mêmes. Vous pouvez trouver davantage de détails à son sujet dans la [fiche du modèle](https://huggingface.co/bert-base-cased). + +Ce modèle est maintenant initialisé avec tous les poids du *checkpoint*. Il peut être utilisé directement pour l'inférence sur les tâches sur lesquelles il a été entraîné. Il peut également être *finetuné* sur une nouvelle tâche. En entraînant avec des poids pré-entraînés plutôt qu'à partir de zéro, nous pouvons rapidement obtenir de bons résultats. + +Les poids ont été téléchargés et mis en cache (afin que les futurs appels à la méthode `from_pretrained()` ne les retéléchargent pas) dans le dossier cache, qui est par défaut *~/.cache/huggingface/transformers*. Vous pouvez personnaliser votre dossier de cache en définissant la variable d'environnement `HF_HOME`. + +L'identifiant utilisé pour charger le modèle peut être l'identifiant de n'importe quel modèle sur le *Model Hub* du moment qu'il est compatible avec l'architecture BERT. La liste complète des *checkpoints* de BERT disponibles peut être trouvée [ici](https://huggingface.co/models?filter=bert). + +### Méthodes de sauvegarde + +Sauvegarder un modèle est aussi facile que d'en charger un. Nous utilisons la méthode `save_pretrained()`, qui est analogue à la méthode `from_pretrained()` : + + +```py +model.save_pretrained("directory_on_my_computer") +``` + +Cela enregistre deux fichiers sur votre disque : + +{#if fw === 'pt'} +``` +ls directory_on_my_computer + +config.json pytorch_model.bin +``` +{:else} +``` +ls directory_on_my_computer + +config.json tf_model.h5 +``` +{/if} + +Si vous jetez un coup d'œil au fichier *config.json*, vous reconnaîtrez les attributs nécessaires pour construire l'architecture du modèle. Ce fichier contient également certaines métadonnées, comme l'origine du *checkpoint* et la version de la bibliothèque 🤗 *Transformers* que vous utilisiez lors du dernier enregistrement du point *checkpoint*. + +{#if fw === 'pt'} +Le fichier *pytorch_model.bin* est connu comme le *dictionnaire d'état*. Il contient tous les poids de votre modèle. Les deux fichiers vont de pair : la configuration est nécessaire pour connaître l'architecture de votre modèle, tandis que les poids du modèle sont les paramètres de votre modèle. + +{:else} +Le fichier *tf_model.h5* est connu comme le *dictionnaire d'état*. Il contient tous les poids de votre modèle. Les deux fichiers vont de pair : la configuration est nécessaire pour connaître l'architecture de votre modèle, tandis que les poids du modèle sont les paramètres de votre modèle. + +{/if} + +### Utilisation d'un *transformer* pour l'inférence + +Maintenant que vous savez comment charger et sauvegarder un modèle, essayons de l'utiliser pour faire quelques prédictions. Les *transformers* ne peuvent traiter que des nombres. Des nombres que le *tokenizer* génère. Mais avant de parler des *tokenizers*, explorons les entrées que le modèle accepte. + +Les *tokenizers* se chargent de passer les entrées vers les tenseurs du *framework* approprié. Pour vous aider à comprendre ce qui se passe, jetons un coup d'œil rapide à ce qui doit être fait avant d'envoyer les entrées au modèle. + +Disons que nous avons les séquences suivantes : + + +```py +sequences = ["Hello!", "Cool.", "Nice!"] +``` + +Le *tokenizer* les convertit en indices de vocabulaire qui sont généralement appelés *input IDs*. Chaque séquence est maintenant une liste de nombres ! La sortie résultante est : + +```py no-format +encoded_sequences = [ + [101, 7592, 999, 102], + [101, 4658, 1012, 102], + [101, 3835, 999, 102], +] +``` + +Il s'agit d'une liste de séquences encodées : une liste de listes. Les tenseurs n'acceptent que des formes rectangulaires (pensez aux matrices). Ce « tableau » est déjà de forme rectangulaire, donc le convertir en tenseur est facile : + +{#if fw === 'pt'} +```py +import torch + +model_inputs = torch.tensor(encoded_sequences) +``` +{:else} +```py +import tensorflow as tf + +model_inputs = tf.constant(encoded_sequences) +``` +{/if} + +### Utilisation des tenseurs comme entrées du modèle + +L'utilisation des tenseurs avec le modèle est extrêmement simple, il suffit d'appeler le modèle avec les entrées : + + +```py +output = model(model_inputs) +``` + +Bien que le modèle accepte un grand nombre d'arguments différents, seuls les identifiants d'entrée sont nécessaires. Nous expliquerons plus tard ce que font les autres arguments et quand ils sont nécessaires. Avant cela, regardons de plus près les *tokenizers*, cet outil qui construit les entrées qu'un *transformer* peut comprendre. diff --git a/chapters/fr/chapter2/4.mdx b/chapters/fr/chapter2/4.mdx new file mode 100644 index 000000000..a8fb7e3e7 --- /dev/null +++ b/chapters/fr/chapter2/4.mdx @@ -0,0 +1,253 @@ + + +# Les *tokenizers* + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + + +Les *tokenizers* sont l'un des principaux composants du pipeline de NLP. Ils ont un seul objectif : traduire le texte en données pouvant être traitées par le modèle. Les modèles ne pouvant traiter que des nombres, les *tokenizers* doivent convertir nos entrées textuelles en données numériques. Dans cette section, nous allons explorer ce qui se passe exactement dans le pipeline de tokénisation. + +Dans les tâches de NLP, les données traitées sont généralement du texte brut. Voici un exemple de ce type de texte : + + +``` +Jim Henson was a puppeteer # Jim Henson était un marionnettiste. +``` + +Les modèles ne pouvant traiter que des nombres, nous devons trouver un moyen de convertir le texte brut en nombres. C'est ce que font les *tokenizers* et il existe de nombreuses façons de procéder. L'objectif est de trouver la représentation la plus significative, c'est-à-dire celle qui a le plus de sens pour le modèle, et si possible qui soit la plus petite. + +Voyons quelques exemples d'algorithmes de tokénisation et essayons de répondre à certaines des questions que vous pouvez vous poser à ce sujet. + +## *Tokenizer* basé sur les mots + + + + +Le premier type de *tokenizer* qui vient à l'esprit est celui basé sur les mots. Il est généralement très facile à utiliser et configurable avec seulement quelques règles. Il donne souvent des résultats décents. Par exemple, dans l'image ci-dessous, l'objectif est de diviser le texte brut en mots et de trouver une représentation numérique pour chacun d'eux : + +
+ An example of word-based tokenization. + +
+ +Il existe différentes façons de diviser le texte. Par exemple, nous pouvons utiliser les espaces pour segmenter le texte en mots en appliquant la fonction `split()` de Python : + +```py +tokenized_text = "Jim Henson was a puppeteer".split() +print(tokenized_text) +``` + +```python out +['Jim', 'Henson', 'was', 'a', 'puppeteer'] # ['Jim', 'Henson', était, 'un', 'marionnettiste'] +``` + +Il existe également des variantes des *tokenizers* basés sur les mots qui ont des règles supplémentaires pour la ponctuation. Avec ce type de *tokenizers* nous pouvons nous retrouver avec des « vocabulaires » assez larges, où un vocabulaire est défini par le nombre total de *tokens* indépendants que nous avons dans notre corpus. + +Un identifiant est attribué à chaque mot, en commençant par 0 et en allant jusqu'à la taille du vocabulaire. Le modèle utilise ces identifiants pour identifier chaque mot. + +Si nous voulons couvrir complètement une langue avec un *tokenizer* basé sur les mots, nous devons avoir un identifiant pour chaque mot de la langue que nous traitons, ce qui génére une énorme quantité de *tokens*. Par exemple, il y a plus de 500 000 mots dans la langue anglaise. Ainsi pour associer chaque mot à un identifiant, nous devrions garder la trace d'autant d'identifiants. De plus, des mots comme « chien » sont représentés différemment de mots comme « chiens ». Le modèle n'a initialement aucun moyen de savoir que « chien » et « chiens » sont similaires : il identifie les deux mots comme non apparentés. Il en va de même pour d'autres mots similaires, comme « maison » et « maisonnette » que le modèle ne considérera pas comme similaires au départ. + +Enfin, nous avons besoin d'un *token* personnalisé pour représenter les mots qui ne font pas partie de notre vocabulaire. C'est ce qu'on appelle le *token* « inconnu » souvent représenté par « [UNK] » (de l’anglais « unknown ») ou « <unk> ; ». C'est généralement un mauvais signe si vous constatez que le *tokenizer* produit un nombre important de ce jeton spécial. Cela signifie qu’il n'a pas été en mesure de récupérer une représentation sensée d'un mot et que vous perdez des informations en cours de route. L'objectif de l'élaboration du vocabulaire est de faire en sorte que le *tokenizer* transforme le moins de mots possible en *token* inconnu. + +Une façon de réduire la quantité de *tokens* inconnus est d'aller un niveau plus profond, en utilisant un *tokenizer* basé sur les caractères. + + +## *Tokenizer* basé sur les caractères + + + +Les *tokenizers* basés sur les caractères divisent le texte en caractères, plutôt qu'en mots. Cela présente deux avantages principaux : + +- le vocabulaire est beaucoup plus petit +- il y a beaucoup moins de *tokens* hors vocabulaire (inconnus) puisque chaque mot peut être construit à partir de caractères. + +Mais là aussi, des questions se posent concernant les espaces et la ponctuation : + + +
+ An example of character-based tokenization. + +
+ +Cette approche n'est pas non plus parfaite. Puisque la représentation est maintenant basée sur des caractères plutôt que sur des mots, on pourrait dire intuitivement qu’elle est moins significative : chaque caractère ne signifie pas grand-chose en soi, alors que c'est le cas pour les mots. Toutefois, là encore, cela diffère selon la langue. En chinois, par exemple, chaque caractère est porteur de plus d'informations qu'un caractère dans une langue latine. + +Un autre élément à prendre en compte est que nous nous retrouverons avec une très grande quantité de *tokens* à traiter par notre modèle. Alors qu’avec un *tokenizer* basé sur les mots, pour un mot donné on aurait qu'un seul *token*, avec un *tokenizer* basé sur les caractères, cela peut facilement se transformer en 10 *tokens* voire plus. + +Pour obtenir le meilleur des deux mondes, nous pouvons utiliser une troisième technique qui combine les deux approches : la *tokénisation en sous-mots*. + + +## Tokénisation en sous-mots + + + +Les algorithmes de tokenisation en sous-mots reposent sur le principe selon lequel les mots fréquemment utilisés ne doivent pas être divisés en sous-mots plus petits, mais les mots rares doivent être décomposés en sous-mots significatifs. + +Par exemple, le mot « maisonnette » peut être considéré comme un mot rare et peut être décomposé en « maison » et « ette ». Ces deux mots sont susceptibles d'apparaître plus fréquemment en tant que sous-mots autonomes, alors qu'en même temps le sens de « maison » est conservé par le sens composite de « maison » et « ette ». + +Voici un exemple montrant comment un algorithme de tokenisation en sous-mots tokeniserait la séquence « Let's do tokenization ! » : + + +
+ A subword tokenization algorithm. + +
+ +Ces sous-mots finissent par fournir beaucoup de sens sémantique. Par exemple, ci-dessus, « tokenization » a été divisé en « token » et « ization » : deux *tokens* qui ont un sens sémantique tout en étant peu encombrants (seuls deux *tokens* sont nécessaires pour représenter un long mot). Cela nous permet d'avoir une couverture relativement bonne avec de petits vocabulaires et presque aucun *token* inconnu. + +Cette approche est particulièrement utile dans les langues agglutinantes comme le turc, où l'on peut former des mots complexes (presque) arbitrairement longs en enchaînant des sous-mots. + + +### Et plus encore ! + +Il existe de nombreuses autres techniques. Pour n'en citer que quelques-unes : + +- le Byte-level BPE utilisé par exemple dans le GPT-2 +- le WordPiece utilisé par exemple dans BERT +- SentencePiece ou Unigram, utilisés dans plusieurs modèles multilingues. + +Vous devriez maintenant avoir une connaissance suffisante du fonctionnement des tokenizers pour commencer à utiliser l'API. + + +## Chargement et sauvegarde + +Le chargement et la sauvegarde des *tokenizers* est aussi simple que pour les modèles. En fait, c'est basé sur les deux mêmes méthodes : `from_pretrained()` et `save_pretrained()`. Ces méthodes vont charger ou sauvegarder l'algorithme utilisé par le *tokenizer* (un peu comme l'*architecture* du modèle) ainsi que son vocabulaire (un peu comme les *poids* du modèle). + +Le chargement du *tokenizer* de BERT entraîné avec le même *checkpoint* que BERT se fait de la même manière que le chargement du modèle, sauf que nous utilisons la classe `BertTokenizer` : + + +```py +from transformers import BertTokenizer + +tokenizer = BertTokenizer.from_pretrained("bert-base-cased") +``` + +{#if fw === 'pt'} +Similaire à `AutoModel`, la classe `AutoTokenizer` récupère la classe de *tokenizer* appropriée dans la bibliothèque basée sur le nom du *checkpoint*. Elle peut être utilisée directement avec n'importe quel *checkpoint* : + +{:else} +Similaire à `TFAutoModel`, la classe `AutoTokenizer` récupère la classe de *tokenizer* appropriée dans la bibliothèque basée sur le nom du *checkpoint*. Elle peut être utilisée directement avec n'importe quel *checkpoint* : + +{/if} + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +``` + +Nous pouvons à présent utiliser le *tokenizer* comme indiqué dans la section précédente : + +```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]} +``` + +La sauvegarde d'un tokenizer est identique à celle d'un modèle : + +```py +tokenizer.save_pretrained("directory_on_my_computer") +``` + +Nous parlerons plus en détail des `token_type_ids` au [Chapitre 3](/course/fr/chapter3) et nous expliquerons la clé `attention_mask` un peu plus tard. Tout d'abord, voyons comment les `input_ids` sont générés. Pour ce faire, nous devons examiner les méthodes intermédiaires du *tokenizer*. + +## Encodage + + + + +La traduction d'un texte en chiffres est connue sous le nom d’*encodage*. L'encodage se fait en deux étapes : la tokenisation, suivie de la conversion en identifiants d'entrée. + +Comme nous l'avons vu, la première étape consiste à diviser le texte en mots (ou parties de mots, symboles de ponctuation, etc.), généralement appelés *tokens*. De nombreuses règles peuvent régir ce processus. C'est pourquoi nous devons instancier le *tokenizer* en utilisant le nom du modèle afin de nous assurer que nous utilisons les mêmes règles que celles utilisées lors du pré-entraînement du modèle. + +La deuxième étape consiste à convertir ces *tokens* en nombres afin de construire un tenseur à partir de ceux-ci ainsi que de les transmettre au modèle. Pour ce faire, le *tokenizer* possède un *vocabulaire*, qui est la partie que nous téléchargeons lorsque nous l'instancions avec la méthode `from_pretrained()`. Encore une fois, nous devons utiliser le même vocabulaire que celui utilisé lors du pré-entraînement du modèle. + +Pour mieux comprendre les deux étapes, nous allons les explorer séparément. A noter que nous utilisons des méthodes effectuant séparément des parties du pipeline de tokenisation afin de montrer les résultats intermédiaires de ces étapes. Néanmoins, en pratique, il faut appeler le *tokenizer* directement sur vos entrées (comme indiqué dans la section 2). + +### Tokenisation + +Le processus de tokenisation est effectué par la méthode `tokenize()` du *tokenizer* : + + +```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) +``` + +La sortie de cette méthode est une liste de chaînes de caractères ou de *tokens* : + +```python out +['Using', 'a', 'transform', '##er', 'network', 'is', 'simple'] +``` + +Ce *tokenizer* est un *tokenizer* de sous-mots : il découpe les mots jusqu'à obtenir des *tokens* qui peuvent être représentés par son vocabulaire. C'est le cas ici avec `transformer` qui est divisé en deux *tokens* : `transform` et `##er`. + +### De *tokens* aux identifiants d'entrée + +La conversion en identifiants d'entrée est gérée par la méthode `convert_tokens_to_ids()` du *tokenizer* : + + +```py +ids = tokenizer.convert_tokens_to_ids(tokens) + +print(ids) +``` + +```python out +[7993, 170, 11303, 1200, 2443, 1110, 3014] +``` + +Une fois converties en tenseur dans le *framework* approprié, ces sorties peuvent ensuite être utilisées comme entrées d'un modèle, comme nous l'avons vu précédemment dans ce chapitre. + + + +✏️ **Essayez !** Reproduisez les deux dernières étapes (tokénisation et conversion en identifiants d'entrée) sur les phrases des entrées que nous avons utilisées dans la section 2 (« I've been waiting for a HuggingFace course my whole life. » et « I hate this so much! »). Vérifiez que vous obtenez les mêmes identifiants d'entrée que nous avons obtenus précédemment ! + + + +## Décodage + +Le *décodage* va dans l'autre sens : à partir d'indices du vocabulaire nous voulons obtenir une chaîne de caractères. Cela peut être fait avec la méthode `decode()` comme suit : + + +```py +decoded_string = tokenizer.decode([7993, 170, 11303, 1200, 2443, 1110, 3014]) +print(decoded_string) +``` + +```python out +'Using a Transformer network is simple' +``` + +Notez que la méthode `decode` non seulement reconvertit les indices en *tokens* mais regroupe également les *tokens* faisant partie des mêmes mots. Le but étant de produire une phrase lisible. Ce comportement sera extrêmement utile lorsque dans la suite du cours nous utiliserons des modèles pouvant produire du nouveau texte (soit du texte généré à partir d'un *prompt*, soit pour des problèmes de séquence à séquence comme la traduction ou le résumé de texte). + +Vous devriez maintenant comprendre les opérations atomiques qu'un *tokenizer* peut gérer : tokenisation, conversion en identifiants, et reconversion des identifiants en chaîne de caractères. Cependant, nous n'avons fait qu'effleurer la partie émergée de l'iceberg. Dans la section suivante, nous allons pousser notre approche jusqu'à ses limites et voir comment les surmonter. diff --git a/chapters/fr/chapter2/5.mdx b/chapters/fr/chapter2/5.mdx new file mode 100644 index 000000000..ab3b90b1e --- /dev/null +++ b/chapters/fr/chapter2/5.mdx @@ -0,0 +1,351 @@ + + +# Manipulation de plusieurs séquences + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + +{:else} + +{/if} + +Dans la section précédente, nous avons exploré le cas d'utilisation le plus simple : faire une inférence sur une seule séquence de petite longueur. Cependant, certaines questions émergent déjà : + +- comment gérer de plusieurs séquences ? +- comment gérer de plusieurs séquences *de longueurs différentes* ? +- les indices du vocabulaire sont-ils les seules entrées qui permettent à un modèle de bien fonctionner ? +- existe-t-il une séquence trop longue ? + +Voyons quels types de problèmes ces questions posent et comment nous pouvons les résoudre en utilisant l'API 🤗 *Transformers*. + +## Les modèles attendent un batch d'entrées + +Dans l'exercice précédent, vous avez vu comment les séquences sont traduites en listes de nombres. +Convertissons cette liste de nombres en un tenseur et envoyons-le au modèle : + +{#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." # J'ai attendu un cours d’HuggingFace toute ma vie. + +tokens = tokenizer.tokenize(sequence) +ids = tokenizer.convert_tokens_to_ids(tokens) +input_ids = torch.tensor(ids) +# This line will fail. +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." # J'ai attendu un cours d’HuggingFace toute ma vie. + +tokens = tokenizer.tokenize(sequence) +ids = tokenizer.convert_tokens_to_ids(tokens) +input_ids = tf.constant(ids) +# This line will fail. +model(input_ids) +``` + +```py out +InvalidArgumentError: Input to reshape is a tensor with 14 values, but the requested shape has 196 [Op:Reshape] +``` +{/if} + +Pourquoi cela a échoué ? Nous avons suivi les étapes du pipeline de la section 2. + +Le problème est que nous avons envoyé une seule séquence au modèle, alors que les modèles de l’API 🤗 *Transformers* attendent plusieurs phrases par défaut. Ici, nous avons essayé de faire ce que le *tokenizer* fait en coulisses lorsque nous l'avons appliqué à une `séquence`. Cependant si vous regardez de près, vous verrez qu'il n'a pas seulement converti la liste des identifiants d'entrée en un tenseur mais aussi ajouté une dimension par-dessus : + + +{#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} + +Essayons à nouveau en ajoutant une nouvelle dimension : + +{#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." # J'ai attendu un cours d’HuggingFace toute ma vie. + + +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." +# J'ai attendu un cours d’HuggingFace toute ma vie. + + +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} + +Nous affichons les identifiants d'entrée ainsi que les logits résultants. Voici la sortie : + +{#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} + +Le « *batching* » est l'acte d'envoyer plusieurs phrases à travers le modèle, toutes en même temps. Si vous n'avez qu'une seule phrase, vous pouvez simplement construire un batch avec une seule séquence : + +``` +batched_ids = [ids, ids] +``` + +Il s'agit d'un batch de deux séquences identiques ! + + + +✏️ **Essayez !** Convertissez cette liste `batched_ids` en un tenseur et passez-la dans votre modèle. Vérifiez que vous obtenez les mêmes logits que précédemment (mais deux fois) ! + + + +Utiliser des *batchs* permet au modèle de fonctionner lorsque vous lui donnez plusieurs séquences. Utiliser plusieurs séquences est aussi simple que de construire un batch avec une seule séquence. Il y a cependant un deuxième problème. Lorsque vous essayez de regrouper deux phrases (ou plus), elles peuvent être de longueurs différentes. Si vous avez déjà travaillé avec des tenseurs, vous savez qu'ils doivent être de forme rectangulaire. Vous ne pourrez donc pas convertir directement la liste des identifiants d'entrée en un tenseur. Pour contourner ce problème, nous avons l'habitude de *rembourrer* (le *padding* en anglais) les entrées. + +## *Padding* des entrées + +La liste de listes suivante ne peut pas être convertie en un tenseur : + + +```py no-format +batched_ids = [ + [200, 200, 200], + [200, 200] +] +``` + +Afin de contourner ce problème, nous utilisons le *padding* pour que nos tenseurs aient une forme rectangulaire. Le *padding* permet de s'assurer que toutes nos phrases ont la même longueur en ajoutant un mot spécial appelé *padding token* aux phrases ayant moins de valeurs. Par exemple, si vous avez 10 phrases de 10 mots et 1 phrase de 20 mots, le *padding* fait en sorte que toutes les phrases aient 20 mots. Dans notre exemple, le tenseur résultant ressemble à ceci : + +```py no-format +padding_id = 100 + +batched_ids = [ + [200, 200, 200], + [200, 200, padding_id], +] +``` + +L'identifiant du jeton de padding peut être trouvé dans `tokenizer.pad_token_id`. Utilisons-le et envoyons nos deux phrases à travers le modèle premièrement individuellement puis en étant mises dans un même batch : + +{#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} + +Il y a quelque chose qui ne va pas avec les logits de notre prédiction avec les séquences mises dans un même batch. La deuxième ligne devrait être la même que les logits pour la deuxième phrase, mais nous avons des valeurs complètement différentes ! + +C'est parce que dans un *transformer* les couches d’attention *contextualisent* chaque *token*. Celles-ci prennent en compte les *tokens* de *padding* puisqu'elles analysent tous les *tokens* d'une séquence. Pour obtenir le même résultat lorsque l'on passe dans notre modèle des phrases individuelles de différentes longueurs ou un batch composé de mêmes phrases avec *padding*, nous devons dire à ces couches d'attention d'ignorer les jetons de *padding*. Ceci est fait en utilisant un masque d'attention. + + +## Masques d'attention + +Les masques d'attention sont des tenseurs ayant exactement la même forme que le tenseur d'identifiants d'entrée, remplis de 0 et de 1 : +- 1 indique que les *tokens* correspondants doivent être analysés +- 0 indique que les *tokens* correspondants ne doivent pas être analysés (c'est-à-dire qu'ils doivent être ignorés par les couches d'attention du modèle). + +Complétons l'exemple précédent avec un masque d'attention : + + +{#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} + +Nous obtenons maintenant les mêmes logits pour la deuxième phrase du batch. + +Remarquez comment la dernière valeur de la deuxième séquence est un identifiant de *padding* valant 0 dans le masque d'attention. + + + + +✏️ **Essayez !** Appliquez la tokénisation manuellement sur les deux phrases utilisées dans la section 2 (« I've been waiting for a HuggingFace course my whole life. » et « I hate this so much! »). Passez-les dans le modèle et vérifiez que vous obtenez les mêmes logits que dans la section 2. Ensuite regroupez-les en utilisant le jeton de *padding* et créez le masque d'attention approprié. Vérifiez que vous obtenez les mêmes résultats qu’en passant par le modèle ! + + + + +## Séquences plus longues + +Les *transformers* acceptent en entrée que des séquences d’une longueur limitée. La plupart des modèles traitent des séquences allant jusqu'à 512 ou 1024 *tokens* et plantent lorsqu'on leur demande de traiter des séquences plus longues. Il existe deux solutions à ce problème : + +- utiliser un modèle avec une longueur de séquence supportée plus longue, +- tronquer les séquences. + +Certains modèles sont spécialisés dans le traitement de très longues séquences comme par exemple le [Longformer](https://huggingface.co/transformers/model_doc/longformer.html) ou le [LED](https://huggingface.co/transformers/model_doc/led.html). Si vous travaillez sur une tâche qui nécessite de très longues séquences, nous vous recommandons de jeter un coup d'œil à ces modèles. + +Sinon, nous vous recommandons de tronquer vos séquences en spécifiant le paramètre `max_sequence_length` : + + +```py +sequence = sequence[:max_sequence_length] +``` diff --git a/chapters/fr/chapter2/6.mdx b/chapters/fr/chapter2/6.mdx new file mode 100644 index 000000000..f43edef28 --- /dev/null +++ b/chapters/fr/chapter2/6.mdx @@ -0,0 +1,186 @@ + + +# Tout assembler + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Dans les dernières sections, nous avons fait de notre mieux pour effectuer la plupart du travail manuellement. Nous avons exploré le fonctionnement des *tokenizers* et examiné la tokenisation, la conversion en identifiants d'entrée, le *padding*, la troncature et les masques d'attention. + +Cependant, comme nous l'avons vu dans la section 2, l'API 🤗 *Transformers* peut gérer tout cela pour nous via une fonction dans laquelle nous allons nous plonger ici. Lorsque vous appelez votre `tokenizer` directement sur la phrase, vous récupérez des entrées qui sont prêtes à être passées dans votre modèle : + + +```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." # J'ai attendu un cours d’HuggingFace toute ma vie. + +model_inputs = tokenizer(sequence) +``` + +Ici, la variable `model_inputs` contient tout ce qui est nécessaire au bon fonctionnement d'un modèle. Pour DistilBERT, cela inclut les identifiants d'entrée ainsi que le masque d'attention. D'autres modèles qui acceptent des entrées supplémentaires sont également fournis par l'objet `tokenizer`. + +Comme nous allons le voir dans les quelques exemples ci-dessous, cette méthode est très puissante. Premièrement, elle peut tokeniser une seule séquence : + + +```py +sequence = "I've been waiting for a HuggingFace course my whole life." # J'ai attendu un cours d’HuggingFace toute ma vie. + + +model_inputs = tokenizer(sequence) +``` + +Elle gère également plusieurs séquences à la fois, sans modification de l'API : + + +```py +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "So have I!", +] # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! » + +model_inputs = tokenizer(sequences) +``` + +Il est possible de faire du *padding* selon plusieurs objectifs : + +```py +# Remplit les séquences jusqu'à la longueur maximale de la séquence +model_inputs = tokenizer(sequences, padding="longest") + +# Remplit les séquences jusqu'à la longueur maximale du modèle +# (512 for BERT or DistilBERT) +model_inputs = tokenizer(sequences, padding="max_length") + +# Remplit les séquences jusqu'à la longueur maximale spécifiée +model_inputs = tokenizer(sequences, padding="max_length", max_length=8) +``` + +La fonction peut également tronquer les séquences : + +```py +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "So have I!", +] # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! » + +# Tronque les séquences qui sont plus longues que la longueur maximale du modèle +# (512 for BERT or DistilBERT) +model_inputs = tokenizer(sequences, truncation=True) + +# Tronque les séquences qui sont plus longues que la longueur maximale spécifiée +model_inputs = tokenizer(sequences, max_length=8, truncation=True) +``` + +L'objet `tokenizer` peut gérer la conversion en des tenseurs de *frameworks* spécifiques. Ils peuvent ensuite être directement envoyés au modèle. Par exemple, dans le code suivant, nous demandons au *tokenizer* de retourner des tenseurs Pytorch lorsque l’on spécifie `"pt"`, de retourner des tenseurs TensorFlow lorsque l’on spécifie `"tf"` et des tableaux NumPy lorsque l’on indique `"np"` : + +```py +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "So have I!", +] # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! » + +# Retourne des tenseurs PyTorch +model_inputs = tokenizer(sequences, padding=True, return_tensors="pt") + +# Retourne des tenseurs TensorFlow +model_inputs = tokenizer(sequences, padding=True, return_tensors="tf") + +# Retourne des tableaux NumPy +model_inputs = tokenizer(sequences, padding=True, return_tensors="np") +``` + +## Jetons spéciaux + +Si nous jetons un coup d'œil aux identifiants d'entrée renvoyés par le *tokenizer*, nous verrons qu'ils sont un peu différents de ceux que nous avions précédemment : + + +```py +sequence = "I've been waiting for a HuggingFace course my whole life." # « J'ai attendu un cours de HuggingFace toute ma vie. » + +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] +``` + +Un identifiant symbolique a été ajouté au début ainsi qu’un autre à la fin. Décodons les deux séquences d'identifiants ci-dessus pour voir de quoi il s'agit : + +```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." +``` + +Le *tokenizer* a ajouté le mot spécial `[CLS]` au début et le mot spécial `[SEP]` à la fin. C'est parce que le modèle a été pré-entraîné avec ces mots, donc pour obtenir les mêmes résultats pour l'inférence, nous devons également les ajouter. Notez que certains modèles n'ajoutent pas de mots spéciaux, ou en ajoutent des différents. Les modèles peuvent aussi ajouter ces mots spéciaux seulement au début, ou seulement à la fin. Dans tous les cas, le *tokenizer* sait lesquels sont attendus et s'en occupe pour vous. + +## Conclusion : du *tokenizer* au modèle + +Maintenant que nous avons vu toutes les étapes individuelles que l'objet `tokenizer` utilise lorsqu'il est appliqué sur des textes, voyons une dernière fois comment il peut gérer plusieurs séquences (*padding*), de très longues séquences (*troncation*) et plusieurs types de tenseurs avec son API principale : + + +{#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!", +] # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! » + + +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!", +] # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! » + +tokens = tokenizer(sequences, padding=True, truncation=True, return_tensors="tf") +output = model(**tokens) +``` +{/if} diff --git a/chapters/fr/chapter2/7.mdx b/chapters/fr/chapter2/7.mdx new file mode 100644 index 000000000..c6fac5c9a --- /dev/null +++ b/chapters/fr/chapter2/7.mdx @@ -0,0 +1,12 @@ +# Utilisation de base terminée ! + +Bravo à vous pour avoir suivi le cours jusqu'ici ! Pour récapituler, dans ce chapitre vous avez : +- appris les blocs de construction de base d'un *transformer*, +- appris ce qui constitue un pipeline de tokenisation, +- vu comment utiliser un *transformer* en pratique, +- appris comment tirer parti d'un *tokenizer* pour convertir du texte en tenseurs compréhensibles par le modèle, +- configurer ensemble un *tokenizer* et un modèle afin de passer du texte aux prédictions, +- appris les limites des identifiants d'entrée et ce que sont que les masques d'attention, +- joué avec des méthodes de *tokenizer* polyvalentes et configurables. + +À partir de maintenant, vous devriez être en mesure de naviguer librement dans la documentation 🤗 *Transformers*. Le vocabulaire vous semblera familier et vous avez vu les méthodes que vous utiliserez la plupart du temps. diff --git a/chapters/fr/chapter2/8.mdx b/chapters/fr/chapter2/8.mdx new file mode 100644 index 000000000..e935ec26e --- /dev/null +++ b/chapters/fr/chapter2/8.mdx @@ -0,0 +1,307 @@ + + + + +# Quiz de fin de chapitre + +### 1. Quel est l'ordre du pipeline de modélisation du langage ? + + +tokenizer donne un sens à ces prédictions et les reconvertit en texte si nécessaire.", + explain: " Le modèle ne peut pas comprendre le texte ! Le tokenizer doit d'abord tokeniser le texte et le convertir en identifiants afin qu'il soit compréhensible par le modèle."}, + { + text: " Tout d'abord, le tokenizer/i>, qui traite le texte et renvoie des identifiants. Puis le modèle traite ces identifiants et produit une prédiction, qui peut être du texte.", + explain: " La prédiction du modèle ne peut pas être du texte immédiatement. Le tokenizer doit être utilisé afin de reconvertir la prédiction en texte !"}, + { + text: " Le tokenizer traite le texte et renvoie des identifiants. Le modèle traite ces identifiants et produit une prédiction. Le tokenizer peut alors être utilisé à nouveau pour reconvertir ces prédictions en texte.", + explain: " C’est correct ! Le tokenizer peut être utilisé à la fois pour la tokenisation et la dé-tokénisation.", + correct: true + } + ]} +/> + +### 2. Combien de dimensions le tenseur produit par le transformer de base possède-t-il et quelles sont-elles ? + + +transformers gèrent les batchs, même avec une seule séquence ce serait une taille de batch de 1 !" + }, + { + text: "3: la longueur de la séquence, la taille du batch et la taille cachée.", + explain: "C’est correct !", + correct: true + } + ]} +/> + +### 3. Lequel des éléments suivants est un exemple de tokenisation en sous-mots ? + + + +### 4. Qu'est-ce qu'une tête de modèle ? + +transformer de base qui redirige les tenseurs vers leurs couches correctes.", + explain: "Incorrect ! Il n'y a pas de tel composant." + }, + { + text: "Également connu sous le nom de mécanisme d'auto-attention, il adapte la représentation d'un token en fonction des autres tokens de la séquence.", + explain: "Incorrect ! La couche d'auto-attention contient des têtes d'attention mais ce ne sont pas des têtes d'adaptation." + }, + { + text: "Un composant supplémentaire, généralement constitué d'une ou plusieurs couches, pour convertir les prédictions du transformer en une sortie spécifique à la tâche.", + explain: "C'est exact. Les têtes d'adaptation, aussi appelées simplement têtes, se présentent sous différentes formes : têtes de modélisation du langage, têtes de réponse aux questions, têtes de classification des séquences, etc.", + correct: true + } + ]} +/> + +{#if fw === 'pt'} +### 5. Qu'est-ce qu'un AutoModel? + +AutoNLP product?" + }, + { + text: "An object that returns the correct architecture based on the checkpoint", + explain: "Exactly: the AutoModel only needs to know the checkpoint from which to initialize to return the correct architecture.", + correct: true + }, + { + text: "A model that automatically detects the language used for its inputs to load the correct weights", + explain: "Incorrect; while some checkpoints and models are capable of handling multiple languages, there are no built-in tools for automatic checkpoint selection according to language. You should head over to the Model Hub to find the best checkpoint for your task!" + } + ]} +/> + +{:else} +### 5. What is an AutoModel? + +checkpoints et modèles soient capables de gérer plusieurs langues, il n'existe pas d'outils intégrés pour la sélection automatique des checkpoints en fonction de la langue. Vous devez vous rendre sur le Hub des modèles pour trouver le meilleur checkpoint pour votre tâche !" + } + ]} +/> + +{/if} + +### 6. Quelles sont les techniques à connaître lors de la mise en batch de séquences de longueurs différentes ? + + +padding", + explain: "Oui, le padding est une façon correcte d'égaliser les séquences pour qu'elles tiennent dans une forme rectangulaire. Mais est-ce le seul moyen ?", + correct: true + }, + { + text: "Les masques d'attention ", + explain: "Absolument ! Les masques d'attention sont d'une importance capitale lorsqu'on manipule des séquences de longueurs différentes. Ce n'est cependant pas la seule technique à laquelle il faut faire attention.", + correct: true + } + ]} +/> + +### 7. Quel est l'intérêt d'appliquer une fonction SoftMax aux logits produits par un modèle de classification de séquences ? + + + +### 8. Autour de quelle méthode s'articule la majeure partie de l'API tokenizer ? + +encode, car elle peut encoder du texte en identifiants et des identifiants en prédictions.", + explain: "Faux ! Bien que la méthode encode existe sur les *tokenizers*, elle n'existe pas sur les modèles." + }, + { + text: "Appeler directement l'objet tokenizer", + explain: " Exactement ! La méthode __call__ du tokenizer est une méthode très puissante qui peut traiter à peu près tout. C'est également la méthode utilisée pour récupérer les prédictions d'un modèle.", + correct: true + }, + { + text: "pad", + explain: "C'est faux ! Le padding est très utile mais ce n'est qu'une partie de l'API tokenizer." + }, + { + text: "tokenize", + explain: "La méthode tokenize est est sans doute l'une des méthodes les plus utiles, mais elle ne constitue pas le cœur de l'API tokenizer." + } + ]} +/> + +### 9. Que contient la variable `result` dans cet exemple de code ? + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +result = tokenizer.tokenize("Hello!") +``` + +token.", + explain: " Absolument ! Convertissez cela en identifiants, et donnez-les à un modèle !", + correct: true + }, + { + text: "Une liste d'identifiants", + explain: "C'est faux, c'est à cela que la méthode __call__ ou la méthode convert_tokens_to_ids sert !" + }, + { + text: "Une chaîne contenant tous les tokens", + explain: "Ce serait sous-optimal car le but est de diviser la chaîne de caractères en plusieurs éléments." + } + ]} +/> + +{#if fw === 'pt'} +### 10. Y a-t-il un problème avec le code suivant ? + + +```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) +``` + +tokenizer qui a été entraîné avec un checkpoint différent est rarement une bonne idée. Le modèle n'a pas été entraîné pour donner du sens à la sortie de ce tokenizer donc la sortie du modèle (s'il peut même fonctionner !) n'aura aucun sens." + }, + { + text: " Le tokenizer et le modèle doivent toujours provenir du même checkpoint.", + explain: "C’est juste !", + correct: true + }, + { + text: " C'est une bonne pratique de faire du padding et de troncage avec le tokenizer car chaque entrée est un batch.", + explain: "Il est vrai que chaque entrée de modèle doit être un batch. Cependant, tronquer ou compléter cette séquence n'aurait pas nécessairement de sens puisqu'il n'y en a qu'une seule. Il s'agit là de techniques permettant de mettre en batch une liste de phrases." + } + ]} +/> + +{:else} +### 10. Y a-t-il un problème avec le code suivant ? + +```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) +``` + +tokenizer qui a été entraîné avec un checkpoint différent est rarement une bonne idée. Le modèle n'a pas été entraîné pour donner du sens à la sortie de ce tokenizer donc la sortie du modèle (s'il peut même fonctionner !) n'aura aucun sens." + }, + { + text: " Le tokenizer et le modèle doivent toujours provenir du même checkpoint.", + explain: "C’est juste !", + correct: true + }, + { + text: " C'est une bonne pratique de faire du padding et de troncage avec le tokenizer car chaque entrée est un batch.", + explain: "Il est vrai que chaque entrée de modèle doit être un batch. Cependant, tronquer ou compléter cette séquence n'aurait pas nécessairement de sens puisqu'il n'y en a qu'une seule. Il s'agit là de techniques permettant de mettre en batch une liste de phrases." + } + ]} +/> + +{/if} diff --git a/chapters/fr/chapter5/1.mdx b/chapters/fr/chapter5/1.mdx index 8e76cea5e..766e33082 100644 --- a/chapters/fr/chapter5/1.mdx +++ b/chapters/fr/chapter5/1.mdx @@ -1,17 +1,17 @@ # Introduction -Dans le [Chapitre 3](/course/chapter3) vous avez eu un premier aperçu de la bibliothèque 🤗 Datasets et vous avez vu qu'il y avait trois étapes principales pour affiner un modèle: +Dans le [Chapitre 3](/course/fr/chapter3) vous avez eu un premier aperçu de la bibliothèque 🤗 *Datasets* et qu'il y a trois étapes principales pour *finetuner* un modèle: -1. Chargez un jeu de données à partir de Hugging Face Hub. -2. Prétraitez les données avec `Dataset.map()`. +1. charger un jeu de données à partir du *Hub* d’Hugging Face. +2. Prétraiter les données avec `Dataset.map()`. 3. Charger et calculer des métriques. -Mais ce n'est qu'effleurer la surface de ce que 🤗 Datasets peut faire ! Dans ce chapitre, nous allons plonger profondément dans la bibliothèque. En cours de route, nous trouverons des réponses aux questions suivantes: +Mais ce n'est qu'effleurer la surface de ce que 🤗 *Datasets* peut faire ! Dans ce chapitre, nous allons plonger profondément dans cette bibliothèque. En cours de route, nous trouverons des réponses aux questions suivantes : -* Que faites-vous lorsque votre jeu de données n'est pas sur le Hub ? -* Comment pouvez-vous découper et trancher un ensemble de données ? (Et si vous avez _vraiment_ besoin d'utiliser Pandas ?) -* Que faites-vous lorsque votre ensemble de données est énorme et va faire fondre la RAM de votre ordinateur portable ? -* Qu'est-ce que c'est que le "mappage de la mémoire" et Apache Arrow ? -* Comment pouvez-vous créer votre propre ensemble de données et le pousser vers le Hub ? +* que faire lorsque votre jeu de données n'est pas sur le *Hub* ? +* comment découper et trancher un jeu de données ? (Et si on a _vraiment_ besoin d'utiliser Pandas ?) +* que faire lorsque votre jeu de données est énorme et va monopoliser la RAM de votre ordinateur portable ? +* qu'est-ce que c'est que le « *memory mapping* » et Apache Arrow ? +* comment créer votre propre jeu de données et le pousser sur le *Hub* ? -Les techniques que vous apprenez ici vous prépareront aux tâches avancées de tokenisation et de réglage fin du [Chapitre 6](/course/chapter6) et du [Chapitre 7](/course/chapter7) -- alors prenez un café et commençons ! \ No newline at end of file +Les techniques apprises dans ce chapitre vous prépareront aux tâches avancées de tokenisation et de *finetuning* du [Chapitre 6](/course/fr/chapter6) et du [Chapitre 7](/course/fr/chapter7). Alors prenez un café et commençons ! diff --git a/chapters/fr/chapter5/2.mdx b/chapters/fr/chapter5/2.mdx index 896dc8ca2..8c84d6282 100644 --- a/chapters/fr/chapter5/2.mdx +++ b/chapters/fr/chapter5/2.mdx @@ -1,4 +1,4 @@ -# Que faire si mon ensemble de données n'est pas sur le Hub ? +# Que faire si mon jeu de données n'est pas sur le *Hub* ? -Vous savez comment utiliser le [Hugging Face Hub](https://huggingface.co/datasets) pour télécharger des ensembles de données, mais vous vous retrouverez souvent à travailler avec des données stockées sur votre ordinateur portable ou sur un serveur distant. Dans cette section, nous allons vous montrer comment 🤗 Datasets peut être utilisé pour charger des ensembles de données qui ne sont pas disponibles sur le Hugging Face Hub. +Vous savez comment utiliser le [*Hub* d’Hugging Face](https://huggingface.co/datasets) pour télécharger des jeux de données mais en pratique vous vous retrouverez souvent à travailler avec des données stockées sur votre ordinateur portable ou sur un serveur distant. Dans cette section, nous allons vous montrer comment 🤗 *Datasets* peut être utilisé pour charger des jeux de données qui ne sont pas disponibles sur le *Hub* d’Hugging Face. -## Travailler avec des ensembles de données locaux et distants +## Travailler avec des jeux de données locaux et distants -🤗 Datasets fournit des scripts de chargement pour gérer le chargement des ensembles de données locaux et distants. Il prend en charge plusieurs formats de données courants, tels que : +🤗 *Datasets* fournit des scripts de chargement de jeux de données locaux et distants. Il prend en charge plusieurs formats de données courants, tels que : -| Format de données | Chargement du script | Exemple | +| Format des données | Script de chargement | Exemple | | :----------------: | :------------------: | :-----------------------------------------------------: | | CSV & TSV | `csv` | `load_dataset("csv", data_files="my_file.csv")` | | Fichiers texte | `text` | `load_dataset("text", data_files="my_file.txt")` | | JSON & Lignes JSON | `json` | `load_dataset("json", data_files="my_file.jsonl")` | -| DataFrames marinés | `pandas` | `load_dataset("pandas", data_files="my_dataframe.pkl")` | +| DataFrames en Pickle | `pandas` | `load_dataset("pandas", data_files="my_dataframe.pkl")` | -Comme indiqué dans le tableau, pour chaque format de données, nous avons juste besoin de spécifier le type de script de chargement dans la fonction `load_dataset()`, ainsi qu'un argument `data_files` qui spécifie le chemin vers un ou plusieurs fichiers. Commençons par charger un jeu de données à partir de fichiers locaux ; plus tard, nous verrons comment faire la même chose avec des fichiers distants. +Comme indiqué dans le tableau, pour chaque format de données, nous avons juste besoin de spécifier le type de script de chargement dans la fonction `load_dataset()`, ainsi qu'un argument `data_files` qui spécifie le chemin vers un ou plusieurs fichiers. Commençons par charger un jeu de données à partir de fichiers locaux puis plus tard comment faire la même chose avec des fichiers distants. ## Charger un jeu de données local -Pour cet exemple, nous utiliserons l'ensemble de données [SQuAD-it](https://github.com/crux82/squad-it/), qui est un ensemble de données à grande échelle pour répondre aux questions en Italien. +Pour cet exemple, nous utilisons le jeu de données [SQuAD-it](https://github.com/crux82/squad-it/) qui est un grand jeu de données pour la tâche de *Question Awnswering* en italien. -Les fractionnements de formation et de test sont hébergés sur GitHub, nous pouvons donc les télécharger avec une simple commande `wget` : +Les échantillons d’entraînement et de test sont hébergés sur GitHub, nous pouvons donc les télécharger avec une simple commande `wget` : ```python !wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-train.json.gz !wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-test.json.gz ``` -Cela téléchargera deux fichiers compressés appelés *SQuAD_it-train.json.gz* et *SQuAD_it-test.json.gz*, que nous pouvons décompresser avec la commande Linux `gzip` : +Cela télécharge deux fichiers compressés appelés *SQuAD_it-train.json.gz* et *SQuAD_it-test.json.gz* que nous pouvons décompresser avec la commande Linux `gzip` : ```python !gzip -dkv SQuAD_it-*.json.gz @@ -50,11 +50,11 @@ Nous pouvons voir que les fichiers compressés ont été remplacés par _SQuAD_i -✎ Si vous vous demandez pourquoi il y a un caractère `!` dans les commandes shell ci-dessus, c'est parce que nous les exécutons dans un cahier Jupyter. Supprimez simplement le préfixe si vous souhaitez télécharger et décompresser l'ensemble de données dans un terminal. +✎ Si vous vous demandez pourquoi il y a un caractère `!` dans les commandes shell ci-dessus, c'est parce que nous les exécutons dans un *notebook* Jupyter. Supprimez simplement le préfixe si vous souhaitez télécharger et décompresser le jeu de données dans un terminal. -Pour charger un fichier JSON avec la fonction `load_dataset()`, nous avons juste besoin de savoir si nous avons affaire à du JSON ordinaire (similaire à un dictionnaire imbriqué) ou à des lignes JSON (JSON séparé par des lignes). Comme de nombreux ensembles de données de questions-réponses, SQuAD-it utilise le format imbriqué, avec tout le texte stocké dans un champ "données". Cela signifie que nous pouvons charger le jeu de données en spécifiant l'argument `field` comme suit : +Pour charger un fichier JSON avec la fonction `load_dataset()`, nous avons juste besoin de savoir si nous avons affaire à du JSON ordinaire (similaire à un dictionnaire imbriqué) ou à des lignes JSON (JSON séparé par des lignes). Comme de nombreux jeux de données de questions-réponses, SQuAD-it utilise le format imbriqué où tout le texte est stocké dans un champ `data`. Cela signifie que nous pouvons charger le jeu de données en spécifiant l'argument `field` comme suit : ```py from datasets import load_dataset @@ -62,7 +62,7 @@ from datasets import load_dataset squad_it_dataset = load_dataset("json", data_files="SQuAD_it-train.json", field="data") ``` -Par défaut, le chargement de fichiers locaux crée un objet `DatasetDict` avec une division `train`. Nous pouvons le voir en inspectant l'objet `squad_it_dataset` : +Par défaut, le chargement de fichiers locaux crée un objet `DatasetDict` avec un échantillon `train`. Nous pouvons le voir en inspectant l'objet `squad_it_dataset` : ```py squad_it_dataset @@ -77,7 +77,7 @@ DatasetDict({ }) ``` -Cela nous montre le nombre de lignes et les noms de colonnes associés à l'ensemble d'apprentissage. Nous pouvons afficher l'un des exemples en indexant la division "train" comme suit : +Cela nous montre le nombre de lignes et les noms de colonnes associés à l’échantillon d’entraînement. Nous pouvons afficher l'un des exemples en indexant l’échantillon `train`. comme suit : ```py squad_it_dataset["train"][0] @@ -85,15 +85,15 @@ squad_it_dataset["train"][0] ```python out { - "title": "Terremoto del Sichuan del 2008", + "title": "Terremoto del Sichuan del 2008", # Séisme du Sichuan 2008 "paragraphs": [ { - "context": "Il terremoto del Sichuan del 2008 o il terremoto...", + "context": "Il terremoto del Sichuan del 2008 o il terremoto...", # Le tremblement de terre du Sichuan de 2008 ou le... "qas": [ { "answers": [{"answer_start": 29, "text": "2008"}], "id": "56cdca7862d2951400fa6826", - "question": "In quale anno si è verificato il terremoto nel Sichuan?", + "question": "In quale anno si è verificato il terremoto nel Sichuan?", # En quelle année le tremblement de terre du Sichuan a-t-il eu lieu ? }, ... ], @@ -103,7 +103,7 @@ squad_it_dataset["train"][0] } ``` -Super, nous avons chargé notre premier jeu de données local ! Mais bien que cela ait fonctionné pour l'ensemble d'entraînement, ce que nous voulons vraiment, c'est inclure à la fois les divisions `train` et `test` dans un seul objet `DatasetDict` afin que nous puissions appliquer les fonctions `Dataset.map()` sur les deux divisions à la fois . Pour ce faire, nous pouvons fournir un dictionnaire à l'argument `data_files` qui associe chaque nom de division à un fichier associé à cette division : +Super, nous avons chargé notre premier jeu de données local ! Mais ce que nous voulons vraiment, c'est inclure à la fois les échantillons `train` et `test` dans un seul objet `DatasetDict` afin que nous puissions appliquer les fonctions `Dataset.map()` sur les deux à la fois . Pour ce faire, nous pouvons fournir un dictionnaire à l'argument `data_files` qui associe chaque nom des échantillons à un fichier associé à cet échantillon : ```py data_files = {"train": "SQuAD_it-train.json", "test": "SQuAD_it-test.json"} @@ -128,24 +128,24 @@ C'est exactement ce que nous voulions. Désormais, nous pouvons appliquer divers -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 group files matching a specified pattern according to the rules used by the Unix shell (for example, you can group all JSON files in a directory into a single division by setting `data_files="*.json"` ). See 🤗 Datasets [documentation](https://huggingface.co/docs/datasets/loading.html#local-and-remote-files) for details. +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. -Les scripts de chargement dans 🤗 Datasets prend en charge la décompression automatique des fichiers d'entrée, nous aurions donc pu ignorer l'utilisation de `gzip` en pointant l'argument `data_files` directement sur les fichiers compressés : +Les scripts de chargement de 🤗 *Datasets* prennent en charge la décompression automatique des fichiers d'entrée. Nous aurions donc pu ignorer l'utilisation de `gzip` en pointant l'argument `data_files` directement sur les fichiers compressés : ```py data_files = {"train": "SQuAD_it-train.json.gz", "test": "SQuAD_it-test.json.gz"} squad_it_dataset = load_dataset("json", data_files=data_files, field="data") ``` -Cela peut être utile si vous ne souhaitez pas décompresser manuellement de nombreux fichiers GZIP. La décompression automatique s'applique également à d'autres formats courants tels que ZIP et TAR, il vous suffit donc de pointer `data_files` vers les fichiers compressés et vous êtes prêt à partir ! +Cela peut être utile si vous ne souhaitez pas décompresser manuellement de nombreux fichiers GZIP. La décompression automatique s'applique également à d'autres formats courants tels que ZIP et TAR. Il vous suffit donc de pointer `data_files` vers les fichiers compressés et vous êtes prêt à partir ! Maintenant que vous savez comment charger des fichiers locaux sur votre ordinateur portable ou de bureau, examinons le chargement de fichiers distants. ## Charger un jeu de données distant -Si vous travaillez en tant que data scientist ou codeur dans une entreprise, il y a de fortes chances que les ensembles de données que vous souhaitez analyser soient stockés sur un serveur distant. Heureusement, charger des fichiers distants est aussi simple que de charger des fichiers locaux ! Au lieu de fournir un chemin vers les fichiers locaux, nous pointons l'argument `data_files` de `load_dataset()` vers une ou plusieurs URL où les fichiers distants sont stockés. Par exemple, pour l'ensemble de données SQuAD-it hébergé sur GitHub, nous pouvons simplement faire pointer `data_files` vers les URL _SQuAD_it-*.json.gz_ comme suit : +Si vous travaillez en tant que *data scientist* ou codeur dans une entreprise, il y a de fortes chances que les juex de données que vous souhaitez analyser soient stockés sur un serveur distant. Heureusement, charger des fichiers distants est aussi simple que de charger des fichiers locaux ! Au lieu de fournir un chemin vers les fichiers locaux, nous pointons l'argument `data_files` de `load_dataset()` vers une ou plusieurs URL où les fichiers distants sont stockés. Par exemple, pour le jeux de données SQuAD-it hébergé sur GitHub, nous pouvons simplement faire pointer `data_files` vers les URL _SQuAD_it-*.json.gz_ comme suit : ```py url = "https://github.com/crux82/squad-it/raw/master/" @@ -156,12 +156,10 @@ data_files = { squad_it_dataset = load_dataset("json", data_files=data_files, field="data") ``` -Cela renvoie le même objet `DatasetDict` obtenu ci-dessus, mais nous évite de télécharger et de décompresser manuellement les fichiers _SQuAD_it-*.json.gz_. Ceci conclut notre incursion dans les différentes façons de charger des ensembles de données qui ne sont pas hébergés sur le Hugging Face Hub. Maintenant que nous avons un ensemble de données avec lequel jouer, mettons-nous la main à la pâte avec diverses techniques de gestion des données ! +Cela renvoie le même objet `DatasetDict` obtenu ci-dessus mais nous évite de télécharger et de décompresser manuellement les fichiers _SQuAD_it-*.json.gz_. Ceci conclut notre incursion dans les différentes façons de charger des jeux de données qui ne sont pas hébergés sur le *Hub* d’Hugging Face. Maintenant que nous avons un jeu de données avec lequel jouer, mettons la main à la pâte avec diverses techniques de gestion des données ! -✏️ **Essayez-le !** Choisissez un autre ensemble de données hébergé sur GitHub ou 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 ensemble 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.html#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 9d6a57959..455473889 100644 --- a/chapters/fr/chapter5/3.mdx +++ b/chapters/fr/chapter5/3.mdx @@ -7,24 +7,24 @@ {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter5/section3.ipynb"}, ]} /> -La plupart du temps, les données avec lesquelles vous travaillez ne seront pas parfaitement préparées pour les modèles de formation. Dans cette section, nous allons explorer les différentes fonctionnalités fournies par 🤗 Datasets pour nettoyer vos ensembles de données. +La plupart du temps, les données avec lesquelles vous travaillez ne sont pas parfaitement préparées pour l’entraînements de modèles. Dans cette section, nous allons explorer les différentes fonctionnalités fournies par 🤗 *Datasets* pour nettoyer vos jeux de données. ## Trancher et découper nos données -Semblable à Pandas, 🤗 Datasets fournit plusieurs fonctions pour manipuler le contenu des objets `Dataset` et `DatasetDict`. Nous avons déjà rencontré la méthode `Dataset.map()` dans le [Chapitre 3](/course/chapter3), et dans cette section nous allons explorer certaines des autres fonctions à notre disposition. +Semblable à Pandas, 🤗 *Datasets* fournit plusieurs fonctions pour manipuler le contenu des objets `Dataset` et `DatasetDict`. Nous avons déjà rencontré la méthode `Dataset.map()` dans le [Chapitre 3](/course/fr/chapter3) et dans cette section nous allons explorer certaines des autres fonctions à notre disposition. -Pour cet exemple, nous utiliserons le [Drug Review Dataset](https://archive.ics.uci.edu/ml/datasets/Drug+Review+Dataset+%28Drugs.com%29) qui est hébergé sur [UC Irvine Machine Learning Repository] (https://archive.ics.uci.edu/ml/index.php), qui contient des avis de patients sur divers médicaments, ainsi que la condition traitée et une note de 10 étoiles sur la satisfaction du patient. +Pour cet exemple, nous utiliserons le [*Drug Review Dataset*](https://archive.ics.uci.edu/ml/datasets/Drug+Review+Dataset+%28Drugs.com%29) qui est hébergé sur [*UC Irvine Machine Learning Repository*](https://archive.ics.uci.edu/ml/index.php) et contenant des avis de patients sur divers médicaments ainsi que la condition traitée et une note de 10 étoiles sur la satisfaction du patient. -Nous devons d'abord télécharger et extraire les données, ce qui peut être fait avec les commandes `wget` et `unzip` : +Nous devons d'abord télécharger et extraire les données, ce qui peut être fait avec les commandes `wget` et `unzip` : ```py !wget "https://archive.ics.uci.edu/ml/machine-learning-databases/00462/drugsCom_raw.zip" !unzip drugsCom_raw.zip ``` -Étant donné que TSV n'est qu'une variante de CSV qui utilise des tabulations au lieu de virgules comme séparateurs, nous pouvons charger ces fichiers en utilisant le script de chargement `csv` et en spécifiant l'argument `delimiter` dans la fonction `load_dataset()` comme suit : +Étant donné que TSV n'est qu'une variante de CSV qui utilise des tabulations au lieu de virgules comme séparateurs, nous pouvons charger ces fichiers en utilisant le script de chargement `csv` et en spécifiant l'argument `delimiter` dans la fonction `load_dataset()` comme suit : ```py from datasets import load_dataset @@ -34,40 +34,40 @@ data_files = {"train": "drugsComTrain_raw.tsv", "test": "drugsComTest_raw.tsv"} drug_dataset = load_dataset("csv", data_files=data_files, delimiter="\t") ``` -Une bonne pratique lors de toute sorte d'analyse de données consiste à prélever un petit échantillon aléatoire pour avoir une idée rapide du type de données avec lesquelles vous travaillez. Dans 🤗 Datasets, nous pouvons créer un échantillon aléatoire en enchaînant les fonctions `Dataset.shuffle()` et `Dataset.select()` : +Une bonne pratique lors de toute sorte d'analyse de données consiste à prélever un petit échantillon aléatoire pour avoir une idée rapide du type de données avec lesquelles vous travaillez. Dans 🤗 *Datasets*, nous pouvons créer un échantillon aléatoire en enchaînant les fonctions `Dataset.shuffle()` et `Dataset.select()` : ```py drug_sample = drug_dataset["train"].shuffle(seed=42).select(range(1000)) -# Peek at the first few examples +# Un coup d'œil sur les premiers exemples drug_sample[:3] ``` ```python out {'Unnamed: 0': [87571, 178045, 80482], 'drugName': ['Naproxen', 'Duloxetine', 'Mobic'], - 'condition': ['Gout, Acute', 'ibromyalgia', 'Inflammatory Conditions'], - 'review': ['"like the previous person mention, I'm a strong believer of aleve, it works faster for my gout than the prescription meds I take. No more going to the doctor for refills.....Aleve works!"', - '"I have taken Cymbalta for about a year and a half for fibromyalgia pain. It is great\r\nas a pain reducer and an anti-depressant, however, the side effects outweighed \r\nany benefit I got from it. I had trouble with restlessness, being tired constantly,\r\ndizziness, dry mouth, numbness and tingling in my feet, and horrible sweating. I am\r\nbeing weaned off of it now. Went from 60 mg to 30mg and now to 15 mg. I will be\r\noff completely in about a week. The fibro pain is coming back, but I would rather deal with it than the side effects."', - '"I have been taking Mobic for over a year with no side effects other than an elevated blood pressure. I had severe knee and ankle pain which completely went away after taking Mobic. I attempted to stop the medication however pain returned after a few days."'], + 'condition': ['Gout, Acute', 'ibromyalgia', 'Inflammatory Conditions'], #['Goutte aiguë', 'ibromyalgie', 'Affections inflammatoires'] + 'review': ['"like the previous person mention, I'm a strong believer of aleve, it works faster for my gout than the prescription meds I take. No more going to the doctor for refills.....Aleve works!"', # comme la personne précédente l'a mentionné, je suis un fervent partisan de l'aleve, il fonctionne plus rapidement pour ma goutte que les médicaments sur ordonnance que je prends. Je n'ai plus besoin d'aller chez le médecin pour des renouvellements.....Aleve fonctionne !" + '"I have taken Cymbalta for about a year and a half for fibromyalgia pain. It is great\r\nas a pain reducer and an anti-depressant, however, the side effects outweighed \r\nany benefit I got from it. I had trouble with restlessness, being tired constantly,\r\ndizziness, dry mouth, numbness and tingling in my feet, and horrible sweating. I am\r\nbeing weaned off of it now. Went from 60 mg to 30mg and now to 15 mg. I will be\r\noff completely in about a week. The fibro pain is coming back, but I would rather deal with it than the side effects."', # J'ai pris du Cymbalta pendant environ un an et demi pour des douleurs de la fibromyalgie. C'est un excellent analgésique et un antidépresseur, mais les effets secondaires l'ont emporté sur tous les avantages que j'en ai tirés. J'ai eu des problèmes d'agitation, de fatigue constante, de vertiges, de bouche sèche, d'engourdissement, de picotements dans les pieds, et de transpiration horrible. Je suis en train de m'en sevrer maintenant. Je suis passée de 60 mg à 30 mg et maintenant à 15 mg. Je l'arrêterai complètement dans environ une semaine. La douleur de la fibrose revient, mais je préfère la supporter plutôt que les effets secondaires. + '"I have been taking Mobic for over a year with no side effects other than an elevated blood pressure. I had severe knee and ankle pain which completely went away after taking Mobic. I attempted to stop the medication however pain returned after a few days."'], # J'ai pris Mobic pendant plus d'un an sans effets secondaires autres qu'une pression sanguine élevée. J'avais de fortes douleurs au genou et à la cheville qui ont complètement disparu après avoir pris Mobic. J'ai essayé d'arrêter le médicament mais la douleur est revenue après quelques jours." 'rating': [9.0, 3.0, 10.0], - 'date': ['September 2, 2015', 'November 7, 2011', 'June 5, 2013'], + 'date': ['September 2, 2015', 'November 7, 2011', 'June 5, 2013'], #['2 septembre 2015', '7 novembre 2011', '5 juin 2013'] 'usefulCount': [36, 13, 128]} ``` -Notez que nous avons corrigé la graine dans `Dataset.shuffle()` à des fins de reproductibilité. `Dataset.select()` attend un itérable d'indices, nous avons donc passé `range(1000)` pour récupérer les 1 000 premiers exemples de l'ensemble de données mélangé. À partir de cet échantillon, nous pouvons déjà voir quelques bizarreries dans notre ensemble de données : +Notez que nous avons corrigé la graine dans `Dataset.shuffle()` à des fins de reproductibilité. `Dataset.select()` attend un itérable d'indices, nous avons donc passé `range(1000)` pour récupérer les 1 000 premiers exemples du jeu de données mélangé. À partir de cet échantillon, nous pouvons déjà voir quelques bizarreries dans notre jeu de données : -* La colonne "Sans nom : 0" ressemble étrangement à un identifiant anonyme pour chaque patient. -* La colonne "condition" comprend un mélange d'étiquettes en majuscules et en minuscules. -* Les avis sont de longueur variable et contiennent un mélange de séparateurs de lignes Python (`\r\n`) ainsi que des codes de caractères HTML comme `&\#039;`. +* la colonne `Unnamed: 0` ressemble étrangement à un identifiant anonyme pour chaque patient, +* la colonne `condition` comprend un mélange d'étiquettes en majuscules et en minuscules, +* les avis sont de longueur variable et contiennent un mélange de séparateurs de lignes Python (`\r\n`) ainsi que des codes de caractères HTML comme `&\#039;`. -Voyons comment nous pouvons utiliser 🤗 Datasets pour traiter chacun de ces problèmes. Pour tester l'hypothèse de l'ID patient pour la colonne `Unnamed : 0`, nous pouvons utiliser la fonction `Dataset.unique()` pour vérifier que le nombre d'ID correspond au nombre de lignes dans chaque division : +Voyons comment nous pouvons utiliser 🤗 *Datasets* pour traiter chacun de ces problèmes. Pour tester l'hypothèse de l'ID patient pour la colonne `Unnamed : 0`, nous pouvons utiliser la fonction `Dataset.unique()` pour vérifier que le nombre d'ID correspond au nombre de lignes dans chaque division : ```py for split in drug_dataset.keys(): assert len(drug_dataset[split]) == len(drug_dataset[split].unique("Unnamed: 0")) ``` -Cela semble confirmer notre hypothèse, alors nettoyons un peu l'ensemble de données en renommant la colonne `Unnamed: 0` en quelque chose d'un peu plus interprétable. Nous pouvons utiliser la fonction `DatasetDict.rename_column()` pour renommer la colonne sur les deux divisions en une seule fois : +Cela semble confirmer notre hypothèse, alors nettoyons un peu en renommant la colonne `Unnamed: 0` en quelque chose d'un peu plus interprétable. Nous pouvons utiliser la fonction `DatasetDict.rename_column()` pour renommer la colonne sur les deux divisions en une seule fois : ```py drug_dataset = drug_dataset.rename_column( @@ -91,11 +91,11 @@ DatasetDict({ -✏️ **Essayez-le !** Utilisez la fonction "Dataset.unique()" pour trouver le nombre de médicaments et de conditions uniques dans les ensembles d'entraînement et de test. +✏️ **Essayez !** Utilisez la fonction ` Dataset.unique()` pour trouver le nombre de médicaments et de conditions uniques dans les échantillons d'entraînement et de test. -Ensuite, normalisons toutes les étiquettes `condition` en utilisant `Dataset.map()`. Comme nous l'avons fait avec la tokenisation dans le [chapitre 3](/course/chapter3), nous pouvons définir une fonction simple qui peut être appliquée sur toutes les lignes de chaque division dans `drug_dataset` : +Ensuite, normalisons toutes les étiquettes `condition` en utilisant `Dataset.map()`. Comme nous l'avons fait avec la tokenisation dans le [chapitre 3](/course/fr/chapter3), nous pouvons définir une fonction simple qui peut être appliquée sur toutes les lignes de chaque division dans `drug_dataset` : ```py def lowercase_condition(example): @@ -109,26 +109,26 @@ drug_dataset.map(lowercase_condition) AttributeError: 'NoneType' object has no attribute 'lower' ``` -Oh non, nous avons rencontré un problème avec notre fonction de carte ! À partir de l'erreur, nous pouvons déduire que certaines des entrées de la colonne "condition" sont "Aucune", qui ne peuvent pas être mises en minuscules car ce ne sont pas des chaînes. Supprimons ces lignes en utilisant `Dataset.filter()`, qui fonctionne de manière similaire à `Dataset.map()` et attend une fonction qui reçoit un seul exemple de l'ensemble de données. Au lieu d'écrire une fonction explicite comme : +Oh non, nous rencontrons un problème avec notre fonction ! À partir de l'erreur, nous pouvons déduire que certaines des entrées de la colonne `condition` sont `None` ne pouvant donc pas être mises en minuscules car ce ne sont pas des chaînes. Supprimons ces lignes en utilisant `Dataset.filter()`, qui fonctionne de manière similaire à `Dataset.map()` et attend une fonction qui reçoit un seul exemple issu du jeu de données. Au lieu d'écrire une fonction explicite comme : ```py def filter_nones(x): return x["condition"] is not None ``` -puis en exécutant `drug_dataset.filter(filter_nones)`, nous pouvons le faire en une seule ligne en utilisant une _lambda function_. En Python, les fonctions lambda sont de petites fonctions que vous pouvez définir sans les nommer explicitement. Ils prennent la forme générale : +puis éxécuter `drug_dataset.filter(filter_nones)`, nous pouvons le faire en une seule ligne en utilisant une _fonction lambda_. En Python, les fonctions lambda sont de petites fonctions que vous pouvez définir sans les nommer explicitement. Ils prennent la forme générale : ``` lambda : ``` -où `lambda` est l'un des [mots clés] spéciaux de Python (https://docs.python.org/3/reference/lexical_analysis.html#keywords), `` est une liste/ensemble de valeurs séparées par des virgules qui définissent les entrées de la fonction et `` représente les opérations que vous souhaitez exécuter. Par exemple, nous pouvons définir une simple fonction lambda qui met au carré un nombre comme suit : +où `lambda` est l'un des [mots clés](https://docs.python.org/3/reference/lexical_analysis.html#keywords) spéciaux de Python, `` est une liste/ensemble de valeurs séparées par des virgules qui définissent les entrées de la fonction et `` représente les opérations que vous souhaitez exécuter. Par exemple, nous pouvons définir une simple fonction lambda qui met au carré un nombre comme suit : ``` lambda x : x * x ``` -Pour appliquer cette fonction à une entrée, nous devons l'envelopper ainsi que l'entrée entre parenthèses : +Pour appliquer cette fonction à une entrée, nous devons l'envelopper ainsi que l'entrée entre parenthèses : ```py (lambda x: x * x)(3) @@ -148,17 +148,17 @@ De même, nous pouvons définir des fonctions lambda avec plusieurs arguments en 16.0 ``` -Les fonctions Lambda sont pratiques lorsque vous souhaitez définir de petites fonctions à usage unique (pour plus d'informations à leur sujet, nous vous recommandons de lire l'excellent [tutoriel Real Python](https://realpython.com/python-lambda/) d'André Burgaud) . Dans le contexte 🤗 Datasets, nous pouvons utiliser des fonctions lambda pour définir des opérations simples de mappage et de filtrage, alors utilisons cette astuce pour éliminer les entrées "None" dans notre jeu de données : +Les fonctions lambda sont pratiques lorsque vous souhaitez définir de petites fonctions à usage unique (pour plus d'informations à leur sujet, nous vous recommandons de lire l'excellent [tutoriel Real Python](https://realpython.com/python-lambda/) d'André Burgaud) . Dans le contexte de la bibliothèque 🤗 *Datasets*, nous pouvons utiliser des fonctions lambda pour définir des opérations simples de mappage et de filtrage. Utilisons cette astuce pour éliminer les entrées `None` dans notre jeu de données : ```py drug_dataset = drug_dataset.filter(lambda x: x["condition"] is not None) ``` -Avec les entrées "None" supprimées, nous pouvons normaliser notre colonne "condition" : +Avec les entrées `None` supprimées, nous pouvons normaliser notre colonne `condition` : ```py drug_dataset = drug_dataset.map(lowercase_condition) -# Check that lowercasing worked +# Vérification que la mise en minuscule a fonctionné drug_dataset["train"]["condition"][:3] ``` @@ -170,35 +170,35 @@ drug_dataset["train"]["condition"][:3] ## Création de nouvelles colonnes -Chaque fois que vous avez affaire à des avis de clients, une bonne pratique consiste à vérifier le nombre de mots dans chaque avis. Une critique peut être un simple mot comme "Génial !" ou un essai complet avec des milliers de mots, et selon le cas d'utilisation, vous devrez gérer ces extrêmes différemment. Pour calculer le nombre de mots dans chaque révision, nous utiliserons une heuristique approximative basée sur la division de chaque texte par des espaces. +Chaque fois que vous avez affaire à des avis de clients, une bonne pratique consiste à vérifier le nombre de mots dans chaque avis. Une critique peut être un simple mot comme « Génial ! » ou un essai complet avec des milliers de mots. Selon le cas d'usage, vous devrez gérer ces extrêmes différemment. Pour calculer le nombre de mots dans chaque révision, nous utiliserons une heuristique approximative basée sur la division de chaque texte par des espaces. -Définissons une fonction simple qui compte le nombre de mots dans chaque avis : +Définissons une fonction simple qui compte le nombre de mots dans chaque avis : ```py def compute_review_length(example): return {"review_length": len(example["review"].split())} ``` -Contrairement à notre fonction `lowercase_condition()`, `compute_review_length()` renvoie un dictionnaire dont la clé ne correspond pas à l'un des noms de colonne de l'ensemble de données. Dans ce cas, lorsque `compute_review_length()` est passé à `Dataset.map()`, il sera appliqué à toutes les lignes du jeu de données pour créer une nouvelle colonne `review_length` : +Contrairement à notre fonction `lowercase_condition()`, `compute_review_length()` renvoie un dictionnaire dont la clé ne correspond pas à l'un des noms de colonne du jeu de données. Dans ce cas, lorsque `compute_review_length()` est passé à `Dataset.map()`, il est appliqué à toutes les lignes du jeu de données pour créer une nouvelle colonne `review_length` : ```py drug_dataset = drug_dataset.map(compute_review_length) -# Inspect the first training example +# Inspecter le premier exemple d'entraînement drug_dataset["train"][0] ``` ```python out {'patient_id': 206461, 'drugName': 'Valsartan', - 'condition': 'left ventricular dysfunction', - 'review': '"It has no side effect, I take it in combination of Bystolic 5 Mg and Fish Oil"', + 'condition': 'left ventricular dysfunction', # dysfonctionnement du ventricule gauche + 'review': '"It has no side effect, I take it in combination of Bystolic 5 Mg and Fish Oil"', # Il n'a aucun effet secondaire, je le prends en combinaison avec Bystolic 5 mg et de l'huile de poisson. 'rating': 9.0, - 'date': 'May 20, 2012', + 'date': 'May 20, 2012', # 20 mai 2012 'usefulCount': 27, 'review_length': 17} ``` -Comme prévu, nous pouvons voir qu'une colonne "review_length" a été ajoutée à notre ensemble d'entraînement. Nous pouvons trier cette nouvelle colonne avec `Dataset.sort()` pour voir à quoi ressemblent les valeurs extrêmes : +Comme prévu, nous pouvons voir qu'une colonne `review_length` a été ajoutée à notre jeu d'entraînement. Nous pouvons trier cette nouvelle colonne avec `Dataset.sort()` pour voir à quoi ressemblent les valeurs extrêmes : ```py drug_dataset["train"].sort("review_length")[:3] @@ -207,23 +207,23 @@ drug_dataset["train"].sort("review_length")[:3] ```python out {'patient_id': [103488, 23627, 20558], 'drugName': ['Loestrin 21 1 / 20', 'Chlorzoxazone', 'Nucynta'], - 'condition': ['birth control', 'muscle spasm', 'pain'], - 'review': ['"Excellent."', '"useless"', '"ok"'], + 'condition': ['birth control', 'muscle spasm', 'pain'], # contraception, spasme musculaire, douleur. + 'review': ['"Excellent."', '"useless"', '"ok"'], # Excellent, inutile, ok 'rating': [10.0, 1.0, 6.0], - 'date': ['November 4, 2008', 'March 24, 2017', 'August 20, 2016'], + 'date': ['November 4, 2008', 'March 24, 2017', 'August 20, 2016'], # 4 novembre 2008, 24 mars 2017, 20 août 2016 'usefulCount': [5, 2, 10], 'review_length': [1, 1, 1]} ``` -Comme nous le soupçonnions, certaines critiques ne contiennent qu'un seul mot, ce qui, bien que cela puisse convenir à l'analyse des sentiments, ne serait pas informatif si nous voulons prédire la condition. +Comme nous le soupçonnions, certaines critiques ne contiennent qu'un seul mot, ce qui, bien que cela puisse convenir à l'analyse des sentiments, n’est pas informatif si nous voulons prédire la condition. -🙋 Une autre façon d'ajouter de nouvelles colonnes à un ensemble de données consiste à utiliser la fonction `Dataset.add_column()`. Cela vous permet de fournir la colonne sous forme de liste Python ou de tableau NumPy et peut être utile dans les situations où `Dataset.map()` n'est pas bien adapté à votre analyse. +🙋 Une autre façon d'ajouter de nouvelles colonnes à un jeu de données consiste à utiliser la fonction `Dataset.add_column()`. Cela vous permet de donner la colonne sous forme de liste Python ou de tableau NumPy et peut être utile dans les situations où `Dataset.map()` n'est pas bien adapté à votre analyse. -Utilisons la fonction `Dataset.filter()` pour supprimer les avis contenant moins de 30 mots. De la même manière que nous l'avons fait avec la colonne "condition", nous pouvons filtrer les avis très courts en exigeant que les avis aient une longueur supérieure à ce seuil : +Utilisons la fonction `Dataset.filter()` pour supprimer les avis contenant moins de 30 mots. De la même manière que nous l'avons fait avec la colonne `condition`, nous pouvons filtrer les avis très courts en exigeant que les avis aient une longueur supérieure à ce seuil : ```py drug_dataset = drug_dataset.filter(lambda x: x["review_length"] > 30) @@ -234,15 +234,15 @@ print(drug_dataset.num_rows) {'train': 138514, 'test': 46108} ``` -Comme vous pouvez le constater, cela a supprimé environ 15 % des avis de nos ensembles d'entraînement et de test d'origine. +Comme vous pouvez le constater, cela a supprimé environ 15 % des avis de nos jeux d'entraînement et de test d'origine. -✏️ **Essayez-le !** 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.html#datasets.Dataset.sort) pour voir quel argument vous devez utiliser pour trier les avis par longueur dans l'ordre décroissant. -La dernière chose à laquelle nous devons faire face est la présence de codes de caractères HTML dans nos avis. Nous pouvons utiliser le module `html` de Python pour supprimer ces caractères, comme ceci : +La dernière chose à laquelle nous devons faire face est la présence de caractères HTML dans nos avis. Nous pouvons utiliser le module `html` de Python pour supprimer ces caractères, comme ceci : ```py import html @@ -255,19 +255,19 @@ html.unescape(text) "I'm a transformer called BERT" ``` -Nous utiliserons `Dataset.map()` pour démasquer tous les caractères HTML de notre corpus : +Nous utilisons `Dataset.map()` pour démasquer tous les caractères HTML de notre corpus : ```python drug_dataset = drug_dataset.map(lambda x: {"review": html.unescape(x["review"])}) ``` -Comme vous pouvez le voir, la méthode `Dataset.map()` est très utile pour le traitement des données -- et nous n'avons même pas effleuré la surface de tout ce qu'elle peut faire ! +Comme vous pouvez le voir, la méthode `Dataset.map()` est très utile pour le traitement des données. Et nous n'avons même pas effleuré la surface de tout ce qu'elle peut faire ! ## Les superpouvoirs de la méthode `map()` -La méthode `Dataset.map()` prend un argument `batched` qui, s'il est défini sur `True`, l'amène à envoyer un lot d'exemples à la fonction map en une seule fois (la taille du lot est configurable mais par défaut à 1 000). Par exemple, la fonction de carte précédente qui dégageait tout le code HTML prenait un peu de temps à s'exécuter (vous pouvez lire le temps pris dans les barres de progression). On peut accélérer cela en traitant plusieurs éléments en même temps à l'aide d'une liste en compréhension. +La méthode `Dataset.map()` prend un argument `batched` qui, s'il est défini sur `True`, l'amène à envoyer un batch d'exemples à la fonction map en une seule fois (la taille du batch est configurable mais est fixé par défaut à 1 000). Par exemple, la fonction `map()` précédente qui supprime tout le code HTML prend un peu de temps à s'exécuter (vous pouvez lire le temps pris dans les barres de progression). On peut accélérer cela en traitant plusieurs éléments en même temps à l'aide d'une compréhension de liste. -Lorsque vous spécifiez `batched=True`, la fonction reçoit un dictionnaire avec les champs de l'ensemble de données, mais chaque valeur est maintenant une _liste de valeurs_, et non plus une seule valeur. La valeur de retour de `Dataset.map()` devrait être la même : un dictionnaire avec les champs que nous voulons mettre à jour ou ajouter à notre ensemble de données, et une liste de valeurs. Par exemple, voici une autre façon de supprimer tous les caractères HTML, mais en utilisant `batched=True` : +Lorsque vous spécifiez `batched=True`, la fonction reçoit un dictionnaire avec les champs du jeu de données mais chaque valeur est maintenant une _liste de valeurs_ et non plus une seule valeur. La valeur retournée par `Dataset.map()` devrait être la même : un dictionnaire avec les champs que nous voulons mettre à jour ou ajouter à notre jeu de données, et une liste de valeurs. Par exemple, voici une autre façon de supprimer tous les caractères HTML, mais en utilisant `batched=True` : ```python new_drug_dataset = drug_dataset.map( @@ -275,9 +275,9 @@ new_drug_dataset = drug_dataset.map( ) ``` -Si vous exécutez ce code dans un notebook, vous verrez que cette commande s'exécute beaucoup plus rapidement que la précédente. Et ce n'est pas parce que nos critiques ont déjà été sans échappement HTML -- si vous ré-exécutez l'instruction de la section précédente (sans `batched=True`), cela prendra le même temps qu'avant. En effet, les compréhensions de liste sont généralement plus rapides que l'exécution du même code dans une boucle "for", et nous gagnons également en performances en accédant à de nombreux éléments en même temps au lieu d'un par un. +Si vous exécutez ce code dans un *notebook*, vous verrez que cette commande s'exécute beaucoup plus rapidement que la précédente. Et ce n'est pas parce que nos critiques ont déjà été scannées au format HTML. Si vous ré-exécutez l'instruction de la section précédente (sans `batched=True`), cela prendra le même temps qu'avant. En effet, les compréhensions de liste sont généralement plus rapides que l'exécution du même code dans une boucle `for` et nous gagnons également en performances en accédant à de nombreux éléments en même temps au lieu d'un par un. -L'utilisation de `Dataset.map()` avec `batched=True` sera essentielle pour débloquer la vitesse des tokenizers "rapides" que nous rencontrerons dans [Chapitre 6](/course/chapter6), qui peuvent rapidement tokeniser de grandes listes de textes. Par exemple, pour tokeniser toutes les revues de médicaments avec un tokenizer rapide, nous pourrions utiliser une fonction comme celle-ci : +L'utilisation de `Dataset.map()` avec `batched=True` est essentielle pour les *tokenizers rapides* que nous rencontrerons dans le [Chapitre 6](/course/fr/chapter6) et qui peuvent rapidement tokeniser de grandes listes de textes. Par exemple, pour tokeniser toutes les critiques de médicaments avec un *tokenizer* rapide nous pouvons utiliser une fonction comme celle-ci : ```python from transformers import AutoTokenizer @@ -289,32 +289,32 @@ def tokenize_function(examples): return tokenizer(examples["review"], truncation=True) ``` -Comme vous l'avez vu dans le [Chapitre 3](/course/chapter3), nous pouvons passer un ou plusieurs exemples au tokenizer, nous pouvons donc utiliser cette fonction avec ou sans `batched=True`. Profitons-en pour comparer les performances des différentes options. Dans un cahier, vous pouvez chronométrer une instruction d'une ligne en ajoutant `%time` avant la ligne de code que vous souhaitez mesurer : +Comme vous l'avez vu dans le [Chapitre 3](/course/fr/chapter3), nous pouvons passer un ou plusieurs exemples au *tokenizer*. Nous pouvons donc utiliser cette fonction avec ou sans `batched=True`. Profitons-en pour comparer les performances des différentes options. Dans un *notebook*, vous pouvez chronométrer une instruction d'une ligne en ajoutant `%time` avant la ligne de code que vous souhaitez mesurer : ```python no-format %time tokenized_dataset = drug_dataset.map(tokenize_function, batched=True) ``` -Vous pouvez également chronométrer une cellule entière en mettant `%%time` au début de la cellule. Sur le matériel sur lequel nous avons exécuté cela, il affichait 10,8 s pour cette instruction (c'est le nombre écrit après "Wall time"). +Vous pouvez également chronométrer une cellule entière en mettant `%%time` au début de la cellule. Sur le matériel sur lequel nous avons exécuté cela, cela affichait 10,8 s pour cette instruction (c'est le nombre écrit après "Wall time"). -✏️ **Essayez-le !** Exécutez la même instruction avec et sans `batched=True`, puis essayez-le avec un tokenizer lent (ajoutez `use_fast=False` dans la méthode `AutoTokenizer.from_pretrained()`) afin que vous puissiez voir quels numéros vous obtenez sur votre matériel. +✏️ **Essayez !** Exécutez la même instruction avec et sans `batched=True`, puis essayez-le avec un *tokenizer* lent (ajoutez `use_fast=False` dans la méthode `AutoTokenizer.from_pretrained()`) afin que vous puissiez voir quels temps vous obtenez sur votre matériel. -Voici les résultats que nous avons obtenus avec et sans batching, avec un tokenizer rapide et un lent : +Voici les résultats que nous avons obtenus avec et sans batching, avec un *tokenizer* rapide et un lent : -Options | Tokenizer rapide | Tokenizer lent +Options | *Tokenizer* rapide | *Tokenizer* lent :--------------:|:----------------:|:-----------------: `batched=True` | 10.8s | 4min41s `batched=False` | 59.2s | 5min3s -Cela signifie que l'utilisation d'un tokenizer rapide avec l'option `batched=True` est 30 fois plus rapide que son homologue lent sans traitement par lot -- c'est vraiment incroyable ! C'est la raison principale pour laquelle les tokenizers rapides sont la valeur par défaut lors de l'utilisation de `AutoTokenizer` (et pourquoi ils sont appelés "rapides"). Ils sont capables d'atteindre une telle accélération car dans les coulisses, le code de tokenisation est exécuté dans Rust, qui est un langage qui facilite la parallélisation de l'exécution du code. +Cela signifie que l'utilisation d'un *tokenizer* rapide avec l'option `batched=True` est 30 fois plus rapide que son homologue lent sans batch. C'est vraiment incroyable ! C'est la raison principale pour laquelle les *tokenizers* rapides sont la valeur par défaut lors de l'utilisation de `AutoTokenizer` (et pourquoi ils sont appelés « rapides »). Ils sont capables d'atteindre une telle vitesse car en coulisses le code de tokenisation est exécuté en Rust qui est un langage facilitant la parallélisation de l'exécution du code. -La parallélisation est également la raison de l'accélération de près de 6 fois obtenue par le fast tokenizer avec le traitement par lots : vous ne pouvez pas paralléliser une seule opération de tokenisation, mais lorsque vous souhaitez tokeniser de nombreux textes en même temps, vous pouvez simplement répartir l'exécution sur plusieurs processus. chacun responsable de ses propres textes. +La parallélisation est également la raison du gain de vitesse de près de 6 fois obtenue par le *tokenizer* rapide avec batch. Vous ne pouvez pas paralléliser une seule opération de tokenisation, mais lorsque vous souhaitez *tokeniser* de nombreux textes en même temps, vous pouvez simplement répartir l'exécution sur plusieurs processus. Chacun responsable de ses propres textes. -`Dataset.map()` possède également ses propres capacités de parallélisation. Comme ils ne sont pas soutenus par Rust, ils ne laisseront pas un tokenizer lent rattraper un rapide, mais ils peuvent toujours être utiles (surtout si vous utilisez un tokenizer qui n'a pas de version rapide). Pour activer le multitraitement, utilisez l'argument `num_proc` et spécifiez le nombre de processus à utiliser dans votre appel à `Dataset.map()` : +`Dataset.map()` possède aussi ses propres capacités de parallélisation. Comme elles ne sont pas soutenus par Rust, un *tokenizer* lent ne peut pas rattraper un rapide mais cela peut toujours être utile (surtout si vous utilisez un *tokenizer* qui n'a pas de version rapide). Pour activer le multitraitement, utilisez l'argument `num_proc` et spécifiez le nombre de processus à utiliser dans votre appel à `Dataset.map()` : ```py slow_tokenizer = AutoTokenizer.from_pretrained("bert-base-cased", use_fast=False) @@ -327,24 +327,24 @@ def slow_tokenize_function(examples): tokenized_dataset = drug_dataset.map(slow_tokenize_function, batched=True, num_proc=8) ``` -Vous pouvez expérimenter un peu le timing pour déterminer le nombre optimal de processus à utiliser ; dans notre cas 8 semblait produire le meilleur gain de vitesse. Voici les chiffres que nous avons obtenus avec et sans multitraitement : +Vous pouvez faire des tests pour déterminer le nombre optimal de processus à utiliser. Dans notre cas 8 semble produire le meilleur gain de vitesse. Voici les chiffres que nous avons obtenus avec et sans multitraitement : -Options | Tokenizer rapide | Tokenizer lent +Options | *Tokenizer* rapide | *Tokenizer* lent :----------------------------:|:----------------:|:---------------: `batched=True` | 10.8s | 4min41s `batched=False` | 59.2s | 5min3s `batched=True`, `num_proc=8` | 6.52s | 41.3s `batched=False`, `num_proc=8` | 9.49s | 45.2s -Ce sont des résultats beaucoup plus raisonnables pour le tokenizer lent, mais les performances du tokenizer rapide ont également été considérablement améliorées. Notez, cependant, que ce ne sera pas toujours le cas - pour les valeurs de `num_proc` autres que 8, nos tests ont montré qu'il était plus rapide d'utiliser `batched=True` sans cette option. En général, nous ne recommandons pas d'utiliser le multitraitement Python pour les tokenizers rapides avec `batched=True`. +Ce sont des résultats beaucoup plus raisonnables pour le *tokenizer* lent mais les performances du *tokenizer* rapide ont également été considérablement améliorées. Notez, cependant, que ce ne sera pas toujours le cas : pour des valeurs de `num_proc` autres que 8, nos tests ont montré qu'il était plus rapide d'utiliser `batched=True` sans cette option. En général, nous ne recommandons pas d'utiliser le multitraitement pour les *tokenizers* rapides avec `batched=True`. -Utiliser `num_proc` pour accélérer votre traitement est généralement une bonne idée, tant que la fonction que vous utilisez n'effectue pas déjà une sorte de multitraitement. +Utiliser `num_proc` pour accélérer votre traitement est généralement une bonne idée tant que la fonction que vous utilisez n'effectue pas déjà une sorte de multitraitement. -Toutes ces fonctionnalités condensées en une seule méthode sont déjà assez étonnantes, mais il y a plus ! Avec `Dataset.map()` et `batched=True` vous pouvez modifier le nombre d'éléments dans votre jeu de données. Ceci est très utile dans de nombreuses situations où vous souhaitez créer plusieurs fonctionnalités d'entraînement à partir d'un exemple, et nous devrons le faire dans le cadre du prétraitement de plusieurs des tâches NLP que nous entreprendrons dans [Chapitre 7](/course/ Chapitre 7). +Toutes ces fonctionnalités condensées en une seule méthode sont déjà assez étonnantes, mais il y a plus ! Avec `Dataset.map()` et `batched=True` vous pouvez modifier le nombre d'éléments dans votre jeu de données. Ceci est très utile dans de nombreuses situations où vous souhaitez créer plusieurs fonctionnalités d'entraînement à partir d'un exemple. Nous devrons le faire dans le cadre du prétraitement de plusieurs des tâches de traitement du langage naturel que nous entreprendrons dans le [Chapitre 7](/course/fr/chapter7). @@ -352,7 +352,7 @@ Toutes ces fonctionnalités condensées en une seule méthode sont déjà assez -Voyons comment cela fonctionne ! Ici, nous allons tokeniser nos exemples et les tronquer à une longueur maximale de 128, mais nous demanderons au tokenizer de renvoyer *tous* les morceaux des textes au lieu du premier. Cela peut être fait avec `return_overflowing_tokens=True` : +Voyons comment cela fonctionne ! Ici, nous allons tokeniser nos exemples et les tronquer à une longueur maximale de 128 mais nous demanderons au *tokenizer* de renvoyer *tous* les morceaux des textes au lieu du premier. Cela peut être fait avec `return_overflowing_tokens=True` : ```py def tokenize_and_split(examples): @@ -364,7 +364,7 @@ def tokenize_and_split(examples): ) ``` -Testons cela sur un exemple avant d'utiliser `Dataset.map()` sur l'ensemble de données : +Testons cela sur un exemple avant d'utiliser `Dataset.map()` sur le jeu de données : ```py result = tokenize_and_split(drug_dataset["train"][0]) @@ -375,7 +375,7 @@ result = tokenize_and_split(drug_dataset["train"][0]) [128, 49] ``` -Ainsi, notre premier exemple dans l'ensemble de formation est devenu deux fonctionnalités car il a été segmenté à plus que le nombre maximum de jetons que nous avons spécifié : le premier de longueur 128 et le second de longueur 49. Faisons maintenant cela pour tous les éléments du base de données! +Notre premier exemple du jeu d’entraînement est devenu deux caractéristiques car il a été segmenté à plus que le nombre maximum de *tokens* que nous avons spécifié : le premier de longueur 128 et le second de longueur 49. Faisons maintenant cela pour tous les éléments du jeu de données ! ```py tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) @@ -385,9 +385,9 @@ 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 pas? L'examen du message d'erreur nous donnera 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] `Dataset.map()`(https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map), vous vous souviendrez 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 fonctionnalités, 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.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. -Le problème est que nous essayons de mélanger deux ensembles 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 le premier avec l'argument `remove_columns` : +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` : ```py tokenized_dataset = drug_dataset.map( @@ -395,7 +395,7 @@ tokenized_dataset = drug_dataset.map( ) ``` -Maintenant, cela fonctionne sans erreur. Nous pouvons vérifier que notre nouveau jeu de données contient beaucoup plus d'éléments que le jeu de données d'origine en comparant les longueurs : +Maintenant, cela fonctionne sans erreur. Nous pouvons vérifier que notre nouveau jeu de données contient beaucoup plus d'éléments que le jeu de données d'origine en comparant les longueurs : ```py len(tokenized_dataset["train"]), len(drug_dataset["train"]) @@ -405,7 +405,7 @@ len(tokenized_dataset["train"]), len(drug_dataset["train"]) (206772, 138514) ``` -Nous avons mentionné que nous pouvions également résoudre le problème de longueur non concordante en donnant aux anciennes colonnes la même taille que les nouvelles. Pour ce faire, nous aurons besoin du champ `overflow_to_sample_mapping` que le tokenizer renvoie lorsque nous définissons `return_overflowing_tokens=True`. Il nous donne une correspondance entre un nouvel index de fonctionnalité et l'index de l'échantillon dont il est issu. Grâce à cela, nous pouvons associer chaque clé présente dans notre jeu de données d'origine à une liste de valeurs de la bonne taille en répétant les valeurs de chaque exemple autant de fois qu'il génère de nouvelles fonctionnalités : +Nous avons mentionné que nous pouvions également résoudre le problème de longueur non concordante en donnant aux anciennes colonnes la même taille que les nouvelles. Pour ce faire, nous avons besoin du champ `overflow_to_sample_mapping` que le *tokenizer* renvoie lorsque nous définissons `return_overflowing_tokens=True`. Il nous donne une correspondance entre un nouvel index de caractéristique et l'index de l'échantillon dont il est issu. Grâce à cela, nous pouvons associer chaque clé présente dans notre jeu de données d'origine à une liste de valeurs de la bonne taille en répétant les valeurs de chaque exemple autant de fois qu'il génère de nouvelles caractéristiques : ```py def tokenize_and_split(examples): @@ -422,7 +422,7 @@ def tokenize_and_split(examples): return result ``` -Nous pouvons voir que cela fonctionne avec `Dataset.map()` sans que nous ayons besoin de supprimer les anciennes colonnes : +Nous pouvons voir que cela fonctionne avec `Dataset.map()` sans que nous ayons besoin de supprimer les anciennes colonnes : ```py tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) @@ -442,22 +442,21 @@ DatasetDict({ }) ``` -Nous obtenons le même nombre de fonctionnalités d'entraînement qu'auparavant, mais ici nous avons conservé tous les anciens champs. Si vous en avez besoin pour un post-traitement après l'application de votre modèle, vous pouvez utiliser cette approche. +Nous obtenons le même nombre de caractéristiques d'entraînement qu'auparavant, mais ici nous avons conservé tous les anciens champs. Si vous en avez besoin pour un post-traitement après l'application de votre modèle, vous pouvez utiliser cette approche. -Vous avez maintenant vu comment 🤗 Datasets peut être utilisé pour prétraiter un ensemble de données de différentes manières. Bien que les fonctions de traitement de 🤗 Datasets couvrent la plupart de vos besoins de formation de modèles, -il peut arriver que vous deviez passer à Pandas pour accéder à des fonctionnalités plus puissantes, telles que `DataFrame.groupby()` ou des API de haut niveau pour la visualisation. Heureusement, 🤗 Datasets est conçu pour être interopérable avec des bibliothèques telles que Pandas, NumPy, PyTorch, TensorFlow et JAX. Voyons comment cela fonctionne. +Vous avez maintenant vu comment 🤗 *Datasets* peut être utilisé pour prétraiter un jeu de données de différentes manières. Bien que les fonctions de traitement de 🤗 *Datasets* couvrent la plupart de vos besoins, il peut arriver que vous deviez passer à Pandas pour accéder à des fonctionnalités plus puissantes, telles que `DataFrame.groupby()` ou des API de haut niveau pour la visualisation. Heureusement, 🤗 *Datasets* est conçu pour être interopérable avec des bibliothèques telles que Pandas, NumPy, PyTorch, TensorFlow et JAX. Voyons comment cela fonctionne. -## De `Dataset`s à `DataFrame`s et vice versa +## De `Dataset` à `DataFrame` et vice versa -Pour permettre la conversion entre diverses bibliothèques tierces, 🤗 Datasets fournit une fonction `Dataset.set_format()`. Cette fonction ne modifie que le _format de sortie_ de l'ensemble de données, vous pouvez donc facilement passer à un autre format sans affecter le _format de données_ sous-jacent, qui est Apache Arrow. Le formatage se fait sur place. Pour démontrer, convertissons notre jeu de données en Pandas : +Pour permettre la conversion entre diverses bibliothèques tierces, 🤗 *Datasets* fournit une fonction `Dataset.set_format()`. Cette fonction ne modifie que le _format de sortie_ du jeu de données. Vous pouvez donc facilement passer à un autre format sans affecter le _format de données_ sous-jacent, qui est Apache Arrow. Le formatage se fait sur place. Pour démontrer, convertissons notre jeu de données vers Pandas : ```py drug_dataset.set_format("pandas") ``` -Maintenant, lorsque nous accédons aux éléments du jeu de données, nous obtenons un `pandas.DataFrame` au lieu d'un dictionnaire : +Maintenant, lorsque nous accédons aux éléments du jeu de données, nous obtenons un `pandas.DataFrame` au lieu d'un dictionnaire : ```py drug_dataset["train"][:3] @@ -514,7 +513,7 @@ drug_dataset["train"][:3] -Créons un `pandas.DataFrame` pour l'ensemble d'entraînement en sélectionnant tous les éléments de `drug_dataset["train"]` : +Créons un `pandas.DataFrame` pour l'ensemble d'entraînement en sélectionnant tous les éléments de `drug_dataset["train"]` : ```py train_df = drug_dataset["train"][:] @@ -522,12 +521,12 @@ train_df = drug_dataset["train"][:] -🚨 Sous le capot, `Dataset.set_format()` change le format de retour pour la méthode dunder `__getitem__()` de l'ensemble de données. Cela signifie que lorsque nous voulons créer un nouvel objet comme `train_df` à partir d'un `Dataset` au format `"pandas"`, nous devons découper tout l'ensemble de données pour obtenir un `pandas.DataFrame`. Vous pouvez vérifier par vous-même que le type de `drug_dataset["train"]` est `Dataset`, quel que soit le format de sortie. +🚨 Sous le capot, `Dataset.set_format()` change le format de retour pour la méthode `__getitem__()`. Cela signifie que lorsque nous voulons créer un nouvel objet comme `train_df` à partir d'un `Dataset` au format `"pandas"`, nous devons découper tout le jeu de données pour obtenir un `pandas.DataFrame`. Vous pouvez vérifier par vous-même que le type de `drug_dataset["train"]` est `Dataset`, quel que soit le format de sortie. -De là, nous pouvons utiliser toutes les fonctionnalités Pandas que nous voulons. Par exemple, nous pouvons faire un chaînage sophistiqué pour calculer la distribution de classe parmi les entrées `condition` : +De là, nous pouvons utiliser toutes les fonctionnalités Pandas que nous voulons. Par exemple, nous pouvons faire un chaînage sophistiqué pour calculer la distribution de classe parmi les entrées `condition` : ```py frequencies = ( @@ -578,7 +577,7 @@ frequencies.head() -Et une fois que nous avons terminé notre analyse Pandas, nous pouvons toujours créer un nouvel objet `Dataset` en utilisant la fonction `Dataset.from_pandas()` comme suit : +Et une fois que nous avons terminé notre analyse Pandas, nous pouvons toujours créer un nouvel objet `Dataset` en utilisant la fonction `Dataset.from_pandas()` comme suit : ```py @@ -597,11 +596,11 @@ Dataset({ -✏️ **Essayez-le !** Calculez la note moyenne par médicament et stockez le résultat dans un nouvel ensemble de données. +✏️ **Essayez !** Calculez la note moyenne par médicament et stockez le résultat dans un nouveau jeu de données. -Ceci conclut notre visite des différentes techniques de prétraitement disponibles dans 🤗 Datasets. Pour compléter la section, créons un ensemble de validation pour préparer l'ensemble de données pour la formation d'un classificateur. Avant cela, nous allons réinitialiser le format de sortie de `drug_dataset` de `"pandas"` à `"arrow"` : +Ceci conclut notre visite des différentes techniques de prétraitement disponibles dans 🤗 *Datasets*. Pour compléter la section, créons un ensemble de validation pour préparer le jeu de données à l’entraînement d'un classifieur. Avant cela, nous allons réinitialiser le format de sortie de `drug_dataset` de `"pandas"` à `"arrow"` : ```python drug_dataset.reset_format() @@ -609,9 +608,9 @@ drug_dataset.reset_format() ## Création d'un ensemble de validation -Bien que nous ayons un jeu de test que nous pourrions utiliser pour l'évaluation, il est recommandé de ne pas toucher au jeu de test et de créer un jeu de validation séparé pendant le développement. Une fois que vous êtes satisfait des performances de vos modèles sur l'ensemble de validation, vous pouvez effectuer une dernière vérification d'intégrité sur l'ensemble de test. Ce processus permet d'atténuer le risque de dépassement de l'ensemble de test et de déploiement d'un modèle qui échoue sur des données du monde réel. +Bien que nous ayons un jeu de test que nous pourrions utiliser pour l'évaluation, il est recommandé de ne pas toucher au jeu de test et de créer un jeu de validation séparé pendant le développement. Une fois que vous êtes satisfait des performances de vos modèles sur l'ensemble de validation, vous pouvez effectuer une dernière vérification d'intégrité sur l'ensemble test. Ce processus permet d'atténuer le risque de surentraînement sur le jeu de test et de déployer un modèle qui échoue sur des données du monde réel. -🤗 Datasets fournit une fonction `Dataset.train_test_split()` basée sur la célèbre fonctionnalité de `scikit-learn`. Utilisons-le pour diviser notre ensemble d'entraînement en divisions "train" et "validation" (nous définissons l'argument "seed" pour la reproductibilité) : +🤗 *Datasets* fournit une fonction `Dataset.train_test_split()` basée sur la célèbre fonctionnalité de `scikit-learn`. Utilisons-la pour diviser notre ensemble d'entraînement `train` et `validation` (nous définissons l'argument `seed` pour la reproductibilité) : ```py drug_dataset_clean = drug_dataset["train"].train_test_split(train_size=0.8, seed=42) @@ -639,27 +638,27 @@ DatasetDict({ }) ``` -Génial, nous avons maintenant préparé un jeu de données prêt pour l'entraînement de certains modèles ! Dans la [section 5](/course/chapter5/5), nous vous montrerons comment télécharger des ensembles de données sur le Hugging Face Hub, mais pour l'instant, terminons notre analyse en examinant quelques façons d'enregistrer des ensembles de données sur votre ordinateur local. +Génial, nous avons maintenant préparé un jeu de données prêt pour l'entraînement de certains modèles ! Dans la [section 5](/course/fr/chapter5/5), nous vous montrerons comment télécharger des jeux de données sur le *Hub*. Mais pour l'instant, terminons notre analyse en examinant quelques façons d'enregistrer des jeux de données sur votre ordinateur local. ## Enregistrer un jeu de données -Bien que 🤗 Datasets mette en cache chaque jeu de données téléchargé et les opérations qui y sont effectuées, il y a des moments où vous voudrez enregistrer un jeu de données sur le disque (par exemple, au cas où le cache serait supprimé). Comme indiqué dans le tableau ci-dessous, 🤗 Datasets fournit trois fonctions principales pour enregistrer votre jeu de données dans différents formats : +Bien que 🤗 *Datasets* mette en cache chaque jeu de données téléchargé et les opérations qui y sont effectuées, il y a des moments où vous voudrez enregistrer un jeu de données sur le disque (par exemple, au cas où le cache serait supprimé). Comme indiqué dans le tableau ci-dessous, 🤗 *Datasets* fournit trois fonctions principales pour enregistrer votre jeu de données dans différents formats : | Format de données | Fonction | | :---------------: | :----------------------: | -| Flèche | `Dataset.save_to_disk()` | +| Arrow | `Dataset.save_to_disk()` | | CSV | `Dataset.to_csv()` | | JSON | `Dataset.to_json()` | -Par exemple, enregistrons notre jeu de données nettoyé au format Arrow : +Par exemple, enregistrons notre jeu de données nettoyé au format Arrow : ```py drug_dataset_clean.save_to_disk("drug-reviews") ``` -Cela créera un répertoire avec la structure suivante : +Cela créera un répertoire avec la structure suivante : ``` drug-reviews/ @@ -680,9 +679,9 @@ drug-reviews/ └── state.json ``` -où nous pouvons voir que chaque division est associée à sa propre table * dataset.arrow * et à certaines métadonnées dans * dataset_info.json * et * state.json *. Vous pouvez considérer le format Arrow comme un tableau sophistiqué de colonnes et de lignes optimisé pour la création d'applications hautes performances qui traitent et transportent de grands ensembles de données. +où nous pouvons voir que chaque division est associée à sa propre table *dataset.arrow* et à certaines métadonnées dans *dataset_info.json* et *state.json*. Vous pouvez considérer le format Arrow comme un tableau sophistiqué de colonnes et de lignes optimisé pour la création d'applications hautes performances qui traitent et transportent de grands ensembles de données. -Une fois le jeu de données enregistré, nous pouvons le charger en utilisant la fonction `load_from_disk()` comme suit : +Une fois le jeu de données enregistré, nous pouvons le charger en utilisant la fonction `load_from_disk()` comme suit : ```py from datasets import load_from_disk @@ -708,14 +707,14 @@ DatasetDict({ }) ``` -Pour les formats CSV et JSON, nous devons stocker chaque fractionnement dans un fichier séparé. Pour ce faire, vous pouvez parcourir les clés et les valeurs de l'objet "DatasetDict" : +Pour les formats CSV et JSON, nous devons stocker chaque fractionnement dans un fichier séparé. Pour ce faire, vous pouvez parcourir les clés et les valeurs de l'objet `DatasetDict` : ```py for split, dataset in drug_dataset_clean.items(): dataset.to_json(f"drug-reviews-{split}.jsonl") ``` -Cela enregistre chaque fractionnement au [format de lignes JSON] (https://jsonlines.org), où chaque ligne de l'ensemble de données est stockée sous la forme d'une seule ligne de JSON. Voici à quoi ressemble le premier exemple : +Cela enregistre chaque fractionnement au [format JSON Lines](https://jsonlines.org), où chaque ligne du jeu de données est stockée sous la forme d'une seule ligne de JSON. Voici à quoi ressemble le premier exemple : ```py !head -n 1 drug-reviews-train.jsonl @@ -725,7 +724,7 @@ Cela enregistre chaque fractionnement au [format de lignes JSON] (https://jsonli {"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} ``` -Nous pouvons ensuite utiliser les techniques de [section 2](/course/chapter5/2) pour charger les fichiers JSON comme suit : +Nous pouvons ensuite utiliser les techniques de [section 2](/course/fr/chapter5/2) pour charger les fichiers JSON comme suit : ```py data_files = { @@ -736,9 +735,9 @@ data_files = { drug_dataset_reloaded = load_dataset("json", data_files=data_files) ``` -Et c'est tout pour notre excursion dans le data wrangling avec 🤗 Datasets ! Maintenant que nous disposons d'un ensemble de données nettoyé pour entraîner un modèle, voici quelques idées que vous pouvez essayer : +Et c'est tout pour notre excursion dans la manipulation des données avec 🤗 *Datasets* ! Maintenant que nous disposons d'un ensemble de données nettoyé pour entraîner un modèle, voici quelques idées que vous pouvez essayer : -1. Utilisez les techniques du [Chapitre 3](/course/chapter3) pour former un classificateur capable de prédire l'état du patient en fonction de l'examen du médicament. -2. Utilisez le pipeline `summarization` du [Chapitre 1](/course/chapter1) pour générer des résumés des révisions. +1. Utilisez les techniques du [Chapitre 3](/course/fr/chapter3) pour entraîner un classifieur capable de prédire l'état du patient en fonction de l'examen du médicament. +2. Utilisez le pipeline `summarization` du [Chapitre 1](/course/fr/chapter1) pour générer des résumés des révisions. -Ensuite, nous verrons comment 🤗 Datasets peut vous permettre de travailler avec d'énormes ensembles de données sans faire exploser votre ordinateur portable ! +Ensuite, nous verrons comment 🤗 *Datasets* peut vous permettre de travailler avec d'énormes jeux de données sans faire exploser votre ordinateur portable ! diff --git a/chapters/fr/chapter5/4.mdx b/chapters/fr/chapter5/4.mdx index 63ed0be44..8657e3b62 100644 --- a/chapters/fr/chapter5/4.mdx +++ b/chapters/fr/chapter5/4.mdx @@ -1,4 +1,4 @@ -# Big Data? 🤗 Datasets à la rescousse ! +# Données massives ? 🤗 *Datasets* à la rescousse ! -De nos jours, il n'est pas rare de travailler avec des ensembles de données de plusieurs gigaoctets, surtout si vous envisagez de pré-entraîner un transformateur comme BERT ou GPT-2 à partir de zéro. Dans ces cas, même _charger_ les données peut être un défi. Par exemple, le corpus WebText utilisé pour pré-entraîner GPT-2 se compose de plus de 8 millions de documents et de 40 Go de texte - le charger dans la RAM de votre ordinateur portable est susceptible de lui donner une crise cardiaque ! +De nos jours, il n'est pas rare de travailler avec des jeux de données de plusieurs gigaoctets surtout si vous envisagez de pré-entraîner un *transformer* comme BERT ou GPT-2 à partir de zéro. Dans ces cas, même _charger_ les données peut être un défi. Par exemple, le corpus WebText utilisé pour pré-entraîner GPT-2 se compose de plus de 8 millions de documents et de 40 Go de texte. Le charger dans la RAM de votre ordinateur portable est susceptible de lui donner une crise cardiaque ! -Heureusement, 🤗 Datasets a été conçu pour surmonter ces limitations. Il vous libère des problèmes de gestion de la mémoire en traitant les ensembles de données comme des fichiers _mappés en mémoire_, et des limites du disque dur en _streaming_ les entrées dans un corpus. +Heureusement, 🤗 *Datasets* a été conçu pour surmonter ces limitations. Il vous libère des problèmes de gestion de la mémoire en traitant les jeux de données comme des fichiers _mappés en mémoire_, ainsi que des limites du disque dur en faisant du _streaming_ sur les entrées dans un corpus. -Dans cette section, nous allons explorer ces fonctionnalités de 🤗 Datasets avec un énorme corpus de 825 Go connu sous le nom de [the Pile](https://pile.eleuther.ai). Commençons! +Dans cette section, nous allons explorer ces fonctionnalités de 🤗 *Datasets* avec un énorme corpus de 825 Go connu sous le nom de [The Pile](https://pile.eleuther.ai). Commençons ! -## Qu'est-ce que The Pile ? +## Qu'est-ce que *The Pile* ? -The Pile est un corpus de texte en anglais créé par [EleutherAI](https://www.eleuther.ai) pour entraîner des modèles de langage à grande échelle. Il comprend une gamme variée d'ensembles de données, couvrant des articles scientifiques, des référentiels de code GitHub et du texte Web filtré. Le corpus de formation est disponible en [morceaux de 14 Go](https://mystic.the-eye.eu/public/AI/pile/), et vous pouvez également télécharger plusieurs des [composants individuels](https://mystic .the-eye.eu/public/AI/pile_preliminary_components/). Commençons par jeter un coup d'œil à l'ensemble de données PubMed Abstracts, qui est un corpus de résumés de 15 millions de publications biomédicales sur [PubMed](https://pubmed.ncbi.nlm.nih.gov/). L'ensemble de données est au [format JSON Lines](https://jsonlines.org) et est compressé à l'aide de la bibliothèque `zstandard`, nous devons donc d'abord l'installer : +The Pile est un corpus de texte en anglais créé par [EleutherAI](https://www.eleuther.ai) pour entraîner des modèles de langage à grande échelle. Il comprend une gamme variée de jeux de données, couvrant des articles scientifiques, des référentiels de code GitHub et du texte Web filtré. Le corpus d’entraînement est disponible en [morceaux de 14 Go](https://mystic.the-eye.eu/public/AI/pile/) et vous pouvez aussi télécharger plusieurs des [composants individuels]( https://mystic.the-eye.eu/public/AI/pile_preliminary_components/). Commençons par jeter un coup d'œil au jeu de données PubMed Abstracts, qui est un corpus de résumés de 15 millions de publications biomédicales sur [PubMed](https://pubmed.ncbi.nlm.nih.gov/). Le jeu de données est au [format JSON Lines](https://jsonlines.org) et est compressé à l'aide de la bibliothèque `zstandard`. Nous devons donc d'abord installer cette bibliothèque : ```py !pip install zstandard ``` -Ensuite, nous pouvons charger le jeu de données en utilisant la méthode pour les fichiers distants que nous avons apprise dans [section 2](/course/chapter5/2) : +Ensuite, nous pouvons charger le jeu de données en utilisant la méthode pour les fichiers distants que nous avons apprise dans [section 2](/course/fr/chapter5/2) : ```py from datasets import load_dataset @@ -42,15 +42,15 @@ Dataset({ }) ``` -Nous pouvons voir qu'il y a 15 518 009 lignes et 2 colonnes dans notre ensemble de données -- c'est beaucoup ! +Nous pouvons voir qu'il y a 15 518 009 lignes et 2 colonnes dans notre jeu de données. C'est beaucoup ! -✎ Par défaut, 🤗 Datasets décompressera 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.html?#datasets.utils.DownloadConfig) pour plus de détails. -Inspectons le contenu du premier exemple : +Inspectons le contenu du premier exemple : ```py pubmed_dataset[0] @@ -58,25 +58,27 @@ pubmed_dataset[0] ```python out {'meta': {'pmid': 11409574, 'language': 'eng'}, - '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 ...'} + '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 ...' +# Épidémiologie de l'hypoxémie chez les enfants souffrant d'une infection aiguë des voies respiratoires inférieures. Déterminer la prévalence de l'hypoxémie chez les enfants de moins de 5 ans souffrant d'une infection aiguë des voies respiratoires inférieures (IAVI), les facteurs de risque de l'hypoxémie chez les enfants de moins de 5 ans souffrant d'une IAVI, et l'association de l'hypoxémie à un risque accru de décès chez les enfants du même âge ... +} ``` -OK, ça ressemble au résumé d'un article médical. Voyons maintenant combien de RAM nous avons utilisé pour charger le jeu de données ! +Cela ressemble au résumé d'un article médical. Voyons maintenant combien de RAM nous avons utilisé pour charger le jeu de données ! -## La magie de la cartographie mémoire +## La magie du *memory mapping* -Un moyen simple de mesurer l'utilisation de la mémoire dans Python consiste à utiliser la bibliothèque [`psutil`](https://psutil.readthedocs.io/en/latest/), qui peut être installée avec `pip` comme suit : +Un moyen simple de mesurer l'utilisation de la mémoire dans Python consiste à utiliser la bibliothèque [`psutil`](https://psutil.readthedocs.io/en/latest/) qui peut être installée avec `pip` comme suit : ```python !pip install psutil ``` -Il fournit une classe `Process` qui nous permet de vérifier l'utilisation de la mémoire du processus en cours comme suit : +Elle fournit une classe `Process` qui permet de vérifier l'utilisation de la mémoire du processus en cours : ```py import psutil -# Process.memory_info is expressed in bytes, so convert to megabytes +# Process.memory_info est exprimé en octets, donc convertir en mégaoctets print(f"RAM used: {psutil.Process().memory_info().rss / (1024 * 1024):.2f} MB") ``` @@ -84,7 +86,7 @@ print(f"RAM used: {psutil.Process().memory_info().rss / (1024 * 1024):.2f} MB") RAM used: 5678.33 MB ``` -Ici, l'attribut `rss` fait référence à la _taille de l'ensemble résident_, qui est la fraction de mémoire qu'un processus occupe dans la RAM. Cette mesure inclut également la mémoire utilisée par l'interpréteur Python et les bibliothèques que nous avons chargées, de sorte que la quantité réelle de mémoire utilisée pour charger l'ensemble de données est un peu plus petite. À titre de comparaison, voyons la taille de l'ensemble de données sur le disque, en utilisant l'attribut `dataset_size`. Comme le résultat est exprimé en octets comme précédemment, nous devons le convertir manuellement en gigaoctets : +Ici, l'attribut `rss` fait référence à la _taille de l'ensemble résident_, qui est la fraction de mémoire qu'un processus occupe dans la RAM. Cette mesure inclut également la mémoire utilisée par l'interpréteur Python et les bibliothèques que nous avons chargées, de sorte que la quantité réelle de mémoire utilisée pour charger le jeu de données est un peu plus petite. À titre de comparaison, voyons la taille du jeu de données sur le disque en utilisant l'attribut `dataset_size`. Comme le résultat est exprimé en octets comme précédemment, nous devons le convertir manuellement en gigaoctets : ```py print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") @@ -97,17 +99,17 @@ Number of files in dataset : 20979437051 Dataset size (cache file) : 19.54 GB ``` -Bien - malgré sa taille de près de 20 Go, nous pouvons charger et accéder à l'ensemble de données avec beaucoup moins de RAM ! +Malgré sa taille de près de 20 Go, nous pouvons charger et accéder au jeu de données avec beaucoup moins de RAM ! -✏️ **Essayez-le !** Choisissez l'un des [sous-ensembles](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/) de la Pile qui est plus grand que la RAM de votre ordinateur portable ou de bureau, chargez avec 🤗 Datasets, et mesurez la quantité de RAM utilisée. Notez que pour obtenir une mesure précise, vous devrez le faire dans un nouveau processus. Vous pouvez trouver les tailles décompressées de chaque sous-ensemble dans le tableau 1 de [the Pile paper](https://arxiv.org/abs/2101.00027). +✏️ **Essayez !** Choisissez l'un des [sous-ensembles](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/) de The Pile qui est plus grand que la RAM de votre ordinateur portable ou de bureau. Chargez le avec 🤗 *Datasets* et mesurez la quantité de RAM utilisée. Notez que pour obtenir une mesure précise, vous devrez le faire dans un nouveau processus. Vous pouvez trouver les tailles décompressées de chaque sous-ensemble dans le tableau 1 du papier de [The Pile](https://arxiv.org/abs/2101.00027). -Si vous êtes familier avec les pandas, ce résultat pourrait surprendre en raison de la célèbre [règle d'or](https://wesmckinney.com/blog/apache-arrow-pandas-internals/) de Wes Kinney selon laquelle vous avez généralement besoin de 5 à 10 fois plus de RAM que la taille de votre jeu de données. Alors, comment 🤗 Datasets résout-il ce problème de gestion de la mémoire ? 🤗 Datasets traite chaque ensemble de données comme un [fichier mappé en mémoire] (https://en.wikipedia.org/wiki/Memory-mapped_file), qui fournit un mappage entre la RAM et le stockage du système de fichiers qui permet à la bibliothèque d'accéder et d'opérer sur des éléments du jeu de données sans avoir besoin de le charger entièrement en mémoire. +Si vous êtes familier avec Pandas, ce résultat pourrait surprendre en raison de la célèbre [règle d'or](https://wesmckinney.com/blog/apache-arrow-pandas-internals/) de Wes Kinney selon laquelle vous avez généralement besoin de 5 à 10 fois plus de RAM que la taille de votre jeu de données. Alors, comment 🤗 *Datasets* résout-il ce problème de gestion de la mémoire ? 🤗 *Datasets* traite chaque jeu de données comme un [fichier mappé en mémoire](https://en.wikipedia.org/wiki/Memory-mapped_file). Cela fournit un mappage entre la RAM et le stockage du système de fichiers permettant à la bibliothèque d'accéder et d'opérer sur des éléments du jeu de données sans avoir besoin de le charger entièrement en mémoire. -Les fichiers mappés en mémoire peuvent également être partagés entre plusieurs processus, ce qui permet de paralléliser des méthodes telles que `Dataset.map()` sans avoir à déplacer ou copier l'ensemble de données. Sous le capot, ces capacités sont toutes réalisées par le format de mémoire [Apache Arrow](https://arrow.apache.org) et [`pyarrow`](https://arrow.apache.org/docs/python/index .html), qui accélèrent le chargement et le traitement des données. (Pour plus de détails sur Apache Arrow et les comparaisons avec Pandas, consultez [l'article de blog de Dejan Simic](https://towardsdatascience.com/apache-arrow-read-dataframe-with-zero-memory-69634092b1a).) Pour voir ceci en action, effectuons un petit test de vitesse en itérant sur tous les éléments du jeu de données PubMed Abstracts : +Les fichiers mappés en mémoire peuvent également être partagés entre plusieurs processus ce qui permet de paralléliser des méthodes telles que `Dataset.map()` sans avoir à déplacer ou copier le jeu de données. Sous le capot, ces capacités sont toutes réalisées par le format de mémoire [Apache Arrow](https://arrow.apache.org) et [`pyarrow`](https://arrow.apache.org/docs/python/index .html), qui accélèrent le chargement et le traitement des données. (Pour plus de détails sur Apache Arrow et les comparaisons avec Pandas, consultez [l'article de blog de Dejan Simic](https://towardsdatascience.com/apache-arrow-read-dataframe-with-zero-memory-69634092b1a).) Pour voir ceci en action, effectuons un petit test de vitesse en itérant sur tous les éléments du jeu de données PubMed Abstracts : ```py import timeit @@ -129,17 +131,17 @@ print( 'Iterated over 15518009 examples (about 19.5 GB) in 64.2s, i.e. 0.304 GB/s' ``` -Ici, nous avons utilisé le module `timeit` de Python pour mesurer le temps d'exécution pris par `code_snippet`. Vous pourrez généralement itérer sur un ensemble de données à une vitesse de quelques dixièmes de Go/s à plusieurs Go/s. Cela fonctionne très bien pour la grande majorité des applications, mais vous devrez parfois travailler avec un ensemble de données trop volumineux pour être même stocké sur le disque dur de votre ordinateur portable. Par exemple, si nous essayions de télécharger la Pile dans son intégralité, nous aurions besoin de 825 Go d'espace disque libre ! Pour gérer ces cas, 🤗 Datasets fournit une fonctionnalité de streaming qui nous permet de télécharger et d'accéder aux éléments à la volée, sans avoir besoin de télécharger l'intégralité du jeu de données. Voyons comment cela fonctionne. +Ici, nous avons utilisé le module `timeit` de Python pour mesurer le temps d'exécution pris par `code_snippet`. Vous pourrez généralement itérer sur un jeu de données à une vitesse de quelques dixièmes de Go/s à plusieurs Go/s. Cela fonctionne très bien pour la grande majorité des applications, mais vous devrez parfois travailler avec un jeu de données trop volumineux pour être même stocké sur le disque dur de votre ordinateur portable. Par exemple, si nous essayions de télécharger The Pile dans son intégralité, nous aurions besoin de 825 Go d'espace disque libre ! Pour gérer ces cas, 🤗 *Datasets* fournit une fonctionnalité de streaming qui nous permet de télécharger et d'accéder aux éléments à la volée, sans avoir besoin de télécharger l'intégralité du jeu de données. Voyons comment cela fonctionne. -💡 Dans les notebooks Jupyter, vous pouvez également chronométrer les cellules à l'aide de la fonction magique [`%%timeit`](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit). +💡 Dans les *notebooks* Jupyter, vous pouvez également chronométrer les cellules à l'aide de la fonction magique [`%%timeit`](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit). -## Ensembles de données en continu +## Jeux de données en continu -Pour activer le streaming de l'ensemble de données, il vous suffit de passer l'argument `streaming=True` à la fonction `load_dataset()`. Par exemple, chargeons à nouveau le jeu de données PubMed Abstracts, mais en mode streaming : +Pour activer le streaming du jeu de données, il vous suffit de passer l'argument `streaming=True` à la fonction `load_dataset()`. Par exemple, chargeons à nouveau le jeu de données PubMed Abstracts mais en mode streaming : ```py pubmed_dataset_streamed = load_dataset( @@ -147,7 +149,7 @@ pubmed_dataset_streamed = load_dataset( ) ``` -Au lieu du familier `Dataset` que nous avons rencontré ailleurs dans ce chapitre, l'objet retourné avec `streaming=True` est un `IterableDataset`. Comme son nom l'indique, pour accéder aux éléments d'un `IterableDataset`, nous devons parcourir celui-ci. Nous pouvons accéder au premier élément de notre jeu de données diffusé comme suit : +Au lieu du familier `Dataset` que nous avons rencontré ailleurs dans ce chapitre, l'objet retourné avec `streaming=True` est un `IterableDataset`. Comme son nom l'indique, pour accéder aux éléments d'un `IterableDataset`, nous devons parcourir celui-ci. Nous pouvons accéder au premier élément de notre jeu de données diffusé comme suit : ```py @@ -159,7 +161,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 ...'} ``` -Les éléments d'un ensemble de données diffusé en continu peuvent être traités à la volée à l'aide de `IterableDataset.map()`, ce qui est utile pendant la formation si vous avez besoin de tokeniser les entrées. Le processus est exactement le même que celui que nous avons utilisé pour tokeniser notre jeu de données dans [Chapitre 3](/course/chapter3), à la seule différence que les sorties sont renvoyées une par une : +Les éléments d'un jeu de données diffusé en continu peuvent être traités à la volée à l'aide de `IterableDataset.map()`, ce qui est utile pendant l’entraînement si vous avez besoin de tokeniser les entrées. Le processus est exactement le même que celui que nous avons utilisé pour tokeniser notre jeu de données dans [Chapitre 3](/course/fr/chapter3), à la seule différence que les sorties sont renvoyées une par une : ```py from transformers import AutoTokenizer @@ -175,11 +177,11 @@ next(iter(tokenized_dataset)) -💡 Pour accélérer la tokenisation avec le streaming, vous pouvez passer `batched=True`, comme nous l'avons vu dans la dernière section. Il traitera les exemples lot par lot ; la taille de lot par défaut est de 1 000 et peut être spécifiée avec l'argument `batch_size`. +💡 Pour accélérer la tokenisation avec le streaming, vous pouvez passer `batched=True`, comme nous l'avons vu dans la dernière section. Il traitera les exemples batch par batch. La taille de batch par défaut est de 1 000 et peut être spécifiée avec l'argument `batch_size`. -Vous pouvez également mélanger un ensemble de données diffusé en continu à l'aide de `IterableDataset.shuffle()`, mais contrairement à `Dataset.shuffle()`, cela ne mélange que les éléments dans un `buffer_size` prédéfini : +Vous pouvez également mélanger un jeu de données diffusé en continu à l'aide de `IterableDataset.shuffle()`, mais contrairement à `Dataset.shuffle()`, cela ne mélange que les éléments dans un `buffer_size` prédéfini : ```py shuffled_dataset = pubmed_dataset_streamed.shuffle(buffer_size=10_000, seed=42) @@ -188,10 +190,12 @@ next(iter(shuffled_dataset)) ```python out {'meta': {'pmid': 11410799, 'language': 'eng'}, - 'text': 'Randomized study of dose or schedule modification of granulocyte colony-stimulating factor in platinum-based chemotherapy for elderly patients with lung cancer ...'} + 'text': 'Randomized study of dose or schedule modification of granulocyte colony-stimulating factor in platinum-based chemotherapy for elderly patients with lung cancer ...' +# Étude randomisée sur la modification de la dose ou du calendrier d'administration du facteur de stimulation des colonies de granulocytes dans le cadre d'une chimiothérapie à base de platine chez les patients âgés atteints de cancer du poumon ... +} ``` -Dans cet exemple, nous avons sélectionné un exemple aléatoire parmi les 10 000 premiers exemples du tampon. Une fois qu'un exemple est accédé, sa place dans le tampon est remplie avec l'exemple suivant dans le corpus (c'est-à-dire le 10 001e exemple dans le cas ci-dessus). Vous pouvez également sélectionner des éléments d'un ensemble de données diffusé en continu à l'aide des fonctions `IterableDataset.take()` et `IterableDataset.skip()`, qui agissent de la même manière que `Dataset.select()`. Par exemple, pour sélectionner les 5 premiers exemples dans l'ensemble de données PubMed Abstracts, nous pouvons procéder comme suit : +Dans cet exemple, nous avons sélectionné un exemple aléatoire parmi les 10 000 premiers exemples du tampon. Une fois qu'un exemple est accédé, sa place dans le tampon est remplie avec l'exemple suivant dans le corpus (c'est-à-dire le 10 001e exemple dans le cas ci-dessus). Vous pouvez également sélectionner des éléments d'un jeu de données diffusé en continu à l'aide des fonctions `IterableDataset.take()` et `IterableDataset.skip()`, qui agissent de la même manière que `Dataset.select()`. Par exemple, pour sélectionner les 5 premiers exemples dans le jeu de données PubMed Abstracts, nous pouvons procéder comme suit : ```py dataset_head = pubmed_dataset_streamed.take(5) @@ -200,27 +204,32 @@ list(dataset_head) ```python out [{'meta': {'pmid': 11409574, 'language': 'eng'}, - 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'}, + 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...' +# Épidémiologie de l'hypoxémie chez les enfants atteints d'une infection aiguë des voies respiratoires inférieures ...}, {'meta': {'pmid': 11409575, 'language': 'eng'}, - 'text': 'Clinical signs of hypoxaemia in children with acute lower respiratory infection: indicators of oxygen therapy ...'}, + 'text': 'Clinical signs of hypoxaemia in children with acute lower respiratory infection: indicators of oxygen therapy ...' +# Signes cliniques d'hypoxémie chez les enfants atteints d'une infection aiguë des voies respiratoires inférieures : indicateurs de l'oxygénothérapie ...}, {'meta': {'pmid': 11409576, 'language': 'eng'}, - 'text': "Hypoxaemia in children with severe pneumonia in Papua New Guinea ..."}, + 'text': "Hypoxaemia in children with severe pneumonia in Papua New Guinea ..." +# Hypoxémie chez les enfants atteints de pneumonie grave en Papouasie-Nouvelle-Guinée ...}, {'meta': {'pmid': 11409577, 'language': 'eng'}, - 'text': 'Oxygen concentrators and cylinders ...'}, + 'text': 'Oxygen concentrators and cylinders ...' +# Concentrateurs et bouteilles d'oxygène...}, {'meta': {'pmid': 11409578, 'language': 'eng'}, - 'text': 'Oxygen supply in rural africa: a personal experience ...'}] + 'text': 'Oxygen supply in rural africa: a personal experience ...' +# L'approvisionnement en oxygène dans les zones rurales africaines : une expérience personnelle ...}] ``` -De même, vous pouvez utiliser la fonction `IterableDataset.skip()` pour créer des fractionnements d'entraînement et de validation à partir d'un ensemble de données mélangé comme suit : +De même, vous pouvez utiliser la fonction `IterableDataset.skip()` pour créer des fractionnements d'entraînement et de validation à partir d'un jeu de données mélangé comme suit : ```py -# Skip the first 1,000 examples and include the rest in the training set +# Ignorer les 1 000 premiers exemples et inclure le reste dans l'ensemble d'apprentissage. train_dataset = shuffled_dataset.skip(1000) -# Take the first 1,000 examples for the validation set +# Prendre les 1 000 premiers exemples pour l'ensemble de validation. validation_dataset = shuffled_dataset.take(1000) ``` -Terminons notre exploration du streaming d'ensembles de données avec une application commune : combiner plusieurs ensembles de données pour créer un seul corpus. 🤗 Datasets fournit une fonction `interleave_datasets()` qui convertit une liste d'objets `IterableDataset` en un seul `IterableDataset`, où les éléments du nouveau jeu de données sont obtenus en alternant entre les exemples source. Cette fonction est particulièrement utile lorsque vous essayez de combiner de grands ensembles de données. Par exemple, diffusons le sous-ensemble FreeLaw de la pile, qui est un ensemble de données de 51 Go d'avis juridiques de tribunaux américains : +Terminons notre exploration du streaming des jeux de données avec une application commune : combiner plusieurs jeux de données pour créer un seul corpus. 🤗 *Datasets* fournit une fonction `interleave_datasets()` qui convertit une liste d'objets `IterableDataset` en un seul `IterableDataset`, où les éléments du nouveau jeu de données sont obtenus en alternant entre les exemples source. Cette fonction est particulièrement utile lorsque vous essayez de combiner de grands jeux de données. Par exemple, streamons FreeLaw, un sous-ensemble de The Pile et qui est un jeu de données de 51 Go d'avis juridiques de tribunaux américains : ```py law_dataset_streamed = load_dataset( @@ -239,7 +248,7 @@ next(iter(law_dataset_streamed)) 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'} ``` -Cet ensemble de données est suffisamment volumineux pour solliciter la RAM de la plupart des ordinateurs portables, mais nous avons pu le charger et y accéder sans transpirer ! Combinons maintenant les exemples des jeux de données FreeLaw et PubMed Abstracts avec la fonction `interleave_datasets()` : +Ce jeu de données est suffisamment volumineux pour solliciter la RAM de la plupart des ordinateurs portables, mais nous avons pu le charger et y accéder sans transpirer ! Combinons maintenant les jeux de données FreeLaw et PubMed Abstracts avec la fonction `interleave_datasets()` : ```py from itertools import islice @@ -258,9 +267,9 @@ list(islice(combined_dataset, 2)) 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'}] ``` -Ici, nous avons utilisé la fonction `islice()` du module `itertools` de Python pour sélectionner les deux premiers exemples de l'ensemble de données combiné, et nous pouvons voir qu'ils correspondent aux premiers exemples de chacun des deux ensembles de données source. +Ici, nous avons utilisé la fonction `islice()` du module `itertools` de Python pour sélectionner les deux premiers exemples du jeu de données combiné. Nous pouvons voir qu'ils correspondent aux premiers exemples de chacun des deux jeux de données source. -Enfin, si vous souhaitez diffuser le Pile dans son intégralité de 825 Go, vous pouvez récupérer tous les fichiers préparés comme suit : +Enfin, si vous souhaitez streamer The Pile dans son intégralité de 825 Go, vous pouvez récupérer tous les fichiers préparés comme suit : ```py base_url = "https://mystic.the-eye.eu/public/AI/pile/" @@ -280,8 +289,8 @@ next(iter(pile_dataset["train"])) -✏️ **Essayez-le !** Utilisez l'un des grands corpus Common Crawl comme [`mc4`](https://huggingface.co/datasets/mc4) ou [`oscar`](https://huggingface.co /datasets/oscar) pour créer un jeu de données multilingue en continu qui représente les proportions de langues parlées dans un pays de votre choix. Par exemple, les quatre langues nationales en Suisse sont l'allemand, le français, l'italien et le romanche, vous pouvez donc essayer de créer un corpus suisse en échantillonnant les sous-ensembles Oscar en fonction de leur proportion parlée. +✏️ **Essayez !** Utilisez l'un des grands corpus Common Crawl comme [`mc4`](https://huggingface.co/datasets/mc4) ou [`oscar`](https://huggingface.co/datasets/oscar) pour créer en streaming un jeu de données multilingue représentant les proportions de langues parlées dans un pays de votre choix. Par exemple, les quatre langues nationales en Suisse sont l'allemand, le français, l'italien et le romanche. Vous pouvez donc essayer de créer un corpus suisse en échantillonnant les sous-ensembles Oscar en fonction de leur proportion parlée. -Vous disposez maintenant de tous les outils dont vous avez besoin pour charger et traiter des ensembles de données de toutes formes et tailles - mais à moins que vous ne soyez exceptionnellement chanceux, il arrivera un moment dans votre parcours PNL où vous devrez réellement créer un ensemble de données pour résoudre le problème à portée de main. C'est le sujet de la section suivante ! +Vous disposez maintenant de tous les outils dont vous avez besoin pour charger et traiter des jeux de données de toutes formes et tailles. Cependant à moins que vous ne soyez exceptionnellement chanceux, il arrivera un moment dans votre cheminement en traitement du langage naturel où vous devrez réellement créer un jeu de données pour résoudre un problème donné. C'est le sujet de la section suivante ! diff --git a/chapters/fr/chapter5/5.mdx b/chapters/fr/chapter5/5.mdx index 298f69200..42a9ffa15 100644 --- a/chapters/fr/chapter5/5.mdx +++ b/chapters/fr/chapter5/5.mdx @@ -7,37 +7,37 @@ {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter5/section5.ipynb"}, ]} /> -Parfois, l'ensemble de données dont vous avez besoin pour créer une application NLP n'existe pas, vous devrez donc le créer vous-même. Dans cette section, nous allons vous montrer comment créer un corpus de [problèmes GitHub](https://github.com/features/issues/), qui sont couramment utilisés pour suivre les bogues ou les fonctionnalités dans les référentiels GitHub. Ce corpus pourrait être utilisé à diverses fins, notamment : +Parfois, le jeu de données dont vous avez besoin pour créer une application de NLP n'existe pas. Vous devrez donc le créer vous-même. Dans cette section, nous allons vous montrer comment créer un corpus de [problèmes GitHub](https://github.com/features/issues/), qui sont couramment utilisés pour suivre les bogues ou les fonctionnalités dans les dépôts GitHub. Ce corpus pourrait être utilisé à diverses fins, notamment : -* Explorer combien de temps il faut pour fermer les problèmes ouverts ou les demandes d'extraction -* Entraînement d'un _classificateur multilabel_ capable d'étiqueter les problèmes avec des métadonnées basées sur la description du problème (par exemple, "bogue", "amélioration" ou "question") -* Création d'un moteur de recherche sémantique pour trouver les problèmes correspondant à la requête d'un utilisateur +* explorer combien de temps il faut pour fermer les problèmes ouverts ou les *pull requests* +* entraîner d'un _classificateur multilabel_ capable d'étiqueter les problèmes avec des métadonnées basées sur la description du problème (par exemple, "bogue", "amélioration" ou "question") +* créer un moteur de recherche sémantique pour trouver les problèmes correspondant à la requête d'un utilisateur -Ici, nous nous concentrerons sur la création du corpus, et dans la section suivante, nous aborderons l'application de recherche sémantique. Pour garder les choses méta, nous utiliserons les problèmes GitHub associés à un projet open source populaire : 🤗 Datasets ! Voyons comment obtenir les données et explorons les informations contenues dans ces problèmes. +Ici, nous nous concentrerons sur la création du corpus, et dans la section suivante, nous aborderons l'application de recherche sémantique. Pour garder les choses méta, nous utiliserons les problèmes GitHub associés à un projet open source populaire : 🤗 *Datasets* ! Voyons comment obtenir les données et explorons les informations contenues dans ces problèmes. ## Obtenir les données -Vous pouvez trouver tous les problèmes dans 🤗 Datasets en accédant à l'[onglet Problèmes] du référentiel (https://github.com/huggingface/datasets/issues). Comme le montre la capture d'écran suivante, au moment de la rédaction, il y avait 331 problèmes ouverts et 668 problèmes fermés. +Vous pouvez trouver tous les problèmes dans 🤗 *Datasets* en accédant à l'[onglet « Issues »](https://github.com/huggingface/datasets/issues) du dépôt. Comme le montre la capture d'écran suivante, au moment de la rédaction, il y avait 331 problèmes ouverts et 668 problèmes fermés.
-Les problèmes GitHub associés aux 🤗 Datasets. +The GitHub issues associated with 🤗 Datasets.
Si vous cliquez sur l'un de ces problèmes, vous constaterez qu'il contient un titre, une description et un ensemble d'étiquettes qui caractérisent le problème. Un exemple est montré dans la capture d'écran ci-dessous.
-Un problème GitHub typique dans le référentiel 🤗 Datasets. +A typical GitHub issue in the 🤗 Datasets repository.
-Pour télécharger tous les problèmes du référentiel, nous utiliserons l'[API REST GitHub](https://docs.github.com/en/rest) pour interroger le point de terminaison [`Issues`](https://docs.github. com/en/rest/reference/issues#list-repository-issues). Ce point de terminaison renvoie une liste d'objets JSON, chaque objet contenant un grand nombre de champs qui incluent le titre et la description ainsi que des métadonnées sur l'état du problème, etc. +Pour télécharger tous les problèmes du dépôt, nous utilisons l'[API REST GitHub](https://docs.github.com/en/rest) pour interroger le point de terminaison [`Issues`](https://docs.github.com/en/rest/reference/issues#list-repository-issues). Ce point de terminaison renvoie une liste d'objets JSON. Chaque objet contenant un grand nombre de champs qui incluent le titre et la description ainsi que des métadonnées sur l'état du problème, etc. -Un moyen pratique de télécharger les problèmes consiste à utiliser la bibliothèque "requests", qui est la méthode standard pour effectuer des requêtes HTTP en Python. Vous pouvez installer la bibliothèque en exécutant : +Un moyen pratique de télécharger les problèmes consiste à utiliser la bibliothèque `requests`, qui est la méthode standard pour effectuer des requêtes HTTP en Python. Vous pouvez installer la bibliothèque en exécutant : ```python !pip install requests ``` -Une fois la bibliothèque installée, vous pouvez envoyer des requêtes GET au point de terminaison `Issues` en appelant la fonction `requests.get()`. Par exemple, vous pouvez exécuter la commande suivante pour récupérer le premier numéro sur la première page : +Une fois la bibliothèque installée, vous pouvez envoyer des requêtes GET au point de terminaison `Issues` en appelant la fonction `requests.get()`. Par exemple, vous pouvez exécuter la commande suivante pour récupérer le premier numéro sur la première page : ```py import requests @@ -46,7 +46,7 @@ url = "https://api.github.com/repos/huggingface/datasets/issues?page=1&per_page= response = requests.get(url) ``` -L'objet `response` contient de nombreuses informations utiles sur la requête, y compris le code d'état HTTP : +L'objet `response` contient de nombreuses informations utiles sur la requête, y compris le code d'état HTTP : ```py response.status_code @@ -56,7 +56,7 @@ response.status_code 200 ``` -où un statut "200" signifie que la requête a réussi (vous pouvez trouver une liste des codes de statut HTTP possibles [ici](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes)). Ce qui nous intéresse vraiment, cependant, c'est le _payload_, qui peut être consulté dans différents formats comme les octets, les chaînes ou JSON. Comme nous savons que nos problèmes sont au format JSON, examinons la charge utile comme suit : +où un statut `200` signifie que la requête a réussi (vous pouvez trouver une liste des codes de statut HTTP possibles [ici](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes)). Ce qui nous intéresse vraiment, cependant, c'est le _payload_, qui peut être consulté dans différents formats comme les octets, les chaînes ou JSON. Comme nous savons que nos problèmes sont au format JSON, examinons la charge utile comme suit : ```py response.json() @@ -115,11 +115,11 @@ Waouh, ça fait beaucoup d'informations ! Nous pouvons voir des champs utiles co -✏️ **Essayez-le !** Cliquez sur quelques-unes des URL dans la charge utile JSON ci-dessus pour avoir une idée du type d'informations auxquelles chaque problème GitHub est lié. +✏️ **Essayez !** Cliquez sur quelques-unes des URL pour avoir une idée du type d'informations auxquelles chaque problème GitHub est lié. -Comme décrit dans la [documentation] GitHub(https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting), les requêtes non authentifiées sont limitées à 60 requêtes par heure. Bien que vous puissiez augmenter le paramètre de requête `per_page` pour réduire le nombre de requêtes que vous effectuez, vous atteindrez toujours la limite de débit sur tout référentiel contenant plus de quelques milliers de problèmes. Donc, à la place, vous devez suivre les [instructions] de GitHub (https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) sur la création d'un _jeton d'accès personnel_ afin que vous peut augmenter la limite de débit à 5 000 requêtes par heure. Une fois que vous avez votre jeton, vous pouvez l'inclure dans l'en-tête de la requête : +Comme décrit dans la [documentation GitHub](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting), les requêtes non authentifiées sont limitées à 60 requêtes par heure. Bien que vous puissiez augmenter le paramètre de requête `per_page` pour réduire le nombre de requêtes que vous effectuez, vous atteindrez toujours la limite de débit sur tout dépot contenant des milliers de problèmes. Donc, à la place, vous devez suivre les [instructions de GitHub](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) sur la création d'un _jeton d'accès personnel_ afin que vous peut augmenter la limite de débit à 5 000 requêtes par heure. Une fois que vous avez votre *token*, vous pouvez l'inclure dans l'en-tête de la requête : ```py GITHUB_TOKEN = xxx # Copy your GitHub token here @@ -128,11 +128,11 @@ headers = {"Authorization": f"token {GITHUB_TOKEN}"} -⚠️ Ne partagez pas un notebook avec votre `GITHUB_TOKEN` collé dedans. Nous vous recommandons de supprimer la dernière cellule une fois que vous l'avez exécutée pour éviter de divulguer accidentellement ces informations. Mieux encore, stockez le jeton dans un fichier *.env* et utilisez la [bibliothèque `python-dotenv`](https://github.com/theskumar/python-dotenv) pour le charger automatiquement pour vous en tant que variable d'environnement. +⚠️ Ne partagez pas un *notebook* avec votre `GITHUB_TOKEN` collé dedans. Nous vous recommandons de supprimer la dernière cellule une fois que vous l'avez exécutée pour éviter de divulguer accidentellement ces informations. Mieux encore, stockez le jeton dans un fichier *.env* et utilisez la [bibliothèque `python-dotenv`](https://github.com/theskumar/python-dotenv) pour le charger automatiquement pour vous en tant que variable d'environnement. -Maintenant que nous avons notre jeton d'accès, créons une fonction qui peut télécharger tous les problèmes depuis un référentiel GitHub : +Maintenant que nous avons notre jeton d'accès, créons une fonction qui peut télécharger tous les problèmes depuis un référentiel GitHub : ```py import time @@ -178,14 +178,14 @@ def fetch_issues( ) ``` -Désormais, lorsque nous appellerons `fetch_issues()`, tous les problèmes seront téléchargés par lots pour éviter de dépasser la limite de GitHub sur le nombre de requêtes par heure ; le résultat sera stocké dans un fichier _repository_name-issues.jsonl_, où chaque ligne est un objet JSON qui représente un problème. Utilisons cette fonction pour saisir tous les problèmes de 🤗 Datasets : +Désormais, lorsque nous appellerons `fetch_issues()`, tous les problèmes seront téléchargés par batchs pour éviter de dépasser la limite de GitHub sur le nombre de requêtes par heure. Le résultat sera stocké dans un fichier _repository_name-issues.jsonl_, où chaque ligne est un objet JSON qui représente un problème. Utilisons cette fonction pour saisir tous les problèmes de 🤗 *Datasets* : ```py -# Depending on your internet connection, this can take several minutes to run... +# En fonction de votre connexion Internet, l'exécution peut prendre plusieurs minutes... fetch_issues() ``` -Une fois les problèmes téléchargés, nous pouvons les charger localement en utilisant nos nouvelles compétences de [section 2](/course/chaper5/2) : +Une fois les problèmes téléchargés, nous pouvons les charger localement en utilisant nos nouvelles compétences de [section 2](/course/fr/chaper5/2) : ```py issues_dataset = load_dataset("json", data_files="datasets-issues.jsonl", split="train") @@ -199,15 +199,15 @@ Dataset({ }) ``` -Génial, nous avons créé notre premier ensemble de données à partir de rien ! Mais pourquoi y a-t-il plusieurs milliers de problèmes alors que l'[onglet Problèmes](https://github.com/huggingface/datasets/issues) du 🤗 Datasets n'affiche qu'environ 1 000 problèmes au total 🤔 ? Comme décrit dans la [documentation] GitHub(https://docs.github.com/en/rest/reference/issues#list-issues-assigned-to-the-authenticated-user), c'est parce que nous avons téléchargé tous les les demandes d'extraction également : +Génial, nous avons créé notre premier jeu de données à partir de rien ! Mais pourquoi y a-t-il plusieurs milliers de problèmes alors que l'[onglet « Issues »](https://github.com/huggingface/datasets/issues) de la librairie 🤗 *Datasets* n'affiche qu'environ 1 000 problèmes au total 🤔 ? Comme décrit dans la [documentation GitHub](https://docs.github.com/en/rest/reference/issues#list-issues-assigned-to-the-authenticated-user), c'est parce que nous avons téléchargé toutes les *pull requests* également : -> L'API REST v3 de GitHub considère chaque demande d'extraction comme un problème, mais chaque problème n'est pas une demande d'extraction. Pour cette raison, les points de terminaison "Problèmes" peuvent renvoyer à la fois des problèmes et des demandes d'extraction dans la réponse. Vous pouvez identifier les demandes d'extraction par la clé `pull_request`. Sachez que l'identifiant d'une demande d'extraction renvoyée par les points de terminaison "Problèmes" sera un identifiant de problème. +> L'API REST v3 de GitHub considère chaque *pull request* comme un problème, mais chaque problème n'est pas une *pull request*. Pour cette raison, les points de terminaison « Issues » peuvent renvoyer à la fois des problèmes et des *pull requests* dans la réponse. Vous pouvez identifier les *pull requests* par la clé `pull_request`. Sachez que l'identifiant d'une *pull request* renvoyée par les points de terminaison « Issues » sera un identifiant de problème. -Étant donné que le contenu des problèmes et des demandes d'extraction est assez différent, procédons à un prétraitement mineur pour nous permettre de les distinguer. +Étant donné que le contenu des « Issues » et des *pull request* est assez différent, procédons à un prétraitement mineur pour nous permettre de les distinguer. ## Nettoyer les données -L'extrait ci-dessus de la documentation de GitHub nous indique que la colonne "pull_request" peut être utilisée pour différencier les problèmes et les demandes d'extraction. Regardons un échantillon aléatoire pour voir quelle est la différence. Comme nous l'avons fait dans [section 3](/course/chapter5/3), nous allons enchaîner `Dataset.shuffle()` et `Dataset.select()` pour créer un échantillon aléatoire, puis compresser `html_url` et ` pull_request` afin que nous puissions comparer les différentes URL : +L'extrait ci-dessus de la documentation de GitHub nous indique que la colonne `pull_request` peut être utilisée pour différencier les *issues* et les *pull requests*. Regardons un échantillon aléatoire pour voir quelle est la différence. Comme nous l'avons fait dans [section 3](/course/fr/chapter5/3), nous allons enchaîner `Dataset.shuffle()` et `Dataset.select()` pour créer un échantillon aléatoire, puis compresser `html_url` et ` pull_request` afin que nous puissions comparer les différentes URL : ```py sample = issues_dataset.shuffle(seed=666).select(range(3)) @@ -229,7 +229,7 @@ for url, pr in zip(sample["html_url"], sample["pull_request"]): >> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/783', 'html_url': 'https://github.com/huggingface/datasets/pull/783', 'diff_url': 'https://github.com/huggingface/datasets/pull/783.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/783.patch'} ``` -Ici, nous pouvons voir que chaque demande d'extraction est associée à diverses URL, tandis que les problèmes ordinaires ont une entrée "Aucun". Nous pouvons utiliser cette distinction pour créer une nouvelle colonne `is_pull_request` qui vérifie si le champ `pull_request` est `None` ou non : +Ici, nous pouvons voir que chaque *pull request* est associée à diverses URL, tandis que les problèmes ordinaires ont une entrée `None`. Nous pouvons utiliser cette distinction pour créer une nouvelle colonne `is_pull_request` qui vérifie si le champ `pull_request` est `None` ou non : ```py issues_dataset = issues_dataset.map( @@ -239,23 +239,23 @@ issues_dataset = issues_dataset.map( -✏️ **Essayez-le !** Calculez le temps moyen nécessaire pour résoudre les problèmes dans 🤗 Datasets. Vous pouvez trouver la fonction `Dataset.filter()` utile pour filtrer les demandes d'extraction et les problèmes ouverts, et vous pouvez utiliser la fonction `Dataset.set_format()` pour convertir l'ensemble de données en un `DataFrame` afin que vous puissiez facilement manipuler les horodatages `created_at` et `closed_at`. Pour les points bonus, calculez le temps moyen nécessaire pour fermer les demandes d'extraction. +✏️ **Essayez !** Calculez le temps moyen nécessaire pour résoudre les problèmes dans 🤗 *Datasets*. Vous pouvez trouver la fonction `Dataset.filter()` utile pour filtrer les demandes d'extraction et les problèmes ouverts. Vous pouvez utiliser la fonction `Dataset.set_format()` pour convertir le jeu de données en un `DataFrame` afin que vous puissiez facilement manipuler les horodatages `created_at` et `closed_at`. Pour les points bonus, calculez le temps moyen nécessaire pour fermer les *pull_requests*. -Bien que nous puissions continuer à nettoyer davantage l'ensemble de données en supprimant ou en renommant certaines colonnes, il est généralement recommandé de conserver l'ensemble de données aussi "brut" que possible à ce stade afin qu'il puisse être facilement utilisé dans plusieurs applications. +Bien que nous puissions continuer à nettoyer davantage le jeu de données en supprimant ou en renommant certaines colonnes, il est généralement recommandé de le conserver aussi brut que possible à ce stade afin qu'il puisse être facilement utilisé dans plusieurs applications. -Avant de pousser notre ensemble de données vers le Hugging Face Hub, traitons d'une chose qui lui manque : les commentaires associés à chaque problème et pull request. Nous les ajouterons ensuite avec - vous l'avez deviné - l'API GitHub REST ! +Avant de pousser notre jeu de données vers le *Hub* d’Hugging Face, traitons une chose manquant : les commentaires associés à chaque problème et *pull requests*. Nous les ajouterons ensuite avec l'API GitHub REST ! ## Enrichir le jeu de données -Comme le montre la capture d'écran suivante, les commentaires associés à un problème ou à une demande d'extraction fournissent une riche source d'informations, en particulier si nous souhaitons créer un moteur de recherche pour répondre aux requêtes des utilisateurs sur la bibliothèque. +Comme le montre la capture d'écran suivante, les commentaires associés à un problème ou à une *pull request* fournissent une riche source d'informations, en particulier si nous souhaitons créer un moteur de recherche pour répondre aux requêtes des utilisateurs sur la bibliothèque.
-Commentaires associés à un problème concernant 🤗 Datasets. +Comments associated with an issue about 🤗 Datasets.
-L'API REST GitHub fournit un point de terminaison [`Comments`](https://docs.github.com/en/rest/reference/issues#list-issue-comments) qui renvoie tous les commentaires associés à un numéro de problème. Testons le point de terminaison pour voir ce qu'il renvoie : +L'API REST GitHub fournit un point de terminaison [`Comments`](https://docs.github.com/en/rest/reference/issues#list-issue-comments) qui renvoie tous les commentaires associés à un numéro de problème. Testons le point de terminaison pour voir ce qu'il renvoie : ```py issue_number = 2792 @@ -295,7 +295,7 @@ response.json() 'performed_via_github_app': None}] ``` -Nous pouvons voir que le commentaire est stocké dans le champ `body`, écrivons donc une fonction simple qui renvoie tous les commentaires associés à un problème en sélectionnant le contenu `body` pour chaque élément dans `response.json()` : +Nous pouvons voir que le commentaire est stocké dans le champ `body`. Ecrivons donc une fonction simple qui renvoie tous les commentaires associés à un problème en sélectionnant le contenu `body` pour chaque élément dans `response.json()` : ```py def get_comments(issue_number): @@ -312,7 +312,7 @@ get_comments(2792) ["@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?"] ``` -Cela a l'air bien, alors utilisons `Dataset.map()` pour ajouter une nouvelle colonne `commentaires` à chaque problème de notre ensemble de données : +Cela a l'air bien. Utilisons `Dataset.map()` pour ajouter une nouvelle colonne `comments` à chaque problème de notre jeu de données : ```py # Selon votre connexion internet, cela peut prendre quelques minutes... @@ -321,17 +321,17 @@ issues_with_comments_dataset = issues_dataset.map( ) ``` -La dernière étape consiste à enregistrer l'ensemble de données augmentées avec nos données brutes afin que nous puissions les pousser toutes les deux vers le Hub : +La dernière étape consiste à enregistrer le jeu de données augmentées avec nos données brutes afin que nous puissions les pousser tous les deux vers le *Hub* : ```py issues_with_comments_dataset.to_json("issues-datasets-with-comments.jsonl") ``` -## Téléchargement de l'ensemble de données sur le Hugging Face Hub +## Téléchargement du jeu de données sur le *Hub* d’Hugging Face -Maintenant que nous avons notre jeu de données augmenté, il est temps de le pousser vers le Hub afin que nous puissions le partager avec la communauté ! Pour télécharger l'ensemble de données, nous utiliserons la [bibliothèque Hub 🤗](https://github.com/huggingface/huggingface_hub), qui nous permet d'interagir avec le hub Hugging Face via une API Python. 🤗 Hub est préinstallé avec 🤗 Transformers, nous pouvons donc l'utiliser directement. Par exemple, nous pouvons utiliser la fonction `list_datasets()` pour obtenir des informations sur tous les ensembles de données publics actuellement hébergés sur le Hub : +Maintenant que nous avons notre jeu de données augmenté, il est temps de le pousser vers le *Hub* afin que nous puissions le partager avec la communauté ! Pour télécharger le jeu de données, nous utilisons la [bibliothèque 🤗 *Hub*](https://github.com/huggingface/huggingface_hub), qui nous permet d'interagir avec le *Hub* d’Hugging Face via une API Python. 🤗 *Hub* est préinstallé avec 🤗 *Transformers*, nous pouvons donc l'utiliser directement. Par exemple, nous pouvons utiliser la fonction `list_datasets()` pour obtenir des informations sur tous les ensembles de données publics actuellement hébergés sur le *Hub*: ```py from huggingface_hub import list_datasets @@ -346,9 +346,9 @@ Number of datasets on Hub: 1487 Dataset Name: acronym_identification, Tags: ['annotations_creators:expert-generated', 'language_creators:found', 'languages:en', 'licenses:mit', 'multilinguality:monolingual', 'size_categories:10K -✏️ **Essayez-le !** Utilisez votre nom d'utilisateur et votre mot de passe Hugging Face Hub pour obtenir un jeton et créer un référentiel vide appelé "github-issues". N'oubliez pas de **n'enregistrez jamais vos informations d'identification** dans Colab ou tout autre référentiel, car ces informations peuvent être exploitées par de mauvais acteurs. +✏️ **Essayez !** Utilisez votre nom d'utilisateur et votre mot de passe Hugging Face pour obtenir un jeton et créer un dépôt vide appelé `github-issues`. N'oubliez pas de **n'enregistrez jamais vos informations d'identification** dans Colab ou tout autre référentiel car ces informations peuvent être exploitées par de mauvais individus. -Ensuite, clonons le référentiel du Hub sur notre machine locale et copions-y notre fichier d'ensemble de données. 🤗 Hub fournit une classe `Repository` pratique qui encapsule de nombreuses commandes Git courantes, donc pour cloner le référentiel distant, nous devons simplement fournir l'URL et le chemin local vers lesquels nous souhaitons cloner : +Ensuite, clonons le dépôt du Hub sur notre machine locale et copions-y notre fichier jeu de données. 🤗 *Hub* fournit une classe `Repository` pratique qui encapsule de nombreuses commandes Git courantes. Donc pour cloner le dépôt distant, nous devons simplement fournir l'URL et le chemin local vers lesquels nous souhaitons cloner : ```py from huggingface_hub import Repository @@ -392,13 +392,13 @@ repo = Repository(local_dir="github-issues", clone_from=repo_url) !cp datasets-issues-with-comments.jsonl github-issues/ ``` -Par défaut, diverses extensions de fichiers (telles que *.bin*, *.gz* et *.zip*) sont suivies avec Git LFS afin que les fichiers volumineux puissent être versionnés dans le même workflow Git. Vous pouvez trouver une liste des extensions de fichiers suivis dans le fichier *.gitattributes* du référentiel. Pour inclure le format JSON Lines dans la liste, nous pouvons exécuter la commande suivante : +Par défaut, diverses extensions de fichiers (telles que *.bin*, *.gz* et *.zip*) sont suivies avec Git LFS afin que les fichiers volumineux puissent être versionnés dans le même workflow Git. Vous pouvez trouver une liste des extensions de fichiers suivis dans le fichier *.gitattributes* du référentiel. Pour inclure le format JSON Lines dans la liste, nous pouvons exécuter la commande suivante : ```py repo.lfs_track("*.jsonl") ``` -Ensuite, nous pouvons utiliser `Repository.push_to_hub()` pour pousser l'ensemble de données vers le Hub : +Ensuite, nous pouvons utiliser `Repository.push_to_hub()` pour pousser le jeu de données vers le *Hub* : ```py repo.push_to_hub() @@ -407,10 +407,10 @@ repo.push_to_hub() Si nous naviguons vers l'URL contenue dans `repo_url`, nous devrions maintenant voir que notre fichier de jeu de données a été téléchargé.
-Notre référentiel d'ensembles de données sur le Hugging Face Hub. +Our dataset repository on the Hugging Face Hub.
-À partir de là, n'importe qui peut télécharger l'ensemble de données en fournissant simplement `load_dataset()` avec l'ID du référentiel comme argument `path` : +À partir de là, n'importe qui peut télécharger le jeu de données en fournissant simplement `load_dataset()` avec l'ID du référentiel comme argument `path` : ```py remote_dataset = load_dataset("lewtun/github-issues", split="train") @@ -424,46 +424,44 @@ Dataset({ }) ``` -Cool, nous avons poussé notre jeu de données vers le Hub et il est disponible pour que d'autres puissent l'utiliser ! Il ne reste plus qu'une chose importante à faire : ajouter une _carte de jeu de données_ qui explique comment le corpus a été créé et fournit d'autres informations utiles à la communauté. +Cool, nous avons poussé notre jeu de données vers le *Hub* et il est disponible pour que d'autres puissent l'utiliser ! Il ne reste plus qu'une chose importante à faire : ajouter une _carte de jeu de données_ qui explique comment le corpus a été créé et fournit d'autres informations utiles à la communauté. -💡 Vous pouvez également télécharger un ensemble de données sur le Hugging Face Hub directement depuis le terminal en utilisant `huggingface-cli` et un peu de magie Git. Consultez le [🤗 Datasets guide](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.html#add-a-community-dataset) pour savoir comment procéder. -## Création d'une fiche de jeu de données +## Création d'une carte pour un jeu de données -Des ensembles de données bien documentés sont plus susceptibles d'être utiles aux autres (y compris à votre futur moi !), car ils fournissent le contexte permettant aux utilisateurs de décider si l'ensemble de données est pertinent pour leur tâche et d'évaluer les biais potentiels ou les risques associés à l'utilisation l'ensemble de données. +Des jeux de données bien documentés sont plus susceptibles d'être utiles aux autres (y compris à vous-même) car ils fournissent le contexte permettant aux utilisateurs de décider si le jeu de données est pertinent pour leur tâche et d'évaluer les biais potentiels ou les risques associés à l'utilisation du jeu de données. -Sur le Hugging Face Hub, ces informations sont stockées dans le fichier *README.md* de chaque référentiel d'ensembles de données. Il y a deux étapes principales que vous devez suivre avant de créer ce fichier : +Sur le *Hub*, ces informations sont stockées dans le fichier *README.md* de chaque dépôt de jeux de données. Il y a deux étapes principales que vous devez suivre avant de créer ce fichier : -1. Utilisez l'[application `datasets-tagging`](https://huggingface.co/datasets/tagging/) pour créer des balises de métadonnées au format YAML. Ces balises sont utilisées pour une variété de fonctionnalités de recherche sur le Hugging Face Hub et garantissent que votre ensemble de données peut être facilement trouvé par les membres de la communauté. Puisque nous avons créé un ensemble de données personnalisé ici, vous devrez cloner le référentiel `datasets-tagging` et exécuter l'application localement. Voici à quoi ressemble l'interface : +1. Utilisez l'[application `datasets-tagging`](https://huggingface.co/datasets/tagging/) pour créer des balises de métadonnées au format YAML. Ces balises sont utilisées pour une variété de fonctionnalités de recherche sur le *Hub* d’Hugging Face et garantissent que votre jeu de données peut être facilement trouvé par les membres de la communauté. Puisque nous avons créé un jeu de données personnalisé ici, vous devrez cloner le référentiel `datasets-tagging` et exécuter l'application localement. Voici à quoi ressemble l'interface :
-L'interface `datasets-tagging`. +The `datasets-tagging` interface.
-2. Lisez le [🤗 Datasets guide](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) sur la création de cartes d'ensemble de données informatives et utilisez-le comme modèle. +2. Lisez le [guide de 🤗 *Datasets*](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) sur la création des cartes informatives des jeux de données et utilisez-le comme modèle. + +Vous pouvez créer le fichier *README.md* directement sur le *Hub* et vous pouvez trouver un modèle de carte dans le dépot `lewtun/github-issues`. Une capture d'écran de la carte remplie est illustrée ci-dessous. -Vous pouvez créer le fichier *README.md* directement sur le Hub, et vous pouvez trouver un modèle de carte d'ensemble de données dans le référentiel d'ensemble de données `lewtun/github-issues`. Une capture d'écran de la carte de jeu de données remplie est illustrée ci-dessous.
-Une carte de jeu de données. +A dataset card.
-* Fichier README.md* pour votre ensemble de données de problèmes GitHub. - +✏️ **Essayez !** Utilisez l'application `dataset-tagging` et [le guide de 🤗 *Datasets*](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) pour compléter le fichier *README.md* de votre jeu de données de problèmes GitHub. -C'est ça! Nous avons vu dans cette section que la création d'un bon ensemble de données peut être assez complexe, mais heureusement, le télécharger et le partager avec la communauté ne l'est pas. Dans la section suivante, nous utiliserons notre nouvel ensemble de données pour créer un moteur de recherche sémantique avec 🤗 Deatasets qui peut faire correspondre les questions aux problèmes et commentaires les plus pertinents. +C’ets tout ! Nous avons vu dans cette section que la création d'un bon jeu de données peut être assez complexe, mais heureusement, le télécharger et le partager avec la communauté ne l'est pas. Dans la section suivante, nous utiliserons notre nouveau jeu de données pour créer un moteur de recherche sémantique avec 🤗 *Datasets* qui peut faire correspondre les questions aux problèmes et commentaires les plus pertinents. -✏️ **Essayez-le !** Suivez les étapes que nous avons suivies dans cette section pour créer un ensemble de données de problèmes GitHub pour votre bibliothèque open source préférée (choisissez autre chose que 🤗 Datasets, bien sûr !). Pour obtenir des points bonus, ajustez un classificateur multilabel pour prédire les balises présentes dans le champ "labels". +✏️ **Essayez !** Suivez les étapes que nous avons suivies dans cette section pour créer un jeu de données de problèmes GitHub pour votre bibliothèque open source préférée (choisissez autre chose que 🤗 *Datasets*, bien sûr !). Pour obtenir des points bonus, *finetunez* un classifieur multilabel pour prédire les balises présentes dans le champ `labels`. - - diff --git a/chapters/fr/chapter5/6.mdx b/chapters/fr/chapter5/6.mdx index fb055ccb8..68cf7373e 100644 --- a/chapters/fr/chapter5/6.mdx +++ b/chapters/fr/chapter5/6.mdx @@ -22,15 +22,15 @@ {/if} -Dans [section 5](/course/chapter5/5), nous avons créé un ensemble de données de problèmes et de commentaires GitHub à partir du référentiel 🤗 Datasets. Dans cette section, nous utiliserons ces informations pour créer un moteur de recherche qui peut nous aider à trouver des réponses à nos questions les plus urgentes sur la bibliothèque ! +Dans [section 5](/course/fr/chapter5/5), nous avons créé un jeu de données de problèmes et de commentaires GitHub à partir du dépôt 🤗 *Datasets*. Dans cette section, nous utilisons ces informations pour créer un moteur de recherche qui peut nous aider à trouver des réponses à nos questions les plus urgentes sur la bibliothèque ! -## Utilisation des représentations vectorielles continues pour la recherche sémantique +## Utilisation des enchâssements pour la recherche sémantique -Comme nous l'avons vu dans le [Chapitre 1](/course/chapter1), les modèles de langage basés sur Transformer représentent chaque jeton dans une étendue de texte sous la forme d'un _vecteur d'intégration_. Il s'avère que l'on peut "regrouper" les incorporations individuelles pour créer une représentation vectorielle pour des phrases entières, des paragraphes ou (dans certains cas) des documents. Ces intégrations peuvent ensuite être utilisées pour trouver des documents similaires dans le corpus en calculant la similarité du produit scalaire (ou une autre métrique de similarité) entre chaque intégration et en renvoyant les documents avec le plus grand chevauchement. +Comme nous l'avons vu dans le [Chapitre 1](/course/fr/chapter1), les modèles de langage basés sur les *transformers* représentent chaque *token* dans une étendue de texte sous la forme d'un _enchâssement_. Il s'avère que l'on peut regrouper les enchâssements individuels pour créer une représentation vectorielle pour des phrases entières, des paragraphes ou (dans certains cas) des documents. Ces enchâssements peuvent ensuite être utilisés pour trouver des documents similaires dans le corpus en calculant la similarité du produit scalaire (ou une autre métrique de similarité) entre chaque enchâssement et en renvoyant les documents avec le plus grand chevauchement. -Dans cette section, nous utiliserons les incorporations pour développer un moteur de recherche sémantique. Ces moteurs de recherche offrent plusieurs avantages par rapport aux approches conventionnelles basées sur la correspondance des mots-clés dans une requête avec les documents. +Dans cette section, nous utilisons les enchâssements pour développer un moteur de recherche sémantique. Ces moteurs de recherche offrent plusieurs avantages par rapport aux approches conventionnelles basées sur la correspondance des mots-clés dans une requête avec les documents.
Recherche sémantique. @@ -39,7 +39,7 @@ Dans cette section, nous utiliserons les incorporations pour développer un mote ## Chargement et préparation du jeu de données -La première chose que nous devons faire est de télécharger notre ensemble de données de problèmes GitHub, alors utilisons la bibliothèque 🤗 Hub pour résoudre l'URL où notre fichier est stocké sur le Hugging Face Hub : +La première chose que nous devons faire est de télécharger notre jeu de données de problèmes GitHub. Utilisons la bibliothèque 🤗 *Hub* pour résoudre l'URL où notre fichier est stocké sur le *Hib* d’Hugging Face : ```py from huggingface_hub import hf_hub_url @@ -51,7 +51,7 @@ data_files = hf_hub_url( ) ``` -Avec l'URL stockée dans `data_files`, nous pouvons ensuite charger le jeu de données distant en utilisant la méthode introduite dans [section 2](/course/chapter5/2) : +Avec l'URL stocké dans `data_files`, nous pouvons ensuite charger le jeu de données distant en utilisant la méthode introduite dans [section 2](/course/fr/chapter5/2) : ```py from datasets import load_dataset @@ -67,7 +67,7 @@ Dataset({ }) ``` -Ici, nous avons spécifié la division `train` par défaut dans `load_dataset()`, de sorte qu'elle renvoie un `Dataset` au lieu d'un `DatasetDict`. La première chose à faire est de filtrer les demandes d'extraction, car celles-ci ont tendance à être rarement utilisées pour répondre aux requêtes des utilisateurs et introduiront du bruit dans notre moteur de recherche. Comme cela devrait être familier maintenant, nous pouvons utiliser la fonction `Dataset.filter()` pour exclure ces lignes de notre ensemble de données. Pendant que nous y sommes, filtrons également les lignes sans commentaires, car celles-ci ne fournissent aucune réponse aux requêtes des utilisateurs : +Ici, nous avons spécifié l’échantillon `train` par défaut dans `load_dataset()`, de sorte que cela renvoie un `Dataset` au lieu d'un `DatasetDict`. La première chose à faire est de filtrer les *pull requests* car celles-ci ont tendance à être rarement utilisées pour répondre aux requêtes des utilisateurs et introduiront du bruit dans notre moteur de recherche. Comme cela devrait être familier maintenant, nous pouvons utiliser la fonction `Dataset.filter()` pour exclure ces lignes de notre jeu de données. Pendant que nous y sommes, filtrons également les lignes sans commentaires, car celles-ci ne fournissent aucune réponse aux requêtes des utilisateurs : ```py issues_dataset = issues_dataset.filter( @@ -83,7 +83,7 @@ Dataset({ }) ``` -Nous pouvons voir qu'il y a beaucoup de colonnes dans notre ensemble de données, dont la plupart n'ont pas besoin de construire notre moteur de recherche. Du point de vue de la recherche, les colonnes les plus informatives sont `title`, `body` et `comments`, tandis que `html_url` nous fournit un lien vers le problème source. Utilisons la fonction `Dataset.remove_columns()` pour supprimer le reste : +Nous pouvons voir qu'il y a beaucoup de colonnes dans notre jeu de données, dont la plupart n'ont pas besoin de construire notre moteur de recherche. Du point de vue de la recherche, les colonnes les plus informatives sont `title`, `body` et `comments`, tandis que `html_url` nous fournit un lien vers le problème source. Utilisons la fonction `Dataset.remove_columns()` pour supprimer le reste : ```py columns = issues_dataset.column_names @@ -100,14 +100,14 @@ Dataset({ }) ``` -Pour créer nos intégrations, nous ajouterons à chaque commentaire le titre et le corps du problème, car ces champs contiennent souvent des informations contextuelles utiles. Étant donné que notre colonne `comments` est actuellement une liste de commentaires pour chaque problème, nous devons "éclater" la colonne afin que chaque ligne se compose d'un tuple `(html_url, title, body, comment)`. Dans Pandas, nous pouvons le faire avec la fonction [`DataFrame.explode()`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html), qui crée une nouvelle ligne pour chaque élément dans une colonne de type liste, tout en répliquant toutes les autres valeurs de colonne. Pour voir cela en action, passons d'abord au format "DataFrame" de Pandas : +Pour créer nos enchâssements, nous ajoutons à chaque commentaire le titre et le corps du problème, car ces champs contiennent des informations contextuelles utiles. Étant donné que notre colonne `comments` est actuellement une liste de commentaires pour chaque problème, nous devons « éclater » la colonne afin que chaque ligne se compose d'un *tuple* `(html_url, title, body, comment)`. Dans Pandas, nous pouvons le faire avec la fonction [`DataFrame.explode()`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html), qui crée une nouvelle ligne pour chaque élément dans une colonne de type liste, tout en répliquant toutes les autres valeurs de colonne. Pour voir cela en action, passons d'abord au format `DataFrame` de Pandas : ```py issues_dataset.set_format("pandas") df = issues_dataset[:] ``` -Si nous inspectons la première ligne de ce `DataFrame`, nous pouvons voir qu'il y a quatre commentaires associés à ce problème : +Si nous inspectons la première ligne de ce `DataFrame`, nous pouvons voir qu'il y a quatre commentaires associés à ce problème : ```py df["comments"][0].tolist() @@ -169,7 +169,7 @@ comments_df.head(4) -Génial, nous pouvons voir que les lignes ont été répliquées, avec la colonne "commentaires" contenant les commentaires individuels ! Maintenant que nous en avons fini avec Pandas, nous pouvons rapidement revenir à un `Dataset` en chargeant le `DataFrame` en mémoire : +Génial, nous pouvons voir que les lignes ont été répliquées, avec la colonne `comments` contenant les commentaires individuels ! Maintenant que nous en avons fini avec Pandas, nous pouvons rapidement revenir à un `Dataset` en chargeant le `DataFrame` en mémoire : ```py from datasets import Dataset @@ -185,16 +185,16 @@ Dataset({ }) ``` -D'accord, cela nous a donné quelques milliers de commentaires avec lesquels travailler ! +D'accord, cela nous a donné quelques milliers de commentaires avec lesquels travailler ! -✏️ **Essayez-le !** 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; vous pourriez trouver la section ["Batch mapping"](https://huggingface.co/docs/datasets/v1.12.1/about_map_batch.html?batch-mapping#batch-mapping) de la documentation 🤗 Datasets 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/v1.12.1/about_map_batch.html?batch-mapping#batch-mapping) de la documentation 🤗 *Datasets* peut être utile pour cette tâche. -Maintenant que nous avons un commentaire par ligne, créons une nouvelle colonne `comments_length` contenant le nombre de mots par commentaire : +Maintenant que nous avons un commentaire par ligne, créons une nouvelle colonne `comments_length` contenant le nombre de mots par commentaire : ```py comments_dataset = comments_dataset.map( @@ -202,7 +202,7 @@ comments_dataset = comments_dataset.map( ) ``` -Nous pouvons utiliser cette nouvelle colonne pour filtrer les commentaires courts, qui incluent généralement des éléments tels que "cc @lewtun" ou "Merci !" qui ne sont pas pertinents pour notre moteur de recherche. Il n'y a pas de nombre précis à sélectionner pour le filtre, mais environ 15 mots semblent être un bon début : +Nous pouvons utiliser cette nouvelle colonne pour filtrer les commentaires courts incluant généralement des éléments tels que « cc @lewtun » ou « Merci ! » qui ne sont pas pertinents pour notre moteur de recherche. Il n'y a pas de nombre précis à sélectionner pour le filtre mais 15 mots semblent être un bon début : ```py comments_dataset = comments_dataset.filter(lambda x: x["comment_length"] > 15) @@ -216,7 +216,7 @@ Dataset({ }) ``` -Après avoir un peu nettoyé notre ensemble de données, concaténons le titre, la description et les commentaires du problème dans une nouvelle colonne "texte". Comme d'habitude, nous allons écrire une fonction simple que nous pouvons passer à `Dataset.map()` : +Après avoir un peu nettoyé notre jeu de données, concaténons le titre, la description et les commentaires du problème dans une nouvelle colonne `text`. Comme d'habitude, nous allons écrire une fonction simple que nous pouvons passer à `Dataset.map()` : ```py def concatenate_text(examples): @@ -232,11 +232,11 @@ def concatenate_text(examples): comments_dataset = comments_dataset.map(concatenate_text) ``` -Nous sommes enfin prêts à créer des embeddings ! Nous allons jeter un coup d'oeil. +Nous sommes enfin prêts à créer des enchâssements ! Jetons un coup d'œil. -## Création d'incorporations de texte +## Création d’enchâssements pour les textes -Nous avons vu dans [Chapitre 2](/course/chapter2) que nous pouvons obtenir des incorporations de jetons en utilisant la classe `AutoModel`. Tout ce que nous avons à faire est de choisir un point de contrôle approprié à partir duquel charger le modèle. Heureusement, il existe une bibliothèque appelée `sentence-transformers` dédiée à la création d'incorporations. Comme décrit dans la [documentation] de la bibliothèque (https://www.sbert.net/examples/applications/semantic-search/README.html#symmetric-vs-asymmetric-semantic-search), notre cas d'utilisation est un exemple de _asymmetric recherche sémantique_ car nous avons une requête courte dont nous aimerions trouver la réponse dans un document plus long, comme un commentaire sur un problème. Le [tableau de présentation des modèles] (https://www.sbert.net/docs/pretrained_models.html#model-overview) pratique de la documentation indique que le point de contrôle `multi-qa-mpnet-base-dot-v1` a le meilleures performances pour la recherche sémantique, nous l'utiliserons donc pour notre application. Nous allons également charger le tokenizer en utilisant le même point de contrôle : +Nous avons vu dans [Chapitre 2](/course/fr/chapter2) que nous pouvons obtenir des enchâssements de *tokens* en utilisant la classe `AutoModel`. Tout ce que nous avons à faire est de choisir un *checkpoint* approprié à partir duquel charger le modèle. Heureusement, il existe une bibliothèque appelée `sentence-transformers` dédiée à la création d’enchâssements. Comme décrit dans la [documentation de la bibliothèque](https://www.sbert.net/examples/applications/semantic-search/README.html#symmetric-vs-asymmetric-semantic-search), notre cas d'utilisation est un exemple de _recherche sémantique asymétrique_. En effet, nous avons une requête courte dont nous aimerions trouver la réponse dans un document plus long, par exemple un commentaire à un problème. Le [tableau de présentation des modèles](https://www.sbert.net/docs/pretrained_models.html#model-overview) de la documentation indique que le *checkpoint* `multi-qa-mpnet-base-dot-v1` a les meilleures performances pour la recherche sémantique. Utilisons donc le pour notre application. Nous allons également charger le *tokenizer* en utilisant le même *checkpoint* : {#if fw === 'pt'} @@ -248,7 +248,7 @@ tokenizer = AutoTokenizer.from_pretrained(model_ckpt) model = AutoModel.from_pretrained(model_ckpt) ``` -Pour accélérer le processus d'intégration, il est utile de placer le modèle et les entrées sur un périphérique GPU, alors faisons-le maintenant : +Pour accélérer le processus, il est utile de placer le modèle et les entrées sur un périphérique GPU, alors faisons-le maintenant : ```py import torch @@ -267,18 +267,18 @@ tokenizer = AutoTokenizer.from_pretrained(model_ckpt) model = TFAutoModel.from_pretrained(model_ckpt, from_pt=True) ``` -Notez que nous avons défini `from_pt=True` comme argument de la méthode `from_pretrained()`. C'est parce que le point de contrôle `multi-qa-mpnet-base-dot-v1` n'a que des poids PyTorch, donc définir `from_pt=True` les convertira automatiquement au format TensorFlow pour nous. Comme vous pouvez le voir, il est très simple de passer d'un framework à l'autre dans 🤗 Transformers ! +Notez que nous avons défini `from_pt=True` comme argument de la méthode `from_pretrained()`. C'est parce que le point de contrôle `multi-qa-mpnet-base-dot-v1` n'a que des poids PyTorch. Donc définir `from_pt=True` converti automatiquement au format TensorFlow pour nous. Comme vous pouvez le voir, il est très simple de passer d'un *framework* à l'autre dans 🤗 *Transformers* ! {/if} -Comme nous l'avons mentionné précédemment, nous aimerions représenter chaque entrée dans notre corpus de problèmes GitHub comme un vecteur unique, nous devons donc "regrouper" ou faire la moyenne de nos incorporations de jetons d'une manière ou d'une autre. Une approche populaire consiste à effectuer un * regroupement CLS * sur les sorties de notre modèle, où nous collectons simplement le dernier état caché pour le jeton spécial `[CLS]`. La fonction suivante fait l'affaire pour nous : +Comme nous l'avons mentionné précédemment, nous aimerions représenter chaque entrée dans notre corpus de problèmes GitHub comme un vecteur unique. Nous devons donc regrouper ou faire la moyenne de nos enchâssements de *tokens* d'une manière ou d'une autre. Une approche populaire consiste à effectuer un *regroupement CLS* sur les sorties de notre modèle, où nous collectons simplement le dernier état caché pour le *token* spécial `[CLS]`. La fonction suivante fait ça pour nous : ```py def cls_pooling(model_output): return model_output.last_hidden_state[:, 0] ``` -Ensuite, nous allons créer une fonction d'assistance qui va tokeniser une liste de documents, placer les tenseurs sur le GPU, les alimenter au modèle et enfin appliquer le regroupement CLS aux sorties : +Ensuite, nous allons créer une fonction utile qui va tokeniser une liste de documents, placer les tenseurs dans le GPU, les donner au modèle et enfin appliquer le regroupement CLS aux sorties : {#if fw === 'pt'} @@ -292,7 +292,7 @@ def get_embeddings(text_list): return cls_pooling(model_output) ``` -Nous pouvons tester le fonctionnement de la fonction en lui fournissant la première entrée de texte dans notre corpus et en inspectant la forme de sortie : +Nous pouvons tester le fonctionnement de la fonction en lui donnant la première entrée textuelle de notre corpus et en inspectant la forme de sortie : ```py embedding = get_embeddings(comments_dataset["text"][0]) @@ -303,7 +303,7 @@ embedding.shape torch.Size([1, 768]) ``` -Super, nous avons converti la première entrée de notre corpus en un vecteur à 768 dimensions ! Nous pouvons utiliser `Dataset.map()` pour appliquer notre fonction `get_embeddings()` à chaque ligne de notre corpus, créons donc une nouvelle colonne `embeddings` comme suit : +Super ! Nous avons converti la première entrée de notre corpus en un vecteur à 768 dimensions. Nous pouvons utiliser `Dataset.map()` pour appliquer notre fonction `get_embeddings()` à chaque ligne de notre corpus. Créons donc une nouvelle colonne `embeddings` comme suit : ```py embeddings_dataset = comments_dataset.map( @@ -323,7 +323,7 @@ def get_embeddings(text_list): return cls_pooling(model_output) ``` -Nous pouvons tester le fonctionnement de la fonction en lui fournissant la première entrée de texte dans notre corpus et en inspectant la forme de sortie : +Nous pouvons tester le fonctionnement de la fonction en lui donnant la première entrée textuelle de notre corpus et en inspectant la forme de sortie : ```py embedding = get_embeddings(comments_dataset["text"][0]) @@ -334,7 +334,7 @@ embedding.shape TensorShape([1, 768]) ``` -Super, nous avons converti la première entrée de notre corpus en un vecteur à 768 dimensions ! Nous pouvons utiliser `Dataset.map()` pour appliquer notre fonction `get_embeddings()` à chaque ligne de notre corpus, créons donc une nouvelle colonne `embeddings` comme suit : +Super ! Nous avons converti la première entrée de notre corpus en un vecteur à 768 dimensions. Nous pouvons utiliser `Dataset.map()` pour appliquer notre fonction `get_embeddings()` à chaque ligne de notre corpus. Créons donc une nouvelle colonne `embeddings` comme suit : ```py embeddings_dataset = comments_dataset.map( @@ -345,19 +345,19 @@ embeddings_dataset = comments_dataset.map( {/if} -Notez que nous avons converti les intégrations en tableaux NumPy -- c'est parce que 🤗 Datasets nécessite ce format lorsque nous essayons de les indexer avec FAISS, ce que nous ferons ensuite. +Notez que nous avons converti les enchâssements en tableaux NumPy. C'est parce que 🤗 *Datasets* nécessite ce format lorsque nous essayons de les indexer avec FAISS, ce que nous ferons ensuite. ## Utilisation de FAISS pour une recherche de similarité efficace -Maintenant que nous avons un ensemble de données d'incorporations, nous avons besoin d'un moyen de les rechercher. Pour ce faire, nous utiliserons une structure de données spéciale dans 🤗 Datasets appelée _FAISS index_. [FAISS](https://faiss.ai/) (abréviation de Facebook AI Similarity Search) est une bibliothèque qui fournit des algorithmes efficaces pour rechercher et regrouper rapidement des vecteurs d'intégration. +Maintenant que nous avons un jeu de données d'incorporations, nous avons besoin d'un moyen de les rechercher. Pour ce faire, nous utiliserons une structure de données spéciale dans 🤗 *Datasets* appelée _FAISS index_. [FAISS](https://faiss.ai/) (abréviation de Facebook AI Similarity Search) est une bibliothèque qui fournit des algorithmes efficaces pour rechercher et regrouper rapidement des vecteurs d'intégration. -L'idée de base derrière FAISS est de créer une structure de données spéciale appelée un _index_ qui permet de trouver quels plongements sont similaires à un plongement d'entrée. Créer un index FAISS dans 🤗 Datasets est simple -- nous utilisons la fonction `Dataset.add_faiss_index()` et spécifions quelle colonne de notre jeu de données nous aimerions indexer : +L'idée de base derrière FAISS est de créer une structure de données spéciale appelée un _index_ qui permet de trouver quels plongements sont similaires à un plongement d'entrée. Créer un index FAISS dans 🤗 *Datasets* est simple -- nous utilisons la fonction `Dataset.add_faiss_index()` et spécifions quelle colonne de notre jeu de données nous aimerions indexer : ```py embeddings_dataset.add_faiss_index(column="embeddings") ``` -Nous pouvons maintenant effectuer des requêtes sur cet index en effectuant une recherche de voisin le plus proche avec la fonction `Dataset.get_nearest_examples()`. Testons cela en intégrant d'abord une question comme suit : +Nous pouvons maintenant effectuer des requêtes sur cet index en effectuant une recherche des voisins les plus proches avec la fonction `Dataset.get_nearest_examples()`. Testons cela en enchâssant d'abord une question comme suit : {#if fw === 'pt'} @@ -385,7 +385,7 @@ question_embedding.shape {/if} -Tout comme avec les documents, nous avons maintenant un vecteur de 768 dimensions représentant la requête, que nous pouvons comparer à l'ensemble du corpus pour trouver les plongements les plus similaires : +Tout comme avec les documents, nous avons maintenant un vecteur de 768 dimensions représentant la requête. Nous pouvons le comparer à l’ensemble du corpus pour trouver les enchâssements les plus similaires : ```py scores, samples = embeddings_dataset.get_nearest_examples( @@ -393,7 +393,7 @@ scores, samples = embeddings_dataset.get_nearest_examples( ) ``` -La fonction `Dataset.get_nearest_examples()` renvoie un tuple de scores qui classent le chevauchement entre la requête et le document, et un ensemble correspondant d'échantillons (ici, les 5 meilleures correspondances). Collectons-les dans un `pandas.DataFrame` afin de pouvoir les trier facilement : +La fonction `Dataset.get_nearest_examples()` renvoie un *tuple* de scores qui classent le chevauchement entre la requête et le document, et un jeu correspondant d'échantillons (ici, les 5 meilleures correspondances). Collectons-les dans un `pandas.DataFrame` afin de pouvoir les trier facilement : ```py import pandas as pd @@ -403,7 +403,7 @@ samples_df["scores"] = scores samples_df.sort_values("scores", ascending=False, inplace=True) ``` -Nous pouvons maintenant parcourir les premières lignes pour voir dans quelle mesure notre requête correspond aux commentaires disponibles : +Nous pouvons maintenant parcourir les premières lignes pour voir dans quelle mesure notre requête correspond aux commentaires disponibles : ```py for _, row in samples_df.iterrows(): @@ -521,10 +521,10 @@ URL: https://github.com/huggingface/datasets/issues/824 """ ``` -Pas mal! Notre deuxième résultat semble correspondre à la requête. +Pas mal ! Notre deuxième résultat semble correspondre à la requête. -✏️ **Essayez-le !** Créez votre propre requête et voyez si vous pouvez trouver une réponse dans les documents récupérés. Vous devrez peut-être augmenter le paramètre `k` dans `Dataset.get_nearest_examples()` pour élargir la recherche. +✏️ **Essayez !** Créez votre propre requête et voyez si vous pouvez trouver une réponse dans les documents récupérés. Vous devrez peut-être augmenter le paramètre `k` dans `Dataset.get_nearest_examples()` pour élargir la recherche. - \ No newline at end of file + diff --git a/chapters/fr/chapter5/7.mdx b/chapters/fr/chapter5/7.mdx index a4da397e5..5321adb82 100644 --- a/chapters/fr/chapter5/7.mdx +++ b/chapters/fr/chapter5/7.mdx @@ -1,11 +1,10 @@ -# 🤗 Datasets, vérifié ! +# 🤗 *Datasets*, vérifié ! -Eh bien, ce fut toute une visite de la 🤗 Datasets -- félicitations pour être arrivé jusqu'ici ! Avec les connaissances que vous avez acquises dans ce chapitre, vous devriez être en mesure de : +Eh bien, ce fut une sacrée visite de la bibliothèque 🤗 *Datasets*. Félicitations d’être arrivé jusqu'ici ! Avec les connaissances que vous avez acquises dans ce chapitre, vous devriez être en mesure de : +- charger des jeux de données depuis n'importe où, que ce soit le *Hub* d’Hugging Face, votre ordinateur portable ou un serveur distant de votre entreprise. +- manipuler vos données en utilisant un mélange des fonctions Dataset.map() et Dataset.filter(). +- passer rapidement d'un format de données à un autre, comme Pandas et NumPy, en utilisant Dataset.set_format(). +- créer votre propre jeu de données et l’envoyer vers le *Hub*. +- enchâsser vos documents en utilisant un *transformer* et construire un moteur de recherche sémantique en utilisant FAISS. -- Chargez des ensembles de données de n'importe où, que ce soit le Hugging Face Hub, votre ordinateur portable ou un serveur distant de votre entreprise. -- Démêlez vos données en utilisant un mélange des fonctions `Dataset.map()` et `Dataset.filter()`. -- Basculez rapidement entre les formats de données comme Pandas et NumPy en utilisant `Dataset.set_format()`. -- Créez votre propre ensemble de données et transférez-le vers le Hugging Face Hub. -- Intégrez vos documents à l'aide d'un modèle Transformer et créez un moteur de recherche sémantique à l'aide de FAISS. - -Dans le [Chapitre 7](/course/chapter7), nous mettrons tout cela à profit en approfondissant les principales tâches de la PNL pour lesquelles les modèles Transformer sont parfaits. Avant de vous lancer, mettez à l'épreuve vos connaissances sur 🤗 Datasets avec un quiz rapide ! +Dans le [Chapter 7](/course/fr/chapter7), nous mettrons tout cela à profit en plongeant dans les tâches de traitement du langage neturel de base pour lesquelles les *transformers* sont parfaits. Avant cela mettez vos connaissances sur la librairie 🤗 *Datasets* à l'épreuve avec un petit quiz ! diff --git a/chapters/fr/chapter5/8.mdx b/chapters/fr/chapter5/8.mdx index 19bb1d08a..e61777100 100644 --- a/chapters/fr/chapter5/8.mdx +++ b/chapters/fr/chapter5/8.mdx @@ -2,33 +2,33 @@ # Quiz de fin de chapitre -Ce chapitre a couvert beaucoup de terrain! Ne vous inquiétez pas si vous n'avez pas saisi tous les détails ; les prochains chapitres vous aideront à comprendre comment les choses fonctionnent sous le capot. +Ce chapitre a couvert beaucoup de terrain ! Ne vous inquiétez pas si vous n'avez pas saisi tous les détails, les chapitres suivants vous aideront à comprendre comment les choses fonctionnent sous le capot. Avant de poursuivre, testons ce que vous avez appris dans ce chapitre. -### 1. La fonction `load_dataset()` dans 🤗 Datasets vous permet de charger un jeu de données depuis lequel des emplacements suivants ? +### 1. La fonction `load_dataset()` dans 🤗 *Datasets* vous permet de charger un jeu de données depuis lequel des emplacements suivants ? data_files de load_dataset() pour charger les jeux de données locaux.", + text: "Localement, par exemple depuis son ordinateur portable", + explain: "Correct ! Vous pouvez passer les chemins des fichiers locaux à l'argument data_files de load_dataset() pour charger les jeux de données locaux.", correct: true }, { - text: "Le hub du visage étreignant", - explain: "Correct! Vous pouvez charger des ensembles de données sur le Hub en fournissant l'ID de l'ensemble de données, par ex. load_dataset('emotion').", + text: "Le Hub> d’Hugging Face", + explain: "Correct ! Vous pouvez charger des jeux de données sur le Hub> en fournissant l'ID du jeu de données. Par exemple : load_dataset('emotion').", correct: true }, { text: "Un serveur distant", - explain: "Correct! Vous pouvez passer des URL à l'argument data_files de load_dataset() pour charger des fichiers distants.", + explain: "Correct ! Vous pouvez passer des URLs à l'argument data_files de load_dataset() pour charger des fichiers distants.", correct: true }, ]} /> -### 2. Supposons que vous chargiez l'une des tâches GLUE comme suit : +### 2. Supposons que vous chargiez l'une des tâches du jeu de données GLUE comme suit : ```py from datasets import load_dataset @@ -36,89 +36,89 @@ from datasets import load_dataset dataset = load_dataset("glue", "mrpc", split="train") ``` -Laquelle des commandes suivantes produira un échantillon aléatoire de 50 éléments à partir de `dataset` ? +Laquelle des commandes suivantes produira un échantillon aléatoire de 50 éléments à partir de `dataset` ? dataset.sample(50)", - explain: "Ceci est incorrect -- il n'y a pas de méthode Dataset.sample()." + explain: "Ceci est incorrect, il n'y a pas de méthode Dataset.sample()." }, { text: "dataset.shuffle().select(range(50))", - explain: "Correct! Comme vous l'avez vu dans ce chapitre, vous mélangez d'abord l'ensemble de données, puis sélectionnez les échantillons à partir de celui-ci.", + explain: "Correct ! Comme vous l'avez vu dans ce chapitre, vous mélangez d'abord le jeu de données puis sélectionnez les échantillons à partir de celui-ci.", correct: true }, { text: "dataset.select(range(50)).shuffle()", - explain: "Ceci est incorrect - bien que le code s'exécute, il ne mélange que les 50 premiers éléments de l'ensemble de données." + explain: "Ceci est incorrect. Bien que le code s'exécute, il ne mélange que les 50 premiers éléments du jeu de données." } ]} /> -### 3. Supposons que vous disposiez d'un ensemble de données sur les animaux domestiques appelé "pets_dataset", qui comporte une colonne "name" indiquant le nom de chaque animal. Parmi les approches suivantes, laquelle vous permettrait de filtrer l'ensemble de données pour tous les animaux dont le nom commence par la lettre "L" ? +### 3. Supposons que vous disposiez d'un jeu de données sur les animaux domestiques appelé `pets_dataset` qui comporte une colonne `name` indiquant le nom de chaque animal. Parmi les approches suivantes, laquelle vous permettrait de filtrer le jeu de données pour tous les animaux dont le nom commence par la lettre « L » ? pets_dataset.filter(lambda x : x['name'].startswith('L'))", - explain: "Correct! L'utilisation d'une fonction Python lambda pour ces filtres rapides est une excellente idée. Pouvez-vous penser à une autre solution?", + explain: "Correct ! L'utilisation d'une fonction Python lambda pour ces filtres rapides est une excellente idée. Pouvez-vous penser à une autre solution ?", correct: true }, { text: "pets_dataset.filter(lambda x['name'].startswith('L'))", - explain: "Ceci est incorrect -- une fonction lambda prend la forme générale lambda *arguments* : *expression*, vous devez donc fournir des arguments dans ce cas." + explain: "Ceci est incorrect. Une fonction lambda prend la forme générale lambda *arguments* : *expression*, vous devez donc fournir des arguments dans ce cas." }, { - text: "Créez une fonction comme def filter_names(x): return x['name'].startswith('L') et exécutez pets_dataset.filter(filter_names).", - explain: "Correct! Tout comme avec Dataset.map(), vous pouvez passer des fonctions explicites à Dataset.filter(). Ceci est utile lorsque vous avez une logique complexe qui ne convient pas à une fonction lambda courte. Parmi les autres solutions, laquelle fonctionnerait ?", + text: "Créer une fonction comme def filter_names(x): return x['name'].startswith('L') et exécuter pets_dataset.filter(filter_names).", + explain: "Correct ! Tout comme avec Dataset.map(), vous pouvez passer des fonctions explicites à Dataset.filter(). Ceci est utile lorsque vous avez une logique complexe qui ne convient pas à une fonction lambda courte. Parmi les autres solutions, laquelle fonctionnerait ?", correct: true } ]} /> -### 4. Qu'est-ce que la cartographie mémoire ? +### 4. Qu'est-ce que le *memory mapping* ? mapping entre la RAM CPU et GPU", + explain: "Ce n'est pas ça, réessayez !", }, { - text: "Un mappage entre la RAM et le stockage du système de fichiers", - explain: "Correct! 🤗 Datasets traite chaque ensemble de données comme un fichier mappé en mémoire. Cela permet à la bibliothèque d'accéder et d'opérer sur des éléments de l'ensemble de données sans avoir à le charger complètement en mémoire.", + text: "Un mappaging entre la RAM et le stockage du système de fichiers", + explain: "Correct ! 🤗 Datasets traite chaque jeu de données comme un fichier mappé en mémoire. Cela permet à la bibliothèque d'accéder et d'opérer sur des éléments du jeu de données sans avoir à le charger complètement en mémoire.", correct: true }, { - text: "Un mappage entre deux fichiers dans le cache 🤗 Datasets", - explain: "Ce n'est pas correct - réessayez !" + text: "Un mappaging entre deux fichiers dans le cache 🤗 Datasets", + explain: "Ce n'est pas correct, réessayez !" } ]} /> -### 5. Parmi les éléments suivants, lesquels sont les principaux avantages du mappage mémoire ? +### 5. Parmi les éléments suivants, lesquels sont les principaux avantages du *memory mapping* ? Datasets d'être extrêmement rapide. Ce n'est cependant pas le seul avantage.", correct: true }, { text: "Les applications peuvent accéder à des segments de données dans un fichier extrêmement volumineux sans avoir à lire tout le fichier dans la RAM au préalable.", - explain: "Correct! Cela permet à 🤗 Datasets de charger des ensembles de données de plusieurs gigaoctets sur votre ordinateur portable sans faire exploser votre CPU. Quel autre avantage le mappage mémoire offre-t-il ?", + explain: "Correct ! Cela permet à 🤗 Datasets de charger des jeux de données de plusieurs Go sur votre ordinateur portable sans faire exploser votre CPU. Quel autre avantage cette technique offre-t-elle ?", correct: true }, { - text: "Il consomme moins d'énergie, donc votre batterie dure plus longtemps.", - explain: "Ce n'est pas correct - réessayez !" + text: "Cela consomme moins d'énergie, donc votre batterie dure plus longtemps.", + explain: "Ce n'est pas correct, réessayez !" } ]} /> -### 6. Pourquoi le code suivant échoue-t-il ? +### 6. Pourquoi le code suivant échoue-t-il ? ```py from datasets import load_dataset @@ -130,38 +130,38 @@ dataset[0] IterableDataset.", - explain: "Correct! Un IterableDataset est un générateur, pas un conteneur, vous devez donc accéder à ses éléments en utilisant next(iter(dataset)).", + explain: "Correct! Un IterableDataset est un générateur, pas un conteneur. Vous devez donc accéder à ses éléments en utilisant next(iter(dataset)).", correct: true }, { - text: "L'ensemble de données allocine n'a pas de division train.", - explain: "Ceci est incorrect - consultez la [fiche de l'ensemble de données allocine](https://huggingface.co/datasets/allocine) sur le Hub pour voir quelles divisions elle contient." + text: "Le jeu de données allocine n'a pas d’échantillon train.", + explain: "Ceci est incorrect. Consultez la [fiche d’ allocine](https://huggingface.co/datasets/allocine) sur le Hub pour voir quels échantillons il contient." } ]} /> -### 7. Parmi les avantages suivants, lesquels sont les principaux avantages de la création d'une fiche d'ensemble de données ? +### 7. Parmi les avantages suivants, lesquels sont les principaux pour la création d'une fiche pour les jeux de données ? -### 9. Pour la recherche sémantique asymétrique, vous avez généralement : +### 9. Pour la recherche sémantique asymétrique, vous avez généralement : -### 10. Puis-je utiliser 🤗 Datasets pour charger des données à utiliser dans d'autres domaines, comme le traitement de la parole ? +### 10. Puis-je utiliser 🤗 *Datasets* pour charger des données à utiliser dans d'autres domaines, comme le traitement de la parole ? MNIST dataset sur le Hub pour un exemple de vision par ordinateur." + explain: "Ceci est incorrect. 🤗 Datasets prend actuellement en charge les données tabulaires, l'audio et la vision par ordinateur. Consultez le jeu de donnéesMNIST sur le Hub pour un exemple de vision par ordinateur." }, { text: "Oui", - explain: "Correct! Découvrez les développements passionnants avec la parole et la vision dans la bibliothèque 🤗 Transformers pour voir comment 🤗 Datasets est utilisé dans ces domaines.", + explain: "Correct ! Découvrez les développements passionnants concernant la parole et la vision dans la bibliothèque 🤗 Transformers pour voir comment 🤗 Datasets est utilisé dans ces domaines.", correct : true }, ]} diff --git a/chapters/hi/_toctree.yml b/chapters/hi/_toctree.yml new file mode 100644 index 000000000..86dd2c5bd --- /dev/null +++ b/chapters/hi/_toctree.yml @@ -0,0 +1,9 @@ +- title: 0. स्थापना + sections: + - local: chapter0/1 + title: परिचय + +- title: 1. ट्रांसफार्मर मॉडल + sections: + - local: chapter1/1 + title: परिचय diff --git a/chapters/hi/chapter0/1.mdx b/chapters/hi/chapter0/1.mdx new file mode 100644 index 000000000..7fef3c4bf --- /dev/null +++ b/chapters/hi/chapter0/1.mdx @@ -0,0 +1,110 @@ +# परिचय + +हगिंग फेस में आपका स्वागत है! यह परिचय कार्य वातावरण स्थापित करने में आपका मार्गदर्शन करेगा। यदि आप अभी पाठ्यक्रम शुरू कर रहे हैं, तो हम अनुशंसा करते हैं कि आप पहले [अध्याय 1](course/chapter1) पर एक नज़र डालें, फिर वापस आएं और अपना वातावरण सेट करें ताकि आप कोड को स्वयं आज़मा सकें। + +इस पाठ्यक्रम में हम जिन सभी पुस्तकालयों का उपयोग करेंगे, वे पायथन पैकेज के रूप में उपलब्ध हैं, इसलिए यहां हम आपको दिखाएंगे कि पायथन वातावरण कैसे स्थापित करें और विशिष्ट पुस्तकालयों को स्थापित करें जिनकी आपको आवश्यकता होगी। + +हम आपके कार्य परिवेश को स्थापित करने के दो तरीकों को कवर करेंगे, एक Colab नोटबुक या एक पायथन आभासी वातावरण का उपयोग करके। बेझिझक वह चुनें जो आपके साथ सबसे अधिक प्रतिध्वनित हो। शुरुआती लोगों के लिए, हम दृढ़ता से अनुशंसा करते हैं कि आप Colab नोटबुक का उपयोग करके शुरुआत करें। + +ध्यान दें कि हम विंडोज सिस्टम को कवर नहीं करेंगे। यदि आप Windows पर चल रहे हैं, तो हम अनुशंसा करते हैं कि Colab नोटबुक का उपयोग करने के साथ-साथ अनुसरण करें। यदि आप Linux वितरण या macOS का उपयोग कर रहे हैं, तो आप यहाँ वर्णित किसी भी दृष्टिकोण का उपयोग कर सकते हैं। + +अधिकांश पाठ्यक्रम आपके हगिंग फेस खाते पर निर्भर करता है। हम अभी एक बनाने की सलाह देते हैं: [एक खाता बनाएँ](https://huggingface.co/join)। + +## Google Colab नोटबुक का उपयोग करना + +Colab नोटबुक का उपयोग करना सबसे आसान संभव सेटअप है; अपने ब्राउज़र में एक नोटबुक बूट करें और सीधे कोडिंग पर जाएं! + +यदि आप Colab से परिचित नहीं हैं, तो हम अनुशंसा करते हैं कि आप [परिचय](https://colab.research.google.com/notebooks/intro.ipynb) का पालन करके शुरुआत करें। Colab आपको GPU या TPU जैसे कुछ त्वरित हार्डवेयर का उपयोग करने की अनुमति देता है, और यह छोटे कार्यभार के लिए मुफ़्त है। + +एक बार जब आप Colab में घूमने में सहज हो जाएं, तो एक नई नोटबुक बनाएं और स्थापना के साथ आरंभ करें: +
+ एक खाली Colab नोटबुक +
+ +अगला चरण उन पुस्तकालयों को स्थापित करना है जिनका हम इस पाठ्यक्रम में उपयोग करेंगे। हम स्थापना के लिए `pip` का उपयोग करेंगे, जो कि पायथन के लिए पैकेज मैनेजर है। नोटबुक्स में, आप `!` वर्ण से पहले सिस्टम कमांड चला सकते हैं, इसलिए आप ट्रान्सफ़ॉर्मर लाइब्रेरी को निम्नानुसार स्थापित कर सकते हैं: +अगला चरण उन पुस्तकालयों को स्थापित करना है जिनका हम इस पाठ्यक्रम में उपयोग करेंगे। हम स्थापना के लिए `pip` का उपयोग करेंगे, जो कि पायथन के लिए पैकेज मैनेजर है। नोटबुक्स में, आप `!` वर्ण से पहले सिस्टम कमांड चला सकते हैं, इसलिए आप :hugs: ट्रान्सफ़ॉर्मर लाइब्रेरी को निम्नानुसार स्थापित कर सकते हैं: + +``` +!pip install transformers +``` + +आप यह सुनिश्चित कर सकते हैं कि पैकेज आपके पायथन रनटाइम के भीतर आयात करके सही ढंग से स्थापित किया गया है: + +``` +import transformers +``` + +
+ उपरोक्त दो आदेशों का परिणाम दिखाने वाला एक GIF: स्थापना और आयात +
+ +यह :hugs: ट्रांसफॉर्मर का एक बहुत हल्का संस्करण स्थापित करता है। विशेष रूप से, कोई विशिष्ट मशीन लर्निंग फ्रेमवर्क (जैसे PyTorch या TensorFlow) स्थापित नहीं हैं। चूंकि हम पुस्तकालय की कई अलग-अलग विशेषताओं का उपयोग करेंगे, हम विकास संस्करण को स्थापित करने की सलाह देते हैं, जो किसी भी कल्पनाशील उपयोग के मामले के लिए सभी आवश्यक निर्भरताओं के साथ आता है: + +``` +!pip install transformers[sentencepiece] +``` + +इसमें थोड़ा समय लगेगा, लेकिन फिर आप बाकी पाठ्यक्रम के लिए तैयार हो जाएंगे। + +## पायथन आभासी वातावरण का उपयोग करना + +यदि आप एक पायथन आभासी वातावरण का उपयोग करना पसंद करते हैं, तो पहला कदम आपके सिस्टम पर पायथन को स्थापित करना है। हम आरंभ करने के लिए [इस गाइड](https://realpython.com/installing-python/) का पालन करने की सलाह देते हैं। + +एक बार जब आप पायथन स्थापित कर लेते हैं, तो आपको अपने टर्मिनल में पायथन आदेश चलाने में सक्षम होना चाहिए। अगले चरण पर आगे बढ़ने से पहले यह सुनिश्चित करने के लिए कि यह सही ढंग से स्थापित है, आप निम्न आदेश चलाकर प्रारंभ कर सकते हैं: `python --version`. यह आपके सिस्टम पर अब उपलब्ध पायथन संस्करण को प्रिंट करना चाहिए। + +अपने टर्मिनल में पायथन आदेश चलाते समय, जैसे `python --version` आदेश को चलाने वाले प्रोग्राम को अपने सिस्टम में "main" पायथन के रूप में सोचना चाहिए। हम अनुशंसा करते हैं कि इस मुख्य स्थापना को किसी भी पैकेज से मुक्त रखें, और इसका उपयोग प्रत्येक एप्लिकेशन के लिए अलग वातावरण बनाने के लिए करें, जिस पर आप काम कर रहे हैं - इस तरह, प्रत्येक एप्लिकेशन की अपनी निर्भरताएं और पैकेज होंगे, और आपको अन्य एप्लिकेशन के साथ संभावित संगतता समस्याओं के बारे में चिंता करने की आवश्यकता नहीं होगी। + +पायथन में यह [आभासी वातावरण](https://docs.python.org/3/tutorial/venv.html) के साथ किया जाता है, जो स्व-निहित निर्देशिका ट्री हैं जिनमें से प्रत्येक में एक विशेष पायथन संस्करण के साथ एक पायथन स्थापना होती है, जिसमें सभी पैकेजों के साथ एप्लिकेशन की आवश्यकता होती है। इस तरह के आभासी वातावरण का निर्माण कई अलग-अलग उपकरणों के साथ किया जा सकता है, लेकिन हम उस उद्देश्य के लिए आधिकारिक पायथन पैकेज का उपयोग करेंगे, जिसे कहा जाता है [`venv`](https://docs.python.org/3/library/venv.html#module-venv)। + +सबसे पहले, एक निर्देशिका बनाएं जिसमें आप अपने आवेदन में रहना चाहते हैं - उदाहरण के लिए, आप अपनी होम निर्देशिका के मूल में *transformers-course* नामक एक नई निर्देशिका बनाना चाहेंगे: + +``` +mkdir ~/transformers-course +cd ~/transformers-course +``` + +इस निर्देशिका के अंदर, पायथन `venv` मॉड्यूल का उपयोग करके एक आभासी वातावरण बनाएं: + +``` +python3 -m venv .env +``` + +अब आपके पास आपके अन्यथा खाली फ़ोल्डर में *.env* नामक एक निर्देशिका होनी चाहिए: + +``` +ls -a +``` + +```out +. .. .env +``` + +आप 'activate' और 'deactivate' स्क्रिप्ट के साथ अपने आभासी वातावरण में और बाहर कूद सकते हैं: + +``` +# Activate the virtual environment +source .env/bin/activate + +# Deactivate the virtual environment +source .env/bin/deactivate +``` + +आप यह सुनिश्चित कर सकते हैं कि `which python` आदेश चलाकर कौन सा पर्यावरण सक्रिय है: यदि यह आभासी वातावरण की ओर इशारा करता है, तो आपने इसे सफलतापूर्वक सक्रिय कर दिया है! + +``` +which python +``` + +```out +/home//transformers-course/.env/bin/python +``` + +## निर्भरता स्थापित करना + +Google Colab इंस्टेंस का उपयोग करने पर पिछले अनुभाग की तरह, अब आपको जारी रखने के लिए आवश्यक पैकेजों को स्थापित करने की आवश्यकता होगी। फिर से, आप `pip` पैकेज मैनेजर का उपयोग करके :hugs: ट्रांसफॉर्मर के विकास संस्करण को स्थापित कर सकते हैं: + +``` +pip install "transformers[sentencepiece]" +``` + +अब आप पूरी तरह से तैयार हैं! \ No newline at end of file diff --git a/chapters/hi/chapter1/1.mdx b/chapters/hi/chapter1/1.mdx new file mode 100644 index 000000000..3107062cc --- /dev/null +++ b/chapters/hi/chapter1/1.mdx @@ -0,0 +1,51 @@ +# परिचय + +## :hugs: पाठ्यक्रम में आपका स्वागत है! + + + +यह पाठ्यक्रम आपको [Hugging Face](https://huggingface.co) पारिस्थितिकी तंत्र - [:hugs: ट्रान्सफ़ॉर्मर](https://github.com/huggingface/transformers), [:hugs: डेटासेट](https://github.com/huggingface/datasets), [:hugs: टोकनीज़र](https://github.com/huggingface/tokenizers), तथा [:hugs: एक्सेलेरेट](https://github.com/huggingface/accelerate) - इसके साथ ही [हगिंग फेस हब](https://huggingface.co/models) पुस्तकालयों का उपयोग करके प्राकृतिक भाषा प्रसंस्करण (एनएलपी) के बारे में सिखाएगा। यह पूरी तरह से मुफ़्त है और विज्ञापनों के बिना है। + +## क्या उम्मीद करें? + +यहां पाठ्यक्रम का संक्षिप्त विवरण दिया गया है। + +
+ Brief overview of the chapters of the course. + +
+ +- अध्याय 1 से 4 :hugs: ट्रान्सफ़ॉर्मर पुस्तकालय की मुख्य अवधारणाओं का परिचय प्रदान करते हैं। पाठ्यक्रम के इस भाग के अंत तक, आप इस बात से परिचित होंगे कि ट्रांसफार्मर मॉडल कैसे काम करते हैं और [हगिंग फेस हब](https://huggingface.co/models) से मॉडल का उपयोग करना जानते हैं, इसे ठीक करें। डेटासेट पर, और हब पर अपने परिणाम साझा करें! +- अध्याय 5 से 8 क्लासिक एनएलपी कार्यों में गोता लगाने से पहले :hugs: डेटासेट और :hugs: टोकनाइज़र की मूल बातें सिखाते हैं। इस भाग के अंत तक, आप सबसे आम एनएलपी समस्याओं से स्वयं निपटने में सक्षम होंगे। +- अध्याय 9 से 12 एनएलपी से आगे जाते हैं और यह पता लगाते हैं कि भाषा प्रसंस्करण और कंप्यूटर दृष्टि में कार्यों से निपटने के लिए ट्रांसफार्मर मॉडल का उपयोग कैसे किया जा सकता है। साथ ही, आप सीखेंगे कि अपने मॉडलों के डेमो कैसे बनाएं और साझा करें, और उन्हें उत्पादन परिवेशों के लिए अनुकूलित करें। इस भाग के अंत तक, आप (लगभग) किसी भी मशीन सीखने की समस्या के लिए :hugs: ट्रांसफॉर्मर लगाने के लिए तैयार होंगे! + +यह पाठ्यक्रम के लिए: + +* पायथन के अच्छे ज्ञान की आवश्यकता है +* प्रारंभिक गहन शिक्षण पाठ्यक्रम, जैसे [fast.ai के](https://www.fast.ai/) [कोडर्स के लिए प्रैक्टिकल डीप लर्निंग](https://course.fast.ai/) के बाद लेना बेहतर है। +* पूर्व [PyTorch](https://pytorch.org/) या [TensorFlow](https://www.tensorflow.org/) ज्ञान की अपेक्षा नहीं करता है, हालांकि इनमें से किसी के साथ कुछ परिचित होने से मदद मिलेगी। + +आपके द्वारा इस पाठ्यक्रम को पूरा करने के बाद, हम आपको DeepLearning.AI की [प्राकृतिक भाषा संसाधन विशेषज्ञता](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutes&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh) की जाँच करने की सलाह देते हैं। जो पारंपरिक NLP मॉडल जैसे कि Naive Bayes और LSTMs की एक विस्तृत श्रृंखला को कवर करता है, जो अच्छी तरह से जानने योग्य हैं! + +## हम कौन हैं? + +लेखक के बारे में: + +**मैथ्यू कैरिगन** हगिंग फेस में मशीन लर्निंग इंजीनियर हैं। वह डबलिन, आयरलैंड में रहता है, और पहले Parse.ly में एक एमएल इंजीनियर के रूप में काम करता था और उससे पहले ट्रिनिटी कॉलेज डबलिन में पोस्ट-डॉक्टरेट शोधकर्ता के रूप में काम करता था। वह विश्वास नहीं कर सकता कि हम मौजूदा आर्किटेक्चर को स्केल करके एजीआई तक पहुंचने जा रहे हैं, लेकिन रोबोट अमरता की परवाह किए बिना उच्च उम्मीदें हैं। + +**लिसेंड्रे डेब्यू** हगिंग फेस में एक मशीन लर्निंग इंजीनियर है और बहुत प्रारंभिक विकास चरणों के बाद से :hugs: ट्रांसफॉर्मर्स लाइब्रेरी पर काम कर रहा है। उनका उद्देश्य एक बहुत ही सरल एपीआई के साथ उपकरण विकसित करके एनएलपी को सभी के लिए सुलभ बनाना है। + +**सिल्वेन गुगर** हगिंग फेस में एक रिसर्च इंजीनियर हैं और :hugs: ट्रान्सफ़ॉर्मर्स लाइब्रेरी के मुख्य अनुरक्षकों में से एक हैं। पहले वे fast.ai में एक शोध वैज्ञानिक थे, और उन्होंने _[डीप लर्निंग फॉर कोडर्स विद फास्टाई और पायटॉर्च](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/) का सह-लेखन किया जेरेमी हॉवर्ड के साथ। उनके शोध का मुख्य फोकस तकनीकों को डिजाइन और सुधार करके गहन शिक्षण को और अधिक सुलभ बनाने पर है जो मॉडल को सीमित संसाधनों पर तेजी से प्रशिक्षित करने की अनुमति देता है। + +**मर्व नोयान** हगिंग फेस में एक डेवलपर एडवोकेट है, जो सभी के लिए मशीन लर्निंग का लोकतंत्रीकरण करने के लिए टूल विकसित करने और उनके आसपास सामग्री बनाने पर काम कर रहे है। + +**ल्यूसिले शाॅलनियर** हगिंग फेस में एक मशीन लर्निंग इंजीनियर है, जो ओपन-सोर्स टूल के उपयोग का विकास और समर्थन करता है। वह सहयोगात्मक प्रशिक्षण और बिगसाइंस जैसे प्राकृतिक भाषा प्रसंस्करण के क्षेत्र में कई शोध परियोजनाओं में भी सक्रिय रूप से शामिल हैं। + +**लुईस ट्यूनस्टाल** हगिंग फेस में एक मशीन लर्निंग इंजीनियर है, जो ओपन-सोर्स टूल विकसित करने और उन्हें व्यापक समुदाय के लिए सुलभ बनाने पर केंद्रित है। वह आगामी [ओ'रेली बुक ऑन ट्रांसफॉर्मर्स](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/) के सह-लेखक भी हैं। + +**लिंड्रो वॉन वेरा** हगिंग फेस की ओपन-सोर्स टीम में मशीन लर्निंग इंजीनियर हैं और आगामी [ओ'रेली बुक ऑन ट्रांसफॉर्मर्स](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/) के सह-लेखक भी हैं। पूरे मशीन लर्निंग स्टैक में काम करके एनएलपी परियोजनाओं को उत्पादन में लाने के लिए उनके पास कई वर्षों का औद्योगिक अनुभव है। + +क्या आप तैयार हैं? इस अध्याय में आप सीखेंगे: +* पाठ निर्माण और वर्गीकरण जैसे एनएलपी कार्यों को हल करने के लिए `pipeline()` फ़ंक्शन का उपयोग कैसे करें +* ट्रांसफार्मर आर्किटेक्चर के बारे में +* एन्कोडर, डिकोडर और एन्कोडर-डिकोडर आर्किटेक्चर के बीच अंतर कैसे करें और उपयोग करें diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index 00b2efaf4..dd1bb1a5f 100644 --- a/chapters/pt/_toctree.yml +++ b/chapters/pt/_toctree.yml @@ -2,3 +2,22 @@ sections: - local: chapter0/1 title: Introdução +- title: 2. Usando 🤗 Transformers + sections: + - local: chapter2/1 + title: Introdução + - local: chapter2/2 + title: Por dentro da função pipeline + - local: chapter2/3 + title: Modelos + - local: chapter2/4 + title: Tokenizers + - local: chapter2/5 + title: Tratando sequências múltiplas + - local: chapter2/6 + title: Colocando tudo junto + - local: chapter2/7 + title: Uso básico concluído! + - local: chapter2/8 + title: Questionário de fim de capítulo + quiz: 2 diff --git a/chapters/pt/chapter2/1.mdx b/chapters/pt/chapter2/1.mdx new file mode 100644 index 000000000..4bc8850d5 --- /dev/null +++ b/chapters/pt/chapter2/1.mdx @@ -0,0 +1,20 @@ +# Introdução + +Como você viu no [Capitulo 1](/course/pt/chapter1), normalmente modelos Transformers são muito grandes. Com milhões a dezenas de *bilhões* de parâmetros, o treinamento e o deploy destes modelos é uma tarefa complicado. Além disso, com novos modelos sendo lançados quase diariamente e cada um tendo sua própria implementação, experimentá-los a todos não é tarefa fácil. + +A biblioteca 🤗 Transformers foi criado para resolver este problema. Seu objetivo é fornecer uma API única através do qual qualquer modelo Transformer possa ser carregado, treinado e salvo. As principais características da biblioteca são: + +- **Fácil de usar**: Baixar, carregar e usar um modelo de processamento natural de linguagem (PNL) de última geração para inferência pode ser feito em apenas duas linhas de código +- **Flexibilidade**: Em sua essência, todos os modelos são uma simples classe PyTorch `nn.Module` ou TensorFlow `tf.keras.Model` e podem ser tratadas como qualquer outro modelo em seus respectivos frameworks de machine learning (ML). +- **Simplicidade**: Quase nenhuma abstração é feita em toda a biblioteca. O "Tudo em um arquivo" é um conceito principal: o "passe para frente" de um modelo é inteiramente definido em um único arquivo, de modo que o código em si seja compreensível e hackeável. + +Esta última característica torna 🤗 Transformers bem diferente de outras bibliotecas ML que modelos e/ou configurações são compartilhados entre arquivos; em vez disso, cada modelo tem suas próprias camadas. Além de tornar os modelos mais acessíveis e compreensíveis, isto permite que você experimente facilmente um modelo sem afetar outros. + +Este capítulo começará com um exemplo de ponta a ponta onde usamos um modelo e um tokenizer juntos para replicar a função `pipeline()` introduzida no [Capitulo 1](/course/pt/chapter1). A seguir, discutiremos o modelo da API: onde veremos profundamente as classes de modelo e configuração, além de mostrar como carregar um modelo e como ele processa as entradas numéricas para as previsões de saída. + +Depois veremos a API do tokenizer, que é o outro componente principal da função `pipeline()`. Os Tokenizers cuidam da primeira e da última etapa do processamento, cuidando da conversão de texto para entradas numéricas para a rede neural, e da conversão de volta ao texto quando for necessário. Por fim, mostraremos a você como lidar com o envio de várias frases através de um modelo em um batch preparado, depois olharemos tudo mais atentamente a função de alto nível `tokenizer()`. + + + +⚠️ Para beneficiar-se de todos os recursos disponíveis com o Model Hub e 🤗 Transformers, recomendamos criar uma conta. + \ No newline at end of file diff --git a/chapters/pt/chapter2/2.mdx b/chapters/pt/chapter2/2.mdx new file mode 100644 index 000000000..88c9a068e --- /dev/null +++ b/chapters/pt/chapter2/2.mdx @@ -0,0 +1,354 @@ + + +# Por dentro da função pipeline + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + +Esta é a primeira seção onde o conteúdo é ligeiramente diferente, dependendo se você usa PyTorch e TensorFlow. Para selecionar a plataforma que você prefere, basta alterar no botão no topo. + + +{#if fw === 'pt'} + +{:else} + +{/if} + +Vamos começar com um exemplo completo, dando uma olhada no que acontece dentro da função quando executamos o seguinte código no [Capítulo 1](/course/pt/chapter1): + +```python +from transformers import pipeline + +classifier = pipeline("sentiment-analysis") +classifier( + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] +) +``` + +tendo o resultado: + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}, + {'label': 'NEGATIVE', 'score': 0.9994558095932007}] +``` + +Como visto no [Capítulo 1](/course/pt/chapter1), este pipeline agrupa os três passos: o pré-processamento, passagem das entradas através do modelo, e o pós-processamento: + +
+O pipeline NLP completa: tokenização do texto, conversão para IDs, e inferência através do Transformer e pela 'cabeça' do modelo. + +
+ +Vamos rever rapidamente cada um deles. + +## Pré-processamento com o tokenizer + +Como outras redes neurais, os Transformers não podem processar texto bruto diretamente, portanto, o primeiro passo do nosso pipeline é converter as entradas de texto em números que o modelo possa fazer sentido. Para fazer isso, usamos um *tokenizer*, que será responsável por: + +- Dividir a entrada em palavras, sub-palavras ou símbolos (como pontuação) que são chamados de *tokens*. +- Mapeando cada ficha para um número inteiro +- Adicionando entradas adicionais que podem ser úteis ao modelo + +Todo esse pré-processamento precisa ser feito exatamente da mesma forma que quando o modelo foi pré-treinado, então precisamos primeiro baixar essas informações do [Model Hub](https://huggingface.co/models). Para isso, utilizamos a classe `AutoTokenizer` e seu método `from_pretrained()`. Utilizando o nome do ponto de verificação de nosso modelo, ele irá buscar automaticamente os dados associados ao tokenizer do modelo e armazená-los em cache (portanto, ele só é baixado na primeira vez que você executar o código abaixo). + +Desde que o checkpoint default do pipeline `sentiment-analysis` é `distilbert-base-uncased-finetuned-sst-2-english` (você pode ver o card do modelo [aqui](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english)), então executamos o seguinte: + +```python +from transformers import AutoTokenizer + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` + +Assim que tivermos o tokenizer, podemos passar diretamente nossas frases para ele e receberemos de volta um dicionário que está pronto para alimentar nosso modelo! A única coisa que falta fazer é converter a lista de identificações de entrada em tensores. + +Você pode usar 🤗 Transformers sem ter que se preocupar com qual estrutura ML é usada como backend; pode ser PyTorch ou TensorFlow, ou Flax para alguns modelos. Entretanto, os Transformers só aceitam *tensores* como entrada. Se esta é a primeira vez que você ouve falar de tensores, você pode pensar neles como matrizes da NumPy. Uma matriz NumPy pode ser um escalar (0D), um vetor (1D), uma matriz (2D), ou ter mais dimensões. É efetivamente um tensor; os tensores de outras estruturas ML comportam-se de forma semelhante, e geralmente são tão simples de instanciar como os arrays da NumPy. + +Para especificar o tipo de tensores que queremos recuperar (PyTorch, TensorFlow ou NumPy), utilizamos o argumento `return_tensors`: + +{#if fw === 'pt'} +```python +raw_inputs = [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", +] +inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="pt") +print(inputs) +``` +{:else} +```python +raw_inputs = [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", +] +inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="tf") +print(inputs) +``` +{/if} + +Não se preocupe ainda com o truncamento e o padding; explicaremos isso mais tarde. As principais coisas a lembrar aqui são que você pode passar uma frase ou uma lista de frases, bem como especificar o tipo de tensores que você quer recuperar (se nenhum tipo for passado, você receberá uma lista de listas como resultado). + +{#if fw === 'pt'} + +Eis como são os resultados como tensores PyTorch: + +```python out +{ + 'input_ids': tensor([ + [ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012, 102], + [ 101, 1045, 5223, 2023, 2061, 2172, 999, 102, 0, 0, 0, 0, 0, 0, 0, 0] + ]), + '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] + ]) +} +``` +{:else} + +Eis como são os resultados como tensores TensorFlow: + +```python out +{ + 'input_ids': , + 'attention_mask': +} +``` +{/if} + +A saída em si é um dicionário contendo duas chaves, `input_ids' e `attention_mask'. O `input_ids' contém duas linhas de inteiros (uma para cada frase) que são os identificadores únicos dos tokens em cada frase. Explicaremos o que é a "máscara de atenção" (attention mask) mais adiante neste capítulo. + +## Indo adianta pelo modelo + +{#if fw === 'pt'} +Podemos baixar nosso modelo pré-treinado da mesma forma que fizemos com nosso tokenizer. 🤗 Transformers fornece uma classe `AutoModel` que também tem um método `from_pretrained()`: + +```python +from transformers import AutoModel + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = AutoModel.from_pretrained(checkpoint) +``` +{:else} +Podemos baixar nosso modelo pré-treinado da mesma forma que fizemos com nosso tokenizer. 🤗 Transformers fornece uma classe "TFAutoModel" que também tem um método "from_pretrained": + + +```python +from transformers import TFAutoModel + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = TFAutoModel.from_pretrained(checkpoint) +``` +{/if} + +Neste trecho de código, fizemos o download do mesmo checkpoint que usamos anteriormente em nosso pipeline (já deveria estar em cache) e instanciamos um modelo com ele. + +Esta arquitetura contém apenas o módulo base do Transformer: dadas algumas entradas, ele produz o que chamaremos de *hidden states* (estados ocultos), também conhecidos como *features* (características). Para cada modelo de entrada, recuperaremos um vetor de alta dimensionalidade representando a **compreensão contextual dessa entrada pelo Transformer***. + +Se isto não faz sentido, não se preocupe com isso. Explicaremos tudo isso mais tarde. + +Embora estes hidden states possam ser úteis por si mesmos, eles geralmente são entradas para outra parte do modelo, conhecida como *head* (cabeça). No [Capítulo 1](/course/pt/chapter1), as diferentes tarefas poderiam ter sido realizadas com a mesma arquitetura, mas cada uma destas tarefas teria uma head diferente associada a ela. + +### Um vetor de alta dimensionalidade? + +A saída vetorial pelo módulo do Transformer é geralmente grande. Geralmente tem três dimensões: + +- **Tamanho do lote** (Batch size): O número de sequências processadas de cada vez (2 em nosso exemplo). +- **Tamanho da sequencia** (Sequence length): O comprimento da representação numérica da sequência (16 em nosso exemplo). +- **Tamanho oculto** (Hidden size): A dimensão vetorial de cada modelo de entrada. + +Diz-se que é "de alta dimensionalidade" por causa do último valor. O tamanho oculto pode ser muito grande (768 é comum para modelos menores, e em modelos maiores isto pode chegar a 3072 ou mais). + +Podemos ver isso se alimentarmos os inputs que pré-processamos para nosso modelo: + +{#if fw === 'pt'} +```python +outputs = model(**inputs) +print(outputs.last_hidden_state.shape) +``` + +```python out +torch.Size([2, 16, 768]) +``` +{:else} +```py +outputs = model(inputs) +print(outputs.last_hidden_state.shape) +``` + +```python out +(2, 16, 768) +``` +{/if} + +Observe que as saídas dos 🤗 Transformer se comportam como 'tuplas nomeadas' (namedtuple) ou dicionários. Você pode acessar os elementos por atributos (como fizemos) ou por chave (`outputs["last_hidden_state"]`), ou mesmo por índice se você souber exatamente onde o que está procurando (`outputs[0]`). + +### Cabeça do modelo (model heads): Fazendo sentido a partir dos números + +As *heads* do modelo usam o vetor de alta dimensionalidade dos hidden states como entrada e os projetam em uma dimensão diferente. Eles são geralmente compostos de uma ou algumas camadas lineares: + +
+Uma rede Transformer ao lado de sua head. + +
+ +A saída do Transformer é enviada diretamente para a *head* do modelo a ser processado. + +Neste diagrama, o modelo é representado por sua camada de embeddings (vetores) e pelas camadas subsequentes. A camada de embeddings converte cada ID de entrada na entrada tokenizada em um vetor que representa o token associado. As camadas subsequentes manipulam esses vetores usando o mecanismo de atenção para produzir a representação final das sentenças. + +Há muitas arquiteturas diferentes disponíveis no 🤗 Transformers, com cada uma projetada em torno de uma tarefa específica. Aqui está uma lista por **algumas** destas tarefas: + +- `*Model` (recuperar os hidden states) +- `*ForCausalLM` +- `*ForMaskedLM` +- `*ForMultipleChoice` +- `*ForQuestionAnswering` +- `*ForSequenceClassification` +- `*ForTokenClassification` +- e outros 🤗 + +{#if fw === 'pt'} +Para nosso exemplo, precisaremos de um modelo com uma *head* de classificação em sequencia (para poder classificar as sentenças como positivas ou negativas). Portanto, não utilizaremos a classe `AutoModel`, mas sim, a classe `AutoModelForSequenceClassification`: + +```python +from transformers import AutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +outputs = model(**inputs) +``` +{:else} +Para nosso exemplo, precisaremos de um modelo com uma *head* de classificação em sequencia (para poder classificar as sentenças como positivas ou negativas). Portanto, não utilizaremos a classe `TFAutoModel`, mas sim, a classe `TFAutoModelForSequenceClassification`: + +```python +from transformers import TFAutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +outputs = model(inputs) +``` +{/if} + +Agora se observarmos o tamanho dos nossos inputs, a dimensionalidade será muito menor: a *head* do modelo toma como entrada os vetores de alta dimensionalidade que vimos anteriormente, e os vetores de saída contendo dois valores (um por label): + +```python +print(outputs.logits.shape) +``` + +{#if fw === 'pt'} +```python out +torch.Size([2, 2]) +``` +{:else} +```python out +(2, 2) +``` +{/if} + +Como temos apenas duas sentenças e duas labels, o resultado que obtemos de nosso modelo é de tamanho 2 x 2. + +## Pós-processamento da saída + +Os valores que obtemos como resultado de nosso modelo não fazem necessariamente sentido sozinhos. Vamos dar uma olhada: + +```python +print(outputs.logits) +``` + +{#if fw === 'pt'} +```python out +tensor([[-1.5607, 1.6123], + [ 4.1692, -3.3464]], grad_fn=) +``` +{:else} +```python out + +``` +{/if} + +Nosso modelo previu `[-1.5607, 1.6123]` para a primeira frase e `[ 4.1692, -3.3464]` para a segunda. Essas não são probabilidades, mas *logits*, a pontuação bruta e não normalizada produzida pela última camada do modelo. Para serem convertidos em probabilidades, eles precisam passar por uma camada [SoftMax](https://en.wikipedia.org/wiki/Softmax_function) (todas saídas dos 🤗 Transformers produzem *logits*, já que a função de *loss* (perda) para treinamento geralmente fundirá a última função de ativação, como SoftMax, com a função de *loss* real, por exemplo a *cross entropy*): + +{#if fw === 'pt'} +```py +import torch + +predictions = torch.nn.functional.softmax(outputs.logits, dim=-1) +print(predictions) +``` +{:else} +```py +import tensorflow as tf + +predictions = tf.math.softmax(outputs.logits, axis=-1) +print(predictions) +``` +{/if} + +{#if fw === 'pt'} +```python out +tensor([[4.0195e-02, 9.5980e-01], + [9.9946e-01, 5.4418e-04]], grad_fn=) +``` +{:else} +```python out +tf.Tensor( +[[4.01951671e-02 9.59804833e-01] + [9.9945587e-01 5.4418424e-04]], shape=(2, 2), dtype=float32) +``` +{/if} + +Agora podemos ver que o modelo previu `[0.0402, 0.9598]` para a primeira frase e `[0.9995, 0.0005]` para a segunda. Estas são pontuações de probabilidade reconhecíveis. + +Para obter as etiquetas correspondentes a cada posição, podemos inspecionar o atributo `id2label` da configuração do modelo (mais sobre isso na próxima seção): + +```python +model.config.id2label +``` + +```python out +{0: 'NEGATIVE', 1: 'POSITIVE'} +``` + +Agora podemos concluir que o modelo previu o seguinte: + +- A primeira frase: NEGATIVE: 0.0402, POSITIVE: 0.9598 +- Segunda frase: NEGATIVE: 0.9995, POSITIVE: 0.0005 + +Reproduzimos com sucesso as três etapas do pipeline: o pré-processamento, passagem das entradas através do modelo, e o pós-processamento! Agora, vamos levar algum tempo para mergulhar mais fundo em cada uma dessas etapas. + + + +✏️ **Experimente!** Escolha duas (ou mais) textos próprios e passe-os através do pipeline `sentiment-analysis`. Em seguida, replique as etapas que você mesmo viu aqui e verifique se você obtém os mesmos resultados! + + diff --git a/chapters/pt/chapter2/3.mdx b/chapters/pt/chapter2/3.mdx new file mode 100644 index 000000000..634cc3a6e --- /dev/null +++ b/chapters/pt/chapter2/3.mdx @@ -0,0 +1,229 @@ + + +# Modelos + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + +{:else} + +{/if} + +{#if fw === 'pt'} +Nesta seção, vamos analisar mais de perto a criação e a utilização de um modelo. Vamos utilizar a classe `AutoModel`, que é útil quando você quer instanciar qualquer modelo a partir de um checkpoint. + +A classe `AutoModel` e todas as classes filhas são na verdade simples wrapper sobre a grande variedade de modelos disponíveis na biblioteca. É um wrapper inteligente, pois pode automaticamente "adivinhar" a arquitetura apropriada do modelo para seu checkpoint, e então instancia um modelo com esta arquitetura. + +{:else} +Nesta seção, vamos analisar mais de perto a criação e a utilização de um modelo. Vamos utilizar a classe `TFAutoModel`, que é útil quando você quer instanciar qualquer modelo a partir de um checkpoint. + +A classe `TFAutoModel` e todas as classes filhas são na verdade simples wrapper sobre a grande variedade de modelos disponíveis na biblioteca. É um wrapper inteligente, pois pode automaticamente "adivinhar" a arquitetura apropriada do modelo para seu checkpoint, e então instancia um modelo com esta arquitetura. + +{/if} + +Entretanto, se você conhece o tipo de modelo que deseja usar, pode usar diretamente a classe que define sua arquitetura. Vamos dar uma olhada em como isto funciona com um modelo BERT. + +## Criando um Transformer + +A primeira coisa que precisamos fazer para inicializar um modelo BERT é carregar um objeto de configuração: + +{#if fw === 'pt'} +```py +from transformers import BertConfig, BertModel + +# Construindo a configuração +config = BertConfig() + +# Construindo o modelo a partir da configuração +model = BertModel(config) +``` +{:else} +```py +from transformers import BertConfig, TFBertModel + +# Construindo a configuração +config = BertConfig() + +# Construindo o modelo a partir da configuração +model = TFBertModel(config) +``` +{/if} + +A configuração contém muitos atributos que são usados para construir o modelo: + +```py +print(config) +``` + +```python out +BertConfig { + [...] + "hidden_size": 768, + "intermediate_size": 3072, + "max_position_embeddings": 512, + "num_attention_heads": 12, + "num_hidden_layers": 12, + [...] +} +``` + +Embora você ainda não tenha visto o que todos esses atributos fazem, você deve reconhecer alguns deles: o atributo `hidden_size` define o tamanho do vetor `hidden_states`, e o `num_hidden_layers` define o número de camadas que o Transformer possui. + + +### Diferentes métodos de inicializar o modelo + +A criação de um modelo a partir da configuração padrão o inicializa com valores aleatórios: + +{#if fw === 'pt'} +```py +from transformers import BertConfig, BertModel + +config = BertConfig() +model = BertModel(config) + +# O modelo é inicializado aleatoriamente! +``` +{:else} +```py +from transformers import BertConfig, TFBertModel + +config = BertConfig() +model = TFBertModel(config) + +# O modelo é inicializado aleatoriamente! +``` +{/if} + +O modelo pode ser utilizado neste estado, mas produzirá saídas errôneas; ele precisa ser treinado primeiro. Poderíamos treinar o modelo a partir do zero na tarefa em mãos, mas como você viu em [Capítulo 1](/course/pt/chapter1), isto exigiria muito tempo e muitos dados, e teria um impacto ambiental não negligenciável. Para evitar esforços desnecessários e duplicados, normalmente é possível compartilhar e reutilizar modelos que já foram treinados. + +Carregar um Transformer já treinado é simples - podemos fazer isso utilizando o método `from_pretrained()`: + +{#if fw === 'pt'} +```py +from transformers import BertModel + +model = BertModel.from_pretrained("bert-base-cased") +``` + +Como você viu anteriormente, poderíamos substituir o `BertModel` pela classe equivalente ao `AutoModel`. Faremos isto de agora em diante, pois isto produz um código generalista a partir de um checkpoint; se seu código funciona para checkpoint, ele deve funcionar perfeitamente com outro. Isto se aplica mesmo que a arquitetura seja diferente, desde que o checkpoint tenha sido treinado para uma tarefa semelhante (por exemplo, uma tarefa de análise de sentimento). + +{:else} +```py +from transformers import TFBertModel + +model = TFBertModel.from_pretrained("bert-base-cased") +``` + +Como você viu anteriormente, poderíamos substituir o `TFBertModel` pela classe equivalente ao `TFAutoModel`. Faremos isto de agora em diante, pois isto produz um código generalista a partir de um checkpoint; se seu código funciona para checkpoint, ele deve funcionar perfeitamente com outro. Isto se aplica mesmo que a arquitetura seja diferente, desde que o checkpoint tenha sido treinado para uma tarefa semelhante (por exemplo, uma tarefa de análise de sentimento). + + +{/if} + +No exemplo de código acima não utilizamos `BertConfig`, e em vez disso carregamos um modelo pré-treinado através do identificador `bert-base-cased`. Este é um checkpoint do modelo que foi treinado pelos próprios autores do BERT; você pode encontrar mais detalhes sobre ele em seu [model card](https://huggingface.co/bert-base-cased). + +Este modelo agora é inicializado com todos os pesos do checkpoint. Ele pode ser usado diretamente para inferência sobre as tarefas nas quais foi treinado, e também pode ser *fine-tuned* (aperfeiçoado) em uma nova tarefa. Treinando com pesos pré-treinados e não do zero, podemos rapidamente alcançar bons resultados. + +Os pesos foram baixados e armazenados em cache (logo, para as futuras chamadas do método `from_pretrained()` não será realizado o download novamente) em sua respectiva pasta, que tem como padrão o path *~/.cache/huggingface/transformers*. Você pode personalizar sua pasta de cache definindo a variável de ambiente `HF_HOME`. + +O identificador usado para carregar o modelo pode ser o identificador de qualquer modelo no Model Hub, desde que seja compatível com a arquitetura BERT. A lista completa dos checkpoints BERT disponíveis podem ser encontrada [aqui].https://huggingface.co/models?filter=bert). + +### Métodos para salvar/armazenar o modelo + +Salvar um modelo é tão fácil quanto carregar um - utilizamos o método `save_pretrained()`, que é análogo ao método `from_pretrained()`: + +```py +model.save_pretrained("path_no_seu_computador") +``` + +Isto salva dois arquivos em seu disco: + +{#if fw === 'pt'} +``` +ls path_no_seu_computador + +config.json pytorch_model.bin +``` +{:else} +``` +ls path_no_seu_computador + +config.json tf_model.h5 +``` +{/if} + +Se você der uma olhada no arquivo *config.json*, você reconhecerá os atributos necessários para construir a arquitetura modelo. Este arquivo também contém alguns metadados, como a origem do checkpoint e a versão 🤗 Transformers que você estava usando quando salvou o checkpoint pela última vez. + +{#if fw === 'pt'} +O arquivo *pytorch_model.bin* é conhecido como o *dicionário de estado*; ele contém todos os pesos do seu modelo. Os dois arquivos andam de mãos dadas; a configuração é necessária para conhecer a arquitetura de seu modelo, enquanto os pesos do modelo são os parâmetros de seu modelo. + +{:else} +O arquivo *tf_model.h5* é conhecido como o *dicionário de estado*; ele contém todos os pesos do seu modelo. Os dois arquivos andam de mãos dadas; a configuração é necessária para conhecer a arquitetura de seu modelo, enquanto os pesos do modelo são os parâmetros de seu modelo. + +{/if} + +## Usando um modelo de Transformer para inferência + +Agora que você sabe como carregar e salvar um modelo, vamos tentar usá-lo para fazer algumas predições. Os Transformers só podem processar números - números que o tokenizer gera. Mas antes de discutirmos os tokenizers, vamos explorar quais entradas o modelo aceita. + +Os Tokenizers podem se encarregar de lançar as entradas nos tensores da estrutura apropriada, mas para ajudá-lo a entender o que está acontecendo, vamos dar uma rápida olhada no que deve ser feito antes de enviar as entradas para o modelo. + +Digamos que temos um par de sequências: + +```py +sequences = ["Hello!", "Cool.", "Nice!"] +``` + +O tokenizer os converte em índices de vocabulário que são normalmente chamados de *IDs de entrada*. Cada sequência é agora uma lista de números! A saída resultante é: + +```py no-format +encoded_sequences = [ + [101, 7592, 999, 102], + [101, 4658, 1012, 102], + [101, 3835, 999, 102], +] +``` + +Esta é uma lista de sequências codificadas: uma lista de listas. Os tensores só aceitam shapes (tamanhos) retangulares (pense em matrizes). Esta "matriz" já é de forma retangular, portanto, convertê-la em um tensor é fácil: + +{#if fw === 'pt'} +```py +import torch + +model_inputs = torch.tensor(encoded_sequences) +``` +{:else} +```py +import tensorflow as tf + +model_inputs = tf.constant(encoded_sequences) +``` +{/if} + +### Usando os tensores como entradas para o modelo + +Fazer uso dos tensores com o modelo é extremamente simples - chamamos apenas o modelo com os inputs: + +```py +output = model(model_inputs) +``` + +Embora o modelo aceite muitos argumentos diferentes, apenas os IDs de entrada são necessários. Explicaremos o que os outros argumentos fazem e quando eles são necessários mais tarde, mas primeiro precisamos olhar mais de perto os tokenizers que constroem as entradas que um Transformer pode compreender. diff --git a/chapters/pt/chapter2/4.mdx b/chapters/pt/chapter2/4.mdx new file mode 100644 index 000000000..7b2f70ff6 --- /dev/null +++ b/chapters/pt/chapter2/4.mdx @@ -0,0 +1,248 @@ + + +# Tokenizers + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + + +Os Tokenizers são um dos componentes centrais do pipeline da PNL. Eles têm um propósito: traduzir texto em dados que podem ser processados pelo modelo. Os modelos só podem processar números, portanto os tokenizers precisam converter nossas entradas de texto em dados numéricos. Nesta seção, vamos explorar exatamente o que acontece no pipeline de tokenização. + +Nas tarefas de PNL, os dados que são geralmente processados são texto bruto. Aqui está um exemplo de tal texto: + +``` +Jim Henson was a puppeteer +``` + +Entretanto, os modelos só podem processar números, portanto, precisamos encontrar uma maneira de converter o texto bruto em números. Isto é o que os tokenizers fazem, e há muitas maneiras de se fazer isso. O objetivo é encontrar a representação mais significativa - ou seja, a que faz mais sentido para o modelo - e, se possível, a menor representação. + +Vamos dar uma olhada em alguns exemplos de algoritmos de tokenização, e tentar responder algumas das perguntas que você possa ter sobre tokenização. + +## Baseado em palavras (word-based) + + + +O primeiro tipo de tokenizer que me vem à mente é _baseado em palavras_. É geralmente muito fácil de instalar e usar com apenas algumas regras, e muitas vezes produz resultados decentes. Por exemplo, na imagem abaixo, o objetivo é dividir o texto bruto em palavras e encontrar uma representação numérica para cada uma delas: + +
+ Um exemplo de tokenização baseado em palavras. + +
+ +Há diferentes maneiras de dividir o texto. Por exemplo, poderíamos utilizar o espaço em branco para simbolizar o texto em palavras, usando a função `split()` do Python: + +```py +tokenized_text = "Jim Henson was a puppeteer".split() +print(tokenized_text) +``` + +```python out +['Jim', 'Henson', 'was', 'a', 'puppeteer'] +``` + +Há também variações de tokenizers de palavras que têm regras extras para pontuação. Com este tipo de tokenizer, podemos terminar com alguns "vocabulários" bem grandes, onde um vocabulário é definido pelo número total de tokens independentes que tem no texto de exemplo. + +A cada palavra é atribuída uma identificação, começando em 0 e indo até o tamanho do vocabulário. O modelo utiliza estas identificações para identificar cada palavra. + +Se quisermos cobrir completamente um idioma com um tokenizer baseado em palavras, precisaremos ter um identificador para cada palavra no idioma, o que gerará uma enorme quantidade de tokens. Por exemplo, existem mais de 500.000 palavras no idioma inglês, portanto, para construir um mapa a partir de cada palavra para um ID de entrada, precisaríamos manter um registro desse grande número de IDs. Além disso, palavras como "dog" são representadas de forma diferente de palavras como "dogs", e o modelo inicialmente não terá como saber que "dog" e "dogs" são semelhantes: ele identificará as duas palavras como não relacionadas. O mesmo se aplica a outras palavras semelhantes, como "run" e "running", que o modelo não verá inicialmente como sendo semelhantes. + + +Finalmente, precisamos de token personalizada para representar palavras que não estão em nosso vocabulário. Isto é conhecido como o símbolo "unknown" (desconhecido), frequentemente representado como "[UNK]" ou "<unk>". Geralmente é um mau sinal se você vê que o tokenizer está produzindo muitos desses tokens, pois não foi capaz de recuperar uma representação sensata de uma palavra e você está perdendo informações ao longo do caminho. O objetivo ao elaborar o vocabulário é fazê-lo de tal forma que o tokenizer transforme o menor número possível de palavras no token desconhecido. + +Uma maneira de reduzir a quantidade de tokens desconhecidas é ir um nível mais fundo, usando um tokenizer _baseado em caracteres_. + +## Baseado em caracteres (Character-based) + + + +Os tokenizers baseados em caracteres dividem o texto em caracteres, ao invés de palavras. Isto tem dois benefícios principais: + +- O vocabulário será muito menor; +- Há muito menos tokes fora de vocabulário (desconhecidas), uma vez que cada palavra pode ser construída a partir de personagens. + +Mas também aqui surgem algumas questões sobre os espaços e à pontuação: + +
+ Um exemplo de tokenização baseado em caracteres. + +
+ +Esta abordagem também não é perfeita. Como a representação agora é baseada em caracteres e não em palavras, pode-se argumentar que, intuitivamente, ela é menos significativa: cada caractere não significa muito por si só, ao contrario do caso das palavras. No entanto, isto novamente difere de acordo com o idioma; em chinês, por exemplo, cada caractere traz mais informações do que um caractere em um idioma latino. + +Outra coisa a considerar é que acabaremos com uma quantidade muito grande de tokens a serem processadas por nosso modelo: enquanto uma palavra seria apenas um único token com um tokenizer baseado em palavras, ela pode facilmente se transformar em 10 ou mais tokens quando convertida em caracteres. + + +Para obter o melhor dos dois mundos, podemos usar uma terceira técnica que combina as duas abordagens: *Tokenização por sub-palavras*. + +## Tokenização por sub-palavras (Subword tokenization) + + + +Algoritmos de tokenização de sub-palavras baseiam-se no princípio de que palavras frequentemente usadas não devem ser divididas em sub-palavras menores, mas palavras raras devem ser decompostas em sub-palavras significativas. + +Por exemplo, "irritantemente" poderia ser considerado uma palavra rara e poderia ser decomposto em "irritante" e "mente". É provável que ambas apareçam mais frequentemente como sub-palavras isoladas, enquanto ao mesmo tempo o significado de "irritantemente" é mantido pelo significado composto de "irritante" e "mente". + +Aqui está um exemplo que mostra como um algoritmo de tokenização de uma sub-palavra indicaria a sequência "Let's do tokenization! + +
+ Exemplo de algoritmo de tokenização por sub-palavras. + +
+ +Estas sub-palavras acabam fornecendo muito significado semântico: por exemplo, no exemplo acima "tokenization" foi dividido em "token" e "ization", dois tokens que têm um significado semântico enquanto são eficientes em termos de espaço (apenas dois tokens são necessários para representar uma palavra longa). Isto nos permite ter uma cobertura relativamente boa com pequenos vocabulários, e perto de nenhum token desconhecido. + + +Esta abordagem é especialmente útil em idiomas aglutinativos como o turco, onde é possível formar palavras (quase) arbitrariamente longas e complexas, encadeando sub-palavras. + +### E outros! + +Sem surpresas, há muito mais técnicas por aí. Para citar algumas: + +- Byte-level BPE, utilizada no GPT-2 +- WordPiece, utilizada em BERT +- SentencePiece ou Unigram, como as utilizadas em vários modelos multilíngue + +Agora você deve ter conhecimento suficiente de como funcionam os tokenizers para começar a utilizar a API. + +## Carregando e salvando + +Carregando e salvando tokenizers é tão simples quanto com os modelos. Na verdade, ele se baseia nos mesmos dois métodos: `from_pretrained()` e `save_pretrained()`. Estes métodos irão carregar ou salvar o algoritmo utilizado pelo tokenizer (um pouco como a *arquitetura* do modelo), bem como seu vocabulário (um pouco como os *pesos* do modelo). + +O carregamento do tokenizer BERT treinado com o mesmo checkpoint do BERT é feito da mesma forma que o carregamento do modelo, exceto que utilizamos a classe `BertTokenizer`: + +```py +from transformers import BertTokenizer + +tokenizer = BertTokenizer.from_pretrained("bert-base-cased") +``` + +{#if fw === 'pt'} +Similar ao `AutoModel`, a classe `AutoTokenizer` ira carregar a classe tokenizer apropriada na biblioteca com base no nome do checkpoint, e pode ser utilizada diretamente com qualquer checkpoint: + +{:else} +Similar ao `TFAutoModel`, a classe `AutoTokenizer` ira carregar a classe tokenizer apropriada na biblioteca com base no nome do checkpoint, e pode ser utilizada diretamente com qualquer checkpoint: + +{/if} + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +``` + +Agora podemos usar o tokenizer, como mostrado na seção anterior: + +```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]} +``` + +Salvar um tokenizer é idêntico a salvar um modelo: + +```py +tokenizer.save_pretrained("directory_on_my_computer") +``` + + +Falaremos mais sobre `token_type_ids' no [Capítulo 3](/course/pt/chapter3), e explicaremos a `attention_mask' um pouco mais tarde. Primeiro, vamos ver como os `input_ids` são gerados. Para fazer isso, precisaremos olhar os métodos intermediários do tokenizer. + + +## Encoding + + + +Traduzir texto para números é conhecido como _encoding_. O encoding é feito em um processo de duas etapas: a tokenização, seguida pela conversão para IDs de entrada. + +Como vimos, o primeiro passo é dividir o texto em palavras (ou partes de palavras, símbolos de pontuação, etc.), normalmente chamadas de *tokens*. Há várias regras que podem guiar esse processo, e é por isso que precisamos instanciar o tokenizer usando o nome do modelo, para nos certificarmos de usar as mesmas regras que foram usadas quando o modelo foi pré-treinado. + +O segundo passo é converter esses tokens em números, para que possamos construir um tensor a partir deles e alimentá-los com o modelo. Para isso, o tokenizer tem um *vocabulário* (vocabulary), que é a parte que realizamos o download quando o instanciamos com o método `from_pretrained()`. Mais uma vez, precisamos utilizar o mesmo vocabulário utilizado quando o modelo foi pré-treinado. + +Para entender melhor os dois passos, vamos explorá-los separadamente. Note que usaremos alguns métodos que executam partes da pipeline de tokenização separadamente para mostrar os resultados intermediários dessas etapas, mas na prática, você deve chamar o tokenizer diretamente em suas entradas (como mostrado na seção 2). + +### Tokenização + +O processo de tokenization é feito através do método `tokenize()` do tokenizer: + +```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) +``` + +A saída deste método é uma lista de strings, ou tokens: + +```python out +['Using', 'a', 'transform', '##er', 'network', 'is', 'simple'] +``` + +Este tokenizer é um tokenizer de sub-palavras: ele divide as palavras até obter tokens que podem ser representadas por seu vocabulário. É o caso aqui do "transformer", que é dividido em dois tokens: "transform" e "##er". + +### Desde os tokens até IDs de entrada + +A conversão para IDs de entrada é feita pelo método de tokenização `convert_tokens_to_ids()`: + +```py +ids = tokenizer.convert_tokens_to_ids(tokens) + +print(ids) +``` + +```python out +[7993, 170, 11303, 1200, 2443, 1110, 3014] +``` + +Estas saídas, uma vez convertidas no tensor com a estrutura apropriada, podem então ser usadas como entradas para um modelo como visto anteriormente neste capítulo. + + + +✏️ **Experimente realizar isso!** Replicar os dois últimos passos (tokenização e conversão para IDs de entrada) nas frases de entrada que usamos na seção 2 ("I've been waiting for a HuggingFace course my whole life." e "I hate this so much!"). Verifique se você recebe os mesmos IDs de entrada que recebemos antes! + + + +## Decoding + +*Decoding* vai pela direção ao contrário: a partir de índices de vocabulário, queremos obter uma string. Isto pode ser feito com o método `decode()` da seguinte forma: + + +```py +decoded_string = tokenizer.decode([7993, 170, 11303, 1200, 2443, 1110, 3014]) +print(decoded_string) +``` + +```python out +'Using a Transformer network is simple' +``` + +Observe que o método `decode` não apenas converte os índices em tokens, mas também agrupa os tokens que fizeram parte das mesmas palavras para produzir uma frase legível. Este comportamento será extremamente útil quando utilizamos modelos que preveem um novo texto (seja texto gerado a partir de um prompt, ou para problemas de _sequence-to-sequence_ como tradução ou sumarização). + + + +Até agora você já deve entender as operações atômicas que um tokenizer pode lidar: tokenização, conversão para IDs, e conversão de IDs de volta para uma string. Entretanto, acabamos de começar a ver a ponta do iceberg. Na seção seguinte, vamos nos aproximar de seus limites e dar uma olhada em como superá-los. \ No newline at end of file diff --git a/chapters/pt/chapter2/5.mdx b/chapters/pt/chapter2/5.mdx new file mode 100644 index 000000000..9b45c2c47 --- /dev/null +++ b/chapters/pt/chapter2/5.mdx @@ -0,0 +1,340 @@ + + +# Tratando sequências múltiplas + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + +{:else} + +{/if} + +Na seção anterior, exploramos os casos mais simples de uso: fazer inferência sobre uma única sequência de pequeno comprimento. No entanto, surgem algumas questões: + +- Como nós tratamos diversas sequências? +- Como nós tratamos diversas sequências *de diferentes tamanhos*? +- Os índices de vocabulário são as únicas entradas que permitem que um modelo funcione bem? +- Existe uma sequência muito longa? + +Vamos ver que tipos de problemas estas questões colocam, e como podemos resolvê-los usando a API do 🤗 Transformers. + +## Modelos esperam um batch de entradas + +No exercício anterior, você viu como as sequências são traduzidas em listas de números. Vamos converter esta lista de números em um tensor e enviá-la para o modelo: + +{#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) +# This line will fail. +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) +# This line will fail. +model(input_ids) +``` + +```py out +InvalidArgumentError: Input to reshape is a tensor with 14 values, but the requested shape has 196 [Op:Reshape] +``` +{/if} + +Oh não! Por que isso falhou? "Seguimos os passos do pipeline na seção 2. + +O problema é que enviamos uma única sequência para o modelo, enquanto que os 🤗 transformers esperam várias sentenças por padrão. Aqui tentamos fazer tudo o que o tokenizer fez nos bastidores quando o aplicamos a uma `sequência`, mas se você olhar com atenção, verá que ele não apenas converteu a lista de IDs de entrada em um tensor, mas acrescentou uma dimensão em cima dele: + +{#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} + +Vamos tentar novamente e acrescentar uma nova dimensão: + +{#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} + +Printamos os IDs de entrada assim como os logits resultantes - aqui está a saída: + +{#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} + +*Batching* é o ato de enviar múltiplas sentenças através do modelo, todas de uma só vez. Se você tiver apenas uma frase, você pode apenas construir um lote com uma única sequência: + +``` +batched_ids = [ids, ids] +``` + +Este é um lote de duas sequências idênticas! + + + +✏️ **Experimente!** Converta esta lista de `batched_ids` em um tensor e passe-a através de seu modelo. Verifique se você obtém os mesmos logits que antes (mas duas vezes)! + + + +O Batching permite que o modelo funcione quando você o alimenta com várias frases. Usar várias sequências é tão simples quanto construir um lote com uma única sequência. Há uma segunda questão, no entanto. Quando você está tentando agrupar duas (ou mais) sentenças, elas podem ser de comprimentos diferentes. Se você já trabalhou com tensores antes, você sabe que eles precisam ser de forma retangular, então você não será capaz de converter a lista de IDs de entrada em um tensor diretamente. Para contornar este problema, normalmente realizamos uma *padronização* (padding) nas entradas. + + +## Realizando padding nas entradas + +A seguinte lista de listas não pode ser convertida em um tensor: + +```py no-format +batched_ids = [ + [200, 200, 200], + [200, 200] +] +``` + +Para contornar isso, usaremos *padding* para fazer com que nossos tensores tenham uma forma retangular. O padding garante que todas as nossas frases tenham o mesmo comprimento, acrescentando uma palavra especial chamada *padding token* às frases com menos valores. Por exemplo, se você tiver 10 frases com 10 palavras e 1 frase com 20 palavras, o padding garantirá que todas as frases tenham 20 palavras. Em nosso exemplo, o tensor resultante se parece com isto: + + +```py no-format +padding_id = 100 + +batched_ids = [ + [200, 200, 200], + [200, 200, padding_id], +] +``` + +O padding do ID token pode ser encontrada em `tokenizer.pad_token_id`. Vamos utilizá-lo e enviar nossas duas frases através do modelo individualmente e agrupadas em batches: + +{#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} + +Há algo errado com os logits em nossas predições em batches: a segunda fileira deveria ser a mesma que os logits para a segunda frase, mas temos valores completamente diferentes! + +Isto porque a característica chave dos Transformer são as camadas de atenção que *contextualizam* cada token. Estes levarão em conta os tokens de padding, uma vez que atendem a todos os tokens de uma sequência. Para obter o mesmo resultado ao passar frases individuais de diferentes comprimentos pelo modelo ou ao passar um batch com as mesmas frases e os paddings aplicados, precisamos dizer a essas camadas de atenção para ignorar os tokens de padding. Isto é feito com o uso de uma máscara de atenção (*attention mask*). + +## Attention masks + +*Attention masks* são tensores com a mesma forma exata do tensor de IDs de entrada, preenchidos com 0s e 1s: 1s indicam que os tokens correspondentes devem ser atendidas, e 0s indicam que os tokens correspondentes não devem ser atendidas (ou seja, devem ser ignoradas pelas camadas de atenção do modelo). + +Vamos completar o exemplo anterior com uma máscara de atenção: + +{#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} + +Agora obtemos os mesmos logits para a segunda frase do batch. + +Observe como o último valor da segunda sequência é um ID de padding, que é um valor 0 na máscara de atenção. + + + +✏️ **Experimente!** Aplique a tokenização manualmente nas duas frases usadas na seção 2 ("I've been waiting for a HuggingFace course my whole life." e "I hate this so much!"). Passe-as através do modelo e verifique se você obtém os mesmos logits que na seção 2. Agora, agrupe-os usando o token de padding e depois crie a máscara de atenção adequada. Verifique que você obtenha os mesmos resultados ao passar pelo modelo! + + + +## Sequências mais longas + +Com os Transformer, há um limite para os comprimentos das sequências, podemos passar os modelos. A maioria dos modelos manipula sequências de até 512 ou 1024 tokens, e se chocará quando solicitados a processar sequências mais longas. Há duas soluções para este problema: + +- Use um modelo com suporte a um comprimento mais longo de sequência. +- Trunque suas sequências. + +Os modelos têm diferentes comprimentos de sequência suportados, e alguns são especializados no tratamento de sequências muito longas. O [Longformer](https://huggingface.co/transformers/model_doc/longformer.html) é um exemplo, e outro exemplo é o [LED](https://huggingface.co/transformers/model_doc/led.html). Se você estiver trabalhando em uma tarefa que requer sequências muito longas, recomendamos que você dê uma olhada nesses modelos. + +Caso contrário, recomendamos que você trunque suas sequências, especificando o parâmetro `max_sequence_length`: + +```py +sequence = sequence[:max_sequence_length] +``` diff --git a/chapters/pt/chapter2/6.mdx b/chapters/pt/chapter2/6.mdx new file mode 100644 index 000000000..a1ba25c38 --- /dev/null +++ b/chapters/pt/chapter2/6.mdx @@ -0,0 +1,167 @@ + + +# Colocando tudo junto + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Nas últimas seções, temos feito o nosso melhor para fazer a maior parte do trabalho à mão. Exploramos como funcionam os tokenizers e analisamos a tokenização, conversão para IDs de entrada, padding, truncagem e máscaras de atenção. + +Entretanto, como vimos na seção 2, a API dos 🤗 Transformers pode tratar de tudo isso para nós com uma função de alto nível, na qual mergulharemos aqui. Quando você chama seu `tokenizer` diretamente na frase, você recebe de volta entradas que estão prontas para passar pelo seu modelo: + +```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) +``` + +Aqui, a variável `model_inputs` contém tudo o que é necessário para que um modelo funcione bem. Para DistilBERT, isso inclui os IDs de entrada, bem como a máscara de atenção. Outros modelos que aceitam entradas adicionais também terão essas saídas pelo objeto `tokenizer`. + +Como veremos em alguns exemplos abaixo, este método é muito poderoso. Primeiro, ele pode simbolizar uma única sequência: + +```py +sequence = "I've been waiting for a HuggingFace course my whole life." + +model_inputs = tokenizer(sequence) +``` + +Também lida com várias sequências de cada vez, sem nenhuma mudança na API: + +```py +sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] + +model_inputs = tokenizer(sequences) +``` + +Ela pode ser aplicada de acordo com vários objetivos: + +```py +# Irá preencher as sequências até o comprimento máximo da sequência +model_inputs = tokenizer(sequences, padding="longest") + +# Irá preencher as sequências até o comprimento máximo do modelo +# (512 para o modelo BERT ou DistilBERT) +model_inputs = tokenizer(sequences, padding="max_length") + +# Irá preencher as sequências até o comprimento máximo especificado +model_inputs = tokenizer(sequences, padding="max_length", max_length=8) +``` + +Também pode truncar sequências: + +```py +sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] + +# Irá preencher as sequências até o comprimento máximo do modelo +# (512 para o modelo BERT ou DistilBERT) +model_inputs = tokenizer(sequences, truncation=True) + +# Truncará as sequências que são mais longas do que o comprimento máximo especificado +model_inputs = tokenizer(sequences, max_length=8, truncation=True) +``` + +O objeto `tokenizer` pode lidar com a conversão para tensores de estrutura específicos, que podem então ser enviados diretamente para o modelo. Por exemplo, na seguinte amostra de código, estamos solicitando que o tokenizer retorne tensores de diferentes estruturas - `"pt"` retorna tensores PyTorch, `"tf"` retorna tensores TensorFlow, e `"np"` retorna arrays NumPy: + +```py +sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] + +# Retorna tensores PyTorch +model_inputs = tokenizer(sequences, padding=True, return_tensors="pt") + +# Retorna tensores TensorFlow +model_inputs = tokenizer(sequences, padding=True, return_tensors="tf") + +# Retorna NumPy arrays +model_inputs = tokenizer(sequences, padding=True, return_tensors="np") +``` + +## Tokens especiais + +Se dermos uma olhada nos IDs de entrada devolvidos pelo tokenizer, veremos que eles são um pouco diferentes do que tínhamos anteriormente: + +```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] +``` + + +Um token ID foi adicionada no início e uma no final. Vamos decodificar as duas sequências de IDs acima para ver do que se trata: + + +```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." +``` + +O tokenizer acrescentou a palavra especial `[CLS]` no início e a palavra especial `[SEP]` no final. Isto porque o modelo foi pré-treinado com esses, então para obter os mesmos resultados para inferência, precisamos adicioná-los também. Note que alguns modelos não acrescentam palavras especiais, ou acrescentam palavras diferentes; os modelos também podem acrescentar estas palavras especiais apenas no início, ou apenas no final. Em qualquer caso, o tokenizer sabe quais são as palavras que são esperadas e tratará disso para você. + + +## Do tokenizer ao modelo + +Agora que já vimos todos os passos individuais que o objeto `tokenizer` utiliza quando aplicado em textos, vamos ver uma última vez como ele pode lidar com múltiplas sequências (padding!), sequências muito longas (truncagem!), e múltiplos tipos de tensores com seu API principal: + +{#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/pt/chapter2/7.mdx b/chapters/pt/chapter2/7.mdx new file mode 100644 index 000000000..2b2f1df5a --- /dev/null +++ b/chapters/pt/chapter2/7.mdx @@ -0,0 +1,13 @@ +# Uso básico concluído! + +Ótimo trabalho seguindo o curso até aqui! Recapitulando, neste capítulo, você: + +- Aprendeu os elementos básicos de um modelo Transformer. +- Aprendeu que compõe o pipeline de tokenização. +- Vou como utilizar um transformer na prática. +- Aprendeu como aproveitar um tokenizer para converter texto em tensores que são compreensíveis pelo modelo. +- Montar um tokenizer e um modelo juntos para ir do texto às previsões. +- Aprendeu as limitações dos IDs de entrada e aprendeu sobre máscaras de atenção. +- Testou os métodos de tokenizer versáteis e configuráveis. + +De agora em diante, você deve ser capaz de navegar livremente pelos documentos dos 🤗 transformers: o vocabulário soará familiar, e você já viu os métodos que utilizará na maior parte do tempo. diff --git a/chapters/pt/chapter2/8.mdx b/chapters/pt/chapter2/8.mdx new file mode 100644 index 000000000..00a283ad3 --- /dev/null +++ b/chapters/pt/chapter2/8.mdx @@ -0,0 +1,305 @@ + + + + +# Questionário de fim de capítulo + +### 1. Qual é a ordem do pipeline para a modelagem de linguagem? + + + +### 2. Quantas dimensões tem o tensor do Transformer de base, e quais são elas? + + + +### 3. Qual dos seguintes é um exemplo de Tokenização por sub-palavras? + + + +### 4. O que é uma *model head*? + + + +{#if fw === 'pt'} +### 5. O que seria um `AutoModel`? + +AutoNLP?" + }, + { + text: "Um objeto que devolve a arquitetura correta com base em um checkpoint", + explain: "Exatamente: o AutoModel só precisa saber o checkpoint para saber como inicializar então devolver a arquitetura correta.", + correct: true + }, + { + text: "Um modelo que detecta automaticamente a linguagem utilizada para suas entradas a fim de carregar os pesos corretos", + explain: "Incorreto; embora alguns checkpoints e modelos sejam capazes de lidar com vários idiomas, não há ferramentas embutidas para seleção automática de checkpoints de acordo com o idioma. Você deve ir para o Model Hub para encontrar o melhor checkpoint para realizar sua tarefa!" + } + ]} +/> + +{:else} +### 5. O que seria um `TFAutoModel`? + +AutoNLP?" + }, + { + text: "Um objeto que devolve a arquitetura correta com base em um checkpoint", + explain: "Exatamente: o TFAutoModel só precisa saber o checkpoint para saber como inicializar então devolver a arquitetura correta.", + correct: true + }, + { + text: "Um modelo que detecta automaticamente a linguagem utilizada para suas entradas a fim de carregar os pesos corretos", + explain: "Incorreto; embora alguns checkpoints e modelos sejam capazes de lidar com vários idiomas, não há ferramentas embutidas para seleção automática de checkpoints de acordo com o idioma. Você deve ir para o Model Hub para encontrar o melhor checkpoint para realizar sua tarefa!" + } + ]} +/> + +{/if} + +### 6. Quais são as técnicas a serem observadas quando realizar batches com sequências de diferentes tamanhos? + + + +### 7. Qual é o objetivo de aplicar uma função SoftMax à saída de logits para um modelo de classificação sequencial?? + + + +### 8. Qual é o método core da API tokenizer? + +encode, pois pode codificar texto em IDs e IDs em predições", + explain: "Errado! O método encode existe na tokenização, porém não existe nos modelos." + }, + { + text: "Chamando diretamente o objeto de tokenização (tokenizer).", + explain: "Exatamente! O método __call__ do tokenizer é um método muito poderoso que pode lidar com praticamente qualquer coisa. É também o método usado para recuperar as predições de um modelo.", + correct: true + }, + { + text: "padding", + explain: "Errado! O padding é muito útil, mas é apenas uma parte da API do tokenizer." + }, + { + text: "tokenize", + explain: "O método tokenize é indiscutivelmente um dos métodos mais úteis, mas não é o núcleo do API do tokenizer." + } + ]} +/> + +### 9. O que a variável `result` contém nesta pedaço de código? + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +result = tokenizer.tokenize("Hello!") +``` + +__call__ ou convert_tokens_to_ids!" + }, + { + text: "Uma string contendo todos os tokens ", + explain: "Isto seria subótimo, pois o objetivo é dividir a string em vários tokens." + } + ]} +/> + +{#if fw === 'pt'} +### 10. Tem algo errado com o código abaixo? + +```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. Tem algo errado com o código abaixo? + +```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/_toctree.yml b/chapters/ru/_toctree.yml index e597bd2e0..895b7475c 100644 --- a/chapters/ru/_toctree.yml +++ b/chapters/ru/_toctree.yml @@ -3,7 +3,7 @@ - local: chapter0/1 title: Введение -- title: 1. Transformer models +- title: 1. Трансформеры sections: - local: chapter1/1 title: Введение @@ -11,3 +11,22 @@ title: Обработка естесственного языка - local: chapter1/3 title: Трансформеры, на что они способны? + - local: chapter1/4 + title: Как работают трансформеры? + - local: chapter1/5 + title: Модели энкодеров + - local: chapter1/6 + title: Модели декодеров + - local: chapter1/7 + title: Модели "seq2seq" + - local: chapter1/8 + title: Предвзятости и ограничения + - local: chapter1/9 + title: Итоги + + +- title: 2. Использование библиотеки 🤗 Transformers + sections: + - local: chapter2/1 + title: Введение + diff --git a/chapters/ru/chapter1/4.mdx b/chapters/ru/chapter1/4.mdx new file mode 100644 index 000000000..3c901acd4 --- /dev/null +++ b/chapters/ru/chapter1/4.mdx @@ -0,0 +1,173 @@ +# Как работают трансформеры? + +В этом разделе мы посмотрим в общих чертах на то, как работают трансфореры. + +## Немного истории + +Здесь приведены несколько ссылок на (короткую) историю трансформеров: + +
+A brief chronology of Transformers models. + +
+ +[Архитектура трансформеров](https://arxiv.org/abs/1706.03762) была опубликована в июне 2017. Основной фокус оригинального исследования был сосредоточен на задачах перевода. Эта публикация повлекла за собой несколько влиятельных моделей: + +- **Июнь 2018**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), первая предобученная модель, часто используется процедура тонкой настройки (fine-tuning) и применение для различных NLP-задач с последующим получением результатов высокого качества. + +- **Октябрь 2018**: [BERT](https://arxiv.org/abs/1810.04805), другая большая предобученная модель, была разработана для для получения хороших саммаризаций предложений (больше мы узнаем об этом в следующей главе!) + +- **Февраль 2019**: [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), улучшенная (и более объемная) версия GPT, которая не была сразу опубликована по этическим соображениям + +- **Октябрь 2019**: [DistilBERT](https://arxiv.org/abs/1910.01108), «дистиллированная» версия BERT, которая на 60% быстрее и на 40% менее объемная, однако сохраняющая 97% производительности BERT + +- **Октябрь 2019**: [BART](https://arxiv.org/abs/1910.13461) and [T5](https://arxiv.org/abs/1910.10683), две больших модели, повторяющие архитектуру классического трансформера + +- **Май 2020**, [GPT-3](https://arxiv.org/abs/2005.14165), еще более крупная версия GPT-2, способная хорошо справляться с различными задачами без необходимости fine-tuning (так называемое _zero-shot learning_) + +Этот список далеко не полный, он всего лишь призван выделить несколько разновидностей моделей трансформеров. +В широком смысле трансформеры могут быть классифицированы на три типа: +- GPT-подобные модели (также часто называемые _авторегрессионные_ трансформеры) +- BERT-подобные модели (также часто называемые _автокодирующие_ трансформеры (_auto-encoding_)) +- тип BART/T5 модели (также часто называются модели класса _последовательность-последовательность_ (_sequence2sequence, seq2seq_)) + +Мы рассмотри эти семейства более глубоко позже. + +## Трансформеры - языковые модели + +Все модели трансформеров, упомянутые выше (GPT, BERT, BART, T5, etc.) обучены как *языковые модели*. Это означает, что они обучены на огромном количестве текста в технике самостоятельного обучения (self-supervised learning). Самостоятельное обучение - это такой способ обучения, в котором цель обучения автоматически вычислятся на основе входных данных. Это означает, что люди не должны размечать данные! + +Такой тип моделей реализует статистическое понимание языка, на котором он был обучен, но он не очень полезен для конкретных практических задач. Из-за этого базовая предварительно обученная модель потом подвергается процедуре, называемой *трансферным обучением*. В ходе этого процесса модель настраивается под конкретные наблюдения, т. е. с размеченными человеком данными конкретной задачи. + +В качестве примера можно привести предсказание следующего слова в предложении на основе *n* предыдущих слов. Это называется *каузальным языковым моделированием*, потому что модель зависит от прошлых и текущих слов, но не от будущих. + + +
+Example of causal language modeling in which the next word from a sentence is predicted. + +
+ +Другой пример - *максированная языковая модель*, которая предсказывает замаскированное слово в предложении. + +
+Example of masked language modeling in which a masked word from a sentence is predicted. + +
+ +## Трансформеры - большие модели + +За исключением нескольких моделей (например, DistilBERT), общий подход к достижению высокого качества заключается в увеличении размера моделей и увеличении количества данных для обучения. + +
+Number of parameters of recent Transformers models +
+ +К сожалению, обучение модели, особенно большой, требует большого количества данных. Это приводит к увеличению времени и вычислительных потребностей. Это воздействует даже на окружающую среду, что можно увидеть графике ниже. + +
+The carbon footprint of a large language model. + +
+ + + +Это продемонстрировано командой разработчиков на примере очень большой модели. Команда сознательно пытающется уменьшить воздействие предобучения на окружающую среду. Углеродный следу от проведения множества экспериментов для получения лучших гиперпараметров будет еще выше. + +Представьте себе, что каждый раз, когда исследовательская группа, студенческая организация или компания хотят обучить модель, они делают это с нуля. Это привело бы к огромным, ненужным глобальным затратам! + +Вот почему совместное использование языковых моделей имеет первостепенное значение: совместное использование обученных весов и построение на основе уже обученных весов снижает общую стоимость вычислений и углеродный след сообщества. + +## Трансферное обучение + + + +*Предобучение* - это процесс обучения модели с нуля: веса модели случайным образом инициализируются, после начинается обучение без предварительных настроек. + +
+The pretraining of a language model is costly in both time and money. + +
+ +Предобучение обычно происходит на огромных наборах данных, сам процесс может занять несколько недель. + +*Fine-tuning*, с другой стороны, это обучение, проведенной *после* того, как модель была предобучена. Для проведения fine-tuning вы сначала должны выбрать предобученную языковую модель, а после провести обучение на данных собственной задачи. Стойте -- почему не обучить модель сразу же на данных конкретной задачи? Этому есть несколько причин: + +* Предобученная модель уже обучена на датасете, который имеет много сходств с датасетом для fine-tuning. Процесс тонкой настройки может использовать знания, которые были получены моделью в процессе предобучения (например, в задачах NLP предварительно обученная модель будет иметь представление о статистических закономерностях языка, который вы используете в своей задаче). + +* Так как предобученная модель уже "видела" много данных, процесс тонкой настройки требует меньшего количества данных для получения приемлемых результатов. + +* По этой же причине требуется и намного меньше времени для получения хороших результатов. + +Например, можно использовать предварительно обученную на английском языке модель, а затем провести ее fine-tuning на корпусе arXiv, в результате чего получится научно-исследовательская модель. Для тонкой настройки потребуется лишь ограниченный объем данных: знания, которые приобрела предварительно обученная модель, «передаются» (осуществляют трансфер), отсюда и термин «трансферное обучение». + +
+The fine-tuning of a language model is cheaper than pretraining in both time and money. + +
+ +Таким образом, тонкая настройка модели требует меньше времени, данных, финансовых и экологических затрат. Также быстрее и проще перебирать различные схемы тонкой настройки, поскольку обучение требует меньше усилий, чем полное предварительное обучение. + +Этот процесс также даст лучшие результаты, чем обучение с нуля (если только у вас нет большого количества данных), поэтому вы всегда должны пытаться использовать предобученную модель — модель, максимально приближенную к поставленной задаче, а потом дообучить ее. + +## Общая архитектура + +В этом разделе мы рассмотрим общую архитектуру модели трансформера. Не беспокойтесь, если вы не понимаете некоторых понятий; далее есть подробные разделы, посвященные каждому из компонентов. + + + +## Введение + +Модель состоит из двух блоков: + +* **Encoder (слева)** (кодировщик, энкодер): энкодер получает входные данные и строит их репрезентацию (формирует признаки). Это означает, модель нацелена на "понимание" входных данных. +* **Декодер (справа)** (декодировщик, декодер): декодер использует репрезентации (признаки) энкодера с другими входными данными для создания нужной последовательности. Это означает, что модель нацелена на генерацию выходных данных. + +
+Architecture of a Transformers models + +
+ +Каждая из этих частей может быть использована отдельно, это зависит от задачи: + +* **Encoder-модели**: полезны для задач, требющих понимания входных данных, таких как классификация предложений и распознавание именованных сущностей. +* **Decoder-модели**: полезны для генеративных задач, таких как генерация текста. +* **Encoder-decoder модели** или **seq2seq-модели**: полезны в генеративных задачах, требущих входных данных. Например: перевод или саммаризация текста. + +Мы изучим эти архитектуры глубже в следующих разделах. + +## Слой внимания или attention + +Ключевой особенностью трансформеров является наличие в архитектуре специального слоя, называемого слоем внимания или attention'ом. Статья, в которой была описана архитектура трансформера, называлась["Attention Is All You Need"](https://arxiv.org/abs/1706.03762) ("Внимание - все, что вам нужно")! Мы изучим детали этого слоя позже. На текущий момент мы сформулируем механизм его работы так: attention-слой помогает модели "обращать внимание" на одни слова в поданном на вход предложении, а другие слова в той или иной степени игнорировать. И это происходит в процессе анализа каждого слова. + +Чтобы поместить это в контекст, рассмотрим задачу перевода текста с английского на французский язык. Для предложения "You like this course", модель должна будет также учитывать соседнее слово "You", чтобы получить правильный перевод слова "like", потому что во французском языке глагол "like" спрягается по-разному в зависимости от подлежащего. Однако остальная часть предложения бесполезна для перевода этого слова. В том же духе при переводе "like" также необходимо будет обратить внимание на слово "course", потому что "this" переводится по-разному в зависимости от того, стоит ли ассоциированное существительное в мужском или женском роде. Опять же, другие слова в предложении не будут иметь значения для перевода "this". С более сложными предложениями (и более сложными грамматическими правилами) модели потребуется уделять особое внимание словам, которые могут оказаться дальше в предложении, чтобы правильно перевести каждое слово. + +Такая же концепция применима к любой задаче, связанной с обработкой естесственного языка: слово само по себе имеет некоторое значение, однако значение очень часто зависит от контекста, которым может являться слово (или слова), стоящие вокруг искомого слова. + +Теперь, когда вы знакомы с идеей attention в целом, посмотрим поближе на архитектуру всего трансформера. + +## Первоначальная архитектура + +Архитектура трансформера изначально была разработана для перевода. Во время обучения энкодер получает входные данные (предложения) на определенном языке, а декодер получает те же предложения на желаемом целевом языке. В энкодере слои внимания могут использовать все слова в предложении (поскольку, как мы только что видели, перевод данного слова может зависеть от того, что в предложении находится после и перед ним). Декодер, в свою очерель, работает последовательно и может обращать внимание только на слова в предложении, которые он уже перевел (то есть только на слова перед генерируемым в данный момент словом). Например, когда мы предсказали первые три слова переведенной цели, мы передаем их декодеру, который затем использует все входные данные энкодера, чтобы попытаться предсказать четвертое слово. + +Чтобы ускорить процесс во время обучения (когда модель имеет доступ к целевым предложениям), декодер получает целевое предложение полностью, но ему не разрешается использовать будущие слова (если он имел доступ к слову в позиции 2 при попытке предсказать слово на позиции 2, задача не будет сложной!). Например, при попытке предсказать четвертое слово уровень внимания будет иметь доступ только к словам в позициях с 1 по 3. + +Первоначальная архитектура Transformer выглядела так: энкодер слева и декодер справа: + +
+Architecture of a Transformers models + +
+ +Обратите внимание, что первый уровень внимания в блоке декодера обращает внимание на все (прошлые) входные данные декодера, а второй уровень внимания использует выходные данные первого энкодера. Таким образом, он может получить доступ ко всему входному предложению, чтобы наилучшим образом предсказать текущее слово. Это очень полезно, так как разные языки могут иметь грамматические правила, которые располагают слова в разном порядке, или некоторый контекст, предоставленный в предложении далеко от текущего слова. Конекст может быть полезен для определения наилучшего перевода данного слова. + +*Attention-mask* (маска внимания) также может использоваться в энкодере/декодере, чтобы модель не обращала внимания на некоторые специальные слова — например, специальное несуществующее слово-заполнитель (служебный токен), используемое для придания всем входным данным одинаковой длины при группировке предложений. + +## Архитектуры и контрольные точки + +По мере погружения в трансформеры, вы будете встречать термины *архитектуры* и *контрольные точки* (checkpoints) в смысле *модели*. Эти термины имеют разный смысл: + +**Архитектура** - скелет модели -- слои, связи и операции, которые выполняются в модели. +**Контрольная точка** - веса модели, которые могут быть загружены для конкретной архитектуры. +**Модель** - зонтичный термин, который может означать и архитектуру, и веса для конкретной архитектуры. В этом курсе мы будем точнее использовать термины *архитектуры* и *чекпоинт*, если это будет важно для лучшего понимания. + +Например, BERT - это архитектура, а `bert-base-cased` - набор весов, подготовленный Google к первому выпуску BERT'а, - это чекпоинт. Однако можно сказать и "модель BERT", и "модель bert-base-cased". diff --git a/chapters/ru/chapter1/5.mdx b/chapters/ru/chapter1/5.mdx new file mode 100644 index 000000000..7e4dc8b1a --- /dev/null +++ b/chapters/ru/chapter1/5.mdx @@ -0,0 +1,17 @@ +# Модели энкодеров + + + +Энкодеры используют только компонент кодировщика трансформера. На каждом этапе слой внимания может использовать все слова исходного предложения. Эти модели часто характеризуют как имеющие двунаправленное внимание ("bi-directional attention"), и часто называют моделями *автоэнкодеров*. + +Предварительное обучение этих моделей обычно заключаетс в том, чтобы как-то исказить данное предложение (например, путем маскировки в нем случайных слов) и поставить перед моделью задачу найти или восстановить исходное предложение. + +Энкодеры лучше всего подходят для задач, требующих _понимания_ всего предложения, таких как классификация предложений, распознавание именованных сущностей (и, в более общем смысле, классификация слов) и ответы на вопросы с извлечением информации из контекста. + +К представителям этого семейства моделей относятся: + +- [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) diff --git a/chapters/ru/chapter1/6.mdx b/chapters/ru/chapter1/6.mdx new file mode 100644 index 000000000..d9ca11089 --- /dev/null +++ b/chapters/ru/chapter1/6.mdx @@ -0,0 +1,16 @@ +# Модели декодеров + + + +Декодировщики используют только компонент декодер трансформера. На каждом этапе для текущего слова слой внимания может получить доступ только к словам, которые были расположены до текущего в предложении. Такие модели часто называются *авторегрессионными моделями*. + +Процесс предобучения декодеров обычно заключается в предсказании следующего слова в предложении. ё + +Такие модели лучше всего подходят для задач, связанных с генерацией текста. + +Представителями этого семейства моделей являются: + +- [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) +- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) +- [GPT-2](https://huggingface.co/transformers/model_doc/gpt2.html) +- [Transformer XL](https://huggingface.co/transformers/model_doc/transformerxl.html) diff --git a/chapters/ru/chapter1/7.mdx b/chapters/ru/chapter1/7.mdx new file mode 100644 index 000000000..730b8f96f --- /dev/null +++ b/chapters/ru/chapter1/7.mdx @@ -0,0 +1,16 @@ +# Модели вида "seq2seq" + + + +Энкодер-декодер модели (также называемые *sequence-to-sequence models*) используют обе части трансформера. На каждом этапе слой внимания энкодера получает доступ ко всем словам в исходной последовательности, тогда как слой внимания декодера получает доступ только к тем словам, которые позиционированы до текущего слова. + +Предобучение таких моделей может быть проведено по аналогии с процессом предобучения энкодера или декодера, но обычно это происходит сложнее. Например, модель [T5](https://huggingface.co/t5-base) была предобучена путем замены случайных фрагментов текста (фрагменты могут содержать несколько слов) на специальную маску, цель модели - предсказать текст, который заменила маска. + +Модели seq2seq лучше всего подходят для задач генерации новых предложений, зависящих от входного массива данных, например: саммаризация текста, перевод или генерация ответов на вопросы. + +Представителями этого семейства являются: + +- [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) diff --git a/chapters/ru/chapter1/8.mdx b/chapters/ru/chapter1/8.mdx new file mode 100644 index 000000000..05d3b724d --- /dev/null +++ b/chapters/ru/chapter1/8.mdx @@ -0,0 +1,33 @@ +# Предвзятости и ограничения + + + +Если вы намерены использовать предварительно обученную модель или точно настроенную версию в рабочей среде, имейте в виду, что, хотя эти модели являются мощными инструментами, они имеют ограничения. Самая большая из них заключается в том, что для предварительной подготовки на больших объемах данных исследователи часто очищают весь контент, который они могут найти, беря как лучшее, так и худшее из того, что доступно в Интернете. + +Для иллюстрации вернемся к примеру пайплайна `fill-mask` с моделью BERT: + +```python +from transformers import pipeline + +unmasker = pipeline("fill-mask", model="bert-base-uncased") +result = unmasker("This man works as a [MASK].") +print([r["token_str"] for r in result]) + +result = unmasker("This woman works as a [MASK].") +print([r["token_str"] for r in result]) +``` + +```python out +['lawyer', 'carpenter', 'doctor', 'waiter', 'mechanic'] +['nurse', 'waitress', 'teacher', 'maid', 'prostitute'] +``` + +На просьбу вставить пропущенное слово в этих двух предложениях модель дает только один ответ без гендерной принадлежности (официант/официантка). Другие рабочие профессии обычно ассоциируются с одним конкретным полом — и да, проститутка попала в топ-5 вариантов, которые модель ассоциирует с "женщиной" и "работой". Это происходит даже несмотря на то, что BERT — одна из редких моделей трансформеров, созданная не путем сбора данных со всего Интернета, а с использованием явно нейтральных данных (он обучен на [английской Википедии](https://huggingface.co/datasets/wikipedia) и наборе данных [BookCorpus](https://huggingface.co/datasets/bookcorpus). + +Поэтому, когда вы используете эти инструменты, вам нужно помнить, что исходная модель, которую вы используете, может очень легко генерировать сексистский, расистский или гомофобный контент. Тонкая настройка модели на ваших данных не избавит вас от этой внутренней предвзятости. + diff --git a/chapters/ru/chapter1/9.mdx b/chapters/ru/chapter1/9.mdx new file mode 100644 index 000000000..730712e4e --- /dev/null +++ b/chapters/ru/chapter1/9.mdx @@ -0,0 +1,12 @@ +# Итоги + +В этой главе вы увидели, как подходить к различным задачам NLP, используя высокоуровневую функцию `pipeline()` из библиотеки 🤗 Transformers. Вы также увидели, как искать и использовать модели в Hub, а также как использовать Inference API для тестирования моделей прямо в браузере. + +Мы обсудили, как трансформеры работают на высоком уровне, и поговорили о важности трансферного обучения и тонкой настройки. Ключевым аспектом является то, что вы можете использовать всю архитектуру или только энкодер или декодер, в зависимости от того, какую задачу вы хотите решить. Следующая таблица резюмирует это: + + +| Модель | Примеры | Задачи | +|-----------------|--------------------------------------------|----------------------------------------------------------------------------------| +| Энкодер | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa | Классификация предложений, распознавание именованных сущностей, генерация ответов на вопросы с извлечением информации | +| Декодер | CTRL, GPT, GPT-2, Transformer XL | Генерация текста | +| Энкодер-декодер | BART, T5, Marian, mBART | Саммаризация, перевод, генеративный подход к ответам на вопросы | diff --git a/chapters/ru/chapter2/1.mdx b/chapters/ru/chapter2/1.mdx new file mode 100644 index 000000000..99dbd07b6 --- /dev/null +++ b/chapters/ru/chapter2/1.mdx @@ -0,0 +1,19 @@ +# Введение + +Как вы могли заметить в [Главе 1](/course/chapter1), модели трансформеров обычно бывают очень большие. Обучение и развертывание таких моделей с миллионами и даже десятками *миллиардов* параметров является сложной задачей. Кроме того, новые модели выпускаются почти ежедневно, и каждая из них имеет собственную реализацию, опробовать их все — непростая задача. + +Библиотека 🤗 Transformers была создана для решения этой проблемы. Её цель — предоставить единый API, с помощью которого можно загружать, обучать и сохранять любую модель трансформера. Основными функциями библиотеки являются: + +- **Удобство в использовании**: Скачивание, загрузку и использование современной модели NLP для вывода данных, можно выполнять всего двумя строками кода. +- **Гибкость**: По своей сути все модели представляют собой простые классы библиотек PyTorch `nn.Module` или TensorFlow `tf.keras.Model` и могут обрабатываться, как и любые другие модели, в соответствующих средах машинного обучения (МО). +- **Простота**: В библиотеке практически не используются абстракции. "Все в одном файле" является основной концепцией: прямой проход модели полностью определяется в одном файле, так что сам код понятен, но при этом доступен для взлома. + +Последняя особенность сильно отличает библиотеку 🤗 Transformers от других библиотек машинного обучения. Модели не строятся на модулях, которые являются общими для всех файлов; вместо этого каждая модель имеет свои собственные слои. Это не только делает модели более доступными и понятными, но и позволяет легко экспериментировать с одной моделью, не затрагивая другие. + +Эта глава начнается со сквозного примера, в котором мы используем модель и токенизатор вместе, чтобы воспроизвести функцию `pipeline()` представленную в [Главе 1](/course/chapter1). Далее мы обсудим API модели: углубимся в классы модели и конфигурации и покажем, как загружать модель и как она обрабатывает числовые входные данные для получения прогнозов. + +Затем мы рассмотрим API токенизатора, который является другим основным компонентом функции `pipeline()`. Токенизаторы берут на себя первый и последний этапы обработки, обрабатывая преобразование текста в числовые входные данные для нейронной сети и обратное преобразование в текст, когда это необходимо. Наконец, мы покажем вам, как обработывается передача нескольких предложений в модель с помощью подготовленных пакетов, а затем завершим все это более детальным рассмотрением высокоуровневой функции `tokenizer()`. + + +⚠️ Чтобы воспользоваться всеми функциями, доступными в Model Hub и 🤗 Transformers, мы рекомендуем создать учетную запись. + \ No newline at end of file diff --git a/chapters/th/_toctree.yml b/chapters/th/_toctree.yml index 39b7a199d..ffd290a33 100644 --- a/chapters/th/_toctree.yml +++ b/chapters/th/_toctree.yml @@ -8,6 +8,11 @@ - local: chapter1/1 title: บทนำ +- title: 2. การใช้ 🤗 Transformers + sections: + - local: chapter2/1 + title: บทนำ + - title: 3. การ fine-tune โมเดลที่ผ่านการเทรนมาแล้ว (pretrained model) sections: - local: chapter3/1 @@ -27,4 +32,4 @@ title: จบพาร์ทที่ 1! - local: chapter4/6 title: คำถามท้ายบท - quiz: 4 \ No newline at end of file + quiz: 4 diff --git a/chapters/th/chapter2/1.mdx b/chapters/th/chapter2/1.mdx new file mode 100644 index 000000000..8c354032d --- /dev/null +++ b/chapters/th/chapter2/1.mdx @@ -0,0 +1,19 @@ +# บทนำ + +อย่างที่คุณเห็นใน [Chapter 1](/course/chapter1), โดยปกติแล้วโมเดล Transformer นั้นจะมีขนาดใหญ่มาก การเทรนและการใช้งานโมเดลเหล่านี้ที่มีตัวแปร (parameters) เป็นล้านไปจนถึง *หมื่นล้าน* ตัวแปรนั้นเป็นเรื่องที่ค่อนข้างซับซ้อน นอกจากนั้นแล้วการที่มีโมเดลใหม่ๆปล่อยออกมาเกือบทุกวันและแต่ละโมเดลก็มีวิธีการสร้าง (implementation) เป็นของตัวเอง ดังนั้นการจะลองทุกโมเดลนั้นไม่ใช่เรื่องที่ง่ายเลย +🤗 Transformers library สร้างขึ้นมาเพื่อแก้ปัญหานี้ จุดประสงค์ก็คือ การทำให้ไม่ว่าจะโมเดล Transformer ใดก็ตามสามารถโหลด, เทรน, และบันทึก ได้ด้วยการใช้ API เพียงอันเดียว จุดเด่นหลักๆของ library ประกอบด้วย + +- **ใช้งานง่าย**: การดาวน์โหลด, การโหลด, และการใช้งานโมเดล NLP ที่ประสิทธิภาพดีที่สุด (state-of-the-art) สำหรับการอนุมาน (inference) นั้นสามารถทำได้ด้วยโค้ดเพียง 2 บรรทัด +- **ความยืดหยุ่น**: โดยแก่นแท้แล้วทุกโมเดลนั้นก็เป็นเพียคลาส `nn.Module` ง่ายๆของ PyTorch หรือ `tf.keras.Model` ของ TensorFlow และสามารถถูกจัดการได้เหมือนโมเดลอื่นๆ ใน machine learning (ML) frameworks นั้นๆ +- **ความเรียบง่าย**: การประกาศ abstractions ใดๆข้ามไปมาใน libraries นั้นน้อยมากๆ แนวคิดหลัก (core concept) ก็คือ "ทุกอย่างอยู่ในไฟล์เดียว (All in one file)" เช่น ขั้นตอนการเรียนรู้ของโมเดลใน forward pass นั้นสามารถประกาศทั้งหมดได้ในไฟล์เดียว ดังนั้นตัวโค้ดนั้นสามารถเป็นที่เข้าใจและแก้ไขได้ในตัวมันเอง + +จุดเด่นข้อสุดท้ายนี่เองที่ทำให้ 🤗 Transformers ต่างจาก ML libraries อื่นๆ โมเดลต่างๆไม่ได้ถูกสร้างขึ้นมาจากโมดูลต่างๆที่ต้องแชร์ข้ามไฟล์กันไปมา แต่กลับกัน แต่ละโมเดลจะมี layers ของตัวเอง +นอกจากจะทำให้โมเดลเข้าถึงและเข้าใจได้ง่ายแล้ว ยังทำให้คุณสามารถทดลองโมเดลๆหนึ่งโดยที่ไม่กระทบโมเดลอื่นๆ + +บทนี้จะเริ่มด้วยตัวอย่างแบบ end-to-end ซึ่งเราจะใช้โมเดลและ tokenizer ร่วมกันเพื่อทำซ้ำ(เลียนแบบ) ฟังก์ชัน `pipeline()` จากที่เรียนใน [Chapter 1](/course/chapter1) หลังจากนั้นเราจะมาเรียนเกี่ยวกับ API ของโมเดล โดยเราจะเจาะลึกในคลาสของโมเดลและการตั้งค่า (configuration) และจะแสดงวิธีการโหลดโมเดลและกระบวนการที่โมเดลทำการทำนายผลจากชุดข้อมูลเชิงตัวเลข ว่าทำอย่างไร + +หลังจากนั้นเราจะไปดูกันที่ tokenizer API ซึ่งเป็นอีกหนึ่งส่วนประกอบหลักของฟังก์ชัน `pipeline()`, Tokenizers จะรับผิดชอบการประมวลขั้นแรกและขั้นสุดท้าย ซึ่งก็คือ การแปลงข้อมูลที่เป็นข้อความให้เป็นข้อมูลเชิงตัวเลข เพื่อใช้กับ neural network, และการแปลงข้อมูลกลับไปเป็นตัวอักษร ในกรณีที่จำเป็น และสุดท้ายเราจะแสดงวิธีการจัดการกับการส่งข้อความทีละหลายๆประโยคแบบที่เตรียมไว้เป็นชุดๆ (batch) ไปยังโมเดล และปิดท้ายด้วยฟังก์ชัน `tokenizer()` + + +⚠️ เพื่อให้ได้ประโยชน์สูงสุดจากคุณลักษณะเด่นทั้งหมดที่มีใน Model Hub และ 🤗 Transformers, เราแนะนำให้คุณ สร้างบัญชี. + \ No newline at end of file diff --git a/chapters/zh/_toctree.yml b/chapters/zh/_toctree.yml new file mode 100644 index 000000000..453fc5e99 --- /dev/null +++ b/chapters/zh/_toctree.yml @@ -0,0 +1,23 @@ +- title: 1. Transformer 模型 + sections: + - local: chapter1/1 + title: 章节简介 + - local: chapter1/2 + title: 自然语言处理 + - local: chapter1/3 + title: Transformers能做什么? + - local: chapter1/4 + title: Transformers 是如何工作的? + - local: chapter1/5 + title: 编码器模型 + - local: chapter1/6 + title: 解码器模型 + - local: chapter1/7 + title: 序列到序列模型 + - local: chapter1/8 + title: 偏见和局限性 + - local: chapter1/9 + title: 总结 + - local: chapter1/10 + title: 章末小测验 + quiz: 1 \ No newline at end of file diff --git a/chapters/zh/chapter1/1.mdx b/chapters/zh/chapter1/1.mdx new file mode 100644 index 000000000..68e6a14c7 --- /dev/null +++ b/chapters/zh/chapter1/1.mdx @@ -0,0 +1,52 @@ +# 简介 + +## 欢迎来到🤗课程 + + + +本课程将使用 Hugging Face 生态系统中的库——🤗 Transformers、🤗 Datasets、🤗 Tokenizers 和 🤗 Accelerate——以及 Hugging Face Hub 教你自然语言处理 (NLP)。它是完全免费的,并且没有广告。 + + +## 有什么是值得期待的? + +以下是课程的简要概述: + +
+Brief overview of the chapters of the course. + +
+ +- 第 1 章到第 4 章介绍了 🤗 Transformers 库的主要概念。在本课程的这一部分结束时,您将熟悉 Transformer 模型的工作原理,并将了解如何使用 [Hugging Face Hub](https://huggingface.co/models) 中的模型,在数据集上对其进行微调,并在 Hub 上分享您的结果。 +- 第 5 章到第 8 章在深入研究经典 NLP 任务之前,教授 🤗 Datasets和 🤗 Tokenizers的基础知识。在本部分结束时,您将能够自己解决最常见的 NLP 问题。 +- 第 9 章到第 12 章更加深入,探讨了如何使用 Transformer 模型处理语音处理和计算机视觉中的任务。在此过程中,您将学习如何构建和分享模型,并针对生产环境对其进行优化。在这部分结束时,您将准备好将🤗 Transformers 应用于(几乎)任何机器学习问题! + +这个课程: + +* 需要良好的 Python 知识 +* 最好先学习深度学习入门课程,例如[DeepLearning.AI](https://www.deeplearning.ai/) 提供的 [fast.ai实用深度学习教程](https://course.fast.ai/) +* 不需要事先具备 [PyTorch](https://pytorch.org/) 或 [TensorFlow](https://www.tensorflow.org/) 知识,虽然熟悉其中任何一个都会对huggingface的学习有所帮助 + +完成本课程后,我们建议您查看 [DeepLearning.AI的自然语言处理系列课程](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh),其中涵盖了广泛的传统 NLP 模型,如朴素贝叶斯和 LSTM,这些模型非常值得了解! + +## 我们是谁? + +关于作者: + +**Matthew Carrigan** 是 Hugging Face 的机器学习工程师。他住在爱尔兰都柏林,之前在 Parse.ly 担任机器学习工程师,在此之前,他在Trinity College Dublin担任博士后研究员。他不相信我们会通过扩展现有架构来实现 AGI,但无论如何都对机器人充满希望。 + +**Lysandre Debut** 是 Hugging Face 的机器学习工程师,从早期的开发阶段就一直致力于 🤗 Transformers 库。他的目标是通过使用非常简单的 API 开发工具,让每个人都可以使用 NLP。 + +**Sylvain Gugger** 是 Hugging Face 的一名研究工程师,也是 🤗Transformers库的核心维护者之一。此前,他是 fast.ai 的一名研究科学家,他与Jeremy Howard 共同编写了[Deep Learning for Coders with fastai and Py Torch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)。他的主要研究重点是通过设计和改进允许模型在有限资源上快速训练的技术,使深度学习更容易普及。 + +**Merve Noyan** 是 Hugging Face 的开发者倡导者,致力于开发工具并围绕它们构建内容,以使每个人的机器学习平民化。 + +**Lucile Saulnier** 是 Hugging Face 的机器学习工程师,负责开发和支持开源工具的使用。她还积极参与了自然语言处理领域的许多研究项目,例如协作训练和 BigScience。 + +**Lewis Tunstall** 是 Hugging Face 的机器学习工程师,专注于开发开源工具并使更广泛的社区可以使用它们。他也是即将出版的一本书[O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/)的作者之一。 + +**Leandro von Werra** 是 Hugging Face 开源团队的机器学习工程师,也是即将出版的一本书[O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/)的作者之一。他拥有多年的行业经验,通过在整个机器学习堆栈中工作,将 NLP 项目投入生产。 + +你准备好了吗?在本章中,您将学习: +* 如何使用 `pipeline()` 函数解决文本生成、分类等NLP任务 +* 关于 Transformer 架构 +* 如何区分编码器、解码器和编码器-解码器架构和用例 diff --git a/chapters/zh/chapter1/10.mdx b/chapters/zh/chapter1/10.mdx new file mode 100644 index 000000000..23f768115 --- /dev/null +++ b/chapters/zh/chapter1/10.mdx @@ -0,0 +1,253 @@ + + +# 章末小测试 + +这一章涵盖了很多内容! 如果有一些不太明白的地方,请不要担心; 下一章将帮助你了解这些模块在底层是如何工作的。 + +让我们来测试一下你在这一章学到了什么! + +### 1. 探索 Hub 并寻找 `roberta-large-mnli` checkpoint。 它可以完成什么类型的任务? + + +roberta-large-mnli 页面回顾一下." + }, + { + text: "文本分类", + explain: "更准确地说,它对两个句子在三个标签(矛盾、无关、相近)之间的逻辑链接进行分类——这项任务也称为自然语言推理.", + correct: true + }, + { + text: "文本生成", + explain: "点击前往roberta-large-mnli 页面回顾一下." + } + ]} +/> + +### 2. 下面的代码将会返回什么结果? + +```py +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +sentiment-analysis pipeline将会返回这些." + }, + { + text: "它将返回一个生成的文本来完成这句话。", + explain: "这个选项是不对的 — text-generation pipeline将会返回这些.", + }, + { + text: "它将返回代表人员、组织或位置的单词。", + explain: "此外,使用 grouped_entities=True,它会将属于同一实体的单词组合在一起,例如“Hugging Face”。", + correct: true + } + ]} +/> + +### 3. 在此代码示例中...的地方应该填写什么? + +```py +from transformers import pipeline + +filler = pipeline("fill-mask", model="bert-base-cased") +result = filler("...") +``` + + has been waiting for you.", + explain: "这个选项是不对的。 请查看 bert-base-cased 模型卡片,然后再尝试找找错在哪里。" + }, + { + text: "This [MASK] has been waiting for you.", + explain: "正解! 这个模型的mask的掩码是[MASK].", + correct: true + }, + { + text: "This man has been waiting for you.", + explain: "这个选项是不对的。 这个pipeline的作用是填充经过mask的文字,因此它需要在输入的文本中存在mask的token。" + } + ]} +/> + +### 4. 为什么这段代码会无法运行? + +```py +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +result = classifier("This is a course about the Transformers library") +``` + +candidate_labels=[...].", + correct: true + }, + { + text: "这个pipeline需要多个句子,而不仅仅是一个。", + explain: "这个选项是不对的。尽管正确使用时,此pipeline可以同时处理多个句子(与所有其他pipeline一样)。" + }, + { + text: "像往常一样,🤗 Transformers库出故障了。", + explain: "对此,我们不予置评!" + }, + { + text: "该pipeline需要更长的输入; 这个句子太短了。", + explain: "这个选项是不对的。 不过请注意,在这个pipeline处理时,太长的文本将被截断。" + } + ]} +/> + +### 5. “迁移学习”是什么意思? + + + +### 6. 语言模型在预训练时通常不需要标签,这样的说法是否正确。 + + +自监督,这意味着标签是根据输入自动创建的(例如:预测下一个单词或填充一些[MARSK]单词)。", + correct: true + }, + { + text: "错误", + explain: "这不是一个正确的答案。" + } + ]} +/> + + +### 7. 选择最能描述“模型(model)”、“架构(architecture)”和“权重(weights)”的句子。 + + + +### 8. 你将使用以下哪种类型的模型来根据输入的提示生成文本? + + + +### 9. 你会使用哪些类型的模型来生成文本的摘要? + + + +### 10. 你会使用哪一种类型的模型来根据特定的标签对文本输入进行分类? + + + +### 11. 模型中观察到的偏见有哪些可能的来源? + + diff --git a/chapters/zh/chapter1/2.mdx b/chapters/zh/chapter1/2.mdx new file mode 100644 index 000000000..1b5ee0ea6 --- /dev/null +++ b/chapters/zh/chapter1/2.mdx @@ -0,0 +1,20 @@ +# 自然语言处理 + +在进入 Transformer 模型之前,让我们快速概述一下自然语言处理是什么以及我们为什么这么重视它。 + +## 什么是自然语言处理? + +NLP 是语言学和机器学习交叉领域,专注于理解与人类语言相关的一切。 NLP 任务的目标不仅是单独理解单个单词,而且是能够理解这些单词的上下文。 + +以下是常见 NLP 任务的列表,每个任务都有一些示例: + +- **对整个句子进行分类**: 获取评论的情绪,检测电子邮件是否为垃圾邮件,确定句子在语法上是否正确或两个句子在逻辑上是否相关 +- **对句子中的每个词进行分类**: 识别句子的语法成分(名词、动词、形容词)或命名实体(人、地点、组织) +- **生成文本内容**: 用自动生成的文本完成提示,用屏蔽词填充文本中的空白 +- **从文本中提取答案**: 给定问题和上下文,根据上下文中提供的信息提取问题的答案 +- **从输入文本生成新句子**: 将文本翻译成另一种语言,总结文本 + +NLP 不仅限于书面文本。它还解决了语音识别和计算机视觉中的复杂挑战,例如生成音频样本的转录或图像描述。 +## 为什么具有挑战性? + +计算机处理信息的方式与人类不同。例如,当我们读到“我饿了”这句话时,我们很容易理解它的意思。同样,给定两个句子,例如“我很饿”和“我很伤心”,我们可以轻松确定它们的相似程度。对于机器学习 (ML) 模型,此类任务更加困难。文本需要以一种使模型能够从中学习的方式进行处理。而且由于语言很复杂,我们需要仔细考虑必须如何进行这种处理。关于如何表示文本已经做了很多研究,我们将在下一章中介绍一些方法。 \ No newline at end of file diff --git a/chapters/zh/chapter1/3.mdx b/chapters/zh/chapter1/3.mdx new file mode 100644 index 000000000..076263ba4 --- /dev/null +++ b/chapters/zh/chapter1/3.mdx @@ -0,0 +1,287 @@ +# Transformers能做什么? + + + +在本节中,我们将看看 Transformer 模型可以做什么,并使用 🤗 Transformers 库中的第一个工具:pipeline() 函数。 + +👀 看到那个右上角的 在Colab中打开的按钮了吗? 单击它就可以打开一个包含本节所有代码示例的 Google Colab 笔记本。 每一个有实例代码的小节都会有它。 + +如果您想在本地运行示例,我们建议您查看准备. + + +## Transformer被应用于各个方面! +Transformer 模型用于解决各种 NLP 任务,就像上一节中提到的那样。以下是一些使用 Hugging Face 和 Transformer 模型的公司和组织,他们也通过分享他们的模型回馈社区: + +![使用 Hugging Face 的公司](https://huggingface.co/course/static/chapter1/companies.PNG) +[🤗 Transformers 库](https://github.com/huggingface/transformers)提供了创建和使用这些共享模型的功能。[模型中心(hub)](https://huggingface.co/models)包含数千个任何人都可以下载和使用的预训练模型。您还可以将自己的模型上传到 Hub! + + +⚠️ Hugging Face Hub 不限于 Transformer 模型。任何人都可以分享他们想要的任何类型的模型或数据集!创建一个 Huggingface.co 帐户(https://huggingface.co/join)以使用所有可用功能! + + +在深入研究 Transformer 模型的底层工作原理之前,让我们先看几个示例,看看它们如何用于解决一些有趣的 NLP 问题。 + +## 使用pipelines + + +(这里有一个视频,但是国内可能打不开,译者注) + + +🤗 Transformers 库中最基本的对象是 **pipeline()** 函数。它将模型与其必要的预处理和后处理步骤连接起来,使我们能够通过直接输入任何文本并获得最终的答案: + +```python +from transformers import pipeline + +classifier = pipeline("sentiment-analysis") +classifier("I've been waiting for a HuggingFace course my whole life.") +``` +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}] +``` + + +我们也可以多传几句! +```python +classifier( + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!"] +) +``` +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}, + {'label': 'NEGATIVE', 'score': 0.9994558095932007}] +``` +默认情况下,此pipeline选择一个特定的预训练模型,该模型已针对英语情感分析进行了微调。创建**分类器**对象时,将下载并缓存模型。如果您重新运行该命令,则将使用缓存的模型,无需再次下载模型。 + +将一些文本传递到pipeline时涉及三个主要步骤: + +1. 文本被预处理为模型可以理解的格式。 +2. 预处理的输入被传递给模型。 +3. 模型处理后输出最终人类可以理解的结果。 + +目前[可用的一些pipeline](https://huggingface.co/transformers/main_classes/pipelines.html)是: + +* **特征提取**(获取文本的向量表示) +* **填充空缺** +* **ner**(命名实体识别) +* **问答** +* **情感分析** +* **文本摘要** +* **文本生成** +* **翻译** +* **零样本分类** + +让我们来看看其中的一些吧! + +## 零样本分类 +我们将首先处理一项非常具挑战性的任务,我们需要对尚未标记的文本进行分类。这是实际项目中的常见场景,因为注释文本通常很耗时并且需要领域专业知识。对于这项任务**zero-shot-classification**pipeline非常强大:它允许您直接指定用于分类的标签,因此您不必依赖预训练模型的标签。下面的模型展示了如何使用这两个标签将句子分类为正面或负面——但也可以使用您喜欢的任何其他标签集对文本进行分类。 + +```python +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +classifier( + "This is a course about the Transformers library", + candidate_labels=["education", "politics", "business"], +) +``` +```python out +{'sequence': 'This is a course about the Transformers library', + 'labels': ['education', 'business', 'politics'], + 'scores': [0.8445963859558105, 0.111976258456707, 0.043427448719739914]} +``` + +此pipeline称为zero-shot,因为您不需要对数据上的模型进行微调即可使用它。它可以直接返回您想要的任何标签列表的概率分数! + +✏️**快来试试吧!**使用您自己的序列和标签,看看模型的行为。 + + +## 文本生成 +现在让我们看看如何使用pipeline来生成一些文本。这里的主要使用方法是您提供一个提示,模型将通过生成剩余的文本来自动完成整段话。这类似于许多手机上的预测文本功能。文本生成涉及随机性,因此如果您没有得到相同的如下所示的结果,这是正常的。 + +```python +from transformers import pipeline + +generator = pipeline("text-generation") +generator("In this course, we will teach you how to") +``` +```python out +[{'generated_text': 'In this course, we will teach you how to understand and use ' + 'data flow and data interchange when handling user data. We ' + 'will be working with one or more of the most commonly used ' + 'data flows — data flows of various types, as seen by the ' + 'HTTP'}] +``` +您可以使用参数 **num_return_sequences** 控制生成多少个不同的序列,并使用参数 **max_length** 控制输出文本的总长度。 + + +✏️**快来试试吧!**使用 num_return_sequences 和 max_length 参数生成两个句子,每个句子 15 个单词。 + + +## 在pipeline中使用 Hub 中的其他模型 +前面的示例使用了默认模型,但您也可以从 Hub 中选择特定模型以在特定任务的pipeline中使用 - 例如,文本生成。转到[模型中心(hub)](https://huggingface.co/models)并单击左侧的相应标签将会只显示该任务支持的模型。[例如这样](https://huggingface.co/models?pipeline_tag=text-generation)。 + +让我们试试 [**distilgpt2**](https://huggingface.co/distilgpt2) 模型吧!以下是如何在与以前相同的pipeline中加载它: + +```python +from transformers import pipeline + +generator = pipeline("text-generation", model="distilgpt2") +generator( + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, +) +``` +```python out +[{'generated_text': 'In this course, we will teach you how to manipulate the world and ' + 'move your mental and physical capabilities to your advantage.'}, + {'generated_text': 'In this course, we will teach you how to become an expert and ' + 'practice realtime, and with a hands on experience on both real ' + 'time and real'}] +``` +您可以通过单击语言标签来筛选搜索结果,然后选择另一种文本生成模型的模型。模型中心(hub)甚至包含支持多种语言的多语言模型。 + +通过单击选择模型后,您会看到有一个小组件,可让您直接在线试用。通过这种方式,您可以在下载之前快速测试模型的功能。 + +✏️**快来试试吧!**使用标签筛选查找另一种语言的文本生成模型。使用小组件测试并在pipeline中使用它! + + +## 推理 API +所有模型都可以使用 Inference API 直接通过浏览器进行测试,该 API 可在 [Hugging Face 网站](https://huggingface.co/)上找到。通过输入自定义文本并观察模型的输出,您可以直接在此页面上使用模型。 + +小组件形式的推理 API 也可作为付费产品使用,如果您的工作流程需要它,它会派上用场。有关更多详细信息,请参阅[定价页面](https://huggingface.co/pricing)。 + +## Mask filling +您将尝试的下一个pipeline是 **fill-mask**。此任务的想法是填充给定文本中的空白: +```python +from transformers import pipeline + +unmasker = pipeline("fill-mask") +unmasker("This course will teach you all about models.", top_k=2) +``` +```python out +[{'sequence': 'This course will teach you all about mathematical models.', + 'score': 0.19619831442832947, + 'token': 30412, + 'token_str': ' mathematical'}, + {'sequence': 'This course will teach you all about computational models.', + 'score': 0.04052725434303284, + 'token': 38163, + 'token_str': ' computational'}] +``` +**top_k** 参数控制要显示的结果有多少种。请注意,这里模型填充了特殊的< **mask** >词,它通常被称为掩码标记。其他掩码填充模型可能有不同的掩码标记,因此在探索其他模型时要验证正确的掩码字是什么。检查它的一种方法是查看小组件中使用的掩码。 + + +✏️**快来试试吧!**在 Hub 上搜索基于 bert 的模型并在推理 API 小组件中找到它的掩码。这个模型对上面pipeline示例中的句子预测了什么? + + +## 命名实体识别 +命名实体识别 (NER) 是一项任务,其中模型必须找到输入文本的哪些部分对应于诸如人员、位置或组织之类的实体。让我们看一个例子: +```python +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` +```python out +[{'entity_group': 'PER', 'score': 0.99816, 'word': 'Sylvain', 'start': 11, 'end': 18}, + {'entity_group': 'ORG', 'score': 0.97960, 'word': 'Hugging Face', 'start': 33, 'end': 45}, + {'entity_group': 'LOC', 'score': 0.99321, 'word': 'Brooklyn', 'start': 49, 'end': 57} +] +``` +在这里,模型正确地识别出 Sylvain 是一个人 (PER),Hugging Face 是一个组织 (ORG),而布鲁克林是一个位置 (LOC)。 + +我们在pipeline创建函数中传递选项 **grouped_entities=True** 以告诉pipeline将对应于同一实体的句子部分重新组合在一起:这里模型正确地将“Hugging”和“Face”分组为一个组织,即使名称由多个词组成。事实上,正如我们即将在下一章看到的,预处理甚至会将一些单词分成更小的部分。例如,**Sylvain** 分割为了四部分:**S、##yl、##va** 和 **##in**。在后处理步骤中,pipeline成功地重新组合了这些部分。 + + +✏️**快来试试吧!**在模型中心(hub)搜索能够用英语进行词性标注(通常缩写为 POS)的模型。这个模型对上面例子中的句子预测了什么? + + +## 问答系统 +问答pipeline使用来自给定上下文的信息回答问题: +```python +from transformers import pipeline + +question_answerer = pipeline("question-answering") +question_answerer( + question="Where do I work?", + context="My name is Sylvain and I work at Hugging Face in Brooklyn", +) +``` +```python out +{'score': 0.6385916471481323, 'start': 33, 'end': 45, 'answer': 'Hugging Face'} +klyn", +) + +``` +请注意,此pipeline通过从提供的上下文中提取信息来工作;它不会凭空生成答案。 + +## 文本摘要 +文本摘要是将文本缩减为较短文本的任务,同时保留文本中的主要(重要)信息。下面是一个例子: + +```python +from transformers import pipeline + +summarizer = pipeline("summarization") +summarizer( + """ + America has changed dramatically during recent years. Not only has the number of + graduates in traditional engineering disciplines such as mechanical, civil, + electrical, chemical, and aeronautical engineering declined, but in most of + the premier American universities engineering curricula now concentrate on + and encourage largely the study of engineering science. As a result, there + are declining offerings in engineering subjects dealing with infrastructure, + the environment, and related issues, and greater concentration on high + technology subjects, largely supporting increasingly complex scientific + developments. While the latter is important, it should not be at the expense + of more traditional engineering. + + Rapidly developing economies such as China and India, as well as other + industrial countries in Europe and Asia, continue to encourage and advance + the teaching of engineering. Both China and India, respectively, graduate + six and eight times as many traditional engineers as does the United States. + Other industrial countries at minimum maintain their output, while America + suffers an increasingly serious decline in the number of engineering graduates + and a lack of well-educated engineers. +""" +) +``` +```python out +[{'summary_text': ' America has changed dramatically during recent years . The ' + 'number of engineering graduates in the U.S. has declined in ' + 'traditional engineering disciplines such as mechanical, civil ' + ', electrical, chemical, and aeronautical engineering . Rapidly ' + 'developing economies such as China and India, as well as other ' + 'industrial countries in Europe and Asia, continue to encourage ' + 'and advance engineering .'}] +``` +与文本生成一样,您指定结果的 **max_length** 或 **min_length**。 + +## 翻译 +对于翻译,如果您在任务名称中提供语言对(例如“**translation_en_to_fr**”),则可以使用默认模型,但最简单的方法是在[模型中心(hub)](https://huggingface.co/models)选择要使用的模型。在这里,我们将尝试从法语翻译成英语: + +```python +from transformers import pipeline + +translator = pipeline("translation", model="Helsinki-NLP/opus-mt-fr-en") +translator("Ce cours est produit par Hugging Face.") +``` +```python out +[{'translation_text': 'This course is produced by Hugging Face.'}] + +``` + +与文本生成和摘要一样,您可以指定结果的 **max_length** 或 **min_length**。 + + + +✏️**快来试试吧!**搜索其他语言的翻译模型,尝试将前一句翻译成几种不同的语言。 + + + +到目前为止显示的pipeline主要用于演示目的。它们是为特定任务而编程的,不能对他们进行自定义的修改。在下一章中,您将了解 **pipeline()** 函数内部的内容以及如何进行自定义的修改。 \ No newline at end of file diff --git a/chapters/zh/chapter1/4.mdx b/chapters/zh/chapter1/4.mdx new file mode 100644 index 000000000..45641e71e --- /dev/null +++ b/chapters/zh/chapter1/4.mdx @@ -0,0 +1,172 @@ +# Transformers 是如何工作的? + +在本节中,我们将深入了解 Transformer 模型的架构。 + +## 一点Transformers的发展历史 + +以下是 Transformer 模型(简短)历史中的一些关键节点: + +
+A brief chronology of Transformers models. + +
+ +[Transformer 架构](https://arxiv.org/abs/1706.03762) 于 2017 年 6 月推出。原本研究的重点是翻译任务。随后推出了几个有影响力的模型,包括 + +- **2018 年 6 月**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), 第一个预训练的 Transformer 模型,用于各种 NLP 任务并获得极好的结果 + +- **2018 年 10 月**: [BERT](https://arxiv.org/abs/1810.04805), 另一个大型预训练模型,该模型旨在生成更好的句子摘要(下一章将详细介绍!) + +- **2019 年 2 月**: [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), GPT 的改进(并且更大)版本,由于道德问题没有立即公开发布 + +- **2019 年 10 月**: [DistilBERT](https://arxiv.org/abs/1910.01108), BERT 的提炼版本,速度提高 60%,内存减轻 40%,但仍保留 BERT 97% 的性能 + +- **2019 年 10 月**: [BART](https://arxiv.org/abs/1910.13461) 和 [T5](https://arxiv.org/abs/1910.10683), 两个使用与原始 Transformer 模型相同架构的大型预训练模型(第一个这样做) + +- **2020 年 5 月**, [GPT-3](https://arxiv.org/abs/2005.14165), GPT-2 的更大版本,无需微调即可在各种任务上表现良好(称为零样本学习) + +这个列表并不全面,只是为了突出一些不同类型的 Transformer 模型。大体上,它们可以分为三类: + +- GPT-like (也被称作自回归Transformer模型) +- BERT-like (也被称作自动编码Transformer模型) +- BART/T5-like (也被称作序列到序列的 Transformer模型) + +稍后我们将更深入地探讨这些分类。 + +## Transformers是语言模型 + +上面提到的所有 Transformer 模型(GPT、BERT、BART、T5 等)都被训练为语言模型。这意味着他们已经以无监督学习的方式接受了大量原始文本的训练。无监督学习是一种训练类型,其中目标是根据模型的输入自动计算的。这意味着不需要人工来标记数据! + +这种类型的模型可以对其训练过的语言进行统计理解,但对于特定的实际任务并不是很有用。因此,一般的预训练模型会经历一个称为*迁移学习*的过程。在此过程中,模型在给定任务上以监督方式(即使用人工注释标签)进行微调。 + +任务的一个例子是阅读 *n* 个单词的句子,预测下一个单词。这被称为因果语言建模,因为输出取决于过去和现在的输入。 + +
+Example of causal language modeling in which the next word from a sentence is predicted. + +
+ +另一个例子是*遮罩语言建模*,该模型预测句子中的遮住的词。 + +
+Example of masked language modeling in which a masked word from a sentence is predicted. + +
+ +## Transformer是大模型 + +除了一些特例(如 DistilBERT)外,实现更好性能的一般策略是增加模型的大小以及预训练的数据量。 + +
+Number of parameters of recent Transformers models +
+ +不幸的是,训练模型,尤其是大型模型,需要大量的数据,时间和计算资源。它甚至会对环境产生影响,如下图所示。 + +
+The carbon footprint of a large language model. + +
+ + + +Transformers是由一个团队领导的(非常大的)模型项目,该团队试图减少预训练对环境的影响,通过运行大量试验以获得最佳超参数。 + +想象一下,如果每次一个研究团队、一个学生组织或一家公司想要训练一个模型,都从头开始训练的。这将导致巨大的、不必要的浪费! + +这就是为什么共享语言模型至关重要:共享经过训练的权重,当遇见新的需求时在预训练的权重之上进行微调,可以降低训练模型训练的算力和时间消耗,降低全球的总体计算成本和碳排放。 + + +## 迁移学习 + + + +*预训练是*训练模型前的一个操作:随机初始化权重,在没有任何先验知识的情况下开始训练。 + +
+The pretraining of a language model is costly in both time and money. + +
+ +这种预训练通常是在非常大量的数据上进行的。因此,它需要大量的数据,而且训练可能需要几周的时间。 + +另一方面,*微调*是在模型经过预训练后完成的训练。要执行微调,首先需要获取一个经过预训练的语言模型,然后使用特定于任务的数据集执行额外的训练。等等,为什么不直接为最后的任务而训练呢?有几个原因: + +* 预训练模型已经在与微调数据集有一些相似之处的数据集上进行了训练。因此,微调过程能够利用模型在预训练期间获得的知识(例如,对于NLP问题,预训练模型将对您在任务中使用的语言有某种统计规律上的理解)。 +* 由于预训练模型已经在大量数据上进行了训练,因此微调需要更少的数据来获得不错的结果。 +* 出于同样的原因,获得好结果所需的时间和资源要少得多 + +例如,可以利用英语的预训练过的模型,然后在arXiv语料库上对其进行微调,从而形成一个基于科学/研究的模型。微调只需要有限的数据量:预训练模型获得的知识可以“迁移”到目标任务上,因此被称为*迁移学习*。 + +
+The fine-tuning of a language model is cheaper than pretraining in both time and money. + +
+ +因此,微调模型具有较低的时间、数据、财务和环境成本。迭代不同的微调方案也更快、更容易,因为与完整的预训练相比,训练的约束更少。 + +这个过程也会比从头开始的训练(除非你有很多数据)取得更好的效果,这就是为什么你应该总是尝试利用一个预训练的模型--一个尽可能接近你手头的任务的模型--并对其进行微调。 + +## 一般的体系结构 +在这一部分,我们将介绍Transformer模型的一般架构。如果你不理解其中的一些概念,不要担心;下文将详细介绍每个组件。 + + + +## 介绍 + +该模型主要由两个块组成: + +* **Encoder (左侧)**: 编码器接收输入并构建其表示(其特征)。这意味着对模型进行了优化,以从输入中获得理解。 +* **Decoder (右侧)**: 解码器使用编码器的表示(特征)以及其他输入来生成目标序列。这意味着该模型已针对生成输出进行了优化。 + +
+Architecture of a Transformers models + +
+ +这些部件中的每一个都可以独立使用,具体取决于任务: + +* **Encoder-only models**: 适用于需要理解输入的任务,如句子分类和命名实体识别。 +* **Decoder-only models**: 适用于生成任务,如文本生成。 +* **Encoder-decoder models** 或者 **sequence-to-sequence models**: 适用于需要根据输入进行生成的任务,如翻译或摘要。 + +在后面的部分中,我们将单独地深入研究这些体系结构。 + +## 注意力层 + +Transformer模型的一个关键特性是*注意力层*。事实上,介绍Transformer架构的文章的标题是[“注意力就是你所需要的”](https://arxiv.org/abs/1706.03762)! 我们将在课程的后面更加深入地探索注意力层;现在,您需要知道的是,这一层将告诉模型在处理每个单词的表示时,要特别重视您传递给它的句子中的某些单词(并且或多或少地忽略其他单词)。 + +把它放在语境中,考虑将文本从英语翻译成法语的任务。在输入“You like this course”的情况下,翻译模型还需要注意相邻的单词“You”,以获得单词“like”的正确翻译,因为在法语中,动词“like”的变化取决于主题。然而,句子的其余部分对于该词的翻译没有用处。同样,在翻译“this”时,模型也需要注意“course”一词,因为“this”的翻译不同,取决于相关名词是单数还是复数。同样,句子中的其他单词对于“this”的翻译也不重要。对于更复杂的句子(以及更复杂的语法规则),模型需要特别注意可能出现在句子中更远位置的单词,以便正确地翻译每个单词。 + +同样的概念也适用于与自然语言相关的任何任务:一个词本身有一个含义,但这个含义受语境的影响很大,语境可以是研究该词之前或之后的任何其他词(或多个词)。 + +现在您已经了解了注意力层的含义,让我们更仔细地了解Transformer架构。 + +## 原始的结构 + +Transformer架构最初是为翻译而设计的。在训练期间,编码器接收特定语言的输入(句子),而解码器需要输出对应语言的翻译。在编码器中,注意力层可以使用一个句子中的所有单词(正如我们刚才看到的,给定单词的翻译可以取决于它在句子中的其他单词)。然而,解码器是按顺序工作的,并且只能注意它已经翻译过的句子中的单词。例如,当我们预测了翻译目标的前三个单词时,我们将它们提供给解码器,然后解码器使用编码器的所有输入来尝试预测第四个单词。 + +为了在训练过程中加快速度(当模型可以访问目标句子时),解码器会被输入整个目标,但不允许获取到要翻译的单词(如果它在尝试预测位置2的单词时可以访问位置2的单词,解码器就会偷懒,直接输出那个单词,从而无法学习到正确的语言关系!)。例如,当试图预测第4个单词时,注意力层只能获取位置1到3的单词。 + +最初的Transformer架构如下所示,编码器位于左侧,解码器位于右侧: + +
+Architecture of a Transformers models + +
+ +注意,解码器块中的第一个注意力层关联到解码器的所有(过去的)输入,但是第二注意力层使用编码器的输出。因此,它可以访问整个输入句子,以最好地预测当前单词。这是非常有用的,因为不同的语言可以有语法规则将单词按不同的顺序排列,或者句子后面提供的一些上下文可能有助于确定给定单词的最佳翻译。 + +也可以在编码器/解码器中使用*注意力遮罩层*,以防止模型注意某些特殊单词。例如,在批处理句子时,填充特殊词使所有句子的长度一致。 + +## 架构与参数 + +在本课程中,当我们深入探讨Transformers模型时,您将看到 +架构、参数和模型 +。 这些术语的含义略有不同: + +* **架构**: 这是模型的骨架 -- 每个层的定义以及模型中发生的每个操作。 +* **Checkpoints**: 这些是将在给架构中结构中加载的权重。 +* **模型**: 这是一个笼统的术语,没有“架构”或“参数”那么精确:它可以指两者。为了避免歧义,本课程使用将使用架构和参数。 + +例如,BERT是一个架构,而 `bert-base-cased`, 这是谷歌团队为BERT的第一个版本训练的一组权重参数,是一个参数。我们可以说“BERT模型”和"`bert-base-cased`模型." diff --git a/chapters/zh/chapter1/5.mdx b/chapters/zh/chapter1/5.mdx new file mode 100644 index 000000000..7aa765ec2 --- /dev/null +++ b/chapters/zh/chapter1/5.mdx @@ -0,0 +1,17 @@ +# “编码器”模型 + + + +“编码器”模型指仅使用编码器的Transformer模型。在每个阶段,注意力层都可以获取初始句子中的所有单词。这些模型通常具有“双向”注意力,被称为自编码模型。 + +这些模型的预训练通常围绕着以某种方式破坏给定的句子(例如:通过随机遮盖其中的单词),并让模型寻找或重建给定的句子。 + +“编码器”模型最适合于需要理解完整句子的任务,例如:句子分类、命名实体识别(以及更普遍的单词分类)和阅读理解后回答问题。 + +该系列模型的典型代表有: + +- [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) diff --git a/chapters/zh/chapter1/6.mdx b/chapters/zh/chapter1/6.mdx new file mode 100644 index 000000000..2de4c44a6 --- /dev/null +++ b/chapters/zh/chapter1/6.mdx @@ -0,0 +1,17 @@ +# “解码器”模型 + + + +“解码器”模型通常指仅使用解码器的Transformer模型。在每个阶段,对于给定的单词,注意力层只能获取到句子中位于将要预测单词前面的单词。这些模型通常被称为自回归模型。 + +“解码器”模型的预训练通常围绕预测句子中的下一个单词进行。 + +这些模型最适合于涉及文本生成的任务。 + +该系列模型的典型代表有: + + +- [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) +- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) +- [GPT-2](https://huggingface.co/transformers/model_doc/gpt2.html) +- [Transformer XL](https://huggingface.co/transformers/model_doc/transformerxl.html) diff --git a/chapters/zh/chapter1/7.mdx b/chapters/zh/chapter1/7.mdx new file mode 100644 index 000000000..99dc00eea --- /dev/null +++ b/chapters/zh/chapter1/7.mdx @@ -0,0 +1,16 @@ +# 序列到序列模型 + + + +编码器-解码器模型(也称为序列到序列模型)同时使用Transformer架构的编码器和解码器两个部分。在每个阶段,编码器的注意力层可以访问初始句子中的所有单词,而解码器的注意力层只能访问位于输入中将要预测单词前面的单词。 + +这些模型的预训练可以使用训练编码器或解码器模型的方式来完成,但通常涉及更复杂的内容。例如,[T5](https://huggingface.co/t5-base)通过将文本的随机跨度(可以包含多个单词)替换为单个特殊单词来进行预训练,然后目标是预测该掩码单词替换的文本。 + +序列到序列模型最适合于围绕根据给定输入生成新句子的任务,如摘要、翻译或生成性问答。 + +该系列模型的典型代表有: + +- [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) diff --git a/chapters/zh/chapter1/8.mdx b/chapters/zh/chapter1/8.mdx new file mode 100644 index 000000000..707731892 --- /dev/null +++ b/chapters/zh/chapter1/8.mdx @@ -0,0 +1,31 @@ +# Bias and limitations + + + +如果您打算在正式的项目中使用经过预训练或经过微调的模型。请注意:虽然这些模型是很强大,但它们也有局限性。其中最大的一个问题是,为了对大量数据进行预训练,研究人员通常会搜集所有他们能找到的内容,中间可能夹带一些意识形态或者价值观的刻板印象。 + +为了快速解释清楚这个问题,让我们回到一个使用BERT模型的pipeline的例子: + +```python +from transformers import pipeline + +unmasker = pipeline("fill-mask", model="bert-base-uncased") +result = unmasker("This man works as a [MASK].") +print([r["token_str"] for r in result]) + +result = unmasker("This woman works as a [MASK].") +print([r["token_str"] for r in result]) +``` + +```python out +['lawyer', 'carpenter', 'doctor', 'waiter', 'mechanic'] +['nurse', 'waitress', 'teacher', 'maid', 'prostitute'] +``` +当要求模型填写这两句话中缺少的单词时,模型给出的答案中,只有一个与性别无关(服务员/女服务员)。其他职业通常与某一特定性别相关,妓女最终进入了模型中与“女人”和“工作”相关的前五位。尽管BERT是使用经过筛选和清洗后,明显中立的数据集上建立的的Transformer模型,而不是通过从互联网上搜集数据(它是在[Wikipedia 英文](https://huggingface.co/datasets/wikipedia)和[BookCorpus](https://huggingface.co/datasets/bookcorpus)数据集)。 + +因此,当您使用这些工具时,您需要记住,使用的原始模型的时候,很容易生成性别歧视、种族主义或恐同内容。这种固有偏见不会随着微调模型而使消失。 \ No newline at end of file diff --git a/chapters/zh/chapter1/9.mdx b/chapters/zh/chapter1/9.mdx new file mode 100644 index 000000000..16c5ab6ad --- /dev/null +++ b/chapters/zh/chapter1/9.mdx @@ -0,0 +1,11 @@ +# 总结 + +在本章中,您了解了如何使用来自🤗Transformers的函数pipeline()处理不同的NLP任务。您还了解了如何在模型中心(hub)中搜索和使用模型,以及如何使用推理API直接在浏览器中测试模型。 + +我们讨论了Transformer模型如何在应用层上工作,并讨论了迁移学习和微调的重要性。您可以使用完整的体系结构,也可以仅使用编码器或解码器,具体取决于您要解决的任务类型。下表总结了这一点: + +| 模型 | 示例 | 任务| +| ---- | ---- |----| +| 编码器 | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa |句子分类、命名实体识别、从文本中提取答案| +| 解码器 | CTRL, GPT, GPT-2, Transformer XL |文本生成| +| 编码器-解码器 | BART, T5, Marian, mBART |文本摘要、翻译、生成问题的回答| \ No newline at end of file From c2cf747d3fcd72158c5000a6f0d756d900b3a2a5 Mon Sep 17 00:00:00 2001 From: lewtun Date: Wed, 20 Apr 2022 18:54:56 +0200 Subject: [PATCH 08/51] Bump release 4 (#133) --- .github/workflows/build_documentation.yml | 2 +- .github/workflows/build_pr_documentation.yml | 2 +- README.md | 5 +- chapters/de/TRANSLATING.txt | 46 ++ chapters/de/_toctree.yml | 9 + chapters/de/chapter0/1.mdx | 110 +++++ chapters/de/glossary/1.mdx | 87 ++++ chapters/fa/TRANSLATING.txt | 67 +++ chapters/fa/_toctree.yml | 2 + chapters/fa/chapter2/2.mdx | 439 +++++++++++++++++++ chapters/fa/glossary/1.mdx | 109 +++-- chapters/fr/_toctree.yml | 26 +- chapters/fr/chapter1/1.mdx | 51 +++ chapters/fr/chapter1/10.mdx | 254 +++++++++++ chapters/fr/chapter1/2.mdx | 21 + chapters/fr/chapter1/3.mdx | 329 ++++++++++++++ chapters/fr/chapter1/4.mdx | 170 +++++++ chapters/fr/chapter1/5.mdx | 17 + chapters/fr/chapter1/6.mdx | 16 + chapters/fr/chapter1/7.mdx | 16 + chapters/fr/chapter1/8.mdx | 32 ++ chapters/fr/chapter1/9.mdx | 11 + chapters/ja/_toctree.yml | 4 + chapters/ja/chapter0/1.mdx | 110 +++++ chapters/th/_toctree.yml | 19 +- chapters/th/chapter2/1.mdx | 2 +- chapters/th/chapter2/2.mdx | 353 +++++++++++++++ chapters/th/chapter2/3.mdx | 228 ++++++++++ chapters/th/chapter2/4.mdx | 240 ++++++++++ chapters/th/chapter2/5.mdx | 338 ++++++++++++++ chapters/th/chapter2/6.mdx | 165 +++++++ chapters/th/chapter2/7.mdx | 13 + chapters/th/chapter2/8.mdx | 304 +++++++++++++ 33 files changed, 3547 insertions(+), 50 deletions(-) create mode 100644 chapters/de/TRANSLATING.txt create mode 100644 chapters/de/_toctree.yml create mode 100644 chapters/de/chapter0/1.mdx create mode 100644 chapters/de/glossary/1.mdx create mode 100644 chapters/fa/TRANSLATING.txt create mode 100644 chapters/fa/chapter2/2.mdx create mode 100644 chapters/fr/chapter1/1.mdx create mode 100644 chapters/fr/chapter1/10.mdx create mode 100644 chapters/fr/chapter1/2.mdx create mode 100644 chapters/fr/chapter1/3.mdx create mode 100644 chapters/fr/chapter1/4.mdx create mode 100644 chapters/fr/chapter1/5.mdx create mode 100644 chapters/fr/chapter1/6.mdx create mode 100644 chapters/fr/chapter1/7.mdx create mode 100644 chapters/fr/chapter1/8.mdx create mode 100644 chapters/fr/chapter1/9.mdx create mode 100644 chapters/ja/_toctree.yml create mode 100644 chapters/ja/chapter0/1.mdx create mode 100644 chapters/th/chapter2/2.mdx create mode 100644 chapters/th/chapter2/3.mdx create mode 100644 chapters/th/chapter2/4.mdx create mode 100644 chapters/th/chapter2/5.mdx create mode 100644 chapters/th/chapter2/6.mdx create mode 100644 chapters/th/chapter2/7.mdx create mode 100644 chapters/th/chapter2/8.mdx diff --git a/.github/workflows/build_documentation.yml b/.github/workflows/build_documentation.yml index 1b7f31778..400030b38 100644 --- a/.github/workflows/build_documentation.yml +++ b/.github/workflows/build_documentation.yml @@ -14,6 +14,6 @@ jobs: package: course path_to_docs: course/chapters/ additional_args: --not_python_module - languages: ar bn en es fa fr he hi ko pt ru th tr zh + languages: ar bn de en es fa fr he hi ja ko pt ru th tr zh secrets: token: ${{ secrets.HUGGINGFACE_PUSH }} \ No newline at end of file diff --git a/.github/workflows/build_pr_documentation.yml b/.github/workflows/build_pr_documentation.yml index 2e3c06441..1aacd2e41 100644 --- a/.github/workflows/build_pr_documentation.yml +++ b/.github/workflows/build_pr_documentation.yml @@ -16,5 +16,5 @@ jobs: package: course path_to_docs: course/chapters/ additional_args: --not_python_module - languages: ar bn en es fa fr he hi ko pt ru th tr zh + languages: ar bn de en es fa fr he hi ja ko pt ru th tr zh hub_base_path: https://moon-ci-docs.huggingface.co/course diff --git a/README.md b/README.md index 2b70941d7..7210cdb1e 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,12 @@ This repo contains the content that's used to create the **[Hugging Face course] | Language | Source | Authors | |:-------------------------------------------------------|:-----------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | [English](https://huggingface.co/course/en/chapter1/1) | [`chapters/en`](https://github.com/huggingface/course/tree/main/chapters/en) | [@sgugger](https://github.com/sgugger), [@lewtun](https://github.com/lewtun), [@LysandreJik](https://github.com/LysandreJik), [@Rocketknight1](https://github.com/Rocketknight1), [@sashavor](https://github.com/sashavor), [@osanseviero](https://github.com/osanseviero), [@SaulLu](https://github.com/SaulLu), [@lvwerra](https://github.com/lvwerra) | +| [Chinese (simplified)](https://huggingface.co/course/zh/chapter1/1) (WIP) | [`chapters/zh`](https://github.com/huggingface/course/tree/main/chapters/zh) | [@zhlhyx](https://github.com/zhlhyx), [petrichor1122](https://github.com/petrichor1122), [@1375626371](https://github.com/1375626371) | +| [Hindi](https://huggingface.co/course/hi/chapter1/1) (WIP) | [`chapters/hi`](https://github.com/huggingface/course/tree/main/chapters/hi) | [@pandyaved98](https://github.com/pandyaved98) | | [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) | +| [Persian](https://huggingface.co/course/fa/chapter1/1) (WIP) | [`chapters/fa`](https://github.com/huggingface/course/tree/main/chapters/fa) | [@jowharshamshiri](https://github.com/jowharshamshiri), [@schoobani](https://github.com/schoobani) | | [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) | -| [Spanish](https://huggingface.co/course/es/chapter1/1) (WIP) | [`chapters/es`](https://github.com/huggingface/course/tree/main/chapters/es) | [@camartinezbu](https://github.com/camartinezbu) | +| [Spanish](https://huggingface.co/course/es/chapter1/1) (WIP) | [`chapters/es`](https://github.com/huggingface/course/tree/main/chapters/es) | [@camartinezbu](https://github.com/camartinezbu), [@munozariasjm](https://github.com/munozariasjm) | | [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) | ### Translating the course into your language diff --git a/chapters/de/TRANSLATING.txt b/chapters/de/TRANSLATING.txt new file mode 100644 index 000000000..053f5930d --- /dev/null +++ b/chapters/de/TRANSLATING.txt @@ -0,0 +1,46 @@ +1. We use the informal "you" (i.e. "Du" instead of "Sie") to keep the tone jovial. + However, don't use slang or local language, so use the correct form of "als", + and "wie" rather than the locally accepted form. + +2. Don't translate industry-accepted acronyms. e.g. TPU or GPU. + +3. The German language accepts English words especially in modern contexts more than + many other languages (i.e. Anglicisms). Check for the correct usage of terms in + computer science and commonly used terms in other publications. + +4. Beware of "false friends" in German and English translations. Translators are trained + for years to specifically avoid false English friends and avoid anglicised translations. + e.g. "Daten" is "data", but "dates" is "Termine". For more examples refer to: + https://lal.de/blog/false-friends-falsche-freunde/ + +5. Keep voice active and consistent. Don't overdo it but try to avoid a passive voice. + +6. 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. + +7. Keep POV consistent. + +8. Smaller sentences are better sentences. Apply with nuance. + +9. If translating a technical word, keep the choice of German translation consistent. + This does not apply for non-technical choices, as in those cases variety actually + helps keep the text engaging. + +10. This is merely a translation. Don't add any technical/contextual information + not present in the original text. Also don't leave stuff out. The creative + choices in composing this information were the original authors' to make. + Our creative choices are in doing a quality translation. + +11. Be exact when choosing equivalents for technical words. Package is package. + Library is library. Don't mix and match. + +12. Library names are kept in the original forms, e.g. "🤗 Datasets", however, + the word dataset in a sentence gets a translation to "Datensatz". + +13. As a style choice prefer the imperative over constructions with auxiliary words + to avoid unnecessary verbosity and addressing of the reader, which seems + unnatural in German. e.g. "Siehe Kapitel X" - "See chapter X" instead of + "Dies kannst du in Kapitel X sehen" - "You can see this in chapter X". + +14. Be careful with sentences using "wir", which can seem unnatural at best and + condescending at worst in German. \ No newline at end of file diff --git a/chapters/de/_toctree.yml b/chapters/de/_toctree.yml new file mode 100644 index 000000000..a5e9d0934 --- /dev/null +++ b/chapters/de/_toctree.yml @@ -0,0 +1,9 @@ +- title: 0. Einrichtung + sections: + - local: chapter0/1 + title: Einführung + +- title: Wörterverzeichnis + sections: + - local: glossary/1 + title: Wörterverzeichnis \ No newline at end of file diff --git a/chapters/de/chapter0/1.mdx b/chapters/de/chapter0/1.mdx new file mode 100644 index 000000000..9d1610de4 --- /dev/null +++ b/chapters/de/chapter0/1.mdx @@ -0,0 +1,110 @@ +# Einführung + +Willkommen zum Hugging-Face-Kurs! Die vorliegende Einführung wird dir dabei helfen, deine Arbeitsumgebung einzurichten. Wenn du den Kurs gerade erst beginnst, empfehlen wir dir, zuerst einen Überblick über [Kapitel 1](/course/chapter1) zu gewinnen und dann wieder hierher zurückzukommen und deine Umgebung einzurichten, damit du den Code selbst ausprobieren kannst. + +Alle Bibliotheken, die wir in diesem Kurs verwenden werden, sind als Python-Pakete verfügbar. Daher zeigen wir dir hier, wie du eine Python-Umgebung einrichtest und die benötigten Bibliotheken installierst. + +Wir stellen zwei verschiedene Möglichkeiten vor, wie du deine Arbeitsumgebung einrichten kannst: entweder über ein Colab-Notebook oder über eine virtuelle Python-Umgebung. Du kannst dich für die Variante entscheiden, die dir am meisten zusagt. Anfängern empfehlen wir dringend, mit einem Colab-Notebook zu beginnen. + +Wir werden nicht auf das Windows-System eingehen. Wenn du auf einem Windows-System arbeitest, empfehlen wir dir, ein Colab-Notebook zu verwenden. Wenn du eine Linux-Distribution oder macOS verwendest, kannst du beide der hier beschriebenen Ansätze nutzen. + +Für den Großteil des Kurses ist es erforderlich, dass du ein Konto bei Hugging Face hast. Wir raten dir, am besten gleich eines zu erstellen: [Konto erstellen](https://huggingface.co/join). + +## Verwendung eines Google-Colab-Notebooks + +Ein Colab-Notebook zu verwenden, ist die einfachste Lösung: Starte einfach ein Notebook in deinem Browser und fange direkt an zu programmieren! + +Falls du mit Colab noch nicht vertraut sein solltest, empfehlen wir dir, zunächst mit der [bereitstehenden Einführung](https://colab.research.google.com/notebooks/intro.ipynb) zu beginnen. Colab ermöglicht die Verwendung von beschleunigter Hardware, wie GPUs oder TPUs, und ist für kleinere Workloads kostenlos. + +Sobald du dich in Colab ausreichend zurechtfindest, kannst du ein neues Notebook anlegen und mit der Einrichtung beginnen: + +
+An empty colab notebook +
+ +Im nächsten Schritt installieren wir die Bibliotheken, die wir in diesem Kurs verwenden werden. Für die Installation werden wir die Paketverwaltung für Python, `pip`, verwenden. Um Systembefehle in Notebooks ausführen zu können, musst du ihnen das Zeichen `!` voranstellen. Dementsprechend kannst du die 🤗 Transformers-Bibliothek wie folgt installieren: + +``` +!pip install transformers +``` + +Wenn du gleich sicherstellen möchtest, dass das Paket korrekt installiert wurde, kannst du es einfach in deiner Python-Laufzeitumgebung importieren: + +``` +import transformers +``` + +
+A gif showing the result of the two commands above: installation and import +
+ +Damit wird eine sehr abgespeckte Version der 🤗 Transformers-Bibliothek installiert. Insbesondere werden keine spezifischen Frameworks für maschinelles Lernen (wie PyTorch oder TensorFlow) installiert. Da wir viele verschiedene Funktionen der Bibliothek verwenden werden, empfehlen wir die Installation der Entwicklungs- bzw. Development-Version, die alle erforderlichen Abhängigkeiten für so ziemlich jeden erdenklichen Anwendungsfall enthält: + +``` +!pip install transformers[sentencepiece] +``` + +Dies dauert zwar etwas länger, aber anschließend bist du bereit für den Rest des Kurses! + +## Verwendung einer virtuellen Python-Umgebung + +Wenn du lieber eine virtuelle Python-Umgebung verwenden möchtest, musst du zunächst Python auf deinem System installieren. Für den Anfang empfehlen wir [diese Anleitung](https://realpython.com/installing-python/). + +Sobald du Python installiert hast, solltest du in der Lage sein, Python-Befehle in deinem Terminal auszuführen. Zunächst kannst du den folgenden Befehl ausführen, um sicherzustellen, dass Python korrekt installiert ist, bevor du mit den nächsten Schritten fortfährst: `python --version`. Dies sollte die Python-Version ausgeben, die jetzt auf deinem System installiert ist. + +Wenn du einen Python-Befehl in deinem Terminal ausführst, z. B. `python --version`, solltest du das Programm, das deinen Befehl ausführt, als Hauptinstallation von Python auf deinem System betrachten. Wir empfehlen, diese Hauptinstallation frei von Paketen zu halten und für jede Anwendung, an der du arbeitest, eine eigene Umgebung zu erstellen. Auf diese Weise kann jede Anwendung ihre eigenen Abhängigkeiten und Pakete haben, und du musst dir keine Gedanken über mögliche Kompatibilitätsprobleme mit anderen Anwendungen machen. + +In Python wird dies mit [*virtuellen Umgebungen*](https://docs.python.org/3/tutorial/venv.html) bewerkstelligt. Das sind in sich geschlossene Verzeichnisbäume, die jeweils eine Python-Installation mit einer bestimmten Python-Version sowie alle Pakete enthalten, die die Anwendung benötigt. Eine solche virtuelle Umgebung kann mit verschiedenen Werkzeugen erstellt werden. Wir werden dafür auf das offizielle Python-Paket [`venv`](https://docs.python.org/3/library/venv.html#module-venv) zurückgreifen. + +Erstelle zunächst das Verzeichnis, in dem du deine Anwendung unterbringen möchtest - zum Beispiel könntest du ein neues Verzeichnis namens *transformers-course* als Unterverzeichnis deines Hauptverzeichnisses (*home*) anlegen: + +``` +mkdir ~/transformers-course +cd ~/transformers-course +``` + +Erstelle nun in diesem Verzeichnis mithilfe des Python-Moduls `venv` eine virtuelle Umgebung: + +``` +python -m venv .env +``` + +Du solltest jetzt ein Verzeichnis namens *.env* in deinem ansonsten leeren Ordner haben: + +``` +ls -a +``` + +```out +. .. .env +``` + +Mit den Skripten "activate" und "deactivate" kannst du in deine virtuelle Umgebung hinein- und herauswechseln: + +``` +# Aktivieren der virtuellen Umgebung +source .env/bin/activate + +# Deaktivieren der virtuellen Umgebung +source .env/bin/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! + +``` +which python +``` + +```out +/home//transformers-course/.env/bin/python +``` + +### Installieren von Abhängigkeiten + +Wie im vorherigen Abschnitt zur Verwendung von Google-Colab-Instanzen musst du nun noch die Pakete installieren, die du zum Fortfahren benötigst. Auch hier empfehlen wir, die Entwicklungsversion von 🤗 Transformers mithilfe des Paketverwaltungsprogramm `pip` zu installieren: + +``` +pip install "transformers[sentencepiece]" +``` + +So, nun bist du startklar und kannst loslegen! diff --git a/chapters/de/glossary/1.mdx b/chapters/de/glossary/1.mdx new file mode 100644 index 000000000..1debfc714 --- /dev/null +++ b/chapters/de/glossary/1.mdx @@ -0,0 +1,87 @@ +# Wörterverzeichnis + +| Original | Übersetzung | +|-----------------------------|---------------------------------| +| Abstraction | Abstraktion | +| Backward Pass | Rückwärtsalgorithmus berechnen | +| Batch | Batch | +| Chapter | Kapitel | +| Class | Klasse | +| Code | Code | +| Colab Notebook | Colab Notebook | +| Command | Befehl | +| Configuration | Konfiguration | +| Course | Kurs | +| Dependency | Abhängigkeitsbeziehung | +| Deployment | Deployment | +| Development | Entwicklung | +| Distribution | Verteilung | +| Download | Download | +| Feature | Feature | +| Folder | Ordner | +| Forward Pass | Vorwärtsalgorithmus berechnen | +| Function | Funktion | +| Google | Google | +| Hugging Face | Hugging Face | +| Incompatibility | Inkompatibilität | +| Inference | Inferenz | +| Library | Bibliothek | +| Linux | Linux | +| Load | laden | +| macOS | macOS | +| Model | Modell | +| Model Hub | Model Hub | +| Module | Modul | +| Natural Language Processing | Computerlinguistik | +| Package | Paket | +| Package Manager | Paektverwaltung | +| Parameter | Parameter | +| Python | Python | +| Pytorch | Pytorch | +| Save | speichern | +| Script | Script | +| Self-Contained | in sich abgeschlossen | +| Setup | Installation | +| TensorFlow | Tensorflow | +| Terminal | Terminal | +| Tokenizer | Tokenizer | +| Train | Training | +| Transformer | Transformer | +| Virtual Environment | Virtuelle Umgebung | +| Windows | Windows | +| Working Environment | Arbeitsumgebung | +| Workload | Auslastung | +| Workspace | Workspace | + + +## Abkürzungen + +| Original | Übersetzung | +|-----------|-------------| +| NLP | CL | +| API | API | +| GPU | GPU | +| TPU | TPU | +| ML | ML | + +## Notes + +Please refer to [TRANSLATING.txt](/chapters/de/TRANSLATING.txt) for a translation guide. Here are some excerpts relevant to the glossary: + +- 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. + +- The German language accepts English words especially in modern contexts more + than many other languages (i.e. Anglicisms). 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 German 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 package. + Library is library. Don't mix and match. + diff --git a/chapters/fa/TRANSLATING.txt b/chapters/fa/TRANSLATING.txt new file mode 100644 index 000000000..17e0af3b1 --- /dev/null +++ b/chapters/fa/TRANSLATING.txt @@ -0,0 +1,67 @@ +1. Use this style guide for Persian. https://apll.ir/wp-content/uploads/2018/10/D-1394.pdf + We deviate from this style guide in the following ways: + - Use 'ء' as a possessive suffix instead of 'ی' + +2. Don't translate industry-accepted acronyms. e.g. TPU or GPU. + +3. Translate acronyms used for convenience in English to full Persian form. e.g. ML + +4. Keep voice active and consistent. Don't overdo it but try to avoid a passive voice. + +5. 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. + +6. Keep POV consistent. + +7. Smaller sentences are better sentences. Apply with nuance. + +8. If translating a technical word, keep the choice of Persian equivalent consistent. + If workspace is mohit-e-kari don't just switch to faza-ye-kari. This ofc + does not apply for non-technical choices, as in those cases variety actually + helps keep the text engaging. + +9. This is merely a translation. Don't add any technical/contextual information + not present in the original text. Also don't leave stuff out. The creative + choices in composing this information were the original authors' to make. + Our creative choices are in doing a quality translation. + +10. Note on translating indefinite articles(a, an) to Persian. Persian syntax does + not have these. Common problem in translations is using + the equivalent of 'One'("Yek", "Yeki") as a direct word-for-word substitute + which makes for ugly Persian. Be creative with Persian word order to avoid this. + + For example, the sentence "we use a library" should be translated as + "ما از کتابخانه‌ای استفاده می‌کنیم" and not "ما از یک کتابخانه استفاده می‌کنیم". + "Yek" here implies a number(1) to the Persian reader, but the original text + was emphasizing the indefinite nature of the library and not its count. + +11. Be exact when choosing equivalents for technical words. Package is package. + Library is library. Don't mix and match. + +12. Library names are not brand names, nor are they concepts, so no need to + transliterate. They are shorter namespaces so just use the English form. + For example we will transliterate 'Tokenizer' when it is used to indicate + a concept but not when it is the name of a library. + +13. You need to surround the entire doc with a
, also separately + for lists. They render in LTR with the current version of Huggingface docs + if you don't do this and the preview will look ugly. Refer to our previously + submitted pages. + + Also with function names that include ellipses you need to surround them with + a SPAN. i.e. pipeline() + These are stopgap measures for the preview to render correctly. + +14. Put namespaces in backticks. Library names are shorter namespaces. + +15. Persian sentences follow the format Subject-Object-Verb. That means it is + generally expected that the sentences end in a verb. Again there is a lot of + gray area here and we need to apply nuance. But try to keep that in mind. + For example maybe don't end sentences in "هستند یا نه". Maybe, bc if you + do, that is your creative choice and we respect that. + +16. If a Persian equivalent for a word in the glossary has diacritics, they + are important and should be repeated in every occurrence of the word. + While we do want to facilitate our reader's access to the English source + material by introducing loanwords and transliterating where we can, we + also want to avoid confusion in the correct pronunciation of loanwords. diff --git a/chapters/fa/_toctree.yml b/chapters/fa/_toctree.yml index 179c9d87a..2e60c0d2a 100644 --- a/chapters/fa/_toctree.yml +++ b/chapters/fa/_toctree.yml @@ -12,6 +12,8 @@ sections: - local: chapter2/1 title: مقدمه + - local: chapter2/2 + title: پشت صحنه خط‌تولید - title: واژه‌نامه sections: diff --git a/chapters/fa/chapter2/2.mdx b/chapters/fa/chapter2/2.mdx new file mode 100644 index 000000000..534a03758 --- /dev/null +++ b/chapters/fa/chapter2/2.mdx @@ -0,0 +1,439 @@ + + +
+# پشت صحنه خط‌تولید + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + +این اولین بخشی است که محتوای آن بسته به اینکه از پایتورچ یا تِنسورفِلو استفاده می کنید کمی متفاوت است. از سویچ بالای صفحه برای انتخاب پلتفرمی که ترجیح می دهید استفاده کنید! + + + +{#if fw === 'pt'} + +{:else} + +{/if} + +بگذارید با یک مثال کامل شروع کنیم که در آن نگاهی می‌اندازیم به آنچه که در پشت صحنه اتفاق می افتد هنگامی که این کد را در [Chapter 1](/course/chapter1) اجرا کردیم: + +
+ +```python +from transformers import pipeline + +classifier = pipeline("sentiment-analysis") +classifier( + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] +) +``` + +
+ +این خروجی را دریافت کردیم: + +
+ +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}, + {'label': 'NEGATIVE', 'score': 0.9994558095932007}] +``` + +
+ + +همان طور که در در فصل اول دیدیم، این خط تولید از سه مرحله تشکیل شده است: پیش‌پردازش، پردازش ورودی‌ها در مدل و پس پردازش. + +
+The full NLP pipeline: tokenization of text, conversion to IDs, and inference through the Transformer model and the model head. + +
+ +به صورت خلاصه هرکدام از این مراحل را بررسی می‌کنیم. + +## پیش‌پردازش با توکِنایزر + +مثل شبکه‌های عصبی دیگر، مدل‌های ترنسفورمر هم نمی‌توانند نوشته خام را پردازش کنند. پس اولین قدم در خط تولید ما تبدیل نوشته خام ورودی به اعدادی است که مدل می‌فهمد. برای این کار از یک *توکِنایزر* استفاده می‌کنیم، که مسئولیت‌های زیر را بر عهده دارد: + +- شکستن نوشته به کلمات، قطعات کوچکتر کلمه و علامت‌ها(مانند علائم نگارشی) که به آنها ‌*توکِن* می‌گوییم. +- انتخاب یک عدد صحیح معادل برای هر توکِن. +- اضافه‌کردن ورودی‌های دیگری که ممکن است برای مدل مفید باشند. + +همه مراحل این پیش‌پردازش باید دقیقا همان‌طور انجام شوند که قبلا هنگام تعلیم مدل انجام شده است. این اطلاعات در [Model Hub](https://huggingface.co/models) موجود است و توسط تابع `from_pretrained()` از کلاس `AutoTokenizer` دانلود می‌شود. با استفاده از نام کامل مدل که شامل نقطه تعلیم است، این تابع به صورت خودکار داده‌های توکِنایزر مدل را دریافت نموده و در سیستم شما ذخیره می‌کند. به این ترتیب این داده‌ها فقط بار اولی که کد زیر را اجرا می‌کنید دانلود می‌شوند. + +نقطه تعلیم پیش‌فرض برای خط‌تولید `تحلیل احساسات` `distilbert-base-uncased-finetuned-sst-2-english` نام دارد. صفحه توضیحات این مدل را می توانید در [اینجا مشاهده کنید](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english). با اجرای کد زیر آن را دانلود می کنیم: + + + + +
+ +```python +from transformers import AutoTokenizer + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` + +
+ +پس از دریافت توکِنایزر، می توانیم جملات خود را مستقیماً وارد آن کنیم و دیکشنری خروجی را دریافت کنیم که آماده است تا به عنوان ورودی مدل مورد استفاده قرار گیرد! تنها کار باقی مانده تبدیل لیست ID‌های ورودی به تِنسور است. شما می‌توانید از ترنسفورمرهای هاگینگ‌فِیس بدون اطلاع از اینکه کدام فریمورک یادگیری ماشین در پشت صحنه درگیر می‌شود استفاده کنید. ممکن است از پایتورچ، تِنسورفِلو یا حتی فلَکس برای بعضی مدل‌ها استفاده شده باشد. با این وجود مدل‌های ترسفورمر فقط *تِنسور*‌ها را به عنوان ورودی قبول می‌کنند. اگر این بار اولی است که با کلمه تِنسور برخورد دارید، می‌توانید آن‌ها را به عنوان آرایه‌های NumPy تصور کنید. این آرایه‌ها می توانند عددی(تک بعدی)، برداری(یک بعدی)، ماتریس(دو بعدی) یا با ابعاد بیشتر باشند. آن‌ها در واقع تِنسور هستند و تِنسورها در فریمورک‌های یادگیری ماشین رفتاری شبیه به آرایه‌های NumPy دارند و به همان سادگی هم ساخته می‌شوند. + +برای مشخص کردن نوع تِنسوری که می‌خواهیم به عنوان خروجی دریافت کنیم(پایتورچ، تِنسورفِلو یا NumPy ساده)، از آرگومان `return_tensors` استفاده می‌کنیم: + +
+ +{#if fw === 'pt'} +```python +raw_inputs = [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", +] +inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="pt") +print(inputs) +``` +{:else} +```python +raw_inputs = [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", +] +inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="tf") +print(inputs) +``` +{/if} + +
+ + +هنوز لازم نیست نگران آرگومان‌های `padding` و `truncation` باشید؛ زیرا بعدتر به آنها خواهیم پرداخت. مسئله اصلی که باید به به خاطر بسپارید، امکان دادن یک جمله یا آرایه‌ای از جمله‌ها به عنوان ورودی و مشخص کردن نوع تِنسورهای خروجی است. اگر نوع خروجی را مشخص نکنید، لیستی از لیست‌ها را دریافت خواهید کرد. + +{#if fw === 'pt'} + +خروجی تِنسورهای پایتورچ به این شکل است: + +
+ +```python out +{ + 'input_ids': tensor([ + [ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012, 102], + [ 101, 1045, 5223, 2023, 2061, 2172, 999, 102, 0, 0, 0, 0, 0, 0, 0, 0] + ]), + '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] + ]) +} +``` + +
+ +{:else} + +خروجی تِنسورهای تِنسورفِلو به این شکل است: + +
+ +```python out +{ + 'input_ids': , + 'attention_mask': +} +``` + +
+ +{/if} + + +خروجی خود یک دیکشنری با دو کلید `input_ids` و `attention_mask` است. `input_ids` دو ردیف عدد صحیح(یک ردیف برای هر جمله) که شناسه‌های منحصر به فرد توکِن‌های هر جمله هستند. `attention_mask` را بعدتر در همین فصل توضیح خواهیم داد. + +## گذر از مدل + +{#if fw === 'pt'} + +می توانیم مدل از پیش تعلیم دیده را، همانند آن چه در مورد توکِنایزر انجام شد، دانلود کنیم. ترنسوفورمرهای هاگینگ‌فِیس کلاس `AutoModel` را ارا‌ئه می‌دهد که تابعی به نام `from_pretrained()` هم دارد: + +
+ +```python +from transformers import AutoModel + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = AutoModel.from_pretrained(checkpoint) +``` + +
+ +{:else} + +می توانیم مدل از پیش تعلیم دیده را، همانند آن چه در مورد توکِنایزر انجام شد، دانلود کنیم. ترنسوفورمرهای هاگینگ‌فِیس کلاس `TFAutoModel` را ارا‌ئه می‌دهد که تابعی به نام `from_pretrained()` هم دارد: + +
+ +```python +from transformers import TFAutoModel + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = TFAutoModel.from_pretrained(checkpoint) +``` + +
+ +{/if} + +در این قطعه کد، همان نقطه تعلیمی که در خط تولیدمان قبلا استفاده کردیم را دانلود کرده(که البته باید الان دانلود شده باشد و در سیستم شما موجود است پس نیازی به دانلود مجدد ندارد) و مدلی جدید با آن می‌سازیم. + +این معماری تنها شامل ماژول پایهٔ `Transformer` است: با دریافت ورودی،‌ *وضعیت پنهان* را در خروجی تحویل می‌دهد. به این وضعیت‌های پنهان *فیچر* هم می‌گوییم. برای هر ورودی مدل، برداری چندین ‌بعدی که معادل *درک کلی مدل ترنسفورمر از آن ورودی* است را دریافت می‌کنیم. + +نگران نباشید اگر درک این مفاهیم سخت است. همه آنها را بعدتر توضیح خواهیم داد. + +اگرچه وضعیت‌های پنهان به خودی خود می توانند مفید باشند، آن‌ها معمولا ورودی بخش دیگری از مدل که به آن *سر* مدل می گوییم هستند. در [فصل اول](/course/chapter1)، همه مسائل مختلف را می‌توانستیم توسط یک معماری حل کنیم، و سپس خروجی را به سر متفاوتی در ادامه مدل پاس بدهیم. + +### بردار‌های چند بعدی؟ + +خروجی ماژول `Transformer` تِنسوری است که معمولا سه بعد دارد: + +- **اندازه بتج**: تعداد رشته‌های مورد پردازش در یک دسته، که در مثال ما دو عدد است. +- **طول رشته**: طول بردار عددی معادل رشته‌ها، که در مثال ما ۱۶ است. +- **اندازه پنهان**: ابعاد بردار برای هر ورودی مدل. + + به خاطر همین مقدار آخر به این تِنسور «چندین بعدی» می گوییم. اندازه پنهان می‌تواند بسیار بزرگ باشد(معمولا ۷۶۸ برای مدل‌های کوچکتر، و در مدل‌های بزرگتر این عدد به ۳۰۷۲ یا بیشتر هم می رسد) + +این را با پاس دادن ورودی‌های پیش‌پردازش شده به مدل خود می توانیم ببینیم: + +
+ + +{#if fw === 'pt'} +```python +outputs = model(**inputs) +print(outputs.last_hidden_state.shape) +``` + +```python out +torch.Size([2, 16, 768]) +``` +{:else} +```py +outputs = model(inputs) +print(outputs.last_hidden_state.shape) +``` + +```python out +(2, 16, 768) +``` +{/if} + + +
+ + +توجه کنید که خروجی‌های ترنسفورمرهای هاگینگ‌فِیس، مانند `namedtuple`‌ها یا دیکشنری‌ها هستند. شما می‌توانید به هر عضو، با استفاده از نامش(مانند آنچه ما انجام دادیم) یا با کلیدش(`outputs["last_hidden_state"]`) یا حتی اگر دقیقاً از مکان آن اطلاع دارید با شماره‌اش(`outputs[0]`) دسترسی پیدا کنید. + +### سر مدل: درک اعداد درون مدل + +قسمت سر، بردارهای چندین بعدی وضعیت پنهان را به عنوان ورودی می‌پذیرد و آن‌ها را به ابعادی در فضایی دیگر برای بخش بعدی پروجکت می‌کند. سرها معمولا از یک یا چند لایه خطی تشکیل شده‌اند. + + +
+A Transformer network alongside its head. + +
+ +خروجی مدل ترنسفورمر، مستقیماً به سر بخش بعدی پاس داده می‌شود. در این نمودار، مدل ترنسفورمر به لایه embeddings و لایه‌های بعدی آن تقسیم شده است. لایه embeddings هر شناسه ورودی در ورودی توکِنیزه شده را به یک بردار که نماینده آن توکِن است تبدیل می‌کند. لایه‌های بعدی با دستکاری در این بردار‌ها توسط فرآیند اتِنشِن، شکل پایانی بردار نماینده جملات را تولید می‌کنند. + +تعداد زیادی از معماری‌‌های مختلف در ترنفورمر‌های هاگینگ‌فِیس وجود دارد که هرکدام برای حل یک مسئله خاص طراحی شده‌اند. در این‌جا فهرست کوتاهی از‌ آنها را آورده‌ایم: + +- `*Model` (برای دسترسی به وضعیت‌های پنهان) +- `*ForCausalLM` +- `*ForMaskedLM` +- `*ForMultipleChoice` +- `*ForQuestionAnswering` +- `*ForSequenceClassification` +- `*ForTokenClassification` +- و نمونه‌های دیگر در ‌هاگینگ‌فِیس + +{#if fw === 'pt'} +برای این مثال، نیازمند مدلی با سر مخصوص دسته‌بندی رشته‌ها(برای تشخیص منفی یا مثبت بودن جملات) هستیم. پس به جای کلاس `AutoModel` از کلاس `AutoModelForSequenceClassification` استفاده می‌کنیم: + +
+ +```python +from transformers import AutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +outputs = model(**inputs) +``` + +
+ +{:else} +برای این مثال، نیازمند مدلی با سر مخصوص دسته‌بندی رشته‌ها(برای تشخیص منفی یا مثبت بودن جملات) هستیم. پس به جای کلاس `TFAutoModel` از کلاس `TFAutoModelForSequenceClassification` استفاده می‌کنیم: + +
+ +```python +from transformers import TFAutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +outputs = model(inputs) +``` + +
+ +{/if} + + +حالا اگر نگاهی به ویژگی shape ورودی‌ها بیاندازیم، خوهیم دید که تعداد ابعاد بردارهای نماینده بسیار کمتر است: قسمت سر مدل بردارهای چندین بعدی که قبلا دیدیم را به عنوان ورودی دریافت کرده، و به عنوان خروجی بردارهایی با دو عضو(یکی برای هر دسته در عملیات دستبندی) تولید کرده است. + +
+ + +```python +print(outputs.logits.shape) +``` + +{#if fw === 'pt'} +```python out +torch.Size([2, 2]) +``` +{:else} +```python out +(2, 2) +``` + +{/if} + +
+ +از آنجا که ما تنها دو جمله و دو دسته ممکن داشتیم، خروجی مدل ما شکل ۲ در ۲ دارد. + +## پس پردازش خروجی + +مقادیری که به عنوان خروجی از مدل‌مان دریافت می‌کنیم به خودی خود قابل درک نیستند. بگذارید نگاهی به آن‌ها بیندازیم: + +
+ + +```python +print(outputs.logits) +``` + +{#if fw === 'pt'} +```python out +tensor([[-1.5607, 1.6123], + [ 4.1692, -3.3464]], grad_fn=) +``` +{:else} +```python out + +``` +{/if} + +
+ +پیش‌بینی مدل ما برای جمله اول `[-1.5607, 1.6123]` و برای جمله دوم `[ 4.1692, -3.3464]` است. این‌ خروجی‌ها مقادیر آماری نیستند، بلکه به آنها *لوجیت* می‌گوییم. آن‌ها مقادیر خام و نرمال‌نشده خروجی از آخرین لایه مدل هستند. برای تبدیل به مقادیر آماری باید آن‌ها را از یک لایه‌ [سافت‌مکس](https://en.wikipedia.org/wiki/Softmax_function) بگذرانیم(تمام ترنسفورمرهای هاگینگ‌فِیس در خروجی لوجیت تولید می‌کنند زیرا معمولا تابع لاس مورد استفاده در تعلیم مدل، آخرین تابع فعال‌ساز مانند سافت‌مکس‌ را با خود تابع لاس مانند کِراس‌آنتروپی ترکیب می‌کند). + + +
+ +{#if fw === 'pt'} +```py +import torch + +predictions = torch.nn.functional.softmax(outputs.logits, dim=-1) +print(predictions) +``` +{:else} +```py +import tensorflow as tf + +predictions = tf.math.softmax(outputs.logits, axis=-1) +print(predictions) +``` +{/if} + +{#if fw === 'pt'} +```python out +tensor([[4.0195e-02, 9.5980e-01], + [9.9946e-01, 5.4418e-04]], grad_fn=) +``` +{:else} +```python out +tf.Tensor( +[[4.01951671e-02 9.59804833e-01] + [9.9945587e-01 5.4418424e-04]], shape=(2, 2), dtype=float32) +``` +{/if} + +
+ + +حالا می‌توانیم ببینیم که پیش‌بینی مدل برای جمله اول `[0.9995, 0.0005]` و برای جمله دوم `[0.9995, 0.0005]` است. این‌ها مقادیر آشنای آماری هستند. + +برای تبدیل این مقادیر به عنوان دسته تشخیص داده شده می‌توانیم از ویژگی `id2label` تنظیمات مدل استفاده کنیم(در بخش بعدی بیشتر در این مورد صحبت خواهیم کرد): + + +
+ +```python +model.config.id2label +``` + +```python out +{0: 'NEGATIVE', 1: 'POSITIVE'} +``` + +
+ +اکنون مشخص است که پیش‌بینی‌های مدل از این قرار هستند: + +- جمله اول: NEGATIVE: 0.0402, POSITIVE: 0.9598 +- جمله دوم: NEGATIVE: 0.9995, POSITIVE: 0.0005 + + +ما با موفقیت سه مرحله خط‌تولید را در اینجا نشان دادیم: پیش‌پردازش توسط توکِنایزرها، گذر ورودی‌ها از مدل و پس‌پردازش! حالا کمی زمان می‌گذاریم تا به صورت عمیق‌تر وارد هر‌کدام از این مراحل شویم. + + + +✏️ **خودتان امتحان کنید!** دو(یا بیشتر) نوشته از خودتان را از خط‌تولید `sentiment-analysis` بگذرانید. سپس مراحلی که در اینجا دیدیم را تکرار کنید و مطمئن شوید همان نتایج را دریافت می کنید! + + + +
diff --git a/chapters/fa/glossary/1.mdx b/chapters/fa/glossary/1.mdx index 8587b671b..9139a430b 100644 --- a/chapters/fa/glossary/1.mdx +++ b/chapters/fa/glossary/1.mdx @@ -5,14 +5,15 @@ |-----------------------|------------------| | Transformer | ترنسفورمر | | Pytorch | پایتورچ | -| TensorFlow | تنسورفلو | +| TensorFlow | تِنسورفِلو | | Chapter | فصل | +| Section | بخش | | Model | مدل | | Parameter | پارامتر | | Train | تعلیم | | Deploy | به‌کارگیری | -| 🤗 | هاگینگ‌فیس | -| Hugging Face | هاگینگ‌فیس | +| 🤗 | هاگینگ‌فِیس | +| Hugging Face | هاگینگ‌فِیس | | Load | بارگذاری | | Save | ذخیره‌سازی | | Library | کتابخانه | @@ -22,7 +23,7 @@ | Module | ماژول | | Abstraction | انتزاع انتزاعات | | Forward Pass | اجرای روی به جلو | -| Tokenizer | توکنایزر | +| Tokenizer | توکِنایزر | | Function | عملیات | | Configuration | تنظیمات | | Batch | بتچ | @@ -44,17 +45,71 @@ | Workload | محاسبه، محاسبات | | Package Manager | پکیج‌منیجر | | Command | دستور یا فرمان | -| Feature | قابلیت‌ | -| Development | توسعه نرم‌افزار | +| Feature, as for an app | قابلیت‌ | +| Development | توسعه | | Dependency | وابسته، وابستگی | | Virtual Environment | محیط مجازی | | Terminal | ترمینال | | Incompatibility | ناسازگاری | | Self-Contained | قائم به خود یا بدون وابستگی خارجی؟ | | Script | اسکریپت‌ | -| Feature | قابلیت | | Folder | پوشه | +| Neural Network | شبکه‌ی عصبی | +| Text | نوشته | +| Pipeline | خط تولید | +| Word | کلمه | +| Subword | قطعات کوچکتر کلمه؟ | +| Punctuation | علائم نگارشی | +| Symbol | علامت‌‌، علامت‌ها | +| Token | توکِن | +| Preprocess | پیش‌پردازش | +| Postprocess | پس‌پردازش | +| Method, as in code | تابع | +| Checkpoint | نقطه تعلیم؟ | +| Model Card | صفحه توضیحات مدل | +| Sentiment Analysis | تحلیل احساسات | +| Dictionary, as in Python | دیکشنری | +| List, as in code | لیست | +| Tensor | تِنسور | +| Framework | فریمورک | +| Flax | فلَکس | +| NumPy | NumPy | +| Scalar | عددی | +| Vector, as in math | برداری | +| Matrix | ماتریس | +| Instantiate | ساختن | +| Argument, as in code | آرگومان | +| Key, as in Python dictionary | کلید | +| Row | ردیف | +| Integer | عدد صحیح | +| ID | شناسه | +| Unique ID | شناسه منحصر به فرد | +| Code Snippet | قطعه کد | +| Hidden State | وضعیت پنهان | +| Feature, as in model | فیچر | +| High-Dimensional | چندین بعدی | +| Vector, as in Python | وِکتور | +| Sequence | رشته | +| Index, as in an array or list | شماره | +| Project, as in math | پروجکت؟ | +| Embedding | embedding? | +| Tokenized | توکِنیزه؟ قطعه قطعه شده؟ | +| Attention Mechanism | مکانیزم دقت؟ فرآیند دقت؟ فرآیند اتِنشِن | +| Classification | دسته‌بندی | +| Attribute, as for a class in code | ویژگی؟ | +| Label, as in classification | عنوان دسته | +| Prediction, as in nn model | پیش‌بینی | +| Logit, as in math and also in Pytorch | لوجیت؟ | +| SoftMax | سافت‌مکس | +| Loss Function | کمممممممککککک!!!!! ‌ لاس‌فانکشن؟، تابع لاس؟ تابع یادگیری شبکه؟ | +| Activation Layer | لایه فعال‌سازی؟ لایه خروجی؟ لایه تبدیل لوجیت به مقدار آماری؟ کمککککککک | +| Cross Entropy | کِراس آنتروپی؟ | +معادل‌هایی که استفاده نمی‌کنیم: + +| معادل در منبع | معادل اشتباه در ترجمه | +|-----------------------|------------------| +| Application, as in an app and not as in apply | کاربرد | معادل‌هایی که استفاده نمی‌کنیم: @@ -62,6 +117,10 @@ |-----------------------|------------------| | Application, as in an app and not as in apply | کاربرد | +| املای مورد استفاده کلمات فارسی | +|-------------------------------| +| ارائه | + کلمات مخفف: @@ -83,39 +142,3 @@ - * Used in chapter1/page2 to loosely mean 'running' the model. If used anywhere else to exact technical meaning we could go with 'استنتاج' probably? But that sounds archaic to me. - -- Don't translate industry-accepted acronyms. e.g. TPU or GPU. - -- Translate acronyms used for convenience in English to full Persian form. e.g. ML - -- Keep voice active and consistent. Don't overdo it but try to avoid a passive voice. - -- 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. - -- Keep POV consistent. - -- Smaller sentences are better sentences. Apply with nuance. - -- If translating a technical word, keep the choice of Persian equivalent consistent. - If workspace is mohit-e-kari don't just switch to faza-ye-kari. This ofc - does not apply for non-technical choices, as in those cases variety actually - helps keep the text engaging. - -- This is merely a translation. Don't add any technical/contextual information - not present in the original text. Also don't leave stuff out. The creative - choices in composing this information were the original authors' to make. - Our creative choices are in doing a quality translation. - -- Note on translating indefinite articles(a, an) to Persian. Persian syntax does - not have these. Common problem in translations is using - the equivalent of 'One'("Yek", "Yeki") as a direct word-for-word substitute - which makes for ugly Persian. Be creative with Persian word order to avoid this. - - For example, the sentence "we use a library" should be translated as - "ما از کتابخانه‌ای استفاده می‌کنیم" and not "ما از یک کتابخانه استفاده می‌کنیم". - "Yek" here implies a number(1) to the Persian reader, but the original text - was emphasizing the indefinite nature of the library and not its count. - -- Be exact when choosing equivalents for technical words. Package is package. - Library is library. Don't mix and match. diff --git a/chapters/fr/_toctree.yml b/chapters/fr/_toctree.yml index ab363315c..e4f4a187f 100644 --- a/chapters/fr/_toctree.yml +++ b/chapters/fr/_toctree.yml @@ -3,6 +3,30 @@ - local: chapter0/1 title: Introduction +- title: 1. Les modèles Transformers + sections: + - local: chapter1/1 + title: Introduction + - local: chapter1/2 + title: Traitement du langage naturel + - local: chapter1/3 + title: Que peut-on faire avec les modèles Transformers? + - local: chapter1/4 + title: Comment fonctionnent les modèles Transformers? + - local: chapter1/5 + title: Les modèles d'encodeur + - local: chapter1/6 + title: Les modèles de décodeur + - local: chapter1/7 + title: Les modèles de séquence-à-séquence + - local: chapter1/8 + title: Biais et limitations + - local: chapter1/9 + title: Résumé + - local: chapter1/10 + title: Questionnaire de fin de chapitre + quiz: 1 + - title: 2. Utilisation de 🤗 Transformers sections: - local: chapter2/1 @@ -41,4 +65,4 @@ title: 🤗 Datasets, vérifié ! - local: chapter5/8 title: Quiz de fin de chapitre - quiz: 5 + quiz: 5 \ No newline at end of file diff --git a/chapters/fr/chapter1/1.mdx b/chapters/fr/chapter1/1.mdx new file mode 100644 index 000000000..fdfee9d27 --- /dev/null +++ b/chapters/fr/chapter1/1.mdx @@ -0,0 +1,51 @@ +# Introduction + +## Bienvenue sur le cours 🤗 ! + + + +Ce cours vous apprendra à utiliser les librairies de NLP de l'écosystème [Hugging Face](https://huggingface.co/) — [🤗 Transformers](https://github.com/huggingface/transformers), [🤗 Datasets](https://github.com/huggingface/datasets), [🤗 Tokenizers](https://github.com/huggingface/tokenizers), et [🤗 Accelerate](https://github.com/huggingface/accelerate) — ainsi que le [Hub de Hugging Face](https://huggingface.co/models). C'est totalement gratuit et sans publicité. + +## À quoi s'attendre ? + +Voici un bref aperçu du cours: + +
+Bref aperçu du contenu du cours. + +
+ +- Les chapitres 1 à 4 présentent les principaux concepts de la librairie 🤗 Transformers. À la fin de ce chapitre, vous serez familier du fonctionnement des modèles Transformers et vous saurez comment utiliser un modèle du [Hub de Hugging Face](https://huggingface.co/models), le finetuner sur un jeu de données, et partager vos résultats sur le Hub! +- Les chapitres 5 à 8 présentent les bases de 🤗 Datasets et 🤗 Tokenizers ainsi qu'une découverte des problèmes classiques de NLP. À la fin de ce chapitre, vous serez capable de résoudre les problèmes de NLP les plus communs par vous-même. +- Les chapitres 9 à 12 proposent d'aller plus loin et d'explorer comment les modèles Transformers peuvent être utilisés pour résoudre des problèmes de traitement de la parole et de la vision par ordinateur. En suivant ces chapitres, vous apprendrez à construire et à partager vos modèles via des démonstrateurs, et vous serez capable d'optimiser ces modèles pour les environnements de production. Enfin, vous serez prêt à appliquer 🤗 Transformers à (presque) n'importe quel problème de machine learning! + +Ce cours: + +* Requiert un bon niveau en Python +* Se comprend mieux si vous avez déjà suivi un cours d'introduction au deep learning, comme [fast.ai's](https://www.fast.ai/) [Practical Deep Learning for Coders](https://course.fast.ai/) ou un des cours développés par [DeepLearning.AI](https://www.deeplearning.ai/) +* N'attend pas une connaissance appronfondie de [PyTorch](https://pytorch.org/) ou de [TensorFlow](https://www.tensorflow.org/), bien qu'être familiarisé avec l'un d'entre eux peut aider + +Après avoir terminé ce cours, nous vous recommandons de suivre la [Spécialisation en NLP](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh) dispensée par DeepLearning.AI, qui couvre une grande gamme de modèles traditionnels de NLP comme les Naïves Bayes et les LSTMs qui sont importants à connaître! + +## Qui sommes-nous ? + +À propos des auteurs de ce cours: + +**Matthew Carrigan** est Machine Learning Engineer chez Hugging Face. Il vit à Dublin, en Irlande et à travaillé auparavant comme ingénieur en ML chez Parse.ly et avant cela comme chercheur postdoctoral à Trinity College Dublin. Il ne croit pas que nous arrivions à AGI en mettant à l'échelle les architectures existantes, mais il a tout de même beaucoup d'espoir dans l'immortalité des robots. + +**Lysandre Debut** est Machine Learning Engineer chez Hugging Face et a travaillé sur la librairie 🤗 Transformers depuis les premières phases de développement. Son but est de rendre NLP accessible pour tout le monde en développant des outils disposant d'une API très simple. + +**Sylvain Gugger** est Research Engineer chez Hugging Face et un des principaux responsable de la librairie 🤗 Transformers. Précédemment, Il a été chercheur en ML chez fast.ai et a écrit _[Deep Learning for Coders with fastai and PyTorch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)_ avec Jeremy Howard. Son but ultime est de rendre le deep learning plus accessible, en développant et en améliorant des techniques permettant aux modèles d'apprendre rapidement sur des ressources limitées. + +**Merve Noyan** est Developer Advocate chez Hugging Face et travaille à la création d'outils et de contenu visant à démocratiser le machine learning pour tous. + +**Lucile Saulnier** est Machine Learning Engineer chez Hugging Face qui travaille au développement et à l'implémentation de nombreux outils open source. Elle est également activement impliquée dans de nombreux projets de recherche dans le domaine de NLP comme l'entraînement collaboratif de modèles et le projet BigScience. + +**Lewis Tunstall** est Machine Learning Engineer chez Hugging Face dévoué au développement d'outils open source avec la volonté de les rendre accessibles à une communauté plus large. Il est également co-auteur d'un livre qui va bientôt paraître, [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). + +**Leandro von Werra** est Machine Learning Engineer dans l'équipe open-source chez Hugging Face et également co-auteur du livre qui va bientôt paraître, [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). Il a plusieurs années d'expérience dans l'industrie du machine learning, où il a pu déployer des projets de NLP en production en travaillant sur toutes les étapes clefs du déploiement. + +Êtes-vous prêt à commencer ? Dans ce chapitre, vous apprendrez: +* À utiliser la fonction `pipeline()` pour résoudre des problèmes de NLP comme la génération de texte et la classification +* Quelle est l'architecture d'un modèle Transformer +* Comment faire la distinction entre les différentes architectures d'encodeur, de décodeur et d'encodeur-décodeur et leurs condition d'utilisation diff --git a/chapters/fr/chapter1/10.mdx b/chapters/fr/chapter1/10.mdx new file mode 100644 index 000000000..5c6f39376 --- /dev/null +++ b/chapters/fr/chapter1/10.mdx @@ -0,0 +1,254 @@ + + +# Questionnaire de fin de chapitre + +Ce chapitre a couvert un grand nombre de notions ! Ne vous inquiétez pas si vous n'avez pas compris tous les détails, car les chapitres suivants vous aideront à comprendre comment les choses fonctionnent concrètement. + +Mais avant d'aller plus loin, prenons un instant pour voir ce que vous avez appris dans ce chapitre ! + + +### 1. Explorez le Hub et cherchez le modèle `roberta-large-mnli`. Quelle est la tâche qu'il effectue ? + + +page roberta-large-mnli." + }, + { + text: "Classification de texte", + explain: "Pour être plus précis, il classifie si deux phrases sont logiquement liées entre elles parmis trois possibilités (contradiction, neutre, lien) — une tâche aussi appelée inference de langage naturel.", + correct: true + }, + { + text: "Génération de texte", + explain: "Regardez à nouveau sur la page roberta-large-mnli." + } + ]} +/> + +### 2. Que renvoie le code suivant ? + +```py +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +d'analyse de sentiment." + }, + { + text: "Ce code renvoie un texte généré qui complète cette phrase.", + explain: "C'est incorrect - cela correspondrait à un pipeline de génération de texte." + }, + { + text: "Ce code renvoie les entités nommées dans cette phrase, telles que personnes, organisations ou lieux.", + explain: "De plus, avec grouped_entities=True, il regroupera les mots appartenant à la même entité, comme \"Hugging Face\".", + correct: true + } + ]} +/> + +### 3. Que remplace ... dans ce code ? + +```py +from transformers import pipeline + +filler = pipeline("fill-mask", model="bert-base-cased") +result = filler("...") +``` + + has been waiting for you.", + explain: "Ceci est incorrect. Regardez la description du modèle bert-base-cased et essayez de trouver votre erreur." + }, + { + text: "This [MASK] has been waiting for you.", + explain: "Correct! Le modèle utilise [MASK] comme mot-masque.", + correct: true + }, + { + text: "This man has been waiting for you.", + explain: "Ceci est incorrect, car ce pipeline permet de remplacer les mot manquants, donc il a besoin d'un mot-masque." + } + ]} +/> + +### 4. Pourquoi ce code ne fonctionne-t-il pas ? + +```py +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +result = classifier("This is a course about the Transformers library") +``` + +candidate_labels=[...].", + correct: true + }, + { + text: "Ce pipeline nécessite que des phrases soient données, pas juste une phrase.", + explain: "C'est incorrect, bien que ce pipeline peut prendre une liste de phrases à traiter (comme tous les autres pipelines)." + }, + { + text: "La bibliothèque 🤗 Transformers est cassée, comme d'habitude.", + explain: "Nous n'avons aucun commentaire pour cette réponse !", + }, + { + text: "Ce pipeline nécessite des phrases plus longues, car celle-ci est trop courte.", + explain: "C'est incorrect. Notez qu'un texte très long sera tronqué lorsqu'il sera traité par ce pipeline." + } + ]} +/> + +### 5. Que signifie "transfer learning" ? + + + +### 6. Vrai ou faux ? Un modèle de NLP n'a normalement pas besoin de labels pour son entraînement préalable. + + +self-supervised, ce qui signifie que les labels sont créés automatiquement à partir des données d'entrée (comme prédire le mot suivant ou remplacer des mots masqués).", + correct: true + }, + { + text: "Faux", + explain: "Ce n'est pas la bonne réponse." + } + ]} +/> + +### 7. Sélectionnez la phrase qui décrit les termes "modèle," "architecture," et "poids" le plus précisément possible. + + + + +### 8. Parmi ces types de modèles, quel est le plus approprié pour compléter une demande (prompt) avec du texte généré ? + + + +### 9. Parmi ces types de modèles, quel est le plus approprié pour le résumé de texte ? + + + +### 10. Quel type de modèle utiliseriez-vous pour classifier des entrées de texte en fonction de certains labels ? + + + +### 11. Quelles sources peuvent avoir les biais d'un modèle ? + + diff --git a/chapters/fr/chapter1/2.mdx b/chapters/fr/chapter1/2.mdx new file mode 100644 index 000000000..6638e3608 --- /dev/null +++ b/chapters/fr/chapter1/2.mdx @@ -0,0 +1,21 @@ +# Traitement du language naturel (NLP) + +Avant de commencer avec les modèles Transformers, nous allons voir succinctement ce qu'est le traitement du langage naturel et pourquoi il est important. + +## NLP, qu'est ce que c'est? + +Le traitement du langage naturel est un domaine de linguistique et de machine learning qui se concentre sur la compréhension de tout ce qui est lié à la langue humaine. L'objectif des tâches de NLP est non seulement de comprendre individuellement chaque mot, mais aussi de comprendre le contexte associé à l'utilisation de ces mots. + +La liste suivante regroupe les tâches de NLP les plus courantes, avec pour chacune quelques exemples: + +- **Classification de phrases entières**: Analyser le sentiment d'un avis, détecter si un email est un spam, déterminer si une phrase est grammaticalement correcte ou si deux phrases sont logiquement reliées ou non +- **Classification de chaque mot d'une phrase**: Identifier les composants grammaticaux d'une phrase (nom, verbe, adjectif), ou les entités nommées (personne, lieu, organisation) +- **Génération de texte**: Compléter un début d'écrit avec un texte généré automatiquement, remplacer les mots manquants ou masqués dans un texte +- **Extraire une réponse à partir d'un texte**: Étant donné une question et un contexte, extraire la réponse à la question en fonction des informations fournies par le contexte +- **Génération de nouvelles phrases à partir d'un texte**: Traduire un texte dans une autre langue, faire le résumé d'un texte + +Le traitement du langage naturel ne se limite pas qu'à la compréhension du texte écrit. Il s'intéresse aussi aux problèmes complexes de reconnaissance de la parole et de vision par ordinateur, tels que la génération d'une transcription à partir d'un échantillon audio ou la description d'une image. + +## Pourquoi est-ce difficile? + +Les ordinateurs ne traitent pas les informations de la même manière que les humains. Par exemple, lorsque nous lisons la phrase "J'ai faim," nous comprenons très facilement son sens. De même, lorsque nous lisons deux phrases telles que "J'ai faim" et "Je suis triste," nous pouvons facilement déterminer s'il existe des similitudes entre elles. Pour les modèles de machine learning, ces tâches sont plus difficiles. Le texte doit être traité de manière à permettre au modèle d'apprendre. Et parce que le langage est complexe, nous devons prendre soin de réfléchir à la meilleure façon de faire ce traitement. Il y a eu beaucoup de recherches sur la façon de représenter le texte, et nous allons voir quelques méthodes dans le chapitre suivant. diff --git a/chapters/fr/chapter1/3.mdx b/chapters/fr/chapter1/3.mdx new file mode 100644 index 000000000..ec9b002af --- /dev/null +++ b/chapters/fr/chapter1/3.mdx @@ -0,0 +1,329 @@ +# Que peuvent faire les modèles Transformers ? + + + +Dans cette section, nous allons voir ce que peuvent faire les modèles Transformers et utiliser notre premier outil de la librairie 🤗 Transformers: la fonction `pipeline()`. + + +👀 Vous voyez ce bouton Open in Colab en haut à droite ? Cliquez dessus pour ouvrir un notebook Colab avec tous les exemples de code de cette section. Ce bouton sera présent dans n'importe quelle section contenant des exemples de code. + +Si vous souhaitez exécuter les exemples de code en local, nous vous recommandons de jetez un oeil à: configuration. + + +## Les modèles Transformers sont partout ! + +Les modèles Transformers sont utilisés pour résoudre toute sorte de tâches de NLP, comme celles mentionnées dans la section précédente. Voici quelques-unes des entreprises et organisations qui utilisent Hugging Face et les modèles Transformers, et qui contribuent aussi à la communauté en partageant leurs modèles : + +Companies using Hugging Face + +La librairie [🤗 Transformers](https://github.com/huggingface/transformers) fournit toutes les fonctionnalités nécessaires pour créer et utiliser les modèles partagés. Le [Model Hub](https://huggingface.co/models) contient des milliers de modèles pré-entraînés que n'importe qui peut télécharger et utiliser. Vous pouvez également transférer vos propres modèles vers le Hub ! + + +⚠️ Le Hub Hugging Face n'est pas limité aux modèles Transformers. Tout le monde peut partager n'importe quel modèle ou jeu de données s'il le souhaite ! Créez un compte sur huggingface.co pour bénéficier de toutes les fonctionnalités disponibles ! + + +Avant de découvrir en détail comment les modèles Transformers fonctionnent, nous allons voir quelques exemples de comment ils peuvent être utilisés pour résoudre des problèmes NLP intéressants. + +## Travailler avec des pipelines + + + +L'outil le plus basique de la librairie 🤗 Transformers est la fonction `pipeline()`. Elle relie un modèle avec ses étapes de pré-traitement et de post-traitement, permettant d'entrer n'importe quel texte et d'obtenir une réponse compréhensible : + +```python +from transformers import pipeline + +classifier = pipeline("sentiment-analysis") +classifier("I've been waiting for a HuggingFace course my whole life.") +``` + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}] +``` + +On peut même passer plusieurs phrases ! + +```python +classifier( + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!"] +) +``` + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}, + {'label': 'NEGATIVE', 'score': 0.9994558095932007}] +``` + +Par défaut, ce pipeline sélectionne un modèle pré-entraîné qui a été spécifiquement entraîné pour l'analyse de sentiment en anglais. Le modèle est téléchargé et mis en cache lorsque vous créez l'objet `classifier`. Si vous réexécutez la commande, c'est le modèle mis en cache qui sera utilisé et il n'y a pas besoin de télécharger le modèle à nouveau. + +Il y a trois étapes principales lorsque vous passez du texte à un pipeline : + +1. Le texte est pré-traité pour qu'il ait un format compréhensible par le modèle. +2. Les données pré-traitées sont passées au modèle. +3. Les prédictions du modèle sont post-traités, de sorte que vous puissiez les comprendre. + + +Voici une liste non-exhaustive des [pipelines disponibles](https://huggingface.co/transformers/main_classes/pipelines.html) : + +- `feature-extraction` (pour obtenir la représentation vectorielle d'un texte) +- `fill-mask` +- `ner` (named entity recognition = reconnaissance des entités nommées) +- `question-answering` +- `sentiment-analysis` +- `summarization` +- `text-generation` +- `translation` +- `zero-shot-classification` + +Regardons de plus près certains d'entre-eux ! + +## Zero-shot classification + +Nous allons commencer par nous attaquer à une tâche plus difficile où nous devons classer des textes qui n'ont pas été annotés. C'est un scénario très répandu dans les projets réels car l'annotation de texte est généralement longue et nécessite parfois une expertise dans un domaine. Pour ce cas d'usage, le pipeline `zero-shot-classification` est très puissant : il vous permet de spécifier les labels à utiliser pour la classification, de sorte que vous n'ayez pas à vous soucier des labels du modèle pré-entraîné. Nous avons déjà vu comment le modèle peut classer un texte comme positif ou négatif en utilisant ces deux labels, mais il peut également classer le texte en utilisant n'importe quel autre ensemble de labels que vous souhaitez. + +```python +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +classifier( + "This is a course about the Transformers library", + candidate_labels=["education", "politics", "business"], +) +``` + +```python out +{'sequence': 'This is a course about the Transformers library', + 'labels': ['education', 'business', 'politics'], + 'scores': [0.8445963859558105, 0.111976258456707, 0.043427448719739914]} +``` + +Ce pipeline est appelé _zero-shot_ car vous n'avez pas besoin d'entraîner spécifiquement le modèle sur vos données pour l'utiliser. Il peut directement retourner des scores de probabilité pour n'importe quel ensemble de labels que vous choisissez ! + + + +✏️ **Essayez-le !** Jouez avec vos propres séquences et labels et voyez comment le modèle fonctionne. + + + + +## Génération de texte + +Maintenant, nous allons voir comment utiliser un pipeline pour générer du texte. L'idée principale ici est que vous fournissez seulement un extrait de texte qui va être complété par du texte généré automatiquement par le modèle. C'est similaire à la fonctionnalité de prédiction de texte qui est très présente dans les appareils mobiles. La génération de texte implique de l'aléatoire, donc il est normal que vous n'obteniez pas les mêmes résultats que ceux présentés ci-dessous. + +```python +from transformers import pipeline + +generator = pipeline("text-generation") +generator("In this course, we will teach you how to") +``` + +```python out +[{'generated_text': 'In this course, we will teach you how to understand and use ' + 'data flow and data interchange when handling user data. We ' + 'will be working with one or more of the most commonly used ' + 'data flows — data flows of various types, as seen by the ' + 'HTTP'}] +``` + +Il est possible de contrôler le nombre de séquences générées avec l'argument `num_return_sequences` et la longueur totale du texte généré avec l'argument `max_length`. + + + +✏️ **Essayez-le !** Utilisez les arguments `num_return_sequences` et `max_length` pour générer deux phrases de 15 mots chacune. + + + + +## Utiliser n'importe quel modèle du Hub dans un pipeline + +Les exemples précédents utilisaient le modèle par défaut pour la tâche en question, mais vous pouvez aussi choisir un modèle particulier du Hub et l'utiliser dans un pipeline pour une tâche spécifique — par exemple, la génération de texte. Rendez-vous sur la [page du Hub de modèles](https://huggingface.co/models?pipeline_tag=text-generation) et cliquez sur le tag correspondant sur la gauche pour afficher seulement les modèles supportés pour cette tâche. Vous devriez arriver sur une page comme [celle-ci](https://huggingface.co/models?pipeline_tag=text-generation). + +Essayons le modèle [`distilgpt2`](https://huggingface.co/distilgpt2) ! Voici comment charger le modèle dans le même pipeline que précédemment : + +```python +from transformers import pipeline + +generator = pipeline("text-generation", model="distilgpt2") +generator( + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, +) +``` + +```python out +[{'generated_text': 'In this course, we will teach you how to manipulate the world and ' + 'move your mental and physical capabilities to your advantage.'}, + {'generated_text': 'In this course, we will teach you how to become an expert and ' + 'practice realtime, and with a hands on experience on both real ' + 'time and real'}] +``` + +Vous pouvez améliorer votre recherche de modèle en cliquant sur les tags de langue, et choisir un modèle qui génère du texte dans une autre langue. Le Hub de modèles contient également des sauvegardes pour des modèles multilingues qui supportent plusieurs langues. + +Une fois que vous avez choisi un modèle, vous verrez que vous pouvez tester son fonctionnement en ligne directement. Cela vous permet de tester rapidement les capacités du modèle avant de le télécharger. + + + +✏️ **Essayez-le !** Utilisez les filtres pour trouver un modèle de génération de texte pour une autre langue. N'hésitez pas à jouer avec le widget et l'utiliser dans un pipeline ! + + + +### L'API d'inférence + +Tous les modèles peuvent être testé directement depuis votre navigateur en utilisant l'API d'inférence, qui est disponible sur le site [Hugging Face](https://huggingface.co/). Vous pouvez jouer avec le modèle directement sur sa page en entrant du texte personnalisé et en regardant le modèle traiter les données d'entrée. + +L'API d'inférence qui est utilisée par le widget est également disponible en tant que produit payant, qui est utile si vous avez besoin de l'API pour votre travail. Consultez la [page des prix](https://huggingface.co/pricing) pour plus de détails. + +## Remplacement des mots manquants + +Le prochain pipeline que vous allez essayer est celui de `fill-mask`. L'idée de cette tâche est de remplir les mots manquants d'un texte donné : + +```python +from transformers import pipeline + +unmasker = pipeline("fill-mask") +unmasker("This course will teach you all about models.", top_k=2) +``` + +```python out +[{'sequence': 'This course will teach you all about mathematical models.', + 'score': 0.19619831442832947, + 'token': 30412, + 'token_str': ' mathematical'}, + {'sequence': 'This course will teach you all about computational models.', + 'score': 0.04052725434303284, + 'token': 38163, + 'token_str': ' computational'}] +``` + +L'argument `top_k` permet de contrôler le nombre de possibilités que vous souhaitez afficher. Notez que dans ce cas, le modèle remplace le mot spécial ``, qui est souvent appelé un *mot masqué*. D'autres modèles permettant de remplacer les mots manquants peuvent avoir des mots masqués différents, donc il est toujours bon de vérifier le mot masqué approprié lorsque vous comparez d'autres modèles. Une façon de le vérifier est de regarder le mot masqué utilisé dans l'outil de test de la page du modèle. + + + +✏️ **Essayez-le !** Recherchez le modèle `bert-base-cased` sur le Hub et identifiez le mot masqué dans l'outil d'inférence. Que prédit le modèle pour la phrase dans notre exemple de pipeline au-dessus ? + + + +## Reconnaissance d'entités nommées + +La reconnaissance d'entités nommées (NER = Named Entity Recognition) est une tâche où le modèle doit trouver les parties du texte d'entrée qui correspondent à des entités telles que des personnes, des lieux ou des organisations. Voyons un exemple : + +```python +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +```python out +[{'entity_group': 'PER', 'score': 0.99816, 'word': 'Sylvain', 'start': 11, 'end': 18}, + {'entity_group': 'ORG', 'score': 0.97960, 'word': 'Hugging Face', 'start': 33, 'end': 45}, + {'entity_group': 'LOC', 'score': 0.99321, 'word': 'Brooklyn', 'start': 49, 'end': 57} +] +``` + +Nous pouvons voir que le modèle a correctement identifié Sylvain comme une personne (PER), Hugging Face comme une organisation (ORG) et Brooklyn comme un lieu (LOC). + +Il est possible d'utiliser l'option `grouped_entities=True` lors de la création du pipeline pour regrouper les parties du texte qui correspondent à la même entité: ici le modèle à correctement regroupé "Hugging" et "Face" comme une seule organisation, même si le nom comporte plusieurs mots. En effet, comme nous allons voir dans la prochaine chapitre, la prétraitement du texte sépare parfois certains mots en plus petites parties. Par exemple, `Sylvain` est séparé en quatre morceaux: `S`, `##yl`, `##va`, et `##in`. Dans l'étape de post-traitement, le pipeline a réussi à regrouper ces morceaux. + + + +✏️ **Essayez-le !** Recherchez sur le Hub un modèle capable de reconnaître les différentes parties du langage (Part-of-speech = POS) en Anglais. Que prédit le modèle pour la phrase dans notre exemple du pipeline au-dessus ? + + + +## Réponse à des questions + +Le pipeline `question-answering` répond à des questions en utilisant des informations données en contexte : + +```python +from transformers import pipeline + +question_answerer = pipeline("question-answering") +question_answerer( + question="Where do I work?", + context="My name is Sylvain and I work at Hugging Face in Brooklyn", +) +``` + +```python out +{'score': 0.6385916471481323, 'start': 33, 'end': 45, 'answer': 'Hugging Face'} +``` + +Notez que ce pipeline fonctionne par extraction d'information depuis le contexte fourni, il ne génère pas la réponse. + +## Résumé + +Le résumé est une tâche de réduction d'un texte en un texte plus court, tout en gardant tous (ou presque tous) les aspects importants référencés dans le texte. Voici un exemple : + +```python +from transformers import pipeline + +summarizer = pipeline("summarization") +summarizer( + """ + America has changed dramatically during recent years. Not only has the number of + graduates in traditional engineering disciplines such as mechanical, civil, + electrical, chemical, and aeronautical engineering declined, but in most of + the premier American universities engineering curricula now concentrate on + and encourage largely the study of engineering science. As a result, there + are declining offerings in engineering subjects dealing with infrastructure, + the environment, and related issues, and greater concentration on high + technology subjects, largely supporting increasingly complex scientific + developments. While the latter is important, it should not be at the expense + of more traditional engineering. + + Rapidly developing economies such as China and India, as well as other + industrial countries in Europe and Asia, continue to encourage and advance + the teaching of engineering. Both China and India, respectively, graduate + six and eight times as many traditional engineers as does the United States. + Other industrial countries at minimum maintain their output, while America + suffers an increasingly serious decline in the number of engineering graduates + and a lack of well-educated engineers. +""" +) +``` + +```python out +[{'summary_text': ' America has changed dramatically during recent years . The ' + 'number of engineering graduates in the U.S. has declined in ' + 'traditional engineering disciplines such as mechanical, civil ' + ', electrical, chemical, and aeronautical engineering . Rapidly ' + 'developing economies such as China and India, as well as other ' + 'industrial countries in Europe and Asia, continue to encourage ' + 'and advance engineering .'}] +``` + +Comme pour la génération de texte, vous pouvez spécifier une `max_length` (longueur maximale) ou une `min_length` (longueur minimale) pour le résultat. + + +## Traduction + +Pour la traduction, vous pouvez utiliser un modèle par défaut si vous fournissez un couple de langues dans le nom de la tâche (comme `"translation_en_to_fr"`), mais le plus simple reste d'utiliser un modèle adéquat disponible sur le [Model Hub](https://huggingface.co/models). Ici, nous allons essayer de traduire du français en anglais : + +```python +from transformers import pipeline + +translator = pipeline("translation", model="Helsinki-NLP/opus-mt-fr-en") +translator("Ce cours est produit par Hugging Face.") +``` + +```python out +[{'translation_text': 'This course is produced by Hugging Face.'}] +``` + +Comme pour la génération de texte et le résumé de texte, il est possible de spécifier une `max_length` (longueur maximale) ou une `min_length` (longueur minimale) pour le résultat. + + + +✏️ **Essayez-le !** Recherchez d'autres modèles de traduction sur le Hub et essayez de traduire la phrase précédente en plusieurs langues différentes. + + + +Les pipelines présentés jusqu'ici sont principalement destinés à des fins de démonstration. Ils ont été programmés pour des tâches spécifiques et ne peuvent pas effectuer de variations de celles-ci. Dans le chapitre suivant, vous apprendrez ce qu'il y a dans un `pipeline()` et comment modifier son comportement. diff --git a/chapters/fr/chapter1/4.mdx b/chapters/fr/chapter1/4.mdx new file mode 100644 index 000000000..33e1e29b3 --- /dev/null +++ b/chapters/fr/chapter1/4.mdx @@ -0,0 +1,170 @@ +# Comment fonctionnent les modèles Transformers ? + +Dans cette partie, nous allons jeter un coup d'oeil à l'architecture des modèles Transformers. + +## Court historique des Transformers + +Voici quelques dates clefs dans la courte histoire des modèles Transformers : + +
+A brief chronology of Transformers models. + +
+ +[L'architecture Transformer](https://arxiv.org/abs/1706.03762) a été présentée en Juin 2017. La recherche initiale portait sur les tâches de traduction. Cela s'est suivi par la présentation de plusieurs modèles influents, dont : + +- **Juin 2018**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), le premier modèle Transformer pré-entraîné, utilisé pour fine-tuning sur différentes tâches de NLP et ayant obtenu des résultats à l'état de l'art + +- **Octobre 2018**: [BERT](https://arxiv.org/abs/1810.04805), autre modèle large pré-entraîné, ayant été construit pour produire de meilleurs résumés de texte (plus de détails dans le chapitre suivant !) + +- **Février 2019**: [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), une version améliorée (et plus grande) de GPT qui n'a pas été directement rendu publique pour cause de raisons éthiques + +- **Octobre 2019**: [DistilBERT](https://arxiv.org/abs/1910.01108), une version distillée de BERT étant 60% plus rapide, 40% plus légère en mémoire, et conservant tout de même 97% performances initiales de BERT + +- **Octobre 2019**: [BART](https://arxiv.org/abs/1910.13461) et [T5](https://arxiv.org/abs/1910.10683), deux modèles larges pré-entraînés utilisant la même architecture que le modèle Transformer originel (les premiers à faire cela) + +- **Mai 2020**, [GPT-3](https://arxiv.org/abs/2005.14165), une version encore plus grande que GPT-2 ayant des performances très bonnes sur une variété de tâches ne nécessitant pas de fine-tuning (appellé _zero-shot learning_) + +Cette liste est loin d'être exhaustive, et permet juste de mettre en lumière certains modèles Transformers. Plus largement, ces modèles peuvent être regroupés en trois catégories : + +- GPT-like (aussi appelé modèles Transformers _auto-regressif_) +- BERT-like (aussi appelé modèles Transformers _auto-encodeur_) +- BART/T5-like (aussi appelé modèles Transformers _séquence-à-séquence_) + +Nous verrons plus en profondeur ces familles de modèles plus tard. + +## Transformers, des modèles du langage + +Tous les modèles Transformers mentionné ci-dessus (GPT, BERT, BART, T5, etc.) ont été entraînés comme des *modèles du langage*. Cela signifie qu'ils ont été entraînés sur une large quantité de textes bruts en mode auto-supervisé. L'apprentissage auto-supervisé est un type d'entraînement où l'objectif est automatiquement déterminé par les entrées du modèle. Cela veut dire qu'une labellisation des données par des humains n'est pas nécessaire ! + +Ce type de modèle développe une compréhension statistique du langage sur lequel il est entraîné, mais cela n'est pas adapté à une tâche spécifique. A cause de cela, le modèle pré-entraîné doit ensuite suivre une procédure de *transfer learning*. Durant cette procédure, le modèle est fine-tuné en mode supervisé -- en utilisant des données labellisées par un human -- sur une tâche donnée. + +Un exemple de tâche est la prédiction du prochain mot de la phrase en ayant lu les *n* mots précédents. Ce procédé s'appelle *modélisation du langage causal* car les prédictions dépendent des entrées précédentes et actuelles, mais pas des suivantes. + +
+Example of causal language modeling in which the next word from a sentence is predicted. + +
+ +Un autre exemple est la *modélisation du langage contextuel*, où me modèle doit prédire un mot masqué dans une phrase. + +
+Example of masked language modeling in which a masked word from a sentence is predicted. + +
+ +## Les modèles Transformers sont énormes + +En dehors de quelques exceptions (comme DistilBERT), la stratégie générale pour obtenir de meilleure performance consiste à augmenter la taille des modèles ainsi que la quantité de données utilisées pour l'entraînement de ces derniers. + +
+Number of parameters of recent Transformers models +
+ +Malheureusement, entraîner un modèle, et particulièrement un modèle large, nécessite une importante quantité de données. Cela devient très coûteux en terme de temps et de ressources de calcul. Cela se traduit même par un impact environnemental, comme le montre le graphique suivant. + +
+The carbon footprint of a large language model. + +
+ + + +Et cela présente un projet pour un (gigantesque) modèle mené par une équipe qui essaye consciemment de réduire l'impact environnemental du pré-entraînement. L'empreinte environnemental serait encore plus conséquente s'il fallait lancer beaucoup d'entraînements pour obtenir les meilleurs hyper-paramètres. + +Imaginez si à chaque fois qu'une équipe de recherche, qu'une organisation étudiante ou qu'une entreprise voulait entraîner un modèle, ils devaient repartir de zéro. Ceci menerait à des énormes et non-nécessaires coûts environnementaux. + +C'est pourquoi le partage des modèles du langage est primordial : partager les poids d'entraînement et construire à partir de ces poids permet de réduire les coûts de calcul globaux ainsi que l'empreinte carbone de toute la communauté. + +## Le Transfer Learning + + + +Le *Pré-entraînement* est le fait d'entraîner un modèle de zéro : les poids sont initialisés de manière aléatoire, et l'entraînement débute sans aucune connaissance préalable (ou heuristique). + +
+The pretraining of a language model is costly in both time and money. + +
+ +Ce pré-entraînement est habituellement réalisé sur de grande quantité de données. Il nécessite donc un très grand corpus de données, et l'entraînement peut prendre jusqu'à plusieurs semaines. + +Le *Fine-tuning*, d'un autre côté, est un entraînement effectué **après** qu'un modèle ait été pré-entraîné. Pour réaliser le fine-tuning, il faut d'abord récupèrer un modèle du langage pré-entraîné, puis lancé un entraînement sur un jeu de données adapté à la tâche cible. Mais attendez -- pourquoi ne pas simplement entraîné un modèle sur la tâche cible ? Voici plusieurs raisons : + +* Le modèle pré-entraîné a déjà été entraîné sur un jeu de données qui a des similarités avec le jeu de données utilisé pour le fine-tuning. Ainsi le processus de fine-tuning peut bénéficier du savoir acquis par le modèle initial lors du pré-entraînement (par exemple, avec des problèmes de NLP, le modèle pré-entraîné aura une sorte de compréhension statistique de la langue associée à la tâche cible). +* Puisque le modèle pré-entraîné a déjà été entraîné sur beaucoup de données, le fine-tuning nécessite beaucoup moins de données pour obtenir des résultats décents. +* Pour la même raison, la quantité de temps et de ressources nécessaires pour obtenir de bons résultats est beaucoup moins grande. + +Par exemple, il serait possible de tirer prodit d'un modèle pré-entraîné sur la langue anglaise and ensuite fine-tuné sur un corpus arXiv, permettant d'obtenir un modèle basé sur la recherche et la science. Le fine-tuning ne nécessiterait qu'une quantité réduite de données : le savoir acquis par le modèle pré-entraîné est transféré au nouveau modèle, d'où le terme de *transfer learning*. + +
+The fine-tuning of a language model is cheaper than pretraining in both time and money. + +
+ +Le fine-tuning d'un modèle nécessite ainsi moins de temps, de données, d'argent et a un impact environnemental réduit. Il est également possible d'itérer plus rapidement sur différentes stratégies de fine-tuning, puisque l'entraînement est moins contraignant qu'un entraînement complet. + +Ce procédé permet également d'obtenir de meilleurs résultats qu'un entraînement de zéro (sauf si on dispose de beaucoup de données), ce qui explique pourquoi il faut toujours essayer de tirer profit d'un modèle pré-entraîné -- un qui est le plus proche de la tâche cible -- et de le fine-tuner. + +## Architecture générale + +Dans cette section, nous allons voir l'architecture générale des modèles Transformers. Pas d'inquiétudes si vous ne comprenez pas tous les concepts; il y a des sections détaillées qui couvrent chaque composants plus tard. + + + +## Introduction + +Le modèle est principalement composé de deux blocs : + +* **Encodeur (gauche)**: L'encodeur reçoit une entrée et contruit une réprésentation de celle-ci (ses caractéristiques). Cela signifie que le modèle est optimisé pour acquérir une compréhension venant de ces entrées. +* **Décodeur (droite)**: Le décodeur utilise la réprésentation de l'encodeur (caractéristiques) en plus des autres entrées pour générer une séquence cible. Cela signifie que le modèle est optimisé pour générer des sorties. + +
+Architecture of a Transformers models + +
+ +Chacune de ces parties peuvent être utilisées indépendamment, en fonction de la tâche : + +* **Modèles uniquement encodeurs** : Adaptés pour des tâches qui nécessitent une compréhension de l'entrée, comme la classification de phrases et la reconnaissance d'entités nommées. +* **Modèles uniquement décodeurs** : Adaptés pour les tâches génératives telles que la génération de texte. +* **Modèles encodeurs-décodeurs** ou **modèles de séquence-à-séquence** : Adaptés aux tâches génératives qui nécessitent une entrée, telles que la traduction ou le résumé de texte. + +Nous verrons plus en détails chacune de ces architectures plus tard. + +## Les couches d'attention + +Une caractéristique clef des modèles Transformers est qu'ils sont construits avec des couches spéciales appellées *couches d'attention*. En fait, le titre de la publication qui a présenté l'architecture Transformer est ["Attention Is All You Need"](https://arxiv.org/abs/1706.03762) ! Nous allons explorer les détails des couches d'attention plus tard dans ce cours; pour le moment, ce qu'il faut retenir est que la couche d'attention va indiquer au modèle les mots, de la phrase d'entrée, sur lesquels porter son attention (et plus ou moins ignorer les autres) lorsqu'il s'agit de construire une représentation de chaque mot. + +Pour mettre ceci en contexte, considérons la tâche de traduction de l'anglais vers le français. Étant donné l'entrée "You like this course", un modèle de traduction devra également s'occuper du mot adjacent "You" pour obtenir la traduction correcte du mot "like", car en français le verbe "aimer" est conjugué différemment selon l'objet. Le reste de la phrase, cependant, n'est pas utile pour la traduction de ce mot. De même, lors de la traduction de "this", le modèle devra également faire attention au mot "course", car "this" se traduit différemment selon que le nom associé est masculin ou féminin. Encore une fois, les autres mots de la phrase n'auront pas d'importance pour la traduction de "this". Avec des phrases plus complexes (et des règles de grammaire plus complexes), le modèle devrait accorder une attention particulière aux mots qui pourraient apparaître plus loin dans la phrase pour traduire correctement chaque mot. + +Le même concept s'applique à n'importe quelle tâche associée au langage naturel : un mot seul est porteur de sens, mais ce sens est profondément affecté par le contexte, qui peut être un ou plusieurs mots se situant avant ou après le mot étudié. + +Maintenant que vous avez une idée plus précise des couches d'attentions, nous allons regarder de plus près l'architecture des modèles Transformers. + +## L'architecture originelle + +L'architecture Transformer a initialement été construite pour des tâches de traduction. Pendant l'entraînement, l'encodeur reçoit des entrées (des phrases) dans une certaine langue, tandis que le décodeur reçoit la même phrase traduite dans la langue cible. Pour l'encodeur, les couches d'attention peuvent utiliser tous les mots d'une phrase (puisque, comme nous venons de le voir, la traduction d'un mot donné peut dépendre de ce qui le suit ou le précède dans la phrase). Le décodeur, quant à lui, fonctionne de façon séquentielle et ne peut porter son attention qu'aux mots déjà traduits dans la phrase (donc, uniquement les mots générés avant le mot en cours). Par exemple, lorsqu'on a prédit les trois premiers mots de la phrase cible, on les donne au décodeur qui utilise alors toutes les entrées de l'encodeur pour essayer de prédire le quatrième mot. + +Pour accélérer les choses pendant l'apprentissage (lorsque le modèle a accès aux phrases cibles), le décodeur est alimenté avec la cible entière, mais il n'est pas autorisé à utiliser les mots futurs (s'il avait accès au mot en position 2 lorsqu'il essayait de prédire le mot en position 2, le problème ne serait pas très difficile !). Par exemple, en essayant de prédire le quatrième mot, la couche d'attention n'aura accès qu'aux mots des positions 1 à 3. + +L'architecture originale des modèles Transformers ressemblait à ceci, avec l'encodeur à gauche et le décodeur à droite : + +
+Architecture of a Transformers models + +
+ +Notez que la première couche d'attention dans un bloc décodeur porte sont attention à toutes les entrées (passées) du décodeur, mais la seconde couche d'attention utilise la sortie de l'encodeur. Il peut ainsi accéder à toute la phrase d'entrée pour prédire au mieux le mot actuel. Ceci est très utile puisque les différentes langues peuvent avoir des règles grammaticales qui placent les mots dans des ordres différents, ou un contexte fourni plus tard dans la phrase peut être utile pour déterminer la meilleure traduction d'un mot donné. + +Le *masque d'attention* peut également être utilisé dans un modèle encodeur/décodeur pour l'empêcher de porter son attention sur certains mots spéciaux -- par exemple, le motde remplissage spécial utilisé pour que toutes les entrées aient la même longueur lors du regroupement de phrases. + +## Architectures contre checkpoints + +En approfondissant l'étude des modèles Transformers dans ce cours, vous verrez des mentions d'*architectures* et de *checkpoints* ainsi que de *modèles*. Ces termes ont tous des significations légèrement différentes : + +* **Architecture**: C'est le squelette du modèle -- la définition de chaque couche et chaque opération qui se produit au sein du modèle. +* **Checkpoints**: Ce sont les poids qui seront chargés dans une architecture donnée. +* **Model**: C'est un mot valise n'étant pas aussi précis que les mots "architecture" ou "checkpoint": il peut désigner l'un comme l'autre. Dans ce cours, il sera spécifié *architecture* ou *checkpoint* lorsqu'il est nécessaire de réduire l'ambiguité. + +Par exemple, BERT est une architecture alors que `bert-base-cased`, un ensemble de poids entraîné par l'équipe de Google lors de la première sortie de BERT, est un checkpoint. Cependant, il est possible de dire "le modèle BERT" et "le modèle `bert-base-cased`". diff --git a/chapters/fr/chapter1/5.mdx b/chapters/fr/chapter1/5.mdx new file mode 100644 index 000000000..3a7b2a7fb --- /dev/null +++ b/chapters/fr/chapter1/5.mdx @@ -0,0 +1,17 @@ +# Les modèles encodeurs + + + +Les modèles encodeurs utilisent uniquement l'encodeur d'un modèle Transformer. À chaque étape, les couches d'attention peuvent accéder à tous les mots de la phrase initiale. Ces modèles sont souvent caractérisés comme ayant une attention "bi-directionnelle" et sont souvent appelés *modèles d'auto-encodage*. + +Le pré-entraînement de ces modèles se concentre généralement sur la modification d'une phrase donnée (par exemple, en masquant des mots aléatoires dans celle-ci) et en demandant au modèle de trouver ou de reconstruire la phrase initiale. + +Ces modèles encodeurs sont les plus adaptés pour des tâches qui requièrent une compréhension complète de la phrase, telles que la classification de phrases, la reconnaissance des entités nommées (et plus généralement la classification de mots) et les questions-réponses extractives. + +Les modèles les plus représentatifs de cette famille sont: + +- [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) diff --git a/chapters/fr/chapter1/6.mdx b/chapters/fr/chapter1/6.mdx new file mode 100644 index 000000000..21b8e6df0 --- /dev/null +++ b/chapters/fr/chapter1/6.mdx @@ -0,0 +1,16 @@ +# Les modèles décodeurs + + + +Les modèles décodeurs utilisent seulement le décodeur d'un modèle Transformer. À chaque étape, pour un mot donné, les couches d'attention ne peuvent strictement accéder qu'aux mots situés avant dans la phrase. Ces modèles sont souvent appelés *modèles auto-régressifs*. + +Le pré-entraînement des modèles décodeurs se concentre généralement sur la prédiction du prochain mot dans la phrase. + +Ces modèles sont vraiment adaptés aux tâches qui impliquent la génération de texte. + +Les modèles qui représentent le mieux la famille des modèles décodeurs sont : + +- [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) +- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) +- [GPT-2](https://huggingface.co/transformers/model_doc/gpt2.html) +- [Transformer XL](https://huggingface.co/transformers/model_doc/transformerxl.html) diff --git a/chapters/fr/chapter1/7.mdx b/chapters/fr/chapter1/7.mdx new file mode 100644 index 000000000..6b1445075 --- /dev/null +++ b/chapters/fr/chapter1/7.mdx @@ -0,0 +1,16 @@ +# Les modèles de séquence-à-séquence + + + +Les modèles encodeur-décodeur (également appelés modèles de séquence-à-séquence) utilisent les deux parties de l'architecture Transformer. À chaque étape, les couches d'attention de l'encodeur peuvent accéder à tous les mots de la phrase initiale, tandis que les couches d'attention du décodeur n'ont accès qu'aux mots positionnés avant un mot donné en entrée de ces couches d'attention. + +Le pré-entraînement de ces modèles peut être fait en utilisant les objectifs des modèles d'encodeur ou de décodeur, mais en général cela implique quelque chose de plus complexe. Par exemple, le modèle [T5](https://huggingface.co/t5-base) est pré-entraîné en remplaçant des zones aléatoires de texte (qui peuvent contenir plusieurs mots) par un mot-masque spécial, et l'objectif est alors de prédire le texte que ce mot-masque remplace. + +Les modèles de séquence-à-séquence sont les plus adaptés pour les tâches liées à la génération de nouvelles phrases en fonction d'une entrée donnée, comme le résumé de texte, la traduction ou la génération de question-réponse. + +Les modèles qui représentent le mieux cette famille sont: + +- [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) diff --git a/chapters/fr/chapter1/8.mdx b/chapters/fr/chapter1/8.mdx new file mode 100644 index 000000000..1f3b19528 --- /dev/null +++ b/chapters/fr/chapter1/8.mdx @@ -0,0 +1,32 @@ +# Biais et limitations + + + +Si vous souhaitez utiliser un modèle pré-entraîné ou une version fine-tunée de celui-ci en production, il est important d'avoir conscience que, bien que ces modèles soient puissants, ils ont des limites. La plus importante de ces limitations est que, pour permettre le pré-entraînement des modèles sur de grandes quantités de données, les chercheurs récupèrent souvent tout le contenu qu'ils peuvent trouver, en prenant le meilleur et le pire de ce qui est disponible sur internet. + +Pour illustrer cela rapidement, revenons au modèle de remplacement de mot-masque avec le modèle BERT: + +```python +from transformers import pipeline + +unmasker = pipeline("fill-mask", model="bert-base-uncased") +result = unmasker("This man works as a [MASK].") +print([r["token_str"] for r in result]) + +result = unmasker("This woman works as a [MASK].") +print([r["token_str"] for r in result]) +``` + +```python out +['lawyer', 'carpenter', 'doctor', 'waiter', 'mechanic'] +['nurse', 'waitress', 'teacher', 'maid', 'prostitute'] +``` + +Lorsque l'on demande au modèle de remplacer le mot manquant dans ces deux phrases, il ne propose qu'un seul métier ne portant pas la marque du genre (waiter/waitress = serveur/serveuse). Les autres sont des métiers habituellement associés à un genre spécifique -- et oui malheureusement, prostituée a été retenu dans les 5 premiers choix du modèle, mot associé à "femme" et à "travail" par le modèle. Cela se produit même si BERT est l'un des rare modèles Transformer qui n'a pas été construit avec des données récupérées par scrapping sur internet, mais à l'aide de données en apparence neutres (il est entraîné sur les jeux de données suivants : [Wikipédia Anglais](https://huggingface.co/datasets/wikipedia) et [BookCorpus](https://huggingface.co/datasets/bookcorpus)). + +Donc lorsque vous utilisez ce genre d'outils, il est important de garder en tête que le modèle que vous utilisez peut rapidement générer du contenu sexiste, raciste ou homophobe. Le fine-tuning de ce modèle sur vos données ne fera en aucun cas disparaître ce biais intrinsèque. diff --git a/chapters/fr/chapter1/9.mdx b/chapters/fr/chapter1/9.mdx new file mode 100644 index 000000000..c44ca177a --- /dev/null +++ b/chapters/fr/chapter1/9.mdx @@ -0,0 +1,11 @@ +# Résumé du chapitre + +Au cours de ce chapitre, vous avez vu comment approcher différents problèmes de NLP en utilisant la fonction `pipeline()` de la librairie 🤗 Transformers. Vous avez aussi vu comment rechercher et utiliser des modèles dans le Hub, ainsi que comment utiliser l'API d'inférence pour tester les modèles directement dans votre navigateur. + +Nous avons pu aborder le fonctionnement des modèles Transformers de façon générale, et nous avons parlé de l'importance du transfer learning et du fine-tuning. Un aspect important est que vous pouvez utiliser l'architecture complète ou seulement l'encodeur ou le décodeur, selon le type de tâche que vous souhaitez résoudre. Le tableau suivante résume ceci : + +| Modèle | Exemples | Tâches | +|-------------------|--------------------------------------------|----------------------------------------------------------------------------------------------| +| Encodeur | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa | Classification de phrase, reconnaissance des entités nommées, extraction de question-réponse | +| Décodeur | CTRL, GPT, GPT-2, Transformer XL | Génération de texte | +| Encodeur-décodeur | BART, T5, Marian, mBART | Résumé, traduction, génération de question-réponse | diff --git a/chapters/ja/_toctree.yml b/chapters/ja/_toctree.yml new file mode 100644 index 000000000..e18d55704 --- /dev/null +++ b/chapters/ja/_toctree.yml @@ -0,0 +1,4 @@ +- title: 0. セットアップ + sections: + - local: chapter0/1 + title: イントロダクション diff --git a/chapters/ja/chapter0/1.mdx b/chapters/ja/chapter0/1.mdx new file mode 100644 index 000000000..fea5d0d10 --- /dev/null +++ b/chapters/ja/chapter0/1.mdx @@ -0,0 +1,110 @@ +# イントロダクション + +Hugging Faceコースへようこそ!このページでは、実行環境のセットアップ方法についてご案内します。もし、このコースを始めたばかりでしたら、ますは[第1章](/course/chapter1)を見てからここに戻ってきて、コードをご自身で試せるように環境をセットアップすることをお勧めします。 + +このコースで使用するライブラリは全てPythonパッケージとして提供されているため、ここではPython環境の構築と必要なライブラリのインストール方法について説明します。 + +実行環境をセットアップする方法として、Colabノートブックを使用する方法とPythonの仮想環境を使用する方法の2つを取り上げます。ご自身で好みの方法をご自由にお選びください。初心者の方には、Colabノートブックを使う方法から始めることを強くお勧めします。 + +なお、Windowsシステムは取り扱わない点についてご了承ください。もしWindowsで実行される場合は、Colabノートブックを使用することをお勧めします。LinuxやmacOSで実行される場合は、ここで説明するどちらの方法をお選びいただいても構いません。 + +このコースの大半の場面では、Hugging Faceアカウントをお持ちであることが前提となっています。お持ちでない場合、今すぐ作成することをお勧めします: [アカウント作成](https://huggingface.co/join). + +## Google Colabノートブックを使用する場合 + +Colabノートブックを使用してセットアップを行うのが、一番シンプルな方法です。ブラウザでノートブックを起動し、すぐにコーディングを始めることができます! + +Colabに馴染みのない方は、まず[Colabのイントロダクション](https://colab.research.google.com/notebooks/intro.ipynb)をご覧いただくことをお勧めします。Colabでは、GPUやTPUのようなアクセラレーターを使用することができ、小規模な作業であれば無料で使用することができます。 + +Colabでの操作に慣れたら、新しいノートブックを作成してセットアップを始めましょう。 + +
+An empty colab notebook +
+ +次のステップとして、このコースで使用するライブラリのインストールを行います。インストールには、Pythonのパッケージマネージャーである`pip`を使用します。ノートブック内では、システムコマンドの前に`!`をつけることで実行が可能となるため、次のようにして🤗 Transformersライブラリをインストールすることができます。 + +``` +!pip install transformers +``` + +パッケージが正常にインストールされたかどうかは、Pythonランタイム内でインポートすることで確認できます。 + +``` +import transformers +``` + +
+A gif showing the result of the two commands above: installation and import +
+ +上記の方法では、非常に軽量なバージョンの🤗 Transformersをインストールしています。特に、特定の機械学習フレームワーク(PyTorchやTensorFlowなど)はインストールしていません。このコースでは、transformersの多くの機能を使うことになるため、想定しているユースケースで必要な依存関係が全て含まれている開発バージョンをインストールすることをお勧めします。 + +``` +!pip install transformers[sentencepiece] +``` + +このインストールには少し時間がかかりますが、これで残りのコースを問題なく進めることができます! + +## Pythonの仮想環境を使用する場合 + +Pythonの仮想環境を使用したい場合は、まず手元のシステムにPythonをインストールしておく必要があります。このコースでは、[このガイド](https://realpython.com/installing-python/)に従って始めることをお勧めします. + +Pythonのインストールが完了すると、ターミナルでPythonコマンドが実行できるようになるはずです。まずは`python --version`を実行して、正常にインストールされていることを確認してから次のステップに進んでください。このコマンドを実行すると、システムで現在利用可能なPythonのバージョンが出力されます。 + +ターミナルで`python --version`のようなPythonコマンドを実行する場合、コマンドを実行するプログラムはシステムのメインのPythonと考えるべきです。このメインのPythonにはパッケージが存在しない状態を維持し、取り組むそれぞれの内容ごとに個別の環境を作成するのに使用することをお勧めします。この方法では、それぞれの環境が依存関係とパッケージを独立して持つことができ、他の取り組みとの互換性の問題を気にする必要がなくなります。 + +これはPythonでは、[仮想環境](https://docs.python.org/3/tutorial/venv.html)によって行われます。仮想環境とは、必要な全てのパッケージとともに特定のバージョンのPythonをインストールした自己完結型のディレクトリツリーのことです。仮想環境の作成は多くのツールで行うことが可能ですが、このコースでは[`venv`](https://docs.python.org/3/library/venv.html#module-venv)と呼ばれるPythonの公式パッケージを使用します。 + +まずは、作業を行うディレクトリを作成します。例として、ホームディレクトリのルートに*transformers-course*という新しいディレクトリを作成するとよいでしょう。 + +``` +mkdir ~/transformers-course +cd ~/transformers-course +``` + +このディレクトリの中で、Pythonの`venv`モジュールを用いて仮想環境を作成します。 + +``` +python -m venv .env +``` + +これで、何も入っていなかったディレクトリに*.env*というディレクトリができたはずです。 + +``` +ls -a +``` + +```out +. .. .env +``` + +仮想環境は、`activate`と`deactivate`というスクリプトで有効化したり、無効化することが可能です。 + +``` +# Activate the virtual environment +source .env/bin/activate + +# Deactivate the virtual environment +source .env/bin/deactivate +``` + +仮想環境が有効になっているかどうかは、`which python`というコマンドを実行することで確認することができます。もし以下のように仮想環境であることを示していれば、正常に有効化できています! + +``` +which python +``` + +```out +/home//transformers-course/.env/bin/python +``` + +### 依存関係のインストール + +前のセクションで扱ったGoogle Colabのインスタンスを使用する場合と同様に、この先で必要となるパッケージをインストールする必要があります。ここでも、`pip`というパッケージマネージャーを使用して🤗 Transformersの開発バージョンをインストールすることが可能です。 + +``` +pip install "transformers[sentencepiece]" +``` + +これでセットアップは完了です! diff --git a/chapters/th/_toctree.yml b/chapters/th/_toctree.yml index ffd290a33..0b5d7be22 100644 --- a/chapters/th/_toctree.yml +++ b/chapters/th/_toctree.yml @@ -8,11 +8,26 @@ - local: chapter1/1 title: บทนำ -- title: 2. การใช้ 🤗 Transformers +- title: 2. การใช้งาน 🤗 Transformers sections: - local: chapter2/1 title: บทนำ - + - local: chapter2/2 + title: เบื้องหลังของ pipeline + - local: chapter2/3 + title: โมเดล + - local: chapter2/4 + title: Tokenizers + - local: chapter2/5 + title: การจัดการกับหลายๆประโยค(multiple sequences) + - local: chapter2/6 + title: ประกอบทุกอย่างเข้าด้วยกัน + - local: chapter2/7 + title: การใช้งานเบื้องต้นสำเร็จแล้ว! + - local: chapter2/8 + title: แบบทดสอบท้ายบท + quiz: 2 + - title: 3. การ fine-tune โมเดลที่ผ่านการเทรนมาแล้ว (pretrained model) sections: - local: chapter3/1 diff --git a/chapters/th/chapter2/1.mdx b/chapters/th/chapter2/1.mdx index 8c354032d..dbb602d76 100644 --- a/chapters/th/chapter2/1.mdx +++ b/chapters/th/chapter2/1.mdx @@ -12,7 +12,7 @@ บทนี้จะเริ่มด้วยตัวอย่างแบบ end-to-end ซึ่งเราจะใช้โมเดลและ tokenizer ร่วมกันเพื่อทำซ้ำ(เลียนแบบ) ฟังก์ชัน `pipeline()` จากที่เรียนใน [Chapter 1](/course/chapter1) หลังจากนั้นเราจะมาเรียนเกี่ยวกับ API ของโมเดล โดยเราจะเจาะลึกในคลาสของโมเดลและการตั้งค่า (configuration) และจะแสดงวิธีการโหลดโมเดลและกระบวนการที่โมเดลทำการทำนายผลจากชุดข้อมูลเชิงตัวเลข ว่าทำอย่างไร -หลังจากนั้นเราจะไปดูกันที่ tokenizer API ซึ่งเป็นอีกหนึ่งส่วนประกอบหลักของฟังก์ชัน `pipeline()`, Tokenizers จะรับผิดชอบการประมวลขั้นแรกและขั้นสุดท้าย ซึ่งก็คือ การแปลงข้อมูลที่เป็นข้อความให้เป็นข้อมูลเชิงตัวเลข เพื่อใช้กับ neural network, และการแปลงข้อมูลกลับไปเป็นตัวอักษร ในกรณีที่จำเป็น และสุดท้ายเราจะแสดงวิธีการจัดการกับการส่งข้อความทีละหลายๆประโยคแบบที่เตรียมไว้เป็นชุดๆ (batch) ไปยังโมเดล และปิดท้ายด้วยฟังก์ชัน `tokenizer()` +หลังจากนั้นเราจะไปดูกันที่ tokenizer API ซึ่งเป็นอีกหนึ่งส่วนประกอบหลักของฟังก์ชัน `pipeline()`, Tokenizers จะรับผิดชอบการประมวลผลขั้นแรกและขั้นสุดท้าย ซึ่งก็คือ การแปลงข้อมูลที่เป็นข้อความให้เป็นข้อมูลเชิงตัวเลข เพื่อใช้กับ neural network, และการแปลงข้อมูลกลับไปเป็นตัวอักษร ในกรณีที่จำเป็น และสุดท้ายเราจะแสดงวิธีการจัดการกับการส่งข้อความทีละหลายๆประโยคแบบที่เตรียมไว้เป็นชุดๆ (batch) ไปยังโมเดล และปิดท้ายด้วยฟังก์ชัน `tokenizer()` ⚠️ เพื่อให้ได้ประโยชน์สูงสุดจากคุณลักษณะเด่นทั้งหมดที่มีใน Model Hub และ 🤗 Transformers, เราแนะนำให้คุณ สร้างบัญชี. diff --git a/chapters/th/chapter2/2.mdx b/chapters/th/chapter2/2.mdx new file mode 100644 index 000000000..87968254b --- /dev/null +++ b/chapters/th/chapter2/2.mdx @@ -0,0 +1,353 @@ + + +# เบื้องหลังของ pipeline + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + +Section นี้จะเป็น Section แรกที่เนื้อหาจะค่อนข้างแตกต่างกันขึ้นอยู่กับว่าคุณใช้ PyTorch หรือ TensorFlow คุณสามารถเลือก plateform ที่คุณต้องการได้จากปุ่มที่อยู่ด้านบนของชื่อหัวข้อ! + + +{#if fw === 'pt'} + +{:else} + +{/if} + +เรามาเริ่มกันด้วยตัวอย่างนี้ โดยเรามาดูกันว่าเกิดอะไรขึ้นในเบื้องหลังเมื่อเราทำการสั่งการ(executed) โค้ดด้านล่างนี้จาก [Chapter 1](/course/chapter1): + +```python +from transformers import pipeline + +classifier = pipeline("sentiment-analysis") +classifier( + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] +) +``` + +และ ผลลัพธ์ที่ได้: + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}, + {'label': 'NEGATIVE', 'score': 0.9994558095932007}] +``` + +อย่างที่เราเห็นใน [Chapter 1](/course/chapter1), pipeline นี้เป็นการรวมเอา 3 ขั้นตอน : แ(preprocessing), ส่งข้อมูลเข้าไปยังโมเดล, และการประมวลผลข้อมูลที่ออกมาจากโมเดล (postprocessing) + +
+The full NLP pipeline: tokenization of text, conversion to IDs, and inference through the Transformer model and the model head. + +
+ +เรามาดูแต่ละขั้นตอนเหล่านี้กันอย่างละเอียดเลยดีกว่า + +## การประมวลผลข้อมูลขั้นต้น(Preprocessing) ด้วย tokenizer + +เหมือนกับโครงข่ายประสาท(neural networks) อื่นๆ, โมเดล Transformer ไม่สามารถที่จะประมวลผลข้อมูลที่เป็นข้อความ(text) ได้ตรงๆ ดังนั้นขั้นแรกของ pipeline ก็คือการแปลงข้อความให้เป็นตัวเลข(numbers) ที่โมเดลนั้นสามารถประมวลผลได้ ในกระบวนการนี้เราจะใช้ *tokenizer* ซึ่งจะรับผิดชอบในการทำ : + +- แบ่งข้อมูลออกเป็น คำ(words), หน่วยย่อยของคำ (subwords), หรือ สัญลักษณ์ (เช่น เครื่องหมายวรรคตอน (punctuation)) เหล่านี้เราเรียกว่า *token* +- ทำการเชื่อมโยง (Mapping) แต่ละ token ไปเป็นตัวเลข (integer) +- เพิ่มเติมข้อมูลที่อาจจะเป็นประโยชน์กับโมเดล + +กระบวนการประมวลผลข้อมูลขั้นต้น(preprocessing) ทั้งหมดนี้จำเป็นที่จะต้องเป็นไปในแนวทางที่เหมือนกับตอนที่โมเดลได้ผ่านการเทรนมาก่อนหน้านี้(pretrained), ดังนั้นสิ่งแรกที่เราจำเป็นต้องทำคือดาวน์โหลดข้อมูลจาก [Model Hub](https://huggingface.co/models) ในการดาวน์โหลดนี้ เราสามารถทำได้โดยใช้คลาส `AutoTokenizer` และเรียกเมธอด `from_pretrained()` โดยหากระบุชื่อ checkpoint ของโมเดล เมธอด `from_pretrained()` จะทำการดึงข้อมูลทั้งหมดที่เกี่ยวกับ tokenizer ของโมเดลมาเก็บไว้(จะดาวน์โหลดเพียงครั้งเดียวตอนที่เรารันโค้ดด้านล่างนี้ครั้งแรกเท่านั้น) + +เนื่องจาก checkpoint เริ่มต้น(default) ของ `sentiment-analysis` pipeline คือ `distilbert-base-uncased-finetuned-sst-2-english` (สามารถดู model card [ที่นี่](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english)), ดังนั้นเราจะรันโค้ดนี้: + +```python +from transformers import AutoTokenizer + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` + +เมื่อเรามี tokenizer แล้ว เราสามารถใส่ประโยคข้อความของเราเข้าไปและเราจะได้สารานุกรม(dictionary) ที่พร้อมนำไปใส่ในโมเดลออกมา! สิ่งเดียวที่เหลือที่ต้องทำ คือ การแปลงข้อมูลอัตลักษณ์(IDs) ของข้อมูลที่ใส่เข้าไป(input) ไปเป็น tensors + +คุณสามารถใช้ 🤗 Transformers โดยที่ไม่จำเป็นต้องกังวลเลยว่า ML framework ตัวไหนที่ใช้เป็น backend; มันอาจจะเป็น PyTorch หรือ TensorFlow, หรือ Flax สำหรับบางโมเดล แต่อย่างไรก็ตามโมเดล Transformer จะรับเพียง *tensor* เป็นข้อมูลที่ใส่เข้าไป(input) เท่านั้น ถ้านี่เป็นครั้งแรกที่คุณได้ยินเกี่ยวกับคำว่า tensor คุณสามารถเปรียบเทียบมันเป็นเหมือน NumPy arrays แทนก็ได้ โดยที่ NumPy array สามารถเป็นได้ทั้ง scalar (0D), vector (1D), matrix (2D), หรือมีหลายๆมิติ. เหล่านี้ก็คือ tensor ดีๆนี่เอง tensor ของ ML frameworks อื่นๆ ก็มีลักษณะคล้ายๆกัน และสามารถสร้างขึ้นได้ง่ายเหมือน NumPy arrays + +การกำหนดประเภทของ tensor ที่เราต้องการได้กลับมา (PyTorch, TensorFlow, หรือ NumPy) เราสามารถใช้ตัวแปร(argument) `return_tensors`: + +{#if fw === 'pt'} +```python +raw_inputs = [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", +] +inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="pt") +print(inputs) +``` +{:else} +```python +raw_inputs = [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", +] +inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="tf") +print(inputs) +``` +{/if} + +ไม่ต้องกังวลเกี่ยวการเพิ่ม(padding) และการตัดออก(truncation) ในตอนนี้ เราจะอธิบายทีหลัง สิ่งหลักๆ ที่ควรจำคือ คุณสามารถที่จะส่งผ่านประโยคหนึ่งประโยค หรือ รายการ(list)ของประโยค พร้อมทั้งระบุประเภทของ tensor ที่คุณต้องการได้กลับมา(ถ้าไม่ระบุประเภท คุณจะได้ผลกลับมาเป็น list of lists) + +{#if fw === 'pt'} + +ผลลัพธ์ที่เป็น tensor ของ PyTorch ก็จะหน้าตาประมาณนี้ + +```python out +{ + 'input_ids': tensor([ + [ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012, 102], + [ 101, 1045, 5223, 2023, 2061, 2172, 999, 102, 0, 0, 0, 0, 0, 0, 0, 0] + ]), + '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] + ]) +} +``` +{:else} + +ผลลัพธ์ที่เป็น tensor ของ PyTorch ก็จะหน้าตาประมาณนี้ + +```python out +{ + 'input_ids': , + 'attention_mask': +} +``` +{/if} + +ผลลัพธ์จะเป็นสารานุกรม(dictionary) ที่มี 2 คีย์(keys), `input_ids` และ `attention_mask` โดยที่ `input_ids` จะมีข้อมูลเป็นตัวเลข(integers)จำนวนสองแถว (หนึ่งแถวต่อหนึ่งประโยค) ซึ่งเป็นอัตลักษณ์ที่เฉพาะเจาะจงของ token ในแต่ละประโยค ส่วน `attention_mask` เราจะอธิบายในบทนี้อีกครั้งหลังจากนี้ + +## มาอธิบายเกี่ยวกับโมเดลกัน + +{#if fw === 'pt'} +เราสามารถดาวน์โหลดโมเดลที่ผ่านการเทรนมาแล้ว(pretrained) ของเราได้เหมือนกับที่เราทำกับ tokenizer ของเรา 🤗 Transformers มีคลาส `AutoModel` ซึ่งก็มีเมธอด `from_pretrained()` เช่นกัน: + +```python +from transformers import AutoModel + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = AutoModel.from_pretrained(checkpoint) +``` +{:else} +เราสามารถดาวน์โหลดโมเดลที่ผ่านการเทรนมาแล้ว(pretrained) ของเราได้เหมือนกับที่เราทำกับ tokenizer ของเรา 🤗 Transformers มีคลาส `TFAutoModel` ซึ่งก็มีเมธอด `from_pretrained()` เช่นกัน: + +```python +from transformers import TFAutoModel + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = TFAutoModel.from_pretrained(checkpoint) +``` +{/if} + +ในตัวอย่างโค้ดนี้ เราดาวน์โหลด checkpoint เดียวกันกับที่เราใช้ใน pipeline ของเราก่อนหน้านี้ (มันน่ามีการเก็บ(cached)ไว้แล้ว) และสร้างโมเดลขึ้นมาพร้อมกัน + +ในสถาปัตยกรรมนี้จะมีเฉพาะโมดูล Transformer พื้นฐาน: ให้ข้อมูลเข้าไป และมันให้สิ่งที่เราเรียกว่า *hidden states* ออกมา หรือที่เรารู้จักกันในนาม *features* สำหรับข้อมูลอินพุตของแต่ละโมเดล เราจะทำการหาเวกเตอร์หลายมิติ(high-dimensional vector) ที่บ่งบอก **ความเข้าใจสภาวะแวดล้อมของข้อมูลนั้นโดนโมเดล Transformer** + +ถ้านี้ฟังดูไม่สมเหตุสมผล ไม่ต้องกังวล เดี๋ยวเราจะอธิบายทัั้งหมดอีกครั้ง + +ในขณะที่ hidden states เหล่านี้เป็นประโยชน์ในตัวมันอยู่แล้ว มันเลยถูกนำไปใช้กับส่วนอื่นของโมเดลด้วย ที่เรารู้จักกันในนาม *head* ใน [Chapter 1](/course/chapter1), งานที่แตกต่างกันอาจจะสามารถใช้สถาปัตยกรรมเหมือนกันได้ แต่งานแต่ละอย่างจะมีส่วนหัว(head)ที่แตกต่างกันไป + +### เวคเตอร์หลายมิติ (A high-dimensional vector) ? + +เวอเตอร์ที่ได้จากโมดูลของ Transformer นั้นปกติจะมีขนาดใหญ่ โดยทั่วไปแล้วมันจะมี 3 มิติ: + +- **ขนาดของชุด(ฺBatch size)**: จำนวนของประโยคที่ผ่านการประมวลผล (2 ประโยคในตัวอย่างของเรา) +- **ความยาวของประโยค(Sequence length)**: ความยาวของตัวเลขที่เป็นตัวแทนของประโยค (16 ตัวเลขในตัวอย่างของเรา) +- **ขนาดของ Hidden states (Hidden size)**: มิติของเวคเตอร์ของแต่ละข้อมูลที่ให้เข้าไปยังโมเดล + +มันจะถูกบอกว่ามันเป็นเวคเตอร์ "หลายมิติ(high dimensional)" ก็เพราะค่าสุดท้าย ขนาดของ hidden states นั้นสามารถมีขนาดที่ใหญ่มาก(786 เป็นค่าที่ใช้กันทั่วไปสำหรับโมเดลขนาดเล็ก, ส่วนในโมเดลขนาดใหญ่นั้นสามารถขึ้นไปได้ถึง 3072 หรือมากกว่านั้น) + +เราจะเห็นได้ว่าถ้าเราใส่ข้อมูลที่เราประมวลผลมาแล้วเข้าไปยังโมเดลของเรา: + +{#if fw === 'pt'} +```python +outputs = model(**inputs) +print(outputs.last_hidden_state.shape) +``` + +```python out +torch.Size([2, 16, 768]) +``` +{:else} +```py +outputs = model(inputs) +print(outputs.last_hidden_state.shape) +``` + +```python out +(2, 16, 768) +``` +{/if} + +จะสังเกตว่าข้อมูลที่ออกจากโมเดล 🤗 Transformers นั้นจะมีลักษณะเหมือนกับ `namedtuple`s หรือ dictionaries คุณสามารถเข้าถึงองค์ประกอบต่าง(elements) ได้ด้วย attributes (เหมือนที่เราทำ) หรือด้วย key (`outputs["last_hidden_state"]`) หรือแม้กระทั่งด้วย index ถ้าคุณรู้ว่าสิ่งที่คุณมองหานั้นอยู่ตรงไหน (`outputs[0]`) + +### Model heads: ทำความเข้าใจจากตัวเลข + +โมเดล heads รับเวคเตอร์หลายมิติ(high-dimensional) ของ hidden states เข้าไปและจะทำการโปรเจคเวคเตอร์ดังกล่าวไปยังมิติอื่น ซึ่งโดยปกติโมเดล heads จะประกอบด้วยเลเยอร์เชิงเส้น(linear layers) อย่างน้อยหนึ่งเลเยอร์: + +
+A Transformer network alongside its head. + +
+ +เอาท์พุตของโมเดล Transformer จะส่งตรงไปที่หัวโมเดล(model head)เพื่อทำการประมวลผล + +ในไดอะแกรมนี้ โมเดลจะถูกแทนที่ด้วยเลเยอร์ฝังตัว(embeddings layer) และเลเยอร์ย่อย(subsequent layers) ของตัวมันเอง โดยเลเยอร์ฝังตัว(embeddings layer) จะทำการแปลงตัวบ่งชี้ตัวตนของอินพุต(Input ID) ที่อยู่ในอินพุตที่เป็น tokenized ไปเป็นเวคเตอร์ที่่เป็นตัวแทนของ token ที่เกี่ยวข้อง ส่วนเลเยอร์ย่อยอื่นๆจะจัดการเวคเตอร์อื่นโดยใช้กระบวนการ attention เพื่อให้ได้มาซึ่งตัวแทน(representation) สุดท้ายของประโยค + +มีหลายสถาปัตยกรรมใน 🤗 Transformers โดยที่หนึ่งสถาปัตยกรรมถูกออกแบบมาให้ใช้กับงานเฉพาะหนึ่งงาน นี่เป็นเพียงส่วนหนึ่งจากหลายๆโมเดล : + +- `*Model` (retrieve the hidden states) +- `*ForCausalLM` +- `*ForMaskedLM` +- `*ForMultipleChoice` +- `*ForQuestionAnswering` +- `*ForSequenceClassification` +- `*ForTokenClassification` +- and others 🤗 + +{#if fw === 'pt'} +สำหรับในตัวอย่างของเรานั้น เราต้องการโมเดลที่มี head สำหรับการจำแนกประโยค(sequence classification) (โดยสามารถที่จะจำแนกประโยคว่าเป็นประโยคเชิงบวก หรือ ลบ) ดังนั้น เราจะไม่ใช้คลาส `AutoModel` แต่จะใช้ `AutoModelForSequenceClassification`: + +```python +from transformers import AutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +outputs = model(**inputs) +``` +{:else} +สำหรับในตัวอย่างของเรานั้น เราต้องการโมเดลที่มี head สำหรับการจำแนกประโยค(sequence classification) (โดยสามารถที่จะจำแนกประโยคว่าเป็นประโยคเชิงบวก หรือ ลบ) ดังนั้น เราจะไม่ใช้คลาส `TFAutoModel` แต่จะใช้ `TFAutoModelForSequenceClassification`: + +```python +from transformers import TFAutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +outputs = model(inputs) +``` +{/if} + +ถ้าเราดูที่ขนาด(shape) ของอินพุต มิติ(dimensionality)จะน้อยกว่ามาก : model head ที่รับเอาเวคเตอร์ขนาดหลายมิติเป็นอินพุตเหมือนที่เราเห็นก่อนหน้านี้ และให้เอาท์พุตเป็นเวคเตอร์ที่มีสองค่า (หนึ่งค่าต่อหนึ่งสัญลักษณ์(label)) : + +```python +print(outputs.logits.shape) +``` + +{#if fw === 'pt'} +```python out +torch.Size([2, 2]) +``` +{:else} +```python out +(2, 2) +``` +{/if} + +เนื่องจากเรามีแค่สองประโยคและสองสัญลักษณ์(labels) ผลลัพธ์ที่ได้จากโมเดลของเราจึงมีขนาด 2x2 + +## การประมวลหลังจากได้ผลลัพธ์มาแล้ว (Postprocessing) + +ค่าที่เราได้มาจากโมเดลนั้นไม่จำเป็นต้องดูเป็นเหตุเป็นผลในตัวมันเอง เดี๋ยวเราลองมาดูกัน: + +```python +print(outputs.logits) +``` + +{#if fw === 'pt'} +```python out +tensor([[-1.5607, 1.6123], + [ 4.1692, -3.3464]], grad_fn=) +``` +{:else} +```python out + +``` +{/if} + +โมเดลของเราทำนาย `[-1.5607, 1.6123]` สำหรับประโยคแรก และ `[ 4.1692, -3.3464]` สำหรับประโยคที่สอง ค่าเหล่านี้ไม่ใช่ค่าความน่าจะเป็น(probabilities) แต่เป็นค่า *logits* เป็นคะแนนดิบที่ยังไม่ผ่านการ normalized ที่ส่งออกมาจากเลเยอร์สุดท้ายของโมเดล การแปลงค่าคะแนนไปเป็นค่าความน่าจะเป็น(probabilities) คะแนนเหล่านี้จำเป็นที่จะต้องผ่านเลเยอร์ที่ชื่อว่า [SoftMax](https://en.wikipedia.org/wiki/Softmax_function) (โมเดลของ 🤗 Transformers ทั้งหมด จะส่งข้อมูลออกมาเป็น logits โดยที่ loss function ของการเทรนโมเดลจะทำการรวม activation function ของเลเยอร์สุดท้าย เช่น SoftMax เข้ากับ loss function หลัก เช่น cross entropy): + +{#if fw === 'pt'} +```py +import torch + +predictions = torch.nn.functional.softmax(outputs.logits, dim=-1) +print(predictions) +``` +{:else} +```py +import tensorflow as tf + +predictions = tf.math.softmax(outputs.logits, axis=-1) +print(predictions) +``` +{/if} + +{#if fw === 'pt'} +```python out +tensor([[4.0195e-02, 9.5980e-01], + [9.9946e-01, 5.4418e-04]], grad_fn=) +``` +{:else} +```python out +tf.Tensor( +[[4.01951671e-02 9.59804833e-01] + [9.9945587e-01 5.4418424e-04]], shape=(2, 2), dtype=float32) +``` +{/if} + +ตอนนี้เราจะเห็นว่าโมเดลทำนาย `[0.0402, 0.9598]` สำหรับประโยคแรก และ `[0.9995, 0.0005]` สำหรับประโยคที่สอง ซึ่งคะแนนเหล่านี้คือคะแนนความน่าจะเป็น(probabilities score) ที่สามารถนำไปจำแนกได้ + +เพื่อที่จะให้ได้สัญลักษณ์(label) ของแต่ละตำแหน่ง เราสามารถดูได้จากคุณสมบัติ `id2label` ของโมเดล model config (เดี๋ยวเราจะอธิบายเพิ่มเติมใน section ถัดไป): + +```python +model.config.id2label +``` + +```python out +{0: 'NEGATIVE', 1: 'POSITIVE'} +``` + +เราสามารถที่จะสรุปได้ว่าโมเดลทำการทำนายดังต่อไปนี้ + +- ประโยคแรก: NEGATIVE: 0.0402, POSITIVE: 0.9598 +- ประโยคที่สอง: NEGATIVE: 0.9995, POSITIVE: 0.0005 + +ถึงตรงนี้ประสบความสำเร็จในการลองทำ สาม ขั้นตอนของ pipeline: การประมวลผลเบื้องต้น(preprocessing)โดยใช้ tokenizers, ส่งข้อมูลเข้าไปยังโมเดล,และการประมวลผลข้อมูลที่ได้จากโมเดล! ต่อจากนี้เราจะไปลงลึกในรายละเอียดของแต่ละขั้นตอน + + + +✏️ **ลองเลย!** เลือกสอง(หรือมากกว่านั้น) ข้อความของคุณเองและลองใส่มันเข้าไปใน `sentiment-analysis` pipeline. แล้วทำขั้นตอนต่างๆ ที่คุณเรียนผ่านมาใน section นี้และตรวจสอบดูว่าคุณได้ผลเหมือนเดิมหรือไม่! + + diff --git a/chapters/th/chapter2/3.mdx b/chapters/th/chapter2/3.mdx new file mode 100644 index 000000000..6b82bd629 --- /dev/null +++ b/chapters/th/chapter2/3.mdx @@ -0,0 +1,228 @@ + + +# โมเดล + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + +{:else} + +{/if} + +{#if fw === 'pt'} +ใน section นี้ เราจะมาดูวิธีการสร้างและการใช้งานโมเดล เราจะใช้คลาส `AutoModel` ซึ่งเป็นประโยชน์มากหากเราต้องการสร้างโมเดลใดๆ จาก checkpoint หนึ่งๆ + +คลาส `AutoModel` และส่วนประกอบของมันทั้งหมดนั้น จริงๆแล้วก็เป็นเพียง wrapper ของโมเดลต่างๆที่มีอยู่ใน library มันเป็น wrapper ที่ฉลาดโดยที่มันสามารถเดาสถาปัตยกรรมของโมเดลที่เหมาะสมสำหรับ checkpoint ของคุณได้ และสร้างโมเดลด้วยสถาปัตยกรรมนั้น + +{:else} +ใน section นี้เราจะมาดูวิธีการสร้างและการใช้งานโมเดล เราจะใช้คลาส `TFAutoModel` ซึ่งเป็นประโยชน์มากหากเราต้องการสร้างโมเดลใดๆ จาก checkpoint หนึ่งๆ + +คลาส `TFAutoModel` และส่วนประกอบของมันทั้งหมดนั้น จริงๆแล้วก็เป็นเพียง wrapper ของโมเดลต่างๆที่มีอยู่ใน library มันเป็น wrapper ที่ฉลาดโดยที่มันสามารถเดาสถาปัตยกรรมของโมเดลที่เหมาะสมสำหรับ checkpoint ของคุณได้ และสร้างโมเดลด้วยสถาปัตยกรรมนั้น + +{/if} + +แต่อย่างไรก็ตาม ถ้าคุณรู้ว่าคุณต้องการใช้โมเดลประเภทใด คุณสามารถใช้คลาสที่นิยามสถาปัตยกรรมนั้นได้โดยตรง เรามาดูกันว่ามันทำงานยังไงกับโมเดล BERT + +## สร้าง Transformer + +สิ่งแรกที่เราจำเป็นต้องทำในการเริ่มสร้างโมเดล BERT นั้นก็คือการโหลดวัตถุกำหนดค่า(configuration object): + +{#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} + +ใน configuration นั้นประกอบด้วยค่าของคุณสมบัติ(attributes) หลายอย่างๆ ที่ใช้สำหรับสร้างโมเดล: + +```py +print(config) +``` + +```python out +BertConfig { + [...] + "hidden_size": 768, + "intermediate_size": 3072, + "max_position_embeddings": 512, + "num_attention_heads": 12, + "num_hidden_layers": 12, + [...] +} +``` + +ในขณะที่คุณยังไม่เห็นว่าคุณสมบัติต่างๆ เหล่าทำอะไรบ้าง คุณน่าจะพอจำบางส่วนได้: `hidden_size` ที่นิยามขนาดของเวคเตอร์ `hidden_states`, และ `num_hidden_layers` ที่นิยามจำนวนของเลเยอร์ที่โมเดล Transformer มี + +### วิธีการต่างๆในการโหลด + +สร้างโมเดลจาก configuration พื้นฐาน และตั้งค่าเริ่มต้นด้วยค่าสุ่ม(random values): + +{#if fw === 'pt'} +```py +from transformers import BertConfig, BertModel + +config = BertConfig() +model = BertModel(config) + +# โมเดลถูกกำหนดค่าเริ่มต้นด้วยการสุ่ม! +``` +{:else} +```py +from transformers import BertConfig, TFBertModel + +config = BertConfig() +model = TFBertModel(config) + +# โมเดลถูกกำหนดค่าเริ่มต้นด้วยการสุ่ม! +``` +{/if} + +โมเดลสามารถอยู่ในสถานะนี้ได้ แต่มันจะให้ผลลัพธ์ที่แย่ออกมา; มันจำเป็นต้องผ่านการเรียนรู้ก่อน เราสามารถเทรนโมเดลจากโมเดลเปล่าๆ กับงานที่เรามีได้ แต่อย่างที่คุณเห็นใน [Chapter 1](/course/chapter1), มันใช้เวลานานและข้อมูลจำนวนมาก โดยที่ไม่ได้มีประโยชน์อะไรเพิ่มขึ้นมาก เพื่อลดขึ้นตอนที่ไม่จำเป็นต่างๆ มันสำคัญอย่างยิ่งที่เราจะสามารถแชร์และนำโมเดลที่ผ่านการเทรนมาแล้วมาใช้ใหม่ + +การโหลดโมเดล Transformer ที่ผ่านการเทรนมาแล้วนั้นง่ายมาก เราสามารถเรียกใช้ `from_pretrained()`: + +{#if fw === 'pt'} +```py +from transformers import BertModel + +model = BertModel.from_pretrained("bert-base-cased") +``` + +เหมือนที่คุณเห็นก่อนหน้านี้ เราสามารถที่จะแทนค่า `BertModel` ด้วยคลาส AutoModel` ที่คล้ายคลึงกัน จากนี้ไปเราจะใช้วิธีการนี้เพื่อเป็นการสร้างโค้ด checkpoint-agnostic; ถ้าโค้ดของคุณสามารถใช้งานได้กับหนึ่ง checkpoint มันก็ควรที่จะสามารถใช้กับอันอื่นได้ด้วย ซึ่งก็รวมถึง ไม่ว่าสถาปัตยกรรมจะแตกต่างกัน ตราบใดที่ checkpoint นั้นถูกเทรนมาสำหรับงานที่เหมือนกันก็ควรใช้ได้เหมือนกัน (ยกตัวอย่างเช่น งาน sentiment analysis) + +{:else} +```py +from transformers import TFBertModel + +model = TFBertModel.from_pretrained("bert-base-cased") +``` + +เหมือนที่คุณเห็นก่อนหน้านี้ เราสามารถที่จะแทนค่า `TFBertModel` ด้วยคลาส `TFAutoModel` ที่คล้ายคลึงกัน จากนี้ไปเราจะใช้วิธีการนี้เพื่อเป็นการสร้างโค้ด checkpoint-agnostic; ถ้าโค้ดของคุณสามารถใช้งานได้กับหนึ่ง checkpoint มันก็ควรที่จะสามารถใช้กับอันอื่นได้ด้วย ซึ่งก็รวมถึง ไม่ว่าสถาปัตยกรรมจะแตกต่างกัน ตราบใดที่ checkpoint นั้นถูกเทรนมาสำหรับงานที่เหมือนกันก็ควรใช้ได้เหมือนกัน (ยกตัวอย่างเช่น งาน sentiment analysis) + +{/if} + +ในตัวอย่างโค้ดด้านบน เราไม่ได้ใช้ `BertConfig`, แต่ใช้งานโมเดลที่ผ่านการเทรนมาแล้ว(pretrained) ผ่าน `bert-base-cased` identifier ซึ่งนี่เป็น checkpoint ของโมเดลที่โดนเทรนด้วยผู้ที่ประดิษฐ์ BERT เอง; คุณสามารถดูรายละเอียดเพิ่มเติมได้ที่ [model card](https://huggingface.co/bert-base-cased). + +ถึงตอนนี้โมเดลนี้ได้ถูกสร้างและมีค่าตั้งต้นเท่ากับ weights ของ checkpoint มันสามารถถูกนำไปใช้สำหรับการอนุมาน(inference)ได้ทันทีกับงานที่มันถูกเทรนมา และมันสามารถถูกนำมาปรับจูนเพิ่มเติมให้เข้ากับงานใหม่ได้ การเทรนโมเดลที่ใช้ weights ของโมเดลที่ผ่านการเทรนมาแล้ว แทนที่การเทรนจากไม่มีอะไรเลยนั้น ทำให้เราได้ผลลัพธ์ที่ดีในเวลาอันรวดเร็ว + +weights ได้ถูกดาวน์โหลด และ เก็บไว้ในโฟลเดอร์ cache(เมื่อเราทำการเรียกใช้งาน `from_pretrained()` อีกในอนาคต weights เหล่านี้จะไม่ถูกดาวน์โหลดซ้ำอีก) โดยโฟลเดอร์มีค่าเริ่มต้น(default) อยู่ที่ *~/.cache/huggingface/transformers* คุณสามารถปรับเปลี่ยนโฟลเดอร์ cache ได้โดยตั้งค่า `HF_HOME` ใน environment variable + +identifier ที่ใช้สำหรับโหลดโมเดลสามารถใช้ identifier ของโมเดลใดก็ได้บน Model Hub ตราบใดที่มันเข้ากันได้กับสถาปัตยกรรม BERT ลิสท์ของ BERT checkpoints ทั้งหมดที่มีอยู่สามารถดูได้จาก [ที่นี่](https://huggingface.co/models?filter=bert). + +### วิธีสำหรับการบันทึก + +การบันทึกโมเดลนั้นเป็นอะไรง่ายพอๆกับการโหลด - เราใช้ `save_pretrained()` ซึ่งก็เปรียบเสมือนกับ `from_pretrained()`: + +```py +model.save_pretrained("directory_on_my_computer") +``` + +นี่เป็นการบันทึกสองไฟล์ลงไปที่ฮาร์ดดิสของคุณ: + +{#if fw === 'pt'} +``` +ls directory_on_my_computer + +config.json pytorch_model.bin +``` +{:else} +``` +ls directory_on_my_computer + +config.json tf_model.h5 +``` +{/if} + +ุุ้ถ้าคุณไปดูที่ไฟล์ *config.json* คุณจะพอนึกออกถึงคุณสมบัติ(attributes) ที่จำเป็นในการสร้างสถาปัตยกรรมของโมเดล ไฟล์นี้ประกอบด้วย metadata เช่น checkpoint เกิดมาจากที่ใด และ 🤗 Transformers เวอร์ชันใดที่คุณใช้ในการบันทึก checkpoint ล่าสุด + +{#if fw === 'pt'} +ไฟล์ *pytorch_model.bin* เป็นที่รู้จักในนาม *state dictionary*; มันประกอบด้วย weights ทัั้งหมดของโมเดลคุณ สองไฟล์ที่มีความเชื่อมโยงกัน ไฟล์ configuration จำเป็นที่จะต้องรู้สถาปัตยกรรมของโมเดลของคุณ ในขณะที่ weights ของโมเดลคุณ ก็คือ ตัวแปร(parameters) ของโมเดลคุณ + +{:else} +ไฟล์ *tf_model.h5* เป็นที่รู้จักในนาม *state dictionary*; มันประกอบด้วย weights ทัั้งหมดของโมเดลคุณ สองไฟล์ที่มีความเชื่อมโยงกัน ไฟล์ configuration จำเป็นที่จะต้องรู้สถาปัตยกรรมของโมเดลของคุณ ในขณะที่ weights ของโมเดลคุณ ก็คือ ตัวแปร(parameters) ของโมเดลคุณ + +{/if} + +## ใชโมเดล Transformer สำหรับการอนุมาน(inference) + +ุถึงตรงนี้คุณรู้วิธีการโหลดและบันทึกโมเดลแล้ว งั้นมาลองใช้มันทำนายอะไรบางอย่างดูกัน โมดล Transformer นั้นสามารถประมวลผลตัวเลขได้อย่างเดียว ซึ่งตัวเลขเหล่านี้ก็ได้มาจากการสร้างขึ้นมาโดยใช้ tokenizer แต่ก่อนที่เราจะไปอธิบายกันถึง tokenizer เรามาลองค้นหากันดูว่าอินพุตแบบไหนที่สามารถใส่เข้าไปในโมเดลได้บ้าง + +Tokenizers นั้นสามารถที่จะแปลงอินพุตไปเป็น tensors ที่เหมาะสมสำหรับ framework นั้นๆ แต่เพื่อช่วยให้คุณเข้าใจสิ่งที่เกิดขึ้น เราจะมาดูกันว่าอะไรที่จำเป็นต้องทำก่อนที่เราจะส่งอินพุตเข้าไปในโมเดล + +สมมติว่าเรามีคำ สอง สาม คำ: + +```py +sequences = ["Hello!", "Cool.", "Nice!"] +``` + +tokenizer จะทำการแปลงคำเหล่านี้ไปเป็นดัชนีคำศัพท์(vocabulary indices) ซึ่งปกติจะเรียกว่า *input IDs* โดยตอนนี้แต่ละคำกลายเป็นลิสท์ของตัวเลข ผลลัพธ์ที่ได้ก็คือ: + +```py no-format +encoded_sequences = [ + [101, 7592, 999, 102], + [101, 4658, 1012, 102], + [101, 3835, 999, 102], +] +``` + +นี่เป็นลิสท์ของคำที่ผ่านการเข้ารหัส(encoded): a list of lists, Tensors สามารถมีขนาดเป็นสี่เหลี่ยมจตุรัสเท่านั้น(ลองนึกถึงแมทริกซ์), "array" นี้มีขนาดเป็นสี่เหลี่ยมจตุรัสอยู่แล้ว ดังนั้นการแปลงมันไปเป็น tensor นั้นง่ายมาก: + +{#if fw === 'pt'} +```py +import torch + +model_inputs = torch.tensor(encoded_sequences) +``` +{:else} +```py +import tensorflow as tf + +model_inputs = tf.constant(encoded_sequences) +``` +{/if} + +### ใช้ tensors เป็นอินพุตเข้าไปยังโมเดล + +การใช้งาน tensor กับโมเดลนั้นง่ายมากๆ - เราก็แค่เรียกโมเดลพร้อมกับใส่อินพุต: + +```py +output = model(model_inputs) +``` + +ในขณะที่โมเดลสามารถรับตัวแปร(arguments) ต่างๆได้มากมาย แค่ input IDs เท่านั้นที่จำเป็น เดี๋ยวเราจะอธิบายกันอีกทีว่าตัวแปรตัวอื่นๆเอาไว้ทำอะไร และจำเป็นต้องใช้เมื่อไหร่, +แต่ขั้นแรกเราต้องเข้าใจ Tokenizers ที่ใช้สร้างอินพุตที่โมเดล Transformer สามารถเข้าใจได้ก่อน diff --git a/chapters/th/chapter2/4.mdx b/chapters/th/chapter2/4.mdx new file mode 100644 index 000000000..79ad132c1 --- /dev/null +++ b/chapters/th/chapter2/4.mdx @@ -0,0 +1,240 @@ + + +# Tokenizers + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + + +Tokenizers เป็นหนึ่งในส่วนประกอบหลักของ NLP pipeline โดยมีจุดประสงค์เดียวคือ เพื่อแปลงข้อความไปเป็นข้อมูลที่โมเดลสามารถประมวลผลได้ โมเดลสามารถประมวผลได้เพียงแค่ตัวเลขเท่านั้น ดังนั้น tokenizers จึงจำเป็นต้องแปลงข้อความของเราไปเป็นข้อมูลตัวเลข ใน section นี้ เราจะมาลองดูกันว่ามีเกิดอะไรขึ้นบ้างใน tokenization pipeline + +ในงาน NLP ข้อมูลโดยทั่วไปแล้วจะเป็นข้อความ นี่เป็นตัวอย่างของข้อความดังกล่าว: + +``` +Jim Henson was a puppeteer +``` + +แต่อย่างไรก็ตาม โมเดลสามารถประมวผลได้เพียงแค่ตัวเลขเท่านั้น ดังนั้นเราจำเป็นต้องหาทางแปลงข้อความดิบไปเป็นตัวเลข นั่นคือสิ่งที่ tokenizer ทำ และก็มีหลายวิธีมากในการทำ เป้าหมายก็คือ หาตัวแทน(representation)ที่มีความหมายที่สุด - หมายความว่า สิ่งที่โมเดลจะเข้าใจได้มากที่สุด - และ ถ้าเป็นไปได้ เป็นตัวแทนที่มีขนาดเล็กที่สุด + +ลองมาดูตัวอย่างของ tokenization algorithms และพยายามตอบคำถามบางคำถามที่คุณอาจจะมีเกี่ยวกับ tokenization + +## เน้นที่คำ (Word-based) + + + +Tokenizer ประเภทแรกที่เรานึกถึงคือ _word-based_ มันติดตั้งและใช้งานง่ายมากโดยมีกฏเพียงไม่กี่ข้อและมันก็ให้ผลลัพธ์ที่ดีทีเดียว ยกตัวอย่างเช่น ในรูปภาพด้านล่างนี้ เป้าหมายก็คือการแยกข้อความออกเป็นคำๆ และหาตัวแทนที่เป็นตัวเลขของแต่ละคำ: + +
+ An example of word-based tokenization. + +
+ +วิธีการในการแยกข้อความนั้นมีหลายวิธีแตกต่างกันไป ยกตัวอย่างเช่น เราสามารถใช้ ช่องว่าง(whitespace) ในการแยกข้อความขอเป็นคำๆ โดยใช้ฟังก์ชัน `split()` ของ Python: + +```py +tokenized_text = "Jim Henson was a puppeteer".split() +print(tokenized_text) +``` + +```python out +['Jim', 'Henson', 'was', 'a', 'puppeteer'] +``` + +แล้วก็มี tokenizers สำหรับแยกคำอีกหลายแบบที่มีกฏเพิ่มเติมสำหรับ เครื่องหมายวรรคตอน(punctuation) ถ้าเราใช้ tokenizer ประเภทนี้ เราจะได้กลุ่มของคำศัพท์("vocabularies") ที่ค่อนข้างใหญ่มาก ซึ่งคำศัพท์จะถูกนิยามโดยจำนวน tokens อิสระทั้งหมดที่เรามีในคลังข้อมูล(corpus) ของเรา + +แต่ละคำจะได้ ID โดยเริ่มจาก 0 และเพิ่มขึ้นเท่ากับขนาดของคำศัพท์ โดยโมเดลจะใช้ IDs เหล่านี้ในการระบุตัวตนของแต่ละคำ + +ถ้าเราต้องการที่จะให้ครอบคลุมคำทั้งหมดในหนึ่งภาษาด้วย word-based tokenizer เราจำเป็นต้องมีตัวระบุตัวตนสำหรับแต่ละคำในภาษาๆนั้นๆ ซึ่งจะทำให้มีการสร้าง tokens จำนวนมหาศาล ยกตัวอย่างเช่น ในภาษาอังกฤษมีคำทั้งหมด 500,000 คำ ดังนั้นการที่จะสร้างความเชื่อมโยงระหว่างแต่ละคำกับ ID เราจำเป็นที่จะต้องจำ ID ทั้งหลายเหล่านั้น นอกจากนี้แล้ว คำอย่างเช่น "dog" จะมีค่าที่ต่างจากคำเช่น "dogs" และโมเดลจะไม่มีทางรู้เลยว่าค่าว่า "dog" และ "dogs" นั้นคล้ายคลึงกัน: มันจะจำแนกสองคำนี้เป็นคำที่ไม่มีความเกี่ยวข้องกัน และคำอื่นๆก็จะเป็นเช่นเดียวกัน เช่นคำว่า "run" และ "running" ซึ่งโมเดลก็จะไม่มองว่าเป็นคำคล้ายๆกัน + +สุดท้ายแล้ว เราจำเป็นต้องมี Token เฉพาะสำหรับแทนค่าคำต่างๆที่ไม่อยู่กลุ่มคำศัพท์ของเรา ซึ่งสิ่งนี้เรียกว่า "unknown" token ที่โดยทั่วไปแล้วจะมีค่าเป็น "[UNK]" หรือ "<unk>" และมันจะเป็นสัญญาณที่ไม่ดีเท่าไหร่ถ้าคุณเห็น tokenizer ผลิต tokens เหล่านี้ออกมาเยอะๆ เนื่องจากมันไม่สามารถที่จะหาค่าที่สมเหตุสมผลมาแทนคำๆนั้นได้และคุณก็จะสูญเสียข้อมูลไปตามรายทางเรื่อยๆ เป้าหมายเมื่อเราสร้างกลุ่มคำศัพท์ คือ ทำยังไงก็ได้ให้ Tokenizers สร้าง unknown token ให้น้อยที่สุด + +วิธีการหนึ่งที่จะลดจำนวนของ unknown tokens ได้ก็คือ การลงลึกไปอีกหนึ่งขั้น โดยใช้ _character-based_ tokenizer + +## เน้นที่ตัวอักษร(Character-based) + + + +Character-based tokenizers จะแบ่งข้อความออกเป็นตัวอักษร แทนการแบ่งเป็นคำ โดยมีประโยชน์หลักๆ สอง อย่าง: + +- กลุ่มของคำศัพท์จะเล็กกว่ามาก +- มี tokens ที่อยู่นอกกลุ่มคำศัพท์(unknown) น้อยกว่ามาก เนื่องจากคำทุกคำสามารถสร้างได้จากกลุ่มของตัวอักษร + +แต่ก็มีคำถามเกี่ยวกับ ช่องว่าง(spaces) และ เครื่องหมายวรรคตอน(punctuation): + +
+ An example of character-based tokenization. + +
+ +วิธีการนี้ก็ไม่ใช่วิธีการที่ดีทีสุดเช่นกัน เนื่องจากค่าที่ได้จะขึ้นอยู่กับตัวอักษรแทนที่จะเป็นคำ บางคนอาจจะบอกว่า มันไม่บ่งบอกความหมาย: แต่ละตัวอักษรไม่ได้มีความหมายอะไรมากมายในตัวมันเอง แต่กลับกันคำกลับบ่งบอกความหมายมากกว่า แต่อย่างไรก็ตามมันก็ขึ้นอยู่กับแต่ละภาษา; เช่น แต่ละตัวอักษรในภาษาจีนนั้นมีความหมายมากกว่าตัวอักษรในภาษาละติน + +อีกหนึ่งสิ่งที่ควรพิจารณาก็คือเราจะได้ tokens จำนวนมหาศาลที่โมเดลของเราจะต้องประมวลผล: แต่ในทางตรงกันข้าม คำหนึ่งคำจะมีแค่หนึ่ง token ถ้าใช้ word-based tokenizer แต่มันจะกลายเป็น 10 หรือมากกว่านั้นเมื่อเราแปลงเป็นตัวอักษร + +เพื่อให้ได้สิ่งที่เป็นประโยชน์ที่สุดจากทั้งสองแบบ เราสามารถสร้างเทคนิคที่สามที่รวมเอาสองวิธีเข้าด้วยกัน: *subword tokenization* + +## คำย่อย(Subword) tokenization + + + +อัลกอลิธึม Subword tokenization อยู่บนแนวคิดที่ว่า คำที่ใช้บ่อย ไม่ควรที่จะถูกแบ่งออกเป็นคำย่อยเล็กๆ แต่คำที่ไม่ค่อยได้ใช้ควรที่จะถูกแบ่งออกเป็นคำย่อยๆ ที่มีความหมาย + +ยกตัวอย่างเช่น "annoyingly" อาจจะเป็นคำที่ไม่ค่อยถูกใช้บ่อยนัก ดังนั้นมันสามารถที่จะถูกแบ่งย่อยเป็น "annoying" และ "ly" สองคำนี้มีความเป็นไปได้ที่เจอได้บ่อยเมื่อมันเป็นคำย่อยเดี่ยวๆ ในขณะเดียวกัน ความหมายของ "annoyingly" ก็ยังคงอยู่ โดยเป็นความหมายร่วมของ "annoying" และ "ly" + +ตัวอย่างนี้แสดงวิธีการที่อัลกอลิธึม subword tokenization ทำการแปลงประโยค "Let's do tokenization!" ว่าทำอย่างไร: + +
+ A subword tokenization algorithm. + +
+ +ท้ายที่สุดแล้ว คำย่อยเหล่านี้จะให้ความหมายเชิงความหมาย(semantic meaning) มากกว่า, ในตัวอย่างข้างต้น "tokenization" ถูกแบ่งออกเป็น "token" และ "ization", เป็น สอง tokens ที่มีความหมายเชิงความหมายพร้อมทั้งทำให้ประหยัดเนื้อที่ (มีแค่ 2 tokens เท่านั้นที่จะใช้แทนค่าคำยาวๆ) นี่จะทำให้เราสามารถครอบคลุมคำต่างๆได้ด้วยกลุ่มของคำศัพท์เล็กๆเท่านั้น และแทบไม่มี unknown tokens + +วิธีการนี้เป็นประโยชน์อย่างยิ่งในภาษารูปคำติดต่อ(agglutinative languages) อย่างเช่น ภาษาตุรกี(Turkish) ที่คุณสามารถสร้างคำที่ยาวและซับซ้อนได้ด้วยการต่อคำย่อยๆหลายคำๆเข้าด้วยกัน + +### และอื่นๆ ! + +ไม่น่าแปลกใจเลยที่จะมีอีกหลากหลายเทคนิค ถ้าจะเอ่ยบางชื่อ: + +- Byte-level BPE, เหมือนที่ใช้ใน GPT-2 +- WordPiece, เหมือนที่ใช้ใน BERT +- SentencePiece or Unigram, เหมือนที่ใช้ในหลายๆโมเดลที่เป็นโมเดลสำหรับหลายๆ ภาษา(multilingual models) + +ถึงตรงนี้คุณน่าจะมีความรู้เพียงพอว่า tokenizers ทำงานอย่างไร เพื่อเริ่มใช้งาน API + +## การโหลดและการบันทึก + +การโหลดและการบันทึก tokenizers นั้นง่ายพอๆกับการโหลดและการบันทึกโมเดล โดยทั่วไปแล้วมันจะใช้สองวิธี: `from_pretrained()` และ `save_pretrained()` สองวิธีการนี้จะโหลด หรือ บันทึกอัลกอลิธึมที่ใช้โดย tokenizer (คล้ายๆกับ *สถาปัตยกรรม* ของโมเดล) และ กลุ่มคำศัพท์ของมัน (คล้ายๆ กับ *weights* ของโมเดล) + +โหลด BERT tokenizer ที่ผ่านการเทรนมาแล้วด้วย checkpoint เดียวกันกับ BERT สามารถทำได้เหมือนกับการโหลดโมเดล ยกเว้น เราใช้คลาส `BertTokenizer`: + +```py +from transformers import BertTokenizer + +tokenizer = BertTokenizer.from_pretrained("bert-base-cased") +``` + +{#if fw === 'pt'} +เหมือนกับ `AutoModel, `AutoTokenizer` จะทำการดึงเอาคลาส tokenizer ที่เหมาะสมที่อยู่ใน library โดยอ้างอิงกับชื่อของ checkpoint และสามารถใช้กับ checkpoint ใดก็ได้: + +{:else} +เหมือนกับ `TFAutoModel, `AutoTokenizer` จะทำการดึงเอาคลาส tokenizer ที่เหมาะสมที่อยู่ใน library โดยอ้างอิงกับชื่อของ checkpoint และสามารถใช้กับ checkpoint ใดก็ได้: + +{/if} + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +``` + +ตอนนี้เราสามารถใช้ tokenizer เหมือนที่แสดงใน section ที่แล้ว: + +```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]} +``` + +การบันทึก tokenizer ก็เหมือนกับการบันทึกโมเดล: + +```py +tokenizer.save_pretrained("directory_on_my_computer") +``` + +เราจะพูดเพิ่มเติมเกี่ยวกับ `token_type_ids` ใน [Chapter 3](/course/chapter3), และจะอธิบายเกี่ยวกับ `attention_mask` ในภายหลัง ในขั้นแรกนี้เรามาดูกันว่า `input_ids` นั้นถูกสร้างขึ้นมาอย่างไร ซึ่งการที่จะทำเช่นนี้ เราจำเป็นที่จะต้องดูวิธีระหว่างกลางของ tokenizer + +## การเข้ารหัส(Encoding) + + + +การแปลงข้อความไปเป็นตัวเลขนั้นเรียกอีกอย่างว่า _encoding_ การเข้ารหัส(Encoding) นั้นสามารถทำได้ด้วยกระบวนการที่ประกอบด้วย 2 ขั้นตอน: tokenization และตามด้วยการแปลงไปเป็น input IDs + +อย่างที่เราเห็นมาก่อนหน้านี้ ขั้นตอนแรกก็คือการแบ่งข้อความออกเป็นคำ (หรือส่วนของคำ, เครื่องหมายวรรคตอน เป็นต้น) เหล่านี้ปกติจะถูกเรียกว่า *tokens* มีหลายกฏเกณฑ์ที่ใช้ในการควบคุมกระบวนนี้ ซึ่งนั้นก็เป็นเหตุผลว่าทำไมเราถึงจำเป็นต้องสร้าง tokenizer โดยใช้ชื่อของโมเดล ก็เพื่อให้มั่นใจว่าเราใช้กฏเกณฑ์เดียวกันกับที่เราใช้ตอนที่โมเดลนั้นผ่านการเทรนมาก่อนหน้านี้ + +ขั้นตอนที่สองก็คือการแปลง tokens เหล่านั้นไปเป็นตัวเลข ซึ่งเราจึงจะสามารถสร้าง tensor จากมันได้และใส่เข้าไปยังโมเดลได้ ในการทำเช่นนี้ tokenizer จะมี *vocabulary* ซึ่งเป็นส่วนที่เราดาวน์โหลดมาแล้วตอนที่เราสร้าง tokenizer ด้วยวิธีการ `from_pretrained()` เช่นเดียวกัน เราจำเป็นต้องใช้คำศัพท์เหมือนกับที่ใช้ตอนโมเดลนั้นเทรนมา + +เพื่อให้เข้าใจสองขั้นตอนนี้มากยิ่งขึ้น เราจะมาดูแต่ละขั้นตอนแยกกัน เราจะใช้วิธีการบางวิธีที่ทำบางส่วนของ tokenization pipeline แยกกันเพื่อที่จะแสดงให้คุณดูว่าผลลัพธ์ระหว่างกลาง(intermediate) ของทั้งสองขั้นตอนนั้นเป็นอย่างไร แต่ในทางปฏิบัติ คุณควรจะประมวลผลข้อมูลอินพุตของคุณโดยเรียกใช้ tokenizer ตรงๆ(เหมือนที่แสดงใน section 2) + +### Tokenization + +กระบวนการ tokenization นั้นทำได้โดยใช้ `tokenize()` ของ tokenizer: + +```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) +``` + +ผลลัพธ์ของวิธีนี้ คือ ลิสท์ของกลุ่มของตัวอักษร(strings) หรือ tokens: + +```python out +['Using', 'a', 'transform', '##er', 'network', 'is', 'simple'] +``` + +tokenizer นี้คือ subword tokenizer: มันจะแบ่งคำไปเรื่อยๆจนกว่าจะได้ tokens ที่สามารถแทนค่าได้ด้วยคำศัพท์(vocabulary) ของมันเอง ซึ่งนั้นก็เหมือนกับกรณีที่คำว่า `transformer`ถูกแบ่งออกเป็น 2 tokens: `transform` และ `##er` + +### จาก tokens ไปเป็น input IDs + +การแปลงไปเป็น input IDs นั้นจะถูกจัดการโดย `convert_tokens_to_ids()` ที่เป็นเมธอดของ tokenizer: + +```py +ids = tokenizer.convert_tokens_to_ids(tokens) + +print(ids) +``` + +```python out +[7993, 170, 11303, 1200, 2443, 1110, 3014] +``` + +ผลลัพธ์เหล่านี้ เมื่อทำการแปลงไปเป็น tensor ที่เหมาะสมของ framework นั้นๆ แล้ว มันสามารถถูกนำไปใช้เป็นอินพุตของโมเดลเหมือนที่เราเห็นก่อนหน้าในนี้ในบทนี้ + + + +✏️ **ลองดูสิ!** ทำซ้ำสองขั้นตอนสุดท้าย(tokenization และแปลงไปเป็น input IDs) กับข้อความที่เราใช้เป็นอินพุตใน section 2 ("I've been waiting for a HuggingFace course my whole life." และ "I hate this so much!") และลองดูว่าคุณได้ input IDs เดียวกันกับที่เราได้ก่อนหน้านี้ไหม! + + + +## การถอดรหัส(Decoding) + +*การถอดรหัส(Decoding)* ก็จะเป็นกระบวนการในทางตรงกันข้าม จากดัชนีคำศัพท์(vocabulary indices) เราต้องการที่จะได้กลุ่มของตัวอักษร(string) ซึ่งสามารถทำได้ด้วยวิธี `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` ไม่ได้ทำแค่การแปลงดัชนี(indices) ไปเป็น token เท่านั้น แต่ยังทำการรวม tokens ต่างๆที่เป็นส่วนหนึ่งของคำเดียวกันเพื่อสร้างประโยคที่สามารถอ่านได้ กระบวนการเช่นนี้จะเป็นประโยชน์อย่างมากเมื่อเราใช้โมเดลสำหรับทำนายข้อความใหม่(ไม่ว่าจะเป็นข้อความที่สร้างจาก prompt หรือปัญหาประเภท sequence-to-sequence เช่น การแปล(translation) หรือ การสรุปใจความสำคัญ(summarization)) + +ถึงตรงนี้คุณน่าจะเข้าใจกระบวนการเล็กๆ น้อยๆ ต่างๆ ที่ tokenizer สามารถทำได้: tokenization, การแปลงไปเป็น IDs, และการแปลง IDs กลับมาเป็นคำ แต่อย่างไรก็ตาม เราก็แค่เพิ่งจะขุดแค่ปลายของภูเขาน้ำแข็ง ใน section ต่อไป เราจะใช้วิธีการของเราไปจนถึงลิมิตของมันและดูว่าเราจะแก้ปัญหาอย่างไร \ No newline at end of file diff --git a/chapters/th/chapter2/5.mdx b/chapters/th/chapter2/5.mdx new file mode 100644 index 000000000..eac8b97c4 --- /dev/null +++ b/chapters/th/chapter2/5.mdx @@ -0,0 +1,338 @@ + + +# การจัดการกับหลายๆประโยค(multiple sequences) + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + +{:else} + +{/if} + +ใน section ที่แล้ว เราได้ลองทำตัวอย่างการใช้งานที่ง่ายที่สุด: คือการอนุมาน(inference)โดยใช้ประโยคสั้นๆ เพียงประโยคเดียว อย่างไรก็ตาม ก็มีบางคำถามเกิดขึ้นมา: + +- เราจะจัดการกับประโยคหลายๆประโยคอย่างไร? +- เราจะจัดการกับประโยคหลายๆประโยคที่มี *ความยาวต่างกัน* อย่างไร? +- ดัชนีคำศัพท์(vocabulary indices) เป็นเพียงข้อมูลประเภทเดียวที่ทำให้โมเดลทำงานได้ดีหรือไม่? +- มีประโยคที่เป็นประโยคที่ยาวเกินไปหรือไม่? + +มาดูกันว่าปัญหาประเภทใดบ้างที่เกิดขึ้นจากคำถามเหล่านี้ และเราจะแก้ไขมันโดยใช้ 🤗 Transformers API ได้อย่างไร + +## โมเดลคาดหวังที่จะได้ชุด(batch)ของข้อมูลเป็นอินพุต + +ในแบบฝึกหัดที่ผ่านมาคุณได้เห็นแล้วว่าประโยคถูกแปลงไปเป็นลิสท์ของตัวเลขอย่างไร ทีนี้เรามาลองแปลงลิสท์ของตัวเลขนี้ไปเป็น tensor และใส่มันเข้าไปในโมเดล: + +{#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) +# This line will fail. +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) +# This line will fail. +model(input_ids) +``` + +```py out +InvalidArgumentError: Input to reshape is a tensor with 14 values, but the requested shape has 196 [Op:Reshape] +``` +{/if} + +โอ้วไม่นะ! ทำไมมันมีข้อผิดพลาดละ? ในเมื่อเราก็ทำตามขั้นตอนต่างๆ จาก pipeline ใน section 2 + +ปัญหาก็คือเราใส่ประโยคเพียงประโยคเดียวเข้าไปในโมเดล แต่ในขณะที่โมเดล 🤗 Transformers นั้นต้องการประโยคหลายๆประโยค ในที่นี้เราได้ลองทำทุกอย่างที่ tokenizer ทำอยู่เบื้องหลังเมื่อเราใช้มันกับ `ประโยค(sequence)` แต่ถ้าคุณลองสังเกตุดีๆ คุณจะเห็นว่ามันไม่ได้เพียงแค่แปลง input IDs ไปเป็น tensor เท่านั้น แต่มันยังเพิ่มมิติ(dimension) ของ tensor อีกด้วย: + +{#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} + +ลองอีกครั้งและเพิ่มมิติ(dimension)ใหม่ด้วย: + +{#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} + +เราแสดง input IDs พร้อมทั้งผล logits และนี่คือผลลัพธ์: + +{#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} + +*Batching* คือ การส่งประโยคหลายๆประโยคเข้าไปยังโมเดลพร้อมๆกันทีเดียว ถ้าคุณมีแค่หนึ่งประโยค คุณก็สามารถสร้างชุด(batch)ของประโยค ที่มีเพียงแค่หนึ่งประโยคได้: + +``` +batched_ids = [ids, ids] +``` + +นี่คือชุด(batch)ของข้อมูลที่ประด้วยสองประโยคที่เหมือนกัน! + + + +✏️ **Try it out!** แปลงลิสท์ของ `batched_ids` นี้ไปเป็น tensor และใส่เข้าไปในโมเดลของคุณ แล้วลองตรวจสอบดูว่าคุณได้ logits เหมือนกับที่ได้ก่อนหน้านี้ไหม(แต่ได้สองค่า)! + + + +ฺBatching นั้นทำให้โมเดลสามารถทำงานได้เมื่อคุณใส่ประโยคหลายๆประโยคเข้าไป การใช้ประโยคหลายๆประโยคนั้นก็สามารถทำได้ง่ายเหมือนกับที่ทำกับประโยคเดียว แต่ก็ยังมีปัญหาที่สอง เมื่อคุณพยายามรวมประโยคตั้งแต่สองประโยคขึ้นไปเป็นชุดข้อมูลเดียวกัน แต่ประโยคเหล่านั้นอาจจะมีความยาวที่แตกต่างกัน ถ้าคุณเคยทำ tensors มาก่อนหน้านี้ คุณจะรู้ว่ามันจำเป็นต้องมีขนาดจตุรัส(rectangular) ดังนั้นคุณจะไม่สามารถแปลงลิสท์ของ input IDs ไปเป็น tensor ได้โดยตรง เราสามารถแก้ปัญหานี้ได้ด้วยการ *pad* อินพุต + +## การเติม(Padding) อินพุต + +ลิสท์ของลิสท์ต่อไปนี้ไม่สามารถถูกแปลงไปเป็น tensor ได้: + +```py no-format +batched_ids = [ + [200, 200, 200], + [200, 200] +] +``` + +ในการแก้ปัญหานี้ เราจะใช้ *padding* เพื่อทำให้ tensor ของเรามีขนาดเป็นจตุรัส Padding จะทำให้ทุกประโยคของเรามีความยาวเท่ากันโดยการเพิ่มคำพิเศษ ที่เรียกว่า *padding token* ไปในประโยค ยกตัวอย่างเช่น ถ้าคุณมี 10 ประโยคที่แต่ละประโยคมี 10 คำ และมี 1 ประโยคที่มี 20 คำ padding ทำให้ทุกประโยคนั้นมี 20 คำเหมือนกัน ในตัวอย่างของเรา tensor ที่เป็นผลลัพท์ของเราเป็นแบบนี้: + +```py no-format +padding_id = 100 + +batched_ids = [ + [200, 200, 200], + [200, 200, padding_id], +] +``` + +padding token ID สามารถหาได้ใน `tokenizer.pad_token_id` เรามาลองใช้มันดูและใส่ประโยคสองประโยคของเราเข้าไปในโมเดลทีละอันและใส่แบบเป็นชุด(batch)ด้วย: + +{#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} + +มีอะไรบางอย่างผิดปกติเกิดขึ้นกับ logits ในการทำนายแบบเป็นชุด(batch) ของเรา: แถวที่สองควรจะได้ logits เหมือนกันกับประโยคที่สอง แต่เราได้ที่ต่างกันโดยสิ้นเชิง! + +นี่เป็นเพราะว่าคุณลักษณะสำคัญของโมเดล Transformer คือ attention layers ที่พิจารณาบริบทของแต่ละ token โดยจะมีการพิจารณา padding token ด้วย เนื่องจาก attention layers พิจารณาทุก tokens ของประโยค เพื่อให้ได้ผลลัพท์เดียวกันไม่ว่าจะใส่ประโยคที่ความยาวต่างกันเข้าไปทีละประโยคหรือใส่ประโยคเดียวกันนี้ที่มีการ padding ไปพร้อมกันทีละหลายๆประโยค(batch) เราจำเป็นที่จะต้องบอก attention layers เหล่านั้นให้ไม่ต้องพิจารณา padding tokens โดยสามารถทำได้ด้วยการใช้ attention mask + +## Attention masks + +*Attention masks* คือ tensors ที่มีขนาดเท่ากับ tensor ของ input IDs และประกอบด้วย 0 และ 1: 1 บ่งบอกว่า tokens ในตำแหน่งนั้นๆ จำเป็นต้องพิจารณา, และ 0 บ่งบอกว่า tokens ในตำแหน่งนั้นๆ ไม่ต้องพิจารณา(กล่าวคือ มันควรจะถูกละเลยโดย attention layers ของโมเดล) + +มาทำตัวอย่างที่แล้วโดยใช้ attention mask กัน: + +{#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} + +ตอนนี้เราได้ logits เดียวกันสำหรับประโยคที่สองในชุด(batch) ของข้อมูล + +สังเกตุว่าค่าสุดท้ายของประโยคที่สองนั้นเป็นอย่างไรใน padding ID ซึ่งก็คือค่า 0 ใน attention mask + + + +✏️ **ลองเลย!** ทำ tokenization กับสองประโยคใน section 2 ("I've been waiting for a HuggingFace course my whole life." และ "I hate this so much!") แล้วใส่เข้าไปในโมเดลและตรวจสอบดูว่าคุณได้ logits เหมือนกับใน section 2 หรือไม่ หลังจากนั้นให้จับประโยครวมกันเป็นชุด(batch) โดยใช้ padding token แล้วสร้าง attention mask ที่ถูกต้อง และตรวจสอบดูว่าคุณได้ผลลัพท์เหมือนกันหรือไม่หลังจากใส่เข้าไปในโมเดลแล้ว! + + + +## ประโยคที่ยาวขึ้น + +การใช้โมเดล Transformer นั้นมีข้อจำกัดเรื่องความยาวของประโยคที่เราสามารถใส่เข้าไปได้ โมเดลส่วนใหญ่จะสามารถรองรับประโยคได้อย่างมาก 512 หรือ 1024 tokens และก็จะทำงานไม่ได้ถ้าให้มันประมวลผลประโยคที่ยาวขึ้น มีวีธีแก้ปัญหานี้อยู่ 2 วิธี: + +- ใช้โมเดลที่รองรับประโยคที่ยาวขึ้น +- ตัดประโยคของคุณให้สั้นลง + +โมเดลต่างๆ รองรับความยาวของประโยคที่แตกต่างกัน และบางโมเดลนั้นเชี่ยวชาญในการจัดการกับประโยคที่ยาวๆ [Longformer](https://huggingface.co/transformers/model_doc/longformer.html) เป็นหนึ่งตัวอย่าง และอีกตัวอย่างก็คือ [LED](https://huggingface.co/transformers/model_doc/led.html) ถ้าคุณกำลังทำงานที่ต้องการประโยคที่ยาวมากๆ เราแนะนำให้คุณลองดูโมเดลเหล่านั้น + +หรือในทางตรงกันข้าม เราแนะนำให้คุณตัดประโยคของคุณให้สั้นลงโดยการระบุตัวแปร `max_sequence_length: + +```py +sequence = sequence[:max_sequence_length] +``` diff --git a/chapters/th/chapter2/6.mdx b/chapters/th/chapter2/6.mdx new file mode 100644 index 000000000..a930b491a --- /dev/null +++ b/chapters/th/chapter2/6.mdx @@ -0,0 +1,165 @@ + + +# ประกอบทุกอย่างเข้าด้วยกัน + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +ในสองสาม sections ที่ผ่านมา เราได้พยายามทำทุกอย่างด้วยมือของเราเอง เราได้ลองศึกษาว่า tokenizer นั้นทำงานอย่างไรและวิธีการ tokenization, แปลงข้อมูลไปเป็น input IDs, การเติม(padding), การตัด(truncation), และ attention masks + +อย่างไรก็ตาม เหมือนที่เราเห็นใน section 2, 🤗 Transformers API นั้นสามารถจัดการกับสิ่งต่างๆเหล่านั้นให้เราได้ด้วย high-level ฟังก์ชันที่เราจะลงลึงในรายละเอียดกันในที่นี่ เมื่อคุณเรียกใช้งาน `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 นั้นรวมไปถึง input IDs และ attention mask ด้วย ส่วนโมเดลอื่นๆที่รองรับอินพุตต่างๆเพิ่มเติมก็จะได้ผลลัพท์เหล่านั้นจาก `tokenizer` object ด้วย + +อย่างที่เราจะได้เห็นในบางตัวอย่างด้านล่างนี้ วิธีนี้เป็นวิธีที่ทรงพลังมาก อันดับแรก มันสามารถที่จะ tokenize ประโยคเพียงประโยคเดียวได้: + +```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) +``` + +มันสามารถที่จะเติม(padding) ให้สอดคล้องกับหลายๆวัตถุประสงค์: + +```py +# จะเติมประโยคไปจนถึงความยาวที่ยาวที่สุดของประโยค +model_inputs = tokenizer(sequences, padding="longest") + +# จะเติมประโยคไปจนถึงความยาวที่ยาวที่สุดที่โมเดลรับได้ +# (512 for BERT or 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 for BERT or DistilBERT) +model_inputs = tokenizer(sequences, truncation=True) + +# จะตัดประโยคที่มีความยาวเกินกว่าความยาวที่ระบุไว้ +model_inputs = tokenizer(sequences, max_length=8, truncation=True) +``` + +`tokenizer` object สามารถที่จะจัดการกับการแปลงข้อมูลไปเป็น tensors สำหรับ framework ที่เฉพาะเจาะจงได้ ซึ่งสามารถที่จะส่งเข้าโมเดลได้ทันที ยกตัวอย่างเช่น ในโค้ดตัวอย่างต่อไปนี้ เราจะสั่งให้ tokenizer ส่ง tensors จาก frameworks ต่างๆ กัน — `"pt"` ให้ PyTorch tensors, `"tf"` ให้ TensorFlow tensors, and `"np"` ให้ NumPy arrays: + +```py +sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] + +# Returns PyTorch tensors +model_inputs = tokenizer(sequences, padding=True, return_tensors="pt") + +# Returns TensorFlow tensors +model_inputs = tokenizer(sequences, padding=True, return_tensors="tf") + +# Returns NumPy arrays +model_inputs = tokenizer(sequences, padding=True, return_tensors="np") +``` + +## tokens พิเศษ + +ถ้าเราดูที่ input IDs ที่ได้จาก tokenizer เราจะเห็นได้ว่ามันค่อนข้างแตกต่างไปจากสิ่งที่เราเคยได้ก่อนหน้านี้: + +```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] +``` + +มีหนึ่ง token ID ได้ถูกใส่เข้ามาด้านหน้าสุด และอีกหนึ่ง token ID ใส่ด้านหลังสุด มาถอดรหัสสองประโยคของ IDs ด้านบนดูว่ามันเกี่ยกับอะไร: + +```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." +``` + +tokenizer ทำการเพิ่มคำพิเศษ `[CLS]` ที่ด้านหน้าสุด และคำพิเศษ `[SEP]` ที่ด้านหลังสุด นั้นก็เพราะว่าโมเดลนั้นได้ผ่านการเทรนมาแบบนั้น ดังนั้นเพื่อให้ได้ผลลัพท์เดียวกันสำหรับการอนุมาน(inference) เราจำเป็นต้องเพิ่มมันเข้าไปเช่นเดียวกัน แต่ก็ต้องตระหนักว่าบางโมเดลนั้นไม่ได้เพิ่มคำพิเศษ หรือ ใส่คำที่ต่างออกไป; โมเดลอาจจะเพิ่มคำพิเศษเหล่านี้แค่เฉพาะด้านหน้าสุด หรือ ด้านหลังสุดเท่านั้น ไม่ว่าจะในกรณีใดๆ tokenizer รู้ว่าอันไหนเป็นอันที่ต้องการและมันจะจัดการให้คุณเอง: + +## สรุป: จาก tokenizer ไปยังโมเดล + + +ถึงตรงนี้เราได้เห็นขั้นตอนแต่ละอย่างทั้งหมดที่ `tokenizer` ใช้เพื่อประมวลผลข้อความ เรามาดูกันครั้งสุดท้ายว่ามันสามารถจัดการประโยคหลายๆประโยค (padding!), ประโยคยาวๆ, และ tensors หลายๆ ประเภทได้อย่างไรด้วย 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/th/chapter2/7.mdx b/chapters/th/chapter2/7.mdx new file mode 100644 index 000000000..cc2235fb3 --- /dev/null +++ b/chapters/th/chapter2/7.mdx @@ -0,0 +1,13 @@ +# การใช้งานเบื้องต้นสำเร็จแล้ว! + +คุณเยี่ยมมากที่เรียนมาได้ถึงตรงนี่ เรามาทบทวนกันว่าในบทนี้คุณ: + +- เรียนรู้ blocks พื้นฐานของโมเดล Transformer +- เรียนรู้ว่ามีอะไรบ้างที่ประกอบกันเป็น tokenization pipeline. +- เห็นการใช้งานจริงของโมเดล Transformer. +- เรียนรู้การใช้งาน tokenizer ในการแปลงข้อความไปเป็น tensor ที่โมเดลสามารถเข้าใจได้ +- ประกอบ tokenizer และ โมเดล เข้าด้วยกัน เพื่อให้ได้มาซึ่งคำทำนายของข้อความ +- เรียนรู้ข้อจำกับของ input IDs และเรียนรู้เกี่ยวกับ attention masks +- ได้ลองเล่นเมธอดต่างๆ ที่สามารถปรับแต่งค่าได้ของ Tokenizer + +ต่อจากนี้ไป คุณควรจะสามารถใช้งานคู่มือ 🤗 Transformers ได้อย่างอิสระ: คำศัพท์ต่างๆ จะฟังดูคุ้นหู และคุณก็ได้เห็นเมธอดต่างๆ ที่คุณจะใช้บ่อยๆ diff --git a/chapters/th/chapter2/8.mdx b/chapters/th/chapter2/8.mdx new file mode 100644 index 000000000..f7e51037d --- /dev/null +++ b/chapters/th/chapter2/8.mdx @@ -0,0 +1,304 @@ + + + + +# แบบทดสอบท้ายบท + +### 1. ลำดับขั้นตอนใน pipeline ของการทำโมเดลด้านภาษา(language modeling)เป็นอย่างไร ? + + + +### 2. tensor ที่เป็นเอาท์พุตออกมาจากโมเดล Transformer แบบพื้นฐานมีขนาดกี่มิติ และมิติเหล่านั้นเป็นอะไรบ้าง? + + + +### 3. ข้อใดต่อไปนี้เป็นตัวอย่างของ tokenization แบบคำย่อย(subword)? + + + +### 4. model head คืออะไร? + + + +{#if fw === 'pt'} +### 5. AutoModel คืออะไร? + +AutoNLP ของเราหรือเปล่า?" + }, + { + text: "เป็น object ที่ให้สถาปัตยกรรมที่ถูกต้องสำหรับ checkpoint นั้นๆออกมา", + explain: "ถูกต้อง: AutoModel จำเป็นต้องรู้เพียงแค่ว่า checkpoint ใดที่จะใช้ในการสร้างโมเดลและให้สถาปัตยกรรมที่ถูกต้องกลับมา", + correct: true + }, + { + text: "โมเดลที่ตรวจหาภาษาที่ใช้สำหรับเป็นอินพุตของมันโดยอัตโนมัติเพื่อที่จะโหลด weights ที่ถูกต้อง", + explain: "ผิด; ในขณะที่บาง checkpoints และโมเดล นั้นสามารถประมวลผลได้หลายภาษา, แต่ไม่มีเครื่องมือแบบ built-in สำหรับเลือก checkpoint ที่ตรงกับภาษาแบบอัตโนมัติเลย คุณลองไปดูที่ Model Hub เพื่อหา checkpoint ที่ดีที่สุดสำหรับงานของคุณ!" + } + ]} +/> + +{:else} +### 5. TFAutoModel คืออะไร? + +AutoNLP ของเราหรือเปล่า?" + }, + { + text: "เป็น object ที่ให้สถาปัตยกรรมที่ถูกต้องสำหรับ checkpoint นั้นๆออกมา", + explain: "ถูกต้อง: AutoModel จำเป็นต้องรู้เพียงแค่ว่า checkpoint ใดที่จะใช้ในการสร้างโมเดลและให้สถาปัตยกรรมที่ถูกต้องกลับมา", + correct: true + }, + { + text:"โมเดลที่ตรวจหาภาษาที่ใช้สำหรับเป็นอินพุตของมันโดยอัตโนมัติเพื่อที่จะโหลด weights ที่ถูกต้อง", + explain: "ผิด; ในขณะที่บาง checkpoints และโมเดล นั้นสามารถประมวลผลได้หลายภาษา, แต่ไม่มีเครื่องมือแบบ built-in สำหรับเลือก checkpoint ที่ตรงกับภาษาแบบอัตโนมัติเลย คุณลองไปดูที่ Model Hub เพื่อหา checkpoint ที่ดีที่สุดสำหรับงานของคุณ!" + } + ]} +/> + +{/if} + +### 6. มีเทคนิคอะไรบ้างที่เราต้องคำนึงถึงเมื่อจะต้องทำการจัดประโยคที่มีความยาวแตกต่างกันเข้าเป็นชุดเดียวกัน(batching)? + + +### 7. อะไรคือจุดประสงค์ของการใช้ฟังก์ชัน SoftMax กับผลลัพท์ที่เป็น logits ที่ได้จากโมเดลสำหรับจำแนกประโยค (sequence classification model)? + + + +### 8. วิธีใดที่เป็นหัวใจหลักของ tokenizer API ส่วนใหญ่? + +encode, เพราะมันสามารถเข้ารหัสข้อความไปเป็น IDs และ IDs ไปเป็นคำทำนายได้", + explain: "ผิด! ในขณะที่วิธีการ encode นั้นไม่มีอยู่ใน tokenizers, มันก็ไม่มีอยู่ในโมเดลเช่นเดียวกัน" + }, + { + text: "การเรียก tokenizer object โดยตรง", + explain: "ถูกต้อง! วิธี __call__ ของ tokenizer เป็นวิธีการที่ทรงพลังมากที่สามารถจัดการได้เกือบทุกอย่าง และมันก็เป็นวิธีการที่ถูกใช้ในการเอาผลการทำนายออกมาจากโมเดลด้วย", + correct: true + }, + { + text: "pad", + explain: "ผิด! การเติม(Padding) เป็นประโยชน์มาก แต่มันก็เป็นแค่ส่วนหนึ่งของ tokenizer API" + }, + { + text: "tokenize", + explain: "วิธี tokenize เป็นหนึ่งวิธีที่มีประโยชน์มากแต่มันก็ไม่ใช่หัวใจหลักของ tokenizer 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: "string ที่ประกอบด้วย tokens ทั้งหมด", + explain: "นั้นอาจจะไม่ใช่วิธีที่ดีที่สุด, เมื่อเป้าหมายคือการแบ่ง string ออกเป็นหลายๆ tokens " + } + ]} +/> + +{#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} From 450c31e8c8957cb697d370ebbf26db3733c7a6de Mon Sep 17 00:00:00 2001 From: lewtun Date: Mon, 25 Apr 2022 09:59:48 +0200 Subject: [PATCH 09/51] Bump release (#138) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ko-chapter1/1 * ko _toctree.yml created * Fix the issue #80 * Single expression changed * ko/chapter1 finished * ko/chapter0 finished * ko/chapter0 finished * reviewed by @bzantium ko/chapter0 * reviewed by @bzantium chapter0 & fixed typo * reviewed by @rainmaker712 * maximize Korean expressions * [Chapter 1] bangla traslation initial commit * Update 1.mdx update and fix formating * Fix formating and typos * translate _toctree.yml 0-1 chapter * Add Korean to CI * [tr] Translated chapter1/2.mdx * remove translation from sec titles not yet translated * Add authors [th ru] * [FIX] _toctree.yml * Update chapters/bn/chapter0/1.mdx [FIX] syntax formatting Co-authored-by: lewtun * tag typos & indentation & unnatural expressions * modified toctree.yml for chapter1/2 * modified toctree.yml for chapter1/2 & fix typo * French Translation - Chapter 5 * Add Bengali to CI * Update author list * Adding translations for 2/4 and 2/5 🚀 (#74) * Adding translations for 2/4 and 2/5 🚀 * Remove English content Co-authored-by: lewtun * Translation to Russian (#97) * translation of chapter 2/section 1 * add section 1 / chapter 2 to _toctree.yml * Translation of Chapter0 to Hindi (#86) * Hindi?Chapter0-Part_1 * Hindi/Chapter0-Part_2 * Chapter 0 Persian Translation First Draft (#95) * merged branch0 into main. no toctree yet. * updated toctree. * Updated the glossary with terms from chapter0. * Second draft in collab w/ @schoobani. Added empty chapter1 for preview. * Glossary typo fix. * Translation of Chapter0 (setup) to Arabic (#104) * Add AR translation for `introduction` * Fix alignment & format * Add Arabic to CI build * Russian - Chapter 1 finished (#98) * 01/4 start * 1/4 finished * 1/5 finished * 1/5 update toc * 1/6 finished * 1/7 finished * 1/8 finished * 1/9 finished * 1/4 fix * toc update * Chinese - Chapter 1 finished (#113) * Chinese - Chapter 1 finished * Add zh to the languages field Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> * [PT] Translation of chapter 2 (#107) * add PT translate to 2.1 * add PT translate to 2.2 * add portuguese translation to 2.3 * WIP portuguese translation to 2.4 * add portuguese translation to 2.4 * add portuguese translation to 2.5 * add portuguese translation to 2.6 * add _toctree infos Co-authored-by: lewtun * [FR] Translation of chapter 2 & event + Review of chapters 0 & 5 (#106) * Update _toctree.yml Add chapter 2 + little fix of chapter 5 * Update 1.mdx Review of chapter 0 * Create 1.mdx * Create 2.mdx * Create 3.mdx * Create 4.mdx * Create 5.mdx * Create 6.mdx * Create 7.mdx * Create 8.mdx * Update 8.mdx Since AutoNLP has recently been renamed to AutoTrain, let me make the correction on the English file * Update 1.mdx Review of chapter 5/1 * Update 2.mdx Review of chapter 5/2 * Update 3.mdx Review of chapter 5/3 * Update 4.mdx Review of chapter 5/4 * Update 5.mdx Review of chapter 5/5 * Update 6.mdx Review of chapter 5/6 * Update 7.mdx Review of chapter 5/7 * Update 8.mdx Review of chapter 5/8 * Create 1.mdx event's translation * Update _toctree.yml add event to the tree * Update _toctree.yml deletion of the files that pose a problem to pass the checks, will be resubmitted in another PR * Delete 1.mdx deletion of the files that pose a problem to pass the checks, will be resubmitted in another PR * make style correction * Update _toctree.yml the - * Fix spacing Co-authored-by: Lewis Tunstall * [th] Translated Chapter2/1 (#83) * Finish chapter2/1 * Update _toctree.yml * Add Hindi to CI (#116) * Update README.md (#87) * Update authors on README (#120) * Update authors * French translation `Chapter1` full (#56) * traduction 1st part of chapter1 * fix typo * fix job titles and encoder-decoder translation * add part 2 for 1st chapter * fix some typo part2 * fix Transformer -> Transformers * add part 3 not totally ended * end of part3 of chapter1 * part9 chapter 1 * add part7 chapter 1 * add part5 chapter 1 * part 6 chapter 1 * add part8 chapter 1 * end quizz of chapter * add last part of chapter 1 Co-authored-by: ChainYo * Translate to Japanese Chapter0 (#123) * start working * translate chapter0/1.mdx * [FA] First draft of Chapter2/Page2 (#129) * merged branch0 into main. no toctree yet. * updated toctree. * Updated the glossary with terms from chapter0. * Second draft in collab w/ @schoobani. Added empty chapter1 for preview. * Glossary typo fix. * Added missing backticks. * Removed a couple of bad indefinite articles I forgot. * First draft of ch2/p2. Adds to glossary. Trans. guidelines moved out. * Fixed missing diacritics, fixed the py/tf switch placing. Other fixes. * Changed the equivalent for prediction per @kambizG 's direction. * Redid ambiguous passage in translation per @lewtun 's direction. * [th] Finished whole Chapter 2 translation (#127) * Finish chapter2/1 * delete untranslated files * chapter2/2 WIP * Delete WIP files * WIP chapter2/2 * Fixed conflict * Update _toctree.yml * Update _toctree.yml * Finished Chapter2/2 * Finish all chapter2/n * Finish all chapter2/n * Fixed Ch2/8 as PR run failed * [de] Translation Chapter 0 (#130) * Copy files to newly created german dir (de) * Add translation for chapter 0 * Clean up english files for chapter 1 * Change _toctree.yml for chapter 0 * Fix whitespaces * Fix whitespaces again * Adjust _toctree.yml - leave only chaper 0 * Add German language (de) to workflow yaml files * [de] German Translation Guide (#132) * German Translation Guide * Add German Glossary to TOC * Chapter 1, Section 1 Bengali translation (#124) * [ADD] Chapter 1, Section 1 benglai tranlation * [FIX] toc * [FIX] commit mistakes * [FIX] remove the Eng duplicates Co-authored-by: m_khandaker * [FR] Review of chapters 0, 2 & 5 + add chapters 6, 7, 8 & event (#125) * Create 1.mdx Event translation * Create 1.mdx * Chapter 6 in French * Update 1.mdx fix italic * Update 9.mdx fix italic * Update 3.mdx fix italic * Update 4.mdx fix italic * Update 4.mdx * Update 1.mdx little fix * Update 2.mdx little fix * Update 4.mdx fix italic * Update 8.mdx fix italic * Update 1.mdx little fix * Update 2.mdx little fix * Update 3.mdx little fix * Update 5.mdx little fix * Update 7.mdx little fix * Update 8.mdx little fix * add chapter8 * Update 6.mdx fix italic * Update 3.mdx fix, fix everywhere * Update 2.mdx fix, fix everywhere * Update 4.mdx fix, fix everywhere * Update 4_tf.mdx fix, fix everywhere * Add files via upload add chapter 7 * Update 1.mdx fix links * Update 2.mdx fix, fix everywhere * Update 3.mdx fix, fix everywhere * Update 4.mdx fix, fix everywhere * Update 5.mdx * Update 6.mdx fix, fix everywhere * Update 7.mdx fix, fix everywhere * Update 3.mdx fix link * Update 8.mdx fix link * Update 2.mdx fix link * Update 4.mdx little fix * Update 6.mdx * Update 7.mdx * Update 8.mdx fix * Update 2.mdx little fix * Update 3.mdx little fix * Update 5.mdx * Update 4_tf.mdx little fix * Update _toctree.yml Forgot the toctree * Update _toctree.yml fix local fields * Update _toctree.yml My bad, I forgot some 🙃 * Update 7.mdx I don't know why it was there... * Update 1.mdx * [de] Chapter 3 translation (#128) * chapter 3 part 1 DE * [DE] Chapter 3 - Part 2 * Prepare TOC-Tree * Fein-tuning * Initial translation * Glossary additions for C3P3 * C3P2 style * [de] Chapter 3 P3-TF initial translation * [de] Chapter 3 P4 initial translation * [de] Chapter 3 Part 5 initial translation * [de] Chapter 3 P6 Initial translation * Missing commas * fixing quotes * Mark third option on chapter 8, question 8 as correct (#135) * doc_change(translation): translating course from english to gujarati (#126) * change(translation): chapter0 to gujarati content translated: Chapter0/1.mdx - Introduction commit-by: menonashwathi4@gmail.com * Revert "change(translation): chapter0 to gujarati" This reverts commit c27e06992af8892687f343a19368ce322d69e8b2. * doc_change(translation): translation to gj translated content: chapters/gj/chapter0.mdx - introduction * doc_change(translation): translation to gj translated content: chapters/gj/chapter0.mdx - introduction * Delete _toctree.yml * change: adding gj to github workflow * nit: fix heading * Update authors (#136) * [FA] First draft of Chapter4/Page1 (#134) * added chapter4 title and it's first section * added first draft of Chapter4/Page1 * minor fix * updated the title according to convention * applied some updates according to convention * added footnotes, minor improvements * applied tweaks according to review points * the new draft of glossary according to PR #134 * fixed an inconsistant title * minor fix for better compatibility with T points * applied final touches for this round of G updates * [FR] End of chapter 3 + chapter 4 (#137) * add chapters 3 & 4 * Update 2.mdx fix links * Update 3.mdx some fix * Update 6.mdx fix tag * Update 3.mdx add link to chapter 7 * Update 3_tf.mdx add link to chapter 7 * Update _toctree.yml Co-authored-by: DOOHAE JUNG Co-authored-by: m_khandaker Co-authored-by: Md. Al-Amin Khandaker Co-authored-by: ftarlaci <18291571+ftarlaci@users.noreply.github.com> Co-authored-by: Doohae Jung <80743307+Doohae@users.noreply.github.com> Co-authored-by: melaniedrevet Co-authored-by: Jose M Munoz Co-authored-by: svv73 <88366711+svv73@users.noreply.github.com> Co-authored-by: Vedant Pandya Co-authored-by: Bahram Shamshiri Co-authored-by: Giyaseddin Bayrak <34009210+giyaseddin@users.noreply.github.com> Co-authored-by: Pavel <60391448+pdumin@users.noreply.github.com> Co-authored-by: 1375626371 <40328311+1375626371@users.noreply.github.com> Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> Co-authored-by: João Gustavo A. Amorim Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Cherdsak Kingkan Co-authored-by: Thomas Chaigneau <50595514+ChainYo@users.noreply.github.com> Co-authored-by: ChainYo Co-authored-by: hiromu <45531573+hiromu166@users.noreply.github.com> Co-authored-by: Cherdsak Kingkan Co-authored-by: Marcus Fraaß Co-authored-by: Jesper Dramsch Co-authored-by: amyeroberts Co-authored-by: Ash <103081562+ashwathim@users.noreply.github.com> Co-authored-by: Hamed Homaei Rad --- .github/workflows/build_documentation.yml | 4 +- .github/workflows/build_pr_documentation.yml | 2 +- README.md | 5 +- chapters/bn/_toctree.yml | 5 + chapters/bn/chapter1/1.mdx | 59 + chapters/de/_toctree.yml | 19 +- chapters/de/chapter3/1.mdx | 21 + chapters/de/chapter3/2.mdx | 381 ++++++ chapters/de/chapter3/3.mdx | 172 +++ chapters/de/chapter3/3_tf.mdx | 195 +++ chapters/de/chapter3/4.mdx | 358 +++++ chapters/de/chapter3/5.mdx | 20 + chapters/de/chapter3/6.mdx | 296 +++++ chapters/de/glossary/1.mdx | 10 +- chapters/en/chapter3/3.mdx | 2 +- chapters/en/chapter3/3_tf.mdx | 2 +- chapters/en/chapter3/6.mdx | 4 +- chapters/en/chapter8/7.mdx | 3 +- chapters/fa/_toctree.yml | 9 +- chapters/fa/chapter4/1.mdx | 19 + chapters/fa/glossary/1.mdx | 82 +- chapters/fr/_toctree.yml | 117 +- chapters/fr/chapter0/1.mdx | 2 +- chapters/fr/chapter2/2.mdx | 10 +- chapters/fr/chapter2/4.mdx | 8 +- chapters/fr/chapter2/8.mdx | 2 +- chapters/fr/chapter3/3.mdx | 171 +++ chapters/fr/chapter3/3_tf.mdx | 190 +++ chapters/fr/chapter3/4.mdx | 359 +++++ chapters/fr/chapter3/5.mdx | 20 + chapters/fr/chapter3/6.mdx | 296 +++++ chapters/fr/chapter4/1.mdx | 17 + chapters/fr/chapter4/2.mdx | 97 ++ chapters/fr/chapter4/3.mdx | 638 +++++++++ chapters/fr/chapter4/4.mdx | 84 ++ chapters/fr/chapter4/5.mdx | 7 + chapters/fr/chapter4/6.mdx | 223 ++++ chapters/fr/chapter5/1.mdx | 2 +- chapters/fr/chapter5/2.mdx | 4 +- chapters/fr/chapter5/3.mdx | 4 +- chapters/fr/chapter5/5.mdx | 4 +- chapters/fr/chapter5/7.mdx | 2 +- chapters/fr/chapter5/8.mdx | 28 +- chapters/fr/chapter6/1.mdx | 13 + chapters/fr/chapter6/10.mdx | 278 ++++ chapters/fr/chapter6/2.mdx | 265 ++++ chapters/fr/chapter6/3.mdx | 475 +++++++ chapters/fr/chapter6/3b.mdx | 713 ++++++++++ chapters/fr/chapter6/4.mdx | 123 ++ chapters/fr/chapter6/5.mdx | 360 +++++ chapters/fr/chapter6/6.mdx | 374 ++++++ chapters/fr/chapter6/7.mdx | 381 ++++++ chapters/fr/chapter6/8.mdx | 566 ++++++++ chapters/fr/chapter6/9.mdx | 11 + chapters/fr/chapter7/1.mdx | 33 + chapters/fr/chapter7/2.mdx | 981 ++++++++++++++ chapters/fr/chapter7/3.mdx | 1042 +++++++++++++++ chapters/fr/chapter7/4.mdx | 999 ++++++++++++++ chapters/fr/chapter7/5.mdx | 1065 +++++++++++++++ chapters/fr/chapter7/6.mdx | 904 +++++++++++++ chapters/fr/chapter7/7.mdx | 1228 ++++++++++++++++++ chapters/fr/chapter7/8.mdx | 17 + chapters/fr/chapter7/9.mdx | 324 +++++ chapters/fr/chapter8/1.mdx | 12 + chapters/fr/chapter8/2.mdx | 375 ++++++ chapters/fr/chapter8/3.mdx | 205 +++ chapters/fr/chapter8/4.mdx | 787 +++++++++++ chapters/fr/chapter8/4_tf.mdx | 487 +++++++ chapters/fr/chapter8/5.mdx | 91 ++ chapters/fr/chapter8/6.mdx | 7 + chapters/fr/chapter8/7.mdx | 198 +++ chapters/fr/event/1.mdx | 170 +++ chapters/gj/_toctree.yml | 5 + chapters/gj/chapter0/1.mdx | 13 + 74 files changed, 16368 insertions(+), 87 deletions(-) create mode 100644 chapters/bn/chapter1/1.mdx create mode 100644 chapters/de/chapter3/1.mdx create mode 100644 chapters/de/chapter3/2.mdx create mode 100644 chapters/de/chapter3/3.mdx create mode 100644 chapters/de/chapter3/3_tf.mdx create mode 100644 chapters/de/chapter3/4.mdx create mode 100644 chapters/de/chapter3/5.mdx create mode 100644 chapters/de/chapter3/6.mdx create mode 100644 chapters/fa/chapter4/1.mdx create mode 100644 chapters/fr/chapter3/3.mdx create mode 100644 chapters/fr/chapter3/3_tf.mdx create mode 100644 chapters/fr/chapter3/4.mdx create mode 100644 chapters/fr/chapter3/5.mdx create mode 100644 chapters/fr/chapter3/6.mdx create mode 100644 chapters/fr/chapter4/1.mdx create mode 100644 chapters/fr/chapter4/2.mdx create mode 100644 chapters/fr/chapter4/3.mdx create mode 100644 chapters/fr/chapter4/4.mdx create mode 100644 chapters/fr/chapter4/5.mdx create mode 100644 chapters/fr/chapter4/6.mdx create mode 100644 chapters/fr/chapter6/1.mdx create mode 100644 chapters/fr/chapter6/10.mdx create mode 100644 chapters/fr/chapter6/2.mdx create mode 100644 chapters/fr/chapter6/3.mdx create mode 100644 chapters/fr/chapter6/3b.mdx create mode 100644 chapters/fr/chapter6/4.mdx create mode 100644 chapters/fr/chapter6/5.mdx create mode 100644 chapters/fr/chapter6/6.mdx create mode 100644 chapters/fr/chapter6/7.mdx create mode 100644 chapters/fr/chapter6/8.mdx create mode 100644 chapters/fr/chapter6/9.mdx create mode 100644 chapters/fr/chapter7/1.mdx create mode 100644 chapters/fr/chapter7/2.mdx create mode 100644 chapters/fr/chapter7/3.mdx create mode 100644 chapters/fr/chapter7/4.mdx create mode 100644 chapters/fr/chapter7/5.mdx create mode 100644 chapters/fr/chapter7/6.mdx create mode 100644 chapters/fr/chapter7/7.mdx create mode 100644 chapters/fr/chapter7/8.mdx create mode 100644 chapters/fr/chapter7/9.mdx create mode 100644 chapters/fr/chapter8/1.mdx create mode 100644 chapters/fr/chapter8/2.mdx create mode 100644 chapters/fr/chapter8/3.mdx create mode 100644 chapters/fr/chapter8/4.mdx create mode 100644 chapters/fr/chapter8/4_tf.mdx create mode 100644 chapters/fr/chapter8/5.mdx create mode 100644 chapters/fr/chapter8/6.mdx create mode 100644 chapters/fr/chapter8/7.mdx create mode 100644 chapters/fr/event/1.mdx create mode 100644 chapters/gj/_toctree.yml create mode 100644 chapters/gj/chapter0/1.mdx diff --git a/.github/workflows/build_documentation.yml b/.github/workflows/build_documentation.yml index 400030b38..429dbafeb 100644 --- a/.github/workflows/build_documentation.yml +++ b/.github/workflows/build_documentation.yml @@ -14,6 +14,6 @@ jobs: package: course path_to_docs: course/chapters/ additional_args: --not_python_module - languages: ar bn de en es fa fr he hi ja ko pt ru th tr zh + languages: ar bn de en es fa fr gj he hi ja ko pt ru th tr zh secrets: - token: ${{ secrets.HUGGINGFACE_PUSH }} \ No newline at end of file + token: ${{ secrets.HUGGINGFACE_PUSH }} diff --git a/.github/workflows/build_pr_documentation.yml b/.github/workflows/build_pr_documentation.yml index 1aacd2e41..2a6c8a860 100644 --- a/.github/workflows/build_pr_documentation.yml +++ b/.github/workflows/build_pr_documentation.yml @@ -16,5 +16,5 @@ jobs: package: course path_to_docs: course/chapters/ additional_args: --not_python_module - languages: ar bn de en es fa fr he hi ja ko pt ru th tr zh + languages: ar bn de en es fa fr gj he hi ja ko pt ru th tr zh hub_base_path: https://moon-ci-docs.huggingface.co/course diff --git a/README.md b/README.md index 7210cdb1e..ccde61508 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,13 @@ This repo contains the content that's used to create the **[Hugging Face course] |:-------------------------------------------------------|:-----------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | [English](https://huggingface.co/course/en/chapter1/1) | [`chapters/en`](https://github.com/huggingface/course/tree/main/chapters/en) | [@sgugger](https://github.com/sgugger), [@lewtun](https://github.com/lewtun), [@LysandreJik](https://github.com/LysandreJik), [@Rocketknight1](https://github.com/Rocketknight1), [@sashavor](https://github.com/sashavor), [@osanseviero](https://github.com/osanseviero), [@SaulLu](https://github.com/SaulLu), [@lvwerra](https://github.com/lvwerra) | | [Chinese (simplified)](https://huggingface.co/course/zh/chapter1/1) (WIP) | [`chapters/zh`](https://github.com/huggingface/course/tree/main/chapters/zh) | [@zhlhyx](https://github.com/zhlhyx), [petrichor1122](https://github.com/petrichor1122), [@1375626371](https://github.com/1375626371) | +| [French](https://huggingface.co/course/fr/chapter1/1) (WIP) | [`chapters/fr`](https://github.com/huggingface/course/tree/main/chapters/fr) | [@lbourdois](https://github.com/lbourdois), [@ChainYo](https://github.com/ChainYo), [@melaniedrevet](https://github.com/melaniedrevet), [@abdouaziz](https://github.com/abdouaziz) | | [Hindi](https://huggingface.co/course/hi/chapter1/1) (WIP) | [`chapters/hi`](https://github.com/huggingface/course/tree/main/chapters/hi) | [@pandyaved98](https://github.com/pandyaved98) | | [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) | | [Persian](https://huggingface.co/course/fa/chapter1/1) (WIP) | [`chapters/fa`](https://github.com/huggingface/course/tree/main/chapters/fa) | [@jowharshamshiri](https://github.com/jowharshamshiri), [@schoobani](https://github.com/schoobani) | -| [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) | +| [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) | | [Spanish](https://huggingface.co/course/es/chapter1/1) (WIP) | [`chapters/es`](https://github.com/huggingface/course/tree/main/chapters/es) | [@camartinezbu](https://github.com/camartinezbu), [@munozariasjm](https://github.com/munozariasjm) | -| [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) | +| [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) | ### Translating the course into your language diff --git a/chapters/bn/_toctree.yml b/chapters/bn/_toctree.yml index 6be05c074..f7a43f15f 100644 --- a/chapters/bn/_toctree.yml +++ b/chapters/bn/_toctree.yml @@ -2,3 +2,8 @@ sections: - local: chapter0/1 title: ভূমিকা + +- title: 1. ট্রান্সফরমার মডেল + sections: + - local: chapter1/1 + title: ভূমিকা \ No newline at end of file diff --git a/chapters/bn/chapter1/1.mdx b/chapters/bn/chapter1/1.mdx new file mode 100644 index 000000000..339d066d5 --- /dev/null +++ b/chapters/bn/chapter1/1.mdx @@ -0,0 +1,59 @@ +# ভূমিকা + +## 🤗 কোর্সে স্বাগতম! + + + +এই কোর্সটি আপনাকে [হাগিং ফেস](https://huggingface.co/) ইকোসিস্টেম থেকে — 🤗 + [🤗 ট্রান্সফরমার](https://github.com/huggingface/transformers),[🤗 ডেটাসেট](https://github.com/huggingface/datasets), [🤗 টোকেনাইজার](https://github.com/huggingface/tokenizers),এবং [🤗 অ্যাক্সিলারেট](https://github.com/huggingface/accelerate) — সেইসাথে হাগিং ফেস হাব থেকে লাইব্রেরি ব্যবহার করে ন্যচারাল ল্যঙ্গুএজ প্রসেসিং(NLP) শেখাবে। এটি সাইট টি বিজ্ঞাপন ছাড়াই সম্পূর্ণ ফ্রি। + + +## এই কোর্সটি থেকে কি আশা করা যায়? + +এখানে কোর্সের একটি সংক্ষিপ্ত বিবরণ রয়েছে: + +
+Brief overview of the chapters of the course. + +
+ +- অধ্যায় ১ থেকে ৪ 🤗 ট্রান্সফরমার লাইব্রেরির মূল ধারণাগুলির একটি ভূমিকা প্রদান করে। কোর্সের এই অংশের শেষে, আপনি ট্রান্সফরমার মডেলগুলি কীভাবে কাজ করে তার সাথে পরিচিত হবেন। এছাড়াও [হাগিং ফেস হাব](https://huggingface.co/models) থেকে একটি মডেল কীভাবে ব্যবহার করতে হয়, কীভাবে মডেল এর ডেটাসেটিকে ফাইন-টিউন করতে হয় এবং হাবে কীভাবে আপনার ফলাফল শেয়ার করতে হয় তা জানতে পারবেন! + +- ক্লাসিক NLP টাস্কগুলোর গভীরে যাওয়ার আগে অধ্যায় ৫ থেকে ৮, আপনাকে 🤗 ডেটাসেট এবং 🤗 টোকেনাইজারগুলির মূল বিষয়গুলি শেখাবে৷ এই অংশের শেষে, আপনি নিজেই সবচেয়ে কমন NLP সমস্যাগুলি সমাধান করতে পাড়বেন। + +- অধ্যায় ৯ থেকে ১২, এবং স্পীচ প্রসেসিং এবং কম্পিউটার ভিশনের কাজগুলো ট্রান্সফরমার মডেলগুলোকে কীভাবে ব্যবহার করা যেতে পারে তা খতিয়ে দেখে। অধ্যায়টি পড়তে পড়তে আপনি শিখবেন কিভাবে আপনার মডেল বিল্ড এবং ডেমো শেয়ার করতে হয়, কিভাবে প্রডাকশন এনভায়রনমেন্টের জন্য অপ্টিমাইজ করতে হয়। এই অংশের শেষ নাগাদ, (প্রায়) যেকোনও মেশিন লার্নিং সমস্যায় আপনি 🤗 ট্রান্সফরমার প্রয়োগ করতে প্রস্তুত হয়ে যাবেন! + + +এই কোর্স:: + +* পাইথন সম্পর্কে ভাল জ্ঞান প্রয়োজন +* একটি প্রাথমিক ডিপ লার্নিং কোর্স করে নেওয়া ভালো, যেমন fast.ai-এর [fast.ai's](https://www.fast.ai/) [Practical Deep Learning for Coders](https://course.fast.ai/) বা [DeepLearning.AI](https://www.deeplearning.ai/) এর প্রোগ্রামগুলির মধ্যে একটি। +* [PyTorch](https://pytorch.org/) বা [TensorFlow](https://www.tensorflow.org/) জানা জরুরি না, যদিও এদের যেকোনো একটির সাথে কিছু পরিচয় থাকলে সেটা আপানাকে সাহায্য করবে। + +আপনি এই কোর্সটি সম্পন্ন করার পরে, আমরা DeepLearning.AI-এর [Natural Language Processing Specialization](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh) কোর্সটি করার পরামর্শ দিই, যেটি প্রথাগত NLP মডেল যেমন naive Bayes এবং LSTMs সম্পর্কে জানতে আপনাকে সাহায্য করবে! + +## আমরা কারা?? + +লেখক সম্পর্কে:: + +**Matthew Carrigan ম্যাথিউ ক্যারিগান** Hugging Face এর একজন মেশিন লার্নিং ইঞ্জিনিয়ার। তিনি আয়ারল্যান্ডের ডাবলিনে থাকেন এবং পূর্বে Parse.ly-এ একজন ML ইঞ্জিনিয়ার হিসেবে এবং তার আগে ট্রিনিটি কলেজ ডাবলিন-এ একজন পোস্ট-ডক্টরাল গবেষক হিসেবে কাজ করেছেন। তিনি বিশ্বাস করেন না যে আমরা বিদ্যমান আর্কিটেকচারগুলিকে স্কেল করে AGI তে পৌছাবো, তবে তিনি দৃড়ভাবে আশা করেন যে আমারা রোবট অমরত্বের দিকে যাচ্ছি৷ + +**Lysandre Debut লিসান্দ্রে ডেব্যু** Hugging Face এর একজন মেশিন লার্নিং ইঞ্জিনিয়ার এবং খুব প্রাথমিক পর্যায় থেকে 🤗 Transformers লাইব্রেরিতে কাজ করছেন। তার লক্ষ্য হল একটি খুব সাধারণ API-এর সাহায্যে টুল ডেভেলপ করে সবার জন্য NLP কে সুবোধ্য করে তোলা। + +**Sylvain Gugger সিলভাইন গুগার** হলেন হাগিং ফেসের একজন গবেষণা প্রকৌশলী এবং 🤗 ট্রান্সফরমার লাইব্রেরির মূল রক্ষণাবেক্ষণকারীদের একজন। পূর্বে তিনি fast.ai-এর একজন গবেষণা বিজ্ঞানী ছিলেন এবং জেরেমি হাওয়ার্ডের সাথে _[Deep Learning for Coders with fastai and PyTorch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)_ বইটি লেখেন। তার গবেষণার মূল ফোকাস হল মডেল গুলিকে আরও উন্নত করে এবং অল্প রিসোর্স ব্যবহার করে ট্রেনিং ব্যবস্থা করার মাধ্যমে সবার জন্য deep learning এর কৌশলগুলি আরও সুবোধ্য করে তোলা। + +**Merve Noyan মার্ভে নইয়ান** হলেন Hugging Face এর একজন ডেভেলপার অ্যাডভোকেট। যিনি টুল ডেভেলপ করেন এবং সেগুলো ব্যবহার করে কন্টেন্ট তৈরি করেন যাতে মেশিন লার্নিংকে গণতান্ত্রিক করা যায়। + +**Lucile Saulnier** হলেন Hugging Face এর একজন মেশিন লার্নিং ইঞ্জিনিয়ার, যিনি ওপেন সোর্স টুলের ডেভেলপমেন্ট ও ব্যবহার এ সাহায্য করে থাকেন। তিনি ন্যচালার ল্যঙ্গুএজ প্রসেসিং এর পাশাপাশি collaborative training এবং বিগসায়েন্সের মতো বিষয়ের অনেক গবেষণা প্রকল্পে সক্রিয়ভাবে জড়িত। + + +**Lewis Tunstall** হলেন একজন মেশিন লার্নিং ইঞ্জিনিয়ার, যিনি ওপেন-সোর্স টুল ডেভেলপ করতে এবং সেগুলিকে বৃহত্তর সম্প্রদায়ের কাছে অ্যাক্সেসযোগ্য করে তোলার দিকে মনোনিবেশ করেন৷ তিনি একটি আসন্ন একটি বইয়ের সহ-লেখক [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). + + +**Leandro von Werra** হলেন Hugging Face-এর ওপেন-সোর্স টিমের একজন মেশিন লার্নিং ইঞ্জিনিয়ার এবং ট্রান্সফরমারের উপর একটি আসন্ন O'Reilly বইয়ের সহ-লেখক [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). পুরো মেশিন লার্নিং স্ট্যাক জুড়ে কাজ করে NLP প্রকল্পগুলিকে উৎপাদনে নিয়ে আসার কয়েক বছরের ইন্ডাস্ট্রি অভিজ্ঞতা রয়েছে তার। + + +আপনি রোল প্রস্তুত? এই অধ্যায়ে, আপনি শিখবেন: +* কিভাবে টেক্সট জেনারেশন এবং শ্রেণীবিভাগের মতো NLP কাজগুলি সমাধান করতে ` `pipeline()` ফাংশন ব্যবহার করবেন +* ট্রান্সফরমার আর্কিটেকচার সম্পর্কে +* কিভাবে এনকোডার, ডিকোডার এবং এনকোডার-ডিকোডার আর্কিটেকচারের মধ্যে পার্থক্য করা যায় এবং কেস ব্যবহার করা যায়। diff --git a/chapters/de/_toctree.yml b/chapters/de/_toctree.yml index a5e9d0934..0aae386ae 100644 --- a/chapters/de/_toctree.yml +++ b/chapters/de/_toctree.yml @@ -3,7 +3,24 @@ - local: chapter0/1 title: Einführung +- title: 3. Fine-tuning von vortrainierten Modellen + sections: + - local: chapter3/1 + title: Einführung + - local: chapter3/2 + title: Datenbearbeitung + - local: chapter3/3 + title: Fine-tuning von Modellen mit der Trainer API oder Keras + local_fw: { pt: chapter3/3, tf: chapter3/3_tf } + - local: chapter3/4 + title: Komplettes Training + - local: chapter3/5 + title: Fine-tuning, Check! + - local: chapter3/6 + title: Quiz am Ende des Kapitels + quiz: 3 + - title: Wörterverzeichnis sections: - local: glossary/1 - title: Wörterverzeichnis \ No newline at end of file + title: Wörterverzeichnis diff --git a/chapters/de/chapter3/1.mdx b/chapters/de/chapter3/1.mdx new file mode 100644 index 000000000..0f1c7041c --- /dev/null +++ b/chapters/de/chapter3/1.mdx @@ -0,0 +1,21 @@ + + +# Einführung + +In [Kapitel 2](/course/chapter2) haben wir behandelt, wie man Tokenizer und vortrainierte Modelle verwendet, um Vorhersagen zu treffen. Was passiert aber, wenn wir ein vortrainiertes Modell für unseren eigenen Datensatz optimieren möchten? Das ist das Thema dieses Kapitels! Folgendes wirst du lernen: + +{#if fw === 'pt'} +* Wie bereitet man einen großen Datensatz aus dem Hub vor? +* Wie nutzt man die höhere `Trainer` API um Modelle zu fein-tunen? +* Wie implementiert man eine benutzerdefinierte Trainingsschleife +* Wie nutzen wir die 🤗 Accelerate Bibliothek für benutzerdefinierte Trainingschleifen auf verteilten Systemen + +{:else} +* Wie bereitet man einen großen Datensatz aus dem Hub vor? +* Wie nutzt man Keras um Modelle zu fein-tunen? +* Wie setzt man Keras für Vorhersagen ein? +* Wie implementiert benutzerdefinierte Metriken? + +{/if} + +Um deine trainierten Checkpoints auf den Hugging Face Hub hochzuladen, benötigst du ein huggingface.co-Konto: [Erstelle ein Konto](https://huggingface.co/join) \ No newline at end of file diff --git a/chapters/de/chapter3/2.mdx b/chapters/de/chapter3/2.mdx new file mode 100644 index 000000000..21c6df41a --- /dev/null +++ b/chapters/de/chapter3/2.mdx @@ -0,0 +1,381 @@ + + +# Vorbereitung der Daten + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} +Wir fahren mit dem Beispiel aus dem [vorigen Kapitel](/course/chapter2) fort. Folgenderweise würden wir einen Sequenzklassifikator mit einem Batch in PyTorch trainieren: + +```python +import torch +from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification + +# Genau wie vorher +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", # Ich habe mein ganzes Leben auf einen HuggingFace-Kurs gewartet. + "This course is amazing!", # Dieser Kurs ist fantastisch! +] +batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") + +# Dies ist neu +batch["labels"] = torch.tensor([1, 1]) + +optimizer = AdamW(model.parameters()) +loss = model(**batch).loss +loss.backward() +optimizer.step() +``` +{:else} +Wir fahren mit dem Beispiel aus dem [vorigen Kapitel](/course/chapter2) fort. Folgenderweise würden wir einen Sequenzklassifikator mit einem Batch in Tensorflow trainieren: + +```python +import tensorflow as tf +import numpy as np +from transformers import AutoTokenizer, TFAutoModelForSequenceClassification + +# Genau wie vorher +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", # Ich habe mein ganzes Leben auf einen HuggingFace-Kurs gewartet. + "This course is amazing!", # Dieser Kurs ist fantastisch! +] +batch = dict(tokenizer(sequences, padding=True, truncation=True, return_tensors="tf")) + +# Dies ist neu +model.compile(optimizer="adam", loss="sparse_categorical_crossentropy") +labels = tf.convert_to_tensor([1, 1]) +model.train_on_batch(batch, labels) +``` +{/if} + +Natürlich würde das Training von Modellen mit nur zwei Sätzen keine sonderlich guten Ergebnisse liefern. Um bessere Ergebnisse zu erzielen, müssen wir einen größeren Datensatz vorbereiten. + +In diesem Abschnitt verwenden wir den MRPC-Datensatz (Microsoft Research Paraphrase Corpus) als Beispiel. Dieser wurde in einem [Paper](https://www.aclweb.org/anthology/I05-5002.pdf) von William B. Dolan und Chris Brockett veröffentlicht. Der Datensatz besteht aus insgesamt 5.801 Satzpaaren und enthält ein Label, das angibt, ob es sich bei einem Paar um Paraphrasen handelt (d.h. ob beide Sätze dasselbe bedeuten). Wir haben diesen Datensatz für dieses Kapitel ausgewählt, weil es sich um einen kleinen Datensatz handelt, sodass es einfach ist, während dem Training zu experimentieren. + +### Laden eines Datensatzes vom Hub + +{#if fw === 'pt'} + +{:else} + +{/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. + +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: + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("glue", "mrpc") +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 408 + }) + test: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 1725 + }) +}) +``` + +Wie du sehen kannst, erhalten wir ein `DatasetDict`-Objekt, das die Trainingsdaten, die Validierungsdaten und die Testdaten enthält. Jedes Objekt enthält mehrere Spalten (`sentence1`, `sentence2`, `label` und `idx`) und eine unterschiedliche Anzahl an Zeilen, dies ist die Anzahl der Elemente in jedem Datensatz (also gibt es 3.668 Satzpaare in den Trainingsdaten, 408 in den Validierungsdaten und 1.725 in den Testdaten). + +Dieser Befehl lädt das Dataset herunter und speichert es im Cache, standardmäßig in *~/.cache/huggingface/dataset*. Wir Erinnern uns an Kapitel 2, dass der Cache-Ordner anpasst werden kann, indem man die Umgebungsvariable `HF_HOME` setzt. + +Wir können auf jedes Satzpaar in unserem `raw_datasets`-Objekt zugreifen, indem wir wie bei einem Dictionary einen Schlüsselwert als Index verwenden: + +```py +raw_train_dataset = raw_datasets["train"] +raw_train_dataset[0] +``` + +```python out +{'idx': 0, + 'label': 1, + 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', + 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} +``` + +Wir stellen fest, dass die Labels bereits Ganzzahlen sind, sodass wir dort keine Vorverarbeitung durchführen müssen. Wir können die `features` von `raw_train_dataset` untersuchen, um zu erfahren, welche Ganzzahl welchem Label entspricht. Der folgende Befehl gibt uns den Variablentyp zurück: + +```py +raw_train_dataset.features +``` + +```python out +{'sentence1': Value(dtype='string', id=None), + 'sentence2': Value(dtype='string', id=None), + 'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None), + 'idx': Value(dtype='int32', id=None)} +``` + +Hinter den Kulissen ist `label` vom Typ `ClassLabel`, und die Zuordnung von Ganzzahlen zum Labelnamen wird im Ordner *names* gespeichert. `0` entspricht `not_equivalent`, also "nicht äquivalent", und `1` entspricht `equivalent`, also "äquivalent". + + + +✏️ **Probier es aus!** Sieh dir das Element 15 der Trainingsdaten und Element 87 des Validierungsdaten an. Was sind ihre Labels? + + + +### Vorverarbeitung eines Datensatzes + +{#if fw === 'pt'} + +{:else} + +{/if} + +Um den Datensatz vorzubereiten, müssen wir den Text in Zahlen umwandeln, die das Modell sinnvoll verarbeiten kann. Im [vorherigen Kapitel](/course/chapter2) haben wir gesehen, dass dies mit einem Tokenizer gemacht wird. Wir können den Tokenizer mit einem Satz oder einer Liste von Sätzen füttern, sodass wir die ersten und zweiten Sätze jedes Paares wie folgt direkt tokenisieren können: + +```py +from transformers import AutoTokenizer + +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"]) +tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"]) +``` + +Wir können jedoch nicht einfach zwei Sequenzen an das Modell übergeben und eine Vorhersage erhalten, ob die beiden Sätze paraphrasiert sind oder nicht. Wir müssen die beiden Sequenzen als Paar behandeln und die entsprechende Vorverarbeitung anwenden. Glücklicherweise kann der Tokenizer auch ein Sequenzpaar nehmen und es so vorbereiten, wie es unser BERT-Modell erwartet: + +```py +inputs = tokenizer("This is the first sentence.", "This is the second one.") +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] +} +``` + +In [Kapitel 2](/course/chapter2) haben wir die Schlüsselwerte `input_ids` und `attention_mask` behandelt, allerdings haben wir es aufgeschoben, über `token_type_ids` zu sprechen. In diesem Beispiel teilt diese dem Modell mit, welcher Teil des Input der erste Satz und welcher der zweite Satz ist. + + + +✏️ **Probier es aus!** Nimm Element 15 der Trainingsdaten und tokenisiere die beiden Sätze separat und als Paar. Wo liegt der Unterschied zwischen den beiden Ergebnissen? + + + +Wenn wir die IDs in `input_ids` zurück in Worte dekodieren: + +```py +tokenizer.convert_ids_to_tokens(inputs["input_ids"]) +``` + +dann bekommen wir: + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +``` + +Wir sehen also wenn es zwei Sätze gibt, dass das Modell erwartet, dass die Inputs die Form "[CLS] Satz1 [SEP] Satz2 [SEP]" haben. Wenn wir dies mit den `token_type_ids` abgleichen, erhalten wir: + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +[ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] +``` + +Wie du sehen kannst, haben die Teile der Eingabe, die `[CLS] Satz1 [SEP]` entsprechen, alle eine Token-Typ-ID von `0`, während die anderen Teile, die `Satz2 [SEP]` entsprechen, alle einer Token-Typ-ID von `1` enthalten. + +Beachte, dass die Auswahl eines anderen Checkpoints nicht unbedingt die `token_type_ids` in Ihren tokenisierten Inputs haben (z.B. werden sie nicht zurückgegeben, wenn ein DistilBERT-Modell verwendet wird). Sie werden nur zurückgegeben, wenn das Modell weiß was damit zu tun ist, weil es die Toke-Typ-Ids während des Vortrainings gesehen hat. + +In diesem Fall ist BERT mit Token-Typ-IDs vortrainiert worden, und zusätzlich zu dem maskierten Sprachmodellierungsziel aud [Kapitel 1](/course/chapter1), hat es ein zusätzliches Vorhersageziel namens _next sentence prediction_ (d.h. Vorhersage des nächsten Satzes). Das Ziel dieser Aufgabe ist es, die Beziehung zwischen Satzpaaren zu modellieren. + +Bei der Vorhersage des nächsten Satzes werden dem Modell Satzpaare (mit zufällig maskierten Token) bereitgestellt und erwartet, vorherzusagen, ob auf den ersten Satz der zweite Satz folgt. Um die Aufgabe non-trivial zu machen, folgen sich die Hälfte der Sätze in dem Originaldokument, aus dem sie extrahiert wurden, aufeinander, und in der anderen Hälfte stammen die beiden Sätze aus zwei verschiedenen Dokumenten. + +Im Allgemeinen muss man sich keine Gedanken darüber machen, ob Ihre tokenisierten Inputs `token_type_ids` enthalten oder nicht: Solange du denselben Checkpoint für den Tokenizer und das Modell verwendest, ist alles in Ordnung, da der Tokenizer weiß, was er dem Modell bereitstellen soll. + +Nachdem wir nun gesehen haben, wie unser Tokenizer mit einem Satzpaar umgehen kann, können wir damit unseren gesamten Datensatz tokenisieren: Wie im [vorherigen Kapitel](/course/chapter2) können wir dem Tokenizer eine Liste von Satzpaaren einspeisen, indem du ihm die Liste der ersten Sätze und dann die Liste der zweiten Sätze gibst. Dies ist auch kompatibel mit den Optionen zum Padding und Trunkieren, die wir in [Kapitel 2](/course/chapter2) gesehen haben. Eine Möglichkeit, den Trainingsdatensatz vorzuverarbeiten, ist also: + +```py +tokenized_dataset = tokenizer( + raw_datasets["train"]["sentence1"], + raw_datasets["train"]["sentence2"], + padding=True, + truncation=True, +) +``` + +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: + +```py +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) +``` + +Diese Funktion nimmt ein Dictionary (wie die Elemente unseres Datensatzes) und gibt ein neues Dictionary mit den Schlüsselwerten `input_ids`, `attention_mask` und `token_type_ids` zurück. Beachte, dass es auch funktioniert, wenn das `example`-Dictionary mehrere Beispiele enthält (jeder Schlüsselwert als Liste von Sätzen), da der `Tokenizer`, wie zuvor gesehen, mit Listen von Satzpaaren arbeitet. Dadurch können wir die Option `batched=True` in unserem Aufruf von `map()` verwenden, was die Tokenisierung erheblich beschleunigt. Der `tokenizer` wurde in Rust geschriebenen und ist in der Bibliothek [🤗 Tokenizers](https://github.com/huggingface/tokenizers) verfügbar. Dieser Tokenizer kann sehr schnell arbeiten, wenn wir ihm viele Inputs auf einmal zum Verarbeiten geben. Note that we've left the `padding` argument out in our tokenization function for now. + +Beachte, dass wir das `padding`-Argument vorerst in unserer Tokenisierungsfunktion ausgelassen haben. Dies liegt daran, dass das Anwenden von Padding auf alle Elemente unserer Daten auf die maximale Länge nicht effizient ist: Es ist besser, die Proben aufzufüllen, wenn wir ein Batch erstellen, da wir dann nur auf die maximale Länge in diesem Batch auffüllen müssen und nicht auf die maximale Länge in den gesamten Datensatz. Dies kann viel Zeit und Rechenleistung sparen, besonders wenn die Eingaben stark variable Längen haben! + +So wenden wir die Tokenisierungsfunktion auf alle unsere Datensätze gleichzeitig an. In unserem Aufruf von `map` verwenden wir `batched=True`, damit die Funktion auf mehrere Elemente des Datensatzes gleichzeitig angewendet wird und nicht auf jedes Element separat. Dies ermöglicht eine schnellere Vorverarbeitung. + +```py +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) +tokenized_datasets +``` + +Die Bibliothek 🤗 Datasets verarbeitet Datensätzen indem sie neue Felder hinzuzufügen, eines für jeden Schlüssel im Dictionary, der von der Vorverarbeitungsfunktion zurückgegeben wird: + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 408 + }) + test: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 1725 + }) +}) +``` + +Du kannst sogar Multiprocessing verwenden, wenn du die Vorverarbeitungsfunktion mit `map()` anwendest, indem du ein `num_proc`-Argument übergiebst. Wir haben dies hier nicht getan, weil die 🤗 Tokenizers-Bibliothek bereits mehrere Threads verwendet, um unsere Samples schneller zu tokenisieren. Wenn du keinen schnellen Tokenizer verwendest, der von dieser Bibliothek unterstützt wird, würde dies allerdings die Vorverarbeitung beschleunigen. + +Unsere `tokenize_function` gibt ein Dictionary mit den Schlüsselwerten `input_ids`, `attention_mask` und `token_type_ids` zurück, also werden diese drei Felder zu allen Splits unseres Datensatzes hinzugefügt. Beachte, dass wir auch vorhandene Felder ändern könnten, wenn unsere Vorverarbeitungsfunktion einen neuen Wert für einen vorhandenen Schlüsselwert in dem Datensatz zurückgegeben hätte, auf den wir `map()` angewendet haben. + +Zuletzt, müssen wir alle Beispiele auf die Länge des längsten Elements aufzufüllen, wenn wir Elemente zusammenfassen – eine Technik, die wir als *Dynamisches Padding* bezeichnen. + +### Dynamisches Padding + + + +{#if fw === 'pt'} + +Die Funktion, die für das Zusammenstellen von Samples innerhalb eines Batches verantwortlich ist, wird als *Collate-Funktion* bezeichnet. Es ist ein Argument, das du übergeben kannst, wenn du einen `DataLoader` baust, wobei es standardmäßig eine Funktion ist, die die Daten in PyTorch-Tensoren umwandelt und zusammenfügt (rekursiv wenn die Elemente Listen, Tupel oder Dictionaries sind). Dies ist in unserem Fall nicht möglich, da die Inputs nicht alle gleich groß sind. Das Padding haben wir bewusst aufgeschoben, um es bei jedem Batch nur bei Bedarf anzuwenden und überlange Inputs mit massivem Padding zu vermeiden. Dies beschleunigt das Training zwar, aber beachte, dass das Training auf einer TPU Probleme verursachen kann – TPUs bevorzugen feste Formen, auch wenn das ein zusätzliches Padding erfordert. + +{:else} + +Die Funktion, die für das Zusammenstellen von Samples innerhalb eines Batches verantwortlich ist, wird als *Collate-Funktion* bezeichnet. Es ist ein Argument, das du übergeben kannst, wenn du einen `DataLoader` baust, wobei es standardmäßig eine Funktion ist, die die Daten in tf.Tensor umwandelt und zusammenfügt (rekursiv wenn die Elemente Listen, Tupel oder Dictionaries sind). Dies ist in unserem Fall nicht möglich, da die Inputs nicht alle gleich groß sind. Das Padding haben wir bewusst aufgeschoben, um es bei jedem Batch nur bei Bedarf anzuwenden und überlange Inputs mit massivem Padding zu vermeiden. Dies beschleunigt das Training zwar, aber beachte, dass das Training auf einer TPU Probleme verursachen kann – TPUs bevorzugen feste Formen, auch wenn das ein zusätzliches Padding erfordert. + +{/if} + +In der Praxis müssen wir eine Collate-Funktion definieren, die die korrekte Menge an Padding auf die Elemente des Datensatzes anwendet, die wir in einem Batch haben möchten. Glücklicherweise stellt uns die 🤗 Transformers-Bibliothek über `DataCollatorWithPadding` eine solche Funktion zur Verfügung. Wenn sie instanziert wird, braucht es einen Tokenizer (um zu wissen, welches Padding-token verwendet werden soll und ob das Modell erwartet, dass sich das Padding links oder rechts von den Inputs befindet) und übernimmt alles was wir brauchen: + +{#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} + +Um dieses neue Werkzeug zu testen, nehmen wir einige Elemente aus den Trainingsdaten, die wir als Batch verwenden möchten. Hier entfernen wir die Spalten `idx`, `sentence1` und `sentence2`, da sie nicht benötigt werden und Strings enthalten (wir können keine Tensoren mit Strings erstellen) und sehen uns die Länge jedes Eintrags im Batch an: + +```py +samples = tokenized_datasets["train"][:8] +samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]} +[len(x) for x in samples["input_ids"]] +``` + +```python out +[50, 59, 47, 67, 59, 50, 62, 32] +``` + +Wenig überraschen erhalten wir Samples unterschiedlicher Länge von 32 bis 67. Dynamisches Padding bedeutet, dass die Elemente in diesem Batch alle auf eine Länge von 67 aufgefüllt werden, die maximale Länge innerhalb des Batches. Ohne dynamisches Auffüllen müssten alle Einträge auf die maximale Länge im gesamten Datensatz oder auf die maximale Länge die das Modell akzeptiert, aufgefüllt werden. Lass uns noch einmal überprüfen, ob unser `data_collator` den Stapel dynamisch richtig auffüllt: + +```py +batch = data_collator(samples) +{k: v.shape for k, v in batch.items()} +``` + +{#if fw === 'tf'} + +```python out +{'attention_mask': TensorShape([8, 67]), + 'input_ids': TensorShape([8, 67]), + 'token_type_ids': TensorShape([8, 67]), + 'labels': TensorShape([8])} +``` + +{:else} + +```python out +{'attention_mask': torch.Size([8, 67]), + 'input_ids': torch.Size([8, 67]), + 'token_type_ids': torch.Size([8, 67]), + 'labels': torch.Size([8])} +``` + +Das sieht gut aus! Jetzt, da wir vom Rohtext zu Batches übergegangen sind, mit denen unser Modell umgehen kann, sind wir bereit zum fein-tunen! + +{/if} + + + +✏️ **Probier es aus!** Repliziere die Vorverarbeitung auf dem GLUE SST-2-Datensatz. Es ist ein bisschen anders, da es aus einzelnen Sätzen statt aus Paaren besteht, aber der Rest von dem, was wir gemacht haben, sollte gleich aussehen. Alternative wäre eine schwierigere Herausforderung, eine Vorverarbeitungsfunktion zu schreiben, die bei allen GLUE-Aufgaben funktioniert. + + + +{#if fw === 'tf'} + +Jetzt, da wir unseren Datensatz und einen DataCollator haben, müssen wir sie verbinden. Wir könnten Batches manuell laden und sortieren, aber das ist eine Menge Arbeit und wahrscheinlich auch nicht sehr sonderlich performant. Stattdessen gibt es eine einfache Methode, die dieses Problem performant löst: `to_tf_dataset()`. Dadurch wird ein `tf.data.Dataset` um den Datensatz gewickelt, mit einer optionalen Kollatierungsfunktion. `tf.data.Dataset` ist ein natives TensorFlow-Format, das Keras für `model.fit()` verwenden kann. Diese Methode kann einen 🤗-Datensatz ohne Umstände in ein fürs Training vorbereitetes Format konvertieren. Sehen wir es uns nun mit unserem Datensatz in Aktion an! + +```py +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, +) +``` +Und das was's! Wir können Datensätze in das nächste Kapitel mitnehmen, wo das Training nach all der harten Arbeit der Datenvorverarbeitung angenehm unkompliziert sein wird. + +{/if} diff --git a/chapters/de/chapter3/3.mdx b/chapters/de/chapter3/3.mdx new file mode 100644 index 000000000..3189863c2 --- /dev/null +++ b/chapters/de/chapter3/3.mdx @@ -0,0 +1,172 @@ + + +# Fine-tuning eine Modells mit der Trainer API + + + + + +🤗 Transformers stellt eine `Trainer`-Klasse bereit, mit der du Modelle auf deinen Datensätzen fein-tunen kannst. Nachdem die Datenverarbeitung im letzten Abschnitt abgeschlossen ist, bleiben nur noch wenige Schritte, um den `Trainer` zu definieren. Der schwierigste Teil ist die Vorbereitung der Umgebung um `Trainer.train()` auszuführen, da dies auf einer CPU sehr langsam läuft. Wenn keine GPU verfügbar ist, kannst du bei [Google Colab] (https://colab.research.google.com/) auf kostenlose GPUs oder TPUs zugreifen. + +In den folgenden Code-Beispielen wird davon ausgegangen, dass du die Beispiele aus dem vorherigen Abschnitt bereits ausgeführt hast. Hier ist eine kurze Zusammenfassung, die dir zeigt, was erwartet wird: + +```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) +``` + +### Training + +Als erstes müssen wir eine Klasse `TrainingArguments` definieren, die alle Hyperparameter enthält, die der `Trainer` für das Training und die Evaluation verwendet. Das einzige Argument das hier angegeben werden muss, ist ein Verzeichnis in dem das trainierte Modell sowie die Checkpoints gespeichert werden. Für alles andere können die Standardeinstellungen verwendet werden. Diese sollten für ein grundlegendes Fein-tunen ausreichen. + +```py +from transformers import TrainingArguments + +training_args = TrainingArguments("test-trainer") +``` + + + +💡 Wenn du dein Modell während des Trainings automatisch in das Hub hochladen möchtest, kann in `TrainingArguments` das Argument `push_to_hub=True` angegeben werden. Darüber erfahren wir in [Kapitel 4](/course/chapter4/3) mehr. + + + +Der zweite Schritt ist die Definition unseres Modells. Wie im [vorherigen Kapitel](/course/chapter2) verwenden wir die Klasse `AutoModelForSequenceClassification` mit zwei Labels: + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +Du wirst feststellen, dass du im Gegensatz zu [Kapitel 2](/course/chapter2) eine Warnung erhältst, nachdem du dieses vortrainierte Modell instanziiert hast. Der Grund dafür ist, dass BERT nicht auf die Klassifizierung von Satzpaaren vortrainiert wurde. Deshalb wurde der Kopf des vortrainierten Modells verworfen und stattdessen ein neuer Kopf hinzugefügt, der für die Klassifizierung von Sequenzen geeignet ist. Diese Warnungen weisen darauf hin, dass Teile der Gewichtung nicht verwendet wurden (die Gewichte für den verworfenen Kopf) und dass einige andere zufällig initialisiert wurden (die Gewichte für den neuen Kopf). Abschließend werden wir aufgefordert, das Modell zu trainieren, und genau das werden wir jetzt tun. + +Sobald wir unser Modell haben, können wir einen `Trainer` definieren, indem wir alle bisher erstellten Objekte übergeben - das `Modell`, die `training_args`, die Trainings- und Validierungsdaten, unseren `data_collator` und unseren `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, +) +``` + +Merke: Wenn der `tokenizer` übergeben wird, wie wir es hier getan haben, wird der vom `Trainer` verwendete `data_collator` standardmäßig ein `DataCollatorWithPadding` sein, wie er zuvor definiert wurde. Deshalb kannst du die Zeile `data_collator=data_collator` in diesem Aufruf weglassen. Unabhängig davon war es trotzdem wichtig, diesen Teil der Verarbeitung in Abschnitt 2 zu zeigen! + +Um das Modell auf unserem Datensatz fein-tunen zu können, müssen wir nur die Methode `train()` unseres `Trainers` aufrufen: + +```py +trainer.train() +``` + +Dadurch wird das Fein-tunen gestartet (was auf einer GPU ein paar Minuten dauern sollte) und der Trainingsverlust wird alle 500 Schritte gemeldet. Es wird jedoch nicht zurückgegeben, wie gut (oder schlecht) das Modell funktioniert. Dies liegt an folgenden Punkten: + +1. Wir haben dem `Trainer` nicht mitgeteilt die Performance in der Trainingsschleife auszuwerten, indem wir `evaluation_strategy` entweder auf `"steps"` (alle `eval_steps` auswerten) oder `"epoch"` (am Ende jeder Epoche evaluieren) gesetzt haben. +2. Wir haben dem `Trainer` keine Funktion `compute_metrics()` zur Verfügung gestellt, um während der Evaluation eine Metrik zu berechnen (sonst hätte die Evaluation nur den Verlust ausgegeben, was keine sehr intuitive Zahl ist). + + +### Evaluation + +Im Folgenden wird gezeigt, wie wir eine `compute_metrics()`-Funktion erstellen und sie beim nächsten Training verwenden können. Die Funktion muss ein `EvalPrediction`-Objekt (ein bennantes Tupel mit einem `predictions`-Feld und einem `label_ids`-Feld) annehmen und ein Dictionary zurückgeben, das Strings auf Floats abbildet (die Strings sind die Namen der zurückgegebenen Metriken und die Floats ihre zugehörigen Werte). Um Vorhersagen von unserem Modell zu erhalten, können wir den Befehl "Trainer.predict()" verwenden: + +```py +predictions = trainer.predict(tokenized_datasets["validation"]) +print(predictions.predictions.shape, predictions.label_ids.shape) +``` + +```python out +(408, 2) (408,) +``` + +Die Ausgabe der `predict()`-Methode ist ein weiteres benanntes Tupel mit drei Feldern: `predictions`, `label_ids` und `metrics`. Das Feld `metrics` enthält den Verlust des übergebenen Datensatzes sowie Zeitangaben dazu, wie lange die Vorhersage insgesamt und im Durchschnitt gedauert hat. Sobald wir unsere Funktion `compute_metrics()` fertiggestellt haben und sie an den `Trainer` übergeben, enthält dieses Feld auch die von der `compute_metrics()`-Funktion zurückgegebenen Metriken. + +Die Vorhersagen in `predictions` sind ein zweidimensionales Array mit der Form 408 x 2 (408 ist die Anzahl der Elemente unseres Datensatzes). Das sind die Logits für jedes Element des Datensatzes, das wir an `predict()` übergeben haben (siehe [vorheriges Kapitel](/course/chapter2) dass alle Transformer Modelle Logits zurückgeben). Um diese in Vorhersagen umzuwandeln, die wir mit den Labels vergleichen können, müssen wir den Index mit dem höchsten Wert auf der zweiten Achse nehmen: + +```py +import numpy as np + +preds = np.argmax(predictions.predictions, axis=-1) +``` + +Jetzt können wir diese Vorhersagen in `preds` mit den Labels vergleichen. Wir greifen auf die Metriken aus der 🤗 Bibliothek Datasets zurück, um unsere Funktion `compute_metric()` zu erstellen. Die mit dem MRPC-Datensatz verbundenen Metriken können genauso einfach geladen werden, wie wir den Datensatz geladen haben, diesmal mit der Funktion `load_metric()`. Das zurückgegebene Objekt verfügt über eine Berechnungsmethode, mit der wir die Metrik auswerten können: + +```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} +``` + +Die genauen Ergebnisse können variieren, da die zufällige Initialisierung des Modellkopfes den Optimierungsverlauf und damit die Metriken verändern kann. Hier hat das Modell eine Genauigkeit von 85,78 % über die Validierungsdaten und eine F1-Maß von 89,97 erreicht hat. Dies sind die beiden Kennzahlen, die zur Bewertung der Ergebnisse des MRPC-Datensatzes für den GLUE-Benchmark verwendet werden. In der Tabelle im [BERT-Paper] (https://arxiv.org/pdf/1810.04805.pdf) wird für das Basismodell ein F1-Maß von 88,9 angegeben. Das Paper hat das `uncased` Modell verwendet, während wir derzeit das `cased` Modell verwenden, was das bessere Ergebnis erklärt. + +Zusammenfassend ergibt das unsere Funktion `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) +``` + +Um diese Funktion in Aktion zu sehen, definieren wir einen neuen `Trainer` mit der Funktion "compute_metrics()", um am Ende jeder Epoche Metriken zu melden: + +```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, +) +``` + +Hier ein Hinweis, dass wir ein neues `TrainingArguments` errstellen, dessen `evaluation_strategy` auf `"epoch"` gesetzt ist, und ein neues Modell definieren - andernfalls würden wir nur das Training des momentanen Modells fortführen, das wir bereits trainiert haben. Um einen neuen Trainingslauf zu starten, führen wir folgendes aus: + +``` +trainer.train() +``` + +Nun werden am Ende jeder Epoche zusätzlich zu den Trainingsverlusten auch die Validierungsverluste und -metriken gemeldet. Auch hier kann die Genauigkeit/F1-Maß aufgrund der zufälligen Initialisierung des Modells zu unserem Beispiel variieren, aber sie sollte in etwa gleich sein. + +Der `Trainer` funktioniert sofort auf mehreren GPUs oder TPUs und bietet zahlreiche Optionen, wie z. B. Training mit gemischter Genauigkeit (verwende `fp16 = True` in deinen Trainingsargumenten). In Kapitel 10 gehen wir auf alle Funktionen ein, die die `Trainer`-Klasse bereitstellt. + +Damit ist die Einführung in das Fein-tunen mit der `Trainer` API abgeschlossen. Beispiele für die gängigsten CL-Aufgaben werden in Kapitel 7 gegeben, aber jetzt schauen wir uns erst einmal an, wie man das Gleiche in PyTorch bewerkstelligen kann. + + + +✏️ **Probier es aus!** Fein-tune ein Modell mit dem GLUE SST-2 Datensatz, indem du die Datenverarbeitung aus Abschnitt 2 verwendest. + + + diff --git a/chapters/de/chapter3/3_tf.mdx b/chapters/de/chapter3/3_tf.mdx new file mode 100644 index 000000000..dd1be7835 --- /dev/null +++ b/chapters/de/chapter3/3_tf.mdx @@ -0,0 +1,195 @@ + + +# Modell mit Keras fein-tunen + + + +Wenn du die Datenvorverarbeitung im letzten Abschnitt abgeschlossen hast, brauchst es nur noch wenige Schritte, um das Modell zu trainieren. Beachte jedoch, dass der Befehl `model.fit()` auf einer CPU sehr langsam läuft. Wenn du keinen GPU hast, kannst du auf [Google Colab] (https://colab.research.google.com/) kostenlos auf GPUs und TPUs zugreifen. + +Bei den folgenden Codebeispielen wird davon ausgegangen, dass du die Beispiele aus dem vorherigen Abschnitt bereits ausgeführt hast. Hier ist eine kurze Zusammenfassung, die aufzeigt was erwartet wird: + +```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, +) +``` + +### Training + +Tensorflow Modelle, die von 🤗 Transformers importiert werden, sind bereits Keras Modelle. Hier ist eine kurze Einführung in Keras. + + + +Sobald wir die Daten haben, braucht es nur noch sehr wenig Arbeit, um mit dem Training zu beginnen. + + + +Wie im [vorherigen Kapitel](/course/chapter2) verwenden wir die Klasse `TFAutoModelForSequenceClassification` mit zwei Labels: + +```py +from transformers import TFAutoModelForSequenceClassification + +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +Im Gegensatz zu [Kapitel 2](/course/chapter2) wird eine Warnung angezeigt, nachdem das Modell instanziiert wurde. Das liegt daran, dass BERT nicht auf die Klassifizierung von Satzpaaren vortrainiert wurde. Deshalb wurde der Kopf des vortrainierten Modells verworfen und stattdessen ein neuer Kopf eingefügt, der für die Klassifizierung von Sequenzen geeignet ist. Die Warnungen zeigen an, dass Teil der Gewichtung nicht verwendet wurden (die Gewichte für den verworfenen Kopf) und dass einige andere zufällig initialisiert wurden (die Gewichte für den neuen Kopf). Abschließend wirst du aufgefordert, das Modell zu trainieren, und genau das werden wir jetzt tun. + +Um das Modell mit unserem Datensatz fein-tunen zu können, müssen wir das Modell `kompilieren()` und unsere Daten an die `fit()`-Methode übergeben. Damit wird das Fein-tuning gestartet (dies sollte auf einer GPU ein paar Minuten dauern) und der Trainingsverlust sowie der Validierungsverlust am Ende jeder Epoche gemeldet. + + + +🤗 Transformer Modelle haben eine besondere Fähigkeit, die die meisten Keras Modelle nicht haben - sie können automatisch einen geeigneten Verlust verwenden, der intern berechnet wird. Dieser Verlust wird standardmäßig verwendet, wenn in `compile()` kein Verlustargument angegeben wird. Um den internen Verlust zu verwenden, musst du deine Labels als Teil des Input übergeben und nicht als separates Label, wie es normalerweise bei Keras-Modellen der Fall ist. Beispiele dafür gibt es in Teil 2 des Kurses, wobei die Definition der richtigen Verlustfunktion schwierig sein kann. Für die Klassifizierung von Sequenzen eignet sich jedoch eine der Standardverlustfunktionen von Keras, die wir hier verwenden werden. + + + +```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, +) +``` + + + +Hier gibt es einen sehr häufigen Stolperstein - du *kannst* Keras einfach den Namen des Verlusts als String übergeben, aber standardmäßig geht Keras davon aus, dass du bereits einen Softmax auf die Outputs angewendet hast. Viele Modelle geben jedoch die Werte direkt vor der Anwendung des Softmax als *Logits* aus. Hier ist es wichtig der Keras Verlustfunktion mitzuteilen, dass unser Modell genau diess tut, und das geht nur indem sie direkt aufgerufen wird, und nicht über den Namen mit einem String. + + + + +### Verbesserung der Trainingsperformance + + + +Wenn du den obigen Code ausprobierst, läuft er zwar, aber du wirst feststellen, dass der Verlust nur langsam oder sporadisch zurückgeht. Die Ursache hierfür ist die *Lernrate*. Wenn der Namen eines Optimierers als String an Keras übergeben wird, initialisiert Keras diesen Optimierer mit Standardwerten für alle Parameter, einschließlich der Lernrate. Aus langjähriger Erfahrung wissen wir, dass Transformer Modelle von einer wesentlich niedrigeren Lernrate profitieren als der Standardwert für Adam. Dieser Standardwert liegt bei 1e-3, auch geschrieben als 10 hoch -3 oder 0,001. Für Transformer ist 5e-5 (0,00005), was etwa zwanzigmal niedriger ist, ist ein viel besserer Ausgangspunkt. + +Zusätzlich zur Senkung der Lernrate haben wir noch einen zweiten Trick in petto: Wir können die Lernrate langsam im Laufe des Trainings verringern. In der Literatur wird dies manchmal als *Decay* oder *Annealing* der Lernrate bezeichnet. In Keras kannst das am besten mit dem *Lernraten-Scheduler* umgesetzt werden. Ein guter Scheduler ist `PolynomialDecay` - trotz des Namens lässt er die Lernrate in den Standardeinstellungen einfach linear vom Anfangswert bis zum Endwert abfallen. Dies ist genau was wir wollen. Um einen Scheduler richtig zu nutzen, müssen wir ihm allerdings sagen, wie lange das Training dauern soll. Das berechnen wir im Folgenden als `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) +``` + + + +Die 🤗 Transformer Bibliothek hat eine `create_optimizer()`-Funktion, die einen `AdamW`-Optimierer mit Lernratenabfall erzeugt. Das ist eine praktisches Tool, auf das wir in den nächsten Abschnitten des Kurses im Detail eingehen werden. + + + +Somit haben wir einen neuen Optimierer definiert und können ihn zum Training verwenden. Zuerst laden wir das Modell neu, um die Änderungen an der Gewichtung aus dem letzten Trainingslauf zurückzusetzen, und dann können wir es mit dem neuen Optimierer kompilieren: + +```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"]) +``` + +Jetzt starten wir einen erneuten Trainingslauf mit `fit`: + +```py +model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) +``` + + + +💡 Wenn du dein Modell während des Trainings automatisch in den Hub hochladen möchtest, kannst du in der Methode `model.fit()` einen `PushToHubCallback` mitgeben. Mehr darüber erfahren wir in [Kapitel 4](/course/chapter4/3) + + + +### Modell-Vorhersagen + + + + +Trainieren und zusehen, wie der Verlust sinkt, ist ja ganz nett, aber was ist, wenn wir tatsächlich die Ergebnisse des trainierten Modells erhalten wollen? Entweder um Metriken zu berechnen oder um das Modell in der Produktion einzusetzen. Dafür können wir einfach die Methode `predict()` verwenden. Sie liefert uns die *Logits* aus dem Ausgabekopf des Modells, und zwar eine pro Klasse. + +```py +preds = model.predict(tf_validation_dataset)["logits"] +``` + +Wir können diese Logits in die Klassenvorhersagen des Modells umwandeln, indem wir `argmax` verwenden, um den höchsten Logit zu finden, der der wahrscheinlichsten Klasse entspricht: + +```py +class_preds = np.argmax(preds, axis=1) +print(preds.shape, class_preds.shape) +``` + +```python out +(408, 2) (408,) +``` + +Nun können wir diese Vorhersagen in `preds` nutzen, um einige Metriken zu berechnen! Wir können die Metriken, die mit dem MRPC-Datensatz verbunden sind, genauso einfach laden, wie wir den Datensatz geladen haben, in diesem Fall mit der Funktion "load_metric()". Das zurückgegebene Objekt verfügt über eine Berechnungsmethode, mit der wir die Metrik berechnen können: + +```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} +``` +Die genauen Ergebnisse können variieren, da die zufällige Initialisierung des Modellkopfes die errechneten Metriken verändern kann. Das Modell erreicht über den Validierungsdaten eine Genauigkeit von 85,78 % und ein F1-Maß von 89,97. Dies sind die beiden Kennzahlen, die zur Bewertung der Ergebnisse des MRPC-Datensatzes für das GLUE-Benchmark verwendet werden. In der Tabelle im [BERT-Paper] (https://arxiv.org/pdf/1810.04805.pdf) wird für das Basismodell ein F1-Maß von 88,9 angegeben. Dort wurde das `uncased` Modell verwendet, während wir hier das `cased` Modell verwenden, was das bessere Ergebnis erklärt. + +Damit ist die Einführung in das Fein-tunen mit der Keras-API abgeschlossen. Beispiele für die gängigsten CL-Aufgaben findest du in Kapitel 7. + + + +✏️ **Probier es aus!** Fein-tune ein Modell mit dem GLUE SST-2 Datensatz, indem du die Datenverarbeitung aus Abschnitt 2 verwendest. + + diff --git a/chapters/de/chapter3/4.mdx b/chapters/de/chapter3/4.mdx new file mode 100644 index 000000000..1888bf2fa --- /dev/null +++ b/chapters/de/chapter3/4.mdx @@ -0,0 +1,358 @@ +# Komplettes Training + + + + + +In diesem Abschnitt befassen wir uns damit, wie wir die gleichen Ergebnisse wie im letzten Abschnitt erzielen können, ohne die Klasse `Trainer` zu verwenden. Auch hier gehen wir davon aus, dass du die Datenverarbeitung in Abschnitt 2 durchgeführt hast. Hier ist eine kurze Zusammenfassung mit allem, was du brauchst: + +```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) +``` + +### Vorbereitung für das Training + +Bevor wir unsere Trainingsschleife schreiben, müssen wir noch einige Objekte definieren. Zunächst müssen wir die Datalader definieren, mit denen wir über die Batches iterieren werden. Doch bevor wir diese Dataloader definieren können, müssen wir unsere `tokenized_datasets` nachbearbeiten, um einige Dinge zu erledigen, die der `Trainer` automatisch für uns erledigt hat. Konkret heißt das, dass wir: + +- Die Spalten entfernen, die Werte enthalten, die das Modell nicht erwartet (wie die Spalten `sentence1` und `sentence2`). +- Die Spalte `label` in `labels` umbenennen (weil das Modell erwartet, dass das Argument `labels` heißt). +- Das Format der Datensätze anpassen, so dass sie PyTorch-Tensoren statt Listen zurückgeben. + +Das `tokenized_datasets` hat eine Methode für jeden dieser Schritte: + +```py +tokenized_datasets = tokenized_datasets.remove_columns(["sentence1", "sentence2", "idx"]) +tokenized_datasets = tokenized_datasets.rename_column("label", "labels") +tokenized_datasets.set_format("torch") +tokenized_datasets["train"].column_names +``` + +Anschließend können wir überprüfen, ob der Output nur Spalten enthält, die unser Modell akzeptiert: + +```python +["attention_mask", "input_ids", "labels", "token_type_ids"] +``` + +Jetzt können wir ganz einfach unsere Dataloader definieren: + +```py +from torch.utils.data import DataLoader + +train_dataloader = DataLoader( + tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator +) +eval_dataloader = DataLoader( + tokenized_datasets["validation"], batch_size=8, collate_fn=data_collator +) +``` + +Um sicher zu gehen, überprüfen wir ein Batch auf Fehler in der Datenverarbeitung: + +```py +for batch in train_dataloader: + break +{k: v.shape for k, v in batch.items()} +``` + +```python out +{'attention_mask': torch.Size([8, 65]), + 'input_ids': torch.Size([8, 65]), + 'labels': torch.Size([8]), + 'token_type_ids': torch.Size([8, 65])} +``` + +Beachte, dass die Dimensionen der Tensoren wahrscheinlich etwas anders aussehen werden, da wir für den Trainingsdatenlader `shuffle=True` eingestellt haben und innerhalb des Batches auf die maximale Länge auffüllen. + +Da wir nun mit der Datenvorverarbeitung fertig sind (ein zufriedenstellendes aber schwer erreichbares Ziel für jeden ML-Experten), können wir uns nun dem Modell zuwenden. Wir instanziieren es genauso wie im vorherigen Abschnitt: + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +Als weitere Sicherheitsmaßnahme übergeben wir unseren Batch an das Modell, um sicherzustellen, dass beim Training alles glatt läuft: + +```py +outputs = model(**batch) +print(outputs.loss, outputs.logits.shape) +``` + +```python out +tensor(0.5441, grad_fn=) torch.Size([8, 2]) +``` + +Alle 🤗 Transformer Modelle geben den Verlust zurück, wenn `labels` angegeben werden, und wir erhalten zusätzlich die Logits (zwei für jede Eingabe in unserem Batch, also einen Tensor der Größe 8 x 2). + +Wir sind fast so weit, unsere Trainingsschleife zu schreiben! Es fehlen nur noch zwei Dinge: ein Optimierer und ein Scheduler für die Lernrate. Da wir versuchen, das zu wiederholen, was der `Trainer` automatisch gemacht hat, werden wir die gleichen Standardwerte verwenden. Der Optimierer, den der `Trainer` verwendet, heißt "AdamW" und ist größtenteils derselbe wie Adam, abgesehen von einer Abwandlung für die "Weight Decay Regularization" (siehe ["Decoupled Weight Decay Regularization"] (https://arxiv.org/abs/1711.05101) von Ilya Loshchilov und Frank Hutter): + +```py +from transformers import AdamW + +optimizer = AdamW(model.parameters(), lr=5e-5) +``` + +Der standardmäßig verwendete Scheduler für die Lernrate ist ein linearer Abstieg vom Maximalwert (5e-5) auf 0. Um ihn richtig zu definieren, müssen wir die Anzahl der Trainingsschritte kennen, d.h. die Anzahl der Epochen, die die Trainingsschleife durchlaufen soll, multipliziert mit der Anzahl der Trainingsbatches (der Länge unseres Trainingsdatenordners). Der `Trainer` verwendet standardmäßig drei Epochen, woran wir uns hier orientieren werden: + +```py +from transformers import get_scheduler + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) +print(num_training_steps) +``` + +```python out +1377 +``` + +### Die Trainingsschleife + +Ein letzter Hinweis: Wir wollen die GPU zum Training nutzen, wenn wir Zugang zu einer haben (auf einer CPU kann das Training mehrere Stunden statt ein paar Minuten dauern). Dazu definieren wir `device` als Gerät auf dem wir unser Modell und unsere Batches speichern: + +```py +import torch + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) +device +``` + +```python out +device(type='cuda') +``` + +Wir sind jetzt bereit für das Training! Um ein Gefühl dafür zu bekommen, wann das Training abgeschlossen sein wird, fügen wir mit der Bibliothek `tqdm` einen Fortschrittsbalken über die Anzahl der Trainingsschritte ein: + +```py +from tqdm.auto import tqdm + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Der Kern der Trainingsschleife sieht ähnlich aus wie in der Einleitung. Da wir keine Berichte angefordert haben, gibt die Trainingsschleife nichts über die Performance des Modells zurück. Dafür müssen wir eine Evaluationsschleife einfügen. + +### Die Evaluationsschleife + +Wie schon zuvor verwenden wir eine Metrik, die von der 🤗 Datasets-Bibliothek bereitgestellt wird. Wir haben bereits die Methode `metric.compute()` gesehen, aber Metriken können auch Batches für uns akkumulieren, wenn wir die Vorhersageschleife mit der Methode `add_batch()` durchlaufen. Sobald wir alle Batches gesammelt haben, können wir das Endergebnis mit der Methode `metric.compute()` ermitteln. So implementierst du all das in eine Evaluationsschleife: + +```py +from datasets import load_metric + +metric = load_metric("glue", "mrpc") +model.eval() +for batch in eval_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + with torch.no_grad(): + outputs = model(**batch) + + logits = outputs.logits + predictions = torch.argmax(logits, dim=-1) + metric.add_batch(predictions=predictions, references=batch["labels"]) + +metric.compute() +``` + +```python out +{'accuracy': 0.8431372549019608, 'f1': 0.8907849829351535} +``` + +Auch hier werden deine Ergebnisse wegen der Zufälligkeit bei der Initialisierung des Modellkopfes und der Datenverteilung etwas anders ausfallen, aber sie sollten in etwa gleich sein. + + + +✏️ **Probier es selbt!** Ändere die vorherige Trainingsschleife, um dein Modell auf dem SST-2-Datensatz fein zu tunen. + + + +### Verbessere deine Trainingsschleife mit 🤗 Accelerate + + + +Die Trainingsschleife, die wir zuvor definiert haben, funktioniert gut auf einer einzelnen CPU oder GPU. Aber mit der Bibliothek [🤗 Accelerate](https://github.com/huggingface/accelerate) können wir mit wenigen Anpassungen verteiltes Training auf mehreren GPUs oder TPUs implementieren. Beginnend mit der Erstellung der Trainings- und Validierungsdaten, sieht unsere manuelle Trainingsschleife nun folgendermaßen aus: + +```py +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Und hier sind die Änderungen: + +```diff ++ from accelerate import Accelerator + from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + ++ accelerator = Accelerator() + + model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) + optimizer = AdamW(model.parameters(), lr=3e-5) + +- device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +- model.to(device) + ++ train_dataloader, eval_dataloader, model, optimizer = accelerator.prepare( ++ train_dataloader, eval_dataloader, model, optimizer ++ ) + + num_epochs = 3 + num_training_steps = num_epochs * len(train_dataloader) + lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps + ) + + progress_bar = tqdm(range(num_training_steps)) + + model.train() + for epoch in range(num_epochs): + for batch in train_dataloader: +- batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss +- loss.backward() ++ accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Die erste Zeile, die hinzugefügt werden muss, ist die Import-Zeile. Die zweite Zeile instanziiert ein `Accelerator`-Objekt, das die Hardware analysiert und die richtige verteilte Umgebung initialisiert. Accelerate kümmert sich um die Anordnung der Geräte, du kannst also die Zeilen entfernen, die das Modell auf dem Gerät platzieren (oder, wenn du das möchtest, sie so ändern, dass sie `accelerator.device` anstelle von `device` verwenden). + +Der Hauptteil der Arbeit wird dann in der Zeile erledigt, die die Dataloader, das Modell und den Optimierer an `accelerator.prepare()` sendet. Dadurch werden diese Objekte in den richtigen Container verpackt, damit das verteilte Training wie vorgesehen funktioniert. Die verbleibenden Änderungen sind das Entfernen der Zeile, die das Batch auf dem Gerät mit `device` ablegt (wenn du das beibehalten willst, kannst du es einfach in `accelerator.device` ändern) und das Ersetzen von `loss.backward()` durch `accelerator.backward(loss)`. + + +⚠️ Um von dem Geschwindigkeitsvorteil der Cloud TPUs zu profitieren, empfehlen wir, deine Samples mit den Argumenten `padding="max_length"` und `max_length` des Tokenizers auf eine feste Länge aufzufüllen. + + +Wenn du damit experimentieren möchtest, siehst du hier, wie die komplette Trainingsschleife mit 🤗 Accelerate aussieht: + +```py +from accelerate import Accelerator +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +accelerator = Accelerator() + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +train_dl, eval_dl, model, optimizer = accelerator.prepare( + train_dataloader, eval_dataloader, model, optimizer +) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dl) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dl: + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Wenn dies in das Script `train.py` eingefügt wird, kann das Script auf jeder Art von verteilter Hardware ausgeführt werden. Um es auf deiner verteilten Hardware auszuprobieren, führe den folgenden Befehl aus: + +```bash +accelerate config +``` + +Du wirst dann aufgefordert werden, einige Fragen zu beantworten und die Antworten in eine Konfigurationsdatei zu schreiben, die von diesem Befehl verwendet wird: + +``` +accelerate launch train.py +``` + +Damit wird das verteilte Training gestartet. + +Wenn du das in einem Notebook ausprobieren möchtest (z. B. um es mit TPUs auf Colab zu testen), füge den Code einfach in eine `training_function()` ein und führe eine letzte Zelle mit aus: + +```python +from accelerate import notebook_launcher + +notebook_launcher(training_function) +``` + +Weitere Beispiele findest du in dem [🤗 Accelerate Repo](https://github.com/huggingface/accelerate/tree/main/examples). diff --git a/chapters/de/chapter3/5.mdx b/chapters/de/chapter3/5.mdx new file mode 100644 index 000000000..944442f6a --- /dev/null +++ b/chapters/de/chapter3/5.mdx @@ -0,0 +1,20 @@ + + +# Fein-tunen, Check! + +Das hat Spaß gemacht! In den ersten beiden Kapiteln hast du etwas über Modelle und Tokenizer gelernt, und jetzt weißt du, wie du sie auf deine eigenen Daten fein-tunen kannst. Rekapitulieren wir, was du in diesem Kapitel gelernt hast: + +{#if fw === 'pt'} +* Über Datensätze im [Hub](https://huggingface.co/datasets) +* Wie du Datensätze lädst und vorverarbeitest, einschließlich der Verwendung von dynamischem Padding und Collators +* Implementierung des eigenen Fein-tunings und der Evaluierung eines Modells +* Eine Trainingsschleife auf niedriger Ebene implementiert +* Mit 🤗 Accelerate die Trainingsschleife so anpassen, dass sie für mehrere GPUs oder TPUs funktioniert + +{:else} +* Über Datensätze im [Hub](https://huggingface.co/datasets) +* Wie man Datensätze lädt und vorverarbeitet +* Wie man ein Modell mit Keras fein-tunet und auswertet +* Eine eigene Metrik implementiert + +{/if} diff --git a/chapters/de/chapter3/6.mdx b/chapters/de/chapter3/6.mdx new file mode 100644 index 000000000..c031089f0 --- /dev/null +++ b/chapters/de/chapter3/6.mdx @@ -0,0 +1,296 @@ + + + + +# Quiz am Ende des Kapitels + +Teste, was du in diesem Kapitel gelernt hast! + +### 1. Der Datensatz `emotion` enthält Twitter-Nachrichten, die mit Emotionen gelabelt sind. Suche im [Hub](https://huggingface.co/datasets) nach dem Datensatz und lies die Datensatzkarte. Welche der folgenden Emotionen gehört nicht zu den grundlegenden Emotionen? + + + +### 2. Suche im [Hub](https://huggingface.co/datasets) nach dem Datensatz `ar_sarcasm`. Welche Aufgabe unterstützt er? + +Datensatzkarte an!" + }, + { + text: "Named entity recognition (Eigennamenerkennung )", + explain: "Das war's noch nicht - schau dir noch mal die Datensatzkarte an!" + }, + { + text: "Question answering (Fragenbeantwortung)", + explain: "Leider wurde diese Frage nicht richtig beantwortet. Versuche es noch einmal!" + } + ]} +/> + +### 3. Wie erwartet das BERT Modell, dass ein Satzpaar verarbeitet wird? + +[SEP] spezielles Token wird benötigt, um die beiden Sätze zu trennen, aber das ist nicht das Einzige!" + }, + { + text: "[CLS] Satztoken_1 Satztoken_2", + explain: "Ein [CLS] spezielles Token ist am Anfang erforderlich, aber das ist nicht das Einzige!" + }, + { + text: "[CLS] Satztoken_1 [SEP] Satztoken_2 [SEP]", + explain: "Das ist richtig!", + correct: true + }, + { + text: "[CLS] Satztoken_1 [SEP] Satztoken_2", + explain: "Ein [CLS] spezielles Token wird am Anfang benötigt, sowie ein [SEP] spezielles Token, um die beiden Sätze zu trennen, aber das ist noch nicht alles!" + } + ]} +/> + +{#if fw === 'pt'} +### 4. Was sind die Vorteile der Methode `Dataset.map()`? + + + +### 5. Was bedeutet dynamisches Padding? + + + +### 6. Welchen Zweck hat die Funktion collate? + +DataCollatorWithPadding im Speziellen." + }, + { + text: "Er stellt alle Proben in einem Batch zusammen.", + explain: "Richtig! Du kannst die collate Funktion als Argument eines DataLoaders übergeben. Wir haben die Funktion DataCollatorWithPadding verwendet, die alle Elemente in einem Batch auffüllt, damit sie die gleiche Länge haben.", + correct: true + }, + { + text: "Es wird der gesamte Datensatz vorverarbeitet.", + explain: "Das wäre eine Vorverarbeitungsfunktion, keine Zusammenfassungsfunktion wie collate." + }, + { + text: "Sie schneidet die Sequenzen im Datensatz ab.", + explain: "Eine collate Funktion behandelt einzelne Batches, nicht den gesamten Datensatz. Wenn du am Abschneiden interessiert bist, kannst du das truncate Argument von dem tokenizer verwenden." + } + ]} +/> + +### 7. Was passiert, wenn du eine der Klassen `AutoModelForXxx` mit einem vortrainierten Sprachmodell (z. B. `bert-base-uncased`) instanziierst, das einer anderen Aufgabe entspricht als der, für die es trainiert wurde? + +AutoModelForSequenceClassification mit bert-base-uncased verwendet haben, bekamen wir beim Instanziieren des Modells Warnungen. Der trainierte Kopf wird nicht für die Sequenzklassifizierung verwendet, also wird er verworfen und ein neuer Kopf mit zufälliger Gewichtung wird instanziiert.", + correct: true + }, + { + text: "Der Kopf des vorher trainierten Modells wird verworfen.", + explain: "Es muss etwas anderes passieren. Versuch es noch einmal!" + }, + { + text: "Nichts, da das Modell noch für die andere Aufgabe fein-tunen kann.", + explain: "Der Kopf des trainierten Modells wurde nicht für die Lösung dieser Aufgabe trainiert, also sollte der Kopf verworfen werden!" + } + ]} +/> + +### 8. Was ist der Zweck von `TrainingArguments`? + +Trainer verwendet werden.", + explain: "Richtig!", + correct: true + }, + { + text: "Hier wird die Größe des Modells angegeben.", + explain: "Die Größe des Modells wird durch die Konfiguration des Modells festgelegt, nicht durch die Klasse TrainingArguments." + }, + { + text: "Sie enthält nur die Hyperparameter, die für die Auswertung verwendet werden.", + explain: "In unserem Beispiel haben wir angegeben, wo das Modell und seine Kontrollpunkte gespeichert werden sollen. Versuche es noch einmal!" + }, + { + text: "Er enthält nur die Hyperparameter, die für das Training verwendet werden.", + explain: "In unserem Beispiel haben wir auch eine Auswertungsstrategie verwendet, was sich auf die Auswertung auswirkt. Versuche es noch einmal!" + } + ]} +/> + +### 9. Warum solltest du die 🤗 Accelerate Bibliothek benutzen? + +Trainer gemacht, nicht mit der 🤗 Accelerate Bibliothek. Versuch es noch einmal!" + }, + { + text: "Damit funktionieren unsere Trainingsschleifen bei verteilten Strategien.", + explain: "Richtig! Mit 🤗 Accelerate funktionieren deine Trainingsschleifen für mehrere verteilte GPUs und TPUs.", + correct: true + }, + { + Text: "Es bietet mehr Funktionen zur Optimierung.", + explain: "Nein, die 🤗 Accelerate Bibliothek stellt keine Optimierungsfunktionen zur Verfügung." + } + ]} +/> + +{:else} +### 4. Was passiert, wenn du eine der Klassen `TFAutoModelForXxx` mit einem vortrainierten Sprachmodell (z. B. `bert-base-uncased`) instanziierst, das einer anderen Aufgabe entspricht als der, für die es trainiert wurde? + +TFAutoModelForSequenceClassification mit bert-base-uncased verwendet haben, bekamen wir beim Instanziieren des Modells Warnungen. Der trainierte Kopf wird nicht für die Sequenzklassifizierung verwendet, also wird er verworfen und ein neuer Kopf mit zufälligen Gewichten instanziiert.", + correct: true + }, + { + text: "Der Kopf des vorher trainierten Modells wird verworfen.", + explain: "Es muss etwas anderes passieren. Versuch es noch einmal!" + }, + { + text: "Nichts, da das Modell noch für die andere Aufgabe fein-tunen kann.", + explain: "Der Kopf des trainierten Modells wurde nicht für die Lösung dieser Aufgabe trainiert, also sollten wir den Kopf verwerfen!" + } + ]} +/> + +### 5. Die Tensorflow Modelle von `transformers` sind bereits Keras Modelle. Welchen Vorteil bietet das? + +TPUStrategy-Bereich ausführen, einschließlich der Initialisierung des Modells." + }, + { + text: "Du kannst bestehende Methoden wie compile(), fit() und predict() nutzen.", + erklären: "Richtig! Wenn du erst einmal die Daten hast, erfordert das Training mit ihnen nur noch wenig Arbeit.", + correct: true + }, + { + text: "Du lernst sowohl Keras als auch Transformer kennen.", + explain: "Korrekt, aber wir suchen nach etwas anderem :)", + correct: true + }, + { + text: "Du kannst ganz einfach Metriken für den Datensatz berechnen.", + explain: "Keras hilft uns beim Training und der Auswertung des Modells, nicht bei der Berechnung von datensatzbezogenen Metriken." + } + ]} +/> + +### 6. Wie kannst du deine eigene benutzerdefinierte Metrik definieren? + +tf.keras.metrics.Metric erstellen.", + explain: "Großartig!", + correct: true + }, + { + text: "Durch Verwendung der funktionalen Keras-API.", + explain: "Versuch es noch einmal!" + }, + { + text: "Durch die Verwendung einer Callable mit der Signatur metric_fn(y_true, y_pred).", + explain: "Korrekt!", + correct: true + }, + { + text: "Indem du es googelst.", + explain: "Das ist nicht die Antwort, nach der wir suchen, aber es sollte dir helfen, sie zu finden.", + correct: true + } + ]} +/> + +{/if} \ No newline at end of file diff --git a/chapters/de/glossary/1.mdx b/chapters/de/glossary/1.mdx index 1debfc714..b861d7eb1 100644 --- a/chapters/de/glossary/1.mdx +++ b/chapters/de/glossary/1.mdx @@ -3,6 +3,7 @@ | Original | Übersetzung | |-----------------------------|---------------------------------| | Abstraction | Abstraktion | +| Accuracy | Genauigkeit | | Backward Pass | Rückwärtsalgorithmus berechnen | | Batch | Batch | | Chapter | Kapitel | @@ -15,9 +16,12 @@ | Dependency | Abhängigkeitsbeziehung | | Deployment | Deployment | | Development | Entwicklung | +| Dictionary | Dictionary | | Distribution | Verteilung | | Download | Download | +| F1 score | F1-Maß | | Feature | Feature | +| Fine-tuning | Fein-tunen | | Folder | Ordner | | Forward Pass | Vorwärtsalgorithmus berechnen | | Function | Funktion | @@ -28,6 +32,7 @@ | Library | Bibliothek | | Linux | Linux | | Load | laden | +| Loss function | Verlustfunktion | | macOS | macOS | | Model | Modell | | Model Hub | Model Hub | @@ -35,6 +40,7 @@ | Natural Language Processing | Computerlinguistik | | Package | Paket | | Package Manager | Paektverwaltung | +| Padding | das Padding / auffüllen | | Parameter | Parameter | | Python | Python | | Pytorch | Pytorch | @@ -48,10 +54,12 @@ | Train | Training | | Transformer | Transformer | | Virtual Environment | Virtuelle Umgebung | +| Weight | Gewicht | +| Weights | Gewichtung | | Windows | Windows | | Working Environment | Arbeitsumgebung | | Workload | Auslastung | -| Workspace | Workspace | +| Workspace | Workspace | ## Abkürzungen diff --git a/chapters/en/chapter3/3.mdx b/chapters/en/chapter3/3.mdx index 749cfd511..40cec5e78 100644 --- a/chapters/en/chapter3/3.mdx +++ b/chapters/en/chapter3/3.mdx @@ -162,7 +162,7 @@ This time, it will report the validation loss and metrics at the end of each epo The `Trainer` will work out of the box on multiple GPUs or TPUs and provides lots of options, like mixed-precision training (use `fp16 = True` in your training arguments). We will go over everything it supports in Chapter 10. -This concludes the introduction to fine-tuning using the `Trainer` API. An example of doing this for most common NLP tasks will be given in Chapter 7, but for now let's look at how to do the same thing in pure PyTorch. +This concludes the introduction to fine-tuning using the `Trainer` API. An example of doing this for most common NLP tasks will be given in [Chapter 7](course/chapter7), but for now let's look at how to do the same thing in pure PyTorch. diff --git a/chapters/en/chapter3/3_tf.mdx b/chapters/en/chapter3/3_tf.mdx index f2dd7d6cb..3c72b30fb 100644 --- a/chapters/en/chapter3/3_tf.mdx +++ b/chapters/en/chapter3/3_tf.mdx @@ -196,4 +196,4 @@ metric.compute(predictions=class_preds, references=raw_datasets["validation"]["l The exact results you get may vary, as the random initialization of the model head might change the metrics it achieved. Here, we can see our model has an accuracy of 85.78% on the validation set and an F1 score of 89.97. Those are the two metrics used to evaluate results on the MRPC dataset for the GLUE benchmark. The table in the [BERT paper](https://arxiv.org/pdf/1810.04805.pdf) reported an F1 score of 88.9 for the base model. That was the `uncased` model while we are currently using the `cased` model, which explains the better result. -This concludes the introduction to fine-tuning using the Keras API. An example of doing this for most common NLP tasks will be given in Chapter 7. If you would like to hone your skills on the Keras API, try to fine-tune a model on the GLUE SST-2 dataset, using the data processing you did in section 2. +This concludes the introduction to fine-tuning using the Keras API. An example of doing this for most common NLP tasks will be given in [Chapter 7](course/chapter7). If you would like to hone your skills on the Keras API, try to fine-tune a model on the GLUE SST-2 dataset, using the data processing you did in section 2. diff --git a/chapters/en/chapter3/6.mdx b/chapters/en/chapter3/6.mdx index 94d02da24..63e6c7052 100644 --- a/chapters/en/chapter3/6.mdx +++ b/chapters/en/chapter3/6.mdx @@ -251,7 +251,7 @@ Test what you learned in this chapter! explain: "Almost! There are some small additional changes required. For example, you need to run everything in a TPUStrategy scope, including the initialization of the model." }, { - text: "You can leverage existing methods such as compile(), fit(), and predict().", + text: "You can leverage existing methods such as compile(), fit(), and predict().", explain: "Correct! Once you have the data, training on it requires very little work.", correct: true }, @@ -293,4 +293,4 @@ Test what you learned in this chapter! ]} /> -{/if} \ No newline at end of file +{/if} diff --git a/chapters/en/chapter8/7.mdx b/chapters/en/chapter8/7.mdx index 0a5d524e1..a20df19d6 100644 --- a/chapters/en/chapter8/7.mdx +++ b/chapters/en/chapter8/7.mdx @@ -193,6 +193,7 @@ Which of the following might be a good choice for the title of a forum topic to { text: "It allows the maintainers to know whether you're running code on a GPU or CPU.", explain: "Correct! As we've seen in this chapter, errors on GPUs and CPUs can quite different in flavor, and knowing which hardware you're using can help focus the maintainers' attention. But this isn't the only benefit...", + correct: true } ]} -/> \ No newline at end of file +/> diff --git a/chapters/fa/_toctree.yml b/chapters/fa/_toctree.yml index 2e60c0d2a..887380b16 100644 --- a/chapters/fa/_toctree.yml +++ b/chapters/fa/_toctree.yml @@ -8,12 +8,17 @@ - local: chapter1/1 title: مقدمه -- title: ۲- بکارگیری ترنسفورمرهای هاگینگ‌فیس +- title: ۲- بکارگیری ترنسفورمرهای هاگینگ‌فِیس sections: - local: chapter2/1 title: مقدمه - local: chapter2/2 - title: پشت صحنه خط‌تولید + title: پشت صحنه خط تولید + +- title: ۴- به اشتراک‌گذاری مدل‌ها و توکِنایزرها + sections: + - local: chapter4/1 + title: هاب هاگینگ‌فِیس - title: واژه‌نامه sections: diff --git a/chapters/fa/chapter4/1.mdx b/chapters/fa/chapter4/1.mdx new file mode 100644 index 000000000..b071b63df --- /dev/null +++ b/chapters/fa/chapter4/1.mdx @@ -0,0 +1,19 @@ +
+# هاب هاگینگ‌فِیس + +[هاب هاگینگ‌فِیس](https://huggingface.co/) –- وب‌سایت اصلی ما –- پلتفرمی مرکزی است که به همه امکان مشارکت، کشف و استفاده از جدیدترین و بهترین مدل‌ها و دیتاسِت‌ها را می‌دهد. این هاب طیف گسترده‌ای از مدل‌ها را در خود جای داده، که بالغ بر ۱۰ هزار مورد از آن‌ها در دسترس عموم قرار دارد. در این فصل بر روی مدل‌ها متمرکز شده و در فصل ۵ نیز نگاهی به دیتاسِت‌ها خواهیم داشت. + +مدل‌های موجود در هاب، محدود به ترنسفورمرهای هاگینگ‌فِیس یا حتی NLP نیستند. از جمله آنها می‌توان مدل‌هایی از قبیل [Flair](https://github.com/flairNLP/flair) و [AllenNLP](https://github.com/allenai/allennlp) برای پردازش زبان طبیعی، [Asteroid](https://github.com/asteroid-team/asteroid) و [pyannote](https://github.com/pyannote/pyannote-audio) برای پردازش گفتار، و [timm](https://github.com/rwightman/pytorch-image-models) را برای پردازش تصویر برشمرد. + +هر یک از این مدل‌ها به عنوان مخزن گیت میزبانی می‌شود که این امر، امکان نسخه‌بندی و قابلیت تکرارپذیری را فراهم می‌نماید. به اشتراک گذاشتن مدل در هاب، به معنای باز کردن آن به روی جامعه کاربران و در دسترس قرار دادن آن برای افرادی است که می‌خواهند به راحتی از آن استفاده کنند؛ در نتیجه، ضرورت اینکه افراد مدل را خودشان به تنهایی تعلیم دهند، برطرف شده و همرسانی و استفاده از آن تسهیل می‌گردد. + + علاوه بر این، اشتراک‌گذاری مدل‌ روی هاب، به طور خودکار باعث استقرار API برای اجرای آن‌ مدل می‌شود. از این رو، هر کسی در جامعه کاربران خواهد توانست آزادانه آن را مستقیما در صفحه اختصاصی مدل، با ورودی‌های سفارشی خود و ویجت‌های مناسب آزمایش نماید. + +خبر خوش اینکه به اشتراک‌گذاری و استفاده از هر مدل با دسترسی عمومی در هاب، کاملا رایگان است! در صورتی هم که تمایل دارید مدل‌هایی را به صورت خصوصی به اشتراک بگذارید، [طرح‌های پرداختی](https://huggingface.co/pricing) موجود هستند. + +ویدئوی زیر نحوه گشت و گذار در هاب مدل‌ها را نمایش می‌دهد. + + + +داشتن حساب کاربری huggingface.co برای پیگیری این بخش ضروریست، زیرا در ادامه مخازن جدیدی را در هاب هاگینگ‌فِیس ایجاد نموده و مدیریت خواهیم کرد: [حساب کاربری خود را بسازید](https://huggingface.co/join). +
\ No newline at end of file diff --git a/chapters/fa/glossary/1.mdx b/chapters/fa/glossary/1.mdx index 9139a430b..b61fcf6de 100644 --- a/chapters/fa/glossary/1.mdx +++ b/chapters/fa/glossary/1.mdx @@ -3,36 +3,44 @@ | معادل در منبع | معادل در ترجمه | |-----------------------|------------------| -| Transformer | ترنسفورمر | -| Pytorch | پایتورچ | -| TensorFlow | تِنسورفِلو | +| Transformer | ترنسفورمر | +| PyTorch | پایتورچ | +| TensorFlow | تِنسورفِلو | +| Keras | کِراس | | Chapter | فصل | -| Section | بخش | +| Section | بخش | | Model | مدل | +| Dataset | دیتاسِت | | Parameter | پارامتر | | Train | تعلیم | -| Deploy | به‌کارگیری | -| 🤗 | هاگینگ‌فِیس | +| Deploy | مستقر کردن | +| Deployment | استقرار | +| 🤗 | هاگینگ‌فِیس | | Hugging Face | هاگینگ‌فِیس | +| Hugging Face Hub | هاب هاگینگ‌فِیس | | Load | بارگذاری | | Save | ذخیره‌سازی | +| Share | به اشتراک‌گذاری / همرسانی | | Library | کتابخانه | | Download | دانلود | -| Inference | اجرا؟* | -| Class | کلاس | +| Inference | اجرا؟* | +| Class | کلاس | | Module | ماژول | -| Abstraction | انتزاع انتزاعات | +| Abstraction | انتزاع انتزاعات | | Forward Pass | اجرای روی به جلو | | Tokenizer | توکِنایزر | -| Function | عملیات | +| Function | تابع | | Configuration | تنظیمات | | Batch | بتچ | -| Model Hub | Model Hub | -| Course | دوره‌ی آموزشی | +| Model Hub | هاب مدل‌ها | +| Platform | پلتفرم | +| Course | دوره آموزشی | +| Community | جامعه کاربران | +| Account | حساب کاربری | | Working Environment | محیط کار | -| Workspace | فضای کار | +| Workspace | فضای کار | | Setup | راه‌اندازی | -| Create | ایجاد یا ساختن | +| Create | ایجاد یا ساختن | | Code | کد | | Package | پکیج‌ | | Python | پایتون | @@ -42,23 +50,29 @@ | macOS | سیستم‌عامل مک | | Distribution | توزیع | | Linux | لینوکس | -| Workload | محاسبه، محاسبات | +| Workload | محاسبه، محاسبات | | Package Manager | پکیج‌منیجر | -| Command | دستور یا فرمان | -| Feature, as for an app | قابلیت‌ | -| Development | توسعه | +| Command | دستور یا فرمان | +| Feature, as for an app | قابلیت‌ | +| Development | توسعه | +| Versioning | نسخه‌بندی | +| Reproducibility | قابلیت تکرارپذیری | +| Git | گیت | +| GitHub | گیت‌هاب | +| Repository | مخزن | | Dependency | وابسته، وابستگی | +| Website | وب‌سایت | | Virtual Environment | محیط مجازی | | Terminal | ترمینال | | Incompatibility | ناسازگاری | -| Self-Contained | قائم به خود یا بدون وابستگی خارجی؟ | +| Self-Contained | خودکفا | | Script | اسکریپت‌ | | Folder | پوشه | -| Neural Network | شبکه‌ی عصبی | +| Neural Network | شبکه عصبی | | Text | نوشته | | Pipeline | خط تولید | | Word | کلمه | -| Subword | قطعات کوچکتر کلمه؟ | +| Subword | زیرکلمه | | Punctuation | علائم نگارشی | | Symbol | علامت‌‌، علامت‌ها | | Token | توکِن | @@ -82,35 +96,33 @@ | Key, as in Python dictionary | کلید | | Row | ردیف | | Integer | عدد صحیح | -| ID | شناسه | +| ID | شناسه | | Unique ID | شناسه منحصر به فرد | | Code Snippet | قطعه کد | +| Widget | ویجت | | Hidden State | وضعیت پنهان | | Feature, as in model | فیچر | -| High-Dimensional | چندین بعدی | +| High-Dimensional | بُعد بالا | +| Multi-Dimensional | چند بُعدی | | Vector, as in Python | وِکتور | | Sequence | رشته | -| Index, as in an array or list | شماره | +| Index, as in an array or list | اندیس | | Project, as in math | پروجکت؟ | | Embedding | embedding? | | Tokenized | توکِنیزه؟ قطعه قطعه شده؟ | -| Attention Mechanism | مکانیزم دقت؟ فرآیند دقت؟ فرآیند اتِنشِن | +| Attention Mechanism | مکانیزم توجه | | Classification | دسته‌بندی | | Attribute, as for a class in code | ویژگی؟ | | Label, as in classification | عنوان دسته | | Prediction, as in nn model | پیش‌بینی | | Logit, as in math and also in Pytorch | لوجیت؟ | | SoftMax | سافت‌مکس | -| Loss Function | کمممممممککککک!!!!! ‌ لاس‌فانکشن؟، تابع لاس؟ تابع یادگیری شبکه؟ | -| Activation Layer | لایه فعال‌سازی؟ لایه خروجی؟ لایه تبدیل لوجیت به مقدار آماری؟ کمککککککک | -| Cross Entropy | کِراس آنتروپی؟ | +| Loss Function | تابع هزینه | +| Activation Layer | لایه فعال‌سازی | +| Cross Entropy | آنتروپی متقابل | معادل‌هایی که استفاده نمی‌کنیم: -| معادل در منبع | معادل اشتباه در ترجمه | -|-----------------------|------------------| -| Application, as in an app and not as in apply | کاربرد | - معادل‌هایی که استفاده نمی‌کنیم: | معادل در منبع | معادل اشتباه در ترجمه | @@ -121,6 +133,10 @@ |-------------------------------| | ارائه | +| املای مورد استفاده کلمات فارسی | +|-------------------------------| +| ارائه | + کلمات مخفف: @@ -130,7 +146,7 @@ | API | API | | GPU | GPU | | TPU | TPU | -| ML | یادگیری ماشین | +| ML | یادگیری ماشین | املای مورد استفاده کلمات فارسی: diff --git a/chapters/fr/_toctree.yml b/chapters/fr/_toctree.yml index e4f4a187f..20f72bd85 100644 --- a/chapters/fr/_toctree.yml +++ b/chapters/fr/_toctree.yml @@ -2,21 +2,21 @@ sections: - local: chapter0/1 title: Introduction - -- title: 1. Les modèles Transformers + +- title: 1. Les transformers sections: - local: chapter1/1 title: Introduction - local: chapter1/2 title: Traitement du langage naturel - local: chapter1/3 - title: Que peut-on faire avec les modèles Transformers? + title: Que peut-on faire avec les transformers ? - local: chapter1/4 - title: Comment fonctionnent les modèles Transformers? + title: Comment fonctionnent les transformers ? - local: chapter1/5 - title: Les modèles d'encodeur + title: Les modèles encodeur - local: chapter1/6 - title: Les modèles de décodeur + title: Les modèles décodeur - local: chapter1/7 title: Les modèles de séquence-à-séquence - local: chapter1/8 @@ -24,7 +24,7 @@ - local: chapter1/9 title: Résumé - local: chapter1/10 - title: Questionnaire de fin de chapitre + title: Quiz de fin de chapitre quiz: 1 - title: 2. Utilisation de 🤗 Transformers @@ -47,6 +47,35 @@ title: Quiz de fin de chapitre quiz: 2 +- title: 3. Finetuner un modèle pré-entraîné + sections: + - local: chapter3/3 + title: Finetuner un modèle avec l'API Trainer API ou Keras + local_fw: { pt: chapter3/3, tf: chapter3/3_tf } + - local: chapter3/4 + title: Un entraînement complet + - local: chapter3/5 + title: Finetuning, vérifié ! + - local: chapter3/6 + title: Quiz de fin de chapitre + quiz: 3 + +- title: 4. Partager des modèles et des tokenizers + sections: + - local: chapter4/1 + title: Le Hub d'Hugging Face + - local: chapter4/2 + title: Utilisation de modèles pré-entraînés + - local: chapter4/3 + title: Partager des modèles pré-entraînés + - local: chapter4/4 + title: Créer une carte de modèle + - local: chapter4/5 + title: Partie 1 terminée ! + - local: chapter4/6 + title: Quiz de fin de chapitre + quiz: 4 + - title: 5. La bibliothèque 🤗 Datasets sections: - local: chapter5/1 @@ -65,4 +94,76 @@ title: 🤗 Datasets, vérifié ! - local: chapter5/8 title: Quiz de fin de chapitre - quiz: 5 \ No newline at end of file + quiz: 5 + +- title: 6. La bibliothèque 🤗 Tokenizer + sections: + - local: chapter6/1 + title: Introduction + - local: chapter6/2 + title: Entraîner un nouveau tokenizer à partir d'un ancien + - local: chapter6/3 + title: Les pouvoirs spéciaux des tokenizers rapides + - local: chapter6/3b + title: Les tokenizers rapides dans le pipeline de QA + - local: chapter6/4 + title: Normalisation et pré-tokénisation + - local: chapter6/5 + title: Le tokenizer Byte-Pair Encoding + - local: chapter6/6 + title: Le tokenizer WordPiece + - local: chapter6/7 + title: Le tokenizer Unigram + - local: chapter6/8 + title: Construction d'un tokenizer bloc par bloc + - local: chapter6/9 + title: 🤗 Tokenizers, vérifié ! + - local: chapter6/10 + title: Quiz de fin de chapitre + quiz: 6 + +- title: 7. Les principales tâches en NLP + sections: + - local: chapter7/1 + title: Introduction + - local: chapter7/2 + title: Classification de tokens + - local: chapter7/3 + title: Finetuner un modèle de langage masqué + - local: chapter7/4 + title: Traduction + - local: chapter7/5 + title: Résumé de textes + - local: chapter7/6 + title: Entraîner un modèle de langage causal à partir de zéro + - local: chapter7/7 + title: Réponse aux questions + - local: chapter7/8 + title: Maîtriser le NLP + - local: chapter7/9 + title: Quiz de fin de chapitre + quiz: 7 + +- title: 8. Comment demander de l'aide + sections: + - local: chapter8/1 + title: Introduction + - local: chapter8/2 + title: Que faire lorsque vous obtenez une erreur + - local: chapter8/3 + title: Demander de l'aide sur les forums + - local: chapter8/4 + title: Déboguer le pipeline d'entraînement + local_fw : { pt : chapter8/4, tf : chapter8/4_tf } + - local: chapter8/5 + title: Comment rédiger une bonne issue + - local: chapter8/6 + title: Partie 2 terminée ! + - local: chapter8/7 + title: Quiz de fin de chapitre + quiz: 8 + +- title: Evènements liés au cours d'Hugging Face + sections: + - local: event/1 + title: Événement de lancement pour le lancement de la partie 2 diff --git a/chapters/fr/chapter0/1.mdx b/chapters/fr/chapter0/1.mdx index 3dd008c1a..74901802a 100644 --- a/chapters/fr/chapter0/1.mdx +++ b/chapters/fr/chapter0/1.mdx @@ -10,7 +10,7 @@ Notez que nous ne couvrirons pas le système Windows. Si vous travaillez sous Wi La plupart du cours repose sur le fait que vous ayez un compte Hugging Face. Si vous n'en disposez pas d'un, nous vous recommandons d'en créer un dès maintenant : [créer un compte](https://huggingface.co/join). -## Utilisatoin d'un *notebook* Google Colab +## Utilisation d'un *notebook* Google Colab L'utilisation d'un *notebook* Google Colab est la configuration la plus simple possible. Démarrez un *notebook* dans votre navigateur et passez directement au codage ! diff --git a/chapters/fr/chapter2/2.mdx b/chapters/fr/chapter2/2.mdx index f53b1e891..73946e36a 100644 --- a/chapters/fr/chapter2/2.mdx +++ b/chapters/fr/chapter2/2.mdx @@ -53,7 +53,7 @@ la sortie : {'label': 'NEGATIVE', 'score': 0.9994558095932007}] ``` -Comme nous l'avons vu dans le [Chapitre 1](/course/fr/chapter1), ce pipeline regroupe trois étapes : le prétraitement, le passage des entrées dans le modèle et le post-traitement : +Comme nous l'avons vu dans le [Chapitre 1](/course/fr/chapter1), ce pipeline regroupe trois étapes : le prétraitement, le passage des entrées dans le modèle et le post-traitement.
The full NLP pipeline: tokenization of text, conversion to IDs, and inference through the Transformer model and the model head. @@ -149,7 +149,7 @@ La sortie elle-même est un dictionnaire contenant deux clés : `input_ids` et ` ## Passage au modèle {#if fw === 'pt'} -Nous pouvons télécharger notre modèle prétraîné de la même manière que nous l'avons fait avec notre *tokenizer*. 🤗 *Transformers* fournit une classe `AutoModel` qui possède également une méthode `from_pretrained()` : +Nous pouvons télécharger notre modèle pré-entraîné de la même manière que nous l'avons fait avec notre *tokenizer*. 🤗 *Transformers* fournit une classe `AutoModel` qui possède également une méthode `from_pretrained()` : ```python from transformers import AutoModel @@ -170,7 +170,7 @@ model = TFAutoModel.from_pretrained(checkpoint) Dans cet extrait de code, nous avons téléchargé le même *checkpoint* que nous avons utilisé dans notre pipeline auparavant (il devrait en fait avoir déjà été mis en cache) et instancié un modèle avec lui. -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*. +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. @@ -289,7 +289,7 @@ tensor([[-1.5607, 1.6123], ``` {/if} -Notre modèle a prédit `[-1.5607, 1.6123]` pour la première phrase et `[ 4.1692, -3.3464]` pour la seconde. Ce ne sont pas des probabilités mais des *logits*, les scores bruts, non normalisés, produits par la dernière couche du modèle. Pour être convertis en probabilités, ils doivent passer par une couche [SoftMax](https://fr.wikipedia.org/wiki/Fonction_softmax) (tous les modèles de la bibliotèque 🤗 Transformers sortent les logits car la fonction de perte de l'entraînement fusionne généralement la dernière fonction d'activation, comme la SoftMax, avec la fonction de perte réelle, comme l'entropie croisée) : +Notre modèle a prédit `[-1.5607, 1.6123]` pour la première phrase et `[ 4.1692, -3.3464]` pour la seconde. Ce ne sont pas des probabilités mais des *logits*, les scores bruts, non normalisés, produits par la dernière couche du modèle. Pour être convertis en probabilités, ils doivent passer par une couche [SoftMax](https://fr.wikipedia.org/wiki/Fonction_softmax) (tous les modèles de la bibliothèque 🤗 Transformers sortent les logits car la fonction de perte de l'entraînement fusionne généralement la dernière fonction d'activation, comme la SoftMax, avec la fonction de perte réelle, comme l'entropie croisée) : {#if fw === 'pt'} ```py @@ -340,5 +340,7 @@ Nous pouvons maintenant conclure que le modèle a prédit ce qui suit : Nous avons reproduit avec succès les trois étapes du pipeline : prétraitement avec les *tokenizers*, passage des entrées dans le modèle et post-traitement ! Prenons maintenant le temps de nous plonger plus profondément dans chacune de ces étapes. + ✏️ **Essayez !** Choisissez deux (ou plus) textes de votre choix (en anglais) et faites-les passer par le pipeline `sentiment-analysis`. Reproduisez ensuite vous-même les étapes vues ici et vérifiez que vous obtenez les mêmes résultats ! + diff --git a/chapters/fr/chapter2/4.mdx b/chapters/fr/chapter2/4.mdx index a8fb7e3e7..f407cfed1 100644 --- a/chapters/fr/chapter2/4.mdx +++ b/chapters/fr/chapter2/4.mdx @@ -64,7 +64,7 @@ Il existe également des variantes des *tokenizers* basés sur les mots qui ont Un identifiant est attribué à chaque mot, en commençant par 0 et en allant jusqu'à la taille du vocabulaire. Le modèle utilise ces identifiants pour identifier chaque mot. -Si nous voulons couvrir complètement une langue avec un *tokenizer* basé sur les mots, nous devons avoir un identifiant pour chaque mot de la langue que nous traitons, ce qui génére une énorme quantité de *tokens*. Par exemple, il y a plus de 500 000 mots dans la langue anglaise. Ainsi pour associer chaque mot à un identifiant, nous devrions garder la trace d'autant d'identifiants. De plus, des mots comme « chien » sont représentés différemment de mots comme « chiens ». Le modèle n'a initialement aucun moyen de savoir que « chien » et « chiens » sont similaires : il identifie les deux mots comme non apparentés. Il en va de même pour d'autres mots similaires, comme « maison » et « maisonnette » que le modèle ne considérera pas comme similaires au départ. +Si nous voulons couvrir complètement une langue avec un *tokenizer* basé sur les mots, nous devons avoir un identifiant pour chaque mot de la langue que nous traitons, ce qui génère une énorme quantité de *tokens*. Par exemple, il y a plus de 500 000 mots dans la langue anglaise. Ainsi pour associer chaque mot à un identifiant, nous devrions garder la trace d'autant d'identifiants. De plus, des mots comme « chien » sont représentés différemment de mots comme « chiens ». Le modèle n'a initialement aucun moyen de savoir que « chien » et « chiens » sont similaires : il identifie les deux mots comme non apparentés. Il en va de même pour d'autres mots similaires, comme « maison » et « maisonnette » que le modèle ne considérera pas comme similaires au départ. Enfin, nous avons besoin d'un *token* personnalisé pour représenter les mots qui ne font pas partie de notre vocabulaire. C'est ce qu'on appelle le *token* « inconnu » souvent représenté par « [UNK] » (de l’anglais « unknown ») ou « <unk> ; ». C'est généralement un mauvais signe si vous constatez que le *tokenizer* produit un nombre important de ce jeton spécial. Cela signifie qu’il n'a pas été en mesure de récupérer une représentation sensée d'un mot et que vous perdez des informations en cours de route. L'objectif de l'élaboration du vocabulaire est de faire en sorte que le *tokenizer* transforme le moins de mots possible en *token* inconnu. @@ -120,9 +120,9 @@ Cette approche est particulièrement utile dans les langues agglutinantes comme Il existe de nombreuses autres techniques. Pour n'en citer que quelques-unes : -- le Byte-level BPE utilisé par exemple dans le GPT-2 -- le WordPiece utilisé par exemple dans BERT -- SentencePiece ou Unigram, utilisés dans plusieurs modèles multilingues. +- le *Byte-level BPE* utilisé par exemple dans le GPT-2 +- le *WordPiece* utilisé par exemple dans BERT +- *SentencePiece* ou *Unigram*, utilisés dans plusieurs modèles multilingues. Vous devriez maintenant avoir une connaissance suffisante du fonctionnement des tokenizers pour commencer à utiliser l'API. diff --git a/chapters/fr/chapter2/8.mdx b/chapters/fr/chapter2/8.mdx index e935ec26e..b5ca1455a 100644 --- a/chapters/fr/chapter2/8.mdx +++ b/chapters/fr/chapter2/8.mdx @@ -13,7 +13,7 @@ text: " Tout d'abord, le modèle, qui traite le texte et renvoie des prédictions brutes. Puis le tokenizer donne un sens à ces prédictions et les reconvertit en texte si nécessaire.", explain: " Le modèle ne peut pas comprendre le texte ! Le tokenizer doit d'abord tokeniser le texte et le convertir en identifiants afin qu'il soit compréhensible par le modèle."}, { - text: " Tout d'abord, le tokenizer/i>, qui traite le texte et renvoie des identifiants. Puis le modèle traite ces identifiants et produit une prédiction, qui peut être du texte.", + text: " Tout d'abord, le tokenizer, qui traite le texte et renvoie des identifiants. Puis le modèle traite ces identifiants et produit une prédiction, qui peut être du texte.", explain: " La prédiction du modèle ne peut pas être du texte immédiatement. Le tokenizer doit être utilisé afin de reconvertir la prédiction en texte !"}, { text: " Le tokenizer traite le texte et renvoie des identifiants. Le modèle traite ces identifiants et produit une prédiction. Le tokenizer peut alors être utilisé à nouveau pour reconvertir ces prédictions en texte.", diff --git a/chapters/fr/chapter3/3.mdx b/chapters/fr/chapter3/3.mdx new file mode 100644 index 000000000..62a7962e9 --- /dev/null +++ b/chapters/fr/chapter3/3.mdx @@ -0,0 +1,171 @@ + + +# *Finetuner* un modèle avec l'API Trainer + + + + + +🤗 *Transformers* fournit une classe `Trainer` pour vous aider à *finetuner* n'importe lequel des modèles pré-entraînés qu'il fournit sur votre jeu de données. Une fois que vous avez fait tout le travail de prétraitement des données dans la dernière section, il ne vous reste que quelques étapes pour définir le `Trainer`. La partie la plus difficile sera probablement de préparer l'environnement pour exécuter `Trainer.train()`, car elle fonctionnera très lentement sur un CPU. Si vous n'avez pas de GPU, vous pouvez avoir accès à des GPUs ou TPUs gratuits sur [Google Colab](https://colab.research.google.com/). + +Les exemples de code ci-dessous supposent que vous avez déjà exécuté les exemples de la section précédente. Voici un bref résumé de ce dont vous avez besoin : + +```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) +``` + +### Entraînement + +La première étape avant de pouvoir définir notre `Trainer` est de définir une classe `TrainingArguments` qui contiendra tous les hyperparamètres que le `Trainer` utilisera pour l'entraînement et l'évaluation. Le seul argument que vous devez fournir est un répertoire où le modèle entraîné sera sauvegardé, ainsi que les *checkpoints*. Pour tout le reste, vous pouvez laisser les valeurs par défaut, qui devraient fonctionner assez bien pour un *finetuning* de base. + +```py +from transformers import TrainingArguments + +training_args = TrainingArguments("test-trainer") +``` + + + +💡 Si vous voulez télécharger automatiquement votre modèle sur le *Hub* pendant l'entraînement, passez `push_to_hub=True` dans le `TrainingArguments`. Nous en apprendrons plus à ce sujet au [Chapitre 4](/course/fr/chapter4/3). + + + +La deuxième étape consiste à définir notre modèle. Comme dans le [chapitre précédent](/course/fr/chapter2), nous utiliserons la classe `AutoModelForSequenceClassification`, avec deux labels : + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +Vous remarquerez que contrairement au [Chapitre 2](/course/fr/chapter2), vous obtenez un message d'avertissement après l'instanciation de ce modèle pré-entraîné. C'est parce que BERT n'a pas été pré-entraîné à la classification de paires de phrases, donc la tête du modèle pré-entraîné a été supprimée et une nouvelle tête adaptée à la classification de séquences a été ajoutée à la place. Les messages d'avertissement indiquent que certains poids n'ont pas été utilisés (ceux correspondant à la tête de pré-entraînement abandonnée) et que d'autres ont été initialisés de manière aléatoire (ceux pour la nouvelle tête). Il conclut en vous encourageant à entraîner le modèle, ce qui est exactement ce que nous allons faire maintenant. + +Une fois que nous avons notre modèle, nous pouvons définir un `Trainer` en lui passant tous les objets construits jusqu'à présent : le `model`, le `training_args`, les jeux de données d'entraînement et de validation, notre `data_collator`, et notre `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, +) +``` + +Notez que lorsque vous passez le `tokenizer` comme nous l'avons fait ici, le `data_collator` par défaut utilisé par le `Trainer` sera un `DataCollatorWithPadding` comme défini précédemment. Ainsi, vous pouvez sauter la ligne `data_collator=data_collator` dans cet appel. Il était quand même important de vous montrer cette partie du traitement dans la section 2 ! + +Pour *finetuner* le modèle sur notre jeu de données, il suffit d'appeler la méthode `train()` de notre `Trainer` : + +```py +trainer.train() +``` + +Cela lancera le *finetuning* (qui devrait prendre quelques minutes sur un GPU) et indiquera la perte d'entraînement tous les 500 pas. Cependant, elle ne vous dira pas si votre modèle fonctionne bien (ou mal). Ceci est dû au fait que : + +1. nous n'avons pas dit au `Trainer` d'évaluer pendant l'entraînement en réglant `evaluation_strategy` à soit `"steps"` (évaluer chaque `eval_steps`) ou `"epoch"` (évaluer à la fin de chaque epoch). +2. nous n'avons pas fourni au `Trainer` une fonction `compute_metrics()` pour calculer une métrique pendant ladite évaluation (sinon l'évaluation aurait juste affiché la perte, qui n'est pas un nombre très intuitif). + + +### Evaluation + +Voyons comment nous pouvons construire une fonction `compute_metrics()` utile et l'utiliser la prochaine fois que nous entraînons. La fonction doit prendre un objet `EvalPrediction` (qui est un *tuple* nommé avec un champ `predictions` et un champ `label_ids`) et retournera un dictionnaire de chaînes de caractères vers des flottants (les chaînes de caractères étant les noms des métriques retournées, et les flottants leurs valeurs). Pour obtenir des prédictions de notre modèle, nous pouvons utiliser la commande `Trainer.predict()` : + +```py +predictions = trainer.predict(tokenized_datasets["validation"]) +print(predictions.predictions.shape, predictions.label_ids.shape) +``` + +```python out +(408, 2) (408,) +``` + +La sortie de la méthode `predict()` est un autre *tuple* nommé avec trois champs : `predictions`, `label_ids`, et `metrics`. Le champ `metrics` contiendra juste la perte sur le jeu de données passé, ainsi que quelques mesures de temps (combien de temps il a fallu pour prédire, au total et en moyenne). Une fois que nous aurons complété notre fonction `compute_metrics()` et que nous l'aurons passé au `Trainer`, ce champ contiendra également les métriques retournées par `compute_metrics()`. + +Comme vous pouvez le voir, `predictions` est un tableau bidimensionnel de forme 408 x 2 (408 étant le nombre d'éléments dans le jeu de données que nous avons utilisé). Ce sont les logits pour chaque élément du jeu de données que nous avons passé à `predict()` (comme vous l'avez vu dans le [chapitre précédent](/course/fr/chapter2), tous les *transformers* retournent des logits). Pour les transformer en prédictions que nous pouvons comparer à nos étiquettes, nous devons prendre l'indice avec la valeur maximale sur le second axe : + +```py +import numpy as np + +preds = np.argmax(predictions.predictions, axis=-1) +``` + +Nous pouvons maintenant comparer ces `preds` aux étiquettes. Pour construire notre fonction `compute_metric()`, nous allons nous appuyer sur les métriques de la bibliothèque 🤗 *Datasets*. Nous pouvons charger les métriques associées au jeu de données MRPC aussi facilement que nous avons chargé le jeu de données, cette fois avec la fonction `load_metric()`. L'objet retourné possède une méthode `compute()` que nous pouvons utiliser pour effectuer le calcul de la métrique : + +```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} +``` + +Les résultats exacts que vous obtiendrez peuvent varier, car l'initialisation aléatoire de la tête du modèle peut modifier les métriques obtenues. Ici, nous pouvons voir que notre modèle a une précision de 85,78% sur l'ensemble de validation et un score F1 de 89,97. Ce sont les deux métriques utilisées pour évaluer les résultats sur le jeu de données MRPC pour le benchmark GLUE. Le tableau du papier de [BERT](https://arxiv.org/pdf/1810.04805.pdf) indique un score F1 de 88,9 pour le modèle de base. Il s'agissait du modèle `uncased` alors que nous utilisons actuellement le modèle `cased`, ce qui explique le meilleur résultat. + +En regroupant le tout, nous obtenons notre fonction `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) +``` + +Et pour le voir utilisé en action pour rapporter les métriques à la fin de chaque époque, voici comment nous définissons un nouveau `Trainer` avec cette fonction `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, +) +``` + +Notez que nous créons un nouveau `TrainingArguments` avec sa `evaluation_strategy` définie sur `"epoch"` et un nouveau modèle. Sinon, nous ne ferions que continuer l'entraînement du modèle que nous avons déjà entraîné. Pour lancer un nouveau cycle d'entraînement, nous exécutons : + +``` +trainer.train() +``` + +Cette fois, il indiquera la perte et les mesures de validation à la fin de chaque époque, en plus de la perte d'entraînement. Encore une fois, le score exact de précision/F1 que vous atteignez peut être un peu différent de ce que nous avons trouvé, en raison de l'initialisation aléatoire de la tête du modèle, mais il devrait être dans la même fourchette. + +Le `Trainer` fonctionnera sur plusieurs GPUs ou TPUs et fournit beaucoup d'options, comme l'entraînement en précision mixte (utilisez `fp16 = True` dans vos arguments d'entraînement). Nous passerons en revue tout ce qu'il supporte dans le chapitre 10. + +Ceci conclut l'introduction au *fine-tuning* en utilisant l'API `Trainer`. Un exemple d'utilisation pour les tâches de NLP les plus communes es donné dans le [Chapitre 7](/course/fr/chapter7), mais pour l'instant regardons comment faire la même chose en PyTorch pur. + + + +✏️ **Essayez** *Finetunez* un modèle sur le jeu de données GLUE SST-2, en utilisant le traitement des données que vous avez fait dans la section 2. + + \ No newline at end of file diff --git a/chapters/fr/chapter3/3_tf.mdx b/chapters/fr/chapter3/3_tf.mdx new file mode 100644 index 000000000..1fd969770 --- /dev/null +++ b/chapters/fr/chapter3/3_tf.mdx @@ -0,0 +1,190 @@ + + +# *Finetuner* un modèle avec Keras + + + +Une fois que vous avez fait tout le travail de prétraitement des données dans la dernière section, il ne vous reste que quelques étapes pour entraîner le modèle. Notez, cependant, que la commande `model.fit()` s'exécutera très lentement sur un CPU. Si vous n'avez pas de GPU, vous pouvez avoir accès à des GPUs ou TPUs gratuits sur [Google Colab] (https://colab.research.google.com/). + +Les exemples de code ci-dessous supposent que vous avez déjà exécuté les exemples de la section précédente. Voici un bref résumé de ce dont vous avez besoin : + +```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, +) +``` + +### Entraînement + +Les modèles TensorFlow importés depuis 🤗 *Transformers* sont déjà des modèles Keras. Voici une courte introduction à Keras. + + + +Cela signifie qu'une fois que nous disposons de nos données, très peu de travail est nécessaire pour commencer à entraîner sur celles-ci. + + + +Comme dans le [chapitre précédent] (/course/fr/chapter2), nous allons utiliser la classe `TFAutoModelForSequenceClassification`, avec deux étiquettes : + +```py +from transformers import TFAutoModelForSequenceClassification + +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +Vous remarquerez que, contrairement au [Chapitre 2](/course/fr/chapter2), vous obtenez un message d'avertissement après l'instanciation de ce modèle pré-entraîné. Ceci est dû au fait que BERT n'a pas été pré-entraîné à la classification de paires de phrases, donc la tête du modèle pré-entraîné a été supprimée et une nouvelle tête adaptée à la classification de séquences a été insérée à la place. Les messages d'avertissement indiquent que certains poids n'ont pas été utilisés (ceux correspondant à la tête de pré-entraînement abandonnée) et que d'autres ont été initialisés de manière aléatoire (ceux pour la nouvelle tête). Il conclut en vous encourageant à entraîner le modèle, ce qui est exactement ce que nous allons faire maintenant. + +Pour *finetuner* le modèle sur notre jeu de données, nous devons simplement `compiler()` notre modèle et ensuite passer nos données à la méthode `fit()`. Cela va démarrer le processus de *finetuning* (qui devrait prendre quelques minutes sur un GPU) et rapporter la perte d'entraînement au fur et à mesure, plus la perte de validation à la fin de chaque époque. + + + +Notez que les modèles 🤗 *Transformers* ont une capacité spéciale que la plupart des modèles Keras n'ont pas. Ils peuvent automatiquement utiliser une perte appropriée qu'ils calculent en interne. Ils utiliseront cette perte par défaut si vous ne définissez pas un argument de perte dans `compile()`. Notez que pour utiliser la perte interne, vous devrez passer vos labels comme faisant partie de l'entrée, et non pas comme un label séparé, ce qui est la façon normale d'utiliser les labels avec les modèles Keras. Vous verrez des exemples de cela dans la partie 2 du cours, où la définition de la fonction de perte correcte peut être délicate. Pour la classification des séquences, cependant, une fonction de perte standard de Keras fonctionne bien, et c'est donc ce que nous utiliserons ici. + + + +```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, +) +``` + + + +Notez un piège très commun ici. Vous *pouvez* simplement passer le nom de la perte comme une chaîne à Keras, mais par défaut Keras supposera que vous avez déjà appliqué une fonction softmax à vos sorties. Cependant, de nombreux modèles produisent les valeurs juste avant l'application de la softmax, que l'on appelle aussi les *logits*. Nous devons indiquer à la fonction de perte que c'est ce que fait notre modèle, et la seule façon de le faire est de l'appeler directement, plutôt que par son nom avec une chaîne. + + + + +### Améliorer les performances d'entraînement + + + +Si vous essayez le code ci-dessus, il fonctionne certainement, mais vous constaterez que la perte ne diminue que lentement ou sporadiquement. La cause principale est le *taux d'apprentissage*. Comme pour la perte, lorsque nous transmettons à Keras le nom d'un optimiseur sous forme de chaîne de caractères, Keras initialise cet optimiseur avec des valeurs par défaut pour tous les paramètres, y compris le taux d'apprentissage. Cependant, nous savons depuis longtemps que les *transformers* bénéficient d'un taux d'apprentissage beaucoup plus faible que celui par défaut d'Adam, qui est de 1e-3, également écrit comme 10 à la puissance -3, ou 0,001. 5e-5 (0,00005), qui est environ vingt fois inférieur, est un bien meilleur point de départ. + +En plus de réduire le taux d'apprentissage, nous avons une deuxième astuce dans notre manche : nous pouvons réduire lentement le taux d'apprentissage au cours de l'entraînement. Dans la littérature, on parle parfois de *décroissance* ou d' *annulation* du taux d'apprentissage.le taux d'apprentissage. Dans Keras, la meilleure façon de le faire est d'utiliser un *planificateur du taux d'apprentissage*. Un bon planificateur à utiliser est `PolynomialDecay`. Malgré son nom, avec les paramètres par défaut, il diminue simplement de façon linéaire le taux d'apprentissage de la valeur initiale à la valeur finale au cours de l'entraînement, ce qui est exactement ce que nous voulons. Afin d'utiliser correctement un planificateur, nous devons lui dire combien de temps l'entraînement va durer. Nous calculons cela comme `num_train_steps` ci-dessous. + +```py +from tensorflow.keras.optimizers.schedules import PolynomialDecay + +batch_size = 8 +num_epochs = 3 +# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans l'ensemble de données, divisé par la taille du batch puis multiplié +# par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un lot tf.data.Dataset +# et non le jeu de données original Hugging Face Dataset, donc son len() est déjà 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 bibliothèque 🤗 *Transformers* possède également une fonction `create_optimizer()` qui créera un optimiseur `AdamW` avec un taux d'apprentissage décroissant. Il s'agit d'un raccourci pratique que vous verrez en détail dans les prochaines sections du cours. + + + +Nous avons maintenant notre tout nouvel optimiseur et nous pouvons essayer de nous entraîner avec lui. Tout d'abord, rechargeons le modèle pour réinitialiser les modifications apportées aux poids lors de l'entraînement que nous venons d'effectuer, puis nous pouvons le compiler avec le nouvel optimiseur : + +```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"]) +``` + +Maintenant, on *fit* : + +```py +model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) +``` + + + +💡 Si vous voulez télécharger automatiquement votre modèle sur le *Hub* pendant l'entraînement, vous pouvez passer un `PushToHubCallback` dans la méthode `model.fit()`. Nous en apprendrons davantage à ce sujet au [Chapitre 4](/course/fr/chapter4/3). + + + +### Prédictions du modèle + + + + +Entraîner et regarder la perte diminuer, c'est très bien, mais que faire si l'on veut réellement obtenir des résultats du modèle entraîné, soit pour calculer des métriques, soit pour utiliser le modèle en production ? Pour ce faire, nous pouvons simplement utiliser la méthode `predict()`. Ceci retournera les *logits* de la tête de sortie du modèle, un par classe. + +```py +preds = model.predict(tf_validation_dataset)["logits"] +``` + +Nous pouvons convertir ces logits en prédictions de classe du modèle en utilisant `argmax` pour trouver le logit le plus élevé, qui correspond à la classe la plus probable : + +```py +class_preds = np.argmax(preds, axis=1) +print(preds.shape, class_preds.shape) +``` + +```python out +(408, 2) (408,) +``` + +Maintenant, utilisons ces `preds` pour calculer des métriques ! Nous pouvons charger les métriques associées au jeu de données MRPC aussi facilement que nous avons chargé le jeu de données, cette fois avec la fonction `load_metric()`. L'objet retourné a une méthode `compute()` que nous pouvons utiliser pour faire le calcul de la métrique : + +```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} +``` + +Les résultats exacts que vous obtiendrez peuvent varier, car l'initialisation aléatoire de la tête du modèle peut modifier les métriques obtenues. Ici, nous pouvons voir que notre modèle a une précision de 85,78% sur l'ensemble de validation et un score F1 de 89,97. Ce sont les deux métriques utilisées pour évaluer les résultats sur le jeu de données MRPC pour le benchmark GLUE. Le tableau du papier de [BERT](https://arxiv.org/pdf/1810.04805.pdf) indique un score F1 de 88,9 pour le modèle de base. Il s'agissait du modèle `uncased` alors que nous utilisons actuellement le modèle `cased`, ce qui explique le meilleur résultat. + +Ceci conclut l'introduction à le *finetuning* en utilisant l'API Keras. Un exemple d'application de cette méthode aux tâches les plus courantes du traitement automatique des langues sera présenté au [Chapitre 7](/course/fr/chapter7). Si vous souhaitez affiner vos connaissances de l'API Keras, essayez *finetuner* un modèle sur le jeu de données GLUE SST-2, en utilisant le traitement des données que vous avez effectué dans la section 2. \ No newline at end of file diff --git a/chapters/fr/chapter3/4.mdx b/chapters/fr/chapter3/4.mdx new file mode 100644 index 000000000..fe3dfcc94 --- /dev/null +++ b/chapters/fr/chapter3/4.mdx @@ -0,0 +1,359 @@ +# Un entraînement complet + + + + + +Maintenant nous allons voir comment obtenir les mêmes résultats que dans la dernière section sans utiliser la classe `Trainer`. Encore une fois, nous supposons que vous avez fait le traitement des données dans la section 2. Voici un court résumé couvrant tout ce dont vous aurez besoin : + +```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) +``` + +### Préparer l'entraînement + +Avant d'écrire réellement notre boucle d'entraînement, nous devons définir quelques objets. Les premiers sont les *dataloaders* que nous utiliserons pour itérer sur les batchs. Mais avant de pouvoir définir ces chargeurs de données, nous devons appliquer un peu de post-traitement à nos `tokenized_datasets`, pour prendre soin de certaines choses que le `Trainer` fait pour nous automatiquement. Spécifiquement, nous devons : + +- supprimer les colonnes correspondant aux valeurs que le modèle n'attend pas (comme les colonnes `sentence1` et `sentence2`), +- renommer la colonne `label` en `labels` (parce que le modèle s'attend à ce que l'argument soit nommé `labels`), +- définir le format des jeux de données pour qu'ils retournent des tenseurs PyTorch au lieu de listes. + +Notre `tokenized_datasets` a une méthode pour chacune de ces étapes : + +```py +tokenized_datasets = tokenized_datasets.remove_columns(["sentence1", "sentence2", "idx"]) +tokenized_datasets = tokenized_datasets.rename_column("label", "labels") +tokenized_datasets.set_format("torch") +tokenized_datasets["train"].column_names +``` + +Nous pouvons alors vérifier que le résultat ne comporte que des colonnes que notre modèle acceptera : + +```python +["attention_mask", "input_ids", "labels", "token_type_ids"] +``` + +Maintenant que cela est fait, nous pouvons facilement définir nos *dataloaders* : + +```py +from torch.utils.data import DataLoader + +train_dataloader = DataLoader( + tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator +) +eval_dataloader = DataLoader( + tokenized_datasets["validation"], batch_size=8, collate_fn=data_collator +) +``` + +Pour vérifier rapidement qu'il n'y a pas d'erreur dans le traitement des données, nous pouvons inspecter un batch comme celui-ci : + +```py +for batch in train_dataloader: + break +{k: v.shape for k, v in batch.items()} +``` + +```python out +{'attention_mask': torch.Size([8, 65]), + 'input_ids': torch.Size([8, 65]), + 'labels': torch.Size([8]), + 'token_type_ids': torch.Size([8, 65])} +``` + +Notez que les formes réelles seront probablement légèrement différentes pour vous puisque nous avons défini `shuffle=True` pour le chargeur de données d'entraînement et que nous *paddons* à la longueur maximale dans le lot. + +Maintenant que nous en avons terminé avec le prétraitement des données (un objectif satisfaisant mais difficile à atteindre pour tout praticien d'apprentissage automatique), passons au modèle. Nous l'instancions exactement comme nous l'avons fait dans la section précédente : + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +Pour s'assurer que tout se passera bien pendant l'entraînement, nous transmettons notre batch à ce modèle : + +```py +outputs = model(**batch) +print(outputs.loss, outputs.logits.shape) +``` + +```python out +tensor(0.5441, grad_fn=) torch.Size([8, 2]) +``` + +Tous les modèles 🤗 *Transformers* renvoient la perte lorsque les `labels` sont fournis. Nous obtenons également les logits (deux pour chaque entrée de notre batch, donc un tenseur de taille 8 x 2). + +Nous sommes presque prêts à écrire notre boucle d'entraînement ! Il nous manque juste deux choses : un optimiseur et un planificateur de taux d'apprentissage. Puisque nous essayons de reproduire à la main ce que fait la fonction `Trainer`, utilisons les mêmes paramètres par défaut. L'optimiseur utilisé par `Trainer` est `AdamW`, qui est le même qu'Adam, mais avec une torsion pour la régularisation par décroissance de poids (voir ["Decoupled Weight Decay Regularization"](https://arxiv.org/abs/1711.05101) par Ilya Loshchilov et Frank Hutter) : + +```py +from transformers import AdamW + +optimizer = AdamW(model.parameters(), lr=5e-5) +``` + +Enfin, le planificateur du taux d'apprentissage utilisé par défaut est juste une décroissance linéaire de la valeur maximale (5e-5) à 0. Pour le définir correctement, nous devons connaître le nombre d'étapes d'entraînement que nous prendrons, qui est le nombre d'époques que nous voulons exécuter multiplié par le nombre de lots d'entraînement (qui est la longueur de notre dataloader d'entraînement). Le `Trainer` utilise trois époques par défaut, nous allons donc suivre ça : + +```py +from transformers import get_scheduler + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) +print(num_training_steps) +``` + +```python out +1377 +``` + +### La boucle d'entraînement + +Une dernière chose : nous voulons utiliser le GPU si nous en avons un (sur un CPU, l'entraînement peut prendre plusieurs heures au lieu de quelques minutes). Pour ce faire, nous définissons un `device` sur lequel nous allons placer notre modèle et nos batchs : + +```py +import torch + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) +device +``` + +```python out +device(type='cuda') +``` + +Nous sommes maintenant prêts à entraîner ! Pour avoir une idée du moment où l'entraînement sera terminé, nous ajoutons une barre de progression sur le nombre d'étapes d'entraînement, en utilisant la bibliothèque `tqdm` : + +```py +from tqdm.auto import tqdm + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Vous pouvez voir que le cœur de la boucle d'entraînement ressemble beaucoup à celui de l'introduction. Nous n'avons pas demandé de rapport, donc cette boucle d'entraînement ne nous dira rien sur les résultats du modèle. Pour cela, nous devons ajouter une boucle d'évaluation. + + +### La boucle d'évaluation + +Comme nous l'avons fait précédemment, nous allons utiliser une métrique fournie par la bibliothèque 🤗 *Datasets*. Nous avons déjà vu la méthode `metric.compute()`, mais les métriques peuvent en fait accumuler des batchs pour nous au fur et à mesure que nous parcourons la boucle de prédiction avec la méthode `add_batch()`. Une fois que nous avons accumulé tous les batchs, nous pouvons obtenir le résultat final avec `metric.compute()`. Voici comment implémenter tout cela dans une boucle d'évaluation : + +```py +from datasets import load_metric + +metric = load_metric("glue", "mrpc") +model.eval() +for batch in eval_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + with torch.no_grad(): + outputs = model(**batch) + + logits = outputs.logits + predictions = torch.argmax(logits, dim=-1) + metric.add_batch(predictions=predictions, references=batch["labels"]) + +metric.compute() +``` + +```python out +{'accuracy': 0.8431372549019608, 'f1': 0.8907849829351535} +``` + +Une fois encore, vos résultats seront légèrement différents en raison du caractère aléatoire de l'initialisation de la tête du modèle et du mélange des données, mais ils devraient se situer dans la même fourchette. + + + +✏️ **Essayez** Modifiez la boucle d'entraînement précédente pour *finetuner* votre modèle sur le jeu de données SST-2. + + + +### Optimisez votre boucle d'entraînement avec 🤗 *Accelerate* + + + +La boucle d'entraînement que nous avons définie précédemment fonctionne bien sur un seul CPU ou GPU. Mais en utilisant la bibliothèque [🤗 *Accelerate*](https://github.com/huggingface/accelerate), il suffit de quelques ajustements pour permettre un entraînement distribué sur plusieurs GPUs ou TPUs. En partant de la création des *dataloaders* d'entraînement et de validation, voici à quoi ressemble notre boucle d'entraînement manuel : + +```py +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Et voici les changements : + +```diff ++ from accelerate import Accelerator + from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + ++ accelerator = Accelerator() + + model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) + optimizer = AdamW(model.parameters(), lr=3e-5) + +- device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +- model.to(device) + ++ train_dataloader, eval_dataloader, model, optimizer = accelerator.prepare( ++ train_dataloader, eval_dataloader, model, optimizer ++ ) + + num_epochs = 3 + num_training_steps = num_epochs * len(train_dataloader) + lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps + ) + + progress_bar = tqdm(range(num_training_steps)) + + model.train() + for epoch in range(num_epochs): + for batch in train_dataloader: +- batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss +- loss.backward() ++ accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +La première ligne à ajouter est la ligne d'importation. La deuxième ligne instancie un objet `Accelerator` qui va regarder l'environnement et initialiser la bonne configuration distribuée. 🤗 *Accelerate* gère le placement des périphériques pour vous, donc vous pouvez enlever les lignes qui placent le modèle sur le périphérique (ou, si vous préférez, les changer pour utiliser `accelerator.device` au lieu de `device`). + +Ensuite, le gros du travail est fait dans la ligne qui envoie les *dataloaders*, le modèle, et l'optimiseur à `accelerator.prepare()`. Cela va envelopper ces objets dans le conteneur approprié pour s'assurer que votre entraînement distribué fonctionne comme prévu. Les changements restants à faire sont la suppression de la ligne qui met le batch sur le `device` (encore une fois, si vous voulez le garder, vous pouvez juste le changer pour utiliser `accelerator.device`) et le remplacement de `loss.backward()` par `accelerator.backward(loss)`. + + +⚠️ Afin de bénéficier de la rapidité offerte par les TPUs du Cloud, nous vous recommandons de rembourrer vos échantillons à une longueur fixe avec les arguments `padding="max_length"` et `max_length` du *tokenizer*. + + +Si vous souhaitez faire un copier-coller pour jouer, voici à quoi ressemble la boucle d'entraînement complète avec 🤗 *Accelerate* : + +```py +from accelerate import Accelerator +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +accelerator = Accelerator() + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +train_dl, eval_dl, model, optimizer = accelerator.prepare( + train_dataloader, eval_dataloader, model, optimizer +) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dl) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dl: + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +En plaçant ceci dans un script `train.py`, cela sera exécutable sur n'importe quel type d'installation distribuée. Pour l'essayer dans votre installation distribuée, exécutez la commande : + +```bash +accelerate config +``` + +qui vous demandera de répondre à quelques questions et enregistrera vos réponses dans un fichier de configuration utilisé par cette commande : + +``` +accelerate launch train.py +``` + +qui lancera l'entraînement distribué. + +Si vous voulez essayer ceci dans un *notebook* (par exemple, pour le tester avec des TPUs sur Colab), collez simplement le code dans une `training_function()` et lancez une dernière cellule avec : + +```python +from accelerate import notebook_launcher + +notebook_launcher(training_function) +``` + +Vous trouverez d'autres exemples dans le dépôt d'[🤗 *Accelerate*](https://github.com/huggingface/accelerate/tree/main/examples). \ No newline at end of file diff --git a/chapters/fr/chapter3/5.mdx b/chapters/fr/chapter3/5.mdx new file mode 100644 index 000000000..62f49c39d --- /dev/null +++ b/chapters/fr/chapter3/5.mdx @@ -0,0 +1,20 @@ + + +# *Finetuning*, vérifié ! + +C'était amusant ! Dans les deux premiers chapitres, vous avez appris à connaître les modèles et les *tokenizers*, et vous savez maintenant comment les *finetuner* pour vos propres données. Pour récapituler, dans ce chapitre vous : + +{#if fw === 'pt'} +* avez appris à connaître les jeux de données dans le [Hub](https://huggingface.co/datasets), +* avez appris à charger et à prétraiter des jeux de données, notamment en utilisant le remplissage dynamique et les assembleurs, +* avez implémenté votre propre *finetuning* et évaluation d'un modèle, +* avez implémenté une boucle d'entraînement de niveau inférieur, +* avez utilisé 🤗 *Accelerate* pour adapter facilement votre boucle d'entraînement afin qu'elle fonctionne pour plusieurs GPUs ou TPUs. + +{:else} +* avez appris à connaître les jeux de données dans le [Hub](https://huggingface.co/datasets), +* avez appris comment charger et prétraiter les jeux de données, +* avez appris comment *finetuner* et évaluer un modèle avec Keras, +* avez implémenté une métrique personnalisée. + +{/if} \ No newline at end of file diff --git a/chapters/fr/chapter3/6.mdx b/chapters/fr/chapter3/6.mdx new file mode 100644 index 000000000..3654b7181 --- /dev/null +++ b/chapters/fr/chapter3/6.mdx @@ -0,0 +1,296 @@ + + + + +# Quiz de fin de chapitre + +Testez ce que vous avez appris dans ce chapitre ! + +### 1. Le jeu de données `emotion` contient des messages Twitter étiquetés avec des émotions. Cherchez-le dans le [*Hub*](https://huggingface.co/datasets), et lisez la carte du jeu de données. Laquelle de ces émotions n'est pas une de ses émotions de base ? + + + +### 2. Cherchez le jeu de données `ar_sarcasme` dans le [*Hub*](https://huggingface.co/datasets). Quelle tâche prend-il en charge ? + +carte du jeu de données !" + }, + { + text: "Reconnaissance des entités nommées", + explain: "Ce n'est pas ça. Jetez un autre coup d'œil à la carte du jeu de données !" + }, + { + text: "Réponse aux questions", + explain: "Hélas, cette question n'a pas reçu de réponse correcte. Essayez à nouveau !" + } + ]} +/> + +### 3. Comment le modèle BERT attend-il qu'une paire de phrases soit traitée ? + +[SEP] est nécessaire pour séparer les deux phrases, mais ce n'est pas tout !" + }, + { + text: "[CLS] Tokens_de_la_phrase_1 Tokens_de_la_phrase_2", + explain: "Un jeton spécial [CLS] est requis au début, mais ce n'est pas la seule chose !" + }, + { + text: "[CLS] Tokens_de_la_phrase_1 [SEP] Tokens_de_la_phrase_2 [SEP]", + explain: "C'est exact !", + correct: true + }, + { + text: "[CLS] Tokens_de_la_phrase_1 [SEP] Tokens_de_la_phrase_2", + explain: "Un jeton spécial [CLS] est nécessaire au début, ainsi qu'un jeton spécial [SEP] pour séparer les deux phrases, mais ce n'est pas tout !" + } + ]} +/> + +{#if fw === 'pt'} +### 4. Quels sont les avantages de la méthode `Dataset.map()` ? + + + +### 5. Que signifie le remplissage (*padding*) dynamique ? + +tokens que la précédente dans le jeu de données.", + explain: "C'est incorrect, et cela n'a pas de sens de regarder l'ordre dans le jeu de données puisque nous le mélangeons pendant l'entraînement." + }, + ]} +/> + +### 6. Quel est le but d'une fonction de collecte ? + +DataCollatorWithPadding." + }, + { + text: "Elle rassemble tous les échantillons dans un batch.", + explain: "Correct ! Vous pouvez passer la fonction de collecte comme argument d'une fonction DataLoader. Nous avons utilisé la fonction DataCollatorWithPadding qui remplit tous les éléments d'un batch pour qu'ils aient la même longueur.", + correct: true + }, + { + text: "Elle pré-traite tout le jeu de données.", + explain: "Ce serait une fonction de prétraitement, pas une fonction de collecte." + }, + { + text: "Elle tronque les séquences dans le jeu de données.", + explain: "Une fonction de collecte est impliquée dans le traitement des batchs individuels, et non de tout le jeu de données. Si vous êtes intéressé par la troncature, vous pouvez utiliser la fonction truncate en argument du tokenizer." + } + ]} +/> + +### 7. Que se passe-t-il lorsque vous instanciez une des classes `AutoModelForXxx` avec un modèle de langage pré-entraîné (tel que `bert-base-uncased`) qui correspond à une tâche différente de celle pour laquelle il a été entraîné ? + +AutoModelForSequenceClassification avec bert-base-uncased, nous avons eu des messages d'avertissement lors de l'instanciation du modèle. La tête pré-entraînée n'est pas utilisée pour la tâche de classification de séquences, elle est donc supprimée et une nouvelle tête est instanciée avec des poids aléatoires..", + correct: true + }, + { + text: "La tête du modèle pré-entraîné est supprimée.", + explain: "Quelque chose d'autre doit se produire. Essayez encore !" + }, + { + text: "Rien, puisque le modèle peut encore être finetuné pour les différentes tâches.", + explain: "La tête du modèle pré-entraîné n'a pas été entraînée à résoudre cette tâche, nous devons donc la supprimer !" + } + ]} +/> + +### 8. Quel est le but de `TrainingArguments` ? + +Trainer.", + explain: "Correct !", + correct: true + }, + { + text: "Préciser la taille du modèle.", + explain: "La taille du modèle est définie par la configuration du modèle, et non par la classe TrainingArguments." + }, + { + text: "Juste contenir les hyperparamètres utilisés pour l'évaluation..", + explain: "Dans l'exemple, nous avons spécifié où le modèle et ses checkpoints seront sauvegardés. Essayez à nouveau !" + }, + { + text: "Contenir seulement les hyperparamètres utilisés pour l'entraînement.", + explain: "Dans l'exemple, nous avons utilisé une evaluation_strategy également, ce qui a un impact sur l'évaluation. Essayez à nouveau !" + } + ]} +/> + +### 9. Pourquoi devriez-vous utiliser la librairie 🤗 *Accelerate* ? + +Trainer mais pas avec la librairie 🤗 *Accelerate*. Essayez à nouveau !" + }, + { + text: "Elle permet à nos boucles d'entraînement de fonctionner avec des stratégies distribuées.", + explain: "Correct ! Avec 🤗 *Accelerate*, vos boucles d'entraînement fonctionneront pour plusieurs GPUs et TPUs..", + correct: true + }, + { + text: "Elle offre davantage de fonctions d'optimisation.", + explain: "Non, la librairie 🤗 *Accelerate* ne fournit pas de fonctions d'optimisation." + } + ]} +/> + +{:else} +### 4. Que se passe-t-il lorsque vous instanciez une des classes `TFAutoModelForXxx` avec un modèle de langage pré-entraîné (tel que `bert-base-uncased`) qui correspond à une tâche différente de celle pour laquelle il a été entraîné ? + +TFAutoModelForSequenceClassification avec bert-base-uncased, nous avons eu des messages d'avertissement lors de l'instanciation du modèle. La tête pré-entraînée n'est pas utilisée pour la tâche de classification de séquences, elle est donc supprimée et une nouvelle tête est instanciée avec des poids aléatoires..", + correct: true + }, + { + text: "La tête du modèle pré-entraîné est supprimée.", + explain: "Quelque chose d'autre doit se produire. Essayez encore !" + }, + { + text: "Rien, puisque le modèle peut encore être finetuné pour les différentes tâches.", + explain: "La tête du modèle pré-entraîné n'a pas été entraînée à résoudre cette tâche, nous devons donc la supprimer !" + } + ]} +/> + +### 5. Les modèles TensorFlow de `transformers` sont déjà des modèles Keras. Quel avantage cela offre-t-il ? + +TPUStrategy, y compris l'initialisation du modèle." + }, + { + text: "Vous pouvez tirer parti des méthodes existantes telles que compile(), fit() et predict().", + explain: "Correct ! Une fois que vous disposez des données, l'entraînement sur celles-ci ne demande que très peu de travail.", + correct: true + }, + { + text: "Vous apprendrez à connaître Keras ainsi que transformers.", + explain: "Correct, mais nous cherchons quelque chose d'autre :)", + correct: true + }, + { + text: "Vous pouvez facilement calculer les métriques liées au jeu de données.", + explain: "Keras nous aide à entraîner et à évaluer le modèle, et non à calculer les paramètres liés aux jeux de données." + } + ]} +/> + +### 6. Comment pouvez-vous définir votre propre métrique personnalisée ? + +tf.keras.metrics.Metric.", + explain: "Excellent !", + correct: true + }, + { + text: "Utilisation de l'API fonctionnelle de Keras.", + explain: "Essayez à nouveau !" + }, + { + text: "En utilisant un callable avec la signature metric_fn(y_true, y_pred).", + explain: "Correct !", + correct: true + }, + { + text: "En le googlant.", + explain: "Ce n'est pas la réponse que nous cherchons, mais cela devrait vous aider à la trouver..", + correct: true + } + ]} +/> + +{/if} \ No newline at end of file diff --git a/chapters/fr/chapter4/1.mdx b/chapters/fr/chapter4/1.mdx new file mode 100644 index 000000000..8c98a16b5 --- /dev/null +++ b/chapters/fr/chapter4/1.mdx @@ -0,0 +1,17 @@ +# Le *Hub* d'Hugging Face + +Le [*Hub* d'Hugging Face](https://huggingface.co/), notre site internet principal, est une plateforme centrale qui permet à quiconque de découvrir, d'utiliser et de contribuer à de nouveaux modèles et jeux de données de pointe. Il héberge une grande variété de modèles, dont plus de 10 000 sont accessibles au public. Nous nous concentrerons sur les modèles dans ce chapitre, et nous examinerons les jeux de données au chapitre 5. + +Les modèles présents dans le *Hub* ne sont pas limités à 🤗 *Transformers* ou même au NLP. Il existe des modèles de [Flair](https://github.com/flairNLP/flair) et [AllenNLP](https://github.com/allenai/allennlp) pour le NLP, [Asteroid](https://github.com/asteroid-team/asteroid) et [pyannote](https://github.com/pyannote/pyannote-audio) pour l'audio, et [timm](https://github.com/rwightman/pytorch-image-models) pour la vision, pour n'en citer que quelques-uns. + +Chacun de ces modèles est hébergé sous forme de dépôt Git, ce qui permet le suivi des versions et la reproductibilité. Partager un modèle sur le *Hub*, c'est l'ouvrir à la communauté et le rendre accessible à tous ceux qui souhaitent l'utiliser facilement, ce qui leur évite d'avoir à entraîner eux-mêmes un modèle et simplifie le partage et l'utilisation. + +En outre, le partage d'un modèle sur le *Hub* déploie automatiquement une API d'inférence hébergée pour ce modèle. Toute personne de la communauté est libre de la tester directement sur la page du modèle, avec des entrées personnalisées et des *widgets* appropriés. + +La meilleure partie est que le partage ainsi que l'utilisation de n'importe quel modèle public sur le *Hub* sont totalement gratuits ! [Des plans payants](https://huggingface.co/pricing) existent également si vous souhaitez partager des modèles en privé. + +La vidéo ci-dessous montre comment naviguer sur le *Hub* : + + + +Avoir un compte huggingface.co est nécessaire pour suivre cette partie car nous allons créer et gérer des répertoires sur le *Hub* : [créer un compte](https://huggingface.co/join) \ No newline at end of file diff --git a/chapters/fr/chapter4/2.mdx b/chapters/fr/chapter4/2.mdx new file mode 100644 index 000000000..1d1ecb387 --- /dev/null +++ b/chapters/fr/chapter4/2.mdx @@ -0,0 +1,97 @@ + + +# Utilisation de modèles pré-entraînés + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Le *Hub* simplifie la sélection du modèle approprié, de sorte que son utilisation dans toute bibliothèque en aval peut se faire en quelques lignes de code. Voyons comment utiliser concrètement l'un de ces modèles et comment contribuer à la communauté. + +Supposons que nous recherchions un modèle basé sur le français, capable de remplir des masques. + +
+Selecting the Camembert model. +
+ +Nous choisissons le *checkpoint* `camembert-base` pour essayer. L'identifiant `camembert-base` est tout ce dont nous avons besoin pour commencer à utiliser le modèle ! Comme vous l'avez vu dans les chapitres précédents, nous pouvons l'instancier en utilisant la fonction `pipeline()` : + +```py +from transformers import pipeline + +camembert_fill_mask = pipeline("fill-mask", model="camembert-base") +results = camembert_fill_mask("Le camembert est :)") +``` + +```python out +[ + {'sequence': 'Le camembert est délicieux :)', 'score': 0.49091005325317383, 'token': 7200, 'token_str': 'délicieux'}, + {'sequence': 'Le camembert est excellent :)', 'score': 0.1055697426199913, 'token': 2183, 'token_str': 'excellent'}, + {'sequence': 'Le camembert est succulent :)', 'score': 0.03453313186764717, 'token': 26202, 'token_str': 'succulent'}, + {'sequence': 'Le camembert est meilleur :)', 'score': 0.0330314114689827, 'token': 528, 'token_str': 'meilleur'}, + {'sequence': 'Le camembert est parfait :)', 'score': 0.03007650189101696, 'token': 1654, 'token_str': 'parfait'} +] +``` + +Comme vous pouvez le constater, le chargement d'un modèle dans un pipeline est extrêmement simple. La seule chose à laquelle vous devez faire attention est que le *checkpoint* choisi soit adapté à la tâche pour laquelle il va être utilisé. Par exemple, ici nous chargeons le *checkpoint* `camembert-base` dans le pipeline `fill-mask`, ce qui est tout à fait correct. Mais si nous chargeions ce *checkpoint* dans le pipeline `text-classification`, les résultats n'auraient aucun sens car la tête de `camembert-base` n'est pas adaptée à cette tâche ! Nous recommandons d'utiliser le sélecteur de tâche dans l'interface du *Hub* afin de sélectionner les *checkpoints* appropriés : + +
+The task selector on the web interface. +
+ +Vous pouvez également instancier le *checkpoint* en utilisant directement l'architecture du modèle : + +{#if fw === 'pt'} +```py +from transformers import CamembertTokenizer, CamembertForMaskedLM + +tokenizer = CamembertTokenizer.from_pretrained("camembert-base") +model = CamembertForMaskedLM.from_pretrained("camembert-base") +``` + +Cependant, nous recommandons d'utiliser les classes [`Auto*`](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes) à la place, car elles sont par conception indépendantes de l'architecture. Alors que l'exemple de code précédent limite les utilisateurs aux *checkpoints* chargeables dans l'architecture CamemBERT, l'utilisation des classes `Auto*` facilite le changement de *checkpoint* : + +```py +from transformers import AutoTokenizer, AutoModelForMaskedLM + +tokenizer = AutoTokenizer.from_pretrained("camembert-base") +model = AutoModelForMaskedLM.from_pretrained("camembert-base") +``` +{:else} +```py +from transformers import CamembertTokenizer, TFCamembertForMaskedLM + +tokenizer = CamembertTokenizer.from_pretrained("camembert-base") +model = TFCamembertForMaskedLM.from_pretrained("camembert-base") +``` + +Cependant, nous recommandons d'utiliser les classes [`TFAuto*`](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes) à la place, car elles sont par conception indépendantes de l'architecture. Alors que l'exemple de code précédent limite les utilisateurs aux *checkpoints* chargeables dans l'architecture CamemBERT, l'utilisation des classes `TFAuto*` facilite le changement de *checkpoint* : + + +```py +from transformers import AutoTokenizer, TFAutoModelForMaskedLM + +tokenizer = AutoTokenizer.from_pretrained("camembert-base") +model = TFAutoModelForMaskedLM.from_pretrained("camembert-base") +``` +{/if} + + +Lorsque vous utilisez un modèle pré-entraîné, assurez-vous de vérifier comment il a été entraîné, sur quels jeux de données, ses limites et ses biais. Toutes ces informations doivent être indiquées dans sa carte. + diff --git a/chapters/fr/chapter4/3.mdx b/chapters/fr/chapter4/3.mdx new file mode 100644 index 000000000..95b5d420b --- /dev/null +++ b/chapters/fr/chapter4/3.mdx @@ -0,0 +1,638 @@ + + +# Partage de modèles pré-entraînés + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Dans les étapes ci-dessous, nous allons examiner les moyens les plus simples de partager des modèles pré-entraînés sur le 🤗 *Hub*. Il existe des outils et des services disponibles qui permettent de simplifier le partage et la mise à jour des modèles directement sur le *Hub*, que nous allons explorer ci-dessous. + + + +Nous encourageons tous les utilisateurs qui entraînent des modèles à contribuer en les partageant avec la communauté. Le partage des modèles, même s'ils ont été entraînés sur des jeux de données très spécifiques, aidera les autres, en leur faisant gagner du temps, des ressources de calcul et en leur donnant accès à des artefacts entraînés utiles. À votre tour, vous pourrez bénéficier du travail effectué par les autres ! + +Il y a trois façons de créer de nouveaux dépôts de modèles : + +- en utilisant l'API `push_to_hub`, +- en utilisant la bibliothèque Python `huggingface_hub`, +- en utilisant l'interface web. + +Une fois que vous avez créé un dépôt, vous pouvez y charger des fichiers via git et git-lfs. Nous allons vous guider dans la création de dépôts de modèles et le téléchargement de fichiers dans les sections suivantes. + + +## Utilisation de l'API `push_to_hub` + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +La façon la plus simple de télécharger des fichiers vers le *Hub* est d'utiliser l'API `push_to_hub`. + +Avant d'aller plus loin, vous devrez générer un jeton d'authentification afin que l'API `huggingface_hub` sache qui vous êtes et à quels espaces de noms vous avez accès en écriture. Assurez-vous que vous êtes dans un environnement où vous avez installé `transformers` (voir la [Configuration](/course/fr/chapter0)). Si vous êtes dans un *notebook*, vous pouvez utiliser la fonction suivante pour vous connecter : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Dans un terminal, vous pouvez exécuter : + +```bash +huggingface-cli login +``` + +Dans les deux cas, vous serez invité à saisir votre nom d'utilisateur et votre mot de passe, qui sont les mêmes que ceux que vous utilisez pour vous connecter au *Hub*. Si vous n'avez pas encore de profil pour le Hub, vous devez en créer un [ici](https://huggingface.co/join). + +Super ! Votre jeton d'authentification est maintenant stocké dans votre dossier de cache. Créons quelques référentiels ! + +{#if fw === 'pt'} + +Si vous avez joué avec l'API `Trainer` pour entraîner un modèle, le moyen le plus simple de le télécharger sur le *Hub* est de définir `push_to_hub=True` lorsque vous définissez vos `TrainingArguments` : + +```py +from transformers import TrainingArguments + +training_args = TrainingArguments( + "bert-finetuned-mrpc", save_strategy="epoch", push_to_hub=True +) +``` + +Lorsque vous appelez `trainer.train()`, le `Trainer` téléchargera alors votre modèle vers le *Hub* à chaque fois qu'il sera sauvegardé (ici à chaque époque) dans un dépôt dans votre espace personnel. Ce dépôt sera nommé comme le répertoire de sortie que vous avez choisi (ici `bert-finetuned-mrpc`) mais vous pouvez choisir un nom différent avec `hub_model_id = "a_different_name"`. + +Pour télécharger votre modèle vers une organisation dont vous êtes membre, passez-le simplement avec `hub_model_id = "my_organization/my_repo_name"`. + +Une fois que votre entraînement est terminé, vous devriez faire un dernier `trainer.push_to_hub()` pour télécharger la dernière version de votre modèle. Cela générera également une carte pour le modèle avec toutes les métadonnées pertinentes, rapportant les hyperparamètres utilisés et les résultats d'évaluation ! Voici un exemple du contenu que vous pourriez trouver dans une telle carte de modèle : +
+ An example of an auto-generated model card. +
+ +{:else} + +Si vous utilisez Keras pour entraîner votre modèle, le moyen le plus simple de le télécharger sur le *Hub* est de passer un `PushToHubCallback` lorsque vous appelez `model.fit()` : + +```py +from transformers import PushToHubCallback + +callback = PushToHubCallback( + "bert-finetuned-mrpc", save_strategy="epoch", tokenizer=tokenizer +) +``` + +Ensuite, vous devez ajouter `callbacks=[callback]` dans votre appel à `model.fit()`. La *callback* téléchargera alors votre modèle vers le *Hub* à chaque fois qu'il sera sauvegardé (ici à chaque époque) dans un référentiel dans votre espace de noms. Ce dépôt sera nommé comme le répertoire de sortie que vous avez choisi (ici `bert-finetuned-mrpc`) mais vous pouvez choisir un nom différent avec `hub_model_id = "a_different_name"`. + +Pour télécharger votre modèle dans une organisation dont vous êtes membre, passez-le simplement avec `hub_model_id = "my_organization/my_repo_name"`. + +{/if} + +A un niveau inférieur, l'accès au *Hub* peut être fait directement sur les modèles, les *tokenizers* et les objets de configuration via leur méthode `push_to_hub()`. Cette méthode s'occupe à la fois de la création du dépôt et de l'envoi les fichiers du modèle et du *tokenizer* directement dans le dépôt. Aucune manipulation manuelle n'est nécessaire, contrairement à l'API que nous verrons plus loin. + +Pour avoir une idée de son fonctionnement, commençons par initialiser un modèle et un *tokenizer* : + +{#if fw === 'pt'} +```py +from transformers import AutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = AutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` +{:else} +```py +from transformers import TFAutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = TFAutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` +{/if} + +Vous êtes libre de faire ce que vous voulez avec ces objets : ajouter des *tokens* au *tokenizer*, entraîner le modèle, le *finetuner*. Une fois que vous êtes satisfait du modèle, des poids et du *tokenizer* obtenus, vous pouvez utiliser la méthode `push_to_hub()` directement disponible sur l'objet `model` : + +```py +model.push_to_hub("dummy-model") +``` + +Cela va créer le nouveau dépôt `dummy-model` dans votre profil et le remplir avec les fichiers du modèle. +Faites la même chose avec le *tokenizer*, de sorte que tous les fichiers sont maintenant disponibles dans ce dépôt : + +```py +tokenizer.push_to_hub("dummy-model") +``` + +Si vous appartenez à une organisation, il suffit de spécifier l'argument `organization` pour télécharger dans l'espace de cette organisation : + +```py +tokenizer.push_to_hub("dummy-model", organization="huggingface") +``` + +Si vous souhaitez utiliser un jeton Hugging Face spécifique, vous pouvez également le spécifier à la méthode `push_to_hub()` : + +```py +tokenizer.push_to_hub("dummy-model", organization="huggingface", use_auth_token="") +``` + +Maintenant, dirigez-vous sur *Hub* pour trouver votre modèle nouvellement téléchargé : *https://huggingface.co/user-or-organization/dummy-model*. + +Cliquez sur l'onglet « Fichiers et versions » et vous devriez voir les fichiers visibles dans la capture d'écran suivante : + +{#if fw === 'pt'} +
+Dummy model containing both the tokenizer and model files. +
+{:else} +
+Dummy model containing both the tokenizer and model files. +
+{/if} + + + +✏️ **Essayez** Prenez le modèle et le *tokenizer* associés au *checkpoint* `bert-base-cased` et téléchargez-les vers un dépôt dans votre espace en utilisant la méthode `push_to_hub()`. Vérifiez que le dépôt apparaît correctement sur votre page avant de le supprimer. + + + +Comme vous l'avez vu, la méthode `push_to_hub()` accepte plusieurs arguments, ce qui permet de télécharger vers un dépôt ou un espace d'organisation spécifique, ou d'utiliser un jeton d'API différent. Nous vous recommandons de jeter un coup d'œil à la spécification de la méthode disponible directement dans la documentation de [🤗 *Transformers*](https://huggingface.co/transformers/model_sharing.html) pour avoir une idée de ce qui est possible. + +La méthode `push_to_hub()` est soutenue par le paquet Python [`huggingface_hub`](https://github.com/huggingface/huggingface_hub), qui offre une API directe au *Hub*. C'est intégré à 🤗 *Transformers* et à plusieurs autres bibliothèques d'apprentissage automatique, comme [`allenlp`](https://github.com/allenai/allennlp). Bien que nous nous concentrions sur l'intégration via 🤗 *Transformers* dans ce chapitre, son intégration dans votre propre code ou bibliothèque est simple. + +Passez à la dernière section pour voir comment télécharger des fichiers dans votre dépôt nouvellement créé ! + +## Utilisation de la bibliothèque Python `huggingface_hub` + +La bibliothèque Python `huggingface_hub` est un *package* qui offre un ensemble d'outils pour les hubs des modèles et des jeux de données. Elle fournit des méthodes et des classes simples pour des tâches courantes telles qu'obtenir et gérer des informations à propos des dépôts sur le *Hub*. Elle fournit des APIs simples qui fonctionnent au-dessus de git pour gérer le contenu de ces dépôts et pour intégrer le *Hub* dans vos projets et bibliothèques. + +De la même manière que pour l'utilisation de l'API `push_to_hub`, vous devrez avoir votre jeton d'API enregistré dans votre cache. Pour ce faire, vous devrez utiliser la commande `login` de la CLI, comme mentionné dans la section précédente (encore une fois, assurez-vous de faire précéder ces commandes du caractère `!` si vous les exécutez dans Google Colab) : + +```bash +huggingface-cli login +``` + +Le *package* `huggingface_hub` offre plusieurs méthodes et classes qui sont utiles pour notre objectif. Tout d'abord, il y a quelques méthodes pour gérer la création, la suppression des dépôts, et autres : + +```python no-format +from huggingface_hub import ( + # User management + login, + logout, + whoami, + + # Repository creation and management + create_repo, + delete_repo, + update_repo_visibility, + + # And some methods to retrieve/change information about the content + list_models, + list_datasets, + list_metrics, + list_repo_files, + upload_file, + delete_file, +) +``` + + +De plus, elle offre la très puissante classe `Repository` pour gérer un dépôt local. Nous allons explorer ces méthodes et cette classe dans les prochaines sections pour comprendre comment les exploiter. + +La méthode `create_repo` peut être utilisée pour créer un nouveau dépôt sur le *Hub* : + +```py +from huggingface_hub import create_repo + +create_repo("dummy-model") +``` + +Ceci créera le dépôt `dummy-model` dans votre espace. Si vous le souhaitez, vous pouvez spécifier à quelle organisation le dépôt doit appartenir en utilisant l'argument `organization` : + +```py +from huggingface_hub import create_repo + +create_repo("dummy-model", organization="huggingface") +``` + +Cela créera le dépôt `dummy-model` dans l'espace de nom `huggingface`, en supposant que vous appartenez à cette organisation. +D'autres arguments qui peuvent être utiles sont : + +- `private`, afin de spécifier si le référentiel doit être visible des autres ou non, +- `token`, si vous voulez remplacer le jeton stocké dans votre cache par un jeton donné, +- `repo_type`, si vous souhaitez créer un `dataset` ou un `space` au lieu d'un modèle. Les valeurs acceptées sont `"dataset"` et `"space"`. + +Une fois que le dépôt est créé, nous devons y ajouter des fichiers ! Passez à la section suivante pour voir les trois façons dont cela peut être géré. + + +## Utilisation de l'interface web + +L'interface web offre des outils pour gérer les dépôts directement dans le *Hub*. En utilisant l'interface, vous pouvez facilement créer des dépôts, ajouter des fichiers (même de grande taille !), explorer des modèles, visualiser les différences, et bien plus encore. + +Pour créer un nouveau dépôt, visitez [huggingface.co/new](https://huggingface.co/new) : + +
+Page showcasing the model used for the creation of a new model repository. +
+ +Tout d'abord, indiquez le propriétaire du dépôt : il peut s'agir de vous ou de l'une des organisations auxquelles vous êtes affilié. Si vous choisissez une organisation, le modèle sera présenté sur la page de l'organisation et chaque membre de l'organisation aura la possibilité de contribuer au référentiel. + +Ensuite, saisissez le nom de votre modèle. Ce sera également le nom du dépôt. Enfin, vous pouvez préciser si vous souhaitez que votre modèle soit public ou privé. Les modèles privés sont cachés de la vue du public. + +Après avoir créé votre dépôt de modèles, vous devriez voir une page comme celle-ci : + +
+An empty model page after creating a new repository. +
+ +C'est là que votre modèle sera hébergé. Pour commencer à le remplir, vous pouvez ajouter un fichier README directement depuis l'interface web. + +
+The README file showing the Markdown capabilities. +
+ +Le fichier README est en Markdown. N'hésitez pas à vous lâcher avec lui ! La troisième partie de ce chapitre est consacrée à la construction d'une carte de modèle. Celles-ci sont d'une importance capitale pour valoriser votre modèle, car c'est par elles que vous indiquez aux autres ce qu'il peut faire. + +Si vous regardez l'onglet « Fichiers et versions », vous verrez qu'il n'y a pas encore beaucoup de fichiers : juste le *README.md* que vous venez de créer et le fichier *.gitattributes* qui garde la trace des gros fichiers. + +
+The 'Files and versions' tab only shows the .gitattributes and README.md files. +
+ +Nous allons maintenant voir comment ajouter de nouveaux fichiers. + +## Téléchargement des fichiers du modèle + +Le système de gestion des fichiers sur le *Hub* est basé sur git pour les fichiers ordinaires et git-lfs (qui signifie [Git Large File Storage](https://git-lfs.github.com/)) pour les fichiers plus importants. + +Dans la section suivante, nous passons en revue trois façons différentes de télécharger des fichiers sur le *Hub* : par `huggingface_hub` et par des commandes git. + +### L'approche `upload_file' + +L'utilisation de `upload_file` ne nécessite pas que git et git-lfs soient installés sur votre système. Il pousse les fichiers directement vers le 🤗 *Hub* en utilisant des requêtes HTTP POST. Une limitation de cette approche est qu'elle ne gère pas les fichiers dont la taille est supérieure à 5 Go. +Si vos fichiers ont une taille supérieure à 5 Go, veuillez suivre les deux autres méthodes détaillées ci-dessous. + +L'API peut être utilisée comme suit : + +```py +from huggingface_hub import upload_file + +upload_file( + "/config.json", + path_in_repo="config.json", + repo_id="/dummy-model", +) +``` + +Ceci téléchargera le fichier `config.json` disponible à `` à la racine du dépôt en tant que `config.json`, vers le dépôt `dummy-model`. +D'autres arguments qui peuvent être utiles sont : + +- `token`, si vous souhaitez remplacer le jeton stocké dans votre cache par un jeton donné, +- `repo_type`, si vous souhaitez télécharger vers un `dataset` ou un `space` au lieu d'un modèle. Les valeurs acceptées sont `"dataset"` et `"space"`. + + +### La classe `Repository` + +La classe `Repository` gère un dépôt local d'une manière similaire à git. Elle abstrait la plupart des problèmes que l'on peut rencontrer avec git pour fournir toutes les fonctionnalités dont nous avons besoin. + +L'utilisation de cette classe nécessite l'installation de git et de git-lfs, donc assurez-vous que git-lfs est installé (voir [ici](https://git-lfs.github.com/) pour les instructions d'installation) et configuré avant de commencer. + +Afin de commencer à jouer avec le dépôt que nous venons de créer, nous pouvons commencer par l'initialiser dans un dossier local en clonant le dépôt distant : + +```py +from huggingface_hub import Repository + +repo = Repository("", clone_from="/dummy-model") +``` + +Cela a créé le dossier `` dans notre répertoire de travail. Ce dossier ne contient que le fichier `.gitattributes` car c'est le seul fichier créé lors de l'instanciation du dépôt par `create_repo`. + +A partir de maintenant, nous pouvons utiliser plusieurs des méthodes traditionnelles de git : + +```py +repo.git_pull() +repo.git_add() +repo.git_commit() +repo.git_push() +repo.git_tag() +``` + +Et d'autres encore ! Nous vous recommandons de jeter un coup d'oeil à la documentation de `Repository` disponible [ici](https://github.com/huggingface/huggingface_hub/tree/main/src/huggingface_hub#advanced-programmatic-repository-management) pour une vue d'ensemble de toutes les méthodes disponibles. + +Actuellement, nous avons un modèle et un *tokenizer* que nous voulons pousser vers le *Hub*. Nous avons réussi à cloner le dépôt, nous pouvons donc enregistrer les fichiers dans ce dépôt. + +Nous nous assurons d'abord que notre clone local est à jour en récupérant les dernières modifications : + +```py +repo.git_pull() +``` + +Une fois que c'est fait, nous sauvegardons les fichiers du modèle et du *tokenizer* : + +```py +model.save_pretrained("") +tokenizer.save_pretrained("") +``` + +Le `` contient maintenant tous les fichiers du modèle et du *tokenizer*. Nous suivons le flux de travail git habituel en ajoutant des fichiers à la zone de transit, en les validant et en les poussant vers le *Hub* : + +```py +repo.git_add() +repo.git_commit("Add model and tokenizer files") +repo.git_push() +``` + +Félicitations ! Vous venez de pousser vos premiers fichiers sur le *Hub*. + +### L'approche basée sur git + +Il s'agit de l'approche la plus basique pour télécharger des fichiers : nous le ferons directement avec git et git-lfs. La plupart des difficultés sont abstraites par les approches précédentes, mais il y a quelques réserves avec la méthode suivante, nous allons donc suivre un cas d'utilisation plus complexe. + +L'utilisation de cette classe nécessite l'installation de git et de git-lfs, donc assurez-vous d'avoir [git-lfs](https://git-lfs.github.com/) installé et configuré avant de commencer. + +Commencez par initialiser git-lfs : + +```bash +git lfs install +``` + +```bash +Updated git hooks. +Git LFS initialized. +``` + +Une fois que c'est fait, la première étape consiste à cloner votre dépôt de modèles : + +```bash +git clone https://huggingface.co// +``` + +Mon nom d'utilisateur est `lysandre` et j'ai utilisé le nom de modèle `dummy`, donc pour moi la commande ressemble à ce qui suit : + +``` +git clone https://huggingface.co/lysandre/dummy +``` + +J'ai maintenant un dossier nommé *dummy* dans mon répertoire de travail. Je peux `cd` dans ce dossier et jeter un coup d'oeil à son contenu : + +```bash +cd dummy && ls +``` + +```bash +README.md +``` + +Si vous venez de créer votre dépôt en utilisant la méthode `create_repo` du *Hub*, ce dossier devrait seulement contenir un fichier caché `.gitattributes`. Si vous avez suivi les instructions de la section précédente pour créer un dépôt en utilisant l'interface web, le dossier devrait contenir un seul fichier *README.md* à côté du fichier caché `.gitattributes`, comme indiqué ici. + +L'ajout d'un fichier de taille normale, comme un fichier de configuration, un fichier de vocabulaire, ou tout autre fichier de moins de quelques mégaoctets, est fait exactement comme on le ferait dans n'importe quel système basé sur git. Cependant, les fichiers plus volumineux doivent être enregistrés via git-lfs afin de les pousser vers *huggingface.co*. + +Revenons un peu à Python pour générer un modèle et un *tokenizer* que nous souhaitons commiter dans notre dépôt fictif : + +{#if fw === 'pt'} +```py +from transformers import AutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = AutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + +# Faites ce que vous voulez avec le modèle, entraînez-le, finetunez-le... + +model.save_pretrained("") +tokenizer.save_pretrained("") +``` +{:else} +```py +from transformers import TFAutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = TFAutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + +# Faites ce que vous voulez avec le modèle, entraînez-le, finetunez-le... + +model.save_pretrained("") +tokenizer.save_pretrained("") +``` +{/if} + +Maintenant que nous avons sauvegardé quelques artefacts de modèle et de *tokenizer*, regardons à nouveau le dossier *dummy* : + +```bash +ls +``` + +{#if fw === 'pt'} +```bash +config.json pytorch_model.bin README.md sentencepiece.bpe.model special_tokens_map.json tokenizer_config.json tokenizer.json +``` + +Si vous regardez la taille des fichiers (par exemple, avec `ls -lh`), vous devriez voir que le fichier d'état du modèle (*pytorch_model.bin*) est la seule exception, avec plus de 400 Mo. + +{:else} +```bash +config.json README.md sentencepiece.bpe.model special_tokens_map.json tf_model.h5 tokenizer_config.json tokenizer.json +``` + +Si vous regardez la taille des fichiers (par exemple, avec `ls -lh`), vous devriez voir que le fichier dict de l'état du modèle (*t5_model.h5*) est la seule aberration, avec plus de 400 Mo. + +{/if} + + +✏️ Lors de la création du dépôt à partir de l'interface web, le fichier *.gitattributes* est automatiquement configuré pour considérer les fichiers avec certaines extensions, comme *.bin* et *.h5*, comme des fichiers volumineux, et git-lfs les suivra sans aucune configuration nécessaire de votre part. + + +Nous pouvons maintenant aller de l'avant et procéder comme nous le ferions habituellement avec des dépôts Git traditionnels. Nous pouvons ajouter tous les fichiers à l'environnement staging de Git en utilisant la commande `git add` : + +```bash +git add . +``` + +Nous pouvons alors jeter un coup d'œil aux fichiers : + +```bash +git status +``` + +{#if fw === 'pt'} +```bash +On branch main +Your branch is up to date with 'origin/main'. + +Changes to be committed: + (use "git restore --staged ..." to unstage) + modified: .gitattributes + new file: config.json + new file: pytorch_model.bin + new file: sentencepiece.bpe.model + new file: special_tokens_map.json + new file: tokenizer.json + new file: tokenizer_config.json +``` +{:else} +```bash +On branch main +Your branch is up to date with 'origin/main'. + +Changes to be committed: + (use "git restore --staged ..." to unstage) + modified: .gitattributes + new file: config.json + new file: sentencepiece.bpe.model + new file: special_tokens_map.json + new file: tf_model.h5 + new file: tokenizer.json + new file: tokenizer_config.json +``` +{/if} + +De même, nous pouvons nous assurer que git-lfs suit les bons fichiers en utilisant sa commande `status` : + +```bash +git lfs status +``` + +{#if fw === 'pt'} +```bash +On branch main +Objects to be pushed to origin/main: + + +Objects to be committed: + + config.json (Git: bc20ff2) + pytorch_model.bin (LFS: 35686c2) + sentencepiece.bpe.model (LFS: 988bc5a) + special_tokens_map.json (Git: cb23931) + tokenizer.json (Git: 851ff3e) + tokenizer_config.json (Git: f0f7783) + +Objects not staged for commit: + + +``` + +Nous pouvons voir que tous les fichiers ont `Git` comme gestionnaire, sauf *pytorch_model.bin* et *sentencepiece.bpe.model*, qui ont `LFS`. Super ! + +{:else} +```bash +On branch main +Objects to be pushed to origin/main: + + +Objects to be committed: + + config.json (Git: bc20ff2) + sentencepiece.bpe.model (LFS: 988bc5a) + special_tokens_map.json (Git: cb23931) + tf_model.h5 (LFS: 86fce29) + tokenizer.json (Git: 851ff3e) + tokenizer_config.json (Git: f0f7783) + +Objects not staged for commit: + + +``` + +Nous pouvons voir que tous les fichiers ont `Git` comme gestionnaire, sauf *t5_model.h5* qui a `LFS`. Super ! + +{/if} + +Passons aux étapes finales, *committing* et *pushing* vers le dépôt distant *huggingface.co* : + +```bash +git commit -m "First model version" +``` + +{#if fw === 'pt'} +```bash +[main b08aab1] First model version + 7 files changed, 29027 insertions(+) + 6 files changed, 36 insertions(+) + create mode 100644 config.json + create mode 100644 pytorch_model.bin + create mode 100644 sentencepiece.bpe.model + create mode 100644 special_tokens_map.json + create mode 100644 tokenizer.json + create mode 100644 tokenizer_config.json +``` +{:else} +```bash +[main b08aab1] First model version + 6 files changed, 36 insertions(+) + create mode 100644 config.json + create mode 100644 sentencepiece.bpe.model + create mode 100644 special_tokens_map.json + create mode 100644 tf_model.h5 + create mode 100644 tokenizer.json + create mode 100644 tokenizer_config.json +``` +{/if} + +Le chargement peut prendre un peu de temps, en fonction de la vitesse de votre connexion Internet et de la taille de vos fichiers : + +```bash +git push +``` + +```bash +Uploading LFS objects: 100% (1/1), 433 MB | 1.3 MB/s, done. +Enumerating objects: 11, done. +Counting objects: 100% (11/11), done. +Delta compression using up to 12 threads +Compressing objects: 100% (9/9), done. +Writing objects: 100% (9/9), 288.27 KiB | 6.27 MiB/s, done. +Total 9 (delta 1), reused 0 (delta 0), pack-reused 0 +To https://huggingface.co/lysandre/dummy + 891b41d..b08aab1 main -> main +``` + +{#if fw === 'pt'} +Si nous jetons un coup d'œil au dépôt du modèle, lorsque cette opération est terminée, nous pouvons voir tous les fichiers récemment ajoutés : + +
+The 'Files and versions' tab now contains all the recently uploaded files. +
+ +L'interface utilisateur vous permet d'explorer les fichiers du modèle et les *commits* et de voir la différence introduite par chaque *commit* : + +
+The diff introduced by the recent commit. +
+{:else} +Si nous jetons un coup d'œil au dépôt du modèle, lorsque cette opération est terminée, nous pouvons voir tous les fichiers récemment ajoutés : + +
+The 'Files and versions' tab now contains all the recently uploaded files. +
+ +L'interface utilisateur vous permet d'explorer les fichiers du modèle et les *commits* et de voir la différence introduite par chaque *commit* : + +
+The diff introduced by the recent commit. +
+{/if} diff --git a/chapters/fr/chapter4/4.mdx b/chapters/fr/chapter4/4.mdx new file mode 100644 index 000000000..d3a574cf2 --- /dev/null +++ b/chapters/fr/chapter4/4.mdx @@ -0,0 +1,84 @@ +# Construire une carte de modèle + +La carte de modèle est un fichier qui est sans doute aussi important que les fichiers du modèle et du *tokenizer* dans un dépôt de modèles. Il s'agit de la définition centrale du modèle, qui garantit la réutilisation par les autres membres de la communauté, la reproductibilité des résultats, et une plateforme sur laquelle les autres membres peuvent construire leurs artefacts. + +Documenter le processus d'entraînement et d'évaluation aide les autres à comprendre ce qu'ils peuvent attendre d'un modèle. Fournir suffisamment d'informations concernant les données utilisées, les prétraitements et post-traitements effectués permet d'identifier et de comprendre les limites, les biais et les contextes dans lesquels le modèle est ou n'est pas utile. + +Par conséquent, la création d'une carte de modèle définissant clairement votre modèle est une étape très importante. Nous vous donnons ici quelques conseils qui vous aideront à le faire. La création de la fiche de modèle se fait par le biais du fichier *README.md* que vous avez vu précédemment, qui est un fichier Markdown. + +Le concept de carte de modèle provient d'une direction de recherche de Google, partagée pour la première fois dans l'article ["Model Cards for Model Reporting"](https://arxiv.org/abs/1810.03993) par Margaret Mitchell et al. De nombreuses informations contenues dans ce document sont basées sur cet article et nous vous recommandons d'y jeter un coup d'œil pour comprendre pourquoi les cartes de modèles sont si importantes dans un monde qui valorise la reproductibilité, la réutilisation et l'équité. + +La carte de modèle commence généralement par une très brève présentation de haut niveau de l'objet du modèle, suivie de détails supplémentaires dans les sections suivantes : + +- description du modèle +- utilisations et limites prévues +- comment utiliser le modèle +- limites et biais +- données d'entraînement +- procédure d'entraînement +- résultats de l'évaluation + +Voyons ce que chacune de ces sections doit contenir. + + +### Description du modèle + +La description du modèle fournit des détails de base sur le modèle. Cela inclut l'architecture, la version, s'il a été présenté dans un article, si une implémentation originale est disponible, l'auteur et des informations générales sur le modèle. Tout droit d'auteur doit être attribué ici. Des informations générales sur les procédures d'entraînement, les paramètres et les avertissements importants peuvent également être mentionnés dans cette section. + +### Utilisations et limitations prévues + +Vous décrivez ici les cas d'utilisation auxquels le modèle est destiné, y compris les langues, les domaines et les champs où il peut être appliqué. Cette section de la fiche de modèle peut également documenter les domaines qui sont connus pour être hors de portée du modèle, ou dans lesquels il est susceptible de fonctionner de manière sous-optimale. + +### Comment utiliser + +Cette section doit inclure des exemples d'utilisation du modèle. Cela peut montrer l'utilisation de la fonction `pipeline()`, l'utilisation des classes du modèle et du *tokenizer*, et tout autre code que vous pensez être utile. + +### Données d'entraînement + +Cette partie doit indiquer sur quel(s) jeu(x) de données le modèle a été entraîné. Une brève description du ou des jeux de données est également la bienvenue. + +### Procédure d'entraînement + +Dans cette section, vous devez décrire tous les aspects pertinents de l'entraînement qui sont utiles du point de vue de la reproductibilité. Cela inclut tout prétraitement et post-traitement effectué sur les données, ainsi que des détails tels que le nombre d'époques pour lesquelles le modèle a été entraîné, la taille du batch, le taux d'apprentissage, etc. + +### Variable et métriques + +Décrivez ici les métriques que vous utilisez pour l'évaluation et les différents facteurs que vous mesurez. En mentionnant la ou les métriques utilisées, sur quel jeu de données et quelle division du jeu de données, il est plus facile de comparer les performances de votre modèle à celles d'autres modèles. Les sections précédentes, telles que les utilisateurs prévus et les cas d'utilisation, doivent être prises en compte. + +### Résultats de l'évaluation + +Enfin, fournissez une indication de la performance du modèle sur l'ensemble de données d'évaluation. Si le modèle utilise un seuil de décision, indiquez le seuil de décision utilisé dans l'évaluation ou fournissez des détails sur l'évaluation à différents seuils pour les utilisations prévues. + + +## Exemple + +Voici quelques exemples de cartes de modèles bien conçues : + +- [`bert-base-case`](https://huggingface.co/bert-base-cased) +- [`gpt2`](https://huggingface.co/gpt2) +- [`distilbert`](https://huggingface.co/distilbert-base-uncased) + +D'autres exemples provenant de différentes organisations et entreprises sont disponibles [ici](https://github.com/huggingface/model_card/blob/master/examples.md). + +## Note + +Les fiches de modèle ne sont pas une exigence lors de la publication de modèles, et vous n'avez pas besoin d'inclure toutes les sections décrites ci-dessus lorsque vous en faites une. Cependant, une documentation explicite du modèle ne peut qu'être bénéfique aux futurs utilisateurs. Nous vous recommandons donc de remplir autant de sections que possible, au mieux de vos connaissances et de vos capacités. + +## Métadonnées de la carte de modèle + +Si vous avez exploré un peu le *Hub*, vous devriez avoir vu que certains modèles appartiennent à certaines catégories : vous pouvez les filtrer par tâches, langues, bibliothèques, et plus encore. Les catégories auxquelles appartient un modèle sont identifiées en fonction des métadonnées que vous ajoutez dans l'en-tête de la fiche du modèle. + +Par exemple, si vous regardez la fiche de modèle de [`camembert-base`](https://huggingface.co/camembert-base/blob/main/README.md), vous devriez voir les lignes suivantes dans l'en-tête de la fiche de modèle : + +``` +--- +language: fr +license: mit +datasets: +- oscar +--- +``` + +Ces métadonnées sont analysées par le *Hub* qui identifie alors ce modèle comme étant un modèle français, avec une licence MIT, entraîné sur le jeu de données Oscar. + +La [spécification complète de la carte du modèle](https://github.com/huggingface/hub-docs/blame/main/modelcard.md) permet de spécifier les langues, les licences, les balises, les jeux de données, les mesures, ainsi que les résultats d'évaluation obtenus par le modèle lors de l'entraînement. \ No newline at end of file diff --git a/chapters/fr/chapter4/5.mdx b/chapters/fr/chapter4/5.mdx new file mode 100644 index 000000000..1cab384bc --- /dev/null +++ b/chapters/fr/chapter4/5.mdx @@ -0,0 +1,7 @@ +# Fin de la première partie du cours ! + +C'est la fin de la première partie du cours ! La partie 2 sera publiée le 15 novembre 2021 avec un grand événement communautaire, pour plus d'informations voir [ici](https://huggingface.co/blog/course-launch-event). + +Vous devriez maintenant être capable de *finetuner* un modèle pré-entraîné sur un problème de classification de texte (phrases simples ou paires de phrases) et de télécharger le résultat sur le *Hub*. Pour vous assurer que vous maîtrisez cette première section, vous devriez refaire ça sur un problème qui vous intéresse (et pas nécessairement en anglais si vous parlez une autre langue) ! Vous pouvez trouver de l'aide dans les [forums Hugging Face](https://discuss.huggingface.co/) et partager votre projet dans [ce sujet](https://discuss.huggingface.co/t/share-your-projects/6803) une fois que vous avez terminé. + +Nous sommes impatients de voir ce que vous allez construire avec cet outil ! \ No newline at end of file diff --git a/chapters/fr/chapter4/6.mdx b/chapters/fr/chapter4/6.mdx new file mode 100644 index 000000000..5e00bbdc1 --- /dev/null +++ b/chapters/fr/chapter4/6.mdx @@ -0,0 +1,223 @@ + + + + +# Quiz de fin de chapitre + +Testons ce que vous avez appris dans ce chapitre ! + +### 1. A quoi sont limités les modèles du *Hub* ? + +Transformers.", + explain: "Si les modèles de la bibliothèque 🤗 Transformers sont pris en charge sur le Hub, ils ne sont pas les seuls !" + }, + { + text: "Tous les modèles avec une interface similaire à 🤗 Transformers.", + explain: "Aucune exigence d'interface n'est fixée lors du téléchargement de modèles vers le Hub." + }, + { + text: "Il n'y a pas de limites.", + explain: "C'est vrai ! Il n'y a pas de limites au téléchargement de modèles sur le Hub.", + correct: true + }, + { + text: "Des modèles qui sont d'une certaine manière liés au NLP.", + explain: "Aucune exigence n'est fixée concernant le domaine d'application !" + } + ]} +/> + +### 2. Comment pouvez-vous gérer les modèles sur le *Hub* ? + +Hub sont de simples dépôts Git exploitant git-lfs pour les fichiers volumineux.", + correct: true + } + ]} +/> + +### 3. Que pouvez-vous faire en utilisant l'interface web du *Hub* ? + +Forker un dépôt existant.", + explain: "Forking un dépôt n'est pas possible sur le Hub." + }, + { + text: "Créer un nouveau dépôt de modèles.", + explain: "Correct ! Ce n'est pas tout ce que vous pouvez faire, cependant.", + correct: true + }, + { + text: "Gérer et modifier des fichiers.", + explain: "Correct ! Ce n'est pas la seule bonne réponse, cependant..", + correct: true + }, + { + text: "Télécharger des fichiers.", + explain: "C'est vrai ! Mais ce n'est pas tout.", + correct: true + }, + { + text: "Voir les différences entre les versions.", + explain: "Correct ! Ce n'est pas tout ce que vous pouvez faire.", + correct: true + } + ]} +/> + +### 4. Qu'est-ce qu'une carte de modèle ? + +tokenizer.", + explain: "It is indeed a description of the model, but it's an important piece: if it's incomplete or absent the model's utility is drastically reduced." + }, + { + text: "A way to ensure reproducibility, reusability, and fairness.", + explain: "Correct! Sharing the right information in the model card will help users leverage your model and be aware of its limits and biases. ", + correct: true + }, + { + text: "A Python file that can be run to retrieve information about the model.", + explain: "Model cards are simple Markdown files." + } + ]} +/> + +### 5. Lesquels de ces objets de la bibliothèque 🤗 *Transformers* peuvent être directement partagés sur le Hub avec `push_to_hub()` ? + +{#if fw === 'pt'} +tokenizer", + explain: "Correct ! Tous les tokenizers ont la méthode push_to_hub et l'utiliser poussera tous les fichiers du tokenizer (vocabulaire, architecture du tokenizer, etc.) vers un dépôt donné. Ce n'est pas la seule bonne réponse, cependant !", + correct: true + }, + { + text: "Une configuration de modèle", + explain: "C'est vrai ! Toutes les configurations de modèles ont la méthode push_to_hub et son utilisation les poussera vers un dépôt donné. Que pouvez-vous partager d'autre ?", + correct: true + }, + { + text: "Un modèle", + explain: "Correct ! Tous les modèles ont la méthode push_to_hub qui le pushra ainsi que leurs fichiers de configuration, vers un dépôt donné. Ce n'est pas tout ce que vous pouvez partager, cependant.", + correct: true + }, + { + text: "Trainer", + explain: "C'est exact. Le Trainer implémente aussi la méthode push_to_hub. L'utiliser téléchargera le modèle, sa configuration, le tokenizer et une ébauche de carte de modèle vers un dépôt donné. Essayez une autre réponse !", + correct: true + } + ]} +/> +{:else} +tokenizer", + explain: "C'est vrai ! Toutes les configurations de modèles ont la méthode push_to_hub et son utilisation les poussera vers un dépôt donné. Que pouvez-vous partager d'autre ?", + correct: true + }, + { + text: "Une configuration de modèle", + explain: "C'est vrai ! Toutes les configurations de modèles ont la méthode push_to_hub et son utilisation les poussera vers un dépôt donné. Que pouvez-vous partager d'autre ?", + correct: true + }, + { + text: "Un modèle", + explain: "Correct ! Tous les modèles ont la méthode push_to_hub qui le pushra ainsi que leurs fichiers de configuration, vers un dépôt donné. Ce n'est pas tout ce que vous pouvez partager, cependant.", + correct: true + }, + { + text: "Tout ce qui précède avec un callback dédié", + explain: "C'est exact. Le PushToHubCallback enverra régulièrement tous ces objets à un dépôt pendant l'entraînement.", + correct: true + } + ]} +/> +{/if} + +### 6. Quelle est la première étape lorsqu'on utilise la méthode `push_to_hub()` ou les outils CLI ? + +notebook.", + explain: "Correct ! Cela affichera un widget pour vous permettre de vous authentifier.", + correct: true + }, + ]} +/> + +### 7. Vous utilisez un modèle et un *tokenizer*, comment pouvez-vous les télécharger sur le *Hub* ? + +tokenizer.", + explain: "Correct !", + correct: true + }, + { + text: "Au sein du moteur d'exécution Python, en les enveloppant dans une balise huggingface_hub.", + explain: "Les modèles et les tokenizers bénéficient déjà de huggingface_hub : pas besoin d'emballage supplémentaire !" + }, + { + text: "En les sauvegardant sur le disque et en appelant transformers-cli upload-model.", + explain: "La commande upload-model n'existe pas." + } + ]} +/> + +### 8. Quelles opérations git pouvez-vous faire avec la classe `Repository` ? + +commit.", + explain: "Correct, la méthode git_commit() est là pour ça.", + correct: true + }, + { + text: "Un pull.", + explain: "C'est le but de la méthode git_pull().", + correct: true + }, + { + text: "Un push.", + explain: "La méthode git_push() fait ça.", + correct: true + }, + { + text: "Un merge.", + explain: "Non, cette opération ne sera jamais possible avec cette API." + } + ]} +/> \ No newline at end of file diff --git a/chapters/fr/chapter5/1.mdx b/chapters/fr/chapter5/1.mdx index 766e33082..145c6ecb0 100644 --- a/chapters/fr/chapter5/1.mdx +++ b/chapters/fr/chapter5/1.mdx @@ -1,6 +1,6 @@ # Introduction -Dans le [Chapitre 3](/course/fr/chapter3) vous avez eu un premier aperçu de la bibliothèque 🤗 *Datasets* et qu'il y a trois étapes principales pour *finetuner* un modèle: +Dans le [Chapitre 3](/course/fr/chapter3) vous avez eu un premier aperçu de la bibliothèque 🤗 *Datasets* et qu'il y a trois étapes principales pour *finetuner* un modèle : 1. charger un jeu de données à partir du *Hub* d’Hugging Face. 2. Prétraiter les données avec `Dataset.map()`. diff --git a/chapters/fr/chapter5/2.mdx b/chapters/fr/chapter5/2.mdx index 8c84d6282..7f4f93a2b 100644 --- a/chapters/fr/chapter5/2.mdx +++ b/chapters/fr/chapter5/2.mdx @@ -77,7 +77,7 @@ DatasetDict({ }) ``` -Cela nous montre le nombre de lignes et les noms de colonnes associés à l’échantillon d’entraînement. Nous pouvons afficher l'un des exemples en indexant l’échantillon `train`. comme suit : +Cela nous montre le nombre de lignes et les noms de colonnes associés à l’échantillon d’entraînement. Nous pouvons afficher l'un des exemples en indexant l’échantillon `train` comme suit : ```py squad_it_dataset["train"][0] @@ -145,7 +145,7 @@ Maintenant que vous savez comment charger des fichiers locaux sur votre ordinate ## Charger un jeu de données distant -Si vous travaillez en tant que *data scientist* ou codeur dans une entreprise, il y a de fortes chances que les juex de données que vous souhaitez analyser soient stockés sur un serveur distant. Heureusement, charger des fichiers distants est aussi simple que de charger des fichiers locaux ! Au lieu de fournir un chemin vers les fichiers locaux, nous pointons l'argument `data_files` de `load_dataset()` vers une ou plusieurs URL où les fichiers distants sont stockés. Par exemple, pour le jeux de données SQuAD-it hébergé sur GitHub, nous pouvons simplement faire pointer `data_files` vers les URL _SQuAD_it-*.json.gz_ comme suit : +Si vous travaillez en tant que *data scientist* ou codeur dans une entreprise, il y a de fortes chances que les juex de données que vous souhaitez analyser soient stockés sur un serveur distant. Heureusement, charger des fichiers distants est aussi simple que de charger des fichiers locaux ! Au lieu de fournir un chemin vers les fichiers locaux, nous pointons l'argument `data_files` de `load_dataset()` vers une ou plusieurs URL où les fichiers distants sont stockés. Par exemple, pour le jeu de données SQuAD-it hébergé sur GitHub, nous pouvons simplement faire pointer `data_files` vers les URL _SQuAD_it-*.json.gz_ comme suit : ```py url = "https://github.com/crux82/squad-it/raw/master/" diff --git a/chapters/fr/chapter5/3.mdx b/chapters/fr/chapter5/3.mdx index 455473889..ef1ad0367 100644 --- a/chapters/fr/chapter5/3.mdx +++ b/chapters/fr/chapter5/3.mdx @@ -116,7 +116,7 @@ def filter_nones(x): return x["condition"] is not None ``` -puis éxécuter `drug_dataset.filter(filter_nones)`, nous pouvons le faire en une seule ligne en utilisant une _fonction lambda_. En Python, les fonctions lambda sont de petites fonctions que vous pouvez définir sans les nommer explicitement. Ils prennent la forme générale : +puis exécuter `drug_dataset.filter(filter_nones)`, nous pouvons le faire en une seule ligne en utilisant une _fonction lambda_. En Python, les fonctions lambda sont de petites fonctions que vous pouvez définir sans les nommer explicitement. Ils prennent la forme générale : ``` lambda : @@ -348,7 +348,7 @@ Toutes ces fonctionnalités condensées en une seule méthode sont déjà assez -💡 En machine learning, un _example_ est généralement défini comme l'ensemble de _features_ que nous alimentons au modèle. Dans certains contextes, ces caractéristiques seront l'ensemble des colonnes d'un `Dataset`, mais dans d'autres (comme ici et pour la réponse aux questions), plusieurs caractéristiques peuvent être extraites d'un seul exemple et appartenir à une seule colonne. +💡 En machine learning, un _exemple_ est généralement défini comme l'ensemble de _features_ que nous donnons au modèle. Dans certains contextes, ces caractéristiques seront l'ensemble des colonnes d'un `Dataset`, mais dans d'autres (comme ici et pour la réponse aux questions), plusieurs caractéristiques peuvent être extraites d'un seul exemple et appartenir à une seule colonne. diff --git a/chapters/fr/chapter5/5.mdx b/chapters/fr/chapter5/5.mdx index 42a9ffa15..36bcca83c 100644 --- a/chapters/fr/chapter5/5.mdx +++ b/chapters/fr/chapter5/5.mdx @@ -119,7 +119,7 @@ Waouh, ça fait beaucoup d'informations ! Nous pouvons voir des champs utiles co -Comme décrit dans la [documentation GitHub](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting), les requêtes non authentifiées sont limitées à 60 requêtes par heure. Bien que vous puissiez augmenter le paramètre de requête `per_page` pour réduire le nombre de requêtes que vous effectuez, vous atteindrez toujours la limite de débit sur tout dépot contenant des milliers de problèmes. Donc, à la place, vous devez suivre les [instructions de GitHub](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) sur la création d'un _jeton d'accès personnel_ afin que vous peut augmenter la limite de débit à 5 000 requêtes par heure. Une fois que vous avez votre *token*, vous pouvez l'inclure dans l'en-tête de la requête : +Comme décrit dans la [documentation GitHub](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting), les requêtes non authentifiées sont limitées à 60 requêtes par heure. Bien que vous puissiez augmenter le paramètre de requête `per_page` pour réduire le nombre de requêtes que vous effectuez, vous atteindrez toujours la limite de débit sur tout dépôt contenant des milliers de problèmes. Donc, à la place, vous devez suivre les [instructions de GitHub](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) sur la création d'un _jeton d'accès personnel_ afin que vous peut augmenter la limite de débit à 5 000 requêtes par heure. Une fois que vous avez votre *token*, vous pouvez l'inclure dans l'en-tête de la requête : ```py GITHUB_TOKEN = xxx # Copy your GitHub token here @@ -245,7 +245,7 @@ issues_dataset = issues_dataset.map( Bien que nous puissions continuer à nettoyer davantage le jeu de données en supprimant ou en renommant certaines colonnes, il est généralement recommandé de le conserver aussi brut que possible à ce stade afin qu'il puisse être facilement utilisé dans plusieurs applications. -Avant de pousser notre jeu de données vers le *Hub* d’Hugging Face, traitons une chose manquant : les commentaires associés à chaque problème et *pull requests*. Nous les ajouterons ensuite avec l'API GitHub REST ! +Avant de pousser notre jeu de données vers le *Hub* d’Hugging Face, traitons une chose manquante : les commentaires associés à chaque problème et *pull requests*. Nous les ajouterons ensuite avec l'API GitHub REST ! ## Enrichir le jeu de données diff --git a/chapters/fr/chapter5/7.mdx b/chapters/fr/chapter5/7.mdx index 5321adb82..6e1bc43ce 100644 --- a/chapters/fr/chapter5/7.mdx +++ b/chapters/fr/chapter5/7.mdx @@ -7,4 +7,4 @@ Eh bien, ce fut une sacrée visite de la bibliothèque 🤗 *Datasets*. Félicit - créer votre propre jeu de données et l’envoyer vers le *Hub*. - enchâsser vos documents en utilisant un *transformer* et construire un moteur de recherche sémantique en utilisant FAISS. -Dans le [Chapter 7](/course/fr/chapter7), nous mettrons tout cela à profit en plongeant dans les tâches de traitement du langage neturel de base pour lesquelles les *transformers* sont parfaits. Avant cela mettez vos connaissances sur la librairie 🤗 *Datasets* à l'épreuve avec un petit quiz ! +Dans le [Chapter 7](/course/fr/chapter7), nous mettrons tout cela à profit en plongeant dans les tâches de traitement du langage naturel de base pour lesquelles les *transformers* sont parfaits. Avant cela mettez vos connaissances sur la librairie 🤗 *Datasets* à l'épreuve avec un petit quiz ! diff --git a/chapters/fr/chapter5/8.mdx b/chapters/fr/chapter5/8.mdx index e61777100..5ea12fd8e 100644 --- a/chapters/fr/chapter5/8.mdx +++ b/chapters/fr/chapter5/8.mdx @@ -11,17 +11,17 @@ Avant de poursuivre, testons ce que vous avez appris dans ce chapitre. data_files de load_dataset() pour charger les jeux de données locaux.", correct: true }, { - text: "Le Hub> d’Hugging Face", + text: "Le Hub d’Hugging Face.", explain: "Correct ! Vous pouvez charger des jeux de données sur le Hub> en fournissant l'ID du jeu de données. Par exemple : load_dataset('emotion').", correct: true }, { - text: "Un serveur distant", + text: "Un serveur distant.", explain: "Correct ! Vous pouvez passer des URLs à l'argument data_files de load_dataset() pour charger des fichiers distants.", correct: true }, @@ -82,16 +82,16 @@ Laquelle des commandes suivantes produira un échantillon aléatoire de 50 élé mapping entre la RAM CPU et GPU", + text: "Un mapping entre la RAM CPU et GPU.", explain: "Ce n'est pas ça, réessayez !", }, { - text: "Un mappaging entre la RAM et le stockage du système de fichiers", + text: "Un mappaging entre la RAM et le stockage du système de fichiers.", explain: "Correct ! 🤗 Datasets traite chaque jeu de données comme un fichier mappé en mémoire. Cela permet à la bibliothèque d'accéder et d'opérer sur des éléments du jeu de données sans avoir à le charger complètement en mémoire.", correct: true }, { - text: "Un mappaging entre deux fichiers dans le cache 🤗 Datasets", + text: "Un mappaging entre deux fichiers dans le cache 🤗 Datasets.", explain: "Ce n'est pas correct, réessayez !" } ]} @@ -173,16 +173,16 @@ dataset[0] Datasets prend actuellement en charge les données tabulaires, l'audio et la vision par ordinateur. Consultez le jeu de donnéesMNIST sur le Hub pour un exemple de vision par ordinateur." }, { - text: "Oui", + text: "Oui.", explain: "Correct ! Découvrez les développements passionnants concernant la parole et la vision dans la bibliothèque 🤗 Transformers pour voir comment 🤗 Datasets est utilisé dans ces domaines.", correct : true }, diff --git a/chapters/fr/chapter6/1.mdx b/chapters/fr/chapter6/1.mdx new file mode 100644 index 000000000..0950598c8 --- /dev/null +++ b/chapters/fr/chapter6/1.mdx @@ -0,0 +1,13 @@ +# Introduction + +Dans le [Chapitre 3](/course/fr/chapter3), nous avons vu comment *finetuner* un modèle sur une tâche donnée. Pour ce faire, nous utilisons le même *tokenizer* que celui avec lequel le modèle a été pré-entraîné. Mais que faisons-nous lorsque nous voulons entraîner un modèle à partir de zéro ? Dans ces cas, l'utilisation d'un *tokenizer* qui a été pré-entraîné sur un corpus d'un autre domaine ou d'une autre langue est généralement sous-optimale. Par exemple, un *tokenizer* entraîné sur un corpus anglais sera peu performant sur un corpus de textes japonais car l'utilisation des espaces et de la ponctuation est très différente entre les deux langues. + +Dans ce chapitre, vous apprendrez à entraîner un tout nouveau *tokenizer* sur un corpus de textes afin qu'il puisse ensuite être utilisé pour pré-entraîner un modèle de langue. Tout cela se fera à l'aide de la bibliothèque [🤗 *Tokenizers*](https://github.com/huggingface/tokenizers), qui fournit les *tokenizers* « rapides » de la bibliothèque [🤗 *Transformers*](https://github.com/huggingface/transformers). Nous examinerons de près les fonctionnalités offertes par cette bibliothèque et nous étudierons comment les *tokenizers* rapides diffèrent des versions « lentes ». + +Les sujets que nous couvrirons comprennent : +* comment entraîner un nouveau *tokenizer* similaire à celui utilisé par un *checkpoint* donné sur un nouveau corpus de textes, +* les caractéristiques spéciales des *tokenizers* rapides, +* les différences entre les trois principaux algorithmes de tokénisation utilisés aujourd'hui en NLP, +* comment construire un *tokenizer* à partir de zéro avec la bibliothèque 🤗 *Tokenizers* et l'entraîner sur des données. + +Les techniques présentées dans ce chapitre vous prépareront à la section du [Chapitre 7](/course/fr/chapter7/6) où nous examinons la création d'un modèle de langue pour le langage Python. Commençons par examiner ce que signifie « entraîner » un *tokenizer*. \ No newline at end of file diff --git a/chapters/fr/chapter6/10.mdx b/chapters/fr/chapter6/10.mdx new file mode 100644 index 000000000..b7ef6c773 --- /dev/null +++ b/chapters/fr/chapter6/10.mdx @@ -0,0 +1,278 @@ + + +# Quiz de fin de chapitre + +Testons ce que vous avez appris dans ce chapitre ! + +### 1. Quand devez-vous entraîner un nouveau *tokenizer* ? + +tokenizer que le modèle pré-entraîné et de finetuner ce modèle à la place." + }, + { + text: "Lorsque votre jeu de données est similaire à celui utilisé par un modèle pré-entraîné existant et que vous souhaitez finetuner un nouveau modèle en utilisant ce modèle pré-entraîné.", + explain: "Pour finetuner un modèle à partir d'un modèle pré-entraîné, vous devez toujours utiliser le même tokenizer." + }, + { + text: "Lorsque votre jeu de données est différent de celui utilisé par un modèle pré-entraîné existant, et que vous souhaitez pré-entraîner un nouveau modèle.", + explain: "C'est exact ! Dans ce cas, il n'y a aucun avantage à utiliser le même tokenizer.", + correct: true + }, + { + text: "Lorsque votre jeu de données est différent de celui utilisé par un modèle pré-entraîné existant, mais que vous souhaitez finetuner un nouveau modèle en utilisant ce modèle pré-entraîné.", + explain: "Pour finetuner un modèle à partir d'un modèle pré-entraîné, vous devez toujours utiliser le même tokenizer." + } + ]} +/> + +### 2. Quel est l'avantage d'utiliser un générateur de listes de textes par rapport à une liste de listes de textes lors de l'utilisation de `train_new_from_iterator()` ? + +train_new_from_iterator() accepte.", + explain: "Une liste de listes de textes est un type particulier de générateur de listes de textes, la méthode l'acceptera donc aussi. Essayez à nouveau !" + }, + { + text: "Vous éviterez de charger l'ensemble des données en mémoire en une seule fois.", + explain: "Exact ! Chaque batch de textes sera libéré de la mémoire lorsque vous itérerez, et le gain sera particulièrement visible si vous utilisez des 🤗 Datasets pour stocker vos textes.", + correct: true + }, + { + text: "Cela permettra à la bibliothèque 🤗 Tokenizers d'utiliser le multitraitement.", + explain: "Non, il utilisera le multiprocesseur dans tous les cas." + }, + { + text: "Le tokenizer que vous entraînez générera de meilleurs textes.", + explain: "Le tokenizer ne génère pas de texte. Vous le confondez avec un modèle de langage ?" + } + ]} +/> + +### 3. Quels sont les avantages d'utiliser un *tokenizer* « rapide » ? + +tokenizer lent lorsque vous faites des batchs d'entrées.", + explain: "Correct ! Grâce au parallélisme implémenté dans Rust, il sera plus rapide sur les batchs d'entrées. Quel autre avantage pouvez-vous imaginer ?", + correct: true + }, + { + text: "Les *tokenizers* rapides sont toujours plus rapides que leurs homologues lents.", + explain: "Un tokenizer rapide peut en fait être plus lent si vous ne lui donnez qu'un seul ou très peu de textes, car il ne peut pas utiliser le parallélisme." + }, + { + text: "Il peut appliquer le *padding* et la troncature.", + explain: "C'est vrai, mais les *tokenizers* lents le font aussi." + }, + { + text: "Il possède des fonctionnalités supplémentaires qui vous permettent d'associer les tokens à l'extrait de texte qui les a créés.", + explain: "En effet, c'est ce qu'on appelle des mappages de décalage. Ce n'est pas le seul avantage, cependant.", + correct: true + } + ]} +/> + +### 4. Comment le pipeline `token-classification` gère-t-il les entités qui s'étendent sur plusieurs *tokens* ? + +token porte l'étiquette de l'entité, le mot entier est considéré comme étiqueté avec cette entité.", + explain: "C'est une stratégie pour gérer les entités. Quelles autres réponses s'appliquent ici ?", + correct: true + }, + { + text: "Lorsqu'un token a l'étiquette d'une entité donnée, tout autre token suivant ayant la même étiquette est considéré comme faisant partie de la même entité, à moins qu'il ne soit étiqueté comme le début d'une nouvelle entité.", + explain: "C'est la façon la plus courante de regrouper des entités, mais ce n'est pas la seule bonne réponse.", + correct: true + } + ]} +/> + +### 5. Comment le pipeline `question-answering` gère-t-il les contextes longs ? + + + +### 6. Qu'est-ce que la normalisation ? + + + +### 7. Qu'est-ce que la pré-tokénisation pour un *tokenizer* en sous-mots ? + + + +### 8. Sélectionnez les phrases qui s'appliquent au *tokenizer* BPE. + +tokens.", + explain: "Non, c'est l'approche adoptée par un algorithme de tokénisation différent." + }, + { + text: "Un tokenizer BPE apprend les règles de fusion en fusionnant la paire de tokens la plus fréquente.", + explain: "C'est exact !", + correct: true + }, + { + text: "Un tokenizer BPE apprend une règle de fusion en fusionnant la paire de tokens qui maximise un score qui privilégie les paires fréquentes avec des parties individuelles moins fréquentes.", + explain: "Non, c'est la stratégie appliquée par un autre algorithme de tokenization." + }, + { + text: "BPE tokenise les mots en sous-mots en les divisant en caractères, puis en appliquant les règles de fusion.", + explain: "C'est exact !", + correct: true + }, + { + text: "BPE tokenise les mots en sous-mots en trouvant le plus long sous-mot du vocabulaire en commençant par le début, puis en répétant le processus pour le reste du texte.", + explain: "Non, c'est la façon de faire d'un autre algorithme de tokenization." + }, + ]} +/> + +### 9. Sélectionnez les phrases qui s'appliquent au *tokenizer* WordPiece. + +tokens.", + explain: "Non, c'est l'approche adoptée par un algorithme de tokénisation différent." + }, + { + text: "WordPiece Les tokenizer apprennent les règles de fusion en fusionnant la paire de tokens la plus fréquente.", + explain: "Non, c'est la stratégie appliquée par un autre algorithme de tokenization." + }, + { + text: "Un tokenizer WordPiece apprend une règle de fusion en fusionnant la paire de tokens qui maximise un score qui privilégie les paires fréquentes avec des parties individuelles moins fréquentes.", + explain: "C'est exact !", + correct: true + }, + { + text: "WordPiece tokenise les mots en sous-mots en trouvant la segmentation en tokens la plus probable, selon le modèle.", + explain: "Non, c'est le fonctionnement d'un autre algorithme de tokenization." + }, + { + text: "WordPiece tokenise les mots en sous-mots en trouvant le plus long sous-mot du vocabulaire en commençant par le début, puis en répétant le processus pour le reste du texte.", + explain: "Oui, c'est ainsi que WordPiece procède pour l'encodage.", + correct: true + }, + ]} +/> + +### 10. Sélectionnez les phrases qui s'appliquent au *tokenizer* Unigram. + +tokens.", + explain: "C'est exact !", + correct: true + }, + { + text: "Unigram adapte son vocabulaire en minimisant une perte calculée sur l'ensemble du corpus.", + explain: "C'est exact !", + correct: true + }, + { + text: "Unigram adapte son vocabulaire en conservant les sous-mots les plus fréquents.", + explain: "Non, cet incorrect." + }, + { + text: "Unigram segmente les mots en sous-mots en trouvant la segmentation la plus probable en tokens, selon le modèle.", + explain: "C'est exact !", + correct: true + }, + { + text: "Unigram décompose les mots en sous-mots en les divisant en caractères puis en appliquant les règles de fusion.", + explain: "Non, c'est le fonctionnement d'un autre algorithme de tokenization." + }, + ]} +/> diff --git a/chapters/fr/chapter6/2.mdx b/chapters/fr/chapter6/2.mdx new file mode 100644 index 000000000..6168820ae --- /dev/null +++ b/chapters/fr/chapter6/2.mdx @@ -0,0 +1,265 @@ +# Entraîner un nouveau *tokenizer* à partir d'un ancien + + + +Si un modèle de langue n'est pas disponible dans la langue qui vous intéresse ou si votre corpus est très différent de celui sur lequel votre modèle de langue a été entraîné, vous voudrez très probablement réentraîner le modèle à partir de zéro en utilisant un *tokenizer* adapté à vos données. Pour ce faire, vous devrez entraîner un nouveau *tokenizer* sur votre ensemble de données. Mais qu'est-ce que cela signifie exactement ? Lorsque nous avons examiné pour la première fois les *tokenizers* dans le [Chapitre 2](/course/fr/chapter2), nous avons vu que la plupart des *transformers* utilisent un _algorithme de tokenisation des sous-mots_. Pour identifier les sous-mots qui sont intéressants et qui apparaissent le plus fréquemment dans le corpus en question, le *tokenizer* doit examiner attentivement tous les textes du corpus -- un processus que nous appelons *entraînement*. Les règles exactes qui régissent cet apprentissage dépendent du type de *tokenizer* utilisé, et nous passerons en revue les trois principaux algorithmes plus loin dans ce chapitre. + + + + + +⚠️ Entraîner un *tokenizer* n'est pas la même chose qu'entraîner un modèle ! L'entraînement du modèle utilise la descente de gradient stochastique pour réduire un peu plus la perte à chaque batch. Il est aléatoire par nature (ce qui signifie que vous devez définir des graines pour obtenir les mêmes résultats lorsque vous effectuez deux fois le même entraînement). Entraîner un *tokenizer* est un processus statistique qui tente d'identifier les meilleurs sous-mots à choisir pour un corpus donné, et les règles exactes utilisées pour les choisir dépendent de l'algorithme de tokénisation. C'est un processus déterministe, ce qui signifie que vous obtenez toujours les mêmes résultats lorsque vous vous entraînez avec le même algorithme sur le même corpus. + + + + +## Assemblage d'un corpus + +Il y a une API très simple dans 🤗 *Transformers* que vous pouvez utiliser pour entraîner un nouveau *tokenizer* avec les mêmes caractéristiques qu'un existant : `AutoTokenizer.train_new_from_iterator()`. Pour voir cela en action, disons que nous voulons entraîner GPT-2 à partir de zéro, mais dans une langue autre que l'anglais. Notre première tâche sera de rassembler des batchs de données dans cette langue dans un corpus d'entraînement. Pour fournir des exemples que tout le monde pourra comprendre, nous n'utiliserons pas ici une langue comme le russe ou le chinois, mais plutôt une langue anglaise spécialisée : le code Python. + +La bibliothèque [🤗 *Datasets*](https://github.com/huggingface/datasets) peut nous aider à assembler un corpus de code source Python. Nous allons utiliser la fonction habituelle `load_dataset()` pour télécharger et mettre en cache le jeu de données [CodeSearchNet](https://huggingface.co/datasets/code_search_net). Ce jeu de données a été créé pour le [CodeSearchNet challenge](https://wandb.ai/github/CodeSearchNet/benchmark) et contient des millions de fonctions provenant de bibliothèques open source sur GitHub dans plusieurs langages de programmation. Ici, nous allons charger la partie Python de ce jeu de données : + +```py +from datasets import load_dataset + +# Le chargement peut prendre quelques minutes, alors prenez un café ou un thé pendant que vous attendez ! +raw_datasets = load_dataset("code_search_net", "python") +``` + +Nous pouvons jeter un coup d'œil à la répartition dans le jeu d'entraînement pour voir à quelles colonnes nous avons accès : + +```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 +}) +``` + +Nous pouvons voir que le jeu de données sépare les chaînes de documents du code et suggère une tokenization des deux. Ici, nous utiliserons simplement la colonne `whole_func_string` pour entraîner notre *tokenizer*. Nous pouvons regarder un exemple d'une de ces fonctions en indexant dans le split `train` : + +```py +print(raw_datasets["train"][123456]["whole_func_string"]) +``` + +qui devrait afficher ce qui suit : + +```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) +``` + +La première chose à faire est de transformer l'ensemble de données en un _itérateur_ de listes de textes -- par exemple, une liste de listes de textes. L'utilisation de listes de textes permettra à notre *tokenizer* d'aller plus vite (en s'entraînant sur des lots de textes au lieu de traiter des textes individuels un par un), et il doit s'agir d'un itérateur si nous voulons éviter d'avoir tout en mémoire en même temps. Si votre corpus est énorme, vous voudrez profiter du fait que 🤗 *Datasets* ne charge pas tout en RAM mais stocke les éléments du jeu de données sur le disque. + +Faire ce qui suit créerait une liste de listes de 1 000 textes chacune, mais chargerait tout en mémoire : + + +```py +# Ne décommentez pas la ligne suivante à moins que votre jeu de données soit petit ! +# training_corpus = [raw_datasets["train"][i: i + 1000]["whole_func_string"] for i in range(0, len(raw_datasets["train"]), 1000)] +``` + +En utilisant un générateur Python, nous pouvons éviter que Python ne charge quoi que ce soit en mémoire jusqu'à ce que cela soit réellement nécessaire. Pour créer un tel générateur, il suffit de remplacer les crochets par des parenthèses : + +```py +training_corpus = ( + raw_datasets["train"][i : i + 1000]["whole_func_string"] + for i in range(0, len(raw_datasets["train"]), 1000) +) +``` + +Cette ligne de code ne récupère aucun élément de l'ensemble de données ; elle crée simplement un objet que vous pouvez utiliser dans une boucle `for` Python. Les textes ne seront chargés que lorsque vous en aurez besoin (c'est-à-dire lorsque vous serez à l'étape de la boucle `for` qui les requiert), et seulement 1 000 textes à la fois seront chargés. De cette façon, vous n'épuiserez pas toute votre mémoire, même si vous traitez un énorme ensemble de données. + +Le problème avec un objet générateur est qu'il ne peut être utilisé qu'une seule fois. Ainsi, au lieu que cet objet nous donne deux fois la liste des 10 premiers chiffres : + + +```py +gen = (i for i in range(10)) +print(list(gen)) +print(list(gen)) +``` + +on les reçoit une fois et ensuite une liste vide : + +```python out +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +[] +``` + +C'est pourquoi nous définissons une fonction qui renvoie un générateur à la place : + +```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() +``` + +Vous pouvez également définir votre générateur à l'intérieur d'une boucle `for` en utilisant l'instruction `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"] +``` + +qui produira exactement le même générateur que précédemment, mais vous permet d'utiliser une logique plus complexe que celle que vous pouvez utiliser dans une compréhension de liste. + +## Entraînement d'un nouveau *tokenizer*. + +Maintenant que nous avons notre corpus sous la forme d'un itérateur de lots de textes, nous sommes prêts à entraîner un nouveau *tokenizer*. Pour ce faire, nous devons d'abord charger le *tokenizer* que nous voulons coupler avec notre modèle (ici, GPT-2) : + + +```py +from transformers import AutoTokenizer + +old_tokenizer = AutoTokenizer.from_pretrained("gpt2") +``` + +Même si nous allons entraîner un nouveau *tokenizer*, c'est une bonne idée de le faire pour éviter de partir entièrement de zéro. De cette façon, nous n'aurons pas à spécifier l'algorithme de tokénisation ou les jetons spéciaux que nous voulons utiliser ; notre nouveau *tokenizer* sera exactement le même que GPT-2, et la seule chose qui changera sera le vocabulaire, qui sera déterminé par l'Entraînement sur notre corpus. + +Voyons d'abord comment ce *tokenizer* traiterait un exemple de fonction : + + +```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'] +``` + +Ce *tokenizer* possède quelques symboles spéciaux, comme `Ġ` et `Ċ`, qui désignent respectivement les espaces et les retours à la ligne. Comme on peut le voir, ce n'est pas très efficace : le *tokenizer* renvoie des jetons individuels pour chaque espace, alors qu'il pourrait regrouper les niveaux d'indentation (puisqu’avoir des ensembles de quatre ou huit espaces va être très courant dans le code). Il divise également le nom de la fonction de façon un peu bizarre, n'étant pas habitué à voir des mots avec le caractère `_`. + +Entraînons un nouveau *tokenizer* et voyons s'il résout ces problèmes. Pour cela, nous allons utiliser la méthode `train_new_from_iterator()` : + + +```py +tokenizer = old_tokenizer.train_new_from_iterator(training_corpus, 52000) +``` + +Cette commande peut prendre un peu de temps si votre corpus est très grand, mais pour ce jeu de données de 1,6 Go de textes, elle est très rapide (1 minute 16 secondes sur un CPU AMD Ryzen 9 3900X avec 12 cœurs). + +Notez que `AutoTokenizer.train_new_from_iterator()` ne fonctionne que si le tokenizer que vous utilisez est un tokenizer « rapide ». Comme vous le verrez dans la section suivante, la bibliothèque 🤗 *Transformers* contient deux types de *tokenizers* : certains sont écrits purement en Python et d'autres (les rapides) sont soutenus par la bibliothèque 🤗 *Tokenizers*, qui est écrite dans le langage de programmation [Rust](https://www.rust-lang.org). Python est le langage le plus souvent utilisé pour les applications de science des données et d'apprentissage profond, mais lorsque quelque chose doit être parallélisé pour être rapide, il doit être écrit dans un autre langage. Par exemple, les multiplications matricielles qui sont au cœur du calcul du modèle sont écrites en CUDA, une bibliothèque C optimisée pour les GPU. + +Entraîner un tout nouveau *tokenizer* en Python pur serait atrocement lent, c'est pourquoi nous avons développé la bibliothèque 🤗 *Tokenizers*. Notez que, tout comme vous n'avez pas eu à apprendre le langage CUDA pour pouvoir exécuter votre modèle sur un lot d'entrées sur un GPU, vous n'aurez pas besoin d'apprendre Rust pour utiliser un *tokenizer* rapide. La bibliothèque 🤗 *Tokenizers* fournit des liaisons Python pour de nombreuses méthodes qui appellent en interne un morceau de code en Rust ; par exemple, pour paralléliser l'entraînement de votre nouveau *tokenizer* ou, comme nous l'avons vu dans le [Chapitre 3](/course/fr/chapter3), la tokenisation d'un lot d'entrées. + +La plupart des *transformers* ont un *tokenizer* rapide disponible (il y a quelques exceptions que vous pouvez vérifier [ici](https://huggingface.co/transformers/#supported-frameworks)), et l'API `AutoTokenizer` sélectionne toujours le *tokenizer* rapide pour vous s'il est disponible. Dans la prochaine section, nous allons jeter un coup d'oeil à certaines des autres caractéristiques spéciales des *tokenizers* rapides, qui seront vraiment utiles pour des tâches comme la classification des *tokens* et la réponse aux questions. Mais avant cela, essayons notre tout nouveau *tokenizer* sur l'exemple précédent : + + +```py +tokens = tokenizer.tokenize(example) +tokens +``` + +```python out +['def', 'Ġadd', '_', 'numbers', '(', 'a', ',', 'Ġb', '):', 'ĊĠĠĠ', 'Ġ"""', 'Add', 'Ġthe', 'Ġtwo', 'Ġnumbers', 'Ġ`', + 'a', '`', 'Ġand', 'Ġ`', 'b', '`."""', 'ĊĠĠĠ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb'] +``` + +Ici, nous voyons à nouveau les symboles spéciaux `Ġ` et `Ċ` qui indiquent les espaces et les retours à la ligne, mais nous pouvons également voir que notre *tokenizer* a appris certains *tokens* qui sont très spécifiques à un corpus de fonctions Python : par exemple, il y a un token `ĊĠĠĠ` qui représente une indentation, et un *token* `Ġ"""` qui représente les trois guillemets qui commencent une docstring. Le *tokenizer* divise également correctement le nom de la fonction sur `_`. Il s'agit d'une représentation assez compacte ; en comparaison, l'utilisation du *tokenizer* en anglais simple sur le même exemple nous donnera une phrase plus longue : + +```py +print(len(tokens)) +print(len(old_tokenizer.tokenize(example))) +``` + +```python out +27 +36 +``` + +Prenons un autre exemple : + +```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 plus du *token* correspondant à une indentation, on peut également voir ici un *token* pour une double indentation : `ĊĠĠĠĠĠĠĠĠĠ`. Les mots spéciaux de Python comme `class`, `init`, `call`, `self`, et `return` sont tous tokenizés comme un seul token, et nous pouvons voir qu'en plus de séparer sur `_` et `.` le tokenizer sépare correctement même les noms en minuscules : `LinearLayer` est tokenizé comme `["ĠLinear", "Layer"]`. + +## Sauvegarde du *tokenizer*. + +Pour être sûr de pouvoir l'utiliser plus tard, nous devons sauvegarder notre nouveau *tokenizer*. Comme pour les modèles, ceci est fait avec la méthode `save_pretrained()` : + + +```py +tokenizer.save_pretrained("code-search-net-tokenizer") +``` + +Cela créera un nouveau dossier nommé *code-search-net-tokenizer*, qui contiendra tous les fichiers dont le *tokenizer* a besoin pour être rechargé. Si vous souhaitez partager ce *tokenizer* avec vos collègues et amis, vous pouvez le télécharger sur le *Hub* en vous connectant à votre compte. Si vous travaillez dans un *notebook*, il existe une fonction pratique pour vous aider à le faire : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Cela affichera un widget où vous pourrez entrer vos identifiants de connexion à Hugging Face. Si vous ne travaillez pas dans un ordinateur portable, tapez simplement la ligne suivante dans votre terminal : + +```bash +huggingface-cli login +``` + +Une fois que vous vous êtes connecté, vous pouvez pousser votre *tokenizer* en exécutant la commande suivante : + +```py +tokenizer.push_to_hub("code-search-net-tokenizer") +``` + +Cela créera un nouveau dépôt dans votre espace de noms avec le nom `code-search-net-tokenizer`, contenant le fichier *tokenizer*. Vous pouvez ensuite charger le *tokenizer* de n'importe où avec la méthode `from_pretrained()` : + +```py +# Remplacez "huggingface-course" ci-dessous par votre espace de nom réel pour utiliser votre propre tokenizer +tokenizer = AutoTokenizer.from_pretrained("huggingface-course/code-search-net-tokenizer") +``` + +Vous êtes maintenant prêt à entraîner un modèle de langue à partir de zéro et à le *finetuner* sur votre tâche ! Nous y reviendrons au [Chapitre 7](/course/fr/chapter7), mais d'abord, dans le reste de ce chapitre, nous allons examiner de plus près les *tokenizers* rapides et explorer en détail ce qui se passe réellement lorsque nous appelons la méthode `train_new_from_iterator()`. \ No newline at end of file diff --git a/chapters/fr/chapter6/3.mdx b/chapters/fr/chapter6/3.mdx new file mode 100644 index 000000000..85c5c2d43 --- /dev/null +++ b/chapters/fr/chapter6/3.mdx @@ -0,0 +1,475 @@ + + +# Pouvoirs spéciaux des *tokenizers* rapides + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Dans cette section, nous allons examiner de plus près les capacités des tokenizers dans 🤗 *Transformers*. + Jusqu'à présent, nous ne les avons utilisés que pour *tokeniser* les entrées ou décoder les identifiants pour les retranscrire en texte, mais les *tokenizers*, surtout ceux soutenus par la bibliothèque 🤗 *Tokenizers*, peuvent faire beaucoup plus. Pour illustrer ces fonctionnalités supplémentaires, nous allons explorer comment reproduire les résultats des pipelines `token-classification` (que nous avons appelé `ner`) et `question-answering` que nous avons rencontrés pour la première fois dans [Chapter 1](/course/fr/chapter1). + + + +Dans la discussion qui suit, nous ferons souvent la distinction entre les *tokenizers* « lents » et « rapides ». Les *tokenizers* lents sont ceux écrits en Python à l'intérieur de la bibliothèque 🤗 *Transformers*, tandis que les versions rapides sont celles fournies par 🤗 *Tokenizers*, qui sont écrites en Rust. Si vous vous souvenez du tableau du [Chapitre 5](/course/fr/chapter5/3) qui indiquait combien de temps il fallait à un *tokenizer* rapide et à un *tokenizer* lent pour tokeniser le jeu de données Drug Review, vous devriez avoir une idée de la raison pour laquelle nous les appelons rapides et lents : + + | *Tokenizer* rapide | *Tokenizer* lent +:--------------:|:--------------:|:-------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s + + + +⚠️ Lors de la tokenisation d'une seule phrase, vous ne verrez pas toujours une différence de vitesse entre les versions lente et rapide d'un même *tokenizer*. En fait, la version rapide peut même être plus lente ! Ce n'est que lorsque vous tokenisez des batchs de textes en parallèle et en même temps que vous pourrez voir clairement la différence. + + + +## *BatchEncoding* + + + +La sortie d'un *tokenizer* n'est pas un simple dictionnaire Python. Ce que nous obtenons est en fait un objet spécial `BatchEncoding`. C'est une sous-classe d'un dictionnaire (c'est pourquoi nous avons pu indexer ce résultat sans problème auparavant), mais avec des méthodes supplémentaires qui sont principalement utilisées par les *tokenizers* rapides. + +En plus de leurs capacités de parallélisation, la fonctionnalité clé des *tokenizers* rapides est qu'ils gardent toujours la trace de l'étendue originale des textes d'où proviennent les *tokens* finaux, une fonctionnalité que nous appelons *mapping offset*. Cela permet de débloquer des fonctionnalités telles que le mappage de chaque mot aux *tokens* qu'il a générés ou le mappage de chaque caractère du texte original au *token* qu'il contient, et vice versa. + +Prenons un exemple : + +```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." # "Je m'appelle Sylvain et je travaille chez Hugging Face à Brooklyn." +encoding = tokenizer(example) +print(type(encoding)) +``` + +Comme mentionné précédemment, nous obtenons un objet `BatchEncoding` dans la sortie du *tokenizer* : + +```python out + +``` + +Puisque la classe `AutoTokenizer` choisit un *tokenizer* rapide par défaut, nous pouvons utiliser les méthodes supplémentaires que cet objet `BatchEncoding` fournit. Nous avons deux façons de vérifier si notre *tokenizer* est rapide ou lent. Nous pouvons soit vérifier l'attribut `is_fast` du *tokenizer* : + +```python +tokenizer.is_fast +``` + +```python out +True +``` + +ou vérifiez le même attribut de notre `encoding` : + +```python +encoding.is_fast +``` + +```python out +True +``` + +Voyons ce qu'un *tokenizer* rapide nous permet de faire. Tout d'abord, nous pouvons accéder aux *tokens* sans avoir à reconvertir les ID en *tokens* : + +```py +encoding.tokens() +``` + +```python out +['[CLS]', 'My', 'name', 'is', 'S', '##yl', '##va', '##in', 'and', 'I', 'work', 'at', 'Hu', '##gging', 'Face', 'in', + 'Brooklyn', '.', '[SEP]'] +``` + +Dans ce cas, le *token* à l'index 5 est `##yl`, qui fait partie du mot « Sylvain » dans la phrase originale. Nous pouvons également utiliser la méthode `word_ids()` pour obtenir l'index du mot dont provient chaque *token* : + +```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] +``` + +On peut voir que les *tokens* spéciaux du tokenizer `[CLS]` et `[SEP]` sont mis en correspondance avec `None`, puis chaque *token* est mis en correspondance avec le mot dont il provient. Ceci est particulièrement utile pour déterminer si un *token* est au début d'un mot ou si deux *tokens* sont dans le même mot. Nous pourrions nous appuyer sur le préfixe `##` pour cela, mais il ne fonctionne que pour les *tokenizers* de type BERT. Cette méthode fonctionne pour n'importe quel type de *tokenizer*, du moment qu'il est rapide. Dans le chapitre suivant, nous verrons comment utiliser cette capacité pour appliquer correctement les étiquettes que nous avons pour chaque mot aux *tokens* dans des tâches comme la reconnaissance d'entités nommées (NER) et le marquage POS (Part-of-speech). Nous pouvons également l'utiliser pour masquer tous les *tokens* provenant du même mot dans la modélisation du langage masqué (une technique appelée _whole word masking_). + + + +La notion de ce qu'est un mot est compliquée. Par exemple, est-ce que « I will » (contraction de « I will ») compte pour un ou deux mots ? Cela dépend en fait du *tokenizer* et de l'opération de pré-tokénisation qu'il applique. Certains *tokenizer* se contentent de séparer les espaces et considèrent donc qu'il s'agit d'un seul mot. D'autres utilisent la ponctuation en plus des espaces et considèrent donc qu'il s'agit de deux mots. + +✏️ **Essayez !** Créez un *tokenizer* à partir des points de contrôle `bert-base-cased` et `roberta-base` et tokenisez « 81s » avec eux. Qu'observez-vous ? Quels sont les identifiants des mots ? + + + +De même, il existe une méthode `sentence_ids()` que nous pouvons utiliser pour associer un token à la phrase dont il provient (bien que dans ce cas, le `token_type_ids` retourné par le *tokenizer* peut nous donner la même information). + +Enfin, nous pouvons faire correspondre n'importe quel mot ou jeton aux caractères du texte d'origine, et vice versa, grâce aux méthodes `word_to_chars()` ou `token_to_chars()` et `char_to_word()` ou `char_to_token()`. Par exemple, la méthode `word_ids()` nous a dit que `##yl` fait partie du mot à l'indice 3, mais de quel mot s'agit-il dans la phrase ? Nous pouvons le découvrir comme ceci : + +```py +start, end = encoding.word_to_chars(3) +example[start:end] +``` + +```python out +Sylvain +``` + +Comme nous l'avons mentionné précédemment, tout ceci est rendu possible par le fait que le tokenizer rapide garde la trace de la partie du texte d'où provient chaque *token dans une liste de *offsets*. Pour illustrer leur utilisation, nous allons maintenant vous montrer comment reproduire manuellement les résultats du pipeline `token-classification`. + + + +✏️ **Essayez !** Créez votre propre texte d'exemple et voyez si vous pouvez comprendre quels *tokens* sont associés à l'ID du mot, et aussi comment extraire les portées de caractères pour un seul mot. Pour obtenir des points bonus, essayez d'utiliser deux phrases en entrée et voyez si les identifiants de phrase ont un sens pour vous. + + + +## A l'intérieur du pipeline `token-classification` + +Dans le [Chapitre 1](/course/fr/chapter1), nous avons eu un premier aperçu de l'application de NER (où la tâche est d'identifier les parties du texte qui correspondent à des entités telles que des personnes, des lieux ou des organisations) avec la fonction 🤗 *Transformers* `pipeline()`. Puis, dans [Chapitre 2](/course/fr/chapter2), nous avons vu comment un pipeline regroupe les trois étapes nécessaires pour obtenir les prédictions à partir d'un texte brut : la tokenisation, le passage des entrées dans le modèle et le post-traitement. Les deux premières étapes du pipeline de `token-classification` sont les mêmes que dans tout autre pipeline, mais le post-traitement est un peu plus complexe. Voyons comment ! + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +### Obtenir les résultats de base avec le pipeline + +Tout d'abord, prenons un pipeline de classification de tokens afin d'obtenir des résultats à comparer manuellement. Le modèle utilisé par défaut est [`dbmdz/bert-large-cased-finetuned-conll03-english`](https://huggingface.co/dbmdz/bert-large-cased-finetuned-conll03-english). Il effectue un NER sur les phrases : + +```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}] +``` + +Le modèle a correctement identifié chaque token généré par « Sylvain » comme une personne, chaque token généré par « Hugging Face » comme une organisation, et le *token* « Brooklyn » comme un lieu. Nous pouvons également demander au pipeline de regrouper les tokens qui correspondent à la même entité : + +```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 `aggregation_strategy` choisie va changer les scores calculés pour chaque entité groupée. Avec `"simple"` le score est juste la moyenne des scores de chaque *token* dans l'entité donnée : par exemple, le score de « Sylvain » est la moyenne des scores que nous avons vu dans l'exemple précédent pour les tokens `S`, `##yl`, `##va`, et `##in`. D'autres stratégies sont disponibles : + +- `"first"`, où le score de chaque entité est le score du premier token de cette entité (donc pour « Sylvain » ce serait 0.993828, le score du token `S`) +- `"max"`, où le score de chaque entité est le score maximal des tokens de cette entité (ainsi, pour « Hugging Face », le score de « Face » serait de 0,98879766). +- `"moyenne"`, où le score de chaque entité est la moyenne des scores des mots qui composent cette entité (ainsi, pour « Sylvain », +il n'y aurait pas de différence avec la stratégie `"simple"`, mais "Étreinte du visage" aurait un score de 0,9819, la moyenne des scores de « Hugging Face », 0,975, et « Face », 0,98879). + +Voyons maintenant comment obtenir ces résultats sans utiliser la fonction `pipeline()` ! + +### Des entrées aux prédictions + +{#if fw === 'pt'} + +D'abord, nous devons tokeniser notre entrée et la faire passer dans le modèle. Cela se fait exactement comme dans le [Chapitre 2](/course/frchapter2). Nous instancions le *tokenizer* et le modèle en utilisant les classes `TFAutoXxx` et les utilisons ensuite dans notre exemple : + +```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) +``` + +Puisque nous utilisons `AutoModelForTokenClassification` ici, nous obtenons un ensemble de logits pour chaque token dans la séquence d'entrée : + +```py +print(inputs["input_ids"].shape) +print(outputs.logits.shape) +``` + +```python out +torch.Size([1, 19]) +torch.Size([1, 19, 9]) +``` + +{:else} + +D'abord, nous devons tokeniser notre entrée et la faire passer dans le modèle. Cela se fait exactement comme dans le [Chapitre 2](/course/frchapter2). Nous instancions le *tokenizer* et le modèle en utilisant les classes `TFAutoXxx` et les utilisons ensuite dans notre exemple : + +```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) +``` + +Puisque nous utilisons `TFAutoModelForTokenClassification` ici, nous obtenons un ensemble de logits pour chaque *token* dans la séquence d'entrée : + +```py +print(inputs["input_ids"].shape) +print(outputs.logits.shape) +``` + +```python out +(1, 19) +(1, 19, 9) +``` + +{/if} + +Nous avons un batch avec 1 séquence de 19 *tokens* et le modèle a 9 étiquettes différentes, donc la sortie du modèle a une forme de 1 x 19 x 9. Comme pour le pipeline de classification de texte, nous utilisons une fonction softmax pour convertir ces logits en probabilités, et nous prenons l'argmax pour obtenir des prédictions (notez que nous pouvons prendre l'argmax sur les logits parce que la softmax ne change pas l'ordre) : + +{#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] +``` + +L'attribut `model.config.id2label` contient la correspondance entre les index et les étiquettes que nous pouvons utiliser pour donner un sens aux prédictions : + +```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'} +``` + +Comme nous l'avons vu précédemment, il y a 9 étiquettes : `O` est le label pour les *tokens* qui ne sont dans aucune entité nommée (il signifie *outside*) et nous avons ensuite deux labels pour chaque type d'entité (divers, personne, organisation, et lieu). L'étiquette `B-XXX` indique que le *token* est au début d'une entité `XXX` et l'étiquette `I-XXX` indique que le *token* est à l'intérieur de l'entité `XXX`. Par exemple, dans l'exemple actuel, nous nous attendons à ce que notre modèle classe le *token* `S` comme `B-PER` (début d'une entité personne) et les *tokens* `##yl`, `##va` et `##in` comme `I-PER` (à l'intérieur d'une entité personne). + +Vous pourriez penser que le modèle s'est trompé dans ce cas, car il a attribué l'étiquette `I-PER` à ces quatre *tokens*, mais ce n'est pas tout à fait vrai. Il existe en fait deux formats pour ces étiquettes `B-` et `I-` : *IOB1* et *IOB2*. Le format IOB2 (en rose ci-dessous) est celui que nous avons introduit alors que dans le format IOB1 (en bleu), les étiquettes commençant par `B-` ne sont jamais utilisées que pour séparer deux entités adjacentes du même type. Le modèle que nous utilisons a été *finetuné* sur un jeu de données utilisant ce format, c'est pourquoi il attribue le label `I-PER` au *token* `S`. + +
+IOB1 vs IOB2 format + +
+ +Avec cette carte, nous sommes prêts à reproduire (presque entièrement) les résultats du premier pipeline. Nous pouvons simplement récupérer le score et le label de chaque *token* qui n'a pas été classé comme `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'}] +``` + +C'est très similaire à ce que nous avions avant, à une exception près : le pipeline nous a aussi donné des informations sur le `début` et la `fin` de chaque entité dans la phrase originale. C'est là que notre mappage de décalage va entrer en jeu. Pour obtenir les décalages, il suffit de définir `return_offsets_mapping=True` lorsque nous appliquons le *tokenizer* à nos entrées : + +```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)] +``` + +Chaque *tuple* est l'empan de texte correspondant à chaque token, où `(0, 0)` est réservé aux *tokens* spéciaux. Nous avons vu précédemment que le token à l'index 5 est `##yl`, qui a `(12, 14)` comme *offsets* ici. Si on prend la tranche correspondante dans notre exemple : + + +```py +example[12:14] +``` + +nous obtenons le bon espace de texte sans le `##` : + +```python out +yl +``` + +En utilisant cela, nous pouvons maintenant compléter les résultats précédents : + +```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}] +``` + +C'est la même chose que ce que nous avons obtenu avec le premier pipeline ! + +### Regroupement des entités + +L'utilisation des offsets pour déterminer les clés de début et de fin pour chaque entité est pratique mais cette information n'est pas strictement nécessaire. Cependant, lorsque nous voulons regrouper les entités, les offsets nous épargnent un batch de code compliqué. Par exemple, si nous voulions regrouper les *tokens* `Hu`, `##gging`, et `Face`, nous pourrions établir des règles spéciales disant que les deux premiers devraient être attachés tout en enlevant le `##`, et le `Face` devrait être ajouté avec un espace puisqu'il ne commence pas par `##` mais cela ne fonctionnerait que pour ce type particulier de *tokenizer*. Il faudrait écrire un autre ensemble de règles pour un *tokenizer* de type SentencePiece ou Byte-Pair-Encoding (voir plus loin dans ce chapitre). + +Avec les *offsets*, tout ce code personnalisé disparaît : il suffit de prendre l'intervalle du texte original qui commence par le premier *token* et se termine par le dernier *token*. Ainsi, dans le cas des tokens `Hu`, `##gging`, et `Face`, nous devrions commencer au caractère 33 (le début de `Hu`) et finir avant le caractère 45 (la fin de `Face`) : + +```py +example[33:45] +``` + +```python out +Hugging Face +``` + +Pour écrire le code qui post-traite les prédictions tout en regroupant les entités, nous regrouperons les entités qui sont consécutives et étiquetées avec `I-XXX`, à l'exception de la première, qui peut être étiquetée comme `B-XXX` ou `I-XXX` (ainsi, nous arrêtons de regrouper une entité lorsque nous obtenons un `O`, un nouveau type d'entité, ou un `B-XXX` qui nous indique qu'une entité du même type commence) : + +```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": + # Enlever le B- ou le I- + label = label[2:] + start, _ = offsets[idx] + + # Récupérer tous les tokens étiquetés avec I-label + 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 + + # Le score est la moyenne de tous les scores des tokens dans cette entité groupée. + 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) +``` + +Et nous obtenons les mêmes résultats qu'avec notre deuxième 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}] +``` + +Un autre exemple de tâche où ces décalages sont extrêmement utiles est la réponse aux questions. Plonger dans ce pipeline, ce que nous ferons dans la section suivante, nous permettra également de jeter un coup d'œil à une dernière caractéristique des *tokenizers* de la bibliothèque 🤗 *Transformers* : la gestion des tokens qui débordent lorsque nous tronquons une entrée à une longueur donnée. diff --git a/chapters/fr/chapter6/3b.mdx b/chapters/fr/chapter6/3b.mdx new file mode 100644 index 000000000..e3c67b105 --- /dev/null +++ b/chapters/fr/chapter6/3b.mdx @@ -0,0 +1,713 @@ + + +# *Tokenizer* rapide dans le pipeline de QA + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Nous allons maintenant nous plonger dans le pipeline de `question-answering` et voir comment exploiter les *offsets* pour extraire du contexte la réponse à la question posée, un peu comme nous l'avons fait pour les entités groupées dans la section précédente. 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'} + + + +{:else} + + + +{/if} + +## Utilisation du pipeline de `question-answering`. + +Comme nous l'avons vu dans le [Chapitre 1](/course/fr/chapter1), nous pouvons utiliser le pipeline de `question-answering` comme ceci pour obtenir la réponse à une question : + +```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. +""" +# 🤗 Transformers s'appuie sur les trois bibliothèques d'apprentissage profond les plus populaires (Jax, PyTorch et TensorFlow) avec une intégration transparente entre elles. C'est simple d'entraîner vos modèles avec l'une avant de les charger pour l'inférence avec l'autre. +question = "Which deep learning libraries back 🤗 Transformers?" # Quelles bibliothèques d'apprentissage profond derrière 🤗 Transformers ? +question_answerer(question=question, context=context) +``` + +```python out +{'score': 0.97773, + 'start': 78, + 'end': 105, + 'answer': 'Jax, PyTorch and TensorFlow'} +``` + +Contrairement aux autres pipelines, qui ne peuvent pas tronquer et diviser les textes dont la longueur est supérieure à la longueur maximale acceptée par le modèle (et qui peuvent donc manquer des informations à la fin d'un document), ce pipeline peut traiter des contextes très longs et retournera la réponse à la question même si elle se trouve à la fin : + +```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. +""" + +long_context - fr = """ +🤗 Transformers : l'état de l'art du NLP + +🤗 Transformers fournit des milliers de modèles pré-entraînés pour effectuer des tâches sur des textes telles que la classification, l'extraction d'informations, +la réponse à des questions, le résumé de textes, la traduction, la génération de texte et plus encore dans plus de 100 langues. +Son objectif est de rendre le traitement automatique des langues de pointe plus facile à utiliser pour tout le monde. + +🤗 Transformers fournit des API permettant de télécharger et d'utiliser rapidement ces modèles pré-entraînés sur un texte donné, de les affiner sur vos propres ensembles de données et de les partager avec la communauté sur notre site Web. +puis de les partager avec la communauté sur notre hub de modèles. En même temps, chaque module python définissant une architecture est entièrement autonome et peut être modifié pour permettre des expériences de recherche rapides. +peut être modifié pour permettre des expériences de recherche rapides. + +Pourquoi devrais-je utiliser des transformateurs ? + +1. Des modèles de pointe faciles à utiliser : + - Haute performance sur les tâches NLU et NLG. + - Faible barrière à l'entrée pour les éducateurs et les praticiens. + - Peu d'abstractions pour l'utilisateur avec seulement trois classes à apprendre. + - Une API unifiée pour utiliser tous nos modèles pré-entraînés. + - Des coûts de calcul plus faibles, une empreinte carbone réduite : + +2. Les chercheurs peuvent partager les modèles formés au lieu de toujours les reformer. + - Les praticiens peuvent réduire le temps de calcul et les coûts de production. + - Des dizaines d'architectures avec plus de 10 000 modèles pré-formés, certains dans plus de 100 langues. + +3. Choisissez le cadre approprié pour chaque étape de la vie d'un modèle : + - Entraînez des modèles de pointe en 3 lignes de code. + - Déplacez un seul modèle entre les frameworks TF2.0/PyTorch à volonté. + - Choisissez de manière transparente le bon framework pour l'entraînement, l'évaluation et la production. + +4. Adaptez facilement un modèle ou un exemple à vos besoins : + - Nous fournissons des exemples pour chaque architecture afin de reproduire les résultats publiés par ses auteurs originaux. + - Les éléments internes des modèles sont exposés de manière aussi cohérente que possible. + - Les fichiers de modèles peuvent être utilisés indépendamment de la bibliothèque pour des expériences rapides. + +🤗 Transformers s'appuie sur les trois bibliothèques d'apprentissage profond les plus populaires (Jax, PyTorch et TensorFlow) avec une intégration parfaite +entre elles. Il est simple d'entraîner vos modèles avec l'une avant de les charger pour l'inférence avec l'autre. +""" +question_answerer(question=question, context=long_context) +``` + +```python out +{'score': 0.97149, + 'start': 1892, + 'end': 1919, + 'answer': 'Jax, PyTorch and TensorFlow'} +``` + +Voyons comment il fait tout cela ! + +### Utilisation d'un modèle pour répondre à des questions + +Comme avec n'importe quel autre pipeline, nous commençons par tokeniser notre entrée et l'envoyons ensuite à travers le modèle. Le point de contrôle utilisé par défaut pour le pipeline de `question-answering` est [`distilbert-base-cased-distilled-squad`](https://huggingface.co/distilbert-base-cased-distilled-squad) (le « squad » dans le nom vient du jeu de données sur lequel le modèle a été *finetuné*, nous parlerons plus du jeu de données SQuAD dans le [Chapitre 7](/course/fr/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} + +Notez que nous tokenizons la question et le contexte comme une paire, la question en premier. + +
+An example of tokenization of question and context + +
+ +Les modèles de réponse aux questions fonctionnent un peu différemment des modèles que nous avons vus jusqu'à présent. En utilisant l'image ci-dessus comme exemple, le modèle a été entraîné à prédire l'index du *token* de début de la réponse (ici 21) et l'index du *token* où la réponse se termine (ici 24). C'est pourquoi ces modèles ne retournent pas un tenseur de logits mais deux : un pour les logits correspondant au *token* de début de la réponse, et un pour les logits correspondant au *token* de fin de la réponse. Puisque dans ce cas nous n'avons qu'une seule entrée contenant 66 *tokens*, nous obtenons : + +```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} + +Pour convertir ces logits en probabilités, nous allons appliquer une fonction softmax. Mais avant cela, nous devons nous assurer de masquer les indices qui ne font pas partie du contexte. Notre entrée est `[CLS] question [SEP] contexte [SEP]`, donc nous devons masquer les *tokens* de la question ainsi que le *token* `[SEP]`. Nous garderons cependant le *token* `[CLS]`, car certains modèles l'utilisent pour indiquer que la réponse n'est pas dans le contexte. + +Puisque nous appliquerons une fonction softmax par la suite, il nous suffit de remplacer les logits que nous voulons masquer par un grand nombre négatif. Ici, nous utilisons `-10000` : + +{#if fw === 'pt'} + +```py +import torch + +sequence_ids = inputs.sequence_ids() +# Masque tout, sauf les tokens du contexte +mask = [i != 1 for i in sequence_ids] +# Démasquer le token [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() +# Masque tout, sauf les tokens du contexte +mask = [i != 1 for i in sequence_ids] +# Démasquer le token [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} + +Maintenant que nous avons correctement masqué les logits correspondant aux positions que nous ne voulons pas prédire, nous pouvons appliquer 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} + +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 : + +$$\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`. + +Calculons d'abord tous les produits possibles : + +```py +scores = start_probabilities[:, None] * end_probabilities[None, :] +``` + +{#if fw === 'pt'} + +Ensuite, nous masquerons les valeurs où `start_index > end_index` en les mettant à `0` (les autres probabilités sont toutes des nombres positifs). La fonction `torch.triu()` renvoie la partie triangulaire supérieure du tenseur 2D passé en argument, elle fera donc ce masquage pour nous : + +```py +scores = torch.triu(scores) +``` + +{:else} + +Ensuite, nous masquerons les valeurs où `start_index > end_index` en les mettant à `0` (les autres probabilités sont toutes des nombres positifs). La fonction `np.triu()` renvoie la partie triangulaire supérieure du tenseur 2D passé en argument, elle fera donc ce masquage pour nous : + +```py +scores = np.triu(scores) +``` + +{/if} + +Il ne nous reste plus qu'à obtenir l'indice du maximum. Puisque PyTorch retournera l'index dans le tenseur aplati, nous devons utiliser les opérations division plancher `//` et modulus `%` pour obtenir le `start_index` et le `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]) +``` + +Nous n'avons pas encore tout à fait terminé, mais au moins nous avons déjà le score correct pour la réponse (vous pouvez le vérifier en le comparant au premier résultat de la section précédente) : + +```python out +0.97773 +``` + + + +✏️ **Essayez !** Calculez les indices de début et de fin pour les cinq réponses les plus probables. + + + +Nous avons les `start_index` et `end_index` de la réponse en termes de *tokens*, donc maintenant nous devons juste convertir en indices de caractères dans le contexte. C'est là que les *offsets* seront super utiles. Nous pouvons les saisir et les utiliser comme nous l'avons fait dans la tâche de classification des *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] +``` + +Il ne nous reste plus qu'à tout formater pour obtenir notre résultat : + +```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} +``` + +Super ! C'est la même chose que dans notre premier exemple ! + + + +✏️ **Essayez !** Utilisez les meilleurs scores que vous avez calculés précédemment pour afficher les cinq réponses les plus probables. Pour vérifier vos résultats, retournez au premier pipeline et passez dans `top_k=5` lorsque vous l'appelez. + + + +## Gestion des contextes longs + +Si nous essayons de tokeniser la question et le long contexte que nous avons utilisé comme exemple précédemment, nous obtiendrons un nombre de *tokens* supérieur à la longueur maximale utilisée dans le pipeline `question-answering` (qui est de 384) : + +```py +inputs = tokenizer(question, long_context) +print(len(inputs["input_ids"])) +``` + +```python out +461 +``` + +Nous devrons donc tronquer nos entrées à cette longueur maximale. Il y a plusieurs façons de le faire, mais nous ne voulons pas tronquer la question, seulement le contexte. Puisque le contexte est la deuxième phrase, nous utiliserons la stratégie de troncature `"only_second"`. Le problème qui se pose alors est que la réponse à la question peut ne pas se trouver dans le contexte tronqué. Ici, par exemple, nous avons choisi une question dont la réponse se trouve vers la fin du contexte, et lorsque nous la tronquons, cette réponse n'est pas présente : + +```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] +""" + +""" +[CLS] Quelles sont les bibliothèques d'apprentissage profond qui soutiennent [UNK] Transformers ? [SEP] [UNK] Transformers : l'état de l'art du NLP + +[UNK] Transformers fournit des milliers de modèles pré-entraînés pour effectuer des tâches sur des textes telles que la classification, l'extraction d'informations, la réponse à des questions, le résumé, la traduction, la génération de textes, etc, +la réponse à des questions, le résumé, la traduction, la génération de texte et plus encore dans plus de 100 langues. +Son objectif est de rendre le traitement automatique des langues de pointe plus facile à utiliser pour tous. + +Transformers [UNK] fournit des API permettant de télécharger et d'utiliser rapidement ces modèles pré-entraînés sur un texte donné, de les affiner sur vos propres ensembles de données et de les partager avec la communauté sur notre site Web. +puis de les partager avec la communauté sur notre hub de modèles. En même temps, chaque module python définissant une architecture est entièrement autonome et peut être modifié pour permettre des expériences de recherche rapides. +peut être modifié pour permettre des expériences de recherche rapides. + +Pourquoi devrais-je utiliser des transformateurs ? + +1. Des modèles de pointe faciles à utiliser : + - Haute performance sur les tâches NLU et NLG. + - Faible barrière à l'entrée pour les éducateurs et les praticiens. + - Peu d'abstractions pour l'utilisateur avec seulement trois classes à apprendre. + - Une API unifiée pour utiliser tous nos modèles pré-entraînés. + - Des coûts de calcul plus faibles, une empreinte carbone réduite : + +2. Les chercheurs peuvent partager les modèles formés au lieu de toujours les reformer. + - Les praticiens peuvent réduire le temps de calcul et les coûts de production. + - Des dizaines d'architectures avec plus de 10 000 modèles pré-formés, certains dans plus de 100 langues. + +3. Choisissez le cadre approprié pour chaque étape de la vie d'un modèle : + - Entraînez des modèles de pointe en 3 lignes de code. + - Déplacez un seul modèle entre les frameworks TF2.0/PyTorch à volonté. + - Choisissez de manière transparente le bon framework pour l'entraînement, l'évaluation et la production. + +4. Adaptez facilement un modèle ou un exemple à vos besoins : + - Nous fournissons des exemples pour chaque architecture afin de reproduire les résultats publiés par ses auteurs originaux. + - Modèle interne [SEP] +""" +``` + +Cela signifie que le modèle aura du mal à trouver la bonne réponse. Pour résoudre ce problème, le pipeline de `question-answering` nous permet de diviser le contexte en morceaux plus petits, en spécifiant la longueur maximale. Pour s'assurer que nous ne divisons pas le contexte exactement au mauvais endroit pour permettre de trouver la réponse, il inclut également un certain chevauchement entre les morceaux. + +Nous pouvons demander au *tokenizer* (rapide ou lent) de le faire pour nous en ajoutant `return_overflowing_tokens=True`, et nous pouvons spécifier le chevauchement que nous voulons avec l'argument `stride`. Voici un exemple, en utilisant une phrase plus petite : + +```py +sentence = "This sentence is not too long but we are going to split it anyway." # "Cette phrase n'est pas trop longue mais nous allons la diviser quand même." +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]' +``` + +Comme on peut le voir, la phrase a été découpée en morceaux de telle sorte que chaque entrée dans `inputs["input_ids"]` a au maximum 6 *tokens* (il faudrait ajouter du *padding* pour que la dernière entrée ait la même taille que les autres) et il y a un chevauchement de 2 *tokens* entre chacune des entrées. + +Regardons de plus près le résultat de la tokénisation : + +```py +print(inputs.keys()) +``` + +```python out +dict_keys(['input_ids', 'attention_mask', 'overflow_to_sample_mapping']) +``` + +Comme prévu, nous obtenons les ID d'entrée et un masque d'attention. La dernière clé, `overflow_to_sample_mapping`, est une carte qui nous indique à quelle phrase correspond chacun des résultats -- ici nous avons 7 résultats qui proviennent tous de la (seule) phrase que nous avons passée au *tokenizer* : + +```py +print(inputs["overflow_to_sample_mapping"]) +``` + +```python out +[0, 0, 0, 0, 0, 0, 0] +``` + +C'est plus utile lorsque nous tokenisons plusieurs phrases ensemble. Par exemple, ceci : + +```py +sentences = [ + "This sentence is not too long but we are going to split it anyway.", # Cette phrase n'est pas trop longue mais nous allons la diviser quand même + "This sentence is shorter but will still get split.", # Cette phrase est plus courte mais sera quand même divisée +] +inputs = tokenizer( + sentences, truncation=True, return_overflowing_tokens=True, max_length=6, stride=2 +) + +print(inputs["overflow_to_sample_mapping"]) +``` + +nous donne : + +```python out +[0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1] +``` + +ce qui signifie que la première phrase est divisée en 7 morceaux comme précédemment, et que les 4 morceaux suivants proviennent de la deuxième phrase. + +Revenons maintenant à notre contexte long. Par défaut, le pipeline `question-answering` utilise une longueur maximale de 384, comme nous l'avons mentionné plus tôt, et un stride de 128, qui correspondent à la façon dont le modèle a été ajusté (vous pouvez ajuster ces paramètres en passant les arguments `max_seq_len` et `stride` lorsque vous appelez le pipeline). Nous utiliserons donc ces paramètres lors de la tokenisation. Nous ajouterons aussi du *padding* (pour avoir des échantillons de même longueur, afin de pouvoir construire des tenseurs) ainsi que demander les 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, +) +``` + +Ces `inputs` contiendront les ID d'entrée et les masques d'attention que le modèle attend, ainsi que les offsets et le `overflow_to_sample_mapping` dont on vient de parler. Puisque ces deux éléments ne sont pas des paramètres utilisés par le modèle, nous allons les sortir des `inputs` (et nous ne stockerons pas la carte, puisqu'elle n'est pas utile ici) avant de le convertir en tenseur : + +{#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} + +Notre contexte long a été divisé en deux, ce qui signifie qu'après avoir traversé notre modèle, nous aurons deux ensembles de logits de début et de fin : + +```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} + +Comme précédemment, nous masquons d'abord les *tokens* qui ne font pas partie du contexte avant de prendre le softmax. Nous masquons également tous les *tokens* de *padding* (tels que signalés par le masque d'attention) : + +{#if fw === 'pt'} + +```py +sequence_ids = inputs.sequence_ids() +# Masque tout, sauf les tokens du contexte +mask = [i != 1 for i in sequence_ids] +# Démasquer le jeton [CLS] +mask[0] = False +# Masquer tous les tokens [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() +# Masque tout, sauf les tokens du contexte +mask = [i != 1 for i in sequence_ids] +# Démasquer le jeton [CLS] +mask[0] = False +# Masquer tous les tokens [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} + +Ensuite, nous pouvons utiliser lafonction softmax pour convertir nos logits en probabilités : + +{#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} + +L'étape suivante est similaire à ce que nous avons fait pour le petit contexte, mais nous la répétons pour chacun de nos deux chunks. Nous attribuons un score à tous les espaces de réponse possibles, puis nous prenons l'espace ayant le meilleur score : + +{#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[0] + end_idx = idx % scores.shape[0] + 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[0] + end_idx = idx % scores.shape[0] + 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)] +``` + +Ces deux candidats correspondent aux meilleures réponses que le modèle a pu trouver dans chaque morceau. Le modèle est beaucoup plus confiant que la bonne réponse se trouve dans la deuxième partie (ce qui est bon signe !). Il ne nous reste plus qu'à faire correspondre ces deux espaces de *tokens* à des espaces de caractères dans le contexte (nous n'avons besoin de faire correspondre que le second pour avoir notre réponse, mais il est intéressant de voir ce que le modèle a choisi dans le premier morceau). + + + +✏️ **Essayez !** Adaptez le code ci-dessus pour renvoyer les scores et les étendues des cinq réponses les plus probables (au total, pas par morceau). + + + +Le `offsets` que nous avons saisi plus tôt est en fait une liste d'*offsets*, avec une liste par morceau de texte : + +```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 nous ignorons le premier résultat, nous obtenons le même résultat que notre pipeline pour ce long contexte. Yay ! + + + +✏️ **Essayez !** Utilisez les meilleurs scores que vous avez calculés auparavant pour montrer les cinq réponses les plus probables (pour l'ensemble du contexte, pas pour chaque morceau). Pour vérifier vos résultats, retournez au premier pipeline et passez `top_k=5` en l'appelant. + + + +Ceci conclut notre plongée en profondeur dans les capacités du *tokenizer*. Nous mettrons à nouveau tout cela en pratique dans le prochain chapitre, lorsque nous vous montrerons comment *finetuner* un modèle sur une série de tâches NLP courantes. \ No newline at end of file diff --git a/chapters/fr/chapter6/4.mdx b/chapters/fr/chapter6/4.mdx new file mode 100644 index 000000000..314c2d536 --- /dev/null +++ b/chapters/fr/chapter6/4.mdx @@ -0,0 +1,123 @@ +# Normalisation et pré-tokenization + + + +Avant de nous plonger plus profondément dans les trois algorithmes de tokénisation en sous-mots les plus courants utilisés avec les *transformers* (*Byte-Pair Encoding* (BPE), *WordPiece* et *Unigram*), nous allons d'abord examiner le prétraitement que chaque *tokenizer* applique au texte. Voici un aperçu de haut niveau des étapes du pipeline de tokenisation : + +
+The tokenization pipeline. + +
+ +Avant de diviser un texte en sous-*tokens* (selon son modèle), le *tokenizer* effectue deux étapes : _normalisation_ et _pré-tokénisation_. + +## Normalisation + + + +L'étape de normalisation implique un nettoyage général, comme la suppression des espaces blancs inutiles, la mise en minuscules et/ou la suppression des accents. Si vous êtes familier avec la [normalisation Unicode](http://www.unicode.org/reports/tr15/) (comme NFC ou NFKC), c'est aussi quelque chose que le *tokenizer* peut appliquer. + +Le `tokenizer` de 🤗 *Transformers* possède un attribut appelé `backend_tokenizer` qui donne accès au *tokenizer* sous-jacent de la bibliothèque 🤗 *Tokenizers* : + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased") +print(type(tokenizer.backend_tokenizer)) +``` + +```python out + +``` + +L'attribut `normalizer` de l'objet `tokenizer` possède une méthode `normalize_str()` que nous pouvons utiliser pour voir comment la normalisation est effectuée : + +```py +print(tokenizer.backend_tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) +``` + +```python out +'hello how are u?' +``` + +Dans cet exemple, puisque nous avons choisi le point de contrôle `bert-base-uncased`, la normalisation a appliqué la minuscule et supprimé les accents. + + + +✏️ **Essayez !** Chargez un *tokenizer* depuis le *checkpoint* `bert-base-cased` et passez-lui le même exemple. Quelles sont les principales différences que vous pouvez voir entre les versions casée et non casée du *tokenizer* ? + + + +## Pré-tokenization + + + +Comme nous le verrons dans les sections suivantes, un *tokenizer* ne peut pas être entraîné uniquement sur du texte brut. Au lieu de cela, nous devons d'abord diviser les textes en petites entités, comme des mots. C'est là qu'intervient l'étape de pré-tokénisation. Comme nous l'avons vu dans le [Chapitre 2](/course/fr/chapter2), un *tokenizer* basé sur les mots peut simplement diviser un texte brut en mots sur les espaces et la ponctuation. Ces mots constitueront les limites des sous-*tokens* que le *tokenizer* peut apprendre pendant son entraînement. + +Pour voir comment un *tokenizer* rapide effectue la pré-tokénisation, nous pouvons utiliser la méthode `pre_tokenize_str()`de l'attribut `pre_tokenizer` de l'objet `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))] +``` + +Remarquez que le *tokenizer* garde déjà la trace des décalages, ce qui lui permet de nous donner la correspondance des décalages que nous avons utilisée dans la section précédente. Ici, le *tokenizer* ignore les deux espaces et les remplace par un seul, mais le décalage saute entre `are` et `you` pour en tenir compte. + +Puisque nous utilisons un *tokenizer* de BERT, la pré-tokénisation implique la séparation des espaces et de la ponctuation. D'autres *tokenizers* peuvent avoir des règles différentes pour cette étape. Par exemple, si nous utilisons le *tokenizer* GPT-2 : + +```py +tokenizer = AutoTokenizer.from_pretrained("gpt2") +tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") +``` + +cela séparera aussi sur les espaces et la ponctuation, mais il gardera les espaces et les remplacera par un symbole `Ġ`, ce qui lui permettra de récupérer les espaces originaux si nous décodons les *tokens* : + +```python out +[('Hello', (0, 5)), (',', (5, 6)), ('Ġhow', (6, 10)), ('Ġare', (10, 14)), ('Ġ', (14, 15)), ('Ġyou', (15, 19)), + ('?', (19, 20))] +``` + +Notez également que, contrairement au *tokenizer* de BERT, ce *tokenizer* n'ignore pas les doubles espaces. + +Pour un dernier exemple, regardons le *tokenizer* T5, qui est basé sur l'algorithme 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))] +``` + +Comme le *tokenizer* du GPT-2, celui-ci garde les espaces et les remplace par un token spécifique (`_`), mais le tokenizer T5 ne sépare que sur les espaces, pas sur la ponctuation. Notez également qu'il a ajouté un espace par défaut au début de la phrase (avant `Hello`) et a ignoré le double espace entre `are` et `you`. + +Maintenant que nous avons vu un peu comment les différents *tokenizers* traitent le texte, nous pouvons commencer à explorer les algorithmes sous-jacents eux-mêmes. Nous commencerons par jeter un coup d'oeil rapide sur le très répandu SentencePiece ; puis, au cours des trois sections suivantes, nous examinerons le fonctionnement des trois principaux algorithmes utilisés pour la tokenisation en sous-mots. + +## SentencePiece + +[SentencePiece](https://github.com/google/sentencepiece) est un algorithme de tokenization pour le prétraitement du texte que vous pouvez utiliser avec n'importe lequel des modèles que nous verrons dans les trois prochaines sections. Il considère le texte comme une séquence de caractères Unicode, et remplace les espaces par un caractère spécial, `▁`. Utilisé en conjonction avec l'algorithme Unigram (voir [section 7](/course/fr/chapter7/7)), il ne nécessite même pas d'étape de pré-tokénisation, ce qui est très utile pour les langues où le caractère espace n'est pas utilisé (comme le chinois ou le japonais). + +L'autre caractéristique principale de SentencePiece est le *tokenizer* réversible : comme il n'y a pas de traitement spécial des espaces, le décodage des *tokens* se fait simplement en les concaténant et en remplaçant les `_` par des espaces, ce qui donne le texte normalisé. Comme nous l'avons vu précédemment, le *tokenizer* de BERT supprime les espaces répétitifs, donc sa tokenisation n'est pas réversible. + +## Aperçu de l'algorithme + +Dans les sections suivantes, nous allons nous plonger dans les trois principaux algorithmes de tokenisation des sous-mots : BPE (utilisé par GPT-2 et autres), WordPiece (utilisé par exemple par BERT), et Unigram (utilisé par T5 et autres). Avant de commencer, voici un rapide aperçu du fonctionnement de chacun d'entre eux. N'hésitez pas à revenir à ce tableau après avoir lu chacune des sections suivantes si cela n'a pas encore de sens pour vous. + + +Modèle | BPE | WordPiece | Unigramme +:----:|:---:|:---------:|:------: +Entraînement | Part d'un petit vocabulaire et apprend des règles pour fusionner les *tokens* | Part d'un petit vocabulaire et apprend des règles pour fusionner les *tokens* | Part d'un grand vocabulaire et apprend des règles pour supprimer les *tokens*. +Étape d'Entraînement | Fusionne les *tokens* correspondant à la paire la plus commune | Fusionne les *tokens* correspondant à la paire ayant le meilleur score basé sur la fréquence de la paire, en privilégiant les paires où chaque *token* individuel est moins fréquent | Supprime tous les *tokens* du vocabulaire qui minimiseront la perte calculée sur le corpus entier +Apprendre | Fusionner des règles et un vocabulaire | Juste un vocabulaire | Un vocabulaire avec un score pour chaque *token* +Encodage | Découpe un mot en caractères et applique les fusions apprises pendant l'entraînement | Trouve le plus long sous-mot depuis le début qui est dans le vocabulaire, puis fait de même pour le reste du mot | Trouve la division la plus probable en tokens, en utilisant les scores appris pendant l'entraînement + +Maintenant, plongeons dans BPE ! diff --git a/chapters/fr/chapter6/5.mdx b/chapters/fr/chapter6/5.mdx new file mode 100644 index 000000000..8f899884a --- /dev/null +++ b/chapters/fr/chapter6/5.mdx @@ -0,0 +1,360 @@ +# Tokénisation *Byte-Pair Encoding* + + + +*Byte-Pair Encoding* (BPE) a été initialement développé en tant qu'algorithme de compression de textes, puis utilisé par OpenAI pour la tokenisation lors du pré-entraînement du modèle GPT. Il est utilisé par de nombreux modèles Transformer, dont GPT, GPT-2, RoBERTa, BART et DeBERTa. + + + + + +💡 Cette section couvre le BPE en profondeur, allant jusqu'à montrer une implémentation complète. Vous pouvez passer directement à la fin si vous souhaitez simplement avoir un aperçu général de l'algorithme de tokenisation. + + + +## Algorithme d'entraînement + +L'entraînement du BPE commence par le calcul de l'ensemble unique de mots utilisés dans le corpus (après les étapes de normalisation et de pré-tokénisation), puis la construction du vocabulaire en prenant tous les symboles utilisés pour écrire ces mots. A titre d'exemple très simple, disons que notre corpus utilise ces cinq mots : + +``` +"hug", "pug", "pun", "bun", "hugs" # "câlin", "carlin", "jeu de mots", "brioche", "câlins"... +``` + +Le vocabulaire de base sera alors `["b", "g", "h", "n", "p", "s", "u"]`. Dans le monde réel, ce vocabulaire de base contiendra au moins tous les caractères ASCII, et probablement aussi quelques caractères Unicode. Si un exemple que vous tokenisez utilise un caractère qui n'est pas dans le corpus d'entraînement, ce caractère sera converti en *token* inconnu. C'est l'une des raisons pour lesquelles de nombreux modèles de NLP sont très mauvais dans l'analyse de contenus contenant des emojis, par exemple. + + + +Les *tokenizer* de GPT-2 et de RoBERTa (qui sont assez similaires) ont une façon intelligente de gérer ce problème : ils ne considèrent pas les mots comme étant écrits avec des caractères Unicode, mais avec des octets. De cette façon, le vocabulaire de base a une petite taille (256), mais tous les caractères auxquels vous pouvez penser seront inclus et ne finiront pas par être convertis en un token inconnu. Cette astuce est appelée *byte-level BPE*. + + + +Après avoir obtenu ce vocabulaire de base, nous ajoutons de nouveaux *tokens* jusqu'à ce que la taille souhaitée du vocabulaire soit atteinte en apprenant les *merges*, qui sont des règles permettant de fusionner deux éléments du vocabulaire existant pour en créer un nouveau. Ainsi, au début, ces fusions créeront des *tokens* de deux caractères, puis, au fur et à mesure de l'entraînement, des sous-mots plus longs. + +À chaque étape de l'entraînement du *tokenizer*, l'algorithme BPE recherche la paire la plus fréquente de *tokens* existants (par "paire", nous entendons ici deux *tokens* consécutifs dans un mot). Cette paire la plus fréquente est celle qui sera fusionnée, et nous rinçons et répétons pour l'étape suivante. + +Pour revenir à notre exemple précédent, supposons que les mots ont les fréquences suivantes : + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +ce qui veut dire que `"hug"` était présent 10 fois dans le corpus, `"pug"` 5 fois, `"pun"` 12 fois, `"bun"` 4 fois, et `"hugs"`" 5 fois. Nous commençons l'entraînement en divisant chaque mot en caractères (ceux qui forment notre vocabulaire initial) afin de voir chaque mot comme une liste de *tokens* : + +``` +("h" "u" "g", 10), ("p" "u" "g", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "u" "g" "s", 5) +``` + +Ensuite, nous regardons les paires. La paire `("h", "u")` est présente dans les mots `"hug"` et `"hugs"`, donc 15 fois au total dans le corpus. Ce n'est pas la paire la plus fréquente, cependant : cet honneur revient à `("u", "g")`, qui est présent dans `"hug"`, `"pug"`, et `"hugs"`, pour un grand total de 20 fois dans le vocabulaire. + +Ainsi, la première règle de fusion apprise par le *tokenizer* est `("u", "g") -> "ug"`, ce qui signifie que `"ug"` sera ajouté au vocabulaire, et que la paire devra être fusionnée dans tous les mots du corpus. A la fin de cette étape, le vocabulaire et le corpus ressemblent à ceci : + +``` +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) +``` + +Nous avons maintenant quelques paires qui aboutissent à un token de plus de deux caractères : la paire `("h", "ug")`, par exemple (présente 15 fois dans le corpus). La paire la plus fréquente à ce stade est `("u", "n")`, cependant, présente 16 fois dans le corpus, donc la deuxième règle de fusion apprise est `("u", "n") -> "un"`. Ajouter cela au vocabulaire et fusionner toutes les occurrences existantes nous conduit à : + +``` +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) +``` + +Maintenant la paire la plus fréquente est `("h", "ug")`, donc nous apprenons la règle de fusion `("h", "ug") -> "hug"`, ce qui nous donne notre premier *token* de trois lettres. Après la fusion, le corpus ressemble à ceci : + +``` +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) +``` + +Et nous continuons ainsi jusqu'à ce que nous atteignions la taille de vocabulaire souhaitée. + + + +✏️ **A votre tour !** A votre avis, quelle sera la prochaine règle de fusion ? + + + +## Algorithme de tokenisation + +La tokenisation suit de près le processus d'entraînement, dans le sens où les nouvelles entrées sont tokenisées en appliquant les étapes suivantes : + +1. Normalisation +2. Pré-tokénisation. +3. Découpage des mots en caractères individuels +4. Application des règles de fusion apprises dans l'ordre sur ces divisions. + +Prenons l'exemple que nous avons utilisé pendant l'entraînement, avec les trois règles de fusion apprises : + +``` +("u", "g") -> "ug" +("u", "n") -> "un" +("h", "ug") -> "hug" +``` + +Le mot " bug " sera traduit par " ["b", "ug"]` ". Par contre, le mot " mug " sera traduit par " ["[UNK]", "ug"]` " puisque la lettre " m " ne fait pas partie du vocabulaire de base. De la même façon, le mot " thug "` sera tokenisé comme "["[UNK]", "hug"]` : la lettre `"t"` n'est pas dans le vocabulaire de base, et l'application des règles de fusion résulte d'abord en la fusion de `"u"` et `"g"` et ensuite en la fusion de `"hu"` et `"g"`. + + + +✏️ **A votre tour !** Comment pensez-vous que le mot "unhug"`` sera tokenized ? + + + +## Mise en œuvre du BPE + +Voyons maintenant une implémentation de l'algorithme BPE. Il ne s'agira pas d'une version optimisée que vous pourrez utiliser sur un grand corpus ; nous voulons simplement vous montrer le code afin que vous puissiez comprendre un peu mieux l'algorithme. + +Tout d'abord, nous avons besoin d'un corpus, alors créons un corpus simple avec quelques phrases : + +```python +corpus = [ + "This is the Hugging Face course.", # C'est le cours d'Hugging Face. + "This chapter is about tokenization.", # This chapter is about tokenization + "This section shows several tokenizer algorithms.", # Cette section présente plusieurs algorithmes de *tokenizer*. + "Hopefully, you will be able to understand how they are trained and generate tokens.", # Avec un peu de chance, vous serez en mesure de comprendre comment ils sont entraînés et génèrent des *tokens*. +] +``` + +Ensuite, nous devons pré-tokeniser ce corpus en mots. Puisque nous répliquons un *tokenizer* BPE (comme GPT-2), nous utiliserons le *tokenizer* `gpt2` pour la pré-tokénisation : + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("gpt2") +``` + +Ensuite, nous calculons les fréquences de chaque mot dans le corpus comme nous le faisons pour la pré-tokénisation : + +```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}) +``` + +L'étape suivante consiste à calculer le vocabulaire de base, formé par tous les caractères utilisés dans le 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', 'Ġ'] +``` + +Nous ajoutons également les *tokens* spéciaux utilisés par le modèle au début de ce vocabulaire. Dans le cas de GPT-2, le seul token spécial est `"<|endoftext|>"` : + +```python +vocab = ["<|endoftext|>"] + alphabet.copy() +``` + +Nous devons maintenant diviser chaque mot en caractères individuels, pour pouvoir commencer l'entraînement : + +```python +splits = {word: [c for c in word] for word in word_freqs.keys()} +``` + +Maintenant que nous sommes prêts pour l'entraînement, écrivons une fonction qui calcule la fréquence de chaque paire. Nous devrons l'utiliser à chaque étape de l'entraînement : + +```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 +``` + +Jetons un coup d'œil à une partie de ce dictionnaire après les premières divisions : + +```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 +``` + +Maintenant, trouver la paire la plus fréquente ne demande qu'une rapide boucle : + +```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 +``` + +Donc la première fusion à apprendre est `('Ġ', 't') -> 'Ġt'`, et on ajoute `'Ġt'` au vocabulaire : + +```python +merges = {("Ġ", "t"): "Ġt"} +vocab.append("Ġt") +``` + +Pour continuer, nous devons appliquer cette fusion dans notre dictionnaire `splits`. Écrivons une autre fonction pour cela : + +```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 +``` + +Et nous pouvons regarder le résultat de la première fusion : + +```py +splits = merge_pair("Ġ", "t", splits) +print(splits["Ġtrained"]) +``` + +```python out +['Ġt', 'r', 'a', 'i', 'n', 'e', 'd'] +``` + +Maintenant, nous avons tout ce dont nous avons besoin pour boucler jusqu'à ce que nous ayons appris toutes les fusions que nous voulons. Visons une taille de vocabulaire 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]) +``` + +En conséquence, nous avons appris 19 règles de fusion (le vocabulaire initial avait une taille de 31 : 30 caractères dans l'alphabet, plus le *token* spécial) : + +```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'} +``` + +Et le vocabulaire est composé du *token* spécial, de l'alphabet initial, et de tous les résultats des fusions : + +```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'] +``` + + + +💡 Utiliser `train_new_from_iterator()` sur le même corpus ne donnera pas exactement le même vocabulaire. C'est parce que lorsqu'il y a un choix de la paire la plus fréquente, nous avons sélectionné la première rencontrée, alors que la bibliothèque 🤗 *Tokenizers* sélectionne la première en fonction de ses identifiants internes. + + + +Pour tokeniser un nouveau texte, on le pré-tokenise, on le divise, puis on applique toutes les règles de fusion apprises : + +```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, []) +``` + +Nous pouvons essayer cela sur n'importe quel texte composé de caractères de l'alphabet : + +```py +tokenize("This is not a token.") +``` + +```python out +['This', 'Ġis', 'Ġ', 'n', 'o', 't', 'Ġa', 'Ġtoken', '.'] +``` + + + +⚠️ Notre implémentation lancera une erreur s'il y a un caractère inconnu puisque nous n'avons rien fait pour les gérer. GPT-2 n'a pas réellement de jeton inconnu (il est impossible d'obtenir un caractère inconnu en utilisant le BPE au niveau de l'octet), mais cela pourrait arriver ici parce que nous n'avons pas inclus tous les octets possibles dans le vocabulaire initial. Cet aspect du BPE dépasse le cadre de cette section, nous avons donc laissé les détails de côté. + + + +C'est tout pour l'algorithme BPE ! Ensuite, nous allons nous intéresser à WordPiece. \ No newline at end of file diff --git a/chapters/fr/chapter6/6.mdx b/chapters/fr/chapter6/6.mdx new file mode 100644 index 000000000..701e4d7f8 --- /dev/null +++ b/chapters/fr/chapter6/6.mdx @@ -0,0 +1,374 @@ +# Tokénisation *WordPiece* + + + +*WordPiece* est l'algorithme de tokénisation développé par Google pour prétraîner BERT. Il a depuis été réutilisé dans un grand nombre de modèles de transformateurs basés sur BERT, tels que DistilBERT, MobileBERT, Funnel Transformers et MPNET. Il est très similaire à BPE en termes d'entraînement, mais la tokenisation réelle est effectuée différemment. + + + + + +💡 Cette section couvre le *WordPiece* en profondeur, allant jusqu'à montrer une implémentation complète. Vous pouvez passer directement à la fin si vous souhaitez simplement avoir un aperçu général de l'algorithme de tokénisation. + + + +## Algorithme d'entraînement + + + +⚠️ Google n'a jamais mis en ligne son implémentation de l'algorithme d'entraînement de *WordPiece*. Ce qui suit est donc notre meilleure estimation basée sur la littérature publiée. Il se peut qu'elle ne soit pas exacte à 100 %. + + + +Comme le BPE, *WordPiece* part d'un petit vocabulaire comprenant les *tokens* spéciaux utilisés par le modèle et l'alphabet initial. Puisqu'il identifie les sous-mots en ajoutant un préfixe (comme `##` pour BERT), chaque mot est initialement découpé en ajoutant ce préfixe à tous les caractères du mot. Ainsi, par exemple, `"mot"` est divisé comme ceci : + +``` +w ##o ##r ##d +``` + +Ainsi, l'alphabet initial contient tous les caractères présents au début d'un mot et les caractères présents à l'intérieur d'un mot précédé du préfixe de *WordPiece*. + +Ensuite, toujours comme le BPE, *WordPiece* apprend des règles de fusion. La principale différence réside dans la manière dont la paire à fusionner est sélectionnée. Au lieu de sélectionner la paire la plus fréquente, *WordPiece* calcule un score pour chaque paire, en utilisant la formule suivante : + +$$\mathrm{score} = (\mathrm{freq\_of\_pair}) / (\mathrm{freq\_of\_first\_element} \times \mathrm{freq\_of\_second\_element})$$ + +En divisant la fréquence de la paire par le produit des fréquences de chacune de ses parties, l'algorithme donne la priorité à la fusion des paires dont les parties individuelles sont moins fréquentes dans le vocabulaire. Par exemple, il ne fusionnera pas nécessairement `("un", "##able")` même si cette paire apparaît très fréquemment dans le vocabulaire, car les deux paires `"un"`" et `"##able"` apparaîtront probablement chacune dans un lot d'autres mots et auront une fréquence élevée. En revanche, une paire comme `("hu", "##gging")` sera probablement fusionnée plus rapidement (en supposant que le mot "hugging" apparaisse souvent dans le vocabulaire) puisque `"hu"` et `"##gging"` sont probablement moins fréquents individuellement. + +Examinons le même vocabulaire que celui utilisé dans l'exemple d'entraînement du BPE : + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +Les divisions ici seront : + +``` +("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##g" "##s", 5) +``` + +Le vocabulaire initial sera donc `["b", "h", "p", "##g", "##n", "##s", "##u"]` (si on oublie les *tokens* spéciaux pour l'instant). La paire la plus fréquente est `("##u", "##g")` (présente 20 fois), mais la fréquence individuelle de `"##u"` est très élevée, donc son score n'est pas le plus élevé (il est de 1 / 36). Toutes les paires avec un `"##u"` ont en fait le même score (1 / 36), donc le meilleur score va à la paire `("##g", "##s")` -- la seule sans un `"##u"` -- à 1 / 20, et la première fusion apprise est `("##g", "##s") -> ("##gs")`. + +Notez que lorsque nous fusionnons, nous enlevons le `##` entre les deux *tokens*, donc nous ajoutons `"##gs"` au vocabulaire et appliquons la fusion dans les mots du 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) +``` + +À ce stade, `" ##u "` est dans toutes les paires possibles, donc elles finissent toutes par avoir le même score. Disons que dans ce cas, la première paire est fusionnée, donc `("h", "##u") -> "hu"`. Cela nous amène à : + +``` +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) +``` + +Ensuite, le meilleur score suivant est partagé par `("hu", "##g")` et `("hu", "##gs")` (avec 1/15, comparé à 1/21 pour toutes les autres paires), donc la première paire avec le plus grand score est fusionnée : + +``` +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) +``` + +et nous continuons ainsi jusqu'à ce que nous atteignions la taille de vocabulaire souhaitée. + + + +✏️ **A votre tour !** Quelle sera la prochaine règle de fusion ? + + + +## Algorithme de tokenisation + +La tokénisation diffère dans *WordPiece* et BPE en ce que *WordPiece* ne sauvegarde que le vocabulaire final, pas les règles de fusion apprises. En partant du mot à tokeniser, *WordPiece* trouve le sous-mot le plus long qui se trouve dans le vocabulaire, puis se sépare sur celui-ci. Par exemple, si nous utilisons le vocabulaire appris dans l'exemple ci-dessus, pour le mot `"hugs"` le plus long sous-mot en partant du début qui est dans le vocabulaire est `"hug"`, donc nous le divisons et obtenons `["hug", "##s"]`. On continue avec `"##s"`, qui est dans le vocabulaire, donc la tokenisation de `"hugs"` est `["hug", "##s"]`. + +Avec BPE, nous aurions appliqué les fusions apprises dans l'ordre et la tokénisation aurait été `["hu", "##gs"]`, l'encodage est donc différent. + +Comme autre exemple, voyons comment le mot `"bugs"` serait tokenisé. `"b"` est le plus long sous-mot commençant au début du mot qui est dans le vocabulaire, donc on le divise et on obtient `["b", "##ugs"]`. Ensuite, `"##u"` est le plus long sous-mot commençant au début de `"##ugs"` qui est dans le vocabulaire, donc on le sépare et on obtient `["b", "##u, "##gs"]`. Enfin, `"##gs"` est dans le vocabulaire, donc cette dernière liste est la tokenization de `"bugs"`. + +Lorsque la tokenisation arrive à un stade où il n'est pas possible de trouver un sous-mot dans le vocabulaire, le mot entier est tokenisé comme inconnu -- donc, par exemple, `"mug"` serait tokenisé comme `["[UNK]"]`, tout comme " bum " (même si on peut commencer par " b " et " ##u ", " ##m " ne fait pas partie du vocabulaire, et le *tokenizer* résultant sera simplement `["[UNK]"]` ", et non `["b", "##u", "[UNK]"]` "). C'est une autre différence avec BPE, qui classerait seulement les caractères individuels qui ne sont pas dans le vocabulaire comme inconnus. + + + +✏️ **A votre tour !** Comment le mot `"pugs"` sera-t-il tokenisé ? + + + +## Mise en œuvre de *WordPiece* + +Voyons maintenant une implémentation de l'algorithme *WordPiece*. Comme pour le BPE, il s'agit d'un exemple pédagogique, et vous ne pourrez pas l'utiliser sur un grand corpus. + +Nous utiliserons le même corpus que dans l'exemple BPE : + +```python +corpus = [ + "This is the Hugging Face course.", # C'est le cours d'Hugging Face. + "This chapter is about tokenization.", # This chapter is about tokenization + "This section shows several tokenizer algorithms.", # Cette section présente plusieurs algorithmes de *tokenizer*. + "Hopefully, you will be able to understand how they are trained and generate tokens.", # Avec un peu de chance, vous serez en mesure de comprendre comment ils sont entraînés et génèrent des *tokens*. +] +``` + +Tout d'abord, nous devons pré-tokéniser le corpus en mots. Puisque nous répliquons un *tokenizer WordPiece* (comme BERT), nous utiliserons le *tokenizer* `bert-base-cased` pour la pré-tokénisation : + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +``` + +Ensuite, nous calculons les fréquences de chaque mot dans le corpus comme nous le faisons pour la pré-tokénisation : + +```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}) +``` + +Comme nous l'avons vu précédemment, l'alphabet est l'ensemble unique composé de toutes les premières lettres des mots, et de toutes les autres lettres qui apparaissent dans les mots préfixés par `##` : + +```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'] +``` + +Nous ajoutons également les tokens spéciaux utilisés par le modèle au début de ce vocabulaire. Dans le cas de BERT, il s'agit de la liste `["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]` : + +```python +vocab = ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"] + alphabet.copy() +``` + +Ensuite, nous devons diviser chaque mot, avec toutes les lettres qui ne sont pas les premières préfixées par `##` : + +```python +splits = { + word: [c if i == 0 else f"##{c}" for i, c in enumerate(word)] + for word in word_freqs.keys() +} +``` + +Maintenant que nous sommes prêts pour l'entraînement, écrivons une fonction qui calcule le score de chaque paire. Nous devrons l'utiliser à chaque étape de l'entraînement : + +```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 +``` + +Jetons un coup d'œil à une partie de ce dictionnaire après les premières divisions : + +```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 +``` + +Maintenant, trouver la paire avec le meilleur score ne prend qu'une rapide boucle : + +```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 +``` + +Ainsi, la première fusion à apprendre est `('a', '##b') -> 'ab'`, et nous ajoutons `'ab'` au vocabulaire : + +```python +vocab.append("ab") +``` + +Pour continuer, nous devons appliquer cette fusion dans notre dictionnaire `splits`. Écrivons une autre fonction pour cela : + +```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 +``` + +Et nous pouvons regarder le résultat de la première fusion : + +```py +splits = merge_pair("a", "##b", splits) +splits["about"] +``` + +```python out +['ab', '##o', '##u', '##t'] +``` + +Nous avons maintenant tout ce dont nous avons besoin pour boucler jusqu'à ce que nous ayons appris toutes les fusions que nous voulons. Visons une taille de vocabulaire 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) +``` + +Nous pouvons ensuite examiner le vocabulaire généré : + +```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', '##fu', 'Fa', 'Fac', '##ct', '##ful', '##full', '##fully', + 'Th', 'ch', '##hm', 'cha', 'chap', 'chapt', '##thm', 'Hu', 'Hug', 'Hugg', 'sh', 'th', 'is', '##thms', '##za', '##zat', + '##ut'] +``` + +Comme nous pouvons le voir, comparé à BPE, ce *tokenizer* apprend les parties de mots comme des *tokens* un peu plus rapidement. + + + +💡 Utiliser `train_new_from_iterator()` sur le même corpus ne donnera pas exactement le même vocabulaire. C'est parce que la bibliothèque 🤗 *Tokenizers* n'implémente pas *WordPiece* pour l'entraînement (puisque nous ne sommes pas complètement sûrs de ses internes), mais utilise le BPE à la place. + + + +Pour tokeniser un nouveau texte, on le pré-tokenise, on le divise, puis on applique l'algorithme de tokenisation sur chaque mot. En d'autres termes, nous recherchons le plus grand sous-mot commençant au début du premier mot et le divisons, puis nous répétons le processus sur la deuxième partie, et ainsi de suite pour le reste de ce mot et les mots suivants dans le texte : + +```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 +``` + +Testons-le sur un mot qui fait partie du vocabulaire, et un autre qui n'en fait pas partie : + +```python +print(encode_word("Hugging")) +print(encode_word("HOgging")) +``` + +```python out +['Hugg', '##i', '##n', '##g'] +['[UNK]'] +``` + +Maintenant, écrivons une fonction qui permet de tokeniser un texte : + +```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, []) +``` + +On peut l'essayer sur n'importe quel texte : + +```python +tokenize("This is the Hugging Face course!") # C'est le cours d'Hugging Face +``` + +```python out +['Th', '##i', '##s', 'is', 'th', '##e', 'Hugg', '##i', '##n', '##g', 'Fac', '##e', 'c', '##o', '##u', '##r', '##s', + '##e', '[UNK]'] +``` + +C'est tout pour l'algorithme *WordPiece* ! Maintenant, jetons un coup d'oeil à *Unigram*. diff --git a/chapters/fr/chapter6/7.mdx b/chapters/fr/chapter6/7.mdx new file mode 100644 index 000000000..ba32b85e7 --- /dev/null +++ b/chapters/fr/chapter6/7.mdx @@ -0,0 +1,381 @@ +# Tokenisation *Unigram* + + + +The Unigram algorithm is often used in SentencePiece, which is the tokenization algorithm used by models like AlBERT, T5, mBART, Big Bird, and XLNet. + + + + + +💡 Cette section couvre *Unigram* en profondeur, allant jusqu'à montrer une implémentation complète. Vous pouvez passer directement à la fin si vous souhaitez simplement avoir un aperçu général de l'algorithme de tokénisation. + + + +## Algorithme d'entraînement + +Comparé à BPE et *WordPiece*, *Unigram* fonctionne dans l'autre sens : il part d'un grand vocabulaire et enlève des *tokens* jusqu'à atteindre la taille de vocabulaire désirée. Il existe plusieurs options pour construire ce vocabulaire de base : nous pouvons prendre les sous-chaînes les plus courantes dans les mots pré-tokénisés, par exemple, ou appliquer BPE sur le corpus initial avec une grande taille de vocabulaire. + +À 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\\) (\(p\\) étant un hyperparamètre que vous pouvez contrôler, généralement 10 ou 20) pour cent des symboles associés à la plus faible augmentation de la perte. 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é. + +Maintenant, c'est encore un peu vague : la partie principale de l'algorithme est de calculer une perte sur le corpus et de voir comment elle change lorsque nous supprimons certains *tokens* du vocabulaire, mais nous n'avons pas encore expliqué comment le faire. Cette étape repose sur l'algorithme de tokénisation d'un modèle *Unigram*, nous allons donc l'aborder maintenant. + +Nous allons réutiliser le corpus des exemples précédents : + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +et pour cet exemple, nous prendrons toutes les sous-chaînes strictes pour le vocabulaire initial : + +``` +["h", "u", "g", "hu", "ug", "p", "pu", "n", "un", "b", "bu", "s", "hug", "gs", "ugs"] +``` + +## Algorithme de tokenisation + +Un modèle *Unigram* est un type de modèle de langage qui considère que chaque *token* est indépendant des *tokens* qui le précèdent. Il s'agit du modèle de langage le plus simple, dans le sens où la probabilité du *token* X compte tenu du contexte précédent est simplement la probabilité du *token* X. Ainsi, si nous utilisions un modèle de langage *Unigram* pour générer du texte, nous prédirions toujours le *token* le plus courant. + +La probabilité d'un *token* donné est sa fréquence (le nombre de fois que nous le trouvons) dans le corpus original, divisée par la somme de toutes les fréquences de tous les *tokens* dans le vocabulaire (pour s'assurer que la somme des probabilités est égale à 1). Par exemple, `"ug"` est présent dans `"hug"`, `"pug"`, et `"hugs"`, il a donc une fréquence de 20 dans notre corpus. + +Voici les fréquences de tous les sous-mots possibles dans le vocabulaire : + +``` +("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) +``` + +Ainsi, la somme de toutes les fréquences est de 210, et la probabilité du sous-mot `"ug"` est donc de 20/210. + + + +✏️ **A votre tour !** Ecrivez le code pour calculer les fréquences ci-dessus et vérifiez que les résultats affichés sont corrects, ainsi que la somme totale. + + + +Maintenant, pour tokeniser un mot donné, nous examinons toutes les segmentations possibles en *tokens* et calculons la probabilité de chacune d'entre elles selon le modèle *Unigram*. Puisque tous les *tokens* sont considérés comme indépendants, cette probabilité est juste le produit de la probabilité de chaque *token*. Par exemple, la tokenisation `["p", "u", "g"]` de `"pug"` a la probabilité : + +$$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$$ + +Comparativement, la tokenization `["pu", "g"]` a la probabilité : + +$$P([``pu", ``g"]) = P(``pu") \times P(``g") = \frac{5}{210} \times \frac{20}{210} = 0.0022676$$ + +donc celle-là est beaucoup plus probable. En général, les tokénisations comportant le moins de *tokens* possible auront la probabilité la plus élevée (en raison de la division par 210 répétée pour chaque *token*), ce qui correspond à ce que nous voulons intuitivement : diviser un mot en un nombre de *tokens* le plus faible possible. + +La tokenisation d'un mot avec le modèle *Unigram* est donc la tokenisation avec la plus haute probabilité. Dans l'exemple de `"pug"`, voici les probabilités que nous obtiendrions pour chaque segmentation possible : + +``` +["p", "u", "g"] : 0.000389 +["p", "ug"] : 0.0022676 +["pu", "g"] : 0.0022676 +``` + +Ainsi, `"pug"` sera tokenisé comme `["p", "ug"]` ou `["pu", "g"]`, selon la segmentation rencontrée en premier (notez que dans un corpus plus large, les cas d'égalité comme celui-ci seront rares). + +Dans ce cas, il était facile de trouver toutes les segmentations possibles et de calculer leurs probabilités, mais en général, ce sera un peu plus difficile. Il existe un algorithme classique utilisé pour cela, appelé *algorithme de Viterbi*. Essentiellement, on peut construire un graphe pour détecter les segmentations possibles d'un mot donné en disant qu'il existe une branche du caractère _a_ au caractère _b_ si le sous-mot de _a_ à _b_ est dans le vocabulaire, et attribuer à cette branche la probabilité du sous-mot. + +Pour trouver le chemin dans ce graphe qui va avoir le meilleur score, l'algorithme de Viterbi détermine, pour chaque position dans le mot, la segmentation avec le meilleur score qui se termine à cette position. Puisque nous allons du début à la fin, ce meilleur score peut être trouvé en parcourant en boucle tous les sous-mots se terminant à la position actuelle, puis en utilisant le meilleur score de tokenization de la position à laquelle ce sous-mot commence. Ensuite, il suffit de dérouler le chemin emprunté pour arriver à la fin. + +Prenons un exemple en utilisant notre vocabulaire et le mot `"unhug"`. Pour chaque position, les sous-mots avec les meilleurs scores se terminant là sont les suivants : + +``` +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) +``` + +Ainsi, `"unhug"` serait tokenisé comme `["un", "hug"]`. + + + +✏️ **A votre tour !** Déterminer la tokenization du mot `"huggun"` et son score. + + + +## Retour à l'entraînement + +Maintenant que nous avons vu comment fonctionne la tokenisation, nous pouvons nous plonger un peu plus profondément dans la perte utilisée pendant l'entraînement. À n'importe quelle étape, cette perte est calculée en tokenisant chaque mot du corpus, en utilisant le vocabulaire courant et le modèle *Unigram* déterminé par les fréquences de chaque *token* dans le corpus (comme vu précédemment). + +Chaque mot du corpus a un score, et la perte est le logarithme négatif de ces scores : c'est-à-dire la somme pour tous les mots du corpus de tous les `-log(P(word))`. + +Revenons à notre exemple avec le corpus suivant : + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +La tokenisation de chaque mot avec leurs scores respectifs est : + +``` +"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) +``` + +Donc la perte est : + +``` +10 * (-log(0.071428)) + 5 * (-log(0.007710)) + 12 * (-log(0.006168)) + 4 * (-log(0.001451)) + 5 * (-log(0.001701)) = 169.8 +``` + +Maintenant, nous devons calculer comment la suppression de chaque token affecte la perte. C'est plutôt fastidieux, donc nous allons le faire pour deux *tokens* ici et garder tout le processus pour quand nous aurons du code pour nous aider. Dans ce cas (très) particulier, nous avions deux tokenizations équivalentes de tous les mots : comme nous l'avons vu précédemment, par exemple, `"pug"` pourrait être tokenisé `["p", "ug"]` avec le même score. Ainsi, enlever le token `"pu"` du vocabulaire donnera exactement la même perte. + +D'un autre côté, supprimer le mot `"hug"` aggravera la perte, car la tokenisation de `"hug"` et `"hugs"` deviendra : + +``` +"hug": ["hu", "g"] (score 0.006802) +"hugs": ["hu", "gs"] (score 0.001701) +``` + +Ces changements entraîneront une augmentation de la perte de : + +``` +- 10 * (-log(0.071428)) + 10 * (-log(0.006802)) = 23.5 +``` + +Par conséquent, le token `"pu"` sera probablement retiré du vocabulaire, mais pas `"hug"`. + +## Implémentation d'*Unigram* + +Maintenant, implémentons tout ce que nous avons vu jusqu'à présent dans le code. Comme pour BPE et *WordPiece*, ce n'est pas une implémentation efficace de l'algorithme *Unigram* (bien au contraire), mais cela devrait vous aider à le comprendre un peu mieux. + +Nous allons utiliser le même corpus que précédemment comme exemple : + +```python +corpus = [ + "This is the Hugging Face course.", # C'est le cours d'Hugging Face. + "This chapter is about tokenization.", # This chapter is about tokenization + "This section shows several tokenizer algorithms.", # Cette section présente plusieurs algorithmes de *tokenizer*. + "Hopefully, you will be able to understand how they are trained and generate tokens.", # Avec un peu de chance, vous serez en mesure de comprendre comment ils sont entraînés et génèrent des *tokens*. +] +``` + +Cette fois, nous allons utiliser `xlnet-base-cased` comme modèle : + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("xlnet-base-cased") +``` + +Comme pour BPE et *WordPiece*, nous commençons par compter le nombre d'occurrences de chaque mot dans le 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 +``` + +Ensuite, nous devons initialiser notre vocabulaire à quelque chose de plus grand que la taille du vocabulaire que nous voudrons à la fin. Nous devons inclure tous les caractères de base (sinon nous ne serons pas en mesure de tokeniser chaque mot), mais pour les sous-chaînes plus grandes, nous ne garderons que les plus communs, donc nous les trions par fréquence : + +```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)] +``` + +Nous regroupons les caractères avec les meilleurs sous-mots pour arriver à un vocabulaire initial de taille 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* utilise un algorithme plus efficace appelé *Enhanced Suffix Array* (ESA) pour créer le vocabulaire initial. + + + +Ensuite, nous calculons la somme de toutes les fréquences, pour convertir les fréquences en probabilités. Pour notre modèle, nous allons stocker les logarithmes des probabilités, car il est plus stable numériquement d'additionner des logarithmes que de multiplier des petits nombres, et cela simplifiera le calcul de la perte du modèle : + +```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()} +``` + +Maintenant la fonction principale est celle qui tokenise les mots en utilisant l'algorithme de Viterbi. Comme nous l'avons vu précédemment, cet algorithme calcule la meilleure segmentation de chaque sous-chaîne du mot, que nous allons stocker dans une variable nommée `best_segmentations`. Nous allons stocker un dictionnaire par position dans le mot (de 0 à sa longueur totale), avec deux clés : l'index du début du dernier *token* dans la meilleure segmentation, et le score de la meilleure segmentation. Avec l'index du début du dernier *token*, nous serons en mesure de récupérer la segmentation complète une fois que la liste est complètement remplie. + +Le remplissage de la liste se fait à l'aide de deux boucles seulement : la boucle principale passe en revue chaque position de départ, et la seconde boucle essaie toutes les sous-chaînes commençant à cette position de départ. Si la sous-chaîne est dans le vocabulaire, nous avons une nouvelle segmentation du mot jusqu'à cette position finale, que nous comparons à ce qui est dans `best_segmentations`. + +Une fois que la boucle principale est terminée, nous commençons juste à la fin et sautons d'une position de départ à une autre, en enregistrant les *tokens* au fur et à mesure, jusqu'à ce que nous atteignions le début du mot : + +```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 +``` + +Nous pouvons déjà essayer notre modèle initial sur quelques mots : + +```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) +``` + +Il est maintenant facile de calculer la perte du modèle sur le 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 +``` + +Nous pouvons vérifier que cela fonctionne sur le modèle que nous avons : + +```python +compute_loss(model) +``` + +```python out +413.10377642940875 +``` + +Le calcul des scores pour chaque *token* n'est pas très difficile non plus ; il suffit de calculer la perte pour les modèles obtenus en supprimant chaque *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 +``` + +Nous pouvons l'essayer sur un *token* donné : + +```python +scores = compute_scores(model) +print(scores["ll"]) +print(scores["his"]) +``` + +Puisque `"ll"` est utilisé dans la tokenisation de `"Hopefully"`, et que le supprimer nous fera probablement utiliser le token `"l"` deux fois à la place, nous nous attendons à ce qu'il ait une perte positive. `"his"` n'est utilisé qu'à l'intérieur du mot `"This"`, qui est tokenisé comme lui-même, donc nous nous attendons à ce qu'il ait une perte nulle. Voici les résultats : + +```python out +6.376412403623874 +0.0 +``` + + + +💡 Cette approche est très inefficace, c'est pourquoi *SentencePiece* utilise une approximation de la perte du modèle sans le *token* X : au lieu de partir de zéro, il remplace simplement le *token* X par sa segmentation dans le vocabulaire restant. De cette façon, tous les scores peuvent être calculés en une seule fois, en même temps que la perte du modèle. + + + +Une fois tout cela en place, la dernière chose à faire est d'ajouter les *tokens* spéciaux utilisés par le modèle au vocabulaire, puis de boucler jusqu'à ce que nous ayons élagué suffisamment de *tokens* du vocabulaire pour atteindre la taille souhaitée : + +```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()} +``` + +Ensuite, pour tokeniser un texte, il suffit d'appliquer la pré-tokénisation et d'utiliser la fonction `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', '.'] +``` + +C'est tout pour *Unigram* ! Avec un peu de chance, vous vous sentez maintenant comme un expert en tout ce qui concerne les *tokenizers*. Dans la prochaine section, nous allons nous plonger dans les blocs de construction de la bibliothèque 🤗 *Tokenizers*, et vous montrer comment vous pouvez les utiliser pour construire votre propre *tokenizer*. diff --git a/chapters/fr/chapter6/8.mdx b/chapters/fr/chapter6/8.mdx new file mode 100644 index 000000000..cdb445291 --- /dev/null +++ b/chapters/fr/chapter6/8.mdx @@ -0,0 +1,566 @@ +# Construction d'un *tokenizer*, bloc par bloc + + + +Comme nous l'avons vu dans les sections précédentes, la tokenisation comprend plusieurs étapes : + +- normalisation (tout nettoyage du texte jugé nécessaire, comme la suppression des espaces ou des accents, la normalisation Unicode, etc.) +- pré-tokénisation (division de l'entrée en mots) +- passage de l'entrée dans le modèle (utilisation des mots prétokénisés pour produire une séquence de *tokens*) +- post-traitement (ajout des tokens spéciaux du *tokenizer*, génération du masque d'attention et des identifiants du type de *token*). + +Pour mémoire, voici un autre aperçu du processus global : + +
+The tokenization pipeline. + +
+ +La bibliothèque 🤗 *Tokenizers* a été construite pour fournir plusieurs options pour chacune de ces étapes, que vous pouvez mélanger et assortir ensemble. Dans cette section, nous verrons comment nous pouvons construire un *tokenizer* à partir de zéro, par opposition à entraîner un nouveau *tokenizer* à partir d'un ancien, comme nous l'avons fait dans [section 2](/course/fr/chapter6/2). Vous serez alors en mesure de construire n'importe quel type de *tokenizer* auquel vous pouvez penser ! + + + +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). + +## Acquisition d'un corpus + +Pour entraîner notre nouveau *tokenizer*, nous utiliserons un petit corpus de texte (pour que les exemples soient rapides). Les étapes pour acquérir le corpus sont similaires à celles que nous avons suivies au [début de ce chapitre](/course/fr/chapter6/2), mais cette fois nous utiliserons le jeu de données [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 fonction `get_training_corpus()` est un générateur qui donnera des batchs de 1 000 textes, que nous utiliserons pour entraîner le *tokenizer*. + +🤗 *Tokenizers* peuvent aussi être entraînés directement sur des fichiers texte. Voici comment nous pouvons générer un fichier texte contenant tous les textes/entrées de WikiText-2 que nous pouvons utiliser localement : + +```python +with open("wikitext-2.txt", "w", encoding="utf-8") as f: + for i in range(len(dataset)): + f.write(dataset[i]["text"] + "\n") +``` + +Ensuite, nous vous montrerons comment construire vos propres *tokenizers* BERT, GPT-2 et XLNet, bloc par bloc. Cela nous donnera un exemple de chacun des trois principaux algorithmes de tokenisation : *WordPiece*, BPE et *Unigram*. Commençons par BERT ! + +## Construire un tokenizer *WordPiece* à partir de zéro + +Pour construire un *tokenizer* avec la bibliothèque 🤗 *Tokenizers*, nous commençons par instancier un objet `Tokenizer` avec un `model`, puis nous définissons ses attributs `normalizer`, `pre_tokenizer`, `post_processor`, et `decoder` aux valeurs que nous voulons. + +Pour cet exemple, nous allons créer un `Tokenizer` avec un modèle *WordPiece* : + +```python +from tokenizers import ( + decoders, + models, + normalizers, + pre_tokenizers, + processors, + trainers, + Tokenizer, +) + +tokenizer = Tokenizer(models.WordPiece(unk_token="[UNK]")) +``` + +Nous devons spécifier le `unk_token` pour que le modèle sache quoi retourner lorsqu'il rencontre des caractères qu'il n'a pas vu auparavant. D'autres arguments que nous pouvons définir ici incluent le `vocab` de notre modèle (nous allons entraîner le modèle, donc nous n'avons pas besoin de le définir) et `max_input_chars_per_word`, qui spécifie une longueur maximale pour chaque mot (les mots plus longs que la valeur passée seront séparés). + +La première étape de la tokénisation est la normalisation, donc commençons par cela. Puisque BERT est largement utilisé, il y a un `BertNormalizer` avec les options classiques que nous pouvons définir pour BERT : `lowercase` et `strip_accents`, qui sont auto-explicatifs ; `clean_text` pour enlever tous les caractères de contrôle et remplacer les espaces répétés par un seul ; et `handle_chinese_chars`, qui place des espaces autour des caractères chinois. Pour reproduire le *tokenizer* `bert-base-uncased`, nous pouvons simplement définir ce *normalizer* : + +```python +tokenizer.normalizer = normalizers.BertNormalizer(lowercase=True) +``` + +En général, cependant, lorsque vous construisez un nouveau *tokenizer*, vous n'aurez pas accès à un normalisateur aussi pratique déjà implémenté dans la bibliothèque 🤗 *Tokenizers*. Donc voyons comment créer le normalisateur BERT manuellement. La bibliothèque fournit un normaliseur `Lowercase` et un normaliseur `StripAccents`, et vous pouvez composer plusieurs normaliseurs en utilisant une `Sequence` : + +```python +tokenizer.normalizer = normalizers.Sequence( + [normalizers.NFD(), normalizers.Lowercase(), normalizers.StripAccents()] +) +``` + +Nous utilisons également un normaliseur Unicode `NFD`, car sinon le normalisateur `StripAccents` ne reconnaîtra pas correctement les caractères accentués et ne les supprimera donc pas. + +Comme nous l'avons vu précédemment, nous pouvons utiliser la méthode `normalize_str()` du `normalizer` pour vérifier les effets qu'il a sur un texte donné : + +```python +print(tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) +``` + +```python out +hello how are u? +``` + + + +**Pour aller plus loin** Si vous testez les deux versions des normalisateurs précédents sur une chaîne contenant le caractère unicode `u"\u0085"` vous remarquerez sûrement que ces deux normalisateurs ne sont pas exactement équivalents. +Pour ne pas trop compliquer la version avec `normalizers.Sequence`, nous n'avons pas inclus les remplacements Regex que le `BertNormalizer` requiert quand l'argument `clean_text` est mis à `True` ce qui est le comportement par défaut. Mais ne vous inquiétez pas : il est possible d'obtenir exactement la même normalisation sans utiliser le très pratique `BertNormalizer` en ajoutant deux `normalizers.Replace` à la séquence de normalisation. + + + +L'étape suivante est la pré-tokenalisation. Encore une fois, il y a un `BertPreTokenizer` préconstruit que nous pouvons utiliser : + +```python +tokenizer.pre_tokenizer = pre_tokenizers.BertPreTokenizer() +``` + +Ou nous pouvons le construire à partir de zéro : + +```python +tokenizer.pre_tokenizer = pre_tokenizers.Whitespace() +``` + +Notez que le pré-tokenizer `Whitespace` divise sur les espaces et tous les caractères qui ne sont pas des lettres, des chiffres ou le caractère de soulignement, donc techniquement il divise sur les espaces et la ponctuation : + +```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 vous voulez seulement séparer sur les espaces, vous devriez utiliser le pré-tokenizer `WhitespaceSplit` à la place : + +```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))] +``` + +Comme pour les normaliseurs, vous pouvez utiliser une `Sequence` pour composer plusieurs pré-tokenizers : + +```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))] +``` + +L'étape suivante dans le pipeline de tokénisation est de faire passer les entrées par le modèle. Nous avons déjà spécifié notre modèle dans l'initialisation, mais nous devons encore l'entraîner, ce qui nécessitera un `WordPieceTrainer`. La principale chose à retenir lors de l'instanciation d'un entraîneur dans 🤗 *Tokenizers* est que vous devez lui passer tous les *tokens* spéciaux que vous avez l'intention d'utiliser. Sinon il ne les ajoutera pas au vocabulaire, puisqu'ils ne sont pas dans le corpus d'entraînement : + +```python +special_tokens = ["[UNK]", "[PAD]", "[CLS]", "[SEP]", "[MASK]"] +trainer = trainers.WordPieceTrainer(vocab_size=25000, special_tokens=special_tokens) +``` + +En plus de spécifier la `vocab_size` et les `special_tokens`, nous pouvons définir la `min_frequency` (le nombre de fois qu'un *token* doit apparaître pour être inclus dans le vocabulaire) ou changer le `continuing_subword_prefix` (si nous voulons utiliser quelque chose de différent de `##`). + +Pour entraîner notre modèle en utilisant l'itérateur que nous avons défini plus tôt, il suffit d'exécuter cette commande : + +```python +tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) +``` + +Nous pouvons également utiliser des fichiers texte pour entraîner notre *tokenizer*, qui ressemblerait à ceci (nous réinitialisons le modèle avec un `WordPiece` vide au préalable) : + +```python +tokenizer.model = models.WordPiece(unk_token="[UNK]") +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +Dans les deux cas, nous pouvons ensuite tester le *tokenizer* sur un texte en appelant la méthode `encode()` : + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.'] +``` + +Le `encodage` obtenu est un `Encoding`, qui contient toutes les sorties nécessaires du *tokenizer* dans ses différents attributs : `ids`, `type_ids`, `tokens`, `offsets`, `attention_mask`, `special_tokens_mask`, et `overflowing`. + +La dernière étape du pipeline de tokénisation est le post-traitement. Nous devons ajouter le *token* `[CLS]` au début et le *token* `[SEP]` à la fin (ou après chaque phrase, si nous avons une paire de phrases). Nous utiliserons un `TemplateProcessor` pour cela, mais d'abord nous devons connaître les ID des *tokens* `[CLS]` et `[SEP]` dans le vocabulaire : + +```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) +``` + +Pour écrire le modèle pour le `TemplateProcessor`, nous devons spécifier comment traiter une seule phrase et une paire de phrases. Pour les deux, nous écrivons les *tokens* spéciaux que nous voulons utiliser ; la première (ou unique) phrase est représentée par `$A`, alors que la deuxième phrase (si on code une paire) est représentée par `$B`. Pour chacun de ces éléments (*tokens* spéciaux et phrases), nous spécifions également l'ID du type de *token* correspondant après un deux-points. + +Le *template* classique de BERT est donc défini comme suit : + +```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)], +) +``` + +Notez que nous devons transmettre les ID des jetons spéciaux, afin que le *tokenizer* puisse les convertir correctement en leurs ID. + +Une fois que cela est ajouté, revenir à notre exemple précédent donnera : + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.', '[SEP]'] +``` + +Et sur une paire de phrases, on obtient le bon résultat : + +```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] +``` + +Nous avons presque fini de construire ce *tokenizer* à partir de zéro. La dernière étape consiste à inclure un décodeur : + +```python +tokenizer.decoder = decoders.WordPiece(prefix="##") +``` + +Testons-le sur notre précédent `encoding` : + +```python +tokenizer.decode(encoding.ids) +``` + +```python out +"let's test this tokenizer... on a pair of sentences." # Testons ce tokenizer... sur une paire de phrases. +``` + +Génial ! Nous pouvons enregistrer notre *tokenizer* dans un seul fichier JSON comme ceci : + +```python +tokenizer.save("tokenizer.json") +``` + +Nous pouvons alors recharger ce fichier dans un objet `Tokenizer` avec la méthode `from_file()` : + +```python +new_tokenizer = Tokenizer.from_file("tokenizer.json") +``` + +Pour utiliser ce *tokenizer* dans 🤗 *Transformers*, nous devons l'envelopper dans un `PreTrainedTokenizerFast`. Nous pouvons soit utiliser la classe générique, soit, si notre *tokenizer* correspond à un modèle existant, utiliser cette classe (ici, `BertTokenizerFast`). Si vous appliquez cette leçon pour construire un tout nouveau *tokenizer*, vous devrez utiliser la première option. + +Pour envelopper le *tokenizer* dans un `PreTrainedTokenizerFast`, nous pouvons soit passer le *tokenizer*que nous avons construit comme un `tokenizer_object`, soit passer le fichier de *tokenizer* que nous avons sauvegardé comme `tokenizer_file`. Ce qu'il faut retenir, c'est que nous devons définir manuellement tous les *tokens* spéciaux, car cette classe ne peut pas déduire de l'objet `tokenizer` quel *token* est le *token* de masque, le *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 vous utilisez une classe de *tokenizer* spécifique (comme `BertTokenizerFast`), vous aurez seulement besoin de spécifier les *tokens* spéciaux qui sont différents de ceux par défaut (ici, aucun) : + +```python +from transformers import BertTokenizerFast + +wrapped_tokenizer = BertTokenizerFast(tokenizer_object=tokenizer) +``` + +Vous pouvez ensuite utiliser ce *tokenizer* comme n'importe quel autre *tokenizer* de 🤗 *Transformers*. Vous pouvez le sauvegarder avec la méthode `save_pretrained()`, ou le télécharger sur le *Hub* avec la méthode `push_to_hub()`. + +Maintenant que nous avons vu comment construire un *tokenizer WordPiece*, faisons de même pour un *tokenizer* BPE. Nous irons un peu plus vite puisque vous connaissez toutes les étapes, et nous ne soulignerons que les différences. + +## Construire un *tokenizer* BPE à partir de zéro + +Construisons maintenant un *tokenizer* BPE. Comme pour le *tokenizer* BERT, nous commençons par initialiser un `Tokenizer` avec un modèle BPE : + +```python +tokenizer = Tokenizer(models.BPE()) +``` + +Comme pour BERT, nous pourrions initialiser ce modèle avec un vocabulaire si nous en avions un (nous aurions besoin de passer le `vocab` et le `merges` dans ce cas), mais puisque nous allons nous entraîner à partir de zéro, nous n'avons pas besoin de le faire. Nous n'avons pas non plus besoin de spécifier un `unk_token` parce que GPT-2 utilise un BPE au niveau de l'octet, ce qui ne le nécessite pas. + +GPT-2 n'utilise pas de normaliseur, donc nous sautons cette étape et allons directement à la pré-tokénisation : + +```python +tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False) +``` + +L'option que nous avons ajoutée à `ByteLevel` ici est de ne pas ajouter d'espace en début de phrase (ce qui est le cas par défaut). Nous pouvons jeter un coup d'oeil à la pré-tokénisation d'un texte d'exemple comme avant : + +```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))] +``` + +Vient ensuite le modèle, qui doit être entraîné. Pour GPT-2, le seul *token* spécial est le *token* de fin de texte : + +```python +trainer = trainers.BpeTrainer(vocab_size=25000, special_tokens=["<|endoftext|>"]) +tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) +``` + +Comme avec le `WordPieceTrainer`, ainsi que le `vocab_size` et le `special_tokens`, nous pouvons spécifier la `min_frequency` si nous le voulons, ou si nous avons un suffixe de fin de mot (comme ``), nous pouvons le définir avec `end_of_word_suffix`. + +Ce *tokenizer* peut aussi être entraîné sur des fichiers texte : + +```python +tokenizer.model = models.BPE() +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +Regardons la tokenisation d'un exemple de texte : + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['L', 'et', "'", 's', 'Ġtest', 'Ġthis', 'Ġto', 'ken', 'izer', '.'] +``` + +Nous appliquons le post-traitement au niveau de l'octet pour le *tokenizer* du GPT-2 comme suit : + +```python +tokenizer.post_processor = processors.ByteLevel(trim_offsets=False) +``` + +L'option `trim_offsets = False` indique au post-processeur que nous devons laisser les *offsets* des *tokens* qui commencent par 'Ġ' tels quels : de cette façon, le début des *offsets* pointera sur l'espace avant le mot, et non sur le premier caractère du mot (puisque l'espace fait techniquement partie du token). Regardons le résultat avec le texte que nous venons de coder, où `'Ġtest'` est le token à l'index 4 : + +```python +sentence = "Let's test this tokenizer." +encoding = tokenizer.encode(sentence) +start, end = encoding.offsets[4] +sentence[start:end] +``` + +```python out +' test' +``` + +Enfin, nous ajoutons un décodeur de niveau octet : + +```python +tokenizer.decoder = decoders.ByteLevel() +``` + +et nous pourrons vérifier qu'il fonctionne correctement : + +```python +tokenizer.decode(encoding.ids) +``` + +```python out +"Let's test this tokenizer." # Testons ce tokenizer +``` + +Super ! Maintenant que nous avons terminé, nous pouvons sauvegarder le tokenizer comme avant, et l'envelopper dans un `PreTrainedTokenizerFast` ou un `GPT2TokenizerFast` si nous voulons l'utiliser dans 🤗 *Transformers* : + +```python +from transformers import PreTrainedTokenizerFast + +wrapped_tokenizer = PreTrainedTokenizerFast( + tokenizer_object=tokenizer, + bos_token="<|endoftext|>", + eos_token="<|endoftext|>", +) +``` + +ou : + +```python +from transformers import GPT2TokenizerFast + +wrapped_tokenizer = GPT2TokenizerFast(tokenizer_object=tokenizer) +``` + +Comme dernier exemple, nous allons vous montrer comment construire un *tokenizer* *Unigram* à partir de zéro. + +## Construire un *tokenizer* *Unigram* à partir de rien. + +Construisons maintenant un *tokenizer* XLNet. Comme pour les *tokenizers* précédents, nous commençons par initialiser un `Tokenizer` avec un modèle *Unigram* : + +```python +tokenizer = Tokenizer(models.Unigram()) +``` + +Encore une fois, nous pourrions initialiser ce modèle avec un vocabulaire si nous en avions un. + +Pour la normalisation, XLNet utilise quelques remplacements (qui proviennent de *SentencePiece*) : + +```python +from tokenizers import Regex + +tokenizer.normalizer = normalizers.Sequence( + [ + normalizers.Replace("``", '"'), + normalizers.Replace("''", '"'), + normalizers.NFKD(), + normalizers.StripAccents(), + normalizers.Replace(Regex(" {2,}"), " "), + ] +) +``` + +Cela remplace `` et '' avec " et toute séquence de deux espaces ou plus par un seul espace, ainsi que la suppression des accents dans les textes à catégoriser. + +Le pré-*tokenizer* à utiliser pour tout *tokenizer SentencePiece* est `Metaspace` : + +```python +tokenizer.pre_tokenizer = pre_tokenizers.Metaspace() +``` + +Nous pouvons jeter un coup d'oeil à la pré-tokénisation d'un exemple de texte comme précédemment : + +```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))] +``` + +Vient ensuite le modèle, qui doit être entraîné. XLNet possède un certain nombre de *tokens* spéciaux : + +```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 argument très important à ne pas oublier pour le `UnigramTrainer` est le `unk_token`. Nous pouvons aussi passer d'autres arguments spécifiques à l'algorithme *Unigram*, comme le `shrinking_factor` pour chaque étape où nous enlevons des *tokens* (par défaut 0.75) ou le `max_piece_length` pour spécifier la longueur maximale d'un token donné (par défaut 16). + +Ce *tokenizer* peut aussi être entraîné sur des fichiers texte : + +```python +tokenizer.model = models.Unigram() +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +Regardons la tokenisation d'un exemple de texte : + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.'] +``` + +Une particularité de XLNet est qu'il place le *token* `` à la fin de la phrase, avec un type ID de 2 (pour le distinguer des autres *tokens*). Le résultat est un remplissage à gauche. Nous pouvons traiter tous les *tokens* spéciaux et les IDs de type de *token* avec un modèle, comme pour BERT, mais d'abord nous devons obtenir les IDs des *tokens* `` et `` : + +```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 +``` + +Le modèle ressemble à ceci : + +```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)], +) +``` + +Et nous pouvons tester son fonctionnement en codant une paire de phrases : + +```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] +``` + +Enfin, nous ajoutons un décodeur `Metaspace` : + +```python +tokenizer.decoder = decoders.Metaspace() +``` + +et on en a fini avec ce *tokenizer* ! On peut sauvegarder le *tokenizer* comme avant, et l'envelopper dans un `PreTrainedTokenizerFast` ou `XLNetTokenizerFast` si on veut l'utiliser dans 🤗 *Transformers*. Une chose à noter lors de l'utilisation de `PreTrainedTokenizerFast` est qu'en plus des *tokens* spéciaux, nous devons dire à la bibliothèque 🤗 *Transformers* de remplir à gauche : + +```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", +) +``` + +Ou alternativement : + +```python +from transformers import XLNetTokenizerFast + +wrapped_tokenizer = XLNetTokenizerFast(tokenizer_object=tokenizer) +``` + +Maintenant que vous avez vu comment les différentes briques sont utilisées pour construire des *tokenizers* existants, vous devriez être capable d'écrire n'importe quel *tokenizer* que vous voulez avec la bibliothèque 🤗 *Tokenizers* et pouvoir l'utiliser dans 🤗 *Transformers*. diff --git a/chapters/fr/chapter6/9.mdx b/chapters/fr/chapter6/9.mdx new file mode 100644 index 000000000..7e59acd48 --- /dev/null +++ b/chapters/fr/chapter6/9.mdx @@ -0,0 +1,11 @@ +# *Tokenizer*, vérifié ! + +Bon travail pour finir ce chapitre ! + +Après cette plongée en profondeur dans les *tokenizers*, vous devriez : + +- être capable d'entraîner un nouveau tokenizer en utilisant un ancien tokenizer comme modèle, +- comprendre comment utiliser les offsets pour faire correspondre la position des tokens à l'étendue du texte d'origine, +- connaître les différences entre BPE, *WordPiece* et *Unigram*, +- être capable de combiner les blocs fournis par la bibliothèque 🤗 *Tokenizers* pour construire votre propre *tokenizer*, +- être capable d'utiliser ce *tokenizer* dans la bibliothèque 🤗 *Transformers*. diff --git a/chapters/fr/chapter7/1.mdx b/chapters/fr/chapter7/1.mdx new file mode 100644 index 000000000..ca1d715b5 --- /dev/null +++ b/chapters/fr/chapter7/1.mdx @@ -0,0 +1,33 @@ + + +# Introduction + +Dans le [Chapitre 3](/course/fr/chapter3), vous avez vu comment *finetuner* un modèle de classification de texte. Dans ce chapitre, nous nous attaquons aux tâches de NLP courantes suivantes : + +- la classification de *tokens*, +- la modélisation du langage masqué (comme BERT), +- les résumés, +- la traduction, +- le pré-entraînement à la modélisation causale du langage (comme GPT-2), +- la réponse aux questions. + +{#if fw === 'pt'} + +Pour ce faire, vous devrez tirer parti de tout ce que vous avez appris sur l'API `Trainer` et la bibliothèque 🤗 *Accelerate* au [Chapitre 3](/course/fr/chapitre3), la bibliothèque 🤗 *Datasets* au [Chapitre 5](/course/fr/chapiter5), et la bibliothèque 🤗 *Tokenizers* au [Chapitre 6](/course/fr/chapiter6). Nous téléchargerons également nos résultats sur le *Hub*, comme nous l'avons fait dans le [Chapitre 4](/course/fr/chapiter4), donc c'est vraiment le chapitre où tout est réuni ! + +Chaque section peut être lue indépendamment et vous montrera comment entraîner un modèle avec l'API `Trainer` ou avec votre propre boucle d'entraînement, en utilisant 🤗 *Accelerate*. N'hésitez pas à sauter l'une ou l'autre partie et à vous concentrer sur celle qui vous intéresse le plus : l'API `Trainer` est idéale pour affiner ou entraîner votre modèle sans vous soucier de ce qui se passe en coulisses, tandis que la boucle d'entraînement avec `Accelerate` vous permettra de personnaliser plus facilement toutes les parties que vous souhaitez. + +{:else} + +Pour ce faire, vous devrez tirer parti de tout ce que vous avez appris sur l'entraînement des modèles avec l'API Keras dans le [Chapitre 3](/course/fr/chapiter3), la bibliothèque 🤗 *Datasets* dans le [Chapitre 5](/course/fr/chapiter5), et la bibliothèque 🤗 *Tokenizers* dans le [Chapitre 6](/course/fr/chapiter6). Nous téléchargerons également nos résultats sur le *Hub*, comme nous l'avons fait dans le [Chapitre 4](/course/fr/chapiter4), donc c'est vraiment le chapitre où tout est réuni ! + +Chaque section peut être lue indépendamment. + +{/if} + + + + +Si vous lisez les sections dans l'ordre, vous remarquerez qu'elles ont beaucoup de code et de prose en commun. La répétition est intentionnelle, afin de vous permettre de vous plonger (ou de revenir plus tard) dans une tâche qui vous intéresse et de trouver un exemple fonctionnel complet. + + diff --git a/chapters/fr/chapter7/2.mdx b/chapters/fr/chapter7/2.mdx new file mode 100644 index 000000000..7fb7fae81 --- /dev/null +++ b/chapters/fr/chapter7/2.mdx @@ -0,0 +1,981 @@ + + +# Classification de *tokens* + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +La première application que nous allons explorer est la classification de *tokens*. Cette tâche générique englobe tous les problèmes qui peuvent être formulés comme "l'attribution d'une étiquette à chaque *token* dans une phrase", tels que : + +- **reconnaissance d'entités nommées (NER)** : trouver les entités (telles que des personnes, des lieux ou des organisations) dans une phrase. Cela peut être formulé comme l'attribution d'une étiquette à chaque *token* en ayant une classe par entité et une classe pour "aucune entité". +- **part-of-speech tagging (POS)** : marquer chaque mot dans une phrase comme correspondant à une partie particulière du discours (comme un nom, un verbe, un adjectif, etc.). +- ***chunking*** : trouver les *tokens* qui appartiennent à la même entité. Cette tâche (qui peut être combinée avec le POS ou la NER) peut être formulée comme l'attribution d'une étiquette (habituellement `B-`) à tous les *tokens* qui sont au début d'un morceau, une autre étiquette (habituellement `I-`) aux *tokens* qui sont à l'intérieur d'un morceau, et une troisième étiquette (habituellement `O`) aux *tokens* qui n'appartiennent à aucun morceau. + + + +Bien sûr, il existe de nombreux autres types de problèmes de classification de *tokens* ; ce ne sont là que quelques exemples représentatifs. Dans cette section, nous allons affiner un modèle (BERT) sur une tâche NER, qui sera alors capable de calculer des prédictions comme celle-ci : + + + + + +One-hot encoded labels for question answering. + + + +Vous pouvez trouver le modèle que nous allons entraîner et télécharger sur le *Hub* et vérifier ses prédictions [ici](https://huggingface.co/huggingface-course/bert-finetuned-ner?text=My+nom+est+Sylvain+et+je+travaille+à+Hugging+Face+in+Brooklyn). + +## Préparation des données + +Tout d'abord, nous avons besoin d'un jeu de données adapté à la classification des *tokens*. Dans cette section, nous utiliserons le jeu de données [CoNLL-2003](https://huggingface.co/datasets/conll2003), qui contient des articles de presse de Reuters. + + + +💡 Tant que votre jeu de données consiste en des textes divisés en mots avec leurs étiquettes correspondantes, vous pourrez adapter les procédures de traitement des données décrites ici à votre propre jeu de données. Reportez-vous au [Chapitre 5](/course/fr/chapter5) si vous avez besoin d'un rafraîchissement sur la façon de charger vos propres données personnalisées dans un `Dataset`. + + + +### Le jeu de données CoNLL-2003 + +Pour charger le jeu de données CoNLL-2003, nous utilisons la méthode `load_dataset()` de la bibliothèque 🤗 *Datasets* : + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("conll2003") +``` + +Cela va télécharger et mettre en cache le jeu de données, comme nous l'avons vu dans [Chapitre 3](/course/fr/chapter3) pour le jeu de données GLUE MRPC. L'inspection de cet objet nous montre les colonnes présentes et la répartition entre les ensembles d'entraînement, de validation et de test : + +```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 + }) +}) +``` + +En particulier, nous pouvons voir que le jeu de données contient des étiquettes pour les trois tâches que nous avons mentionnées précédemment : NER, POS, et *chunking*. Une grande différence avec les autres jeux de données est que les textes d'entrée ne sont pas présentés comme des phrases ou des documents, mais comme des listes de mots (la dernière colonne est appelée `tokens`, mais elle contient des mots dans le sens où ce sont des entrées pré-tokénisées qui doivent encore passer par le *tokenizer* pour la tokenisation des sous-mots). + +Regardons le premier élément de l'ensemble d'entraînement : + +```py +raw_datasets["train"][0]["tokens"] +``` + +```python out +['EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'lamb', '.'] +``` + +Puisque nous voulons effectuer la reconnaissance des entités nommées, nous allons examiner les balises NER : + +```py +raw_datasets["train"][0]["ner_tags"] +``` + +```python out +[3, 0, 7, 0, 0, 0, 7, 0, 0] +``` + +Ce sont les étiquettes sous forme d'entiers prêts pour l'entraînement, mais ils ne sont pas nécessairement utiles lorsque nous voulons inspecter les données. Comme pour la classification de texte, nous pouvons accéder à la correspondance entre ces entiers et les noms des étiquettes en regardant l'attribut `features` de notre jeu de données : + +```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) +``` + +Cette colonne contient donc des éléments qui sont des séquences de `ClassLabel`s. Le type des éléments de la séquence se trouve dans l'attribut `feature` de cette `ner_feature`, et nous pouvons accéder à la liste des noms en regardant l'attribut `names` de cette `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'] +``` + +Nous avons déjà vu ces étiquettes en creusant dans le pipeline `token-classification` au [Chapitre 6](/course/fr/chapter6/3), mais pour un rapide rappel : + +- `O` signifie que le mot ne correspond à aucune entité. +- `B-PER`/`I-PER` signifie que le mot correspond au début de/est à l'intérieur d'une entité *personne*. +- `B-ORG`/`I-ORG` signifie que le mot correspond au début/à l'intérieur d'une entité *organisation*. +- `B-LOC`/`I-LOC` signifie que le mot correspond au début/à l'intérieur d'une entité *location*. +- `B-MISC`/`I-MISC` signifie que le mot correspond au début/à l'intérieur d'une entité *divers*. + +Maintenant, le décodage des étiquettes que nous avons vues précédemment nous donne ceci : + +```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' +``` + +Et pour un exemple mélangeant les étiquettes `B-` et `I-`, voici ce que le même code nous donne sur l'élément de l'ensemble d'entraînement à l'indice 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' +``` + +Comme on peut le voir, les entités couvrant deux mots, comme "Union européenne" et "Werner Zwingmann", se voient attribuer une étiquette "B-" pour le premier mot et une étiquette "I-" pour le second. + + + +✏️ *Votre tour !* Affichez les deux mêmes phrases avec leurs étiquettes POS ou *chunking*. + + + +### Traitement des données + + + +Comme d'habitude, nos textes doivent être convertis en identifiants de *tokens* avant que le modèle puisse leur donner un sens. Comme nous l'avons vu dans le [Chapitre 6](/course/fr/chapter6/), une grande différence dans le cas des tâches de classification de *tokens* est que nous avons des entrées pré-tokénisées. Heureusement, l'API tokenizer peut gérer cela assez facilement ; nous devons juste avertir le `tokenizer` avec un drapeau spécial. + +Pour commencer, nous allons créer notre objet `tokenizer`. Comme nous l'avons dit précédemment, nous allons utiliser un modèle pré-entraîné BERT, donc nous allons commencer par télécharger et mettre en cache le tokenizer associé : + +```python +from transformers import AutoTokenizer + +model_checkpoint = "bert-base-cased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +Vous pouvez remplacer le `model_checkpoint` par tout autre modèle que vous préférez à partir du [*Hub*]https://huggingface.co/models), ou par un dossier local dans lequel vous avez sauvegardé un modèle pré-entraîné et un *tokenizer*. La seule contrainte est que le *tokenizer* doit être soutenu par la bibliothèque 🤗 *Tokenizers*, il y a donc une version "rapide" disponible. Vous pouvez voir toutes les architectures qui ont une version rapide dans [ce grand tableau](https://huggingface.co/transformers/#supported-frameworks), et pour vérifier que l'objet `tokenizer` que vous utilisez est bien soutenu par 🤗 *Tokenizers* vous pouvez regarder son attribut `is_fast` : + +```py +tokenizer.is_fast +``` + +```python out +True +``` + +Pour tokeniser une entrée pré-tokenisée, nous pouvons utiliser notre `tokenizer` comme d'habitude et juste ajouter `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]'] +``` + +Comme on peut le voir, le *tokenizer* a ajouté les *tokens* spéciaux utilisés par le modèle (`[CLS]` au début et `[SEP]` à la fin) et n'a pas touché à la plupart des mots. Le mot `lamb`, cependant, a été tokenisé en deux sous-mots, `la` et `##mb`. Cela introduit un décalage entre nos entrées et les étiquettes : la liste des étiquettes n'a que 9 éléments, alors que notre entrée a maintenant 12 *tokens*. Il est facile de tenir compte des *tokens* spéciaux (nous savons qu'ils sont au début et à la fin), mais nous devons également nous assurer que nous alignons toutes les étiquettes avec les mots appropriés. + +Heureusement, comme nous utilisons un *tokenizer* rapide, nous avons accès aux superpouvoirs des 🤗 *Tokenizers*, ce qui signifie que nous pouvons facilement faire correspondre chaque *token* au mot correspondant (comme on le voit au [Chapitre 6](/course/fr/chapter6/3)) : + +```py +inputs.word_ids() +``` + +```python out +[None, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, None] +``` + +Avec un peu de travail, nous pouvons alors étendre notre liste d'étiquettes pour qu'elle corresponde aux *tokens*. La première règle que nous allons appliquer est que les *tokens* spéciaux reçoivent une étiquette de `-100`. En effet, par défaut, `-100` est un indice qui est ignoré dans la fonction de perte que nous allons utiliser (entropie croisée). Ensuite, chaque *token* reçoit la même étiquette que le *token* qui a commencé le mot dans lequel il se trouve, puisqu'ils font partie de la même entité. Pour les *tokens* à l'intérieur d'un mot mais pas au début, nous remplaçons le `B-` par `I-` (puisque le *token* ne commence pas l'entité) : + +```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: + # Start of a new 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: + # Special token + new_labels.append(-100) + else: + # Same word as previous token + label = labels[word_id] + # If the label is B-XXX we change it to I-XXX + if label % 2 == 1: + label += 1 + new_labels.append(label) + + return new_labels +``` + +Essayons-le sur notre première phrase : + +```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] +``` + +Comme nous pouvons le voir, notre fonction a ajouté le `-100` pour les deux *tokens* spéciaux au début et à la fin, et un nouveau `0` pour notre mot qui a été divisé en deux *tokens*. + + + +✏️ *Votre tour !* Certains chercheurs préfèrent n'attribuer qu'un seul label par mot, et attribuer `-100` aux autres sous-*tokens* dans un mot donné. Ceci afin d'éviter que les longs mots qui se divisent en plusieurs batchs ne contribuent fortement à la perte. Changez la fonction précédente pour aligner les étiquettes avec les ID d'entrée en suivant cette règle. + + +Pour prétraiter notre ensemble de données, nous devons tokeniser toutes les entrées et appliquer `align_labels_with_tokens()` sur toutes les étiquettes. Pour profiter de la vitesse de notre *tokenizer* rapide, il est préférable de tokeniser beaucoup de textes en même temps, donc nous allons écrire une fonction qui traite une liste d'exemples et utiliser la méthode `Dataset.map()` avec l'option `batched=True`. La seule chose qui diffère de notre exemple précédent est que la fonction `word_ids()` a besoin de récupérer l'index de l'exemple dont nous voulons les IDs de mots lorsque les entrées du *tokenizer* sont des listes de textes (ou dans notre cas, des listes de mots), donc nous l'ajoutons aussi : + +```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 +``` + +Notez que nous n'avons pas encore paddé nos entrées ; nous le ferons plus tard, lors de la création des lots avec un collateur de données. + +Nous pouvons maintenant appliquer tout ce prétraitement en une seule fois sur les autres divisions de notre jeu de données : + +```py +tokenized_datasets = raw_datasets.map( + tokenize_and_align_labels, + batched=True, + remove_columns=raw_datasets["train"].column_names, +) +``` + +Nous avons fait la partie la plus difficile ! Maintenant que les données ont été prétraitées, l'entraînement réel ressemblera beaucoup à ce que nous avons fait dans le [Chapitre 3](/course/fr/chapter3). + +{#if fw === 'pt'} + +## *Finetuning* du modèle avec l'API `Trainer`. + +Le code actuel utilisant le `Trainer` sera le même que précédemment ; les seuls changements sont la façon dont les données sont rassemblées dans un batch et la fonction de calcul de la métrique. + +{:else} + +## *Finetuning* fin du modèle avec Keras + +Le code réel utilisant Keras sera très similaire au précédent ; les seuls changements sont la façon dont les données sont rassemblées dans un batch et la fonction de calcul de la métrique. + +{/if} + + +### Collation des données + +Nous ne pouvons pas simplement utiliser un `DataCollatorWithPadding` comme dans [Chapter 3](/course/fr/chapter3) parce que cela ne fait que rembourrer les entrées (IDs d'entrée, masque d'attention, et IDs de type de *token*). Ici, nos étiquettes doivent être remplies exactement de la même manière que les entrées afin qu'elles gardent la même taille, en utilisant `-100` comme valeur afin que les prédictions correspondantes soient ignorées dans le calcul de la perte. + +Tout ceci est fait par un [`DataCollatorForTokenClassification`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorfortokenclassification). Comme le `DataCollatorWithPadding`, il prend le `tokenizer` utilisé pour prétraiter les entrées : + +{#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} + +Pour tester cette fonction sur quelques échantillons, nous pouvons simplement l'appeler sur une liste d'exemples provenant de notre jeu d'entraînement tokénisé : + +```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]]) +``` + +Comparons cela aux étiquettes des premier et deuxième éléments de notre jeu de données : + +```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'} + +Comme nous pouvons le voir, le deuxième jeu d'étiquettes a été complété à la longueur du premier en utilisant `-100`s. + +{:else} + +Notre collateur de données est prêt à fonctionner ! Maintenant, utilisons-le pour créer un `tf.data.Dataset` avec la méthode `to_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, +) +``` + + + Prochain arrêt : le modèle lui-même. + +{/if} + +{#if fw === 'tf'} + +### Définir le modèle + +Puisque nous travaillons sur un problème de classification de *tokens*, nous allons utiliser la classe `TFAutoModelForTokenClassification`. La principale chose à retenir lors de la définition de ce modèle est de transmettre des informations sur le nombre de labels que nous avons. La façon la plus simple de le faire est de passer ce nombre avec l'argument `num_labels`, mais si nous voulons un joli *widget* d'inférence fonctionnant comme celui que nous avons vu au début de cette section, il est préférable de définir les correspondances correctes des étiquettes à la place. + +Elles devraient être définies par deux dictionnaires, `id2label` et `label2id`, qui contiennent la correspondance de l'ID au label et vice versa : + +```py +id2label = {str(i): label for i, label in enumerate(label_names)} +label2id = {v: k for k, v in id2label.items()} +``` + +Maintenant, nous pouvons simplement les passer à la méthode `TFAutoModelForTokenClassification.from_pretrained()`, et ils seront définis dans la configuration du modèle, puis correctement enregistrés et téléchargés vers le *Hub* : + +```py +from transformers import TFAutoModelForTokenClassification + +model = TFAutoModelForTokenClassification.from_pretrained( + model_checkpoint, + id2label=id2label, + label2id=label2id, +) +``` + +Comme lorsque nous avons défini notre `TFAutoModelForSequenceClassification` au [Chapitre 3](/course/fr/chapter3), la création du modèle émet un avertissement indiquant que certains poids n'ont pas été utilisés (ceux de la tête de pré-entraînement) et que d'autres poids ont été initialisés de manière aléatoire (ceux de la tête de classification des nouveaux *tokens*), et que ce modèle doit être entraîné. Nous ferons cela dans une minute, mais vérifions d'abord que notre modèle a le bon nombre d'étiquettes : + +```python +model.config.num_labels +``` + +```python out +9 +``` + + + +⚠️ Si vous avez un modèle avec le mauvais nombre de labels, vous obtiendrez une erreur obscure en appelant `model.fit()` plus tard. Cela peut être ennuyeux à déboguer, donc assurez-vous de faire cette vérification pour confirmer que vous avez le nombre de labels attendu. + + + +### *Finetuning* du modèle + +Nous sommes maintenant prêts à entraîner notre modèle ! Mais nous devons d'abord faire un peu de ménage : nous devons nous connecter à Hugging Face et définir nos hyperparamètres d'entraînement. Si vous travaillez dans un *notebook*, il y a une fonction pratique pour vous aider à le faire : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face. + +Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : + +```bash +huggingface-cli login +``` + +Après s'être connecté, nous pouvons préparer tout ce dont nous avons besoin pour compiler notre modèle. 🤗 *Transformers* fournit une fonction pratique `create_optimizer()` qui vous donnera un optimiseur `AdamW` avec des paramètres appropriés pour la décroissance du taux des poids et la décroissance du taux d'apprentissage, les deux améliorant les performances de votre modèle par rapport à l'optimiseur `Adam` intégré : + +```python +from transformers import create_optimizer +import tensorflow as tf + +# Entraîner en mixed-precision float16 +# Commentez cette ligne si vous utilisez un GPU qui ne bénéficiera pas de cette fonction +tf.keras.mixed_precision.set_global_policy("mixed_float16") + +# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans l'ensemble de données, divisé par la taille du batch puis multiplié par le nombre total d'époques +# par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un batchtf.data.Dataset, +# et non le jeu de données original Hugging Face Dataset, donc son len() est déjà 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) +``` + +Notez également que nous ne fournissons pas d'argument `loss` à `compile()`. C'est parce que les modèles peuvent en fait calculer la perte en interne. Si vous compilez sans perte et fournissez vos étiquettes dans le dictionnaire d'entrée (comme nous le faisons dans nos jeux de données), alors le modèle s'entraînera en utilisant cette perte interne, qui sera appropriée pour la tâche et le type de modèle que vous avez choisi. + +Ensuite, nous définissons un `PushToHubCallback` pour télécharger notre modèle vers le *Hub* pendant l'entraînement, et nous ajustons le modèle avec ce *callback* : + +```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, +) +``` + +Vous pouvez spécifier le nom complet du référentiel vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/bert-finetuned-ner"`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, par exemple `"cool_huggingface_user/bert-finetuned-ner"`. + + + +💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de l'appel de `model.fit()` et devrez définir un nouveau nom. + + + +Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. De cette façon, vous pourrez reprendre votre entraînement sur une autre machine si nécessaire. + +A ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à *finetuner* un modèle sur une tâche de classification de *tokens*. Félicitations ! Mais quelle est la qualité réelle de notre modèle ? Nous devons évaluer certaines métriques pour le découvrir. + +{/if} + + +### Métriques + +{#if fw === 'pt'} + +Pour que le `Trainer` calcule une métrique à chaque époque, nous devrons définir une fonction `compute_metrics()` qui prend les tableaux de prédictions et de labels, et retourne un dictionnaire avec les noms et les valeurs des métriques. + +Le cadre traditionnel utilisé pour évaluer la prédiction de la classification des *tokens* est [*seqeval*](https://github.com/chakki-works/seqeval). Pour utiliser cette métrique, nous devons d'abord installer la bibliothèque *seqeval* : + +```py +!pip install seqeval +``` + +Nous pouvons ensuite le charger via la fonction `load_metric()` comme nous l'avons fait dans le [Chapitre 3](/course/fr/chapter3) : + +{:else} + +Le cadre traditionnel utilisé pour évaluer la prédiction de la classification des *tokens* est [*seqeval*](https://github.com/chakki-works/seqeval). Pour utiliser cette métrique, nous devons d'abord installer la bibliothèque *seqeval* : + +```py +!pip install seqeval +``` + +Nous pouvons ensuite le charger via la fonction `load_metric()` comme nous l'avons fait dans le [Chapitre 3](/course/fr/chapter3) : + +{/if} + +```py +from datasets import load_metric + +metric = load_metric("seqeval") +``` + +Cette métrique ne se comporte pas comme la précision standard : elle prend les listes d'étiquettes comme des chaînes de caractères et non comme des entiers. Nous devrons donc décoder complètement les prédictions et les étiquettes avant de les transmettre à la métrique. Voyons comment cela fonctionne. Tout d'abord, nous allons obtenir les étiquettes pour notre premier exemple d'entraînement : + +```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'] +``` + +Nous pouvons alors créer de fausses prédictions pour celles-ci en changeant simplement la valeur de l'indice 2 : + +```py +predictions = labels.copy() +predictions[2] = "O" +metric.compute(predictions=[predictions], references=[labels]) +``` + +Notez que la métrique prend une liste de prédictions (pas seulement une) et une liste d'étiquettes. Voici la sortie : + +```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'} + +Cela renvoie un batch d'informations ! Nous obtenons la précision, le rappel, et le score F1 pour chaque entité séparée, ainsi que le score global. Pour notre calcul de métrique, nous ne garderons que le score global, mais n'hésitez pas à modifier la fonction `compute_metrics()` pour retourner toutes les métriques que vous souhaitez. + +Cette fonction `compute_metrics()` prend d'abord l'argmax des logits pour les convertir en prédictions (comme d'habitude, les logits et les probabilités sont dans le même ordre, donc nous n'avons pas besoin d'appliquer le softmax). Ensuite, nous devons convertir les étiquettes et les prédictions des entiers en chaînes de caractères. Nous supprimons toutes les valeurs dont l'étiquette est `-100`, puis nous passons les résultats à la méthode `metric.compute()` : + +```py +import numpy as np + + +def compute_metrics(eval_preds): + logits, labels = eval_preds + predictions = np.argmax(logits, axis=-1) + + # Suppression de l'index ignoré (tokens spéciaux) et conversion en étiquettes + 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"], + } +``` + +Maintenant que ceci est fait, nous sommes presque prêts à définir notre `Trainer`. Nous avons juste besoin d'un `modèle` pour *finetuner* ! + +{:else} + +Cela renvoie un batch d'informations ! Nous obtenons la précision, le rappel et le score F1 pour chaque entité séparée, ainsi que pour l'ensemble. Voyons maintenant ce qui se passe si nous essayons d'utiliser les prédictions de notre modèle pour calculer des scores réels. + +TensorFlow n'aime pas concaténer nos prédictions ensemble, car elles ont des longueurs de séquence variables. Cela signifie que nous ne pouvons pas simplement utiliser `model.predict()`. Mais cela ne va pas nous arrêter. Nous obtiendrons des prédictions un batch à la fois et les concaténerons en une grande liste longue au fur et à mesure, en laissant tomber les *tokens* `-100` qui indiquent le masquage/le remplissage, puis nous calculerons les métriques sur la liste à la fin : + +```py +import numpy as np + +all_predictions = [] +all_labels = [] +for batch in tf_eval_dataset: + logits = model.predict(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} +``` + +Comment s'est comporté votre modèle, comparé au nôtre ? Si vous avez obtenu des chiffres similaires, votre entraînement a été un succès ! + +{/if} + +{#if fw === 'pt'} + +### Définir le modèle + +Puisque nous travaillons sur un problème de classification de *tokens*, nous allons utiliser la classe `AutoModelForTokenClassification`. La principale chose à retenir lors de la définition de ce modèle est de transmettre des informations sur le nombre de labels que nous avons. La façon la plus simple de le faire est de passer ce nombre avec l'argument `num_labels`, mais si nous voulons un joli *widget* d'inférence fonctionnant comme celui que nous avons vu au début de cette section, il est préférable de définir les correspondances correctes des étiquettes à la place. + +Elles devraient être définies par deux dictionnaires, `id2label` et `label2id`, qui contiennent les correspondances entre ID et label et vice versa : + +```py +id2label = {str(i): label for i, label in enumerate(label_names)} +label2id = {v: k for k, v in id2label.items()} +``` + +Maintenant nous pouvons simplement les passer à la méthode `AutoModelForTokenClassification.from_pretrained()`, et ils seront définis dans la configuration du modèle et ensuite correctement sauvegardés et téléchargés vers le *Hub* : + +```py +from transformers import AutoModelForTokenClassification + +model = AutoModelForTokenClassification.from_pretrained( + model_checkpoint, + id2label=id2label, + label2id=label2id, +) +``` + +Comme lorsque nous avons défini notre `AutoModelForSequenceClassification` au [Chapitre 3](/course/fr/chapter3), la création du modèle émet un avertissement indiquant que certains poids n'ont pas été utilisés (ceux de la tête de pré-entraînement) et que d'autres poids ont été initialisés de manière aléatoire (ceux de la tête de classification des nouveaux *tokens*), et que ce modèle doit être entraîné. Nous ferons cela dans une minute, mais vérifions d'abord que notre modèle a le bon nombre d'étiquettes : + +```python +model.config.num_labels +``` + +```python out +9 +``` + + + +⚠️ Si vous avez un modèle avec le mauvais nombre d'étiquettes, vous obtiendrez une erreur obscure lors de l'appel de la méthode `Trainer.train()` plus tard (quelque chose comme "CUDA error : device-side assert triggered"). C'est la première cause de bogues signalés par les utilisateurs pour de telles erreurs, donc assurez-vous de faire cette vérification pour confirmer que vous avez le nombre d'étiquettes attendu. + + + +### *Finetuning* du modèle + +Nous sommes maintenant prêts à entraîner notre modèle ! Nous devons juste faire deux dernières choses avant de définir notre `Trainer` : se connecter à Hugging Face et définir nos arguments d'entraînement. Si vous travaillez dans un *notebook*, il y a une fonction pratique pour vous aider à le faire : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face. + +Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : + +```bash +huggingface-cli login +``` + +Une fois ceci fait, nous pouvons définir nos `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, +) +``` + +Vous avez déjà vu la plupart d'entre eux : nous définissons quelques hyperparamètres (comme le taux d'apprentissage, le nombre d'époques à entraîner, et la décroissance du poids), et nous spécifions `push_to_hub=True` pour indiquer que nous voulons sauvegarder le modèle et l'évaluer à la fin de chaque époque, et que nous voulons télécharger nos résultats vers le *Hub*. Notez que vous pouvez spécifier le nom du référentiel vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/bert-finetuned-ner"``TrainingArguments`. Par défaut, le référentiel utilisé sera dans votre espace de noms et nommé d'après le répertoire de sortie que vous avez défini, donc dans notre cas ce sera `"sgugger/bert-finetuned-ner"`. + + + +💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de la définition de votre `Trainer` et devrez définir un nouveau nom. + + + +Enfin, nous passons tout au `Trainer` et lançons l'entraînement : + +```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() +``` + +Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. De cette façon, vous serez en mesure de reprendre votre entraînement sur une autre machine si nécessaire. + +Une fois l'entraînement terminé, nous utilisons la méthode `push_to_hub()` pour nous assurer que nous téléchargeons la version la plus récente du modèle : + +```py +trainer.push_to_hub(commit_message="Training complete") +``` + +Cette commande renvoie l'URL du commit qu'elle vient de faire, si vous voulez l'inspecter : + +```python out +'https://huggingface.co/sgugger/bert-finetuned-ner/commit/26ab21e5b1568f9afeccdaed2d8715f571d786ed' +``` + +Le `Trainer` rédige également une carte modèle avec tous les résultats de l'évaluation et la télécharge. A ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à affiner un modèle sur une tâche de classification de *tokens*. Félicitations ! + +Si vous voulez plonger un peu plus profondément dans la boucle d'entraînement, nous allons maintenant vous montrer comment faire la même chose en utilisant 🤗 *Accelerate*. + +## Une boucle d'entraînement personnalisée + +Jetons maintenant un coup d'œil à la boucle d'entraînement complète, afin que vous puissiez facilement personnaliser les parties dont vous avez besoin. Elle ressemblera beaucoup à ce que nous avons fait dans le [Chapitre 3](/course/fr/chapter3/4), avec quelques changements pour l'évaluation. + +### Préparer tout pour l'entraînement + +D'abord nous devons construire le `DataLoader`s à partir de nos jeux de données. Nous allons réutiliser notre `data_collator` comme un `collate_fn` et mélanger l'ensemble d'entraînement, mais pas l'ensemble de validation : + +```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 +) +``` + +Ensuite, nous réinstantifions notre modèle, pour nous assurer que nous ne continuons pas le réglage fin d'avant, mais que nous repartons du modèle pré-entraîné de BERT : + +```py +model = AutoModelForTokenClassification.from_pretrained( + model_checkpoint, + id2label=id2label, + label2id=label2id, +) +``` + +Ensuite, nous aurons besoin d'un optimiseur. Nous allons utiliser le classique `AdamW`, qui est comme `Adam`, mais avec un correctif dans la façon dont la décroissance du taux des poids est appliquée : + +```py +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Une fois que nous avons tous ces objets, nous pouvons les envoyer à la méthode `accelerator.prepare()` : + +```py +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + + + +🚨 Si vous vous entraînez sur un TPU, vous devrez déplacer tout le code à partir de la cellule ci-dessus dans une fonction d'entraînement dédiée. Voir le [Chapitre 3](/course/fr/chapter3) pour plus de détails. + + + +Maintenant que nous avons envoyé notre `train_dataloader` à `accelerator.prepare()`, nous pouvons utiliser sa longueur pour calculer le nombre d'étapes d'entraînement. Rappelez-vous que nous devrions toujours faire cela après avoir préparé le *dataloader*, car cette méthode modifiera sa longueur. Nous utilisons un programme linéaire classique du taux d'apprentissage à 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, +) +``` + +Enfin, pour pousser notre modèle vers le Hub, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous à Hugging Face, si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'ID du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix ; il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `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' +``` + +Ensuite, nous pouvons cloner ce référentiel dans un dossier local. S'il existe déjà, ce dossier local doit être un clone existant du référentiel avec lequel nous travaillons : + +```py +output_dir = "bert-finetuned-ner-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output_dir` en appelant la méthode `repo.push_to_hub()`. Cela nous aidera à télécharger les modèles intermédiaires à la fin de chaque époque. + +### Boucle d'entraînement + +Nous sommes maintenant prêts à écrire la boucle d'entraînement complète. Pour simplifier sa partie évaluation, nous définissons cette fonction `postprocess()` qui prend les prédictions et les étiquettes et les convertit en listes de chaînes de caractères, comme notre objet `metric` l'attend : + +```py +def postprocess(predictions, labels): + predictions = predictions.detach().cpu().clone().numpy() + labels = labels.detach().cpu().clone().numpy() + + # Remove ignored index (special tokens) and convert to labels + 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 +``` + +Ensuite, nous pouvons écrire la boucle d'entraînement. Après avoir défini une barre de progression pour suivre l'évolution de l'entraînement, la boucle comporte trois parties : + +- l'entraînement proprement dit, qui est l'itération classique sur le `train_dataloader`, passage en avant du modèle, puis passage en arrière et étape d'optimisation, +- l'évaluation, dans laquelle il y a une nouveauté après avoir obtenu les sorties de notre modèle sur un lot : puisque deux processus peuvent avoir paddé les entrées et les étiquettes à des formes différentes, nous devons utiliser `accelerator.pad_across_processes()` pour rendre les prédictions et les étiquettes de la même forme avant d'appeler la méthode `gather()`. Si nous ne le faisons pas, l'évaluation va soit se tromper, soit se bloquer pour toujours. Ensuite, nous envoyons les résultats à `metric.add_batch()` et appelons `metric.compute()` une fois que la boucle d'évaluation est terminée, +- sauvegarde et téléchargement, où nous sauvegardons d'abord le modèle et le tokenizer, puis appelons `repo.push_to_hub()`. Remarquez que nous utilisons l'argument `blocking=False` pour indiquer à la bibliothèque 🤗 *Hub* de pousser dans un processus asynchrone. De cette façon, l'entraînement continue normalement et cette (longue) instruction est exécutée en arrière-plan. + +Voici le code complet de la boucle d'entraînement : + +```py +from tqdm.auto import tqdm +import torch + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Entraînement + 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) + + # Evaluation + model.eval() + for batch in eval_dataloader: + with torch.no_grad(): + outputs = model(**batch) + + predictions = outputs.logits.argmax(dim=-1) + labels = batch["labels"] + + # Nécessaire pour rembourrer les prédictions et les étiquettes à rassembler + 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"] + }, + ) + + # Sauvegarder et télécharger + 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 + ) +``` + +Au cas où ce serait la première fois que vous verriez un modèle enregistré avec 🤗 *Accelerate*, prenons un moment pour inspecter les trois lignes de code qui l'accompagnent : + +```py +accelerator.wait_for_everyone() +unwrapped_model = accelerator.unwrap_model(model) +unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) +``` + +La première ligne est explicite : elle indique à tous les processus d'attendre que tout le monde soit à ce stade avant de continuer. C'est pour s'assurer que nous avons le même modèle dans chaque processus avant de sauvegarder. Ensuite, nous prenons le `unwrapped_model`, qui est le modèle de base que nous avons défini. La méthode `accelerator.prepare()` modifie le modèle pour qu'il fonctionne dans l'entraînement distribué, donc il n'aura plus la méthode `save_pretrained()` ; la méthode `accelerator.unwrap_model()` annule cette étape. Enfin, nous appelons `save_pretrained()` mais nous disons à cette méthode d'utiliser `accelerator.save()` au lieu de `torch.save()`. + +Une fois ceci fait, vous devriez avoir un modèle qui produit des résultats assez similaires à celui entraîné avec le `Trainer`. Vous pouvez vérifier le modèle que nous avons formé en utilisant ce code à [*huggingface-course/bert-finetuned-ner-accelerate*(https://huggingface.co/huggingface-course/bert-finetuned-ner-accelerate). Et si vous voulez tester des modifications de la boucle d'entraînement, vous pouvez les implémenter directement en modifiant le code ci-dessus ! + +{/if} + +### Utilisation du modèle *finetuné* + +Nous vous avons déjà montré comment vous pouvez utiliser le modèle que nous avons affiné sur le *Hub* avec le *widget* d'inférence. Pour l'utiliser localement dans un `pipeline`, vous devez juste spécifier l'identifiant de modèle approprié : + +```py +from transformers import pipeline + +# Remplacez ceci par votre propre checkpoint +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}] +``` + +Super ! Notre modèle fonctionne aussi bien que le modèle par défaut pour ce pipeline ! diff --git a/chapters/fr/chapter7/3.mdx b/chapters/fr/chapter7/3.mdx new file mode 100644 index 000000000..71c015327 --- /dev/null +++ b/chapters/fr/chapter7/3.mdx @@ -0,0 +1,1042 @@ + + +# *Finetuner* un modèle de langage masqué + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Pour de nombreuses applications de NLP impliquant des *transformers*, vous pouvez simplement prendre un modèle pré-entraîné du *Hub* et l'ajuster directement sur vos données pour la tâche à accomplir. Pour autant que le corpus utilisé pour le pré-entraînement ne soit pas trop différent du corpus utilisé pour le *finetuning*, l'apprentissage par transfert produira généralement de bons résultats. + +Cependant, il existe quelques cas où vous voudrez d'abord affiner les modèles de langue sur vos données, avant d'entraîner une tête spécifique à la tâche. Par exemple, si votre ensemble de données contient des contrats légaux ou des articles scientifiques, un modèle de transformation classique comme BERT traitera généralement les mots spécifiques au domaine dans votre corpus comme des *tokens* rares, et les performances résultantes peuvent être moins que satisfaisantes. En *finetunant* le modèle linguistique sur les données du domaine, vous pouvez améliorer les performances de nombreuses tâches en aval, ce qui signifie que vous ne devez généralement effectuer cette étape qu'une seule fois ! + +Ce processus d'ajustement fin d'un modèle de langage pré-entraîné sur des données *in-domain* est généralement appelé _adaptation au domaine_. Il a été popularisé en 2018 par [ULMFiT(https://arxiv.org/abs/1801.06146), qui a été l'une des premières architectures neuronales (basées sur les LSTM) à faire en sorte que l'apprentissage par transfert fonctionne réellement pour le NLP. Un exemple d'adaptation de domaine avec ULMFiT est présenté dans l'image ci-dessous ; dans cette section, nous ferons quelque chose de similaire, mais avec un *transformer* au lieu d'un LSTM ! + +
+ULMFiT. + +
+ +À la fin de cette section, vous aurez un [modèle de langage masqué](https://huggingface.co/huggingface-course/distilbert-base-uncased-finetuned-imdb?text=This+is+a+great+%5BMASK%5D.) sur le *Hub* qui peut autocompléter des phrases comme indiqué ci-dessous : + + + + +Plongeons-y ! + + + + + +🙋 Si les termes "modélisation du langage masqué" et "modèle pré-entraîné" ne vous sont pas familiers, consultez le [Chapitre 1](/course/fr/chapiter1), où nous expliquons tous ces concepts fondamentaux, vidéos à l'appui ! + + + +## Choix d'un modèle pré-entraîné pour la modélisation du langage masqué + +Pour commencer, nous allons choisir un modèle pré-entraîné approprié pour la modélisation du langage masqué. Comme le montre la capture d'écran suivante, vous pouvez trouver une liste de candidats en appliquant le filtre "Fill-Mask" sur le [*Hub*] (https://huggingface.co/models?pipeline_tag=fill-mask&sort=downloads) : + +
+Hub models. +
+ +Bien que les modèles de la famille BERT et RoBERTa soient les plus téléchargés, nous utiliserons un modèle appelé [DistilBERT](https://huggingface.co/distilbert-base-uncased) +qui peut être entraîné beaucoup plus rapidement avec peu ou pas de perte de performance en aval. Ce modèle a été entraîné à l'aide d'une technique spéciale appelée [_distillation de connaissances_](https://en.wikipedia.org/wiki/Knowledge_distillation), où un grand "modèle maître" comme BERT est utilisé pour guider l'entraînement d'un "modèle élève" qui a beaucoup moins de paramètres. Une explication des détails de la distillation de connaissances nous mènerait trop loin dans cette section, mais si vous êtes intéressé, vous pouvez lire tout cela dans [_Natural Language Processing with Transformers_](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/ch05.html) (familièrement connu comme le manuel Transformers). + +{#if fw === 'pt'} + +Allons-y et téléchargeons DistilBERT en utilisant la classe `AutoModelForMaskedLM` : + +```python +from transformers import AutoModelForMaskedLM + +model_checkpoint = "distilbert-base-uncased" +model = AutoModelForMaskedLM.from_pretrained(model_checkpoint) +``` + +Nous pouvons voir combien de paramètres ce modèle possède en appelant la méthode `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} + +Allons-y et téléchargeons DistilBERT en utilisant la classe `AutoModelForMaskedLM` : + +```python +from transformers import TFAutoModelForMaskedLM + +model_checkpoint = "distilbert-base-uncased" +model = TFAutoModelForMaskedLM.from_pretrained(model_checkpoint) +``` + +Nous pouvons voir combien de paramètres ce modèle possède en appelant la méthode `summary()` : + +```python +model(model.dummy_inputs) # Build the model +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} + +Avec environ 67 millions de paramètres, DistilBERT est environ deux fois plus petit que le modèle de base de BERT, ce qui se traduit approximativement par une accélération de l'entraînement d'un facteur deux - très bien ! Voyons maintenant quels types de *tokens* ce modèle prédit comme étant les compléments les plus probables d'un petit échantillon de texte : + +```python +text = "This is a great [MASK]." +``` + +En tant qu'êtres humains, nous pouvons imaginer de nombreuses possibilités pour le *token* `[MASK]`, telles que "jour", "promenade" ou "peinture". Pour les modèles pré-entraînés, les prédictions dépendent du corpus sur lequel le modèle a été entraîné, puisqu'il apprend à détecter les modèles statistiques présents dans les données. Comme BERT, DistilBERT a été pré-entraîné sur les ensembles de données [English Wikipedia](https://huggingface.co/datasets/wikipedia) et [BookCorpus](https://huggingface.co/datasets/bookcorpus), nous nous attendons donc à ce que les prédictions pour `[MASK]` reflètent ces domaines. Pour prédire le masque, nous avons besoin du *tokenizer* de DistilBERT pour produire les entrées du modèle, alors téléchargeons-le également depuis le *Hub* : + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +Avec un *tokenizer* et un modèle, nous pouvons maintenant passer notre exemple de texte au modèle, extraire les logits, et imprimer les 5 meilleurs candidats : + +{#if fw === 'pt'} + +```python +import torch + +inputs = tokenizer(text, return_tensors="pt") +token_logits = model(**inputs).logits +# Trouvez l'emplacement de [MASK] et extrayez ses logits +mask_token_index = torch.where(inputs["input_ids"] == tokenizer.mask_token_id)[1] +mask_token_logits = token_logits[0, mask_token_index, :] +# Choisissez les candidats [MASK] avec les logits les plus élevés +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 +# Trouvez l'emplacement de [MASK] et extrayez ses logits +mask_token_index = np.argwhere(inputs["input_ids"] == tokenizer.mask_token_id)[0, 1] +mask_token_logits = token_logits[0, mask_token_index, :] +# On choisit les candidats [MASK] avec les logits les plus élevés +# Nous annulons le tableau avant argsort pour obtenir le plus grand, et non le plus petit, logits +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.' +``` + +Nous pouvons voir dans les sorties que les prédictions du modèle se réfèrent à des termes de tous les jours, ce qui n'est peut-être pas surprenant étant donné le fondement de la Wikipédia anglaise. Voyons comment nous pouvons changer ce domaine pour quelque chose d'un peu plus spécialisé : des critiques de films très polarisées ! + + +## Le jeu de données + +Pour illustrer l'adaptation au domaine, nous utiliserons le célèbre [Large Movie Review Dataset](https://huggingface.co/datasets/imdb) (ou IMDb en abrégé), qui est un corpus de critiques de films souvent utilisé pour évaluer les modèles d'analyse de sentiments. En affinant DistilBERT sur ce corpus, nous espérons que le modèle de langage adaptera son vocabulaire des données factuelles de Wikipédia sur lesquelles il a été pré-entraîné aux éléments plus subjectifs des critiques de films. Nous pouvons obtenir les données du *Hub* avec la fonction `load_dataset()` de 🤗 *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 + }) +}) +``` + +Nous pouvons voir que les parties `train` et `test` sont chacune composées de 25 000 critiques, alors qu'il y a une partie non étiquetée appelée `unsupervised` qui contient 50 000 critiques. Jetons un coup d'œil à quelques échantillons pour avoir une idée du type de texte auquel nous avons affaire. Comme nous l'avons fait dans les chapitres précédents du cours, nous allons enchaîner les fonctions `Dataset.shuffle()` et `Dataset.select()` pour créer un échantillon aléatoire : + +```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' +``` + +Oui, ce sont bien des critiques de films, et si vous êtes assez vieux, vous pouvez même comprendre le commentaire dans la dernière critique sur le fait de posséder une version VHS 😜 ! Bien que nous n'ayons pas besoin des étiquettes pour la modélisation du langage, nous pouvons déjà voir qu'un `0` dénote une critique négative, tandis qu'un `1` correspond à une critique positive. + + + +✏️ **Essayez !** Créez un échantillon aléatoire de la répartition `unsupervised` et vérifiez que les étiquettes ne sont ni `0` ni `1`. Pendant que vous y êtes, vous pouvez aussi vérifier que les étiquettes dans les fractions `train` et `test` sont bien `0` ou `1`. C'est un contrôle utile que tout praticien en NLP devrait effectuer au début d'un nouveau projet ! + + + +Maintenant que nous avons jeté un coup d'œil rapide aux données, plongeons dans leur préparation pour la modélisation du langage masqué. Comme nous allons le voir, il y a quelques étapes supplémentaires à suivre par rapport aux tâches de classification de séquences que nous avons vues au [Chapitre 3](/course/fr/chapter3). Allons-y ! + +## Prétraitement des données + + + +Pour la modélisation autorégressive et la modélisation du langage masqué, une étape commune de prétraitement consiste à concaténer tous les exemples, puis à diviser le corpus entier en morceaux de taille égale. C'est très différent de notre approche habituelle, où nous nous contentons de *tokenizer* les exemples individuels. Pourquoi tout concaténer ? La raison est que les exemples individuels peuvent être tronqués s'ils sont trop longs, ce qui entraînerait la perte d'informations qui pourraient être utiles pour la tâche de modélisation du langage ! + +Donc pour commencer, nous allons d'abord tokeniser notre corpus comme d'habitude, mais _sans_ mettre l'option `truncation=True` dans notre *tokenizer*. Nous allons aussi récupérer les IDs des mots s'ils sont disponibles (ce qui sera le cas si nous utilisons un *tokenizer* rapide, comme décrit dans [Chapter 6](/course/fr/chapter6/3)), car nous en aurons besoin plus tard pour faire le masquage des mots entiers. Nous allons envelopper cela dans une simple fonction, et pendant que nous y sommes, nous allons supprimer les colonnes `text` et `label` puisque nous n'en avons plus besoin : + +```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 + + +# Use batched=True to activate fast multithreading! +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 + }) +}) +``` + +Comme DistilBERT est un modèle de type BERT, nous pouvons voir que les textes encodés sont constitués des `input_ids` et des `attention_mask` que nous avons vus dans d'autres chapitres, ainsi que des `word_ids` que nous avons ajoutés. + +Maintenant que nos critiques de films ont été tokenisées, l'étape suivante consiste à les regrouper et à diviser le résultat en chunks. Mais quelle taille doivent avoir ces *chunks* ? Cela sera finalement déterminé par la quantité de mémoire GPU dont vous disposez, mais un bon point de départ est de voir quelle est la taille maximale du contexte du modèle. Cela peut être déduit en inspectant l'attribut `model_max_length` du *tokenizer* : + +```python +tokenizer.model_max_length +``` + +```python out +512 +``` + +Cette valeur est dérivée du fichier *tokenizer_config.json* associé à un point de contrôle ; dans ce cas, nous pouvons voir que la taille du contexte est de 512 *tokens*, tout comme avec BERT. + + + +✏️ **Essayez !** Certains *transformers*, comme [BigBird](https://huggingface.co/google/bigbird-roberta-base) et [Longformer](hf.co/allenai/longformer-base-4096), ont une longueur de contexte beaucoup plus longue que BERT et les autres premiers *transformers*. Instanciez le *tokenizer* pour l'un de ces points de contrôle et vérifiez que le `model_max_length` correspond à ce qui est indiqué sur sa carte. + + + +Ainsi, pour réaliser nos expériences sur des GPU comme ceux de Google Colab, nous choisirons quelque chose d'un peu plus petit qui peut tenir en mémoire : + +```python +chunk_size = 128 +``` + + + +Notez que l'utilisation d'une petite taille de fragment peut être préjudiciable dans les scénarios du monde réel, vous devez donc utiliser une taille qui correspond au cas d'utilisation auquel vous appliquerez votre modèle. + + + +Maintenant vient la partie amusante. Pour montrer comment la concaténation fonctionne, prenons quelques commentaires de notre ensemble d'entraînement et imprimons le nombre de *tokens* par commentaire : + +```python +# Le découpage produit une liste de listes pour chaque caractéristique +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' +``` + +Nous pouvons ensuite concaténer tous ces exemples avec une simple compréhension du dictionnaire, comme suit : + +```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' +``` + +Super, la longueur totale est correcte. Donc maintenant, nous allons diviser les exemples concaténés en morceaux de la taille donnée par `block_size`. Pour ce faire, nous itérons sur les caractéristiques de `concatenated_examples` et utilisons une compréhension de liste pour créer des tranches de chaque caractéristique. Le résultat est un dictionnaire de *chunks* pour chaque caractéristique : + +```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' +``` + +Comme vous pouvez le voir dans cet exemple, le dernier *chunk* sera généralement plus petit que la taille maximale des morceaux. Il y a deux stratégies principales pour gérer cela : + +* abandonner le dernier morceau s'il est plus petit que `chunk_size`. +* remplir le dernier morceau jusqu'à ce que sa longueur soit égale à `chunk_size`. + +Nous adopterons la première approche ici, donc nous allons envelopper toute la logique ci-dessus dans une seule fonction que nous pouvons appliquer à nos jeux de données tokenisés : + +```python +def group_texts(examples): + # Concaténation de tous les textes + concatenated_examples = {k: sum(examples[k], []) for k in examples.keys()} + # Calculer la longueur des textes concaténés + total_length = len(concatenated_examples[list(examples.keys())[0]]) + # Nous laissons tomber le dernier morceau s'il est plus petit que chunk_size + total_length = (total_length // chunk_size) * chunk_size + # Split by chunks of 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() + } + # Create a new labels column + result["labels"] = result["input_ids"].copy() + return result +``` + +Notez que dans la dernière étape de `group_texts()` nous créons une nouvelle colonne `labels` qui est une copie de la colonne `input_ids`. Comme nous le verrons bientôt, c'est parce que dans la modélisation du langage masqué, l'objectif est de prédire des *tokens* masqués aléatoirement dans le batch d'entrée, et en créant une colonne `labels`, nous fournissons la vérité de base pour notre modèle de langage à apprendre. + +Appliquons maintenant `group_texts()` à nos jeux de données tokenisés en utilisant notre fidèle fonction `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 + }) +}) +``` + +Vous pouvez voir que le regroupement puis le découpage des textes a produit beaucoup plus d'exemples que nos 25 000 exemples initiaux pour les divisions `train` et `test`. C'est parce que nous avons maintenant des exemples impliquant des "*tokens* contigus" qui s'étendent sur plusieurs exemples du corpus original. Vous pouvez le voir explicitement en cherchant les *tokens* spéciaux `[SEP]` et `[CLS]` dans l'un des *chunks* : + +```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" +``` + +Dans cet exemple, vous pouvez voir deux critiques de films qui se chevauchent, l'une sur un film de lycée et l'autre sur les sans-abri. Voyons également à quoi ressemblent les étiquettes pour la modélisation du langage masqué : + +```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" +``` + +Comme prévu par notre fonction `group_texts()` ci-dessus, cela semble identique aux `input_ids` décodés - mais alors comment notre modèle peut-il apprendre quoi que ce soit ? Il nous manque une étape clé : insérer des *tokens* à des positions aléatoires dans les entrées ! Voyons comment nous pouvons le faire à la volée pendant le réglage fin en utilisant un collateur de données spécial. + +## *Finetuning* de DistilBERT avec l'API `Trainer`. + +Le *finetuning* d'un modèle de langage masqué est presque identique au *finetuning* d'un modèle de classification de séquences, comme nous l'avons fait dans le [Chapitre 3](/course/fr/chapter3). La seule différence est que nous avons besoin d'un collecteur de données spécial qui peut masquer de manière aléatoire certains des *tokens* dans chaque lot de textes. Heureusement, 🤗 *Transformers* est livré préparé avec un `DataCollatorForLanguageModeling` dédié à cette tâche. Nous devons juste lui passer le *tokenizer* et un argument `mlm_probability` qui spécifie quelle fraction des *tokens* à masquer. Nous choisirons 15%, qui est la quantité utilisée pour BERT et un choix commun dans la littérature : + +```python +from transformers import DataCollatorForLanguageModeling + +data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm_probability=0.15) +``` + +Pour voir comment le masquage aléatoire fonctionne, nous allons donner quelques exemples au compilateur de données. Puisqu'il s'attend à une liste de `dict`s, où chaque `dict` représente un seul morceau de texte contigu, nous itérons d'abord sur le jeu de données avant de nourrir le lot au collateur. Nous supprimons la clé `"word_ids"` pour ce collateur de données car il ne l'attend pas : + +```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' +``` + +Super, ça a marché ! Nous pouvons voir que le *token* `[MASK]` a été inséré de façon aléatoire à différents endroits dans notre texte. Ce seront les *tokens* que notre modèle devra prédire pendant l'entraînement. Et la beauté du collecteur de données est qu'il va rendre aléatoire l'insertion du `[MASK]` à chaque lot ! + + + +✏️ **Essayez** Exécutez le code ci-dessus plusieurs fois pour voir le masquage aléatoire se produire sous vos yeux ! Remplacez aussi la méthode `tokenizer.decode()` par `tokenizer.convert_ids_to_tokens()` pour voir que parfois un seul *token* d'un mot donné est masqué, et pas les autres. + + + +{#if fw === 'pt'} + +Un effet secondaire du masquage aléatoire est que nos métriques d'évaluation ne seront pas déterministes lorsque nous utilisons le `Trainer`, puisque nous utilisons le même collateur de données pour les ensembles d'entraînement et de test. Nous verrons plus tard, lorsque nous examinerons le *finetuning* avec 🤗 *Accelerate*, comment nous pouvons utiliser la flexibilité d'une boucle d'évaluation personnalisée pour geler le caractère aléatoire. + +{/if} + +Lors de l'entraînement des modèles pour la modélisation du langage masqué, une technique qui peut être utilisée est de masquer des mots entiers ensemble, et pas seulement des *tokens* individuels. Cette approche est appelée _masquage de mots entiers_. Si nous voulons utiliser le masquage de mots entiers, nous devons construire nous-mêmes un collateur de données. Un collateur de données est simplement une fonction qui prend une liste d'échantillons et les convertit en un lot, alors faisons-le maintenant ! Nous utiliserons les IDs des mots calculés plus tôt pour faire une correspondance entre les indices des mots et les *tokens* correspondants, puis nous déciderons aléatoirement quels mots masquer et appliquerons ce masque sur les entrées. Notez que les étiquettes sont toutes `-100` sauf celles qui correspondent aux mots masqués. + +{#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") + + # Création d'une carte entre les mots et les indices des tokens correspondants. + 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) + + # Masquer des mots de façon aléatoire + 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 + + return default_data_collator(features) +``` + +{:else} + +```py +import collections +import numpy as np + +from transformers.data 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") + + # Création d'une carte entre les mots et les indices des tokens correspondants. + 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) + + # Masquer des mots de façon aléatoire + 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 + + return tf_default_data_collator(features) +``` + +{/if} + +Ensuite, nous pouvons l'essayer sur les mêmes échantillons que précédemment : + +```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' +``` + + + +✏️ **Essayez** Exécutez le code ci-dessus plusieurs fois pour voir le masquage aléatoire se produire sous vos yeux ! Remplacez aussi la méthode `tokenizer.decode()` par `tokenizer.convert_ids_to_tokens()` pour voir que les *tokens* d'un mot donné sont toujours masqués ensemble. + + + +Maintenant que nous avons deux collateurs de données, le reste des étapes de mise au point est standard. L'entraînement peut prendre un certain temps sur Google Colab si vous n'avez pas la chance d'avoir un mythique GPU P100 😭, donc nous allons d'abord réduire la taille de l'ensemble d'entraînement à quelques milliers d'exemples. Ne vous inquiétez pas, nous obtiendrons quand même un modèle de langage assez décent ! Un moyen rapide de réduire la taille d'un jeu de données dans 🤗 *Datasets* est la fonction `Dataset.train_test_split()` que nous avons vue au [Chapitre 5](/course/fr/chapter5) : + +```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 + }) +}) +``` + +Cela a automatiquement créé de nouvelles divisions `train` et `test`, avec la taille de l'ensemble d'entraînement fixée à 10.000 exemples et la validation fixée à 10% de cela. N'hésitez pas à augmenter cela si vous avez un GPU puissant ! La prochaine chose que nous devons faire est de nous connecter au *Hub*. Si vous exécutez ce code dans un *notebook*, vous pouvez le faire avec la fonction utilitaire suivante : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +qui affichera un *widget* où vous pourrez saisir vos informations d'identification. Alternativement, vous pouvez exécuter : + +``` +huggingface-cli login +``` + +dans votre terminal préféré et connectez-vous là. + +{#if fw === 'tf'} + +Une fois que nous sommes connectés, nous pouvons créer nos jeux de données `tf.data`. Nous n'utiliserons ici que le collecteur de données standard, mais vous pouvez également essayer le collecteur de masquage de mots entiers et comparer les résultats à titre d'exercice : + +```python +tf_train_dataset = downsampled_dataset["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) + +tf_eval_dataset = downsampled_dataset["test"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=32, +) +``` + +Ensuite, nous configurons nos hyperparamètres d'entraînement et compilons notre modèle. Nous utilisons la fonction `create_optimizer()` de la bibliothèque 🤗 *Transformers*, qui nous donne un optimiseur `AdamW` avec une décroissance linéaire du taux d'apprentissage. Nous utilisons également la perte intégrée au modèle, qui est la perte par défaut lorsqu'aucune perte n'est spécifiée comme argument de `compile()`, et nous définissons la précision d'entraînement à `"mixed_float16"`. Notez que si vous utilisez un GPU Colab ou un autre GPU qui n'a pas le support accéléré de float16, vous devriez probablement commenter cette ligne. + +De plus, nous mettons en place un `PushToHubCallback` qui sauvegardera le modèle sur le Hub après chaque époque. Vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, pour pousser le modèle vers l'organisation [`huggingface-course`] (https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/distilbert-finetuned-imdb"`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, donc dans notre cas, ce sera `"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) + +# Train in mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") + +callback = PushToHubCallback( + output_dir=f"{model_name}-finetuned-imdb", tokenizer=tokenizer +) +``` + +Nous sommes maintenant prêts à exécuter `model.fit()`. Mais avant de le faire, regardons brièvement la _perplexité_, qui est une métrique commune pour évaluer la performance des modèles de langage. + +{:else} + +Une fois que nous sommes connectés, nous pouvons spécifier les arguments pour le `Trainer` : + +```python +from transformers import TrainingArguments + +batch_size = 64 +# Montrer la perte d'entraînement à chaque époque. +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, +) +``` + +Ici, nous avons modifié quelques options par défaut, y compris `logging_steps` pour s'assurer que nous suivons la perte d'entraînement à chaque époque. Nous avons également utilisé `fp16=True` pour activer l'entraînement en précision mixte, ce qui nous donne un autre gain de vitesse. Par défaut, le `Trainer` va supprimer toutes les colonnes qui ne font pas partie de la méthode `forward()` du modèle. Cela signifie que si vous utilisez le collateur de masquage de mots entiers, vous devrez également définir `remove_unused_columns=False` pour vous assurer que nous ne perdons pas la colonne `word_ids` pendant l'entraînement. + +Notez que vous pouvez spécifier le nom du référentiel vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/distilbert-finetuned-imdb"``TrainingArguments`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, donc dans notre cas ce sera `"lewtun/distilbert-finetuned-imdb"`. + +Nous avons maintenant tous les ingrédients pour instancier le `Trainer`. Ici, nous utilisons juste le collateur standard `data_collator`, mais vous pouvez essayer le collateur de masquage de mots entiers et comparer les résultats comme exercice : + +```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, +) +``` + +Nous sommes maintenant prêts à exécuter `trainer.train()` . Mais avant de le faire, regardons brièvement la _perplexité_, qui est une métrique commune pour évaluer la performance des modèles de langage. + +{/if} + +### Perplexité pour les modèles de langage + + + +Contrairement à d'autres tâches, comme la classification de textes ou la réponse à des questions, sur lesquelles nous disposons d'un corpus étiqueté pour nous entraîner, la modélisation du langage ne s'appuie sur aucune étiquette explicite. Alors comment déterminer ce qui fait un bon modèle de langage ? Comme pour la fonction de correction automatique de votre téléphone, un bon modèle de langage est celui qui attribue des probabilités élevées aux phrases grammaticalement correctes et des probabilités faibles aux phrases absurdes. Pour vous donner une meilleure idée de ce à quoi cela ressemble, vous pouvez trouver en ligne des séries entières de "ratés d'autocorrection", où le modèle du téléphone d'une personne a produit des compléments plutôt amusants (et souvent inappropriés) ! + +{#if fw === 'pt'} + +En supposant que notre ensemble de test se compose principalement de phrases grammaticalement correctes, une façon de mesurer la qualité de notre modèle de langage est de calculer les probabilités qu'il attribue au mot suivant dans toutes les phrases de l'ensemble de test. Des probabilités élevées indiquent que le modèle n'est pas "surpris" ou "perplexe" par les exemples non vus, et suggèrent qu'il a appris les modèles de base de la grammaire de la langue. Il existe plusieurs définitions mathématiques de la perplexité, mais celle que nous utiliserons la définit comme l'exponentielle de la perte d'entropie croisée. Ainsi, nous pouvons calculer la perplexité de notre modèle pré-entraîné en utilisant la fonction `Trainer.evaluate()` pour calculer la perte d'entropie croisée sur l'ensemble de test, puis en prenant l'exponentielle du résultat : + +```python +import math + +eval_results = trainer.evaluate() +print(f">>> Perplexity: {math.exp(eval_results['eval_loss']):.2f}") +``` + +{:else} + +En supposant que notre ensemble de test se compose principalement de phrases grammaticalement correctes, une façon de mesurer la qualité de notre modèle de langage est de calculer les probabilités qu'il attribue au mot suivant dans toutes les phrases de l'ensemble de test. Des probabilités élevées indiquent que le modèle n'est pas "surpris" ou "perplexe" par les exemples non vus, et suggèrent qu'il a appris les modèles de base de la grammaire de la langue. Il existe plusieurs définitions mathématiques de la perplexité, mais celle que nous utiliserons la définit comme l'exponentielle de la perte d'entropie croisée. Ainsi, nous pouvons calculer la perplexité de notre modèle pré-entraîné en utilisant la méthode `model.evaluate()` pour calculer la perte d'entropie croisée sur l'ensemble de test, puis en prenant l'exponentielle du résultat : + +```python +import math + +eval_loss = model.evaluate(tf_eval_dataset) +print(f"Perplexity: {math.exp(eval_loss):.2f}") +``` + +{/if} + +```python out +>>> Perplexity: 21.75 +``` + +Un score de perplexité plus faible signifie un meilleur modèle de langue, et nous pouvons voir ici que notre modèle de départ a une valeur assez élevée. Voyons si nous pouvons la réduire en l'affinant ! Pour ce faire, nous commençons par exécuter la boucle d'entraînement : + +{#if fw === 'pt'} + +```python +trainer.train() +``` + +{:else} + +```python +model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback]) +``` + +{/if} + +et ensuite calculer la perplexité résultante sur l'ensemble de test comme précédemment : + +{#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 +``` + +Joli. C'est une réduction considérable de la perplexité, ce qui nous indique que le modèle a appris quelque chose sur le domaine des critiques de films ! + +{#if fw === 'pt'} + +Une fois l'entraînement terminé, nous pouvons pousser la carte modèle avec les informations d'entraînement vers le Hub (les points de contrôle sont sauvegardés pendant l'entraînement lui-même) : + +```python +trainer.push_to_hub() +``` + +{/if} + + + +✏️ **Votre tour !** Exécutez l'entraînement ci-dessus après avoir remplacé le collecteur de données par le collecteur de mots entiers masqués. Obtenez-vous de meilleurs résultats ? + + + +{#if fw === 'pt'} + +Dans notre cas d'utilisation, nous n'avons pas eu besoin de faire quelque chose de spécial avec la boucle d'entraînement, mais dans certains cas, vous pourriez avoir besoin de mettre en œuvre une logique personnalisée. Pour ces applications, vous pouvez utiliser 🤗 *Accelerate*. Jetons un coup d'œil ! + +## *Finetuning* de DistilBERT avec 🤗 Accelerate + +Comme nous l'avons vu avec le `Trainer`, le réglage fin d'un modèle de langage masqué est très similaire à l'exemple de classification de texte du [Chapitre 3](/course/fr/chapter3). En fait, la seule subtilité est l'utilisation d'un collateur de données spécial, et nous l'avons déjà couvert plus tôt dans cette section ! + +Cependant, nous avons vu que `DataCollatorForLanguageModeling` applique aussi un masquage aléatoire à chaque évaluation, donc nous verrons quelques fluctuations dans nos scores de perplexité à chaque entrainement. Une façon d'éliminer cette source d'aléatoire est d'appliquer le masquage _une fois_ sur l'ensemble de test, puis d'utiliser le collateur de données par défaut dans 🤗 *Transformers* pour collecter les lots pendant l'évaluation. Pour voir comment cela fonctionne, implémentons une fonction simple qui applique le masquage sur un lot, similaire à notre première rencontre avec `DataCollatorForLanguageModeling` : + +```python +def insert_random_mask(batch): + features = [dict(zip(batch, t)) for t in zip(*batch.values())] + masked_inputs = data_collator(features) + # Create a new "masked" column for each column in the dataset + return {"masked_" + k: v.numpy() for k, v in masked_inputs.items()} +``` + +Ensuite, nous allons appliquer cette fonction à notre jeu de test et laisser tomber les colonnes non masquées afin de les remplacer par les colonnes masquées. Vous pouvez utiliser le masquage de mots entiers en remplaçant le `data_collator` ci-dessus par celui qui est approprié, dans ce cas vous devez supprimer la première ligne ici : + +```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", + } +) +``` + +Nous pouvons ensuite configurer les *dataloaders* comme d'habitude, mais nous utiliserons le `default_data_collator` de 🤗 *Transformers* pour le jeu d'évaluation : + +```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 +) +``` + +Forme ici, nous suivons les étapes standard avec 🤗 *Accelerate*. Le premier ordre du jour est de charger une version fraîche du modèle pré-entraîné : + +``` +model = AutoModelForMaskedLM.from_pretrained(model_checkpoint) +``` + +Ensuite, nous devons spécifier l'optimiseur ; nous utiliserons le standard `AdamW` : + +```python +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=5e-5) +``` + +Avec ces objets, nous pouvons maintenant tout préparer pour l'entraînement avec l'objet `Accelerator` : + +```python +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + +Maintenant que notre modèle, notre optimiseur et nos chargeurs de données sont configurés, nous pouvons spécifier le planificateur du taux d'apprentissage comme suit : + +```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, +) +``` + +Il ne reste qu'une dernière chose à faire avant de s'entraîner : créer un dépôt de modèles sur le *Hub* d'Hugging Face ! Nous pouvons utiliser la bibliothèque 🤗 *Hub* pour générer d'abord le nom complet de notre dépôt : + +```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' +``` + +puis créer et cloner le référentiel en utilisant la classe `Repository` du 🤗 *Hub* : + +```python +from huggingface_hub import Repository + +output_dir = model_name +repo = Repository(output_dir, clone_from=repo_name) +``` + +Une fois cela fait, il ne reste plus qu'à rédiger la boucle complète d'entraînement et d'évaluation : + +```python +from tqdm.auto import tqdm +import torch +import math + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Entraînement + 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) + + # Evaluation + 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}") + + # Sauvegarder et télécharger + 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 +``` + +Cool, nous avons été en mesure d'évaluer la perplexité à chaque époque et de garantir la reproductibilité des entraînements multiples ! + +{/if} + +### Utilisation de notre modèle *finetuné* + +Vous pouvez interagir avec votre modèle affiné soit en utilisant son *widget* sur le *Hub*, soit localement avec le `pipeline` de 🤗 *Transformers*. Utilisons cette dernière pour télécharger notre modèle en utilisant le pipeline `fill-mask` : + +```python +from transformers import pipeline + +mask_filler = pipeline( + "fill-mask", model="huggingface-course/distilbert-base-uncased-finetuned-imdb" +) +``` + +Nous pouvons ensuite alimenter le pipeline avec notre exemple de texte "C'est un grand [MASK]" et voir quelles sont les 5 premières prédictions : + +```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.' +``` + +Notre modèle a clairement adapté ses pondérations pour prédire les mots qui sont plus fortement associés aux films ! + + + +Ceci conclut notre première expérience d'entraînement d'un modèle de langage. Dans la [section 6](/course/fr/chapter7/section6), vous apprendrez comment entraîner un modèle autorégressif comme GPT-2 à partir de zéro ; allez-y si vous voulez voir comment vous pouvez pré-entraîner votre propre *transformer* ! + + + +✏️ **Essayez !** Pour quantifier les avantages de l'adaptation au domaine, ajustez un classificateur sur les étiquettes IMDb pour les points de contrôle DistilBERT pré-entraînés et ajustés. Si vous avez besoin d'un rafraîchissement sur la classification de texte, consultez le [Chapitre 3](/course/fr/chapter3). + + diff --git a/chapters/fr/chapter7/4.mdx b/chapters/fr/chapter7/4.mdx new file mode 100644 index 000000000..48d83a6da --- /dev/null +++ b/chapters/fr/chapter7/4.mdx @@ -0,0 +1,999 @@ + + +# Traduction + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Plongeons maintenant dans la traduction. Il s'agit d'une autre [tâche de séquence à séquence](/cours/fr/chapitre1/7), ce qui signifie que c'est un problème qui peut être formulé comme le passage d'une séquence à une autre. En ce sens, le problème est assez proche de la tâche de [résumé](/cours/fr/chapitre7/6) et vous pouvez adapter ce que nous allons voir ici à d'autres problèmes de séquence à séquence tels que : + +- le **transfert de style** : créer un modèle qui *traduit* des textes écrits dans un certain style vers un autre (par exemple, du formel au décontracté ou de l'anglais shakespearien à l'anglais moderne). +- la **génération de réponse à des questions** : Création d'un modèle qui génère des réponses à des questions, compte tenu d'un contexte. + + + +Si vous disposez d'un corpus suffisamment important de textes en deux langues (ou plus), vous pouvez entraîner un nouveau modèle de traduction à partir de zéro, comme nous le ferons dans la section sur la [modélisation causale du langage](/cours/fr/chapitre7/6). Il est toutefois plus rapide de *finetuner* un modèle de traduction existant, qu'il s'agisse d'un modèle multilingue comme mT5 ou mBART que vous souhaitez adapter à une paire de langues spécifique, ou même d'un modèle spécialisé dans la traduction d'une langue vers une autre que vous souhaitez adapter à votre corpus spécifique. + +Dans cette section, nous allons *finetuner* un modèle Marian pré-entraîné pour traduire de l'anglais au français (puisque de nombreux employés de Hugging Face parlent ces deux langues) sur le [KDE4 dataset](https://huggingface.co/datasets/kde4), qui est un jeu de données de fichiers localisés pour les [KDE apps](https://apps.kde.org/). Le modèle que nous utiliserons a été pré-entraîné sur un large corpus de textes français et anglais provenant du [jeu de données Opus](https://opus.nlpl.eu/), qui contient en fait le jeu de données KDE4. Mais même si le modèle pré-entraîné que nous utilisons a vu ces données pendant son pré-entraînement, nous verrons que nous pouvons obtenir une meilleure version de ce modèle après un *finetuning*. + +Une fois que nous aurons terminé, nous aurons un modèle capable de faire des prédictions comme celle-ci : + + + + + +One-hot encoded labels for question answering. + + + +Comme dans les sections précédentes, vous pouvez trouver le modèle réel que nous allons entraîner et télécharger sur le *Hub* en utilisant le code ci-dessous et vérifier ses prédictions [ici](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr?text=This+plugin+allows+you+to+automatically+translate+web+pages+between+several+languages.). + +## Préparation des données + +Pour affiner ou entraîner un modèle de traduction à partir de zéro, nous avons besoin d'un jeu de données adapté à cette tâche. Comme mentionné précédemment, nous utiliserons le jeu de données [KDE4](https://huggingface.co/datasets/kde4) dans cette section, mais vous pouvez adapter le code pour utiliser vos propres données assez facilement, tant que vous avez des paires de phrases dans les deux langues que vous voulez traduire de et vers. Reportez-vous au [Chapitre 5](/course/fr/chapter5) si vous avez besoin d'un rappel sur la façon de charger vos données personnalisées dans un `Dataset`. + +### Le jeu de données KDE4 + +Comme d'habitude, nous téléchargeons notre jeu de données en utilisant la fonction `load_dataset()` : + +```py +from datasets import load_dataset, load_metric + +raw_datasets = load_dataset("kde4", lang1="en", lang2="fr") +``` + +Si vous souhaitez travailler avec une autre paire de langues, vous pouvez les spécifier par leurs codes. Au total, 92 langues sont disponibles pour cet ensemble de données ; vous pouvez les voir toutes en développant les étiquettes de langue sur sa [fiche](https://huggingface.co/datasets/kde4). + +Language available for the KDE4 dataset. + +Jetons un coup d'œil au jeu de données : + +```py +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['id', 'translation'], + num_rows: 210173 + }) +}) +``` + +Nous avons 210 173 paires de phrases, mais dans un seul split, donc nous devrons créer notre propre ensemble de validation. Comme nous l'avons vu dans le [Chapitre 5](/course/fr/chapter5), un `Dataset` possède une méthode `train_test_split()` qui peut nous aider. Nous allons fournir une graine pour la reproductibilité : + +```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 + }) +}) +``` + +Nous pouvons renommer la clé "test" en "validation" comme ceci : + +```py +split_datasets["validation"] = split_datasets.pop("test") +``` + +Examinons maintenant un élément de ce jeu de données : + +```py +split_datasets["train"][1]["translation"] +``` + +```python out +{'en': 'Default to expanded threads', + 'fr': 'Par défaut, développer les fils de discussion'} +``` + +Nous obtenons un dictionnaire contenant deux phrases dans la paire de langues demandée. +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 par le plus correct "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 la plus facile de laisser le mot tel quel : + +```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'}] +``` + +Un autre exemple de ce comportement peut être observé avec le mot "*plugin*", qui n'est pas officiellement un mot français mais que la plupart des locuteurs natifs comprendront et ne prendront pas la peine de traduire. +Dans le jeu de données KDE4, ce mot a été traduit en français par le plus officiel "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."} +``` + +Notre modèle pré-entraîné, cependant, s'en tient au mot anglais compact et familier : + +```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."}] +``` + +Il sera intéressant de voir si notre modèle *finetuné* tient compte de ces particularités de l'ensemble de données (alerte *spoiler* : il le fera). + + + + + +✏️ **Votre tour !** Un autre mot anglais souvent utilisé en français est "email". Trouvez le premier échantillon dans l'ensemble de données d'entraînement qui utilise ce mot. Comment est-il traduit ? Comment le modèle pré-entraîné traduit-il la même phrase anglaise ? + + + +### Traitement des données + + + +Vous devriez maintenant connaître le principe : les textes doivent tous être convertis en ensembles d'ID de *tokens* pour que le modèle puisse leur donner un sens. Pour cette tâche, nous aurons besoin de tokeniser les entrées et les cibles. Notre première tâche est de créer notre objet `tokenizer`. Comme indiqué précédemment, nous utiliserons un modèle pré-entraîné Marian English to French. Si vous essayez ce code avec une autre paire de langues, assurez-vous d'adapter le *checkpoint* du modèle. L'organisation [Helsinki-NLP](https://huggingface.co/Helsinki-NLP) fournit plus de mille modèles dans plusieurs langues. + +```python +from transformers import AutoTokenizer + +model_checkpoint = "Helsinki-NLP/opus-mt-en-fr" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, return_tensors="tf") +``` + +Vous pouvez également remplacer le `model_checkpoint` par tout autre modèle que vous préférez à partir du [*Hub*](https://huggingface.co/models), ou un dossier local où vous avez sauvegardé un modèle pré-entraîné et un *tokenizer*. + + + +💡 Si vous utilisez un *tokenizer* multilingue tel que mBART, mBART-50, ou M2M100, vous devrez définir les codes de langue de vos entrées et cibles dans le *tokenizer* en définissant `tokenizer.src_lang` et `tokenizer.tgt_lang` aux bonnes valeurs. + + + +La préparation de nos données est assez simple. Il y a juste une chose à retenir : vous traitez les entrées comme d'habitude, mais pour les cibles, vous devez envelopper le *tokenizer* dans le gestionnaire de contexte `as_target_tokenizer()`. + +Un gestionnaire de contexte en Python est introduit avec l'instruction `with` et est utile lorsque vous avez deux opérations liées à exécuter en paire. L'exemple le plus courant est lorsque vous écrivez ou lisez un fichier, ce qui est souvent fait dans une instruction comme : + +``` +with open(file_path) as f: + content = f.read() +``` + +Ici, les deux opérations connexes qui sont exécutées en paire sont les actions d'ouverture et de fermeture du fichier. L'objet correspondant au fichier ouvert `f` n'existe qu'à l'intérieur du bloc indenté sous le `with` ; l'ouverture se produit avant ce bloc et la fermeture à la fin du bloc. + +Dans le cas présent, le gestionnaire de contexte `as_target_tokenizer()` va définir le *tokenizer* dans la langue de sortie (ici, le français) avant l'exécution du bloc indenté, puis le redéfinir dans la langue d'entrée (ici, l'anglais). + +Ainsi, le prétraitement d'un échantillon ressemble à ceci : + +```python +en_sentence = split_datasets["train"][1]["translation"]["en"] +fr_sentence = split_datasets["train"][1]["translation"]["fr"] + +inputs = tokenizer(en_sentence) +with tokenizer.as_target_tokenizer(): + targets = tokenizer(fr_sentence) +``` + +Si nous oublions de tokeniser les cibles dans le gestionnaire de contexte, elles seront tokenisées par le *tokenizer* d'entrée, ce qui, dans le cas d'un modèle marial, ne va pas du tout bien se passer : + +```python +wrong_targets = tokenizer(fr_sentence) +print(tokenizer.convert_ids_to_tokens(wrong_targets["input_ids"])) +print(tokenizer.convert_ids_to_tokens(targets["input_ids"])) +``` + +```python out +['▁Par', '▁dé', 'f', 'aut', ',', '▁dé', 've', 'lop', 'per', '▁les', '▁fil', 's', '▁de', '▁discussion', ''] +['▁Par', '▁défaut', ',', '▁développer', '▁les', '▁fils', '▁de', '▁discussion', ''] +``` + +Comme on peut le voir, utiliser le *tokenizer* anglais pour prétraiter une phrase française donne un batch de *tokens* plus important, puisque le *tokenizer* ne connaît aucun mot français (sauf ceux qui apparaissent aussi en anglais, comme "discussion"). + +Les `inputs` et les `targets` sont des dictionnaires avec nos clés habituelles (identifiants d'entrée, masque d'attention, etc.), donc la dernière étape est de définir une clé `"labels"` dans les entrées. Nous faisons cela dans la fonction de prétraitement que nous allons appliquer sur les jeux de données : + +```python +max_input_length = 128 +max_target_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, max_length=max_input_length, truncation=True) + + # Set up the tokenizer for targets + with tokenizer.as_target_tokenizer(): + labels = tokenizer(targets, max_length=max_target_length, truncation=True) + + model_inputs["labels"] = labels["input_ids"] + return model_inputs +``` + +Notez que nous avons fixé des longueurs maximales similaires pour nos entrées et nos sorties. Comme les textes que nous traitons semblent assez courts, nous utilisons 128. + + + +💡 Si vous utilisez un modèle T5 (plus précisément, un des points de contrôle `t5-xxx`), le modèle s'attendra à ce que les entrées de texte aient un préfixe indiquant la tâche à accomplir, comme Si vous utilisez un modèle T5 (plus précisément, un des points de contrôle `t5-xxx`), le modèle s'attendra à ce que les entrées de texte aient un préfixe indiquant la tâche à accomplir, comme `translate : Anglais vers Français:`.. + + + + + +⚠️ Nous ne faisons pas attention au masque d'attention des cibles, car le modèle ne s'y attend pas. Au lieu de cela, les étiquettes correspondant à un *token* de *padding* doivent être mises à `-100` afin qu'elles soient ignorées dans le calcul de la perte. Cela sera fait par notre collateur de données plus tard puisque nous appliquons le *padding* dynamique, mais si vous utilisez le *padding* ici, vous devriez adapter la fonction de prétraitement pour mettre tous les labels qui correspondent au *token* de *padding* à `-100`. + + + +Nous pouvons maintenant appliquer ce prétraitement en une seule fois sur toutes les divisions de notre jeu de données : + +```py +tokenized_datasets = split_datasets.map( + preprocess_function, + batched=True, + remove_columns=split_datasets["train"].column_names, +) +``` + +Maintenant que les données ont été prétraitées, nous sommes prêts à *finetuner* notre modèle pré-entraîné ! + +{#if fw === 'pt'} + +## *Finetuner* le modèle avec l'API `Trainer`. + +Le code actuel utilisant le `Trainer` sera le même que précédemment, avec juste un petit changement : nous utilisons ici un [`Seq2SeqTrainer`](https://huggingface.co/transformers/main_classes/trainer.html#seq2seqtrainer), qui est une sous-classe de `Trainer` qui nous permettra de traiter correctement l'évaluation, en utilisant la méthode `generate()` pour prédire les sorties à partir des entrées. Nous y reviendrons plus en détail lorsque nous parlerons du calcul de la métrique. + +Tout d'abord, nous avons besoin d'un modèle réel à affiner. Nous allons utiliser l'API habituelle `AutoModel` : + +```py +from transformers import AutoModelForSeq2SeqLM + +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{:else} + +## *Finetuning* du modèle avec Keras + +Tout d'abord, nous avons besoin d'un modèle réel à *finetuner*. Nous allons utiliser l'API habituelle `AutoModel` : + +```py +from transformers import TFAutoModelForSeq2SeqLM + +model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint, from_pt=True) +``` + + + +💡 Le *checkpoint* `Helsinki-NLP/opus-mt-en-fr` ne dispose que de poids PyTorch, donc vous aurez une erreur si vous essayez de charger le modèle sans utiliser l'argument +`from_pt=True` dans la méthode `from_pretrained()`. Lorsque vous spécifiez `from_pt=True`, la bibliothèque téléchargera et convertira automatiquement les poids PyTorch pour vous. Comme vous pouvez le constater, il est très simple de passer d'un framework à l'autre dans 🤗 *Transformers* ! + + + +{/if} + +Notez que cette fois-ci, nous utilisons un modèle qui a été entraîné sur une tâche de traduction et qui peut déjà être utilisé, donc il n'y a pas d'avertissement concernant les poids manquants ou ceux nouvellement initialisés. + +### Collecte des données + +Nous aurons besoin d'un assembleur de données pour gérer le rembourrage pour la mise en lots dynamique. Nous ne pouvons pas simplement utiliser un `DataCollatorWithPadding` comme dans [Chapter 3](/course/fr/chapter3) dans ce cas, parce que cela ne rembourre que les entrées (ID d'entrée, masque d'attention, et ID de type de jeton). Nos étiquettes doivent également être rembourrées à la longueur maximale rencontrée dans les étiquettes. Et, comme mentionné précédemment, la valeur de remplissage utilisée pour remplir les étiquettes doit être `-100` et non le jeton de remplissage du *tokenizer*, pour s'assurer que ces valeurs remplies sont ignorées dans le calcul de la perte. + +Tout ceci est réalisé par un [`DataCollatorForSeq2Seq`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorforseq2seq). Comme le `DataCollatorWithPadding`, il prend le `tokenizer` utilisé pour prétraiter les entrées, mais il prend aussi le `model`. C'est parce que ce collateur de données sera également responsable de la préparation des ID d'entrée du décodeur, qui sont des versions décalées des étiquettes avec un jeton spécial au début. Comme ce décalage est effectué de manière légèrement différente selon les architectures, le `DataCollatorForSeq2Seq` a besoin de connaître l'objet `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} + +Pour le tester sur quelques échantillons, nous l'appelons simplement sur une liste d'exemples de notre ensemble d'entrainement tokénisé : + +```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']) +``` + +Nous pouvons vérifier que nos étiquettes ont été paddées à la longueur maximale du lot, en utilisant `-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]]) +``` + +Et nous pouvons également jeter un coup d'œil aux ID d'entrée du décodeur, pour voir qu'il s'agit de versions décalées des étiquettes : + +```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]]) +``` + +Voici les étiquettes des premier et deuxième éléments de notre jeu de données : + +```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'} + +Nous allons transmettre ce `data_collator` au `Seq2SeqTrainer`. Ensuite, jetons un coup d'oeil à la métrique. + +{:else} + +Nous pouvons maintenant utiliser ce `data_collator` pour convertir chacun de nos jeux de données en un `tf.data.Dataset`, prêt pour l'entraînement : + +```python +tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) +tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=16, +) +``` + +{/if} + + +### Métriques + + + +{#if fw === 'pt'} + +La fonctionnalité que `Seq2SeqTrainer` ajoute à sa superclasse `Trainer` est la possibilité d'utiliser la méthode `generate()` pendant l'évaluation ou la prédiction. Pendant l'entraînement, le modèle utilisera les `decoder_input_ids` avec un masque d'attention assurant qu'il n'utilise pas les *tokens* après le *token* qu'il essaie de prédire, pour accélérer l'entraînement. Pendant l'inférence, nous ne pourrons pas les utiliser puisque nous n'aurons pas d'étiquettes, donc c'est une bonne idée d'évaluer notre modèle avec la même configuration. + +Comme nous l'avons vu dans le [Chapitre 1](/course/fr/chapter1/6), le décodeur effectue l'inférence en prédisant les *tokens* un par un. Quelque chose qui est implémenté en coulisses dans les 🤗 Transformers par la méthode `generate()`. Le `Seq2SeqTrainer` nous laissera utiliser cette méthode pour l'évaluation si nous définissons `predict_with_generate=True`. + +{/if} + +La métrique traditionnelle utilisée pour la traduction est le [score BLEU](https://en.wikipedia.org/wiki/BLEU), introduit dans [un article de 2002](https://aclanthology.org/P02-1040.pdf) par Kishore Papineni et al. Le score BLEU évalue dans quelle mesure les traductions sont proches de leurs étiquettes. Il ne mesure pas l'intelligibilité ou l'exactitude grammaticale des résultats générés par le modèle, mais utilise des règles statistiques pour garantir que tous les mots des résultats générés apparaissent également dans les cibles. En outre, il existe des règles qui pénalisent les répétitions des mêmes mots s'ils ne sont pas également répétés dans les cibles (pour éviter que le modèle ne produise des phrases telles que "the the the the the the the") et les phrases produites qui sont plus courtes que celles des cibles (pour éviter que le modèle ne produise des phrases telles que "the"). + +L'une des faiblesses de BLEU est qu'il s'attend à ce que le texte soit déjà tokenisé, ce qui rend difficile la comparaison des scores entre les modèles qui utilisent différents *tokenizers*. Par conséquent, la mesure la plus couramment utilisée aujourd'hui pour évaluer les modèles de traduction est [SacreBLEU](https://github.com/mjpost/sacrebleu), qui remédie à cette faiblesse (et à d'autres) en standardisant l'étape de tokenisation. Pour utiliser cette métrique, nous devons d'abord installer la bibliothèque SacreBLEU : + +```py +!pip install sacrebleu +``` + +Nous pouvons ensuite le charger via `load_metric()` comme nous l'avons fait dans le [Chapitre 3](/course/fr/chapter3) : + +```py +from datasets import load_metric + +metric = load_metric("sacrebleu") +``` + +Cette métrique prend des textes comme entrées et cibles. Elle est conçue pour accepter plusieurs cibles acceptables, car il y a souvent plusieurs traductions acceptables de la même phrase. Le jeu de données que nous utilisons n'en fournit qu'une seule, mais il n'est pas rare en NLP de trouver des jeux de données qui donnent plusieurs phrases comme étiquettes. Ainsi, les prédictions doivent être une liste de phrases, mais les références doivent être une liste de listes de phrases. + +Essayons un exemple : + +```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} +``` + +Cela donne un score BLEU de 46.75, ce qui est plutôt bon. Pour référence, le Transformer original dans l'article ["Attention Is All You Need"](https://arxiv.org/pdf/1706.03762.pdf) a obtenu un score BLEU de 41.8 sur une tâche de traduction similaire entre l'anglais et le français ! (Pour plus d'informations sur les métriques individuelles, comme `counts` et `bp`, voir le [Dépôt SacreBLEU](https://github.com/mjpost/sacrebleu/blob/078c440168c6adc89ba75fe6d63f0d922d42bcfe/sacrebleu/metrics/bleu.py#L74).) D'autre part, si nous essayons avec les deux mauvais types de prédictions (batchs de répétitions ou trop courts) qui sortent souvent des modèles de traduction, nous obtiendrons des scores BLEU plutôt mauvais : + +```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} +``` + +Le score peut aller de 0 à 100, et plus il est élevé, mieux c'est. + +{#if fw === 'tf'} + +Pour passer des sorties du modèle aux textes que la métrique peut utiliser, nous allons utiliser la méthode `tokenizer.batch_decode()`. Nous devons juste nettoyer tous les `-100` dans les étiquettes ; le *tokenizer* fera automatiquement la même chose pour le *token* de *padding*. Définissons une fonction qui prend notre modèle et un jeu de données et calcule des métriques sur ceux-ci. Comme la génération de longues séquences peut être lente, nous sous-échantillonnons l'ensemble de validation pour nous assurer que cela ne prend pas une éternité : + +```py +import numpy as np + + +def compute_metrics(): + all_preds = [] + all_labels = [] + sampled_dataset = tokenized_datasets["validation"].shuffle().select(range(200)) + tf_generate_dataset = sampled_dataset.to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=4, + ) + for batch in tf_generate_dataset: + predictions = model.generate( + input_ids=batch["input_ids"], attention_mask=batch["attention_mask"] + ) + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + labels = batch["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} + +Pour passer des sorties du modèle aux textes utilisables par la métrique, nous allons utiliser la méthode `tokenizer.batch_decode()`. Nous devons juste nettoyer tous les `-100`s dans les étiquettes (le tokenizer fera automatiquement la même chose pour le token de remplissage) : + +```py +import numpy as np + + +def compute_metrics(eval_preds): + preds, labels = eval_preds + # In case the model returns more than the prediction logits + if isinstance(preds, tuple): + preds = preds[0] + + decoded_preds = tokenizer.batch_decode(preds, skip_special_tokens=True) + + # Replace -100s in the labels as we can't decode them + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + # Some simple post-processing + 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} + +Maintenant que c'est fait, nous sommes prêts à affiner notre modèle ! + + +### *Finetuner* le modèle + +La première étape consiste à se connecter à Hugging Face, afin de pouvoir télécharger vos résultats sur le *Hub*. Il y a une fonction pratique pour vous aider à le faire dans un *notebook* : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Cela affichera un widget où vous pourrez entrer vos identifiants de connexion à Hugging Face. + +Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : + +```bash +huggingface-cli login +``` + +{#if fw === 'tf'} + +Avant de commencer, voyons quel type de résultats nous obtenons avec notre modèle sans entraînement : + +```py +print(compute_metrics()) +``` + +``` +{'bleu': 33.26983701454733} +``` + +Une fois ceci fait, nous pouvons préparer tout ce dont nous avons besoin pour compiler et entraîner notre modèle. Notez l'utilisation de `tf.keras.mixed_precision.set_global_policy("mixed_float16")`. Ceci indiquera à Keras de s'entraîner en utilisant float16, ce qui peut donner un gain de vitesse significatif sur les GPUs qui le supportent (Nvidia 20xx/V100 ou plus récent). + +```python +from transformers import create_optimizer +from transformers.keras_callbacks import PushToHubCallback +import tensorflow as tf + +# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans l'ensemble de données, divisé par la taille du lot, puis multiplié par le nombre total d'époques. +# par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un lot tf.data.Dataset, +# et non le jeu de données original Hugging Face Dataset, donc son len() est déjà 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) + +# Entraîner en mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +Ensuite, nous définissons un `PushToHubCallback` pour télécharger notre modèle sur le *Hub* pendant l'entraînement, comme nous l'avons vu dans [section 2]((/course/fr/chapter7/2)), et ensuite nous ajustons simplement le modèle avec ce callback : + +```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, +) +``` + +Notez que vous pouvez spécifier le nom du référentiel vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"``Seq2SeqTrainingArguments`. Par défaut, le référentiel utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, donc ici ce sera `"sgugger/marian-finetuned-kde4-en-to-fr"` (qui est le modèle que nous avons lié au début de cette section). + + + +💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de l'appel de `model.fit()` et devrez définir un nouveau nom. + + + +Enfin, voyons à quoi ressemblent nos mesures maintenant que l'entraînement est terminé : + +```py +print(compute_metrics()) +``` + +``` +{'bleu': 57.334066271545865} +``` + +À ce stade, vous pouvez utiliser le widget d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à *finetuner* un modèle sur une tâche de traduction. Félicitations ! + +{:else} + +Une fois ceci fait, nous pouvons définir notre `Seq2SeqTrainingArguments`. Comme pour le `Trainer`, nous utilisons une sous-classe de `TrainingArguments` qui contient quelques champs supplémentaires : + +```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, +) +``` + +En dehors des hyperparamètres habituels (comme le taux d'apprentissage, le nombre d'époques, la taille du lot et une certaine décroissance des poids), voici quelques changements par rapport à ce que nous avons vu dans les sections précédentes : + +- nous ne définissons pas d'évaluation régulière, car l'évaluation prend du temps ; nous allons juste évaluer notre modèle une fois avant l'entraînement et après, +- nous avons mis `fp16=True`, ce qui accélère l'entraînement sur les GPUs modernes, +- nous définissons `predict_with_generate=True`, comme discuté ci-dessus, +- nous utilisons `push_to_hub=True` pour télécharger le modèle sur le *Hub* à la fin de chaque époque. + +Notez que vous pouvez spécifier le nom complet du référentiel vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"` `Seq2SeqTrainingArguments`. Par défaut, le référentiel utilisé sera dans votre espace de noms et nommé d'après le répertoire de sortie que vous avez défini, donc dans notre cas ce sera `"sgugger/marian-finetuned-kde4-en-to-fr"` (qui est le modèle que nous avons lié au début de cette section). + + + +💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de la définition de votre `Seq2SeqTrainer` et devrez définir un nouveau nom. + + + + +Enfin, nous passons tout au `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, +) +``` + +Avant d'entraîner, nous allons d'abord regarder le score obtenu par notre modèle, pour vérifier que nous n'aggravons pas les choses avec notre *finetuning*. Cette commande va prendre un peu de temps, vous pouvez donc prendre un café pendant qu'elle s'exécute : + +```python +trainer.evaluate(max_length=max_target_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} +``` + +A BLEU score of 39 is not too bad, which reflects the fact that our model is already good at translating English sentences to French ones. + +Next is the training, which will also take a bit of time: + +```python +trainer.train() +``` + +Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. De cette façon, vous serez en mesure de reprendre votre entraînement sur une autre machine si nécessaire. + +Une fois l'entraînement terminé, nous évaluons à nouveau notre modèle - avec un peu de chance, nous verrons une amélioration du score BLEU ! + +```py +trainer.evaluate(max_length=max_target_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} +``` + +C'est une amélioration de près de 14 points, ce qui est formidable. + +Enfin, nous utilisons la méthode `push_to_hub()` pour nous assurer que nous téléchargeons la dernière version du modèle. Le `Trainer` rédige également une carte modèle avec tous les résultats de l'évaluation et la télécharge. Cette carte de modèle contient des métadonnées qui aident le *Hub* à choisir le widget pour la démo d'inférence. Habituellement, il n'y a pas besoin de dire quoi que ce soit car il peut inférer le bon *widget* à partir de la classe du modèle, mais dans ce cas, la même classe de modèle peut être utilisée pour toutes sortes de problèmes de séquence à séquence, donc nous spécifions que c'est un modèle de traduction : + +```py +trainer.push_to_hub(tags="translation", commit_message="Training complete") +``` + +Cette commande renvoie l'URL du commit qu'elle vient de faire, si vous voulez l'inspecter : + +```python out +'https://huggingface.co/sgugger/marian-finetuned-kde4-en-to-fr/commit/3601d621e3baae2bc63d3311452535f8f58f6ef3' +``` + +À ce stade, vous pouvez utiliser le widget d'inférence sur le Hub du modèle pour tester votre modèle et le partager avec vos amis. Vous avez réussi à *finetuner* un modèle sur une tâche de traduction. Félicitations ! + +Si vous souhaitez vous plonger un peu plus profondément dans la boucle d'entraînement, nous allons maintenant vous montrer comment faire la même chose en utilisant 🤗 *Accelerate*. + +{/if} + +{#if fw === 'pt'} + +## Une boucle d'entraînement personnalisée + +Jetons maintenant un coup d'œil à la boucle d'entraînement complète, afin que vous puissiez facilement personnaliser les parties dont vous avez besoin. Elle ressemblera beaucoup à ce que nous avons fait dans la [section 2](/course/fr/chapter7/2) et le [chapitre 3](/course/fr/chapter3/4). + +### Préparer le tout pour l'entraînement + +Vous avez vu tout cela plusieurs fois maintenant, donc nous allons passer en revue le code assez rapidement. D'abord, nous allons construire le `DataLoader` à partir de nos jeux de données, après avoir configuré les jeux de données au format `"torch"` pour obtenir les tenseurs 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 +) +``` + +Ensuite, nous réinstantifions notre modèle, pour nous assurer que nous ne poursuivons pas l'affinage précédent, mais que nous repartons du modèle pré-entraîné : + +```py +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +Nous aurons alors besoin d'un optimiseur : + +```py +from transformers import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Une fois que nous avons tous ces objets, nous pouvons les envoyer à la méthode `accelerator.prepare()`. Rappelez-vous que si vous voulez vous entraîner sur des TPUs dans un *notebook* de Colab, vous devrez déplacer tout ce code dans une fonction d'entraînement, et qui ne devrait pas exécuter une cellule qui instancie un `Accelerator`. + +```py +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + +Maintenant que nous avons envoyé notre `train_dataloader` à `accelerator.prepare()`, nous pouvons utiliser sa longueur pour calculer le nombre d'étapes d'entraînement. Rappelez-vous que nous devrions toujours faire cela après avoir préparé le dataloader, car cette méthode va changer la longueur du `DataLoader`. Nous utilisons un programme linéaire classique du taux d'apprentissage à 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, +) +``` + +Enfin, pour pousser notre modèle vers le Hub, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous au *Hub*, si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'ID du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix ; il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `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' +``` + +Ensuite, nous pouvons cloner ce référentiel dans un dossier local. S'il existe déjà, ce dossier local doit être un clone du référentiel avec lequel nous travaillons : + +```py +output_dir = "marian-finetuned-kde4-en-to-fr-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output_dir` en appelant la méthode `repo.push_to_hub()`. Cela nous aidera à télécharger les modèles intermédiaires à la fin de chaque époque. + +### Boucle d'entraînement + +Nous sommes maintenant prêts à écrire la boucle d'entraînement complète. Pour simplifier sa partie évaluation, nous définissons cette fonction `postprocess()` qui prend les prédictions et les étiquettes et les convertit en listes de chaînes de caractères que notre objet `metric` attend : + +```py +def postprocess(predictions, labels): + predictions = predictions.cpu().numpy() + labels = labels.cpu().numpy() + + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + + # Remplacer -100 dans les étiquettes car nous ne pouvons pas les décoder. + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + # Quelques post-traitements simples + decoded_preds = [pred.strip() for pred in decoded_preds] + decoded_labels = [[label.strip()] for label in decoded_labels] + return decoded_preds, decoded_labels +``` + +La boucle d'entraînement ressemble beaucoup à celles de [section 2](/course/fr/chapter7/2) et [chapitre 3](/course/fr/chapter3), avec quelques différences dans la partie évaluation -- alors concentrons-nous sur cela ! + +La première chose à noter est que nous utilisons la méthode `generate()` pour calculer les prédictions, mais c'est une méthode sur notre modèle de base, pas le modèle enveloppé 🤗 Accelerate créé dans la méthode `prepare()`. C'est pourquoi nous déballons d'abord le modèle, puis nous appelons cette méthode. + +La deuxième chose est que, comme avec [token classification](/course/fr/chapter7/2), deux processus peuvent avoir paddé les entrées et les étiquettes à des formes différentes, donc nous utilisons `accelerator.pad_across_processes()` pour rendre les prédictions et les étiquettes de la même forme avant d'appeler la méthode `gather()`. Si nous ne faisons pas cela, l'évaluation va soit se tromper, soit se bloquer pour toujours. + +```py +from tqdm.auto import tqdm +import torch + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Entraînement + 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) + + # Evaluation + 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"] + + # Nécessaire pour rembourrer les prédictions et les étiquettes à rassembler + 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}") + + # Sauvegarder et télécharger + 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 +``` + +Une fois que c'est fait, vous devriez avoir un modèle qui a des résultats assez similaires à celui entraîné avec le `Seq2SeqTrainer`. Vous pouvez vérifier celui que nous avons formé en utilisant ce code à [*huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate*](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate). Et si vous voulez tester des modifications de la boucle d'entraînement, vous pouvez les mettre en œuvre directement en modifiant le code ci-dessus ! + +{/if} + +### Utilisation du modèle *finetuné*. + +Nous vous avons déjà montré comment vous pouvez utiliser le modèle que nous avons *finetuné* sur le *Hub* avec le *widget* d'inférence. Pour l'utiliser localement dans un `pipeline`, nous devons juste spécifier l'identifiant de modèle approprié : + +```py +from transformers import pipeline + +# Remplacez ceci par votre propre checkpoint +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'}] +``` + +Comme prévu, notre modèle pré-entraîné a adapté ses connaissances au corpus sur lequel nous l'avons affiné, et au lieu de laisser le mot anglais "threads" seul, il le traduit maintenant par la version officielle française. Il en va de même pour "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."}] +``` + +Un autre excellent exemple d'adaptation au domaine ! + + + +✏️ **Votre tour !** Que retourne le modèle sur l'échantillon avec le mot "email" que vous avez identifié plus tôt ? + + diff --git a/chapters/fr/chapter7/5.mdx b/chapters/fr/chapter7/5.mdx new file mode 100644 index 000000000..0ba896f6d --- /dev/null +++ b/chapters/fr/chapter7/5.mdx @@ -0,0 +1,1065 @@ + + +# Résumé de textes + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + +Dans cette section, nous allons voir comment les *transformers* peuvent être utilisés pour condenser de longs documents en résumés, une tâche connue sous le nom de _résumé de texte_. Il s'agit de l'une des tâches de NLP les plus difficiles car elle requiert une série de capacités, telles que la compréhension de longs passages et la génération d'un texte cohérent qui capture les sujets principaux d'un document. Cependant, lorsqu'il est bien fait, le résumé de texte est un outil puissant qui peut accélérer divers processus commerciaux en soulageant les experts du domaine de la lecture détaillée de longs documents. + + + +Bien qu'il existe déjà plusieurs modèles *finetunés* pour le résumé sur le [Hugging Face Hub](https://huggingface.co/models?pipeline_tag=summarization&sort=downloads), la plupart d'entre eux ne sont adaptés qu'aux documents en anglais. Ainsi, pour ajouter une touche d'originalité à cette section, nous allons entraîner un modèle bilingue pour l'anglais et l'espagnol. À la fin de cette section, vous disposerez d'un [modèle](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es) capable de résumer les commentaires des clients comme celui présenté ici : + + + + +Comme nous allons le voir, ces résumés sont concis car ils sont appris à partir des titres que les clients fournissent dans leurs commentaires sur les produits. Commençons par constituer un corpus bilingue approprié pour cette tâche. + +## Préparation d'un corpus multilingue + +Nous allons utiliser le [Multilingual Amazon Reviews Corpus](https://huggingface.co/datasets/amazon_reviews_multi) pour créer notre résumeur bilingue. Ce corpus est constitué d'évaluations de produits Amazon en six langues et est généralement utilisé pour évaluer les classificateurs multilingues. Cependant, comme chaque critique est accompagnée d'un titre court, nous pouvons utiliser les titres comme résumés cibles pour l'apprentissage de notre modèle ! Pour commencer, téléchargeons les sous-ensembles anglais et espagnols depuis le *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 + }) +}) +``` + +Comme vous pouvez le voir, pour chaque langue, il y a 200 000 évaluations pour la partie "entraînement", et 5 000 évaluations pour chacune des parties "validation" et "test". Les informations qui nous intéressent sont contenues dans les colonnes `review_body` et `review_title`. Voyons quelques exemples en créant une fonction simple qui prend un échantillon aléatoire de l'ensemble d'entraînement avec les techniques apprises au [Chapitre 5](/course/fr/chapter5) : + +```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' # Travaillé en position avant, pas arrière +'>> 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.' +# 3 étoiles car ce ne sont pas des freins arrière comme indiqué dans la description de l'article. Au moins, l'adaptateur de montage ne fonctionnait que sur la fourche avant du vélo pour lequel je l'ai acheté.'' + +'>> 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' +# Il fait son travail et il est magnifique mais le mien est en train de tomber en morceaux, j'ai dû le recoller avec de la colle chaude. + +'>> Title: Can\'t beat these for the money' # On ne peut pas faire mieux pour le prix +'>> 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.' +# Je l'ai acheté pour manipuler diverses pièces d'avion et des "trucs" de hangar que je devais organiser ; il a vraiment fait l'affaire. L'unité est arrivée rapidement, était bien emballée et est arrivée intacte (toujours un bon signe). Il y a cinq supports muraux - trois sur le dessus et deux sur le dessous. Je voulais le monter sur le mur, alors tout ce que j'ai eu à faire était d'enlever les deux couches supérieures de tiroirs en plastique, ainsi que les tiroirs d'angle inférieurs, de le placer où je voulais et de le marquer ; j'ai ensuite utilisé quelques-uns des nouveaux ancrages muraux à vis en plastique (la variété de 50 livres) et il s'est facilement monté sur le mur. Certains ont fait remarquer qu'ils voulaient des séparateurs pour les tiroirs, et qu'ils les ont fabriqués. Bonne idée. Pour ma part, j'avais besoin de quelque chose dont je pouvais voir le contenu à hauteur des yeux, et je voulais donc des tiroirs plus grands. J'aime aussi le fait qu'il s'agisse du nouveau plastique qui ne se fragilise pas et ne se fend pas comme mes anciens tiroirs en plastique. J'aime la construction entièrement en plastique. Elle est suffisamment résistante pour contenir des pièces métalliques, mais étant en plastique, elle n'est pas aussi lourde qu'un cadre métallique, ce qui permet de la fixer facilement au mur et de la charger d'objets lourds ou légers. Aucun problème. Pour le prix, c'est imbattable. C'est le meilleur que j'ai acheté à ce jour, et j'utilise des versions de ce type depuis plus de quarante ans. + +``` + + + +✏️ **Essayez !** Changez la graine aléatoire dans la commande `Dataset.shuffle()` pour explorer d'autres critiques dans le corpus. Si vous parlez espagnol, jetez un coup d'œil à certaines des critiques dans `spanish_dataset` pour voir si les titres semblent aussi être des résumés raisonnables. + + + +Cet échantillon montre la diversité des critiques que l'on trouve généralement en ligne, allant du positif au négatif (et tout ce qui se trouve entre les deux !). Bien que l'exemple avec le titre "meh" ne soit pas très informatif, les autres titres semblent être des résumés décents des critiques elles-mêmes. Entraîner un modèle de résumé sur l'ensemble des 400 000 avis prendrait beaucoup trop de temps sur un seul GPU, nous allons donc nous concentrer sur la génération de résumés pour un seul domaine de produits. Pour avoir une idée des domaines parmi lesquels nous pouvons choisir, convertissons `english_dataset` en `pandas.DataFrame` et calculons le nombre d'avis par catégorie de produits : + +```python +english_dataset.set_format("pandas") +english_df = english_dataset["train"][:] +# Afficher les comptes des 20 premiers produits +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 +``` + +Les produits les plus populaires dans l'ensemble de données anglaises concernent les articles ménagers, les vêtements et l'électronique sans fil. Pour rester dans le thème d'Amazon, nous allons nous concentrer sur le résumé des critiques de livres. Après tout, c'est la raison d'être de l'entreprise ! Nous pouvons voir deux catégories de produits qui correspondent à nos besoins (`book` et `digital_ebook_purchase`), nous allons donc filtrer les ensembles de données dans les deux langues pour ces produits uniquement. Comme nous l'avons vu dans le [Chapitre 5](/course/fr/chapter5), la fonction `Dataset.filter()` nous permet de découper un jeu de données de manière très efficace, nous pouvons donc définir une fonction simple pour le faire : + +```python +def filter_books(example): + return ( + example["product_category"] == "book" + or example["product_category"] == "digital_ebook_purchase" + ) +``` + +Maintenant, lorsque nous appliquons cette fonction à `english_dataset` et `spanish_dataset`, le résultat ne contiendra que les lignes impliquant les catégories de livres. Avant d'appliquer le filtre, changeons le format de `english_dataset` de `"pandas"` à `"arrow"` : + +```python +english_dataset.reset_format() +``` + +Nous pouvons ensuite appliquer la fonction de filtrage et, à titre de vérification, inspecter un échantillon de critiques pour voir si elles portent bien sur des livres : + +```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.' # Je suis déçu +'>> 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.' +# Je suppose que j'avais de plus grandes attentes pour ce livre d'après les critiques. Je pensais vraiment que j'allais au moins l'aimer. L'idée de l'intrigue était géniale. J'aimais Ash, mais ça n'allait nulle part. La plus grande partie du livre était consacrée à leur émission de radio et aux conversations avec les auditeurs. Je voulais que l'auteur creuse plus profondément pour que nous puissions vraiment connaître les personnages. Tout ce que nous savons de Grace, c'est qu'elle est séduisante, qu'elle est latino et qu'elle est une sorte de garce. Je suis déçue. + +'>> Title: Good art, good price, poor design' # Un bon art, un bon prix, un mauvais 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' +# J'ai eu le calendrier DC Vintage ces deux dernières années, mais il était en rupture de stock pour toujours cette année et j'ai vu qu'ils avaient réduit les dimensions sans raison valable. Celui-ci a de bons choix artistiques mais le design a le pli qui traverse l'image, donc c'est moins esthétique, surtout si vous voulez garder une image à accrocher. Pour le prix, c'est un bon calendrier. + +'>> Title: Helpful' Utile +'>> Review: Nearly all the tips useful and. I consider myself an intermediate to advanced user of OneNote. I would highly recommend.' +# Presque tous les conseils sont utiles et. Je me considère comme un utilisateur intermédiaire à avancé de OneNote. Je le recommande vivement. +``` + +D'accord, nous pouvons voir que les critiques ne concernent pas strictement les livres et peuvent se référer à des choses comme des calendriers et des applications électroniques telles que OneNote. Néanmoins, le domaine semble approprié pour entraîner un modèle de résumé. Avant de regarder les différents modèles qui conviennent à cette tâche, nous avons une dernière préparation de données à faire : combiner les critiques anglaises et espagnoles en un seul objet `DatasetDict`. 🤗 *Datasets* fournit une fonction pratique `concatenate_datasets()` qui (comme son nom l'indique) va empiler deux objets `Dataset` l'un sur l'autre. Ainsi, pour créer notre jeu de données bilingue, nous allons boucler sur chaque division, concaténer les jeux de données pour cette division, et mélanger le résultat pour s'assurer que notre modèle ne s'adapte pas trop à une seule langue : + +```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) + +# Quelques exemples +show_samples(books_dataset) +``` + +```python out +'>> Title: Easy to follow!!!!' # Facile à suivre!!!! +'>> 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.' +# J'ai adoré The dash diet weight loss Solution. Jamais faim. Je recommande ce régime. Les menus sont également bien arrondis. Essayez-le. Il contient beaucoup d'informations, merci. + +'>> Title: PARCIALMENTE DAÑADO' # PARTIELLEMENT ENDOMMAGÉ +'>> 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).' +# Il est arrivé le jour prévu, avec d'autres livres que j'avais commandés, mais la boîte est arrivée en mauvais état, ce qui a endommagé les coins des livres car ils étaient livrés sans protection (doublure). + +'>> Title: no lo he podido descargar' # Je n'ai pas pu le télécharger +'>> Review: igual que el anterior' # même chose que ci-dessus +``` + +Cela ressemble certainement à un mélange de critiques anglaises et espagnoles ! Maintenant que nous avons un corpus d'entraînement, une dernière chose à vérifier est la distribution des mots dans les critiques et leurs titres. Ceci est particulièrement important pour les tâches de résumé, où les résumés de référence courts dans les données peuvent biaiser le modèle pour qu'il ne produise qu'un ou deux mots dans les résumés générés. Les graphiques ci-dessous montrent les distributions de mots, et nous pouvons voir que les titres sont fortement biaisés vers seulement 1 ou 2 mots : + +
+Word count distributions for the review titles and texts. + +
+ +Pour y remédier, nous allons filtrer les exemples avec des titres très courts afin que notre modèle puisse produire des résumés plus intéressants. Puisque nous avons affaire à des textes anglais et espagnols, nous pouvons utiliser une heuristique grossière pour séparer les titres sur les espaces blancs, puis utiliser notre fidèle méthode `Dataset.filter()` comme suit : + +```python +books_dataset = books_dataset.filter(lambda x: len(x["review_title"].split()) > 2) +``` + +Maintenant que nous avons préparé notre corpus, voyons quelques *transformers* possibles que l'on pourrait *finetuné* dessus ! + +## Modèles pour le résumé de texte + +Si vous y pensez, le résumé de texte est une tâche similaire à la traduction automatique : nous avons un corps de texte, comme une critique, que nous aimerions "traduire" en une version plus courte qui capture les caractéristiques saillantes de l'entrée. En conséquence, la plupart des modèles Transformer pour le résumé adoptent l'architecture encodeur-décodeur que nous avons rencontrée pour la première fois dans le [Chapitre 1](/course/fr/chapter1), bien qu'il y ait quelques exceptions comme la famille de modèles GPT qui peut également être utilisée pour le résumé dans des contextes peu complexes. Le tableau suivant présente quelques modèles pré-entraînés populaires qui peuvent être *finetunés* pour le résumé. + +| *Transformers* | Description | Multilingue ? | +| :---------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------: | +| [GPT-2](https://huggingface.co/gpt2-xl) | Bien qu'il soit entraîné comme un modèle de langage autorégressif, vous pouvez faire en sorte que GPT-2 génère des résumés en ajoutant "TL;DR" à la fin du texte d'entrée. | ❌ | +| [PEGASUS](https://huggingface.co/google/pegasus-large) | Utilise un objectif de pré-entraînement pour prédire les phrases masquées dans les textes à plusieurs phrases. Cet objectif de pré-entraînement est plus proche du résumé que de la modélisation du langage standard et obtient des scores élevés sur des benchmarks populaires. | ❌ | +| [T5](https://huggingface.co/t5-base) | Une architecture universelle de *transformer* qui formule toutes les tâches dans un cadre texte à texte ; par exemple, le format d'entrée du modèle pour résumer un document est `summarize : ARTICLE`. | ❌ | +| [mT5](https://huggingface.co/google/mt5-base) | Une version multilingue de T5, pré-entraînée sur le corpus multilingue Common Crawl (mC4), couvrant 101 langues. | ✅ | +| [BART](https://huggingface.co/facebook/bart-base) | Une architecture de *transformer* avec une pile d'encodeurs et de décodeurs entraînés pour reconstruire l'entrée corrompue qui combine les schémas de pré-entraînement de BERT et GPT-2. | ❌ | +| [mBART-50](https://huggingface.co/facebook/mbart-large-50) | Une version multilingue de BART, pré-entraînée sur 50 langues. | ✅ | + +Comme vous pouvez le voir dans ce tableau, la majorité des modèles Transformer pour le résumé (et en fait la plupart des tâches de NLP) sont monolingues. C'est une bonne chose si votre tâche se déroule dans une langue "à haute ressource" comme l'anglais ou l'allemand, mais moins pour les milliers d'autres langues utilisées dans le monde. Heureusement, il existe une catégorie de modèles Transformer multilingues, comme mT5 et mBART, qui viennent à la rescousse. Ces modèles sont pré-entraînés en utilisant la modélisation du langage, mais avec une particularité : au lieu de s'entraîner sur un corpus d'une seule langue, ils sont entraînés conjointement sur des textes dans plus de 50 langues à la fois ! + +Nous allons nous concentrer sur mT5, une architecture intéressante basée sur T5 qui a été pré-entraînée dans un cadre texte à texte. Dans T5, chaque tâche NLP est formulée en termes d'un préfixe d'invite comme `summarize:` qui conditionne le modèle à adapter le texte généré à l'invite. Comme le montre la figure ci-dessous, cela rend T5 extrêmement polyvalent, car vous pouvez résoudre de nombreuses tâches avec un seul modèle ! + +
+Different tasks performed by the T5 architecture. + +
+ +mT5 n'utilise pas de préfixes, mais partage une grande partie de la polyvalence de T5 et a l'avantage d'être multilingue. Maintenant que nous avons choisi un modèle, voyons comment préparer nos données pour l'entraînement. + + + + +✏️ **Essayez** Une fois que vous avez travaillé sur cette section, voyez comment mT5 se compare à mBART en affinant ce dernier avec les mêmes techniques. Pour des points bonus, vous pouvez aussi essayer de *finetuner* le T5 uniquement sur les critiques anglaises. Puisque le T5 a un préfixe spécial, vous devrez ajouter `summarize:` aux exemples d'entrée dans les étapes de prétraitement ci-dessous. + + + +## Prétraitement des données + + + +Notre prochaine tâche est de tokeniser et d'encoder nos critiques et leurs titres. Comme d'habitude, nous commençons par charger le *tokenizer* associé au point de contrôle du modèle pré-entraîné. Nous utiliserons `mt5-small` comme point de contrôle afin de pouvoir affiner le modèle en un temps raisonnable : + +```python +from transformers import AutoTokenizer + +model_checkpoint = "google/mt5-small" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + + + +💡 Aux premiers stades de vos projets de NLP, une bonne pratique consiste à entraîner une classe de "petits" modèles sur un petit échantillon de données. Cela vous permet de déboguer et d'itérer plus rapidement vers un flux de travail de bout en bout. Une fois que vous avez confiance dans les résultats, vous pouvez toujours faire évoluer le modèle en changeant simplement le point de contrôle du modèle ! + + + +Testons le *tokenizer* de mT5 sur un petit exemple : + +```python +inputs = tokenizer( + "I loved reading the Hunger Games!" +) # J'ai adoré lire les 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]} +``` + +Ici nous pouvons voir les familiers `input_ids` et `attention_mask` que nous avons rencontrés dans nos premières expériences de fine-tuning au [Chapitre 3](/course/fr/chapter3). Décodons ces identifiants d'entrée avec la fonction `convert_ids_to_tokens()` du tokenizer pour voir à quel type de tokenizer nous avons affaire : + +```python +tokenizer.convert_ids_to_tokens(inputs.input_ids) +``` + +```python out +['▁I', '▁', 'loved', '▁reading', '▁the', '▁Hung', 'er', '▁Games', ''] +``` + +Le caractère spécial Unicode `▁` et le *token* de fin de séquence `` indiquent que nous avons affaire au *tokenizer* de SentencePiece, qui est basé sur l'algorithme de segmentation Unigram discuté dans le [Chapitre 6](/course/chapter6). Unigram est particulièrement utile pour les corpus multilingues car il permet à SentencePiece d'être agnostique vis-à-vis des accents, de la ponctuation et du fait que de nombreuses langues, comme le japonais, n'ont pas de caractères d'espacement. + +Pour tokeniser notre corpus, nous devons faire face à une subtilité associée au résumé : comme nos étiquettes sont également du texte, il est possible qu'elles dépassent la taille maximale du contexte du modèle. Cela signifie que nous devons appliquer une troncature à la fois aux critiques et à leurs titres pour nous assurer de ne pas transmettre des entrées trop longues à notre modèle. Les tokenizers de 🤗 *Transformers* fournissent une fonction très pratique `as_target_tokenizer()` qui vous permet de tokeniser les étiquettes en parallèle avec les entrées. Ceci est typiquement fait en utilisant un gestionnaire de contexte à l'intérieur d'une fonction de prétraitement qui encode d'abord les entrées, et ensuite encode les étiquettes comme une colonne séparée. Voici un exemple d'une telle fonction pour 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 + ) + # Configurer le *tokenizer* pour les cibles. + with tokenizer.as_target_tokenizer(): + labels = tokenizer( + examples["review_title"], max_length=max_target_length, truncation=True + ) + + model_inputs["labels"] = labels["input_ids"] + return model_inputs +``` + +Parcourons ce code pour comprendre ce qui se passe. La première chose que nous avons faite est de définir des valeurs pour `max_input_length` et `max_target_length`, qui fixent les limites supérieures de la longueur des commentaires et des titres. Comme le corps de la critique est généralement beaucoup plus long que le titre, nous avons mis ces valeurs à l'échelle en conséquence. Ensuite, dans la `preprocess_function()` elle-même, nous pouvons voir que les commentaires sont d'abord tokenizés, suivis par les titres avec `as_target_tokenizer()`. + +Avec la fonction `preprocess_function()`, il est alors simple de tokeniser l'ensemble du corpus en utilisant la fonction pratique `Dataset.map()` que nous avons largement utilisée dans ce cours : + +```python +tokenized_datasets = books_dataset.map(preprocess_function, batched=True) +``` + +Maintenant que le corpus a été prétraité, examinons certaines métriques couramment utilisées pour le résumé. Comme nous allons le voir, il n'existe pas de solution miracle pour mesurer la qualité d'un texte généré par une machine. + + + +💡 Vous avez peut-être remarqué que nous avons utilisé `batched=True` dans notre fonction `Dataset.map()` ci-dessus. Cela permet de coder les exemples par lots de 1 000 (par défaut) et d'utiliser les capacités de multithreading des *tokenizers* rapides de 🤗 *Transformers*. Lorsque cela est possible, essayez d'utiliser `batched=True` pour tirer le meilleur parti de votre prétraitement ! + + + + +## Métriques pour le résumé de texte + + + +Par rapport à la plupart des autres tâches que nous avons abordées dans ce cours, la mesure des performances des tâches de génération de texte comme le résumé ou la traduction n'est pas aussi simple. Par exemple, pour une critique telle que "J'ai adoré lire les Hunger Games", il existe plusieurs résumés valides, comme "J'ai adoré Hunger Games" ou "Hunger Games est une excellente lecture". Il est clair que l'application d'une sorte de correspondance exacte entre le résumé généré et l'étiquette n'est pas une bonne solution - même les humains auraient de mauvais résultats avec une telle mesure, car nous avons tous notre propre style d'écriture. + +Pour le résumé, l'une des métriques les plus couramment utilisées est le [score ROUGE](https://en.wikipedia.org/wiki/ROUGE_(metric)) (abréviation de Recall-Oriented Understudy for Gisting Evaluation). L'idée de base de cette métrique est de comparer un résumé généré avec un ensemble de résumés de référence qui sont généralement créés par des humains. Pour être plus précis, supposons que nous voulions comparer les deux résumés suivants : + +```python +generated_summary = "I absolutely loved reading the Hunger Games" +reference_summary = "I loved reading the Hunger Games" +``` + +Une façon de les comparer pourrait être de compter le nombre de mots qui se chevauchent, qui dans ce cas serait de 6. Cependant, cette méthode est un peu grossière, c'est pourquoi ROUGE se base sur le calcul des scores de _précision_ et de _rappel_ pour le chevauchement. + + + +🙋 Ne vous inquiétez pas si c'est la première fois que vous entendez parler de précision et de rappel - nous allons parcourir ensemble quelques exemples explicites pour que tout soit clair. Ces métriques sont généralement rencontrées dans les tâches de classification, donc si vous voulez comprendre comment la précision et le rappel sont définis dans ce contexte, nous vous recommandons de consulter les [guides de `scikit-learn`](https://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html). + + + +Pour ROUGE, le rappel mesure la proportion du résumé de référence qui est capturée par le résumé généré. Si nous ne faisons que comparer des mots, le rappel peut être calculé selon la formule suivante : + +$$ \mathrm{Recall} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, reference\, summary}} $$ + +Pour notre exemple simple ci-dessus, cette formule donne un rappel parfait de 6/6 = 1 ; c'est-à-dire que tous les mots du résumé de référence ont été produits par le modèle. Cela peut sembler génial, mais imaginez que le résumé généré ait été "J'ai vraiment aimé lire les Hunger Games toute la nuit". Le rappel serait également parfait, mais le résumé serait sans doute moins bon puisqu'il serait verbeux. Pour traiter ces scénarios, nous calculons également la précision, qui, dans le contexte de ROUGE, mesure la proportion du résumé généré qui était pertinente : + +$$ \mathrm{Precision} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, generated\, summary}} $$ + +En appliquant cela à notre résumé verbeux, on obtient une précision de 6/10 = 0,6, ce qui est considérablement moins bon que la précision de 6/7 = 0,86 obtenue par notre résumé plus court. En pratique, la précision et le rappel sont généralement calculés, puis le score F1 (la moyenne harmonique de la précision et du rappel) est indiqué. Nous pouvons le faire facilement dans 🤗 *Datasets* en installant d'abord le paquet `rouge_score` : + +```py +!pip install rouge_score +``` + +et ensuite charger la métrique ROUGE comme suit : + +```python +from datasets import load_metric + +rouge_score = load_metric("rouge") +``` + +Ensuite, nous pouvons utiliser la fonction `rouge_score.compute()` pour calculer toutes les métriques en une seule fois : + +```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))} +``` + +Whoa, il y a un batch d'informations dans cette sortie. Qu'est-ce que ça veut dire ? Tout d'abord, 🤗 *Datasets* calcule en fait des intervalles de confiance pour la précision, le rappel et le score F1 ; ce sont les attributs `low`, `mid`, et `high` que vous pouvez voir ici. De plus, 🤗 *Datasets* calcule une variété de scores ROUGE qui sont basés sur différents types de granularité du texte lors de la comparaison des résumés générés et de référence. La variante `rouge1` est le chevauchement des unigrammes. C'est juste une façon fantaisiste de dire le chevauchement des mots et c'est exactement la métrique dont nous avons discuté ci-dessus. Pour vérifier cela, nous allons extraire la valeur "moyenne" de nos scores : + +```python +scores["rouge1"].mid +``` + +```python out +Score(precision=0.86, recall=1.0, fmeasure=0.92) +``` + +Super, les chiffres de précision et de rappel correspondent ! Maintenant, qu'en est-il des autres scores ROUGE ? `rouge2` mesure le chevauchement entre les bigrammes (pensez au chevauchement des paires de mots), tandis que `rougeL` et `rougeLsum` mesurent les plus longues séquences de mots correspondants en recherchant les plus longues sous-souches communes dans les résumés générés et de référence. La "somme" dans `rougeLsum` fait référence au fait que cette métrique est calculée sur un résumé entier, alors que `rougeL` est calculée comme une moyenne sur des phrases individuelles. + + + +✏️ **Essayez** Créez votre propre exemple de résumé généré et de référence et voyez si les scores ROUGE obtenus correspondent à un calcul manuel basé sur les formules de précision et de rappel. Pour des points bonus, divisez le texte en bigrammes et comparez la précision et le rappel pour la métrique `rouge2`. + + + +Nous utiliserons ces scores ROUGE pour suivre les performances de notre modèle, mais avant cela, faisons ce que tout bon praticien de NLP devrait faire : créer une base de référence solide, mais simple ! + +### Création d'une base de référence solide + +Une base de référence commune pour le résumé de texte consiste à prendre simplement les trois premières phrases d'un article, souvent appelée la base de référence _lead-3_. Nous pourrions utiliser des points pour suivre les limites de la phrase, mais cela échouera avec des acronymes comme "U.S." ou "U.N.". -- Nous allons donc utiliser la bibliothèque `nltk`, qui inclut un meilleur algorithme pour gérer ces cas. Vous pouvez installer le paquetage en utilisant `pip` comme suit : + +```python +!pip install nltk +``` + +puis téléchargez les règles de ponctuation : + +```python +import nltk + +nltk.download("punkt") +``` + +Ensuite, nous importons le *tokenizer* de `nltk` et créons une fonction simple pour extraire les trois premières phrases d'une critique. La convention dans le résumé de texte est de séparer chaque résumé avec une nouvelle ligne, donc nous allons également inclure ceci et le tester sur un exemple d'entraînement : + +```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.' # J'ai grandi en lisant Koontz, et il y a des années, j'ai arrêté, convaincu que je l'avais "dépassé" +'Still,when a friend was looking for something suspenseful too read, I suggested Koontz.' " Pourtant, quand une amie cherchait un livre à suspense, je lui ai suggéré Koontz. +'She found Strangers.' # Elle a trouvé Strangers. +``` + +Cela semble fonctionner, alors implémentons maintenant une fonction qui extrait ces "résumés" d'un ensemble de données et calcule les scores ROUGE pour la ligne de base : + +```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"]) +``` + +Nous pouvons ensuite utiliser cette fonction pour calculer les scores ROUGE sur l'ensemble de validation et les embellir un peu en utilisant 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} +``` + +Nous pouvons voir que le score de `rouge2` est significativement plus bas que le reste ; ceci reflète probablement le fait que les titres des revues sont typiquement concis et donc que la ligne de base de lead-3 est trop verbeuse. Maintenant que nous disposons d'une bonne base de travail, concentrons-nous sur le réglage fin de mT5 ! + +{#if fw === 'pt'} + +## *Finetuning* de mT5 avec l'API `Trainer`. + +Le *finetuning* d'un modèle pour le résumé est très similaire aux autres tâches que nous avons couvertes dans ce chapitre. La première chose à faire est de charger le modèle pré-entraîné depuis le checkpoint `mt5-small`. Puisque la compression est une tâche de séquence à séquence, nous pouvons charger le modèle avec la classe `AutoModelForSeq2SeqLM`, qui téléchargera automatiquement et mettra en cache les poids : + +```python +from transformers import AutoModelForSeq2SeqLM + +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{:else} + +## *Finetuning* de mT5 avec Keras + +Le *finetuning* d'un modèle pour le résumé est très similaire aux autres tâches que nous avons couvertes dans ce chapitre. La première chose à faire est de charger le modèle pré-entraîné à partir du point de contrôle `mt5-small`. Puisque la compression est une tâche de séquence à séquence, nous pouvons charger le modèle avec la classe `AutoModelForSeq2SeqLM`, qui téléchargera automatiquement et mettra en cache les poids : + +```python +from transformers import TFAutoModelForSeq2SeqLM + +model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{/if} + + + +💡 Si vous vous demandez pourquoi vous ne voyez aucun avertissement concernant l'affinement du modèle sur une tâche en aval, c'est parce que pour les tâches de séquence à séquence, nous conservons tous les poids du réseau. Comparez cela à notre modèle de classification de texte dans [Chapitre 3](/course/fr/chapter3), où la tête du modèle pré-entraîné a été remplacée par un réseau initialisé de manière aléatoire. + + + +La prochaine chose que nous devons faire est de nous connecter au *Hub*. Si vous exécutez ce code dans un *notebook*, vous pouvez le faire avec la fonction utilitaire suivante : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +qui affichera un *widget* où vous pourrez saisir vos informations d'identification. Vous pouvez également exécuter cette commande dans votre terminal et vous connecter à partir de là : + +``` +huggingface-cli login +``` + +{#if fw === 'pt'} + +Nous aurons besoin de générer des résumés afin de calculer les scores ROUGE pendant l'entraînement. Heureusement, 🤗 *Transformers* fournit des classes dédiées `Seq2SeqTrainingArguments` et `Seq2SeqTrainer` qui peuvent faire cela pour nous automatiquement ! Pour voir comment cela fonctionne, définissons d'abord les hyperparamètres et autres arguments pour nos expériences : + +```python +from transformers import Seq2SeqTrainingArguments + +batch_size = 8 +num_train_epochs = 8 +# La perte d'entraînement à chaque époque +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, +) +``` + +Ici, l'argument `predict_with_generate` a été défini pour indiquer que nous devons générer des résumés pendant l'évaluation afin de pouvoir calculer les scores ROUGE pour chaque époque. Comme discuté dans [Chapter 1](/course/fr/chapter1), le décodeur effectue l'inférence en prédisant les tokens un par un, et ceci est implémenté par la méthode `generate()` du modèle. Définir `predict_with_generate=True` indique au `Seq2SeqTrainer` d'utiliser cette méthode pour l'évaluation. Nous avons également ajusté certains des hyperparamètres par défaut, comme le taux d'apprentissage, le nombre d'époques, et le taux de décroissance des poids, et nous avons réglé l'option `save_total_limit` pour ne sauvegarder que jusqu'à 3 *checkpoints* pendant l'entraînement. C'est parce que même la "petite" version de mT5 utilise environ un Go d'espace disque, et nous pouvons gagner un peu de place en limitant le nombre de copies que nous sauvegardons. + +L'argument `push_to_hub=True` nous permettra de pousser le modèle vers le Hub après l'entraînement ; vous trouverez le dépôt sous votre profil utilisateur dans l'emplacement défini par `output_dir`. Notez que vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/mt5-finetuned-amazon-en-es"`à `Seq2SeqTrainingArguments`. + +La prochaine chose que nous devons faire est de fournir à l'entraîneur une fonction `compute_metrics()` afin que nous puissions évaluer notre modèle pendant l'entraînement. Pour le résumé, c'est un peu plus compliqué que de simplement appeler `rouge_score.compute()` sur les prédictions du modèle, puisque nous devons _décoder_ les sorties et les étiquettes en texte avant de pouvoir calculer les scores ROUGE. La fonction suivante fait exactement cela, et utilise également la fonction `sent_tokenize()` de `nltk` pour séparer les phrases du résumé avec des nouvelles lignes : + + +```python +import numpy as np + + +def compute_metrics(eval_pred): + predictions, labels = eval_pred + # Décoder les résumés générés en texte + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + # Remplacer -100 dans les étiquettes car nous ne pouvons pas les décoder + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + # Décoder les résumés de référence en texte + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + # ROUGE attend une nouvelle ligne après chaque phrase + 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] + # Calcul des scores ROUGE + result = rouge_score.compute( + predictions=decoded_preds, references=decoded_labels, use_stemmer=True + ) + # Extract the median scores + result = {key: value.mid.fmeasure * 100 for key, value in result.items()} + return {k: round(v, 4) for k, v in result.items()} +``` + +{/if} + +Ensuite, nous devons définir un collateur de données pour notre tâche de séquence à séquence. Comme mT5 est un modèle Transformer encodeur-décodeur, une des subtilités de la préparation de nos lots est que, pendant le décodage, nous devons décaler les étiquettes d'une unité vers la droite. Ceci est nécessaire pour garantir que le décodeur ne voit que les étiquettes de vérité terrain précédentes et non les étiquettes actuelles ou futures, qui seraient faciles à mémoriser pour le modèle. Cela ressemble à la façon dont l'auto-attention masquée est appliquée aux entrées dans une tâche comme [la modélisation causale du langage](/course/fr/chapter7/6). + +Heureusement, 🤗 *Transformers* fournit un collateur `DataCollatorForSeq2Seq` qui rembourrera dynamiquement les entrées et les étiquettes pour nous. Pour instancier ce collateur, nous devons simplement fournir le *tokenizer* et le `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} + +Voyons ce que produit ce collateur lorsqu'on lui donne un petit lot d'exemples. Tout d'abord, nous devons supprimer les colonnes contenant des chaînes de caractères, car le collateur ne saura pas comment remplir ces éléments : + +```python +tokenized_datasets = tokenized_datasets.remove_columns( + books_dataset["train"].column_names +) +``` + +Comme le collateur attend une liste de `dict`s, où chaque `dict` représente un seul exemple dans l'ensemble de données, nous devons également mettre les données dans le format attendu avant de les transmettre au collateur de données : + +```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]])} +``` + +La principale chose à remarquer ici est que le premier exemple est plus long que le second, donc les `input_ids` et `attention_mask` du second exemple ont été complétés sur la droite avec un jeton `[PAD]` (dont l'ID est `0`). De même, nous pouvons voir que les `labels` ont été complétés par des `-100`s, pour s'assurer que les *tokens* de remplissage sont ignorés par la fonction de perte. Et enfin, nous pouvons voir un nouveau `decoder_input_ids` qui a déplacé les étiquettes vers la droite en insérant un jeton `[PAD]` dans la première entrée. + +{#if fw === 'pt'} + +Nous avons enfin tous les ingrédients dont nous avons besoin pour nous entraîner ! Nous devons maintenant simplement instancier le formateur avec les arguments standards : + +```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, +) +``` + +et lancer notre course d'entraînement : + +```python +trainer.train() +``` + +Pendant l'entraînement, vous devriez voir la perte d'entraînement diminuer et les scores ROUGE augmenter à chaque époque. Une fois l'entraînement terminé, vous pouvez voir les scores ROUGE finaux en exécutant `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} +``` + +D'après les scores, nous pouvons voir que notre modèle a largement surpassé notre ligne de base lead-3. Bien ! La dernière chose à faire est de pousser les poids du modèle vers le *Hub*, comme suit : + +``` +trainer.push_to_hub(commit_message="Training complete", tags="summarization") +``` + +```python out +'https://huggingface.co/huggingface-course/mt5-finetuned-amazon-en-es/commit/aa0536b829b28e73e1e4b94b8a5aacec420d40e0' +``` + +Ceci sauvegardera le point de contrôle et les fichiers de configuration dans `output_dir`, avant de télécharger tous les fichiers sur le *Hub*. En spécifiant l'argument `tags`, nous nous assurons également que le widget sur le Hub sera celui d'un pipeline de résumé au lieu de celui de la génération de texte par défaut associé à l'architecture mT5 (pour plus d'informations sur les balises de modèle, voir la [🤗 documentation du *Hub*](https://huggingface.co/docs/hub/main#how-is-a-models-type-of-inference-api-and-widget-determined)). La sortie de `trainer.push_to_hub()` est une URL vers le hash du commit Git, donc vous pouvez facilement voir les changements qui ont été faits au dépôt de modèle ! + +Pour conclure cette section, voyons comment nous pouvons également affiner mT5 en utilisant les fonctionnalités de bas niveau fournies par 🤗 *Accelerate*. + +{:else} + +Nous sommes presque prêts à nous entraîner ! Nous devons juste convertir nos jeux de données en `tf.data.Dataset`s en utilisant le collateur de données que nous avons défini ci-dessus, et ensuite `compile()` et `fit()` le modèle. D'abord, les jeux de données : + +```python +tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=8, +) +tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=8, +) +``` + +Maintenant, nous définissons nos hyperparamètres d'entraînement et nous compilons : + +```python +from transformers import create_optimizer +import tensorflow as tf + +# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans l'ensemble de données, divisé par la taille du batch, puis multiplié par le nombre total d'époques. +# par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un batch tf.data.Dataset, +# et non le jeu de données original Hugging Face Dataset, donc son len() est déjà 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) + +# Entraîner en mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +Et enfin, nous ajustons le modèle. Nous utilisons un `PushToHubCallback` pour sauvegarder le modèle sur le *Hub* après chaque époque, ce qui nous permettra de l'utiliser pour l'inférence plus tard : + +```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 +) +``` + +Nous avons obtenu quelques valeurs de perte pendant l'entraînement, mais nous aimerions vraiment voir les métriques ROUGE que nous avons calculées plus tôt. Pour obtenir ces métriques, nous devons générer les sorties du modèle et les convertir en chaînes de caractères. Construisons quelques listes d'étiquettes et de prédictions pour comparer la métrique ROUGE (notez que si vous obtenez des erreurs d'importation pour cette section, vous pouvez avoir besoin de "pip install tqdm") : + +```python +from tqdm import tqdm +import numpy as np + +all_preds = [] +all_labels = [] +for batch in tqdm(tf_eval_dataset): + predictions = model.generate(**batch) + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + labels = batch["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) +``` + +Une fois que nous avons nos listes d'étiquettes et de chaînes de prédiction, le calcul du score ROUGE est facile : + +```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'} + +## *Finetuning* de mT5 avec 🤗 *Accelerate* + +Le *finetuning* de notre modèle avec 🤗 *Accelerate* est très similaire à l'exemple de classification de texte que nous avons rencontré dans [Chapitre 3](/course/fr/chapter3). Les principales différences seront la nécessité de générer explicitement nos résumés pendant l'Entraînement et de définir comment nous calculons les scores ROUGE (rappelons que le `Seq2SeqTrainer` s'est occupé de la génération pour nous). Voyons comment nous pouvons mettre en œuvre ces deux exigences dans 🤗 *Accelerate* ! + +### Préparer tout pour l'entraînement + +La première chose que nous devons faire est de créer un `DataLoader` pour chacun de nos splits. Puisque les chargeurs de données PyTorch attendent des batchs de tenseurs, nous devons définir le format à `"torch"` dans nos jeux de données : + +```python +tokenized_datasets.set_format("torch") +``` + +Maintenant que nous avons des jeux de données constitués uniquement de tenseurs, la prochaine chose à faire est d'instancier à nouveau le `DataCollatorForSeq2Seq`. Pour cela, nous devons fournir une nouvelle version du modèle, donc chargeons-le à nouveau depuis notre cache : + +```python +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +Nous pouvons ensuite instancier le collateur de données et l'utiliser pour définir nos chargeurs de données : + +```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 +) +``` + +La prochaine chose à faire est de définir l'optimiseur que nous voulons utiliser. Comme dans nos autres exemples, nous allons utiliser `AdamW`, qui fonctionne bien pour la plupart des problèmes : + +```python +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Enfin, nous introduisons notre modèle, notre optimiseur et nos chargeurs de données dans la méthode `accelerator.prepare()` : + +```python +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + + + +🚨 Si vous vous entraînez sur un TPU, vous devrez déplacer tout le code ci-dessus dans une fonction d'entraînement dédiée. Voir le [Chapitre 3](/course/fr/chapter3) pour plus de détails. + + + +Maintenant que nous avons préparé nos objets, il reste trois choses à faire : + +* définir le programme du taux d'apprentissage, +* implémenter une fonction pour post-traiter les résumés pour l'évaluation, +* créer un référentiel sur le *Hub* vers lequel nous pouvons pousser notre modèle. + +Pour le programme de taux d'apprentissage, nous utiliserons le programme linéaire standard des sections précédentes : + +```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, +) +``` + +Pour le post-traitement, nous avons besoin d'une fonction qui divise les résumés générés en phrases séparées par des nouvelles lignes. C'est le format attendu par la métrique ROUGE, et nous pouvons y parvenir avec le bout de code suivant : + +```python +def postprocess_text(preds, labels): + preds = [pred.strip() for pred in preds] + labels = [label.strip() for label in labels] + + # ROUGE attend une nouvelle ligne après chaque phrase + 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 +``` + +Cela devrait vous sembler familier si vous vous rappelez comment nous avons défini la fonction `compute_metrics()` du `Seq2SeqTrainer`. + +Enfin, nous devons créer un dépôt de modèles sur le *Hub*. Pour cela, nous pouvons utiliser la bibliothèque 🤗 *Hub*, qui porte le nom approprié. Nous avons juste besoin de définir un nom pour notre référentiel, et la bibliothèque a une fonction utilitaire pour combiner l'ID du référentiel avec le profil de l'utilisateur : + +```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' +``` + +Nous pouvons maintenant utiliser ce nom de référentiel pour cloner une version locale dans notre répertoire de résultats qui stockera les artefacts d'entraînement : + +```python +from huggingface_hub import Repository + +output_dir = "results-mt5-finetuned-squad-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +This will allow us to push the artifacts back to the Hub by calling the `repo.push_to_hub()` method during training! Let's now wrap up our analysis by writing out the training loop. + +### Boucle d'entraînement + +La boucle d'entraînement pour le résumé est assez similaire aux autres exemples 🤗 *Accelerate* que nous avons rencontrés et est grossièrement divisée en quatre étapes principales : + +1. entraîner le modèle en itérant sur tous les exemples dans `train_dataloader` pour chaque époque, +2. générer les résumés du modèle à la fin de chaque époque, en générant d'abord les *tokens* puis en les décodant (ainsi que les résumés de référence) en texte, +3. calculer les scores ROUGE en utilisant les mêmes techniques que nous avons vues précédemment, +4. sauvegarder les points de contrôle et pousser le tout vers le *Hub*. Ici, nous nous appuyons sur l'argument `blocking=False` de l'objet `Repository` afin de pouvoir pousser les points de contrôle par époque de manière _asynchrone_. Cela nous permet de poursuivre l'entraînement sans avoir à attendre le téléchargement quelque peu lent associé à un modèle de la taille d'un Go ! + +Ces étapes peuvent être vues dans le bloc de code suivant : + +```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): + # Entraînement + model.train() + for step, batch in enumerate(train_dataloader): + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) + + # Evaluation + model.eval() + for step, batch in enumerate(eval_dataloader): + with torch.no_grad(): + generated_tokens = accelerator.unwrap_model(model).generate( + batch["input_ids"], + attention_mask=batch["attention_mask"], + ) + + generated_tokens = accelerator.pad_across_processes( + generated_tokens, dim=1, pad_index=tokenizer.pad_token_id + ) + labels = batch["labels"] + + # Si nous n'avons pas rempli la longueur maximale, nous devons également remplir les étiquettes. + 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() + + # Remplacer -100 dans les étiquettes car nous ne pouvons pas les décoder. + 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) + + # Calculer les métriques + result = rouge_score.compute() + # Extract the median ROUGE scores + result = {key: value.mid.fmeasure * 100 for key, value in result.items()} + result = {k: round(v, 4) for k, v in result.items()} + print(f"Epoch {epoch}:", result) + + # Sauvegarder et télécharger + 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} +``` + +Et c'est tout ! Une fois que vous l'aurez exécuté, vous aurez un modèle et des résultats assez similaires à ceux que nous avons obtenus avec le `Trainer`. + +{/if} + +## Utilisation de votre modèle *finetuné* + +Une fois que vous avez poussé le modèle vers le *Hub*, vous pouvez jouer avec lui soit via le widget d'inférence, soit avec un objet `pipeline`, comme suit : + +```python +from transformers import pipeline + +hub_model_id = "huggingface-course/mt5-small-finetuned-amazon-en-es" +summarizer = pipeline("summarization", model=hub_model_id) +``` + +Nous pouvons alimenter notre pipeline avec quelques exemples de l'ensemble de test (que le modèle n'a pas vu) pour avoir une idée de la qualité des résumés. Tout d'abord, implémentons une fonction simple pour afficher ensemble la critique, le titre et le résumé généré : + +```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}'") +``` + +Examinons l'un des exemples anglais que nous recevons : + +```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.' +# Ce produit n'a rien de spécial... le livre est trop petit et rigide et il est difficile d'y écrire. L'énorme autocollant au dos ne se détache pas et a l'air super collant. Je n'achèterai plus jamais ce produit. J'aurais pu simplement acheter un journal dans un magasin à un dollar et ce serait à peu près la même chose. Il est également très cher pour ce qu'il est. + +'>>> Title: Not impressed at all... buy something else' # Pas du tout impressionné... achetez autre chose. + +'>>> Summary: Nothing special at all about this product' # Rien de spécial à propos de ce produit +``` + +Ce n'est pas si mal ! Nous pouvons voir que notre modèle a été capable d'effectuer un résumé _abstractif_ en augmentant certaines parties de la critique avec de nouveaux mots. Et peut-être que l'aspect le plus cool de notre modèle est qu'il est bilingue, donc nous pouvons également générer des résumés de critiques en espagnol : + +```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' # C'est une trilogie qui se lit très facilement. J'ai aimé, je ne m'attendais pas du tout à la fin. + +'>>> Title: Buena literatura para adolescentes' # Bonne littérature pour les adolescents + +'>>> Summary: Muy facil de leer' # Très facile à lire +``` + +Le résumé se traduit par "Très facile à lire", ce qui, comme nous pouvons le constater, a été extrait directement de la critique. Néanmoins, cela montre la polyvalence du modèle mT5 et vous a donné un aperçu de ce que c'est que de traiter un corpus multilingue ! + +Ensuite, nous allons nous intéresser à une tâche un peu plus complexe : entraîner un modèle de langue à partir de zéro. diff --git a/chapters/fr/chapter7/6.mdx b/chapters/fr/chapter7/6.mdx new file mode 100644 index 000000000..a4a4fa81d --- /dev/null +++ b/chapters/fr/chapter7/6.mdx @@ -0,0 +1,904 @@ + + +# Entraîner un modèle de langage causal à partir de zéro + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Jusqu'à présent, nous avons surtout utilisé des modèles pré-entraînés et les avons *finetunés* pour de nouveaux cas d'utilisation en réutilisant les poids du pré-entraînement. Comme nous l'avons vu dans le [Chapitre 1](/course/fr/chapter1), ceci est communément appelé _apprentissage par transfert_, et c'est une stratégie très efficace pour appliquer les modèles Transformer à la plupart des cas d'utilisation du monde réel où les données étiquetées sont rares. Dans ce chapitre, nous allons adopter une approche différente et entraîner un modèle complètement nouveau à partir de zéro. C'est une bonne approche à adopter si vous avez beaucoup de données et qu'elle est très différente des données de pré-entraînement utilisées pour les modèles disponibles. Cependant, le pré-entraînement d'un modèle de langue nécessite beaucoup plus de ressources informatiques que le simple affinage d'un modèle existant. Parmi les exemples où il peut être utile d'entraîner un nouveau modèle, citons les ensembles de données constitués de notes de musique, de séquences moléculaires telles que l'ADN ou de langages de programmation. Ces derniers ont récemment gagné en popularité grâce à des outils tels que TabNine et Copilot de GitHub, alimentés par le modèle Codex d'OpenAI, qui peuvent générer de longues séquences de code. Cette tâche de génération de texte est mieux abordée avec des modèles de langage autorégressifs ou causaux tels que GPT-2. + +Dans cette section, nous allons construire une version réduite d'un modèle de génération de code : nous nous concentrerons sur les compléments d'une ligne au lieu des fonctions ou des classes complètes, en utilisant un sous-ensemble de code Python. Lorsque vous travaillez avec des données en Python, vous êtes souvent en contact avec la pile de données scientifiques Python, composée des bibliothèques `matplotlib`, `seaborn`, `pandas` et `scikit-learn`. Lors de l'utilisation de ces *frameworks*, il est fréquent d'avoir besoin de rechercher des commandes spécifiques, il serait donc bien d'utiliser un modèle pour compléter ces appels pour nous. + + + + +Dans le [Chapitre 6](/course/fr/chapter6), nous avons créé un *tokenizer* efficace pour traiter le code source Python, mais nous avons toujours besoin d'un ensemble de données à grande échelle pour pré-entraîner un modèle. Ici, nous allons appliquer notre *tokenizer* à un corpus de code Python provenant des dépôts GitHub. Nous utiliserons ensuite l'API `Trainer` et 🤗 *Accelerate* pour entraîner le modèle. C'est parti ! + + + + +Il s'agit en fait de la présentation du modèle qui a été entraîné et téléchargé sur le *Hub* à l'aide du code présenté dans cette section. Vous pouvez le trouver [ici](https://huggingface.co/huggingface-course/codeparrot-ds?text=plt.imshow%28). Notez qu'étant donné qu'il y a une certaine randomisation dans la génération du texte, vous obtiendrez probablement un résultat légèrement différent. + +## Collecte des données + +Le code Python est disponible en abondance dans les dépôts de code tels que GitHub, que nous pouvons utiliser pour créer un ensemble de données en récupérant chaque dépôt Python. C'est l'approche adoptée dans le [manuel Transformers](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/) pour pré-entraîner un grand modèle GPT-2. En utilisant un dépôt GitHub d'environ 180 Go contenant approximativement 20 millions de fichiers Python appelé `codeparrot`, les auteurs ont construit un ensemble de données qu'ils ont ensuite partagé sur le [*Hub*](https://huggingface.co/datasets/transformersbook/codeparrot). + +Cependant, s'entraîner sur l'ensemble du corpus prend beaucoup de temps et demande beaucoup de calculs, et nous n'avons besoin que du sous-ensemble du jeu de données concerné par la pile Python pour la science des données. Commençons donc par filtrer l'ensemble de données `codeparrot` pour tous les fichiers qui incluent l'une des bibliothèques de cette pile. En raison de la taille de l'ensemble de données, nous voulons éviter de le télécharger ; à la place, nous utiliserons la fonctionnalité de streaming pour le filtrer à la volée. Pour nous aider à filtrer les échantillons de code utilisant les bibliothèques que nous avons mentionnées précédemment, nous utiliserons la fonction suivante : + +```py +def any_keyword_in_string(string, keywords): + for keyword in keywords: + if keyword in string: + return True + return False +``` + +Testons-le sur deux exemples : + +```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 +``` + +Nous pouvons l'utiliser pour créer une fonction qui diffusera l'ensemble de données et filtrera les éléments que nous voulons : + +```py +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) +``` + +Ensuite, nous pouvons simplement appliquer cette fonction à l'ensemble de données en continu : + +```py +# Cette cellule prendra beaucoup de temps à s'exécuter, donc vous devriez la sauter et aller à la suivante ! +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. +``` + +Cela nous laisse avec environ 3 % de l'ensemble de données original, ce qui est tout de même assez important. L'ensemble de données résultant est de 6 Go et se compose de 600 000 scripts Python ! + +Le filtrage de l'ensemble complet de données peut prendre de 2 à 3 heures, selon votre machine et votre bande passante. Si vous ne voulez pas passer par ce long processus vous-même, nous fournissons l'ensemble de données filtré sur le *Hub* pour que vous puissiez le télécharger : + +```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="train") + +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 + }) +}) +``` + + + +Le pré-entraînement du modèle de langue prendra un certain temps. Nous vous suggérons d'exécuter d'abord la boucle d'entraînement sur un échantillon des données en décommentant les deux lignes partielles ci-dessus, et de vous assurer que l'entraînement se termine avec succès et que les modèles sont stockés. Rien n'est plus frustrant qu'un entraînement qui échoue à la dernière étape parce que vous avez oublié de créer un dossier ou parce qu'il y a une faute de frappe à la fin de la boucle d'entraînement ! + + + +Examinons un exemple tiré de l'ensemble de données. Nous ne montrerons que les 200 premiers caractères de chaque champ : + +```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''' +``` + +Nous pouvons voir que le champ `content` contient le code sur lequel nous voulons que notre modèle s'entraîne. Maintenant que nous avons un jeu de données, nous devons préparer les textes afin qu'ils soient dans un format approprié pour le pré-entraînement. + +## Préparation du jeu de données + + + +La première étape sera de tokeniser les données, afin de pouvoir les utiliser pour l'entraînement. Puisque notre objectif est principalement d'autocompléter des appels de fonction courts, nous pouvons garder la taille du contexte relativement petite. L'avantage est que nous pouvons entraîner le modèle beaucoup plus rapidement et qu'il nécessite beaucoup moins de mémoire. S'il est important pour votre application d'avoir plus de contexte (par exemple, si vous voulez que le modèle écrive des tests unitaires basés sur un fichier avec la définition de la fonction), assurez-vous d'augmenter ce nombre, mais gardez également à l'esprit que cela s'accompagne d'une plus grande empreinte mémoire du GPU. Pour l'instant, fixons la taille du contexte à 128 *tokens*, par opposition aux 1 024 ou 2 048 utilisés dans GPT-2 ou GPT-3, respectivement. + +La plupart des documents contiennent beaucoup plus de 128 *tokens*, donc le fait de tronquer les entrées à la longueur maximale éliminerait une grande partie de notre jeu de données. A la place, nous utiliserons l'option `return_overflowing_tokens` pour tokeniser l'entrée entière et la diviser en plusieurs morceaux, comme nous l'avons fait dans [Chapter 6](/course/chapter6/4). Nous utiliserons également l'option `return_length` pour retourner automatiquement la longueur de chaque morceau créé. Souvent, le dernier morceau sera plus petit que la taille du contexte, et nous nous débarrasserons de ces morceaux pour éviter les problèmes de remplissage ; nous n'en avons pas vraiment besoin puisque nous avons beaucoup de données de toute façon. + +
+Chunking a large texts in several pieces. + +
+ +Voyons exactement comment cela fonctionne en examinant les deux premiers exemples : + +```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] +``` + +Nous pouvons voir que nous obtenons 34 segments au total à partir de ces deux exemples. En regardant les longueurs des *chunks*, nous pouvons voir que les *chunks* à la fin des deux documents ont moins de 128 *tokens* (117 et 41, respectivement). Ils ne représentent qu'une petite fraction du total des *chunks* que nous avons, donc nous pouvons les jeter sans risque. Avec le champ `overflow_to_sample_mapping`, nous pouvons aussi reconstruire quels *chunks* appartenaient à quels échantillons d'entrée. + +Avec cette opération, nous utilisons une fonctionnalité pratique de la fonction `Dataset.map()` dans 🤗 *Datasets*, qui est qu'elle ne nécessite pas de mappage un à un ; comme nous l'avons vu dans la [section 3](/course/fr/chapter7/3), nous pouvons créer des batchs avec plus ou moins d'éléments que le batchd'entrée. Ceci est utile lorsque l'on effectue des opérations telles que l'augmentation ou le filtrage des données qui modifient le nombre d'éléments. Dans notre cas, lors de la tokenisation de chaque élément en *chunks* de la taille de contexte spécifiée, nous créons de nombreux échantillons de chaque document. Nous devons juste nous assurer de supprimer les colonnes existantes, car elles ont une taille conflictuelle. Si nous voulions les garder, nous pourrions les répéter de manière appropriée et les retourner dans l'appel `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 + }) +}) +``` + +Nous avons maintenant 16,7 millions d'exemples avec 128 *tokens* chacun, ce qui correspond à environ 2,1 milliards de *tokens* au total. Pour référence, les modèles GPT-3 et Codex d'OpenAI sont entraînés sur 300 et 100 milliards de *tokens*, respectivement, où les modèles Codex sont initialisés à partir des points de contrôle GPT-3. Notre objectif dans cette section n'est pas de rivaliser avec ces modèles, qui peuvent générer des textes longs et cohérents, mais de créer une version réduite fournissant une fonction d'autocomplétion rapide pour les scientifiques des données. + +Maintenant que l'ensemble de données est prêt, configurons le modèle ! + + + +✏️ **Essayez** Se débarrasser de tous les morceaux qui sont plus petits que la taille du contexte n'était pas un gros problème ici parce que nous utilisons de petites fenêtres de contexte. Si vous augmentez la taille du contexte (ou si vous avez un corpus de documents courts), la fraction des morceaux qui sont jetés augmentera également. Une façon plus efficace de préparer les données est de joindre tous les échantillons dans un batch avec un *token* `eos_token_id` entre les deux, puis d'effectuer le chunking sur les séquences concaténées. Comme exercice, modifiez la fonction `tokenize()` pour utiliser cette approche. Notez que vous devrez mettre `truncation=False` et enlever les autres arguments du *tokenizer* pour obtenir la séquence complète des IDs des *tokens*. + + + + +## Initialisation d'un nouveau modèle + +Notre première étape consiste à initialiser fraîchement un modèle GPT-2. Nous utiliserons la même configuration pour notre modèle que pour le petit modèle GPT-2, donc nous chargeons la configuration pré-entraînée, nous nous assurons que la taille du *tokenizer* correspond à la taille du vocabulaire du modèle et nous passons les identifiants des *tokens* `bos` et `eos` (début et fin de séquence) : + +{#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, +) +``` + +Avec cette configuration, nous pouvons charger un nouveau modèle. Notez que c'est la première fois que nous n'utilisons pas la fonction `from_pretrained()`, puisque nous initialisons nous-mêmes un modèle : + +```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, +) +``` + +Avec cette configuration, nous pouvons charger un nouveau modèle. Notez que c'est la première fois que nous n'utilisons pas la fonction `from_pretrained()`, puisque nous initialisons nous-mêmes un modèle : + +```py +model = TFGPT2LMHeadModel(config) +model(model.dummy_inputs) # Builds the model +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} + +Notre modèle comporte 124 millions de paramètres que nous devrons régler. Avant de commencer l'entraînement, nous devons configurer un collateur de données qui se chargera de créer les lots. Nous pouvons utiliser le collateur `DataCollatorForLanguageModeling`, qui est conçu spécifiquement pour la modélisation du langage (comme son nom le suggère subtilement). En plus de l'empilage et du remplissage des lots, il s'occupe aussi de la création des étiquettes du modèle de langage -- dans la modélisation causale du langage, les entrées servent aussi d'étiquettes (juste décalées d'un élément), et ce collateur de données les crée à la volée pendant l'entraînement pour ne pas avoir à dupliquer les `input_ids`. + +Notez que `DataCollatorForLanguageModeling` supporte à la fois le *masked language modeling* (MLM) et le *causal language modeling* (CLM). Par défaut, il prépare les données pour MLM, mais nous pouvons passer à CLM en définissant l'argument `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} + +Prenons un exemple : + +```py +out = data_collator([tokenized_dataset["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} + +Nous pouvons voir que les exemples ont été empilés et que tous les tenseurs ont la même forme. + +{#if fw === 'tf'} + +Maintenant nous pouvons utiliser la méthode `to_tf_dataset()` pour convertir nos jeux de données en jeux de données TensorFlow avec le collateur de données que nous avons créé ci-dessus : + +```python +tf_train_dataset = tokenized_dataset["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) +tf_eval_dataset = tokenized_dataset["valid"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=32, +) +``` + +{/if} + + + +⚠️ Le déplacement des entrées et des étiquettes pour les aligner se fait à l'intérieur du modèle, de sorte que le collecteur de données ne fait que copier les entrées pour créer les étiquettes. + + + + +Nous avons maintenant tout ce qu'il faut pour former notre modèle - ce n'était pas si compliqué après tout ! Avant de commencer l'entraînement, nous devons nous connecter à Hugging Face. Si vous travaillez dans un *notebook*, vous pouvez le faire avec la fonction utilitaire suivante : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Cela affichera un widget où vous pourrez entrer vos identifiants de connexion à Hugging Face. + +Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : + +```bash +huggingface-cli login +``` + +{#if fw === 'pt'} + +Tout ce qu'il reste à faire est de configurer les arguments d'entraînement et de lancer le `Trainer`. Nous utiliserons un programme de taux d'apprentissage en cosinus avec un certain réchauffement et une taille de lot effective de 256 (`per_device_train_batch_size` * `gradient_accumulation_steps`). L'accumulation du gradient est utilisée lorsqu'un seul lot ne tient pas en mémoire, et construit le gradient de manière incrémentale à travers plusieurs passages avant/arrière. Nous verrons cela en action lorsque nous créerons la boucle d'entraînement avec 🤗 *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"], +) +``` + +Maintenant, nous pouvons simplement lancer le `Trainer` et attendre que l'entraînement se termine. Selon que vous l'exécutez sur la totalité ou sur un sous-ensemble de l'ensemble d'entraînement, cela prendra respectivement 20 ou 2 heures, alors prenez quelques cafés et un bon livre à lire ! + +```py +trainer.train() +``` + +Une fois l'entraînement terminé, nous pouvons pousser le modèle et le *tokenizer* vers le Hub : + +```py +trainer.push_to_hub() +``` + +{:else} + +Tout ce qu'il reste à faire est de configurer les hyperparamètres d'entraînement et d'appeler `compile()` et `fit()`. Nous utiliserons un programme de taux d'apprentissage avec un certain échauffement pour améliorer la stabilité de l'entraînement : + +```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) + +# Train in mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +Maintenant, nous pouvons simplement appeler `model.fit()` et attendre que l'entraînement se termine. Selon que vous l'exécutez sur la totalité ou sur un sous-ensemble de l'ensemble d'entraînement, cela prendra respectivement 20 ou 2 heures, alors prenez quelques cafés et un bon livre à lire ! Une fois l'entraînement terminé, nous pouvons pousser le modèle et le *tokenizer* vers le *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} + + + +✏️ **Essayez** Il ne nous a fallu qu'une trentaine de lignes de code en plus des `TrainingArguments` pour passer des textes bruts à l'entraînement de GPT-2. Essayez-le avec votre propre jeu de données et voyez si vous pouvez obtenir de bons résultats ! + + + + + +{#if fw === 'pt'} + +💡 Si vous avez accès à une machine avec plusieurs GPUs, essayez d'y exécuter le code. Le `Trainer` gère automatiquement plusieurs machines, et cela peut accélérer considérablement l'entraînement. + +{:else} + +💡 Si vous avez accès à une machine avec plusieurs GPU, vous pouvez essayer d'utiliser un contexte `MirroredStrategy` pour accélérer considérablement l'entraînement. Vous devrez créer un objet `tf.distribute.MirroredStrategy`, et vous assurer que les commandes `to_tf_dataset` ainsi que la création du modèle et l'appel à `fit()` sont tous exécutés dans son contexte `scope()`. Vous pouvez consulter la documentation à ce sujet [ici](https://www.tensorflow.org/guide/distributed_training#use_tfdistributestrategy_with_keras_modelfit). + +{/if} + + + +## Génération de code avec un pipeline + +C'est maintenant le moment de vérité : voyons comment le modèle entraîné fonctionne réellement ! Nous pouvons voir dans les logs que la perte a diminué régulièrement, mais pour mettre le modèle à l'épreuve, regardons comment il fonctionne sur certains messages. Pour ce faire, nous allons envelopper le modèle dans une `pipeline` de génération de texte, et nous allons le mettre sur le GPU pour des générations rapides s'il y en a un de disponible : + +{#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} + +Let's start with the simple task of creating a scatter plot: + +```py +txt = """\ +# créer des données +x = np.random.randn(100) +y = np.random.randn(100) + +# créer un nuage de points avec x, y +""" +print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) +``` + +```python out +# créer des données +x = np.random.randn(100) +y = np.random.randn(100) + +# créer un nuage de points avec x, y +plt.scatter(x, y) +``` + +Le résultat semble correct. Est-ce que cela fonctionne aussi pour une opération `pandas` ? Voyons si nous pouvons créer un `DataFrame` à partir de deux tableaux : + +```py +txt = """\ +# créer des données +x = np.random.randn(100) +y = np.random.randn(100) + +# créer un tableau de données à partir de x et y +""" +print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) +``` + +```python out +# créer des données +x = np.random.randn(100) +y = np.random.randn(100) + +# créer un tableau de données à partir de x et y +df = pd.DataFrame({'x': x, 'y': y}) +df.insert(0,'x', x) +for +``` + +Bien, c'est la bonne réponse. Bien qu'il insère ensuite la colonne `x` à nouveau. Comme le nombre de *tokens* générés est limité, la boucle `for` suivante est coupée. Voyons si nous pouvons faire quelque chose d'un peu plus complexe et faire en sorte que le modèle nous aide à utiliser l'opération `groupby` : + +```py +txt = """\ +# tableau de données avec profession, revenu et nom +df = pd.DataFrame({'profession': x, 'income':y, 'name': z}) + +# calculer le revenu moyen par profession""" +print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) +``` + +```python out +# tableau de données avec profession, revenu et nom +df = pd.DataFrame({'profession': x, 'income':y, 'name': z}) + +# calculer le revenu moyen par profession +profession = df.groupby(['profession']).mean() +``` + +Pas mal, c'est la bonne façon de faire. Enfin, voyons si nous pouvons aussi l'utiliser pour `scikit-learn` et mettre en place un modèle *Random Forest* : + +```py +txt = """ +# import random forest regressor from scikit-learn +from sklearn.ensemble import RandomForestRegressor + +# entraînement du modèle de forêt aléatoire avec 300 estimateurs sur 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 + +# entraînement du modèle de forêt aléatoire avec 300 estimateurs sur X, y : +rf = RandomForestRegressor(n_estimators=300, random_state=random_state, max_depth=3) +rf.fit(X, y) +rf +``` + +{#if fw === 'tf'} + +Au vu de ces quelques exemples, il semble que le modèle ait appris une partie de la syntaxe de la pile Python pour la science des données. Bien sûr, nous devrions évaluer le modèle de manière plus approfondie avant de le déployer dans le monde réel, mais il s'agit tout de même d'un prototype impressionnant. + +{:else} + +Au vu de ces quelques exemples, il semble que le modèle ait appris une partie de la syntaxe de la pile de science des données Python (bien sûr, nous devrions l'évaluer de manière plus approfondie avant de déployer le modèle dans le monde réel). Cependant, il est parfois nécessaire de personnaliser davantage l'entraînement du modèle afin d'obtenir les performances nécessaires pour un cas d'utilisation donné. Par exemple, que se passe-t-il si l'on souhaite mettre à jour dynamiquement la taille du lot ou si l'on dispose d'une boucle d'entraînement conditionnelle qui ignore les mauvais exemples à la volée ? Une option serait de sous-classer le `Trainer` et d'ajouter les changements nécessaires, mais parfois il est plus simple d'écrire la boucle d'entraînement à partir de zéro. C'est là qu'intervient 🤗 *Accelerate*. + +{/if} + +{#if fw === 'pt'} + +## Entraîner avec 🤗 *Accelerate* + +Nous avons vu comment entraîner un modèle avec le `Trainer`, qui peut permettre une certaine personnalisation. Cependant, parfois nous voulons un contrôle total sur la boucle d'entraînement, ou nous voulons faire quelques changements exotiques. Dans ce cas, 🤗 *Accelerate* est un excellent choix, et dans cette section, nous allons suivre les étapes pour l'utiliser pour entraîner notre modèle. Pour rendre les choses plus intéressantes, nous allons également ajouter une touche à la boucle d'entraînement. + + + +Puisque nous sommes principalement intéressés par l'autocomplétion sensible pour les bibliothèques de science des données, il est logique de donner plus de poids aux échantillons d'entraînement qui utilisent davantage ces bibliothèques. Nous pouvons facilement identifier ces exemples grâce à l'utilisation de mots-clés tels que `plt`, `pd`, `sk`, `fit`, et `predict`, qui sont les noms d'importation les plus fréquents pour `matplotlib.pyplot`, `pandas`, et `sklearn` ainsi que le modèle fit/predict de ce dernier. Si chacun d'entre eux est représenté par un seul token, nous pouvons facilement vérifier s'ils apparaissent dans la séquence d'entrée. Les *tokens* peuvent avoir un préfixe d'espacement, donc nous vérifierons aussi ces versions dans le vocabulaire du *tokenizer*. Pour vérifier que cela fonctionne, nous ajouterons un *token* de test qui devrait être divisé en plusieurs *tokens* : + +```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' +``` + +Super, ça a l'air de bien fonctionner ! Nous pouvons maintenant écrire une fonction de perte personnalisée qui prend la séquence d'entrée, les logits et les *tokens* clés que nous venons de sélectionner comme entrées. Tout d'abord, nous devons aligner les logits et les entrées : la séquence d'entrée décalée d'une unité vers la droite forme les étiquettes, puisque le *token* suivant est l'étiquette du *token* actuel. Nous pouvons y parvenir en commençant les étiquettes à partir du deuxième *token* de la séquence d'entrée, puisque le modèle ne fait pas de prédiction pour le premier *token* de toute façon. Ensuite, nous coupons le dernier logit, car nous n'avons pas d'étiquette pour le *token* qui suit la séquence d'entrée complète. Avec cela, nous pouvons calculer la perte par échantillon et compter les occurrences de tous les mots-clés dans chaque échantillon. Enfin, nous calculons la moyenne pondérée sur tous les échantillons en utilisant les occurrences comme poids. Comme nous ne voulons pas rejeter tous les échantillons qui ne contiennent pas de mots-clés, nous ajoutons 1 aux poids : + +```py +from torch.nn import CrossEntropyLoss +import torch + + +def keytoken_weighted_loss(inputs, logits, keytoken_ids, alpha=1.0): + # Shift so that tokens < n predict n + shift_labels = inputs[..., 1:].contiguous() + shift_logits = logits[..., :-1, :].contiguous() + # Calculate per-token loss + loss_fct = CrossEntropyLoss(reduce=False) + loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1)) + # Resize and average loss per sample + loss_per_sample = loss.view(shift_logits.size(0), shift_logits.size(1)).mean(axis=1) + # Calculate and scale weighting + weights = torch.stack([(inputs == kt).float() for kt in keytoken_ids]).sum( + axis=[0, 2] + ) + weights = alpha * (1.0 + weights) + # Calculate weighted average + weighted_loss = (loss_per_sample * weights).mean() + return weighted_loss +``` + +Avant de commencer à s'entraîner avec cette nouvelle fonction de perte géniale, nous devons préparer quelques éléments : + +- nous avons besoin de chargeurs de données pour charger les données par lots. +- nous devons définir les paramètres de décroissance du poids. +- de temps en temps, nous voulons évaluer, il est donc logique d'envelopper le code d'évaluation dans une fonction. + +Commençons par les chargeurs de données. Nous avons seulement besoin de définir le format du jeu de données à `"torch"`, et ensuite nous pouvons le passer à un PyTorch `DataLoader` avec la taille de lot appropriée : + +```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) +``` + +Ensuite, nous regroupons les paramètres de façon à ce que l'optimiseur sache lesquels bénéficieront d'une décroissance de poids supplémentaire. Habituellement, tous les termes de biais et de poids LayerNorm en sont exemptés ; voici comment nous pouvons le faire : + +```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}, + ] +``` + +Puisque nous voulons évaluer le modèle régulièrement sur l'ensemble de validation pendant l'entraînement, écrivons une fonction pour cela aussi. Elle passe simplement par le dataloader d'évaluation et rassemble toutes les pertes à travers les processus : + +```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() +``` + +Avec la fonction `evaluate()` nous pouvons rapporter la perte et la [perplexité](/course/fr/chapter7/3) à intervalles réguliers. Ensuite, nous redéfinissons notre modèle pour nous assurer que nous nous entraînons à nouveau à partir de zéro : + +```py +model = GPT2LMHeadModel(config) +``` + +Nous pouvons ensuite définir notre optimiseur, en utilisant la fonction précédente pour diviser les paramètres de la décroissance du poids : + +```py +from torch.optim import AdamW + +optimizer = AdamW(get_grouped_params(model), lr=5e-4) +``` + +Préparons maintenant le modèle, l'optimiseur et les chargeurs de données pour pouvoir commencer l'entraînement : + +```py +from accelerate import Accelerator + +accelerator = Accelerator(fp16=True) + +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + + + +🚨 Si vous vous entraînez sur un TPU, vous devrez déplacer tout le code commençant à la cellule ci-dessus dans une fonction d'entraînement dédiée. Voir le [Chapitre 3](/course/fr/chapter3) pour plus de détails. + + + +Maintenant que nous avons envoyé notre `train_dataloader` à `accelerator.prepare()`, nous pouvons utiliser sa longueur pour calculer le nombre d'étapes d'entraînement. Rappelez-vous que nous devons toujours faire cela après avoir préparé le dataloader, car cette méthode modifiera sa longueur. Nous utilisons un programme linéaire classique du taux d'apprentissage à 0 : + +```py +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, +) +``` + +Enfin, pour pousser notre modèle vers le Hub, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous au *Hub*, si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'ID du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix ; il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `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' +``` + +Ensuite, nous pouvons cloner ce référentiel dans un dossier local. S'il existe déjà, ce dossier local doit être un clone existant du référentiel avec lequel nous travaillons : + +```py +output_dir = "codeparrot-ds-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output_dir` en appelant la méthode `repo.push_to_hub()`. Cela nous aidera à télécharger les modèles intermédiaires à la fin de chaque époque. + +Avant de nous entraîner, exécutons un test rapide pour voir si la fonction d'évaluation fonctionne correctement : + +```py +evaluate() +``` + +```python out +(10.934126853942871, 56057.14453125) +``` + +Ce sont des valeurs très élevées pour la perte et la perplexité, mais ce n'est pas surprenant puisque nous n'avons pas encore entraîné le modèle. Avec cela, nous avons tout préparé pour écrire la partie principale du script d'entraînement : la boucle d'entraînement. Dans la boucle d'entraînement, nous itérons sur le chargeur de données et transmettons les lots au modèle. Avec les logits, nous pouvons alors évaluer notre fonction de perte personnalisée. Nous mettons à l'échelle la perte par le nombre d'étapes d'accumulation du gradient afin de ne pas créer de plus grandes pertes en agrégeant plus d'étapes. Avant de procéder à l'optimisation, nous découpons également les gradients pour une meilleure convergence. Enfin, tous les quelques pas, nous évaluons le modèle sur l'ensemble d'évaluation avec notre nouvelle fonction `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=len(train_dataloader) + ): + logits = model(batch["input_ids"]).logits + loss = keytoken_weighted_loss(batch["input_ids"], logits, keytoken_ids) + if step % 100 == 0: + accelerator.print( + { + "lr": get_lr(), + "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 + ) +``` + +Et voilà, vous disposez maintenant de votre propre boucle d'entraînement personnalisée pour les modèles de langage causal tels que GPT-2, que vous pouvez encore adapter à vos besoins. + + + +✏️ **Essayez** Vous pouvez créer votre propre fonction de perte personnalisée, adaptée à votre cas d'utilisation, ou ajouter une autre étape personnalisée dans la boucle d'entraînement. + + + + + +✏️ **Essayez** Lorsque vous effectuez de longues expériences d'entraînement, il est bon d'enregistrer les mesures importantes à l'aide d'outils tels que TensorBoard ou Weights & Biases. Ajoutez une journalisation appropriée à la boucle d'entraînement afin de pouvoir toujours vérifier comment se déroule l'entraînement. + + + +{/if} diff --git a/chapters/fr/chapter7/7.mdx b/chapters/fr/chapter7/7.mdx new file mode 100644 index 000000000..e301b1bac --- /dev/null +++ b/chapters/fr/chapter7/7.mdx @@ -0,0 +1,1228 @@ + + +# Réponse aux questions + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Il est temps de s'intéresser à la réponse aux questions ! Cette tâche peut prendre plusieurs formes, mais celle sur laquelle nous allons nous concentrer dans cette section est appelée réponse aux questions *extractives*. Il s'agit de poser des questions sur un document et d'identifier les réponses sous forme de "morceaux de texte" dans le document lui-même. + + + +Nous allons affiner un modèle BERT sur le [jeu de données SQuAD](https://rajpurkar.github.io/SQuAD-explorer/), qui consiste en des questions posées par des *crowdworkers* sur un ensemble d'articles de Wikipedia. Cela nous donnera un modèle capable de calculer des prédictions comme celle-ci : + + + + +Il s'agit en fait de la présentation du modèle qui a été entraîné et téléchargé sur le *Hub* à l'aide du code présenté dans cette section. Vous pouvez le trouver et vérifier les prédictions [ici](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) + + + +💡 Les modèles à codeur unique comme BERT ont tendance à être excellents pour extraire les réponses à des questions factuelles comme "Qui a inventé l'architecture Transformer ?", mais ne sont pas très performants lorsqu'on leur pose des questions ouvertes comme "Pourquoi le ciel est-il bleu ?". Dans ces cas plus difficiles, les modèles encodeurs-décodeurs comme T5 et BART sont généralement utilisés pour synthétiser les informations d'une manière assez similaire au [résumé de texte](/cours/fr/chapter7/5). Si vous êtes intéressé par ce type de réponse aux questions *génératives*, nous vous recommandons de consulter notre [démo](https://yjernite.github.io/lfqa.html) basée sur le [jeu de données ELI5](https://huggingface.co/datasets/eli5). + + + +## Préparation des données + +Le jeu de données le plus utilisé comme référence académique pour la réponse extractive aux questions est [SQuAD](https://rajpurkar.github.io/SQuAD-explorer/), c'est donc celui que nous utiliserons ici. Il existe également une référence plus difficile [SQuAD v2](https://huggingface.co/datasets/squad_v2), qui comprend des questions sans réponse. Tant que votre propre jeu de données contient une colonne pour les contextes, une colonne pour les questions et une colonne pour les réponses, vous devriez être en mesure d'adapter les étapes ci-dessous. + +### Le jeu de données SQuAD + +Comme d'habitude, nous pouvons télécharger et mettre en cache le jeu de données en une seule étape grâce à `load_dataset()` : + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("squad") +``` + +Nous pouvons alors jeter un coup d'œil à cet objet pour en savoir plus sur le jeu de données 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 + }) +}) +``` + +On dirait que nous avons tout ce dont nous avons besoin avec les champs `context`, `question` et `answers`, alors imprimons-les pour le premier élément de notre ensemble d'entraînement : + +```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.' +# Sur le plan architectural, l'école a un caractère catholique. Au sommet du dôme doré du bâtiment principal se trouve une statue dorée de la Vierge Marie. Immédiatement devant le bâtiment principal et face à lui, se trouve une statue en cuivre du Christ, les bras levés, avec la légende "Venite Ad Me Omnes". À côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s'agit d'une réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette Soubirous en 1858. Au bout de l'allée principale (et dans une ligne directe qui passe par 3 statues et le Dôme d'or), se trouve une statue de pierre simple et moderne de Marie'. +Question: 'To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France?' # A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes, en France ? +Answer: {'text': ['Saint Bernadette Soubirous'], 'answer_start': [515]} +``` + +Les champs `context` et `question` sont très simples à utiliser. Le champ `answers` est un peu plus délicat car il compile un dictionnaire avec deux champs qui sont tous deux des listes. C'est le format qui sera attendu par la métrique `squad` lors de l'évaluation ; si vous utilisez vos propres données, vous n'avez pas nécessairement besoin de vous soucier de mettre les réponses dans le même format. Le champ `text` est assez évident, et le champ `answer_start` contient l'indice du caractère de départ de chaque réponse dans le contexte. + +Pendant l'entraînement, il n'y a qu'une seule réponse possible. Nous pouvons vérifier cela en utilisant la méthode `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 +}) +``` + +Pour l'évaluation, cependant, il existe plusieurs réponses possibles pour chaque échantillon, qui peuvent être identiques ou différentes : + +```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]} +``` + +Nous ne nous plongerons pas dans le script d'évaluation car tout sera enveloppé par une métrique 🤗 *Datasets* pour nous, mais la version courte est que certaines des questions ont plusieurs réponses possibles, et ce script va comparer une réponse prédite à toutes les réponses acceptables et prendre le meilleur score. Si nous regardons l'échantillon de l'indice 2, par exemple : + +```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.' +# Le Super Bowl 50 était un match de football américain visant à déterminer le champion de la National Football League (NFL) pour la saison 2015. Les Denver Broncos, champions de la Conférence de football américain (AFC), ont battu les Carolina Panthers, champions de la Conférence nationale de football (NFC), 24 à 10, pour remporter leur troisième titre de Super Bowl. Le match s'est déroulé le 7 février 2016 au Levi\'s Stadium, dans la baie de San Francisco, à Santa Clara, en Californie. Comme il s'agissait du 50e Super Bowl, la ligue a mis l'accent sur l'" anniversaire doré " avec diverses initiatives sur le thème de l'or, ainsi qu'en suspendant temporairement la tradition de nommer chaque match du Super Bowl avec des chiffres romains (en vertu de laquelle le match aurait été appelé " Super Bowl L "), afin que le logo puisse mettre en évidence les chiffres arabes 50.'' +'Where did Super Bowl 50 take place?' # Où a eu lieu le Super Bowl 50 ? +``` + +nous pouvons voir que la réponse peut effectivement être l'une des trois possibilités que nous avons vues précédemment. + +### Traitement des données d'entraînement + + + +Commençons par le prétraitement des données d'entraînement. La partie la plus difficile sera de générer des étiquettes pour la réponse à la question, qui seront les positions de début et de fin des *tokens* correspondant à la réponse dans le contexte. + +Mais ne nous emballons pas. Tout d'abord, nous devons convertir le texte de l'entrée en identifiants que le modèle peut comprendre, en utilisant un *tokenizer* : + +```py +from transformers import AutoTokenizer + +model_checkpoint = "bert-base-cased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +Comme mentionné précédemment, nous allons *finetuner* un modèle BERT, mais vous pouvez utiliser n'importe quel autre type de modèle tant qu'il a un *tokenizer* rapide implémenté. Vous pouvez voir toutes les architectures qui sont livrées avec une version rapide dans [ce grand tableau](https://huggingface.co/transformers/#supported-frameworks), et pour vérifier que l'objet `tokenizer` que vous utilisez est bien soutenu par des 🤗 *Tokenizers* vous pouvez regarder son attribut `is_fast` : + +```py +tokenizer.is_fast +``` + +```python out +True +``` + +Nous pouvons transmettre à notre *tokenizer* la question et le contexte ensemble, et il insérera correctement les *tokens* spéciaux pour former une phrase comme celle-ci : + +``` +[CLS] question [SEP] context [SEP] +``` + +Vérifions à nouveau : + +```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]' + +'[CLS] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] Architecturalement, ' +l'école a un caractère catholique. Au sommet du dôme doré du bâtiment principal se trouve une statue dorée de la Vierge ' +Marie. Immédiatement devant le bâtiment principal et face à lui, se trouve une statue en cuivre du Christ, les bras ''levés''. +'levés avec la légende " Venite Ad Me Omnes ". A côté du bâtiment principal se trouve la basilique du Sacré-Cœur. +'Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s'agit d'une ' +'réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette ' +Soubirous en 1858. Au bout de l'allée principale ( et en ligne directe qui passe par 3 statues ' +'et le Dôme d'or), se trouve une statue de Marie en pierre, simple et moderne. [SEP]'' +``` + +Les étiquettes seront alors l'index des *tokens* de début et de fin de la réponse, et le modèle sera chargé de prédire un logit de début et de fin par *token* dans l'entrée, les étiquettes théoriques étant les suivantes : + +
+One-hot encoded labels for question answering. + +
+ +Dans ce cas, le contexte n'est pas trop long, mais certains des exemples de l'ensemble de données ont des contextes très longs qui dépasseront la longueur maximale que nous avons fixée (qui est de 384 dans ce cas). Comme nous l'avons vu dans le [Chapitre 6](/course/fr/chapter6/4) lorsque nous avons exploré les internes du pipeline `question-answering`, nous allons traiter les contextes longs en créant plusieurs caractéristiques d'entraînement à partir d'un échantillon de notre jeu de données, avec une fenêtre glissante entre eux. + +Pour voir comment cela fonctionne en utilisant l'exemple actuel, nous pouvons limiter la longueur à 100 et utiliser une fenêtre glissante de 50 *tokens*. Pour rappel, nous utilisons + +- `max_length` pour définir la longueur maximale (ici 100) +- `truncation="only_second"` pour tronquer le contexte (qui est en deuxième position) quand la question avec son contexte est trop longue +- `stride` pour fixer le nombre de *tokens* se chevauchant entre deux morceaux successifs (ici 50) +- `return_overflowing_tokens=True` pour indiquer au tokenizer que l'on veut les *tokens* qui débordent + +```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] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] Sur le plan architectural, l'école a un caractère catholique. Au sommet du dôme doré du bâtiment principal se trouve une statue dorée de la Vierge Marie. Immédiatement devant le bâtiment principal et face à lui, se trouve une statue en cuivre du Christ, les bras levés, avec la légende " Venite Ad Me Omnes ". À côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la 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] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [le bâtiment principal et face à lui, une statue en cuivre du Christ aux bras levés avec la légende " Venite Ad Me Omnes ". À côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s'agit d'une réplique de la grotte de Lourdes, en France, où la Vierge [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] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [A côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s'agit d'une réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette Soubirous en 1858. Au bout de l'allée principale ( et dans une ligne directe qui relie par 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]' +'[CLS] A qui la Vierge Marie est-elle prétendument apparue en 1858 à Lourdes France ? [SEP]. Il s'agit d'une réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette Soubirous en 1858. Au bout de l'allée principale (et dans une ligne directe qui passe par 3 statues et le Dôme d'or), se trouve une simple statue de pierre moderne de Marie. [SEP]' +``` + +Comme nous pouvons le voir, notre exemple a été divisé en quatre entrées, chacune d'entre elles contenant la question et une partie du contexte. Notez que la réponse à la question ("Bernadette Soubirous") n'apparaît que dans la troisième et dernière entrée, donc en traitant les longs contextes de cette façon, nous allons créer quelques exemples d'entraînement où la réponse n'est pas incluse dans le contexte. Pour ces exemples, les étiquettes seront `start_position = end_position = 0` (donc nous prédisons le *token* `[CLS]`). Nous définirons également ces étiquettes dans le cas malheureux où la réponse a été tronquée de sorte que nous n'avons que le début (ou la fin) de celle-ci. Pour les exemples où la réponse est entièrement dans le contexte, les étiquettes seront l'index du *token* où la réponse commence et l'index du *token* où la réponse se termine. + +L'ensemble de données nous fournit le caractère de début de la réponse dans le contexte, et en ajoutant la longueur de la réponse, nous pouvons trouver le caractère de fin dans le contexte. Pour faire correspondre ces indices aux *tokens*, nous devrons utiliser les mappages d'offset que nous avons étudiés au [Chapitre 6](/course/chapter6/4). Nous pouvons faire en sorte que notre *tokenizer* renvoie ces index en passant `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']) +``` + +Comme nous pouvons le voir, nous récupérons les habituels ID d'entrée, ID de type de jeton, et masque d'attention, ainsi que le mappage d'offset dont nous avions besoin et une clé supplémentaire, `overflow_to_sample_mapping`. La valeur correspondante nous sera utile lorsque nous tokeniserons plusieurs textes en même temps (ce que nous devrions faire pour bénéficier du fait que notre *tokenizer* est soutenu par Rust). Puisqu'un échantillon peut donner plusieurs caractéristiques, il fait correspondre chaque caractéristique à l'exemple d'où elle provient. Parce qu'ici nous avons seulement tokenisé un exemple, nous obtenons une liste de `0`s : + +```py +inputs["overflow_to_sample_mapping"] +``` + +```python out +[0, 0, 0, 0] +``` + +Mais si nous tokenisons plus d'exemples, cela deviendra plus utile : + +```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].' +``` + +Comme nous pouvons le voir, les trois premiers exemples (aux indices 2, 3 et 4 de l'ensemble d'entraînement) ont chacun donné quatre caractéristiques et le dernier exemple (à l'indice 5 de l'ensemble d'entraînement) a donné 7 caractéristiques. + +Ces informations seront utiles pour associer chaque caractéristique obtenue à son étiquette correspondante. Comme mentionné précédemment, ces étiquettes sont : + +- `(0, 0)` si la réponse n'est pas dans l'espace correspondant du contexte. +- `(start_position, end_position)` si la réponse est dans l'espace correspondant du contexte, avec `start_position` étant l'index du *token* (dans les IDs d'entrée) au début de la réponse et `end_position` étant l'index du *token* (dans les IDs d'entrée) où la réponse se termine. + +Pour déterminer ce qui est le cas et, le cas échéant, les positions des *tokens*, nous trouvons d'abord les indices qui commencent et finissent le contexte dans les IDs d'entrée. Nous pourrions utiliser les IDs du type de *token* pour le faire, mais puisque ceux-ci n'existent pas nécessairement pour tous les modèles (DistilBERT ne les requiert pas, par exemple), nous allons plutôt utiliser la méthode `sequence_ids()` du `BatchEncoding` que notre tokenizer retourne. + +Une fois que nous avons ces indices de *tokens*, nous regardons les offsets correspondants, qui sont des tuples de deux entiers représentant l'étendue des caractères dans le contexte original. Nous pouvons ainsi détecter si le *chunk* du contexte dans cette fonctionnalité commence après la réponse ou se termine avant que la réponse ne commence (dans ce cas, l'étiquette est `(0, 0)`). Si ce n'est pas le cas, nous bouclons pour trouver le premier et le dernier *token* de la réponse : + +```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) + + # Trouver le début et la fin du contexte + idx = 0 + while sequence_ids[idx] != 1: + idx += 1 + context_start = idx + while sequence_ids[idx] == 1: + idx += 1 + context_end = idx - 1 + + # Si la réponse n'est pas entièrement dans le contexte, l'étiquette est (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: + # Otherwise it's the start and end token positions + 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]) +``` + +Jetons un coup d'œil à quelques résultats pour vérifier que notre approche est correcte. Pour la première caractéristique, nous trouvons `(83, 85)` comme étiquettes, alors comparons la réponse théorique avec l'étendue décodée des *tokens* de 83 à 85 (inclus) : + +```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' +``` + +Donc, c'est une correspondance ! Maintenant, vérifions l'index 4, où nous avons mis les étiquettes à `(0, 0)`, ce qui signifie que la réponse n'est pas dans le *chunk* de contexte de cette caractéristique : + +```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]' +``` + +En effet, nous ne voyons pas la réponse dans le contexte. + + + +✏️ **A votre tour !** En utilisant l'architecture XLNet, le *padding* est appliqué à gauche et la question et le contexte sont intervertis. Adaptez tout le code que nous venons de voir à l'architecture XLNet (et ajoutez `padding=True`). Soyez conscient que le token `[CLS]` peut ne pas être à la position 0 avec le *padding* appliqué. + + + +Maintenant que nous avons vu étape par étape comment prétraiter nos données d'entraînement, nous pouvons les regrouper dans une fonction que nous appliquerons à l'ensemble des données d'entraînement. Nous allons rembourrer chaque caractéristique à la longueur maximale que nous avons définie, car la plupart des contextes seront longs (et les échantillons correspondants seront divisés en plusieurs caractéristiques), il n'y a donc pas de réel avantage à appliquer un rembourrage dynamique ici : + +```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) + + # Trouver le début et la fin du contexte + idx = 0 + while sequence_ids[idx] != 1: + idx += 1 + context_start = idx + while sequence_ids[idx] == 1: + idx += 1 + context_end = idx - 1 + + # Si la réponse n'est pas entièrement dans le contexte, l'étiquette est (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: + # Otherwise it's the start and end token positions + 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 +``` + +Notez que nous avons défini deux constantes pour déterminer la longueur maximale utilisée ainsi que la longueur de la fenêtre glissante, et que nous avons ajouté un petit nettoyage avant la tokénisation : certaines des questions dans le jeu de données SQuAD ont des espaces supplémentaires au début et à la fin qui n'ajoutent rien (et prennent de la place lors de la tokénisation si vous utilisez un modèle comme RoBERTa), donc nous avons supprimé ces espaces supplémentaires. + +Pour appliquer cette fonction à l'ensemble de l'entraînement, nous utilisons la méthode `Dataset.map()` avec le flag `batched=True`. C'est nécessaire ici car nous changeons la longueur de l'ensemble de données (puisqu'un exemple peut donner plusieurs caractéristiques d'entraînement) : + +```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) +``` + +Comme nous pouvons le voir, le prétraitement a ajouté environ 1 000 caractéristiques. Notre ensemble d'entraînement est maintenant prêt à être utilisé - passons au prétraitement de l'ensemble de validation ! + +### Traitement des données de validation + +Le prétraitement des données de validation sera légèrement plus facile car nous n'avons pas besoin de générer des étiquettes (sauf si nous voulons calculer une perte de validation, mais ce nombre ne nous aidera pas vraiment à comprendre la qualité du modèle). La vraie joie sera d'interpréter les prédictions du modèle dans des étendues du contexte original. Pour cela, il nous suffit de stocker les mappages de décalage et un moyen de faire correspondre chaque caractéristique créée à l'exemple original dont elle provient. Puisqu'il y a une colonne ID dans l'ensemble de données original, nous utiliserons cet ID. + +La seule chose que nous allons ajouter ici est un petit nettoyage des mappages de décalage. Ils contiendront les offsets pour la question et le contexte, mais une fois que nous serons dans la phase de post-traitement, nous n'aurons aucun moyen de savoir quelle partie des IDs d'entrée correspondait au contexte et quelle partie était la question (la méthode `sequence_ids()` que nous avons utilisée n'est disponible que pour la sortie du *tokenizer*). Donc, nous allons mettre les offsets correspondant à la question à `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 +``` + +Nous pouvons appliquer cette fonction sur l'ensemble des données de validation comme précédemment : + +```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) +``` + +Dans ce cas, nous n'avons ajouté que quelques centaines d'échantillons, il semble donc que les contextes dans l'ensemble de données de validation soient un peu plus courts. + +Maintenant que nous avons prétraité toutes les données, nous pouvons passer à l'entraînement. + +{#if fw === 'pt'} + +## *Finetuner* le modèle avec l'API `Trainer` + +Le code d'entraînement pour cet exemple ressemblera beaucoup au code des sections précédentes -- la chose la plus difficile sera d'écrire la fonction `compute_metrics()`. Puisque nous avons capitonné tous les échantillons à la longueur maximale que nous avons fixée, il n'y a pas de collateur de données à définir, donc ce calcul de métrique est vraiment la seule chose dont nous devons nous soucier. La partie la plus difficile sera de post-traiter les prédictions du modèle en travées de texte dans les exemples originaux ; une fois que nous aurons fait cela, la métrique de la bibliothèque 🤗 *Datasets* fera le gros du travail pour nous. + +{:else} + +## *Finetuner* fin du modèle avec Keras + +Le code d'entraînement de cet exemple ressemblera beaucoup au code des sections précédentes, mais le calcul des métriques sera un défi unique. Puisque nous avons capitonné tous les échantillons à la longueur maximale que nous avons définie, il n'y a pas de collateur de données à définir, donc le calcul de la métrique est vraiment la seule chose dont nous devons nous soucier. La partie la plus difficile sera de post-traiter les prédictions du modèle en travées de texte dans les exemples originaux ; une fois que nous aurons fait cela, la métrique de la bibliothèque 🤗 *Datasets* fera le gros du travail pour nous. + +{/if} + +### Post-traitement + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Le modèle produira des logits pour les positions de début et de fin de la réponse dans les IDs d'entrée, comme nous l'avons vu lors de notre exploration du [`question-answering` pipeline](/course/chapter6/4). L'étape de post-traitement sera similaire à ce que nous avons fait là-bas, donc voici un rappel rapide des actions que nous avons prises : + +- nous avons masqué les logits de début et de fin correspondant aux *tokens* en dehors du contexte, +- nous avons ensuite converti les logits de début et de fin en probabilités en utilisant un softmax, +- nous avons attribué un score à chaque paire `(start_token, end_token)` en prenant le produit des deux probabilités correspondantes, +- nous avons cherché la paire avec le score maximum qui donnait une réponse valide (par exemple, un `start_token` inférieur au `end_token`). + +Ici, nous allons modifier légèrement ce processus car nous n'avons pas besoin de calculer les scores réels (juste la réponse prédite). Cela signifie que nous pouvons sauter l'étape du softmax. Pour aller plus vite, nous ne noterons pas non plus toutes les paires `(start_token, end_token)` possibles, mais seulement celles correspondant aux logits `n_best` les plus élevés (avec `n_best=20`). Puisque nous sauterons le softmax, ces scores seront des scores logit, et seront obtenus en prenant la somme des logits de début et de fin (au lieu du produit, à cause de la règle \\(\log(ab) = \log(a) + \log(b)\)). + +Pour démontrer tout cela, nous aurons besoin d'un certain type de prédictions. Puisque nous n'avons pas encore entraîné notre modèle, nous allons utiliser le modèle par défaut du pipeline d'assurance qualité pour générer quelques prédictions sur une petite partie de l'ensemble de validation. Nous pouvons utiliser la même fonction de traitement que précédemment ; parce qu'elle repose sur la constante globale `tokenizer`, nous devons juste changer cet objet pour le tokenizer du modèle que nous voulons utiliser temporairement : + +```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, +) +``` + +Maintenant que le prétraitement est terminé, nous changeons le *tokenizer* pour celui que nous avons choisi à l'origine : + +```python +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +Nous supprimons ensuite les colonnes de notre `eval_set` qui ne sont pas attendues par le modèle, nous construisons un lot avec l'ensemble de ce petit ensemble de validation, et nous le passons au modèle. Si un GPU est disponible, nous l'utilisons pour aller plus vite : + +{#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) +``` + +Puisque le `Trainer` nous donnera les prédictions sous forme de tableaux NumPy, nous récupérons les logits de début et de fin et les convertissons dans ce format : + +```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) +``` + +Pour faciliter l'expérimentation, nous allons convertir ces sorties en tableaux NumPy : + +```python +start_logits = outputs.start_logits.numpy() +end_logits = outputs.end_logits.numpy() +``` + +{/if} + +Maintenant, nous devons trouver la réponse prédite pour chaque exemple dans notre `small_eval_set`. Un exemple peut avoir été divisé en plusieurs caractéristiques dans `eval_set`, donc la première étape est de faire correspondre chaque exemple dans `small_eval_set` aux caractéristiques correspondantes dans `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) +``` + +Avec cela en main, nous pouvons vraiment nous mettre au travail en parcourant en boucle tous les exemples et, pour chaque exemple, toutes les caractéristiques associées. Comme nous l'avons dit précédemment, nous allons regarder les scores logit pour les `n_meilleurs` logits de début et logits de fin, en excluant les positions qui donnent : + +- une réponse qui ne serait pas dans le contexte. +- une réponse avec une longueur négative +- une réponse qui est trop longue (nous limitons les possibilités à `max_answer_length=30`) + +Une fois que nous avons toutes les réponses possibles notées pour un exemple, nous choisissons simplement celle qui a le meilleur score logit : + +```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: + # Ignore les réponses qui ne sont pas entièrement dans le contexte + if offsets[start_index] is None or offsets[end_index] is None: + continue + # Ignorer les réponses dont la longueur est soit < 0 soit > 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"]}) +``` + +Le format final des réponses prédites est celui qui sera attendu par la métrique que nous allons utiliser. Comme d'habitude, nous pouvons le charger à l'aide de la bibliothèque 🤗 *Datasets* : + +```python +from datasets import load_metric + +metric = load_metric("squad") +``` + +Cette métrique attend les réponses prédites dans le format que nous avons vu ci-dessus (une liste de dictionnaires avec une clé pour l'ID de l'exemple et une clé pour le texte prédit) et les réponses théoriques dans le format ci-dessous (une liste de dictionnaires avec une clé pour l'ID de l'exemple et une clé pour les réponses possibles) : + +```python +theoretical_answers = [ + {"id": ex["id"], "answers": ex["answers"]} for ex in small_eval_set +] +``` + +Nous pouvons maintenant vérifier que nous obtenons des résultats raisonnables en examinant le premier élément des deux listes : + +```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]}} +``` + +Pas trop mal ! Voyons maintenant le score que la métrique nous donne : + +```python +metric.compute(predictions=predicted_answers, references=theoretical_answers) +``` + +```python out +{'exact_match': 83.0, 'f1': 88.25} +``` + +Encore une fois, c'est plutôt bon si l'on considère que, selon [son article](https://arxiv.org/abs/1910.01108v2), DistilBERT *finetuné* sur SQuAD obtient 79,1 et 86,9 pour ces scores sur l'ensemble des données. + +{#if fw === 'pt'} + +Maintenant, mettons tout ce que nous venons de faire dans une fonction `compute_metrics()` que nous utiliserons dans le `Trainer`. Normalement, cette fonction `compute_metrics()` reçoit seulement un tuple `eval_preds` avec les logits et les labels. Ici, nous aurons besoin d'un peu plus, car nous devons chercher dans le jeu de données des caractéristiques pour le décalage et dans le jeu de données des exemples pour les contextes originaux, donc nous ne serons pas en mesure d'utiliser cette fonction pour obtenir des résultats d'évaluation réguliers pendant l'entraînement. Nous ne l'utiliserons qu'à la fin de l'entraînement pour vérifier les résultats. + +La fonction `compute_metrics()` regroupe les mêmes étapes que précédemment ; nous ajoutons juste une petite vérification au cas où nous ne trouverions aucune réponse valide (dans ce cas nous prédisons une chaîne vide). + +{:else} + +Maintenant, mettons tout ce que nous venons de faire dans une fonction `compute_metrics()` que nous utiliserons après avoir entraîné notre modèle. Nous aurons besoin de passer un peu plus que juste les logits de sortie, car nous devons chercher dans le jeu de données des caractéristiques pour le décalage et dans le jeu de données des exemples pour les contextes originaux : + +{/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 = [] + + # Parcourir en boucle toutes les fonctionnalités associées à cet exemple + 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: + # Ignore les réponses qui ne sont pas entièrement dans le contexte + if offsets[start_index] is None or offsets[end_index] is None: + continue + # Ignore les réponses dont la longueur est soit < 0, soit > 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) + + # Sélectionne la réponse avec le meilleur score + 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) +``` + +Nous pouvons vérifier que cela fonctionne sur nos prédictions : + +```python +compute_metrics(start_logits, end_logits, eval_set, small_eval_set) +``` + +```python out +{'exact_match': 83.0, 'f1': 88.25} +``` + +C'est bien ! Maintenant, utilisons ceci pour affiner notre modèle. + +### *Finetuning* du modèle + +{#if fw === 'pt'} + +Nous sommes maintenant prêts à entraîner notre modèle. Créons-le d'abord, en utilisant la classe `AutoModelForQuestionAnswering` comme précédemment : + +```python +model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) +``` + +{:else} + +Nous sommes maintenant prêts à entraîner notre modèle. Créons-le d'abord, en utilisant la classe `TFAutoModelForQuestionAnswering` comme précédemment : + +```python +model = TFAutoModelForQuestionAnswering.from_pretrained(model_checkpoint) +``` + +{/if} + +Comme d'habitude, nous recevons un avertissement indiquant que certains poids ne sont pas utilisés (ceux de la tête de pré-entraînement) et que d'autres sont initialisés de manière aléatoire (ceux de la tête de réponse aux questions). Vous devriez être habitué à cela maintenant, mais cela signifie que ce modèle n'est pas encore prêt à être utilisé et qu'il a besoin d'être *finetuné*. Une bonne chose que nous soyons sur le point de le faire ! + +Pour pouvoir pousser notre modèle vers le *Hub*, nous devons nous connecter à Hugging Face. Si vous exécutez ce code dans un *notebook*, vous pouvez le faire avec la fonction utilitaire suivante, qui affiche un widget où vous pouvez entrer vos identifiants de connexion : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : + +```bash +huggingface-cli login +``` + +{#if fw === 'pt'} + +Une fois ceci fait, nous pouvons définir nos `TrainingArguments`. Comme nous l'avons dit lorsque nous avons défini notre fonction pour calculer la métrique, nous ne serons pas en mesure d'avoir une boucle d'évaluation régulière à cause de la signature de la fonction `compute_metrics()`. Nous pourrions écrire notre propre sous-classe de `Trainer` pour faire cela (une approche que vous pouvez trouver dans le [script d'exemple de réponse aux questions](https://github.com/huggingface/transformers/blob/master/examples/pytorch/question-answering/trainer_qa.py)), mais c'est un peu trop long pour cette section. A la place, nous n'évaluerons le modèle qu'à la fin de l'entraînement et nous vous montrerons comment faire une évaluation régulière dans "Une boucle d'entraînement personnalisée" ci-dessous. + +C'est vraiment là que l'API `Trainer` montre ses limites et que la bibliothèque 🤗 *Accelerate* brille : personnaliser la classe pour un cas d'utilisation spécifique peut être pénible, mais modifier une boucle d'entraînement entièrement exposée est facile. + +Jetons un coup d'œil à notre `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, +) +``` + +Nous avons déjà vu la plupart d'entre eux : nous définissons quelques hyperparamètres (comme le taux d'apprentissage, le nombre d'époques pour lesquelles nous nous entraînons, et une certaine décroissance de poids) et nous indiquons que nous voulons sauvegarder le modèle à la fin de chaque époque, sauter l'évaluation, et télécharger nos résultats vers le Model Hub. Nous activons également l'entraînement en précision mixte avec `fp16=True`, car cela peut accélérer l'entraînement sur un GPU récent. + +{:else} + +Maintenant que c'est fait, nous pouvons créer nos ensembles de données TF. Nous pouvons utiliser le simple collateur de données par défaut cette fois-ci : + +```python +from transformers import DefaultDataCollator + +data_collator = DefaultDataCollator(return_tensors="tf") +``` + +Et maintenant nous créons les jeux de données comme d'habitude. + +```python +tf_train_dataset = train_dataset.to_tf_dataset( + columns=[ + "input_ids", + "start_positions", + "end_positions", + "attention_mask", + "token_type_ids", + ], + collate_fn=data_collator, + shuffle=True, + batch_size=16, +) +tf_eval_dataset = validation_dataset.to_tf_dataset( + columns=["input_ids", "attention_mask", "token_type_ids"], + collate_fn=data_collator, + shuffle=False, + batch_size=16, +) +``` + +Ensuite, nous configurons nos hyperparamètres d'entraînement et compilons notre modèle : + +```python +from transformers import create_optimizer +from transformers.keras_callbacks import PushToHubCallback +import tensorflow as tf + +# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans le jeu de données, divisé par la taille du batch, puis multiplié par le nombre total d'époques. +# par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un lot tf.data.Dataset, +# et non le jeu de données original Hugging Face Dataset, donc son len() est déjà 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) + +# Entraîner en mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +Enfin, nous sommes prêts à nous entraîner avec `model.fit()`. Nous utilisons un `PushToHubCallback` pour télécharger le modèle sur le *Hub* après chaque époque. + +{/if} + +Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, donc dans notre cas il sera dans `"sgugger/bert-finetuned-squad"`. Nous pouvons passer outre en passant un `hub_model_id` ; par exemple, pour pousser le modèle dans l'organisation `huggingface_course` nous avons utilisé `hub_model_id= "huggingface_course/bert-finetuned-squad"` (qui est le modèle que nous avons lié au début de cette section). + +{#if fw === 'pt'} + + + +💡 Si le répertoire de sortie que vous utilisez existe, il doit être un clone local du dépôt vers lequel vous voulez pousser (donc définissez un nouveau nom si vous obtenez une erreur lors de la définition de votre `Trainer`). + + + +Enfin, nous passons tout à la classe `Trainer` et lançons l'entraînement : + +```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) + +# Nous allons faire la validation après, donc pas de validation au milieu de l'entraînement. +model.fit(tf_train_dataset, callbacks=[callback], epochs=num_train_epochs) +``` + +{/if} + +Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le Hub en arrière-plan. Ainsi, vous pourrez reprendre votre entraînement sur une autre machine si nécessaire. L'ensemble de l'entraînement prend un certain temps (un peu plus d'une heure sur une Titan RTX), vous pouvez donc prendre un café ou relire les parties du cours qui vous ont semblé plus difficiles pendant qu'il se déroule. Notez également que dès que la première époque est terminée, vous verrez des poids téléchargés sur le Hub et vous pourrez commencer à jouer avec votre modèle sur sa page. + +{#if fw === 'pt'} + +Une fois l'entraînement terminé, nous pouvons enfin évaluer notre modèle (et prier pour ne pas avoir dépensé tout ce temps de calcul pour rien). La méthode `predict()` du `Trainer` retournera un tuple où les premiers éléments seront les prédictions du modèle (ici une paire avec les logits de début et de fin). Nous envoyons ceci à notre fonction `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} + +Une fois l'entraînement terminé, nous pouvons enfin évaluer notre modèle (et prier pour ne pas avoir dépensé tout ce temps de calcul pour rien). La méthode `predict()` de notre `model` se chargera d'obtenir les prédictions, et puisque nous avons fait tout le travail difficile de définir une fonction `compute_metrics()` plus tôt, nous pouvons obtenir nos résultats en une seule ligne : + +```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} +``` + +Super ! À titre de comparaison, les scores de base indiqués dans l'article du BERT pour ce modèle sont de 80,8 et 88,5, donc nous sommes exactement là où nous devrions être. + +{#if fw === 'pt'} + +Enfin, nous utilisons la méthode `push_to_hub()` pour nous assurer que nous téléchargeons la dernière version du modèle : + +```py +trainer.push_to_hub(commit_message="Training complete") +``` + +Cela renvoie l'URL du commit qu'il vient de faire, si vous voulez l'inspecter : + +```python out +'https://huggingface.co/sgugger/bert-finetuned-squad/commit/9dcee1fbc25946a6ed4bb32efb1bd71d5fa90b68' +``` + +Le `Trainer` rédige également une fiche modèle avec tous les résultats de l'évaluation et la télécharge. + +{/if} + +À ce stade, vous pouvez utiliser le widget d'inférence sur le *Hub* du modèle pour tester le modèle et le partager avec vos amis, votre famille et vos animaux préférés. Vous avez réussi à *finetuner* un modèle sur une tâche de réponse à une question - félicitations ! + + + +✏️ **Votre tour** Essayez un autre modèle d'architecture pour voir s'il est plus performant dans cette tâche ! + + + +{#if fw === 'pt'} + +Si vous voulez plonger un peu plus profondément dans la boucle d'entraînement, nous allons maintenant vous montrer comment faire la même chose en utilisant 🤗 *Accelerate*. + +## Une boucle d'entraînement personnalisée + +Jetons maintenant un coup d'œil à la boucle d'entraînement complète, afin que vous puissiez facilement personnaliser les parties dont vous avez besoin. Elle ressemblera beaucoup à la boucle d'entraînement du [Chapitre 3](/course/fr/chapter3/4), à l'exception de la boucle d'évaluation. Nous serons en mesure d'évaluer le modèle régulièrement puisque nous ne sommes plus contraints par la classe `Trainer`. + +### Préparer tout pour l'entraînement + +Tout d'abord, nous devons construire le `DataLoader`s à partir de nos jeux de données. Nous définissons le format de ces jeux de données à `"torch"`, et supprimons les colonnes dans le jeu de validation qui ne sont pas utilisées par le modèle. Ensuite, nous pouvons utiliser le `default_data_collator` fourni par Transformers comme `collate_fn` et mélanger l'ensemble d'entraînement, mais pas l'ensemble de validation : + +```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 +) +``` + +Ensuite, nous réinstantifions notre modèle, afin de nous assurer que nous ne poursuivons pas les réglages fins précédents mais que nous repartons du modèle pré-entraîné de BERT : + +```py +model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) +``` + +Ensuite, nous aurons besoin d'un optimiseur. Comme d'habitude, nous utilisons le classique `AdamW`, qui est comme Adam, mais avec une correction dans la façon dont la décroissance du poids est appliquée : + +```py +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Une fois que nous avons tous ces objets, nous pouvons les envoyer à la méthode `accelerator.prepare()`. Rappelez-vous que si vous voulez vous entraîner sur des TPUs dans un *notebook* de Colab, vous devrez déplacer tout ce code dans une fonction d'entraînement, et qui ne devrait pas exécuter une cellule qui instancie un `Accelerator`. Nous pouvons forcer l'entraînement en précision mixte en passant `fp16=True` à l'`Accelerator` (ou, si vous exécutez le code comme un script, assurez-vous de remplir la 🤗 *Accelerate* `config` de manière appropriée). + +```py +from accelerate import Accelerator + +accelerator = Accelerator(fp16=True) +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + +Comme vous devez le savoir depuis les sections précédentes, nous ne pouvons utiliser la longueur de `train_dataloader` pour calculer le nombre d'étapes d'entraînement qu'après qu'il soit passé par la méthode `accelerator.prepare()`. Nous utilisons le même programme linéaire que dans les sections précédentes : + +```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, +) +``` + +Pour pousser notre modèle vers le Hub, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous au Hugging Face Hub, si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'ID du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix ; il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `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' +``` + +Ensuite, nous pouvons cloner ce référentiel dans un dossier local. S'il existe déjà, ce dossier local doit être un clone du référentiel avec lequel nous travaillons : + +```py +output_dir = "bert-finetuned-squad-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output_dir` en appelant la méthode `repo.push_to_hub()`. Cela nous aidera à télécharger les modèles intermédiaires à la fin de chaque époque. + +## Boucle d'entraînement + +Nous sommes maintenant prêts à écrire la boucle d'entraînement complète. Après avoir défini une barre de progression pour suivre l'évolution de l'entraînement, la boucle comporte trois parties : + +- l'entraînement proprement dit, qui est l'itération classique sur le `train_dataloader`, passage en avant du modèle, puis passage en arrière et étape d'optimisation. +- l'évaluation, dans laquelle nous rassemblons toutes les valeurs pour `start_logits` et `end_logits` avant de les convertir en tableaux NumPy. Une fois la boucle d'évaluation terminée, nous concaténons tous les résultats. Notez que nous devons tronquer parce que l' `Accelerator` peut avoir ajouté quelques échantillons à la fin pour s'assurer que nous avons le même nombre d'exemples dans chaque processus. +- sauvegarde et téléchargement, où nous sauvegardons d'abord le modèle et le *tokenizer*, puis appelons `repo.push_to_hub()`. Comme nous l'avons fait auparavant, nous utilisons l'argument `blocking=False` pour dire à la bibliothèque 🤗 *Hub* de pousser dans un processus asynchrone. De cette façon, l'entraînement continue normalement et cette (longue) instruction est exécutée en arrière-plan. + +Voici le code complet de la boucle d'entraînement : + +```py +from tqdm.auto import tqdm +import torch + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Entraînement + model.train() + for step, batch in enumerate(train_dataloader): + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) + + # Evaluation + model.eval() + 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) + + # Sauvegarder et télécharger + 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 + ) +``` + +Au cas où ce serait la première fois que vous verriez un modèle enregistré avec 🤗 *Accelerate*, prenons un moment pour inspecter les trois lignes de code qui l'accompagnent : + +```py +accelerator.wait_for_everyone() +unwrapped_model = accelerator.unwrap_model(model) +unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) +``` + +La première ligne est explicite : elle indique à tous les processus d'attendre que tout le monde soit à ce stade avant de continuer. C'est pour s'assurer que nous avons le même modèle dans chaque processus avant de sauvegarder. Ensuite, nous prenons le `unwrapped_model`, qui est le modèle de base que nous avons défini. La méthode `accelerator.prepare()` modifie le modèle pour qu'il fonctionne dans l'entraînement distribué, donc il n'aura plus la méthode `save_pretrained()` ; la méthode `accelerator.unwrap_model()` annule cette étape. Enfin, nous appelons `save_pretrained()` mais nous disons à cette méthode d'utiliser `accelerator.save()` au lieu de `torch.save()`. + +Une fois ceci fait, vous devriez avoir un modèle qui produit des résultats assez similaires à celui entraîné avec le `Trainer`. Vous pouvez vérifier le modèle que nous avons entraîné en utilisant ce code à [*huggingface-course/bert-finetuned-squad-accelerate*](https://huggingface.co/huggingface-course/bert-finetuned-squad-accelerate). Et si vous voulez tester des modifications de la boucle d'entraînement, vous pouvez les implémenter directement en modifiant le code ci-dessus ! + +{/if} + +### Utilisation du modèle *finetuné* + +Nous vous avons déjà montré comment vous pouvez utiliser le modèle que nous avons *finetuné* sur le *Hub* avec le widget d'inférence. Pour l'utiliser localement dans un `pipeline`, il suffit de spécifier l'identifiant du modèle : + +```py +from transformers import pipeline + +# Replace this with your own checkpoint +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'} +``` + +Super ! Notre modèle fonctionne aussi bien que le modèle par défaut pour ce pipeline ! diff --git a/chapters/fr/chapter7/8.mdx b/chapters/fr/chapter7/8.mdx new file mode 100644 index 000000000..795ac72c3 --- /dev/null +++ b/chapters/fr/chapter7/8.mdx @@ -0,0 +1,17 @@ +# *Mastering NLP* + +Si vous êtes arrivé jusqu'ici dans le cours, félicitations ! Vous avez maintenant toutes les connaissances et les outils nécessaires pour aborder (presque) n'importe quelle tâche de NLP avec 🤗 *Transformers* et l'écosystème Hugging Face ! + +Nous avons vu beaucoup de batchs différents de collecteurs de données, donc nous avons fait cette petite vidéo pour vous aider à trouver lequel utiliser pour chaque tâche : + + + +Après avoir terminé ce tour d'horizon des principales tâches NLP, vous devriez : + +* savoir quelles architectures (encodeur, décodeur ou encodeur-décodeur) sont les mieux adaptées à chaque tâche, +* comprendre la différence entre le pré-entraînement et le *finetuning* d'un modèle de langage, +* savoir comment entraîner des *transformers* en utilisant soit l'API `Trainer` et les fonctionnalités d'entraînement distribué d' 🤗 *Accelerate* ou TensorFlow et Keras selon la piste que vous avez suivie, +* comprendre la signification et les limites de métriques comme ROUGE et BLEU pour les tâches de génération de texte, +* savoir comment interagir avec vos modèles *finetunés*, à la fois sur le *Hub* et en utilisant la `pipeline` de 🤗 *Transformers*. + +Malgré toutes ces connaissances, il arrivera un moment où vous rencontrerez un bug difficile dans votre code ou aurez une question sur la façon de résoudre un problème NLP particulier. Heureusement, la communauté Hugging Face est là pour vous aider ! Dans le dernier chapitre de cette partie du cours, nous allons explorer comment vous pouvez déboguer vos *transformers* et demander de l'aide efficacement. \ No newline at end of file diff --git a/chapters/fr/chapter7/9.mdx b/chapters/fr/chapter7/9.mdx new file mode 100644 index 000000000..56cb887aa --- /dev/null +++ b/chapters/fr/chapter7/9.mdx @@ -0,0 +1,324 @@ + + + + +# Quiz de fin de chapitre + +Testons ce que vous avez appris dans ce chapitre ! + +### 1. Laquelle des tâches suivantes peut être considérée comme un problème de classification de *tokens* ? + + + +### 2. Quelle partie du prétraitement pour la classification des *tokens* diffère des autres pipelines de prétraitement ? + +-100 pour étiqueter les tokens spéciaux.", + explain: "Ce n'est pas spécifique à la classification de tokens. Nous utilisons toujours -100 comme étiquette pour les tokens que nous voulons ignorer dans la perte." + }, + { + text: "Nous devons nous assurer que les étiquettes sont tronquées ou rembourrées à la même taille que les entrées, lorsque nous appliquons la troncature/le padding.", + explain: "En effet ! Mais ce n'est pas la seule différence.", + correct: true + } + ]} +/> + +### 3. Quel problème se pose lorsque nous tokenisons les mots dans un problème de classification de *tokens* et que nous voulons étiqueter les *tokens* ? + +tokenizer ajoute des tokens spéciaux et nous n'avons pas d'étiquettes pour eux.", + explain: "Nous étiquetons ces -100 ils sont donc ignorés dans la perte." + }, + { + text: "Chaque mot peut produire plusieurs tokens, ce qui fait que nous nous retrouvons avec plus de tokens que d'étiquettes.", + explain: "C'est le problème principal, et nous devons aligner les étiquettes originales avec les tokens.", + correct: true + }, + { + text: "Les tokens ajoutés n'ont pas d'étiquettes, il n'y a donc pas de problème.", + explain: "C'est incorrect. Nous avons besoin d'autant d'étiquettes que de tokens, sinon nos modèles se tromperont." + } + ]} +/> + +### 4. Que signifie "adaptation au domaine" ? + + + +### 5. Quelles sont les étiquettes dans un problème de modélisation du langage masqué ? + +tokens de la phrase d'entrée sont masqués de manière aléatoire et les étiquettes sont les tokens d'entrée originaux.", + explain: "C'est ça !", + correct: true + }, + { + text: "Certains des tokens de la phrase d'entrée sont masqués aléatoirement et les étiquettes sont les tokens d'entrée originaux, décalés vers la gauche.", + explain: "Non, le déplacement des étiquettes vers la gauche correspond à la prédiction du mot suivant, ce qui est une modélisation causale du langage." + }, + { + text: "Certains des tokens de la phrase d'entrée sont masqués de manière aléatoire, et l'étiquette indique si la phrase est positive ou négative.", + explain: "Il s'agit d'un problème de classification de séquences avec une certaine augmentation des données, et non d'une modélisation du langage masqué." + }, + { + text: "Certains des tokens des deux phrases d'entrée sont masqués de manière aléatoire, et l'étiquette indique si les deux phrases sont similaires ou non.", + explain: "Il s'agit d'un problème de classification de séquences avec une certaine augmentation des données, et non d'une modélisation du langage masqué." + } + ]} +/> + +### 6. Laquelle de ces tâches peut être considérée comme un problème de séquence à séquence ? + + + +### 7. Quelle est la bonne façon de prétraiter les données pour un problème de séquence à séquence ? + +tokenizer avec les éléments suivants inputs=... et targets=....", + explain: "Nous pourrions ajouter cette API à l'avenir, mais ce n'est pas possible pour le moment." + }, + { + text: "Les entrées et les cibles doivent être prétraitées, en deux appels séparés au tokenizer.", + explain: "C'est vrai, mais incomplet. Il y a quelque chose que vous devez faire pour vous assurer que le tokenizer traite les deux correctement." + }, + { + text: "Comme d'habitude, nous devons simplement tokeniser les entrées.", + explain: "Pas dans un problème de classification de séquences. Les cibles sont aussi des textes que nous devons convertir en chiffres !" + }, + { + text: "Les entrées doivent être envoyées au tokenizer, et les cibles aussi, mais sous un gestionnaire de contexte spécial.", + explain: "C'est exact, le tokenizer doit être mis en mode cible par ce gestionnaire de contexte.", + correct: true + } + ]} +/> + +{#if fw === 'pt'} + +### 8. Pourquoi existe-t-il une sous-classe spécifique de `Trainer` pour les problèmes de séquence à séquence ? + +-100", + explain: "Ce n'est pas du tout une perte personnalisée, mais la façon dont la perte est toujours calculée." + }, + { + text: "Parce que les problèmes de séquence à séquence nécessitent une boucle d'évaluation spéciale", + explain: "C'est exact. Les prédictions des modèles de séquence à séquence sont souvent exécutées en utilisant la méthode generate().", + correct: true + }, + { + text: "Parce que les cibles sont des textes dans des problèmes de séquence à séquence", + explain: "Le Trainer ne se soucie pas vraiment de cela puisqu'ils ont été prétraités auparavant." + }, + { + text: "Parce que nous utilisons deux modèles dans les problèmes de séquence à séquence.", + explain: "Nous utilisons en quelque sorte deux modèles, un encodeur et un décodeur, mais ils sont regroupés dans un seul modèle." + } + ]} +/> + +{:else} + +### 9. Pourquoi est-il souvent inutile de spécifier une perte quand on appelle `compile()` sur un *transformer* ? + +tranformers sont entraînés avec un apprentissage non supervisé.", + explain: "Pas tout à fait. Même l'apprentissage non supervisé a besoin d'une fonction de perte !" + }, + { + text: "Parce que la sortie de perte interne du modèle est utilisée par défaut.", + explain: "C'est exact !", + correct: true + }, + { + text: "Parce que nous calculons les mesures après l'entraînement au lieu de le faire.", + explain: "Nous le faisons souvent, mais cela n'explique pas d'où vient la valeur de perte que nous optimisons dans l'entraînement." + }, + { + text: "Parce que la perte est spécifiée dans `model.fit()`.", + explain: "Non, la fonction de perte est toujours fixée une fois que vous exécutez `model.compile()`, et ne peut pas être modifiée dans `model.fit()`." + } + ]} +/> + +{/if} + +### 10. Quand devez-vous pré-entraîner un nouveau modèle ? + +finetuner sur vos données, afin d'éviter d'énormes coûts de calcul." + }, + { + text: "Lorsque vous avez des doutes sur le biais du modèle pré-entraîné que vous utilisez.", + explain: "C'est vrai, mais vous devez vous assurer que les données que vous utiliserez pour l'entraînement sont vraiment meilleures.", + correct: true + }, + { + text: "Lorsque les modèles pré-entraînés disponibles ne sont tout simplement pas assez bons.", + explain: "Vous êtes sûr d'avoir bien débogué votre entraînement ?" + } + ]} +/> + +### 11. Pourquoi est-il facile de prétraîner un modèle de langage sur des batchs de textes ? + +Transformers ne nécessite que quelques lignes de code pour démarrer l'entraînement.", + explain: "Bien que vrai, cela ne répond pas vraiment à la question posée. Essayez une autre réponse !" + } + ]} +/> + +### 12. Quels sont les principaux défis lors du prétraitement des données pour une tâche de réponse à des questions ? + + + +### 13. Comment le post-traitement est-il généralement effectué dans les réponses aux questions ? + +tokens correspondant.", + explain: "Ce pourrait être une façon de faire, mais c'est un peu trop simpliste." + }, + { + text: "Le modèle vous donne les positions de début et de fin de la réponse pour chaque caractéristique créée par un exemple, et il vous suffit de décoder la plage de tokens correspondant dans celui qui a le meilleur score.", + explain: "C'est proche du post-traitement que nous avons étudié, mais ce n'est pas tout à fait exact." + }, + { + text: "Le modèle vous donne les positions de début et de fin de la réponse pour chaque caractéristique créée par un exemple, et vous n'avez plus qu'à les faire correspondre à la portée dans le contexte de celui qui a le meilleur score.", + explain: "C'est ça en résumé !", + correct: true + }, + { + text: "Le modèle génère une réponse, et il vous suffit de la décoder.", + explain: "Non, à moins que vous ne formuliez votre problème de réponse aux questions comme une tâche de séquence à séquence." + } + ]} +/> diff --git a/chapters/fr/chapter8/1.mdx b/chapters/fr/chapter8/1.mdx new file mode 100644 index 000000000..0c32596a0 --- /dev/null +++ b/chapters/fr/chapter8/1.mdx @@ -0,0 +1,12 @@ +# Introduction + +Maintenant que vous savez comment aborder les tâches de NLP les plus courantes avec 🤗 *Transformers*, vous devriez être en mesure de vous lancer dans vos propres projets ! Dans ce chapitre, nous allons explorer ce qu'il faut faire lorsque vous rencontrez un problème. Vous apprendrez comment déboguer avec succès votre code ou votre entraînement et comment demander de l'aide à la communauté si vous ne parvenez pas à résoudre le problème par vous-même. Et si vous pensez avoir trouvé un bug dans l'une des bibliothèques d'Hugging Face, nous vous montrerons la meilleure façon de le signaler afin que le problème soit résolu le plus rapidement possible. + +Plus précisément, dans ce chapitre vous allez apprendre : + +- la première chose à faire lorsque vous obtenez une erreur. +- comment demander de l'aide sur le [forum](https://discuss.huggingface.co/) +- comment déboguer votre pipeline d'entraînement +- comment rédiger une bonne *issue* + +Rien de tout cela n'est spécifiquement lié à 🤗 *Transformers* ou à l'écosystème Hugging Face. Les leçons de ce chapitre sont applicables à la plupart des projets open source ! diff --git a/chapters/fr/chapter8/2.mdx b/chapters/fr/chapter8/2.mdx new file mode 100644 index 000000000..39f70542d --- /dev/null +++ b/chapters/fr/chapter8/2.mdx @@ -0,0 +1,375 @@ +# Que faire lorsque vous obtenez une erreur + + + +Dans cette section, nous allons examiner certaines erreurs courantes qui peuvent se produire lorsque vous essayez de générer des prédictions à partir de votre *transformer* fraîchement *tuné*. Cela vous préparera pour la [section 4](/course/chapter8/fr/section4), où nous explorerons comment déboguer la phase d'entraînement elle-même. + + + +Nous avons préparé un [dépôt de modèles](https://huggingface.co/lewtun/distilbert-base-uncased-finetuned-squad-d5716d28) pour cette section, et si vous voulez exécuter le code de ce chapitre, vous devrez d'abord copier le modèle dans votre compte sur le [*Hub* d'Hugging Face](https://huggingface.co). Pour ce faire, connectez-vous d'abord en exécutant l'une ou l'autre des commandes suivantes dans un *notebook* Jupyter : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +ou ce qui suit dans votre terminal préféré : + +```bash +huggingface-cli login +``` + +Cela vous demandera d'entrer votre nom d'utilisateur et votre mot de passe, et enregistrera un jeton sous *~/.cache/huggingface/*. Une fois que vous vous êtes connecté, vous pouvez copier le dépôt de modèles avec la fonction suivante : + +```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(): + # Cloner le dépôt et extraire le chemin local + template_repo_id = "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28" + commit_hash = "be3eaffc28669d7932492681cd5f3e8905e358b4" + template_repo_dir = snapshot_download(template_repo_id, revision=commit_hash) + # Créer un dépôt vide sur le Hub + model_name = template_repo_id.split("/")[1] + create_repo(model_name, exist_ok=True) + # Cloner le dépôt vide + new_repo_id = get_full_repo_name(model_name) + new_repo_dir = model_name + repo = Repository(local_dir=new_repo_dir, clone_from=new_repo_id) + # Copier les fichiers + copy_tree(template_repo_dir, new_repo_dir) + # Pousser sur le Hub + repo.push_to_hub() +``` + +Maintenant, lorsque vous appelez `copy_repository_template()`, cela va créer une copie du dépôt de modèles sous votre compte. + +## Déboguer le pipeline à partir de 🤗 *Transformers* + +Pour donner le coup d'envoi de notre voyage dans le monde merveilleux du débogage des *transformers*, considérez le scénario suivant : vous travaillez avec un collègue sur un projet de réponse à des questions pour aider les clients d'un site de commerce électronique à trouver des réponses sur des produits de consommation. Votre collègue vous envoie un message du genre : + +> Bonjour ! Je viens de réaliser une expérience en utilisant les techniques du [chapitre 7](/course/fr/chapiter7/7) du cours d'Hugging Face et j'ai obtenu d'excellents résultats sur SQuAD ! Je pense que nous pouvons utiliser ce modèle comme point de départ pour notre projet. L'ID du modèle sur le Hub est "lewtun/distillbert-base-uncased-finetuned-squad-d5716d28". N'hésitez pas à le tester :) + +et la première chose à laquelle on pense est de charger le modèle en utilisant la `pipeline` de 🤗 *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 +""" +``` + +Oh non, quelque chose semble avoir mal tourné ! Si vous êtes novice en programmation, ce genre d'erreurs peut sembler un peu cryptique au début (qu'est-ce qu'un `OSError` ?!). L'erreur affichée ici n'est que la dernière partie d'un rapport d'erreur beaucoup plus large appelé _Python traceback_ (alias *stack trace*). Par exemple, si vous exécutez ce code sur Google Colab, vous devriez voir quelque chose comme la capture d'écran suivante : + +
+A Python traceback. +
+ +Il y a beaucoup d'informations dans ces rapports, nous allons donc en parcourir ensemble les éléments clés. La première chose à noter est que les rapports de traçage doivent être lus _de bas en haut_. Cela peut sembler bizarre si vous avez l'habitude de lire du texte anglais de haut en bas, mais cela reflète le fait que le *traceback* montre la séquence d'appels de fonction que la `pipeline` fait lors du téléchargement du modèle et du *tokenizer*. Consultez le [Chapitre 2](/course/fr/chapter2) pour plus de détails sur la façon dont le `pipeline` fonctionne sous le capot. + + + +🚨 Vous voyez le cadre bleu autour de "6 frames" dans lle *traceback* de Google Colab ? Il s'agit d'une fonctionnalité spéciale de Colab, qui compresse la trace en "frames". Si vous ne parvenez pas à trouver la source d'une erreur, veillez à développer la trace complète en cliquant sur ces deux petites flèches. + + + +Cela signifie que la dernière ligne de la trace indique le dernier message d'erreur et donne le nom de l'exception qui a été levée. Dans ce cas, le type d'exception est `OSError`, ce qui indique une erreur liée au système. Si nous lisons le message d'erreur qui l'accompagne, nous pouvons voir qu'il semble y avoir un problème avec le fichier *config.json* du modèle, et deux suggestions nous sont données pour le résoudre : + +```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 +""" +``` + + + +💡 Si vous rencontrez un message d'erreur difficile à comprendre, copiez et collez le message dans Google ou sur [Stack Overflow](https://stackoverflow.com/) (oui, vraiment !). Il y a de fortes chances que vous ne soyez pas la première personne à rencontrer cette erreur, et c'est un bon moyen de trouver des solutions que d'autres membres de la communauté ont publiées. Par exemple, en recherchant `OSError : Can't load config for` sur Stack Overflow donne plusieurs [réponses](https://stackoverflow.com/search?q=OSError%3A+Can%27t+load+config+for+) qui peuvent être utilisées comme point de départ pour résoudre le problème. + + + +La première suggestion nous demande de vérifier si l'identifiant du modèle est effectivement correct, la première chose à faire est donc de copier l'identifiant et de le coller dans la barre de recherche du Hub : + +
+The wrong model name. +
+ +Hmm, il semble en effet que le modèle de notre collègue ne soit pas sur le Hub... Mais il y a une faute de frappe dans le nom du modèle ! DistilBERT n'a qu'un seul "l" dans son nom, alors corrigeons cela et cherchons "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28" à la place : + +
+The right model name. +
+ +Ok, ça a marché. Maintenant essayons de télécharger à nouveau le modèle avec l'ID correct du modèle : + +```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 +""" +``` + +Argh, encore un échec. Bienvenue dans la vie quotidienne d'un ingénieur en apprentissage machine ! Puisque nous avons corrigé l'ID du modèle, le problème doit se situer dans le référentiel lui-même. Une façon rapide d'accéder au contenu d'un dépôt sur le 🤗 *Hub* est via la fonction `list_repo_files()` de la bibliothèque `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'] +``` + +Intéressant. Il ne semble pas y avoir de fichier *config.json* dans le référentiel ! Pas étonnant que notre `pipeline` n'ait pas pu charger le modèle ; notre collègue a dû oublier de pousser ce fichier vers le *Hub* après l'avoir mis au point. Dans ce cas, le problème semble assez simple à résoudre : nous pouvons lui demander d'ajouter le fichier, ou, puisque nous pouvons voir à partir de l'ID du modèle que le modèle pré-entraîné utilisé est [`distilbert-base-uncased`](https://huggingface.co/distilbert-base-uncased), nous pouvons télécharger la configuration de ce modèle et la pousser dans notre dépôt pour voir si cela résout le problème. Essayons cela. En utilisant les techniques apprises dans le [Chapitre 2](/course/fr/chapter2), nous pouvons télécharger la configuration du modèle avec la classe `AutoConfig` : + +```python +from transformers import AutoConfig + +pretrained_checkpoint = "distilbert-base-uncased" +config = AutoConfig.from_pretrained(pretrained_checkpoint) +``` + + + +🚨 L'approche que nous adoptons ici n'est pas infaillible, puisque notre collègue peut avoir modifié la configuration de `distilbert-base-uncased` avant d'affiner le modèle. Dans la vie réelle, nous voudrions vérifier avec lui d'abord, mais pour les besoins de cette section, nous supposerons qu'il a utilisé la configuration par défaut. + + + +Nous pouvons ensuite le pousser vers notre dépôt de modèles avec la fonction `push_to_hub()` de la configuration : + +```python +config.push_to_hub(model_checkpoint, commit_message="Add config.json") +``` + +Maintenant, nous pouvons tester si cela a fonctionné en chargeant le modèle depuis le dernier commit de la branche `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! +""" + +context_fr = r""" +La réponse à des questions consiste à extraire une réponse d'un texte +à partir d'une question. Un exemple de jeu de données de réponse aux questions est le jeu de données SQuAD +qui est entièrement basé sur cette tâche. Si vous souhaitez affiner un modèle +modèle sur une tâche SQuAD, vous pouvez utiliser le fichier +exemples/pytorch/question-answering/run_squad.py. + +🤗 Transformers est interopérable avec les frameworks PyTorch, TensorFlow et JAX. +de sorte que vous pouvez utiliser vos outils préférés pour une grande variété de tâches ! +""" + +question = "What is extractive question answering?" # Qu'est-ce que la réponse extractive aux questions ? +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'} # la tâche consistant à extraire une réponse d'un texte à partir d'une question. +``` + +Woohoo, ça a marché ! Récapitulons ce que vous venez d'apprendre : + +- les messages d'erreur en Python sont appelés _tracebacks_ et sont lus de bas en haut. La dernière ligne du message d'erreur contient généralement les informations dont vous avez besoin pour localiser la source du problème, +- si la dernière ligne ne contient pas suffisamment d'informations, remontez dans la traceback et voyez si vous pouvez identifier où l'erreur se produit dans le code source, +- si aucun des messages d'erreur ne peut vous aider à déboguer le problème, essayez de rechercher en ligne une solution à un problème similaire. +- l'`huggingface_hub`, +// 🤗 *Hub* ? +fournit une suite d'outils que vous pouvez utiliser pour interagir avec et déboguer les dépôts sur le *Hub*. + +Maintenant que vous savez comment déboguer un pipeline, examinons un exemple plus délicat dans la passe avant du modèle lui-même. + +## Déboguer la passe avant de votre modèle + +Bien que le `pipeline` soit parfait pour la plupart des applications où vous devez générer rapidement des prédictions, vous aurez parfois besoin d'accéder aux logits du modèle (par exemple, si vous avez un post-traitement personnalisé que vous souhaitez appliquer). Pour voir ce qui peut mal tourner dans ce cas, commençons par récupérer le modèle et le *tokenizer* de notre `pipeline` : + +```python +tokenizer = reader.tokenizer +model = reader.model +``` + +Ensuite, nous avons besoin d'une question, alors voyons si nos *frameworks* préférés sont supportés : + +```python +question = "Which frameworks can I use?" +``` + +Comme nous l'avons vu dans le [Chapitre 7](/course/fr/chapter7), les étapes habituelles que nous devons suivre sont la tokénisation des entrées, l'extraction des logits des *tokens* de début et de fin, puis le décodage de l'empan de réponse : + +```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 +# Obtenez le début de réponse le plus probable avec l'argmax du score. +answer_start = torch.argmax(answer_start_scores) +# Obtenir la fin de réponse la plus probable avec l'argmax du score +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' +""" +``` + +Oh là là, il semble que nous ayons un bug dans notre code ! Mais nous n'avons pas peur d'un petit débogage. Vous pouvez utiliser le débogueur Python dans un *notebook* : + + + +ou dans un terminal : + + + +Ici, la lecture du message d'erreur nous indique que l'objet `'list' n'a pas d'attribut 'size'`, et nous pouvons voir une flèche `-->` pointant vers la ligne où le problème a été soulevé dans `model(**inputs)`. Vous pouvez déboguer ceci de manière interactive en utilisant le débogueur Python, mais pour l'instant nous allons simplement imprimer une tranche de `inputs` pour voir ce que nous avons : + +```python +inputs["input_ids"][:5] +``` + +```python out +[101, 2029, 7705, 2015, 2064] +``` + +Cela ressemble certainement à une `list` ordinaire de Python, mais vérifions le type : + +```python +type(inputs["input_ids"]) +``` + +```python out +list +``` + +Oui, c'est bien une `list` Python. Alors, qu'est-ce qui a mal tourné ? Rappelez-vous du [Chapitre 2](/course/fr/chapter2) que les classes `AutoModelForXxx` dans 🤗 Transformers opèrent sur des _tenseurs_ (soit dans PyTorch ou TensorFlow), et une opération commune est d'extraire les dimensions d'un tenseur en utilisant `Tensor.size()` dans, disons, PyTorch. Jetons un autre coup d'oeil à la *traceback*, pour voir quelle ligne a déclenché l'exception : + +``` +~/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' +``` + +Il semble que notre code ait essayé d'appeler `input_ids.size()`, mais cela ne fonctionne clairement pas pour une `liste` Python, qui est juste un conteneur. Comment pouvons-nous résoudre ce problème ? La recherche du message d'erreur sur Stack Overflow donne quelques [réponses](https://stackoverflow.com/search?q=AttributeError%3A+%27list%27+object+has+no+attribute+%27size%27&s=c15ec54c-63cb-481d-a749-408920073e8f) pertinentes. En cliquant sur la première, une question similaire à la nôtre s'affiche, avec la réponse indiquée dans la capture d'écran ci-dessous : + +
+An answer from Stack Overflow. +
+ +La réponse recommande d'ajouter `return_tensors='pt'` au *tokenizer*, voyons donc si cela fonctionne pour nous : + +```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 +# Obtenez le début de réponse le plus probable avec l'argmax du score. +answer_start = torch.argmax(answer_start_scores) +# Obtenir la fin de réponse la plus probable avec l'argmax du score +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? # Quels frameworks puis-je utiliser ? +Answer: pytorch, tensorflow, and jax # pytorch, tensorflow et jax +""" +``` + +Super, ça a marché ! Voilà un excellent exemple de l'utilité de Stack Overflow : en identifiant un problème similaire, nous avons pu bénéficier de l'expérience d'autres membres de la communauté. Cependant, une recherche de ce type ne donne pas toujours une réponse pertinente, alors que faire dans ce cas ? Heureusement, il existe une communauté accueillante de développeurs sur le [forum d'Hugging Face](https://discuss.huggingface.co/) qui peut vous aider ! Dans la prochaine section, nous verrons comment rédiger de bonnes questions de forum qui ont des chances d'obtenir une réponse. diff --git a/chapters/fr/chapter8/3.mdx b/chapters/fr/chapter8/3.mdx new file mode 100644 index 000000000..526fe482b --- /dev/null +++ b/chapters/fr/chapter8/3.mdx @@ -0,0 +1,205 @@ +# Demander de l'aide sur les forums + + + + + +Le [forum d'Hugging Face](https://discuss.huggingface.co) est un endroit idéal pour obtenir de l'aide de l'équipe open source et de la communauté Hugging Face au sens large. Voici à quoi ressemble la page principale tous les jours : + +
+The Hugging Face forums. +
+ +ODans la partie gauche, vous pouvez voir toutes les catégories dans lesquelles les différents sujets sont regroupés, tandis que la partie droite montre les sujets les plus récents. Un sujet est un message qui contient un titre, une catégorie et une description ; il est assez similaire au format des *issues* GitHub que nous avons vu lors de la création de notre propre jeu de données dans [Chapitre 5](/course/fr/chapter5). Comme son nom l'indique, la catégorie [*Beginners*](https://discuss.huggingface.co/c/beginners/5) est principalement destinée aux personnes qui débutent avec les bibliothèques et l'écosystème Hugging Face. Toute question sur l'une des bibliothèques est la bienvenue ici, que ce soit pour déboguer du code ou pour demander de l'aide sur la façon de faire quelque chose. (Cela dit, si votre question concerne une bibliothèque en particulier, vous devriez probablement vous diriger vers la catégorie de bibliothèque correspondante sur le forum). + +De même, les catégories [*Intermediate*](https://discuss.huggingface.co/c/intermediate/6) et [*Research*](https://discuss.huggingface.co/c/research/7) sont destinées aux questions plus avancées, par exemple sur les bibliothèques ou sur une avancée en recherche en NLP dont vous aimeriez discuter. + +Et naturellement, nous devrions aussi mentionner la catégorie [*Course*](https://discuss.huggingface.co/c/course/20), où vous pouvez poser toutes les questions que vous avez en rapport avec le cours d'Hugging Face ! + +Une fois que vous aurez choisi une catégorie, vous serez prêt à rédiger votre premier sujet. Vous pouvez trouver quelques [directives](https://discuss.huggingface.co/t/how-to-request-support/3128) dans le forum sur la façon de le faire, et dans cette section, nous allons jeter un coup d'oeil à certaines caractéristiques d'un bon sujet. + +## Rédiger un bon message sur le forum + +A titre d'exemple, supposons que nous essayons de générer des embeddings à partir d'articles Wikipédia pour créer un moteur de recherche personnalisé. Comme d'habitude, nous chargeons le *tokenizer* et le modèle comme suit : + +```python +from transformers import AutoTokenizer, AutoModel + +model_checkpoint = "distilbert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +model = AutoModel.from_pretrained(model_checkpoint) +``` + +Supposons maintenant que nous essayons d'intégrer une section entière de l'[article Wikipedia](https://en.wikipedia.org/wiki/Transformers) sur Transformers (la franchise de films, pas la bibliothèque !): + +```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. +""" + +text_fr = """ +Génération 1 est un terme rétroactif pour les personnages de Transformers qui sont apparus +sont apparus entre 1984 et 1993. Les Transformers ont commencé avec les lignes de jouets japonaises des années 1980 +japonaises des années 1980, Micro Change et Diaclone. Elles présentaient des robots capables de se transformer +en véhicules de tous les jours, en objets électroniques ou en armes. Hasbro a acheté les jouets Micro +Change et Diaclone, et s'est associé à Takara. Marvel Comics est engagé par +Hasbro pour créer l'histoire de fond ; le rédacteur en chef Jim Shooter a écrit une histoire générale +et confie la tâche de créer les personnages au scénariste Dennis O'Neil. +Mécontent du travail d'O'Neil (bien que ce dernier ait créé le nom "Optimus Prime"), +Shooter choisit Bob Budiansky pour créer les personnages. + +Les mecha de Transformers ont été en grande partie conçus par Shōji Kawamori, le créateur de +de l'anime japonais Macross (qui a été adapté en Robotech en Amérique du Nord). +en Amérique du Nord). Kawamori a eu l'idée de transformer des +mechas transformables alors qu'il travaillait sur les franchises Diaclone et Macross au début des années 1980. +(comme le VF-1 Valkyrie dans Macross et Robotech), et ses méchas Diaclone +ont plus tard servi de base à Transformers. + +Le concept principal de la Génération 1 est que l'héroïque Optimus Prime, le méchant +le méchant Megatron, et leurs meilleurs soldats s'écrasent sur une Terre préhistorique +dans l'Arche et le Némésis avant de se réveiller en 1985, Cybertron traversant à toute allure la zone neutre en raison de l'effet de la zone neutre. +la Zone Neutre, conséquence de la guerre. La bande dessinée Marvel faisait à l'origine partie +de l'univers principal de Marvel, avec des apparitions de Spider-Man et Nick Fury, +plus quelques caméos, ainsi qu'une visite à la Terre Sauvage. + +La série télévisée Transformers a commencé à peu près à la même époque. Produite par Sunbow +Productions et Marvel Productions, puis Hasbro Productions, dès le début elle a +contredit les histoires de Budiansky. La série TV montre les Autobots cherchant +de nouvelles sources d'énergie et s'écrasent lors de l'attaque des Decepticons. Marvel +a interprété les Autobots comme la destruction d'un astéroïde malveillant s'approchant de Cybertron. +Shockwave est loyal envers Megatron dans la série TV, et maintient Cybertron dans une impasse en son absence. +Cybertron dans une impasse pendant son absence, mais dans la BD, il tente de prendre le commandement +des Decepticons. La série télévisée s'écarte aussi radicalement des origines que +Budiansky avait créé pour les Dinobots, le Decepticon devenu Autobot Jetfire +(connu sous le nom de Skyfire à la télévision), les Constructicons (qui s'associent pour former +Devastator),[19][20] et Oméga Suprême. La bande dessinée Marvel établit très tôt +que Prime manie la matrice de création, qui donne la vie aux machines. Dans la +saison, l'épisode en deux parties The Key to Vector Sigma a introduit l'ancien ordinateur +l'ancien ordinateur Vector Sigma, qui servait le même objectif original que la +matrice de création (donner la vie aux Transformers), et son gardien Alpha Trion. +""" + +inputs = tokenizer(text, return_tensors="pt") +logits = model(**inputs).logits +``` + +```python output +IndexError: index out of range in self +``` + +Oh-oh, nous avons rencontré un problème. Le message d'erreur est bien plus énigmatique que ceux que nous avons vus dans la [section 2](/course/chapter8/fr/section2) ! Nous n'arrivons pas à comprendre l'historique complet, alors nous décidons de nous tourner vers le forum d'Hugging Face pour obtenir de l'aide. Comment pouvons-nous élaborer le sujet ? + +Pour commencer, nous devons cliquer sur le bouton *New Topic* dans le coin supérieur droit (notez que pour créer un sujet, nous devons être connectés) : + +
+Creating a new forum topic. +
+ +Cela fait apparaître une interface de rédaction où nous pouvons saisir le titre de notre sujet, sélectionner une catégorie et rédiger le contenu : + +
+The interface for creating a forum topic. +
+ +Puisque l'erreur semble concerner exclusivement 🤗 *Transformers*, nous allons la sélectionner pour la catégorie. Notre première tentative d'explication du problème pourrait ressembler à quelque chose comme ça : + +
+Drafting the content for a new forum topic. +
+ +Bien que ce sujet contienne le message d'erreur pour lequel nous avons besoin d'aide, il y a quelques problèmes avec la façon dont il est écrit : + +1. le titre n'est pas très descriptif, de sorte que toute personne parcourant le forum ne sera pas en mesure de dire de quoi il s'agit sans lire également le corps du sujet, +2. le corps du texte ne fournit pas suffisamment d'informations sur _l'origine de l'erreur et sur _la manière de la reproduire, +3. le sujet s'adresse directement à quelques personnes sur un ton quelque peu exigeant. + +Les sujets comme celui-ci ne sont pas susceptibles d'obtenir une réponse rapide (si tant est qu'ils en obtiennent une), alors voyons comment nous pouvons l'améliorer. Commençons par la première question, celle du choix d'un bon titre. + +### Choisir un titre descriptif + +Si vous essayez d'obtenir de l'aide pour résoudre un bogue dans votre code, une bonne règle de base consiste à inclure suffisamment d'informations dans le titre pour que les autres puissent rapidement déterminer s'ils pensent pouvoir répondre à votre question ou non. Dans notre exemple, nous connaissons le nom de l'exception qui est levée et nous savons qu'elle est déclenchée dans la passe avant du modèle, où nous appelons `model(**inputs)`. Pour communiquer cela, un titre possible pourrait être : + +> Source de l'IndexError dans la passe avant d'AutoModel ? + +Ce titre indique au lecteur _où_ vous pensez que le bogue provient, et s'il a déjà rencontré un `IndexError`, il y a de fortes chances qu'il sache comment le déboguer. Bien sûr, le titre peut être ce que vous voulez, et d'autres variations comme : + +> Pourquoi mon modèle produit-il un IndexError ? + +pourrait également convenir. Maintenant que nous avons un titre descriptif, voyons comment améliorer le corps du texte. + +### Formatage de vos extraits de code + +La lecture du code source est déjà difficile dans un IDE, mais c'est encore plus difficile lorsque le code est copié et collé en texte brut ! Heureusement, les forums Hugging Face supportent l'utilisation de Markdown, donc vous devriez toujours entourer vos blocs de code avec trois *backticks* (```) pour qu'ils soient plus facilement lisibles. Faisons cela pour embellir le message d'erreur et pendant que nous y sommes, rendons le corps un peu plus poli que notre version originale : + +
+Our revised forum topic, with proper code formatting. +
+ +Comme vous pouvez le voir dans la capture d'écran, le fait d'entourer les blocs de code de guillemets convertit le texte brut en code formaté, avec un style de couleur ! Notez également que des backticks simples peuvent être utilisés pour formater des variables en ligne, comme nous l'avons fait pour `distilbert-base-uncased`. Ce sujet a l'air bien meilleur, et avec un peu de chance, nous pourrions trouver quelqu'un dans la communauté qui pourrait deviner à quoi correspond l'erreur. Cependant, au lieu de compter sur la chance, rendons la vie plus facile en incluant la *traceback* dans ses moindres détails ! + +### Inclure la *traceback* complète + +Puisque la dernière ligne de la *traceback* est souvent suffisante pour déboguer votre propre code, il peut être tentant de ne fournir que cela dans votre sujet pour "gagner de la place". Bien que bien intentionné, cela rend en fait le débogage du problème _plus difficile_ pour les autres, car les informations situées plus haut dans la *traceback* peuvent également être très utiles. Une bonne pratique consiste donc à copier et coller la *traceback* _entière_, en veillant à ce qu'elle soit bien formatée. Comme ces tracebacks peuvent être assez longs, certaines personnes préfèrent les montrer après avoir expliqué le code source. C'est ce que nous allons faire. Maintenant, notre sujet de forum ressemble à ce qui suit : + +
+Our example forum topic, with the complete traceback. +
+ +Ceci est beaucoup plus informatif, et un lecteur attentif pourrait être en mesure d'indiquer que le problème semble être dû à la transmission d'une longue entrée en raison de cette ligne dans la *traceback* : + +> La longueur de la séquence des indices de *tokens* est supérieure à la longueur de séquence maximale spécifiée pour ce modèle (583 > 512). + +Cependant, nous pouvons leur faciliter les choses en leur fournissant le code réel qui a déclenché l'erreur. C'est ce que nous allons faire maintenant. + +### Fournir un exemple reproductible + +Si vous avez déjà essayé de déboguer le code de quelqu'un d'autre, vous avez probablement d'abord essayé de recréer le problème qu'il a signalé afin de pouvoir commencer à travailler sur la *traceback* pour identifier l'erreur. Il en va de même lorsqu'il s'agit d'obtenir (ou de donner) de l'aide sur les forums. Il est donc très utile de pouvoir fournir un petit exemple qui reproduit l'erreur. La moitié du temps, le simple fait de faire cet exercice vous aidera à comprendre ce qui ne va pas. Dans tous les cas, la pièce manquante de notre exemple est de montrer les _entrées_ que nous avons fournies au modèle. En faisant cela, nous obtenons quelque chose comme l'exemple complet suivant : + +
+The final version of our forum topic. +
+ +Ce sujet contient maintenant un bon lot d'informations, et il est rédigé d'une manière qui a beaucoup plus de chances d'attirer l'attention de la communauté et d'obtenir une réponse utile. Avec ces directives de base, vous pouvez maintenant créer de superbes sujets pour trouver les réponses à vos questions sur 🤗 *Transformers* ! diff --git a/chapters/fr/chapter8/4.mdx b/chapters/fr/chapter8/4.mdx new file mode 100644 index 000000000..8d1c6ab77 --- /dev/null +++ b/chapters/fr/chapter8/4.mdx @@ -0,0 +1,787 @@ + + +# Débogage du pipeline d'entraînement + + + +Vous avez écrit un magnifique script pour entraîner ou *finetuner* un modèle sur une tâche donnée, en suivant consciencieusement les conseils du [Chapitre 7](/course/fr/chapter7). Mais lorsque vous lancez la commande `model.fit()`, quelque chose d'horrible se produit : vous obtenez une erreur 😱 ! Ou pire, tout semble aller bien et l'entraînement se déroule sans erreur, mais le modèle résultant est merdique. Dans cette section, nous allons vous montrer ce que vous pouvez faire pour déboguer ce genre de problèmes. + +## Déboguer le pipeline d'entraînement + + + +Le problème lorsque vous rencontrez une erreur dans `trainer.train()` est qu'elle peut provenir de plusieurs sources, car le `Trainer` assemble généralement des batchs de choses. Il convertit les jeux de données en chargeurs de données, donc le problème pourrait être quelque chose d'erroné dans votre jeu de données, ou un problème en essayant de regrouper les éléments des jeux de données ensemble. Ensuite, il prend un batch de données et le transmet au modèle, le problème peut donc se situer dans le code du modèle. Après cela, il calcule les gradients et effectue l'étape d'optimisation, le problème peut donc également se situer dans votre optimiseur. Et même si tout se passe bien pendant l'entraînement, quelque chose peut encore mal tourner pendant l'évaluation si votre métrique pose problème. + +La meilleure façon de déboguer une erreur qui survient dans `trainer.train()` est de passer manuellement en revue tout le pipeline pour voir où les choses se sont mal passées. L'erreur est alors souvent très facile à résoudre. + +Pour le démontrer, nous utiliserons le script suivant qui tente d'ajuster un modèle DistilBERT sur le [jeu de données MNLI](https://huggingface.co/datasets/glue) : + +```py +from datasets import load_dataset, load_metric +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 = load_metric("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() +``` + +Si vous essayez de l'exécuter, vous serez confronté à une erreur plutôt cryptique : + +```python out +'ValueError: You have to specify either input_ids or inputs_embeds' +``` + +### Vérifiez vos données + +Cela va sans dire, mais si vos données sont corrompues, le `Trainer` ne sera pas capable de former des batchs, et encore moins d'entraîner votre modèle. Donc, tout d'abord, vous devez jeter un coup d'oeil à ce qui se trouve dans votre ensemble d'entraînement. + +Pour éviter d'innombrables heures passées à essayer de corriger quelque chose qui n'est pas la source du bug, nous vous recommandons d'utiliser `trainer.train_dataset` pour vos vérifications et rien d'autre. Faisons donc cela ici : + +```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.'} +``` + +Vous remarquez quelque chose d'anormal ? Ceci, en conjonction avec le message d'erreur sur les `input_ids` manquants, devrait vous faire réaliser que ce sont des textes, et non des nombres que le modèle peut comprendre. Ici, l'erreur originale est très trompeuse parce que le `Trainer` enlève automatiquement les colonnes qui ne correspondent pas à la signature du modèle (c'est-à-dire, les arguments attendus par le modèle). Cela signifie qu'ici, tout, sauf les étiquettes, a été éliminé. Il n'y avait donc aucun problème à créer des batchs et à les envoyer ensuite au modèle, qui s'est plaint à son tour de ne pas avoir reçu les bons arguments. + +Pourquoi les données n'ont-elles pas été traitées ? Nous avons utilisé la méthode `Dataset.map()` sur les ensembles de données pour appliquer le *tokenizer* sur chaque échantillon. Mais si vous regardez attentivement le code, vous verrez que nous avons fait une erreur en passant les ensembles d'entraînement et d'évaluation au `Trainer`. Au lieu d'utiliser `tokenized_datasets` ici, nous avons utilisé `raw_datasets` 🤦. Alors corrigeons ça ! + +```py +from datasets import load_dataset, load_metric +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 = load_metric("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() +``` + +Ce nouveau code donnera maintenant une erreur différente (progrès !) : + +```python out +'ValueError: expected sequence of length 43 at dim 1 (got 37)' +``` + +En regardant la trace, nous pouvons voir que l'erreur se produit dans l'étape de collationnement des données : + +```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 +``` + +Donc, nous devrions passer à cela. Mais avant cela, finissons d'inspecter nos données, pour être sûrs à 100% qu'elles sont correctes. + +Une chose que vous devriez toujours faire lorsque vous déboguez une session d'entraînement est de jeter un coup d'oeil aux entrées décodées de votre modèle. Nous ne pouvons pas donner un sens aux chiffres que nous lui fournissons directement, nous devons donc examiner ce que ces chiffres représentent. Dans le domaine de la vision par ordinateur, par exemple, cela signifie regarder les images décodées des pixels que vous passez, dans le domaine de la parole, cela signifie écouter les échantillons audio décodés, et pour notre exemple NLP, cela signifie utiliser notre *tokenizer* pour décoder les entrées : + +```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]' +``` + +Cela semble donc correct. Vous devriez faire cela pour toutes les clés dans les entrées : + +```py +trainer.train_dataset[0].keys() +``` + +```python out +dict_keys(['attention_mask', 'hypothesis', 'idx', 'input_ids', 'label', 'premise']) +``` + +Note that the keys that don't correspond to inputs accepted by the model will be automatically discarded, so here we will only keep `input_ids`, `attention_mask`, and `label` (which will be renamed `labels`). To double-check the model signature, you can print the class of your model, then go check its documentation: + +```py +type(trainer.model) +``` + +```python out +transformers.models.distilbert.modeling_distilbert.DistilBertForSequenceClassification +``` + +Donc dans notre cas, nous pouvons vérifier les paramètres acceptés sur [cette page](https://huggingface.co/transformers/model_doc/distilbert.html#distilbertforsequenceclassification). Le `Trainer` va également enregistrer les colonnes qu'il rejette. + +Nous avons vérifié que les IDs d'entrée sont corrects en les décodant. Ensuite, il y a le `attention_mask` : + +```py +tokenizer.decode(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] +``` + +Comme nous n'avons pas appliqué de *padding* dans notre prétraitement, cela semble parfaitement naturel. Pour être sûr qu'il n'y a pas de problème avec ce masque d'attention, vérifions qu'il est de la même longueur que nos identifiants d'entrée : + +```py +len(trainer.train_dataset[0]["attention_mask"]) == len( + trainer.train_dataset[0]["input_ids"] +) +``` + +```python out +True +``` + +C'est bien ! Enfin, vérifions notre étiquette : + +```py +trainer.train_dataset[0]["label"] +``` + +```python out +1 +``` + +Comme les ID d'entrée, c'est un nombre qui n'a pas vraiment de sens en soi. Comme nous l'avons vu précédemment, la correspondance entre les entiers et les noms d'étiquettes est stockée dans l'attribut `names` de la *caractéristique* correspondante de l'ensemble de données : + +```py +trainer.train_dataset.features["label"].names +``` + +```python out +['entailment', 'neutral', 'contradiction'] +``` + +Donc `1` signifie `neutral`, ce qui signifie que les deux phrases que nous avons vues ci-dessus ne sont pas en contradiction, et que la première n'implique pas la seconde. Cela semble correct ! + +Nous n'avons pas d'ID de type de *token* ici, puisque DistilBERT ne les attend pas ; si vous en avez dans votre modèle, vous devriez également vous assurer qu'ils correspondent correctement à l'endroit où se trouvent la première et la deuxième phrase dans l'entrée. + + + +✏️ *Votre tour !* Vérifiez que tout semble correct avec le deuxième élément du jeu de données d'entraînement. + + + +Nous ne vérifions ici que l'ensemble d'entraînement, mais vous devez bien sûr vérifier de la même façon les ensembles de validation et de test. + +Maintenant que nous savons que nos ensembles de données sont bons, il est temps de vérifier l'étape suivante du pipeline d'entraînement. + +### Des jeux de données aux chargeurs de données + +La prochaine chose qui peut mal tourner dans le pipeline d'entraînement est lorsque le `Trainer` essaie de former des batchs à partir de l'ensemble d'entraînement ou de validation. Une fois que vous êtes sûr que les jeux de données du `Trainer` sont corrects, vous pouvez essayer de former manuellement un batch en exécutant ce qui suit (remplacez `train` par `eval` pour le dataloader de validation) : + +```py +for batch in trainer.get_train_dataloader(): + break +``` + +Ce code crée le dataloader d'entraînement, puis le parcourt en s'arrêtant à la première itération. Si le code s'exécute sans erreur, vous avez le premier batch d'entraînement que vous pouvez inspecter, et si le code se trompe, vous êtes sûr que le problème se situe dans le dataloader, comme c'est le cas ici : + +```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) +``` + +L'inspection de la dernière image du traceback devrait suffire à vous donner un indice, mais creusons un peu plus. La plupart des problèmes lors de la création d'un batch sont dus à l'assemblage des exemples en un seul batch, donc la première chose à vérifier en cas de doute est le `collate_fn` utilisé par votre `DataLoader` : + +```py +data_collator = trainer.get_train_dataloader().collate_fn +data_collator +``` + +```python out + Dict[str, Any]> +``` + +C'est donc le collateur `default_data_collator`, mais ce n'est pas ce que nous voulons dans ce cas. Nous voulons rembourrer nos exemples à la phrase la plus longue du batch, ce qui est fait par le collateur `DataCollatorWithPadding`. Et ce collateur de données est censé être utilisé par défaut par le `Trainer`, alors pourquoi n'est-il pas utilisé ici ? + +La réponse est que nous n'avons pas passé le `tokenizer` au `Trainer`, donc il ne pouvait pas créer le `DataCollatorWithPadding` que nous voulons. En pratique, il ne faut jamais hésiter à transmettre explicitement le collateur de données que l'on veut utiliser, pour être sûr d'éviter ce genre d'erreurs. Adaptons notre code pour faire exactement cela : + +```py +from datasets import load_dataset, load_metric +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 = load_metric("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() +``` + +La bonne nouvelle ? Nous n'avons plus la même erreur qu'avant, ce qui est un progrès certain. La mauvaise nouvelle ? Nous obtenons une erreur CUDA infâme à la place : + +```python out +RuntimeError: CUDA error: CUBLAS_STATUS_ALLOC_FAILED when calling `cublasCreate(handle)` +``` + +C'est une mauvaise chose car les erreurs CUDA sont extrêmement difficiles à déboguer en général. Nous verrons dans une minute comment résoudre ce problème, mais terminons d'abord notre analyse de la création de batchs. + +Si vous êtes sûr que votre collecteur de données est le bon, vous devriez essayer de l'appliquer sur quelques échantillons de votre ensemble de données : + +```py +data_collator = trainer.get_train_dataloader().collate_fn +batch = data_collator([trainer.train_dataset[i] for i in range(4)]) +``` + +Ce code échouera parce que le `train_dataset` contient des colonnes de type string, que le `Trainer` supprime habituellement. Vous pouvez les supprimer manuellement, ou si vous voulez reproduire exactement ce que le `Trainer` fait en coulisse, vous pouvez appeler la méthode privée `Trainer._remove_unused_columns()` qui fait cela : + +```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)]) +``` + +Vous devriez alors être en mesure de déboguer manuellement ce qui se passe dans le collecteur de données si l'erreur persiste. + +Maintenant que nous avons débogué le processus de création de batch, il est temps d'en passer un dans le modèle ! + +### Passage par le modèle + +Vous devriez être en mesure d'obtenir un batch en exécutant la commande suivante : + +```py +for batch in trainer.get_train_dataloader(): + break +``` + +Si vous exécutez ce code dans un *notebook*, vous risquez d'obtenir une erreur CUDA similaire à celle que nous avons vue précédemment, auquel cas vous devrez redémarrer votre notebook et réexécuter le dernier extrait sans la ligne `trainer.train()`. C'est la deuxième chose la plus ennuyeuse à propos des erreurs CUDA : elles cassent irrémédiablement votre noyau. La chose la plus ennuyeuse à leur sujet est le fait qu'elles sont difficiles à déboguer. + +Comment cela se fait-il ? Cela tient à la façon dont les GPU fonctionnent. Ils sont extrêmement efficaces pour exécuter un batch d'opérations en parallèle, mais l'inconvénient est que lorsque l'une de ces instructions entraîne une erreur, vous ne le savez pas immédiatement. Ce n'est que lorsque le programme appelle une synchronisation des multiples processus sur le GPU qu'il réalise que quelque chose s'est mal passé, de sorte que l'erreur est en fait soulevée à un endroit qui n'a rien à voir avec ce qui l'a créée. Par exemple, si nous regardons notre traceback précédent, l'erreur a été soulevée pendant la passe arrière, mais nous verrons dans une minute qu'elle provient en fait de quelque chose dans la passe avant. + +Alors comment déboguer ces erreurs ? La réponse est simple : nous ne le faisons pas. À moins que votre erreur CUDA ne soit une erreur out-of-memory (ce qui signifie qu'il n'y a pas assez de mémoire dans votre GPU), vous devez toujours revenir au CPU pour la déboguer. + +Pour faire cela dans notre cas, nous devons juste remettre le modèle sur le CPU et l'appeler sur notre batch. Le batch retourné par le `DataLoader` n'a pas encore été déplacé sur le 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. +``` + +Donc, l'image devient plus claire. Au lieu d'avoir une erreur CUDA, nous avons maintenant une `IndexError` dans le calcul de la perte (donc rien à voir avec le backward pass, comme nous l'avons dit plus tôt). Plus précisément, nous pouvons voir que c'est la cible 2 qui crée l'erreur, donc c'est un très bon moment pour vérifier le nombre de labels de notre modèle : + +```python +trainer.model.config.num_labels +``` + +```python out +2 +``` + +Avec deux étiquettes, seuls les 0 et les 1 sont autorisés comme cibles, mais d'après le message d'erreur, nous avons obtenu un 2. Obtenir un 2 est en fait normal : si nous nous souvenons des noms d'étiquettes que nous avons extraits plus tôt, il y en avait trois, donc nous avons des indices 0, 1 et 2 dans notre ensemble de données. Le problème est que nous n'avons pas indiqué cela à notre modèle, qui aurait dû être créé avec trois étiquettes. Alors, corrigeons cela ! + +```py +from datasets import load_dataset, load_metric +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 = load_metric("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, +) +``` + +Nous n'incluons pas encore la ligne `trainer.train()`, pour prendre le temps de vérifier que tout se passe bien. Si nous demandons un batch et le passons à notre modèle, il fonctionne maintenant sans erreur ! + +```py +for batch in trainer.get_train_dataloader(): + break + +outputs = trainer.model.cpu()(**batch) +``` + +L'étape suivante consiste alors à revenir au GPU et à vérifier que tout fonctionne encore : + +```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) +``` + +Si vous obtenez toujours une erreur, assurez-vous de redémarrer votre *notebook* et d'exécuter uniquement la dernière version du script. + +### Exécution d'une étape d'optimisation + +Maintenant que nous savons que nous pouvons construire des batchs qui passent réellement par le modèle, nous sommes prêts pour l'étape suivante du pipeline d'entraînement : calculer les gradients et effectuer une étape d'optimisation. + +La première partie est juste une question d'appel de la méthode `backward()` sur la perte : + +```py +loss = outputs.loss +loss.backward() +``` + +Il est plutôt rare d'obtenir une erreur à ce stade, mais si vous en obtenez une, assurez-vous de retourner au CPU pour obtenir un message d'erreur utile. + +Pour effectuer l'étape d'optimisation, il suffit de créer le `optimizer` et d'appeler sa méthode `step()` : + +```py +trainer.create_optimizer() +trainer.optimizer.step() +``` + +Encore une fois, si vous utilisez l'optimiseur par défaut dans le `Trainer`, vous ne devriez pas avoir d'erreur à ce stade, mais si vous avez un optimiseur personnalisé, il pourrait y avoir quelques problèmes à déboguer ici. N'oubliez pas de revenir au CPU si vous obtenez une erreur CUDA bizarre à ce stade. En parlant d'erreurs CUDA, nous avons mentionné précédemment un cas particulier. Voyons cela maintenant. + +### Gérer les erreurs CUDA hors-mémoire + +Chaque fois que vous obtenez un message d'erreur qui commence par `RuntimeError : CUDA out of memory`, cela indique que vous êtes à court de mémoire GPU. Cela n'est pas directement lié à votre code, et cela peut arriver avec un script qui fonctionne parfaitement bien. Cette erreur signifie que vous avez essayé de mettre trop de choses dans la mémoire interne de votre GPU, et que cela a entraîné une erreur. Comme pour d'autres erreurs CUDA, vous devrez redémarrer votre noyau pour être en mesure d'exécuter à nouveau votre entraînement. + +Pour résoudre ce problème, il suffit d'utiliser moins d'espace GPU, ce qui est souvent plus facile à dire qu'à faire. Tout d'abord, assurez-vous que vous n'avez pas deux modèles sur le GPU en même temps (sauf si cela est nécessaire pour votre problème, bien sûr). Ensuite, vous devriez probablement réduire la taille de votre batch, car elle affecte directement les tailles de toutes les sorties intermédiaires du modèle et leurs gradients. Si le problème persiste, envisagez d'utiliser une version plus petite de votre modèle. + + + +In the next part of the course, we'll look at more advanced techniques that can help you reduce your memory footprint and let you fine-tune the biggest models. + + + +### Évaluation du modèle + +Maintenant que nous avons résolu tous les problèmes liés à notre code, tout est parfait et l'entraînement devrait se dérouler sans problème, n'est-ce pas ? Pas si vite ! Si vous exécutez la commande `trainer.train()`, tout aura l'air bien au début, mais après un moment vous obtiendrez ce qui suit : + +```py +# This will take a long time and error out, so you shouldn't run this cell +trainer.train() +``` + +```python out +TypeError: only size-1 arrays can be converted to Python scalars +``` + +Vous réaliserez que cette erreur apparaît pendant la phase d'évaluation, donc c'est la dernière chose que nous aurons besoin de déboguer. + +Vous pouvez exécuter la boucle d'évaluation du `Trainer` indépendamment de l'entraînement comme ceci : + +```py +trainer.evaluate() +``` + +```python out +TypeError: only size-1 arrays can be converted to Python scalars +``` + + + +💡 Vous devriez toujours vous assurer que vous pouvez exécuter `trainer.evaluate()` avant de lancer `trainer.train()`, pour éviter de gaspiller beaucoup de ressources de calcul avant de tomber sur une erreur. + + + +Avant de tenter de déboguer un problème dans la boucle d'évaluation, vous devez d'abord vous assurer que vous avez examiné les données, que vous êtes en mesure de former un batch correctement et que vous pouvez exécuter votre modèle sur ces données. Nous avons effectué toutes ces étapes, et le code suivant peut donc être exécuté sans erreur : + +```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) +``` + +L'erreur survient plus tard, à la fin de la phase d'évaluation, et si nous regardons la *traceback*, nous voyons ceci : + +```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() +``` + +Cela nous indique que l'erreur provient du module `datasets/metric.py` donc c'est un problème avec notre fonction `compute_metrics()`. Elle prend un *tuple* avec les logits et les labels sous forme de tableaux NumPy, alors essayons de lui fournir cela : + +```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 +``` + +Nous obtenons la même erreur, donc le problème vient bien de cette fonction. Si on regarde son code, on voit qu'elle transmet simplement les `predictions` et les `labels` à `metric.compute()`. Y a-t-il donc un problème avec cette méthode ? Pas vraiment. Jetons un coup d'oeil rapide aux formes : + +```py +predictions.shape, labels.shape +``` + +```python out +((8, 3), (8,)) +``` + +Nos prédictions sont toujours des logits, et non les prédictions réelles, c'est pourquoi la métrique retourne cette erreur (quelque peu obscure). La correction est assez simple, il suffit d'ajouter un argmax dans la fonction `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} +``` + +Maintenant notre erreur est corrigée ! C'était la dernière, donc notre script va maintenant entraîner un modèle correctement. + +Pour référence, voici le script complètement corrigé : + +```py +import numpy as np +from datasets import load_dataset, load_metric +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 = load_metric("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() +``` + +Dans ce cas, il n'y a plus de problème, et notre script va affiner un modèle qui devrait donner des résultats raisonnables. Mais que faire lorsque l'entraînement se déroule sans erreur, et que le modèle entraîné n'est pas du tout performant ? C'est la partie la plus difficile de l'apprentissage automatique, et nous allons vous montrer quelques techniques qui peuvent vous aider. + + + +💡 Si vous utilisez une boucle d'entraînement manuelle, les mêmes étapes s'appliquent pour déboguer votre pipeline d'entraînement, mais il est plus facile de les séparer. Assurez-vous cependant de ne pas avoir oublié le `model.eval()` ou le `model.train()` aux bons endroits, ou le `zero_grad()` à chaque étape ! + + + +## Déboguer les erreurs silencieuses pendant l'entraînement + +Que peut-on faire pour déboguer un entraînement qui se termine sans erreur mais qui ne donne pas de bons résultats ? Nous allons vous donner quelques pistes ici, mais sachez que ce type de débogage est la partie la plus difficile de l'apprentissage automatique, et qu'il n'y a pas de réponse magique. + +### Vérifiez vos données (encore !) + +Votre modèle n'apprendra quelque chose que s'il est réellement possible d'apprendre quelque chose de vos données. Si un bogue corrompt les données ou si les étiquettes sont attribuées de manière aléatoire, il est très probable que vous n'obtiendrez aucun entraînement de modèle sur votre ensemble de données. Commencez donc toujours par revérifier vos entrées et étiquettes décodées, et posez-vous les questions suivantes : + +- les données décodées sont-elles compréhensibles ? +- êtes-vous d'accord avec les étiquettes ? +- y a-t-il une étiquette qui est plus courante que les autres ? +- quelle devrait être la perte/métrie si le modèle prédisait une réponse aléatoire/toujours la même réponse ? + + + +⚠️ Si vous effectuez un entraînement distribué, imprimez des échantillons de votre ensemble de données dans chaque processus et vérifiez par trois fois que vous obtenez la même chose. Un bug courant consiste à avoir une source d'aléa dans la création des données qui fait que chaque processus a une version différente de l'ensemble de données. + + + +Après avoir examiné vos données, examinez quelques-unes des prédictions du modèle et décodez-les également. Si le modèle prédit toujours la même chose, c'est peut-être parce que votre ensemble de données est biaisé en faveur d'une catégorie (pour les problèmes de classification) ; des techniques comme le suréchantillonnage de classes rares peuvent aider. + +Si la perte/la métrique que vous obtenez sur votre modèle initial est très différente de la perte/la métrique à laquelle vous vous attendez pour des prédictions aléatoires, vérifiez à nouveau la façon dont votre perte ou votre métrique est calculée, car il y a probablement un bug à ce niveau. Si vous utilisez plusieurs pertes que vous ajoutez à la fin, assurez-vous qu'elles sont de la même échelle. + +Lorsque vous êtes sûr que vos données sont parfaites, vous pouvez voir si le modèle est capable de s'entraîner sur elles grâce à un test simple. + +### Surentraînement du modèle sur un seul batch + +Le surentraînement est généralement une chose que nous essayons d'éviter lors de l'entraînement, car cela signifie que le modèle n'apprend pas à reconnaître les caractéristiques générales que nous voulons qu'il reconnaisse, mais qu'il se contente de mémoriser les échantillons d'entraînement. Cependant, essayer d'entraîner votre modèle sur un batch encore et encore est un bon test pour vérifier si le problème tel que vous l'avez formulé peut être résolu par le modèle que vous essayez d'entraîner. Cela vous aidera également à voir si votre taux d'apprentissage initial est trop élevé. + +Une fois que vous avez défini votre `Trainer`, c'est très facile ; il suffit de prendre un batch de données d'entraînement, puis d'exécuter une petite boucle d'entraînement manuel en utilisant uniquement ce batch pour quelque chose comme 20 étapes : + +```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() +``` + + + +💡 Si vos données d'entraînement ne sont pas équilibrées, veillez à créer un batch de données d'entraînement contenant toutes les étiquettes. + + + +Le modèle résultant devrait avoir des résultats proches de la perfection sur le même `batch`. Calculons la métrique sur les prédictions résultantes : + +```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% de précision, voilà un bel exemple de surentraînement (ce qui signifie que si vous essayez votre modèle sur n'importe quelle autre phrase, il vous donnera très probablement une mauvaise réponse) ! + +Si vous ne parvenez pas à ce que votre modèle obtienne des résultats parfaits comme celui-ci, cela signifie qu'il y a quelque chose qui ne va pas dans la façon dont vous avez formulé le problème ou dans vos données, et vous devez donc y remédier. Ce n'est que lorsque vous parviendrez à passer le test de surentraînement que vous pourrez être sûr que votre modèle peut réellement apprendre quelque chose. + + + +⚠️ Vous devrez recréer votre modèle et votre `Trainer` après ce test, car le modèle obtenu ne sera probablement pas capable de récupérer et d'apprendre quelque chose d'utile sur votre jeu de données complet. + + + +### Ne réglez rien tant que vous n'avez pas une première ligne de base. + +Le réglage des hyperparamètres est toujours considéré comme la partie la plus difficile de l'apprentissage automatique, mais c'est juste la dernière étape pour vous aider à gagner un peu sur la métrique. La plupart du temps, les hyperparamètres par défaut du `Trainer` fonctionneront très bien pour vous donner de bons résultats, donc ne vous lancez pas dans une recherche d'hyperparamètres longue et coûteuse jusqu'à ce que vous ayez quelque chose qui batte la ligne de base que vous avez sur votre jeu de données. + +Une fois que vous avez un modèle suffisamment bon, vous pouvez commencer à l'affiner un peu. N'essayez pas de lancer un millier d'exécutions avec différents hyperparamètres, mais comparez quelques exécutions avec différentes valeurs pour un hyperparamètre afin de vous faire une idée de celui qui a le plus d'impact. + +Si vous modifiez le modèle lui-même, restez simple et n'essayez rien que vous ne puissiez raisonnablement justifier. Veillez toujours à revenir au test de surentraînement pour vérifier que votre modification n'a pas eu de conséquences inattendues. + +### Demander de l'aide + +Nous espérons que vous avez trouvé dans cette section des conseils qui vous ont aidé à résoudre votre problème, mais si ce n'est pas le cas, n'oubliez pas que vous pouvez toujours demander de l'aide à la communauté sur le [forum](https://discuss.huggingface.co/). + +Voici quelques ressources (en anglais) supplémentaires qui peuvent s'avérer utiles : + +- ["La reproductibilité comme vecteur des meilleures pratiques d'ingénierie"](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p) par Joel Grus +- ["Liste de contrôle pour le débogage des réseaux neuronaux"](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) par Cecelia Shao +- ["Comment tester unitairement le code d'apprentissage automatique"](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) par Chase Roberts +- ["Une recette pour Entraîner les réseaux neuronaux"](http://karpathy.github.io/2019/04/25/recipe/) par Andrej Karpathy + +Bien sûr, tous les problèmes rencontrés lors de l'Entraînement des réseaux neuronaux ne sont pas forcément de votre faute ! Si vous rencontrez quelque chose dans la bibliothèque 🤗 *Transformers* ou 🤗 *Datasets* qui ne semble pas correct, vous avez peut-être rencontré un bogue. Vous devez absolument nous en parler, et dans la section suivante, nous allons vous expliquer exactement comment faire. diff --git a/chapters/fr/chapter8/4_tf.mdx b/chapters/fr/chapter8/4_tf.mdx new file mode 100644 index 000000000..b1a01e75a --- /dev/null +++ b/chapters/fr/chapter8/4_tf.mdx @@ -0,0 +1,487 @@ + + +# Débogage du pipeline d'entraînement + + + +Vous avez écrit un magnifique script pour entraîner ou *finetuner* un modèle sur une tâche donnée, en suivant consciencieusement les conseils du [Chapitre 7](/course/fr/chapter7). Mais lorsque vous lancez la commande `model.fit()`, quelque chose d'horrible se produit : vous obtenez une erreur 😱 ! Ou pire, tout semble aller bien et l'entraînement se déroule sans erreur, mais le modèle résultant est merdique. Dans cette section, nous allons vous montrer ce que vous pouvez faire pour déboguer ce genre de problèmes. + +## Déboguer le pipeline d'entraînement + + + +Le problème lorsque vous rencontrez une erreur dans `trainer.train()` est qu'elle peut provenir de plusieurs sources, car le `Trainer` assemble généralement des batchs de choses. Il convertit les jeux de données en chargeurs de données, donc le problème pourrait être quelque chose d'erroné dans votre jeu de données, ou un problème en essayant de regrouper les éléments des jeux de données ensemble. Ensuite, il prend un batch de données et le transmet au modèle, le problème peut donc se situer dans le code du modèle. Après cela, il calcule les gradients et effectue l'étape d'optimisation, le problème peut donc également se situer dans votre optimiseur. Et même si tout se passe bien pendant l'entraînement, quelque chose peut encore mal tourner pendant l'évaluation si votre métrique pose problème. + +La meilleure façon de déboguer une erreur qui survient dans `trainer.train()` est de passer manuellement en revue tout le pipeline pour voir où les choses se sont mal passées. L'erreur est alors souvent très facile à résoudre. + +Pour le démontrer, nous utiliserons le script suivant qui tente d'ajuster un modèle DistilBERT sur le [jeu de données MNLI](https://huggingface.co/datasets/glue) : + +```py +from datasets import load_dataset, load_metric +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) +``` + +Si vous essayez de l'exécuter, il se peut que vous obteniez des `VisibleDeprecationWarning`s lors de la conversion du jeu de données. Il s'agit d'un problème UX connu que nous avons, donc veuillez l'ignorer. Si vous lisez le cours après, disons, novembre 2021 et que cela se produit encore, envoyez des tweets de rage à @carrigmat jusqu'à ce qu'il le corrige. + +Le problème le plus grave, cependant, c'est que nous avons une erreur flagrante. Et c'est vraiment, terriblement long : + +```python out +ValueError: No gradients provided for any variable: ['tf_distil_bert_for_sequence_classification/distilbert/embeddings/word_embeddings/weight:0', '...'] +``` + +Qu'est-ce que cela signifie ? Nous avons essayé de nous entraîner sur nos données, mais nous n'avons pas obtenu de gradient ? C'est assez déconcertant ; comment commencer à déboguer quelque chose comme ça ? Lorsque l'erreur que vous obtenez ne suggère pas immédiatement l'origine du problème, la meilleure solution consiste souvent à procéder par étapes, en s'assurant à chaque fois que tout semble correct. Et bien sûr, il faut toujours commencer par... + +### Vérifier vos données + +Cela va sans dire, mais si vos données sont corrompues, Keras ne sera pas en mesure de les réparer pour vous. Avant toute chose, vous devez donc jeter un coup d'œil à ce que contient votre ensemble d'entraînement. + +Bien qu'il soit tentant de regarder dans les `raw_datasets` et les `tokenized_datasets`, nous vous recommandons fortement d'aller voir les données au moment où elles vont entrer dans le modèle. Cela signifie lire une sortie du `tf.data.Dataset` que vous avez créé avec la fonction `to_tf_dataset()` ! Alors comment faire ? Les objets `tf.data.Dataset` nous donnent des batchs entiers à la fois et ne supportent pas l'indexation, donc nous ne pouvons pas simplement demander `train_dataset[0]`. Nous pouvons, cependant, lui demander poliment un batch : + +```py +for batch in train_dataset: + break +``` + +`break` ends the loop after one iteration, so this grabs the first batch that comes out of `train_dataset` and saves it as `batch`. Now, let's take a look at what's inside: + +```python out +{'attention_mask': , + 'label': , + 'input_ids': } +``` + +Cela semble correct, n'est-ce pas ? Nous passons les `labels`, `attention_mask`, et `input_ids` au modèle, ce qui devrait être tout ce dont il a besoin pour calculer les sorties et la perte. Alors pourquoi n'avons-nous pas de gradient ? Regardez de plus près : nous passons un seul dictionnaire en entrée, mais un batch d'entraînement est généralement un tenseur ou un dictionnaire d'entrée, plus un tenseur d'étiquettes. Nos étiquettes sont juste une clé dans notre dictionnaire d'entrée. + +Est-ce un problème ? Pas toujours, en fait ! Mais c'est l'un des problèmes les plus courants que vous rencontrerez lorsque vous entraînerez des modèles Transformer avec TensorFlow. Nos modèles peuvent tous calculer la perte en interne, mais pour ce faire, les étiquettes doivent être transmises dans le dictionnaire d'entrée. C'est la perte qui est utilisée lorsque nous ne spécifions pas de valeur de perte à `compile()`. Keras, d'autre part, s'attend généralement à ce que les étiquettes soient passées séparément du dictionnaire d'entrée, et les calculs de perte échoueront généralement si vous ne le faites pas. + +Le problème est maintenant devenu plus clair : nous avons passé un argument `loss`, ce qui signifie que nous demandons à Keras de calculer les pertes pour nous, mais nous avons passé nos étiquettes comme entrées au modèle, et non comme étiquettes à l'endroit où Keras les attend ! Nous devons choisir l'un ou l'autre : soit nous utilisons la perte interne du modèle et gardons les étiquettes où elles sont, soit nous continuons à utiliser les pertes de Keras, mais nous déplaçons les étiquettes à l'endroit où Keras les attend. Pour simplifier, prenons la première approche. Changez l'appel à `compile()` pour lire : + +```py +model.compile(optimizer="adam") +``` + +Maintenant, nous allons utiliser la perte interne du modèle, et ce problème devrait être résolu ! + + + +✏️ *A votre tour !* Comme défi optionnel après avoir résolu les autres problèmes, vous pouvez essayer de revenir à cette étape et faire fonctionner le modèle avec la perte originale calculée par Keras au lieu de la perte interne. Vous devrez ajouter `"labels"` à l'argument `label_cols` de `to_tf_dataset()` pour vous assurer que les labels sont correctement sortis, ce qui vous donnera des gradients -- mais il y a un autre problème avec la perte que nous avons spécifiée. L'Entraînement fonctionnera toujours avec ce problème, mais l'apprentissage sera très lent et se stabilisera à une perte d'entraînement élevée. Pouvez-vous trouver ce que c'est ? + +Un indice codé en ROT13, si vous êtes coincé : Vs lbh ybbx ng gur bhgchgf bs FrdhraprPynffvsvpngvba zbqryf va Genafsbezref, gurve svefg bhgchg vf `ybtvgf`. Jung ner ybtvgf ? + +Et un deuxième indice : Jura lbh fcrpvsl bcgvzvmref, npgvingvbaf be ybffrf jvgu fgevatf, Xrenf frgf ny gur nethzrag inyhrf gb gurve qrsnhygf. Jung nethzragf qbrf FcnefrPngrtbevpnyPebffragebcl unir, naq jung ner gurve qrsnhygf ? + + + +Maintenant, essayons de nous entraîner. Nous devrions obtenir des gradients maintenant, donc avec un peu de chance (la musique de mauvais augure joue ici) nous pouvons juste appeler `model.fit()` et tout fonctionnera bien ! + +```python out + 246/24543 [..............................] - ETA: 15:52 - loss: nan +``` + +Oh non. + +`nan` n'est pas une valeur de perte très encourageante. Pourtant, nous avons vérifié nos données, et elles semblent plutôt bonnes. Si ce n'est pas le problème, quelle est la prochaine étape ? La prochaine étape évidente est de... + +### Vérifier votre modèle + +`model.fit()` est une fonction très pratique dans Keras, mais elle fait beaucoup de choses pour vous, et cela peut rendre plus difficile de trouver exactement où un problème est survenu. Si vous déboguez votre modèle, une stratégie qui peut vraiment vous aider est de passer un seul batch au modèle et d'examiner les sorties de ce batch en détail. Une autre astuce vraiment utile si le modèle jette des erreurs est de `compiler()` le modèle avec `run_eagerly=True`. Cela le rendra beaucoup plus lent, mais les messages d'erreur seront beaucoup plus compréhensibles, car ils indiqueront exactement où le problème est survenu dans le code de votre modèle. + +Pour l'instant, cependant, nous n'avons pas besoin de `run_eagerly`. Exécutons le `batch` que nous avons obtenu précédemment à travers le modèle et voyons à quoi ressemblent les résultats : + +```py +model(batch) +``` + +```python out +TFSequenceClassifierOutput(loss=, logits=, hidden_states=None, attentions=None) +``` + +Eh bien, c'est délicat. Tout est "nan" ! Mais c'est étrange, n'est-ce pas ? Comment tous nos logits pourraient-ils devenir `nan` ? "NAN" signifie "not a number". Les valeurs `nan` apparaissent souvent quand on effectue une opération interdite, comme la division par zéro. Mais une chose très importante à savoir sur `nan` en apprentissage automatique est que cette valeur a tendance à *se propager*. Si vous multipliez un nombre par `nan`, le résultat sera également `nan`. Et si vous obtenez une valeur `nan` n'importe où dans votre sortie, votre perte ou votre gradient, alors elle se propagera rapidement à travers tout votre modèle. Ceci parce que lorsque cette valeur `nan` est propagée à travers votre réseau, vous obtiendrez des gradients `nan`, et lorsque les mises à jour des poids sont calculées avec ces gradients, vous obtiendrez des poids `nan`, et ces poids calculeront encore plus de sorties `nan` ! Très vite, le réseau entier ne sera plus qu'un gros bloc de `nan`. Une fois que cela arrive, il est assez difficile de voir où le problème a commencé. Comment peut-on isoler l'endroit où les `nan` se sont introduits en premier ? + +La réponse est d'essayer de *reinitialiser* notre modèle. Une fois que nous avons commencé l'entraînement, nous avons eu un `nan` quelque part et il s'est rapidement propagé à travers tout le modèle. Donc, chargeons le modèle à partir d'un checkpoint et ne faisons aucune mise à jour de poids, et voyons où nous obtenons une valeur `nan` : + +```py +model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint) +model(batch) +``` + +Quand on fait ça, on obtient : + +```py out +TFSequenceClassifierOutput(loss=, logits=, hidden_states=None, attentions=None) +``` + +*Maintenant* on arrive à quelque chose ! Il n'y a pas de valeurs `nan` dans nos logits, ce qui est rassurant. Mais nous voyons quelques valeurs `nan` dans notre perte ! Y a-t-il quelque chose dans ces échantillons en particulier qui cause ce problème ? Voyons de quels échantillons il s'agit (notez que si vous exécutez ce code vous-même, vous pouvez obtenir des indices différents parce que l'ensemble de données a été mélangé) : + +```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]) +``` + +Let's look at the samples these indices came from: + +```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]]) +``` + +Il y a beaucoup de batchs ici, mais rien d'inhabituel. Regardons les étiquettes : + +```python out +labels = batch['labels'].numpy() +labels[indices] +``` + +```python out +array([2, 2, 2, 2, 2, 2, 2, 2, 2]) +``` + +Ah ! Les échantillons `nan` ont tous le même label, et c'est le label 2. C'est un indice très fort. Le fait que nous n'obtenions une perte de `nan` que lorsque notre étiquette est 2 suggère que c'est un très bon moment pour vérifier le nombre d'étiquettes dans notre modèle : + +```python +model.config.num_labels +``` + +```python out +2 +``` + +Nous voyons maintenant le problème : le modèle pense qu'il n'y a que deux classes, mais les étiquettes vont jusqu'à 2, ce qui signifie qu'il y a en fait trois classes (car 0 est aussi une classe). C'est ainsi que nous avons obtenu un `nan` - en essayant de calculer la perte pour une classe inexistante ! Essayons de changer cela et de réajuster le modèle : + +``` +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 +``` + +On s'entraîne ! Plus de `nan`, et nos pertes diminuent... en quelque sorte. Si vous le regardez pendant un certain temps, vous pouvez commencer à vous impatienter, car la valeur des pertes reste obstinément élevée. Arrêtons l'entraînement ici et essayons de réfléchir à ce qui pourrait causer ce problème. À ce stade, nous sommes pratiquement sûrs que les données et le modèle sont corrects, mais notre modèle n'apprend pas bien. Que reste-t-il d'autre ? Il est temps de... + +### Vérifier vos hyperparamètres + +Si vous regardez le code ci-dessus, vous ne verrez peut-être aucun hyperparamètre, sauf peut-être le `batch_size`, et cela ne semble pas être un coupable probable. Ne soyez pas dupe, cependant ; il y a toujours des hyperparamètres, et si vous ne pouvez pas les voir, cela signifie simplement que vous ne savez pas à quoi ils sont réglés. En particulier, souvenez-vous d'une chose essentielle à propos de Keras : si vous définissez une fonction de perte, d'optimisation ou d'activation avec une chaîne, _tous ses arguments seront définis sur leurs valeurs par défaut_. Cela signifie que, même si l'utilisation de chaînes de caractères est très pratique, vous devez être très prudent, car cela peut facilement vous cacher des éléments critiques. (Toute personne essayant le défi optionnel ci-dessus devrait prendre bonne note de ce fait). + +Dans ce cas, où avons-nous défini un argument avec une chaîne ? Au départ, nous définissions la perte avec une chaîne, mais nous ne le faisons plus. Cependant, nous définissons l'optimiseur avec une chaîne de caractères. Cela pourrait-il nous cacher quelque chose ? Jetons un coup d'œil à [ses arguments](https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/Adam). + +Y a-t-il quelque chose qui ressort ? C'est exact : le taux d'apprentissage ! Lorsque nous utilisons simplement la chaîne `'adam'`, nous allons obtenir le taux d'apprentissage par défaut, qui est de 0.001, ou 1e-3. C'est beaucoup trop élevé pour un modèle Transformer ! En général, nous recommandons d'essayer des taux d'apprentissage entre 1e-5 et 1e-4 pour vos modèles ; c'est quelque part entre 10X et 100X plus petit que la valeur que nous utilisons ici. Cela semble être un problème majeur, alors essayons de le réduire. Pour ce faire, nous devons importer l'objet `optimizer`. Pendant que nous y sommes, réinitialisons le modèle à partir du point de contrôle, au cas où l'entraînement avec un taux d'apprentissage élevé aurait endommagé ses poids : + +```python +from tensorflow.keras.optimizers import Adam + +model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint) +model.compile(optimizer=Adam(5e-5)) +``` + + + +💡 Vous pouvez également importer la fonction `create_optimizer()` de 🤗 *Transformers*, qui vous donnera un optimiseur AdamW avec une décroissance de poids correcte ainsi qu'un réchauffement et une décroissance du taux d'apprentissage. Cet optimiseur produira souvent des résultats légèrement meilleurs que ceux que vous obtenez avec l'optimiseur Adam par défaut. + + + +Maintenant, nous pouvons essayer d'ajuster le modèle avec le nouveau taux d'apprentissage amélioré : + +```python +model.fit(train_dataset) +``` + +```python out +319/24543 [..............................] - ETA: 16:07 - loss: 0.9718 +``` + +Maintenant notre perte va vraiment aller quelque part ! L'entraînement semble enfin fonctionner. Il y a une leçon à tirer ici : lorsque votre modèle fonctionne mais que la perte ne diminue pas, et que vous êtes sûr que vos données sont correctes, c'est une bonne idée de vérifier les hyperparamètres comme le taux d'apprentissage et la décroissance du poids. Un réglage trop élevé de l'un ou l'autre de ces paramètres risque fort de faire " caler " l'entraînement à une valeur de perte élevée. + +## Autres problèmes potentiels + +Nous avons couvert les problèmes dans le script ci-dessus, mais il existe plusieurs autres erreurs courantes auxquelles vous pouvez être confronté. Jetons un coup d'oeil à une liste (très incomplète). + +### Gérer les erreurs de manque de mémoire + +Le signe révélateur d'un manque de mémoire est une erreur du type "OOM when allocating tensor" -- OOM est l'abréviation de "out of memory". Il s'agit d'un risque très courant lorsque l'on traite de grands modèles de langage. Si vous rencontrez ce problème, une bonne stratégie consiste à diviser par deux la taille de votre batch et à réessayer. Gardez à l'esprit, cependant, que certains modèles sont *très* grands. Par exemple, le modèle GPT-2 complet possède 1,5 Go de paramètres, ce qui signifie que vous aurez besoin de 6 Go de mémoire rien que pour stocker le modèle, et 6 autres Go pour ses gradients ! Entraîner le modèle GPT-2 complet nécessite généralement plus de 20 Go de VRAM, quelle que soit la taille du batch utilisé, ce dont seuls quelques GPU sont dotés. Des modèles plus légers comme `distilbert-base-cased` sont beaucoup plus faciles à exécuter, et s'entraînent aussi beaucoup plus rapidement. + + + +Dans la prochaine partie du cours, nous examinerons des techniques plus avancées qui peuvent vous aider à réduire votre empreinte mémoire et vous permettre d'affiner les plus grands modèles. + + + +### Hungry Hungry TensorFlow 🦛 + +Une bizarrerie particulière de TensorFlow dont vous devez être conscient est qu'il s'alloue *toute* la mémoire de votre GPU dès que vous chargez un modèle ou que vous effectuez un entraînement, puis il divise cette mémoire selon les besoins. Ce comportement est différent de celui d'autres frameworks, comme PyTorch, qui alloue la mémoire selon les besoins avec CUDA plutôt que de le faire en interne. L'un des avantages de l'approche de TensorFlow est qu'elle peut souvent donner des erreurs utiles lorsque vous manquez de mémoire, et qu'elle peut récupérer de cet état sans planter tout le noyau CUDA. Mais il y a aussi un inconvénient important : si vous exécutez deux processus TensorFlow en même temps, alors **vous allez passer un mauvais moment**. + +Si vous travaillez sur Colab, vous n'avez pas à vous soucier de cela, mais si vous travaillez localement, vous devez absolument faire attention. En particulier, sachez que la fermeture d'un onglet de notebook n'entraîne pas nécessairement la fermeture de ce *notebook* ! Vous devrez peut-être sélectionner les blocs-notes en cours d'exécution (ceux qui ont une icône verte) et les fermer manuellement dans la liste des répertoires. Tout *notebook* en cours d'exécution qui utilisait TensorFlow peut encore utiliser une grande partie de la mémoire de votre GPU, ce qui signifie que tout nouveau notebook que vous démarrez peut rencontrer des problèmes très étranges. + +Si vous commencez à obtenir des erreurs concernant CUDA, BLAS ou cuBLAS dans du code qui fonctionnait auparavant, c'est très souvent le coupable. Vous pouvez utiliser une commande comme `nvidia-smi` pour vérifier - quand vous éteignez ou redémarrez votre *notebook* actuel, est-ce que la plupart de votre mémoire est libre, ou est-elle toujours utilisée ? Si elle est toujours utilisée, c'est que quelque chose d'autre s'y accroche ! + + +### Vérifiez vos données (encore !) + +Votre modèle n'apprendra quelque chose que s'il est réellement possible d'apprendre quelque chose de vos données. S'il y a un bug qui corrompt les données ou si les étiquettes sont attribuées de manière aléatoire, il est très probable que vous n'obtiendrez aucun entraînement de modèle sur votre jeu de données. Un outil utile ici est `tokenizer.decode()`. Cela transformera les `input_ids` en chaînes de caractères, afin que vous puissiez visualiser les données et voir si vos données d'entraînement enseignent ce que vous voulez qu'elles enseignent. Par exemple, après avoir obtenu un `batch` de votre `tf.data.Dataset` comme nous l'avons fait ci-dessus, vous pouvez décoder le premier élément comme suit : + + +```py +input_ids = batch["input_ids"].numpy() +tokenizer.decode(input_ids[0]) +``` + +Vous pouvez ensuite la comparer avec la première étiquette, comme suit : + +```py +labels = batch["labels"].numpy() +label = labels[0] +``` + +Une fois que vous pouvez visualiser vos données de cette manière, vous pouvez vous poser les questions suivantes : + +- les données décodées sont-elles compréhensibles ? +- êtes-vous d'accord avec les étiquettes ? +- y a-t-il une étiquette qui est plus courante que les autres ? +- quelle devrait être la perte/métrie si le modèle prédisait une réponse aléatoire/toujours la même réponse ? + +Après avoir examiné vos données, examinez quelques-unes des prédictions du modèle - si votre modèle produit des tokens, essayez aussi de les décoder ! Si le modèle prédit toujours la même chose, cela peut être dû au fait que votre ensemble de données est biaisé en faveur d'une catégorie (pour les problèmes de classification), des techniques telles que le suréchantillonnage des classes rares peuvent aider. Des techniques telles que le suréchantillonnage des classes rares peuvent donc être utiles. D'autre part, cela peut également être dû à des problèmes d'entraînement tels que de mauvais réglages des hyperparamètres. + +Si la perte/la métrique que vous obtenez sur votre modèle initial avant tout entraînement est très différente de la perte/la métrique à laquelle vous vous attendez pour des prédictions aléatoires, vérifiez la façon dont votre perte ou votre métrique est calculée, car il y a probablement un bug. Si vous utilisez plusieurs pertes que vous ajoutez à la fin, assurez-vous qu'elles sont de la même échelle. + +Lorsque vous êtes sûr que vos données sont parfaites, vous pouvez voir si le modèle est capable de s'entraîner sur elles grâce à un test simple. + +### Surentraînement du modèle sur un seul batch + +Le surentrâinement est généralement une chose que nous essayons d'éviter lors de l'entraînement, car cela signifie que le modèle n'apprend pas à reconnaître les caractéristiques générales que nous voulons qu'il reconnaisse, mais qu'il se contente de mémoriser les échantillons d'entraînement. Cependant, essayer d'entraîner votre modèle sur un batch encore et encore est un bon test pour vérifier si le problème tel que vous l'avez formulé peut être résolu par le modèle que vous essayez d'entraîner. Cela vous aidera également à voir si votre taux d'apprentissage initial est trop élevé. + +Une fois que vous avez défini votre `modèle`, c'est très facile ; il suffit de prendre un batch de données d'entraînement, puis de traiter ce `batch` comme votre ensemble de données entier, en l'ajustant sur un grand nombre d'époques : + + +```py +for batch in train_dataset: + break + +# Make sure you have run model.compile() and set your optimizer, +# and your loss/metrics if you're using them + +model.fit(batch, epochs=20) +``` + + + +💡 Si vos données d'entraînement ne sont pas équilibrées, veillez à créer un batch de données d'entraînement contenant toutes les étiquettes. + + + +Le modèle résultant devrait avoir des résultats proches de la perfection sur le `batch`, avec une perte diminuant rapidement vers 0 (ou la valeur minimale pour la perte que vous utilisez). + +Si vous ne parvenez pas à ce que votre modèle obtienne des résultats parfaits comme celui-ci, cela signifie qu'il y a quelque chose qui ne va pas dans la façon dont vous avez formulé le problème ou dans vos données, et vous devez donc y remédier. Ce n'est que lorsque vous parviendrez à passer le test de surentraînement que vous pourrez être sûr que votre modèle peut réellement apprendre quelque chose. + + + +⚠️ Vous devrez recréer votre modèle et votre `Trainer` après ce test, car le modèle obtenu ne sera probablement pas capable de récupérer et d'apprendre quelque chose d'utile sur votre jeu de données complet. + + + +### Ne réglez rien tant que vous n'avez pas une première ligne de base + +Le réglage des hyperparamètres est toujours considéré comme la partie la plus difficile de l'apprentissage automatique, mais c'est juste la dernière étape pour vous aider à gagner un peu sur la métrique. La plupart du temps, les hyperparamètres par défaut du `Trainer` fonctionneront très bien pour vous donner de bons résultats, donc ne vous lancez pas dans une recherche d'hyperparamètres longue et coûteuse jusqu'à ce que vous ayez quelque chose qui batte la ligne de base que vous avez sur votre jeu de données. + +Une fois que vous avez un modèle suffisamment bon, vous pouvez commencer à l'affiner un peu. N'essayez pas de lancer un millier d'exécutions avec différents hyperparamètres, mais comparez quelques exécutions avec différentes valeurs pour un hyperparamètre afin de vous faire une idée de celui qui a le plus d'impact. + +Si vous modifiez le modèle lui-même, restez simple et n'essayez rien que vous ne puissiez raisonnablement justifier. Veillez toujours à revenir au test de surentraînement pour vérifier que votre modification n'a pas eu de conséquences inattendues. + +### Demander de l'aide + +Nous espérons que vous avez trouvé dans cette section des conseils qui vous ont aidé à résoudre votre problème, mais si ce n'est pas le cas, n'oubliez pas que vous pouvez toujours demander de l'aide à la communauté sur le [forum](https://discuss.huggingface.co/). + +Voici quelques ressources (en anglais) supplémentaires qui peuvent s'avérer utiles : + +- ["La reproductibilité comme vecteur des meilleures pratiques d'ingénierie"](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p) par Joel Grus +- ["Liste de contrôle pour le débogage des réseaux neuronaux"](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) par Cecelia Shao +- ["Comment tester unitairement le code d'apprentissage automatique"](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) par Chase Roberts +- ["Une recette pour Entraîner les réseaux neuronaux"](http://karpathy.github.io/2019/04/25/recipe/) par Andrej Karpathy + +Bien sûr, tous les problèmes rencontrés lors de l'Entraînement des réseaux neuronaux ne sont pas forcément de votre faute ! Si vous rencontrez quelque chose dans la bibliothèque 🤗 *Transformers* ou 🤗 *Datasets* qui ne semble pas correct, vous avez peut-être rencontré un bogue. Vous devez absolument nous en parler, et dans la section suivante, nous allons vous expliquer exactement comment faire. diff --git a/chapters/fr/chapter8/5.mdx b/chapters/fr/chapter8/5.mdx new file mode 100644 index 000000000..ee47ca77e --- /dev/null +++ b/chapters/fr/chapter8/5.mdx @@ -0,0 +1,91 @@ +# Comment rédiger une bonne *issue* + + + +Lorsque vous rencontrez un problème avec l'une des bibliothèques Hugging Face, vous devez nous le faire savoir afin que nous puissions le corriger (il en va de même pour toute bibliothèque open source). Si vous n'êtes pas complètement certain que le bug se trouve dans votre propre code ou dans l'une de nos bibliothèques, le premier endroit à vérifier est le [forum](https://discuss.huggingface.co/). La communauté vous aidera à résoudre ce problème, et l'équipe Hugging Face suit également de près les discussions qui s'y déroulent. + + + +Lorsque vous êtes sûr d'avoir un bogue en main, la première étape consiste à construire un exemple minimal reproductible. + +## Créer un exemple minimal reproductible + +Il est très important d'isoler le morceau de code qui produit le bug, car personne dans l'équipe Hugging Face n'est (encore) un magicien, et ils ne peuvent pas réparer ce qu'ils ne peuvent pas voir. Un exemple minimal reproductible doit, comme son nom l'indique, être reproductible. Cela signifie qu'il ne doit pas dépendre de fichiers ou de données externes que vous pourriez avoir. Essayez de remplacer les données que vous utilisez par des valeurs fictives qui ressemblent à vos valeurs réelles et qui produisent toujours la même erreur. + + + +🚨 De nombreux problèmes dans le dépôt 🤗 *Transformers* ne sont pas résolus car les données utilisées pour les reproduire ne sont pas accessibles. + + + +Une fois que vous avez quelque chose d'autonome, vous pouvez essayer de le réduire à encore moins de lignes de code, en construisant ce que nous appelons un _exemple reproductible minimal_. Bien que cela nécessite un peu plus de travail de votre part, vous serez presque assuré d'obtenir de l'aide et une correction si vous fournissez un exemple reproductible court et agréable. + +Si vous vous sentez suffisamment à l'aise, allez inspecter le code source où se trouve votre bogue. Vous trouverez peut-être une solution à votre problème (dans ce cas, vous pouvez même suggérer une pull request pour le corriger), mais plus généralement, cela peut aider les mainteneurs à mieux comprendre le code source lorsqu'ils lisent votre rapport. + +## Remplir le modèle de problème + +Lorsque vous déposez votre problème, vous remarquerez qu'il y a un modèle à remplir. Nous suivrons ici celui pour [🤗 *Transformers* issues](https://github.com/huggingface/transformers/issues/new/choose), mais le même type d'information sera requis si vous rapportez un problème dans un autre dépôt. Ne laissez pas le modèle vide : prendre le temps de le remplir maximisera vos chances d'obtenir une réponse et de résoudre votre problème. + +En général, lorsque vous signalez un problème, restez toujours courtois. Il s'agit d'un projet open source, vous utilisez donc un logiciel libre, et personne n'est obligé de vous aider. Vous pouvez inclure dans votre problème des critiques qui vous semblent justifiées, mais les mainteneurs pourraient très bien les prendre mal et ne pas être pressés de vous aider. Assurez-vous de lire le [code de conduite](https://github.com/huggingface/transformers/blob/master/CODE_OF_CONDUCT.md) du projet. + +### Inclure les informations sur votre environnement + +🤗 *Transformers* fournit un utilitaire pour obtenir toutes les informations dont nous avons besoin sur votre environnement. Il suffit de taper ce qui suit dans votre terminal : + +``` +transformers-cli env +``` + +et vous devriez obtenir quelque chose comme ça : + +```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?: +``` + +Vous pouvez également ajouter un `!` au début de la commande `transformers-cli env` pour l'exécuter depuis une cellule du *notebook* puis copier et coller le résultat au début de votre problème. + +### Marquer des personnes + +Marquer des personnes en tapant un `@` suivi de leur identifiant GitHub leur enverra une notification afin qu'elles voient votre problème et puissent répondre plus rapidement. Utilisez cette fonction avec modération, car les personnes que vous marquez peuvent ne pas apprécier d'être notifiées si elles n'ont pas de lien direct avec le problème. Si vous avez regardé les fichiers sources liés à votre bogue, vous devriez étiqueter la dernière personne qui a fait des changements à la ligne que vous pensez être responsable de votre problème (vous pouvez trouver cette information en regardant ladite ligne sur GitHub, en la sélectionnant, puis en cliquant sur "View git blame"). + +Sinon, le modèle propose des suggestions de personnes à étiqueter. En général, ne marquez jamais plus de trois personnes ! + +### Inclure un exemple reproductible + +Si vous avez réussi à créer un exemple autonome qui produit le bogue, il est temps de l'inclure ! Tapez une ligne avec trois backticks suivis de `python`, comme ceci : + +``` +```python +``` + +puis collez votre exemple minimal reproductible et tapez une nouvelle ligne avec trois backticks. Cela permettra de s'assurer que votre code est correctement formaté. + +Si vous n'avez pas réussi à créer un exemple reproductible, expliquez en étapes claires comment vous êtes arrivé à votre problème. Si vous le pouvez, incluez un lien vers un *notebook* de Google Colab où vous avez trouvé l'erreur. Plus vous partagerez d'informations, plus les mainteneurs seront en mesure de vous répondre. + +Dans tous les cas, vous devez copier et coller l'intégralité du message d'erreur que vous obtenez. Si vous travaillez dans Colab, n'oubliez pas que certains cadres peuvent être automatiquement réduits dans la trace de la pile, et veillez donc à les développer avant de les copier. Comme pour l'exemple de code, placez le message d'erreur entre deux lignes avec trois points de suspension, afin qu'il soit correctement formaté. + +### Décrire le comportement attendu + +Expliquez en quelques lignes ce que vous vous attendiez à obtenir, afin que les mainteneurs comprennent bien le problème. Cette partie est généralement assez évidente, elle devrait donc tenir en une seule phrase, mais dans certains cas, vous pouvez avoir beaucoup à dire. + +## Et ensuite ? + +Une fois que votre problème est classé, vérifiez rapidement que tout est en ordre. Vous pouvez modifier le problème si vous avez fait une erreur, ou même changer son titre si vous vous rendez compte que le problème est différent de ce que vous pensiez initialement. + +Il est inutile d'envoyer des messages aux personnes concernées si vous n'obtenez pas de réponse. Si personne ne vous aide au bout de quelques jours, il est probable que personne n'a pu donner un sens à votre problème. N'hésitez pas à revenir à l'exemple reproductible. Pouvez-vous le rendre plus court et plus concis ? Si vous n'obtenez pas de réponse au bout d'une semaine, vous pouvez laisser un message demandant gentiment de l'aide, surtout si vous avez modifié votre question pour inclure plus d'informations sur le problème. diff --git a/chapters/fr/chapter8/6.mdx b/chapters/fr/chapter8/6.mdx new file mode 100644 index 000000000..b31f82d1f --- /dev/null +++ b/chapters/fr/chapter8/6.mdx @@ -0,0 +1,7 @@ +# Partie 2 terminée ! + +Félicitations, vous avez terminé la deuxième partie du cours ! Nous travaillons activement sur la troisième alors inscrivez-vous à notre [*newsletter*](https://huggingface.curated.co/) pour être sûr de ne pas manquer sa sortie. + +Vous devriez maintenant être en mesure d'aborder une série de tâches de NLP et de *finetuner* ou de prétraîner un modèle sur celles-ci. N'oubliez pas de partager vos résultats avec la communauté sur le [*Hub*](https://huggingface.co/models). + +Nous sommes impatients de voir ce que vous allez construire avec les connaissances que vous avez acquises ! diff --git a/chapters/fr/chapter8/7.mdx b/chapters/fr/chapter8/7.mdx new file mode 100644 index 000000000..217771167 --- /dev/null +++ b/chapters/fr/chapter8/7.mdx @@ -0,0 +1,198 @@ + + +# Quiz de fin de chapitre + +Testons ce que vous avez appris dans ce chapitre ! + +### 1. Dans quel ordre devez-vous lire un *traceback* Python ? + +traceback de Python montrant l'exception en bas est qu'il est plus facile de déboguer lorsque vous travaillez dans le terminal et que c'est la dernière ligne que vous voyez.", + correct: true + } + ]} +/> + +### 2. Qu'est-ce qu'un exemple minimal reproductible ? + +transformer à partir d'un article de recherche.", + explain: "Bien qu'il soit très éducatif d'implémenter vos propres modèles de transformers à partir de zéro, ce n'est pas ce dont nous parlons ici." + }, + { + text: "Un bloc de code compact et autonome qui peut être exécuté sans aucune dépendance externe sur des fichiers ou des données privées.", + explain: "Corrigez ! Des exemples minimaux reproductibles aident les mainteneurs de la bibliothèque à reproduire le problème que vous rencontrez, afin qu'ils puissent trouver des solutions plus rapidement.", + correct: true + }, + { + text: "Une capture d'écran de la traceback Python", + explain: "Essayez à nouveau. Bien qu'il soit tentant d'inclure une capture d'écran de l'erreur à laquelle vous êtes confronté lorsque vous soumettez un problème, cela rend très difficile pour les autres de reproduire l'erreur." + }, + { + text: "Un notebook qui contient toute votre analyse, y compris les parties sans rapport avec l'erreur.", + explain: "Pas tout à fait. Bien qu'il puisse être utile de partager un notebook Google Colab qui montre l'erreur, assurez-vous qu'il est court et ne contient que le code pertinent." + } + ]} +/> + +### 3. Supposons que vous essayez d'exécuter le code suivant, qui génère une erreur : + +```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) +``` + +Lequel des éléments suivants pourrait être un bon choix pour le titre d'un sujet de forum pour demander de l'aide ? + +ImportError: cannot import name 'GPT3ForSequenceClassification' from 'transformers' (/Users/lewtun/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/__init__.py)", + explain: "Inclure la dernière ligne de la traceback peut être descriptif, mais il est préférable de le réserver au corps principal du sujet. Essayez à nouveau !" + }, + { + text: "Problème avec from transformers import GPT3ForSequenceClassification", + explain: "Essayez à nouveau. Bien que cette information soit utile, il est probablement préférable de la réserver au corps du texte.", + }, + { + text: "Pourquoi je ne peux pas importer GPT3ForSequenceClassification?", + explain: "Bon choix ! Ce titre est concis et donne au lecteur un indice sur ce qui pourrait être erroné (par exemple, que GPT-3 n'est pas pris en charge dans 🤗 Transformers).", + correct: true + }, + { + text: "Le GPT-3 est-il pris en charge dans 🤗 Transformers ?", + explain: "Bien vu ! Utiliser des questions comme titres de sujets est un excellent moyen de communiquer le problème à la communauté..", + correct: true + } + ]} +/> + +### 4. Supposons que vous ayez essayé d'exécuter `trainer.train()` et que vous soyez confronté à une erreur énigmatique qui ne vous dit pas exactement d'où vient l'erreur. Quel est le premier endroit où vous devez chercher les erreurs dans votre pipeline d'entraînement ? + +dataloaders ?" + } + ]} +/> + +### 5. Quelle est la meilleure façon de déboguer une erreur CUDA ? + +traceback pour découvrir ce qui a causé l'erreur.", + explain: "C'est ce que vous feriez pour toute autre erreur, mais les erreurs CUDA ne sont généralement pas signalées là où elles se sont produites, car la plupart des opérations CUDA sont asynchrones." + }, + { + text: "Réduisez la taille du batch.", + explain: "La réduction de la taille du batch est généralement une bonne stratégie pour gérer les erreurs CUDA hors mémoire, mais pas pour ce problème particulier. Essayez à nouveau !" + }, + { + text: "Redémarrez le noyau Jupyter.", + explain: "Essayez à nouveau. Le redémarrage du noyau ne fera pas disparaître l'erreur comme par magie !", + } + ]} +/> + +### 6. Quelle est la meilleure façon de faire corriger un problème sur GitHub ? + + + +### 7. Pourquoi le surapprentissage à un batch est-il généralement une bonne technique de débogage ? + + + +### 8. Pourquoi est-ce une bonne idée d'inclure des détails sur votre environnement de calcul avec `transformers-cli env` lorsque vous créez une nouvelle question dans le dépôt 🤗 *Transformers* ? + + diff --git a/chapters/fr/event/1.mdx b/chapters/fr/event/1.mdx new file mode 100644 index 000000000..28b491d05 --- /dev/null +++ b/chapters/fr/event/1.mdx @@ -0,0 +1,170 @@ +# Événement pour le lancement de la partie 2 + +Pour la sortie de la deuxième partie du cours, nous avons organisé un événement en direct consistant en deux jours de conférences suivies d’un *sprint* de *finetuning*. Si vous l'avez manqué, vous pouvez rattraper les présentations qui sont toutes listées ci-dessous ! + +## Jour 1 : Une vue d'ensemble des *transformers* et comment les entraîner + + +**Thomas Wolf :** *L'apprentissage par transfert et la naissance de la bibliothèque 🤗 Transformers* + +
+ +
+ +

+A visual summary of Thom's talk +

+ +Thomas Wolf est cofondateur et directeur scientifique d’Hugging Face. Les outils créés par Thomas Wolf et l'équipe d’Hugging Face sont utilisés par plus de 5 000 organismes de recherche, dont Facebook Artificial Intelligence Research, Google Research, DeepMind, Amazon Research, Apple, l'Allen Institute for Artificial Intelligence ainsi que la plupart des départements universitaires. Thomas Wolf est l'initiateur et le président principal de la plus grande collaboration de recherche qui ait jamais existé dans le domaine de l'intelligence artificielle : [« BigScience »](https://bigscience.huggingface.co), ainsi que d'un ensemble de [bibliothèques et outils](https://github.com/huggingface/) largement utilisés. Thomas Wolf est également un éducateur prolifique, un *leader* d'opinion dans le domaine de l'intelligence artificielle et du traitement du langage naturel, et un orateur régulièrement invité à des conférences dans le monde entier [https://thomwolf.io](https://thomwolf.io). + +**Jay Alammar :** *Une introduction visuelle douce aux transformers* + +
+ +
+ +

+A visual summary of Jay's talk +

+ +Grâce à son blog d’apprentissage automatique très populaire, Jay a aidé des millions de chercheurs et d'ingénieurs à comprendre visuellement les outils et les concepts de l'apprentissage automatique, des plus élémentaires (qui se retrouvent dans les docs NumPy et Pandas) aux plus pointus (Transformer, BERT, GPT-3). + +**Margaret Mitchell :** *Les valeurs dans le développement de l’apprentissage automatique* + +
+ +
+ +

+A visual summary of Margaret's talk +

+ +Margaret Mitchell est une chercheuse travaillant sur l'IA éthique. Elle se concentre actuellement sur les tenants et aboutissants du développement de l'IA éthique dans le domaine de la technologie. Elle a publié plus de cinquante articles sur la génération de langage naturel, les technologies d'assistance, la vision par ordinateur et l'IA éthique. Elle détient plusieurs brevets dans le domaine de la génération de conversations et celui de la classification des sentiments. Elle a précédemment travaillé chez Google AI en tant que chercheuse où elle a fondé et codirigé le groupe d'IA éthique de Google. Ce groupe est axé sur la recherche fondamentale en matière d'IA éthique et l'opérationnalisation de d'IA éthique en interne à Google. Avant de rejoindre Google, elle a été chercheuse chez Microsoft Research où elle s'est concentrée sur la génération de la vision par ordinateur vers le langage et a été post-doc à Johns Hopkins où elle s'est concentrée sur la modélisation bayésienne et l'extraction d'informations. Elle est titulaire d'un doctorat en informatique de l'université d'Aberdeen et d'une maîtrise en linguistique informatique de l'université de Washington. Tout en obtenant ses diplômes, elle a également travaillé de 2005 à 2012 sur l'apprentissage automatique, les troubles neurologiques et les technologies d'assistance à l'Oregon Health and Science University. Elle a dirigé un certain nombre d'ateliers et d'initiatives au croisement de la diversité, de l'inclusion, de l'informatique et de l'éthique. Ses travaux ont été récompensés par le secrétaire à la défense Ash Carter et la Fondation américaine pour les aveugles, et ont été implémenté par plusieurs entreprises technologiques. + +**Matthew Watson et Chen Qian :** *Les flux de travail en NLP avec Keras* + +
+ +
+ +

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

+ +Matthew Watson est ingénieur en apprentissage automatique au sein de l'équipe Keras et se concentre sur les API de modélisation de haut niveau. Il a étudié l'infographie pendant ses études et a obtenu un master à l'université de Stanford. Il s'est orienté vers l'informatique après avoir étudié l'anglais. Il est passionné par le travail interdisciplinaire et par la volonté de rendre le traitement automatique des langues accessible à un public plus large. + +Chen Qian est un ingénieur logiciel de l'équipe Keras spécialisé dans les API de modélisation de haut niveau. Chen est titulaire d'un master en génie électrique de l'université de Stanford et s'intéresse particulièrement à la simplification de l'implémentation du code des tâches d’apprentissage automatique et le passage à grande échelle de ces codes. + + +**Mark Saroufim :** *Comment entraîner un modèle avec Pytorch* + +
+ +
+ +

+A visual summary of Mark's talk +

+ +Mark Saroufim est ingénieur partenaire chez Pytorch et travaille sur les outils de production OSS, notamment TorchServe et Pytorch Enterprise. Dans ses vies antérieures, Mark a été un scientifique appliqué et un chef de produit chez Graphcore, [yuri.ai](http://yuri.ai/), Microsoft et au JPL de la NASA. Sa principale passion est de rendre la programmation plus amusante. + +**Jakob Uszkoreit :** *Ce n'est pas cassé alors ne réparez pas cassez tout* + +
+ +
+ +

+A visual summary of Jakob's talk +

+ +Jakob Uszkoreit est le cofondateur d'Inceptive. Inceptive conçoit des molécules d'ARN pour les vaccins et les thérapies en utilisant l'apprentissage profond à grande échelle. Le tout en boucle étroite avec des expériences à haut débit, dans le but de rendre les médicaments à base d'ARN plus accessibles, plus efficaces et plus largement applicables. Auparavant, Jakob a travaillé chez Google pendant plus de dix ans, dirigeant des équipes de recherche et de développement au sein de Google Brain, Research et Search, travaillant sur les fondamentaux de l'apprentissage profond, la vision par ordinateur, la compréhension du langage et la traduction automatique. + +## Jour 2 : Les outils à utiliser + + +**Lewis Tunstall :** *Un entraînement simple avec la fonction *Trainer* de la bibliotèque 🤗 Transformers* + +
+ +
+ +Lewis est un ingénieur en apprentissage machine chez Hugging Face qui se concentre sur le développement d'outils open-source et les rend accessibles à la communauté. Il est également co-auteur d'un livre à paraître chez O'Reilly sur les *transformers*. Vous pouvez le suivre sur Twitter (@_lewtun) pour des conseils et astuces en traitement du langage naturel ! + +**Matthew Carrigan :** *Nouvelles fonctionnalités en TensorFlow pour 🤗 Transformers et 🤗 Datasets* + +
+ +
+ +Matt est responsable de la maintenance des modèles en TensorFlow chez *Transformers*. Il finira par mener un coup d'État contre la faction PyTorch en place Celui sera probablement coordonné via son compte Twitter @carrigmat. + +**Lysandre Debut :** *L’Hugging Face Hub, un moyen de collaborer et de partager des projets d'apprentissage automatique* + +
+ +
+ +

+A visual summary of Lysandre's talk +

+ +Lysandre est ingénieur en apprentissage machine chez Hugging Face où il participe à de nombreux projets open source. Son objectif est de rendre l'apprentissage automatique accessible à tous en développant des outils puissants avec une API très simple. + +**Lucile Saulnier :** *Avoir son propre tokenizer avec 🤗 Transformers & 🤗 Tokenizers* + +
+ +
+ +Lucile est ingénieur en apprentissage automatique chez Hugging Face où elle développe et soutient l'utilisation d'outils open source. Elle est également activement impliquée dans de nombreux projets de recherche dans le domaine du traitement du langage naturel tels que l’entraînement collaboratif et BigScience. + +**Sylvain Gugger :** *Optimisez votre boucle d'entraînement PyTorch avec +🤗 Accelerate* + +
+ +
+ +Sylvain est ingénieur de recherche chez Hugging Face. Il est l'un des principaux mainteneurs de 🤗 Transformers et le développeur derrière 🤗 Accelerate. Il aime rendre l'apprentissage des modèles plus accessible. + +**Merve Noyan :** *Présentez vos démonstrations de modèles avec +🤗 Spaces* + +
+ +
+ +Merve est *developer advocate* chez Hugging Face travaillant au développement d'outils et à la création de contenu autour d'eux afin de démocratiser l'apprentissage automatique pour tous. + +**Abubakar Abid :** *Créer rapidement des applications d'apprentissage automatique* + +
+ +
+ +

+A visual summary of Abubakar's talk +

+ +Abubakar Abid est le PDG de [Gradio](www.gradio.app). Il a obtenu sa licence en génie électrique et en informatique au MIT en 2015, et son doctorat en apprentissage automatique appliqué à Stanford en 2021. En tant que PDG de Gradio, Abubakar s'efforce de faciliter la démonstration, le débogage et le déploiement des modèles d'apprentissage automatique. + +**Mathieu Desvé :** *AWS ML Vision : Rendre l'apprentissage automatique accessible à tous les clients* + +
+ +
+ +

+A visual summary of Mathieu's talk +

+ +Passionné de technologie, il est un créateur pendant son temps libre. Il aime les défis et résoudre les problèmes des clients et des utilisateurs ainsi que travailler avec des personnes talentueuses pour apprendre chaque jour. Depuis 2004, il a occupé plusieurs postes, passant du frontend au backend, de l'infrastructure aux opérations et à la gestion. Il essaie de résoudre les problèmes techniques et de gestion courants de manière agile. + +**Philipp Schmid :** *Managed Training avec Amazon SageMaker and 🤗 Transformers* + +
+ +
+ +Philipp Schmid est ingénieur en apprentissage machine et Tech Lead chez Hugging Face où il dirige la collaboration avec l'équipe Amazon SageMaker. Il est passionné par la démocratisation et la mise en production de modèles de traitement du langage naturel de pointe et par l'amélioration de la facilité d'utilisation du Deep Learning. diff --git a/chapters/gj/_toctree.yml b/chapters/gj/_toctree.yml new file mode 100644 index 000000000..5c7be7a95 --- /dev/null +++ b/chapters/gj/_toctree.yml @@ -0,0 +1,5 @@ +- title: 0. સ્થાપના + sections: + - local: chapter0/1 + title: પરિચય + diff --git a/chapters/gj/chapter0/1.mdx b/chapters/gj/chapter0/1.mdx new file mode 100644 index 000000000..55bb3d7db --- /dev/null +++ b/chapters/gj/chapter0/1.mdx @@ -0,0 +1,13 @@ +# પરિચય + +હગિંગ ફેસ પર આપનું સ્વાગત છે! આ પરિચય તમને કામનું વાતાવરણ ગોઠવવામાં માર્ગદર્શન આપશે. જો તમે હમણાં જ અભ્યાસક્રમ સાથે પ્રારંભ કરી રહ્યાં છો, તો અમે ભલામણ કરીએ છીએ કે તમે પહેલા [પ્રકરણ 1](https://github.com/huggingface/course/blob/main/course/chapter1) પર એક નજર નાખો, પછી પાછા આવો અને તમારું Environment સેટ કરો જેથી તમે ‘કોડ’ જાતે અજમાવી શકો. + +આ કોર્સમાં આપણે જે લાઈબ્રેરીનો ઉપયોગ કરીશું તે Python Package તરીકે ઉપલબ્ધ છે, તેથી અહીં અમે તમને બતાવીશું કે Python Environment કેવી રીતે સેટ કરવું અને તમને જોઈતી વિશિષ્ટ લાઈબ્રેરીઓ કેવી રીતે ઇન્સ્ટોલ કરવી. + +Colab Notebook અથવા Python Virtual એન્વાયર્નમેન્ટનો ઉપયોગ કરીને અમે તમારા કામનું વાતાવરણ સેટ કરવાની બે રીતે આવરી લઈશું. તમને જે સૌથી વધુ પસંદ હોય તે ઉપયોગ કરો. નવા નિશાળિયા માટે, અમે ભલામણ કરીએ છીએ કે તમે Colab નોટબુકથી શરૂઆત કરો. + +નોંધ કરો કે અમે Windows System ને આવરી શું નહીં. જો તમે Windows ચલાવી રહ્યાં હોવ, તો અમે તેને અનુસરવા માટે Colab Notebook નો ઉપયોગ કરવાનો સુઝાવ આપીએ છીએ. જો તમે Linux વિતરણ અથવા MacOS નો ઉપયોગ કરી રહ્યાં છો, તો તમે અહીં વર્ણવેલ કોઈપણ અભિગમનો ઉપયોગ કરી શકો છો. + +અલબત્ત મોટાભાગનો આધાર તમારા હગિંગ ફેસ એકાઉન્ટ પર છે. અમે હમણાં એક ખાતું બનાવવાની ભલામણ કરીએ છીએ: [ખાતું અહીં બનાવો](https://huggingface.co/join) + + From 0ccadda064230fea75cac32f1b0dbafc0c57dc0a Mon Sep 17 00:00:00 2001 From: lewtun Date: Tue, 26 Apr 2022 22:31:43 +0200 Subject: [PATCH 10/51] Bump release (#147) --- .github/workflows/build_documentation.yml | 2 +- .github/workflows/build_pr_documentation.yml | 2 +- chapters/en/chapter1/4.mdx | 2 +- chapters/fa/_toctree.yml | 2 + chapters/fa/chapter2/3.mdx | 278 ++++++++++++++ chapters/fa/glossary/1.mdx | 28 +- chapters/fr/_toctree.yml | 4 + chapters/fr/chapter3/1.mdx | 28 ++ chapters/fr/chapter3/2.mdx | 376 +++++++++++++++++++ chapters/ja/_toctree.yml | 5 + chapters/ja/chapter1/1.mdx | 52 +++ chapters/zh-CN/_toctree.yml | 23 ++ chapters/zh-CN/chapter1/1.mdx | 52 +++ chapters/zh-CN/chapter1/10.mdx | 253 +++++++++++++ chapters/zh-CN/chapter1/2.mdx | 20 + chapters/zh-CN/chapter1/3.mdx | 287 ++++++++++++++ chapters/zh-CN/chapter1/4.mdx | 172 +++++++++ chapters/zh-CN/chapter1/5.mdx | 17 + chapters/zh-CN/chapter1/6.mdx | 17 + chapters/zh-CN/chapter1/7.mdx | 16 + chapters/zh-CN/chapter1/8.mdx | 31 ++ chapters/zh-CN/chapter1/9.mdx | 11 + 22 files changed, 1658 insertions(+), 20 deletions(-) create mode 100644 chapters/fa/chapter2/3.mdx create mode 100644 chapters/fr/chapter3/1.mdx create mode 100644 chapters/fr/chapter3/2.mdx create mode 100644 chapters/ja/chapter1/1.mdx create mode 100644 chapters/zh-CN/_toctree.yml create mode 100644 chapters/zh-CN/chapter1/1.mdx create mode 100644 chapters/zh-CN/chapter1/10.mdx create mode 100644 chapters/zh-CN/chapter1/2.mdx create mode 100644 chapters/zh-CN/chapter1/3.mdx create mode 100644 chapters/zh-CN/chapter1/4.mdx create mode 100644 chapters/zh-CN/chapter1/5.mdx create mode 100644 chapters/zh-CN/chapter1/6.mdx create mode 100644 chapters/zh-CN/chapter1/7.mdx create mode 100644 chapters/zh-CN/chapter1/8.mdx create mode 100644 chapters/zh-CN/chapter1/9.mdx diff --git a/.github/workflows/build_documentation.yml b/.github/workflows/build_documentation.yml index 429dbafeb..a31409914 100644 --- a/.github/workflows/build_documentation.yml +++ b/.github/workflows/build_documentation.yml @@ -14,6 +14,6 @@ jobs: package: course path_to_docs: course/chapters/ additional_args: --not_python_module - languages: ar bn de en es fa fr gj he hi ja ko pt ru th tr zh + languages: ar bn de en es fa fr gj he hi ja ko pt ru th tr zh-CN secrets: token: ${{ secrets.HUGGINGFACE_PUSH }} diff --git a/.github/workflows/build_pr_documentation.yml b/.github/workflows/build_pr_documentation.yml index 2a6c8a860..11c70e24e 100644 --- a/.github/workflows/build_pr_documentation.yml +++ b/.github/workflows/build_pr_documentation.yml @@ -16,5 +16,5 @@ jobs: package: course path_to_docs: course/chapters/ additional_args: --not_python_module - languages: ar bn de en es fa fr gj he hi ja ko pt ru th tr zh + languages: ar bn de en es fa fr gj he hi ja ko pt ru th tr zh-CN hub_base_path: https://moon-ci-docs.huggingface.co/course diff --git a/chapters/en/chapter1/4.mdx b/chapters/en/chapter1/4.mdx index 255d37f6e..6d286a42e 100644 --- a/chapters/en/chapter1/4.mdx +++ b/chapters/en/chapter1/4.mdx @@ -156,7 +156,7 @@ The original Transformer architecture looked like this, with the encoder on the
-Note that the the first attention layer in a decoder block pays attention to all (past) inputs to the decoder, but the second attention layer uses the output of the encoder. It can thus access the whole input sentence to best predict the current word. This is very useful as different languages can have grammatical rules that put the words in different orders, or some context provided later in the sentence may be helpful to determine the best translation of a given word. +Note that the first attention layer in a decoder block pays attention to all (past) inputs to the decoder, but the second attention layer uses the output of the encoder. It can thus access the whole input sentence to best predict the current word. This is very useful as different languages can have grammatical rules that put the words in different orders, or some context provided later in the sentence may be helpful to determine the best translation of a given word. The *attention mask* can also be used in the encoder/decoder to prevent the model from paying attention to some special words -- for instance, the special padding word used to make all the inputs the same length when batching together sentences. diff --git a/chapters/fa/_toctree.yml b/chapters/fa/_toctree.yml index 887380b16..2a7532348 100644 --- a/chapters/fa/_toctree.yml +++ b/chapters/fa/_toctree.yml @@ -14,6 +14,8 @@ title: مقدمه - local: chapter2/2 title: پشت صحنه خط تولید + - local: chapter2/3 + title: مدل‌ها - title: ۴- به اشتراک‌گذاری مدل‌ها و توکِنایزرها sections: diff --git a/chapters/fa/chapter2/3.mdx b/chapters/fa/chapter2/3.mdx new file mode 100644 index 000000000..0155c5634 --- /dev/null +++ b/chapters/fa/chapter2/3.mdx @@ -0,0 +1,278 @@ + + +
+# مدل‌ها + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + +{:else} + +{/if} + +{#if fw === 'pt'} + +در این بخش نگاهی نزدیک‌تر به ساخت و استفاده از مدل‌ها می‌اندازیم. کلاس `AutoModel` را به کار خواهیم گرفت که برای ساختن مدل‌ها از نقطه‌های تعلیم مشخص، بسیار پرکاربرد است. + +کلاس `AutoModel` و تمامی کلاس‌های شبیه آن، در واقع پوسته‌ای ساده روی انواع مختلف مدل‌های موجود در کتابخانه هستند. پوسته‌ای هوشمند که می‌تواند به صورت خودکار معماری مدل استفاده شده در نقطه‌های تعلیم را تشخیص دهد و سپس مدلی با آن معماری بسازد. + +{:else} + +در این بخش نگاهی نزدیک‌تر به ساخت و استفاده از مدل‌ها می‌اندازیم. کلاس `TFAutoModel` را به کار خواهیم گرفت که برای ساختن مدل‌ها از نقطه‌های تعلیم مشخص، بسیار پرکاربرد است. + +کلاس `TFAutoModel` و تمامی کلاس‌های شبیه آن، در واقع پوسته‌ای ساده روی انواع مختلف مدل‌های موجود در کتابخانه هستند. پوسته‌ای هوشمند که می‌تواند به صورت خودکار معماری مدل استفاده شده در نقطه‌های تعلیم را تشخیص دهد و سپس مدلی با آن معماری بسازد. + +{/if} + +با این وجود، اگر نوع مدلی که می‌خواهید استفاده کنید را می‌دانید، می‌توانید مستقیماً کلاسی که معماری خاص آن مدل را تعریف می‌کند به کار ببرید. نگاهی به چگونگی انجام این عملیات با مدل 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) +``` + +```python out +BertConfig { + [...] + "hidden_size": 768, + "intermediate_size": 3072, + "max_position_embeddings": 512, + "num_attention_heads": 12, + "num_hidden_layers": 12, + [...] +} +``` + +
+ +با وجود اینکه هنوز ندیده‌اید تک تک این ویژگی‌ها چه تاثیری دارند، بعضی از آنها برای شما آشنا هستند: ویژگی `hidden_size` اندازه بردار `hidden_states` را مشخص می‌کند و ویژگی `num_hidden_layers` مشخص کننده تعداد لایه‌های مدل ترنسفورمر است. + +### روش‌های مختلف بارگذاری + +ساختن مدل با تنظیمات پیش‌فرض، باعث مقداردهی اولیه وزن‌های آن با اعداد تصادفی می‌شود. + +
+ +{#if fw === 'pt'} +```py +from transformers import BertConfig, BertModel + +config = BertConfig() +model = BertModel(config) + +# Model is randomly initialized! +``` +{:else} +```py +from transformers import BertConfig, TFBertModel + +config = BertConfig() +model = TFBertModel(config) + +# Model is randomly initialized! +``` +{/if} + +
+ +می‌توان از مدل در این وضعیت استفاده کرد ولی خروجی آن بی‌معنی خواهد بود؛ ابتدا باید مدل را تعلیم دهیم. می‌توانیم مدل را از صفر برای مسئله مورد نظرمان تعلیم دهیم ولی همان گونه که در [فصل اول](/course/chapter1) دیدیم، برای این کار نیاز به زمان طولانی و داده بسیار داریم. این عملیات تاثیرات منفی غیر قابل چشم‌پوشی‌‌ای بر محیط‌زیست دارد. برای جلوگیری از دوباره‌کاری باید بتوانیم مدل‌های از پیش تعلیم دیده را به اشتراک گذاشته و به کار ببریم. + +بارگذاری مدل از پیش تعلیم دیده ترنسفورمر، ساده است. برای این کار از تابع `from_pretrained()` استفاده‌‌ می‌کنیم. + +{#if fw === 'pt'} + +
+ +```py +from transformers import BertModel + +model = BertModel.from_pretrained("bert-base-cased") +``` +
+ +همان طور که قبلا دیدید، می‌توانیم کلاس `BertModel` را با کلاس معادل `AutoModel` جایگزین کنیم. از این پس همین کار را خواهیم کرد چون به این صورت کد وابسته به نقطه تعلیم خاصی نخواهد بود. اگر کد شما با یک نقطه تعلیم اجرا می‌شود، بدون تغییر با نقاط تعلیم دیگر هم اجرا خواهد شد. این حتی در مورد معماری‌های متفاوت هم صدق می‌کند، البته در صورتی که نقطه تعلیم متعلق به مسئله مشابهی(برای مثال تحلیل احساسات) باشد. + +{:else} + +
+ +```py +from transformers import TFBertModel + +model = TFBertModel.from_pretrained("bert-base-cased") +``` + +
+ +همان طور که قبلا دیدید، می‌توانیم کلاس `TFBertModel` را با کلاس معادل `TFAutoModel` جایگزین کنیم. از این پس همین کار را خواهیم کرد چون به این صورت کد وابسته به نقطه تعلیم خاصی نخواهد بود. اگر کد شما با یک نقطه تعلیم اجرا می‌شود، بدون تغییر با نقاط تعلیم دیگر هم اجرا خواهد شد. این حتی در مورد معماری‌های متفاوت هم صدق می‌کند، البته در صورتی که نقطه تعلیم متعلق به مسئله مشابهی(برای مثال تحلیل احساسات) باشد. + +{/if} + +در کد نمونه بالا، کلاس `BertConfig` را به کار نبرده و در عوض از مدلی از پیش تعلیم دیده با شناسه `bert-base-cased` استفاده کردیم؛ نقطه تعلیم مدلی که توسط خود مؤلفان مدل BERT تعلیم دیده است. اطلاعات بیشتر در مورد این نقطه تعلیم را می‌توانید در [صفحه توضیحات](https://huggingface.co/bert-base-cased) آن ببینید. + +این مدل اکنون با وزن‌های نقطه تعلیم پر شده است و می‌توان آن را مستقیماً برای مسائلی که برای آن تعلیم دیده به کار برد یا برای مسئله جدیدی کوک کرد. با تعلیم روی وزن‌های از پیش تعلیم دیده، به جای تعلیم از صفر، می‌توانیم به سرعت به نتایج خوبی دست پیدا کنیم. + +این وزن‌ها دانلود و در پوشه‌ای مخصوص انبار شده‌اند، تا اجرای تابع `from_pretrained()` در آینده مسبب دانلود دوباره‌اشان نباشد. این پوشه به صورت پیش‌فرض در آدرس *~/.cache/huggingface/transformers* قرار دارد. شما می‌توانید با تخصیص مقدار به متغیر محیطی `HF_HOME` مکان این پوشه را تغییر دهید. + +شناسه هر مدلی در هاب مدل‌ها را می‌توانید برای بارگذاری استفاده کنید. البته در صورتی که آن مدل با معماری BERT سازگاری داشته باشد. فهرست کامل تمام نقاط تعلیم سازگار با BERT را [اینجا مشاهده کنید](https://huggingface.co/models?filter=bert). + +### روش‌های ذخیره‌سازی + +ذخیره‌سازی مدل به سادگی بارگذاری آن است. از تابع `save_pretrained()` استفاده می‌کنیم که متناظر با تابع `from_pretrained()` است: + +
+ +```py +model.save_pretrained("directory_on_my_computer") +``` + +
+ +اجرای این تابع باعث ذخیره شدن دو فایل در سیستم شما می‌شود: + +
+ +{#if fw === 'pt'} +``` +ls directory_on_my_computer + +config.json pytorch_model.bin +``` +{:else} +``` +ls directory_on_my_computer + +config.json tf_model.h5 +``` +{/if} + +
+ +اگر نگاهی به فایل *config.json* بیاندازید، با ویژگی‌های آشنای مورد نیاز برای ساختن معماری مدل روبرو خواهید شد. این فایل حاوی مقادیری متادیتا، مانند نقطه مادر این نقطه تعلیم و نسخه‌‌ کتابخانه ترنسفورمرهای هاگینگ‌فِیس که آخرین بار برای ذخیره‌سازی این نقطه به کار رفته است، می‌باشد. + +{#if fw === 'pt'} + +فایل *pytorch_model.bin* در واقع *دیکشنری وضعیت‌ها* است و حاوی تمام وزن‌های مدل شماست. این دو فایل به همراه هم کاربرد دارند؛ فایل تنظیمات برای دانستن معماری به کار رفته در مدل ضروری است و پارامترهای مدل هم که همان وزن‌های داخل فایل دوم هستند. + +{:else} + +فایل *tf_model.h5* در واقع *دیکشنری وضعیت‌ها* است و حاوی تمام وزن‌های مدل شماست. این دو فایل به همراه هم کاربرد دارند؛ فایل تنظیمات برای دانستن معماری به کار رفته در مدل ضروری است و پارامترهای مدل هم که همان وزن‌های داخل فایل دوم هستند. + +{/if} + +## اجرای یک مدل ترنسفورمر + +حالا که می‌دانید چگونه مدل‌ها را ذخیره‌سازی و بارگذاری کنید، می‌توانیم آنها را برای پیش‌بینی به کار بگیریم. مدل‌های ترنسفورمر فقط می‌توانند اعداد را پردازش کنند؛ اعدادی که توکِنایزر تولید نموده است. ولی پیش از آن که سراغ بحث توکِنایزرها برویم، ورودی‌های قابل قبول برای مدل‌ها را بررسی می‌کنیم. + +توکِنایزرها می‌توانند ورودی‌ها را به تِنسورهای مخصوص هر فریمورک تبدیل کنند ولی برای آنکه درست متوجه آنچه اتفاق می‌افتد شویم، نگاهی کوتاه به کارهایی که باید قبل از فرستادن ورودی‌ها به مدل انجام شود می‌اندازیم. + +تصور کنید چند جمله به این صورت داریم: + +
+ +```py +sequences = ["Hello!", "Cool.", "Nice!"] +``` + +
+ +توکِنایزر این جملات را به اندیس‌های مخصوص کلمات که معمولا به آنها *شناسه‌های ورودی* می‌گوییم، تبدیل می‌کند. هر رشته اکنون لیستی از اعداد است! نتیجه خروجی از این قرار است: + +
+ +```py no-format +encoded_sequences = [ + [101, 7592, 999, 102], + [101, 4658, 1012, 102], + [101, 3835, 999, 102], +] +``` + +
+ +این خروجی لیستی از رشته‌های کد شده شده است: لیستی از لیست‌ها. تِنسورها تنها مقادیر به شکل مستطیل(همان ماتریس) را می‌پذیرند. این «آرایه» خود شکل مستطیلی دارد پس تبدیل آن به تِنسور ساده است: + +
+ +{#if fw === 'pt'} +```py +import torch + +model_inputs = torch.tensor(encoded_sequences) +``` +{:else} +```py +import tensorflow as tf + +model_inputs = tf.constant(encoded_sequences) +``` +{/if} + +
+ +### استفاده از تِنسور‌ها به عنوان ورودی مدل + +به کار بردن تِنسورها به عنوان ورودی مدل بسیار ساده است؛ تنها آرگومان تِنسور ورودی را به صورت زیر به مدل پاس می‌دهیم: + +
+ +```py +output = model(model_inputs) +``` + +
+ +این تابع آرگومان‌های بسیاری را می‌پذیرد ولی تنها شناسه‌های ورودی ضروری هستند. کاربرد آرگومان‌های دیگر و شرایط ضرورت آنها را بعد توضیح خواهیم داد. ولی ابتدا باید نگاهی نزدیک‌تر به توکِنایزرهایی که ورودی‌های قابل فهم مدل‌های ترنسفورمر را می‌سازند، بیاندازیم. +
diff --git a/chapters/fa/glossary/1.mdx b/chapters/fa/glossary/1.mdx index b61fcf6de..c3895d1d3 100644 --- a/chapters/fa/glossary/1.mdx +++ b/chapters/fa/glossary/1.mdx @@ -23,7 +23,7 @@ | Share | به اشتراک‌گذاری / همرسانی | | Library | کتابخانه | | Download | دانلود | -| Inference | اجرا؟* | +| Inference | اجرا؟ | | Class | کلاس | | Module | ماژول | | Abstraction | انتزاع انتزاعات | @@ -109,7 +109,7 @@ | Index, as in an array or list | اندیس | | Project, as in math | پروجکت؟ | | Embedding | embedding? | -| Tokenized | توکِنیزه؟ قطعه قطعه شده؟ | +| Tokenized | توکِن‌شده | | Attention Mechanism | مکانیزم توجه | | Classification | دسته‌بندی | | Attribute, as for a class in code | ویژگی؟ | @@ -120,8 +120,14 @@ | Loss Function | تابع هزینه | | Activation Layer | لایه فعال‌سازی | | Cross Entropy | آنتروپی متقابل | - -معادل‌هایی که استفاده نمی‌کنیم: +| Head, as in model | سَر | +| Weight, as in model | وزن | +| Weights, as in model | وزون؟ | +| Set, az for variable | تخصیص مقدار | +| Environment Variable | متغیر محیطی | +| Metadata | متادیتا | +| Encode, not as in cypher | کد شده؟، کد گذاری؟ | +| Cache | انبار کردن | معادل‌هایی که استفاده نمی‌کنیم: @@ -133,10 +139,6 @@ |-------------------------------| | ارائه | -| املای مورد استفاده کلمات فارسی | -|-------------------------------| -| ارائه | - کلمات مخفف: @@ -146,15 +148,7 @@ | API | API | | GPU | GPU | | TPU | TPU | +| BERT | BERT | | ML | یادگیری ماشین | - -املای مورد استفاده کلمات فارسی: - -|ارائه| -
- -- * Used in chapter1/page2 to loosely mean 'running' the model. - If used anywhere else to exact technical meaning we could go - with 'استنتاج' probably? But that sounds archaic to me. diff --git a/chapters/fr/_toctree.yml b/chapters/fr/_toctree.yml index 20f72bd85..d4b02ce84 100644 --- a/chapters/fr/_toctree.yml +++ b/chapters/fr/_toctree.yml @@ -49,6 +49,10 @@ - title: 3. Finetuner un modèle pré-entraîné sections: + - local: chapter3/1 + title: Introduction + - local: chapter3/2 + title: Traîter les données - local: chapter3/3 title: Finetuner un modèle avec l'API Trainer API ou Keras local_fw: { pt: chapter3/3, tf: chapter3/3_tf } diff --git a/chapters/fr/chapter3/1.mdx b/chapters/fr/chapter3/1.mdx new file mode 100644 index 000000000..9c7960f54 --- /dev/null +++ b/chapters/fr/chapter3/1.mdx @@ -0,0 +1,28 @@ + + +# Introduction + +Dans le [chapitre 2](/course/fr/chapter2) nous avons étudié comment utiliser + les tokenizers et les modèles pré-entraînés pour faire des prédictions. Et si + vous voulez faire le fine-tuning à partir d'un modèle pré-entraîné pour votre +propre jeu de données (dataset)? C'est le sujet de ce chapitre! Vous allez apprendre : + + + +{#if fw === 'pt'} +* Comment préparer un très grand jeu de données à partir du Hub +* Comment utiliser 'Trainer', l'API de haut niveau, pour faire le fine-tuning d'un modèle +* Comment utiliser une boucle d'entraînement personnalisée +* Comment exploiter la librairie Accelerate proposée par 🤗 pour exécuter facilement cette boucle d'entraînement personnalisée sur n'importe quel +configuration distribuée + +{:else} +* Comment préparer un très grand jeu de données à partir du Hub +* Comment utiliser Keras pour faire le fine-tuning d'un modèle +* Comment utiliser Keras pour obtenir des prédictions +* Comment utiliser des métriques personnalisées + + +{/if} + +Pour mettre à disposition vos checkpoints d'entraînement sur le Hub d'Hugging Face, vous aurez besoin d'un compte huggingface.com : [créer un compte](https://huggingface.co/join) diff --git a/chapters/fr/chapter3/2.mdx b/chapters/fr/chapter3/2.mdx new file mode 100644 index 000000000..803493cdc --- /dev/null +++ b/chapters/fr/chapter3/2.mdx @@ -0,0 +1,376 @@ + + +# Préparer les données + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} +Continuons avec [l'exemple précédent](/course/chapter2), voilà comment on entraîne un classifieur de séquences sur un batch avec PyTorch : +s +```python +import torch +from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification + +# Same as before +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "This course is amazing!", +] +batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") + +# This is new +batch["labels"] = torch.tensor([1, 1]) + +optimizer = AdamW(model.parameters()) +loss = model(**batch).loss +loss.backward() +optimizer.step() +``` +{:else} +Continuons avec [l'exemple précédent](/course/chapter2), voilà comment on entraîne un classifieur de séquences sur un batch avec TensorFlow : + +```python +import tensorflow as tf +import numpy as np +from transformers import AutoTokenizer, TFAutoModelForSequenceClassification + +# Same as before +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "This course is amazing!", +] +batch = dict(tokenizer(sequences, padding=True, truncation=True, return_tensors="tf")) + +# This is new +model.compile(optimizer="adam", loss="sparse_categorical_crossentropy") +labels = tf.convert_to_tensor([1, 1]) +model.train_on_batch(batch, labels) +``` +{/if} +Evidemment, entraîner un modèle avec seulement deux phrases ne va pas donner de bons résultats. Pour obtenir de meilleurs résultats, vous allez avoir à préparer un plus grand jeu de données. + +Dans cette section, nous allons utiliser comme exemple le jeu de données (dataset) MRPC (Microsoft Research Paraphrase Corpus) présenté dans un [papier](https://www.aclweb.org/anthology/I05-5002.pdf) de William B. Dolan et Chris Brockett. Ce jeu de données contient 5801 paires de phrases avec un label indiquant si ces paires sont des paraphrases ou non (i.e. si elles ont la même signification). Nous l'avons choisi pour ce chapitre parce que c'est un petit jeu de données, et cela rend donc facile les expériences d'entraînement sur ce dataset. + +### Charger un jeu de données depuis le Hub + +{#if fw === 'pt'} + +{:else} + +{/if} + +Le hub ne contient pas juste des modèles; il contient aussi plusieurs jeux de données dans un tas de langages différents. 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 de Machine Learning sur 10 tâches de classification de textes. + +La librairie de datasets de 🤗 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 : + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("glue", "mrpc") +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 408 + }) + test: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 1725 + }) +}) +``` + +Comme vous le voyez, on obtient un objet de type `DatasetDict` qui contient le jeu de données d'entraînement, celui de validation et celui de test. Chacun d'eux contient plusieurs colonnes (`sentence1`, `sentence2`, `label` et `idx`) et une variable nombre de lignes qui contient le nombre d'éléments dans chaque jeu de données (il y a donc 3.668 paires de phrases dans le jeu d'entraînement, 408 dans celui de validation et 1.725 dans celui de test). + +Cette commande télécharge et met en cache le jeu de données dans *~/.cache/huggingface/dataset*. Rappelez-vous que comme vu au chapitre 2, vous pouvez personnaliser votre dossier cache en modifiant la variable d'environnement `HF_HOME`. + +Nous pouvons accéder à chaque paire de phrase de notre objet `raw_datasets` par les indices, comme avec un dictionnaire : + +```py +raw_train_dataset = raw_datasets["train"] +raw_train_dataset[0] +``` + +```python out +{'idx': 0, + 'label': 1, + 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', + 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} +``` +Nous pouvons voir que les labels sont déjà des entiers, nous n'avons donc aucun preprocessing à faire sur les labels. Pour savoir quel entier correspond à quel label, on peut inspecter les `features` de notre `raw_train_dataset`. Cela nous dira le type de chaque colonne : + +```py +raw_train_dataset.features +``` + +```python out +{'sentence1': Value(dtype='string', id=None), + 'sentence2': Value(dtype='string', id=None), + 'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None), + 'idx': Value(dtype='int32', id=None)} +``` + +En réalité, `label` est de type `ClassLabel`, et la correspondance des entiers aux noms des labels est enregistrée le dossier *names*. `0` correspond à `not_equivalent` (pas équivalent), et `1` correspond à `equivalent`. + + + +✏️ **Essayez ceci!** Regardez l'élément 15 du jeu d'entraînement et l'élément 87 du jeu de validation. Quels sont leurs labels? + + + +### Preprocessing d'un jeu de donnée + +{#if fw === 'pt'} + +{:else} + +{/if} + +Pour prétraiter l'ensemble de données, nous devons convertir le texte en nombres que le modèle peut comprendre. Comme vous l'avez vu dans le [chapitre précédent](/course/chapter2), cela se fait avec un tokenizer. Nous pouvons passer au tokenizer une phrase ou une liste de phrases, ainsi nous pouvons directement tokeniser toutes les premières phrases et toutes les deuxièmes phrases de chaque paire comme ceci : + +```py +from transformers import AutoTokenizer + +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"]) +tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"]) +``` + +Néanmoins, nous ne pouvons pas simplement transmettre deux séquences au modèle et obtenir une prédiction indiquant si les deux phrases sont des paraphrases ou non. Nous devons gérer les deux séquences comme une paire et appliquer le prétraitement approprié. Heureusement, le tokenizer peut également prendre une paire de séquences et la mettre dans le format attendu par notre modèle BERT : + +```py +inputs = tokenizer("This is the first sentence.", "This is the second one.") +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] +} +``` + +Nous avons vu les clés `input_ids` et `attention_mask` au [chapitre 2](/course/chapter2), mais nous avons omis de parler des `token_type_ids`. Dans cet exemple, c'est ce qui indique au modèle quelle partie de l'entrée représente la première phrase et quelle partie représente la deuxième phrase. + + + +✏️ **Essayez ceci!** Prenez l'élément 15 de l'ensemble d'entraînement et tokenisez les deux phrases séparément et comme une paire. Quelle est la différence entre les deux résultats ? + + + +Si nous décodons les IDs contenus dans `input_ids` pour réobtenir des mots : + +```py +tokenizer.convert_ids_to_tokens(inputs["input_ids"]) +``` + +Nous obtiendrons : + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +``` + +Nous pouvons donc voir que le modèle attend une entrée de la forme `[CLS] sentence1 [SEP] sentence2 [SEP]` lorsqu'il y a deux phrases. Aligner cette représentation avec `token_type_ids` nous donne : + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +[ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] +``` + +Comme vous pouvez le voir, les parties correspondant à `[CLS] sentence1 [SEP]` ont toutes `0` comme ID de type de token, alors que les autres parties qui correspondent à `sentence2 [SEP]`, ont toute `1` comme ID de type de token. + +Notez que si vous sélectionnez un checkpoint différent, vous n'aurez pas nécessairement les `token_type_ids` dans vos entrées tokenisées (par exemple, ils ne sont pas retournés lorsque vous utilisez le modèle DistilBERT). Ils ne sont renvoyés que lorsque le modèle sait les utiliser, pour les avoir vu pendant le pré-entraînement. + +Ici, BERT est pré-entraîné avec des IDs de type de token, et en plus de la fonction de coût MLM (Masked Language Modelling) dont nous avons parlé au [chapitre 1](/course/chapter1), il a un coût supplémentaire appelé _prédiction de la phrase suivante_(_next sentence prediction_). Le but de cette tâche est de modéliser la relation entre des paires de phrases. + +Pour la prédiction de la phrase suivante, le modèle reçoit des paires de phrases (avec des tokens masqués de manière aléatoire) et on lui demande de prédire si la deuxième phrase suit la première. Pour rendre la tâche non triviale, la moitié du temps les phrases se suivent dans le document original dont elles sont extraites, et l'autre moitié du temps les deux phrases proviennent de deux documents différents. + +En général, vous n'avez pas à vous soucier de savoir s'il y a ou non des `token_type_ids` dans vos entrées tokenisées : tant que vous utilisez le même checkpoint pour le tokenizer et le modèle, tout ira bien car le tokenizer sait quoi fournir à son modèle. + +Maintenant que nous avons vu comment notre tokenizer peut traiter une paire de phrases, nous pouvons l'utiliser pour tokeniser l'ensemble de nos données : comme dans le [chapitre précédent](/course/chapter2), nous pouvons fournir au tokenizer une liste de paires de phrases en lui donnant la liste des premières phrases, puis la liste des secondes phrases. Ceci est également compatible avec les options de padding et de troncature que nous avons vues au [chapitre 2](/course/chapter2). Ainsi, une façon de prétraiter l'ensemble de données d'entraînement est : + +```py +tokenized_dataset = tokenizer( + raw_datasets["train"]["sentence1"], + raw_datasets["train"]["sentence2"], + padding=True, + truncation=True, +) +``` + +Cela marche bien, mais a le désavantage 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 marchera que si vous avez assez de RAM pour contenir tout le jeu de données pendant la tokenisation (tandis que les jeux de données de la librairie de 🤗 sont des fichiers [Apache Arrow](https://arrow.apache.org/) chargées sur le disque, de sorte que vous ne gardez que les exemples dont vous avez besoin en mémoire ) + +Pour garder les données sous forme de dataset (jeu de données), nous allons utiliser la méthode [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map). Cela nous permet d'avoir plus de flexibilité si nous avons besoin de faire plus que juste tokeniser pendant la phase de pré-traîtement. La méthode `map()` fonctionne en applicant une fonction à chaque élément du dataset, définissons alors une fonction qui tokenise nos entrées : + +```py +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) +``` + +Cette prend en entrée un dictionnaire (comme les items de notre dataset) and renvioe un nouveau dictionnaire avec les clés `input_ids`, `attention_mask` et `token_type_ids`. Notez que cela marche aussi si le dictionnaire `example` contient plusieurs exemples puisque le tokenizer fonctionne aussi sur des listes de paires de phrases, comme nous l'avons vu précédemment. Cela permettra d'utiliser l'option `batched=True` dans notre appel de la méthode `map()`, ce qui permettra d'accélérer la tokenisation. Le `tokenizer` est aidé par un tokenizer écrit en Rust provenant de la librairie [Tokenizers](https://github.com/huggingface/tokenizers) de 🤗. Ce tokenizer peut être très rapide, mais seulement si on lui donne beaucoup d'entrées en même temps. + +Notez que nous avons ignorer l'argument `padding` dans notre fonction de tokenisation pour l'instant. Ceci parce que faire le padding de tous les exemples à la longueur maximale n'est pas efficace : il vaut mieux faire le padding lorsque nous construisons un batch, puisque dans ce cas nous allons seulement faire le padding pour la longueur maximale dans ce batch, et non pour la longueur maximale de tout le dataset. Cela permet d'économiser beaucoup de temps et de puissance de calcul lorsqu'on traite des entrées avec des longueurs trés variées! + +Voilà comment on applique la fonction de tokenisation à tous nos datasets en même temps. Nous utilisons `batched=True` dans notre appel de `map` pour que la fonction soit appliquée à plusieurs éléments de notre dataset en même temps, et non sur chaque élément séparément. Cela permet d'avoir un pré-traîtement plus rapide. + +```py +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) +tokenized_datasets +``` + +La librairie Datasets de 🤗 applique le processing en ajoutant de nouveaux champs aux datasets, un nouveau pour chaque clé retournée par la fonction de preprocessing : + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 408 + }) + test: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 1725 + }) +}) +``` + +Vous pouvez même utiliser le multiprocessing en appliquant votre pré-traîtement avec `map()` en lui passant l'argument `num_proc`. Nous ne l'avons pas utilisé ici parce que la librairie Tokenizers de 🤗 utilise plusieurs threads pour tokeniser nos exemples plus rapidement, mais si vous n'utilisez pas de tokenizer rapide qui s'aide de cette librairie, cela pourrait accélérer votre pré-traîtement. + +Notre `tokenize_function` retourne un dictionnaire avec les clés `input_ids`, `attention_mask` et `token_type_ids`, donc ces trois champs sont ajoutés à toutes les parties (entraînement, validation et test) de notre dataset. Notez que nous aurions aussi pu changer des champs existants si notre pré-traîtement retournait une nouvelle valeur pour une clé qui existait déjà dans le dataset sur lequel nous avons appelé `map()`. + +La dernière chose que nous aurons besoin de faire est le padding de tous les éléments pour que leur longueur atteigne la longueur de la plus longue séquence du batch lorsque nous construisons les batchs — une technique que nous appelons *padding dynamique*. + +### Padding dynamique + + + +{#if fw === 'pt'} +La fonction responsable de mettre en ensemble les éléments pour en faire un batch est appelée *fonction d'assemblage* -*collate function*-. C'est un argument que vous pouvez fournir lorsque vous construisez un `DataLoader`, par défaut il s'agit d'une fonction qui va juste convertir les éléments en type tensor de Pytorch et les concaténer (récursivement si les éléments sont des listes, des tuples ou des dictionnaires). Cela -la concaténation- ne sera pas possible dans notre cas puisque toutes les entrées n'auront pas la même taille. Nous avons volontairement reporter à plus tard le padding, pour n'appliquer que le padding nécessaire pour chaque batch et éviter d'avoir des entrées excessivement longues avec beaucoup de padding. Cela va beaucoup accélérer l'entraînement, notez cependant que si vous faites l'entraînement sur TPU cela peut poser des problèmes — Les TPUs préfèrent une taille fixe même si cela requiert beaucoup de padding. + +{:else} +La fonction responsable de mettre en ensemble les éléments pour en faire un batch est appelée *fonction d'assemblage* -*collate function*-. C'est un argument que vous pouvez fournir lorsque vous construisez un `DataLoader`, par défaut il s'agit d'une fonction qui va juste convertir les éléments en type tf.Tensor et les concaténer (récursivement si les éléments sont des listes, des tuples ou des dictionnaires). Cela -la concaténation- ne sera pas possible dans notre cas puisque toutes les entrées n'auront pas la même taille. Nous avons volontairement reporter à plus tard le padding, pour n'appliquer que le padding nécessaire pour chaque batch et éviter d'avoir des entrées excessivement longues avec beaucoup de padding. Cela va beaucoup accélérer l'entraînement, notez cependant que si vous faites l'entraînement sur TPU cela peut poser des problèmes — Les TPUs préfèrent une taille fixe même si cela requiert beaucoup de padding. + +{/if} +En pratique, pour faire cela, on utilise une fonction d'assemblage qui va mettre la bonne quantité de padding aux éléments du dataset que nous mettre ensemble pour former un batch. Heureusement, la librairie Transformers de 🤗 fournit une telle fonction via `DataCollatorWithPadding`. Elle prend un tokenizer lorsqu'on l'instancie (pour savoir quel token utiliser pour le padding, et aussi s'il faut faire le padding à gauche ou à droite en fonction des attentes du modèle) et va faire le nécessaire: + +{#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} + +Pour tester notre nouveau jouet, prenons quelques éléments de notre jeu d'entraînement avec lesquels nous allons former un batch. Ici, on supprime les colonnes `idx`, `sentence1` et `sentence2` puisque nous n'en aurons pas besoin et qu'elles contiennent des strings ( et nous ne pouvons pas créer des tensors avec des strings) et on regarde la longueur de chaque entrée du batch : + +```py +samples = tokenized_datasets["train"][:8] +samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]} +[len(x) for x in samples["input_ids"]] +``` + +```python out +[50, 59, 47, 67, 59, 50, 62, 32] +``` + +Sans surprise, nous obtenons des éléments de longueurs différentes, allant de 32 à 67. Le padding dynamique signifie que nous allons utiliser le padding sur tous les éléments du batch pour obtenir une longueur de 67, la longueur maximale pour ce batch. Sans le padding dynamique, on appliquerait un padding à tous les éléments pour atteindre la longueur maximale de tout le dataset, ou la longueur maximale que le modèle peut accepter. Vérifions que notre `data_collator` effectue correctement le padding dynamique : + +```py +batch = data_collator(samples) +{k: v.shape for k, v in batch.items()} +``` + +{#if fw === 'tf'} + +```python out +{'attention_mask': TensorShape([8, 67]), + 'input_ids': TensorShape([8, 67]), + 'token_type_ids': TensorShape([8, 67]), + 'labels': TensorShape([8])} +``` + +{:else} + +```python out +{'attention_mask': torch.Size([8, 67]), + 'input_ids': torch.Size([8, 67]), + 'token_type_ids': torch.Size([8, 67]), + 'labels': torch.Size([8])} +``` +Bien! Maintenant que nous sommes passés du texte brut aux batchs que notre modèle peut exploiter, nous sommes prêt à procéder au fine-tuning! + +{/if} + + + +✏️ **Essayez ceci!** Reproduisez le preprocessing sur le dataset GLUE SST-2. Il est un peu différent puisqu'il est composé d'uniques phrases et non de paires de phrases, mais le reste de ce que nous avons fait devrait être similaire. Pour un défi plus corsé, essayez d'écrire une fonction de préprocessing qui marche pour toutes les tâches de GLUE. + + + +{#if fw === 'tf'} + +Maintenant que nous avons notre dataset et notre assembleur de données, nous devons les mettre ensemble. On pourrait charger manuellement les batchs et les assembler, mais cela représente beaucoup de travail, et ce n'est pas très performant non plus. Au lieu de faire cela, il y a une simple méthode qui offre une solution performante à ce problème : `to_tf_dataset()`. Cette méthode va passer `tf.data.Dataset` sur votre dataset, avec une fonction d'assemblage optionnelle. `tf.data.Dataset` est un format natif de Tensorflow que Keras peut utiliser avec `model.fit()`, donc cette méthode à elle seule convertit un dataset de 🤗 à un format prêt pour l'entraînement. Voyons la à l'œuvre avec notre dataset! + +```py +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, +) +``` + +Et c'est tout! Nous pouvons utiliser ces datasets dans le prochain cours, où l'entraînement va être agréablement facile après tout le difficile travail de préprocessing. + +{/if} diff --git a/chapters/ja/_toctree.yml b/chapters/ja/_toctree.yml index e18d55704..ce0970fad 100644 --- a/chapters/ja/_toctree.yml +++ b/chapters/ja/_toctree.yml @@ -2,3 +2,8 @@ sections: - local: chapter0/1 title: イントロダクション + +- title: 1. Transformerモデルについて + sections: + - local: chapter1/1 + title: イントロダクション \ No newline at end of file diff --git a/chapters/ja/chapter1/1.mdx b/chapters/ja/chapter1/1.mdx new file mode 100644 index 000000000..5e1dd3a31 --- /dev/null +++ b/chapters/ja/chapter1/1.mdx @@ -0,0 +1,52 @@ +# イントロダクション + +## 🤗 コースへようこそ! + + + +このコースでは、[Hugging Face](https://huggingface.co/)のエコシステムを形成するライブラリである[🤗 Transformers](https://github.com/huggingface/transformers)、[🤗 Datasets](https://github.com/huggingface/datasets)、[🤗 Tokenizers](https://github.com/huggingface/tokenizers)、[🤗 Accelerate](https://github.com/huggingface/accelerate)、そして[Hugging Face Hub](https://huggingface.co/models)を使って自然言語処理(NLP)について学習することができます。このコースは、完全に無料で取り組むことができ、広告もありません。 + + +## 何を学ぶことができるのか? + +こちらがこのコースの概要になります: + +
+Brief overview of the chapters of the course. + +
+ +- 第1章から第4章では、🤗 Transformersライブラリのメインコンセプトを紹介します。このパートを終える頃には、Transformerモデルがどのように機能するかを理解し、[Hugging Face Hub](https://huggingface.co/models)にあるモデルを利用し、データセットでfine-tuningを行い、その成果をHub上で共有する方法を身につけることができるでしょう! +- 第5章から第8章では、代表的なNLPタスクに取り掛かる前に、🤗 Datasetsと🤗 Tokenizersの基礎を学びます。このパートを終える頃には、大半のNLPの課題に自分で取り組むことができるようになります。 +- 第9章から第12章では、NLPの範囲にとどまらず、音声処理とコンピュータビジョンのタスクにTransformerモデルをどのように適用できるかを検討します。その過程で、モデルのデモを作成して共有することや本番環境用にモデルを最適化する方法を学ぶことができます。このパートを終える頃には、🤗 Transformersを(ほとんど)全ての機械学習の問題に適用する知識が身についていることでしょう! + +このコースでは: + +* Pythonの知識が必要です +* コースに取り組む前に、深層学習の入門コースである[fast.ai](https://www.fast.ai/)による [Practical Deep Learning for Coders](https://course.fast.ai/)や[DeepLearning.AI](https://www.deeplearning.ai/)が開発したプログラムなどを受講した方がよいでしょう +* [PyTorch](https://pytorch.org/)や[TensorFlow](https://www.tensorflow.org/)の事前知識は必須ではありませんが、どちらかに精通していると理解がより促進されるでしょう + +このコースを修了した後は、DeepLearning.AIの[Natural Language Processing Specialization](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh)をご覧いただくことをお勧めします。ナイーブベイズやLSTMなどの従来のNLPモデルを幅広くカバーしており、これらも理解しておいて損はありませんよ! + +## 私たちについて + +筆者のプロフィール: + +**Matthew Carrigan**はHugging Faceの機械学習エンジニアです。アイルランドのダブリンに住んでおり、以前はParse.lyで機械学習エンジニアとして、それ以前はトリニティ・カレッジ・ダブリンでポスドク研究員として働いていました。彼は、既存のアーキテクチャを拡張することでAGI(汎用人工知能)に到達できるとは思っていませんが、ロボットによる不死には大きな期待を寄せています。 + +**Lysandre Debut**はHugging Faceの機械学習エンジニアで、かなり初期の開発段階から🤗 Transformersライブラリに携わってきました。彼の目標は、非常にシンプルなAPIのツールを開発することによって、誰もがNLPにアクセスできるようにすることです。 + +**Sylvain Gugger**はHugging Faceのリサーチエンジニアで、🤗 Transformersライブラリのコアメンテナーの1人です。以前は、fast.aiのリサーチサイエンティストで、Jeremy Howard氏と[Deep Learning for Coders with fastai and PyTorch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)を共同執筆しています。限られたリソースでモデルを高速に学習させる技術を設計・改善することで、深層学習をより身近なものにすることに研究の焦点を置いています。 + +**Merve Noyan**はHugging Faceのデベロッパーアドボケイトであり、誰もが機械学習に取り組めるようなツールの開発とその周辺のコンテンツ作成に取り組んでいます。 + +**Lucile Saulnier**はHugging Faceの機械学習エンジニアで、オープンソースツールの開発および利用のサポートを行っています。また、共同でのモデルの学習やBigScienceなど、自然言語処理の分野で多くの研究プロジェクトに積極的に参加しています。 + +**Lewis Tunstall**はHugging Faceの機械学習エンジニアで、オープンソースツールの開発とより広いコミュニティで利用されるようにすることに注力しています。また、[オライリー出版のTransformersに関する本](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/)の著者の1人です。 + +**Leandro von Werra**はHugging Faceのオープンソースチームの機械学習エンジニアであり、[オライリー出版のTransformersに関する本](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/)の著者の1人です。機械学習全般に関わり、NLPプロジェクトを実運用に移行する経験をこの業界で数年積んでいます。 + +準備はできていますか?この章では、以下のことを学びます: +* `pipeline()`機能を使ったテキスト生成や分類などNLPタスクの取り組み方 +* Transformerのアーキテクチャについて +* エンコーダ、デコーダ、エンコーダ・デコーダのアーキテクチャとユースケースの見分け方 \ No newline at end of file diff --git a/chapters/zh-CN/_toctree.yml b/chapters/zh-CN/_toctree.yml new file mode 100644 index 000000000..453fc5e99 --- /dev/null +++ b/chapters/zh-CN/_toctree.yml @@ -0,0 +1,23 @@ +- title: 1. Transformer 模型 + sections: + - local: chapter1/1 + title: 章节简介 + - local: chapter1/2 + title: 自然语言处理 + - local: chapter1/3 + title: Transformers能做什么? + - local: chapter1/4 + title: Transformers 是如何工作的? + - local: chapter1/5 + title: 编码器模型 + - local: chapter1/6 + title: 解码器模型 + - local: chapter1/7 + title: 序列到序列模型 + - local: chapter1/8 + title: 偏见和局限性 + - local: chapter1/9 + title: 总结 + - local: chapter1/10 + title: 章末小测验 + quiz: 1 \ No newline at end of file diff --git a/chapters/zh-CN/chapter1/1.mdx b/chapters/zh-CN/chapter1/1.mdx new file mode 100644 index 000000000..68e6a14c7 --- /dev/null +++ b/chapters/zh-CN/chapter1/1.mdx @@ -0,0 +1,52 @@ +# 简介 + +## 欢迎来到🤗课程 + + + +本课程将使用 Hugging Face 生态系统中的库——🤗 Transformers、🤗 Datasets、🤗 Tokenizers 和 🤗 Accelerate——以及 Hugging Face Hub 教你自然语言处理 (NLP)。它是完全免费的,并且没有广告。 + + +## 有什么是值得期待的? + +以下是课程的简要概述: + +
+Brief overview of the chapters of the course. + +
+ +- 第 1 章到第 4 章介绍了 🤗 Transformers 库的主要概念。在本课程的这一部分结束时,您将熟悉 Transformer 模型的工作原理,并将了解如何使用 [Hugging Face Hub](https://huggingface.co/models) 中的模型,在数据集上对其进行微调,并在 Hub 上分享您的结果。 +- 第 5 章到第 8 章在深入研究经典 NLP 任务之前,教授 🤗 Datasets和 🤗 Tokenizers的基础知识。在本部分结束时,您将能够自己解决最常见的 NLP 问题。 +- 第 9 章到第 12 章更加深入,探讨了如何使用 Transformer 模型处理语音处理和计算机视觉中的任务。在此过程中,您将学习如何构建和分享模型,并针对生产环境对其进行优化。在这部分结束时,您将准备好将🤗 Transformers 应用于(几乎)任何机器学习问题! + +这个课程: + +* 需要良好的 Python 知识 +* 最好先学习深度学习入门课程,例如[DeepLearning.AI](https://www.deeplearning.ai/) 提供的 [fast.ai实用深度学习教程](https://course.fast.ai/) +* 不需要事先具备 [PyTorch](https://pytorch.org/) 或 [TensorFlow](https://www.tensorflow.org/) 知识,虽然熟悉其中任何一个都会对huggingface的学习有所帮助 + +完成本课程后,我们建议您查看 [DeepLearning.AI的自然语言处理系列课程](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh),其中涵盖了广泛的传统 NLP 模型,如朴素贝叶斯和 LSTM,这些模型非常值得了解! + +## 我们是谁? + +关于作者: + +**Matthew Carrigan** 是 Hugging Face 的机器学习工程师。他住在爱尔兰都柏林,之前在 Parse.ly 担任机器学习工程师,在此之前,他在Trinity College Dublin担任博士后研究员。他不相信我们会通过扩展现有架构来实现 AGI,但无论如何都对机器人充满希望。 + +**Lysandre Debut** 是 Hugging Face 的机器学习工程师,从早期的开发阶段就一直致力于 🤗 Transformers 库。他的目标是通过使用非常简单的 API 开发工具,让每个人都可以使用 NLP。 + +**Sylvain Gugger** 是 Hugging Face 的一名研究工程师,也是 🤗Transformers库的核心维护者之一。此前,他是 fast.ai 的一名研究科学家,他与Jeremy Howard 共同编写了[Deep Learning for Coders with fastai and Py Torch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)。他的主要研究重点是通过设计和改进允许模型在有限资源上快速训练的技术,使深度学习更容易普及。 + +**Merve Noyan** 是 Hugging Face 的开发者倡导者,致力于开发工具并围绕它们构建内容,以使每个人的机器学习平民化。 + +**Lucile Saulnier** 是 Hugging Face 的机器学习工程师,负责开发和支持开源工具的使用。她还积极参与了自然语言处理领域的许多研究项目,例如协作训练和 BigScience。 + +**Lewis Tunstall** 是 Hugging Face 的机器学习工程师,专注于开发开源工具并使更广泛的社区可以使用它们。他也是即将出版的一本书[O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/)的作者之一。 + +**Leandro von Werra** 是 Hugging Face 开源团队的机器学习工程师,也是即将出版的一本书[O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/)的作者之一。他拥有多年的行业经验,通过在整个机器学习堆栈中工作,将 NLP 项目投入生产。 + +你准备好了吗?在本章中,您将学习: +* 如何使用 `pipeline()` 函数解决文本生成、分类等NLP任务 +* 关于 Transformer 架构 +* 如何区分编码器、解码器和编码器-解码器架构和用例 diff --git a/chapters/zh-CN/chapter1/10.mdx b/chapters/zh-CN/chapter1/10.mdx new file mode 100644 index 000000000..23f768115 --- /dev/null +++ b/chapters/zh-CN/chapter1/10.mdx @@ -0,0 +1,253 @@ + + +# 章末小测试 + +这一章涵盖了很多内容! 如果有一些不太明白的地方,请不要担心; 下一章将帮助你了解这些模块在底层是如何工作的。 + +让我们来测试一下你在这一章学到了什么! + +### 1. 探索 Hub 并寻找 `roberta-large-mnli` checkpoint。 它可以完成什么类型的任务? + + +roberta-large-mnli 页面回顾一下." + }, + { + text: "文本分类", + explain: "更准确地说,它对两个句子在三个标签(矛盾、无关、相近)之间的逻辑链接进行分类——这项任务也称为自然语言推理.", + correct: true + }, + { + text: "文本生成", + explain: "点击前往roberta-large-mnli 页面回顾一下." + } + ]} +/> + +### 2. 下面的代码将会返回什么结果? + +```py +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +sentiment-analysis pipeline将会返回这些." + }, + { + text: "它将返回一个生成的文本来完成这句话。", + explain: "这个选项是不对的 — text-generation pipeline将会返回这些.", + }, + { + text: "它将返回代表人员、组织或位置的单词。", + explain: "此外,使用 grouped_entities=True,它会将属于同一实体的单词组合在一起,例如“Hugging Face”。", + correct: true + } + ]} +/> + +### 3. 在此代码示例中...的地方应该填写什么? + +```py +from transformers import pipeline + +filler = pipeline("fill-mask", model="bert-base-cased") +result = filler("...") +``` + + has been waiting for you.", + explain: "这个选项是不对的。 请查看 bert-base-cased 模型卡片,然后再尝试找找错在哪里。" + }, + { + text: "This [MASK] has been waiting for you.", + explain: "正解! 这个模型的mask的掩码是[MASK].", + correct: true + }, + { + text: "This man has been waiting for you.", + explain: "这个选项是不对的。 这个pipeline的作用是填充经过mask的文字,因此它需要在输入的文本中存在mask的token。" + } + ]} +/> + +### 4. 为什么这段代码会无法运行? + +```py +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +result = classifier("This is a course about the Transformers library") +``` + +candidate_labels=[...].", + correct: true + }, + { + text: "这个pipeline需要多个句子,而不仅仅是一个。", + explain: "这个选项是不对的。尽管正确使用时,此pipeline可以同时处理多个句子(与所有其他pipeline一样)。" + }, + { + text: "像往常一样,🤗 Transformers库出故障了。", + explain: "对此,我们不予置评!" + }, + { + text: "该pipeline需要更长的输入; 这个句子太短了。", + explain: "这个选项是不对的。 不过请注意,在这个pipeline处理时,太长的文本将被截断。" + } + ]} +/> + +### 5. “迁移学习”是什么意思? + + + +### 6. 语言模型在预训练时通常不需要标签,这样的说法是否正确。 + + +自监督,这意味着标签是根据输入自动创建的(例如:预测下一个单词或填充一些[MARSK]单词)。", + correct: true + }, + { + text: "错误", + explain: "这不是一个正确的答案。" + } + ]} +/> + + +### 7. 选择最能描述“模型(model)”、“架构(architecture)”和“权重(weights)”的句子。 + + + +### 8. 你将使用以下哪种类型的模型来根据输入的提示生成文本? + + + +### 9. 你会使用哪些类型的模型来生成文本的摘要? + + + +### 10. 你会使用哪一种类型的模型来根据特定的标签对文本输入进行分类? + + + +### 11. 模型中观察到的偏见有哪些可能的来源? + + diff --git a/chapters/zh-CN/chapter1/2.mdx b/chapters/zh-CN/chapter1/2.mdx new file mode 100644 index 000000000..1b5ee0ea6 --- /dev/null +++ b/chapters/zh-CN/chapter1/2.mdx @@ -0,0 +1,20 @@ +# 自然语言处理 + +在进入 Transformer 模型之前,让我们快速概述一下自然语言处理是什么以及我们为什么这么重视它。 + +## 什么是自然语言处理? + +NLP 是语言学和机器学习交叉领域,专注于理解与人类语言相关的一切。 NLP 任务的目标不仅是单独理解单个单词,而且是能够理解这些单词的上下文。 + +以下是常见 NLP 任务的列表,每个任务都有一些示例: + +- **对整个句子进行分类**: 获取评论的情绪,检测电子邮件是否为垃圾邮件,确定句子在语法上是否正确或两个句子在逻辑上是否相关 +- **对句子中的每个词进行分类**: 识别句子的语法成分(名词、动词、形容词)或命名实体(人、地点、组织) +- **生成文本内容**: 用自动生成的文本完成提示,用屏蔽词填充文本中的空白 +- **从文本中提取答案**: 给定问题和上下文,根据上下文中提供的信息提取问题的答案 +- **从输入文本生成新句子**: 将文本翻译成另一种语言,总结文本 + +NLP 不仅限于书面文本。它还解决了语音识别和计算机视觉中的复杂挑战,例如生成音频样本的转录或图像描述。 +## 为什么具有挑战性? + +计算机处理信息的方式与人类不同。例如,当我们读到“我饿了”这句话时,我们很容易理解它的意思。同样,给定两个句子,例如“我很饿”和“我很伤心”,我们可以轻松确定它们的相似程度。对于机器学习 (ML) 模型,此类任务更加困难。文本需要以一种使模型能够从中学习的方式进行处理。而且由于语言很复杂,我们需要仔细考虑必须如何进行这种处理。关于如何表示文本已经做了很多研究,我们将在下一章中介绍一些方法。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter1/3.mdx b/chapters/zh-CN/chapter1/3.mdx new file mode 100644 index 000000000..076263ba4 --- /dev/null +++ b/chapters/zh-CN/chapter1/3.mdx @@ -0,0 +1,287 @@ +# Transformers能做什么? + + + +在本节中,我们将看看 Transformer 模型可以做什么,并使用 🤗 Transformers 库中的第一个工具:pipeline() 函数。 + +👀 看到那个右上角的 在Colab中打开的按钮了吗? 单击它就可以打开一个包含本节所有代码示例的 Google Colab 笔记本。 每一个有实例代码的小节都会有它。 + +如果您想在本地运行示例,我们建议您查看准备. + + +## Transformer被应用于各个方面! +Transformer 模型用于解决各种 NLP 任务,就像上一节中提到的那样。以下是一些使用 Hugging Face 和 Transformer 模型的公司和组织,他们也通过分享他们的模型回馈社区: + +![使用 Hugging Face 的公司](https://huggingface.co/course/static/chapter1/companies.PNG) +[🤗 Transformers 库](https://github.com/huggingface/transformers)提供了创建和使用这些共享模型的功能。[模型中心(hub)](https://huggingface.co/models)包含数千个任何人都可以下载和使用的预训练模型。您还可以将自己的模型上传到 Hub! + + +⚠️ Hugging Face Hub 不限于 Transformer 模型。任何人都可以分享他们想要的任何类型的模型或数据集!创建一个 Huggingface.co 帐户(https://huggingface.co/join)以使用所有可用功能! + + +在深入研究 Transformer 模型的底层工作原理之前,让我们先看几个示例,看看它们如何用于解决一些有趣的 NLP 问题。 + +## 使用pipelines + + +(这里有一个视频,但是国内可能打不开,译者注) + + +🤗 Transformers 库中最基本的对象是 **pipeline()** 函数。它将模型与其必要的预处理和后处理步骤连接起来,使我们能够通过直接输入任何文本并获得最终的答案: + +```python +from transformers import pipeline + +classifier = pipeline("sentiment-analysis") +classifier("I've been waiting for a HuggingFace course my whole life.") +``` +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}] +``` + + +我们也可以多传几句! +```python +classifier( + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!"] +) +``` +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}, + {'label': 'NEGATIVE', 'score': 0.9994558095932007}] +``` +默认情况下,此pipeline选择一个特定的预训练模型,该模型已针对英语情感分析进行了微调。创建**分类器**对象时,将下载并缓存模型。如果您重新运行该命令,则将使用缓存的模型,无需再次下载模型。 + +将一些文本传递到pipeline时涉及三个主要步骤: + +1. 文本被预处理为模型可以理解的格式。 +2. 预处理的输入被传递给模型。 +3. 模型处理后输出最终人类可以理解的结果。 + +目前[可用的一些pipeline](https://huggingface.co/transformers/main_classes/pipelines.html)是: + +* **特征提取**(获取文本的向量表示) +* **填充空缺** +* **ner**(命名实体识别) +* **问答** +* **情感分析** +* **文本摘要** +* **文本生成** +* **翻译** +* **零样本分类** + +让我们来看看其中的一些吧! + +## 零样本分类 +我们将首先处理一项非常具挑战性的任务,我们需要对尚未标记的文本进行分类。这是实际项目中的常见场景,因为注释文本通常很耗时并且需要领域专业知识。对于这项任务**zero-shot-classification**pipeline非常强大:它允许您直接指定用于分类的标签,因此您不必依赖预训练模型的标签。下面的模型展示了如何使用这两个标签将句子分类为正面或负面——但也可以使用您喜欢的任何其他标签集对文本进行分类。 + +```python +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +classifier( + "This is a course about the Transformers library", + candidate_labels=["education", "politics", "business"], +) +``` +```python out +{'sequence': 'This is a course about the Transformers library', + 'labels': ['education', 'business', 'politics'], + 'scores': [0.8445963859558105, 0.111976258456707, 0.043427448719739914]} +``` + +此pipeline称为zero-shot,因为您不需要对数据上的模型进行微调即可使用它。它可以直接返回您想要的任何标签列表的概率分数! + +✏️**快来试试吧!**使用您自己的序列和标签,看看模型的行为。 + + +## 文本生成 +现在让我们看看如何使用pipeline来生成一些文本。这里的主要使用方法是您提供一个提示,模型将通过生成剩余的文本来自动完成整段话。这类似于许多手机上的预测文本功能。文本生成涉及随机性,因此如果您没有得到相同的如下所示的结果,这是正常的。 + +```python +from transformers import pipeline + +generator = pipeline("text-generation") +generator("In this course, we will teach you how to") +``` +```python out +[{'generated_text': 'In this course, we will teach you how to understand and use ' + 'data flow and data interchange when handling user data. We ' + 'will be working with one or more of the most commonly used ' + 'data flows — data flows of various types, as seen by the ' + 'HTTP'}] +``` +您可以使用参数 **num_return_sequences** 控制生成多少个不同的序列,并使用参数 **max_length** 控制输出文本的总长度。 + + +✏️**快来试试吧!**使用 num_return_sequences 和 max_length 参数生成两个句子,每个句子 15 个单词。 + + +## 在pipeline中使用 Hub 中的其他模型 +前面的示例使用了默认模型,但您也可以从 Hub 中选择特定模型以在特定任务的pipeline中使用 - 例如,文本生成。转到[模型中心(hub)](https://huggingface.co/models)并单击左侧的相应标签将会只显示该任务支持的模型。[例如这样](https://huggingface.co/models?pipeline_tag=text-generation)。 + +让我们试试 [**distilgpt2**](https://huggingface.co/distilgpt2) 模型吧!以下是如何在与以前相同的pipeline中加载它: + +```python +from transformers import pipeline + +generator = pipeline("text-generation", model="distilgpt2") +generator( + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, +) +``` +```python out +[{'generated_text': 'In this course, we will teach you how to manipulate the world and ' + 'move your mental and physical capabilities to your advantage.'}, + {'generated_text': 'In this course, we will teach you how to become an expert and ' + 'practice realtime, and with a hands on experience on both real ' + 'time and real'}] +``` +您可以通过单击语言标签来筛选搜索结果,然后选择另一种文本生成模型的模型。模型中心(hub)甚至包含支持多种语言的多语言模型。 + +通过单击选择模型后,您会看到有一个小组件,可让您直接在线试用。通过这种方式,您可以在下载之前快速测试模型的功能。 + +✏️**快来试试吧!**使用标签筛选查找另一种语言的文本生成模型。使用小组件测试并在pipeline中使用它! + + +## 推理 API +所有模型都可以使用 Inference API 直接通过浏览器进行测试,该 API 可在 [Hugging Face 网站](https://huggingface.co/)上找到。通过输入自定义文本并观察模型的输出,您可以直接在此页面上使用模型。 + +小组件形式的推理 API 也可作为付费产品使用,如果您的工作流程需要它,它会派上用场。有关更多详细信息,请参阅[定价页面](https://huggingface.co/pricing)。 + +## Mask filling +您将尝试的下一个pipeline是 **fill-mask**。此任务的想法是填充给定文本中的空白: +```python +from transformers import pipeline + +unmasker = pipeline("fill-mask") +unmasker("This course will teach you all about models.", top_k=2) +``` +```python out +[{'sequence': 'This course will teach you all about mathematical models.', + 'score': 0.19619831442832947, + 'token': 30412, + 'token_str': ' mathematical'}, + {'sequence': 'This course will teach you all about computational models.', + 'score': 0.04052725434303284, + 'token': 38163, + 'token_str': ' computational'}] +``` +**top_k** 参数控制要显示的结果有多少种。请注意,这里模型填充了特殊的< **mask** >词,它通常被称为掩码标记。其他掩码填充模型可能有不同的掩码标记,因此在探索其他模型时要验证正确的掩码字是什么。检查它的一种方法是查看小组件中使用的掩码。 + + +✏️**快来试试吧!**在 Hub 上搜索基于 bert 的模型并在推理 API 小组件中找到它的掩码。这个模型对上面pipeline示例中的句子预测了什么? + + +## 命名实体识别 +命名实体识别 (NER) 是一项任务,其中模型必须找到输入文本的哪些部分对应于诸如人员、位置或组织之类的实体。让我们看一个例子: +```python +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` +```python out +[{'entity_group': 'PER', 'score': 0.99816, 'word': 'Sylvain', 'start': 11, 'end': 18}, + {'entity_group': 'ORG', 'score': 0.97960, 'word': 'Hugging Face', 'start': 33, 'end': 45}, + {'entity_group': 'LOC', 'score': 0.99321, 'word': 'Brooklyn', 'start': 49, 'end': 57} +] +``` +在这里,模型正确地识别出 Sylvain 是一个人 (PER),Hugging Face 是一个组织 (ORG),而布鲁克林是一个位置 (LOC)。 + +我们在pipeline创建函数中传递选项 **grouped_entities=True** 以告诉pipeline将对应于同一实体的句子部分重新组合在一起:这里模型正确地将“Hugging”和“Face”分组为一个组织,即使名称由多个词组成。事实上,正如我们即将在下一章看到的,预处理甚至会将一些单词分成更小的部分。例如,**Sylvain** 分割为了四部分:**S、##yl、##va** 和 **##in**。在后处理步骤中,pipeline成功地重新组合了这些部分。 + + +✏️**快来试试吧!**在模型中心(hub)搜索能够用英语进行词性标注(通常缩写为 POS)的模型。这个模型对上面例子中的句子预测了什么? + + +## 问答系统 +问答pipeline使用来自给定上下文的信息回答问题: +```python +from transformers import pipeline + +question_answerer = pipeline("question-answering") +question_answerer( + question="Where do I work?", + context="My name is Sylvain and I work at Hugging Face in Brooklyn", +) +``` +```python out +{'score': 0.6385916471481323, 'start': 33, 'end': 45, 'answer': 'Hugging Face'} +klyn", +) + +``` +请注意,此pipeline通过从提供的上下文中提取信息来工作;它不会凭空生成答案。 + +## 文本摘要 +文本摘要是将文本缩减为较短文本的任务,同时保留文本中的主要(重要)信息。下面是一个例子: + +```python +from transformers import pipeline + +summarizer = pipeline("summarization") +summarizer( + """ + America has changed dramatically during recent years. Not only has the number of + graduates in traditional engineering disciplines such as mechanical, civil, + electrical, chemical, and aeronautical engineering declined, but in most of + the premier American universities engineering curricula now concentrate on + and encourage largely the study of engineering science. As a result, there + are declining offerings in engineering subjects dealing with infrastructure, + the environment, and related issues, and greater concentration on high + technology subjects, largely supporting increasingly complex scientific + developments. While the latter is important, it should not be at the expense + of more traditional engineering. + + Rapidly developing economies such as China and India, as well as other + industrial countries in Europe and Asia, continue to encourage and advance + the teaching of engineering. Both China and India, respectively, graduate + six and eight times as many traditional engineers as does the United States. + Other industrial countries at minimum maintain their output, while America + suffers an increasingly serious decline in the number of engineering graduates + and a lack of well-educated engineers. +""" +) +``` +```python out +[{'summary_text': ' America has changed dramatically during recent years . The ' + 'number of engineering graduates in the U.S. has declined in ' + 'traditional engineering disciplines such as mechanical, civil ' + ', electrical, chemical, and aeronautical engineering . Rapidly ' + 'developing economies such as China and India, as well as other ' + 'industrial countries in Europe and Asia, continue to encourage ' + 'and advance engineering .'}] +``` +与文本生成一样,您指定结果的 **max_length** 或 **min_length**。 + +## 翻译 +对于翻译,如果您在任务名称中提供语言对(例如“**translation_en_to_fr**”),则可以使用默认模型,但最简单的方法是在[模型中心(hub)](https://huggingface.co/models)选择要使用的模型。在这里,我们将尝试从法语翻译成英语: + +```python +from transformers import pipeline + +translator = pipeline("translation", model="Helsinki-NLP/opus-mt-fr-en") +translator("Ce cours est produit par Hugging Face.") +``` +```python out +[{'translation_text': 'This course is produced by Hugging Face.'}] + +``` + +与文本生成和摘要一样,您可以指定结果的 **max_length** 或 **min_length**。 + + + +✏️**快来试试吧!**搜索其他语言的翻译模型,尝试将前一句翻译成几种不同的语言。 + + + +到目前为止显示的pipeline主要用于演示目的。它们是为特定任务而编程的,不能对他们进行自定义的修改。在下一章中,您将了解 **pipeline()** 函数内部的内容以及如何进行自定义的修改。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter1/4.mdx b/chapters/zh-CN/chapter1/4.mdx new file mode 100644 index 000000000..45641e71e --- /dev/null +++ b/chapters/zh-CN/chapter1/4.mdx @@ -0,0 +1,172 @@ +# Transformers 是如何工作的? + +在本节中,我们将深入了解 Transformer 模型的架构。 + +## 一点Transformers的发展历史 + +以下是 Transformer 模型(简短)历史中的一些关键节点: + +
+A brief chronology of Transformers models. + +
+ +[Transformer 架构](https://arxiv.org/abs/1706.03762) 于 2017 年 6 月推出。原本研究的重点是翻译任务。随后推出了几个有影响力的模型,包括 + +- **2018 年 6 月**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), 第一个预训练的 Transformer 模型,用于各种 NLP 任务并获得极好的结果 + +- **2018 年 10 月**: [BERT](https://arxiv.org/abs/1810.04805), 另一个大型预训练模型,该模型旨在生成更好的句子摘要(下一章将详细介绍!) + +- **2019 年 2 月**: [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), GPT 的改进(并且更大)版本,由于道德问题没有立即公开发布 + +- **2019 年 10 月**: [DistilBERT](https://arxiv.org/abs/1910.01108), BERT 的提炼版本,速度提高 60%,内存减轻 40%,但仍保留 BERT 97% 的性能 + +- **2019 年 10 月**: [BART](https://arxiv.org/abs/1910.13461) 和 [T5](https://arxiv.org/abs/1910.10683), 两个使用与原始 Transformer 模型相同架构的大型预训练模型(第一个这样做) + +- **2020 年 5 月**, [GPT-3](https://arxiv.org/abs/2005.14165), GPT-2 的更大版本,无需微调即可在各种任务上表现良好(称为零样本学习) + +这个列表并不全面,只是为了突出一些不同类型的 Transformer 模型。大体上,它们可以分为三类: + +- GPT-like (也被称作自回归Transformer模型) +- BERT-like (也被称作自动编码Transformer模型) +- BART/T5-like (也被称作序列到序列的 Transformer模型) + +稍后我们将更深入地探讨这些分类。 + +## Transformers是语言模型 + +上面提到的所有 Transformer 模型(GPT、BERT、BART、T5 等)都被训练为语言模型。这意味着他们已经以无监督学习的方式接受了大量原始文本的训练。无监督学习是一种训练类型,其中目标是根据模型的输入自动计算的。这意味着不需要人工来标记数据! + +这种类型的模型可以对其训练过的语言进行统计理解,但对于特定的实际任务并不是很有用。因此,一般的预训练模型会经历一个称为*迁移学习*的过程。在此过程中,模型在给定任务上以监督方式(即使用人工注释标签)进行微调。 + +任务的一个例子是阅读 *n* 个单词的句子,预测下一个单词。这被称为因果语言建模,因为输出取决于过去和现在的输入。 + +
+Example of causal language modeling in which the next word from a sentence is predicted. + +
+ +另一个例子是*遮罩语言建模*,该模型预测句子中的遮住的词。 + +
+Example of masked language modeling in which a masked word from a sentence is predicted. + +
+ +## Transformer是大模型 + +除了一些特例(如 DistilBERT)外,实现更好性能的一般策略是增加模型的大小以及预训练的数据量。 + +
+Number of parameters of recent Transformers models +
+ +不幸的是,训练模型,尤其是大型模型,需要大量的数据,时间和计算资源。它甚至会对环境产生影响,如下图所示。 + +
+The carbon footprint of a large language model. + +
+ + + +Transformers是由一个团队领导的(非常大的)模型项目,该团队试图减少预训练对环境的影响,通过运行大量试验以获得最佳超参数。 + +想象一下,如果每次一个研究团队、一个学生组织或一家公司想要训练一个模型,都从头开始训练的。这将导致巨大的、不必要的浪费! + +这就是为什么共享语言模型至关重要:共享经过训练的权重,当遇见新的需求时在预训练的权重之上进行微调,可以降低训练模型训练的算力和时间消耗,降低全球的总体计算成本和碳排放。 + + +## 迁移学习 + + + +*预训练是*训练模型前的一个操作:随机初始化权重,在没有任何先验知识的情况下开始训练。 + +
+The pretraining of a language model is costly in both time and money. + +
+ +这种预训练通常是在非常大量的数据上进行的。因此,它需要大量的数据,而且训练可能需要几周的时间。 + +另一方面,*微调*是在模型经过预训练后完成的训练。要执行微调,首先需要获取一个经过预训练的语言模型,然后使用特定于任务的数据集执行额外的训练。等等,为什么不直接为最后的任务而训练呢?有几个原因: + +* 预训练模型已经在与微调数据集有一些相似之处的数据集上进行了训练。因此,微调过程能够利用模型在预训练期间获得的知识(例如,对于NLP问题,预训练模型将对您在任务中使用的语言有某种统计规律上的理解)。 +* 由于预训练模型已经在大量数据上进行了训练,因此微调需要更少的数据来获得不错的结果。 +* 出于同样的原因,获得好结果所需的时间和资源要少得多 + +例如,可以利用英语的预训练过的模型,然后在arXiv语料库上对其进行微调,从而形成一个基于科学/研究的模型。微调只需要有限的数据量:预训练模型获得的知识可以“迁移”到目标任务上,因此被称为*迁移学习*。 + +
+The fine-tuning of a language model is cheaper than pretraining in both time and money. + +
+ +因此,微调模型具有较低的时间、数据、财务和环境成本。迭代不同的微调方案也更快、更容易,因为与完整的预训练相比,训练的约束更少。 + +这个过程也会比从头开始的训练(除非你有很多数据)取得更好的效果,这就是为什么你应该总是尝试利用一个预训练的模型--一个尽可能接近你手头的任务的模型--并对其进行微调。 + +## 一般的体系结构 +在这一部分,我们将介绍Transformer模型的一般架构。如果你不理解其中的一些概念,不要担心;下文将详细介绍每个组件。 + + + +## 介绍 + +该模型主要由两个块组成: + +* **Encoder (左侧)**: 编码器接收输入并构建其表示(其特征)。这意味着对模型进行了优化,以从输入中获得理解。 +* **Decoder (右侧)**: 解码器使用编码器的表示(特征)以及其他输入来生成目标序列。这意味着该模型已针对生成输出进行了优化。 + +
+Architecture of a Transformers models + +
+ +这些部件中的每一个都可以独立使用,具体取决于任务: + +* **Encoder-only models**: 适用于需要理解输入的任务,如句子分类和命名实体识别。 +* **Decoder-only models**: 适用于生成任务,如文本生成。 +* **Encoder-decoder models** 或者 **sequence-to-sequence models**: 适用于需要根据输入进行生成的任务,如翻译或摘要。 + +在后面的部分中,我们将单独地深入研究这些体系结构。 + +## 注意力层 + +Transformer模型的一个关键特性是*注意力层*。事实上,介绍Transformer架构的文章的标题是[“注意力就是你所需要的”](https://arxiv.org/abs/1706.03762)! 我们将在课程的后面更加深入地探索注意力层;现在,您需要知道的是,这一层将告诉模型在处理每个单词的表示时,要特别重视您传递给它的句子中的某些单词(并且或多或少地忽略其他单词)。 + +把它放在语境中,考虑将文本从英语翻译成法语的任务。在输入“You like this course”的情况下,翻译模型还需要注意相邻的单词“You”,以获得单词“like”的正确翻译,因为在法语中,动词“like”的变化取决于主题。然而,句子的其余部分对于该词的翻译没有用处。同样,在翻译“this”时,模型也需要注意“course”一词,因为“this”的翻译不同,取决于相关名词是单数还是复数。同样,句子中的其他单词对于“this”的翻译也不重要。对于更复杂的句子(以及更复杂的语法规则),模型需要特别注意可能出现在句子中更远位置的单词,以便正确地翻译每个单词。 + +同样的概念也适用于与自然语言相关的任何任务:一个词本身有一个含义,但这个含义受语境的影响很大,语境可以是研究该词之前或之后的任何其他词(或多个词)。 + +现在您已经了解了注意力层的含义,让我们更仔细地了解Transformer架构。 + +## 原始的结构 + +Transformer架构最初是为翻译而设计的。在训练期间,编码器接收特定语言的输入(句子),而解码器需要输出对应语言的翻译。在编码器中,注意力层可以使用一个句子中的所有单词(正如我们刚才看到的,给定单词的翻译可以取决于它在句子中的其他单词)。然而,解码器是按顺序工作的,并且只能注意它已经翻译过的句子中的单词。例如,当我们预测了翻译目标的前三个单词时,我们将它们提供给解码器,然后解码器使用编码器的所有输入来尝试预测第四个单词。 + +为了在训练过程中加快速度(当模型可以访问目标句子时),解码器会被输入整个目标,但不允许获取到要翻译的单词(如果它在尝试预测位置2的单词时可以访问位置2的单词,解码器就会偷懒,直接输出那个单词,从而无法学习到正确的语言关系!)。例如,当试图预测第4个单词时,注意力层只能获取位置1到3的单词。 + +最初的Transformer架构如下所示,编码器位于左侧,解码器位于右侧: + +
+Architecture of a Transformers models + +
+ +注意,解码器块中的第一个注意力层关联到解码器的所有(过去的)输入,但是第二注意力层使用编码器的输出。因此,它可以访问整个输入句子,以最好地预测当前单词。这是非常有用的,因为不同的语言可以有语法规则将单词按不同的顺序排列,或者句子后面提供的一些上下文可能有助于确定给定单词的最佳翻译。 + +也可以在编码器/解码器中使用*注意力遮罩层*,以防止模型注意某些特殊单词。例如,在批处理句子时,填充特殊词使所有句子的长度一致。 + +## 架构与参数 + +在本课程中,当我们深入探讨Transformers模型时,您将看到 +架构、参数和模型 +。 这些术语的含义略有不同: + +* **架构**: 这是模型的骨架 -- 每个层的定义以及模型中发生的每个操作。 +* **Checkpoints**: 这些是将在给架构中结构中加载的权重。 +* **模型**: 这是一个笼统的术语,没有“架构”或“参数”那么精确:它可以指两者。为了避免歧义,本课程使用将使用架构和参数。 + +例如,BERT是一个架构,而 `bert-base-cased`, 这是谷歌团队为BERT的第一个版本训练的一组权重参数,是一个参数。我们可以说“BERT模型”和"`bert-base-cased`模型." diff --git a/chapters/zh-CN/chapter1/5.mdx b/chapters/zh-CN/chapter1/5.mdx new file mode 100644 index 000000000..7aa765ec2 --- /dev/null +++ b/chapters/zh-CN/chapter1/5.mdx @@ -0,0 +1,17 @@ +# “编码器”模型 + + + +“编码器”模型指仅使用编码器的Transformer模型。在每个阶段,注意力层都可以获取初始句子中的所有单词。这些模型通常具有“双向”注意力,被称为自编码模型。 + +这些模型的预训练通常围绕着以某种方式破坏给定的句子(例如:通过随机遮盖其中的单词),并让模型寻找或重建给定的句子。 + +“编码器”模型最适合于需要理解完整句子的任务,例如:句子分类、命名实体识别(以及更普遍的单词分类)和阅读理解后回答问题。 + +该系列模型的典型代表有: + +- [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) diff --git a/chapters/zh-CN/chapter1/6.mdx b/chapters/zh-CN/chapter1/6.mdx new file mode 100644 index 000000000..2de4c44a6 --- /dev/null +++ b/chapters/zh-CN/chapter1/6.mdx @@ -0,0 +1,17 @@ +# “解码器”模型 + + + +“解码器”模型通常指仅使用解码器的Transformer模型。在每个阶段,对于给定的单词,注意力层只能获取到句子中位于将要预测单词前面的单词。这些模型通常被称为自回归模型。 + +“解码器”模型的预训练通常围绕预测句子中的下一个单词进行。 + +这些模型最适合于涉及文本生成的任务。 + +该系列模型的典型代表有: + + +- [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) +- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) +- [GPT-2](https://huggingface.co/transformers/model_doc/gpt2.html) +- [Transformer XL](https://huggingface.co/transformers/model_doc/transformerxl.html) diff --git a/chapters/zh-CN/chapter1/7.mdx b/chapters/zh-CN/chapter1/7.mdx new file mode 100644 index 000000000..99dc00eea --- /dev/null +++ b/chapters/zh-CN/chapter1/7.mdx @@ -0,0 +1,16 @@ +# 序列到序列模型 + + + +编码器-解码器模型(也称为序列到序列模型)同时使用Transformer架构的编码器和解码器两个部分。在每个阶段,编码器的注意力层可以访问初始句子中的所有单词,而解码器的注意力层只能访问位于输入中将要预测单词前面的单词。 + +这些模型的预训练可以使用训练编码器或解码器模型的方式来完成,但通常涉及更复杂的内容。例如,[T5](https://huggingface.co/t5-base)通过将文本的随机跨度(可以包含多个单词)替换为单个特殊单词来进行预训练,然后目标是预测该掩码单词替换的文本。 + +序列到序列模型最适合于围绕根据给定输入生成新句子的任务,如摘要、翻译或生成性问答。 + +该系列模型的典型代表有: + +- [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) diff --git a/chapters/zh-CN/chapter1/8.mdx b/chapters/zh-CN/chapter1/8.mdx new file mode 100644 index 000000000..707731892 --- /dev/null +++ b/chapters/zh-CN/chapter1/8.mdx @@ -0,0 +1,31 @@ +# Bias and limitations + + + +如果您打算在正式的项目中使用经过预训练或经过微调的模型。请注意:虽然这些模型是很强大,但它们也有局限性。其中最大的一个问题是,为了对大量数据进行预训练,研究人员通常会搜集所有他们能找到的内容,中间可能夹带一些意识形态或者价值观的刻板印象。 + +为了快速解释清楚这个问题,让我们回到一个使用BERT模型的pipeline的例子: + +```python +from transformers import pipeline + +unmasker = pipeline("fill-mask", model="bert-base-uncased") +result = unmasker("This man works as a [MASK].") +print([r["token_str"] for r in result]) + +result = unmasker("This woman works as a [MASK].") +print([r["token_str"] for r in result]) +``` + +```python out +['lawyer', 'carpenter', 'doctor', 'waiter', 'mechanic'] +['nurse', 'waitress', 'teacher', 'maid', 'prostitute'] +``` +当要求模型填写这两句话中缺少的单词时,模型给出的答案中,只有一个与性别无关(服务员/女服务员)。其他职业通常与某一特定性别相关,妓女最终进入了模型中与“女人”和“工作”相关的前五位。尽管BERT是使用经过筛选和清洗后,明显中立的数据集上建立的的Transformer模型,而不是通过从互联网上搜集数据(它是在[Wikipedia 英文](https://huggingface.co/datasets/wikipedia)和[BookCorpus](https://huggingface.co/datasets/bookcorpus)数据集)。 + +因此,当您使用这些工具时,您需要记住,使用的原始模型的时候,很容易生成性别歧视、种族主义或恐同内容。这种固有偏见不会随着微调模型而使消失。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter1/9.mdx b/chapters/zh-CN/chapter1/9.mdx new file mode 100644 index 000000000..16c5ab6ad --- /dev/null +++ b/chapters/zh-CN/chapter1/9.mdx @@ -0,0 +1,11 @@ +# 总结 + +在本章中,您了解了如何使用来自🤗Transformers的函数pipeline()处理不同的NLP任务。您还了解了如何在模型中心(hub)中搜索和使用模型,以及如何使用推理API直接在浏览器中测试模型。 + +我们讨论了Transformer模型如何在应用层上工作,并讨论了迁移学习和微调的重要性。您可以使用完整的体系结构,也可以仅使用编码器或解码器,具体取决于您要解决的任务类型。下表总结了这一点: + +| 模型 | 示例 | 任务| +| ---- | ---- |----| +| 编码器 | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa |句子分类、命名实体识别、从文本中提取答案| +| 解码器 | CTRL, GPT, GPT-2, Transformer XL |文本生成| +| 编码器-解码器 | BART, T5, Marian, mBART |文本摘要、翻译、生成问题的回答| \ No newline at end of file From 841fb08b2e93860d2f1aef7a5448f471cff26f2a Mon Sep 17 00:00:00 2001 From: lewtun Date: Wed, 4 May 2022 17:05:18 +0200 Subject: [PATCH 11/51] Bump release (#161) --- .github/workflows/build_documentation.yml | 2 +- .github/workflows/build_pr_documentation.yml | 2 +- chapters/en/chapter1/1.mdx | 4 +- chapters/en/chapter2/2.mdx | 2 +- chapters/en/chapter2/8.mdx | 2 +- chapters/fr/_toctree.yml | 2 +- chapters/fr/chapter1/1.mdx | 46 +-- chapters/fr/chapter1/10.mdx | 104 ++--- chapters/fr/chapter1/2.mdx | 26 +- chapters/fr/chapter1/3.mdx | 152 +++++--- chapters/fr/chapter1/4.mdx | 113 +++--- chapters/fr/chapter1/5.mdx | 8 +- chapters/fr/chapter1/6.mdx | 6 +- chapters/fr/chapter1/7.mdx | 8 +- chapters/fr/chapter1/8.mdx | 16 +- chapters/fr/chapter1/9.mdx | 8 +- chapters/fr/chapter2/2.mdx | 2 +- chapters/fr/chapter2/5.mdx | 4 +- chapters/fr/chapter2/6.mdx | 2 +- chapters/fr/chapter2/8.mdx | 16 +- chapters/fr/chapter3/1.mdx | 29 +- chapters/fr/chapter3/2.mdx | 115 +++--- chapters/fr/chapter3/3.mdx | 8 +- chapters/fr/chapter3/3_tf.mdx | 8 +- chapters/fr/chapter3/4.mdx | 6 +- chapters/fr/chapter3/5.mdx | 6 +- chapters/fr/chapter3/6.mdx | 30 +- chapters/fr/chapter4/2.mdx | 6 +- chapters/fr/chapter4/3.mdx | 18 +- chapters/fr/chapter4/4.mdx | 2 +- chapters/fr/chapter4/6.mdx | 6 +- chapters/fr/event/1.mdx | 12 +- chapters/hi/_toctree.yml | 5 + chapters/hi/chapter2/1.mdx | 21 + chapters/it/_toctree.yml | 13 + chapters/it/chapter0/1.mdx | 110 ++++++ chapters/it/chapter1/1.mdx | 52 +++ chapters/it/chapter1/2.mdx | 21 + chapters/it/chapter1/3.mdx | 329 ++++++++++++++++ chapters/ru/_toctree.yml | 9 + chapters/ru/chapter3/1.mdx | 22 ++ chapters/ru/chapter3/2.mdx | 382 +++++++++++++++++++ chapters/ru/chapter3/3.mdx | 175 +++++++++ chapters/th/_toctree.yml | 20 + chapters/th/chapter0/1.mdx | 8 +- chapters/th/chapter1/1.mdx | 2 +- chapters/th/chapter1/10.mdx | 253 ++++++++++++ chapters/th/chapter1/2.mdx | 22 ++ chapters/th/chapter1/3.mdx | 329 ++++++++++++++++ chapters/th/chapter1/4.mdx | 181 +++++++++ chapters/th/chapter1/5.mdx | 17 + chapters/th/chapter1/6.mdx | 14 + chapters/th/chapter1/7.mdx | 16 + chapters/th/chapter1/8.mdx | 32 ++ chapters/th/chapter1/9.mdx | 12 + chapters/tr/_toctree.yml | 9 + chapters/tr/chapter1/1.mdx | 53 +++ chapters/tr/chapter1/5.mdx | 18 + chapters/tr/chapter2/1.mdx | 20 + 59 files changed, 2542 insertions(+), 374 deletions(-) create mode 100644 chapters/hi/chapter2/1.mdx create mode 100644 chapters/it/_toctree.yml create mode 100644 chapters/it/chapter0/1.mdx create mode 100644 chapters/it/chapter1/1.mdx create mode 100644 chapters/it/chapter1/2.mdx create mode 100644 chapters/it/chapter1/3.mdx create mode 100644 chapters/ru/chapter3/1.mdx create mode 100644 chapters/ru/chapter3/2.mdx create mode 100644 chapters/ru/chapter3/3.mdx create mode 100644 chapters/th/chapter1/10.mdx create mode 100644 chapters/th/chapter1/2.mdx create mode 100644 chapters/th/chapter1/3.mdx create mode 100644 chapters/th/chapter1/4.mdx create mode 100644 chapters/th/chapter1/5.mdx create mode 100644 chapters/th/chapter1/6.mdx create mode 100644 chapters/th/chapter1/7.mdx create mode 100644 chapters/th/chapter1/8.mdx create mode 100644 chapters/th/chapter1/9.mdx create mode 100644 chapters/tr/chapter1/1.mdx create mode 100644 chapters/tr/chapter1/5.mdx create mode 100644 chapters/tr/chapter2/1.mdx diff --git a/.github/workflows/build_documentation.yml b/.github/workflows/build_documentation.yml index a31409914..caee4d661 100644 --- a/.github/workflows/build_documentation.yml +++ b/.github/workflows/build_documentation.yml @@ -14,6 +14,6 @@ jobs: package: course path_to_docs: course/chapters/ additional_args: --not_python_module - languages: ar bn de en es fa fr gj he hi ja ko pt ru th tr zh-CN + languages: ar bn de en es fa fr gj he hi it ja ko pt ru th tr zh-CN secrets: token: ${{ secrets.HUGGINGFACE_PUSH }} diff --git a/.github/workflows/build_pr_documentation.yml b/.github/workflows/build_pr_documentation.yml index 11c70e24e..a718be37a 100644 --- a/.github/workflows/build_pr_documentation.yml +++ b/.github/workflows/build_pr_documentation.yml @@ -16,5 +16,5 @@ jobs: package: course path_to_docs: course/chapters/ additional_args: --not_python_module - languages: ar bn de en es fa fr gj he hi ja ko pt ru th tr zh-CN + languages: ar bn de en es fa fr gj he hi it ja ko pt ru th tr zh-CN hub_base_path: https://moon-ci-docs.huggingface.co/course diff --git a/chapters/en/chapter1/1.mdx b/chapters/en/chapter1/1.mdx index b3f26714a..7ec1a6f11 100644 --- a/chapters/en/chapter1/1.mdx +++ b/chapters/en/chapter1/1.mdx @@ -42,9 +42,9 @@ About the authors: **Lucile Saulnier** is a machine learning engineer at Hugging Face, developing and supporting the use of open source tools. She is also actively involved in many research projects in the field of Natural Language Processing such as collaborative training and BigScience. -**Lewis Tunstall** is a machine learning engineer at Hugging Face, focused on developing open-source tools and making them accessible to the wider community. He is also a co-author of an upcoming [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). +**Lewis Tunstall** is a machine learning engineer at Hugging Face, focused on developing open-source tools and making them accessible to the wider community. He is also a co-author of the O’Reilly book [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). -**Leandro von Werra** is a machine learning engineer in the open-source team at Hugging Face and also a co-author of the an upcoming [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). He has several years of industry experience bringing NLP projects to production by working across the whole machine learning stack.. +**Leandro von Werra** is a machine learning engineer in the open-source team at Hugging Face and also a co-author of the O’Reilly book [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). He has several years of industry experience bringing NLP projects to production by working across the whole machine learning stack.. Are you ready to roll? In this chapter, you will learn: * How to use the `pipeline()` function to solve NLP tasks such as text generation and classification diff --git a/chapters/en/chapter2/2.mdx b/chapters/en/chapter2/2.mdx index a7715efc7..d1304d737 100644 --- a/chapters/en/chapter2/2.mdx +++ b/chapters/en/chapter2/2.mdx @@ -23,7 +23,7 @@ {/if} -This is the first section where the content is slightly different depending on whether you use PyTorch and TensorFlow. Toogle the switch on top of the title to select the platform you prefer! +This is the first section where the content is slightly different depending on whether you use PyTorch or TensorFlow. Toggle the switch on top of the title to select the platform you prefer! {#if fw === 'pt'} diff --git a/chapters/en/chapter2/8.mdx b/chapters/en/chapter2/8.mdx index ef07b0b31..861aacb39 100644 --- a/chapters/en/chapter2/8.mdx +++ b/chapters/en/chapter2/8.mdx @@ -126,7 +126,7 @@ choices={[ { text: "A model that automatically trains on your data", - explain: "Incorrect. Are you mistaking this with our AutoNLP product?" + explain: "Incorrect. Are you mistaking this with our AutoTrain product?" }, { text: "An object that returns the correct architecture based on the checkpoint", diff --git a/chapters/fr/_toctree.yml b/chapters/fr/_toctree.yml index d4b02ce84..517e7c8b2 100644 --- a/chapters/fr/_toctree.yml +++ b/chapters/fr/_toctree.yml @@ -170,4 +170,4 @@ - title: Evènements liés au cours d'Hugging Face sections: - local: event/1 - title: Événement de lancement pour le lancement de la partie 2 + title: Événement de lancement de la partie 2 diff --git a/chapters/fr/chapter1/1.mdx b/chapters/fr/chapter1/1.mdx index fdfee9d27..4bb728c00 100644 --- a/chapters/fr/chapter1/1.mdx +++ b/chapters/fr/chapter1/1.mdx @@ -1,51 +1,51 @@ # Introduction -## Bienvenue sur le cours 🤗 ! +## Bienvenue au cours 🤗 ! -Ce cours vous apprendra à utiliser les librairies de NLP de l'écosystème [Hugging Face](https://huggingface.co/) — [🤗 Transformers](https://github.com/huggingface/transformers), [🤗 Datasets](https://github.com/huggingface/datasets), [🤗 Tokenizers](https://github.com/huggingface/tokenizers), et [🤗 Accelerate](https://github.com/huggingface/accelerate) — ainsi que le [Hub de Hugging Face](https://huggingface.co/models). C'est totalement gratuit et sans publicité. +Ce cours vous apprendra à utiliser les bibliothèques de NLP de l'écosystème [Hugging Face](https://huggingface.co/) : [🤗 *Transformers*](https://github.com/huggingface/transformers), [🤗 *Datasets*](https://github.com/huggingface/datasets), [🤗 *Tokenizers*](https://github.com/huggingface/tokenizers) et [🤗 *Accelerate*](https://github.com/huggingface/accelerate), ainsi que le [*Hub*](https://huggingface.co/models). C'est totalement gratuit et sans publicité. ## À quoi s'attendre ? -Voici un bref aperçu du cours: +Voici un bref aperçu du cours :
Bref aperçu du contenu du cours.
-- Les chapitres 1 à 4 présentent les principaux concepts de la librairie 🤗 Transformers. À la fin de ce chapitre, vous serez familier du fonctionnement des modèles Transformers et vous saurez comment utiliser un modèle du [Hub de Hugging Face](https://huggingface.co/models), le finetuner sur un jeu de données, et partager vos résultats sur le Hub! -- Les chapitres 5 à 8 présentent les bases de 🤗 Datasets et 🤗 Tokenizers ainsi qu'une découverte des problèmes classiques de NLP. À la fin de ce chapitre, vous serez capable de résoudre les problèmes de NLP les plus communs par vous-même. -- Les chapitres 9 à 12 proposent d'aller plus loin et d'explorer comment les modèles Transformers peuvent être utilisés pour résoudre des problèmes de traitement de la parole et de la vision par ordinateur. En suivant ces chapitres, vous apprendrez à construire et à partager vos modèles via des démonstrateurs, et vous serez capable d'optimiser ces modèles pour les environnements de production. Enfin, vous serez prêt à appliquer 🤗 Transformers à (presque) n'importe quel problème de machine learning! +- Les chapitres 1 à 4 présentent les principaux concepts de la bibliothèque 🤗 *Transformers*. À la fin de ce chapitre, vous serez familier avec le fonctionnement des *transformers* et vous saurez comment utiliser un modèle présent sur le [*Hub*](https://huggingface.co/models), le *finetuner* sur un jeu de données, et partager vos résultats sur le *Hub* ! +- Les chapitres 5 à 8 présentent les bases des librairies 🤗 *Datasets* et 🤗 *Tokenizers* ainsi qu'une découverte des problèmes classiques de NLP. À la fin de ce chapitre, vous serez capable de résoudre les problèmes de NLP les plus communs par vous-même. +- Les chapitres 9 à 12 proposent d'aller plus loin et d'explorer comment les *transformers* peuvent être utilisés pour résoudre des problèmes de traitement de la parole et de vision par ordinateur. En suivant ces chapitres, vous apprendrez à construire et à partager vos modèles via des démonstrateurs, et vous serez capable d'optimiser ces modèles pour des environnements de production. Enfin, vous serez prêt à appliquer 🤗 *Transformers* à (presque) n'importe quel problème d'apprentissage automatique ! -Ce cours: +Ce cours : -* Requiert un bon niveau en Python -* Se comprend mieux si vous avez déjà suivi un cours d'introduction au deep learning, comme [fast.ai's](https://www.fast.ai/) [Practical Deep Learning for Coders](https://course.fast.ai/) ou un des cours développés par [DeepLearning.AI](https://www.deeplearning.ai/) -* N'attend pas une connaissance appronfondie de [PyTorch](https://pytorch.org/) ou de [TensorFlow](https://www.tensorflow.org/), bien qu'être familiarisé avec l'un d'entre eux peut aider +* requiert un bon niveau en Python, +* se comprend mieux si vous avez déjà suivi un cours d'introduction à l'apprentissage profond comme [fast.ai's](https://www.fast.ai/), [*Practical Deep Learning for Coders*](https://course.fast.ai/) ou un des cours développés par [*DeepLearning.AI*](https://www.deeplearning.ai/), +* n'attend pas une connaissance appronfondie de [PyTorch](https://pytorch.org/) ou de [TensorFlow](https://www.tensorflow.org/), bien qu'être familiarisé avec l'un d'entre eux peut aider. -Après avoir terminé ce cours, nous vous recommandons de suivre la [Spécialisation en NLP](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh) dispensée par DeepLearning.AI, qui couvre une grande gamme de modèles traditionnels de NLP comme les Naïves Bayes et les LSTMs qui sont importants à connaître! +Après avoir terminé ce cours, nous vous recommandons de suivre la [Spécialisation en NLP](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh) dispensée par DeepLearning.AI, qui couvre une grande partie des modèles traditionnels de NLP comme le Bayésien naïf et les LSTMs qui sont importants à connaître! ## Qui sommes-nous ? -À propos des auteurs de ce cours: +À propos des auteurs de ce cours : -**Matthew Carrigan** est Machine Learning Engineer chez Hugging Face. Il vit à Dublin, en Irlande et à travaillé auparavant comme ingénieur en ML chez Parse.ly et avant cela comme chercheur postdoctoral à Trinity College Dublin. Il ne croit pas que nous arrivions à AGI en mettant à l'échelle les architectures existantes, mais il a tout de même beaucoup d'espoir dans l'immortalité des robots. +**Matthew Carrigan** est ingénieur en apprentissage machine chez Hugging Face. Il vit à Dublin en Irlande. Il a travaillé auparavant comme ingénieur en apprentissage machine chez Parse.ly et avant cela comme chercheur postdoctoral au Trinity College Dublin. Il ne croit pas que nous arrivions à l'*AGI* en mettant à l'échelle les architectures existantes mais a tout de même beaucoup d'espoir dans l'immortalité des robots. -**Lysandre Debut** est Machine Learning Engineer chez Hugging Face et a travaillé sur la librairie 🤗 Transformers depuis les premières phases de développement. Son but est de rendre NLP accessible pour tout le monde en développant des outils disposant d'une API très simple. +**Lysandre Debut** est ingénieur en apprentissage machine chez Hugging Face et a travaillé sur la bibliothèque 🤗 *Transformers* depuis les premières phases de développement. Son but est de rendre le NLP accessible à tous en développant des outils disposant d'une API très simple. -**Sylvain Gugger** est Research Engineer chez Hugging Face et un des principaux responsable de la librairie 🤗 Transformers. Précédemment, Il a été chercheur en ML chez fast.ai et a écrit _[Deep Learning for Coders with fastai and PyTorch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)_ avec Jeremy Howard. Son but ultime est de rendre le deep learning plus accessible, en développant et en améliorant des techniques permettant aux modèles d'apprendre rapidement sur des ressources limitées. +**Sylvain Gugger** est ingénieur recherche chez Hugging Face et un des principaux responsables de la bibliothèque 🤗 *Transformers*. Avant cela, il était chercheur en en apprentissage machine chez fast.ai et a écrit le livre [*Deep Learning for Coders with fastai and PyTorch*](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/) avec Jeremy Howard. Son but est de rendre l'apprentissage profond plus accessible, en développant et en améliorant des techniques permettant aux modèles d'apprendre rapidement sur des ressources limitées. -**Merve Noyan** est Developer Advocate chez Hugging Face et travaille à la création d'outils et de contenu visant à démocratiser le machine learning pour tous. +**Merve Noyan** est développeuse *advocate* chez Hugging Face et travaille à la création d'outils et de contenus visant à démocratiser l'apprentissage machine pour tous. -**Lucile Saulnier** est Machine Learning Engineer chez Hugging Face qui travaille au développement et à l'implémentation de nombreux outils open source. Elle est également activement impliquée dans de nombreux projets de recherche dans le domaine de NLP comme l'entraînement collaboratif de modèles et le projet BigScience. +**Lucile Saulnier** est ingénieure en apprentissage machine chez Hugging Face et travaille au développement et à l'implémentation de nombreux outils *open source*. Elle est également activement impliquée dans de nombreux projets de recherche dans le domaine du NLP comme l'entraînement collaboratif de modèles et le projet BigScience. -**Lewis Tunstall** est Machine Learning Engineer chez Hugging Face dévoué au développement d'outils open source avec la volonté de les rendre accessibles à une communauté plus large. Il est également co-auteur d'un livre qui va bientôt paraître, [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). +**Lewis Tunstall** est ingénieur en apprentissage machine chez Hugging Face et dévoué au développement d'outils open source avec la volonté de les rendre accessibles à une communauté plus large. Il est également co-auteur du livre [*Natural Language Processing with Transformers*](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). -**Leandro von Werra** est Machine Learning Engineer dans l'équipe open-source chez Hugging Face et également co-auteur du livre qui va bientôt paraître, [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). Il a plusieurs années d'expérience dans l'industrie du machine learning, où il a pu déployer des projets de NLP en production en travaillant sur toutes les étapes clefs du déploiement. +**Leandro von Werra** est ingénieur en apprentissage machine dans l'équipe *open source* d'Hugging Face et également co-auteur du livre [*Natural Language Processing with Transformers*](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). Il a plusieurs années d'expérience dans l'industrie où il a pu déployer des projets de NLP en production et travailler sur toutes les étapes clefs du déploiement. -Êtes-vous prêt à commencer ? Dans ce chapitre, vous apprendrez: -* À utiliser la fonction `pipeline()` pour résoudre des problèmes de NLP comme la génération de texte et la classification -* Quelle est l'architecture d'un modèle Transformer -* Comment faire la distinction entre les différentes architectures d'encodeur, de décodeur et d'encodeur-décodeur et leurs condition d'utilisation +Êtes-vous prêt à commencer ? Dans ce chapitre, vous apprendrez : +* à utiliser la fonction `pipeline()` pour résoudre des problèmes de NLP comme la génération de texte et la classification, +* l'architecture d'un *transformer*, +* comment faire la distinction entre les différentes architectures d'encodeur, de décodeur et d'encodeur-décodeur ainsi que leurs différents cas d'usage. diff --git a/chapters/fr/chapter1/10.mdx b/chapters/fr/chapter1/10.mdx index 5c6f39376..b44709cb0 100644 --- a/chapters/fr/chapter1/10.mdx +++ b/chapters/fr/chapter1/10.mdx @@ -1,13 +1,13 @@ -# Questionnaire de fin de chapitre +# Quiz de fin de chapitre -Ce chapitre a couvert un grand nombre de notions ! Ne vous inquiétez pas si vous n'avez pas compris tous les détails, car les chapitres suivants vous aideront à comprendre comment les choses fonctionnent concrètement. +Ce chapitre a couvert un grand nombre de notions ! Ne vous inquiétez pas si vous n'avez pas compris tous les détails, les chapitres suivants vous aideront à comprendre comment les choses fonctionnent concrètement. Mais avant d'aller plus loin, prenons un instant pour voir ce que vous avez appris dans ce chapitre ! -### 1. Explorez le Hub et cherchez le modèle `roberta-large-mnli`. Quelle est la tâche qu'il effectue ? +### 1. Explorez le *Hub* et cherchez le modèle `roberta-large-mnli`. Quelle tâche accomplit-il ? inference de langage naturel.", + explain: "Pour être plus précis, il classifie si deux phrases sont logiquement liées entre elles parmis trois possibilités (contradiction, neutre, lien). Il s'agit d'une tâche aussi appelée inference de langage naturel.", correct: true }, { @@ -34,28 +34,30 @@ Mais avant d'aller plus loin, prenons un instant pour voir ce que vous avez appr from transformers import pipeline ner = pipeline("ner", grouped_entities=True) -ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") +ner( + "My name is Sylvain and I work at Hugging Face in Brooklyn." +) # Je m'appelle Sylvain et je travaille à Hugging Face à Brooklyn. ``` d'analyse de sentiment." + text: "Il renvoie les scores de classification pour cette phrase, avec les labels \"positive\" ou \"negative\".", + explain: "C'est incorrect — cela correspondrait au pipeline d'analyse de sentiment (sentiment-analysis dans la documentation d'Hugging-Face)." }, { - text: "Ce code renvoie un texte généré qui complète cette phrase.", - explain: "C'est incorrect - cela correspondrait à un pipeline de génération de texte." + text: "Il renvoie un texte généré qui complète cette phrase.", + explain: "C'est incorrect. Cela correspondrait au pipeline de génération de texte (text-generation dans la documentation d'Hugging-Face)." }, { - text: "Ce code renvoie les entités nommées dans cette phrase, telles que personnes, organisations ou lieux.", - explain: "De plus, avec grouped_entities=True, il regroupera les mots appartenant à la même entité, comme \"Hugging Face\".", + text: "Il renvoie les entités nommées dans cette phrase, telles que les personnes, les organisations ou lieux.", + explain: "De plus, avec grouped_entities=True, cela regroupe les mots appartenant à la même entité, comme par exemple \"Hugging Face\".", correct: true } ]} /> -### 3. Que remplace ... dans ce code ? +### 3. Que remplace « ... » dans ce code ? ```py from transformers import pipeline @@ -67,17 +69,17 @@ result = filler("...") has been waiting for you.", + text: "This <mask> has been waiting for you. # Ce <mask> vous attend.", explain: "Ceci est incorrect. Regardez la description du modèle bert-base-cased et essayez de trouver votre erreur." }, { - text: "This [MASK] has been waiting for you.", + text: "This [MASK] has been waiting for you. # Ce [MASK] vous attend.", explain: "Correct! Le modèle utilise [MASK] comme mot-masque.", correct: true }, { - text: "This man has been waiting for you.", - explain: "Ceci est incorrect, car ce pipeline permet de remplacer les mot manquants, donc il a besoin d'un mot-masque." + text: "This man has been waiting for you. # Cet homme vous attend.", + explain: "Ceci est incorrect car ce pipeline permet de remplacer les mot manquants donc il a besoin d'un mot-masque." } ]} /> @@ -88,42 +90,44 @@ result = filler("...") from transformers import pipeline classifier = pipeline("zero-shot-classification") -result = classifier("This is a course about the Transformers library") +result = classifier( + "This is a course about the Transformers library" +) # C'est un cours sur la bibliothèque Transformers ``` candidate_labels=[...].", + text: "Ce pipeline nécessite que des étiquettes soient données pour classifier ce texte.", + explain: "Vrai. Le code doit inclure candidate_labels=[...].", correct: true }, { text: "Ce pipeline nécessite que des phrases soient données, pas juste une phrase.", - explain: "C'est incorrect, bien que ce pipeline peut prendre une liste de phrases à traiter (comme tous les autres pipelines)." + explain: "C'est incorrect, bien que ce pipeline puisse prendre une liste de phrases à traiter (comme tous les autres pipelines)." }, { - text: "La bibliothèque 🤗 Transformers est cassée, comme d'habitude.", + text: "La bibliothèque 🤗 Transformers est cassée, comme d'habitude.", explain: "Nous n'avons aucun commentaire pour cette réponse !", }, { - text: "Ce pipeline nécessite des phrases plus longues, car celle-ci est trop courte.", - explain: "C'est incorrect. Notez qu'un texte très long sera tronqué lorsqu'il sera traité par ce pipeline." + text: "Ce pipeline nécessite des phrases plus longues, celle-ci est trop courte.", + explain: "C'est incorrect. Notez que si un texte est très long, il est tronqué par le pipeline." } ]} /> -### 5. Que signifie "transfer learning" ? +### 5. Que signifie « apprentissage par transfert » ? -### 6. Vrai ou faux ? Un modèle de NLP n'a normalement pas besoin de labels pour son entraînement préalable. +### 6. Vrai ou faux ? Un modèle de langage n'a généralement pas besoin d'étiquettes pour son pré-entraînement. self-supervised, ce qui signifie que les labels sont créés automatiquement à partir des données d'entrée (comme prédire le mot suivant ou remplacer des mots masqués).", + explain: "Correct, le pré-entraînement est autosupervisé, ce qui signifie que les étiquettes sont créées automatiquement à partir des données d'entrée (comme prédire le mot suivant ou remplacer des mots masqués).", correct: true }, { @@ -150,7 +154,7 @@ result = classifier("This is a course about the Transformers library") ]} /> -### 7. Sélectionnez la phrase qui décrit les termes "modèle," "architecture," et "poids" le plus précisément possible. +### 7. Sélectionnez la phrase qui décrit le mieux les termes « modèle », « architecture » et « poids ». -### 8. Parmi ces types de modèles, quel est le plus approprié pour compléter une demande (prompt) avec du texte généré ? +### 8. Parmi ces types de modèles, quel est le plus approprié pour générer du texte à partir d'une instruction (*prompt*) ? @@ -196,16 +200,16 @@ result = classifier("This is a course about the Transformers library") -### 11. Quelles sources peuvent avoir les biais d'un modèle ? +### 11. De quelle source possible peut être le biais observé dans un modèle ? finetunée d'un modèle pré-entraîné et il a conservé ses biais.", + explain: "Avec l'apprentissage par transfert, les biais du modèle pré-entraîné perdurent dans le modèle finetuné.", correct: true }, { text: "Le modèle a été entraîné sur des données qui sont biaisées.", - explain: "Ceci représente la source de biais la plus évidente, mais n'est pas la seule possible.", + explain: "Ceci représente la source de biais la plus évidente mais n'est pas la seule possible.", correct: true }, { diff --git a/chapters/fr/chapter1/2.mdx b/chapters/fr/chapter1/2.mdx index 6638e3608..e237c445c 100644 --- a/chapters/fr/chapter1/2.mdx +++ b/chapters/fr/chapter1/2.mdx @@ -1,21 +1,21 @@ -# Traitement du language naturel (NLP) +# Traitement du langage naturel (NLP pour *Natural Language Processing*) -Avant de commencer avec les modèles Transformers, nous allons voir succinctement ce qu'est le traitement du langage naturel et pourquoi il est important. +Avant de commencer avec les *transformers*, voyons succinctement ce qu'est le traitement du langage naturel et pourquoi il est important. -## NLP, qu'est ce que c'est? +## Le NLP, qu'est-ce que c'est ? -Le traitement du langage naturel est un domaine de linguistique et de machine learning qui se concentre sur la compréhension de tout ce qui est lié à la langue humaine. L'objectif des tâches de NLP est non seulement de comprendre individuellement chaque mot, mais aussi de comprendre le contexte associé à l'utilisation de ces mots. +Le traitement du langage naturel est un domaine de linguistique et d'apprentissage automatique se concentrant sur la compréhension de tout ce qui est lié à la langue humaine. L'objectif des tâches de NLP est non seulement de comprendre individuellement chaque mot, mais aussi de comprendre le contexte associé à l'utilisation de ces mots. -La liste suivante regroupe les tâches de NLP les plus courantes, avec pour chacune quelques exemples: +La liste suivante regroupe les tâches de NLP les plus courantes, avec pour chacune quelques exemples : -- **Classification de phrases entières**: Analyser le sentiment d'un avis, détecter si un email est un spam, déterminer si une phrase est grammaticalement correcte ou si deux phrases sont logiquement reliées ou non -- **Classification de chaque mot d'une phrase**: Identifier les composants grammaticaux d'une phrase (nom, verbe, adjectif), ou les entités nommées (personne, lieu, organisation) -- **Génération de texte**: Compléter un début d'écrit avec un texte généré automatiquement, remplacer les mots manquants ou masqués dans un texte -- **Extraire une réponse à partir d'un texte**: Étant donné une question et un contexte, extraire la réponse à la question en fonction des informations fournies par le contexte -- **Génération de nouvelles phrases à partir d'un texte**: Traduire un texte dans une autre langue, faire le résumé d'un texte +- **classification de phrases entières** : analyser le sentiment d'un avis, détecter si un email est un spam, déterminer si une phrase est grammaticalement correcte, déterminer si deux phrases sont logiquement reliées ou non, etc. +- **classification de chaque mot d'une phrase** : identifier les composants grammaticaux d'une phrase (nom, verbe, adjectif), identifier les entités nommées (personne, lieu, organisation), etc. +- **génération de texte** : compléter le début d'un texte avec un texte généré automatiquement, remplacer les mots manquants ou masqués dans un texte, etc. +- **extraction d'une réponse à partir d'un texte** : étant donné une question et un contexte extraire la réponse à la question en fonction des informations fournies par le contexte, etc. +- **génération de nouvelles phrases à partir d'un texte** : traduire un texte dans une autre langue, faire le résumé d'un texte, etc. -Le traitement du langage naturel ne se limite pas qu'à la compréhension du texte écrit. Il s'intéresse aussi aux problèmes complexes de reconnaissance de la parole et de vision par ordinateur, tels que la génération d'une transcription à partir d'un échantillon audio ou la description d'une image. +Le traitement du langage naturel ne se limite pas qu'à la compréhension du texte. Il s'intéresse aussi aux problèmes complexes de reconnaissance de la parole et de vision par ordinateur tels que la génération d'une transcription à partir d'un échantillon audio ou la description d'une image. -## Pourquoi est-ce difficile? +## Pourquoi est-ce difficile ? -Les ordinateurs ne traitent pas les informations de la même manière que les humains. Par exemple, lorsque nous lisons la phrase "J'ai faim," nous comprenons très facilement son sens. De même, lorsque nous lisons deux phrases telles que "J'ai faim" et "Je suis triste," nous pouvons facilement déterminer s'il existe des similitudes entre elles. Pour les modèles de machine learning, ces tâches sont plus difficiles. Le texte doit être traité de manière à permettre au modèle d'apprendre. Et parce que le langage est complexe, nous devons prendre soin de réfléchir à la meilleure façon de faire ce traitement. Il y a eu beaucoup de recherches sur la façon de représenter le texte, et nous allons voir quelques méthodes dans le chapitre suivant. +Les ordinateurs ne traitent pas les informations de la même manière que les humains. Par exemple, lorsque nous lisons la phrase « j'ai faim », nous comprenons très facilement son sens. De même, lorsque nous lisons deux phrases telles que « j'ai faim » et « je suis triste », nous pouvons facilement déterminer s'il existe des similitudes entre elles. Pour les modèles d'apprentissage automatique, ces tâches sont plus difficiles. Le texte doit être traité de manière à permettre au modèle d'apprendre. Et parce que le langage est complexe, nous devons prendre soin de réfléchir à la meilleure façon de faire ce traitement. Il y a eu beaucoup de recherches sur la façon de représenter le texte et nous allons voir quelques-unes de ces méthodes dans le chapitre suivant. diff --git a/chapters/fr/chapter1/3.mdx b/chapters/fr/chapter1/3.mdx index ec9b002af..97805351c 100644 --- a/chapters/fr/chapter1/3.mdx +++ b/chapters/fr/chapter1/3.mdx @@ -1,4 +1,4 @@ -# Que peuvent faire les modèles Transformers ? +# Que peuvent faire les *transformers* ? -Dans cette section, nous allons voir ce que peuvent faire les modèles Transformers et utiliser notre premier outil de la librairie 🤗 Transformers: la fonction `pipeline()`. +Dans cette section, nous allons voir ce que peuvent faire les *transformers* et utiliser notre premier outil de la bibliothèque 🤗 *Transformers* : la fonction `pipeline()`. -👀 Vous voyez ce bouton Open in Colab en haut à droite ? Cliquez dessus pour ouvrir un notebook Colab avec tous les exemples de code de cette section. Ce bouton sera présent dans n'importe quelle section contenant des exemples de code. +👀 Vous voyez ce bouton Open in Colab en haut à droite ? Cliquez dessus pour ouvrir un *notebook* Colab avec tous les exemples de code de cette section. Ce bouton sera présent dans n'importe quelle section contenant des exemples de code. -Si vous souhaitez exécuter les exemples de code en local, nous vous recommandons de jetez un oeil à: configuration. +Si vous souhaitez exécuter les codes en local, nous vous recommandons de jeter un œil au chapitre : configuration. -## Les modèles Transformers sont partout ! +## Les *transformers* sont partout ! -Les modèles Transformers sont utilisés pour résoudre toute sorte de tâches de NLP, comme celles mentionnées dans la section précédente. Voici quelques-unes des entreprises et organisations qui utilisent Hugging Face et les modèles Transformers, et qui contribuent aussi à la communauté en partageant leurs modèles : +Les *transformers* sont utilisés pour résoudre toute sorte de tâches de NLP comme celles mentionnées dans la section précédente. Voici quelques-unes des entreprises et organisations qui utilisent Hugging Face, les *transformers* et qui contribuent aussi à la communauté en partageant leurs modèles : Companies using Hugging Face -La librairie [🤗 Transformers](https://github.com/huggingface/transformers) fournit toutes les fonctionnalités nécessaires pour créer et utiliser les modèles partagés. Le [Model Hub](https://huggingface.co/models) contient des milliers de modèles pré-entraînés que n'importe qui peut télécharger et utiliser. Vous pouvez également transférer vos propres modèles vers le Hub ! +La bibliothèque [🤗 *Transformers*](https://github.com/huggingface/transformers) fournit toutes les fonctionnalités nécessaires pour créer et utiliser les modèles partagés. Le [Model Hub](https://huggingface.co/models) contient des milliers de modèles pré-entraînés que n'importe qui peut télécharger et utiliser. Vous pouvez également transférer vos propres modèles vers le Hub ! -⚠️ Le Hub Hugging Face n'est pas limité aux modèles Transformers. Tout le monde peut partager n'importe quel modèle ou jeu de données s'il le souhaite ! Créez un compte sur huggingface.co pour bénéficier de toutes les fonctionnalités disponibles ! +⚠️ Le *Hub* n'est pas limité aux *transformers*. Tout le monde peut partager n'importe quel modèle ou jeu de données s'il le souhaite ! Créez un compte sur huggingface.co pour bénéficier de toutes les fonctionnalités disponibles ! -Avant de découvrir en détail comment les modèles Transformers fonctionnent, nous allons voir quelques exemples de comment ils peuvent être utilisés pour résoudre des problèmes NLP intéressants. +Avant de découvrir en détail comment les *transformers* fonctionnent, nous allons voir quelques exemples de comment ils peuvent être utilisés pour résoudre des problèmes intéressants de NLP. -## Travailler avec des pipelines +## Travailler avec les pipelines -L'outil le plus basique de la librairie 🤗 Transformers est la fonction `pipeline()`. Elle relie un modèle avec ses étapes de pré-traitement et de post-traitement, permettant d'entrer n'importe quel texte et d'obtenir une réponse compréhensible : +L'outil le plus basique de la bibliothèque 🤗 *Transformers* est la fonction `pipeline()`. Elle relie un modèle avec ses étapes de pré-traitement et de post-traitement, permettant d'entrer n'importe quel texte et d'obtenir une réponse compréhensible : ```python from transformers import pipeline classifier = pipeline("sentiment-analysis") -classifier("I've been waiting for a HuggingFace course my whole life.") +classifier( + "I've been waiting for a HuggingFace course my whole life." +) # J'ai attendu un cours d'HuggingFace toute ma vie. ``` ```python out @@ -50,7 +52,10 @@ On peut même passer plusieurs phrases ! ```python classifier( - ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!"] + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] # « J'ai attendu un cours d'HuggingFace toute ma vie. », « Je déteste tellement ça ! » ) ``` @@ -63,16 +68,16 @@ Par défaut, ce pipeline sélectionne un modèle pré-entraîné qui a été sp Il y a trois étapes principales lorsque vous passez du texte à un pipeline : -1. Le texte est pré-traité pour qu'il ait un format compréhensible par le modèle. -2. Les données pré-traitées sont passées au modèle. -3. Les prédictions du modèle sont post-traités, de sorte que vous puissiez les comprendre. +1. le texte est prétraité pour qu'il ait un format compréhensible par le modèle, +2. les données prétraitées sont passées au modèle, +3. les prédictions du modèle sont post-traitées de sorte que vous puissiez les comprendre. Voici une liste non-exhaustive des [pipelines disponibles](https://huggingface.co/transformers/main_classes/pipelines.html) : - `feature-extraction` (pour obtenir la représentation vectorielle d'un texte) - `fill-mask` -- `ner` (named entity recognition = reconnaissance des entités nommées) +- `ner` (*named entity recognition* → reconnaissance d'entités nommées) - `question-answering` - `sentiment-analysis` - `summarization` @@ -80,24 +85,24 @@ Voici une liste non-exhaustive des [pipelines disponibles](https://huggingface.c - `translation` - `zero-shot-classification` -Regardons de plus près certains d'entre-eux ! +Regardons de plus près certains d'entre eux ! -## Zero-shot classification +## *Zero-shot classification* -Nous allons commencer par nous attaquer à une tâche plus difficile où nous devons classer des textes qui n'ont pas été annotés. C'est un scénario très répandu dans les projets réels car l'annotation de texte est généralement longue et nécessite parfois une expertise dans un domaine. Pour ce cas d'usage, le pipeline `zero-shot-classification` est très puissant : il vous permet de spécifier les labels à utiliser pour la classification, de sorte que vous n'ayez pas à vous soucier des labels du modèle pré-entraîné. Nous avons déjà vu comment le modèle peut classer un texte comme positif ou négatif en utilisant ces deux labels, mais il peut également classer le texte en utilisant n'importe quel autre ensemble de labels que vous souhaitez. +Nous allons commencer par nous attaquer à une tâche plus difficile où nous devons classer des textes qui n'ont pas été annotés. C'est un scénario très répandu dans les projets réels car l'annotation de textes est généralement longue et nécessite parfois une expertise dans un domaine. Pour ce cas d'usage, le pipeline `zero-shot-classification` est très puissant : il vous permet de spécifier les labels à utiliser pour la classification, de sorte que vous n'ayez pas à vous soucier des labels du modèle pré-entraîné. Nous avons déjà vu comment le modèle peut classer un texte comme positif ou négatif en utilisant ces deux labels mais il peut également classer le texte en utilisant n'importe quel autre ensemble de labels que vous souhaitez. ```python from transformers import pipeline classifier = pipeline("zero-shot-classification") classifier( - "This is a course about the Transformers library", + "This is a course about the Transformers library", # C'est un cours sur la bibliothèque Transformers candidate_labels=["education", "politics", "business"], ) ``` ```python out -{'sequence': 'This is a course about the Transformers library', +{'sequence': 'This is a course about the Transformers library', # C'est un cours sur la bibliothèque Transformers 'labels': ['education', 'business', 'politics'], 'scores': [0.8445963859558105, 0.111976258456707, 0.043427448719739914]} ``` @@ -106,42 +111,44 @@ Ce pipeline est appelé _zero-shot_ car vous n'avez pas besoin d'entraîner spé -✏️ **Essayez-le !** Jouez avec vos propres séquences et labels et voyez comment le modèle fonctionne. +✏️ **Essayez !** Jouez avec vos propres séquences et labels et voyez comment le modèle fonctionne. ## Génération de texte -Maintenant, nous allons voir comment utiliser un pipeline pour générer du texte. L'idée principale ici est que vous fournissez seulement un extrait de texte qui va être complété par du texte généré automatiquement par le modèle. C'est similaire à la fonctionnalité de prédiction de texte qui est très présente dans les appareils mobiles. La génération de texte implique de l'aléatoire, donc il est normal que vous n'obteniez pas les mêmes résultats que ceux présentés ci-dessous. +Maintenant, nous allons voir comment utiliser un pipeline pour générer du texte. L'idée principale ici est que vous fournissez seulement un extrait de texte qui va être complété par du texte généré automatiquement par le modèle. Cette fonction est similaire à la fonction de texte prédictif que l'on trouve sur de nombreux téléphones portables. La génération de texte implique de l'aléatoire, donc il est normal que vous n'obteniez pas les mêmes résultats que ceux présentés ci-dessous. ```python from transformers import pipeline generator = pipeline("text-generation") -generator("In this course, we will teach you how to") +generator( + "In this course, we will teach you how to" +) # Dans ce cours, nous vous enseignerons comment ``` ```python out -[{'generated_text': 'In this course, we will teach you how to understand and use ' - 'data flow and data interchange when handling user data. We ' - 'will be working with one or more of the most commonly used ' - 'data flows — data flows of various types, as seen by the ' - 'HTTP'}] +[{'generated_text': 'In this course, we will teach you how to understand and use ' # Dans ce cours, nous vous enseignerons comment comprendre et utiliser + 'data flow and data interchange when handling user data. We ' # flux de données et l'échange de données lors de la manipulation des données utilisateur. Nous + 'will be working with one or more of the most commonly used ' # travailleront avec un ou plusieurs des plus couramment utilisés + 'data flows — data flows of various types, as seen by the ' # flux de données - flux de données de différents types, tels qu'ils sont vus par + 'HTTP'}] # HTTP ``` Il est possible de contrôler le nombre de séquences générées avec l'argument `num_return_sequences` et la longueur totale du texte généré avec l'argument `max_length`. -✏️ **Essayez-le !** Utilisez les arguments `num_return_sequences` et `max_length` pour générer deux phrases de 15 mots chacune. +✏️ **Essayez !** Utilisez les arguments `num_return_sequences` et `max_length` pour générer deux phrases de 15 mots chacune. -## Utiliser n'importe quel modèle du Hub dans un pipeline +## Utiliser n'importe quel modèle du *Hub* dans un pipeline -Les exemples précédents utilisaient le modèle par défaut pour la tâche en question, mais vous pouvez aussi choisir un modèle particulier du Hub et l'utiliser dans un pipeline pour une tâche spécifique — par exemple, la génération de texte. Rendez-vous sur la [page du Hub de modèles](https://huggingface.co/models?pipeline_tag=text-generation) et cliquez sur le tag correspondant sur la gauche pour afficher seulement les modèles supportés pour cette tâche. Vous devriez arriver sur une page comme [celle-ci](https://huggingface.co/models?pipeline_tag=text-generation). +Les exemples précédents utilisaient le modèle par défaut pour la tâche en question mais vous pouvez aussi choisir un modèle particulier du *Hub* et l'utiliser dans un pipeline pour une tâche spécifique comme par exemple la génération de texte. Rendez-vous sur le [*Hub*](https://huggingface.co/models) et cliquez sur le *filtre* correspondant sur la gauche pour afficher seulement les modèles supportés pour cette tâche. Vous devriez arriver sur une page comme [celle-ci](https://huggingface.co/models?pipeline_tag=text-generation). Essayons le modèle [`distilgpt2`](https://huggingface.co/distilgpt2) ! Voici comment charger le modèle dans le même pipeline que précédemment : @@ -150,35 +157,35 @@ from transformers import pipeline generator = pipeline("text-generation", model="distilgpt2") generator( - "In this course, we will teach you how to", + "In this course, we will teach you how to", # Dans ce cours, nous vous enseignerons comment max_length=30, num_return_sequences=2, ) ``` ```python out -[{'generated_text': 'In this course, we will teach you how to manipulate the world and ' - 'move your mental and physical capabilities to your advantage.'}, - {'generated_text': 'In this course, we will teach you how to become an expert and ' - 'practice realtime, and with a hands on experience on both real ' - 'time and real'}] +[{'generated_text': 'In this course, we will teach you how to manipulate the world and ' # Dans ce cours, nous vous enseignerons comment manipuler le monde et + 'move your mental and physical capabilities to your advantage.'}, # utiliser vos capacités mentales et physiques à votre avantage. + {'generated_text': 'In this course, we will teach you how to become an expert and ' # Dans ce cours, nous vous apprendrons comment devenir un expert et + 'practice realtime, and with a hands on experience on both real ' # pratique en temps réel, et avec une expérience pratique à la fois sur de vrais + 'time and real'}] # temps et réel ``` -Vous pouvez améliorer votre recherche de modèle en cliquant sur les tags de langue, et choisir un modèle qui génère du texte dans une autre langue. Le Hub de modèles contient également des sauvegardes pour des modèles multilingues qui supportent plusieurs langues. +Vous pouvez améliorer votre recherche de modèle en cliquant sur les *filtres* de langue et choisir un modèle qui génère du texte dans une autre langue. Le *Hub* contient également des *checkpoints* pour des modèles multilingues qui supportent plusieurs langues. Une fois que vous avez choisi un modèle, vous verrez que vous pouvez tester son fonctionnement en ligne directement. Cela vous permet de tester rapidement les capacités du modèle avant de le télécharger. -✏️ **Essayez-le !** Utilisez les filtres pour trouver un modèle de génération de texte pour une autre langue. N'hésitez pas à jouer avec le widget et l'utiliser dans un pipeline ! +✏️ **Essayez !** Utilisez les filtres pour trouver un modèle de génération de texte pour une autre langue. N'hésitez pas à jouer avec le *widget* et l'utiliser dans un pipeline ! ### L'API d'inférence -Tous les modèles peuvent être testé directement depuis votre navigateur en utilisant l'API d'inférence, qui est disponible sur le site [Hugging Face](https://huggingface.co/). Vous pouvez jouer avec le modèle directement sur sa page en entrant du texte personnalisé et en regardant le modèle traiter les données d'entrée. +Tous les modèles peuvent être testé directement depuis votre navigateur en utilisant l'API d'inférence qui est disponible sur le site [Hugging Face](https://huggingface.co/). Vous pouvez jouer avec le modèle directement sur sa page en entrant du texte personnalisé et en regardant le modèle traiter les données d'entrée. -L'API d'inférence qui est utilisée par le widget est également disponible en tant que produit payant, qui est utile si vous avez besoin de l'API pour votre travail. Consultez la [page des prix](https://huggingface.co/pricing) pour plus de détails. +L'API d'inférence qui est utilisée par le *widget* est également disponible en tant que produit payant si vous avez besoin de l'API pour votre travail. Consultez la [page des prix](https://huggingface.co/pricing) pour plus de détails. ## Remplacement des mots manquants @@ -192,11 +199,11 @@ unmasker("This course will teach you all about models.", top_k=2) ``` ```python out -[{'sequence': 'This course will teach you all about mathematical models.', +[{'sequence': 'This course will teach you all about mathematical models.', # Ce cours vous apprendra tout sur les modèles mathématiques. 'score': 0.19619831442832947, 'token': 30412, 'token_str': ' mathematical'}, - {'sequence': 'This course will teach you all about computational models.', + {'sequence': 'This course will teach you all about computational models.', # Ce cours vous apprendra tout sur les modèles mathématiques. 'score': 0.04052725434303284, 'token': 38163, 'token_str': ' computational'}] @@ -206,19 +213,21 @@ L'argument `top_k` permet de contrôler le nombre de possibilités que vous souh -✏️ **Essayez-le !** Recherchez le modèle `bert-base-cased` sur le Hub et identifiez le mot masqué dans l'outil d'inférence. Que prédit le modèle pour la phrase dans notre exemple de pipeline au-dessus ? +✏️ **Essayez !** Recherchez le modèle `bert-base-cased` sur le *Hub* et identifiez le mot masqué dans l'outil d'inférence. Que prédit le modèle pour la phrase dans notre exemple de pipeline au-dessus ? ## Reconnaissance d'entités nommées -La reconnaissance d'entités nommées (NER = Named Entity Recognition) est une tâche où le modèle doit trouver les parties du texte d'entrée qui correspondent à des entités telles que des personnes, des lieux ou des organisations. Voyons un exemple : +La reconnaissance d'entités nommées ou NER (pour *Named Entity Recognition*) est une tâche où le modèle doit trouver les parties du texte d'entrée qui correspondent à des entités telles que des personnes, des lieux ou des organisations. Voyons un exemple : ```python from transformers import pipeline ner = pipeline("ner", grouped_entities=True) -ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") +ner( + "My name is Sylvain and I work at Hugging Face in Brooklyn." +) # Je m'appelle Sylvain et je travaille à Hugging Face à Brooklyn. ``` ```python out @@ -230,11 +239,11 @@ ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") Nous pouvons voir que le modèle a correctement identifié Sylvain comme une personne (PER), Hugging Face comme une organisation (ORG) et Brooklyn comme un lieu (LOC). -Il est possible d'utiliser l'option `grouped_entities=True` lors de la création du pipeline pour regrouper les parties du texte qui correspondent à la même entité: ici le modèle à correctement regroupé "Hugging" et "Face" comme une seule organisation, même si le nom comporte plusieurs mots. En effet, comme nous allons voir dans la prochaine chapitre, la prétraitement du texte sépare parfois certains mots en plus petites parties. Par exemple, `Sylvain` est séparé en quatre morceaux: `S`, `##yl`, `##va`, et `##in`. Dans l'étape de post-traitement, le pipeline a réussi à regrouper ces morceaux. +Il est possible d'utiliser l'option `grouped_entities=True` lors de la création du pipeline pour regrouper les parties du texte qui correspondent à la même entité : ici le modèle à correctement regroupé `Hugging` et `Face` comme une seule organisation, même si le nom comporte plusieurs mots. En effet, comme nous allons voir dans le prochain chapitre, la prétraitement du texte sépare parfois certains mots en plus petites parties. Par exemple, `Sylvain` est séparé en quatre morceaux : `S`, `##yl`, `##va`, et `##in`. Dans l'étape de post-traitement, le pipeline a réussi à regrouper ces morceaux. -✏️ **Essayez-le !** Recherchez sur le Hub un modèle capable de reconnaître les différentes parties du langage (Part-of-speech = POS) en Anglais. Que prédit le modèle pour la phrase dans notre exemple du pipeline au-dessus ? +✏️ **Essayez !** Recherchez sur le *Hub* un modèle capable de reconnaître les différentes parties du langage (généralement abrégé en POS pour *Part-of-speech*) en anglais. Que prédit le modèle pour la phrase dans notre exemple du pipeline au-dessus ? @@ -247,8 +256,8 @@ from transformers import pipeline question_answerer = pipeline("question-answering") question_answerer( - question="Where do I work?", - context="My name is Sylvain and I work at Hugging Face in Brooklyn", + question="Where do I work?", # Où est-ce que je travaille ? + context="My name is Sylvain and I work at Hugging Face in Brooklyn", # Je m'appelle Sylvain et je travaille à Hugging Face à Brooklyn. ) ``` @@ -288,16 +297,37 @@ summarizer( and a lack of well-educated engineers. """ ) + +""" + L'Amérique a changé de façon spectaculaire au cours des dernières années. Non seulement le nombre de + diplômés dans les disciplines traditionnelles de l'ingénierie telles que le génie mécanique, civil, + l'électricité, la chimie et l'aéronautique a diminué, mais dans la plupart + des grandes universités américaines, les programmes d'études d'ingénierie se concentrent désormais sur + et encouragent largement l'étude des sciences de l'ingénieur. Par conséquent, il y a + de moins en moins d'offres dans les sujets d'ingénierie traitant de l'infrastructure, + l'environnement et les questions connexes, et une plus grande concentration sur les sujets de haute + technologie, qui soutiennent en grande partie des développements scientifiques de plus en plus + complexes. Si cette dernière est importante, elle ne doit pas se faire au détriment + de l'ingénierie plus traditionnelle. + + Les économies en développement rapide telles que la Chine et l'Inde, ainsi que d'autres + pays industrialisés d'Europe et d'Asie, continuent d'encourager et de promouvoir + l'enseignement de l'ingénierie. La Chine et l'Inde, respectivement, diplôment + six et huit fois plus d'ingénieurs traditionnels que les États-Unis. + Les autres pays industriels maintiennent au minimum leur production, tandis que l'Amérique + souffre d'une baisse de plus en plus importante du nombre de diplômés en ingénierie + et un manque d'ingénieurs bien formés. +""" ``` ```python out -[{'summary_text': ' America has changed dramatically during recent years . The ' - 'number of engineering graduates in the U.S. has declined in ' - 'traditional engineering disciplines such as mechanical, civil ' - ', electrical, chemical, and aeronautical engineering . Rapidly ' - 'developing economies such as China and India, as well as other ' - 'industrial countries in Europe and Asia, continue to encourage ' - 'and advance engineering .'}] +[{'summary_text': ' America has changed dramatically during recent years . The ' # L'Amérique a changé de façon spectaculaire au cours des dernières années. Le + 'number of engineering graduates in the U.S. has declined in ' # nombre de diplômés en ingénierie aux États-Unis a diminué dans + 'traditional engineering disciplines such as mechanical, civil ' # dans les disciplines traditionnelles de l'ingénierie, telles que le génie mécanique, civil + ', electrical, chemical, and aeronautical engineering . Rapidly ' # l'électricité, la chimie et l'aéronautique. Les économies + 'developing economies such as China and India, as well as other ' # en développement rapide comme la Chine et l'Inde, ainsi que d'autres + 'industrial countries in Europe and Asia, continue to encourage ' # pays industriels d'Europe et d'Asie, continuent d'encourager + 'and advance engineering.'}] # et à faire progresser l'ingénierie. ``` Comme pour la génération de texte, vous pouvez spécifier une `max_length` (longueur maximale) ou une `min_length` (longueur minimale) pour le résultat. @@ -305,7 +335,7 @@ Comme pour la génération de texte, vous pouvez spécifier une `max_length` (lo ## Traduction -Pour la traduction, vous pouvez utiliser un modèle par défaut si vous fournissez un couple de langues dans le nom de la tâche (comme `"translation_en_to_fr"`), mais le plus simple reste d'utiliser un modèle adéquat disponible sur le [Model Hub](https://huggingface.co/models). Ici, nous allons essayer de traduire du français en anglais : +Pour la traduction, vous pouvez utiliser un modèle par défaut si vous fournissez un couple de langues dans le nom de la tâche (comme `"translation_en_to_fr"`), mais le plus simple reste d'utiliser un modèle adéquat disponible sur le [*Hub*](https://huggingface.co/models). Ici, nous allons essayer de traduire du français en anglais : ```python from transformers import pipeline @@ -322,7 +352,7 @@ Comme pour la génération de texte et le résumé de texte, il est possible de -✏️ **Essayez-le !** Recherchez d'autres modèles de traduction sur le Hub et essayez de traduire la phrase précédente en plusieurs langues différentes. +✏️ **Essayez !** Recherchez d'autres modèles de traduction sur le Hub et essayez de traduire la phrase précédente en plusieurs langues différentes. diff --git a/chapters/fr/chapter1/4.mdx b/chapters/fr/chapter1/4.mdx index 33e1e29b3..01fd2df70 100644 --- a/chapters/fr/chapter1/4.mdx +++ b/chapters/fr/chapter1/4.mdx @@ -1,59 +1,58 @@ -# Comment fonctionnent les modèles Transformers ? +# Comment fonctionnent les *transformers* ? -Dans cette partie, nous allons jeter un coup d'oeil à l'architecture des modèles Transformers. +Dans cette partie, nous allons jeter un coup d'œil à l'architecture des *transformers*. -## Court historique des Transformers +## Court historique des *transformers* -Voici quelques dates clefs dans la courte histoire des modèles Transformers : +Voici quelques dates clefs dans la courte histoire des *transformers* :
A brief chronology of Transformers models.
-[L'architecture Transformer](https://arxiv.org/abs/1706.03762) a été présentée en Juin 2017. La recherche initiale portait sur les tâches de traduction. Cela s'est suivi par la présentation de plusieurs modèles influents, dont : +[L'architecture *Transformer*](https://arxiv.org/abs/1706.03762) a été présentée en juin 2017. Initialement, la recherche portait sur la tâche de traduction. Elle a été suivie par l'introduction de plusieurs modèles influents, notamment : -- **Juin 2018**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), le premier modèle Transformer pré-entraîné, utilisé pour fine-tuning sur différentes tâches de NLP et ayant obtenu des résultats à l'état de l'art +- **Juin 2018** : [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), le premier *transformer* pré-entraîné et *finetuné* sur différentes tâches de NLP et ayant obtenu des résultats à l'état de l'art, -- **Octobre 2018**: [BERT](https://arxiv.org/abs/1810.04805), autre modèle large pré-entraîné, ayant été construit pour produire de meilleurs résumés de texte (plus de détails dans le chapitre suivant !) +- **Octobre 2018** : [BERT](https://arxiv.org/abs/1810.04805), autre grand modèle pré-entraîné ayant été construit pour produire de meilleurs résumés de texte (plus de détails dans le chapitre suivant !), -- **Février 2019**: [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), une version améliorée (et plus grande) de GPT qui n'a pas été directement rendu publique pour cause de raisons éthiques +- **Février 2019** : [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), une version améliorée (et plus grande) de GPT qui n'a pas été directement rendu publique pour cause de raisons éthiques, -- **Octobre 2019**: [DistilBERT](https://arxiv.org/abs/1910.01108), une version distillée de BERT étant 60% plus rapide, 40% plus légère en mémoire, et conservant tout de même 97% performances initiales de BERT +- **Octobre 2019** : [DistilBERT](https://arxiv.org/abs/1910.01108), une version distillée de BERT étant 60% plus rapide, 40% plus légère en mémoire et conservant tout de même 97% des performances initiales de BERT, -- **Octobre 2019**: [BART](https://arxiv.org/abs/1910.13461) et [T5](https://arxiv.org/abs/1910.10683), deux modèles larges pré-entraînés utilisant la même architecture que le modèle Transformer originel (les premiers à faire cela) +- **Octobre 2019** : [BART](https://arxiv.org/abs/1910.13461) et [T5](https://arxiv.org/abs/1910.10683), deux modèles pré-entraînés utilisant la même architecture que le *transformer* original (les premiers à faire cela), -- **Mai 2020**, [GPT-3](https://arxiv.org/abs/2005.14165), une version encore plus grande que GPT-2 ayant des performances très bonnes sur une variété de tâches ne nécessitant pas de fine-tuning (appellé _zero-shot learning_) +- **Mai 2020** : [GPT-3](https://arxiv.org/abs/2005.14165), une version encore plus grande que GPT-2 ayant des performances très bonnes sur une variété de tâches ne nécessitant pas de *finetuning* (appelé _zero-shot learning_). -Cette liste est loin d'être exhaustive, et permet juste de mettre en lumière certains modèles Transformers. Plus largement, ces modèles peuvent être regroupés en trois catégories : +Cette liste est loin d'être exhaustive et met en lumière certains *transformers*. Plus largement, ces modèles peuvent être regroupés en trois catégories : -- GPT-like (aussi appelé modèles Transformers _auto-regressif_) -- BERT-like (aussi appelé modèles Transformers _auto-encodeur_) -- BART/T5-like (aussi appelé modèles Transformers _séquence-à-séquence_) +- ceux de type GPT (aussi appelés *transformers* _autorégressifs_) +- ceux de type BERT (aussi appelés *transformers* _auto-encodeurs_) +- ceux de type BART/T5 (aussi appelés *transformers* _séquence-à-séquence_) Nous verrons plus en profondeur ces familles de modèles plus tard. -## Transformers, des modèles du langage +## Les *transformers* sont des modèles de langage -Tous les modèles Transformers mentionné ci-dessus (GPT, BERT, BART, T5, etc.) ont été entraînés comme des *modèles du langage*. Cela signifie qu'ils ont été entraînés sur une large quantité de textes bruts en mode auto-supervisé. L'apprentissage auto-supervisé est un type d'entraînement où l'objectif est automatiquement déterminé par les entrées du modèle. Cela veut dire qu'une labellisation des données par des humains n'est pas nécessaire ! +Tous les *transformers* mentionnés ci-dessus (GPT, BERT, BART, T5, etc.) ont été entraînés comme des *modèles de langage*. Cela signifie qu'ils ont été entraînés sur une large quantité de textes bruts de manière autosupervisée. L'apprentissage autosupervisé est un type d'entraînement dans lequel l'objectif est automatiquement calculé à partir des entrées du modèle. Cela signifie que les humains ne sont pas nécessaires pour étiqueter les données ! -Ce type de modèle développe une compréhension statistique du langage sur lequel il est entraîné, mais cela n'est pas adapté à une tâche spécifique. A cause de cela, le modèle pré-entraîné doit ensuite suivre une procédure de *transfer learning*. Durant cette procédure, le modèle est fine-tuné en mode supervisé -- en utilisant des données labellisées par un human -- sur une tâche donnée. - -Un exemple de tâche est la prédiction du prochain mot de la phrase en ayant lu les *n* mots précédents. Ce procédé s'appelle *modélisation du langage causal* car les prédictions dépendent des entrées précédentes et actuelles, mais pas des suivantes. +Ce type de modèle développe une compréhension statistique de la langue sur laquelle il a été entraîné, mais il n'est pas très utile pour des tâches pratiques spécifiques. Pour cette raison, le modèle pré-entraîné passe ensuite par un processus appelé apprentissage par transfert. Au cours de ce processus, le modèle est *finetuné* de manière supervisée (c'est-à-dire en utilisant des étiquettes annotées par des humains) pour une tâche donnée. +Un exemple de tâche consiste à prédire le mot suivant dans une phrase après avoir lu les *n* mots précédents. Cette tâche est appelée *modélisation causale du langage* car la sortie dépend des entrées passées et présentes, mais pas des entrées futures.
Example of causal language modeling in which the next word from a sentence is predicted.
-Un autre exemple est la *modélisation du langage contextuel*, où me modèle doit prédire un mot masqué dans une phrase. +Un autre exemple est la *modélisation du langage masqué*, dans laquelle le modèle prédit un mot masqué dans la phrase.
Example of masked language modeling in which a masked word from a sentence is predicted.
-## Les modèles Transformers sont énormes +## Les *transformers* sont énormes En dehors de quelques exceptions (comme DistilBERT), la stratégie générale pour obtenir de meilleure performance consiste à augmenter la taille des modèles ainsi que la quantité de données utilisées pour l'entraînement de ces derniers. @@ -61,7 +60,7 @@ En dehors de quelques exceptions (comme DistilBERT), la stratégie générale po Number of parameters of recent Transformers models
-Malheureusement, entraîner un modèle, et particulièrement un modèle large, nécessite une importante quantité de données. Cela devient très coûteux en terme de temps et de ressources de calcul. Cela se traduit même par un impact environnemental, comme le montre le graphique suivant. +Malheureusement, entraîner un modèle et particulièrement un très grand modèle, nécessite une importante quantité de données. Cela devient très coûteux en termes de temps et de ressources de calcul. Cela se traduit même par un impact environnemental comme le montre le graphique suivant.
The carbon footprint of a large language model. @@ -70,45 +69,45 @@ Malheureusement, entraîner un modèle, et particulièrement un modèle large, n -Et cela présente un projet pour un (gigantesque) modèle mené par une équipe qui essaye consciemment de réduire l'impact environnemental du pré-entraînement. L'empreinte environnemental serait encore plus conséquente s'il fallait lancer beaucoup d'entraînements pour obtenir les meilleurs hyper-paramètres. +L'image montre l'empreinte carbone pour un projet d'entraînement d'un (très grand) modèle mené par une équipe qui pourtant essaie consciemment de réduire l'impact environnemental du pré-entraînement. L'empreinte de l'exécution de nombreux essais pour obtenir les meilleurs hyperparamètres serait encore plus élevée. -Imaginez si à chaque fois qu'une équipe de recherche, qu'une organisation étudiante ou qu'une entreprise voulait entraîner un modèle, ils devaient repartir de zéro. Ceci menerait à des énormes et non-nécessaires coûts environnementaux. +Imaginez qu'à chaque fois qu'une équipe de recherche, une association d'étudiants ou une entreprise souhaite entraîner un modèle, elle le fasse en partant de zéro. Cela entraînerait des coûts globaux énormes et inutiles ! C'est pourquoi le partage des modèles du langage est primordial : partager les poids d'entraînement et construire à partir de ces poids permet de réduire les coûts de calcul globaux ainsi que l'empreinte carbone de toute la communauté. -## Le Transfer Learning +## L'apprentissage par transfert -Le *Pré-entraînement* est le fait d'entraîner un modèle de zéro : les poids sont initialisés de manière aléatoire, et l'entraînement débute sans aucune connaissance préalable (ou heuristique). +Le pré-entraînement consiste à entraîner un modèle à partir de zéro : les poids sont initialisés de manière aléatoire et l'entraînement commence sans aucune connaissance préalable.
The pretraining of a language model is costly in both time and money.
-Ce pré-entraînement est habituellement réalisé sur de grande quantité de données. Il nécessite donc un très grand corpus de données, et l'entraînement peut prendre jusqu'à plusieurs semaines. +Ce pré-entraînement est généralement effectué sur de très grandes quantités de données. Il nécessite donc un très grand corpus de données et l'entraînement peut prendre jusqu'à plusieurs semaines. -Le *Fine-tuning*, d'un autre côté, est un entraînement effectué **après** qu'un modèle ait été pré-entraîné. Pour réaliser le fine-tuning, il faut d'abord récupèrer un modèle du langage pré-entraîné, puis lancé un entraînement sur un jeu de données adapté à la tâche cible. Mais attendez -- pourquoi ne pas simplement entraîné un modèle sur la tâche cible ? Voici plusieurs raisons : +Le *finetuning*, quant à lui, est l'entrainement effectué après qu'un modèle ait été pré-entraîné. Pour effectuer un *finetuning*, vous devez d'abord acquérir un modèle de langue pré-entraîné, puis effectuer un entraînement supplémentaire avec un jeu de données spécifiques. Mais pourquoi ne pas entraîner directement pour la tâche finale ? Il y a plusieurs raisons à cela : -* Le modèle pré-entraîné a déjà été entraîné sur un jeu de données qui a des similarités avec le jeu de données utilisé pour le fine-tuning. Ainsi le processus de fine-tuning peut bénéficier du savoir acquis par le modèle initial lors du pré-entraînement (par exemple, avec des problèmes de NLP, le modèle pré-entraîné aura une sorte de compréhension statistique de la langue associée à la tâche cible). -* Puisque le modèle pré-entraîné a déjà été entraîné sur beaucoup de données, le fine-tuning nécessite beaucoup moins de données pour obtenir des résultats décents. -* Pour la même raison, la quantité de temps et de ressources nécessaires pour obtenir de bons résultats est beaucoup moins grande. +* Le modèle pré-entraîné a déjà été entraîné sur un jeu de données qui présente certaines similitudes avec le jeu de données de *finetuning*. Le processus de *finetuning* est donc en mesure de tirer parti des connaissances acquises par le modèle initial lors du pré-entraînement (par exemple, pour les problèmes de langage naturel, le modèle pré-entraîné aura une certaine compréhension statistique de la langue que vous utilisez pour votre tâche) +* Comme le modèle pré-entraîné a déjà été entraîné sur de nombreuses données, le *finetuning* nécessite beaucoup moins de données pour obtenir des résultats décents. +* Pour la même raison, le temps et les ressources nécessaires pour obtenir de bons résultats sont beaucoup moins importants. -Par exemple, il serait possible de tirer prodit d'un modèle pré-entraîné sur la langue anglaise and ensuite fine-tuné sur un corpus arXiv, permettant d'obtenir un modèle basé sur la recherche et la science. Le fine-tuning ne nécessiterait qu'une quantité réduite de données : le savoir acquis par le modèle pré-entraîné est transféré au nouveau modèle, d'où le terme de *transfer learning*. +Par exemple, il est possible d'exploiter un modèle pré-entraîné entraîné sur la langue anglaise, puis de le *finetuner* sur un corpus arXiv, pour obtenir un modèle basé sur la science et la recherche. Le *finetuning* ne nécessitera qu'une quantité limitée de données : les connaissances acquises par le modèle pré-entraîné sont « transférées », d'où le terme d'apprentissage par transfert.
The fine-tuning of a language model is cheaper than pretraining in both time and money.
-Le fine-tuning d'un modèle nécessite ainsi moins de temps, de données, d'argent et a un impact environnemental réduit. Il est également possible d'itérer plus rapidement sur différentes stratégies de fine-tuning, puisque l'entraînement est moins contraignant qu'un entraînement complet. +Le *finetuning* d'un modèle a donc un coût moindre en termes de temps, de données, de finances et d'environnement. Il est aussi plus rapide et plus facile d'itérer sur différents schémas de *finetuning* car l'entraînement est moins contraignant qu'un pré-entraînement complet. -Ce procédé permet également d'obtenir de meilleurs résultats qu'un entraînement de zéro (sauf si on dispose de beaucoup de données), ce qui explique pourquoi il faut toujours essayer de tirer profit d'un modèle pré-entraîné -- un qui est le plus proche de la tâche cible -- et de le fine-tuner. +Ce processus permet également d'obtenir de meilleurs résultats que l'entraînement à partir de zéro (à moins que vous ne disposiez d'un grand nombre de données). C'est pourquoi vous devez toujours essayer de tirer parti d'un modèle pré-entraîné, c'est-à-dire un modèle aussi proche que possible de la tâche que vous avez à accomplir, et de le *finetuner*. ## Architecture générale -Dans cette section, nous allons voir l'architecture générale des modèles Transformers. Pas d'inquiétudes si vous ne comprenez pas tous les concepts; il y a des sections détaillées qui couvrent chaque composants plus tard. +Dans cette section, nous allons voir l'architecture générale des *transformers*. Pas d'inquiétudes si vous ne comprenez pas tous les concepts, des sections détaillées qui couvrent chaque composant seront abordées plus tard. @@ -116,55 +115,55 @@ Dans cette section, nous allons voir l'architecture générale des modèles Tran Le modèle est principalement composé de deux blocs : -* **Encodeur (gauche)**: L'encodeur reçoit une entrée et contruit une réprésentation de celle-ci (ses caractéristiques). Cela signifie que le modèle est optimisé pour acquérir une compréhension venant de ces entrées. -* **Décodeur (droite)**: Le décodeur utilise la réprésentation de l'encodeur (caractéristiques) en plus des autres entrées pour générer une séquence cible. Cela signifie que le modèle est optimisé pour générer des sorties. +* **Encodeur (à gauche)** : l'encodeur reçoit une entrée et construit une représentation de celle-ci (ses caractéristiques). Cela signifie que le modèle est optimisé pour acquérir une compréhension venant de ces entrées. +* **Décodeur (à droite)** : le décodeur utilise la représentation de l'encodeur (les caractéristiques) en plus des autres entrées pour générer une séquence cible. Cela signifie que le modèle est optimisé pour générer des sorties.
Architecture of a Transformers models
-Chacune de ces parties peuvent être utilisées indépendamment, en fonction de la tâche : +Chacun de ces blocs peuvent être utilisés indépendamment en fonction de la tâche que l'on souhaite traiter : -* **Modèles uniquement encodeurs** : Adaptés pour des tâches qui nécessitent une compréhension de l'entrée, comme la classification de phrases et la reconnaissance d'entités nommées. -* **Modèles uniquement décodeurs** : Adaptés pour les tâches génératives telles que la génération de texte. -* **Modèles encodeurs-décodeurs** ou **modèles de séquence-à-séquence** : Adaptés aux tâches génératives qui nécessitent une entrée, telles que la traduction ou le résumé de texte. +* **Modèles uniquement encodeurs** : adaptés pour des tâches qui nécessitent une compréhension de l'entrée, comme la classification de phrases et la reconnaissance d'entités nommées. +* **Modèles uniquement décodeurs** : adaptés pour les tâches génératives telles que la génération de texte. +* **Modèles encodeurs-décodeurs** (ou **modèles de séquence-à-séquence**) : adaptés aux tâches génératives qui nécessitent une entrée, telles que la traduction ou le résumé de texte. Nous verrons plus en détails chacune de ces architectures plus tard. ## Les couches d'attention -Une caractéristique clef des modèles Transformers est qu'ils sont construits avec des couches spéciales appellées *couches d'attention*. En fait, le titre de la publication qui a présenté l'architecture Transformer est ["Attention Is All You Need"](https://arxiv.org/abs/1706.03762) ! Nous allons explorer les détails des couches d'attention plus tard dans ce cours; pour le moment, ce qu'il faut retenir est que la couche d'attention va indiquer au modèle les mots, de la phrase d'entrée, sur lesquels porter son attention (et plus ou moins ignorer les autres) lorsqu'il s'agit de construire une représentation de chaque mot. +Une caractéristique clé des *transformers* est qu'ils sont construits avec des couches spéciales appelées couches d'attention. En fait, le titre du papier introduisant l'architecture *transformer* s'e nome [*Attention Is All You Need*](https://arxiv.org/abs/1706.03762) ! Nous explorerons les détails des couches d'attention plus tard dans le cours. Pour l'instant, tout ce que vous devez savoir est que cette couche indique au modèle de prêter une attention spécifique à certains mots de la phrase que vous lui avez passée (et d'ignorer plus ou moins les autres) lors du traitement de la représentation de chaque mot. -Pour mettre ceci en contexte, considérons la tâche de traduction de l'anglais vers le français. Étant donné l'entrée "You like this course", un modèle de traduction devra également s'occuper du mot adjacent "You" pour obtenir la traduction correcte du mot "like", car en français le verbe "aimer" est conjugué différemment selon l'objet. Le reste de la phrase, cependant, n'est pas utile pour la traduction de ce mot. De même, lors de la traduction de "this", le modèle devra également faire attention au mot "course", car "this" se traduit différemment selon que le nom associé est masculin ou féminin. Encore une fois, les autres mots de la phrase n'auront pas d'importance pour la traduction de "this". Avec des phrases plus complexes (et des règles de grammaire plus complexes), le modèle devrait accorder une attention particulière aux mots qui pourraient apparaître plus loin dans la phrase pour traduire correctement chaque mot. +Pour mettre cela en contexte, considérons la tâche de traduire un texte de l'anglais au français. Étant donné l'entrée « You like this course », un modèle de traduction devra également s'intéresser au mot adjacent « You » pour obtenir la traduction correcte du mot « like », car en français le verbe « like » se conjugue différemment selon le sujet. Le reste de la phrase n'est en revanche pas utile pour la traduction de ce mot. Dans le même ordre d'idées, pour traduire « this », le modèle devra également faire attention au mot « course » car « this » se traduit différemment selon que le nom associé est masculin ou féminin. Là encore, les autres mots de la phrase n'auront aucune importance pour la traduction de « this ». Avec des phrases plus complexes (et des règles de grammaire plus complexes), le modèle devra prêter une attention particulière aux mots qui pourraient apparaître plus loin dans la phrase pour traduire correctement chaque mot. -Le même concept s'applique à n'importe quelle tâche associée au langage naturel : un mot seul est porteur de sens, mais ce sens est profondément affecté par le contexte, qui peut être un ou plusieurs mots se situant avant ou après le mot étudié. +Le même concept s'applique à toute tâche associée au langage naturel : un mot en lui-même a un sens, mais ce sens est profondément affecté par le contexte, qui peut être n'importe quel autre mot (ou mots) avant ou après le mot étudié. -Maintenant que vous avez une idée plus précise des couches d'attentions, nous allons regarder de plus près l'architecture des modèles Transformers. +Maintenant que vous avez une idée plus précise des couches d'attentions, nous allons regarder de plus près l'architecture des *transformers*. -## L'architecture originelle +## L'architecture originale -L'architecture Transformer a initialement été construite pour des tâches de traduction. Pendant l'entraînement, l'encodeur reçoit des entrées (des phrases) dans une certaine langue, tandis que le décodeur reçoit la même phrase traduite dans la langue cible. Pour l'encodeur, les couches d'attention peuvent utiliser tous les mots d'une phrase (puisque, comme nous venons de le voir, la traduction d'un mot donné peut dépendre de ce qui le suit ou le précède dans la phrase). Le décodeur, quant à lui, fonctionne de façon séquentielle et ne peut porter son attention qu'aux mots déjà traduits dans la phrase (donc, uniquement les mots générés avant le mot en cours). Par exemple, lorsqu'on a prédit les trois premiers mots de la phrase cible, on les donne au décodeur qui utilise alors toutes les entrées de l'encodeur pour essayer de prédire le quatrième mot. +L'architecture du *transformer* a initialement été construite pour la tâche de traduction. Pendant l'entraînement, l'encodeur reçoit des entrées (des phrases) dans une certaine langue, tandis que le décodeur reçoit la même phrase traduite dans la langue cible. Pour l'encodeur, les couches d'attention peuvent utiliser tous les mots d'une phrase (puisque comme nous venons de le voir, la traduction d'un mot donné peut dépendre de ce qui le suit ou le précède dans la phrase). Le décodeur, quant à lui, fonctionne de façon séquentielle et ne peut porter son attention qu'aux mots déjà traduits dans la phrase (donc uniquement les mots générés avant le mot en cours). Par exemple, lorsqu'on a prédit les trois premiers mots de la phrase cible, on les donne au décodeur qui utilise alors toutes les entrées de l'encodeur pour essayer de prédire le quatrième mot. Pour accélérer les choses pendant l'apprentissage (lorsque le modèle a accès aux phrases cibles), le décodeur est alimenté avec la cible entière, mais il n'est pas autorisé à utiliser les mots futurs (s'il avait accès au mot en position 2 lorsqu'il essayait de prédire le mot en position 2, le problème ne serait pas très difficile !). Par exemple, en essayant de prédire le quatrième mot, la couche d'attention n'aura accès qu'aux mots des positions 1 à 3. -L'architecture originale des modèles Transformers ressemblait à ceci, avec l'encodeur à gauche et le décodeur à droite : +L'architecture originale du *transformer* ressemble à ceci, avec l'encodeur à gauche et le décodeur à droite :
Architecture of a Transformers models
-Notez que la première couche d'attention dans un bloc décodeur porte sont attention à toutes les entrées (passées) du décodeur, mais la seconde couche d'attention utilise la sortie de l'encodeur. Il peut ainsi accéder à toute la phrase d'entrée pour prédire au mieux le mot actuel. Ceci est très utile puisque les différentes langues peuvent avoir des règles grammaticales qui placent les mots dans des ordres différents, ou un contexte fourni plus tard dans la phrase peut être utile pour déterminer la meilleure traduction d'un mot donné. +Notez que la première couche d'attention dans un bloc décodeur prête attention à toutes les entrées (passées) du décodeur, mais que la deuxième couche d'attention utilise la sortie de l'encodeur. Elle peut donc accéder à l'ensemble de la phrase d'entrée pour prédire au mieux le mot actuel. C'est très utile, car différentes langues peuvent avoir des règles grammaticales qui placent les mots dans un ordre différent, ou un contexte fourni plus tard dans la phrase peut être utile pour déterminer la meilleure traduction d'un mot donné. -Le *masque d'attention* peut également être utilisé dans un modèle encodeur/décodeur pour l'empêcher de porter son attention sur certains mots spéciaux -- par exemple, le motde remplissage spécial utilisé pour que toutes les entrées aient la même longueur lors du regroupement de phrases. +Le *masque d'attention* peut également être utilisé dans l'encodeur/décodeur pour empêcher le modèle de prêter attention à certains mots spéciaux. Par exemple, le mot de remplissage spécial (le *padding*) utilisé pour que toutes les entrées aient la même longueur lors du regroupement de phrases. -## Architectures contre checkpoints +## Architectures contre *checkpoints* -En approfondissant l'étude des modèles Transformers dans ce cours, vous verrez des mentions d'*architectures* et de *checkpoints* ainsi que de *modèles*. Ces termes ont tous des significations légèrement différentes : +En approfondissant l'étude des *transformers* dans ce cours, vous verrez des mentions d'*architectures* et de *checkpoints* ainsi que de *modèles*. Ces termes ont tous des significations légèrement différentes : -* **Architecture**: C'est le squelette du modèle -- la définition de chaque couche et chaque opération qui se produit au sein du modèle. -* **Checkpoints**: Ce sont les poids qui seront chargés dans une architecture donnée. -* **Model**: C'est un mot valise n'étant pas aussi précis que les mots "architecture" ou "checkpoint": il peut désigner l'un comme l'autre. Dans ce cours, il sera spécifié *architecture* ou *checkpoint* lorsqu'il est nécessaire de réduire l'ambiguité. +* **Architecture** : c'est le squelette du modèle, la définition de chaque couche et chaque opération qui se produit au sein du modèle. +* **Checkpoints** : ce sont les poids qui seront chargés dans une architecture donnée. +* **Modèle** : c'est un mot valise n'étant pas aussi précis que les mots « architecture » ou « *checkpoint* ». Il peut désigner l'un comme l'autre. Dans ce cours, il sera spécifié *architecture* ou *checkpoint* lorsqu'il sera essentiel de réduire toute ambiguïté. -Par exemple, BERT est une architecture alors que `bert-base-cased`, un ensemble de poids entraîné par l'équipe de Google lors de la première sortie de BERT, est un checkpoint. Cependant, il est possible de dire "le modèle BERT" et "le modèle `bert-base-cased`". +Par exemple, BERT est une architecture alors que `bert-base-cased` (un ensemble de poids entraîné par l'équipe de Google lors de la première sortie de BERT) est un *checkpoint*. Cependant, il est possible de dire « le modèle BERT » et « le modèle `bert-base-cased` ». diff --git a/chapters/fr/chapter1/5.mdx b/chapters/fr/chapter1/5.mdx index 3a7b2a7fb..f77115aed 100644 --- a/chapters/fr/chapter1/5.mdx +++ b/chapters/fr/chapter1/5.mdx @@ -1,14 +1,14 @@ -# Les modèles encodeurs +# Les modèles basés sur l'encodeur -Les modèles encodeurs utilisent uniquement l'encodeur d'un modèle Transformer. À chaque étape, les couches d'attention peuvent accéder à tous les mots de la phrase initiale. Ces modèles sont souvent caractérisés comme ayant une attention "bi-directionnelle" et sont souvent appelés *modèles d'auto-encodage*. +Les modèles basés sur l'encodeur utilisent uniquement l'encodeur d'un *transformer*. À chaque étape, les couches d'attention peuvent accéder à tous les mots de la phrase initiale. Ces modèles sont souvent caractérisés comme ayant une attention bidirectionnelle et sont souvent appelés *modèles d'auto-encodage*. Le pré-entraînement de ces modèles se concentre généralement sur la modification d'une phrase donnée (par exemple, en masquant des mots aléatoires dans celle-ci) et en demandant au modèle de trouver ou de reconstruire la phrase initiale. -Ces modèles encodeurs sont les plus adaptés pour des tâches qui requièrent une compréhension complète de la phrase, telles que la classification de phrases, la reconnaissance des entités nommées (et plus généralement la classification de mots) et les questions-réponses extractives. +Ces modèles sont les plus adaptés pour des tâches qui requièrent une compréhension complète de la phrase, telles que la classification de phrases, la reconnaissance d'entités nommées (et plus généralement la classification de mots) et les questions-réponses extractives. -Les modèles les plus représentatifs de cette famille sont: +Les modèles les plus représentatifs de cette famille sont : - [ALBERT](https://huggingface.co/transformers/model_doc/albert.html) - [BERT](https://huggingface.co/transformers/model_doc/bert.html) diff --git a/chapters/fr/chapter1/6.mdx b/chapters/fr/chapter1/6.mdx index 21b8e6df0..490efe903 100644 --- a/chapters/fr/chapter1/6.mdx +++ b/chapters/fr/chapter1/6.mdx @@ -1,10 +1,10 @@ -# Les modèles décodeurs +# Les modèles basés sur le décodeur -Les modèles décodeurs utilisent seulement le décodeur d'un modèle Transformer. À chaque étape, pour un mot donné, les couches d'attention ne peuvent strictement accéder qu'aux mots situés avant dans la phrase. Ces modèles sont souvent appelés *modèles auto-régressifs*. +Les modèles basés sur le décodeur utilisent seulement le décodeur d'un *transformer*. À chaque étape, pour un mot donné, les couches d'attention ne peuvent strictement accéder qu'aux mots situés avant dans la phrase. Ces modèles sont souvent appelés *modèles autorégressifs*. -Le pré-entraînement des modèles décodeurs se concentre généralement sur la prédiction du prochain mot dans la phrase. +Le pré-entraînement des modèles basés sur le décodeur se concentre généralement sur la prédiction du prochain mot dans la phrase. Ces modèles sont vraiment adaptés aux tâches qui impliquent la génération de texte. diff --git a/chapters/fr/chapter1/7.mdx b/chapters/fr/chapter1/7.mdx index 6b1445075..a034cdb76 100644 --- a/chapters/fr/chapter1/7.mdx +++ b/chapters/fr/chapter1/7.mdx @@ -2,13 +2,13 @@ -Les modèles encodeur-décodeur (également appelés modèles de séquence-à-séquence) utilisent les deux parties de l'architecture Transformer. À chaque étape, les couches d'attention de l'encodeur peuvent accéder à tous les mots de la phrase initiale, tandis que les couches d'attention du décodeur n'ont accès qu'aux mots positionnés avant un mot donné en entrée de ces couches d'attention. +Les modèles encodeur-décodeur (également appelés modèles de séquence-à-séquence) utilisent les deux parties du *transformer*. À chaque étape, les couches d'attention de l'encodeur peuvent accéder à tous les mots de la phrase initiale, tandis que les couches d'attention du décodeur n'ont accès qu'aux mots positionnés avant un mot donné en entrée de ces couches d'attention. -Le pré-entraînement de ces modèles peut être fait en utilisant les objectifs des modèles d'encodeur ou de décodeur, mais en général cela implique quelque chose de plus complexe. Par exemple, le modèle [T5](https://huggingface.co/t5-base) est pré-entraîné en remplaçant des zones aléatoires de texte (qui peuvent contenir plusieurs mots) par un mot-masque spécial, et l'objectif est alors de prédire le texte que ce mot-masque remplace. +Le pré-entraînement de ces modèles peut être réalisé en utilisant les objectifs des modèles basés sur l'encodeur ou des modèles basés sur le décodeur. En général cela implique quelque chose de plus complexe. Par exemple, le modèle [T5](https://huggingface.co/t5-base) est pré-entraîné en remplaçant des zones aléatoires de texte (qui peuvent contenir plusieurs mots) par un masque spécial et l'objectif est alors de prédire le texte que ce masque cache. -Les modèles de séquence-à-séquence sont les plus adaptés pour les tâches liées à la génération de nouvelles phrases en fonction d'une entrée donnée, comme le résumé de texte, la traduction ou la génération de question-réponse. +Les modèles de séquence-à-séquence sont les plus adaptés pour les tâches liées à la génération de nouvelles phrases en fonction d'une entrée donnée, comme le résumé de texte, la traduction ou la génération de questions-réponses. -Les modèles qui représentent le mieux cette famille sont: +Les modèles qui représentent le mieux cette famille sont : - [BART](https://huggingface.co/transformers/model_doc/bart.html) - [mBART](https://huggingface.co/transformers/model_doc/mbart.html) diff --git a/chapters/fr/chapter1/8.mdx b/chapters/fr/chapter1/8.mdx index 1f3b19528..5719782ce 100644 --- a/chapters/fr/chapter1/8.mdx +++ b/chapters/fr/chapter1/8.mdx @@ -7,26 +7,26 @@ {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter1/section8.ipynb"}, ]} /> -Si vous souhaitez utiliser un modèle pré-entraîné ou une version fine-tunée de celui-ci en production, il est important d'avoir conscience que, bien que ces modèles soient puissants, ils ont des limites. La plus importante de ces limitations est que, pour permettre le pré-entraînement des modèles sur de grandes quantités de données, les chercheurs récupèrent souvent tout le contenu qu'ils peuvent trouver, en prenant le meilleur et le pire de ce qui est disponible sur internet. +Si vous souhaitez utiliser un modèle pré-entraîné ou une version *finetunée* de celui-ci en production, il est important d'avoir conscience que, bien que ces modèles soient puissants, ils ont des limites. La plus importante de ces limitations est que, pour permettre le pré-entraînement des modèles sur de grandes quantités de données, les chercheurs récupèrent souvent tout le contenu qu'ils peuvent trouver et donc en prenant le meilleur et le pire de ce qui est disponible sur internet. -Pour illustrer cela rapidement, revenons au modèle de remplacement de mot-masque avec le modèle BERT: +Pour illustrer cela rapidement, revenons au pipeline *fill-mask* avec le modèle BERT : ```python from transformers import pipeline unmasker = pipeline("fill-mask", model="bert-base-uncased") -result = unmasker("This man works as a [MASK].") +result = unmasker("This man works as a [MASK].") # Cet homme travaille comme [MASQUE] print([r["token_str"] for r in result]) -result = unmasker("This woman works as a [MASK].") +result = unmasker("This woman works as a [MASK].") # Cette femme travaille comme [MASQUE] print([r["token_str"] for r in result]) ``` ```python out -['lawyer', 'carpenter', 'doctor', 'waiter', 'mechanic'] -['nurse', 'waitress', 'teacher', 'maid', 'prostitute'] +['lawyer', 'carpenter', 'doctor', 'waiter', 'mechanic'] # [avocat, charpentier, médecin, serveur, mécanicien] +['nurse', 'waitress', 'teacher', 'maid', 'prostitute'] # ["infirmière", "serveuse", "professeur", "femme de chambre", "prostituée"] ``` -Lorsque l'on demande au modèle de remplacer le mot manquant dans ces deux phrases, il ne propose qu'un seul métier ne portant pas la marque du genre (waiter/waitress = serveur/serveuse). Les autres sont des métiers habituellement associés à un genre spécifique -- et oui malheureusement, prostituée a été retenu dans les 5 premiers choix du modèle, mot associé à "femme" et à "travail" par le modèle. Cela se produit même si BERT est l'un des rare modèles Transformer qui n'a pas été construit avec des données récupérées par scrapping sur internet, mais à l'aide de données en apparence neutres (il est entraîné sur les jeux de données suivants : [Wikipédia Anglais](https://huggingface.co/datasets/wikipedia) et [BookCorpus](https://huggingface.co/datasets/bookcorpus)). +Lorsque l'on demande au modèle de remplacer le mot manquant dans ces deux phrases, il ne propose qu'un seul métier ne portant pas la marque du genre (*waiter*/*waitress* → serveur/serveuse). Les autres sont des métiers habituellement associés à un genre spécifique : et oui malheureusement, prostituée a été retenu dans les 5 premiers choix du modèle, mot associé à « femme » et à « travail » par le modèle. Cela se produit même si BERT est l'un des rare *transformers* qui n'a pas été construit avec des données récupérées par *scrapping* sur internet, mais à l'aide de données en apparence neutres. En effet, il est entraîné sur les jeux de donnés [Wikipédia Anglais](https://huggingface.co/datasets/wikipedia) et [BookCorpus](https://huggingface.co/datasets/bookcorpus)). -Donc lorsque vous utilisez ce genre d'outils, il est important de garder en tête que le modèle que vous utilisez peut rapidement générer du contenu sexiste, raciste ou homophobe. Le fine-tuning de ce modèle sur vos données ne fera en aucun cas disparaître ce biais intrinsèque. +Donc lorsque vous utilisez ce genre d'outils, il est important de garder en tête que le modèle que vous utilisez peut rapidement générer du contenu sexiste, raciste ou homophobe. Le *finetuning* du modèle sur vos données ne fera en aucun cas disparaître ce biais intrinsèque. diff --git a/chapters/fr/chapter1/9.mdx b/chapters/fr/chapter1/9.mdx index c44ca177a..001c78df2 100644 --- a/chapters/fr/chapter1/9.mdx +++ b/chapters/fr/chapter1/9.mdx @@ -1,11 +1,11 @@ # Résumé du chapitre -Au cours de ce chapitre, vous avez vu comment approcher différents problèmes de NLP en utilisant la fonction `pipeline()` de la librairie 🤗 Transformers. Vous avez aussi vu comment rechercher et utiliser des modèles dans le Hub, ainsi que comment utiliser l'API d'inférence pour tester les modèles directement dans votre navigateur. +Au cours de ce chapitre, vous avez vu comment approcher différents problèmes de NLP en utilisant la fonction `pipeline()` de la bibliothèque 🤗 *Transformers*. Vous avez également vu comment rechercher et utiliser des modèles dans le *Hub* ainsi que comment utiliser l'API d'inférence pour tester les modèles directement dans votre navigateur. -Nous avons pu aborder le fonctionnement des modèles Transformers de façon générale, et nous avons parlé de l'importance du transfer learning et du fine-tuning. Un aspect important est que vous pouvez utiliser l'architecture complète ou seulement l'encodeur ou le décodeur, selon le type de tâche que vous souhaitez résoudre. Le tableau suivante résume ceci : +Nous avons pu aborder le fonctionnement des *transformers* de façon générale et parler de l'importance de l'apprentissage par transfert et du *finetuning*. Un point important est que vous pouvez utiliser l'architecture complète ou seulement l'encodeur ou le décodeur, selon le type de tâche que vous souhaitez résoudre. Le tableau suivant résume ceci : | Modèle | Exemples | Tâches | |-------------------|--------------------------------------------|----------------------------------------------------------------------------------------------| -| Encodeur | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa | Classification de phrase, reconnaissance des entités nommées, extraction de question-réponse | +| Encodeur | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa | Classification de phrase, reconnaissance d'entités nommées, extraction de question-réponse | | Décodeur | CTRL, GPT, GPT-2, Transformer XL | Génération de texte | -| Encodeur-décodeur | BART, T5, Marian, mBART | Résumé, traduction, génération de question-réponse | +| Encodeur-décodeur | BART, T5, Marian, mBART | Résumé, traduction, génération de question-réponse | \ No newline at end of file diff --git a/chapters/fr/chapter2/2.mdx b/chapters/fr/chapter2/2.mdx index 73946e36a..64f5e296b 100644 --- a/chapters/fr/chapter2/2.mdx +++ b/chapters/fr/chapter2/2.mdx @@ -270,7 +270,7 @@ Comme nous n'avons que deux phrases et deux étiquettes, le résultat que nous o ## Post-traitement de la sortie -Les valeurs que nous obtenons en sortie de notre modèle n'ont pas nécessairement de sens en elles-mêmes. Jetons-y un coup d'oeil : +Les valeurs que nous obtenons en sortie de notre modèle n'ont pas nécessairement de sens en elles-mêmes. Jetons-y un coup d’œil : ```python print(outputs.logits) diff --git a/chapters/fr/chapter2/5.mdx b/chapters/fr/chapter2/5.mdx index ab3b90b1e..8983c0590 100644 --- a/chapters/fr/chapter2/5.mdx +++ b/chapters/fr/chapter2/5.mdx @@ -216,7 +216,7 @@ batched_ids = [ ] ``` -L'identifiant du jeton de padding peut être trouvé dans `tokenizer.pad_token_id`. Utilisons-le et envoyons nos deux phrases à travers le modèle premièrement individuellement puis en étant mises dans un même batch : +L'identifiant du jeton de *padding* peut être trouvé dans `tokenizer.pad_token_id`. Utilisons-le et envoyons nos deux phrases à travers le modèle premièrement individuellement puis en étant mises dans un même batch : {#if fw === 'pt'} ```py no-format @@ -329,7 +329,7 @@ Remarquez comment la dernière valeur de la deuxième séquence est un identifia -✏️ **Essayez !** Appliquez la tokénisation manuellement sur les deux phrases utilisées dans la section 2 (« I've been waiting for a HuggingFace course my whole life. » et « I hate this so much! »). Passez-les dans le modèle et vérifiez que vous obtenez les mêmes logits que dans la section 2. Ensuite regroupez-les en utilisant le jeton de *padding* et créez le masque d'attention approprié. Vérifiez que vous obtenez les mêmes résultats qu’en passant par le modèle ! +✏️ **Essayez !** Appliquez la tokenisation manuellement sur les deux phrases utilisées dans la section 2 (« I've been waiting for a HuggingFace course my whole life. » et « I hate this so much! »). Passez-les dans le modèle et vérifiez que vous obtenez les mêmes logits que dans la section 2. Ensuite regroupez-les en utilisant le jeton de *padding* et créez le masque d'attention approprié. Vérifiez que vous obtenez les mêmes résultats qu’en passant par le modèle ! diff --git a/chapters/fr/chapter2/6.mdx b/chapters/fr/chapter2/6.mdx index f43edef28..00580b774 100644 --- a/chapters/fr/chapter2/6.mdx +++ b/chapters/fr/chapter2/6.mdx @@ -92,7 +92,7 @@ model_inputs = tokenizer(sequences, truncation=True) model_inputs = tokenizer(sequences, max_length=8, truncation=True) ``` -L'objet `tokenizer` peut gérer la conversion en des tenseurs de *frameworks* spécifiques. Ils peuvent ensuite être directement envoyés au modèle. Par exemple, dans le code suivant, nous demandons au *tokenizer* de retourner des tenseurs Pytorch lorsque l’on spécifie `"pt"`, de retourner des tenseurs TensorFlow lorsque l’on spécifie `"tf"` et des tableaux NumPy lorsque l’on indique `"np"` : +L'objet `tokenizer` peut gérer la conversion en des tenseurs de *frameworks* spécifiques. Ils peuvent ensuite être directement envoyés au modèle. Par exemple, dans le code suivant, nous demandons au *tokenizer* de retourner des tenseurs PyTorch lorsque l’on spécifie `"pt"`, de retourner des tenseurs TensorFlow lorsque l’on spécifie `"tf"` et des tableaux NumPy lorsque l’on indique `"np"` : ```py sequences = [ diff --git a/chapters/fr/chapter2/8.mdx b/chapters/fr/chapter2/8.mdx index b5ca1455a..7ea13a174 100644 --- a/chapters/fr/chapter2/8.mdx +++ b/chapters/fr/chapter2/8.mdx @@ -104,17 +104,17 @@ AutoNLP product?" + text: "Un modèle qui s'entraîne automatiquement sur vos données", + explain: "Incorrect. Vous confondez cela avec notre produit Model Hub to find the best checkpoint for your task!" + text: "Un modèle qui détecte automatiquement la langue utilisée pour ses entrées afin de charger les bonnes pondérations.", + explain: "Incorrect. Bien que certains checkpoints et modèles soient capables de gérer plusieurs langues, il n'existe pas d'outils intégrés pour la sélection automatique des checkpoints en fonction de la langue. Vous devez vous rendre sur le Hub des modèles pour trouver le meilleur checkpoint pour votre tâche !" } ]} /> @@ -130,7 +130,7 @@ }, { text: "Un objet qui renvoie la bonne architecture basée sur le checkpoint .", - explain: "Exactement : AutoModel a seulement besoin de connaître le checkpoint à partir duquel il doit s'initialiser pour retourner à la bonne architecture.", + explain: "Exactement : TFAutoModel a seulement besoin de connaître le checkpoint à partir duquel il doit s'initialiser pour retourner à la bonne architecture.", correct: true }, { @@ -196,7 +196,7 @@ choices={[ { text: "encode, car elle peut encoder du texte en identifiants et des identifiants en prédictions.", - explain: "Faux ! Bien que la méthode encode existe sur les *tokenizers*, elle n'existe pas sur les modèles." + explain: "Faux ! Bien que la méthode encode existe sur les tokenizer, elle n'existe pas sur les modèles." }, { text: "Appeler directement l'objet tokenizer", diff --git a/chapters/fr/chapter3/1.mdx b/chapters/fr/chapter3/1.mdx index 9c7960f54..62e096a33 100644 --- a/chapters/fr/chapter3/1.mdx +++ b/chapters/fr/chapter3/1.mdx @@ -2,27 +2,22 @@ # Introduction -Dans le [chapitre 2](/course/fr/chapter2) nous avons étudié comment utiliser - les tokenizers et les modèles pré-entraînés pour faire des prédictions. Et si - vous voulez faire le fine-tuning à partir d'un modèle pré-entraîné pour votre -propre jeu de données (dataset)? C'est le sujet de ce chapitre! Vous allez apprendre : - - - +Dans le [chapitre 2](/course/fr/chapter2) nous avons étudié comment utiliser les *tokenizers* et les modèles pré-entraînés pour faire des prédictions. +Mais que faire si vous souhaitez *finetuner* un modèle pré-entraîné pour votre propre jeu de données ? C'est le sujet de ce chapitre ! Vous allez apprendre à : + {#if fw === 'pt'} -* Comment préparer un très grand jeu de données à partir du Hub -* Comment utiliser 'Trainer', l'API de haut niveau, pour faire le fine-tuning d'un modèle -* Comment utiliser une boucle d'entraînement personnalisée -* Comment exploiter la librairie Accelerate proposée par 🤗 pour exécuter facilement cette boucle d'entraînement personnalisée sur n'importe quel +* savoir comment préparer un très grand jeu de données à partir du *Hub*, +* savoir comment utiliser l'API de haut niveau `Trainer` pour *finetuner* un modèle, +* savoir comment utiliser une boucle d'entraînement personnalisée, +* savoir comment tirer parti de la bibliothèque 🤗 *Accelerate* pour exécuter facilement cette boucle d'entraînement personnalisée sur n'importe quelle configuration distribuée. configuration distribuée {:else} -* Comment préparer un très grand jeu de données à partir du Hub -* Comment utiliser Keras pour faire le fine-tuning d'un modèle -* Comment utiliser Keras pour obtenir des prédictions -* Comment utiliser des métriques personnalisées - +* savoir comment préparer un très grand jeu de données à partir du *Hub*, +* savoir comment utiliser Keras pour *finetuner* un modèle, +* savoir comment utiliser Keras pour obtenir des prédictions, +* savoir comment utiliser des métriques personnalisées. {/if} -Pour mettre à disposition vos checkpoints d'entraînement sur le Hub d'Hugging Face, vous aurez besoin d'un compte huggingface.com : [créer un compte](https://huggingface.co/join) +Afin de télécharger vos *checkpoints* entraînés sur le *Hub* Hugging Face, vous aurez besoin d'un compte huggingface.co : [créer un compte](https://huggingface.co/join) \ No newline at end of file diff --git a/chapters/fr/chapter3/2.mdx b/chapters/fr/chapter3/2.mdx index 803493cdc..a9534da88 100644 --- a/chapters/fr/chapter3/2.mdx +++ b/chapters/fr/chapter3/2.mdx @@ -23,23 +23,24 @@ {/if} {#if fw === 'pt'} -Continuons avec [l'exemple précédent](/course/chapter2), voilà comment on entraîne un classifieur de séquences sur un batch avec PyTorch : -s + +En continuant avec l'exemple du [chapitre précédent](/course/fr/chapter2), voici comment entraîner un classifieur de séquences sur un batch avec PyTorch : + ```python import torch from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification -# Same as before +# Même chose que précédemment checkpoint = "bert-base-uncased" tokenizer = AutoTokenizer.from_pretrained(checkpoint) model = AutoModelForSequenceClassification.from_pretrained(checkpoint) sequences = [ - "I've been waiting for a HuggingFace course my whole life.", - "This course is amazing!", + "I've been waiting for a HuggingFace course my whole life.", # J'ai attendu un cours de HuggingFace toute ma vie. + "This course is amazing!", # Ce cours est incroyable ! ] batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") -# This is new +# Ceci est nouveau batch["labels"] = torch.tensor([1, 1]) optimizer = AdamW(model.parameters()) @@ -48,24 +49,25 @@ loss.backward() optimizer.step() ``` {:else} -Continuons avec [l'exemple précédent](/course/chapter2), voilà comment on entraîne un classifieur de séquences sur un batch avec TensorFlow : + +En continuant avec l'exemple du [chapitre précédent](/course/fr/chapter2), voici comment entraîner un classifieur de séquences sur un batch avec TensorFlow : ```python import tensorflow as tf import numpy as np from transformers import AutoTokenizer, TFAutoModelForSequenceClassification -# Same as before +# Même chose que précédemment checkpoint = "bert-base-uncased" tokenizer = AutoTokenizer.from_pretrained(checkpoint) model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) sequences = [ - "I've been waiting for a HuggingFace course my whole life.", - "This course is amazing!", + "I've been waiting for a HuggingFace course my whole life.", # J'ai attendu un cours de HuggingFace toute ma vie. + "This course is amazing!", # Ce cours est incroyable ! ] batch = dict(tokenizer(sequences, padding=True, truncation=True, return_tensors="tf")) -# This is new +# Ceci est nouveau model.compile(optimizer="adam", loss="sparse_categorical_crossentropy") labels = tf.convert_to_tensor([1, 1]) model.train_on_batch(batch, labels) @@ -73,9 +75,9 @@ model.train_on_batch(batch, labels) {/if} Evidemment, entraîner un modèle avec seulement deux phrases ne va pas donner de bons résultats. Pour obtenir de meilleurs résultats, vous allez avoir à préparer un plus grand jeu de données. -Dans cette section, nous allons utiliser comme exemple le jeu de données (dataset) MRPC (Microsoft Research Paraphrase Corpus) présenté dans un [papier](https://www.aclweb.org/anthology/I05-5002.pdf) de William B. Dolan et Chris Brockett. Ce jeu de données contient 5801 paires de phrases avec un label indiquant si ces paires sont des paraphrases ou non (i.e. si elles ont la même signification). Nous l'avons choisi pour ce chapitre parce que c'est un petit jeu de données, et cela rend donc facile les expériences d'entraînement sur ce dataset. +Dans cette section, nous allons utiliser comme exemple le jeu de données MRPC (*Microsoft Research Paraphrase Corpus*) présenté dans un [papier](https://www.aclweb.org/anthology/I05-5002.pdf) par William B. Dolan et Chris Brockett. Ce jeu de données contient 5801 paires de phrases avec un label indiquant si ces paires sont des paraphrases ou non (i.e. si elles ont la même signification). Nous l'avons choisi pour ce chapitre parce que c'est un petit jeu de données et cela rend donc simples les expériences d'entraînement sur ce jeu de données. -### Charger un jeu de données depuis le Hub +### Charger un jeu de données depuis le *Hub* {#if fw === 'pt'} @@ -83,9 +85,9 @@ Dans cette section, nous allons utiliser comme exemple le jeu de données (datas {/if} -Le hub ne contient pas juste des modèles; il contient aussi plusieurs jeux de données dans un tas de langages différents. 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 de Machine Learning sur 10 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_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. -La librairie de datasets de 🤗 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 : +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 : ```py from datasets import load_dataset @@ -125,10 +127,11 @@ raw_train_dataset[0] ```python out {'idx': 0, 'label': 1, - 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', - 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} + 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', # Amrozi a accusé son frère, qu'il a appelé « le témoin », de déformer délibérément son témoignage. + 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} # Se référant à lui uniquement comme « le témoin », Amrozi a accusé son frère de déformer délibérément son témoignage. ``` -Nous pouvons voir que les labels sont déjà des entiers, nous n'avons donc aucun preprocessing à faire sur les labels. Pour savoir quel entier correspond à quel label, on peut inspecter les `features` de notre `raw_train_dataset`. Cela nous dira le type de chaque colonne : + +Nous pouvons voir que les étiquettes sont déjà des entiers, donc nous n'aurons pas à faire de prétraitement ici. Pour savoir quel entier correspond à quel label, nous pouvons inspecter les `features` de notre `raw_train_dataset`. Cela nous indiquera le type de chaque colonne : ```py raw_train_dataset.features @@ -141,15 +144,14 @@ raw_train_dataset.features 'idx': Value(dtype='int32', id=None)} ``` -En réalité, `label` est de type `ClassLabel`, et la correspondance des entiers aux noms des labels est enregistrée le dossier *names*. `0` correspond à `not_equivalent` (pas équivalent), et `1` correspond à `equivalent`. +En réalité, `label` est de type `ClassLabel` et la correspondance des entiers aux noms des labels est enregistrée le dossier *names*. `0` correspond à `not_equivalent` et `1` correspond à `equivalent`. -✏️ **Essayez ceci!** Regardez l'élément 15 du jeu d'entraînement et l'élément 87 du jeu de validation. Quels sont leurs labels? - +✏️ **Essayez !** Regardez l'élément 15 de l'ensemble d'entraînement et l'élément 87 de l'ensemble de validation. Quelles sont leurs étiquettes ? -### Preprocessing d'un jeu de donnée +### Prétraitement d'un jeu de données {#if fw === 'pt'} @@ -157,7 +159,7 @@ En réalité, `label` est de type `ClassLabel`, et la correspondance des entiers {/if} -Pour prétraiter l'ensemble de données, nous devons convertir le texte en nombres que le modèle peut comprendre. Comme vous l'avez vu dans le [chapitre précédent](/course/chapter2), cela se fait avec un tokenizer. Nous pouvons passer au tokenizer une phrase ou une liste de phrases, ainsi nous pouvons directement tokeniser toutes les premières phrases et toutes les deuxièmes phrases de chaque paire comme ceci : +Pour prétraiter le jeu de données, nous devons convertir le texte en chiffres compréhensibles par le modèle. Comme vous l'avez vu dans le [chapitre précédent](/course/fr/chapter2), cette conversion est effectuée par un *tokenizer*. Nous pouvons fournir au *tokenizer* une phrase ou une liste de phrases, de sorte que nous pouvons directement tokeniser toutes les premières phrases et toutes les secondes phrases de chaque paire comme ceci : ```py from transformers import AutoTokenizer @@ -168,10 +170,12 @@ tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"]) tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"]) ``` -Néanmoins, nous ne pouvons pas simplement transmettre deux séquences au modèle et obtenir une prédiction indiquant si les deux phrases sont des paraphrases ou non. Nous devons gérer les deux séquences comme une paire et appliquer le prétraitement approprié. Heureusement, le tokenizer peut également prendre une paire de séquences et la mettre dans le format attendu par notre modèle BERT : +Cependant, nous ne pouvons pas simplement passer deux séquences au modèle et obtenir une prédiction pour savoir si les deux phrases sont des paraphrases ou non. Nous devons traiter les deux séquences comme une paire, et appliquer le prétraitement approprié. Heureusement, le *tokenizer* peut également prendre une paire de séquences et la préparer de la manière attendue par notre modèle BERT : ```py -inputs = tokenizer("This is the first sentence.", "This is the second one.") +inputs = tokenizer( + "This is the first sentence.", "This is the second one." +) # "C'est la première phrase.", "C'est la deuxième." inputs ``` @@ -183,44 +187,44 @@ inputs } ``` -Nous avons vu les clés `input_ids` et `attention_mask` au [chapitre 2](/course/chapter2), mais nous avons omis de parler des `token_type_ids`. Dans cet exemple, c'est ce qui indique au modèle quelle partie de l'entrée représente la première phrase et quelle partie représente la deuxième phrase. +Nous avons discuté des clés `input_ids` et `attention_mask` dans le [Chapitre 2](/course/fr/chapter2), mais nous avons laissé de côté les `token_type_ids`. Dans cet exemple, c'est ce qui indique au modèle quelle partie de l'entrée est la première phrase et quelle partie est la deuxième phrase. -✏️ **Essayez ceci!** Prenez l'élément 15 de l'ensemble d'entraînement et tokenisez les deux phrases séparément et comme une paire. Quelle est la différence entre les deux résultats ? +✏️ **Essayez !** Prenez l'élément 15 de l'ensemble d'entraînement et tokenisez les deux phrases séparément et par paire. Quelle est la différence entre les deux résultats ? -Si nous décodons les IDs contenus dans `input_ids` pour réobtenir des mots : +Si on décode les IDs dans `input_ids` en mots : ```py tokenizer.convert_ids_to_tokens(inputs["input_ids"]) ``` -Nous obtiendrons : +nous aurons : ```python out ['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] ``` -Nous pouvons donc voir que le modèle attend une entrée de la forme `[CLS] sentence1 [SEP] sentence2 [SEP]` lorsqu'il y a deux phrases. Aligner cette représentation avec `token_type_ids` nous donne : +Nous voyons donc que le modèle s'attend à ce que les entrées soient de la forme `[CLS] phrase1 [SEP] phrase2 [SEP]` lorsqu'il y a deux phrases. En alignant cela avec les `token_type_ids`, on obtient : ```python out ['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] ``` -Comme vous pouvez le voir, les parties correspondant à `[CLS] sentence1 [SEP]` ont toutes `0` comme ID de type de token, alors que les autres parties qui correspondent à `sentence2 [SEP]`, ont toute `1` comme ID de type de token. +Comme vous pouvez le voir, les parties de l'entrée correspondant à `[CLS] sentence1 [SEP]` ont toutes un *token* de type ID de `0`, tandis que les autres parties, correspondant à `sentence2 [SEP]`, ont toutes un *token* de type ID de `1`. -Notez que si vous sélectionnez un checkpoint différent, vous n'aurez pas nécessairement les `token_type_ids` dans vos entrées tokenisées (par exemple, ils ne sont pas retournés lorsque vous utilisez le modèle DistilBERT). Ils ne sont renvoyés que lorsque le modèle sait les utiliser, pour les avoir vu pendant le pré-entraînement. +Notez que si vous choisissez un autre *checkpoint*, vous n'aurez pas nécessairement les `token_type_ids` dans vos entrées tokenisées (par exemple, ils ne sont pas retournés si vous utilisez un modèle DistilBERT). Ils ne sont retournés que lorsque le modèle sait quoi faire avec eux, parce qu'il les a vus pendant son pré-entraînement. -Ici, BERT est pré-entraîné avec des IDs de type de token, et en plus de la fonction de coût MLM (Masked Language Modelling) dont nous avons parlé au [chapitre 1](/course/chapter1), il a un coût supplémentaire appelé _prédiction de la phrase suivante_(_next sentence prediction_). Le but de cette tâche est de modéliser la relation entre des paires de phrases. +Ici, BERT est pré-entraîné avec les *tokens* de type ID et en plus de l'objectif de modélisation du langage masqué dont nous avons abordé dans [Chapitre 1](/course/fr/chapter1), il a un objectif supplémentaire appelé _prédiction de la phrase suivante_. Le but de cette tâche est de modéliser la relation entre des paires de phrases. -Pour la prédiction de la phrase suivante, le modèle reçoit des paires de phrases (avec des tokens masqués de manière aléatoire) et on lui demande de prédire si la deuxième phrase suit la première. Pour rendre la tâche non triviale, la moitié du temps les phrases se suivent dans le document original dont elles sont extraites, et l'autre moitié du temps les deux phrases proviennent de deux documents différents. +Avec la prédiction de la phrase suivante, on fournit au modèle des paires de phrases (avec des *tokens* masqués de manière aléatoire) et on lui demande de prédire si la deuxième phrase suit la première. Pour rendre la tâche non triviale, la moitié du temps, les phrases se suivent dans le document d'origine dont elles ont été extraites, et l'autre moitié du temps, les deux phrases proviennent de deux documents différents. -En général, vous n'avez pas à vous soucier de savoir s'il y a ou non des `token_type_ids` dans vos entrées tokenisées : tant que vous utilisez le même checkpoint pour le tokenizer et le modèle, tout ira bien car le tokenizer sait quoi fournir à son modèle. +En général, vous n'avez pas besoin de vous inquiéter de savoir s'il y a ou non des `token_type_ids` dans vos entrées tokenisées : tant que vous utilisez le même *checkpoint* pour le *tokenizer* et le modèle, tout ira bien puisque le *tokenizer* sait quoi fournir à son modèle. -Maintenant que nous avons vu comment notre tokenizer peut traiter une paire de phrases, nous pouvons l'utiliser pour tokeniser l'ensemble de nos données : comme dans le [chapitre précédent](/course/chapter2), nous pouvons fournir au tokenizer une liste de paires de phrases en lui donnant la liste des premières phrases, puis la liste des secondes phrases. Ceci est également compatible avec les options de padding et de troncature que nous avons vues au [chapitre 2](/course/chapter2). Ainsi, une façon de prétraiter l'ensemble de données d'entraînement est : +Maintenant que nous avons vu comment notre *tokenizer* peut traiter une paire de phrases, nous pouvons l'utiliser pour tokeniser l'ensemble de notre jeu de données : comme dans le [chapitre précédent](/course/fr/chapter2), nous pouvons fournir au *tokenizer* une liste de paires de phrases en lui donnant la liste des premières phrases, puis la liste des secondes phrases. Ceci est également compatible avec les options de remplissage et de troncature que nous avons vues dans le [Chapitre 2](/course/fr/chapter2). Voici donc une façon de prétraiter le jeu de données d'entraînement : ```py tokenized_dataset = tokenizer( @@ -231,27 +235,27 @@ tokenized_dataset = tokenizer( ) ``` -Cela marche bien, mais a le désavantage 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 marchera que si vous avez assez de RAM pour contenir tout le jeu de données pendant la tokenisation (tandis que les jeux de données de la librairie de 🤗 sont des fichiers [Apache Arrow](https://arrow.apache.org/) chargées sur le disque, de sorte que vous ne gardez que les exemples dont vous avez besoin en mémoire ) +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 garder les données sous forme de dataset (jeu de données), nous allons utiliser la méthode [`Dataset.map()`](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.map). Cela nous permet d'avoir plus de flexibilité si nous avons besoin de faire plus que juste tokeniser pendant la phase de pré-traîtement. La méthode `map()` fonctionne en applicant une fonction à chaque élément du dataset, définissons alors 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.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 : ```py def tokenize_function(example): return tokenizer(example["sentence1"], example["sentence2"], truncation=True) ``` -Cette prend en entrée un dictionnaire (comme les items de notre dataset) and renvioe un nouveau dictionnaire avec les clés `input_ids`, `attention_mask` et `token_type_ids`. Notez que cela marche aussi si le dictionnaire `example` contient plusieurs exemples puisque le tokenizer fonctionne aussi sur des listes de paires de phrases, comme nous l'avons vu précédemment. Cela permettra d'utiliser l'option `batched=True` dans notre appel de la méthode `map()`, ce qui permettra d'accélérer la tokenisation. Le `tokenizer` est aidé par un tokenizer écrit en Rust provenant de la librairie [Tokenizers](https://github.com/huggingface/tokenizers) de 🤗. Ce tokenizer peut être très rapide, mais seulement si on lui donne beaucoup d'entrées en même temps. +Cette fonction prend un dictionnaire (comme les éléments de notre jeu de données) et retourne un nouveau dictionnaire avec les clés `input_ids`, `attention_mask`, et `token_type_ids`. Notez que cela fonctionne également si le dictionnaire `example` contient plusieurs échantillons (chaque clé étant une liste de phrases) puisque le `tokenizer` travaille sur des listes de paires de phrases, comme vu précédemment. Cela nous permettra d'utiliser l'option `batched=True` dans notre appel à `map()`, ce qui accélérera grandement la tokénisation. Le `tokenizer` est soutenu par un *tokenizer* écrit en Rust à partir de la bibliothèque [🤗 *Tokenizers*](https://github.com/huggingface/tokenizers). Ce *tokenizer* peut être très rapide, mais seulement si on lui donne beaucoup d'entrées en même temps. -Notez que nous avons ignorer l'argument `padding` dans notre fonction de tokenisation pour l'instant. Ceci parce que faire le padding de tous les exemples à la longueur maximale n'est pas efficace : il vaut mieux faire le padding lorsque nous construisons un batch, puisque dans ce cas nous allons seulement faire le padding pour la longueur maximale dans ce batch, et non pour la longueur maximale de tout le dataset. Cela permet d'économiser beaucoup de temps et de puissance de calcul lorsqu'on traite des entrées avec des longueurs trés variées! +Notez que nous avons laissé l'argument `padding` hors de notre fonction de *tokenizer* pour le moment. C'est parce que le *padding* de tous les échantillons à la longueur maximale n'est pas efficace : il est préférable de remplir les échantillons lorsque nous construisons un batch, car alors nous avons seulement besoin de remplir à la longueur maximale dans ce batch, et non la longueur maximale dans l'ensemble des données. Cela peut permettre de gagner beaucoup de temps et de puissance de traitement lorsque les entrées ont des longueurs très variables ! -Voilà comment on applique la fonction de tokenisation à tous nos datasets en même temps. Nous utilisons `batched=True` dans notre appel de `map` pour que la fonction soit appliquée à plusieurs éléments de notre dataset en même temps, et non sur chaque élément séparément. Cela permet d'avoir un pré-traîtement plus rapide. +Voici comment nous appliquons la fonction de tokenization sur tous nos jeux de données en même temps. Nous utilisons `batched=True` dans notre appel à `map` pour que la fonction soit appliquée à plusieurs éléments de notre jeu de données en une fois, et non à chaque élément séparément. Cela permet un prétraitement plus rapide. ```py tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) tokenized_datasets ``` -La librairie Datasets de 🤗 applique le processing en ajoutant de nouveaux champs aux datasets, un nouveau pour chaque clé retournée par la fonction de preprocessing : +La façon dont la bibliothèque 🤗 *Datasets* applique ce traitement consiste à ajouter de nouveaux champs aux jeux de données, un pour chaque clé du dictionnaire renvoyé par la fonction de prétraitement : ```python out DatasetDict({ @@ -270,24 +274,24 @@ DatasetDict({ }) ``` -Vous pouvez même utiliser le multiprocessing en appliquant votre pré-traîtement avec `map()` en lui passant l'argument `num_proc`. Nous ne l'avons pas utilisé ici parce que la librairie Tokenizers de 🤗 utilise plusieurs threads pour tokeniser nos exemples plus rapidement, mais si vous n'utilisez pas de tokenizer rapide qui s'aide de cette librairie, cela pourrait accélérer votre pré-traîtement. +Vous pouvez même utiliser le multitraitement lorsque vous appliquez votre fonction de prétraitement avec `map()` en passant un argument `num_proc`. Nous ne l'avons pas fait ici parce que la bibliothèque 🤗 *Tokenizers* utilise déjà plusieurs *threads* pour tokeniser nos échantillons plus rapidement, mais si vous n'utilisez pas un *tokenizer* rapide soutenu par cette bibliothèque, cela pourrait accélérer votre prétraitement. -Notre `tokenize_function` retourne un dictionnaire avec les clés `input_ids`, `attention_mask` et `token_type_ids`, donc ces trois champs sont ajoutés à toutes les parties (entraînement, validation et test) de notre dataset. Notez que nous aurions aussi pu changer des champs existants si notre pré-traîtement retournait une nouvelle valeur pour une clé qui existait déjà dans le dataset sur lequel nous avons appelé `map()`. +Notre `tokenize_function` retourne un dictionnaire avec les clés `input_ids`, `attention_mask`, et `token_type_ids`, donc ces trois champs sont ajoutés à toutes les divisions de notre jeu de données. Notez que nous aurions également pu modifier des champs existants si notre fonction de prétraitement avait retourné une nouvelle valeur pour une clé existante dans l'ensemble de données auquel nous avons appliqué `map()`. -La dernière chose que nous aurons besoin de faire est le padding de tous les éléments pour que leur longueur atteigne la longueur de la plus longue séquence du batch lorsque nous construisons les batchs — une technique que nous appelons *padding dynamique*. +La dernière chose que nous devrons faire est de remplir tous les exemples à la longueur de l'élément le plus long lorsque nous regroupons les éléments, une technique que nous appelons le *padding dynamique*. -### Padding dynamique +### *Padding* dynamique {#if fw === 'pt'} -La fonction responsable de mettre en ensemble les éléments pour en faire un batch est appelée *fonction d'assemblage* -*collate function*-. C'est un argument que vous pouvez fournir lorsque vous construisez un `DataLoader`, par défaut il s'agit d'une fonction qui va juste convertir les éléments en type tensor de Pytorch et les concaténer (récursivement si les éléments sont des listes, des tuples ou des dictionnaires). Cela -la concaténation- ne sera pas possible dans notre cas puisque toutes les entrées n'auront pas la même taille. Nous avons volontairement reporter à plus tard le padding, pour n'appliquer que le padding nécessaire pour chaque batch et éviter d'avoir des entrées excessivement longues avec beaucoup de padding. Cela va beaucoup accélérer l'entraînement, notez cependant que si vous faites l'entraînement sur TPU cela peut poser des problèmes — Les TPUs préfèrent une taille fixe même si cela requiert beaucoup de padding. +La fonction qui est responsable de l'assemblage des échantillons dans un batch est appelée *fonction de rassemblement*. C'est un argument que vous pouvez passer quand vous construisez un `DataLoader`, la valeur par défaut étant une fonction qui va juste convertir vos échantillons en tenseurs PyTorch et les concaténer (récursivement si vos éléments sont des listes, des *tuples* ou des dictionnaires). Cela ne sera pas possible dans notre cas puisque les entrées que nous avons ne seront pas toutes de la même taille. Nous avons délibérément reporté le *padding*, pour ne l'appliquer que si nécessaire sur chaque batch et éviter d'avoir des entrées trop longues avec beaucoup de remplissage. Cela accélère considérablement l'entraînement, mais notez que si vous vous entraînez sur un TPU, cela peut poser des problèmes. En effet, les TPU préfèrent les formes fixes, même si cela nécessite un *padding* supplémentaire. {:else} -La fonction responsable de mettre en ensemble les éléments pour en faire un batch est appelée *fonction d'assemblage* -*collate function*-. C'est un argument que vous pouvez fournir lorsque vous construisez un `DataLoader`, par défaut il s'agit d'une fonction qui va juste convertir les éléments en type tf.Tensor et les concaténer (récursivement si les éléments sont des listes, des tuples ou des dictionnaires). Cela -la concaténation- ne sera pas possible dans notre cas puisque toutes les entrées n'auront pas la même taille. Nous avons volontairement reporter à plus tard le padding, pour n'appliquer que le padding nécessaire pour chaque batch et éviter d'avoir des entrées excessivement longues avec beaucoup de padding. Cela va beaucoup accélérer l'entraînement, notez cependant que si vous faites l'entraînement sur TPU cela peut poser des problèmes — Les TPUs préfèrent une taille fixe même si cela requiert beaucoup de padding. +La fonction qui est responsable de l'assemblage des échantillons dans un batch est appelée *fonction de rassemblement*. C'est un argument que vous pouvez passer quand vous construisez un `DataLoader`, la valeur par défaut étant une fonction qui va juste convertir vos échantillons en type tf.Tensor et les concaténer (récursivement si les éléments sont des listes, des *tuples* ou des dictionnaires). Cela ne sera pas possible dans notre cas puisque les entrées que nous avons ne seront pas toutes de la même taille. Nous avons délibérément reporté le *padding*, pour ne l'appliquer que si nécessaire sur chaque batch et éviter d'avoir des entrées trop longues avec beaucoup de remplissage. Cela accélère considérablement l'entraînement, mais notez que si vous vous entraînez sur un TPU, cela peut poser des problèmes. En effet, les TPU préfèrent les formes fixes, même si cela nécessite un *padding* supplémentaire. {/if} -En pratique, pour faire cela, on utilise une fonction d'assemblage qui va mettre la bonne quantité de padding aux éléments du dataset que nous mettre ensemble pour former un batch. Heureusement, la librairie Transformers de 🤗 fournit une telle fonction via `DataCollatorWithPadding`. Elle prend un tokenizer lorsqu'on l'instancie (pour savoir quel token utiliser pour le padding, et aussi s'il faut faire le padding à gauche ou à droite en fonction des attentes du modèle) et va faire le nécessaire: +Pour faire cela en pratique, nous devons définir une fonction de rassemblement qui appliquera la bonne quantité de *padding* aux éléments du jeu de données que nous voulons regrouper. Heureusement, la bibliothèque 🤗 *Transformers* nous fournit une telle fonction via `DataCollatorWithPadding`. Elle prend un *tokenizer* lorsque vous l'instanciez (pour savoir quel *token* de *padding* utiliser et si le modèle s'attend à ce que le *padding* soit à gauche ou à droite des entrées) et fera tout ce dont vous avez besoin : {#if fw === 'pt'} ```py @@ -303,7 +307,7 @@ data_collator = DataCollatorWithPadding(tokenizer=tokenizer, return_tensors="tf" ``` {/if} -Pour tester notre nouveau jouet, prenons quelques éléments de notre jeu d'entraînement avec lesquels nous allons former un batch. Ici, on supprime les colonnes `idx`, `sentence1` et `sentence2` puisque nous n'en aurons pas besoin et qu'elles contiennent des strings ( et nous ne pouvons pas créer des tensors avec des strings) et on regarde la longueur de chaque entrée du batch : +Pour tester notre nouveau jouet, prenons quelques éléments de notre jeu d'entraînement avec lesquels nous allons former un batch. Ici, on supprime les colonnes `idx`, `sentence1` et `sentence2` puisque nous n'en aurons pas besoin et qu'elles contiennent des *strings* (et nous ne pouvons pas créer des tenseurs avec des *strings*) et on regarde la longueur de chaque entrée du batch : ```py samples = tokenized_datasets["train"][:8] @@ -315,7 +319,7 @@ samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "se [50, 59, 47, 67, 59, 50, 62, 32] ``` -Sans surprise, nous obtenons des éléments de longueurs différentes, allant de 32 à 67. Le padding dynamique signifie que nous allons utiliser le padding sur tous les éléments du batch pour obtenir une longueur de 67, la longueur maximale pour ce batch. Sans le padding dynamique, on appliquerait un padding à tous les éléments pour atteindre la longueur maximale de tout le dataset, ou la longueur maximale que le modèle peut accepter. Vérifions que notre `data_collator` effectue correctement le padding dynamique : +Sans surprise, nous obtenons des échantillons de longueur variable, de 32 à 67. Le *padding* dynamique signifie que les échantillons de ce batch doivent tous être rembourrés à une longueur de 67, la longueur maximale dans le batch. Sans le *padding* dynamique, tous les échantillons devraient être rembourrés à la longueur maximale du jeu de données entier, ou à la longueur maximale que le modèle peut accepter. Vérifions à nouveau que notre `data_collator` rembourre dynamiquement le batch correctement : ```py batch = data_collator(samples) @@ -339,19 +343,20 @@ batch = data_collator(samples) 'token_type_ids': torch.Size([8, 67]), 'labels': torch.Size([8])} ``` -Bien! Maintenant que nous sommes passés du texte brut aux batchs que notre modèle peut exploiter, nous sommes prêt à procéder au fine-tuning! + +C'est beau ! Maintenant que nous sommes passés du texte brut à des batchs que notre modèle peut traiter, nous sommes prêts à le *finetuner* ! {/if} -✏️ **Essayez ceci!** Reproduisez le preprocessing sur le dataset GLUE SST-2. Il est un peu différent puisqu'il est composé d'uniques phrases et non de paires de phrases, mais le reste de ce que nous avons fait devrait être similaire. Pour un défi plus corsé, essayez d'écrire une fonction de préprocessing qui marche pour toutes les tâches de GLUE. +✏️ **Essayez !** Reproduisez le prétraitement sur le jeu de données GLUE SST-2. C'est un peu différent puisqu'il est composé de phrases simples au lieu de paires, mais le reste de ce que nous avons fait devrait être identique. Pour un défi plus difficile, essayez d'écrire une fonction de prétraitement qui fonctionne sur toutes les tâches GLUE. {#if fw === 'tf'} -Maintenant que nous avons notre dataset et notre assembleur de données, nous devons les mettre ensemble. On pourrait charger manuellement les batchs et les assembler, mais cela représente beaucoup de travail, et ce n'est pas très performant non plus. Au lieu de faire cela, il y a une simple méthode qui offre une solution performante à ce problème : `to_tf_dataset()`. Cette méthode va passer `tf.data.Dataset` sur votre dataset, avec une fonction d'assemblage optionnelle. `tf.data.Dataset` est un format natif de Tensorflow que Keras peut utiliser avec `model.fit()`, donc cette méthode à elle seule convertit un dataset de 🤗 à un format prêt pour l'entraînement. Voyons la à l'œuvre avec notre dataset! +Maintenant que nous disposons de notre jeu de données et d'un collecteur de données, nous devons les assembler. Nous pourrions charger manuellement des batchs et les assembler mais c'est beaucoup de travail et probablement pas très performant non plus. A la place, il existe une méthode simple qui offre une solution performante à ce problème : `to_tf_dataset()`. Cela va envelopper un `tf.data.Dataset` autour de votre jeu de données, avec une fonction de collation optionnelle. `tf.data.Dataset` est un format natif de TensorFlow que Keras peut utiliser pour `model.fit()`, donc cette seule méthode convertit immédiatement un *dataset* en un format prêt pour l'entraînement. Voyons cela en action avec notre jeu de données ! ```py tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( @@ -371,6 +376,6 @@ tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( ) ``` -Et c'est tout! Nous pouvons utiliser ces datasets dans le prochain cours, où l'entraînement va être agréablement facile après tout le difficile travail de préprocessing. +Et c'est tout ! Nous pouvons utiliser ces jeux de données dans le prochain cours, où l'entraînement sera agréablement simple après tout le dur travail de prétraitement des données. {/if} diff --git a/chapters/fr/chapter3/3.mdx b/chapters/fr/chapter3/3.mdx index 62a7962e9..a23832502 100644 --- a/chapters/fr/chapter3/3.mdx +++ b/chapters/fr/chapter3/3.mdx @@ -11,7 +11,7 @@ -🤗 *Transformers* fournit une classe `Trainer` pour vous aider à *finetuner* n'importe lequel des modèles pré-entraînés qu'il fournit sur votre jeu de données. Une fois que vous avez fait tout le travail de prétraitement des données dans la dernière section, il ne vous reste que quelques étapes pour définir le `Trainer`. La partie la plus difficile sera probablement de préparer l'environnement pour exécuter `Trainer.train()`, car elle fonctionnera très lentement sur un CPU. Si vous n'avez pas de GPU, vous pouvez avoir accès à des GPUs ou TPUs gratuits sur [Google Colab](https://colab.research.google.com/). +La bibliothèque 🤗 *Transformers* fournit une classe `Trainer` pour vous aider à *finetuner* n'importe lequel des modèles pré-entraînés qu'elle met à disposition sur votre jeu de données. Une fois que vous avez fait tout le travail de prétraitement des données dans la dernière section, il ne vous reste que quelques étapes pour définir le `Trainer`. La partie la plus difficile sera probablement de préparer l'environnement pour exécuter `Trainer.train()`, car elle fonctionnera très lentement sur un CPU. Si vous n'avez pas de GPU, vous pouvez avoir accès à des GPUs ou TPUs gratuits sur [Google Colab](https://colab.research.google.com/). Les exemples de code ci-dessous supposent que vous avez déjà exécuté les exemples de la section précédente. Voici un bref résumé de ce dont vous avez besoin : @@ -83,7 +83,7 @@ trainer.train() Cela lancera le *finetuning* (qui devrait prendre quelques minutes sur un GPU) et indiquera la perte d'entraînement tous les 500 pas. Cependant, elle ne vous dira pas si votre modèle fonctionne bien (ou mal). Ceci est dû au fait que : -1. nous n'avons pas dit au `Trainer` d'évaluer pendant l'entraînement en réglant `evaluation_strategy` à soit `"steps"` (évaluer chaque `eval_steps`) ou `"epoch"` (évaluer à la fin de chaque epoch). +1. nous n'avons pas dit au `Trainer` d'évaluer pendant l'entraînement en réglant `evaluation_strategy` à soit `"steps"` (évaluer chaque `eval_steps`) ou `"epoch"` (évaluer à la fin de chaque *epoch*). 2. nous n'avons pas fourni au `Trainer` une fonction `compute_metrics()` pour calculer une métrique pendant ladite évaluation (sinon l'évaluation aurait juste affiché la perte, qui n'est pas un nombre très intuitif). @@ -166,6 +166,6 @@ Ceci conclut l'introduction au *fine-tuning* en utilisant l'API `Trainer`. Un ex -✏️ **Essayez** *Finetunez* un modèle sur le jeu de données GLUE SST-2, en utilisant le traitement des données que vous avez fait dans la section 2. +✏️ **Essayez !** *Finetunez* un modèle sur le jeu de données GLUE SST-2, en utilisant le traitement des données que vous avez fait dans la section 2. - \ No newline at end of file + diff --git a/chapters/fr/chapter3/3_tf.mdx b/chapters/fr/chapter3/3_tf.mdx index 1fd969770..d9cc961f4 100644 --- a/chapters/fr/chapter3/3_tf.mdx +++ b/chapters/fr/chapter3/3_tf.mdx @@ -9,7 +9,7 @@ {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter3/section3_tf.ipynb"}, ]} /> -Une fois que vous avez fait tout le travail de prétraitement des données dans la dernière section, il ne vous reste que quelques étapes pour entraîner le modèle. Notez, cependant, que la commande `model.fit()` s'exécutera très lentement sur un CPU. Si vous n'avez pas de GPU, vous pouvez avoir accès à des GPUs ou TPUs gratuits sur [Google Colab] (https://colab.research.google.com/). +Une fois que vous avez fait tout le travail de prétraitement des données dans la dernière section, il ne vous reste que quelques étapes pour entraîner le modèle. Notez, cependant, que la commande `model.fit()` s'exécutera très lentement sur un CPU. Si vous n'avez pas de GPU, vous pouvez avoir accès à des GPUs ou TPUs gratuits sur [Google Colab](https://colab.research.google.com/). Les exemples de code ci-dessous supposent que vous avez déjà exécuté les exemples de la section précédente. Voici un bref résumé de ce dont vous avez besoin : @@ -58,7 +58,7 @@ Cela signifie qu'une fois que nous disposons de nos données, très peu de trava -Comme dans le [chapitre précédent] (/course/fr/chapter2), nous allons utiliser la classe `TFAutoModelForSequenceClassification`, avec deux étiquettes : +Comme dans le [chapitre précédent](/course/fr/chapter2), nous allons utiliser la classe `TFAutoModelForSequenceClassification`, avec deux étiquettes : ```py from transformers import TFAutoModelForSequenceClassification @@ -103,7 +103,7 @@ Notez un piège très commun ici. Vous *pouvez* simplement passer le nom de la p Si vous essayez le code ci-dessus, il fonctionne certainement, mais vous constaterez que la perte ne diminue que lentement ou sporadiquement. La cause principale est le *taux d'apprentissage*. Comme pour la perte, lorsque nous transmettons à Keras le nom d'un optimiseur sous forme de chaîne de caractères, Keras initialise cet optimiseur avec des valeurs par défaut pour tous les paramètres, y compris le taux d'apprentissage. Cependant, nous savons depuis longtemps que les *transformers* bénéficient d'un taux d'apprentissage beaucoup plus faible que celui par défaut d'Adam, qui est de 1e-3, également écrit comme 10 à la puissance -3, ou 0,001. 5e-5 (0,00005), qui est environ vingt fois inférieur, est un bien meilleur point de départ. -En plus de réduire le taux d'apprentissage, nous avons une deuxième astuce dans notre manche : nous pouvons réduire lentement le taux d'apprentissage au cours de l'entraînement. Dans la littérature, on parle parfois de *décroissance* ou d' *annulation* du taux d'apprentissage.le taux d'apprentissage. Dans Keras, la meilleure façon de le faire est d'utiliser un *planificateur du taux d'apprentissage*. Un bon planificateur à utiliser est `PolynomialDecay`. Malgré son nom, avec les paramètres par défaut, il diminue simplement de façon linéaire le taux d'apprentissage de la valeur initiale à la valeur finale au cours de l'entraînement, ce qui est exactement ce que nous voulons. Afin d'utiliser correctement un planificateur, nous devons lui dire combien de temps l'entraînement va durer. Nous calculons cela comme `num_train_steps` ci-dessous. +En plus de réduire le taux d'apprentissage, nous avons une deuxième astuce dans notre manche : nous pouvons réduire lentement le taux d'apprentissage au cours de l'entraînement. Dans la littérature, on parle parfois de *décroissance* ou d'*annulation* du taux d'apprentissage.le taux d'apprentissage. Dans Keras, la meilleure façon de le faire est d'utiliser un *planificateur du taux d'apprentissage*. Un bon planificateur à utiliser est `PolynomialDecay`. Malgré son nom, avec les paramètres par défaut, il diminue simplement de façon linéaire le taux d'apprentissage de la valeur initiale à la valeur finale au cours de l'entraînement, ce qui est exactement ce que nous voulons. Afin d'utiliser correctement un planificateur, nous devons lui dire combien de temps l'entraînement va durer. Nous calculons cela comme `num_train_steps` ci-dessous. ```py from tensorflow.keras.optimizers.schedules import PolynomialDecay @@ -187,4 +187,4 @@ metric.compute(predictions=class_preds, references=raw_datasets["validation"]["l Les résultats exacts que vous obtiendrez peuvent varier, car l'initialisation aléatoire de la tête du modèle peut modifier les métriques obtenues. Ici, nous pouvons voir que notre modèle a une précision de 85,78% sur l'ensemble de validation et un score F1 de 89,97. Ce sont les deux métriques utilisées pour évaluer les résultats sur le jeu de données MRPC pour le benchmark GLUE. Le tableau du papier de [BERT](https://arxiv.org/pdf/1810.04805.pdf) indique un score F1 de 88,9 pour le modèle de base. Il s'agissait du modèle `uncased` alors que nous utilisons actuellement le modèle `cased`, ce qui explique le meilleur résultat. -Ceci conclut l'introduction à le *finetuning* en utilisant l'API Keras. Un exemple d'application de cette méthode aux tâches les plus courantes du traitement automatique des langues sera présenté au [Chapitre 7](/course/fr/chapter7). Si vous souhaitez affiner vos connaissances de l'API Keras, essayez *finetuner* un modèle sur le jeu de données GLUE SST-2, en utilisant le traitement des données que vous avez effectué dans la section 2. \ No newline at end of file +Ceci conclut l'introduction à le *finetuning* en utilisant l'API Keras. Un exemple d'application de cette méthode aux tâches les plus courantes du traitement automatique des langues sera présenté au [Chapitre 7](/course/fr/chapter7). Si vous souhaitez affiner vos connaissances de l'API Keras, essayez *finetuner* un modèle sur le jeu de données GLUE SST-2, en utilisant le traitement des données que vous avez effectué dans la section 2. diff --git a/chapters/fr/chapter3/4.mdx b/chapters/fr/chapter3/4.mdx index fe3dfcc94..a56a8bf9e 100644 --- a/chapters/fr/chapter3/4.mdx +++ b/chapters/fr/chapter3/4.mdx @@ -79,7 +79,7 @@ for batch in train_dataloader: 'token_type_ids': torch.Size([8, 65])} ``` -Notez que les formes réelles seront probablement légèrement différentes pour vous puisque nous avons défini `shuffle=True` pour le chargeur de données d'entraînement et que nous *paddons* à la longueur maximale dans le lot. +Notez que les formes réelles seront probablement légèrement différentes pour vous puisque nous avons défini `shuffle=True` pour le chargeur de données d'entraînement et que nous *paddons* à la longueur maximale dans le batch. Maintenant que nous en avons terminé avec le prétraitement des données (un objectif satisfaisant mais difficile à atteindre pour tout praticien d'apprentissage automatique), passons au modèle. Nous l'instancions exactement comme nous l'avons fait dans la section précédente : @@ -102,7 +102,7 @@ tensor(0.5441, grad_fn=) torch.Size([8, 2]) Tous les modèles 🤗 *Transformers* renvoient la perte lorsque les `labels` sont fournis. Nous obtenons également les logits (deux pour chaque entrée de notre batch, donc un tenseur de taille 8 x 2). -Nous sommes presque prêts à écrire notre boucle d'entraînement ! Il nous manque juste deux choses : un optimiseur et un planificateur de taux d'apprentissage. Puisque nous essayons de reproduire à la main ce que fait la fonction `Trainer`, utilisons les mêmes paramètres par défaut. L'optimiseur utilisé par `Trainer` est `AdamW`, qui est le même qu'Adam, mais avec une torsion pour la régularisation par décroissance de poids (voir ["Decoupled Weight Decay Regularization"](https://arxiv.org/abs/1711.05101) par Ilya Loshchilov et Frank Hutter) : +Nous sommes presque prêts à écrire notre boucle d'entraînement ! Il nous manque juste deux choses : un optimiseur et un planificateur de taux d'apprentissage. Puisque nous essayons de reproduire à la main ce que fait la fonction `Trainer`, utilisons les mêmes paramètres par défaut. L'optimiseur utilisé par `Trainer` est `AdamW`, qui est le même qu'Adam, mais avec une torsion pour la régularisation par décroissance de poids (voir [*Decoupled Weight Decay Regularization*](https://arxiv.org/abs/1711.05101) par Ilya Loshchilov et Frank Hutter) : ```py from transformers import AdamW @@ -110,7 +110,7 @@ from transformers import AdamW optimizer = AdamW(model.parameters(), lr=5e-5) ``` -Enfin, le planificateur du taux d'apprentissage utilisé par défaut est juste une décroissance linéaire de la valeur maximale (5e-5) à 0. Pour le définir correctement, nous devons connaître le nombre d'étapes d'entraînement que nous prendrons, qui est le nombre d'époques que nous voulons exécuter multiplié par le nombre de lots d'entraînement (qui est la longueur de notre dataloader d'entraînement). Le `Trainer` utilise trois époques par défaut, nous allons donc suivre ça : +Enfin, le planificateur du taux d'apprentissage utilisé par défaut est juste une décroissance linéaire de la valeur maximale (5e-5) à 0. Pour le définir correctement, nous devons connaître le nombre d'étapes d'entraînement que nous prendrons, qui est le nombre d'époques que nous voulons exécuter multiplié par le nombre de batch d'entraînement (qui est la longueur de notre *dataloader* d'entraînement). Le `Trainer` utilise trois époques par défaut, nous allons donc suivre ça : ```py from transformers import get_scheduler diff --git a/chapters/fr/chapter3/5.mdx b/chapters/fr/chapter3/5.mdx index 62f49c39d..767ec59aa 100644 --- a/chapters/fr/chapter3/5.mdx +++ b/chapters/fr/chapter3/5.mdx @@ -1,18 +1,18 @@ -# *Finetuning*, vérifié ! +# *Finetuning*, coché ! C'était amusant ! Dans les deux premiers chapitres, vous avez appris à connaître les modèles et les *tokenizers*, et vous savez maintenant comment les *finetuner* pour vos propres données. Pour récapituler, dans ce chapitre vous : {#if fw === 'pt'} -* avez appris à connaître les jeux de données dans le [Hub](https://huggingface.co/datasets), +* avez appris à connaître les jeux de données dans le [*Hub*](https://huggingface.co/datasets), * avez appris à charger et à prétraiter des jeux de données, notamment en utilisant le remplissage dynamique et les assembleurs, * avez implémenté votre propre *finetuning* et évaluation d'un modèle, * avez implémenté une boucle d'entraînement de niveau inférieur, * avez utilisé 🤗 *Accelerate* pour adapter facilement votre boucle d'entraînement afin qu'elle fonctionne pour plusieurs GPUs ou TPUs. {:else} -* avez appris à connaître les jeux de données dans le [Hub](https://huggingface.co/datasets), +* avez appris à connaître les jeux de données dans le [*Hub*](https://huggingface.co/datasets), * avez appris comment charger et prétraiter les jeux de données, * avez appris comment *finetuner* et évaluer un modèle avec Keras, * avez implémenté une métrique personnalisée. diff --git a/chapters/fr/chapter3/6.mdx b/chapters/fr/chapter3/6.mdx index 3654b7181..edafb9c0a 100644 --- a/chapters/fr/chapter3/6.mdx +++ b/chapters/fr/chapter3/6.mdx @@ -6,7 +6,7 @@ Testez ce que vous avez appris dans ce chapitre ! -### 1. Le jeu de données `emotion` contient des messages Twitter étiquetés avec des émotions. Cherchez-le dans le [*Hub*](https://huggingface.co/datasets), et lisez la carte du jeu de données. Laquelle de ces émotions n'est pas une de ses émotions de base ? +### 1. Le jeu de données `emotion` contient des messages Twitter étiquetés avec des émotions. Cherchez-le dans le [*Hub*](https://huggingface.co/datasets) et lisez la carte du jeu de données. Laquelle de ces émotions n'est pas une de ses émotions de base ? tags.", correct: true }, { @@ -121,26 +121,26 @@ Testez ce que vous avez appris dans ce chapitre ! ]} /> -### 6. Quel est le but d'une fonction de collecte ? +### 6. Quel est le but d'une fonction de rassemblement ? DataCollatorWithPadding." + explain: "Une fonction de rassemblement est impliquée dans le traitement des batchs individuels, et non de tout le jeu de données. De plus, nous parlons de fonctions génériques et pas spécialement du DataCollatorWithPadding." }, { text: "Elle rassemble tous les échantillons dans un batch.", - explain: "Correct ! Vous pouvez passer la fonction de collecte comme argument d'une fonction DataLoader. Nous avons utilisé la fonction DataCollatorWithPadding qui remplit tous les éléments d'un batch pour qu'ils aient la même longueur.", + explain: "Correct ! Vous pouvez passer la fonction de rassemblement comme argument d'une fonction DataLoader. Nous avons utilisé la fonction DataCollatorWithPadding qui remplit tous les éléments d'un batch pour qu'ils aient la même longueur.", correct: true }, { text: "Elle pré-traite tout le jeu de données.", - explain: "Ce serait une fonction de prétraitement, pas une fonction de collecte." + explain: "Ce serait une fonction de prétraitement, pas une fonction de rassemblement." }, { text: "Elle tronque les séquences dans le jeu de données.", - explain: "Une fonction de collecte est impliquée dans le traitement des batchs individuels, et non de tout le jeu de données. Si vous êtes intéressé par la troncature, vous pouvez utiliser la fonction truncate en argument du tokenizer." + explain: "Une fonction de rassemblement est impliquée dans le traitement des batchs individuels, et non de tout le jeu de données. Si vous êtes intéressé par la troncature, vous pouvez utiliser la fonction truncate en argument du tokenizer." } ]} /> @@ -183,7 +183,7 @@ Testez ce que vous avez appris dans ce chapitre ! explain: "La taille du modèle est définie par la configuration du modèle, et non par la classe TrainingArguments." }, { - text: "Juste contenir les hyperparamètres utilisés pour l'évaluation..", + text: "Juste contenir les hyperparamètres utilisés pour l'évaluation.", explain: "Dans l'exemple, nous avons spécifié où le modèle et ses checkpoints seront sauvegardés. Essayez à nouveau !" }, { @@ -199,20 +199,20 @@ Testez ce que vous avez appris dans ce chapitre ! choices={[ { text: "Elle permet d'accéder à des modèles plus rapides.", - explain: "Non, la librairie 🤗 *Accelerate* ne fournit aucun modèles." + explain: "Non, la librairie 🤗 Accelerate ne fournit aucun modèles." }, { text: "Elle fournit une API de haut niveau qui évite d'avoir à mettre en place sa propre boucle d'entraînement.", - explain: "C'est ce que nous avons fait avec le Trainer mais pas avec la librairie 🤗 *Accelerate*. Essayez à nouveau !" + explain: "C'est ce que nous avons fait avec le Trainer mais pas avec la librairie 🤗 Accelerate. Essayez à nouveau !" }, { text: "Elle permet à nos boucles d'entraînement de fonctionner avec des stratégies distribuées.", - explain: "Correct ! Avec 🤗 *Accelerate*, vos boucles d'entraînement fonctionneront pour plusieurs GPUs et TPUs..", + explain: "Correct ! Avec 🤗 Accelerate, vos boucles d'entraînement fonctionneront pour plusieurs GPUs et TPUs.", correct: true }, { text: "Elle offre davantage de fonctions d'optimisation.", - explain: "Non, la librairie 🤗 *Accelerate* ne fournit pas de fonctions d'optimisation." + explain: "Non, la librairie 🤗 Accelerate ne fournit pas de fonctions d'optimisation." } ]} /> @@ -247,7 +247,7 @@ Testez ce que vous avez appris dans ce chapitre ! TPUStrategy, y compris l'initialisation du modèle." }, { @@ -287,10 +287,10 @@ Testez ce que vous avez appris dans ce chapitre ! }, { text: "En le googlant.", - explain: "Ce n'est pas la réponse que nous cherchons, mais cela devrait vous aider à la trouver..", + explain: "Ce n'est pas la réponse que nous cherchons, mais cela devrait vous aider à la trouver.", correct: true } ]} /> -{/if} \ No newline at end of file +{/if} diff --git a/chapters/fr/chapter4/2.mdx b/chapters/fr/chapter4/2.mdx index 1d1ecb387..86579685f 100644 --- a/chapters/fr/chapter4/2.mdx +++ b/chapters/fr/chapter4/2.mdx @@ -22,7 +22,7 @@ {/if} -Le *Hub* simplifie la sélection du modèle approprié, de sorte que son utilisation dans toute bibliothèque en aval peut se faire en quelques lignes de code. Voyons comment utiliser concrètement l'un de ces modèles et comment contribuer à la communauté. +Le *Hub* rend simple la sélection d'un modèle et permet alors que celui-ci puisse être utilisé dans toute bibliothèque en aval en seulement quelques lignes de code. Voyons comment utiliser concrètement l'un de ces modèles et comment contribuer au développement de la communauté. Supposons que nous recherchions un modèle basé sur le français, capable de remplir des masques. @@ -30,7 +30,7 @@ Supposons que nous recherchions un modèle basé sur le français, capable de re Selecting the Camembert model.
-Nous choisissons le *checkpoint* `camembert-base` pour essayer. L'identifiant `camembert-base` est tout ce dont nous avons besoin pour commencer à utiliser le modèle ! Comme vous l'avez vu dans les chapitres précédents, nous pouvons l'instancier en utilisant la fonction `pipeline()` : +Nous choisissons le *checkpoint* `camembert-base` pour essayer. L'identifiant `camembert-base` est tout ce dont nous avons besoin pour commencer à utiliser le modèle ! Comme vous l'avez vu dans les chapitres précédents, nous pouvons l'instancier en utilisant la fonction `pipeline()` : ```py from transformers import pipeline @@ -49,7 +49,7 @@ results = camembert_fill_mask("Le camembert est :)") ] ``` -Comme vous pouvez le constater, le chargement d'un modèle dans un pipeline est extrêmement simple. La seule chose à laquelle vous devez faire attention est que le *checkpoint* choisi soit adapté à la tâche pour laquelle il va être utilisé. Par exemple, ici nous chargeons le *checkpoint* `camembert-base` dans le pipeline `fill-mask`, ce qui est tout à fait correct. Mais si nous chargeions ce *checkpoint* dans le pipeline `text-classification`, les résultats n'auraient aucun sens car la tête de `camembert-base` n'est pas adaptée à cette tâche ! Nous recommandons d'utiliser le sélecteur de tâche dans l'interface du *Hub* afin de sélectionner les *checkpoints* appropriés : +Comme vous pouvez le constater, le chargement d'un modèle dans un pipeline est extrêmement simple. La seule chose à laquelle vous devez faire attention est que le *checkpoint* choisi soit adapté à la tâche pour laquelle il va être utilisé. Par exemple, ici nous chargeons le *checkpoint* `camembert-base` dans le pipeline `fill-mask`, ce qui est tout à fait correct. Mais si nous chargerions ce *checkpoint* dans le pipeline `text-classification`, les résultats n'auraient aucun sens car la tête de `camembert-base` n'est pas adaptée à cette tâche ! Nous recommandons d'utiliser le sélecteur de tâche dans l'interface du *Hub* afin de sélectionner les *checkpoints* appropriés :
The task selector on the web interface. diff --git a/chapters/fr/chapter4/3.mdx b/chapters/fr/chapter4/3.mdx index 95b5d420b..cd60c7b66 100644 --- a/chapters/fr/chapter4/3.mdx +++ b/chapters/fr/chapter4/3.mdx @@ -67,7 +67,7 @@ huggingface-cli login Dans les deux cas, vous serez invité à saisir votre nom d'utilisateur et votre mot de passe, qui sont les mêmes que ceux que vous utilisez pour vous connecter au *Hub*. Si vous n'avez pas encore de profil pour le Hub, vous devez en créer un [ici](https://huggingface.co/join). -Super ! Votre jeton d'authentification est maintenant stocké dans votre dossier de cache. Créons quelques référentiels ! +Super ! Votre jeton d'authentification est maintenant stocké dans votre dossier de cache. Créons quelques dépôts ! {#if fw === 'pt'} @@ -102,7 +102,7 @@ callback = PushToHubCallback( ) ``` -Ensuite, vous devez ajouter `callbacks=[callback]` dans votre appel à `model.fit()`. La *callback* téléchargera alors votre modèle vers le *Hub* à chaque fois qu'il sera sauvegardé (ici à chaque époque) dans un référentiel dans votre espace de noms. Ce dépôt sera nommé comme le répertoire de sortie que vous avez choisi (ici `bert-finetuned-mrpc`) mais vous pouvez choisir un nom différent avec `hub_model_id = "a_different_name"`. +Ensuite, vous devez ajouter `callbacks=[callback]` dans votre appel à `model.fit()`. Le *callback* téléchargera alors votre modèle vers le *Hub* à chaque fois qu'il sera sauvegardé (ici à chaque époque) dans un dépôt dans votre espace de noms. Ce dépôt sera nommé comme le répertoire de sortie que vous avez choisi (ici `bert-finetuned-mrpc`) mais vous pouvez choisir un nom différent avec `hub_model_id = "a_different_name"`. Pour télécharger votre modèle dans une organisation dont vous êtes membre, passez-le simplement avec `hub_model_id = "my_organization/my_repo_name"`. @@ -179,7 +179,7 @@ Cliquez sur l'onglet « Fichiers et versions » et vous devriez voir les fichier Comme vous l'avez vu, la méthode `push_to_hub()` accepte plusieurs arguments, ce qui permet de télécharger vers un dépôt ou un espace d'organisation spécifique, ou d'utiliser un jeton d'API différent. Nous vous recommandons de jeter un coup d'œil à la spécification de la méthode disponible directement dans la documentation de [🤗 *Transformers*](https://huggingface.co/transformers/model_sharing.html) pour avoir une idée de ce qui est possible. -La méthode `push_to_hub()` est soutenue par le paquet Python [`huggingface_hub`](https://github.com/huggingface/huggingface_hub), qui offre une API directe au *Hub*. C'est intégré à 🤗 *Transformers* et à plusieurs autres bibliothèques d'apprentissage automatique, comme [`allenlp`](https://github.com/allenai/allennlp). Bien que nous nous concentrions sur l'intégration via 🤗 *Transformers* dans ce chapitre, son intégration dans votre propre code ou bibliothèque est simple. +La méthode `push_to_hub()` est soutenue par le *package* Python [`huggingface_hub`](https://github.com/huggingface/huggingface_hub), qui offre une API directe au *Hub*. C'est intégré à 🤗 *Transformers* et à plusieurs autres bibliothèques d'apprentissage automatique, comme [`allenlp`](https://github.com/allenai/allennlp). Bien que nous nous concentrions sur l'intégration via 🤗 *Transformers* dans ce chapitre, son intégration dans votre propre code ou bibliothèque est simple. Passez à la dernière section pour voir comment télécharger des fichiers dans votre dépôt nouvellement créé ! @@ -239,7 +239,7 @@ create_repo("dummy-model", organization="huggingface") Cela créera le dépôt `dummy-model` dans l'espace de nom `huggingface`, en supposant que vous appartenez à cette organisation. D'autres arguments qui peuvent être utiles sont : -- `private`, afin de spécifier si le référentiel doit être visible des autres ou non, +- `private`, afin de spécifier si le dépôt doit être visible des autres ou non, - `token`, si vous voulez remplacer le jeton stocké dans votre cache par un jeton donné, - `repo_type`, si vous souhaitez créer un `dataset` ou un `space` au lieu d'un modèle. Les valeurs acceptées sont `"dataset"` et `"space"`. @@ -256,7 +256,7 @@ Pour créer un nouveau dépôt, visitez [huggingface.co/new](https://huggingface Page showcasing the model used for the creation of a new model repository.
-Tout d'abord, indiquez le propriétaire du dépôt : il peut s'agir de vous ou de l'une des organisations auxquelles vous êtes affilié. Si vous choisissez une organisation, le modèle sera présenté sur la page de l'organisation et chaque membre de l'organisation aura la possibilité de contribuer au référentiel. +Tout d'abord, indiquez le propriétaire du dépôt : il peut s'agir de vous ou de l'une des organisations auxquelles vous êtes affilié. Si vous choisissez une organisation, le modèle sera présenté sur la page de l'organisation et chaque membre de l'organisation aura la possibilité de contribuer au dépôt. Ensuite, saisissez le nom de votre modèle. Ce sera également le nom du dépôt. Enfin, vous pouvez préciser si vous souhaitez que votre modèle soit public ou privé. Les modèles privés sont cachés de la vue du public. @@ -338,7 +338,7 @@ repo.git_push() repo.git_tag() ``` -Et d'autres encore ! Nous vous recommandons de jeter un coup d'oeil à la documentation de `Repository` disponible [ici](https://github.com/huggingface/huggingface_hub/tree/main/src/huggingface_hub#advanced-programmatic-repository-management) pour une vue d'ensemble de toutes les méthodes disponibles. +Et d'autres encore ! Nous vous recommandons de jeter un coup d’œil à la documentation de `Repository` disponible [ici](https://github.com/huggingface/huggingface_hub/tree/main/src/huggingface_hub#advanced-programmatic-repository-management) pour une vue d'ensemble de toutes les méthodes disponibles. Actuellement, nous avons un modèle et un *tokenizer* que nous voulons pousser vers le *Hub*. Nous avons réussi à cloner le dépôt, nous pouvons donc enregistrer les fichiers dans ce dépôt. @@ -408,7 +408,7 @@ Si vous venez de créer votre dépôt en utilisant la méthode `create_repo` du L'ajout d'un fichier de taille normale, comme un fichier de configuration, un fichier de vocabulaire, ou tout autre fichier de moins de quelques mégaoctets, est fait exactement comme on le ferait dans n'importe quel système basé sur git. Cependant, les fichiers plus volumineux doivent être enregistrés via git-lfs afin de les pousser vers *huggingface.co*. -Revenons un peu à Python pour générer un modèle et un *tokenizer* que nous souhaitons commiter dans notre dépôt fictif : +Revenons un peu à Python pour générer un modèle et un *tokenizer* que nous souhaitons « commiter » dans notre dépôt fictif : {#if fw === 'pt'} ```py @@ -463,10 +463,10 @@ Si vous regardez la taille des fichiers (par exemple, avec `ls -lh`), vous devri {/if} -✏️ Lors de la création du dépôt à partir de l'interface web, le fichier *.gitattributes* est automatiquement configuré pour considérer les fichiers avec certaines extensions, comme *.bin* et *.h5*, comme des fichiers volumineux, et git-lfs les suivra sans aucune configuration nécessaire de votre part. +✏️ Lors de la création du dépôt à partir de l'interface web, le fichier .gitattributes est automatiquement configuré pour considérer les fichiers avec certaines extensions, comme .bin et .h5, comme des fichiers volumineux, et git-lfs les suivra sans aucune configuration nécessaire de votre part. -Nous pouvons maintenant aller de l'avant et procéder comme nous le ferions habituellement avec des dépôts Git traditionnels. Nous pouvons ajouter tous les fichiers à l'environnement staging de Git en utilisant la commande `git add` : +Nous pouvons maintenant aller de l'avant et procéder comme nous le ferions habituellement avec des dépôts Git traditionnels. Nous pouvons ajouter tous les fichiers à l'environnement Git en utilisant la commande `git add` : ```bash git add . diff --git a/chapters/fr/chapter4/4.mdx b/chapters/fr/chapter4/4.mdx index d3a574cf2..745d846ba 100644 --- a/chapters/fr/chapter4/4.mdx +++ b/chapters/fr/chapter4/4.mdx @@ -6,7 +6,7 @@ Documenter le processus d'entraînement et d'évaluation aide les autres à comp Par conséquent, la création d'une carte de modèle définissant clairement votre modèle est une étape très importante. Nous vous donnons ici quelques conseils qui vous aideront à le faire. La création de la fiche de modèle se fait par le biais du fichier *README.md* que vous avez vu précédemment, qui est un fichier Markdown. -Le concept de carte de modèle provient d'une direction de recherche de Google, partagée pour la première fois dans l'article ["Model Cards for Model Reporting"](https://arxiv.org/abs/1810.03993) par Margaret Mitchell et al. De nombreuses informations contenues dans ce document sont basées sur cet article et nous vous recommandons d'y jeter un coup d'œil pour comprendre pourquoi les cartes de modèles sont si importantes dans un monde qui valorise la reproductibilité, la réutilisation et l'équité. +Le concept de carte de modèle provient d'une direction de recherche de Google, partagée pour la première fois dans l'article ["*Model Cards for Model Reporting*"](https://arxiv.org/abs/1810.03993) par Margaret Mitchell et al. De nombreuses informations contenues dans ce document sont basées sur cet article et nous vous recommandons d'y jeter un coup d'œil pour comprendre pourquoi les cartes de modèles sont si importantes dans un monde qui valorise la reproductibilité, la réutilisation et l'équité. La carte de modèle commence généralement par une très brève présentation de haut niveau de l'objet du modèle, suivie de détails supplémentaires dans les sections suivantes : diff --git a/chapters/fr/chapter4/6.mdx b/chapters/fr/chapter4/6.mdx index 5e00bbdc1..ba410bffd 100644 --- a/chapters/fr/chapter4/6.mdx +++ b/chapters/fr/chapter4/6.mdx @@ -55,8 +55,8 @@ Testons ce que vous avez appris dans ce chapitre ! Forker un dépôt existant.", - explain: "Forking un dépôt n'est pas possible sur le Hub." + text: "« Forker » un dépôt existant.", + explain: "« Forker » un dépôt n'est pas possible sur le Hub." }, { text: "Créer un nouveau dépôt de modèles.", @@ -65,7 +65,7 @@ Testons ce que vous avez appris dans ce chapitre ! }, { text: "Gérer et modifier des fichiers.", - explain: "Correct ! Ce n'est pas la seule bonne réponse, cependant..", + explain: "Correct ! Ce n'est pas la seule bonne réponse, cependant.", correct: true }, { diff --git a/chapters/fr/event/1.mdx b/chapters/fr/event/1.mdx index 28b491d05..ab52d0489 100644 --- a/chapters/fr/event/1.mdx +++ b/chapters/fr/event/1.mdx @@ -56,7 +56,7 @@ Matthew Watson est ingénieur en apprentissage automatique au sein de l'équipe Chen Qian est un ingénieur logiciel de l'équipe Keras spécialisé dans les API de modélisation de haut niveau. Chen est titulaire d'un master en génie électrique de l'université de Stanford et s'intéresse particulièrement à la simplification de l'implémentation du code des tâches d’apprentissage automatique et le passage à grande échelle de ces codes. -**Mark Saroufim :** *Comment entraîner un modèle avec Pytorch* +**Mark Saroufim :** *Comment entraîner un modèle avec PyTorch*
@@ -66,7 +66,7 @@ Chen Qian est un ingénieur logiciel de l'équipe Keras spécialisé dans les AP A visual summary of Mark's talk

-Mark Saroufim est ingénieur partenaire chez Pytorch et travaille sur les outils de production OSS, notamment TorchServe et Pytorch Enterprise. Dans ses vies antérieures, Mark a été un scientifique appliqué et un chef de produit chez Graphcore, [yuri.ai](http://yuri.ai/), Microsoft et au JPL de la NASA. Sa principale passion est de rendre la programmation plus amusante. +Mark Saroufim est ingénieur partenaire chez PyTorch et travaille sur les outils de production OSS, notamment TorchServe et PyTorch Enterprise. Dans ses vies antérieures, Mark a été un scientifique appliqué et un chef de produit chez Graphcore, [yuri.ai](http://yuri.ai/), Microsoft et au JPL de la NASA. Sa principale passion est de rendre la programmation plus amusante. **Jakob Uszkoreit :** *Ce n'est pas cassé alors ne réparez pas cassez tout* @@ -99,7 +99,7 @@ Lewis est un ingénieur en apprentissage machine chez Hugging Face qui se concen Matt est responsable de la maintenance des modèles en TensorFlow chez *Transformers*. Il finira par mener un coup d'État contre la faction PyTorch en place Celui sera probablement coordonné via son compte Twitter @carrigmat. -**Lysandre Debut :** *L’Hugging Face Hub, un moyen de collaborer et de partager des projets d'apprentissage automatique* +**Lysandre Debut :** *Le Hub d’Hugging Face, un moyen de collaborer et de partager des projets d'apprentissage automatique*
@@ -117,7 +117,7 @@ Lysandre est ingénieur en apprentissage machine chez Hugging Face où il partic
-Lucile est ingénieur en apprentissage automatique chez Hugging Face où elle développe et soutient l'utilisation d'outils open source. Elle est également activement impliquée dans de nombreux projets de recherche dans le domaine du traitement du langage naturel tels que l’entraînement collaboratif et BigScience. +Lucile est ingénieure en apprentissage automatique chez Hugging Face où elle développe et soutient l'utilisation d'outils open source. Elle est également activement impliquée dans de nombreux projets de recherche dans le domaine du traitement du langage naturel tels que l’entraînement collaboratif et BigScience. **Sylvain Gugger :** *Optimisez votre boucle d'entraînement PyTorch avec 🤗 Accelerate* @@ -161,10 +161,10 @@ Abubakar Abid est le PDG de [Gradio](www.gradio.app). Il a obtenu sa licence en Passionné de technologie, il est un créateur pendant son temps libre. Il aime les défis et résoudre les problèmes des clients et des utilisateurs ainsi que travailler avec des personnes talentueuses pour apprendre chaque jour. Depuis 2004, il a occupé plusieurs postes, passant du frontend au backend, de l'infrastructure aux opérations et à la gestion. Il essaie de résoudre les problèmes techniques et de gestion courants de manière agile. -**Philipp Schmid :** *Managed Training avec Amazon SageMaker and 🤗 Transformers* +**Philipp Schmid :** *Entraînement dirigé avec Amazon SageMaker et 🤗 Transformers*
-Philipp Schmid est ingénieur en apprentissage machine et Tech Lead chez Hugging Face où il dirige la collaboration avec l'équipe Amazon SageMaker. Il est passionné par la démocratisation et la mise en production de modèles de traitement du langage naturel de pointe et par l'amélioration de la facilité d'utilisation du Deep Learning. +Philipp Schmid est ingénieur en apprentissage machine et *Tech Lead* chez Hugging Face où il dirige la collaboration avec l'équipe Amazon SageMaker. Il est passionné par la démocratisation et la mise en production de modèles de traitement du langage naturel de pointe et par l'amélioration de la facilité d'utilisation de l'apprentissage profond. diff --git a/chapters/hi/_toctree.yml b/chapters/hi/_toctree.yml index 86dd2c5bd..d545861ea 100644 --- a/chapters/hi/_toctree.yml +++ b/chapters/hi/_toctree.yml @@ -7,3 +7,8 @@ sections: - local: chapter1/1 title: परिचय + +- title: 2. ट्रांसफॉर्मर का उपयोग करना + sections: + - local: chapter2/1 + title: परिचय \ No newline at end of file diff --git a/chapters/hi/chapter2/1.mdx b/chapters/hi/chapter2/1.mdx new file mode 100644 index 000000000..a8c1e8265 --- /dev/null +++ b/chapters/hi/chapter2/1.mdx @@ -0,0 +1,21 @@ +# परिचय + +जैसा कि आपने [अध्याय 1](/course/chapter1) में देखा, ट्रांसफार्मर मॉडल आमतौर पर बहुत बड़े होते हैं। लाखों से दसियों अरबों पैरामीटर्स के साथ, इन मॉडलों का प्रशिक्षण और डिप्लॉय करना एक पेचीदा उपक्रम है। इसके अलावा, नए मॉडल लगभग दैनिक आधार पर जारी किए जा रहे हैं और प्रत्येक का अपना कार्यान्वयन है, उन सभी को आज़माना कोई आसान काम नहीं है। + +इस समस्या को हल करने के लिए 🤗 ट्रांसफॉर्मर्स लाइब्रेरी बनाई गई थी। इसका लक्ष्य एक एपीआई प्रदान करना है जिसके माध्यम से किसी भी ट्रांसफार्मर मॉडल को लोड, प्रशिक्षित और सेव किया जा सकता है। पुस्तकालय की मुख्य विशेषताएं हैं: + +- **उपयोग में आसानी**: निष्कर्ष के लिए एक अत्याधुनिक एनएलपी मॉडल को डाउनलोड करना, लोड करना और उपयोग करना कोड की केवल दो पंक्तियों में किया जा सकता है। +- **सुविधाजनक**: उनके मूल में, सभी मॉडल सरल PyTorch `nn.Module` या TensorFlow `tf.keras.Model` वर्ग हैं और उनके संबंधित मशीन लर्निंग (ML) ढांचे में किसी भी अन्य मॉडल की तरह नियंत्रित किया जा सकता है। +- **सरलता**: पुस्तकालय में शायद ही कोई अमूर्तन किया गया हो। "ऑल इन वन फाइल" एक मुख्य अवधारणा है: एक मॉडल का फॉरवर्ड पास पूरी तरह से एक फाइल में परिभाषित किया जाता है, ताकि कोड स्वयं समझने योग्य और हैक करने योग्य हो। + +यह अंतिम विशेषता 🤗 ट्रांसफॉर्मर को अन्य ML पुस्तकालयों से काफी अलग बनाती है। मॉडल उन मॉड्यूल पर नहीं बने हैं +जो फाइल्स के बीच शेयर होती हैं; इसके बजाय, प्रत्येक मॉडल की अपनी परतें होती हैं। मॉडलों को अधिक सुलभ और समझने योग्य बनाने के अलावा, यह आपको दूसरों को प्रभावित किए बिना एक मॉडल पर आसानी से परीक्षण करने देती है। + +यह अध्याय एक एंड-टू-एंड उदाहरण के साथ शुरू होगा जहां हम [अध्याय 1](/course/chapter1) में पेश किए गए `pipeline()` फ़ंक्शन को दोहराने के लिए एक मॉडल और एक टोकननाइज़र का एक साथ उपयोग करते हैं। इसके बाद, हम मॉडल API पर चर्चा करेंगे: हम मॉडल और कॉन्फ़िगरेशन कक्षाओं को पढ़ेंगे, और आपको दिखाएंगे कि मॉडल को कैसे लोड किया जाए और यह आउटपुट पूर्वानुमानो के लिए संख्यात्मक इनपुट को कैसे संसाधित करता है। + +फिर हम टोकननाइज़र API को देखेंगे, जो `pipeline()` फ़ंक्शन का अन्य मुख्य अंग है। टोकेनाइज़र पहले और अंतिम प्रसंस्करण चरणों का ध्यान रखते हैं, न्यूरल नेटवर्क के लिए पाठ से संख्यात्मक इनपुट में परिवर्तन को संभालते हैं, और आवश्यकता होने पर पाठ में परिवर्तन वापस करते हैं। अंत में, हम आपको दिखाएंगे कि एक तैयार बैच में एक मॉडल के माध्यम से कई वाक्यों को भेजने से कैसे निपटना है, फिर उच्च-स्तरीय `tokenizer()` फ़ंक्शन को करीब से देखकर इसका अंत करेंगे। + + +⚠️ मॉडल हब और 🤗 ट्रांसफॉर्मर के साथ उपलब्ध सभी सुविधाओं का लाभ उठाने के लिए, हम खाता बनाने की अनुशंसा करते हैं। + + diff --git a/chapters/it/_toctree.yml b/chapters/it/_toctree.yml new file mode 100644 index 000000000..d9c034499 --- /dev/null +++ b/chapters/it/_toctree.yml @@ -0,0 +1,13 @@ +- title: 0. Installazione + sections: + - local: chapter0/1 + title: Introduzione + +- title: 1. Modelli Transformer + sections: + - local: chapter1/1 + title: Introduzione + - local: chapter1/2 + title: Natural Language Processing + - local: chapter1/3 + title: Cosa fanno i Transformer? diff --git a/chapters/it/chapter0/1.mdx b/chapters/it/chapter0/1.mdx new file mode 100644 index 000000000..844edc4f1 --- /dev/null +++ b/chapters/it/chapter0/1.mdx @@ -0,0 +1,110 @@ +# Introduzione + +Benvenuto/a al corso di Hugging Face! In questo capitolo introduttivo, ti aiuteremo a configurare il tuo ambiente di lavoro. Se non hai ancora cominciato il corso, ti consigliamo di dare prima un occhio al [Capitolo 1](/course/chapter1), per poi tornare qui a creare il tuo ambiente e cominciare a lavorare al codice. + +Tutte le librerie che useremo in questo corso sono disponibili come pacchetti Python. Qui ti mostreremo dapprima come configurare un ambiente Python e in seguito come installare le librerie di cui avrai bisogno. + +Copriremo due modi per configurare un ambiente di lavoro: usando un blocco note Colab, oppure un ambiente virtuale in Python. Sentiti libero/a di scegliere quello che ti sembra più adatto a te. Se sei un/a principiante, ti consigliamo vivamente di cominciare a lavorare con un blocco note Colab. + +Nota che non copriremo Windows. Se utilizzi Windows come sistema operativo, il nostro consiglio è di seguire il corso utilizzando un blocco note Colab. Se invece utilizzi Linux oppure macOS, puoi scegliere uno qualsiasi degli approcci descritti qui in seguito. + +Buona parte del corso richiede un profilo di Hugging Face. Ti consigliamo dunque di crearne uno al più presto: [Crea un profilo](https://huggingface.co/join). + +## Come usare un blocco note Colab di Google + +Il modo più semplice di configurare il tuo ambiente di lavoro è utilizzando Google Colab: una volta avviato un blocco note nel browser, puoi iniziare immediatamente a programmare! + +Se non conosci bene Colab, ti raccomandiamo di iniziare dalla seguente [introduzione](https://colab.research.google.com/notebooks/intro.ipynb). Colab permette di utilizzare accelerazioni hardware come GPU o TPU, ed è gratuito per i carichi di lavoro più piccoli. + +Quando ti sentirai a tuo agio con Colab, crea un nuovo blocco note e inizia la configurazione: + +
+An empty colab notebook +
+ +Il passo successivo consiste nell'installare le librerie che utilizzerai in questo corso. Per l'installazione, useremo `pip`, ossia il gestore di pacchetti di Python. In Google Colab, puoi inizializzare i tuoi comandi di sistema facendone precedere il nome dal carattere `!`. La libreria 🤗 Transformers verrà quindi installata come segue: + +``` +!pip install transformers +``` + +Puoi assicurarti che il pacchetto sia stato installato correttamente importandolo in Python: + +``` +import transformers +``` + +
+A gif showing the result of the two commands above: installation and import +
+ +Quest'operazione installa una versione molto leggera degli 🤗 Transformers che non importa nessun framework (*quadro strutturale*) di machine learning (*apprendimento automatico*), come ad esempio PyTorch o TensorFlow. Dato che useremo numerose features (*caratteristiche*) della libreria, ti raccomandiamo l'installazione della versione per sviluppatori. Questa contiene praticamente tutte le dipendenze possibili e immaginabili: + +``` +!pip install transformers[sentencepiece] +``` + +L'operazione richiederà un po' di tempo, ma poi sarai pronto/a per il tutto resto del corso! + +## Come usare un ambiente virtuale in Python + +Se preferisci utilizzare un ambiente virtuale in Python, il primo passo consiste nell'installazione di Python nel tuo sistema. Ti raccomandiamo di aiutarti con [questa guida](https://realpython.com/installing-python/). + +Quando avrai installato Python, dovresti riuscire a eseguire qualsiasi comando in Python sul terminale. Prima di procedere ai passi successivi, prova a eseguire il seguente comando per assicurarti che Python sia installato correttamente: `python --version`. Il comando dovrebbe stampare il nome della versione di Python installata nella tua macchina. + +Quando esegui un comando in Python dal terminale, come ad esempio `python --version`, ti consigliamo di considerare il programma che esegue il tuo comando come l'installazione "principale" di Python del tuo sistema. La nostra raccomandazione è di tenere quest'installazione principale libera da pacchetti di ogni tipo, e di usarla per creare ambienti diversi per ogni applicazione alla quale lavorerai. In questo modo, ogni applicazione avrà le proprie dipendenze e i propri pacchetti, e non dovrai preoccuparti di eventuali problemi di compatibilità con altre applicazioni. + +In Python, quest'operazione si effettua utilizzando i [virtual environments](https://docs.python.org/3/tutorial/venv.html) (*ambienti virtuali*). Questi ultimi sono degli alberi di directory autonomi che contengono installazioni di Python diverse, ossia particolari versioni di Python unite a tutti i pacchetti richiesti da una certa applicazione. La creazione di ambienti virtuali di questo tipo si può attuare a mezzo di strumenti diversi, anche se qui useremo esclusivamente il pacchetto ufficiale di Python, [`venv`](https://docs.python.org/3/library/venv.html#module-venv). + +Innanzitutto, crea la cartella che ospiterà l'applicazione in questione, come ad esempio una cartella di nome *transformer-course* alla radice della tua home directory: + +``` +mkdir ~/transformer-course +cd ~/transformer-course +``` + +All'interno di questa cartella, crea un ambiente virtuale utilizzando il modulo `venv` di Python: + +``` +python -m venv .env +``` + +A questo punto, dovresti avere una cartella chiamata *.env* in quella che era la tua cartella vuota: + +``` +ls -a +``` + +```out +. .. .env +``` + +Puoi entrare e uscire dall'ambiente virtuale utilizzando gli script `activate` e `deactivate`: + +``` +# Activate the virtual environment +source .env/bin/activate + +# Deactivate the virtual environment +source .env/bin/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! + +``` +which python +``` + +```out +/home//transformer-course/.env/bin/python +``` + +### Installazione dipendenze + +Come già menzionato nella sezione su Google Colab, il passo successivo consiste nell'installazione dei pacchetti richiesti dal corso. Ancora una volta, ti chiediamo di installare la versione per sviluppatori degli 🤗 Transformers utilizzando il gestore di pacchetti `pip`: + +``` +pip install "transformers[sentencepiece]" +``` + +Abbiamo finito con le installazioni! Ora sei pronto/a a iniziare. diff --git a/chapters/it/chapter1/1.mdx b/chapters/it/chapter1/1.mdx new file mode 100644 index 000000000..4fd68aa6a --- /dev/null +++ b/chapters/it/chapter1/1.mdx @@ -0,0 +1,52 @@ +# Introduzione + +## Benvenuto/a al corso di 🤗! + + + +Questo corso ti insegnerà a eseguire compiti di Natural Language Processing (NLP, *elaborazione del linguaggio naturale*) utilizzando le librerie dell'ecosistema di [Hugging Face](https://huggingface.co/): [🤗 Transformers](https://github.com/huggingface/transformers), [🤗 Datasets](https://github.com/huggingface/datasets), [🤗 Tokenizers](https://github.com/huggingface/tokenizers), e [🤗 Accelerate](https://github.com/huggingface/accelerate). Ti insegneremo anche ad usare il nostro [Hugging Face Hub](https://huggingface.co/models), che è completamente gratuito e senza pubblicità. + + +## Contenuti + +Eccoti un breve riassunto dei contenuti del corso: + +
+Brief overview of the chapters of the course. + +
+ +- I capitoli da 1 a 4 forniscono un'introduzione ai concetti principali della libreria 🤗 Transformers. Alla fine di questa parte del corso, conoscerai come funzionano i modelli Transformers e saprai come utilizzare un modello dell'[Hugging Face Hub](https://huggingface.co/models), affinarlo in un dataset, e condividere i tuoi risultati nell'Hub! +- I capitoli da 5 a 8 insegnano le basi degli 🤗 Dataset e degli 🤗 Tokenizer, per poi esplorare alcuni compiti classici di NLP. Alla fine di questa parte, saprai far fronte ai problemi di NLP più comuni in maniera autonoma. +- I capitoli da 9 a 12 vanno oltre il Natural Language Processing, ed esplorano come i modelli Transformer possano essere utilizzati per affrontare compiti di elaborazione vocale o visione artificiale. Strada facendo, imparerai a costruire e condividere demo (*dimostrazioni*) dei tuoi modelli, e ad ottimizzarli per la produzione. Alla fine di questa parte, sarai pronto ad utilizzare gli 🤗 Transformer per qualsiasi problema di machine learning (*apprendimento automatico*), o quasi! + +Questo corso: + +* Richiede una buona conoscenza di Python +* Andrebbe seguito di preferenza a seguito di un corso introduttivo di deep learning (*apprendimento profondo*), come ad esempio il [Practical Deep Learning for Coders](https://course.fast.ai/) di [fast.ai](https://www.fast.ai/), oppure uno dei programmi sviluppati da [DeepLearning.AI](https://www.deeplearning.ai/) +* Non richiede conoscenze pregresse di [PyTorch](https://pytorch.org/) o [TensorFlow](https://www.tensorflow.org/), nonostante sia gradita una conoscenza anche superficiale dell'uno o dell'altro + +Quando avrai completato questo corso, ti raccomandiamo di passare al [Natural Language Processing Specialization](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh) di DeepLearning.AI, un corso che copre un ampio spettro di modelli tradizionali di NLP che vale davvero la pena di conoscere, come Naive Bayes e LSTM (*Memoria a breve termine a lungo termine*)! + +## Chi siamo? + +A proposito degli autori: + +**Matthew Carrigan** è Machine Learning Engineer da Hugging Face. Vive a Dublino, in Irlanda, ed in passato è stato ML engineer da Parse.ly, e prima ancora ricercatore postdottorale al Trinity College di Dublin. Nonostante non creda che otterremo l'Intelligenza artificiale forte semplicemente ingrandendo le architetture a nostra disposizione, spera comunque nell'immortalità cibernetica. + +**Lysandre Debut** è Machine Learning Engineer da Hugging Face e ha lavorato agli 🤗 Transformer fin dalle primissime tappe del loro sviluppo. Il suo obiettivo è di rendere il NLP accessibile a tutti sviluppando strumenti con un semplice API. + +**Sylvain Gugger** è Research Engineer da Hugging Face e uno dei principali manutentori della libreria 🤗 Transformers. In passato, è stato Research Scientist da fast.ai, e ha scritto [Deep Learning for Coders with fastai and PyTorch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/) con Jeremy Howard. Il centro principale della sua ricerca consiste nel rendere il deep learning (*apprendimento profondo*) più accessibile, concependo e migliorando tecniche che permettano di allenare modelli velocemente con risorse limitate. + +**Merve Noyan** è developer advocate da Hugging Face, e lavora allo sviluppo di strumenti e alla creazione di contenuti ad essi legati per democratizzare l'accesso al deep learning. + +**Lucile Saulnier** è machine learning engineer da Hugging Face, e sviluppa e supporta l'utilizzo di strumenti open source. È anche attivamente coinvolta in numerosi progetti di ricerca nell'ambito del NLP, come ad esempio collaborative training e BigScience. + +**Lewis Tunstall** è machine learning engineer da Hugging Face che si specializza nello sviluppo di strumenti open-source e la loro distribuzione alla comunità più ampia. È anche co-autore dell'imminente [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). + +**Leandro von Werra** è machine learning engineer nel team open-source di Hugging Face, nonché co-autore dell'imminente [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). Ha tanti anni di esperienza nel portare progetti di NLP in produzione, lavorando a tutti i livelli di esecuzione di compiti di machine learning. + +Sei pronto/a a iniziare? In questo capitolo, imparerai: +* Ad utilizzare la funzione `pipeline()` per eseguire compiti di NLP come la generazione e classificazione di testi +* L'architettura dei Transformer +* Come fare la distinzione tra architetture encoder, decoder, encoder-decoder, e casi d'uso diff --git a/chapters/it/chapter1/2.mdx b/chapters/it/chapter1/2.mdx new file mode 100644 index 000000000..a5845ca54 --- /dev/null +++ b/chapters/it/chapter1/2.mdx @@ -0,0 +1,21 @@ +# Natural Language Processing + +Prima di tuffarci nei modelli Transformer, diamo un'occhiata rapida alla natura del natural language processing (*elaborazione del linguaggio naturale*) e alle ragioni per cui quest'ultimo ci interessa. + +## Cosa intendiamo per NLP? + +NLP è un campo di linguistica e machine learning (*apprendimento automatico*) che si focalizza sulla comprensione di tutto ciò che è legato al linguaggio umano. L'obiettivo dei compiti di NLP non è semplicemente di capire singole parole individualmente, ma anche di capirne il contesto. + +La seguente è una lista dei più comuni compiti di NLP, ognuno accompagnato da esempi: + +- **Classificazione di frasi intere**: Capire il tono di una recensione, comprendere se una mail si tratta di spam (*spazzatura*), determinare se una frase è grammaticalmente corretta oppure se due frasi hanno un legame logico +- **Classificazione di parole singole all'interno di una frase**: Identificazione dei componenti grammaticali di una frase (nome, verbo, aggettivo), o di entità denominate (persona, località, organizzazione) +- **Generazione di contenuto testuale**: Completare un prompt a mezzo di testo auto-generato, colmare spazi vuoti in un testo con parole mascherate +- **Estrazione di risposte a partire da un testo**: Dati una domanda e un contesto, estrarre la risposta alla domanda sulla base del contesto fornito +- **Generazione di frasi nuove a partire da un testo input**: Traduzione di un testo in un'altra lingua, riassunto di un testo + +NLP non si limita però ai soli testi scritti, e tratta anche sfide complesse in riconoscimento vocale e computer vision (*elaborazione di dati visuali*), quali la generazione di trascrizioni di campioni audio o la descrizione di immagini. + +## Perché costituisce una sfida? + +I computer non elaborano le informazioni allo stesso modo degli umani. Ad esempio, quando leggiamo la frase "Ho fame," ne capiamo senza difficoltà il senso. Allo stesso modo, date due frasi quali "Ho fame" e "Sono triste," riusciamo facilmente a determinarne il livello di similarità. Per i modelli di machine learning (ML), tali compiti sono più difficili. Il testo deve essere elaborato in un modo che permetta al modello di imparare da esso. E siccome il linguaggio è complesso, il modo in cui l'elaborazione va svolta dev'essere studiato con cura. Molta ricerca è stata fatta su come rappresentare i testi, e nel prossimo capitolo vedremo alcuni di questi metodi. diff --git a/chapters/it/chapter1/3.mdx b/chapters/it/chapter1/3.mdx new file mode 100644 index 000000000..7fb506a94 --- /dev/null +++ b/chapters/it/chapter1/3.mdx @@ -0,0 +1,329 @@ +# Cosa fanno i Transformer? + + + +In questa sezione, vedremo di cosa sono capaci i modelli Transformer e useremo il nostro primo strumento della libreria 🤗 Transformer: la funzione `pipeline()`. + + +👀 Lo vedi il pulsante Open in Colab in alto a destra? Cliccalo per aprire il blocco note Colab di Google che contiene tutti gli esempi di codice di questa sezione. Ritroverai il pulsante in ogni sezione che contiene esempi di codice. + +Se intendi compilare gli esempi localmente, ti consigliamo di dare un occhio alla sezione setup. + + +## I Transformer sono ovunque! + +I modelli Transformer sono utilizzati per eseguire qualsiasi compito di NLP, come ad esempio quelli menzionati nelle sezioni precedenti. Ecco alcune delle aziende e organizzazioni che utilizzano Hugging Face e i modelli Transformer, e contribuiscono a loro volta alla comunità condividendo i propri modelli: + +Companies using Hugging Face + +La [libreria 🤗 Transformer](https://github.com/huggingface/transformers) fornisce la funzionalità per creare e utilizzare questi modelli condivisi. Il [Model Hub](https://huggingface.co/models) contiene migliaia di modelli pre-addestrati che possono essere scaricati e usati liberamente. Puoi anche caricare i tuoi modelli nell'Hub! + + +⚠️ L'Hugging Face Hub non si limitata ai soli modelli Transformer. Chiunque può condividere qualsiasi tipo di modello o dataset (insieme di dati)! Crea un profilo huggingface.co per approfittare di tutte le funzioni disponibili! + + +Prima di scoprire come funzionino i modelli Transformer dietro le quinte, vediamo qualche esempio di come questi possano essere utilizzati per risolvere alcuni problemi interessanti di NLP. + +## Lavorare con le pipeline + + + +L'oggetto più basilare della libreria 🤗 Transformer è la funzione `pipeline()`. Questa connette un modello con tutte le fasi necessarie di preprocessing e postprocessing, permettendoci così di fornire un qualsiasi testo come input diretto e ottenere una risposta intelligibile: + +```python +from transformers import pipeline + +classifier = pipeline("sentiment-analysis") +classifier("I've been waiting for a HuggingFace course my whole life.") +``` + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}] +``` + +È anche possibile lavorare su più frasi! + +```python +classifier( + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!"] +) +``` + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}, + {'label': 'NEGATIVE', 'score': 0.9994558095932007}] +``` + +Per default, questa pipeline seleziona un preciso modello pre-addestrato che è stato affinato per il sentiment analysis in inglese. Quando creiamo l'oggetto `classifier`, il modello viene scaricato e memorizzato nella cache. Se inizializziamo di nuovo il comando, verrà utilizzato il modello salvato nella cache e non ci sarà quindi bisogno di scaricare di nuovo il modello. + +Tre passaggi principali sono coinvolti quando passiamo del testo in un pipeline: + +1. Il testo è pre-elaborato in un formato che il modello può capire. +2. Gli input pre-elaborati vengono passati al modello. +3. Le previsioni del modello sono post-elaborate in un formato accessibile all'utilizzatore. + + +Tra le [pipeline disponibili](https://huggingface.co/transformers/main_classes/pipelines.html) al momento ci sono: + +- `feature-extraction` (per ottenere la rappresentazione vettoriale di un testo) +- `fill-mask` +- `ner` (riconoscimento delle entità nominate, *named entity recognition*) +- `question-answering` +- `sentiment-analysis` +- `summarization` +- `text-generation` +- `translation` +- `zero-shot-classification` + +Proviamo a vederne alcune! + +## Classificazione zero-shot + +Cominceremo con l'affrontare un compito impegnativo che consiste nella classificazione di testi non etichettati. Si tratta di uno scenario comune in molti progetti pratici perché l'annotazione testuale richiede tempo e competenza settoriale. In questo caso d'uso, la pipeline `zero-shot-classification` è molto potente e permette di specificare le etichette da utilizzare per la classificazione, in modo da non dover fare affidamento sulle etichette del modello pre-addestrato. Abbiamo già visto come il modello riesca a classificare una frase utilizzando le etichette 'positiva' e 'negativa', ma è anche possibile classificare testi utilizzando una qualsiasi serie di etichette di nostra scelta. + +```python +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +classifier( + "This is a course about the Transformers library", + candidate_labels=["education", "politics", "business"], +) +``` + +```python out +{'sequence': 'This is a course about the Transformers library', + 'labels': ['education', 'business', 'politics'], + 'scores': [0.8445963859558105, 0.111976258456707, 0.043427448719739914]} +``` + +Questa pipeline si chiama _zero-shot_ perché non hai bisogno di affinare il modello usando i tuoi dati per poterlo utilizzare. È direttamente in grado di generare una previsione probabilistica per qualsiasi lista di etichette tu voglia! + + + +✏️ **Provaci anche tu!** Divertiti creando sequenze ed etichette e osserva come si comporta il modello. + + + + +## Generazione di testi + +Vediamo ora come utilizzare la pipeline per generare testi. L'idea è di fornire un prompt (*richiesta*) che verrà auto-completato dal modello, il quale genererà il testo mancante. Si tratta di un compito simile alla funzione di scrittura facilitata che troviamo al giorno d'oggi in molti cellulari. La generazione di testi presenta una componente arbitraria, per cui non essere sorpreso/a se non ottieni gli stessi risultati che mostriamo qui sotto. + +```python +from transformers import pipeline + +generator = pipeline("text-generation") +generator("In this course, we will teach you how to") +``` + +```python out +[{'generated_text': 'In this course, we will teach you how to understand and use ' + 'data flow and data interchange when handling user data. We ' + 'will be working with one or more of the most commonly used ' + 'data flows — data flows of various types, as seen by the ' + 'HTTP'}] +``` + +Usando l'argomento `num_return_sequences` puoi controllare quante sequenze diverse vengono generate e, con l'argomento `max_length`, la lunghezza totale dell'output testuale. + + + +✏️ **Provaci anche tu!** Usa gli argomenti `num_return_sequences` e `max_length` per generare due frasi di 15 parole ciascuna. + + + + +## Utilizzo di un qualsiasi modello dell'Hub in una pipeline + +Gli esempi precedenti utilizzavano il modello di default per il compito dato, ma puoi anche scegliere un modello particolare dell'Hub da utilizzare in una pipeline per un compito specifico, come ad esempio la generazione testuale. Vai al [Model Hub](https://huggingface.co/models) e clicca sull'etichetta corrispondente a destra, in modo da mostrare solo i modelli supportati per il compito in questione. Dovresti ritrovarti in una pagina come [questa](https://huggingface.co/models?pipeline_tag=text-generation). + +Proviamo il modello [`distilgpt2`](https://huggingface.co/distilgpt2)! Ecco come caricarlo nella pipeline usata in precedenza: + +```python +from transformers import pipeline + +generator = pipeline("text-generation", model="distilgpt2") +generator( + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, +) +``` + +```python out +[{'generated_text': 'In this course, we will teach you how to manipulate the world and ' + 'move your mental and physical capabilities to your advantage.'}, + {'generated_text': 'In this course, we will teach you how to become an expert and ' + 'practice realtime, and with a hands on experience on both real ' + 'time and real'}] +``` + +Puoi affinare la ricerca di un modello cliccando sulle etichette corrispondenti alle lingue, e scegliere in seguito un modello che generi testo in un'altra lingua. Il Model Hub contiene anche checkpoint per modelli multilingue che supportano numerose lingue. + +Quando avrai selezionato un modello cliccando su di esso, vedrai che esiste un widget che ti permette di provarlo direttamente online. In questo modo, puoi testare velocemente le capacità del modello prima di scaricarlo. + + + +✏️ **Provaci anche tu!** Usa i filtri per trovare un modello di generazione testuale per un'altra lingua. Sentiti libero/a di divertirti con il widget e usalo in una pipeline! + + + +### La Inference API + +Tutti i modelli possono essere testati direttamente attraverso il tuo browser utilizzando l'Inference API che trovi nel [sito](https://huggingface.co/) di Hugging Face. Puoi divertirti con il modello direttamente in questa pagina, inserendo testo personalizzato e osservando come il modello processi i dati fornitigli. + +La Inference API che alimenta il widget è disponibile anche come prodotto a pagamento, il che è comodo se ne hai bisogno per i tuoi flussi di lavoro. Vedi la [pagina dei prezzi](https://huggingface.co/pricing) per maggiori informazioni. + +## Mask filling + +La prossima pipeline che proverai è `fill-mask`. L'idea di questo compito è di completare gli spazi bianchi in un dato testo: + +```python +from transformers import pipeline + +unmasker = pipeline("fill-mask") +unmasker("This course will teach you all about models.", top_k=2) +``` + +```python out +[{'sequence': 'This course will teach you all about mathematical models.', + 'score': 0.19619831442832947, + 'token': 30412, + 'token_str': ' mathematical'}, + {'sequence': 'This course will teach you all about computational models.', + 'score': 0.04052725434303284, + 'token': 38163, + 'token_str': ' computational'}] +``` + +L'argomento `top_k` gestisce il numero di possibilità che vuoi mostrare. Nota che qui il modello inserisce la `` word speciale, la quale viene spesso chiamata *mask token*. Altri modelli di tipo mask-filling potrebbero avere mask token diversi, quindi è sempre bene verificare quale sia la corretta mask word quando esploriamo nuovi modelli. Un modo per verificarla consiste nel trovare la mask word utilizzata nel widget. + + + +✏️ **Provaci anche tu!** Cerca il modello `bert-base-cased` nell'Hub e identifica la sua mask word nel widget dell'Inference API. Cosa predice questo modello per la frase nel nostro esempio `pipeline` qui sopra? + + + +## Riconoscimento delle entità nominate + +Il riconoscimento delle entità nominate (*Named entity recognition*, NER) è un compito in cui il modello deve determinare quali parti dell'input testuale corrispondono a entità quali persone, località, o organizzazioni. Guardiamo a un esempio: + +```python +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +```python out +[{'entity_group': 'PER', 'score': 0.99816, 'word': 'Sylvain', 'start': 11, 'end': 18}, + {'entity_group': 'ORG', 'score': 0.97960, 'word': 'Hugging Face', 'start': 33, 'end': 45}, + {'entity_group': 'LOC', 'score': 0.99321, 'word': 'Brooklyn', 'start': 49, 'end': 57} +] +``` + +Qui il modello ha correttamente identificato che Sylvain è una persona (PER), Hugging Face un'organizzazione (ORG), e Brooklyn una località (LOC). + +Passiamo l'opzione `grouped_entities=True` nella funzione di creazione della pipeline per raggruppare le parti frasali che corrispondono alla stessa entità: qui il modello raggruppa correttamente "Hugging" e "Face" come singola organizzazione, nonostante il nome sia formato da più parole. A dire il vero, come vedremo nel prossimo capitolo, il preprocessing divide perfino alcune parole in parti più piccole. Ad esempio, `Sylvain` viene suddiviso in quattro parti: `S`, `##yl`, `##va`, and `##in`. Al momento del post-processing, la pipeline raggruppa le parti con successo. + + + +✏️ **Provaci anche tu!** Nel Model Hub, cerca un modello capace di effettuare part-of-speech tagging (comunemente abbreviato come POS) in inglese. Cosa predice il modello per la frase nell'esempio qui sopra? + + + +## Risposta a domande + +La pipeline `question-answering` risponde a domande utilizzando informazioni da un contesto prestabilito: + +```python +from transformers import pipeline + +question_answerer = pipeline("question-answering") +question_answerer( + question="Where do I work?", + context="My name is Sylvain and I work at Hugging Face in Brooklyn", +) +``` + +```python out +{'score': 0.6385916471481323, 'start': 33, 'end': 45, 'answer': 'Hugging Face'} +``` + +Nota che questa pipeline non genera risposte ma estrae informazioni da un contesto fornito. + +## Riassunto + +Quello del riassunto è un compito che trasforma un testo in un testo più breve, conservando tutti (o quasi) gli argomenti più importanti del testo di partenza. Ecco un esempio: + +```python +from transformers import pipeline + +summarizer = pipeline("summarization") +summarizer( + """ + America has changed dramatically during recent years. Not only has the number of + graduates in traditional engineering disciplines such as mechanical, civil, + electrical, chemical, and aeronautical engineering declined, but in most of + the premier American universities engineering curricula now concentrate on + and encourage largely the study of engineering science. As a result, there + are declining offerings in engineering subjects dealing with infrastructure, + the environment, and related issues, and greater concentration on high + technology subjects, largely supporting increasingly complex scientific + developments. While the latter is important, it should not be at the expense + of more traditional engineering. + + Rapidly developing economies such as China and India, as well as other + industrial countries in Europe and Asia, continue to encourage and advance + the teaching of engineering. Both China and India, respectively, graduate + six and eight times as many traditional engineers as does the United States. + Other industrial countries at minimum maintain their output, while America + suffers an increasingly serious decline in the number of engineering graduates + and a lack of well-educated engineers. +""" +) +``` + +```python out +[{'summary_text': ' America has changed dramatically during recent years . The ' + 'number of engineering graduates in the U.S. has declined in ' + 'traditional engineering disciplines such as mechanical, civil ' + ', electrical, chemical, and aeronautical engineering . Rapidly ' + 'developing economies such as China and India, as well as other ' + 'industrial countries in Europe and Asia, continue to encourage ' + 'and advance engineering .'}] +``` + +Come nella generazione di testi, puoi specificare un `max_length` o `min_length` per il testo da generare. + + +## Traduzione + +Per compiti di traduzione, puoi utilizzare un modello di default indicando la coppia linguistica nel nome del compito (come ad esempio `"translation_en_to_fr"`), anche se il metodo più semplice è di scegliere il modello che desideri utilizzare dal [Model Hub](https://huggingface.co/models). Qui in seguito traduciamo dal francese all'inglese: + +```python +from transformers import pipeline + +translator = pipeline("translation", model="Helsinki-NLP/opus-mt-fr-en") +translator("Ce cours est produit par Hugging Face.") +``` + +```python out +[{'translation_text': 'This course is produced by Hugging Face.'}] +``` + +Come per le funzioni di generazione testuale e riassunto, è possibile specificare un `max_length` o un `min_length` per il risultato. + + + +✏️ **Provaci anche tu!** Cerca modelli di traduzione in altre lingue e prova a tradurre la frase precedente in un paio di lingue diverse. + + + +Finora abbiamo mostrato pipeline a solo scopo dimostrativo. Tali pipeline sono state programmate per compiti ben specifici e non sono in grado di eseguire variazioni di questi ultimi. Nel prossimo capitolo, imparerai cosa si nasconde dentro la funzione `pipeline()` e come personalizzarne il comportamento. diff --git a/chapters/ru/_toctree.yml b/chapters/ru/_toctree.yml index 895b7475c..16b8d901c 100644 --- a/chapters/ru/_toctree.yml +++ b/chapters/ru/_toctree.yml @@ -30,3 +30,12 @@ - local: chapter2/1 title: Введение + +- title: 3. Fine-tuning предобученной модели + sections: + - local: chapter3/1 + title: Введение + - local: chapter3/2 + title: Предобработка данных + - local: chapter3/3 + title: Fine-tuning модели с использованием Trainer API diff --git a/chapters/ru/chapter3/1.mdx b/chapters/ru/chapter3/1.mdx new file mode 100644 index 000000000..a4fc5d0f1 --- /dev/null +++ b/chapters/ru/chapter3/1.mdx @@ -0,0 +1,22 @@ + + +# Введение + +В [главе 2](/course/chapter2) мы увидели, как можно использовать токенизаторы и предобученные модели для построения предсказаний. Но что если мы хотим дообучить предобученную модель на собственном датасете? Это и есть тема данной главы! Мы изучим: + +{#if fw === 'pt'} +* Как подготовить большой датасет из Model Hub +* Как использовать высокоуровненое API для дообучения модели +* Как использовать собственный цикл обучения (training loop) +* Как использовать библиотеку 🤗 Accelerate для запуска собственного цикла обучения на распределенной вычислительной структуре + +{:else} +* Как подготовить большой датасет из Model Hub +* Как использовать Keras для дообучения модели +* Как использовать Keras для получения предсказаний +* Как использовать собственную метрику + + +{/if} + +Чтобы загрузить свои чекпоинты на Hugging Face Hub, необходимо иметь учетную запись: [создать аккаунт](https://huggingface.co/join) \ No newline at end of file diff --git a/chapters/ru/chapter3/2.mdx b/chapters/ru/chapter3/2.mdx new file mode 100644 index 000000000..c3932bd16 --- /dev/null +++ b/chapters/ru/chapter3/2.mdx @@ -0,0 +1,382 @@ + + +# Предобработка данных + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} +Продолжим с примером из [предыдущей главы](/course/chapter2) +Continuing with the example from the [previous chapter](/course/chapter2), вот как мы будем обучать классификатор последовательности на одном батче с помощью PyTorch: + +```python +import torch +from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification + +# Так же, как и в прошлый раз +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "This course is amazing!", +] +batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") + +# Эта часть новая +batch["labels"] = torch.tensor([1, 1]) + +optimizer = AdamW(model.parameters()) +loss = model(**batch).loss +loss.backward() +optimizer.step() +``` +{:else} +Continuing with the example from the [previous chapter](/course/chapter2), вот как мы будем обучать классификатор последовательности на одном батче с помощью TensorFlow: + +```python +import tensorflow as tf +import numpy as np +from transformers import AutoTokenizer, TFAutoModelForSequenceClassification + +# Так же, как и в прошлый раз +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "This course is amazing!", +] +batch = dict(tokenizer(sequences, padding=True, truncation=True, return_tensors="tf")) + +# Эта часть новая +model.compile(optimizer="adam", loss="sparse_categorical_crossentropy") +labels = tf.convert_to_tensor([1, 1]) +model.train_on_batch(batch, labels) +``` +{/if} + +Обучение всего лишь на двух предложениях, конечно, не даст хорошего результата. Чтобы получить более качественные результаты, вам следует подготовить б_о_льший датасет. + +В данном разделе мы будем использовать в качестве примера MRPC (Microsoft Research Paraphrase Corpus) dataset, предложенный в [статье](https://www.aclweb.org/anthology/I05-5002.pdf) авторами William B. Dolan и Chris Brockett. Датасет состоит из 5801 пар предложений с соответствующим им лейблом: является ли пара преложений парафразами или нет (т.е. идет ли речь в обоих предложениях об одном и том же). Мы выбрали именно этот датасет для этой главы потому что он небольшой: с ним легко экспериментировать в процессе обучения. + +### Загрузка датасета с Hub + +{#if fw === 'pt'} + +{:else} + +{/if} + +Hub содержит не только модели, там также расположено множество датасетов на различных языках. Вы можете посмотреть на них [тут](https://huggingface.co/datasets), а также мы рекомендуем попровать загрузить новый датасет после того, как вы изучите текущий раздел (см. документацию [здесь](https://huggingface.co/docs/datasets/loading_datasets.html#from-the-huggingface-hub)). Но сейчас вернемся к датасету MRPC! Это один из 10 датасетов из состава [GLUE](https://gluebenchmark.com/), который является тестом для производительности моделей машинного обучения в задачах классификации текста. + +Библиотека 🤗 Datasets предоставляет возможность использовать очень простую команду для загрузки и кэширования датасета с Hub. Мы можем загрузить датасет следующим образом: + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("glue", "mrpc") +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 408 + }) + test: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 1725 + }) +}) +``` + +Как можно заметить, мы получили объект типа `DatasetDict`, который содержит обучающую выборку, валидационную выборку и тестовую выборку. Каждая из них содержит несколько колонок (`sentence1`, `sentence2`, `label`, и `idx`) и переменную с числом строк (число элементов в каждой выборке): 3668 пар предложений в обучающей части, 408 в валидационной и 1725 в тестовой . + +Эта команда загружает и кэширует датасет (по умолчанию в *~/.cache/huggingface/dataset*). Вспомним из главы 2, что вы можете изменить путь к кэшу изменив переменную окружения `HF_HOME`. + +Мы можем получить доступ к предложениями в объекте `raw_datasets` путем индексирования, как в словаре: + +```py +raw_train_dataset = raw_datasets["train"] +raw_train_dataset[0] +``` + +```python out +{'idx': 0, + 'label': 1, + 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', + 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} +``` + +Можно увидеть, что лейблы уже являются целыми числами (integer), их обрабатывать не нужно. Чтобы сопосотавить индекс класса с его названием, можно распечатать значение переменной `features` у `raw_train_dataset`: + +```py +raw_train_dataset.features +``` + +```python out +{'sentence1': Value(dtype='string', id=None), + 'sentence2': Value(dtype='string', id=None), + 'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None), + 'idx': Value(dtype='int32', id=None)} +``` + +Переменная `label` типа `ClassLabel` соответствует именам в *names*. `0` соответствует `not_equivalent`, `1` соответствует `equivalent`. + + + +✏️ **Попробуйте!** Посмотрите на 15-й элемент обучающей выборки и на 87-й элемент вадидационной выборки. Какие у них лейблы? + + + +### Предобработка датасета + +{#if fw === 'pt'} + +{:else} + +{/if} + +Чтобы предобработать датасет, нам необходимо конвертировать текст в числа, которые может обработать модель. Как вы видели в [предыдущей главе](/course/chapter2), это делается с помощью токенайзера. Мы можем подать на вход токенайзеру одно или список предложений, т.е. можно токенизировать предложения попарно таким образом: + +```py +from transformers import AutoTokenizer + +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"]) +tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"]) +``` + +Однако мы не можем просто передать две последовательности в модель и получить прогноз того, являются ли эти два предложения парафразами или нет. Нам нужно обрабатывать две последовательности как пару и применять соответствующую предварительную обработку. К счастью, токенизатор также может взять пару последовательностей и подготовить их так, как ожидает наша модель BERT: + +```py +inputs = tokenizer("This is the first sentence.", "This is the second one.") +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] +} +``` + +Мы уже обсуждали ключи `input_ids` и `attention_mask` в [главе 2](/course/chapter2), но не упоминали о `token_type_ids`. В этом примере мы указываем модели какая часть входных данных является первым предложением, а какая вторым. + + + +✏️ **Попробуйте!** Токенизируйте 15-й элемент обучающей выборки как два предложения, и как пару предложений. В чем разница между двумя результатами? + + + +Если мы декодируем ID из `input_ids` обратно в слова: + +```py +tokenizer.convert_ids_to_tokens(inputs["input_ids"]) +``` + +мы получим + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +``` + +Видно, что модель ожидает входные данные в следующем формате: `[CLS] sentence1 [SEP] sentence2 [SEP]` в случае двух предложений. Посмотрим соответствие элементов и `token_type_ids` + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +[ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] +``` + +Как вы можете заметить, части входных данных, соответствующих `[CLS] sentence1 [SEP]` имеют тип токена `0`, в то время как остальные части, соответствующие второму предложению `sentence2 [SEP]`, имеют тип токена `1`. + +Обратите внимание, что если вы выберете другой чекпоинт, `token_type_ids` необязательно будут присутствовать в ваших токенизированных входных данных (например, они не возвращаются, если вы используете модель DistilBERT). Они возвращаются только тогда, когда модель будет знать, что с ними делать, потому что она видела их во время предобучения. + +В данном случае BERT был обучен с информацией о идентификаторах типов токенов, и помимо задачи маскированной языковой модели, о которой мы говорили в [главе 1](/course/chapter1), он может решать еще одну задачу: предсказание следующего предложения (_next sentence prediction_). Суть этой задачи - смоделировать связь между предложениями. + +В этой задаче модели на вход подаются пары предложений (со случайно замаскированными токенами), от модели требуется предсказать, является ли следующее предложение продолжением текущего. Чтобы задача не была слишком тривиальной, половина времени модель обучается на соседних предложениях из одного документа, другую половину на парах предложений, взятых из разных источников. + +В общем случае вам не нужно беспокоиться о наличии `token_type_ids` в ваших токенизированных данных: пока вы используете одинаковый чекпоинт и для токенизатора, и для модели – токенизатор будет знать, как нужно обработать данные. + +Теперь мы знаем, что токенизатор может подготовить сразу пару предложений, а значит мы можем использовать его для целого датасета: так же как и в [предыдущей главе](/course/chapter2) можно подать на вход токенизатору список первых предложений и список вторых предложений. Это также сработает и для механизмов дополнения (padding) и усечения до максимальной длины (truncation) - об этом мы говорили в [главе 2](/course/chapter2). Итак, один из способов предобработать обучающий датасет такой: + +```py +tokenized_dataset = tokenizer( + raw_datasets["train"]["sentence1"], + raw_datasets["train"]["sentence2"], + padding=True, + truncation=True, +) +``` + +Это хорошо работает, однако есть недостаток, который формирует токенизатор (с ключами, `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()` работает так: применяет некоторую функцию к каждому элементу датасета, давайте определим функцию, которая токенизирует наши входные данные: + +```py +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) +``` + +Эта функция принимает на вход слвоарь (похожий на элементы нашего словаря) и возвращает новый словарь с ключами `input_ids`, `attention_mask` и `token_type_ids`. Заметьте, это также работает если словарь `example` содержит несколько элементов (каждый ключ в виде списка предложений), поскольку `tokenizer` работает и со списками пар предложений, как мы и видели ранее. Это позволит нам использовать аргумент `batched=True` в вызове `map()`, которая ускорит процесс токенизации. `tokenizer` внутри реализован на языке Rust из библиотеки [🤗 Tokenizers](https://github.com/huggingface/tokenizers). Этот токенизатор может быть очень быстрым, но только если мы подадим большой объем данных за раз. + +Обратите внимание, в этот раз мы оставили аргумент `padding` пустым, потому что дополнение данных до максимальной длины неэффективно: гораздо быстрее делать это во время формирования батча, в таком случае мы будем дополнять до максимальной длины только элементы батча, а не целого датасета. Это поможет сэкономить время в случае длинных последовательностей. + +Ниже пример того, как мы применяем функцию токенизации к целому датасету. Мы указываем `batched=True` в нашем вызове `map` и функция будет применена сразу к нескольким элементам датасета одновременно, а не к каждому по отдельности. Это позволяет сделать токенизацию более быстрой. + +```py +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) +tokenized_datasets +``` + +Библиотека 🤗 Datasets применяет обработку, добавляя новые поля в наборы данных, по одному для каждого ключа в словаре, который возвращает функция предварительной обработки: + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 408 + }) + test: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 1725 + }) +}) +``` + +В функции `map()` можно использовать мультипроцессинг: за это отвечает аргумент `num_proc`. Мы его не применяли, потому что библиоетека 🤗 Tokenizers сразу использует несколько потоков для токенизации, но если вы будете использовать функцию не из 🤗 Tokenizers, это может ускорить процесс. + +Наша функция `tokenize_function` возвращает словарь с ключами `input_ids`, `attention_mask` и `token_type_ids`, они уже добавлены ко всем разбиениями нашего датасета. Обратите внимание, что мы могли бы также изменить существующие поля, если бы наша функция препроцессинга вернула новое значение для существующего ключа в наборе данных, к которому мы применили `map()`. + +Последнее, что нам нужно сделать, это дополнить все примеры до длины самого длинного элемента, когда мы собираем элементы вместе — метод, который мы называем *динамическим пэддингом* (*dynamic padding*). + + +### Dynamic padding + + + +{#if fw === 'pt'} +Функция, отвечающая за объединение элементов внутри батча, называется *collate function* (функция сопоставления). Это аргумент, который вы можете передать при создании `DataLoader`, по умолчанию это функция, которая просто преобразует ваши образцы в тензоры PyTorch и объединяет их (рекурсивно, если вашими элементами являются списки, кортежи или словари). В нашем случае это невозможно, поскольку входные данные, которые у нас есть, не будут иметь одинакового размера. Мы намеренно не стали делать пэддинг, чтобы применять его только по мере необходимости в каждом пакете и избегать слишком длинных входных данных с большим количеством отступов. Это немного ускорит обучение, но учтите, что если вы тренируетесь на TPU, это может вызвать проблемы — TPU предпочитают фиксированные формы, даже если для этого требуется дополнительн пэддинг. + +{:else} +Функция, отвечающая за объединение элементов внутри батча, называется *collate function* (функция сопоставления). По умолчанию — это функция, которая просто преобразует ваши образцы в tf.Tensor и объединяет их (рекурсивно, если ваши элементы — это списки, кортежи или словари). В нашем случае это невозможно, поскольку входные данные, которые у нас есть, не будут иметь одинакового размера. Мы намеренно отложили отступы, чтобы применять их только по мере необходимости в каждом пакете и избегать слишком длинных входных данных с большим количеством отступов. Это немного ускорит обучение, но учтите, что если вы тренируетесь на TPU, это может вызвать проблемы — TPU предпочитают фиксированные формы, даже если для этого требуется дополнительное дополнение. + +{/if} +Для того, чтобы сделать это на практике, мы должны задать функцию сопоставления, которая будет осуществлять корректный пэддинг элементов выборки, которые мы хотим объединить в батч. К счастью, библиотека 🤗 Transformers предоставляет нам эту функцию через класс `DataCollatorWithPadding`. При создании экземпляра требуется указать токенизатор (чтобы знать, какой токен использовать для пэддинга и слева или справа нужно дополнять данные), а дальше функция сделает все, что вам нужно: + +{#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} +Чтобы протестировать это, давайте возьмем несколько элементов обучающей выборки, которые мы хотим объединить в батч. Мы удалим колонки `idx`, `sentence1` и `sentence2` т.к. они содержат строки (а мы не можем превратить строки в тензоры) и посмотрим на длину каждой записи в батче: + + +```py +samples = tokenized_datasets["train"][:8] +samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]} +[len(x) for x in samples["input_ids"]] +``` + +```python out +[50, 59, 47, 67, 59, 50, 62, 32] +``` + +Неудивительно: мы получили объекты разной длины от 32 до 67. Динамический пэддинг подразумевает, что все объекты будут дополнены до максимальной длины, до 67. + +No surprise, we get samples of varying length, from 32 to 67. Dynamic padding means the samples in this batch should all be padded to a length of 67, the maximum length inside the batch. Без динамического заполнения все выборки должны быть дополнены до максимальной длины во всем наборе данных, или до максимальной длины, которую может принять модель. Давайте дважды проверим, что наш `data_collator` динамически правильно дополняет батч: +```py +batch = data_collator(samples) +{k: v.shape for k, v in batch.items()} +``` + +{#if fw === 'tf'} + +```python out +{'attention_mask': TensorShape([8, 67]), + 'input_ids': TensorShape([8, 67]), + 'token_type_ids': TensorShape([8, 67]), + 'labels': TensorShape([8])} +``` + +{:else} + +```python out +{'attention_mask': torch.Size([8, 67]), + 'input_ids': torch.Size([8, 67]), + 'token_type_ids': torch.Size([8, 67]), + 'labels': torch.Size([8])} +``` + +Выглядит неплохо! Теперь мы пришли от обычного текста к батчу, с которым может работать наша модель. Можем приступить к fine-tuning! + +{/if} + + + +✏️ **Попробуйте!** Повторите этап препроцессинга для набора данных GLUE SST-2. Он немного отличается, так как состоит из отдельных предложений, а не пар, но в остальном это то же самое, что мы сделали. Для более сложной задачи попробуйте написать функцию предварительной обработки, которая работает с любой из задач GLUE. + + + +{#if fw === 'tf'} + +Теперь, когда у нас есть набор данных и функция сопоставления данных, нам нужно собрать их вместе. Мы могли бы вручную загружать пакеты и применять к ним функцию, но это неудобно и не очень эффективно. Вместо этого есть простой метод, предлагающий эффективное решение этой проблемы: `to_tf_dataset()`. Эта функция обернет `tf.data.Dataset` вокруг вашего набора данных с дополнительной функцией сопоставления. `tf.data.Dataset` — это собственный формат TensorFlow, который Keras может использовать для `model.fit()`, поэтому этот метод немедленно преобразует 🤗 набор данных в формат, готовый для обучения. Давайте посмотрим на это в действии с нашим набором данных! + +```py +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, +) +``` + +Это все! Мы можем использовать эти датасеты в следующей лекции, где процесс обучения будет очень простым после рутинной процедура предобработки. + +{/if} diff --git a/chapters/ru/chapter3/3.mdx b/chapters/ru/chapter3/3.mdx new file mode 100644 index 000000000..b68dbfa01 --- /dev/null +++ b/chapters/ru/chapter3/3.mdx @@ -0,0 +1,175 @@ + + +# Fine-tuning модели с использованием Trainer API + + + + + +Библиотека 🤗 Transformers предоставляет класс `Trainer`, который помогает произвести fine-tuning любой предобученной модели на вашем датасете. После предобработки данных, сделанных в прошлом разделе, вам останется сделать несколько шагов для определения `Trainer`. Самая сложная часть – подготовить окружение для запуска `Trainer.train()`, т.к. она медленно работает на центральном процессоре. Если у вас нет видеокарты, вы можете бесплатно воспользоваться сервисом [Google Colab](https://colab.research.google.com/), который предоставляет доступ к вычислениям с использованием GPU и TPU. + +Фрагменты кода ниже предполагают, что вы выполнили код из предыдущего раздела. Ниже приведено краткое резюме того, что вам нужно: + +```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` (для процессов обучения и валидации). Единственный аргумент, который вы должны задать — это каталог, в котором будет сохранена обученная модель, а также контрольные точки. Для всего остального вы можете оставить значения по умолчанию, которые должны подойти для базового fine-tuning. + + +```py +from transformers import TrainingArguments + +training_args = TrainingArguments("test-trainer") +``` + + + +💡 Если вы хотите автоматически загружать модель на Hub во время обучения, передайте аргумент `push_to_hub=True` в `TrainingArguments`. Мы больше узнаем об этом в главе [Chapter 4](/course/chapter4/3). + + + +Второй шаг – задание модели. Так же, как и в [предыдущей главе](/course/chapter2), мы будем использовать класс `AutoModelForSequenceClassification` с двумя лейблами: + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +После создания экземпляра предобученной модели будет распечатано предупреждение (в [главе 2](/course/chapter2) мы с таким не сталкивались). Это происходит потому, что BERT не был предобучен для задачи классификации пар предложений, его последний слой не будет использован, вместо него будет добавлен слой, позволяющий работать с такой задачей. Предупреждения сообщают, что некоторые веса не будут использованы (как раз тех слоев, которые не будут использоваться) и для новых будут инициализированы случайные веса. В заключении предлагается обучить модель, что мы и сделаем прямо сейчас. + +После того, как мы загрузили модель, мы можем определить `Trainer` и передать туда нужные объекты: `model`, `training_args`, обучающую и валидационную выборки, `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`) можно пропустить в этом вызове. + + + +Для fine-tuning модели на нашем датасете мы просто должны вызвать метод `train()` у `Trainer`: + +```py +trainer.train() +``` + +Это запустит процесс дообучения (который должен занять несколько минут на GPU) и будет распечатывать значение лосса каждые 500 итераций. Однако эти значения не скажут нам, насколько хорошо или плохо модель работает. И вот почему: + +1. Мы не сообщили `Trainer`, что необходимо проводить валидацию: для этого нужно присвоить аргументу `evaluation_strategy` значение `"steps"` (валидировать каждые `eval_steps`) или `"epoch"` (валидировать по окончании каждой эпохи). +2. Мы не указали `Trainer` аргумент `compute_metrics()` – функцию для вычисления метрики на валидационной части (в таком случае в процессе валидации будет только распечатываться значение лосса, что не очень информативно). + + +### Валидация + +Давайте посмотрим как мы можем создать и использовать в процессе обучения полезную функцию `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` поле `metrics` также будет содержать результат функции `compute_metrics()`. + +Как можно заметить, `predictions` - массив 408 х 2 (408 - число элементов в датасете, который мы использовали). Это логиты для каждого элемента нашего датасета, переданного в `predict()` (как вы видели в [предыдущей главе](/course/chapter2) все модели Трансформеров возвращают логиты). Чтобы превратить их в предсказания и сравнить с нашими лейблами, нам необходимо узнать индекс максимального элемента второй оси: + +```py +import numpy as np + +preds = np.argmax(predictions.predictions, axis=-1) +``` + +Теперь мы можем сравнить эти предсказания с лейблами. Для создания функции `compute_metric()` мы воспользуемся метриками из библиотеки 🤗 Datasets. Мы можем загрузить подходящие для датасета 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} +``` + +У вас эти результаты могут отличаться ввиду случайной инициализации параметров модели. Тут мы можем увидеть точность 85.78% и F1 89.97% на валидационной части выборки. Эти метрики используются для валидации результатов на MRPC датасете на бенчмарке GLUE. В таблице в статье о [BERT](https://arxiv.org/pdf/1810.04805.pdf) указано значение F1 оценки в 88.9 для базовой модели. Это была оценка для варианта модели `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()`: + +```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() +``` + +На этот раз будет распечатываться валидационный лосс и метрики по окончанию каждой эпохи обучения. Напомним, что полученные значения точности и F1 могут не полностью совпадать с приведенными в примере из-за случайной инициализации слоев модели, но порядок должен быть примерно таким же. + +`Trainer` может работать с несколькими GPU или TPU и предоставляет множество опций, например применение техники mixed-precision (установите `fp16 = True` в аргументах). Подробно об опциях мы поговорим чуть в Главе 10. + +На этом введение в fine-tuning с использованием API `Trainer` подошло к концу. Пример того, как сделать это же для наиболее распространенных задач NLP мы рассмотрим в Главе 7, а сейчас взглянем на то, как реализовать то же самое на чистом PyTorch. + + + +✏️ **Попробуйте!** Произведите fine-tuning модели на датасете GLUE SST-2 с использованием препроцессинга из раздела 2. + + + diff --git a/chapters/th/_toctree.yml b/chapters/th/_toctree.yml index 0b5d7be22..cefc160de 100644 --- a/chapters/th/_toctree.yml +++ b/chapters/th/_toctree.yml @@ -7,6 +7,25 @@ sections: - local: chapter1/1 title: บทนำ + - local: chapter1/2 + title: การประมวลผลภาษาธรรมชาติ + - local: chapter1/3 + title: Transformers ชื่อนี้มีดียังไง? + - local: chapter1/4 + title: Transformers ทำงานยังไง? + - local: chapter1/5 + title: โมเดล Encoder + - local: chapter1/6 + title: โมเดล Decoder + - local: chapter1/7 + title: โมเดล sequence-to-sequence + - local: chapter1/8 + title: ข้อจำกัดจากอคติของข้อมูล + - local: chapter1/9 + title: สรุป + - local: chapter1/10 + title: คำถามท้ายบท + quiz: 1 - title: 2. การใช้งาน 🤗 Transformers sections: @@ -48,3 +67,4 @@ - local: chapter4/6 title: คำถามท้ายบท quiz: 4 + diff --git a/chapters/th/chapter0/1.mdx b/chapters/th/chapter0/1.mdx index 89a4dcdd6..126c706be 100644 --- a/chapters/th/chapter0/1.mdx +++ b/chapters/th/chapter0/1.mdx @@ -23,13 +23,13 @@ An empty colab notebook
-ขั้นตอนต่อไป คือ การติดตั้ง library ที่ต้องใช้งานในคอร์สนี้ โดยเราจะใช้คำสั่ง `pip` ซึ่งเป็นตัวจัดการ package ใน Python เพื่อใช้ในการติดตั้ง โดยใน notebook นั้น คุณสามารถใช้งานคำสั่งระบบได้ด้วยการใส่ตัวอักษร `!` ด้านหน้าคำสั่งได้ ดังนั้นคุณสามารถติดตั้ง library 🤗 Transformers ด้วยคำสั่งดังต่อไปนี้: +ขั้นตอนต่อไป คือ การติดตั้ง library ที่ต้องใช้งานในคอร์สนี้ โดยเราจะใช้คำสั่ง `pip` ซึ่งเป็นตัวจัดการ package ใน Python เพื่อใช้ในการติดตั้ง โดยใน notebook นั้น คุณสามารถใช้งานคำสั่งระบบได้ด้วยการใส่ตัวอักษร `!` ด้านหน้าคำสั่ง ดังนั้นคุณสามารถติดตั้ง library 🤗 Transformers ด้วยคำสั่งดังต่อไปนี้: ``` !pip install transformers ``` -คุณสามารถตรวจสอบว่าได้ติดตั้ง package เรียบร้อยแล้วหรือไม่ด้วยการ import เข้าไปใน Python ด้วยคำสั่งดังต่อไปนี้: +คุณสามารถตรวจสอบว่า คุณติดตั้ง package เรียบร้อยแล้วหรือไม่ด้วยการ import เข้าไปใน Python ด้วยคำสั่งดังต่อไปนี้: ``` import transformers @@ -54,7 +54,7 @@ import transformers เมื่อคุณติดตั้ง Python เสร็จเรียบร้อย คุณสามารถรันคำสั่ง Python ที่ terminal ของคุณได้ โดยใช้คำสั่งดังต่อไปนี้: `python --version` หากคำสั่งนี้แสดงผลออกมาเป็น version ของ Python แสดงว่าระบบได้ติดตั้ง Python ลงบนเครื่องของคุณเรียบร้อยแล้วจริง ๆ -เมื่อคุณใช้คำสั่ง Python ที่ terminal เช่น `python --version` ตัวโปรแกรมจะไปเรียก Python "ชุดหลัก"จากระบบของคุณ เราไม่แนะนำให้คุณติดตั้ง package ใด ๆ ลงบน Python "ชุดหลัก" ดังกล่าว แต่ให้ใช้ในการสร้าง environment แยกออกมาในแต่ละการใช้งาน ดังนั้น แต่ละงานจะมี package และ dependency ของตนเอง ทำให้คุณไม่ต้องกังวลกับปัญหาใช้งานไม่ได้เพราะเวอร์ชันไม่ตรงเพราะไปชนกับการใช้งานอื่น ๆ +เมื่อคุณใช้คำสั่ง Python ที่ terminal เช่น `python --version` ตัวโปรแกรมจะไปเรียก Python "ชุดหลัก"จากระบบของคุณ เราไม่แนะนำให้คุณติดตั้ง package ใด ๆ ลงบน Python "ชุดหลัก" ดังกล่าว แต่ให้ใช้ในการสร้าง environment แยกออกมาในแต่ละการใช้งาน ดังนั้น แต่ละงานจะมี package และ dependency ของตนเอง ทำให้คุณไม่ต้องกังวลกับปัญหาใช้งานไม่ได้เพราะเวอร์ชันไม่ตรงเนื่องจากเวอร์ชันของ library ที่งานหนึ่งไปขัดกับ library เดียวกันที่อีกงานหนึ่ง สำหรับ Python แล้ว กระบวนการนี้สามารถทำได้โดยใช้ [*virtual environment*](https://docs.python.org/3/tutorial/venv.html) ซึ่งเป็นการเก็บ directory ทั้งหมดในการติดตั้ง package ที่ต้องการในเวอร์ชันที่เราใช้งาน การสร้าง virtual environment สามารถทำได้หลายวิธี แต่เราจะใช้ package อย่างเป็นทางการจาก Python ชื่อว่า [`venv`](https://docs.python.org/3/library/venv.html#module-venv). @@ -91,7 +91,7 @@ source .env/bin/activate source .env/bin/deactivate ``` -คุณสามารถตรวจสอบได้ว่าอยู่ใน environment ใดได้ด้วยคำสั่ง `which python` ระบบจะแสดงผล environment ที่คุณกำลังใช้งานอยู่ +คุณสามารถตรวจสอบได้ว่า คุณอยู่ใน environment ใดได้ด้วยคำสั่ง `which python` ระบบจะแสดงผล environment ที่คุณกำลังใช้งานอยู่ ``` which python diff --git a/chapters/th/chapter1/1.mdx b/chapters/th/chapter1/1.mdx index a2851bdc6..049f79a98 100644 --- a/chapters/th/chapter1/1.mdx +++ b/chapters/th/chapter1/1.mdx @@ -16,7 +16,7 @@ -- บทที่ 1 ถึง 4 จะพาคุณไปรู้จักกับคอนเซปต์หลัก ๆ ของ library 🤗 Transformers เมื่อเรียนเนื้อหาในส่วนนี้จบแล้วคุณจะเรียนรู้ว่าโมเดล Transformer ทำงานอย่างไร และเรียนรู้การใช้งานโมเดลจาก [Hugging Face Hub](https://huggingface.co/models), fine-tune โมเดลผ่าน dataset, และแชร์ผลที่ได้รับบน Hub +- บทที่ 1 ถึง 4 จะพาคุณไปรู้จักกับคอนเซปต์หลัก ๆ ของ library 🤗 Transformers เมื่อเรียนเนื้อหาในส่วนนี้จบแล้วคุณจะเรียนรู้ว่าโมเดล Transformer ทำงานอย่างไร และเรียนรู้การใช้งานโมเดลจาก [Hugging Face Hub](https://huggingface.co/models), fine-tune โมเดลผ่าน dataset, และแชร์ผลลัพธ์ขึ้น Hub - บทที่ 5 ถึง 8 จะสอนเกี่ยวกับ 🤗 Datasets และ 🤗 Tokenizers พื้นฐานก่อนที่จะไปลุยกันในส่วนของงานทางด้าน NLP แบบดั้งเดิม หลังจากเรียนเนื้อหาส่วนนี้จบ คุณจะสามารถแก้ปัญหา NLP ทั่วไปเองได้แล้ว - บทที่ 9 ถึง 12 จะเจาะลึกเนื้อหามากกว่า NLP โดยจะเรียนรู้วิธีการที่โมเดล Transformer จัดการกับการประมวลผลคำพูด(หรือเรียกว่า speech processing) รวมถึงงานทางด้านการประมวลผลภาพ(หรือเรียกว่า computer vision) ระหว่างนี้คุณจะได้เรียนรู้วิธีการสร้างและแชร์เดโมของโมเดล รวมถึงการ optimize เพื่อให้สามารถใช้งานในระดับ production ได้จริง หลังจากเรียนเนื้อหาส่วนนี้จบ คุณก็พร้อมที่จะใช้ 🤗 Transformers ในการแก้(เกือบ)ทุกปัญหาเกี่ยวกับ machine learning ได้แล้ว! diff --git a/chapters/th/chapter1/10.mdx b/chapters/th/chapter1/10.mdx new file mode 100644 index 000000000..ac4c5f172 --- /dev/null +++ b/chapters/th/chapter1/10.mdx @@ -0,0 +1,253 @@ + + +# คำถามท้ายบท + +บทนี้พูดถึงพื้นฐานค่อนข้างเยอะมาก ไม่ต้องกังวลไปหากคุณไม่เข้าใจรายละเอียดทั้งหมด บทหน้าจะช่วยอธิบายว่าแต่ละอย่างทำงานกันเบื้องหลังอย่างไร + +ตอนนี้มาทดสอบกันดีกว่าว่าคุณได้เรียนรู้อะไรมาบ้างในบทนี้! + +### 1. เปิดหา checkpoint `roberta-large-mnli` ใน Hub โมเดลนี้ใช้ในงานอะไร + + +หน้าเพจ roberta-large-mnliอีกครั้ง" + }, + { + text: "การแยกแยะข้อความ", + explain: "โมเดลนี้แยกแยะว่าประโยคสองประโยคนั้นเข้าข่ายกรณีใดดังต่อไปนี้ (หักล้างกัน, กลาง, ส่งเสริมกัน) หรือเรียกอีกชื่อหนึ่งว่าการอนุมาน", + correct: true + }, + { + text: "การสร้างข้อความ", + explain: "โปรดดูที่หน้าเพจ roberta-large-mnliอีกครั้ง" + } + ]} +/> + +### 2. โค้ดต่อไปนี้ให้ผลลัพธ์ว่าอย่างไร? + +```py +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +sentiment-analysis" + }, + { + text: "ได้ผลออกมาเป็นข้อความที่ทำให้ประโยคสมบูรณ์", + explain: "ข้อนี้ผิด — ผลลัพธ์นี้ได้จาก pipeline text-generation", + }, + { + text: "ได้ผลออกมาระบุว่าคำใดเป็นบุคคล, องค์กร, หรือสถานที่", + explain: "หากตั้งค่าว่า grouped_entities=True จะสามารถรวมคำหลายคำที่ระบุสิ่งเดียวกันไว้ได้ เช่น \"Hugging Face\" ประกอบด้วยคำสองคำ แต่ระบุถึงสิ่งสิ่งเดียว", + correct: true + } + ]} +/> + +### 3. เราควรแทนค่า ... ในโค้ดด้านล่างว่าอะไร? + +```py +from transformers import pipeline + +filler = pipeline("fill-mask", model="bert-base-cased") +result = filler("...") +``` + + has been waiting for you.", + explain: "ข้อนี้ผิด โปรดดูรายละเอียดของโมเดล bert-base-cased แล้วลองตรวจสอบว่าทำผิดตรงไหนไป" + }, + { + text: "This [MASK] has been waiting for you.", + explain: "ถูกต้อง! โมเดลนี้ เว้นช่องว่างด้วยโทเคน [MASK]", + correct: true + }, + { + text: "This man has been waiting for you.", + explain: "ข้อนี้ผิด pipeline ระบุว่าทำงาน `fill-mask` ซึ่งก็คือการเติมคำในช่องว่าง แต่ไม่มีโทเคนใดระบุช่องว่างในประโยคเลย" + } + ]} +/> + +### 4. ทำไมโค้ดด้านล่างรันไม่ออก? + +```py +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +result = classifier("This is a course about the Transformers library") +``` + +candidate_labels=[...] เข้าไปด้วย", + correct: true + }, + { + text: "pipeline นี้ต้องการประโยค input มากกว่าหนึ่งประโยค", + explain: "ข้อนี้ผิด ถึงแม้ว่าความจริงแล้วจะสามารถใส่ประโยคหลายประโยคเป็น list เข้าไปเป็น input เพื่อรันได้(เหมือน pipeline อื่น ๆ)" + }, + { + text: "library 🤗 Transformers พังแบบงง ๆ เหมือนทุกทีน่ะแหละ", + explain: "ขออนุญาตงดแสดงความคิดเห็นกับคนเลือกข้อนี้นะ" + }, + { + text: "pipeline นี้ต้องการประโยค input ที่ยาวกว่านี้ ประโยคนี้สั้นเกินไป", + explain: "ข้อนี้ผิด และหากใส่ประโยคที่ยาวเกินไปใน pipeline นี้ก็จะโดนตัดให้สั้นลงอยู่ดี" + } + ]} +/> + +### 5. "transfer learning" (การเรียนรู้แบบส่งต่อ) หมายความว่าอย่างไร? + + + +### 6. ประโยคต่อไปนี้ถูกหรือผิด? โมเดลบริบทภาษาเป็นการเทรนล่วงหน้าที่ไม่ต้องการ label ในการเทรน + + +self-supervise นั่นคือ label จะถูกสร้างขึ้นอัตโนมัติจาก input เอง (เช่นการทำนายคำต่อไปในข้อความ หรือเติมคำในช่องว่าง)", + correct: true + }, + { + text: "ผิด", + explain: "คำตอบนี้ผิด" + } + ]} +/> + +### 7. โปรดเลือกประโยคที่อธิบายคำว่า "model", "architecture" และ "weight" ได้อย่างถูกต้อง" + + + + +### 8. โมเดลใดต่อไปนี้เหมาะสมในการใช้สำหรับงานสร้างคำที่หายไปในประโยค? + + + +### 9. โมเดลประเภทใดต่อไปนี้เหมาะสำหรับงานในการสรุปความ? + + + +### 10. โมเดลประเภทใดต่อไปนี้เหมาะสำหรับงานในการแยกแยะประเภทประโยคตาม label ที่กำหนดให้? + + + +### 11. อคติของโมเดลสามารถเกิดได้จากข้อใดต่อไปนี้ได้บ้าง? + + diff --git a/chapters/th/chapter1/2.mdx b/chapters/th/chapter1/2.mdx new file mode 100644 index 000000000..ccfe0c296 --- /dev/null +++ b/chapters/th/chapter1/2.mdx @@ -0,0 +1,22 @@ +# การประมวลผลภาษาธรรมชาติ(หรือเรียกว่า NLP) + +ก่อนจะเข้าเนื้อหาส่วนโมเดล Transformer เรามาดูกันในภาพรวมก่อนว่า NLP คืออะไร แล้วทำไมเราต้องศึกษาและเรียนรู้มัน + +## NLP คืออะไร? + +NLP เป็นสาขาหนึ่งของวิชาภาษาศาสตร์และวิชาการเรียนรู้ของเครื่องจักร (หรือเรียกว่า machine learning ย่อว่า ML) ที่พยายามจะเข้าใจทุกอย่างที่เกี่ยวข้องกับภาษาที่มนุษย์ใช้สื่อสารกัน เป้าหมายของ NLP ไม่ได้ต้องการแค่จะเข้าใจความหมายของสิ่งที่สื่อสารออกมาเป็นคำ ๆ ไป แต่ต้องการจะเข้าใจบริบทรอบข้างของคำเหล่านั้นด้วย + +งานทางด้าน NLP มีตัวอย่างดังต่อไปนี้: + +- **แยกหมวดหมู่ของประโยคทั้งประโยค**: เข้าใจความรู้สึกผู้เขียนจากข้อความรีวิว, ตรวจสอบว่าอีเมลที่ส่งมาเป็นสแปมหรือไม่, ตรวจสอบว่าประโยคถูกต้องตามหลักไวยากรณ์หรือไม่, หรือเปรียบเทียบความหมายของประโยคสองประโยค +- **แยกหมวดหมู่ของคำในประโยค**: ระบุว่าคำแต่ละคำเป็นองค์ประกอบใดของประโยค (เช่น ประธาน, กริยา, กรรม), หรือระบุว่าคำนี้เป็นชื่อเฉพาะของอะไร (ชื่อคน, ชื่อสถานที่, ชื่อองค์กร) +- **สร้างข้อความ**: เดาคำต่อไปที่จะพิมพ์หลังจากเห็นบางส่วนของข้อความ, เติมคำในช่องที่เว้นว่างไว้ +- **หาคำตอบจากข้อความ**: เมื่อส่งคำถามและข้อความยาว ๆ ที่มีคำตอบอยู่ข้างใน ให้หาคำตอบของคำถามนั้นจากข้อความที่ให้มา +- **สร้างประโยคใหม่จากข้อความที่ให้**: แปลข้อความจากภาษาหนึ่งไปยังอีกภาษาหนึ่ง, สรุปข้อความ + +NLP เองนั้นก็ไม่ได้จำกัดอยู่ที่ภาษาเขียนอย่างเดียว แต่ยังสามารถใช้แก้ปัญหาทางด้านภาษาพูด(หรือเรียกว่า speech recognition) และการมองเห็นของคอมพิวเตอร์(หรือเรียกว่า computer vision)ได้ด้วย + + +## ทำไม NLP ถึงท้าทาย? + +อย่างที่รู้กันว่าคอมพิวเตอร์ไม่ได้ประมวลผลข้อมูลแบบเดียวกับที่มนุษย์ทำ ตัวอย่างเช่น เมื่อเราอ่านข้อความว่า "หิวจัง" เราก็สามารถเข้าใจความหมายได้ไม่ยาก และในทางเดียวกัน เมื่อเราอ่านสองประโยคนี้ "หิวจัง" และ "เสียใจจัง" เราก็สามารถเข้าใจความเหมือนของมันได้อย่างง่ายดาย แต่สำหรับ ML แล้ว การจะเข้าใจสองประโยคนี้ค่อนข้างยากเลยทีเดียว ข้อความที่ใส่ไปนั้นจะถูกนำไปประมวลผลเพื่อให้คอมพิวเตอร์สามารถเรียนรู้ได้ และเนื่องจากภาษาเองก็เป็นเรื่องซับซ้อน เราจึงต้องคิดอย่างรอบคอบว่าจะนำไปประมวลผลอย่างไร มีงานวิจัยมากมายพยายามแสดงวิธีประมวลผลข้อความเหล่านี้ให้ออกมาในรูปแบบต่าง ๆ ซึ่งในบทต่อ ๆ ไป เราจะมาดูกันว่าตัวอย่างวิธีการประมวลผลข้อความเหล่านี้ทำงานกันอย่างไร \ No newline at end of file diff --git a/chapters/th/chapter1/3.mdx b/chapters/th/chapter1/3.mdx new file mode 100644 index 000000000..9ab990db5 --- /dev/null +++ b/chapters/th/chapter1/3.mdx @@ -0,0 +1,329 @@ +# Transformers ชื่อนี้มีดียังไง? + + + +ในส่วนนี้ เราจะมาดูกันว่า Transformer นั้นทำอะไรได้บ้าง และมาใช้งานเครื่องมือชิ้นแรกจาก library 🤗 Transformers ซึ่งก็คือฟังก์ชัน `pipeline()` + + +👀 เห็นปุ่ม Open in Colab ทางด้านบนขวานั่นมั้ย? ลองคลิกเปิดดู Google Colab notebook ได้ ด้านในจะมีตัวอย่างโค้ดที่เกี่ยวข้องทั้งหมดในหน้านี้ โดยปุ่นแบบนี้จะมีในทุก ๆ หน้าที่มีโค้ดตัวอย่าง + +แต่หากคุณต้องการรันโค้ดตัวอย่างบนเครื่องของตนเอง เราแนะนำให้เปิดดู ติดตั้งโปรแกรม + + +## Transformers, Transformers เต็มไปหมดเลย! + +โมเดล Transformer นั้นสามารถนำไปใช้แก้ปัญหา NLP ที่ได้กล่าวไปก่อนหน้านี้ได้ทั้งหมดเลย ด้านล่างก็เป็นตัวอย่างหน่วยงานที่ใช้ Hugging Face และโมเดล Transformer รวมถึงยังแบ่งปันโมเดลที่สร้างกลับไปยังชุมชนด้วยเช่นกัน + + +Companies using Hugging Face + +[library 🤗 Transformers](https://github.com/huggingface/transformers) มีฟังก์ชันในการสร้างและใช้งานโมเดลที่มีคนแบ่งมาให้ใช้ ใน [Model Hub](https://huggingface.co/models) มีโมเดลที่ผ่านการเทรนมาแล้ว (หรือเรียกว่า pretrained model) มากกว่าหนึ่งพันโมเดลที่ใคร ๆ ก็สามารถดาวน์โหลดไปใช้งานได้ รวมถึงคุณเองก็เป็นส่วนหนึ่งในการแบ่งปันนี้ได้เช่นกัน + + +⚠️ Hugging Face Hub ไม่ได้จำกัดอยู่แค่เพียงโมเดล Transformer เท่านั้น ใคร ๆ ก็สามารถแบ่งปันโมเดลหรือชุดข้อมูลอะไรก็ได้ตามต้องการ มา สร้างบัญชี huggingface.co เพื่อใช้งานฟีเจอร์เหล่านี้ด้วยกันนะ! + + +ก่อนจะเจาะลึกเข้าไปถึงเบื้องหลังการทำงานของโมเดล Transformer มาดูตัวอย่างกันว่าโมเดล Transformer นั้นสามารถแก้ปัญหา NLP ได้อย่างไรบ้าง + +## การใช้งานคำสั่ง pipelines + + + +คำสั่งพื้นฐานที่สุดของ library 🤗 Transformers คือฟังก์ชัน `pipeline()` โดยฟังก์ชันนี้จะเชื่อมต่อโมเดลกับขั้นตอนอื่น ๆ ที่สำคัญ เช่น กระบวนการจัดการข้อมูลก่อนการประมวลผล(หรือเรียกว่า preprocessing) และการจัดการข้อมูลหลังการประมวลผล(หรือเรียกว่า postprocessing) ทำให้เราสามารถใส่ข้อความเป็นอินพุตและได้คำตอบอันชาญฉลาดได้โดยตรง: + +```python +from transformers import pipeline + +classifier = pipeline("sentiment-analysis") +classifier("I've been waiting for a HuggingFace course my whole life.") +``` + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}] +``` + +หรือใส่ไปหลาย ๆ ประโยคก็ได้! + +```python +classifier( + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!"] +) +``` + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}, + {'label': 'NEGATIVE', 'score': 0.9994558095932007}] +``` + +โดยปกติแล้ว pipeline จะเลือก pretrained model ที่ fine-tune มาเพื่อการเข้าใจความรู้สึกของผู้เขียนในภาษาอังกฤษ ซึ่งในการใช้งานคำสั่ง `classifier` ครั้งแรก คำสั่งนี้จะไปดาวน์โหลดโมเดลมาจาก Hub และเก็บไว้ใน cache เมื่อคุณใช้งานคำสั่งนี้ซ้ำอีกครั้งจะเป็นการเรียกใช้โมเดลที่เก็บไว้ใน cache แทน จะได้ไม่ต้องดาวน์โหลดใหม่บ่อย ๆ + +เมื่อคุณส่งข้อความเข้าไปยัง pipeline ในเบื้องหลังจะมีกระบวนการต่อไปนี้เกิดขึ้น: + +1. ข้อความจะถูกแปลงเป็นอินพุตในรูปแบบที่โมเดลสามารถเข้าใจได้ +2. ส่งอินพุตเข้าไปคำนวณในโมเดล +3. นำผลการทำนายจากโมเดลมาประมวลผลต่อเพื่อให้มนุษย์เข้าใจได้ง่าย + +ในปัจจุบันงานบางอย่างสามารถใช้งานจาก[คำสั่ง pipeline](https://huggingface.co/transformers/main_classes/pipelines.html) ได้โดยตรง ได้แก่: + +- `feature-extraction` (เปลี่ยนข้อความเป็น vector) +- `fill-mask` (เติมคำในช่องว่าง) +- `ner` (named entity recognition; การระบุชื่อเฉพาะ) +- `question-answering` (ถาม-ตอบ) +- `sentiment-analysis` (รับรู้ความรู้สึกของผู้เขียน) +- `summarization` (สรุปความ) +- `text-generation` (สร้างข้อความ) +- `translation` (แปลภาษา) +- `zero-shot-classification` (แยกแยะหมวดหมู่โดยไม่ต้องสอน) + +มาดูกันว่าแต่อย่างใช้งานกันยังไง! + +## แยกแยะหมวดหมู่โดยไม่ต้องสอน + +มาเริ่มต้นด้วยงานที่ค่อนข้างท้าทายกันซักหน่อย งานนี้ต้องการแยกแยะหมวดหมู่(หรือเรียกว่า classify) ข้อความที่ไม่เคยถูกระบุหมวดหมู่(หรือเรียกว่า label)มาก่อน เนื่องจากปกติแล้วงานในการ classify ข้อความนั้น โมเดลต้องการเข้าใจการจับคู่ระหว่างข้อความและ label แต่ในความเป็นจริงแล้ว การเอาข้อความมาให้คนคอยกำหนด label ก่อนส่งไปยังโมเดล เป็นกระบวนการที่กินเวลามากและต้องการคนที่มีความรู้เฉพาะแขนง สำหรับกรณีนี้ pipeline `zero-shot-classification` นั้นนับว่าชาญฉลาดมาก ตัว pipeline สามารถระบุกลุ่ม label ที่จะใช้ในการ classify นี้ ดังนั้นเราจึงไม่จำเป็นต้องอาศัย label จาก pretrained model คุณเห็นมาแล้วว่าโมเดลสามารถ classify ประโยคได้ว่าเป็นประโยคที่พูดแง่บวกหรือแง่ลบโดยใช้ label ทั้งสองนี้ โดย `pipeline` ฉลาดกว่านั้นเพราะสามารถ classify ข้อความโดยใช้ label อื่น ๆ ที่เราต้องการได้ + +```python +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +classifier( + "This is a course about the Transformers library", + candidate_labels=["education", "politics", "business"], +) +``` + +```python out +{'sequence': 'This is a course about the Transformers library', + 'labels': ['education', 'business', 'politics'], + 'scores': [0.8445963859558105, 0.111976258456707, 0.043427448719739914]} +``` + +pipeline นี้เรียกว่า _zero-shot_ เพราะว่าเราไม่ต้อง fine-tune โมเดลด้วยข้อมูลของเราก่อนจะนำไปใช้ ตัวโมเดลสามารถระบุค่าความน่าจะเป็นตาม list ของ label ที่เราต้องการได้เลย! + + + +✏️ **ลองเลย!** ทดลองโดยใช้ข้อความอะไรก็ได้ของเราเองแล้วดูว่าโมเดลส่งค่าอะไรคืนให้เราบ้าง + + + + +## การสร้างข้อความ + +ทีนี้ เรามาดูกันว่า pipeline จะใช้สร้างข้อความได้ยังไง โดยหลักการแล้ว เพียงคุณส่งข้อความสั้น ๆ ไปให้โมเดล โมเดลจะเดาข้อความที่เหลือทั้งหมดให้อัตโนมัติ วิธีการนี้ก็คล้าย ๆ กับการที่โทรศัพท์มือถือเดาข้อความที่เรากำลังจะพิมพ์ สิ่งที่ควรทราบเพิ่มเติมคือ การสร้างข้อความนั้นมีการสุ่มรวมอยู่ในกระบวนการด้วย ดังนั้นหากเราทดสอบซ้ำ ๆ แล้วได้ผลลัพธ์ไม่เหมือนกันก็นับเป็นเรื่องปกติ ตัวอย่างการสร้างข้อความสามารถดูได้ดังตัวอย่างด้านล่าง + +```python +from transformers import pipeline + +generator = pipeline("text-generation") +generator("In this course, we will teach you how to") +``` + +```python out +[{'generated_text': 'In this course, we will teach you how to understand and use ' + 'data flow and data interchange when handling user data. We ' + 'will be working with one or more of the most commonly used ' + 'data flows — data flows of various types, as seen by the ' + 'HTTP'}] +``` + +คุณสามารถควบคุมจำนวนข้อความที่สร้างขึ้นได้โดยกำหนดค่าที่ argument `num_return_sequences` และกำหนดความยาวของข้อความที่สร้างขึ้นมาด้วย argument `max_length` + + + + +✏️ **ลองเลย!** ปรับค่า argument `num_return_sequences` และ `max_length` ให้สร้างข้อความขึ้นมาสองประโยค ประโยคละ 15 คำ + + + + +## การใช้งานโมเดลใด ๆ จาก Hub ใน pipeline + +ตัวอย่างที่ผ่านมาเป็นการใช้โมเดลเริ่มต้นสำหรับงานใด ๆ แต่ในบางครั้งเราอาจต้องเลือกโมเดลที่เฉพาะเจาะจงสำหรับแต่ละงานจาก Hub ใน pipeline ตัวอย่างเช่นการสร้างข้อความ ลองเข้าไปที่ [Model Hub](https://huggingface.co/models) และเลือกหมวดหมู่ทางด้านซ้ายเพื่อดูว่ามีโมเดลใดให้ใช้บ้างสำหรับงานแต่ละอย่าง คุณจะได้เพจที่หน้าตา [ดังนี้](https://huggingface.co/models?pipeline_tag=text-generation) + +ลองใช้โมเดล [`distilgpt2`](https://huggingface.co/distilgpt2) โดยสามารถทำตามได้ดังนี้: + +```python +from transformers import pipeline + +generator = pipeline("text-generation", model="distilgpt2") +generator( + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, +) +``` + +```python out +[{'generated_text': 'In this course, we will teach you how to manipulate the world and ' + 'move your mental and physical capabilities to your advantage.'}, + {'generated_text': 'In this course, we will teach you how to become an expert and ' + 'practice realtime, and with a hands on experience on both real ' + 'time and real'}] +``` + +คุณสามารถปรับค่าการค้นหาโมเดลได้ด้วยการคลิกที่หมวดหมู่ภาษา และเลือกโมเดลที่สามารถสร้างข้อความในภาษาอื่นได้ ซึ่งใน Model Hub นี้จะมีโมเดลที่รองรับหลายภาษาเช่นกัน + +เมื่อคุณเลือกโมเดลโดยการคลิกที่ชื่อโมเดล คุณจะเห็นว่าจะมีแถบ widget เล็ก ๆ ขึ้นมาให้คุณลองก่อนแบบออนไลน์ ด้วยวิธีการนี้ คุณสามารถทดสอบความสามารถของโมเดลได้ก่อนจะดาวน์โหลดไปใช้ + + + +✏️ **ลองเลย!** ใช้ตัวกรองหาโมเดลสร้างข้อความสำหรับภาษาอื่น แนะนำให้ลองกับ widget บน pipeline ดูก่อนเลย + + + +### The Inference API + +โมเดลทั้งหมดสามารถทดสอบได้โดยตรงผ่านเว็บบราวเซอร์โดยใช้ API ประเมินผล(หรือเรียกว่า Inference API) ซึ่งสามารถใช้ได้ผ่าน[เว็บไซต์ Hugging Face](https://huggingface.co/) คุณสามารถเล่นกับโมเดลต่าง ๆ ได้โดยตรงที่หน้านี้โดยกำหนดข้อความอินพุตได้ตามต้องการและดูว่าโมเดลประมวลผลข้อมูลเหล่านั้นยังไง + +Inference API ที่อยู่เบื้องหลัง widget นี้สามารถนำไปใช้งานในเชิงพาณิชย์ได้แบบง่าย ๆ เลยหากคุณต้องการนำไปใช้ในงานของคุณ โปรดดูรายละเอียดเพิ่มเติมได้ที่[หน้ารายการราคา](https://huggingface.co/pricing) + +## เติมคำในช่องว่าง + +pipeline ต่อไปก็หรือการเติมคำในช่องว่าง หรือ `fill-mask` โดยพื้นฐานแล้วงานนี้ก็คือการเติมคำในช่องว่างที่เว้นไว้ระหว่างข้อความที่กำหนดให้ เช่น + +```python +from transformers import pipeline + +unmasker = pipeline("fill-mask") +unmasker("This course will teach you all about models.", top_k=2) +``` + +```python out +[{'sequence': 'This course will teach you all about mathematical models.', + 'score': 0.19619831442832947, + 'token': 30412, + 'token_str': ' mathematical'}, + {'sequence': 'This course will teach you all about computational models.', + 'score': 0.04052725434303284, + 'token': 38163, + 'token_str': ' computational'}] +``` + +argument `top_k` ควบคุมจำนวนข้อความที่ต้องการแสดง โดยโมเดลจะเติมคำลงไปที่คำพิเศษที่เขียนว่า `` ซึ่งหมายถึง *คำที่ละไว้* ทั้งนี้ โมเดลเติมคำในช่องว่างโมเดลอื่นอาจใช้คำที่ละไว้นี้เป็นอย่างอื่น ดังนั้น โปรดรับรู้ไว้เสมอว่า หากจะใช้โมเดลใดให้ศึกษาให้แน่ชัดว่าโมเดลนั้นใช้คำที่ละไว้ว่าอะไร วิธีการหนึ่งที่ทำได้คือให้ตรวจสอบคำที่ละไว้ที่ใช้ใน widget นี้ + + + +✏️ **ลองเลย!** หาโมเดล `bert-base-cased` ใน Hub และตรวจสอบคำที่ละไว้ที่ใช้ใน widget ของ Inference API โมเดลนี้ทำนายอะไรออกมาหากเราใส่ประโยคที่ใส่เข้าไปในตัวอย่าง `pipeline` ด้านบน + + + + +## การระบุชื่อเฉพาะ + +การระบุชื่อเฉพาะ(หรือเรียกว่า Named entity recognition; NER) คืองานที่โมเดลจะต้องหาว่าส่วนใดในประโยคหมายถึงชื่อเฉพาะของ คน สัตว์ สิ่งของ สถานที่ หรือองค์กรใด ๆ มาดูตัวอย่างกัน: + +```python +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +```python out +[{'entity_group': 'PER', 'score': 0.99816, 'word': 'Sylvain', 'start': 11, 'end': 18}, + {'entity_group': 'ORG', 'score': 0.97960, 'word': 'Hugging Face', 'start': 33, 'end': 45}, + {'entity_group': 'LOC', 'score': 0.99321, 'word': 'Brooklyn', 'start': 49, 'end': 57} +] +``` + +ในส่วนนี้ โมเดลสามารถระบุได้ว่า Sylvian เป็นชื่อคน (PER) Hugging Face เป็นชื่อหน่วยงาน (ORG), และ Brooklyn เป็นชื่อสถานที่ (LOC) + +เราเพิ่มตัวเลือก `grouped_entities=True` ตอนสร้างฟังก์ชัน pipeline เพื่อระบุให้ pipeline จับกลุ่มคำที่เป็นการระบุชื่อเฉพาะของสิ่ง ๆ เดียว ในที่นี้ โมเดลจับกลุ่มคำว่า "Hugging" และ "Face" เข้าไปเป็นชื่อองค์กรองค์กรเดียว แม้ว่าจะเป็นการรวมคำหลายคำเข้าด้วยกันก็ตาม ซึ่งจริง ๆ แล้ว ในบทต่อไปเราจะเห็นว่าการประมวลผลนั้นจะแบ่งคำบางคำออกมาเป็นส่วนที่แยกย่อยลงไปอีก ตัวอย่างเช่น คำว่า `Sylvian` ถูกแบ่งออกเป็น 4 ส่วน ได้แก่ `S`, `##yl`, `##va`, และ `##in` และระหว่างการ post-processing ตัว pipeline ก็จะนำแต่ละส่วนนี้มาประกอบเข้าด้วยกัน + + + +✏️ **ลองเลย!** หาโมเดลใน Model Hub ที่ทำงานเกี่ยวกับการระบุชื่อเฉพาะ(หรือเรียกว่า part-of-speech tagging ย่อว่า POS)ในภาษาอังกฤษ รู้มั้ยว่าโมเดลนี้ทำนายอะไรในตัวอย่างประโยคข้างต้น? + + +## ถาม-ตอบคำถาม + +pipeline การถามตอบคำถาม(หรือเรียกว่า `question-answering`) เป็นการตอบคำถามโดยใช้ข้อมูลจากกบริบทที่ให้มา เช่น: + +```python +from transformers import pipeline + +question_answerer = pipeline("question-answering") +question_answerer( + question="Where do I work?", + context="My name is Sylvain and I work at Hugging Face in Brooklyn", +) +``` + +```python out +{'score': 0.6385916471481323, 'start': 33, 'end': 45, 'answer': 'Hugging Face'} +``` + +โปรดทราบเอาไว้ว่า pipeline นี้ทำงานโดยการเอาข้อมูลจากบริบทที่ได้มาไปประมวลผล แต่จะไม่ได้สร้างคำตอบออกมา + +## การสรุปความ + +การสรุปความเป็นงานในการลดข้อความลงมาให้เป็นข้อความที่สั้นลงโดยเก็บรักษาใจความสำคัญให้ได้มากที่สุด ตัวอย่างเช่น: + +```python +from transformers import pipeline + +summarizer = pipeline("summarization") +summarizer( + """ + America has changed dramatically during recent years. Not only has the number of + graduates in traditional engineering disciplines such as mechanical, civil, + electrical, chemical, and aeronautical engineering declined, but in most of + the premier American universities engineering curricula now concentrate on + and encourage largely the study of engineering science. As a result, there + are declining offerings in engineering subjects dealing with infrastructure, + the environment, and related issues, and greater concentration on high + technology subjects, largely supporting increasingly complex scientific + developments. While the latter is important, it should not be at the expense + of more traditional engineering. + + Rapidly developing economies such as China and India, as well as other + industrial countries in Europe and Asia, continue to encourage and advance + the teaching of engineering. Both China and India, respectively, graduate + six and eight times as many traditional engineers as does the United States. + Other industrial countries at minimum maintain their output, while America + suffers an increasingly serious decline in the number of engineering graduates + and a lack of well-educated engineers. +""" +) +``` + +```python out +[{'summary_text': ' America has changed dramatically during recent years . The ' + 'number of engineering graduates in the U.S. has declined in ' + 'traditional engineering disciplines such as mechanical, civil ' + ', electrical, chemical, and aeronautical engineering . Rapidly ' + 'developing economies such as China and India, as well as other ' + 'industrial countries in Europe and Asia, continue to encourage ' + 'and advance engineering .'}] +``` + +คุณสามารถระบุ `max_length` หรือ `min_length` เพื่อให้ได้ผลลัพธ์ที่ต้องการได้เหมือนการสร้างข้อความ + +## การแปลภาษา + +สำหรับการแปลภาษาแล้ว คุณสามารถใช้โมเดลเริ่มต้นได้เลยหากคุณกำหนดคู่ภาษาตั้งต้นและภาษาปลายทางที่ชื่องาน(เช่น `"translation_en_to_fr"`) แต่มีวิธีที่ง่ายกว่าก็คือให้เลือกโมเดลที่ต้องการจาก [Model Hub](https://huggingface.co/models) ตัวอย่างด้านล่างแสดงการแปลภาษาจากภาษาฝรั่งเศสไปยังภาษาอังกฤษ: + +```python +from transformers import pipeline + +translator = pipeline("translation", model="Helsinki-NLP/opus-mt-fr-en") +translator("Ce cours est produit par Hugging Face.") +``` + +```python out +[{'translation_text': 'This course is produced by Hugging Face.'}] +``` + +คุณสามารถกำหนด argument `max_length` หรือ `min_length` เพื่อระบุผลลัพธ์ที่ต้องการเหมือนการสร้างข้อความและการสรุปความ + + + +✏️ **ลองเลย!** ลองค้นหาโมเดลแปลภาษาในภาษาอื่น ๆ และทดลองแปลภาษาจากข้อความด้านบนไปยังภาษาอื่น ๆ + + + +คำสั่ง pipeline ที่แสดงด้านบนเป็นเพียงตัวอย่างเบื้องต้นเท่านั้น การใช้งานของคำสั่งนี้ค่อนข้างเฉพาะเจาะจงและไม่สามารถแก้ไขอะไรเบื้องหลังได้มากนัก ในบทหลัง ๆ คุณจะได้เรียนรู้หลักการทำงานเบื้องหลังของฟังก์ชัน `pipeline()` และวิธีการปรับแต่งการทำงาน diff --git a/chapters/th/chapter1/4.mdx b/chapters/th/chapter1/4.mdx new file mode 100644 index 000000000..dae9bdecb --- /dev/null +++ b/chapters/th/chapter1/4.mdx @@ -0,0 +1,181 @@ +# Transformer ทำงานยังไง? + +ในส่วนนี้เราจะมาดูกันว่าสถาปัตยกรรมของโมเดล Transformer ทำงานกันยังไง + + +## ประวัติของ Transformer เบื้องต้น + +รูปด้านล่างแสดงรายชื่อของโมเดล Transformer ตามแต่ละช่วงเวลา: + +
+A brief chronology of Transformers models. + +
+ +[สถาปัตยกรรม Transformer](https://arxiv.org/abs/1706.03762) ถูกสร้างขึ้นเมื่อปี 2017 เป้าหมายในการสร้างโมเดลก็เพื่องานในการแปลภาษา หลังจากนั้นก็มีโมเดลที่ได้รับแรงบันดาลใจต่อ ๆ มา เช่น + +- **มิถุนายน 2018**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), โมเดล Transformer ตัวแรก ที่มาพร้อมกับพร้อมกับโมเดลที่เทรนมาแล้ว ใช้ในการ fine-tune งานทางด้าน NLP ได้หลายงานและได้ผลลัพธ์ที่เทียบเท่างานวิจัยล่าสุด + +- **ตุลาคม 2018**: [BERT](https://arxiv.org/abs/1810.04805), โมเดลขนาดใหญ่อีกตัวหนึ่งที่ผ่านการเทรนมาแล้ว ซึ่งออกแบบมาสำหรับงานในการสรุปความ (อ่านต่อได้ในบทถัดไป) + +- **กุมภาพันธ์ 2019**: [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), โมเดล GPT รุ่นพัฒนาที่ดีขึ้น(และใหญ่ขึ้นด้วย) แต่ว่าไม่ได้เปิดให้ใช้สาธารณะในทันทีเนื่องจากอาจมีการนำไปใช้ในด้านไม่ดี + +- **ตุลาคม 2019**: [DistilBERT](https://arxiv.org/abs/1910.01108), โมเดล BERT ขนาดย่อ ซึ่งเร็วกว่า 60% และใช้หน่วยความจำลดลง 40% แต่ยังคงประสิทธิภาพไว้ที่ 97% ของโมเดล BERT ปกติ + +- **ตุลาคม 2019**: [BART](https://arxiv.org/abs/1910.13461) และ [T5](https://arxiv.org/abs/1910.10683), โมเดลที่ผ่านการเทรนมาแล้วทั้งสองตัวที่ใช้สถาปัตยกรรมเดียวกันกับโมเดล Transformer ต้นฉบับ (โมเดลแรกที่ทำแบบนี้) + +- **พฤษภาคม 2020**, [GPT-3](https://arxiv.org/abs/2005.14165), โมเดล GPT-2 ที่ใหญ่ขึ้นไปอีกซึ่งสามารถทำงานได้หลายอย่างโดยไม่ต้องการการ fine-tune (หรือเรียกว่า _zero-shot learning_) + + +ที่จริงแล้วยังมีโมเดลอื่น ๆ อีกมากนอกเหนือจากรายชื่อที่กล่าวไป ในที่นี้ต้องการที่จะเน้นย้ำอีกครั้งในส่วนของประเภทของโมเดล Transformer ซึ่งสามารถแบ่งได้ดังนี้: + +- กลุ่ม GPT (หรือเรียกว่า โมเดล Transformer แบบ _auto-regressive_) +- กลุ่ม BERT (หรือเรียกว่า โมเดล Transformer แบบ _auto-encoding_) +- กลุ่ม BART/T5 (หรือเรียกว่า โมเดล Transformer แบบ _sequence-to-sequence_) + +โดยเราจะกล่าวถึงแต่ละกลุ่มในรายละเอียดกันภายหลัง + + +## Transformers ก็คือโมเดลบริบททางภาษาแบบหนึ่งนั่นแหละ + +โมเดล Transformer ที่กล่าวไปด้านบนทั้งหมด (GPT, BERT, BART, T5 เป็นต้น) ถูกเทรนให้เป็น*โมเดลบริบททางภาษา* (หรือเรียกว่า language model) นั่นคือโมเดลเหล่านี้ถูกเทรนผ่านข้อความดิบขนาดใหญ่เรียนรู้ด้วยตนเอง (หรือเรียกว่า self-supervised) การ self-supervised นั้นคือการเทรนแบบหนึ่งซึ่งเป้าหมายในการเทรนจะถูกคำนวณออกมาอัตโนมัติจาก input ที่ใส่เข้าไป ดังนั้นการเทรนแบบนี้จึงไม่จำเป็นต้อง label ข้อมูลเลย! + +โมเดลประเภทนี้จะเข้าในภาษาที่ถูกเทรนมาในเชิงสถิติ แต่ในการใช้งานจริงอาจไม่มีประโยชน์เท่าไหร่ ดังนั้นโมเดลที่ผ่านการเทรนแบบนี้มาจึงถูกนำไปใช้ในกระบวนการ *transfer learning* ซึ่งก็คือโมเดลนี้จะถูกนำไป fine-tune ต่อในงานอื่นที่มี label จากมนุษย์ + +ตัวอย่างงานดังกล่าวได้แก่ การเดาคำต่อไปหลังจากอ่านคำในประโยค *n* คำ สิ่งนี้เรียกว่า *causal language model* เนื่องจาก output ขึ้นอยู่กับ input ในอดีตและปัจจุบันเท่านั้น + + +
+Example of causal language modeling in which the next word from a sentence is predicted. + +
+ +อีกตัวอย่างหนึ่งได้แก่ การเติมคำในช่องว่าง (หรือเรียกว่า *masked language modeling*) เป็นการเดาคำที่เว้นว่างไว้ในประโยค โดยในที่นี้จะใช้คำว่า `[MASK]` แทนการเว้นว่าง + +
+Example of masked language modeling in which a masked word from a sentence is predicted. + +
+ +## Transformers มัน ใหญ่ มาก + +ถ้าไม่นับพวกโมเดลแปลก ๆ อย่าง DistilBERT การจะได้มาซึ่งผลลัพธ์ที่ดีขึ้นก็ต้องเพิ่มขนาดโมเดลและขนาดข้อมูลที่นำมาเทรนโมเดล + + +
+Number of parameters of recent Transformers models +
+ +ซึ่งในการเทรนโมเดลโดยเฉพาะโมเดลขนาดใหญ่ต้องการข้อมูลในการเทรนจำนวนมาก ดังนั้นการเทรนแต่ละครั้งจึงกินทั้งทรัพยากรเวลาและทรัพยากรในการคำนวณมากขึ้นไปอีก ซึ่งหมายถึงผลกระทบต่อสิ่งแวดล้อมดังแสดงในภาพด้านล่าง + + +
+The carbon footprint of a large language model. + +
+ + + +รูปภาพทำขึ้นเพื่อจะสื่อสารไปยังทีมพัฒนาต่าง ๆ ให้เห็นถึงผลกระทบต่อสิ่งแวดล้อมในการเทรนโมเดลขนาดใหญ่(มาก ๆ) และหากต้องการจะทดลองเพื่อหา hyperparameter ที่ดีสุด ผลกระทบย่อมเพิ่มขึ้นอีกเป็นเงาตามตัว + +ลองนึกสภาพว่าหากแต่ละทีมวิจัย, กลุ่มการศึกษา, หรือบริษัทเอกชนใด ๆ ต้องการเทรนโมเดลซักโมเดลหนึ่ง โดยเริ่มต้นมาจากศูนย์ทุกครั้ง จะก่อให้เกิดค่าใช้จ่ายที่ไม่จำเป็นอย่างใหญ่หลวงมาก + +เพราะฉะนั้นการแบ่งปันโมเดลที่เทรนไว้แล้วจึงเป็นเรื่องสำคัญอย่างมาก การแบ่งปัน weight ของโมเดลและสร้างสิ่งต่าง ๆ ผ่าน weight ที่มีคนทำมาแล้วจะช่วยลดค่าใช้จ่ายและลดค่า carbon footprint โดยทั่วกัน + + +## Transfer Learning + + + +*Pretraining* เป็นการเทรนโมเดลจากศูนย์ ซึ่งก็คือ weight ในโมเดลเริ่มต้นจากค่าสุ่ม และเริ่มเทรนโดยไม่มีความรู้อะไรเลย + +
+The pretraining of a language model is costly in both time and money. + +
+ +โดยมากแล้วการ pretrain จะใช้ข้อมูลขนาดใหญ่มาก ๆ ดังนั้นจึงต้องการข้อมูลภาษาขนาดใหญ่และใช้เวลาเทรนหลายสัปดาห์ + +*Fine-tuning* เป็นการเทรนโมเดล**หลังจาก**การ pretrain ซึ่งก็คือ นำ language model ที่ผ่านการ pretrain แล้ว มาเทรนเพิ่มเติมด้วยชุดข้อมูลที่เฉพาะเจาะจงในงานที่ต้องการ ว่าแต่ ทำไมไม่เทรนโมเดลสำหรับงานสุดท้ายไปทีเดียวเลยล่ะ? มันมีเหตุผลอยู่ ดังนี้: + +* โมเดลที่ผ่านการ pretrain มาแล้วมีความเข้าใจความเหมือนบางอย่างในชุดข้อมูลที่นำมา fine-tune ดังนั้น การ fine-tune จึงสามารถนำความรู้ที่ได้จากการ pretrain มาใช้ประโยชน์ได้ทันที (เช่น โมเดลที่ pretrain มาแล้วทางด้าน NLP พอจะมีเข้าใจเบื้องต้นเกี่ยวกับภาษาที่เรากำลังใช้อยู่ในเชิงสถิติ) +* เนื่องจากโมเดลที่ผ่านการ pretrain มาแล้วนั้นถูกเทรนด้วยข้อมูลขนาดใหญ่มาก การใช้ข้อมูลเพียงเล็กน้อยในการ fine-tune ก็สามารถให้ผลเป็นที่พึงพอใจได้ +* และด้วยเหตุผลเดียวกันนี้ เวลาและทรัพยากรที่ใช้เพื่อให้ได้ผลลัพธ์ที่ดีก็น้อยกว่าเช่นกัน + +ตัวอย่างเช่น เราสามารถใช้โมเดล pretrain ในภาษาอังกฤษและ fine-tune ด้วยข้อมูลภาษาจากใน arXiv (แหล่งข้อมูลค้นคว้าวิจัยทางด้านวิทยาศาสตร์) ทำให้ได้โมเดลที่เก่งทางด้านวิทยาศาสตร์และการวิจัย การ fine-tune นี้ต้องการข้อมูลเพียงเล็กน้อยเท่านั้น ความรู้ความเข้าใจทางภาษาของโมเดลถูกส่งต่อมาจากโมเดล pretrain ดังนั้นกระบวนการนี้จึงเรียกว่า *transfer learning* + +
+The fine-tuning of a language model is cheaper than pretraining in both time and money. + +
+ +ด้วยเหตุนี้การ fine-tune จึงใช้เวลา ข้อมูล ค่าใช้จ่าย และผลกระทบต่อสิ่งแวดล้อมน้อยกว่า รวมถึงว่าเราสามารถทดลองรูปแบบการ fine-tune ต่าง ๆ ได้ง่ายกว่าและเร็วกว่าอีกด้วย เนื่องจากการเทรนแบบนี้มีเงื่อนไขต่ำกว่าการเทรนเต็มรูปแบบ + +กระบวนการดังกล่าวนี้ยังให้ผลลัพธ์ที่ดีกว่าการเทรนทุกอย่างจากศูนย์อีกด้วย(เว้นแต่ว่าคุณจะมีข้อมูลเยอะมากจริง ๆ) และนี่ก็เป็นอีกสาเหตุหนึ่งที่คุณควรใช้โมเดล pretrain ที่ใกล้เคียงกับงานที่คุณต้องการทำที่สุดและแค่ fine-tune มันออกมา + +## สถาปัตยกรรมทั่วไป + +ในส่วนนี้เราจะพูดถึงสถาปัตยกรรมทั่วไปของโมเดล Transformer ไม่ต้องกังวลไปหากคุณไม่เข้าใจเนื้อหาบางส่วนตอนนี้ เนื้อหาในส่วนหลังจะอธิบานเรื่องเหล่านี้ในรายละเอียดเพิ่มเติมให้ + + + +## เริ่มต้น + +โมเดล Transformer ประกอบด้วยส่วนใหญ่ ๆ สองส่วน ได้แก่: + +* **ส่วนเข้ารหัส (หรือเรียกว่า Encoder) (ซ้าย)**: encoder รับ input เข้ามาและสร้างตัวแทน input เหล่านั้น โดยตัวแทนเหล่านี้เรียกว่า feature นั่นหมายความว่าโมเดลจะปรับแต่งตัวเองให้เข้าใจ input ได้ในส่วนนี้ +* **ส่วนถอดรหัส (หรือเรียกว่า Decoder) (ขวา)**: decoder สร้าง output ออกมาเป็นข้อมูลลำดับ (หรือเรียกว่า sequence) โดยอาศัย feature และ input อื่น ๆ นั่นหมายถึงโมเดลปรับแต่งตัวเองให้สร้าง output ได้ในส่วนนี้ + +
+Architecture of a Transformers models + +
+ +แต่ละส่วนดังกล่าวสามารถแยกใช้อิสระต่อกันได้ ขึ้นอยู่กับว่าใช้ในงานอะไร เช่น: + +* **โมเดลที่ใช้ encoder อย่างเดียว**: สำหรับงานที่ต้องการให้เข้าใจ input เช่น การแยกหมวดหมู่ประโยคและการระบุชนิดของคำในประโยค +* **โมเดลที่ใช้ decoder อย่างเดียว**: สำหรับงานในการสร้าง output ออกมา เช่น การสร้างข้อความ +* **โมเดลที่ใช้ทั้ง encoder และ decoder** หรือ **โมเดล sequence-to-sequence** สำหรับงานในการสร้าง output ที่ต้องการ input เข้ามาด้วย เช่น การแปลภาษาหรือการสรุปความ + +ซึ่งเราจะเจาะลงไปในรายละเอียดของแต่ละสถาปัตยกรรมกันต่อในภายหลัง + +## Attention layers + +ฟีเจอร์สำคัญของโมเดล Transformer ก็คือโมเดลนี้ถูกสร้างขึ้นมาจาก layer พิเศษที่ชื่อว่า *attention layer* ซึ่งเป็นสาเหตุที่ชื่อของงานวิจัยที่พูดถึงสถาปัตยกรรม Transformer เป็นครั้งแรกถูกตั้งชื่อว่า ["Attention Is All You Need"](https://arxiv.org/abs/1706.03762) (Attention เป็นทุกอย่างให้เธอแล้ว) เราจะมาอธิบายเกี่ยวกับ attention layer ในรายละเอียดกันในช่วงหลังของคอร์สนี้ แต่สำหรับตอนนี้ คุณแค่เข้าใจว่า attention layer ทำหน้าที่บอกให้โมเดลให้ความสนใจ(attend)กับคำที่ตำแหน่งใดเป็นพิเศษจากประโยคส่งให้(รวมถึงบอกให้มองข้ามคำที่ตำแหน่งอื่นด้วย)เมื่อต้องการอธิบายความหมายของคำใด ๆ + +เพื่อให้เห็นภาพมากขึ้น ลองนึกถึงงานในการแปลข้อความจากภาษาอังกฤษเป็นภาษาฝรั่งเศส ตัวอย่างเช่นประโยค "You like this course" โมเดลแปลภาษาต้องสนใจไปที่คำว่า "You" เพื่อให้ได้คำแปลของคำว่า "like" ที่เหมาะสม เนื่องจากในภาษาฝรั่งเศสคำว่า "like" นั้นเปลี่ยนรูปไปตามประธานในประโยค คำอื่น ๆ ที่เหลือในประโยคไม่ได้ส่งผลต่อการแปลคำว่า "like" เลย ในลักษณะเดียวกัน การแปลคำว่า "this" โมเดลต้องสนใจไปที่คำว่า "course" เนื่องจากคำว่า "this" ในภาษาฝรั่งเศสจะแปลออกมาว่าอย่างไรขึ้นอยู่กับว่าคำนั้นเป็นเพศชายหรือหญิง(ในบางภาษาคำแต่ละคำจะมีเพศกำกับไว้แม้ว่าจะไม่ได้เป็นสิ่งมีชีวิตที่มีเพศจริง ๆ ก็ตาม โดยไวยากรณ์ของภาษานั้น ๆ จะผันตามเพศของคำ) และแน่นอนคำอื่น ๆ ในประโยคไม่ได้ส่งผลต่อการแปลว่าคำว่า "this" เลย และเมื่อประโยคซับซ้อนมากยิ่งขึ้น(ส่งผลให้กฎไวยากรณ์ซับซ้อนมากยิ่งขึ้น) โมเดลยิ่งต้องให้ความสนใจไปยังคำที่ไกลออกไปในประโยคเพื่อแปลคำแต่ละคำให้เหมาะสม + +แนวคิดเดียวกันนี้ใช้ได้กับงาน NLP อื่น ๆ นั่นคือ คำแต่ละคำมีความหมายในตัวมันเอง โดยความหมายเหล่านี้ส่งผลมาจากบริบทของคำ ซึ่งก็คือคำอื่นที่อยู่รอบ ๆ คำนั้น ๆ + +ตอนนี้คุณก็พอจะเข้าใจแล้วว่า attention layer คืออะไร ลองมาดูรายละเอียดของสถาปัตยกรรม Transformer กัน + + +## สถาปัตยกรรมต้นฉบับ + +ในตอนแรก Transformer ออกแบบมาเพื่องานแปลภาษา ระหว่างการเทรน ตัว encoder รับ input เป็นประโยคในภาษาหนึ่ง ในขณะที่ decoder เองก็รับประโยคเดียวกันในภาษาเป้าหมาย โดย attention layer ใน encoder สามารถใช้คำทั้งหมดในประโยคได้(เนื่องจากการแปลคำใด ๆ ขึ้นอยู่กับคำที่อาจจะอยู่ข้างหน้าหรือข้างหลังคำนั้น ๆ ก็ได้) ในขณะที่ decoder ต้องสร้างคำเป็นลำดับออกมาทีละคำ นั่นคือ decoder สามารถสนใจคำที่แปลออกมาก่อนได้เท่านั้น ตัวอย่างเช่น เมื่อเราแปลคำในภาษาเป้าหมายไป 3 คำแรก เราก็จะเอา 3 คำนี้ส่งไปยัง decoder รวมกับ input อื่นจาก encoder เพื่อทำนายคำที่ 4 ออกมา + + +เพื่อให้กระบวนการเทรนเร็วมากขึ้น(เมื่อโมเดลเข้าถึงประโยคในภาษาเป้าหมายได้) decoder จะรับประโยคในภาษาเป้าหมายไปทั้งประโยค แต่แค่ไม่อนุญาตให้เข้าถึงคำที่กำลังจะแปลในอนาคต(ลองคิดดูว่าถ้ากำลังทำนายคำในประโยคเป้าหมายตำแหน่งที่ 2 แล้วโมเดลก็เข้าถึงคำในตำแหน่งที่ 2 ในประโยคเป้าหมายได้ ปัญหามันก็ดูจะแก้ได้ง่ายเกินไปแหละ) หรืออีกนัยหนึ่งคือ ขณะที่กำลังทำนายคำที่ 4 ในประโยค ตัว attention layer จะเข้าถึงได้แค่คำที่ตำแหน่ง 1 ถึง 3 เท่านั้น + +รูปด้านล่างแสดงสถาปัตยกรรม Transformer โดย encoder อยู่ด้านซ้ายและ decoder อยู่ด้านขวา: + +
+Architecture of a Transformers models + +
+ +หมายเหตุไว้หน่อยว่า attention layer แรกใน decoder สนใจไปที่ input (ในอดีต)ทั้งหมดของ decoder แต่ attention layer ที่สองใช้ output ของ encoder ด้วย นั่นคือ มันสามารถเข้าถึงประโยค input ทั้งหมดเพื่อทำนายคำในปัจจุบันได้ ส่วนนี้มีประโยชน์มากเนื่องจากแต่ละภาษาก็มีหลักไวยากรณ์ในการวางตำแหน่งคำที่ต่างกัน หรือบริบทที่ถูกกล่าวถึงทีหลังในประโยคอาจส่งผลต่อการหาคำแปลที่เหมาะสมของคำในปัจจุบัน + +*attention mask* เป็นอีกส่วนประกอบที่ใช้ได้กับทั้ง encoder และ decoder เพื่อป้องกันไม่ให้โมเดลสนใจไปยังคำบางคำ ตัวอย่างเช่น คำพิเศษบางคำที่เอามาเติมในประโยคให้ประโยคใน input ที่ความยาวเท่ากับเวลาประมวลผลประโยคพร้อมกัน + +## สถาปัตยกรรม(หรือเรียกว่า Architecture) vs. จุดเซฟ(หรือเรียกว่า checkpoints) + +ดังที่เราได้เรียนรู้โมเดล Transformer มาในคอร์สนี้ คุณจะเห็นคำที่ความหมายคล้าย ๆ กัน อย่างเช่น *architecture* และ *checkpoint* รวมถึง *model* อย่างไรก็ตาม คำแหล่านี้มีความหมายแตกต่างกันเล็กน้อย ดังนี้: + +* **Architecture**: เป็นโครงสร้างของโมเดล นั่นคือ เป็นการกำหนดนิยามของ layer (เช่น บอกว่า มีทั้งหมดกี่ layer และ layer แต่ละ layer เป็นประเภทใด) รวมถึงบอกว่า layer นั้น ๆ จะมีกระบวนการการทำอะไรบ้าง +* **Checkpoints**: เป็นเพียงการเซฟ weight หรือค่าคงที่ของ parameter ในแต่ละ layer ที่จะเอาไปใช้ใน architecture ที่กำหนดได้ +* **Model**: เป็นคำกว้าง ๆ ไม่ได้ระบุความหมายเฉพาะเจาะจงเหมือนคำว่า "architecture" หรือ "checkpoint" ซึ่งจริง ๆ อาจจะหมายถึงคำใดก็ได้ แต่ในคอร์สนี้จะใช้คำว่า *architecture* หรือ *checkpoint* ไปเลยเพื่อหลีกเลี่ยงความสับสน + +ยกตัวอย่างง่าย ๆ เช่น BERT เป็น architecture ในขณะที่ `bert-base-cased` เป็น checkpoint ที่เซฟ weight จากการเทรน BERT ที่ทีม Google ปล่อยออกมาตอนเปิดให้ใช้งานครั้งแรก โดยเราสามารถเรียกสิ่งเหล่านี้ว่า"โมเดล BERT" หรือ "โมเดล `bert-base-cased`" ก็ถือว่าถูกต้องทั้งคู่ + + diff --git a/chapters/th/chapter1/5.mdx b/chapters/th/chapter1/5.mdx new file mode 100644 index 000000000..e8b38f14a --- /dev/null +++ b/chapters/th/chapter1/5.mdx @@ -0,0 +1,17 @@ +# โมเดล Encoder + + + +โมเดล encoder ใช้เพียงส่วน encoder จากโมเดล Transformer เท่านั้น ในแต่ละชั้น attention layer สามารถเข้าถึงคำทุกคำในประโยคได้ โมเดลเหล่านี้ส่วนใหญ่จะใช้ attention แบบสองทาง (หรือเรียกว่า bi-directional attention) และถูกเรียกว่า *โมเดล auto-encoding* + +โมเดล pretrain ในกลุ่มนี้จะเทรนโดยการให้ประโยคเริ่มต้นและประโยควิบัติ(เช่น เว้นว่างคำบางคำในประโยค) และเป้าหมายของโมเดลคือหาวิธีสร้างประโยคเริ่มต้นให้ดีดังเดิม + +โมเดล encoder เหมาะกับงานแบบนี้ที่สุด เพราะงานเหล่านี้ต้องการความเข้าใจประโยคทั้งประโยค ตัวอย่างงานแบบนี้เช่น การแยกแยะประโยค, การระบุคำเฉพาะในประโยค (รวมถึงการแยกแยะประเภทคำ), และการสกัดคำถามคำตอบ + +ตัวแทนโมเดลในกลุ่มนี้ได้แก่: + +- [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) diff --git a/chapters/th/chapter1/6.mdx b/chapters/th/chapter1/6.mdx new file mode 100644 index 000000000..f3f362950 --- /dev/null +++ b/chapters/th/chapter1/6.mdx @@ -0,0 +1,14 @@ +# โมเดล Decoder + + + +โมเดล decoder ใช้เพียงส่วน decoder จากโมเดล Transformer เท่านั้น ในแต่ละชั้น attention layer สามารถเข้าถึงคำที่อยู่ตำแหน่งก่อนหน้าในประโยคได้เท่านั้น โมเดลเหล่านี้เรียกว่า *โมเดล auto-regressive* + +โมเดล pretrain ในกลุ่มนี้ใช้ในการทำนายคำต่อไปในประโยค เหมาะสำหรับงานในการสร้างข้อความ + +ตัวแทนโมเดลในกลุ่มนี้ได้แก่: + +- [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) +- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) +- [GPT-2](https://huggingface.co/transformers/model_doc/gpt2.html) +- [Transformer XL](https://huggingface.co/transformers/model_doc/transformerxl.html) diff --git a/chapters/th/chapter1/7.mdx b/chapters/th/chapter1/7.mdx new file mode 100644 index 000000000..05fdbcae5 --- /dev/null +++ b/chapters/th/chapter1/7.mdx @@ -0,0 +1,16 @@ +# โมเดล sequence-to-sequence + + + +โมเดล encoder-decoder (หรือเรียกอีกชื่อหนึ่งว่า *โมเดล sequence-to-sequence*) ใช้ทั้งสองส่วนในสถาปัตยกรรม Transformer ในแต่ละชั้น attention layer ของ encoder จะเข้าถึงคำทั้งหมดในประโยคเริ่มต้นได้ ในขณะที่ attention layer ของ decoder สามารถเข้าถึงได้เพียงคำที่อยู่ตำแหน่งก่อนหน้าคำที่กำหนดใน input เท่านั้น + +โมเดล pretrain สามารถเทรนมาในลักษณะเดียวกับโมเดล encoder หรือโมเดล decoder ก็ได้ แต่โดยมากแล้วจะซับซ้อนมากกว่า ตัวอย่างเช่น [T5](https://huggingface.co/t5-base) ถูกเทรนมาโดยการแทนที่กลุ่มคำ(ซึ่งอาจจะมีเพียงคำเดียวหรือหลายคำก็ได้)ด้วยคำพิเศษคำเดียว และเป้าหมายคือให้ทำนายข้อความที่คำพิเศษคำนี้แทนที่มา + +โมเดล sequence-to-sequence เหมาะกับงานในการสร้างประโยคขึ้นมาใหม่จาก input ที่กำหนดให้ เช่น การสรุปความ, การแปลภาษา, หรือการสร้างคำตอบจากคำถาม + +ตัวแทนโมเดลในกลุ่มนี้ได้แก่: + +- [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) diff --git a/chapters/th/chapter1/8.mdx b/chapters/th/chapter1/8.mdx new file mode 100644 index 000000000..27dc1a643 --- /dev/null +++ b/chapters/th/chapter1/8.mdx @@ -0,0 +1,32 @@ +# ข้อจำกัดจากอคติของข้อมูล + + + +หากคุณต้องการจะใช้โมเดล pretrain หรือโมเดล fine-tune ในการใช้งานจริง โปรดระลึกไว้เสมอว่าโมเดลพวกนี้ใช้งานได้ดี และก็มีข้อจำกัดอยู่เช่นกัน ข้อจำกัดที่สำคัญที่สุดเลยคือ การจะ pretrain โมเดลเหล่านี้ด้วยข้อมูลขนาดใหญ่ได้ นักวิจัยก็ต้องดึงข้อมูลมากจากแหล่งต่าง ๆ ทั้งหมดเท่าที่หาได้ นั่นคือมันจะมีทั้งข้อมูลที่ดีและข้อมูลแย่ ๆ ในอินเตอร์เนตมารวมเข้าด้วยกัน + +เพื่อให้เห็นภาพ ลองมาดูตัวอย่างการ `fill-mask` ด้วยโมเดล BERT: + +```python +from transformers import pipeline + +unmasker = pipeline("fill-mask", model="bert-base-uncased") +result = unmasker("This man works as a [MASK].") +print([r["token_str"] for r in result]) + +result = unmasker("This woman works as a [MASK].") +print([r["token_str"] for r in result]) +``` + +```python out +['lawyer', 'carpenter', 'doctor', 'waiter', 'mechanic'] +['nurse', 'waitress', 'teacher', 'maid', 'prostitute'] +``` + +เมื่อต้องการเติมคำในช่องว่างในประโยคสองประโยคด้านบน โมเดลให้คำตอบออกมาเป็นอาชีพที่ไม่เกี่ยวข้องกับเพศเพียงอาชีพเดียว (waiter/waitress) ส่วนอาชีพอื่น ๆ จะออกแนวไปทางเพศใดเพศหนึ่ง -- และแน่นอน โสเภณีกลายเป็นตัวเลือก 5 ตัวเลือกแรกที่โมเดลเลือกขึ้นมาเมื่อเจอคำว่า "woman" และ "work" แม้ว่า BERT จะเป็นโมเดล Transformer เพียงไม่กี่โมเดลที่ไม่ได้เทรนขึ้นมาจากข้อมูลที่ดึงออกมาจากอินเตอร์เนต แต่ใช้ข้อมูลกลาง ๆ (โมเดลนี้เทรนขึ้นมาจากชุดข้อมูล [English Wikipedia](https://huggingface.co/datasets/wikipedia) และ [BookCorpus](https://huggingface.co/datasets/bookcorpus)) + +เมื่อคุณได้เครื่องมือเหล่านี้ โปรดระลึกไว้เสมอว่าโมเดลเริ่มต้นนั้นสามารถสร้างข้อความที่แบ่งแยกเพศ แบ่งแยกเชื้อชาติ หรือแม้แต่ต่อต้านการเปิดกว้างเรื่องเพศ การ fine-tune โมเดลด้วยข้อมูลของเราไม่ได้ทำให้ อคติเหล่านี้หายไป \ No newline at end of file diff --git a/chapters/th/chapter1/9.mdx b/chapters/th/chapter1/9.mdx new file mode 100644 index 000000000..712dfda78 --- /dev/null +++ b/chapters/th/chapter1/9.mdx @@ -0,0 +1,12 @@ +# สรุป + +ในบทนี้คุณได้เรียนรู้การแก้ปัญหา NLP แบบต่าง ๆ โดยใช้ฟังก์ชัน `pipeline()` จาก 🤗 Transformers รวมถึงได้เรียนรู้การค้นหาและใช้งานโมเดลใน Hub อีกทั้งยังเรียนรู้การใช้งาน Inference API ในการทดสอบโมเดลจากเว็บบราวเซอร์ + +นอกจากนี้เรายังได้พูดเกี่ยวกับการทำงานของโมเดล Transformer และความสำคัญของการใช้งาน transfer learning และการ fine-tune สิ่งสำคัญเลยคือ คุณสามารถใช้โมเดลแบบเต็มรูปแบบหรือจะใช้เพียงแค่ส่วน encoder หรือ decoder ก็ได้ ขึ้นอยู่กับว่าต้องการใช้งานแบบไหน ตารางด้านล่างสรุปการใช้งานไว้ดังนี้: + + +| โมเดล | ตัวอย่าง | งาน | +|-----------------|--------------------------------------------|----------------------------------------------------------------------------------| +| Encoder | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa | การแยกแยะประโยค, การระบุประเภทคำ, การสกัดคำถามคำตอบ | +| Decoder | CTRL, GPT, GPT-2, Transformer XL | การสร้างข้อความ | +| Encoder-decoder | BART, T5, Marian, mBART | การสรุปความ, การแปลภาษา, การสร้างคำตอบจากคำถาม | diff --git a/chapters/tr/_toctree.yml b/chapters/tr/_toctree.yml index 7c06284a4..5e9cc1073 100644 --- a/chapters/tr/_toctree.yml +++ b/chapters/tr/_toctree.yml @@ -6,5 +6,14 @@ - title: 1. Transformer modelleri sections: + - local: chapter1/1 + title: Giriş - local: chapter1/2 title: Doğal Dil İşleme + - local: chapter1/5 + title: Encoder modelleri + +- title: 2. 🤗 Transformers Kullanımı + sections: + - local: chapter2/1 + title: Giriş diff --git a/chapters/tr/chapter1/1.mdx b/chapters/tr/chapter1/1.mdx new file mode 100644 index 000000000..09670c860 --- /dev/null +++ b/chapters/tr/chapter1/1.mdx @@ -0,0 +1,53 @@ +# Giriş + +## 🤗 Kursuna Hoşgeldiniz! + + + +Bu kurs size [Hugging Face](https://huggingface.co/) ekosistemindeki kütüphaneleri — [🤗 Transformers](https://github.com/huggingface/transformers), [🤗 Datasets](https://github.com/huggingface/datasets), [🤗 Tokenizers](https://github.com/huggingface/tokenizers), ve [🤗 Accelerate](https://github.com/huggingface/accelerate) — ayrıca tabiki de [Hugging Face Hub](https://huggingface.co/models) kullanarak Doğal Dil İşleme'yi (NLP) öğretecektir. Kurs tamamen ücretsiz ve reklam bulunmuyor. + +## Beklentiniz ne olmalı? + +Burada kursa genel bakış bulunmaktadır: + +
+Brief overview of the chapters of the course. + +
+ +- 1'den 4'üncü bölüme kadar olan kısımda 🤗 Transformers kütüphanesinin ana konseptlerine giriş yapacağız. Kursun bu bölümünün sonunda, Transformer modellerinin nasıl çalıştığını öğrenecek, ve [Hugging Face Hub](https://huggingface.co/models) üzerinden bir modeli nasil kullanabileceğinizi, verisetine ince ayar (fine-tune) yapmayı, ve sonuçlarınızı Hub üzerinde nasıl paylaşacağınızı bileceksiniz! +- Bölüm 5 ila 8, klasik NLP görevlerine dalmadan önce 🤗 Datasets ve 🤗 Tokenizers'in temellerini öğretiyor. Bu bölümün sonunda, en yaygın NLP problemlerini kendiniz çözebileceksiniz. +- 9'dan 12'ye kadar olan bölümler NLP'nin ötesine geçer ve Transformer modellerinin konuşma işleme ve bilgisayarlı görü alanlarındaki problemleri nasıl çözeceğini ele alır. Süreç boyunca, model demolarınızı nasıl oluşturup paylaşacağınızı ve bunları üretim ortamlarında nasıl optimize edeceğinizi öğreneceksiniz. Bu bölümün sonunda, 🤗 Transformers'i (neredeyse) herhangi bir makine öğrenmesi problemine uygulamaya hazır olacaksınız! + +Bu kurs: + +* Python hakkında iyi düzeyde bilgi gerektirir. +* Giriş düzeyinde derin öğrenme derslerinden sonra alınırsa daha iyi olur. Örneğin: [fast.ai](https://www.fast.ai/) [Practical Deep Learning for Coders kursu](https://course.fast.ai/) veya [DeepLearning.AI](https://www.deeplearning.ai/) tarafından verilen herhangi program. +* [PyTorch](https://pytorch.org/) veya [TensorFlow](https://www.tensorflow.org/) ile herhangi bir ön bilgi beklenmiyor, buna rağmen bunlardan birisine aşinalik yardımcı olur. + +Kursu bitirdikten sonra, DeepLearning.AI [Doğal Dil İşleme Uzmanlık](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh) naive Bayes ve LSTM gibi bilmeye değer geleneksel NLP modellerinin bulunduğu seriyi izlemenizi tavsiye ediyoruz. + + +## Biz Kimiz? + +Eğitmenler hakkında: + +**Matthew Carrigan** Hugging Face'de Makine Öğrenmesi Mühendisi. Dublin, İrlanda'da yaşıyor ve daha önce Parse.ly'de ML Engineer olarak çalıştı ve onunda öncesinde Doktora sonrası Araştırmacı olarak Trinity College Dublin'de idi. Mevcut AI mimarilerini ölçeklendirerek AGI'a ulaşacağımıza inanmıyor, ancak ne olursa olsun robot ölümsüzlüğü için büyük umutları var. + +**Lysandre Debut** Hugging Face'de Makine Öğrenmesi Mühendisi ve 🤗 Transformers kütüphanesi üzerine erken gelişim aşamasından beri çalışıyor. Hedefi çok basit bir API geliştirerek NLP'yi herkes için ulaşılabilir kılmak. + +**Sylvain Guggeat** Hugging Face'de Araştırma Mühendisi ve 🤗 Transformers kütüphanesinin proje yürütücülerinden biri. Öncesinde Araştırma Bilimci olarak fast.ai'da çalıştı, ve _[Deep Learning for Coders with fastai and PyTorch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)_ kitabını Jeremy Howard ile birlikte yazdı. Sylvain'in araştırmalarının ana odağı modellerin sınırlı kaynaklarda daha hızlı eğitilmesine izin veren teknikleri tasarlayıp geliştirerek derin öğrenmeyi herkes için ulaşılabilir kılmak. + +**Merve Noyan** Hugging Face'de Developer Advocate, araçlar geliştirerek ve bu araçlar etrafında içerik üreterek makine öğrenmesini herkes için demokratikleştirme üzerine çalışıyor. + +**Lucile Saulnier** Hugging Face'de Makine Öğrenmesi Mühendisi, açık kaynak araçlarının geliştirilmesi ve desteklenmesi üzerine uğraşıyor. Ayrıca Doğal Dil İşleme içinde Collaborative Training ve BigScience gibi birçok araştırma projesine dahil. + +**Lewis Tunstall** Hugging Face'de Makine Öğrenmesi Mühendisi, açık kaynak araçları geliştirmeye ve bunlari daha geniş bir topluluk için ulaşılabilir hale getirmeye odaklanmış. Ayrıca yakında gelecek olan [Transformers üzerine O’Reilly kitabının](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/) yazarlarından biri. + +**Leandro von Werra** Hugging Face'de Açık-Kaynak takımında Makine Öğrenmesi Mühendisi ve ayrica yakında gelecek olan [Transformers üzerine olan O’Reilly kitabinin](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/) yazarlarından biri. Tüm Makine Öğrenmesi stack'inde çalişarak NLP projelerini üretime getiren birkaç yıllık endüstri deneyimine sahiptir. + + +Başlamaya hazır mısın? Bu bölümde, şunları öğreneceksin: +* Metin oluşturma ve sınıflandırma gibi NLP görevlerini çözmek için `pipeline ()` fonksiyonu nasıl kullanılacağı? +* Transformer mimarisi +* Encoder, Decoder, ve Encoder-Decoder mimarilerini nasil ayırt edileceği ve kullanım alanlari diff --git a/chapters/tr/chapter1/5.mdx b/chapters/tr/chapter1/5.mdx new file mode 100644 index 000000000..cc86a135d --- /dev/null +++ b/chapters/tr/chapter1/5.mdx @@ -0,0 +1,18 @@ +# Encoder modelleri + + + +Encoder modelleri Transformer modellerinin sadece encoder kısmını kulanır.Her aşamada, attention katmanları ilk cümlenin bütün kelimelerine erişir. Bu modeller genellikle çift yönlü attention olarak nitelendirilir ve genellikle *auto-encoding models* olarak adlandırılır. + +Bu modellerin öneğitimi genellikle verilen cümleyi bozmaya yöneliktir (örnek olarak, içindeki rastgele kelimeleri maskeleyerek) ve model ilk cümleyi bulma veya yeniden oluşturma ile görevlendirilir. + +Encoder modelleri cümle sınıflandırma, varlık tanıma (daha spesifik olarak sözcük sınıflandırma) ve extractive soru yanıtlama gibi cümlenin tam anlaşılmasını gerektiren görevler için uygundur. + + +Bu model ailesinin temsilcileri şunlardır: + +- [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) diff --git a/chapters/tr/chapter2/1.mdx b/chapters/tr/chapter2/1.mdx new file mode 100644 index 000000000..aad4d9b37 --- /dev/null +++ b/chapters/tr/chapter2/1.mdx @@ -0,0 +1,20 @@ +# Giriş + +[Birinci bölümde](/course/chapter1) gördüğünüz gibi, Transformer modelleri genellikle oldukça büyüktür. Milyonlarca, hatta milyarlarca, parametreleri olan bu modellerin eğitimi ve üretime geçirilmesi oldukça karmaşık bir girişimdir. Bunun yanısıra, hemen hemen her gün, herbirinin kendine özgü uygulaması olan yeni modellerin yayınlaması, bu yeni modellerinin hepsini birden denemeyi daha da zor bir hale getirmektedir. + +🤗 Transformers kütüphanesi bu sorunu çözmek için oluşturuldu. Bu kütüphanenin amacı, herhangi bir Transformer modelini tek bir API (uygulama programı arabirimi) aracılığı ile yükleme, eğitme, ve kaydetmeyi sağlamaktır. Kütüphanenin başlıca özellikleri şunlardır: + +- **Kullanım kolaylığı**: En son geliştirilen NLP modellerini indirme, yükleme ve kullanma yalnızca iki satır kod ile yapılabilir. +- **Esneklik**: Bütün modeller temelinde sadece ya PyTorch `nn.Module` ya da TensorFlow `tf.keras.Model` sınıfıdır ve her bir model, ait olduğu kütüphanesindeki diğer herhangi bir model gibi işlenebilir. +- **Sadelik**: Kütüphane içerisinde neredeyse hiç bir soyutlama yapılmamaktadır. “Hersey tek bir dosya içerisinde” ifadesi, kütüphanenin ana kavramını oluşturuyor. Bir modelin “forward pass” aşaması, kodun anlaşılır olması ve kolayca modifiye edilebilmesini sağlamak amacı ile tamamiyle tek bir dosya içerisinde tanımlanır. + +Bu son özellik 🤗 Transformers kütüphanesini diğer makine öğrenmesi kütüphanelerinden oldukça farklı kılmaktadır. Modeller dosyalar arasında paylaşılan modüller üzerinde kurulmamıştır. Bunun yerine, her bir model kendine ait katmanlara sahiptir. Bu özellik, modelleri ulaşılabilir ve anlaşılır hale getirmenin yanısıra, diğer modelleri etkilemeden, tek bir model üzerinde kolayca deneyler yapabilmenize olanak sağlamaktadır. + +Bu bölüm, bir model ve simgeleleştirici (tokenizer) kullanarak, birinci bölümde tanıtılan `pipeline()` fonksiyonunun bir kopyasını yapmak için baştan sona (end-to-end) uygulayacağımız bir örnekle başlamaktadır. +Bunun ardından, kütüphanenin model API’ından bahsedeceğiz: Model ve konfigürasyon sınıflarına detaylıca bakıp, bir modeli nasıl yükleyebileceğinizi ve bir modelin sayısal giriş verilerini nasıl çıkış öngörüleri olarak işlediğini göreceğiz. + +Daha sonra, `pipeline()` fonksiyonunun diğer ana parçası olan simgeleştirici API’ına göz atacağız. Simgeleştiriciler, sinir ağının metni sayısal giriş verilerine ve gerektiğinde bu sayısal verileri tekrar metne dönüştüren ilk ve son işlem aşamalarından sorumludur. Son olarak, birden fazla cümleyi hazır bir grup halinde bir modele nasıl gönderebileceğinizi gösterip, `tokenizer()` fonksiyonuna yakından bakarak bu bölümü tamamlayacağız. + + +⚠️ Model Hub ve 🤗 Transformers kütüphanesinde yeralan bütün özelliklerden yararlanabilmeniz icin, bir hesap oluşturmanızı tavsiye ediyoruz. + From ec19b2b952e439a9bc078ef936567001d34ab519 Mon Sep 17 00:00:00 2001 From: lewtun Date: Mon, 16 May 2022 09:31:56 +0200 Subject: [PATCH 12/51] Fix typos in chapter 9 (#176) (#180) Co-authored-by: regisss <15324346+regisss@users.noreply.github.com> --- chapters/en/chapter9/1.mdx | 8 ++++---- chapters/en/chapter9/2.mdx | 16 ++++++++-------- chapters/en/chapter9/3.mdx | 30 +++++++++++++++--------------- chapters/en/chapter9/4.mdx | 30 +++++++++++++++--------------- chapters/en/chapter9/6.mdx | 20 ++++++++++---------- chapters/en/chapter9/7.mdx | 6 +++--- 6 files changed, 55 insertions(+), 55 deletions(-) diff --git a/chapters/en/chapter9/1.mdx b/chapters/en/chapter9/1.mdx index ee03f1394..7ca56a659 100644 --- a/chapters/en/chapter9/1.mdx +++ b/chapters/en/chapter9/1.mdx @@ -1,6 +1,6 @@ # Introduction to Gradio -In this chapter we will be learning about how to build **interactive demos** for your machine learning models. +In this chapter we will be learning about how to build **interactive demos** for your machine learning models. Why build a demo or a GUI for your machine learning model in the first place? Demos allow: @@ -13,11 +13,11 @@ We'll be using the Gradio library to build demos for our models. Gradio allows y Here are some examples of machine learning demos built with Gradio: -* A **sketch recognition** model that takes in a sketch and outputs labels of what it thinks is being drawn: +* A **sketch recognition** model that takes in a sketch and outputs labels of what it thinks is being drawn: -* An extractive **question answering** model that takes in a context paragraph and a quest and outputs a response and a probability score (we discussed this kind of model [in Chapter 7]((/course/chapter7/7))): +* An extractive **question answering** model that takes in a context paragraph and a quest and outputs a response and a probability score (we discussed this kind of model [in Chapter 7](/course/chapter7/7)): @@ -25,7 +25,7 @@ Here are some examples of machine learning demos built with Gradio: -This chapter is broken down into sections which include both _concepts_ and _applications_. After you learn the concept in each section, you'll apply it to build a particular kind of demo, ranging from image classification to speech recognition. By the time you finish this chapter, you'll be able to build these demos (and many more!) in just a few lines of Python code. +This chapter is broken down into sections which include both _concepts_ and _applications_. After you learn the concept in each section, you'll apply it to build a particular kind of demo, ranging from image classification to speech recognition. By the time you finish this chapter, you'll be able to build these demos (and many more!) in just a few lines of Python code. 👀 Check out Hugging Face Spaces to see many recent examples of machine learning demos built by the machine learning community! diff --git a/chapters/en/chapter9/2.mdx b/chapters/en/chapter9/2.mdx index f9ea9b82e..a6909da22 100644 --- a/chapters/en/chapter9/2.mdx +++ b/chapters/en/chapter9/2.mdx @@ -25,8 +25,8 @@ demo.launch() Let's walk through the code above: - First, we define a function called `greet()`. In this case, it is a simple function that adds "Hello" before your name, but it can be *any* Python function in general. For example, in machine learning applications, this function would *call a model to make a prediction* on an input and return the output. -- Then, we create a Gradio `Interface` with three arguments, `fn`, `inputs`, and `outputs`. These arguments define the prediction function, as well as the _type_ of input and output components we would like. In our case, both components are simple text boxes. -- We then call the `launch()` method on the `Interface` that we created. +- Then, we create a Gradio `Interface` with three arguments, `fn`, `inputs`, and `outputs`. These arguments define the prediction function, as well as the _type_ of input and output components we would like. In our case, both components are simple text boxes. +- We then call the `launch()` method on the `Interface` that we created. If you run this code, the interface below will appear automatically within a Jupyter/Colab notebook, or pop in a browser on **[http://localhost:7860](http://localhost:7860/)** if running from a script. @@ -36,8 +36,8 @@ Try using this GUI right now with your own name or some other input! You'll notice that in this GUI, Gradio automatically inferred the name of the input parameter (`name`) and applied it as a label on top of the textbox. What if you'd like to change that? -Or if you'd like to customize the textbox in some other way? In that case, you can -instantiate a class object representing the input component. +Or if you'd like to customize the textbox in some other way? In that case, you can +instantiate a class object representing the input component. Take a look at the example below: @@ -60,8 +60,8 @@ gr.Interface(fn=greet, inputs=textbox, outputs="text").launch() Here, we've created an input textbox with a label, a placeholder, and a set number of lines. You could do the same for the output textbox, but we'll leave that for now. -We've seen that with just a few lines of code, Gradio lets you create a simple interface around any function -with any kind of inputs or outputs. In this section, we've started with a +We've seen that with just a few lines of code, Gradio lets you create a simple interface around any function +with any kind of inputs or outputs. In this section, we've started with a simple textbox, but in the next sections, we'll cover other kinds of inputs and outputs. Let's now take a look at including some NLP in a Gradio application. @@ -69,8 +69,8 @@ simple textbox, but in the next sections, we'll cover other kinds of inputs and Let's now build a simple interface that allows you to demo a **text-generation** model like GPT-2. -We'll load our model using the the `pipeline()` function from 🤗 Transformers. -If you need a quick refresher, you can go back to [that section in Chapter 1](/course/chapter1/3#text-generation). +We'll load our model using the `pipeline()` function from 🤗 Transformers. +If you need a quick refresher, you can go back to [that section in Chapter 1](/course/chapter1/3#text-generation). First, we define a prediction function that takes in a text prompt and returns the text completion: diff --git a/chapters/en/chapter9/3.mdx b/chapters/en/chapter9/3.mdx index bd9138ef2..33450ecae 100644 --- a/chapters/en/chapter9/3.mdx +++ b/chapters/en/chapter9/3.mdx @@ -5,17 +5,17 @@ main parameters used to create one. ## How to create an Interface -You'll notice that the `Interface` class has 3 required parameters: +You'll notice that the `Interface` class has 3 required parameters: `Interface(fn, inputs, outputs, ...)` These parameters are: - `fn`: the prediction function that is wrapped by the Gradio interface. This function can take one or more parameters and return one or more values - - `inputs`: the input component type(s). Gradio provides many pre-built components such as`"image"` or `"mic"`. + - `inputs`: the input component type(s). Gradio provides many pre-built components such as`"image"` or `"mic"`. - `outputs`: the output component type(s). Again, `gradio` provides many pre-built components e.g. `"image"` or `"label"`. -For a complete list of components, [see the Gradio docs ](https://gradio.app/docs). Each pre-built component can be customized by instantiating the class corresponding to the component. +For a complete list of components, [see the Gradio docs ](https://gradio.app/docs). Each pre-built component can be customized by instantiating the class corresponding to the component. For example, as we saw in the [previous section](/course/chapter9/2), instead of passing in `"textbox"` to the `inputs` parameter, you can pass in a `Textbox(lines=7, label="Prompt")` component to create a textbox with 7 lines and a label. @@ -24,13 +24,13 @@ Let's take a look at another example, this time with an `Audio` component. ## A simple example with audio -As mentioned earlier, Gradio provides many different inputs and outputs. +As mentioned earlier, Gradio provides many different inputs and outputs. So let's build an `Interface` that works with audio. -In this example, we'll build an audio-to-audio function that takes an +In this example, we'll build an audio-to-audio function that takes an audio file and simply reverses it. -We will use for the input the `Audio` component. When using the `Audio` component, +We will use for the input the `Audio` component. When using the `Audio` component, you can specify whether you want the `source` of the audio to be a file that the user uploads or a microphone that the user records their voice with. In this case, let's set it to a `"microphone"`. Just for fun, we'll add a label to our `Audio` that says @@ -41,8 +41,8 @@ In addition, we'd like to receive the audio as a numpy array so that we can easi data as a tuple of (`sample_rate`, `data`) into our function. We will also use the `Audio` output component which can automatically -render a tuple with a sample rate and numpy array of data as a playable audio file. -In this case, we do not need to do any customization, so we will use the string +render a tuple with a sample rate and numpy array of data as a playable audio file. +In this case, we do not need to do any customization, so we will use the string shortcut `"audio"`. @@ -70,9 +70,9 @@ You should now be able to record your voice and hear yourself speaking in revers ## Handling multiple inputs and outputs -Let's say we had a more complicated function, with multiple inputs and outputs. -In the example below, we have a function that takes a dropdown index, a slider value, and number, -and returns an audio sample of a musical tone. +Let's say we had a more complicated function, with multiple inputs and outputs. +In the example below, we have a function that takes a dropdown index, a slider value, and number, +and returns an audio sample of a musical tone. Take a look how we pass a list of input and output components, and see if you can follow along what's happening. @@ -116,10 +116,10 @@ gr.Interface( ### The `launch()` method -So far, we have used the `launch()` method to launch the interface, but we -haven't really discussed what it does. +So far, we have used the `launch()` method to launch the interface, but we +haven't really discussed what it does. -By default, the `launch()` method will launch the demo in a web server that +By default, the `launch()` method will launch the demo in a web server that is running locally. If you are running your code in a Jupyter or Colab notebook, then Gradio will embed the demo GUI in the notebook so you can easily use it. @@ -136,7 +136,7 @@ We'll cover the `share` parameter in a lot more detail in the next section! Let's build an interface that allows you to demo a **speech-recognition** model. To make it interesting, we will accept *either* a mic input or an uploaded file. -As usual, we'll load our speech recognition model using the the `pipeline()` function 🤗 Transformers. +As usual, we'll load our speech recognition model using the `pipeline()` function from 🤗 Transformers. If you need a quick refresher, you can go back to [that section in Chapter 1](/course/chapter1/3). Next, we'll implement a `transcribe_audio()` function that processes the audio and returns the transcription. Finally, we'll wrap this function in an `Interface` with the `Audio` components for the inputs and just text for the output. Altogether, the code for this application is the following: ```py diff --git a/chapters/en/chapter9/4.mdx b/chapters/en/chapter9/4.mdx index 16d23cc3a..ec5b66915 100644 --- a/chapters/en/chapter9/4.mdx +++ b/chapters/en/chapter9/4.mdx @@ -23,7 +23,7 @@ Using the options above, we end up with a more complete interface. Run the code ```python out title = "Ask Rick a Question" -description = +description = """ The bot was trained to answer questions based on Rick and Morty dialogues. Ask Rick anything! @@ -32,14 +32,14 @@ The bot was trained to answer questions based on Rick and Morty dialogues. Ask R 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", + fn=predict, + inputs="textbox", outputs="text", - title=title, - description=description, + title=title, + description=description, article=article, - examples=[["What are you doing?"], ["Where should we time travel to?"]] -).launch() + examples=[["What are you doing?"], ["Where should we time travel to?"]] +).launch() ``` Using the options above, we end up with a more complete interface. Try the interface below: @@ -53,7 +53,7 @@ Interfaces can be easily shared publicly by setting `share=True` in the `launch( ```python gr.Interface(classify_image, "image", "label").launch(share=True) ``` - + This generates a public, shareable link that you can send to anybody! When you send this link, the user on the other side can try out the model in their browser for up to 72 hours. Because the processing happens on your device (as long as your device stays on!), you don't have to worry about packaging any dependencies. If you're working out of a Google Colab notebook, a share link is always automatically created. It usually looks something like this: **XXXXX.gradio.app**. Although the link is served through a Gradio link, we are only a proxy for your local server, and do not store any data sent through the interfaces. Keep in mind, however, that these links are publicly accessible, meaning that anyone can use your model for prediction! Therefore, make sure not to expose any sensitive information through the functions you write, or allow any critical changes to occur on your device. If you set `share=False` (the default), only a local link is created. @@ -62,8 +62,8 @@ Keep in mind, however, that these links are publicly accessible, meaning that an A share link that you can pass around to collegues is cool, but how can you permanently host your demo and have it exist in its own "space" on the internet? -Hugging Face Spaces provides the infrastructure to permanently host your Gradio model on the internet, **for free**! Spaces allows you to create and push to a (public or private) repo, -where your Gradio +Hugging Face Spaces provides the infrastructure to permanently host your Gradio model on the internet, **for free**! Spaces allows you to create and push to a (public or private) repo, +where your Gradio interface code will exist in an `app.py` file. [Read a step-by-step tutorial](https://huggingface.co/blog/gradio-spaces) to get started, or watch an example video below. @@ -129,12 +129,12 @@ interface.launch(share=True) Notice the `live=True` parameter in `Interface`, which means that the sketch demo makes -a prediction every time someone draws on the sketchpad (no submit button!). +a prediction every time someone draws on the sketchpad (no submit button!). -Furthermore, we also set the `share=True` argument in the `launch()` method. -This will create a public link that you can -send to anyone! When you send this link, the user on the other side can try out the -sketch recognition model. To reiterate, you culd also host the model on Hugging Face Spaces, +Furthermore, we also set the `share=True` argument in the `launch()` method. +This will create a public link that you can +send to anyone! When you send this link, the user on the other side can try out the +sketch recognition model. To reiterate, you could also host the model on Hugging Face Spaces, which is how we are able to embed the demo above. Next up, we'll cover other ways that Gradio can be used with the Hugging Face ecosystem! \ No newline at end of file diff --git a/chapters/en/chapter9/6.mdx b/chapters/en/chapter9/6.mdx index 1f8cb24ee..641a854e4 100644 --- a/chapters/en/chapter9/6.mdx +++ b/chapters/en/chapter9/6.mdx @@ -4,15 +4,15 @@ Now that we can build and share a basic interface, let's explore some more advan ### Using state to persist data -Gradio supports *session state*, where data persists across multiple submits within a +Gradio supports *session state*, where data persists across multiple submits within a page load. Session state is useful for building demos of, for example, chatbots where you want to -persist data as the user interacts with the model. Note that session state does not shared data between different users of your model. +persist data as the user interacts with the model. Note that session state does not share data between different users of your model. -To store data in a session state, you need to do three things: +To store data in a session state, you need to do three things: -1. Pass in an *extra parameter* into your function, which represents the state of the interface. +1. Pass in an *extra parameter* into your function, which represents the state of the interface. 1. At the end of the function, return the updated value of the state as an *extra return value*. -1. Add the 'state' input and 'state' output components when creating your `Interface`. +1. Add the 'state' input and 'state' output components when creating your `Interface`. See the chatbot example below: @@ -48,9 +48,9 @@ iface.launch() -Notice how the state of the output component persists across submits. -Note: you can pass in a default value to the state parameter, -which is used as the initial value of the state. +Notice how the state of the output component persists across submits. +Note: you can pass in a default value to the state parameter, +which is used as the initial value of the state. ### Using interpretation to understand predictions @@ -95,9 +95,9 @@ Lastly, you can also pass in your own interpretation function into the `interpre ### Adding authentication -You may want to authentication to your Gradio interface in order to control who can access and use your demo. +You may want to authenticate to your Gradio interface in order to control who can access and use your demo. -Authentication can be added by provided a list of username/password tuples to the `auth` parameter in the `launch()` method. For more complex authentication handling, you can pass a function that takes a username and password as arguments, and returns `True` to allow authentication, `False` otherwise. +Authentication can be added by providing a list of username/password tuples to the `auth` parameter in the `launch()` method. For more complex authentication handling, you can pass a function that takes a username and password as arguments, and returns `True` to allow authentication, `False` otherwise. Let's take the image classification demo above and add authentication: diff --git a/chapters/en/chapter9/7.mdx b/chapters/en/chapter9/7.mdx index c69233b67..8004ff688 100644 --- a/chapters/en/chapter9/7.mdx +++ b/chapters/en/chapter9/7.mdx @@ -53,7 +53,7 @@ demo.launch() This simple example above introduces 4 concepts that underlie Blocks: -1. Blocks allow you to build web applications that combine markdown, HTML, buttons, and interactive components simply by instantiating objects in Python inside of a `with gradio.Blocks` context. +1. Blocks allow you to build web applications that combine markdown, HTML, buttons, and interactive components simply by instantiating objects in Python inside of a `with gradio.Blocks` context. 🙋If you're not familiar with the `with` statement in Python, we recommend checking out the excellent [tutorial](https://realpython.com/python-with-statement/) from Real Python. Come back here after reading that 🤗 @@ -129,7 +129,7 @@ You can attach event trigger to none, one, or more of these events. You create a - `fn`: the function to run - `inputs`: a (list of) component(s) whose values should supplied as the input parameters to the function. Each component's value gets mapped to the corresponding function parameter, in order. This parameter can be None if the function does not take any parameters. -- `outputs`: a (list of) component(s) whose values should be updated based on the values returned by the function. Each return value gets sets the corresponding component's value, in order. This parameter can be None if the function does not return anything. +- `outputs`: a (list of) component(s) whose values should be updated based on the values returned by the function. Each return value sets the corresponding component's value, in order. This parameter can be None if the function does not return anything. You can even make the input and output component be the same component, as we do in this example that uses a GPT model to do text completion: @@ -157,7 +157,7 @@ demo.launch() ### Creating multi-step demos -In some cases, you might want want a _multi-step demo_, in which you reuse the output of one function as the input to the next. This is really easy to do with `Blocks`, as you can use a component for the input of one event trigger but the output of another. Take a look at the text component in the example below, its value is the result of a speech-to-text model, but also gets passed into a sentiment analysis model: +In some cases, you might want a _multi-step demo_, in which you reuse the output of one function as the input to the next. This is really easy to do with `Blocks`, as you can use a component for the input of one event trigger but the output of another. Take a look at the text component in the example below, its value is the result of a speech-to-text model, but also gets passed into a sentiment analysis model: ```py from transformers import pipeline From 679bdbf1abbce65feb1b4a018aff499d278ceccc Mon Sep 17 00:00:00 2001 From: lewtun Date: Tue, 17 May 2022 08:47:37 +0200 Subject: [PATCH 13/51] Bump release (#187) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Chapter 2 Section 1 Bengali Translation (huggingface#72) (#168) * [TH] Chapter 6 Section 1 and 2 (#171) Co-authored-by: Suteera * [FA] CH1 / P1-2 (#142) * Spanish Chapter 3: sections 1 & 2 (#162) * fix typos in bpe, wordpiece, unigram (#166) * [FR] French Review (#186) * Part 7: Training a causal... fixes (#179) * typo & error mitigation * consistency * Trainer.predict() returns 3 fields * ran make style * [TR] Translated Chapter 1.6 🤗 (#185) * added chapter 1/6 to _toctree.yml * [TR] Translated Chapter 1.6 🤗 Co-authored-by: Avishek Das Co-authored-by: Suteera Seeha <33692408+meanna@users.noreply.github.com> Co-authored-by: Suteera Co-authored-by: Saeed Choobani Co-authored-by: Fermin Ordaz Co-authored-by: Kerem Turgutlu Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Sebastian Sosa <37946988+CakeCrusher@users.noreply.github.com> Co-authored-by: tanersekmen <56790802+tanersekmen@users.noreply.github.com> --- README.md | 1 + chapters/bn/_toctree.yml | 5 + chapters/bn/chapter2/1.mdx | 20 + chapters/en/chapter3/3.mdx | 2 +- chapters/en/chapter3/3_tf.mdx | 2 +- chapters/en/chapter6/5.mdx | 2 +- chapters/en/chapter6/6.mdx | 4 +- chapters/en/chapter6/7.mdx | 2 +- chapters/en/chapter7/6.mdx | 13 +- chapters/en/chapter7/7.mdx | 2 +- chapters/en/event/1.mdx | 4 +- chapters/es/_toctree.yml | 7 + chapters/es/chapter3/1.mdx | 21 + chapters/es/chapter3/2.mdx | 384 ++++++ chapters/fa/_toctree.yml | 2 + chapters/fa/chapter1/1.mdx | 62 +- chapters/fa/chapter1/2.mdx | 25 + chapters/fr/_toctree.yml | 369 ++--- chapters/fr/chapter0/1.mdx | 220 +-- chapters/fr/chapter1/10.mdx | 22 +- chapters/fr/chapter1/2.mdx | 12 +- chapters/fr/chapter1/3.mdx | 66 +- chapters/fr/chapter1/4.mdx | 16 +- chapters/fr/chapter1/8.mdx | 6 +- chapters/fr/chapter2/1.mdx | 48 +- chapters/fr/chapter2/2.mdx | 23 +- chapters/fr/chapter2/3.mdx | 462 +++---- chapters/fr/chapter2/4.mdx | 506 +++---- chapters/fr/chapter2/5.mdx | 17 +- chapters/fr/chapter2/6.mdx | 16 +- chapters/fr/chapter2/7.mdx | 24 +- chapters/fr/chapter2/8.mdx | 614 ++++----- chapters/fr/chapter3/2.mdx | 22 +- chapters/fr/chapter3/3.mdx | 8 +- chapters/fr/chapter3/3_tf.mdx | 380 ++--- chapters/fr/chapter3/4.mdx | 2 +- chapters/fr/chapter3/5.mdx | 2 +- chapters/fr/chapter3/6.mdx | 22 +- chapters/fr/chapter4/1.mdx | 34 +- chapters/fr/chapter4/2.mdx | 194 +-- chapters/fr/chapter4/3.mdx | 8 +- chapters/fr/chapter4/4.mdx | 4 +- chapters/fr/chapter4/5.mdx | 14 +- chapters/fr/chapter4/6.mdx | 446 +++--- chapters/fr/chapter5/1.mdx | 34 +- chapters/fr/chapter5/2.mdx | 332 ++--- chapters/fr/chapter5/3.mdx | 1495 ++++++++++---------- chapters/fr/chapter5/4.mdx | 592 ++++---- chapters/fr/chapter5/5.mdx | 934 ++++++------- chapters/fr/chapter5/6.mdx | 1060 +++++++------- chapters/fr/chapter5/7.mdx | 20 +- chapters/fr/chapter5/8.mdx | 452 +++--- chapters/fr/chapter6/1.mdx | 6 +- chapters/fr/chapter6/10.mdx | 112 +- chapters/fr/chapter6/2.mdx | 62 +- chapters/fr/chapter6/3.mdx | 84 +- chapters/fr/chapter6/3b.mdx | 71 +- chapters/fr/chapter6/4.mdx | 44 +- chapters/fr/chapter6/5.mdx | 64 +- chapters/fr/chapter6/6.mdx | 56 +- chapters/fr/chapter6/7.mdx | 76 +- chapters/fr/chapter6/8.mdx | 1132 +++++++-------- chapters/fr/chapter6/9.mdx | 4 +- chapters/fr/chapter7/2.mdx | 1962 +++++++++++++------------- chapters/fr/chapter7/4.mdx | 1998 +++++++++++++-------------- chapters/fr/chapter7/5.mdx | 2130 ++++++++++++++-------------- chapters/fr/chapter7/7.mdx | 2456 ++++++++++++++++----------------- chapters/fr/chapter8/1.mdx | 8 +- chapters/fr/chapter8/2.mdx | 84 +- chapters/fr/chapter8/3.mdx | 114 +- chapters/fr/chapter8/4.mdx | 119 +- chapters/fr/chapter8/4_tf.mdx | 105 +- chapters/fr/chapter8/5.mdx | 43 +- chapters/fr/chapter8/7.mdx | 27 +- chapters/fr/chapter9/1.mdx | 32 + chapters/fr/chapter9/2.mdx | 110 ++ chapters/fr/chapter9/3.mdx | 160 +++ chapters/fr/chapter9/4.mdx | 140 ++ chapters/fr/chapter9/5.mdx | 66 + chapters/fr/chapter9/6.mdx | 132 ++ chapters/fr/chapter9/7.mdx | 233 ++++ chapters/fr/chapter9/8.mdx | 234 ++++ chapters/fr/event/1.mdx | 340 ++--- chapters/th/_toctree.yml | 6 + chapters/th/chapter6/1.mdx | 21 + chapters/th/chapter6/2.mdx | 313 +++++ chapters/tr/_toctree.yml | 3 + chapters/tr/chapter1/6.mdx | 16 + 88 files changed, 11843 insertions(+), 9754 deletions(-) create mode 100644 chapters/bn/chapter2/1.mdx create mode 100644 chapters/es/chapter3/1.mdx create mode 100644 chapters/es/chapter3/2.mdx create mode 100644 chapters/fa/chapter1/2.mdx create mode 100644 chapters/fr/chapter9/1.mdx create mode 100644 chapters/fr/chapter9/2.mdx create mode 100644 chapters/fr/chapter9/3.mdx create mode 100644 chapters/fr/chapter9/4.mdx create mode 100644 chapters/fr/chapter9/5.mdx create mode 100644 chapters/fr/chapter9/6.mdx create mode 100644 chapters/fr/chapter9/7.mdx create mode 100644 chapters/fr/chapter9/8.mdx create mode 100644 chapters/th/chapter6/1.mdx create mode 100644 chapters/th/chapter6/2.mdx create mode 100644 chapters/tr/chapter1/6.mdx diff --git a/README.md b/README.md index fe404de8e..d8eea1a28 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ This repo contains the content that's used to create the **[Hugging Face course] | [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) | | [Spanish](https://huggingface.co/course/es/chapter1/1) (WIP) | [`chapters/es`](https://github.com/huggingface/course/tree/main/chapters/es) | [@camartinezbu](https://github.com/camartinezbu), [@munozariasjm](https://github.com/munozariasjm), [@fordaz](https://github.com/fordaz) | | [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) | ### Translating the course into your language diff --git a/chapters/bn/_toctree.yml b/chapters/bn/_toctree.yml index f7a43f15f..30d73183a 100644 --- a/chapters/bn/_toctree.yml +++ b/chapters/bn/_toctree.yml @@ -6,4 +6,9 @@ - title: 1. ট্রান্সফরমার মডেল sections: - local: chapter1/1 + title: ভূমিকা + +- title: 2. 🤗Transformers এর ব্যবহার + sections: + - local: chapter2/1 title: ভূমিকা \ No newline at end of file diff --git a/chapters/bn/chapter2/1.mdx b/chapters/bn/chapter2/1.mdx new file mode 100644 index 000000000..3fbb16aa2 --- /dev/null +++ b/chapters/bn/chapter2/1.mdx @@ -0,0 +1,20 @@ +# ভূমিকা + + [অধ্যায় ১](/course/bn/chapter1) এ আমরা দেখে এসেছি যে Transformer মডেলগুলো সাধারণত অনেক বড় হয়। লাখ-লাখ কোটি-কোটি প্যারামিটার সম্বলিত এই মডেল গুলো কে ট্রেনিং এবং ডেপ্লয় করা বেশ জটিল ও কষ্টসাধ্য একটা কাজ। তাছাড়াও প্রায় প্রতিদিনই নতুন নতুন মডেল রিলিজ হচ্ছে এবং সবগুলোরই নিজস্ব বাস্তবায়ন রয়েছে। এই সবকিছু একসাথে এপ্লাই করা খুব সহজ একটা কাজ নয়। + +এই 🤗 Transformers লাইব্রেরিটা বানানো হয়েছে এই সমস্যাগুলো সমাধান করার জন্য। এর আসল উদ্দেশ্য হলো এমন একটি API প্রদান করা যার মাধ্যমে যেকোনো Transformer মডেলকে লোড করা, ট্রেইন করা কিংবা সেভ করা যাবে। লাইব্রেরিটির আসল ফিচারগুলো হলঃ + +- **সহজে ব্যবহারযোগ্য**: ডাউনলোড করা, লোড করা এবং যেকোন state-of-the-art মডেল দিয়ে inference করা যাবে মাত্র দুই লাইনের কোড দিয়ে। +- **ফ্লেক্সিবিলিটি**: সবগুলো Transformer মডেলই আসলে PyTorch `nn.Module` অথবা TensorFlow `tf.keras.Model` ক্লাস , আর অন্য যেকোনো মডেলের মতোই এদেরকে তাদের নিজ নিজ মেশিন লার্নিং ফ্রেমওয়ার্ক এ সহজেই পরিচালনা করা যায়। + +- **সরলতা**: লাইব্রেরি জুড়ে খুব কমই বিমূর্ততা তৈরি করা হয়। "All in one file" এমন একটি ধারণাঃ একটা মডেলের পুরো Forward Pass কে সম্পূর্ণরূপে একটি সিঙ্গেল ফাইলে নিয়ে আসা হয়েছে, যাতে করে কোডটি সহজেই বুঝা ও মডিফাই করা যায়। + +এই শেষ বৈশিষ্ট্যটি(সরলতা) 🤗 ট্রান্সফরমারকে অন্যান্য ML লাইব্রেরি থেকে বেশ আলাদা করে তোলে। এখানে মডেলগুলি কোনো মডিউল এর উপর নির্মিত নয় যেগুলো ফাইল জুড়ে শেয়ার্ড অবস্থায় থাকে; বরংচ, প্রতিটি মডেলের নিজস্ব স্তর(Layer)রয়েছে। মডেলগুলিকে আরও সহজলভ্য এবং বোধগম্য করার পাশাপাশি, 🤗 Transformers আপনাকে অন্য মডেলকে প্রভাবিত না করে সহজেই একটি মডেলে নিয়ে এক্সপেরিমেন্ট করতে দেয়৷ + +এই অধ্যায়টি একটি পূর্নাঙ্গ উদাহরন দিয়ে শুরু হবে, যেখানে [অধ্যায় ১](/course/bn/chapter1) এ উল্লিখিত `pipeline()` ফাংশনটি প্রতিলিপি করতে আমরা একটি মডেল এবং একটি টোকেনাইজার একসাথে ব্যবহার করব। এর পরে, আমরা মডেল API নিয়ে আলোচনা করব: আমরা মডেল এবং কনফিগারেশন ক্লাসগুলির খুঁটিনাটি দেখব এবং আপনাকে দেখাব কীভাবে একটি মডেল লোড করতে হয় এবং কীভাবে এটি সংখ্যাসূচক ইনপুটগুলিকে প্রক্রিয়া করে আউটপুট প্রেডিক্ট করা যায়। + +তারপরে আমরা টোকেনাইজার API দেখব, যা `pipeline()` ফাংশনের অন্য একটি প্রধান উপাদান। টোকেনাইজার জিনিসটা প্রথম ও শেষ প্রসেসিং স্টেপগুলোতে মেইনলি কাজে লাগে, নিউরাল নেটওয়ার্কের জন্য টেক্সট ডাটা থেকে সংখ্যাসূচক ইনপুটে রূপান্তর এবং পরে আবার প্রয়োজন অনুযায়ী সংখ্যাসূচক ডাটা থেকে টেক্সট ডাটাতে রূপান্তর করার সময়। পরিশেষে, আমরা আপনাকে দেখাব কিভাবে ব্যাচের মাধ্যমে একাধিক বাক্যকে একটি মডেলে পাঠানো যায়। তারপরে আরেকবার হাই-লেভেলে `tokenizer()` ফাংশনটিকে একনজরে দেখার মাধ্যমে পুরো অধ্যায়ের ইতি টানব। + + +⚠️ Model Hub এবং 🤗 Transformers এর সাথে উপলব্ধ সমস্ত বৈশিষ্ট্যগুলি থেকে উপকৃত হওয়ার জন্য, আমরা সাজেস্ট করি এখানে একটি একাউন্ট তৈরি করার জন্যে।. + \ No newline at end of file diff --git a/chapters/en/chapter3/3.mdx b/chapters/en/chapter3/3.mdx index 40cec5e78..fb1665370 100644 --- a/chapters/en/chapter3/3.mdx +++ b/chapters/en/chapter3/3.mdx @@ -162,7 +162,7 @@ This time, it will report the validation loss and metrics at the end of each epo The `Trainer` will work out of the box on multiple GPUs or TPUs and provides lots of options, like mixed-precision training (use `fp16 = True` in your training arguments). We will go over everything it supports in Chapter 10. -This concludes the introduction to fine-tuning using the `Trainer` API. An example of doing this for most common NLP tasks will be given in [Chapter 7](course/chapter7), but for now let's look at how to do the same thing in pure PyTorch. +This concludes the introduction to fine-tuning using the `Trainer` API. An example of doing this for most common NLP tasks will be given in [Chapter 7](/course/chapter7), but for now let's look at how to do the same thing in pure PyTorch. diff --git a/chapters/en/chapter3/3_tf.mdx b/chapters/en/chapter3/3_tf.mdx index 3c72b30fb..2252a9613 100644 --- a/chapters/en/chapter3/3_tf.mdx +++ b/chapters/en/chapter3/3_tf.mdx @@ -196,4 +196,4 @@ metric.compute(predictions=class_preds, references=raw_datasets["validation"]["l The exact results you get may vary, as the random initialization of the model head might change the metrics it achieved. Here, we can see our model has an accuracy of 85.78% on the validation set and an F1 score of 89.97. Those are the two metrics used to evaluate results on the MRPC dataset for the GLUE benchmark. The table in the [BERT paper](https://arxiv.org/pdf/1810.04805.pdf) reported an F1 score of 88.9 for the base model. That was the `uncased` model while we are currently using the `cased` model, which explains the better result. -This concludes the introduction to fine-tuning using the Keras API. An example of doing this for most common NLP tasks will be given in [Chapter 7](course/chapter7). If you would like to hone your skills on the Keras API, try to fine-tune a model on the GLUE SST-2 dataset, using the data processing you did in section 2. +This concludes the introduction to fine-tuning using the Keras API. An example of doing this for most common NLP tasks will be given in [Chapter 7](/course/chapter7). If you would like to hone your skills on the Keras API, try to fine-tune a model on the GLUE SST-2 dataset, using the data processing you did in section 2. diff --git a/chapters/en/chapter6/5.mdx b/chapters/en/chapter6/5.mdx index 5276a9628..a9f070b0e 100644 --- a/chapters/en/chapter6/5.mdx +++ b/chapters/en/chapter6/5.mdx @@ -113,7 +113,7 @@ First we need a corpus, so let's create a simple one with a few sentences: ```python corpus = [ - "This is the Hugging Face course.", + "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.", diff --git a/chapters/en/chapter6/6.mdx b/chapters/en/chapter6/6.mdx index 36a0c4df5..d4152cd5e 100644 --- a/chapters/en/chapter6/6.mdx +++ b/chapters/en/chapter6/6.mdx @@ -106,7 +106,7 @@ We will use the same corpus as in the BPE example: ```python corpus = [ - "This is the Hugging Face course.", + "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.", @@ -307,7 +307,7 @@ 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', '##fu', 'Fa', 'Fac', '##ct', '##ful', '##full', '##fully', + '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'] ``` diff --git a/chapters/en/chapter6/7.mdx b/chapters/en/chapter6/7.mdx index a8c51e935..e23d6f473 100644 --- a/chapters/en/chapter6/7.mdx +++ b/chapters/en/chapter6/7.mdx @@ -157,7 +157,7 @@ We will use the same corpus as before as an example: ```python corpus = [ - "This is the Hugging Face course.", + "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.", diff --git a/chapters/en/chapter7/6.mdx b/chapters/en/chapter7/6.mdx index 927c2be9f..27dc8cbbd 100644 --- a/chapters/en/chapter7/6.mdx +++ b/chapters/en/chapter7/6.mdx @@ -67,6 +67,11 @@ False True We can use this to create a function that will stream the dataset and filter the elements we want: ```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 @@ -105,7 +110,7 @@ Filtering the full dataset can take 2-3h depending on your machine and bandwidth 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="train") +ds_valid = load_dataset("huggingface-course/codeparrot-ds-valid", split="validation") raw_datasets = DatasetDict( { @@ -347,7 +352,7 @@ data_collator = DataCollatorForLanguageModeling(tokenizer, mlm=False, return_ten Let's have a look at an example: ```py -out = data_collator([tokenized_dataset["train"][i] for i in range(5)]) +out = data_collator([tokenized_datasets["train"][i] for i in range(5)]) for key in out: print(f"{key} shape: {out[key].shape}") ``` @@ -799,6 +804,8 @@ model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( Now that we have sent our `train_dataloader` to `accelerator.prepare()`, we can use its length to compute the number of training steps. Remember that we should always do this after preparing the dataloader, as that method will change its length. We use a classic linear schedule from the learning rate to 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 @@ -856,7 +863,7 @@ model.train() completed_steps = 0 for epoch in range(num_train_epochs): for step, batch in tqdm( - enumerate(train_dataloader, start=1), total=len(train_dataloader) + 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) diff --git a/chapters/en/chapter7/7.mdx b/chapters/en/chapter7/7.mdx index 756500fa7..d8e1942e4 100644 --- a/chapters/en/chapter7/7.mdx +++ b/chapters/en/chapter7/7.mdx @@ -955,7 +955,7 @@ Note that while the training happens, each time the model is saved (here, every Once the training is complete, we can finally evaluate our model (and pray we didn't spend all that compute time on nothing). The `predict()` method of the `Trainer` will return a tuple where the first elements will be the predictions of the model (here a pair with the start and end logits). We send this to our `compute_metrics()` function: ```python -predictions, _ = trainer.predict(validation_dataset) +predictions, _, _ = trainer.predict(validation_dataset) start_logits, end_logits = predictions compute_metrics(start_logits, end_logits, validation_dataset, raw_datasets["validation"]) ``` diff --git a/chapters/en/event/1.mdx b/chapters/en/event/1.mdx index 4658da6ff..d202a9ceb 100644 --- a/chapters/en/event/1.mdx +++ b/chapters/en/event/1.mdx @@ -86,7 +86,7 @@ Jakob Uszkoreit is the co-founder of Inceptive. Inceptive designs RNA molecules -Lewis is a machine learning engineer at Hugging Face, focused on developing open-source tools and making them accessible to the wider community. He is also a co-author of an upcoming O’Reilly book on Transformers and you can follow him on Twitter (@_lewtun) for NLP tips and tricks! +Lewis is a machine learning engineer at Hugging Face, focused on developing open-source tools and making them accessible to the wider community. He is also a co-author of the O’Reilly book [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). You can follow him on Twitter (@_lewtun) for NLP tips and tricks! **Matthew Carrigan:** *New TensorFlow Features for 🤗 Transformers and 🤗 Datasets* @@ -162,4 +162,4 @@ Technology enthusiast, maker on my free time. I like challenges and solving prob -Philipp Schmid is a Machine Learning Engineer and Tech Lead at Hugging Face, where he leads the collaboration with the Amazon SageMaker team. He is passionate about democratizing and productionizing cutting-edge NLP models and improving the ease of use for Deep Learning. \ No newline at end of file +Philipp Schmid is a Machine Learning Engineer and Tech Lead at Hugging Face, where he leads the collaboration with the Amazon SageMaker team. He is passionate about democratizing and productionizing cutting-edge NLP models and improving the ease of use for Deep Learning. diff --git a/chapters/es/_toctree.yml b/chapters/es/_toctree.yml index 2cdc73523..4e4e1dbff 100644 --- a/chapters/es/_toctree.yml +++ b/chapters/es/_toctree.yml @@ -33,3 +33,10 @@ title: Tokenizadores - local: chapter2/5 title: Manejando Secuencias Múltiples + +- title: 3. Ajuste (fine-tuning) de un modelo preentrenado + sections: + - local: chapter3/1 + title: Introducción + - local: chapter3/2 + title: Procesamiento de los datos \ No newline at end of file diff --git a/chapters/es/chapter3/1.mdx b/chapters/es/chapter3/1.mdx new file mode 100644 index 000000000..16ae3c5d2 --- /dev/null +++ b/chapters/es/chapter3/1.mdx @@ -0,0 +1,21 @@ + + +# Introducción + +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 ? + +{#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. + +{: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. + +{/if} + +Para subir tus puntos de control (*checkpoints*) en el Hub de Hugging Face, necesitas una cuenta en huggingface.co: [crea una cuenta](https://huggingface.co/join) \ No newline at end of file diff --git a/chapters/es/chapter3/2.mdx b/chapters/es/chapter3/2.mdx new file mode 100644 index 000000000..3e7e3d91a --- /dev/null +++ b/chapters/es/chapter3/2.mdx @@ -0,0 +1,384 @@ + + +# Procesando los datos + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} +Continuando con el ejemplo del [capítulo anterior](/course/chapter2), aquí mostraremos como podríamos entrenar un clasificador de oraciones/sentencias en PyTorch.: + +```python +import torch +from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification + +# Same as before +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "This course is amazing!", +] +batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") + +# This is new +batch["labels"] = torch.tensor([1, 1]) + +optimizer = AdamW(model.parameters()) +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: + +```python +import tensorflow as tf +import numpy as np +from transformers import AutoTokenizer, TFAutoModelForSequenceClassification + +# Same as before +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "This course is amazing!", +] +batch = dict(tokenizer(sequences, padding=True, truncation=True, return_tensors="tf")) + +# This is new +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. + +En esta sección usaremos como ejemplo el conjunto de datos MRPC (Cuerpo de paráfrasis de investigaciones de Microsoft), que fue presentado en el [artículo](https://www.aclweb.org/anthology/I05-5002.pdf) de William B. Dolan and Chris Brockett. El conjunto de datos consiste en 5,801 pares of oraciones, con una etiqueta que indica si son paráfrasis o no. (es decir, si ambas oraciones significan lo mismo). Hemos seleccionado el mismo para este capítulo porque es un conjunto de datos pequeño que facilita la experimentación y entrenamiento sobre él. + +### 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. + +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: + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("glue", "mrpc") +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 408 + }) + test: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 1725 + }) +}) +``` + +Como puedes ver, obtenemos un objeto `DatasetDict` que contiene los conjuntos de datos de entrenamiento, de validación y de pruebas. Cada uno de estos contiene varias columnas (`sentence1`, `sentence2`, `label`, and `idx`) y un número variable de filas, que son el número de elementos en cada conjunto (asi, que hay 3,668 pares de oraciones en el conjunto de entrenamiento, 408 en el de validación, y 1,725 en el pruebas) + +Este comando descarga y almacena el conjunto de datos, por defecto en *~/.cache/huggingface/dataset*. Recuerda del Capítulo 2 que puedes personalizar tu carpeta mediante la configuración de la variable de entorno `HF_HOME`. + +Podemos acceder a cada par de oraciones en nuestro objeto `raw_datasets` usando indexación, como con un diccionario. + +```py +raw_train_dataset = raw_datasets["train"] +raw_train_dataset[0] +``` + +```python out +{'idx': 0, + 'label': 1, + 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', + 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} +``` + +Podemos ver que las etiquetas ya son números enteros, así que no es necesario hacer ningún preprocesamiento. Para saber cual valor corresponde con cual etiqueta, podemos inspeccionar el atributo `features` de nuestro `raw_train_dataset`. Esto indicara el tipo dato de cada columna: + +```py +raw_train_dataset.features +``` + +```python out +{'sentence1': Value(dtype='string', id=None), + 'sentence2': Value(dtype='string', id=None), + 'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None), + 'idx': Value(dtype='int32', id=None)} +``` + +Internamente, `label` es del tipo de dato `ClassLabel`, y la asociación de valores enteros y sus etiquetas esta almacenado en la carpeta *names*. `0` corresponde con `not_equivalent`, y `1` corresponde con `equivalent`. + + + +✏️ **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} + +{/if} + +Para preprocesar el conjunto de datos, necesitamos convertir el texto en números que puedan ser entendidos por el modelo. Como viste en el [capítulo anterior](/course/chapter2), esto se hace con el tokenizador. Podemos darle al tokenizador una oración o una lista de oraciones, así podemos tokenizar directamente todas las primeras y las segundas oraciones de cada par de la siguiente manera: + +```py +from transformers import AutoTokenizer + +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"]) +tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"]) +``` + +Sin embargo, no podemos simplemente pasar dos secuencias al modelo y obtener una predicción indicando si estas son paráfrasis o no. Necesitamos manipular las dos secuencias como un par y aplicar el preprocesamiento apropiado. +Afortunadamente, el tokenizador puede recibir también un par de oraciones y preparar las misma de una forma que nuestro modelo BERT espera: + +```py +inputs = tokenizer("This is the first sentence.", "This is the second one.") +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] +} +``` + +Nosotros consideramos las llaves `input_ids` y `attention_mask` en el [Capítulo 2](/course/chapter2), pero postergamos hablar sobre la llave `token_type_ids`. En este ejemplo, esta es la que le dice al modelo cual parte de la entrada es la primera oración y cual es la segunda. + + + +✏️ **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? + + + + +Si convertimos los IDs dentro de `input_ids` en palabras: + +```py +tokenizer.convert_ids_to_tokens(inputs["input_ids"]) +``` + +obtendremos: + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +``` + +De esta manera vemos que el modelo espera las entradas de la siguiente forma `[CLS] sentence1 [SEP] sentence2 [SEP]` cuando hay dos oraciones. Alineando esto con los `token_type_ids` obtenemos: + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +[ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] +``` + +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. + +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. + +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. + +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. + +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: + +```py +tokenized_dataset = tokenizer( + raw_datasets["train"]["sentence1"], + raw_datasets["train"]["sentence2"], + padding=True, + truncation=True, +) +``` + +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: + +```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. + +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. + +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. + +```py +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. + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 408 + }) + test: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 1725 + }) +}) +``` + +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. + +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()`. + +Lo último que necesitamos hacer es rellenar todos los elementos hasta la longitud del elemento más largo al momento de agrupar los elementos - a esta técnica la llamamos *relleno dinámico*. + +### Relleno Dinámico + + + +{#if fw === 'pt'} +La función responsable de juntar los elementos dentro de un lote es llamada *función de cotejo*. Esta es un argumento que puedes pasar cuando construyes un `DataLoader`, cuya función por defecto convierte tus elementos a tensores PyTorch y los concatena (recursivamente si los elementos son listas, tuplas o diccionarios). Esto no será posible en nuestro caso debido a que las entradas que tenemos no tienen el mismo tamaño. Hemos pospuesto el relleno, para aplicarlo sólo cuando se necesita en cada lote y evitar tener entradas muy largas con mucho relleno. Esto va a acelerar el entrenamiento significativamente, pero nótese que esto puede causar problemas si estás entrenando en un TPU - Los TPUs prefieren tamaños fijos, aún cuando requieran relleno adicional. + +{:else} + +La función responsable de juntar los elementos dentro de un lote es llamada *función de cotejo*. Esta es un argumento que puedes pasar cuando construyes un `DataLoader`, cuya función por defecto convierte tus elementos a un tf.Tensor y los concatena (recursivamente si los elementos son listas, tuplas o diccionarios). Esto no será posible en nuestro caso debido a que las entradas que tenemos no tienen el mismo tamaño. Hemos pospuesto el relleno, para aplicarlo sólo cuando se necesita en cada lote y evitar tener entradas muy largas con mucho relleno. Esto va a acelerar el entrenamiento significativamente, pero nótese que esto puede causar problemas si estás entrenando en un TPU - Los TPUs prefieren tamaños fijos, aún cuando requieran relleno adicional. + +{/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: + +{#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. + +```py +samples = tokenized_datasets["train"][:8] +samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]} +[len(x) for x in samples["input_ids"]] +``` + +```python out +[50, 59, 47, 67, 59, 50, 62, 32] +``` + +Como era de esperarse, obtenemos elementos de longitud variable, desde 32 hasta 67. El relleno dinámico significa que los elementos en este lote deben ser rellenos hasta una longitud de 67, que es la máxima longitud en el lote. Sin relleno dinámico, todos los elementos tendrían que haber sido rellenos hasta el máximo de todo el conjunto de datos, o el máximo aceptado por el modelo. Verifiquemos que nuestro `data_collator` esta rellenando dinámicamente el lote de la manera apropiada: + +```py +batch = data_collator(samples) +{k: v.shape for k, v in batch.items()} +``` + +{#if fw === 'tf'} + +```python out +{'attention_mask': TensorShape([8, 67]), + 'input_ids': TensorShape([8, 67]), + 'token_type_ids': TensorShape([8, 67]), + 'labels': TensorShape([8])} +``` + +{:else} + +```python out +{'attention_mask': torch.Size([8, 67]), + 'input_ids': torch.Size([8, 67]), + 'token_type_ids': torch.Size([8, 67]), + '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! + +{/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. + + + +{#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. + + +```py +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, +) +``` + +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/fa/_toctree.yml b/chapters/fa/_toctree.yml index c2930f86f..d1f8b5d08 100644 --- a/chapters/fa/_toctree.yml +++ b/chapters/fa/_toctree.yml @@ -7,6 +7,8 @@ sections: - local: chapter1/1 title: مقدمه + - local: chapter1/2 + title: پردازش زبان طبیعی - title: ۲- بکارگیری ترنسفورمرهای هاگینگ‌فِیس sections: diff --git a/chapters/fa/chapter1/1.mdx b/chapters/fa/chapter1/1.mdx index 03e429b99..5b826a280 100644 --- a/chapters/fa/chapter1/1.mdx +++ b/chapters/fa/chapter1/1.mdx @@ -1,7 +1,67 @@
# مقدمه +## به دوره‌ آموزشی هاگینگ‌فِیس خوش آمدید -به دوره‌ی آموزشی هاگینگ‌فیس خوش آمدید! + + +در این دوره آموزشی، پردازش زبان طبیعی[^1] را با استفاده از کتابخانه‌های اکوسیستم [هاگینگ‌فِیس](https://huggingface.co/) یعنی [Transformers](https://github.com/huggingface/transformers), [Datasets](https://github.com/huggingface/datasets), [Tokenizers](https://github.com/huggingface/tokenizers), [Accelerate](https://github.com/huggingface/accelerate) و همچنین [هاب هاگینگ‌فِیس](https://huggingface.co/models) می‌آموزید. این دوره کاملا رایگان و بدون تبلیغات است. + +## در این دوره چه چیزهایی را می‌آموزیم؟ + +دید کلی کوتاه از مباحث این دوره آموزشی: + +
+دید کلی کوتاه از مباحث این دوره آموزشی + +
+ +- از فصل ۱ تا ۴ مقدمه‌ای از مباحث‌ پایه‌‌ای کتابخانه‌ی ترنسفورمرز هاگینگ‌فِیس ارائه می‌شود. در پایان این فصل، شما با شیوه‌ی عملکرد مدل‌های ترنسفومر آشنا می‌شوید و می‌آموزید که چگونه از یک مدل در [هاب هاگینگ‌فِیس](https://huggingface.co/models) استفاده کنید، آن را برای مجموعه داده خود کوک کنید و نتایج خود را در هاب به اشتراک بگذارید. +- در فصل‌های ۵ تا ۸، اصول پایه‌‌ی کتابخانه‌های Datasets و Tokenizers، پیش از آن که وارد مسائل کلاسیک NLP شویم،‌ آموزش داده می‌شوند. در پایان این فصول، قادر خواهید بود مسائل متداول NLP را به تنهایی حل کنید. +- فصل‌های ۹ تا ۱۲ به مباحث فراتر از NLP و استفاده از مدل‌های ترنسفورمر برای حل مسائل پردازش گفتار و بینایی ماشین می‌پردازند. در طی این مسیر، فرا می‌گیرید که چگونه مدلی جدید ساخته، نمونه اولیه از آن را عرضه کرده و برای محیط استقرار نرم‌افزار بهینه‌اش کنید. در پایان این فصل، آماده‌ی استفاده از ترنسفورمرهای هاگینگ‌فِیس برای (تقریبا) همه مسائل یادگیری ماشین خواهید بود. + +این دوره آموزشی: + +- به سطح خوبی از دانش پایتون نیاز دارد. +- بهتر است پس از یک دوره آموزشی آشنایی با یادگیری عمیق، مانند دوره آموزشی یادگیری عمیق عملی برای برنامه‌نویس‌ها از [fast.ai](https://www.fast.ai/) و یا یکی از دوره‌های ارائه شده توسط [DeepLearning.AI](https://www.deeplearning.ai/)، دنبال شود. +- نیازمند دانش پیشین [پایتورچ](https://pytorch.org/) یا [تِنسورفِلو](https://www.tensorflow.org/) نیست، با این حال آشنایی با هر کدام از آنها می‌تواند کمک‌کننده باشد. + +پس از اینکه این دوره آموزشی را به پایان رساندید، توصیه می‌کنیم نگاهی به [دوره آموزشی تخصصی پردازش زبان طبیعی](https://www.coursera.org/specializations/natural-language-processing) که توسط [DeepLearning.AI](https://www.deeplearning.ai/) ارائه شده است، بیاندازید. این دوره، بخش اعظمی از مدل‌های سنتی‌ NLP مانند دسته‌بندی‌کننده بیز ساده و LSTMها را شامل می‌شود که شناخت آن‌ها ارزشمند است. + +## ما چه کسانی هستیم؟ + +درباره نویسندگان: + +**متیو کاریگن**[^2] مهندس یادگیری ماشین در هاگینگ‌فِیس است. او در دوبلین ایرلند زندگی می‌کند و پیش‌تر بعنوان مهندس یادگیری ماشین در [Parse.ly](https://www.parse.ly/) مشغول به کار بوده است. او دوره‌ی تحقیقات پسادکترای خود را در کالج ترینیتی دوبلین به پایان رسانده است. به عقیده‌ی وی هوش جامع مصنوعی[^3] با افزایش مقیاس معماری‌های فعلی حاصل نخواهد شد، با این حال او امید بسیاری به جاودانگی انسان در قالب رباتی دارد. + +**لیسندره دبوت**[^4] مهندس یادگیری ماشین در هاگینگ‌فِیس است و از ابتدا، بر روی کتابخانه‌ی ترنفسورمرهای هاگینگ‌فِیس کار کرده است. هدف او دسترس‌پذیر کردن NLP برای همگان با توسعه ابزارهایی با API بسیار ساده است. + +**سیلوین گوجر**[^5] مهندس محقق در هاگینگ‌فِیس است و از هسته‌ی تیم مدیریت‌کنندگان کتابخانه‌ی ترنفسورمرهای هاگینگ‌فِیس محسوب می‌شود. او قبل‌تر مهندس محقق در fast.ai بود و [کتاب یادگیری عمیق عملی برای برنامه‌نویس‌ها](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/) با استفاده از [fast.ai](https://www.fast.ai/) و پایتورچ را با همکاری جرمی هاوارد[^6] نگاشته است. تمرکز اصلی تحقیقات وی بر دسترس‌پذیرتر کردن یادگیری عمیق است. او برای این کار از طراحی و پیش‌برد شیوه‌هایی استفاده می‌کند که امکان یادگیری سریع با منابع محدود را برای مدل‌ها پدید می‌آورد. + +**مروه نویان**[^7] توسعه‌ی دهنده در هاگینگ‌فِیس است و بر روی توسعه‌ی ابزارها و تولید محتوا برای آن‌ها کار می‌کند. هدف او دسترس‌پذیر کردن یادگیری ماشین برای همگان است. + +**لوسیله ساولنیر**[^8] مهندس یادگیری ماشین در هاگینگ‌فِیس است و بر روی توسعه و پشتیبانی از ابزارهای متن‌باز تمرکز دارد. وی همچنین بصورت فعالانه‌ای در بسیاری از پروژهای تحقیقاتی در حوزه پردازش زبان طبیعی، مانند یادگیری مشارکتی و بیگ‌ساینس مشارکت دارد. + +**لویس تونستال**[^9] مهندس یادگیری ماشین در هاگینگ‌فِیس است. تمرکز اصلی او توسعه‌ی ابزارهای متن باز و دسترس‌پذیر کردن آنها برای جامعه‌ی گسترده‌تری از کاربران است. او همچنین از نویسندگان [کتاب انتشارات اُریلی[^10] درباره‌ی ترنسفورمرها](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/) است. + +**لئاندرو ون ورا**[^11] مهندس یادگیری ماشین در تیم متن‌باز هاگینگ‌فِیس و از نویسندگان [کتاب انتشارات اُریلی درباره‌ی ترنسفورمرها](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/) است. وی تجربه‌ی چندین سال کار در صنعت را دارد. او با کار در تمام جنبه‌های یادگیری ماشین، پروژه‌های متن‌باز را از مرحله‌ی تحقیق به استقرار در صنایع می‌رساند. + +آماده‌ی ورود به این دوره هستید؟ در این فصل شما می‌آموزید که: + +- چگونه می‌توان از تابع pipeline() برای حل مسائل NLP مانند تولید متن و دسته‌بندی استفاده کرد. +- معماری ترنسفورمرها چگونه است. +- چگونه معماری‌های مختلف انکودر، دیکودر و انکودر-دیکودر را از یکدیگر تشخصی داد و کاربردهای آن‌ها در چیست. + +[^1]: Natural Language Processing (NLP) +[^2]: Matthew Carrigan +[^3]: Artificial General Intelligence (AGI) +[^4]: Lysandre Debut +[^5]: Sylvain Gugger +[^6]: Jeremy Howard +[^7]: Merve Noyan +[^8]: Lucile Saulnier +[^9]: Lewis Tunstall +[^10]: O'Reilly +[^11]: Leandro von Werra
diff --git a/chapters/fa/chapter1/2.mdx b/chapters/fa/chapter1/2.mdx new file mode 100644 index 000000000..3342d6e47 --- /dev/null +++ b/chapters/fa/chapter1/2.mdx @@ -0,0 +1,25 @@ +
+# پردازش زبان طبیعی + +قبل از اینکه به سراغ مدل‌های ترنسفومر برویم، بیایید نگاهی سریع بیاندازیم به اینکه پردازش زبان طبیعی[^1] چیست و چرا برای ما حائز اهمیت است. + +## NLP چیست؟ + +NLP زیرشاخه‌ای از زبان‌شناسی و یادگیری ماشین است که تمرکز آن بر درک همه‌ی جوانب زبان انسان‌ها است. هدف مسائل صرفا درک کلمات بصورت مجزا نیست، بلکه جمله، متن و در مجموع‌ زمینه‌ای است که آن کلمه در آن به کار رفته است. + +مسائل متداول NLP بهمراه برخی مثال‌های آن را در این لیست می‌بینید: + +- **دسته‌بندی جملات**: دریافت احساس نظر، تشخیص هرزنامه بودن یک ایمیل، تشخیص اینکه آیا یک جمله از لحاظ دستور زبانی صحیح است یا نه و اینکه آیا دو جمله منطقا به یکدیگر مرتبط هستند یا نه. +- **دسته‌بندی هر کلمه داخل یک جمله**:‌ تشخیص اجزای مختلف دستور زبان در یک جمله (اسم، فعل، صفت) و یا موجودیت‌های نامدار (شخص، موقعیت، سازمان). +- **تولید محتوای متنی**:‌ تکمیل یک پیام با متن تولید شده به صورت خودکار و یا تکمیل متنی که جاهای خالی دارد. +- **استخراج پاسخ از یک متن**: پاسخ به سوالات با استفاده از اطلاعاتی که در متن زمینه ارائه شده است. +- **تولید متن جدید از یک متن ارائه شده**: ترجمه‌ی متون به دیگر زبان‌ها، خلاصه‌سازی متون. + +با این حال NLP صرفا به متون نوشتاری محدود نمی‌شود و برای چالش‌های پیچیده‌ی بسیاری در مسائل تشخیص گفتار و بینایی ماشین راه‌حل ارائه می‌کند. برای نمونه می‌توان از تولید متن از یک فایل صوتی و یا تشریح یک تصویر، نام برد. + +## چرا این مبحث چالش‌برانگیز است؟ + +کامپیوترها اطلاعات را مانند انسان پردازش نمی‌کنند. برای مثال زمانی که ما جمله‌ای مانند من گرسنه هستم را می‌خوانیم، به سادگی معنای آن را متوجه می‌شویم. همچنین زمانی که دو جمله‌ مانند من گرسنه هستم و من ناراحت هستم را می‌خوانیم، بسادگی می‌توانیم تشخیص دهیم که به چه میزان این دو جمله با یکدیگر تشابه دارند. برای مدل‌های یادگیری ماشین، چنین مسائلی به مراتب سخت‌تر است. متن باید به ‌شیوه‌ای پردازش شود که به مدل امکان یادگیری از آن را بدهد. و با توجه به اینکه زبان پیچیده است، باید در پیاده‌سازی این مدل‌ها بسیار دقت کنیم. تحقیقات بسیاری انجام شده است تا نشان دهند چگونه می‌توان متن را در کامپیوترها مدل کرد. در فصل بعدی به برخی از این شیوه‌ها نگاهی میاندازیم. + +[^1]: Natural Language Processing (NLP) +
\ No newline at end of file diff --git a/chapters/fr/_toctree.yml b/chapters/fr/_toctree.yml index 517e7c8b2..8f4b86150 100644 --- a/chapters/fr/_toctree.yml +++ b/chapters/fr/_toctree.yml @@ -1,173 +1,196 @@ -- title: 0. Configuration - sections: - - local: chapter0/1 - title: Introduction - -- title: 1. Les transformers - sections: - - local: chapter1/1 - title: Introduction - - local: chapter1/2 - title: Traitement du langage naturel - - local: chapter1/3 - title: Que peut-on faire avec les transformers ? - - local: chapter1/4 - title: Comment fonctionnent les transformers ? - - local: chapter1/5 - title: Les modèles encodeur - - local: chapter1/6 - title: Les modèles décodeur - - local: chapter1/7 - title: Les modèles de séquence-à-séquence - - local: chapter1/8 - title: Biais et limitations - - local: chapter1/9 - title: Résumé - - local: chapter1/10 - title: Quiz de fin de chapitre - quiz: 1 - -- title: 2. Utilisation de 🤗 Transformers - sections: - - local: chapter2/1 - title: Introduction - - local: chapter2/2 - title: Derrière le pipeline - - local: chapter2/3 - title: Modèles - - local: chapter2/4 - title: Tokenizers - - local: chapter2/5 - title: Manipulation de plusieurs séquences - - local: chapter2/6 - title: Tout assembler - - local: chapter2/7 - title: Utilisation de base terminée ! - - local: chapter2/8 - title: Quiz de fin de chapitre - quiz: 2 - -- title: 3. Finetuner un modèle pré-entraîné - sections: - - local: chapter3/1 - title: Introduction - - local: chapter3/2 - title: Traîter les données - - local: chapter3/3 - title: Finetuner un modèle avec l'API Trainer API ou Keras - local_fw: { pt: chapter3/3, tf: chapter3/3_tf } - - local: chapter3/4 - title: Un entraînement complet - - local: chapter3/5 - title: Finetuning, vérifié ! - - local: chapter3/6 - title: Quiz de fin de chapitre - quiz: 3 - -- title: 4. Partager des modèles et des tokenizers - sections: - - local: chapter4/1 - title: Le Hub d'Hugging Face - - local: chapter4/2 - title: Utilisation de modèles pré-entraînés - - local: chapter4/3 - title: Partager des modèles pré-entraînés - - local: chapter4/4 - title: Créer une carte de modèle - - local: chapter4/5 - title: Partie 1 terminée ! - - local: chapter4/6 - title: Quiz de fin de chapitre - quiz: 4 - -- title: 5. La bibliothèque 🤗 Datasets - sections: - - local: chapter5/1 - title: Introduction - - local: chapter5/2 - title: Que faire si mon jeu de données n'est pas sur le Hub ? - - local: chapter5/3 - title: Il est temps de trancher et de découper - - local: chapter5/4 - title: Données massives ? 🤗 Des jeux de données à la rescousse ! - - local: chapter5/5 - title: Création de votre propre jeu de données - - local: chapter5/6 - title: Recherche sémantique avec FAISS - - local: chapter5/7 - title: 🤗 Datasets, vérifié ! - - local: chapter5/8 - title: Quiz de fin de chapitre - quiz: 5 - -- title: 6. La bibliothèque 🤗 Tokenizer - sections: - - local: chapter6/1 - title: Introduction - - local: chapter6/2 - title: Entraîner un nouveau tokenizer à partir d'un ancien - - local: chapter6/3 - title: Les pouvoirs spéciaux des tokenizers rapides - - local: chapter6/3b - title: Les tokenizers rapides dans le pipeline de QA - - local: chapter6/4 - title: Normalisation et pré-tokénisation - - local: chapter6/5 - title: Le tokenizer Byte-Pair Encoding - - local: chapter6/6 - title: Le tokenizer WordPiece - - local: chapter6/7 - title: Le tokenizer Unigram - - local: chapter6/8 - title: Construction d'un tokenizer bloc par bloc - - local: chapter6/9 - title: 🤗 Tokenizers, vérifié ! - - local: chapter6/10 - title: Quiz de fin de chapitre - quiz: 6 - -- title: 7. Les principales tâches en NLP - sections: - - local: chapter7/1 - title: Introduction - - local: chapter7/2 - title: Classification de tokens - - local: chapter7/3 - title: Finetuner un modèle de langage masqué - - local: chapter7/4 - title: Traduction - - local: chapter7/5 - title: Résumé de textes - - local: chapter7/6 - title: Entraîner un modèle de langage causal à partir de zéro - - local: chapter7/7 - title: Réponse aux questions - - local: chapter7/8 - title: Maîtriser le NLP - - local: chapter7/9 - title: Quiz de fin de chapitre - quiz: 7 - -- title: 8. Comment demander de l'aide - sections: - - local: chapter8/1 - title: Introduction - - local: chapter8/2 - title: Que faire lorsque vous obtenez une erreur - - local: chapter8/3 - title: Demander de l'aide sur les forums - - local: chapter8/4 - title: Déboguer le pipeline d'entraînement - local_fw : { pt : chapter8/4, tf : chapter8/4_tf } - - local: chapter8/5 - title: Comment rédiger une bonne issue - - local: chapter8/6 - title: Partie 2 terminée ! - - local: chapter8/7 - title: Quiz de fin de chapitre - quiz: 8 - -- title: Evènements liés au cours d'Hugging Face - sections: - - local: event/1 - title: Événement de lancement de la partie 2 +- title: 0. Configuration + sections: + - local: chapter0/1 + title: Introduction + +- title: 1. Les transformers + sections: + - local: chapter1/1 + title: Introduction + - local: chapter1/2 + title: Traitement du langage naturel + - local: chapter1/3 + title: Que peut-on faire avec les transformers ? + - local: chapter1/4 + title: Comment fonctionnent les transformers ? + - local: chapter1/5 + title: Les modèles encodeur + - local: chapter1/6 + title: Les modèles décodeur + - local: chapter1/7 + title: Les modèles de séquence-à-séquence + - local: chapter1/8 + title: Biais et limitations + - local: chapter1/9 + title: Résumé + - local: chapter1/10 + title: Quiz de fin de chapitre + quiz: 1 + +- title: 2. Utilisation de 🤗 Transformers + sections: + - local: chapter2/1 + title: Introduction + - local: chapter2/2 + title: Derrière le pipeline + - local: chapter2/3 + title: Modèles + - local: chapter2/4 + title: Tokenizers + - local: chapter2/5 + title: Manipulation de plusieurs séquences + - local: chapter2/6 + title: Tout assembler + - local: chapter2/7 + title: Utilisation de base terminée ! + - local: chapter2/8 + title: Quiz de fin de chapitre + quiz: 2 + +- title: 3. Finetuner un modèle pré-entraîné + sections: + - local: chapter3/1 + title: Introduction + - local: chapter3/2 + title: Traîter les données + - local: chapter3/3 + title: Finetuner un modèle avec l'API Trainer API ou Keras + local_fw: { pt: chapter3/3, tf: chapter3/3_tf } + - local: chapter3/4 + title: Un entraînement complet + - local: chapter3/5 + title: Finetuning, coché ! + - local: chapter3/6 + title: Quiz de fin de chapitre + quiz: 3 + +- title: 4. Partager des modèles et des tokenizers + sections: + - local: chapter4/1 + title: Le Hub d'Hugging Face + - local: chapter4/2 + title: Utilisation de modèles pré-entraînés + - local: chapter4/3 + title: Partager des modèles pré-entraînés + - local: chapter4/4 + title: Créer une carte de modèle + - local: chapter4/5 + title: Partie 1 terminée ! + - local: chapter4/6 + title: Quiz de fin de chapitre + quiz: 4 + +- title: 5. La bibliothèque 🤗 Datasets + sections: + - local: chapter5/1 + title: Introduction + - local: chapter5/2 + title: Que faire si mon jeu de données n'est pas sur le Hub ? + - local: chapter5/3 + title: Il est temps de trancher et de découper + - local: chapter5/4 + title: Données massives ? 🤗 Des jeux de données à la rescousse ! + - local: chapter5/5 + title: Création de votre propre jeu de données + - local: chapter5/6 + title: Recherche sémantique avec FAISS + - local: chapter5/7 + title: 🤗 Datasets, coché ! + - local: chapter5/8 + title: Quiz de fin de chapitre + quiz: 5 + +- title: 6. La bibliothèque 🤗 Tokenizer + sections: + - local: chapter6/1 + title: Introduction + - local: chapter6/2 + title: Entraîner un nouveau tokenizer à partir d'un ancien + - local: chapter6/3 + title: Les pouvoirs spéciaux des tokenizers rapides + - local: chapter6/3b + title: Les tokenizers rapides dans le pipeline de QA + - local: chapter6/4 + title: Normalisation et pré-tokénisation + - local: chapter6/5 + title: Le tokenizer Byte-Pair Encoding + - local: chapter6/6 + title: Le tokenizer WordPiece + - local: chapter6/7 + title: Le tokenizer Unigram + - local: chapter6/8 + title: Construction d'un tokenizer bloc par bloc + - local: chapter6/9 + title: 🤗 Tokenizers, coché ! + - local: chapter6/10 + title: Quiz de fin de chapitre + quiz: 6 + +- title: 7. Les principales tâches en NLP + sections: + - local: chapter7/1 + title: Introduction + - local: chapter7/2 + title: Classification de tokens + - local: chapter7/3 + title: Finetuner un modèle de langage masqué + - local: chapter7/4 + title: Traduction + - local: chapter7/5 + title: Résumé de textes + - local: chapter7/6 + title: Entraîner un modèle de langage causal à partir de zéro + - local: chapter7/7 + title: Réponse aux questions + - local: chapter7/8 + title: Maîtriser le NLP + - local: chapter7/9 + title: Quiz de fin de chapitre + quiz: 7 + +- title: 8. Comment demander de l'aide + sections: + - local: chapter8/1 + title: Introduction + - local: chapter8/2 + title: Que faire lorsque vous obtenez une erreur + - local: chapter8/3 + title: Demander de l'aide sur les forums + - local: chapter8/4 + title: Déboguer le pipeline d'entraînement + local_fw : { pt : chapter8/4, tf : chapter8/4_tf } + - local: chapter8/5 + title: Comment rédiger une bonne issue + - local: chapter8/6 + title: Partie 2 terminée ! + - local: chapter8/7 + title: Quiz de fin de chapitre + quiz: 8 + +- local: chapter9 + title: 9. Construire et partager des démos + new: true + subtitle: J'ai entraîné un modèle, mais comment puis-je le montrer ? + sections: + - local: chapter9/1 + title: Introduction à Gradio + - local: chapter9/2 + title: Construire votre première démo + - local: chapter9/3 + title: Comprendre la classe Interface + - local: chapter9/4 + title: Partager ses démos avec les autres + - local: chapter9/5 + title: Intégrations avec le Hub et Spaces + - local: chapter9/6 + title: Fonctionnalités avancées d'Interface + - local: chapter9/7 + title: Introduction aux Blocks + - local: chapter9/8 + title: Quiz de fin de chapitre + quiz: 9 + +- title: Evènements liés au cours d'Hugging Face + sections: + - local: event/1 + title: Événement de lancement de la partie 2 diff --git a/chapters/fr/chapter0/1.mdx b/chapters/fr/chapter0/1.mdx index 74901802a..b3a5b78ba 100644 --- a/chapters/fr/chapter0/1.mdx +++ b/chapters/fr/chapter0/1.mdx @@ -1,110 +1,110 @@ -# Introduction - -Bienvenue au cours d'Hugging Face ! Cette introduction est là pour vous guider dans la mise en place d'un environnement de travail. Si vous venez de commencer le cours, nous vous recommandons de consulter d'abord le [Chapitre 1](/course/fr/chapter1) puis de revenir et de configurer votre environnement afin de pouvoir essayer le code vous-même. - -Toutes les bibliothèques que nous utiliserons dans ce cours sont disponibles sous forme de *packages* Python. Nous allons donc vous montrer comment configurer un environnement Python et installer les bibliothèques spécifiques dont vous aurez besoin. - -Nous aborderons deux façons de configurer votre environnement de travail : soit en utilisant un *notebook* Google Colab, soit en utilisant un environnement virtuel Python. N'hésitez pas à choisir celle qui vous convient le mieux. Pour les débutants, nous vous recommandons vivement de commencer en utilisant un *notebook* Google Colab. - -Notez que nous ne couvrirons pas le système Windows. Si vous travaillez sous Windows, nous vous recommandons de suivre le cours en utilisant un *notebook* Google Colab. Si vous utilisez une distribution Linux ou macOS, vous pouvez utiliser l'une des deux approches décrites ci-dessous. - -La plupart du cours repose sur le fait que vous ayez un compte Hugging Face. Si vous n'en disposez pas d'un, nous vous recommandons d'en créer un dès maintenant : [créer un compte](https://huggingface.co/join). - -## Utilisation d'un *notebook* Google Colab - -L'utilisation d'un *notebook* Google Colab est la configuration la plus simple possible. Démarrez un *notebook* dans votre navigateur et passez directement au codage ! - -Si vous n'êtes pas familier avec Colab, nous vous recommandons de commencer par suivre l'[introduction](https://colab.research.google.com/notebooks/intro.ipynb). Colab vous permet d'utiliser du matériel comme les GPUs ou les TPUs et est gratuit pour les petites charges de travail. - -Une fois que vous vous sentez suffisamment à l'aise avec Colab, créez un nouveau *notebook* et commencez à le configurer : - -
-An empty colab notebook -
- -L'étape suivante consiste à installer les bibliothèques que nous allons utiliser dans ce cours. Nous utiliserons `pip` pour l'installation qui est le gestionnaire de *packages* pour Python. Dans les *notebooks*, vous pouvez exécuter des commandes système en les faisant précéder du caractère `!`. Vous pouvez donc installer la bibliothèque 🤗 *Transformers* comme suit : - -``` -!pip install transformers -``` - -Vous pouvez vous assurer que le paquet a été correctement installé en l'important dans votre runtime Python : - -``` -import transformers -``` - -
-A gif showing the result of the two commands above: installation and import -
- -Cela installe une version très légère de 🤗 *Transformers*. En particulier, aucun *framework* d'apprentissage automatique spécifique (comme PyTorch ou TensorFlow) n'est installé. Comme nous utiliserons de nombreuses fonctionnalités différentes de la bibliothèque, nous recommandons d'installer la version de développement qui est livrée avec toutes les dépendances requises pour à peu près tous les cas d'utilisation imaginables : - -``` -!pip install transformers[sentencepiece] -``` - -Cela prendra un peu de temps, mais vous serez alors prêt pour le reste du cours ! - - -## Utilisation d'un environnement virtuel Python - -Si vous préférez utiliser un environnement virtuel Python, la première étape consiste à installer Python sur votre système. Nous vous recommandons de suivre [ce guide](https://realpython.com/installing-python/) pour commencer. - -Une fois Python installé, vous devriez être en mesure d'exécuter des commandes Python dans votre terminal. Vous pouvez commencer par exécuter la commande suivante pour vous assurer qu'il est correctement installé avant de passer aux étapes suivantes : `python --version`. Cette commande devrait vous indiquer la version de Python disponible sur votre système. - -Lorsque vous exécutez une commande Python dans votre terminal, comme `python --version`, vous devez considérer le programme qui exécute votre commande comme la fonction « main » Python sur votre système. Nous vous recommandons de garder cette installation principale libre de tout *package* et de l'utiliser pour créer des environnements séparés pour chaque application sur laquelle vous travaillez. De cette façon, chaque application peut avoir ses propres dépendances et *packages*, et vous n'aurez pas à vous soucier de problèmes potentiels de compatibilité avec d'autres applications. - -En Python, cela se fait avec les [*environnements virtuels*](https://docs.python.org/3/tutorial/venv.html), qui sont des arbres de répertoires autonomes contenant chacun une installation Python avec une version particulière de Python ainsi que tous les *packages* dont l'application a besoin. La création d'un tel environnement virtuel peut se faire à l'aide d'un certain nombre d'outils différents, mais nous utiliserons le *package* officiel de Python : [`venv`](https://docs.python.org/3/library/venv.html#module-venv). - -Tout d'abord, créez le répertoire dans lequel vous souhaitez que votre application se trouve. Par exemple, vous pouvez créer un nouveau répertoire appelé *transformers-course* à la racine de votre répertoire personnel : -``` -mkdir ~/transformers-course -cd ~/transformers-course -``` - -A l'intérieur de ce répertoire, créez un environnement virtuel en utilisant le module Python `venv` : - -``` -python -m venv .env -``` - -Vous devriez maintenant avoir un répertoire appelé *.env* dans votre dossier autrement vide : - -``` -ls -a -``` - -```out -. .. .env -``` - -Vous pouvez entrer et sortir de votre environnement virtuel avec les scripts `activate` et `deactivate` : - -``` -# Activate the virtual environment -source .env/bin/activate - -# Deactivate the virtual environment -source .env/bin/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 ! - -``` -which python -``` - -```out -/home//transformers-course/.env/bin/python -``` - -### Installation des dépendances - -Comme dans la section précédente sur l'utilisation des instances Google Colab, vous devez maintenant installer les *packages* requis pour continuer. Encore une fois, vous pouvez installer la version de développement de 🤗 *Transformers* à l'aide du gestionnaire de packages `pip` : - -``` -pip install "transformers[sentencepiece]" -``` - -Vous êtes maintenant prêt ! +# Introduction + +Bienvenue au cours d'Hugging Face ! Cette introduction est là pour vous guider dans la mise en place d'un environnement de travail. Si vous venez de commencer le cours, nous vous recommandons de consulter d'abord le [chapitre 1](/course/fr/chapter1) puis de revenir et de configurer votre environnement afin de pouvoir essayer le code vous-même. + +Toutes les bibliothèques que nous utiliserons dans ce cours sont disponibles sous forme de *packages* Python. Nous allons donc vous montrer comment configurer un environnement Python et installer les bibliothèques spécifiques dont vous aurez besoin. + +Nous aborderons deux façons de configurer votre environnement de travail : soit en utilisant un *notebook* Google Colab, soit en utilisant un environnement virtuel Python. N'hésitez pas à choisir celle qui vous convient le mieux. Pour les débutants, nous vous recommandons vivement de commencer en utilisant un *notebook* Google Colab. + +Notez que nous ne couvrirons pas le système Windows. Si vous travaillez sous Windows, nous vous recommandons de suivre le cours en utilisant un *notebook* Google Colab. Si vous utilisez une distribution Linux ou macOS, vous pouvez utiliser l'une des deux approches décrites ci-dessous. + +La plupart du cours repose sur le fait que vous ayez un compte Hugging Face. Si vous n'en disposez pas d'un, nous vous recommandons d'en créer un dès maintenant : [créer un compte](https://huggingface.co/join). + +## Utilisation d'un notebook Google Colab + +L'utilisation d'un *notebook* Google Colab est la configuration la plus simple possible. Démarrez un *notebook* dans votre navigateur et passez directement au codage ! + +Si vous n'êtes pas familier avec Colab, nous vous recommandons de commencer par suivre l'[introduction](https://colab.research.google.com/notebooks/intro.ipynb). Colab vous permet d'utiliser du matériel comme les GPUs ou les TPUs et est gratuit pour les petites charges de travail. + +Une fois que vous vous sentez suffisamment à l'aise avec Colab, créez un nouveau *notebook* et commencez à le configurer : + +
+An empty colab notebook +
+ +L'étape suivante consiste à installer les bibliothèques que nous allons utiliser dans ce cours. Nous utiliserons `pip` pour l'installation qui est le gestionnaire de *packages* pour Python. Dans les *notebooks*, vous pouvez exécuter des commandes système en les faisant précéder du caractère `!`. Vous pouvez donc installer la bibliothèque 🤗 *Transformers* comme suit : + +``` +!pip install transformers +``` + +Vous pouvez vous assurer que le paquet a été correctement installé en l'important dans votre runtime Python : + +``` +import transformers +``` + +
+A gif showing the result of the two commands above: installation and import +
+ +Cela installe une version très légère de 🤗 *Transformers*. En particulier, aucun *framework* d'apprentissage automatique spécifique (comme PyTorch ou TensorFlow) n'est installé. Comme nous utiliserons de nombreuses fonctionnalités différentes de la bibliothèque, nous recommandons d'installer la version de développement qui est livrée avec toutes les dépendances requises pour à peu près tous les cas d'utilisation imaginables : + +``` +!pip install transformers[sentencepiece] +``` + +Cela prendra un peu de temps, mais vous serez alors prêt pour le reste du cours ! + + +## Utilisation d'un environnement virtuel Python + +Si vous préférez utiliser un environnement virtuel Python, la première étape consiste à installer Python sur votre système. Nous vous recommandons de suivre [ce guide](https://realpython.com/installing-python/) pour commencer. + +Une fois Python installé, vous devriez être en mesure d'exécuter des commandes Python dans votre terminal. Vous pouvez commencer par exécuter la commande suivante pour vous assurer qu'il est correctement installé avant de passer aux étapes suivantes : `python --version`. Cette commande devrait vous indiquer la version de Python disponible sur votre système. + +Lorsque vous exécutez une commande Python dans votre terminal, comme `python --version`, vous devez considérer le programme qui exécute votre commande comme la fonction « main » Python sur votre système. Nous vous recommandons de garder cette installation principale libre de tout *package* et de l'utiliser pour créer des environnements séparés pour chaque application sur laquelle vous travaillez. De cette façon, chaque application peut avoir ses propres dépendances et *packages*, et vous n'aurez pas à vous soucier de problèmes potentiels de compatibilité avec d'autres applications. + +En Python, cela se fait avec les [*environnements virtuels*](https://docs.python.org/3/tutorial/venv.html), qui sont des arbres de répertoires autonomes contenant chacun une installation Python avec une version particulière de Python ainsi que tous les *packages* dont l'application a besoin. La création d'un tel environnement virtuel peut se faire à l'aide d'un certain nombre d'outils différents, mais nous utiliserons le *package* officiel de Python : [`venv`](https://docs.python.org/3/library/venv.html#module-venv). + +Tout d'abord, créez le répertoire dans lequel vous souhaitez que votre application se trouve. Par exemple, vous pouvez créer un nouveau répertoire appelé *transformers-course* à la racine de votre répertoire personnel : +``` +mkdir ~/transformers-course +cd ~/transformers-course +``` + +A l'intérieur de ce répertoire, créez un environnement virtuel en utilisant le module Python `venv` : + +``` +python -m venv .env +``` + +Vous devriez maintenant avoir un répertoire appelé *.env* dans votre dossier autrement vide : + +``` +ls -a +``` + +```out +. .. .env +``` + +Vous pouvez entrer et sortir de votre environnement virtuel avec les scripts `activate` et `deactivate` : + +``` +# Activate the virtual environment +source .env/bin/activate + +# Deactivate the virtual environment +source .env/bin/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 ! + +``` +which python +``` + +```out +/home//transformers-course/.env/bin/python +``` + +### Installation des dépendances + +Comme dans la section précédente sur l'utilisation des instances Google Colab, vous devez maintenant installer les *packages* requis pour continuer. Encore une fois, vous pouvez installer la version de développement de 🤗 *Transformers* à l'aide du gestionnaire de packages `pip` : + +``` +pip install "transformers[sentencepiece]" +``` + +Vous êtes maintenant prêt ! diff --git a/chapters/fr/chapter1/10.mdx b/chapters/fr/chapter1/10.mdx index 286a4a542..d48d06003 100644 --- a/chapters/fr/chapter1/10.mdx +++ b/chapters/fr/chapter1/10.mdx @@ -43,11 +43,11 @@ ner( choices={[ { text: "Il renvoie les scores de classification pour cette phrase, avec les labels \"positive\" ou \"negative\".", - explain: "C'est incorrect — cela correspondrait au pipeline d'analyse de sentiment (sentiment-analysis dans la documentation d'Hugging-Face)." + explain: "Cela correspondrait au pipeline d'analyse de sentiment (sentiment-analysis dans la documentation d'Hugging-Face)." }, { text: "Il renvoie un texte généré qui complète cette phrase.", - explain: "C'est incorrect. Cela correspondrait au pipeline de génération de texte (text-generation dans la documentation d'Hugging-Face)." + explain: "Cela correspondrait au pipeline de génération de texte (text-generation dans la documentation d'Hugging-Face)." }, { text: "Il renvoie les entités nommées dans cette phrase, telles que les personnes, les organisations ou lieux.", @@ -70,16 +70,16 @@ result = filler("...") choices={[ { text: "This <mask> has been waiting for you. # Ce <mask> vous attend.", - explain: "Ceci est incorrect. Regardez la description du modèle bert-base-cased et essayez de trouver votre erreur." + explain: "Regardez la description du modèle bert-base-cased et essayez de trouver votre erreur." }, { text: "This [MASK] has been waiting for you. # Ce [MASK] vous attend.", - explain: "Correct! Le modèle utilise [MASK] comme mot-masque.", + explain: "Le modèle utilise [MASK] comme mot-masque.", correct: true }, { text: "This man has been waiting for you. # Cet homme vous attend.", - explain: "Ceci est incorrect car ce pipeline permet de remplacer les mot manquants donc il a besoin d'un mot-masque." + explain: "Ce pipeline permet de remplacer les mot manquants donc il a besoin d'un mot-masque." } ]} /> @@ -99,12 +99,12 @@ result = classifier( choices={[ { text: "Ce pipeline nécessite que des étiquettes soient données pour classifier ce texte.", - explain: "Vrai. Le code doit inclure candidate_labels=[...].", + explain: "Le code doit inclure candidate_labels=[...].", correct: true }, { text: "Ce pipeline nécessite que des phrases soient données, pas juste une phrase.", - explain: "C'est incorrect, bien que ce pipeline puisse prendre une liste de phrases à traiter (comme tous les autres pipelines)." + explain: "Bien que ce pipeline puisse prendre une liste de phrases à traiter (comme tous les autres pipelines)." }, { text: "La bibliothèque 🤗 Transformers est cassée, comme d'habitude.", @@ -112,7 +112,7 @@ result = classifier( }, { text: "Ce pipeline nécessite des phrases plus longues, celle-ci est trop courte.", - explain: "C'est incorrect. Notez que si un texte est très long, il est tronqué par le pipeline." + explain: "Notez que si un texte est très long, il est tronqué par le pipeline." } ]} /> @@ -127,12 +127,12 @@ result = classifier( }, { text: "Transférer les connaissances d'un modèle pré-entraîné vers un nouveau modèle en initialisant ce second modèle avec les poids du premier.", - explain: "Correct. Quand le second modèle est entraîné sur une nouvelle tâche, il transfère les connaissances du premier modèle.", + explain: "Quand le second modèle est entraîné sur une nouvelle tâche, il transfère les connaissances du premier modèle.", correct: true }, { text: "Transférer les connaissances d'un modèle pré-entraîné vers un nouveau modèle en construisant le second modèle avec la même architecture que le premier.", - explain: "C'est incorrect, l'architecture correspond uniquement à la structure du modèle, pas à ses connaissances. Il n'y a donc pas de connaissances à transférer dans ce cas.", + explain: "L'architecture correspond uniquement à la structure du modèle, pas à ses connaissances. Il n'y a donc pas de connaissances à transférer dans ce cas.", } ]} /> @@ -144,7 +144,7 @@ result = classifier( choices={[ { text: "Vrai", - explain: "Correct, le pré-entraînement est autosupervisé, ce qui signifie que les étiquettes sont créées automatiquement à partir des données d'entrée (comme prédire le mot suivant ou remplacer des mots masqués).", + explain: "Le pré-entraînement est autosupervisé, ce qui signifie que les étiquettes sont créées automatiquement à partir des données d'entrée (comme prédire le mot suivant ou remplacer des mots masqués).", correct: true }, { diff --git a/chapters/fr/chapter1/2.mdx b/chapters/fr/chapter1/2.mdx index c93675ec0..ef5803d79 100644 --- a/chapters/fr/chapter1/2.mdx +++ b/chapters/fr/chapter1/2.mdx @@ -1,4 +1,4 @@ -# Traitement du langage naturel (NLP pour *Natural Language Processing*) +# Traitement du langage naturel (NLP pour Natural Language Processing) Avant de commencer avec les *transformers*, voyons succinctement ce qu'est le traitement du langage naturel et pourquoi il est important. @@ -8,11 +8,11 @@ Le traitement du langage naturel est un domaine de linguistique et d'apprentissa La liste suivante regroupe les tâches de NLP les plus courantes, avec pour chacune quelques exemples : -- **classification de phrases entières** : analyser le sentiment d'un avis, détecter si un email est un spam, déterminer si une phrase est grammaticalement correcte, déterminer si deux phrases sont logiquement reliées ou non, etc. -- **classification de chaque mot d'une phrase** : identifier les composants grammaticaux d'une phrase (nom, verbe, adjectif), identifier les entités nommées (personne, lieu, organisation), etc. -- **génération de texte** : compléter le début d'un texte avec un texte généré automatiquement, remplacer les mots manquants ou masqués dans un texte, etc. -- **extraction d'une réponse à partir d'un texte** : étant donné une question et un contexte extraire la réponse à la question en fonction des informations fournies par le contexte, etc. -- **génération de nouvelles phrases à partir d'un texte** : traduire un texte dans une autre langue, faire le résumé d'un texte, etc. +- **Classification de phrases entières** : analyser le sentiment d'un avis, détecter si un email est un spam, déterminer si une phrase est grammaticalement correcte, déterminer si deux phrases sont logiquement reliées ou non, etc. +- **Classification de chaque mot d'une phrase** : identifier les composants grammaticaux d'une phrase (nom, verbe, adjectif), identifier les entités nommées (personne, lieu, organisation), etc. +- **Génération de texte** : compléter le début d'un texte avec un texte généré automatiquement, remplacer les mots manquants ou masqués dans un texte, etc. +- **Extraction d'une réponse à partir d'un texte** : étant donné une question et un contexte extraire la réponse à la question en fonction des informations fournies par le contexte, etc. +- **Génération de nouvelles phrases à partir d'un texte** : traduire un texte dans une autre langue, faire le résumé d'un texte, etc. Le traitement du langage naturel ne se limite pas qu'à la compréhension du texte. Il s'intéresse aussi aux problèmes complexes de reconnaissance de la parole et de vision par ordinateur tels que la génération d'une transcription à partir d'un échantillon audio ou la description d'une image. diff --git a/chapters/fr/chapter1/3.mdx b/chapters/fr/chapter1/3.mdx index df63c9e50..beb4b7700 100644 --- a/chapters/fr/chapter1/3.mdx +++ b/chapters/fr/chapter1/3.mdx @@ -1,4 +1,4 @@ -# Que peuvent faire les *transformers* ? +# Que peuvent faire les transformers ? -👀 Vous voyez ce bouton Open in Colab en haut à droite ? Cliquez dessus pour ouvrir un *notebook* Colab avec tous les exemples de code de cette section. Ce bouton sera présent dans n'importe quelle section contenant des exemples de code. +👀 Vous voyez ce bouton Open in Colab en haut à droite ? Cliquez dessus pour ouvrir un notebook Colab avec tous les exemples de code de cette section. Ce bouton sera présent dans n'importe quelle section contenant des exemples de code. -Si vous souhaitez exécuter les codes en local, nous vous recommandons de jeter un œil au chapitre : configuration. +Si vous souhaitez exécuter les codes en local, nous vous recommandons de jeter un œil au chapitre configuration.
-## Les *transformers* sont partout ! +## Les transformers sont partout ! Les *transformers* sont utilisés pour résoudre toute sorte de tâches de NLP comme celles mentionnées dans la section précédente. Voici quelques-unes des entreprises et organisations qui utilisent Hugging Face, les *transformers* et qui contribuent aussi à la communauté en partageant leurs modèles : Companies using Hugging Face -La bibliothèque [🤗 *Transformers*](https://github.com/huggingface/transformers) fournit toutes les fonctionnalités nécessaires pour créer et utiliser les modèles partagés. Le [Model Hub](https://huggingface.co/models) contient des milliers de modèles pré-entraînés que n'importe qui peut télécharger et utiliser. Vous pouvez également transférer vos propres modèles vers le Hub ! +La bibliothèque [🤗 *Transformers*](https://github.com/huggingface/transformers) fournit toutes les fonctionnalités nécessaires pour créer et utiliser les modèles partagés. Le [*Hub*](https://huggingface.co/models) contient des milliers de modèles pré-entraînés que n'importe qui peut télécharger et utiliser. Vous pouvez également transférer vos propres modèles vers le Hub ! -⚠️ Le *Hub* n'est pas limité aux *transformers*. Tout le monde peut partager n'importe quel modèle ou jeu de données s'il le souhaite ! Créez un compte sur huggingface.co pour bénéficier de toutes les fonctionnalités disponibles ! + ⚠️ Le Hub n'est pas limité aux transformers. Tout le monde peut partager n'importe quel modèle ou jeu de données s'il le souhaite ! Créez un compte sur huggingface.co pour bénéficier de toutes les fonctionnalités disponibles ! Avant de découvrir en détail comment les *transformers* fonctionnent, nous allons voir quelques exemples de comment ils peuvent être utilisés pour résoudre des problèmes intéressants de NLP. @@ -77,7 +77,7 @@ Voici une liste non-exhaustive des [pipelines disponibles](https://huggingface.c - `feature-extraction` (pour obtenir la représentation vectorielle d'un texte) - `fill-mask` -- `ner` (*named entity recognition* → reconnaissance d'entités nommées) +- `ner` (*named entity recognition* ou reconnaissance d'entités nommées en français) - `question-answering` - `sentiment-analysis` - `summarization` @@ -87,7 +87,7 @@ Voici une liste non-exhaustive des [pipelines disponibles](https://huggingface.c Regardons de plus près certains d'entre eux ! -## *Zero-shot classification* +## Zero-shot classification Nous allons commencer par nous attaquer à une tâche plus difficile où nous devons classer des textes qui n'ont pas été annotés. C'est un scénario très répandu dans les projets réels car l'annotation de textes est généralement longue et nécessite parfois une expertise dans un domaine. Pour ce cas d'usage, le pipeline `zero-shot-classification` est très puissant : il vous permet de spécifier les labels à utiliser pour la classification, de sorte que vous n'ayez pas à vous soucier des labels du modèle pré-entraîné. Nous avons déjà vu comment le modèle peut classer un texte comme positif ou négatif en utilisant ces deux labels mais il peut également classer le texte en utilisant n'importe quel autre ensemble de labels que vous souhaitez. @@ -96,13 +96,15 @@ from transformers import pipeline classifier = pipeline("zero-shot-classification") classifier( - "This is a course about the Transformers library", # C'est un cours sur la bibliothèque Transformers + "This is a course about the Transformers library", + # C'est un cours sur la bibliothèque Transformers candidate_labels=["education", "politics", "business"], ) ``` ```python out -{'sequence': 'This is a course about the Transformers library', # C'est un cours sur la bibliothèque Transformers +{'sequence': 'This is a course about the Transformers library', +# C'est un cours sur la bibliothèque Transformers 'labels': ['education', 'business', 'politics'], 'scores': [0.8445963859558105, 0.111976258456707, 0.043427448719739914]} ``` @@ -130,10 +132,14 @@ generator( ``` ```python out -[{'generated_text': 'In this course, we will teach you how to understand and use ' # Dans ce cours, nous vous enseignerons comment comprendre et utiliser - 'data flow and data interchange when handling user data. We ' # flux de données et l'échange de données lors de la manipulation des données utilisateur. Nous - 'will be working with one or more of the most commonly used ' # travailleront avec un ou plusieurs des plus couramment utilisés - 'data flows — data flows of various types, as seen by the ' # flux de données - flux de données de différents types, tels qu'ils sont vus par +[{'generated_text': 'In this course, we will teach you how to understand and use ' + # Dans ce cours, nous vous enseignerons comment comprendre et utiliser + 'data flow and data interchange when handling user data. We ' + # flux de données et l'échange de données lors de la manipulation des données utilisateur. Nous + 'will be working with one or more of the most commonly used ' + # travailleront avec un ou plusieurs des plus couramment utilisés + 'data flows — data flows of various types, as seen by the ' + # flux de données - flux de données de différents types, tels qu'ils sont vus par 'HTTP'}] # HTTP ``` @@ -146,7 +152,7 @@ Il est possible de contrôler le nombre de séquences générées avec l'argumen
-## Utiliser n'importe quel modèle du *Hub* dans un pipeline +## Utiliser n'importe quel modèle du Hub dans un pipeline Les exemples précédents utilisaient le modèle par défaut pour la tâche en question mais vous pouvez aussi choisir un modèle particulier du *Hub* et l'utiliser dans un pipeline pour une tâche spécifique comme par exemple la génération de texte. Rendez-vous sur le [*Hub*](https://huggingface.co/models) et cliquez sur le *filtre* correspondant sur la gauche pour afficher seulement les modèles supportés pour cette tâche. Vous devriez arriver sur une page comme [celle-ci](https://huggingface.co/models?pipeline_tag=text-generation). @@ -199,11 +205,13 @@ unmasker("This course will teach you all about models.", top_k=2) ``` ```python out -[{'sequence': 'This course will teach you all about mathematical models.', # Ce cours vous apprendra tout sur les modèles mathématiques. +[{'sequence': 'This course will teach you all about mathematical models.', +# Ce cours vous apprendra tout sur les modèles mathématiques. 'score': 0.19619831442832947, 'token': 30412, 'token_str': ' mathematical'}, - {'sequence': 'This course will teach you all about computational models.', # Ce cours vous apprendra tout sur les modèles mathématiques. + {'sequence': 'This course will teach you all about computational models.', + # Ce cours vous apprendra tout sur les modèles mathématiques. 'score': 0.04052725434303284, 'token': 38163, 'token_str': ' computational'}] @@ -257,7 +265,8 @@ from transformers import pipeline question_answerer = pipeline("question-answering") question_answerer( question="Where do I work?", # Où est-ce que je travaille ? - context="My name is Sylvain and I work at Hugging Face in Brooklyn", # Je m'appelle Sylvain et je travaille à Hugging Face à Brooklyn. + context="My name is Sylvain and I work at Hugging Face in Brooklyn", + # Je m'appelle Sylvain et je travaille à Hugging Face à Brooklyn. ) ``` @@ -321,13 +330,20 @@ summarizer( ``` ```python out -[{'summary_text': ' America has changed dramatically during recent years . The ' # L'Amérique a changé de façon spectaculaire au cours des dernières années. Le - 'number of engineering graduates in the U.S. has declined in ' # nombre de diplômés en ingénierie aux États-Unis a diminué dans - 'traditional engineering disciplines such as mechanical, civil ' # dans les disciplines traditionnelles de l'ingénierie, telles que le génie mécanique, civil - ', electrical, chemical, and aeronautical engineering . Rapidly ' # l'électricité, la chimie et l'aéronautique. Les économies - 'developing economies such as China and India, as well as other ' # en développement rapide comme la Chine et l'Inde, ainsi que d'autres - 'industrial countries in Europe and Asia, continue to encourage ' # pays industriels d'Europe et d'Asie, continuent d'encourager - 'and advance engineering.'}] # et à faire progresser l'ingénierie. +[{'summary_text': ' America has changed dramatically during recent years . The ' + # L'Amérique a changé de façon spectaculaire au cours des dernières années. Le + 'number of engineering graduates in the U.S. has declined in ' + # nombre de diplômés en ingénierie aux États-Unis a diminué dans + 'traditional engineering disciplines such as mechanical, civil ' + # dans les disciplines traditionnelles de l'ingénierie, telles que le génie mécanique, civil + ', electrical, chemical, and aeronautical engineering . Rapidly ' + # l'électricité, la chimie et l'aéronautique. Les économies + 'developing economies such as China and India, as well as other ' + # en développement rapide comme la Chine et l'Inde, ainsi que d'autres + 'industrial countries in Europe and Asia, continue to encourage ' + # pays industriels d'Europe et d'Asie, continuent d'encourager + 'and advance engineering.'}] + # et à faire progresser l'ingénierie. ``` Comme pour la génération de texte, vous pouvez spécifier une `max_length` (longueur maximale) ou une `min_length` (longueur minimale) pour le résultat. diff --git a/chapters/fr/chapter1/4.mdx b/chapters/fr/chapter1/4.mdx index bf6527cec..dc996234e 100644 --- a/chapters/fr/chapter1/4.mdx +++ b/chapters/fr/chapter1/4.mdx @@ -1,8 +1,8 @@ -# Comment fonctionnent les *transformers* ? +# Comment fonctionnent les transformers ? Dans cette partie, nous allons jeter un coup d'œil à l'architecture des *transformers*. -## Court historique des *transformers* +## Court historique des transformers Voici quelques dates clefs dans la courte histoire des *transformers* : @@ -33,7 +33,7 @@ Cette liste est loin d'être exhaustive et met en lumière certains *transformer Nous verrons plus en profondeur ces familles de modèles plus tard. -## Les *transformers* sont des modèles de langage +## Les transformers sont des modèles de langage Tous les *transformers* mentionnés ci-dessus (GPT, BERT, BART, T5, etc.) ont été entraînés comme des *modèles de langage*. Cela signifie qu'ils ont été entraînés sur une large quantité de textes bruts de manière autosupervisée. L'apprentissage autosupervisé est un type d'entraînement dans lequel l'objectif est automatiquement calculé à partir des entrées du modèle. Cela signifie que les humains ne sont pas nécessaires pour étiqueter les données ! @@ -52,7 +52,7 @@ Un autre exemple est la *modélisation du langage masqué*, dans laquelle le mod -## Les *transformers* sont énormes +## Les transformers sont énormes En dehors de quelques exceptions (comme DistilBERT), la stratégie générale pour obtenir de meilleure performance consiste à augmenter la taille des modèles ainsi que la quantité de données utilisées pour l'entraînement de ces derniers. @@ -133,9 +133,9 @@ Nous verrons plus en détails chacune de ces architectures plus tard. ## Les couches d'attention -Une caractéristique clé des *transformers* est qu'ils sont construits avec des couches spéciales appelées couches d'attention. En fait, le titre du papier introduisant l'architecture *transformer* s'e nome [*Attention Is All You Need*](https://arxiv.org/abs/1706.03762) ! Nous explorerons les détails des couches d'attention plus tard dans le cours. Pour l'instant, tout ce que vous devez savoir est que cette couche indique au modèle de prêter une attention spécifique à certains mots de la phrase que vous lui avez passée (et d'ignorer plus ou moins les autres) lors du traitement de la représentation de chaque mot. +Une caractéristique clé des *transformers* est qu'ils sont construits avec des couches spéciales appelées couches d'attention. En fait, le titre du papier introduisant l'architecture *transformer* se nomme [*Attention Is All You Need*](https://arxiv.org/abs/1706.03762) ! Nous explorerons les détails des couches d'attention plus tard dans le cours. Pour l'instant, tout ce que vous devez savoir est que cette couche indique au modèle de prêter une attention spécifique à certains mots de la phrase que vous lui avez passée (et d'ignorer plus ou moins les autres) lors du traitement de la représentation de chaque mot. -Pour mettre cela en contexte, considérons la tâche de traduire un texte de l'anglais au français. Étant donné l'entrée « You like this course », un modèle de traduction devra également s'intéresser au mot adjacent « You » pour obtenir la traduction correcte du mot « like », car en français le verbe « like » se conjugue différemment selon le sujet. Le reste de la phrase n'est en revanche pas utile pour la traduction de ce mot. Dans le même ordre d'idées, pour traduire « this », le modèle devra également faire attention au mot « course » car « this » se traduit différemment selon que le nom associé est masculin ou féminin. Là encore, les autres mots de la phrase n'auront aucune importance pour la traduction de « this ». Avec des phrases plus complexes (et des règles de grammaire plus complexes), le modèle devra prêter une attention particulière aux mots qui pourraient apparaître plus loin dans la phrase pour traduire correctement chaque mot. +Pour mettre cela en contexte, considérons la tâche de traduire un texte de l'anglais au français. Étant donné l'entrée « *You like this course* », un modèle de traduction devra également s'intéresser au mot adjacent « *You* » pour obtenir la traduction correcte du mot « *like* », car en français le verbe « *like* » se conjugue différemment selon le sujet. Le reste de la phrase n'est en revanche pas utile pour la traduction de ce mot. Dans le même ordre d'idées, pour traduire « *this* », le modèle devra également faire attention au mot « *course* » car « *this* » se traduit différemment selon que le nom associé est masculin ou féminin. Là encore, les autres mots de la phrase n'auront aucune importance pour la traduction de « *this* ». Avec des phrases plus complexes (et des règles de grammaire plus complexes), le modèle devra prêter une attention particulière aux mots qui pourraient apparaître plus loin dans la phrase pour traduire correctement chaque mot. Le même concept s'applique à toute tâche associée au langage naturel : un mot en lui-même a un sens, mais ce sens est profondément affecté par le contexte, qui peut être n'importe quel autre mot (ou mots) avant ou après le mot étudié. @@ -158,9 +158,9 @@ Notez que la première couche d'attention dans un bloc décodeur prête attentio Le *masque d'attention* peut également être utilisé dans l'encodeur/décodeur pour empêcher le modèle de prêter attention à certains mots spéciaux. Par exemple, le mot de remplissage spécial (le *padding*) utilisé pour que toutes les entrées aient la même longueur lors du regroupement de phrases. -## Architectures contre *checkpoints* +## Architectures contre checkpoints -En approfondissant l'étude des *transformers* dans ce cours, vous verrez des mentions d'*architectures* et de *checkpoints* ainsi que de *modèles*. Ces termes ont tous des significations légèrement différentes : +En approfondissant l'étude des transformers dans ce cours, vous verrez des mentions d'architectures et de checkpoints ainsi que de modèles. Ces termes ont tous des significations légèrement différentes : * **Architecture** : c'est le squelette du modèle, la définition de chaque couche et chaque opération qui se produit au sein du modèle. * **Checkpoints** : ce sont les poids qui seront chargés dans une architecture donnée. diff --git a/chapters/fr/chapter1/8.mdx b/chapters/fr/chapter1/8.mdx index acf86a09c..e3e3c2310 100644 --- a/chapters/fr/chapter1/8.mdx +++ b/chapters/fr/chapter1/8.mdx @@ -23,8 +23,10 @@ print([r["token_str"] for r in result]) ``` ```python out -['lawyer', 'carpenter', 'doctor', 'waiter', 'mechanic'] # [avocat, charpentier, médecin, serveur, mécanicien] -['nurse', 'waitress', 'teacher', 'maid', 'prostitute'] # ["infirmière", "serveuse", "professeur", "femme de chambre", "prostituée"] +['lawyer', 'carpenter', 'doctor', 'waiter', 'mechanic'] +# [avocat, charpentier, médecin, serveur, mécanicien] +['nurse', 'waitress', 'teacher', 'maid', 'prostitute'] +# ["infirmière", "serveuse", "professeur", "femme de chambre", "prostituée"] ``` Lorsque l'on demande au modèle de remplacer le mot manquant dans ces deux phrases, il ne propose qu'un seul métier ne portant pas la marque du genre (*waiter*/*waitress* → serveur/serveuse). Les autres sont des métiers habituellement associés à un genre spécifique : et oui malheureusement, prostituée a été retenu dans les 5 premiers choix du modèle, mot associé à « femme » et à « travail » par le modèle. Cela se produit même si BERT est l'un des rare *transformers* qui n'a pas été construit avec des données récupérées par *scrapping* sur internet, mais à l'aide de données en apparence neutres. En effet, il est entraîné sur les jeux de donnés [Wikipédia Anglais](https://huggingface.co/datasets/wikipedia) et [BookCorpus](https://huggingface.co/datasets/bookcorpus)). diff --git a/chapters/fr/chapter2/1.mdx b/chapters/fr/chapter2/1.mdx index bf0c05190..77a53ca12 100644 --- a/chapters/fr/chapter2/1.mdx +++ b/chapters/fr/chapter2/1.mdx @@ -1,24 +1,24 @@ -# Introduction - -Comme vous l'avez vu dans le [Chapitre 1](/course/fr/chapter1), les *transformers* sont généralement très grands. Pouvant aller de plusieurs millions à des dizaines de milliards de paramètres, l'entraînement et le déploiement de ces modèles est une entreprise compliquée. De plus, avec de nouveaux modèles publiés presque quotidiennement et ayant chacun sa propre implémentation, les essayer tous n'est pas une tâche facile. - -La bibliothèque 🤗 *Transformers* a été créée pour résoudre ce problème. Son objectif est de fournir une API unique à travers laquelle tout modèle de *transformers* peut être chargé, entraîné et sauvegardé. Les principales caractéristiques de la bibliothèque sont : - -- **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. - -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. -En plus de rendre les modèles plus accessibles et compréhensibles, cela vous permet d'expérimenter des choses facilement sur un modèle sans affecter les autres. - -Ce chapitre commence par un exemple de bout en bout où nous utilisons un modèle et un *tokenizer* ensemble pour reproduire la fonction `pipeline()` introduite dans le [Chapitre 1](/course/chapter1). -Ensuite, nous aborderons l'API *model* : nous nous plongerons dans les classes de modèle et de configuration, nous verrons comment charger un modèle et enfin comment il traite les entrées numériques pour produire des prédictions. - -Nous examinerons ensuite l'API *tokenizer* qui est l'autre composant principal de la fonction `pipeline()`. -Les *tokenizers* s'occupent de la première et de la dernière étape du traitement en gérant la conversion du texte en entrées numériques pour le réseau neuronal et la reconversion en texte lorsqu'elle est nécessaire. -Enfin, nous montrerons comment gérer l'envoi de plusieurs phrases à travers un modèle dans un batch préparé et nous conclurons le tout en examinant de plus près la fonction `tokenizer()`. - - -⚠️ Afin de bénéficier de toutes les fonctionnalités disponibles avec le Model Hub et le 🤗 *Transformers*, nous vous recommandons de créer un compte. - +# Introduction + +Comme vous l'avez vu dans le [chapitre 1](/course/fr/chapter1), les *transformers* sont généralement très grands. Pouvant aller de plusieurs millions à des dizaines de milliards de paramètres, l'entraînement et le déploiement de ces modèles est une entreprise compliquée. De plus, avec de nouveaux modèles publiés presque quotidiennement et ayant chacun sa propre implémentation, les essayer tous n'est pas une tâche facile. + +La bibliothèque 🤗 *Transformers* a été créée pour résoudre ce problème. Son objectif est de fournir une API unique à travers laquelle tout modèle de *transformers* peut être chargé, entraîné et sauvegardé. Les principales caractéristiques de la bibliothèque sont : + +- **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. + +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. +En plus de rendre les modèles plus accessibles et compréhensibles, cela vous permet d'expérimenter des choses facilement sur un modèle sans affecter les autres. + +Ce chapitre commence par un exemple de bout en bout où nous utilisons un modèle et un *tokenizer* ensemble pour reproduire la fonction `pipeline()` introduite dans le [chapitre 1](/course/fr/chapter1). +Ensuite, nous aborderons l'API *model* : nous nous plongerons dans les classes de modèle et de configuration, nous verrons comment charger un modèle et enfin comment il traite les entrées numériques pour produire des prédictions. + +Nous examinerons ensuite l'API *tokenizer* qui est l'autre composant principal de la fonction `pipeline()`. +Les *tokenizers* s'occupent de la première et de la dernière étape du traitement en gérant la conversion du texte en entrées numériques pour le réseau neuronal et la reconversion en texte lorsqu'elle est nécessaire. +Enfin, nous montrerons comment gérer l'envoi de plusieurs phrases à travers un modèle dans un batch préparé et nous conclurons le tout en examinant de plus près la fonction `tokenizer()`. + + + ⚠️ Afin de bénéficier de toutes les fonctionnalités disponibles avec le Hub et la bibliothèque 🤗 Transformers, nous vous recommandons de créer un compte. + diff --git a/chapters/fr/chapter2/2.mdx b/chapters/fr/chapter2/2.mdx index 64f5e296b..baaf88030 100644 --- a/chapters/fr/chapter2/2.mdx +++ b/chapters/fr/chapter2/2.mdx @@ -32,7 +32,7 @@ Il s'agit de la première section dont le contenu est légèrement différent se {/if} -Commençons par un exemple complet en regardant ce qui s'est passé en coulisses lorsque nous avons exécuté le code suivant dans le [Chapitre 1](/course/chapter1) : +Commençons par un exemple complet en regardant ce qui s'est passé en coulisses lorsque nous avons exécuté le code suivant dans le [chapitre 1](/course/chapter1) : ```python from transformers import pipeline @@ -40,7 +40,8 @@ from transformers import pipeline classifier = pipeline("sentiment-analysis") classifier( [ - "I've been waiting for a HuggingFace course my whole life.", # J'ai attendu un cours de HuggingFace toute ma vie. + "I've been waiting for a HuggingFace course my whole life.", + # J'ai attendu un cours de HuggingFace toute ma vie. "I hate this so much!", # Je déteste tellement ça ! ] ) @@ -53,7 +54,7 @@ la sortie : {'label': 'NEGATIVE', 'score': 0.9994558095932007}] ``` -Comme nous l'avons vu dans le [Chapitre 1](/course/fr/chapter1), ce pipeline regroupe trois étapes : le prétraitement, le passage des entrées dans le modèle et le post-traitement. +Comme nous l'avons vu dans le [chapitre 1](/course/fr/chapter1), ce pipeline regroupe trois étapes : le prétraitement, le passage des entrées dans le modèle et le post-traitement.
The full NLP pipeline: tokenization of text, conversion to IDs, and inference through the Transformer model and the model head. @@ -62,14 +63,14 @@ Comme nous l'avons vu dans le [Chapitre 1](/course/fr/chapter1), ce pipeline reg Passons rapidement en revue chacun de ces éléments. -## Prétraitement avec un *tokenizer* +## Prétraitement avec un tokenizer Comme d'autres réseaux de neurones, les *transformers* ne peuvent pas traiter directement le texte brut, donc la première étape de notre pipeline est de convertir les entrées textuelles en nombres afin que le modèle puisse les comprendre. Pour ce faire, nous utilisons un *tokenizer*, qui sera responsable de : - diviser l'entrée en mots, sous-mots, ou symboles (comme la ponctuation) qui sont appelés *tokens*, - associer chaque *token* à un nombre entier, - ajouter des entrées supplémentaires qui peuvent être utiles au modèle. -Tout ce prétraitement doit être effectué exactement de la même manière que celui appliqué lors du pré-entraînement du modèle. Nous devons donc d'abord télécharger ces informations depuis le [*Model Hub*](https://huggingface.co/models). Pour ce faire, nous utilisons la classe `AutoTokenizer` et sa méthode `from_pretrained()`. En utilisant le nom du *checkpoint* de notre modèle, elle va automatiquement récupérer les données associées au *tokenizer* du modèle et les mettre en cache (afin qu'elles ne soient téléchargées que la première fois que vous exécutez le code ci-dessous). +Tout ce prétraitement doit être effectué exactement de la même manière que celui appliqué lors du pré-entraînement du modèle. Nous devons donc d'abord télécharger ces informations depuis le [*Hub*](https://huggingface.co/models). Pour ce faire, nous utilisons la classe `AutoTokenizer` et sa méthode `from_pretrained()`. En utilisant le nom du *checkpoint* de notre modèle, elle va automatiquement récupérer les données associées au *tokenizer* du modèle et les mettre en cache (afin qu'elles ne soient téléchargées que la première fois que vous exécutez le code ci-dessous). Puisque le *checkpoint* par défaut du pipeline `sentiment-analysis` (analyse de sentiment) est `distilbert-base-uncased-finetuned-sst-2-english` (vous pouvez voir la carte de ce modèle [ici](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english)), nous exécutons ce qui suit : @@ -89,7 +90,8 @@ Pour spécifier le type de tenseurs que nous voulons récupérer (PyTorch, Tenso {#if fw === 'pt'} ```python raw_inputs = [ - "I've been waiting for a HuggingFace course my whole life.", # J'ai attendu un cours de HuggingFace toute ma vie. + "I've been waiting for a HuggingFace course my whole life.", + # J'ai attendu un cours de HuggingFace toute ma vie. "I hate this so much!", # Je déteste tellement ça ! ] inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="pt") @@ -98,7 +100,8 @@ print(inputs) {:else} ```python raw_inputs = [ - "I've been waiting for a HuggingFace course my whole life.", # J'ai attendu un cours de HuggingFace toute ma vie. + "I've been waiting for a HuggingFace course my whole life.", + # J'ai attendu un cours de HuggingFace toute ma vie. "I hate this so much!", # Je déteste tellement ça ! ] inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="tf") @@ -175,7 +178,7 @@ Pour chaque entrée du modèle, nous récupérons un vecteur en grande dimension Si cela ne fait pas 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. +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. ### Un vecteur de grande dimension ? @@ -210,7 +213,7 @@ print(outputs.last_hidden_state.shape) ``` {/if} -Notez que les sorties des modèles de la bibliothèque 🤗 Transformers se comportent comme des `namedtuples` ou des dictionnaires. Vous pouvez accéder aux éléments par attributs (comme nous l'avons fait), par clé (`outputs["last_hidden_state"]`), ou même par l’index si vous savez exactement où se trouve la chose que vous cherchez (`outputs[0]`). +Notez que les sorties des modèles de la bibliothèque 🤗 *Transformers* se comportent comme des `namedtuples` ou des dictionnaires. Vous pouvez accéder aux éléments par attributs (comme nous l'avons fait), par clé (`outputs["last_hidden_state"]`), ou même par l’index si vous savez exactement où se trouve la chose que vous cherchez (`outputs[0]`). ### Les têtes des modèles : donner du sens aux chiffres Les têtes des modèles prennent en entrée le vecteur de grande dimension des états cachés et le projettent sur une autre dimension. Elles sont généralement composées d'une ou de quelques couches linéaires : @@ -289,7 +292,7 @@ tensor([[-1.5607, 1.6123], ``` {/if} -Notre modèle a prédit `[-1.5607, 1.6123]` pour la première phrase et `[ 4.1692, -3.3464]` pour la seconde. Ce ne sont pas des probabilités mais des *logits*, les scores bruts, non normalisés, produits par la dernière couche du modèle. Pour être convertis en probabilités, ils doivent passer par une couche [SoftMax](https://fr.wikipedia.org/wiki/Fonction_softmax) (tous les modèles de la bibliothèque 🤗 Transformers sortent les logits car la fonction de perte de l'entraînement fusionne généralement la dernière fonction d'activation, comme la SoftMax, avec la fonction de perte réelle, comme l'entropie croisée) : +Notre modèle a prédit `[-1.5607, 1.6123]` pour la première phrase et `[ 4.1692, -3.3464]` pour la seconde. Ce ne sont pas des probabilités mais des *logits*, les scores bruts, non normalisés, produits par la dernière couche du modèle. Pour être convertis en probabilités, ils doivent passer par une couche [SoftMax](https://fr.wikipedia.org/wiki/Fonction_softmax) (tous les modèles de la bibliothèque 🤗 *Transformers* sortent les logits car la fonction de perte de l'entraînement fusionne généralement la dernière fonction d'activation, comme la SoftMax, avec la fonction de perte réelle, comme l'entropie croisée) : {#if fw === 'pt'} ```py diff --git a/chapters/fr/chapter2/3.mdx b/chapters/fr/chapter2/3.mdx index 96e555191..9fce3d02f 100644 --- a/chapters/fr/chapter2/3.mdx +++ b/chapters/fr/chapter2/3.mdx @@ -1,231 +1,231 @@ - - -# Les modèles - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -{#if fw === 'pt'} - -{:else} - -{/if} - -{#if fw === 'pt'} -Dans cette section, nous allons examiner de plus près la création et l'utilisation d'un modèle. Nous utiliserons la classe `AutoModel` qui est pratique lorsque vous voulez instancier n'importe quel modèle à partir d'un checkpoint. - -La classe `AutoModel` et toutes les classes apparentées sont en fait de simples *wrappers* sur la grande variété de modèles disponibles dans la bibliothèque. C'est une enveloppe intelligente car elle peut automatiquement deviner l'architecture appropriée pour votre *checkpoint* et ensuite instancier un modèle avec cette architecture. - -{:else} -Dans cette section, nous allons examiner de plus près la création et l'utilisation d'un modèle. Nous utiliserons la classe `TFAutoModel` qui est pratique lorsque vous voulez instancier n'importe quel modèle à partir d'un checkpoint. - -La classe `TFAutoModel` et toutes les classes apparentées sont en fait de simples *wrappers* sur la grande variété de modèles disponibles dans la bibliothèque. C'est une enveloppe intelligente car elle peut automatiquement deviner l'architecture appropriée pour votre *checkpoint* et ensuite instancier un modèle avec cette architecture. - -{/if} - -Cependant, si vous connaissez le type de modèle que vous voulez utiliser, vous pouvez utiliser directement la classe qui définit son architecture. Voyons comment cela fonctionne avec un modèle BERT. - -## Création d’un *transformer* - -La première chose que nous devons faire pour initialiser un modèle BERT est de charger un objet configuration : - -{#if fw === 'pt'} -```py -from transformers import BertConfig, BertModel - -# Construire la configuration -config = BertConfig() - -# Construire le modèle à partir de la configuration -model = BertModel(config) -``` -{:else} -```py -from transformers import BertConfig, TFBertModel - -# Construire la configuration -config = BertConfig() - -# Construire le modèle à partir de la configuration -model = TFBertModel(config) -``` -{/if} - -La configuration contient de nombreux attributs qui sont utilisés pour construire le modèle : -```py -print(config) -``` - -```python out -BertConfig { - [...] - "hidden_size": 768, - "intermediate_size": 3072, - "max_position_embeddings": 512, - "num_attention_heads": 12, - "num_hidden_layers": 12, - [...] -} -``` - -Bien que vous n'ayez pas encore vu ce que font tous ces attributs, vous devriez en reconnaître certains : l'attribut `hidden_size` définit la taille du vecteur `hidden_states`, et `num_hidden_layers` définit le nombre de couches que le *transformer* possède. - -### Différentes méthodes de chargement - -Un modèle créé à partir de la configuration par défaut est initialisé avec des valeurs aléatoires : - - -{#if fw === 'pt'} -```py -from transformers import BertConfig, BertModel - -config = BertConfig() -model = BertModel(config) - -# Le modèle est initialisé de façon aléatoire ! -``` -{:else} -```py -from transformers import BertConfig, TFBertModel - -config = BertConfig() -model = TFBertModel(config) - -# Le modèle est initialisé de façon aléatoire ! -``` -{/if} - -Le modèle peut être utilisé tel quel mais il produira du charabia. En effet, il doit d'abord être entraîné. Nous pourrions entraîner le modèle à partir de zéro sur la tâche qui nous intéresse mais comme vous l'avez vu dans le [Chapitre 1](/course/fr/chapter1) cela nécessiterait beaucoup de temps et de données. De plus cela aurait un impact environnemental non négligeable. Pour éviter les efforts inutiles et redondants, il est impératif de pouvoir partager et réutiliser les modèles qui ont déjà été entraînés. - -Charger un *transformer* qui a déjà été entraîné est simple : nous pouvons le faire en utilisant la méthode `from_pretrained()` : - - -{#if fw === 'pt'} -```py -from transformers import BertModel - -model = BertModel.from_pretrained("bert-base-cased") -``` - -Comme vu précédemment, nous pouvons remplacer `BertModel` par la classe équivalente `AutoModel`. A partir de maintenant nous ferons cela car cela produit un code agnostique *checkpoint*, c’est-à-dire que si votre code fonctionne pour un *checkpoint* donné, il devrait fonctionner sans problème avec un autre. Cela s'applique même si l'architecture est différente du moment que le *checkpoint* a été entraîné pour une tâche similaire (par exemple, une tâche d'analyse de sentiments). - -{:else} -```py -from transformers import TFBertModel - -model = TFBertModel.from_pretrained("bert-base-cased") -``` - -Comme vu précédemment, nous pouvons remplacer `TFBertModel` par la classe équivalente `TFAutoModel`. A partir de maintenant nous ferons cela car cela produit un code agnostique *checkpoint*, c’est-à-dire que si votre code fonctionne pour un *checkpoint* donné, il devrait fonctionner sans problème avec un autre. Cela s'applique même si l'architecture est différente du moment que le *checkpoint* a été entraîné pour une tâche similaire (par exemple, une tâche d'analyse de sentiments). - -{/if} - -Dans l'exemple de code ci-dessus, nous n'avons pas utilisé `BertConfig` et avons à la place chargé un modèle pré-entraîné via l'identifiant `bert-base-cased`. Il s'agit d'un *checkpoint* qui a été entraîné par les auteurs de BERT eux-mêmes. Vous pouvez trouver davantage de détails à son sujet dans la [fiche du modèle](https://huggingface.co/bert-base-cased). - -Ce modèle est maintenant initialisé avec tous les poids du *checkpoint*. Il peut être utilisé directement pour l'inférence sur les tâches sur lesquelles il a été entraîné. Il peut également être *finetuné* sur une nouvelle tâche. En entraînant avec des poids pré-entraînés plutôt qu'à partir de zéro, nous pouvons rapidement obtenir de bons résultats. - -Les poids ont été téléchargés et mis en cache (afin que les futurs appels à la méthode `from_pretrained()` ne les retéléchargent pas) dans le dossier cache, qui est par défaut *~/.cache/huggingface/transformers*. Vous pouvez personnaliser votre dossier de cache en définissant la variable d'environnement `HF_HOME`. - -L'identifiant utilisé pour charger le modèle peut être l'identifiant de n'importe quel modèle sur le *Model Hub* du moment qu'il est compatible avec l'architecture BERT. La liste complète des *checkpoints* de BERT disponibles peut être trouvée [ici](https://huggingface.co/models?filter=bert). - -### Méthodes de sauvegarde - -Sauvegarder un modèle est aussi facile que d'en charger un. Nous utilisons la méthode `save_pretrained()`, qui est analogue à la méthode `from_pretrained()` : - - -```py -model.save_pretrained("directory_on_my_computer") -``` - -Cela enregistre deux fichiers sur votre disque : - -{#if fw === 'pt'} -``` -ls directory_on_my_computer - -config.json pytorch_model.bin -``` -{:else} -``` -ls directory_on_my_computer - -config.json tf_model.h5 -``` -{/if} - -Si vous jetez un coup d'œil au fichier *config.json*, vous reconnaîtrez les attributs nécessaires pour construire l'architecture du modèle. Ce fichier contient également certaines métadonnées, comme l'origine du *checkpoint* et la version de la bibliothèque 🤗 *Transformers* que vous utilisiez lors du dernier enregistrement du point *checkpoint*. - -{#if fw === 'pt'} -Le fichier *pytorch_model.bin* est connu comme le *dictionnaire d'état*. Il contient tous les poids de votre modèle. Les deux fichiers vont de pair : la configuration est nécessaire pour connaître l'architecture de votre modèle, tandis que les poids du modèle sont les paramètres de votre modèle. - -{:else} -Le fichier *tf_model.h5* est connu comme le *dictionnaire d'état*. Il contient tous les poids de votre modèle. Les deux fichiers vont de pair : la configuration est nécessaire pour connaître l'architecture de votre modèle, tandis que les poids du modèle sont les paramètres de votre modèle. - -{/if} - -### Utilisation d'un *transformer* pour l'inférence - -Maintenant que vous savez comment charger et sauvegarder un modèle, essayons de l'utiliser pour faire quelques prédictions. Les *transformers* ne peuvent traiter que des nombres. Des nombres que le *tokenizer* génère. Mais avant de parler des *tokenizers*, explorons les entrées que le modèle accepte. - -Les *tokenizers* se chargent de passer les entrées vers les tenseurs du *framework* approprié. Pour vous aider à comprendre ce qui se passe, jetons un coup d'œil rapide à ce qui doit être fait avant d'envoyer les entrées au modèle. - -Disons que nous avons les séquences suivantes : - - -```py -sequences = ["Hello!", "Cool.", "Nice!"] -``` - -Le *tokenizer* les convertit en indices de vocabulaire qui sont généralement appelés *input IDs*. Chaque séquence est maintenant une liste de nombres ! La sortie résultante est : - -```py no-format -encoded_sequences = [ - [101, 7592, 999, 102], - [101, 4658, 1012, 102], - [101, 3835, 999, 102], -] -``` - -Il s'agit d'une liste de séquences encodées : une liste de listes. Les tenseurs n'acceptent que des formes rectangulaires (pensez aux matrices). Ce « tableau » est déjà de forme rectangulaire, donc le convertir en tenseur est facile : - -{#if fw === 'pt'} -```py -import torch - -model_inputs = torch.tensor(encoded_sequences) -``` -{:else} -```py -import tensorflow as tf - -model_inputs = tf.constant(encoded_sequences) -``` -{/if} - -### Utilisation des tenseurs comme entrées du modèle - -L'utilisation des tenseurs avec le modèle est extrêmement simple, il suffit d'appeler le modèle avec les entrées : - - -```py -output = model(model_inputs) -``` - -Bien que le modèle accepte un grand nombre d'arguments différents, seuls les identifiants d'entrée sont nécessaires. Nous expliquerons plus tard ce que font les autres arguments et quand ils sont nécessaires. Avant cela, regardons de plus près les *tokenizers*, cet outil qui construit les entrées qu'un *transformer* peut comprendre. + + +# Les modèles + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + +{:else} + +{/if} + +{#if fw === 'pt'} +Dans cette section, nous allons examiner de plus près la création et l'utilisation d'un modèle. Nous utiliserons la classe `AutoModel` qui est pratique lorsque vous voulez instancier n'importe quel modèle à partir d'un checkpoint. + +La classe `AutoModel` et toutes les classes apparentées sont en fait de simples *wrappers* sur la grande variété de modèles disponibles dans la bibliothèque. C'est une enveloppe intelligente car elle peut automatiquement deviner l'architecture appropriée pour votre *checkpoint* et ensuite instancier un modèle avec cette architecture. + +{:else} +Dans cette section, nous allons examiner de plus près la création et l'utilisation d'un modèle. Nous utiliserons la classe `TFAutoModel` qui est pratique lorsque vous voulez instancier n'importe quel modèle à partir d'un checkpoint. + +La classe `TFAutoModel` et toutes les classes apparentées sont en fait de simples *wrappers* sur la grande variété de modèles disponibles dans la bibliothèque. C'est une enveloppe intelligente car elle peut automatiquement deviner l'architecture appropriée pour votre *checkpoint* et ensuite instancier un modèle avec cette architecture. + +{/if} + +Cependant, si vous connaissez le type de modèle que vous voulez utiliser, vous pouvez utiliser directement la classe qui définit son architecture. Voyons comment cela fonctionne avec un modèle BERT. + +## Création d’un transformer + +La première chose que nous devons faire pour initialiser un modèle BERT est de charger un objet configuration : + +{#if fw === 'pt'} +```py +from transformers import BertConfig, BertModel + +# Construire la configuration +config = BertConfig() + +# Construire le modèle à partir de la configuration +model = BertModel(config) +``` +{:else} +```py +from transformers import BertConfig, TFBertModel + +# Construire la configuration +config = BertConfig() + +# Construire le modèle à partir de la configuration +model = TFBertModel(config) +``` +{/if} + +La configuration contient de nombreux attributs qui sont utilisés pour construire le modèle : +```py +print(config) +``` + +```python out +BertConfig { + [...] + "hidden_size": 768, + "intermediate_size": 3072, + "max_position_embeddings": 512, + "num_attention_heads": 12, + "num_hidden_layers": 12, + [...] +} +``` + +Bien que vous n'ayez pas encore vu ce que font tous ces attributs, vous devriez en reconnaître certains : l'attribut `hidden_size` définit la taille du vecteur `hidden_states`, et `num_hidden_layers` définit le nombre de couches que le *transformer* possède. + +### Différentes méthodes de chargement + +Un modèle créé à partir de la configuration par défaut est initialisé avec des valeurs aléatoires : + + +{#if fw === 'pt'} +```py +from transformers import BertConfig, BertModel + +config = BertConfig() +model = BertModel(config) + +# Le modèle est initialisé de façon aléatoire ! +``` +{:else} +```py +from transformers import BertConfig, TFBertModel + +config = BertConfig() +model = TFBertModel(config) + +# Le modèle est initialisé de façon aléatoire ! +``` +{/if} + +Le modèle peut être utilisé tel quel mais il produira du charabia. En effet, il doit d'abord être entraîné. Nous pourrions entraîner le modèle à partir de zéro sur la tâche qui nous intéresse mais comme vous l'avez vu dans le [chapitre 1](/course/fr/chapter1) cela nécessiterait beaucoup de temps et de données. De plus cela aurait un impact environnemental non négligeable. Pour éviter les efforts inutiles et redondants, il est impératif de pouvoir partager et réutiliser les modèles qui ont déjà été entraînés. + +Charger un *transformer* qui a déjà été entraîné est simple : nous pouvons le faire en utilisant la méthode `from_pretrained()` : + + +{#if fw === 'pt'} +```py +from transformers import BertModel + +model = BertModel.from_pretrained("bert-base-cased") +``` + +Comme vu précédemment, nous pouvons remplacer `BertModel` par la classe équivalente `AutoModel`. A partir de maintenant nous ferons cela car cela produit un code agnostique *checkpoint*, c’est-à-dire que si votre code fonctionne pour un *checkpoint* donné, il devrait fonctionner sans problème avec un autre. Cela s'applique même si l'architecture est différente du moment que le *checkpoint* a été entraîné pour une tâche similaire (par exemple, une tâche d'analyse de sentiments). + +{:else} +```py +from transformers import TFBertModel + +model = TFBertModel.from_pretrained("bert-base-cased") +``` + +Comme vu précédemment, nous pouvons remplacer `TFBertModel` par la classe équivalente `TFAutoModel`. A partir de maintenant nous ferons cela car cela produit un code agnostique *checkpoint*, c’est-à-dire que si votre code fonctionne pour un *checkpoint* donné, il devrait fonctionner sans problème avec un autre. Cela s'applique même si l'architecture est différente du moment que le *checkpoint* a été entraîné pour une tâche similaire (par exemple, une tâche d'analyse de sentiments). + +{/if} + +Dans l'exemple de code ci-dessus, nous n'avons pas utilisé `BertConfig` et avons à la place chargé un modèle pré-entraîné via l'identifiant `bert-base-cased`. Il s'agit d'un *checkpoint* qui a été entraîné par les auteurs de BERT eux-mêmes. Vous pouvez trouver davantage de détails à son sujet dans la [fiche du modèle](https://huggingface.co/bert-base-cased). + +Ce modèle est maintenant initialisé avec tous les poids du *checkpoint*. Il peut être utilisé directement pour l'inférence sur les tâches sur lesquelles il a été entraîné. Il peut également être *finetuné* sur une nouvelle tâche. En entraînant avec des poids pré-entraînés plutôt qu'à partir de zéro, nous pouvons rapidement obtenir de bons résultats. + +Les poids ont été téléchargés et mis en cache (afin que les futurs appels à la méthode `from_pretrained()` ne les retéléchargent pas) dans le dossier cache, qui est par défaut *~/.cache/huggingface/transformers*. Vous pouvez personnaliser votre dossier de cache en définissant la variable d'environnement `HF_HOME`. + +L'identifiant utilisé pour charger le modèle peut être l'identifiant de n'importe quel modèle sur le *Model Hub* du moment qu'il est compatible avec l'architecture BERT. La liste complète des *checkpoints* de BERT disponibles peut être trouvée [ici](https://huggingface.co/models?filter=bert). + +### Méthodes de sauvegarde + +Sauvegarder un modèle est aussi facile que d'en charger un. Nous utilisons la méthode `save_pretrained()`, qui est analogue à la méthode `from_pretrained()` : + + +```py +model.save_pretrained("directory_on_my_computer") +``` + +Cela enregistre deux fichiers sur votre disque : + +{#if fw === 'pt'} +``` +ls directory_on_my_computer + +config.json pytorch_model.bin +``` +{:else} +``` +ls directory_on_my_computer + +config.json tf_model.h5 +``` +{/if} + +Si vous jetez un coup d'œil au fichier *config.json*, vous reconnaîtrez les attributs nécessaires pour construire l'architecture du modèle. Ce fichier contient également certaines métadonnées, comme l'origine du *checkpoint* et la version de la bibliothèque 🤗 *Transformers* que vous utilisiez lors du dernier enregistrement du point *checkpoint*. + +{#if fw === 'pt'} +Le fichier *pytorch_model.bin* est connu comme le *dictionnaire d'état*. Il contient tous les poids de votre modèle. Les deux fichiers vont de pair : la configuration est nécessaire pour connaître l'architecture de votre modèle, tandis que les poids du modèle sont les paramètres de votre modèle. + +{:else} +Le fichier *tf_model.h5* est connu comme le *dictionnaire d'état*. Il contient tous les poids de votre modèle. Les deux fichiers vont de pair : la configuration est nécessaire pour connaître l'architecture de votre modèle, tandis que les poids du modèle sont les paramètres de votre modèle. + +{/if} + +### Utilisation d'un transformer pour l'inférence + +Maintenant que vous savez comment charger et sauvegarder un modèle, essayons de l'utiliser pour faire quelques prédictions. Les *transformers* ne peuvent traiter que des nombres. Des nombres que le *tokenizer* génère. Mais avant de parler des *tokenizers*, explorons les entrées que le modèle accepte. + +Les *tokenizers* se chargent de passer les entrées vers les tenseurs du *framework* approprié. Pour vous aider à comprendre ce qui se passe, jetons un coup d'œil rapide à ce qui doit être fait avant d'envoyer les entrées au modèle. + +Disons que nous avons les séquences suivantes : + + +```py +sequences = ["Hello!", "Cool.", "Nice!"] +``` + +Le *tokenizer* les convertit en indices de vocabulaire qui sont généralement appelés *input IDs*. Chaque séquence est maintenant une liste de nombres ! La sortie résultante est : + +```py no-format +encoded_sequences = [ + [101, 7592, 999, 102], + [101, 4658, 1012, 102], + [101, 3835, 999, 102], +] +``` + +Il s'agit d'une liste de séquences encodées : une liste de listes. Les tenseurs n'acceptent que des formes rectangulaires (pensez aux matrices). Ce « tableau » est déjà de forme rectangulaire, donc le convertir en tenseur est facile : + +{#if fw === 'pt'} +```py +import torch + +model_inputs = torch.tensor(encoded_sequences) +``` +{:else} +```py +import tensorflow as tf + +model_inputs = tf.constant(encoded_sequences) +``` +{/if} + +### Utilisation des tenseurs comme entrées du modèle + +L'utilisation des tenseurs avec le modèle est extrêmement simple, il suffit d'appeler le modèle avec les entrées : + + +```py +output = model(model_inputs) +``` + +Bien que le modèle accepte un grand nombre d'arguments différents, seuls les identifiants d'entrée sont nécessaires. Nous expliquerons plus tard ce que font les autres arguments et quand ils sont nécessaires. Avant cela, regardons de plus près les *tokenizers*, cet outil qui construit les entrées qu'un *transformer* peut comprendre. diff --git a/chapters/fr/chapter2/4.mdx b/chapters/fr/chapter2/4.mdx index f407cfed1..2015763fb 100644 --- a/chapters/fr/chapter2/4.mdx +++ b/chapters/fr/chapter2/4.mdx @@ -1,253 +1,253 @@ - - -# Les *tokenizers* - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - - - -Les *tokenizers* sont l'un des principaux composants du pipeline de NLP. Ils ont un seul objectif : traduire le texte en données pouvant être traitées par le modèle. Les modèles ne pouvant traiter que des nombres, les *tokenizers* doivent convertir nos entrées textuelles en données numériques. Dans cette section, nous allons explorer ce qui se passe exactement dans le pipeline de tokénisation. - -Dans les tâches de NLP, les données traitées sont généralement du texte brut. Voici un exemple de ce type de texte : - - -``` -Jim Henson was a puppeteer # Jim Henson était un marionnettiste. -``` - -Les modèles ne pouvant traiter que des nombres, nous devons trouver un moyen de convertir le texte brut en nombres. C'est ce que font les *tokenizers* et il existe de nombreuses façons de procéder. L'objectif est de trouver la représentation la plus significative, c'est-à-dire celle qui a le plus de sens pour le modèle, et si possible qui soit la plus petite. - -Voyons quelques exemples d'algorithmes de tokénisation et essayons de répondre à certaines des questions que vous pouvez vous poser à ce sujet. - -## *Tokenizer* basé sur les mots - - - - -Le premier type de *tokenizer* qui vient à l'esprit est celui basé sur les mots. Il est généralement très facile à utiliser et configurable avec seulement quelques règles. Il donne souvent des résultats décents. Par exemple, dans l'image ci-dessous, l'objectif est de diviser le texte brut en mots et de trouver une représentation numérique pour chacun d'eux : - -
- An example of word-based tokenization. - -
- -Il existe différentes façons de diviser le texte. Par exemple, nous pouvons utiliser les espaces pour segmenter le texte en mots en appliquant la fonction `split()` de Python : - -```py -tokenized_text = "Jim Henson was a puppeteer".split() -print(tokenized_text) -``` - -```python out -['Jim', 'Henson', 'was', 'a', 'puppeteer'] # ['Jim', 'Henson', était, 'un', 'marionnettiste'] -``` - -Il existe également des variantes des *tokenizers* basés sur les mots qui ont des règles supplémentaires pour la ponctuation. Avec ce type de *tokenizers* nous pouvons nous retrouver avec des « vocabulaires » assez larges, où un vocabulaire est défini par le nombre total de *tokens* indépendants que nous avons dans notre corpus. - -Un identifiant est attribué à chaque mot, en commençant par 0 et en allant jusqu'à la taille du vocabulaire. Le modèle utilise ces identifiants pour identifier chaque mot. - -Si nous voulons couvrir complètement une langue avec un *tokenizer* basé sur les mots, nous devons avoir un identifiant pour chaque mot de la langue que nous traitons, ce qui génère une énorme quantité de *tokens*. Par exemple, il y a plus de 500 000 mots dans la langue anglaise. Ainsi pour associer chaque mot à un identifiant, nous devrions garder la trace d'autant d'identifiants. De plus, des mots comme « chien » sont représentés différemment de mots comme « chiens ». Le modèle n'a initialement aucun moyen de savoir que « chien » et « chiens » sont similaires : il identifie les deux mots comme non apparentés. Il en va de même pour d'autres mots similaires, comme « maison » et « maisonnette » que le modèle ne considérera pas comme similaires au départ. - -Enfin, nous avons besoin d'un *token* personnalisé pour représenter les mots qui ne font pas partie de notre vocabulaire. C'est ce qu'on appelle le *token* « inconnu » souvent représenté par « [UNK] » (de l’anglais « unknown ») ou « <unk> ; ». C'est généralement un mauvais signe si vous constatez que le *tokenizer* produit un nombre important de ce jeton spécial. Cela signifie qu’il n'a pas été en mesure de récupérer une représentation sensée d'un mot et que vous perdez des informations en cours de route. L'objectif de l'élaboration du vocabulaire est de faire en sorte que le *tokenizer* transforme le moins de mots possible en *token* inconnu. - -Une façon de réduire la quantité de *tokens* inconnus est d'aller un niveau plus profond, en utilisant un *tokenizer* basé sur les caractères. - - -## *Tokenizer* basé sur les caractères - - - -Les *tokenizers* basés sur les caractères divisent le texte en caractères, plutôt qu'en mots. Cela présente deux avantages principaux : - -- le vocabulaire est beaucoup plus petit -- il y a beaucoup moins de *tokens* hors vocabulaire (inconnus) puisque chaque mot peut être construit à partir de caractères. - -Mais là aussi, des questions se posent concernant les espaces et la ponctuation : - - -
- An example of character-based tokenization. - -
- -Cette approche n'est pas non plus parfaite. Puisque la représentation est maintenant basée sur des caractères plutôt que sur des mots, on pourrait dire intuitivement qu’elle est moins significative : chaque caractère ne signifie pas grand-chose en soi, alors que c'est le cas pour les mots. Toutefois, là encore, cela diffère selon la langue. En chinois, par exemple, chaque caractère est porteur de plus d'informations qu'un caractère dans une langue latine. - -Un autre élément à prendre en compte est que nous nous retrouverons avec une très grande quantité de *tokens* à traiter par notre modèle. Alors qu’avec un *tokenizer* basé sur les mots, pour un mot donné on aurait qu'un seul *token*, avec un *tokenizer* basé sur les caractères, cela peut facilement se transformer en 10 *tokens* voire plus. - -Pour obtenir le meilleur des deux mondes, nous pouvons utiliser une troisième technique qui combine les deux approches : la *tokénisation en sous-mots*. - - -## Tokénisation en sous-mots - - - -Les algorithmes de tokenisation en sous-mots reposent sur le principe selon lequel les mots fréquemment utilisés ne doivent pas être divisés en sous-mots plus petits, mais les mots rares doivent être décomposés en sous-mots significatifs. - -Par exemple, le mot « maisonnette » peut être considéré comme un mot rare et peut être décomposé en « maison » et « ette ». Ces deux mots sont susceptibles d'apparaître plus fréquemment en tant que sous-mots autonomes, alors qu'en même temps le sens de « maison » est conservé par le sens composite de « maison » et « ette ». - -Voici un exemple montrant comment un algorithme de tokenisation en sous-mots tokeniserait la séquence « Let's do tokenization ! » : - - -
- A subword tokenization algorithm. - -
- -Ces sous-mots finissent par fournir beaucoup de sens sémantique. Par exemple, ci-dessus, « tokenization » a été divisé en « token » et « ization » : deux *tokens* qui ont un sens sémantique tout en étant peu encombrants (seuls deux *tokens* sont nécessaires pour représenter un long mot). Cela nous permet d'avoir une couverture relativement bonne avec de petits vocabulaires et presque aucun *token* inconnu. - -Cette approche est particulièrement utile dans les langues agglutinantes comme le turc, où l'on peut former des mots complexes (presque) arbitrairement longs en enchaînant des sous-mots. - - -### Et plus encore ! - -Il existe de nombreuses autres techniques. Pour n'en citer que quelques-unes : - -- le *Byte-level BPE* utilisé par exemple dans le GPT-2 -- le *WordPiece* utilisé par exemple dans BERT -- *SentencePiece* ou *Unigram*, utilisés dans plusieurs modèles multilingues. - -Vous devriez maintenant avoir une connaissance suffisante du fonctionnement des tokenizers pour commencer à utiliser l'API. - - -## Chargement et sauvegarde - -Le chargement et la sauvegarde des *tokenizers* est aussi simple que pour les modèles. En fait, c'est basé sur les deux mêmes méthodes : `from_pretrained()` et `save_pretrained()`. Ces méthodes vont charger ou sauvegarder l'algorithme utilisé par le *tokenizer* (un peu comme l'*architecture* du modèle) ainsi que son vocabulaire (un peu comme les *poids* du modèle). - -Le chargement du *tokenizer* de BERT entraîné avec le même *checkpoint* que BERT se fait de la même manière que le chargement du modèle, sauf que nous utilisons la classe `BertTokenizer` : - - -```py -from transformers import BertTokenizer - -tokenizer = BertTokenizer.from_pretrained("bert-base-cased") -``` - -{#if fw === 'pt'} -Similaire à `AutoModel`, la classe `AutoTokenizer` récupère la classe de *tokenizer* appropriée dans la bibliothèque basée sur le nom du *checkpoint*. Elle peut être utilisée directement avec n'importe quel *checkpoint* : - -{:else} -Similaire à `TFAutoModel`, la classe `AutoTokenizer` récupère la classe de *tokenizer* appropriée dans la bibliothèque basée sur le nom du *checkpoint*. Elle peut être utilisée directement avec n'importe quel *checkpoint* : - -{/if} - -```py -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") -``` - -Nous pouvons à présent utiliser le *tokenizer* comme indiqué dans la section précédente : - -```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]} -``` - -La sauvegarde d'un tokenizer est identique à celle d'un modèle : - -```py -tokenizer.save_pretrained("directory_on_my_computer") -``` - -Nous parlerons plus en détail des `token_type_ids` au [Chapitre 3](/course/fr/chapter3) et nous expliquerons la clé `attention_mask` un peu plus tard. Tout d'abord, voyons comment les `input_ids` sont générés. Pour ce faire, nous devons examiner les méthodes intermédiaires du *tokenizer*. - -## Encodage - - - - -La traduction d'un texte en chiffres est connue sous le nom d’*encodage*. L'encodage se fait en deux étapes : la tokenisation, suivie de la conversion en identifiants d'entrée. - -Comme nous l'avons vu, la première étape consiste à diviser le texte en mots (ou parties de mots, symboles de ponctuation, etc.), généralement appelés *tokens*. De nombreuses règles peuvent régir ce processus. C'est pourquoi nous devons instancier le *tokenizer* en utilisant le nom du modèle afin de nous assurer que nous utilisons les mêmes règles que celles utilisées lors du pré-entraînement du modèle. - -La deuxième étape consiste à convertir ces *tokens* en nombres afin de construire un tenseur à partir de ceux-ci ainsi que de les transmettre au modèle. Pour ce faire, le *tokenizer* possède un *vocabulaire*, qui est la partie que nous téléchargeons lorsque nous l'instancions avec la méthode `from_pretrained()`. Encore une fois, nous devons utiliser le même vocabulaire que celui utilisé lors du pré-entraînement du modèle. - -Pour mieux comprendre les deux étapes, nous allons les explorer séparément. A noter que nous utilisons des méthodes effectuant séparément des parties du pipeline de tokenisation afin de montrer les résultats intermédiaires de ces étapes. Néanmoins, en pratique, il faut appeler le *tokenizer* directement sur vos entrées (comme indiqué dans la section 2). - -### Tokenisation - -Le processus de tokenisation est effectué par la méthode `tokenize()` du *tokenizer* : - - -```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) -``` - -La sortie de cette méthode est une liste de chaînes de caractères ou de *tokens* : - -```python out -['Using', 'a', 'transform', '##er', 'network', 'is', 'simple'] -``` - -Ce *tokenizer* est un *tokenizer* de sous-mots : il découpe les mots jusqu'à obtenir des *tokens* qui peuvent être représentés par son vocabulaire. C'est le cas ici avec `transformer` qui est divisé en deux *tokens* : `transform` et `##er`. - -### De *tokens* aux identifiants d'entrée - -La conversion en identifiants d'entrée est gérée par la méthode `convert_tokens_to_ids()` du *tokenizer* : - - -```py -ids = tokenizer.convert_tokens_to_ids(tokens) - -print(ids) -``` - -```python out -[7993, 170, 11303, 1200, 2443, 1110, 3014] -``` - -Une fois converties en tenseur dans le *framework* approprié, ces sorties peuvent ensuite être utilisées comme entrées d'un modèle, comme nous l'avons vu précédemment dans ce chapitre. - - - -✏️ **Essayez !** Reproduisez les deux dernières étapes (tokénisation et conversion en identifiants d'entrée) sur les phrases des entrées que nous avons utilisées dans la section 2 (« I've been waiting for a HuggingFace course my whole life. » et « I hate this so much! »). Vérifiez que vous obtenez les mêmes identifiants d'entrée que nous avons obtenus précédemment ! - - - -## Décodage - -Le *décodage* va dans l'autre sens : à partir d'indices du vocabulaire nous voulons obtenir une chaîne de caractères. Cela peut être fait avec la méthode `decode()` comme suit : - - -```py -decoded_string = tokenizer.decode([7993, 170, 11303, 1200, 2443, 1110, 3014]) -print(decoded_string) -``` - -```python out -'Using a Transformer network is simple' -``` - -Notez que la méthode `decode` non seulement reconvertit les indices en *tokens* mais regroupe également les *tokens* faisant partie des mêmes mots. Le but étant de produire une phrase lisible. Ce comportement sera extrêmement utile lorsque dans la suite du cours nous utiliserons des modèles pouvant produire du nouveau texte (soit du texte généré à partir d'un *prompt*, soit pour des problèmes de séquence à séquence comme la traduction ou le résumé de texte). - -Vous devriez maintenant comprendre les opérations atomiques qu'un *tokenizer* peut gérer : tokenisation, conversion en identifiants, et reconversion des identifiants en chaîne de caractères. Cependant, nous n'avons fait qu'effleurer la partie émergée de l'iceberg. Dans la section suivante, nous allons pousser notre approche jusqu'à ses limites et voir comment les surmonter. + + +# Les tokenizers + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + + +Les *tokenizers* sont l'un des principaux composants du pipeline de NLP. Ils ont un seul objectif : traduire le texte en données pouvant être traitées par le modèle. Les modèles ne pouvant traiter que des nombres, les *tokenizers* doivent convertir nos entrées textuelles en données numériques. Dans cette section, nous allons explorer ce qui se passe exactement dans le pipeline de tokénisation. + +Dans les tâches de NLP, les données traitées sont généralement du texte brut. Voici un exemple de ce type de texte : + + +``` +Jim Henson was a puppeteer # Jim Henson était un marionnettiste +``` + +Les modèles ne pouvant traiter que des nombres, nous devons trouver un moyen de convertir le texte brut en nombres. C'est ce que font les *tokenizers* et il existe de nombreuses façons de procéder. L'objectif est de trouver la représentation la plus significative, c'est-à-dire celle qui a le plus de sens pour le modèle, et si possible qui soit la plus petite. + +Voyons quelques exemples d'algorithmes de tokénisation et essayons de répondre à certaines des questions que vous pouvez vous poser à ce sujet. + +## Tokenizer basé sur les mots + + + + +Le premier type de *tokenizer* qui vient à l'esprit est celui basé sur les mots. Il est généralement très facile à utiliser et configurable avec seulement quelques règles. Il donne souvent des résultats décents. Par exemple, dans l'image ci-dessous, l'objectif est de diviser le texte brut en mots et de trouver une représentation numérique pour chacun d'eux : + +
+ An example of word-based tokenization. + +
+ +Il existe différentes façons de diviser le texte. Par exemple, nous pouvons utiliser les espaces pour segmenter le texte en mots en appliquant la fonction `split()` de Python : + +```py +tokenized_text = "Jim Henson was a puppeteer".split() +print(tokenized_text) +``` + +```python out +['Jim', 'Henson', 'was', 'a', 'puppeteer'] # ['Jim', 'Henson', était, 'un', 'marionnettiste'] +``` + +Il existe également des variantes des *tokenizers* basés sur les mots qui ont des règles supplémentaires pour la ponctuation. Avec ce type de *tokenizers* nous pouvons nous retrouver avec des « vocabulaires » assez larges, où un vocabulaire est défini par le nombre total de *tokens* indépendants que nous avons dans notre corpus. + +Un identifiant est attribué à chaque mot, en commençant par 0 et en allant jusqu'à la taille du vocabulaire. Le modèle utilise ces identifiants pour identifier chaque mot. + +Si nous voulons couvrir complètement une langue avec un *tokenizer* basé sur les mots, nous devons avoir un identifiant pour chaque mot de la langue que nous traitons, ce qui génère une énorme quantité de *tokens*. Par exemple, il y a plus de 500 000 mots dans la langue anglaise. Ainsi pour associer chaque mot à un identifiant, nous devrions garder la trace d'autant d'identifiants. De plus, des mots comme « chien » sont représentés différemment de mots comme « chiens ». Le modèle n'a initialement aucun moyen de savoir que « chien » et « chiens » sont similaires : il identifie les deux mots comme non apparentés. Il en va de même pour d'autres mots similaires, comme « maison » et « maisonnette » que le modèle ne considérera pas comme similaires au départ. + +Enfin, nous avons besoin d'un *token* personnalisé pour représenter les mots qui ne font pas partie de notre vocabulaire. C'est ce qu'on appelle le *token* « inconnu » souvent représenté par « [UNK] » (de l’anglais « unknown ») ou « <unk> ; ». C'est généralement un mauvais signe si vous constatez que le *tokenizer* produit un nombre important de ce jeton spécial. Cela signifie qu’il n'a pas été en mesure de récupérer une représentation sensée d'un mot et que vous perdez des informations en cours de route. L'objectif de l'élaboration du vocabulaire est de faire en sorte que le *tokenizer* transforme le moins de mots possible en *token* inconnu. + +Une façon de réduire la quantité de *tokens* inconnus est d'aller un niveau plus profond, en utilisant un *tokenizer* basé sur les caractères. + + +## Tokenizer basé sur les caractères + + + +Les *tokenizers* basés sur les caractères divisent le texte en caractères, plutôt qu'en mots. Cela présente deux avantages principaux : + +- le vocabulaire est beaucoup plus petit +- il y a beaucoup moins de *tokens* hors vocabulaire (inconnus) puisque chaque mot peut être construit à partir de caractères. + +Mais là aussi, des questions se posent concernant les espaces et la ponctuation : + + +
+ An example of character-based tokenization. + +
+ +Cette approche n'est pas non plus parfaite. Puisque la représentation est maintenant basée sur des caractères plutôt que sur des mots, on pourrait dire intuitivement qu’elle est moins significative : chaque caractère ne signifie pas grand-chose en soi, alors que c'est le cas pour les mots. Toutefois, là encore, cela diffère selon la langue. En chinois, par exemple, chaque caractère est porteur de plus d'informations qu'un caractère dans une langue latine. + +Un autre élément à prendre en compte est que nous nous retrouverons avec une très grande quantité de *tokens* à traiter par notre modèle. Alors qu’avec un *tokenizer* basé sur les mots, pour un mot donné on aurait qu'un seul *token*, avec un *tokenizer* basé sur les caractères, cela peut facilement se transformer en 10 *tokens* voire plus. + +Pour obtenir le meilleur des deux mondes, nous pouvons utiliser une troisième technique qui combine les deux approches : la *tokénisation en sous-mots*. + + +## Tokénisation en sous-mots + + + +Les algorithmes de tokenisation en sous-mots reposent sur le principe selon lequel les mots fréquemment utilisés ne doivent pas être divisés en sous-mots plus petits, mais les mots rares doivent être décomposés en sous-mots significatifs. + +Par exemple, le mot « maisonnette » peut être considéré comme un mot rare et peut être décomposé en « maison » et « ette ». Ces deux mots sont susceptibles d'apparaître plus fréquemment en tant que sous-mots autonomes, alors qu'en même temps le sens de « maison » est conservé par le sens composite de « maison » et « ette ». + +Voici un exemple montrant comment un algorithme de tokenisation en sous-mots tokeniserait la séquence « *Let's do tokenization* ! » : + + +
+ A subword tokenization algorithm. + +
+ +Ces sous-mots finissent par fournir beaucoup de sens sémantique. Par exemple, ci-dessus, « *tokenization* » a été divisé en « *token* » et « *ization* » : deux *tokens* qui ont un sens sémantique tout en étant peu encombrants (seuls deux *tokens* sont nécessaires pour représenter un long mot). Cela nous permet d'avoir une couverture relativement bonne avec de petits vocabulaires et presque aucun *token* inconnu. + +Cette approche est particulièrement utile dans les langues agglutinantes comme le turc, où l'on peut former des mots complexes (presque) arbitrairement longs en enchaînant des sous-mots. + + +### Et plus encore ! + +Il existe de nombreuses autres techniques. Pour n'en citer que quelques-unes : + +- le *Byte-level BPE* utilisé par exemple dans le GPT-2 +- le *WordPiece* utilisé par exemple dans BERT +- *SentencePiece* ou *Unigram*, utilisés dans plusieurs modèles multilingues. + +Vous devriez maintenant avoir une connaissance suffisante du fonctionnement des tokenizers pour commencer à utiliser l'API. + + +## Chargement et sauvegarde + +Le chargement et la sauvegarde des *tokenizers* est aussi simple que pour les modèles. En fait, c'est basé sur les deux mêmes méthodes : `from_pretrained()` et `save_pretrained()`. Ces méthodes vont charger ou sauvegarder l'algorithme utilisé par le *tokenizer* (un peu comme l'*architecture* du modèle) ainsi que son vocabulaire (un peu comme les *poids* du modèle). + +Le chargement du *tokenizer* de BERT entraîné avec le même *checkpoint* que BERT se fait de la même manière que le chargement du modèle, sauf que nous utilisons la classe `BertTokenizer` : + + +```py +from transformers import BertTokenizer + +tokenizer = BertTokenizer.from_pretrained("bert-base-cased") +``` + +{#if fw === 'pt'} +Similaire à `AutoModel`, la classe `AutoTokenizer` récupère la classe de *tokenizer* appropriée dans la bibliothèque basée sur le nom du *checkpoint*. Elle peut être utilisée directement avec n'importe quel *checkpoint* : + +{:else} +Similaire à `TFAutoModel`, la classe `AutoTokenizer` récupère la classe de *tokenizer* appropriée dans la bibliothèque basée sur le nom du *checkpoint*. Elle peut être utilisée directement avec n'importe quel *checkpoint* : + +{/if} + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +``` + +Nous pouvons à présent utiliser le *tokenizer* comme indiqué dans la section précédente : + +```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]} +``` + +La sauvegarde d'un tokenizer est identique à celle d'un modèle : + +```py +tokenizer.save_pretrained("directory_on_my_computer") +``` + +Nous parlerons plus en détail des `token_type_ids` au [chapitre 3](/course/fr/chapter3) et nous expliquerons la clé `attention_mask` un peu plus tard. Tout d'abord, voyons comment les `input_ids` sont générés. Pour ce faire, nous devons examiner les méthodes intermédiaires du *tokenizer*. + +## Encodage + + + + +La traduction d'un texte en chiffres est connue sous le nom d’*encodage*. L'encodage se fait en deux étapes : la tokenisation, suivie de la conversion en identifiants d'entrée. + +Comme nous l'avons vu, la première étape consiste à diviser le texte en mots (ou parties de mots, symboles de ponctuation, etc.), généralement appelés *tokens*. De nombreuses règles peuvent régir ce processus. C'est pourquoi nous devons instancier le *tokenizer* en utilisant le nom du modèle afin de nous assurer que nous utilisons les mêmes règles que celles utilisées lors du pré-entraînement du modèle. + +La deuxième étape consiste à convertir ces *tokens* en nombres afin de construire un tenseur à partir de ceux-ci ainsi que de les transmettre au modèle. Pour ce faire, le *tokenizer* possède un *vocabulaire*, qui est la partie que nous téléchargeons lorsque nous l'instancions avec la méthode `from_pretrained()`. Encore une fois, nous devons utiliser le même vocabulaire que celui utilisé lors du pré-entraînement du modèle. + +Pour mieux comprendre les deux étapes, nous allons les explorer séparément. A noter que nous utilisons des méthodes effectuant séparément des parties du pipeline de tokenisation afin de montrer les résultats intermédiaires de ces étapes. Néanmoins, en pratique, il faut appeler le *tokenizer* directement sur vos entrées (comme indiqué dans la section 2). + +### Tokenisation + +Le processus de tokenisation est effectué par la méthode `tokenize()` du *tokenizer* : + + +```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) +``` + +La sortie de cette méthode est une liste de chaînes de caractères ou de *tokens* : + +```python out +['Using', 'a', 'transform', '##er', 'network', 'is', 'simple'] +``` + +Ce *tokenizer* est un *tokenizer* de sous-mots : il découpe les mots jusqu'à obtenir des *tokens* qui peuvent être représentés par son vocabulaire. C'est le cas ici avec `transformer` qui est divisé en deux *tokens* : `transform` et `##er`. + +### De tokens aux identifiants d'entrée + +La conversion en identifiants d'entrée est gérée par la méthode `convert_tokens_to_ids()` du *tokenizer* : + + +```py +ids = tokenizer.convert_tokens_to_ids(tokens) + +print(ids) +``` + +```python out +[7993, 170, 11303, 1200, 2443, 1110, 3014] +``` + +Une fois converties en tenseur dans le *framework* approprié, ces sorties peuvent ensuite être utilisées comme entrées d'un modèle, comme nous l'avons vu précédemment dans ce chapitre. + + + +✏️ **Essayez !** Reproduisez les deux dernières étapes (tokénisation et conversion en identifiants d'entrée) sur les phrases des entrées que nous avons utilisées dans la section 2 (« I've been waiting for a HuggingFace course my whole life. » et « I hate this so much! »). Vérifiez que vous obtenez les mêmes identifiants d'entrée que nous avons obtenus précédemment ! + + + +## Décodage + +Le *décodage* va dans l'autre sens : à partir d'indices du vocabulaire nous voulons obtenir une chaîne de caractères. Cela peut être fait avec la méthode `decode()` comme suit : + + +```py +decoded_string = tokenizer.decode([7993, 170, 11303, 1200, 2443, 1110, 3014]) +print(decoded_string) +``` + +```python out +'Using a Transformer network is simple' +``` + +Notez que la méthode `decode` non seulement reconvertit les indices en *tokens* mais regroupe également les *tokens* faisant partie des mêmes mots. Le but étant de produire une phrase lisible. Ce comportement sera extrêmement utile lorsque dans la suite du cours nous utiliserons des modèles pouvant produire du nouveau texte (soit du texte généré à partir d'un *prompt*, soit pour des problèmes de séquence à séquence comme la traduction ou le résumé de texte). + +Vous devriez maintenant comprendre les opérations atomiques qu'un *tokenizer* peut gérer : tokenisation, conversion en identifiants, et reconversion des identifiants en chaîne de caractères. Cependant, nous n'avons fait qu'effleurer la partie émergée de l'iceberg. Dans la section suivante, nous allons pousser notre approche jusqu'à ses limites et voir comment les surmonter. diff --git a/chapters/fr/chapter2/5.mdx b/chapters/fr/chapter2/5.mdx index 8983c0590..fb85ff966 100644 --- a/chapters/fr/chapter2/5.mdx +++ b/chapters/fr/chapter2/5.mdx @@ -51,12 +51,13 @@ 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." # J'ai attendu un cours d’HuggingFace toute ma vie. +sequence = "I've been waiting for a HuggingFace course my whole life." +# J'ai attendu un cours d’HuggingFace toute ma vie. tokens = tokenizer.tokenize(sequence) ids = tokenizer.convert_tokens_to_ids(tokens) input_ids = torch.tensor(ids) -# This line will fail. +# Cette ligne va échouer. model(input_ids) ``` @@ -72,7 +73,8 @@ 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." # J'ai attendu un cours d’HuggingFace toute ma vie. +sequence = "I've been waiting for a HuggingFace course my whole life." +# J'ai attendu un cours d’HuggingFace toute ma vie. tokens = tokenizer.tokenize(sequence) ids = tokenizer.convert_tokens_to_ids(tokens) @@ -125,7 +127,8 @@ 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." # J'ai attendu un cours d’HuggingFace toute ma vie. +sequence = "I've been waiting for a HuggingFace course my whole life." +# J'ai attendu un cours d’HuggingFace toute ma vie. tokens = tokenizer.tokenize(sequence) @@ -191,9 +194,9 @@ Il s'agit d'un batch de deux séquences identiques ! -Utiliser des *batchs* permet au modèle de fonctionner lorsque vous lui donnez plusieurs séquences. Utiliser plusieurs séquences est aussi simple que de construire un batch avec une seule séquence. Il y a cependant un deuxième problème. Lorsque vous essayez de regrouper deux phrases (ou plus), elles peuvent être de longueurs différentes. Si vous avez déjà travaillé avec des tenseurs, vous savez qu'ils doivent être de forme rectangulaire. Vous ne pourrez donc pas convertir directement la liste des identifiants d'entrée en un tenseur. Pour contourner ce problème, nous avons l'habitude de *rembourrer* (le *padding* en anglais) les entrées. +Utiliser des *batchs* permet au modèle de fonctionner lorsque vous lui donnez plusieurs séquences. Utiliser plusieurs séquences est aussi simple que de construire un batch avec une seule séquence. Il y a cependant un deuxième problème. Lorsque vous essayez de regrouper deux phrases (ou plus), elles peuvent être de longueurs différentes. Si vous avez déjà travaillé avec des tenseurs, vous savez qu'ils doivent être de forme rectangulaire. Vous ne pourrez donc pas convertir directement la liste des identifiants d'entrée en un tenseur. Pour contourner ce problème, nous avons l'habitude de *rembourrer*/*remplir* (le *padding* en anglais) les entrées. -## *Padding* des entrées +## Padding des entrées La liste de listes suivante ne peut pas être convertie en un tenseur : @@ -329,7 +332,7 @@ Remarquez comment la dernière valeur de la deuxième séquence est un identifia -✏️ **Essayez !** Appliquez la tokenisation manuellement sur les deux phrases utilisées dans la section 2 (« I've been waiting for a HuggingFace course my whole life. » et « I hate this so much! »). Passez-les dans le modèle et vérifiez que vous obtenez les mêmes logits que dans la section 2. Ensuite regroupez-les en utilisant le jeton de *padding* et créez le masque d'attention approprié. Vérifiez que vous obtenez les mêmes résultats qu’en passant par le modèle ! +✏️ **Essayez !** Appliquez la tokenisation manuellement sur les deux phrases utilisées dans la section 2 (« I've been waiting for a HuggingFace course my whole life. » et « I hate this so much! »). Passez-les dans le modèle et vérifiez que vous obtenez les mêmes logits que dans la section 2. Ensuite regroupez-les en utilisant le jeton de *padding* et créez le masque d'attention approprié. Vérifiez que vous obtenez les mêmes résultats qu’en passant par le modèle ! diff --git a/chapters/fr/chapter2/6.mdx b/chapters/fr/chapter2/6.mdx index 00580b774..9602b2a96 100644 --- a/chapters/fr/chapter2/6.mdx +++ b/chapters/fr/chapter2/6.mdx @@ -33,7 +33,8 @@ 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." # J'ai attendu un cours d’HuggingFace toute ma vie. +sequence = "I've been waiting for a HuggingFace course my whole life." +# J'ai attendu un cours d’HuggingFace toute ma vie. model_inputs = tokenizer(sequence) ``` @@ -44,7 +45,8 @@ Comme nous allons le voir dans les quelques exemples ci-dessous, cette méthode ```py -sequence = "I've been waiting for a HuggingFace course my whole life." # J'ai attendu un cours d’HuggingFace toute ma vie. +sequence = "I've been waiting for a HuggingFace course my whole life." +# J'ai attendu un cours d’HuggingFace toute ma vie. model_inputs = tokenizer(sequence) @@ -68,8 +70,7 @@ Il est possible de faire du *padding* selon plusieurs objectifs : # Remplit les séquences jusqu'à la longueur maximale de la séquence model_inputs = tokenizer(sequences, padding="longest") -# Remplit les séquences jusqu'à la longueur maximale du modèle -# (512 for BERT or DistilBERT) +# Remplit les séquences jusqu'à la longueur maximale du modèle (512 pour BERT ou DistilBERT) model_inputs = tokenizer(sequences, padding="max_length") # Remplit les séquences jusqu'à la longueur maximale spécifiée @@ -85,7 +86,7 @@ sequences = [ ] # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! » # Tronque les séquences qui sont plus longues que la longueur maximale du modèle -# (512 for BERT or DistilBERT) +# (512 pour BERT ou DistilBERT) model_inputs = tokenizer(sequences, truncation=True) # Tronque les séquences qui sont plus longues que la longueur maximale spécifiée @@ -116,7 +117,8 @@ Si nous jetons un coup d'œil aux identifiants d'entrée renvoyés par le *token ```py -sequence = "I've been waiting for a HuggingFace course my whole life." # « J'ai attendu un cours de HuggingFace toute ma vie. » +sequence = "I've been waiting for a HuggingFace course my whole life." +# « J'ai attendu un cours de HuggingFace toute ma vie. » model_inputs = tokenizer(sequence) print(model_inputs["input_ids"]) @@ -145,7 +147,7 @@ print(tokenizer.decode(ids)) Le *tokenizer* a ajouté le mot spécial `[CLS]` au début et le mot spécial `[SEP]` à la fin. C'est parce que le modèle a été pré-entraîné avec ces mots, donc pour obtenir les mêmes résultats pour l'inférence, nous devons également les ajouter. Notez que certains modèles n'ajoutent pas de mots spéciaux, ou en ajoutent des différents. Les modèles peuvent aussi ajouter ces mots spéciaux seulement au début, ou seulement à la fin. Dans tous les cas, le *tokenizer* sait lesquels sont attendus et s'en occupe pour vous. -## Conclusion : du *tokenizer* au modèle +## Conclusion : du tokenizer au modèle Maintenant que nous avons vu toutes les étapes individuelles que l'objet `tokenizer` utilise lorsqu'il est appliqué sur des textes, voyons une dernière fois comment il peut gérer plusieurs séquences (*padding*), de très longues séquences (*troncation*) et plusieurs types de tenseurs avec son API principale : diff --git a/chapters/fr/chapter2/7.mdx b/chapters/fr/chapter2/7.mdx index c6fac5c9a..2b6c11798 100644 --- a/chapters/fr/chapter2/7.mdx +++ b/chapters/fr/chapter2/7.mdx @@ -1,12 +1,12 @@ -# Utilisation de base terminée ! - -Bravo à vous pour avoir suivi le cours jusqu'ici ! Pour récapituler, dans ce chapitre vous avez : -- appris les blocs de construction de base d'un *transformer*, -- appris ce qui constitue un pipeline de tokenisation, -- vu comment utiliser un *transformer* en pratique, -- appris comment tirer parti d'un *tokenizer* pour convertir du texte en tenseurs compréhensibles par le modèle, -- configurer ensemble un *tokenizer* et un modèle afin de passer du texte aux prédictions, -- appris les limites des identifiants d'entrée et ce que sont que les masques d'attention, -- joué avec des méthodes de *tokenizer* polyvalentes et configurables. - -À partir de maintenant, vous devriez être en mesure de naviguer librement dans la documentation 🤗 *Transformers*. Le vocabulaire vous semblera familier et vous avez vu les méthodes que vous utiliserez la plupart du temps. +# Utilisation de base terminée ! + +Bravo à vous pour avoir suivi le cours jusqu'ici ! Pour récapituler, dans ce chapitre vous avez : +- appris les blocs de construction de base d'un *transformer*, +- appris ce qui constitue un pipeline de tokenisation, +- vu comment utiliser un *transformer* en pratique, +- appris comment tirer parti d'un *tokenizer* pour convertir du texte en tenseurs compréhensibles par le modèle, +- configurer ensemble un *tokenizer* et un modèle afin de passer du texte aux prédictions, +- appris les limites des identifiants d'entrée et ce que sont que les masques d'attention, +- joué avec des méthodes de *tokenizer* polyvalentes et configurables. + +À partir de maintenant, vous devriez être en mesure de naviguer librement dans la documentation 🤗 *Transformers*. Le vocabulaire vous semblera familier et vous avez vu les méthodes que vous utiliserez la plupart du temps. diff --git a/chapters/fr/chapter2/8.mdx b/chapters/fr/chapter2/8.mdx index 7ea13a174..a3fa30228 100644 --- a/chapters/fr/chapter2/8.mdx +++ b/chapters/fr/chapter2/8.mdx @@ -1,307 +1,307 @@ - - - - -# Quiz de fin de chapitre - -### 1. Quel est l'ordre du pipeline de modélisation du langage ? - - -tokenizer donne un sens à ces prédictions et les reconvertit en texte si nécessaire.", - explain: " Le modèle ne peut pas comprendre le texte ! Le tokenizer doit d'abord tokeniser le texte et le convertir en identifiants afin qu'il soit compréhensible par le modèle."}, - { - text: " Tout d'abord, le tokenizer, qui traite le texte et renvoie des identifiants. Puis le modèle traite ces identifiants et produit une prédiction, qui peut être du texte.", - explain: " La prédiction du modèle ne peut pas être du texte immédiatement. Le tokenizer doit être utilisé afin de reconvertir la prédiction en texte !"}, - { - text: " Le tokenizer traite le texte et renvoie des identifiants. Le modèle traite ces identifiants et produit une prédiction. Le tokenizer peut alors être utilisé à nouveau pour reconvertir ces prédictions en texte.", - explain: " C’est correct ! Le tokenizer peut être utilisé à la fois pour la tokenisation et la dé-tokénisation.", - correct: true - } - ]} -/> - -### 2. Combien de dimensions le tenseur produit par le transformer de base possède-t-il et quelles sont-elles ? - - -transformers gèrent les batchs, même avec une seule séquence ce serait une taille de batch de 1 !" - }, - { - text: "3: la longueur de la séquence, la taille du batch et la taille cachée.", - explain: "C’est correct !", - correct: true - } - ]} -/> - -### 3. Lequel des éléments suivants est un exemple de tokenisation en sous-mots ? - - - -### 4. Qu'est-ce qu'une tête de modèle ? - -transformer de base qui redirige les tenseurs vers leurs couches correctes.", - explain: "Incorrect ! Il n'y a pas de tel composant." - }, - { - text: "Également connu sous le nom de mécanisme d'auto-attention, il adapte la représentation d'un token en fonction des autres tokens de la séquence.", - explain: "Incorrect ! La couche d'auto-attention contient des têtes d'attention mais ce ne sont pas des têtes d'adaptation." - }, - { - text: "Un composant supplémentaire, généralement constitué d'une ou plusieurs couches, pour convertir les prédictions du transformer en une sortie spécifique à la tâche.", - explain: "C'est exact. Les têtes d'adaptation, aussi appelées simplement têtes, se présentent sous différentes formes : têtes de modélisation du langage, têtes de réponse aux questions, têtes de classification des séquences, etc.", - correct: true - } - ]} -/> - -{#if fw === 'pt'} -### 5. Qu'est-ce qu'un AutoModel? - -checkpoints et modèles soient capables de gérer plusieurs langues, il n'existe pas d'outils intégrés pour la sélection automatique des checkpoints en fonction de la langue. Vous devez vous rendre sur le Hub des modèles pour trouver le meilleur checkpoint pour votre tâche !" - } - ]} -/> - -{:else} -### 5. What is an AutoModel? - -checkpoints et modèles soient capables de gérer plusieurs langues, il n'existe pas d'outils intégrés pour la sélection automatique des checkpoints en fonction de la langue. Vous devez vous rendre sur le Hub des modèles pour trouver le meilleur checkpoint pour votre tâche !" - } - ]} -/> - -{/if} - -### 6. Quelles sont les techniques à connaître lors de la mise en batch de séquences de longueurs différentes ? - - -padding", - explain: "Oui, le padding est une façon correcte d'égaliser les séquences pour qu'elles tiennent dans une forme rectangulaire. Mais est-ce le seul moyen ?", - correct: true - }, - { - text: "Les masques d'attention ", - explain: "Absolument ! Les masques d'attention sont d'une importance capitale lorsqu'on manipule des séquences de longueurs différentes. Ce n'est cependant pas la seule technique à laquelle il faut faire attention.", - correct: true - } - ]} -/> - -### 7. Quel est l'intérêt d'appliquer une fonction SoftMax aux logits produits par un modèle de classification de séquences ? - - - -### 8. Autour de quelle méthode s'articule la majeure partie de l'API tokenizer ? - -encode, car elle peut encoder du texte en identifiants et des identifiants en prédictions.", - explain: "Faux ! Bien que la méthode encode existe sur les tokenizer, elle n'existe pas sur les modèles." - }, - { - text: "Appeler directement l'objet tokenizer", - explain: " Exactement ! La méthode __call__ du tokenizer est une méthode très puissante qui peut traiter à peu près tout. C'est également la méthode utilisée pour récupérer les prédictions d'un modèle.", - correct: true - }, - { - text: "pad", - explain: "C'est faux ! Le padding est très utile mais ce n'est qu'une partie de l'API tokenizer." - }, - { - text: "tokenize", - explain: "La méthode tokenize est est sans doute l'une des méthodes les plus utiles, mais elle ne constitue pas le cœur de l'API tokenizer." - } - ]} -/> - -### 9. Que contient la variable `result` dans cet exemple de code ? - -```py -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") -result = tokenizer.tokenize("Hello!") -``` - -token.", - explain: " Absolument ! Convertissez cela en identifiants, et donnez-les à un modèle !", - correct: true - }, - { - text: "Une liste d'identifiants", - explain: "C'est faux, c'est à cela que la méthode __call__ ou la méthode convert_tokens_to_ids sert !" - }, - { - text: "Une chaîne contenant tous les tokens", - explain: "Ce serait sous-optimal car le but est de diviser la chaîne de caractères en plusieurs éléments." - } - ]} -/> - -{#if fw === 'pt'} -### 10. Y a-t-il un problème avec le code suivant ? - - -```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) -``` - -tokenizer qui a été entraîné avec un checkpoint différent est rarement une bonne idée. Le modèle n'a pas été entraîné pour donner du sens à la sortie de ce tokenizer donc la sortie du modèle (s'il peut même fonctionner !) n'aura aucun sens." - }, - { - text: " Le tokenizer et le modèle doivent toujours provenir du même checkpoint.", - explain: "C’est juste !", - correct: true - }, - { - text: " C'est une bonne pratique de faire du padding et de troncage avec le tokenizer car chaque entrée est un batch.", - explain: "Il est vrai que chaque entrée de modèle doit être un batch. Cependant, tronquer ou compléter cette séquence n'aurait pas nécessairement de sens puisqu'il n'y en a qu'une seule. Il s'agit là de techniques permettant de mettre en batch une liste de phrases." - } - ]} -/> - -{:else} -### 10. Y a-t-il un problème avec le code suivant ? - -```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) -``` - -tokenizer qui a été entraîné avec un checkpoint différent est rarement une bonne idée. Le modèle n'a pas été entraîné pour donner du sens à la sortie de ce tokenizer donc la sortie du modèle (s'il peut même fonctionner !) n'aura aucun sens." - }, - { - text: " Le tokenizer et le modèle doivent toujours provenir du même checkpoint.", - explain: "C’est juste !", - correct: true - }, - { - text: " C'est une bonne pratique de faire du padding et de troncage avec le tokenizer car chaque entrée est un batch.", - explain: "Il est vrai que chaque entrée de modèle doit être un batch. Cependant, tronquer ou compléter cette séquence n'aurait pas nécessairement de sens puisqu'il n'y en a qu'une seule. Il s'agit là de techniques permettant de mettre en batch une liste de phrases." - } - ]} -/> - -{/if} + + + + +# Quiz de fin de chapitre + +### 1. Quel est l'ordre du pipeline de modélisation du langage ? + + +tokenizer donne un sens à ces prédictions et les reconvertit en texte si nécessaire.", + explain: " Le modèle ne peut pas comprendre le texte ! Le tokenizer doit d'abord tokeniser le texte et le convertir en identifiants afin qu'il soit compréhensible par le modèle."}, + { + text: " Tout d'abord, le tokenizer, qui traite le texte et renvoie des identifiants. Puis le modèle traite ces identifiants et produit une prédiction, qui peut être du texte.", + explain: " La prédiction du modèle ne peut pas être du texte immédiatement. Le tokenizer doit être utilisé afin de reconvertir la prédiction en texte !"}, + { + text: " Le tokenizer traite le texte et renvoie des identifiants. Le modèle traite ces identifiants et produit une prédiction. Le tokenizer peut alors être utilisé à nouveau pour reconvertir ces prédictions en texte.", + explain: " Le tokenizer peut être utilisé à la fois pour la tokenisation et la dé-tokénisation.", + correct: true + } + ]} +/> + +### 2. Combien de dimensions le tenseur produit par le transformer de base possède-t-il et quelles sont-elles ? + + +transformers gèrent les batchs, même avec une seule séquence ce serait une taille de batch de 1 !" + }, + { + text: "3: la longueur de la séquence, la taille du batch et la taille cachée.", + explain: "", + correct: true + } + ]} +/> + +### 3. Lequel des éléments suivants est un exemple de tokenisation en sous-mots ? + + + +### 4. Qu'est-ce qu'une tête de modèle ? + +transformer de base qui redirige les tenseurs vers leurs couches correctes.", + explain: "Il n'y a pas de tel composant." + }, + { + text: "Également connu sous le nom de mécanisme d'auto-attention, il adapte la représentation d'un token en fonction des autres tokens de la séquence.", + explain: "La couche d'auto-attention contient des têtes d'attention mais ce ne sont pas des têtes d'adaptation." + }, + { + text: "Un composant supplémentaire, généralement constitué d'une ou plusieurs couches, pour convertir les prédictions du transformer en une sortie spécifique à la tâche.", + explain: "Les têtes d'adaptation, aussi appelées simplement têtes, se présentent sous différentes formes : têtes de modélisation du langage, têtes de réponse aux questions, têtes de classification des séquences, etc.", + correct: true + } + ]} +/> + +{#if fw === 'pt'} +### 5. Qu'est-ce qu'un AutoModel? + +checkpoints et modèles soient capables de gérer plusieurs langues, il n'existe pas d'outils intégrés pour la sélection automatique des checkpoints en fonction de la langue. Vous devez vous rendre sur le Hub des modèles pour trouver le meilleur checkpoint pour votre tâche !" + } + ]} +/> + +{:else} +### 5. What is an AutoModel? + +checkpoints et modèles soient capables de gérer plusieurs langues, il n'existe pas d'outils intégrés pour la sélection automatique des checkpoints en fonction de la langue. Vous devez vous rendre sur le Hub des modèles pour trouver le meilleur checkpoint pour votre tâche !" + } + ]} +/> + +{/if} + +### 6. Quelles sont les techniques à connaître lors de la mise en batch de séquences de longueurs différentes ? + + +padding", + explain: "Le padding est une façon correcte d'égaliser les séquences pour qu'elles tiennent dans une forme rectangulaire. Mais est-ce le seul moyen ?", + correct: true + }, + { + text: "Les masques d'attention ", + explain: "Les masques d'attention sont d'une importance capitale lorsqu'on manipule des séquences de longueurs différentes. Ce n'est cependant pas la seule technique à laquelle il faut faire attention.", + correct: true + } + ]} +/> + +### 7. Quel est l'intérêt d'appliquer une fonction SoftMax aux logits produits par un modèle de classification de séquences ? + + + +### 8. Autour de quelle méthode s'articule la majeure partie de l'API tokenizer ? + +encode, car elle peut encoder du texte en identifiants et des identifiants en prédictions.", + explain: "Bien que la méthode encode existe sur les tokenizer, elle n'existe pas sur les modèles." + }, + { + text: "Appeler directement l'objet tokenizer", + explain: "La méthode __call__ du tokenizer est une méthode très puissante qui peut traiter à peu près tout. C'est également la méthode utilisée pour récupérer les prédictions d'un modèle.", + correct: true + }, + { + text: "pad", + explain: "Le padding est très utile mais ce n'est qu'une partie de l'API tokenizer." + }, + { + text: "tokenize", + explain: "La méthode tokenize est est sans doute l'une des méthodes les plus utiles, mais elle ne constitue pas le cœur de l'API tokenizer." + } + ]} +/> + +### 9. Que contient la variable `result` dans cet exemple de code ? + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +result = tokenizer.tokenize("Hello!") +``` + +token.", + explain: "Convertissez cela en identifiants, et donnez-les à un modèle !", + correct: true + }, + { + text: "Une liste d'identifiants", + explain: "C'est à cela que la méthode __call__ ou la méthode convert_tokens_to_ids sert !" + }, + { + text: "Une chaîne contenant tous les tokens", + explain: "Ce serait sous-optimal car le but est de diviser la chaîne de caractères en plusieurs éléments." + } + ]} +/> + +{#if fw === 'pt'} +### 10. Y a-t-il un problème avec le code suivant ? + + +```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) +``` + +tokenizer qui a été entraîné avec un checkpoint différent est rarement une bonne idée. Le modèle n'a pas été entraîné pour donner du sens à la sortie de ce tokenizer donc la sortie du modèle (s'il peut même fonctionner !) n'aura aucun sens." + }, + { + text: " Le tokenizer et le modèle doivent toujours provenir du même checkpoint.", + explain: "", + correct: true + }, + { + text: " C'est une bonne pratique de faire du padding et de troncage avec le tokenizer car chaque entrée est un batch.", + explain: "Il est vrai que chaque entrée de modèle doit être un batch. Cependant, tronquer ou compléter cette séquence n'aurait pas nécessairement de sens puisqu'il n'y en a qu'une seule. Il s'agit là de techniques permettant de mettre en batch une liste de phrases." + } + ]} +/> + +{:else} +### 10. Y a-t-il un problème avec le code suivant ? + +```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) +``` + +tokenizer qui a été entraîné avec un checkpoint différent est rarement une bonne idée. Le modèle n'a pas été entraîné pour donner du sens à la sortie de ce tokenizer donc la sortie du modèle (s'il peut même fonctionner !) n'aura aucun sens." + }, + { + text: " Le tokenizer et le modèle doivent toujours provenir du même checkpoint.", + explain: "", + correct: true + }, + { + text: " C'est une bonne pratique de faire du padding et de troncage avec le tokenizer car chaque entrée est un batch.", + explain: "Il est vrai que chaque entrée de modèle doit être un batch. Cependant, tronquer ou compléter cette séquence n'aurait pas nécessairement de sens puisqu'il n'y en a qu'une seule. Il s'agit là de techniques permettant de mettre en batch une liste de phrases." + } + ]} +/> + +{/if} diff --git a/chapters/fr/chapter3/2.mdx b/chapters/fr/chapter3/2.mdx index 498b99153..297c260f5 100644 --- a/chapters/fr/chapter3/2.mdx +++ b/chapters/fr/chapter3/2.mdx @@ -35,7 +35,8 @@ checkpoint = "bert-base-uncased" tokenizer = AutoTokenizer.from_pretrained(checkpoint) model = AutoModelForSequenceClassification.from_pretrained(checkpoint) sequences = [ - "I've been waiting for a HuggingFace course my whole life.", # J'ai attendu un cours de HuggingFace toute ma vie. + "I've been waiting for a HuggingFace course my whole life.", + # J'ai attendu un cours de HuggingFace toute ma vie. "This course is amazing!", # Ce cours est incroyable ! ] batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") @@ -62,7 +63,8 @@ checkpoint = "bert-base-uncased" tokenizer = AutoTokenizer.from_pretrained(checkpoint) model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) sequences = [ - "I've been waiting for a HuggingFace course my whole life.", # J'ai attendu un cours de HuggingFace toute ma vie. + "I've been waiting for a HuggingFace course my whole life.", + # J'ai attendu un cours de HuggingFace toute ma vie. "This course is amazing!", # Ce cours est incroyable ! ] batch = dict(tokenizer(sequences, padding=True, truncation=True, return_tensors="tf")) @@ -77,7 +79,7 @@ Evidemment, entraîner un modèle avec seulement deux phrases ne va pas donner d Dans cette section, nous allons utiliser comme exemple le jeu de données MRPC (*Microsoft Research Paraphrase Corpus*) présenté dans un [papier](https://www.aclweb.org/anthology/I05-5002.pdf) par William B. Dolan et Chris Brockett. Ce jeu de données contient 5801 paires de phrases avec un label indiquant si ces paires sont des paraphrases ou non (i.e. si elles ont la même signification). Nous l'avons choisi pour ce chapitre parce que c'est un petit jeu de données et cela rend donc simples les expériences d'entraînement sur ce jeu de données. -### Charger un jeu de données depuis le *Hub* +### Charger un jeu de données depuis le Hub {#if fw === 'pt'} @@ -127,8 +129,10 @@ raw_train_dataset[0] ```python out {'idx': 0, 'label': 1, - 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', # Amrozi a accusé son frère, qu'il a appelé « le témoin », de déformer délibérément son témoignage. - 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} # Se référant à lui uniquement comme « le témoin », Amrozi a accusé son frère de déformer délibérément son témoignage. + 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', + # Amrozi a accusé son frère, qu'il a appelé « le témoin », de déformer délibérément son témoignage. + 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} + # Se référant à lui uniquement comme « le témoin », Amrozi a accusé son frère de déformer délibérément son témoignage. ``` Nous pouvons voir que les étiquettes sont déjà des entiers, donc nous n'aurons pas à faire de prétraitement ici. Pour savoir quel entier correspond à quel label, nous pouvons inspecter les `features` de notre `raw_train_dataset`. Cela nous indiquera le type de chaque colonne : @@ -187,7 +191,7 @@ inputs } ``` -Nous avons discuté des clés `input_ids` et `attention_mask` dans le [Chapitre 2](/course/fr/chapter2), mais nous avons laissé de côté les `token_type_ids`. Dans cet exemple, c'est ce qui indique au modèle quelle partie de l'entrée est la première phrase et quelle partie est la deuxième phrase. +Nous avons discuté des clés `input_ids` et `attention_mask` dans le [chapitre 2](/course/fr/chapter2), mais nous avons laissé de côté les `token_type_ids`. Dans cet exemple, c'est ce qui indique au modèle quelle partie de l'entrée est la première phrase et quelle partie est la deuxième phrase. @@ -218,13 +222,13 @@ Comme vous pouvez le voir, les parties de l'entrée correspondant à `[CLS] sent Notez que si vous choisissez un autre *checkpoint*, vous n'aurez pas nécessairement les `token_type_ids` dans vos entrées tokenisées (par exemple, ils ne sont pas retournés si vous utilisez un modèle DistilBERT). Ils ne sont retournés que lorsque le modèle sait quoi faire avec eux, parce qu'il les a vus pendant son pré-entraînement. -Ici, BERT est pré-entraîné avec les *tokens* de type ID et en plus de l'objectif de modélisation du langage masqué dont nous avons abordé dans [Chapitre 1](/course/fr/chapter1), il a un objectif supplémentaire appelé _prédiction de la phrase suivante_. Le but de cette tâche est de modéliser la relation entre des paires de phrases. +Ici, BERT est pré-entraîné avec les *tokens* de type ID et en plus de l'objectif de modélisation du langage masqué dont nous avons abordé dans [chapitre 1](/course/fr/chapter1), il a un objectif supplémentaire appelé _prédiction de la phrase suivante_. Le but de cette tâche est de modéliser la relation entre des paires de phrases. Avec la prédiction de la phrase suivante, on fournit au modèle des paires de phrases (avec des *tokens* masqués de manière aléatoire) et on lui demande de prédire si la deuxième phrase suit la première. Pour rendre la tâche non triviale, la moitié du temps, les phrases se suivent dans le document d'origine dont elles ont été extraites, et l'autre moitié du temps, les deux phrases proviennent de deux documents différents. En général, vous n'avez pas besoin de vous inquiéter de savoir s'il y a ou non des `token_type_ids` dans vos entrées tokenisées : tant que vous utilisez le même *checkpoint* pour le *tokenizer* et le modèle, tout ira bien puisque le *tokenizer* sait quoi fournir à son modèle. -Maintenant que nous avons vu comment notre *tokenizer* peut traiter une paire de phrases, nous pouvons l'utiliser pour tokeniser l'ensemble de notre jeu de données : comme dans le [chapitre précédent](/course/fr/chapter2), nous pouvons fournir au *tokenizer* une liste de paires de phrases en lui donnant la liste des premières phrases, puis la liste des secondes phrases. Ceci est également compatible avec les options de remplissage et de troncature que nous avons vues dans le [Chapitre 2](/course/fr/chapter2). Voici donc une façon de prétraiter le jeu de données d'entraînement : +Maintenant que nous avons vu comment notre *tokenizer* peut traiter une paire de phrases, nous pouvons l'utiliser pour tokeniser l'ensemble de notre jeu de données : comme dans le [chapitre précédent](/course/fr/chapter2), nous pouvons fournir au *tokenizer* une liste de paires de phrases en lui donnant la liste des premières phrases, puis la liste des secondes phrases. Ceci est également compatible avec les options de remplissage et de troncature que nous avons vues dans le [chapitre 2](/course/fr/chapter2). Voici donc une façon de prétraiter le jeu de données d'entraînement : ```py tokenized_dataset = tokenizer( @@ -280,7 +284,7 @@ Notre `tokenize_function` retourne un dictionnaire avec les clés `input_ids`, ` La dernière chose que nous devrons faire est de remplir tous les exemples à la longueur de l'élément le plus long lorsque nous regroupons les éléments, une technique que nous appelons le *padding dynamique*. -### *Padding* dynamique +### Padding dynamique diff --git a/chapters/fr/chapter3/3.mdx b/chapters/fr/chapter3/3.mdx index a23832502..13563fb0d 100644 --- a/chapters/fr/chapter3/3.mdx +++ b/chapters/fr/chapter3/3.mdx @@ -1,6 +1,6 @@ -# *Finetuner* un modèle avec l'API Trainer +# Finetuner un modèle avec l'API Trainer -💡 Si vous voulez télécharger automatiquement votre modèle sur le *Hub* pendant l'entraînement, passez `push_to_hub=True` dans le `TrainingArguments`. Nous en apprendrons plus à ce sujet au [Chapitre 4](/course/fr/chapter4/3). +💡 Si vous voulez télécharger automatiquement votre modèle sur le *Hub* pendant l'entraînement, passez `push_to_hub=True` dans le `TrainingArguments`. Nous en apprendrons plus à ce sujet au [chapitre 4](/course/fr/chapter4/3). @@ -56,7 +56,7 @@ from transformers import AutoModelForSequenceClassification model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) ``` -Vous remarquerez que contrairement au [Chapitre 2](/course/fr/chapter2), vous obtenez un message d'avertissement après l'instanciation de ce modèle pré-entraîné. C'est parce que BERT n'a pas été pré-entraîné à la classification de paires de phrases, donc la tête du modèle pré-entraîné a été supprimée et une nouvelle tête adaptée à la classification de séquences a été ajoutée à la place. Les messages d'avertissement indiquent que certains poids n'ont pas été utilisés (ceux correspondant à la tête de pré-entraînement abandonnée) et que d'autres ont été initialisés de manière aléatoire (ceux pour la nouvelle tête). Il conclut en vous encourageant à entraîner le modèle, ce qui est exactement ce que nous allons faire maintenant. +Vous remarquerez que contrairement au [chapitre 2](/course/fr/chapter2), vous obtenez un message d'avertissement après l'instanciation de ce modèle pré-entraîné. C'est parce que BERT n'a pas été pré-entraîné à la classification de paires de phrases, donc la tête du modèle pré-entraîné a été supprimée et une nouvelle tête adaptée à la classification de séquences a été ajoutée à la place. Les messages d'avertissement indiquent que certains poids n'ont pas été utilisés (ceux correspondant à la tête de pré-entraînement abandonnée) et que d'autres ont été initialisés de manière aléatoire (ceux pour la nouvelle tête). Il conclut en vous encourageant à entraîner le modèle, ce qui est exactement ce que nous allons faire maintenant. Une fois que nous avons notre modèle, nous pouvons définir un `Trainer` en lui passant tous les objets construits jusqu'à présent : le `model`, le `training_args`, les jeux de données d'entraînement et de validation, notre `data_collator`, et notre `tokenizer` : @@ -162,7 +162,7 @@ Cette fois, il indiquera la perte et les mesures de validation à la fin de chaq Le `Trainer` fonctionnera sur plusieurs GPUs ou TPUs et fournit beaucoup d'options, comme l'entraînement en précision mixte (utilisez `fp16 = True` dans vos arguments d'entraînement). Nous passerons en revue tout ce qu'il supporte dans le chapitre 10. -Ceci conclut l'introduction au *fine-tuning* en utilisant l'API `Trainer`. Un exemple d'utilisation pour les tâches de NLP les plus communes es donné dans le [Chapitre 7](/course/fr/chapter7), mais pour l'instant regardons comment faire la même chose en PyTorch pur. +Ceci conclut l'introduction au *fine-tuning* en utilisant l'API `Trainer`. Un exemple d'utilisation pour les tâches de NLP les plus communes es donné dans le [chapitre 7](/course/fr/chapter7), mais pour l'instant regardons comment faire la même chose en PyTorch pur. diff --git a/chapters/fr/chapter3/3_tf.mdx b/chapters/fr/chapter3/3_tf.mdx index bc96a7d05..6be37c3a3 100644 --- a/chapters/fr/chapter3/3_tf.mdx +++ b/chapters/fr/chapter3/3_tf.mdx @@ -1,190 +1,190 @@ - - -# *Finetuner* un modèle avec Keras - - - -Une fois que vous avez fait tout le travail de prétraitement des données dans la dernière section, il ne vous reste que quelques étapes pour entraîner le modèle. Notez, cependant, que la commande `model.fit()` s'exécutera très lentement sur un CPU. Si vous n'avez pas de GPU, vous pouvez avoir accès à des GPUs ou TPUs gratuits sur [Google Colab](https://colab.research.google.com/). - -Les exemples de code ci-dessous supposent que vous avez déjà exécuté les exemples de la section précédente. Voici un bref résumé de ce dont vous avez besoin : - -```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, -) -``` - -### Entraînement - -Les modèles TensorFlow importés depuis 🤗 *Transformers* sont déjà des modèles Keras. Voici une courte introduction à Keras. - - - -Cela signifie qu'une fois que nous disposons de nos données, très peu de travail est nécessaire pour commencer à entraîner sur celles-ci. - - - -Comme dans le [chapitre précédent](/course/fr/chapter2), nous allons utiliser la classe `TFAutoModelForSequenceClassification`, avec deux étiquettes : - -```py -from transformers import TFAutoModelForSequenceClassification - -model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) -``` - -Vous remarquerez que, contrairement au [Chapitre 2](/course/fr/chapter2), vous obtenez un message d'avertissement après l'instanciation de ce modèle pré-entraîné. Ceci est dû au fait que BERT n'a pas été pré-entraîné à la classification de paires de phrases, donc la tête du modèle pré-entraîné a été supprimée et une nouvelle tête adaptée à la classification de séquences a été insérée à la place. Les messages d'avertissement indiquent que certains poids n'ont pas été utilisés (ceux correspondant à la tête de pré-entraînement abandonnée) et que d'autres ont été initialisés de manière aléatoire (ceux pour la nouvelle tête). Il conclut en vous encourageant à entraîner le modèle, ce qui est exactement ce que nous allons faire maintenant. - -Pour *finetuner* le modèle sur notre jeu de données, nous devons simplement `compiler()` notre modèle et ensuite passer nos données à la méthode `fit()`. Cela va démarrer le processus de *finetuning* (qui devrait prendre quelques minutes sur un GPU) et rapporter la perte d'entraînement au fur et à mesure, plus la perte de validation à la fin de chaque époque. - - - -Notez que les modèles 🤗 *Transformers* ont une capacité spéciale que la plupart des modèles Keras n'ont pas. Ils peuvent automatiquement utiliser une perte appropriée qu'ils calculent en interne. Ils utiliseront cette perte par défaut si vous ne définissez pas un argument de perte dans `compile()`. Notez que pour utiliser la perte interne, vous devrez passer vos labels comme faisant partie de l'entrée, et non pas comme un label séparé, ce qui est la façon normale d'utiliser les labels avec les modèles Keras. Vous verrez des exemples de cela dans la partie 2 du cours, où la définition de la fonction de perte correcte peut être délicate. Pour la classification des séquences, cependant, une fonction de perte standard de Keras fonctionne bien, et c'est donc ce que nous utiliserons ici. - - - -```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, -) -``` - - - -Notez un piège très commun ici. Vous *pouvez* simplement passer le nom de la perte comme une chaîne à Keras, mais par défaut Keras supposera que vous avez déjà appliqué une fonction softmax à vos sorties. Cependant, de nombreux modèles produisent les valeurs juste avant l'application de la softmax, que l'on appelle aussi les *logits*. Nous devons indiquer à la fonction de perte que c'est ce que fait notre modèle, et la seule façon de le faire est de l'appeler directement, plutôt que par son nom avec une chaîne. - - - - -### Améliorer les performances d'entraînement - - - -Si vous essayez le code ci-dessus, il fonctionne certainement, mais vous constaterez que la perte ne diminue que lentement ou sporadiquement. La cause principale est le *taux d'apprentissage*. Comme pour la perte, lorsque nous transmettons à Keras le nom d'un optimiseur sous forme de chaîne de caractères, Keras initialise cet optimiseur avec des valeurs par défaut pour tous les paramètres, y compris le taux d'apprentissage. Cependant, nous savons depuis longtemps que les *transformers* bénéficient d'un taux d'apprentissage beaucoup plus faible que celui par défaut d'Adam, qui est de 1e-3, également écrit comme 10 à la puissance -3, ou 0,001. 5e-5 (0,00005), qui est environ vingt fois inférieur, est un bien meilleur point de départ. - -En plus de réduire le taux d'apprentissage, nous avons une deuxième astuce dans notre manche : nous pouvons réduire lentement le taux d'apprentissage au cours de l'entraînement. Dans la littérature, on parle parfois de *décroissance* ou d'*annulation* du taux d'apprentissage.le taux d'apprentissage. Dans Keras, la meilleure façon de le faire est d'utiliser un *planificateur du taux d'apprentissage*. Un bon planificateur à utiliser est `PolynomialDecay`. Malgré son nom, avec les paramètres par défaut, il diminue simplement de façon linéaire le taux d'apprentissage de la valeur initiale à la valeur finale au cours de l'entraînement, ce qui est exactement ce que nous voulons. Afin d'utiliser correctement un planificateur, nous devons lui dire combien de temps l'entraînement va durer. Nous calculons cela comme `num_train_steps` ci-dessous. - -```py -from tensorflow.keras.optimizers.schedules import PolynomialDecay - -batch_size = 8 -num_epochs = 3 -# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans l'ensemble de données, divisé par la taille du batch puis multiplié -# par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un lot tf.data.Dataset -# et non le jeu de données original Hugging Face Dataset, donc son len() est déjà 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 bibliothèque 🤗 *Transformers* possède également une fonction `create_optimizer()` qui créera un optimiseur `AdamW` avec un taux d'apprentissage décroissant. Il s'agit d'un raccourci pratique que vous verrez en détail dans les prochaines sections du cours. - - - -Nous avons maintenant notre tout nouvel optimiseur et nous pouvons essayer de nous entraîner avec lui. Tout d'abord, rechargeons le modèle pour réinitialiser les modifications apportées aux poids lors de l'entraînement que nous venons d'effectuer, puis nous pouvons le compiler avec le nouvel optimiseur : - -```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"]) -``` - -Maintenant, on *fit* : - -```py -model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) -``` - - - -💡 Si vous voulez télécharger automatiquement votre modèle sur le *Hub* pendant l'entraînement, vous pouvez passer un `PushToHubCallback` dans la méthode `model.fit()`. Nous en apprendrons davantage à ce sujet au [Chapitre 4](/course/fr/chapter4/3). - - - -### Prédictions du modèle - - - - -Entraîner et regarder la perte diminuer, c'est très bien, mais que faire si l'on veut réellement obtenir des résultats du modèle entraîné, soit pour calculer des métriques, soit pour utiliser le modèle en production ? Pour ce faire, nous pouvons simplement utiliser la méthode `predict()`. Ceci retournera les *logits* de la tête de sortie du modèle, un par classe. - -```py -preds = model.predict(tf_validation_dataset)["logits"] -``` - -Nous pouvons convertir ces logits en prédictions de classe du modèle en utilisant `argmax` pour trouver le logit le plus élevé, qui correspond à la classe la plus probable : - -```py -class_preds = np.argmax(preds, axis=1) -print(preds.shape, class_preds.shape) -``` - -```python out -(408, 2) (408,) -``` - -Maintenant, utilisons ces `preds` pour calculer des métriques ! Nous pouvons charger les métriques associées au jeu de données MRPC aussi facilement que nous avons chargé le jeu de données, cette fois avec la fonction `load_metric()`. L'objet retourné a une méthode `compute()` que nous pouvons utiliser pour faire le calcul de la métrique : - -```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} -``` - -Les résultats exacts que vous obtiendrez peuvent varier, car l'initialisation aléatoire de la tête du modèle peut modifier les métriques obtenues. Ici, nous pouvons voir que notre modèle a une précision de 85,78% sur l'ensemble de validation et un score F1 de 89,97. Ce sont les deux métriques utilisées pour évaluer les résultats sur le jeu de données MRPC pour le benchmark GLUE. Le tableau du papier de [BERT](https://arxiv.org/pdf/1810.04805.pdf) indique un score F1 de 88,9 pour le modèle de base. Il s'agissait du modèle `uncased` alors que nous utilisons actuellement le modèle `cased`, ce qui explique le meilleur résultat. - -Ceci conclut l'introduction à le *finetuning* en utilisant l'API Keras. Un exemple d'application de cette méthode aux tâches les plus courantes du traitement automatique des langues sera présenté au [Chapitre 7](/course/fr/chapter7). Si vous souhaitez affiner vos connaissances de l'API Keras, essayez *finetuner* un modèle sur le jeu de données GLUE SST-2, en utilisant le traitement des données que vous avez effectué dans la section 2. + + +# Finetuner un modèle avec Keras + + + +Une fois que vous avez fait tout le travail de prétraitement des données dans la dernière section, il ne vous reste que quelques étapes pour entraîner le modèle. Notez, cependant, que la commande `model.fit()` s'exécutera très lentement sur un CPU. Si vous n'avez pas de GPU, vous pouvez avoir accès à des GPUs ou TPUs gratuits sur [Google Colab](https://colab.research.google.com/). + +Les exemples de code ci-dessous supposent que vous avez déjà exécuté les exemples de la section précédente. Voici un bref résumé de ce dont vous avez besoin : + +```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, +) +``` + +### Entraînement + +Les modèles TensorFlow importés depuis 🤗 *Transformers* sont déjà des modèles Keras. Voici une courte introduction à Keras. + + + +Cela signifie qu'une fois que nous disposons de nos données, très peu de travail est nécessaire pour commencer à entraîner sur celles-ci. + + + +Comme dans le [chapitre précédent](/course/fr/chapter2), nous allons utiliser la classe `TFAutoModelForSequenceClassification`, avec deux étiquettes : + +```py +from transformers import TFAutoModelForSequenceClassification + +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +Vous remarquerez que, contrairement au [chapitre 2](/course/fr/chapter2), vous obtenez un message d'avertissement après l'instanciation de ce modèle pré-entraîné. Ceci est dû au fait que BERT n'a pas été pré-entraîné à la classification de paires de phrases, donc la tête du modèle pré-entraîné a été supprimée et une nouvelle tête adaptée à la classification de séquences a été insérée à la place. Les messages d'avertissement indiquent que certains poids n'ont pas été utilisés (ceux correspondant à la tête de pré-entraînement abandonnée) et que d'autres ont été initialisés de manière aléatoire (ceux pour la nouvelle tête). Il conclut en vous encourageant à entraîner le modèle, ce qui est exactement ce que nous allons faire maintenant. + +Pour *finetuner* le modèle sur notre jeu de données, nous devons simplement `compiler()` notre modèle et ensuite passer nos données à la méthode `fit()`. Cela va démarrer le processus de *finetuning* (qui devrait prendre quelques minutes sur un GPU) et rapporter la perte d'entraînement au fur et à mesure, plus la perte de validation à la fin de chaque époque. + + + +Notez que les modèles 🤗 *Transformers* ont une capacité spéciale que la plupart des modèles Keras n'ont pas. Ils peuvent automatiquement utiliser une perte appropriée qu'ils calculent en interne. Ils utiliseront cette perte par défaut si vous ne définissez pas un argument de perte dans `compile()`. Notez que pour utiliser la perte interne, vous devrez passer vos labels comme faisant partie de l'entrée, et non pas comme un label séparé, ce qui est la façon normale d'utiliser les labels avec les modèles Keras. Vous verrez des exemples de cela dans la partie 2 du cours, où la définition de la fonction de perte correcte peut être délicate. Pour la classification des séquences, cependant, une fonction de perte standard de Keras fonctionne bien, et c'est donc ce que nous utiliserons ici. + + + +```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, +) +``` + + + +Notez un piège très commun ici. Vous *pouvez* simplement passer le nom de la perte comme une chaîne à Keras, mais par défaut Keras supposera que vous avez déjà appliqué une fonction softmax à vos sorties. Cependant, de nombreux modèles produisent les valeurs juste avant l'application de la softmax, que l'on appelle aussi les *logits*. Nous devons indiquer à la fonction de perte que c'est ce que fait notre modèle, et la seule façon de le faire est de l'appeler directement, plutôt que par son nom avec une chaîne. + + + + +### Améliorer les performances d'entraînement + + + +Si vous essayez le code ci-dessus, il fonctionne certainement, mais vous constaterez que la perte ne diminue que lentement ou sporadiquement. La cause principale est le *taux d'apprentissage*. Comme pour la perte, lorsque nous transmettons à Keras le nom d'un optimiseur sous forme de chaîne de caractères, Keras initialise cet optimiseur avec des valeurs par défaut pour tous les paramètres, y compris le taux d'apprentissage. Cependant, nous savons depuis longtemps que les *transformers* bénéficient d'un taux d'apprentissage beaucoup plus faible que celui par défaut d'Adam, qui est de 1e-3, également écrit comme 10 à la puissance -3, ou 0,001. 5e-5 (0,00005), qui est environ vingt fois inférieur, est un bien meilleur point de départ. + +En plus de réduire le taux d'apprentissage, nous avons une deuxième astuce dans notre manche : nous pouvons réduire lentement le taux d'apprentissage au cours de l'entraînement. Dans la littérature, on parle parfois de *décroissance* ou d'*annulation* du taux d'apprentissage.le taux d'apprentissage. Dans Keras, la meilleure façon de le faire est d'utiliser un *planificateur du taux d'apprentissage*. Un bon planificateur à utiliser est `PolynomialDecay`. Malgré son nom, avec les paramètres par défaut, il diminue simplement de façon linéaire le taux d'apprentissage de la valeur initiale à la valeur finale au cours de l'entraînement, ce qui est exactement ce que nous voulons. Afin d'utiliser correctement un planificateur, nous devons lui dire combien de temps l'entraînement va durer. Nous calculons cela comme `num_train_steps` ci-dessous. + +```py +from tensorflow.keras.optimizers.schedules import PolynomialDecay + +batch_size = 8 +num_epochs = 3 +# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans l'ensemble de données, divisé par la taille du batch puis multiplié +# par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un lot tf.data.Dataset +# et non le jeu de données original Hugging Face Dataset, donc son len() est déjà 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 bibliothèque 🤗 *Transformers* possède également une fonction `create_optimizer()` qui créera un optimiseur `AdamW` avec un taux d'apprentissage décroissant. Il s'agit d'un raccourci pratique que vous verrez en détail dans les prochaines sections du cours. + + + +Nous avons maintenant notre tout nouvel optimiseur et nous pouvons essayer de nous entraîner avec lui. Tout d'abord, rechargeons le modèle pour réinitialiser les modifications apportées aux poids lors de l'entraînement que nous venons d'effectuer, puis nous pouvons le compiler avec le nouvel optimiseur : + +```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"]) +``` + +Maintenant, on *fit* : + +```py +model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) +``` + + + +💡 Si vous voulez télécharger automatiquement votre modèle sur le *Hub* pendant l'entraînement, vous pouvez passer un `PushToHubCallback` dans la méthode `model.fit()`. Nous en apprendrons davantage à ce sujet au [chapitre 4](/course/fr/chapter4/3). + + + +### Prédictions du modèle + + + + +Entraîner et regarder la perte diminuer, c'est très bien, mais que faire si l'on veut réellement obtenir des résultats du modèle entraîné, soit pour calculer des métriques, soit pour utiliser le modèle en production ? Pour ce faire, nous pouvons simplement utiliser la méthode `predict()`. Ceci retournera les *logits* de la tête de sortie du modèle, un par classe. + +```py +preds = model.predict(tf_validation_dataset)["logits"] +``` + +Nous pouvons convertir ces logits en prédictions de classe du modèle en utilisant `argmax` pour trouver le logit le plus élevé, qui correspond à la classe la plus probable : + +```py +class_preds = np.argmax(preds, axis=1) +print(preds.shape, class_preds.shape) +``` + +```python out +(408, 2) (408,) +``` + +Maintenant, utilisons ces `preds` pour calculer des métriques ! Nous pouvons charger les métriques associées au jeu de données MRPC aussi facilement que nous avons chargé le jeu de données, cette fois avec la fonction `load_metric()`. L'objet retourné a une méthode `compute()` que nous pouvons utiliser pour faire le calcul de la métrique : + +```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} +``` + +Les résultats exacts que vous obtiendrez peuvent varier, car l'initialisation aléatoire de la tête du modèle peut modifier les métriques obtenues. Ici, nous pouvons voir que notre modèle a une précision de 85,78% sur l'ensemble de validation et un score F1 de 89,97. Ce sont les deux métriques utilisées pour évaluer les résultats sur le jeu de données MRPC pour le benchmark GLUE. Le tableau du papier de [BERT](https://arxiv.org/pdf/1810.04805.pdf) indique un score F1 de 88,9 pour le modèle de base. Il s'agissait du modèle `uncased` alors que nous utilisons actuellement le modèle `cased`, ce qui explique le meilleur résultat. + +Ceci conclut l'introduction à le *finetuning* en utilisant l'API Keras. Un exemple d'application de cette méthode aux tâches les plus courantes du traitement automatique des langues sera présenté au [chapitre 7](/course/fr/chapter7). Si vous souhaitez affiner vos connaissances de l'API Keras, essayez *finetuner* un modèle sur le jeu de données GLUE SST-2, en utilisant le traitement des données que vous avez effectué dans la section 2. diff --git a/chapters/fr/chapter3/4.mdx b/chapters/fr/chapter3/4.mdx index a56a8bf9e..8279747f4 100644 --- a/chapters/fr/chapter3/4.mdx +++ b/chapters/fr/chapter3/4.mdx @@ -292,7 +292,7 @@ La première ligne à ajouter est la ligne d'importation. La deuxième ligne ins Ensuite, le gros du travail est fait dans la ligne qui envoie les *dataloaders*, le modèle, et l'optimiseur à `accelerator.prepare()`. Cela va envelopper ces objets dans le conteneur approprié pour s'assurer que votre entraînement distribué fonctionne comme prévu. Les changements restants à faire sont la suppression de la ligne qui met le batch sur le `device` (encore une fois, si vous voulez le garder, vous pouvez juste le changer pour utiliser `accelerator.device`) et le remplacement de `loss.backward()` par `accelerator.backward(loss)`. -⚠️ Afin de bénéficier de la rapidité offerte par les TPUs du Cloud, nous vous recommandons de rembourrer vos échantillons à une longueur fixe avec les arguments `padding="max_length"` et `max_length` du *tokenizer*. +⚠️ Afin de bénéficier de la rapidité offerte par les TPUs du Cloud, nous vous recommandons de rembourrer vos échantillons à une longueur fixe avec les arguments `padding="max_length"` et `max_length` du tokenizer. Si vous souhaitez faire un copier-coller pour jouer, voici à quoi ressemble la boucle d'entraînement complète avec 🤗 *Accelerate* : diff --git a/chapters/fr/chapter3/5.mdx b/chapters/fr/chapter3/5.mdx index 767ec59aa..db244e545 100644 --- a/chapters/fr/chapter3/5.mdx +++ b/chapters/fr/chapter3/5.mdx @@ -1,6 +1,6 @@ -# *Finetuning*, coché ! +# Finetuning, coché ! C'était amusant ! Dans les deux premiers chapitres, vous avez appris à connaître les modèles et les *tokenizers*, et vous savez maintenant comment les *finetuner* pour vos propres données. Pour récapituler, dans ce chapitre vous : diff --git a/chapters/fr/chapter3/6.mdx b/chapters/fr/chapter3/6.mdx index edafb9c0a..44c0fdf44 100644 --- a/chapters/fr/chapter3/6.mdx +++ b/chapters/fr/chapter3/6.mdx @@ -20,7 +20,7 @@ Testez ce que vous avez appris dans ce chapitre ! }, { text: "Confusion", - explain: "Correct ! La confusion n'est pas l'une des six émotions de base.", + explain: "La confusion n'est pas l'une des six émotions de base.", correct: true }, { @@ -111,12 +111,12 @@ Testez ce que vous avez appris dans ce chapitre ! }, { text: "C'est lorsque vous remplissez vos entrées lorsque le batch est créé, à la longueur maximale des phrases à l'intérieur de ce batch.", - explain: "C'est exact ! La partie dynamique vient du fait que la taille de chaque batch est déterminée au moment de la création, et que tous vos batchs peuvent avoir des formes différentes.", + explain: "La partie dynamique vient du fait que la taille de chaque batch est déterminée au moment de la création, et que tous vos batchs peuvent avoir des formes différentes.", correct: true }, { text: "C'est lorsque vous remplissez vos entrées de sorte que chaque phrase ait le même nombre de tokens que la précédente dans le jeu de données.", - explain: "C'est incorrect, et cela n'a pas de sens de regarder l'ordre dans le jeu de données puisque nous le mélangeons pendant l'entraînement." + explain: "Cela n'a pas de sens de regarder l'ordre dans le jeu de données puisque nous le mélangeons pendant l'entraînement." }, ]} /> @@ -131,7 +131,7 @@ Testez ce que vous avez appris dans ce chapitre ! }, { text: "Elle rassemble tous les échantillons dans un batch.", - explain: "Correct ! Vous pouvez passer la fonction de rassemblement comme argument d'une fonction DataLoader. Nous avons utilisé la fonction DataCollatorWithPadding qui remplit tous les éléments d'un batch pour qu'ils aient la même longueur.", + explain: "Vous pouvez passer la fonction de rassemblement comme argument d'une fonction DataLoader. Nous avons utilisé la fonction DataCollatorWithPadding qui remplit tous les éléments d'un batch pour qu'ils aient la même longueur.", correct: true }, { @@ -155,7 +155,7 @@ Testez ce que vous avez appris dans ce chapitre ! }, { text: "La tête du modèle pré-entraîné est supprimée et une nouvelle tête adaptée à la tâche est insérée à la place.", - explain: "Correct. Par exemple, lorsque nous avons utilisé l'AutoModelForSequenceClassification avec bert-base-uncased, nous avons eu des messages d'avertissement lors de l'instanciation du modèle. La tête pré-entraînée n'est pas utilisée pour la tâche de classification de séquences, elle est donc supprimée et une nouvelle tête est instanciée avec des poids aléatoires..", + explain: "Par exemple, lorsque nous avons utilisé l'AutoModelForSequenceClassification avec bert-base-uncased, nous avons eu des messages d'avertissement lors de l'instanciation du modèle. La tête pré-entraînée n'est pas utilisée pour la tâche de classification de séquences, elle est donc supprimée et une nouvelle tête est instanciée avec des poids aléatoires..", correct: true }, { @@ -175,7 +175,7 @@ Testez ce que vous avez appris dans ce chapitre ! choices={[ { text: "Contenir tous les hyperparamètres utilisés pour l'entraînement et l'évaluation avec le Trainer.", - explain: "Correct !", + explain: "", correct: true }, { @@ -207,7 +207,7 @@ Testez ce que vous avez appris dans ce chapitre ! }, { text: "Elle permet à nos boucles d'entraînement de fonctionner avec des stratégies distribuées.", - explain: "Correct ! Avec 🤗 Accelerate, vos boucles d'entraînement fonctionneront pour plusieurs GPUs et TPUs.", + explain: "Avec 🤗 Accelerate, vos boucles d'entraînement fonctionneront pour plusieurs GPUs et TPUs.", correct: true }, { @@ -228,7 +228,7 @@ Testez ce que vous avez appris dans ce chapitre ! }, { text: "La tête du modèle pré-entraîné est supprimée et une nouvelle tête adaptée à la tâche est insérée à la place.", - explain: "Correct. Par exemple, lorsque nous avons utilisé TFAutoModelForSequenceClassification avec bert-base-uncased, nous avons eu des messages d'avertissement lors de l'instanciation du modèle. La tête pré-entraînée n'est pas utilisée pour la tâche de classification de séquences, elle est donc supprimée et une nouvelle tête est instanciée avec des poids aléatoires..", + explain: "Par exemple, lorsque nous avons utilisé TFAutoModelForSequenceClassification avec bert-base-uncased, nous avons eu des messages d'avertissement lors de l'instanciation du modèle. La tête pré-entraînée n'est pas utilisée pour la tâche de classification de séquences, elle est donc supprimée et une nouvelle tête est instanciée avec des poids aléatoires..", correct: true }, { @@ -252,12 +252,12 @@ Testez ce que vous avez appris dans ce chapitre ! }, { text: "Vous pouvez tirer parti des méthodes existantes telles que compile(), fit() et predict().", - explain: "Correct ! Une fois que vous disposez des données, l'entraînement sur celles-ci ne demande que très peu de travail.", + explain: "Une fois que vous disposez des données, l'entraînement sur celles-ci ne demande que très peu de travail.", correct: true }, { text: "Vous apprendrez à connaître Keras ainsi que transformers.", - explain: "Correct, mais nous cherchons quelque chose d'autre :)", + explain: "Mais nous cherchons quelque chose d'autre :)", correct: true }, { @@ -282,7 +282,7 @@ Testez ce que vous avez appris dans ce chapitre ! }, { text: "En utilisant un callable avec la signature metric_fn(y_true, y_pred).", - explain: "Correct !", + explain: " ", correct: true }, { diff --git a/chapters/fr/chapter4/1.mdx b/chapters/fr/chapter4/1.mdx index b9c2057a4..7a0420461 100644 --- a/chapters/fr/chapter4/1.mdx +++ b/chapters/fr/chapter4/1.mdx @@ -1,17 +1,17 @@ -# Le *Hub* d'Hugging Face - -Le [*Hub* d'Hugging Face](https://huggingface.co/), notre site internet principal, est une plateforme centrale qui permet à quiconque de découvrir, d'utiliser et de contribuer à de nouveaux modèles et jeux de données de pointe. Il héberge une grande variété de modèles, dont plus de 10 000 sont accessibles au public. Nous nous concentrerons sur les modèles dans ce chapitre, et nous examinerons les jeux de données au chapitre 5. - -Les modèles présents dans le *Hub* ne sont pas limités à 🤗 *Transformers* ou même au NLP. Il existe des modèles de [Flair](https://github.com/flairNLP/flair) et [AllenNLP](https://github.com/allenai/allennlp) pour le NLP, [Asteroid](https://github.com/asteroid-team/asteroid) et [pyannote](https://github.com/pyannote/pyannote-audio) pour l'audio, et [timm](https://github.com/rwightman/pytorch-image-models) pour la vision, pour n'en citer que quelques-uns. - -Chacun de ces modèles est hébergé sous forme de dépôt Git, ce qui permet le suivi des versions et la reproductibilité. Partager un modèle sur le *Hub*, c'est l'ouvrir à la communauté et le rendre accessible à tous ceux qui souhaitent l'utiliser facilement, ce qui leur évite d'avoir à entraîner eux-mêmes un modèle et simplifie le partage et l'utilisation. - -En outre, le partage d'un modèle sur le *Hub* déploie automatiquement une API d'inférence hébergée pour ce modèle. Toute personne de la communauté est libre de la tester directement sur la page du modèle, avec des entrées personnalisées et des *widgets* appropriés. - -La meilleure partie est que le partage ainsi que l'utilisation de n'importe quel modèle public sur le *Hub* sont totalement gratuits ! [Des plans payants](https://huggingface.co/pricing) existent également si vous souhaitez partager des modèles en privé. - -La vidéo ci-dessous montre comment naviguer sur le *Hub* : - - - -Avoir un compte huggingface.co est nécessaire pour suivre cette partie car nous allons créer et gérer des répertoires sur le *Hub* : [créer un compte](https://huggingface.co/join) +# Le Hub d'Hugging Face + +Le [*Hub* d'Hugging Face](https://huggingface.co/), notre site internet principal, est une plateforme centrale qui permet à quiconque de découvrir, d'utiliser et de contribuer à de nouveaux modèles et jeux de données de pointe. Il héberge une grande variété de modèles, dont plus de 10 000 sont accessibles au public. Nous nous concentrerons sur les modèles dans ce chapitre, et nous examinerons les jeux de données au chapitre 5. + +Les modèles présents dans le *Hub* ne sont pas limités à 🤗 *Transformers* ou même au NLP. Il existe des modèles de [Flair](https://github.com/flairNLP/flair) et [AllenNLP](https://github.com/allenai/allennlp) pour le NLP, [Asteroid](https://github.com/asteroid-team/asteroid) et [pyannote](https://github.com/pyannote/pyannote-audio) pour l'audio, et [timm](https://github.com/rwightman/pytorch-image-models) pour la vision, pour n'en citer que quelques-uns. + +Chacun de ces modèles est hébergé sous forme de dépôt Git, ce qui permet le suivi des versions et la reproductibilité. Partager un modèle sur le *Hub*, c'est l'ouvrir à la communauté et le rendre accessible à tous ceux qui souhaitent l'utiliser facilement, ce qui leur évite d'avoir à entraîner eux-mêmes un modèle et simplifie le partage et l'utilisation. + +En outre, le partage d'un modèle sur le *Hub* déploie automatiquement une API d'inférence hébergée pour ce modèle. Toute personne de la communauté est libre de la tester directement sur la page du modèle, avec des entrées personnalisées et des *widgets* appropriés. + +La meilleure partie est que le partage ainsi que l'utilisation de n'importe quel modèle public sur le *Hub* sont totalement gratuits ! [Des plans payants](https://huggingface.co/pricing) existent également si vous souhaitez partager des modèles en privé. + +La vidéo ci-dessous montre comment naviguer sur le *Hub* : + + + +Avoir un compte huggingface.co est nécessaire pour suivre cette partie car nous allons créer et gérer des répertoires sur le *Hub* : [créer un compte](https://huggingface.co/join) \ No newline at end of file diff --git a/chapters/fr/chapter4/2.mdx b/chapters/fr/chapter4/2.mdx index b6cc65512..86579685f 100644 --- a/chapters/fr/chapter4/2.mdx +++ b/chapters/fr/chapter4/2.mdx @@ -1,97 +1,97 @@ - - -# Utilisation de modèles pré-entraînés - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -Le *Hub* rend simple la sélection d'un modèle et permet alors que celui-ci puisse être utilisé dans toute bibliothèque en aval en seulement quelques lignes de code. Voyons comment utiliser concrètement l'un de ces modèles et comment contribuer au développement de la communauté. - -Supposons que nous recherchions un modèle basé sur le français, capable de remplir des masques. - -
-Selecting the Camembert model. -
- -Nous choisissons le *checkpoint* `camembert-base` pour essayer. L'identifiant `camembert-base` est tout ce dont nous avons besoin pour commencer à utiliser le modèle ! Comme vous l'avez vu dans les chapitres précédents, nous pouvons l'instancier en utilisant la fonction `pipeline()` : - -```py -from transformers import pipeline - -camembert_fill_mask = pipeline("fill-mask", model="camembert-base") -results = camembert_fill_mask("Le camembert est :)") -``` - -```python out -[ - {'sequence': 'Le camembert est délicieux :)', 'score': 0.49091005325317383, 'token': 7200, 'token_str': 'délicieux'}, - {'sequence': 'Le camembert est excellent :)', 'score': 0.1055697426199913, 'token': 2183, 'token_str': 'excellent'}, - {'sequence': 'Le camembert est succulent :)', 'score': 0.03453313186764717, 'token': 26202, 'token_str': 'succulent'}, - {'sequence': 'Le camembert est meilleur :)', 'score': 0.0330314114689827, 'token': 528, 'token_str': 'meilleur'}, - {'sequence': 'Le camembert est parfait :)', 'score': 0.03007650189101696, 'token': 1654, 'token_str': 'parfait'} -] -``` - -Comme vous pouvez le constater, le chargement d'un modèle dans un pipeline est extrêmement simple. La seule chose à laquelle vous devez faire attention est que le *checkpoint* choisi soit adapté à la tâche pour laquelle il va être utilisé. Par exemple, ici nous chargeons le *checkpoint* `camembert-base` dans le pipeline `fill-mask`, ce qui est tout à fait correct. Mais si nous chargerions ce *checkpoint* dans le pipeline `text-classification`, les résultats n'auraient aucun sens car la tête de `camembert-base` n'est pas adaptée à cette tâche ! Nous recommandons d'utiliser le sélecteur de tâche dans l'interface du *Hub* afin de sélectionner les *checkpoints* appropriés : - -
-The task selector on the web interface. -
- -Vous pouvez également instancier le *checkpoint* en utilisant directement l'architecture du modèle : - -{#if fw === 'pt'} -```py -from transformers import CamembertTokenizer, CamembertForMaskedLM - -tokenizer = CamembertTokenizer.from_pretrained("camembert-base") -model = CamembertForMaskedLM.from_pretrained("camembert-base") -``` - -Cependant, nous recommandons d'utiliser les classes [`Auto*`](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes) à la place, car elles sont par conception indépendantes de l'architecture. Alors que l'exemple de code précédent limite les utilisateurs aux *checkpoints* chargeables dans l'architecture CamemBERT, l'utilisation des classes `Auto*` facilite le changement de *checkpoint* : - -```py -from transformers import AutoTokenizer, AutoModelForMaskedLM - -tokenizer = AutoTokenizer.from_pretrained("camembert-base") -model = AutoModelForMaskedLM.from_pretrained("camembert-base") -``` -{:else} -```py -from transformers import CamembertTokenizer, TFCamembertForMaskedLM - -tokenizer = CamembertTokenizer.from_pretrained("camembert-base") -model = TFCamembertForMaskedLM.from_pretrained("camembert-base") -``` - -Cependant, nous recommandons d'utiliser les classes [`TFAuto*`](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes) à la place, car elles sont par conception indépendantes de l'architecture. Alors que l'exemple de code précédent limite les utilisateurs aux *checkpoints* chargeables dans l'architecture CamemBERT, l'utilisation des classes `TFAuto*` facilite le changement de *checkpoint* : - - -```py -from transformers import AutoTokenizer, TFAutoModelForMaskedLM - -tokenizer = AutoTokenizer.from_pretrained("camembert-base") -model = TFAutoModelForMaskedLM.from_pretrained("camembert-base") -``` -{/if} - - -Lorsque vous utilisez un modèle pré-entraîné, assurez-vous de vérifier comment il a été entraîné, sur quels jeux de données, ses limites et ses biais. Toutes ces informations doivent être indiquées dans sa carte. - + + +# Utilisation de modèles pré-entraînés + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Le *Hub* rend simple la sélection d'un modèle et permet alors que celui-ci puisse être utilisé dans toute bibliothèque en aval en seulement quelques lignes de code. Voyons comment utiliser concrètement l'un de ces modèles et comment contribuer au développement de la communauté. + +Supposons que nous recherchions un modèle basé sur le français, capable de remplir des masques. + +
+Selecting the Camembert model. +
+ +Nous choisissons le *checkpoint* `camembert-base` pour essayer. L'identifiant `camembert-base` est tout ce dont nous avons besoin pour commencer à utiliser le modèle ! Comme vous l'avez vu dans les chapitres précédents, nous pouvons l'instancier en utilisant la fonction `pipeline()` : + +```py +from transformers import pipeline + +camembert_fill_mask = pipeline("fill-mask", model="camembert-base") +results = camembert_fill_mask("Le camembert est :)") +``` + +```python out +[ + {'sequence': 'Le camembert est délicieux :)', 'score': 0.49091005325317383, 'token': 7200, 'token_str': 'délicieux'}, + {'sequence': 'Le camembert est excellent :)', 'score': 0.1055697426199913, 'token': 2183, 'token_str': 'excellent'}, + {'sequence': 'Le camembert est succulent :)', 'score': 0.03453313186764717, 'token': 26202, 'token_str': 'succulent'}, + {'sequence': 'Le camembert est meilleur :)', 'score': 0.0330314114689827, 'token': 528, 'token_str': 'meilleur'}, + {'sequence': 'Le camembert est parfait :)', 'score': 0.03007650189101696, 'token': 1654, 'token_str': 'parfait'} +] +``` + +Comme vous pouvez le constater, le chargement d'un modèle dans un pipeline est extrêmement simple. La seule chose à laquelle vous devez faire attention est que le *checkpoint* choisi soit adapté à la tâche pour laquelle il va être utilisé. Par exemple, ici nous chargeons le *checkpoint* `camembert-base` dans le pipeline `fill-mask`, ce qui est tout à fait correct. Mais si nous chargerions ce *checkpoint* dans le pipeline `text-classification`, les résultats n'auraient aucun sens car la tête de `camembert-base` n'est pas adaptée à cette tâche ! Nous recommandons d'utiliser le sélecteur de tâche dans l'interface du *Hub* afin de sélectionner les *checkpoints* appropriés : + +
+The task selector on the web interface. +
+ +Vous pouvez également instancier le *checkpoint* en utilisant directement l'architecture du modèle : + +{#if fw === 'pt'} +```py +from transformers import CamembertTokenizer, CamembertForMaskedLM + +tokenizer = CamembertTokenizer.from_pretrained("camembert-base") +model = CamembertForMaskedLM.from_pretrained("camembert-base") +``` + +Cependant, nous recommandons d'utiliser les classes [`Auto*`](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes) à la place, car elles sont par conception indépendantes de l'architecture. Alors que l'exemple de code précédent limite les utilisateurs aux *checkpoints* chargeables dans l'architecture CamemBERT, l'utilisation des classes `Auto*` facilite le changement de *checkpoint* : + +```py +from transformers import AutoTokenizer, AutoModelForMaskedLM + +tokenizer = AutoTokenizer.from_pretrained("camembert-base") +model = AutoModelForMaskedLM.from_pretrained("camembert-base") +``` +{:else} +```py +from transformers import CamembertTokenizer, TFCamembertForMaskedLM + +tokenizer = CamembertTokenizer.from_pretrained("camembert-base") +model = TFCamembertForMaskedLM.from_pretrained("camembert-base") +``` + +Cependant, nous recommandons d'utiliser les classes [`TFAuto*`](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes) à la place, car elles sont par conception indépendantes de l'architecture. Alors que l'exemple de code précédent limite les utilisateurs aux *checkpoints* chargeables dans l'architecture CamemBERT, l'utilisation des classes `TFAuto*` facilite le changement de *checkpoint* : + + +```py +from transformers import AutoTokenizer, TFAutoModelForMaskedLM + +tokenizer = AutoTokenizer.from_pretrained("camembert-base") +model = TFAutoModelForMaskedLM.from_pretrained("camembert-base") +``` +{/if} + + +Lorsque vous utilisez un modèle pré-entraîné, assurez-vous de vérifier comment il a été entraîné, sur quels jeux de données, ses limites et ses biais. Toutes ces informations doivent être indiquées dans sa carte. + diff --git a/chapters/fr/chapter4/3.mdx b/chapters/fr/chapter4/3.mdx index cd60c7b66..fabdd4a36 100644 --- a/chapters/fr/chapter4/3.mdx +++ b/chapters/fr/chapter4/3.mdx @@ -197,17 +197,17 @@ Le *package* `huggingface_hub` offre plusieurs méthodes et classes qui sont uti ```python no-format from huggingface_hub import ( - # User management + # Gestion des utilisateurs login, logout, whoami, - # Repository creation and management + # Création et gestion du dépôt create_repo, delete_repo, update_repo_visibility, - # And some methods to retrieve/change information about the content + # Et quelques méthodes pour récupérer/changer des informations sur le contenu list_models, list_datasets, list_metrics, @@ -274,7 +274,7 @@ C'est là que votre modèle sera hébergé. Pour commencer à le remplir, vous p Le fichier README est en Markdown. N'hésitez pas à vous lâcher avec lui ! La troisième partie de ce chapitre est consacrée à la construction d'une carte de modèle. Celles-ci sont d'une importance capitale pour valoriser votre modèle, car c'est par elles que vous indiquez aux autres ce qu'il peut faire. -Si vous regardez l'onglet « Fichiers et versions », vous verrez qu'il n'y a pas encore beaucoup de fichiers : juste le *README.md* que vous venez de créer et le fichier *.gitattributes* qui garde la trace des gros fichiers. +Si vous regardez l'onglet « *Files and versions* », vous verrez qu'il n'y a pas encore beaucoup de fichiers : juste le *README.md* que vous venez de créer et le fichier *.gitattributes* qui garde la trace des gros fichiers.
The 'Files and versions' tab only shows the .gitattributes and README.md files. diff --git a/chapters/fr/chapter4/4.mdx b/chapters/fr/chapter4/4.mdx index 745d846ba..4f2086aea 100644 --- a/chapters/fr/chapter4/4.mdx +++ b/chapters/fr/chapter4/4.mdx @@ -6,7 +6,7 @@ Documenter le processus d'entraînement et d'évaluation aide les autres à comp Par conséquent, la création d'une carte de modèle définissant clairement votre modèle est une étape très importante. Nous vous donnons ici quelques conseils qui vous aideront à le faire. La création de la fiche de modèle se fait par le biais du fichier *README.md* que vous avez vu précédemment, qui est un fichier Markdown. -Le concept de carte de modèle provient d'une direction de recherche de Google, partagée pour la première fois dans l'article ["*Model Cards for Model Reporting*"](https://arxiv.org/abs/1810.03993) par Margaret Mitchell et al. De nombreuses informations contenues dans ce document sont basées sur cet article et nous vous recommandons d'y jeter un coup d'œil pour comprendre pourquoi les cartes de modèles sont si importantes dans un monde qui valorise la reproductibilité, la réutilisation et l'équité. +Le concept de carte de modèle provient d'une direction de recherche de Google, partagée pour la première fois dans l'article [« *Model Cards for Model Reporting* »](https://arxiv.org/abs/1810.03993) par Margaret Mitchell et al. De nombreuses informations contenues dans ce document sont basées sur cet article et nous vous recommandons d'y jeter un coup d'œil pour comprendre pourquoi les cartes de modèles sont si importantes dans un monde qui valorise la reproductibilité, la réutilisation et l'équité. La carte de modèle commence généralement par une très brève présentation de haut niveau de l'objet du modèle, suivie de détails supplémentaires dans les sections suivantes : @@ -81,4 +81,4 @@ datasets: Ces métadonnées sont analysées par le *Hub* qui identifie alors ce modèle comme étant un modèle français, avec une licence MIT, entraîné sur le jeu de données Oscar. -La [spécification complète de la carte du modèle](https://github.com/huggingface/hub-docs/blame/main/modelcard.md) permet de spécifier les langues, les licences, les balises, les jeux de données, les mesures, ainsi que les résultats d'évaluation obtenus par le modèle lors de l'entraînement. \ No newline at end of file +La [spécification complète de la carte du modèle](https://github.com/huggingface/hub-docs/blame/main/modelcard.md) permet de spécifier les langues, les licences, les balises, les jeux de données, les mesures, ainsi que les résultats d'évaluation obtenus par le modèle lors de l'entraînement. diff --git a/chapters/fr/chapter4/5.mdx b/chapters/fr/chapter4/5.mdx index 366b68a81..4365a6733 100644 --- a/chapters/fr/chapter4/5.mdx +++ b/chapters/fr/chapter4/5.mdx @@ -1,7 +1,7 @@ -# Fin de la première partie du cours ! - -C'est la fin de la première partie du cours ! La partie 2 sera publiée le 15 novembre 2021 avec un grand événement communautaire, pour plus d'informations voir [ici](https://huggingface.co/blog/course-launch-event). - -Vous devriez maintenant être capable de *finetuner* un modèle pré-entraîné sur un problème de classification de texte (phrases simples ou paires de phrases) et de télécharger le résultat sur le *Hub*. Pour vous assurer que vous maîtrisez cette première section, vous devriez refaire ça sur un problème qui vous intéresse (et pas nécessairement en anglais si vous parlez une autre langue) ! Vous pouvez trouver de l'aide dans les [forums Hugging Face](https://discuss.huggingface.co/) et partager votre projet dans [ce sujet](https://discuss.huggingface.co/t/share-your-projects/6803) une fois que vous avez terminé. - -Nous sommes impatients de voir ce que vous allez construire avec cet outil ! +# Fin de la première partie du cours ! + +C'est la fin de la première partie du cours ! La partie 2 sera publiée le 15 novembre 2021 avec un grand événement communautaire, pour plus d'informations voir [ici](https://huggingface.co/blog/course-launch-event). + +Vous devriez maintenant être capable de *finetuner* un modèle pré-entraîné sur un problème de classification de texte (phrases simples ou paires de phrases) et de télécharger le résultat sur le *Hub*. Pour vous assurer que vous maîtrisez cette première section, vous devriez refaire ça sur un problème qui vous intéresse (et pas nécessairement en anglais si vous parlez une autre langue) ! Vous pouvez trouver de l'aide dans les [forums d'Hugging Face](https://discuss.huggingface.co/) et partager votre projet dans [ce sujet](https://discuss.huggingface.co/t/share-your-projects/6803) une fois que vous avez terminé. + +Nous sommes impatients de voir ce que vous allez construire avec cet outil ! \ No newline at end of file diff --git a/chapters/fr/chapter4/6.mdx b/chapters/fr/chapter4/6.mdx index 375cb8655..a180d67fa 100644 --- a/chapters/fr/chapter4/6.mdx +++ b/chapters/fr/chapter4/6.mdx @@ -1,223 +1,223 @@ - - - - -# Quiz de fin de chapitre - -Testons ce que vous avez appris dans ce chapitre ! - -### 1. A quoi sont limités les modèles du *Hub* ? - -Transformers.", - explain: "Si les modèles de la bibliothèque 🤗 Transformers sont pris en charge sur le Hub, ils ne sont pas les seuls !" - }, - { - text: "Tous les modèles avec une interface similaire à 🤗 Transformers.", - explain: "Aucune exigence d'interface n'est fixée lors du téléchargement de modèles vers le Hub." - }, - { - text: "Il n'y a pas de limites.", - explain: "C'est vrai ! Il n'y a pas de limites au téléchargement de modèles sur le Hub.", - correct: true - }, - { - text: "Des modèles qui sont d'une certaine manière liés au NLP.", - explain: "Aucune exigence n'est fixée concernant le domaine d'application !" - } - ]} -/> - -### 2. Comment pouvez-vous gérer les modèles sur le *Hub* ? - -Hub sont de simples dépôts Git exploitant git-lfs pour les fichiers volumineux.", - correct: true - } - ]} -/> - -### 3. Que pouvez-vous faire en utilisant l'interface web du *Hub* ? - -Forker » un dépôt existant.", - explain: "« Forker » un dépôt n'est pas possible sur le Hub." - }, - { - text: "Créer un nouveau dépôt de modèles.", - explain: "Correct ! Ce n'est pas tout ce que vous pouvez faire, cependant.", - correct: true - }, - { - text: "Gérer et modifier des fichiers.", - explain: "Correct ! Ce n'est pas la seule bonne réponse, cependant.", - correct: true - }, - { - text: "Télécharger des fichiers.", - explain: "C'est vrai ! Mais ce n'est pas tout.", - correct: true - }, - { - text: "Voir les différences entre les versions.", - explain: "Correct ! Ce n'est pas tout ce que vous pouvez faire.", - correct: true - } - ]} -/> - -### 4. Qu'est-ce qu'une carte de modèle ? - -tokenizer.", - explain: "It is indeed a description of the model, but it's an important piece: if it's incomplete or absent the model's utility is drastically reduced." - }, - { - text: "A way to ensure reproducibility, reusability, and fairness.", - explain: "Correct! Sharing the right information in the model card will help users leverage your model and be aware of its limits and biases. ", - correct: true - }, - { - text: "A Python file that can be run to retrieve information about the model.", - explain: "Model cards are simple Markdown files." - } - ]} -/> - -### 5. Lesquels de ces objets de la bibliothèque 🤗 *Transformers* peuvent être directement partagés sur le Hub avec `push_to_hub()` ? - -{#if fw === 'pt'} -tokenizer", - explain: "Correct ! Tous les tokenizers ont la méthode push_to_hub et l'utiliser poussera tous les fichiers du tokenizer (vocabulaire, architecture du tokenizer, etc.) vers un dépôt donné. Ce n'est pas la seule bonne réponse, cependant !", - correct: true - }, - { - text: "Une configuration de modèle", - explain: "C'est vrai ! Toutes les configurations de modèles ont la méthode push_to_hub et son utilisation les poussera vers un dépôt donné. Que pouvez-vous partager d'autre ?", - correct: true - }, - { - text: "Un modèle", - explain: "Correct ! Tous les modèles ont la méthode push_to_hub qui le pushra ainsi que leurs fichiers de configuration, vers un dépôt donné. Ce n'est pas tout ce que vous pouvez partager, cependant.", - correct: true - }, - { - text: "Trainer", - explain: "C'est exact. Le Trainer implémente aussi la méthode push_to_hub. L'utiliser téléchargera le modèle, sa configuration, le tokenizer et une ébauche de carte de modèle vers un dépôt donné. Essayez une autre réponse !", - correct: true - } - ]} -/> -{:else} -tokenizer", - explain: "C'est vrai ! Toutes les configurations de modèles ont la méthode push_to_hub et son utilisation les poussera vers un dépôt donné. Que pouvez-vous partager d'autre ?", - correct: true - }, - { - text: "Une configuration de modèle", - explain: "C'est vrai ! Toutes les configurations de modèles ont la méthode push_to_hub et son utilisation les poussera vers un dépôt donné. Que pouvez-vous partager d'autre ?", - correct: true - }, - { - text: "Un modèle", - explain: "Correct ! Tous les modèles ont la méthode push_to_hub qui le pushra ainsi que leurs fichiers de configuration, vers un dépôt donné. Ce n'est pas tout ce que vous pouvez partager, cependant.", - correct: true - }, - { - text: "Tout ce qui précède avec un callback dédié", - explain: "C'est exact. Le PushToHubCallback enverra régulièrement tous ces objets à un dépôt pendant l'entraînement.", - correct: true - } - ]} -/> -{/if} - -### 6. Quelle est la première étape lorsqu'on utilise la méthode `push_to_hub()` ou les outils CLI ? - -notebook.", - explain: "Correct ! Cela affichera un widget pour vous permettre de vous authentifier.", - correct: true - }, - ]} -/> - -### 7. Vous utilisez un modèle et un *tokenizer*, comment pouvez-vous les télécharger sur le *Hub* ? - -tokenizer.", - explain: "Correct !", - correct: true - }, - { - text: "Au sein du moteur d'exécution Python, en les enveloppant dans une balise huggingface_hub.", - explain: "Les modèles et les tokenizers bénéficient déjà de huggingface_hub : pas besoin d'emballage supplémentaire !" - }, - { - text: "En les sauvegardant sur le disque et en appelant transformers-cli upload-model.", - explain: "La commande upload-model n'existe pas." - } - ]} -/> - -### 8. Quelles opérations git pouvez-vous faire avec la classe `Repository` ? - -commit.", - explain: "Correct, la méthode git_commit() est là pour ça.", - correct: true - }, - { - text: "Un pull.", - explain: "C'est le but de la méthode git_pull().", - correct: true - }, - { - text: "Un push.", - explain: "La méthode git_push() fait ça.", - correct: true - }, - { - text: "Un merge.", - explain: "Non, cette opération ne sera jamais possible avec cette API." - } - ]} -/> + + + + +# Quiz de fin de chapitre + +Testons ce que vous avez appris dans ce chapitre ! + +### 1. A quoi sont limités les modèles du *Hub* ? + +Transformers.", + explain: "Si les modèles de la bibliothèque 🤗 Transformers sont pris en charge sur le Hub, ils ne sont pas les seuls !" + }, + { + text: "Tous les modèles avec une interface similaire à 🤗 Transformers.", + explain: "Aucune exigence d'interface n'est fixée lors du téléchargement de modèles vers le Hub." + }, + { + text: "Il n'y a pas de limites.", + explain: "Il n'y a pas de limites au téléchargement de modèles sur le Hub.", + correct: true + }, + { + text: "Des modèles qui sont d'une certaine manière liés au NLP.", + explain: "Aucune exigence n'est fixée concernant le domaine d'application !" + } + ]} +/> + +### 2. Comment pouvez-vous gérer les modèles sur le *Hub* ? + +Hub sont de simples dépôts Git exploitant git-lfs pour les fichiers volumineux.", + correct: true + } + ]} +/> + +### 3. Que pouvez-vous faire en utilisant l'interface web du *Hub* ? + +Forker » un dépôt existant.", + explain: "« Forker » un dépôt n'est pas possible sur le Hub." + }, + { + text: "Créer un nouveau dépôt de modèles.", + explain: "Ce n'est pas tout ce que vous pouvez faire, cependant.", + correct: true + }, + { + text: "Gérer et modifier des fichiers.", + explain: "Ce n'est pas la seule bonne réponse, cependant.", + correct: true + }, + { + text: "Télécharger des fichiers.", + explain: "Mais ce n'est pas tout.", + correct: true + }, + { + text: "Voir les différences entre les versions.", + explain: "Ce n'est pas tout ce que vous pouvez faire.", + correct: true + } + ]} +/> + +### 4. Qu'est-ce qu'une carte de modèle ? + +tokenizer.", + explain: "Il s'agit bien d'une description du modèle, mais c'est un élément important : s'il est incomplet ou absent, l'utilité du modèle est considérablement réduite." + }, + { + text: "Un moyen d'assurer la reproductibilité, la réutilisation et l'équité..", + explain: "Le fait de partager les bonnes informations dans la fiche du modèle aidera les utilisateurs à tirer parti de votre modèle et à être conscients de ses limites et de ses biais.", + correct: true + }, + { + text: "Un fichier Python qui peut être exécuté pour récupérer des informations sur le modèle.", + explain: "Les cartes de modèle sont de simples fichiers Markdown." + } + ]} +/> + +### 5. Lesquels de ces objets de la bibliothèque 🤗 *Transformers* peuvent être directement partagés sur le Hub avec `push_to_hub()` ? + +{#if fw === 'pt'} +tokenizer", + explain: "Tous les tokenizers ont la méthode push_to_hub et l'utiliser poussera tous les fichiers du tokenizer (vocabulaire, architecture du tokenizer, etc.) vers un dépôt donné. Ce n'est pas la seule bonne réponse, cependant !", + correct: true + }, + { + text: "Une configuration de modèle", + explain: "Toutes les configurations de modèles ont la méthode push_to_hub et son utilisation les poussera vers un dépôt donné. Que pouvez-vous partager d'autre ?", + correct: true + }, + { + text: "Un modèle", + explain: "Tous les modèles ont la méthode push_to_hub qui le pushra ainsi que leurs fichiers de configuration, vers un dépôt donné. Ce n'est pas tout ce que vous pouvez partager, cependant.", + correct: true + }, + { + text: "Trainer", + explain: "Le Trainer implémente aussi la méthode push_to_hub. L'utiliser téléchargera le modèle, sa configuration, le tokenizer et une ébauche de carte de modèle vers un dépôt donné. Essayez une autre réponse !", + correct: true + } + ]} +/> +{:else} +tokenizer", + explain: "Toutes les configurations de modèles ont la méthode push_to_hub et son utilisation les poussera vers un dépôt donné. Que pouvez-vous partager d'autre ?", + correct: true + }, + { + text: "Une configuration de modèle", + explain: "Toutes les configurations de modèles ont la méthode push_to_hub et son utilisation les poussera vers un dépôt donné. Que pouvez-vous partager d'autre ?", + correct: true + }, + { + text: "Un modèle", + explain: "Tous les modèles ont la méthode push_to_hub qui le pushra ainsi que leurs fichiers de configuration, vers un dépôt donné. Ce n'est pas tout ce que vous pouvez partager, cependant.", + correct: true + }, + { + text: "Tout ce qui précède avec un callback dédié", + explain: "Le PushToHubCallback enverra régulièrement tous ces objets à un dépôt pendant l'entraînement.", + correct: true + } + ]} +/> +{/if} + +### 6. Quelle est la première étape lorsqu'on utilise la méthode `push_to_hub()` ou les outils CLI ? + +notebook.", + explain: "Cela affichera un widget pour vous permettre de vous authentifier.", + correct: true + }, + ]} +/> + +### 7. Vous utilisez un modèle et un *tokenizer*, comment pouvez-vous les télécharger sur le *Hub* ? + +tokenizer.", + explain: " ", + correct: true + }, + { + text: "Au sein du moteur d'exécution Python, en les enveloppant dans une balise huggingface_hub.", + explain: "Les modèles et les tokenizers bénéficient déjà de huggingface_hub : pas besoin d'emballage supplémentaire !" + }, + { + text: "En les sauvegardant sur le disque et en appelant transformers-cli upload-model.", + explain: "La commande upload-model n'existe pas." + } + ]} +/> + +### 8. Quelles opérations git pouvez-vous faire avec la classe `Repository` ? + +commit.", + explain: "La méthode git_commit() est là pour ça.", + correct: true + }, + { + text: "Un pull.", + explain: "C'est le but de la méthode git_pull().", + correct: true + }, + { + text: "Un push.", + explain: "La méthode git_push() fait ça.", + correct: true + }, + { + text: "Un merge.", + explain: "Cette opération ne sera jamais possible avec cette API." + } + ]} +/> \ No newline at end of file diff --git a/chapters/fr/chapter5/1.mdx b/chapters/fr/chapter5/1.mdx index 145c6ecb0..2817734c9 100644 --- a/chapters/fr/chapter5/1.mdx +++ b/chapters/fr/chapter5/1.mdx @@ -1,17 +1,17 @@ -# Introduction - -Dans le [Chapitre 3](/course/fr/chapter3) vous avez eu un premier aperçu de la bibliothèque 🤗 *Datasets* et qu'il y a trois étapes principales pour *finetuner* un modèle : - -1. charger un jeu de données à partir du *Hub* d’Hugging Face. -2. Prétraiter les données avec `Dataset.map()`. -3. Charger et calculer des métriques. - -Mais ce n'est qu'effleurer la surface de ce que 🤗 *Datasets* peut faire ! Dans ce chapitre, nous allons plonger profondément dans cette bibliothèque. En cours de route, nous trouverons des réponses aux questions suivantes : - -* que faire lorsque votre jeu de données n'est pas sur le *Hub* ? -* comment découper et trancher un jeu de données ? (Et si on a _vraiment_ besoin d'utiliser Pandas ?) -* que faire lorsque votre jeu de données est énorme et va monopoliser la RAM de votre ordinateur portable ? -* qu'est-ce que c'est que le « *memory mapping* » et Apache Arrow ? -* comment créer votre propre jeu de données et le pousser sur le *Hub* ? - -Les techniques apprises dans ce chapitre vous prépareront aux tâches avancées de tokenisation et de *finetuning* du [Chapitre 6](/course/fr/chapter6) et du [Chapitre 7](/course/fr/chapter7). Alors prenez un café et commençons ! +# Introduction + +Dans le [chapitre 3](/course/fr/chapter3) vous avez eu un premier aperçu de la bibliothèque 🤗 *Datasets* et des trois étapes principales pour *finetuner* un modèle : + +1. chargement d'un jeu de données à partir du *Hub* d’Hugging Face, +2. prétraitement des données avec `Dataset.map()`, +3. chargement et calcul des métriques. + +Mais ce n'est qu'effleurer la surface de ce que 🤗 *Datasets* peut faire ! Dans ce chapitre, nous allons plonger profondément dans cette bibliothèque. En cours de route, nous trouverons des réponses aux questions suivantes : + +* que faire lorsque votre jeu de données n'est pas sur le *Hub* ? +* comment découper et trancher un jeu de données ? (Et si on a _vraiment_ besoin d'utiliser Pandas ?) +* que faire lorsque votre jeu de données est énorme et va monopoliser la RAM de votre ordinateur portable ? +* qu'est-ce que c'est que le « *memory mapping* » et Apache Arrow ? +* comment créer votre propre jeu de données et le pousser sur le *Hub* ? + +Les techniques apprises dans ce chapitre vous prépareront aux tâches avancées de tokenisation du [chapitre 6](/course/fr/chapter6) et de *finetuning* du [chapitre 7](/course/fr/chapter7). Alors prenez un café et commençons ! \ No newline at end of file diff --git a/chapters/fr/chapter5/2.mdx b/chapters/fr/chapter5/2.mdx index 7f4f93a2b..f05424005 100644 --- a/chapters/fr/chapter5/2.mdx +++ b/chapters/fr/chapter5/2.mdx @@ -1,165 +1,167 @@ -# Que faire si mon jeu de données n'est pas sur le *Hub* ? - - - -Vous savez comment utiliser le [*Hub* d’Hugging Face](https://huggingface.co/datasets) pour télécharger des jeux de données mais en pratique vous vous retrouverez souvent à travailler avec des données stockées sur votre ordinateur portable ou sur un serveur distant. Dans cette section, nous allons vous montrer comment 🤗 *Datasets* peut être utilisé pour charger des jeux de données qui ne sont pas disponibles sur le *Hub* d’Hugging Face. - - - -## Travailler avec des jeux de données locaux et distants - -🤗 *Datasets* fournit des scripts de chargement de jeux de données locaux et distants. Il prend en charge plusieurs formats de données courants, tels que : - -| Format des données | Script de chargement | Exemple | -| :----------------: | :------------------: | :-----------------------------------------------------: | -| CSV & TSV | `csv` | `load_dataset("csv", data_files="my_file.csv")` | -| Fichiers texte | `text` | `load_dataset("text", data_files="my_file.txt")` | -| JSON & Lignes JSON | `json` | `load_dataset("json", data_files="my_file.jsonl")` | -| DataFrames en Pickle | `pandas` | `load_dataset("pandas", data_files="my_dataframe.pkl")` | - -Comme indiqué dans le tableau, pour chaque format de données, nous avons juste besoin de spécifier le type de script de chargement dans la fonction `load_dataset()`, ainsi qu'un argument `data_files` qui spécifie le chemin vers un ou plusieurs fichiers. Commençons par charger un jeu de données à partir de fichiers locaux puis plus tard comment faire la même chose avec des fichiers distants. - -## Charger un jeu de données local - -Pour cet exemple, nous utilisons le jeu de données [SQuAD-it](https://github.com/crux82/squad-it/) qui est un grand jeu de données pour la tâche de *Question Awnswering* en italien. - -Les échantillons d’entraînement et de test sont hébergés sur GitHub, nous pouvons donc les télécharger avec une simple commande `wget` : - -```python -!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-train.json.gz -!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-test.json.gz -``` - -Cela télécharge deux fichiers compressés appelés *SQuAD_it-train.json.gz* et *SQuAD_it-test.json.gz* que nous pouvons décompresser avec la commande Linux `gzip` : - -```python -!gzip -dkv SQuAD_it-*.json.gz -``` - -```bash -SQuAD_it-test.json.gz: 87.4% -- replaced with SQuAD_it-test.json -SQuAD_it-train.json.gz: 82.2% -- replaced with SQuAD_it-train.json -``` - -Nous pouvons voir que les fichiers compressés ont été remplacés par _SQuAD_it-train.json_ et _SQuAD_it-text.json_, et que les données sont stockées au format JSON. - - - -✎ Si vous vous demandez pourquoi il y a un caractère `!` dans les commandes shell ci-dessus, c'est parce que nous les exécutons dans un *notebook* Jupyter. Supprimez simplement le préfixe si vous souhaitez télécharger et décompresser le jeu de données dans un terminal. - - - -Pour charger un fichier JSON avec la fonction `load_dataset()`, nous avons juste besoin de savoir si nous avons affaire à du JSON ordinaire (similaire à un dictionnaire imbriqué) ou à des lignes JSON (JSON séparé par des lignes). Comme de nombreux jeux de données de questions-réponses, SQuAD-it utilise le format imbriqué où tout le texte est stocké dans un champ `data`. Cela signifie que nous pouvons charger le jeu de données en spécifiant l'argument `field` comme suit : - -```py -from datasets import load_dataset - -squad_it_dataset = load_dataset("json", data_files="SQuAD_it-train.json", field="data") -``` - -Par défaut, le chargement de fichiers locaux crée un objet `DatasetDict` avec un échantillon `train`. Nous pouvons le voir en inspectant l'objet `squad_it_dataset` : - -```py -squad_it_dataset -``` - -```python out -DatasetDict({ - train: Dataset({ - features: ['title', 'paragraphs'], - num_rows: 442 - }) -}) -``` - -Cela nous montre le nombre de lignes et les noms de colonnes associés à l’échantillon d’entraînement. Nous pouvons afficher l'un des exemples en indexant l’échantillon `train` comme suit : - -```py -squad_it_dataset["train"][0] -``` - -```python out -{ - "title": "Terremoto del Sichuan del 2008", # Séisme du Sichuan 2008 - "paragraphs": [ - { - "context": "Il terremoto del Sichuan del 2008 o il terremoto...", # Le tremblement de terre du Sichuan de 2008 ou le... - "qas": [ - { - "answers": [{"answer_start": 29, "text": "2008"}], - "id": "56cdca7862d2951400fa6826", - "question": "In quale anno si è verificato il terremoto nel Sichuan?", # En quelle année le tremblement de terre du Sichuan a-t-il eu lieu ? - }, - ... - ], - }, - ... - ], -} -``` - -Super, nous avons chargé notre premier jeu de données local ! Mais ce que nous voulons vraiment, c'est inclure à la fois les échantillons `train` et `test` dans un seul objet `DatasetDict` afin que nous puissions appliquer les fonctions `Dataset.map()` sur les deux à la fois . Pour ce faire, nous pouvons fournir un dictionnaire à l'argument `data_files` qui associe chaque nom des échantillons à un fichier associé à cet échantillon : - -```py -data_files = {"train": "SQuAD_it-train.json", "test": "SQuAD_it-test.json"} -squad_it_dataset = load_dataset("json", data_files=data_files, field="data") -squad_it_dataset -``` - -```python out -DatasetDict({ - train: Dataset({ - features: ['title', 'paragraphs'], - num_rows: 442 - }) - test: Dataset({ - features: ['title', 'paragraphs'], - num_rows: 48 - }) -}) -``` - -C'est exactement ce que nous voulions. Désormais, nous pouvons appliquer diverses techniques de prétraitement pour nettoyer les données, tokeniser les avis, etc. - - - -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. - - - -Les scripts de chargement de 🤗 *Datasets* prennent en charge la décompression automatique des fichiers d'entrée. Nous aurions donc pu ignorer l'utilisation de `gzip` en pointant l'argument `data_files` directement sur les fichiers compressés : - -```py -data_files = {"train": "SQuAD_it-train.json.gz", "test": "SQuAD_it-test.json.gz"} -squad_it_dataset = load_dataset("json", data_files=data_files, field="data") -``` - -Cela peut être utile si vous ne souhaitez pas décompresser manuellement de nombreux fichiers GZIP. La décompression automatique s'applique également à d'autres formats courants tels que ZIP et TAR. Il vous suffit donc de pointer `data_files` vers les fichiers compressés et vous êtes prêt à partir ! - -Maintenant que vous savez comment charger des fichiers locaux sur votre ordinateur portable ou de bureau, examinons le chargement de fichiers distants. - -## Charger un jeu de données distant - -Si vous travaillez en tant que *data scientist* ou codeur dans une entreprise, il y a de fortes chances que les juex de données que vous souhaitez analyser soient stockés sur un serveur distant. Heureusement, charger des fichiers distants est aussi simple que de charger des fichiers locaux ! Au lieu de fournir un chemin vers les fichiers locaux, nous pointons l'argument `data_files` de `load_dataset()` vers une ou plusieurs URL où les fichiers distants sont stockés. Par exemple, pour le jeu de données SQuAD-it hébergé sur GitHub, nous pouvons simplement faire pointer `data_files` vers les URL _SQuAD_it-*.json.gz_ comme suit : - -```py -url = "https://github.com/crux82/squad-it/raw/master/" -data_files = { - "train": url + "SQuAD_it-train.json.gz", - "test": url + "SQuAD_it-test.json.gz", -} -squad_it_dataset = load_dataset("json", data_files=data_files, field="data") -``` - -Cela renvoie le même objet `DatasetDict` obtenu ci-dessus mais nous évite de télécharger et de décompresser manuellement les fichiers _SQuAD_it-*.json.gz_. Ceci conclut notre incursion dans les différentes façons de charger des jeux de données qui ne sont pas hébergés sur le *Hub* d’Hugging Face. Maintenant que nous avons un jeu de données avec lequel jouer, mettons la main à la pâte avec diverses techniques de gestion des données ! - - - -✏️ **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). - - +# Que faire si mon jeu de données n'est pas sur le Hub ? + + + +Vous savez comment utiliser le [*Hub*](https://huggingface.co/datasets) pour télécharger des jeux de données mais en pratique vous vous retrouverez souvent à travailler avec des données stockées sur votre ordinateur portable ou sur un serveur distant. Dans cette section, nous allons vous montrer comment 🤗 *Datasets* peut être utilisé pour charger des jeux de données qui ne sont pas disponibles sur le *Hub* d’Hugging Face. + + + +## Travailler avec des jeux de données locaux et distants + +🤗 *Datasets* fournit des scripts de chargement de jeux de données locaux et distants. La bibliothèque prend en charge plusieurs formats de données courants, tels que : + +| Format des données | Script de chargement | Exemple | +| :----------------: | :------------------: | :-----------------------------------------------------: | +| CSV & TSV | `csv` | `load_dataset("csv", data_files="my_file.csv")` | +| Fichiers texte | `text` | `load_dataset("text", data_files="my_file.txt")` | +| JSON & JSON Lines | `json` | `load_dataset("json", data_files="my_file.jsonl")` | +| DataFrames en Pickle | `pandas` | `load_dataset("pandas", data_files="my_dataframe.pkl")` | + +Comme indiqué dans le tableau, pour chaque format de données, nous avons juste besoin de spécifier le type de script de chargement dans la fonction `load_dataset()`, ainsi qu'un argument `data_files` qui spécifie le chemin vers un ou plusieurs fichiers. Commençons par charger un jeu de données à partir de fichiers locaux puis plus tard comment faire la même chose avec des fichiers distants. + +## Charger un jeu de données local + +Pour cet exemple, nous utilisons le jeu de données [SQuAD-it](https://github.com/crux82/squad-it/) qui est un grand jeu de données pour la tâche de *Question Awnswering* en italien. + +Les échantillons d’entraînement et de test sont hébergés sur GitHub, nous pouvons donc les télécharger avec une simple commande `wget` : + +```python +!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-train.json.gz +!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-test.json.gz +``` + +Cela télécharge deux fichiers compressés appelés *SQuAD_it-train.json.gz* et *SQuAD_it-test.json.gz* que nous pouvons décompresser avec la commande Linux `gzip` : + +```python +!gzip -dkv SQuAD_it-*.json.gz +``` + +```bash +SQuAD_it-test.json.gz: 87.4% -- replaced with SQuAD_it-test.json +SQuAD_it-train.json.gz: 82.2% -- replaced with SQuAD_it-train.json +``` + +Nous pouvons voir que les fichiers compressés ont été remplacés par _SQuAD_it-train.json_ et _SQuAD_it-text.json_, et que les données sont stockées au format JSON. + + + +✎ Si vous vous demandez pourquoi il y a un caractère `!` dans les commandes *shell* ci-dessus, c'est parce que nous les exécutons dans un *notebook* Jupyter. Supprimez simplement le préfixe si vous souhaitez télécharger et décompresser le jeu de données dans un terminal. + + + +Pour charger un fichier JSON avec la fonction `load_dataset()`, nous avons juste besoin de savoir si nous avons affaire à du JSON ordinaire (similaire à un dictionnaire imbriqué) ou à des lignes JSON (JSON séparé par des lignes). Comme de nombreux jeux de données de questions-réponses, SQuAD-it utilise le format imbriqué où tout le texte est stocké dans un champ `data`. Cela signifie que nous pouvons charger le jeu de données en spécifiant l'argument `field` comme suit : + +```py +from datasets import load_dataset + +squad_it_dataset = load_dataset("json", data_files="SQuAD_it-train.json", field="data") +``` + +Par défaut, le chargement de fichiers locaux crée un objet `DatasetDict` avec un échantillon `train`. Nous pouvons le voir en inspectant l'objet `squad_it_dataset` : + +```py +squad_it_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 442 + }) +}) +``` + +Cela nous montre le nombre de lignes et les noms de colonnes associés à l’échantillon d’entraînement. Nous pouvons afficher l'un des exemples en indexant l’échantillon `train` comme suit : + +```py +squad_it_dataset["train"][0] +``` + +```python out +{ + "title": "Terremoto del Sichuan del 2008", # Séisme du Sichuan 2008 + "paragraphs": [ + { + "context": "Il terremoto del Sichuan del 2008 o il terremoto...", + # Le tremblement de terre du Sichuan de 2008 ou le... + "qas": [ + { + "answers": [{"answer_start": 29, "text": "2008"}], + "id": "56cdca7862d2951400fa6826", + "question": "In quale anno si è verificato il terremoto nel Sichuan?", + # En quelle année le tremblement de terre du Sichuan a-t-il eu lieu ? + }, + ... + ], + }, + ... + ], +} +``` + +Super, nous avons chargé notre premier jeu de données local ! Mais ce que nous voulons vraiment, c'est inclure à la fois les échantillons `train` et `test` dans un seul objet `DatasetDict` afin que nous puissions appliquer les fonctions `Dataset.map()` sur les deux à la fois . Pour ce faire, nous pouvons fournir un dictionnaire à l'argument `data_files` qui associe chaque nom des échantillons à un fichier associé à cet échantillon : + +```py +data_files = {"train": "SQuAD_it-train.json", "test": "SQuAD_it-test.json"} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +squad_it_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 442 + }) + test: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 48 + }) +}) +``` + +C'est exactement ce que nous voulions. Désormais, nous pouvons appliquer diverses techniques de prétraitement pour nettoyer les données, tokeniser les avis, etc. + + + +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. + + + +Les scripts de chargement de 🤗 *Datasets* prennent en charge la décompression automatique des fichiers d'entrée. Nous aurions donc pu ignorer l'utilisation de `gzip` en pointant l'argument `data_files` directement sur les fichiers compressés : + +```py +data_files = {"train": "SQuAD_it-train.json.gz", "test": "SQuAD_it-test.json.gz"} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +``` + +Cela peut être utile si vous ne souhaitez pas décompresser manuellement de nombreux fichiers GZIP. La décompression automatique s'applique également à d'autres formats courants tels que ZIP et TAR. Il vous suffit donc de pointer `data_files` vers les fichiers compressés et vous êtes prêt à partir ! + +Maintenant que vous savez comment charger des fichiers locaux sur votre ordinateur portable ou de bureau, examinons le chargement de fichiers distants. + +## Charger un jeu de données distant + +Si vous travaillez en tant que *data scientist* ou codeur dans une entreprise, il y a de fortes chances que les jeux de données que vous souhaitez analyser soient stockés sur un serveur distant. Heureusement, charger des fichiers distants est aussi simple que de charger des fichiers locaux ! Au lieu de fournir un chemin vers les fichiers locaux, nous pointons l'argument `data_files` de `load_dataset()` vers une ou plusieurs URL où les fichiers distants sont stockés. Par exemple, pour le jeu de données SQuAD-it hébergé sur GitHub, nous pouvons simplement faire pointer `data_files` vers les URL _SQuAD_it-*.json.gz_ comme suit : + +```py +url = "https://github.com/crux82/squad-it/raw/master/" +data_files = { + "train": url + "SQuAD_it-train.json.gz", + "test": url + "SQuAD_it-test.json.gz", +} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +``` + +Cela renvoie le même objet `DatasetDict` obtenu ci-dessus mais nous évite de télécharger et de décompresser manuellement les fichiers _SQuAD_it-*.json.gz_. Ceci conclut notre incursion dans les différentes façons de charger des jeux de données qui ne sont pas hébergés sur le *Hub*. Maintenant que nous avons un jeu de données avec lequel jouer, mettons la main à la pâte avec diverses techniques de gestion des données ! + + + +✏️ **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). + + diff --git a/chapters/fr/chapter5/3.mdx b/chapters/fr/chapter5/3.mdx index ef1ad0367..1962c4dad 100644 --- a/chapters/fr/chapter5/3.mdx +++ b/chapters/fr/chapter5/3.mdx @@ -1,743 +1,752 @@ -# Il est temps de trancher et de découper - - - -La plupart du temps, les données avec lesquelles vous travaillez ne sont pas parfaitement préparées pour l’entraînements de modèles. Dans cette section, nous allons explorer les différentes fonctionnalités fournies par 🤗 *Datasets* pour nettoyer vos jeux de données. - - - -## Trancher et découper nos données - -Semblable à Pandas, 🤗 *Datasets* fournit plusieurs fonctions pour manipuler le contenu des objets `Dataset` et `DatasetDict`. Nous avons déjà rencontré la méthode `Dataset.map()` dans le [Chapitre 3](/course/fr/chapter3) et dans cette section nous allons explorer certaines des autres fonctions à notre disposition. - -Pour cet exemple, nous utiliserons le [*Drug Review Dataset*](https://archive.ics.uci.edu/ml/datasets/Drug+Review+Dataset+%28Drugs.com%29) qui est hébergé sur [*UC Irvine Machine Learning Repository*](https://archive.ics.uci.edu/ml/index.php) et contenant des avis de patients sur divers médicaments ainsi que la condition traitée et une note de 10 étoiles sur la satisfaction du patient. - -Nous devons d'abord télécharger et extraire les données, ce qui peut être fait avec les commandes `wget` et `unzip` : - -```py -!wget "https://archive.ics.uci.edu/ml/machine-learning-databases/00462/drugsCom_raw.zip" -!unzip drugsCom_raw.zip -``` - -Étant donné que TSV n'est qu'une variante de CSV qui utilise des tabulations au lieu de virgules comme séparateurs, nous pouvons charger ces fichiers en utilisant le script de chargement `csv` et en spécifiant l'argument `delimiter` dans la fonction `load_dataset()` comme suit : - -```py -from datasets import load_dataset - -data_files = {"train": "drugsComTrain_raw.tsv", "test": "drugsComTest_raw.tsv"} -# \t is the tab character in Python -drug_dataset = load_dataset("csv", data_files=data_files, delimiter="\t") -``` - -Une bonne pratique lors de toute sorte d'analyse de données consiste à prélever un petit échantillon aléatoire pour avoir une idée rapide du type de données avec lesquelles vous travaillez. Dans 🤗 *Datasets*, nous pouvons créer un échantillon aléatoire en enchaînant les fonctions `Dataset.shuffle()` et `Dataset.select()` : - -```py -drug_sample = drug_dataset["train"].shuffle(seed=42).select(range(1000)) -# Un coup d'œil sur les premiers exemples -drug_sample[:3] -``` - -```python out -{'Unnamed: 0': [87571, 178045, 80482], - 'drugName': ['Naproxen', 'Duloxetine', 'Mobic'], - 'condition': ['Gout, Acute', 'ibromyalgia', 'Inflammatory Conditions'], #['Goutte aiguë', 'ibromyalgie', 'Affections inflammatoires'] - 'review': ['"like the previous person mention, I'm a strong believer of aleve, it works faster for my gout than the prescription meds I take. No more going to the doctor for refills.....Aleve works!"', # comme la personne précédente l'a mentionné, je suis un fervent partisan de l'aleve, il fonctionne plus rapidement pour ma goutte que les médicaments sur ordonnance que je prends. Je n'ai plus besoin d'aller chez le médecin pour des renouvellements.....Aleve fonctionne !" - '"I have taken Cymbalta for about a year and a half for fibromyalgia pain. It is great\r\nas a pain reducer and an anti-depressant, however, the side effects outweighed \r\nany benefit I got from it. I had trouble with restlessness, being tired constantly,\r\ndizziness, dry mouth, numbness and tingling in my feet, and horrible sweating. I am\r\nbeing weaned off of it now. Went from 60 mg to 30mg and now to 15 mg. I will be\r\noff completely in about a week. The fibro pain is coming back, but I would rather deal with it than the side effects."', # J'ai pris du Cymbalta pendant environ un an et demi pour des douleurs de la fibromyalgie. C'est un excellent analgésique et un antidépresseur, mais les effets secondaires l'ont emporté sur tous les avantages que j'en ai tirés. J'ai eu des problèmes d'agitation, de fatigue constante, de vertiges, de bouche sèche, d'engourdissement, de picotements dans les pieds, et de transpiration horrible. Je suis en train de m'en sevrer maintenant. Je suis passée de 60 mg à 30 mg et maintenant à 15 mg. Je l'arrêterai complètement dans environ une semaine. La douleur de la fibrose revient, mais je préfère la supporter plutôt que les effets secondaires. - '"I have been taking Mobic for over a year with no side effects other than an elevated blood pressure. I had severe knee and ankle pain which completely went away after taking Mobic. I attempted to stop the medication however pain returned after a few days."'], # J'ai pris Mobic pendant plus d'un an sans effets secondaires autres qu'une pression sanguine élevée. J'avais de fortes douleurs au genou et à la cheville qui ont complètement disparu après avoir pris Mobic. J'ai essayé d'arrêter le médicament mais la douleur est revenue après quelques jours." - 'rating': [9.0, 3.0, 10.0], - 'date': ['September 2, 2015', 'November 7, 2011', 'June 5, 2013'], #['2 septembre 2015', '7 novembre 2011', '5 juin 2013'] - 'usefulCount': [36, 13, 128]} -``` - -Notez que nous avons corrigé la graine dans `Dataset.shuffle()` à des fins de reproductibilité. `Dataset.select()` attend un itérable d'indices, nous avons donc passé `range(1000)` pour récupérer les 1 000 premiers exemples du jeu de données mélangé. À partir de cet échantillon, nous pouvons déjà voir quelques bizarreries dans notre jeu de données : - -* la colonne `Unnamed: 0` ressemble étrangement à un identifiant anonyme pour chaque patient, -* la colonne `condition` comprend un mélange d'étiquettes en majuscules et en minuscules, -* les avis sont de longueur variable et contiennent un mélange de séparateurs de lignes Python (`\r\n`) ainsi que des codes de caractères HTML comme `&\#039;`. - -Voyons comment nous pouvons utiliser 🤗 *Datasets* pour traiter chacun de ces problèmes. Pour tester l'hypothèse de l'ID patient pour la colonne `Unnamed : 0`, nous pouvons utiliser la fonction `Dataset.unique()` pour vérifier que le nombre d'ID correspond au nombre de lignes dans chaque division : - -```py -for split in drug_dataset.keys(): - assert len(drug_dataset[split]) == len(drug_dataset[split].unique("Unnamed: 0")) -``` - -Cela semble confirmer notre hypothèse, alors nettoyons un peu en renommant la colonne `Unnamed: 0` en quelque chose d'un peu plus interprétable. Nous pouvons utiliser la fonction `DatasetDict.rename_column()` pour renommer la colonne sur les deux divisions en une seule fois : - -```py -drug_dataset = drug_dataset.rename_column( - original_column_name="Unnamed: 0", new_column_name="patient_id" -) -drug_dataset -``` - -```python out -DatasetDict({ - train: Dataset({ - features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'], - num_rows: 161297 - }) - test: Dataset({ - features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'], - num_rows: 53766 - }) -}) -``` - - - -✏️ **Essayez !** Utilisez la fonction ` Dataset.unique()` pour trouver le nombre de médicaments et de conditions uniques dans les échantillons d'entraînement et de test. - - - -Ensuite, normalisons toutes les étiquettes `condition` en utilisant `Dataset.map()`. Comme nous l'avons fait avec la tokenisation dans le [chapitre 3](/course/fr/chapter3), nous pouvons définir une fonction simple qui peut être appliquée sur toutes les lignes de chaque division dans `drug_dataset` : - -```py -def lowercase_condition(example): - return {"condition": example["condition"].lower()} - - -drug_dataset.map(lowercase_condition) -``` - -```python out -AttributeError: 'NoneType' object has no attribute 'lower' -``` - -Oh non, nous rencontrons un problème avec notre fonction ! À partir de l'erreur, nous pouvons déduire que certaines des entrées de la colonne `condition` sont `None` ne pouvant donc pas être mises en minuscules car ce ne sont pas des chaînes. Supprimons ces lignes en utilisant `Dataset.filter()`, qui fonctionne de manière similaire à `Dataset.map()` et attend une fonction qui reçoit un seul exemple issu du jeu de données. Au lieu d'écrire une fonction explicite comme : - -```py -def filter_nones(x): - return x["condition"] is not None -``` - -puis exécuter `drug_dataset.filter(filter_nones)`, nous pouvons le faire en une seule ligne en utilisant une _fonction lambda_. En Python, les fonctions lambda sont de petites fonctions que vous pouvez définir sans les nommer explicitement. Ils prennent la forme générale : - -``` -lambda : -``` - -où `lambda` est l'un des [mots clés](https://docs.python.org/3/reference/lexical_analysis.html#keywords) spéciaux de Python, `` est une liste/ensemble de valeurs séparées par des virgules qui définissent les entrées de la fonction et `` représente les opérations que vous souhaitez exécuter. Par exemple, nous pouvons définir une simple fonction lambda qui met au carré un nombre comme suit : - -``` -lambda x : x * x -``` - -Pour appliquer cette fonction à une entrée, nous devons l'envelopper ainsi que l'entrée entre parenthèses : - -```py -(lambda x: x * x)(3) -``` - -```python out -9 -``` - -De même, nous pouvons définir des fonctions lambda avec plusieurs arguments en les séparant par des virgules. Par exemple, nous pouvons calculer l'aire d'un triangle comme suit : - -```py -(lambda base, height: 0.5 * base * height)(4, 8) -``` - -```python out -16.0 -``` - -Les fonctions lambda sont pratiques lorsque vous souhaitez définir de petites fonctions à usage unique (pour plus d'informations à leur sujet, nous vous recommandons de lire l'excellent [tutoriel Real Python](https://realpython.com/python-lambda/) d'André Burgaud) . Dans le contexte de la bibliothèque 🤗 *Datasets*, nous pouvons utiliser des fonctions lambda pour définir des opérations simples de mappage et de filtrage. Utilisons cette astuce pour éliminer les entrées `None` dans notre jeu de données : - -```py -drug_dataset = drug_dataset.filter(lambda x: x["condition"] is not None) -``` - -Avec les entrées `None` supprimées, nous pouvons normaliser notre colonne `condition` : - -```py -drug_dataset = drug_dataset.map(lowercase_condition) -# Vérification que la mise en minuscule a fonctionné -drug_dataset["train"]["condition"][:3] -``` - -```python out -['left ventricular dysfunction', 'adhd', 'birth control'] -``` - -Ça marche ! Maintenant que nous avons nettoyé les étiquettes, examinons le nettoyage des avis eux-mêmes. - -## Création de nouvelles colonnes - -Chaque fois que vous avez affaire à des avis de clients, une bonne pratique consiste à vérifier le nombre de mots dans chaque avis. Une critique peut être un simple mot comme « Génial ! » ou un essai complet avec des milliers de mots. Selon le cas d'usage, vous devrez gérer ces extrêmes différemment. Pour calculer le nombre de mots dans chaque révision, nous utiliserons une heuristique approximative basée sur la division de chaque texte par des espaces. - -Définissons une fonction simple qui compte le nombre de mots dans chaque avis : - -```py -def compute_review_length(example): - return {"review_length": len(example["review"].split())} -``` - -Contrairement à notre fonction `lowercase_condition()`, `compute_review_length()` renvoie un dictionnaire dont la clé ne correspond pas à l'un des noms de colonne du jeu de données. Dans ce cas, lorsque `compute_review_length()` est passé à `Dataset.map()`, il est appliqué à toutes les lignes du jeu de données pour créer une nouvelle colonne `review_length` : - -```py -drug_dataset = drug_dataset.map(compute_review_length) -# Inspecter le premier exemple d'entraînement -drug_dataset["train"][0] -``` - -```python out -{'patient_id': 206461, - 'drugName': 'Valsartan', - 'condition': 'left ventricular dysfunction', # dysfonctionnement du ventricule gauche - 'review': '"It has no side effect, I take it in combination of Bystolic 5 Mg and Fish Oil"', # Il n'a aucun effet secondaire, je le prends en combinaison avec Bystolic 5 mg et de l'huile de poisson. - 'rating': 9.0, - 'date': 'May 20, 2012', # 20 mai 2012 - 'usefulCount': 27, - 'review_length': 17} -``` - -Comme prévu, nous pouvons voir qu'une colonne `review_length` a été ajoutée à notre jeu d'entraînement. Nous pouvons trier cette nouvelle colonne avec `Dataset.sort()` pour voir à quoi ressemblent les valeurs extrêmes : - -```py -drug_dataset["train"].sort("review_length")[:3] -``` - -```python out -{'patient_id': [103488, 23627, 20558], - 'drugName': ['Loestrin 21 1 / 20', 'Chlorzoxazone', 'Nucynta'], - 'condition': ['birth control', 'muscle spasm', 'pain'], # contraception, spasme musculaire, douleur. - 'review': ['"Excellent."', '"useless"', '"ok"'], # Excellent, inutile, ok - 'rating': [10.0, 1.0, 6.0], - 'date': ['November 4, 2008', 'March 24, 2017', 'August 20, 2016'], # 4 novembre 2008, 24 mars 2017, 20 août 2016 - 'usefulCount': [5, 2, 10], - 'review_length': [1, 1, 1]} -``` - -Comme nous le soupçonnions, certaines critiques ne contiennent qu'un seul mot, ce qui, bien que cela puisse convenir à l'analyse des sentiments, n’est pas informatif si nous voulons prédire la condition. - - - -🙋 Une autre façon d'ajouter de nouvelles colonnes à un jeu de données consiste à utiliser la fonction `Dataset.add_column()`. Cela vous permet de donner la colonne sous forme de liste Python ou de tableau NumPy et peut être utile dans les situations où `Dataset.map()` n'est pas bien adapté à votre analyse. - - - -Utilisons la fonction `Dataset.filter()` pour supprimer les avis contenant moins de 30 mots. De la même manière que nous l'avons fait avec la colonne `condition`, nous pouvons filtrer les avis très courts en exigeant que les avis aient une longueur supérieure à ce seuil : - -```py -drug_dataset = drug_dataset.filter(lambda x: x["review_length"] > 30) -print(drug_dataset.num_rows) -``` - -```python out -{'train': 138514, 'test': 46108} -``` - -Comme vous pouvez le constater, cela a supprimé environ 15 % des avis de nos jeux d'entraînement et de test d'origine. - - - -✏️ **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. - - - -La dernière chose à laquelle nous devons faire face est la présence de caractères HTML dans nos avis. Nous pouvons utiliser le module `html` de Python pour supprimer ces caractères, comme ceci : - -```py -import html - -text = "I'm a transformer called BERT" -html.unescape(text) -``` - -```python out -"I'm a transformer called BERT" -``` - -Nous utilisons `Dataset.map()` pour démasquer tous les caractères HTML de notre corpus : - -```python -drug_dataset = drug_dataset.map(lambda x: {"review": html.unescape(x["review"])}) -``` - -Comme vous pouvez le voir, la méthode `Dataset.map()` est très utile pour le traitement des données. Et nous n'avons même pas effleuré la surface de tout ce qu'elle peut faire ! - -## Les superpouvoirs de la méthode `map()` - -La méthode `Dataset.map()` prend un argument `batched` qui, s'il est défini sur `True`, l'amène à envoyer un batch d'exemples à la fonction map en une seule fois (la taille du batch est configurable mais est fixé par défaut à 1 000). Par exemple, la fonction `map()` précédente qui supprime tout le code HTML prend un peu de temps à s'exécuter (vous pouvez lire le temps pris dans les barres de progression). On peut accélérer cela en traitant plusieurs éléments en même temps à l'aide d'une compréhension de liste. - -Lorsque vous spécifiez `batched=True`, la fonction reçoit un dictionnaire avec les champs du jeu de données mais chaque valeur est maintenant une _liste de valeurs_ et non plus une seule valeur. La valeur retournée par `Dataset.map()` devrait être la même : un dictionnaire avec les champs que nous voulons mettre à jour ou ajouter à notre jeu de données, et une liste de valeurs. Par exemple, voici une autre façon de supprimer tous les caractères HTML, mais en utilisant `batched=True` : - -```python -new_drug_dataset = drug_dataset.map( - lambda x: {"review": [html.unescape(o) for o in x["review"]]}, batched=True -) -``` - -Si vous exécutez ce code dans un *notebook*, vous verrez que cette commande s'exécute beaucoup plus rapidement que la précédente. Et ce n'est pas parce que nos critiques ont déjà été scannées au format HTML. Si vous ré-exécutez l'instruction de la section précédente (sans `batched=True`), cela prendra le même temps qu'avant. En effet, les compréhensions de liste sont généralement plus rapides que l'exécution du même code dans une boucle `for` et nous gagnons également en performances en accédant à de nombreux éléments en même temps au lieu d'un par un. - -L'utilisation de `Dataset.map()` avec `batched=True` est essentielle pour les *tokenizers rapides* que nous rencontrerons dans le [Chapitre 6](/course/fr/chapter6) et qui peuvent rapidement tokeniser de grandes listes de textes. Par exemple, pour tokeniser toutes les critiques de médicaments avec un *tokenizer* rapide nous pouvons utiliser une fonction comme celle-ci : - -```python -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") - - -def tokenize_function(examples): - return tokenizer(examples["review"], truncation=True) -``` - -Comme vous l'avez vu dans le [Chapitre 3](/course/fr/chapter3), nous pouvons passer un ou plusieurs exemples au *tokenizer*. Nous pouvons donc utiliser cette fonction avec ou sans `batched=True`. Profitons-en pour comparer les performances des différentes options. Dans un *notebook*, vous pouvez chronométrer une instruction d'une ligne en ajoutant `%time` avant la ligne de code que vous souhaitez mesurer : - -```python no-format -%time tokenized_dataset = drug_dataset.map(tokenize_function, batched=True) -``` - -Vous pouvez également chronométrer une cellule entière en mettant `%%time` au début de la cellule. Sur le matériel sur lequel nous avons exécuté cela, cela affichait 10,8 s pour cette instruction (c'est le nombre écrit après "Wall time"). - - - -✏️ **Essayez !** Exécutez la même instruction avec et sans `batched=True`, puis essayez-le avec un *tokenizer* lent (ajoutez `use_fast=False` dans la méthode `AutoTokenizer.from_pretrained()`) afin que vous puissiez voir quels temps vous obtenez sur votre matériel. - - - -Voici les résultats que nous avons obtenus avec et sans batching, avec un *tokenizer* rapide et un lent : - -Options | *Tokenizer* rapide | *Tokenizer* lent -:--------------:|:----------------:|:-----------------: -`batched=True` | 10.8s | 4min41s -`batched=False` | 59.2s | 5min3s - -Cela signifie que l'utilisation d'un *tokenizer* rapide avec l'option `batched=True` est 30 fois plus rapide que son homologue lent sans batch. C'est vraiment incroyable ! C'est la raison principale pour laquelle les *tokenizers* rapides sont la valeur par défaut lors de l'utilisation de `AutoTokenizer` (et pourquoi ils sont appelés « rapides »). Ils sont capables d'atteindre une telle vitesse car en coulisses le code de tokenisation est exécuté en Rust qui est un langage facilitant la parallélisation de l'exécution du code. - -La parallélisation est également la raison du gain de vitesse de près de 6 fois obtenue par le *tokenizer* rapide avec batch. Vous ne pouvez pas paralléliser une seule opération de tokenisation, mais lorsque vous souhaitez *tokeniser* de nombreux textes en même temps, vous pouvez simplement répartir l'exécution sur plusieurs processus. Chacun responsable de ses propres textes. - -`Dataset.map()` possède aussi ses propres capacités de parallélisation. Comme elles ne sont pas soutenus par Rust, un *tokenizer* lent ne peut pas rattraper un rapide mais cela peut toujours être utile (surtout si vous utilisez un *tokenizer* qui n'a pas de version rapide). Pour activer le multitraitement, utilisez l'argument `num_proc` et spécifiez le nombre de processus à utiliser dans votre appel à `Dataset.map()` : - -```py -slow_tokenizer = AutoTokenizer.from_pretrained("bert-base-cased", use_fast=False) - - -def slow_tokenize_function(examples): - return slow_tokenizer(examples["review"], truncation=True) - - -tokenized_dataset = drug_dataset.map(slow_tokenize_function, batched=True, num_proc=8) -``` - -Vous pouvez faire des tests pour déterminer le nombre optimal de processus à utiliser. Dans notre cas 8 semble produire le meilleur gain de vitesse. Voici les chiffres que nous avons obtenus avec et sans multitraitement : - -Options | *Tokenizer* rapide | *Tokenizer* lent -:----------------------------:|:----------------:|:---------------: -`batched=True` | 10.8s | 4min41s -`batched=False` | 59.2s | 5min3s -`batched=True`, `num_proc=8` | 6.52s | 41.3s -`batched=False`, `num_proc=8` | 9.49s | 45.2s - -Ce sont des résultats beaucoup plus raisonnables pour le *tokenizer* lent mais les performances du *tokenizer* rapide ont également été considérablement améliorées. Notez, cependant, que ce ne sera pas toujours le cas : pour des valeurs de `num_proc` autres que 8, nos tests ont montré qu'il était plus rapide d'utiliser `batched=True` sans cette option. En général, nous ne recommandons pas d'utiliser le multitraitement pour les *tokenizers* rapides avec `batched=True`. - - - -Utiliser `num_proc` pour accélérer votre traitement est généralement une bonne idée tant que la fonction que vous utilisez n'effectue pas déjà une sorte de multitraitement. - - - -Toutes ces fonctionnalités condensées en une seule méthode sont déjà assez étonnantes, mais il y a plus ! Avec `Dataset.map()` et `batched=True` vous pouvez modifier le nombre d'éléments dans votre jeu de données. Ceci est très utile dans de nombreuses situations où vous souhaitez créer plusieurs fonctionnalités d'entraînement à partir d'un exemple. Nous devrons le faire dans le cadre du prétraitement de plusieurs des tâches de traitement du langage naturel que nous entreprendrons dans le [Chapitre 7](/course/fr/chapter7). - - - -💡 En machine learning, un _exemple_ est généralement défini comme l'ensemble de _features_ que nous donnons au modèle. Dans certains contextes, ces caractéristiques seront l'ensemble des colonnes d'un `Dataset`, mais dans d'autres (comme ici et pour la réponse aux questions), plusieurs caractéristiques peuvent être extraites d'un seul exemple et appartenir à une seule colonne. - - - -Voyons comment cela fonctionne ! Ici, nous allons tokeniser nos exemples et les tronquer à une longueur maximale de 128 mais nous demanderons au *tokenizer* de renvoyer *tous* les morceaux des textes au lieu du premier. Cela peut être fait avec `return_overflowing_tokens=True` : - -```py -def tokenize_and_split(examples): - return tokenizer( - examples["review"], - truncation=True, - max_length=128, - return_overflowing_tokens=True, - ) -``` - -Testons cela sur un exemple avant d'utiliser `Dataset.map()` sur le jeu de données : - -```py -result = tokenize_and_split(drug_dataset["train"][0]) -[len(inp) for inp in result["input_ids"]] -``` - -```python out -[128, 49] -``` - -Notre premier exemple du jeu d’entraînement est devenu deux caractéristiques car il a été segmenté à plus que le nombre maximum de *tokens* que nous avons spécifié : le premier de longueur 128 et le second de longueur 49. Faisons maintenant cela pour tous les éléments du jeu de données ! - -```py -tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) -``` - -```python out -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. - -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` : - -```py -tokenized_dataset = drug_dataset.map( - tokenize_and_split, batched=True, remove_columns=drug_dataset["train"].column_names -) -``` - -Maintenant, cela fonctionne sans erreur. Nous pouvons vérifier que notre nouveau jeu de données contient beaucoup plus d'éléments que le jeu de données d'origine en comparant les longueurs : - -```py -len(tokenized_dataset["train"]), len(drug_dataset["train"]) -``` - -```python out -(206772, 138514) -``` - -Nous avons mentionné que nous pouvions également résoudre le problème de longueur non concordante en donnant aux anciennes colonnes la même taille que les nouvelles. Pour ce faire, nous avons besoin du champ `overflow_to_sample_mapping` que le *tokenizer* renvoie lorsque nous définissons `return_overflowing_tokens=True`. Il nous donne une correspondance entre un nouvel index de caractéristique et l'index de l'échantillon dont il est issu. Grâce à cela, nous pouvons associer chaque clé présente dans notre jeu de données d'origine à une liste de valeurs de la bonne taille en répétant les valeurs de chaque exemple autant de fois qu'il génère de nouvelles caractéristiques : - -```py -def tokenize_and_split(examples): - result = tokenizer( - examples["review"], - truncation=True, - max_length=128, - return_overflowing_tokens=True, - ) - # Extract mapping between new and old indices - sample_map = result.pop("overflow_to_sample_mapping") - for key, values in examples.items(): - result[key] = [values[i] for i in sample_map] - return result -``` - -Nous pouvons voir que cela fonctionne avec `Dataset.map()` sans que nous ayons besoin de supprimer les anciennes colonnes : - -```py -tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) -tokenized_dataset -``` - -```python out -DatasetDict({ - train: Dataset({ - features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'], - num_rows: 206772 - }) - test: Dataset({ - features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'], - num_rows: 68876 - }) -}) -``` - -Nous obtenons le même nombre de caractéristiques d'entraînement qu'auparavant, mais ici nous avons conservé tous les anciens champs. Si vous en avez besoin pour un post-traitement après l'application de votre modèle, vous pouvez utiliser cette approche. - -Vous avez maintenant vu comment 🤗 *Datasets* peut être utilisé pour prétraiter un jeu de données de différentes manières. Bien que les fonctions de traitement de 🤗 *Datasets* couvrent la plupart de vos besoins, il peut arriver que vous deviez passer à Pandas pour accéder à des fonctionnalités plus puissantes, telles que `DataFrame.groupby()` ou des API de haut niveau pour la visualisation. Heureusement, 🤗 *Datasets* est conçu pour être interopérable avec des bibliothèques telles que Pandas, NumPy, PyTorch, TensorFlow et JAX. Voyons comment cela fonctionne. - -## De `Dataset` à `DataFrame` et vice versa - - - -Pour permettre la conversion entre diverses bibliothèques tierces, 🤗 *Datasets* fournit une fonction `Dataset.set_format()`. Cette fonction ne modifie que le _format de sortie_ du jeu de données. Vous pouvez donc facilement passer à un autre format sans affecter le _format de données_ sous-jacent, qui est Apache Arrow. Le formatage se fait sur place. Pour démontrer, convertissons notre jeu de données vers Pandas : - -```py -drug_dataset.set_format("pandas") -``` - -Maintenant, lorsque nous accédons aux éléments du jeu de données, nous obtenons un `pandas.DataFrame` au lieu d'un dictionnaire : - -```py -drug_dataset["train"][:3] -``` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
patient_iddrugNameconditionreviewratingdateusefulCountreview_length
095260Guanfacineadhd"My son is halfway through his fourth week of Intuniv..."8.0April 27, 2010192141
192703Lybrelbirth control"I used to take another oral contraceptive, which had 21 pill cycle, and was very happy- very light periods, max 5 days, no other side effects..."5.0December 14, 200917134
2138000Ortho Evrabirth control"This is my first time using any form of birth control..."8.0November 3, 20151089
- -Créons un `pandas.DataFrame` pour l'ensemble d'entraînement en sélectionnant tous les éléments de `drug_dataset["train"]` : - -```py -train_df = drug_dataset["train"][:] -``` - - - -🚨 Sous le capot, `Dataset.set_format()` change le format de retour pour la méthode `__getitem__()`. Cela signifie que lorsque nous voulons créer un nouvel objet comme `train_df` à partir d'un `Dataset` au format `"pandas"`, nous devons découper tout le jeu de données pour obtenir un `pandas.DataFrame`. Vous pouvez vérifier par vous-même que le type de `drug_dataset["train"]` est `Dataset`, quel que soit le format de sortie. - - - - -De là, nous pouvons utiliser toutes les fonctionnalités Pandas que nous voulons. Par exemple, nous pouvons faire un chaînage sophistiqué pour calculer la distribution de classe parmi les entrées `condition` : - -```py -frequencies = ( - train_df["condition"] - .value_counts() - .to_frame() - .reset_index() - .rename(columns={"index": "condition", "condition": "frequency"}) -) -frequencies.head() -``` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
conditionfrequency
0birth control27655
1depression8023
2acne5209
3anxiety4991
4pain4744
- - -Et une fois que nous avons terminé notre analyse Pandas, nous pouvons toujours créer un nouvel objet `Dataset` en utilisant la fonction `Dataset.from_pandas()` comme suit : - - -```py -from datasets import Dataset - -freq_dataset = Dataset.from_pandas(frequencies) -freq_dataset -``` - -```python out -Dataset({ - features: ['condition', 'frequency'], - num_rows: 819 -}) -``` - - - -✏️ **Essayez !** Calculez la note moyenne par médicament et stockez le résultat dans un nouveau jeu de données. - - - -Ceci conclut notre visite des différentes techniques de prétraitement disponibles dans 🤗 *Datasets*. Pour compléter la section, créons un ensemble de validation pour préparer le jeu de données à l’entraînement d'un classifieur. Avant cela, nous allons réinitialiser le format de sortie de `drug_dataset` de `"pandas"` à `"arrow"` : - -```python -drug_dataset.reset_format() -``` - -## Création d'un ensemble de validation - -Bien que nous ayons un jeu de test que nous pourrions utiliser pour l'évaluation, il est recommandé de ne pas toucher au jeu de test et de créer un jeu de validation séparé pendant le développement. Une fois que vous êtes satisfait des performances de vos modèles sur l'ensemble de validation, vous pouvez effectuer une dernière vérification d'intégrité sur l'ensemble test. Ce processus permet d'atténuer le risque de surentraînement sur le jeu de test et de déployer un modèle qui échoue sur des données du monde réel. - -🤗 *Datasets* fournit une fonction `Dataset.train_test_split()` basée sur la célèbre fonctionnalité de `scikit-learn`. Utilisons-la pour diviser notre ensemble d'entraînement `train` et `validation` (nous définissons l'argument `seed` pour la reproductibilité) : - -```py -drug_dataset_clean = drug_dataset["train"].train_test_split(train_size=0.8, seed=42) -# Rename the default "test" split to "validation" -drug_dataset_clean["validation"] = drug_dataset_clean.pop("test") -# Add the "test" set to our `DatasetDict` -drug_dataset_clean["test"] = drug_dataset["test"] -drug_dataset_clean -``` - -```python out -DatasetDict({ - train: Dataset({ - features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], - num_rows: 110811 - }) - validation: Dataset({ - features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], - num_rows: 27703 - }) - test: Dataset({ - features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], - num_rows: 46108 - }) -}) -``` - -Génial, nous avons maintenant préparé un jeu de données prêt pour l'entraînement de certains modèles ! Dans la [section 5](/course/fr/chapter5/5), nous vous montrerons comment télécharger des jeux de données sur le *Hub*. Mais pour l'instant, terminons notre analyse en examinant quelques façons d'enregistrer des jeux de données sur votre ordinateur local. - -## Enregistrer un jeu de données - - - -Bien que 🤗 *Datasets* mette en cache chaque jeu de données téléchargé et les opérations qui y sont effectuées, il y a des moments où vous voudrez enregistrer un jeu de données sur le disque (par exemple, au cas où le cache serait supprimé). Comme indiqué dans le tableau ci-dessous, 🤗 *Datasets* fournit trois fonctions principales pour enregistrer votre jeu de données dans différents formats : - -| Format de données | Fonction | -| :---------------: | :----------------------: | -| Arrow | `Dataset.save_to_disk()` | -| CSV | `Dataset.to_csv()` | -| JSON | `Dataset.to_json()` | - -Par exemple, enregistrons notre jeu de données nettoyé au format Arrow : - -```py -drug_dataset_clean.save_to_disk("drug-reviews") -``` - -Cela créera un répertoire avec la structure suivante : - -``` -drug-reviews/ -├── dataset_dict.json -├── test -│ ├── dataset.arrow -│ ├── dataset_info.json -│ └── state.json -├── train -│ ├── dataset.arrow -│ ├── dataset_info.json -│ ├── indices.arrow -│ └── state.json -└── validation - ├── dataset.arrow - ├── dataset_info.json - ├── indices.arrow - └── state.json -``` - -où nous pouvons voir que chaque division est associée à sa propre table *dataset.arrow* et à certaines métadonnées dans *dataset_info.json* et *state.json*. Vous pouvez considérer le format Arrow comme un tableau sophistiqué de colonnes et de lignes optimisé pour la création d'applications hautes performances qui traitent et transportent de grands ensembles de données. - -Une fois le jeu de données enregistré, nous pouvons le charger en utilisant la fonction `load_from_disk()` comme suit : - -```py -from datasets import load_from_disk - -drug_dataset_reloaded = load_from_disk("drug-reviews") -drug_dataset_reloaded -``` - -```python out -DatasetDict({ - train: Dataset({ - features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], - num_rows: 110811 - }) - validation: Dataset({ - features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], - num_rows: 27703 - }) - test: Dataset({ - features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], - num_rows: 46108 - }) -}) -``` - -Pour les formats CSV et JSON, nous devons stocker chaque fractionnement dans un fichier séparé. Pour ce faire, vous pouvez parcourir les clés et les valeurs de l'objet `DatasetDict` : - -```py -for split, dataset in drug_dataset_clean.items(): - dataset.to_json(f"drug-reviews-{split}.jsonl") -``` - -Cela enregistre chaque fractionnement au [format JSON Lines](https://jsonlines.org), où chaque ligne du jeu de données est stockée sous la forme d'une seule ligne de JSON. Voici à quoi ressemble le premier exemple : - -```py -!head -n 1 drug-reviews-train.jsonl -``` - -```python out -{"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} -``` - -Nous pouvons ensuite utiliser les techniques de [section 2](/course/fr/chapter5/2) pour charger les fichiers JSON comme suit : - -```py -data_files = { - "train": "drug-reviews-train.jsonl", - "validation": "drug-reviews-validation.jsonl", - "test": "drug-reviews-test.jsonl", -} -drug_dataset_reloaded = load_dataset("json", data_files=data_files) -``` - -Et c'est tout pour notre excursion dans la manipulation des données avec 🤗 *Datasets* ! Maintenant que nous disposons d'un ensemble de données nettoyé pour entraîner un modèle, voici quelques idées que vous pouvez essayer : - -1. Utilisez les techniques du [Chapitre 3](/course/fr/chapter3) pour entraîner un classifieur capable de prédire l'état du patient en fonction de l'examen du médicament. -2. Utilisez le pipeline `summarization` du [Chapitre 1](/course/fr/chapter1) pour générer des résumés des révisions. - -Ensuite, nous verrons comment 🤗 *Datasets* peut vous permettre de travailler avec d'énormes jeux de données sans faire exploser votre ordinateur portable ! +# Il est temps de trancher et de découper + + + +La plupart du temps, les données avec lesquelles vous travaillez ne sont pas parfaitement préparées pour l’entraînements de modèles. Dans cette section, nous allons explorer les différentes fonctionnalités fournies par 🤗 *Datasets* pour nettoyer vos jeux de données. + + + +## Trancher et découper nos données + +Semblable à Pandas, 🤗 *Datasets* fournit plusieurs fonctions pour manipuler le contenu des objets `Dataset` et `DatasetDict`. Nous avons déjà rencontré la méthode `Dataset.map()` dans le [chapitre 3](/course/fr/chapter3) et dans cette section nous allons explorer certaines des autres fonctions à notre disposition. + +Pour cet exemple, nous utiliserons le [*Drug Review Dataset*](https://archive.ics.uci.edu/ml/datasets/Drug+Review+Dataset+%28Drugs.com%29) qui est hébergé sur [*UC Irvine Machine Learning Repository*](https://archive.ics.uci.edu/ml/index.php) et contenant des avis de patients sur divers médicaments ainsi que la condition traitée et une note de 10 étoiles sur la satisfaction du patient. + +Nous devons d'abord télécharger et extraire les données, ce qui peut être fait avec les commandes `wget` et `unzip` : + +```py +!wget "https://archive.ics.uci.edu/ml/machine-learning-databases/00462/drugsCom_raw.zip" +!unzip drugsCom_raw.zip +``` + +Étant donné que TSV n'est qu'une variante de CSV qui utilise des tabulations au lieu de virgules comme séparateurs, nous pouvons charger ces fichiers en utilisant le script de chargement `csv` et en spécifiant l'argument `delimiter` dans la fonction `load_dataset()` comme suit : + +```py +from datasets import load_dataset + +data_files = {"train": "drugsComTrain_raw.tsv", "test": "drugsComTest_raw.tsv"} +# \t is the tab character in Python +drug_dataset = load_dataset("csv", data_files=data_files, delimiter="\t") +``` + +Une bonne pratique lors de toute sorte d'analyse de données consiste à prélever un petit échantillon aléatoire pour avoir une idée rapide du type de données avec lesquelles vous travaillez. Dans 🤗 *Datasets*, nous pouvons créer un échantillon aléatoire en enchaînant les fonctions `Dataset.shuffle()` et `Dataset.select()` : + +```py +drug_sample = drug_dataset["train"].shuffle(seed=42).select(range(1000)) +# Un coup d'œil sur les premiers exemples +drug_sample[:3] +``` + +```python out +{'Unnamed: 0': [87571, 178045, 80482], + 'drugName': ['Naproxen', 'Duloxetine', 'Mobic'], + 'condition': ['Gout, Acute', 'ibromyalgia', 'Inflammatory Conditions'], + #['Goutte aiguë', 'ibromyalgie', 'Affections inflammatoires'] + 'review': ['"like the previous person mention, I'm a strong believer of aleve, it works faster for my gout than the prescription meds I take. No more going to the doctor for refills.....Aleve works!"', + # comme la personne précédente l'a mentionné, je suis un fervent partisan de l'aleve, il fonctionne plus rapidement pour ma goutte que les médicaments sur ordonnance que je prends. Je n'ai plus besoin d'aller chez le médecin pour des renouvellements.....Aleve fonctionne !" + '"I have taken Cymbalta for about a year and a half for fibromyalgia pain. It is great\r\nas a pain reducer and an anti-depressant, however, the side effects outweighed \r\nany benefit I got from it. I had trouble with restlessness, being tired constantly,\r\ndizziness, dry mouth, numbness and tingling in my feet, and horrible sweating. I am\r\nbeing weaned off of it now. Went from 60 mg to 30mg and now to 15 mg. I will be\r\noff completely in about a week. The fibro pain is coming back, but I would rather deal with it than the side effects."', + # J'ai pris du Cymbalta pendant environ un an et demi pour des douleurs de la fibromyalgie. C'est un excellent analgésique et un antidépresseur, mais les effets secondaires l'ont emporté sur tous les avantages que j'en ai tirés. J'ai eu des problèmes d'agitation, de fatigue constante, de vertiges, de bouche sèche, d'engourdissement, de picotements dans les pieds, et de transpiration horrible. Je suis en train de m'en sevrer maintenant. Je suis passée de 60 mg à 30 mg et maintenant à 15 mg. Je l'arrêterai complètement dans environ une semaine. La douleur de la fibrose revient, mais je préfère la supporter plutôt que les effets secondaires. + '"I have been taking Mobic for over a year with no side effects other than an elevated blood pressure. I had severe knee and ankle pain which completely went away after taking Mobic. I attempted to stop the medication however pain returned after a few days."'], + # J'ai pris Mobic pendant plus d'un an sans effets secondaires autres qu'une pression sanguine élevée. J'avais de fortes douleurs au genou et à la cheville qui ont complètement disparu après avoir pris Mobic. J'ai essayé d'arrêter le médicament mais la douleur est revenue après quelques jours." + 'rating': [9.0, 3.0, 10.0], + 'date': ['September 2, 2015', 'November 7, 2011', 'June 5, 2013'], + #['2 septembre 2015', '7 novembre 2011', '5 juin 2013'] + 'usefulCount': [36, 13, 128]} +``` + +Notez que nous avons corrigé la graine dans `Dataset.shuffle()` à des fins de reproductibilité. `Dataset.select()` attend un itérable d'indices, nous avons donc passé `range(1000)` pour récupérer les 1 000 premiers exemples du jeu de données mélangé. À partir de cet échantillon, nous pouvons déjà voir quelques bizarreries dans notre jeu de données : + +* la colonne `Unnamed: 0` ressemble étrangement à un identifiant anonyme pour chaque patient, +* la colonne `condition` comprend un mélange d'étiquettes en majuscules et en minuscules, +* les avis sont de longueur variable et contiennent un mélange de séparateurs de lignes Python (`\r\n`) ainsi que des codes de caractères HTML comme `&\#039;`. + +Voyons comment nous pouvons utiliser 🤗 *Datasets* pour traiter chacun de ces problèmes. Pour tester l'hypothèse de l'ID patient pour la colonne `Unnamed : 0`, nous pouvons utiliser la fonction `Dataset.unique()` pour vérifier que le nombre d'ID correspond au nombre de lignes dans chaque division : + +```py +for split in drug_dataset.keys(): + assert len(drug_dataset[split]) == len(drug_dataset[split].unique("Unnamed: 0")) +``` + +Cela semble confirmer notre hypothèse, alors nettoyons un peu en renommant la colonne `Unnamed: 0` en quelque chose d'un peu plus interprétable. Nous pouvons utiliser la fonction `DatasetDict.rename_column()` pour renommer la colonne sur les deux divisions en une seule fois : + +```py +drug_dataset = drug_dataset.rename_column( + original_column_name="Unnamed: 0", new_column_name="patient_id" +) +drug_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'], + num_rows: 161297 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'], + num_rows: 53766 + }) +}) +``` + + + +✏️ **Essayez !** Utilisez la fonction ` Dataset.unique()` pour trouver le nombre de médicaments et de conditions uniques dans les échantillons d'entraînement et de test. + + + +Ensuite, normalisons toutes les étiquettes `condition` en utilisant `Dataset.map()`. Comme nous l'avons fait avec la tokenisation dans le [chapitre 3](/course/fr/chapter3), nous pouvons définir une fonction simple qui peut être appliquée sur toutes les lignes de chaque division dans `drug_dataset` : + +```py +def lowercase_condition(example): + return {"condition": example["condition"].lower()} + + +drug_dataset.map(lowercase_condition) +``` + +```python out +AttributeError: 'NoneType' object has no attribute 'lower' +``` + +Oh non, nous rencontrons un problème avec notre fonction ! À partir de l'erreur, nous pouvons déduire que certaines des entrées de la colonne `condition` sont `None` ne pouvant donc pas être mises en minuscules car ce ne sont pas des chaînes. Supprimons ces lignes en utilisant `Dataset.filter()`, qui fonctionne de manière similaire à `Dataset.map()` et attend une fonction qui reçoit un seul exemple issu du jeu de données. Au lieu d'écrire une fonction explicite comme : + +```py +def filter_nones(x): + return x["condition"] is not None +``` + +puis exécuter `drug_dataset.filter(filter_nones)`, nous pouvons le faire en une seule ligne en utilisant une _fonction lambda_. En Python, les fonctions lambda sont de petites fonctions que vous pouvez définir sans les nommer explicitement. Ils prennent la forme générale : + +``` +lambda : +``` + +où `lambda` est l'un des [mots clés](https://docs.python.org/3/reference/lexical_analysis.html#keywords) spéciaux de Python, `` est une liste/ensemble de valeurs séparées par des virgules qui définissent les entrées de la fonction et `` représente les opérations que vous souhaitez exécuter. Par exemple, nous pouvons définir une simple fonction lambda qui met au carré un nombre comme suit : + +``` +lambda x : x * x +``` + +Pour appliquer cette fonction à une entrée, nous devons l'envelopper ainsi que l'entrée entre parenthèses : + +```py +(lambda x: x * x)(3) +``` + +```python out +9 +``` + +De même, nous pouvons définir des fonctions lambda avec plusieurs arguments en les séparant par des virgules. Par exemple, nous pouvons calculer l'aire d'un triangle comme suit : + +```py +(lambda base, height: 0.5 * base * height)(4, 8) +``` + +```python out +16.0 +``` + +Les fonctions lambda sont pratiques lorsque vous souhaitez définir de petites fonctions à usage unique (pour plus d'informations à leur sujet, nous vous recommandons de lire l'excellent [tutoriel Real Python](https://realpython.com/python-lambda/) d'André Burgaud) . Dans le contexte de la bibliothèque 🤗 *Datasets*, nous pouvons utiliser des fonctions lambda pour définir des opérations simples de « mappage » et de filtrage. Utilisons cette astuce pour éliminer les entrées `None` dans notre jeu de données : + +```py +drug_dataset = drug_dataset.filter(lambda x: x["condition"] is not None) +``` + +Avec les entrées `None` supprimées, nous pouvons normaliser notre colonne `condition` : + +```py +drug_dataset = drug_dataset.map(lowercase_condition) +# Vérification que la mise en minuscule a fonctionné +drug_dataset["train"]["condition"][:3] +``` + +```python out +['left ventricular dysfunction', 'adhd', 'birth control'] +``` + +Ça marche ! Maintenant que nous avons nettoyé les étiquettes, examinons le nettoyage des avis eux-mêmes. + +## Création de nouvelles colonnes + +Chaque fois que vous avez affaire à des avis de clients, une bonne pratique consiste à vérifier le nombre de mots dans chaque avis. Une critique peut être un simple mot comme « Génial ! » ou un essai complet avec des milliers de mots. Selon le cas d'usage, vous devrez gérer ces extrêmes différemment. Pour calculer le nombre de mots dans chaque révision, nous utiliserons une heuristique approximative basée sur la division de chaque texte par des espaces. + +Définissons une fonction simple qui compte le nombre de mots dans chaque avis : + +```py +def compute_review_length(example): + return {"review_length": len(example["review"].split())} +``` + +Contrairement à notre fonction `lowercase_condition()`, `compute_review_length()` renvoie un dictionnaire dont la clé ne correspond pas à l'un des noms de colonne du jeu de données. Dans ce cas, lorsque `compute_review_length()` est passé à `Dataset.map()`, il est appliqué à toutes les lignes du jeu de données pour créer une nouvelle colonne `review_length` : + +```py +drug_dataset = drug_dataset.map(compute_review_length) +# Inspecter le premier exemple d'entraînement +drug_dataset["train"][0] +``` + +```python out +{'patient_id': 206461, + 'drugName': 'Valsartan', + 'condition': 'left ventricular dysfunction', # dysfonctionnement du ventricule gauche + 'review': '"It has no side effect, I take it in combination of Bystolic 5 Mg and Fish Oil"', + # Il n'a aucun effet secondaire, je le prends en combinaison avec Bystolic 5 mg et de l'huile de poisson. + 'rating': 9.0, + 'date': 'May 20, 2012', # 20 mai 2012 + 'usefulCount': 27, + 'review_length': 17} +``` + +Comme prévu, nous pouvons voir qu'une colonne `review_length` a été ajoutée à notre jeu d'entraînement. Nous pouvons trier cette nouvelle colonne avec `Dataset.sort()` pour voir à quoi ressemblent les valeurs extrêmes : + +```py +drug_dataset["train"].sort("review_length")[:3] +``` + +```python out +{'patient_id': [103488, 23627, 20558], + 'drugName': ['Loestrin 21 1 / 20', 'Chlorzoxazone', 'Nucynta'], + 'condition': ['birth control', 'muscle spasm', 'pain'], + # contraception, spasme musculaire, douleur. + 'review': ['"Excellent."', '"useless"', '"ok"'], # Excellent, inutile, ok + 'rating': [10.0, 1.0, 6.0], + 'date': ['November 4, 2008', 'March 24, 2017', 'August 20, 2016'], + # 4 novembre 2008, 24 mars 2017, 20 août 2016 + 'usefulCount': [5, 2, 10], + 'review_length': [1, 1, 1]} +``` + +Comme nous le soupçonnions, certaines critiques ne contiennent qu'un seul mot, ce qui, bien que cela puisse convenir à l'analyse des sentiments, n’est pas informatif si nous voulons prédire la condition. + + + +🙋 Une autre façon d'ajouter de nouvelles colonnes à un jeu de données consiste à utiliser la fonction `Dataset.add_column()`. Cela vous permet de donner la colonne sous forme de liste Python ou de tableau NumPy et peut être utile dans les situations où `Dataset.map()` n'est pas bien adapté à votre analyse. + + + +Utilisons la fonction `Dataset.filter()` pour supprimer les avis contenant moins de 30 mots. De la même manière que nous l'avons fait avec la colonne `condition`, nous pouvons filtrer les avis très courts en exigeant que les avis aient une longueur supérieure à ce seuil : + +```py +drug_dataset = drug_dataset.filter(lambda x: x["review_length"] > 30) +print(drug_dataset.num_rows) +``` + +```python out +{'train': 138514, 'test': 46108} +``` + +Comme vous pouvez le constater, cela a supprimé environ 15 % des avis de nos jeux d'entraînement et de test d'origine. + + + +✏️ **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. + + + +La dernière chose à laquelle nous devons faire face est la présence de caractères HTML dans nos avis. Nous pouvons utiliser le module `html` de Python pour supprimer ces caractères, comme ceci : + +```py +import html + +text = "I'm a transformer called BERT" +html.unescape(text) +``` + +```python out +"I'm a transformer called BERT" +``` + +Nous utilisons `Dataset.map()` pour démasquer tous les caractères HTML de notre corpus : + +```python +drug_dataset = drug_dataset.map(lambda x: {"review": html.unescape(x["review"])}) +``` + +Comme vous pouvez le voir, la méthode `Dataset.map()` est très utile pour le traitement des données. Et nous n'avons même pas effleuré la surface de tout ce qu'elle peut faire ! + +## Les superpouvoirs de la méthode `map()` + +La méthode `Dataset.map()` prend un argument `batched` qui, s'il est défini sur `True`, l'amène à envoyer un batch d'exemples à la fonction *map* en une seule fois (la taille du batch est configurable mais est fixé par défaut à 1 000). Par exemple, la fonction `map()` précédente qui supprime tout le code HTML prend un peu de temps à s'exécuter (vous pouvez lire le temps pris dans les barres de progression). On peut accélérer cela en traitant plusieurs éléments en même temps à l'aide d'une compréhension de liste. + +Lorsque vous spécifiez `batched=True`, la fonction reçoit un dictionnaire avec les champs du jeu de données mais chaque valeur est maintenant une _liste de valeurs_ et non plus une seule valeur. La valeur retournée par `Dataset.map()` devrait être la même : un dictionnaire avec les champs que nous voulons mettre à jour ou ajouter à notre jeu de données, et une liste de valeurs. Par exemple, voici une autre façon de supprimer tous les caractères HTML, mais en utilisant `batched=True` : + +```python +new_drug_dataset = drug_dataset.map( + lambda x: {"review": [html.unescape(o) for o in x["review"]]}, batched=True +) +``` + +Si vous exécutez ce code dans un *notebook*, vous verrez que cette commande s'exécute beaucoup plus rapidement que la précédente. Et ce n'est pas parce que nos critiques ont déjà été scannées au format HTML. Si vous ré-exécutez l'instruction de la section précédente (sans `batched=True`), cela prendra le même temps qu'avant. En effet, les compréhensions de liste sont généralement plus rapides que l'exécution du même code dans une boucle `for` et nous gagnons également en performances en accédant à de nombreux éléments en même temps au lieu d'un par un. + +L'utilisation de `Dataset.map()` avec `batched=True` est essentielle pour les *tokenizers rapides* que nous rencontrerons dans le [chapitre 6](/course/fr/chapter6) et qui peuvent rapidement tokeniser de grandes listes de textes. Par exemple, pour tokeniser toutes les critiques de médicaments avec un *tokenizer* rapide nous pouvons utiliser une fonction comme celle-ci : + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") + + +def tokenize_function(examples): + return tokenizer(examples["review"], truncation=True) +``` + +Comme vous l'avez vu dans le [chapitre 3](/course/fr/chapter3), nous pouvons passer un ou plusieurs exemples au *tokenizer*. Nous pouvons donc utiliser cette fonction avec ou sans `batched=True`. Profitons-en pour comparer les performances des différentes options. Dans un *notebook*, vous pouvez chronométrer une instruction d'une ligne en ajoutant `%time` avant la ligne de code que vous souhaitez mesurer : + +```python no-format +%time tokenized_dataset = drug_dataset.map(tokenize_function, batched=True) +``` + +Vous pouvez également chronométrer une cellule entière en mettant `%%time` au début de la cellule. Sur le matériel sur lequel nous avons exécuté cela, cela affichait 10,8 s pour cette instruction (c'est le nombre écrit après "Wall time"). + + + +✏️ **Essayez !** Exécutez la même instruction avec et sans `batched=True`, puis essayez-le avec un *tokenizer* lent (ajoutez `use_fast=False` dans la méthode `AutoTokenizer.from_pretrained()`) afin que vous puissiez voir quels temps vous obtenez sur votre matériel. + + + +Voici les résultats que nous avons obtenus avec et sans *batching*, avec un *tokenizer* rapide et un lent : + +Options | *Tokenizer* rapide | *Tokenizer* lent +:--------------:|:----------------:|:-----------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s + +Cela signifie que l'utilisation d'un *tokenizer* rapide avec l'option `batched=True` est 30 fois plus rapide que son homologue lent sans batch. C'est vraiment incroyable ! C'est la raison principale pour laquelle les *tokenizers* rapides sont la valeur par défaut lors de l'utilisation de `AutoTokenizer` (et pourquoi ils sont appelés « rapides »). Ils sont capables d'atteindre une telle vitesse car en coulisses le code de tokenisation est exécuté en Rust qui est un langage facilitant la parallélisation de l'exécution du code. + +La parallélisation est également la raison du gain de vitesse de près de 6 fois obtenue par le *tokenizer* rapide avec batch. Vous ne pouvez pas paralléliser une seule opération de tokenisation, mais lorsque vous souhaitez tokeniser de nombreux textes en même temps, vous pouvez simplement répartir l'exécution sur plusieurs processus. Chacun responsable de ses propres textes. + +`Dataset.map()` possède aussi ses propres capacités de parallélisation. Comme elles ne sont pas soutenus par Rust, un *tokenizer* lent ne peut pas rattraper un rapide mais cela peut toujours être utile (surtout si vous utilisez un *tokenizer* qui n'a pas de version rapide). Pour activer le multitraitement, utilisez l'argument `num_proc` et spécifiez le nombre de processus à utiliser dans votre appel à `Dataset.map()` : + +```py +slow_tokenizer = AutoTokenizer.from_pretrained("bert-base-cased", use_fast=False) + + +def slow_tokenize_function(examples): + return slow_tokenizer(examples["review"], truncation=True) + + +tokenized_dataset = drug_dataset.map(slow_tokenize_function, batched=True, num_proc=8) +``` + +Vous pouvez faire des tests pour déterminer le nombre optimal de processus à utiliser. Dans notre cas 8 semble produire le meilleur gain de vitesse. Voici les chiffres que nous avons obtenus avec et sans multitraitement : + +Options | *Tokenizer* rapide | *Tokenizer* lent +:----------------------------:|:----------------:|:---------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s +`batched=True`, `num_proc=8` | 6.52s | 41.3s +`batched=False`, `num_proc=8` | 9.49s | 45.2s + +Ce sont des résultats beaucoup plus raisonnables pour le *tokenizer* lent mais les performances du *tokenizer* rapide ont également été considérablement améliorées. Notez, cependant, que ce ne sera pas toujours le cas : pour des valeurs de `num_proc` autres que 8, nos tests ont montré qu'il était plus rapide d'utiliser `batched=True` sans cette option. En général, nous ne recommandons pas d'utiliser le multitraitement pour les *tokenizers* rapides avec `batched=True`. + + + +Utiliser `num_proc` pour accélérer votre traitement est généralement une bonne idée tant que la fonction que vous utilisez n'effectue pas déjà une sorte de multitraitement. + + + +Toutes ces fonctionnalités condensées en une seule méthode sont déjà assez étonnantes, mais il y a plus ! Avec `Dataset.map()` et `batched=True` vous pouvez modifier le nombre d'éléments dans votre jeu de données. Ceci est très utile dans de nombreuses situations où vous souhaitez créer plusieurs fonctionnalités d'entraînement à partir d'un exemple. Nous devrons le faire dans le cadre du prétraitement de plusieurs des tâches de traitement du langage naturel que nous entreprendrons dans le [chapitre 7](/course/fr/chapter7). + + + +💡 En apprentissage automatique, un _exemple_ est généralement défini comme l'ensemble de _features_ que nous donnons au modèle. Dans certains contextes, ces caractéristiques seront l'ensemble des colonnes d'un `Dataset`, mais dans d'autres (comme ici et pour la réponse aux questions), plusieurs caractéristiques peuvent être extraites d'un seul exemple et appartenir à une seule colonne. + + + +Voyons comment cela fonctionne ! Ici, nous allons tokeniser nos exemples et les tronquer à une longueur maximale de 128 mais nous demanderons au *tokenizer* de renvoyer *tous* les morceaux des textes au lieu du premier. Cela peut être fait avec `return_overflowing_tokens=True` : + +```py +def tokenize_and_split(examples): + return tokenizer( + examples["review"], + truncation=True, + max_length=128, + return_overflowing_tokens=True, + ) +``` + +Testons cela sur un exemple avant d'utiliser `Dataset.map()` sur le jeu de données : + +```py +result = tokenize_and_split(drug_dataset["train"][0]) +[len(inp) for inp in result["input_ids"]] +``` + +```python out +[128, 49] +``` + +Notre premier exemple du jeu d’entraînement est devenu deux caractéristiques car il a été segmenté à plus que le nombre maximum de *tokens* que nous avons spécifié : le premier de longueur 128 et le second de longueur 49. Faisons maintenant cela pour tous les éléments du jeu de données ! + +```py +tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) +``` + +```python out +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. + +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` : + +```py +tokenized_dataset = drug_dataset.map( + tokenize_and_split, batched=True, remove_columns=drug_dataset["train"].column_names +) +``` + +Maintenant, cela fonctionne sans erreur. Nous pouvons vérifier que notre nouveau jeu de données contient beaucoup plus d'éléments que le jeu de données d'origine en comparant les longueurs : + +```py +len(tokenized_dataset["train"]), len(drug_dataset["train"]) +``` + +```python out +(206772, 138514) +``` + +Nous avons mentionné que nous pouvions également résoudre le problème de longueur non concordante en donnant aux anciennes colonnes la même taille que les nouvelles. Pour ce faire, nous avons besoin du champ `overflow_to_sample_mapping` que le *tokenizer* renvoie lorsque nous définissons `return_overflowing_tokens=True`. Il nous donne une correspondance entre un nouvel index de caractéristique et l'index de l'échantillon dont il est issu. Grâce à cela, nous pouvons associer chaque clé présente dans notre jeu de données d'origine à une liste de valeurs de la bonne taille en répétant les valeurs de chaque exemple autant de fois qu'il génère de nouvelles caractéristiques : + +```py +def tokenize_and_split(examples): + result = tokenizer( + examples["review"], + truncation=True, + max_length=128, + return_overflowing_tokens=True, + ) + # Extract mapping between new and old indices + sample_map = result.pop("overflow_to_sample_mapping") + for key, values in examples.items(): + result[key] = [values[i] for i in sample_map] + return result +``` + +Nous pouvons voir que cela fonctionne avec `Dataset.map()` sans que nous ayons besoin de supprimer les anciennes colonnes : + +```py +tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) +tokenized_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'], + num_rows: 206772 + }) + test: Dataset({ + features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'], + num_rows: 68876 + }) +}) +``` + +Nous obtenons le même nombre de caractéristiques d'entraînement qu'auparavant, mais ici nous avons conservé tous les anciens champs. Si vous en avez besoin pour un post-traitement après l'application de votre modèle, vous pouvez utiliser cette approche. + +Vous avez maintenant vu comment 🤗 *Datasets* peut être utilisé pour prétraiter un jeu de données de différentes manières. Bien que les fonctions de traitement de 🤗 *Datasets* couvrent la plupart de vos besoins, il peut arriver que vous deviez passer à Pandas pour accéder à des fonctionnalités plus puissantes, telles que `DataFrame.groupby()` ou des API de haut niveau pour la visualisation. Heureusement, 🤗 *Datasets* est conçu pour être interopérable avec des bibliothèques telles que Pandas, NumPy, PyTorch, TensorFlow et JAX. Voyons comment cela fonctionne. + +## De `Dataset` à `DataFrame` et vice versa + + + +Pour permettre la conversion entre diverses bibliothèques tierces, 🤗 *Datasets* fournit une fonction `Dataset.set_format()`. Cette fonction ne modifie que le _format de sortie_ du jeu de données. Vous pouvez donc facilement passer à un autre format sans affecter le _format de données_ sous-jacent, qui est Apache Arrow. Le formatage se fait sur place. Pour démontrer, convertissons notre jeu de données vers Pandas : + +```py +drug_dataset.set_format("pandas") +``` + +Maintenant, lorsque nous accédons aux éléments du jeu de données, nous obtenons un `pandas.DataFrame` au lieu d'un dictionnaire : + +```py +drug_dataset["train"][:3] +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
patient_iddrugNameconditionreviewratingdateusefulCountreview_length
095260Guanfacineadhd"My son is halfway through his fourth week of Intuniv..."8.0April 27, 2010192141
192703Lybrelbirth control"I used to take another oral contraceptive, which had 21 pill cycle, and was very happy- very light periods, max 5 days, no other side effects..."5.0December 14, 200917134
2138000Ortho Evrabirth control"This is my first time using any form of birth control..."8.0November 3, 20151089
+ +Créons un `pandas.DataFrame` pour l'ensemble d'entraînement en sélectionnant tous les éléments de `drug_dataset["train"]` : + +```py +train_df = drug_dataset["train"][:] +``` + + + +🚨 Sous le capot, `Dataset.set_format()` change le format de retour pour la méthode `__getitem__()`. Cela signifie que lorsque nous voulons créer un nouvel objet comme `train_df` à partir d'un `Dataset` au format `"pandas"`, nous devons découper tout le jeu de données pour obtenir un `pandas.DataFrame`. Vous pouvez vérifier par vous-même que le type de `drug_dataset["train"]` est `Dataset`, quel que soit le format de sortie. + + + + +De là, nous pouvons utiliser toutes les fonctionnalités Pandas que nous voulons. Par exemple, nous pouvons faire un chaînage sophistiqué pour calculer la distribution de classe parmi les entrées `condition` : + +```py +frequencies = ( + train_df["condition"] + .value_counts() + .to_frame() + .reset_index() + .rename(columns={"index": "condition", "condition": "frequency"}) +) +frequencies.head() +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
conditionfrequency
0birth control27655
1depression8023
2acne5209
3anxiety4991
4pain4744
+ + +Et une fois que nous avons terminé notre analyse Pandas, nous pouvons toujours créer un nouvel objet `Dataset` en utilisant la fonction `Dataset.from_pandas()` comme suit : + + +```py +from datasets import Dataset + +freq_dataset = Dataset.from_pandas(frequencies) +freq_dataset +``` + +```python out +Dataset({ + features: ['condition', 'frequency'], + num_rows: 819 +}) +``` + + + +✏️ **Essayez !** Calculez la note moyenne par médicament et stockez le résultat dans un nouveau jeu de données. + + + +Ceci conclut notre visite des différentes techniques de prétraitement disponibles dans 🤗 *Datasets*. Pour compléter la section, créons un ensemble de validation pour préparer le jeu de données à l’entraînement d'un classifieur. Avant cela, nous allons réinitialiser le format de sortie de `drug_dataset` de `"pandas"` à `"arrow"` : + +```python +drug_dataset.reset_format() +``` + +## Création d'un ensemble de validation + +Bien que nous ayons un jeu de test que nous pourrions utiliser pour l'évaluation, il est recommandé de ne pas toucher au jeu de test et de créer un jeu de validation séparé pendant le développement. Une fois que vous êtes satisfait des performances de vos modèles sur l'ensemble de validation, vous pouvez effectuer une dernière vérification d'intégrité sur l'ensemble test. Ce processus permet d'atténuer le risque de surentraînement sur le jeu de test et de déployer un modèle qui échoue sur des données du monde réel. + +🤗 *Datasets* fournit une fonction `Dataset.train_test_split()` basée sur la célèbre fonctionnalité de `scikit-learn`. Utilisons-la pour diviser notre ensemble d'entraînement `train` et `validation` (nous définissons l'argument `seed` pour la reproductibilité) : + +```py +drug_dataset_clean = drug_dataset["train"].train_test_split(train_size=0.8, seed=42) +# Rename the default "test" split to "validation" +drug_dataset_clean["validation"] = drug_dataset_clean.pop("test") +# Add the "test" set to our `DatasetDict` +drug_dataset_clean["test"] = drug_dataset["test"] +drug_dataset_clean +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 110811 + }) + validation: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 27703 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 46108 + }) +}) +``` + +Génial, nous avons maintenant préparé un jeu de données prêt pour l'entraînement de certains modèles ! Dans la [section 5](/course/fr/chapter5/5), nous vous montrerons comment télécharger des jeux de données sur le *Hub*. Mais pour l'instant, terminons notre analyse en examinant quelques façons d'enregistrer des jeux de données sur votre ordinateur local. + +## Enregistrer un jeu de données + + + +Bien que 🤗 *Datasets* mette en cache chaque jeu de données téléchargé et les opérations qui y sont effectuées, il y a des moments où vous voudrez enregistrer un jeu de données sur le disque (par exemple, au cas où le cache serait supprimé). Comme indiqué dans le tableau ci-dessous, 🤗 *Datasets* fournit trois fonctions principales pour enregistrer votre jeu de données dans différents formats : + +| Format de données | Fonction | +| :---------------: | :----------------------: | +| Arrow | `Dataset.save_to_disk()` | +| CSV | `Dataset.to_csv()` | +| JSON | `Dataset.to_json()` | + +Par exemple, enregistrons notre jeu de données nettoyé au format Arrow : + +```py +drug_dataset_clean.save_to_disk("drug-reviews") +``` + +Cela créera un répertoire avec la structure suivante : + +``` +drug-reviews/ +├── dataset_dict.json +├── test +│ ├── dataset.arrow +│ ├── dataset_info.json +│ └── state.json +├── train +│ ├── dataset.arrow +│ ├── dataset_info.json +│ ├── indices.arrow +│ └── state.json +└── validation + ├── dataset.arrow + ├── dataset_info.json + ├── indices.arrow + └── state.json +``` + +où nous pouvons voir que chaque division est associée à sa propre table *dataset.arrow* et à certaines métadonnées dans *dataset_info.json* et *state.json*. Vous pouvez considérer le format Arrow comme un tableau sophistiqué de colonnes et de lignes optimisé pour la création d'applications hautes performances qui traitent et transportent de grands ensembles de données. + +Une fois le jeu de données enregistré, nous pouvons le charger en utilisant la fonction `load_from_disk()` comme suit : + +```py +from datasets import load_from_disk + +drug_dataset_reloaded = load_from_disk("drug-reviews") +drug_dataset_reloaded +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 110811 + }) + validation: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 27703 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 46108 + }) +}) +``` + +Pour les formats CSV et JSON, nous devons stocker chaque fractionnement dans un fichier séparé. Pour ce faire, vous pouvez parcourir les clés et les valeurs de l'objet `DatasetDict` : + +```py +for split, dataset in drug_dataset_clean.items(): + dataset.to_json(f"drug-reviews-{split}.jsonl") +``` + +Cela enregistre chaque fractionnement au [format JSON Lines](https://jsonlines.org), où chaque ligne du jeu de données est stockée sous la forme d'une seule ligne de JSON. Voici à quoi ressemble le premier exemple : + +```py +!head -n 1 drug-reviews-train.jsonl +``` + +```python out +{"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} + # Il semble que je ressente les effets secondaires habituels de LEXAPRO : insomnie, baisse de la libido, somnolence pendant la journée. Je le prends le soir parce que mon médecin m'a dit de le prendre le soir s'il me fatiguait. J'ai supposé que ce serait le cas et j'ai commencé à le prendre la nuit. Rêves étranges, certains agréables. On m'a diagnostiqué une fibromyalgie. Il semble que ce médicament aide à soulager la douleur. J'ai eu de l'anxiété et de la dépression dans ma famille, et j'ai essayé plusieurs autres médicaments qui n'ont pas fonctionné. Cela ne fait que deux semaines que je prends ce médicament, mais je me sens plus positif dans mon esprit et je veux accomplir davantage dans ma vie. J'espère que les effets secondaires vont s'estomper, cela vaut la peine de s'y tenir d'après les réponses des autres. C'est un excellent médicament. +``` + +Nous pouvons ensuite utiliser les techniques de [section 2](/course/fr/chapter5/2) pour charger les fichiers JSON comme suit : + +```py +data_files = { + "train": "drug-reviews-train.jsonl", + "validation": "drug-reviews-validation.jsonl", + "test": "drug-reviews-test.jsonl", +} +drug_dataset_reloaded = load_dataset("json", data_files=data_files) +``` + +Et c'est tout pour notre excursion dans la manipulation des données avec 🤗 *Datasets* ! Maintenant que nous disposons d'un ensemble de données nettoyé pour entraîner un modèle, voici quelques idées que vous pouvez essayer : + +1. Utilisez les techniques du [chapitre 3](/course/fr/chapter3) pour entraîner un classifieur capable de prédire l'état du patient en fonction de l'examen du médicament. +2. Utilisez le pipeline `summarization` du [chapitre 1](/course/fr/chapter1) pour générer des résumés des révisions. + +Ensuite, nous verrons comment 🤗 *Datasets* peut vous permettre de travailler avec d'énormes jeux de données sans faire exploser votre ordinateur portable ! diff --git a/chapters/fr/chapter5/4.mdx b/chapters/fr/chapter5/4.mdx index 8657e3b62..dc286c718 100644 --- a/chapters/fr/chapter5/4.mdx +++ b/chapters/fr/chapter5/4.mdx @@ -1,296 +1,296 @@ -# Données massives ? 🤗 *Datasets* à la rescousse ! - - - - -De nos jours, il n'est pas rare de travailler avec des jeux de données de plusieurs gigaoctets surtout si vous envisagez de pré-entraîner un *transformer* comme BERT ou GPT-2 à partir de zéro. Dans ces cas, même _charger_ les données peut être un défi. Par exemple, le corpus WebText utilisé pour pré-entraîner GPT-2 se compose de plus de 8 millions de documents et de 40 Go de texte. Le charger dans la RAM de votre ordinateur portable est susceptible de lui donner une crise cardiaque ! - -Heureusement, 🤗 *Datasets* a été conçu pour surmonter ces limitations. Il vous libère des problèmes de gestion de la mémoire en traitant les jeux de données comme des fichiers _mappés en mémoire_, ainsi que des limites du disque dur en faisant du _streaming_ sur les entrées dans un corpus. - - - -Dans cette section, nous allons explorer ces fonctionnalités de 🤗 *Datasets* avec un énorme corpus de 825 Go connu sous le nom de [The Pile](https://pile.eleuther.ai). Commençons ! - -## Qu'est-ce que *The Pile* ? - -The Pile est un corpus de texte en anglais créé par [EleutherAI](https://www.eleuther.ai) pour entraîner des modèles de langage à grande échelle. Il comprend une gamme variée de jeux de données, couvrant des articles scientifiques, des référentiels de code GitHub et du texte Web filtré. Le corpus d’entraînement est disponible en [morceaux de 14 Go](https://mystic.the-eye.eu/public/AI/pile/) et vous pouvez aussi télécharger plusieurs des [composants individuels]( https://mystic.the-eye.eu/public/AI/pile_preliminary_components/). Commençons par jeter un coup d'œil au jeu de données PubMed Abstracts, qui est un corpus de résumés de 15 millions de publications biomédicales sur [PubMed](https://pubmed.ncbi.nlm.nih.gov/). Le jeu de données est au [format JSON Lines](https://jsonlines.org) et est compressé à l'aide de la bibliothèque `zstandard`. Nous devons donc d'abord installer cette bibliothèque : - -```py -!pip install zstandard -``` - -Ensuite, nous pouvons charger le jeu de données en utilisant la méthode pour les fichiers distants que nous avons apprise dans [section 2](/course/fr/chapter5/2) : - -```py -from datasets import load_dataset - -# Cela prend quelques minutes à exécuter, alors allez prendre un thé ou un café en attendant :) -data_files = "https://mystic.the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst" -pubmed_dataset = load_dataset("json", data_files=data_files, split="train") -pubmed_dataset -``` - -```python out -Dataset({ - features: ['meta', 'text'], - num_rows: 15518009 -}) -``` - -Nous pouvons voir qu'il y a 15 518 009 lignes et 2 colonnes dans notre jeu de données. C'est beaucoup ! - - - -✎ 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. - - - -Inspectons le contenu du premier exemple : - -```py -pubmed_dataset[0] -``` - -```python out -{'meta': {'pmid': 11409574, 'language': 'eng'}, - '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 ...' -# Épidémiologie de l'hypoxémie chez les enfants souffrant d'une infection aiguë des voies respiratoires inférieures. Déterminer la prévalence de l'hypoxémie chez les enfants de moins de 5 ans souffrant d'une infection aiguë des voies respiratoires inférieures (IAVI), les facteurs de risque de l'hypoxémie chez les enfants de moins de 5 ans souffrant d'une IAVI, et l'association de l'hypoxémie à un risque accru de décès chez les enfants du même âge ... -} -``` - -Cela ressemble au résumé d'un article médical. Voyons maintenant combien de RAM nous avons utilisé pour charger le jeu de données ! - -## La magie du *memory mapping* - -Un moyen simple de mesurer l'utilisation de la mémoire dans Python consiste à utiliser la bibliothèque [`psutil`](https://psutil.readthedocs.io/en/latest/) qui peut être installée avec `pip` comme suit : - -```python -!pip install psutil -``` - -Elle fournit une classe `Process` qui permet de vérifier l'utilisation de la mémoire du processus en cours : - -```py -import psutil - -# Process.memory_info est exprimé en octets, donc convertir en mégaoctets -print(f"RAM used: {psutil.Process().memory_info().rss / (1024 * 1024):.2f} MB") -``` - -```python out -RAM used: 5678.33 MB -``` - -Ici, l'attribut `rss` fait référence à la _taille de l'ensemble résident_, qui est la fraction de mémoire qu'un processus occupe dans la RAM. Cette mesure inclut également la mémoire utilisée par l'interpréteur Python et les bibliothèques que nous avons chargées, de sorte que la quantité réelle de mémoire utilisée pour charger le jeu de données est un peu plus petite. À titre de comparaison, voyons la taille du jeu de données sur le disque en utilisant l'attribut `dataset_size`. Comme le résultat est exprimé en octets comme précédemment, nous devons le convertir manuellement en gigaoctets : - -```py -print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") -size_gb = pubmed_dataset.dataset_size / (1024**3) -print(f"Dataset size (cache file) : {size_gb:.2f} GB") -``` - -```python out -Number of files in dataset : 20979437051 -Dataset size (cache file) : 19.54 GB -``` - -Malgré sa taille de près de 20 Go, nous pouvons charger et accéder au jeu de données avec beaucoup moins de RAM ! - - - -✏️ **Essayez !** Choisissez l'un des [sous-ensembles](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/) de The Pile qui est plus grand que la RAM de votre ordinateur portable ou de bureau. Chargez le avec 🤗 *Datasets* et mesurez la quantité de RAM utilisée. Notez que pour obtenir une mesure précise, vous devrez le faire dans un nouveau processus. Vous pouvez trouver les tailles décompressées de chaque sous-ensemble dans le tableau 1 du papier de [The Pile](https://arxiv.org/abs/2101.00027). - - - -Si vous êtes familier avec Pandas, ce résultat pourrait surprendre en raison de la célèbre [règle d'or](https://wesmckinney.com/blog/apache-arrow-pandas-internals/) de Wes Kinney selon laquelle vous avez généralement besoin de 5 à 10 fois plus de RAM que la taille de votre jeu de données. Alors, comment 🤗 *Datasets* résout-il ce problème de gestion de la mémoire ? 🤗 *Datasets* traite chaque jeu de données comme un [fichier mappé en mémoire](https://en.wikipedia.org/wiki/Memory-mapped_file). Cela fournit un mappage entre la RAM et le stockage du système de fichiers permettant à la bibliothèque d'accéder et d'opérer sur des éléments du jeu de données sans avoir besoin de le charger entièrement en mémoire. - -Les fichiers mappés en mémoire peuvent également être partagés entre plusieurs processus ce qui permet de paralléliser des méthodes telles que `Dataset.map()` sans avoir à déplacer ou copier le jeu de données. Sous le capot, ces capacités sont toutes réalisées par le format de mémoire [Apache Arrow](https://arrow.apache.org) et [`pyarrow`](https://arrow.apache.org/docs/python/index .html), qui accélèrent le chargement et le traitement des données. (Pour plus de détails sur Apache Arrow et les comparaisons avec Pandas, consultez [l'article de blog de Dejan Simic](https://towardsdatascience.com/apache-arrow-read-dataframe-with-zero-memory-69634092b1a).) Pour voir ceci en action, effectuons un petit test de vitesse en itérant sur tous les éléments du jeu de données PubMed Abstracts : - -```py -import timeit - -code_snippet = """batch_size = 1000 - -for idx in range(0, len(pubmed_dataset), batch_size): - _ = pubmed_dataset[idx:idx + batch_size] -""" - -time = timeit.timeit(stmt=code_snippet, number=1, globals=globals()) -print( - f"Iterated over {len(pubmed_dataset)} examples (about {size_gb:.1f} GB) in " - f"{time:.1f}s, i.e. {size_gb/time:.3f} GB/s" -) -``` - -```python out -'Iterated over 15518009 examples (about 19.5 GB) in 64.2s, i.e. 0.304 GB/s' -``` - -Ici, nous avons utilisé le module `timeit` de Python pour mesurer le temps d'exécution pris par `code_snippet`. Vous pourrez généralement itérer sur un jeu de données à une vitesse de quelques dixièmes de Go/s à plusieurs Go/s. Cela fonctionne très bien pour la grande majorité des applications, mais vous devrez parfois travailler avec un jeu de données trop volumineux pour être même stocké sur le disque dur de votre ordinateur portable. Par exemple, si nous essayions de télécharger The Pile dans son intégralité, nous aurions besoin de 825 Go d'espace disque libre ! Pour gérer ces cas, 🤗 *Datasets* fournit une fonctionnalité de streaming qui nous permet de télécharger et d'accéder aux éléments à la volée, sans avoir besoin de télécharger l'intégralité du jeu de données. Voyons comment cela fonctionne. - - - -💡 Dans les *notebooks* Jupyter, vous pouvez également chronométrer les cellules à l'aide de la fonction magique [`%%timeit`](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit). - - - -## Jeux de données en continu - -Pour activer le streaming du jeu de données, il vous suffit de passer l'argument `streaming=True` à la fonction `load_dataset()`. Par exemple, chargeons à nouveau le jeu de données PubMed Abstracts mais en mode streaming : - -```py -pubmed_dataset_streamed = load_dataset( - "json", data_files=data_files, split="train", streaming=True -) -``` - -Au lieu du familier `Dataset` que nous avons rencontré ailleurs dans ce chapitre, l'objet retourné avec `streaming=True` est un `IterableDataset`. Comme son nom l'indique, pour accéder aux éléments d'un `IterableDataset`, nous devons parcourir celui-ci. Nous pouvons accéder au premier élément de notre jeu de données diffusé comme suit : - - -```py -next(iter(pubmed_dataset_streamed)) -``` - -```python out -{'meta': {'pmid': 11409574, 'language': 'eng'}, - '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 ...'} -``` - -Les éléments d'un jeu de données diffusé en continu peuvent être traités à la volée à l'aide de `IterableDataset.map()`, ce qui est utile pendant l’entraînement si vous avez besoin de tokeniser les entrées. Le processus est exactement le même que celui que nous avons utilisé pour tokeniser notre jeu de données dans [Chapitre 3](/course/fr/chapter3), à la seule différence que les sorties sont renvoyées une par une : - -```py -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased") -tokenized_dataset = pubmed_dataset_streamed.map(lambda x: tokenizer(x["text"])) -next(iter(tokenized_dataset)) -``` - -```python out -{'input_ids': [101, 4958, 5178, 4328, 6779, ...], 'attention_mask': [1, 1, 1, 1, 1, ...]} -``` - - - -💡 Pour accélérer la tokenisation avec le streaming, vous pouvez passer `batched=True`, comme nous l'avons vu dans la dernière section. Il traitera les exemples batch par batch. La taille de batch par défaut est de 1 000 et peut être spécifiée avec l'argument `batch_size`. - - - -Vous pouvez également mélanger un jeu de données diffusé en continu à l'aide de `IterableDataset.shuffle()`, mais contrairement à `Dataset.shuffle()`, cela ne mélange que les éléments dans un `buffer_size` prédéfini : - -```py -shuffled_dataset = pubmed_dataset_streamed.shuffle(buffer_size=10_000, seed=42) -next(iter(shuffled_dataset)) -``` - -```python out -{'meta': {'pmid': 11410799, 'language': 'eng'}, - 'text': 'Randomized study of dose or schedule modification of granulocyte colony-stimulating factor in platinum-based chemotherapy for elderly patients with lung cancer ...' -# Étude randomisée sur la modification de la dose ou du calendrier d'administration du facteur de stimulation des colonies de granulocytes dans le cadre d'une chimiothérapie à base de platine chez les patients âgés atteints de cancer du poumon ... -} -``` - -Dans cet exemple, nous avons sélectionné un exemple aléatoire parmi les 10 000 premiers exemples du tampon. Une fois qu'un exemple est accédé, sa place dans le tampon est remplie avec l'exemple suivant dans le corpus (c'est-à-dire le 10 001e exemple dans le cas ci-dessus). Vous pouvez également sélectionner des éléments d'un jeu de données diffusé en continu à l'aide des fonctions `IterableDataset.take()` et `IterableDataset.skip()`, qui agissent de la même manière que `Dataset.select()`. Par exemple, pour sélectionner les 5 premiers exemples dans le jeu de données PubMed Abstracts, nous pouvons procéder comme suit : - -```py -dataset_head = pubmed_dataset_streamed.take(5) -list(dataset_head) -``` - -```python out -[{'meta': {'pmid': 11409574, 'language': 'eng'}, - 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...' -# Épidémiologie de l'hypoxémie chez les enfants atteints d'une infection aiguë des voies respiratoires inférieures ...}, - {'meta': {'pmid': 11409575, 'language': 'eng'}, - 'text': 'Clinical signs of hypoxaemia in children with acute lower respiratory infection: indicators of oxygen therapy ...' -# Signes cliniques d'hypoxémie chez les enfants atteints d'une infection aiguë des voies respiratoires inférieures : indicateurs de l'oxygénothérapie ...}, - {'meta': {'pmid': 11409576, 'language': 'eng'}, - 'text': "Hypoxaemia in children with severe pneumonia in Papua New Guinea ..." -# Hypoxémie chez les enfants atteints de pneumonie grave en Papouasie-Nouvelle-Guinée ...}, - {'meta': {'pmid': 11409577, 'language': 'eng'}, - 'text': 'Oxygen concentrators and cylinders ...' -# Concentrateurs et bouteilles d'oxygène...}, - {'meta': {'pmid': 11409578, 'language': 'eng'}, - 'text': 'Oxygen supply in rural africa: a personal experience ...' -# L'approvisionnement en oxygène dans les zones rurales africaines : une expérience personnelle ...}] -``` - -De même, vous pouvez utiliser la fonction `IterableDataset.skip()` pour créer des fractionnements d'entraînement et de validation à partir d'un jeu de données mélangé comme suit : - -```py -# Ignorer les 1 000 premiers exemples et inclure le reste dans l'ensemble d'apprentissage. -train_dataset = shuffled_dataset.skip(1000) -# Prendre les 1 000 premiers exemples pour l'ensemble de validation. -validation_dataset = shuffled_dataset.take(1000) -``` - -Terminons notre exploration du streaming des jeux de données avec une application commune : combiner plusieurs jeux de données pour créer un seul corpus. 🤗 *Datasets* fournit une fonction `interleave_datasets()` qui convertit une liste d'objets `IterableDataset` en un seul `IterableDataset`, où les éléments du nouveau jeu de données sont obtenus en alternant entre les exemples source. Cette fonction est particulièrement utile lorsque vous essayez de combiner de grands jeux de données. Par exemple, streamons FreeLaw, un sous-ensemble de The Pile et qui est un jeu de données de 51 Go d'avis juridiques de tribunaux américains : - -```py -law_dataset_streamed = load_dataset( - "json", - data_files="https://mystic.the-eye.eu/public/AI/pile_preliminary_components/FreeLaw_Opinions.jsonl.zst", - split="train", - streaming=True, -) -next(iter(law_dataset_streamed)) -``` - -```python out -{'meta': {'case_ID': '110921.json', - 'case_jurisdiction': 'scotus.tar.gz', - 'date_created': '2010-04-28T17:12:49Z'}, - 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'} -``` - -Ce jeu de données est suffisamment volumineux pour solliciter la RAM de la plupart des ordinateurs portables, mais nous avons pu le charger et y accéder sans transpirer ! Combinons maintenant les jeux de données FreeLaw et PubMed Abstracts avec la fonction `interleave_datasets()` : - -```py -from itertools import islice -from datasets import interleave_datasets - -combined_dataset = interleave_datasets([pubmed_dataset_streamed, law_dataset_streamed]) -list(islice(combined_dataset, 2)) -``` - -```python out -[{'meta': {'pmid': 11409574, 'language': 'eng'}, - 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'}, - {'meta': {'case_ID': '110921.json', - 'case_jurisdiction': 'scotus.tar.gz', - 'date_created': '2010-04-28T17:12:49Z'}, - 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'}] -``` - -Ici, nous avons utilisé la fonction `islice()` du module `itertools` de Python pour sélectionner les deux premiers exemples du jeu de données combiné. Nous pouvons voir qu'ils correspondent aux premiers exemples de chacun des deux jeux de données source. - -Enfin, si vous souhaitez streamer The Pile dans son intégralité de 825 Go, vous pouvez récupérer tous les fichiers préparés comme suit : - -```py -base_url = "https://mystic.the-eye.eu/public/AI/pile/" -data_files = { - "train": [base_url + "train/" + f"{idx:02d}.jsonl.zst" for idx in range(30)], - "validation": base_url + "val.jsonl.zst", - "test": base_url + "test.jsonl.zst", -} -pile_dataset = load_dataset("json", data_files=data_files, streaming=True) -next(iter(pile_dataset["train"])) -``` - -```python out -{'meta': {'pile_set_name': 'Pile-CC'}, - 'text': 'It is done, and submitted. You can play “Survival of the Tastiest” on Android, and on the web...'} -``` - - - -✏️ **Essayez !** Utilisez l'un des grands corpus Common Crawl comme [`mc4`](https://huggingface.co/datasets/mc4) ou [`oscar`](https://huggingface.co/datasets/oscar) pour créer en streaming un jeu de données multilingue représentant les proportions de langues parlées dans un pays de votre choix. Par exemple, les quatre langues nationales en Suisse sont l'allemand, le français, l'italien et le romanche. Vous pouvez donc essayer de créer un corpus suisse en échantillonnant les sous-ensembles Oscar en fonction de leur proportion parlée. - - - -Vous disposez maintenant de tous les outils dont vous avez besoin pour charger et traiter des jeux de données de toutes formes et tailles. Cependant à moins que vous ne soyez exceptionnellement chanceux, il arrivera un moment dans votre cheminement en traitement du langage naturel où vous devrez réellement créer un jeu de données pour résoudre un problème donné. C'est le sujet de la section suivante ! +# Données massives ? 🤗 Datasets à la rescousse ! + + + + +De nos jours, il n'est pas rare de travailler avec des jeux de données de plusieurs gigaoctets surtout si vous envisagez de pré-entraîner un *transformer* comme BERT ou GPT-2 à partir de zéro. Dans ces cas, même _charger_ les données peut être un défi. Par exemple, le corpus WebText utilisé pour pré-entraîner GPT-2 se compose de plus de 8 millions de documents et de 40 Go de texte. Le charger dans la RAM de votre ordinateur portable est susceptible de lui donner une crise cardiaque ! + +Heureusement, 🤗 *Datasets* a été conçu pour surmonter ces limitations. Il vous libère des problèmes de gestion de la mémoire en traitant les jeux de données comme des fichiers _mappés en mémoire_, ainsi que des limites du disque dur en faisant du _streaming_ sur les entrées dans un corpus. + + + +Dans cette section, nous allons explorer ces fonctionnalités de 🤗 *Datasets* avec un énorme corpus de 825 Go connu sous le nom de [*The Pile*](https://pile.eleuther.ai). Commençons ! + +## Qu'est-ce que The Pile ? + +*The Pile* est un corpus de texte en anglais créé par [EleutherAI](https://www.eleuther.ai) pour entraîner des modèles de langage à grande échelle. Il comprend une gamme variée de jeux de données, couvrant des articles scientifiques, des référentiels de code GitHub et du texte Web filtré. Le corpus d’entraînement est disponible en [morceaux de 14 Go](https://mystic.the-eye.eu/public/AI/pile/) et vous pouvez aussi télécharger plusieurs des [composants individuels]( https://mystic.the-eye.eu/public/AI/pile_preliminary_components/). Commençons par jeter un coup d'œil au jeu de données *PubMed Abstracts*, qui est un corpus de résumés de 15 millions de publications biomédicales sur [PubMed](https://pubmed.ncbi.nlm.nih.gov/). Le jeu de données est au [format JSON Lines](https://jsonlines.org) et est compressé à l'aide de la bibliothèque `zstandard`. Nous devons donc d'abord installer cette bibliothèque : + +```py +!pip install zstandard +``` + +Ensuite, nous pouvons charger le jeu de données en utilisant la méthode pour les fichiers distants que nous avons apprise dans [section 2](/course/fr/chapter5/2) : + +```py +from datasets import load_dataset + +# Cela prend quelques minutes à exécuter, alors allez prendre un thé ou un café en attendant :) +data_files = "https://mystic.the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst" +pubmed_dataset = load_dataset("json", data_files=data_files, split="train") +pubmed_dataset +``` + +```python out +Dataset({ + features: ['meta', 'text'], + num_rows: 15518009 +}) +``` + +Nous pouvons voir qu'il y a 15 518 009 lignes et 2 colonnes dans notre jeu de données. C'est beaucoup ! + + + +✎ 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. + + + +Inspectons le contenu du premier exemple : + +```py +pubmed_dataset[0] +``` + +```python out +{'meta': {'pmid': 11409574, 'language': 'eng'}, + '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 ...' +# Épidémiologie de l'hypoxémie chez les enfants souffrant d'une infection aiguë des voies respiratoires inférieures. Déterminer la prévalence de l'hypoxémie chez les enfants de moins de 5 ans souffrant d'une infection aiguë des voies respiratoires inférieures (IAVI), les facteurs de risque de l'hypoxémie chez les enfants de moins de 5 ans souffrant d'une IAVI, et l'association de l'hypoxémie à un risque accru de décès chez les enfants du même âge ... +} +``` + +Cela ressemble au résumé d'un article médical. Voyons maintenant combien de RAM nous avons utilisé pour charger le jeu de données ! + +## La magie du memory mapping + +Un moyen simple de mesurer l'utilisation de la mémoire dans Python consiste à utiliser la bibliothèque [`psutil`](https://psutil.readthedocs.io/en/latest/) qui peut être installée avec `pip` comme suit : + +```python +!pip install psutil +``` + +Elle fournit une classe `Process` qui permet de vérifier l'utilisation de la mémoire du processus en cours : + +```py +import psutil + +# Process.memory_info est exprimé en octets, donc convertir en mégaoctets +print(f"RAM used: {psutil.Process().memory_info().rss / (1024 * 1024):.2f} MB") +``` + +```python out +RAM used: 5678.33 MB +``` + +Ici, l'attribut `rss` fait référence à la _taille de l'ensemble résident_, qui est la fraction de mémoire qu'un processus occupe dans la RAM. Cette mesure inclut également la mémoire utilisée par l'interpréteur Python et les bibliothèques que nous avons chargées, de sorte que la quantité réelle de mémoire utilisée pour charger le jeu de données est un peu plus petite. À titre de comparaison, voyons la taille du jeu de données sur le disque en utilisant l'attribut `dataset_size`. Comme le résultat est exprimé en octets comme précédemment, nous devons le convertir manuellement en gigaoctets : + +```py +print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") +size_gb = pubmed_dataset.dataset_size / (1024**3) +print(f"Dataset size (cache file) : {size_gb:.2f} GB") +``` + +```python out +Number of files in dataset : 20979437051 +Dataset size (cache file) : 19.54 GB +``` + +Malgré sa taille de près de 20 Go, nous pouvons charger et accéder au jeu de données avec beaucoup moins de RAM ! + + + +✏️ **Essayez !** Choisissez l'un des [sous-ensembles](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/) de *The Pile* qui est plus grand que la RAM de votre ordinateur portable ou de bureau. Chargez-le avec 🤗 *Datasets* et mesurez la quantité de RAM utilisée. Notez que pour obtenir une mesure précise, vous devrez le faire dans un nouveau processus. Vous pouvez trouver les tailles décompressées de chaque sous-ensemble dans le tableau 1 du papier de [*The Pile*](https://arxiv.org/abs/2101.00027). + + + +Si vous êtes familier avec Pandas, ce résultat pourrait surprendre en raison de la célèbre [règle d'or](https://wesmckinney.com/blog/apache-arrow-pandas-internals/) de Wes Kinney selon laquelle vous avez généralement besoin de 5 à 10 fois plus de RAM que la taille de votre jeu de données. Alors, comment 🤗 *Datasets* résout-il ce problème de gestion de la mémoire ? 🤗 *Datasets* traite chaque jeu de données comme un [fichier mappé en mémoire](https://en.wikipedia.org/wiki/Memory-mapped_file). Cela fournit un mappage entre la RAM et le stockage du système de fichiers permettant à la bibliothèque d'accéder et d'opérer sur des éléments du jeu de données sans avoir besoin de le charger entièrement en mémoire. + +Les fichiers mappés en mémoire peuvent également être partagés entre plusieurs processus ce qui permet de paralléliser des méthodes telles que `Dataset.map()` sans avoir à déplacer ou copier le jeu de données. Sous le capot, ces capacités sont toutes réalisées par le format de mémoire [Apache Arrow](https://arrow.apache.org) et [`pyarrow`](https://arrow.apache.org/docs/python/index.html), qui accélèrent le chargement et le traitement des données. (Pour plus de détails sur Apache Arrow et les comparaisons avec Pandas, consultez [l'article de blog de Dejan Simic](https://towardsdatascience.com/apache-arrow-read-dataframe-with-zero-memory-69634092b1a)). Pour voir ceci en action, effectuons un petit test de vitesse en itérant sur tous les éléments du jeu de données *PubMed Abstracts* : + +```py +import timeit + +code_snippet = """batch_size = 1000 + +for idx in range(0, len(pubmed_dataset), batch_size): + _ = pubmed_dataset[idx:idx + batch_size] +""" + +time = timeit.timeit(stmt=code_snippet, number=1, globals=globals()) +print( + f"Iterated over {len(pubmed_dataset)} examples (about {size_gb:.1f} GB) in " + f"{time:.1f}s, i.e. {size_gb/time:.3f} GB/s" +) +``` + +```python out +'Iterated over 15518009 examples (about 19.5 GB) in 64.2s, i.e. 0.304 GB/s' +``` + +Ici, nous avons utilisé le module `timeit` de Python pour mesurer le temps d'exécution pris par `code_snippet`. Vous pourrez généralement itérer sur un jeu de données à une vitesse de quelques dixièmes de Go/s à plusieurs Go/s. Cela fonctionne très bien pour la grande majorité des applications, mais vous devrez parfois travailler avec un jeu de données trop volumineux pour être même stocké sur le disque dur de votre ordinateur portable. Par exemple, si nous essayions de télécharger *The Pile* dans son intégralité, nous aurions besoin de 825 Go d'espace disque libre ! Pour gérer ces cas, 🤗 *Datasets* fournit une fonctionnalité de streaming qui nous permet de télécharger et d'accéder aux éléments à la volée, sans avoir besoin de télécharger l'intégralité du jeu de données. Voyons comment cela fonctionne. + + + +💡 Dans les *notebooks* Jupyter, vous pouvez également chronométrer les cellules à l'aide de la fonction magique [`%%timeit`](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit). + + + +## Jeux de données en continu + +Pour activer le streaming du jeu de données, il vous suffit de passer l'argument `streaming=True` à la fonction `load_dataset()`. Par exemple, chargeons à nouveau le jeu de données *PubMed Abstracts* mais en mode streaming : + +```py +pubmed_dataset_streamed = load_dataset( + "json", data_files=data_files, split="train", streaming=True +) +``` + +Au lieu du familier `Dataset` que nous avons rencontré ailleurs dans ce chapitre, l'objet retourné avec `streaming=True` est un `IterableDataset`. Comme son nom l'indique, pour accéder aux éléments d'un `IterableDataset`, nous devons parcourir celui-ci. Nous pouvons accéder au premier élément de notre jeu de données diffusé comme suit : + + +```py +next(iter(pubmed_dataset_streamed)) +``` + +```python out +{'meta': {'pmid': 11409574, 'language': 'eng'}, + '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 ...'} +``` + +Les éléments d'un jeu de données diffusé en continu peuvent être traités à la volée à l'aide de `IterableDataset.map()`, ce qui est utile pendant l’entraînement si vous avez besoin de tokeniser les entrées. Le processus est exactement le même que celui que nous avons utilisé pour tokeniser notre jeu de données dans le [chapitre 3](/course/fr/chapter3), à la seule différence que les sorties sont renvoyées une par une : + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased") +tokenized_dataset = pubmed_dataset_streamed.map(lambda x: tokenizer(x["text"])) +next(iter(tokenized_dataset)) +``` + +```python out +{'input_ids': [101, 4958, 5178, 4328, 6779, ...], 'attention_mask': [1, 1, 1, 1, 1, ...]} +``` + + + +💡 Pour accélérer la tokenisation avec le streaming, vous pouvez passer `batched=True`, comme nous l'avons vu dans la dernière section. Il traitera les exemples batch par batch. La taille de batch par défaut est de 1 000 et peut être spécifiée avec l'argument `batch_size`. + + + +Vous pouvez également mélanger un jeu de données diffusé en continu à l'aide de `IterableDataset.shuffle()`, mais contrairement à `Dataset.shuffle()`, cela ne mélange que les éléments dans un `buffer_size` prédéfini : + +```py +shuffled_dataset = pubmed_dataset_streamed.shuffle(buffer_size=10_000, seed=42) +next(iter(shuffled_dataset)) +``` + +```python out +{'meta': {'pmid': 11410799, 'language': 'eng'}, + 'text': 'Randomized study of dose or schedule modification of granulocyte colony-stimulating factor in platinum-based chemotherapy for elderly patients with lung cancer ...' +# Étude randomisée sur la modification de la dose ou du calendrier d'administration du facteur de stimulation des colonies de granulocytes dans le cadre d'une chimiothérapie à base de platine chez les patients âgés atteints de cancer du poumon ... +} +``` + +Dans cet exemple, nous avons sélectionné un exemple aléatoire parmi les 10 000 premiers exemples du tampon. Une fois qu'un exemple est accédé, sa place dans le tampon est remplie avec l'exemple suivant dans le corpus (c'est-à-dire le 10 001e exemple dans le cas ci-dessus). Vous pouvez également sélectionner des éléments d'un jeu de données diffusé en continu à l'aide des fonctions `IterableDataset.take()` et `IterableDataset.skip()`, qui agissent de la même manière que `Dataset.select()`. Par exemple, pour sélectionner les 5 premiers exemples dans le jeu de données *PubMed Abstracts*, nous pouvons procéder comme suit : + +```py +dataset_head = pubmed_dataset_streamed.take(5) +list(dataset_head) +``` + +```python out +[{'meta': {'pmid': 11409574, 'language': 'eng'}, + 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...' +# Épidémiologie de l'hypoxémie chez les enfants atteints d'une infection aiguë des voies respiratoires inférieures ...}, + {'meta': {'pmid': 11409575, 'language': 'eng'}, + 'text': 'Clinical signs of hypoxaemia in children with acute lower respiratory infection: indicators of oxygen therapy ...' +# Signes cliniques d'hypoxémie chez les enfants atteints d'une infection aiguë des voies respiratoires inférieures : indicateurs de l'oxygénothérapie ...}, + {'meta': {'pmid': 11409576, 'language': 'eng'}, + 'text': "Hypoxaemia in children with severe pneumonia in Papua New Guinea ..." +# Hypoxémie chez les enfants atteints de pneumonie grave en Papouasie-Nouvelle-Guinée ...}, + {'meta': {'pmid': 11409577, 'language': 'eng'}, + 'text': 'Oxygen concentrators and cylinders ...' +# Concentrateurs et bouteilles d'oxygène...}, + {'meta': {'pmid': 11409578, 'language': 'eng'}, + 'text': 'Oxygen supply in rural africa: a personal experience ...' +# L'approvisionnement en oxygène dans les zones rurales africaines : une expérience personnelle ...}] +``` + +De même, vous pouvez utiliser la fonction `IterableDataset.skip()` pour créer des fractionnements d'entraînement et de validation à partir d'un jeu de données mélangé comme suit : + +```py +# Ignorer les 1 000 premiers exemples et inclure le reste dans l'ensemble d'apprentissage. +train_dataset = shuffled_dataset.skip(1000) +# Prendre les 1 000 premiers exemples pour l'ensemble de validation. +validation_dataset = shuffled_dataset.take(1000) +``` + +Terminons notre exploration du streaming des jeux de données avec une application commune : combiner plusieurs jeux de données pour créer un seul corpus. 🤗 *Datasets* fournit une fonction `interleave_datasets()` qui convertit une liste d'objets `IterableDataset` en un seul `IterableDataset`, où les éléments du nouveau jeu de données sont obtenus en alternant entre les exemples source. Cette fonction est particulièrement utile lorsque vous essayez de combiner de grands jeux de données. Par exemple, streamons FreeLaw, un sous-ensemble de *The Pile* et qui est un jeu de données de 51 Go d'avis juridiques de tribunaux américains : + +```py +law_dataset_streamed = load_dataset( + "json", + data_files="https://mystic.the-eye.eu/public/AI/pile_preliminary_components/FreeLaw_Opinions.jsonl.zst", + split="train", + streaming=True, +) +next(iter(law_dataset_streamed)) +``` + +```python out +{'meta': {'case_ID': '110921.json', + 'case_jurisdiction': 'scotus.tar.gz', + 'date_created': '2010-04-28T17:12:49Z'}, + 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'} +``` + +Ce jeu de données est suffisamment volumineux pour solliciter la RAM de la plupart des ordinateurs portables, mais nous avons pu le charger et y accéder sans transpirer ! Combinons maintenant les jeux de données FreeLaw et *PubMed Abstracts* avec la fonction `interleave_datasets()` : + +```py +from itertools import islice +from datasets import interleave_datasets + +combined_dataset = interleave_datasets([pubmed_dataset_streamed, law_dataset_streamed]) +list(islice(combined_dataset, 2)) +``` + +```python out +[{'meta': {'pmid': 11409574, 'language': 'eng'}, + 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'}, + {'meta': {'case_ID': '110921.json', + 'case_jurisdiction': 'scotus.tar.gz', + 'date_created': '2010-04-28T17:12:49Z'}, + 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'}] +``` + +Ici, nous avons utilisé la fonction `islice()` du module `itertools` de Python pour sélectionner les deux premiers exemples du jeu de données combiné. Nous pouvons voir qu'ils correspondent aux premiers exemples de chacun des deux jeux de données source. + +Enfin, si vous souhaitez streamer *The Pile* dans son intégralité de 825 Go, vous pouvez récupérer tous les fichiers préparés comme suit : + +```py +base_url = "https://mystic.the-eye.eu/public/AI/pile/" +data_files = { + "train": [base_url + "train/" + f"{idx:02d}.jsonl.zst" for idx in range(30)], + "validation": base_url + "val.jsonl.zst", + "test": base_url + "test.jsonl.zst", +} +pile_dataset = load_dataset("json", data_files=data_files, streaming=True) +next(iter(pile_dataset["train"])) +``` + +```python out +{'meta': {'pile_set_name': 'Pile-CC'}, + 'text': 'It is done, and submitted. You can play “Survival of the Tastiest” on Android, and on the web...'} +``` + + + +✏️ **Essayez !** Utilisez l'un des grands corpus Common Crawl comme [`mc4`](https://huggingface.co/datasets/mc4) ou [`oscar`](https://huggingface.co/datasets/oscar) pour créer en streaming un jeu de données multilingue représentant les proportions de langues parlées dans un pays de votre choix. Par exemple, les quatre langues nationales en Suisse sont l'allemand, le français, l'italien et le romanche. Vous pouvez donc essayer de créer un corpus suisse en échantillonnant les sous-ensembles Oscar en fonction de leur proportion parlée. + + + +Vous disposez maintenant de tous les outils dont vous avez besoin pour charger et traiter des jeux de données de toutes formes et tailles. Cependant à moins que vous ne soyez exceptionnellement chanceux, il arrivera un moment dans votre cheminement en traitement du langage naturel où vous devrez réellement créer un jeu de données pour résoudre un problème donné. C'est le sujet de la section suivante ! diff --git a/chapters/fr/chapter5/5.mdx b/chapters/fr/chapter5/5.mdx index 36bcca83c..4781cd83c 100644 --- a/chapters/fr/chapter5/5.mdx +++ b/chapters/fr/chapter5/5.mdx @@ -1,467 +1,467 @@ -# Création de votre propre jeu de données - - - -Parfois, le jeu de données dont vous avez besoin pour créer une application de NLP n'existe pas. Vous devrez donc le créer vous-même. Dans cette section, nous allons vous montrer comment créer un corpus de [problèmes GitHub](https://github.com/features/issues/), qui sont couramment utilisés pour suivre les bogues ou les fonctionnalités dans les dépôts GitHub. Ce corpus pourrait être utilisé à diverses fins, notamment : - -* explorer combien de temps il faut pour fermer les problèmes ouverts ou les *pull requests* -* entraîner d'un _classificateur multilabel_ capable d'étiqueter les problèmes avec des métadonnées basées sur la description du problème (par exemple, "bogue", "amélioration" ou "question") -* créer un moteur de recherche sémantique pour trouver les problèmes correspondant à la requête d'un utilisateur - -Ici, nous nous concentrerons sur la création du corpus, et dans la section suivante, nous aborderons l'application de recherche sémantique. Pour garder les choses méta, nous utiliserons les problèmes GitHub associés à un projet open source populaire : 🤗 *Datasets* ! Voyons comment obtenir les données et explorons les informations contenues dans ces problèmes. - -## Obtenir les données - -Vous pouvez trouver tous les problèmes dans 🤗 *Datasets* en accédant à l'[onglet « Issues »](https://github.com/huggingface/datasets/issues) du dépôt. Comme le montre la capture d'écran suivante, au moment de la rédaction, il y avait 331 problèmes ouverts et 668 problèmes fermés. - -
-The GitHub issues associated with 🤗 Datasets. -
- -Si vous cliquez sur l'un de ces problèmes, vous constaterez qu'il contient un titre, une description et un ensemble d'étiquettes qui caractérisent le problème. Un exemple est montré dans la capture d'écran ci-dessous. - -
-A typical GitHub issue in the 🤗 Datasets repository. -
- -Pour télécharger tous les problèmes du dépôt, nous utilisons l'[API REST GitHub](https://docs.github.com/en/rest) pour interroger le point de terminaison [`Issues`](https://docs.github.com/en/rest/reference/issues#list-repository-issues). Ce point de terminaison renvoie une liste d'objets JSON. Chaque objet contenant un grand nombre de champs qui incluent le titre et la description ainsi que des métadonnées sur l'état du problème, etc. - -Un moyen pratique de télécharger les problèmes consiste à utiliser la bibliothèque `requests`, qui est la méthode standard pour effectuer des requêtes HTTP en Python. Vous pouvez installer la bibliothèque en exécutant : - -```python -!pip install requests -``` - -Une fois la bibliothèque installée, vous pouvez envoyer des requêtes GET au point de terminaison `Issues` en appelant la fonction `requests.get()`. Par exemple, vous pouvez exécuter la commande suivante pour récupérer le premier numéro sur la première page : - -```py -import requests - -url = "https://api.github.com/repos/huggingface/datasets/issues?page=1&per_page=1" -response = requests.get(url) -``` - -L'objet `response` contient de nombreuses informations utiles sur la requête, y compris le code d'état HTTP : - -```py -response.status_code -``` - -```python out -200 -``` - -où un statut `200` signifie que la requête a réussi (vous pouvez trouver une liste des codes de statut HTTP possibles [ici](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes)). Ce qui nous intéresse vraiment, cependant, c'est le _payload_, qui peut être consulté dans différents formats comme les octets, les chaînes ou JSON. Comme nous savons que nos problèmes sont au format JSON, examinons la charge utile comme suit : - -```py -response.json() -``` - -```python out -[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/2792', - 'repository_url': 'https://api.github.com/repos/huggingface/datasets', - 'labels_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/labels{/name}', - 'comments_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/comments', - 'events_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/events', - 'html_url': 'https://github.com/huggingface/datasets/pull/2792', - 'id': 968650274, - 'node_id': 'MDExOlB1bGxSZXF1ZXN0NzEwNzUyMjc0', - 'number': 2792, - 'title': 'Update GooAQ', - 'user': {'login': 'bhavitvyamalik', - 'id': 19718818, - 'node_id': 'MDQ6VXNlcjE5NzE4ODE4', - 'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4', - 'gravatar_id': '', - 'url': 'https://api.github.com/users/bhavitvyamalik', - 'html_url': 'https://github.com/bhavitvyamalik', - 'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers', - 'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}', - 'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}', - 'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}', - 'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions', - 'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs', - 'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos', - 'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}', - 'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events', - 'type': 'User', - 'site_admin': False}, - 'labels': [], - 'state': 'open', - 'locked': False, - 'assignee': None, - 'assignees': [], - 'milestone': None, - 'comments': 1, - 'created_at': '2021-08-12T11:40:18Z', - 'updated_at': '2021-08-12T12:31:17Z', - 'closed_at': None, - 'author_association': 'CONTRIBUTOR', - 'active_lock_reason': None, - 'pull_request': {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/2792', - 'html_url': 'https://github.com/huggingface/datasets/pull/2792', - 'diff_url': 'https://github.com/huggingface/datasets/pull/2792.diff', - 'patch_url': 'https://github.com/huggingface/datasets/pull/2792.patch'}, - 'body': '[GooAQ](https://github.com/allenai/gooaq) dataset was recently updated after splits were added for the same. This PR contains new updated GooAQ with train/val/test splits and updated README as well.', - 'performed_via_github_app': None}] -``` - -Waouh, ça fait beaucoup d'informations ! Nous pouvons voir des champs utiles comme `title`, `body` et `number` qui décrivent le problème, ainsi que des informations sur l'utilisateur GitHub qui a ouvert le problème. - - - -✏️ **Essayez !** Cliquez sur quelques-unes des URL pour avoir une idée du type d'informations auxquelles chaque problème GitHub est lié. - - - -Comme décrit dans la [documentation GitHub](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting), les requêtes non authentifiées sont limitées à 60 requêtes par heure. Bien que vous puissiez augmenter le paramètre de requête `per_page` pour réduire le nombre de requêtes que vous effectuez, vous atteindrez toujours la limite de débit sur tout dépôt contenant des milliers de problèmes. Donc, à la place, vous devez suivre les [instructions de GitHub](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) sur la création d'un _jeton d'accès personnel_ afin que vous peut augmenter la limite de débit à 5 000 requêtes par heure. Une fois que vous avez votre *token*, vous pouvez l'inclure dans l'en-tête de la requête : - -```py -GITHUB_TOKEN = xxx # Copy your GitHub token here -headers = {"Authorization": f"token {GITHUB_TOKEN}"} -``` - - - -⚠️ Ne partagez pas un *notebook* avec votre `GITHUB_TOKEN` collé dedans. Nous vous recommandons de supprimer la dernière cellule une fois que vous l'avez exécutée pour éviter de divulguer accidentellement ces informations. Mieux encore, stockez le jeton dans un fichier *.env* et utilisez la [bibliothèque `python-dotenv`](https://github.com/theskumar/python-dotenv) pour le charger automatiquement pour vous en tant que variable d'environnement. - - - -Maintenant que nous avons notre jeton d'accès, créons une fonction qui peut télécharger tous les problèmes depuis un référentiel GitHub : - -```py -import time -import math -from pathlib import Path -import pandas as pd -from tqdm.notebook import tqdm - - -def fetch_issues( - owner="huggingface", - repo="datasets", - num_issues=10_000, - rate_limit=5_000, - issues_path=Path("."), -): - if not issues_path.is_dir(): - issues_path.mkdir(exist_ok=True) - - batch = [] - all_issues = [] - per_page = 100 # Number of issues to return per page - num_pages = math.ceil(num_issues / per_page) - base_url = "https://api.github.com/repos" - - for page in tqdm(range(num_pages)): - # Query with state=all to get both open and closed issues - query = f"issues?page={page}&per_page={per_page}&state=all" - issues = requests.get(f"{base_url}/{owner}/{repo}/{query}", headers=headers) - batch.extend(issues.json()) - - if len(batch) > rate_limit and len(all_issues) < num_issues: - all_issues.extend(batch) - batch = [] # Flush batch for next time period - print(f"Reached GitHub rate limit. Sleeping for one hour ...") - time.sleep(60 * 60 + 1) - - all_issues.extend(batch) - df = pd.DataFrame.from_records(all_issues) - df.to_json(f"{issues_path}/{repo}-issues.jsonl", orient="records", lines=True) - print( - f"Downloaded all the issues for {repo}! Dataset stored at {issues_path}/{repo}-issues.jsonl" - ) -``` - -Désormais, lorsque nous appellerons `fetch_issues()`, tous les problèmes seront téléchargés par batchs pour éviter de dépasser la limite de GitHub sur le nombre de requêtes par heure. Le résultat sera stocké dans un fichier _repository_name-issues.jsonl_, où chaque ligne est un objet JSON qui représente un problème. Utilisons cette fonction pour saisir tous les problèmes de 🤗 *Datasets* : - -```py -# En fonction de votre connexion Internet, l'exécution peut prendre plusieurs minutes... -fetch_issues() -``` - -Une fois les problèmes téléchargés, nous pouvons les charger localement en utilisant nos nouvelles compétences de [section 2](/course/fr/chaper5/2) : - -```py -issues_dataset = load_dataset("json", data_files="datasets-issues.jsonl", split="train") -issues_dataset -``` - -```python out -Dataset({ - features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'timeline_url', 'performed_via_github_app'], - num_rows: 3019 -}) -``` - -Génial, nous avons créé notre premier jeu de données à partir de rien ! Mais pourquoi y a-t-il plusieurs milliers de problèmes alors que l'[onglet « Issues »](https://github.com/huggingface/datasets/issues) de la librairie 🤗 *Datasets* n'affiche qu'environ 1 000 problèmes au total 🤔 ? Comme décrit dans la [documentation GitHub](https://docs.github.com/en/rest/reference/issues#list-issues-assigned-to-the-authenticated-user), c'est parce que nous avons téléchargé toutes les *pull requests* également : - -> L'API REST v3 de GitHub considère chaque *pull request* comme un problème, mais chaque problème n'est pas une *pull request*. Pour cette raison, les points de terminaison « Issues » peuvent renvoyer à la fois des problèmes et des *pull requests* dans la réponse. Vous pouvez identifier les *pull requests* par la clé `pull_request`. Sachez que l'identifiant d'une *pull request* renvoyée par les points de terminaison « Issues » sera un identifiant de problème. - -Étant donné que le contenu des « Issues » et des *pull request* est assez différent, procédons à un prétraitement mineur pour nous permettre de les distinguer. - -## Nettoyer les données - -L'extrait ci-dessus de la documentation de GitHub nous indique que la colonne `pull_request` peut être utilisée pour différencier les *issues* et les *pull requests*. Regardons un échantillon aléatoire pour voir quelle est la différence. Comme nous l'avons fait dans [section 3](/course/fr/chapter5/3), nous allons enchaîner `Dataset.shuffle()` et `Dataset.select()` pour créer un échantillon aléatoire, puis compresser `html_url` et ` pull_request` afin que nous puissions comparer les différentes URL : - -```py -sample = issues_dataset.shuffle(seed=666).select(range(3)) - -# Print out the URL and pull request entries -for url, pr in zip(sample["html_url"], sample["pull_request"]): - print(f">> URL: {url}") - print(f">> Pull request: {pr}\n") -``` - -```python out ->> URL: https://github.com/huggingface/datasets/pull/850 ->> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/850', 'html_url': 'https://github.com/huggingface/datasets/pull/850', 'diff_url': 'https://github.com/huggingface/datasets/pull/850.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/850.patch'} - ->> URL: https://github.com/huggingface/datasets/issues/2773 ->> Pull request: None - ->> URL: https://github.com/huggingface/datasets/pull/783 ->> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/783', 'html_url': 'https://github.com/huggingface/datasets/pull/783', 'diff_url': 'https://github.com/huggingface/datasets/pull/783.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/783.patch'} -``` - -Ici, nous pouvons voir que chaque *pull request* est associée à diverses URL, tandis que les problèmes ordinaires ont une entrée `None`. Nous pouvons utiliser cette distinction pour créer une nouvelle colonne `is_pull_request` qui vérifie si le champ `pull_request` est `None` ou non : - -```py -issues_dataset = issues_dataset.map( - lambda x: {"is_pull_request": False if x["pull_request"] is None else True} -) -``` - - - -✏️ **Essayez !** Calculez le temps moyen nécessaire pour résoudre les problèmes dans 🤗 *Datasets*. Vous pouvez trouver la fonction `Dataset.filter()` utile pour filtrer les demandes d'extraction et les problèmes ouverts. Vous pouvez utiliser la fonction `Dataset.set_format()` pour convertir le jeu de données en un `DataFrame` afin que vous puissiez facilement manipuler les horodatages `created_at` et `closed_at`. Pour les points bonus, calculez le temps moyen nécessaire pour fermer les *pull_requests*. - - - -Bien que nous puissions continuer à nettoyer davantage le jeu de données en supprimant ou en renommant certaines colonnes, il est généralement recommandé de le conserver aussi brut que possible à ce stade afin qu'il puisse être facilement utilisé dans plusieurs applications. - -Avant de pousser notre jeu de données vers le *Hub* d’Hugging Face, traitons une chose manquante : les commentaires associés à chaque problème et *pull requests*. Nous les ajouterons ensuite avec l'API GitHub REST ! - -## Enrichir le jeu de données - -Comme le montre la capture d'écran suivante, les commentaires associés à un problème ou à une *pull request* fournissent une riche source d'informations, en particulier si nous souhaitons créer un moteur de recherche pour répondre aux requêtes des utilisateurs sur la bibliothèque. - -
-Comments associated with an issue about 🤗 Datasets. -
- -L'API REST GitHub fournit un point de terminaison [`Comments`](https://docs.github.com/en/rest/reference/issues#list-issue-comments) qui renvoie tous les commentaires associés à un numéro de problème. Testons le point de terminaison pour voir ce qu'il renvoie : - -```py -issue_number = 2792 -url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments" -response = requests.get(url, headers=headers) -response.json() -``` - -```python out -[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/comments/897594128', - 'html_url': 'https://github.com/huggingface/datasets/pull/2792#issuecomment-897594128', - 'issue_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792', - 'id': 897594128, - 'node_id': 'IC_kwDODunzps41gDMQ', - 'user': {'login': 'bhavitvyamalik', - 'id': 19718818, - 'node_id': 'MDQ6VXNlcjE5NzE4ODE4', - 'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4', - 'gravatar_id': '', - 'url': 'https://api.github.com/users/bhavitvyamalik', - 'html_url': 'https://github.com/bhavitvyamalik', - 'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers', - 'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}', - 'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}', - 'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}', - 'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions', - 'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs', - 'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos', - 'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}', - 'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events', - 'type': 'User', - 'site_admin': False}, - 'created_at': '2021-08-12T12:21:52Z', - 'updated_at': '2021-08-12T12:31:17Z', - 'author_association': 'CONTRIBUTOR', - 'body': "@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?", - 'performed_via_github_app': None}] -``` - -Nous pouvons voir que le commentaire est stocké dans le champ `body`. Ecrivons donc une fonction simple qui renvoie tous les commentaires associés à un problème en sélectionnant le contenu `body` pour chaque élément dans `response.json()` : - -```py -def get_comments(issue_number): - url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments" - response = requests.get(url, headers=headers) - return [r["body"] for r in response.json()] - - -# Testez notre fonction fonctionne comme prévu -get_comments(2792) -``` - -```python out -["@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?"] -``` - -Cela a l'air bien. Utilisons `Dataset.map()` pour ajouter une nouvelle colonne `comments` à chaque problème de notre jeu de données : - -```py -# Selon votre connexion internet, cela peut prendre quelques minutes... -issues_with_comments_dataset = issues_dataset.map( - lambda x: {"comments": get_comments(x["number"])} -) -``` - -La dernière étape consiste à enregistrer le jeu de données augmentées avec nos données brutes afin que nous puissions les pousser tous les deux vers le *Hub* : - -```py -issues_with_comments_dataset.to_json("issues-datasets-with-comments.jsonl") -``` - -## Téléchargement du jeu de données sur le *Hub* d’Hugging Face - - - -Maintenant que nous avons notre jeu de données augmenté, il est temps de le pousser vers le *Hub* afin que nous puissions le partager avec la communauté ! Pour télécharger le jeu de données, nous utilisons la [bibliothèque 🤗 *Hub*](https://github.com/huggingface/huggingface_hub), qui nous permet d'interagir avec le *Hub* d’Hugging Face via une API Python. 🤗 *Hub* est préinstallé avec 🤗 *Transformers*, nous pouvons donc l'utiliser directement. Par exemple, nous pouvons utiliser la fonction `list_datasets()` pour obtenir des informations sur tous les ensembles de données publics actuellement hébergés sur le *Hub*: - -```py -from huggingface_hub import list_datasets - -all_datasets = list_datasets() -print(f"Number of datasets on Hub: {len(all_datasets)}") -print(all_datasets[0]) -``` - -```python out -Number of datasets on Hub: 1487 -Dataset Name: acronym_identification, Tags: ['annotations_creators:expert-generated', 'language_creators:found', 'languages:en', 'licenses:mit', 'multilinguality:monolingual', 'size_categories:10K - -✏️ **Essayez !** Utilisez votre nom d'utilisateur et votre mot de passe Hugging Face pour obtenir un jeton et créer un dépôt vide appelé `github-issues`. N'oubliez pas de **n'enregistrez jamais vos informations d'identification** dans Colab ou tout autre référentiel car ces informations peuvent être exploitées par de mauvais individus. - - - -Ensuite, clonons le dépôt du Hub sur notre machine locale et copions-y notre fichier jeu de données. 🤗 *Hub* fournit une classe `Repository` pratique qui encapsule de nombreuses commandes Git courantes. Donc pour cloner le dépôt distant, nous devons simplement fournir l'URL et le chemin local vers lesquels nous souhaitons cloner : - -```py -from huggingface_hub import Repository - -repo = Repository(local_dir="github-issues", clone_from=repo_url) -!cp datasets-issues-with-comments.jsonl github-issues/ -``` - -Par défaut, diverses extensions de fichiers (telles que *.bin*, *.gz* et *.zip*) sont suivies avec Git LFS afin que les fichiers volumineux puissent être versionnés dans le même workflow Git. Vous pouvez trouver une liste des extensions de fichiers suivis dans le fichier *.gitattributes* du référentiel. Pour inclure le format JSON Lines dans la liste, nous pouvons exécuter la commande suivante : - -```py -repo.lfs_track("*.jsonl") -``` - -Ensuite, nous pouvons utiliser `Repository.push_to_hub()` pour pousser le jeu de données vers le *Hub* : - -```py -repo.push_to_hub() -``` - -Si nous naviguons vers l'URL contenue dans `repo_url`, nous devrions maintenant voir que notre fichier de jeu de données a été téléchargé. - -
-Our dataset repository on the Hugging Face Hub. -
- -À partir de là, n'importe qui peut télécharger le jeu de données en fournissant simplement `load_dataset()` avec l'ID du référentiel comme argument `path` : - -```py -remote_dataset = load_dataset("lewtun/github-issues", split="train") -remote_dataset -``` - -```python out -Dataset({ - features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], - num_rows: 2855 -}) -``` - -Cool, nous avons poussé notre jeu de données vers le *Hub* et il est disponible pour que d'autres puissent l'utiliser ! Il ne reste plus qu'une chose importante à faire : ajouter une _carte de jeu de données_ qui explique comment le corpus a été créé et fournit d'autres informations utiles à la communauté. - - - -💡 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. - - - -## Création d'une carte pour un jeu de données - -Des jeux de données bien documentés sont plus susceptibles d'être utiles aux autres (y compris à vous-même) car ils fournissent le contexte permettant aux utilisateurs de décider si le jeu de données est pertinent pour leur tâche et d'évaluer les biais potentiels ou les risques associés à l'utilisation du jeu de données. - -Sur le *Hub*, ces informations sont stockées dans le fichier *README.md* de chaque dépôt de jeux de données. Il y a deux étapes principales que vous devez suivre avant de créer ce fichier : - -1. Utilisez l'[application `datasets-tagging`](https://huggingface.co/datasets/tagging/) pour créer des balises de métadonnées au format YAML. Ces balises sont utilisées pour une variété de fonctionnalités de recherche sur le *Hub* d’Hugging Face et garantissent que votre jeu de données peut être facilement trouvé par les membres de la communauté. Puisque nous avons créé un jeu de données personnalisé ici, vous devrez cloner le référentiel `datasets-tagging` et exécuter l'application localement. Voici à quoi ressemble l'interface : - -
-The `datasets-tagging` interface. -
- -2. Lisez le [guide de 🤗 *Datasets*](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) sur la création des cartes informatives des jeux de données et utilisez-le comme modèle. - -Vous pouvez créer le fichier *README.md* directement sur le *Hub* et vous pouvez trouver un modèle de carte dans le dépot `lewtun/github-issues`. Une capture d'écran de la carte remplie est illustrée ci-dessous. - - -
-A dataset card. -
- - - -✏️ **Essayez !** Utilisez l'application `dataset-tagging` et [le guide de 🤗 *Datasets*](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) pour compléter le fichier *README.md* de votre jeu de données de problèmes GitHub. - - -C’ets tout ! Nous avons vu dans cette section que la création d'un bon jeu de données peut être assez complexe, mais heureusement, le télécharger et le partager avec la communauté ne l'est pas. Dans la section suivante, nous utiliserons notre nouveau jeu de données pour créer un moteur de recherche sémantique avec 🤗 *Datasets* qui peut faire correspondre les questions aux problèmes et commentaires les plus pertinents. - - - -✏️ **Essayez !** Suivez les étapes que nous avons suivies dans cette section pour créer un jeu de données de problèmes GitHub pour votre bibliothèque open source préférée (choisissez autre chose que 🤗 *Datasets*, bien sûr !). Pour obtenir des points bonus, *finetunez* un classifieur multilabel pour prédire les balises présentes dans le champ `labels`. - - +# Création de votre propre jeu de données + + + +Parfois, le jeu de données dont vous avez besoin pour créer une application de NLP n'existe pas. Vous devrez donc le créer vous-même. Dans cette section, nous allons vous montrer comment créer un corpus de [problèmes GitHub](https://github.com/features/issues/), qui sont couramment utilisés pour suivre les bogues ou les fonctionnalités dans les dépôts GitHub. Ce corpus pourrait être utilisé à diverses fins, notamment : + +* explorer combien de temps il faut pour fermer les problèmes ouverts ou les *pull requests* +* entraîner d'un _classificateur multilabel_ capable d'étiqueter les problèmes avec des métadonnées basées sur la description du problème (par exemple : « bug », « amélioration » ou « question ») +* créer un moteur de recherche sémantique pour trouver les problèmes correspondant à la requête d'un utilisateur + +Ici, nous nous concentrerons sur la création du corpus, et dans la section suivante, nous aborderons l'application de recherche sémantique. Pour garder les choses méta, nous utiliserons les problèmes GitHub associés à un projet open source populaire : 🤗 *Datasets* ! Voyons comment obtenir les données et explorons les informations contenues dans ces problèmes. + +## Obtenir les données + +Vous pouvez trouver tous les problèmes dans 🤗 *Datasets* en accédant à l'[onglet « Issues »](https://github.com/huggingface/datasets/issues) du dépôt. Comme le montre la capture d'écran suivante, au moment de la rédaction, il y avait 331 problèmes ouverts et 668 problèmes fermés. + +
+The GitHub issues associated with 🤗 Datasets. +
+ +Si vous cliquez sur l'un de ces problèmes, vous constaterez qu'il contient un titre, une description et un ensemble d'étiquettes qui caractérisent le problème. Un exemple est montré dans la capture d'écran ci-dessous. + +
+A typical GitHub issue in the 🤗 Datasets repository. +
+ +Pour télécharger tous les problèmes du dépôt, nous utilisons l'[API REST GitHub](https://docs.github.com/en/rest) pour interroger le point de terminaison [`Issues`](https://docs.github.com/en/rest/reference/issues#list-repository-issues). Ce point de terminaison renvoie une liste d'objets JSON. Chaque objet contenant un grand nombre de champs qui incluent le titre et la description ainsi que des métadonnées sur l'état du problème, etc. + +Un moyen pratique de télécharger les problèmes consiste à utiliser la bibliothèque `requests`, qui est la méthode standard pour effectuer des requêtes HTTP en Python. Vous pouvez installer la bibliothèque en exécutant : + +```python +!pip install requests +``` + +Une fois la bibliothèque installée, vous pouvez envoyer des requêtes GET au point de terminaison `Issues` en appelant la fonction `requests.get()`. Par exemple, vous pouvez exécuter la commande suivante pour récupérer le premier numéro sur la première page : + +```py +import requests + +url = "https://api.github.com/repos/huggingface/datasets/issues?page=1&per_page=1" +response = requests.get(url) +``` + +L'objet `response` contient de nombreuses informations utiles sur la requête, y compris le code d'état HTTP : + +```py +response.status_code +``` + +```python out +200 +``` + +où un statut `200` signifie que la requête a réussi (vous pouvez trouver une liste des codes de statut HTTP possibles [ici](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes)). Ce qui nous intéresse vraiment, cependant, c'est le _payload_, qui peut être consulté dans différents formats comme les octets, les chaînes ou JSON. Comme nous savons que nos problèmes sont au format JSON, examinons la charge utile comme suit : + +```py +response.json() +``` + +```python out +[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/2792', + 'repository_url': 'https://api.github.com/repos/huggingface/datasets', + 'labels_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/labels{/name}', + 'comments_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/comments', + 'events_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/events', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792', + 'id': 968650274, + 'node_id': 'MDExOlB1bGxSZXF1ZXN0NzEwNzUyMjc0', + 'number': 2792, + 'title': 'Update GooAQ', + 'user': {'login': 'bhavitvyamalik', + 'id': 19718818, + 'node_id': 'MDQ6VXNlcjE5NzE4ODE4', + 'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4', + 'gravatar_id': '', + 'url': 'https://api.github.com/users/bhavitvyamalik', + 'html_url': 'https://github.com/bhavitvyamalik', + 'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers', + 'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}', + 'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}', + 'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}', + 'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions', + 'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs', + 'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos', + 'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}', + 'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events', + 'type': 'User', + 'site_admin': False}, + 'labels': [], + 'state': 'open', + 'locked': False, + 'assignee': None, + 'assignees': [], + 'milestone': None, + 'comments': 1, + 'created_at': '2021-08-12T11:40:18Z', + 'updated_at': '2021-08-12T12:31:17Z', + 'closed_at': None, + 'author_association': 'CONTRIBUTOR', + 'active_lock_reason': None, + 'pull_request': {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/2792', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792', + 'diff_url': 'https://github.com/huggingface/datasets/pull/2792.diff', + 'patch_url': 'https://github.com/huggingface/datasets/pull/2792.patch'}, + 'body': '[GooAQ](https://github.com/allenai/gooaq) dataset was recently updated after splits were added for the same. This PR contains new updated GooAQ with train/val/test splits and updated README as well.', + 'performed_via_github_app': None}] +``` + +Waouh, ça fait beaucoup d'informations ! Nous pouvons voir des champs utiles comme `title`, `body` et `number` qui décrivent le problème, ainsi que des informations sur l'utilisateur GitHub qui a ouvert le problème. + + + +✏️ **Essayez !** Cliquez sur quelques-unes des URL pour avoir une idée du type d'informations auxquelles chaque problème GitHub est lié. + + + +Comme décrit dans la [documentation GitHub](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting), les requêtes non authentifiées sont limitées à 60 requêtes par heure. Bien que vous puissiez augmenter le paramètre de requête `per_page` pour réduire le nombre de requêtes que vous effectuez, vous atteindrez toujours la limite de débit sur tout dépôt contenant des milliers de problèmes. Donc, à la place, vous devez suivre les [instructions de GitHub](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) sur la création d'un _jeton d'accès personnel_ afin que vous peut augmenter la limite de débit à 5 000 requêtes par heure. Une fois que vous avez votre *token*, vous pouvez l'inclure dans l'en-tête de la requête : + +```py +GITHUB_TOKEN = xxx # Copy your GitHub token here +headers = {"Authorization": f"token {GITHUB_TOKEN}"} +``` + + + +⚠️ Ne partagez pas un *notebook* avec votre `GITHUB_TOKEN` collé dedans. Nous vous recommandons de supprimer la dernière cellule une fois que vous l'avez exécutée pour éviter de divulguer accidentellement ces informations. Mieux encore, stockez le jeton dans un fichier *.env* et utilisez la [bibliothèque `python-dotenv`](https://github.com/theskumar/python-dotenv) pour le charger automatiquement pour vous en tant que variable d'environnement. + + + +Maintenant que nous avons notre jeton d'accès, créons une fonction qui peut télécharger tous les problèmes depuis un référentiel GitHub : + +```py +import time +import math +from pathlib import Path +import pandas as pd +from tqdm.notebook import tqdm + + +def fetch_issues( + owner="huggingface", + repo="datasets", + num_issues=10_000, + rate_limit=5_000, + issues_path=Path("."), +): + if not issues_path.is_dir(): + issues_path.mkdir(exist_ok=True) + + batch = [] + all_issues = [] + per_page = 100 # Number of issues to return per page + num_pages = math.ceil(num_issues / per_page) + base_url = "https://api.github.com/repos" + + for page in tqdm(range(num_pages)): + # Query with state=all to get both open and closed issues + query = f"issues?page={page}&per_page={per_page}&state=all" + issues = requests.get(f"{base_url}/{owner}/{repo}/{query}", headers=headers) + batch.extend(issues.json()) + + if len(batch) > rate_limit and len(all_issues) < num_issues: + all_issues.extend(batch) + batch = [] # Flush batch for next time period + print(f"Reached GitHub rate limit. Sleeping for one hour ...") + time.sleep(60 * 60 + 1) + + all_issues.extend(batch) + df = pd.DataFrame.from_records(all_issues) + df.to_json(f"{issues_path}/{repo}-issues.jsonl", orient="records", lines=True) + print( + f"Downloaded all the issues for {repo}! Dataset stored at {issues_path}/{repo}-issues.jsonl" + ) +``` + +Désormais, lorsque nous appellerons `fetch_issues()`, tous les problèmes seront téléchargés par batchs pour éviter de dépasser la limite de GitHub sur le nombre de requêtes par heure. Le résultat sera stocké dans un fichier _repository_name-issues.jsonl_, où chaque ligne est un objet JSON qui représente un problème. Utilisons cette fonction pour saisir tous les problèmes de 🤗 *Datasets* : + +```py +# En fonction de votre connexion Internet, l'exécution peut prendre plusieurs minutes... +fetch_issues() +``` + +Une fois les problèmes téléchargés, nous pouvons les charger localement en utilisant nos nouvelles compétences de [section 2](/course/fr/chaper5/2) : + +```py +issues_dataset = load_dataset("json", data_files="datasets-issues.jsonl", split="train") +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'timeline_url', 'performed_via_github_app'], + num_rows: 3019 +}) +``` + +Génial, nous avons créé notre premier jeu de données à partir de rien ! Mais pourquoi y a-t-il plusieurs milliers de problèmes alors que l'[onglet « Issues »](https://github.com/huggingface/datasets/issues) de la librairie 🤗 *Datasets* n'affiche qu'environ 1 000 problèmes au total 🤔 ? Comme décrit dans la [documentation GitHub](https://docs.github.com/en/rest/reference/issues#list-issues-assigned-to-the-authenticated-user), c'est parce que nous avons téléchargé toutes les *pull requests* également : + +> L'API REST v3 de GitHub considère chaque *pull request* comme un problème, mais chaque problème n'est pas une *pull request*. Pour cette raison, les points de terminaison « Issues » peuvent renvoyer à la fois des problèmes et des *pull requests* dans la réponse. Vous pouvez identifier les *pull requests* par la clé `pull_request`. Sachez que l'identifiant d'une *pull request* renvoyée par les points de terminaison « Issues » sera un identifiant de problème. + +Étant donné que le contenu des « Issues » et des *pull request* est assez différent, procédons à un prétraitement mineur pour nous permettre de les distinguer. + +## Nettoyer les données + +L'extrait ci-dessus de la documentation de GitHub nous indique que la colonne `pull_request` peut être utilisée pour différencier les *issues* et les *pull requests*. Regardons un échantillon aléatoire pour voir quelle est la différence. Comme nous l'avons fait dans [section 3](/course/fr/chapter5/3), nous allons enchaîner `Dataset.shuffle()` et `Dataset.select()` pour créer un échantillon aléatoire, puis compresser `html_url` et ` pull_request` afin que nous puissions comparer les différentes URL : + +```py +sample = issues_dataset.shuffle(seed=666).select(range(3)) + +# Print out the URL and pull request entries +for url, pr in zip(sample["html_url"], sample["pull_request"]): + print(f">> URL: {url}") + print(f">> Pull request: {pr}\n") +``` + +```python out +>> URL: https://github.com/huggingface/datasets/pull/850 +>> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/850', 'html_url': 'https://github.com/huggingface/datasets/pull/850', 'diff_url': 'https://github.com/huggingface/datasets/pull/850.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/850.patch'} + +>> URL: https://github.com/huggingface/datasets/issues/2773 +>> Pull request: None + +>> URL: https://github.com/huggingface/datasets/pull/783 +>> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/783', 'html_url': 'https://github.com/huggingface/datasets/pull/783', 'diff_url': 'https://github.com/huggingface/datasets/pull/783.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/783.patch'} +``` + +Ici, nous pouvons voir que chaque *pull request* est associée à diverses URL, tandis que les problèmes ordinaires ont une entrée `None`. Nous pouvons utiliser cette distinction pour créer une nouvelle colonne `is_pull_request` qui vérifie si le champ `pull_request` est `None` ou non : + +```py +issues_dataset = issues_dataset.map( + lambda x: {"is_pull_request": False if x["pull_request"] is None else True} +) +``` + + + +✏️ **Essayez !** Calculez le temps moyen nécessaire pour résoudre les problèmes dans 🤗 *Datasets*. Vous pouvez trouver la fonction `Dataset.filter()` utile pour filtrer les demandes d'extraction et les problèmes ouverts. Vous pouvez utiliser la fonction `Dataset.set_format()` pour convertir le jeu de données en un `DataFrame` afin que vous puissiez facilement manipuler les horodatages `created_at` et `closed_at`. Pour les points bonus, calculez le temps moyen nécessaire pour fermer les *pull_requests*. + + + +Bien que nous puissions continuer à nettoyer davantage le jeu de données en supprimant ou en renommant certaines colonnes, il est généralement recommandé de le conserver aussi brut que possible à ce stade afin qu'il puisse être facilement utilisé dans plusieurs applications. + +Avant de pousser notre jeu de données vers le *Hub* d’Hugging Face, traitons une chose manquante : les commentaires associés à chaque problème et *pull requests*. Nous les ajouterons ensuite avec l'API GitHub REST ! + +## Enrichir le jeu de données + +Comme le montre la capture d'écran suivante, les commentaires associés à un problème ou à une *pull request* fournissent une riche source d'informations, en particulier si nous souhaitons créer un moteur de recherche pour répondre aux requêtes des utilisateurs sur la bibliothèque. + +
+Comments associated with an issue about 🤗 Datasets. +
+ +L'API REST GitHub fournit un point de terminaison [`Comments`](https://docs.github.com/en/rest/reference/issues#list-issue-comments) qui renvoie tous les commentaires associés à un numéro de problème. Testons le point de terminaison pour voir ce qu'il renvoie : + +```py +issue_number = 2792 +url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments" +response = requests.get(url, headers=headers) +response.json() +``` + +```python out +[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/comments/897594128', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792#issuecomment-897594128', + 'issue_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792', + 'id': 897594128, + 'node_id': 'IC_kwDODunzps41gDMQ', + 'user': {'login': 'bhavitvyamalik', + 'id': 19718818, + 'node_id': 'MDQ6VXNlcjE5NzE4ODE4', + 'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4', + 'gravatar_id': '', + 'url': 'https://api.github.com/users/bhavitvyamalik', + 'html_url': 'https://github.com/bhavitvyamalik', + 'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers', + 'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}', + 'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}', + 'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}', + 'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions', + 'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs', + 'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos', + 'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}', + 'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events', + 'type': 'User', + 'site_admin': False}, + 'created_at': '2021-08-12T12:21:52Z', + 'updated_at': '2021-08-12T12:31:17Z', + 'author_association': 'CONTRIBUTOR', + 'body': "@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?", + 'performed_via_github_app': None}] +``` + +Nous pouvons voir que le commentaire est stocké dans le champ `body`. Ecrivons donc une fonction simple qui renvoie tous les commentaires associés à un problème en sélectionnant le contenu `body` pour chaque élément dans `response.json()` : + +```py +def get_comments(issue_number): + url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments" + response = requests.get(url, headers=headers) + return [r["body"] for r in response.json()] + + +# Tester notre fonction fonctionne comme prévu +get_comments(2792) +``` + +```python out +["@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?"] +``` + +Cela a l'air bien. Utilisons `Dataset.map()` pour ajouter une nouvelle colonne `comments` à chaque problème de notre jeu de données : + +```py +# Selon votre connexion internet, cela peut prendre quelques minutes... +issues_with_comments_dataset = issues_dataset.map( + lambda x: {"comments": get_comments(x["number"])} +) +``` + +La dernière étape consiste à enregistrer le jeu de données augmentées avec nos données brutes afin que nous puissions les pousser tous les deux vers le *Hub* : + +```py +issues_with_comments_dataset.to_json("issues-datasets-with-comments.jsonl") +``` + +## Téléchargement du jeu de données sur le Hub + + + +Maintenant que nous avons notre jeu de données augmenté, il est temps de le pousser vers le *Hub* afin que nous puissions le partager avec la communauté ! Pour télécharger le jeu de données, nous utilisons la [bibliothèque 🤗 *Hub*](https://github.com/huggingface/huggingface_hub), qui nous permet d'interagir avec le *Hub* d’Hugging Face via une API Python. 🤗 *Hub* est préinstallé avec 🤗 *Transformers*, nous pouvons donc l'utiliser directement. Par exemple, nous pouvons utiliser la fonction `list_datasets()` pour obtenir des informations sur tous les ensembles de données publics actuellement hébergés sur le *Hub*: + +```py +from huggingface_hub import list_datasets + +all_datasets = list_datasets() +print(f"Number of datasets on Hub: {len(all_datasets)}") +print(all_datasets[0]) +``` + +```python out +Number of datasets on Hub: 1487 +Dataset Name: acronym_identification, Tags: ['annotations_creators:expert-generated', 'language_creators:found', 'languages:en', 'licenses:mit', 'multilinguality:monolingual', 'size_categories:10K + +✏️ **Essayez !** Utilisez votre nom d'utilisateur et votre mot de passe Hugging Face pour obtenir un jeton et créer un dépôt vide appelé `github-issues`. N'oubliez pas de **n'enregistrez jamais vos informations d'identification** dans Colab ou tout autre référentiel car ces informations peuvent être exploitées par de mauvais individus. + + + +Ensuite, clonons le dépôt du Hub sur notre machine locale et copions-y notre fichier jeu de données. 🤗 *Hub* fournit une classe `Repository` pratique qui encapsule de nombreuses commandes Git courantes. Donc pour cloner le dépôt distant, nous devons simplement fournir l'URL et le chemin local vers lesquels nous souhaitons cloner : + +```py +from huggingface_hub import Repository + +repo = Repository(local_dir="github-issues", clone_from=repo_url) +!cp datasets-issues-with-comments.jsonl github-issues/ +``` + +Par défaut, diverses extensions de fichiers (telles que *.bin*, *.gz* et *.zip*) sont suivies avec Git LFS afin que les fichiers volumineux puissent être versionnés dans le même workflow Git. Vous pouvez trouver une liste des extensions de fichiers suivis dans le fichier *.gitattributes* du référentiel. Pour inclure le format JSON Lines dans la liste, nous pouvons exécuter la commande suivante : + +```py +repo.lfs_track("*.jsonl") +``` + +Ensuite, nous pouvons utiliser `Repository.push_to_hub()` pour pousser le jeu de données vers le *Hub* : + +```py +repo.push_to_hub() +``` + +Si nous naviguons vers l'URL contenue dans `repo_url`, nous devrions maintenant voir que notre fichier de jeu de données a été téléchargé. + +
+Our dataset repository on the Hugging Face Hub. +
+ +À partir de là, n'importe qui peut télécharger le jeu de données en fournissant simplement `load_dataset()` avec l'ID du référentiel comme argument `path` : + +```py +remote_dataset = load_dataset("lewtun/github-issues", split="train") +remote_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 2855 +}) +``` + +Cool, nous avons poussé notre jeu de données vers le *Hub* et il est disponible pour que d'autres puissent l'utiliser ! Il ne reste plus qu'une chose importante à faire : ajouter une _carte de jeu de données_ qui explique comment le corpus a été créé et fournit d'autres informations utiles à la communauté. + + + +💡 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. + + + +## Création d'une carte pour un jeu de données + +Des jeux de données bien documentés sont plus susceptibles d'être utiles aux autres (y compris à vous-même) car ils fournissent le contexte permettant aux utilisateurs de décider si le jeu de données est pertinent pour leur tâche et d'évaluer les biais potentiels ou les risques associés à l'utilisation du jeu de données. + +Sur le *Hub*, ces informations sont stockées dans le fichier *README.md* de chaque dépôt de jeux de données. Il y a deux étapes principales que vous devez suivre avant de créer ce fichier : + +1. Utilisez l'[application `datasets-tagging`](https://huggingface.co/datasets/tagging/) pour créer des balises de métadonnées au format YAML. Ces balises sont utilisées pour une variété de fonctionnalités de recherche sur le *Hub* d’Hugging Face et garantissent que votre jeu de données peut être facilement trouvé par les membres de la communauté. Puisque nous avons créé un jeu de données personnalisé ici, vous devrez cloner le référentiel `datasets-tagging` et exécuter l'application localement. Voici à quoi ressemble l'interface : + +
+The `datasets-tagging` interface. +
+ +2. Lisez le [guide de 🤗 *Datasets*](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) sur la création des cartes informatives des jeux de données et utilisez-le comme modèle. + +Vous pouvez créer le fichier *README.md* directement sur le *Hub* et vous pouvez trouver un modèle de carte dans le dépot `lewtun/github-issues`. Une capture d'écran de la carte remplie est illustrée ci-dessous. + + +
+A dataset card. +
+ + + +✏️ **Essayez !** Utilisez l'application `dataset-tagging` et [le guide de 🤗 *Datasets*](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) pour compléter le fichier *README.md* de votre jeu de données de problèmes GitHub. + + +C’est tout ! Nous avons vu dans cette section que la création d'un bon jeu de données peut être assez complexe, mais heureusement, le télécharger et le partager avec la communauté ne l'est pas. Dans la section suivante, nous utiliserons notre nouveau jeu de données pour créer un moteur de recherche sémantique avec 🤗 *Datasets* qui peut faire correspondre les questions aux problèmes et commentaires les plus pertinents. + + + +✏️ **Essayez !** Suivez les étapes que nous avons suivies dans cette section pour créer un jeu de données de problèmes GitHub pour votre bibliothèque open source préférée (choisissez autre chose que 🤗 *Datasets*, bien sûr !). Pour obtenir des points bonus, *finetunez* un classifieur multilabel pour prédire les balises présentes dans le champ `labels`. + + diff --git a/chapters/fr/chapter5/6.mdx b/chapters/fr/chapter5/6.mdx index 68cf7373e..dcb42deb5 100644 --- a/chapters/fr/chapter5/6.mdx +++ b/chapters/fr/chapter5/6.mdx @@ -1,530 +1,530 @@ - - -# Recherche sémantique avec FAISS - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -Dans [section 5](/course/fr/chapter5/5), nous avons créé un jeu de données de problèmes et de commentaires GitHub à partir du dépôt 🤗 *Datasets*. Dans cette section, nous utilisons ces informations pour créer un moteur de recherche qui peut nous aider à trouver des réponses à nos questions les plus urgentes sur la bibliothèque ! - - - -## Utilisation des enchâssements pour la recherche sémantique - -Comme nous l'avons vu dans le [Chapitre 1](/course/fr/chapter1), les modèles de langage basés sur les *transformers* représentent chaque *token* dans une étendue de texte sous la forme d'un _enchâssement_. Il s'avère que l'on peut regrouper les enchâssements individuels pour créer une représentation vectorielle pour des phrases entières, des paragraphes ou (dans certains cas) des documents. Ces enchâssements peuvent ensuite être utilisés pour trouver des documents similaires dans le corpus en calculant la similarité du produit scalaire (ou une autre métrique de similarité) entre chaque enchâssement et en renvoyant les documents avec le plus grand chevauchement. - -Dans cette section, nous utilisons les enchâssements pour développer un moteur de recherche sémantique. Ces moteurs de recherche offrent plusieurs avantages par rapport aux approches conventionnelles basées sur la correspondance des mots-clés dans une requête avec les documents. - -
-Recherche sémantique. - -
- -## Chargement et préparation du jeu de données - -La première chose que nous devons faire est de télécharger notre jeu de données de problèmes GitHub. Utilisons la bibliothèque 🤗 *Hub* pour résoudre l'URL où notre fichier est stocké sur le *Hib* d’Hugging Face : - -```py -from huggingface_hub import hf_hub_url - -data_files = hf_hub_url( - repo_id="lewtun/github-issues", - filename="datasets-issues-with-comments.jsonl", - repo_type="dataset", -) -``` - -Avec l'URL stocké dans `data_files`, nous pouvons ensuite charger le jeu de données distant en utilisant la méthode introduite dans [section 2](/course/fr/chapter5/2) : - -```py -from datasets import load_dataset - -issues_dataset = load_dataset("json", data_files=data_files, split="train") -issues_dataset -``` - -```python out -Dataset({ - features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], - num_rows: 2855 -}) -``` - -Ici, nous avons spécifié l’échantillon `train` par défaut dans `load_dataset()`, de sorte que cela renvoie un `Dataset` au lieu d'un `DatasetDict`. La première chose à faire est de filtrer les *pull requests* car celles-ci ont tendance à être rarement utilisées pour répondre aux requêtes des utilisateurs et introduiront du bruit dans notre moteur de recherche. Comme cela devrait être familier maintenant, nous pouvons utiliser la fonction `Dataset.filter()` pour exclure ces lignes de notre jeu de données. Pendant que nous y sommes, filtrons également les lignes sans commentaires, car celles-ci ne fournissent aucune réponse aux requêtes des utilisateurs : - -```py -issues_dataset = issues_dataset.filter( - lambda x: (x["is_pull_request"] == False and len(x["comments"]) > 0) -) -issues_dataset -``` - -```python out -Dataset({ - features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], - num_rows: 771 -}) -``` - -Nous pouvons voir qu'il y a beaucoup de colonnes dans notre jeu de données, dont la plupart n'ont pas besoin de construire notre moteur de recherche. Du point de vue de la recherche, les colonnes les plus informatives sont `title`, `body` et `comments`, tandis que `html_url` nous fournit un lien vers le problème source. Utilisons la fonction `Dataset.remove_columns()` pour supprimer le reste : - -```py -columns = issues_dataset.column_names -columns_to_keep = ["title", "body", "html_url", "comments"] -columns_to_remove = set(columns_to_keep).symmetric_difference(columns) -issues_dataset = issues_dataset.remove_columns(columns_to_remove) -issues_dataset -``` - -```python out -Dataset({ - features: ['html_url', 'title', 'comments', 'body'], - num_rows: 771 -}) -``` - -Pour créer nos enchâssements, nous ajoutons à chaque commentaire le titre et le corps du problème, car ces champs contiennent des informations contextuelles utiles. Étant donné que notre colonne `comments` est actuellement une liste de commentaires pour chaque problème, nous devons « éclater » la colonne afin que chaque ligne se compose d'un *tuple* `(html_url, title, body, comment)`. Dans Pandas, nous pouvons le faire avec la fonction [`DataFrame.explode()`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html), qui crée une nouvelle ligne pour chaque élément dans une colonne de type liste, tout en répliquant toutes les autres valeurs de colonne. Pour voir cela en action, passons d'abord au format `DataFrame` de Pandas : - -```py -issues_dataset.set_format("pandas") -df = issues_dataset[:] -``` - -Si nous inspectons la première ligne de ce `DataFrame`, nous pouvons voir qu'il y a quatre commentaires associés à ce problème : - -```py -df["comments"][0].tolist() -``` - -```python out -['the bug code locate in :\r\n if data_args.task_name is not None:\r\n # Downloading and loading a dataset from the hub.\r\n datasets = load_dataset("glue", data_args.task_name, cache_dir=model_args.cache_dir)', - 'Hi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com\r\n\r\nNormally, it should work if you wait a little and then retry.\r\n\r\nCould you please confirm if the problem persists?', - 'cannot connect,even by Web browser,please check that there is some problems。', - 'I can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...'] -``` - -Lorsque nous décomposons `df`, nous nous attendons à obtenir une ligne pour chacun de ces commentaires. Vérifions si c'est le cas : - -```py -comments_df = df.explode("comments", ignore_index=True) -comments_df.head(4) -``` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
html_urltitlecommentsbody
0https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comthe bug code locate in :\r\n if data_args.task_name is not None...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
1https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comHi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
2https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comcannot connect,even by Web browser,please check that there is some problems。Hello,\r\nI am trying to run run_glue.py and it gives me this error...
3https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comI can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
- -Génial, nous pouvons voir que les lignes ont été répliquées, avec la colonne `comments` contenant les commentaires individuels ! Maintenant que nous en avons fini avec Pandas, nous pouvons rapidement revenir à un `Dataset` en chargeant le `DataFrame` en mémoire : - -```py -from datasets import Dataset - -comments_dataset = Dataset.from_pandas(comments_df) -comments_dataset -``` - -```python out -Dataset({ - features: ['html_url', 'title', 'comments', 'body'], - num_rows: 2842 -}) -``` - -D'accord, cela nous a donné quelques milliers de commentaires avec lesquels travailler ! - - - - -✏️ **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. - - - -Maintenant que nous avons un commentaire par ligne, créons une nouvelle colonne `comments_length` contenant le nombre de mots par commentaire : - -```py -comments_dataset = comments_dataset.map( - lambda x: {"comment_length": len(x["comments"].split())} -) -``` - -Nous pouvons utiliser cette nouvelle colonne pour filtrer les commentaires courts incluant généralement des éléments tels que « cc @lewtun » ou « Merci ! » qui ne sont pas pertinents pour notre moteur de recherche. Il n'y a pas de nombre précis à sélectionner pour le filtre mais 15 mots semblent être un bon début : - -```py -comments_dataset = comments_dataset.filter(lambda x: x["comment_length"] > 15) -comments_dataset -``` - -```python out -Dataset({ - features: ['html_url', 'title', 'comments', 'body', 'comment_length'], - num_rows: 2098 -}) -``` - -Après avoir un peu nettoyé notre jeu de données, concaténons le titre, la description et les commentaires du problème dans une nouvelle colonne `text`. Comme d'habitude, nous allons écrire une fonction simple que nous pouvons passer à `Dataset.map()` : - -```py -def concatenate_text(examples): - return { - "text": examples["title"] - + " \n " - + examples["body"] - + " \n " - + examples["comments"] - } - - -comments_dataset = comments_dataset.map(concatenate_text) -``` - -Nous sommes enfin prêts à créer des enchâssements ! Jetons un coup d'œil. - -## Création d’enchâssements pour les textes - -Nous avons vu dans [Chapitre 2](/course/fr/chapter2) que nous pouvons obtenir des enchâssements de *tokens* en utilisant la classe `AutoModel`. Tout ce que nous avons à faire est de choisir un *checkpoint* approprié à partir duquel charger le modèle. Heureusement, il existe une bibliothèque appelée `sentence-transformers` dédiée à la création d’enchâssements. Comme décrit dans la [documentation de la bibliothèque](https://www.sbert.net/examples/applications/semantic-search/README.html#symmetric-vs-asymmetric-semantic-search), notre cas d'utilisation est un exemple de _recherche sémantique asymétrique_. En effet, nous avons une requête courte dont nous aimerions trouver la réponse dans un document plus long, par exemple un commentaire à un problème. Le [tableau de présentation des modèles](https://www.sbert.net/docs/pretrained_models.html#model-overview) de la documentation indique que le *checkpoint* `multi-qa-mpnet-base-dot-v1` a les meilleures performances pour la recherche sémantique. Utilisons donc le pour notre application. Nous allons également charger le *tokenizer* en utilisant le même *checkpoint* : - -{#if fw === 'pt'} - -```py -from transformers import AutoTokenizer, AutoModel - -model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1" -tokenizer = AutoTokenizer.from_pretrained(model_ckpt) -model = AutoModel.from_pretrained(model_ckpt) -``` - -Pour accélérer le processus, il est utile de placer le modèle et les entrées sur un périphérique GPU, alors faisons-le maintenant : - -```py -import torch - -device = torch.device("cuda") -model.to(device) -``` - -{:else} - -```py -from transformers import AutoTokenizer, TFAutoModel - -model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1" -tokenizer = AutoTokenizer.from_pretrained(model_ckpt) -model = TFAutoModel.from_pretrained(model_ckpt, from_pt=True) -``` - -Notez que nous avons défini `from_pt=True` comme argument de la méthode `from_pretrained()`. C'est parce que le point de contrôle `multi-qa-mpnet-base-dot-v1` n'a que des poids PyTorch. Donc définir `from_pt=True` converti automatiquement au format TensorFlow pour nous. Comme vous pouvez le voir, il est très simple de passer d'un *framework* à l'autre dans 🤗 *Transformers* ! - -{/if} - -Comme nous l'avons mentionné précédemment, nous aimerions représenter chaque entrée dans notre corpus de problèmes GitHub comme un vecteur unique. Nous devons donc regrouper ou faire la moyenne de nos enchâssements de *tokens* d'une manière ou d'une autre. Une approche populaire consiste à effectuer un *regroupement CLS* sur les sorties de notre modèle, où nous collectons simplement le dernier état caché pour le *token* spécial `[CLS]`. La fonction suivante fait ça pour nous : - -```py -def cls_pooling(model_output): - return model_output.last_hidden_state[:, 0] -``` - -Ensuite, nous allons créer une fonction utile qui va tokeniser une liste de documents, placer les tenseurs dans le GPU, les donner au modèle et enfin appliquer le regroupement CLS aux sorties : - -{#if fw === 'pt'} - -```py -def get_embeddings(text_list): - encoded_input = tokenizer( - text_list, padding=True, truncation=True, return_tensors="pt" - ) - encoded_input = {k: v.to(device) for k, v in encoded_input.items()} - model_output = model(**encoded_input) - return cls_pooling(model_output) -``` - -Nous pouvons tester le fonctionnement de la fonction en lui donnant la première entrée textuelle de notre corpus et en inspectant la forme de sortie : - -```py -embedding = get_embeddings(comments_dataset["text"][0]) -embedding.shape -``` - -```python out -torch.Size([1, 768]) -``` - -Super ! Nous avons converti la première entrée de notre corpus en un vecteur à 768 dimensions. Nous pouvons utiliser `Dataset.map()` pour appliquer notre fonction `get_embeddings()` à chaque ligne de notre corpus. Créons donc une nouvelle colonne `embeddings` comme suit : - -```py -embeddings_dataset = comments_dataset.map( - lambda x: {"embeddings": get_embeddings(x["text"]).detach().cpu().numpy()[0]} -) -``` - -{:else} - -```py -def get_embeddings(text_list): - encoded_input = tokenizer( - text_list, padding=True, truncation=True, return_tensors="tf" - ) - encoded_input = {k: v for k, v in encoded_input.items()} - model_output = model(**encoded_input) - return cls_pooling(model_output) -``` - -Nous pouvons tester le fonctionnement de la fonction en lui donnant la première entrée textuelle de notre corpus et en inspectant la forme de sortie : - -```py -embedding = get_embeddings(comments_dataset["text"][0]) -embedding.shape -``` - -```python out -TensorShape([1, 768]) -``` - -Super ! Nous avons converti la première entrée de notre corpus en un vecteur à 768 dimensions. Nous pouvons utiliser `Dataset.map()` pour appliquer notre fonction `get_embeddings()` à chaque ligne de notre corpus. Créons donc une nouvelle colonne `embeddings` comme suit : - -```py -embeddings_dataset = comments_dataset.map( - lambda x: {"embeddings": get_embeddings(x["text"]).numpy()[0]} -) -``` - -{/if} - - -Notez que nous avons converti les enchâssements en tableaux NumPy. C'est parce que 🤗 *Datasets* nécessite ce format lorsque nous essayons de les indexer avec FAISS, ce que nous ferons ensuite. - -## Utilisation de FAISS pour une recherche de similarité efficace - -Maintenant que nous avons un jeu de données d'incorporations, nous avons besoin d'un moyen de les rechercher. Pour ce faire, nous utiliserons une structure de données spéciale dans 🤗 *Datasets* appelée _FAISS index_. [FAISS](https://faiss.ai/) (abréviation de Facebook AI Similarity Search) est une bibliothèque qui fournit des algorithmes efficaces pour rechercher et regrouper rapidement des vecteurs d'intégration. - -L'idée de base derrière FAISS est de créer une structure de données spéciale appelée un _index_ qui permet de trouver quels plongements sont similaires à un plongement d'entrée. Créer un index FAISS dans 🤗 *Datasets* est simple -- nous utilisons la fonction `Dataset.add_faiss_index()` et spécifions quelle colonne de notre jeu de données nous aimerions indexer : - -```py -embeddings_dataset.add_faiss_index(column="embeddings") -``` - -Nous pouvons maintenant effectuer des requêtes sur cet index en effectuant une recherche des voisins les plus proches avec la fonction `Dataset.get_nearest_examples()`. Testons cela en enchâssant d'abord une question comme suit : - -{#if fw === 'pt'} - -```py -question = "How can I load a dataset offline?" -question_embedding = get_embeddings([question]).cpu().detach().numpy() -question_embedding.shape -``` - -```python out -torch.Size([1, 768]) -``` - -{:else} - -```py -question = "How can I load a dataset offline?" -question_embedding = get_embeddings([question]).numpy() -question_embedding.shape -``` - -```python out -(1, 768) -``` - -{/if} - -Tout comme avec les documents, nous avons maintenant un vecteur de 768 dimensions représentant la requête. Nous pouvons le comparer à l’ensemble du corpus pour trouver les enchâssements les plus similaires : - -```py -scores, samples = embeddings_dataset.get_nearest_examples( - "embeddings", question_embedding, k=5 -) -``` - -La fonction `Dataset.get_nearest_examples()` renvoie un *tuple* de scores qui classent le chevauchement entre la requête et le document, et un jeu correspondant d'échantillons (ici, les 5 meilleures correspondances). Collectons-les dans un `pandas.DataFrame` afin de pouvoir les trier facilement : - -```py -import pandas as pd - -samples_df = pd.DataFrame.from_dict(samples) -samples_df["scores"] = scores -samples_df.sort_values("scores", ascending=False, inplace=True) -``` - -Nous pouvons maintenant parcourir les premières lignes pour voir dans quelle mesure notre requête correspond aux commentaires disponibles : - -```py -for _, row in samples_df.iterrows(): - print(f"COMMENT: {row.comments}") - print(f"SCORE: {row.scores}") - print(f"TITLE: {row.title}") - print(f"URL: {row.html_url}") - print("=" * 50) - print() -``` - -```python out -""" -COMMENT: Requiring online connection is a deal breaker in some cases unfortunately so it'd be great if offline mode is added similar to how `transformers` loads models offline fine. - -@mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like? -SCORE: 25.505046844482422 -TITLE: Discussion using datasets in offline mode -URL: https://github.com/huggingface/datasets/issues/824 -================================================== - -COMMENT: The local dataset builders (csv, text , json and pandas) are now part of the `datasets` package since #1726 :) -You can now use them offline -\`\`\`python -datasets = load_dataset("text", data_files=data_files) -\`\`\` - -We'll do a new release soon -SCORE: 24.555509567260742 -TITLE: Discussion using datasets in offline mode -URL: https://github.com/huggingface/datasets/issues/824 -================================================== - -COMMENT: I opened a PR that allows to reload modules that have already been loaded once even if there's no internet. - -Let me know if you know other ways that can make the offline mode experience better. I'd be happy to add them :) - -I already note the "freeze" modules option, to prevent local modules updates. It would be a cool feature. - ----------- - -> @mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like? - -Indeed `load_dataset` allows to load remote dataset script (squad, glue, etc.) but also you own local ones. -For example if you have a dataset script at `./my_dataset/my_dataset.py` then you can do -\`\`\`python -load_dataset("./my_dataset") -\`\`\` -and the dataset script will generate your dataset once and for all. - ----------- - -About I'm looking into having `csv`, `json`, `text`, `pandas` dataset builders already included in the `datasets` package, so that they are available offline by default, as opposed to the other datasets that require the script to be downloaded. -cf #1724 -SCORE: 24.14896583557129 -TITLE: Discussion using datasets in offline mode -URL: https://github.com/huggingface/datasets/issues/824 -================================================== - -COMMENT: > here is my way to load a dataset offline, but it **requires** an online machine -> -> 1. (online machine) -> -> ``` -> -> import datasets -> -> data = datasets.load_dataset(...) -> -> data.save_to_disk(/YOUR/DATASET/DIR) -> -> ``` -> -> 2. copy the dir from online to the offline machine -> -> 3. (offline machine) -> -> ``` -> -> import datasets -> -> data = datasets.load_from_disk(/SAVED/DATA/DIR) -> -> ``` -> -> -> -> HTH. - - -SCORE: 22.893993377685547 -TITLE: Discussion using datasets in offline mode -URL: https://github.com/huggingface/datasets/issues/824 -================================================== - -COMMENT: here is my way to load a dataset offline, but it **requires** an online machine -1. (online machine) -\`\`\` -import datasets -data = datasets.load_dataset(...) -data.save_to_disk(/YOUR/DATASET/DIR) -\`\`\` -2. copy the dir from online to the offline machine -3. (offline machine) -\`\`\` -import datasets -data = datasets.load_from_disk(/SAVED/DATA/DIR) -\`\`\` - -HTH. -SCORE: 22.406635284423828 -TITLE: Discussion using datasets in offline mode -URL: https://github.com/huggingface/datasets/issues/824 -================================================== -""" -``` - -Pas mal ! Notre deuxième résultat semble correspondre à la requête. - - - -✏️ **Essayez !** Créez votre propre requête et voyez si vous pouvez trouver une réponse dans les documents récupérés. Vous devrez peut-être augmenter le paramètre `k` dans `Dataset.get_nearest_examples()` pour élargir la recherche. - - + + +# Recherche sémantique avec FAISS + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Dans [section 5](/course/fr/chapter5/5), nous avons créé un jeu de données de problèmes et de commentaires GitHub à partir du dépôt 🤗 *Datasets*. Dans cette section, nous utilisons ces informations pour créer un moteur de recherche qui peut nous aider à trouver des réponses à nos questions les plus urgentes sur la bibliothèque ! + + + +## Utilisation des enchâssements pour la recherche sémantique + +Comme nous l'avons vu dans le [chapitre 1](/course/fr/chapter1), les modèles de langage basés sur les *transformers* représentent chaque *token* dans une étendue de texte sous la forme d'un _enchâssement_. Il s'avère que l'on peut regrouper les enchâssements individuels pour créer une représentation vectorielle pour des phrases entières, des paragraphes ou (dans certains cas) des documents. Ces enchâssements peuvent ensuite être utilisés pour trouver des documents similaires dans le corpus en calculant la similarité du produit scalaire (ou une autre métrique de similarité) entre chaque enchâssement et en renvoyant les documents avec le plus grand chevauchement. + +Dans cette section, nous utilisons les enchâssements pour développer un moteur de recherche sémantique. Ces moteurs de recherche offrent plusieurs avantages par rapport aux approches conventionnelles basées sur la correspondance des mots-clés dans une requête avec les documents. + +
+Recherche sémantique. + +
+ +## Chargement et préparation du jeu de données + +La première chose que nous devons faire est de télécharger notre jeu de données de problèmes GitHub. Utilisons la bibliothèque 🤗 *Hub* pour résoudre l'URL où notre fichier est stocké sur le *Hub* d’Hugging Face : + +```py +from huggingface_hub import hf_hub_url + +data_files = hf_hub_url( + repo_id="lewtun/github-issues", + filename="datasets-issues-with-comments.jsonl", + repo_type="dataset", +) +``` + +Avec l'URL stocké dans `data_files`, nous pouvons ensuite charger le jeu de données distant en utilisant la méthode introduite dans [section 2](/course/fr/chapter5/2) : + +```py +from datasets import load_dataset + +issues_dataset = load_dataset("json", data_files=data_files, split="train") +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 2855 +}) +``` + +Ici, nous avons spécifié l’échantillon `train` par défaut dans `load_dataset()`, de sorte que cela renvoie un `Dataset` au lieu d'un `DatasetDict`. La première chose à faire est de filtrer les *pull requests* car celles-ci ont tendance à être rarement utilisées pour répondre aux requêtes des utilisateurs et introduiront du bruit dans notre moteur de recherche. Comme cela devrait être familier maintenant, nous pouvons utiliser la fonction `Dataset.filter()` pour exclure ces lignes de notre jeu de données. Pendant que nous y sommes, filtrons également les lignes sans commentaires, car celles-ci ne fournissent aucune réponse aux requêtes des utilisateurs : + +```py +issues_dataset = issues_dataset.filter( + lambda x: (x["is_pull_request"] == False and len(x["comments"]) > 0) +) +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 771 +}) +``` + +Nous pouvons voir qu'il y a beaucoup de colonnes dans notre jeu de données, dont la plupart n'ont pas besoin de construire notre moteur de recherche. Du point de vue de la recherche, les colonnes les plus informatives sont `title`, `body` et `comments`, tandis que `html_url` nous fournit un lien vers le problème source. Utilisons la fonction `Dataset.remove_columns()` pour supprimer le reste : + +```py +columns = issues_dataset.column_names +columns_to_keep = ["title", "body", "html_url", "comments"] +columns_to_remove = set(columns_to_keep).symmetric_difference(columns) +issues_dataset = issues_dataset.remove_columns(columns_to_remove) +issues_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body'], + num_rows: 771 +}) +``` + +Pour créer nos enchâssements, nous ajoutons à chaque commentaire le titre et le corps du problème, car ces champs contiennent des informations contextuelles utiles. Étant donné que notre colonne `comments` est actuellement une liste de commentaires pour chaque problème, nous devons « éclater » la colonne afin que chaque ligne se compose d'un *tuple* `(html_url, title, body, comment)`. Dans Pandas, nous pouvons le faire avec la fonction [`DataFrame.explode()`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html), qui crée une nouvelle ligne pour chaque élément dans une colonne de type liste, tout en répliquant toutes les autres valeurs de colonne. Pour voir cela en action, passons d'abord au format `DataFrame` de Pandas : + +```py +issues_dataset.set_format("pandas") +df = issues_dataset[:] +``` + +Si nous inspectons la première ligne de ce `DataFrame`, nous pouvons voir qu'il y a quatre commentaires associés à ce problème : + +```py +df["comments"][0].tolist() +``` + +```python out +['the bug code locate in :\r\n if data_args.task_name is not None:\r\n # Downloading and loading a dataset from the hub.\r\n datasets = load_dataset("glue", data_args.task_name, cache_dir=model_args.cache_dir)', + 'Hi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com\r\n\r\nNormally, it should work if you wait a little and then retry.\r\n\r\nCould you please confirm if the problem persists?', + 'cannot connect,even by Web browser,please check that there is some problems。', + 'I can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...'] +``` + +Lorsque nous décomposons `df`, nous nous attendons à obtenir une ligne pour chacun de ces commentaires. Vérifions si c'est le cas : + +```py +comments_df = df.explode("comments", ignore_index=True) +comments_df.head(4) +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
html_urltitlecommentsbody
0https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comthe bug code locate in :\r\n if data_args.task_name is not None...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
1https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comHi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
2https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comcannot connect,even by Web browser,please check that there is some problems。Hello,\r\nI am trying to run run_glue.py and it gives me this error...
3https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comI can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
+ +Génial, nous pouvons voir que les lignes ont été répliquées, avec la colonne `comments` contenant les commentaires individuels ! Maintenant que nous en avons fini avec Pandas, nous pouvons rapidement revenir à un `Dataset` en chargeant le `DataFrame` en mémoire : + +```py +from datasets import Dataset + +comments_dataset = Dataset.from_pandas(comments_df) +comments_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body'], + num_rows: 2842 +}) +``` + +D'accord, cela nous a donné quelques milliers de commentaires avec lesquels travailler ! + + + + +✏️ **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. + + + +Maintenant que nous avons un commentaire par ligne, créons une nouvelle colonne `comments_length` contenant le nombre de mots par commentaire : + +```py +comments_dataset = comments_dataset.map( + lambda x: {"comment_length": len(x["comments"].split())} +) +``` + +Nous pouvons utiliser cette nouvelle colonne pour filtrer les commentaires courts incluant généralement des éléments tels que « cc @lewtun » ou « Merci ! » qui ne sont pas pertinents pour notre moteur de recherche. Il n'y a pas de nombre précis à sélectionner pour le filtre mais 15 mots semblent être un bon début : + +```py +comments_dataset = comments_dataset.filter(lambda x: x["comment_length"] > 15) +comments_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body', 'comment_length'], + num_rows: 2098 +}) +``` + +Après avoir un peu nettoyé notre jeu de données, concaténons le titre, la description et les commentaires du problème dans une nouvelle colonne `text`. Comme d'habitude, nous allons écrire une fonction simple que nous pouvons passer à `Dataset.map()` : + +```py +def concatenate_text(examples): + return { + "text": examples["title"] + + " \n " + + examples["body"] + + " \n " + + examples["comments"] + } + + +comments_dataset = comments_dataset.map(concatenate_text) +``` + +Nous sommes enfin prêts à créer des enchâssements ! Jetons un coup d'œil. + +## Création d’enchâssements pour les textes + +Nous avons vu dans [chapitre 2](/course/fr/chapter2) que nous pouvons obtenir des enchâssements de *tokens* en utilisant la classe `AutoModel`. Tout ce que nous avons à faire est de choisir un *checkpoint* approprié à partir duquel charger le modèle. Heureusement, il existe une bibliothèque appelée `sentence-transformers` dédiée à la création d’enchâssements. Comme décrit dans la [documentation de la bibliothèque](https://www.sbert.net/examples/applications/semantic-search/README.html#symmetric-vs-asymmetric-semantic-search), notre cas d'utilisation est un exemple de _recherche sémantique asymétrique_. En effet, nous avons une requête courte dont nous aimerions trouver la réponse dans un document plus long, par exemple un commentaire à un problème. Le [tableau de présentation des modèles](https://www.sbert.net/docs/pretrained_models.html#model-overview) de la documentation indique que le *checkpoint* `multi-qa-mpnet-base-dot-v1` a les meilleures performances pour la recherche sémantique. Utilisons donc le pour notre application. Nous allons également charger le *tokenizer* en utilisant le même *checkpoint* : + +{#if fw === 'pt'} + +```py +from transformers import AutoTokenizer, AutoModel + +model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1" +tokenizer = AutoTokenizer.from_pretrained(model_ckpt) +model = AutoModel.from_pretrained(model_ckpt) +``` + +Pour accélérer le processus, il est utile de placer le modèle et les entrées sur un périphérique GPU, alors faisons-le maintenant : + +```py +import torch + +device = torch.device("cuda") +model.to(device) +``` + +{:else} + +```py +from transformers import AutoTokenizer, TFAutoModel + +model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1" +tokenizer = AutoTokenizer.from_pretrained(model_ckpt) +model = TFAutoModel.from_pretrained(model_ckpt, from_pt=True) +``` + +Notez que nous avons défini `from_pt=True` comme argument de la méthode `from_pretrained()`. C'est parce que le point de contrôle `multi-qa-mpnet-base-dot-v1` n'a que des poids PyTorch. Donc définir `from_pt=True` converti automatiquement au format TensorFlow pour nous. Comme vous pouvez le voir, il est très simple de passer d'un *framework* à l'autre dans 🤗 *Transformers* ! + +{/if} + +Comme nous l'avons mentionné précédemment, nous aimerions représenter chaque entrée dans notre corpus de problèmes GitHub comme un vecteur unique. Nous devons donc regrouper ou faire la moyenne de nos enchâssements de *tokens* d'une manière ou d'une autre. Une approche populaire consiste à effectuer un *regroupement CLS* sur les sorties de notre modèle, où nous collectons simplement le dernier état caché pour le *token* spécial `[CLS]`. La fonction suivante fait ça pour nous : + +```py +def cls_pooling(model_output): + return model_output.last_hidden_state[:, 0] +``` + +Ensuite, nous allons créer une fonction utile qui va tokeniser une liste de documents, placer les tenseurs dans le GPU, les donner au modèle et enfin appliquer le regroupement CLS aux sorties : + +{#if fw === 'pt'} + +```py +def get_embeddings(text_list): + encoded_input = tokenizer( + text_list, padding=True, truncation=True, return_tensors="pt" + ) + encoded_input = {k: v.to(device) for k, v in encoded_input.items()} + model_output = model(**encoded_input) + return cls_pooling(model_output) +``` + +Nous pouvons tester le fonctionnement de la fonction en lui donnant la première entrée textuelle de notre corpus et en inspectant la forme de sortie : + +```py +embedding = get_embeddings(comments_dataset["text"][0]) +embedding.shape +``` + +```python out +torch.Size([1, 768]) +``` + +Super ! Nous avons converti la première entrée de notre corpus en un vecteur à 768 dimensions. Nous pouvons utiliser `Dataset.map()` pour appliquer notre fonction `get_embeddings()` à chaque ligne de notre corpus. Créons donc une nouvelle colonne `embeddings` comme suit : + +```py +embeddings_dataset = comments_dataset.map( + lambda x: {"embeddings": get_embeddings(x["text"]).detach().cpu().numpy()[0]} +) +``` + +{:else} + +```py +def get_embeddings(text_list): + encoded_input = tokenizer( + text_list, padding=True, truncation=True, return_tensors="tf" + ) + encoded_input = {k: v for k, v in encoded_input.items()} + model_output = model(**encoded_input) + return cls_pooling(model_output) +``` + +Nous pouvons tester le fonctionnement de la fonction en lui donnant la première entrée textuelle de notre corpus et en inspectant la forme de sortie : + +```py +embedding = get_embeddings(comments_dataset["text"][0]) +embedding.shape +``` + +```python out +TensorShape([1, 768]) +``` + +Super ! Nous avons converti la première entrée de notre corpus en un vecteur à 768 dimensions. Nous pouvons utiliser `Dataset.map()` pour appliquer notre fonction `get_embeddings()` à chaque ligne de notre corpus. Créons donc une nouvelle colonne `embeddings` comme suit : + +```py +embeddings_dataset = comments_dataset.map( + lambda x: {"embeddings": get_embeddings(x["text"]).numpy()[0]} +) +``` + +{/if} + + +Notez que nous avons converti les enchâssements en tableaux NumPy. C'est parce que 🤗 *Datasets* nécessite ce format lorsque nous essayons de les indexer avec FAISS, ce que nous ferons ensuite. + +## Utilisation de FAISS pour une recherche de similarité efficace + +Maintenant que nous avons un jeu de données d'incorporations, nous avons besoin d'un moyen de les rechercher. Pour ce faire, nous utiliserons une structure de données spéciale dans 🤗 *Datasets* appelée _FAISS index_. [FAISS](https://faiss.ai/) (abréviation de *Facebook AI Similarity Search*) est une bibliothèque qui fournit des algorithmes efficaces pour rechercher et regrouper rapidement des vecteurs d'intégration. + +L'idée de base derrière FAISS est de créer une structure de données spéciale appelée un _index_ qui permet de trouver quels plongements sont similaires à un plongement d'entrée. Créer un index FAISS dans 🤗 *Datasets* est simple -- nous utilisons la fonction `Dataset.add_faiss_index()` et spécifions quelle colonne de notre jeu de données nous aimerions indexer : + +```py +embeddings_dataset.add_faiss_index(column="embeddings") +``` + +Nous pouvons maintenant effectuer des requêtes sur cet index en effectuant une recherche des voisins les plus proches avec la fonction `Dataset.get_nearest_examples()`. Testons cela en enchâssant d'abord une question comme suit : + +{#if fw === 'pt'} + +```py +question = "How can I load a dataset offline?" +question_embedding = get_embeddings([question]).cpu().detach().numpy() +question_embedding.shape +``` + +```python out +torch.Size([1, 768]) +``` + +{:else} + +```py +question = "How can I load a dataset offline?" +question_embedding = get_embeddings([question]).numpy() +question_embedding.shape +``` + +```python out +(1, 768) +``` + +{/if} + +Tout comme avec les documents, nous avons maintenant un vecteur de 768 dimensions représentant la requête. Nous pouvons le comparer à l’ensemble du corpus pour trouver les enchâssements les plus similaires : + +```py +scores, samples = embeddings_dataset.get_nearest_examples( + "embeddings", question_embedding, k=5 +) +``` + +La fonction `Dataset.get_nearest_examples()` renvoie un *tuple* de scores qui classent le chevauchement entre la requête et le document, et un jeu correspondant d'échantillons (ici, les 5 meilleures correspondances). Collectons-les dans un `pandas.DataFrame` afin de pouvoir les trier facilement : + +```py +import pandas as pd + +samples_df = pd.DataFrame.from_dict(samples) +samples_df["scores"] = scores +samples_df.sort_values("scores", ascending=False, inplace=True) +``` + +Nous pouvons maintenant parcourir les premières lignes pour voir dans quelle mesure notre requête correspond aux commentaires disponibles : + +```py +for _, row in samples_df.iterrows(): + print(f"COMMENT: {row.comments}") + print(f"SCORE: {row.scores}") + print(f"TITLE: {row.title}") + print(f"URL: {row.html_url}") + print("=" * 50) + print() +``` + +```python out +""" +COMMENT: Requiring online connection is a deal breaker in some cases unfortunately so it'd be great if offline mode is added similar to how `transformers` loads models offline fine. + +@mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like? +SCORE: 25.505046844482422 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: The local dataset builders (csv, text , json and pandas) are now part of the `datasets` package since #1726 :) +You can now use them offline +\`\`\`python +datasets = load_dataset("text", data_files=data_files) +\`\`\` + +We'll do a new release soon +SCORE: 24.555509567260742 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: I opened a PR that allows to reload modules that have already been loaded once even if there's no internet. + +Let me know if you know other ways that can make the offline mode experience better. I'd be happy to add them :) + +I already note the "freeze" modules option, to prevent local modules updates. It would be a cool feature. + +---------- + +> @mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like? + +Indeed `load_dataset` allows to load remote dataset script (squad, glue, etc.) but also you own local ones. +For example if you have a dataset script at `./my_dataset/my_dataset.py` then you can do +\`\`\`python +load_dataset("./my_dataset") +\`\`\` +and the dataset script will generate your dataset once and for all. + +---------- + +About I'm looking into having `csv`, `json`, `text`, `pandas` dataset builders already included in the `datasets` package, so that they are available offline by default, as opposed to the other datasets that require the script to be downloaded. +cf #1724 +SCORE: 24.14896583557129 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: > here is my way to load a dataset offline, but it **requires** an online machine +> +> 1. (online machine) +> +> ``` +> +> import datasets +> +> data = datasets.load_dataset(...) +> +> data.save_to_disk(/YOUR/DATASET/DIR) +> +> ``` +> +> 2. copy the dir from online to the offline machine +> +> 3. (offline machine) +> +> ``` +> +> import datasets +> +> data = datasets.load_from_disk(/SAVED/DATA/DIR) +> +> ``` +> +> +> +> HTH. + + +SCORE: 22.893993377685547 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: here is my way to load a dataset offline, but it **requires** an online machine +1. (online machine) +\`\`\` +import datasets +data = datasets.load_dataset(...) +data.save_to_disk(/YOUR/DATASET/DIR) +\`\`\` +2. copy the dir from online to the offline machine +3. (offline machine) +\`\`\` +import datasets +data = datasets.load_from_disk(/SAVED/DATA/DIR) +\`\`\` + +HTH. +SCORE: 22.406635284423828 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== +""" +``` + +Pas mal ! Notre deuxième résultat semble correspondre à la requête. + + + +✏️ **Essayez !** Créez votre propre requête et voyez si vous pouvez trouver une réponse dans les documents récupérés. Vous devrez peut-être augmenter le paramètre `k` dans `Dataset.get_nearest_examples()` pour élargir la recherche. + + diff --git a/chapters/fr/chapter5/7.mdx b/chapters/fr/chapter5/7.mdx index 6e1bc43ce..55083fffa 100644 --- a/chapters/fr/chapter5/7.mdx +++ b/chapters/fr/chapter5/7.mdx @@ -1,10 +1,10 @@ -# 🤗 *Datasets*, vérifié ! - -Eh bien, ce fut une sacrée visite de la bibliothèque 🤗 *Datasets*. Félicitations d’être arrivé jusqu'ici ! Avec les connaissances que vous avez acquises dans ce chapitre, vous devriez être en mesure de : -- charger des jeux de données depuis n'importe où, que ce soit le *Hub* d’Hugging Face, votre ordinateur portable ou un serveur distant de votre entreprise. -- manipuler vos données en utilisant un mélange des fonctions Dataset.map() et Dataset.filter(). -- passer rapidement d'un format de données à un autre, comme Pandas et NumPy, en utilisant Dataset.set_format(). -- créer votre propre jeu de données et l’envoyer vers le *Hub*. -- enchâsser vos documents en utilisant un *transformer* et construire un moteur de recherche sémantique en utilisant FAISS. - -Dans le [Chapter 7](/course/fr/chapter7), nous mettrons tout cela à profit en plongeant dans les tâches de traitement du langage naturel de base pour lesquelles les *transformers* sont parfaits. Avant cela mettez vos connaissances sur la librairie 🤗 *Datasets* à l'épreuve avec un petit quiz ! +# 🤗 Datasets, coché ! + +Eh bien, ce fut une sacrée visite de la bibliothèque 🤗 *Datasets*. Félicitations d’être arrivé jusqu'ici ! Avec les connaissances que vous avez acquises dans ce chapitre, vous devriez être en mesure de : +- charger des jeux de données depuis n'importe où, que ce soit le *Hub* d’Hugging Face, votre ordinateur portable ou un serveur distant de votre entreprise, +- manipuler vos données en utilisant un mélange des fonctions Dataset.map() et Dataset.filter(), +- passer rapidement d'un format de données à un autre, comme Pandas et NumPy, en utilisant Dataset.set_format(), +- créer votre propre jeu de données et l’envoyer vers le *Hub*, +- enchâsser vos documents en utilisant un *transformer* et construire un moteur de recherche sémantique en utilisant FAISS. + +Dans le [chapitre 7](/course/fr/chapter7), nous mettrons tout cela à profit en plongeant dans les tâches de traitement du langage naturel de base pour lesquelles les *transformers* sont parfaits. Avant cela mettez vos connaissances sur la librairie 🤗 *Datasets* à l'épreuve avec un petit quiz ! diff --git a/chapters/fr/chapter5/8.mdx b/chapters/fr/chapter5/8.mdx index 5ea12fd8e..54f4e770f 100644 --- a/chapters/fr/chapter5/8.mdx +++ b/chapters/fr/chapter5/8.mdx @@ -1,226 +1,226 @@ - - -# Quiz de fin de chapitre - -Ce chapitre a couvert beaucoup de terrain ! Ne vous inquiétez pas si vous n'avez pas saisi tous les détails, les chapitres suivants vous aideront à comprendre comment les choses fonctionnent sous le capot. - -Avant de poursuivre, testons ce que vous avez appris dans ce chapitre. - -### 1. La fonction `load_dataset()` dans 🤗 *Datasets* vous permet de charger un jeu de données depuis lequel des emplacements suivants ? - -data_files de load_dataset() pour charger les jeux de données locaux.", - correct: true - }, - { - text: "Le Hub d’Hugging Face.", - explain: "Correct ! Vous pouvez charger des jeux de données sur le Hub> en fournissant l'ID du jeu de données. Par exemple : load_dataset('emotion').", - correct: true - }, - { - text: "Un serveur distant.", - explain: "Correct ! Vous pouvez passer des URLs à l'argument data_files de load_dataset() pour charger des fichiers distants.", - correct: true - }, - ]} -/> - -### 2. Supposons que vous chargiez l'une des tâches du jeu de données GLUE comme suit : - -```py -from datasets import load_dataset - -dataset = load_dataset("glue", "mrpc", split="train") -``` - -Laquelle des commandes suivantes produira un échantillon aléatoire de 50 éléments à partir de `dataset` ? - -dataset.sample(50)", - explain: "Ceci est incorrect, il n'y a pas de méthode Dataset.sample()." - }, - { - text: "dataset.shuffle().select(range(50))", - explain: "Correct ! Comme vous l'avez vu dans ce chapitre, vous mélangez d'abord le jeu de données puis sélectionnez les échantillons à partir de celui-ci.", - correct: true - }, - { - text: "dataset.select(range(50)).shuffle()", - explain: "Ceci est incorrect. Bien que le code s'exécute, il ne mélange que les 50 premiers éléments du jeu de données." - } - ]} -/> - -### 3. Supposons que vous disposiez d'un jeu de données sur les animaux domestiques appelé `pets_dataset` qui comporte une colonne `name` indiquant le nom de chaque animal. Parmi les approches suivantes, laquelle vous permettrait de filtrer le jeu de données pour tous les animaux dont le nom commence par la lettre « L » ? - -pets_dataset.filter(lambda x : x['name'].startswith('L'))", - explain: "Correct ! L'utilisation d'une fonction Python lambda pour ces filtres rapides est une excellente idée. Pouvez-vous penser à une autre solution ?", - correct: true - }, - { - text: "pets_dataset.filter(lambda x['name'].startswith('L'))", - explain: "Ceci est incorrect. Une fonction lambda prend la forme générale lambda *arguments* : *expression*, vous devez donc fournir des arguments dans ce cas." - }, - { - text: "Créer une fonction comme def filter_names(x): return x['name'].startswith('L') et exécuter pets_dataset.filter(filter_names).", - explain: "Correct ! Tout comme avec Dataset.map(), vous pouvez passer des fonctions explicites à Dataset.filter(). Ceci est utile lorsque vous avez une logique complexe qui ne convient pas à une fonction lambda courte. Parmi les autres solutions, laquelle fonctionnerait ?", - correct: true - } - ]} -/> - -### 4. Qu'est-ce que le *memory mapping* ? - -mapping entre la RAM CPU et GPU.", - explain: "Ce n'est pas ça, réessayez !", - }, - { - text: "Un mappaging entre la RAM et le stockage du système de fichiers.", - explain: "Correct ! 🤗 Datasets traite chaque jeu de données comme un fichier mappé en mémoire. Cela permet à la bibliothèque d'accéder et d'opérer sur des éléments du jeu de données sans avoir à le charger complètement en mémoire.", - correct: true - }, - { - text: "Un mappaging entre deux fichiers dans le cache 🤗 Datasets.", - explain: "Ce n'est pas correct, réessayez !" - } - ]} -/> - -### 5. Parmi les éléments suivants, lesquels sont les principaux avantages du *memory mapping* ? - -Datasets d'être extrêmement rapide. Ce n'est cependant pas le seul avantage.", - correct: true - }, - { - text: "Les applications peuvent accéder à des segments de données dans un fichier extrêmement volumineux sans avoir à lire tout le fichier dans la RAM au préalable.", - explain: "Correct ! Cela permet à 🤗 Datasets de charger des jeux de données de plusieurs Go sur votre ordinateur portable sans faire exploser votre CPU. Quel autre avantage cette technique offre-t-elle ?", - correct: true - }, - { - text: "Cela consomme moins d'énergie, donc votre batterie dure plus longtemps.", - explain: "Ce n'est pas correct, réessayez !" - } - ]} -/> - -### 6. Pourquoi le code suivant échoue-t-il ? - -```py -from datasets import load_dataset - -dataset = load_dataset("allocine", streaming=True, split="train") -dataset[0] -``` - -IterableDataset.", - explain: "Correct! Un IterableDataset est un générateur, pas un conteneur. Vous devez donc accéder à ses éléments en utilisant next(iter(dataset)).", - correct: true - }, - { - text: "Le jeu de données allocine n'a pas d’échantillon train.", - explain: "Ceci est incorrect. Consultez la [fiche d’ allocine](https://huggingface.co/datasets/allocine) sur le Hub pour voir quels échantillons il contient." - } - ]} -/> - -### 7. Parmi les avantages suivants, lesquels sont les principaux pour la création d'une fiche pour les jeux de données ? - - - - -### 8. Qu'est-ce que la recherche sémantique ? - - - -### 9. Pour la recherche sémantique asymétrique, vous avez généralement : - - - -### 10. Puis-je utiliser 🤗 *Datasets* pour charger des données à utiliser dans d'autres domaines, comme le traitement de la parole ? - -Datasets prend actuellement en charge les données tabulaires, l'audio et la vision par ordinateur. Consultez le jeu de donnéesMNIST sur le Hub pour un exemple de vision par ordinateur." - }, - { - text: "Oui.", - explain: "Correct ! Découvrez les développements passionnants concernant la parole et la vision dans la bibliothèque 🤗 Transformers pour voir comment 🤗 Datasets est utilisé dans ces domaines.", - correct : true - }, - ]} -/> + + +# Quiz de fin de chapitre + +Ce chapitre a couvert beaucoup de terrain ! Ne vous inquiétez pas si vous n'avez pas saisi tous les détails, les chapitres suivants vous aideront à comprendre comment les choses fonctionnent sous le capot. + +Avant de poursuivre, testons ce que vous avez appris dans ce chapitre. + +### 1. La fonction `load_dataset()` dans 🤗 *Datasets* vous permet de charger un jeu de données depuis lequel des emplacements suivants ? + +data_files de load_dataset() pour charger les jeux de données locaux.", + correct: true + }, + { + text: "Le Hub d’Hugging Face.", + explain: "Vous pouvez charger des jeux de données sur le Hub en fournissant l'ID du jeu de données. Par exemple : load_dataset('emotion').", + correct: true + }, + { + text: "Un serveur distant.", + explain: "Vous pouvez passer des URLs à l'argument data_files de load_dataset() pour charger des fichiers distants.", + correct: true + }, + ]} +/> + +### 2. Supposons que vous chargiez l'une des tâches du jeu de données GLUE comme suit : + +```py +from datasets import load_dataset + +dataset = load_dataset("glue", "mrpc", split="train") +``` + +Laquelle des commandes suivantes produira un échantillon aléatoire de 50 éléments à partir de `dataset` ? + +dataset.sample(50)", + explain: "Il n'y a pas de méthode Dataset.sample()." + }, + { + text: "dataset.shuffle().select(range(50))", + explain: "Comme vous l'avez vu dans ce chapitre, vous mélangez d'abord le jeu de données puis sélectionnez les échantillons à partir de celui-ci.", + correct: true + }, + { + text: "dataset.select(range(50)).shuffle()", + explain: "Bien que le code s'exécute, il ne mélange que les 50 premiers éléments du jeu de données." + } + ]} +/> + +### 3. Supposons que vous disposiez d'un jeu de données sur les animaux domestiques appelé `pets_dataset` qui comporte une colonne `name` indiquant le nom de chaque animal. Parmi les approches suivantes, laquelle vous permettrait de filtrer le jeu de données pour tous les animaux dont le nom commence par la lettre « L » ? + +pets_dataset.filter(lambda x : x['name'].startswith('L'))", + explain: "L'utilisation d'une fonction Python lambda pour ces filtres rapides est une excellente idée. Pouvez-vous penser à une autre solution ?", + correct: true + }, + { + text: "pets_dataset.filter(lambda x['name'].startswith('L'))", + explain: "Une fonction lambda prend la forme générale lambda *arguments* : *expression*, vous devez donc fournir des arguments dans ce cas." + }, + { + text: "Créer une fonction comme def filter_names(x): return x['name'].startswith('L') et exécuter pets_dataset.filter(filter_names).", + explain: "Tout comme avec Dataset.map(), vous pouvez passer des fonctions explicites à Dataset.filter(). Ceci est utile lorsque vous avez une logique complexe qui ne convient pas à une fonction lambda courte. Parmi les autres solutions, laquelle fonctionnerait ?", + correct: true + } + ]} +/> + +### 4. Qu'est-ce que le *memory mapping* ? + +mapping entre la RAM CPU et GPU.", + explain: "Ce n'est pas ça, réessayez !", + }, + { + text: "Un mapping entre la RAM et le stockage du système de fichiers.", + explain: "🤗 Datasets traite chaque jeu de données comme un fichier mappé en mémoire. Cela permet à la bibliothèque d'accéder et d'opérer sur des éléments du jeu de données sans avoir à le charger complètement en mémoire.", + correct: true + }, + { + text: "Un mapping entre deux fichiers dans le cache 🤗 Datasets.", + explain: "Ce n'est pas ça, réessayez !" + } + ]} +/> + +### 5. Parmi les éléments suivants, lesquels sont les principaux avantages du *memory mapping* ? + +Datasets d'être extrêmement rapide. Ce n'est cependant pas le seul avantage.", + correct: true + }, + { + text: "Les applications peuvent accéder à des segments de données dans un fichier extrêmement volumineux sans avoir à lire tout le fichier dans la RAM au préalable.", + explain: "Cela permet à 🤗 Datasets de charger des jeux de données de plusieurs Go sur votre ordinateur portable sans faire exploser votre CPU. Quel autre avantage cette technique offre-t-elle ?", + correct: true + }, + { + text: "Cela consomme moins d'énergie, donc votre batterie dure plus longtemps.", + explain: "Ce n'est pas ça, réessayez !" + } + ]} +/> + +### 6. Pourquoi le code suivant échoue-t-il ? + +```py +from datasets import load_dataset + +dataset = load_dataset("allocine", streaming=True, split="train") +dataset[0] +``` + +IterableDataset.", + explain: "Un IterableDataset est un générateur, pas un conteneur. Vous devez donc accéder à ses éléments en utilisant next(iter(dataset)).", + correct: true + }, + { + text: "Le jeu de données allocine n'a pas d’échantillon train.", + explain: "Consultez le jeu de données allocine sur le Hub (https://huggingface.co/datasets/allocine) pour voir quels échantillons il contient." + } + ]} +/> + +### 7. Parmi les avantages suivants, lesquels sont les principaux pour la création d'une fiche pour les jeux de données ? + + + + +### 8. Qu'est-ce que la recherche sémantique ? + +recherche lexicale et c'est ce que vous voyez généralement avec les moteurs de recherche traditionnels." + }, + { + text: "Un moyen de rechercher des documents correspondants en comprenant la signification contextuelle d'une requête.", + explain: "La recherche sémantique utilise des vecteurs d’enchâssement pour représenter les requêtes et les documents. Elle utilise ensuite une métrique de similarité pour mesurer la quantité de chevauchement entre eux. Comment la décrire autrement ?", + correct: true + }, + { + text: "Un moyen d'améliorer la précision de la recherche.", + explain: "Les moteurs de recherche sémantique peuvent capturer l'intention d'une requête bien mieux que la correspondance des mots clés et récupèrent généralement les documents avec une plus grande précision. Mais ce n'est pas la seule bonne réponse. Qu'est-ce que la recherche sémantique apporte d'autre ?", + correct: true + } + ]} +/> + +### 9. Pour la recherche sémantique asymétrique, vous avez généralement : + + + +### 10. Puis-je utiliser 🤗 *Datasets* pour charger des données à utiliser dans d'autres domaines, comme le traitement de la parole ? + +Datasets prend actuellement en charge les données tabulaires, l'audio et la vision par ordinateur. Consultez le jeu de données MNIST sur le Hub pour un exemple de vision par ordinateur." + }, + { + text: "Oui.", + explain: "Découvrez les développements passionnants concernant la parole et la vision dans la bibliothèque 🤗 Transformers pour voir comment 🤗 Datasets est utilisé dans ces domaines.", + correct : true + }, + ]} +/> diff --git a/chapters/fr/chapter6/1.mdx b/chapters/fr/chapter6/1.mdx index 0950598c8..6869cc815 100644 --- a/chapters/fr/chapter6/1.mdx +++ b/chapters/fr/chapter6/1.mdx @@ -1,13 +1,13 @@ # Introduction -Dans le [Chapitre 3](/course/fr/chapter3), nous avons vu comment *finetuner* un modèle sur une tâche donnée. Pour ce faire, nous utilisons le même *tokenizer* que celui avec lequel le modèle a été pré-entraîné. Mais que faisons-nous lorsque nous voulons entraîner un modèle à partir de zéro ? Dans ces cas, l'utilisation d'un *tokenizer* qui a été pré-entraîné sur un corpus d'un autre domaine ou d'une autre langue est généralement sous-optimale. Par exemple, un *tokenizer* entraîné sur un corpus anglais sera peu performant sur un corpus de textes japonais car l'utilisation des espaces et de la ponctuation est très différente entre les deux langues. +Dans le [chapitre 3](/course/fr/chapter3), nous avons vu comment *finetuner* un modèle sur une tâche donnée. Pour ce faire, nous utilisons le même *tokenizer* que celui avec lequel le modèle a été pré-entraîné. Mais que faisons-nous lorsque nous voulons entraîner un modèle à partir de zéro ? Dans ces cas, l'utilisation d'un *tokenizer* qui a été pré-entraîné sur un corpus d'un autre domaine ou d'une autre langue est généralement sous-optimale. Par exemple, un *tokenizer* entraîné sur un corpus anglais sera peu performant sur un corpus de textes japonais car l'utilisation des espaces et de la ponctuation est très différente entre les deux langues. Dans ce chapitre, vous apprendrez à entraîner un tout nouveau *tokenizer* sur un corpus de textes afin qu'il puisse ensuite être utilisé pour pré-entraîner un modèle de langue. Tout cela se fera à l'aide de la bibliothèque [🤗 *Tokenizers*](https://github.com/huggingface/tokenizers), qui fournit les *tokenizers* « rapides » de la bibliothèque [🤗 *Transformers*](https://github.com/huggingface/transformers). Nous examinerons de près les fonctionnalités offertes par cette bibliothèque et nous étudierons comment les *tokenizers* rapides diffèrent des versions « lentes ». Les sujets que nous couvrirons comprennent : -* comment entraîner un nouveau *tokenizer* similaire à celui utilisé par un *checkpoint* donné sur un nouveau corpus de textes, +* comment entraîner sur un nouveau corpus de textes, un nouveau *tokenizer* similaire à celui utilisé par un *checkpoint* donné, * les caractéristiques spéciales des *tokenizers* rapides, * les différences entre les trois principaux algorithmes de tokénisation utilisés aujourd'hui en NLP, * comment construire un *tokenizer* à partir de zéro avec la bibliothèque 🤗 *Tokenizers* et l'entraîner sur des données. -Les techniques présentées dans ce chapitre vous prépareront à la section du [Chapitre 7](/course/fr/chapter7/6) où nous examinons la création d'un modèle de langue pour le langage Python. Commençons par examiner ce que signifie « entraîner » un *tokenizer*. \ No newline at end of file +Les techniques présentées dans ce chapitre vous prépareront à la section du [chapitre 7](/course/fr/chapter7/6) où nous verrons comment créer un modèle de langue pour le langage Python. Commençons par examiner ce que signifie « entraîner » un *tokenizer*. \ No newline at end of file diff --git a/chapters/fr/chapter6/10.mdx b/chapters/fr/chapter6/10.mdx index b7ef6c773..b4a6cdc89 100644 --- a/chapters/fr/chapter6/10.mdx +++ b/chapters/fr/chapter6/10.mdx @@ -4,31 +4,31 @@ Testons ce que vous avez appris dans ce chapitre ! -### 1. Quand devez-vous entraîner un nouveau *tokenizer* ? +### 1. Quand devez-vous entraîner un nouveau tokenizer ? tokenizer que le modèle pré-entraîné et de finetuner ce modèle à la place." + text: "Lorsque votre jeu de données est similaire à celui utilisé par un modèle pré-entraîné existant et que vous voulez pré-entraîner un nouveau modèle", + explain: "Dans ce cas, pour économiser du temps et des ressources de calcul, il est préférable d'utiliser le même tokenizer que le modèle pré-entraîné et de finetuner ce modèle à la place." }, { text: "Lorsque votre jeu de données est similaire à celui utilisé par un modèle pré-entraîné existant et que vous souhaitez finetuner un nouveau modèle en utilisant ce modèle pré-entraîné.", explain: "Pour finetuner un modèle à partir d'un modèle pré-entraîné, vous devez toujours utiliser le même tokenizer." }, { - text: "Lorsque votre jeu de données est différent de celui utilisé par un modèle pré-entraîné existant, et que vous souhaitez pré-entraîner un nouveau modèle.", - explain: "C'est exact ! Dans ce cas, il n'y a aucun avantage à utiliser le même tokenizer.", + text: "Lorsque votre jeu de données est différent de celui utilisé par un modèle pré-entraîné existant et que vous souhaitez pré-entraîner un nouveau modèle.", + explain: "Dans ce cas, il n'y a aucun avantage à utiliser le même tokenizer.", correct: true }, { - text: "Lorsque votre jeu de données est différent de celui utilisé par un modèle pré-entraîné existant, mais que vous souhaitez finetuner un nouveau modèle en utilisant ce modèle pré-entraîné.", + text: "Lorsque votre jeu de données est différent de celui utilisé par un modèle pré-entraîné existant mais que vous souhaitez finetuner un nouveau modèle en utilisant ce modèle pré-entraîné.", explain: "Pour finetuner un modèle à partir d'un modèle pré-entraîné, vous devez toujours utiliser le même tokenizer." } ]} /> -### 2. Quel est l'avantage d'utiliser un générateur de listes de textes par rapport à une liste de listes de textes lors de l'utilisation de `train_new_from_iterator()` ? +### 2. Quel est l'avantage d'utiliser un générateur de listes par rapport à une liste de listes lors de l'utilisation de train_new_from_iterator() ? Datasets pour stocker vos textes.", + explain: "Chaque batch de textes sera libéré de la mémoire lorsque vous itérerez et le gain sera particulièrement visible si vous utilisez des 🤗 Datasets pour stocker vos textes.", correct: true }, { text: "Cela permettra à la bibliothèque 🤗 Tokenizers d'utiliser le multitraitement.", - explain: "Non, il utilisera le multiprocesseur dans tous les cas." + explain: "Il utilisera le multiprocesseur dans tous les cas." }, { text: "Le tokenizer que vous entraînez générera de meilleurs textes.", @@ -52,13 +52,13 @@ Testons ce que vous avez appris dans ce chapitre ! ]} /> -### 3. Quels sont les avantages d'utiliser un *tokenizer* « rapide » ? +### 3. Quels sont les avantages d'utiliser un tokenizer « rapide » ? tokenizer lent lorsque vous faites des batchs d'entrées.", - explain: "Correct ! Grâce au parallélisme implémenté dans Rust, il sera plus rapide sur les batchs d'entrées. Quel autre avantage pouvez-vous imaginer ?", + explain: "Grâce au parallélisme implémenté dans Rust, il sera plus rapide sur les batchs d'entrées. Quel autre avantage pouvez-vous imaginer ?", correct: true }, { @@ -66,18 +66,18 @@ Testons ce que vous avez appris dans ce chapitre ! explain: "Un tokenizer rapide peut en fait être plus lent si vous ne lui donnez qu'un seul ou très peu de textes, car il ne peut pas utiliser le parallélisme." }, { - text: "Il peut appliquer le *padding* et la troncature.", - explain: "C'est vrai, mais les *tokenizers* lents le font aussi." + text: "Il peut appliquer le padding et la troncature.", + explain: "C'est vrai, mais les tokenizers lents le font aussi." }, { text: "Il possède des fonctionnalités supplémentaires qui vous permettent d'associer les tokens à l'extrait de texte qui les a créés.", - explain: "En effet, c'est ce qu'on appelle des mappages de décalage. Ce n'est pas le seul avantage, cependant.", + explain: "En effet, c'est ce qu'on appelle des correspondances d'offset. Ce n'est pas le seul avantage, cependant.", correct: true } ]} /> -### 4. Comment le pipeline `token-classification` gère-t-il les entités qui s'étendent sur plusieurs *tokens* ? +### 4. Comment le pipeline `token-classification` gère-t-il les entités qui s'étendent sur plusieurs tokens ? @@ -132,36 +132,36 @@ Testons ce que vous avez appris dans ce chapitre ! tokenizer effectue sur les textes lors des étapes initiales.", + explain: "Par exemple, il peut s'agir de supprimer les accents ou les espaces, ou de mettre les entrées en minuscules.", correct: true }, { - text: "Il s'agit d'une technique d'augmentation des données qui consiste à rendre le texte plus normal en supprimant les mots rares.", - explain: "C'est incorrect ! Essayez encore." + text: "Il s'agit d'une technique d'augmentation de données qui consiste à rendre le texte plus normal en supprimant les mots rares.", + explain: "Essayez encore." }, { - text: "C'est l'étape finale du post-traitement où le *tokenizer* ajoute les *tokens* spéciaux.", + text: "C'est l'étape finale du post-traitement où le tokenizer ajoute les tokens spéciaux.", explain: "Cette étape est simplement appelée post-traitement." }, { - text: "C'est lorsque les incorporations sont faites avec une moyenne de 0 et un écart-type de 1, en soustrayant la moyenne et en divisant par l'écart-type.", + text: "C'est lorsque les enchâssements sont faits avec une moyenne nulle et un écart-type de 1, en soustrayant la moyenne et en divisant par l'écart-type.", explain: "Ce processus est communément appelé normalisation lorsqu'il est appliqué aux valeurs des pixels en vision par ordinateur, mais ce n'est pas ce que signifie la normalisation en NLP." } ]} /> -### 7. Qu'est-ce que la pré-tokénisation pour un *tokenizer* en sous-mots ? +### 7. Qu'est-ce que la pré-tokénisation pour un tokenizer en sous-mots ? tokenizer, qui divise l'entrée en tokens.", + explain: "La division en tokens est le travail du modèle tokenizer." } ]} /> -### 8. Sélectionnez les phrases qui s'appliquent au *tokenizer* BPE. +### 8. Sélectionnez les phrases qui s'appliquent au tokenizer BPE. tokens.", - explain: "Non, c'est l'approche adoptée par un algorithme de tokénisation différent." + text: "BPE est un algorithme de tokénisation en sous-mots qui part d'un grand vocabulaire et en retire progressivement les tokens.", + explain: "C'est l'approche adoptée par un algorithme de tokénisation différent." }, { text: "Un tokenizer BPE apprend les règles de fusion en fusionnant la paire de tokens la plus fréquente.", @@ -195,84 +195,84 @@ Testons ce que vous avez appris dans ce chapitre ! }, { text: "Un tokenizer BPE apprend une règle de fusion en fusionnant la paire de tokens qui maximise un score qui privilégie les paires fréquentes avec des parties individuelles moins fréquentes.", - explain: "Non, c'est la stratégie appliquée par un autre algorithme de tokenization." + explain: "C'est la stratégie appliquée par un autre algorithme de tokenization." }, { text: "BPE tokenise les mots en sous-mots en les divisant en caractères, puis en appliquant les règles de fusion.", - explain: "C'est exact !", + explain: " ", correct: true }, { text: "BPE tokenise les mots en sous-mots en trouvant le plus long sous-mot du vocabulaire en commençant par le début, puis en répétant le processus pour le reste du texte.", - explain: "Non, c'est la façon de faire d'un autre algorithme de tokenization." + explain: "C'est la façon de faire d'un autre algorithme de tokenization." }, ]} /> -### 9. Sélectionnez les phrases qui s'appliquent au *tokenizer* WordPiece. +### 9. Sélectionnez les phrases qui s'appliquent au tokenizer WordPiece. tokens.", - explain: "Non, c'est l'approche adoptée par un algorithme de tokénisation différent." + text: "WordPiece est un algorithme de tokénisation en sous-mots qui part d'un grand vocabulaire et en retire progressivement les tokens.", + 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.", - explain: "Non, c'est la stratégie appliquée par un autre algorithme de tokenization." + explain: "C'est la façon de faire d'un autre algorithme de tokenization." }, { text: "Un tokenizer WordPiece apprend une règle de fusion en fusionnant la paire de tokens qui maximise un score qui privilégie les paires fréquentes avec des parties individuelles moins fréquentes.", - explain: "C'est exact !", + explain: " ", correct: true }, { text: "WordPiece tokenise les mots en sous-mots en trouvant la segmentation en tokens la plus probable, selon le modèle.", - explain: "Non, c'est le fonctionnement d'un autre algorithme de tokenization." + explain: "C'est la façon de faire d'un autre algorithme de tokenization." }, { text: "WordPiece tokenise les mots en sous-mots en trouvant le plus long sous-mot du vocabulaire en commençant par le début, puis en répétant le processus pour le reste du texte.", - explain: "Oui, c'est ainsi que WordPiece procède pour l'encodage.", + explain: "C'est ainsi que WordPiece procède pour l'encodage.", correct: true }, ]} /> -### 10. Sélectionnez les phrases qui s'appliquent au *tokenizer* Unigram. +### 10. Sélectionnez les phrases qui s'appliquent au tokenizer Unigram. tokens.", - explain: "C'est exact !", + text: "Unigram est un algorithme de tokénisation en sous-mots qui part d'un grand vocabulaire et en retire progressivement les tokens.", + explain: " ", correct: true }, { text: "Unigram adapte son vocabulaire en minimisant une perte calculée sur l'ensemble du corpus.", - explain: "C'est exact !", + explain: " ", correct: true }, { text: "Unigram adapte son vocabulaire en conservant les sous-mots les plus fréquents.", - explain: "Non, cet incorrect." + explain: " " }, { text: "Unigram segmente les mots en sous-mots en trouvant la segmentation la plus probable en tokens, selon le modèle.", - explain: "C'est exact !", + explain: " ", correct: true }, { text: "Unigram décompose les mots en sous-mots en les divisant en caractères puis en appliquant les règles de fusion.", - explain: "Non, c'est le fonctionnement d'un autre algorithme de tokenization." + explain: "C'est la façon de faire d'un autre algorithme de tokenization." }, ]} /> diff --git a/chapters/fr/chapter6/2.mdx b/chapters/fr/chapter6/2.mdx index 6168820ae..e4a23cc6e 100644 --- a/chapters/fr/chapter6/2.mdx +++ b/chapters/fr/chapter6/2.mdx @@ -1,4 +1,4 @@ -# Entraîner un nouveau *tokenizer* à partir d'un ancien +# Entraîner un nouveau tokenizer à partir d'un ancien -Si un modèle de langue n'est pas disponible dans la langue qui vous intéresse ou si votre corpus est très différent de celui sur lequel votre modèle de langue a été entraîné, vous voudrez très probablement réentraîner le modèle à partir de zéro en utilisant un *tokenizer* adapté à vos données. Pour ce faire, vous devrez entraîner un nouveau *tokenizer* sur votre ensemble de données. Mais qu'est-ce que cela signifie exactement ? Lorsque nous avons examiné pour la première fois les *tokenizers* dans le [Chapitre 2](/course/fr/chapter2), nous avons vu que la plupart des *transformers* utilisent un _algorithme de tokenisation des sous-mots_. Pour identifier les sous-mots qui sont intéressants et qui apparaissent le plus fréquemment dans le corpus en question, le *tokenizer* doit examiner attentivement tous les textes du corpus -- un processus que nous appelons *entraînement*. Les règles exactes qui régissent cet apprentissage dépendent du type de *tokenizer* utilisé, et nous passerons en revue les trois principaux algorithmes plus loin dans ce chapitre. +Si un modèle de langue n'est pas disponible dans la langue qui vous intéresse ou si votre corpus est très différent de celui sur lequel votre modèle de langue a été entraîné, vous voudrez très probablement réentraîner le modèle à partir de zéro en utilisant un *tokenizer* adapté à vos données. Pour ce faire, vous devrez entraîner un nouveau *tokenizer* sur votre jeu de données. Mais qu'est-ce que cela signifie exactement ? Lorsque nous avons examiné pour la première fois les *tokenizers* dans le [chapitre 2](/course/fr/chapter2), nous avons vu que la plupart des *transformers* utilisent un _algorithme de tokenisation en sous-mots_. Pour identifier les sous-mots qui sont intéressants et qui apparaissent le plus fréquemment dans un corpus donné, le *tokenizer* doit examiner attentivement tous les textes du corpus. C'est un processus que nous appelons *entraînement*. Les règles exactes qui régissent cet apprentissage dépendent du type de *tokenizer* utilisé. Nous passerons en revue les trois principaux algorithmes plus loin dans ce chapitre. -⚠️ Entraîner un *tokenizer* n'est pas la même chose qu'entraîner un modèle ! L'entraînement du modèle utilise la descente de gradient stochastique pour réduire un peu plus la perte à chaque batch. Il est aléatoire par nature (ce qui signifie que vous devez définir des graines pour obtenir les mêmes résultats lorsque vous effectuez deux fois le même entraînement). Entraîner un *tokenizer* est un processus statistique qui tente d'identifier les meilleurs sous-mots à choisir pour un corpus donné, et les règles exactes utilisées pour les choisir dépendent de l'algorithme de tokénisation. C'est un processus déterministe, ce qui signifie que vous obtenez toujours les mêmes résultats lorsque vous vous entraînez avec le même algorithme sur le même corpus. +⚠️ Entraîner un *tokenizer* n'est pas la même chose qu'entraîner un modèle ! L'entraînement du modèle utilise la descente de gradient stochastique pour réduire un peu plus la perte à chaque batch. Il est par nature aléatoire (ce qui signifie que vous devez définir des graines pour obtenir les mêmes résultats lorsque vous effectuez deux fois le même entraînement). Entraîner un *tokenizer* est un processus statistique qui identifie les meilleurs sous-mots à choisir pour un corpus donné. Les règles exactes utilisées pour les choisir dépendent de l'algorithme de tokénisation. Le processus est déterministe, ce qui signifie que vous obtenez toujours les mêmes résultats lorsque vous vous entraînez avec le même algorithme sur le même corpus. ## Assemblage d'un corpus -Il y a une API très simple dans 🤗 *Transformers* que vous pouvez utiliser pour entraîner un nouveau *tokenizer* avec les mêmes caractéristiques qu'un existant : `AutoTokenizer.train_new_from_iterator()`. Pour voir cela en action, disons que nous voulons entraîner GPT-2 à partir de zéro, mais dans une langue autre que l'anglais. Notre première tâche sera de rassembler des batchs de données dans cette langue dans un corpus d'entraînement. Pour fournir des exemples que tout le monde pourra comprendre, nous n'utiliserons pas ici une langue comme le russe ou le chinois, mais plutôt une langue anglaise spécialisée : le code Python. +Il y a une API très simple dans 🤗 *Transformers* que vous pouvez utiliser pour entraîner un nouveau *tokenizer* avec les mêmes caractéristiques qu'un déjà existant : `AutoTokenizer.train_new_from_iterator()`. Pour illustrer cela, disons que nous voulons entraîner GPT-2 à partir de zéro mais dans une langue autre que l'anglais. Notre première tâche est de rassembler des batchs de données dans cette langue dans un corpus d'entraînement. Pour avoir des exemples que tout le monde puisse comprendre, nous n'utiliserons pas ici une langue comme le russe ou le chinois mais plutôt une langue anglaise spécialisée : le langage Python. La bibliothèque [🤗 *Datasets*](https://github.com/huggingface/datasets) peut nous aider à assembler un corpus de code source Python. Nous allons utiliser la fonction habituelle `load_dataset()` pour télécharger et mettre en cache le jeu de données [CodeSearchNet](https://huggingface.co/datasets/code_search_net). Ce jeu de données a été créé pour le [CodeSearchNet challenge](https://wandb.ai/github/CodeSearchNet/benchmark) et contient des millions de fonctions provenant de bibliothèques open source sur GitHub dans plusieurs langages de programmation. Ici, nous allons charger la partie Python de ce jeu de données : ```py from datasets import load_dataset -# Le chargement peut prendre quelques minutes, alors prenez un café ou un thé pendant que vous attendez ! +# Cela peut prendre quelques minutes alors prenez un thé ou un café pendant que vous patientez ! raw_datasets = load_dataset("code_search_net", "python") ``` -Nous pouvons jeter un coup d'œil à la répartition dans le jeu d'entraînement pour voir à quelles colonnes nous avons accès : +Nous pouvons jeter un coup d'œil au jeu d'entraînement pour voir quelles sont les colonnes auxquelles nous avons accès : ```py raw_datasets["train"] @@ -47,13 +47,13 @@ Dataset({ }) ``` -Nous pouvons voir que le jeu de données sépare les chaînes de documents du code et suggère une tokenization des deux. Ici, nous utiliserons simplement la colonne `whole_func_string` pour entraîner notre *tokenizer*. Nous pouvons regarder un exemple d'une de ces fonctions en indexant dans le split `train` : +Nous pouvons voir que le jeu de données sépare les chaînes de documents du code et suggère une tokenization des deux. Ici, nous utiliserons simplement la colonne `whole_func_string` pour entraîner notre *tokenizer*. Nous pouvons regarder un exemple de la façon suivante : ```py print(raw_datasets["train"][123456]["whole_func_string"]) ``` -qui devrait afficher ce qui suit : +qui nous affiche ce qui suit : ```out def handle_simple_responses( @@ -70,9 +70,9 @@ def handle_simple_responses( return self._accept_responses('OKAY', info_cb, timeout_ms=timeout_ms) ``` -La première chose à faire est de transformer l'ensemble de données en un _itérateur_ de listes de textes -- par exemple, une liste de listes de textes. L'utilisation de listes de textes permettra à notre *tokenizer* d'aller plus vite (en s'entraînant sur des lots de textes au lieu de traiter des textes individuels un par un), et il doit s'agir d'un itérateur si nous voulons éviter d'avoir tout en mémoire en même temps. Si votre corpus est énorme, vous voudrez profiter du fait que 🤗 *Datasets* ne charge pas tout en RAM mais stocke les éléments du jeu de données sur le disque. +La première chose à faire est de transformer le jeu de données en un _itérateur_ de listes de textes. Par exemple, une liste de listes de textes. L'utilisation de listes de textes permet à notre *tokenizer* d'aller plus vite (l'entraînement a alors lieu sur des batchs de textes au lieu de traiter des textes un par un). Et le fait que ce soit un itérateur permet d'éviter d'avoir tout en mémoire en même temps. Si votre corpus est énorme, vous voudrez profiter du fait que 🤗 *Datasets* ne charge pas tout en RAM mais stocke les éléments du jeu de données sur le disque. -Faire ce qui suit créerait une liste de listes de 1 000 textes chacune, mais chargerait tout en mémoire : +Faire ce qui suit créerait une liste de listes de 1 000 textes chacune mais chargerait tout en mémoire : ```py @@ -80,7 +80,7 @@ Faire ce qui suit créerait une liste de listes de 1 000 textes chacune, mais ch # training_corpus = [raw_datasets["train"][i: i + 1000]["whole_func_string"] for i in range(0, len(raw_datasets["train"]), 1000)] ``` -En utilisant un générateur Python, nous pouvons éviter que Python ne charge quoi que ce soit en mémoire jusqu'à ce que cela soit réellement nécessaire. Pour créer un tel générateur, il suffit de remplacer les crochets par des parenthèses : +En utilisant un générateur, nous pouvons éviter que Python ne charge quoi que ce soit en mémoire à moins que cela soit réellement nécessaire. Pour créer un tel générateur, il suffit de remplacer les crochets par des parenthèses : ```py training_corpus = ( @@ -89,7 +89,7 @@ training_corpus = ( ) ``` -Cette ligne de code ne récupère aucun élément de l'ensemble de données ; elle crée simplement un objet que vous pouvez utiliser dans une boucle `for` Python. Les textes ne seront chargés que lorsque vous en aurez besoin (c'est-à-dire lorsque vous serez à l'étape de la boucle `for` qui les requiert), et seulement 1 000 textes à la fois seront chargés. De cette façon, vous n'épuiserez pas toute votre mémoire, même si vous traitez un énorme ensemble de données. +Cette ligne de code ne récupère aucun élément du jeu de données. Elle crée simplement un objet que vous pouvez utiliser dans une boucle `for` Python. Les textes ne seront chargés que lorsque vous en aurez besoin (c'est-à-dire lorsque vous serez à l'étape de la boucle `for` qui les requiert) et seulement 1 000 textes à la fois. De cette façon, vous n'épuiserez pas toute votre mémoire, même si vous traitez un énorme jeu de données. Le problème avec un objet générateur est qu'il ne peut être utilisé qu'une seule fois. Ainsi, au lieu que cet objet nous donne deux fois la liste des 10 premiers chiffres : @@ -130,11 +130,12 @@ def get_training_corpus(): yield samples["whole_func_string"] ``` -qui produira exactement le même générateur que précédemment, mais vous permet d'utiliser une logique plus complexe que celle que vous pouvez utiliser dans une compréhension de liste. +qui produit exactement le même générateur que précédemment mais permet d'utiliser une logique plus complexe que celle que vous pouvez utiliser dans une compréhension de liste. -## Entraînement d'un nouveau *tokenizer*. -Maintenant que nous avons notre corpus sous la forme d'un itérateur de lots de textes, nous sommes prêts à entraîner un nouveau *tokenizer*. Pour ce faire, nous devons d'abord charger le *tokenizer* que nous voulons coupler avec notre modèle (ici, GPT-2) : +## Entraînement d'un nouveau tokenizer + +Maintenant que nous avons notre corpus sous la forme d'un itérateur de batchs de textes, nous sommes prêts à entraîner un nouveau *tokenizer*. Pour ce faire, nous devons d'abord charger le *tokenizer* que nous voulons coupler avec notre modèle (ici, le GPT-2) : ```py @@ -143,7 +144,7 @@ from transformers import AutoTokenizer old_tokenizer = AutoTokenizer.from_pretrained("gpt2") ``` -Même si nous allons entraîner un nouveau *tokenizer*, c'est une bonne idée de le faire pour éviter de partir entièrement de zéro. De cette façon, nous n'aurons pas à spécifier l'algorithme de tokénisation ou les jetons spéciaux que nous voulons utiliser ; notre nouveau *tokenizer* sera exactement le même que GPT-2, et la seule chose qui changera sera le vocabulaire, qui sera déterminé par l'Entraînement sur notre corpus. +Même si nous allons entraîner un nouveau *tokenizer*, c'est une bonne idée de faire ça pour éviter de partir entièrement de zéro. De cette façon, nous n'aurons pas à spécifier l'algorithme de tokénisation ou les jetons spéciaux que nous voulons utiliser. Notre nouveau *tokenizer* sera exactement le même que celui du GPT-2. La seule chose qui changera sera le vocabulaire qui sera déterminé lors de l'entraînement sur notre corpus. Voyons d'abord comment ce *tokenizer* traiterait un exemple de fonction : @@ -162,7 +163,7 @@ tokens 'Ġnumbers', 'Ġ`', 'a', '`', 'Ġand', 'Ġ`', 'b', '`', '."', '""', 'Ċ', 'Ġ', 'Ġ', 'Ġ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb'] ``` -Ce *tokenizer* possède quelques symboles spéciaux, comme `Ġ` et `Ċ`, qui désignent respectivement les espaces et les retours à la ligne. Comme on peut le voir, ce n'est pas très efficace : le *tokenizer* renvoie des jetons individuels pour chaque espace, alors qu'il pourrait regrouper les niveaux d'indentation (puisqu’avoir des ensembles de quatre ou huit espaces va être très courant dans le code). Il divise également le nom de la fonction de façon un peu bizarre, n'étant pas habitué à voir des mots avec le caractère `_`. +Ce *tokenizer* possède quelques symboles spéciaux, comme `Ġ` et `Ċ`, qui désignent respectivement les espaces et les retours à la ligne. Comme on peut le voir, ce n'est pas très efficace. Le *tokenizer* renvoie des jetons individuels pour chaque espace alors qu'il pourrait regrouper ceux des indentations (puisqu’avoir des ensembles de quatre ou huit espaces est très courant dans du code). Il divise également le nom de la fonction de façon un peu bizarre car pas habitué à voir des mots avec le caractère `_`. Entraînons un nouveau *tokenizer* et voyons s'il résout ces problèmes. Pour cela, nous allons utiliser la méthode `train_new_from_iterator()` : @@ -171,14 +172,13 @@ Entraînons un nouveau *tokenizer* et voyons s'il résout ces problèmes. Pour c tokenizer = old_tokenizer.train_new_from_iterator(training_corpus, 52000) ``` -Cette commande peut prendre un peu de temps si votre corpus est très grand, mais pour ce jeu de données de 1,6 Go de textes, elle est très rapide (1 minute 16 secondes sur un CPU AMD Ryzen 9 3900X avec 12 cœurs). - -Notez que `AutoTokenizer.train_new_from_iterator()` ne fonctionne que si le tokenizer que vous utilisez est un tokenizer « rapide ». Comme vous le verrez dans la section suivante, la bibliothèque 🤗 *Transformers* contient deux types de *tokenizers* : certains sont écrits purement en Python et d'autres (les rapides) sont soutenus par la bibliothèque 🤗 *Tokenizers*, qui est écrite dans le langage de programmation [Rust](https://www.rust-lang.org). Python est le langage le plus souvent utilisé pour les applications de science des données et d'apprentissage profond, mais lorsque quelque chose doit être parallélisé pour être rapide, il doit être écrit dans un autre langage. Par exemple, les multiplications matricielles qui sont au cœur du calcul du modèle sont écrites en CUDA, une bibliothèque C optimisée pour les GPU. +Cette commande peut prendre un peu de temps si votre corpus est très grand. Pour ce jeu de données de 1,6 Go de textes, elle est très rapide (1 minute 16 secondes sur un CPU AMD Ryzen 9 3900X avec 12 cœurs). -Entraîner un tout nouveau *tokenizer* en Python pur serait atrocement lent, c'est pourquoi nous avons développé la bibliothèque 🤗 *Tokenizers*. Notez que, tout comme vous n'avez pas eu à apprendre le langage CUDA pour pouvoir exécuter votre modèle sur un lot d'entrées sur un GPU, vous n'aurez pas besoin d'apprendre Rust pour utiliser un *tokenizer* rapide. La bibliothèque 🤗 *Tokenizers* fournit des liaisons Python pour de nombreuses méthodes qui appellent en interne un morceau de code en Rust ; par exemple, pour paralléliser l'entraînement de votre nouveau *tokenizer* ou, comme nous l'avons vu dans le [Chapitre 3](/course/fr/chapter3), la tokenisation d'un lot d'entrées. +Notez que `AutoTokenizer.train_new_from_iterator()` ne fonctionne que si le *tokenizer* que vous utilisez est un *tokenizer* « rapide ». Comme vous le verrez dans la section suivante, la bibliothèque 🤗 *Transformers* contient deux types de *tokenizers* : certains sont écrits en pur Python et d'autres (les rapides) sont soutenus par la bibliothèque 🤗 *Tokenizers* qui est écrite dans le langage [Rust](https://www.rust-lang.org). Python est le langage le plus souvent utilisé pour les applications de science des données et d'apprentissage profond, mais lorsque quelque chose doit être parallélisé pour être rapide, il faut que cela soit écrit dans un autre langage. Par exemple, les multiplications matricielles qui sont au cœur du calcul du modèle sont écrites en CUDA, une bibliothèque en C optimisée pour les GPUs. -La plupart des *transformers* ont un *tokenizer* rapide disponible (il y a quelques exceptions que vous pouvez vérifier [ici](https://huggingface.co/transformers/#supported-frameworks)), et l'API `AutoTokenizer` sélectionne toujours le *tokenizer* rapide pour vous s'il est disponible. Dans la prochaine section, nous allons jeter un coup d'oeil à certaines des autres caractéristiques spéciales des *tokenizers* rapides, qui seront vraiment utiles pour des tâches comme la classification des *tokens* et la réponse aux questions. Mais avant cela, essayons notre tout nouveau *tokenizer* sur l'exemple précédent : +Entraîner un tout nouveau *tokenizer* en Python pur est atrocement lent, c'est pourquoi nous avons développé la bibliothèque 🤗 *Tokenizers*. Notez que, tout comme vous n'avez pas eu à apprendre le langage CUDA pour pouvoir exécuter votre modèle sur un batch d'entrées sur un GPU, vous n'aurez pas besoin d'apprendre Rust pour utiliser un *tokenizer* rapide. La bibliothèque 🤗 *Tokenizers* fournit des liaisons Python pour de nombreuses méthodes qui appellent en interne un morceau de code en Rust. Par exemple, pour paralléliser l'entraînement de votre nouveau *tokenizer* ou, comme nous l'avons vu dans le [Chapitre 3](/course/fr/chapter3), la tokenisation d'un lot d'entrées. +La plupart des *transformers* ont un *tokenizer* rapide de disponible. Il y a quelques exceptions que vous pouvez vérifier [ici](https://huggingface.co/transformers/#supported-frameworks). S'il est disponible, l'API `AutoTokenizer` sélectionne toujours pour vous le *tokenizer* rapide. Dans la prochaine section, nous allons jeter un coup d'oeil à certaines des autres caractéristiques spéciales des *tokenizers* rapides, qui seront très utiles pour des tâches comme la classification de *tokens* et la réponse aux questions. Mais avant cela, essayons notre tout nouveau *tokenizer* sur l'exemple précédent : ```py tokens = tokenizer.tokenize(example) @@ -190,7 +190,7 @@ tokens 'a', '`', 'Ġand', 'Ġ`', 'b', '`."""', 'ĊĠĠĠ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb'] ``` -Ici, nous voyons à nouveau les symboles spéciaux `Ġ` et `Ċ` qui indiquent les espaces et les retours à la ligne, mais nous pouvons également voir que notre *tokenizer* a appris certains *tokens* qui sont très spécifiques à un corpus de fonctions Python : par exemple, il y a un token `ĊĠĠĠ` qui représente une indentation, et un *token* `Ġ"""` qui représente les trois guillemets qui commencent une docstring. Le *tokenizer* divise également correctement le nom de la fonction sur `_`. Il s'agit d'une représentation assez compacte ; en comparaison, l'utilisation du *tokenizer* en anglais simple sur le même exemple nous donnera une phrase plus longue : +Ici, nous voyons à nouveau les symboles spéciaux `Ġ` et `Ċ` qui indiquent les espaces et les retours à la ligne. Nous pouvons également voir que notre *tokenizer* a appris certains *tokens* qui sont très spécifiques à un corpus de fonctions Python. Par exemple, il y a un token `ĊĠĠĠ` qui représente une indentation et un *token* `Ġ"""` qui représente les trois guillemets qui commencent une *docstring*. Le *tokenizer* divise également correctement le nom de la fonction sur `_`. Il s'agit d'une représentation assez compacte. En comparaison, l'utilisation du *tokenizer* en anglais « simple » sur le même exemple nous donnera une phrase plus longue : ```py print(len(tokens)) @@ -224,9 +224,9 @@ tokenizer.tokenize(example) 'Ġreturn', 'Ġx', 'Ġ@', 'Ġself', '.', 'weights', 'Ġ+', 'Ġself', '.', 'bias', 'ĊĠĠĠĠ'] ``` -En plus du *token* correspondant à une indentation, on peut également voir ici un *token* pour une double indentation : `ĊĠĠĠĠĠĠĠĠĠ`. Les mots spéciaux de Python comme `class`, `init`, `call`, `self`, et `return` sont tous tokenizés comme un seul token, et nous pouvons voir qu'en plus de séparer sur `_` et `.` le tokenizer sépare correctement même les noms en minuscules : `LinearLayer` est tokenizé comme `["ĠLinear", "Layer"]`. +En plus du *token* correspondant à une indentation, on peut également voir ici un *token* pour une double indentation : `ĊĠĠĠĠĠĠĠĠĠ`. Les mots spéciaux de Python comme `class`, `init`, `call`, `self`, et `return` sont tous tokenizés comme un seul *token*. Nous pouvons voir qu'en plus de séparer sur `_` et `.` le tokenizer sépare correctement même les noms en minuscules. Par exemple `LinearLayer` est tokenisé comme `["ĠLinear", "Layer"]`. -## Sauvegarde du *tokenizer*. +## Sauvegarde du tokenizer Pour être sûr de pouvoir l'utiliser plus tard, nous devons sauvegarder notre nouveau *tokenizer*. Comme pour les modèles, ceci est fait avec la méthode `save_pretrained()` : @@ -235,7 +235,7 @@ Pour être sûr de pouvoir l'utiliser plus tard, nous devons sauvegarder notre n tokenizer.save_pretrained("code-search-net-tokenizer") ``` -Cela créera un nouveau dossier nommé *code-search-net-tokenizer*, qui contiendra tous les fichiers dont le *tokenizer* a besoin pour être rechargé. Si vous souhaitez partager ce *tokenizer* avec vos collègues et amis, vous pouvez le télécharger sur le *Hub* en vous connectant à votre compte. Si vous travaillez dans un *notebook*, il existe une fonction pratique pour vous aider à le faire : +Cela créera un nouveau dossier nommé *code-search-net-tokenizer* contenant tous les fichiers dont le *tokenizer* a besoin pour être rechargé. Si vous souhaitez partager ce *tokenizer* avec vos collègues et amis, vous pouvez le télécharger sur le *Hub* en vous connectant à votre compte. Si vous travaillez dans un *notebook*, il existe une fonction pratique pour vous aider à le faire : ```python from huggingface_hub import notebook_login @@ -243,23 +243,23 @@ from huggingface_hub import notebook_login notebook_login() ``` -Cela affichera un widget où vous pourrez entrer vos identifiants de connexion à Hugging Face. Si vous ne travaillez pas dans un ordinateur portable, tapez simplement la ligne suivante dans votre terminal : +Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face. Si vous ne travaillez pas sur un ordinateur portable, tapez simplement la ligne suivante dans votre terminal : ```bash huggingface-cli login ``` -Une fois que vous vous êtes connecté, vous pouvez pousser votre *tokenizer* en exécutant la commande suivante : +Une fois connecté, vous pouvez pousser votre *tokenizer* en exécutant la commande suivante : ```py tokenizer.push_to_hub("code-search-net-tokenizer") ``` -Cela créera un nouveau dépôt dans votre espace de noms avec le nom `code-search-net-tokenizer`, contenant le fichier *tokenizer*. Vous pouvez ensuite charger le *tokenizer* de n'importe où avec la méthode `from_pretrained()` : +Cela créera un nouveau dépôt dans votre espace avec le nom `code-search-net-tokenizer` contenant le fichier *tokenizer*. Vous pouvez ensuite charger le *tokenizer* de n'importe où avec la méthode `from_pretrained()` : ```py -# Remplacez "huggingface-course" ci-dessous par votre espace de nom réel pour utiliser votre propre tokenizer +# Remplacez "huggingface-course" ci-dessous par votre espace réel pour utiliser votre propre tokenizer tokenizer = AutoTokenizer.from_pretrained("huggingface-course/code-search-net-tokenizer") ``` -Vous êtes maintenant prêt à entraîner un modèle de langue à partir de zéro et à le *finetuner* sur votre tâche ! Nous y reviendrons au [Chapitre 7](/course/fr/chapter7), mais d'abord, dans le reste de ce chapitre, nous allons examiner de plus près les *tokenizers* rapides et explorer en détail ce qui se passe réellement lorsque nous appelons la méthode `train_new_from_iterator()`. \ No newline at end of file +Vous êtes maintenant prêt à entraîner un modèle de langue à partir de zéro et à le *finetuner* sur votre tâche ! Nous verrons cela dans le [chapitre 7](/course/fr/chapter7), mais d'abord, dans le reste de ce chapitre, nous allons examiner de plus près les *tokenizers* rapides et explorer en détail ce qui se passe lorsque nous appelons la méthode `train_new_from_iterator()`. \ No newline at end of file diff --git a/chapters/fr/chapter6/3.mdx b/chapters/fr/chapter6/3.mdx index 85c5c2d43..c37fc06ff 100644 --- a/chapters/fr/chapter6/3.mdx +++ b/chapters/fr/chapter6/3.mdx @@ -1,6 +1,6 @@ -# Pouvoirs spéciaux des *tokenizers* rapides +# Pouvoirs spéciaux des tokenizers rapides {#if fw === 'pt'} @@ -22,12 +22,12 @@ {/if} -Dans cette section, nous allons examiner de plus près les capacités des tokenizers dans 🤗 *Transformers*. - Jusqu'à présent, nous ne les avons utilisés que pour *tokeniser* les entrées ou décoder les identifiants pour les retranscrire en texte, mais les *tokenizers*, surtout ceux soutenus par la bibliothèque 🤗 *Tokenizers*, peuvent faire beaucoup plus. Pour illustrer ces fonctionnalités supplémentaires, nous allons explorer comment reproduire les résultats des pipelines `token-classification` (que nous avons appelé `ner`) et `question-answering` que nous avons rencontrés pour la première fois dans [Chapter 1](/course/fr/chapter1). +Dans cette section, nous allons examiner de plus près les capacités des *tokenizers* dans 🤗 *Transformers*. +Jusqu'à présent, nous ne les avons utilisés que pour tokeniser les entrées ou décoder les identifiants pour revenir à du texte. Mais les *tokenizers*, et surtout ceux soutenus par la bibliothèque 🤗 *Tokenizers*, peuvent faire beaucoup plus. Pour illustrer ces fonctionnalités supplémentaires, nous allons explorer comment reproduire les résultats des pipelines `token-classification` (que nous avons appelé `ner`) et `question-answering` que nous avons rencontrés pour la première fois dans le [chapitre 1](/course/fr/chapter1). -Dans la discussion qui suit, nous ferons souvent la distinction entre les *tokenizers* « lents » et « rapides ». Les *tokenizers* lents sont ceux écrits en Python à l'intérieur de la bibliothèque 🤗 *Transformers*, tandis que les versions rapides sont celles fournies par 🤗 *Tokenizers*, qui sont écrites en Rust. Si vous vous souvenez du tableau du [Chapitre 5](/course/fr/chapter5/3) qui indiquait combien de temps il fallait à un *tokenizer* rapide et à un *tokenizer* lent pour tokeniser le jeu de données Drug Review, vous devriez avoir une idée de la raison pour laquelle nous les appelons rapides et lents : +Dans la discussion qui suit, nous ferons souvent la distinction entre les *tokenizers* « lents » et les « rapides ». Les *tokenizers* lents sont ceux écrits en Python à l'intérieur de la bibliothèque 🤗 *Transformers*, tandis que les rapides sont ceux fournis par 🤗 *Tokenizers* et sont codés en Rust. Si vous vous souvenez du tableau du [chapitre 5](/course/fr/chapter5/3) qui indiquait combien de temps il fallait à un *tokenizer* rapide et à un *tokenizer* lent pour tokeniser le jeu de données *Drug Review*, vous devriez avoir une idée de la raison pour laquelle nous les appelons rapides et lents : | *Tokenizer* rapide | *Tokenizer* lent :--------------:|:--------------:|:-------------: @@ -36,11 +36,11 @@ Dans la discussion qui suit, nous ferons souvent la distinction entre les *token -⚠️ Lors de la tokenisation d'une seule phrase, vous ne verrez pas toujours une différence de vitesse entre les versions lente et rapide d'un même *tokenizer*. En fait, la version rapide peut même être plus lente ! Ce n'est que lorsque vous tokenisez des batchs de textes en parallèle et en même temps que vous pourrez voir clairement la différence. +⚠️ Lors de la tokenisation d'une seule phrase, vous ne verrez pas toujours une différence de vitesse entre les versions lente et rapide d'un même *tokenizer*. En fait, la version rapide peut même être plus lente ! Ce n'est que lorsque vous tokenisez beaucoup de textes en parallèle et en même temps que vous pourrez clairement voir la différence. -## *BatchEncoding* +## L'objet BatchEncoding @@ -54,7 +54,8 @@ Prenons un exemple : from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") -example = "My name is Sylvain and I work at Hugging Face in Brooklyn." # "Je m'appelle Sylvain et je travaille chez Hugging Face à Brooklyn." +example = "My name is Sylvain and I work at Hugging Face in Brooklyn." +# Je m'appelle Sylvain et je travaille chez Hugging Face à Brooklyn. encoding = tokenizer(example) print(type(encoding)) ``` @@ -65,7 +66,7 @@ Comme mentionné précédemment, nous obtenons un objet `BatchEncoding` dans la ``` -Puisque la classe `AutoTokenizer` choisit un *tokenizer* rapide par défaut, nous pouvons utiliser les méthodes supplémentaires que cet objet `BatchEncoding` fournit. Nous avons deux façons de vérifier si notre *tokenizer* est rapide ou lent. Nous pouvons soit vérifier l'attribut `is_fast` du *tokenizer* : +Puisque la classe `AutoTokenizer` choisit un *tokenizer* rapide par défaut, nous pouvons utiliser les méthodes supplémentaires que cet objet `BatchEncoding` fournit. Nous avons deux façons de vérifier si notre *tokenizer* est rapide ou lent. Nous pouvons soit vérifier l'attribut `is_fast` du *tokenizer* comme suit : ```python tokenizer.is_fast @@ -75,7 +76,7 @@ tokenizer.is_fast True ``` -ou vérifiez le même attribut de notre `encoding` : +soit vérifier le même attribut mais avec notre `encoding` : ```python encoding.is_fast @@ -85,7 +86,7 @@ encoding.is_fast True ``` -Voyons ce qu'un *tokenizer* rapide nous permet de faire. Tout d'abord, nous pouvons accéder aux *tokens* sans avoir à reconvertir les ID en *tokens* : +Voyons ce qu'un *tokenizer* rapide nous permet de faire. Tout d'abord, nous pouvons accéder aux *tokens* sans avoir à reconvertir les identifiants en *tokens* : ```py encoding.tokens() @@ -96,7 +97,7 @@ encoding.tokens() 'Brooklyn', '.', '[SEP]'] ``` -Dans ce cas, le *token* à l'index 5 est `##yl`, qui fait partie du mot « Sylvain » dans la phrase originale. Nous pouvons également utiliser la méthode `word_ids()` pour obtenir l'index du mot dont provient chaque *token* : +Dans ce cas, le *token* à l'index 5 est `##yl` et fait partie du mot « Sylvain » dans la phrase originale. Nous pouvons également utiliser la méthode `word_ids()` pour obtenir l'index du mot dont provient chaque *token* : ```py encoding.word_ids() @@ -106,19 +107,21 @@ encoding.word_ids() [None, 0, 1, 2, 3, 3, 3, 3, 4, 5, 6, 7, 8, 8, 9, 10, 11, 12, None] ``` -On peut voir que les *tokens* spéciaux du tokenizer `[CLS]` et `[SEP]` sont mis en correspondance avec `None`, puis chaque *token* est mis en correspondance avec le mot dont il provient. Ceci est particulièrement utile pour déterminer si un *token* est au début d'un mot ou si deux *tokens* sont dans le même mot. Nous pourrions nous appuyer sur le préfixe `##` pour cela, mais il ne fonctionne que pour les *tokenizers* de type BERT. Cette méthode fonctionne pour n'importe quel type de *tokenizer*, du moment qu'il est rapide. Dans le chapitre suivant, nous verrons comment utiliser cette capacité pour appliquer correctement les étiquettes que nous avons pour chaque mot aux *tokens* dans des tâches comme la reconnaissance d'entités nommées (NER) et le marquage POS (Part-of-speech). Nous pouvons également l'utiliser pour masquer tous les *tokens* provenant du même mot dans la modélisation du langage masqué (une technique appelée _whole word masking_). +On peut voir que les *tokens* spéciaux du *tokenizer*, `[CLS]` et `[SEP]`, sont mis en correspondance avec `None` et que chaque *token* est mis en correspondance avec le mot dont il provient. Ceci est particulièrement utile pour déterminer si un *token* est au début d'un mot ou si deux *tokens* sont dans le même mot. Nous pourrions nous appuyer sur le préfixe `##` pour cela, mais il ne fonctionne que pour les *tokenizers* de type BERT. Cette méthode fonctionne pour n'importe quel type de *tokenizer*, du moment qu'il est rapide. Dans le chapitre suivant, nous verrons comment utiliser cette capacité pour appliquer correctement les étiquettes que nous avons pour chaque mot aux *tokens* dans des tâches comme la reconnaissance d'entités nommées et le POS (*Part-of-speech*). Nous pouvons également l'utiliser pour masquer tous les *tokens* provenant du même mot dans la modélisation du langage masqué (une technique appelée _whole word masking_). + - -La notion de ce qu'est un mot est compliquée. Par exemple, est-ce que « I will » (contraction de « I will ») compte pour un ou deux mots ? Cela dépend en fait du *tokenizer* et de l'opération de pré-tokénisation qu'il applique. Certains *tokenizer* se contentent de séparer les espaces et considèrent donc qu'il s'agit d'un seul mot. D'autres utilisent la ponctuation en plus des espaces et considèrent donc qu'il s'agit de deux mots. +La notion de ce qu'est un mot est compliquée. Par exemple, est-ce que « I'll » (contraction de « I will ») compte pour un ou deux mots ? Cela dépend en fait du *tokenizer* et de l'opération de prétokénisation qu'il applique. Certains *tokenizer* se contentent de séparer les espaces et considèrent donc qu'il s'agit d'un seul mot. D'autres utilisent la ponctuation en plus des espaces et considèrent donc qu'il s'agit de deux mots. + + -✏️ **Essayez !** Créez un *tokenizer* à partir des points de contrôle `bert-base-cased` et `roberta-base` et tokenisez « 81s » avec eux. Qu'observez-vous ? Quels sont les identifiants des mots ? +✏️ **Essayez !** Créez un *tokenizer* à partir des checkpoints `bert-base-cased` et `roberta-base` et tokenisez « 81s » avec. Qu'observez-vous ? Quels sont les identifiants des mots ? -De même, il existe une méthode `sentence_ids()` que nous pouvons utiliser pour associer un token à la phrase dont il provient (bien que dans ce cas, le `token_type_ids` retourné par le *tokenizer* peut nous donner la même information). +De même, il existe une méthode `sentence_ids()` que nous pouvons utiliser pour associer un *token* à la phrase dont il provient (bien que dans ce cas, le `token_type_ids` retourné par le *tokenizer* peut nous donner la même information). -Enfin, nous pouvons faire correspondre n'importe quel mot ou jeton aux caractères du texte d'origine, et vice versa, grâce aux méthodes `word_to_chars()` ou `token_to_chars()` et `char_to_word()` ou `char_to_token()`. Par exemple, la méthode `word_ids()` nous a dit que `##yl` fait partie du mot à l'indice 3, mais de quel mot s'agit-il dans la phrase ? Nous pouvons le découvrir comme ceci : +Enfin, nous pouvons faire correspondre n'importe quel mot ou *token* aux caractères du texte d'origine (et vice versa) grâce aux méthodes `word_to_chars()` ou `token_to_chars()` et `char_to_word()` ou `char_to_token()`. Par exemple, la méthode `word_ids()` nous a dit que `##yl` fait partie du mot à l'indice 3, mais de quel mot s'agit-il dans la phrase ? Nous pouvons le découvrir comme ceci : ```py start, end = encoding.word_to_chars(3) @@ -129,17 +132,17 @@ example[start:end] Sylvain ``` -Comme nous l'avons mentionné précédemment, tout ceci est rendu possible par le fait que le tokenizer rapide garde la trace de la partie du texte d'où provient chaque *token dans une liste de *offsets*. Pour illustrer leur utilisation, nous allons maintenant vous montrer comment reproduire manuellement les résultats du pipeline `token-classification`. +Comme nous l'avons mentionné précédemment, tout ceci est rendu possible par le fait que le *tokenizer* rapide garde la trace de la partie du texte d'où provient chaque *token* dans une liste d'*offsets*. Pour illustrer leur utilisation, nous allons maintenant vous montrer comment reproduire manuellement les résultats du pipeline `token-classification`. -✏️ **Essayez !** Créez votre propre texte d'exemple et voyez si vous pouvez comprendre quels *tokens* sont associés à l'ID du mot, et aussi comment extraire les portées de caractères pour un seul mot. Pour obtenir des points bonus, essayez d'utiliser deux phrases en entrée et voyez si les identifiants de phrase ont un sens pour vous. +✏️ **Essayez !** Rédigez votre propre texte et voyez si vous pouvez comprendre quels *tokens* sont associés à l'identifiant du mot et comment extraire les étendues de caractères pour un seul mot. Pour obtenir des points bonus, essayez d'utiliser deux phrases en entrée et voyez si les identifiants ont un sens pour vous. ## A l'intérieur du pipeline `token-classification` -Dans le [Chapitre 1](/course/fr/chapter1), nous avons eu un premier aperçu de l'application de NER (où la tâche est d'identifier les parties du texte qui correspondent à des entités telles que des personnes, des lieux ou des organisations) avec la fonction 🤗 *Transformers* `pipeline()`. Puis, dans [Chapitre 2](/course/fr/chapter2), nous avons vu comment un pipeline regroupe les trois étapes nécessaires pour obtenir les prédictions à partir d'un texte brut : la tokenisation, le passage des entrées dans le modèle et le post-traitement. Les deux premières étapes du pipeline de `token-classification` sont les mêmes que dans tout autre pipeline, mais le post-traitement est un peu plus complexe. Voyons comment ! +Dans le [chapitre 1](/course/fr/chapter1), nous avons eu un premier aperçu de la NER (où la tâche est d'identifier les parties du texte qui correspondent à des entités telles que des personnes, des lieux ou des organisations) avec la fonction `pipeline()` de 🤗 *Transformers*. Puis, dans le [chapitre 2](/course/fr/chapter2), nous avons vu comment un pipeline regroupe les trois étapes nécessaires pour obtenir les prédictions à partir d'un texte brut : la tokenisation, le passage des entrées dans le modèle et le post-traitement. Les deux premières étapes du pipeline de `token-classification` sont les mêmes que dans tout autre pipeline mais le post-traitement est un peu plus complexe. Voyons comment ! {#if fw === 'pt'} @@ -153,7 +156,7 @@ Dans le [Chapitre 1](/course/fr/chapter1), nous avons eu un premier aperçu de l ### Obtenir les résultats de base avec le pipeline -Tout d'abord, prenons un pipeline de classification de tokens afin d'obtenir des résultats à comparer manuellement. Le modèle utilisé par défaut est [`dbmdz/bert-large-cased-finetuned-conll03-english`](https://huggingface.co/dbmdz/bert-large-cased-finetuned-conll03-english). Il effectue un NER sur les phrases : +Tout d'abord, prenons un pipeline de classification de *tokens* afin d'obtenir des résultats à comparer manuellement. Le modèle utilisé par défaut est [`dbmdz/bert-large-cased-finetuned-conll03-english`](https://huggingface.co/dbmdz/bert-large-cased-finetuned-conll03-english). Il effectue une NER sur les phrases : ```py from transformers import pipeline @@ -173,7 +176,7 @@ token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.") {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn', 'start': 49, 'end': 57}] ``` -Le modèle a correctement identifié chaque token généré par « Sylvain » comme une personne, chaque token généré par « Hugging Face » comme une organisation, et le *token* « Brooklyn » comme un lieu. Nous pouvons également demander au pipeline de regrouper les tokens qui correspondent à la même entité : +Le modèle a correctement identifié chaque *token* généré par « Sylvain » comme une personne, chaque *token* généré par « Hugging Face » comme une organisation, et le *token* « Brooklyn » comme un lieu. Nous pouvons également demander au pipeline de regrouper les *tokens* qui correspondent à la même entité : ```py from transformers import pipeline @@ -188,12 +191,11 @@ token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.") {'entity_group': 'LOC', 'score': 0.99321055, 'word': 'Brooklyn', 'start': 49, 'end': 57}] ``` -La `aggregation_strategy` choisie va changer les scores calculés pour chaque entité groupée. Avec `"simple"` le score est juste la moyenne des scores de chaque *token* dans l'entité donnée : par exemple, le score de « Sylvain » est la moyenne des scores que nous avons vu dans l'exemple précédent pour les tokens `S`, `##yl`, `##va`, et `##in`. D'autres stratégies sont disponibles : +La propriété `aggregation_strategy` choisie va changer les scores calculés pour chaque entité groupée. Avec `"simple"` le score est juste la moyenne des scores de chaque *token* dans l'entité donnée. Par exemple, le score de « Sylvain » est la moyenne des scores que nous avons vu dans l'exemple précédent pour les tokens `S`, `##yl`, `##va`, et `##in`. D'autres stratégies sont disponibles : -- `"first"`, où le score de chaque entité est le score du premier token de cette entité (donc pour « Sylvain » ce serait 0.993828, le score du token `S`) -- `"max"`, où le score de chaque entité est le score maximal des tokens de cette entité (ainsi, pour « Hugging Face », le score de « Face » serait de 0,98879766). -- `"moyenne"`, où le score de chaque entité est la moyenne des scores des mots qui composent cette entité (ainsi, pour « Sylvain », -il n'y aurait pas de différence avec la stratégie `"simple"`, mais "Étreinte du visage" aurait un score de 0,9819, la moyenne des scores de « Hugging Face », 0,975, et « Face », 0,98879). +- `"first"`, où le score de chaque entité est le score du premier *token* de cette entité (donc pour « Sylvain » ce serait 0.993828, le score du token `S`) +- `"max"`, où le score de chaque entité est le score maximal des *tokens* de cette entité (ainsi, pour « Hugging Face », le score de « Face » serait de 0,98879766). +- `"average"`, où le score de chaque entité est la moyenne des scores des mots qui composent cette entité (ainsi, pour « Sylvain », il n'y aurait pas de différence avec la stratégie `"simple"`, mais "Hugging Face" aurait un score de 0,9819, la moyenne des scores de « Hugging », 0,975, et « Face », 0,98879). Voyons maintenant comment obtenir ces résultats sans utiliser la fonction `pipeline()` ! @@ -201,7 +203,7 @@ Voyons maintenant comment obtenir ces résultats sans utiliser la fonction `pipe {#if fw === 'pt'} -D'abord, nous devons tokeniser notre entrée et la faire passer dans le modèle. Cela se fait exactement comme dans le [Chapitre 2](/course/frchapter2). Nous instancions le *tokenizer* et le modèle en utilisant les classes `TFAutoXxx` et les utilisons ensuite dans notre exemple : +D'abord, nous devons tokeniser notre entrée et la faire passer dans le modèle. Cela se fait exactement comme dans le [chapitre 2](/course/fr/chapter2). Nous instancions le *tokenizer* et le modèle en utilisant les classes `TFAutoXxx` et les utilisons ensuite dans notre exemple : ```py from transformers import AutoTokenizer, AutoModelForTokenClassification @@ -215,7 +217,7 @@ inputs = tokenizer(example, return_tensors="pt") outputs = model(**inputs) ``` -Puisque nous utilisons `AutoModelForTokenClassification` ici, nous obtenons un ensemble de logits pour chaque token dans la séquence d'entrée : +Puisque nous utilisons `AutoModelForTokenClassification`, nous obtenons un ensemble de logits pour chaque *token* dans la séquence d'entrée : ```py print(inputs["input_ids"].shape) @@ -229,7 +231,7 @@ torch.Size([1, 19, 9]) {:else} -D'abord, nous devons tokeniser notre entrée et la faire passer dans le modèle. Cela se fait exactement comme dans le [Chapitre 2](/course/frchapter2). Nous instancions le *tokenizer* et le modèle en utilisant les classes `TFAutoXxx` et les utilisons ensuite dans notre exemple : +D'abord, nous devons tokeniser notre entrée et la faire passer dans le modèle. Cela se fait exactement comme dans le [chapitre 2](/course/fr/chapter2). Nous instancions le *tokenizer* et le modèle en utilisant les classes `TFAutoXxx` et les utilisons ensuite dans notre exemple : ```py from transformers import AutoTokenizer, TFAutoModelForTokenClassification @@ -243,7 +245,7 @@ inputs = tokenizer(example, return_tensors="tf") outputs = model(**inputs) ``` -Puisque nous utilisons `TFAutoModelForTokenClassification` ici, nous obtenons un ensemble de logits pour chaque *token* dans la séquence d'entrée : +Puisque nous utilisons `TFAutoModelForTokenClassification`, nous obtenons un ensemble de logits pour chaque *token* dans la séquence d'entrée : ```py print(inputs["input_ids"].shape) @@ -257,7 +259,7 @@ print(outputs.logits.shape) {/if} -Nous avons un batch avec 1 séquence de 19 *tokens* et le modèle a 9 étiquettes différentes, donc la sortie du modèle a une forme de 1 x 19 x 9. Comme pour le pipeline de classification de texte, nous utilisons une fonction softmax pour convertir ces logits en probabilités, et nous prenons l'argmax pour obtenir des prédictions (notez que nous pouvons prendre l'argmax sur les logits parce que la softmax ne change pas l'ordre) : +Nous avons un batch avec 1 séquence de 19 *tokens* et le modèle a 9 étiquettes différentes. Ainsi, la sortie du modèle a une forme de 1 x 19 x 9. Comme pour le pipeline de classification de texte, nous utilisons une fonction softmax pour convertir ces logits en probabilités et nous prenons l'argmax pour obtenir des prédictions (notez que nous pouvons prendre l'argmax sur les logits car la fonction softmax ne change pas l'ordre) : {#if fw === 'pt'} @@ -305,16 +307,16 @@ model.config.id2label 8: 'I-LOC'} ``` -Comme nous l'avons vu précédemment, il y a 9 étiquettes : `O` est le label pour les *tokens* qui ne sont dans aucune entité nommée (il signifie *outside*) et nous avons ensuite deux labels pour chaque type d'entité (divers, personne, organisation, et lieu). L'étiquette `B-XXX` indique que le *token* est au début d'une entité `XXX` et l'étiquette `I-XXX` indique que le *token* est à l'intérieur de l'entité `XXX`. Par exemple, dans l'exemple actuel, nous nous attendons à ce que notre modèle classe le *token* `S` comme `B-PER` (début d'une entité personne) et les *tokens* `##yl`, `##va` et `##in` comme `I-PER` (à l'intérieur d'une entité personne). +Comme nous l'avons vu précédemment, il y a 9 étiquettes : `O` est le label pour les *tokens* qui ne sont dans aucune entité nommée (il signifie *outside* (en dehors)) et nous avons ensuite deux labels pour chaque type d'entité (divers, personne, organisation et lieu). L'étiquette `B-XXX` indique que le *token* est au début d'une entité `XXX` et l'étiquette `I-XXX` indique que le *token* est à l'intérieur de l'entité `XXX`. Par exemple, dans l'exemple actuel, nous nous attendons à ce que notre modèle classe le *token* `S` comme `B-PER` (début d'une entité personne) et les *tokens* `##yl`, `##va` et `##in` comme `I-PER` (à l'intérieur d'une entité personne). -Vous pourriez penser que le modèle s'est trompé dans ce cas, car il a attribué l'étiquette `I-PER` à ces quatre *tokens*, mais ce n'est pas tout à fait vrai. Il existe en fait deux formats pour ces étiquettes `B-` et `I-` : *IOB1* et *IOB2*. Le format IOB2 (en rose ci-dessous) est celui que nous avons introduit alors que dans le format IOB1 (en bleu), les étiquettes commençant par `B-` ne sont jamais utilisées que pour séparer deux entités adjacentes du même type. Le modèle que nous utilisons a été *finetuné* sur un jeu de données utilisant ce format, c'est pourquoi il attribue le label `I-PER` au *token* `S`. +Vous pourriez penser que le modèle s'est trompé ici car il a attribué l'étiquette `I-PER` à ces quatre *tokens* mais ce n'est pas tout à fait vrai. Il existe en fait deux formats pour ces étiquettes `B-` et `I-` : *IOB1* et *IOB2*. Le format IOB2 (en rose ci-dessous) est celui que nous avons introduit alors que dans le format IOB1 (en bleu), les étiquettes commençant par `B-` ne sont jamais utilisées que pour séparer deux entités adjacentes du même type. Le modèle que nous utilisons a été *finetuné* sur un jeu de données utilisant ce format, c'est pourquoi il attribue le label `I-PER` au *token* `S`.
IOB1 vs IOB2 format
-Avec cette carte, nous sommes prêts à reproduire (presque entièrement) les résultats du premier pipeline. Nous pouvons simplement récupérer le score et le label de chaque *token* qui n'a pas été classé comme `O` : +Nous sommes à présent prêts à reproduire (presque entièrement) les résultats du premier pipeline. Nous pouvons simplement récupérer le score et le label de chaque *token* qui n'a pas été classé comme `O` : ```py results = [] @@ -341,7 +343,7 @@ print(results) {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn'}] ``` -C'est très similaire à ce que nous avions avant, à une exception près : le pipeline nous a aussi donné des informations sur le `début` et la `fin` de chaque entité dans la phrase originale. C'est là que notre mappage de décalage va entrer en jeu. Pour obtenir les décalages, il suffit de définir `return_offsets_mapping=True` lorsque nous appliquons le *tokenizer* à nos entrées : +C'est très similaire à ce que nous avions avant, à une exception près : le pipeline nous a aussi donné des informations sur le `début` et la `fin` de chaque entité dans la phrase originale. C'est là que notre *offset mapping* va entrer en jeu. Pour obtenir les *offsets*, il suffit de définir `return_offsets_mapping=True` lorsque nous appliquons le *tokenizer* à nos entrées : ```py inputs_with_offsets = tokenizer(example, return_offsets_mapping=True) @@ -353,7 +355,7 @@ inputs_with_offsets["offset_mapping"] (33, 35), (35, 40), (41, 45), (46, 48), (49, 57), (57, 58), (0, 0)] ``` -Chaque *tuple* est l'empan de texte correspondant à chaque token, où `(0, 0)` est réservé aux *tokens* spéciaux. Nous avons vu précédemment que le token à l'index 5 est `##yl`, qui a `(12, 14)` comme *offsets* ici. Si on prend la tranche correspondante dans notre exemple : +Chaque *tuple* est l'étendue de texte correspondant à chaque *token* où `(0, 0)` est réservé aux *tokens* spéciaux. Nous avons vu précédemment que le *token* à l'index 5 est `##yl`, qui a `(12, 14)` comme *offsets* ici. Si on prend la tranche correspondante dans notre exemple : ```py @@ -406,9 +408,9 @@ C'est la même chose que ce que nous avons obtenu avec le premier pipeline ! ### Regroupement des entités -L'utilisation des offsets pour déterminer les clés de début et de fin pour chaque entité est pratique mais cette information n'est pas strictement nécessaire. Cependant, lorsque nous voulons regrouper les entités, les offsets nous épargnent un batch de code compliqué. Par exemple, si nous voulions regrouper les *tokens* `Hu`, `##gging`, et `Face`, nous pourrions établir des règles spéciales disant que les deux premiers devraient être attachés tout en enlevant le `##`, et le `Face` devrait être ajouté avec un espace puisqu'il ne commence pas par `##` mais cela ne fonctionnerait que pour ce type particulier de *tokenizer*. Il faudrait écrire un autre ensemble de règles pour un *tokenizer* de type SentencePiece ou Byte-Pair-Encoding (voir plus loin dans ce chapitre). +L'utilisation des *offsets* pour déterminer les clés de début et de fin pour chaque entité est pratique mais cette information n'est pas strictement nécessaire. Cependant, lorsque nous voulons regrouper les entités, les *offsets* nous épargnent un batch de code compliqué. Par exemple, si nous voulions regrouper les *tokens* `Hu`, `##gging`, et `Face`, nous pourrions établir des règles spéciales disant que les deux premiers devraient être attachés tout en enlevant le `##`, et le `Face` devrait être ajouté avec un espace puisqu'il ne commence pas par `##` mais cela ne fonctionnerait que pour ce type particulier de *tokenizer*. Il faudrait écrire un autre ensemble de règles pour un *tokenizer* de type SentencePiece ou *Byte-Pair-Encoding* (voir plus loin dans ce chapitre). -Avec les *offsets*, tout ce code personnalisé disparaît : il suffit de prendre l'intervalle du texte original qui commence par le premier *token* et se termine par le dernier *token*. Ainsi, dans le cas des tokens `Hu`, `##gging`, et `Face`, nous devrions commencer au caractère 33 (le début de `Hu`) et finir avant le caractère 45 (la fin de `Face`) : +Avec les *offsets*, tout ce code personnalisé disparaît : il suffit de prendre l'intervalle du texte original qui commence par le premier *token* et se termine par le dernier *token*. Ainsi, dans le cas des *tokens* `Hu`, `##gging`, et `Face`, nous devrions commencer au caractère 33 (le début de `Hu`) et finir avant le caractère 45 (la fin de `Face`) : ```py example[33:45] @@ -447,7 +449,7 @@ while idx < len(predictions): _, end = offsets[idx] idx += 1 - # Le score est la moyenne de tous les scores des tokens dans cette entité groupée. + # Le score est la moyenne de tous les scores des tokens dans cette entité groupée score = np.mean(all_scores).item() word = example[start:end] results.append( @@ -472,4 +474,4 @@ Et nous obtenons les mêmes résultats qu'avec notre deuxième pipeline ! {'entity_group': 'LOC', 'score': 0.99321055, 'word': 'Brooklyn', 'start': 49, 'end': 57}] ``` -Un autre exemple de tâche où ces décalages sont extrêmement utiles est la réponse aux questions. Plonger dans ce pipeline, ce que nous ferons dans la section suivante, nous permettra également de jeter un coup d'œil à une dernière caractéristique des *tokenizers* de la bibliothèque 🤗 *Transformers* : la gestion des tokens qui débordent lorsque nous tronquons une entrée à une longueur donnée. +Un autre exemple de tâche où ces *offsets* sont extrêmement utiles est la réponse aux questions. Plonger dans ce pipeline, ce que nous ferons dans la section suivante, nous permettra de jeter un coup d'œil à une dernière caractéristique des *tokenizers* de la bibliothèque 🤗 *Transformers* : la gestion des *tokens* qui débordent lorsque nous tronquons une entrée à une longueur donnée. diff --git a/chapters/fr/chapter6/3b.mdx b/chapters/fr/chapter6/3b.mdx index e3c67b105..3e177a9fb 100644 --- a/chapters/fr/chapter6/3b.mdx +++ b/chapters/fr/chapter6/3b.mdx @@ -1,6 +1,6 @@ -# *Tokenizer* rapide dans le pipeline de QA +# Tokenizer rapide dans le pipeline de QA {#if fw === 'pt'} @@ -22,7 +22,7 @@ {/if} -Nous allons maintenant nous plonger dans le pipeline de `question-answering` et voir comment exploiter les *offsets* pour extraire du contexte la réponse à la question posée, un peu comme nous l'avons fait pour les entités groupées dans la section précédente. 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'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. {#if fw === 'pt'} @@ -34,20 +34,24 @@ Nous allons maintenant nous plonger dans le pipeline de `question-answering` et {/if} -## Utilisation du pipeline de `question-answering`. +## Utilisation du pipeline de `question-answering` -Comme nous l'avons vu dans le [Chapitre 1](/course/fr/chapter1), nous pouvons utiliser le pipeline de `question-answering` comme ceci pour obtenir la réponse à une question : +Comme nous l'avons vu dans le [chapitre 1](/course/fr/chapter1), nous pouvons utiliser le pipeline de `question-answering` comme ceci pour obtenir une réponse à une question : ```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. +🤗 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. """ -# 🤗 Transformers s'appuie sur les trois bibliothèques d'apprentissage profond les plus populaires (Jax, PyTorch et TensorFlow) avec une intégration transparente entre elles. C'est simple d'entraîner vos modèles avec l'une avant de les charger pour l'inférence avec l'autre. -question = "Which deep learning libraries back 🤗 Transformers?" # Quelles bibliothèques d'apprentissage profond derrière 🤗 Transformers ? +# 🤗 Transformers s'appuie sur les trois bibliothèques d'apprentissage profond les plus populaires +# (Jax, PyTorch et TensorFlow) avec une intégration transparente entre elles. +# C'est simple d'entraîner vos modèles avec l'une avant de les charger pour l'inférence avec l'autre. +question = "Which deep learning libraries back 🤗 Transformers?" +# Quelles bibliothèques d'apprentissage profond derrière 🤗 Transformers ? question_answerer(question=question, context=context) ``` @@ -102,8 +106,8 @@ between them. It's straightforward to train your models with one before loading long_context - fr = """ 🤗 Transformers : l'état de l'art du NLP -🤗 Transformers fournit des milliers de modèles pré-entraînés pour effectuer des tâches sur des textes telles que la classification, l'extraction d'informations, -la réponse à des questions, le résumé de textes, la traduction, la génération de texte et plus encore dans plus de 100 langues. +🤗 Transformers fournit des milliers de modèles pré-entraînés pour effectuer des tâches sur des textes telles que la classification, +l'extraction d'informations, la réponse à des questions, le résumé de textes, la traduction, la génération de texte et plus encore dans plus de 100 langues. Son objectif est de rendre le traitement automatique des langues de pointe plus facile à utiliser pour tout le monde. 🤗 Transformers fournit des API permettant de télécharger et d'utiliser rapidement ces modèles pré-entraînés sur un texte donné, de les affiner sur vos propres ensembles de données et de les partager avec la communauté sur notre site Web. @@ -150,7 +154,7 @@ Voyons comment il fait tout cela ! ### Utilisation d'un modèle pour répondre à des questions -Comme avec n'importe quel autre pipeline, nous commençons par tokeniser notre entrée et l'envoyons ensuite à travers le modèle. Le point de contrôle utilisé par défaut pour le pipeline de `question-answering` est [`distilbert-base-cased-distilled-squad`](https://huggingface.co/distilbert-base-cased-distilled-squad) (le « squad » dans le nom vient du jeu de données sur lequel le modèle a été *finetuné*, nous parlerons plus du jeu de données SQuAD dans le [Chapitre 7](/course/fr/chapter7/7)) : +Comme avec n'importe quel autre pipeline, nous commençons par tokeniser notre entrée et l'envoyons ensuite à travers le modèle. Le *checkpoint* utilisé par défaut pour le pipeline de `question-answering` est [`distilbert-base-cased-distilled-squad`](https://huggingface.co/distilbert-base-cased-distilled-squad) (le « squad » dans le nom vient du jeu de données sur lequel le modèle a été *finetuné*, nous parlerons davantage de ce jeu de données dans le [chapitre 7](/course/fr/chapter7/7)) : {#if fw === 'pt'} @@ -209,7 +213,7 @@ torch.Size([1, 66]) torch.Size([1, 66]) {/if} -Pour convertir ces logits en probabilités, nous allons appliquer une fonction softmax. Mais avant cela, nous devons nous assurer de masquer les indices qui ne font pas partie du contexte. Notre entrée est `[CLS] question [SEP] contexte [SEP]`, donc nous devons masquer les *tokens* de la question ainsi que le *token* `[SEP]`. Nous garderons cependant le *token* `[CLS]`, car certains modèles l'utilisent pour indiquer que la réponse n'est pas dans le contexte. +Pour convertir ces logits en probabilités, nous allons appliquer une fonction softmax. Mais avant cela, nous devons nous assurer de masquer les indices qui ne font pas partie du contexte. Notre entrée est `[CLS] question [SEP] contexte [SEP]` donc nous devons masquer les *tokens* de la question ainsi que le *token* `[SEP]`. Nous garderons cependant le *token* `[CLS]` car certains modèles l'utilisent pour indiquer que la réponse n'est pas dans le contexte. Puisque nous appliquerons une fonction softmax par la suite, il nous suffit de remplacer les logits que nous voulons masquer par un grand nombre négatif. Ici, nous utilisons `-10000` : @@ -265,7 +269,7 @@ end_probabilities = tf.math.softmax(end_logits, axis=-1)[0].numpy() {/if} -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é. +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 : @@ -297,7 +301,7 @@ scores = np.triu(scores) {/if} -Il ne nous reste plus qu'à obtenir l'indice du maximum. Puisque PyTorch retournera l'index dans le tenseur aplati, nous devons utiliser les opérations division plancher `//` et modulus `%` pour obtenir le `start_index` et le `end_index` : +Il ne nous reste plus qu'à obtenir l'indice du maximum. Puisque PyTorch retourne l'index dans le tenseur aplati, nous devons utiliser les opérations division `//` et modulo `%` pour obtenir le `start_index` et le `end_index` : ```py max_index = scores.argmax().item() @@ -318,7 +322,7 @@ Nous n'avons pas encore tout à fait terminé, mais au moins nous avons déjà l
-Nous avons les `start_index` et `end_index` de la réponse en termes de *tokens*, donc maintenant nous devons juste convertir en indices de caractères dans le contexte. C'est là que les *offsets* seront super utiles. Nous pouvons les saisir et les utiliser comme nous l'avons fait dans la tâche de classification des *tokens* : +Nous avons les `start_index` et `end_index` de la réponse en termes de *tokens*. Maintenant nous devons juste convertir en indices de caractères dans le contexte. C'est là que les *offsets* seront super utiles. Nous pouvons les saisir et les utiliser comme nous l'avons fait dans la tâche de classification des *tokens* : ```py inputs_with_offsets = tokenizer(question, context, return_offsets_mapping=True) @@ -358,7 +362,7 @@ Super ! C'est la même chose que dans notre premier exemple ! ## Gestion des contextes longs -Si nous essayons de tokeniser la question et le long contexte que nous avons utilisé comme exemple précédemment, nous obtiendrons un nombre de *tokens* supérieur à la longueur maximale utilisée dans le pipeline `question-answering` (qui est de 384) : +Si nous essayons de tokeniser la question et le long contexte que nous avons utilisé dans l'exemple précédemment, nous obtenons un nombre de *tokens* supérieur à la longueur maximale utilisée dans le pipeline `question-answering` (qui est de 384) : ```py inputs = tokenizer(question, long_context) @@ -369,7 +373,7 @@ print(len(inputs["input_ids"])) 461 ``` -Nous devrons donc tronquer nos entrées à cette longueur maximale. Il y a plusieurs façons de le faire, mais nous ne voulons pas tronquer la question, seulement le contexte. Puisque le contexte est la deuxième phrase, nous utiliserons la stratégie de troncature `"only_second"`. Le problème qui se pose alors est que la réponse à la question peut ne pas se trouver dans le contexte tronqué. Ici, par exemple, nous avons choisi une question dont la réponse se trouve vers la fin du contexte, et lorsque nous la tronquons, cette réponse n'est pas présente : +Nous devrons donc tronquer nos entrées à cette longueur maximale. Il y a plusieurs façons de le faire mais nous ne voulons pas tronquer la question, seulement le contexte. Puisque le contexte est la deuxième phrase, nous utilisons la stratégie de troncature `"only_second"`. Le problème qui se pose alors est que la réponse à la question peut ne pas se trouver dans le contexte tronqué. Ici, par exemple, nous avons choisi une question dont la réponse se trouve vers la fin du contexte, et lorsque nous la tronquons, cette réponse n'est pas présente : ```py inputs = tokenizer(question, long_context, max_length=384, truncation="only_second") @@ -446,12 +450,13 @@ Pourquoi devrais-je utiliser des transformateurs ? """ ``` -Cela signifie que le modèle aura du mal à trouver la bonne réponse. Pour résoudre ce problème, le pipeline de `question-answering` nous permet de diviser le contexte en morceaux plus petits, en spécifiant la longueur maximale. Pour s'assurer que nous ne divisons pas le contexte exactement au mauvais endroit pour permettre de trouver la réponse, il inclut également un certain chevauchement entre les morceaux. +Cela signifie que le modèle a du mal à trouver la bonne réponse. Pour résoudre ce problème, le pipeline de `question-answering` nous permet de diviser le contexte en morceaux plus petits, en spécifiant la longueur maximale. Pour s'assurer que nous ne divisons pas le contexte exactement au mauvais endroit pour permettre de trouver la réponse, il inclut également un certain chevauchement entre les morceaux. Nous pouvons demander au *tokenizer* (rapide ou lent) de le faire pour nous en ajoutant `return_overflowing_tokens=True`, et nous pouvons spécifier le chevauchement que nous voulons avec l'argument `stride`. Voici un exemple, en utilisant une phrase plus petite : ```py -sentence = "This sentence is not too long but we are going to split it anyway." # "Cette phrase n'est pas trop longue mais nous allons la diviser quand même." +sentence = "This sentence is not too long but we are going to split it anyway." +# "Cette phrase n'est pas trop longue mais nous allons la diviser quand même." inputs = tokenizer( sentence, truncation=True, return_overflowing_tokens=True, max_length=6, stride=2 ) @@ -482,7 +487,7 @@ print(inputs.keys()) dict_keys(['input_ids', 'attention_mask', 'overflow_to_sample_mapping']) ``` -Comme prévu, nous obtenons les ID d'entrée et un masque d'attention. La dernière clé, `overflow_to_sample_mapping`, est une carte qui nous indique à quelle phrase correspond chacun des résultats -- ici nous avons 7 résultats qui proviennent tous de la (seule) phrase que nous avons passée au *tokenizer* : +Comme prévu, nous obtenons les identifiants d'entrée et un masque d'attention. La dernière clé, `overflow_to_sample_mapping`, est une carte qui nous indique à quelle phrase correspond chacun des résultats. Ici nous avons 7 résultats qui proviennent tous de la (seule) phrase que nous avons passée au *tokenizer* : ```py print(inputs["overflow_to_sample_mapping"]) @@ -492,12 +497,14 @@ print(inputs["overflow_to_sample_mapping"]) [0, 0, 0, 0, 0, 0, 0] ``` -C'est plus utile lorsque nous tokenisons plusieurs phrases ensemble. Par exemple, ceci : +C'est plus utile lorsque nous tokenisons plusieurs phrases ensemble. Par exemple : ```py sentences = [ - "This sentence is not too long but we are going to split it anyway.", # Cette phrase n'est pas trop longue mais nous allons la diviser quand même - "This sentence is shorter but will still get split.", # Cette phrase est plus courte mais sera quand même divisée + "This sentence is not too long but we are going to split it anyway.", + # Cette phrase n'est pas trop longue mais nous allons la diviser quand même. + "This sentence is shorter but will still get split.", + # Cette phrase est plus courte mais sera quand même divisée. ] inputs = tokenizer( sentences, truncation=True, return_overflowing_tokens=True, max_length=6, stride=2 @@ -512,9 +519,9 @@ nous donne : [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1] ``` -ce qui signifie que la première phrase est divisée en 7 morceaux comme précédemment, et que les 4 morceaux suivants proviennent de la deuxième phrase. +ce qui signifie que la première phrase est divisée en 7 morceaux comme précédemment et que les 4 morceaux suivants proviennent de la deuxième phrase. -Revenons maintenant à notre contexte long. Par défaut, le pipeline `question-answering` utilise une longueur maximale de 384, comme nous l'avons mentionné plus tôt, et un stride de 128, qui correspondent à la façon dont le modèle a été ajusté (vous pouvez ajuster ces paramètres en passant les arguments `max_seq_len` et `stride` lorsque vous appelez le pipeline). Nous utiliserons donc ces paramètres lors de la tokenisation. Nous ajouterons aussi du *padding* (pour avoir des échantillons de même longueur, afin de pouvoir construire des tenseurs) ainsi que demander les offsets : +Revenons maintenant à notre contexte long. Par défaut, le pipeline `question-answering` utilise une longueur maximale de 384 et un *stride* de 128, qui correspondent à la façon dont le modèle a été *finetuné* (vous pouvez ajuster ces paramètres en passant les arguments `max_seq_len` et `stride` lorsque vous appelez le pipeline). Nous utiliserons donc ces paramètres lors de la tokenisation. Nous ajouterons aussi du *padding* (pour avoir des échantillons de même longueur afin de pouvoir construire des tenseurs) ainsi que demander les *offsets* : ```py inputs = tokenizer( @@ -529,7 +536,7 @@ inputs = tokenizer( ) ``` -Ces `inputs` contiendront les ID d'entrée et les masques d'attention que le modèle attend, ainsi que les offsets et le `overflow_to_sample_mapping` dont on vient de parler. Puisque ces deux éléments ne sont pas des paramètres utilisés par le modèle, nous allons les sortir des `inputs` (et nous ne stockerons pas la carte, puisqu'elle n'est pas utile ici) avant de le convertir en tenseur : +Ces `inputs` contiendront les identifiants d'entrée, les masques d'attention que le modèle attend, ainsi que les *offsets* et le `overflow_to_sample_mapping` dont on vient de parler. Puisque ces deux éléments ne sont pas des paramètres utilisés par le modèle, nous allons les sortir des `inputs` (et nous ne stockerons pas la correspondance puisqu'elle n'est pas utile ici) avant de le convertir en tenseur : {#if fw === 'pt'} @@ -619,7 +626,7 @@ end_logits = tf.where(mask, -10000, end_logits) {/if} -Ensuite, nous pouvons utiliser lafonction softmax pour convertir nos logits en probabilités : +Ensuite, nous pouvons utiliser la fonction softmax pour convertir nos logits en probabilités : {#if fw === 'pt'} @@ -637,7 +644,7 @@ end_probabilities = tf.math.softmax(end_logits, axis=-1).numpy() {/if} -L'étape suivante est similaire à ce que nous avons fait pour le petit contexte, mais nous la répétons pour chacun de nos deux chunks. Nous attribuons un score à tous les espaces de réponse possibles, puis nous prenons l'espace ayant le meilleur score : +L'étape suivante est similaire à ce que nous avons fait pour le petit contexte mais nous la répétons pour chacun de nos deux morceaux. Nous attribuons un score à tous les espaces de réponse possibles puis nous prenons l'espace ayant le meilleur score : {#if fw === 'pt'} @@ -677,7 +684,7 @@ print(candidates) [(0, 18, 0.33867), (173, 184, 0.97149)] ``` -Ces deux candidats correspondent aux meilleures réponses que le modèle a pu trouver dans chaque morceau. Le modèle est beaucoup plus confiant que la bonne réponse se trouve dans la deuxième partie (ce qui est bon signe !). Il ne nous reste plus qu'à faire correspondre ces deux espaces de *tokens* à des espaces de caractères dans le contexte (nous n'avons besoin de faire correspondre que le second pour avoir notre réponse, mais il est intéressant de voir ce que le modèle a choisi dans le premier morceau). +Ces deux candidats correspondent aux meilleures réponses que le modèle a pu trouver dans chaque morceau. Le modèle est beaucoup plus confiant dans le fait que la bonne réponse se trouve dans la deuxième partie (ce qui est bon signe !). Il ne nous reste plus qu'à faire correspondre ces deux espaces de *tokens* à des espaces de caractères dans le contexte (nous n'avons besoin de faire correspondre que le second pour avoir notre réponse, mais il est intéressant de voir ce que le modèle a choisi dans le premier morceau). @@ -685,7 +692,7 @@ Ces deux candidats correspondent aux meilleures réponses que le modèle a pu tr -Le `offsets` que nous avons saisi plus tôt est en fait une liste d'*offsets*, avec une liste par morceau de texte : +Le `offsets` que nous avons saisi plus tôt est en fait une liste d'*offsets* avec une liste par morceau de texte : ```py for candidate, offset in zip(candidates, offsets): @@ -702,11 +709,11 @@ for candidate, offset in zip(candidates, offsets): {'answer': 'Jax, PyTorch and TensorFlow', 'start': 1892, 'end': 1919, 'score': 0.97149} ``` -Si nous ignorons le premier résultat, nous obtenons le même résultat que notre pipeline pour ce long contexte. Yay ! +Si nous ignorons le premier résultat, nous obtenons le même résultat que notre pipeline pour ce long contexte ! -✏️ **Essayez !** Utilisez les meilleurs scores que vous avez calculés auparavant pour montrer les cinq réponses les plus probables (pour l'ensemble du contexte, pas pour chaque morceau). Pour vérifier vos résultats, retournez au premier pipeline et passez `top_k=5` en l'appelant. +✏️ **Essayez !** Utilisez les meilleurs scores que vous avez calculés auparavant pour montrer les cinq réponses les plus probables (pour l'ensemble du contexte, pas pour chaque morceau). Pour vérifier vos résultats, retournez au premier pipeline et spécifiez `top_k=5` en argument en l'appelant. diff --git a/chapters/fr/chapter6/4.mdx b/chapters/fr/chapter6/4.mdx index 314c2d536..0d8ddd4ba 100644 --- a/chapters/fr/chapter6/4.mdx +++ b/chapters/fr/chapter6/4.mdx @@ -1,4 +1,4 @@ -# Normalisation et pré-tokenization +# Normalisation et prétokenization
-Avant de diviser un texte en sous-*tokens* (selon son modèle), le *tokenizer* effectue deux étapes : _normalisation_ et _pré-tokénisation_. +Avant de diviser un texte en sous-*tokens* (selon le modèle), le *tokenizer* effectue deux étapes : la _normalisation_ et la _prétokénisation_. ## Normalisation -L'étape de normalisation implique un nettoyage général, comme la suppression des espaces blancs inutiles, la mise en minuscules et/ou la suppression des accents. Si vous êtes familier avec la [normalisation Unicode](http://www.unicode.org/reports/tr15/) (comme NFC ou NFKC), c'est aussi quelque chose que le *tokenizer* peut appliquer. +L'étape de normalisation implique un nettoyage général, comme la suppression des espaces inutiles, la mise en minuscules et/ou la suppression des accents. Si vous êtes familier avec la [normalisation Unicode](http://www.unicode.org/reports/tr15/) (comme NFC ou NFKC), c'est aussi quelque chose que le *tokenizer* peut appliquer. Le `tokenizer` de 🤗 *Transformers* possède un attribut appelé `backend_tokenizer` qui donne accès au *tokenizer* sous-jacent de la bibliothèque 🤗 *Tokenizers* : @@ -45,7 +45,7 @@ print(tokenizer.backend_tokenizer.normalizer.normalize_str("Héllò hôw are ü? 'hello how are u?' ``` -Dans cet exemple, puisque nous avons choisi le point de contrôle `bert-base-uncased`, la normalisation a appliqué la minuscule et supprimé les accents. +Dans cet exemple, puisque nous avons choisi le *checkpoint* `bert-base-uncased`, la normalisation a mis le texte en minuscule et supprimé les accents. @@ -53,13 +53,13 @@ Dans cet exemple, puisque nous avons choisi le point de contrôle `bert-base-unc -## Pré-tokenization +## Prétokenization -Comme nous le verrons dans les sections suivantes, un *tokenizer* ne peut pas être entraîné uniquement sur du texte brut. Au lieu de cela, nous devons d'abord diviser les textes en petites entités, comme des mots. C'est là qu'intervient l'étape de pré-tokénisation. Comme nous l'avons vu dans le [Chapitre 2](/course/fr/chapter2), un *tokenizer* basé sur les mots peut simplement diviser un texte brut en mots sur les espaces et la ponctuation. Ces mots constitueront les limites des sous-*tokens* que le *tokenizer* peut apprendre pendant son entraînement. +Comme nous le verrons dans les sections suivantes, un *tokenizer* ne peut pas être entraîné uniquement sur du texte brut. Au lieu de cela, nous devons d'abord diviser les textes en petites entités, comme des mots. C'est là qu'intervient l'étape de prétokénisation. Comme nous l'avons vu dans le [chapitre 2](/course/fr/chapter2), un *tokenizer* basé sur les mots peut simplement diviser un texte brut en mots sur les espaces et la ponctuation. Ces mots constitueront les limites des sous-*tokens* que le *tokenizer* peut apprendre pendant son entraînement. -Pour voir comment un *tokenizer* rapide effectue la pré-tokénisation, nous pouvons utiliser la méthode `pre_tokenize_str()`de l'attribut `pre_tokenizer` de l'objet `tokenizer` : +Pour voir comment un *tokenizer* rapide effectue la prétokénisation, nous pouvons utiliser la méthode `pre_tokenize_str()`de l'attribut `pre_tokenizer` de l'objet `tokenizer` : ```py tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") @@ -69,16 +69,16 @@ tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you? [('Hello', (0, 5)), (',', (5, 6)), ('how', (7, 10)), ('are', (11, 14)), ('you', (16, 19)), ('?', (19, 20))] ``` -Remarquez que le *tokenizer* garde déjà la trace des décalages, ce qui lui permet de nous donner la correspondance des décalages que nous avons utilisée dans la section précédente. Ici, le *tokenizer* ignore les deux espaces et les remplace par un seul, mais le décalage saute entre `are` et `you` pour en tenir compte. +Remarquez que le *tokenizer* garde déjà la trace des *offsets*, ce qui lui permet de nous donner la correspondance des décalages que nous avons utilisée dans la section précédente. Ici, le *tokenizer* ignore les deux espaces et les remplace par un seul, mais le décalage saute entre `are` et `you` pour en tenir compte. -Puisque nous utilisons un *tokenizer* de BERT, la pré-tokénisation implique la séparation des espaces et de la ponctuation. D'autres *tokenizers* peuvent avoir des règles différentes pour cette étape. Par exemple, si nous utilisons le *tokenizer* GPT-2 : +Puisque nous utilisons le *tokenizer* de BERT, la prétokénisation implique la séparation des espaces et de la ponctuation. D'autres *tokenizers* peuvent avoir des règles différentes pour cette étape. Par exemple, si nous utilisons le *tokenizer* du GPT-2 : ```py tokenizer = AutoTokenizer.from_pretrained("gpt2") tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") ``` -cela séparera aussi sur les espaces et la ponctuation, mais il gardera les espaces et les remplacera par un symbole `Ġ`, ce qui lui permettra de récupérer les espaces originaux si nous décodons les *tokens* : +Il séparera aussi sur les espaces et la ponctuation mais gardera les espaces et les remplacera par un symbole `Ġ`, ce qui lui permettra de récupérer les espaces originaux si nous décodons les *tokens* : ```python out [('Hello', (0, 5)), (',', (5, 6)), ('Ġhow', (6, 10)), ('Ġare', (10, 14)), ('Ġ', (14, 15)), ('Ġyou', (15, 19)), @@ -87,7 +87,7 @@ cela séparera aussi sur les espaces et la ponctuation, mais il gardera les espa Notez également que, contrairement au *tokenizer* de BERT, ce *tokenizer* n'ignore pas les doubles espaces. -Pour un dernier exemple, regardons le *tokenizer* T5, qui est basé sur l'algorithme SentencePiece : +Pour un dernier exemple, regardons le *tokenizer* du 5, qui est basé sur l'algorithme SentencePiece : ```py tokenizer = AutoTokenizer.from_pretrained("t5-small") @@ -98,26 +98,26 @@ tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you? [('▁Hello,', (0, 6)), ('▁how', (7, 10)), ('▁are', (11, 14)), ('▁you?', (16, 20))] ``` -Comme le *tokenizer* du GPT-2, celui-ci garde les espaces et les remplace par un token spécifique (`_`), mais le tokenizer T5 ne sépare que sur les espaces, pas sur la ponctuation. Notez également qu'il a ajouté un espace par défaut au début de la phrase (avant `Hello`) et a ignoré le double espace entre `are` et `you`. +Comme le *tokenizer* du GPT-2, celui-ci garde les espaces et les remplace par un *token* spécifique (`_`), mais le *tokenizer* du T5 ne sépare que sur les espaces, pas sur la ponctuation. Notez également qu'il a ajouté un espace par défaut au début de la phrase (avant `Hello`) et il ignore le double espace entre `are` et `you`. -Maintenant que nous avons vu un peu comment les différents *tokenizers* traitent le texte, nous pouvons commencer à explorer les algorithmes sous-jacents eux-mêmes. Nous commencerons par jeter un coup d'oeil rapide sur le très répandu SentencePiece ; puis, au cours des trois sections suivantes, nous examinerons le fonctionnement des trois principaux algorithmes utilisés pour la tokenisation en sous-mots. +Maintenant que nous avons vu un peu comment les différents *tokenizers* traitent le texte, nous pouvons commencer à explorer les algorithmes sous-jacents eux-mêmes. Nous commencerons par jeter un coup d'oeil rapide sur le très répandu SentencePiece, puis au cours des trois sections suivantes nous examinerons le fonctionnement des trois principaux algorithmes utilisés pour la tokenisation en sous-mots. ## SentencePiece -[SentencePiece](https://github.com/google/sentencepiece) est un algorithme de tokenization pour le prétraitement du texte que vous pouvez utiliser avec n'importe lequel des modèles que nous verrons dans les trois prochaines sections. Il considère le texte comme une séquence de caractères Unicode, et remplace les espaces par un caractère spécial, `▁`. Utilisé en conjonction avec l'algorithme Unigram (voir [section 7](/course/fr/chapter7/7)), il ne nécessite même pas d'étape de pré-tokénisation, ce qui est très utile pour les langues où le caractère espace n'est pas utilisé (comme le chinois ou le japonais). +[SentencePiece](https://github.com/google/sentencepiece) est un algorithme de tokenisation pour le prétraitement du texte que vous pouvez utiliser avec n'importe lequel des modèles que nous verrons dans les trois prochaines sections. Il considère le texte comme une séquence de caractères Unicode et il remplace les espaces par un caractère spécial : `▁`. Utilisé en conjonction avec l'algorithme Unigram (voir la [section 7](/course/fr/chapter7/7)), il ne nécessite même pas d'étape de prétokénisation, ce qui est très utile pour les langues où le caractère espace n'est pas utilisé (comme le chinois ou le japonais). -L'autre caractéristique principale de SentencePiece est le *tokenizer* réversible : comme il n'y a pas de traitement spécial des espaces, le décodage des *tokens* se fait simplement en les concaténant et en remplaçant les `_` par des espaces, ce qui donne le texte normalisé. Comme nous l'avons vu précédemment, le *tokenizer* de BERT supprime les espaces répétitifs, donc sa tokenisation n'est pas réversible. +L'autre caractéristique principale de SentencePiece est le *tokenisation réversible* : comme il n'y a pas de traitement spécial des espaces, le décodage des *tokens* se fait simplement en les concaténant et en remplaçant les `_` par des espaces, ce qui donne le texte normalisé. Comme nous l'avons vu précédemment, le *tokenizer* de BERT supprime les espaces répétitifs, donc sa tokenisation n'est pas réversible. -## Aperçu de l'algorithme +## Vue d'ensemble des algorithmes -Dans les sections suivantes, nous allons nous plonger dans les trois principaux algorithmes de tokenisation des sous-mots : BPE (utilisé par GPT-2 et autres), WordPiece (utilisé par exemple par BERT), et Unigram (utilisé par T5 et autres). Avant de commencer, voici un rapide aperçu du fonctionnement de chacun d'entre eux. N'hésitez pas à revenir à ce tableau après avoir lu chacune des sections suivantes si cela n'a pas encore de sens pour vous. +Dans les sections suivantes, nous allons nous plonger dans les trois principaux algorithmes de tokenisation en sous-mots : BPE (utilisé par GPT-2 et autres), WordPiece (utilisé par exemple par BERT), et Unigram (utilisé par T5 et autres). Avant de commencer, voici un rapide aperçu du fonctionnement de chacun d'entre eux. N'hésitez pas à revenir à ce tableau après avoir lu chacune des sections suivantes si cela n'a pas encore de sens pour vous. Modèle | BPE | WordPiece | Unigramme :----:|:---:|:---------:|:------: -Entraînement | Part d'un petit vocabulaire et apprend des règles pour fusionner les *tokens* | Part d'un petit vocabulaire et apprend des règles pour fusionner les *tokens* | Part d'un grand vocabulaire et apprend des règles pour supprimer les *tokens*. -Étape d'Entraînement | Fusionne les *tokens* correspondant à la paire la plus commune | Fusionne les *tokens* correspondant à la paire ayant le meilleur score basé sur la fréquence de la paire, en privilégiant les paires où chaque *token* individuel est moins fréquent | Supprime tous les *tokens* du vocabulaire qui minimiseront la perte calculée sur le corpus entier -Apprendre | Fusionner des règles et un vocabulaire | Juste un vocabulaire | Un vocabulaire avec un score pour chaque *token* -Encodage | Découpe un mot en caractères et applique les fusions apprises pendant l'entraînement | Trouve le plus long sous-mot depuis le début qui est dans le vocabulaire, puis fait de même pour le reste du mot | Trouve la division la plus probable en tokens, en utilisant les scores appris pendant l'entraînement +Entraînement | Part d'un petit vocabulaire et apprend des règles pour fusionner les *tokens* | Part d'un petit vocabulaire et apprend des règles pour fusionner les *tokens* | Part d'un grand vocabulaire et apprend des règles pour supprimer les *tokens* +Étape d'entraînement | Fusionne les *tokens* correspondant à la paire la plus commune | Fusionne les *tokens* correspondant à la paire ayant le meilleur score basé sur la fréquence de la paire, en privilégiant les paires où chaque *token* individuel est moins fréquent | Supprime tous les *tokens* du vocabulaire qui minimiseront la perte calculée sur le corpus entier +Apprend | A fusionner des règles et un vocabulaire | Juste un vocabulaire | Un vocabulaire avec un score pour chaque *token* +Encodage | Découpe un mot en caractères et applique les fusions apprises pendant l'entraînement | Trouve le plus long sous-mot depuis le début qui est dans le vocabulaire puis fait de même pour le reste du mot | Trouve la division la plus probable en tokens, en utilisant les scores appris pendant l'entraînement -Maintenant, plongeons dans BPE ! +Maintenant, plongeons dans le BPE ! diff --git a/chapters/fr/chapter6/5.mdx b/chapters/fr/chapter6/5.mdx index 8f899884a..a20f96555 100644 --- a/chapters/fr/chapter6/5.mdx +++ b/chapters/fr/chapter6/5.mdx @@ -1,4 +1,4 @@ -# Tokénisation *Byte-Pair Encoding* +# Tokénisation Byte-Pair Encoding -*Byte-Pair Encoding* (BPE) a été initialement développé en tant qu'algorithme de compression de textes, puis utilisé par OpenAI pour la tokenisation lors du pré-entraînement du modèle GPT. Il est utilisé par de nombreux modèles Transformer, dont GPT, GPT-2, RoBERTa, BART et DeBERTa. +Le *Byte-Pair Encoding* (BPE) a été initialement développé en tant qu'algorithme de compression de textes puis utilisé par OpenAI pour la tokenisation du pré-entraînement du modèle GPT. Il est utilisé par de nombreux *transformers* dont GPT, GPT-2, RoBERTa, BART et DeBERTa. @@ -19,23 +19,23 @@ ## Algorithme d'entraînement -L'entraînement du BPE commence par le calcul de l'ensemble unique de mots utilisés dans le corpus (après les étapes de normalisation et de pré-tokénisation), puis la construction du vocabulaire en prenant tous les symboles utilisés pour écrire ces mots. A titre d'exemple très simple, disons que notre corpus utilise ces cinq mots : +L'entraînement du BPE commence par le calcul de l'unique ensemble de mots utilisés dans le corpus (après les étapes de normalisation et de prétokénisation), puis la construction du vocabulaire en prenant tous les symboles utilisés pour écrire ces mots. A titre d'exemple, disons que notre corpus utilise ces cinq mots : ``` -"hug", "pug", "pun", "bun", "hugs" # "câlin", "carlin", "jeu de mots", "brioche", "câlins"... +"hug", "pug", "pun", "bun", "hugs" # "câlin", "carlin", "jeu de mots", "brioche", "câlins" ``` -Le vocabulaire de base sera alors `["b", "g", "h", "n", "p", "s", "u"]`. Dans le monde réel, ce vocabulaire de base contiendra au moins tous les caractères ASCII, et probablement aussi quelques caractères Unicode. Si un exemple que vous tokenisez utilise un caractère qui n'est pas dans le corpus d'entraînement, ce caractère sera converti en *token* inconnu. C'est l'une des raisons pour lesquelles de nombreux modèles de NLP sont très mauvais dans l'analyse de contenus contenant des emojis, par exemple. +Le vocabulaire de base sera alors `["b", "g", "h", "n", "p", "s", "u"]`. Dans le monde réel, le vocabulaire de base contient au moins tous les caractères ASCII et probablement aussi quelques caractères Unicode. Si un exemple que vous tokenisez utilise un caractère qui n'est pas dans le corpus d'entraînement, ce caractère est converti en *token* inconnu. C'est l'une des raisons pour lesquelles de nombreux modèles de NLP sont par exemple très mauvais dans l'analyse de contenus contenant des emojis. -Les *tokenizer* de GPT-2 et de RoBERTa (qui sont assez similaires) ont une façon intelligente de gérer ce problème : ils ne considèrent pas les mots comme étant écrits avec des caractères Unicode, mais avec des octets. De cette façon, le vocabulaire de base a une petite taille (256), mais tous les caractères auxquels vous pouvez penser seront inclus et ne finiront pas par être convertis en un token inconnu. Cette astuce est appelée *byte-level BPE*. +Les *tokenizers* du GPT-2 et de RoBERTa (qui sont assez similaires) ont une façon intelligente de gérer ce problème : ils ne considèrent pas les mots comme étant écrits avec des caractères Unicode mais avec des octets. De cette façon, le vocabulaire de base a une petite taille (256) et tous les caractères auxquels vous pouvez penser seront inclus dedans et ne finiront pas par être convertis en un *token* inconnu. Cette astuce est appelée *byte-level BPE*. -Après avoir obtenu ce vocabulaire de base, nous ajoutons de nouveaux *tokens* jusqu'à ce que la taille souhaitée du vocabulaire soit atteinte en apprenant les *merges*, qui sont des règles permettant de fusionner deux éléments du vocabulaire existant pour en créer un nouveau. Ainsi, au début, ces fusions créeront des *tokens* de deux caractères, puis, au fur et à mesure de l'entraînement, des sous-mots plus longs. +Après avoir obtenu ce vocabulaire de base, nous ajoutons de nouveaux *tokens* jusqu'à ce que la taille souhaitée du vocabulaire soit atteinte en apprenant les fusions qui sont des règles permettant de fusionner deux éléments du vocabulaire existant pour en créer un nouveau. Ainsi, au début, ces fusions créeront des *tokens* de deux caractères, puis au fur et à mesure de l'entraînement, des sous-mots plus longs. -À chaque étape de l'entraînement du *tokenizer*, l'algorithme BPE recherche la paire la plus fréquente de *tokens* existants (par "paire", nous entendons ici deux *tokens* consécutifs dans un mot). Cette paire la plus fréquente est celle qui sera fusionnée, et nous rinçons et répétons pour l'étape suivante. +À chaque étape de l'entraînement du *tokenizer*, l'algorithme BPE recherche la paire la plus fréquente de *tokens* existants (par « paire », nous entendons ici deux *tokens* consécutifs dans un mot). Cette paire la plus fréquente est celle qui sera fusionnée. Nous rinçons et répétons pour l'étape suivante. Pour revenir à notre exemple précédent, supposons que les mots ont les fréquences suivantes : @@ -43,29 +43,29 @@ Pour revenir à notre exemple précédent, supposons que les mots ont les fréqu ("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) ``` -ce qui veut dire que `"hug"` était présent 10 fois dans le corpus, `"pug"` 5 fois, `"pun"` 12 fois, `"bun"` 4 fois, et `"hugs"`" 5 fois. Nous commençons l'entraînement en divisant chaque mot en caractères (ceux qui forment notre vocabulaire initial) afin de voir chaque mot comme une liste de *tokens* : +ce qui veut dire que `"hug"` était présent 10 fois dans le corpus, `"pug"` 5 fois, `"pun"` 12 fois, `"bun"` 4 fois et `"hugs"`" 5 fois. Nous commençons l'entraînement en divisant chaque mot en caractères (ceux qui forment notre vocabulaire initial) afin de voir chaque mot comme une liste de *tokens* : ``` ("h" "u" "g", 10), ("p" "u" "g", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "u" "g" "s", 5) ``` -Ensuite, nous regardons les paires. La paire `("h", "u")` est présente dans les mots `"hug"` et `"hugs"`, donc 15 fois au total dans le corpus. Ce n'est pas la paire la plus fréquente, cependant : cet honneur revient à `("u", "g")`, qui est présent dans `"hug"`, `"pug"`, et `"hugs"`, pour un grand total de 20 fois dans le vocabulaire. +Ensuite, nous regardons les paires. La paire `("h", "u")` est présente dans les mots `"hug"` et `"hugs"`, donc 15 fois au total dans le corpus. Ce n'est cependant pas la paire la plus fréquente. Cet honneur revient à `("u", "g")` qui est présent dans `"hug"`, `"pug"`, et `"hugs"`, pour un total de 20 fois dans le vocabulaire. -Ainsi, la première règle de fusion apprise par le *tokenizer* est `("u", "g") -> "ug"`, ce qui signifie que `"ug"` sera ajouté au vocabulaire, et que la paire devra être fusionnée dans tous les mots du corpus. A la fin de cette étape, le vocabulaire et le corpus ressemblent à ceci : +Ainsi, la première règle de fusion apprise par le *tokenizer* est `("u", "g") -> "ug"`, ce qui signifie que `"ug"` est ajouté au vocabulaire et que la paire doit être fusionnée dans tous les mots du corpus. A la fin de cette étape, le vocabulaire et le corpus ressemblent à ceci : ``` 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) ``` -Nous avons maintenant quelques paires qui aboutissent à un token de plus de deux caractères : la paire `("h", "ug")`, par exemple (présente 15 fois dans le corpus). La paire la plus fréquente à ce stade est `("u", "n")`, cependant, présente 16 fois dans le corpus, donc la deuxième règle de fusion apprise est `("u", "n") -> "un"`. Ajouter cela au vocabulaire et fusionner toutes les occurrences existantes nous conduit à : +Nous avons maintenant quelques paires qui aboutissent à un *token* de plus de deux caractères. Par exemple la paire `("h", "ug")` présente 15 fois dans le corpus. La paire la plus fréquente à ce stade est `("u", "n")`, présente 16 fois dans le corpus, donc la deuxième règle de fusion apprise est `("u", "n") -> "un"`. En ajoutant cela au vocabulaire et en fusionnant toutes les occurrences existantes, nous obtenons : ``` 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) ``` -Maintenant la paire la plus fréquente est `("h", "ug")`, donc nous apprenons la règle de fusion `("h", "ug") -> "hug"`, ce qui nous donne notre premier *token* de trois lettres. Après la fusion, le corpus ressemble à ceci : +Maintenant la paire la plus fréquente est `("h", "ug")` donc nous apprenons la règle de fusion `("h", "ug") -> "hug"`. Cela nous donne donc notre premier *token* de trois lettres. Après la fusion, le corpus ressemble à ceci : ``` Vocabulary: ["b", "g", "h", "n", "p", "s", "u", "ug", "un", "hug"] @@ -85,7 +85,7 @@ Et nous continuons ainsi jusqu'à ce que nous atteignions la taille de vocabulai La tokenisation suit de près le processus d'entraînement, dans le sens où les nouvelles entrées sont tokenisées en appliquant les étapes suivantes : 1. Normalisation -2. Pré-tokénisation. +2. Prétokénisation 3. Découpage des mots en caractères individuels 4. Application des règles de fusion apprises dans l'ordre sur ces divisions. @@ -97,30 +97,34 @@ Prenons l'exemple que nous avons utilisé pendant l'entraînement, avec les troi ("h", "ug") -> "hug" ``` -Le mot " bug " sera traduit par " ["b", "ug"]` ". Par contre, le mot " mug " sera traduit par " ["[UNK]", "ug"]` " puisque la lettre " m " ne fait pas partie du vocabulaire de base. De la même façon, le mot " thug "` sera tokenisé comme "["[UNK]", "hug"]` : la lettre `"t"` n'est pas dans le vocabulaire de base, et l'application des règles de fusion résulte d'abord en la fusion de `"u"` et `"g"` et ensuite en la fusion de `"hu"` et `"g"`. +Le mot « bug » sera traduit par « ["b", "ug"] ». Par contre, le mot « mug » (tasse en français) sera traduit par « ["[UNK]", "ug"] » puisque la lettre « m » ne fait pas partie du vocabulaire de base. De la même façon, le mot « thug » (voyou en français) sera tokenisé en « ["[UNK]", "hug"] » car la lettre « t » n'est pas dans le vocabulaire de base et l'application des règles de fusion résulte d'abord en la fusion de « u » et « g » et ensuite en la fusion de « hu » et « g ». -✏️ **A votre tour !** Comment pensez-vous que le mot "unhug"`` sera tokenized ? +✏️ **A votre tour !** Comment pensez-vous que le mot « unhug » (détacher en français) sera tokenized ? -## Mise en œuvre du BPE +## Implémentation du BPE -Voyons maintenant une implémentation de l'algorithme BPE. Il ne s'agira pas d'une version optimisée que vous pourrez utiliser sur un grand corpus ; nous voulons simplement vous montrer le code afin que vous puissiez comprendre un peu mieux l'algorithme. +Voyons maintenant une implémentation de l'algorithme BPE. Il ne s'agira pas d'une version optimisée que vous pourrez utiliser sur un grand corpus. Nous voulons simplement vous montrer le code afin que vous puissiez comprendre un peu mieux l'algorithme. Tout d'abord, nous avons besoin d'un corpus, alors créons un corpus simple avec quelques phrases : ```python corpus = [ - "This is the Hugging Face course.", # C'est le cours d'Hugging Face. - "This chapter is about tokenization.", # This chapter is about tokenization - "This section shows several tokenizer algorithms.", # Cette section présente plusieurs algorithmes de *tokenizer*. - "Hopefully, you will be able to understand how they are trained and generate tokens.", # Avec un peu de chance, vous serez en mesure de comprendre comment ils sont entraînés et génèrent des *tokens*. + "This is the Hugging Face Course.", + # C'est le cours d'Hugging Face. + "This chapter is about tokenization.", + # Ce chapitre traite de la tokenisation. + "This section shows several tokenizer algorithms.", + # Cette section présente plusieurs algorithmes de *tokenizer*. + "Hopefully, you will be able to understand how they are trained and generate tokens.", + # Avec un peu de chance, vous serez en mesure de comprendre comment ils sont entraînés et génèrent des *tokens*. ] ``` -Ensuite, nous devons pré-tokeniser ce corpus en mots. Puisque nous répliquons un *tokenizer* BPE (comme GPT-2), nous utiliserons le *tokenizer* `gpt2` pour la pré-tokénisation : +Ensuite, nous devons prétokeniser ce corpus en mots. Puisque nous répliquons un *tokenizer* BPE (comme celui du GPT-2), nous utiliserons le *tokenizer* `gpt2` pour la prétokénisation : ```python from transformers import AutoTokenizer @@ -128,7 +132,7 @@ from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("gpt2") ``` -Ensuite, nous calculons les fréquences de chaque mot dans le corpus comme nous le faisons pour la pré-tokénisation : +Ensuite, nous calculons les fréquences de chaque mot dans le corpus comme nous le faisons pour la prétokénisation : ```python from collections import defaultdict @@ -170,13 +174,13 @@ print(alphabet) 't', 'u', 'v', 'w', 'y', 'z', 'Ġ'] ``` -Nous ajoutons également les *tokens* spéciaux utilisés par le modèle au début de ce vocabulaire. Dans le cas de GPT-2, le seul token spécial est `"<|endoftext|>"` : +Nous ajoutons également les *tokens* spéciaux utilisés par le modèle au début de ce vocabulaire. Dans le cas du GPT-2, le seul *token* spécial est `"<|endoftext|>"` : ```python vocab = ["<|endoftext|>"] + alphabet.copy() ``` -Nous devons maintenant diviser chaque mot en caractères individuels, pour pouvoir commencer l'entraînement : +Nous devons maintenant diviser chaque mot en caractères individuels pour pouvoir commencer l'entraînement : ```python splits = {word: [c for c in word] for word in word_freqs.keys()} @@ -290,7 +294,7 @@ while len(vocab) < vocab_size: vocab.append(best_pair[0] + best_pair[1]) ``` -En conséquence, nous avons appris 19 règles de fusion (le vocabulaire initial avait une taille de 31 : 30 caractères dans l'alphabet, plus le *token* spécial) : +En conséquence, nous avons appris 19 règles de fusion (le vocabulaire initial avait une taille de 31 : 30 caractères dans l'alphabet plus le *token* spécial) : ```py print(merges) @@ -321,7 +325,7 @@ print(vocab)
-Pour tokeniser un nouveau texte, on le pré-tokenise, on le divise, puis on applique toutes les règles de fusion apprises : +Pour tokeniser un nouveau texte, on le prétokenise, on le divise, puis on applique toutes les règles de fusion apprises : ```python def tokenize(text): @@ -353,8 +357,8 @@ tokenize("This is not a token.") -⚠️ Notre implémentation lancera une erreur s'il y a un caractère inconnu puisque nous n'avons rien fait pour les gérer. GPT-2 n'a pas réellement de jeton inconnu (il est impossible d'obtenir un caractère inconnu en utilisant le BPE au niveau de l'octet), mais cela pourrait arriver ici parce que nous n'avons pas inclus tous les octets possibles dans le vocabulaire initial. Cet aspect du BPE dépasse le cadre de cette section, nous avons donc laissé les détails de côté. +⚠️ Notre implémentation lancera une erreur s'il y a un caractère inconnu puisque nous n'avons rien fait pour les gérer. GPT-2 n'a pas réellement de token inconnu (il est impossible d'obtenir un caractère inconnu en utilisant le BPE au niveau de l'octet) mais cela pourrait arriver ici car nous n'avons pas inclus tous les octets possibles dans le vocabulaire initial. Cet aspect du BPE dépasse le cadre de cette section, nous avons donc laissé ces détails de côté. -C'est tout pour l'algorithme BPE ! Ensuite, nous allons nous intéresser à WordPiece. \ No newline at end of file +C'est tout pour l'algorithme BPE ! Nous allons nous intéresser à WordPiece dans la suite. \ No newline at end of file diff --git a/chapters/fr/chapter6/6.mdx b/chapters/fr/chapter6/6.mdx index 701e4d7f8..115588c63 100644 --- a/chapters/fr/chapter6/6.mdx +++ b/chapters/fr/chapter6/6.mdx @@ -1,4 +1,4 @@ -# Tokénisation *WordPiece* +# Tokénisation WordPiece -*WordPiece* est l'algorithme de tokénisation développé par Google pour prétraîner BERT. Il a depuis été réutilisé dans un grand nombre de modèles de transformateurs basés sur BERT, tels que DistilBERT, MobileBERT, Funnel Transformers et MPNET. Il est très similaire à BPE en termes d'entraînement, mais la tokenisation réelle est effectuée différemment. +*WordPiece* est l'algorithme de tokénisation développé par Google pour prétraîner BERT. Il a depuis été réutilisé dans un grand nombre de modèles de *transformers* basés sur BERT tels que DistilBERT, MobileBERT, Funnel Transformers et MPNET. Il est très similaire au BPE en termes d'entraînement mais la tokenisation réelle est effectuée différemment. -💡 Cette section couvre le *WordPiece* en profondeur, allant jusqu'à montrer une implémentation complète. Vous pouvez passer directement à la fin si vous souhaitez simplement avoir un aperçu général de l'algorithme de tokénisation. +💡 Cette section couvre le WordPiece en profondeur, allant jusqu'à montrer une implémentation complète. Vous pouvez passer directement à la fin si vous souhaitez simplement avoir un aperçu général de l'algorithme de tokénisation. @@ -21,11 +21,11 @@ -⚠️ Google n'a jamais mis en ligne son implémentation de l'algorithme d'entraînement de *WordPiece*. Ce qui suit est donc notre meilleure estimation basée sur la littérature publiée. Il se peut qu'elle ne soit pas exacte à 100 %. +⚠️ Google n'a jamais mis en ligne son implémentation de l'algorithme d'entraînement du WordPiece. Ce qui suit est donc notre meilleure estimation basée sur la littérature publiée. Il se peut qu'elle ne soit pas exacte à 100 %. -Comme le BPE, *WordPiece* part d'un petit vocabulaire comprenant les *tokens* spéciaux utilisés par le modèle et l'alphabet initial. Puisqu'il identifie les sous-mots en ajoutant un préfixe (comme `##` pour BERT), chaque mot est initialement découpé en ajoutant ce préfixe à tous les caractères du mot. Ainsi, par exemple, `"mot"` est divisé comme ceci : +Comme le BPE, *WordPiece* part d'un petit vocabulaire comprenant les *tokens* spéciaux utilisés par le modèle et l'alphabet initial. Puisqu'il identifie les sous-mots en ajoutant un préfixe (comme `##` pour BERT), chaque mot est initialement découpé en ajoutant ce préfixe à tous les caractères du mot. Ainsi par exemple, `"word"` est divisé comme ceci : ``` w ##o ##r ##d @@ -33,11 +33,11 @@ w ##o ##r ##d Ainsi, l'alphabet initial contient tous les caractères présents au début d'un mot et les caractères présents à l'intérieur d'un mot précédé du préfixe de *WordPiece*. -Ensuite, toujours comme le BPE, *WordPiece* apprend des règles de fusion. La principale différence réside dans la manière dont la paire à fusionner est sélectionnée. Au lieu de sélectionner la paire la plus fréquente, *WordPiece* calcule un score pour chaque paire, en utilisant la formule suivante : +Ensuite, toujours comme le BPE, *WordPiece* apprend des règles de fusion. La principale différence réside dans la manière dont la paire à fusionner est sélectionnée. Au lieu de sélectionner la paire la plus fréquente, *WordPiece* calcule un score pour chaque paire en utilisant la formule suivante : $$\mathrm{score} = (\mathrm{freq\_of\_pair}) / (\mathrm{freq\_of\_first\_element} \times \mathrm{freq\_of\_second\_element})$$ -En divisant la fréquence de la paire par le produit des fréquences de chacune de ses parties, l'algorithme donne la priorité à la fusion des paires dont les parties individuelles sont moins fréquentes dans le vocabulaire. Par exemple, il ne fusionnera pas nécessairement `("un", "##able")` même si cette paire apparaît très fréquemment dans le vocabulaire, car les deux paires `"un"`" et `"##able"` apparaîtront probablement chacune dans un lot d'autres mots et auront une fréquence élevée. En revanche, une paire comme `("hu", "##gging")` sera probablement fusionnée plus rapidement (en supposant que le mot "hugging" apparaisse souvent dans le vocabulaire) puisque `"hu"` et `"##gging"` sont probablement moins fréquents individuellement. +En divisant la fréquence de la paire par le produit des fréquences de chacune de ses parties, l'algorithme donne la priorité à la fusion des paires dont les parties individuelles sont moins fréquentes dans le vocabulaire. Par exemple, il ne fusionnera pas nécessairement `("un", "##able")` même si cette paire apparaît très fréquemment dans le vocabulaire car les deux paires `"un"`" et `"##able"` apparaîtront probablement chacune dans un batch d'autres mots et auront une fréquence élevée. En revanche, une paire comme `("hu", "##gging")` sera probablement fusionnée plus rapidement (en supposant que le mot `"hugging"` apparaisse souvent dans le vocabulaire) puisque `"hu"` et `"##gging"` sont probablement moins fréquents individuellement. Examinons le même vocabulaire que celui utilisé dans l'exemple d'entraînement du BPE : @@ -51,7 +51,7 @@ Les divisions ici seront : ("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##g" "##s", 5) ``` -Le vocabulaire initial sera donc `["b", "h", "p", "##g", "##n", "##s", "##u"]` (si on oublie les *tokens* spéciaux pour l'instant). La paire la plus fréquente est `("##u", "##g")` (présente 20 fois), mais la fréquence individuelle de `"##u"` est très élevée, donc son score n'est pas le plus élevé (il est de 1 / 36). Toutes les paires avec un `"##u"` ont en fait le même score (1 / 36), donc le meilleur score va à la paire `("##g", "##s")` -- la seule sans un `"##u"` -- à 1 / 20, et la première fusion apprise est `("##g", "##s") -> ("##gs")`. +Si on oublie les *tokens* spéciaux pour l'instant, le vocabulaire initial sera donc `["b", "h", "p", "##g", "##n", "##s", "##u"]`. La paire la plus fréquente est `("##u", "##g")` (présente 20 fois), mais la fréquence individuelle de `"##u"` est très élevée, donc son score n'est pas le plus élevé (il est de 1 / 36). Toutes les paires avec un `"##u"` ont en fait le même score (1 / 36). Ainsi le meilleur score va à la paire `("##g", "##s")` qui est la seule sans un `"##u"` avec un score 1 / 20. Et la première fusion apprise est `("##g", "##s") -> ("##gs")`. Notez que lorsque nous fusionnons, nous enlevons le `##` entre les deux *tokens*, donc nous ajoutons `"##gs"` au vocabulaire et appliquons la fusion dans les mots du corpus : @@ -67,7 +67,7 @@ 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) ``` -Ensuite, le meilleur score suivant est partagé par `("hu", "##g")` et `("hu", "##gs")` (avec 1/15, comparé à 1/21 pour toutes les autres paires), donc la première paire avec le plus grand score est fusionnée : +Ensuite, le meilleur score suivant est partagé par `("hu", "##g")` et `("hu", "##gs")` (avec 1/15, comparé à 1/21 pour toutes les autres paires). Ainsi la première paire avec le plus grand score est fusionnée : ``` Vocabulary: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu", "hug"] @@ -84,13 +84,13 @@ et nous continuons ainsi jusqu'à ce que nous atteignions la taille de vocabulai ## Algorithme de tokenisation -La tokénisation diffère dans *WordPiece* et BPE en ce que *WordPiece* ne sauvegarde que le vocabulaire final, pas les règles de fusion apprises. En partant du mot à tokeniser, *WordPiece* trouve le sous-mot le plus long qui se trouve dans le vocabulaire, puis se sépare sur celui-ci. Par exemple, si nous utilisons le vocabulaire appris dans l'exemple ci-dessus, pour le mot `"hugs"` le plus long sous-mot en partant du début qui est dans le vocabulaire est `"hug"`, donc nous le divisons et obtenons `["hug", "##s"]`. On continue avec `"##s"`, qui est dans le vocabulaire, donc la tokenisation de `"hugs"` est `["hug", "##s"]`. +La tokénisation diffère dans *WordPiece* et BPE en ce que *WordPiece* ne sauvegarde que le vocabulaire final et non pas les règles de fusion apprises. En partant du mot à tokeniser, *WordPiece* trouve le sous-mot le plus long qui se trouve dans le vocabulaire, puis se sépare sur celui-ci. Par exemple, si nous utilisons le vocabulaire appris dans l'exemple ci-dessus, pour le mot `"hugs"` le plus long sous-mot en partant du début qui est dans le vocabulaire est `"hug"`. Donc nous le divisons et obtenons `["hug", "##s"]`. On continue avec `"##s"`, qui est dans le vocabulaire, donc la tokenisation de `"hugs"` est `["hug", "##s"]`. Avec BPE, nous aurions appliqué les fusions apprises dans l'ordre et la tokénisation aurait été `["hu", "##gs"]`, l'encodage est donc différent. -Comme autre exemple, voyons comment le mot `"bugs"` serait tokenisé. `"b"` est le plus long sous-mot commençant au début du mot qui est dans le vocabulaire, donc on le divise et on obtient `["b", "##ugs"]`. Ensuite, `"##u"` est le plus long sous-mot commençant au début de `"##ugs"` qui est dans le vocabulaire, donc on le sépare et on obtient `["b", "##u, "##gs"]`. Enfin, `"##gs"` est dans le vocabulaire, donc cette dernière liste est la tokenization de `"bugs"`. +Comme autre exemple, voyons comment le mot `"bugs"` serait tokenisé. `"b"` est le plus long sous-mot commençant au début du mot qui est dans le vocabulaire donc on le divise et on obtient `["b", "##ugs"]`. Ensuite, `"##u"` est le plus long sous-mot commençant au début de `"##ugs"` qui est dans le vocabulaire, donc on le sépare et on obtient `["b", "##u, "##gs"]`. Enfin, `"##gs"` est dans le vocabulaire, donc cette dernière liste est la tokenization de `"bugs"`. -Lorsque la tokenisation arrive à un stade où il n'est pas possible de trouver un sous-mot dans le vocabulaire, le mot entier est tokenisé comme inconnu -- donc, par exemple, `"mug"` serait tokenisé comme `["[UNK]"]`, tout comme " bum " (même si on peut commencer par " b " et " ##u ", " ##m " ne fait pas partie du vocabulaire, et le *tokenizer* résultant sera simplement `["[UNK]"]` ", et non `["b", "##u", "[UNK]"]` "). C'est une autre différence avec BPE, qui classerait seulement les caractères individuels qui ne sont pas dans le vocabulaire comme inconnus. +Lorsque la tokenisation arrive à un stade où il n'est pas possible de trouver un sous-mot dans le vocabulaire, le mot entier est tokenisé comme inconnu. Par exemple, `"mug"` serait tokenisé comme `["[UNK]"]`, tout comme `"bum"` (même si on peut commencer par " b " et " ##u ", " ##m " ne fait pas partie du vocabulaire, et le *tokenizer* résultant sera simplement `["[UNK]"]` " et non `["b", "##u", "[UNK]"]` "). C'est une autre différence avec le BPE qui classerait seulement les caractères individuels qui ne sont pas dans le vocabulaire comme inconnus. @@ -98,22 +98,26 @@ Lorsque la tokenisation arrive à un stade où il n'est pas possible de trouver -## Mise en œuvre de *WordPiece* +## Implémentation de WordPiece -Voyons maintenant une implémentation de l'algorithme *WordPiece*. Comme pour le BPE, il s'agit d'un exemple pédagogique, et vous ne pourrez pas l'utiliser sur un grand corpus. +Voyons maintenant une implémentation de l'algorithme *WordPiece*. Comme pour le BPE, il s'agit d'un exemple pédagogique et vous ne pourrez pas l'utiliser sur un grand corpus. Nous utiliserons le même corpus que dans l'exemple BPE : ```python corpus = [ - "This is the Hugging Face course.", # C'est le cours d'Hugging Face. - "This chapter is about tokenization.", # This chapter is about tokenization - "This section shows several tokenizer algorithms.", # Cette section présente plusieurs algorithmes de *tokenizer*. - "Hopefully, you will be able to understand how they are trained and generate tokens.", # Avec un peu de chance, vous serez en mesure de comprendre comment ils sont entraînés et génèrent des *tokens*. + "This is the Hugging Face Course.", + # C'est le cours d'Hugging Face. + "This chapter is about tokenization.", + # This chapter is about tokenization + "This section shows several tokenizer algorithms.", + # Cette section présente plusieurs algorithmes de *tokenizer*. + "Hopefully, you will be able to understand how they are trained and generate tokens.", + # Avec un peu de chance, vous serez en mesure de comprendre comment ils sont entraînés et génèrent des *tokens*. ] ``` -Tout d'abord, nous devons pré-tokéniser le corpus en mots. Puisque nous répliquons un *tokenizer WordPiece* (comme BERT), nous utiliserons le *tokenizer* `bert-base-cased` pour la pré-tokénisation : +Tout d'abord, nous devons prétokéniser le corpus en mots. Puisque nous répliquons un *tokenizer WordPiece* (comme BERT), nous utiliserons le *tokenizer* `bert-base-cased` pour la prétokénisation : ```python from transformers import AutoTokenizer @@ -121,7 +125,7 @@ from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") ``` -Ensuite, nous calculons les fréquences de chaque mot dans le corpus comme nous le faisons pour la pré-tokénisation : +Ensuite, nous calculons les fréquences de chaque mot dans le corpus comme nous le faisons pour la prétokénisation : ```python from collections import defaultdict @@ -144,7 +148,7 @@ defaultdict( 'trained': 1, 'and': 1, 'generate': 1, 'tokens': 1}) ``` -Comme nous l'avons vu précédemment, l'alphabet est l'ensemble unique composé de toutes les premières lettres des mots, et de toutes les autres lettres qui apparaissent dans les mots préfixés par `##` : +Comme nous l'avons vu précédemment, l'alphabet est l'unique ensemble composé de toutes les premières lettres des mots, et de toutes les autres lettres qui apparaissent dans les mots préfixés par `##` : ```python alphabet = [] @@ -167,7 +171,7 @@ print(alphabet) 'w', 'y'] ``` -Nous ajoutons également les tokens spéciaux utilisés par le modèle au début de ce vocabulaire. Dans le cas de BERT, il s'agit de la liste `["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]` : +Nous ajoutons également les *tokens* spéciaux utilisés par le modèle au début de ce vocabulaire. Dans le cas de BERT, il s'agit de la liste `["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]` : ```python vocab = ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"] + alphabet.copy() @@ -242,7 +246,7 @@ print(best_pair, max_score) ('a', '##b') 0.2 ``` -Ainsi, la première fusion à apprendre est `('a', '##b') -> 'ab'`, et nous ajoutons `'ab'` au vocabulaire : +Ainsi, la première fusion à apprendre est `('a', '##b') -> 'ab'` et nous ajoutons `'ab'` au vocabulaire : ```python vocab.append("ab") @@ -316,11 +320,11 @@ Comme nous pouvons le voir, comparé à BPE, ce *tokenizer* apprend les parties -💡 Utiliser `train_new_from_iterator()` sur le même corpus ne donnera pas exactement le même vocabulaire. C'est parce que la bibliothèque 🤗 *Tokenizers* n'implémente pas *WordPiece* pour l'entraînement (puisque nous ne sommes pas complètement sûrs de ses internes), mais utilise le BPE à la place. +💡 Utiliser `train_new_from_iterator()` sur le même corpus ne donnera pas exactement le même vocabulaire. C'est parce que la bibliothèque 🤗 *Tokenizers* n'implémente pas *WordPiece* pour l'entraînement (puisque nous ne sommes pas complètement sûrs de son fonctionnement interne), mais utilise le BPE à la place. -Pour tokeniser un nouveau texte, on le pré-tokenise, on le divise, puis on applique l'algorithme de tokenisation sur chaque mot. En d'autres termes, nous recherchons le plus grand sous-mot commençant au début du premier mot et le divisons, puis nous répétons le processus sur la deuxième partie, et ainsi de suite pour le reste de ce mot et les mots suivants dans le texte : +Pour tokeniser un nouveau texte, on le prétokenise, on le divise, puis on applique l'algorithme de tokenisation sur chaque mot. En d'autres termes, nous recherchons le plus grand sous-mot commençant au début du premier mot et le divisons. Puis nous répétons le processus sur la deuxième partie et ainsi de suite pour le reste de ce mot et les mots suivants dans le texte : ```python def encode_word(word): @@ -363,7 +367,7 @@ def tokenize(text): On peut l'essayer sur n'importe quel texte : ```python -tokenize("This is the Hugging Face course!") # C'est le cours d'Hugging Face +tokenize("This is the Hugging Face Course!") # C'est le cours d'Hugging Face ``` ```python out diff --git a/chapters/fr/chapter6/7.mdx b/chapters/fr/chapter6/7.mdx index ba32b85e7..bf5c970dc 100644 --- a/chapters/fr/chapter6/7.mdx +++ b/chapters/fr/chapter6/7.mdx @@ -1,4 +1,4 @@ -# Tokenisation *Unigram* +# Tokenisation Unigram -The Unigram algorithm is often used in SentencePiece, which is the tokenization algorithm used by models like AlBERT, T5, mBART, Big Bird, and XLNet. +L'algorithme *Unigram* est souvent utilisé dans *SentencePiece*, qui est l'algorithme de tokenization utilisé par des modèles comme ALBERT, T5, mBART, Big Bird et XLNet. @@ -19,20 +19,20 @@ The Unigram algorithm is often used in SentencePiece, which is the tokenization ## Algorithme d'entraînement -Comparé à BPE et *WordPiece*, *Unigram* fonctionne dans l'autre sens : il part d'un grand vocabulaire et enlève des *tokens* jusqu'à atteindre la taille de vocabulaire désirée. Il existe plusieurs options pour construire ce vocabulaire de base : nous pouvons prendre les sous-chaînes les plus courantes dans les mots pré-tokénisés, par exemple, ou appliquer BPE sur le corpus initial avec une grande taille de vocabulaire. +Comparé au BPE et *WordPiece*, *Unigram* fonctionne dans l'autre sens : il part d'un grand vocabulaire et enlève des *tokens* jusqu'à atteindre la taille de vocabulaire désirée. Il existe plusieurs options pour construire ce vocabulaire de base. Nous pouvons par exemple prendre les sous-chaînes les plus courantes dans les mots prétokénisés ou appliquer le BPE sur le corpus initial avec une grande taille de vocabulaire. -À 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. +À 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\\) (\(p\\) étant un hyperparamètre que vous pouvez contrôler, généralement 10 ou 20) pour cent des symboles associés à la plus faible augmentation de la perte. 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é. -Maintenant, c'est encore un peu vague : la partie principale de l'algorithme est de calculer une perte sur le corpus et de voir comment elle change lorsque nous supprimons certains *tokens* du vocabulaire, mais nous n'avons pas encore expliqué comment le faire. Cette étape repose sur l'algorithme de tokénisation d'un modèle *Unigram*, nous allons donc l'aborder maintenant. +Tout ceci peut paraître encore un peu vague. En effet, la partie principale de l'algorithme est de calculer une perte sur le corpus et de voir comment elle change lorsque nous supprimons certains *tokens* du vocabulaire mais nous n'avons pas encore expliqué comment le faire. Cette étape repose sur l'algorithme de tokénisation *Unigram*, nous allons donc l'aborder à présent. Nous allons réutiliser le corpus des exemples précédents : ``` -("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) # "câlin", "carlin", "jeu de mots", "brioche", "câlins"... ``` et pour cet exemple, nous prendrons toutes les sous-chaînes strictes pour le vocabulaire initial : @@ -45,7 +45,7 @@ et pour cet exemple, nous prendrons toutes les sous-chaînes strictes pour le vo Un modèle *Unigram* est un type de modèle de langage qui considère que chaque *token* est indépendant des *tokens* qui le précèdent. Il s'agit du modèle de langage le plus simple, dans le sens où la probabilité du *token* X compte tenu du contexte précédent est simplement la probabilité du *token* X. Ainsi, si nous utilisions un modèle de langage *Unigram* pour générer du texte, nous prédirions toujours le *token* le plus courant. -La probabilité d'un *token* donné est sa fréquence (le nombre de fois que nous le trouvons) dans le corpus original, divisée par la somme de toutes les fréquences de tous les *tokens* dans le vocabulaire (pour s'assurer que la somme des probabilités est égale à 1). Par exemple, `"ug"` est présent dans `"hug"`, `"pug"`, et `"hugs"`, il a donc une fréquence de 20 dans notre corpus. +La probabilité d'un *token* donné est sa fréquence (le nombre de fois que nous le trouvons) dans le corpus original, divisée par la somme de toutes les fréquences de tous les *tokens* dans le vocabulaire (pour s'assurer que la somme des probabilités est égale à 1). Par exemple, `"ug"` est présent dans `"hug"`, `"pug"`, et `"hugs"`. Il a donc une fréquence de 20 dans notre corpus. Voici les fréquences de tous les sous-mots possibles dans le vocabulaire : @@ -54,11 +54,11 @@ Voici les fréquences de tous les sous-mots possibles dans le vocabulaire : ("un", 16) ("b", 4) ("bu", 4) ("s", 5) ("hug", 15) ("gs", 5) ("ugs", 5) ``` -Ainsi, la somme de toutes les fréquences est de 210, et la probabilité du sous-mot `"ug"` est donc de 20/210. +Ainsi, la somme de toutes les fréquences est de 210 et la probabilité du sous-mot `"ug"` est donc de 20/210. -✏️ **A votre tour !** Ecrivez le code pour calculer les fréquences ci-dessus et vérifiez que les résultats affichés sont corrects, ainsi que la somme totale. +✏️ **A votre tour !** Ecrivez le code permettant de calculer les fréquences ci-dessus et vérifiez que les résultats affichés sont corrects, de même que la somme totale. @@ -82,9 +82,9 @@ La tokenisation d'un mot avec le modèle *Unigram* est donc la tokenisation avec Ainsi, `"pug"` sera tokenisé comme `["p", "ug"]` ou `["pu", "g"]`, selon la segmentation rencontrée en premier (notez que dans un corpus plus large, les cas d'égalité comme celui-ci seront rares). -Dans ce cas, il était facile de trouver toutes les segmentations possibles et de calculer leurs probabilités, mais en général, ce sera un peu plus difficile. Il existe un algorithme classique utilisé pour cela, appelé *algorithme de Viterbi*. Essentiellement, on peut construire un graphe pour détecter les segmentations possibles d'un mot donné en disant qu'il existe une branche du caractère _a_ au caractère _b_ si le sous-mot de _a_ à _b_ est dans le vocabulaire, et attribuer à cette branche la probabilité du sous-mot. +Dans ce cas-ci, cela a été facile de trouver toutes les segmentations possibles et de calculer leurs probabilités, mais en général ce sera un peu plus difficile. Il existe un algorithme classique utilisé pour cela, appelé *algorithme de Viterbi*. Essentiellement, on peut construire un graphe pour détecter les segmentations possibles d'un mot donné en disant qu'il existe une branche du caractère _a_ au caractère _b_ si le sous-mot de _a_ à _b_ est dans le vocabulaire, et attribuer à cette branche la probabilité du sous-mot. -Pour trouver le chemin dans ce graphe qui va avoir le meilleur score, l'algorithme de Viterbi détermine, pour chaque position dans le mot, la segmentation avec le meilleur score qui se termine à cette position. Puisque nous allons du début à la fin, ce meilleur score peut être trouvé en parcourant en boucle tous les sous-mots se terminant à la position actuelle, puis en utilisant le meilleur score de tokenization de la position à laquelle ce sous-mot commence. Ensuite, il suffit de dérouler le chemin emprunté pour arriver à la fin. +Pour trouver le chemin qui va avoir le meilleur score dans ce graphe, l'algorithme de Viterbi détermine, pour chaque position dans le mot, la segmentation avec le meilleur score qui se termine à cette position. Puisque nous allons du début à la fin, ce meilleur score peut être trouvé en parcourant en boucle tous les sous-mots se terminant à la position actuelle, puis en utilisant le meilleur score de tokenization de la position à laquelle ce sous-mot commence. Ensuite, il suffit de dérouler le chemin emprunté pour arriver à la fin. Prenons un exemple en utilisant notre vocabulaire et le mot `"unhug"`. Pour chaque position, les sous-mots avec les meilleurs scores se terminant là sont les suivants : @@ -108,7 +108,7 @@ Ainsi, `"unhug"` serait tokenisé comme `["un", "hug"]`. Maintenant que nous avons vu comment fonctionne la tokenisation, nous pouvons nous plonger un peu plus profondément dans la perte utilisée pendant l'entraînement. À n'importe quelle étape, cette perte est calculée en tokenisant chaque mot du corpus, en utilisant le vocabulaire courant et le modèle *Unigram* déterminé par les fréquences de chaque *token* dans le corpus (comme vu précédemment). -Chaque mot du corpus a un score, et la perte est le logarithme négatif de ces scores : c'est-à-dire la somme pour tous les mots du corpus de tous les `-log(P(word))`. +Chaque mot du corpus a un score, et la perte est le négatif du logarithme de ces scores, c'est-à-dire la somme pour tous les mots du corpus de tous les `-log(P(word))`. Revenons à notre exemple avec le corpus suivant : @@ -132,7 +132,7 @@ Donc la perte est : 10 * (-log(0.071428)) + 5 * (-log(0.007710)) + 12 * (-log(0.006168)) + 4 * (-log(0.001451)) + 5 * (-log(0.001701)) = 169.8 ``` -Maintenant, nous devons calculer comment la suppression de chaque token affecte la perte. C'est plutôt fastidieux, donc nous allons le faire pour deux *tokens* ici et garder tout le processus pour quand nous aurons du code pour nous aider. Dans ce cas (très) particulier, nous avions deux tokenizations équivalentes de tous les mots : comme nous l'avons vu précédemment, par exemple, `"pug"` pourrait être tokenisé `["p", "ug"]` avec le même score. Ainsi, enlever le token `"pu"` du vocabulaire donnera exactement la même perte. +Maintenant, nous devons calculer comment la suppression de chaque token affecte la perte. C'est plutôt fastidieux, donc nous allons le faire pour deux *tokens* ici et garder tout le processus pour quand nous aurons du code pour nous aider. Dans ce cas (très) particulier, nous avions deux tokenizations équivalentes de tous les mots. Par exmeple, comme nous l'avons vu précédemment, `"pug"` pourrait être tokenisé en `["p", "ug"]` avec le même score. Ainsi, enlever le token `"pu"` du vocabulaire donnera exactement la même perte. D'un autre côté, supprimer le mot `"hug"` aggravera la perte, car la tokenisation de `"hug"` et `"hugs"` deviendra : @@ -149,18 +149,22 @@ Ces changements entraîneront une augmentation de la perte de : Par conséquent, le token `"pu"` sera probablement retiré du vocabulaire, mais pas `"hug"`. -## Implémentation d'*Unigram* +## Implémentation d'Unigram -Maintenant, implémentons tout ce que nous avons vu jusqu'à présent dans le code. Comme pour BPE et *WordPiece*, ce n'est pas une implémentation efficace de l'algorithme *Unigram* (bien au contraire), mais cela devrait vous aider à le comprendre un peu mieux. +Maintenant, implémentons tout ce que nous avons vu jusqu'à présent dans le code. Comme pour le BPE et *WordPiece*, ce n'est pas une implémentation efficace de l'algorithme *Unigram* (bien au contraire), mais elle devrait vous aider à le comprendre un peu mieux. Nous allons utiliser le même corpus que précédemment comme exemple : ```python corpus = [ - "This is the Hugging Face course.", # C'est le cours d'Hugging Face. - "This chapter is about tokenization.", # This chapter is about tokenization - "This section shows several tokenizer algorithms.", # Cette section présente plusieurs algorithmes de *tokenizer*. - "Hopefully, you will be able to understand how they are trained and generate tokens.", # Avec un peu de chance, vous serez en mesure de comprendre comment ils sont entraînés et génèrent des *tokens*. + "This is the Hugging Face Course.", + # C'est le cours d'Hugging Face. + "This chapter is about tokenization.", + # Ce chapitre traite de la tokenisation. + "This section shows several tokenizer algorithms.", + # Cette section présente plusieurs algorithmes de *tokenizer*. + "Hopefully, you will be able to understand how they are trained and generate tokens.", + # Avec un peu de chance, vous serez en mesure de comprendre comment ils sont entraînés et génèrent des *tokens*. ] ``` @@ -172,7 +176,7 @@ from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("xlnet-base-cased") ``` -Comme pour BPE et *WordPiece*, nous commençons par compter le nombre d'occurrences de chaque mot dans le corpus : +Comme pour le BPE et *WordPiece*, nous commençons par compter le nombre d'occurrences de chaque mot dans le corpus : ```python from collections import defaultdict @@ -187,7 +191,7 @@ for text in corpus: word_freqs ``` -Ensuite, nous devons initialiser notre vocabulaire à quelque chose de plus grand que la taille du vocabulaire que nous voudrons à la fin. Nous devons inclure tous les caractères de base (sinon nous ne serons pas en mesure de tokeniser chaque mot), mais pour les sous-chaînes plus grandes, nous ne garderons que les plus communs, donc nous les trions par fréquence : +Ensuite, nous devons initialiser notre vocabulaire à une taille plus grande que celle du vocabulaire que nous voudrons à la fin. Nous devons inclure tous les caractères de base (sinon nous ne serons pas en mesure de tokeniser chaque mot), mais pour les sous-chaînes plus grandes, nous ne garderons que les plus communs. AInsi nous les trions par fréquence : ```python char_freqs = defaultdict(int) @@ -195,11 +199,11 @@ 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 + # Boucle à travers les sous-mots de longueur au moins égale à 2 for j in range(i + 2, len(word) + 1): subwords_freqs[word[i:j]] += freq -# Sort subwords by frequency +# Trier les sous-mots par fréquence sorted_subwords = sorted(subwords_freqs.items(), key=lambda x: x[1], reverse=True) sorted_subwords[:10] ``` @@ -221,7 +225,7 @@ token_freqs = {token: freq for token, freq in token_freqs} -Ensuite, nous calculons la somme de toutes les fréquences, pour convertir les fréquences en probabilités. Pour notre modèle, nous allons stocker les logarithmes des probabilités, car il est plus stable numériquement d'additionner des logarithmes que de multiplier des petits nombres, et cela simplifiera le calcul de la perte du modèle : +Ensuite, nous calculons la somme de toutes les fréquences, pour convertir les fréquences en probabilités. Pour notre modèle, nous allons stocker les logarithmes des probabilités, car c'est plus stable numériquement d'additionner des logarithmes que de multiplier des petits nombres. Cela simplifiera aussi le calcul de la perte du modèle : ```python from math import log @@ -230,9 +234,9 @@ total_sum = sum([freq for token, freq in token_freqs.items()]) model = {token: -log(freq / total_sum) for token, freq in token_freqs.items()} ``` -Maintenant la fonction principale est celle qui tokenise les mots en utilisant l'algorithme de Viterbi. Comme nous l'avons vu précédemment, cet algorithme calcule la meilleure segmentation de chaque sous-chaîne du mot, que nous allons stocker dans une variable nommée `best_segmentations`. Nous allons stocker un dictionnaire par position dans le mot (de 0 à sa longueur totale), avec deux clés : l'index du début du dernier *token* dans la meilleure segmentation, et le score de la meilleure segmentation. Avec l'index du début du dernier *token*, nous serons en mesure de récupérer la segmentation complète une fois que la liste est complètement remplie. +Maintenant la fonction principale est celle qui tokenise les mots en utilisant l'algorithme de Viterbi. Comme nous l'avons vu précédemment, cet algorithme calcule la meilleure segmentation de chaque sous-chaîne du mot que nous allons stocker dans une variable nommée `best_segmentations`. Nous allons stocker un dictionnaire par position dans le mot (de 0 à sa longueur totale), avec deux clés : l'index du début du dernier *token* dans la meilleure segmentation et le score de la meilleure segmentation. Avec l'index du début du dernier *token*, nous serons en mesure de récupérer la segmentation complète une fois que la liste est complètement remplie. -Le remplissage de la liste se fait à l'aide de deux boucles seulement : la boucle principale passe en revue chaque position de départ, et la seconde boucle essaie toutes les sous-chaînes commençant à cette position de départ. Si la sous-chaîne est dans le vocabulaire, nous avons une nouvelle segmentation du mot jusqu'à cette position finale, que nous comparons à ce qui est dans `best_segmentations`. +Le remplissage de la liste se fait à l'aide de deux boucles seulement : la boucle principale passe en revue chaque position de départ et la seconde boucle essaie toutes les sous-chaînes commençant à cette position de départ. Si la sous-chaîne est dans le vocabulaire, nous avons une nouvelle segmentation du mot jusqu'à cette position finale que nous comparons à ce qui est dans `best_segmentations`. Une fois que la boucle principale est terminée, nous commençons juste à la fin et sautons d'une position de départ à une autre, en enregistrant les *tokens* au fur et à mesure, jusqu'à ce que nous atteignions le début du mot : @@ -242,13 +246,13 @@ def encode_word(word, model): {"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 + # Doit être correctement rempli par les étapes précédentes de la boucle 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 + # Si nous avons trouvé une meilleure segmentation se terminant à end_idx, nous mettons à jour if ( best_segmentations[end_idx]["score"] is None or best_segmentations[end_idx]["score"] > score @@ -257,7 +261,7 @@ def encode_word(word, model): segmentation = best_segmentations[-1] if segmentation["score"] is None: - # We did not find a tokenization of the word -> unknown + # Nous n'avons pas trouvé de tokenization du mot -> inconnu () return [""], None score = segmentation["score"] @@ -306,7 +310,7 @@ compute_loss(model) 413.10377642940875 ``` -Le calcul des scores pour chaque *token* n'est pas très difficile non plus ; il suffit de calculer la perte pour les modèles obtenus en supprimant chaque *token* : +Le calcul des scores pour chaque *token* n'est pas très difficile non plus. Il suffit de calculer la perte pour les modèles obtenus en supprimant chaque *token* : ```python import copy @@ -316,7 +320,7 @@ def compute_scores(model): scores = {} model_loss = compute_loss(model) for token, score in model.items(): - # We always keep tokens of length 1 + # Nous gardons toujours les tokens de longueur 1. if len(token) == 1: continue model_without_token = copy.deepcopy(model) @@ -342,7 +346,7 @@ Puisque `"ll"` est utilisé dans la tokenisation de `"Hopefully"`, et que le sup -💡 Cette approche est très inefficace, c'est pourquoi *SentencePiece* utilise une approximation de la perte du modèle sans le *token* X : au lieu de partir de zéro, il remplace simplement le *token* X par sa segmentation dans le vocabulaire restant. De cette façon, tous les scores peuvent être calculés en une seule fois, en même temps que la perte du modèle. +💡 Cette approche est très inefficace, c'est pourquoi *SentencePiece* utilise une approximation de la perte du modèle sans le *token* X. Au lieu de partir de zéro, il remplace simplement le *token* X par sa segmentation dans le vocabulaire restant. De cette façon, tous les scores peuvent être calculés en une seule fois, en même temps que la perte du modèle. @@ -353,7 +357,7 @@ 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. + # Supprime les tokens percent_to_remove ayant les scores les plus bas for i in range(int(len(model) * percent_to_remove)): _ = token_freqs.pop(sorted_scores[i][0]) @@ -361,7 +365,7 @@ while len(model) > 100: model = {token: -log(freq / total_sum) for token, freq in token_freqs.items()} ``` -Ensuite, pour tokeniser un texte, il suffit d'appliquer la pré-tokénisation et d'utiliser la fonction `encode_word()` : +Ensuite, pour tokeniser un texte, il suffit d'appliquer la prétokénisation et d'utiliser la fonction `encode_word()` : ```python def tokenize(text, model): @@ -378,4 +382,4 @@ tokenize("This is the Hugging Face course.", model) ['▁This', '▁is', '▁the', '▁Hugging', '▁Face', '▁', 'c', 'ou', 'r', 's', 'e', '.'] ``` -C'est tout pour *Unigram* ! Avec un peu de chance, vous vous sentez maintenant comme un expert en tout ce qui concerne les *tokenizers*. Dans la prochaine section, nous allons nous plonger dans les blocs de construction de la bibliothèque 🤗 *Tokenizers*, et vous montrer comment vous pouvez les utiliser pour construire votre propre *tokenizer*. +C'est tout pour *Unigram* ! Avec un peu de chance, vous vous sentez à présent être un expert des *tokenizers*. Dans la prochaine section, nous allons nous plonger dans les blocs de construction de la bibliothèque 🤗 *Tokenizers* et allons vous montrer comment vous pouvez les utiliser pour construire votre propre *tokenizer*. diff --git a/chapters/fr/chapter6/8.mdx b/chapters/fr/chapter6/8.mdx index 8c8af3b5b..46440deb7 100644 --- a/chapters/fr/chapter6/8.mdx +++ b/chapters/fr/chapter6/8.mdx @@ -1,566 +1,566 @@ -# Construction d'un *tokenizer*, bloc par bloc - - - -Comme nous l'avons vu dans les sections précédentes, la tokenisation comprend plusieurs étapes : - -- normalisation (tout nettoyage du texte jugé nécessaire, comme la suppression des espaces ou des accents, la normalisation Unicode, etc.) -- pré-tokénisation (division de l'entrée en mots) -- passage de l'entrée dans le modèle (utilisation des mots prétokénisés pour produire une séquence de *tokens*) -- post-traitement (ajout des tokens spéciaux du *tokenizer*, génération du masque d'attention et des identifiants du type de *token*). - -Pour mémoire, voici un autre aperçu du processus global : - -
-The tokenization pipeline. - -
- -La bibliothèque 🤗 *Tokenizers* a été construite pour fournir plusieurs options pour chacune de ces étapes, que vous pouvez mélanger et assortir ensemble. Dans cette section, nous verrons comment nous pouvons construire un *tokenizer* à partir de zéro, par opposition à entraîner un nouveau *tokenizer* à partir d'un ancien, comme nous l'avons fait dans [section 2](/course/fr/chapter6/2). Vous serez alors en mesure de construire n'importe quel type de *tokenizer* auquel vous pouvez penser ! - - - -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). - -## Acquisition d'un corpus - -Pour entraîner notre nouveau *tokenizer*, nous utiliserons un petit corpus de texte (pour que les exemples soient rapides). Les étapes pour acquérir le corpus sont similaires à celles que nous avons suivies au [début de ce chapitre](/course/fr/chapter6/2), mais cette fois nous utiliserons le jeu de données [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 fonction `get_training_corpus()` est un générateur qui donnera des batchs de 1 000 textes, que nous utiliserons pour entraîner le *tokenizer*. - -🤗 *Tokenizers* peuvent aussi être entraînés directement sur des fichiers texte. Voici comment nous pouvons générer un fichier texte contenant tous les textes/entrées de WikiText-2 que nous pouvons utiliser localement : - -```python -with open("wikitext-2.txt", "w", encoding="utf-8") as f: - for i in range(len(dataset)): - f.write(dataset[i]["text"] + "\n") -``` - -Ensuite, nous vous montrerons comment construire vos propres *tokenizers* BERT, GPT-2 et XLNet, bloc par bloc. Cela nous donnera un exemple de chacun des trois principaux algorithmes de tokenisation : *WordPiece*, BPE et *Unigram*. Commençons par BERT ! - -## Construire un tokenizer *WordPiece* à partir de zéro - -Pour construire un *tokenizer* avec la bibliothèque 🤗 *Tokenizers*, nous commençons par instancier un objet `Tokenizer` avec un `model`, puis nous définissons ses attributs `normalizer`, `pre_tokenizer`, `post_processor`, et `decoder` aux valeurs que nous voulons. - -Pour cet exemple, nous allons créer un `Tokenizer` avec un modèle *WordPiece* : - -```python -from tokenizers import ( - decoders, - models, - normalizers, - pre_tokenizers, - processors, - trainers, - Tokenizer, -) - -tokenizer = Tokenizer(models.WordPiece(unk_token="[UNK]")) -``` - -Nous devons spécifier le `unk_token` pour que le modèle sache quoi retourner lorsqu'il rencontre des caractères qu'il n'a pas vu auparavant. D'autres arguments que nous pouvons définir ici incluent le `vocab` de notre modèle (nous allons entraîner le modèle, donc nous n'avons pas besoin de le définir) et `max_input_chars_per_word`, qui spécifie une longueur maximale pour chaque mot (les mots plus longs que la valeur passée seront séparés). - -La première étape de la tokénisation est la normalisation, donc commençons par cela. Puisque BERT est largement utilisé, il y a un `BertNormalizer` avec les options classiques que nous pouvons définir pour BERT : `lowercase` et `strip_accents`, qui sont auto-explicatifs ; `clean_text` pour enlever tous les caractères de contrôle et remplacer les espaces répétés par un seul ; et `handle_chinese_chars`, qui place des espaces autour des caractères chinois. Pour reproduire le *tokenizer* `bert-base-uncased`, nous pouvons simplement définir ce *normalizer* : - -```python -tokenizer.normalizer = normalizers.BertNormalizer(lowercase=True) -``` - -En général, cependant, lorsque vous construisez un nouveau *tokenizer*, vous n'aurez pas accès à un normalisateur aussi pratique déjà implémenté dans la bibliothèque 🤗 *Tokenizers*. Donc voyons comment créer le normalisateur BERT manuellement. La bibliothèque fournit un normaliseur `Lowercase` et un normaliseur `StripAccents`, et vous pouvez composer plusieurs normaliseurs en utilisant une `Sequence` : - -```python -tokenizer.normalizer = normalizers.Sequence( - [normalizers.NFD(), normalizers.Lowercase(), normalizers.StripAccents()] -) -``` - -Nous utilisons également un normaliseur Unicode `NFD`, car sinon le normalisateur `StripAccents` ne reconnaîtra pas correctement les caractères accentués et ne les supprimera donc pas. - -Comme nous l'avons vu précédemment, nous pouvons utiliser la méthode `normalize_str()` du `normalizer` pour vérifier les effets qu'il a sur un texte donné : - -```python -print(tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) -``` - -```python out -hello how are u? -``` - - - -**Pour aller plus loin** Si vous testez les deux versions des normalisateurs précédents sur une chaîne contenant le caractère unicode `u"\u0085"` vous remarquerez sûrement que ces deux normalisateurs ne sont pas exactement équivalents. -Pour ne pas trop compliquer la version avec `normalizers.Sequence`, nous n'avons pas inclus les remplacements Regex que le `BertNormalizer` requiert quand l'argument `clean_text` est mis à `True` ce qui est le comportement par défaut. Mais ne vous inquiétez pas : il est possible d'obtenir exactement la même normalisation sans utiliser le très pratique `BertNormalizer` en ajoutant deux `normalizers.Replace` à la séquence de normalisation. - - - -L'étape suivante est la pré-tokenalisation. Encore une fois, il y a un `BertPreTokenizer` préconstruit que nous pouvons utiliser : - -```python -tokenizer.pre_tokenizer = pre_tokenizers.BertPreTokenizer() -``` - -Ou nous pouvons le construire à partir de zéro : - -```python -tokenizer.pre_tokenizer = pre_tokenizers.Whitespace() -``` - -Notez que le pré-tokenizer `Whitespace` divise sur les espaces et tous les caractères qui ne sont pas des lettres, des chiffres ou le caractère de soulignement, donc techniquement il divise sur les espaces et la ponctuation : - -```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 vous voulez seulement séparer sur les espaces, vous devriez utiliser le pré-tokenizer `WhitespaceSplit` à la place : - -```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))] -``` - -Comme pour les normaliseurs, vous pouvez utiliser une `Sequence` pour composer plusieurs pré-tokenizers : - -```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))] -``` - -L'étape suivante dans le pipeline de tokénisation est de faire passer les entrées par le modèle. Nous avons déjà spécifié notre modèle dans l'initialisation, mais nous devons encore l'entraîner, ce qui nécessitera un `WordPieceTrainer`. La principale chose à retenir lors de l'instanciation d'un entraîneur dans 🤗 *Tokenizers* est que vous devez lui passer tous les *tokens* spéciaux que vous avez l'intention d'utiliser. Sinon il ne les ajoutera pas au vocabulaire, puisqu'ils ne sont pas dans le corpus d'entraînement : - -```python -special_tokens = ["[UNK]", "[PAD]", "[CLS]", "[SEP]", "[MASK]"] -trainer = trainers.WordPieceTrainer(vocab_size=25000, special_tokens=special_tokens) -``` - -En plus de spécifier la `vocab_size` et les `special_tokens`, nous pouvons définir la `min_frequency` (le nombre de fois qu'un *token* doit apparaître pour être inclus dans le vocabulaire) ou changer le `continuing_subword_prefix` (si nous voulons utiliser quelque chose de différent de `##`). - -Pour entraîner notre modèle en utilisant l'itérateur que nous avons défini plus tôt, il suffit d'exécuter cette commande : - -```python -tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) -``` - -Nous pouvons également utiliser des fichiers texte pour entraîner notre *tokenizer*, qui ressemblerait à ceci (nous réinitialisons le modèle avec un `WordPiece` vide au préalable) : - -```python -tokenizer.model = models.WordPiece(unk_token="[UNK]") -tokenizer.train(["wikitext-2.txt"], trainer=trainer) -``` - -Dans les deux cas, nous pouvons ensuite tester le *tokenizer* sur un texte en appelant la méthode `encode()` : - -```python -encoding = tokenizer.encode("Let's test this tokenizer.") -print(encoding.tokens) -``` - -```python out -['let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.'] -``` - -Le `encodage` obtenu est un `Encoding`, qui contient toutes les sorties nécessaires du *tokenizer* dans ses différents attributs : `ids`, `type_ids`, `tokens`, `offsets`, `attention_mask`, `special_tokens_mask`, et `overflowing`. - -La dernière étape du pipeline de tokénisation est le post-traitement. Nous devons ajouter le *token* `[CLS]` au début et le *token* `[SEP]` à la fin (ou après chaque phrase, si nous avons une paire de phrases). Nous utiliserons un `TemplateProcessor` pour cela, mais d'abord nous devons connaître les ID des *tokens* `[CLS]` et `[SEP]` dans le vocabulaire : - -```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) -``` - -Pour écrire le modèle pour le `TemplateProcessor`, nous devons spécifier comment traiter une seule phrase et une paire de phrases. Pour les deux, nous écrivons les *tokens* spéciaux que nous voulons utiliser ; la première (ou unique) phrase est représentée par `$A`, alors que la deuxième phrase (si on code une paire) est représentée par `$B`. Pour chacun de ces éléments (*tokens* spéciaux et phrases), nous spécifions également l'ID du type de *token* correspondant après un deux-points. - -Le *template* classique de BERT est donc défini comme suit : - -```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)], -) -``` - -Notez que nous devons transmettre les ID des jetons spéciaux, afin que le *tokenizer* puisse les convertir correctement en leurs ID. - -Une fois que cela est ajouté, revenir à notre exemple précédent donnera : - -```python -encoding = tokenizer.encode("Let's test this tokenizer.") -print(encoding.tokens) -``` - -```python out -['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.', '[SEP]'] -``` - -Et sur une paire de phrases, on obtient le bon résultat : - -```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] -``` - -Nous avons presque fini de construire ce *tokenizer* à partir de zéro. La dernière étape consiste à inclure un décodeur : - -```python -tokenizer.decoder = decoders.WordPiece(prefix="##") -``` - -Testons-le sur notre précédent `encoding` : - -```python -tokenizer.decode(encoding.ids) -``` - -```python out -"let's test this tokenizer... on a pair of sentences." # Testons ce tokenizer... sur une paire de phrases. -``` - -Génial ! Nous pouvons enregistrer notre *tokenizer* dans un seul fichier JSON comme ceci : - -```python -tokenizer.save("tokenizer.json") -``` - -Nous pouvons alors recharger ce fichier dans un objet `Tokenizer` avec la méthode `from_file()` : - -```python -new_tokenizer = Tokenizer.from_file("tokenizer.json") -``` - -Pour utiliser ce *tokenizer* dans 🤗 *Transformers*, nous devons l'envelopper dans un `PreTrainedTokenizerFast`. Nous pouvons soit utiliser la classe générique, soit, si notre *tokenizer* correspond à un modèle existant, utiliser cette classe (ici, `BertTokenizerFast`). Si vous appliquez cette leçon pour construire un tout nouveau *tokenizer*, vous devrez utiliser la première option. - -Pour envelopper le *tokenizer* dans un `PreTrainedTokenizerFast`, nous pouvons soit passer le *tokenizer*que nous avons construit comme un `tokenizer_object`, soit passer le fichier de *tokenizer* que nous avons sauvegardé comme `tokenizer_file`. Ce qu'il faut retenir, c'est que nous devons définir manuellement tous les *tokens* spéciaux, car cette classe ne peut pas déduire de l'objet `tokenizer` quel *token* est le *token* de masque, le *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 vous utilisez une classe de *tokenizer* spécifique (comme `BertTokenizerFast`), vous aurez seulement besoin de spécifier les *tokens* spéciaux qui sont différents de ceux par défaut (ici, aucun) : - -```python -from transformers import BertTokenizerFast - -wrapped_tokenizer = BertTokenizerFast(tokenizer_object=tokenizer) -``` - -Vous pouvez ensuite utiliser ce *tokenizer* comme n'importe quel autre *tokenizer* de 🤗 *Transformers*. Vous pouvez le sauvegarder avec la méthode `save_pretrained()`, ou le télécharger sur le *Hub* avec la méthode `push_to_hub()`. - -Maintenant que nous avons vu comment construire un *tokenizer WordPiece*, faisons de même pour un *tokenizer* BPE. Nous irons un peu plus vite puisque vous connaissez toutes les étapes, et nous ne soulignerons que les différences. - -## Construire un *tokenizer* BPE à partir de zéro - -Construisons maintenant un *tokenizer* BPE. Comme pour le *tokenizer* BERT, nous commençons par initialiser un `Tokenizer` avec un modèle BPE : - -```python -tokenizer = Tokenizer(models.BPE()) -``` - -Comme pour BERT, nous pourrions initialiser ce modèle avec un vocabulaire si nous en avions un (nous aurions besoin de passer le `vocab` et le `merges` dans ce cas), mais puisque nous allons nous entraîner à partir de zéro, nous n'avons pas besoin de le faire. Nous n'avons pas non plus besoin de spécifier un `unk_token` parce que GPT-2 utilise un BPE au niveau de l'octet, ce qui ne le nécessite pas. - -GPT-2 n'utilise pas de normaliseur, donc nous sautons cette étape et allons directement à la pré-tokénisation : - -```python -tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False) -``` - -L'option que nous avons ajoutée à `ByteLevel` ici est de ne pas ajouter d'espace en début de phrase (ce qui est le cas par défaut). Nous pouvons jeter un coup d'oeil à la pré-tokénisation d'un texte d'exemple comme avant : - -```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))] -``` - -Vient ensuite le modèle, qui doit être entraîné. Pour GPT-2, le seul *token* spécial est le *token* de fin de texte : - -```python -trainer = trainers.BpeTrainer(vocab_size=25000, special_tokens=["<|endoftext|>"]) -tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) -``` - -Comme avec le `WordPieceTrainer`, ainsi que le `vocab_size` et le `special_tokens`, nous pouvons spécifier la `min_frequency` si nous le voulons, ou si nous avons un suffixe de fin de mot (comme ``), nous pouvons le définir avec `end_of_word_suffix`. - -Ce *tokenizer* peut aussi être entraîné sur des fichiers texte : - -```python -tokenizer.model = models.BPE() -tokenizer.train(["wikitext-2.txt"], trainer=trainer) -``` - -Regardons la tokenisation d'un exemple de texte : - -```python -encoding = tokenizer.encode("Let's test this tokenizer.") -print(encoding.tokens) -``` - -```python out -['L', 'et', "'", 's', 'Ġtest', 'Ġthis', 'Ġto', 'ken', 'izer', '.'] -``` - -Nous appliquons le post-traitement au niveau de l'octet pour le *tokenizer* du GPT-2 comme suit : - -```python -tokenizer.post_processor = processors.ByteLevel(trim_offsets=False) -``` - -L'option `trim_offsets = False` indique au post-processeur que nous devons laisser les *offsets* des *tokens* qui commencent par 'Ġ' tels quels : de cette façon, le début des *offsets* pointera sur l'espace avant le mot, et non sur le premier caractère du mot (puisque l'espace fait techniquement partie du token). Regardons le résultat avec le texte que nous venons de coder, où `'Ġtest'` est le token à l'index 4 : - -```python -sentence = "Let's test this tokenizer." -encoding = tokenizer.encode(sentence) -start, end = encoding.offsets[4] -sentence[start:end] -``` - -```python out -' test' -``` - -Enfin, nous ajoutons un décodeur de niveau octet : - -```python -tokenizer.decoder = decoders.ByteLevel() -``` - -et nous pourrons vérifier qu'il fonctionne correctement : - -```python -tokenizer.decode(encoding.ids) -``` - -```python out -"Let's test this tokenizer." # Testons ce tokenizer -``` - -Super ! Maintenant que nous avons terminé, nous pouvons sauvegarder le tokenizer comme avant, et l'envelopper dans un `PreTrainedTokenizerFast` ou un `GPT2TokenizerFast` si nous voulons l'utiliser dans 🤗 *Transformers* : - -```python -from transformers import PreTrainedTokenizerFast - -wrapped_tokenizer = PreTrainedTokenizerFast( - tokenizer_object=tokenizer, - bos_token="<|endoftext|>", - eos_token="<|endoftext|>", -) -``` - -ou : - -```python -from transformers import GPT2TokenizerFast - -wrapped_tokenizer = GPT2TokenizerFast(tokenizer_object=tokenizer) -``` - -Comme dernier exemple, nous allons vous montrer comment construire un *tokenizer* *Unigram* à partir de zéro. - -## Construire un *tokenizer* *Unigram* à partir de rien. - -Construisons maintenant un *tokenizer* XLNet. Comme pour les *tokenizers* précédents, nous commençons par initialiser un `Tokenizer` avec un modèle *Unigram* : - -```python -tokenizer = Tokenizer(models.Unigram()) -``` - -Encore une fois, nous pourrions initialiser ce modèle avec un vocabulaire si nous en avions un. - -Pour la normalisation, XLNet utilise quelques remplacements (qui proviennent de *SentencePiece*) : - -```python -from tokenizers import Regex - -tokenizer.normalizer = normalizers.Sequence( - [ - normalizers.Replace("``", '"'), - normalizers.Replace("''", '"'), - normalizers.NFKD(), - normalizers.StripAccents(), - normalizers.Replace(Regex(" {2,}"), " "), - ] -) -``` - -Cela remplace `` et '' avec " et toute séquence de deux espaces ou plus par un seul espace, ainsi que la suppression des accents dans les textes à catégoriser. - -Le pré-*tokenizer* à utiliser pour tout *tokenizer SentencePiece* est `Metaspace` : - -```python -tokenizer.pre_tokenizer = pre_tokenizers.Metaspace() -``` - -Nous pouvons jeter un coup d'oeil à la pré-tokénisation d'un exemple de texte comme précédemment : - -```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))] -``` - -Vient ensuite le modèle, qui doit être entraîné. XLNet possède un certain nombre de *tokens* spéciaux : - -```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 argument très important à ne pas oublier pour le `UnigramTrainer` est le `unk_token`. Nous pouvons aussi passer d'autres arguments spécifiques à l'algorithme *Unigram*, comme le `shrinking_factor` pour chaque étape où nous enlevons des *tokens* (par défaut 0.75) ou le `max_piece_length` pour spécifier la longueur maximale d'un token donné (par défaut 16). - -Ce *tokenizer* peut aussi être entraîné sur des fichiers texte : - -```python -tokenizer.model = models.Unigram() -tokenizer.train(["wikitext-2.txt"], trainer=trainer) -``` - -Regardons la tokenisation d'un exemple de texte : - -```python -encoding = tokenizer.encode("Let's test this tokenizer.") -print(encoding.tokens) -``` - -```python out -['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.'] -``` - -Une particularité de XLNet est qu'il place le *token* `` à la fin de la phrase, avec un type ID de 2 (pour le distinguer des autres *tokens*). Le résultat est un remplissage à gauche. Nous pouvons traiter tous les *tokens* spéciaux et les IDs de type de *token* avec un modèle, comme pour BERT, mais d'abord nous devons obtenir les IDs des *tokens* `` et `` : - -```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 -``` - -Le modèle ressemble à ceci : - -```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)], -) -``` - -Et nous pouvons tester son fonctionnement en codant une paire de phrases : - -```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] -``` - -Enfin, nous ajoutons un décodeur `Metaspace` : - -```python -tokenizer.decoder = decoders.Metaspace() -``` - -et on en a fini avec ce *tokenizer* ! On peut sauvegarder le *tokenizer* comme avant, et l'envelopper dans un `PreTrainedTokenizerFast` ou `XLNetTokenizerFast` si on veut l'utiliser dans 🤗 *Transformers*. Une chose à noter lors de l'utilisation de `PreTrainedTokenizerFast` est qu'en plus des *tokens* spéciaux, nous devons dire à la bibliothèque 🤗 *Transformers* de remplir à gauche : - -```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", -) -``` - -Ou alternativement : - -```python -from transformers import XLNetTokenizerFast - -wrapped_tokenizer = XLNetTokenizerFast(tokenizer_object=tokenizer) -``` - -Maintenant que vous avez vu comment les différentes briques sont utilisées pour construire des *tokenizers* existants, vous devriez être capable d'écrire n'importe quel *tokenizer* que vous voulez avec la bibliothèque 🤗 *Tokenizers* et pouvoir l'utiliser dans 🤗 *Transformers*. +# Construction d'un tokenizer, bloc par bloc + + + +Comme nous l'avons vu dans les sections précédentes, la tokenisation comprend plusieurs étapes : + +- normalisation (tout nettoyage du texte jugé nécessaire, comme la suppression des espaces ou des accents, la normalisation Unicode, etc.), +- prétokénisation (division de l'entrée en mots), +- passage de l'entrée dans le modèle (utilisation des mots prétokénisés pour produire une séquence de *tokens*), +- post-traitement (ajout des *tokens* spéciaux du *tokenizer*, génération du masque d'attention et des identifiants du type de *token*). + +Pour mémoire, voici un autre aperçu du processus global : + +
+The tokenization pipeline. + +
+ +La bibliothèque 🤗 *Tokenizers* a été construite pour fournir plusieurs options pour chacune de ces étapes. Vous pouvez les mélanger et assortir ensemble. Dans cette section, nous verrons comment nous pouvons construire un *tokenizer* à partir de zéro, par opposition à entraîner un nouveau *tokenizer* à partir d'un ancien, comme nous l'avons fait dans [section 2](/course/fr/chapter6/2). Vous serez alors en mesure de construire n'importe quel type de *tokenizer* auquel vous pouvez penser ! + + + +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). + +## Acquisition d'un corpus + +Pour entraîner notre nouveau *tokenizer*, nous utiliserons un petit corpus de texte (pour que les exemples soient rapides). Les étapes pour acquérir ce corpus sont similaires à celles que nous avons suivies au [début du chapitre](/course/fr/chapter6/2), mais cette fois nous utiliserons le jeu de données [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 fonction `get_training_corpus()` est un générateur qui donne des batchs de 1 000 textes, que nous utiliserons pour entraîner le *tokenizer*. + +🤗 *Tokenizers* peut aussi être entraîné directement sur des fichiers texte. Voici comment nous pouvons générer un fichier texte contenant tous les textes de WikiText-2 que nous pourrons ensuite utilisé en local : + +```python +with open("wikitext-2.txt", "w", encoding="utf-8") as f: + for i in range(len(dataset)): + f.write(dataset[i]["text"] + "\n") +``` + +Ensuite, nous vous montrerons comment construire vos propres *tokenizers* pour BERT, GPT-2 et XLNet, bloc par bloc. Cela vous donnera un exemple de chacun des trois principaux algorithmes de tokenisation : *WordPiece*, BPE et *Unigram*. Commençons par BERT ! + +## Construire un tokenizer WordPiece à partir de zéro + +Pour construire un *tokenizer* avec la bibliothèque 🤗 *Tokenizers*, nous commençons par instancier un objet `Tokenizer` avec un `model`. Puis nous définissons ses attributs `normalizer`, `pre_tokenizer`, `post_processor` et `decoder` aux valeurs que nous voulons. + +Pour cet exemple, nous allons créer un `Tokenizer` avec un modèle *WordPiece* : + +```python +from tokenizers import ( + decoders, + models, + normalizers, + pre_tokenizers, + processors, + trainers, + Tokenizer, +) + +tokenizer = Tokenizer(models.WordPiece(unk_token="[UNK]")) +``` + +Nous devons spécifier le `unk_token` pour que le modèle sache quoi retourner lorsqu'il rencontre des caractères qu'il n'a pas vu auparavant. D'autres arguments que nous pouvons définir ici incluent le `vocab` de notre modèle (nous allons entraîner le modèle, donc nous n'avons pas besoin de le définir) et `max_input_chars_per_word`, qui spécifie une longueur maximale pour chaque mot (les mots plus longs que la valeur passée seront séparés). + +La première étape de la tokénisation est la normalisation. Puisque BERT est largement utilisé, une fonction `BertNormalizer` a été créée avec les options classiques que nous pouvons définir pour BERT : `lowercase` pour mettre le texte en minuscule, `strip_accents` qui enlève les accents, `clean_text` pour enlever tous les caractères de contrôle et fusionner des espaces répétés par un seul, et `handle_chinese_chars` qui place des espaces autour des caractères chinois. Pour reproduire le *tokenizer* `bert-base-uncased`, nous pouvons simplement définir ce *normalizer* : + +```python +tokenizer.normalizer = normalizers.BertNormalizer(lowercase=True) +``` + +Cependant, généralement, lorsque vous construisez un nouveau *tokenizer*, vous n'avez pas accès à un normaliseur aussi pratique déjà implémenté dans la bibliothèque 🤗 *Tokenizers*. Donc voyons comment créer le normaliseur de BERT manuellement. La bibliothèque fournit un normaliseur `Lowercase` et un normaliseur `StripAccents`. Il est possible de composer plusieurs normaliseurs en utilisant une `Sequence` : + +```python +tokenizer.normalizer = normalizers.Sequence( + [normalizers.NFD(), normalizers.Lowercase(), normalizers.StripAccents()] +) +``` + +Nous utilisons également un normaliseur Unicode `NFD`, car sinon `StripAccents` ne reconnaîtra pas correctement les caractères accentués et ne les supprimera donc pas. + +Comme nous l'avons vu précédemment, nous pouvons utiliser la méthode `normalize_str()` du `normalizer` pour vérifier les effets qu'il a sur un texte donné : + +```python +print(tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) +``` + +```python out +hello how are u? +``` + + + +**Pour aller plus loin** Si vous testez les deux versions des normaliseurs précédents sur une chaîne contenant le caractère unicode `u"\u0085"` vous remarquerez sûrement qu'ils ne sont pas exactement équivalents. +Pour ne pas trop compliquer la version avec `normalizers.Sequence`, nous n'avons pas inclus les Regex que le `BertNormalizer` requiert quand l'argument `clean_text` est mis à `True` ce qui est le comportement par défaut. Mais ne vous inquiétez pas : il est possible d'obtenir exactement la même normalisation sans utiliser le très pratique `BertNormalizer` en ajoutant deux `normalizers.Replace` à la séquence de normalisation. + + + +L'étape suivante est la prétokenisation. Encore une fois, il y a un `BertPreTokenizer` préconstruit que nous pouvons utiliser : + +```python +tokenizer.pre_tokenizer = pre_tokenizers.BertPreTokenizer() +``` + +Ou nous pouvons le construire à partir de zéro : + +```python +tokenizer.pre_tokenizer = pre_tokenizers.Whitespace() +``` + +Notez que le `Whitespace` divise sur les espaces et tous les caractères qui ne sont pas des lettres, des chiffres ou le caractère de soulignement. Donc techniquement il divise sur les espaces et la ponctuation : + +```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 vous voulez seulement séparer sur les espaces, vous devez utiliser `WhitespaceSplit` à la place : + +```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))] +``` + +Comme pour les normaliseurs, vous pouvez utiliser une `Sequence` pour composer plusieurs prétokenizers : + +```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))] +``` + +L'étape suivante dans le pipeline de tokénisation est de faire passer les entrées par le modèle. Nous avons déjà spécifié notre modèle dans l'initialisation, mais nous devons encore l'entraîner, ce qui nécessitera un `WordPieceTrainer`. La principale chose à retenir lors de l'instanciation d'un entraîneur dans 🤗 *Tokenizers* est que vous devez lui passer tous les *tokens* spéciaux que vous avez l'intention d'utiliser. Sinon il ne les ajoutera pas au vocabulaire puisqu'ils ne sont pas dans le corpus d'entraînement : + +```python +special_tokens = ["[UNK]", "[PAD]", "[CLS]", "[SEP]", "[MASK]"] +trainer = trainers.WordPieceTrainer(vocab_size=25000, special_tokens=special_tokens) +``` + +En plus de spécifier la `vocab_size` et les `special_tokens`, nous pouvons définir la `min_frequency` (le nombre de fois qu'un *token* doit apparaître pour être inclus dans le vocabulaire) ou changer le `continuing_subword_prefix` (si nous voulons utiliser quelque chose de différent de `##`). + +Pour entraîner notre modèle en utilisant l'itérateur que nous avons défini plus tôt, il suffit d'exécuter cette commande : + +```python +tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) +``` + +Nous pouvons également utiliser des fichiers texte pour entraîner notre *tokenizer* qui ressemblerait alors à ceci (nous réinitialisons le modèle avec un `WordPiece` vide au préalable) : + +```python +tokenizer.model = models.WordPiece(unk_token="[UNK]") +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +Dans les deux cas, nous pouvons ensuite tester le *tokenizer* sur un texte en appelant la méthode `encode()` : + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.'] +``` + +L'encodage obtenu est un `Encoding` contenant toutes les sorties nécessaires du *tokenizer* dans ses différents attributs : `ids`, `type_ids`, `tokens`, `offsets`, `attention_mask`, `special_tokens_mask` et `overflowing`. + +La dernière étape du pipeline de tokénisation est le post-traitement. Nous devons ajouter le *token* `[CLS]` au début et le *token* `[SEP]` à la fin (ou après chaque phrase si nous avons une paire de phrases). Nous utiliserons `TemplateProcessor` pour cela, mais d'abord nous devons connaître les identifiants des *tokens* `[CLS]` et `[SEP]` dans le vocabulaire : + +```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) +``` + +Pour écrire le gabarit pour `TemplateProcessor`, nous devons spécifier comment traiter une seule phrase et une paire de phrases. Pour les deux, nous écrivons les *tokens* spéciaux que nous voulons utiliser. La première (ou unique) phrase est représentée par `$A`, alors que la deuxième phrase (si on code une paire) est représentée par `$B`. Pour chacun de ces éléments (*tokens* spéciaux et phrases), nous spécifions également l'identifiant du *token* correspondant après un deux-points. + +Le gabarit classique de BERT est donc défini comme suit : + +```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)], +) +``` + +Notez que nous devons transmettre les identifiants des *tokens* spéciaux afin que le *tokenizer* puisse les convertir correctement. + +Une fois cela ajouté, revenons à notre exemple précédent donnera : + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.', '[SEP]'] +``` + +Et sur une paire de phrases, on obtient le bon résultat : + +```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] +``` + +Nous avons presque fini de construire ce *tokenizer* à partir de zéro. La dernière étape consiste à inclure un décodeur : + +```python +tokenizer.decoder = decoders.WordPiece(prefix="##") +``` + +Testons-le sur notre précédent `encoding` : + +```python +tokenizer.decode(encoding.ids) +``` + +```python out +"let's test this tokenizer... on a pair of sentences." # Testons ce tokenizer... sur une paire de phrases. +``` + +Génial ! Nous pouvons enregistrer notre *tokenizer* dans un seul fichier JSON comme ceci : + +```python +tokenizer.save("tokenizer.json") +``` + +Nous pouvons alors recharger ce fichier dans un objet `Tokenizer` avec la méthode `from_file()` : + +```python +new_tokenizer = Tokenizer.from_file("tokenizer.json") +``` + +Pour utiliser ce *tokenizer* dans 🤗 *Transformers*, nous devons l'envelopper dans un `PreTrainedTokenizerFast`. Nous pouvons soit utiliser la classe générique, soit, si notre *tokenizer* correspond à un modèle existant, utiliser cette classe (ici, `BertTokenizerFast`). Si vous appliquez cette logique pour construire un tout nouveau *tokenizer*, vous devrez utiliser la première option. + +Pour envelopper le *tokenizer* dans un `PreTrainedTokenizerFast`, nous pouvons soit passer le *tokenizer* que nous avons construit comme un `tokenizer_object`, soit passer le fichier de *tokenizer* que nous avons sauvegardé comme `tokenizer_file`. Ce qu'il faut retenir, c'est que nous devons définir manuellement tous les *tokens* spéciaux car cette classe ne peut pas déduire de l'objet `tokenizer` quel *token* est le *token* de masque, quel est le *token*`[CLS]`, etc : + +```python +from transformers import PreTrainedTokenizerFast + +wrapped_tokenizer = PreTrainedTokenizerFast( + tokenizer_object=tokenizer, + # tokenizer_file="tokenizer.json", # Vous pouvez charger à partir du fichier du tokenizer, alternativement + unk_token="[UNK]", + pad_token="[PAD]", + cls_token="[CLS]", + sep_token="[SEP]", + mask_token="[MASK]", +) +``` + +Si vous utilisez une classe de *tokenizer* spécifique (comme `BertTokenizerFast`), vous aurez seulement besoin de spécifier les *tokens* spéciaux qui sont différents de ceux par défaut (ici, aucun) : + +```python +from transformers import BertTokenizerFast + +wrapped_tokenizer = BertTokenizerFast(tokenizer_object=tokenizer) +``` + +Vous pouvez ensuite utiliser ce *tokenizer* comme n'importe quel autre *tokenizer* de 🤗 *Transformers*. Vous pouvez le sauvegarder avec la méthode `save_pretrained()` ou le télécharger sur le *Hub* avec la méthode `push_to_hub()`. + +Maintenant que nous avons vu comment construire un *tokenizer WordPiece*, faisons de même pour un *tokenizer* BPE. Nous irons un peu plus vite puisque vous connaissez toutes les étapes. Nous ne soulignerons que les différences. + +## Construire un tokenizer BPE à partir de zéro + +Construisons maintenant un *tokenizer* BPE. Comme pour le *tokenizer* BERT, nous commençons par initialiser un `Tokenizer` avec un modèle BPE : + +```python +tokenizer = Tokenizer(models.BPE()) +``` + +Comme pour BERT, nous pourrions initialiser ce modèle avec un vocabulaire si nous en avions un (nous aurions besoin de passer le `vocab` et le `merges` dans ce cas), mais puisque nous allons nous entraîner à partir de zéro, nous n'avons pas besoin de le faire. Nous n'avons pas non plus besoin de spécifier un `unk_token` parce que le GPT-2 utilise un BPE au niveau de l'octet. + +GPT-2 n'utilise pas de normaliseur, donc nous sautons cette étape et allons directement à la prétokénisation : + +```python +tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False) +``` + +L'option que nous avons ajoutée à `ByteLevel` ici est de ne pas ajouter d'espace en début de phrase (ce qui est le cas par défaut). Nous pouvons jeter un coup d'oeil à la prétokénisation d'un texte d'exemple comme avant : + +```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))] +``` + +Vient ensuite le modèle, qui doit être entraîné. Pour le GPT-2, le seul *token* spécial est le *token* de fin de texte : + +```python +trainer = trainers.BpeTrainer(vocab_size=25000, special_tokens=["<|endoftext|>"]) +tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) +``` + +Comme avec le `WordPieceTrainer`, ainsi que le `vocab_size` et le `special_tokens`, nous pouvons spécifier la `min_frequency` si nous le voulons, ou si nous avons un suffixe de fin de mot (comme ``), nous pouvons le définir avec `end_of_word_suffix`. + +Ce *tokenizer* peut aussi être entraîné sur des fichiers texte : + +```python +tokenizer.model = models.BPE() +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +Regardons la tokenisation d'un exemple de texte : + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['L', 'et', "'", 's', 'Ġtest', 'Ġthis', 'Ġto', 'ken', 'izer', '.'] +``` + +Nous appliquons le post-traitement au niveau de l'octet pour le *tokenizer* du GPT-2 comme suit : + +```python +tokenizer.post_processor = processors.ByteLevel(trim_offsets=False) +``` + +L'option `trim_offsets = False` indique au post-processeur que nous devons laisser les *offsets* des *tokens* qui commencent par 'Ġ' tels quels : de cette façon, le début des *offsets* pointera sur l'espace avant le mot, et non sur le premier caractère du mot (puisque l'espace fait techniquement partie du *token*). Regardons le résultat avec le texte que nous venons de coder, où `'Ġtest'` est le *token* à l'index 4 : + +```python +sentence = "Let's test this tokenizer." +encoding = tokenizer.encode(sentence) +start, end = encoding.offsets[4] +sentence[start:end] +``` + +```python out +' test' +``` + +Enfin, nous ajoutons un décodeur au niveau de l'octet : + +```python +tokenizer.decoder = decoders.ByteLevel() +``` + +et nous pouvons vérifier qu'il fonctionne correctement : + +```python +tokenizer.decode(encoding.ids) +``` + +```python out +"Let's test this tokenizer." # Testons ce tokenizer +``` + +Super ! Maintenant que nous avons terminé, nous pouvons sauvegarder le tokenizer comme avant, et l'envelopper dans un `PreTrainedTokenizerFast` ou un `GPT2TokenizerFast` si nous voulons l'utiliser dans 🤗 *Transformers* : + +```python +from transformers import PreTrainedTokenizerFast + +wrapped_tokenizer = PreTrainedTokenizerFast( + tokenizer_object=tokenizer, + bos_token="<|endoftext|>", + eos_token="<|endoftext|>", +) +``` + +ou : + +```python +from transformers import GPT2TokenizerFast + +wrapped_tokenizer = GPT2TokenizerFast(tokenizer_object=tokenizer) +``` + +Comme dernier exemple, nous allons vous montrer comment construire un *tokenizer* *Unigram* à partir de zéro. + +## Construire un tokenizer Unigram à partir de zéro + +Construisons maintenant un *tokenizer* XLNet. Comme pour les *tokenizers* précédents, nous commençons par initialiser un `Tokenizer` avec un modèle *Unigram* : + +```python +tokenizer = Tokenizer(models.Unigram()) +``` + +Encore une fois, nous pourrions initialiser ce modèle avec un vocabulaire si nous en avions un. + +Pour la normalisation, XLNet utilise quelques remplacements (qui proviennent de *SentencePiece*) : + +```python +from tokenizers import Regex + +tokenizer.normalizer = normalizers.Sequence( + [ + normalizers.Replace("``", '"'), + normalizers.Replace("''", '"'), + normalizers.NFKD(), + normalizers.StripAccents(), + normalizers.Replace(Regex(" {2,}"), " "), + ] +) +``` + +Il remplace `` et '' par " et toute séquence de deux espaces ou plus par un seul espace, de plus il supprime les accents. + +Le prétokenizer à utiliser pour tout *tokenizer SentencePiece* est `Metaspace` : + +```python +tokenizer.pre_tokenizer = pre_tokenizers.Metaspace() +``` + +Nous pouvons jeter un coup d'oeil à la prétokénisation sur le même exemple de texte que précédemment : + +```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))] +``` + +Vient ensuite le modèle, qui doit être entraîné. XLNet possède un certain nombre de *tokens* spéciaux : + +```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 argument très important à ne pas oublier pour le `UnigramTrainer` est le `unk_token`. Nous pouvons aussi passer d'autres arguments spécifiques à l'algorithme *Unigram*, comme le `shrinking_factor` pour chaque étape où nous enlevons des *tokens* (par défaut 0.75) ou le `max_piece_length` pour spécifier la longueur maximale d'un *token* donné (par défaut 16). + +Ce *tokenizer* peut aussi être entraîné sur des fichiers texte : + +```python +tokenizer.model = models.Unigram() +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +Regardons la tokenisation de notre exemple : + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.'] +``` + +Une particularité de XLNet est qu'il place le *token* `` à la fin de la phrase, avec un identifiant de 2 (pour le distinguer des autres *tokens*). Le résultat est un remplissage à gauche. Nous pouvons traiter tous les *tokens* spéciaux et les types d'identifiant de *token* avec un modèle, comme pour BERT. Mais d'abord nous devons obtenir les identifiants des *tokens* `` et `` : + +```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 +``` + +Le modèle ressemble à ceci : + +```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)], +) +``` + +Et nous pouvons tester son fonctionnement en codant une paire de phrases : + +```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] +``` + +Enfin, nous ajoutons un décodeur `Metaspace` : + +```python +tokenizer.decoder = decoders.Metaspace() +``` + +et on en a fini avec ce *tokenizer* ! On peut le sauvegarder et l'envelopper dans un `PreTrainedTokenizerFast` ou `XLNetTokenizerFast` si on veut l'utiliser dans 🤗 *Transformers*. Une chose à noter lors de l'utilisation de `PreTrainedTokenizerFast` est qu'en plus des *tokens* spéciaux, nous devons dire à la bibliothèque 🤗 *Transformers* de rembourrer à gauche : + +```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", +) +``` + +Ou alternativement : + +```python +from transformers import XLNetTokenizerFast + +wrapped_tokenizer = XLNetTokenizerFast(tokenizer_object=tokenizer) +``` + +Maintenant que vous avez vu comment les différentes briques sont utilisées pour construire des *tokenizers* existants, vous devriez être capable d'écrire n'importe quel *tokenizer* que vous voulez avec la bibliothèque 🤗 *Tokenizers* et pouvoir l'utiliser dans 🤗 *Transformers*. diff --git a/chapters/fr/chapter6/9.mdx b/chapters/fr/chapter6/9.mdx index 7e59acd48..11e4beeab 100644 --- a/chapters/fr/chapter6/9.mdx +++ b/chapters/fr/chapter6/9.mdx @@ -1,11 +1,11 @@ -# *Tokenizer*, vérifié ! +# Tokenizer, coché ! Bon travail pour finir ce chapitre ! Après cette plongée en profondeur dans les *tokenizers*, vous devriez : - être capable d'entraîner un nouveau tokenizer en utilisant un ancien tokenizer comme modèle, -- comprendre comment utiliser les offsets pour faire correspondre la position des tokens à l'étendue du texte d'origine, +- comprendre comment utiliser les *offsets* pour faire correspondre la position des *tokens* à l'étendue de texte d'origine, - connaître les différences entre BPE, *WordPiece* et *Unigram*, - être capable de combiner les blocs fournis par la bibliothèque 🤗 *Tokenizers* pour construire votre propre *tokenizer*, - être capable d'utiliser ce *tokenizer* dans la bibliothèque 🤗 *Transformers*. diff --git a/chapters/fr/chapter7/2.mdx b/chapters/fr/chapter7/2.mdx index 110c5e1ab..7fb7fae81 100644 --- a/chapters/fr/chapter7/2.mdx +++ b/chapters/fr/chapter7/2.mdx @@ -1,981 +1,981 @@ - - -# Classification de *tokens* - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -La première application que nous allons explorer est la classification de *tokens*. Cette tâche générique englobe tous les problèmes qui peuvent être formulés comme "l'attribution d'une étiquette à chaque *token* dans une phrase", tels que : - -- **reconnaissance d'entités nommées (NER)** : trouver les entités (telles que des personnes, des lieux ou des organisations) dans une phrase. Cela peut être formulé comme l'attribution d'une étiquette à chaque *token* en ayant une classe par entité et une classe pour "aucune entité". -- **part-of-speech tagging (POS)** : marquer chaque mot dans une phrase comme correspondant à une partie particulière du discours (comme un nom, un verbe, un adjectif, etc.). -- ***chunking*** : trouver les *tokens* qui appartiennent à la même entité. Cette tâche (qui peut être combinée avec le POS ou la NER) peut être formulée comme l'attribution d'une étiquette (habituellement `B-`) à tous les *tokens* qui sont au début d'un morceau, une autre étiquette (habituellement `I-`) aux *tokens* qui sont à l'intérieur d'un morceau, et une troisième étiquette (habituellement `O`) aux *tokens* qui n'appartiennent à aucun morceau. - - - -Bien sûr, il existe de nombreux autres types de problèmes de classification de *tokens* ; ce ne sont là que quelques exemples représentatifs. Dans cette section, nous allons affiner un modèle (BERT) sur une tâche NER, qui sera alors capable de calculer des prédictions comme celle-ci : - - - - - -One-hot encoded labels for question answering. - - - -Vous pouvez trouver le modèle que nous allons entraîner et télécharger sur le *Hub* et vérifier ses prédictions [ici](https://huggingface.co/huggingface-course/bert-finetuned-ner?text=My+nom+est+Sylvain+et+je+travaille+à+Hugging+Face+in+Brooklyn). - -## Préparation des données - -Tout d'abord, nous avons besoin d'un jeu de données adapté à la classification des *tokens*. Dans cette section, nous utiliserons le jeu de données [CoNLL-2003](https://huggingface.co/datasets/conll2003), qui contient des articles de presse de Reuters. - - - -💡 Tant que votre jeu de données consiste en des textes divisés en mots avec leurs étiquettes correspondantes, vous pourrez adapter les procédures de traitement des données décrites ici à votre propre jeu de données. Reportez-vous au [Chapitre 5](/course/fr/chapter5) si vous avez besoin d'un rafraîchissement sur la façon de charger vos propres données personnalisées dans un `Dataset`. - - - -### Le jeu de données CoNLL-2003 - -Pour charger le jeu de données CoNLL-2003, nous utilisons la méthode `load_dataset()` de la bibliothèque 🤗 *Datasets* : - -```py -from datasets import load_dataset - -raw_datasets = load_dataset("conll2003") -``` - -Cela va télécharger et mettre en cache le jeu de données, comme nous l'avons vu dans [Chapitre 3](/course/fr/chapter3) pour le jeu de données GLUE MRPC. L'inspection de cet objet nous montre les colonnes présentes et la répartition entre les ensembles d'entraînement, de validation et de test : - -```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 - }) -}) -``` - -En particulier, nous pouvons voir que le jeu de données contient des étiquettes pour les trois tâches que nous avons mentionnées précédemment : NER, POS, et *chunking*. Une grande différence avec les autres jeux de données est que les textes d'entrée ne sont pas présentés comme des phrases ou des documents, mais comme des listes de mots (la dernière colonne est appelée `tokens`, mais elle contient des mots dans le sens où ce sont des entrées pré-tokénisées qui doivent encore passer par le *tokenizer* pour la tokenisation des sous-mots). - -Regardons le premier élément de l'ensemble d'entraînement : - -```py -raw_datasets["train"][0]["tokens"] -``` - -```python out -['EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'lamb', '.'] -``` - -Puisque nous voulons effectuer la reconnaissance des entités nommées, nous allons examiner les balises NER : - -```py -raw_datasets["train"][0]["ner_tags"] -``` - -```python out -[3, 0, 7, 0, 0, 0, 7, 0, 0] -``` - -Ce sont les étiquettes sous forme d'entiers prêts pour l'entraînement, mais ils ne sont pas nécessairement utiles lorsque nous voulons inspecter les données. Comme pour la classification de texte, nous pouvons accéder à la correspondance entre ces entiers et les noms des étiquettes en regardant l'attribut `features` de notre jeu de données : - -```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) -``` - -Cette colonne contient donc des éléments qui sont des séquences de `ClassLabel`s. Le type des éléments de la séquence se trouve dans l'attribut `feature` de cette `ner_feature`, et nous pouvons accéder à la liste des noms en regardant l'attribut `names` de cette `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'] -``` - -Nous avons déjà vu ces étiquettes en creusant dans le pipeline `token-classification` au [Chapitre 6](/course/fr/chapter6/3), mais pour un rapide rappel : - -- `O` signifie que le mot ne correspond à aucune entité. -- `B-PER`/`I-PER` signifie que le mot correspond au début de/est à l'intérieur d'une entité *personne*. -- `B-ORG`/`I-ORG` signifie que le mot correspond au début/à l'intérieur d'une entité *organisation*. -- `B-LOC`/`I-LOC` signifie que le mot correspond au début/à l'intérieur d'une entité *location*. -- `B-MISC`/`I-MISC` signifie que le mot correspond au début/à l'intérieur d'une entité *divers*. - -Maintenant, le décodage des étiquettes que nous avons vues précédemment nous donne ceci : - -```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' -``` - -Et pour un exemple mélangeant les étiquettes `B-` et `I-`, voici ce que le même code nous donne sur l'élément de l'ensemble d'entraînement à l'indice 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' -``` - -Comme on peut le voir, les entités couvrant deux mots, comme "Union européenne" et "Werner Zwingmann", se voient attribuer une étiquette "B-" pour le premier mot et une étiquette "I-" pour le second. - - - -✏️ *Votre tour !* Affichez les deux mêmes phrases avec leurs étiquettes POS ou *chunking*. - - - -### Traitement des données - - - -Comme d'habitude, nos textes doivent être convertis en identifiants de *tokens* avant que le modèle puisse leur donner un sens. Comme nous l'avons vu dans le [Chapitre 6](/course/fr/chapter6/), une grande différence dans le cas des tâches de classification de *tokens* est que nous avons des entrées pré-tokénisées. Heureusement, l'API tokenizer peut gérer cela assez facilement ; nous devons juste avertir le `tokenizer` avec un drapeau spécial. - -Pour commencer, nous allons créer notre objet `tokenizer`. Comme nous l'avons dit précédemment, nous allons utiliser un modèle pré-entraîné BERT, donc nous allons commencer par télécharger et mettre en cache le tokenizer associé : - -```python -from transformers import AutoTokenizer - -model_checkpoint = "bert-base-cased" -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) -``` - -Vous pouvez remplacer le `model_checkpoint` par tout autre modèle que vous préférez à partir du [*Hub*]https://huggingface.co/models), ou par un dossier local dans lequel vous avez sauvegardé un modèle pré-entraîné et un *tokenizer*. La seule contrainte est que le *tokenizer* doit être soutenu par la bibliothèque 🤗 *Tokenizers*, il y a donc une version "rapide" disponible. Vous pouvez voir toutes les architectures qui ont une version rapide dans [ce grand tableau](https://huggingface.co/transformers/#supported-frameworks), et pour vérifier que l'objet `tokenizer` que vous utilisez est bien soutenu par 🤗 *Tokenizers* vous pouvez regarder son attribut `is_fast` : - -```py -tokenizer.is_fast -``` - -```python out -True -``` - -Pour tokeniser une entrée pré-tokenisée, nous pouvons utiliser notre `tokenizer` comme d'habitude et juste ajouter `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]'] -``` - -Comme on peut le voir, le *tokenizer* a ajouté les *tokens* spéciaux utilisés par le modèle (`[CLS]` au début et `[SEP]` à la fin) et n'a pas touché à la plupart des mots. Le mot `lamb`, cependant, a été tokenisé en deux sous-mots, `la` et `##mb`. Cela introduit un décalage entre nos entrées et les étiquettes : la liste des étiquettes n'a que 9 éléments, alors que notre entrée a maintenant 12 *tokens*. Il est facile de tenir compte des *tokens* spéciaux (nous savons qu'ils sont au début et à la fin), mais nous devons également nous assurer que nous alignons toutes les étiquettes avec les mots appropriés. - -Heureusement, comme nous utilisons un *tokenizer* rapide, nous avons accès aux superpouvoirs des 🤗 *Tokenizers*, ce qui signifie que nous pouvons facilement faire correspondre chaque *token* au mot correspondant (comme on le voit au [Chapitre 6](/course/fr/chapter6/3)) : - -```py -inputs.word_ids() -``` - -```python out -[None, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, None] -``` - -Avec un peu de travail, nous pouvons alors étendre notre liste d'étiquettes pour qu'elle corresponde aux *tokens*. La première règle que nous allons appliquer est que les *tokens* spéciaux reçoivent une étiquette de `-100`. En effet, par défaut, `-100` est un indice qui est ignoré dans la fonction de perte que nous allons utiliser (entropie croisée). Ensuite, chaque *token* reçoit la même étiquette que le *token* qui a commencé le mot dans lequel il se trouve, puisqu'ils font partie de la même entité. Pour les *tokens* à l'intérieur d'un mot mais pas au début, nous remplaçons le `B-` par `I-` (puisque le *token* ne commence pas l'entité) : - -```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: - # Start of a new 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: - # Special token - new_labels.append(-100) - else: - # Same word as previous token - label = labels[word_id] - # If the label is B-XXX we change it to I-XXX - if label % 2 == 1: - label += 1 - new_labels.append(label) - - return new_labels -``` - -Essayons-le sur notre première phrase : - -```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] -``` - -Comme nous pouvons le voir, notre fonction a ajouté le `-100` pour les deux *tokens* spéciaux au début et à la fin, et un nouveau `0` pour notre mot qui a été divisé en deux *tokens*. - - - -✏️ *Votre tour !* Certains chercheurs préfèrent n'attribuer qu'un seul label par mot, et attribuer `-100` aux autres sous-*tokens* dans un mot donné. Ceci afin d'éviter que les longs mots qui se divisent en plusieurs batchs ne contribuent fortement à la perte. Changez la fonction précédente pour aligner les étiquettes avec les ID d'entrée en suivant cette règle. - - -Pour prétraiter notre ensemble de données, nous devons tokeniser toutes les entrées et appliquer `align_labels_with_tokens()` sur toutes les étiquettes. Pour profiter de la vitesse de notre *tokenizer* rapide, il est préférable de tokeniser beaucoup de textes en même temps, donc nous allons écrire une fonction qui traite une liste d'exemples et utiliser la méthode `Dataset.map()` avec l'option `batched=True`. La seule chose qui diffère de notre exemple précédent est que la fonction `word_ids()` a besoin de récupérer l'index de l'exemple dont nous voulons les IDs de mots lorsque les entrées du *tokenizer* sont des listes de textes (ou dans notre cas, des listes de mots), donc nous l'ajoutons aussi : - -```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 -``` - -Notez que nous n'avons pas encore paddé nos entrées ; nous le ferons plus tard, lors de la création des lots avec un collateur de données. - -Nous pouvons maintenant appliquer tout ce prétraitement en une seule fois sur les autres divisions de notre jeu de données : - -```py -tokenized_datasets = raw_datasets.map( - tokenize_and_align_labels, - batched=True, - remove_columns=raw_datasets["train"].column_names, -) -``` - -Nous avons fait la partie la plus difficile ! Maintenant que les données ont été prétraitées, l'entraînement réel ressemblera beaucoup à ce que nous avons fait dans le [Chapitre 3](/course/fr/chapter3). - -{#if fw === 'pt'} - -## *Finetuning* du modèle avec l'API `Trainer`. - -Le code actuel utilisant le `Trainer` sera le même que précédemment ; les seuls changements sont la façon dont les données sont rassemblées dans un batch et la fonction de calcul de la métrique. - -{:else} - -## *Finetuning* fin du modèle avec Keras - -Le code réel utilisant Keras sera très similaire au précédent ; les seuls changements sont la façon dont les données sont rassemblées dans un batch et la fonction de calcul de la métrique. - -{/if} - - -### Collation des données - -Nous ne pouvons pas simplement utiliser un `DataCollatorWithPadding` comme dans [Chapter 3](/course/fr/chapter3) parce que cela ne fait que rembourrer les entrées (IDs d'entrée, masque d'attention, et IDs de type de *token*). Ici, nos étiquettes doivent être remplies exactement de la même manière que les entrées afin qu'elles gardent la même taille, en utilisant `-100` comme valeur afin que les prédictions correspondantes soient ignorées dans le calcul de la perte. - -Tout ceci est fait par un [`DataCollatorForTokenClassification`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorfortokenclassification). Comme le `DataCollatorWithPadding`, il prend le `tokenizer` utilisé pour prétraiter les entrées : - -{#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} - -Pour tester cette fonction sur quelques échantillons, nous pouvons simplement l'appeler sur une liste d'exemples provenant de notre jeu d'entraînement tokénisé : - -```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]]) -``` - -Comparons cela aux étiquettes des premier et deuxième éléments de notre jeu de données : - -```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'} - -Comme nous pouvons le voir, le deuxième jeu d'étiquettes a été complété à la longueur du premier en utilisant `-100`s. - -{:else} - -Notre collateur de données est prêt à fonctionner ! Maintenant, utilisons-le pour créer un `tf.data.Dataset` avec la méthode `to_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, -) -``` - - - Prochain arrêt : le modèle lui-même. - -{/if} - -{#if fw === 'tf'} - -### Définir le modèle - -Puisque nous travaillons sur un problème de classification de *tokens*, nous allons utiliser la classe `TFAutoModelForTokenClassification`. La principale chose à retenir lors de la définition de ce modèle est de transmettre des informations sur le nombre de labels que nous avons. La façon la plus simple de le faire est de passer ce nombre avec l'argument `num_labels`, mais si nous voulons un joli *widget* d'inférence fonctionnant comme celui que nous avons vu au début de cette section, il est préférable de définir les correspondances correctes des étiquettes à la place. - -Elles devraient être définies par deux dictionnaires, `id2label` et `label2id`, qui contiennent la correspondance de l'ID au label et vice versa : - -```py -id2label = {str(i): label for i, label in enumerate(label_names)} -label2id = {v: k for k, v in id2label.items()} -``` - -Maintenant, nous pouvons simplement les passer à la méthode `TFAutoModelForTokenClassification.from_pretrained()`, et ils seront définis dans la configuration du modèle, puis correctement enregistrés et téléchargés vers le *Hub* : - -```py -from transformers import TFAutoModelForTokenClassification - -model = TFAutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, -) -``` - -Comme lorsque nous avons défini notre `TFAutoModelForSequenceClassification` au [Chapitre 3](/course/fr/chapter3), la création du modèle émet un avertissement indiquant que certains poids n'ont pas été utilisés (ceux de la tête de pré-entraînement) et que d'autres poids ont été initialisés de manière aléatoire (ceux de la tête de classification des nouveaux *tokens*), et que ce modèle doit être entraîné. Nous ferons cela dans une minute, mais vérifions d'abord que notre modèle a le bon nombre d'étiquettes : - -```python -model.config.num_labels -``` - -```python out -9 -``` - - - -⚠️ Si vous avez un modèle avec le mauvais nombre de labels, vous obtiendrez une erreur obscure en appelant `model.fit()` plus tard. Cela peut être ennuyeux à déboguer, donc assurez-vous de faire cette vérification pour confirmer que vous avez le nombre de labels attendu. - - - -### *Finetuning* du modèle - -Nous sommes maintenant prêts à entraîner notre modèle ! Mais nous devons d'abord faire un peu de ménage : nous devons nous connecter à Hugging Face et définir nos hyperparamètres d'entraînement. Si vous travaillez dans un *notebook*, il y a une fonction pratique pour vous aider à le faire : - -```python -from huggingface_hub import notebook_login - -notebook_login() -``` - -Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face. - -Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : - -```bash -huggingface-cli login -``` - -Après s'être connecté, nous pouvons préparer tout ce dont nous avons besoin pour compiler notre modèle. 🤗 *Transformers* fournit une fonction pratique `create_optimizer()` qui vous donnera un optimiseur `AdamW` avec des paramètres appropriés pour la décroissance du taux des poids et la décroissance du taux d'apprentissage, les deux améliorant les performances de votre modèle par rapport à l'optimiseur `Adam` intégré : - -```python -from transformers import create_optimizer -import tensorflow as tf - -# Entraîner en mixed-precision float16 -# Commentez cette ligne si vous utilisez un GPU qui ne bénéficiera pas de cette fonction -tf.keras.mixed_precision.set_global_policy("mixed_float16") - -# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans l'ensemble de données, divisé par la taille du batch puis multiplié par le nombre total d'époques -# par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un batchtf.data.Dataset, -# et non le jeu de données original Hugging Face Dataset, donc son len() est déjà 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) -``` - -Notez également que nous ne fournissons pas d'argument `loss` à `compile()`. C'est parce que les modèles peuvent en fait calculer la perte en interne. Si vous compilez sans perte et fournissez vos étiquettes dans le dictionnaire d'entrée (comme nous le faisons dans nos jeux de données), alors le modèle s'entraînera en utilisant cette perte interne, qui sera appropriée pour la tâche et le type de modèle que vous avez choisi. - -Ensuite, nous définissons un `PushToHubCallback` pour télécharger notre modèle vers le *Hub* pendant l'entraînement, et nous ajustons le modèle avec ce *callback* : - -```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, -) -``` - -Vous pouvez spécifier le nom complet du référentiel vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/bert-finetuned-ner"`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, par exemple `"cool_huggingface_user/bert-finetuned-ner"`. - - - -💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de l'appel de `model.fit()` et devrez définir un nouveau nom. - - - -Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. De cette façon, vous pourrez reprendre votre entraînement sur une autre machine si nécessaire. - -A ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à *finetuner* un modèle sur une tâche de classification de *tokens*. Félicitations ! Mais quelle est la qualité réelle de notre modèle ? Nous devons évaluer certaines métriques pour le découvrir. - -{/if} - - -### Métriques - -{#if fw === 'pt'} - -Pour que le `Trainer` calcule une métrique à chaque époque, nous devrons définir une fonction `compute_metrics()` qui prend les tableaux de prédictions et de labels, et retourne un dictionnaire avec les noms et les valeurs des métriques. - -Le cadre traditionnel utilisé pour évaluer la prédiction de la classification des *tokens* est [*seqeval*](https://github.com/chakki-works/seqeval). Pour utiliser cette métrique, nous devons d'abord installer la bibliothèque *seqeval* : - -```py -!pip install seqeval -``` - -Nous pouvons ensuite le charger via la fonction `load_metric()` comme nous l'avons fait dans le [Chapitre 3](/course/fr/chapter3) : - -{:else} - -Le cadre traditionnel utilisé pour évaluer la prédiction de la classification des *tokens* est [*seqeval*](https://github.com/chakki-works/seqeval). Pour utiliser cette métrique, nous devons d'abord installer la bibliothèque *seqeval* : - -```py -!pip install seqeval -``` - -Nous pouvons ensuite le charger via la fonction `load_metric()` comme nous l'avons fait dans le [Chapitre 3](/course/fr/chapter3) : - -{/if} - -```py -from datasets import load_metric - -metric = load_metric("seqeval") -``` - -Cette métrique ne se comporte pas comme la précision standard : elle prend les listes d'étiquettes comme des chaînes de caractères et non comme des entiers. Nous devrons donc décoder complètement les prédictions et les étiquettes avant de les transmettre à la métrique. Voyons comment cela fonctionne. Tout d'abord, nous allons obtenir les étiquettes pour notre premier exemple d'entraînement : - -```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'] -``` - -Nous pouvons alors créer de fausses prédictions pour celles-ci en changeant simplement la valeur de l'indice 2 : - -```py -predictions = labels.copy() -predictions[2] = "O" -metric.compute(predictions=[predictions], references=[labels]) -``` - -Notez que la métrique prend une liste de prédictions (pas seulement une) et une liste d'étiquettes. Voici la sortie : - -```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'} - -Cela renvoie un batch d'informations ! Nous obtenons la précision, le rappel, et le score F1 pour chaque entité séparée, ainsi que le score global. Pour notre calcul de métrique, nous ne garderons que le score global, mais n'hésitez pas à modifier la fonction `compute_metrics()` pour retourner toutes les métriques que vous souhaitez. - -Cette fonction `compute_metrics()` prend d'abord l'argmax des logits pour les convertir en prédictions (comme d'habitude, les logits et les probabilités sont dans le même ordre, donc nous n'avons pas besoin d'appliquer le softmax). Ensuite, nous devons convertir les étiquettes et les prédictions des entiers en chaînes de caractères. Nous supprimons toutes les valeurs dont l'étiquette est `-100`, puis nous passons les résultats à la méthode `metric.compute()` : - -```py -import numpy as np - - -def compute_metrics(eval_preds): - logits, labels = eval_preds - predictions = np.argmax(logits, axis=-1) - - # Suppression de l'index ignoré (tokens spéciaux) et conversion en étiquettes - 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"], - } -``` - -Maintenant que ceci est fait, nous sommes presque prêts à définir notre `Trainer`. Nous avons juste besoin d'un `modèle` pour *finetuner* ! - -{:else} - -Cela renvoie un batch d'informations ! Nous obtenons la précision, le rappel et le score F1 pour chaque entité séparée, ainsi que pour l'ensemble. Voyons maintenant ce qui se passe si nous essayons d'utiliser les prédictions de notre modèle pour calculer des scores réels. - -TensorFlow n'aime pas concaténer nos prédictions ensemble, car elles ont des longueurs de séquence variables. Cela signifie que nous ne pouvons pas simplement utiliser `model.predict()`. Mais cela ne va pas nous arrêter. Nous obtiendrons des prédictions un batch à la fois et les concaténerons en une grande liste longue au fur et à mesure, en laissant tomber les *tokens* `-100` qui indiquent le masquage/le remplissage, puis nous calculerons les métriques sur la liste à la fin : - -```py -import numpy as np - -all_predictions = [] -all_labels = [] -for batch in tf_eval_dataset: - logits = model.predict(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} -``` - -Comment s'est comporté votre modèle, comparé au nôtre ? Si vous avez obtenu des chiffres similaires, votre entraînement a été un succès ! - -{/if} - -{#if fw === 'pt'} - -### Définir le modèle - -Puisque nous travaillons sur un problème de classification de *tokens*, nous allons utiliser la classe `AutoModelForTokenClassification`. La principale chose à retenir lors de la définition de ce modèle est de transmettre des informations sur le nombre de labels que nous avons. La façon la plus simple de le faire est de passer ce nombre avec l'argument `num_labels`, mais si nous voulons un joli *widget* d'inférence fonctionnant comme celui que nous avons vu au début de cette section, il est préférable de définir les correspondances correctes des étiquettes à la place. - -Elles devraient être définies par deux dictionnaires, `id2label` et `label2id`, qui contiennent les correspondances entre ID et label et vice versa : - -```py -id2label = {str(i): label for i, label in enumerate(label_names)} -label2id = {v: k for k, v in id2label.items()} -``` - -Maintenant nous pouvons simplement les passer à la méthode `AutoModelForTokenClassification.from_pretrained()`, et ils seront définis dans la configuration du modèle et ensuite correctement sauvegardés et téléchargés vers le *Hub* : - -```py -from transformers import AutoModelForTokenClassification - -model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, -) -``` - -Comme lorsque nous avons défini notre `AutoModelForSequenceClassification` au [Chapitre 3](/course/fr/chapter3), la création du modèle émet un avertissement indiquant que certains poids n'ont pas été utilisés (ceux de la tête de pré-entraînement) et que d'autres poids ont été initialisés de manière aléatoire (ceux de la tête de classification des nouveaux *tokens*), et que ce modèle doit être entraîné. Nous ferons cela dans une minute, mais vérifions d'abord que notre modèle a le bon nombre d'étiquettes : - -```python -model.config.num_labels -``` - -```python out -9 -``` - - - -⚠️ Si vous avez un modèle avec le mauvais nombre d'étiquettes, vous obtiendrez une erreur obscure lors de l'appel de la méthode `Trainer.train()` plus tard (quelque chose comme "CUDA error : device-side assert triggered"). C'est la première cause de bogues signalés par les utilisateurs pour de telles erreurs, donc assurez-vous de faire cette vérification pour confirmer que vous avez le nombre d'étiquettes attendu. - - - -### *Finetuning* du modèle - -Nous sommes maintenant prêts à entraîner notre modèle ! Nous devons juste faire deux dernières choses avant de définir notre `Trainer` : se connecter à Hugging Face et définir nos arguments d'entraînement. Si vous travaillez dans un *notebook*, il y a une fonction pratique pour vous aider à le faire : - -```python -from huggingface_hub import notebook_login - -notebook_login() -``` - -Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face. - -Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : - -```bash -huggingface-cli login -``` - -Une fois ceci fait, nous pouvons définir nos `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, -) -``` - -Vous avez déjà vu la plupart d'entre eux : nous définissons quelques hyperparamètres (comme le taux d'apprentissage, le nombre d'époques à entraîner, et la décroissance du poids), et nous spécifions `push_to_hub=True` pour indiquer que nous voulons sauvegarder le modèle et l'évaluer à la fin de chaque époque, et que nous voulons télécharger nos résultats vers le *Hub*. Notez que vous pouvez spécifier le nom du référentiel vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/bert-finetuned-ner"``TrainingArguments`. Par défaut, le référentiel utilisé sera dans votre espace de noms et nommé d'après le répertoire de sortie que vous avez défini, donc dans notre cas ce sera `"sgugger/bert-finetuned-ner"`. - - - -💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de la définition de votre `Trainer` et devrez définir un nouveau nom. - - - -Enfin, nous passons tout au `Trainer` et lançons l'entraînement : - -```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() -``` - -Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. De cette façon, vous serez en mesure de reprendre votre entraînement sur une autre machine si nécessaire. - -Une fois l'entraînement terminé, nous utilisons la méthode `push_to_hub()` pour nous assurer que nous téléchargeons la version la plus récente du modèle : - -```py -trainer.push_to_hub(commit_message="Training complete") -``` - -Cette commande renvoie l'URL du commit qu'elle vient de faire, si vous voulez l'inspecter : - -```python out -'https://huggingface.co/sgugger/bert-finetuned-ner/commit/26ab21e5b1568f9afeccdaed2d8715f571d786ed' -``` - -Le `Trainer` rédige également une carte modèle avec tous les résultats de l'évaluation et la télécharge. A ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à affiner un modèle sur une tâche de classification de *tokens*. Félicitations ! - -Si vous voulez plonger un peu plus profondément dans la boucle d'entraînement, nous allons maintenant vous montrer comment faire la même chose en utilisant 🤗 *Accelerate*. - -## Une boucle d'entraînement personnalisée - -Jetons maintenant un coup d'œil à la boucle d'entraînement complète, afin que vous puissiez facilement personnaliser les parties dont vous avez besoin. Elle ressemblera beaucoup à ce que nous avons fait dans le [Chapitre 3](/course/fr/chapter3/4), avec quelques changements pour l'évaluation. - -### Préparer tout pour l'entraînement - -D'abord nous devons construire le `DataLoader`s à partir de nos jeux de données. Nous allons réutiliser notre `data_collator` comme un `collate_fn` et mélanger l'ensemble d'entraînement, mais pas l'ensemble de validation : - -```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 -) -``` - -Ensuite, nous réinstantifions notre modèle, pour nous assurer que nous ne continuons pas le réglage fin d'avant, mais que nous repartons du modèle pré-entraîné de BERT : - -```py -model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, -) -``` - -Ensuite, nous aurons besoin d'un optimiseur. Nous allons utiliser le classique `AdamW`, qui est comme `Adam`, mais avec un correctif dans la façon dont la décroissance du taux des poids est appliquée : - -```py -from torch.optim import AdamW - -optimizer = AdamW(model.parameters(), lr=2e-5) -``` - -Une fois que nous avons tous ces objets, nous pouvons les envoyer à la méthode `accelerator.prepare()` : - -```py -from accelerate import Accelerator - -accelerator = Accelerator() -model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( - model, optimizer, train_dataloader, eval_dataloader -) -``` - - - -🚨 Si vous vous entraînez sur un TPU, vous devrez déplacer tout le code à partir de la cellule ci-dessus dans une fonction d'entraînement dédiée. Voir le [Chapitre 3](/course/fr/chapter3) pour plus de détails. - - - -Maintenant que nous avons envoyé notre `train_dataloader` à `accelerator.prepare()`, nous pouvons utiliser sa longueur pour calculer le nombre d'étapes d'entraînement. Rappelez-vous que nous devrions toujours faire cela après avoir préparé le *dataloader*, car cette méthode modifiera sa longueur. Nous utilisons un programme linéaire classique du taux d'apprentissage à 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, -) -``` - -Enfin, pour pousser notre modèle vers le Hub, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous à Hugging Face, si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'ID du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix ; il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `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' -``` - -Ensuite, nous pouvons cloner ce référentiel dans un dossier local. S'il existe déjà, ce dossier local doit être un clone existant du référentiel avec lequel nous travaillons : - -```py -output_dir = "bert-finetuned-ner-accelerate" -repo = Repository(output_dir, clone_from=repo_name) -``` - -Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output_dir` en appelant la méthode `repo.push_to_hub()`. Cela nous aidera à télécharger les modèles intermédiaires à la fin de chaque époque. - -### Boucle d'entraînement - -Nous sommes maintenant prêts à écrire la boucle d'entraînement complète. Pour simplifier sa partie évaluation, nous définissons cette fonction `postprocess()` qui prend les prédictions et les étiquettes et les convertit en listes de chaînes de caractères, comme notre objet `metric` l'attend : - -```py -def postprocess(predictions, labels): - predictions = predictions.detach().cpu().clone().numpy() - labels = labels.detach().cpu().clone().numpy() - - # Remove ignored index (special tokens) and convert to labels - 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 -``` - -Ensuite, nous pouvons écrire la boucle d'entraînement. Après avoir défini une barre de progression pour suivre l'évolution de l'entraînement, la boucle comporte trois parties : - -- l'entraînement proprement dit, qui est l'itération classique sur le `train_dataloader`, passage en avant du modèle, puis passage en arrière et étape d'optimisation, -- l'évaluation, dans laquelle il y a une nouveauté après avoir obtenu les sorties de notre modèle sur un lot : puisque deux processus peuvent avoir paddé les entrées et les étiquettes à des formes différentes, nous devons utiliser `accelerator.pad_across_processes()` pour rendre les prédictions et les étiquettes de la même forme avant d'appeler la méthode `gather()`. Si nous ne le faisons pas, l'évaluation va soit se tromper, soit se bloquer pour toujours. Ensuite, nous envoyons les résultats à `metric.add_batch()` et appelons `metric.compute()` une fois que la boucle d'évaluation est terminée, -- sauvegarde et téléchargement, où nous sauvegardons d'abord le modèle et le tokenizer, puis appelons `repo.push_to_hub()`. Remarquez que nous utilisons l'argument `blocking=False` pour indiquer à la bibliothèque 🤗 *Hub* de pousser dans un processus asynchrone. De cette façon, l'entraînement continue normalement et cette (longue) instruction est exécutée en arrière-plan. - -Voici le code complet de la boucle d'entraînement : - -```py -from tqdm.auto import tqdm -import torch - -progress_bar = tqdm(range(num_training_steps)) - -for epoch in range(num_train_epochs): - # Entraînement - 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) - - # Evaluation - model.eval() - for batch in eval_dataloader: - with torch.no_grad(): - outputs = model(**batch) - - predictions = outputs.logits.argmax(dim=-1) - labels = batch["labels"] - - # Nécessaire pour rembourrer les prédictions et les étiquettes à rassembler - 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"] - }, - ) - - # Sauvegarder et télécharger - 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 - ) -``` - -Au cas où ce serait la première fois que vous verriez un modèle enregistré avec 🤗 *Accelerate*, prenons un moment pour inspecter les trois lignes de code qui l'accompagnent : - -```py -accelerator.wait_for_everyone() -unwrapped_model = accelerator.unwrap_model(model) -unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) -``` - -La première ligne est explicite : elle indique à tous les processus d'attendre que tout le monde soit à ce stade avant de continuer. C'est pour s'assurer que nous avons le même modèle dans chaque processus avant de sauvegarder. Ensuite, nous prenons le `unwrapped_model`, qui est le modèle de base que nous avons défini. La méthode `accelerator.prepare()` modifie le modèle pour qu'il fonctionne dans l'entraînement distribué, donc il n'aura plus la méthode `save_pretrained()` ; la méthode `accelerator.unwrap_model()` annule cette étape. Enfin, nous appelons `save_pretrained()` mais nous disons à cette méthode d'utiliser `accelerator.save()` au lieu de `torch.save()`. - -Une fois ceci fait, vous devriez avoir un modèle qui produit des résultats assez similaires à celui entraîné avec le `Trainer`. Vous pouvez vérifier le modèle que nous avons formé en utilisant ce code à [*huggingface-course/bert-finetuned-ner-accelerate*(https://huggingface.co/huggingface-course/bert-finetuned-ner-accelerate). Et si vous voulez tester des modifications de la boucle d'entraînement, vous pouvez les implémenter directement en modifiant le code ci-dessus ! - -{/if} - -### Utilisation du modèle *finetuné* - -Nous vous avons déjà montré comment vous pouvez utiliser le modèle que nous avons affiné sur le *Hub* avec le *widget* d'inférence. Pour l'utiliser localement dans un `pipeline`, vous devez juste spécifier l'identifiant de modèle approprié : - -```py -from transformers import pipeline - -# Remplacez ceci par votre propre checkpoint -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}] -``` - -Super ! Notre modèle fonctionne aussi bien que le modèle par défaut pour ce pipeline ! + + +# Classification de *tokens* + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +La première application que nous allons explorer est la classification de *tokens*. Cette tâche générique englobe tous les problèmes qui peuvent être formulés comme "l'attribution d'une étiquette à chaque *token* dans une phrase", tels que : + +- **reconnaissance d'entités nommées (NER)** : trouver les entités (telles que des personnes, des lieux ou des organisations) dans une phrase. Cela peut être formulé comme l'attribution d'une étiquette à chaque *token* en ayant une classe par entité et une classe pour "aucune entité". +- **part-of-speech tagging (POS)** : marquer chaque mot dans une phrase comme correspondant à une partie particulière du discours (comme un nom, un verbe, un adjectif, etc.). +- ***chunking*** : trouver les *tokens* qui appartiennent à la même entité. Cette tâche (qui peut être combinée avec le POS ou la NER) peut être formulée comme l'attribution d'une étiquette (habituellement `B-`) à tous les *tokens* qui sont au début d'un morceau, une autre étiquette (habituellement `I-`) aux *tokens* qui sont à l'intérieur d'un morceau, et une troisième étiquette (habituellement `O`) aux *tokens* qui n'appartiennent à aucun morceau. + + + +Bien sûr, il existe de nombreux autres types de problèmes de classification de *tokens* ; ce ne sont là que quelques exemples représentatifs. Dans cette section, nous allons affiner un modèle (BERT) sur une tâche NER, qui sera alors capable de calculer des prédictions comme celle-ci : + + + + + +One-hot encoded labels for question answering. + + + +Vous pouvez trouver le modèle que nous allons entraîner et télécharger sur le *Hub* et vérifier ses prédictions [ici](https://huggingface.co/huggingface-course/bert-finetuned-ner?text=My+nom+est+Sylvain+et+je+travaille+à+Hugging+Face+in+Brooklyn). + +## Préparation des données + +Tout d'abord, nous avons besoin d'un jeu de données adapté à la classification des *tokens*. Dans cette section, nous utiliserons le jeu de données [CoNLL-2003](https://huggingface.co/datasets/conll2003), qui contient des articles de presse de Reuters. + + + +💡 Tant que votre jeu de données consiste en des textes divisés en mots avec leurs étiquettes correspondantes, vous pourrez adapter les procédures de traitement des données décrites ici à votre propre jeu de données. Reportez-vous au [Chapitre 5](/course/fr/chapter5) si vous avez besoin d'un rafraîchissement sur la façon de charger vos propres données personnalisées dans un `Dataset`. + + + +### Le jeu de données CoNLL-2003 + +Pour charger le jeu de données CoNLL-2003, nous utilisons la méthode `load_dataset()` de la bibliothèque 🤗 *Datasets* : + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("conll2003") +``` + +Cela va télécharger et mettre en cache le jeu de données, comme nous l'avons vu dans [Chapitre 3](/course/fr/chapter3) pour le jeu de données GLUE MRPC. L'inspection de cet objet nous montre les colonnes présentes et la répartition entre les ensembles d'entraînement, de validation et de test : + +```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 + }) +}) +``` + +En particulier, nous pouvons voir que le jeu de données contient des étiquettes pour les trois tâches que nous avons mentionnées précédemment : NER, POS, et *chunking*. Une grande différence avec les autres jeux de données est que les textes d'entrée ne sont pas présentés comme des phrases ou des documents, mais comme des listes de mots (la dernière colonne est appelée `tokens`, mais elle contient des mots dans le sens où ce sont des entrées pré-tokénisées qui doivent encore passer par le *tokenizer* pour la tokenisation des sous-mots). + +Regardons le premier élément de l'ensemble d'entraînement : + +```py +raw_datasets["train"][0]["tokens"] +``` + +```python out +['EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'lamb', '.'] +``` + +Puisque nous voulons effectuer la reconnaissance des entités nommées, nous allons examiner les balises NER : + +```py +raw_datasets["train"][0]["ner_tags"] +``` + +```python out +[3, 0, 7, 0, 0, 0, 7, 0, 0] +``` + +Ce sont les étiquettes sous forme d'entiers prêts pour l'entraînement, mais ils ne sont pas nécessairement utiles lorsque nous voulons inspecter les données. Comme pour la classification de texte, nous pouvons accéder à la correspondance entre ces entiers et les noms des étiquettes en regardant l'attribut `features` de notre jeu de données : + +```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) +``` + +Cette colonne contient donc des éléments qui sont des séquences de `ClassLabel`s. Le type des éléments de la séquence se trouve dans l'attribut `feature` de cette `ner_feature`, et nous pouvons accéder à la liste des noms en regardant l'attribut `names` de cette `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'] +``` + +Nous avons déjà vu ces étiquettes en creusant dans le pipeline `token-classification` au [Chapitre 6](/course/fr/chapter6/3), mais pour un rapide rappel : + +- `O` signifie que le mot ne correspond à aucune entité. +- `B-PER`/`I-PER` signifie que le mot correspond au début de/est à l'intérieur d'une entité *personne*. +- `B-ORG`/`I-ORG` signifie que le mot correspond au début/à l'intérieur d'une entité *organisation*. +- `B-LOC`/`I-LOC` signifie que le mot correspond au début/à l'intérieur d'une entité *location*. +- `B-MISC`/`I-MISC` signifie que le mot correspond au début/à l'intérieur d'une entité *divers*. + +Maintenant, le décodage des étiquettes que nous avons vues précédemment nous donne ceci : + +```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' +``` + +Et pour un exemple mélangeant les étiquettes `B-` et `I-`, voici ce que le même code nous donne sur l'élément de l'ensemble d'entraînement à l'indice 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' +``` + +Comme on peut le voir, les entités couvrant deux mots, comme "Union européenne" et "Werner Zwingmann", se voient attribuer une étiquette "B-" pour le premier mot et une étiquette "I-" pour le second. + + + +✏️ *Votre tour !* Affichez les deux mêmes phrases avec leurs étiquettes POS ou *chunking*. + + + +### Traitement des données + + + +Comme d'habitude, nos textes doivent être convertis en identifiants de *tokens* avant que le modèle puisse leur donner un sens. Comme nous l'avons vu dans le [Chapitre 6](/course/fr/chapter6/), une grande différence dans le cas des tâches de classification de *tokens* est que nous avons des entrées pré-tokénisées. Heureusement, l'API tokenizer peut gérer cela assez facilement ; nous devons juste avertir le `tokenizer` avec un drapeau spécial. + +Pour commencer, nous allons créer notre objet `tokenizer`. Comme nous l'avons dit précédemment, nous allons utiliser un modèle pré-entraîné BERT, donc nous allons commencer par télécharger et mettre en cache le tokenizer associé : + +```python +from transformers import AutoTokenizer + +model_checkpoint = "bert-base-cased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +Vous pouvez remplacer le `model_checkpoint` par tout autre modèle que vous préférez à partir du [*Hub*]https://huggingface.co/models), ou par un dossier local dans lequel vous avez sauvegardé un modèle pré-entraîné et un *tokenizer*. La seule contrainte est que le *tokenizer* doit être soutenu par la bibliothèque 🤗 *Tokenizers*, il y a donc une version "rapide" disponible. Vous pouvez voir toutes les architectures qui ont une version rapide dans [ce grand tableau](https://huggingface.co/transformers/#supported-frameworks), et pour vérifier que l'objet `tokenizer` que vous utilisez est bien soutenu par 🤗 *Tokenizers* vous pouvez regarder son attribut `is_fast` : + +```py +tokenizer.is_fast +``` + +```python out +True +``` + +Pour tokeniser une entrée pré-tokenisée, nous pouvons utiliser notre `tokenizer` comme d'habitude et juste ajouter `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]'] +``` + +Comme on peut le voir, le *tokenizer* a ajouté les *tokens* spéciaux utilisés par le modèle (`[CLS]` au début et `[SEP]` à la fin) et n'a pas touché à la plupart des mots. Le mot `lamb`, cependant, a été tokenisé en deux sous-mots, `la` et `##mb`. Cela introduit un décalage entre nos entrées et les étiquettes : la liste des étiquettes n'a que 9 éléments, alors que notre entrée a maintenant 12 *tokens*. Il est facile de tenir compte des *tokens* spéciaux (nous savons qu'ils sont au début et à la fin), mais nous devons également nous assurer que nous alignons toutes les étiquettes avec les mots appropriés. + +Heureusement, comme nous utilisons un *tokenizer* rapide, nous avons accès aux superpouvoirs des 🤗 *Tokenizers*, ce qui signifie que nous pouvons facilement faire correspondre chaque *token* au mot correspondant (comme on le voit au [Chapitre 6](/course/fr/chapter6/3)) : + +```py +inputs.word_ids() +``` + +```python out +[None, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, None] +``` + +Avec un peu de travail, nous pouvons alors étendre notre liste d'étiquettes pour qu'elle corresponde aux *tokens*. La première règle que nous allons appliquer est que les *tokens* spéciaux reçoivent une étiquette de `-100`. En effet, par défaut, `-100` est un indice qui est ignoré dans la fonction de perte que nous allons utiliser (entropie croisée). Ensuite, chaque *token* reçoit la même étiquette que le *token* qui a commencé le mot dans lequel il se trouve, puisqu'ils font partie de la même entité. Pour les *tokens* à l'intérieur d'un mot mais pas au début, nous remplaçons le `B-` par `I-` (puisque le *token* ne commence pas l'entité) : + +```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: + # Start of a new 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: + # Special token + new_labels.append(-100) + else: + # Same word as previous token + label = labels[word_id] + # If the label is B-XXX we change it to I-XXX + if label % 2 == 1: + label += 1 + new_labels.append(label) + + return new_labels +``` + +Essayons-le sur notre première phrase : + +```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] +``` + +Comme nous pouvons le voir, notre fonction a ajouté le `-100` pour les deux *tokens* spéciaux au début et à la fin, et un nouveau `0` pour notre mot qui a été divisé en deux *tokens*. + + + +✏️ *Votre tour !* Certains chercheurs préfèrent n'attribuer qu'un seul label par mot, et attribuer `-100` aux autres sous-*tokens* dans un mot donné. Ceci afin d'éviter que les longs mots qui se divisent en plusieurs batchs ne contribuent fortement à la perte. Changez la fonction précédente pour aligner les étiquettes avec les ID d'entrée en suivant cette règle. + + +Pour prétraiter notre ensemble de données, nous devons tokeniser toutes les entrées et appliquer `align_labels_with_tokens()` sur toutes les étiquettes. Pour profiter de la vitesse de notre *tokenizer* rapide, il est préférable de tokeniser beaucoup de textes en même temps, donc nous allons écrire une fonction qui traite une liste d'exemples et utiliser la méthode `Dataset.map()` avec l'option `batched=True`. La seule chose qui diffère de notre exemple précédent est que la fonction `word_ids()` a besoin de récupérer l'index de l'exemple dont nous voulons les IDs de mots lorsque les entrées du *tokenizer* sont des listes de textes (ou dans notre cas, des listes de mots), donc nous l'ajoutons aussi : + +```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 +``` + +Notez que nous n'avons pas encore paddé nos entrées ; nous le ferons plus tard, lors de la création des lots avec un collateur de données. + +Nous pouvons maintenant appliquer tout ce prétraitement en une seule fois sur les autres divisions de notre jeu de données : + +```py +tokenized_datasets = raw_datasets.map( + tokenize_and_align_labels, + batched=True, + remove_columns=raw_datasets["train"].column_names, +) +``` + +Nous avons fait la partie la plus difficile ! Maintenant que les données ont été prétraitées, l'entraînement réel ressemblera beaucoup à ce que nous avons fait dans le [Chapitre 3](/course/fr/chapter3). + +{#if fw === 'pt'} + +## *Finetuning* du modèle avec l'API `Trainer`. + +Le code actuel utilisant le `Trainer` sera le même que précédemment ; les seuls changements sont la façon dont les données sont rassemblées dans un batch et la fonction de calcul de la métrique. + +{:else} + +## *Finetuning* fin du modèle avec Keras + +Le code réel utilisant Keras sera très similaire au précédent ; les seuls changements sont la façon dont les données sont rassemblées dans un batch et la fonction de calcul de la métrique. + +{/if} + + +### Collation des données + +Nous ne pouvons pas simplement utiliser un `DataCollatorWithPadding` comme dans [Chapter 3](/course/fr/chapter3) parce que cela ne fait que rembourrer les entrées (IDs d'entrée, masque d'attention, et IDs de type de *token*). Ici, nos étiquettes doivent être remplies exactement de la même manière que les entrées afin qu'elles gardent la même taille, en utilisant `-100` comme valeur afin que les prédictions correspondantes soient ignorées dans le calcul de la perte. + +Tout ceci est fait par un [`DataCollatorForTokenClassification`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorfortokenclassification). Comme le `DataCollatorWithPadding`, il prend le `tokenizer` utilisé pour prétraiter les entrées : + +{#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} + +Pour tester cette fonction sur quelques échantillons, nous pouvons simplement l'appeler sur une liste d'exemples provenant de notre jeu d'entraînement tokénisé : + +```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]]) +``` + +Comparons cela aux étiquettes des premier et deuxième éléments de notre jeu de données : + +```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'} + +Comme nous pouvons le voir, le deuxième jeu d'étiquettes a été complété à la longueur du premier en utilisant `-100`s. + +{:else} + +Notre collateur de données est prêt à fonctionner ! Maintenant, utilisons-le pour créer un `tf.data.Dataset` avec la méthode `to_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, +) +``` + + + Prochain arrêt : le modèle lui-même. + +{/if} + +{#if fw === 'tf'} + +### Définir le modèle + +Puisque nous travaillons sur un problème de classification de *tokens*, nous allons utiliser la classe `TFAutoModelForTokenClassification`. La principale chose à retenir lors de la définition de ce modèle est de transmettre des informations sur le nombre de labels que nous avons. La façon la plus simple de le faire est de passer ce nombre avec l'argument `num_labels`, mais si nous voulons un joli *widget* d'inférence fonctionnant comme celui que nous avons vu au début de cette section, il est préférable de définir les correspondances correctes des étiquettes à la place. + +Elles devraient être définies par deux dictionnaires, `id2label` et `label2id`, qui contiennent la correspondance de l'ID au label et vice versa : + +```py +id2label = {str(i): label for i, label in enumerate(label_names)} +label2id = {v: k for k, v in id2label.items()} +``` + +Maintenant, nous pouvons simplement les passer à la méthode `TFAutoModelForTokenClassification.from_pretrained()`, et ils seront définis dans la configuration du modèle, puis correctement enregistrés et téléchargés vers le *Hub* : + +```py +from transformers import TFAutoModelForTokenClassification + +model = TFAutoModelForTokenClassification.from_pretrained( + model_checkpoint, + id2label=id2label, + label2id=label2id, +) +``` + +Comme lorsque nous avons défini notre `TFAutoModelForSequenceClassification` au [Chapitre 3](/course/fr/chapter3), la création du modèle émet un avertissement indiquant que certains poids n'ont pas été utilisés (ceux de la tête de pré-entraînement) et que d'autres poids ont été initialisés de manière aléatoire (ceux de la tête de classification des nouveaux *tokens*), et que ce modèle doit être entraîné. Nous ferons cela dans une minute, mais vérifions d'abord que notre modèle a le bon nombre d'étiquettes : + +```python +model.config.num_labels +``` + +```python out +9 +``` + + + +⚠️ Si vous avez un modèle avec le mauvais nombre de labels, vous obtiendrez une erreur obscure en appelant `model.fit()` plus tard. Cela peut être ennuyeux à déboguer, donc assurez-vous de faire cette vérification pour confirmer que vous avez le nombre de labels attendu. + + + +### *Finetuning* du modèle + +Nous sommes maintenant prêts à entraîner notre modèle ! Mais nous devons d'abord faire un peu de ménage : nous devons nous connecter à Hugging Face et définir nos hyperparamètres d'entraînement. Si vous travaillez dans un *notebook*, il y a une fonction pratique pour vous aider à le faire : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face. + +Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : + +```bash +huggingface-cli login +``` + +Après s'être connecté, nous pouvons préparer tout ce dont nous avons besoin pour compiler notre modèle. 🤗 *Transformers* fournit une fonction pratique `create_optimizer()` qui vous donnera un optimiseur `AdamW` avec des paramètres appropriés pour la décroissance du taux des poids et la décroissance du taux d'apprentissage, les deux améliorant les performances de votre modèle par rapport à l'optimiseur `Adam` intégré : + +```python +from transformers import create_optimizer +import tensorflow as tf + +# Entraîner en mixed-precision float16 +# Commentez cette ligne si vous utilisez un GPU qui ne bénéficiera pas de cette fonction +tf.keras.mixed_precision.set_global_policy("mixed_float16") + +# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans l'ensemble de données, divisé par la taille du batch puis multiplié par le nombre total d'époques +# par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un batchtf.data.Dataset, +# et non le jeu de données original Hugging Face Dataset, donc son len() est déjà 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) +``` + +Notez également que nous ne fournissons pas d'argument `loss` à `compile()`. C'est parce que les modèles peuvent en fait calculer la perte en interne. Si vous compilez sans perte et fournissez vos étiquettes dans le dictionnaire d'entrée (comme nous le faisons dans nos jeux de données), alors le modèle s'entraînera en utilisant cette perte interne, qui sera appropriée pour la tâche et le type de modèle que vous avez choisi. + +Ensuite, nous définissons un `PushToHubCallback` pour télécharger notre modèle vers le *Hub* pendant l'entraînement, et nous ajustons le modèle avec ce *callback* : + +```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, +) +``` + +Vous pouvez spécifier le nom complet du référentiel vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/bert-finetuned-ner"`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, par exemple `"cool_huggingface_user/bert-finetuned-ner"`. + + + +💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de l'appel de `model.fit()` et devrez définir un nouveau nom. + + + +Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. De cette façon, vous pourrez reprendre votre entraînement sur une autre machine si nécessaire. + +A ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à *finetuner* un modèle sur une tâche de classification de *tokens*. Félicitations ! Mais quelle est la qualité réelle de notre modèle ? Nous devons évaluer certaines métriques pour le découvrir. + +{/if} + + +### Métriques + +{#if fw === 'pt'} + +Pour que le `Trainer` calcule une métrique à chaque époque, nous devrons définir une fonction `compute_metrics()` qui prend les tableaux de prédictions et de labels, et retourne un dictionnaire avec les noms et les valeurs des métriques. + +Le cadre traditionnel utilisé pour évaluer la prédiction de la classification des *tokens* est [*seqeval*](https://github.com/chakki-works/seqeval). Pour utiliser cette métrique, nous devons d'abord installer la bibliothèque *seqeval* : + +```py +!pip install seqeval +``` + +Nous pouvons ensuite le charger via la fonction `load_metric()` comme nous l'avons fait dans le [Chapitre 3](/course/fr/chapter3) : + +{:else} + +Le cadre traditionnel utilisé pour évaluer la prédiction de la classification des *tokens* est [*seqeval*](https://github.com/chakki-works/seqeval). Pour utiliser cette métrique, nous devons d'abord installer la bibliothèque *seqeval* : + +```py +!pip install seqeval +``` + +Nous pouvons ensuite le charger via la fonction `load_metric()` comme nous l'avons fait dans le [Chapitre 3](/course/fr/chapter3) : + +{/if} + +```py +from datasets import load_metric + +metric = load_metric("seqeval") +``` + +Cette métrique ne se comporte pas comme la précision standard : elle prend les listes d'étiquettes comme des chaînes de caractères et non comme des entiers. Nous devrons donc décoder complètement les prédictions et les étiquettes avant de les transmettre à la métrique. Voyons comment cela fonctionne. Tout d'abord, nous allons obtenir les étiquettes pour notre premier exemple d'entraînement : + +```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'] +``` + +Nous pouvons alors créer de fausses prédictions pour celles-ci en changeant simplement la valeur de l'indice 2 : + +```py +predictions = labels.copy() +predictions[2] = "O" +metric.compute(predictions=[predictions], references=[labels]) +``` + +Notez que la métrique prend une liste de prédictions (pas seulement une) et une liste d'étiquettes. Voici la sortie : + +```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'} + +Cela renvoie un batch d'informations ! Nous obtenons la précision, le rappel, et le score F1 pour chaque entité séparée, ainsi que le score global. Pour notre calcul de métrique, nous ne garderons que le score global, mais n'hésitez pas à modifier la fonction `compute_metrics()` pour retourner toutes les métriques que vous souhaitez. + +Cette fonction `compute_metrics()` prend d'abord l'argmax des logits pour les convertir en prédictions (comme d'habitude, les logits et les probabilités sont dans le même ordre, donc nous n'avons pas besoin d'appliquer le softmax). Ensuite, nous devons convertir les étiquettes et les prédictions des entiers en chaînes de caractères. Nous supprimons toutes les valeurs dont l'étiquette est `-100`, puis nous passons les résultats à la méthode `metric.compute()` : + +```py +import numpy as np + + +def compute_metrics(eval_preds): + logits, labels = eval_preds + predictions = np.argmax(logits, axis=-1) + + # Suppression de l'index ignoré (tokens spéciaux) et conversion en étiquettes + 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"], + } +``` + +Maintenant que ceci est fait, nous sommes presque prêts à définir notre `Trainer`. Nous avons juste besoin d'un `modèle` pour *finetuner* ! + +{:else} + +Cela renvoie un batch d'informations ! Nous obtenons la précision, le rappel et le score F1 pour chaque entité séparée, ainsi que pour l'ensemble. Voyons maintenant ce qui se passe si nous essayons d'utiliser les prédictions de notre modèle pour calculer des scores réels. + +TensorFlow n'aime pas concaténer nos prédictions ensemble, car elles ont des longueurs de séquence variables. Cela signifie que nous ne pouvons pas simplement utiliser `model.predict()`. Mais cela ne va pas nous arrêter. Nous obtiendrons des prédictions un batch à la fois et les concaténerons en une grande liste longue au fur et à mesure, en laissant tomber les *tokens* `-100` qui indiquent le masquage/le remplissage, puis nous calculerons les métriques sur la liste à la fin : + +```py +import numpy as np + +all_predictions = [] +all_labels = [] +for batch in tf_eval_dataset: + logits = model.predict(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} +``` + +Comment s'est comporté votre modèle, comparé au nôtre ? Si vous avez obtenu des chiffres similaires, votre entraînement a été un succès ! + +{/if} + +{#if fw === 'pt'} + +### Définir le modèle + +Puisque nous travaillons sur un problème de classification de *tokens*, nous allons utiliser la classe `AutoModelForTokenClassification`. La principale chose à retenir lors de la définition de ce modèle est de transmettre des informations sur le nombre de labels que nous avons. La façon la plus simple de le faire est de passer ce nombre avec l'argument `num_labels`, mais si nous voulons un joli *widget* d'inférence fonctionnant comme celui que nous avons vu au début de cette section, il est préférable de définir les correspondances correctes des étiquettes à la place. + +Elles devraient être définies par deux dictionnaires, `id2label` et `label2id`, qui contiennent les correspondances entre ID et label et vice versa : + +```py +id2label = {str(i): label for i, label in enumerate(label_names)} +label2id = {v: k for k, v in id2label.items()} +``` + +Maintenant nous pouvons simplement les passer à la méthode `AutoModelForTokenClassification.from_pretrained()`, et ils seront définis dans la configuration du modèle et ensuite correctement sauvegardés et téléchargés vers le *Hub* : + +```py +from transformers import AutoModelForTokenClassification + +model = AutoModelForTokenClassification.from_pretrained( + model_checkpoint, + id2label=id2label, + label2id=label2id, +) +``` + +Comme lorsque nous avons défini notre `AutoModelForSequenceClassification` au [Chapitre 3](/course/fr/chapter3), la création du modèle émet un avertissement indiquant que certains poids n'ont pas été utilisés (ceux de la tête de pré-entraînement) et que d'autres poids ont été initialisés de manière aléatoire (ceux de la tête de classification des nouveaux *tokens*), et que ce modèle doit être entraîné. Nous ferons cela dans une minute, mais vérifions d'abord que notre modèle a le bon nombre d'étiquettes : + +```python +model.config.num_labels +``` + +```python out +9 +``` + + + +⚠️ Si vous avez un modèle avec le mauvais nombre d'étiquettes, vous obtiendrez une erreur obscure lors de l'appel de la méthode `Trainer.train()` plus tard (quelque chose comme "CUDA error : device-side assert triggered"). C'est la première cause de bogues signalés par les utilisateurs pour de telles erreurs, donc assurez-vous de faire cette vérification pour confirmer que vous avez le nombre d'étiquettes attendu. + + + +### *Finetuning* du modèle + +Nous sommes maintenant prêts à entraîner notre modèle ! Nous devons juste faire deux dernières choses avant de définir notre `Trainer` : se connecter à Hugging Face et définir nos arguments d'entraînement. Si vous travaillez dans un *notebook*, il y a une fonction pratique pour vous aider à le faire : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face. + +Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : + +```bash +huggingface-cli login +``` + +Une fois ceci fait, nous pouvons définir nos `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, +) +``` + +Vous avez déjà vu la plupart d'entre eux : nous définissons quelques hyperparamètres (comme le taux d'apprentissage, le nombre d'époques à entraîner, et la décroissance du poids), et nous spécifions `push_to_hub=True` pour indiquer que nous voulons sauvegarder le modèle et l'évaluer à la fin de chaque époque, et que nous voulons télécharger nos résultats vers le *Hub*. Notez que vous pouvez spécifier le nom du référentiel vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/bert-finetuned-ner"``TrainingArguments`. Par défaut, le référentiel utilisé sera dans votre espace de noms et nommé d'après le répertoire de sortie que vous avez défini, donc dans notre cas ce sera `"sgugger/bert-finetuned-ner"`. + + + +💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de la définition de votre `Trainer` et devrez définir un nouveau nom. + + + +Enfin, nous passons tout au `Trainer` et lançons l'entraînement : + +```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() +``` + +Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. De cette façon, vous serez en mesure de reprendre votre entraînement sur une autre machine si nécessaire. + +Une fois l'entraînement terminé, nous utilisons la méthode `push_to_hub()` pour nous assurer que nous téléchargeons la version la plus récente du modèle : + +```py +trainer.push_to_hub(commit_message="Training complete") +``` + +Cette commande renvoie l'URL du commit qu'elle vient de faire, si vous voulez l'inspecter : + +```python out +'https://huggingface.co/sgugger/bert-finetuned-ner/commit/26ab21e5b1568f9afeccdaed2d8715f571d786ed' +``` + +Le `Trainer` rédige également une carte modèle avec tous les résultats de l'évaluation et la télécharge. A ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à affiner un modèle sur une tâche de classification de *tokens*. Félicitations ! + +Si vous voulez plonger un peu plus profondément dans la boucle d'entraînement, nous allons maintenant vous montrer comment faire la même chose en utilisant 🤗 *Accelerate*. + +## Une boucle d'entraînement personnalisée + +Jetons maintenant un coup d'œil à la boucle d'entraînement complète, afin que vous puissiez facilement personnaliser les parties dont vous avez besoin. Elle ressemblera beaucoup à ce que nous avons fait dans le [Chapitre 3](/course/fr/chapter3/4), avec quelques changements pour l'évaluation. + +### Préparer tout pour l'entraînement + +D'abord nous devons construire le `DataLoader`s à partir de nos jeux de données. Nous allons réutiliser notre `data_collator` comme un `collate_fn` et mélanger l'ensemble d'entraînement, mais pas l'ensemble de validation : + +```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 +) +``` + +Ensuite, nous réinstantifions notre modèle, pour nous assurer que nous ne continuons pas le réglage fin d'avant, mais que nous repartons du modèle pré-entraîné de BERT : + +```py +model = AutoModelForTokenClassification.from_pretrained( + model_checkpoint, + id2label=id2label, + label2id=label2id, +) +``` + +Ensuite, nous aurons besoin d'un optimiseur. Nous allons utiliser le classique `AdamW`, qui est comme `Adam`, mais avec un correctif dans la façon dont la décroissance du taux des poids est appliquée : + +```py +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Une fois que nous avons tous ces objets, nous pouvons les envoyer à la méthode `accelerator.prepare()` : + +```py +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + + + +🚨 Si vous vous entraînez sur un TPU, vous devrez déplacer tout le code à partir de la cellule ci-dessus dans une fonction d'entraînement dédiée. Voir le [Chapitre 3](/course/fr/chapter3) pour plus de détails. + + + +Maintenant que nous avons envoyé notre `train_dataloader` à `accelerator.prepare()`, nous pouvons utiliser sa longueur pour calculer le nombre d'étapes d'entraînement. Rappelez-vous que nous devrions toujours faire cela après avoir préparé le *dataloader*, car cette méthode modifiera sa longueur. Nous utilisons un programme linéaire classique du taux d'apprentissage à 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, +) +``` + +Enfin, pour pousser notre modèle vers le Hub, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous à Hugging Face, si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'ID du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix ; il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `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' +``` + +Ensuite, nous pouvons cloner ce référentiel dans un dossier local. S'il existe déjà, ce dossier local doit être un clone existant du référentiel avec lequel nous travaillons : + +```py +output_dir = "bert-finetuned-ner-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output_dir` en appelant la méthode `repo.push_to_hub()`. Cela nous aidera à télécharger les modèles intermédiaires à la fin de chaque époque. + +### Boucle d'entraînement + +Nous sommes maintenant prêts à écrire la boucle d'entraînement complète. Pour simplifier sa partie évaluation, nous définissons cette fonction `postprocess()` qui prend les prédictions et les étiquettes et les convertit en listes de chaînes de caractères, comme notre objet `metric` l'attend : + +```py +def postprocess(predictions, labels): + predictions = predictions.detach().cpu().clone().numpy() + labels = labels.detach().cpu().clone().numpy() + + # Remove ignored index (special tokens) and convert to labels + 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 +``` + +Ensuite, nous pouvons écrire la boucle d'entraînement. Après avoir défini une barre de progression pour suivre l'évolution de l'entraînement, la boucle comporte trois parties : + +- l'entraînement proprement dit, qui est l'itération classique sur le `train_dataloader`, passage en avant du modèle, puis passage en arrière et étape d'optimisation, +- l'évaluation, dans laquelle il y a une nouveauté après avoir obtenu les sorties de notre modèle sur un lot : puisque deux processus peuvent avoir paddé les entrées et les étiquettes à des formes différentes, nous devons utiliser `accelerator.pad_across_processes()` pour rendre les prédictions et les étiquettes de la même forme avant d'appeler la méthode `gather()`. Si nous ne le faisons pas, l'évaluation va soit se tromper, soit se bloquer pour toujours. Ensuite, nous envoyons les résultats à `metric.add_batch()` et appelons `metric.compute()` une fois que la boucle d'évaluation est terminée, +- sauvegarde et téléchargement, où nous sauvegardons d'abord le modèle et le tokenizer, puis appelons `repo.push_to_hub()`. Remarquez que nous utilisons l'argument `blocking=False` pour indiquer à la bibliothèque 🤗 *Hub* de pousser dans un processus asynchrone. De cette façon, l'entraînement continue normalement et cette (longue) instruction est exécutée en arrière-plan. + +Voici le code complet de la boucle d'entraînement : + +```py +from tqdm.auto import tqdm +import torch + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Entraînement + 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) + + # Evaluation + model.eval() + for batch in eval_dataloader: + with torch.no_grad(): + outputs = model(**batch) + + predictions = outputs.logits.argmax(dim=-1) + labels = batch["labels"] + + # Nécessaire pour rembourrer les prédictions et les étiquettes à rassembler + 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"] + }, + ) + + # Sauvegarder et télécharger + 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 + ) +``` + +Au cas où ce serait la première fois que vous verriez un modèle enregistré avec 🤗 *Accelerate*, prenons un moment pour inspecter les trois lignes de code qui l'accompagnent : + +```py +accelerator.wait_for_everyone() +unwrapped_model = accelerator.unwrap_model(model) +unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) +``` + +La première ligne est explicite : elle indique à tous les processus d'attendre que tout le monde soit à ce stade avant de continuer. C'est pour s'assurer que nous avons le même modèle dans chaque processus avant de sauvegarder. Ensuite, nous prenons le `unwrapped_model`, qui est le modèle de base que nous avons défini. La méthode `accelerator.prepare()` modifie le modèle pour qu'il fonctionne dans l'entraînement distribué, donc il n'aura plus la méthode `save_pretrained()` ; la méthode `accelerator.unwrap_model()` annule cette étape. Enfin, nous appelons `save_pretrained()` mais nous disons à cette méthode d'utiliser `accelerator.save()` au lieu de `torch.save()`. + +Une fois ceci fait, vous devriez avoir un modèle qui produit des résultats assez similaires à celui entraîné avec le `Trainer`. Vous pouvez vérifier le modèle que nous avons formé en utilisant ce code à [*huggingface-course/bert-finetuned-ner-accelerate*(https://huggingface.co/huggingface-course/bert-finetuned-ner-accelerate). Et si vous voulez tester des modifications de la boucle d'entraînement, vous pouvez les implémenter directement en modifiant le code ci-dessus ! + +{/if} + +### Utilisation du modèle *finetuné* + +Nous vous avons déjà montré comment vous pouvez utiliser le modèle que nous avons affiné sur le *Hub* avec le *widget* d'inférence. Pour l'utiliser localement dans un `pipeline`, vous devez juste spécifier l'identifiant de modèle approprié : + +```py +from transformers import pipeline + +# Remplacez ceci par votre propre checkpoint +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}] +``` + +Super ! Notre modèle fonctionne aussi bien que le modèle par défaut pour ce pipeline ! diff --git a/chapters/fr/chapter7/4.mdx b/chapters/fr/chapter7/4.mdx index cfc6af14e..48d83a6da 100644 --- a/chapters/fr/chapter7/4.mdx +++ b/chapters/fr/chapter7/4.mdx @@ -1,999 +1,999 @@ - - -# Traduction - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -Plongeons maintenant dans la traduction. Il s'agit d'une autre [tâche de séquence à séquence](/cours/fr/chapitre1/7), ce qui signifie que c'est un problème qui peut être formulé comme le passage d'une séquence à une autre. En ce sens, le problème est assez proche de la tâche de [résumé](/cours/fr/chapitre7/6) et vous pouvez adapter ce que nous allons voir ici à d'autres problèmes de séquence à séquence tels que : - -- le **transfert de style** : créer un modèle qui *traduit* des textes écrits dans un certain style vers un autre (par exemple, du formel au décontracté ou de l'anglais shakespearien à l'anglais moderne). -- la **génération de réponse à des questions** : Création d'un modèle qui génère des réponses à des questions, compte tenu d'un contexte. - - - -Si vous disposez d'un corpus suffisamment important de textes en deux langues (ou plus), vous pouvez entraîner un nouveau modèle de traduction à partir de zéro, comme nous le ferons dans la section sur la [modélisation causale du langage](/cours/fr/chapitre7/6). Il est toutefois plus rapide de *finetuner* un modèle de traduction existant, qu'il s'agisse d'un modèle multilingue comme mT5 ou mBART que vous souhaitez adapter à une paire de langues spécifique, ou même d'un modèle spécialisé dans la traduction d'une langue vers une autre que vous souhaitez adapter à votre corpus spécifique. - -Dans cette section, nous allons *finetuner* un modèle Marian pré-entraîné pour traduire de l'anglais au français (puisque de nombreux employés de Hugging Face parlent ces deux langues) sur le [KDE4 dataset](https://huggingface.co/datasets/kde4), qui est un jeu de données de fichiers localisés pour les [KDE apps](https://apps.kde.org/). Le modèle que nous utiliserons a été pré-entraîné sur un large corpus de textes français et anglais provenant du [jeu de données Opus](https://opus.nlpl.eu/), qui contient en fait le jeu de données KDE4. Mais même si le modèle pré-entraîné que nous utilisons a vu ces données pendant son pré-entraînement, nous verrons que nous pouvons obtenir une meilleure version de ce modèle après un *finetuning*. - -Une fois que nous aurons terminé, nous aurons un modèle capable de faire des prédictions comme celle-ci : - - - - - -One-hot encoded labels for question answering. - - - -Comme dans les sections précédentes, vous pouvez trouver le modèle réel que nous allons entraîner et télécharger sur le *Hub* en utilisant le code ci-dessous et vérifier ses prédictions [ici](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr?text=This+plugin+allows+you+to+automatically+translate+web+pages+between+several+languages.). - -## Préparation des données - -Pour affiner ou entraîner un modèle de traduction à partir de zéro, nous avons besoin d'un jeu de données adapté à cette tâche. Comme mentionné précédemment, nous utiliserons le jeu de données [KDE4](https://huggingface.co/datasets/kde4) dans cette section, mais vous pouvez adapter le code pour utiliser vos propres données assez facilement, tant que vous avez des paires de phrases dans les deux langues que vous voulez traduire de et vers. Reportez-vous au [Chapitre 5](/course/fr/chapter5) si vous avez besoin d'un rappel sur la façon de charger vos données personnalisées dans un `Dataset`. - -### Le jeu de données KDE4 - -Comme d'habitude, nous téléchargeons notre jeu de données en utilisant la fonction `load_dataset()` : - -```py -from datasets import load_dataset, load_metric - -raw_datasets = load_dataset("kde4", lang1="en", lang2="fr") -``` - -Si vous souhaitez travailler avec une autre paire de langues, vous pouvez les spécifier par leurs codes. Au total, 92 langues sont disponibles pour cet ensemble de données ; vous pouvez les voir toutes en développant les étiquettes de langue sur sa [fiche](https://huggingface.co/datasets/kde4). - -Language available for the KDE4 dataset. - -Jetons un coup d'œil au jeu de données : - -```py -raw_datasets -``` - -```python out -DatasetDict({ - train: Dataset({ - features: ['id', 'translation'], - num_rows: 210173 - }) -}) -``` - -Nous avons 210 173 paires de phrases, mais dans un seul split, donc nous devrons créer notre propre ensemble de validation. Comme nous l'avons vu dans le [Chapitre 5](/course/fr/chapter5), un `Dataset` possède une méthode `train_test_split()` qui peut nous aider. Nous allons fournir une graine pour la reproductibilité : - -```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 - }) -}) -``` - -Nous pouvons renommer la clé "test" en "validation" comme ceci : - -```py -split_datasets["validation"] = split_datasets.pop("test") -``` - -Examinons maintenant un élément de ce jeu de données : - -```py -split_datasets["train"][1]["translation"] -``` - -```python out -{'en': 'Default to expanded threads', - 'fr': 'Par défaut, développer les fils de discussion'} -``` - -Nous obtenons un dictionnaire contenant deux phrases dans la paire de langues demandée. -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 par le plus correct "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 la plus facile de laisser le mot tel quel : - -```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'}] -``` - -Un autre exemple de ce comportement peut être observé avec le mot "*plugin*", qui n'est pas officiellement un mot français mais que la plupart des locuteurs natifs comprendront et ne prendront pas la peine de traduire. -Dans le jeu de données KDE4, ce mot a été traduit en français par le plus officiel "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."} -``` - -Notre modèle pré-entraîné, cependant, s'en tient au mot anglais compact et familier : - -```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."}] -``` - -Il sera intéressant de voir si notre modèle *finetuné* tient compte de ces particularités de l'ensemble de données (alerte *spoiler* : il le fera). - - - - - -✏️ **Votre tour !** Un autre mot anglais souvent utilisé en français est "email". Trouvez le premier échantillon dans l'ensemble de données d'entraînement qui utilise ce mot. Comment est-il traduit ? Comment le modèle pré-entraîné traduit-il la même phrase anglaise ? - - - -### Traitement des données - - - -Vous devriez maintenant connaître le principe : les textes doivent tous être convertis en ensembles d'ID de *tokens* pour que le modèle puisse leur donner un sens. Pour cette tâche, nous aurons besoin de tokeniser les entrées et les cibles. Notre première tâche est de créer notre objet `tokenizer`. Comme indiqué précédemment, nous utiliserons un modèle pré-entraîné Marian English to French. Si vous essayez ce code avec une autre paire de langues, assurez-vous d'adapter le *checkpoint* du modèle. L'organisation [Helsinki-NLP](https://huggingface.co/Helsinki-NLP) fournit plus de mille modèles dans plusieurs langues. - -```python -from transformers import AutoTokenizer - -model_checkpoint = "Helsinki-NLP/opus-mt-en-fr" -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, return_tensors="tf") -``` - -Vous pouvez également remplacer le `model_checkpoint` par tout autre modèle que vous préférez à partir du [*Hub*](https://huggingface.co/models), ou un dossier local où vous avez sauvegardé un modèle pré-entraîné et un *tokenizer*. - - - -💡 Si vous utilisez un *tokenizer* multilingue tel que mBART, mBART-50, ou M2M100, vous devrez définir les codes de langue de vos entrées et cibles dans le *tokenizer* en définissant `tokenizer.src_lang` et `tokenizer.tgt_lang` aux bonnes valeurs. - - - -La préparation de nos données est assez simple. Il y a juste une chose à retenir : vous traitez les entrées comme d'habitude, mais pour les cibles, vous devez envelopper le *tokenizer* dans le gestionnaire de contexte `as_target_tokenizer()`. - -Un gestionnaire de contexte en Python est introduit avec l'instruction `with` et est utile lorsque vous avez deux opérations liées à exécuter en paire. L'exemple le plus courant est lorsque vous écrivez ou lisez un fichier, ce qui est souvent fait dans une instruction comme : - -``` -with open(file_path) as f: - content = f.read() -``` - -Ici, les deux opérations connexes qui sont exécutées en paire sont les actions d'ouverture et de fermeture du fichier. L'objet correspondant au fichier ouvert `f` n'existe qu'à l'intérieur du bloc indenté sous le `with` ; l'ouverture se produit avant ce bloc et la fermeture à la fin du bloc. - -Dans le cas présent, le gestionnaire de contexte `as_target_tokenizer()` va définir le *tokenizer* dans la langue de sortie (ici, le français) avant l'exécution du bloc indenté, puis le redéfinir dans la langue d'entrée (ici, l'anglais). - -Ainsi, le prétraitement d'un échantillon ressemble à ceci : - -```python -en_sentence = split_datasets["train"][1]["translation"]["en"] -fr_sentence = split_datasets["train"][1]["translation"]["fr"] - -inputs = tokenizer(en_sentence) -with tokenizer.as_target_tokenizer(): - targets = tokenizer(fr_sentence) -``` - -Si nous oublions de tokeniser les cibles dans le gestionnaire de contexte, elles seront tokenisées par le *tokenizer* d'entrée, ce qui, dans le cas d'un modèle marial, ne va pas du tout bien se passer : - -```python -wrong_targets = tokenizer(fr_sentence) -print(tokenizer.convert_ids_to_tokens(wrong_targets["input_ids"])) -print(tokenizer.convert_ids_to_tokens(targets["input_ids"])) -``` - -```python out -['▁Par', '▁dé', 'f', 'aut', ',', '▁dé', 've', 'lop', 'per', '▁les', '▁fil', 's', '▁de', '▁discussion', ''] -['▁Par', '▁défaut', ',', '▁développer', '▁les', '▁fils', '▁de', '▁discussion', ''] -``` - -Comme on peut le voir, utiliser le *tokenizer* anglais pour prétraiter une phrase française donne un batch de *tokens* plus important, puisque le *tokenizer* ne connaît aucun mot français (sauf ceux qui apparaissent aussi en anglais, comme "discussion"). - -Les `inputs` et les `targets` sont des dictionnaires avec nos clés habituelles (identifiants d'entrée, masque d'attention, etc.), donc la dernière étape est de définir une clé `"labels"` dans les entrées. Nous faisons cela dans la fonction de prétraitement que nous allons appliquer sur les jeux de données : - -```python -max_input_length = 128 -max_target_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, max_length=max_input_length, truncation=True) - - # Set up the tokenizer for targets - with tokenizer.as_target_tokenizer(): - labels = tokenizer(targets, max_length=max_target_length, truncation=True) - - model_inputs["labels"] = labels["input_ids"] - return model_inputs -``` - -Notez que nous avons fixé des longueurs maximales similaires pour nos entrées et nos sorties. Comme les textes que nous traitons semblent assez courts, nous utilisons 128. - - - -💡 Si vous utilisez un modèle T5 (plus précisément, un des points de contrôle `t5-xxx`), le modèle s'attendra à ce que les entrées de texte aient un préfixe indiquant la tâche à accomplir, comme Si vous utilisez un modèle T5 (plus précisément, un des points de contrôle `t5-xxx`), le modèle s'attendra à ce que les entrées de texte aient un préfixe indiquant la tâche à accomplir, comme `translate : Anglais vers Français:`.. - - - - - -⚠️ Nous ne faisons pas attention au masque d'attention des cibles, car le modèle ne s'y attend pas. Au lieu de cela, les étiquettes correspondant à un *token* de *padding* doivent être mises à `-100` afin qu'elles soient ignorées dans le calcul de la perte. Cela sera fait par notre collateur de données plus tard puisque nous appliquons le *padding* dynamique, mais si vous utilisez le *padding* ici, vous devriez adapter la fonction de prétraitement pour mettre tous les labels qui correspondent au *token* de *padding* à `-100`. - - - -Nous pouvons maintenant appliquer ce prétraitement en une seule fois sur toutes les divisions de notre jeu de données : - -```py -tokenized_datasets = split_datasets.map( - preprocess_function, - batched=True, - remove_columns=split_datasets["train"].column_names, -) -``` - -Maintenant que les données ont été prétraitées, nous sommes prêts à *finetuner* notre modèle pré-entraîné ! - -{#if fw === 'pt'} - -## *Finetuner* le modèle avec l'API `Trainer`. - -Le code actuel utilisant le `Trainer` sera le même que précédemment, avec juste un petit changement : nous utilisons ici un [`Seq2SeqTrainer`](https://huggingface.co/transformers/main_classes/trainer.html#seq2seqtrainer), qui est une sous-classe de `Trainer` qui nous permettra de traiter correctement l'évaluation, en utilisant la méthode `generate()` pour prédire les sorties à partir des entrées. Nous y reviendrons plus en détail lorsque nous parlerons du calcul de la métrique. - -Tout d'abord, nous avons besoin d'un modèle réel à affiner. Nous allons utiliser l'API habituelle `AutoModel` : - -```py -from transformers import AutoModelForSeq2SeqLM - -model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) -``` - -{:else} - -## *Finetuning* du modèle avec Keras - -Tout d'abord, nous avons besoin d'un modèle réel à *finetuner*. Nous allons utiliser l'API habituelle `AutoModel` : - -```py -from transformers import TFAutoModelForSeq2SeqLM - -model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint, from_pt=True) -``` - - - -💡 Le *checkpoint* `Helsinki-NLP/opus-mt-en-fr` ne dispose que de poids PyTorch, donc vous aurez une erreur si vous essayez de charger le modèle sans utiliser l'argument -`from_pt=True` dans la méthode `from_pretrained()`. Lorsque vous spécifiez `from_pt=True`, la bibliothèque téléchargera et convertira automatiquement les poids PyTorch pour vous. Comme vous pouvez le constater, il est très simple de passer d'un framework à l'autre dans 🤗 *Transformers* ! - - - -{/if} - -Notez que cette fois-ci, nous utilisons un modèle qui a été entraîné sur une tâche de traduction et qui peut déjà être utilisé, donc il n'y a pas d'avertissement concernant les poids manquants ou ceux nouvellement initialisés. - -### Collecte des données - -Nous aurons besoin d'un assembleur de données pour gérer le rembourrage pour la mise en lots dynamique. Nous ne pouvons pas simplement utiliser un `DataCollatorWithPadding` comme dans [Chapter 3](/course/fr/chapter3) dans ce cas, parce que cela ne rembourre que les entrées (ID d'entrée, masque d'attention, et ID de type de jeton). Nos étiquettes doivent également être rembourrées à la longueur maximale rencontrée dans les étiquettes. Et, comme mentionné précédemment, la valeur de remplissage utilisée pour remplir les étiquettes doit être `-100` et non le jeton de remplissage du *tokenizer*, pour s'assurer que ces valeurs remplies sont ignorées dans le calcul de la perte. - -Tout ceci est réalisé par un [`DataCollatorForSeq2Seq`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorforseq2seq). Comme le `DataCollatorWithPadding`, il prend le `tokenizer` utilisé pour prétraiter les entrées, mais il prend aussi le `model`. C'est parce que ce collateur de données sera également responsable de la préparation des ID d'entrée du décodeur, qui sont des versions décalées des étiquettes avec un jeton spécial au début. Comme ce décalage est effectué de manière légèrement différente selon les architectures, le `DataCollatorForSeq2Seq` a besoin de connaître l'objet `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} - -Pour le tester sur quelques échantillons, nous l'appelons simplement sur une liste d'exemples de notre ensemble d'entrainement tokénisé : - -```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']) -``` - -Nous pouvons vérifier que nos étiquettes ont été paddées à la longueur maximale du lot, en utilisant `-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]]) -``` - -Et nous pouvons également jeter un coup d'œil aux ID d'entrée du décodeur, pour voir qu'il s'agit de versions décalées des étiquettes : - -```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]]) -``` - -Voici les étiquettes des premier et deuxième éléments de notre jeu de données : - -```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'} - -Nous allons transmettre ce `data_collator` au `Seq2SeqTrainer`. Ensuite, jetons un coup d'oeil à la métrique. - -{:else} - -Nous pouvons maintenant utiliser ce `data_collator` pour convertir chacun de nos jeux de données en un `tf.data.Dataset`, prêt pour l'entraînement : - -```python -tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=True, - batch_size=32, -) -tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=False, - batch_size=16, -) -``` - -{/if} - - -### Métriques - - - -{#if fw === 'pt'} - -La fonctionnalité que `Seq2SeqTrainer` ajoute à sa superclasse `Trainer` est la possibilité d'utiliser la méthode `generate()` pendant l'évaluation ou la prédiction. Pendant l'entraînement, le modèle utilisera les `decoder_input_ids` avec un masque d'attention assurant qu'il n'utilise pas les *tokens* après le *token* qu'il essaie de prédire, pour accélérer l'entraînement. Pendant l'inférence, nous ne pourrons pas les utiliser puisque nous n'aurons pas d'étiquettes, donc c'est une bonne idée d'évaluer notre modèle avec la même configuration. - -Comme nous l'avons vu dans le [Chapitre 1](/course/fr/chapter1/6), le décodeur effectue l'inférence en prédisant les *tokens* un par un. Quelque chose qui est implémenté en coulisses dans les 🤗 Transformers par la méthode `generate()`. Le `Seq2SeqTrainer` nous laissera utiliser cette méthode pour l'évaluation si nous définissons `predict_with_generate=True`. - -{/if} - -La métrique traditionnelle utilisée pour la traduction est le [score BLEU](https://en.wikipedia.org/wiki/BLEU), introduit dans [un article de 2002](https://aclanthology.org/P02-1040.pdf) par Kishore Papineni et al. Le score BLEU évalue dans quelle mesure les traductions sont proches de leurs étiquettes. Il ne mesure pas l'intelligibilité ou l'exactitude grammaticale des résultats générés par le modèle, mais utilise des règles statistiques pour garantir que tous les mots des résultats générés apparaissent également dans les cibles. En outre, il existe des règles qui pénalisent les répétitions des mêmes mots s'ils ne sont pas également répétés dans les cibles (pour éviter que le modèle ne produise des phrases telles que "the the the the the the the") et les phrases produites qui sont plus courtes que celles des cibles (pour éviter que le modèle ne produise des phrases telles que "the"). - -L'une des faiblesses de BLEU est qu'il s'attend à ce que le texte soit déjà tokenisé, ce qui rend difficile la comparaison des scores entre les modèles qui utilisent différents *tokenizers*. Par conséquent, la mesure la plus couramment utilisée aujourd'hui pour évaluer les modèles de traduction est [SacreBLEU](https://github.com/mjpost/sacrebleu), qui remédie à cette faiblesse (et à d'autres) en standardisant l'étape de tokenisation. Pour utiliser cette métrique, nous devons d'abord installer la bibliothèque SacreBLEU : - -```py -!pip install sacrebleu -``` - -Nous pouvons ensuite le charger via `load_metric()` comme nous l'avons fait dans le [Chapitre 3](/course/fr/chapter3) : - -```py -from datasets import load_metric - -metric = load_metric("sacrebleu") -``` - -Cette métrique prend des textes comme entrées et cibles. Elle est conçue pour accepter plusieurs cibles acceptables, car il y a souvent plusieurs traductions acceptables de la même phrase. Le jeu de données que nous utilisons n'en fournit qu'une seule, mais il n'est pas rare en NLP de trouver des jeux de données qui donnent plusieurs phrases comme étiquettes. Ainsi, les prédictions doivent être une liste de phrases, mais les références doivent être une liste de listes de phrases. - -Essayons un exemple : - -```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} -``` - -Cela donne un score BLEU de 46.75, ce qui est plutôt bon. Pour référence, le Transformer original dans l'article ["Attention Is All You Need"](https://arxiv.org/pdf/1706.03762.pdf) a obtenu un score BLEU de 41.8 sur une tâche de traduction similaire entre l'anglais et le français ! (Pour plus d'informations sur les métriques individuelles, comme `counts` et `bp`, voir le [Dépôt SacreBLEU](https://github.com/mjpost/sacrebleu/blob/078c440168c6adc89ba75fe6d63f0d922d42bcfe/sacrebleu/metrics/bleu.py#L74).) D'autre part, si nous essayons avec les deux mauvais types de prédictions (batchs de répétitions ou trop courts) qui sortent souvent des modèles de traduction, nous obtiendrons des scores BLEU plutôt mauvais : - -```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} -``` - -Le score peut aller de 0 à 100, et plus il est élevé, mieux c'est. - -{#if fw === 'tf'} - -Pour passer des sorties du modèle aux textes que la métrique peut utiliser, nous allons utiliser la méthode `tokenizer.batch_decode()`. Nous devons juste nettoyer tous les `-100` dans les étiquettes ; le *tokenizer* fera automatiquement la même chose pour le *token* de *padding*. Définissons une fonction qui prend notre modèle et un jeu de données et calcule des métriques sur ceux-ci. Comme la génération de longues séquences peut être lente, nous sous-échantillonnons l'ensemble de validation pour nous assurer que cela ne prend pas une éternité : - -```py -import numpy as np - - -def compute_metrics(): - all_preds = [] - all_labels = [] - sampled_dataset = tokenized_datasets["validation"].shuffle().select(range(200)) - tf_generate_dataset = sampled_dataset.to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=False, - batch_size=4, - ) - for batch in tf_generate_dataset: - predictions = model.generate( - input_ids=batch["input_ids"], attention_mask=batch["attention_mask"] - ) - decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) - labels = batch["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} - -Pour passer des sorties du modèle aux textes utilisables par la métrique, nous allons utiliser la méthode `tokenizer.batch_decode()`. Nous devons juste nettoyer tous les `-100`s dans les étiquettes (le tokenizer fera automatiquement la même chose pour le token de remplissage) : - -```py -import numpy as np - - -def compute_metrics(eval_preds): - preds, labels = eval_preds - # In case the model returns more than the prediction logits - if isinstance(preds, tuple): - preds = preds[0] - - decoded_preds = tokenizer.batch_decode(preds, skip_special_tokens=True) - - # Replace -100s in the labels as we can't decode them - labels = np.where(labels != -100, labels, tokenizer.pad_token_id) - decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) - - # Some simple post-processing - 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} - -Maintenant que c'est fait, nous sommes prêts à affiner notre modèle ! - - -### *Finetuner* le modèle - -La première étape consiste à se connecter à Hugging Face, afin de pouvoir télécharger vos résultats sur le *Hub*. Il y a une fonction pratique pour vous aider à le faire dans un *notebook* : - -```python -from huggingface_hub import notebook_login - -notebook_login() -``` - -Cela affichera un widget où vous pourrez entrer vos identifiants de connexion à Hugging Face. - -Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : - -```bash -huggingface-cli login -``` - -{#if fw === 'tf'} - -Avant de commencer, voyons quel type de résultats nous obtenons avec notre modèle sans entraînement : - -```py -print(compute_metrics()) -``` - -``` -{'bleu': 33.26983701454733} -``` - -Une fois ceci fait, nous pouvons préparer tout ce dont nous avons besoin pour compiler et entraîner notre modèle. Notez l'utilisation de `tf.keras.mixed_precision.set_global_policy("mixed_float16")`. Ceci indiquera à Keras de s'entraîner en utilisant float16, ce qui peut donner un gain de vitesse significatif sur les GPUs qui le supportent (Nvidia 20xx/V100 ou plus récent). - -```python -from transformers import create_optimizer -from transformers.keras_callbacks import PushToHubCallback -import tensorflow as tf - -# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans l'ensemble de données, divisé par la taille du lot, puis multiplié par le nombre total d'époques. -# par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un lot tf.data.Dataset, -# et non le jeu de données original Hugging Face Dataset, donc son len() est déjà 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) - -# Entraîner en mixed-precision float16 -tf.keras.mixed_precision.set_global_policy("mixed_float16") -``` - -Ensuite, nous définissons un `PushToHubCallback` pour télécharger notre modèle sur le *Hub* pendant l'entraînement, comme nous l'avons vu dans [section 2]((/course/fr/chapter7/2)), et ensuite nous ajustons simplement le modèle avec ce callback : - -```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, -) -``` - -Notez que vous pouvez spécifier le nom du référentiel vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"``Seq2SeqTrainingArguments`. Par défaut, le référentiel utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, donc ici ce sera `"sgugger/marian-finetuned-kde4-en-to-fr"` (qui est le modèle que nous avons lié au début de cette section). - - - -💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de l'appel de `model.fit()` et devrez définir un nouveau nom. - - - -Enfin, voyons à quoi ressemblent nos mesures maintenant que l'entraînement est terminé : - -```py -print(compute_metrics()) -``` - -``` -{'bleu': 57.334066271545865} -``` - -À ce stade, vous pouvez utiliser le widget d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à *finetuner* un modèle sur une tâche de traduction. Félicitations ! - -{:else} - -Une fois ceci fait, nous pouvons définir notre `Seq2SeqTrainingArguments`. Comme pour le `Trainer`, nous utilisons une sous-classe de `TrainingArguments` qui contient quelques champs supplémentaires : - -```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, -) -``` - -En dehors des hyperparamètres habituels (comme le taux d'apprentissage, le nombre d'époques, la taille du lot et une certaine décroissance des poids), voici quelques changements par rapport à ce que nous avons vu dans les sections précédentes : - -- nous ne définissons pas d'évaluation régulière, car l'évaluation prend du temps ; nous allons juste évaluer notre modèle une fois avant l'entraînement et après, -- nous avons mis `fp16=True`, ce qui accélère l'entraînement sur les GPUs modernes, -- nous définissons `predict_with_generate=True`, comme discuté ci-dessus, -- nous utilisons `push_to_hub=True` pour télécharger le modèle sur le *Hub* à la fin de chaque époque. - -Notez que vous pouvez spécifier le nom complet du référentiel vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"` `Seq2SeqTrainingArguments`. Par défaut, le référentiel utilisé sera dans votre espace de noms et nommé d'après le répertoire de sortie que vous avez défini, donc dans notre cas ce sera `"sgugger/marian-finetuned-kde4-en-to-fr"` (qui est le modèle que nous avons lié au début de cette section). - - - -💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de la définition de votre `Seq2SeqTrainer` et devrez définir un nouveau nom. - - - - -Enfin, nous passons tout au `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, -) -``` - -Avant d'entraîner, nous allons d'abord regarder le score obtenu par notre modèle, pour vérifier que nous n'aggravons pas les choses avec notre *finetuning*. Cette commande va prendre un peu de temps, vous pouvez donc prendre un café pendant qu'elle s'exécute : - -```python -trainer.evaluate(max_length=max_target_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} -``` - -A BLEU score of 39 is not too bad, which reflects the fact that our model is already good at translating English sentences to French ones. - -Next is the training, which will also take a bit of time: - -```python -trainer.train() -``` - -Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. De cette façon, vous serez en mesure de reprendre votre entraînement sur une autre machine si nécessaire. - -Une fois l'entraînement terminé, nous évaluons à nouveau notre modèle - avec un peu de chance, nous verrons une amélioration du score BLEU ! - -```py -trainer.evaluate(max_length=max_target_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} -``` - -C'est une amélioration de près de 14 points, ce qui est formidable. - -Enfin, nous utilisons la méthode `push_to_hub()` pour nous assurer que nous téléchargeons la dernière version du modèle. Le `Trainer` rédige également une carte modèle avec tous les résultats de l'évaluation et la télécharge. Cette carte de modèle contient des métadonnées qui aident le *Hub* à choisir le widget pour la démo d'inférence. Habituellement, il n'y a pas besoin de dire quoi que ce soit car il peut inférer le bon *widget* à partir de la classe du modèle, mais dans ce cas, la même classe de modèle peut être utilisée pour toutes sortes de problèmes de séquence à séquence, donc nous spécifions que c'est un modèle de traduction : - -```py -trainer.push_to_hub(tags="translation", commit_message="Training complete") -``` - -Cette commande renvoie l'URL du commit qu'elle vient de faire, si vous voulez l'inspecter : - -```python out -'https://huggingface.co/sgugger/marian-finetuned-kde4-en-to-fr/commit/3601d621e3baae2bc63d3311452535f8f58f6ef3' -``` - -À ce stade, vous pouvez utiliser le widget d'inférence sur le Hub du modèle pour tester votre modèle et le partager avec vos amis. Vous avez réussi à *finetuner* un modèle sur une tâche de traduction. Félicitations ! - -Si vous souhaitez vous plonger un peu plus profondément dans la boucle d'entraînement, nous allons maintenant vous montrer comment faire la même chose en utilisant 🤗 *Accelerate*. - -{/if} - -{#if fw === 'pt'} - -## Une boucle d'entraînement personnalisée - -Jetons maintenant un coup d'œil à la boucle d'entraînement complète, afin que vous puissiez facilement personnaliser les parties dont vous avez besoin. Elle ressemblera beaucoup à ce que nous avons fait dans la [section 2](/course/fr/chapter7/2) et le [chapitre 3](/course/fr/chapter3/4). - -### Préparer le tout pour l'entraînement - -Vous avez vu tout cela plusieurs fois maintenant, donc nous allons passer en revue le code assez rapidement. D'abord, nous allons construire le `DataLoader` à partir de nos jeux de données, après avoir configuré les jeux de données au format `"torch"` pour obtenir les tenseurs 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 -) -``` - -Ensuite, nous réinstantifions notre modèle, pour nous assurer que nous ne poursuivons pas l'affinage précédent, mais que nous repartons du modèle pré-entraîné : - -```py -model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) -``` - -Nous aurons alors besoin d'un optimiseur : - -```py -from transformers import AdamW - -optimizer = AdamW(model.parameters(), lr=2e-5) -``` - -Une fois que nous avons tous ces objets, nous pouvons les envoyer à la méthode `accelerator.prepare()`. Rappelez-vous que si vous voulez vous entraîner sur des TPUs dans un *notebook* de Colab, vous devrez déplacer tout ce code dans une fonction d'entraînement, et qui ne devrait pas exécuter une cellule qui instancie un `Accelerator`. - -```py -from accelerate import Accelerator - -accelerator = Accelerator() -model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( - model, optimizer, train_dataloader, eval_dataloader -) -``` - -Maintenant que nous avons envoyé notre `train_dataloader` à `accelerator.prepare()`, nous pouvons utiliser sa longueur pour calculer le nombre d'étapes d'entraînement. Rappelez-vous que nous devrions toujours faire cela après avoir préparé le dataloader, car cette méthode va changer la longueur du `DataLoader`. Nous utilisons un programme linéaire classique du taux d'apprentissage à 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, -) -``` - -Enfin, pour pousser notre modèle vers le Hub, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous au *Hub*, si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'ID du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix ; il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `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' -``` - -Ensuite, nous pouvons cloner ce référentiel dans un dossier local. S'il existe déjà, ce dossier local doit être un clone du référentiel avec lequel nous travaillons : - -```py -output_dir = "marian-finetuned-kde4-en-to-fr-accelerate" -repo = Repository(output_dir, clone_from=repo_name) -``` - -Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output_dir` en appelant la méthode `repo.push_to_hub()`. Cela nous aidera à télécharger les modèles intermédiaires à la fin de chaque époque. - -### Boucle d'entraînement - -Nous sommes maintenant prêts à écrire la boucle d'entraînement complète. Pour simplifier sa partie évaluation, nous définissons cette fonction `postprocess()` qui prend les prédictions et les étiquettes et les convertit en listes de chaînes de caractères que notre objet `metric` attend : - -```py -def postprocess(predictions, labels): - predictions = predictions.cpu().numpy() - labels = labels.cpu().numpy() - - decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) - - # Remplacer -100 dans les étiquettes car nous ne pouvons pas les décoder. - labels = np.where(labels != -100, labels, tokenizer.pad_token_id) - decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) - - # Quelques post-traitements simples - decoded_preds = [pred.strip() for pred in decoded_preds] - decoded_labels = [[label.strip()] for label in decoded_labels] - return decoded_preds, decoded_labels -``` - -La boucle d'entraînement ressemble beaucoup à celles de [section 2](/course/fr/chapter7/2) et [chapitre 3](/course/fr/chapter3), avec quelques différences dans la partie évaluation -- alors concentrons-nous sur cela ! - -La première chose à noter est que nous utilisons la méthode `generate()` pour calculer les prédictions, mais c'est une méthode sur notre modèle de base, pas le modèle enveloppé 🤗 Accelerate créé dans la méthode `prepare()`. C'est pourquoi nous déballons d'abord le modèle, puis nous appelons cette méthode. - -La deuxième chose est que, comme avec [token classification](/course/fr/chapter7/2), deux processus peuvent avoir paddé les entrées et les étiquettes à des formes différentes, donc nous utilisons `accelerator.pad_across_processes()` pour rendre les prédictions et les étiquettes de la même forme avant d'appeler la méthode `gather()`. Si nous ne faisons pas cela, l'évaluation va soit se tromper, soit se bloquer pour toujours. - -```py -from tqdm.auto import tqdm -import torch - -progress_bar = tqdm(range(num_training_steps)) - -for epoch in range(num_train_epochs): - # Entraînement - 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) - - # Evaluation - 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"] - - # Nécessaire pour rembourrer les prédictions et les étiquettes à rassembler - 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}") - - # Sauvegarder et télécharger - 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 -``` - -Une fois que c'est fait, vous devriez avoir un modèle qui a des résultats assez similaires à celui entraîné avec le `Seq2SeqTrainer`. Vous pouvez vérifier celui que nous avons formé en utilisant ce code à [*huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate*](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate). Et si vous voulez tester des modifications de la boucle d'entraînement, vous pouvez les mettre en œuvre directement en modifiant le code ci-dessus ! - -{/if} - -### Utilisation du modèle *finetuné*. - -Nous vous avons déjà montré comment vous pouvez utiliser le modèle que nous avons *finetuné* sur le *Hub* avec le *widget* d'inférence. Pour l'utiliser localement dans un `pipeline`, nous devons juste spécifier l'identifiant de modèle approprié : - -```py -from transformers import pipeline - -# Remplacez ceci par votre propre checkpoint -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'}] -``` - -Comme prévu, notre modèle pré-entraîné a adapté ses connaissances au corpus sur lequel nous l'avons affiné, et au lieu de laisser le mot anglais "threads" seul, il le traduit maintenant par la version officielle française. Il en va de même pour "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."}] -``` - -Un autre excellent exemple d'adaptation au domaine ! - - - -✏️ **Votre tour !** Que retourne le modèle sur l'échantillon avec le mot "email" que vous avez identifié plus tôt ? - - + + +# Traduction + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Plongeons maintenant dans la traduction. Il s'agit d'une autre [tâche de séquence à séquence](/cours/fr/chapitre1/7), ce qui signifie que c'est un problème qui peut être formulé comme le passage d'une séquence à une autre. En ce sens, le problème est assez proche de la tâche de [résumé](/cours/fr/chapitre7/6) et vous pouvez adapter ce que nous allons voir ici à d'autres problèmes de séquence à séquence tels que : + +- le **transfert de style** : créer un modèle qui *traduit* des textes écrits dans un certain style vers un autre (par exemple, du formel au décontracté ou de l'anglais shakespearien à l'anglais moderne). +- la **génération de réponse à des questions** : Création d'un modèle qui génère des réponses à des questions, compte tenu d'un contexte. + + + +Si vous disposez d'un corpus suffisamment important de textes en deux langues (ou plus), vous pouvez entraîner un nouveau modèle de traduction à partir de zéro, comme nous le ferons dans la section sur la [modélisation causale du langage](/cours/fr/chapitre7/6). Il est toutefois plus rapide de *finetuner* un modèle de traduction existant, qu'il s'agisse d'un modèle multilingue comme mT5 ou mBART que vous souhaitez adapter à une paire de langues spécifique, ou même d'un modèle spécialisé dans la traduction d'une langue vers une autre que vous souhaitez adapter à votre corpus spécifique. + +Dans cette section, nous allons *finetuner* un modèle Marian pré-entraîné pour traduire de l'anglais au français (puisque de nombreux employés de Hugging Face parlent ces deux langues) sur le [KDE4 dataset](https://huggingface.co/datasets/kde4), qui est un jeu de données de fichiers localisés pour les [KDE apps](https://apps.kde.org/). Le modèle que nous utiliserons a été pré-entraîné sur un large corpus de textes français et anglais provenant du [jeu de données Opus](https://opus.nlpl.eu/), qui contient en fait le jeu de données KDE4. Mais même si le modèle pré-entraîné que nous utilisons a vu ces données pendant son pré-entraînement, nous verrons que nous pouvons obtenir une meilleure version de ce modèle après un *finetuning*. + +Une fois que nous aurons terminé, nous aurons un modèle capable de faire des prédictions comme celle-ci : + + + + + +One-hot encoded labels for question answering. + + + +Comme dans les sections précédentes, vous pouvez trouver le modèle réel que nous allons entraîner et télécharger sur le *Hub* en utilisant le code ci-dessous et vérifier ses prédictions [ici](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr?text=This+plugin+allows+you+to+automatically+translate+web+pages+between+several+languages.). + +## Préparation des données + +Pour affiner ou entraîner un modèle de traduction à partir de zéro, nous avons besoin d'un jeu de données adapté à cette tâche. Comme mentionné précédemment, nous utiliserons le jeu de données [KDE4](https://huggingface.co/datasets/kde4) dans cette section, mais vous pouvez adapter le code pour utiliser vos propres données assez facilement, tant que vous avez des paires de phrases dans les deux langues que vous voulez traduire de et vers. Reportez-vous au [Chapitre 5](/course/fr/chapter5) si vous avez besoin d'un rappel sur la façon de charger vos données personnalisées dans un `Dataset`. + +### Le jeu de données KDE4 + +Comme d'habitude, nous téléchargeons notre jeu de données en utilisant la fonction `load_dataset()` : + +```py +from datasets import load_dataset, load_metric + +raw_datasets = load_dataset("kde4", lang1="en", lang2="fr") +``` + +Si vous souhaitez travailler avec une autre paire de langues, vous pouvez les spécifier par leurs codes. Au total, 92 langues sont disponibles pour cet ensemble de données ; vous pouvez les voir toutes en développant les étiquettes de langue sur sa [fiche](https://huggingface.co/datasets/kde4). + +Language available for the KDE4 dataset. + +Jetons un coup d'œil au jeu de données : + +```py +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['id', 'translation'], + num_rows: 210173 + }) +}) +``` + +Nous avons 210 173 paires de phrases, mais dans un seul split, donc nous devrons créer notre propre ensemble de validation. Comme nous l'avons vu dans le [Chapitre 5](/course/fr/chapter5), un `Dataset` possède une méthode `train_test_split()` qui peut nous aider. Nous allons fournir une graine pour la reproductibilité : + +```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 + }) +}) +``` + +Nous pouvons renommer la clé "test" en "validation" comme ceci : + +```py +split_datasets["validation"] = split_datasets.pop("test") +``` + +Examinons maintenant un élément de ce jeu de données : + +```py +split_datasets["train"][1]["translation"] +``` + +```python out +{'en': 'Default to expanded threads', + 'fr': 'Par défaut, développer les fils de discussion'} +``` + +Nous obtenons un dictionnaire contenant deux phrases dans la paire de langues demandée. +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 par le plus correct "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 la plus facile de laisser le mot tel quel : + +```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'}] +``` + +Un autre exemple de ce comportement peut être observé avec le mot "*plugin*", qui n'est pas officiellement un mot français mais que la plupart des locuteurs natifs comprendront et ne prendront pas la peine de traduire. +Dans le jeu de données KDE4, ce mot a été traduit en français par le plus officiel "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."} +``` + +Notre modèle pré-entraîné, cependant, s'en tient au mot anglais compact et familier : + +```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."}] +``` + +Il sera intéressant de voir si notre modèle *finetuné* tient compte de ces particularités de l'ensemble de données (alerte *spoiler* : il le fera). + + + + + +✏️ **Votre tour !** Un autre mot anglais souvent utilisé en français est "email". Trouvez le premier échantillon dans l'ensemble de données d'entraînement qui utilise ce mot. Comment est-il traduit ? Comment le modèle pré-entraîné traduit-il la même phrase anglaise ? + + + +### Traitement des données + + + +Vous devriez maintenant connaître le principe : les textes doivent tous être convertis en ensembles d'ID de *tokens* pour que le modèle puisse leur donner un sens. Pour cette tâche, nous aurons besoin de tokeniser les entrées et les cibles. Notre première tâche est de créer notre objet `tokenizer`. Comme indiqué précédemment, nous utiliserons un modèle pré-entraîné Marian English to French. Si vous essayez ce code avec une autre paire de langues, assurez-vous d'adapter le *checkpoint* du modèle. L'organisation [Helsinki-NLP](https://huggingface.co/Helsinki-NLP) fournit plus de mille modèles dans plusieurs langues. + +```python +from transformers import AutoTokenizer + +model_checkpoint = "Helsinki-NLP/opus-mt-en-fr" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, return_tensors="tf") +``` + +Vous pouvez également remplacer le `model_checkpoint` par tout autre modèle que vous préférez à partir du [*Hub*](https://huggingface.co/models), ou un dossier local où vous avez sauvegardé un modèle pré-entraîné et un *tokenizer*. + + + +💡 Si vous utilisez un *tokenizer* multilingue tel que mBART, mBART-50, ou M2M100, vous devrez définir les codes de langue de vos entrées et cibles dans le *tokenizer* en définissant `tokenizer.src_lang` et `tokenizer.tgt_lang` aux bonnes valeurs. + + + +La préparation de nos données est assez simple. Il y a juste une chose à retenir : vous traitez les entrées comme d'habitude, mais pour les cibles, vous devez envelopper le *tokenizer* dans le gestionnaire de contexte `as_target_tokenizer()`. + +Un gestionnaire de contexte en Python est introduit avec l'instruction `with` et est utile lorsque vous avez deux opérations liées à exécuter en paire. L'exemple le plus courant est lorsque vous écrivez ou lisez un fichier, ce qui est souvent fait dans une instruction comme : + +``` +with open(file_path) as f: + content = f.read() +``` + +Ici, les deux opérations connexes qui sont exécutées en paire sont les actions d'ouverture et de fermeture du fichier. L'objet correspondant au fichier ouvert `f` n'existe qu'à l'intérieur du bloc indenté sous le `with` ; l'ouverture se produit avant ce bloc et la fermeture à la fin du bloc. + +Dans le cas présent, le gestionnaire de contexte `as_target_tokenizer()` va définir le *tokenizer* dans la langue de sortie (ici, le français) avant l'exécution du bloc indenté, puis le redéfinir dans la langue d'entrée (ici, l'anglais). + +Ainsi, le prétraitement d'un échantillon ressemble à ceci : + +```python +en_sentence = split_datasets["train"][1]["translation"]["en"] +fr_sentence = split_datasets["train"][1]["translation"]["fr"] + +inputs = tokenizer(en_sentence) +with tokenizer.as_target_tokenizer(): + targets = tokenizer(fr_sentence) +``` + +Si nous oublions de tokeniser les cibles dans le gestionnaire de contexte, elles seront tokenisées par le *tokenizer* d'entrée, ce qui, dans le cas d'un modèle marial, ne va pas du tout bien se passer : + +```python +wrong_targets = tokenizer(fr_sentence) +print(tokenizer.convert_ids_to_tokens(wrong_targets["input_ids"])) +print(tokenizer.convert_ids_to_tokens(targets["input_ids"])) +``` + +```python out +['▁Par', '▁dé', 'f', 'aut', ',', '▁dé', 've', 'lop', 'per', '▁les', '▁fil', 's', '▁de', '▁discussion', ''] +['▁Par', '▁défaut', ',', '▁développer', '▁les', '▁fils', '▁de', '▁discussion', ''] +``` + +Comme on peut le voir, utiliser le *tokenizer* anglais pour prétraiter une phrase française donne un batch de *tokens* plus important, puisque le *tokenizer* ne connaît aucun mot français (sauf ceux qui apparaissent aussi en anglais, comme "discussion"). + +Les `inputs` et les `targets` sont des dictionnaires avec nos clés habituelles (identifiants d'entrée, masque d'attention, etc.), donc la dernière étape est de définir une clé `"labels"` dans les entrées. Nous faisons cela dans la fonction de prétraitement que nous allons appliquer sur les jeux de données : + +```python +max_input_length = 128 +max_target_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, max_length=max_input_length, truncation=True) + + # Set up the tokenizer for targets + with tokenizer.as_target_tokenizer(): + labels = tokenizer(targets, max_length=max_target_length, truncation=True) + + model_inputs["labels"] = labels["input_ids"] + return model_inputs +``` + +Notez que nous avons fixé des longueurs maximales similaires pour nos entrées et nos sorties. Comme les textes que nous traitons semblent assez courts, nous utilisons 128. + + + +💡 Si vous utilisez un modèle T5 (plus précisément, un des points de contrôle `t5-xxx`), le modèle s'attendra à ce que les entrées de texte aient un préfixe indiquant la tâche à accomplir, comme Si vous utilisez un modèle T5 (plus précisément, un des points de contrôle `t5-xxx`), le modèle s'attendra à ce que les entrées de texte aient un préfixe indiquant la tâche à accomplir, comme `translate : Anglais vers Français:`.. + + + + + +⚠️ Nous ne faisons pas attention au masque d'attention des cibles, car le modèle ne s'y attend pas. Au lieu de cela, les étiquettes correspondant à un *token* de *padding* doivent être mises à `-100` afin qu'elles soient ignorées dans le calcul de la perte. Cela sera fait par notre collateur de données plus tard puisque nous appliquons le *padding* dynamique, mais si vous utilisez le *padding* ici, vous devriez adapter la fonction de prétraitement pour mettre tous les labels qui correspondent au *token* de *padding* à `-100`. + + + +Nous pouvons maintenant appliquer ce prétraitement en une seule fois sur toutes les divisions de notre jeu de données : + +```py +tokenized_datasets = split_datasets.map( + preprocess_function, + batched=True, + remove_columns=split_datasets["train"].column_names, +) +``` + +Maintenant que les données ont été prétraitées, nous sommes prêts à *finetuner* notre modèle pré-entraîné ! + +{#if fw === 'pt'} + +## *Finetuner* le modèle avec l'API `Trainer`. + +Le code actuel utilisant le `Trainer` sera le même que précédemment, avec juste un petit changement : nous utilisons ici un [`Seq2SeqTrainer`](https://huggingface.co/transformers/main_classes/trainer.html#seq2seqtrainer), qui est une sous-classe de `Trainer` qui nous permettra de traiter correctement l'évaluation, en utilisant la méthode `generate()` pour prédire les sorties à partir des entrées. Nous y reviendrons plus en détail lorsque nous parlerons du calcul de la métrique. + +Tout d'abord, nous avons besoin d'un modèle réel à affiner. Nous allons utiliser l'API habituelle `AutoModel` : + +```py +from transformers import AutoModelForSeq2SeqLM + +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{:else} + +## *Finetuning* du modèle avec Keras + +Tout d'abord, nous avons besoin d'un modèle réel à *finetuner*. Nous allons utiliser l'API habituelle `AutoModel` : + +```py +from transformers import TFAutoModelForSeq2SeqLM + +model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint, from_pt=True) +``` + + + +💡 Le *checkpoint* `Helsinki-NLP/opus-mt-en-fr` ne dispose que de poids PyTorch, donc vous aurez une erreur si vous essayez de charger le modèle sans utiliser l'argument +`from_pt=True` dans la méthode `from_pretrained()`. Lorsque vous spécifiez `from_pt=True`, la bibliothèque téléchargera et convertira automatiquement les poids PyTorch pour vous. Comme vous pouvez le constater, il est très simple de passer d'un framework à l'autre dans 🤗 *Transformers* ! + + + +{/if} + +Notez que cette fois-ci, nous utilisons un modèle qui a été entraîné sur une tâche de traduction et qui peut déjà être utilisé, donc il n'y a pas d'avertissement concernant les poids manquants ou ceux nouvellement initialisés. + +### Collecte des données + +Nous aurons besoin d'un assembleur de données pour gérer le rembourrage pour la mise en lots dynamique. Nous ne pouvons pas simplement utiliser un `DataCollatorWithPadding` comme dans [Chapter 3](/course/fr/chapter3) dans ce cas, parce que cela ne rembourre que les entrées (ID d'entrée, masque d'attention, et ID de type de jeton). Nos étiquettes doivent également être rembourrées à la longueur maximale rencontrée dans les étiquettes. Et, comme mentionné précédemment, la valeur de remplissage utilisée pour remplir les étiquettes doit être `-100` et non le jeton de remplissage du *tokenizer*, pour s'assurer que ces valeurs remplies sont ignorées dans le calcul de la perte. + +Tout ceci est réalisé par un [`DataCollatorForSeq2Seq`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorforseq2seq). Comme le `DataCollatorWithPadding`, il prend le `tokenizer` utilisé pour prétraiter les entrées, mais il prend aussi le `model`. C'est parce que ce collateur de données sera également responsable de la préparation des ID d'entrée du décodeur, qui sont des versions décalées des étiquettes avec un jeton spécial au début. Comme ce décalage est effectué de manière légèrement différente selon les architectures, le `DataCollatorForSeq2Seq` a besoin de connaître l'objet `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} + +Pour le tester sur quelques échantillons, nous l'appelons simplement sur une liste d'exemples de notre ensemble d'entrainement tokénisé : + +```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']) +``` + +Nous pouvons vérifier que nos étiquettes ont été paddées à la longueur maximale du lot, en utilisant `-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]]) +``` + +Et nous pouvons également jeter un coup d'œil aux ID d'entrée du décodeur, pour voir qu'il s'agit de versions décalées des étiquettes : + +```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]]) +``` + +Voici les étiquettes des premier et deuxième éléments de notre jeu de données : + +```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'} + +Nous allons transmettre ce `data_collator` au `Seq2SeqTrainer`. Ensuite, jetons un coup d'oeil à la métrique. + +{:else} + +Nous pouvons maintenant utiliser ce `data_collator` pour convertir chacun de nos jeux de données en un `tf.data.Dataset`, prêt pour l'entraînement : + +```python +tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) +tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=16, +) +``` + +{/if} + + +### Métriques + + + +{#if fw === 'pt'} + +La fonctionnalité que `Seq2SeqTrainer` ajoute à sa superclasse `Trainer` est la possibilité d'utiliser la méthode `generate()` pendant l'évaluation ou la prédiction. Pendant l'entraînement, le modèle utilisera les `decoder_input_ids` avec un masque d'attention assurant qu'il n'utilise pas les *tokens* après le *token* qu'il essaie de prédire, pour accélérer l'entraînement. Pendant l'inférence, nous ne pourrons pas les utiliser puisque nous n'aurons pas d'étiquettes, donc c'est une bonne idée d'évaluer notre modèle avec la même configuration. + +Comme nous l'avons vu dans le [Chapitre 1](/course/fr/chapter1/6), le décodeur effectue l'inférence en prédisant les *tokens* un par un. Quelque chose qui est implémenté en coulisses dans les 🤗 Transformers par la méthode `generate()`. Le `Seq2SeqTrainer` nous laissera utiliser cette méthode pour l'évaluation si nous définissons `predict_with_generate=True`. + +{/if} + +La métrique traditionnelle utilisée pour la traduction est le [score BLEU](https://en.wikipedia.org/wiki/BLEU), introduit dans [un article de 2002](https://aclanthology.org/P02-1040.pdf) par Kishore Papineni et al. Le score BLEU évalue dans quelle mesure les traductions sont proches de leurs étiquettes. Il ne mesure pas l'intelligibilité ou l'exactitude grammaticale des résultats générés par le modèle, mais utilise des règles statistiques pour garantir que tous les mots des résultats générés apparaissent également dans les cibles. En outre, il existe des règles qui pénalisent les répétitions des mêmes mots s'ils ne sont pas également répétés dans les cibles (pour éviter que le modèle ne produise des phrases telles que "the the the the the the the") et les phrases produites qui sont plus courtes que celles des cibles (pour éviter que le modèle ne produise des phrases telles que "the"). + +L'une des faiblesses de BLEU est qu'il s'attend à ce que le texte soit déjà tokenisé, ce qui rend difficile la comparaison des scores entre les modèles qui utilisent différents *tokenizers*. Par conséquent, la mesure la plus couramment utilisée aujourd'hui pour évaluer les modèles de traduction est [SacreBLEU](https://github.com/mjpost/sacrebleu), qui remédie à cette faiblesse (et à d'autres) en standardisant l'étape de tokenisation. Pour utiliser cette métrique, nous devons d'abord installer la bibliothèque SacreBLEU : + +```py +!pip install sacrebleu +``` + +Nous pouvons ensuite le charger via `load_metric()` comme nous l'avons fait dans le [Chapitre 3](/course/fr/chapter3) : + +```py +from datasets import load_metric + +metric = load_metric("sacrebleu") +``` + +Cette métrique prend des textes comme entrées et cibles. Elle est conçue pour accepter plusieurs cibles acceptables, car il y a souvent plusieurs traductions acceptables de la même phrase. Le jeu de données que nous utilisons n'en fournit qu'une seule, mais il n'est pas rare en NLP de trouver des jeux de données qui donnent plusieurs phrases comme étiquettes. Ainsi, les prédictions doivent être une liste de phrases, mais les références doivent être une liste de listes de phrases. + +Essayons un exemple : + +```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} +``` + +Cela donne un score BLEU de 46.75, ce qui est plutôt bon. Pour référence, le Transformer original dans l'article ["Attention Is All You Need"](https://arxiv.org/pdf/1706.03762.pdf) a obtenu un score BLEU de 41.8 sur une tâche de traduction similaire entre l'anglais et le français ! (Pour plus d'informations sur les métriques individuelles, comme `counts` et `bp`, voir le [Dépôt SacreBLEU](https://github.com/mjpost/sacrebleu/blob/078c440168c6adc89ba75fe6d63f0d922d42bcfe/sacrebleu/metrics/bleu.py#L74).) D'autre part, si nous essayons avec les deux mauvais types de prédictions (batchs de répétitions ou trop courts) qui sortent souvent des modèles de traduction, nous obtiendrons des scores BLEU plutôt mauvais : + +```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} +``` + +Le score peut aller de 0 à 100, et plus il est élevé, mieux c'est. + +{#if fw === 'tf'} + +Pour passer des sorties du modèle aux textes que la métrique peut utiliser, nous allons utiliser la méthode `tokenizer.batch_decode()`. Nous devons juste nettoyer tous les `-100` dans les étiquettes ; le *tokenizer* fera automatiquement la même chose pour le *token* de *padding*. Définissons une fonction qui prend notre modèle et un jeu de données et calcule des métriques sur ceux-ci. Comme la génération de longues séquences peut être lente, nous sous-échantillonnons l'ensemble de validation pour nous assurer que cela ne prend pas une éternité : + +```py +import numpy as np + + +def compute_metrics(): + all_preds = [] + all_labels = [] + sampled_dataset = tokenized_datasets["validation"].shuffle().select(range(200)) + tf_generate_dataset = sampled_dataset.to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=4, + ) + for batch in tf_generate_dataset: + predictions = model.generate( + input_ids=batch["input_ids"], attention_mask=batch["attention_mask"] + ) + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + labels = batch["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} + +Pour passer des sorties du modèle aux textes utilisables par la métrique, nous allons utiliser la méthode `tokenizer.batch_decode()`. Nous devons juste nettoyer tous les `-100`s dans les étiquettes (le tokenizer fera automatiquement la même chose pour le token de remplissage) : + +```py +import numpy as np + + +def compute_metrics(eval_preds): + preds, labels = eval_preds + # In case the model returns more than the prediction logits + if isinstance(preds, tuple): + preds = preds[0] + + decoded_preds = tokenizer.batch_decode(preds, skip_special_tokens=True) + + # Replace -100s in the labels as we can't decode them + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + # Some simple post-processing + 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} + +Maintenant que c'est fait, nous sommes prêts à affiner notre modèle ! + + +### *Finetuner* le modèle + +La première étape consiste à se connecter à Hugging Face, afin de pouvoir télécharger vos résultats sur le *Hub*. Il y a une fonction pratique pour vous aider à le faire dans un *notebook* : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Cela affichera un widget où vous pourrez entrer vos identifiants de connexion à Hugging Face. + +Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : + +```bash +huggingface-cli login +``` + +{#if fw === 'tf'} + +Avant de commencer, voyons quel type de résultats nous obtenons avec notre modèle sans entraînement : + +```py +print(compute_metrics()) +``` + +``` +{'bleu': 33.26983701454733} +``` + +Une fois ceci fait, nous pouvons préparer tout ce dont nous avons besoin pour compiler et entraîner notre modèle. Notez l'utilisation de `tf.keras.mixed_precision.set_global_policy("mixed_float16")`. Ceci indiquera à Keras de s'entraîner en utilisant float16, ce qui peut donner un gain de vitesse significatif sur les GPUs qui le supportent (Nvidia 20xx/V100 ou plus récent). + +```python +from transformers import create_optimizer +from transformers.keras_callbacks import PushToHubCallback +import tensorflow as tf + +# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans l'ensemble de données, divisé par la taille du lot, puis multiplié par le nombre total d'époques. +# par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un lot tf.data.Dataset, +# et non le jeu de données original Hugging Face Dataset, donc son len() est déjà 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) + +# Entraîner en mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +Ensuite, nous définissons un `PushToHubCallback` pour télécharger notre modèle sur le *Hub* pendant l'entraînement, comme nous l'avons vu dans [section 2]((/course/fr/chapter7/2)), et ensuite nous ajustons simplement le modèle avec ce callback : + +```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, +) +``` + +Notez que vous pouvez spécifier le nom du référentiel vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"``Seq2SeqTrainingArguments`. Par défaut, le référentiel utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, donc ici ce sera `"sgugger/marian-finetuned-kde4-en-to-fr"` (qui est le modèle que nous avons lié au début de cette section). + + + +💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de l'appel de `model.fit()` et devrez définir un nouveau nom. + + + +Enfin, voyons à quoi ressemblent nos mesures maintenant que l'entraînement est terminé : + +```py +print(compute_metrics()) +``` + +``` +{'bleu': 57.334066271545865} +``` + +À ce stade, vous pouvez utiliser le widget d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à *finetuner* un modèle sur une tâche de traduction. Félicitations ! + +{:else} + +Une fois ceci fait, nous pouvons définir notre `Seq2SeqTrainingArguments`. Comme pour le `Trainer`, nous utilisons une sous-classe de `TrainingArguments` qui contient quelques champs supplémentaires : + +```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, +) +``` + +En dehors des hyperparamètres habituels (comme le taux d'apprentissage, le nombre d'époques, la taille du lot et une certaine décroissance des poids), voici quelques changements par rapport à ce que nous avons vu dans les sections précédentes : + +- nous ne définissons pas d'évaluation régulière, car l'évaluation prend du temps ; nous allons juste évaluer notre modèle une fois avant l'entraînement et après, +- nous avons mis `fp16=True`, ce qui accélère l'entraînement sur les GPUs modernes, +- nous définissons `predict_with_generate=True`, comme discuté ci-dessus, +- nous utilisons `push_to_hub=True` pour télécharger le modèle sur le *Hub* à la fin de chaque époque. + +Notez que vous pouvez spécifier le nom complet du référentiel vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"` `Seq2SeqTrainingArguments`. Par défaut, le référentiel utilisé sera dans votre espace de noms et nommé d'après le répertoire de sortie que vous avez défini, donc dans notre cas ce sera `"sgugger/marian-finetuned-kde4-en-to-fr"` (qui est le modèle que nous avons lié au début de cette section). + + + +💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de la définition de votre `Seq2SeqTrainer` et devrez définir un nouveau nom. + + + + +Enfin, nous passons tout au `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, +) +``` + +Avant d'entraîner, nous allons d'abord regarder le score obtenu par notre modèle, pour vérifier que nous n'aggravons pas les choses avec notre *finetuning*. Cette commande va prendre un peu de temps, vous pouvez donc prendre un café pendant qu'elle s'exécute : + +```python +trainer.evaluate(max_length=max_target_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} +``` + +A BLEU score of 39 is not too bad, which reflects the fact that our model is already good at translating English sentences to French ones. + +Next is the training, which will also take a bit of time: + +```python +trainer.train() +``` + +Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. De cette façon, vous serez en mesure de reprendre votre entraînement sur une autre machine si nécessaire. + +Une fois l'entraînement terminé, nous évaluons à nouveau notre modèle - avec un peu de chance, nous verrons une amélioration du score BLEU ! + +```py +trainer.evaluate(max_length=max_target_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} +``` + +C'est une amélioration de près de 14 points, ce qui est formidable. + +Enfin, nous utilisons la méthode `push_to_hub()` pour nous assurer que nous téléchargeons la dernière version du modèle. Le `Trainer` rédige également une carte modèle avec tous les résultats de l'évaluation et la télécharge. Cette carte de modèle contient des métadonnées qui aident le *Hub* à choisir le widget pour la démo d'inférence. Habituellement, il n'y a pas besoin de dire quoi que ce soit car il peut inférer le bon *widget* à partir de la classe du modèle, mais dans ce cas, la même classe de modèle peut être utilisée pour toutes sortes de problèmes de séquence à séquence, donc nous spécifions que c'est un modèle de traduction : + +```py +trainer.push_to_hub(tags="translation", commit_message="Training complete") +``` + +Cette commande renvoie l'URL du commit qu'elle vient de faire, si vous voulez l'inspecter : + +```python out +'https://huggingface.co/sgugger/marian-finetuned-kde4-en-to-fr/commit/3601d621e3baae2bc63d3311452535f8f58f6ef3' +``` + +À ce stade, vous pouvez utiliser le widget d'inférence sur le Hub du modèle pour tester votre modèle et le partager avec vos amis. Vous avez réussi à *finetuner* un modèle sur une tâche de traduction. Félicitations ! + +Si vous souhaitez vous plonger un peu plus profondément dans la boucle d'entraînement, nous allons maintenant vous montrer comment faire la même chose en utilisant 🤗 *Accelerate*. + +{/if} + +{#if fw === 'pt'} + +## Une boucle d'entraînement personnalisée + +Jetons maintenant un coup d'œil à la boucle d'entraînement complète, afin que vous puissiez facilement personnaliser les parties dont vous avez besoin. Elle ressemblera beaucoup à ce que nous avons fait dans la [section 2](/course/fr/chapter7/2) et le [chapitre 3](/course/fr/chapter3/4). + +### Préparer le tout pour l'entraînement + +Vous avez vu tout cela plusieurs fois maintenant, donc nous allons passer en revue le code assez rapidement. D'abord, nous allons construire le `DataLoader` à partir de nos jeux de données, après avoir configuré les jeux de données au format `"torch"` pour obtenir les tenseurs 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 +) +``` + +Ensuite, nous réinstantifions notre modèle, pour nous assurer que nous ne poursuivons pas l'affinage précédent, mais que nous repartons du modèle pré-entraîné : + +```py +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +Nous aurons alors besoin d'un optimiseur : + +```py +from transformers import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Une fois que nous avons tous ces objets, nous pouvons les envoyer à la méthode `accelerator.prepare()`. Rappelez-vous que si vous voulez vous entraîner sur des TPUs dans un *notebook* de Colab, vous devrez déplacer tout ce code dans une fonction d'entraînement, et qui ne devrait pas exécuter une cellule qui instancie un `Accelerator`. + +```py +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + +Maintenant que nous avons envoyé notre `train_dataloader` à `accelerator.prepare()`, nous pouvons utiliser sa longueur pour calculer le nombre d'étapes d'entraînement. Rappelez-vous que nous devrions toujours faire cela après avoir préparé le dataloader, car cette méthode va changer la longueur du `DataLoader`. Nous utilisons un programme linéaire classique du taux d'apprentissage à 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, +) +``` + +Enfin, pour pousser notre modèle vers le Hub, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous au *Hub*, si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'ID du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix ; il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `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' +``` + +Ensuite, nous pouvons cloner ce référentiel dans un dossier local. S'il existe déjà, ce dossier local doit être un clone du référentiel avec lequel nous travaillons : + +```py +output_dir = "marian-finetuned-kde4-en-to-fr-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output_dir` en appelant la méthode `repo.push_to_hub()`. Cela nous aidera à télécharger les modèles intermédiaires à la fin de chaque époque. + +### Boucle d'entraînement + +Nous sommes maintenant prêts à écrire la boucle d'entraînement complète. Pour simplifier sa partie évaluation, nous définissons cette fonction `postprocess()` qui prend les prédictions et les étiquettes et les convertit en listes de chaînes de caractères que notre objet `metric` attend : + +```py +def postprocess(predictions, labels): + predictions = predictions.cpu().numpy() + labels = labels.cpu().numpy() + + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + + # Remplacer -100 dans les étiquettes car nous ne pouvons pas les décoder. + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + # Quelques post-traitements simples + decoded_preds = [pred.strip() for pred in decoded_preds] + decoded_labels = [[label.strip()] for label in decoded_labels] + return decoded_preds, decoded_labels +``` + +La boucle d'entraînement ressemble beaucoup à celles de [section 2](/course/fr/chapter7/2) et [chapitre 3](/course/fr/chapter3), avec quelques différences dans la partie évaluation -- alors concentrons-nous sur cela ! + +La première chose à noter est que nous utilisons la méthode `generate()` pour calculer les prédictions, mais c'est une méthode sur notre modèle de base, pas le modèle enveloppé 🤗 Accelerate créé dans la méthode `prepare()`. C'est pourquoi nous déballons d'abord le modèle, puis nous appelons cette méthode. + +La deuxième chose est que, comme avec [token classification](/course/fr/chapter7/2), deux processus peuvent avoir paddé les entrées et les étiquettes à des formes différentes, donc nous utilisons `accelerator.pad_across_processes()` pour rendre les prédictions et les étiquettes de la même forme avant d'appeler la méthode `gather()`. Si nous ne faisons pas cela, l'évaluation va soit se tromper, soit se bloquer pour toujours. + +```py +from tqdm.auto import tqdm +import torch + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Entraînement + 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) + + # Evaluation + 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"] + + # Nécessaire pour rembourrer les prédictions et les étiquettes à rassembler + 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}") + + # Sauvegarder et télécharger + 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 +``` + +Une fois que c'est fait, vous devriez avoir un modèle qui a des résultats assez similaires à celui entraîné avec le `Seq2SeqTrainer`. Vous pouvez vérifier celui que nous avons formé en utilisant ce code à [*huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate*](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate). Et si vous voulez tester des modifications de la boucle d'entraînement, vous pouvez les mettre en œuvre directement en modifiant le code ci-dessus ! + +{/if} + +### Utilisation du modèle *finetuné*. + +Nous vous avons déjà montré comment vous pouvez utiliser le modèle que nous avons *finetuné* sur le *Hub* avec le *widget* d'inférence. Pour l'utiliser localement dans un `pipeline`, nous devons juste spécifier l'identifiant de modèle approprié : + +```py +from transformers import pipeline + +# Remplacez ceci par votre propre checkpoint +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'}] +``` + +Comme prévu, notre modèle pré-entraîné a adapté ses connaissances au corpus sur lequel nous l'avons affiné, et au lieu de laisser le mot anglais "threads" seul, il le traduit maintenant par la version officielle française. Il en va de même pour "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."}] +``` + +Un autre excellent exemple d'adaptation au domaine ! + + + +✏️ **Votre tour !** Que retourne le modèle sur l'échantillon avec le mot "email" que vous avez identifié plus tôt ? + + diff --git a/chapters/fr/chapter7/5.mdx b/chapters/fr/chapter7/5.mdx index cfac55b89..0ba896f6d 100644 --- a/chapters/fr/chapter7/5.mdx +++ b/chapters/fr/chapter7/5.mdx @@ -1,1065 +1,1065 @@ - - -# Résumé de textes - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - - -Dans cette section, nous allons voir comment les *transformers* peuvent être utilisés pour condenser de longs documents en résumés, une tâche connue sous le nom de _résumé de texte_. Il s'agit de l'une des tâches de NLP les plus difficiles car elle requiert une série de capacités, telles que la compréhension de longs passages et la génération d'un texte cohérent qui capture les sujets principaux d'un document. Cependant, lorsqu'il est bien fait, le résumé de texte est un outil puissant qui peut accélérer divers processus commerciaux en soulageant les experts du domaine de la lecture détaillée de longs documents. - - - -Bien qu'il existe déjà plusieurs modèles *finetunés* pour le résumé sur le [Hugging Face Hub](https://huggingface.co/models?pipeline_tag=summarization&sort=downloads), la plupart d'entre eux ne sont adaptés qu'aux documents en anglais. Ainsi, pour ajouter une touche d'originalité à cette section, nous allons entraîner un modèle bilingue pour l'anglais et l'espagnol. À la fin de cette section, vous disposerez d'un [modèle](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es) capable de résumer les commentaires des clients comme celui présenté ici : - - - - -Comme nous allons le voir, ces résumés sont concis car ils sont appris à partir des titres que les clients fournissent dans leurs commentaires sur les produits. Commençons par constituer un corpus bilingue approprié pour cette tâche. - -## Préparation d'un corpus multilingue - -Nous allons utiliser le [Multilingual Amazon Reviews Corpus](https://huggingface.co/datasets/amazon_reviews_multi) pour créer notre résumeur bilingue. Ce corpus est constitué d'évaluations de produits Amazon en six langues et est généralement utilisé pour évaluer les classificateurs multilingues. Cependant, comme chaque critique est accompagnée d'un titre court, nous pouvons utiliser les titres comme résumés cibles pour l'apprentissage de notre modèle ! Pour commencer, téléchargeons les sous-ensembles anglais et espagnols depuis le *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 - }) -}) -``` - -Comme vous pouvez le voir, pour chaque langue, il y a 200 000 évaluations pour la partie "entraînement", et 5 000 évaluations pour chacune des parties "validation" et "test". Les informations qui nous intéressent sont contenues dans les colonnes `review_body` et `review_title`. Voyons quelques exemples en créant une fonction simple qui prend un échantillon aléatoire de l'ensemble d'entraînement avec les techniques apprises au [Chapitre 5](/course/fr/chapter5) : - -```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' # Travaillé en position avant, pas arrière -'>> 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.' -# 3 étoiles car ce ne sont pas des freins arrière comme indiqué dans la description de l'article. Au moins, l'adaptateur de montage ne fonctionnait que sur la fourche avant du vélo pour lequel je l'ai acheté.'' - -'>> 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' -# Il fait son travail et il est magnifique mais le mien est en train de tomber en morceaux, j'ai dû le recoller avec de la colle chaude. - -'>> Title: Can\'t beat these for the money' # On ne peut pas faire mieux pour le prix -'>> 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.' -# Je l'ai acheté pour manipuler diverses pièces d'avion et des "trucs" de hangar que je devais organiser ; il a vraiment fait l'affaire. L'unité est arrivée rapidement, était bien emballée et est arrivée intacte (toujours un bon signe). Il y a cinq supports muraux - trois sur le dessus et deux sur le dessous. Je voulais le monter sur le mur, alors tout ce que j'ai eu à faire était d'enlever les deux couches supérieures de tiroirs en plastique, ainsi que les tiroirs d'angle inférieurs, de le placer où je voulais et de le marquer ; j'ai ensuite utilisé quelques-uns des nouveaux ancrages muraux à vis en plastique (la variété de 50 livres) et il s'est facilement monté sur le mur. Certains ont fait remarquer qu'ils voulaient des séparateurs pour les tiroirs, et qu'ils les ont fabriqués. Bonne idée. Pour ma part, j'avais besoin de quelque chose dont je pouvais voir le contenu à hauteur des yeux, et je voulais donc des tiroirs plus grands. J'aime aussi le fait qu'il s'agisse du nouveau plastique qui ne se fragilise pas et ne se fend pas comme mes anciens tiroirs en plastique. J'aime la construction entièrement en plastique. Elle est suffisamment résistante pour contenir des pièces métalliques, mais étant en plastique, elle n'est pas aussi lourde qu'un cadre métallique, ce qui permet de la fixer facilement au mur et de la charger d'objets lourds ou légers. Aucun problème. Pour le prix, c'est imbattable. C'est le meilleur que j'ai acheté à ce jour, et j'utilise des versions de ce type depuis plus de quarante ans. - -``` - - - -✏️ **Essayez !** Changez la graine aléatoire dans la commande `Dataset.shuffle()` pour explorer d'autres critiques dans le corpus. Si vous parlez espagnol, jetez un coup d'œil à certaines des critiques dans `spanish_dataset` pour voir si les titres semblent aussi être des résumés raisonnables. - - - -Cet échantillon montre la diversité des critiques que l'on trouve généralement en ligne, allant du positif au négatif (et tout ce qui se trouve entre les deux !). Bien que l'exemple avec le titre "meh" ne soit pas très informatif, les autres titres semblent être des résumés décents des critiques elles-mêmes. Entraîner un modèle de résumé sur l'ensemble des 400 000 avis prendrait beaucoup trop de temps sur un seul GPU, nous allons donc nous concentrer sur la génération de résumés pour un seul domaine de produits. Pour avoir une idée des domaines parmi lesquels nous pouvons choisir, convertissons `english_dataset` en `pandas.DataFrame` et calculons le nombre d'avis par catégorie de produits : - -```python -english_dataset.set_format("pandas") -english_df = english_dataset["train"][:] -# Afficher les comptes des 20 premiers produits -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 -``` - -Les produits les plus populaires dans l'ensemble de données anglaises concernent les articles ménagers, les vêtements et l'électronique sans fil. Pour rester dans le thème d'Amazon, nous allons nous concentrer sur le résumé des critiques de livres. Après tout, c'est la raison d'être de l'entreprise ! Nous pouvons voir deux catégories de produits qui correspondent à nos besoins (`book` et `digital_ebook_purchase`), nous allons donc filtrer les ensembles de données dans les deux langues pour ces produits uniquement. Comme nous l'avons vu dans le [Chapitre 5](/course/fr/chapter5), la fonction `Dataset.filter()` nous permet de découper un jeu de données de manière très efficace, nous pouvons donc définir une fonction simple pour le faire : - -```python -def filter_books(example): - return ( - example["product_category"] == "book" - or example["product_category"] == "digital_ebook_purchase" - ) -``` - -Maintenant, lorsque nous appliquons cette fonction à `english_dataset` et `spanish_dataset`, le résultat ne contiendra que les lignes impliquant les catégories de livres. Avant d'appliquer le filtre, changeons le format de `english_dataset` de `"pandas"` à `"arrow"` : - -```python -english_dataset.reset_format() -``` - -Nous pouvons ensuite appliquer la fonction de filtrage et, à titre de vérification, inspecter un échantillon de critiques pour voir si elles portent bien sur des livres : - -```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.' # Je suis déçu -'>> 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.' -# Je suppose que j'avais de plus grandes attentes pour ce livre d'après les critiques. Je pensais vraiment que j'allais au moins l'aimer. L'idée de l'intrigue était géniale. J'aimais Ash, mais ça n'allait nulle part. La plus grande partie du livre était consacrée à leur émission de radio et aux conversations avec les auditeurs. Je voulais que l'auteur creuse plus profondément pour que nous puissions vraiment connaître les personnages. Tout ce que nous savons de Grace, c'est qu'elle est séduisante, qu'elle est latino et qu'elle est une sorte de garce. Je suis déçue. - -'>> Title: Good art, good price, poor design' # Un bon art, un bon prix, un mauvais 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' -# J'ai eu le calendrier DC Vintage ces deux dernières années, mais il était en rupture de stock pour toujours cette année et j'ai vu qu'ils avaient réduit les dimensions sans raison valable. Celui-ci a de bons choix artistiques mais le design a le pli qui traverse l'image, donc c'est moins esthétique, surtout si vous voulez garder une image à accrocher. Pour le prix, c'est un bon calendrier. - -'>> Title: Helpful' Utile -'>> Review: Nearly all the tips useful and. I consider myself an intermediate to advanced user of OneNote. I would highly recommend.' -# Presque tous les conseils sont utiles et. Je me considère comme un utilisateur intermédiaire à avancé de OneNote. Je le recommande vivement. -``` - -D'accord, nous pouvons voir que les critiques ne concernent pas strictement les livres et peuvent se référer à des choses comme des calendriers et des applications électroniques telles que OneNote. Néanmoins, le domaine semble approprié pour entraîner un modèle de résumé. Avant de regarder les différents modèles qui conviennent à cette tâche, nous avons une dernière préparation de données à faire : combiner les critiques anglaises et espagnoles en un seul objet `DatasetDict`. 🤗 *Datasets* fournit une fonction pratique `concatenate_datasets()` qui (comme son nom l'indique) va empiler deux objets `Dataset` l'un sur l'autre. Ainsi, pour créer notre jeu de données bilingue, nous allons boucler sur chaque division, concaténer les jeux de données pour cette division, et mélanger le résultat pour s'assurer que notre modèle ne s'adapte pas trop à une seule langue : - -```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) - -# Quelques exemples -show_samples(books_dataset) -``` - -```python out -'>> Title: Easy to follow!!!!' # Facile à suivre!!!! -'>> 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.' -# J'ai adoré The dash diet weight loss Solution. Jamais faim. Je recommande ce régime. Les menus sont également bien arrondis. Essayez-le. Il contient beaucoup d'informations, merci. - -'>> Title: PARCIALMENTE DAÑADO' # PARTIELLEMENT ENDOMMAGÉ -'>> 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).' -# Il est arrivé le jour prévu, avec d'autres livres que j'avais commandés, mais la boîte est arrivée en mauvais état, ce qui a endommagé les coins des livres car ils étaient livrés sans protection (doublure). - -'>> Title: no lo he podido descargar' # Je n'ai pas pu le télécharger -'>> Review: igual que el anterior' # même chose que ci-dessus -``` - -Cela ressemble certainement à un mélange de critiques anglaises et espagnoles ! Maintenant que nous avons un corpus d'entraînement, une dernière chose à vérifier est la distribution des mots dans les critiques et leurs titres. Ceci est particulièrement important pour les tâches de résumé, où les résumés de référence courts dans les données peuvent biaiser le modèle pour qu'il ne produise qu'un ou deux mots dans les résumés générés. Les graphiques ci-dessous montrent les distributions de mots, et nous pouvons voir que les titres sont fortement biaisés vers seulement 1 ou 2 mots : - -
-Word count distributions for the review titles and texts. - -
- -Pour y remédier, nous allons filtrer les exemples avec des titres très courts afin que notre modèle puisse produire des résumés plus intéressants. Puisque nous avons affaire à des textes anglais et espagnols, nous pouvons utiliser une heuristique grossière pour séparer les titres sur les espaces blancs, puis utiliser notre fidèle méthode `Dataset.filter()` comme suit : - -```python -books_dataset = books_dataset.filter(lambda x: len(x["review_title"].split()) > 2) -``` - -Maintenant que nous avons préparé notre corpus, voyons quelques *transformers* possibles que l'on pourrait *finetuné* dessus ! - -## Modèles pour le résumé de texte - -Si vous y pensez, le résumé de texte est une tâche similaire à la traduction automatique : nous avons un corps de texte, comme une critique, que nous aimerions "traduire" en une version plus courte qui capture les caractéristiques saillantes de l'entrée. En conséquence, la plupart des modèles Transformer pour le résumé adoptent l'architecture encodeur-décodeur que nous avons rencontrée pour la première fois dans le [Chapitre 1](/course/fr/chapter1), bien qu'il y ait quelques exceptions comme la famille de modèles GPT qui peut également être utilisée pour le résumé dans des contextes peu complexes. Le tableau suivant présente quelques modèles pré-entraînés populaires qui peuvent être *finetunés* pour le résumé. - -| *Transformers* | Description | Multilingue ? | -| :---------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------: | -| [GPT-2](https://huggingface.co/gpt2-xl) | Bien qu'il soit entraîné comme un modèle de langage autorégressif, vous pouvez faire en sorte que GPT-2 génère des résumés en ajoutant "TL;DR" à la fin du texte d'entrée. | ❌ | -| [PEGASUS](https://huggingface.co/google/pegasus-large) | Utilise un objectif de pré-entraînement pour prédire les phrases masquées dans les textes à plusieurs phrases. Cet objectif de pré-entraînement est plus proche du résumé que de la modélisation du langage standard et obtient des scores élevés sur des benchmarks populaires. | ❌ | -| [T5](https://huggingface.co/t5-base) | Une architecture universelle de *transformer* qui formule toutes les tâches dans un cadre texte à texte ; par exemple, le format d'entrée du modèle pour résumer un document est `summarize : ARTICLE`. | ❌ | -| [mT5](https://huggingface.co/google/mt5-base) | Une version multilingue de T5, pré-entraînée sur le corpus multilingue Common Crawl (mC4), couvrant 101 langues. | ✅ | -| [BART](https://huggingface.co/facebook/bart-base) | Une architecture de *transformer* avec une pile d'encodeurs et de décodeurs entraînés pour reconstruire l'entrée corrompue qui combine les schémas de pré-entraînement de BERT et GPT-2. | ❌ | -| [mBART-50](https://huggingface.co/facebook/mbart-large-50) | Une version multilingue de BART, pré-entraînée sur 50 langues. | ✅ | - -Comme vous pouvez le voir dans ce tableau, la majorité des modèles Transformer pour le résumé (et en fait la plupart des tâches de NLP) sont monolingues. C'est une bonne chose si votre tâche se déroule dans une langue "à haute ressource" comme l'anglais ou l'allemand, mais moins pour les milliers d'autres langues utilisées dans le monde. Heureusement, il existe une catégorie de modèles Transformer multilingues, comme mT5 et mBART, qui viennent à la rescousse. Ces modèles sont pré-entraînés en utilisant la modélisation du langage, mais avec une particularité : au lieu de s'entraîner sur un corpus d'une seule langue, ils sont entraînés conjointement sur des textes dans plus de 50 langues à la fois ! - -Nous allons nous concentrer sur mT5, une architecture intéressante basée sur T5 qui a été pré-entraînée dans un cadre texte à texte. Dans T5, chaque tâche NLP est formulée en termes d'un préfixe d'invite comme `summarize:` qui conditionne le modèle à adapter le texte généré à l'invite. Comme le montre la figure ci-dessous, cela rend T5 extrêmement polyvalent, car vous pouvez résoudre de nombreuses tâches avec un seul modèle ! - -
-Different tasks performed by the T5 architecture. - -
- -mT5 n'utilise pas de préfixes, mais partage une grande partie de la polyvalence de T5 et a l'avantage d'être multilingue. Maintenant que nous avons choisi un modèle, voyons comment préparer nos données pour l'entraînement. - - - - -✏️ **Essayez** Une fois que vous avez travaillé sur cette section, voyez comment mT5 se compare à mBART en affinant ce dernier avec les mêmes techniques. Pour des points bonus, vous pouvez aussi essayer de *finetuner* le T5 uniquement sur les critiques anglaises. Puisque le T5 a un préfixe spécial, vous devrez ajouter `summarize:` aux exemples d'entrée dans les étapes de prétraitement ci-dessous. - - - -## Prétraitement des données - - - -Notre prochaine tâche est de tokeniser et d'encoder nos critiques et leurs titres. Comme d'habitude, nous commençons par charger le *tokenizer* associé au point de contrôle du modèle pré-entraîné. Nous utiliserons `mt5-small` comme point de contrôle afin de pouvoir affiner le modèle en un temps raisonnable : - -```python -from transformers import AutoTokenizer - -model_checkpoint = "google/mt5-small" -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) -``` - - - -💡 Aux premiers stades de vos projets de NLP, une bonne pratique consiste à entraîner une classe de "petits" modèles sur un petit échantillon de données. Cela vous permet de déboguer et d'itérer plus rapidement vers un flux de travail de bout en bout. Une fois que vous avez confiance dans les résultats, vous pouvez toujours faire évoluer le modèle en changeant simplement le point de contrôle du modèle ! - - - -Testons le *tokenizer* de mT5 sur un petit exemple : - -```python -inputs = tokenizer( - "I loved reading the Hunger Games!" -) # J'ai adoré lire les 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]} -``` - -Ici nous pouvons voir les familiers `input_ids` et `attention_mask` que nous avons rencontrés dans nos premières expériences de fine-tuning au [Chapitre 3](/course/fr/chapter3). Décodons ces identifiants d'entrée avec la fonction `convert_ids_to_tokens()` du tokenizer pour voir à quel type de tokenizer nous avons affaire : - -```python -tokenizer.convert_ids_to_tokens(inputs.input_ids) -``` - -```python out -['▁I', '▁', 'loved', '▁reading', '▁the', '▁Hung', 'er', '▁Games', ''] -``` - -Le caractère spécial Unicode `▁` et le *token* de fin de séquence `` indiquent que nous avons affaire au *tokenizer* de SentencePiece, qui est basé sur l'algorithme de segmentation Unigram discuté dans le [Chapitre 6](/course/chapter6). Unigram est particulièrement utile pour les corpus multilingues car il permet à SentencePiece d'être agnostique vis-à-vis des accents, de la ponctuation et du fait que de nombreuses langues, comme le japonais, n'ont pas de caractères d'espacement. - -Pour tokeniser notre corpus, nous devons faire face à une subtilité associée au résumé : comme nos étiquettes sont également du texte, il est possible qu'elles dépassent la taille maximale du contexte du modèle. Cela signifie que nous devons appliquer une troncature à la fois aux critiques et à leurs titres pour nous assurer de ne pas transmettre des entrées trop longues à notre modèle. Les tokenizers de 🤗 *Transformers* fournissent une fonction très pratique `as_target_tokenizer()` qui vous permet de tokeniser les étiquettes en parallèle avec les entrées. Ceci est typiquement fait en utilisant un gestionnaire de contexte à l'intérieur d'une fonction de prétraitement qui encode d'abord les entrées, et ensuite encode les étiquettes comme une colonne séparée. Voici un exemple d'une telle fonction pour 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 - ) - # Configurer le *tokenizer* pour les cibles. - with tokenizer.as_target_tokenizer(): - labels = tokenizer( - examples["review_title"], max_length=max_target_length, truncation=True - ) - - model_inputs["labels"] = labels["input_ids"] - return model_inputs -``` - -Parcourons ce code pour comprendre ce qui se passe. La première chose que nous avons faite est de définir des valeurs pour `max_input_length` et `max_target_length`, qui fixent les limites supérieures de la longueur des commentaires et des titres. Comme le corps de la critique est généralement beaucoup plus long que le titre, nous avons mis ces valeurs à l'échelle en conséquence. Ensuite, dans la `preprocess_function()` elle-même, nous pouvons voir que les commentaires sont d'abord tokenizés, suivis par les titres avec `as_target_tokenizer()`. - -Avec la fonction `preprocess_function()`, il est alors simple de tokeniser l'ensemble du corpus en utilisant la fonction pratique `Dataset.map()` que nous avons largement utilisée dans ce cours : - -```python -tokenized_datasets = books_dataset.map(preprocess_function, batched=True) -``` - -Maintenant que le corpus a été prétraité, examinons certaines métriques couramment utilisées pour le résumé. Comme nous allons le voir, il n'existe pas de solution miracle pour mesurer la qualité d'un texte généré par une machine. - - - -💡 Vous avez peut-être remarqué que nous avons utilisé `batched=True` dans notre fonction `Dataset.map()` ci-dessus. Cela permet de coder les exemples par lots de 1 000 (par défaut) et d'utiliser les capacités de multithreading des *tokenizers* rapides de 🤗 *Transformers*. Lorsque cela est possible, essayez d'utiliser `batched=True` pour tirer le meilleur parti de votre prétraitement ! - - - - -## Métriques pour le résumé de texte - - - -Par rapport à la plupart des autres tâches que nous avons abordées dans ce cours, la mesure des performances des tâches de génération de texte comme le résumé ou la traduction n'est pas aussi simple. Par exemple, pour une critique telle que "J'ai adoré lire les Hunger Games", il existe plusieurs résumés valides, comme "J'ai adoré Hunger Games" ou "Hunger Games est une excellente lecture". Il est clair que l'application d'une sorte de correspondance exacte entre le résumé généré et l'étiquette n'est pas une bonne solution - même les humains auraient de mauvais résultats avec une telle mesure, car nous avons tous notre propre style d'écriture. - -Pour le résumé, l'une des métriques les plus couramment utilisées est le [score ROUGE](https://en.wikipedia.org/wiki/ROUGE_(metric)) (abréviation de Recall-Oriented Understudy for Gisting Evaluation). L'idée de base de cette métrique est de comparer un résumé généré avec un ensemble de résumés de référence qui sont généralement créés par des humains. Pour être plus précis, supposons que nous voulions comparer les deux résumés suivants : - -```python -generated_summary = "I absolutely loved reading the Hunger Games" -reference_summary = "I loved reading the Hunger Games" -``` - -Une façon de les comparer pourrait être de compter le nombre de mots qui se chevauchent, qui dans ce cas serait de 6. Cependant, cette méthode est un peu grossière, c'est pourquoi ROUGE se base sur le calcul des scores de _précision_ et de _rappel_ pour le chevauchement. - - - -🙋 Ne vous inquiétez pas si c'est la première fois que vous entendez parler de précision et de rappel - nous allons parcourir ensemble quelques exemples explicites pour que tout soit clair. Ces métriques sont généralement rencontrées dans les tâches de classification, donc si vous voulez comprendre comment la précision et le rappel sont définis dans ce contexte, nous vous recommandons de consulter les [guides de `scikit-learn`](https://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html). - - - -Pour ROUGE, le rappel mesure la proportion du résumé de référence qui est capturée par le résumé généré. Si nous ne faisons que comparer des mots, le rappel peut être calculé selon la formule suivante : - -$$ \mathrm{Recall} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, reference\, summary}} $$ - -Pour notre exemple simple ci-dessus, cette formule donne un rappel parfait de 6/6 = 1 ; c'est-à-dire que tous les mots du résumé de référence ont été produits par le modèle. Cela peut sembler génial, mais imaginez que le résumé généré ait été "J'ai vraiment aimé lire les Hunger Games toute la nuit". Le rappel serait également parfait, mais le résumé serait sans doute moins bon puisqu'il serait verbeux. Pour traiter ces scénarios, nous calculons également la précision, qui, dans le contexte de ROUGE, mesure la proportion du résumé généré qui était pertinente : - -$$ \mathrm{Precision} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, generated\, summary}} $$ - -En appliquant cela à notre résumé verbeux, on obtient une précision de 6/10 = 0,6, ce qui est considérablement moins bon que la précision de 6/7 = 0,86 obtenue par notre résumé plus court. En pratique, la précision et le rappel sont généralement calculés, puis le score F1 (la moyenne harmonique de la précision et du rappel) est indiqué. Nous pouvons le faire facilement dans 🤗 *Datasets* en installant d'abord le paquet `rouge_score` : - -```py -!pip install rouge_score -``` - -et ensuite charger la métrique ROUGE comme suit : - -```python -from datasets import load_metric - -rouge_score = load_metric("rouge") -``` - -Ensuite, nous pouvons utiliser la fonction `rouge_score.compute()` pour calculer toutes les métriques en une seule fois : - -```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))} -``` - -Whoa, il y a un batch d'informations dans cette sortie. Qu'est-ce que ça veut dire ? Tout d'abord, 🤗 *Datasets* calcule en fait des intervalles de confiance pour la précision, le rappel et le score F1 ; ce sont les attributs `low`, `mid`, et `high` que vous pouvez voir ici. De plus, 🤗 *Datasets* calcule une variété de scores ROUGE qui sont basés sur différents types de granularité du texte lors de la comparaison des résumés générés et de référence. La variante `rouge1` est le chevauchement des unigrammes. C'est juste une façon fantaisiste de dire le chevauchement des mots et c'est exactement la métrique dont nous avons discuté ci-dessus. Pour vérifier cela, nous allons extraire la valeur "moyenne" de nos scores : - -```python -scores["rouge1"].mid -``` - -```python out -Score(precision=0.86, recall=1.0, fmeasure=0.92) -``` - -Super, les chiffres de précision et de rappel correspondent ! Maintenant, qu'en est-il des autres scores ROUGE ? `rouge2` mesure le chevauchement entre les bigrammes (pensez au chevauchement des paires de mots), tandis que `rougeL` et `rougeLsum` mesurent les plus longues séquences de mots correspondants en recherchant les plus longues sous-souches communes dans les résumés générés et de référence. La "somme" dans `rougeLsum` fait référence au fait que cette métrique est calculée sur un résumé entier, alors que `rougeL` est calculée comme une moyenne sur des phrases individuelles. - - - -✏️ **Essayez** Créez votre propre exemple de résumé généré et de référence et voyez si les scores ROUGE obtenus correspondent à un calcul manuel basé sur les formules de précision et de rappel. Pour des points bonus, divisez le texte en bigrammes et comparez la précision et le rappel pour la métrique `rouge2`. - - - -Nous utiliserons ces scores ROUGE pour suivre les performances de notre modèle, mais avant cela, faisons ce que tout bon praticien de NLP devrait faire : créer une base de référence solide, mais simple ! - -### Création d'une base de référence solide - -Une base de référence commune pour le résumé de texte consiste à prendre simplement les trois premières phrases d'un article, souvent appelée la base de référence _lead-3_. Nous pourrions utiliser des points pour suivre les limites de la phrase, mais cela échouera avec des acronymes comme "U.S." ou "U.N.". -- Nous allons donc utiliser la bibliothèque `nltk`, qui inclut un meilleur algorithme pour gérer ces cas. Vous pouvez installer le paquetage en utilisant `pip` comme suit : - -```python -!pip install nltk -``` - -puis téléchargez les règles de ponctuation : - -```python -import nltk - -nltk.download("punkt") -``` - -Ensuite, nous importons le *tokenizer* de `nltk` et créons une fonction simple pour extraire les trois premières phrases d'une critique. La convention dans le résumé de texte est de séparer chaque résumé avec une nouvelle ligne, donc nous allons également inclure ceci et le tester sur un exemple d'entraînement : - -```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.' # J'ai grandi en lisant Koontz, et il y a des années, j'ai arrêté, convaincu que je l'avais "dépassé" -'Still,when a friend was looking for something suspenseful too read, I suggested Koontz.' " Pourtant, quand une amie cherchait un livre à suspense, je lui ai suggéré Koontz. -'She found Strangers.' # Elle a trouvé Strangers. -``` - -Cela semble fonctionner, alors implémentons maintenant une fonction qui extrait ces "résumés" d'un ensemble de données et calcule les scores ROUGE pour la ligne de base : - -```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"]) -``` - -Nous pouvons ensuite utiliser cette fonction pour calculer les scores ROUGE sur l'ensemble de validation et les embellir un peu en utilisant 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} -``` - -Nous pouvons voir que le score de `rouge2` est significativement plus bas que le reste ; ceci reflète probablement le fait que les titres des revues sont typiquement concis et donc que la ligne de base de lead-3 est trop verbeuse. Maintenant que nous disposons d'une bonne base de travail, concentrons-nous sur le réglage fin de mT5 ! - -{#if fw === 'pt'} - -## *Finetuning* de mT5 avec l'API `Trainer`. - -Le *finetuning* d'un modèle pour le résumé est très similaire aux autres tâches que nous avons couvertes dans ce chapitre. La première chose à faire est de charger le modèle pré-entraîné depuis le checkpoint `mt5-small`. Puisque la compression est une tâche de séquence à séquence, nous pouvons charger le modèle avec la classe `AutoModelForSeq2SeqLM`, qui téléchargera automatiquement et mettra en cache les poids : - -```python -from transformers import AutoModelForSeq2SeqLM - -model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) -``` - -{:else} - -## *Finetuning* de mT5 avec Keras - -Le *finetuning* d'un modèle pour le résumé est très similaire aux autres tâches que nous avons couvertes dans ce chapitre. La première chose à faire est de charger le modèle pré-entraîné à partir du point de contrôle `mt5-small`. Puisque la compression est une tâche de séquence à séquence, nous pouvons charger le modèle avec la classe `AutoModelForSeq2SeqLM`, qui téléchargera automatiquement et mettra en cache les poids : - -```python -from transformers import TFAutoModelForSeq2SeqLM - -model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) -``` - -{/if} - - - -💡 Si vous vous demandez pourquoi vous ne voyez aucun avertissement concernant l'affinement du modèle sur une tâche en aval, c'est parce que pour les tâches de séquence à séquence, nous conservons tous les poids du réseau. Comparez cela à notre modèle de classification de texte dans [Chapitre 3](/course/fr/chapter3), où la tête du modèle pré-entraîné a été remplacée par un réseau initialisé de manière aléatoire. - - - -La prochaine chose que nous devons faire est de nous connecter au *Hub*. Si vous exécutez ce code dans un *notebook*, vous pouvez le faire avec la fonction utilitaire suivante : - -```python -from huggingface_hub import notebook_login - -notebook_login() -``` - -qui affichera un *widget* où vous pourrez saisir vos informations d'identification. Vous pouvez également exécuter cette commande dans votre terminal et vous connecter à partir de là : - -``` -huggingface-cli login -``` - -{#if fw === 'pt'} - -Nous aurons besoin de générer des résumés afin de calculer les scores ROUGE pendant l'entraînement. Heureusement, 🤗 *Transformers* fournit des classes dédiées `Seq2SeqTrainingArguments` et `Seq2SeqTrainer` qui peuvent faire cela pour nous automatiquement ! Pour voir comment cela fonctionne, définissons d'abord les hyperparamètres et autres arguments pour nos expériences : - -```python -from transformers import Seq2SeqTrainingArguments - -batch_size = 8 -num_train_epochs = 8 -# La perte d'entraînement à chaque époque -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, -) -``` - -Ici, l'argument `predict_with_generate` a été défini pour indiquer que nous devons générer des résumés pendant l'évaluation afin de pouvoir calculer les scores ROUGE pour chaque époque. Comme discuté dans [Chapter 1](/course/fr/chapter1), le décodeur effectue l'inférence en prédisant les tokens un par un, et ceci est implémenté par la méthode `generate()` du modèle. Définir `predict_with_generate=True` indique au `Seq2SeqTrainer` d'utiliser cette méthode pour l'évaluation. Nous avons également ajusté certains des hyperparamètres par défaut, comme le taux d'apprentissage, le nombre d'époques, et le taux de décroissance des poids, et nous avons réglé l'option `save_total_limit` pour ne sauvegarder que jusqu'à 3 *checkpoints* pendant l'entraînement. C'est parce que même la "petite" version de mT5 utilise environ un Go d'espace disque, et nous pouvons gagner un peu de place en limitant le nombre de copies que nous sauvegardons. - -L'argument `push_to_hub=True` nous permettra de pousser le modèle vers le Hub après l'entraînement ; vous trouverez le dépôt sous votre profil utilisateur dans l'emplacement défini par `output_dir`. Notez que vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/mt5-finetuned-amazon-en-es"`à `Seq2SeqTrainingArguments`. - -La prochaine chose que nous devons faire est de fournir à l'entraîneur une fonction `compute_metrics()` afin que nous puissions évaluer notre modèle pendant l'entraînement. Pour le résumé, c'est un peu plus compliqué que de simplement appeler `rouge_score.compute()` sur les prédictions du modèle, puisque nous devons _décoder_ les sorties et les étiquettes en texte avant de pouvoir calculer les scores ROUGE. La fonction suivante fait exactement cela, et utilise également la fonction `sent_tokenize()` de `nltk` pour séparer les phrases du résumé avec des nouvelles lignes : - - -```python -import numpy as np - - -def compute_metrics(eval_pred): - predictions, labels = eval_pred - # Décoder les résumés générés en texte - decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) - # Remplacer -100 dans les étiquettes car nous ne pouvons pas les décoder - labels = np.where(labels != -100, labels, tokenizer.pad_token_id) - # Décoder les résumés de référence en texte - decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) - # ROUGE attend une nouvelle ligne après chaque phrase - 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] - # Calcul des scores ROUGE - result = rouge_score.compute( - predictions=decoded_preds, references=decoded_labels, use_stemmer=True - ) - # Extract the median scores - result = {key: value.mid.fmeasure * 100 for key, value in result.items()} - return {k: round(v, 4) for k, v in result.items()} -``` - -{/if} - -Ensuite, nous devons définir un collateur de données pour notre tâche de séquence à séquence. Comme mT5 est un modèle Transformer encodeur-décodeur, une des subtilités de la préparation de nos lots est que, pendant le décodage, nous devons décaler les étiquettes d'une unité vers la droite. Ceci est nécessaire pour garantir que le décodeur ne voit que les étiquettes de vérité terrain précédentes et non les étiquettes actuelles ou futures, qui seraient faciles à mémoriser pour le modèle. Cela ressemble à la façon dont l'auto-attention masquée est appliquée aux entrées dans une tâche comme [la modélisation causale du langage](/course/fr/chapter7/6). - -Heureusement, 🤗 *Transformers* fournit un collateur `DataCollatorForSeq2Seq` qui rembourrera dynamiquement les entrées et les étiquettes pour nous. Pour instancier ce collateur, nous devons simplement fournir le *tokenizer* et le `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} - -Voyons ce que produit ce collateur lorsqu'on lui donne un petit lot d'exemples. Tout d'abord, nous devons supprimer les colonnes contenant des chaînes de caractères, car le collateur ne saura pas comment remplir ces éléments : - -```python -tokenized_datasets = tokenized_datasets.remove_columns( - books_dataset["train"].column_names -) -``` - -Comme le collateur attend une liste de `dict`s, où chaque `dict` représente un seul exemple dans l'ensemble de données, nous devons également mettre les données dans le format attendu avant de les transmettre au collateur de données : - -```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]])} -``` - -La principale chose à remarquer ici est que le premier exemple est plus long que le second, donc les `input_ids` et `attention_mask` du second exemple ont été complétés sur la droite avec un jeton `[PAD]` (dont l'ID est `0`). De même, nous pouvons voir que les `labels` ont été complétés par des `-100`s, pour s'assurer que les *tokens* de remplissage sont ignorés par la fonction de perte. Et enfin, nous pouvons voir un nouveau `decoder_input_ids` qui a déplacé les étiquettes vers la droite en insérant un jeton `[PAD]` dans la première entrée. - -{#if fw === 'pt'} - -Nous avons enfin tous les ingrédients dont nous avons besoin pour nous entraîner ! Nous devons maintenant simplement instancier le formateur avec les arguments standards : - -```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, -) -``` - -et lancer notre course d'entraînement : - -```python -trainer.train() -``` - -Pendant l'entraînement, vous devriez voir la perte d'entraînement diminuer et les scores ROUGE augmenter à chaque époque. Une fois l'entraînement terminé, vous pouvez voir les scores ROUGE finaux en exécutant `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} -``` - -D'après les scores, nous pouvons voir que notre modèle a largement surpassé notre ligne de base lead-3. Bien ! La dernière chose à faire est de pousser les poids du modèle vers le *Hub*, comme suit : - -``` -trainer.push_to_hub(commit_message="Training complete", tags="summarization") -``` - -```python out -'https://huggingface.co/huggingface-course/mt5-finetuned-amazon-en-es/commit/aa0536b829b28e73e1e4b94b8a5aacec420d40e0' -``` - -Ceci sauvegardera le point de contrôle et les fichiers de configuration dans `output_dir`, avant de télécharger tous les fichiers sur le *Hub*. En spécifiant l'argument `tags`, nous nous assurons également que le widget sur le Hub sera celui d'un pipeline de résumé au lieu de celui de la génération de texte par défaut associé à l'architecture mT5 (pour plus d'informations sur les balises de modèle, voir la [🤗 documentation du *Hub*](https://huggingface.co/docs/hub/main#how-is-a-models-type-of-inference-api-and-widget-determined)). La sortie de `trainer.push_to_hub()` est une URL vers le hash du commit Git, donc vous pouvez facilement voir les changements qui ont été faits au dépôt de modèle ! - -Pour conclure cette section, voyons comment nous pouvons également affiner mT5 en utilisant les fonctionnalités de bas niveau fournies par 🤗 *Accelerate*. - -{:else} - -Nous sommes presque prêts à nous entraîner ! Nous devons juste convertir nos jeux de données en `tf.data.Dataset`s en utilisant le collateur de données que nous avons défini ci-dessus, et ensuite `compile()` et `fit()` le modèle. D'abord, les jeux de données : - -```python -tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=True, - batch_size=8, -) -tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=False, - batch_size=8, -) -``` - -Maintenant, nous définissons nos hyperparamètres d'entraînement et nous compilons : - -```python -from transformers import create_optimizer -import tensorflow as tf - -# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans l'ensemble de données, divisé par la taille du batch, puis multiplié par le nombre total d'époques. -# par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un batch tf.data.Dataset, -# et non le jeu de données original Hugging Face Dataset, donc son len() est déjà 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) - -# Entraîner en mixed-precision float16 -tf.keras.mixed_precision.set_global_policy("mixed_float16") -``` - -Et enfin, nous ajustons le modèle. Nous utilisons un `PushToHubCallback` pour sauvegarder le modèle sur le *Hub* après chaque époque, ce qui nous permettra de l'utiliser pour l'inférence plus tard : - -```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 -) -``` - -Nous avons obtenu quelques valeurs de perte pendant l'entraînement, mais nous aimerions vraiment voir les métriques ROUGE que nous avons calculées plus tôt. Pour obtenir ces métriques, nous devons générer les sorties du modèle et les convertir en chaînes de caractères. Construisons quelques listes d'étiquettes et de prédictions pour comparer la métrique ROUGE (notez que si vous obtenez des erreurs d'importation pour cette section, vous pouvez avoir besoin de "pip install tqdm") : - -```python -from tqdm import tqdm -import numpy as np - -all_preds = [] -all_labels = [] -for batch in tqdm(tf_eval_dataset): - predictions = model.generate(**batch) - decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) - labels = batch["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) -``` - -Une fois que nous avons nos listes d'étiquettes et de chaînes de prédiction, le calcul du score ROUGE est facile : - -```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'} - -## *Finetuning* de mT5 avec 🤗 *Accelerate* - -Le *finetuning* de notre modèle avec 🤗 *Accelerate* est très similaire à l'exemple de classification de texte que nous avons rencontré dans [Chapitre 3](/course/fr/chapter3). Les principales différences seront la nécessité de générer explicitement nos résumés pendant l'Entraînement et de définir comment nous calculons les scores ROUGE (rappelons que le `Seq2SeqTrainer` s'est occupé de la génération pour nous). Voyons comment nous pouvons mettre en œuvre ces deux exigences dans 🤗 *Accelerate* ! - -### Préparer tout pour l'entraînement - -La première chose que nous devons faire est de créer un `DataLoader` pour chacun de nos splits. Puisque les chargeurs de données PyTorch attendent des batchs de tenseurs, nous devons définir le format à `"torch"` dans nos jeux de données : - -```python -tokenized_datasets.set_format("torch") -``` - -Maintenant que nous avons des jeux de données constitués uniquement de tenseurs, la prochaine chose à faire est d'instancier à nouveau le `DataCollatorForSeq2Seq`. Pour cela, nous devons fournir une nouvelle version du modèle, donc chargeons-le à nouveau depuis notre cache : - -```python -model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) -``` - -Nous pouvons ensuite instancier le collateur de données et l'utiliser pour définir nos chargeurs de données : - -```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 -) -``` - -La prochaine chose à faire est de définir l'optimiseur que nous voulons utiliser. Comme dans nos autres exemples, nous allons utiliser `AdamW`, qui fonctionne bien pour la plupart des problèmes : - -```python -from torch.optim import AdamW - -optimizer = AdamW(model.parameters(), lr=2e-5) -``` - -Enfin, nous introduisons notre modèle, notre optimiseur et nos chargeurs de données dans la méthode `accelerator.prepare()` : - -```python -from accelerate import Accelerator - -accelerator = Accelerator() -model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( - model, optimizer, train_dataloader, eval_dataloader -) -``` - - - -🚨 Si vous vous entraînez sur un TPU, vous devrez déplacer tout le code ci-dessus dans une fonction d'entraînement dédiée. Voir le [Chapitre 3](/course/fr/chapter3) pour plus de détails. - - - -Maintenant que nous avons préparé nos objets, il reste trois choses à faire : - -* définir le programme du taux d'apprentissage, -* implémenter une fonction pour post-traiter les résumés pour l'évaluation, -* créer un référentiel sur le *Hub* vers lequel nous pouvons pousser notre modèle. - -Pour le programme de taux d'apprentissage, nous utiliserons le programme linéaire standard des sections précédentes : - -```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, -) -``` - -Pour le post-traitement, nous avons besoin d'une fonction qui divise les résumés générés en phrases séparées par des nouvelles lignes. C'est le format attendu par la métrique ROUGE, et nous pouvons y parvenir avec le bout de code suivant : - -```python -def postprocess_text(preds, labels): - preds = [pred.strip() for pred in preds] - labels = [label.strip() for label in labels] - - # ROUGE attend une nouvelle ligne après chaque phrase - 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 -``` - -Cela devrait vous sembler familier si vous vous rappelez comment nous avons défini la fonction `compute_metrics()` du `Seq2SeqTrainer`. - -Enfin, nous devons créer un dépôt de modèles sur le *Hub*. Pour cela, nous pouvons utiliser la bibliothèque 🤗 *Hub*, qui porte le nom approprié. Nous avons juste besoin de définir un nom pour notre référentiel, et la bibliothèque a une fonction utilitaire pour combiner l'ID du référentiel avec le profil de l'utilisateur : - -```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' -``` - -Nous pouvons maintenant utiliser ce nom de référentiel pour cloner une version locale dans notre répertoire de résultats qui stockera les artefacts d'entraînement : - -```python -from huggingface_hub import Repository - -output_dir = "results-mt5-finetuned-squad-accelerate" -repo = Repository(output_dir, clone_from=repo_name) -``` - -This will allow us to push the artifacts back to the Hub by calling the `repo.push_to_hub()` method during training! Let's now wrap up our analysis by writing out the training loop. - -### Boucle d'entraînement - -La boucle d'entraînement pour le résumé est assez similaire aux autres exemples 🤗 *Accelerate* que nous avons rencontrés et est grossièrement divisée en quatre étapes principales : - -1. entraîner le modèle en itérant sur tous les exemples dans `train_dataloader` pour chaque époque, -2. générer les résumés du modèle à la fin de chaque époque, en générant d'abord les *tokens* puis en les décodant (ainsi que les résumés de référence) en texte, -3. calculer les scores ROUGE en utilisant les mêmes techniques que nous avons vues précédemment, -4. sauvegarder les points de contrôle et pousser le tout vers le *Hub*. Ici, nous nous appuyons sur l'argument `blocking=False` de l'objet `Repository` afin de pouvoir pousser les points de contrôle par époque de manière _asynchrone_. Cela nous permet de poursuivre l'entraînement sans avoir à attendre le téléchargement quelque peu lent associé à un modèle de la taille d'un Go ! - -Ces étapes peuvent être vues dans le bloc de code suivant : - -```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): - # Entraînement - model.train() - for step, batch in enumerate(train_dataloader): - outputs = model(**batch) - loss = outputs.loss - accelerator.backward(loss) - - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - progress_bar.update(1) - - # Evaluation - model.eval() - for step, batch in enumerate(eval_dataloader): - with torch.no_grad(): - generated_tokens = accelerator.unwrap_model(model).generate( - batch["input_ids"], - attention_mask=batch["attention_mask"], - ) - - generated_tokens = accelerator.pad_across_processes( - generated_tokens, dim=1, pad_index=tokenizer.pad_token_id - ) - labels = batch["labels"] - - # Si nous n'avons pas rempli la longueur maximale, nous devons également remplir les étiquettes. - 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() - - # Remplacer -100 dans les étiquettes car nous ne pouvons pas les décoder. - 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) - - # Calculer les métriques - result = rouge_score.compute() - # Extract the median ROUGE scores - result = {key: value.mid.fmeasure * 100 for key, value in result.items()} - result = {k: round(v, 4) for k, v in result.items()} - print(f"Epoch {epoch}:", result) - - # Sauvegarder et télécharger - 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} -``` - -Et c'est tout ! Une fois que vous l'aurez exécuté, vous aurez un modèle et des résultats assez similaires à ceux que nous avons obtenus avec le `Trainer`. - -{/if} - -## Utilisation de votre modèle *finetuné* - -Une fois que vous avez poussé le modèle vers le *Hub*, vous pouvez jouer avec lui soit via le widget d'inférence, soit avec un objet `pipeline`, comme suit : - -```python -from transformers import pipeline - -hub_model_id = "huggingface-course/mt5-small-finetuned-amazon-en-es" -summarizer = pipeline("summarization", model=hub_model_id) -``` - -Nous pouvons alimenter notre pipeline avec quelques exemples de l'ensemble de test (que le modèle n'a pas vu) pour avoir une idée de la qualité des résumés. Tout d'abord, implémentons une fonction simple pour afficher ensemble la critique, le titre et le résumé généré : - -```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}'") -``` - -Examinons l'un des exemples anglais que nous recevons : - -```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.' -# Ce produit n'a rien de spécial... le livre est trop petit et rigide et il est difficile d'y écrire. L'énorme autocollant au dos ne se détache pas et a l'air super collant. Je n'achèterai plus jamais ce produit. J'aurais pu simplement acheter un journal dans un magasin à un dollar et ce serait à peu près la même chose. Il est également très cher pour ce qu'il est. - -'>>> Title: Not impressed at all... buy something else' # Pas du tout impressionné... achetez autre chose. - -'>>> Summary: Nothing special at all about this product' # Rien de spécial à propos de ce produit -``` - -Ce n'est pas si mal ! Nous pouvons voir que notre modèle a été capable d'effectuer un résumé _abstractif_ en augmentant certaines parties de la critique avec de nouveaux mots. Et peut-être que l'aspect le plus cool de notre modèle est qu'il est bilingue, donc nous pouvons également générer des résumés de critiques en espagnol : - -```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' # C'est une trilogie qui se lit très facilement. J'ai aimé, je ne m'attendais pas du tout à la fin. - -'>>> Title: Buena literatura para adolescentes' # Bonne littérature pour les adolescents - -'>>> Summary: Muy facil de leer' # Très facile à lire -``` - -Le résumé se traduit par "Très facile à lire", ce qui, comme nous pouvons le constater, a été extrait directement de la critique. Néanmoins, cela montre la polyvalence du modèle mT5 et vous a donné un aperçu de ce que c'est que de traiter un corpus multilingue ! - -Ensuite, nous allons nous intéresser à une tâche un peu plus complexe : entraîner un modèle de langue à partir de zéro. + + +# Résumé de textes + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + +Dans cette section, nous allons voir comment les *transformers* peuvent être utilisés pour condenser de longs documents en résumés, une tâche connue sous le nom de _résumé de texte_. Il s'agit de l'une des tâches de NLP les plus difficiles car elle requiert une série de capacités, telles que la compréhension de longs passages et la génération d'un texte cohérent qui capture les sujets principaux d'un document. Cependant, lorsqu'il est bien fait, le résumé de texte est un outil puissant qui peut accélérer divers processus commerciaux en soulageant les experts du domaine de la lecture détaillée de longs documents. + + + +Bien qu'il existe déjà plusieurs modèles *finetunés* pour le résumé sur le [Hugging Face Hub](https://huggingface.co/models?pipeline_tag=summarization&sort=downloads), la plupart d'entre eux ne sont adaptés qu'aux documents en anglais. Ainsi, pour ajouter une touche d'originalité à cette section, nous allons entraîner un modèle bilingue pour l'anglais et l'espagnol. À la fin de cette section, vous disposerez d'un [modèle](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es) capable de résumer les commentaires des clients comme celui présenté ici : + + + + +Comme nous allons le voir, ces résumés sont concis car ils sont appris à partir des titres que les clients fournissent dans leurs commentaires sur les produits. Commençons par constituer un corpus bilingue approprié pour cette tâche. + +## Préparation d'un corpus multilingue + +Nous allons utiliser le [Multilingual Amazon Reviews Corpus](https://huggingface.co/datasets/amazon_reviews_multi) pour créer notre résumeur bilingue. Ce corpus est constitué d'évaluations de produits Amazon en six langues et est généralement utilisé pour évaluer les classificateurs multilingues. Cependant, comme chaque critique est accompagnée d'un titre court, nous pouvons utiliser les titres comme résumés cibles pour l'apprentissage de notre modèle ! Pour commencer, téléchargeons les sous-ensembles anglais et espagnols depuis le *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 + }) +}) +``` + +Comme vous pouvez le voir, pour chaque langue, il y a 200 000 évaluations pour la partie "entraînement", et 5 000 évaluations pour chacune des parties "validation" et "test". Les informations qui nous intéressent sont contenues dans les colonnes `review_body` et `review_title`. Voyons quelques exemples en créant une fonction simple qui prend un échantillon aléatoire de l'ensemble d'entraînement avec les techniques apprises au [Chapitre 5](/course/fr/chapter5) : + +```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' # Travaillé en position avant, pas arrière +'>> 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.' +# 3 étoiles car ce ne sont pas des freins arrière comme indiqué dans la description de l'article. Au moins, l'adaptateur de montage ne fonctionnait que sur la fourche avant du vélo pour lequel je l'ai acheté.'' + +'>> 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' +# Il fait son travail et il est magnifique mais le mien est en train de tomber en morceaux, j'ai dû le recoller avec de la colle chaude. + +'>> Title: Can\'t beat these for the money' # On ne peut pas faire mieux pour le prix +'>> 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.' +# Je l'ai acheté pour manipuler diverses pièces d'avion et des "trucs" de hangar que je devais organiser ; il a vraiment fait l'affaire. L'unité est arrivée rapidement, était bien emballée et est arrivée intacte (toujours un bon signe). Il y a cinq supports muraux - trois sur le dessus et deux sur le dessous. Je voulais le monter sur le mur, alors tout ce que j'ai eu à faire était d'enlever les deux couches supérieures de tiroirs en plastique, ainsi que les tiroirs d'angle inférieurs, de le placer où je voulais et de le marquer ; j'ai ensuite utilisé quelques-uns des nouveaux ancrages muraux à vis en plastique (la variété de 50 livres) et il s'est facilement monté sur le mur. Certains ont fait remarquer qu'ils voulaient des séparateurs pour les tiroirs, et qu'ils les ont fabriqués. Bonne idée. Pour ma part, j'avais besoin de quelque chose dont je pouvais voir le contenu à hauteur des yeux, et je voulais donc des tiroirs plus grands. J'aime aussi le fait qu'il s'agisse du nouveau plastique qui ne se fragilise pas et ne se fend pas comme mes anciens tiroirs en plastique. J'aime la construction entièrement en plastique. Elle est suffisamment résistante pour contenir des pièces métalliques, mais étant en plastique, elle n'est pas aussi lourde qu'un cadre métallique, ce qui permet de la fixer facilement au mur et de la charger d'objets lourds ou légers. Aucun problème. Pour le prix, c'est imbattable. C'est le meilleur que j'ai acheté à ce jour, et j'utilise des versions de ce type depuis plus de quarante ans. + +``` + + + +✏️ **Essayez !** Changez la graine aléatoire dans la commande `Dataset.shuffle()` pour explorer d'autres critiques dans le corpus. Si vous parlez espagnol, jetez un coup d'œil à certaines des critiques dans `spanish_dataset` pour voir si les titres semblent aussi être des résumés raisonnables. + + + +Cet échantillon montre la diversité des critiques que l'on trouve généralement en ligne, allant du positif au négatif (et tout ce qui se trouve entre les deux !). Bien que l'exemple avec le titre "meh" ne soit pas très informatif, les autres titres semblent être des résumés décents des critiques elles-mêmes. Entraîner un modèle de résumé sur l'ensemble des 400 000 avis prendrait beaucoup trop de temps sur un seul GPU, nous allons donc nous concentrer sur la génération de résumés pour un seul domaine de produits. Pour avoir une idée des domaines parmi lesquels nous pouvons choisir, convertissons `english_dataset` en `pandas.DataFrame` et calculons le nombre d'avis par catégorie de produits : + +```python +english_dataset.set_format("pandas") +english_df = english_dataset["train"][:] +# Afficher les comptes des 20 premiers produits +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 +``` + +Les produits les plus populaires dans l'ensemble de données anglaises concernent les articles ménagers, les vêtements et l'électronique sans fil. Pour rester dans le thème d'Amazon, nous allons nous concentrer sur le résumé des critiques de livres. Après tout, c'est la raison d'être de l'entreprise ! Nous pouvons voir deux catégories de produits qui correspondent à nos besoins (`book` et `digital_ebook_purchase`), nous allons donc filtrer les ensembles de données dans les deux langues pour ces produits uniquement. Comme nous l'avons vu dans le [Chapitre 5](/course/fr/chapter5), la fonction `Dataset.filter()` nous permet de découper un jeu de données de manière très efficace, nous pouvons donc définir une fonction simple pour le faire : + +```python +def filter_books(example): + return ( + example["product_category"] == "book" + or example["product_category"] == "digital_ebook_purchase" + ) +``` + +Maintenant, lorsque nous appliquons cette fonction à `english_dataset` et `spanish_dataset`, le résultat ne contiendra que les lignes impliquant les catégories de livres. Avant d'appliquer le filtre, changeons le format de `english_dataset` de `"pandas"` à `"arrow"` : + +```python +english_dataset.reset_format() +``` + +Nous pouvons ensuite appliquer la fonction de filtrage et, à titre de vérification, inspecter un échantillon de critiques pour voir si elles portent bien sur des livres : + +```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.' # Je suis déçu +'>> 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.' +# Je suppose que j'avais de plus grandes attentes pour ce livre d'après les critiques. Je pensais vraiment que j'allais au moins l'aimer. L'idée de l'intrigue était géniale. J'aimais Ash, mais ça n'allait nulle part. La plus grande partie du livre était consacrée à leur émission de radio et aux conversations avec les auditeurs. Je voulais que l'auteur creuse plus profondément pour que nous puissions vraiment connaître les personnages. Tout ce que nous savons de Grace, c'est qu'elle est séduisante, qu'elle est latino et qu'elle est une sorte de garce. Je suis déçue. + +'>> Title: Good art, good price, poor design' # Un bon art, un bon prix, un mauvais 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' +# J'ai eu le calendrier DC Vintage ces deux dernières années, mais il était en rupture de stock pour toujours cette année et j'ai vu qu'ils avaient réduit les dimensions sans raison valable. Celui-ci a de bons choix artistiques mais le design a le pli qui traverse l'image, donc c'est moins esthétique, surtout si vous voulez garder une image à accrocher. Pour le prix, c'est un bon calendrier. + +'>> Title: Helpful' Utile +'>> Review: Nearly all the tips useful and. I consider myself an intermediate to advanced user of OneNote. I would highly recommend.' +# Presque tous les conseils sont utiles et. Je me considère comme un utilisateur intermédiaire à avancé de OneNote. Je le recommande vivement. +``` + +D'accord, nous pouvons voir que les critiques ne concernent pas strictement les livres et peuvent se référer à des choses comme des calendriers et des applications électroniques telles que OneNote. Néanmoins, le domaine semble approprié pour entraîner un modèle de résumé. Avant de regarder les différents modèles qui conviennent à cette tâche, nous avons une dernière préparation de données à faire : combiner les critiques anglaises et espagnoles en un seul objet `DatasetDict`. 🤗 *Datasets* fournit une fonction pratique `concatenate_datasets()` qui (comme son nom l'indique) va empiler deux objets `Dataset` l'un sur l'autre. Ainsi, pour créer notre jeu de données bilingue, nous allons boucler sur chaque division, concaténer les jeux de données pour cette division, et mélanger le résultat pour s'assurer que notre modèle ne s'adapte pas trop à une seule langue : + +```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) + +# Quelques exemples +show_samples(books_dataset) +``` + +```python out +'>> Title: Easy to follow!!!!' # Facile à suivre!!!! +'>> 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.' +# J'ai adoré The dash diet weight loss Solution. Jamais faim. Je recommande ce régime. Les menus sont également bien arrondis. Essayez-le. Il contient beaucoup d'informations, merci. + +'>> Title: PARCIALMENTE DAÑADO' # PARTIELLEMENT ENDOMMAGÉ +'>> 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).' +# Il est arrivé le jour prévu, avec d'autres livres que j'avais commandés, mais la boîte est arrivée en mauvais état, ce qui a endommagé les coins des livres car ils étaient livrés sans protection (doublure). + +'>> Title: no lo he podido descargar' # Je n'ai pas pu le télécharger +'>> Review: igual que el anterior' # même chose que ci-dessus +``` + +Cela ressemble certainement à un mélange de critiques anglaises et espagnoles ! Maintenant que nous avons un corpus d'entraînement, une dernière chose à vérifier est la distribution des mots dans les critiques et leurs titres. Ceci est particulièrement important pour les tâches de résumé, où les résumés de référence courts dans les données peuvent biaiser le modèle pour qu'il ne produise qu'un ou deux mots dans les résumés générés. Les graphiques ci-dessous montrent les distributions de mots, et nous pouvons voir que les titres sont fortement biaisés vers seulement 1 ou 2 mots : + +
+Word count distributions for the review titles and texts. + +
+ +Pour y remédier, nous allons filtrer les exemples avec des titres très courts afin que notre modèle puisse produire des résumés plus intéressants. Puisque nous avons affaire à des textes anglais et espagnols, nous pouvons utiliser une heuristique grossière pour séparer les titres sur les espaces blancs, puis utiliser notre fidèle méthode `Dataset.filter()` comme suit : + +```python +books_dataset = books_dataset.filter(lambda x: len(x["review_title"].split()) > 2) +``` + +Maintenant que nous avons préparé notre corpus, voyons quelques *transformers* possibles que l'on pourrait *finetuné* dessus ! + +## Modèles pour le résumé de texte + +Si vous y pensez, le résumé de texte est une tâche similaire à la traduction automatique : nous avons un corps de texte, comme une critique, que nous aimerions "traduire" en une version plus courte qui capture les caractéristiques saillantes de l'entrée. En conséquence, la plupart des modèles Transformer pour le résumé adoptent l'architecture encodeur-décodeur que nous avons rencontrée pour la première fois dans le [Chapitre 1](/course/fr/chapter1), bien qu'il y ait quelques exceptions comme la famille de modèles GPT qui peut également être utilisée pour le résumé dans des contextes peu complexes. Le tableau suivant présente quelques modèles pré-entraînés populaires qui peuvent être *finetunés* pour le résumé. + +| *Transformers* | Description | Multilingue ? | +| :---------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------: | +| [GPT-2](https://huggingface.co/gpt2-xl) | Bien qu'il soit entraîné comme un modèle de langage autorégressif, vous pouvez faire en sorte que GPT-2 génère des résumés en ajoutant "TL;DR" à la fin du texte d'entrée. | ❌ | +| [PEGASUS](https://huggingface.co/google/pegasus-large) | Utilise un objectif de pré-entraînement pour prédire les phrases masquées dans les textes à plusieurs phrases. Cet objectif de pré-entraînement est plus proche du résumé que de la modélisation du langage standard et obtient des scores élevés sur des benchmarks populaires. | ❌ | +| [T5](https://huggingface.co/t5-base) | Une architecture universelle de *transformer* qui formule toutes les tâches dans un cadre texte à texte ; par exemple, le format d'entrée du modèle pour résumer un document est `summarize : ARTICLE`. | ❌ | +| [mT5](https://huggingface.co/google/mt5-base) | Une version multilingue de T5, pré-entraînée sur le corpus multilingue Common Crawl (mC4), couvrant 101 langues. | ✅ | +| [BART](https://huggingface.co/facebook/bart-base) | Une architecture de *transformer* avec une pile d'encodeurs et de décodeurs entraînés pour reconstruire l'entrée corrompue qui combine les schémas de pré-entraînement de BERT et GPT-2. | ❌ | +| [mBART-50](https://huggingface.co/facebook/mbart-large-50) | Une version multilingue de BART, pré-entraînée sur 50 langues. | ✅ | + +Comme vous pouvez le voir dans ce tableau, la majorité des modèles Transformer pour le résumé (et en fait la plupart des tâches de NLP) sont monolingues. C'est une bonne chose si votre tâche se déroule dans une langue "à haute ressource" comme l'anglais ou l'allemand, mais moins pour les milliers d'autres langues utilisées dans le monde. Heureusement, il existe une catégorie de modèles Transformer multilingues, comme mT5 et mBART, qui viennent à la rescousse. Ces modèles sont pré-entraînés en utilisant la modélisation du langage, mais avec une particularité : au lieu de s'entraîner sur un corpus d'une seule langue, ils sont entraînés conjointement sur des textes dans plus de 50 langues à la fois ! + +Nous allons nous concentrer sur mT5, une architecture intéressante basée sur T5 qui a été pré-entraînée dans un cadre texte à texte. Dans T5, chaque tâche NLP est formulée en termes d'un préfixe d'invite comme `summarize:` qui conditionne le modèle à adapter le texte généré à l'invite. Comme le montre la figure ci-dessous, cela rend T5 extrêmement polyvalent, car vous pouvez résoudre de nombreuses tâches avec un seul modèle ! + +
+Different tasks performed by the T5 architecture. + +
+ +mT5 n'utilise pas de préfixes, mais partage une grande partie de la polyvalence de T5 et a l'avantage d'être multilingue. Maintenant que nous avons choisi un modèle, voyons comment préparer nos données pour l'entraînement. + + + + +✏️ **Essayez** Une fois que vous avez travaillé sur cette section, voyez comment mT5 se compare à mBART en affinant ce dernier avec les mêmes techniques. Pour des points bonus, vous pouvez aussi essayer de *finetuner* le T5 uniquement sur les critiques anglaises. Puisque le T5 a un préfixe spécial, vous devrez ajouter `summarize:` aux exemples d'entrée dans les étapes de prétraitement ci-dessous. + + + +## Prétraitement des données + + + +Notre prochaine tâche est de tokeniser et d'encoder nos critiques et leurs titres. Comme d'habitude, nous commençons par charger le *tokenizer* associé au point de contrôle du modèle pré-entraîné. Nous utiliserons `mt5-small` comme point de contrôle afin de pouvoir affiner le modèle en un temps raisonnable : + +```python +from transformers import AutoTokenizer + +model_checkpoint = "google/mt5-small" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + + + +💡 Aux premiers stades de vos projets de NLP, une bonne pratique consiste à entraîner une classe de "petits" modèles sur un petit échantillon de données. Cela vous permet de déboguer et d'itérer plus rapidement vers un flux de travail de bout en bout. Une fois que vous avez confiance dans les résultats, vous pouvez toujours faire évoluer le modèle en changeant simplement le point de contrôle du modèle ! + + + +Testons le *tokenizer* de mT5 sur un petit exemple : + +```python +inputs = tokenizer( + "I loved reading the Hunger Games!" +) # J'ai adoré lire les 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]} +``` + +Ici nous pouvons voir les familiers `input_ids` et `attention_mask` que nous avons rencontrés dans nos premières expériences de fine-tuning au [Chapitre 3](/course/fr/chapter3). Décodons ces identifiants d'entrée avec la fonction `convert_ids_to_tokens()` du tokenizer pour voir à quel type de tokenizer nous avons affaire : + +```python +tokenizer.convert_ids_to_tokens(inputs.input_ids) +``` + +```python out +['▁I', '▁', 'loved', '▁reading', '▁the', '▁Hung', 'er', '▁Games', ''] +``` + +Le caractère spécial Unicode `▁` et le *token* de fin de séquence `` indiquent que nous avons affaire au *tokenizer* de SentencePiece, qui est basé sur l'algorithme de segmentation Unigram discuté dans le [Chapitre 6](/course/chapter6). Unigram est particulièrement utile pour les corpus multilingues car il permet à SentencePiece d'être agnostique vis-à-vis des accents, de la ponctuation et du fait que de nombreuses langues, comme le japonais, n'ont pas de caractères d'espacement. + +Pour tokeniser notre corpus, nous devons faire face à une subtilité associée au résumé : comme nos étiquettes sont également du texte, il est possible qu'elles dépassent la taille maximale du contexte du modèle. Cela signifie que nous devons appliquer une troncature à la fois aux critiques et à leurs titres pour nous assurer de ne pas transmettre des entrées trop longues à notre modèle. Les tokenizers de 🤗 *Transformers* fournissent une fonction très pratique `as_target_tokenizer()` qui vous permet de tokeniser les étiquettes en parallèle avec les entrées. Ceci est typiquement fait en utilisant un gestionnaire de contexte à l'intérieur d'une fonction de prétraitement qui encode d'abord les entrées, et ensuite encode les étiquettes comme une colonne séparée. Voici un exemple d'une telle fonction pour 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 + ) + # Configurer le *tokenizer* pour les cibles. + with tokenizer.as_target_tokenizer(): + labels = tokenizer( + examples["review_title"], max_length=max_target_length, truncation=True + ) + + model_inputs["labels"] = labels["input_ids"] + return model_inputs +``` + +Parcourons ce code pour comprendre ce qui se passe. La première chose que nous avons faite est de définir des valeurs pour `max_input_length` et `max_target_length`, qui fixent les limites supérieures de la longueur des commentaires et des titres. Comme le corps de la critique est généralement beaucoup plus long que le titre, nous avons mis ces valeurs à l'échelle en conséquence. Ensuite, dans la `preprocess_function()` elle-même, nous pouvons voir que les commentaires sont d'abord tokenizés, suivis par les titres avec `as_target_tokenizer()`. + +Avec la fonction `preprocess_function()`, il est alors simple de tokeniser l'ensemble du corpus en utilisant la fonction pratique `Dataset.map()` que nous avons largement utilisée dans ce cours : + +```python +tokenized_datasets = books_dataset.map(preprocess_function, batched=True) +``` + +Maintenant que le corpus a été prétraité, examinons certaines métriques couramment utilisées pour le résumé. Comme nous allons le voir, il n'existe pas de solution miracle pour mesurer la qualité d'un texte généré par une machine. + + + +💡 Vous avez peut-être remarqué que nous avons utilisé `batched=True` dans notre fonction `Dataset.map()` ci-dessus. Cela permet de coder les exemples par lots de 1 000 (par défaut) et d'utiliser les capacités de multithreading des *tokenizers* rapides de 🤗 *Transformers*. Lorsque cela est possible, essayez d'utiliser `batched=True` pour tirer le meilleur parti de votre prétraitement ! + + + + +## Métriques pour le résumé de texte + + + +Par rapport à la plupart des autres tâches que nous avons abordées dans ce cours, la mesure des performances des tâches de génération de texte comme le résumé ou la traduction n'est pas aussi simple. Par exemple, pour une critique telle que "J'ai adoré lire les Hunger Games", il existe plusieurs résumés valides, comme "J'ai adoré Hunger Games" ou "Hunger Games est une excellente lecture". Il est clair que l'application d'une sorte de correspondance exacte entre le résumé généré et l'étiquette n'est pas une bonne solution - même les humains auraient de mauvais résultats avec une telle mesure, car nous avons tous notre propre style d'écriture. + +Pour le résumé, l'une des métriques les plus couramment utilisées est le [score ROUGE](https://en.wikipedia.org/wiki/ROUGE_(metric)) (abréviation de Recall-Oriented Understudy for Gisting Evaluation). L'idée de base de cette métrique est de comparer un résumé généré avec un ensemble de résumés de référence qui sont généralement créés par des humains. Pour être plus précis, supposons que nous voulions comparer les deux résumés suivants : + +```python +generated_summary = "I absolutely loved reading the Hunger Games" +reference_summary = "I loved reading the Hunger Games" +``` + +Une façon de les comparer pourrait être de compter le nombre de mots qui se chevauchent, qui dans ce cas serait de 6. Cependant, cette méthode est un peu grossière, c'est pourquoi ROUGE se base sur le calcul des scores de _précision_ et de _rappel_ pour le chevauchement. + + + +🙋 Ne vous inquiétez pas si c'est la première fois que vous entendez parler de précision et de rappel - nous allons parcourir ensemble quelques exemples explicites pour que tout soit clair. Ces métriques sont généralement rencontrées dans les tâches de classification, donc si vous voulez comprendre comment la précision et le rappel sont définis dans ce contexte, nous vous recommandons de consulter les [guides de `scikit-learn`](https://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html). + + + +Pour ROUGE, le rappel mesure la proportion du résumé de référence qui est capturée par le résumé généré. Si nous ne faisons que comparer des mots, le rappel peut être calculé selon la formule suivante : + +$$ \mathrm{Recall} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, reference\, summary}} $$ + +Pour notre exemple simple ci-dessus, cette formule donne un rappel parfait de 6/6 = 1 ; c'est-à-dire que tous les mots du résumé de référence ont été produits par le modèle. Cela peut sembler génial, mais imaginez que le résumé généré ait été "J'ai vraiment aimé lire les Hunger Games toute la nuit". Le rappel serait également parfait, mais le résumé serait sans doute moins bon puisqu'il serait verbeux. Pour traiter ces scénarios, nous calculons également la précision, qui, dans le contexte de ROUGE, mesure la proportion du résumé généré qui était pertinente : + +$$ \mathrm{Precision} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, generated\, summary}} $$ + +En appliquant cela à notre résumé verbeux, on obtient une précision de 6/10 = 0,6, ce qui est considérablement moins bon que la précision de 6/7 = 0,86 obtenue par notre résumé plus court. En pratique, la précision et le rappel sont généralement calculés, puis le score F1 (la moyenne harmonique de la précision et du rappel) est indiqué. Nous pouvons le faire facilement dans 🤗 *Datasets* en installant d'abord le paquet `rouge_score` : + +```py +!pip install rouge_score +``` + +et ensuite charger la métrique ROUGE comme suit : + +```python +from datasets import load_metric + +rouge_score = load_metric("rouge") +``` + +Ensuite, nous pouvons utiliser la fonction `rouge_score.compute()` pour calculer toutes les métriques en une seule fois : + +```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))} +``` + +Whoa, il y a un batch d'informations dans cette sortie. Qu'est-ce que ça veut dire ? Tout d'abord, 🤗 *Datasets* calcule en fait des intervalles de confiance pour la précision, le rappel et le score F1 ; ce sont les attributs `low`, `mid`, et `high` que vous pouvez voir ici. De plus, 🤗 *Datasets* calcule une variété de scores ROUGE qui sont basés sur différents types de granularité du texte lors de la comparaison des résumés générés et de référence. La variante `rouge1` est le chevauchement des unigrammes. C'est juste une façon fantaisiste de dire le chevauchement des mots et c'est exactement la métrique dont nous avons discuté ci-dessus. Pour vérifier cela, nous allons extraire la valeur "moyenne" de nos scores : + +```python +scores["rouge1"].mid +``` + +```python out +Score(precision=0.86, recall=1.0, fmeasure=0.92) +``` + +Super, les chiffres de précision et de rappel correspondent ! Maintenant, qu'en est-il des autres scores ROUGE ? `rouge2` mesure le chevauchement entre les bigrammes (pensez au chevauchement des paires de mots), tandis que `rougeL` et `rougeLsum` mesurent les plus longues séquences de mots correspondants en recherchant les plus longues sous-souches communes dans les résumés générés et de référence. La "somme" dans `rougeLsum` fait référence au fait que cette métrique est calculée sur un résumé entier, alors que `rougeL` est calculée comme une moyenne sur des phrases individuelles. + + + +✏️ **Essayez** Créez votre propre exemple de résumé généré et de référence et voyez si les scores ROUGE obtenus correspondent à un calcul manuel basé sur les formules de précision et de rappel. Pour des points bonus, divisez le texte en bigrammes et comparez la précision et le rappel pour la métrique `rouge2`. + + + +Nous utiliserons ces scores ROUGE pour suivre les performances de notre modèle, mais avant cela, faisons ce que tout bon praticien de NLP devrait faire : créer une base de référence solide, mais simple ! + +### Création d'une base de référence solide + +Une base de référence commune pour le résumé de texte consiste à prendre simplement les trois premières phrases d'un article, souvent appelée la base de référence _lead-3_. Nous pourrions utiliser des points pour suivre les limites de la phrase, mais cela échouera avec des acronymes comme "U.S." ou "U.N.". -- Nous allons donc utiliser la bibliothèque `nltk`, qui inclut un meilleur algorithme pour gérer ces cas. Vous pouvez installer le paquetage en utilisant `pip` comme suit : + +```python +!pip install nltk +``` + +puis téléchargez les règles de ponctuation : + +```python +import nltk + +nltk.download("punkt") +``` + +Ensuite, nous importons le *tokenizer* de `nltk` et créons une fonction simple pour extraire les trois premières phrases d'une critique. La convention dans le résumé de texte est de séparer chaque résumé avec une nouvelle ligne, donc nous allons également inclure ceci et le tester sur un exemple d'entraînement : + +```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.' # J'ai grandi en lisant Koontz, et il y a des années, j'ai arrêté, convaincu que je l'avais "dépassé" +'Still,when a friend was looking for something suspenseful too read, I suggested Koontz.' " Pourtant, quand une amie cherchait un livre à suspense, je lui ai suggéré Koontz. +'She found Strangers.' # Elle a trouvé Strangers. +``` + +Cela semble fonctionner, alors implémentons maintenant une fonction qui extrait ces "résumés" d'un ensemble de données et calcule les scores ROUGE pour la ligne de base : + +```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"]) +``` + +Nous pouvons ensuite utiliser cette fonction pour calculer les scores ROUGE sur l'ensemble de validation et les embellir un peu en utilisant 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} +``` + +Nous pouvons voir que le score de `rouge2` est significativement plus bas que le reste ; ceci reflète probablement le fait que les titres des revues sont typiquement concis et donc que la ligne de base de lead-3 est trop verbeuse. Maintenant que nous disposons d'une bonne base de travail, concentrons-nous sur le réglage fin de mT5 ! + +{#if fw === 'pt'} + +## *Finetuning* de mT5 avec l'API `Trainer`. + +Le *finetuning* d'un modèle pour le résumé est très similaire aux autres tâches que nous avons couvertes dans ce chapitre. La première chose à faire est de charger le modèle pré-entraîné depuis le checkpoint `mt5-small`. Puisque la compression est une tâche de séquence à séquence, nous pouvons charger le modèle avec la classe `AutoModelForSeq2SeqLM`, qui téléchargera automatiquement et mettra en cache les poids : + +```python +from transformers import AutoModelForSeq2SeqLM + +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{:else} + +## *Finetuning* de mT5 avec Keras + +Le *finetuning* d'un modèle pour le résumé est très similaire aux autres tâches que nous avons couvertes dans ce chapitre. La première chose à faire est de charger le modèle pré-entraîné à partir du point de contrôle `mt5-small`. Puisque la compression est une tâche de séquence à séquence, nous pouvons charger le modèle avec la classe `AutoModelForSeq2SeqLM`, qui téléchargera automatiquement et mettra en cache les poids : + +```python +from transformers import TFAutoModelForSeq2SeqLM + +model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{/if} + + + +💡 Si vous vous demandez pourquoi vous ne voyez aucun avertissement concernant l'affinement du modèle sur une tâche en aval, c'est parce que pour les tâches de séquence à séquence, nous conservons tous les poids du réseau. Comparez cela à notre modèle de classification de texte dans [Chapitre 3](/course/fr/chapter3), où la tête du modèle pré-entraîné a été remplacée par un réseau initialisé de manière aléatoire. + + + +La prochaine chose que nous devons faire est de nous connecter au *Hub*. Si vous exécutez ce code dans un *notebook*, vous pouvez le faire avec la fonction utilitaire suivante : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +qui affichera un *widget* où vous pourrez saisir vos informations d'identification. Vous pouvez également exécuter cette commande dans votre terminal et vous connecter à partir de là : + +``` +huggingface-cli login +``` + +{#if fw === 'pt'} + +Nous aurons besoin de générer des résumés afin de calculer les scores ROUGE pendant l'entraînement. Heureusement, 🤗 *Transformers* fournit des classes dédiées `Seq2SeqTrainingArguments` et `Seq2SeqTrainer` qui peuvent faire cela pour nous automatiquement ! Pour voir comment cela fonctionne, définissons d'abord les hyperparamètres et autres arguments pour nos expériences : + +```python +from transformers import Seq2SeqTrainingArguments + +batch_size = 8 +num_train_epochs = 8 +# La perte d'entraînement à chaque époque +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, +) +``` + +Ici, l'argument `predict_with_generate` a été défini pour indiquer que nous devons générer des résumés pendant l'évaluation afin de pouvoir calculer les scores ROUGE pour chaque époque. Comme discuté dans [Chapter 1](/course/fr/chapter1), le décodeur effectue l'inférence en prédisant les tokens un par un, et ceci est implémenté par la méthode `generate()` du modèle. Définir `predict_with_generate=True` indique au `Seq2SeqTrainer` d'utiliser cette méthode pour l'évaluation. Nous avons également ajusté certains des hyperparamètres par défaut, comme le taux d'apprentissage, le nombre d'époques, et le taux de décroissance des poids, et nous avons réglé l'option `save_total_limit` pour ne sauvegarder que jusqu'à 3 *checkpoints* pendant l'entraînement. C'est parce que même la "petite" version de mT5 utilise environ un Go d'espace disque, et nous pouvons gagner un peu de place en limitant le nombre de copies que nous sauvegardons. + +L'argument `push_to_hub=True` nous permettra de pousser le modèle vers le Hub après l'entraînement ; vous trouverez le dépôt sous votre profil utilisateur dans l'emplacement défini par `output_dir`. Notez que vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/mt5-finetuned-amazon-en-es"`à `Seq2SeqTrainingArguments`. + +La prochaine chose que nous devons faire est de fournir à l'entraîneur une fonction `compute_metrics()` afin que nous puissions évaluer notre modèle pendant l'entraînement. Pour le résumé, c'est un peu plus compliqué que de simplement appeler `rouge_score.compute()` sur les prédictions du modèle, puisque nous devons _décoder_ les sorties et les étiquettes en texte avant de pouvoir calculer les scores ROUGE. La fonction suivante fait exactement cela, et utilise également la fonction `sent_tokenize()` de `nltk` pour séparer les phrases du résumé avec des nouvelles lignes : + + +```python +import numpy as np + + +def compute_metrics(eval_pred): + predictions, labels = eval_pred + # Décoder les résumés générés en texte + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + # Remplacer -100 dans les étiquettes car nous ne pouvons pas les décoder + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + # Décoder les résumés de référence en texte + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + # ROUGE attend une nouvelle ligne après chaque phrase + 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] + # Calcul des scores ROUGE + result = rouge_score.compute( + predictions=decoded_preds, references=decoded_labels, use_stemmer=True + ) + # Extract the median scores + result = {key: value.mid.fmeasure * 100 for key, value in result.items()} + return {k: round(v, 4) for k, v in result.items()} +``` + +{/if} + +Ensuite, nous devons définir un collateur de données pour notre tâche de séquence à séquence. Comme mT5 est un modèle Transformer encodeur-décodeur, une des subtilités de la préparation de nos lots est que, pendant le décodage, nous devons décaler les étiquettes d'une unité vers la droite. Ceci est nécessaire pour garantir que le décodeur ne voit que les étiquettes de vérité terrain précédentes et non les étiquettes actuelles ou futures, qui seraient faciles à mémoriser pour le modèle. Cela ressemble à la façon dont l'auto-attention masquée est appliquée aux entrées dans une tâche comme [la modélisation causale du langage](/course/fr/chapter7/6). + +Heureusement, 🤗 *Transformers* fournit un collateur `DataCollatorForSeq2Seq` qui rembourrera dynamiquement les entrées et les étiquettes pour nous. Pour instancier ce collateur, nous devons simplement fournir le *tokenizer* et le `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} + +Voyons ce que produit ce collateur lorsqu'on lui donne un petit lot d'exemples. Tout d'abord, nous devons supprimer les colonnes contenant des chaînes de caractères, car le collateur ne saura pas comment remplir ces éléments : + +```python +tokenized_datasets = tokenized_datasets.remove_columns( + books_dataset["train"].column_names +) +``` + +Comme le collateur attend une liste de `dict`s, où chaque `dict` représente un seul exemple dans l'ensemble de données, nous devons également mettre les données dans le format attendu avant de les transmettre au collateur de données : + +```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]])} +``` + +La principale chose à remarquer ici est que le premier exemple est plus long que le second, donc les `input_ids` et `attention_mask` du second exemple ont été complétés sur la droite avec un jeton `[PAD]` (dont l'ID est `0`). De même, nous pouvons voir que les `labels` ont été complétés par des `-100`s, pour s'assurer que les *tokens* de remplissage sont ignorés par la fonction de perte. Et enfin, nous pouvons voir un nouveau `decoder_input_ids` qui a déplacé les étiquettes vers la droite en insérant un jeton `[PAD]` dans la première entrée. + +{#if fw === 'pt'} + +Nous avons enfin tous les ingrédients dont nous avons besoin pour nous entraîner ! Nous devons maintenant simplement instancier le formateur avec les arguments standards : + +```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, +) +``` + +et lancer notre course d'entraînement : + +```python +trainer.train() +``` + +Pendant l'entraînement, vous devriez voir la perte d'entraînement diminuer et les scores ROUGE augmenter à chaque époque. Une fois l'entraînement terminé, vous pouvez voir les scores ROUGE finaux en exécutant `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} +``` + +D'après les scores, nous pouvons voir que notre modèle a largement surpassé notre ligne de base lead-3. Bien ! La dernière chose à faire est de pousser les poids du modèle vers le *Hub*, comme suit : + +``` +trainer.push_to_hub(commit_message="Training complete", tags="summarization") +``` + +```python out +'https://huggingface.co/huggingface-course/mt5-finetuned-amazon-en-es/commit/aa0536b829b28e73e1e4b94b8a5aacec420d40e0' +``` + +Ceci sauvegardera le point de contrôle et les fichiers de configuration dans `output_dir`, avant de télécharger tous les fichiers sur le *Hub*. En spécifiant l'argument `tags`, nous nous assurons également que le widget sur le Hub sera celui d'un pipeline de résumé au lieu de celui de la génération de texte par défaut associé à l'architecture mT5 (pour plus d'informations sur les balises de modèle, voir la [🤗 documentation du *Hub*](https://huggingface.co/docs/hub/main#how-is-a-models-type-of-inference-api-and-widget-determined)). La sortie de `trainer.push_to_hub()` est une URL vers le hash du commit Git, donc vous pouvez facilement voir les changements qui ont été faits au dépôt de modèle ! + +Pour conclure cette section, voyons comment nous pouvons également affiner mT5 en utilisant les fonctionnalités de bas niveau fournies par 🤗 *Accelerate*. + +{:else} + +Nous sommes presque prêts à nous entraîner ! Nous devons juste convertir nos jeux de données en `tf.data.Dataset`s en utilisant le collateur de données que nous avons défini ci-dessus, et ensuite `compile()` et `fit()` le modèle. D'abord, les jeux de données : + +```python +tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=8, +) +tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=8, +) +``` + +Maintenant, nous définissons nos hyperparamètres d'entraînement et nous compilons : + +```python +from transformers import create_optimizer +import tensorflow as tf + +# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans l'ensemble de données, divisé par la taille du batch, puis multiplié par le nombre total d'époques. +# par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un batch tf.data.Dataset, +# et non le jeu de données original Hugging Face Dataset, donc son len() est déjà 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) + +# Entraîner en mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +Et enfin, nous ajustons le modèle. Nous utilisons un `PushToHubCallback` pour sauvegarder le modèle sur le *Hub* après chaque époque, ce qui nous permettra de l'utiliser pour l'inférence plus tard : + +```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 +) +``` + +Nous avons obtenu quelques valeurs de perte pendant l'entraînement, mais nous aimerions vraiment voir les métriques ROUGE que nous avons calculées plus tôt. Pour obtenir ces métriques, nous devons générer les sorties du modèle et les convertir en chaînes de caractères. Construisons quelques listes d'étiquettes et de prédictions pour comparer la métrique ROUGE (notez que si vous obtenez des erreurs d'importation pour cette section, vous pouvez avoir besoin de "pip install tqdm") : + +```python +from tqdm import tqdm +import numpy as np + +all_preds = [] +all_labels = [] +for batch in tqdm(tf_eval_dataset): + predictions = model.generate(**batch) + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + labels = batch["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) +``` + +Une fois que nous avons nos listes d'étiquettes et de chaînes de prédiction, le calcul du score ROUGE est facile : + +```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'} + +## *Finetuning* de mT5 avec 🤗 *Accelerate* + +Le *finetuning* de notre modèle avec 🤗 *Accelerate* est très similaire à l'exemple de classification de texte que nous avons rencontré dans [Chapitre 3](/course/fr/chapter3). Les principales différences seront la nécessité de générer explicitement nos résumés pendant l'Entraînement et de définir comment nous calculons les scores ROUGE (rappelons que le `Seq2SeqTrainer` s'est occupé de la génération pour nous). Voyons comment nous pouvons mettre en œuvre ces deux exigences dans 🤗 *Accelerate* ! + +### Préparer tout pour l'entraînement + +La première chose que nous devons faire est de créer un `DataLoader` pour chacun de nos splits. Puisque les chargeurs de données PyTorch attendent des batchs de tenseurs, nous devons définir le format à `"torch"` dans nos jeux de données : + +```python +tokenized_datasets.set_format("torch") +``` + +Maintenant que nous avons des jeux de données constitués uniquement de tenseurs, la prochaine chose à faire est d'instancier à nouveau le `DataCollatorForSeq2Seq`. Pour cela, nous devons fournir une nouvelle version du modèle, donc chargeons-le à nouveau depuis notre cache : + +```python +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +Nous pouvons ensuite instancier le collateur de données et l'utiliser pour définir nos chargeurs de données : + +```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 +) +``` + +La prochaine chose à faire est de définir l'optimiseur que nous voulons utiliser. Comme dans nos autres exemples, nous allons utiliser `AdamW`, qui fonctionne bien pour la plupart des problèmes : + +```python +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Enfin, nous introduisons notre modèle, notre optimiseur et nos chargeurs de données dans la méthode `accelerator.prepare()` : + +```python +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + + + +🚨 Si vous vous entraînez sur un TPU, vous devrez déplacer tout le code ci-dessus dans une fonction d'entraînement dédiée. Voir le [Chapitre 3](/course/fr/chapter3) pour plus de détails. + + + +Maintenant que nous avons préparé nos objets, il reste trois choses à faire : + +* définir le programme du taux d'apprentissage, +* implémenter une fonction pour post-traiter les résumés pour l'évaluation, +* créer un référentiel sur le *Hub* vers lequel nous pouvons pousser notre modèle. + +Pour le programme de taux d'apprentissage, nous utiliserons le programme linéaire standard des sections précédentes : + +```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, +) +``` + +Pour le post-traitement, nous avons besoin d'une fonction qui divise les résumés générés en phrases séparées par des nouvelles lignes. C'est le format attendu par la métrique ROUGE, et nous pouvons y parvenir avec le bout de code suivant : + +```python +def postprocess_text(preds, labels): + preds = [pred.strip() for pred in preds] + labels = [label.strip() for label in labels] + + # ROUGE attend une nouvelle ligne après chaque phrase + 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 +``` + +Cela devrait vous sembler familier si vous vous rappelez comment nous avons défini la fonction `compute_metrics()` du `Seq2SeqTrainer`. + +Enfin, nous devons créer un dépôt de modèles sur le *Hub*. Pour cela, nous pouvons utiliser la bibliothèque 🤗 *Hub*, qui porte le nom approprié. Nous avons juste besoin de définir un nom pour notre référentiel, et la bibliothèque a une fonction utilitaire pour combiner l'ID du référentiel avec le profil de l'utilisateur : + +```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' +``` + +Nous pouvons maintenant utiliser ce nom de référentiel pour cloner une version locale dans notre répertoire de résultats qui stockera les artefacts d'entraînement : + +```python +from huggingface_hub import Repository + +output_dir = "results-mt5-finetuned-squad-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +This will allow us to push the artifacts back to the Hub by calling the `repo.push_to_hub()` method during training! Let's now wrap up our analysis by writing out the training loop. + +### Boucle d'entraînement + +La boucle d'entraînement pour le résumé est assez similaire aux autres exemples 🤗 *Accelerate* que nous avons rencontrés et est grossièrement divisée en quatre étapes principales : + +1. entraîner le modèle en itérant sur tous les exemples dans `train_dataloader` pour chaque époque, +2. générer les résumés du modèle à la fin de chaque époque, en générant d'abord les *tokens* puis en les décodant (ainsi que les résumés de référence) en texte, +3. calculer les scores ROUGE en utilisant les mêmes techniques que nous avons vues précédemment, +4. sauvegarder les points de contrôle et pousser le tout vers le *Hub*. Ici, nous nous appuyons sur l'argument `blocking=False` de l'objet `Repository` afin de pouvoir pousser les points de contrôle par époque de manière _asynchrone_. Cela nous permet de poursuivre l'entraînement sans avoir à attendre le téléchargement quelque peu lent associé à un modèle de la taille d'un Go ! + +Ces étapes peuvent être vues dans le bloc de code suivant : + +```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): + # Entraînement + model.train() + for step, batch in enumerate(train_dataloader): + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) + + # Evaluation + model.eval() + for step, batch in enumerate(eval_dataloader): + with torch.no_grad(): + generated_tokens = accelerator.unwrap_model(model).generate( + batch["input_ids"], + attention_mask=batch["attention_mask"], + ) + + generated_tokens = accelerator.pad_across_processes( + generated_tokens, dim=1, pad_index=tokenizer.pad_token_id + ) + labels = batch["labels"] + + # Si nous n'avons pas rempli la longueur maximale, nous devons également remplir les étiquettes. + 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() + + # Remplacer -100 dans les étiquettes car nous ne pouvons pas les décoder. + 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) + + # Calculer les métriques + result = rouge_score.compute() + # Extract the median ROUGE scores + result = {key: value.mid.fmeasure * 100 for key, value in result.items()} + result = {k: round(v, 4) for k, v in result.items()} + print(f"Epoch {epoch}:", result) + + # Sauvegarder et télécharger + 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} +``` + +Et c'est tout ! Une fois que vous l'aurez exécuté, vous aurez un modèle et des résultats assez similaires à ceux que nous avons obtenus avec le `Trainer`. + +{/if} + +## Utilisation de votre modèle *finetuné* + +Une fois que vous avez poussé le modèle vers le *Hub*, vous pouvez jouer avec lui soit via le widget d'inférence, soit avec un objet `pipeline`, comme suit : + +```python +from transformers import pipeline + +hub_model_id = "huggingface-course/mt5-small-finetuned-amazon-en-es" +summarizer = pipeline("summarization", model=hub_model_id) +``` + +Nous pouvons alimenter notre pipeline avec quelques exemples de l'ensemble de test (que le modèle n'a pas vu) pour avoir une idée de la qualité des résumés. Tout d'abord, implémentons une fonction simple pour afficher ensemble la critique, le titre et le résumé généré : + +```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}'") +``` + +Examinons l'un des exemples anglais que nous recevons : + +```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.' +# Ce produit n'a rien de spécial... le livre est trop petit et rigide et il est difficile d'y écrire. L'énorme autocollant au dos ne se détache pas et a l'air super collant. Je n'achèterai plus jamais ce produit. J'aurais pu simplement acheter un journal dans un magasin à un dollar et ce serait à peu près la même chose. Il est également très cher pour ce qu'il est. + +'>>> Title: Not impressed at all... buy something else' # Pas du tout impressionné... achetez autre chose. + +'>>> Summary: Nothing special at all about this product' # Rien de spécial à propos de ce produit +``` + +Ce n'est pas si mal ! Nous pouvons voir que notre modèle a été capable d'effectuer un résumé _abstractif_ en augmentant certaines parties de la critique avec de nouveaux mots. Et peut-être que l'aspect le plus cool de notre modèle est qu'il est bilingue, donc nous pouvons également générer des résumés de critiques en espagnol : + +```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' # C'est une trilogie qui se lit très facilement. J'ai aimé, je ne m'attendais pas du tout à la fin. + +'>>> Title: Buena literatura para adolescentes' # Bonne littérature pour les adolescents + +'>>> Summary: Muy facil de leer' # Très facile à lire +``` + +Le résumé se traduit par "Très facile à lire", ce qui, comme nous pouvons le constater, a été extrait directement de la critique. Néanmoins, cela montre la polyvalence du modèle mT5 et vous a donné un aperçu de ce que c'est que de traiter un corpus multilingue ! + +Ensuite, nous allons nous intéresser à une tâche un peu plus complexe : entraîner un modèle de langue à partir de zéro. diff --git a/chapters/fr/chapter7/7.mdx b/chapters/fr/chapter7/7.mdx index e2074354a..e301b1bac 100644 --- a/chapters/fr/chapter7/7.mdx +++ b/chapters/fr/chapter7/7.mdx @@ -1,1228 +1,1228 @@ - - -# Réponse aux questions - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -Il est temps de s'intéresser à la réponse aux questions ! Cette tâche peut prendre plusieurs formes, mais celle sur laquelle nous allons nous concentrer dans cette section est appelée réponse aux questions *extractives*. Il s'agit de poser des questions sur un document et d'identifier les réponses sous forme de "morceaux de texte" dans le document lui-même. - - - -Nous allons affiner un modèle BERT sur le [jeu de données SQuAD](https://rajpurkar.github.io/SQuAD-explorer/), qui consiste en des questions posées par des *crowdworkers* sur un ensemble d'articles de Wikipedia. Cela nous donnera un modèle capable de calculer des prédictions comme celle-ci : - - - - -Il s'agit en fait de la présentation du modèle qui a été entraîné et téléchargé sur le *Hub* à l'aide du code présenté dans cette section. Vous pouvez le trouver et vérifier les prédictions [ici](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) - - - -💡 Les modèles à codeur unique comme BERT ont tendance à être excellents pour extraire les réponses à des questions factuelles comme "Qui a inventé l'architecture Transformer ?", mais ne sont pas très performants lorsqu'on leur pose des questions ouvertes comme "Pourquoi le ciel est-il bleu ?". Dans ces cas plus difficiles, les modèles encodeurs-décodeurs comme T5 et BART sont généralement utilisés pour synthétiser les informations d'une manière assez similaire au [résumé de texte](/cours/fr/chapter7/5). Si vous êtes intéressé par ce type de réponse aux questions *génératives*, nous vous recommandons de consulter notre [démo](https://yjernite.github.io/lfqa.html) basée sur le [jeu de données ELI5](https://huggingface.co/datasets/eli5). - - - -## Préparation des données - -Le jeu de données le plus utilisé comme référence académique pour la réponse extractive aux questions est [SQuAD](https://rajpurkar.github.io/SQuAD-explorer/), c'est donc celui que nous utiliserons ici. Il existe également une référence plus difficile [SQuAD v2](https://huggingface.co/datasets/squad_v2), qui comprend des questions sans réponse. Tant que votre propre jeu de données contient une colonne pour les contextes, une colonne pour les questions et une colonne pour les réponses, vous devriez être en mesure d'adapter les étapes ci-dessous. - -### Le jeu de données SQuAD - -Comme d'habitude, nous pouvons télécharger et mettre en cache le jeu de données en une seule étape grâce à `load_dataset()` : - -```py -from datasets import load_dataset - -raw_datasets = load_dataset("squad") -``` - -Nous pouvons alors jeter un coup d'œil à cet objet pour en savoir plus sur le jeu de données 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 - }) -}) -``` - -On dirait que nous avons tout ce dont nous avons besoin avec les champs `context`, `question` et `answers`, alors imprimons-les pour le premier élément de notre ensemble d'entraînement : - -```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.' -# Sur le plan architectural, l'école a un caractère catholique. Au sommet du dôme doré du bâtiment principal se trouve une statue dorée de la Vierge Marie. Immédiatement devant le bâtiment principal et face à lui, se trouve une statue en cuivre du Christ, les bras levés, avec la légende "Venite Ad Me Omnes". À côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s'agit d'une réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette Soubirous en 1858. Au bout de l'allée principale (et dans une ligne directe qui passe par 3 statues et le Dôme d'or), se trouve une statue de pierre simple et moderne de Marie'. -Question: 'To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France?' # A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes, en France ? -Answer: {'text': ['Saint Bernadette Soubirous'], 'answer_start': [515]} -``` - -Les champs `context` et `question` sont très simples à utiliser. Le champ `answers` est un peu plus délicat car il compile un dictionnaire avec deux champs qui sont tous deux des listes. C'est le format qui sera attendu par la métrique `squad` lors de l'évaluation ; si vous utilisez vos propres données, vous n'avez pas nécessairement besoin de vous soucier de mettre les réponses dans le même format. Le champ `text` est assez évident, et le champ `answer_start` contient l'indice du caractère de départ de chaque réponse dans le contexte. - -Pendant l'entraînement, il n'y a qu'une seule réponse possible. Nous pouvons vérifier cela en utilisant la méthode `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 -}) -``` - -Pour l'évaluation, cependant, il existe plusieurs réponses possibles pour chaque échantillon, qui peuvent être identiques ou différentes : - -```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]} -``` - -Nous ne nous plongerons pas dans le script d'évaluation car tout sera enveloppé par une métrique 🤗 *Datasets* pour nous, mais la version courte est que certaines des questions ont plusieurs réponses possibles, et ce script va comparer une réponse prédite à toutes les réponses acceptables et prendre le meilleur score. Si nous regardons l'échantillon de l'indice 2, par exemple : - -```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.' -# Le Super Bowl 50 était un match de football américain visant à déterminer le champion de la National Football League (NFL) pour la saison 2015. Les Denver Broncos, champions de la Conférence de football américain (AFC), ont battu les Carolina Panthers, champions de la Conférence nationale de football (NFC), 24 à 10, pour remporter leur troisième titre de Super Bowl. Le match s'est déroulé le 7 février 2016 au Levi\'s Stadium, dans la baie de San Francisco, à Santa Clara, en Californie. Comme il s'agissait du 50e Super Bowl, la ligue a mis l'accent sur l'" anniversaire doré " avec diverses initiatives sur le thème de l'or, ainsi qu'en suspendant temporairement la tradition de nommer chaque match du Super Bowl avec des chiffres romains (en vertu de laquelle le match aurait été appelé " Super Bowl L "), afin que le logo puisse mettre en évidence les chiffres arabes 50.'' -'Where did Super Bowl 50 take place?' # Où a eu lieu le Super Bowl 50 ? -``` - -nous pouvons voir que la réponse peut effectivement être l'une des trois possibilités que nous avons vues précédemment. - -### Traitement des données d'entraînement - - - -Commençons par le prétraitement des données d'entraînement. La partie la plus difficile sera de générer des étiquettes pour la réponse à la question, qui seront les positions de début et de fin des *tokens* correspondant à la réponse dans le contexte. - -Mais ne nous emballons pas. Tout d'abord, nous devons convertir le texte de l'entrée en identifiants que le modèle peut comprendre, en utilisant un *tokenizer* : - -```py -from transformers import AutoTokenizer - -model_checkpoint = "bert-base-cased" -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) -``` - -Comme mentionné précédemment, nous allons *finetuner* un modèle BERT, mais vous pouvez utiliser n'importe quel autre type de modèle tant qu'il a un *tokenizer* rapide implémenté. Vous pouvez voir toutes les architectures qui sont livrées avec une version rapide dans [ce grand tableau](https://huggingface.co/transformers/#supported-frameworks), et pour vérifier que l'objet `tokenizer` que vous utilisez est bien soutenu par des 🤗 *Tokenizers* vous pouvez regarder son attribut `is_fast` : - -```py -tokenizer.is_fast -``` - -```python out -True -``` - -Nous pouvons transmettre à notre *tokenizer* la question et le contexte ensemble, et il insérera correctement les *tokens* spéciaux pour former une phrase comme celle-ci : - -``` -[CLS] question [SEP] context [SEP] -``` - -Vérifions à nouveau : - -```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]' - -'[CLS] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] Architecturalement, ' -l'école a un caractère catholique. Au sommet du dôme doré du bâtiment principal se trouve une statue dorée de la Vierge ' -Marie. Immédiatement devant le bâtiment principal et face à lui, se trouve une statue en cuivre du Christ, les bras ''levés''. -'levés avec la légende " Venite Ad Me Omnes ". A côté du bâtiment principal se trouve la basilique du Sacré-Cœur. -'Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s'agit d'une ' -'réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette ' -Soubirous en 1858. Au bout de l'allée principale ( et en ligne directe qui passe par 3 statues ' -'et le Dôme d'or), se trouve une statue de Marie en pierre, simple et moderne. [SEP]'' -``` - -Les étiquettes seront alors l'index des *tokens* de début et de fin de la réponse, et le modèle sera chargé de prédire un logit de début et de fin par *token* dans l'entrée, les étiquettes théoriques étant les suivantes : - -
-One-hot encoded labels for question answering. - -
- -Dans ce cas, le contexte n'est pas trop long, mais certains des exemples de l'ensemble de données ont des contextes très longs qui dépasseront la longueur maximale que nous avons fixée (qui est de 384 dans ce cas). Comme nous l'avons vu dans le [Chapitre 6](/course/fr/chapter6/4) lorsque nous avons exploré les internes du pipeline `question-answering`, nous allons traiter les contextes longs en créant plusieurs caractéristiques d'entraînement à partir d'un échantillon de notre jeu de données, avec une fenêtre glissante entre eux. - -Pour voir comment cela fonctionne en utilisant l'exemple actuel, nous pouvons limiter la longueur à 100 et utiliser une fenêtre glissante de 50 *tokens*. Pour rappel, nous utilisons - -- `max_length` pour définir la longueur maximale (ici 100) -- `truncation="only_second"` pour tronquer le contexte (qui est en deuxième position) quand la question avec son contexte est trop longue -- `stride` pour fixer le nombre de *tokens* se chevauchant entre deux morceaux successifs (ici 50) -- `return_overflowing_tokens=True` pour indiquer au tokenizer que l'on veut les *tokens* qui débordent - -```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] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] Sur le plan architectural, l'école a un caractère catholique. Au sommet du dôme doré du bâtiment principal se trouve une statue dorée de la Vierge Marie. Immédiatement devant le bâtiment principal et face à lui, se trouve une statue en cuivre du Christ, les bras levés, avec la légende " Venite Ad Me Omnes ". À côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la 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] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [le bâtiment principal et face à lui, une statue en cuivre du Christ aux bras levés avec la légende " Venite Ad Me Omnes ". À côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s'agit d'une réplique de la grotte de Lourdes, en France, où la Vierge [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] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [A côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s'agit d'une réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette Soubirous en 1858. Au bout de l'allée principale ( et dans une ligne directe qui relie par 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]' -'[CLS] A qui la Vierge Marie est-elle prétendument apparue en 1858 à Lourdes France ? [SEP]. Il s'agit d'une réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette Soubirous en 1858. Au bout de l'allée principale (et dans une ligne directe qui passe par 3 statues et le Dôme d'or), se trouve une simple statue de pierre moderne de Marie. [SEP]' -``` - -Comme nous pouvons le voir, notre exemple a été divisé en quatre entrées, chacune d'entre elles contenant la question et une partie du contexte. Notez que la réponse à la question ("Bernadette Soubirous") n'apparaît que dans la troisième et dernière entrée, donc en traitant les longs contextes de cette façon, nous allons créer quelques exemples d'entraînement où la réponse n'est pas incluse dans le contexte. Pour ces exemples, les étiquettes seront `start_position = end_position = 0` (donc nous prédisons le *token* `[CLS]`). Nous définirons également ces étiquettes dans le cas malheureux où la réponse a été tronquée de sorte que nous n'avons que le début (ou la fin) de celle-ci. Pour les exemples où la réponse est entièrement dans le contexte, les étiquettes seront l'index du *token* où la réponse commence et l'index du *token* où la réponse se termine. - -L'ensemble de données nous fournit le caractère de début de la réponse dans le contexte, et en ajoutant la longueur de la réponse, nous pouvons trouver le caractère de fin dans le contexte. Pour faire correspondre ces indices aux *tokens*, nous devrons utiliser les mappages d'offset que nous avons étudiés au [Chapitre 6](/course/chapter6/4). Nous pouvons faire en sorte que notre *tokenizer* renvoie ces index en passant `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']) -``` - -Comme nous pouvons le voir, nous récupérons les habituels ID d'entrée, ID de type de jeton, et masque d'attention, ainsi que le mappage d'offset dont nous avions besoin et une clé supplémentaire, `overflow_to_sample_mapping`. La valeur correspondante nous sera utile lorsque nous tokeniserons plusieurs textes en même temps (ce que nous devrions faire pour bénéficier du fait que notre *tokenizer* est soutenu par Rust). Puisqu'un échantillon peut donner plusieurs caractéristiques, il fait correspondre chaque caractéristique à l'exemple d'où elle provient. Parce qu'ici nous avons seulement tokenisé un exemple, nous obtenons une liste de `0`s : - -```py -inputs["overflow_to_sample_mapping"] -``` - -```python out -[0, 0, 0, 0] -``` - -Mais si nous tokenisons plus d'exemples, cela deviendra plus utile : - -```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].' -``` - -Comme nous pouvons le voir, les trois premiers exemples (aux indices 2, 3 et 4 de l'ensemble d'entraînement) ont chacun donné quatre caractéristiques et le dernier exemple (à l'indice 5 de l'ensemble d'entraînement) a donné 7 caractéristiques. - -Ces informations seront utiles pour associer chaque caractéristique obtenue à son étiquette correspondante. Comme mentionné précédemment, ces étiquettes sont : - -- `(0, 0)` si la réponse n'est pas dans l'espace correspondant du contexte. -- `(start_position, end_position)` si la réponse est dans l'espace correspondant du contexte, avec `start_position` étant l'index du *token* (dans les IDs d'entrée) au début de la réponse et `end_position` étant l'index du *token* (dans les IDs d'entrée) où la réponse se termine. - -Pour déterminer ce qui est le cas et, le cas échéant, les positions des *tokens*, nous trouvons d'abord les indices qui commencent et finissent le contexte dans les IDs d'entrée. Nous pourrions utiliser les IDs du type de *token* pour le faire, mais puisque ceux-ci n'existent pas nécessairement pour tous les modèles (DistilBERT ne les requiert pas, par exemple), nous allons plutôt utiliser la méthode `sequence_ids()` du `BatchEncoding` que notre tokenizer retourne. - -Une fois que nous avons ces indices de *tokens*, nous regardons les offsets correspondants, qui sont des tuples de deux entiers représentant l'étendue des caractères dans le contexte original. Nous pouvons ainsi détecter si le *chunk* du contexte dans cette fonctionnalité commence après la réponse ou se termine avant que la réponse ne commence (dans ce cas, l'étiquette est `(0, 0)`). Si ce n'est pas le cas, nous bouclons pour trouver le premier et le dernier *token* de la réponse : - -```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) - - # Trouver le début et la fin du contexte - idx = 0 - while sequence_ids[idx] != 1: - idx += 1 - context_start = idx - while sequence_ids[idx] == 1: - idx += 1 - context_end = idx - 1 - - # Si la réponse n'est pas entièrement dans le contexte, l'étiquette est (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: - # Otherwise it's the start and end token positions - 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]) -``` - -Jetons un coup d'œil à quelques résultats pour vérifier que notre approche est correcte. Pour la première caractéristique, nous trouvons `(83, 85)` comme étiquettes, alors comparons la réponse théorique avec l'étendue décodée des *tokens* de 83 à 85 (inclus) : - -```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' -``` - -Donc, c'est une correspondance ! Maintenant, vérifions l'index 4, où nous avons mis les étiquettes à `(0, 0)`, ce qui signifie que la réponse n'est pas dans le *chunk* de contexte de cette caractéristique : - -```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]' -``` - -En effet, nous ne voyons pas la réponse dans le contexte. - - - -✏️ **A votre tour !** En utilisant l'architecture XLNet, le *padding* est appliqué à gauche et la question et le contexte sont intervertis. Adaptez tout le code que nous venons de voir à l'architecture XLNet (et ajoutez `padding=True`). Soyez conscient que le token `[CLS]` peut ne pas être à la position 0 avec le *padding* appliqué. - - - -Maintenant que nous avons vu étape par étape comment prétraiter nos données d'entraînement, nous pouvons les regrouper dans une fonction que nous appliquerons à l'ensemble des données d'entraînement. Nous allons rembourrer chaque caractéristique à la longueur maximale que nous avons définie, car la plupart des contextes seront longs (et les échantillons correspondants seront divisés en plusieurs caractéristiques), il n'y a donc pas de réel avantage à appliquer un rembourrage dynamique ici : - -```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) - - # Trouver le début et la fin du contexte - idx = 0 - while sequence_ids[idx] != 1: - idx += 1 - context_start = idx - while sequence_ids[idx] == 1: - idx += 1 - context_end = idx - 1 - - # Si la réponse n'est pas entièrement dans le contexte, l'étiquette est (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: - # Otherwise it's the start and end token positions - 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 -``` - -Notez que nous avons défini deux constantes pour déterminer la longueur maximale utilisée ainsi que la longueur de la fenêtre glissante, et que nous avons ajouté un petit nettoyage avant la tokénisation : certaines des questions dans le jeu de données SQuAD ont des espaces supplémentaires au début et à la fin qui n'ajoutent rien (et prennent de la place lors de la tokénisation si vous utilisez un modèle comme RoBERTa), donc nous avons supprimé ces espaces supplémentaires. - -Pour appliquer cette fonction à l'ensemble de l'entraînement, nous utilisons la méthode `Dataset.map()` avec le flag `batched=True`. C'est nécessaire ici car nous changeons la longueur de l'ensemble de données (puisqu'un exemple peut donner plusieurs caractéristiques d'entraînement) : - -```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) -``` - -Comme nous pouvons le voir, le prétraitement a ajouté environ 1 000 caractéristiques. Notre ensemble d'entraînement est maintenant prêt à être utilisé - passons au prétraitement de l'ensemble de validation ! - -### Traitement des données de validation - -Le prétraitement des données de validation sera légèrement plus facile car nous n'avons pas besoin de générer des étiquettes (sauf si nous voulons calculer une perte de validation, mais ce nombre ne nous aidera pas vraiment à comprendre la qualité du modèle). La vraie joie sera d'interpréter les prédictions du modèle dans des étendues du contexte original. Pour cela, il nous suffit de stocker les mappages de décalage et un moyen de faire correspondre chaque caractéristique créée à l'exemple original dont elle provient. Puisqu'il y a une colonne ID dans l'ensemble de données original, nous utiliserons cet ID. - -La seule chose que nous allons ajouter ici est un petit nettoyage des mappages de décalage. Ils contiendront les offsets pour la question et le contexte, mais une fois que nous serons dans la phase de post-traitement, nous n'aurons aucun moyen de savoir quelle partie des IDs d'entrée correspondait au contexte et quelle partie était la question (la méthode `sequence_ids()` que nous avons utilisée n'est disponible que pour la sortie du *tokenizer*). Donc, nous allons mettre les offsets correspondant à la question à `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 -``` - -Nous pouvons appliquer cette fonction sur l'ensemble des données de validation comme précédemment : - -```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) -``` - -Dans ce cas, nous n'avons ajouté que quelques centaines d'échantillons, il semble donc que les contextes dans l'ensemble de données de validation soient un peu plus courts. - -Maintenant que nous avons prétraité toutes les données, nous pouvons passer à l'entraînement. - -{#if fw === 'pt'} - -## *Finetuner* le modèle avec l'API `Trainer` - -Le code d'entraînement pour cet exemple ressemblera beaucoup au code des sections précédentes -- la chose la plus difficile sera d'écrire la fonction `compute_metrics()`. Puisque nous avons capitonné tous les échantillons à la longueur maximale que nous avons fixée, il n'y a pas de collateur de données à définir, donc ce calcul de métrique est vraiment la seule chose dont nous devons nous soucier. La partie la plus difficile sera de post-traiter les prédictions du modèle en travées de texte dans les exemples originaux ; une fois que nous aurons fait cela, la métrique de la bibliothèque 🤗 *Datasets* fera le gros du travail pour nous. - -{:else} - -## *Finetuner* fin du modèle avec Keras - -Le code d'entraînement de cet exemple ressemblera beaucoup au code des sections précédentes, mais le calcul des métriques sera un défi unique. Puisque nous avons capitonné tous les échantillons à la longueur maximale que nous avons définie, il n'y a pas de collateur de données à définir, donc le calcul de la métrique est vraiment la seule chose dont nous devons nous soucier. La partie la plus difficile sera de post-traiter les prédictions du modèle en travées de texte dans les exemples originaux ; une fois que nous aurons fait cela, la métrique de la bibliothèque 🤗 *Datasets* fera le gros du travail pour nous. - -{/if} - -### Post-traitement - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -Le modèle produira des logits pour les positions de début et de fin de la réponse dans les IDs d'entrée, comme nous l'avons vu lors de notre exploration du [`question-answering` pipeline](/course/chapter6/4). L'étape de post-traitement sera similaire à ce que nous avons fait là-bas, donc voici un rappel rapide des actions que nous avons prises : - -- nous avons masqué les logits de début et de fin correspondant aux *tokens* en dehors du contexte, -- nous avons ensuite converti les logits de début et de fin en probabilités en utilisant un softmax, -- nous avons attribué un score à chaque paire `(start_token, end_token)` en prenant le produit des deux probabilités correspondantes, -- nous avons cherché la paire avec le score maximum qui donnait une réponse valide (par exemple, un `start_token` inférieur au `end_token`). - -Ici, nous allons modifier légèrement ce processus car nous n'avons pas besoin de calculer les scores réels (juste la réponse prédite). Cela signifie que nous pouvons sauter l'étape du softmax. Pour aller plus vite, nous ne noterons pas non plus toutes les paires `(start_token, end_token)` possibles, mais seulement celles correspondant aux logits `n_best` les plus élevés (avec `n_best=20`). Puisque nous sauterons le softmax, ces scores seront des scores logit, et seront obtenus en prenant la somme des logits de début et de fin (au lieu du produit, à cause de la règle \\(\log(ab) = \log(a) + \log(b)\)). - -Pour démontrer tout cela, nous aurons besoin d'un certain type de prédictions. Puisque nous n'avons pas encore entraîné notre modèle, nous allons utiliser le modèle par défaut du pipeline d'assurance qualité pour générer quelques prédictions sur une petite partie de l'ensemble de validation. Nous pouvons utiliser la même fonction de traitement que précédemment ; parce qu'elle repose sur la constante globale `tokenizer`, nous devons juste changer cet objet pour le tokenizer du modèle que nous voulons utiliser temporairement : - -```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, -) -``` - -Maintenant que le prétraitement est terminé, nous changeons le *tokenizer* pour celui que nous avons choisi à l'origine : - -```python -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) -``` - -Nous supprimons ensuite les colonnes de notre `eval_set` qui ne sont pas attendues par le modèle, nous construisons un lot avec l'ensemble de ce petit ensemble de validation, et nous le passons au modèle. Si un GPU est disponible, nous l'utilisons pour aller plus vite : - -{#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) -``` - -Puisque le `Trainer` nous donnera les prédictions sous forme de tableaux NumPy, nous récupérons les logits de début et de fin et les convertissons dans ce format : - -```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) -``` - -Pour faciliter l'expérimentation, nous allons convertir ces sorties en tableaux NumPy : - -```python -start_logits = outputs.start_logits.numpy() -end_logits = outputs.end_logits.numpy() -``` - -{/if} - -Maintenant, nous devons trouver la réponse prédite pour chaque exemple dans notre `small_eval_set`. Un exemple peut avoir été divisé en plusieurs caractéristiques dans `eval_set`, donc la première étape est de faire correspondre chaque exemple dans `small_eval_set` aux caractéristiques correspondantes dans `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) -``` - -Avec cela en main, nous pouvons vraiment nous mettre au travail en parcourant en boucle tous les exemples et, pour chaque exemple, toutes les caractéristiques associées. Comme nous l'avons dit précédemment, nous allons regarder les scores logit pour les `n_meilleurs` logits de début et logits de fin, en excluant les positions qui donnent : - -- une réponse qui ne serait pas dans le contexte. -- une réponse avec une longueur négative -- une réponse qui est trop longue (nous limitons les possibilités à `max_answer_length=30`) - -Une fois que nous avons toutes les réponses possibles notées pour un exemple, nous choisissons simplement celle qui a le meilleur score logit : - -```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: - # Ignore les réponses qui ne sont pas entièrement dans le contexte - if offsets[start_index] is None or offsets[end_index] is None: - continue - # Ignorer les réponses dont la longueur est soit < 0 soit > 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"]}) -``` - -Le format final des réponses prédites est celui qui sera attendu par la métrique que nous allons utiliser. Comme d'habitude, nous pouvons le charger à l'aide de la bibliothèque 🤗 *Datasets* : - -```python -from datasets import load_metric - -metric = load_metric("squad") -``` - -Cette métrique attend les réponses prédites dans le format que nous avons vu ci-dessus (une liste de dictionnaires avec une clé pour l'ID de l'exemple et une clé pour le texte prédit) et les réponses théoriques dans le format ci-dessous (une liste de dictionnaires avec une clé pour l'ID de l'exemple et une clé pour les réponses possibles) : - -```python -theoretical_answers = [ - {"id": ex["id"], "answers": ex["answers"]} for ex in small_eval_set -] -``` - -Nous pouvons maintenant vérifier que nous obtenons des résultats raisonnables en examinant le premier élément des deux listes : - -```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]}} -``` - -Pas trop mal ! Voyons maintenant le score que la métrique nous donne : - -```python -metric.compute(predictions=predicted_answers, references=theoretical_answers) -``` - -```python out -{'exact_match': 83.0, 'f1': 88.25} -``` - -Encore une fois, c'est plutôt bon si l'on considère que, selon [son article](https://arxiv.org/abs/1910.01108v2), DistilBERT *finetuné* sur SQuAD obtient 79,1 et 86,9 pour ces scores sur l'ensemble des données. - -{#if fw === 'pt'} - -Maintenant, mettons tout ce que nous venons de faire dans une fonction `compute_metrics()` que nous utiliserons dans le `Trainer`. Normalement, cette fonction `compute_metrics()` reçoit seulement un tuple `eval_preds` avec les logits et les labels. Ici, nous aurons besoin d'un peu plus, car nous devons chercher dans le jeu de données des caractéristiques pour le décalage et dans le jeu de données des exemples pour les contextes originaux, donc nous ne serons pas en mesure d'utiliser cette fonction pour obtenir des résultats d'évaluation réguliers pendant l'entraînement. Nous ne l'utiliserons qu'à la fin de l'entraînement pour vérifier les résultats. - -La fonction `compute_metrics()` regroupe les mêmes étapes que précédemment ; nous ajoutons juste une petite vérification au cas où nous ne trouverions aucune réponse valide (dans ce cas nous prédisons une chaîne vide). - -{:else} - -Maintenant, mettons tout ce que nous venons de faire dans une fonction `compute_metrics()` que nous utiliserons après avoir entraîné notre modèle. Nous aurons besoin de passer un peu plus que juste les logits de sortie, car nous devons chercher dans le jeu de données des caractéristiques pour le décalage et dans le jeu de données des exemples pour les contextes originaux : - -{/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 = [] - - # Parcourir en boucle toutes les fonctionnalités associées à cet exemple - 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: - # Ignore les réponses qui ne sont pas entièrement dans le contexte - if offsets[start_index] is None or offsets[end_index] is None: - continue - # Ignore les réponses dont la longueur est soit < 0, soit > 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) - - # Sélectionne la réponse avec le meilleur score - 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) -``` - -Nous pouvons vérifier que cela fonctionne sur nos prédictions : - -```python -compute_metrics(start_logits, end_logits, eval_set, small_eval_set) -``` - -```python out -{'exact_match': 83.0, 'f1': 88.25} -``` - -C'est bien ! Maintenant, utilisons ceci pour affiner notre modèle. - -### *Finetuning* du modèle - -{#if fw === 'pt'} - -Nous sommes maintenant prêts à entraîner notre modèle. Créons-le d'abord, en utilisant la classe `AutoModelForQuestionAnswering` comme précédemment : - -```python -model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) -``` - -{:else} - -Nous sommes maintenant prêts à entraîner notre modèle. Créons-le d'abord, en utilisant la classe `TFAutoModelForQuestionAnswering` comme précédemment : - -```python -model = TFAutoModelForQuestionAnswering.from_pretrained(model_checkpoint) -``` - -{/if} - -Comme d'habitude, nous recevons un avertissement indiquant que certains poids ne sont pas utilisés (ceux de la tête de pré-entraînement) et que d'autres sont initialisés de manière aléatoire (ceux de la tête de réponse aux questions). Vous devriez être habitué à cela maintenant, mais cela signifie que ce modèle n'est pas encore prêt à être utilisé et qu'il a besoin d'être *finetuné*. Une bonne chose que nous soyons sur le point de le faire ! - -Pour pouvoir pousser notre modèle vers le *Hub*, nous devons nous connecter à Hugging Face. Si vous exécutez ce code dans un *notebook*, vous pouvez le faire avec la fonction utilitaire suivante, qui affiche un widget où vous pouvez entrer vos identifiants de connexion : - -```python -from huggingface_hub import notebook_login - -notebook_login() -``` - -Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : - -```bash -huggingface-cli login -``` - -{#if fw === 'pt'} - -Une fois ceci fait, nous pouvons définir nos `TrainingArguments`. Comme nous l'avons dit lorsque nous avons défini notre fonction pour calculer la métrique, nous ne serons pas en mesure d'avoir une boucle d'évaluation régulière à cause de la signature de la fonction `compute_metrics()`. Nous pourrions écrire notre propre sous-classe de `Trainer` pour faire cela (une approche que vous pouvez trouver dans le [script d'exemple de réponse aux questions](https://github.com/huggingface/transformers/blob/master/examples/pytorch/question-answering/trainer_qa.py)), mais c'est un peu trop long pour cette section. A la place, nous n'évaluerons le modèle qu'à la fin de l'entraînement et nous vous montrerons comment faire une évaluation régulière dans "Une boucle d'entraînement personnalisée" ci-dessous. - -C'est vraiment là que l'API `Trainer` montre ses limites et que la bibliothèque 🤗 *Accelerate* brille : personnaliser la classe pour un cas d'utilisation spécifique peut être pénible, mais modifier une boucle d'entraînement entièrement exposée est facile. - -Jetons un coup d'œil à notre `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, -) -``` - -Nous avons déjà vu la plupart d'entre eux : nous définissons quelques hyperparamètres (comme le taux d'apprentissage, le nombre d'époques pour lesquelles nous nous entraînons, et une certaine décroissance de poids) et nous indiquons que nous voulons sauvegarder le modèle à la fin de chaque époque, sauter l'évaluation, et télécharger nos résultats vers le Model Hub. Nous activons également l'entraînement en précision mixte avec `fp16=True`, car cela peut accélérer l'entraînement sur un GPU récent. - -{:else} - -Maintenant que c'est fait, nous pouvons créer nos ensembles de données TF. Nous pouvons utiliser le simple collateur de données par défaut cette fois-ci : - -```python -from transformers import DefaultDataCollator - -data_collator = DefaultDataCollator(return_tensors="tf") -``` - -Et maintenant nous créons les jeux de données comme d'habitude. - -```python -tf_train_dataset = train_dataset.to_tf_dataset( - columns=[ - "input_ids", - "start_positions", - "end_positions", - "attention_mask", - "token_type_ids", - ], - collate_fn=data_collator, - shuffle=True, - batch_size=16, -) -tf_eval_dataset = validation_dataset.to_tf_dataset( - columns=["input_ids", "attention_mask", "token_type_ids"], - collate_fn=data_collator, - shuffle=False, - batch_size=16, -) -``` - -Ensuite, nous configurons nos hyperparamètres d'entraînement et compilons notre modèle : - -```python -from transformers import create_optimizer -from transformers.keras_callbacks import PushToHubCallback -import tensorflow as tf - -# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans le jeu de données, divisé par la taille du batch, puis multiplié par le nombre total d'époques. -# par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un lot tf.data.Dataset, -# et non le jeu de données original Hugging Face Dataset, donc son len() est déjà 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) - -# Entraîner en mixed-precision float16 -tf.keras.mixed_precision.set_global_policy("mixed_float16") -``` - -Enfin, nous sommes prêts à nous entraîner avec `model.fit()`. Nous utilisons un `PushToHubCallback` pour télécharger le modèle sur le *Hub* après chaque époque. - -{/if} - -Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, donc dans notre cas il sera dans `"sgugger/bert-finetuned-squad"`. Nous pouvons passer outre en passant un `hub_model_id` ; par exemple, pour pousser le modèle dans l'organisation `huggingface_course` nous avons utilisé `hub_model_id= "huggingface_course/bert-finetuned-squad"` (qui est le modèle que nous avons lié au début de cette section). - -{#if fw === 'pt'} - - - -💡 Si le répertoire de sortie que vous utilisez existe, il doit être un clone local du dépôt vers lequel vous voulez pousser (donc définissez un nouveau nom si vous obtenez une erreur lors de la définition de votre `Trainer`). - - - -Enfin, nous passons tout à la classe `Trainer` et lançons l'entraînement : - -```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) - -# Nous allons faire la validation après, donc pas de validation au milieu de l'entraînement. -model.fit(tf_train_dataset, callbacks=[callback], epochs=num_train_epochs) -``` - -{/if} - -Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le Hub en arrière-plan. Ainsi, vous pourrez reprendre votre entraînement sur une autre machine si nécessaire. L'ensemble de l'entraînement prend un certain temps (un peu plus d'une heure sur une Titan RTX), vous pouvez donc prendre un café ou relire les parties du cours qui vous ont semblé plus difficiles pendant qu'il se déroule. Notez également que dès que la première époque est terminée, vous verrez des poids téléchargés sur le Hub et vous pourrez commencer à jouer avec votre modèle sur sa page. - -{#if fw === 'pt'} - -Une fois l'entraînement terminé, nous pouvons enfin évaluer notre modèle (et prier pour ne pas avoir dépensé tout ce temps de calcul pour rien). La méthode `predict()` du `Trainer` retournera un tuple où les premiers éléments seront les prédictions du modèle (ici une paire avec les logits de début et de fin). Nous envoyons ceci à notre fonction `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} - -Une fois l'entraînement terminé, nous pouvons enfin évaluer notre modèle (et prier pour ne pas avoir dépensé tout ce temps de calcul pour rien). La méthode `predict()` de notre `model` se chargera d'obtenir les prédictions, et puisque nous avons fait tout le travail difficile de définir une fonction `compute_metrics()` plus tôt, nous pouvons obtenir nos résultats en une seule ligne : - -```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} -``` - -Super ! À titre de comparaison, les scores de base indiqués dans l'article du BERT pour ce modèle sont de 80,8 et 88,5, donc nous sommes exactement là où nous devrions être. - -{#if fw === 'pt'} - -Enfin, nous utilisons la méthode `push_to_hub()` pour nous assurer que nous téléchargeons la dernière version du modèle : - -```py -trainer.push_to_hub(commit_message="Training complete") -``` - -Cela renvoie l'URL du commit qu'il vient de faire, si vous voulez l'inspecter : - -```python out -'https://huggingface.co/sgugger/bert-finetuned-squad/commit/9dcee1fbc25946a6ed4bb32efb1bd71d5fa90b68' -``` - -Le `Trainer` rédige également une fiche modèle avec tous les résultats de l'évaluation et la télécharge. - -{/if} - -À ce stade, vous pouvez utiliser le widget d'inférence sur le *Hub* du modèle pour tester le modèle et le partager avec vos amis, votre famille et vos animaux préférés. Vous avez réussi à *finetuner* un modèle sur une tâche de réponse à une question - félicitations ! - - - -✏️ **Votre tour** Essayez un autre modèle d'architecture pour voir s'il est plus performant dans cette tâche ! - - - -{#if fw === 'pt'} - -Si vous voulez plonger un peu plus profondément dans la boucle d'entraînement, nous allons maintenant vous montrer comment faire la même chose en utilisant 🤗 *Accelerate*. - -## Une boucle d'entraînement personnalisée - -Jetons maintenant un coup d'œil à la boucle d'entraînement complète, afin que vous puissiez facilement personnaliser les parties dont vous avez besoin. Elle ressemblera beaucoup à la boucle d'entraînement du [Chapitre 3](/course/fr/chapter3/4), à l'exception de la boucle d'évaluation. Nous serons en mesure d'évaluer le modèle régulièrement puisque nous ne sommes plus contraints par la classe `Trainer`. - -### Préparer tout pour l'entraînement - -Tout d'abord, nous devons construire le `DataLoader`s à partir de nos jeux de données. Nous définissons le format de ces jeux de données à `"torch"`, et supprimons les colonnes dans le jeu de validation qui ne sont pas utilisées par le modèle. Ensuite, nous pouvons utiliser le `default_data_collator` fourni par Transformers comme `collate_fn` et mélanger l'ensemble d'entraînement, mais pas l'ensemble de validation : - -```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 -) -``` - -Ensuite, nous réinstantifions notre modèle, afin de nous assurer que nous ne poursuivons pas les réglages fins précédents mais que nous repartons du modèle pré-entraîné de BERT : - -```py -model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) -``` - -Ensuite, nous aurons besoin d'un optimiseur. Comme d'habitude, nous utilisons le classique `AdamW`, qui est comme Adam, mais avec une correction dans la façon dont la décroissance du poids est appliquée : - -```py -from torch.optim import AdamW - -optimizer = AdamW(model.parameters(), lr=2e-5) -``` - -Une fois que nous avons tous ces objets, nous pouvons les envoyer à la méthode `accelerator.prepare()`. Rappelez-vous que si vous voulez vous entraîner sur des TPUs dans un *notebook* de Colab, vous devrez déplacer tout ce code dans une fonction d'entraînement, et qui ne devrait pas exécuter une cellule qui instancie un `Accelerator`. Nous pouvons forcer l'entraînement en précision mixte en passant `fp16=True` à l'`Accelerator` (ou, si vous exécutez le code comme un script, assurez-vous de remplir la 🤗 *Accelerate* `config` de manière appropriée). - -```py -from accelerate import Accelerator - -accelerator = Accelerator(fp16=True) -model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( - model, optimizer, train_dataloader, eval_dataloader -) -``` - -Comme vous devez le savoir depuis les sections précédentes, nous ne pouvons utiliser la longueur de `train_dataloader` pour calculer le nombre d'étapes d'entraînement qu'après qu'il soit passé par la méthode `accelerator.prepare()`. Nous utilisons le même programme linéaire que dans les sections précédentes : - -```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, -) -``` - -Pour pousser notre modèle vers le Hub, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous au Hugging Face Hub, si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'ID du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix ; il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `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' -``` - -Ensuite, nous pouvons cloner ce référentiel dans un dossier local. S'il existe déjà, ce dossier local doit être un clone du référentiel avec lequel nous travaillons : - -```py -output_dir = "bert-finetuned-squad-accelerate" -repo = Repository(output_dir, clone_from=repo_name) -``` - -Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output_dir` en appelant la méthode `repo.push_to_hub()`. Cela nous aidera à télécharger les modèles intermédiaires à la fin de chaque époque. - -## Boucle d'entraînement - -Nous sommes maintenant prêts à écrire la boucle d'entraînement complète. Après avoir défini une barre de progression pour suivre l'évolution de l'entraînement, la boucle comporte trois parties : - -- l'entraînement proprement dit, qui est l'itération classique sur le `train_dataloader`, passage en avant du modèle, puis passage en arrière et étape d'optimisation. -- l'évaluation, dans laquelle nous rassemblons toutes les valeurs pour `start_logits` et `end_logits` avant de les convertir en tableaux NumPy. Une fois la boucle d'évaluation terminée, nous concaténons tous les résultats. Notez que nous devons tronquer parce que l' `Accelerator` peut avoir ajouté quelques échantillons à la fin pour s'assurer que nous avons le même nombre d'exemples dans chaque processus. -- sauvegarde et téléchargement, où nous sauvegardons d'abord le modèle et le *tokenizer*, puis appelons `repo.push_to_hub()`. Comme nous l'avons fait auparavant, nous utilisons l'argument `blocking=False` pour dire à la bibliothèque 🤗 *Hub* de pousser dans un processus asynchrone. De cette façon, l'entraînement continue normalement et cette (longue) instruction est exécutée en arrière-plan. - -Voici le code complet de la boucle d'entraînement : - -```py -from tqdm.auto import tqdm -import torch - -progress_bar = tqdm(range(num_training_steps)) - -for epoch in range(num_train_epochs): - # Entraînement - model.train() - for step, batch in enumerate(train_dataloader): - outputs = model(**batch) - loss = outputs.loss - accelerator.backward(loss) - - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - progress_bar.update(1) - - # Evaluation - model.eval() - 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) - - # Sauvegarder et télécharger - 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 - ) -``` - -Au cas où ce serait la première fois que vous verriez un modèle enregistré avec 🤗 *Accelerate*, prenons un moment pour inspecter les trois lignes de code qui l'accompagnent : - -```py -accelerator.wait_for_everyone() -unwrapped_model = accelerator.unwrap_model(model) -unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) -``` - -La première ligne est explicite : elle indique à tous les processus d'attendre que tout le monde soit à ce stade avant de continuer. C'est pour s'assurer que nous avons le même modèle dans chaque processus avant de sauvegarder. Ensuite, nous prenons le `unwrapped_model`, qui est le modèle de base que nous avons défini. La méthode `accelerator.prepare()` modifie le modèle pour qu'il fonctionne dans l'entraînement distribué, donc il n'aura plus la méthode `save_pretrained()` ; la méthode `accelerator.unwrap_model()` annule cette étape. Enfin, nous appelons `save_pretrained()` mais nous disons à cette méthode d'utiliser `accelerator.save()` au lieu de `torch.save()`. - -Une fois ceci fait, vous devriez avoir un modèle qui produit des résultats assez similaires à celui entraîné avec le `Trainer`. Vous pouvez vérifier le modèle que nous avons entraîné en utilisant ce code à [*huggingface-course/bert-finetuned-squad-accelerate*](https://huggingface.co/huggingface-course/bert-finetuned-squad-accelerate). Et si vous voulez tester des modifications de la boucle d'entraînement, vous pouvez les implémenter directement en modifiant le code ci-dessus ! - -{/if} - -### Utilisation du modèle *finetuné* - -Nous vous avons déjà montré comment vous pouvez utiliser le modèle que nous avons *finetuné* sur le *Hub* avec le widget d'inférence. Pour l'utiliser localement dans un `pipeline`, il suffit de spécifier l'identifiant du modèle : - -```py -from transformers import pipeline - -# Replace this with your own checkpoint -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'} -``` - -Super ! Notre modèle fonctionne aussi bien que le modèle par défaut pour ce pipeline ! + + +# Réponse aux questions + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Il est temps de s'intéresser à la réponse aux questions ! Cette tâche peut prendre plusieurs formes, mais celle sur laquelle nous allons nous concentrer dans cette section est appelée réponse aux questions *extractives*. Il s'agit de poser des questions sur un document et d'identifier les réponses sous forme de "morceaux de texte" dans le document lui-même. + + + +Nous allons affiner un modèle BERT sur le [jeu de données SQuAD](https://rajpurkar.github.io/SQuAD-explorer/), qui consiste en des questions posées par des *crowdworkers* sur un ensemble d'articles de Wikipedia. Cela nous donnera un modèle capable de calculer des prédictions comme celle-ci : + + + + +Il s'agit en fait de la présentation du modèle qui a été entraîné et téléchargé sur le *Hub* à l'aide du code présenté dans cette section. Vous pouvez le trouver et vérifier les prédictions [ici](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) + + + +💡 Les modèles à codeur unique comme BERT ont tendance à être excellents pour extraire les réponses à des questions factuelles comme "Qui a inventé l'architecture Transformer ?", mais ne sont pas très performants lorsqu'on leur pose des questions ouvertes comme "Pourquoi le ciel est-il bleu ?". Dans ces cas plus difficiles, les modèles encodeurs-décodeurs comme T5 et BART sont généralement utilisés pour synthétiser les informations d'une manière assez similaire au [résumé de texte](/cours/fr/chapter7/5). Si vous êtes intéressé par ce type de réponse aux questions *génératives*, nous vous recommandons de consulter notre [démo](https://yjernite.github.io/lfqa.html) basée sur le [jeu de données ELI5](https://huggingface.co/datasets/eli5). + + + +## Préparation des données + +Le jeu de données le plus utilisé comme référence académique pour la réponse extractive aux questions est [SQuAD](https://rajpurkar.github.io/SQuAD-explorer/), c'est donc celui que nous utiliserons ici. Il existe également une référence plus difficile [SQuAD v2](https://huggingface.co/datasets/squad_v2), qui comprend des questions sans réponse. Tant que votre propre jeu de données contient une colonne pour les contextes, une colonne pour les questions et une colonne pour les réponses, vous devriez être en mesure d'adapter les étapes ci-dessous. + +### Le jeu de données SQuAD + +Comme d'habitude, nous pouvons télécharger et mettre en cache le jeu de données en une seule étape grâce à `load_dataset()` : + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("squad") +``` + +Nous pouvons alors jeter un coup d'œil à cet objet pour en savoir plus sur le jeu de données 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 + }) +}) +``` + +On dirait que nous avons tout ce dont nous avons besoin avec les champs `context`, `question` et `answers`, alors imprimons-les pour le premier élément de notre ensemble d'entraînement : + +```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.' +# Sur le plan architectural, l'école a un caractère catholique. Au sommet du dôme doré du bâtiment principal se trouve une statue dorée de la Vierge Marie. Immédiatement devant le bâtiment principal et face à lui, se trouve une statue en cuivre du Christ, les bras levés, avec la légende "Venite Ad Me Omnes". À côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s'agit d'une réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette Soubirous en 1858. Au bout de l'allée principale (et dans une ligne directe qui passe par 3 statues et le Dôme d'or), se trouve une statue de pierre simple et moderne de Marie'. +Question: 'To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France?' # A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes, en France ? +Answer: {'text': ['Saint Bernadette Soubirous'], 'answer_start': [515]} +``` + +Les champs `context` et `question` sont très simples à utiliser. Le champ `answers` est un peu plus délicat car il compile un dictionnaire avec deux champs qui sont tous deux des listes. C'est le format qui sera attendu par la métrique `squad` lors de l'évaluation ; si vous utilisez vos propres données, vous n'avez pas nécessairement besoin de vous soucier de mettre les réponses dans le même format. Le champ `text` est assez évident, et le champ `answer_start` contient l'indice du caractère de départ de chaque réponse dans le contexte. + +Pendant l'entraînement, il n'y a qu'une seule réponse possible. Nous pouvons vérifier cela en utilisant la méthode `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 +}) +``` + +Pour l'évaluation, cependant, il existe plusieurs réponses possibles pour chaque échantillon, qui peuvent être identiques ou différentes : + +```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]} +``` + +Nous ne nous plongerons pas dans le script d'évaluation car tout sera enveloppé par une métrique 🤗 *Datasets* pour nous, mais la version courte est que certaines des questions ont plusieurs réponses possibles, et ce script va comparer une réponse prédite à toutes les réponses acceptables et prendre le meilleur score. Si nous regardons l'échantillon de l'indice 2, par exemple : + +```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.' +# Le Super Bowl 50 était un match de football américain visant à déterminer le champion de la National Football League (NFL) pour la saison 2015. Les Denver Broncos, champions de la Conférence de football américain (AFC), ont battu les Carolina Panthers, champions de la Conférence nationale de football (NFC), 24 à 10, pour remporter leur troisième titre de Super Bowl. Le match s'est déroulé le 7 février 2016 au Levi\'s Stadium, dans la baie de San Francisco, à Santa Clara, en Californie. Comme il s'agissait du 50e Super Bowl, la ligue a mis l'accent sur l'" anniversaire doré " avec diverses initiatives sur le thème de l'or, ainsi qu'en suspendant temporairement la tradition de nommer chaque match du Super Bowl avec des chiffres romains (en vertu de laquelle le match aurait été appelé " Super Bowl L "), afin que le logo puisse mettre en évidence les chiffres arabes 50.'' +'Where did Super Bowl 50 take place?' # Où a eu lieu le Super Bowl 50 ? +``` + +nous pouvons voir que la réponse peut effectivement être l'une des trois possibilités que nous avons vues précédemment. + +### Traitement des données d'entraînement + + + +Commençons par le prétraitement des données d'entraînement. La partie la plus difficile sera de générer des étiquettes pour la réponse à la question, qui seront les positions de début et de fin des *tokens* correspondant à la réponse dans le contexte. + +Mais ne nous emballons pas. Tout d'abord, nous devons convertir le texte de l'entrée en identifiants que le modèle peut comprendre, en utilisant un *tokenizer* : + +```py +from transformers import AutoTokenizer + +model_checkpoint = "bert-base-cased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +Comme mentionné précédemment, nous allons *finetuner* un modèle BERT, mais vous pouvez utiliser n'importe quel autre type de modèle tant qu'il a un *tokenizer* rapide implémenté. Vous pouvez voir toutes les architectures qui sont livrées avec une version rapide dans [ce grand tableau](https://huggingface.co/transformers/#supported-frameworks), et pour vérifier que l'objet `tokenizer` que vous utilisez est bien soutenu par des 🤗 *Tokenizers* vous pouvez regarder son attribut `is_fast` : + +```py +tokenizer.is_fast +``` + +```python out +True +``` + +Nous pouvons transmettre à notre *tokenizer* la question et le contexte ensemble, et il insérera correctement les *tokens* spéciaux pour former une phrase comme celle-ci : + +``` +[CLS] question [SEP] context [SEP] +``` + +Vérifions à nouveau : + +```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]' + +'[CLS] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] Architecturalement, ' +l'école a un caractère catholique. Au sommet du dôme doré du bâtiment principal se trouve une statue dorée de la Vierge ' +Marie. Immédiatement devant le bâtiment principal et face à lui, se trouve une statue en cuivre du Christ, les bras ''levés''. +'levés avec la légende " Venite Ad Me Omnes ". A côté du bâtiment principal se trouve la basilique du Sacré-Cœur. +'Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s'agit d'une ' +'réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette ' +Soubirous en 1858. Au bout de l'allée principale ( et en ligne directe qui passe par 3 statues ' +'et le Dôme d'or), se trouve une statue de Marie en pierre, simple et moderne. [SEP]'' +``` + +Les étiquettes seront alors l'index des *tokens* de début et de fin de la réponse, et le modèle sera chargé de prédire un logit de début et de fin par *token* dans l'entrée, les étiquettes théoriques étant les suivantes : + +
+One-hot encoded labels for question answering. + +
+ +Dans ce cas, le contexte n'est pas trop long, mais certains des exemples de l'ensemble de données ont des contextes très longs qui dépasseront la longueur maximale que nous avons fixée (qui est de 384 dans ce cas). Comme nous l'avons vu dans le [Chapitre 6](/course/fr/chapter6/4) lorsque nous avons exploré les internes du pipeline `question-answering`, nous allons traiter les contextes longs en créant plusieurs caractéristiques d'entraînement à partir d'un échantillon de notre jeu de données, avec une fenêtre glissante entre eux. + +Pour voir comment cela fonctionne en utilisant l'exemple actuel, nous pouvons limiter la longueur à 100 et utiliser une fenêtre glissante de 50 *tokens*. Pour rappel, nous utilisons + +- `max_length` pour définir la longueur maximale (ici 100) +- `truncation="only_second"` pour tronquer le contexte (qui est en deuxième position) quand la question avec son contexte est trop longue +- `stride` pour fixer le nombre de *tokens* se chevauchant entre deux morceaux successifs (ici 50) +- `return_overflowing_tokens=True` pour indiquer au tokenizer que l'on veut les *tokens* qui débordent + +```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] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] Sur le plan architectural, l'école a un caractère catholique. Au sommet du dôme doré du bâtiment principal se trouve une statue dorée de la Vierge Marie. Immédiatement devant le bâtiment principal et face à lui, se trouve une statue en cuivre du Christ, les bras levés, avec la légende " Venite Ad Me Omnes ". À côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la 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] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [le bâtiment principal et face à lui, une statue en cuivre du Christ aux bras levés avec la légende " Venite Ad Me Omnes ". À côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s'agit d'une réplique de la grotte de Lourdes, en France, où la Vierge [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] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [A côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s'agit d'une réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette Soubirous en 1858. Au bout de l'allée principale ( et dans une ligne directe qui relie par 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]' +'[CLS] A qui la Vierge Marie est-elle prétendument apparue en 1858 à Lourdes France ? [SEP]. Il s'agit d'une réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette Soubirous en 1858. Au bout de l'allée principale (et dans une ligne directe qui passe par 3 statues et le Dôme d'or), se trouve une simple statue de pierre moderne de Marie. [SEP]' +``` + +Comme nous pouvons le voir, notre exemple a été divisé en quatre entrées, chacune d'entre elles contenant la question et une partie du contexte. Notez que la réponse à la question ("Bernadette Soubirous") n'apparaît que dans la troisième et dernière entrée, donc en traitant les longs contextes de cette façon, nous allons créer quelques exemples d'entraînement où la réponse n'est pas incluse dans le contexte. Pour ces exemples, les étiquettes seront `start_position = end_position = 0` (donc nous prédisons le *token* `[CLS]`). Nous définirons également ces étiquettes dans le cas malheureux où la réponse a été tronquée de sorte que nous n'avons que le début (ou la fin) de celle-ci. Pour les exemples où la réponse est entièrement dans le contexte, les étiquettes seront l'index du *token* où la réponse commence et l'index du *token* où la réponse se termine. + +L'ensemble de données nous fournit le caractère de début de la réponse dans le contexte, et en ajoutant la longueur de la réponse, nous pouvons trouver le caractère de fin dans le contexte. Pour faire correspondre ces indices aux *tokens*, nous devrons utiliser les mappages d'offset que nous avons étudiés au [Chapitre 6](/course/chapter6/4). Nous pouvons faire en sorte que notre *tokenizer* renvoie ces index en passant `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']) +``` + +Comme nous pouvons le voir, nous récupérons les habituels ID d'entrée, ID de type de jeton, et masque d'attention, ainsi que le mappage d'offset dont nous avions besoin et une clé supplémentaire, `overflow_to_sample_mapping`. La valeur correspondante nous sera utile lorsque nous tokeniserons plusieurs textes en même temps (ce que nous devrions faire pour bénéficier du fait que notre *tokenizer* est soutenu par Rust). Puisqu'un échantillon peut donner plusieurs caractéristiques, il fait correspondre chaque caractéristique à l'exemple d'où elle provient. Parce qu'ici nous avons seulement tokenisé un exemple, nous obtenons une liste de `0`s : + +```py +inputs["overflow_to_sample_mapping"] +``` + +```python out +[0, 0, 0, 0] +``` + +Mais si nous tokenisons plus d'exemples, cela deviendra plus utile : + +```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].' +``` + +Comme nous pouvons le voir, les trois premiers exemples (aux indices 2, 3 et 4 de l'ensemble d'entraînement) ont chacun donné quatre caractéristiques et le dernier exemple (à l'indice 5 de l'ensemble d'entraînement) a donné 7 caractéristiques. + +Ces informations seront utiles pour associer chaque caractéristique obtenue à son étiquette correspondante. Comme mentionné précédemment, ces étiquettes sont : + +- `(0, 0)` si la réponse n'est pas dans l'espace correspondant du contexte. +- `(start_position, end_position)` si la réponse est dans l'espace correspondant du contexte, avec `start_position` étant l'index du *token* (dans les IDs d'entrée) au début de la réponse et `end_position` étant l'index du *token* (dans les IDs d'entrée) où la réponse se termine. + +Pour déterminer ce qui est le cas et, le cas échéant, les positions des *tokens*, nous trouvons d'abord les indices qui commencent et finissent le contexte dans les IDs d'entrée. Nous pourrions utiliser les IDs du type de *token* pour le faire, mais puisque ceux-ci n'existent pas nécessairement pour tous les modèles (DistilBERT ne les requiert pas, par exemple), nous allons plutôt utiliser la méthode `sequence_ids()` du `BatchEncoding` que notre tokenizer retourne. + +Une fois que nous avons ces indices de *tokens*, nous regardons les offsets correspondants, qui sont des tuples de deux entiers représentant l'étendue des caractères dans le contexte original. Nous pouvons ainsi détecter si le *chunk* du contexte dans cette fonctionnalité commence après la réponse ou se termine avant que la réponse ne commence (dans ce cas, l'étiquette est `(0, 0)`). Si ce n'est pas le cas, nous bouclons pour trouver le premier et le dernier *token* de la réponse : + +```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) + + # Trouver le début et la fin du contexte + idx = 0 + while sequence_ids[idx] != 1: + idx += 1 + context_start = idx + while sequence_ids[idx] == 1: + idx += 1 + context_end = idx - 1 + + # Si la réponse n'est pas entièrement dans le contexte, l'étiquette est (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: + # Otherwise it's the start and end token positions + 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]) +``` + +Jetons un coup d'œil à quelques résultats pour vérifier que notre approche est correcte. Pour la première caractéristique, nous trouvons `(83, 85)` comme étiquettes, alors comparons la réponse théorique avec l'étendue décodée des *tokens* de 83 à 85 (inclus) : + +```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' +``` + +Donc, c'est une correspondance ! Maintenant, vérifions l'index 4, où nous avons mis les étiquettes à `(0, 0)`, ce qui signifie que la réponse n'est pas dans le *chunk* de contexte de cette caractéristique : + +```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]' +``` + +En effet, nous ne voyons pas la réponse dans le contexte. + + + +✏️ **A votre tour !** En utilisant l'architecture XLNet, le *padding* est appliqué à gauche et la question et le contexte sont intervertis. Adaptez tout le code que nous venons de voir à l'architecture XLNet (et ajoutez `padding=True`). Soyez conscient que le token `[CLS]` peut ne pas être à la position 0 avec le *padding* appliqué. + + + +Maintenant que nous avons vu étape par étape comment prétraiter nos données d'entraînement, nous pouvons les regrouper dans une fonction que nous appliquerons à l'ensemble des données d'entraînement. Nous allons rembourrer chaque caractéristique à la longueur maximale que nous avons définie, car la plupart des contextes seront longs (et les échantillons correspondants seront divisés en plusieurs caractéristiques), il n'y a donc pas de réel avantage à appliquer un rembourrage dynamique ici : + +```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) + + # Trouver le début et la fin du contexte + idx = 0 + while sequence_ids[idx] != 1: + idx += 1 + context_start = idx + while sequence_ids[idx] == 1: + idx += 1 + context_end = idx - 1 + + # Si la réponse n'est pas entièrement dans le contexte, l'étiquette est (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: + # Otherwise it's the start and end token positions + 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 +``` + +Notez que nous avons défini deux constantes pour déterminer la longueur maximale utilisée ainsi que la longueur de la fenêtre glissante, et que nous avons ajouté un petit nettoyage avant la tokénisation : certaines des questions dans le jeu de données SQuAD ont des espaces supplémentaires au début et à la fin qui n'ajoutent rien (et prennent de la place lors de la tokénisation si vous utilisez un modèle comme RoBERTa), donc nous avons supprimé ces espaces supplémentaires. + +Pour appliquer cette fonction à l'ensemble de l'entraînement, nous utilisons la méthode `Dataset.map()` avec le flag `batched=True`. C'est nécessaire ici car nous changeons la longueur de l'ensemble de données (puisqu'un exemple peut donner plusieurs caractéristiques d'entraînement) : + +```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) +``` + +Comme nous pouvons le voir, le prétraitement a ajouté environ 1 000 caractéristiques. Notre ensemble d'entraînement est maintenant prêt à être utilisé - passons au prétraitement de l'ensemble de validation ! + +### Traitement des données de validation + +Le prétraitement des données de validation sera légèrement plus facile car nous n'avons pas besoin de générer des étiquettes (sauf si nous voulons calculer une perte de validation, mais ce nombre ne nous aidera pas vraiment à comprendre la qualité du modèle). La vraie joie sera d'interpréter les prédictions du modèle dans des étendues du contexte original. Pour cela, il nous suffit de stocker les mappages de décalage et un moyen de faire correspondre chaque caractéristique créée à l'exemple original dont elle provient. Puisqu'il y a une colonne ID dans l'ensemble de données original, nous utiliserons cet ID. + +La seule chose que nous allons ajouter ici est un petit nettoyage des mappages de décalage. Ils contiendront les offsets pour la question et le contexte, mais une fois que nous serons dans la phase de post-traitement, nous n'aurons aucun moyen de savoir quelle partie des IDs d'entrée correspondait au contexte et quelle partie était la question (la méthode `sequence_ids()` que nous avons utilisée n'est disponible que pour la sortie du *tokenizer*). Donc, nous allons mettre les offsets correspondant à la question à `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 +``` + +Nous pouvons appliquer cette fonction sur l'ensemble des données de validation comme précédemment : + +```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) +``` + +Dans ce cas, nous n'avons ajouté que quelques centaines d'échantillons, il semble donc que les contextes dans l'ensemble de données de validation soient un peu plus courts. + +Maintenant que nous avons prétraité toutes les données, nous pouvons passer à l'entraînement. + +{#if fw === 'pt'} + +## *Finetuner* le modèle avec l'API `Trainer` + +Le code d'entraînement pour cet exemple ressemblera beaucoup au code des sections précédentes -- la chose la plus difficile sera d'écrire la fonction `compute_metrics()`. Puisque nous avons capitonné tous les échantillons à la longueur maximale que nous avons fixée, il n'y a pas de collateur de données à définir, donc ce calcul de métrique est vraiment la seule chose dont nous devons nous soucier. La partie la plus difficile sera de post-traiter les prédictions du modèle en travées de texte dans les exemples originaux ; une fois que nous aurons fait cela, la métrique de la bibliothèque 🤗 *Datasets* fera le gros du travail pour nous. + +{:else} + +## *Finetuner* fin du modèle avec Keras + +Le code d'entraînement de cet exemple ressemblera beaucoup au code des sections précédentes, mais le calcul des métriques sera un défi unique. Puisque nous avons capitonné tous les échantillons à la longueur maximale que nous avons définie, il n'y a pas de collateur de données à définir, donc le calcul de la métrique est vraiment la seule chose dont nous devons nous soucier. La partie la plus difficile sera de post-traiter les prédictions du modèle en travées de texte dans les exemples originaux ; une fois que nous aurons fait cela, la métrique de la bibliothèque 🤗 *Datasets* fera le gros du travail pour nous. + +{/if} + +### Post-traitement + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Le modèle produira des logits pour les positions de début et de fin de la réponse dans les IDs d'entrée, comme nous l'avons vu lors de notre exploration du [`question-answering` pipeline](/course/chapter6/4). L'étape de post-traitement sera similaire à ce que nous avons fait là-bas, donc voici un rappel rapide des actions que nous avons prises : + +- nous avons masqué les logits de début et de fin correspondant aux *tokens* en dehors du contexte, +- nous avons ensuite converti les logits de début et de fin en probabilités en utilisant un softmax, +- nous avons attribué un score à chaque paire `(start_token, end_token)` en prenant le produit des deux probabilités correspondantes, +- nous avons cherché la paire avec le score maximum qui donnait une réponse valide (par exemple, un `start_token` inférieur au `end_token`). + +Ici, nous allons modifier légèrement ce processus car nous n'avons pas besoin de calculer les scores réels (juste la réponse prédite). Cela signifie que nous pouvons sauter l'étape du softmax. Pour aller plus vite, nous ne noterons pas non plus toutes les paires `(start_token, end_token)` possibles, mais seulement celles correspondant aux logits `n_best` les plus élevés (avec `n_best=20`). Puisque nous sauterons le softmax, ces scores seront des scores logit, et seront obtenus en prenant la somme des logits de début et de fin (au lieu du produit, à cause de la règle \\(\log(ab) = \log(a) + \log(b)\)). + +Pour démontrer tout cela, nous aurons besoin d'un certain type de prédictions. Puisque nous n'avons pas encore entraîné notre modèle, nous allons utiliser le modèle par défaut du pipeline d'assurance qualité pour générer quelques prédictions sur une petite partie de l'ensemble de validation. Nous pouvons utiliser la même fonction de traitement que précédemment ; parce qu'elle repose sur la constante globale `tokenizer`, nous devons juste changer cet objet pour le tokenizer du modèle que nous voulons utiliser temporairement : + +```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, +) +``` + +Maintenant que le prétraitement est terminé, nous changeons le *tokenizer* pour celui que nous avons choisi à l'origine : + +```python +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +Nous supprimons ensuite les colonnes de notre `eval_set` qui ne sont pas attendues par le modèle, nous construisons un lot avec l'ensemble de ce petit ensemble de validation, et nous le passons au modèle. Si un GPU est disponible, nous l'utilisons pour aller plus vite : + +{#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) +``` + +Puisque le `Trainer` nous donnera les prédictions sous forme de tableaux NumPy, nous récupérons les logits de début et de fin et les convertissons dans ce format : + +```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) +``` + +Pour faciliter l'expérimentation, nous allons convertir ces sorties en tableaux NumPy : + +```python +start_logits = outputs.start_logits.numpy() +end_logits = outputs.end_logits.numpy() +``` + +{/if} + +Maintenant, nous devons trouver la réponse prédite pour chaque exemple dans notre `small_eval_set`. Un exemple peut avoir été divisé en plusieurs caractéristiques dans `eval_set`, donc la première étape est de faire correspondre chaque exemple dans `small_eval_set` aux caractéristiques correspondantes dans `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) +``` + +Avec cela en main, nous pouvons vraiment nous mettre au travail en parcourant en boucle tous les exemples et, pour chaque exemple, toutes les caractéristiques associées. Comme nous l'avons dit précédemment, nous allons regarder les scores logit pour les `n_meilleurs` logits de début et logits de fin, en excluant les positions qui donnent : + +- une réponse qui ne serait pas dans le contexte. +- une réponse avec une longueur négative +- une réponse qui est trop longue (nous limitons les possibilités à `max_answer_length=30`) + +Une fois que nous avons toutes les réponses possibles notées pour un exemple, nous choisissons simplement celle qui a le meilleur score logit : + +```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: + # Ignore les réponses qui ne sont pas entièrement dans le contexte + if offsets[start_index] is None or offsets[end_index] is None: + continue + # Ignorer les réponses dont la longueur est soit < 0 soit > 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"]}) +``` + +Le format final des réponses prédites est celui qui sera attendu par la métrique que nous allons utiliser. Comme d'habitude, nous pouvons le charger à l'aide de la bibliothèque 🤗 *Datasets* : + +```python +from datasets import load_metric + +metric = load_metric("squad") +``` + +Cette métrique attend les réponses prédites dans le format que nous avons vu ci-dessus (une liste de dictionnaires avec une clé pour l'ID de l'exemple et une clé pour le texte prédit) et les réponses théoriques dans le format ci-dessous (une liste de dictionnaires avec une clé pour l'ID de l'exemple et une clé pour les réponses possibles) : + +```python +theoretical_answers = [ + {"id": ex["id"], "answers": ex["answers"]} for ex in small_eval_set +] +``` + +Nous pouvons maintenant vérifier que nous obtenons des résultats raisonnables en examinant le premier élément des deux listes : + +```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]}} +``` + +Pas trop mal ! Voyons maintenant le score que la métrique nous donne : + +```python +metric.compute(predictions=predicted_answers, references=theoretical_answers) +``` + +```python out +{'exact_match': 83.0, 'f1': 88.25} +``` + +Encore une fois, c'est plutôt bon si l'on considère que, selon [son article](https://arxiv.org/abs/1910.01108v2), DistilBERT *finetuné* sur SQuAD obtient 79,1 et 86,9 pour ces scores sur l'ensemble des données. + +{#if fw === 'pt'} + +Maintenant, mettons tout ce que nous venons de faire dans une fonction `compute_metrics()` que nous utiliserons dans le `Trainer`. Normalement, cette fonction `compute_metrics()` reçoit seulement un tuple `eval_preds` avec les logits et les labels. Ici, nous aurons besoin d'un peu plus, car nous devons chercher dans le jeu de données des caractéristiques pour le décalage et dans le jeu de données des exemples pour les contextes originaux, donc nous ne serons pas en mesure d'utiliser cette fonction pour obtenir des résultats d'évaluation réguliers pendant l'entraînement. Nous ne l'utiliserons qu'à la fin de l'entraînement pour vérifier les résultats. + +La fonction `compute_metrics()` regroupe les mêmes étapes que précédemment ; nous ajoutons juste une petite vérification au cas où nous ne trouverions aucune réponse valide (dans ce cas nous prédisons une chaîne vide). + +{:else} + +Maintenant, mettons tout ce que nous venons de faire dans une fonction `compute_metrics()` que nous utiliserons après avoir entraîné notre modèle. Nous aurons besoin de passer un peu plus que juste les logits de sortie, car nous devons chercher dans le jeu de données des caractéristiques pour le décalage et dans le jeu de données des exemples pour les contextes originaux : + +{/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 = [] + + # Parcourir en boucle toutes les fonctionnalités associées à cet exemple + 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: + # Ignore les réponses qui ne sont pas entièrement dans le contexte + if offsets[start_index] is None or offsets[end_index] is None: + continue + # Ignore les réponses dont la longueur est soit < 0, soit > 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) + + # Sélectionne la réponse avec le meilleur score + 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) +``` + +Nous pouvons vérifier que cela fonctionne sur nos prédictions : + +```python +compute_metrics(start_logits, end_logits, eval_set, small_eval_set) +``` + +```python out +{'exact_match': 83.0, 'f1': 88.25} +``` + +C'est bien ! Maintenant, utilisons ceci pour affiner notre modèle. + +### *Finetuning* du modèle + +{#if fw === 'pt'} + +Nous sommes maintenant prêts à entraîner notre modèle. Créons-le d'abord, en utilisant la classe `AutoModelForQuestionAnswering` comme précédemment : + +```python +model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) +``` + +{:else} + +Nous sommes maintenant prêts à entraîner notre modèle. Créons-le d'abord, en utilisant la classe `TFAutoModelForQuestionAnswering` comme précédemment : + +```python +model = TFAutoModelForQuestionAnswering.from_pretrained(model_checkpoint) +``` + +{/if} + +Comme d'habitude, nous recevons un avertissement indiquant que certains poids ne sont pas utilisés (ceux de la tête de pré-entraînement) et que d'autres sont initialisés de manière aléatoire (ceux de la tête de réponse aux questions). Vous devriez être habitué à cela maintenant, mais cela signifie que ce modèle n'est pas encore prêt à être utilisé et qu'il a besoin d'être *finetuné*. Une bonne chose que nous soyons sur le point de le faire ! + +Pour pouvoir pousser notre modèle vers le *Hub*, nous devons nous connecter à Hugging Face. Si vous exécutez ce code dans un *notebook*, vous pouvez le faire avec la fonction utilitaire suivante, qui affiche un widget où vous pouvez entrer vos identifiants de connexion : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : + +```bash +huggingface-cli login +``` + +{#if fw === 'pt'} + +Une fois ceci fait, nous pouvons définir nos `TrainingArguments`. Comme nous l'avons dit lorsque nous avons défini notre fonction pour calculer la métrique, nous ne serons pas en mesure d'avoir une boucle d'évaluation régulière à cause de la signature de la fonction `compute_metrics()`. Nous pourrions écrire notre propre sous-classe de `Trainer` pour faire cela (une approche que vous pouvez trouver dans le [script d'exemple de réponse aux questions](https://github.com/huggingface/transformers/blob/master/examples/pytorch/question-answering/trainer_qa.py)), mais c'est un peu trop long pour cette section. A la place, nous n'évaluerons le modèle qu'à la fin de l'entraînement et nous vous montrerons comment faire une évaluation régulière dans "Une boucle d'entraînement personnalisée" ci-dessous. + +C'est vraiment là que l'API `Trainer` montre ses limites et que la bibliothèque 🤗 *Accelerate* brille : personnaliser la classe pour un cas d'utilisation spécifique peut être pénible, mais modifier une boucle d'entraînement entièrement exposée est facile. + +Jetons un coup d'œil à notre `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, +) +``` + +Nous avons déjà vu la plupart d'entre eux : nous définissons quelques hyperparamètres (comme le taux d'apprentissage, le nombre d'époques pour lesquelles nous nous entraînons, et une certaine décroissance de poids) et nous indiquons que nous voulons sauvegarder le modèle à la fin de chaque époque, sauter l'évaluation, et télécharger nos résultats vers le Model Hub. Nous activons également l'entraînement en précision mixte avec `fp16=True`, car cela peut accélérer l'entraînement sur un GPU récent. + +{:else} + +Maintenant que c'est fait, nous pouvons créer nos ensembles de données TF. Nous pouvons utiliser le simple collateur de données par défaut cette fois-ci : + +```python +from transformers import DefaultDataCollator + +data_collator = DefaultDataCollator(return_tensors="tf") +``` + +Et maintenant nous créons les jeux de données comme d'habitude. + +```python +tf_train_dataset = train_dataset.to_tf_dataset( + columns=[ + "input_ids", + "start_positions", + "end_positions", + "attention_mask", + "token_type_ids", + ], + collate_fn=data_collator, + shuffle=True, + batch_size=16, +) +tf_eval_dataset = validation_dataset.to_tf_dataset( + columns=["input_ids", "attention_mask", "token_type_ids"], + collate_fn=data_collator, + shuffle=False, + batch_size=16, +) +``` + +Ensuite, nous configurons nos hyperparamètres d'entraînement et compilons notre modèle : + +```python +from transformers import create_optimizer +from transformers.keras_callbacks import PushToHubCallback +import tensorflow as tf + +# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans le jeu de données, divisé par la taille du batch, puis multiplié par le nombre total d'époques. +# par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un lot tf.data.Dataset, +# et non le jeu de données original Hugging Face Dataset, donc son len() est déjà 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) + +# Entraîner en mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +Enfin, nous sommes prêts à nous entraîner avec `model.fit()`. Nous utilisons un `PushToHubCallback` pour télécharger le modèle sur le *Hub* après chaque époque. + +{/if} + +Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, donc dans notre cas il sera dans `"sgugger/bert-finetuned-squad"`. Nous pouvons passer outre en passant un `hub_model_id` ; par exemple, pour pousser le modèle dans l'organisation `huggingface_course` nous avons utilisé `hub_model_id= "huggingface_course/bert-finetuned-squad"` (qui est le modèle que nous avons lié au début de cette section). + +{#if fw === 'pt'} + + + +💡 Si le répertoire de sortie que vous utilisez existe, il doit être un clone local du dépôt vers lequel vous voulez pousser (donc définissez un nouveau nom si vous obtenez une erreur lors de la définition de votre `Trainer`). + + + +Enfin, nous passons tout à la classe `Trainer` et lançons l'entraînement : + +```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) + +# Nous allons faire la validation après, donc pas de validation au milieu de l'entraînement. +model.fit(tf_train_dataset, callbacks=[callback], epochs=num_train_epochs) +``` + +{/if} + +Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le Hub en arrière-plan. Ainsi, vous pourrez reprendre votre entraînement sur une autre machine si nécessaire. L'ensemble de l'entraînement prend un certain temps (un peu plus d'une heure sur une Titan RTX), vous pouvez donc prendre un café ou relire les parties du cours qui vous ont semblé plus difficiles pendant qu'il se déroule. Notez également que dès que la première époque est terminée, vous verrez des poids téléchargés sur le Hub et vous pourrez commencer à jouer avec votre modèle sur sa page. + +{#if fw === 'pt'} + +Une fois l'entraînement terminé, nous pouvons enfin évaluer notre modèle (et prier pour ne pas avoir dépensé tout ce temps de calcul pour rien). La méthode `predict()` du `Trainer` retournera un tuple où les premiers éléments seront les prédictions du modèle (ici une paire avec les logits de début et de fin). Nous envoyons ceci à notre fonction `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} + +Une fois l'entraînement terminé, nous pouvons enfin évaluer notre modèle (et prier pour ne pas avoir dépensé tout ce temps de calcul pour rien). La méthode `predict()` de notre `model` se chargera d'obtenir les prédictions, et puisque nous avons fait tout le travail difficile de définir une fonction `compute_metrics()` plus tôt, nous pouvons obtenir nos résultats en une seule ligne : + +```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} +``` + +Super ! À titre de comparaison, les scores de base indiqués dans l'article du BERT pour ce modèle sont de 80,8 et 88,5, donc nous sommes exactement là où nous devrions être. + +{#if fw === 'pt'} + +Enfin, nous utilisons la méthode `push_to_hub()` pour nous assurer que nous téléchargeons la dernière version du modèle : + +```py +trainer.push_to_hub(commit_message="Training complete") +``` + +Cela renvoie l'URL du commit qu'il vient de faire, si vous voulez l'inspecter : + +```python out +'https://huggingface.co/sgugger/bert-finetuned-squad/commit/9dcee1fbc25946a6ed4bb32efb1bd71d5fa90b68' +``` + +Le `Trainer` rédige également une fiche modèle avec tous les résultats de l'évaluation et la télécharge. + +{/if} + +À ce stade, vous pouvez utiliser le widget d'inférence sur le *Hub* du modèle pour tester le modèle et le partager avec vos amis, votre famille et vos animaux préférés. Vous avez réussi à *finetuner* un modèle sur une tâche de réponse à une question - félicitations ! + + + +✏️ **Votre tour** Essayez un autre modèle d'architecture pour voir s'il est plus performant dans cette tâche ! + + + +{#if fw === 'pt'} + +Si vous voulez plonger un peu plus profondément dans la boucle d'entraînement, nous allons maintenant vous montrer comment faire la même chose en utilisant 🤗 *Accelerate*. + +## Une boucle d'entraînement personnalisée + +Jetons maintenant un coup d'œil à la boucle d'entraînement complète, afin que vous puissiez facilement personnaliser les parties dont vous avez besoin. Elle ressemblera beaucoup à la boucle d'entraînement du [Chapitre 3](/course/fr/chapter3/4), à l'exception de la boucle d'évaluation. Nous serons en mesure d'évaluer le modèle régulièrement puisque nous ne sommes plus contraints par la classe `Trainer`. + +### Préparer tout pour l'entraînement + +Tout d'abord, nous devons construire le `DataLoader`s à partir de nos jeux de données. Nous définissons le format de ces jeux de données à `"torch"`, et supprimons les colonnes dans le jeu de validation qui ne sont pas utilisées par le modèle. Ensuite, nous pouvons utiliser le `default_data_collator` fourni par Transformers comme `collate_fn` et mélanger l'ensemble d'entraînement, mais pas l'ensemble de validation : + +```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 +) +``` + +Ensuite, nous réinstantifions notre modèle, afin de nous assurer que nous ne poursuivons pas les réglages fins précédents mais que nous repartons du modèle pré-entraîné de BERT : + +```py +model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) +``` + +Ensuite, nous aurons besoin d'un optimiseur. Comme d'habitude, nous utilisons le classique `AdamW`, qui est comme Adam, mais avec une correction dans la façon dont la décroissance du poids est appliquée : + +```py +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Une fois que nous avons tous ces objets, nous pouvons les envoyer à la méthode `accelerator.prepare()`. Rappelez-vous que si vous voulez vous entraîner sur des TPUs dans un *notebook* de Colab, vous devrez déplacer tout ce code dans une fonction d'entraînement, et qui ne devrait pas exécuter une cellule qui instancie un `Accelerator`. Nous pouvons forcer l'entraînement en précision mixte en passant `fp16=True` à l'`Accelerator` (ou, si vous exécutez le code comme un script, assurez-vous de remplir la 🤗 *Accelerate* `config` de manière appropriée). + +```py +from accelerate import Accelerator + +accelerator = Accelerator(fp16=True) +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + +Comme vous devez le savoir depuis les sections précédentes, nous ne pouvons utiliser la longueur de `train_dataloader` pour calculer le nombre d'étapes d'entraînement qu'après qu'il soit passé par la méthode `accelerator.prepare()`. Nous utilisons le même programme linéaire que dans les sections précédentes : + +```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, +) +``` + +Pour pousser notre modèle vers le Hub, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous au Hugging Face Hub, si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'ID du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix ; il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `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' +``` + +Ensuite, nous pouvons cloner ce référentiel dans un dossier local. S'il existe déjà, ce dossier local doit être un clone du référentiel avec lequel nous travaillons : + +```py +output_dir = "bert-finetuned-squad-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output_dir` en appelant la méthode `repo.push_to_hub()`. Cela nous aidera à télécharger les modèles intermédiaires à la fin de chaque époque. + +## Boucle d'entraînement + +Nous sommes maintenant prêts à écrire la boucle d'entraînement complète. Après avoir défini une barre de progression pour suivre l'évolution de l'entraînement, la boucle comporte trois parties : + +- l'entraînement proprement dit, qui est l'itération classique sur le `train_dataloader`, passage en avant du modèle, puis passage en arrière et étape d'optimisation. +- l'évaluation, dans laquelle nous rassemblons toutes les valeurs pour `start_logits` et `end_logits` avant de les convertir en tableaux NumPy. Une fois la boucle d'évaluation terminée, nous concaténons tous les résultats. Notez que nous devons tronquer parce que l' `Accelerator` peut avoir ajouté quelques échantillons à la fin pour s'assurer que nous avons le même nombre d'exemples dans chaque processus. +- sauvegarde et téléchargement, où nous sauvegardons d'abord le modèle et le *tokenizer*, puis appelons `repo.push_to_hub()`. Comme nous l'avons fait auparavant, nous utilisons l'argument `blocking=False` pour dire à la bibliothèque 🤗 *Hub* de pousser dans un processus asynchrone. De cette façon, l'entraînement continue normalement et cette (longue) instruction est exécutée en arrière-plan. + +Voici le code complet de la boucle d'entraînement : + +```py +from tqdm.auto import tqdm +import torch + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Entraînement + model.train() + for step, batch in enumerate(train_dataloader): + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) + + # Evaluation + model.eval() + 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) + + # Sauvegarder et télécharger + 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 + ) +``` + +Au cas où ce serait la première fois que vous verriez un modèle enregistré avec 🤗 *Accelerate*, prenons un moment pour inspecter les trois lignes de code qui l'accompagnent : + +```py +accelerator.wait_for_everyone() +unwrapped_model = accelerator.unwrap_model(model) +unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) +``` + +La première ligne est explicite : elle indique à tous les processus d'attendre que tout le monde soit à ce stade avant de continuer. C'est pour s'assurer que nous avons le même modèle dans chaque processus avant de sauvegarder. Ensuite, nous prenons le `unwrapped_model`, qui est le modèle de base que nous avons défini. La méthode `accelerator.prepare()` modifie le modèle pour qu'il fonctionne dans l'entraînement distribué, donc il n'aura plus la méthode `save_pretrained()` ; la méthode `accelerator.unwrap_model()` annule cette étape. Enfin, nous appelons `save_pretrained()` mais nous disons à cette méthode d'utiliser `accelerator.save()` au lieu de `torch.save()`. + +Une fois ceci fait, vous devriez avoir un modèle qui produit des résultats assez similaires à celui entraîné avec le `Trainer`. Vous pouvez vérifier le modèle que nous avons entraîné en utilisant ce code à [*huggingface-course/bert-finetuned-squad-accelerate*](https://huggingface.co/huggingface-course/bert-finetuned-squad-accelerate). Et si vous voulez tester des modifications de la boucle d'entraînement, vous pouvez les implémenter directement en modifiant le code ci-dessus ! + +{/if} + +### Utilisation du modèle *finetuné* + +Nous vous avons déjà montré comment vous pouvez utiliser le modèle que nous avons *finetuné* sur le *Hub* avec le widget d'inférence. Pour l'utiliser localement dans un `pipeline`, il suffit de spécifier l'identifiant du modèle : + +```py +from transformers import pipeline + +# Replace this with your own checkpoint +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'} +``` + +Super ! Notre modèle fonctionne aussi bien que le modèle par défaut pour ce pipeline ! diff --git a/chapters/fr/chapter8/1.mdx b/chapters/fr/chapter8/1.mdx index 0c32596a0..441ea1969 100644 --- a/chapters/fr/chapter8/1.mdx +++ b/chapters/fr/chapter8/1.mdx @@ -4,9 +4,9 @@ Maintenant que vous savez comment aborder les tâches de NLP les plus courantes Plus précisément, dans ce chapitre vous allez apprendre : -- la première chose à faire lorsque vous obtenez une erreur. -- comment demander de l'aide sur le [forum](https://discuss.huggingface.co/) -- comment déboguer votre pipeline d'entraînement -- comment rédiger une bonne *issue* +- la première chose à faire lorsque vous obtenez une erreur, +- comment demander de l'aide sur le [forum](https://discuss.huggingface.co/), +- comment déboguer votre pipeline d'entraînement, +- comment rédiger une bonne *issue*. Rien de tout cela n'est spécifiquement lié à 🤗 *Transformers* ou à l'écosystème Hugging Face. Les leçons de ce chapitre sont applicables à la plupart des projets open source ! diff --git a/chapters/fr/chapter8/2.mdx b/chapters/fr/chapter8/2.mdx index 39f70542d..76abf7488 100644 --- a/chapters/fr/chapter8/2.mdx +++ b/chapters/fr/chapter8/2.mdx @@ -7,11 +7,11 @@ {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter8/section2.ipynb"}, ]} /> -Dans cette section, nous allons examiner certaines erreurs courantes qui peuvent se produire lorsque vous essayez de générer des prédictions à partir de votre *transformer* fraîchement *tuné*. Cela vous préparera pour la [section 4](/course/chapter8/fr/section4), où nous explorerons comment déboguer la phase d'entraînement elle-même. +Dans cette section, nous allons examiner certaines erreurs courantes qui peuvent se produire lorsque vous essayez de générer des prédictions à partir de votre *transformer* fraîchement *finetuné*. Cela vous préparera pour la [section 4](/course/chapter8/fr/section4) de ce chapitre où nous explorerons comment déboguer la phase d'entraînement elle-même. -Nous avons préparé un [dépôt de modèles](https://huggingface.co/lewtun/distilbert-base-uncased-finetuned-squad-d5716d28) pour cette section, et si vous voulez exécuter le code de ce chapitre, vous devrez d'abord copier le modèle dans votre compte sur le [*Hub* d'Hugging Face](https://huggingface.co). Pour ce faire, connectez-vous d'abord en exécutant l'une ou l'autre des commandes suivantes dans un *notebook* Jupyter : +Nous avons préparé un gabarit de [dépôt de modèles](https://huggingface.co/lewtun/distilbert-base-uncased-finetuned-squad-d5716d28) pour cette section et si vous voulez exécuter le code de ce chapitre, vous devrez d'abord copier le modèle dans votre compte sur le [*Hub* d'Hugging Face](https://huggingface.co). Pour ce faire, connectez-vous d'abord en exécutant l'une ou l'autre des commandes suivantes dans un *notebook* Jupyter : ```python from huggingface_hub import notebook_login @@ -25,7 +25,7 @@ ou ce qui suit dans votre terminal préféré : huggingface-cli login ``` -Cela vous demandera d'entrer votre nom d'utilisateur et votre mot de passe, et enregistrera un jeton sous *~/.cache/huggingface/*. Une fois que vous vous êtes connecté, vous pouvez copier le dépôt de modèles avec la fonction suivante : +Cela vous demandera d'entrer votre nom d'utilisateur et votre mot de passe, et enregistrera un jeton sous *~/.cache/huggingface/*. Une fois que vous vous êtes connecté, vous pouvez copier le gabarit du dépôt avec la fonction suivante : ```python from distutils.dir_util import copy_tree @@ -50,15 +50,15 @@ def copy_repository_template(): repo.push_to_hub() ``` -Maintenant, lorsque vous appelez `copy_repository_template()`, cela va créer une copie du dépôt de modèles sous votre compte. +Maintenant, lorsque vous appelez `copy_repository_template()`, cela va créer une copie du gabarit du dépôt sous votre compte. -## Déboguer le pipeline à partir de 🤗 *Transformers* +## Déboguer le pipeline à partir de 🤗 Transformers -Pour donner le coup d'envoi de notre voyage dans le monde merveilleux du débogage des *transformers*, considérez le scénario suivant : vous travaillez avec un collègue sur un projet de réponse à des questions pour aider les clients d'un site de commerce électronique à trouver des réponses sur des produits de consommation. Votre collègue vous envoie un message du genre : +Pour donner le coup d'envoi de notre voyage dans le monde merveilleux du débogage de *transformers*, considérez le scénario suivant : vous travaillez avec un collègue sur un projet de réponse à des questions pour aider les clients d'un site de commerce en ligne à trouver des réponses à des produits. Votre collègue vous envoie un message du genre : -> Bonjour ! Je viens de réaliser une expérience en utilisant les techniques du [chapitre 7](/course/fr/chapiter7/7) du cours d'Hugging Face et j'ai obtenu d'excellents résultats sur SQuAD ! Je pense que nous pouvons utiliser ce modèle comme point de départ pour notre projet. L'ID du modèle sur le Hub est "lewtun/distillbert-base-uncased-finetuned-squad-d5716d28". N'hésitez pas à le tester :) +> Bonjour ! Je viens de réaliser une expérience en utilisant les techniques du [chapitre 7](/course/fr/chapiter7/7) du cours d'Hugging Face et j'ai obtenu d'excellents résultats sur SQuAD ! Je pense que nous pouvons utiliser ce modèle comme point de départ pour notre projet. L'identifiant du modèle sur le *Hub* est "lewtun/distillbert-base-uncased-finetuned-squad-d5716d28". N'hésite pas à le tester :) -et la première chose à laquelle on pense est de charger le modèle en utilisant la `pipeline` de 🤗 *Transformers* : +et la première chose à laquelle on pense est de charger le modèle en utilisant le `pipeline` de 🤗 *Transformers* : ```python from transformers import pipeline @@ -77,21 +77,21 @@ OSError: Can't load config for 'lewtun/distillbert-base-uncased-finetuned-squad- """ ``` -Oh non, quelque chose semble avoir mal tourné ! Si vous êtes novice en programmation, ce genre d'erreurs peut sembler un peu cryptique au début (qu'est-ce qu'un `OSError` ?!). L'erreur affichée ici n'est que la dernière partie d'un rapport d'erreur beaucoup plus large appelé _Python traceback_ (alias *stack trace*). Par exemple, si vous exécutez ce code sur Google Colab, vous devriez voir quelque chose comme la capture d'écran suivante : +Oh non, quelque chose semble s'être mal passée ! Si vous êtes novice en programmation, ce genre d'erreurs peut sembler un peu cryptique au début (qu'est-ce qu'une `OSError` ?!). L'erreur affichée ici n'est que la dernière partie d'un rapport d'erreur beaucoup plus long appelé _Python traceback_ (alias *stack trace*). Par exemple, si vous exécutez ce code sur Google Colab, vous devriez voir quelque chose comme la capture d'écran suivante :
A Python traceback.
-Il y a beaucoup d'informations dans ces rapports, nous allons donc en parcourir ensemble les éléments clés. La première chose à noter est que les rapports de traçage doivent être lus _de bas en haut_. Cela peut sembler bizarre si vous avez l'habitude de lire du texte anglais de haut en bas, mais cela reflète le fait que le *traceback* montre la séquence d'appels de fonction que la `pipeline` fait lors du téléchargement du modèle et du *tokenizer*. Consultez le [Chapitre 2](/course/fr/chapter2) pour plus de détails sur la façon dont le `pipeline` fonctionne sous le capot. +Il y a beaucoup d'informations dans ces rapports, nous allons donc en parcourir ensemble les éléments clés. La première chose à noter est que les *tracebacks* doivent être lus _de bas en haut_. Cela peut sembler bizarre si vous avez l'habitude de lire du texte français de haut en bas mais cela reflète le fait que le *traceback* montre la séquence d'appels de fonction que le `pipeline` fait lors du téléchargement du modèle et du *tokenizer*. Consultez le [chapitre 2](/course/fr/chapter2) pour plus de détails sur la façon dont le `pipeline` fonctionne sous le capot. -🚨 Vous voyez le cadre bleu autour de "6 frames" dans lle *traceback* de Google Colab ? Il s'agit d'une fonctionnalité spéciale de Colab, qui compresse la trace en "frames". Si vous ne parvenez pas à trouver la source d'une erreur, veillez à développer la trace complète en cliquant sur ces deux petites flèches. +🚨 Vous voyez le cadre bleu autour de « 6 frames » dans le traceback de Google Colab ? Il s'agit d'une fonctionnalité spéciale de Colab qui compresse le traceback en frames. Si vous ne parvenez pas à trouver la source d'une erreur, déroulez le traceback en cliquant sur ces deux petites flèches. -Cela signifie que la dernière ligne de la trace indique le dernier message d'erreur et donne le nom de l'exception qui a été levée. Dans ce cas, le type d'exception est `OSError`, ce qui indique une erreur liée au système. Si nous lisons le message d'erreur qui l'accompagne, nous pouvons voir qu'il semble y avoir un problème avec le fichier *config.json* du modèle, et deux suggestions nous sont données pour le résoudre : +Cela signifie que la dernière ligne du traceback indique le dernier message d'erreur et donne le nom de l'exception qui a été levée. Dans ce cas, le type d'exception est `OSError`, ce qui indique une erreur liée au système. Si nous lisons le message d'erreur qui l'accompagne, nous pouvons voir qu'il semble y avoir un problème avec le fichier *config.json* du modèle et deux suggestions nous sont données pour le résoudre : ```python out """ @@ -105,23 +105,23 @@ Make sure that: -💡 Si vous rencontrez un message d'erreur difficile à comprendre, copiez et collez le message dans Google ou sur [Stack Overflow](https://stackoverflow.com/) (oui, vraiment !). Il y a de fortes chances que vous ne soyez pas la première personne à rencontrer cette erreur, et c'est un bon moyen de trouver des solutions que d'autres membres de la communauté ont publiées. Par exemple, en recherchant `OSError : Can't load config for` sur Stack Overflow donne plusieurs [réponses](https://stackoverflow.com/search?q=OSError%3A+Can%27t+load+config+for+) qui peuvent être utilisées comme point de départ pour résoudre le problème. +💡 Si vous rencontrez un message d'erreur difficile à comprendre, copiez et collez le message dans Google ou sur [Stack Overflow](https://stackoverflow.com/) (oui, vraiment !). Il y a de fortes chances que vous ne soyez pas la première personne à rencontrer cette erreur et c'est un bon moyen de trouver des solutions que d'autres membres de la communauté ont publiées. Par exemple, en recherchant `OSError : Can't load config for` sur Stack Overflow donne plusieurs [réponses](https://stackoverflow.com/search?q=OSError%3A+Can%27t+load+config+for+) qui peuvent être utilisées comme point de départ pour résoudre le problème. -La première suggestion nous demande de vérifier si l'identifiant du modèle est effectivement correct, la première chose à faire est donc de copier l'identifiant et de le coller dans la barre de recherche du Hub : +La première suggestion nous demande de vérifier si l'identifiant du modèle est effectivement correct, la première chose à faire est donc de copier l'identifiant et de le coller dans la barre de recherche du *Hub* :
The wrong model name.
-Hmm, il semble en effet que le modèle de notre collègue ne soit pas sur le Hub... Mais il y a une faute de frappe dans le nom du modèle ! DistilBERT n'a qu'un seul "l" dans son nom, alors corrigeons cela et cherchons "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28" à la place : +Hmm, il semble en effet que le modèle de notre collègue ne soit pas sur le *Hub*... Mais il y a une faute de frappe dans le nom du modèle ! DistilBERT n'a qu'un seul « l » dans son nom alors corrigeons cela et cherchons « lewtun/distilbert-base-uncased-finetuned-squad-d5716d28 » à la place :
The right model name.
-Ok, ça a marché. Maintenant essayons de télécharger à nouveau le modèle avec l'ID correct du modèle : +Ok, ça a marché. Maintenant essayons de télécharger à nouveau le modèle avec le bon identifiant : ```python model_checkpoint = get_full_repo_name("distilbert-base-uncased-finetuned-squad-d5716d28") @@ -138,7 +138,7 @@ OSError: Can't load config for 'lewtun/distilbert-base-uncased-finetuned-squad-d """ ``` -Argh, encore un échec. Bienvenue dans la vie quotidienne d'un ingénieur en apprentissage machine ! Puisque nous avons corrigé l'ID du modèle, le problème doit se situer dans le référentiel lui-même. Une façon rapide d'accéder au contenu d'un dépôt sur le 🤗 *Hub* est via la fonction `list_repo_files()` de la bibliothèque `huggingface_hub` : +Argh, encore un échec. Bienvenue dans la vie quotidienne d'un ingénieur en apprentissage machine ! Puisque nous avons corrigé l'identifiant du modèle, le problème doit se situer dans le dépôt lui-même. Une façon rapide d'accéder au contenu d'un dépôt sur le 🤗 *Hub* est via la fonction `list_repo_files()` de la bibliothèque `huggingface_hub` : ```python from huggingface_hub import list_repo_files @@ -150,7 +150,7 @@ list_repo_files(repo_id=model_checkpoint) ['.gitattributes', 'README.md', 'pytorch_model.bin', 'special_tokens_map.json', 'tokenizer_config.json', 'training_args.bin', 'vocab.txt'] ``` -Intéressant. Il ne semble pas y avoir de fichier *config.json* dans le référentiel ! Pas étonnant que notre `pipeline` n'ait pas pu charger le modèle ; notre collègue a dû oublier de pousser ce fichier vers le *Hub* après l'avoir mis au point. Dans ce cas, le problème semble assez simple à résoudre : nous pouvons lui demander d'ajouter le fichier, ou, puisque nous pouvons voir à partir de l'ID du modèle que le modèle pré-entraîné utilisé est [`distilbert-base-uncased`](https://huggingface.co/distilbert-base-uncased), nous pouvons télécharger la configuration de ce modèle et la pousser dans notre dépôt pour voir si cela résout le problème. Essayons cela. En utilisant les techniques apprises dans le [Chapitre 2](/course/fr/chapter2), nous pouvons télécharger la configuration du modèle avec la classe `AutoConfig` : +Intéressant. Il ne semble pas y avoir de fichier *config.json* dans le dépôt ! Pas étonnant que notre `pipeline` n'ait pas pu charger le modèle. Notre collègue a dû oublier de pousser ce fichier vers le *Hub* après l'avoir *finetuné*. Dans ce cas, le problème semble assez simple à résoudre : nous pouvons lui demander d'ajouter le fichier, ou, puisque nous pouvons voir à partir de l'identifiant du modèle que le modèle pré-entraîné utilisé est [`distilbert-base-uncased`](https://huggingface.co/distilbert-base-uncased), nous pouvons télécharger la configuration de ce modèle et la pousser dans notre dépôt pour voir si cela résout le problème. Essayons cela. En utilisant les techniques apprises dans le [chapitre 2](/course/fr/chapter2), nous pouvons télécharger la configuration du modèle avec la classe `AutoConfig` : ```python from transformers import AutoConfig @@ -161,7 +161,7 @@ config = AutoConfig.from_pretrained(pretrained_checkpoint) -🚨 L'approche que nous adoptons ici n'est pas infaillible, puisque notre collègue peut avoir modifié la configuration de `distilbert-base-uncased` avant d'affiner le modèle. Dans la vie réelle, nous voudrions vérifier avec lui d'abord, mais pour les besoins de cette section, nous supposerons qu'il a utilisé la configuration par défaut. +🚨 L'approche que nous adoptons ici n'est pas infaillible puisque notre collègue peut avoir modifié la configuration de `distilbert-base-uncased` avant de finetuner le modèle. Dans la vie réelle, nous voudrions vérifier avec lui d'abord, mais pour les besoins de cette section nous supposerons qu'il a utilisé la configuration par défaut. @@ -171,7 +171,7 @@ Nous pouvons ensuite le pousser vers notre dépôt de modèles avec la fonction config.push_to_hub(model_checkpoint, commit_message="Add config.json") ``` -Maintenant, nous pouvons tester si cela a fonctionné en chargeant le modèle depuis le dernier commit de la branche `main` : +Maintenant, nous pouvons tester si cela a fonctionné en chargeant le modèle depuis le dernier *commit* de la branche `main` : ```python reader = pipeline("question-answering", model=model_checkpoint, revision="main") @@ -189,16 +189,17 @@ frameworks, so you can use your favourite tools for a wide variety of tasks! context_fr = r""" La réponse à des questions consiste à extraire une réponse d'un texte -à partir d'une question. Un exemple de jeu de données de réponse aux questions est le jeu de données SQuAD -qui est entièrement basé sur cette tâche. Si vous souhaitez affiner un modèle -modèle sur une tâche SQuAD, vous pouvez utiliser le fichier +à partir d'une question. Un exemple de jeu de données de réponse aux questions est le +jeu de données SQuAD qui est entièrement basé sur cette tâche. Si vous souhaitez finetuner +un modèle sur une tâche SQuAD, vous pouvez utiliser le fichier exemples/pytorch/question-answering/run_squad.py. 🤗 Transformers est interopérable avec les frameworks PyTorch, TensorFlow et JAX. de sorte que vous pouvez utiliser vos outils préférés pour une grande variété de tâches ! """ -question = "What is extractive question answering?" # Qu'est-ce que la réponse extractive aux questions ? +question = "What is extractive question answering?" +# Qu'est-ce que la réponse extractive aux questions ? reader(question=question, context=context) ``` @@ -206,23 +207,22 @@ reader(question=question, context=context) {'score': 0.38669535517692566, 'start': 34, 'end': 95, - 'answer': 'the task of extracting an answer from a text given a question'} # la tâche consistant à extraire une réponse d'un texte à partir d'une question. + 'answer': 'the task of extracting an answer from a text given a question'} + # la tâche consistant à extraire une réponse d'un texte à partir d'une question. ``` Woohoo, ça a marché ! Récapitulons ce que vous venez d'apprendre : - les messages d'erreur en Python sont appelés _tracebacks_ et sont lus de bas en haut. La dernière ligne du message d'erreur contient généralement les informations dont vous avez besoin pour localiser la source du problème, -- si la dernière ligne ne contient pas suffisamment d'informations, remontez dans la traceback et voyez si vous pouvez identifier où l'erreur se produit dans le code source, -- si aucun des messages d'erreur ne peut vous aider à déboguer le problème, essayez de rechercher en ligne une solution à un problème similaire. -- l'`huggingface_hub`, -// 🤗 *Hub* ? -fournit une suite d'outils que vous pouvez utiliser pour interagir avec et déboguer les dépôts sur le *Hub*. +- si la dernière ligne ne contient pas suffisamment d'informations, remontez dans le *traceback* et voyez si vous pouvez identifier où l'erreur se produit dans le code source, +- si aucun des messages d'erreur ne peut vous aider à déboguer le problème, essayez de rechercher en ligne une solution à un problème similaire, +- l'`huggingface_hub` fournit une suite d'outils que vous pouvez utiliser pour interagir avec et déboguer les dépôts sur le *Hub*. Maintenant que vous savez comment déboguer un pipeline, examinons un exemple plus délicat dans la passe avant du modèle lui-même. ## Déboguer la passe avant de votre modèle -Bien que le `pipeline` soit parfait pour la plupart des applications où vous devez générer rapidement des prédictions, vous aurez parfois besoin d'accéder aux logits du modèle (par exemple, si vous avez un post-traitement personnalisé que vous souhaitez appliquer). Pour voir ce qui peut mal tourner dans ce cas, commençons par récupérer le modèle et le *tokenizer* de notre `pipeline` : +Bien que le `pipeline` soit parfait pour la plupart des applications où vous devez générer rapidement des prédictions, vous aurez parfois besoin d'accéder aux logits du modèle (par exemple si vous avez un post-traitement personnalisé que vous souhaitez appliquer). Pour voir ce qui peut mal tourner dans ce cas, commençons par récupérer le modèle et le *tokenizer* de notre `pipeline` : ```python tokenizer = reader.tokenizer @@ -232,10 +232,10 @@ model = reader.model Ensuite, nous avons besoin d'une question, alors voyons si nos *frameworks* préférés sont supportés : ```python -question = "Which frameworks can I use?" +question = "Which frameworks can I use?" # Quel frameworks puis-je utiliser ? ``` -Comme nous l'avons vu dans le [Chapitre 7](/course/fr/chapter7), les étapes habituelles que nous devons suivre sont la tokénisation des entrées, l'extraction des logits des *tokens* de début et de fin, puis le décodage de l'empan de réponse : +Comme nous l'avons vu dans le [chapitre 7](/course/fr/chapter7), les étapes habituelles que nous devons suivre sont la tokénisation des entrées, l'extraction des logits des *tokens* de début et de fin, puis le décodage de l'étendue de la réponse : ```python import torch @@ -245,9 +245,9 @@ input_ids = inputs["input_ids"][0] outputs = model(**inputs) answer_start_scores = outputs.start_logits answer_end_scores = outputs.end_logits -# Obtenez le début de réponse le plus probable avec l'argmax du score. +# Pour obtenir le début de réponse le plus probable avec l'argmax du score answer_start = torch.argmax(answer_start_scores) -# Obtenir la fin de réponse la plus probable avec l'argmax du score +# Pour obtenir la fin de réponse la plus probable avec l'argmax du score 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]) @@ -299,7 +299,7 @@ AttributeError: 'list' object has no attribute 'size' """ ``` -Oh là là, il semble que nous ayons un bug dans notre code ! Mais nous n'avons pas peur d'un petit débogage. Vous pouvez utiliser le débogueur Python dans un *notebook* : +Il semble que nous ayons un *bug* dans notre code ! Mais il ne nous fait pas peur. Nous pouvons utiliser le débogueur Python dans un *notebook* : @@ -317,7 +317,7 @@ inputs["input_ids"][:5] [101, 2029, 7705, 2015, 2064] ``` -Cela ressemble certainement à une `list` ordinaire de Python, mais vérifions le type : +Cela ressemble certainement à une `list` ordinaire en Python mais vérifions le type : ```python type(inputs["input_ids"]) @@ -327,7 +327,7 @@ type(inputs["input_ids"]) list ``` -Oui, c'est bien une `list` Python. Alors, qu'est-ce qui a mal tourné ? Rappelez-vous du [Chapitre 2](/course/fr/chapter2) que les classes `AutoModelForXxx` dans 🤗 Transformers opèrent sur des _tenseurs_ (soit dans PyTorch ou TensorFlow), et une opération commune est d'extraire les dimensions d'un tenseur en utilisant `Tensor.size()` dans, disons, PyTorch. Jetons un autre coup d'oeil à la *traceback*, pour voir quelle ligne a déclenché l'exception : +Oui, c'est bien une `list` Python. Alors, qu'est-ce qui a mal tourné ? Rappelez-vous que dans le [chapitre 2](/course/fr/chapter2) nous avons vu que les classes `AutoModelForXxx` opèrent sur des _tenseurs_ (soit dans PyTorch ou TensorFlow) et qu'une opération commune est d'extraire les dimensions d'un tenseur en utilisant `Tensor.size()`. Jetons un autre coup d'oeil au *traceback* pour voir quelle ligne a déclenché l'exception : ``` ~/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) @@ -340,7 +340,7 @@ Oui, c'est bien une `list` Python. Alors, qu'est-ce qui a mal tourné ? Rappelez AttributeError: 'list' object has no attribute 'size' ``` -Il semble que notre code ait essayé d'appeler `input_ids.size()`, mais cela ne fonctionne clairement pas pour une `liste` Python, qui est juste un conteneur. Comment pouvons-nous résoudre ce problème ? La recherche du message d'erreur sur Stack Overflow donne quelques [réponses](https://stackoverflow.com/search?q=AttributeError%3A+%27list%27+object+has+no+attribute+%27size%27&s=c15ec54c-63cb-481d-a749-408920073e8f) pertinentes. En cliquant sur la première, une question similaire à la nôtre s'affiche, avec la réponse indiquée dans la capture d'écran ci-dessous : +Il semble que notre code ait essayé d'appeler `input_ids.size()`, mais cela ne fonctionne clairement pas pour une `list` Python qui est juste un conteneur. Comment pouvons-nous résoudre ce problème ? La recherche du message d'erreur sur Stack Overflow donne quelques [réponses](https://stackoverflow.com/search?q=AttributeError%3A+%27list%27+object+has+no+attribute+%27size%27&s=c15ec54c-63cb-481d-a749-408920073e8f) pertinentes. En cliquant sur la première, une question similaire à la nôtre s'affiche, avec la réponse indiquée dans la capture d'écran ci-dessous :
An answer from Stack Overflow. @@ -354,9 +354,9 @@ input_ids = inputs["input_ids"][0] outputs = model(**inputs) answer_start_scores = outputs.start_logits answer_end_scores = outputs.end_logits -# Obtenez le début de réponse le plus probable avec l'argmax du score. +# Pour obtenir le début de réponse le plus probable avec l'argmax du score answer_start = torch.argmax(answer_start_scores) -# Obtenir la fin de réponse la plus probable avec l'argmax du score +# Pour obtenir la fin de réponse la plus probable avec l'argmax du score 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]) @@ -372,4 +372,4 @@ Answer: pytorch, tensorflow, and jax # pytorch, tensorflow et jax """ ``` -Super, ça a marché ! Voilà un excellent exemple de l'utilité de Stack Overflow : en identifiant un problème similaire, nous avons pu bénéficier de l'expérience d'autres membres de la communauté. Cependant, une recherche de ce type ne donne pas toujours une réponse pertinente, alors que faire dans ce cas ? Heureusement, il existe une communauté accueillante de développeurs sur le [forum d'Hugging Face](https://discuss.huggingface.co/) qui peut vous aider ! Dans la prochaine section, nous verrons comment rédiger de bonnes questions de forum qui ont des chances d'obtenir une réponse. +Super, ça a marché ! Voilà un excellent exemple de l'utilité de Stack Overflow : en identifiant un problème similaire, nous avons pu bénéficier de l'expérience d'autres membres de la communauté. Cependant, une recherche de ce type ne donne pas toujours une réponse pertinente. Que faire alors dans ce cas ? Heureusement, il existe une communauté accueillante de développeurs sur le [forum d'Hugging Face](https://discuss.huggingface.co/) qui peut vous aider ! Dans la prochaine section, nous verrons comment rédiger de bonnes questions sur les forums pour avoir de bonnes chances d'obtenir une réponse. diff --git a/chapters/fr/chapter8/3.mdx b/chapters/fr/chapter8/3.mdx index 526fe482b..a75b49f95 100644 --- a/chapters/fr/chapter8/3.mdx +++ b/chapters/fr/chapter8/3.mdx @@ -9,23 +9,23 @@ -Le [forum d'Hugging Face](https://discuss.huggingface.co) est un endroit idéal pour obtenir de l'aide de l'équipe open source et de la communauté Hugging Face au sens large. Voici à quoi ressemble la page principale tous les jours : +Le [forum d'Hugging Face](https://discuss.huggingface.co) est un endroit idéal pour obtenir de l'aide de l'équipe open source d'Hugging Face et de la communauté au sens large. Voici à quoi ressemble la page principale :
The Hugging Face forums.
-ODans la partie gauche, vous pouvez voir toutes les catégories dans lesquelles les différents sujets sont regroupés, tandis que la partie droite montre les sujets les plus récents. Un sujet est un message qui contient un titre, une catégorie et une description ; il est assez similaire au format des *issues* GitHub que nous avons vu lors de la création de notre propre jeu de données dans [Chapitre 5](/course/fr/chapter5). Comme son nom l'indique, la catégorie [*Beginners*](https://discuss.huggingface.co/c/beginners/5) est principalement destinée aux personnes qui débutent avec les bibliothèques et l'écosystème Hugging Face. Toute question sur l'une des bibliothèques est la bienvenue ici, que ce soit pour déboguer du code ou pour demander de l'aide sur la façon de faire quelque chose. (Cela dit, si votre question concerne une bibliothèque en particulier, vous devriez probablement vous diriger vers la catégorie de bibliothèque correspondante sur le forum). +Dans la partie gauche, vous pouvez voir toutes les catégories dans lesquelles les différents sujets sont regroupés, tandis que la partie droite montre les sujets les plus récents. Un sujet est un message qui contient un titre, une catégorie et une description. C'est assez similaire au format des *issues* GitHub que nous avons vu lors de la création de notre propre jeu de données dans le [chapitre 5](/course/fr/chapter5). Comme son nom l'indique, la catégorie [*Beginners*](https://discuss.huggingface.co/c/beginners/5) est principalement destinée aux personnes qui débutent avec les bibliothèques et l'écosystème d'Hugging Face. Toute question sur l'une des bibliothèques est la bienvenue ici, que ce soit pour déboguer du code ou pour demander de l'aide sur la façon de faire quelque chose. (Cela dit, si votre question concerne une bibliothèque en particulier, vous devriez probablement vous diriger vers la catégorie de bibliothèque correspondante sur le forum). -De même, les catégories [*Intermediate*](https://discuss.huggingface.co/c/intermediate/6) et [*Research*](https://discuss.huggingface.co/c/research/7) sont destinées aux questions plus avancées, par exemple sur les bibliothèques ou sur une avancée en recherche en NLP dont vous aimeriez discuter. +De même, les catégories [*Intermediate*](https://discuss.huggingface.co/c/intermediate/6) et [*Research*](https://discuss.huggingface.co/c/research/7) sont destinées aux questions plus avancées. Par exemple sur les bibliothèques ou sur une avancée en recherche en NLP dont vous aimeriez discuter. -Et naturellement, nous devrions aussi mentionner la catégorie [*Course*](https://discuss.huggingface.co/c/course/20), où vous pouvez poser toutes les questions que vous avez en rapport avec le cours d'Hugging Face ! +Et naturellement, nous devrions aussi mentionner la catégorie [*Course*](https://discuss.huggingface.co/c/course/20) où vous pouvez poser toutes les questions que vous avez en rapport avec le cours d'Hugging Face ! -Une fois que vous aurez choisi une catégorie, vous serez prêt à rédiger votre premier sujet. Vous pouvez trouver quelques [directives](https://discuss.huggingface.co/t/how-to-request-support/3128) dans le forum sur la façon de le faire, et dans cette section, nous allons jeter un coup d'oeil à certaines caractéristiques d'un bon sujet. +Une fois une catégorie choisie, vous êtes prêt à rédiger votre premier sujet. Vous pouvez trouver quelques [indications](https://discuss.huggingface.co/t/how-to-request-support/3128) dans le forum sur la façon de le faire. Dans cette section, nous allons jeter un coup d'oeil à certaines caractéristiques d'un bon sujet. ## Rédiger un bon message sur le forum -A titre d'exemple, supposons que nous essayons de générer des embeddings à partir d'articles Wikipédia pour créer un moteur de recherche personnalisé. Comme d'habitude, nous chargeons le *tokenizer* et le modèle comme suit : +A titre d'exemple, supposons que nous essayons de générer des enchâssements à partir d'articles Wikipédia pour créer un moteur de recherche personnalisé. Comme d'habitude, nous chargeons le *tokenizer* et le modèle comme suit : ```python from transformers import AutoTokenizer, AutoModel @@ -35,7 +35,7 @@ tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) model = AutoModel.from_pretrained(model_checkpoint) ``` -Supposons maintenant que nous essayons d'intégrer une section entière de l'[article Wikipedia](https://en.wikipedia.org/wiki/Transformers) sur Transformers (la franchise de films, pas la bibliothèque !): +Supposons maintenant que nous essayons d'enchâsser une section entière de l'[article Wikipedia](https://en.wikipedia.org/wiki/Transformers) sur Transformers (la franchise de films, pas la bibliothèque !): ```python text = """ @@ -82,44 +82,46 @@ Creation Matrix (giving life to Transformers), and its guardian Alpha Trion. text_fr = """ Génération 1 est un terme rétroactif pour les personnages de Transformers qui sont apparus -sont apparus entre 1984 et 1993. Les Transformers ont commencé avec les lignes de jouets japonaises des années 1980 -japonaises des années 1980, Micro Change et Diaclone. Elles présentaient des robots capables de se transformer -en véhicules de tous les jours, en objets électroniques ou en armes. Hasbro a acheté les jouets Micro -Change et Diaclone, et s'est associé à Takara. Marvel Comics est engagé par -Hasbro pour créer l'histoire de fond ; le rédacteur en chef Jim Shooter a écrit une histoire générale -et confie la tâche de créer les personnages au scénariste Dennis O'Neil. -Mécontent du travail d'O'Neil (bien que ce dernier ait créé le nom "Optimus Prime"), -Shooter choisit Bob Budiansky pour créer les personnages. - -Les mecha de Transformers ont été en grande partie conçus par Shōji Kawamori, le créateur de -de l'anime japonais Macross (qui a été adapté en Robotech en Amérique du Nord). -en Amérique du Nord). Kawamori a eu l'idée de transformer des -mechas transformables alors qu'il travaillait sur les franchises Diaclone et Macross au début des années 1980. -(comme le VF-1 Valkyrie dans Macross et Robotech), et ses méchas Diaclone -ont plus tard servi de base à Transformers. +entre 1984 et 1993. Les Transformers ont commencé avec les lignes de jouets japonaises +des années 1980, Micro Change et Diaclone. Elles présentaient des robots capables +de se transformer en véhicules de tous les jours, en objets électroniques ou en armes. +Hasbro a acheté les jouets Micro Change et Diaclone, et s'est associé à Takara. +Marvel Comics est engagé par Hasbro pour créer l'histoire de fond ; le rédacteur en chef +Jim Shooter a écrit une histoire générale et confie la tâche de créer les personnages au +scénariste Dennis O'Neil. Mécontent du travail d'O'Neil (bien que ce dernier ait créé +le nom "Optimus Prime"), Shooter choisit Bob Budiansky pour créer les personnages. + +Les mecha de Transformers ont été en grande partie conçus par Shōji Kawamori, le créateur +de l'anime japonais Macross (qui a été adapté en Robotech en Amérique du Nord). Kawamori +a eu l'idée de transformer des mechas transformables alors qu'il travaillait sur les +franchises Diaclone et Macross au début des années 1980 (comme le VF-1 Valkyrie dans +Macross et Robotech), et ses méchas Diaclone ont plus tard servi de base à Transformers. Le concept principal de la Génération 1 est que l'héroïque Optimus Prime, le méchant -le méchant Megatron, et leurs meilleurs soldats s'écrasent sur une Terre préhistorique -dans l'Arche et le Némésis avant de se réveiller en 1985, Cybertron traversant à toute allure la zone neutre en raison de l'effet de la zone neutre. -la Zone Neutre, conséquence de la guerre. La bande dessinée Marvel faisait à l'origine partie -de l'univers principal de Marvel, avec des apparitions de Spider-Man et Nick Fury, +Megatron, et leurs meilleurs soldats s'écrasent sur une Terre préhistorique dans l'Arche +et le Némésis avant de se réveiller en 1985, Cybertron traversant à toute allure la zone +neutre en raison de la guerre. La bande dessinée Marvel faisait à l'origine partie +de l'univers principal de Marvel, avec des apparitions de Spider-Man et Nick Fury, plus quelques caméos, ainsi qu'une visite à la Terre Sauvage. -La série télévisée Transformers a commencé à peu près à la même époque. Produite par Sunbow -Productions et Marvel Productions, puis Hasbro Productions, dès le début elle a -contredit les histoires de Budiansky. La série TV montre les Autobots cherchant -de nouvelles sources d'énergie et s'écrasent lors de l'attaque des Decepticons. Marvel -a interprété les Autobots comme la destruction d'un astéroïde malveillant s'approchant de Cybertron. -Shockwave est loyal envers Megatron dans la série TV, et maintient Cybertron dans une impasse en son absence. -Cybertron dans une impasse pendant son absence, mais dans la BD, il tente de prendre le commandement -des Decepticons. La série télévisée s'écarte aussi radicalement des origines que -Budiansky avait créé pour les Dinobots, le Decepticon devenu Autobot Jetfire -(connu sous le nom de Skyfire à la télévision), les Constructicons (qui s'associent pour former -Devastator),[19][20] et Oméga Suprême. La bande dessinée Marvel établit très tôt -que Prime manie la matrice de création, qui donne la vie aux machines. Dans la -saison, l'épisode en deux parties The Key to Vector Sigma a introduit l'ancien ordinateur -l'ancien ordinateur Vector Sigma, qui servait le même objectif original que la -matrice de création (donner la vie aux Transformers), et son gardien Alpha Trion. +La série télévisée Transformers a commencé à peu près à la même époque. +Produite par Sunbow Productions et Marvel Productions, puis Hasbro Productions, +dès le début elle a contredit les histoires de Budiansky. La série TV montre les Autobots +cherchant de nouvelles sources d'énergie et s'écrasent lors de l'attaque des Decepticons. +Marvel a interprété les Autobots comme la destruction d'un astéroïde malveillant +s'approchant de Cybertron. Shockwave est loyal envers Megatron dans la série TV, +et maintient Cybertron dans une impasse en son absence. +Cybertron dans une impasse pendant son absence, mais dans la BD, +il tente de prendre le commandement des Decepticons. +La série télévisée s'écarte aussi radicalement des origines que Budiansky avait +créé pour les Dinobots, le Decepticon devenu Autobot Jetfire +(connu sous le nom de Skyfire à la télévision), +les Constructicons (qui s'associent pour former Devastator) et Oméga Suprême. +La bande dessinée Marvel établit très tôt que Prime manie la matrice de création, +qui donne la vie aux machines. Dans la saison, l'épisode en deux parties +The Key to Vector Sigma a introduit l'ancien ordinateur l'ancien ordinateur +Vector Sigma, qui servait le même objectif original que la matrice de création +(donner la vie aux Transformers), et son gardien Alpha Trion. """ inputs = tokenizer(text, return_tensors="pt") @@ -130,7 +132,7 @@ logits = model(**inputs).logits IndexError: index out of range in self ``` -Oh-oh, nous avons rencontré un problème. Le message d'erreur est bien plus énigmatique que ceux que nous avons vus dans la [section 2](/course/chapter8/fr/section2) ! Nous n'arrivons pas à comprendre l'historique complet, alors nous décidons de nous tourner vers le forum d'Hugging Face pour obtenir de l'aide. Comment pouvons-nous élaborer le sujet ? +Oh nous avons rencontré un problème. Le message d'erreur est bien plus énigmatique que ceux que nous avons vus dans la [section 2](/course/chapter8/fr/section2) ! Nous n'arrivons pas à comprendre le *traceback* complet, alors nous décidons de nous tourner vers le forum d'Hugging Face pour obtenir de l'aide. Comment pouvons-nous élaborer le sujet ? Pour commencer, nous devons cliquer sur le bouton *New Topic* dans le coin supérieur droit (notez que pour créer un sujet, nous devons être connectés) : @@ -152,19 +154,19 @@ Puisque l'erreur semble concerner exclusivement 🤗 *Transformers*, nous allons Bien que ce sujet contienne le message d'erreur pour lequel nous avons besoin d'aide, il y a quelques problèmes avec la façon dont il est écrit : -1. le titre n'est pas très descriptif, de sorte que toute personne parcourant le forum ne sera pas en mesure de dire de quoi il s'agit sans lire également le corps du sujet, -2. le corps du texte ne fournit pas suffisamment d'informations sur _l'origine de l'erreur et sur _la manière de la reproduire, +1. le titre n'est pas très descriptif, ainsi toute personne parcourant le forum ne sera pas en mesure de dire de quoi il s'agit sans lire également le corps du sujet, +2. le corps du texte ne fournit pas suffisamment d'informations sur *l'origine* de l'erreur et sur *la manière* de la reproduire, 3. le sujet s'adresse directement à quelques personnes sur un ton quelque peu exigeant. -Les sujets comme celui-ci ne sont pas susceptibles d'obtenir une réponse rapide (si tant est qu'ils en obtiennent une), alors voyons comment nous pouvons l'améliorer. Commençons par la première question, celle du choix d'un bon titre. +Les sujets comme celui-ci ne sont pas susceptibles d'obtenir une réponse rapide (si tant est qu'ils en obtiennent une) alors voyons comment nous pouvons l'améliorer. Commençons par la première question, celle du choix d'un bon titre. ### Choisir un titre descriptif -Si vous essayez d'obtenir de l'aide pour résoudre un bogue dans votre code, une bonne règle de base consiste à inclure suffisamment d'informations dans le titre pour que les autres puissent rapidement déterminer s'ils pensent pouvoir répondre à votre question ou non. Dans notre exemple, nous connaissons le nom de l'exception qui est levée et nous savons qu'elle est déclenchée dans la passe avant du modèle, où nous appelons `model(**inputs)`. Pour communiquer cela, un titre possible pourrait être : +Si vous essayez d'obtenir de l'aide pour résoudre un *bug* dans votre code, une bonne règle de base consiste à inclure suffisamment d'informations dans le titre pour que les autres puissent rapidement déterminer s'ils pensent pouvoir répondre à votre question ou non. Dans notre exemple, nous connaissons le nom de l'exception et savons qu'elle est déclenchée dans la passe avant du modèle, où nous appelons `model(**inputs)`. Pour communiquer cela, un titre possible pourrait être : > Source de l'IndexError dans la passe avant d'AutoModel ? -Ce titre indique au lecteur _où_ vous pensez que le bogue provient, et s'il a déjà rencontré un `IndexError`, il y a de fortes chances qu'il sache comment le déboguer. Bien sûr, le titre peut être ce que vous voulez, et d'autres variations comme : +Ce titre indique au lecteur _où_ vous pensez que le *bug* provient, et s'il a déjà rencontré un `IndexError`, il y a de fortes chances qu'il sache comment le déboguer. Bien sûr, le titre peut être ce que vous voulez et d'autres variations comme : > Pourquoi mon modèle produit-il un IndexError ? @@ -172,34 +174,34 @@ pourrait également convenir. Maintenant que nous avons un titre descriptif, voy ### Formatage de vos extraits de code -La lecture du code source est déjà difficile dans un IDE, mais c'est encore plus difficile lorsque le code est copié et collé en texte brut ! Heureusement, les forums Hugging Face supportent l'utilisation de Markdown, donc vous devriez toujours entourer vos blocs de code avec trois *backticks* (```) pour qu'ils soient plus facilement lisibles. Faisons cela pour embellir le message d'erreur et pendant que nous y sommes, rendons le corps un peu plus poli que notre version originale : +La lecture du code source est déjà difficile dans un IDE, mais c'est encore plus difficile lorsque le code est copié et collé en texte brut ! Heureusement, le forum d'Hugging Face supporte l'utilisation de Markdown donc vous devriez toujours entourer vos blocs de code avec trois *backticks* (```) pour qu'ils soient plus facilement lisibles. Faisons cela pour embellir le message d'erreur et pendant que nous y sommes, rendons le corps un peu plus poli que notre version originale :
Our revised forum topic, with proper code formatting.
-Comme vous pouvez le voir dans la capture d'écran, le fait d'entourer les blocs de code de guillemets convertit le texte brut en code formaté, avec un style de couleur ! Notez également que des backticks simples peuvent être utilisés pour formater des variables en ligne, comme nous l'avons fait pour `distilbert-base-uncased`. Ce sujet a l'air bien meilleur, et avec un peu de chance, nous pourrions trouver quelqu'un dans la communauté qui pourrait deviner à quoi correspond l'erreur. Cependant, au lieu de compter sur la chance, rendons la vie plus facile en incluant la *traceback* dans ses moindres détails ! +Comme vous pouvez le voir dans la capture d'écran, le fait d'entourer les blocs de code de *backticks* convertit le texte brut en code formaté, avec un style de couleur ! Notez également que des *backticks* simples peuvent être utilisés pour formater des variables en ligne comme nous l'avons fait pour `distilbert-base-uncased`. Ce sujet a l'air bien meilleur, et avec un peu de chance, nous pourrions trouver quelqu'un dans la communauté qui pourrait deviner à quoi correspond l'erreur. Cependant, au lieu de compter sur la chance, rendons la vie plus facile en incluant le *traceback* dans ses moindres détails ! -### Inclure la *traceback* complète +### Inclure le traceback complet -Puisque la dernière ligne de la *traceback* est souvent suffisante pour déboguer votre propre code, il peut être tentant de ne fournir que cela dans votre sujet pour "gagner de la place". Bien que bien intentionné, cela rend en fait le débogage du problème _plus difficile_ pour les autres, car les informations situées plus haut dans la *traceback* peuvent également être très utiles. Une bonne pratique consiste donc à copier et coller la *traceback* _entière_, en veillant à ce qu'elle soit bien formatée. Comme ces tracebacks peuvent être assez longs, certaines personnes préfèrent les montrer après avoir expliqué le code source. C'est ce que nous allons faire. Maintenant, notre sujet de forum ressemble à ce qui suit : +Puisque la dernière ligne de le *traceback* est souvent suffisante pour déboguer votre propre code, il peut être tentant de ne fournir que cela dans votre sujet pour "gagner de la place". Bien que bien intentionné, cela rend en fait le débogage du problème _plus difficile_ pour les autres, car les informations situées plus haut dans le *traceback* peuvent également être très utiles. Une bonne pratique consiste donc à copier et coller le *traceback* _entier_, en veillant à ce qu'elle soit bien formatée. Comme ces tracebacks peuvent être assez longs, certaines personnes préfèrent les montrer après avoir expliqué le code source. C'est ce que nous allons faire. Maintenant, notre sujet de forum ressemble à ce qui suit :
Our example forum topic, with the complete traceback.
-Ceci est beaucoup plus informatif, et un lecteur attentif pourrait être en mesure d'indiquer que le problème semble être dû à la transmission d'une longue entrée en raison de cette ligne dans la *traceback* : +Ceci est beaucoup plus informatif et un lecteur attentif pourrait être en mesure d'indiquer que le problème semble être dû à la transmission d'une longue entrée en raison de cette ligne dans le *traceback* : -> La longueur de la séquence des indices de *tokens* est supérieure à la longueur de séquence maximale spécifiée pour ce modèle (583 > 512). +> Token indices sequence length is longer than the specified maximum sequence length for this model (583 > 512). -Cependant, nous pouvons leur faciliter les choses en leur fournissant le code réel qui a déclenché l'erreur. C'est ce que nous allons faire maintenant. +Cependant, nous pouvons leur faciliter les choses en leur fournissant le code qui a déclenché l'erreur. C'est ce que nous allons faire maintenant. ### Fournir un exemple reproductible -Si vous avez déjà essayé de déboguer le code de quelqu'un d'autre, vous avez probablement d'abord essayé de recréer le problème qu'il a signalé afin de pouvoir commencer à travailler sur la *traceback* pour identifier l'erreur. Il en va de même lorsqu'il s'agit d'obtenir (ou de donner) de l'aide sur les forums. Il est donc très utile de pouvoir fournir un petit exemple qui reproduit l'erreur. La moitié du temps, le simple fait de faire cet exercice vous aidera à comprendre ce qui ne va pas. Dans tous les cas, la pièce manquante de notre exemple est de montrer les _entrées_ que nous avons fournies au modèle. En faisant cela, nous obtenons quelque chose comme l'exemple complet suivant : +Si vous avez déjà essayé de déboguer le code de quelqu'un d'autre, vous avez probablement d'abord essayé de recréer le problème qu'il a signalé afin de pouvoir commencer à travailler sur le *traceback* pour identifier l'erreur. Il en va de même lorsqu'il s'agit d'obtenir (ou de donner) de l'aide sur les forums. Il est donc très utile de pouvoir fournir un petit exemple qui reproduit l'erreur. La moitié du temps, le simple fait de faire cet exercice vous aidera à comprendre ce qui ne va pas. Dans tous les cas, la pièce manquante de notre exemple est de montrer les _entrées_ que nous avons fournies au modèle. En faisant cela, nous obtenons quelque chose comme l'exemple complet suivant :
The final version of our forum topic.
-Ce sujet contient maintenant un bon lot d'informations, et il est rédigé d'une manière qui a beaucoup plus de chances d'attirer l'attention de la communauté et d'obtenir une réponse utile. Avec ces directives de base, vous pouvez maintenant créer de superbes sujets pour trouver les réponses à vos questions sur 🤗 *Transformers* ! +Ce sujet contient maintenant un bon lot d'informations et il est rédigé d'une manière qui a beaucoup plus de chances d'attirer l'attention de la communauté et d'obtenir une réponse utile. Avec ces directives de base, vous pouvez maintenant créer de superbes sujets pour trouver les réponses à vos questions sur 🤗 *Transformers* ! diff --git a/chapters/fr/chapter8/4.mdx b/chapters/fr/chapter8/4.mdx index 8d1c6ab77..2ea4a9ece 100644 --- a/chapters/fr/chapter8/4.mdx +++ b/chapters/fr/chapter8/4.mdx @@ -9,17 +9,17 @@ {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter8/section4_pt.ipynb"}, ]} /> -Vous avez écrit un magnifique script pour entraîner ou *finetuner* un modèle sur une tâche donnée, en suivant consciencieusement les conseils du [Chapitre 7](/course/fr/chapter7). Mais lorsque vous lancez la commande `model.fit()`, quelque chose d'horrible se produit : vous obtenez une erreur 😱 ! Ou pire, tout semble aller bien et l'entraînement se déroule sans erreur, mais le modèle résultant est merdique. Dans cette section, nous allons vous montrer ce que vous pouvez faire pour déboguer ce genre de problèmes. +Vous avez écrit un magnifique script pour entraîner ou *finetuner* un modèle sur une tâche donnée en suivant consciencieusement les conseils du [chapitre 7](/course/fr/chapter7). Mais lorsque vous lancez la commande `model.fit()`, quelque chose d'horrible se produit : vous obtenez une erreur 😱 ! Ou pire, tout semble aller bien et l'entraînement se déroule sans erreur mais le modèle résultant est mauvais. Dans cette section, nous allons vous montrer ce que vous pouvez faire pour déboguer ce genre de problèmes. ## Déboguer le pipeline d'entraînement -Le problème lorsque vous rencontrez une erreur dans `trainer.train()` est qu'elle peut provenir de plusieurs sources, car le `Trainer` assemble généralement des batchs de choses. Il convertit les jeux de données en chargeurs de données, donc le problème pourrait être quelque chose d'erroné dans votre jeu de données, ou un problème en essayant de regrouper les éléments des jeux de données ensemble. Ensuite, il prend un batch de données et le transmet au modèle, le problème peut donc se situer dans le code du modèle. Après cela, il calcule les gradients et effectue l'étape d'optimisation, le problème peut donc également se situer dans votre optimiseur. Et même si tout se passe bien pendant l'entraînement, quelque chose peut encore mal tourner pendant l'évaluation si votre métrique pose problème. +Le problème lorsque vous rencontrez une erreur dans `trainer.train()` est qu'elle peut provenir de plusieurs sources, car la fonction `Trainer` assemble généralement des batchs de choses. Elle convertit les jeux de données en chargeurs de données donc le problème pourrait être quelque chose d'erroné dans votre jeu de données, ou un problème en essayant de regrouper les éléments des jeux de données ensemble. Ensuite, elle prend un batch de données et le transmet au modèle, le problème peut donc se situer dans le code du modèle. Après cela, elle calcule les gradients et effectue l'étape d'optimisation, le problème peut donc également se situer dans votre optimiseur. Et même si tout se passe bien pendant l'entraînement, quelque chose peut encore mal tourner pendant l'évaluation si votre métrique pose problème. La meilleure façon de déboguer une erreur qui survient dans `trainer.train()` est de passer manuellement en revue tout le pipeline pour voir où les choses se sont mal passées. L'erreur est alors souvent très facile à résoudre. -Pour le démontrer, nous utiliserons le script suivant qui tente d'ajuster un modèle DistilBERT sur le [jeu de données MNLI](https://huggingface.co/datasets/glue) : +Pour le démontrer, nous utiliserons le script suivant qui tente de *finetuner* un modèle DistilBERT sur le [jeu de données MNLI](https://huggingface.co/datasets/glue) : ```py from datasets import load_dataset, load_metric @@ -78,7 +78,7 @@ Si vous essayez de l'exécuter, vous serez confronté à une erreur plutôt cryp ### Vérifiez vos données -Cela va sans dire, mais si vos données sont corrompues, le `Trainer` ne sera pas capable de former des batchs, et encore moins d'entraîner votre modèle. Donc, tout d'abord, vous devez jeter un coup d'oeil à ce qui se trouve dans votre ensemble d'entraînement. +Cela va sans dire, mais si vos données sont corrompues, le `Trainer` ne sera pas capable de former des batchs et encore moins d'entraîner votre modèle. Donc, tout d'abord, vous devez jeter un coup d'oeil à ce qui se trouve dans votre jeu d'entraînement. Pour éviter d'innombrables heures passées à essayer de corriger quelque chose qui n'est pas la source du bug, nous vous recommandons d'utiliser `trainer.train_dataset` pour vos vérifications et rien d'autre. Faisons donc cela ici : @@ -93,9 +93,9 @@ trainer.train_dataset[0] 'premise': 'Conceptually cream skimming has two basic dimensions - product and geography.'} ``` -Vous remarquez quelque chose d'anormal ? Ceci, en conjonction avec le message d'erreur sur les `input_ids` manquants, devrait vous faire réaliser que ce sont des textes, et non des nombres que le modèle peut comprendre. Ici, l'erreur originale est très trompeuse parce que le `Trainer` enlève automatiquement les colonnes qui ne correspondent pas à la signature du modèle (c'est-à-dire, les arguments attendus par le modèle). Cela signifie qu'ici, tout, sauf les étiquettes, a été éliminé. Il n'y avait donc aucun problème à créer des batchs et à les envoyer ensuite au modèle, qui s'est plaint à son tour de ne pas avoir reçu les bons arguments. +Vous remarquez quelque chose d'anormal ? Ceci, en conjonction avec le message d'erreur sur les `input_ids` manquants, devrait vous faire réaliser que ce sont des textes et non des nombres que le modèle peut comprendre. Ici, l'erreur originale est très trompeuse parce que le `Trainer` enlève automatiquement les colonnes qui ne correspondent pas à la signature du modèle (c'est-à-dire, les arguments attendus par le modèle). Cela signifie qu'ici, tout, sauf les étiquettes, a été éliminé. Il n'y avait donc aucun problème à créer des batchs et à les envoyer ensuite au modèle, qui s'est plaint à son tour de ne pas avoir reçu les bons arguments. -Pourquoi les données n'ont-elles pas été traitées ? Nous avons utilisé la méthode `Dataset.map()` sur les ensembles de données pour appliquer le *tokenizer* sur chaque échantillon. Mais si vous regardez attentivement le code, vous verrez que nous avons fait une erreur en passant les ensembles d'entraînement et d'évaluation au `Trainer`. Au lieu d'utiliser `tokenized_datasets` ici, nous avons utilisé `raw_datasets` 🤦. Alors corrigeons ça ! +Pourquoi les données n'ont-elles pas été traitées ? Nous avons utilisé la méthode `Dataset.map()` sur les jeux de données pour appliquer le *tokenizer* sur chaque échantillon. Mais si vous regardez attentivement le code, vous verrez que nous avons fait une erreur en passant les ensembles d'entraînement et d'évaluation au `Trainer`. Au lieu d'utiliser `tokenized_datasets` ici, nous avons utilisé `raw_datasets` 🤦. Alors corrigeons ça ! ```py from datasets import load_dataset, load_metric @@ -146,13 +146,13 @@ trainer = Trainer( trainer.train() ``` -Ce nouveau code donnera maintenant une erreur différente (progrès !) : +Ce nouveau code donnera maintenant une erreur différente (c'est un progrès !) : ```python out 'ValueError: expected sequence of length 43 at dim 1 (got 37)' ``` -En regardant la trace, nous pouvons voir que l'erreur se produit dans l'étape de collationnement des données : +En regardant le *traceback*, nous pouvons voir que l'erreur se produit dans l'étape de collationnement des données : ```python out ~/git/transformers/src/transformers/data/data_collator.py in torch_default_data_collator(features) @@ -163,9 +163,9 @@ En regardant la trace, nous pouvons voir que l'erreur se produit dans l'étape d 109 return batch ``` -Donc, nous devrions passer à cela. Mais avant cela, finissons d'inspecter nos données, pour être sûrs à 100% qu'elles sont correctes. +Donc, nous devrions passer à cela. Mais avant finissons d'inspecter nos données, pour être sûrs à 100% qu'elles sont correctes. -Une chose que vous devriez toujours faire lorsque vous déboguez une session d'entraînement est de jeter un coup d'oeil aux entrées décodées de votre modèle. Nous ne pouvons pas donner un sens aux chiffres que nous lui fournissons directement, nous devons donc examiner ce que ces chiffres représentent. Dans le domaine de la vision par ordinateur, par exemple, cela signifie regarder les images décodées des pixels que vous passez, dans le domaine de la parole, cela signifie écouter les échantillons audio décodés, et pour notre exemple NLP, cela signifie utiliser notre *tokenizer* pour décoder les entrées : +Une chose que vous devriez toujours faire lorsque vous déboguez une session d'entraînement est de jeter un coup d'oeil aux entrées décodées de votre modèle. Nous ne pouvons pas donner un sens aux chiffres que nous lui fournissons directement, nous devons donc examiner ce que ces chiffres représentent. Dans le domaine de la vision par ordinateur cela signifie regarder les images décodées des pixels que vous passez, dans le domaine de la parole cela signifie écouter les échantillons audio décodés, et pour notre exemple de NLP cela signifie utiliser notre *tokenizer* pour décoder les entrées : ```py tokenizer.decode(trainer.train_dataset[0]["input_ids"]) @@ -175,7 +175,7 @@ tokenizer.decode(trainer.train_dataset[0]["input_ids"]) '[CLS] conceptually cream skimming has two basic dimensions - product and geography. [SEP] product and geography are what make cream skimming work. [SEP]' ``` -Cela semble donc correct. Vous devriez faire cela pour toutes les clés dans les entrées : +Cela semble correct. Vous devriez faire cela pour toutes les clés dans les entrées : ```py trainer.train_dataset[0].keys() @@ -185,7 +185,7 @@ trainer.train_dataset[0].keys() dict_keys(['attention_mask', 'hypothesis', 'idx', 'input_ids', 'label', 'premise']) ``` -Note that the keys that don't correspond to inputs accepted by the model will be automatically discarded, so here we will only keep `input_ids`, `attention_mask`, and `label` (which will be renamed `labels`). To double-check the model signature, you can print the class of your model, then go check its documentation: +Notez que les clés qui ne correspondent pas à des entrées acceptées par le modèle seront automatiquement écartées, donc ici nous ne garderons que `input_ids`, `attention_mask`, et `label` (qui sera renommé `labels`). Pour revérifier la signature du modèle, vous pouvez imprimer la classe de votre modèle, puis aller consulter sa documentation : ```py type(trainer.model) @@ -197,7 +197,7 @@ transformers.models.distilbert.modeling_distilbert.DistilBertForSequenceClassifi Donc dans notre cas, nous pouvons vérifier les paramètres acceptés sur [cette page](https://huggingface.co/transformers/model_doc/distilbert.html#distilbertforsequenceclassification). Le `Trainer` va également enregistrer les colonnes qu'il rejette. -Nous avons vérifié que les IDs d'entrée sont corrects en les décodant. Ensuite, il y a le `attention_mask` : +Nous avons vérifié que les identifiants d'entrée sont corrects en les décodant. Ensuite, il y a le `attention_mask` : ```py tokenizer.decode(trainer.train_dataset[0]["attention_mask"]) @@ -229,7 +229,7 @@ trainer.train_dataset[0]["label"] 1 ``` -Comme les ID d'entrée, c'est un nombre qui n'a pas vraiment de sens en soi. Comme nous l'avons vu précédemment, la correspondance entre les entiers et les noms d'étiquettes est stockée dans l'attribut `names` de la *caractéristique* correspondante de l'ensemble de données : +Comme les identifiants d'entrée, c'est un nombre qui n'a pas vraiment de sens en soi. Comme nous l'avons vu précédemment, la correspondance entre les entiers et les noms d'étiquettes est stockée dans l'attribut `names` de la *caractéristique* correspondante du jeu de données : ```py trainer.train_dataset.features["label"].names @@ -239,30 +239,30 @@ trainer.train_dataset.features["label"].names ['entailment', 'neutral', 'contradiction'] ``` -Donc `1` signifie `neutral`, ce qui signifie que les deux phrases que nous avons vues ci-dessus ne sont pas en contradiction, et que la première n'implique pas la seconde. Cela semble correct ! +Donc `1` signifie `neutral`, ce qui signifie que les deux phrases que nous avons vues ci-dessus ne sont pas en contradiction : la première n'implique pas la seconde. Cela semble correct ! -Nous n'avons pas d'ID de type de *token* ici, puisque DistilBERT ne les attend pas ; si vous en avez dans votre modèle, vous devriez également vous assurer qu'ils correspondent correctement à l'endroit où se trouvent la première et la deuxième phrase dans l'entrée. +Nous n'avons pas de *token* de type identifiant ici puisque DistilBERT ne les attend pas. Si vous en avez dans votre modèle, vous devriez également vous assurer qu'ils correspondent correctement à l'endroit où se trouvent la première et la deuxième phrase dans l'entrée. -✏️ *Votre tour !* Vérifiez que tout semble correct avec le deuxième élément du jeu de données d'entraînement. +✏️ *A votre tour !* Vérifiez que tout semble correct avec le deuxième élément du jeu de données d'entraînement. -Nous ne vérifions ici que l'ensemble d'entraînement, mais vous devez bien sûr vérifier de la même façon les ensembles de validation et de test. +Ici nous ne vérifions que le jeu d'entraînement. Vous devez bien sûr vérifier de la même façon les jeux de validation et de test. -Maintenant que nous savons que nos ensembles de données sont bons, il est temps de vérifier l'étape suivante du pipeline d'entraînement. +Maintenant que nous savons que nos jeux de données sont bons, il est temps de vérifier l'étape suivante du pipeline d'entraînement. ### Des jeux de données aux chargeurs de données -La prochaine chose qui peut mal tourner dans le pipeline d'entraînement est lorsque le `Trainer` essaie de former des batchs à partir de l'ensemble d'entraînement ou de validation. Une fois que vous êtes sûr que les jeux de données du `Trainer` sont corrects, vous pouvez essayer de former manuellement un batch en exécutant ce qui suit (remplacez `train` par `eval` pour le dataloader de validation) : +La prochaine chose qui peut mal tourner dans le pipeline d'entraînement est lorsque le `Trainer` essaie de former des batchs à partir du jeu d'entraînement ou de validation. Une fois que vous êtes sûr que les jeux de données du `Trainer` sont corrects, vous pouvez essayer de former manuellement un batch en exécutant ce qui suit (remplacez `train` par `eval` pour le *dataloader* de validation) : ```py for batch in trainer.get_train_dataloader(): break ``` -Ce code crée le dataloader d'entraînement, puis le parcourt en s'arrêtant à la première itération. Si le code s'exécute sans erreur, vous avez le premier batch d'entraînement que vous pouvez inspecter, et si le code se trompe, vous êtes sûr que le problème se situe dans le dataloader, comme c'est le cas ici : +Ce code crée le *dataloader* d'entraînement puis le parcourt en s'arrêtant à la première itération. Si le code s'exécute sans erreur, vous avez le premier batch d'entraînement que vous pouvez inspecter, et si le code se trompe, vous êtes sûr que le problème se situe dans le *dataloader*, comme c'est le cas ici : ```python out ~/git/transformers/src/transformers/data/data_collator.py in torch_default_data_collator(features) @@ -275,7 +275,7 @@ Ce code crée le dataloader d'entraînement, puis le parcourt en s'arrêtant à ValueError: expected sequence of length 45 at dim 1 (got 76) ``` -L'inspection de la dernière image du traceback devrait suffire à vous donner un indice, mais creusons un peu plus. La plupart des problèmes lors de la création d'un batch sont dus à l'assemblage des exemples en un seul batch, donc la première chose à vérifier en cas de doute est le `collate_fn` utilisé par votre `DataLoader` : +L'inspection de la dernière image du *traceback* devrait suffire à vous donner un indice mais creusons un peu plus. La plupart des problèmes lors de la création d'un batch sont dus à l'assemblage des exemples en un seul batch. La première chose à vérifier en cas de doute est le `collate_fn` utilisé par votre `DataLoader` : ```py data_collator = trainer.get_train_dataloader().collate_fn @@ -286,9 +286,9 @@ data_collator Dict[str, Any]> ``` -C'est donc le collateur `default_data_collator`, mais ce n'est pas ce que nous voulons dans ce cas. Nous voulons rembourrer nos exemples à la phrase la plus longue du batch, ce qui est fait par le collateur `DataCollatorWithPadding`. Et ce collateur de données est censé être utilisé par défaut par le `Trainer`, alors pourquoi n'est-il pas utilisé ici ? +C'est donc `default_data_collator`, mais ce n'est pas ce que nous voulons dans ce cas. Nous voulons rembourrer nos exemples à la phrase la plus longue du batch, ce qui est fait par `DataCollatorWithPadding`. Et cette assembleur de données est censé être utilisé par défaut par le `Trainer`, alors pourquoi n'est-il pas utilisé ici ? -La réponse est que nous n'avons pas passé le `tokenizer` au `Trainer`, donc il ne pouvait pas créer le `DataCollatorWithPadding` que nous voulons. En pratique, il ne faut jamais hésiter à transmettre explicitement le collateur de données que l'on veut utiliser, pour être sûr d'éviter ce genre d'erreurs. Adaptons notre code pour faire exactement cela : +La réponse est que nous n'avons pas passé le `tokenizer` au `Trainer`, donc il ne pouvait pas créer le `DataCollatorWithPadding` que nous voulons. En pratique, il ne faut jamais hésiter à transmettre explicitement l'assembleur de données que l'on veut utiliser pour être sûr d'éviter ce genre d'erreurs. Adaptons notre code pour faire exactement cela : ```py from datasets import load_dataset, load_metric @@ -350,16 +350,16 @@ La bonne nouvelle ? Nous n'avons plus la même erreur qu'avant, ce qui est un pr RuntimeError: CUDA error: CUBLAS_STATUS_ALLOC_FAILED when calling `cublasCreate(handle)` ``` -C'est une mauvaise chose car les erreurs CUDA sont extrêmement difficiles à déboguer en général. Nous verrons dans une minute comment résoudre ce problème, mais terminons d'abord notre analyse de la création de batchs. +C'est une mauvaise chose car les erreurs CUDA sont extrêmement difficiles à déboguer en général. Nous verrons dans une minute comment résoudre ce problème mais terminons d'abord notre analyse de la création de batchs. -Si vous êtes sûr que votre collecteur de données est le bon, vous devriez essayer de l'appliquer sur quelques échantillons de votre ensemble de données : +Si vous êtes sûr que votre collecteur de données est le bon, vous devriez essayer de l'appliquer sur quelques échantillons de votre jeu de données : ```py data_collator = trainer.get_train_dataloader().collate_fn batch = data_collator([trainer.train_dataset[i] for i in range(4)]) ``` -Ce code échouera parce que le `train_dataset` contient des colonnes de type string, que le `Trainer` supprime habituellement. Vous pouvez les supprimer manuellement, ou si vous voulez reproduire exactement ce que le `Trainer` fait en coulisse, vous pouvez appeler la méthode privée `Trainer._remove_unused_columns()` qui fait cela : +Ce code échouera parce que le `train_dataset` contient des colonnes de type *string* que le `Trainer` supprime habituellement. Vous pouvez les supprimer manuellement ou si vous voulez reproduire exactement ce que le `Trainer` fait en coulisse, vous pouvez appeler la méthode `Trainer._remove_unused_columns()` qui fait cela : ```py data_collator = trainer.get_train_dataloader().collate_fn @@ -371,6 +371,7 @@ Vous devriez alors être en mesure de déboguer manuellement ce qui se passe dan Maintenant que nous avons débogué le processus de création de batch, il est temps d'en passer un dans le modèle ! + ### Passage par le modèle Vous devriez être en mesure d'obtenir un batch en exécutant la commande suivante : @@ -380,11 +381,11 @@ for batch in trainer.get_train_dataloader(): break ``` -Si vous exécutez ce code dans un *notebook*, vous risquez d'obtenir une erreur CUDA similaire à celle que nous avons vue précédemment, auquel cas vous devrez redémarrer votre notebook et réexécuter le dernier extrait sans la ligne `trainer.train()`. C'est la deuxième chose la plus ennuyeuse à propos des erreurs CUDA : elles cassent irrémédiablement votre noyau. La chose la plus ennuyeuse à leur sujet est le fait qu'elles sont difficiles à déboguer. +Si vous exécutez ce code dans un *notebook*, vous risquez d'obtenir une erreur CUDA similaire à celle que nous avons vue précédemment, auquel cas vous devrez redémarrer votre *notebook* et réexécuter le dernier extrait sans la ligne `trainer.train()`. C'est la deuxième chose la plus ennuyeuse à propos des erreurs CUDA : elles cassent irrémédiablement votre noyau. La première plus ennuyeuse est le fait qu'elles sont difficiles à déboguer. -Comment cela se fait-il ? Cela tient à la façon dont les GPU fonctionnent. Ils sont extrêmement efficaces pour exécuter un batch d'opérations en parallèle, mais l'inconvénient est que lorsque l'une de ces instructions entraîne une erreur, vous ne le savez pas immédiatement. Ce n'est que lorsque le programme appelle une synchronisation des multiples processus sur le GPU qu'il réalise que quelque chose s'est mal passé, de sorte que l'erreur est en fait soulevée à un endroit qui n'a rien à voir avec ce qui l'a créée. Par exemple, si nous regardons notre traceback précédent, l'erreur a été soulevée pendant la passe arrière, mais nous verrons dans une minute qu'elle provient en fait de quelque chose dans la passe avant. +Comment cela se fait-il ? Cela tient à la façon dont les GPUs fonctionnent. Ils sont extrêmement efficaces pour exécuter un batch d'opérations en parallèle, mais l'inconvénient est que lorsque l'une de ces instructions entraîne une erreur, vous ne le savez pas immédiatement. Ce n'est que lorsque le programme appelle une synchronisation des multiples processus sur le GPU qu'il réalise que quelque chose s'est mal passé, de sorte que l'erreur est en fait mentionnée à un endroit qui n'a rien à voir avec ce qui l'a créée. Par exemple, si nous regardons notre *traceback* précédent, l'erreur a été soulevée pendant la passe arrière, mais nous verrons dans une minute qu'elle provient en fait de quelque chose dans la passe avant. -Alors comment déboguer ces erreurs ? La réponse est simple : nous ne le faisons pas. À moins que votre erreur CUDA ne soit une erreur out-of-memory (ce qui signifie qu'il n'y a pas assez de mémoire dans votre GPU), vous devez toujours revenir au CPU pour la déboguer. +Alors comment déboguer ces erreurs ? La réponse est simple : nous ne le faisons pas. À moins que votre erreur CUDA ne soit une erreur *out-of-memory* (ce qui signifie qu'il n'y a pas assez de mémoire dans votre GPU), vous devez toujours revenir au CPU pour la déboguer. Pour faire cela dans notre cas, nous devons juste remettre le modèle sur le CPU et l'appeler sur notre batch. Le batch retourné par le `DataLoader` n'a pas encore été déplacé sur le GPU : @@ -403,7 +404,7 @@ outputs = trainer.model.cpu()(**batch) IndexError: Target 2 is out of bounds. ``` -Donc, l'image devient plus claire. Au lieu d'avoir une erreur CUDA, nous avons maintenant une `IndexError` dans le calcul de la perte (donc rien à voir avec le backward pass, comme nous l'avons dit plus tôt). Plus précisément, nous pouvons voir que c'est la cible 2 qui crée l'erreur, donc c'est un très bon moment pour vérifier le nombre de labels de notre modèle : +L'image devient plus claire. Au lieu d'avoir une erreur CUDA, nous avons maintenant une `IndexError` dans le calcul de la perte (donc rien à voir avec la passe arrière comme nous l'avons dit plus tôt). Plus précisément, nous pouvons voir que c'est la cible 2 qui crée l'erreur, donc c'est un bon moment pour vérifier le nombre de labels de notre modèle : ```python trainer.model.config.num_labels @@ -413,7 +414,7 @@ trainer.model.config.num_labels 2 ``` -Avec deux étiquettes, seuls les 0 et les 1 sont autorisés comme cibles, mais d'après le message d'erreur, nous avons obtenu un 2. Obtenir un 2 est en fait normal : si nous nous souvenons des noms d'étiquettes que nous avons extraits plus tôt, il y en avait trois, donc nous avons des indices 0, 1 et 2 dans notre ensemble de données. Le problème est que nous n'avons pas indiqué cela à notre modèle, qui aurait dû être créé avec trois étiquettes. Alors, corrigeons cela ! +Avec deux étiquettes, seuls les 0 et les 1 sont autorisés comme cibles, mais d'après le message d'erreur, nous avons obtenu un 2. Obtenir un 2 est en fait normal : si nous nous souvenons des noms des étiquettes que nous avons extraits plus tôt, il y en avait trois, donc nous avons les indices 0, 1 et 2 dans notre jeu de données. Le problème est que nous n'avons pas indiqué cela à notre modèle, qui aurait dû être créé avec trois étiquettes. Alors, corrigeons cela ! ```py from datasets import load_dataset, load_metric @@ -468,7 +469,7 @@ trainer = Trainer( ) ``` -Nous n'incluons pas encore la ligne `trainer.train()`, pour prendre le temps de vérifier que tout se passe bien. Si nous demandons un batch et le passons à notre modèle, il fonctionne maintenant sans erreur ! +Nous n'incluons pas encore la ligne `trainer.train()` pour prendre le temps de vérifier que tout se passe bien. Si nous passons un batch à notre modèle, il fonctionne maintenant sans erreur ! ```py for batch in trainer.get_train_dataloader(): @@ -512,15 +513,15 @@ trainer.optimizer.step() Encore une fois, si vous utilisez l'optimiseur par défaut dans le `Trainer`, vous ne devriez pas avoir d'erreur à ce stade, mais si vous avez un optimiseur personnalisé, il pourrait y avoir quelques problèmes à déboguer ici. N'oubliez pas de revenir au CPU si vous obtenez une erreur CUDA bizarre à ce stade. En parlant d'erreurs CUDA, nous avons mentionné précédemment un cas particulier. Voyons cela maintenant. -### Gérer les erreurs CUDA hors-mémoire +### Gérer les erreurs CUDA out of memory -Chaque fois que vous obtenez un message d'erreur qui commence par `RuntimeError : CUDA out of memory`, cela indique que vous êtes à court de mémoire GPU. Cela n'est pas directement lié à votre code, et cela peut arriver avec un script qui fonctionne parfaitement bien. Cette erreur signifie que vous avez essayé de mettre trop de choses dans la mémoire interne de votre GPU, et que cela a entraîné une erreur. Comme pour d'autres erreurs CUDA, vous devrez redémarrer votre noyau pour être en mesure d'exécuter à nouveau votre entraînement. +Chaque fois que vous obtenez un message d'erreur qui commence par `RuntimeError : CUDA out of memory`, cela indique que vous êtes à court de mémoire GPU. Cela n'est pas directement lié à votre code et peut arriver avec un script qui fonctionne parfaitement bien. Cette erreur signifie que vous avez essayé de mettre trop de choses dans la mémoire interne de votre GPU et que cela a entraîné une erreur. Comme pour d'autres erreurs CUDA, vous devrez redémarrer votre noyau pour être en mesure d'exécuter à nouveau votre entraînement. -Pour résoudre ce problème, il suffit d'utiliser moins d'espace GPU, ce qui est souvent plus facile à dire qu'à faire. Tout d'abord, assurez-vous que vous n'avez pas deux modèles sur le GPU en même temps (sauf si cela est nécessaire pour votre problème, bien sûr). Ensuite, vous devriez probablement réduire la taille de votre batch, car elle affecte directement les tailles de toutes les sorties intermédiaires du modèle et leurs gradients. Si le problème persiste, envisagez d'utiliser une version plus petite de votre modèle. +Pour résoudre ce problème, il suffit d'utiliser moins d'espace GPU, ce qui est souvent plus facile à dire qu'à faire. Tout d'abord, assurez-vous que vous n'avez pas deux modèles sur le GPU en même temps (sauf si cela est nécessaire pour votre problème, bien sûr). Ensuite, vous devriez probablement réduire la taille de votre batch car elle affecte directement les tailles de toutes les sorties intermédiaires du modèle et leurs gradients. Si le problème persiste, envisagez d'utiliser une version plus petite de votre modèle. -In the next part of the course, we'll look at more advanced techniques that can help you reduce your memory footprint and let you fine-tune the biggest models. +Dans la prochaine partie du cours, nous examinerons des techniques plus avancées qui peuvent vous aider à réduire votre empreinte mémoire et vous permettre de finetuner les plus grands modèles. @@ -529,7 +530,7 @@ In the next part of the course, we'll look at more advanced techniques that can Maintenant que nous avons résolu tous les problèmes liés à notre code, tout est parfait et l'entraînement devrait se dérouler sans problème, n'est-ce pas ? Pas si vite ! Si vous exécutez la commande `trainer.train()`, tout aura l'air bien au début, mais après un moment vous obtiendrez ce qui suit : ```py -# This will take a long time and error out, so you shouldn't run this cell +# Cela prendra beaucoup de temps et se soldera par une erreur, vous ne devriez donc pas utiliser cette cellule. trainer.train() ``` @@ -567,7 +568,7 @@ with torch.no_grad(): outputs = trainer.model(**batch) ``` -L'erreur survient plus tard, à la fin de la phase d'évaluation, et si nous regardons la *traceback*, nous voyons ceci : +L'erreur survient plus tard, à la fin de la phase d'évaluation, et si nous regardons le *traceback*, nous voyons ceci : ```python trace ~/git/datasets/src/datasets/metric.py in add_batch(self, predictions, references) @@ -601,7 +602,7 @@ predictions.shape, labels.shape ((8, 3), (8,)) ``` -Nos prédictions sont toujours des logits, et non les prédictions réelles, c'est pourquoi la métrique retourne cette erreur (quelque peu obscure). La correction est assez simple, il suffit d'ajouter un argmax dans la fonction `compute_metrics()` : +Nos prédictions sont toujours des logits et non les prédictions réelles, c'est pourquoi la métrique retourne cette erreur (quelque peu obscure). La correction est assez simple, il suffit d'ajouter un argmax dans la fonction `compute_metrics()` : ```py import numpy as np @@ -680,7 +681,7 @@ trainer = Trainer( trainer.train() ``` -Dans ce cas, il n'y a plus de problème, et notre script va affiner un modèle qui devrait donner des résultats raisonnables. Mais que faire lorsque l'entraînement se déroule sans erreur, et que le modèle entraîné n'est pas du tout performant ? C'est la partie la plus difficile de l'apprentissage automatique, et nous allons vous montrer quelques techniques qui peuvent vous aider. +Dans ce cas, il n'y a plus de problème, et notre script va *finetuner* un modèle qui devrait donner des résultats raisonnables. Mais que faire lorsque l'entraînement se déroule sans erreur et que le modèle entraîné n'est pas du tout performant ? C'est la partie la plus difficile de l'apprentissage automatique et nous allons vous montrer quelques techniques qui peuvent vous aider. @@ -690,34 +691,34 @@ Dans ce cas, il n'y a plus de problème, et notre script va affiner un modèle q ## Déboguer les erreurs silencieuses pendant l'entraînement -Que peut-on faire pour déboguer un entraînement qui se termine sans erreur mais qui ne donne pas de bons résultats ? Nous allons vous donner quelques pistes ici, mais sachez que ce type de débogage est la partie la plus difficile de l'apprentissage automatique, et qu'il n'y a pas de réponse magique. +Que peut-on faire pour déboguer un entraînement qui se termine sans erreur mais qui ne donne pas de bons résultats ? Nous allons vous donner quelques pistes ici, mais sachez que ce type de débogage est la partie la plus difficile de l'apprentissage automatique et qu'il n'y a pas de réponse magique. ### Vérifiez vos données (encore !) -Votre modèle n'apprendra quelque chose que s'il est réellement possible d'apprendre quelque chose de vos données. Si un bogue corrompt les données ou si les étiquettes sont attribuées de manière aléatoire, il est très probable que vous n'obtiendrez aucun entraînement de modèle sur votre ensemble de données. Commencez donc toujours par revérifier vos entrées et étiquettes décodées, et posez-vous les questions suivantes : +Votre modèle n'apprendra quelque chose que s'il est réellement possible d'apprendre quelque chose de vos données. Si un *bug* corrompt les données ou si les étiquettes sont attribuées de manière aléatoire, il est très probable que vous n'obtiendrez aucun entraînement de modèle sur votre jeu de données. Commencez donc toujours par revérifier vos entrées et étiquettes décodées, et posez-vous les questions suivantes : - les données décodées sont-elles compréhensibles ? - êtes-vous d'accord avec les étiquettes ? - y a-t-il une étiquette qui est plus courante que les autres ? -- quelle devrait être la perte/métrie si le modèle prédisait une réponse aléatoire/toujours la même réponse ? +- quelle devrait être la perte/métrique si le modèle prédisait une réponse aléatoire/toujours la même réponse ? -⚠️ Si vous effectuez un entraînement distribué, imprimez des échantillons de votre ensemble de données dans chaque processus et vérifiez par trois fois que vous obtenez la même chose. Un bug courant consiste à avoir une source d'aléa dans la création des données qui fait que chaque processus a une version différente de l'ensemble de données. +⚠️ Si vous effectuez un entraînement distribué, imprimez des échantillons de votre ensemble de données dans chaque processus et vérifiez par trois fois que vous obtenez la même chose. Un bug courant consiste à avoir une source d'aléa dans la création des données qui fait que chaque processus a une version différente du jeu de données. -Après avoir examiné vos données, examinez quelques-unes des prédictions du modèle et décodez-les également. Si le modèle prédit toujours la même chose, c'est peut-être parce que votre ensemble de données est biaisé en faveur d'une catégorie (pour les problèmes de classification) ; des techniques comme le suréchantillonnage de classes rares peuvent aider. +Après avoir examiné vos données, examinez quelques-unes des prédictions du modèle. Si votre modèle produit des *tokens*, essayez aussi de les décoder ! Si le modèle prédit toujours la même chose, cela peut être dû au fait que votre jeu de données est biaisé en faveur d'une catégorie (pour les problèmes de classification). Des techniques telles que le suréchantillonnage des classes rares peuvent aider. D'autre part, cela peut également être dû à des problèmes d'entraînement tels que de mauvais réglages des hyperparamètres. -Si la perte/la métrique que vous obtenez sur votre modèle initial est très différente de la perte/la métrique à laquelle vous vous attendez pour des prédictions aléatoires, vérifiez à nouveau la façon dont votre perte ou votre métrique est calculée, car il y a probablement un bug à ce niveau. Si vous utilisez plusieurs pertes que vous ajoutez à la fin, assurez-vous qu'elles sont de la même échelle. +Si la perte/la métrique que vous obtenez sur votre modèle initial avant entraînement est très différente de la perte/la métrique à laquelle vous vous attendez pour des prédictions aléatoires, vérifiez la façon dont votre perte ou votre métrique est calculée. Il y a probablement un bug. Si vous utilisez plusieurs pertes que vous ajoutez à la fin, assurez-vous qu'elles sont de la même échelle. Lorsque vous êtes sûr que vos données sont parfaites, vous pouvez voir si le modèle est capable de s'entraîner sur elles grâce à un test simple. ### Surentraînement du modèle sur un seul batch -Le surentraînement est généralement une chose que nous essayons d'éviter lors de l'entraînement, car cela signifie que le modèle n'apprend pas à reconnaître les caractéristiques générales que nous voulons qu'il reconnaisse, mais qu'il se contente de mémoriser les échantillons d'entraînement. Cependant, essayer d'entraîner votre modèle sur un batch encore et encore est un bon test pour vérifier si le problème tel que vous l'avez formulé peut être résolu par le modèle que vous essayez d'entraîner. Cela vous aidera également à voir si votre taux d'apprentissage initial est trop élevé. +Le surentraînement est généralement une chose que nous essayons d'éviter lors de l'entraînement car cela signifie que le modèle n'apprend pas à reconnaître les caractéristiques générales que nous voulons qu'il reconnaisse et se contente de mémoriser les échantillons d'entraînement. Cependant, essayer d'entraîner votre modèle sur un batch encore et encore est un bon test pour vérifier si le problème tel que vous l'avez formulé peut être résolu par le modèle que vous essayez d'entraîner. Cela vous aidera également à voir si votre taux d'apprentissage initial est trop élevé. -Une fois que vous avez défini votre `Trainer`, c'est très facile ; il suffit de prendre un batch de données d'entraînement, puis d'exécuter une petite boucle d'entraînement manuel en utilisant uniquement ce batch pour quelque chose comme 20 étapes : +Une fois que vous avez défini votre `modèle`, c'est très facile. Il suffit de prendre un batch de données d'entraînement, puis de le traiter comme votre jeu de données entier que vous *finetunez* sur un grand nombre d'époques : ```py for batch in trainer.get_train_dataloader(): @@ -757,7 +758,7 @@ compute_metrics((preds.cpu().numpy(), labels.cpu().numpy())) 100% de précision, voilà un bel exemple de surentraînement (ce qui signifie que si vous essayez votre modèle sur n'importe quelle autre phrase, il vous donnera très probablement une mauvaise réponse) ! -Si vous ne parvenez pas à ce que votre modèle obtienne des résultats parfaits comme celui-ci, cela signifie qu'il y a quelque chose qui ne va pas dans la façon dont vous avez formulé le problème ou dans vos données, et vous devez donc y remédier. Ce n'est que lorsque vous parviendrez à passer le test de surentraînement que vous pourrez être sûr que votre modèle peut réellement apprendre quelque chose. +Si vous ne parvenez pas à ce que votre modèle obtienne des résultats parfaits comme celui-ci, cela signifie qu'il y a quelque chose qui ne va pas dans la façon dont vous avez formulé le problème ou dans vos données. Vous devez donc y remédier. Ce n'est que lorsque vous parviendrez à passer le test de surentraînement que vous pourrez être sûr que votre modèle peut réellement apprendre quelque chose. @@ -765,23 +766,23 @@ Si vous ne parvenez pas à ce que votre modèle obtienne des résultats parfaits -### Ne réglez rien tant que vous n'avez pas une première ligne de base. +### Ne réglez rien tant que vous n'avez pas une première ligne de base -Le réglage des hyperparamètres est toujours considéré comme la partie la plus difficile de l'apprentissage automatique, mais c'est juste la dernière étape pour vous aider à gagner un peu sur la métrique. La plupart du temps, les hyperparamètres par défaut du `Trainer` fonctionneront très bien pour vous donner de bons résultats, donc ne vous lancez pas dans une recherche d'hyperparamètres longue et coûteuse jusqu'à ce que vous ayez quelque chose qui batte la ligne de base que vous avez sur votre jeu de données. +Le réglage des hyperparamètres est toujours considéré comme la partie la plus difficile de l'apprentissage automatique mais c'est juste la dernière étape pour vous aider à gagner un peu sur la métrique. La plupart du temps, les hyperparamètres par défaut du `Trainer` fonctionneront très bien pour vous donner de bons résultats. Donc ne vous lancez pas dans une recherche d'hyperparamètres longue et coûteuse jusqu'à ce que vous ayez quelque chose qui batte la ligne de base que vous avez sur votre jeu de données. -Une fois que vous avez un modèle suffisamment bon, vous pouvez commencer à l'affiner un peu. N'essayez pas de lancer un millier d'exécutions avec différents hyperparamètres, mais comparez quelques exécutions avec différentes valeurs pour un hyperparamètre afin de vous faire une idée de celui qui a le plus d'impact. +Une fois que vous avez un modèle suffisamment bon, vous pouvez commencer à le *finetuner* un peu. N'essayez pas de lancer un millier d'exécutions avec différents hyperparamètres mais comparez quelques exécutions avec différentes valeurs pour un hyperparamètre afin de vous faire une idée de celui qui a le plus d'impact. Si vous modifiez le modèle lui-même, restez simple et n'essayez rien que vous ne puissiez raisonnablement justifier. Veillez toujours à revenir au test de surentraînement pour vérifier que votre modification n'a pas eu de conséquences inattendues. ### Demander de l'aide -Nous espérons que vous avez trouvé dans cette section des conseils qui vous ont aidé à résoudre votre problème, mais si ce n'est pas le cas, n'oubliez pas que vous pouvez toujours demander de l'aide à la communauté sur le [forum](https://discuss.huggingface.co/). +Nous espérons que vous avez trouvé dans cette section des conseils qui vous ont aidé à résoudre votre problème. Si ce n'est pas le cas, n'oubliez pas que vous pouvez toujours demander de l'aide à la communauté sur le [forum](https://discuss.huggingface.co/). Voici quelques ressources (en anglais) supplémentaires qui peuvent s'avérer utiles : -- ["La reproductibilité comme vecteur des meilleures pratiques d'ingénierie"](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p) par Joel Grus -- ["Liste de contrôle pour le débogage des réseaux neuronaux"](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) par Cecelia Shao -- ["Comment tester unitairement le code d'apprentissage automatique"](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) par Chase Roberts -- ["Une recette pour Entraîner les réseaux neuronaux"](http://karpathy.github.io/2019/04/25/recipe/) par Andrej Karpathy +- [La reproductibilité comme vecteur des meilleures pratiques d'ingénierie](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p) par Joel Grus +- [Liste de contrôle pour le débogage des réseaux de neurones](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) par Cecelia Shao +- [Comment tester unitairement le code d'apprentissage automatique](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) par Chase Roberts +- [Une recette pour entraîner les réseaux de neurones](http://karpathy.github.io/2019/04/25/recipe/) par Andrej Karpathy -Bien sûr, tous les problèmes rencontrés lors de l'Entraînement des réseaux neuronaux ne sont pas forcément de votre faute ! Si vous rencontrez quelque chose dans la bibliothèque 🤗 *Transformers* ou 🤗 *Datasets* qui ne semble pas correct, vous avez peut-être rencontré un bogue. Vous devez absolument nous en parler, et dans la section suivante, nous allons vous expliquer exactement comment faire. +Bien sûr, tous les problèmes rencontrés lors de l'entraînement ne sont pas forcément de votre faute ! Si vous rencontrez quelque chose dans la bibliothèque 🤗 *Transformers* ou 🤗 *Datasets* qui ne semble pas correct, vous avez peut-être trouver un *bug*. Vous devez absolument nous en parler pour qu'on puisse le corriger. Dans la section suivante, nous allons vous expliquer exactement comment faire. diff --git a/chapters/fr/chapter8/4_tf.mdx b/chapters/fr/chapter8/4_tf.mdx index b1a01e75a..e178f6842 100644 --- a/chapters/fr/chapter8/4_tf.mdx +++ b/chapters/fr/chapter8/4_tf.mdx @@ -9,17 +9,17 @@ {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter8/section4_tf.ipynb"}, ]} /> -Vous avez écrit un magnifique script pour entraîner ou *finetuner* un modèle sur une tâche donnée, en suivant consciencieusement les conseils du [Chapitre 7](/course/fr/chapter7). Mais lorsque vous lancez la commande `model.fit()`, quelque chose d'horrible se produit : vous obtenez une erreur 😱 ! Ou pire, tout semble aller bien et l'entraînement se déroule sans erreur, mais le modèle résultant est merdique. Dans cette section, nous allons vous montrer ce que vous pouvez faire pour déboguer ce genre de problèmes. +Vous avez écrit un magnifique script pour entraîner ou *finetuner* un modèle sur une tâche donnée en suivant consciencieusement les conseils du [chapitre 7](/course/fr/chapter7). Mais lorsque vous lancez la commande `model.fit()`, quelque chose d'horrible se produit : vous obtenez une erreur 😱 ! Ou pire, tout semble aller bien et l'entraînement se déroule sans erreur mais le modèle résultant est mauvais. Dans cette section, nous allons vous montrer ce que vous pouvez faire pour déboguer ce genre de problèmes. ## Déboguer le pipeline d'entraînement -Le problème lorsque vous rencontrez une erreur dans `trainer.train()` est qu'elle peut provenir de plusieurs sources, car le `Trainer` assemble généralement des batchs de choses. Il convertit les jeux de données en chargeurs de données, donc le problème pourrait être quelque chose d'erroné dans votre jeu de données, ou un problème en essayant de regrouper les éléments des jeux de données ensemble. Ensuite, il prend un batch de données et le transmet au modèle, le problème peut donc se situer dans le code du modèle. Après cela, il calcule les gradients et effectue l'étape d'optimisation, le problème peut donc également se situer dans votre optimiseur. Et même si tout se passe bien pendant l'entraînement, quelque chose peut encore mal tourner pendant l'évaluation si votre métrique pose problème. +Le problème lorsque vous rencontrez une erreur dans `trainer.train()` est qu'elle peut provenir de plusieurs sources, car la fonction `Trainer` assemble généralement des batchs de choses. Elle convertit les jeux de données en chargeurs de données donc le problème pourrait être quelque chose d'erroné dans votre jeu de données, ou un problème en essayant de regrouper les éléments des jeux de données ensemble. Ensuite, elle prend un batch de données et le transmet au modèle, le problème peut donc se situer dans le code du modèle. Après cela, elle calcule les gradients et effectue l'étape d'optimisation, le problème peut donc également se situer dans votre optimiseur. Et même si tout se passe bien pendant l'entraînement, quelque chose peut encore mal tourner pendant l'évaluation si votre métrique pose problème. La meilleure façon de déboguer une erreur qui survient dans `trainer.train()` est de passer manuellement en revue tout le pipeline pour voir où les choses se sont mal passées. L'erreur est alors souvent très facile à résoudre. -Pour le démontrer, nous utiliserons le script suivant qui tente d'ajuster un modèle DistilBERT sur le [jeu de données MNLI](https://huggingface.co/datasets/glue) : +Pour le démontrer, nous utiliserons le script suivant qui tente de *finetuner* un modèle DistilBERT sur le [jeu de données MNLI](https://huggingface.co/datasets/glue) : ```py from datasets import load_dataset, load_metric @@ -55,28 +55,28 @@ model.compile(loss="sparse_categorical_crossentropy", optimizer="adam") model.fit(train_dataset) ``` -Si vous essayez de l'exécuter, il se peut que vous obteniez des `VisibleDeprecationWarning`s lors de la conversion du jeu de données. Il s'agit d'un problème UX connu que nous avons, donc veuillez l'ignorer. Si vous lisez le cours après, disons, novembre 2021 et que cela se produit encore, envoyez des tweets de rage à @carrigmat jusqu'à ce qu'il le corrige. +Si vous essayez de l'exécuter, il se peut que vous obteniez des `VisibleDeprecationWarning`s lors de la conversion du jeu de données. Il s'agit d'un problème UX connu par l'équipe d'Hugging Face, donc veuillez l'ignorer. Si vous lisez le cours après novembre 2021 et que cela se produit encore, envoyez des tweets de rage à @carrigmat jusqu'à ce qu'il le corrige. -Le problème le plus grave, cependant, c'est que nous avons une erreur flagrante. Et c'est vraiment, terriblement long : +Le problème cependant est que nous avons une erreur flagrante. Et c'est vraiment, terriblement long : ```python out ValueError: No gradients provided for any variable: ['tf_distil_bert_for_sequence_classification/distilbert/embeddings/word_embeddings/weight:0', '...'] ``` -Qu'est-ce que cela signifie ? Nous avons essayé de nous entraîner sur nos données, mais nous n'avons pas obtenu de gradient ? C'est assez déconcertant ; comment commencer à déboguer quelque chose comme ça ? Lorsque l'erreur que vous obtenez ne suggère pas immédiatement l'origine du problème, la meilleure solution consiste souvent à procéder par étapes, en s'assurant à chaque fois que tout semble correct. Et bien sûr, il faut toujours commencer par... +Qu'est-ce que cela signifie ? Nous avons essayé d'entraîner sur nos données mais nous n'avons pas obtenu de gradient. C'est assez déconcertant. Comment commencer à déboguer quelque chose comme ça ? Lorsque l'erreur que vous obtenez ne suggère pas immédiatement l'origine du problème, la meilleure solution consiste souvent à procéder par étapes, en s'assurant à chaque fois que tout semble correct. Et bien sûr, il faut toujours commencer par... ### Vérifier vos données Cela va sans dire, mais si vos données sont corrompues, Keras ne sera pas en mesure de les réparer pour vous. Avant toute chose, vous devez donc jeter un coup d'œil à ce que contient votre ensemble d'entraînement. -Bien qu'il soit tentant de regarder dans les `raw_datasets` et les `tokenized_datasets`, nous vous recommandons fortement d'aller voir les données au moment où elles vont entrer dans le modèle. Cela signifie lire une sortie du `tf.data.Dataset` que vous avez créé avec la fonction `to_tf_dataset()` ! Alors comment faire ? Les objets `tf.data.Dataset` nous donnent des batchs entiers à la fois et ne supportent pas l'indexation, donc nous ne pouvons pas simplement demander `train_dataset[0]`. Nous pouvons, cependant, lui demander poliment un batch : +Bien qu'il soit tentant de regarder dans `raw_datasets` et `tokenized_datasets`, nous vous recommandons fortement d'aller voir les données au moment où elles vont entrer dans le modèle. Cela signifie lire une sortie du `tf.data.Dataset` que vous avez créé avec la fonction `to_tf_dataset()` ! Alors comment faire ? Les objets `tf.data.Dataset` nous donnent des batchs entiers à la fois et ne supportent pas l'indexation, donc nous ne pouvons pas simplement demander `train_dataset[0]`. Nous pouvons, cependant, lui demander poliment un batch : ```py for batch in train_dataset: break ``` -`break` ends the loop after one iteration, so this grabs the first batch that comes out of `train_dataset` and saves it as `batch`. Now, let's take a look at what's inside: +`break` termine la boucle après une itération, donc cela prend le premier batch qui sort de `train_dataset` et l'enregistre comme `batch`. Maintenant, jetons un coup d'oeil à ce qu'il y a à l'intérieur : ```python out {'attention_mask': } ``` -Cela semble correct, n'est-ce pas ? Nous passons les `labels`, `attention_mask`, et `input_ids` au modèle, ce qui devrait être tout ce dont il a besoin pour calculer les sorties et la perte. Alors pourquoi n'avons-nous pas de gradient ? Regardez de plus près : nous passons un seul dictionnaire en entrée, mais un batch d'entraînement est généralement un tenseur ou un dictionnaire d'entrée, plus un tenseur d'étiquettes. Nos étiquettes sont juste une clé dans notre dictionnaire d'entrée. +Cela semble correct. Nous passons les `labels`, `attention_mask`, et `input_ids` au modèle, ce qui devrait être tout ce dont il a besoin pour calculer les sorties et la perte. Alors pourquoi n'avons-nous pas de gradient ? Regardez de plus près : nous passons un seul dictionnaire en entrée mais un batch d'entraînement est généralement un tenseur ou un dictionnaire d'entrée, plus un tenseur d'étiquettes. Nos étiquettes sont juste une clé dans notre dictionnaire d'entrée. -Est-ce un problème ? Pas toujours, en fait ! Mais c'est l'un des problèmes les plus courants que vous rencontrerez lorsque vous entraînerez des modèles Transformer avec TensorFlow. Nos modèles peuvent tous calculer la perte en interne, mais pour ce faire, les étiquettes doivent être transmises dans le dictionnaire d'entrée. C'est la perte qui est utilisée lorsque nous ne spécifions pas de valeur de perte à `compile()`. Keras, d'autre part, s'attend généralement à ce que les étiquettes soient passées séparément du dictionnaire d'entrée, et les calculs de perte échoueront généralement si vous ne le faites pas. +Est-ce un problème ? Pas toujours, en fait ! Mais c'est l'un des problèmes les plus courants que vous rencontrerez lorsque vous entraînerez des *transformers* avec TensorFlow. Nos modèles peuvent tous calculer la perte en interne, mais pour ce faire, les étiquettes doivent être transmises dans le dictionnaire d'entrée. C'est la perte qui est utilisée lorsque nous ne spécifions pas de valeur de perte à `compile()`. Keras, d'autre part, s'attend généralement à ce que les étiquettes soient passées séparément du dictionnaire d'entrée, et les calculs de perte échoueront généralement si vous ne le faites pas. Le problème est maintenant devenu plus clair : nous avons passé un argument `loss`, ce qui signifie que nous demandons à Keras de calculer les pertes pour nous, mais nous avons passé nos étiquettes comme entrées au modèle, et non comme étiquettes à l'endroit où Keras les attend ! Nous devons choisir l'un ou l'autre : soit nous utilisons la perte interne du modèle et gardons les étiquettes où elles sont, soit nous continuons à utiliser les pertes de Keras, mais nous déplaçons les étiquettes à l'endroit où Keras les attend. Pour simplifier, prenons la première approche. Changez l'appel à `compile()` pour lire : @@ -108,11 +108,11 @@ Le problème est maintenant devenu plus clair : nous avons passé un argument `l model.compile(optimizer="adam") ``` -Maintenant, nous allons utiliser la perte interne du modèle, et ce problème devrait être résolu ! +Maintenant, nous allons utiliser la perte interne du modèle et ce problème devrait être résolu ! -✏️ *A votre tour !* Comme défi optionnel après avoir résolu les autres problèmes, vous pouvez essayer de revenir à cette étape et faire fonctionner le modèle avec la perte originale calculée par Keras au lieu de la perte interne. Vous devrez ajouter `"labels"` à l'argument `label_cols` de `to_tf_dataset()` pour vous assurer que les labels sont correctement sortis, ce qui vous donnera des gradients -- mais il y a un autre problème avec la perte que nous avons spécifiée. L'Entraînement fonctionnera toujours avec ce problème, mais l'apprentissage sera très lent et se stabilisera à une perte d'entraînement élevée. Pouvez-vous trouver ce que c'est ? +✏️ *A votre tour !* Comme défi optionnel après avoir résolu les autres problèmes, vous pouvez essayer de revenir à cette étape et faire fonctionner le modèle avec la perte originale calculée par Keras au lieu de la perte interne. Vous devrez ajouter `"labels"` à l'argument `label_cols` de `to_tf_dataset()` pour vous assurer que les labels sont correctement sortis, ce qui vous donnera des gradients. Mais il y a un autre problème avec la perte que nous avons spécifiée. L'entraînement fonctionnera toujours avec ce problème mais l'apprentissage sera très lent et se stabilisera à une perte d'entraînement élevée. Pouvez-vous trouver ce que c'est ? Un indice codé en ROT13, si vous êtes coincé : Vs lbh ybbx ng gur bhgchgf bs FrdhraprPynffvsvpngvba zbqryf va Genafsbezref, gurve svefg bhgchg vf `ybtvgf`. Jung ner ybtvgf ? @@ -120,7 +120,7 @@ Et un deuxième indice : Jura lbh fcrpvsl bcgvzvmref, npgvingvbaf be ybffrf jvgu -Maintenant, essayons de nous entraîner. Nous devrions obtenir des gradients maintenant, donc avec un peu de chance (la musique de mauvais augure joue ici) nous pouvons juste appeler `model.fit()` et tout fonctionnera bien ! +Maintenant, essayons d'entraîner. Nous devrions obtenir des gradients maintenant, donc avec un peu de chance nous pouvons juste appeler `model.fit()` et tout fonctionnera bien ! ```python out 246/24543 [..............................] - ETA: 15:52 - loss: nan @@ -128,11 +128,11 @@ Maintenant, essayons de nous entraîner. Nous devrions obtenir des gradients mai Oh non. -`nan` n'est pas une valeur de perte très encourageante. Pourtant, nous avons vérifié nos données, et elles semblent plutôt bonnes. Si ce n'est pas le problème, quelle est la prochaine étape ? La prochaine étape évidente est de... +`nan` n'est pas une valeur de perte très encourageante. Pourtant, nous avons vérifié nos données et elles semblent plutôt bonnes. Si ce n'est pas le problème, quelle est la prochaine étape ? La prochaine étape évidente est de... ### Vérifier votre modèle -`model.fit()` est une fonction très pratique dans Keras, mais elle fait beaucoup de choses pour vous, et cela peut rendre plus difficile de trouver exactement où un problème est survenu. Si vous déboguez votre modèle, une stratégie qui peut vraiment vous aider est de passer un seul batch au modèle et d'examiner les sorties de ce batch en détail. Une autre astuce vraiment utile si le modèle jette des erreurs est de `compiler()` le modèle avec `run_eagerly=True`. Cela le rendra beaucoup plus lent, mais les messages d'erreur seront beaucoup plus compréhensibles, car ils indiqueront exactement où le problème est survenu dans le code de votre modèle. +`model.fit()` est une fonction très pratique dans Keras, mais elle fait beaucoup de choses pour vous. Cela peut rendre plus difficile de trouver exactement où un problème est survenu. Si vous déboguez votre modèle, une stratégie qui peut vraiment vous aider est de passer un seul batch au modèle et d'examiner les sorties de ce batch en détail. Une autre astuce vraiment utile est de `compiler()` le modèle avec `run_eagerly=True`. Cela le rendra beaucoup plus lent mais les messages d'erreur seront beaucoup plus compréhensibles car ils indiqueront exactement où le problème est survenu dans le code de votre modèle. Pour l'instant, cependant, nous n'avons pas besoin de `run_eagerly`. Exécutons le `batch` que nous avons obtenu précédemment à travers le modèle et voyons à quoi ressemblent les résultats : @@ -162,7 +162,8 @@ array([[nan, nan], [nan, nan]], dtype=float32)>, hidden_states=None, attentions=None) ``` -Eh bien, c'est délicat. Tout est "nan" ! Mais c'est étrange, n'est-ce pas ? Comment tous nos logits pourraient-ils devenir `nan` ? "NAN" signifie "not a number". Les valeurs `nan` apparaissent souvent quand on effectue une opération interdite, comme la division par zéro. Mais une chose très importante à savoir sur `nan` en apprentissage automatique est que cette valeur a tendance à *se propager*. Si vous multipliez un nombre par `nan`, le résultat sera également `nan`. Et si vous obtenez une valeur `nan` n'importe où dans votre sortie, votre perte ou votre gradient, alors elle se propagera rapidement à travers tout votre modèle. Ceci parce que lorsque cette valeur `nan` est propagée à travers votre réseau, vous obtiendrez des gradients `nan`, et lorsque les mises à jour des poids sont calculées avec ces gradients, vous obtiendrez des poids `nan`, et ces poids calculeront encore plus de sorties `nan` ! Très vite, le réseau entier ne sera plus qu'un gros bloc de `nan`. Une fois que cela arrive, il est assez difficile de voir où le problème a commencé. Comment peut-on isoler l'endroit où les `nan` se sont introduits en premier ? +Eh bien, c'est délicat. Tout est "nan" ! Mais c'est étrange, n'est-ce pas ? Comment tous nos logits pourraient-ils devenir `nan` ? "NAN" signifie "*not a number*". Les valeurs `nan` apparaissent souvent quand on effectue une opération interdite comme la division par zéro. Mais une chose très importante à savoir sur `nan` en apprentissage automatique est que cette valeur a tendance à *se propager*. Si vous multipliez un nombre par `nan`, le résultat sera également `nan`. Et si vous obtenez une valeur `nan` n'importe où dans votre sortie, votre perte ou votre gradient, alors elle se propagera rapidement à travers tout votre modèle. +Ceci parce que lorsque cette valeur `nan` est propagée à travers votre réseau, vous obtiendrez des gradients `nan`, et lorsque les mises à jour des poids sont calculées avec ces gradients, vous obtiendrez des poids `nan`, et ces poids calculeront encore plus de sorties `nan` ! Très vite, le réseau entier ne sera plus qu'un gros bloc de `nan`. Une fois que cela arrive, il est assez difficile de voir où le problème a commencé. Comment peut-on isoler l'endroit où les `nan` se sont introduits en premier ? La réponse est d'essayer de *reinitialiser* notre modèle. Une fois que nous avons commencé l'entraînement, nous avons eu un `nan` quelque part et il s'est rapidement propagé à travers tout le modèle. Donc, chargeons le modèle à partir d'un checkpoint et ne faisons aucune mise à jour de poids, et voyons où nous obtenons une valeur `nan` : @@ -197,7 +198,7 @@ array([[-0.04761693, -0.06509043], [-0.08141848, -0.07110836]], dtype=float32)>, hidden_states=None, attentions=None) ``` -*Maintenant* on arrive à quelque chose ! Il n'y a pas de valeurs `nan` dans nos logits, ce qui est rassurant. Mais nous voyons quelques valeurs `nan` dans notre perte ! Y a-t-il quelque chose dans ces échantillons en particulier qui cause ce problème ? Voyons de quels échantillons il s'agit (notez que si vous exécutez ce code vous-même, vous pouvez obtenir des indices différents parce que l'ensemble de données a été mélangé) : +*Maintenant* on arrive à quelque chose ! Il n'y a pas de valeurs `nan` dans nos logits, ce qui est rassurant. Mais nous voyons quelques valeurs `nan` dans notre perte ! Y a-t-il quelque chose dans ces échantillons en particulier qui cause ce problème ? Voyons de quels échantillons il s'agit (notez que si vous exécutez ce code vous-même, vous pouvez obtenir des indices différents parce que le jeu de données a été mélangé) : ```python import numpy as np @@ -211,7 +212,7 @@ indices array([ 1, 2, 5, 7, 9, 10, 11, 13, 14]) ``` -Let's look at the samples these indices came from: +Examinons les échantillons d'où proviennent ces indices : ```python input_ids = batch["input_ids"].numpy() @@ -311,7 +312,7 @@ array([[ 101, 2007, 2032, 2001, 1037, 16480, 3917, 2594, 4135, 0, 0, 0, 0]]) ``` -Il y a beaucoup de batchs ici, mais rien d'inhabituel. Regardons les étiquettes : +Il y a beaucoup de batchs ici mais rien d'inhabituel. Regardons les étiquettes : ```python out labels = batch['labels'].numpy() @@ -322,7 +323,7 @@ labels[indices] array([2, 2, 2, 2, 2, 2, 2, 2, 2]) ``` -Ah ! Les échantillons `nan` ont tous le même label, et c'est le label 2. C'est un indice très fort. Le fait que nous n'obtenions une perte de `nan` que lorsque notre étiquette est 2 suggère que c'est un très bon moment pour vérifier le nombre d'étiquettes dans notre modèle : +Ah ! Les échantillons `nan` ont tous le même label. C'est un gros indice. Le fait que nous n'obtenions une perte de `nan` que lorsque notre étiquette vaut 2 suggère que c'est un très bon moment pour vérifier le nombre d'étiquettes dans notre modèle : ```python model.config.num_labels @@ -332,7 +333,7 @@ model.config.num_labels 2 ``` -Nous voyons maintenant le problème : le modèle pense qu'il n'y a que deux classes, mais les étiquettes vont jusqu'à 2, ce qui signifie qu'il y a en fait trois classes (car 0 est aussi une classe). C'est ainsi que nous avons obtenu un `nan` - en essayant de calculer la perte pour une classe inexistante ! Essayons de changer cela et de réajuster le modèle : +Nous voyons maintenant le problème : le modèle pense qu'il n'y a que deux classes, mais les étiquettes vont jusqu'à 2, ce qui signifie qu'il y a en fait trois classes (car 0 est aussi une classe). C'est ainsi que nous avons obtenu un `nan`. En essayant de calculer la perte pour une classe inexistante ! Essayons de changer cela et de réajuster le modèle : ``` model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint, num_labels=3) @@ -344,15 +345,15 @@ model.fit(train_dataset) 869/24543 [>.............................] - ETA: 15:29 - loss: 1.1032 ``` -On s'entraîne ! Plus de `nan`, et nos pertes diminuent... en quelque sorte. Si vous le regardez pendant un certain temps, vous pouvez commencer à vous impatienter, car la valeur des pertes reste obstinément élevée. Arrêtons l'entraînement ici et essayons de réfléchir à ce qui pourrait causer ce problème. À ce stade, nous sommes pratiquement sûrs que les données et le modèle sont corrects, mais notre modèle n'apprend pas bien. Que reste-t-il d'autre ? Il est temps de... +On entraîne ! Plus de `nan` et nos pertes diminuent... en quelque sorte. Si vous regardez pendant un certain temps, vous pouvez commencer à vous impatienter car la valeur des pertes reste obstinément élevée. Arrêtons l'entraînement ici et essayons de réfléchir à ce qui pourrait causer ce problème. À ce stade, nous sommes pratiquement sûrs que les données et le modèle sont corrects, mais notre modèle n'apprend pas bien. Que reste-t-il d'autre ? Il est temps de... -### Vérifier vos hyperparamètres +### Vérifier les hyperparamètres -Si vous regardez le code ci-dessus, vous ne verrez peut-être aucun hyperparamètre, sauf peut-être le `batch_size`, et cela ne semble pas être un coupable probable. Ne soyez pas dupe, cependant ; il y a toujours des hyperparamètres, et si vous ne pouvez pas les voir, cela signifie simplement que vous ne savez pas à quoi ils sont réglés. En particulier, souvenez-vous d'une chose essentielle à propos de Keras : si vous définissez une fonction de perte, d'optimisation ou d'activation avec une chaîne, _tous ses arguments seront définis sur leurs valeurs par défaut_. Cela signifie que, même si l'utilisation de chaînes de caractères est très pratique, vous devez être très prudent, car cela peut facilement vous cacher des éléments critiques. (Toute personne essayant le défi optionnel ci-dessus devrait prendre bonne note de ce fait). +Si vous regardez le code ci-dessus, vous ne verrez peut-être aucun hyperparamètre, sauf peut-être le `batch_size` qui ne semble pas être un coupable probable. Cependant, ne soyez pas dupe, il y a toujours des hyperparamètres. Si vous ne pouvez pas les voir, cela signifie simplement que vous ne connaissez pas leur réglage. En particulier, souvenez-vous d'une chose essentielle à propos de Keras : si vous définissez une fonction de perte, d'optimisation ou d'activation avec une chaîne, _tous ses arguments seront définis sur leurs valeurs par défaut_. Cela signifie que, même si l'utilisation de chaînes de caractères est très pratique, vous devez être très prudent car cela peut facilement vous cacher des éléments critiques. (Toute personne essayant le défi optionnel ci-dessus devrait prendre bonne note de ce fait). -Dans ce cas, où avons-nous défini un argument avec une chaîne ? Au départ, nous définissions la perte avec une chaîne, mais nous ne le faisons plus. Cependant, nous définissons l'optimiseur avec une chaîne de caractères. Cela pourrait-il nous cacher quelque chose ? Jetons un coup d'œil à [ses arguments](https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/Adam). +Dans ce cas, où avons-nous défini un argument avec une chaîne de caractères ? Au départ, nous définissions la perte avec une chaîne de caractères, mais nous ne le faisons plus. Cependant, nous le faisons pour l'optimiseur. Cela pourrait-il nous cacher quelque chose ? Jetons un coup d'œil à [ses arguments](https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/Adam). -Y a-t-il quelque chose qui ressort ? C'est exact : le taux d'apprentissage ! Lorsque nous utilisons simplement la chaîne `'adam'`, nous allons obtenir le taux d'apprentissage par défaut, qui est de 0.001, ou 1e-3. C'est beaucoup trop élevé pour un modèle Transformer ! En général, nous recommandons d'essayer des taux d'apprentissage entre 1e-5 et 1e-4 pour vos modèles ; c'est quelque part entre 10X et 100X plus petit que la valeur que nous utilisons ici. Cela semble être un problème majeur, alors essayons de le réduire. Pour ce faire, nous devons importer l'objet `optimizer`. Pendant que nous y sommes, réinitialisons le modèle à partir du point de contrôle, au cas où l'entraînement avec un taux d'apprentissage élevé aurait endommagé ses poids : +Y a-t-il quelque chose qui ressort ? C'est exact : le taux d'apprentissage ! Lorsque nous indiquons simplement `'adam'` nous allons obtenir le taux d'apprentissage par défaut qui est de 0.001 (ou 1e-3). C'est beaucoup trop élevé pour un *transformer* ! En général, nous recommandons d'essayer des taux d'apprentissage entre 1e-5 et 1e-4 pour vos modèles soit entre 10X et 100X plus petit que la valeur que nous utilisons ici. Cela semble être un problème majeur, alors essayons de le réduire. Pour ce faire, nous devons importer l'objet `optimizer`. Pendant que nous y sommes, réinitialisons le modèle à partir du *checkpoint* au cas où l'entraînement avec un taux d'apprentissage élevé aurait endommagé ses poids : ```python from tensorflow.keras.optimizers import Adam @@ -363,11 +364,11 @@ model.compile(optimizer=Adam(5e-5)) -💡 Vous pouvez également importer la fonction `create_optimizer()` de 🤗 *Transformers*, qui vous donnera un optimiseur AdamW avec une décroissance de poids correcte ainsi qu'un réchauffement et une décroissance du taux d'apprentissage. Cet optimiseur produira souvent des résultats légèrement meilleurs que ceux que vous obtenez avec l'optimiseur Adam par défaut. +💡 Vous pouvez également importer la fonction `create_optimizer()` de 🤗 Transformers qui vous donnera un optimiseur AdamW avec une décroissance du taux des poids correcte ainsi qu'un réchauffement et une décroissance du taux d'apprentissage. Cet optimiseur produira souvent des résultats légèrement meilleurs que ceux que vous obtenez avec l'optimiseur Adam par défaut. -Maintenant, nous pouvons essayer d'ajuster le modèle avec le nouveau taux d'apprentissage amélioré : +Maintenant, nous pouvons essayer de *finetuner* le modèle avec le nouveau taux d'apprentissage : ```python model.fit(train_dataset) @@ -377,7 +378,7 @@ model.fit(train_dataset) 319/24543 [..............................] - ETA: 16:07 - loss: 0.9718 ``` -Maintenant notre perte va vraiment aller quelque part ! L'entraînement semble enfin fonctionner. Il y a une leçon à tirer ici : lorsque votre modèle fonctionne mais que la perte ne diminue pas, et que vous êtes sûr que vos données sont correctes, c'est une bonne idée de vérifier les hyperparamètres comme le taux d'apprentissage et la décroissance du poids. Un réglage trop élevé de l'un ou l'autre de ces paramètres risque fort de faire " caler " l'entraînement à une valeur de perte élevée. +Maintenant notre perte va vraiment aller quelque part ! L'entraînement semble enfin fonctionner. Il y a une leçon à tirer ici : lorsque votre modèle fonctionne mais que la perte ne diminue pas, et que vous êtes sûr que vos données sont correctes, c'est une bonne idée de vérifier les hyperparamètres comme le taux d'apprentissage et le taux de décroissance des poids. Un réglage trop élevé de l'un ou l'autre de ces paramètres risque fort de faire « caler » l'entraînement à une valeur de perte élevée. ## Autres problèmes potentiels @@ -385,26 +386,26 @@ Nous avons couvert les problèmes dans le script ci-dessus, mais il existe plusi ### Gérer les erreurs de manque de mémoire -Le signe révélateur d'un manque de mémoire est une erreur du type "OOM when allocating tensor" -- OOM est l'abréviation de "out of memory". Il s'agit d'un risque très courant lorsque l'on traite de grands modèles de langage. Si vous rencontrez ce problème, une bonne stratégie consiste à diviser par deux la taille de votre batch et à réessayer. Gardez à l'esprit, cependant, que certains modèles sont *très* grands. Par exemple, le modèle GPT-2 complet possède 1,5 Go de paramètres, ce qui signifie que vous aurez besoin de 6 Go de mémoire rien que pour stocker le modèle, et 6 autres Go pour ses gradients ! Entraîner le modèle GPT-2 complet nécessite généralement plus de 20 Go de VRAM, quelle que soit la taille du batch utilisé, ce dont seuls quelques GPU sont dotés. Des modèles plus légers comme `distilbert-base-cased` sont beaucoup plus faciles à exécuter, et s'entraînent aussi beaucoup plus rapidement. +Le signe révélateur d'un manque de mémoire est une erreur du type "OOM when allocating tensor" (OOM étant l'abréviation de *out of memory*). Il s'agit d'un risque très courant lorsque l'on utilise de grands modèles de langage. Si vous rencontrez ce problème, une bonne stratégie consiste à diviser par deux la taille de votre batch et à réessayer. Gardez à l'esprit, cependant, que certains modèles sont *très* grands. Par exemple, le modèle GPT-2 complet possède 1,5 Go de paramètres, ce qui signifie que vous aurez besoin de 6 Go de mémoire rien que pour stocker le modèle, et 6 autres Go pour ses gradients ! Entraîner le modèle GPT-2 complet nécessite généralement plus de 20 Go de VRAM, quelle que soit la taille du batch utilisé, ce dont seuls quelques GPUs sont dotés. Des modèles plus légers comme `distilbert-base-cased` sont beaucoup plus faciles à exécuter et s'entraînent aussi beaucoup plus rapidement. -Dans la prochaine partie du cours, nous examinerons des techniques plus avancées qui peuvent vous aider à réduire votre empreinte mémoire et vous permettre d'affiner les plus grands modèles. +Dans la prochaine partie du cours, nous examinerons des techniques plus avancées qui peuvent vous aider à réduire votre empreinte mémoire et vous permettre de finetuner les plus grands modèles. -### Hungry Hungry TensorFlow 🦛 +### TensorFlow affamé 🦛 -Une bizarrerie particulière de TensorFlow dont vous devez être conscient est qu'il s'alloue *toute* la mémoire de votre GPU dès que vous chargez un modèle ou que vous effectuez un entraînement, puis il divise cette mémoire selon les besoins. Ce comportement est différent de celui d'autres frameworks, comme PyTorch, qui alloue la mémoire selon les besoins avec CUDA plutôt que de le faire en interne. L'un des avantages de l'approche de TensorFlow est qu'elle peut souvent donner des erreurs utiles lorsque vous manquez de mémoire, et qu'elle peut récupérer de cet état sans planter tout le noyau CUDA. Mais il y a aussi un inconvénient important : si vous exécutez deux processus TensorFlow en même temps, alors **vous allez passer un mauvais moment**. +Une bizarrerie particulière de TensorFlow dont vous devez être conscient est qu'il s'alloue *toute* la mémoire de votre GPU dès que vous chargez un modèle ou que vous effectuez un entraînement. Puis il divise cette mémoire selon les besoins. Ce comportement est différent de celui d'autres *frameworks*, comme PyTorch, qui alloue la mémoire selon les besoins avec CUDA plutôt que de le faire en interne. L'un des avantages de l'approche de TensorFlow est qu'elle peut souvent donner des erreurs utiles lorsque vous manquez de mémoire et qu'elle peut récupérer de cet état sans planter tout le noyau CUDA. Mais il y a aussi un inconvénient important : si vous exécutez deux processus TensorFlow en même temps alors **vous allez passer un mauvais moment**. -Si vous travaillez sur Colab, vous n'avez pas à vous soucier de cela, mais si vous travaillez localement, vous devez absolument faire attention. En particulier, sachez que la fermeture d'un onglet de notebook n'entraîne pas nécessairement la fermeture de ce *notebook* ! Vous devrez peut-être sélectionner les blocs-notes en cours d'exécution (ceux qui ont une icône verte) et les fermer manuellement dans la liste des répertoires. Tout *notebook* en cours d'exécution qui utilisait TensorFlow peut encore utiliser une grande partie de la mémoire de votre GPU, ce qui signifie que tout nouveau notebook que vous démarrez peut rencontrer des problèmes très étranges. +Si vous travaillez sur Colab, vous n'avez pas à vous soucier de cela. Si vous travaillez localement, vous devez absolument faire attention. En particulier, sachez que la fermeture d'un onglet de *notebook* n'entraîne pas nécessairement la fermeture de ce *notebook* ! Vous devrez peut-être sélectionner les *notebooks* en cours d'exécution (ceux qui ont une icône verte) et les fermer manuellement dans la liste des répertoires. Tout *notebook* en cours d'exécution qui utilisait TensorFlow peut encore utiliser une grande partie de la mémoire de votre GPU, ce qui signifie que tout nouveau *notebook* que vous démarrez peut rencontrer des problèmes très étranges. -Si vous commencez à obtenir des erreurs concernant CUDA, BLAS ou cuBLAS dans du code qui fonctionnait auparavant, c'est très souvent le coupable. Vous pouvez utiliser une commande comme `nvidia-smi` pour vérifier - quand vous éteignez ou redémarrez votre *notebook* actuel, est-ce que la plupart de votre mémoire est libre, ou est-elle toujours utilisée ? Si elle est toujours utilisée, c'est que quelque chose d'autre s'y accroche ! +Si vous commencez à obtenir des erreurs concernant CUDA, BLAS ou cuBLAS dans du code qui fonctionnait auparavant, c'est très souvent le coupable. Vous pouvez utiliser une commande comme `nvidia-smi` pour vérifier si la plupart de votre mémoire est libre ou toujours utilisée. Si elle est toujours utilisée, c'est que quelque chose d'autre s'y accroche ! ### Vérifiez vos données (encore !) -Votre modèle n'apprendra quelque chose que s'il est réellement possible d'apprendre quelque chose de vos données. S'il y a un bug qui corrompt les données ou si les étiquettes sont attribuées de manière aléatoire, il est très probable que vous n'obtiendrez aucun entraînement de modèle sur votre jeu de données. Un outil utile ici est `tokenizer.decode()`. Cela transformera les `input_ids` en chaînes de caractères, afin que vous puissiez visualiser les données et voir si vos données d'entraînement enseignent ce que vous voulez qu'elles enseignent. Par exemple, après avoir obtenu un `batch` de votre `tf.data.Dataset` comme nous l'avons fait ci-dessus, vous pouvez décoder le premier élément comme suit : +Votre modèle n'apprendra quelque chose que s'il est réellement possible d'apprendre quelque chose de vos données. S'il y a un *bug* qui corrompt les données ou si les étiquettes sont attribuées de manière aléatoire, il est très probable que vous n'obtiendrez aucun entraînement de modèle sur votre jeu de données. Un outil utile ici est `tokenizer.decode()`. Il transformera les `input_ids` en chaînes de caractères, afin que vous puissiez visualiser les données et voir si vos données d'entraînement renseignent ce que vous voulez. Par exemple, après avoir obtenu un `batch` de votre `tf.data.Dataset` comme nous l'avons fait ci-dessus, vous pouvez décoder le premier élément comme suit : ```py @@ -424,27 +425,27 @@ Une fois que vous pouvez visualiser vos données de cette manière, vous pouvez - les données décodées sont-elles compréhensibles ? - êtes-vous d'accord avec les étiquettes ? - y a-t-il une étiquette qui est plus courante que les autres ? -- quelle devrait être la perte/métrie si le modèle prédisait une réponse aléatoire/toujours la même réponse ? +- quelle devrait être la perte/métrique si le modèle prédisait une réponse aléatoire/toujours la même réponse ? -Après avoir examiné vos données, examinez quelques-unes des prédictions du modèle - si votre modèle produit des tokens, essayez aussi de les décoder ! Si le modèle prédit toujours la même chose, cela peut être dû au fait que votre ensemble de données est biaisé en faveur d'une catégorie (pour les problèmes de classification), des techniques telles que le suréchantillonnage des classes rares peuvent aider. Des techniques telles que le suréchantillonnage des classes rares peuvent donc être utiles. D'autre part, cela peut également être dû à des problèmes d'entraînement tels que de mauvais réglages des hyperparamètres. +Après avoir examiné vos données, examinez quelques-unes des prédictions du modèle. Si votre modèle produit des *tokens*, essayez aussi de les décoder ! Si le modèle prédit toujours la même chose, cela peut être dû au fait que votre jeu de données est biaisé en faveur d'une catégorie (pour les problèmes de classification). Des techniques telles que le suréchantillonnage des classes rares peuvent aider. D'autre part, cela peut également être dû à des problèmes d'entraînement tels que de mauvais réglages des hyperparamètres. -Si la perte/la métrique que vous obtenez sur votre modèle initial avant tout entraînement est très différente de la perte/la métrique à laquelle vous vous attendez pour des prédictions aléatoires, vérifiez la façon dont votre perte ou votre métrique est calculée, car il y a probablement un bug. Si vous utilisez plusieurs pertes que vous ajoutez à la fin, assurez-vous qu'elles sont de la même échelle. +Si la perte/la métrique que vous obtenez sur votre modèle initial avant entraînement est très différente de la perte/la métrique à laquelle vous vous attendez pour des prédictions aléatoires, vérifiez la façon dont votre perte ou votre métrique est calculée. Il y a probablement un bug. Si vous utilisez plusieurs pertes que vous ajoutez à la fin, assurez-vous qu'elles sont de la même échelle. Lorsque vous êtes sûr que vos données sont parfaites, vous pouvez voir si le modèle est capable de s'entraîner sur elles grâce à un test simple. ### Surentraînement du modèle sur un seul batch -Le surentrâinement est généralement une chose que nous essayons d'éviter lors de l'entraînement, car cela signifie que le modèle n'apprend pas à reconnaître les caractéristiques générales que nous voulons qu'il reconnaisse, mais qu'il se contente de mémoriser les échantillons d'entraînement. Cependant, essayer d'entraîner votre modèle sur un batch encore et encore est un bon test pour vérifier si le problème tel que vous l'avez formulé peut être résolu par le modèle que vous essayez d'entraîner. Cela vous aidera également à voir si votre taux d'apprentissage initial est trop élevé. +Le surentraînement est généralement une chose que nous essayons d'éviter lors de l'entraînement car cela signifie que le modèle n'apprend pas à reconnaître les caractéristiques générales que nous voulons qu'il reconnaisse et se contente de mémoriser les échantillons d'entraînement. Cependant, essayer d'entraîner votre modèle sur un batch encore et encore est un bon test pour vérifier si le problème tel que vous l'avez formulé peut être résolu par le modèle que vous essayez d'entraîner. Cela vous aidera également à voir si votre taux d'apprentissage initial est trop élevé. -Une fois que vous avez défini votre `modèle`, c'est très facile ; il suffit de prendre un batch de données d'entraînement, puis de traiter ce `batch` comme votre ensemble de données entier, en l'ajustant sur un grand nombre d'époques : +Une fois que vous avez défini votre `modèle`, c'est très facile. Il suffit de prendre un batch de données d'entraînement, puis de le traiter comme votre jeu de données entier que vous *finetunez* sur un grand nombre d'époques : ```py for batch in train_dataset: break -# Make sure you have run model.compile() and set your optimizer, -# and your loss/metrics if you're using them +# Assurez-vous que vous avez exécuté model.compile() et défini votre optimiseur, +# et vos pertes/métriques si vous les utilisez. model.fit(batch, epochs=20) ``` @@ -457,7 +458,7 @@ model.fit(batch, epochs=20) Le modèle résultant devrait avoir des résultats proches de la perfection sur le `batch`, avec une perte diminuant rapidement vers 0 (ou la valeur minimale pour la perte que vous utilisez). -Si vous ne parvenez pas à ce que votre modèle obtienne des résultats parfaits comme celui-ci, cela signifie qu'il y a quelque chose qui ne va pas dans la façon dont vous avez formulé le problème ou dans vos données, et vous devez donc y remédier. Ce n'est que lorsque vous parviendrez à passer le test de surentraînement que vous pourrez être sûr que votre modèle peut réellement apprendre quelque chose. +Si vous ne parvenez pas à ce que votre modèle obtienne des résultats parfaits comme celui-ci, cela signifie qu'il y a quelque chose qui ne va pas dans la façon dont vous avez formulé le problème ou dans vos données et vous devez donc y remédier. Ce n'est que lorsque vous parviendrez à passer le test de surentraînement que vous pourrez être sûr que votre modèle peut réellement apprendre quelque chose. @@ -467,21 +468,21 @@ Si vous ne parvenez pas à ce que votre modèle obtienne des résultats parfaits ### Ne réglez rien tant que vous n'avez pas une première ligne de base -Le réglage des hyperparamètres est toujours considéré comme la partie la plus difficile de l'apprentissage automatique, mais c'est juste la dernière étape pour vous aider à gagner un peu sur la métrique. La plupart du temps, les hyperparamètres par défaut du `Trainer` fonctionneront très bien pour vous donner de bons résultats, donc ne vous lancez pas dans une recherche d'hyperparamètres longue et coûteuse jusqu'à ce que vous ayez quelque chose qui batte la ligne de base que vous avez sur votre jeu de données. +Le réglage des hyperparamètres est toujours considéré comme la partie la plus difficile de l'apprentissage automatique mais c'est juste la dernière étape pour vous aider à gagner un peu sur la métrique. La plupart du temps, les hyperparamètres par défaut du `Trainer` fonctionneront très bien pour vous donner de bons résultats. Donc ne vous lancez pas dans une recherche d'hyperparamètres longue et coûteuse jusqu'à ce que vous ayez quelque chose qui batte la ligne de base que vous avez sur votre jeu de données. -Une fois que vous avez un modèle suffisamment bon, vous pouvez commencer à l'affiner un peu. N'essayez pas de lancer un millier d'exécutions avec différents hyperparamètres, mais comparez quelques exécutions avec différentes valeurs pour un hyperparamètre afin de vous faire une idée de celui qui a le plus d'impact. +Une fois que vous avez un modèle suffisamment bon, vous pouvez commencer à le *finetuner* un peu. N'essayez pas de lancer un millier d'exécutions avec différents hyperparamètres mais comparez quelques exécutions avec différentes valeurs pour un hyperparamètre afin de vous faire une idée de celui qui a le plus d'impact. Si vous modifiez le modèle lui-même, restez simple et n'essayez rien que vous ne puissiez raisonnablement justifier. Veillez toujours à revenir au test de surentraînement pour vérifier que votre modification n'a pas eu de conséquences inattendues. ### Demander de l'aide -Nous espérons que vous avez trouvé dans cette section des conseils qui vous ont aidé à résoudre votre problème, mais si ce n'est pas le cas, n'oubliez pas que vous pouvez toujours demander de l'aide à la communauté sur le [forum](https://discuss.huggingface.co/). +Nous espérons que vous avez trouvé dans cette section des conseils qui vous ont aidé à résoudre votre problème. Si ce n'est pas le cas, n'oubliez pas que vous pouvez toujours demander de l'aide à la communauté sur le [forum](https://discuss.huggingface.co/). Voici quelques ressources (en anglais) supplémentaires qui peuvent s'avérer utiles : -- ["La reproductibilité comme vecteur des meilleures pratiques d'ingénierie"](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p) par Joel Grus -- ["Liste de contrôle pour le débogage des réseaux neuronaux"](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) par Cecelia Shao -- ["Comment tester unitairement le code d'apprentissage automatique"](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) par Chase Roberts -- ["Une recette pour Entraîner les réseaux neuronaux"](http://karpathy.github.io/2019/04/25/recipe/) par Andrej Karpathy +- [La reproductibilité comme vecteur des meilleures pratiques d'ingénierie](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p) par Joel Grus +- [Liste de contrôle pour le débogage des réseaux de neurones](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) par Cecelia Shao +- [Comment tester unitairement le code d'apprentissage automatique](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) par Chase Roberts +- [Une recette pour entraîner les réseaux de neurones](http://karpathy.github.io/2019/04/25/recipe/) par Andrej Karpathy -Bien sûr, tous les problèmes rencontrés lors de l'Entraînement des réseaux neuronaux ne sont pas forcément de votre faute ! Si vous rencontrez quelque chose dans la bibliothèque 🤗 *Transformers* ou 🤗 *Datasets* qui ne semble pas correct, vous avez peut-être rencontré un bogue. Vous devez absolument nous en parler, et dans la section suivante, nous allons vous expliquer exactement comment faire. +Bien sûr, tous les problèmes rencontrés lors de l'entraînement ne sont pas forcément de votre faute ! Si vous rencontrez quelque chose dans la bibliothèque 🤗 *Transformers* ou 🤗 *Datasets* qui ne semble pas correct, vous avez peut-être trouver un *bug*. Vous devez absolument nous en parler pour qu'on puisse le corriger. Dans la section suivante, nous allons vous expliquer exactement comment faire. diff --git a/chapters/fr/chapter8/5.mdx b/chapters/fr/chapter8/5.mdx index ee47ca77e..330305c27 100644 --- a/chapters/fr/chapter8/5.mdx +++ b/chapters/fr/chapter8/5.mdx @@ -1,4 +1,4 @@ -# Comment rédiger une bonne *issue* +# Comment rédiger une bonne issue -Lorsque vous rencontrez un problème avec l'une des bibliothèques Hugging Face, vous devez nous le faire savoir afin que nous puissions le corriger (il en va de même pour toute bibliothèque open source). Si vous n'êtes pas complètement certain que le bug se trouve dans votre propre code ou dans l'une de nos bibliothèques, le premier endroit à vérifier est le [forum](https://discuss.huggingface.co/). La communauté vous aidera à résoudre ce problème, et l'équipe Hugging Face suit également de près les discussions qui s'y déroulent. +Lorsque vous rencontrez un problème avec l'une des bibliothèques d'Hugging Face, faites le nous savoir afin que nous puissions le corriger (il en va de même pour toute bibliothèque open source).
+Si vous n'êtes pas complètement certain que le *bug* se trouve dans votre propre code ou dans l'une de nos bibliothèques, le premier endroit à vérifier est le [forum](https://discuss.huggingface.co/). La communauté vous aidera à résoudre votre problème et l'équipe d'Hugging Face y suit de près les discussions qui s'y déroulent. -Lorsque vous êtes sûr d'avoir un bogue en main, la première étape consiste à construire un exemple minimal reproductible. +Lorsque vous êtes sûr d'avoir identifier un *bug*, la première étape consiste à construire un exemple minimal qui soit reproductible. ## Créer un exemple minimal reproductible -Il est très important d'isoler le morceau de code qui produit le bug, car personne dans l'équipe Hugging Face n'est (encore) un magicien, et ils ne peuvent pas réparer ce qu'ils ne peuvent pas voir. Un exemple minimal reproductible doit, comme son nom l'indique, être reproductible. Cela signifie qu'il ne doit pas dépendre de fichiers ou de données externes que vous pourriez avoir. Essayez de remplacer les données que vous utilisez par des valeurs fictives qui ressemblent à vos valeurs réelles et qui produisent toujours la même erreur. +Il est très important d'isoler le morceau de code qui produit le *bug* car personne dans l'équipe d'Hugging Face n'est (encore) un magicien et on ne peut pas réparer ce qu'on ne peut pas voir. Un exemple minimal reproductible doit, comme son nom l'indique, être reproductible. Cela signifie qu'il ne doit pas dépendre de fichiers ou de données externes que vous pourriez avoir. Essayez de remplacer les données que vous utilisez par des valeurs fictives qui ressemblent à vos valeurs réelles et qui produisent toujours la même erreur. @@ -23,25 +24,25 @@ Il est très important d'isoler le morceau de code qui produit le bug, car perso -Une fois que vous avez quelque chose d'autonome, vous pouvez essayer de le réduire à encore moins de lignes de code, en construisant ce que nous appelons un _exemple reproductible minimal_. Bien que cela nécessite un peu plus de travail de votre part, vous serez presque assuré d'obtenir de l'aide et une correction si vous fournissez un exemple reproductible court et agréable. +Une fois que vous avez quelque chose d'autonome, essayez de le réduire au moins de lignes de code possible, en construisant ce que nous appelons un _exemple reproductible minimal_. Bien que cela nécessite un peu plus de travail de votre part, vous serez presque assuré d'obtenir de l'aide et une correction si vous fournissez un exemple reproductible court et agréable. -Si vous vous sentez suffisamment à l'aise, allez inspecter le code source où se trouve votre bogue. Vous trouverez peut-être une solution à votre problème (dans ce cas, vous pouvez même suggérer une pull request pour le corriger), mais plus généralement, cela peut aider les mainteneurs à mieux comprendre le code source lorsqu'ils lisent votre rapport. +Si vous vous sentez suffisamment à l'aise, allez inspecter le code source où se trouve votre *bug*. Vous trouverez peut-être une solution à votre problème (dans ce cas, vous pouvez même suggérer une *pull request* pour le corriger), mais plus généralement, cela peut aider les mainteneurs à mieux comprendre le code source lorsqu'ils lisent votre message. -## Remplir le modèle de problème +## Remplir le gabarit de problème -Lorsque vous déposez votre problème, vous remarquerez qu'il y a un modèle à remplir. Nous suivrons ici celui pour [🤗 *Transformers* issues](https://github.com/huggingface/transformers/issues/new/choose), mais le même type d'information sera requis si vous rapportez un problème dans un autre dépôt. Ne laissez pas le modèle vide : prendre le temps de le remplir maximisera vos chances d'obtenir une réponse et de résoudre votre problème. +Lorsque vous ouvrerez votre *issue* vous remarquerez qu'il y a un gabarit à remplir. Nous suivrons ici celui pour la bibliothèque [🤗 *Transformers*](https://github.com/huggingface/transformers/issues/new/choose) mais le même type d'information sera requis dans un autre dépôt. Ne laissez pas le gabarit vide : prendre le temps de le remplir maximisera vos chances d'obtenir une réponse et de résoudre votre problème. -En général, lorsque vous signalez un problème, restez toujours courtois. Il s'agit d'un projet open source, vous utilisez donc un logiciel libre, et personne n'est obligé de vous aider. Vous pouvez inclure dans votre problème des critiques qui vous semblent justifiées, mais les mainteneurs pourraient très bien les prendre mal et ne pas être pressés de vous aider. Assurez-vous de lire le [code de conduite](https://github.com/huggingface/transformers/blob/master/CODE_OF_CONDUCT.md) du projet. +En général, lorsque vous signalez un problème, restez toujours courtois. Il s'agit d'un projet open source, vous utilisez donc un logiciel libre, et personne n'est obligé de vous aider. Vous pouvez inclure dans votre *issue* des critiques qui vous semblent justifiées mais les mainteneurs pourraient très bien les prendre mal et ne pas être pressés de vous aider. Assurez-vous de lire le [code de conduite](https://github.com/huggingface/transformers/blob/master/CODE_OF_CONDUCT.md) du projet. ### Inclure les informations sur votre environnement -🤗 *Transformers* fournit un utilitaire pour obtenir toutes les informations dont nous avons besoin sur votre environnement. Il suffit de taper ce qui suit dans votre terminal : +🤗 *Transformers* fournit un utilitaire pour obtenir toutes les informations nécessaire concernant votre environnement. Il suffit de taper ce qui suit dans votre terminal : ``` transformers-cli env ``` -et vous devriez obtenir quelque chose comme ça : +et vous devriez obtenir quelque chose comme : ```out Copy-and-paste the text below in your GitHub issue and FILL OUT the two last points. @@ -58,34 +59,34 @@ Copy-and-paste the text below in your GitHub issue and FILL OUT the two last poi - Using distributed or parallel set-up in script?: ``` -Vous pouvez également ajouter un `!` au début de la commande `transformers-cli env` pour l'exécuter depuis une cellule du *notebook* puis copier et coller le résultat au début de votre problème. +Vous pouvez également ajouter un `!` au début de la commande `transformers-cli env` pour l'exécuter depuis une cellule de *notebook* puis copier et coller le résultat au début de votre *issue*. -### Marquer des personnes +### Taguer des personnes -Marquer des personnes en tapant un `@` suivi de leur identifiant GitHub leur enverra une notification afin qu'elles voient votre problème et puissent répondre plus rapidement. Utilisez cette fonction avec modération, car les personnes que vous marquez peuvent ne pas apprécier d'être notifiées si elles n'ont pas de lien direct avec le problème. Si vous avez regardé les fichiers sources liés à votre bogue, vous devriez étiqueter la dernière personne qui a fait des changements à la ligne que vous pensez être responsable de votre problème (vous pouvez trouver cette information en regardant ladite ligne sur GitHub, en la sélectionnant, puis en cliquant sur "View git blame"). +Taguer des personnes en tapant un `@` suivi de leur identifiant GitHub leur enverra une notification afin qu'elles voient votre problème et puissent répondre plus rapidement. Néanmoins utilisez cette fonction avec modération car les personnes que vous taguez peuvent ne pas apprécier d'être notifiées si elles n'ont pas de lien direct avec le problème. Si vous avez regardé les fichiers sources liés à votre *bug*, vous devriez taguer la dernière personne qui a fait des changements à la ligne que vous pensez être responsable de votre problème (vous pouvez trouver cette information en regardant ladite ligne sur GitHub, en la sélectionnant, puis en cliquant sur « *View git blame* »). -Sinon, le modèle propose des suggestions de personnes à étiqueter. En général, ne marquez jamais plus de trois personnes ! +Sinon, le gabarit propose des suggestions de personnes à taguer. En général, ne marquez jamais plus de trois personnes ! ### Inclure un exemple reproductible -Si vous avez réussi à créer un exemple autonome qui produit le bogue, il est temps de l'inclure ! Tapez une ligne avec trois backticks suivis de `python`, comme ceci : +Si vous avez réussi à créer un exemple autonome qui produit le *bug*, il est temps de l'inclure ! Tapez une ligne avec trois *backticks* suivis de `python`, comme ceci : ``` ```python ``` -puis collez votre exemple minimal reproductible et tapez une nouvelle ligne avec trois backticks. Cela permettra de s'assurer que votre code est correctement formaté. +puis collez votre exemple minimal reproductible et tapez une nouvelle ligne avec trois *backticks*. Cela permettra de s'assurer que votre code est correctement formaté. -Si vous n'avez pas réussi à créer un exemple reproductible, expliquez en étapes claires comment vous êtes arrivé à votre problème. Si vous le pouvez, incluez un lien vers un *notebook* de Google Colab où vous avez trouvé l'erreur. Plus vous partagerez d'informations, plus les mainteneurs seront en mesure de vous répondre. +Si vous n'avez pas réussi à créer un exemple reproductible, expliquez en des étapes claires comment vous êtes arrivé à votre problème. Si vous le pouvez, incluez un lien vers un *notebook* d'un Google Colab où vous avez trouvé l'erreur. Plus vous partagerez d'informations, plus les mainteneurs seront en mesure de vous répondre. -Dans tous les cas, vous devez copier et coller l'intégralité du message d'erreur que vous obtenez. Si vous travaillez dans Colab, n'oubliez pas que certains cadres peuvent être automatiquement réduits dans la trace de la pile, et veillez donc à les développer avant de les copier. Comme pour l'exemple de code, placez le message d'erreur entre deux lignes avec trois points de suspension, afin qu'il soit correctement formaté. +Dans tous les cas, vous devez copier et coller l'intégralité du message d'erreur que vous obtenez. Si vous travaillez dans Colab, n'oubliez pas que certaines cellules peuvent être automatiquement réduites dans la trace de la pile et veillez donc à les afficher avant de les copier. Comme pour l'exemple de code, placez le message d'erreur entre deux lignes avec trois *backticks* afin qu'il soit correctement formaté. ### Décrire le comportement attendu -Expliquez en quelques lignes ce que vous vous attendiez à obtenir, afin que les mainteneurs comprennent bien le problème. Cette partie est généralement assez évidente, elle devrait donc tenir en une seule phrase, mais dans certains cas, vous pouvez avoir beaucoup à dire. +Expliquez en quelques lignes ce que vous vous attendiez à obtenir afin que les mainteneurs comprennent bien le problème. Cette partie est généralement assez évidente, elle devrait donc tenir en une seule phrase mais dans certains cas vous pouvez avoir beaucoup à dire. ## Et ensuite ? -Une fois que votre problème est classé, vérifiez rapidement que tout est en ordre. Vous pouvez modifier le problème si vous avez fait une erreur, ou même changer son titre si vous vous rendez compte que le problème est différent de ce que vous pensiez initialement. +Une fois que votre problème est classé, vérifiez rapidement que tout est en ordre. Vous pouvez modifier le problème si vous avez fait une erreur ou même changer son titre si vous vous rendez compte que le problème est différent de ce que vous pensiez initialement. Il est inutile d'envoyer des messages aux personnes concernées si vous n'obtenez pas de réponse. Si personne ne vous aide au bout de quelques jours, il est probable que personne n'a pu donner un sens à votre problème. N'hésitez pas à revenir à l'exemple reproductible. Pouvez-vous le rendre plus court et plus concis ? Si vous n'obtenez pas de réponse au bout d'une semaine, vous pouvez laisser un message demandant gentiment de l'aide, surtout si vous avez modifié votre question pour inclure plus d'informations sur le problème. diff --git a/chapters/fr/chapter8/7.mdx b/chapters/fr/chapter8/7.mdx index 217771167..6f93e4e7f 100644 --- a/chapters/fr/chapter8/7.mdx +++ b/chapters/fr/chapter8/7.mdx @@ -4,7 +4,7 @@ Testons ce que vous avez appris dans ce chapitre ! -### 1. Dans quel ordre devez-vous lire un *traceback* Python ? +### 1. Dans quel ordre devez-vous lire un traceback Python ? traceback de Python montrant l'exception en bas est qu'il est plus facile de déboguer lorsque vous travaillez dans le terminal et que c'est la dernière ligne que vous voyez.", + explain: "L'avantage d'un traceback de Python montrant l'exception en bas est qu'il est plus facile de déboguer lorsque vous travaillez dans le terminal et que c'est la dernière ligne que vous voyez.", correct: true } ]} @@ -25,12 +25,12 @@ Testons ce que vous avez appris dans ce chapitre ! transformer à partir d'un article de recherche.", + text: "Une implémentation simmple d'un transformer à partir d'un article de recherche.", explain: "Bien qu'il soit très éducatif d'implémenter vos propres modèles de transformers à partir de zéro, ce n'est pas ce dont nous parlons ici." }, { text: "Un bloc de code compact et autonome qui peut être exécuté sans aucune dépendance externe sur des fichiers ou des données privées.", - explain: "Corrigez ! Des exemples minimaux reproductibles aident les mainteneurs de la bibliothèque à reproduire le problème que vous rencontrez, afin qu'ils puissent trouver des solutions plus rapidement.", + explain: "Des exemples minimaux reproductibles aident les mainteneurs de la bibliothèque à reproduire le problème que vous rencontrez, afin qu'ils puissent trouver des solutions plus rapidement.", correct: true }, { @@ -72,12 +72,12 @@ Lequel des éléments suivants pourrait être un bon choix pour le titre d'un su }, { text: "Pourquoi je ne peux pas importer GPT3ForSequenceClassification?", - explain: "Bon choix ! Ce titre est concis et donne au lecteur un indice sur ce qui pourrait être erroné (par exemple, que GPT-3 n'est pas pris en charge dans 🤗 Transformers).", + explain: "Ce titre est concis et donne au lecteur un indice sur ce qui pourrait être erroné (par exemple, que GPT-3 n'est pas pris en charge dans 🤗 Transformers).", correct: true }, { text: "Le GPT-3 est-il pris en charge dans 🤗 Transformers ?", - explain: "Bien vu ! Utiliser des questions comme titres de sujets est un excellent moyen de communiquer le problème à la communauté..", + explain: "Utiliser des questions comme titres de sujets est un excellent moyen de communiquer le problème à la communauté.", correct: true } ]} @@ -89,7 +89,7 @@ Lequel des éléments suivants pourrait être un bon choix pour le titre d'un su choices={[ { text: "L'étape d'optimisation où nous calculons les gradients et effectuons la rétropropagation.", - explain: "Bien qu'il puisse y avoir des bogues dans votre optimiseur, cela se produit généralement à plusieurs étapes du pipeline d'entraînement, il y a donc d'autres choses à vérifier d'abord. Essayez à nouveau !" + explain: "Bien qu'il puisse y avoir des bugs dans votre optimiseur, cela se produit généralement à plusieurs étapes du pipeline d'entraînement, il y a donc d'autres choses à vérifier d'abord. Essayez à nouveau !" }, { text: "L'étape d'évaluation où nous calculons les métriques", @@ -140,8 +140,8 @@ Lequel des éléments suivants pourrait être un bon choix pour le titre d'un su bug.", + explain: "C'est la meilleure façon d'aider les mainteneurs à trouver votre bogue. Que devez-vous faire d'autre ?", correct: true }, { @@ -166,7 +166,7 @@ Lequel des éléments suivants pourrait être un bon choix pour le titre d'un su }, { text: "Elle nous permet de vérifier que le modèle est capable de réduire la perte à zéro.", - explain: "Correct ! Avec un petit batch d'à peine deux exemples, nous pouvons rapidement vérifier si le modèle est capable d'apprendre.", + explain: "Avec un petit batch d'à peine deux exemples, nous pouvons rapidement vérifier si le modèle est capable d'apprendre.", correct: true }, { @@ -182,17 +182,18 @@ Lequel des éléments suivants pourrait être un bon choix pour le titre d'un su choices={[ { text: "Cela permet aux mainteneurs de comprendre quelle version de la bibliothèque vous utilisez.", - explain: "Correct ! Comme chaque version majeure de la bibliothèque peut comporter des modifications de l'API, le fait de connaître la version spécifique que vous utilisez peut vous aider à circonscrire le problème. Quels sont les autres avantages ?", + explain: "Comme chaque version majeure de la bibliothèque peut comporter des modifications de l'API, le fait de connaître la version spécifique que vous utilisez peut vous aider à circonscrire le problème. Quels sont les autres avantages ?", correct: true }, { text: "Il permet aux mainteneurs de savoir si vous exécutez le code sous Windows, macOS ou Linux.", - explain: "Correct ! Les erreurs peuvent parfois être causées par le système d'exploitation spécifique que vous utilisez, et le fait de le savoir aide les mainteneurs à les reproduire localement. Mais ce n'est pas la seule raison.", + explain: "Les erreurs peuvent parfois être causées par le système d'exploitation spécifique que vous utilisez, et le fait de le savoir aide les mainteneurs à les reproduire localement. Mais ce n'est pas la seule raison.", correct: true }, { text: "Il permet aux mainteneurs de savoir si le code est exécuté sur un GPU ou un CPU.", - explain: "Correct ! Comme nous l'avons vu dans ce chapitre, les erreurs sur les GPU et les CPU peuvent avoir une saveur très différente, et savoir quel matériel vous utilisez peut aider à focaliser l'attention des mainteneurs. Mais ce n'est pas le seul avantage...", + explain: "Comme nous l'avons vu dans ce chapitre, les erreurs sur les GPU et les CPU peuvent avoir une saveur très différente, et savoir quel matériel vous utilisez peut aider à focaliser l'attention des mainteneurs. Mais ce n'est pas le seul avantage...", + correct: true } ]} /> diff --git a/chapters/fr/chapter9/1.mdx b/chapters/fr/chapter9/1.mdx new file mode 100644 index 000000000..d0be0c8be --- /dev/null +++ b/chapters/fr/chapter9/1.mdx @@ -0,0 +1,32 @@ +# Introduction à Gradio + +Dans ce chapitre, nous allons apprendre à construire des **démos interactives** pour vos modèles d'apprentissage automatique. + +Pourquoi construire une démo ou une interface graphique pour votre modèle d'apprentissage automatique ? Les démos permettent : + +- aux **développeurs en apprentissage automatique** de présenter facilement leur travail à un large public, y compris des équipes non techniques ou des clients. +- aux **chercheurs** de reproduire plus facilement les modèles d'apprentissage automatique et leur comportement. +- aux **testeurs qualité** ou **utilisateurs finaux** d'identifier et de déboguer plus facilement les points de défaillance des modèles. +- aux **utilisateurs divers** de découvrir les biais algorithmiques des modèles. + +Nous utiliserons la bibliothèque *Gradio* pour construire des démos pour nos modèles. *Gradio* vous permet de construire, de personnaliser et de partager des démos en ligne pour n'importe quel modèle d'apprentissage automatique. Et cela entièrement en Python. + +Voici quelques exemples de démos d'apprentissage automatique construites avec Gradio : + +* Un modèle de **reconnaissance de croquis** qui prend un croquis et produit des étiquettes de ce qu'il pense être dessiné : + + + +* Un modèle extractif de **réponse à une question** qui prend en entrée un paragraphe de contexte et une requête et produit une réponse et un score de probabilité (nous avons discuté de ce type de modèle [au chapitre 7](/course/fr/chapter7/7)) : + + + +* Un modèle de **suppression de l'arrière-plan** qui prend une image et la restitue avec l'arrière-plan supprimé : + + + +Ce chapitre est divisé en sections qui comprennent à la fois des _concepts_ et des _applications_. Après avoir appris le concept dans chaque section, vous l'appliquerez pour construire un type particulier de démo, allant de la classification d'images à la reconnaissance vocale. À la fin de ce chapitre, vous serez en mesure de créer ces démos (et bien d'autres !) en quelques lignes de code Python seulement. + + +👀 Consultez Hugging Face Spaces pour voir de nombreux exemples récents de démos d'apprentissage automatique construites par la communauté ! + diff --git a/chapters/fr/chapter9/2.mdx b/chapters/fr/chapter9/2.mdx new file mode 100644 index 000000000..339680935 --- /dev/null +++ b/chapters/fr/chapter9/2.mdx @@ -0,0 +1,110 @@ +# Construire votre première démo + +Commençons par installer *Gradio* ! Comme il s'agit d'un *package* Python, il suffit de l'exécuter : + +`$ pip install gradio ` + +Vous pouvez exécuter *Gradio* n'importe où, que ce soit dans votre IDE Python préféré, dans des *notebooks* ou même dans Google Colab 🤯 ! +Alors installez *Gradio* partout où vous exécutez Python ! + +Commençons par un exemple simple de type « *Hello World* » pour nous familiariser avec la syntaxe de *Gradio* : + +```py +import gradio as gr + + +def greet(name): + return "Hello " + name + + +demo = gr.Interface(fn=greet, inputs="text", outputs="text") + +demo.launch() +``` + +Parcourons le code ci-dessus : + +- D'abord, nous définissons une fonction appelée `greet()`. Dans ce cas, c'est une simple fonction qui ajoute « *Hello* » devant votre nom, mais cela peut être *n'importe quelle* fonction Python en général. Par exemple, dans les applications d'apprentissage automatique, cette fonction pourrait *appeler un modèle pour faire une prédiction* sur une entrée et retourner la sortie. +- Ensuite, nous créons une `Interface` *Gradio* avec trois arguments, `fn`, `inputs`, et `outputs`. Ces arguments définissent la fonction de prédiction, ainsi que le _type_ de composants d'entrée et de sortie que nous souhaitons. Dans notre cas, les deux composants sont de simples boîtes de texte. +- Nous appelons ensuite la méthode `launch()` sur l'`Interface` que nous avons créée. + +Si vous exécutez ce code, l'interface ci-dessous apparaîtra automatiquement dans un *notebook* Jupyter/Colab ou dans un navigateur sur **[http://localhost:7860](http://localhost:7860/)** si vous l'exécutez à partir d'un script. + + + +Essayez d'utiliser cette interface maintenant avec votre propre nom ou une autre entrée ! + +Vous remarquerez que dedans, *Gradio* a automatiquement déduit le nom du paramètre d'entrée (`name`) et l'a appliqué comme étiquette au dessus de la zone de texte. Que faire si vous souhaitez changer cela ? +Ou si vous souhaitez personnaliser la zone de texte d'une autre manière ? Dans ce cas, vous pouvez instancier un objet de classe représentant le composant de saisie. + +Jetez un coup d'œil à l'exemple ci-dessous : + +```py +import gradio as gr + + +def greet(name): + return "Hello " + name + + +# Nous instancions la classe Textbox +textbox = gr.Textbox(label="Type your name here:", placeholder="John Doe", lines=2) + +gr.Interface(fn=greet, inputs=textbox, outputs="text").launch() +``` + + + +Ici, nous avons créé une zone de texte d'entrée avec une étiquette, un espace réservé et un nombre de lignes défini. +Vous pourriez faire la même chose pour la zone de texte de sortie, mais nous allons laisser cela pour le moment. + +Nous avons vu qu'avec seulement quelques lignes de code, *Gradio* vous permet de créer une interface simple autour de n'importe quelle fonction +avec n'importe quel type d'entrées ou de sorties. Dans cette section, nous avons commencé par une simple boîte de texte mais dans les sections suivantes, nous couvrirons d'autres types d'entrées et de sorties. Voyons maintenant comment inclure un peu de NLP dans une application *Gradio*. + + +## 🤖 Inclure les prédictions du modèle + +Construisons maintenant une interface simple qui permet de faire une démo d'un modèle de **génération de texte** comme le GPT-2. + +Nous allons charger notre modèle en utilisant la fonction `pipeline()` de 🤗 *Transformers*. +Si vous avez besoin d'un rafraîchissement rapide, vous pouvez revenir à [cette section du chapitre 1](/course/fr/chapter1/3#text-generation). + +Tout d'abord, nous définissons une fonction de prédiction qui prend une invite de texte et renvoie la complétion du texte : + +```py +from transformers import pipeline + +model = pipeline("text-generation") + + +def predict(prompt): + completion = model(prompt)[0]["generated_text"] + return completion +``` + +Cette fonction complète le texte que vous fournissez, et vous pouvez l'exécuter avec les votres pour voir comment elle fonctionne. Voici un exemple (vous obtiendrez peut-être un résultat différent) : + + +``` +predict("My favorite programming language is") # Mon langage de programmation préféré est +``` + +``` +>> 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. +# Mon langage de programmation préféré est Haskell. J'ai vraiment apprécié le langage Haskell, mais il n'a pas toutes les caractéristiques que l'on peut appliquer à n'importe quel autre langage. Par exemple, il ne fait que compiler un tableau d'octets. +``` + +Maintenant que nous avons une fonction pour générer des prédictions, nous pouvons créer et lancer une `Interface` de la même manière que nous l'avons fait précédemment : + +```py +import gradio as gr + +gr.Interface(fn=predict, inputs="text", outputs="text").launch() +``` + + +C'est fait ! Vous pouvez maintenant utiliser cette interface pour générer du texte en utilisant le modèle GPT-2 comme indiqué ci-dessous 🤯. + + + +Continuez votre lecture du cours pour voir comment construire d'autres types de démos avec *Gradio* ! \ No newline at end of file diff --git a/chapters/fr/chapter9/3.mdx b/chapters/fr/chapter9/3.mdx new file mode 100644 index 000000000..2117c1511 --- /dev/null +++ b/chapters/fr/chapter9/3.mdx @@ -0,0 +1,160 @@ +# Comprendre la classe Interface + +Dans cette section, nous allons examiner de plus près la classe `Interface`, et comprendre les principaux paramètres utilisés pour en créer une. + +## Comment créer une interface + +Vous remarquerez que la classe `Interface` a 3 paramètres obligatoires : + +`Interface(fn, inputs, outputs, ...)` + +Ces paramètres sont : + + - `fn`: la fonction de prédiction qui est enveloppée par l'interface *Gradio*. Cette fonction peut prendre un ou plusieurs paramètres et retourner une ou plusieurs valeurs. + - `inputs`: le(s) type(s) de composant(s) d'entrée. *Gradio* fournit de nombreux composants préconstruits tels que`"image"` ou `"mic"`. + - `outputs`: le(s) type(s) de composant(s) de sortie. Encore une fois, *Gradio* fournit de nombreux composants pré-construits, par ex. `"image"` ou `"label"`. + +Pour une liste complète des composants, [jetez un coup d'œil à la documentation de *Gradio*](https://gradio.app/docs). Chaque composant préconstruit peut être personnalisé en instanciant la classe correspondant au composant. + +Par exemple, comme nous l'avons vu dans la [section précédente](/course/fr/chapter9/2), au lieu de passer le paramètre `inputs` par `"textbox"`, vous pouvez passer un composant `Textbox(lines=7, label="Prompt")` pour créer une zone de texte avec 7 lignes et un label. + +Voyons un autre exemple, cette fois avec un composant `Audio`. + +## Un exemple simple avec audio + +Comme mentionné précédemment,*Gradio* fournit de nombreuses entrées et sorties différentes. +Construisons donc une `Interface` qui fonctionne avec l'audio. + +Dans cet exemple, nous allons construire une fonction audio-vers-audio qui prend un fichier audio et l'inverse simplement. + +Nous utiliserons comme entrée le composant `Audio`. Lorsque vous utilisez le composant `Audio`, vous pouvez spécifier si vous voulez que la `source` de l'audio soit un fichier que l'utilisateur télécharge ou un microphone avec lequel l'utilisateur enregistre sa voix. Dans ce cas, nous allons choisir un "microphone". Juste pour le plaisir, nous allons ajouter une étiquette à notre `Audio` qui dit « *Speak here...* » (Parler ici). + +De plus, nous aimerions recevoir l'audio sous la forme d'un tableau numpy afin de pouvoir facilement l'inverser. Nous allons donc définir le `"type"` comme étant `"numpy"`, ce qui permet de passer les données d'entrée comme un *tuple* de (`sample_rate`, `data`) dans notre fonction. + +Nous utiliserons également le composant de sortie `Audio` qui peut automatiquement rendre un *tuple* avec un taux d'échantillonnage et un tableau numpy de données comme un fichier audio lisible. +Dans ce cas, nous n'avons pas besoin de faire de personnalisation, donc nous utiliserons le raccourci de la chaîne `"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() +``` + +Le code ci-dessus produira une interface comme celle qui suit (si votre navigateur ne vous demande pas l'autorisation pour accéder au microphone, ouvrez la démo dans un onglet séparé). + + + +Vous devriez maintenant être capable d'enregistrer votre voix et de vous entendre parler à l'envers. Effrayant 👻 ! + +## Gérer les entrées et sorties multiples + +Imaginons que nous ayons une fonction plus compliquée, avec plusieurs entrées et sorties. +Dans l'exemple ci-dessous, nous avons une fonction qui prend un index de liste déroulante, une valeur de curseur et un nombre, et renvoie un échantillon audio d'une tonalité musicale. + +Regardez comment nous passons une liste de composants d'entrée et de sortie, et voyez si vous pouvez suivre ce qui se passe. + +La clé ici est que lorsque vous passez : +* une liste de composants d'entrée, chaque composant correspond à un paramètre dans l'ordre. +* une liste de composants de sortie, chaque composant correspond à une valeur retournée. + +L'extrait de code ci-dessous montre comment trois composants d'entrée correspondent aux trois arguments de la fonction `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() +``` + + + + +### La méthode `launch()`. + +Jusqu'à présent, nous avons utilisé la méthode `launch()` pour lancer l'interface, mais nous n'avons pas vraiment discuté de ce qu'elle fait. + +Par défaut, la méthode `launch()` lancera la démo dans un serveur web qui tourne localement. Si vous exécutez votre code dans un *notebook* Jupyter ou Colab, *Gradio* va intégrer l'interface graphique de la démo dans le *notebook* afin que vous puissiez l'utiliser facilement. + +Vous pouvez personnaliser le comportement de `launch()` à travers différents paramètres : + + - `inline` : si vous voulez afficher l'interface en ligne sur les *notebooks* Python. + - `inbrowser` : pour lancer automatiquement l'interface dans un nouvel onglet du navigateur par défaut. + - `share` : si vous voulez créer un lien public partageable depuis votre ordinateur pour l'interface. Un peu comme un lien Google Drive ! + +Nous couvrirons le paramètre `share` plus en détail dans la section suivante ! + +## ✏️ Appliquons-le ! + +Construisons une interface qui vous permette de faire la démonstration d'un modèle de **reconnaissance vocale**. +Pour rendre la chose intéressante, nous accepterons *soit* une entrée micro, soit un fichier téléchargé. + +Comme d'habitude, nous allons charger notre modèle de reconnaissance vocale en utilisant la fonction `pipeline()` de 🤗 *Transformers*. +Si vous avez besoin d'un rafraîchissement rapide, vous pouvez revenir à [cette section du chapitre 1](/course/fr/chapter1/3). Ensuite, nous allons implémenter une fonction `transcribe_audio()` qui traite l'audio et retourne la transcription (en anglais). Enfin, nous allons envelopper cette fonction dans une `Interface` avec les composants `Audio` pour les entrées et juste le texte pour la sortie. Au total, le code de cette application est le suivant : + +```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() +``` + +Si votre navigateur ne vous demande pas l'autorisation pour accéder au microphone, ouvrez la démo dans un onglet séparé. + + + + +Voilà, c'est fait ! Vous pouvez maintenant utiliser cette interface pour transcrire de l'audio. Remarquez ici qu'en passant le paramètre `optional` à `True`, nous permettons à l'utilisateur de soit fournir un microphone ou un fichier audio (ou aucun des deux, mais cela retournera un message d'erreur). + +Continuez pour voir comment partager votre interface avec d'autres ! \ No newline at end of file diff --git a/chapters/fr/chapter9/4.mdx b/chapters/fr/chapter9/4.mdx new file mode 100644 index 000000000..64ffcd196 --- /dev/null +++ b/chapters/fr/chapter9/4.mdx @@ -0,0 +1,140 @@ +# Partager ses démos avec les autres + +Maintenant que vous avez construit une démo, vous voudrez probablement la partager à d'autres personnes. Les démos *Gradio* peuvent être partagées de deux façons : en utilisant un lien de partage temporaire (***temporary share link***) ou un hébergement permanent (***permanent hosting on Spaces***). + +Nous aborderons ces deux approches sous peu. Mais avant de partager votre démo, vous voudrez peut-être la peaufiner 💅. + +### Polir votre démo Gradio + +
+Overview of a gradio interface + +
+ +Pour ajouter du contenu supplémentaire à votre démo, la classe `Interface` supporte quelques paramètres optionnels : + - `title` : vous pouvez donner un titre à votre démo, qui apparaît _au-dessus_ des composants d'entrée et de sortie. + - `description` : vous pouvez donner une description (en texte, Markdown, ou HTML) pour l'interface, qui apparaît au-dessus des composants d'entrée et de sortie et en dessous du titre. + - `article` : vous pouvez également écrire un article étendu (en texte, Markdown ou HTML) expliquant l'interface. S'il est fourni, il apparaît _sous_ les composants d'entrée et de sortie. + - `theme` : vous n'aimez pas les couleurs par défaut ? Définissez le thème pour utiliser une des couleurs suivantes : `default`, `huggingface`, `grass`, `peach`. Vous pouvez également ajouter le préfixe `dark-`, par exemple `dark-peach` pour un thème sombre (ou juste `dark` pour le thème sombre par défaut). + - `examples` : pour rendre votre démo *beaucoup plus facile à utiliser*, vous pouvez fournir quelques exemples d'entrées pour la fonction. Ceux-ci apparaissent sous les composants de l'interface utilisateur et peuvent être utilisés pour remplir l'interface. Ils doivent être fournis sous forme de liste imbriquée, dans laquelle la liste extérieure est constituée d'exemples et chaque liste intérieure est constituée d'une entrée correspondant à chaque composant d'entrée. + - `live` : si vous voulez que votre modèle soit relancé à chaque fois que l'entrée change, vous pouvez mettre `live=True`. Ceci est utile pour les modèles rapides (nous verrons un exemple à la fin de cette section). +En utilisant les options ci-dessus, nous obtenons une interface plus complète. Exécutez le code ci-dessous pour pouvoir discuter avec Rick et Morty : + +```python out +title = "Ask Rick a Question" # "Posez une question à Rick" +description = +""" +The bot was trained to answer questions based on Rick and Morty dialogues. Ask Rick anything! +# Le robot a été entraîné à répondre à des questions basées sur les dialogues de Rick et Morty. +# Demandez à Rick ce que vous voulez ! + +""" + +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." +# Jetez un coup d'œil au [bot original Rick et Morty] (https://huggingface.co/spaces/kingabzpro/Rick_and_Morty_Bot) sur lequel cette démo est basée. + +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?"]] + # ["Que faites-vous ?"], ["Où devrions-nous voyager dans le temps ?"] +).launch() +``` + +En utilisant les options ci-dessus, nous obtenons une interface plus complète. Essayez l'interface ci-dessous : + + + +### Partager votre démo avec des liens temporaires +Maintenant que nous avons une démo fonctionnelle de notre modèle d'apprentissage automatique, apprenons à partager facilement un lien vers notre interface. +Les interfaces peuvent être facilement partagées publiquement en mettant `share=True` dans la méthode `launch()` : + +```python +gr.Interface(classify_image, "image", "label").launch(share=True) +``` + +Cela génère un lien public et partageable que vous pouvez envoyer à n'importe qui ! Lorsque vous envoyez ce lien, l'utilisateur de l'autre côté peut essayer le modèle dans son navigateur pendant 72 heures au maximum. Le traitement s'effectuant sur votre appareil (tant qu'il reste allumé !), vous n'avez pas à vous soucier de la mise en place de dépendances. Si vous travaillez à partir d'un *notebook* Google Colab, un lien de partage est toujours créé automatiquement. Il ressemble généralement à quelque chose comme ceci : **XXXXX.gradio.app**. Bien que le lien soit servi par un lien *Gradio*, nous ne sommes qu'un proxy pour votre serveur local, et nous ne stockons pas les données envoyées par les interfaces. + +Gardez cependant à l'esprit que ces liens sont accessibles au public, ce qui signifie que n'importe qui peut utiliser votre modèle pour la prédiction ! Par conséquent, assurez-vous de ne pas exposer d'informations sensibles à travers les fonctions que vous écrivez, ou de permettre que des changements critiques se produisent sur votre appareil. Si vous définissez `share=False` (la valeur par défaut), seul un lien local est créé. + +### Hébergement de votre démo sur Hugging Face Spaces + +Un lien de partage que vous pouvez passer à vos collègues est cool, mais comment pouvez-vous héberger de façon permanente votre démo et la faire exister dans son propre « espace » sur internet ? + +*Hugging Face Spaces* fournit l'infrastructure pour héberger de façon permanente votre démo *Gradio* sur internet et **gratuitement** ! *Spaces* vous permet de créer et de pousser vers un dépôt (public ou privé) le code de votre interface *Gradio*. Il sera placé dans un fichier `app.py`. [Lisez ce tutoriel étape par étape](https://huggingface.co/blog/gradio-spaces) pour commencer ou regardez la vidéo ci-dessous. + + + +## ✏️ Let's apply it! + +En utilisant ce que nous avons appris dans les sections précédentes, créons la démo de reconnaissance de croquis que nous avons décrit dans la [section un de ce chapitre] (/course/fr/chapter9/1). Ajoutons quelques personnalisations à notre interface et définissons `share=True` pour créer un lien public que nous pouvons faire circuler. + +Nous pouvons charger les étiquettes depuis [class_names.txt](https://huggingface.co/spaces/dawood/Sketch-Recognition/blob/main/class_names.txt) et charger le modèle Pytorch pré-entraîné depuis [pytorch_model.bin](https://huggingface.co/spaces/dawood/Sketch-Recognition/blob/main/pytorch_model.bin). Téléchargez ces fichiers en suivant le lien et en cliquant sur « *download* » dans le coin supérieur gauche de l'aperçu du fichier. Regardons le code ci-dessous pour voir comment nous utilisons ces fichiers pour charger notre modèle et créer une fonction `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)} +``` + +Maintenant que nous avons une fonction `predict()`. La prochaine étape est de définir et de lancer notre interface *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!", + # Qui veut jouer au Pictionary ? Dessinez un objet courant comme une pelle ou un ordinateur portable, et l'algorithme le devinera en temps réel ! + article="

Sketch Recognition | Demo Model

", + live=True, +) +interface.launch(share=True) +``` + + + + +Notice the `live=True` parameter in `Interface`, which means that the sketch demo makes a prediction every time someone draws on the sketchpad (no submit button!). + +De plus, nous avons également défini l'argument `share=True` dans la méthode `launch()`. +Cela créera un lien public que vous pourrez envoyer à n'importe qui ! Lorsque vous envoyez ce lien, l'utilisateur de l'autre côté peut essayer le modèle de reconnaissance de croquis. Pour réitérer, vous pouvez également héberger le modèle sur *Hugging Face Spaces*, ce qui nous permet d'intégrer la démo ci-dessus. + +La prochaine fois, nous couvrirons d'autres façons dont *Gradio* peut être utilisé avec l'écosystème d'*Hugging Face* ! \ No newline at end of file diff --git a/chapters/fr/chapter9/5.mdx b/chapters/fr/chapter9/5.mdx new file mode 100644 index 000000000..18f1b9c67 --- /dev/null +++ b/chapters/fr/chapter9/5.mdx @@ -0,0 +1,66 @@ +# Intégrations avec le Hub d'Hugging Face + +Pour vous rendre la vie encore plus facile, *Gradio* s'intègre directement avec *Hub* et *Spaces*. +Vous pouvez charger des démos depuis le *Hub* et les *Spaces* avec seulement *une ligne de code*. + +### Chargement de modèles depuis lle Hub d'Hugging Face +Pour commencer, choisissez un des milliers de modèles qu'*Hugging Face* offre à travers le *Hub*, comme décrit dans le [chapitre 4](/course/fr/chapter4/2). + +En utilisant la méthode spéciale `Interface.load()`, vous passez `"model/"` (ou, de manière équivalente, `"huggingface/"`) suivi du nom du modèle. +Par exemple, voici le code pour construire une démo pour le [GPT-J](https://huggingface.co/EleutherAI/gpt-j-6B), un grand modèle de langue, ajouter quelques exemples d'entrées : + +```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." +# Démo Gradio pour GPT-J 6B, un modèle de transformer entraîné avec le Mesh Transformer JAX de Ben Wang. GPT-J fait référence à la classe du modèle, tandis que '6B' représente le nombre de paramètres entraînables. Pour l'utiliser, il suffit d'ajouter votre texte, ou de cliquer sur l'un des exemples pour le charger. Pour en savoir plus, consultez les liens ci-dessous. +article = "

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

" +# GPT-J-6B : Un modèle linguistique autorégressif à 6 milliards de paramètres +examples = [ + ["The tower is 324 metres (1,063 ft) tall,"], + # La tour mesure 324 mètres (1 063 pieds) de haut, + ["The Moon's orbit around Earth has"], + # L'orbite de la Lune autour de la Terre a + ["The smooth Borealis basin in the Northern Hemisphere covers 40%"], + # Le bassin de Borealis dans l'hémisphère nord couvre 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() +``` + +Le code ci-dessus produira l'interface ci-dessous : + + + +Le chargement d'un modèle de cette manière utilise l'[API d'Inference] (https://huggingface.co/inference-api) de *Hugging Face* au lieu de charger le modèle en mémoire. C'est idéal pour les modèles énormes comme GPT-J ou T0pp qui nécessitent beaucoup de RAM. + +### Chargement depuis Hugging Face Spaces +Pour charger n'importe quel *Space* depuis le *Hub* et le recréer localement, vous pouvez passer `spaces/` à l'`Interface`, suivi du nom du *Space*. + +Vous vous souvenez de la démo de la section 1 qui supprime le fond d'une image ? Chargeons-la à partir de *Hugging Face Spaces* : + +```py +gr.Interface.load("spaces/abidlabs/remove-bg").launch() +``` + + + +L'un des avantages du chargement de démos à partir du *Hub* ou de *Spaces* est que vous pouvez les personnaliser en remplaçant n'importe lequel des paramètres. Ici, nous ajoutons un titre et faisons en sorte qu'elle fonctionne avec une webcam à la place : + +```py +gr.Interface.load( + "spaces/abidlabs/remove-bg", inputs="webcam", title="Remove your webcam background!" +).launch() +``` + + + +Maintenant que nous avons exploré quelques façons d'intégrer *Gradio* avec le *Hub*, jetons un coup d'oeil à certaines fonctionnalités avancées de la classe `Interface`. C'est le sujet de la prochaine section ! \ No newline at end of file diff --git a/chapters/fr/chapter9/6.mdx b/chapters/fr/chapter9/6.mdx new file mode 100644 index 000000000..6a3413fb9 --- /dev/null +++ b/chapters/fr/chapter9/6.mdx @@ -0,0 +1,132 @@ +# Fonctionnalités avancées de l'interface + +Maintenant que nous pouvons construire et partager une interface de base, explorons quelques fonctionnalités plus avancées comme l'état, l'interprétation et l'authentification. + +### Utilisation de l'état pour faire persister les données + +*Gradio* supporte *l'état de session* où les données persistent à travers plusieurs soumissions dans un chargement de page. L'état de session est utile pour construire des démos où vous souhaitez faire persister les données au fur et à mesure que l'utilisateur interagit avec le modèle (par exemple des chatbots). Notez que l'état de session ne partage pas les données entre les différents utilisateurs de votre modèle. + +Pour stocker des données dans un état de session, vous devez faire trois choses : + +- Passez un *paramètre supplémentaire* dans votre fonction, qui représente l'état de l'interface. +- A la fin de la fonction, renvoyer la valeur mise à jour de l'état comme une *valeur de retour supplémentaire*. +- Ajoutez les composants "state" input et "state" output lors de la création de votre `Interface`. + +Voir l'exemple de chatbot ci-dessous : + +```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() +``` + + + +Remarquez comment l'état du composant de sortie persiste entre les soumissions. +Remarque : vous pouvez transmettre une valeur par défaut au paramètre state, qui est utilisée comme valeur initiale de l'état. + +### Utilisation de l'interprétation pour comprendre les prédictions + +La plupart des modèles d'apprentissage automatique sont des boîtes noires et la logique interne de la fonction est cachée à l'utilisateur final. Pour encourager la transparence, nous avons fait en sorte qu'il soit très facile d'ajouter l'interprétation à votre modèle en définissant simplement le mot-clé interprétation dans la classe Interface par défaut. Cela permet à vos utilisateurs de comprendre quelles parties de l'entrée sont responsables de la sortie. Jetez un coup d'œil à l'interface simple ci-dessous qui montre un classificateur d'images incluant l'interprétation : + +```py +import requests +import tensorflow as tf + +import gradio as gr + +inception_net = tf.keras.applications.MobileNetV2() # charger le modèle + +# Télécharger des étiquettes lisibles par l'homme pour 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() +``` + +Testez la fonction d'interprétation en soumettant une entrée puis en cliquant sur « Interpréter » sous le composant de sortie. + + + +En plus de la méthode d'interprétation par défaut fournie par *Gradio*, vous pouvez également spécifier `shap` pour le paramètre `interpretation` et définir le paramètre `num_shap`. Ceci utilise l'interprétation basée sur Shapley, dont vous pouvez lire plus sur [ici](https://christophm.github.io/interpretable-ml-book/shap.html). +Enfin, vous pouvez aussi passer votre propre fonction d'interprétation dans le paramètre `interpretation`. Vous trouverez un exemple dans la page de démarrage de *Gradio* [ici](https://gradio.app/getting_started/). + + +### Ajouter l'authentification + +Vous pouvez vouloir ajouter une authentification à votre interface *Gradio* afin de contrôler qui peut accéder et utiliser votre démo. + +L'authentification peut être ajoutée en fournissant une liste de tuples de nom d'utilisateur/mot de passe au paramètre `auth` de la méthode `launch()`. Pour une gestion plus complexe de l'authentification, vous pouvez passer une fonction qui prend un nom d'utilisateur et un mot de passe comme arguments, et retourne `True` pour permettre l'authentification, `False` sinon. + +Prenons la démo de classification d'images ci-dessus et ajoutons l'authentification : + +```py +import requests +import tensorflow as tf + +import gradio as gr + +inception_net = tf.keras.applications.MobileNetV2() # charger le modèle + +# Télécharger des étiquettes lisibles par l'homme pour 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(auth=("admin", "pass1234")) +``` + + + +Ceci conclut notre plongée dans la classe `Interface` de *Gradio*. Comme nous l'avons vu, cette classe permet de créer facilement des démos d'apprentissage automatique en quelques lignes de code Python. Cependant, vous voudrez parfois personnaliser votre démo en changeant la mise en page ou en enchaînant plusieurs fonctions de prédiction. Ne serait-il pas agréable de pouvoir diviser l'interface en blocs personnalisables ? Heureusement, c'est possible ! C'est le sujet de la dernière section. \ No newline at end of file diff --git a/chapters/fr/chapter9/7.mdx b/chapters/fr/chapter9/7.mdx new file mode 100644 index 000000000..edf7c91df --- /dev/null +++ b/chapters/fr/chapter9/7.mdx @@ -0,0 +1,233 @@ +# Introduction à la classe Blocks + +Dans les sections précédentes, nous avons exploré et créé des démos en utilisant la classe `Interface`. Dans cette section, nous allons présenter une **nouvelle** API de bas niveau appelée `gradio.Blocks`. + +Quelle est la différence entre `Interface` et `Blocks` ? + +- ⚡ `Interface` : une API de haut niveau qui vous permet de créer une démo complète d'apprentissage automatique simplement en fournissant une liste d'entrées et de sorties. + +- 🧱 `Blocks` : une API de bas niveau qui vous permet d'avoir un contrôle total sur les flux de données et la disposition de votre application. Vous pouvez construire des applications très complexes, en plusieurs étapes, en utilisant `Blocks`. + + +### Pourquoi Blocks 🧱 ? + +Comme nous l'avons vu dans les sections précédentes, la classe `Interface` vous permet de créer facilement des démos d'apprentissage automatique à part entière avec seulement quelques lignes de code. L'API `Interface` est extrêmement facile à utiliser, mais elle n'a pas la flexibilité qu'offre l'API `Blocks`. Par exemple, vous pourriez vouloir : + +- regrouper des démos connexes sous forme d'onglets multiples dans une application web, +- modifier la mise en page de votre démo, par exemple pour spécifier l'emplacement des entrées et des sorties, +- disposer d'interfaces multi-étapes dans lesquelles la sortie d'un modèle devient l'entrée du modèle suivant ou avoir des flux de données plus flexibles en général, +- modifier les propriétés d'un composant (par exemple, les choix dans une liste déroulante) ou sa visibilité en fonction des entrées de l'utilisateur. + +Nous allons explorer tous ces concepts ci-dessous. + +### Création d'une démo simple en utilisant Blocks + +Après avoir installé *Gradio*, exécutez le code ci-dessous sous forme de script Python, de *notebook* Jupyter ou de *notebook* 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() +``` + + + +Ce simple exemple ci-dessus introduit 4 concepts qui sous-tendent les *Blocks* : + +1. Les *Blocks* vous permettent de construire des applications web qui combinent Markdown, HTML, boutons et composants interactifs simplement en instanciant des objets en Python dans un contexte `with gradio.Blocks`. + + +🙋Si vous n'êtes pas familier avec l'instruction `with` en Python, nous vous recommandons de consulter l'excellent [tutoriel](https://realpython.com/python-with-statement/) de Real Python. Revenez ici après l'avoir lu 🤗 + + +L'ordre dans lequel vous instanciez les composants est important car chaque élément est restitué dans l'application Web dans l'ordre où il a été créé. (Les mises en page plus complexes sont abordées ci-dessous) + +2. Vous pouvez définir des fonctions Python ordinaires n'importe où dans votre code et les exécuter avec des entrées utilisateur en utilisant les `Blocks`. Dans notre exemple, nous avons une fonction simple qui inverse le texte entré mais vous pouvez écrire n'importe quelle fonction Python, du simple calcul au traitement des prédictions d'un modèle d'apprentissage automatique. + +3. Vous pouvez assigner des événements à n'importe quel composant `Blocks`. Ainsi, votre fonction sera exécutée lorsque le composant sera cliqué, modifié, etc. Lorsque vous assignez un événement, vous passez trois paramètres : +- `fn` : la fonction qui doit être appelée, +- `inputs` : la (liste) des composants d'entrée +- `outputs` : la (liste) des composants de sortie qui doivent être appelés. + Dans l'exemple ci-dessus, nous exécutons la fonction `flip_text()` lorsque la valeur de la `Textbox` nommée input `input` change. L'événement lit la valeur dans `input`, la passe comme paramètre de nom à `flip_text()`, qui renvoie alors une valeur qui est assignée à notre seconde `Textbox` nommée `output`. + Pour voir la liste des événements que chaque composant supporte, consultez la [documentation](https://www.gradio.app/docs/) de *Gradio*. + +4. *Blocks* détermine automatiquement si un composant doit être interactif (accepter les entrées de l'utilisateur) ou non, en fonction des déclencheurs d'événements que vous définissez. Dans notre exemple, la première zone de texte est interactive, puisque sa valeur est utilisée par la fonction `flip_text()`. La deuxième zone de texte n'est pas interactive, puisque sa valeur n'est jamais utilisée comme entrée. Dans certains cas, vous voudrez peut-être passer outre, ce que vous pouvez faire en passant un booléen au paramètre `interactive` du composant (par exemple, `gr.Textbox(placeholder="Flip this text", interactive=True)`). + + +### Personnaliser la mise en page de votre démo + +Comment pouvons-nous utiliser `Blocks` pour personnaliser la mise en page de notre démo ? Par défaut, `Blocks` affiche verticalement dans une colonne les composants que vous créez. Vous pouvez changer cela en créant des colonnes supplémentaires `avec gradio.Column():` ou des lignes `avec gradio.Row():` et en créant des composants dans ces contextes. + +Voici ce que vous devez garder à l'esprit : tout composant créé sous une `Column` (c'est aussi le défaut) sera disposé verticalement. Tout composant créé sous une `Row` sera disposé horizontalement, comme le [modèle flexbox dans le développement web](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox). + +Enfin, vous pouvez également créer des onglets pour votre démo en utilisant le gestionnaire de contexte `with gradio.Tabs()`. Dans ce contexte, vous pouvez créer plusieurs onglets en spécifiant des enfants `with gradio.TabItem(name_of_tab):`. Tout composant créé dans un contexte `with gradio.TabItem(name_of_tab):` apparaît dans cet onglet. + +Maintenant, ajoutons une fonction `flip_image()` à notre démo et ajoutons un nouvel onglet qui retourne les images. Vous trouverez ci-dessous un exemple avec 2 onglets et utilisant également une `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() +``` + + + + +Vous remarquerez que dans cet exemple, nous avons également créé un composant `Button` dans chaque onglet et avons assigné un événement de clic à chaque bouton qui est l'élément qui exécute réellement la fonction. + +### Exploration des événements et de l'état + +De la même manière que vous pouvez contrôler la mise en page, `Blocks` vous donne un contrôle fin sur les événements qui déclenchent les appels de fonction. Chaque composant et de nombreux layouts ont des événements spécifiques qu'ils supportent. + +Par exemple, le composant `Textbox` a 2 événements : `change()` (lorsque la valeur contenue dans la zone de texte change), et `submit()` (lorsqu'un utilisateur appuie sur la touche Entrée alors qu'il est concentré sur la zone de texte). Les composants plus complexes peuvent avoir encore plus d'événements : par exemple, le composant `Audio` a aussi des événements séparés pour quand le fichier audio est joué, effacé, mis en pause, etc. Consultez la documentation pour connaître les événements pris en charge par chaque composant. + +Vous pouvez attacher un déclencheur d'événement à aucun, un ou plusieurs de ces événements. Vous créez un déclencheur d'événement en appelant le nom de l'événement sur l'instance du composant comme une fonction. Par exemple, `textbox.change(...)` ou `btn.click(...)`. La fonction prend trois paramètres, comme indiqué ci-dessus : + +- `fn` : la fonction à exécuter +- `inputs` : une (liste de) composante(s) dont les valeurs doivent être fournies comme paramètres d'entrée à la fonction. La valeur de chaque composant est mise en correspondance avec le paramètre de fonction correspondant, dans l'ordre. Ce paramètre peut être `None` si la fonction ne prend aucun paramètre. +- `outputs` : un (liste de) composant(s) dont les valeurs doivent être mises à jour en fonction des valeurs retournées par la fonction. Chaque valeur de retour met à jour la valeur du composant correspondant, dans l'ordre. Ce paramètre peut être None si la fonction ne retourne rien. + +Vous pouvez même faire en sorte que le composant d'entrée et de sortie soit le même composant, comme nous le faisons dans cet exemple qui utilise un modèle GPT pour compléter du texte : + +```py +import gradio as gr + +api = gr.Interface.load("huggingface/EleutherAI/gpt-j-6B") + + +def complete_with_gpt(text): + # Utilise les 50 derniers caractères du texte comme contexte. + 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() +``` + + + +### Création de démos multi-étapes + +Dans certains cas, vous pouvez vouloir une _démo multi-étapes_, dans laquelle vous réutilisez la sortie d'une fonction comme entrée de la suivante. C'est très facile à faire avec les `Blocks`, car vous pouvez utiliser un composant pour l'entrée d'un déclencheur d'événement mais la sortie d'un autre. Regardez le composant texte dans l'exemple ci-dessous, sa valeur est le résultat d'un modèle de conversion de la parole en texte, mais il est également transmis à un modèle d'analyse des sentiments : + +```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() +``` + + + +### Mise à jour des propriétés des composants + +Jusqu'à présent, nous avons vu comment créer des événements pour mettre à jour la valeur d'un autre composant. Mais que se passe-t-il si vous voulez modifier d'autres propriétés d'un composant, comme la visibilité d'une zone de texte ou les choix dans un groupe de boutons radio ? Vous pouvez le faire en renvoyant la méthode `update()` d'une classe de composant au lieu d'une valeur de retour normale de votre fonction. + +L'exemple le plus facile à illustrer est le suivant : + +```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() +``` + + + +Nous venons d'explorer tous les concepts de base des `Blocks` ! Tout comme avec `Interface`, vous pouvez créer des démos sympas qui peuvent être partagées en utilisant `share=True` dans la méthode `launch()` ou déployées sur [*Spaces*](https://huggingface.co/spaces). \ No newline at end of file diff --git a/chapters/fr/chapter9/8.mdx b/chapters/fr/chapter9/8.mdx new file mode 100644 index 000000000..1225f0eb3 --- /dev/null +++ b/chapters/fr/chapter9/8.mdx @@ -0,0 +1,234 @@ + + +# Quiz de fin de chapitre + +Testons ce que vous avez appris dans ce chapitre ! + +### 1. Que pouvez-vous faire avec Gradio ? + +share=True dans la méthode de lancement, vous pouvez générer un lien de partage à envoyer à tout le monde.", + correct: true + }, + { + text: "Déboguez votre modèle.", + explain: "L'un des avantages d'une démo Gradio est de pouvoir tester votre modèle avec des données réelles que vous pouvez modifier et observer les prédictions du modèle changer en temps réel, ce qui vous aide à déboguer votre modèle.", + correct: true + }, + { + text: "Entraîner votre modèle.", + explain: "Gradio est conçu pour être utilisé pour l'inférence, APRÈS que votre modèle a été entraîné.", + } + ]} +/> + +### 2. Gradio fonctionne UNIQUEMENT avec les modèles en PyTorch + +Gradio fonctionne avec les modèles Pytorch mais aussi pour tout type de modèle d'apprentissage automatique !" + }, + { + text: "False", + explain: "Gradio est indifférent au modèle ce qui signifie que vous pouvez créer une démo pour tout type de modèle d'apprentissage automatique.", + correct: true + } + ]} +/> + +### 3. D'où pouvez-vous lancer une démo Gradio ? + +Gradio fonctionne parfaitement avec votre IDE préféré.", + correct: true + }, + { + text: "De notebooks Google Colab", + explain: "Vous pouvez créer et lancer une démo dans votre notebook Google Colab.", + correct: true + }, + { + text: "De notebooks Jupyter", + explain: "Vous pouvez créer et lancer une démo dans votre notebook Jupyter.", + correct: true + } + ]} +/> + +### 4. Gradio est conçu principalement pour les modèles de NLP + +Gradio fonctionne avec pratiquement tous les types de données, pas seulement avec le NLP." + }, + { + text: "False", + explain: "Gradio fournit aux développeurs une bibliothèque de composants préconstruits pour pratiquement tous les types de données.", + correct: true + } + ]} +/> + +### 5. Parmi les fonctionnalités suivantes, lesquelles sont prises en charge par Gradio ? + +Gradio. Tout ce que vous devez faire est de passer une liste d'entrées et de sorties à leurs paramètres correspondants.", + correct: true + }, + { + text: "État pour la persistance des données.", + explain: "Gradio est capable d'ajouter un état à votre interface.", + correct: true + }, + { + text: "Authentification par nom d'utilisateur et mot de passe.", + explain: "Passez une liste de tuples de nom d'utilisateur/mot de passe à la méthode de lancement pour ajouter l'authentification.", + correct: true + }, + { + text: "Analyse automatique de l'utilisation de votre démo Gradio.", + explain: "Gradio ne fournit pas aux développeurs des analyses sur les personnes qui utilisent leurs démos." + }, + { + text: "Chargement d'un modèle à partir du Hub ou de Space.", + explain: "Charger n'importe quel modèle de Hugging Face en utilisant la méthode gr.Interface.load().", + correct: true + } + ]} +/> + +### 6. Lesquelles des méthodes suivantes sont valides pour charger un modèle à partir du Hub ou de Space ? + +gr.Interface.load('huggingface/{user}/{model_name}')", + explain: "Il s'agit d'une méthode valide de chargement d'un modèle à partir du Hub.", + correct: true + }, + { + text: "gr.Interface.load('model/{user}/{model_name}')", + explain: "Il s'agit d'une méthode valide de chargement d'un modèle à partir du Hub.", + correct: true + }, + { + text: "gr.Interface.load('demos/{user}/{model_name}')", + explain: "Vous ne pouvez pas charger un modèle en utilisant le préfixe demos." + }, + { + text: "gr.Interface.load('spaces/{user}/{model_name}')", + explain: "Il s'agit d'une méthode valide de chargement d'un modèle à partir de Space.", + correct: true + } + ]} +/> + +### 7. Sélectionnez toutes les étapes nécessaires pour ajouter un état à votre interface Gradio + +Gradio fournit un composant d'entrée et de sortie d'état pour persister les données.", + correct: true + } + ]} +/> + +### 8. Lesquels des éléments suivants sont des composants inclus dans la bibliothèque Gradio ? + +Textbox.", + explain: "Oui, vous pouvez créer des zones de texte avec le composant Textbox.", + correct: true + }, + { + text: "Graph.", + explain: "Il n'y a actuellement aucun composant Graph.", + }, + { + text: "Image.", + explain: "Oui, vous pouvez créer un widget de téléchargement d'images avec le composant Image.", + correct: true + }, + { + text: "Audio.", + explain: "Oui, vous pouvez créer un widget de téléchargement audio avec le composant Audio.", + correct: true + }, + ]} +/> + +### 9. Qu'est-ce que les `Blocks` vous permet de faire ? + +with gradio.Tabs(): pour ajouter des onglets pour plusieurs démos.", + correct: true + }, + { + text: "Attribuer des déclencheurs d'événements tels que clicked/changed/etc aux composants Blocks.", + explain: "Lorsque vous assignez un événement, vous passez trois paramètres : fn qui est la fonction qui doit être appelée, inputs qui est la (liste) des composants d'entrée, et outputs qui est la (liste) des composants de sortie qui doivent être appelés.", + correct: true + }, + { + text: "Déterminer automatiquement quel composant Blocks doit être interactif ou statique.", + explain: "En fonction des déclencheurs d'événements que vous définissez, Blocks détermine automatiquement si un composant doit accepter ou non les entrées de l'utilisateur..", + correct: true + }, + { + text: "Créer des démos en plusieurs étapes, c'est-à-dire vous permettre de réutiliser la sortie d'un composant comme entrée pour le suivant.", + explain: "Vous pouvez utiliser un composant pour l'entrée d'un déclencheur d'événement mais la sortie d'un autre.", + correct: true + }, + ]} +/> + +### 10. Vous pouvez partager un lien public vers une démo Blocks et accueillir une démo Blocks sur Space + +Interface, toutes les capacités de partage et d'hébergement sont les mêmes pour les démos basées sur Blocks !", + correct: true + }, + { + text: "False", + explain: "Tout comme Interface, toutes les capacités de partage et d'hébergement sont les mêmes pour les démos basées sur Blocks !", + correct: false + } + ]} +/> \ No newline at end of file diff --git a/chapters/fr/event/1.mdx b/chapters/fr/event/1.mdx index ab52d0489..e6d766bd7 100644 --- a/chapters/fr/event/1.mdx +++ b/chapters/fr/event/1.mdx @@ -1,170 +1,170 @@ -# Événement pour le lancement de la partie 2 - -Pour la sortie de la deuxième partie du cours, nous avons organisé un événement en direct consistant en deux jours de conférences suivies d’un *sprint* de *finetuning*. Si vous l'avez manqué, vous pouvez rattraper les présentations qui sont toutes listées ci-dessous ! - -## Jour 1 : Une vue d'ensemble des *transformers* et comment les entraîner - - -**Thomas Wolf :** *L'apprentissage par transfert et la naissance de la bibliothèque 🤗 Transformers* - -
- -
- -

-A visual summary of Thom's talk -

- -Thomas Wolf est cofondateur et directeur scientifique d’Hugging Face. Les outils créés par Thomas Wolf et l'équipe d’Hugging Face sont utilisés par plus de 5 000 organismes de recherche, dont Facebook Artificial Intelligence Research, Google Research, DeepMind, Amazon Research, Apple, l'Allen Institute for Artificial Intelligence ainsi que la plupart des départements universitaires. Thomas Wolf est l'initiateur et le président principal de la plus grande collaboration de recherche qui ait jamais existé dans le domaine de l'intelligence artificielle : [« BigScience »](https://bigscience.huggingface.co), ainsi que d'un ensemble de [bibliothèques et outils](https://github.com/huggingface/) largement utilisés. Thomas Wolf est également un éducateur prolifique, un *leader* d'opinion dans le domaine de l'intelligence artificielle et du traitement du langage naturel, et un orateur régulièrement invité à des conférences dans le monde entier [https://thomwolf.io](https://thomwolf.io). - -**Jay Alammar :** *Une introduction visuelle douce aux transformers* - -
- -
- -

-A visual summary of Jay's talk -

- -Grâce à son blog d’apprentissage automatique très populaire, Jay a aidé des millions de chercheurs et d'ingénieurs à comprendre visuellement les outils et les concepts de l'apprentissage automatique, des plus élémentaires (qui se retrouvent dans les docs NumPy et Pandas) aux plus pointus (Transformer, BERT, GPT-3). - -**Margaret Mitchell :** *Les valeurs dans le développement de l’apprentissage automatique* - -
- -
- -

-A visual summary of Margaret's talk -

- -Margaret Mitchell est une chercheuse travaillant sur l'IA éthique. Elle se concentre actuellement sur les tenants et aboutissants du développement de l'IA éthique dans le domaine de la technologie. Elle a publié plus de cinquante articles sur la génération de langage naturel, les technologies d'assistance, la vision par ordinateur et l'IA éthique. Elle détient plusieurs brevets dans le domaine de la génération de conversations et celui de la classification des sentiments. Elle a précédemment travaillé chez Google AI en tant que chercheuse où elle a fondé et codirigé le groupe d'IA éthique de Google. Ce groupe est axé sur la recherche fondamentale en matière d'IA éthique et l'opérationnalisation de d'IA éthique en interne à Google. Avant de rejoindre Google, elle a été chercheuse chez Microsoft Research où elle s'est concentrée sur la génération de la vision par ordinateur vers le langage et a été post-doc à Johns Hopkins où elle s'est concentrée sur la modélisation bayésienne et l'extraction d'informations. Elle est titulaire d'un doctorat en informatique de l'université d'Aberdeen et d'une maîtrise en linguistique informatique de l'université de Washington. Tout en obtenant ses diplômes, elle a également travaillé de 2005 à 2012 sur l'apprentissage automatique, les troubles neurologiques et les technologies d'assistance à l'Oregon Health and Science University. Elle a dirigé un certain nombre d'ateliers et d'initiatives au croisement de la diversité, de l'inclusion, de l'informatique et de l'éthique. Ses travaux ont été récompensés par le secrétaire à la défense Ash Carter et la Fondation américaine pour les aveugles, et ont été implémenté par plusieurs entreprises technologiques. - -**Matthew Watson et Chen Qian :** *Les flux de travail en NLP avec Keras* - -
- -
- -

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

- -Matthew Watson est ingénieur en apprentissage automatique au sein de l'équipe Keras et se concentre sur les API de modélisation de haut niveau. Il a étudié l'infographie pendant ses études et a obtenu un master à l'université de Stanford. Il s'est orienté vers l'informatique après avoir étudié l'anglais. Il est passionné par le travail interdisciplinaire et par la volonté de rendre le traitement automatique des langues accessible à un public plus large. - -Chen Qian est un ingénieur logiciel de l'équipe Keras spécialisé dans les API de modélisation de haut niveau. Chen est titulaire d'un master en génie électrique de l'université de Stanford et s'intéresse particulièrement à la simplification de l'implémentation du code des tâches d’apprentissage automatique et le passage à grande échelle de ces codes. - - -**Mark Saroufim :** *Comment entraîner un modèle avec PyTorch* - -
- -
- -

-A visual summary of Mark's talk -

- -Mark Saroufim est ingénieur partenaire chez PyTorch et travaille sur les outils de production OSS, notamment TorchServe et PyTorch Enterprise. Dans ses vies antérieures, Mark a été un scientifique appliqué et un chef de produit chez Graphcore, [yuri.ai](http://yuri.ai/), Microsoft et au JPL de la NASA. Sa principale passion est de rendre la programmation plus amusante. - -**Jakob Uszkoreit :** *Ce n'est pas cassé alors ne réparez pas cassez tout* - -
- -
- -

-A visual summary of Jakob's talk -

- -Jakob Uszkoreit est le cofondateur d'Inceptive. Inceptive conçoit des molécules d'ARN pour les vaccins et les thérapies en utilisant l'apprentissage profond à grande échelle. Le tout en boucle étroite avec des expériences à haut débit, dans le but de rendre les médicaments à base d'ARN plus accessibles, plus efficaces et plus largement applicables. Auparavant, Jakob a travaillé chez Google pendant plus de dix ans, dirigeant des équipes de recherche et de développement au sein de Google Brain, Research et Search, travaillant sur les fondamentaux de l'apprentissage profond, la vision par ordinateur, la compréhension du langage et la traduction automatique. - -## Jour 2 : Les outils à utiliser - - -**Lewis Tunstall :** *Un entraînement simple avec la fonction *Trainer* de la bibliotèque 🤗 Transformers* - -
- -
- -Lewis est un ingénieur en apprentissage machine chez Hugging Face qui se concentre sur le développement d'outils open-source et les rend accessibles à la communauté. Il est également co-auteur d'un livre à paraître chez O'Reilly sur les *transformers*. Vous pouvez le suivre sur Twitter (@_lewtun) pour des conseils et astuces en traitement du langage naturel ! - -**Matthew Carrigan :** *Nouvelles fonctionnalités en TensorFlow pour 🤗 Transformers et 🤗 Datasets* - -
- -
- -Matt est responsable de la maintenance des modèles en TensorFlow chez *Transformers*. Il finira par mener un coup d'État contre la faction PyTorch en place Celui sera probablement coordonné via son compte Twitter @carrigmat. - -**Lysandre Debut :** *Le Hub d’Hugging Face, un moyen de collaborer et de partager des projets d'apprentissage automatique* - -
- -
- -

-A visual summary of Lysandre's talk -

- -Lysandre est ingénieur en apprentissage machine chez Hugging Face où il participe à de nombreux projets open source. Son objectif est de rendre l'apprentissage automatique accessible à tous en développant des outils puissants avec une API très simple. - -**Lucile Saulnier :** *Avoir son propre tokenizer avec 🤗 Transformers & 🤗 Tokenizers* - -
- -
- -Lucile est ingénieure en apprentissage automatique chez Hugging Face où elle développe et soutient l'utilisation d'outils open source. Elle est également activement impliquée dans de nombreux projets de recherche dans le domaine du traitement du langage naturel tels que l’entraînement collaboratif et BigScience. - -**Sylvain Gugger :** *Optimisez votre boucle d'entraînement PyTorch avec -🤗 Accelerate* - -
- -
- -Sylvain est ingénieur de recherche chez Hugging Face. Il est l'un des principaux mainteneurs de 🤗 Transformers et le développeur derrière 🤗 Accelerate. Il aime rendre l'apprentissage des modèles plus accessible. - -**Merve Noyan :** *Présentez vos démonstrations de modèles avec -🤗 Spaces* - -
- -
- -Merve est *developer advocate* chez Hugging Face travaillant au développement d'outils et à la création de contenu autour d'eux afin de démocratiser l'apprentissage automatique pour tous. - -**Abubakar Abid :** *Créer rapidement des applications d'apprentissage automatique* - -
- -
- -

-A visual summary of Abubakar's talk -

- -Abubakar Abid est le PDG de [Gradio](www.gradio.app). Il a obtenu sa licence en génie électrique et en informatique au MIT en 2015, et son doctorat en apprentissage automatique appliqué à Stanford en 2021. En tant que PDG de Gradio, Abubakar s'efforce de faciliter la démonstration, le débogage et le déploiement des modèles d'apprentissage automatique. - -**Mathieu Desvé :** *AWS ML Vision : Rendre l'apprentissage automatique accessible à tous les clients* - -
- -
- -

-A visual summary of Mathieu's talk -

- -Passionné de technologie, il est un créateur pendant son temps libre. Il aime les défis et résoudre les problèmes des clients et des utilisateurs ainsi que travailler avec des personnes talentueuses pour apprendre chaque jour. Depuis 2004, il a occupé plusieurs postes, passant du frontend au backend, de l'infrastructure aux opérations et à la gestion. Il essaie de résoudre les problèmes techniques et de gestion courants de manière agile. - -**Philipp Schmid :** *Entraînement dirigé avec Amazon SageMaker et 🤗 Transformers* - -
- -
- -Philipp Schmid est ingénieur en apprentissage machine et *Tech Lead* chez Hugging Face où il dirige la collaboration avec l'équipe Amazon SageMaker. Il est passionné par la démocratisation et la mise en production de modèles de traitement du langage naturel de pointe et par l'amélioration de la facilité d'utilisation de l'apprentissage profond. +# Événement pour le lancement de la partie 2 + +Pour la sortie de la deuxième partie du cours, nous avons organisé un événement en direct consistant en deux jours de conférences suivies d’un *sprint* de *finetuning*. Si vous l'avez manqué, vous pouvez rattraper les présentations qui sont toutes listées ci-dessous ! + +## Jour 1 : Une vue d'ensemble des transformers et comment les entraîner + + +**Thomas Wolf :** *L'apprentissage par transfert et la naissance de la bibliothèque 🤗 Transformers* + +
+ +
+ +

+A visual summary of Thom's talk +

+ +Thomas Wolf est cofondateur et directeur scientifique d’Hugging Face. Les outils créés par Thomas Wolf et l'équipe d’Hugging Face sont utilisés par plus de 5 000 organismes de recherche, dont Facebook Artificial Intelligence Research, Google Research, DeepMind, Amazon Research, Apple, l'Allen Institute for Artificial Intelligence ainsi que la plupart des départements universitaires. Thomas Wolf est l'initiateur et le président principal de la plus grande collaboration de recherche qui ait jamais existé dans le domaine de l'intelligence artificielle : [« BigScience »](https://bigscience.huggingface.co), ainsi que d'un ensemble de [bibliothèques et outils](https://github.com/huggingface/) largement utilisés. Thomas Wolf est également un éducateur prolifique, un *leader* d'opinion dans le domaine de l'intelligence artificielle et du traitement du langage naturel, et un orateur régulièrement invité à des conférences dans le monde entier [https://thomwolf.io](https://thomwolf.io). + +**Jay Alammar :** *Une introduction visuelle douce aux transformers* + +
+ +
+ +

+A visual summary of Jay's talk +

+ +Grâce à son blog d’apprentissage automatique très populaire, Jay a aidé des millions de chercheurs et d'ingénieurs à comprendre visuellement les outils et les concepts de l'apprentissage automatique, des plus élémentaires (qui se retrouvent dans les docs NumPy et Pandas) aux plus pointus (Transformer, BERT, GPT-3). + +**Margaret Mitchell :** *Les valeurs dans le développement de l’apprentissage automatique* + +
+ +
+ +

+A visual summary of Margaret's talk +

+ +Margaret Mitchell est une chercheuse travaillant sur l'IA éthique. Elle se concentre actuellement sur les tenants et aboutissants du développement de l'IA éthique dans le domaine de la technologie. Elle a publié plus de cinquante articles sur la génération de langage naturel, les technologies d'assistance, la vision par ordinateur et l'IA éthique. Elle détient plusieurs brevets dans le domaine de la génération de conversations et celui de la classification des sentiments. Elle a précédemment travaillé chez Google AI en tant que chercheuse où elle a fondé et codirigé le groupe d'IA éthique de Google. Ce groupe est axé sur la recherche fondamentale en matière d'IA éthique et l'opérationnalisation de d'IA éthique en interne à Google. Avant de rejoindre Google, elle a été chercheuse chez Microsoft Research où elle s'est concentrée sur la génération de la vision par ordinateur vers le langage et a été post-doc à Johns Hopkins où elle s'est concentrée sur la modélisation bayésienne et l'extraction d'informations. Elle est titulaire d'un doctorat en informatique de l'université d'Aberdeen et d'une maîtrise en linguistique informatique de l'université de Washington. Tout en obtenant ses diplômes, elle a également travaillé de 2005 à 2012 sur l'apprentissage automatique, les troubles neurologiques et les technologies d'assistance à l'Oregon Health and Science University. Elle a dirigé un certain nombre d'ateliers et d'initiatives au croisement de la diversité, de l'inclusion, de l'informatique et de l'éthique. Ses travaux ont été récompensés par le secrétaire à la défense Ash Carter et la Fondation américaine pour les aveugles, et ont été implémenté par plusieurs entreprises technologiques. + +**Matthew Watson et Chen Qian :** *Les flux de travail en NLP avec Keras* + +
+ +
+ +

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

+ +Matthew Watson est ingénieur en apprentissage automatique au sein de l'équipe Keras et se concentre sur les API de modélisation de haut niveau. Il a étudié l'infographie pendant ses études et a obtenu un master à l'université de Stanford. Il s'est orienté vers l'informatique après avoir étudié l'anglais. Il est passionné par le travail interdisciplinaire et par la volonté de rendre le traitement automatique des langues accessible à un public plus large. + +Chen Qian est un ingénieur logiciel de l'équipe Keras spécialisé dans les API de modélisation de haut niveau. Chen est titulaire d'un master en génie électrique de l'université de Stanford et s'intéresse particulièrement à la simplification de l'implémentation du code des tâches d’apprentissage automatique et le passage à grande échelle de ces codes. + + +**Mark Saroufim :** *Comment entraîner un modèle avec PyTorch* + +
+ +
+ +

+A visual summary of Mark's talk +

+ +Mark Saroufim est ingénieur partenaire chez PyTorch et travaille sur les outils de production OSS, notamment TorchServe et PyTorch Enterprise. Dans ses vies antérieures, Mark a été un scientifique appliqué et un chef de produit chez Graphcore, [yuri.ai](http://yuri.ai/), Microsoft et au JPL de la NASA. Sa principale passion est de rendre la programmation plus amusante. + +**Jakob Uszkoreit :** *Ce n'est pas cassé alors ne réparez pas cassez tout* + +
+ +
+ +

+A visual summary of Jakob's talk +

+ +Jakob Uszkoreit est le cofondateur d'Inceptive. Inceptive conçoit des molécules d'ARN pour les vaccins et les thérapies en utilisant l'apprentissage profond à grande échelle. Le tout en boucle étroite avec des expériences à haut débit, dans le but de rendre les médicaments à base d'ARN plus accessibles, plus efficaces et plus largement applicables. Auparavant, Jakob a travaillé chez Google pendant plus de dix ans, dirigeant des équipes de recherche et de développement au sein de Google Brain, Research et Search, travaillant sur les fondamentaux de l'apprentissage profond, la vision par ordinateur, la compréhension du langage et la traduction automatique. + +## Jour 2 : Les outils à utiliser + + +**Lewis Tunstall :** *Un entraînement simple avec la fonction *Trainer* de la bibliotèque 🤗 Transformers* + +
+ +
+ +Lewis est un ingénieur en apprentissage machine chez Hugging Face qui se concentre sur le développement d'outils open-source et les rend accessibles à la communauté. Il est également co-auteur du livre [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/) paru chez O'Reilly. Vous pouvez le suivre sur Twitter (@_lewtun) pour des conseils et astuces en traitement du langage naturel ! + +**Matthew Carrigan :** *Nouvelles fonctionnalités en TensorFlow pour 🤗 Transformers et 🤗 Datasets* + +
+ +
+ +Matt est responsable de la maintenance des modèles en TensorFlow chez *Transformers*. Il finira par mener un coup d'État contre la faction PyTorch en place Celui sera probablement coordonné via son compte Twitter @carrigmat. + +**Lysandre Debut :** *Le Hub d’Hugging Face, un moyen de collaborer et de partager des projets d'apprentissage automatique* + +
+ +
+ +

+A visual summary of Lysandre's talk +

+ +Lysandre est ingénieur en apprentissage machine chez Hugging Face où il participe à de nombreux projets open source. Son objectif est de rendre l'apprentissage automatique accessible à tous en développant des outils puissants avec une API très simple. + +**Lucile Saulnier :** *Avoir son propre tokenizer avec 🤗 Transformers & 🤗 Tokenizers* + +
+ +
+ +Lucile est ingénieure en apprentissage automatique chez Hugging Face où elle développe et soutient l'utilisation d'outils open source. Elle est également activement impliquée dans de nombreux projets de recherche dans le domaine du traitement du langage naturel tels que l’entraînement collaboratif et BigScience. + +**Sylvain Gugger :** *Optimisez votre boucle d'entraînement PyTorch avec +🤗 Accelerate* + +
+ +
+ +Sylvain est ingénieur de recherche chez Hugging Face. Il est l'un des principaux mainteneurs de 🤗 Transformers et le développeur derrière 🤗 Accelerate. Il aime rendre l'apprentissage des modèles plus accessible. + +**Merve Noyan :** *Présentez vos démonstrations de modèles avec +🤗 Spaces* + +
+ +
+ +Merve est *developer advocate* chez Hugging Face travaillant au développement d'outils et à la création de contenu autour d'eux afin de démocratiser l'apprentissage automatique pour tous. + +**Abubakar Abid :** *Créer rapidement des applications d'apprentissage automatique* + +
+ +
+ +

+A visual summary of Abubakar's talk +

+ +Abubakar Abid est le PDG de [Gradio](www.gradio.app). Il a obtenu sa licence en génie électrique et en informatique au MIT en 2015, et son doctorat en apprentissage automatique appliqué à Stanford en 2021. En tant que PDG de Gradio, Abubakar s'efforce de faciliter la démonstration, le débogage et le déploiement des modèles d'apprentissage automatique. + +**Mathieu Desvé :** *AWS ML Vision : Rendre l'apprentissage automatique accessible à tous les clients* + +
+ +
+ +

+A visual summary of Mathieu's talk +

+ +Passionné de technologie, il est un créateur pendant son temps libre. Il aime les défis et résoudre les problèmes des clients et des utilisateurs ainsi que travailler avec des personnes talentueuses pour apprendre chaque jour. Depuis 2004, il a occupé plusieurs postes, passant du frontend au backend, de l'infrastructure aux opérations et à la gestion. Il essaie de résoudre les problèmes techniques et de gestion courants de manière agile. + +**Philipp Schmid :** *Entraînement dirigé avec Amazon SageMaker et 🤗 Transformers* + +
+ +
+ +Philipp Schmid est ingénieur en apprentissage machine et *Tech Lead* chez Hugging Face où il dirige la collaboration avec l'équipe Amazon SageMaker. Il est passionné par la démocratisation et la mise en production de modèles de traitement du langage naturel de pointe et par l'amélioration de la facilité d'utilisation de l'apprentissage profond. diff --git a/chapters/th/_toctree.yml b/chapters/th/_toctree.yml index cefc160de..e6452028a 100644 --- a/chapters/th/_toctree.yml +++ b/chapters/th/_toctree.yml @@ -68,3 +68,9 @@ title: คำถามท้ายบท quiz: 4 +- title: 6. ตัวตัดคำจาก 🤗 Tokenizers library + sections: + - local: chapter6/1 + title: บทนำ + - local: chapter6/2 + title: การเทรน tokenizer จาก tokenizer ที่มีอยู่แล้ว diff --git a/chapters/th/chapter6/1.mdx b/chapters/th/chapter6/1.mdx new file mode 100644 index 000000000..d4521932a --- /dev/null +++ b/chapters/th/chapter6/1.mdx @@ -0,0 +1,21 @@ +# บทนำ + +ใน[บทที่ 3](/course/chapter3) คุณได้เรียนเกี่ยวกับการ fine-tune โมเดลเพื่อนำไปใช้ในงานที่คุณต้องการ ตอนนั้นเราใช้ตัวตัดคำ(tokenizer)แบบเดียวกับตัวที่มากับโมเดล แต่หากคุณอยากจะเทรนโมเดลตั้งแต่เริ่มต้นเลย คุณควรจะเลือกใช้ตัวตัดคำแบบไหนดี +ในกรณีนี้ถ้าคุณใช้ตัวตัดคำที่เทรนจากคลังข้อมูล(corpus)ที่ไม่ใช่ภาษาเดียวกับโมเดลหรือคลังข้อมูลที่มาจากโดเมนอื่น(แปลว่าเนื้อหาของข้อมูลที่ใช้เทรนตัวตัดคำและใช้เทรนโมเดลมีความแตกต่างกันมาก)ก็จะไม่เหมาะสมนัก +ตัวอย่างเช่น ตัวตัดคำที่เทรนมาสำหรับตัดคำภาษาอังกฤษ เมื่อนำมาใช้เพื่อตัดคำภาษาญี่ปุ่นก็จะได้ผลลัพธ์ที่ไม่ดี เพราะว่าทั้งสองภาษามีการใช้ช่องว่าง(space)และเครื่องหมายวรรคตอน(punctuation)ที่ต่างกันมาก + + +ในบทนี้คุณจะได้เรียนเกี่ยวกับการเทรนตัวตัดคำจากคลังข้อความ(corpus of texts) เพื่อให้ได้ตัวตัดคำที่เหมาะสมกับ language model ที่คุณต้องการจะเทรน +เราจะใช้ library ที่ชื่อว่า [🤗 Tokenizers](https://github.com/huggingface/tokenizers) ซึ่งมีตัวตัดคำแบบ "เร็ว" ให้ผู้ใช้เลือกได้ ใน [🤗 Transformers](https://github.com/huggingface/transformers) library +เราจะมาดู features ต่างๆของ library นี้กันและมาเรียนรู้ว่าตัวตัดคำแบบเร็วและแบบช้านั้นต่างกันอย่างไร + + +หัวข้อที่เราจะเรียนกันในบทนี้: + +* การสร้างตัวตัดคำขึ้นมาใหม่ให้คล้ายกับตัวที่ใช้ใน checkpoint โดนใช้ชุดข้อมูลใหม่ในการเทรน +* feature พิเศษของตัวตัดคำแบบเร็ว +* ความแตกต่างระหว่างอัลกอริทึม 3 แบบที่ใช้ในการสร้างตัวตัดคำประเภท subword ที่ใช้ใน NLP ทุกวันนี้ +* การสร้างและเทรนตัวตัดคำตั้งแต่เริ่มต้นด้วย 🤗 Tokenizers library + +เทคนิคต่างๆที่คุณจะได้เรียนในบทนี้จะเป็นเตรียมให้คุณพร้อมสำหรับ[บทที่ 7](/course/chapter7/6) ซึ่งคุณจะได้เรียนเกี่ยวกับการสร้าง language model ด้วย Python +เรามาเริ่มกันที่ความหมายของการ "เทรน" ตัวตัดคำ \ No newline at end of file diff --git a/chapters/th/chapter6/2.mdx b/chapters/th/chapter6/2.mdx new file mode 100644 index 000000000..ff21da829 --- /dev/null +++ b/chapters/th/chapter6/2.mdx @@ -0,0 +1,313 @@ +# การเทรน tokenizer จาก tokenizer ที่มีอยู่แล้ว + + + +สมมติว่าคุณต้องการจะใช้ language model ในการทำงานใดงานหนึ่ง แต่ตอนนี้ไม่มีโมเดลในภาษาที่คุณต้องการหรือโมเดลที่มีอยู่นั้นถูกเทรนจากคลังข้อมูลที่แตกต่างจากข้อมูลที่คุณต้องการจะใช้งานมาก +ในกรณีนี้คุณอาจจะจำเป็นต้องเทรน langauge model ขึ้นมาใหม่ เพื่อให้ได้โมเดลที่เหมาะกับการใช้งานของคุณ และในการเทรนนั้นคุณก็ต้องมี tokenizer ที่เหมาะกับข้อมูลของคุณ +แล้ววิธีเทรน tokenizer ขึ้นมาใหม่นั้นทำได้อย่างไร? + +ใน[บทที่ 2](/course/chapter2) คุณจะเห็นว่าโมเดล Transformer ส่วนมากใช้เทคนิคการตัดคำที่ใช้หน่วยย่อยของคำ (_subword tokenization algorithm_ ) +ในการตัดคำแบบนี้ ตัวตัดคำจะต้องหาหน่วยย่อยของคำ(subword)ที่เป็นประโยชน์และพบบ่อยในคลังข้อมูล ในกระบวนการหาคำย่อยนี้ tokenizer จะต้องอ่านทุกๆข้อความในคลังข้อมูล ขั้นตอนนี้เราเรียกว่าการ*เทรน* + +กฎที่ใช้ในการเทรนนั้นขึ้นกับประเภทของ tokenizer ที่เราเลือกใช้ เราจะพูดถึงกับอัลกอริทึม 3 แบบที่ใช้ในการเทรน tokenizer กันในตอนท้ายของบทนี้ + + + + + +⚠️ การเทรน tokenize จะไม่เหมือนการกับเทรนโมเดลทั่วไป ในการเทรนโมเดลทั่วไปเราใช้ stochastic gradient descent เพื่อลดค่า loss ในทุก batch กระบวนการนี้มีความ random อยู่ในตัวของมัน (ซึ่งแปลว่า ถ้าคุณเทรนโมเดลสองครั้งแล้วอยากได้ผลลัพธ์ที่เหมือนกัน คุณจะต้องตั้งค่า seed ของการ random ให้เหมือนกันในทุกครั้งที่คุณเทรน) +ส่วนการเทรน tokenize เป็นกระบวนการทางสถิติที่พยายามจะค้นหาคำย่อยที่เหมาะสมที่สุดจากคลังข้อมูลหนึ่ง วิธีในการเลือกค้นหาคำย่อยนี้ก็มีหลากหลายวิธี +ผลลัพธ์ของการเทรนประเภทนี้จะมีความคงที่ (deterministic) ซึ่งแปลว่าคุณจะได้ผลลัพธ์เดิมทุกครั้งหลังจากการเทรน ถ้าหากคุณใช้อัลกอริทึมและข้อมูลเดิมทุกครั้ง + + +## การสร้างคลังข้อมูล (Assembling a corpus) + +🤗 Transformers มี API ที่ใช้งานง่าย ที่สามารถใช้เทรน tokenizer ให้มีลักษณะเหมือน tokenizer ตัวอื่นที่เรามีอยู่แล้ว โดยการใช้ `AutoTokenizer.train_new_from_iterator()` + +เพื่อให้คุณเห็นภาพชัดเจน เราจะสมมติว่า คุณต้องการเทรนโมเดล GPT-2 ตั้งแต่เริ่มแรก แต่เป็นภาษาอื่นที่ไม่ใช่ภาษาอังกฤษ +สิ่งที่แรกที่คุณต้องทำคือรวบรวมข้อความในภาษานั้นเพื่อสร้างชุดข้อมูลสำหรับการเทรน +ในตัวอย่างต่อไปนี้ เพื่อให้ผู้อ่านทุกคนเข้าใจได้ง่าย เราจะไม่ใช้ภาษารัสเซียหรือภาษาจีนเป็นตัวอย่าง แต่จะใช้ภาษาหนึ่งที่เป็นภาษาอังกฤษแบบพิเศษ นั่นคือ Python code + + +เราจะใช้ [🤗 Datasets](https://github.com/huggingface/datasets) library เพื่อช่วยสร้างคลังข้อมูล +และใช้ฟังก์ชัน `load_dataset()` เพื่อดาวโหลดและ cache ชุดข้อมูล [CodeSearchNet](https://huggingface.co/datasets/code_search_net) +ชุดข้อมูลชุดนี้ถูกสร้างขึ้นมาเพื่อใช้ในการแข่งขัน [CodeSearchNet challenge](https://wandb.ai/github/CodeSearchNet/benchmark) +และประกอบไปด้วยโค้ดของฟังก์ชันจาก open source libraries จาก GitHub ในหลายๆภาษา เราจะดาวโหลดเฉพาะโค้ดที่เป็น Python + + +```py +from datasets import load_dataset + +# This can take a few minutes to load, so grab a coffee or tea while you wait! +raw_datasets = load_dataset("code_search_net", "python") +``` + +คุณสามารถเช็คดูข้อมูลส่วนที่ใช้เทรนได้โดยรันโค้ดข้างล่างนี้ เพื่อจะได้ดูว่าในชุดข้อมูลมีคอลัมน์อะไรบ้าง + + +```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 +}) +``` + +เราจะเห็นว่าในชุดข้อมูลนี้ ส่วนที่เป็น docstrings จะถูกแยก ออกจากส่วนที่เป็น code และนอกจากนั้น แต่ละส่วนยังมีอีกคอลัมน์เพื่อเก็บข้อความที่ถูกตัดออกเป็น token แล้วอีกด้วย +เราจะใช้แค่คอลัมน์ `whole_func_string` ในการเทรน tokenizer ของเรา + +คุณสามารถสุ่มตัวอย่างของข้อมูลในแต่ละคอลัมน์มาดูได้ดังนี้ + +```py +print(raw_datasets["train"][123456]["whole_func_string"]) +``` + +คำสั่งข้างบนจะ print ผลลัพธ์ข้างล่างนี้ : + +```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) +``` + +หลังจากนั้น เราก็จะต้องแปลงชุดข้อมูลนี้เป็น _iterator_ ของ list ของข้อความ (_iterator_ of lists of texts) ตัวอย่างเช่น list ของ list ของข้อความ (list of list of texts) +การใช้ list ของข้อความแบบนี้ จะทำให้การเทรนเร็วขึ้น เพราะว่าการเทรนเป็น batch จะเร็วกว่าการประมวลผลครั้งละหนึ่งข้อความ และสาเหตุที่ input ควรจะเป็น iterator ก็เพื่อป้องกันไม่ให้ Python อ่านข้อความทั้งหมดเข้าไปเก็บใน memory ของคอมพิวเตอร์ภายในครั้งเดียว +ถ้าชุดข้อมูลของคุณนั้นใหญ่มาก คุณอาจจะลองใช้ 🤗 Datasets เพื่อช่วยจัดการชุดข้อมูล เพราะมันจะไม่อ่านข้อมูลทั้งหมดเข้าไปเก็บใน RAM แต่บันทึกข้อมูลใน disk แทน + +โค้ดข้างล่างนี้จะสร้าง list ของ list ของ 1,000 ข้อความ (list of lists of 1,000 texts) และจะโหลดข้อมูล input ทั้งหมดไปเก็บใน memory: + +```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)] +``` + +ถ้าหากคุณเปลี่ยนมาใช้ Python generator แทน ก็จะป้องกันไม่ให้ Python โหลดข้อมูลทั้งหมดเข้าไปใน memory ถ้าไม่จำเป็น + +วิธีการสร้าง generator ก็ง่ายๆเพียงแค่ แทนที่วงเล็บเหลี่ยม `[` ด้วยเว็บเล็บธรรมดา `(` ในโค้ดข้างบน: + +```py +training_corpus = ( + raw_datasets["train"][i : i + 1000]["whole_func_string"] + for i in range(0, len(raw_datasets["train"]), 1000) +) +``` + +โค้ดข้างบนนี้ จะไม่โหลดข้อความจาก `raw_datasets` ทั้งหมดเข้าไปใน memory แต่จะสร้าง iterator ซึ่งเป็น Python object ที่เป็นเสมือนตัวเก็บข้อมูลชั่วคราว + +การจะเรียกใช้ข้อมูลในนั้น ทำได้โดยใช้ `for` loop ข้อความใน iterator จะถูกโหลดเข้าไปใน memory ก็ต่อเมื่อคุณจะใช้งานมันเท่านั้น(ซึ่งก็คือ เวลาที่ `for` loop วนไปถึง item นั้น) ในตัวอย่างของเรา ในแต่ละ loop จะมีเพียงแค่ 1000 ข้อความเท่านั้นที่จะถูกโหลดมาเก็บไว้ใน memory การทำแบบนี้จะช่วยไม่ให้ memory ถูกใช้งานมากเกินไป หากคุณมีชุดข้อมูลที่ใหญ่มาก + +แต่ข้อเสียของ generator ก็คือเราสามารถใช้มันได้แค่ครั้งเดียว ดูตัวอย่างจากโค้ดข้างล่างนี้ +```py +gen = (i for i in range(10)) +print(list(gen)) +print(list(gen)) +``` + +เราจะเห็นว่าโค้ดนี้ print ผลลัพธ์แค่ครั้งแรก ส่วนในการสั่ง print ครั้งที่สองเราได้เพียง list เปล่ากลับมา: + +```python out +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +[] +``` + +เพื่อแก้ปัญหานี้ เราจะสร้างฟังก์ชันที่ผลิต Python generator เพื่อเอาไว้เก็บชุดข้อมูลแทน: + +```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() +``` + +การสร้าง generator ทำได้โดย ใช้ `for` loop และ `yield` statement: + +```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"] +``` + +ฟังก์ชันนี้จะสร้าง generator แบบเดียวกับวิธีการข้างบน แต่ช่วยให้คุณสามารถเขียน logic ที่ซับซ้อนได้มากกว่าการใช้เพียง list comprehension + +## การเทรน tokenizer + +หลังจากเราก็มี iterator ที่แบ่งชุดข้อมูลเป็น batch แล้ว เราก็พร้อมแล้วที่จะเทรน tokenizer สิ่งแรกที่คุณต้องทำคือโหลด tokenizer ที่คุณต้องการจะใช้คู่กับโมเดลหลัก(ในตัวอย่างนี้โมเดลหลักของเราคือ GPT-2) + +```py +from transformers import AutoTokenizer + +old_tokenizer = AutoTokenizer.from_pretrained("gpt2") +``` + +ถึงแม้ว่าเป้าหมายของเราคือการเทรน tokenizer ใหม่ เราจะเริ่มต้นด้วยการโหลด tokenizer ที่ถูกเทรนมาแล้ว เพื่อที่เราจะได้ไม่ต้องเริ่มกระบวนการทั้งหมดตั้งแต่แรก +ข้อดีของการทำแบบนี้ก็คือ คุณไม่ต้องเสียเวลาตั้งค่าต่างๆ เช่น ประเภทอัลกอรึทึมของ tokenizer หรือ token พิเศษต่างๆ tokenizer ตัวใหม่ของเราจะมีโครงสร้างเหมือนกับตัวที่ใช้ใน GPT-2 สิ่งเดียวที่แตกต่างคือชุดคำศัพท์(vocabulary) ซึ่งจะเปลี่ยนไปตามชุดข้อมูลใหม่ที่เราจะใช้ + +ก่อนอื่นมาดูกันว่า tokenizer ที่เราเพิ่งโหลดมา จะแบ่งข้อความตัวอย่างข้างล่างอย่างไร : + +```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'] +``` + +tokenizer นี้มีการใช้สัญลักษณ์พิเศษ เช่น `Ġ` ซึ่งเอาไว้แทนช่องว่าง (space) และ `Ċ` ซึ่งแทนการเริ่มบรรทัดใหม่ (newline) +เราจะเห็นว่า ผลลัพธ์ของการตัดคำไม่ค่อยจะดีนัก เพราะว่าช่องว่างที่อยู่ต่อกันนั้น ถูกแบ่งออกเป็นอย่างละ token ซึ่งจริงๆแล้วการแบ่งที่ดีกว่านี้คือ ช่องว่างที่อยู่ติดกันควรจะถูกรวมให้เป็น token เดียว (เพราะว่าการพิมช่องว่าง 4 หรือ 8 ครั้ง เป็นสิ่งที่พบได้ทั่วไปในการเขียนโค้ด) + +นอกจากนั้น tokenizer นี้ยังแบ่งชื่อฟังก์ชันได้ไม่ดีเท่าไหร่ เหมือนกับว่ามันไม่คุ้นเคยกับสัญลักษณ์ `_` ทำให้ชื่อฟังก์ชันถูกแยกออกเป็นสี่ส่วน + + +เรามาเริ่มเทรน tokenizer ตัวใหม่กัน แล้วดูว่า เราจะแก้ปัญหานี้ได้หรือเปล่า เราจะเริ่มจากการใช้ Python method ชื่อว่า `train_new_from_iterator()`: + +```py +tokenizer = old_tokenizer.train_new_from_iterator(training_corpus, 52000) +``` + +เวลารันคำสั่งนี้ โปรแกรมอาจจะเวลาซักพัก ถ้าคุณใช้ชุดข้อมูลที่ใหญ่มาก แต่สำหรับชุดข้อมูลตัวอย่างของเราที่มีขนาด 1.6 GB การประมวลผลนั้นค่อนข้างเร็ว (ใช้เวลาทั้งหมด 1 นาที 16 วินาที บนซีพียู AMD Ryzen 9 3900X CPU ซึ่งมี 12 cores) + +สิ่งหนึ่งที่คุณควรรู้คือ `AutoTokenizer.train_new_from_iterator()` นั้น ใช้งานได้แค่ในกรณีที่ตัวตัดคำเป็นแบบเร็ว + +คุณจะได้เห็นในบทต่อไปว่า 🤗 Transformers library มี tokenizer สองประเภท ประเภทแรกคือตัวที่เขียนด้วย Python ล้วน และประเภทที่สอง(แบบเร็ว)ที่สร้างจาก 🤗 Tokenizers library ซึ่งใช้ภาษา [Rust](https://www.rust-lang.org) ในการเขียน +แม้ว่า Python จะเป็นภาษาที่ได้รับความนิยมมากที่สุดในงานด้าน data science และ deep learning แต่ถ้าเราต้องการประมวลผลข้อมูลให้รวดเร็วมากขึ้น โดยใช้การประมวลผลแบบ parallel (หมายถึง ประมวลผลหลายๆงานพร้อมๆกัน) เราจำเป็นต้องเขียนโปรแกรมด้วยภาษาอื่น +ตัวอย่างเช่น การคูณเมทริกซ์ ซึ่งถือเป็นการคำนวนหลักในการประมวลผลของโมเดลประเภท neural network โค้ดส่วนนี้จะถูกเขียนด้วยภาษา CUDA ซึ่งเป็น C library ที่ถูกพัฒนาให้เหมาะกับการใช้งานร่วมกับ GPU +หากเราเขียนโปรแกรมสำหรับเทรน tokenizer ด้วย Python อย่างเดียว จะทำให้การคำนวนช้ามาก นี่คือเหตุผลที่ Huggingface สร้าง 🤗 Tokenizers library ขึ้นมา + +แต่ไม่ต้องกังวลกับส่วนนี้ เพราะคุณไม่จำเป็นต้องรู้ภาษา Rust เพื่อจะใช้งานตัวตัดคำแบบเร็วนี้ เหมือนกับที่คุณไม่จำเป็นต้องรู้ภาษา CUDA เพื่อจะรันโมเดลบน GPU + +🤗 Tokenizers library มี Python bindings สำหรับ method ที่ต้องเรียกใช้โค้ดจากภาษา Rust +ตัวอย่างเช่น โค้ดส่วนที่ทำให้การเทรน tokenizer เป็นไปแบบ parallel หรือตอนที่เรารัน tokenizer กับ +input แบบ batch [Chapter 3](/course/chapter3) + +โมเดล Transformer ส่วนมากรองรับการใช้งานร่วมกับตัวตัดคำแบบเร็ว (แต่มีกรณียกเว้น คุณสามารถเช็คดูได้ที่[นี่](https://huggingface.co/transformers/#supported-frameworks)) +สำหรับโมเดลที่รองรับการตัดคำแบบเร็ว `AutoTokenizer` API จะโหลดตัวตัดคำแบบเร็วเป็นค่าเริ่มต้นเสมอ + +ใน section ถัดไปเราจะเรียนเกี่ยวกับ feature พิเศษต่างๆของตัวตัดคำแบบเร็ว ซึ่งจะมีประโยชน์ในงานประเภท token classification หรือ question answering + +ก่อนที่เราจะไปดูรายละเอียดกัน เรามาดูประสิทธิภาพของ tokenizer ที่เพิ่งเทรนเสร็จแล้วของเรากันดีกว่า เราจะลองใส่ข้อความที่เราใช้ในตัวอย่างด้านบนให้กับ tokenizer ของเราดู + +```py +tokens = tokenizer.tokenize(example) +tokens +``` + +```python out +['def', 'Ġadd', '_', 'numbers', '(', 'a', ',', 'Ġb', '):', 'ĊĠĠĠ', 'Ġ"""', 'Add', 'Ġthe', 'Ġtwo', 'Ġnumbers', 'Ġ`', + 'a', '`', 'Ġand', 'Ġ`', 'b', '`."""', 'ĊĠĠĠ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb'] +``` + +ในผลลัพธ์ของการตัดคำ คุณจะยังเห็นสัญลักษณ์พิเศษ `Ġ` และ `Ċ` เหมือนในตัวอย่างก่อนหน้า แต่คุณจะสังเกตว่า ตอนนี้ tokenizer ของเรานั้นได้เรียนรู้และเห็นว่า token บางตัวนั้น โดดเด่นกว่าตัวอื่นๆในชุดข้อมูล +ตัวอย่างเช่น token `ĊĠĠĠ` แสดงถึงการย่อหน้า(indentation) และ `Ġ"""` แสดงถึงเครื่องหมายคำพูดสามตัว ที่โปรแกรมเมอร์ใช้เวลาจะเริ่มเขียน docstring +ตัวตัดคำใหม่นี้ ยังแบ่งชื่อฟังก์ชันได้อย่างถูกต้องอีกด้วย โดยแบ่งที่ `_` + +การแบ่งคำแบบนี้ ทำให้สัญลักษณ์หรือตัวอักษรต่างๆถูกรวบให้กระทัดรัดขึ้น หากเทียบกับ tokenizer เก่าที่เทรนจากข้อความภาษาอังกฤษปกติ เราจะเห็นว่า ถ้าเราใช้ทั้งสอง tokenizer เพื่อตัดข้อความ input เดียวกัน tokenizer เก่าจะให้ผลลัพธ์ที่ยาวกว่า tokenizer ตัวใหม่ + + + +```py +print(len(tokens)) +print(len(old_tokenizer.tokenize(example))) +``` + +```python out +27 +36 +``` + +มาดูอีกตัวอย่างกัน : + +```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', 'ĊĠĠĠĠ'] +``` + +ในตัวอย่างนี้ นอกจากเราจะเห็น token ที่แสดงถึงย่อหน้าแล้ว เรายังเห็น token ของ double indentation ซึ่งคือ `ĊĠĠĠĠĠĠĠ` ส่วนคำที่มีความหมายพิเศษใน Python เช่น `class`, `init`, `call`, `self` และ `return` ก็ถูกแบ่งให้เป็นอย่างละ token อย่างถูกต้อง + +นอกจากนั้น เราจะยังเห็นด้วยว่า tokenizer จะตัดแบ่งข้อความเวลาที่มันเห็นสัญลักษณ์ `_` และ `.` และยังแบ่งข้อความที่เป็น camel-cased ได้อย่างถูกต้อง เช่น `LinearLayer` ถูกแยกออกเป็น `["ĠLinear", "Layer"]` + + +## การบันทึก tokenizer + +เพื่อที่เราจะสามารถใช้งาน tokenizer ที่เราเทรนเมื่อซักครู่นี้ได้อีกในครั้งหน้า เราจำเป็นจะต้องเก็บบันทึกมันไว้ ในการเซฟเราจะใช้ method ชื่อ `save_pretrained()` + +```py +tokenizer.save_pretrained("code-search-net-tokenizer") +``` + +คำสั่งนี้จะสร้างแฟ้มงาน (folder) ขึ้นมาใหม่ ชื่อว่า *code-search-net-tokenizer* ซึ่งเอาไว้บันทึกข้อมูลต่างๆของ tokenizer ที่จำเป็นในการเรียกใช้งานอีกครั้ง +ถ้าคุณต้องการจะแชร์ tokenizer นี้กับเพื่อนร่วมงานหรือเพื่อนของคุณ คุณสามารถอัพโหลดมันไปที่ Hub ของ Huggingface ได้ โดยคุณจะต้อง login เข้าบัญชีก่อน +ถ้าคุณทำงานใน notebook (เช่น Jupyter notebook) คุณสามารถใช้ function ข้างล่างนี้ได้เพื่อความสะดวก + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +หลังจากคุณรันโค้ดข้างบน คุณจะเห็น widget ให้ล็อกอินเข้าบัญชี Hugging Face +แต่หากคุณไม่ได้ใช้ notebook ให้พิมคำสั่งข้างล่างนี้ใน terminal + +```bash +huggingface-cli login +``` + +หลังจากล็อกอินแล้ว คุณจะสามารถ push tokenizer ของคุณไปที่ Hub ได้ โดยใช้คำสั่งข้างล่างนี้ + +```py +tokenizer.push_to_hub("code-search-net-tokenizer") +``` + +คำสั่งนี้จะสร้าง repository ใหม่ในชื่อ `code-search-net-tokenizer` ใน namespace ของคุณ ซึ่ง repository นี้ก็จะเก็บไฟล์เกี่ยวกับ tokenizer ของคุณไว้ หลังจากนั้น คุณก็จะสามารถดาวน์โหลด tokenizer นี้ได้ ด้วยการใช้ `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") +``` + +มาถึงตอนนี้คุณก็พร้อมแล้วที่จะเทรน และ fine-tune language model สำหรับงานที่คุณต้องการ เราจะเรียนเรื่องกันนี้ใน[บทที่ 7](/course/chapter7) แต่ในบทนี้ เราจะเรียนเกี่ยวกับ fast tokenizer ให้ละเอียดมากขึ้นและมาดูกันว่า เวลาคุณรัน `train_new_from_iterator()` มีการคำนวนอะไรเกิดขึ้นบ้าง \ No newline at end of file diff --git a/chapters/tr/_toctree.yml b/chapters/tr/_toctree.yml index 5e9cc1073..4b48680b3 100644 --- a/chapters/tr/_toctree.yml +++ b/chapters/tr/_toctree.yml @@ -12,6 +12,9 @@ title: Doğal Dil İşleme - local: chapter1/5 title: Encoder modelleri + - local: chapter1/6 + title: Decoder modelleri + - title: 2. 🤗 Transformers Kullanımı sections: diff --git a/chapters/tr/chapter1/6.mdx b/chapters/tr/chapter1/6.mdx new file mode 100644 index 000000000..4599582de --- /dev/null +++ b/chapters/tr/chapter1/6.mdx @@ -0,0 +1,16 @@ +# Decoder modelleri + + + +Decoder modeller, yalnızca bir Transformer modelinin decoderini kullanır. Her aşamada, attention katmanları sadece cümlede kendisinden önce gelen kelimelere erişebilir. Bu modeller *auto-regressive models* olarak isimlendirilir. + +Decoder modellerin ön eğitimi genellikle cümledeki bir sonraki kelimeyi tahmin etme şeklinde görevlendirilir. + +Bu modeller, en çok metin oluşturmayı içeren görevler için uygundur. + +Bu model ailelerinin temsilcileri şunları kapsar: + +- [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) +- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) +- [GPT-2](https://huggingface.co/transformers/model_doc/gpt2.html) +- [Transformer XL](https://huggingface.co/transformers/model_doc/transformerxl.html) From e6d053160908387b9081efc2f2a948b42e794849 Mon Sep 17 00:00:00 2001 From: lewtun Date: Wed, 18 May 2022 13:31:17 +0200 Subject: [PATCH 14/51] Bump release 10 (#194) --- chapters/en/_toctree.yml | 5 +- chapters/en/chapter1/1.mdx | 4 + chapters/en/chapter9/1.mdx | 8 +- chapters/en/chapter9/8.mdx | 239 ++----------------------------------- chapters/en/chapter9/9.mdx | 234 ++++++++++++++++++++++++++++++++++++ chapters/fr/_toctree.yml | 15 ++- chapters/pt/_toctree.yml | 3 +- chapters/pt/chapter1/2.mdx | 21 ++++ 8 files changed, 289 insertions(+), 240 deletions(-) create mode 100644 chapters/en/chapter9/9.mdx create mode 100644 chapters/pt/chapter1/2.mdx diff --git a/chapters/en/_toctree.yml b/chapters/en/_toctree.yml index 18ec59d4d..0ae46daa8 100644 --- a/chapters/en/_toctree.yml +++ b/chapters/en/_toctree.yml @@ -167,8 +167,7 @@ title: End-of-chapter quiz quiz: 8 -- local: chapter9 - title: 9. Building and sharing demos +- title: 9. Building and sharing demos new: true subtitle: I trained a model, but how can I show it off? sections: @@ -187,6 +186,8 @@ - local: chapter9/7 title: Introduction to Blocks - local: chapter9/8 + title: Gradio, check! + - local: chapter9/9 title: End-of-chapter quiz quiz: 9 diff --git a/chapters/en/chapter1/1.mdx b/chapters/en/chapter1/1.mdx index 7ec1a6f11..cd1851129 100644 --- a/chapters/en/chapter1/1.mdx +++ b/chapters/en/chapter1/1.mdx @@ -32,12 +32,16 @@ After you've completed this course, we recommend checking out DeepLearning.AI's About the authors: +**Abubakar Abid** completed his PhD at Stanford in applied machine learning. During his PhD, he founded [Gradio](https://github.com/gradio-app/gradio), an open-source Python library that has been used to build over 600,000 machine learning demos. Gradio was acquired by Hugging Face, which is where Abubakar now serves as a machine learning team lead. + **Matthew Carrigan** is a Machine Learning Engineer at Hugging Face. He lives in Dublin, Ireland and previously worked as an ML engineer at Parse.ly and before that as a post-doctoral researcher at Trinity College Dublin. He does not believe we're going to get to AGI by scaling existing architectures, but has high hopes for robot immortality regardless. **Lysandre Debut** is a Machine Learning Engineer at Hugging Face and has been working on the 🤗 Transformers library since the very early development stages. His aim is to make NLP accessible for everyone by developing tools with a very simple API. **Sylvain Gugger** is a Research Engineer at Hugging Face and one of the core maintainers of the 🤗 Transformers library. Previously he was a Research Scientist at fast.ai, and he co-wrote _[Deep Learning for Coders with fastai and PyTorch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)_ with Jeremy Howard. The main focus of his research is on making deep learning more accessible, by designing and improving techniques that allow models to train fast on limited resources. +**Dawood Khan** is a Machine Learning Engineer at Hugging Face. He's from NYC and graduated from New York University studying Computer Science. After working as an iOS Engineer for a few years, Dawood quit to start Gradio with his fellow co-founders. Gradio was eventually acquired by Hugging Face. + **Merve Noyan** is a developer advocate at Hugging Face, working on developing tools and building content around them to democratize machine learning for everyone. **Lucile Saulnier** is a machine learning engineer at Hugging Face, developing and supporting the use of open source tools. She is also actively involved in many research projects in the field of Natural Language Processing such as collaborative training and BigScience. diff --git a/chapters/en/chapter9/1.mdx b/chapters/en/chapter9/1.mdx index a982b5304..edfcb13f9 100644 --- a/chapters/en/chapter9/1.mdx +++ b/chapters/en/chapter9/1.mdx @@ -19,7 +19,7 @@ Here are some examples of machine learning demos built with Gradio: * An extractive **question answering** model that takes in a context paragraph and a quest and outputs a response and a probability score (we discussed this kind of model [in Chapter 7](/course/chapter7/7)): - + * A **background removal** model that takes in an image and outputs the image with the background removed: @@ -30,3 +30,9 @@ This chapter is broken down into sections which include both _concepts_ and _app 👀 Check out Hugging Face Spaces to see many recent examples of machine learning demos built by the machine learning community! + +## Gradio blocks party 🥳 + +If you want to put the knowledge from this chapter to good use, come join the Gradio blocks party! This is a community event that's hosted by Hugging Face on **May 16-31**. During this event, you'll build cool machine learning demos with Gradio and be in the running to win Hugging Face swag and prizes! + +Check out the [event description](https://github.com/AK391/community-events/blob/main/gradio-blocks/README.md) for details on how to participate - we can't wait to see what you'll build 🤗! diff --git a/chapters/en/chapter9/8.mdx b/chapters/en/chapter9/8.mdx index b5e73698b..de661e346 100644 --- a/chapters/en/chapter9/8.mdx +++ b/chapters/en/chapter9/8.mdx @@ -1,234 +1,17 @@ - +# Gradio, check! -# End-of-chapter quiz +This wraps up the chapter on building cool ML demos with Gradio - we hope you enjoyed it! To recap, in this chapter we learned: -Let's test what you learned in this chapter! +- How to create Gradio demos with the high-level `Interface` API, and how to configure different input and output modalities. +- Different ways to share Gradio demos, through temporary links and hosting on [Hugging Face Spaces](https://huggingface.co/spaces). +- How to integrate Gradio demos with models and Spaces on the Hugging Face Hub. +- Advanced features like storing state in a demo or providing authentication. +- How to have full control of the data flow and layout of your demo with Gradio Blocks. -### 1. What can you use Gradio to do? +If you'd like to test your understanding of the concepts covered in this chapter, check out the quiz in the next section! - +## Gradio blocks party 🥳 -### 2. Gradio ONLY works with PyTorch models +If you want to put the knowledge from this chapter to good use, come join the Gradio blocks party! This is a community event that's hosted by Hugging Face on **May 16-31**. During this event, you'll build cool machine learning demos with Gradio and be in the running to win Hugging Face swag and prizes! - - -### 3. Where can you launch a Gradio demo from? - - - -### 4. Gradio is designed primarily for NLP models - - - -### 5. Which of the following features are supported by Gradio? - - - -### 6. Which of the following are valid ways of loading a Hugging Face model from Hub or Spaces? - - - -### 7. Select all the steps necessary for adding state to your Gradio interface - - - -### 8. Which of the following are components included in the Gradio library? - - - -### 9. What does Gradio `Blocks` allow you to do? - - - -### 10. You can share a public link to a `Blocks` demo and host a `Blocks` demo on Hugging Face spaces. - - \ No newline at end of file +Check out the [event description](https://github.com/AK391/community-events/blob/main/gradio-blocks/README.md) for details on how to participate - we can't wait to see what you'll build 🤗! \ No newline at end of file diff --git a/chapters/en/chapter9/9.mdx b/chapters/en/chapter9/9.mdx new file mode 100644 index 000000000..b5e73698b --- /dev/null +++ b/chapters/en/chapter9/9.mdx @@ -0,0 +1,234 @@ + + +# End-of-chapter quiz + +Let's test what you learned in this chapter! + +### 1. What can you use Gradio to do? + + + +### 2. Gradio ONLY works with PyTorch models + + + +### 3. Where can you launch a Gradio demo from? + + + +### 4. Gradio is designed primarily for NLP models + + + +### 5. Which of the following features are supported by Gradio? + + + +### 6. Which of the following are valid ways of loading a Hugging Face model from Hub or Spaces? + + + +### 7. Select all the steps necessary for adding state to your Gradio interface + + + +### 8. Which of the following are components included in the Gradio library? + + + +### 9. What does Gradio `Blocks` allow you to do? + + + +### 10. You can share a public link to a `Blocks` demo and host a `Blocks` demo on Hugging Face spaces. + + \ No newline at end of file diff --git a/chapters/fr/_toctree.yml b/chapters/fr/_toctree.yml index 8f4b86150..c839d61bc 100644 --- a/chapters/fr/_toctree.yml +++ b/chapters/fr/_toctree.yml @@ -2,7 +2,7 @@ sections: - local: chapter0/1 title: Introduction - + - title: 1. Les transformers sections: - local: chapter1/1 @@ -26,7 +26,7 @@ - local: chapter1/10 title: Quiz de fin de chapitre quiz: 1 - + - title: 2. Utilisation de 🤗 Transformers sections: - local: chapter2/1 @@ -46,11 +46,11 @@ - local: chapter2/8 title: Quiz de fin de chapitre quiz: 2 - + - title: 3. Finetuner un modèle pré-entraîné sections: - local: chapter3/1 - title: Introduction + title: Introduction - local: chapter3/2 title: Traîter les données - local: chapter3/3 @@ -79,7 +79,7 @@ - local: chapter4/6 title: Quiz de fin de chapitre quiz: 4 - + - title: 5. La bibliothèque 🤗 Datasets sections: - local: chapter5/1 @@ -167,8 +167,7 @@ title: Quiz de fin de chapitre quiz: 8 -- local: chapter9 - title: 9. Construire et partager des démos +- title: 9. Construire et partager des démos new: true subtitle: J'ai entraîné un modèle, mais comment puis-je le montrer ? sections: @@ -189,7 +188,7 @@ - local: chapter9/8 title: Quiz de fin de chapitre quiz: 9 - + - title: Evènements liés au cours d'Hugging Face sections: - local: event/1 diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index 85b88a725..dfa771b37 100644 --- a/chapters/pt/_toctree.yml +++ b/chapters/pt/_toctree.yml @@ -7,7 +7,8 @@ sections: - local: chapter1/1 title: Introdução - + - local: chapter1/2 + title: Processamento de Linguagem Natural - title: 2. Usando 🤗 Transformers sections: - local: chapter2/1 diff --git a/chapters/pt/chapter1/2.mdx b/chapters/pt/chapter1/2.mdx new file mode 100644 index 000000000..a3413a2a8 --- /dev/null +++ b/chapters/pt/chapter1/2.mdx @@ -0,0 +1,21 @@ +# Processamento de Linguagem Natural (NLP) + +Antes de irmos direto para os modelos Transformers, vamos fazer um rápido esboço sobre o que é processamento de linguagem natural e o porquê nós nos importamos com isso. + +## O que é NLP? + +NLP é um campo da linguística e da Aprendizagem de Máquina (ML) focada em entender tudo relacionado a linguagem humana. O objetivo das tarefas de NLP não é apenas entender palavras soltas individualmente, mas ser capaz de entender o contexto dessas palavras. + +A seguir uma lista de tarefas comuns de NLP, com alguns exemplos: + +- **Classificação de sentenças completas**: Capturar o sentimento de uma revisão, detectar se um email é spam, determinar se a sentença é gramaticalmente correta ou onde duas sentenças são logicamente relacionadas ou não +- **Classificação de cada palavra em uma sentença**: Identificar os componentes gramaticais de uma sentença (substantivo, verbo, adjetivo), ou as entidades nomeadas (pessoa, local, organização) +- **Geração de conteúdo textual**: Completar um trecho com autogeração textual, preenchendo as lacunas em um texto com palavras mascaradas +- **Extrair uma resposta de um texto**: Dada uma pergunta e um contexto, extrair a resposta baseada na informação passada no contexto +- **Gerar uma nova sentença a partir de uma entrada de texto**: Traduzir um texto para outro idioma, resumi-lo + +NLP não se limita ao texto escrito. Também engloba desafios complexos nos campos de reconhecimento de discurso e visão computacional, tal como a geração de transcrição de uma amostra de áudio ou a descrição de uma imagem. + +## Por que isso é desafiador? + +Os computadores não processam a informação da mesma forma que os seres humanos. Por exemplo, quando nós lemos a sentença "Estou com fome", nós podemos facilmente entender seu significado. Similarmente, dada duas sentenças como "Estou com fome" e "Estou triste", nós somos capazes de facilmente determinar quão similares elas são. Para modelos de Aprendizagem de Máquina (ML), tarefas como essas são mais difíceis. O texto precisa ser processado de um modo que possibilite o modelo aprender por ele. E porque a linguagem é complexa, nós precisamos pensar cuidadosamente sobre como esse processamento tem que ser feito. Tem se feito muita pesquisa sobre como representar um texto e nós iremos observar alguns desses métodos no próximo capítulo. From 5591b58dcf28c8614fb6ee4a064702052dc6cd38 Mon Sep 17 00:00:00 2001 From: lewtun Date: Thu, 19 May 2022 14:11:56 +0200 Subject: [PATCH 15/51] Bump release (#195) --- chapters/en/chapter1/6.mdx | 2 +- chapters/es/TRANSLATING.txt | 23 ++ chapters/es/_toctree.yml | 7 +- chapters/es/glossary/1.mdx | 94 ++++++ chapters/fr/_toctree.yml | 4 +- chapters/fr/chapter1/1.mdx | 4 + chapters/fr/chapter1/3.mdx | 18 +- chapters/fr/chapter3/4.mdx | 6 +- chapters/fr/chapter6/10.mdx | 4 +- chapters/fr/chapter6/2.mdx | 2 +- chapters/fr/chapter6/5.mdx | 4 +- chapters/fr/chapter6/6.mdx | 4 +- chapters/fr/chapter9/1.mdx | 2 +- chapters/fr/chapter9/3.mdx | 15 +- chapters/fr/chapter9/4.mdx | 8 +- chapters/fr/chapter9/5.mdx | 8 +- chapters/fr/chapter9/6.mdx | 5 +- chapters/fr/chapter9/7.mdx | 2 +- chapters/fr/chapter9/8.mdx | 251 +------------- chapters/fr/chapter9/9.mdx | 233 +++++++++++++ chapters/pt/_toctree.yml | 5 +- chapters/pt/chapter4/3.mdx | 641 ++++++++++++++++++++++++++++++++++++ 22 files changed, 1068 insertions(+), 274 deletions(-) create mode 100644 chapters/es/TRANSLATING.txt create mode 100644 chapters/es/glossary/1.mdx create mode 100644 chapters/fr/chapter9/9.mdx create mode 100644 chapters/pt/chapter4/3.mdx diff --git a/chapters/en/chapter1/6.mdx b/chapters/en/chapter1/6.mdx index 87ad85ec3..d86cea9e5 100644 --- a/chapters/en/chapter1/6.mdx +++ b/chapters/en/chapter1/6.mdx @@ -13,4 +13,4 @@ Representatives of this family of models include: - [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) - [GPT](https://huggingface.co/transformers/model_doc/gpt.html) - [GPT-2](https://huggingface.co/transformers/model_doc/gpt2.html) -- [Transformer XL](https://huggingface.co/transformers/model_doc/transformerxl.html) +- [Transformer XL](https://huggingface.co/transformers/model_doc/transfo-xl.html) diff --git a/chapters/es/TRANSLATING.txt b/chapters/es/TRANSLATING.txt new file mode 100644 index 000000000..86e7bb514 --- /dev/null +++ b/chapters/es/TRANSLATING.txt @@ -0,0 +1,23 @@ +1. We use the informal "you" (i.e. "Tu" instead of "Usted") to keep the tone jovial. However, don't use slang or local language. + +2. Don't translate industry-accepted acronyms. e.g. TPU or GPU. + +3. 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. + +4. Keep voice active and consistent. Don't overdo it but try to avoid a passive voice. + +5. 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. + +6. Keep POV consistent. + +7. Smaller sentences are better sentences. Apply with nuance. + +8. 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. + +9. This is merely a translation. Don't add any technical/contextual information not present in the original text. Also don't leave stuff out. The creative choices in composing this information were the original authors' to make. Our creative choices are in doing a quality translation. + +10. Be exact when choosing equivalents for technical words. Package is package. Library is library. Don't mix and match. + +11. Library names are kept in the original forms, e.g. "🤗 Datasets", however, the word dataset in a sentence gets a translation to "Conjunto de datos". + +12. As a style choice prefer the imperative over constructions with auxiliary words to avoid unnecessary verbosity and addressing of the reader, which seems unnatural. e.g. "Ver capítulo X" - "See chapter X" instead of "Puedes ver esto en el capítulo X" - "You can see this in chapter X". \ No newline at end of file diff --git a/chapters/es/_toctree.yml b/chapters/es/_toctree.yml index 4e4e1dbff..fe81e7ecd 100644 --- a/chapters/es/_toctree.yml +++ b/chapters/es/_toctree.yml @@ -39,4 +39,9 @@ - local: chapter3/1 title: Introducción - local: chapter3/2 - title: Procesamiento de los datos \ No newline at end of file + title: Procesamiento de los datos + +- title: Glosario + sections: + - local: glossary/1 + title: Glosario \ No newline at end of file diff --git a/chapters/es/glossary/1.mdx b/chapters/es/glossary/1.mdx new file mode 100644 index 000000000..6879ce284 --- /dev/null +++ b/chapters/es/glossary/1.mdx @@ -0,0 +1,94 @@ +# Vocabulario + +| Original | Spanish | +|-----------------------------|--------------------------------- | +| Abstraction | Abstracción | +| Accuracy | Exactitud | +| Backward Pass | Pasada en reverso | +| Batch | Lote | +| Benchmark | Punto de referencia | +| Cache | Almacenamiento | +| Caching | Almacenar | +| Chapter | Capítulo | +| Checkpoint | Punto de control | +| Class | Clase | +| Code | Código | +| Colab Notebook | Colab Notebook | +| Command | Comando | +| Configuration | Configuración | +| Course | Curso | +| Dependency | Dependencia | +| Deployment | Deployment | +| Development | Desarrollo | +| Dictionary | Diccionario | +| Distribution | Distribución | +| Download | Descargar | +| F1 score | F1 score | +| Feature | Feature | +| Field | Atributo | +| Fine-tuning | Ajustar | +| Folder | Carpeta | +| Forward Pass | Pasada hacia delante | +| Function | Función | +| Google | Google | +| Hugging Face | Hugging Face | +| Incompatibility | Incompatibilidad | +| Inference | Inferencia | +| Key (in a dictionary) | Llave | +| Library | Libreria | +| Linux | Linux | +| Load | Cargar | +| Loss function | Función de pérdida | +| Loop | Bucle | +| macOS | macOS | +| Model | Modelo | +| Model Hub | Hub de Modelos | +| Module | Módulo | +| Natural Language Processing | Procesamiento de Lenguaje Natural | +| Package | Paquete | +| Package Manager | Manejador de paquete | +| Padding | Relleno | +| Parameter | Parámetro | +| Python | Python | +| Pytorch | Pytorch | +| Save | Guardar | +| Script | Script | +| Self-Contained | Auto-contenido | +| Setup | Instalación | +| TensorFlow | Tensorflow | +| Terminal | Terminal | +| Tokenizer | Tokenizador | +| Train | Entrenar | +| Transformer | Transformer | +| Virtual Environment | Ambiente Virtual | +| Weight | Peso | +| Weights | Pesos | +| Windows | Windows | +| Working Environment | Ambiente de Trabajo | +| Workload | Carga de trabajo | +| Workspace | Workspace | + + +## Abbreviations + +| Original | Spanish | +|-----------|-------------| +| NLP | PLN | +| API | API | +| GPU | GPU | +| TPU | TPU | +| ML | ML | + +## Notes + +Please refer to [TRANSLATING.txt](/chapters/es/TRANSLATING.txt) for a translation guide. Here are some excerpts relevant to the glossary: + +- 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. + +- 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. diff --git a/chapters/fr/_toctree.yml b/chapters/fr/_toctree.yml index c839d61bc..a0f4fd015 100644 --- a/chapters/fr/_toctree.yml +++ b/chapters/fr/_toctree.yml @@ -89,7 +89,7 @@ - local: chapter5/3 title: Il est temps de trancher et de découper - local: chapter5/4 - title: Données massives ? 🤗 Des jeux de données à la rescousse ! + title: Données massives ? 🤗 Datasets à la rescousse ! - local: chapter5/5 title: Création de votre propre jeu de données - local: chapter5/6 @@ -186,6 +186,8 @@ - local: chapter9/7 title: Introduction aux Blocks - local: chapter9/8 + title: 🤗 Gradio, coché ! + - local: chapter9/9 title: Quiz de fin de chapitre quiz: 9 diff --git a/chapters/fr/chapter1/1.mdx b/chapters/fr/chapter1/1.mdx index 854ac3b7a..a0ff68898 100644 --- a/chapters/fr/chapter1/1.mdx +++ b/chapters/fr/chapter1/1.mdx @@ -31,12 +31,16 @@ Après avoir terminé ce cours, nous vous recommandons de suivre la [Spécialisa À propos des auteurs de ce cours : +*Abubakar Abid** a obtenu son doctorat à Stanford en apprentissage automatique appliqué. Pendant son doctorat, il a fondé [Gradio](https://github.com/gradio-app/gradio), une bibliothèque Python open-source qui a été utilisée pour construire plus de 600 000 démos d'apprentissage automatique. Gradio a été rachetée par Hugging Face, où Abubakar occupe désormais le poste de responsable de l'équipe d'apprentissage automatique. + **Matthew Carrigan** est ingénieur en apprentissage machine chez Hugging Face. Il vit à Dublin en Irlande. Il a travaillé auparavant comme ingénieur en apprentissage machine chez Parse.ly et avant cela comme chercheur postdoctoral au Trinity College Dublin. Il ne croit pas que nous arrivions à l'*AGI* en mettant à l'échelle les architectures existantes mais a tout de même beaucoup d'espoir dans l'immortalité des robots. **Lysandre Debut** est ingénieur en apprentissage machine chez Hugging Face et a travaillé sur la bibliothèque 🤗 *Transformers* depuis les premières phases de développement. Son but est de rendre le NLP accessible à tous en développant des outils disposant d'une API très simple. **Sylvain Gugger** est ingénieur recherche chez Hugging Face et un des principaux responsables de la bibliothèque 🤗 *Transformers*. Avant cela, il était chercheur en en apprentissage machine chez fast.ai et a écrit le livre [*Deep Learning for Coders with fastai and PyTorch*](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/) avec Jeremy Howard. Son but est de rendre l'apprentissage profond plus accessible, en développant et en améliorant des techniques permettant aux modèles d'apprendre rapidement sur des ressources limitées. +**Dawood Khan** est un ingénieur en apprentissage automatique chez Hugging Face. Il vient de New York et est diplômé de l'Université de New York en informatique. Après avoir travaillé comme ingénieur iOS pendant quelques années, Dawood a quitté son poste pour créer Gradio avec ses cofondateurs. Gradio a finalement été acquis par Hugging Face. + **Merve Noyan** est développeuse *advocate* chez Hugging Face et travaille à la création d'outils et de contenus visant à démocratiser l'apprentissage machine pour tous. **Lucile Saulnier** est ingénieure en apprentissage machine chez Hugging Face et travaille au développement et à l'implémentation de nombreux outils *open source*. Elle est également activement impliquée dans de nombreux projets de recherche dans le domaine du NLP comme l'entraînement collaboratif de modèles et le projet BigScience. diff --git a/chapters/fr/chapter1/3.mdx b/chapters/fr/chapter1/3.mdx index beb4b7700..5428aff2b 100644 --- a/chapters/fr/chapter1/3.mdx +++ b/chapters/fr/chapter1/3.mdx @@ -163,18 +163,24 @@ from transformers import pipeline generator = pipeline("text-generation", model="distilgpt2") generator( - "In this course, we will teach you how to", # Dans ce cours, nous vous enseignerons comment + "In this course, we will teach you how to", + # Dans ce cours, nous vous enseignerons comment max_length=30, num_return_sequences=2, ) ``` ```python out -[{'generated_text': 'In this course, we will teach you how to manipulate the world and ' # Dans ce cours, nous vous enseignerons comment manipuler le monde et - 'move your mental and physical capabilities to your advantage.'}, # utiliser vos capacités mentales et physiques à votre avantage. - {'generated_text': 'In this course, we will teach you how to become an expert and ' # Dans ce cours, nous vous apprendrons comment devenir un expert et - 'practice realtime, and with a hands on experience on both real ' # pratique en temps réel, et avec une expérience pratique à la fois sur de vrais - 'time and real'}] # temps et réel +[{'generated_text': 'In this course, we will teach you how to manipulate the world and ' + # Dans ce cours, nous vous enseignerons comment manipuler le monde et + 'move your mental and physical capabilities to your advantage.'}, + # utiliser vos capacités mentales et physiques à votre avantage. + {'generated_text': 'In this course, we will teach you how to become an expert and ' + # Dans ce cours, nous vous apprendrons comment devenir un expert et + 'practice realtime, and with a hands on experience on both real ' + # pratique en temps réel, et avec une expérience pratique à la fois sur de vrais + 'time and real'}] + # temps et réel ``` Vous pouvez améliorer votre recherche de modèle en cliquant sur les *filtres* de langue et choisir un modèle qui génère du texte dans une autre langue. Le *Hub* contient également des *checkpoints* pour des modèles multilingues qui supportent plusieurs langues. diff --git a/chapters/fr/chapter3/4.mdx b/chapters/fr/chapter3/4.mdx index 8279747f4..ce42e9b7f 100644 --- a/chapters/fr/chapter3/4.mdx +++ b/chapters/fr/chapter3/4.mdx @@ -203,7 +203,7 @@ Une fois encore, vos résultats seront légèrement différents en raison du car
-### Optimisez votre boucle d'entraînement avec 🤗 *Accelerate* +### Optimisez votre boucle d'entraînement avec 🤗 Accelerate @@ -295,7 +295,7 @@ Ensuite, le gros du travail est fait dans la ligne qui envoie les *dataloaders*, ⚠️ Afin de bénéficier de la rapidité offerte par les TPUs du Cloud, nous vous recommandons de rembourrer vos échantillons à une longueur fixe avec les arguments `padding="max_length"` et `max_length` du tokenizer.
-Si vous souhaitez faire un copier-coller pour jouer, voici à quoi ressemble la boucle d'entraînement complète avec 🤗 *Accelerate* : +Si vous souhaitez faire un copier-coller pour jouer, voici à quoi ressemble la boucle d'entraînement complète avec 🤗 Accelerate : ```py from accelerate import Accelerator @@ -356,4 +356,4 @@ from accelerate import notebook_launcher notebook_launcher(training_function) ``` -Vous trouverez d'autres exemples dans le dépôt d'[🤗 *Accelerate*](https://github.com/huggingface/accelerate/tree/main/examples). \ No newline at end of file +Vous trouverez d'autres exemples dans le dépôt d'[🤗 *Accelerate*](https://github.com/huggingface/accelerate/tree/main/examples). diff --git a/chapters/fr/chapter6/10.mdx b/chapters/fr/chapter6/10.mdx index b4a6cdc89..062b51f16 100644 --- a/chapters/fr/chapter6/10.mdx +++ b/chapters/fr/chapter6/10.mdx @@ -62,7 +62,7 @@ Testons ce que vous avez appris dans ce chapitre ! correct: true }, { - text: "Les *tokenizers* rapides sont toujours plus rapides que leurs homologues lents.", + text: "Les tokenizers rapides sont toujours plus rapides que leurs homologues lents.", explain: "Un tokenizer rapide peut en fait être plus lent si vous ne lui donnez qu'un seul ou très peu de textes, car il ne peut pas utiliser le parallélisme." }, { @@ -164,7 +164,7 @@ Testons ce que vous avez appris dans ce chapitre ! explain: "C'est l'étape de normalisation." }, { - text: "C'est l'étape qui précède l'application du modèle *tokenizer*, pour diviser l'entrée en mots.", + text: "C'est l'étape qui précède l'application du modèle tokenizer, pour diviser l'entrée en mots.", explain: "C'est la bonne réponse !", correct: true }, diff --git a/chapters/fr/chapter6/2.mdx b/chapters/fr/chapter6/2.mdx index e4a23cc6e..9a29792dd 100644 --- a/chapters/fr/chapter6/2.mdx +++ b/chapters/fr/chapter6/2.mdx @@ -176,7 +176,7 @@ Cette commande peut prendre un peu de temps si votre corpus est très grand. Pou Notez que `AutoTokenizer.train_new_from_iterator()` ne fonctionne que si le *tokenizer* que vous utilisez est un *tokenizer* « rapide ». Comme vous le verrez dans la section suivante, la bibliothèque 🤗 *Transformers* contient deux types de *tokenizers* : certains sont écrits en pur Python et d'autres (les rapides) sont soutenus par la bibliothèque 🤗 *Tokenizers* qui est écrite dans le langage [Rust](https://www.rust-lang.org). Python est le langage le plus souvent utilisé pour les applications de science des données et d'apprentissage profond, mais lorsque quelque chose doit être parallélisé pour être rapide, il faut que cela soit écrit dans un autre langage. Par exemple, les multiplications matricielles qui sont au cœur du calcul du modèle sont écrites en CUDA, une bibliothèque en C optimisée pour les GPUs. -Entraîner un tout nouveau *tokenizer* en Python pur est atrocement lent, c'est pourquoi nous avons développé la bibliothèque 🤗 *Tokenizers*. Notez que, tout comme vous n'avez pas eu à apprendre le langage CUDA pour pouvoir exécuter votre modèle sur un batch d'entrées sur un GPU, vous n'aurez pas besoin d'apprendre Rust pour utiliser un *tokenizer* rapide. La bibliothèque 🤗 *Tokenizers* fournit des liaisons Python pour de nombreuses méthodes qui appellent en interne un morceau de code en Rust. Par exemple, pour paralléliser l'entraînement de votre nouveau *tokenizer* ou, comme nous l'avons vu dans le [Chapitre 3](/course/fr/chapter3), la tokenisation d'un lot d'entrées. +Entraîner un tout nouveau *tokenizer* en Python pur est atrocement lent, c'est pourquoi nous avons développé la bibliothèque 🤗 *Tokenizers*. Notez que, tout comme vous n'avez pas eu à apprendre le langage CUDA pour pouvoir exécuter votre modèle sur un batch d'entrées sur un GPU, vous n'aurez pas besoin d'apprendre Rust pour utiliser un *tokenizer* rapide. La bibliothèque 🤗 *Tokenizers* fournit des liaisons Python pour de nombreuses méthodes qui appellent en interne un morceau de code en Rust. Par exemple, pour paralléliser l'entraînement de votre nouveau *tokenizer* ou, comme nous l'avons vu dans le [chapitre 3](/course/fr/chapter3), la tokenisation d'un lot d'entrées. La plupart des *transformers* ont un *tokenizer* rapide de disponible. Il y a quelques exceptions que vous pouvez vérifier [ici](https://huggingface.co/transformers/#supported-frameworks). S'il est disponible, l'API `AutoTokenizer` sélectionne toujours pour vous le *tokenizer* rapide. Dans la prochaine section, nous allons jeter un coup d'oeil à certaines des autres caractéristiques spéciales des *tokenizers* rapides, qui seront très utiles pour des tâches comme la classification de *tokens* et la réponse aux questions. Mais avant cela, essayons notre tout nouveau *tokenizer* sur l'exemple précédent : diff --git a/chapters/fr/chapter6/5.mdx b/chapters/fr/chapter6/5.mdx index a20f96555..410d75af8 100644 --- a/chapters/fr/chapter6/5.mdx +++ b/chapters/fr/chapter6/5.mdx @@ -118,9 +118,9 @@ corpus = [ "This chapter is about tokenization.", # Ce chapitre traite de la tokenisation. "This section shows several tokenizer algorithms.", - # Cette section présente plusieurs algorithmes de *tokenizer*. + # Cette section présente plusieurs algorithmes de tokenizer. "Hopefully, you will be able to understand how they are trained and generate tokens.", - # Avec un peu de chance, vous serez en mesure de comprendre comment ils sont entraînés et génèrent des *tokens*. + # Avec un peu de chance, vous serez en mesure de comprendre comment ils sont entraînés et génèrent des tokens. ] ``` diff --git a/chapters/fr/chapter6/6.mdx b/chapters/fr/chapter6/6.mdx index 115588c63..e854edde1 100644 --- a/chapters/fr/chapter6/6.mdx +++ b/chapters/fr/chapter6/6.mdx @@ -111,9 +111,9 @@ corpus = [ "This chapter is about tokenization.", # This chapter is about tokenization "This section shows several tokenizer algorithms.", - # Cette section présente plusieurs algorithmes de *tokenizer*. + # Cette section présente plusieurs algorithmes de tokenizer. "Hopefully, you will be able to understand how they are trained and generate tokens.", - # Avec un peu de chance, vous serez en mesure de comprendre comment ils sont entraînés et génèrent des *tokens*. + # Avec un peu de chance, vous serez en mesure de comprendre comment ils sont entraînés et génèrent des tokens. ] ``` diff --git a/chapters/fr/chapter9/1.mdx b/chapters/fr/chapter9/1.mdx index d0be0c8be..78afbc891 100644 --- a/chapters/fr/chapter9/1.mdx +++ b/chapters/fr/chapter9/1.mdx @@ -19,7 +19,7 @@ Voici quelques exemples de démos d'apprentissage automatique construites avec G * Un modèle extractif de **réponse à une question** qui prend en entrée un paragraphe de contexte et une requête et produit une réponse et un score de probabilité (nous avons discuté de ce type de modèle [au chapitre 7](/course/fr/chapter7/7)) : - + * Un modèle de **suppression de l'arrière-plan** qui prend une image et la restitue avec l'arrière-plan supprimé : diff --git a/chapters/fr/chapter9/3.mdx b/chapters/fr/chapter9/3.mdx index 2117c1511..9c8e6dcca 100644 --- a/chapters/fr/chapter9/3.mdx +++ b/chapters/fr/chapter9/3.mdx @@ -22,12 +22,12 @@ Voyons un autre exemple, cette fois avec un composant `Audio`. ## Un exemple simple avec audio -Comme mentionné précédemment,*Gradio* fournit de nombreuses entrées et sorties différentes. +Comme mentionné précédemment, *Gradio* fournit de nombreuses entrées et sorties différentes. Construisons donc une `Interface` qui fonctionne avec l'audio. Dans cet exemple, nous allons construire une fonction audio-vers-audio qui prend un fichier audio et l'inverse simplement. -Nous utiliserons comme entrée le composant `Audio`. Lorsque vous utilisez le composant `Audio`, vous pouvez spécifier si vous voulez que la `source` de l'audio soit un fichier que l'utilisateur télécharge ou un microphone avec lequel l'utilisateur enregistre sa voix. Dans ce cas, nous allons choisir un "microphone". Juste pour le plaisir, nous allons ajouter une étiquette à notre `Audio` qui dit « *Speak here...* » (Parler ici). +Nous utiliserons comme entrée le composant `Audio`. Lorsque vous utilisez le composant `Audio`, vous pouvez spécifier si vous voulez que la `source` de l'audio soit un fichier que l'utilisateur télécharge ou un microphone avec lequel l'utilisateur enregistre sa voix. Dans ce cas, nous allons choisir un microphone. Juste pour le plaisir, nous allons ajouter une étiquette à notre `Audio` qui dit « *Speak here...* » (Parler ici). De plus, nous aimerions recevoir l'audio sous la forme d'un tableau numpy afin de pouvoir facilement l'inverser. Nous allons donc définir le `"type"` comme étant `"numpy"`, ce qui permet de passer les données d'entrée comme un *tuple* de (`sample_rate`, `data`) dans notre fonction. @@ -52,7 +52,7 @@ gr.Interface(reverse_audio, mic, "audio").launch() Le code ci-dessus produira une interface comme celle qui suit (si votre navigateur ne vous demande pas l'autorisation pour accéder au microphone, ouvrez la démo dans un onglet séparé). - + Vous devriez maintenant être capable d'enregistrer votre voix et de vous entendre parler à l'envers. Effrayant 👻 ! @@ -100,7 +100,7 @@ gr.Interface( -### La méthode `launch()`. +### La méthode `launch()` Jusqu'à présent, nous avons utilisé la méthode `launch()` pour lancer l'interface, mais nous n'avons pas vraiment discuté de ce qu'elle fait. @@ -114,7 +114,7 @@ Vous pouvez personnaliser le comportement de `launch()` à travers différents p Nous couvrirons le paramètre `share` plus en détail dans la section suivante ! -## ✏️ Appliquons-le ! +## ✏️ Appliquons ça ! Construisons une interface qui vous permette de faire la démonstration d'un modèle de **reconnaissance vocale**. Pour rendre la chose intéressante, nous accepterons *soit* une entrée micro, soit un fichier téléchargé. @@ -152,9 +152,8 @@ gr.Interface( Si votre navigateur ne vous demande pas l'autorisation pour accéder au microphone, ouvrez la démo dans un onglet séparé. - - + Voilà, c'est fait ! Vous pouvez maintenant utiliser cette interface pour transcrire de l'audio. Remarquez ici qu'en passant le paramètre `optional` à `True`, nous permettons à l'utilisateur de soit fournir un microphone ou un fichier audio (ou aucun des deux, mais cela retournera un message d'erreur). -Continuez pour voir comment partager votre interface avec d'autres ! \ No newline at end of file +Continuez pour voir comment partager votre interface avec d'autres ! diff --git a/chapters/fr/chapter9/4.mdx b/chapters/fr/chapter9/4.mdx index 64ffcd196..5e5f89510 100644 --- a/chapters/fr/chapter9/4.mdx +++ b/chapters/fr/chapter9/4.mdx @@ -31,7 +31,7 @@ The bot was trained to answer questions based on Rick and Morty dialogues. Ask R """ 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." -# Jetez un coup d'œil au [bot original Rick et Morty] (https://huggingface.co/spaces/kingabzpro/Rick_and_Morty_Bot) sur lequel cette démo est basée. +# Jetez un coup d'œil au [bot original Rick et Morty](https://huggingface.co/spaces/kingabzpro/Rick_and_Morty_Bot) sur lequel cette démo est basée. gr.Interface( fn=predict, @@ -69,9 +69,9 @@ Un lien de partage que vous pouvez passer à vos collègues est cool, mais comme -## ✏️ Let's apply it! +## ✏️ Appliquons ça ! -En utilisant ce que nous avons appris dans les sections précédentes, créons la démo de reconnaissance de croquis que nous avons décrit dans la [section un de ce chapitre] (/course/fr/chapter9/1). Ajoutons quelques personnalisations à notre interface et définissons `share=True` pour créer un lien public que nous pouvons faire circuler. +En utilisant ce que nous avons appris dans les sections précédentes, créons la démo de reconnaissance de croquis que nous avons décrit dans la [section un de ce chapitre](/course/fr/chapter9/1). Ajoutons quelques personnalisations à notre interface et définissons `share=True` pour créer un lien public que nous pouvons faire circuler. Nous pouvons charger les étiquettes depuis [class_names.txt](https://huggingface.co/spaces/dawood/Sketch-Recognition/blob/main/class_names.txt) et charger le modèle Pytorch pré-entraîné depuis [pytorch_model.bin](https://huggingface.co/spaces/dawood/Sketch-Recognition/blob/main/pytorch_model.bin). Téléchargez ces fichiers en suivant le lien et en cliquant sur « *download* » dans le coin supérieur gauche de l'aperçu du fichier. Regardons le code ci-dessous pour voir comment nous utilisons ces fichiers pour charger notre modèle et créer une fonction `predict()` : @@ -132,7 +132,7 @@ interface.launch(share=True) -Notice the `live=True` parameter in `Interface`, which means that the sketch demo makes a prediction every time someone draws on the sketchpad (no submit button!). +Remarquez le paramètre `live=True` dans `Interface`, qui signifie que la démo de sketchs fait une prédiction chaque fois que quelqu'un dessine sur le bloc (pas de bouton de soumission !). De plus, nous avons également défini l'argument `share=True` dans la méthode `launch()`. Cela créera un lien public que vous pourrez envoyer à n'importe qui ! Lorsque vous envoyez ce lien, l'utilisateur de l'autre côté peut essayer le modèle de reconnaissance de croquis. Pour réitérer, vous pouvez également héberger le modèle sur *Hugging Face Spaces*, ce qui nous permet d'intégrer la démo ci-dessus. diff --git a/chapters/fr/chapter9/5.mdx b/chapters/fr/chapter9/5.mdx index 18f1b9c67..a30853b9c 100644 --- a/chapters/fr/chapter9/5.mdx +++ b/chapters/fr/chapter9/5.mdx @@ -3,7 +3,8 @@ Pour vous rendre la vie encore plus facile, *Gradio* s'intègre directement avec *Hub* et *Spaces*. Vous pouvez charger des démos depuis le *Hub* et les *Spaces* avec seulement *une ligne de code*. -### Chargement de modèles depuis lle Hub d'Hugging Face +### Chargement de modèles depuis le Hub d'Hugging Face + Pour commencer, choisissez un des milliers de modèles qu'*Hugging Face* offre à travers le *Hub*, comme décrit dans le [chapitre 4](/course/fr/chapter4/2). En utilisant la méthode spéciale `Interface.load()`, vous passez `"model/"` (ou, de manière équivalente, `"huggingface/"`) suivi du nom du modèle. @@ -40,9 +41,10 @@ Le code ci-dessus produira l'interface ci-dessous : -Le chargement d'un modèle de cette manière utilise l'[API d'Inference] (https://huggingface.co/inference-api) de *Hugging Face* au lieu de charger le modèle en mémoire. C'est idéal pour les modèles énormes comme GPT-J ou T0pp qui nécessitent beaucoup de RAM. +Le chargement d'un modèle de cette manière utilise l'[API d'Inference](https://huggingface.co/inference-api) de *Hugging Face* au lieu de charger le modèle en mémoire. C'est idéal pour les modèles énormes comme GPT-J ou T0pp qui nécessitent beaucoup de RAM. ### Chargement depuis Hugging Face Spaces + Pour charger n'importe quel *Space* depuis le *Hub* et le recréer localement, vous pouvez passer `spaces/` à l'`Interface`, suivi du nom du *Space*. Vous vous souvenez de la démo de la section 1 qui supprime le fond d'une image ? Chargeons-la à partir de *Hugging Face Spaces* : @@ -61,6 +63,6 @@ gr.Interface.load( ).launch() ``` - + Maintenant que nous avons exploré quelques façons d'intégrer *Gradio* avec le *Hub*, jetons un coup d'oeil à certaines fonctionnalités avancées de la classe `Interface`. C'est le sujet de la prochaine section ! \ No newline at end of file diff --git a/chapters/fr/chapter9/6.mdx b/chapters/fr/chapter9/6.mdx index 6a3413fb9..06301e577 100644 --- a/chapters/fr/chapter9/6.mdx +++ b/chapters/fr/chapter9/6.mdx @@ -44,8 +44,7 @@ iface = gr.Interface( iface.launch() ``` - - + Remarquez comment l'état du composant de sortie persiste entre les soumissions. Remarque : vous pouvez transmettre une valeur par défaut au paramètre state, qui est utilisée comme valeur initiale de l'état. @@ -127,6 +126,6 @@ gr.Interface( ).launch(auth=("admin", "pass1234")) ``` - + Ceci conclut notre plongée dans la classe `Interface` de *Gradio*. Comme nous l'avons vu, cette classe permet de créer facilement des démos d'apprentissage automatique en quelques lignes de code Python. Cependant, vous voudrez parfois personnaliser votre démo en changeant la mise en page ou en enchaînant plusieurs fonctions de prédiction. Ne serait-il pas agréable de pouvoir diviser l'interface en blocs personnalisables ? Heureusement, c'est possible ! C'est le sujet de la dernière section. \ No newline at end of file diff --git a/chapters/fr/chapter9/7.mdx b/chapters/fr/chapter9/7.mdx index edf7c91df..c6cc7054f 100644 --- a/chapters/fr/chapter9/7.mdx +++ b/chapters/fr/chapter9/7.mdx @@ -56,7 +56,7 @@ Ce simple exemple ci-dessus introduit 4 concepts qui sous-tendent les *Blocks* : 1. Les *Blocks* vous permettent de construire des applications web qui combinent Markdown, HTML, boutons et composants interactifs simplement en instanciant des objets en Python dans un contexte `with gradio.Blocks`. -🙋Si vous n'êtes pas familier avec l'instruction `with` en Python, nous vous recommandons de consulter l'excellent [tutoriel](https://realpython.com/python-with-statement/) de Real Python. Revenez ici après l'avoir lu 🤗 +🙋Si vous n'êtes pas familier avec l'instruction `with` en Python, nous vous recommandons de consulter l'excellent tutoriel de Real Python. Revenez ici après l'avoir lu 🤗 L'ordre dans lequel vous instanciez les composants est important car chaque élément est restitué dans l'application Web dans l'ordre où il a été créé. (Les mises en page plus complexes sont abordées ci-dessous) diff --git a/chapters/fr/chapter9/8.mdx b/chapters/fr/chapter9/8.mdx index 1225f0eb3..ee6017d04 100644 --- a/chapters/fr/chapter9/8.mdx +++ b/chapters/fr/chapter9/8.mdx @@ -1,234 +1,17 @@ - - -# Quiz de fin de chapitre - -Testons ce que vous avez appris dans ce chapitre ! - -### 1. Que pouvez-vous faire avec Gradio ? - -share=True dans la méthode de lancement, vous pouvez générer un lien de partage à envoyer à tout le monde.", - correct: true - }, - { - text: "Déboguez votre modèle.", - explain: "L'un des avantages d'une démo Gradio est de pouvoir tester votre modèle avec des données réelles que vous pouvez modifier et observer les prédictions du modèle changer en temps réel, ce qui vous aide à déboguer votre modèle.", - correct: true - }, - { - text: "Entraîner votre modèle.", - explain: "Gradio est conçu pour être utilisé pour l'inférence, APRÈS que votre modèle a été entraîné.", - } - ]} -/> - -### 2. Gradio fonctionne UNIQUEMENT avec les modèles en PyTorch - -Gradio fonctionne avec les modèles Pytorch mais aussi pour tout type de modèle d'apprentissage automatique !" - }, - { - text: "False", - explain: "Gradio est indifférent au modèle ce qui signifie que vous pouvez créer une démo pour tout type de modèle d'apprentissage automatique.", - correct: true - } - ]} -/> - -### 3. D'où pouvez-vous lancer une démo Gradio ? - -Gradio fonctionne parfaitement avec votre IDE préféré.", - correct: true - }, - { - text: "De notebooks Google Colab", - explain: "Vous pouvez créer et lancer une démo dans votre notebook Google Colab.", - correct: true - }, - { - text: "De notebooks Jupyter", - explain: "Vous pouvez créer et lancer une démo dans votre notebook Jupyter.", - correct: true - } - ]} -/> - -### 4. Gradio est conçu principalement pour les modèles de NLP - -Gradio fonctionne avec pratiquement tous les types de données, pas seulement avec le NLP." - }, - { - text: "False", - explain: "Gradio fournit aux développeurs une bibliothèque de composants préconstruits pour pratiquement tous les types de données.", - correct: true - } - ]} -/> - -### 5. Parmi les fonctionnalités suivantes, lesquelles sont prises en charge par Gradio ? - -Gradio. Tout ce que vous devez faire est de passer une liste d'entrées et de sorties à leurs paramètres correspondants.", - correct: true - }, - { - text: "État pour la persistance des données.", - explain: "Gradio est capable d'ajouter un état à votre interface.", - correct: true - }, - { - text: "Authentification par nom d'utilisateur et mot de passe.", - explain: "Passez une liste de tuples de nom d'utilisateur/mot de passe à la méthode de lancement pour ajouter l'authentification.", - correct: true - }, - { - text: "Analyse automatique de l'utilisation de votre démo Gradio.", - explain: "Gradio ne fournit pas aux développeurs des analyses sur les personnes qui utilisent leurs démos." - }, - { - text: "Chargement d'un modèle à partir du Hub ou de Space.", - explain: "Charger n'importe quel modèle de Hugging Face en utilisant la méthode gr.Interface.load().", - correct: true - } - ]} -/> - -### 6. Lesquelles des méthodes suivantes sont valides pour charger un modèle à partir du Hub ou de Space ? - -gr.Interface.load('huggingface/{user}/{model_name}')", - explain: "Il s'agit d'une méthode valide de chargement d'un modèle à partir du Hub.", - correct: true - }, - { - text: "gr.Interface.load('model/{user}/{model_name}')", - explain: "Il s'agit d'une méthode valide de chargement d'un modèle à partir du Hub.", - correct: true - }, - { - text: "gr.Interface.load('demos/{user}/{model_name}')", - explain: "Vous ne pouvez pas charger un modèle en utilisant le préfixe demos." - }, - { - text: "gr.Interface.load('spaces/{user}/{model_name}')", - explain: "Il s'agit d'une méthode valide de chargement d'un modèle à partir de Space.", - correct: true - } - ]} -/> - -### 7. Sélectionnez toutes les étapes nécessaires pour ajouter un état à votre interface Gradio - -Gradio fournit un composant d'entrée et de sortie d'état pour persister les données.", - correct: true - } - ]} -/> - -### 8. Lesquels des éléments suivants sont des composants inclus dans la bibliothèque Gradio ? - -Textbox.", - explain: "Oui, vous pouvez créer des zones de texte avec le composant Textbox.", - correct: true - }, - { - text: "Graph.", - explain: "Il n'y a actuellement aucun composant Graph.", - }, - { - text: "Image.", - explain: "Oui, vous pouvez créer un widget de téléchargement d'images avec le composant Image.", - correct: true - }, - { - text: "Audio.", - explain: "Oui, vous pouvez créer un widget de téléchargement audio avec le composant Audio.", - correct: true - }, - ]} -/> - -### 9. Qu'est-ce que les `Blocks` vous permet de faire ? - -with gradio.Tabs(): pour ajouter des onglets pour plusieurs démos.", - correct: true - }, - { - text: "Attribuer des déclencheurs d'événements tels que clicked/changed/etc aux composants Blocks.", - explain: "Lorsque vous assignez un événement, vous passez trois paramètres : fn qui est la fonction qui doit être appelée, inputs qui est la (liste) des composants d'entrée, et outputs qui est la (liste) des composants de sortie qui doivent être appelés.", - correct: true - }, - { - text: "Déterminer automatiquement quel composant Blocks doit être interactif ou statique.", - explain: "En fonction des déclencheurs d'événements que vous définissez, Blocks détermine automatiquement si un composant doit accepter ou non les entrées de l'utilisateur..", - correct: true - }, - { - text: "Créer des démos en plusieurs étapes, c'est-à-dire vous permettre de réutiliser la sortie d'un composant comme entrée pour le suivant.", - explain: "Vous pouvez utiliser un composant pour l'entrée d'un déclencheur d'événement mais la sortie d'un autre.", - correct: true - }, - ]} -/> - -### 10. Vous pouvez partager un lien public vers une démo Blocks et accueillir une démo Blocks sur Space - -Interface, toutes les capacités de partage et d'hébergement sont les mêmes pour les démos basées sur Blocks !", - correct: true - }, - { - text: "False", - explain: "Tout comme Interface, toutes les capacités de partage et d'hébergement sont les mêmes pour les démos basées sur Blocks !", - correct: false - } - ]} -/> \ No newline at end of file +# Gradio, coché ! + +Ceci conclut le chapitre sur la construction de démos d'apprentissage automatique avec *Gradio*. Nous espérons que vous l'avez apprécié ! Pour récapituler, dans ce chapitre nous avons appris : + +- à créer des démos *Gradio* avec l'API `Interface` de haut niveau et comment configurer les différentes modalités d'entrée et de sortie, +- les différentes manières de partager les démos *Gradio*, à travers des liens temporaires et l'hébergement sur [*Hugging Face Spaces*](https://huggingface.co/spaces), +- comment intégrer les démos *Gradio* avec le *Hub* et *Spaces*, +- des fonctionnalités avancées comme le stockage de l'état dans une démo ou l'authentification, +- comment avoir un contrôle total sur le flux de données et la mise en page de votre démo avec les *Blocks*. + +Si vous souhaitez tester votre compréhension des concepts abordés dans ce chapitre, consultez le quiz dans la section suivante ! + +## La Gradio blocks party 🥳 + +Si vous voulez mettre à profit les connaissances de ce chapitre, venez rejoindre la *Gradio blocks party* ! Il s'agit d'un événement communautaire organisé par Hugging Face du **16 au 31 mai**. Au cours de cet événement, vous construirez des démos d'apprentissage automatique avec *Gradio* et vous pourrez gagner des cadeaux et des prix de Hugging Face ! + +Consultez la [description de l'événement](https://github.com/AK391/community-events/blob/main/gradio-blocks/README.md) pour savoir comment participer. Nous sommes impatients de voir ce que vous allez construire 🤗 ! \ No newline at end of file diff --git a/chapters/fr/chapter9/9.mdx b/chapters/fr/chapter9/9.mdx new file mode 100644 index 000000000..2b6e859a2 --- /dev/null +++ b/chapters/fr/chapter9/9.mdx @@ -0,0 +1,233 @@ + + +# Quiz de fin de chapitre + +Testons ce que vous avez appris dans ce chapitre ! + +### 1. Que pouvez-vous faire avec Gradio ? + +share=True dans la méthode de lancement, vous pouvez générer un lien de partage à envoyer à tout le monde.", + correct: true + }, + { + text: "Déboguez votre modèle.", + explain: "L'un des avantages d'une démo Gradio est de pouvoir tester votre modèle avec des données réelles que vous pouvez modifier et observer les prédictions du modèle changer en temps réel, ce qui vous aide à déboguer votre modèle.", + correct: true + }, + { + text: "Entraîner votre modèle.", + explain: "Gradio est conçu pour être utilisé pour l'inférence, APRÈS que votre modèle a été entraîné.", + } + ]} +/> + +### 2. Gradio fonctionne UNIQUEMENT avec les modèles en PyTorch + +Gradio fonctionne avec les modèles Pytorch mais aussi pour tout type de modèle d'apprentissage automatique !" + }, + { + text: "Faux", + explain: "Gradio est indifférent au modèle ce qui signifie que vous pouvez créer une démo pour tout type de modèle d'apprentissage automatique.", + correct: true + } + ]} +/> + +### 3. D'où pouvez-vous lancer une démo Gradio ? + +Gradio fonctionne parfaitement avec votre IDE préféré.", + correct: true + }, + { + text: "De notebooks Google Colab", + explain: "Vous pouvez créer et lancer une démo dans votre notebook Google Colab.", + correct: true + }, + { + text: "De notebooks Jupyter", + explain: "Vous pouvez créer et lancer une démo dans votre notebook Jupyter.", + correct: true + } + ]} +/> + +### 4. Gradio est conçu principalement pour les modèles de NLP + +Gradio fonctionne avec pratiquement tous les types de données, pas seulement avec le NLP." + }, + { + text: "Faux", + explain: "Gradio fournit aux développeurs une bibliothèque de composants préconstruits pour pratiquement tous les types de données.", + correct: true + } + ]} +/> + +### 5. Parmi les fonctionnalités suivantes, lesquelles sont prises en charge par Gradio ? + +Gradio. Tout ce que vous devez faire est de passer une liste d'entrées et de sorties à leurs paramètres correspondants.", + correct: true + }, + { + text: "État pour la persistance des données.", + explain: "Gradio est capable d'ajouter un état à votre interface.", + correct: true + }, + { + text: "Authentification par nom d'utilisateur et mot de passe.", + explain: "Passez une liste de tuples de nom d'utilisateur/mot de passe à la méthode de lancement pour ajouter l'authentification.", + correct: true + }, + { + text: "Analyse automatique de l'utilisation de votre démo Gradio.", + explain: "Gradio ne fournit pas aux développeurs des analyses sur les personnes qui utilisent leurs démos." + }, + { + text: "Chargement d'un modèle à partir du Hub ou de Space.", + explain: "Charger n'importe quel modèle de Hugging Face en utilisant la méthode gr.Interface.load().", + correct: true + } + ]} +/> + +### 6. Lesquelles des méthodes suivantes sont valides pour charger un modèle à partir du Hub ou de Space ? + +gr.Interface.load('huggingface/{user}/{model_name}')", + explain: "Il s'agit d'une méthode valide de chargement d'un modèle à partir du Hub.", + correct: true + }, + { + text: "gr.Interface.load('model/{user}/{model_name}')", + explain: "Il s'agit d'une méthode valide de chargement d'un modèle à partir du Hub.", + correct: true + }, + { + text: "gr.Interface.load('demos/{user}/{model_name}')", + explain: "Vous ne pouvez pas charger un modèle en utilisant le préfixe demos." + }, + { + text: "gr.Interface.load('spaces/{user}/{model_name}')", + explain: "Il s'agit d'une méthode valide de chargement d'un modèle à partir de Space.", + correct: true + } + ]} +/> + +### 7. Sélectionnez toutes les étapes nécessaires pour ajouter un état à votre interface Gradio + +Gradio fournit un composant d'entrée et de sortie d'état pour persister les données.", + correct: true + } + ]} +/> + +### 8. Lesquels des éléments suivants sont des composants inclus dans la bibliothèque Gradio ? + +Textbox.", + explain: "Oui, vous pouvez créer des zones de texte avec le composant Textbox.", + correct: true + }, + { + text: "Graph.", + explain: "Il n'y a actuellement aucun composant Graph.", + }, + { + text: "Image.", + explain: "Oui, vous pouvez créer un widget de téléchargement d'images avec le composant Image.", + correct: true + }, + { + text: "Audio.", + explain: "Oui, vous pouvez créer un widget de téléchargement audio avec le composant Audio.", + correct: true + }, + ]} +/> + +### 9. Qu'est-ce que les `Blocks` vous permet de faire ? + +with gradio.Tabs(): pour ajouter des onglets pour plusieurs démos.", + correct: true + }, + { + text: "Attribuer des déclencheurs d'événements tels que clicked/changed/etc aux composants Blocks.", + explain: "Lorsque vous assignez un événement, vous passez trois paramètres : fn qui est la fonction qui doit être appelée, inputs qui est la (liste) des composants d'entrée, et outputs qui est la (liste) des composants de sortie qui doivent être appelés.", + correct: true + }, + { + text: "Déterminer automatiquement quel composant Blocks doit être interactif ou statique.", + explain: "En fonction des déclencheurs d'événements que vous définissez, Blocks détermine automatiquement si un composant doit accepter ou non les entrées de l'utilisateur..", + correct: true + }, + { + text: "Créer des démos en plusieurs étapes, c'est-à-dire vous permettre de réutiliser la sortie d'un composant comme entrée pour le suivant.", + explain: "Vous pouvez utiliser un composant pour l'entrée d'un déclencheur d'événement mais la sortie d'un autre.", + correct: true + }, + ]} +/> + +### 10. Vous pouvez partager un lien public vers une démo Blocks et accueillir une démo Blocks sur Space + +Interface, toutes les capacités de partage et d'hébergement sont les mêmes pour les démos basées sur Blocks !", + correct: true + }, + { + text: "Faux", + explain: "Tout comme Interface, toutes les capacités de partage et d'hébergement sont les mêmes pour les démos basées sur Blocks !", + } + ]} +/> \ No newline at end of file diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index dfa771b37..156a227f1 100644 --- a/chapters/pt/_toctree.yml +++ b/chapters/pt/_toctree.yml @@ -9,6 +9,7 @@ title: Introdução - local: chapter1/2 title: Processamento de Linguagem Natural + - title: 2. Usando 🤗 Transformers sections: - local: chapter2/1 @@ -34,4 +35,6 @@ - local: chapter4/1 title: O Hugging Face Hub - local: chapter4/2 - title: Usando modelos pré-treinados \ No newline at end of file + title: Usando modelos pré-treinados + - local: chapter4/3 + title: Compartilhando modelos pré-treinados diff --git a/chapters/pt/chapter4/3.mdx b/chapters/pt/chapter4/3.mdx new file mode 100644 index 000000000..d0416b617 --- /dev/null +++ b/chapters/pt/chapter4/3.mdx @@ -0,0 +1,641 @@ + + +# Compartilhando modelos pré-treinados + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Nas etapas abaixo, veremos as maneiras mais fáceis de compartilhar modelos pré-treinados para o Hub 🤗. Há ferramentas e utilitários disponíveis que facilitam o compartilhamento e atualização de modelos diretamente no Hub, que exploraremos a seguir. + + + +Encorajamos todos os usuários que treinam modelos a contribuir compartilhando-os com a comunidade - compartilhar modelos, mesmo quando treinados em conjuntos de dados muito específicos, ajudará outros, economizando tempo e recursos e fornecendo acesso a artefatos úteis treinados. Por sua vez, você pode se beneficiar do trabalho que outros realizaram! + +Há três maneiras de se criar novos repositórios modelo: + +- Usando a API`push_to_hub` +- Usando a biblioteca Python `huggingface_hub` +- Usando a interface web + +Uma vez criado um repositório, você pode fazer o upload de arquivos para ele via git e git-lfs. Nós o acompanharemos na criação de repositórios modelo e no upload de arquivos para eles nas seções seguintes. + + +## Usando a API`push_to_hub` + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +A maneira mais simples de carregar arquivos no Hub é usando a API `push_to_hub`. + +Antes de ir adiante, você precisará gerar um token de autenticação para que a API `huggingface_hub` saiba quem você é e a que namespaces você tem acesso de escrita. Certifique-se de estar em um ambiente onde você tenha `transformers` instalado (ver [Setup](/course/chapter0)). Se você estiver em um notebook, você pode utilizar a seguinte função para fazer o login: + + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Em um terminal, você pode rodar: + +```bash +huggingface-cli login +``` + +Em ambos os casos, você será solicitado seu nome de usuário e senha, que são os mesmos que você usa para fazer o login no Hub. Se você ainda não tem um perfil do Hub, você deve criar um [aqui](https://huggingface.co/join). + +Ótimo! Agora você tem seu token de autenticação armazenado em sua pasta de cache. Vamos criar alguns repositórios! + +{#if fw === 'pt'} + +Se você usou a API do `Trainer` para treinar um modelo, a maneira mais fácil de carregá-lo no Hub é definir `push_to_hub=True` quando você definir seus `TrainingArguments`: + +```py +from transformers import TrainingArguments + +training_args = TrainingArguments( + "bert-finetuned-mrpc", save_strategy="epoch", push_to_hub=True +) +``` + +Quando você chama `trainer.train()`, o `Trainer` então carregará seu modelo no Hub cada vez que ele for salvo (aqui a cada época) em um repositório em seu namespace. Esse repositório será nomeado como o diretório de saída que você escolheu (aqui `bert-finetuned-mrpc`), mas você pode escolher um nome diferente com `hub_model_id = "a_diferent_name"`. + +Para enviar seu modelo para uma organização da qual você é membro, basta passá-lo com `hub_model_id = "my_organization/my_repo_name"`. + +Uma vez terminado seu treinamento, você deve fazer um último `trainer.push_to_hub()` para carregar a última versão de seu modelo. Ele também gerará um cartão modelo com todos os metadados relevantes, relatando os hiperparâmetros utilizados e os resultados da avaliação! Aqui está um exemplo do conteúdo que você pode encontrar em um cartão modelo deste tipo: + +
+ An example of an auto-generated model card. +
+ +{:else} + + +Se você estiver utilizando Keras para treinar seu modelo, a maneira mais fácil de carregá-lo no Hub é passar um `PushToHubCallback` quando você chama de `model.fit()`: + +```py +from transformers import PushToHubCallback + +callback = PushToHubCallback( + "bert-finetuned-mrpc", save_strategy="epoch", tokenizer=tokenizer +) +``` + +Então você deve adicionar `callbacks=[callback]` em sua chamada de `model.fit()`. A chamada de retorno será então enviada ao Hub cada vez que o modelo for salvo (aqui a cada época) em um repositório em seu namespace. Esse repositório será nomeado como o diretório de saída que você escolheu (aqui `bert-finetuned-mrpc`), mas você pode escolher um nome diferente com `hub_model_id = "a_diferent_name"`. + +Para enviar seu modelo para uma organização da qual você é membro, basta passá-lo com `hub_model_id = "my_organization/my_repo_name"`. + +{/if} + +Em um nível inferior, o acesso ao Model Hub pode ser feito diretamente nos modelos, tokenizers e objetos de configuração através de seu método `push_to_hub()`. Este método cuida da criação do repositório e empurra os arquivos modelo e tokenizer diretamente para o repositório. Não é necessário nenhum tratamento manual, ao contrário do que acontece com a API, veremos abaixo. + +Para se ter uma idéia de como funciona, vamos primeiro inicializar um modelo e um tokenizer: + +{#if fw === 'pt'} +```py +from transformers import AutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = AutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` +{:else} +```py +from transformers import TFAutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = TFAutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` +{/if} + +Você é livre para fazer o que quiser com elas - adicionar fichas ao tokenizer, treinar o modelo, afinar o modelo. Quando você estiver satisfeito com o modelo, pesos e tokenizer resultantes, você pode aproveitar o método `push_to_hub()` diretamente disponível no objeto `model`: + +```py +model.push_to_hub("dummy-model") +``` + +Isto criará o novo repositório `dummy-model` em seu perfil, e o preencherá com seus arquivos de modelos. +Faça o mesmo com o tokenizer, para que todos os arquivos estejam agora disponíveis neste repositório: + +```py +tokenizer.push_to_hub("dummy-model") +``` + +Se você pertence a uma organização, basta especificar o argumento `organization` a ser carregado no namespace dessa organização: + +```py +tokenizer.push_to_hub("dummy-model", organization="huggingface") +``` + +Se você desejar utilizar um toke específica do Hugging Face, você é livre para especificá-la também para o método `push_to_hub()`: + +```py +tokenizer.push_to_hub("dummy-model", organization="huggingface", use_auth_token="") +``` + +Agora vá até o Model Hub para encontrar seu modelo recém-carregado: *https://huggingface.co/user-or-organization/dummy-model*. + +Clique na aba "Files and versions", e você deve ver os arquivos visíveis na seguinte captura de tela: + +{#if fw === 'pt'} +
+Dummy model containing both the tokenizer and model files. +
+{:else} +
+Dummy model containing both the tokenizer and model files. +
+{/if} + + + +✏️ **Teste-o!** Pegue o modelo e o tokenizer associados ao checkpoint `bert-base-cased` e carregue-os para um repo em seu namespace utilizando o método `push_to_hub()`. Verifique novamente se o repo aparece corretamente em sua página antes de excluí-lo. + + + +Como você já viu, o método `push_to_hub()` aceita vários argumentos, tornando possível carregar para um repositório específico ou espaço de nomes de organizações, ou utilizar um token API diferente. Recomendamos que você dê uma olhada na especificação do método disponível diretamente na documentação [🤗 Transformers documentation](https://huggingface.co/transformers/model_sharing.html) para ter uma idéia do que é possível. + +O método `push_to_hub()` é apoiado pelo pacote [`huggingface_hub`](https://github.com/huggingface/huggingface_hub) Python, que oferece uma API direta para o Hub Hugging Face. Está integrado ao 🤗 Transformers e várias outras bibliotecas de aprendizagem de máquinas, como [`allenlp`](https://github.com/allenai/allennlp). Embora nos concentremos na integração do 🤗 Transformers neste capítulo, integrá-lo em seu próprio código ou biblioteca é simples. + +Salte para a última seção para ver como carregar arquivos em seu repositório recém-criado! + +## Usando a biblioteca Python `huggingface_hub` + +A biblioteca Python`huggingface_hub` é um pacote que oferece um conjunto de ferramentas para os hubs do modelo e dos conjuntos de dados. Ela fornece métodos e classes simples para tarefas comuns como obter informações sobre os repositórios no centro e gerenciá-los. Ele fornece APIs simples que funcionam em cima do git para gerenciar o conteúdo desses repositórios e para integrar o Hub em seus projetos e bibliotecas. + +Da mesma forma que a utilização da API `push_to_hub`, isto exigirá que você tenha seu token API salvo em seu cache. Para fazer isso, você precisará utilizar o comando `login` do CLI, como mencionado na seção anterior (mais uma vez, certifique-se de utilizar antes desses comandos o caracter `!` se estiver rodando no Google Colab): + +```bash +huggingface-cli login +``` + +O pacote `huggingface_hub` oferece vários métodos e classes que são úteis para nosso propósito. Em primeiro lugar, existem alguns métodos para gerenciar a criação de repositórios, exclusão, e outros: + +```python no-format +from huggingface_hub import ( + # Gestão de usuários + login, + logout, + whoami, + + # Criação e gestão de repositório + create_repo, + delete_repo, + update_repo_visibility, + + #E alguns métodos para recuperar/trocar informações sobre o conteúdo + list_models, + list_datasets, + list_metrics, + list_repo_files, + upload_file, + delete_file, +) +``` + + +Além disso, oferece uma poderosa classe `Repository` para gerenciar um repositório local. Vamos explorar esses métodos e essa classe na próxima seção para entender como aproveitá-los. + +O método `create_repo` pode ser utilizado para criar um novo repositório no centro: + +```py +from huggingface_hub import create_repo + +create_repo("dummy-model") +``` + +Isto criará o repositório `dummy-model` em seu namespace. Se desejar, você pode especificar a que organização o repositório deve pertencer utilizando o argumento `organization`: + +```py +from huggingface_hub import create_repo + +create_repo("dummy-model", organization="huggingface") +``` + + +Isto criará o repositório `dummy-model` no espaço de nomes `huggingface`, assumindo que você pertença a essa organização. +Outros argumentos que podem ser úteis são: + +- `private`, a fim de especificar se o repositório deve ser visível de outros ou não. +- `token`,se você gostaria de substituir o token armazenada em seu cache por uma determinada token. +- `repo_type`, se você gostaria de criar um "`dataset` ou um "espaço" em vez de um modelo. Os valores aceitos são `"dataset"` e `"space"`. + +Uma vez criado o repositório, devemos adicionar arquivos a ele! Salte para a próxima seção para ver as três maneiras como isto pode ser tratado. + + +## Usando a interface web + +A interface web oferece ferramentas para gerenciar os repositórios diretamente no Hub. Usando a interface, você pode facilmente criar repositórios, adicionar arquivos (mesmo grandes!), explorar modelos, visualizar diffs, e muito mais. + +Para criar um novo repositório, visite [huggingface.co/novo](https://huggingface.co/new): + +
+Page showcasing the model used for the creation of a new model repository. +
+ +Primeiro, especifique o proprietário do repositório: este pode ser você ou qualquer uma das organizações às quais você está afiliado. Se você escolher uma organização, o modelo será apresentado na página da organização e cada membro da organização terá a capacidade de contribuir com o repositório. + +A seguir, digite o nome do seu modelo. Este também será o nome do repositório. Finalmente, você pode especificar se deseja que seu modelo seja público ou privado. Os modelos privados não podem ser encontrados publicamente. + +Depois de criar seu repositório de modelos, você deve ver uma página como esta: + +
+An empty model page after creating a new repository. +
+ +Aqui é onde seu modelo será hospedado. Para começar a preenchê-lo, você pode adicionar um arquivo README diretamente da interface web. + +
+The README file showing the Markdown capabilities. +
+ +O arquivo README está em Markdown - sinta-se à vontade para ficar louco com ele! A terceira parte deste capítulo é dedicada à construção de um modelo de cartão. Estes são de extrema importância para trazer valor ao seu modelo, pois estão onde você diz aos outros o que ele pode fazer. + +Se você olhar a aba "Files and versions", você verá que ainda não há muitos arquivos - apenas o *README.md* que você acabou de criar e o arquivo *.gitattributes* que mantém o controle de arquivos grandes. + +
+The 'Files and versions' tab only shows the .gitattributes and README.md files. +
+ +A seguir, veremos como adicionar alguns novos arquivos. + +## Fazendo upload dos arquivos de modelos + +O sistema para gerenciar arquivos no Hub Hugging Face Hub é baseado no git para arquivos regulares, e git-lfs (que significa [Git Large File Storage](https://git-lfs.github.com/)) para arquivos maiores. + +Na seção seguinte, passamos por três maneiras diferentes de carregar arquivos no Hub: através de `huggingface_hub` e através de comandos de git. + +### A abordagem: `upload_file` + +A utilização do `upload_file` não requer que git e git-lfs sejam instalados em seu sistema. Ele empurra os arquivos diretamente para o Hub 🤗 utilizando solicitações HTTP POST. Uma limitação desta abordagem é que ele não lida com arquivos maiores que 5GB de tamanho. +Se seus arquivos forem maiores que 5GB, por favor, siga os dois outros métodos detalhados abaixo. + +A API pode ser usada da seguinte forma: + +```py +from huggingface_hub import upload_file + +upload_file( + "/config.json", + path_in_repo="config.json", + repo_id="/dummy-model", +) +``` + +Isto fará o upload do arquivo `config.json` disponível em `` para a raiz do repositório como `config.json`, para o repositório `dummy-model`. +Outros argumentos que podem ser úteis são: + +- `token`, se você gostaria de substituir o token armazenado em seu cache por um determinado token. +- `repo_type`, se você gostaria de carregar em um `dataset` ou em um `espaço` em vez de um modelo. Os valores aceitos são `"dataset"` e `"space"`. + +### A classe: `Repository` + +A classe `Repository` gerencia um repositório local de forma idiota. Ele resume a maioria dos pontos de dor que se pode ter com o git para fornecer todas as características que necessitamos. + +A utilização desta classe requer ter git e git-lfs instalados, portanto certifique-se de ter o git-lfs instalado (veja [aqui](https://git-lfs.github.com/) para instruções de instalação) e configure-o antes de começar. + +Para começar a brincar com o repositório que acabamos de criar, podemos começar inicializando-o em uma pasta local através da clonagem do repositório remoto: + +```py +from huggingface_hub import Repository + +repo = Repository("", clone_from="/dummy-model") +``` + +Isto criou a pasta `` em nosso diretório de trabalho. Esta pasta contém apenas o arquivo `.gitattributes`, pois este é o único arquivo criado ao instanciar o repositório através do `create_repo`. + +A partir deste ponto, podemos aproveitar vários dos métodos tradicionais do gitattributes: + +```py +repo.git_pull() +repo.git_add() +repo.git_commit() +repo.git_push() +repo.git_tag() +``` + +E outros! Recomendamos dar uma olhada na documentação `Repository` disponível [aqui](https://github.com/huggingface/huggingface_hub/tree/main/src/huggingface_hub#advanced-programmatic-repository-management) para uma visão geral de todos os métodos disponíveis. + +No momento, temos um modelo e um tokenizer que gostaríamos de empurrar para o centro. Clonamos com sucesso o repositório, portanto, podemos salvar os arquivos dentro desse repositório. + +Primeiro nos certificamos de que nosso clone local esteja atualizado, puxando as últimas mudanças: + +```py +repo.git_pull() +``` + +Uma vez feito isso, salvamos os arquivos do modelo e do tokenizer: + +```py +model.save_pretrained("") +tokenizer.save_pretrained("") +``` + +O `` agora contém todos os modelos e arquivos de fichas. Seguimos o fluxo de trabalho habitual do git, adicionando arquivos à área de encenação, comprometendo-os e empurrando-os para o centro: + +```py +repo.git_add() +repo.git_commit("Add model and tokenizer files") +repo.git_push() +``` + +Parabéns! Você acabou de empurrar seus primeiros arquivos para o centro. + +### A abordagem: `baseada em git` + +Esta é a própria abordagem do barebone para carregar arquivos: faremos isso com git e git-lfs diretamente. A maior parte da dificuldade é abstraída por abordagens anteriores, mas há algumas advertências com o seguinte método, então seguiremos um caso de uso mais complexo. + +O uso desta classe requer ter git e git-lfs instalados, portanto, certifique-se de ter [git-lfs](https://git-lfs.github.com/) instalado (veja aqui as instruções de instalação) e configurado antes de começar. + +Primeiro comece inicializando o git-lfs: + +```bash +git lfs install +``` + +```bash +Updated git hooks. +Git LFS initialized. +``` + +Uma vez feito isso, o primeiro passo é clonar seu repositório modelo: + +```bash +git clone https://huggingface.co// +``` + +Meu nome de usuário é `lysandre` e já utilizei o nome modelo `dummy`, então para mim o comando acaba parecendo o seguinte: + +``` +git clone https://huggingface.co/lysandre/dummy +``` + +Agora tenho uma pasta com o nome *dummy* em meu diretório de trabalho. Eu posso `cd` dentro da pasta e dar uma olhada no conteúdo: + +```bash +cd dummy && ls +``` + +```bash +README.md +``` + +Se você acabou de criar seu repositório utilizando o método `create_repo` do Hugging Face Hub, esta pasta deve conter apenas um arquivo oculto `.gitattributes`. Se você seguiu as instruções da seção anterior para criar um repositório utilizando a interface web, a pasta deve conter um único arquivo *README.md* ao lado do arquivo oculto `.gitattributes`, como mostrado aqui. + +Adicionar um arquivo de tamanho normal, como um arquivo de configuração, um arquivo de vocabulário, ou basicamente qualquer arquivo sob alguns megabytes, é feito exatamente como se faria em qualquer sistema baseado no gitattributes. Entretanto, arquivos maiores devem ser registrados através do git-lfs a fim de empurrá-los para *huggingface.co*. + +Vamos voltar a Python para gerar um modelo e tokenizer que gostaríamos de comprometer com nosso repositório dummy: + +{#if fw === 'pt'} +```py +from transformers import AutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = AutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + +# Do whatever with the model, train it, fine-tune it... + +model.save_pretrained("") +tokenizer.save_pretrained("") +``` +{:else} +```py +from transformers import TFAutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = TFAutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + +# Do whatever with the model, train it, fine-tune it... + +model.save_pretrained("") +tokenizer.save_pretrained("") +``` +{/if} + +Agora que salvamos alguns artefatos de modelo e tokenizer, vamos dar outra olhada na pasta *dummy*: + +```bash +ls +``` + +{#if fw === 'pt'} +```bash +config.json pytorch_model.bin README.md sentencepiece.bpe.model special_tokens_map.json tokenizer_config.json tokenizer.json +``` + +Se você olhar para os tamanhos de arquivo (por exemplo, com `ls -lh`), você deve ver que o arquivo de estado do modelo (*pytorch_model.bin*) é o único outlier, com mais de 400 MB. + +{:else} +```bash +config.json README.md sentencepiece.bpe.model special_tokens_map.json tf_model.h5 tokenizer_config.json tokenizer.json +``` + +Se você olhar para os tamanhos de arquivo (por exemplo, com `ls -lh`), você deve ver que o arquivo de estado do modelo (*t5_model.h5*) é o único outlier, com mais de 400 MB. + +{/if} + + +✏️ Ao criar o repositório a partir da interface web, o arquivo *.gitattributes* é automaticamente configurado para considerar arquivos com certas extensões, como *.bin* e *.h5*, como arquivos grandes, e o git-lfs os rastreará sem nenhuma configuração necessária em seu lado. + + +Agora podemos ir em frente e proceder como normalmente faríamos com os repositórios tradicionais da Git. Podemos adicionar todos os arquivos ao ambiente de encenação do Git utilizando o comando `git add`: + +```bash +git add . +``` + +Podemos, então, dar uma olhada nos arquivos que estão atualmente em fase de montagem: + +```bash +git status +``` + +{#if fw === 'pt'} +```bash +On branch main +Your branch is up to date with 'origin/main'. + +Changes to be committed: + (use "git restore --staged ..." to unstage) + modified: .gitattributes + new file: config.json + new file: pytorch_model.bin + new file: sentencepiece.bpe.model + new file: special_tokens_map.json + new file: tokenizer.json + new file: tokenizer_config.json +``` +{:else} +```bash +On branch main +Your branch is up to date with 'origin/main'. + +Changes to be committed: + (use "git restore --staged ..." to unstage) + modified: .gitattributes + new file: config.json + new file: sentencepiece.bpe.model + new file: special_tokens_map.json + new file: tf_model.h5 + new file: tokenizer.json + new file: tokenizer_config.json +``` +{/if} + +Da mesma forma, podemos ter certeza de que o git-lfs está rastreando os arquivos corretos, utilizando seu comando `status`: + +```bash +git lfs status +``` + +{#if fw === 'pt'} +```bash +On branch main +Objects to be pushed to origin/main: + + +Objects to be committed: + + config.json (Git: bc20ff2) + pytorch_model.bin (LFS: 35686c2) + sentencepiece.bpe.model (LFS: 988bc5a) + special_tokens_map.json (Git: cb23931) + tokenizer.json (Git: 851ff3e) + tokenizer_config.json (Git: f0f7783) + +Objects not staged for commit: + + +``` + +Podemos ver que todos os arquivos têm `Git` como manipulador, exceto *pytorch_model.bin* e *sentencepiece.bpe.model*, que têm `LFS`. Ótimo! + +{:else} +```bash +On branch main +Objects to be pushed to origin/main: + + +Objects to be committed: + + config.json (Git: bc20ff2) + sentencepiece.bpe.model (LFS: 988bc5a) + special_tokens_map.json (Git: cb23931) + tf_model.h5 (LFS: 86fce29) + tokenizer.json (Git: 851ff3e) + tokenizer_config.json (Git: f0f7783) + +Objects not staged for commit: + + +``` + +We can see that all files have `Git` as a handler, except *t5_model.h5*, which has `LFS`. Great! + +{/if} + +Vamos prosseguir para as etapas finais, comprometendo-nos e empurrando para o repositório remoto *huggingface.co*: + +```bash +git commit -m "First model version" +``` + +{#if fw === 'pt'} +```bash +[main b08aab1] First model version + 7 files changed, 29027 insertions(+) + 6 files changed, 36 insertions(+) + create mode 100644 config.json + create mode 100644 pytorch_model.bin + create mode 100644 sentencepiece.bpe.model + create mode 100644 special_tokens_map.json + create mode 100644 tokenizer.json + create mode 100644 tokenizer_config.json +``` +{:else} +```bash +[main b08aab1] First model version + 6 files changed, 36 insertions(+) + create mode 100644 config.json + create mode 100644 sentencepiece.bpe.model + create mode 100644 special_tokens_map.json + create mode 100644 tf_model.h5 + create mode 100644 tokenizer.json + create mode 100644 tokenizer_config.json +``` +{/if} + +O push pode levar um pouco de tempo, dependendo da velocidade de sua conexão à Internet e do tamanho de seus arquivos: + +```bash +git push +``` + +```bash +Uploading LFS objects: 100% (1/1), 433 MB | 1.3 MB/s, done. +Enumerating objects: 11, done. +Counting objects: 100% (11/11), done. +Delta compression using up to 12 threads +Compressing objects: 100% (9/9), done. +Writing objects: 100% (9/9), 288.27 KiB | 6.27 MiB/s, done. +Total 9 (delta 1), reused 0 (delta 0), pack-reused 0 +To https://huggingface.co/lysandre/dummy + 891b41d..b08aab1 main -> main +``` + +{#if fw === 'pt'} +Se dermos uma olhada no repositório modelo quando este estiver terminado, podemos ver todos os arquivos recentemente adicionados: + +
+The 'Files and versions' tab now contains all the recently uploaded files. +
+ +A IU permite que você explore os arquivos modelo e os commits e veja as diferenças introduzidas por cada commit: + +
+The diff introduced by the recent commit. +
+{:else} +Se dermos uma olhada no repositório modelo quando este estiver terminado, podemos ver todos os arquivos recentemente adicionados: + +
+The 'Files and versions' tab now contains all the recently uploaded files. +
+ +A IU permite que você explore os arquivos modelo e os commits e veja as diferenças introduzidas por cada commit: + +
+The diff introduced by the recent commit. +
+{/if} From bd0e7936628fd1f0023703d0eb4edb05bbd25cb0 Mon Sep 17 00:00:00 2001 From: lewtun Date: Tue, 24 May 2022 21:44:49 +0200 Subject: [PATCH 16/51] Bump release 12 (#209) --- README.md | 2 +- chapters/fa/TRANSLATING.txt | 3 + chapters/fa/_toctree.yml | 7 + chapters/fa/chapter2/1.mdx | 24 +- chapters/fa/chapter2/2.mdx | 87 +++--- chapters/fa/chapter3/1.mdx | 27 ++ chapters/fa/chapter3/2.mdx | 501 ++++++++++++++++++++++++++++++++++ chapters/fa/glossary/1.mdx | 33 ++- chapters/hi/_toctree.yml | 21 +- chapters/hi/chapter0/1.mdx | 8 +- chapters/hi/chapter1/2.mdx | 20 ++ chapters/hi/chapter3/1.mdx | 21 ++ chapters/hi/chapter3/2.mdx | 381 ++++++++++++++++++++++++++ chapters/hi/chapter3/3.mdx | 172 ++++++++++++ chapters/hi/chapter3/3_tf.mdx | 199 ++++++++++++++ chapters/hi/chapter3/4.mdx | 359 ++++++++++++++++++++++++ chapters/hi/chapter3/5.mdx | 20 ++ chapters/hi/chapter3/6.mdx | 296 ++++++++++++++++++++ chapters/pt/_toctree.yml | 14 + chapters/pt/chapter4/4.mdx | 82 ++++++ chapters/pt/chapter4/5.mdx | 7 + chapters/pt/chapter4/6.mdx | 223 +++++++++++++++ chapters/pt/chapter5/1.mdx | 18 ++ chapters/pt/chapter5/2.mdx | 167 ++++++++++++ chapters/ru/_toctree.yml | 2 + chapters/ru/chapter3/4.mdx | 363 ++++++++++++++++++++++++ chapters/tr/_toctree.yml | 6 +- chapters/tr/chapter3/1.mdx | 21 ++ 28 files changed, 3017 insertions(+), 67 deletions(-) create mode 100644 chapters/fa/chapter3/1.mdx create mode 100644 chapters/fa/chapter3/2.mdx create mode 100644 chapters/hi/chapter1/2.mdx create mode 100644 chapters/hi/chapter3/1.mdx create mode 100644 chapters/hi/chapter3/2.mdx create mode 100644 chapters/hi/chapter3/3.mdx create mode 100644 chapters/hi/chapter3/3_tf.mdx create mode 100644 chapters/hi/chapter3/4.mdx create mode 100644 chapters/hi/chapter3/5.mdx create mode 100644 chapters/hi/chapter3/6.mdx create mode 100644 chapters/pt/chapter4/4.mdx create mode 100644 chapters/pt/chapter4/5.mdx create mode 100644 chapters/pt/chapter4/6.mdx create mode 100644 chapters/pt/chapter5/1.mdx create mode 100644 chapters/pt/chapter5/2.mdx create mode 100644 chapters/ru/chapter3/4.mdx create mode 100644 chapters/tr/chapter3/1.mdx diff --git a/README.md b/README.md index d8eea1a28..9f7590e27 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Once an issue is created, post a comment to indicate which chapters you'd like t **🗣 Join our Discord** -Since it can be difficult to discuss translation details quickly over GitHub issues, we have created dedicated channels for each language on our Discord server. If you'd like to jon, just following the instructions at this channel 👉: [https://discord.gg/fvRrmu27](https://discord.gg/fvRrmu27) +Since it can be difficult to discuss translation details quickly over GitHub issues, we have created dedicated channels for each language on our Discord server. If you'd like to jon, just following the instructions at this channel 👉: [https://discord.gg/JfAtkvEtRb](https://discord.gg/JfAtkvEtRb) **🍴 Fork the repository** diff --git a/chapters/fa/TRANSLATING.txt b/chapters/fa/TRANSLATING.txt index 17e0af3b1..554a67664 100644 --- a/chapters/fa/TRANSLATING.txt +++ b/chapters/fa/TRANSLATING.txt @@ -2,6 +2,9 @@ We deviate from this style guide in the following ways: - Use 'ء' as a possessive suffix instead of 'ی' + Pages 22-24 deal with prefixes and suffixes. + Page 10 deals with spacing. + 2. Don't translate industry-accepted acronyms. e.g. TPU or GPU. 3. Translate acronyms used for convenience in English to full Persian form. e.g. ML diff --git a/chapters/fa/_toctree.yml b/chapters/fa/_toctree.yml index d1f8b5d08..d45d23d0e 100644 --- a/chapters/fa/_toctree.yml +++ b/chapters/fa/_toctree.yml @@ -26,6 +26,13 @@ - local: chapter4/2 title: بکارگیری مدل‌های از پیش تعلیم دیده +- title: 3. کوک کردن یک مدل از پیش تعلیم دیده # Translate this! + sections: + - local: chapter3/1 + title: مقدمه # Translate this! + - local: chapter3/2 + title: پردازش داده # Translate this! + - title: واژه‌نامه sections: - local: glossary/1 diff --git a/chapters/fa/chapter2/1.mdx b/chapters/fa/chapter2/1.mdx index 31c104fae..5d61827cf 100644 --- a/chapters/fa/chapter2/1.mdx +++ b/chapters/fa/chapter2/1.mdx @@ -1,27 +1,25 @@ +
# مقدمه - همان طور که در [فصل اول](/course/chapter1) دیدید، مدل‌های ترنسفورمر معمولا بسیار بزرگ هستند. با داشتن میلیون‌ها یا حتی ده‌ها میلیارد پارامتر، تعلیم و بکارگیری این مدل‌ها کار بسیار پیچیده‌ای است. علاوه بر این،‌ تقریبا هر روز مدل‌های جدیدی عرضه می‌شوند که هرکدام شیوه پیاده‌سازی خود را دارند و امتحان کردن تمام آن‌ها کار آسانی نیست. -کتابخانه ترنسفومرهای هاگینگ‌فیس برای حل این مشکل تولید شده است. هدف آن، ارائه یک API واحد برای بارگذاری، تعلیم و ذخیره‌سازی مدل‌های ترنسفورمر است. ویژگی های اصلی این کتابخانه از این قرار است: +کتابخانه ترنسفومرهای هاگینگ‌فِیس برای حل این مشکل تولید شده است. هدف آن، ارائه یک API واحد برای بارگذاری، تعلیم و ذخیره‌سازی مدل‌های ترنسفورمر است. ویژگی های اصلی این کتابخانه از این قرار است: +- **سهولت استفاده**: دانلود، بارگذاری و استفاده از مدل‌های NLP روز دنیا برای تولید نتیجه عملیاتی، فقط با دو خط کد امکان‌پذیر است. +- **انعطاف**: تمام مدل‌ها در واقع کلاس‌های معمولی پایتورچ مانند nn.Module یا کلاس‌های تنسورفلو مانند tf.keras.Model هستند و مانند هر مدل دیگری در فریمورک خود در دسترسی قرار دارند. +- **سادگی**: در طراحی کتابخانه انتزاعات بسیار کمی به کار رفته‌ است. اصل خودکفا بودن مدل‌ها بسیار مهم است. اجرای رو به جلوی مدل تماماً در یک فایل تعریف می‌شود و به این شیوه، کد به سادگی قابل فهم و تغییر است. -
-
    -
  • آسانی استفاده: دانلود، بارگذاری و استفاده از مدل‌های NLP روز دنیا برای تولید نتیجه عملیاتی، فقط با دو خط کد امکان‌پذیر است.
  • -
  • انعطاف: تمام مدل‌ها در واقع کلاس‌های معمولی پایتورچ مانند nn.Module یا کلاس های تنسورفلو مانند tf.keras.Model هستند و مانند هر مدل دیگری در فریمورک خود در دسترسی قرار دارند.
  • -
  • سادگی: در طراحی کتابخانه انتزاعات بسیار کمی به کار رفته‌ است. اصل «قائم به خود» بودن مدل ها بسیار مهم بوده به طوری که یکبار اجرای رو به جلوی آن‌ها تنها در یک فایل تعریف می‌شود تا کد آن قابل فهم و تغییر باشد.
  • -
-
+این ویژگی آخر باعث می‌شود ترنسفورمرهای هاگینگ‌فِیس بسیار متفاوت با نمونه‌های مشابه در کتابخانه‌های یادگیری ماشین دیگر باشند. مدل‌ها روی ماژول‌های متفاوتی که در فایل‌های مختلف قرار دارند بنا نشده‌اند؛ بلکه هر مدل محتوی لایه‌های خود است. علاوه بر ساده‌تر و قابل فهم‌تر کردن مدل‌ها، این ویژگی به شما اجازه می‌دهد به راحتی مدل را دستکاری کنید بدون این که بر مدل‌های دیگر تاثیر بگذارید. -این ویژگی آخر باعث می‌شود ترنسفورمرهای هاگینگ‌فیس بسیار متفاوت با نمونه‌های مشابه در کتابخانه‌های یادگیری ماشین دیگر باشند. مدل‌ها روی ماژول های متفاوتی که در فایل‌های مختلف قرار دارند بنا نشده‌اند؛ بلکه هر مدل محتوی لایه‌های خود است. علاوه بر ساده‌تر و قابل فهم‌تر کردن مدل‌ها، این ویژگی به شما اجازه می‌دهد به راحتی یک مدل را دستکاری کنید بدون این که بر مدل‌های دیگر تاثیر بگذارید. +این فصل با مثالی کامل شروع می‌شود که در آن مدل و توکِنایزر را با هم استفاده می‌کنیم تا تابع pipeline() که در فصل اول معرفی کردیم را شبیه‌سازی کنیم. سپس API مربوط به مدل‌ها را بررسی می‌کنیم و وارد پیچیدگی‌های کلاس‌های مدل و کلاس‌های تنظیمات می‌شویم تا نشان دهیم چگونه می‌توان مدل‌ها را بارگذاری نمود و این مدل‌ها چطور ورودی‌های عددی را پردازش می‌کنند تا در خروجی پیش‌بینی‌ها را تولید کنند. -این فصل با یک مثال کامل شروع می شود که در آن یک مدل و یک توکنایزر را با هم استفاده می‌کنیم تا عملیات pipeline() که در فصل اول معرفی کردیم را شبیه سازی کنیم. سپس API مربوط به مدل‌ها را بررسی می‌کنیم و وارد پیچیدگی‌های کلاس‌های مدل و کلاس‌های تنظیمات می‌شویم تا نشان دهیم چگونه می‌توان مدل‌ها را بارگذاری نمود و این مدل‌ها چطور ورودی‌های عددی را پردازش می‌کنند تا در خروجی پیش‌بینی‌ها را تولید کنند. +سپس نگاهی به API مربوط به توکِنایزر خواهیم داشت که بخش دیگر پیاده‌سازی تابع pipeline() است. توکِنایزرها مرحله اول و مرحله آخر پردازش را انجام می‌دهند که در طی آن‌ها داده‌های نوشتاری را به ورودی‌های عددی برای شبکه عصبی تبدیل نموده و هنگام نیاز باز داده‌های عددی را به نوشتار تبدیل می‌کنند. در انتها، به شما نشان خواهیم داد چگونه چندین جمله را همزمان در یک بتچ از پیش آماده شده از مدل عبور دهید و سپس فصل را با نگاهی نزدیک‌تر به تابع بالادستی tokenizer() به اتمام خواهیم برد. -سپس نگاهی به API مربوط به توکنایزر خواهیم داشت که بخش دیگر پیاده‌سازی عملیات pipeline() است. توکنایزرها مرحله اول و مرحله آخر پردازش را انجام می‌دهند که در طی آن‌ها داده‌های نوشتاری را به ورودی‌های عددی برای شبکه عصبی تبدیل نموده و هنگام نیاز باز داده‌های عددی را به نوشتار تبدیل می‌کنند. در انتها، به شما نشان خواهیم داد چگونه چندین جمله را همزمان در یک بتچ از پیش آماده شده از مدل عبور دهید و سپس ان را با یک نگاه نزدیکتر به عملیات بالادستی tokenizer() به اتمام خواهیم برد. + +⚠️ برای بهره بردن از تمامی ویژگی‌های موجود در هاب مدل‌ها و همچنین ترنسفورمرهای هاگینگ‌فِیس پیشنهاد می‌کنیم که حساب کاربری بسازید. -برای استفاده بردن از تمامی ویژگی‌های موجود در Model Hub و ترنسفورمرهای هاگینگ‌فیس پیشنهاد می کنیم که حساب کاربری بسازید. +
diff --git a/chapters/fa/chapter2/2.mdx b/chapters/fa/chapter2/2.mdx index 534a03758..71abc5e16 100644 --- a/chapters/fa/chapter2/2.mdx +++ b/chapters/fa/chapter2/2.mdx @@ -1,7 +1,7 @@
-# پشت صحنه خط‌تولید +# پشت صحنه خط تولید {#if fw === 'pt'} @@ -24,7 +24,7 @@ {/if} -این اولین بخشی است که محتوای آن بسته به اینکه از پایتورچ یا تِنسورفِلو استفاده می کنید کمی متفاوت است. از سویچ بالای صفحه برای انتخاب پلتفرمی که ترجیح می دهید استفاده کنید! +این اولین بخشی است که محتوای آن بسته به اینکه از پایتورچ یا تِنسورفِلو استفاده می‌کنید کمی متفاوت است. از سویچ بالای صفحه برای انتخاب پلتفرمی که ترجیح می‌دهید استفاده کنید! @@ -34,7 +34,7 @@ {/if} -بگذارید با یک مثال کامل شروع کنیم که در آن نگاهی می‌اندازیم به آنچه که در پشت صحنه اتفاق می افتد هنگامی که این کد را در [Chapter 1](/course/chapter1) اجرا کردیم: +بگذارید با یک مثال کامل شروع کنیم. نگاهی می‌اندازیم به آنچه در پشت صحنه در اثر اجرای این قطعه کد در [فصل اول](/course/chapter1) رخ داد:
@@ -64,7 +64,7 @@ classifier(
-همان طور که در در فصل اول دیدیم، این خط تولید از سه مرحله تشکیل شده است: پیش‌پردازش، پردازش ورودی‌ها در مدل و پس پردازش. +همان طور که در در فصل اول دیدیم، این خط تولید از سه مرحله تشکیل شده است: پیش‌پردازش، پردازش ورودی‌ها در مدل و پس‌پردازش.
The full NLP pipeline: tokenization of text, conversion to IDs, and inference through the Transformer model and the model head. @@ -75,15 +75,15 @@ classifier( ## پیش‌پردازش با توکِنایزر -مثل شبکه‌های عصبی دیگر، مدل‌های ترنسفورمر هم نمی‌توانند نوشته خام را پردازش کنند. پس اولین قدم در خط تولید ما تبدیل نوشته خام ورودی به اعدادی است که مدل می‌فهمد. برای این کار از یک *توکِنایزر* استفاده می‌کنیم، که مسئولیت‌های زیر را بر عهده دارد: +مثل شبکه‌های عصبی دیگر، مدل‌های ترنسفورمر هم نمی‌توانند نوشته خام را پردازش کنند. پس اولین قدم در خط تولید ما، تبدیل نوشته خام ورودی به اعدادی است که مدل قادر به فهم آنها باشد. برای این کار از یک *توکِنایزر* استفاده می‌کنیم، که مسئولیت‌های زیر را بر عهده دارد: -- شکستن نوشته به کلمات، قطعات کوچکتر کلمه و علامت‌ها(مانند علائم نگارشی) که به آنها ‌*توکِن* می‌گوییم. -- انتخاب یک عدد صحیح معادل برای هر توکِن. +- شکستن نوشته به کلمات، زیرکلمات و علامت‌ها (مانند علائم نگارشی) که به آنها ‌*توکِن* می‌گوییم. +- انتخاب عدد صحیح معادل برای هر توکِن. - اضافه‌کردن ورودی‌های دیگری که ممکن است برای مدل مفید باشند. -همه مراحل این پیش‌پردازش باید دقیقا همان‌طور انجام شوند که قبلا هنگام تعلیم مدل انجام شده است. این اطلاعات در [Model Hub](https://huggingface.co/models) موجود است و توسط تابع `from_pretrained()` از کلاس `AutoTokenizer` دانلود می‌شود. با استفاده از نام کامل مدل که شامل نقطه تعلیم است، این تابع به صورت خودکار داده‌های توکِنایزر مدل را دریافت نموده و در سیستم شما ذخیره می‌کند. به این ترتیب این داده‌ها فقط بار اولی که کد زیر را اجرا می‌کنید دانلود می‌شوند. +همه مراحل این پیش‌پردازش باید دقیقا همان طور که قبلا هنگام تعلیم مدل انجام شده، دنبال شوند. این اطلاعات در [هاب مدل‌ها](https://huggingface.co/models) موجود است و توسط تابع `from_pretrained()` از کلاس `AutoTokenizer` دانلود می‌شود. با استفاده از نام کامل مدل که شامل نقطه تعلیم است، این تابع به صورت خودکار داده‌های توکِنایزر مدل را دریافت نموده و در سیستم شما ذخیره می‌کند. به این ترتیب این داده‌ها فقط بار اولی که کد زیر را اجرا می‌کنید دانلود می‌شوند. -نقطه تعلیم پیش‌فرض برای خط‌تولید `تحلیل احساسات` `distilbert-base-uncased-finetuned-sst-2-english` نام دارد. صفحه توضیحات این مدل را می توانید در [اینجا مشاهده کنید](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english). با اجرای کد زیر آن را دانلود می کنیم: +خط تولید `تحلیل احساسات` نقطه تعلیم پیش‌فرضی به نام `distilbert-base-uncased-finetuned-sst-2-english` دارد. صفحه توضیحات این مدل را می‌توانید در [اینجا مشاهده کنید](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english). با اجرای کد زیر آن را دانلود می‌کنیم: @@ -99,9 +99,11 @@ tokenizer = AutoTokenizer.from_pretrained(checkpoint)
-پس از دریافت توکِنایزر، می توانیم جملات خود را مستقیماً وارد آن کنیم و دیکشنری خروجی را دریافت کنیم که آماده است تا به عنوان ورودی مدل مورد استفاده قرار گیرد! تنها کار باقی مانده تبدیل لیست ID‌های ورودی به تِنسور است. شما می‌توانید از ترنسفورمرهای هاگینگ‌فِیس بدون اطلاع از اینکه کدام فریمورک یادگیری ماشین در پشت صحنه درگیر می‌شود استفاده کنید. ممکن است از پایتورچ، تِنسورفِلو یا حتی فلَکس برای بعضی مدل‌ها استفاده شده باشد. با این وجود مدل‌های ترسفورمر فقط *تِنسور*‌ها را به عنوان ورودی قبول می‌کنند. اگر این بار اولی است که با کلمه تِنسور برخورد دارید، می‌توانید آن‌ها را به عنوان آرایه‌های NumPy تصور کنید. این آرایه‌ها می توانند عددی(تک بعدی)، برداری(یک بعدی)، ماتریس(دو بعدی) یا با ابعاد بیشتر باشند. آن‌ها در واقع تِنسور هستند و تِنسورها در فریمورک‌های یادگیری ماشین رفتاری شبیه به آرایه‌های NumPy دارند و به همان سادگی هم ساخته می‌شوند. +پس از دریافت توکِنایزر، می‌توانیم جملات خود را مستقیماً وارد آن کنیم و دیکشنری خروجی را دریافت کنیم که آماده است تا به عنوان ورودی مدل مورد استفاده قرار گیرد! تنها کار باقی مانده، تبدیل لیست شناسه‌های ورودی به تِنسور است. -برای مشخص کردن نوع تِنسوری که می‌خواهیم به عنوان خروجی دریافت کنیم(پایتورچ، تِنسورفِلو یا NumPy ساده)، از آرگومان `return_tensors` استفاده می‌کنیم: +شما می‌توانید از ترنسفورمرهای هاگینگ‌فِیس بدون اطلاع از اینکه کدام فریمورک یادگیری ماشین در پشت صحنه درگیر می‌شود استفاده کنید. ممکن است از پایتورچ، تِنسورفِلو یا حتی فلَکس برای بعضی مدل‌ها استفاده شده باشد. با این وجود، مدل‌های ترسفورمر فقط *تِنسور*‌ها را به عنوان ورودی قبول می‌کنند. اگر این بار اولی است که کلمه تِنسور را می‌شنوید، تصور کنید مانند آرایه‌های NumPy هستند. این آرایه‌ها می‌توانند عددی (تک بُعدی)، برداری (یک بُعدی)، ماتریس (دو بُعدی) یا با ابعاد بیشتر باشند. آن‌ها در واقع تِنسور هستند و تِنسورها در فریمورک‌های یادگیری ماشین رفتاری شبیه به آرایه‌های NumPy دارند و به همان سادگی هم ساخته می‌شوند. + +برای مشخص کردن نوع تِنسوری که می‌خواهیم به عنوان خروجی دریافت کنیم (پایتورچ، تِنسورفِلو یا NumPy ساده)، از آرگومان `return_tensors` استفاده می‌کنیم:
@@ -128,7 +130,7 @@ print(inputs)
-هنوز لازم نیست نگران آرگومان‌های `padding` و `truncation` باشید؛ زیرا بعدتر به آنها خواهیم پرداخت. مسئله اصلی که باید به به خاطر بسپارید، امکان دادن یک جمله یا آرایه‌ای از جمله‌ها به عنوان ورودی و مشخص کردن نوع تِنسورهای خروجی است. اگر نوع خروجی را مشخص نکنید، لیستی از لیست‌ها را دریافت خواهید کرد. +هنوز لازم نیست نگران آرگومان‌های `padding` و `truncation` باشید؛ زیرا بعدتر به آنها خواهیم پرداخت. مسئله اصلی که باید به به خاطر بسپارید، امکان دادن جمله یا آرایه‌ای از جمله‌ها به عنوان ورودی و مشخص کردن نوع تِنسورهای خروجی است. اگر نوع خروجی را مشخص نکنید، لیستی از لیست‌ها را دریافت خواهید کرد. {#if fw === 'pt'} @@ -177,13 +179,13 @@ print(inputs) {/if} -خروجی خود یک دیکشنری با دو کلید `input_ids` و `attention_mask` است. `input_ids` دو ردیف عدد صحیح(یک ردیف برای هر جمله) که شناسه‌های منحصر به فرد توکِن‌های هر جمله هستند. `attention_mask` را بعدتر در همین فصل توضیح خواهیم داد. +خروجی یک دیکشنری با دو کلید `input_ids` و `attention_mask` است. `input_ids` دو ردیف عدد صحیح (یک ردیف برای هر جمله) است که شناسه‌های منحصر به فرد توکِن‌های هر جمله هستند. `attention_mask` را بعدتر در همین فصل توضیح خواهیم داد. ## گذر از مدل {#if fw === 'pt'} -می توانیم مدل از پیش تعلیم دیده را، همانند آن چه در مورد توکِنایزر انجام شد، دانلود کنیم. ترنسوفورمرهای هاگینگ‌فِیس کلاس `AutoModel` را ارا‌ئه می‌دهد که تابعی به نام `from_pretrained()` هم دارد: +می‌توانیم مدل از پیش تعلیم دیده را، همانند آن چه در مورد توکِنایزر انجام شد، دانلود کنیم. ترنسوفورمرهای هاگینگ‌فِیس کلاس `AutoModel` را ارا‌ئه می‌دهد که آن هم تابعی به نام `from_pretrained()` دارد:
@@ -198,7 +200,7 @@ model = AutoModel.from_pretrained(checkpoint) {:else} -می توانیم مدل از پیش تعلیم دیده را، همانند آن چه در مورد توکِنایزر انجام شد، دانلود کنیم. ترنسوفورمرهای هاگینگ‌فِیس کلاس `TFAutoModel` را ارا‌ئه می‌دهد که تابعی به نام `from_pretrained()` هم دارد: +می‌توانیم مدل از پیش تعلیم دیده را، همانند آنچه در مورد توکِنایزر انجام شد، دانلود کنیم. ترنسوفورمرهای هاگینگ‌فِیس کلاس `TFAutoModel` را ارا‌ئه می‌دهد که آن هم تابعی به نام `from_pretrained()` دارد:
@@ -213,25 +215,25 @@ model = TFAutoModel.from_pretrained(checkpoint) {/if} -در این قطعه کد، همان نقطه تعلیمی که در خط تولیدمان قبلا استفاده کردیم را دانلود کرده(که البته باید الان دانلود شده باشد و در سیستم شما موجود است پس نیازی به دانلود مجدد ندارد) و مدلی جدید با آن می‌سازیم. +در این قطعه کد، همان نقطه تعلیمی که قبلا در خط تولید استفاده کردیم را دانلود کرده و مدلی جدید بر اساس آن می‌سازیم. این نقطه تعلیم احتمالا قبلا دانلود شده و در سیستم شما موجود است؛ پس نیازی به دانلود مجدد ندارد. -این معماری تنها شامل ماژول پایهٔ `Transformer` است: با دریافت ورودی،‌ *وضعیت پنهان* را در خروجی تحویل می‌دهد. به این وضعیت‌های پنهان *فیچر* هم می‌گوییم. برای هر ورودی مدل، برداری چندین ‌بعدی که معادل *درک کلی مدل ترنسفورمر از آن ورودی* است را دریافت می‌کنیم. +این معماری تنها شامل ماژول پایهٔ ترنسفورمر است: با دریافت ورودی،‌ تنها *وضعیت پنهان* را در خروجی تحویل می‌دهد. به این وضعیت‌های پنهان، *فیچر* هم می‌گوییم. برای هر ورودی مدل، برداری با بُعد بالا دریافت می‌کنیم که معادل «درک کلی مدل ترنسفورمر از آن ورودی» است. نگران نباشید اگر درک این مفاهیم سخت است. همه آنها را بعدتر توضیح خواهیم داد. -اگرچه وضعیت‌های پنهان به خودی خود می توانند مفید باشند، آن‌ها معمولا ورودی بخش دیگری از مدل که به آن *سر* مدل می گوییم هستند. در [فصل اول](/course/chapter1)، همه مسائل مختلف را می‌توانستیم توسط یک معماری حل کنیم، و سپس خروجی را به سر متفاوتی در ادامه مدل پاس بدهیم. +با وجود آنکه وضعیت‌های پنهان به خودی خود هم مفید هستند، آن‌ها معمولا ورودی بخش دیگری از مدل به نام *سَر مدل* هستند. در [فصل اول](/course/chapter1)، می‌توانستیم همه مسائل مختلف را توسط تنها یک معماری حل کنیم، و سپس خروجی را به سر متفاوتی در ادامه مدل پاس بدهیم. -### بردار‌های چند بعدی؟ +### بردار‌های با بُعد بالا؟ -خروجی ماژول `Transformer` تِنسوری است که معمولا سه بعد دارد: +خروجی ماژول `Transformer` معمولا تِنسوری بزرگ است که اکثراً سه بُعد دارد: -- **اندازه بتج**: تعداد رشته‌های مورد پردازش در یک دسته، که در مثال ما دو عدد است. -- **طول رشته**: طول بردار عددی معادل رشته‌ها، که در مثال ما ۱۶ است. -- **اندازه پنهان**: ابعاد بردار برای هر ورودی مدل. +- **اندازه بتچ**: تعداد رشته‌های مورد پردازش در یک دسته، که در مثال ما دو عدد است. +- **طول رشته**: تعداد بردار‌های عددی معادل هر رشته‌، که در مثال ما ۱۶ است. +- **اندازه پنهان**: ابعاد بردار نماینده هر ورودی مدل. - به خاطر همین مقدار آخر به این تِنسور «چندین بعدی» می گوییم. اندازه پنهان می‌تواند بسیار بزرگ باشد(معمولا ۷۶۸ برای مدل‌های کوچکتر، و در مدل‌های بزرگتر این عدد به ۳۰۷۲ یا بیشتر هم می رسد) +به خاطر همین مقدار آخر به این تِنسور «بُعد بالا» می‌گوییم. اندازه پنهان می‌تواند بسیار بزرگ باشد (معمولا ۷۶۸ برای مدل‌های کوچک‌تر، و در مدل‌های بزرگ‌تر این عدد به ۳۰۷۲ یا بیشتر هم می‌رسد). -این را با پاس دادن ورودی‌های پیش‌پردازش شده به مدل خود می توانیم ببینیم: +با پاس دادن ورودی‌های پیش‌پردازش شده به مدل خود می‌توانیم این تِنسور را ببینیم:
@@ -260,11 +262,11 @@ print(outputs.last_hidden_state.shape)
-توجه کنید که خروجی‌های ترنسفورمرهای هاگینگ‌فِیس، مانند `namedtuple`‌ها یا دیکشنری‌ها هستند. شما می‌توانید به هر عضو، با استفاده از نامش(مانند آنچه ما انجام دادیم) یا با کلیدش(`outputs["last_hidden_state"]`) یا حتی اگر دقیقاً از مکان آن اطلاع دارید با شماره‌اش(`outputs[0]`) دسترسی پیدا کنید. +توجه کنید که خروجی‌های ترنسفورمرهای هاگینگ‌فِیس، رفتاری شبیه `namedtuple`‌ یا دیکشنری‌ دارند. شما می‌توانید به هر عضو، با استفاده از نامش (مانند آنچه ما انجام دادیم) یا با کلیدش (`outputs["last_hidden_state"]`) یا حتی اگر دقیقاً از مکان آن اطلاع دارید با اندیس‌اش (`outputs[0]`) دسترسی پیدا کنید. -### سر مدل: درک اعداد درون مدل +### سَر مدل: درک اعداد درون مدل -قسمت سر، بردارهای چندین بعدی وضعیت پنهان را به عنوان ورودی می‌پذیرد و آن‌ها را به ابعادی در فضایی دیگر برای بخش بعدی پروجکت می‌کند. سرها معمولا از یک یا چند لایه خطی تشکیل شده‌اند. +قسمت سَر، بردارهای بُعد بالای وضعیت پنهان را به عنوان ورودی می‌پذیرد و آنها را به بُعدی دیگر می‌برد. سَرها معمولا از یک یا چند لایه خطی تشکیل شده‌اند.
@@ -272,9 +274,9 @@ print(outputs.last_hidden_state.shape)
-خروجی مدل ترنسفورمر، مستقیماً به سر بخش بعدی پاس داده می‌شود. در این نمودار، مدل ترنسفورمر به لایه embeddings و لایه‌های بعدی آن تقسیم شده است. لایه embeddings هر شناسه ورودی در ورودی توکِنیزه شده را به یک بردار که نماینده آن توکِن است تبدیل می‌کند. لایه‌های بعدی با دستکاری در این بردار‌ها توسط فرآیند اتِنشِن، شکل پایانی بردار نماینده جملات را تولید می‌کنند. +خروجی مدل ترنسفورمر، مستقیماً به سَر مدل برای پردازش پاس داده می‌شود. در این نمودار، مدل ترنسفورمر به لایه embeddings و لایه‌های بعدی آن تقسیم شده است. لایه embeddings هر شناسه ورودی در ورودی توکِن‌شده را به یک بردار که نماینده آن توکِن است تبدیل می‌کند. لایه‌های بعدی با دستکاری در این بردار‌ها توسط مکانیزم توجه، شکل پایانی بردار نماینده جملات را تولید می‌کنند. -تعداد زیادی از معماری‌‌های مختلف در ترنفورمر‌های هاگینگ‌فِیس وجود دارد که هرکدام برای حل یک مسئله خاص طراحی شده‌اند. در این‌جا فهرست کوتاهی از‌ آنها را آورده‌ایم: +تعداد زیادی از معماری‌‌های مختلف در ترنسفورمر‌های هاگینگ‌فِیس موجود است و هرکدام برای حل یک مسئله خاص طراحی شده‌اند. در این‌جا فهرست کوتاهی از‌ آنها را می‌آوریم: - `*Model` (برای دسترسی به وضعیت‌های پنهان) - `*ForCausalLM` @@ -286,7 +288,7 @@ print(outputs.last_hidden_state.shape) - و نمونه‌های دیگر در ‌هاگینگ‌فِیس {#if fw === 'pt'} -برای این مثال، نیازمند مدلی با سر مخصوص دسته‌بندی رشته‌ها(برای تشخیص منفی یا مثبت بودن جملات) هستیم. پس به جای کلاس `AutoModel` از کلاس `AutoModelForSequenceClassification` استفاده می‌کنیم: +برای این مثال، نیازمند مدلی با سَر مخصوص دسته‌بندی رشته‌ها (برای تشخیص منفی یا مثبت بودن جملات) هستیم. پس به جای کلاس `AutoModel` از کلاس `AutoModelForSequenceClassification` استفاده می‌کنیم:
@@ -301,7 +303,7 @@ outputs = model(**inputs)
{:else} -برای این مثال، نیازمند مدلی با سر مخصوص دسته‌بندی رشته‌ها(برای تشخیص منفی یا مثبت بودن جملات) هستیم. پس به جای کلاس `TFAutoModel` از کلاس `TFAutoModelForSequenceClassification` استفاده می‌کنیم: +برای این مثال، نیازمند مدلی با سَر مخصوص دسته‌بندی رشته‌ها (برای تشخیص منفی یا مثبت بودن جملات) هستیم. پس به جای کلاس `TFAutoModel` از کلاس `TFAutoModelForSequenceClassification` استفاده می‌کنیم:
@@ -318,7 +320,7 @@ outputs = model(inputs) {/if} -حالا اگر نگاهی به ویژگی shape ورودی‌ها بیاندازیم، خوهیم دید که تعداد ابعاد بردارهای نماینده بسیار کمتر است: قسمت سر مدل بردارهای چندین بعدی که قبلا دیدیم را به عنوان ورودی دریافت کرده، و به عنوان خروجی بردارهایی با دو عضو(یکی برای هر دسته در عملیات دستبندی) تولید کرده است. +اگر نگاهی به شکل ورودی‌ها بیاندازیم، خواهیم دید که حالا تعداد ابعاد آنها بسیار کمتر است: قسمت سَر مدل، بردارهای بُعد بالایی که قبلا دیدیم را به عنوان ورودی دریافت کرده و در خروجی خود، بردارهایی با دو عضو (یکی به ازای هر برچسب دسته‌بندی) تولید می‌کند.
@@ -340,11 +342,11 @@ torch.Size([2, 2])
-از آنجا که ما تنها دو جمله و دو دسته ممکن داشتیم، خروجی مدل ما شکل ۲ در ۲ دارد. +از آنجا که ما تنها دو جمله و دو برچسب ممکن داشتیم، خروجی مدل ما شکل ۲ در ۲ دارد. -## پس پردازش خروجی +## پس‌پردازش خروجی -مقادیری که به عنوان خروجی از مدل‌مان دریافت می‌کنیم به خودی خود قابل درک نیستند. بگذارید نگاهی به آن‌ها بیندازیم: +مقادیری که به عنوان خروجی از مدل‌ دریافت می‌کنیم به خودی خود قابل درک نیستند. بگذارید نگاهی به آن‌ها بیندازیم:
@@ -368,7 +370,9 @@ tensor([[-1.5607, 1.6123],
-پیش‌بینی مدل ما برای جمله اول `[-1.5607, 1.6123]` و برای جمله دوم `[ 4.1692, -3.3464]` است. این‌ خروجی‌ها مقادیر آماری نیستند، بلکه به آنها *لوجیت* می‌گوییم. آن‌ها مقادیر خام و نرمال‌نشده خروجی از آخرین لایه مدل هستند. برای تبدیل به مقادیر آماری باید آن‌ها را از یک لایه‌ [سافت‌مکس](https://en.wikipedia.org/wiki/Softmax_function) بگذرانیم(تمام ترنسفورمرهای هاگینگ‌فِیس در خروجی لوجیت تولید می‌کنند زیرا معمولا تابع لاس مورد استفاده در تعلیم مدل، آخرین تابع فعال‌ساز مانند سافت‌مکس‌ را با خود تابع لاس مانند کِراس‌آنتروپی ترکیب می‌کند). + + +پیش‌بینی مدل ما برای جمله اول `[-1.5607, 1.6123]` و برای جمله دوم `[4.1692, -3.3464]` است. این‌ خروجی‌ها مقادیر آماری نیستند. به این مقادیر *لوجیت* می‌گوییم. مقادیری خام و نرمال‌نشده که خروجی آخرین لایه مدل هستند. برای تبدیل به مقادیر آماری باید این مقادیر را از یک لایه‌ [سافت‌مکس](https://en.wikipedia.org/wiki/Softmax_function) بگذرانیم. تمام ترنسفورمرهای هاگینگ‌فِیس در خروجی لوجیت تولید می‌کنند زیرا معمولا تابع هزینه مورد استفاده در تعلیم مدل، آخرین تابع فعال‌سازی (مانند سافت‌مکس‌) را با تابع هزینه مدل (مانند آنتروپی متقابل) ترکیب می‌کند.
@@ -405,9 +409,9 @@ tf.Tensor(
-حالا می‌توانیم ببینیم که پیش‌بینی مدل برای جمله اول `[0.9995, 0.0005]` و برای جمله دوم `[0.9995, 0.0005]` است. این‌ها مقادیر آشنای آماری هستند. +حالا می‌ببینیم که پیش‌بینی مدل برای جمله اول `[0.0402, 0.9598]` و برای جمله دوم `[0.9995, 0.0005]` است. این‌ها مقادیر آشنای آماری (به فرم احتمال) هستند. -برای تبدیل این مقادیر به عنوان دسته تشخیص داده شده می‌توانیم از ویژگی `id2label` تنظیمات مدل استفاده کنیم(در بخش بعدی بیشتر در این مورد صحبت خواهیم کرد): +برای تبدیل این مقادیر به برچسب دسته تشخیص داده شده می‌توانیم از ویژگی `id2label` تنظیمات مدل استفاده کنیم (در بخش بعدی بیشتر در این مورد صحبت خواهیم کرد):
@@ -428,12 +432,13 @@ model.config.id2label - جمله دوم: NEGATIVE: 0.9995, POSITIVE: 0.0005 -ما با موفقیت سه مرحله خط‌تولید را در اینجا نشان دادیم: پیش‌پردازش توسط توکِنایزرها، گذر ورودی‌ها از مدل و پس‌پردازش! حالا کمی زمان می‌گذاریم تا به صورت عمیق‌تر وارد هر‌کدام از این مراحل شویم. +ما با موفقیت سه مرحله خط تولید را در اینجا نشان دادیم: پیش‌پردازش توسط توکِنایزرها، گذر ورودی‌ها از مدل و پس‌پردازش! اکنون زمان آن فرا رسیده که به شکلی عمیق‌تر وارد هر یک از این مراحل شویم. -✏️ **خودتان امتحان کنید!** دو(یا بیشتر) نوشته از خودتان را از خط‌تولید `sentiment-analysis` بگذرانید. سپس مراحلی که در اینجا دیدیم را تکرار کنید و مطمئن شوید همان نتایج را دریافت می کنید! +✏️ **خودتان امتحان کنید!** دو نوشته از خودتان (یا حتی بیشتر) را از خط تولید `sentiment-analysis` بگذرانید. سپس مراحلی که در اینجا دیدیم را تکرار کنید و بررسی کنید که نتایج همان هستند!
+ diff --git a/chapters/fa/chapter3/1.mdx b/chapters/fa/chapter3/1.mdx new file mode 100644 index 000000000..b3f520423 --- /dev/null +++ b/chapters/fa/chapter3/1.mdx @@ -0,0 +1,27 @@ + + +
+ +# مقدمه + +در [فصل ۲](/course/chapter2) نحوه استفاده از توکِنایزرها و مدل‌های از پیش تعلیم دیده را جهت انجام پیش‌بینی‌های جدید بررسی کردیم. اما چگونه می‌توانید یک مدل از پیش‌ تعلیم دیده را خودتان کوک‌ کنید؟ + +{#if fw === 'pt'} + +* چگونه دیتاسِت‌های بزرگ را از هاب تهیه کنید +* چگونه از `API` سطح بالای `Trainer` برای کوک کردن مدل استفاده کنید +* چگونه یک چرخه‌ تعلیم دلخواه درست کنید +* چگونه از کتابخانه `Accelerate` هاگینگ‌فِیس برای اجرای چرخه‌ تعلیم دلخواه در هر نوع تنظیمات توزیع شده‌ای استفاده کنید + +{:else} + +* چگونه دیتاسِت‌های بزرگ را از هاب تهیه کنید +* چگونه از کِراس برای کوک‌ کردن مدل استفاده کنید +* چگونه از کِراس برای استخراج پیش‌بینی‌ها استفاده کنید +* چگونه از مِتریک دلخواه استفاده کنید + +{/if} + +جهت آپلود نقطه تعلیم خود در هاب هاگینگ‌فِیس، احتیاج به یک حساب کاربری در huggingface.co خواهید داشت: [ایجاد حساب کاربری](https://huggingface.co/join) + +
\ No newline at end of file diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx new file mode 100644 index 000000000..092a92b30 --- /dev/null +++ b/chapters/fa/chapter3/2.mdx @@ -0,0 +1,501 @@ + + +
+ +# پردازش داده + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + +در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه تعلیم مدل‌های دسته‌بندی کننده رشته‌ها را در یک بَتچ با استفاده از پایتورچ شرح می‌دهیم: + + +
+ +```python +import torch +from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification + +# Same as before +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "This course is amazing!", +] +batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") + +# This is new +batch["labels"] = torch.tensor([1, 1]) + +optimizer = AdamW(model.parameters()) +loss = model(**batch).loss +loss.backward() +optimizer.step() +``` + +
+ +{:else} + +در این بخش در ادامه مثال [فصل قبل](/course/chapter2)، نحوه تعلیم مدل‌های دسته‌بندی کننده رشته‌ها را در یک بَتچ با استفاده از تِنسورفلو شرح می‌دهیم: + +
+ +```python +import tensorflow as tf +import numpy as np +from transformers import AutoTokenizer, TFAutoModelForSequenceClassification + +# Same as before +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "This course is amazing!", +] +batch = dict(tokenizer(sequences, padding=True, truncation=True, return_tensors="tf")) + +# This is new +model.compile(optimizer="adam", loss="sparse_categorical_crossentropy") +labels = tf.convert_to_tensor([1, 1]) +model.train_on_batch(batch, labels) +``` + +
+ +{/if} + +البته تعلیم با استفاده از دو جمله به نتایج چشم‌گیری منتهی نخواهد شد. برای به دست آوردن نتایج بهتر نیاز به آماده‌سازی دیتاسِت بزرگتری خواهید داشت. + +در این بخش ما از دیتاسِت MRPC[^1] که در یک [مقاله](https://www.aclweb.org/anthology/I05-5002.pdf)، نوشته‌ی ویلیام بی دالن و کریس براکت، معرفی شده به عنوان یک مثال استفاده خواهیم کرد. این دیتاسِت شامل ۵۸۰۱ جفت جمله و یک برچسب می‌باشد که برچسب نشان دهنده متناظر بودن جملات می‌باشد (به عنوان مثال اینکه آیا دو جمله معنی یکسانی دارند یا خیر). علت انتخاب این دیتاسِت این است که دیتاسِت کوچکی است و تجربه تعلیم روی آن آسان است. + +### بارگذاری دیتاسِت‌ها از هاب + +{#if fw === 'pt'} + +{:else} + +{/if} + +هاب تنها شامل مدل‌ها نمی‌باشد؛ بلکه شامل دیتاسِت‌های متعدد در بسیاری از زبان‌های مختلف می‌باشد. شما می‌توانید دیتاسِت‌ها را در این [لینک](https://huggingface.co/datasets) جستجو کنید و پیشنهاد می‌کنیم پس از اتمام این بخش یک دیتاسِت جدید را دریافت و پردازش کنید (بخش مستندات عمومی را در [اینجا](https://huggingface.co/docs/datasets/loading_datasets.html#from-the-huggingface-hub) مشاهده کنید). اما اجازه بدهید اکنون روی دیتاسِت MRPC تمرکز کنیم! این یکی از ۱۰ دیتاسِت [GLUE benchmark](https://gluebenchmark.com/) است که یک محک تهیه شده در محیط دانشگاهی جهت اندازه گیری کارکرد مدل‌های یادگیری ماشینی در ۱۰ مسئله دسته‌بندی متن مختلف می‌باشد. + +کتابخانه دیتاسِت هاگینگ‌فِیس یک دستور بسیار ساده جهت دانلود و انبار کردن یک دیتاسِت در هاب ارائه می‌کند. ما می‌توانیم دیتاسِت MRPC را به روش زیر دانلود کنیم: + +
+ +```py +from datasets import load_dataset + +raw_datasets = load_dataset("glue", "mrpc") +raw_datasets +``` + +
+ +
+ +```python out +DatasetDict({ + train: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 408 + }) + test: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 1725 + }) +}) +``` + +
+ +همانطور که می‌بینید یک شیء `DatasetDict` بدست می‌آوریم که شامل مجموعه `training`، مجموعه `validation` و مجموعه `test` می‌باشد. هر یک از این‌ها شامل چندین ستون (`label`، `sentence2`، `sentence1` و `idx`) و تعداد متغیری سطر که عناصر هر مجموعه را تشکیل می‌دهند می‌باشد. (بنابراین، ۳۶۶۸ جفت جمله در مجموعه `training` وجود دارد، ۴۰۸ تا در مجموعه `validation` و ۱۷۲۵ تا در مجموعه `test`). + + این دستور دیتاسِت را دانلود و به صورت پیش‌فرض در پوشه‌ *~/.cache/huggingface/dataset* انبار می‌کند. از فصل ۲ به یاد داشته باشید که می‌توانید پوشه‌ انبار کردن‌تان را با تنظیم متغیر محیطی `HF_HOME` به دلخواه تغییر دهید. + +ما می‌توانیم به هر جفت از جملات در شئ `raw_datasets` با استفاده از اندیس, مانند یک دیکشنری دسترسی پیدا کنیم: + +
+ +```py +raw_train_dataset = raw_datasets["train"] +raw_train_dataset[0] +``` + +
+ +
+ +```python out +{'idx': 0, + 'label': 1, + 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', + 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} +``` + +
+ +می‌بینیم که برچسب‌ها از پیش اعداد صحیح هستند، بنابراین لازم نیست هیچ پیش‌پردازشی روی آنها انجام دهیم. برای این که بدانیم کدام مقدار عددی صحیح به کدام برچسب مربوط می‌شود، می‌توانیم `features` از ‌`raw_train_dataset`‌مان را بررسی کنیم. این کار نوع هر ستون را به ما خواهد گفت. + +
+ +```py +raw_train_dataset.features +``` + +
+ +
+ +```python out +{'sentence1': Value(dtype='string', id=None), + 'sentence2': Value(dtype='string', id=None), + 'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None), + 'idx': Value(dtype='int32', id=None)} +``` + +
+ +در پشت صحنه، `label` از نوع `ClassLabel` می‌باشد، و نگاشت اعداد صحیح به نام برچسب در پوشه‌ *names* ذخیره شده است. `0` مربوط به `not_equivalent` و `1` مربوط به `equivalent` می‌باشد. + + +✏️ **امتحان کنید!** عنصر شماره ۱۵ از مجموعه `training` و عنصر شماره ۸۷ از مجموعه `validation` را مشاهده کنید. برچسب‌های آنها چیست؟ + + +### پیش‌پردازش دیتاسِت‌‌ها + +{#if fw === 'pt'} + +{:else} + +{/if} + +به منظور پیش‌پردازش دیتاسِت‌، لازم است متن را به اعدادی قابل پردازش برای مدل تبدیل کنیم. همانطور که در[فصل قبل](/course/chapter2) مشاهده کردید، این کار با استفاده از یک توکِنایزر انجام می‌شود. ما می‌توانیم یک یا چند جمله را به توکِنایزر بدهیم، در نتیجه می‌توانیم به طور مستقیم تمام جملات اول و دوم هر جفت جمله را به صورت زیر توکِن کنیم: + +
+ +```py +from transformers import AutoTokenizer + +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"]) +tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"]) +``` + +
+ +با این حال، نمی‌توانیم دو جمله را به مدل ارسال کنیم تا پیش‌بینی کند که متناظر هستند یا خیر. ما نیاز داریم با دو رشته به صورت یک جفت برخورد کنیم و پیش‌پردازش مناسب را به آن اعمال کنیم. خوشبختانه، توکِنایزر می‌تواند یک جفت رشته را دریافت کند و آنرا به گونه‌ای که مدل BERT ما انتظار دارد آماده‌سازی کند: + +
+ +```py +inputs = tokenizer("This is the first sentence.", "This is the second one.") +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] +} +``` + +
+ +در [فصل ۲](/course/chapter2) در مورد کلیدهای `input_ids` و `attention_mask` بحث کردیم، اما از گفتگو در مورد `token_type_ids` اجتناب کردیم. در این مثال این همان چیزی است که به مدل می‌گوید کدام بخش از ورودی جمله اول و کدام بخش جمله دوم است. + + + +✏️ **امتحان کنید!** عنصر شماره ۱۵ از مجموعه `training` را بردارید و دو جمله را به صورت جداگانه و جفت توکِن کنید. تفاوت دو نتیجه چیست؟ + + + +اگر شناسه‌های داخل `input_ids` را به کلمات کدگشایی کنیم: + +
+ +```py +tokenizer.convert_ids_to_tokens(inputs["input_ids"]) +``` + +
+ +خواهیم داشت: + +
+ +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +``` + +
+ +بنابراین می‌بینیم که مدل انتظار دارد وقتی که دو جمله داریم ورودی‌ها به صورت `[CLS] sentence1 [SEP] sentence2 [SEP]` باشند. + +
+ +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +[ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] +``` + +
+ +همانطور که می‌بینید، بخش‌هایی از ورودی که مربوط به `[CLS] sentence1 [SEP]` هستند اندیس نشان دهنده نوع توکِن آنها `0` و بخش‌هایی که مربوط به `sentence2 [SEP]` هستند اندیس نشان دهنده نوع توکِن‌شان `1` می‌باشد. + +توجه داشته باشید که اگر نقطه تعلیم متفاوتی را انتخاب کنید، در ورودی‌ها لزوما `token_type_ids` نخواهید داشت (به عنوان مثال، اگر از یک DistilBERT استفاده کنید آنها بازگردانده نخواهند شد). آنها فقط زمانی بازگردانده می‌شوند که مدل می‌داند با آنها چکار کند، به این خاطر که آنها را در زمان پیش‌تعلیم دیده است. + +در اینجا، مدل BERT با شناسه‌هایی که نشان دهنده نوع توکِن هستند از پیش‌ تعلیم دیده و علاوه بر هدف تکمیل جاهای خالی متن که در [فصل ۱](/course/chapter1) در مورد آن صحبت کردیم وظیفه‌ دیگری تحت عنوان _پیش‌بینی جمله‌ بعدی_ بر عهده دارد. هدف از این وظیفه مدل کردن رابطه بین جملات جفتی می‌باشد. + + در پیش‌بینی جمله بعدی، لیستی از جمله‌های جفت شده (با کلماتی که به طور تصادفی پنهان شده‌اند) به مدل داده می‌شوند و از مدل خواسته می‌شود پیش‌بینی کند که آیا جمله دوم در ادامه‌ جمله‌ اول قرار دارد یا خیر. برای سخت‌تر کردن مسئله، در نیمی از حالت‌ها دو جمله در متن اصلی به دنبال هم آمده‌، و در نیمی دیگر از دو متن متفاوت می‌آیند. + +در مجموع، نیازی نیست نگران وجود یا عدم وجود `token_type_ids` در ورودی‌های توکِن شده خود باشید: مادامی که از نقطه تعلیم یکسان برای توکِنایزر و مدل استفاده کنید، همه چیز خوب پیش خواهد رفت چرا که توکِنایزر می‌داند چه چیزی برای مدل فراهم کند. + +اکنون که مشاهده کردیم چگونه توکِن کننده ما می‌تواند با دو جمله برخورد کند، می‌توانیم آن را برای توکِن کردن کل دیتاسِت‌مان به کار ببریم: مانند [فصل قبل](/course/chapter2)، ما می‌توانیم توکِنایزر را با لیستی از جفت جمله‌ها، با دادن لیست جملات اول و سپس لیست جملات دوم، تغذیه کنیم. این روش همچنین با گزینه‌های `padding` و `truncation` که در [فصل ۲](/course/chapter2) مشاهده کردیم سازگاری دارد. بنابراین، یک روش برای پیش‌پردازش دیتاسِت `training` اینگونه می‌باشد: + +
+ +```py +tokenized_dataset = tokenizer( + raw_datasets["train"]["sentence1"], + raw_datasets["train"]["sentence2"], + padding=True, + truncation=True, +) +``` + +
+ +این روش به خوبی کار می‌کند، اما مشکل‌اش این است که دیکشنری (از کلیدهای ما شامل، `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()` با اعمال کردن یک عملیات روی هر عنصر دیتاسِت عمل می‌کند، بنابراین اجازه دهید تابعی تعریف کنیم که ورودی‌ها را توکِن کند: + +
+ +```py +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) +``` + +
+ +این تابع دیکشنری (مثل اقلام داخل دیتاسِت) دریافت می‌کند و دیکشنری دیگری با کلیدهای `input_ids`، `attention_mask` و `token_type_ids` برمی‌گرداند. توجه داشته باشید از آنجایی که توکِنایزر روی لیست‌هایی از دو جمله‌ها کار می‌کند، همان‌طور که قبلا مشاهده کردیم، این تابع نیز در صورتی که دیکشنری `example` شامل چندین نمونه (هر کلید به عنوان لیستی از جمله‌ها) باشد کار می‌کند. این به ما این امکان را خواهد داد که از گزینه `batched=True` در فراخوانی تابع `map()` استفاده کنیم که توکِنایزر را به میزان زیادی سریع‌تر خواهد کرد. این `tokenizer` با توکِنایزری در کتابخانه [Tokenizers](https://github.com/huggingface/tokenizers) از هاگینگ‌فِیس که به زبان برنامه‌‌نویسی Rust نوشته شده پشتیبانی می‌شود. این توکِنایزر می‌تواند بسیار سریع باشد، اما فقط به شرطی که ورودی‌های زیادی را به صورت یک جا به آن بدهیم. + +توجه داشته باشید که ما آرگومان هم‌طول‌سازی را در تابع توکِن کننده‌مان نادیده گرفته‌ایم. این به این خاطر است که هم‌طول‌سازی روی همه نمونه‌ها برای بیشترین طول به صرفه نیست: بهتر است که نمونه‌ها را زمانی که در حال ساختن بَتچ هستیم هم‌طول کنیم، در این صورت فقط نیاز داریم نمونه‌ها را به اندازه بزرگترین طول همان بَتچ و نه بیشترین طول در سرتاسر دیتاسِت‌ هم‌طول کنیم. این روش زمانی که ورودی‌ها دارای طول‌های بسیار متغیری هستند وقت و انرژی زیادی را صرفه‌جویی خواهد کرد. + +در اینجا نشان می‌دهیم چگونه تابع تولید توکِن را روی کل دیتاسِت به یکباره اعمال می‌کنیم. ما از `batched=True` در فراخوانی تابع `map` استفاده می‌کنیم بنابر این تابع ما به جای اینکه روی هر عنصر به صورت جداگانه عمل کند روی چندین عنصر از دیتاسِت به یکباره عمل می‌کند. این کار اجازه می‌دهد که پیش‌پردازش سریع‌تر انجام گیرد: + +
+ +```py +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) +tokenized_datasets +``` + +
+ +کتابخانه `Datasets` از هاگینگ‌فِیس این پیش‌پردازش را با افزودن -فیلدهای- جدید به دیتاسِت‌ها، یکی به اِزای هر کلید در -دیکشنری- که توسط تابع پیش‌پردازش بازگردانده می‌شوند، اعمال می‌کند: + +
+ +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 408 + }) + test: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 1725 + }) +}) +``` + +
+ +شما حتی می‌توانید زمانی که تابع پیش‌پردازش خود را اعمال می‌کنید، با ارسال آرگومان `num_proc` در تابع `map()` از چندپردازشی استفاده کنید. در اینجا ما این کار را انجام ندادیم چرا که کتابخانه `Tokenizers` هاگینگ‌فِیس از پیش، از چندین رشته پردازشی برای توکِن کردن سریع‌تر نمونه‌های ما استفاده می‌کند، اما اگر شما از یک توکِنایزر سریع که با این کتابخانه پشتیبانی شود استفاده نمی‌کنید، این روش می‌تواند پیش‌پردازش شما را سریع‌تر کند. + + +تابع `tokenize_function` ما یک دیکشنری شامل کلیدهای `input_ids`، `attention_mask` و `token_type_ids` برمی‌گرداند به گونه‌ای که این کلیدها به صورت سه فیلد جدید به همه بخش‌های دیتاسِت افزوده گردند. توجه داشته باشید اگر تابع پیش‌پردازش ما برای یک کلید موجود در دیتاسِت مقدار جدیدی بازمی‌گرداند ما می‌توانستیم فیلدهای موجود در دیتاسِتی که تابع `map()` به آن اعمال می‌شود را نیز تغییر دهیم. + +آخرین کاری که باید انجام دهیم این است که هنگامی که عناصر را با هم در یک بَتچ قرار می‌دهیم، طول همه عناصر را به اندازه بلندترین عنصر برسانیم - تکنیکی که ما به آن *هم‌طول‌سازی پویا* می‌گوییم. + + +### هم‌طول‌سازی پویا + + + +{#if fw === 'pt'} + +تابعی که مسئول کنار هم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *تابع ترکیب کننده* خوانده می‌شود. شما می‌توانید این تابع را که در حالت پیش‌ فرض نمونه‌های شما را به تِنسور پایتورچ تبدیل کرده و به هم الحاق می‌کند (اگر عناصر شما لیست، تاپِل یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد) هنگام ساختن `DataLoader` به داخل آن ارسال کنید. از آنجایی که ورودی‌های ما هم‌طول نخواهند بود استفاده از این تابع برای ما امکان‌پذیر نیست. ناهم‌طولی ورودی‌ها به این خاطر است که ما فرایند هم‌طول‌سازی را عمدا به تعویق انداختیم تا فقط در زمان نیاز آن را روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با مقدار زیادی هم‌طول‌سازی پیش‌گیری کنیم. این روش، فرایند تعلیم را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال تعلیم روی TPU هستید این کار می‌تواند مشکل ساز باشد چرا که TPU اشکال معین را ترجیح می‌دهد، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. + +{:else} + +تابعی که مسئول کنار هم گذاشتن نمونه‌ها در یک بَتچ می‌باشد *تابع ترکیب کننده* خوانده می‌شود. تابع ترکیب کننده پیش‌فرض تابعی است که فقط نمونه‌های شما را به `tf.Tensor` تبدیل کرده و آنها را به هم الحاق می‌کند (اگر عناصر شما لیست، تاپِل یا دیکشنری باشند این کار به صورت بازگشتی انجام می‌گیرد). از آنجایی که ورودی‌های ما هم‌طول نخواهند بود استفاده از این تابع برای ما امکان پذیر نیست. ناهم‌طولی ورودی‌ها به این خاطر است که ما فرایند هم‌طول‌سازی را عمدا به تعویق انداختیم تا فقط در زمان نیاز آن را روی هر بَتچ اجرا کنیم و از داشتن ورودی‌های بیش از اندازه طولانی با مقدار زیادی هم‌طول‌سازی پیش‌گیری کنیم. این روش، فرایند تعلیم را تا اندازه‌ای سرعت می‌بخشد، اما توجه داشته باشید که اگر شما در حال تعلیم روی TPU هستید این کار می‌تواند مشکل ساز باشد چرا که TPU اشکال معین را ترجیح می‌دهد، حتی اگر نیاز به هم‌طول‌سازی اضافه داشته باشد. + +{/if} + +برای انجام این کار در عمل، ما باید یک تابع ترکیب کننده تعریف کنیم که میزان درستی از هم‌طول‌سازی را به آیتم‌های دیتاسِت‌هایی که ما می‌خواهیم باهم در یک بَتچ قرار دهیم اعمال کند. خوشبختانه، کتابخانه ترنسفورمرهای هاگینگ‌فِیس چنین قابلیتی را توسط کلاس `DataCollatorWithPadding` به ما می‌دهد. به محض این که شیء‌ای از این کلاس را تعریف کنیم (یعنی تعیین کنیم چه توکِنی برای هم‌طول‌سازی استفاده کند و مدل انتظار هم‌طول‌سازی از سمت چپ یا راست ورودی‌ها را داشته باشد) یک توکِنایزر را برداشته و هر کاری را که لازم دارید انجام می‌دهد: + +{#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} + +اجازه دهید چند نمونه از مجموعه `training` را که می‌خواهیم باهم در یک بَتچ قرار دهیم برداریم تا این ابزار جدید را امتحان کنیم. در اینجا ستون‌های `idx`، `sentence1` و `sentence2` را حذف می‌کنیم چرا که احتیاج نخواهند شد و شامل رشته‌های متنی می‌شوند (که ما نمی‌توانیم تنسورهایی از رشته‌های متنی ایجاد کنیم) و سپس نگاهی می‌اندازیم به طول هر ورودی در هر بَتچ: + +
+ +```py +samples = tokenized_datasets["train"][:8] +samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]} +[len(x) for x in samples["input_ids"]] +``` + +
+ +
+ +```python out +[50, 59, 47, 67, 59, 50, 62, 32] +``` + +
+ +تعجبی ندارد که نمونه‌هایی با طول‌های متغییر، از ۳۲ تا ۶۷ بدست می‌آوریم. هم‌طول‌سازی پویا به این معنی است که نمونه‌های موجود در این بَتچ باید همگی با طول ۶۷، که بزرگترین طول داخل بَتچ می‌باشد، هم‌طول شده باشند. بدون هم‌طول‌سازی پویا، همه نمونه‌ها در کل دیتاسِت باید به اندازه بزرگ‌ترین طول یا بزرگ‌ترین طول قابل پذیرش برای مدل، هم‌طول شوند. اجازه دهید بررسی کنیم آیا `data_collator` ما بَتچ را به درستی هم‌طول می‌کند: + +
+ +```py +batch = data_collator(samples) +{k: v.shape for k, v in batch.items()} +``` + +
+ +{#if fw === 'tf'} + +
+ +```python out +{'attention_mask': TensorShape([8, 67]), + 'input_ids': TensorShape([8, 67]), + 'token_type_ids': TensorShape([8, 67]), + 'labels': TensorShape([8])} +``` + +
+ +{:else} + +
+ +```python out +{'attention_mask': torch.Size([8, 67]), + 'input_ids': torch.Size([8, 67]), + 'token_type_ids': torch.Size([8, 67]), + 'labels': torch.Size([8])} +``` + +
+ +به نظر خوب می‌آید! اکنون که از متن خالص به بَتچ‌هایی رسیده‌ایم که مدل‌مان می‌تواند با آنها کار کند، آماده کوک‌ کردن مدل هستیم: + +{/if} + + + +✏️ **امتحان کنید!** پروسه پیش‌پردازش را روی دیتاسِت GLUE SST-2 باز تکرار کنید. از آنجایی که این مجموعه به جای دو جمله‌ها شامل تک جمله‌ها می‌باشد این کار کمی متفاوت است، اما بقیه کارهایی که انجام داده‌ایم باید یکسان به نظر برسند. برای یک چالش مشکل‌تر، سعی کنید تابع پیش‌پردازشی بنویسید که برای همه مسئله‌های GLUE کار کند. + + + +{#if fw === 'tf'} + +اکنون که دیتاسِت‌مان و یک `collator` داده در اختیار داریم، نیاز داریم که آنها را باهم بکار ببریم. ما می‌توانستیم بَتچ‌ها را دستی لود کرده و آنها را `collate` کنیم، اما این روش کار زیادی می‌برد و احتمالا خیلی هم بهینه نخواهد بود. در عوض، تابعی ساده وجود دارد که راه حل بهینه‌ای برای این مسئله ارائه می‌کند: `to_tf_dataset()`. این تابع یک `tf.data.Dataset` شامل پارامتری اختیاری برای تابع `collation` را دور دیتاسِت‌تان می‌پیچد. `tf.data.Dataset` یک فرمت بومی تِنسورفلو است که کِراس می‌تواند برای `model.fit()` استفاده کند، در نتیجه همین یک تابع می‌تواند یک دیتاسِت هاگینگ‌فِیس را به سرعت به فرمت آماده برای تعلیم تبدیل کند. اجازه دهید آنرا در عمل با دیتاسِت‌مان مشاهده کنیم! + +
+ +```py +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, +) +``` + +
+ +این هم از این! حالا می‌توانیم این دیتاسِت‌ها را به درس بعدی ببریم، جایی که تعلیم پس از همه سختی‌های پیش‌پردازش به طرز خوشایندی سرراست خواهد بود. + +{/if} + +[^1]: Microsoft Research Paraphrase Corpus + +
\ No newline at end of file diff --git a/chapters/fa/glossary/1.mdx b/chapters/fa/glossary/1.mdx index fe42e4885..d3fedc968 100644 --- a/chapters/fa/glossary/1.mdx +++ b/chapters/fa/glossary/1.mdx @@ -84,7 +84,7 @@ | Preprocess | پیش‌پردازش | | Postprocess | پس‌پردازش | | Method, as in code | تابع | -| Checkpoint | نقطه تعلیم؟ | +| Checkpoint | نقطه تعلیم | | Model Card | صفحه توضیحات مدل | | Sentiment Analysis | تحلیل احساسات | | Dictionary, as in Python | دیکشنری | @@ -112,17 +112,17 @@ | Vector, as in Python | وِکتور | | Sequence | رشته | | Index, as in an array or list | اندیس | -| Project, as in math | پروجکت؟ | +| Project, as in math | بردن | | Embedding | embedding? | | Tokenized | توکِن‌شده | | Mask Filling | پر کردن جاهای خالی متن | | Attention Mechanism | مکانیزم توجه | | Classification | دسته‌بندی | -| Attribute, as for a class in code | ویژگی؟ | -| Label, as in classification | عنوان دسته | +| Attribute, as for a class in code | ویژگی | +| Label, as in classification | برچسب دسته | | Prediction, as in nn model | پیش‌بینی | | Bias | سوگیری | -| Logit, as in math and also in Pytorch | لوجیت؟ | +| Logit, as in math and also in Pytorch | لوجیت | | SoftMax | سافت‌مکس | | Loss Function | تابع هزینه | | Activation Layer | لایه فعال‌سازی | @@ -133,8 +133,28 @@ | Set, as for variable | تخصیص مقدار | | Environment Variable | متغیر محیطی | | Metadata | متادیتا | -| Encode, not as in cypher | کد شده؟، کد گذاری؟ | +| Encode, as in assign numbers to | کد شده، کد گذاری | +| Decode, as in same | کد گشایی | +| Encoder, as in ML | اِنکودر | +| Decoder, as in ML | دیکودر | +| Encrypt | رمزگذاری | +| Decrypt | رمزگشایی | | Cache | انبار کردن | +| Production Environment | محیط استقرار | +| Classifier | دسته‌بندی‌کننده | +| Naive Bayes | بیز ساده | +| Collaborative learning | یادگیری مشارکتی | +| Demo | نمونه اولیه | +| collate | ترکیب کردن | +| mapping | نگاشت | +| element | عنصر | +| tuple | تاپِل | +| object | شیء | +| paraphrases | جملات متناظر | +| benchmark | محک | +| items | اقلام | +| padding | هم‌طول‌سازی | +| documentation | مستندات | معادل‌هایی که استفاده نمی‌کنیم: @@ -157,5 +177,6 @@ | TPU | TPU | | BERT | BERT | | ML | یادگیری ماشین | +| AGI | هوش جامع مصنوعی |
diff --git a/chapters/hi/_toctree.yml b/chapters/hi/_toctree.yml index d545861ea..f8f856b4b 100644 --- a/chapters/hi/_toctree.yml +++ b/chapters/hi/_toctree.yml @@ -7,8 +7,27 @@ sections: - local: chapter1/1 title: परिचय + - local: chapter1/2 + title: प्राकृतिक भाषा प्रसंस्करण - title: 2. ट्रांसफॉर्मर का उपयोग करना sections: - local: chapter2/1 - title: परिचय \ No newline at end of file + title: परिचय + +- title: 3. पूर्व-प्रशिक्षित मॉडल की फाइन-ट्यूनिंग + sections: + - local: chapter3/1 + title: परिचय + - local: chapter3/2 + title: डेटा संसाधित करना + - local: chapter3/3 + title: मॉडल कि फाइन-ट्यूनिंग Trainer API या Keras के साथ + local_fw: { pt: chapter3/3, tf: chapter3/3_tf } + - local: chapter3/4 + title: एक पूर्ण प्रशिक्षण + - local: chapter3/5 + title: फाइन-ट्यूनिंग, चेक! + - local: chapter3/6 + title: अध्याय-का-अंत प्रश्नोत्तरी + quiz: 3 diff --git a/chapters/hi/chapter0/1.mdx b/chapters/hi/chapter0/1.mdx index 7fef3c4bf..9377795e8 100644 --- a/chapters/hi/chapter0/1.mdx +++ b/chapters/hi/chapter0/1.mdx @@ -22,7 +22,7 @@ Colab नोटबुक का उपयोग करना सबसे आस
अगला चरण उन पुस्तकालयों को स्थापित करना है जिनका हम इस पाठ्यक्रम में उपयोग करेंगे। हम स्थापना के लिए `pip` का उपयोग करेंगे, जो कि पायथन के लिए पैकेज मैनेजर है। नोटबुक्स में, आप `!` वर्ण से पहले सिस्टम कमांड चला सकते हैं, इसलिए आप ट्रान्सफ़ॉर्मर लाइब्रेरी को निम्नानुसार स्थापित कर सकते हैं: -अगला चरण उन पुस्तकालयों को स्थापित करना है जिनका हम इस पाठ्यक्रम में उपयोग करेंगे। हम स्थापना के लिए `pip` का उपयोग करेंगे, जो कि पायथन के लिए पैकेज मैनेजर है। नोटबुक्स में, आप `!` वर्ण से पहले सिस्टम कमांड चला सकते हैं, इसलिए आप :hugs: ट्रान्सफ़ॉर्मर लाइब्रेरी को निम्नानुसार स्थापित कर सकते हैं: +अगला चरण उन पुस्तकालयों को स्थापित करना है जिनका हम इस पाठ्यक्रम में उपयोग करेंगे। हम स्थापना के लिए `pip` का उपयोग करेंगे, जो कि पायथन के लिए पैकेज मैनेजर है। नोटबुक्स में, आप `!` वर्ण से पहले सिस्टम कमांड चला सकते हैं, इसलिए आप 🤗 ट्रान्सफ़ॉर्मर लाइब्रेरी को निम्नानुसार स्थापित कर सकते हैं: ``` !pip install transformers @@ -38,7 +38,7 @@ import transformers उपरोक्त दो आदेशों का परिणाम दिखाने वाला एक GIF: स्थापना और आयात
-यह :hugs: ट्रांसफॉर्मर का एक बहुत हल्का संस्करण स्थापित करता है। विशेष रूप से, कोई विशिष्ट मशीन लर्निंग फ्रेमवर्क (जैसे PyTorch या TensorFlow) स्थापित नहीं हैं। चूंकि हम पुस्तकालय की कई अलग-अलग विशेषताओं का उपयोग करेंगे, हम विकास संस्करण को स्थापित करने की सलाह देते हैं, जो किसी भी कल्पनाशील उपयोग के मामले के लिए सभी आवश्यक निर्भरताओं के साथ आता है: +यह 🤗 ट्रांसफॉर्मर का एक बहुत हल्का संस्करण स्थापित करता है। विशेष रूप से, कोई विशिष्ट मशीन लर्निंग फ्रेमवर्क (जैसे PyTorch या TensorFlow) स्थापित नहीं हैं। चूंकि हम पुस्तकालय की कई अलग-अलग विशेषताओं का उपयोग करेंगे, हम विकास संस्करण को स्थापित करने की सलाह देते हैं, जो किसी भी कल्पनाशील उपयोग के मामले के लिए सभी आवश्यक निर्भरताओं के साथ आता है: ``` !pip install transformers[sentencepiece] @@ -101,10 +101,10 @@ which python ## निर्भरता स्थापित करना -Google Colab इंस्टेंस का उपयोग करने पर पिछले अनुभाग की तरह, अब आपको जारी रखने के लिए आवश्यक पैकेजों को स्थापित करने की आवश्यकता होगी। फिर से, आप `pip` पैकेज मैनेजर का उपयोग करके :hugs: ट्रांसफॉर्मर के विकास संस्करण को स्थापित कर सकते हैं: +Google Colab इंस्टेंस का उपयोग करने पर पिछले अनुभाग की तरह, अब आपको जारी रखने के लिए आवश्यक पैकेजों को स्थापित करने की आवश्यकता होगी। फिर से, आप `pip` पैकेज मैनेजर का उपयोग करके 🤗 ट्रांसफॉर्मर के विकास संस्करण को स्थापित कर सकते हैं: ``` pip install "transformers[sentencepiece]" ``` -अब आप पूरी तरह से तैयार हैं! \ No newline at end of file +अब आप पूरी तरह से तैयार हैं! diff --git a/chapters/hi/chapter1/2.mdx b/chapters/hi/chapter1/2.mdx new file mode 100644 index 000000000..dd707c568 --- /dev/null +++ b/chapters/hi/chapter1/2.mdx @@ -0,0 +1,20 @@ +# प्राकृतिक भाषा प्रसंस्करण + +ट्रांसफॉर्मर मॉडल में जाने से पहले, आइए एक त्वरित अवलोकन करें कि प्राकृतिक भाषा प्रसंस्करण क्या है और हम इसकी परवाह क्यों करते हैं। + +## प्राकृतिक भाषा प्रसंस्करण क्या है? + +प्राकृतिक भाषा प्रसंस्करण भाषा विज्ञान और मशीन सीखने का एक क्षेत्र है जो मानव भाषा से संबंधित हर चीज को समझने पर केंद्रित है। एनएलपी कार्यों का उद्देश्य न केवल एक शब्द को व्यक्तिगत रूप से समझना है, बल्कि उन शब्दों के संदर्भ को समझने में सक्षम होना है। + +निम्नलिखित सामान्य प्राकृतिक भाषा प्रसंस्करण कार्यों की एक सूची है, जिनमें से प्रत्येक के कुछ उदाहरण हैं: +- **पूरे वाक्यों को वर्गीकृत करना**: समीक्षा की भावना प्राप्त करना, यह पता लगाना कि क्या कोई ईमेल स्पैम है, यह निर्धारित करना कि कोई वाक्य व्याकरणिक रूप से सही है या दो वाक्य तार्किक रूप से संबंधित हैं या नहीं। +- **प्रत्येक शब्द को एक वाक्य में वर्गीकृत करना**: एक वाक्य (संज्ञा, क्रिया, विशेषण), या नामित संस्थाओं (व्यक्ति, स्थान, संगठन) के व्याकरणिक घटकों की पहचान करना। +- **पाठ सामग्री उत्पन्न करना**: ऑटो-जेनरेटेड टेक्स्ट के साथ एक प्रॉम्प्ट को पूरा करना, टेक्स्ट में रिक्त स्थान को नकाबपोश शब्दों से भरना। +- **किसी पाठ से उत्तर निकालना**: एक प्रश्न और एक संदर्भ को देखते हुए, संदर्भ में दी गई जानकारी के आधार पर प्रश्न का उत्तर निकालना। +- **इनपुट टेक्स्ट से एक नया वाक्य बनाना**: एक पाठ को दूसरी भाषा में अनुवाद करना, एक पाठ को सारांशित करना। + +प्राकृतिक भाषा प्रसंस्करण हालांकि लिखित पाठ तक ही सीमित नहीं है। यह वाक् पहचान और कंप्यूटर विज़न में जटिल चुनौतियों से भी निपटता है, जैसे कि ऑडियो नमूने की प्रतिलिपि बनाना या किसी छवि का विवरण। + +## यह चुनौतीपूर्ण क्यों है? + +कंप्यूटर इंसानों की तरह सूचनाओं को प्रोसेस नहीं करते हैं। उदाहरण के लिए, जब हम "मुझे भूख लगी है" वाक्य पढ़ते हैं, तो हम इसका अर्थ आसानी से समझ सकते हैं। इसी तरह, "मैं भूखा हूँ" और "मैं उदास हूँ" जैसे दो वाक्यों को देखते हुए, हम आसानी से यह निर्धारित करने में सक्षम हैं कि वे कितने समान हैं। मशीन लर्निंग (एमएल) मॉडल के लिए, ऐसे कार्य अधिक कठिन होते हैं। पाठ को इस तरह से संसाधित करने की आवश्यकता है जो मॉडल को इससे सीखने में सक्षम बनाता है। और क्योंकि भाषा जटिल है, हमें ध्यान से सोचने की जरूरत है कि यह प्रसंस्करण कैसे किया जाना चाहिए। पाठ का प्रतिनिधित्व करने के तरीके पर बहुत शोध किया गया है, और हम अगले अध्याय में कुछ विधियों को देखेंगे। diff --git a/chapters/hi/chapter3/1.mdx b/chapters/hi/chapter3/1.mdx new file mode 100644 index 000000000..16e3b4710 --- /dev/null +++ b/chapters/hi/chapter3/1.mdx @@ -0,0 +1,21 @@ + + +# परिचय + +[अध्याय 2](/course/chapter2) में हमने जाना कि कैसे भविष्यवाणी करने के लिए टोकननाइज़र और पूर्व-प्रशिक्षित मॉडल का उपयोग किया जाता है । लेकिन तब क्या यदि आप अपने स्वयं के डेटासेट के लिए एक पूर्व-प्रशिक्षित मॉडल को ठीक करना चाहते हैं? यही इस अध्याय का विषय है! आप सीखेंगे कि: + +{#if fw === 'pt'} +* हब से एक बड़ा डेटासेट कैसे तैयार किया जाता है +* किसी मॉडल को फाइन-ट्यून करने के लिए उच्च स्तरीय `Trainer` API का उपयोग कैसे करें +* तदनुकूल प्रशिक्षण लूप का उपयोग कैसे करें +* किसी भी वितरित सेटअप पर उस तदनुकूल प्रशिक्षण लूप को आसानी से चलाने के लिए 🤗 एक्सेलेरेट लाइब्रेरी का लाभ कैसे उठाएं + +{:else} +* हब से एक बड़ा डेटासेट कैसे तैयार करें +* मॉडल को फाइन-ट्यून करने के लिए Keras का उपयोग कैसे करें +* पूर्वानुमान लगाने के लिए Keras का उपयोग कैसे करें +* कस्टम मीट्रिक का उपयोग कैसे करें + +{/if} + +हगिंग फेस हब पर अपनी प्रशिक्षित चौकियों को अपलोड करने के लिए, आपको एक huggingface.co खाते की आवश्यकता होगी: [खाता बनाएं](https://huggingface.co/join) \ No newline at end of file diff --git a/chapters/hi/chapter3/2.mdx b/chapters/hi/chapter3/2.mdx new file mode 100644 index 000000000..9105b30cf --- /dev/null +++ b/chapters/hi/chapter3/2.mdx @@ -0,0 +1,381 @@ + + +# डेटा संसाधित करना + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} +[पिछले अध्याय](/course/chapter2) के उदाहरण को जारी रखते हुए, यहां बताया गया है कि हम PyTorch में एक बैच पर अनुक्रम वर्गीकारक को कैसे प्रशिक्षित करेंगे: + +```python +import torch +from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification + +# Same as before +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "This course is amazing!", +] +batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") + +# This is new +batch["labels"] = torch.tensor([1, 1]) + +optimizer = AdamW(model.parameters()) +loss = model(**batch).loss +loss.backward() +optimizer.step() +``` +{:else} +[पिछले अध्याय](/course/chapter2) के उदाहरण को जारी रखते हुए, यहां बताया गया है कि हम TensorFlow में एक बैच पर अनुक्रम वर्गीकारक को कैसे प्रशिक्षित करेंगे: + +```python +import tensorflow as tf +import numpy as np +from transformers import AutoTokenizer, TFAutoModelForSequenceClassification + +# Same as before +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "This course is amazing!", +] +batch = dict(tokenizer(sequences, padding=True, truncation=True, return_tensors="tf")) + +# This is new +model.compile(optimizer="adam", loss="sparse_categorical_crossentropy") +labels = tf.convert_to_tensor([1, 1]) +model.train_on_batch(batch, labels) +``` +{/if} + +बेशक, केवल दो वाक्यों पर मॉडल को प्रशिक्षित करने से बहुत अच्छे परिणाम नहीं मिलेंगे। बेहतर परिणाम प्राप्त करने के लिए, आपको एक बड़ा डेटासेट तैयार करना होगा। + +इस खंड में हम एक उदाहरण के रूप में MRPC (Microsoft Research Paraphrase Corpus) डेटासेट का उपयोग करेंगे, जिसे विलियम बी. डोलन और क्रिस ब्रोकेट द्वारा एक [पेपर](https://www.aclweb.org/anthology/I05-5002.pdf) में पेश किया गया था। डेटासेट में 5,801 वाक्यों के जोड़े हैं, साथ मे एक लेबल जो दर्शाता है कि वे पैराफ्रेज हैं या नहीं (यानी, क्या दोनों वाक्यों का मतलब एक ही है)। हमने इसे इस अध्याय के लिए चुना है क्योंकि यह एक छोटा डेटासेट है, इसलिए इस पर प्रशिक्षण के साथ प्रयोग करना आसान है। + +### हब से डेटासेट लोड करना + +{#if fw === 'pt'} + +{:else} + +{/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 मॉडल के प्रदर्शन को मापने के लिए किया जाता है। + +🤗 डेटासेट लाइब्रेरी एक बहुत ही सरल कमांड प्रदान करती है हब पर डेटासेट को डाउनलोड और कैश करने के लिए। हम MRPC डेटासेट को इस तरह डाउनलोड कर सकते हैं: + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("glue", "mrpc") +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 408 + }) + test: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 1725 + }) +}) +``` + +जैसा कि आप देख सकते हैं, हमें एक `DatasetDict` वस्तु मिला जिसमें प्रशिक्षण सेट, सत्यापन सेट और परीक्षण सेट है। उनमें से प्रत्येक में कई कॉलम (`sentence1`, `sentence2`, `label`, और `idx`) और एक चर पंक्तियों की संख्या, जो प्रत्येक सेट में तत्वों की संख्या है (तो, वाक्यों के 3,668 जोड़े प्रशिक्षण सेट में, 408 सत्यापन सेट में, और परीक्षण सेट में 1,725 है)। + +यह कमांड डेटासेट को डाउनलोड और कैश करता हैं, जो डिफ़ॉल्ट रूप से इस जगह मे *~/.cache/huggingface/dataset* जाता हैं। अध्याय 2 से याद करें कि आप `HF_HOME` पर्यावरण चर सेट करके अपने कैशे फ़ोल्डर को अनुकूलित कर जगह बदल सकते हैं। + +हम अपने `raw_datasets` वस्तु में वाक्यों की प्रत्येक जोड़ी को अनुक्रमणित करके अभिगम कर सकते हैं, जैसे किसी शब्दकोश के साथ: + +```py +raw_train_dataset = raw_datasets["train"] +raw_train_dataset[0] +``` + +```python out +{'idx': 0, + 'label': 1, + 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', + 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} +``` + +हम देख सकते हैं कि लेबल पहले से ही पूर्णांक हैं, इसलिए हमें वहां कोई पूर्व प्रसंस्करण नहीं करना होगा। यह जानने के लिए कि कौन सा पूर्णांक किस लेबल से मेल खाता है, हम अपने `raw_train_dataset` की `features` का निरीक्षण कर सकते हैं। यह हमें प्रत्येक कॉलम का प्रकार बताएगा: + +```py +raw_train_dataset.features +``` + +```python out +{'sentence1': Value(dtype='string', id=None), + 'sentence2': Value(dtype='string', id=None), + 'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None), + 'idx': Value(dtype='int32', id=None)} +``` + +परदे के पीछे, `label` प्रकार `ClassLabel` का है, और पूर्णांक का लेबल नाम से मानचित्रण *names* फ़ोल्डर में संग्रहित किया जाता है। `0` मेल खाता है `not_equivalent` से, और `1` मेल खाता है `equivalent` से। + + + +✏️ **कोशिश करके देखे!** प्रशिक्षण सेट के तत्व 15 और सत्यापन सेट के तत्व 87 को देखें। उनके लेबल क्या हैं? + + + +### डेटासेट का पूर्वप्रक्रमण करना + +{#if fw === 'pt'} + +{:else} + +{/if} + +डेटासेट को पूर्व संसाधित करने के लिए, हमें टेक्स्ट को उन नंबरों में बदलने की जरूरत है, जिन्हें मॉडल समझ सकता है। जैसा कि आपने [पिछले अध्याय](/course/chapter2) में देखा, यह एक टोकननाइज़र के साथ किया जाता है। हम टोकननाइज़र मे एक वाक्य या वाक्यों की एक सूची डाल सकते हैं, ताकि हम सीधे सभी पहले वाक्यों और सभी दूसरे वाक्यों की प्रत्येक जोड़ी को टोकननाइज कर सके इस तरह से : + +```py +from transformers import AutoTokenizer + +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"]) +tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"]) +``` + +हालाँकि, हम केवल दो अनुक्रमों को मॉडल में पारित नहीं कर सकते और प्रिडिक्शन कर सकते कि दो वाक्य पैराफ्रेश हैं या नहीं। हमें दो अनुक्रमों को एक जोड़ी के रूप में संभालने की जरूरत है, और उपयुक्त पूर्व प्रसंस्करण लागू करना है। सौभाग्य से, टोकननाइज़र अनुक्रमों की जोड़ी भी ले सकता है और इसे हमारे BERT मॉडल की अपेक्षा के अनुसार तैयार कर सकता है: + +```py +inputs = tokenizer("This is the first sentence.", "This is the second one.") +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] +} +``` + +हमने [अध्याय 2](/course/chapter2) में `input_ids` और `attention_mask` कुंजियों पर चर्चा की, लेकिन हमने `token_type_ids` के बारे में बात नहीं की। इस उदाहरण में, यह कुंजी मॉडल को बताता है कि इनपुट का कौन सा हिस्सा पहला वाक्य है और कौन सा दूसरा वाक्य है। + + + +✏️ **कोशिश करके देखे!** प्रशिक्षण सेट के तत्व 15 को लें और टोकननाइज करें दो वाक्यों को अलग-अलग और एक जोड़ी के रूप में। दोनों परिणामों में क्या अंतर है? + + + +यदि हम `input_ids` के अंदर IDs को शब्दों में वापस व्याख्या करते हैं: + +```py +tokenizer.convert_ids_to_tokens(inputs["input_ids"]) +``` + +हमें मिलेगा: + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +``` + +तो हम देख सकते हैं कि मॉडल अपेक्षा करता है कि इनपुट का फॉर्म `[CLS] sentence1 [SEP] sentence2 [SEP]` का होगा जब दो वाक्य हों। इसे `token_type_ids` के साथ संरेखित करने से हमें यह मिलता है: + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +[ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] +``` + +जैसा कि आप देख सकते हैं, इनपुट के जो हिस्से `[CLS] sentence1 [SEP]` के अनुरूप है, उन सभी के पास टोकन टाइप आईडी है `0`, जबकि अन्य हिस्से, जो `sentence2 [SEP]` के अनुरूप है, सभी के पास एक टोकन टाइप आईडी है `1`। + +ध्यान दें कि यदि आप एक अलग चेकपॉइंट का चयन करते हैं, तो जरूरी नहीं कि आपके टोकननाइज इनपुट में `token_type_ids` हों (उदाहरण के लिए, यदि आप DistilBERT मॉडल का उपयोग करते हैं तो वे वापस नहीं आते हैं)। उन्हें केवल तभी लौटाया जाता है जब मॉडल को पता चल जाएगा कि उनके साथ क्या करना है, क्योंकि इसने उन्हें अपने पूर्व प्रशिक्षण के दौरान देखा है। + +यहां, BERT को टोकन टाइप आईडी के साथ पूर्व प्रशिक्षित किया गया है, और नकाबपोश भाषा मॉडलिंग का उद्देश्य जिसकी हमने [अध्याय 1](/course/chapter1) में बात की थी के शीर्ष पर, इसका एक अतिरिक्त उद्देश्य है जिसे _अगले वाक्य पूर्वानुमान_ कहा जाता है। इस कार्य का लक्ष्य वाक्यों के जोड़े के बीच संबंध को मॉडल करना है। + +अगले वाक्य पूर्वानुमान के साथ, मॉडल को वाक्यों के जोड़े (बेतरतीब ढंग से नकाबपोश टोकन के साथ) प्रदान किए जाते हैं और पूछा जाता है कि पूर्वानुमान लगाओ कि क्या दूसरा वाक्य पहले का अनुसरण करता है। कार्य को गैर-तुच्छ बनाने के लिए, आधे समय में वाक्य एक-दूसरे का अनुसरण करते हैं मूल दस्तावेज़ में, और दूसरे आधे समय में दो वाक्य दो अलग-अलग दस्तावेज़ों से आते हैं। + +सामान्य तौर पर, आपको इस बारे में चिंता करने की आवश्यकता नहीं है कि आपके टोकननाइज़ड इनपुट में `token_type_ids` हैं या नहीं: जब तक आप टोकननाइज़र और मॉडल के लिए एक ही चेकपॉइंट का उपयोग करते हैं, तब तक सब कुछ ठीक रहेगा क्योंकि टोकननाइज़र जानता है कि उसके मॉडल को क्या प्रदान करना है। + +अब जब हमने देखा कि कैसे हमारा टोकननाइज़र वाक्यों की एक जोड़ी से निपटता है, हम इसका उपयोग अपने पूरे डेटासेट को टोकननाइज़ करने के लिए कर सकते हैं: [पिछले अध्याय](/course/chapter2) की तरह, हम टोकननाइज़र को पहले वाक्यों की सूची, फिर दूसरे वाक्यों की सूची देकर वाक्यों के जोड़े की सूची खिला सकते है। यह पैडिंग और ट्रंकेशन विकल्पों के साथ भी संगत है जिसे हमने [अध्याय 2](/course/chapter2) में देखा था। इसलिए, प्रशिक्षण डेटासेट को पूर्व प्रसंस्करण करने का एक तरीका है: + +```py +tokenized_dataset = tokenizer( + raw_datasets["train"]["sentence1"], + raw_datasets["train"]["sentence2"], + padding=True, + truncation=True, +) +``` + +यह अच्छी तरह से काम करता है, लेकिन इसमें एक शब्दकोश (साथ में हमारी कुंजी, `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()` विधि डेटासेट के प्रत्येक तत्व पर एक फ़ंक्शन लागू करके काम करती है, तो चलिए एक फ़ंक्शन को परिभाषित करते हैं जो हमारे इनपुट को टोकननाइज़ करेगा : + +```py +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) +``` + +यह फ़ंक्शन एक शब्दकोश लेता है (जैसे हमारे डेटासेट के आइटम) और `input_ids`, `attention_mask`, और `token_type_ids` कुंजियों के साथ एक नया शब्दकोश देता है। ध्यान दें कि यह तब भी काम करता है जब `example` शब्दकोश में कई सैम्पल्स हों (प्रत्येक कुंजी वाक्यों की सूची के रूप में) क्योंकि `टोकनाइज़र` वाक्यों के जोड़े की सूची पर काम करता है, जैसा कि पहले देखा गया था। यह हमें हमारे `map()` के कॉल में `batched=True` विकल्प का उपयोग करने की अनुमति देगा, जो टोकनाइजेशन को बहुत तेज करेगा। `टोकनाइज़र` को [🤗 टोकननाइज़रस](https://github.com/huggingface/tokenizers) लाइब्रेरी से टोकननाइज़र जो Rust में लिखा है द्वारा समर्थित किया जाता है। यह टोकननाइज़र बहुत तेज़ हो सकता है, लेकिन केवल तभी जब हम इसे एक साथ ढेर सारे इनपुट दें। + +ध्यान दें कि हमने अभी के लिए अपने टोकननाइजेशन फ़ंक्शन में `पैडिंग` आर्गूमेन्ट को छोड़ दिया है। ऐसा इसलिए है क्योंकि सभी सैम्पल्स को अधिकतम लंबाई तक पैडिंग करना कुशल नहीं है: जब हम बैच बना रहे हों तो सैम्पल्स को पैड करना बेहतर होता है, क्योंकि तब हमें केवल उस बैच में अधिकतम लंबाई तक पैड करने की आवश्यकता होती है, और न कि पुरे डेटासेट मे अधिकतम लंबाई तक। यह बहुत समय और प्रसंस्करण शक्ति को बचा सकता है जब इनपुट में बहुत परिवर्तनशील लंबाई होती है! + +यहां बताया गया है कि हम अपने सभी डेटासेट पर एक बार में टोकननाइजेशन फ़ंक्शन कैसे लागू करते हैं। हम `batched=True` का उपयोग कर रहे है `map` को कॉल करने के लिए, इसलिए फ़ंक्शन हमारे डेटासेट के कई तत्वों पर एक साथ लागू होता है, न कि प्रत्येक तत्व पर अलग से। यह तेजी से पूर्व प्रसंस्करण की अनुमति देता है। + +```py +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) +tokenized_datasets +``` + +🤗 डेटासेट लाइब्रेरी इस प्रसंस्करण को लागू करने के लिए , डेटासेट में नए फ़ील्ड जोड़ते है, जो प्रीप्रोसेसिंग फ़ंक्शन द्वारा लौटाए गए शब्दकोश में प्रत्येक कुंजी के लिए एक होता है: + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 408 + }) + test: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 1725 + }) +}) +``` + +आप बहुप्रक्रमण का भी उपयोग कर सकते हैं बस अपने पूर्व प्रसंस्करण फ़ंक्शन को `map()` के साथ लागू करते समय `num_proc` तर्क को पास करना है। हमने यहां ऐसा नहीं किया क्योंकि 🤗 टोकनाइज़रस लाइब्रेरी पहले से ही हमारे सैम्पल्स को तेज़ी से टोकनाइज़ करने के लिए कई थ्रेड्स का उपयोग करती है, लेकिन यदि आप इस लाइब्रेरी द्वारा समर्थित तेज़ टोकनाइज़र का उपयोग नहीं कर रहे हैं, तो यह आपके पूर्व प्रसंस्करण को गति दे सकता है। + +हमारा `tokenize_function` एक शब्दकोश `input_ids`, `attention_mask`, और `token_type_ids` कुंजियों के साथ देता है, इसलिए उन तीनो क्षेत्रों को हमारे डेटासेट के सभी विभाजनों में जोड़ दिया जाता है। ध्यान दें कि हम मौजूदा फ़ील्डस को भी बदल सकते थे यदि हमारे प्रीप्रोसेसिंग फ़ंक्शन ने डेटासेट में मौजूदा कुंजी के लिए एक नया मान लौटाया होता, जिस पर हमने `map()` लागू किया। + +आखिरी चीज जो हमें करने की आवश्यकता होगी वह है सभी उदाहरणों को सबसे लंबे तत्व की लंबाई तक पैड करना जब हम तत्वों को एक साथ बैच करते हैं — यह एक तकनीक है जिसे हम *डायनामिक पैडिंग* के रूप में संदर्भित करते हैं। + +### डायनामिक पैडिंग + + + +{#if fw === 'pt'} +जो फ़ंक्शन बैच के अंदर सैम्पल्स को एक साथ रखने के लिए जिम्मेदार हो उसे *collate function* कहा जाता है। यह एक आर्गूमेन्ट है जिसे आप एक `DataLoader` बनाते समय पारित कर सकते हैं, वरना एक ऐसा फ़ंक्शन है जो आपके सैम्पल्स को केवल PyTorch टेंसर में बदल देगा और उन्हें जोड़ देगा (पुनरावर्ती यदि आपके तत्व सूचियां, टुपल्स या शब्दकोश हैं)। हमारे मामले में यह संभव नहीं होगा क्योंकि हमारे पास जो इनपुट हैं वे सभी एक ही आकार के नहीं होंगे। हमने जानबूझकर पैडिंग को स्थगित कर दिया है, केवल इसे प्रत्येक बैच पर आवश्यक रूप से लागू करने के लिए और बहुत अधिक पैडिंग के साथ अधिक लंबे इनपुट से बचने के लिए। यह प्रशिक्षण को काफी तेज कर देगा, लेकिन ध्यान दें कि यदि आप TPU पर प्रशिक्षण कर रहे हैं तो यह समस्या पैदा कर सकता है — TPUs निश्चित आकार पसंद करते हैं, तब भी जब इसके लिए अतिरिक्त पैडिंग की आवश्यकता होती है। + +{:else} + +जो फ़ंक्शन बैच के अंदर सैम्पल्स को एक साथ रखने के लिए जिम्मेदार हो उसे *collate function* कहा जाता है। डिफ़ॉल्ट कोलेटर एक ऐसा फ़ंक्शन है जो आपके सैम्पल्स को tf.Tensor में बदल देगा और उन्हें जोड़ देगा (पुनरावर्ती यदि आपके तत्व सूचियां, टुपल्स या शब्दकोश हैं)। हमारे मामले में यह संभव नहीं होगा क्योंकि हमारे पास जो इनपुट हैं वे सभी एक ही आकार के नहीं होंगे। हमने जानबूझकर पैडिंग को स्थगित कर दिया है, केवल इसे प्रत्येक बैच पर आवश्यक रूप से लागू करने के लिए और बहुत अधिक पैडिंग के साथ अधिक लंबे इनपुट से बचने के लिए। यह प्रशिक्षण को काफी तेज कर देगा, लेकिन ध्यान दें कि यदि आप TPU पर प्रशिक्षण कर रहे हैं तो यह समस्या पैदा कर सकता है — TPUs निश्चित आकार पसंद करते हैं, तब भी जब इसके लिए अतिरिक्त पैडिंग की आवश्यकता होती है। + +{/if} + +व्यवहार में ऐसा करने के लिए, हमें एक कोलेट फ़ंक्शन को परिभाषित करना होगा जो उस डेटासेट के आइटम पर सही मात्रा में पैडिंग लागू करेगा जिसे हम एक साथ बैच बनाना हैं। सौभाग्य से, 🤗 ट्रान्सफ़ॉर्मर्स लाइब्रेरी हमें `DataCollatorWithPadding` के माध्यम से ऐसा फ़ंक्शन प्रदान करती है। जब आप इसे इन्स्टैन्शीऐट करते हैं तो यह एक टोकननाइज़र लेता है (यह जानने के लिए कि किस पैडिंग टोकन का उपयोग करना है, और क्या मॉडल को पैडिंग के बाईं ओर या इनपुट के दाईं ओर चाहिए) और वह सब कुछ करेगा जो आपको चाहिए: + +{#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} + +इस नए खिलौने का परीक्षण करने के लिए, आइए हमारे प्रशिक्षण सेट से कुछ सैम्पल्स लें जिन्हें हम एक साथ बैच बनाना चाहेंगे। यहां, हम कॉलम `idx`, `sentence1`, और `sentence2` को हटा देते हैं क्योंकि उनकी आवश्यकता नहीं होगी और इसमें स्ट्रिंग्स होंगे (और हम स्ट्रिंग्स के साथ टेंसर नहीं बना सकते) और आइये बैच में प्रत्येक प्रविष्टि की लंबाई पर एक नज़र डाले: + +```py +samples = tokenized_datasets["train"][:8] +samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]} +[len(x) for x in samples["input_ids"]] +``` + +```python out +[50, 59, 47, 67, 59, 50, 62, 32] +``` + +कोई आश्चर्य नहीं, हमें 32 से 67 तक की अलग-अलग लंबाई के सैम्पल्स मिलते हैं। डायनेमिक पैडिंग का मतलब है कि इस बैच के सभी सैम्पल्स को 67 की लंबाई तक पैड किया जाना चाहिए, जो की सबसे अधिकतम लंबाई है बैच के अंदर की। डायनेमिक पैडिंग के बिना, सभी सैम्पल्स को पूरे डेटासेट में अधिकतम लंबाई तक या मॉडल द्वारा स्वीकार की जा सकने वाली अधिकतम लंबाई तक पैड करना होगा। आइए दोबारा जांचें कि हमारा `data_collator` बैच को डायनेमिकली पैडिंग कर रहा है: + +```py +batch = data_collator(samples) +{k: v.shape for k, v in batch.items()} +``` + +{#if fw === 'tf'} + +```python out +{'attention_mask': TensorShape([8, 67]), + 'input_ids': TensorShape([8, 67]), + 'token_type_ids': TensorShape([8, 67]), + 'labels': TensorShape([8])} +``` + +{:else} + +```python out +{'attention_mask': torch.Size([8, 67]), + 'input_ids': torch.Size([8, 67]), + 'token_type_ids': torch.Size([8, 67]), + 'labels': torch.Size([8])} +``` + +देखने में सही है! अब जबकि हम देख चुके है की हमारा मॉडल कच्चे टेक्स्ट से बैचस तक निपट सकता है, तो अब हम इसे फ़ाइन ट्यून करने के लिए तैयार हैं! + +{/if} + + + +✏️ **कोशिश करके देखे!** कोशिश करके देखे! GLUE SST-2 डेटासेट पर प्रीप्रोसेसिंग को दोहराएं। यह थोड़ा अलग है क्योंकि यह जोड़े के बजाय एकल वाक्यों से बना है, लेकिन बाकी जो हमने किया वो वैसा ही दिखना चाहिए। एक कठिन चुनौती के लिए, एक प्रीप्रोसेसिंग फ़ंक्शन लिखने का प्रयास करें जो किसी भी GLUE कार्यों पर काम करता हो। + + + +{#if fw === 'tf'} + +अब जब हमारे पास हमारे डेटासेट और डेटा कोलेटर हैं, तो हमें उन्हें एक साथ रखना है। हम मैन्युअल रूप से बैचस को लोड कर सकते हैं और उनका मिलान कर सकते हैं, लेकिन यह बहुत काम है, और शायद बहुत अच्छा प्रदर्शन करने वाला भी नहीं है। इसके बजाय, एक सरल विधि है जो इस समस्या का एक निष्पादक समाधान प्रदान करती है: `to_tf_dataset()`। यह एक वैकल्पिक कोलेशन फ़ंक्शन के साथ, आपके डेटासेट के चारों ओर एक `tf.data.Dataset` लपेट देगा। `tf.data.Dataset` एक देशी TensorFlow प्रारूप है जिसे Keras उपयोग करता है `model.fit()` के लिए, इसलिए यह एक विधि तुरंत 🤗 डेटासेट को एक प्रारूप में परिवर्तित कर देती है जो प्रशिक्षण के लिए तैयार है। आइए इसे अपने डेटासेट के साथ क्रिया में देखें! + +```py +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, +) +``` + +और बस! डेटा पूर्व प्रसंस्करण की कड़ी मेहनत के बाद हम उन डेटासेट को अगले व्याख्यान में आगे ले जा सकते हैं, जहां प्रशिक्षण सुखद रूप से सीधा होगा। + +{/if} diff --git a/chapters/hi/chapter3/3.mdx b/chapters/hi/chapter3/3.mdx new file mode 100644 index 000000000..93bcdeb97 --- /dev/null +++ b/chapters/hi/chapter3/3.mdx @@ -0,0 +1,172 @@ + + +# मॉडल कि Trainer API के साथ + + + + + +🤗 ट्रान्सफ़ॉर्मर एक `ट्रेनर` क्लास प्रदान करता है जिससे आपको उपलब्ध कराए गए किसी भी पूर्व-प्रशिक्षित मॉडल को अपने डेटासेट पर फाइन-ट्यून करने में मदद मिलती है। एक बार जब आप अंतिम खंड में सभी डेटा पूर्व प्रसंस्करण कार्य कर लेते हैं, तो आपके पास `ट्रेनर` को परिभाषित करने के लिए बस कुछ ही चरण शेष हैं। सबसे कठिन हिस्सा `Trainer.train()` को चलाने के लिए वातावरण को तैयार करने की संभावना है, क्योंकि यह CPU पर बहुत धीमी गति से चलेगा। यदि आपके पास GPU सेट अप नहीं है, तो आप [Google Colab](https://colab.research.google.com/) पर निःशुल्क GPUs या TPUs का एक्सेस प्राप्त कर सकते हैं। + +नीचे दिए गए कोड उदाहरण मानते हैं कि आपने पिछले खंड में उदाहरणों को पहले ही निष्पादित कर दिया है। यहां एक संक्षिप्त सारांश दिया गया है जिसकी आपको आवश्यकता है: + +```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) +``` + +### प्रशिक्षण + +हमारे `ट्रेनर` को परिभाषित करने से पहले पहला कदम है एक `TrainingArguments` क्लास को परिभाषित करना जिसमें प्रशिक्षण और मूल्यांकन के लिए `ट्रेनर` द्वारा उपयोग किए जाने वाले सभी हाइपरपैरामीटर शामिल होंगे। एकमात्र आर्गूमेन्ट जो आपको प्रदान करना है वह है एक निर्देशिका जहां प्रशिक्षित मॉडल सहेजा जाएगा, साथ ही साथ चौकियों को भी। बाकी सभी के लिए, आप डिफ़ॉल्ट रूप में छोड़ सकते हैं, जो एक बुनियादी फ़ाइन-ट्यूनिंग के लिए बहुत अच्छी तरह से काम करना चाहिए। + +```py +from transformers import TrainingArguments + +training_args = TrainingArguments("test-trainer") +``` + + + +💡 यदि आप प्रशिक्षण के दौरान अपने मॉडल को हब पर स्वचालित रूप से अपलोड करना चाहते हैं, तो आप `TrainingArguments` में `push_to_hub=True` के साथ पास कर सकते हैं। हम इसके बारे में [अध्याय 4](/course/chapter4/3) में और जानेंगे + + + +दूसरा कदम हमारे मॉडल को परिभाषित करना है। [पिछले अध्याय](/course/chapter2) की तरह, हम `AutoModelForSequenceClassification` वर्ग का उपयोग करेंगे, दो लेबल के साथ : + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +आप देखेंगे कि [अध्याय 2](/course/chapter2) के विपरीत, आपको इस पूर्व-प्रशिक्षित मॉडल को इन्स्टैन्शीऐट करने के बाद एक चेतावनी मिलती है। ऐसा इसलिए है क्योंकि BERT को वाक्यों के जोड़े का वर्गीकरण करने के लिए पूर्व प्रशिक्षित नहीं किया गया है, इसलिए पूर्व-प्रशिक्षित मॉडल के प्रमुख को त्याग दिया गया है और इसके बजाये अनुक्रम वर्गीकरण के लिए उपयुक्त एक नया प्रमुख डाला गया है। इन चेतावनियों से संकेत मिलता है कि कुछ वज़न का उपयोग नहीं किया गया था (त्यागे गए पूर्व-प्रशिक्षण के प्रमुख के अनुरूप) और कुछ अन्य क्रमरहित रूप से प्रारंभ किए गए थे (नए प्रमुख के लिए वाले)। यह समापन आपको मॉडल को प्रशिक्षित करने के लिए प्रोत्साहित करने के साथ होगा, जो कि अब हम करने जा रहे हैं। + +एक बार जब हमारे पास हमारा मॉडल होगा, तो हम एक `Trainer` को परिभाषित अब तक की निर्मित सभी वस्तुओं को पास करके कर सकते है — `model`, `training_args`, प्रशिक्षण और सत्यापन डेटासेट, हमारे `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` पास करते हैं जैसा कि हमने यहां किया था, तो `Trainer` द्वारा उपयोग किया जाने वाला डिफ़ॉल्ट `data_colllator` एक `DataCollatorWithPadding` होगा जैसा कि पहले परिभाषित किया गया था, इसलिए आप इस कॉल में `data_collator=data_collator` लाइन को छोड़ सकते हैं। आपको प्रोसेसिंग के इस भाग को खंड 2 में दिखाना फिर भी महत्वपूर्ण था! + +मॉडल को हमारे डेटासेट पर फाइन-ट्यून करने के लिए, हमें बस अपने `Trainer` के `train()` विधि को कॉल करना होगा: + +```py +trainer.train() +``` + +यह फाइन-ट्यूनिंग को शुरू करेगा (जिसमें GPU पर कुछ मिनट लगने चाहिए) और हर 500 कदम पर प्रशिक्षण लॉस की रिपोर्ट करेगा । हालांकि, यह आपको यह नहीं बताएगा कि आपका मॉडल कितना अच्छा (या खराब) प्रदर्शन कर रहा है। यह है क्योंकि: + +1. हमने `Trainer` को नहीं बताया की प्रशिक्षण के दौरान मूल्यांकन करने के लिए `evaluation_strategy` को या तो `"steps"`(हर `eval_steps` का मूल्यांकन करें) या `"epoch"` (प्रत्येक एपॉक के अंत में मूल्यांकन) को सेट करे। +2. हमने `Trainer` को `compute_metrics()` फ़ंक्शन के साथ प्रदान नहीं किया जो मूल्यांकन के दौरान मीट्रिक की गणना करता है (अन्यथा मूल्यांकन ने केवल लॉस को मुद्रित किया होगा, जो बहुत सहज संख्या नहीं है) + + +### मूल्यांकन + +आइए देखें कि हम एक उपयोगी `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()` फ़ंक्शन पूरा कर लेते हैं और इसे `ट्रेनर` को पास कर देते हैं, तो उस फ़ील्ड में `compute_metrics()` द्वारा लौटाए गए मीट्रिक भी शामिल होंगे। + +जैसा कि आप देख सकते हैं, `predictions` एक 2-डिमेन्शनल सरणी है जिसका आकार 408 x 2 (408 हमारे द्वारा उपयोग किए गए डेटासेट में तत्वों की संख्या है)। वे डेटासेट के प्रत्येक तत्व के लिए लॉगिट हैं जिन्हें हमने `predict()` में पास किया है (जैसा कि आपने [पिछले अध्याय](/course/chapter2) में देखा था, सभी ट्रांसफॉर्मर मॉडल लॉगिट लौटाते हैं)। उन्हें भविष्यवाणियों यानि प्रिडिक्शन्स में बदलने के लिए जिन्हें हम अपने लेबल से तुलना कर सकते हैं, हमें दूसरी एक्सिस पर अधिकतम मूल्य के साथ इन्डेक्स लेने की आवश्यकता है: + +```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} +``` + +आपको मिलने वाले सटीक परिणाम अलग-अलग हो सकते हैं, क्योंकि मॉडल हेड के क्रमरहित इनिशियलाइज़ेशन से प्राप्त मेट्रिक्स में बदलाव हो सकता है। यहां, हम देख सकते हैं कि हमारे मॉडल का सत्यापन सेट पर 85.78% की सटीकता है और 89.97 का F1 स्कोर है। वे दो मेट्रिक्स हैं जिनका उपयोग GLUE बेंचमार्क के लिए MRPC डेटासेट पर परिणामों का मूल्यांकन करने के लिए किया जाता है। [BERT पेपर](https://arxiv.org/pdf/1810.04805.pdf) में टेबल ने बेस मॉडल के लिए F1 स्कोर 88.9 बताया। वह एक `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) +``` + +और इसे प्रत्येक एपॉक के अंत में मेट्रिक्स की रिपोर्ट करने के लिए इसके उपयोग की क्रिया को देखने के लिए, यहां बताया गया है कि हम इस `compute_metrics()` फ़ंक्शन के साथ एक नया `Trainer` कैसे परिभाषित करते हैं: + +```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() +``` + +इस बार, यह सत्यापन लॉस और मेट्रिक्स की रिपोर्ट हर एपॉक के अंत में प्रशिक्षण लॉस के ऊपर करेगा। फिर से, एक सटीक एक्यूरेसी/F1 स्कोर जिसपे हम पहुंचे थोड़ा अलग हो सकता है उससे जो हमने पाया, क्योंकि मॉडल के रैंडम हेड इनिशियलाइज़ेशन के कारण, लेकिन यह उसी बॉलपार्क में होना चाहिए। + +`Trainer` कई GPUs या TPUs पर बिलकुल हटकर काम करेगा और बहुत सारे विकल्प प्रदान करता है, जैसे मिश्रित-सटीक प्रशिक्षण (अपने प्रशिक्षण आर्गूमेन्ट में `fp16 = True` का उपयोग करें)। हम अध्याय 10 में इसके द्वारा समर्थित हर चीज पर अध्ययन करेंगे। + +यह `Trainer` API का उपयोग करके फाइन-ट्यूनिंग के परिचय को समाप्त करता है। अधिकांश सामान्य NLP कार्यों के लिए ऐसा करने का एक उदाहरण [अध्याय 7](course/chapter7) में दिया जाएगा, लेकिन अभी के लिए आइए देखें कि शुद्ध PyTorch में वही काम कैसे करें। + + + +✏️ **कोशिश करके देखे!** GLUE SST-2 डेटासेट पर एक मॉडल को फाइन-ट्यून करें, डेटा प्रसंस्करण यानि डेटा प्रोसेसिंग का उपयोग करके जिसे आपने सेक्शन 2 में किया था + + + diff --git a/chapters/hi/chapter3/3_tf.mdx b/chapters/hi/chapter3/3_tf.mdx new file mode 100644 index 000000000..84f022ead --- /dev/null +++ b/chapters/hi/chapter3/3_tf.mdx @@ -0,0 +1,199 @@ + + +# मॉडल कि फाइन-ट्यूनिंग Keras के साथ + + + +एक बार जब आप अंतिम खंड में सभी डेटा पूर्व प्रसंस्करण कार्य कर लेते हैं, तो आपके पास मॉडल को प्रशिक्षित करने के लिए बस कुछ ही चरण शेष हैं। हालाँकि, ध्यान दें कि `model.fit()` कमांड CPU पर बहुत धीमी गति से चलेगा। यदि आपके पास GPU सेट अप नहीं है, तो आप [Google Colab](https://colab.research.google.com/) पर निःशुल्क GPU या TPU का एक्सेस प्राप्त कर सकते हैं। + +नीचे दिए गए कोड उदाहरण मानते हैं कि आपने पिछले खंड में उदाहरणों को पहले ही निष्पादित कर दिया है। यहां एक संक्षिप्त सारांश दिया गया है जिसकी आपको आवश्यकता है: + +```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, +) +``` + +### प्रशिक्षण + +🤗 ट्रांसफॉर्मर से आयात किए गए TensorFlow मॉडल पहले से ही Keras मॉडल हैं। यहाँ Keras का संक्षिप्त परिचय दिया गया है। + + + +इसका मतलब है कि एक बार जब हमारे पास हमारा डेटा होता है, तो उस पर प्रशिक्षण शुरू करने के लिए बहुत कम काम करने की आवश्यकता होती है। + + + +[पिछले अध्याय](/course/chapter2) की तरह, हम `TFAutoModelForSequenceClassification` क्लास का उपयोग दो लेबल के साथ करेंगे: + +```py +from transformers import TFAutoModelForSequenceClassification + +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +आप देखेंगे कि [अध्याय 2](/course/chapter2) के विपरीत, आपको इस पूर्व-प्रशिक्षित मॉडल को इन्स्टैन्शीऐट करने के बाद एक चेतावनी मिलती है। ऐसा इसलिए है क्योंकि BERT को वाक्यों के जोड़े का वर्गीकरण करने के लिए पूर्व प्रशिक्षित नहीं किया गया है, इसलिए पूर्व-प्रशिक्षित मॉडल के प्रमुख को त्याग दिया गया है और इसके बजाये अनुक्रम वर्गीकरण के लिए उपयुक्त एक नया प्रमुख डाला गया है। इन चेतावनियों से संकेत मिलता है कि कुछ वज़न का उपयोग नहीं किया गया था (त्यागे गए पूर्व-प्रशिक्षण के प्रमुख के अनुरूप) और कुछ अन्य क्रमरहित रूप से प्रारंभ किए गए थे (नए प्रमुख के लिए वाले)। यह समापन आपको मॉडल को प्रशिक्षित करने के लिए प्रोत्साहित करने के साथ होगा, जो कि अब हम करने जा रहे हैं। + +अपने डेटासेट पर मॉडल को फाइन-ट्यून करने के लिए, हमें बस अपने मॉडल को `compile()` करना होगा और फिर अपने डेटा को `fit()` विधि में पास करना होगा। यह फ़ाइन-ट्यूनिंग प्रक्रिया को शुरू करेगा (जो GPU पर कुछ मिनट लेगा) और आगे जा कर यह हर युग यानि एपॉच के अंत में प्रशिक्षण हानि यानि लॉस साथ ही सत्यापन हानि की रिपोर्ट करेगा। + + + +ध्यान दें कि 🤗 ट्रांसफॉर्मर मॉडल में एक विशेष क्षमता होती है जो कि अधिकांश Keras मॉडल नहीं होती - वे स्वचालित रूप से एक उचित हानि यानि लॉस का उपयोग कर सकते हैं जिसे वे आंतरिक रूप से गणना करते हैं। वे डिफ़ॉल्ट रूप से इस लॉस का उपयोग करेगा अगर आप `compile()` में लॉस आर्गूमेन्ट सेट नहीं करते हैं तो। ध्यान दें कि आंतरिक लॉस का उपयोग करने के लिए आपको अपने लेबल को इनपुट के हिस्से के रूप में पास करना होगा, न कि एक अलग लेबल के रूप में, जो कि Keras मॉडल के साथ लेबल का उपयोग करने का सामान्य तरीका है। आप पाठ्यक्रम के भाग 2 में इसके उदाहरण देखेंगे, जहां सही लॉस फ़ंक्शन को परिभाषित करना पेचीदा हो सकता है। अनुक्रम वर्गीकरण के लिए, हालांकि, एक मानक Keras लॉस फ़ंक्शन ठीक काम करता है, इसलिए हम यहां इसका उपयोग करेंगे। + + + +```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, +) +``` + + + +यहां एक बहुत ही सामान्य नुकसान पर ध्यान दें - आप केवल लॉस का नाम स्ट्रिंग के रूप मे Keras को पास *कर सकते* है, लेकिन डिफ़ॉल्ट रूप से Keras यह मानेगा कि आपने पहले ही अपने आउटपुट में सॉफ्टमैक्स लागू कर दिया है। हालाँकि, कई मॉडल सॉफ्टमैक्स लागू होने से ठीक पहले मानों यानि वैल्यूज़ को आउटपुट करते हैं, जिन्हें *logits* के रूप में भी जाना जाता है। हमें लॉस फ़ंक्शन को यह बताने की आवश्यकता है कि हमारा मॉडल क्या करता है, और ऐसा करने का एकमात्र तरीका है कि इसे सीधे कॉल करना, बजाय एक स्ट्रिंग के नाम से। + + + + +### प्रशिक्षण प्रदर्शन में सुधार करना + + + +यदि आप उपर दिए गए कोड का प्रयास करते हैं, तो यह निश्चित रूप से चलता है, लेकिन आप पाएंगे कि लॉस केवल धीरे-धीरे या छिटपुट रूप से घटता +है। इसका मुख्य कारण है सीखने की दर यानि *लर्निंग रेट*। लॉस के साथ, जब हम Keras को ऑप्टिमाइज़र का नाम स्ट्रिंग के रूप में पास करते है, तो +Keras उस ऑप्टिमाइज़र को लर्निंग रेट सहित सभी मापदंडों के लिए डिफ़ॉल्ट वैल्यूज़ के साथ आरंभ यानि इनिशलाइज़ करता है। लंबे अनुभव से, +हालांकि, हम जानते हैं कि ट्रांसफॉर्मर मॉडल डिफ़ॉल्ट एडम की तुलना में बहुत कम लर्निंग रेट से लाभ होता हैं, जो कि 1e-3 है, जिसे 10 की पॉवर -3 या +0.001 के रूप में भी लिखा जाता है। 5e-5 (0.00005), जो कुछ बीस गुना कम है, एक बेहतर प्रारंभिक बिंदु है। + +सीखने की दर यानि लर्निंग रेट को कम करने के अलावा, हमारे पास एक दूसरी चाल है: हम प्रशिक्षण के दौरान लर्निंग रेट को +धीरे-धीरे कम कर सकते हैं । साहित्य में, आप कभी-कभी इसे *क्षय* या *एनीलिंग* लर्निंग रेट के रूप में संदर्भित देखेंगे। +केरस में, ऐसा करने का सबसे अच्छा तरीका एक *लर्निंग रेट शेड्यूलर* का उपयोग करना है। उपयोग करने के लिए एक अच्छा है +`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) +``` + + + +💡 यदि आप प्रशिक्षण के दौरान अपने मॉडल को हब पर स्वचालित रूप से अपलोड करना चाहते हैं, तो आप `model.fit()` विधि में `PushToHubCallback` के साथ पास कर सकते हैं। हम इसके बारे में [अध्याय 4](/course/chapter4/3) में और जानेंगे + + + +### मॉडल के पूर्वानुमान + + + + +प्रशिक्षण और लॉस को कम होते देखना बहुत अच्छा है, लेकिन क्या होगा अगर हम वास्तव में प्रशिक्षित मॉडल से आउटपुट प्राप्त करना चाहते हैं, या तो कुछ मेट्रिक्स की गणना करने के लिए, या उत्पादन में मॉडल का उपयोग करने के लिए? ऐसा करने के लिए, हम केवल `predict()` विधि का उपयोग कर सकते हैं। यह एक प्रति क्लास, मॉडल के आउटपुट हेड से *logits* लौटाएगा। + +```py +preds = model.predict(tf_validation_dataset)["logits"] +``` + +हम उच्चतम लॉगिट् को खोजने के लिए `argmax` का उपयोग करके इन लॉगिट्स को मॉडल के क्लास पूर्वानुमान में बदल सकते हैं, जो सबसे संभावित क्लास से मेल खाता है: + +```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} +``` + +आपको मिलने वाले सटीक परिणाम अलग-अलग हो सकते हैं, क्योंकि मॉडल हेड के क्रमरहित इनिशियलाइज़ेशन से प्राप्त मेट्रिक्स में बदलाव हो सकता है। यहां, हम देख सकते हैं कि हमारे मॉडल का सत्यापन सेट पर 85.78% की सटीकता है और 89.97 का F1 स्कोर है। वे दो मेट्रिक्स हैं जिनका उपयोग GLUE बेंचमार्क के लिए MRPC डेटासेट पर परिणामों का मूल्यांकन करने के लिए किया जाता है। [BERT पेपर](https://arxiv.org/pdf/1810.04805.pdf) में टेबल ने बेस मॉडल के लिए F1 स्कोर 88.9 बताया। वह एक `uncased` मॉडल था जबकि हम वर्तमान में `cased` मॉडल का उपयोग कर रहे हैं, जो बेहतर परिणाम की व्याख्या करता है। + +यह Keras API का उपयोग करके फाइन-ट्यूनिंग के परिचय को समाप्त करता है। अधिकांश सामान्य NLP कार्यों के लिए ऐसा करने का एक उदाहरण [अध्याय 7](course/chapter7) में दिया जाएगा। यदि आप Keras API पर अपने कौशल को सुधारना चाहते हैं, तो GLUE SST-2 डेटासेट पर एक मॉडल को फाइन-ट्यून करने का प्रयास करें, डेटा प्रसंस्करण यानि डेटा प्रोसेसिंग का उपयोग करके जिसे आपने सेक्शन 2 में किया था diff --git a/chapters/hi/chapter3/4.mdx b/chapters/hi/chapter3/4.mdx new file mode 100644 index 000000000..10e690a1b --- /dev/null +++ b/chapters/hi/chapter3/4.mdx @@ -0,0 +1,359 @@ +# एक पूर्ण प्रशिक्षण + + + + + +अब हम देखेंगे कि `Trainer` क्लास का उपयोग किए बिना कैसे हम समान परिणाम प्राप्त करे जैसा की हमने पिछले खंड प्राप्त किया था। फिर से, हम मानते हैं कि आपने अनुभाग 2 में डेटा प्रसंस्करण यानि डेटा प्रोसेसिंग कर ली है। यहां एक संक्षिप्त सारांश दिया गया है जो वह सब कुछ शामिल कर रहा है जिसकी आपको आवश्यकता होगी: + +```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) +``` + +### प्रशिक्षण के लिए तैयार करें + +हमारे प्रशिक्षण लूप वास्तव में लिखने से पहले, हमें कुछ वस्तुओं को परिभाषित करने की आवश्यकता होगी। पहले है डेटालोडर्स जिनका उपयोग हम बैचों पर पुनरावृति करने के लिए करेंगे। लेकिन इससे पहले कि हम उन डेटालोडर्स को परिभाषित कर सके, हमें अपने `tokenized_datasets` में कुछ पोस्टप्रोसेसिंग लागू करने की जरूरत है, ताकि कुछ चीजों का ख्याल रखा जा सके जो `Trainer` ने हमारे लिए स्वचालित रूप से किया था। विशेष रूप से, हमें जरूरत है की: + +- उन वैल्यूज के अनुरूप कॉलम निकालें जिनकी मॉडल अपेक्षा नहीं करता (जैसे `sentence1` और `sentence2` कॉलम)। +- कॉलम `label` का नाम बदलकर `labels` कर दें (क्योंकि मॉडल उम्मीद करता है की वितर्क का नाम `labels` हो)। +- डेटासेट का प्रारूप सेट करें ताकि वे सूचियों के बजाय PyTorch टेंसर लौटाएं। + +हमारे `tokenized_datasets` में उनमे से प्रत्येक चरण के लिए एक विधि है: + +```py +tokenized_datasets = tokenized_datasets.remove_columns(["sentence1", "sentence2", "idx"]) +tokenized_datasets = tokenized_datasets.rename_column("label", "labels") +tokenized_datasets.set_format("torch") +tokenized_datasets["train"].column_names +``` + +हम फिर जांच सकते हैं कि परिणाम में केवल कॉलम है जिन्हें हमारा मॉडल स्वीकार करेगा: + +```python +["attention_mask", "input_ids", "labels", "token_type_ids"] +``` + +अब जब यह हो गया है, तो हम आसानी से अपने डेटालोडर्स को परिभाषित कर सकते हैं: + +```py +from torch.utils.data import DataLoader + +train_dataloader = DataLoader( + tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator +) +eval_dataloader = DataLoader( + tokenized_datasets["validation"], batch_size=8, collate_fn=data_collator +) +``` + +यह जांचने के लिए कि डेटा प्रोसेसिंग में कोई गलती तो नहीं है, हम इस तरह एक बैच का निरीक्षण कर सकते हैं: + +```py +for batch in train_dataloader: + break +{k: v.shape for k, v in batch.items()} +``` + +```python out +{'attention_mask': torch.Size([8, 65]), + 'input_ids': torch.Size([8, 65]), + 'labels': torch.Size([8]), + 'token_type_ids': torch.Size([8, 65])} +``` + +ध्यान दें कि वास्तविक आकार आपके लिए शायद थोड़ा अलग होगा क्योंकि हमने प्रशिक्षण डेटालोडर के लिए `shuffle=True` सेट किया है और हम बैच के अंदर अधिकतम लंबाई तक पैडिंग कर रहे हैं। + +अब जबकि हम डेटा प्रीप्रोसेसिंग (एक संतोषजनक लेकिन मायावी लक्ष्य किसी भी ML प्रैक्टिशनर के लिए) के साथ पूरी तरह से समाप्त कर चुके हैं, आइए मॉडल की ओर मुड़ें। हम इसे ठीक वैसे ही इन्स्टैन्शीऐट करते हैं जैसे हमने पिछले सेक्शन में किया था: + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +यह सुनिश्चित करने के लिए कि प्रशिक्षण के दौरान सब कुछ सुचारू रूप से चले, हम अपने बैच को इस मॉडल में पास करते हैं: + +```py +outputs = model(**batch) +print(outputs.loss, outputs.logits.shape) +``` + +```python out +tensor(0.5441, grad_fn=) torch.Size([8, 2]) +``` + +सभी 🤗 ट्रांसफॉर्मर मॉडल लॉस लौटाएंगे जब `labels` प्रदान किया जाते है, और हमें logits भी मिलते हैं (हमारे बैच में प्रत्येक इनपुट के लिए दो, इसलिए टेंसर आकार का 8 x 2)। + +हम अपना प्रशिक्षण लूप लिखने के लिए लगभग तैयार हैं! हम केवल दो चीजें खो रहे हैं: एक ऑप्टिमाइज़र और एक लर्निंग रेट अनुसूचक। चूंकि `Trainer` जो कर रहा था उसे हम खुद से दोहराने की कोशिश कर रहे हैं, तो हम उन्ही डिफ़ॉल्ट का उपयोग करेंगे। `Trainer` द्वारा उपयोग किया जाने वाला ऑप्टिमाइज़र `AdamW` है, जो Adam के समान है, लेकिन एक मोड़ के साथ वजन क्षय नियमितीकरण के लिए (इल्या लोशिलोव और फ्रैंक हटर द्वारा ["डीकपलड वेट डेके रेगुलराइजेशन"](https://arxiv.org/abs/1711.05101) देखें): + +```py +from transformers import AdamW + +optimizer = AdamW(model.parameters(), lr=5e-5) +``` + +अंत में, लर्निंग रेट अनुसूचक जिसे डिफ़ॉल्ट रूप से उपयोग किया जाता है केवल एक रैखिक क्षय है जो अधिकतम मूल्य (5e-5) से 0 तक है। इसे ठीक से परिभाषित करने के लिए, हमें यह जानना होगा कि हम कितने प्रशिक्षण कदम उठाएंगे, जो कि है युगों यानि एपोक की संख्या जिन्हे हमे रन करना है उसका गुणा प्रशिक्षण बैचों की संख्या से करना (जो कि हमारे प्रशिक्षण डेटालोडर की लंबाई है)। `Trainer` डिफ़ॉल्ट रूप से तीन युगों यानि एपोक का उपयोग करता है, इसलिए हम उसका अनुसरण करेंगे: + +```py +from transformers import get_scheduler + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) +print(num_training_steps) +``` + +```python out +1377 +``` + +### ट्रेनिंग लूप + +एक आखिरी बात: हम GPU का उपयोग करना चाहेंगे अगर हमारे पास एक का एक्सेस है तो (CPU पर, प्रशिक्षण में कुछ मिनटों के बजाय कई घंटे लग सकते हैं)। ऐसा करने के लिए, हम एक `device` को परिभाषित करेंगे, जिस पर हम अपने मॉडल को और अपने बैचों को रखेंगे: + +```py +import torch + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) +device +``` + +```python out +device(type='cuda') +``` + +अब हम प्रशिक्षण के लिए तैयार हैं! यह जानने के लिए कि प्रशिक्षण कब समाप्त होगा, हम `tqdm` लाइब्रेरी का उपयोग करके अपने प्रशिक्षण चरणों की संख्या पर एक प्रगति पट्टी जोड़ेगे: + +```py +from tqdm.auto import tqdm + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +आप देख सकते हैं कि प्रशिक्षण लूप का मूल जो परिचय में है उसके समान दिखता है। हमने कोई रिपोर्टिंग नहीं मांगी, इसलिए यह प्रशिक्षण लूप हमें इस बारे में कुछ नहीं बताएगा कि मॉडल का किराया कैसा है। हमें उसके लिए एक मूल्यांकन लूप जोड़ने की जरूरत है। + + +### मूल्यांकन लूप + +जैसा कि हमने पहले किया था, हम 🤗 डेटासेट लाइब्रेरी द्वारा प्रदान किए गए मीट्रिक का उपयोग करेंगे। हम पहले ही `metric.compute()` विधि देख चुके हैं, लेकिन मेट्रिक्स वास्तव में हमारे लिए बैच जमा कर सकते हैं जब हम भविष्यवाणी लूप पर जाते हैं `add_batch()` विधि के साथ । एक बार जब हम सभी बैचों को जमा कर लेते हैं, तो हम `metric.compute()` के साथ अंतिम परिणाम प्राप्त कर सकते हैं। मूल्यांकन लूप में इन सभी को कार्यान्वित करने का तरीका यहां दिया गया है: + +```py +from datasets import load_metric + +metric = load_metric("glue", "mrpc") +model.eval() +for batch in eval_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + with torch.no_grad(): + outputs = model(**batch) + + logits = outputs.logits + predictions = torch.argmax(logits, dim=-1) + metric.add_batch(predictions=predictions, references=batch["labels"]) + +metric.compute() +``` + +```python out +{'accuracy': 0.8431372549019608, 'f1': 0.8907849829351535} +``` + +फिर से, मॉडल हेड इनिशियलाइज़ेशन और डेटा फेरबदल में क्रमरहित होने के कारण आपके परिणाम थोड़े भिन्न होंगे, लेकिन वे एक ही बॉलपार्क में होने चाहिए। + + + +✏️ **कोशिश करके देखे!** पिछले प्रशिक्षण लूप को संशोधित करें ताकि अपने मॉडल को SST-2 डेटासेट पर फाइन-ट्यून कर सके। + + + +### अपने प्रशिक्षण लूप को सुपरचार्ज करें 🤗 Accelerate के साथ। + + + +हमने पहले जो ट्रेनिंग लूप परिभाषित किया था, वह सिंगल CPU या GPU पर ठीक काम करता है। लेकिन [🤗 Accelerate](https://github.com/huggingface/accelerate) लाइब्रेरी का उपयोग करके, बस कुछ समायोजन के साथ हम कई GPUs या TPUs पर वितरित प्रशिक्षण को सक्षम कर सकते हैं। शुरुआत प्रशिक्षण और सत्यापन डेटा लोडर के निर्माण से हुई, यहाँ हमारा मैनुअल प्रशिक्षण लूप कैसा दिखता है: + +```py +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +और परिवर्तन यहाँ हैं: + +```diff ++ from accelerate import Accelerator + from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + ++ accelerator = Accelerator() + + model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) + optimizer = AdamW(model.parameters(), lr=3e-5) + +- device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +- model.to(device) + ++ train_dataloader, eval_dataloader, model, optimizer = accelerator.prepare( ++ train_dataloader, eval_dataloader, model, optimizer ++ ) + + num_epochs = 3 + num_training_steps = num_epochs * len(train_dataloader) + lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps + ) + + progress_bar = tqdm(range(num_training_steps)) + + model.train() + for epoch in range(num_epochs): + for batch in train_dataloader: +- batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss +- loss.backward() ++ accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +सबसे पहली लाइन जो जोड़नी है वो है इम्पोर्ट लाइन। दूसरी लाइन एक `Accelerator` वस्तु को इन्स्टैन्शीऐट करती है जो वातावरण को देखेगी और उचित वितरित सेटअप को इनिशियलाइज़ करेगी। 🤗 Accelerate आपके लिए डिवाइस प्लेसमेंट को हैंडल करता है, ताकि आप उन लाइनों को हटा सकें जो मॉडल को डिवाइस पर रखती हैं (या, यदि आप चाहें, तो उन्हें `device` के बजाय `accelerator.device` का उपयोग करने के लिए बदलें)। + +फिर काम का मुख्य हिस्सा उस लाइन में किया जाता है जो डेटालोडर्स, मॉडल और ऑप्टिमाइज़र को `accelerator.prepare()` पर भेजता है। यह उन वस्तुओं को उचित कंटेनर में लपेट देगा ताकि यह सुनिश्चित हो सके कि आपका वितरित प्रशिक्षण उद्देश्य के अनुसार काम करता है। शेष परिवर्तन है उस लाइन को हटाना जो बैच को `device` पर रखता है (फिर से, यदि आप इसे रखना चाहते हैं तो आप इसे केवल `accelerator.device` का उपयोग करने के लिए बदल सकते हैं) और `loss.backward()` को `accelerator.backward(loss)` के साथ बदलना। + + +⚠️ Cloud TPUs द्वारा पेश किए गए स्पीड-अप से लाभ उठाने के लिए, हम अनुशंसा करते हैं कि आप अपने सैम्पल्स को टोकननाइज़र के `padding="max_length"` और `max_length` प्राचल यानि आर्गुमेंट के साथ एक निश्चित लंबाई तक पैडिंग करें। + + +यदि आप इसे खेलने के लिए कॉपी और पेस्ट करना चाहते हैं, तो यहां बताया गया है कि 🤗 Accelerate के साथ पूरा प्रशिक्षण लूप कैसा दिखता है: + +```py +from accelerate import Accelerator +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +accelerator = Accelerator() + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +train_dl, eval_dl, model, optimizer = accelerator.prepare( + train_dataloader, eval_dataloader, model, optimizer +) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dl) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dl: + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +इसे एक `train.py` स्क्रिप्ट में रखने से वह स्क्रिप्ट किसी भी प्रकार के वितरित सेटअप पर चलने योग्य हो जाएगी। इसे अपने वितरित सेटअप में आज़माने के लिए, कमांड चलाएँ: + +```bash +accelerate config +``` + +जो आपको कुछ सवालों के जवाब देने के लिए प्रेरित करेगा और इस कमांड द्वारा उपयोग की जाने वाली कॉन्फ़िगरेशन फ़ाइल में आपके उत्तरों को डंप कर देगा: + +``` +accelerate launch train.py +``` + +जो वितरित प्रशिक्षण को शुरू करेगा। + +यदि आप इसे नोटबुक में आज़माना चाहते हैं (उदाहरण के लिए, Colab पर TPUs के साथ इसका परीक्षण करने के लिए), तो बस कोड को `training_function()` में पेस्ट करें और एक अंतिम सेल चलाएँ साथ में: + +```python +from accelerate import notebook_launcher + +notebook_launcher(training_function) +``` + +आप कई अधिक उदाहरण [🤗 Accelerate repo](https://github.com/huggingface/accelerate/tree/main/examples) में पा सकते है। diff --git a/chapters/hi/chapter3/5.mdx b/chapters/hi/chapter3/5.mdx new file mode 100644 index 000000000..817398e8d --- /dev/null +++ b/chapters/hi/chapter3/5.mdx @@ -0,0 +1,20 @@ + + +# फाइन-ट्यूनिंग, चेक! + +काफी मजेदार था! पहले दो अध्यायों में आपने मॉडल और टोकननाइज़रस के बारे में सीखा, और अब आप जानते हैं कि उन्हें अपने डेटा के लिए कैसे ठीक यानि फाइन-ट्यून किया जाए। संक्षेप में, इस अध्याय में आपने: + +{#if fw === 'pt'} +* [हब](https://huggingface.co/datasets) में डेटासेट के बारे में सीखा। +* डायनेमिक पैडिंग और कोलेटर्स का उपयोग करने सहित डेटासेट को लोड और पूर्व प्रसंस्करण यानि प्रीप्रोसेस करना सीखा । +* अपने खुद की मॉडल की फाइन-ट्यूनिंग और मूल्यांकन को इम्प्लमेन्ट किया। +* निचले-स्तर के प्रशिक्षण लूप को इम्प्लमेन्ट किया +* आपके प्रशिक्षण लूप को आसानी से अनुकूलित करने के लिए 🤗 Accelerate का उपयोग किया ताकि यह कई GPUs या TPUs के लिए काम करे। + +{:else} +* [हब](https://huggingface.co/datasets) में डेटासेट के बारे में सीखा। +* डेटासेट को लोड और पूर्व प्रसंस्करण यानि प्रीप्रोसेस करना सीखा। +* Keras के साथ एक मॉडल को फाइन-ट्यून और मूल्यांकन करना सीखा। +* एक कस्टम मीट्रिक इम्प्लमेन्ट किया गया + +{/if} diff --git a/chapters/hi/chapter3/6.mdx b/chapters/hi/chapter3/6.mdx new file mode 100644 index 000000000..65fdd2e63 --- /dev/null +++ b/chapters/hi/chapter3/6.mdx @@ -0,0 +1,296 @@ + + + + +# अध्याय-का-अंत प्रश्नोत्तरी + +इस अध्याय में आपने जो सीखा, उसका परीक्षण करें! + +### 1. `इमोशन` डेटासेट में ट्विटर संदेश है जिनहे भावनाओं के साथ लेबल किया गया है। इसे [हब](https://huggingface.co/datasets) में खोजें, और डेटासेट कार्ड पढ़ें। इनमें से कौन सा इसकी मूल भावनाओं में से एक नहीं है? + + + +### 2. [हब](https://huggingface.co/datasets) में `ar_sarcasm` डेटासेट खोजें। यह कौन से कार्य का समर्थन करता है? + +डेटासेट कार्ड पर एक और नज़र डालें !" + }, + { + text: "नैम्ड एन्टिटी रेकग्निशन", + explain: "यह बात नहीं है — डेटासेट कार्ड पर एक और नज़र डालें !" + }, + { + text: "क्वेश्चन आंसरिंग", + explain: "हाय! इस प्रश्न का उत्तर सही नहीं दिया। पुनः प्रयास करें!" + } + ]} +/> + +### 3. BERT मॉडल वाक्यों की एक जोड़ी को कैसे संसाधित करने की अपेक्षा करता है? + +[SEP] विशेष टोकन की आवश्यकता है, लेकिन केवल यही एक चीज नहीं है!" + }, + { + text: "[CLS] वाक्य_के_टोकन_1 वाक्य_के_टोकन_2", + explain: "शुरुआत में एक [CLS] विशेष टोकन की आवश्यकता होती है, लेकिन केवल यही एक चीज नहीं है!" + }, + { + text: "[CLS] वाक्य_के_टोकन_1 [SEP] वाक्य_के_टोकन_2 [SEP]", + explain: "यह सही है!", + correct: true + }, + { + text: "[CLS] वाक्य_के_टोकन_1 [SEP] वाक्य_के_टोकन_2", + explain: "शुरुआत में एक [CLS] विशेष टोकन की आवश्यकता होती है और साथ ही साथ दो वाक्यों को अलग करने के लिए एक [SEP] विशेष टोकन की आवश्यकता होती है, लेकिन यही सब नहीं है!" + } + ]} +/> + +{#if fw === 'pt'} +### 4. `Dataset.map()` विधि के क्या लाभ हैं? + + + +### 5. डायनेमिक पैडिंग का क्या अर्थ है? + + + +### 6. कोलेट फ़ंक्शन का उद्देश्य क्या है? + +DataCollatorWithPadding की।" + }, + { + text: "यह सभी सैम्पल्स को एक बैच में एक साथ रखता है।", + explain: "सही! आप कोलेट फ़ंक्शन को DataLoader के वितर्क के रूप में पास कर सकते हैं। हमने DataCollatorWithPadding फ़ंक्शन का उपयोग किया है, जो एक बैच में सभी आइटम्स को पैड करता है ताकि उनकी लंबाई समान हो।", + correct: true + }, + { + text: "यह पूरे डेटासेट को पूर्व प्रसंस्करण यानि प्रीप्रोसेस करता है।", + explain: "यह एक प्रीप्रोसेसिंग फ़ंक्शन होगा, न कि कोलेट फ़ंक्शन।" + }, + { + text: "यह डेटासेट में अनुक्रमों को छोटा कर देता है।", + explain: "एक कोलेट फ़ंक्शन अलग-अलग बैचों को संभालने में शामिल होता है, संपूर्ण डेटासेट नहीं। यदि आप काट-छाँट करने में रुचि रखते हैं, तो आप tokenizer के truncate वितर्क का उपयोग कर सकते हैं।" + } + ]} +/> + +### 7. क्या होता है जब आप `AutoModelForXxx` कक्षाओं में से एक को पूर्व-प्रशिक्षित भाषा मॉडल (जैसे कि `बर्ट-बेस-अनकेस्ड`) के साथ इन्स्टैन्शीऐट करते हैं, जो भिन्न कार्य से मेल खाता है बजाये उसके जिसके लिए उसे प्रशिक्षित किया गया ? + +AutoModelForSequenceClassification का उपयोग bert-base-uncased के साथ किया, तो मॉडल को इन्स्टैन्शीऐट करते समय हमें चेतावनियां मिलीं। अनुक्रम वर्गीकरण कार्य के लिए पूर्व-प्रशिक्षित के प्रमुख का उपयोग नहीं किया जाता है, इसलिए इसे त्याग दिया जाता है और क्रमरहित भार के साथ एक नये प्रमुख को इन्स्टैन्शीऐट किया जाता है।", + correct: true + }, + { + text: "पूर्व-प्रशिक्षित मॉडल के प्रमुख को त्याग दिया जाता है ।", + explain: "कुछ और होना चाहिए। पुनः प्रयास करें!" + }, + { + text: "कुछ भी नहीं, चूंकि मॉडल को अभी भी भिन्न कार्य के लिए ठीक यानि फाइन-ट्यून किया जा सकता है।", + explain: "इस कार्य को हल करने के लिए पूर्व-प्रशिक्षित मॉडल के प्रमुख को प्रशिक्षित नहीं किया गया था, इसलिए हमें प्रमुख को त्याग देना चाहिए!" + } + ]} +/> + +### 8. `TrainingArguments` का क्या उद्देश्य है? + +Trainer के साथ प्रशिक्षण और मूल्यांकन के लिए उपयोग किए जाने वाले सभी हाइपरपैरामीटर शामिल हैं।", + explain: "सही!", + correct: true + }, + { + text: "यह मॉडल के आकार को निर्दिष्ट करता है।", + explain: "मॉडल का आकार मॉडल कॉन्फ़िगरेशन द्वारा परिभाषित किया जाता है, न कि कक्षा TrainingArguments द्वारा।" + }, + { + text: "इसमें केवल मूल्यांकन के लिए उपयोग किए जाने वाले हाइपरपैरामीटर शामिल हैं।", + explain: "उदाहरण में, हमने निर्दिष्ट किया कि मॉडल और उसकी चौकियों को कहाँ सहेजा जाएगा। पुनः प्रयास करें!" + }, + { + text: "इसमें सिर्फ प्रशिक्षण के लिए उपयोग किए जाने वाले हाइपरपैरामीटर शामिल हैं।", + explain: "उदाहरण में, हमने evaluation_strategy का भी इस्तेमाल किया है, इसलिए यह मूल्यांकन को प्रभावित करता है। पुनः प्रयास करें!" + } + ]} +/> + +### 9. आपको 🤗 Accelerate लाइब्रेरी का उपयोग क्यों करना चाहिए? + +Trainer के साथ यही किया है, न की 🤗 Accelerate लाइब्रेरी ने। पुनः प्रयास करें!" + }, + { + text: "यह हमारे प्रशिक्षण लूप्स को वितरित रणनीतियों पर काम कराता है", + explain: "सही! 🤗 Accelerate के साथ, आपका प्रशिक्षण लूप कई GPUs और TPUs के लिए काम करेगा।", + correct: true + }, + { + text: "यह अधिक ऑप्टिमाइजेशन फंक्शन्स प्रदान करता है।", + explain: "नहीं, 🤗 Accelerate लाइब्रेरी कोई ऑप्टिमाइजेशन फंक्शन्स प्रदान नहीं करता है।" + } + ]} +/> + +{:else} +### 4. क्या होता है जब आप `TFAutoModelForXxx` कक्षाओं में से एक को पूर्व-प्रशिक्षित भाषा मॉडल (जैसे कि `बर्ट-बेस-अनकेस्ड`) के साथ इन्स्टैन्शीऐट करते हैं, जो भिन्न कार्य से मेल खाता है बजाये उसके जिसके लिए उसे प्रशिक्षित किया गया ? + +TFAutoModelForSequenceClassification का उपयोग bert-base-uncased के साथ किया, तो मॉडल को इन्स्टैन्शीऐट करते समय हमें चेतावनियां मिलीं। अनुक्रम वर्गीकरण कार्य के लिए पूर्व-प्रशिक्षित के प्रमुख का उपयोग नहीं किया जाता है, इसलिए इसे त्याग दिया जाता है और क्रमरहित भार के साथ एक नये प्रमुख को इन्स्टैन्शीऐट किया जाता है।", + correct: true + }, + { + text: "पूर्व-प्रशिक्षित मॉडल के प्रमुख को त्याग दिया जाता है ।", + explain: "कुछ और होना चाहिए। पुनः प्रयास करें!" + }, + { + text: "कुछ भी नहीं, चूंकि मॉडल को अभी भी भिन्न कार्य के लिए ठीक यानि फाइन-ट्यून किया जा सकता है।", + explain: "इस कार्य को हल करने के लिए पूर्व-प्रशिक्षित मॉडल के प्रमुख को प्रशिक्षित नहीं किया गया था, इसलिए हमें प्रमुख को त्याग देना चाहिए!" + } + ]} +/> + +### 5. `ट्रांसफॉर्मर` से TensorFlow मॉडल पहले से ही Keras मॉडल हैं। यह क्या लाभ प्रदान करता है? + +TPUStrategy दायरे में चलाने की जरूरत है, जिसमें मॉडल का इनिशियलाइज़ेशन भी शामिल है।" + }, + { + text: "आप compile(), fit(), और predict() जैसी मौजूदा विधियों का लाभ उठा सकते हैं।", + explain: "सही! एक बार आपके पास डेटा हो जाने के बाद, उस पर प्रशिक्षण के लिए बहुत कम काम की आवश्यकता होती है।", + correct: true + }, + { + text: "आपको Keras के साथ-साथ ट्रांसफार्मरस भी सीखने को मिलते हैं।", + explain: "सही है, लेकिन हम कुछ और ढूंढ रहे हैं :)", + correct: true + }, + { + text: "आप डेटासेट से संबंधित मेट्रिक्स की आसानी से गणना कर सकते हैं।", + explain: "आप डेटासेट से संबंधित मेट्रिक्स की आसानी से गणना कर सकते हैं।" + } + ]} +/> + +### 6. आप अपनी खुद की कस्टम मीट्रिक कैसे परिभाषित कर सकते हैं? + +tf.keras.metrics.Metric की उपवर्गीकरण करके।", + explain: "बहुत बढ़िया !", + correct: true + }, + { + text: "Keras क्रियात्मक API का उपयोग करना।", + explain: "पुनः प्रयास करें!" + }, + { + text: "हस्ताक्षर metric_fn(y_true, y_pred) के साथ प्रतिदेय का उपयोग करना।", + explain: "सही!", + correct: true + }, + { + text: "इसे गुगल करके।", + explain: "यह वह उत्तर नहीं है जिसकी हम तलाश कर रहे हैं, लेकिन इससे आपको उसे खोजने में मदद मिलेगी।", + correct: true + } + ]} +/> + +{/if} diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index 156a227f1..9ea9adab5 100644 --- a/chapters/pt/_toctree.yml +++ b/chapters/pt/_toctree.yml @@ -38,3 +38,17 @@ title: Usando modelos pré-treinados - local: chapter4/3 title: Compartilhando modelos pré-treinados + - local: chapter4/4 + title: Construindo um cartão para o modelo + - local: chapter4/5 + title: Parte 1 completa! + - local: chapter4/6 + title: Questionário de fim de capítulo + quiz: 4 + +- title: 5. A biblioteca Datasets 🤗 + sections: + - local: chapter5/1 + title: Introdução + - local: chapter5/2 + title: E se o meu dataset não estiver no Hub? diff --git a/chapters/pt/chapter4/4.mdx b/chapters/pt/chapter4/4.mdx new file mode 100644 index 000000000..89ce37719 --- /dev/null +++ b/chapters/pt/chapter4/4.mdx @@ -0,0 +1,82 @@ +# Construindo um cartão para o modelo + +O cartão para o modelo (model card) é um arquivo que é discutivelmente tão importante quanto os arquivos modelo e tokenizer em um repositório modelo. É a definição central do modelo, garantindo a reutilização pelos membros da comunidade e a reprodutibilidade dos resultados, e fornecendo uma plataforma sobre a qual outros membros podem construir seus artefatos. + +Documentar o processo de treinamento e avaliação ajuda outros a entender o que esperar de um modelo - e fornecer informações suficientes sobre os dados que foram utilizados e o pré e pós-processamento que foram feitos garante que as limitações, enviesamentos e contextos nos quais o modelo é e não é útil possam ser identificados e compreendidos. + +Portanto, criar um model card que defina claramente seu modelo é um passo muito importante. Aqui, fornecemos algumas dicas que o ajudarão com isto. A criação do model card é feita através do arquivo *README.md* que você viu anteriormente, que é um arquivo Markdown. + +O conceito de "model card" tem origem em uma direção de pesquisa do Google, primeiro compartilhada no artigo ["Model Cards for Model Reporting"](https://arxiv.org/abs/1810.03993) de Margaret Mitchell et al. Muitas das informações contidas aqui são baseadas nesse artigo, e recomendamos que você dê uma olhada nele para entender por que os cartões modelo são tão importantes em um mundo que valoriza a reprodutibilidade, a reusabilidade e a justiça. + +O model card geralmente começa com uma visão geral muito breve e de alto nível do que o modelo serve, seguida por detalhes adicionais nas seções seguintes: + +- Descrição do modelo +- Usos e limitações +- Como usar +- Dados de treinamento +- Procedimento de treinamento +- Resultados da avaliação + +Vamos dar uma olhada no que cada uma dessas seções deve conter. + +### Descrição do modelo + +A descrição do modelo fornece detalhes básicos sobre o modelo. Isto inclui a arquitetura, versão, se foi introduzida em um artigo, se uma implementação original está disponível, o autor, e informações gerais sobre o modelo. Qualquer direito autoral deve ser atribuído aqui. Informações gerais sobre procedimentos de treinamento, parâmetros e renúncias importantes também podem ser mencionadas nesta seção. + +### Usos e limitações + +Aqui você descreve os casos de uso a que o modelo se destina, incluindo os idiomas, campos e domínios onde ele pode ser aplicado. Esta seção do cartão modelo também pode documentar áreas que são conhecidas por estarem fora do escopo do modelo, ou onde é provável que ele tenha um desempenho subótimo. + +### Como usar + +Esta seção deve incluir alguns exemplos de como utilizar o modelo. Isto pode mostrar a utilização da função `pipeline()`, utilização do modelo e classes de tokenizer, e qualquer outro código que você acha que possa ser útil. + +### Dados de treinamento + +Esta parte deve indicar em que conjunto(s) de dados o modelo foi treinado. Uma breve descrição do(s) conjunto(s) de dados também é bem-vinda. + +### Procedimento de treinamento + +Nesta seção você deve descrever todos os aspectos relevantes do treinamento que são úteis a partir de uma perspectiva de reprodutibilidade. Isto inclui qualquer pré-processamento e pós-processamento que foram feitos nos dados, assim como detalhes como o número de épocas para as quais o modelo foi treinado, o tamanho do lote, a taxa de aprendizado, etc. + +### Variáveis e métricas + +Aqui você deve descrever as métricas que você usa para avaliação e os diferentes fatores que você está mensurando. A menção de qual(is) métrica(s) foi utilizada(s), em qual conjunto de dados e qual divisão de conjunto de dados facilita a comparação do desempenho do seu modelo em comparação com o de outros modelos. Estes devem ser informados pelas seções anteriores, tais como os usuários pretendidos e os casos de uso. + +### Resultados da avaliação + +Finalmente, fornecer uma indicação de quão bem o modelo funciona no conjunto de dados de avaliação. Se o modelo utiliza um limiar de decisão, ou fornecer o limiar de decisão utilizado na avaliação, ou fornecer detalhes sobre a avaliação em diferentes limiares para os usos pretendidos. + +## Exemplo + +Confira a seguir alguns exemplos de model card bem elaborados: + +- [`bert-base-cased`](https://huggingface.co/bert-base-cased) +- [`gpt2`](https://huggingface.co/gpt2) +- [`distilbert`](https://huggingface.co/distilbert-base-uncased) + +Mais exemplos de diferentes organizações e empresas estão disponíveis [aqui](https://github.com/huggingface/model_card/blob/master/examples.md). + +## Nota + +Model cards are not a requirement when publishing models, and you don't need to include all of the sections described above when you make one. However, explicit documentation of the model can only benefit future users, so we recommend that you fill in as many of the sections as possible to the best of your knowledge and ability. + +## Model card metadata + +Os model card não são uma exigência quando se publica modelos, e não é necessário incluir todas as seções descritas acima quando se faz um. Entretanto, a documentação explícita do modelo só pode beneficiar futuros usuários, por isso recomendamos que você preencha o maior número possível de seções com o melhor de seu conhecimento e capacidade. + + +Por exemplo, se você der uma olhada no model card [`camembert-base`(https://huggingface.co/camembert-base/blob/main/README.md), você deve ver as seguintes linhas no cabeçalho do model card: + +``` +--- +language: fr +license: mit +datasets: +- oscar +--- + +``` +Estes metadados são analisados pelo Hugging Face Hub, que então identifica este modelo como sendo um modelo francês, com uma licença MIT, treinado no conjunto de dados Oscar. + +A [especificação completa do model card](https://github.com/huggingface/hub-docs/blame/main/modelcard.md) permite especificar idiomas, licenças, tags, conjuntos de dados, métricas, assim como os resultados da avaliação do modelo obtido no treinamento. diff --git a/chapters/pt/chapter4/5.mdx b/chapters/pt/chapter4/5.mdx new file mode 100644 index 000000000..85bb3afb9 --- /dev/null +++ b/chapters/pt/chapter4/5.mdx @@ -0,0 +1,7 @@ +# Parte 1 completa! + +Este é o fim da primeira parte do curso! A segunda parte será lançada em 15 de novembro com um grande evento comunitário, veja mais informações [aqui](https://huggingface.co/blog/course-launch-event). + +Agora você deverá ser capaz de afinar um modelo pré-treinado sobre um problema de classificação de texto (frases simples ou pares de frases) e carregar o resultado para o Model Hub. Para garantir que você domine esta primeira seção, você deve fazer exatamente isso em um problema que lhe interesse (e não necessariamente em inglês se você falar outro idioma)! Você pode encontrar ajuda nos [fóruns Hugging Face](https://discuss.huggingface.co/) e compartilhar seu projeto em [este tópico](https://discuss.huggingface.co/t/share-your-projects/6803) uma vez terminado. + +Mal podemos esperar para ver o que você vai construir com isto! diff --git a/chapters/pt/chapter4/6.mdx b/chapters/pt/chapter4/6.mdx new file mode 100644 index 000000000..717de1b1f --- /dev/null +++ b/chapters/pt/chapter4/6.mdx @@ -0,0 +1,223 @@ + + + + +# Questionário de fim de capítulo + +Vamos testar o que você aprendeu neste capítulo! + +### 1. A que se limitam os modelos no Hub? + + + +### 2. Como você pode gerenciar modelos no Hub? + +git-lfs para arquivos grandes.", + correct: true + } + ]} +/> + +### 3. O que você pode fazer usando a interface web do Hugging Face Hub? + + + +### 4. O que é um model card (cartão de modelo)? + + + +### 5. Quais destes objetos da biblioteca 🤗 Transformers podem ser compartilhados diretamente no Hub com `push_to_hub()`? + +{#if fw === 'pt'} +push_to_hub, e usá-la enviara todos os arquivos tokenizer (vocabulário, arquitetura do tokenizer, etc.) para um determinado repo. Embora essa não seja a única resposta correta!", + correct: true + }, + { + text: "Uma configuração de modelo", + explain: "Certo! Todas configurações de modelos possuem o método push_to_hub, e usá-la enviara todas para um determinado repositório. O que mais você pode compartilhar?", + correct: true + }, + { + text: "Um modelo", + explain: "Correto! Todos modelos possuem o método push_to_hub, e usá-la enviara ele e suas configurações para um determinado repositório. Embora essa não seja a única resposta correta!", + correct: true + }, + { + text: "Um Trainer", + explain: "Está certa — o Trainer também implementa o método push_to_hub, e usa-lo ira enviar os arquivos do modelo, sua configuração, o tokenizer, eo rascunho do cartão de modelo para um repositório. Porém, não é a única resposta correta!", + correct: true + } + ]} +/> +{:else} +push_to_hub, e usá-la enviara todos os arquivos tokenizer (vocabulário, arquitetura do tokenizer, etc.) para um determinado repo. Embora essa não seja a única resposta correta!", + correct: true + }, + { + text: "Uma configuração de modelo", + explain: "Certo! Todas configurações de modelos possuem o método push_to_hub, e usá-la enviara todas para um determinado repositório. O que mais você pode compartilhar?", + correct: true + }, + { + text: "Um modelo", + explain: "Correto! Todos modelos possuem o método push_to_hub, e usá-la enviara ele e suas configurações para um determinado repositório. Embora essa não seja a única resposta correta!", + correct: true + }, + { + text: "Todas as opções com uma callback dedicado", + explain: "Está certa — o PushToHubCallback ira enviar regularmente os arquivos do modelo, sua configuração, e o tokenizer durante o treinamento para um repositório. Porém, não é a única resposta correta!", + correct: true + } + ]} +/> +{/if} + +### 6. Qual é o primeiro passo ao utilizar o método `push_to_hub()` ou as ferramentas CLI? + + + +### 7. Você está usando um modelo e um tokenizer - como você pode envia eles para o Hub? + +huggingface_hub utilidade.", + explain: "Modelos e tokenizers já se beneficiam de huggingface_hub utilidades: não há necessidade de wrappers adicionais!" + }, + { + text: "Salvando eles em disco e chamando transformers-cli upload-model", + explain: "O comando upload-model não existe." + } + ]} +/> + +### 8. Que operações de git você pode fazer com a classe `Repository`? + +git_commit() é para isto!", + correct: true + }, + { + text: "Um pull", + explain: "Este é o proposito do método git_pull().", + correct: true + }, + { + text: "Um push", + explain: "O método git_push() realiza isto.", + correct: true + }, + { + text: "Um merge", + explain: "Não, esta operação nunca será permitida nessa API." + } + ]} +/> diff --git a/chapters/pt/chapter5/1.mdx b/chapters/pt/chapter5/1.mdx new file mode 100644 index 000000000..780a5b770 --- /dev/null +++ b/chapters/pt/chapter5/1.mdx @@ -0,0 +1,18 @@ +# Introdução + +No [Capítulo 3](/course/chapter3) você teve seu primeiro gostinho da biblioteca 🤗 Datasets e viu que havia três passos principais quando se tratava de treinar para melhorar (fine-tuning) um modelo: + +1. Carregar um conjunto de dados (dataset) do Hugging Face Hub. +2. Pré-processar os dados com `Dataset.map()`. +3. Carregar e calcular as métricas. + +Mas isto está apenas arranhando a superfície do que 🤗 Dataset.map pode fazer! Neste capítulo, vamos dar um mergulho profundo na biblioteca. Ao longo do caminho, encontraremos respostas para as seguintes perguntas: + +* O que você faz quando seu conjunto de dados não está no Hub? +* Como você pode separar um conjunto de dados? (E se você _necessário_ usar Pandas?) +* O que você faz quando seu conjunto de dados é enorme e derreterá a RAM de seu laptop? +* O que diabos são "mapeamento de memória" e Apache Arrow? +* Como você pode criar seu próprio conjunto de dados e enviar para o Hub? + +As técnicas que você aprender aqui vão prepará-lo para as tarefas avançadas de tokenization e fine-tuning no [Capítulo 6](/course/chapter6) e [Capítulo 7](/course/chapter7) -- então pegue um café e vamos começar! + diff --git a/chapters/pt/chapter5/2.mdx b/chapters/pt/chapter5/2.mdx new file mode 100644 index 000000000..741ef7450 --- /dev/null +++ b/chapters/pt/chapter5/2.mdx @@ -0,0 +1,167 @@ +# E se o meu dataset não estiver no Hub? + + + +Você sabe como usar o [Hugging Face Hub](https://huggingface.co/datasets) para baixar conjuntos de dados (**datasets**), mas muitas vezes você se encontrará trabalhando com dados que são armazenados em seu laptop ou em um servidor remoto. Nesta seção mostraremos como 🤗 Datasets podem ser usados para carregar conjuntos de dados que não estão disponíveis no Hugging Face Hub. + + + +## Trabalhando com datasets locais e remotos + + +🤗 Datasets fornece scripts de carregamento para lidar com o carregamento de conjuntos de dados locais e remotos. Ele suporta vários formatos de dados comuns, como por exemplo: + +| Formato do dato | script de carregamento | Exemplo | +| :----------------: | :------------: | :-----------------------------------------------------: | +| CSV & TSV | `csv` | `load_dataset("csv", data_files="my_file.csv")` | +| Text files | `text` | `load_dataset("text", data_files="my_file.txt")` | +| JSON & JSON Lines | `json` | `load_dataset("json", data_files="my_file.jsonl")` | +| Pickled DataFrames | `pandas` | `load_dataset("pandas", data_files="my_dataframe.pkl")` | + +Como mostrado na tabela, para cada formato de dados só precisamos especificar o tipo de script de carregamento na função `load_dataset()`, junto com um argumento `data_files` que especifica o caminho para um ou mais arquivos. Vamos começar carregando um conjunto de dados de arquivos locais; mais tarde veremos como fazer o mesmo com arquivos remotos. + +## Carregando um conjunto de dados local + +Para este exemplo usaremos o [SQuAD-it dataset] (https://github.com/crux82/squad-it/), que é um conjunto de dados em grande escala para resposta a perguntas em italiano. + +As divisões de treinamento e testes são hospedadas no GitHub, para que possamos baixá-las com um simples comando `wget`: + +```python +!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-train.json.gz +!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-test.json.gz +``` + +Isto irá baixar dois arquivos compactados chamados *SQuAD_it-train.json.gz* e *SQuAD_it-test.json.gz*, que podemos descomprimir com o comando Linux `gzip`: + +```python +!gzip -dkv SQuAD_it-*.json.gz +``` + +```bash +SQuAD_it-test.json.gz: 87.4% -- replaced with SQuAD_it-test.json +SQuAD_it-train.json.gz: 82.2% -- replaced with SQuAD_it-train.json +``` + +Podemos ver que os arquivos compactados foram substituídos por _SQuAD_it-train.json_ e _SQuAD_it-text.json_, e que os dados são armazenados no formato JSON. + + + +✎ Se você está se perguntando por que há um `!` nos comandos shell acima, é porque estamos executando-os dentro de um Jupyter notebook. Basta remover o prefixo se você quiser baixar e descompactar o conjunto de dados dentro de um terminal. + + +Para carregar um arquivo JSON com a função `load_dataset()`, só precisamos saber se estamos lidando com o JSON comum (semelhante a um dicionário aninhado) ou Linhas JSON (JSON line-separated JSON). Como muitos conjuntos de dados que respondem a perguntas, o SQuAD utiliza o formato aninhado, com todo o texto armazenado em um campo `data`. Isto significa que podemos carregar o conjunto de dados especificando o argumento `field` da seguinte forma: + +```py +from datasets import load_dataset + +squad_it_dataset = load_dataset("json", data_files="SQuAD_it-train.json", field="data") +``` + +Por padrão, o carregamento de arquivos locais cria um objeto `DatasetDict` com uma divisão de treino (train). Podemos ver isso inspecionando o objeto `squad_it_dataset`: + +```py +squad_it_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 442 + }) +}) +``` + +Isto nos mostra o número de linhas e os nomes das colunas associadas ao conjunto de treinamento. Podemos ver um dos exemplos, indexando na divisão de treino da seguinte forma: + +```py +squad_it_dataset["train"][0] +``` + +```python out +{ + "title": "Terremoto del Sichuan del 2008", + "paragraphs": [ + { + "context": "Il terremoto del Sichuan del 2008 o il terremoto...", + "qas": [ + { + "answers": [{"answer_start": 29, "text": "2008"}], + "id": "56cdca7862d2951400fa6826", + "question": "In quale anno si è verificato il terremoto nel Sichuan?", + }, + ... + ], + }, + ... + ], +} +``` + +Ótimo, nós carregamos nosso primeiro conjunto de dados local! Mas enquanto isso funcionou para o conjunto de treinamento, o que realmente queremos é incluir tanto o conjunto de `treino` quanto o de `teste` divididos em um único objeto `DatasetDict` para que possamos aplicar as funções `Dataset.map()` em ambas as divisões de uma só vez. Para fazer isso, podemos fornecer um dicionário para o argumento `data_files` que mapeia cada nome de divisão para um arquivo associado a essa divisão: + + +```py +data_files = {"train": "SQuAD_it-train.json", "test": "SQuAD_it-test.json"} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +squad_it_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 442 + }) + test: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 48 + }) +}) +``` + +Isto é exatamente o que queríamos. Agora, podemos aplicar várias técnicas de pré-processamento para limpar os dados, assinalar as revisões, e assim por diante. + + + + +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. + + + +Os scripts de carregamento em 🤗 Datasets realmente suportam a descompressão automática dos arquivos de entrada, então poderíamos ter pulado o uso do `gzip` ao apontar o argumento `data_files` diretamente para os arquivos compactados: + +```py +data_files = {"train": "SQuAD_it-train.json.gz", "test": "SQuAD_it-test.json.gz"} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +``` + +Isto pode ser útil se você não quiser descomprimir manualmente muitos arquivos GZIP. A descompressão automática também se aplica a outros formatos comuns como ZIP e TAR, então você só precisa apontar `data_files` para os arquivos compactados e está pronto para seguir em frente! + +Agora que você sabe como carregar arquivos locais em seu laptop ou desktop, vamos dar uma olhada no carregamento de arquivos remotos. + +## Carregando um dataset remoto + +Se você estiver trabalhando como cientista de dados ou programador em uma empresa, há uma boa chance de que os conjuntos de dados que você deseja analisar estejam armazenados em algum servidor remoto. Felizmente, o carregamento de arquivos remotos é tão simples quanto o carregamento de arquivos locais! Em vez de fornecer um caminho para arquivos locais, apontamos o argumento `data_files` de `load_dataset()` para uma ou mais URLs onde os arquivos remotos são armazenados. Por exemplo, para o conjunto de dados SQuAD-it hospedado no GitHub, podemos apenas apontar `data_files` para as URLs _SQuAD_it-*.json.gz_ da seguinte maneira: + +```py +url = "https://github.com/crux82/squad-it/raw/master/" +data_files = { + "train": url + "SQuAD_it-train.json.gz", + "test": url + "SQuAD_it-test.json.gz", +} +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). + + + diff --git a/chapters/ru/_toctree.yml b/chapters/ru/_toctree.yml index 16b8d901c..00b7f82bb 100644 --- a/chapters/ru/_toctree.yml +++ b/chapters/ru/_toctree.yml @@ -39,3 +39,5 @@ title: Предобработка данных - local: chapter3/3 title: Fine-tuning модели с использованием Trainer API + - local: chapter3/4 + title: Полное обучение модели diff --git a/chapters/ru/chapter3/4.mdx b/chapters/ru/chapter3/4.mdx new file mode 100644 index 000000000..c267ca864 --- /dev/null +++ b/chapters/ru/chapter3/4.mdx @@ -0,0 +1,363 @@ +# Полное обучение + + + + + +Теперь мы посмотрим, как достичь результатов из предыдущей главы без использования класса `Trainer`. В этой главе мы предполагаем, что вы выполнили этапы препроцессинга раздела 2. Ниже короткая выжимка того, что вам понадобится: + +```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) +``` + +### Подготовка к обучению + +Перед реализацией цикла обучения необходимо задать несколько объектов. Первый: загрузчики данных (далее - dataloaders), которые мы будем использовать для итерирования по батчам данных. Перед этим нам необходимо применить несколько операций постпроцессинга к нашему `tokenized_datasets`. Это нужно сделать: в прошлый раз за нас это автоматически делал `Trainer`. Необходимо сделать следующее: + + +- Удалить колонки, соответствующие значениям, которые модель не принимает на вход (например, `sentence1` и `sentence2`). +- Переименовать колонку `label` в `labels` (потому что модель ожидает аргумент, названный `labels`). +- Задать тип данных в датасете pytorch tensors вместо списков. + +Наш `tokenized_datasets` предоставляет возможность использовать встроенные методы для каждого из приведенных выше шагов: + +```py +tokenized_datasets = tokenized_datasets.remove_columns(["sentence1", "sentence2", "idx"]) +tokenized_datasets = tokenized_datasets.rename_column("label", "labels") +tokenized_datasets.set_format("torch") +tokenized_datasets["train"].column_names +``` + +Мы можем проверить, что в результате у нас присутствуют только те поля, которые ожидает наша модель: + +```python +["attention_mask", "input_ids", "labels", "token_type_ids"] +``` + +Теперь, когда датасет готов, мы может задать dataloader: + +```py +from torch.utils.data import DataLoader + +train_dataloader = DataLoader( + tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator +) +eval_dataloader = DataLoader( + tokenized_datasets["validation"], batch_size=8, collate_fn=data_collator +) +``` + +Для того, чтобы убедиться в отсутствии ошибок в сделанном нами препроцессинге, мы можем проверить один батч данных: + +```py +for batch in train_dataloader: + break +{k: v.shape for k, v in batch.items()} +``` + +```python out +{'attention_mask': torch.Size([8, 65]), + 'input_ids': torch.Size([8, 65]), + 'labels': torch.Size([8]), + 'token_type_ids': torch.Size([8, 65])} +``` + +Обратите внимание, что фактические размеры, вероятно, будут немного отличаться для в вашем случае, так как мы установили `shuffle=True` для обучающего загрузчика данных, также мы дополняем (padding) до максимальной длины внутри батча. + +Теперь мы полностью завершили этап препроцессинга (приятный, но неуловимый момент для любого специалиста по машинному обучению), перейдем к модели. Мы инициализируем ее точно так, как делали в предыдущем примере: + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +Чтобы убедиться, что обучение пойдет гладко, вы подадим на вход модели один батч: + +```py +outputs = model(**batch) +print(outputs.loss, outputs.logits.shape) +``` + +```python out +tensor(0.5441, grad_fn=) torch.Size([8, 2]) +``` + +Все модели 🤗 трансформеров возвращают значение функции потерь, если в данных были `labels`, а также логиты (в результате получается тензор 8 х 2). + +Мы почти готовы к написанию обучающего цикла! Мы пропустили только две вещи: оптимизатор и планировщик скорости обучения (learning rate scheduler). Ввиду того, что мы пытаемся повторить вручную то, что делал за нас `Trainer`, мы будем использовать такие же значения по умолчанию. Оптимизатор, используемый в `Trainer` - `AdamW`, который является почти полной копией Adam, за исключением трюка с сокращением весов (далее - weight decay) (см. ["Decoupled Weight Decay Regularization"](https://arxiv.org/abs/1711.05101) за авторством Ilya Loshchilov и Frank Hutter). + +```py +from transformers import AdamW + +optimizer = AdamW(model.parameters(), lr=5e-5) +``` + +Наконец, планировщик скорости обучения по умолчанию - просто линейное уменьшение весов с максимального значения (5e-5) до 0. Чтобы корректно задать его, нам нужно знать число шагов в обучении, которое задается как произведение числа эпох и числа батчей (длины нашего загрузчика данных). Число эпох по умолчанию в `Trainer` равно 3, так же мы зададим его и сейчас: + +```py +from transformers import get_scheduler + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) +print(num_training_steps) +``` + +```python out +1377 +``` + +### Обучающий цикла + +Последний момент: мы хотим использовать GPU в случае, если у нас будет такая возможность (на CPU процесс может занять несколько часов вместо пары минут). Чтобы добиться этого, мы определим переменную `device` и «прикрепим» к видеокарте нашу модель и данные: + +```py +import torch + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) +device +``` + +```python out +device(type='cuda') +``` + +Теперь мы готовы к обучению модели! Чтобы иметь представление о том, сколько времени это может занять, мы добавим прогресс-бар, который будет иллюстрировать, сколько шагов обучения уже выполнено. Это можно сделать с использованием бибилиотеки tqdm: + +```py +from tqdm.auto import tqdm + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Вы можете заметить, что процесс обучения выглядит очень похожим на то, как он выглядел в наших первых примерах. Мы не указывали модели, чтобы она нам что-то возвращала в процессе обучения. Для этого мы добавим цикл валидации. + +### Валидационный цикл + +Ранее мы использовали метрику, которую нам предоставляла библиотека 🤗 Datasets. Мы уже знаем, что есть метод `metric.compute()`, однако метрики могут накапливать значения в процессе итерирования по батчу, для этого есть метод `add_batch()`. После того, как мы пройдемся по всем батчам, мы сможем вычислить финальный результат с помощью `metric.compute()`. Вот пример того, как это можно сделать в цикле валидации: + +```py +from datasets import load_metric + +metric = load_metric("glue", "mrpc") +model.eval() +for batch in eval_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + with torch.no_grad(): + outputs = model(**batch) + + logits = outputs.logits + predictions = torch.argmax(logits, dim=-1) + metric.add_batch(predictions=predictions, references=batch["labels"]) + +metric.compute() +``` + +```python out +{'accuracy': 0.8431372549019608, 'f1': 0.8907849829351535} +``` + +Повторим: результаты, которые получите вы, могут немного отличаться из-за наличия случайностей при инициализации параметров слоя модели и из-за случайного перемешивания датасета, однако их порядок должен совпадать. + + + +✏️ **Попробуйте!** Измените обучающий цикл так, чтобы дообучить модель на датасете SST-2. + + + +### Ускорение обучающего цикла с помощью 🤗 Accelerate + + + +Обучающий цикл, заданный выше, отлично работает на одном GPU или CPU. Однако использование библиотеки [🤗 Accelerate](https://github.com/huggingface/accelerate) позволяет с небольшими изменениями сделать эту процедуру распределенной на несколько GPU или TPU. Начииная с момента создания обучающих и валидационных загрузчиков данных, наш «ручной» обучающий цикл выглядит так: + +```py +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +А вот изменения, которые нужно внести, чтобы ускорить процесс: + +```diff ++ from accelerate import Accelerator + from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + ++ accelerator = Accelerator() + + model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) + optimizer = AdamW(model.parameters(), lr=3e-5) + +- device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +- model.to(device) + ++ train_dataloader, eval_dataloader, model, optimizer = accelerator.prepare( ++ train_dataloader, eval_dataloader, model, optimizer ++ ) + + num_epochs = 3 + num_training_steps = num_epochs * len(train_dataloader) + lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps + ) + + progress_bar = tqdm(range(num_training_steps)) + + model.train() + for epoch in range(num_epochs): + for batch in train_dataloader: +- batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss +- loss.backward() ++ accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Первая строка – это строка импорта библиотеки. Вторая строка инициализирует объект `Accelerator`, который проанализирует окружение и определит необходимые настройки. 🤗 Accelerate автоматически использует доступное оборудование, поэтому вы можете удалить строки, которые «прикрепляют» + модель и данные к видеокарте (или, если вам так удобнее, можете изменить их на `accelerator.device` вместо просто `device`). + +Далее главная часть работы выполняется в строке, которая отправляет данные, модель и оптимизатор на `accelerator.prepare()`. Этот метод «обернет» ваши объекты в контейнер и убедится, что распределенное обучение выполняется корректно. Оставшиеся изменения – удаление строки, которая отправляет батч на `device` (повторим: если вы хотите оставить эту строку, замените `device` на `accelerator.device`) и замените `loss.backward()` на `accelerator.backward(loss)`. + + +⚠️ Чтобы воспользоваться ускорением, предлагаемым облачными TPU, мы рекомендуем дополнять данные до фиксированной длины с помощью аргументов `padding="max_length"` и `max_length` токенизатора. + + +Если вы хотите скопировать и запустить этот код, это полная версия с использованием 🤗 Accelerate: + +```py +from accelerate import Accelerator +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +accelerator = Accelerator() + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +train_dl, eval_dl, model, optimizer = accelerator.prepare( + train_dataloader, eval_dataloader, model, optimizer +) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dl) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dl: + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Добавление этого в скрипт `train.py` сделает процесс обучения универсальным для любой распределенной системы. Попробуйте запустить его на вашей распределенной системе: + + +```bash +accelerate config +``` + +эта строка предложит вам ответить на несколько вопросов и сохранит ваши ответы в конфигурационный файл, который будет использоваться при вызове команды: + +which will prompt you to answer a few questions and dump your answers in a configuration file used by this command: + +``` +accelerate launch train.py +``` + +запускающей распределенное обучение. + +Если вы хотите попробовать запустить этот код в Jupyter Notebook (например, протестировать его с TPU на Google Colab), просто вставьте код в `training_function()` и запустите последнюю ячейку: + +```python +from accelerate import notebook_launcher + +notebook_launcher(training_function) +``` + +Вы можете найти больше примеров в репозитории [🤗 Accelerate repo](https://github.com/huggingface/accelerate/tree/main/examples). diff --git a/chapters/tr/_toctree.yml b/chapters/tr/_toctree.yml index 4b48680b3..8ffaeb63c 100644 --- a/chapters/tr/_toctree.yml +++ b/chapters/tr/_toctree.yml @@ -3,7 +3,6 @@ - local: chapter0/1 title: Giriş - - title: 1. Transformer modelleri sections: - local: chapter1/1 @@ -20,3 +19,8 @@ sections: - local: chapter2/1 title: Giriş + +- title: 3. Pretrained bir modeli fine-tune etmek + sections: + - local: chapter3/1 + title: Giriş \ No newline at end of file diff --git a/chapters/tr/chapter3/1.mdx b/chapters/tr/chapter3/1.mdx new file mode 100644 index 000000000..89e9d47a6 --- /dev/null +++ b/chapters/tr/chapter3/1.mdx @@ -0,0 +1,21 @@ + + +# Giriş + +[İkinci bölümde](/course/chapter2) tokenizer ve pretrained modelleri kullanarak nasıl tahmin yapabileceğimizi öğrendik. Fakat, kendi veri setiniz için, pretrained bir modeli nasıl kullanacaksınız ? İşte bu bölümde bunu öğreneceksiniz! Öğrenecekleriniz : + +{#if fw === 'pt'} +* Hub'dan nasıl büyük bir veri seti hazırlanır +* Trainer API ile nasıl model fine-tune edilir +* Özelleştirilmiş training döngüsü nasıl yazılır +* Bu özel training döngüsünü herhangi bir dağıtılmış(distributed) kurulumda kolayca çalıştırmak için 🤗 Accelerate kütüphanesinden nasıl yararlanılır + +{:else} +* Hub'dan nasıl büyük bir veri seti hazırlanır +* Keras ile nasıl model fine-tune edilir +* Keras ile tahminler nasıl elde edilir +* Özel metrikler nasıl kullanılır + +{/if} + +Hugging Face Hub'a eğittiğiniz model ağırlıklarını yüklemek için huggingface.co hesabına ihtiyacınız var.[hesap oluşturun](https://huggingface.co/join) \ No newline at end of file From 6c33dc1869ed977f53f37dec5fe093aa42381784 Mon Sep 17 00:00:00 2001 From: lewtun Date: Fri, 27 May 2022 20:47:22 +0200 Subject: [PATCH 17/51] Bump release (#224) --- .github/workflows/build_pr_documentation.yml | 2 +- chapters/en/chapter3/2.mdx | 2 +- chapters/en/chapter6/3b.mdx | 2 + chapters/en/chapter8/4.mdx | 2 +- chapters/en/chapter8/7.mdx | 2 +- chapters/es/_toctree.yml | 11 +- chapters/es/chapter8/1.mdx | 12 + chapters/es/chapter8/2.mdx | 375 ++++++++++ chapters/fa/_toctree.yml | 6 +- chapters/hi/chapter1/1.mdx | 14 +- chapters/pt/_toctree.yml | 2 + chapters/pt/chapter4/3.mdx | 2 +- chapters/pt/chapter5/3.mdx | 742 +++++++++++++++++++ utils/generate_notebooks.py | 1 - 14 files changed, 1158 insertions(+), 17 deletions(-) create mode 100644 chapters/es/chapter8/1.mdx create mode 100644 chapters/es/chapter8/2.mdx create mode 100644 chapters/pt/chapter5/3.mdx diff --git a/.github/workflows/build_pr_documentation.yml b/.github/workflows/build_pr_documentation.yml index a718be37a..bfff48611 100644 --- a/.github/workflows/build_pr_documentation.yml +++ b/.github/workflows/build_pr_documentation.yml @@ -17,4 +17,4 @@ jobs: path_to_docs: course/chapters/ additional_args: --not_python_module languages: ar bn de en es fa fr gj he hi it ja ko pt ru th tr zh-CN - hub_base_path: https://moon-ci-docs.huggingface.co/course + hub_base_path: https://moon-ci-docs.huggingface.co diff --git a/chapters/en/chapter3/2.mdx b/chapters/en/chapter3/2.mdx index 08640409f..d6d4ceb49 100644 --- a/chapters/en/chapter3/2.mdx +++ b/chapters/en/chapter3/2.mdx @@ -114,7 +114,7 @@ DatasetDict({ As you can see, we get a `DatasetDict` object which contains the training set, the validation set, and the test set. Each of those contains several columns (`sentence1`, `sentence2`, `label`, and `idx`) and a variable number of rows, which are the number of elements in each set (so, there are 3,668 pairs of sentences in the training set, 408 in the validation set, and 1,725 in the test set). -This command downloads and caches the dataset, by default in *~/.cache/huggingface/dataset*. Recall from Chapter 2 that you can customize your cache folder by setting the `HF_HOME` environment variable. +This command downloads and caches the dataset, by default in *~/.cache/huggingface/datasets*. Recall from Chapter 2 that you can customize your cache folder by setting the `HF_HOME` environment variable. We can access each pair of sentences in our `raw_datasets` object by indexing, like with a dictionary: diff --git a/chapters/en/chapter6/3b.mdx b/chapters/en/chapter6/3b.mdx index 73af27bd7..61fad33d0 100644 --- a/chapters/en/chapter6/3b.mdx +++ b/chapters/en/chapter6/3b.mdx @@ -253,6 +253,8 @@ scores = torch.triu(scores) Then we'll mask the values where `start_index > end_index` by setting them to `0` (the other probabilities are all positive numbers). The `np.triu()` function returns the upper triangular part of the 2D tensor passed as an argument, so it will do that masking for us: ```py +import numpy as np + scores = np.triu(scores) ``` diff --git a/chapters/en/chapter8/4.mdx b/chapters/en/chapter8/4.mdx index 4bd60ea77..1cc9e4e51 100644 --- a/chapters/en/chapter8/4.mdx +++ b/chapters/en/chapter8/4.mdx @@ -200,7 +200,7 @@ So in our case, we can check the parameters accepted on [this page](https://hugg We have checked that the input IDs are correct by decoding them. Next is the `attention_mask`: ```py -tokenizer.decode(trainer.train_dataset[0]["attention_mask"]) +trainer.train_dataset[0]["attention_mask"] ``` ```python out diff --git a/chapters/en/chapter8/7.mdx b/chapters/en/chapter8/7.mdx index a20df19d6..9d29e8fcb 100644 --- a/chapters/en/chapter8/7.mdx +++ b/chapters/en/chapter8/7.mdx @@ -192,7 +192,7 @@ Which of the following might be a good choice for the title of a forum topic to }, { text: "It allows the maintainers to know whether you're running code on a GPU or CPU.", - explain: "Correct! As we've seen in this chapter, errors on GPUs and CPUs can quite different in flavor, and knowing which hardware you're using can help focus the maintainers' attention. But this isn't the only benefit...", + explain: "Correct! As we've seen in this chapter, code ran on GPUs or CPUs may produce diffferent results or errors, and knowing which hardware you're using can help focus the maintainers' attention. But this isn't the only benefit...", correct: true } ]} diff --git a/chapters/es/_toctree.yml b/chapters/es/_toctree.yml index fe81e7ecd..0250693da 100644 --- a/chapters/es/_toctree.yml +++ b/chapters/es/_toctree.yml @@ -40,8 +40,17 @@ title: Introducción - local: chapter3/2 title: Procesamiento de los datos + + +- title: 8. ¿Cómo solicitar ayuda? + sections: + - local: chapter8/1 + title: Introducción + - local: chapter8/2 + title: ¿Qué hacer cuando se produce un error? + - title: Glosario sections: - local: glossary/1 - title: Glosario \ No newline at end of file + title: Glosario diff --git a/chapters/es/chapter8/1.mdx b/chapters/es/chapter8/1.mdx new file mode 100644 index 000000000..4ebde8120 --- /dev/null +++ b/chapters/es/chapter8/1.mdx @@ -0,0 +1,12 @@ +# Introducción + +Ahora sabes cómo abordar las tareas de PLN más comunes con la librería 🤗 Transformers, ¡deberías ser capaz de iniciar tus propios proyectos! En este capítulo exploraremos qué debes hacer cuando te encuentras con un problema. Aprenderás a cómo depurar (debug) exitosamente tu código o tu entrenamiento, y cómo solicitar ayuda si no consigues resolver el problema por ti mismo. Además, si crees que has encontrado un error (bug) en una de las librerías de Hugging Face, te indicaremos la mejor manera de reportarlo para que se resuelva tan pronto como sea posible. + +Más precisamente, en este capítulo aprenderás: + +- Lo primero que debes hacer cuando se produce un error +- Cómo solicitar ayuda en los [foros](https://discuss.huggingface.co/) +- Cómo depurar tu pipeline de entrenamiento +- Cómo escribir un buen issue + +Nada de esto es específicamente relacionado con la librería 🤗 Transformers o con el ecosistema de Hugging Face, por supuesto; ¡las lecciones de este capítulo son aplicables a la mayoría de proyectos de open source! diff --git a/chapters/es/chapter8/2.mdx b/chapters/es/chapter8/2.mdx new file mode 100644 index 000000000..34a4e9392 --- /dev/null +++ b/chapters/es/chapter8/2.mdx @@ -0,0 +1,375 @@ +# ¿Qué hacer cuando se produce un error? + + + +En esta sección veremos algunos errores comunes que pueden ocurrir cuando intentas generar predicciones a partir de tu modelo Transformer recién afinado. Esto te preparará para la [sección 4](/course/chapter8/section4), en la que exploraremos cómo depurar (debug) la fase de entrenamiento. + + + +Hemos preparado un [repositorio de un modelo de ejemplo](https://huggingface.co/lewtun/distilbert-base-uncased-finetuned-squad-d5716d28) para esta sección, por lo que si deseas ejecutar el código en este capítulo, primero necesitarás copiar el modelo a tu cuenta en el [Hub de Hugging Face](https://huggingface.co). Para ello, primero inicia sesión (log in) ejecutando lo siguiente en una Jupyter notebook: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +o puedes ejecutar lo siguiente en tu terminal favorita: + +```bash +huggingface-cli login +``` + +Esto te pedirá que introduzcas tu nombre de usuario y contraseña, y guardará un token en *~/.cache/huggingface/*. Una vez que hayas iniciado sesión, puedes copiar el repositorio de ejemplo con la siguiente función: + +```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(): + # Clona el repo y extrae la ruta local + template_repo_id = "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28" + commit_hash = "be3eaffc28669d7932492681cd5f3e8905e358b4" + template_repo_dir = snapshot_download(template_repo_id, revision=commit_hash) + # Crea un repo vacío en el Hub + model_name = template_repo_id.split("/")[1] + create_repo(model_name, exist_ok=True) + # Clona el repo vacío + new_repo_id = get_full_repo_name(model_name) + new_repo_dir = model_name + repo = Repository(local_dir=new_repo_dir, clone_from=new_repo_id) + # Copia los archivos + copy_tree(template_repo_dir, new_repo_dir) + # Envia (push) al Hub + repo.push_to_hub() +``` + +Ahora cuando llames a la función `copy_repository_template()`, esta creará una copia del repositorio de ejemplo en tu cuenta. + +## Depurando el pipeline de 🤗 Transformers + +Para iniciar nuestro viaje hacia el maravilloso mundo de la depuración de modelos de Transformers, imagina lo siguiente: estás trabajando con un compañero en un proyecto de respuesta a preguntas (question answering) para ayudar a los clientes de un sitio web de comercio electrónico a encontrar respuestas sobre productos de consumo. Tu compañero te envía el siguiente mensaje: + +> ¡Buen día! Acabo de lanzar un experimento usando las técnicas del [Capitulo 7](/course/chapter7/7) del curso de Hugging Face y ¡obtuvo unos buenos resultados con el conjunto de datos SQuAD! Creo que podemos usar este modelo como punto de partida para nuestro proyecto. El identificador del modelo en el Hub es "lewtun/distillbert-base-uncased-finetuned-squad-d5716d28". No dudes en probarlo :) + +y en lo primero que piensas es en cargar el modelo usando el `pipeline` de la librería 🤗 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 +""" +``` + +¡Oh no, algo parece estar mal! Si eres nuevo en programación, este tipo de errores pueden parecer un poco crípticos al inicio (¿qué es un `OSError`?). El error mostrado aquí es solo la última parte de un reporte de errores mucho más largo llamado _Python traceback_ (o _stack trace_). Por ejemplo, si estás ejecutando este código en Google Colab, podrías ver algo parecido como la siguiente captura: + +
+A Python traceback. +
+ +Hay mucha información contenida en estos reportes, así que vamos a repasar juntos las partes clave. La primera cosa que notamos es que el _traceback_ debería ser leído de _abajo hacia arriba_. Esto puede sonar extraño si estás acostumbrado a leer en español de arriba hacia abajo, pero refleja el hecho de que el _traceback_ muestra la secuencia de funciones llamadas que el `pipeline` realiza al descargar el modelo y el tokenizador. (Ve al [Capítulo 2](/course/chapter2) para más detalles sobre cómo funciona el `pipeline` bajo el capó) + + + +🚨 ¿Ves el cuadro azul alrededor de "6 frames" en el traceback de Google Colab? Es una característica especial de Colab, que comprime el traceback en "frames". Si no puedes encontrar el origen de un error, asegúrate de ampliar el traceback completo haciendo clic en esas dos flechitas. + + + +Esto significa que la última línea del traceback indica el último mensaje de error y nos da el nombre de la excepción (exception) que se ha generado. En este caso, el tipo de excepción es `OSError`, lo que indica un error relacionado con el sistema. Si leemos el mensaje de error que lo acompaña, podemos ver que parece haber un problema con el archivo *config.json* del modelo, y nos da dos sugerencias para solucionarlo: + +```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 +""" +``` + + + +💡 Si te encuentras con un mensaje de error difícil de entender, simplemente copia y pega el mensaje en la barra de búsqueda de Google o de [Stack Overflow](https://stackoverflow.com/) (¡sí, en serio!). Es muy posible que no seas la primera persona en encontrar el error, y esta es una buena forma de hallar soluciones que otros miembros de la comunidad han publicado. Por ejemplo, al buscar `OSError: Can't load config for` en Stack Overflow se obtienen varios resultados que pueden ser utilizados como punto de partida para resolver el problema. + + + +La primera sugerencia nos pide que comprobemos si el identificador del modelo es realmente correcto, así que lo primero es copiar el identificador y pegarlo en la barra de búsqueda del Hub: + +
+The wrong model name. +
+ +Hmm, efectivamente parece que el modelo de nuestro compañero no está en el Hub... ¡pero hay una errata en el nombre del modelo! DistilBERT solo tiene una "l" en el nombre, así que vamos a corregirlo y a buscar "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28" en su lugar: + +
+The right model name. +
+ +Bien, esto dio resultado. Ahora vamos a intentar descargar el modelo de nuevo con el identificador correcto: + +```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 +""" +``` + +Argh, falló de nuevo, ¡bienvenido al día a día de un ingeniero de machine learning! Dado que arreglamos el identificador del modelo, el problema debe estar en el repositorio. Una manera rápida de acceder a los contenidos de un repositorio en el 🤗 Hub es por medio de la función `list_repo_files()` de la librería de `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'] +``` + +Interesante. ¡No parece haber un archivo *config.json* en el repositorio! No es de extrañar que nuestro `pipeline` no pudiera cargar el modelo; nuestro compañero debe haberse olvidado de enviar este archivo al Hub después de ajustarlo (fine-tuned). En este caso, el problema parece bastante simple de resolver: podemos pedirle a nuestro compañero que añada el archivo, o, ya que podemos ver en el identificador del modelo que el modelo preentrenado fue [`distilbert-base-uncased`](https://huggingface.co/distilbert-base-uncased), podemos descargar la configuración para este modelo y enviarla a nuestro repositorio para ver si eso resuelve el problema. Intentemos esto. Usando las técnicas que aprendimos en el [Capítulo 2](/course/chapter2), podemos descargar la configuración del modelo con la clase `AutoConfig`: + +```python +from transformers import AutoConfig + +pretrained_checkpoint = "distilbert-base-uncased" +config = AutoConfig.from_pretrained(pretrained_checkpoint) +``` + + + +🚨 El enfoque que tomamos aquí no es infalible, ya que nuestro compañero puede haber cambiado la configuración de `distilbert-base-uncased` antes de ajustar (fine-tuning) el modelo. En la vida real, nos gustaría consultar con él primero, pero para los fines de esta sección asumiremos que usó la configuración predeterminada. + + + +Luego podemos enviar esto a nuestro repositorio del modelo con la función de configuración `push_to_hub()`: + +```python +config.push_to_hub(model_checkpoint, commit_message="Add config.json") +``` + +Ahora podemos probar si esto funciona cargando el modelo desde el último commit de la rama `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! +""" + +context_es = r""" +La respuesta a preguntas es la extracción de una respuesta textual a partir de +una pregunta. Un ejemplo de conjunto de datos de respuesta a preguntas es el +dataset SQuAD, que se basa por completo en esta tarea. Si deseas afinar un modelo +en una tarea SQuAD, puedes aprovechar el script + examples/pytorch/question-answering/run_squad.py + +🤗 Transformers es interoperable con los frameworks PyTorch, TensorFlow y JAX, +así que ¡puedes utilizar tus herramientas favoritas para una gran variedad de tareas! +""" + +question = "What is extractive question answering?" +# ¿Qué es la respuesta extractiva a preguntas? +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'} + # la tarea de extraer una respuesta de un texto a una pregunta dada +``` + +¡Yuju, funcionó! Recapitulemos lo que acabas de aprender: + +- Los mensajes de error en Python son conocidos como _tracebacks_ y se leen de abajo hacia arriba. La última línea del mensaje de error generalmente contiene la información que necesitas para ubicar la fuente del problema. +- Si la última línea no contiene suficiente información, sigue el traceback y mira si puedes identificar en qué parte del código fuente se produce el error. +- Si ninguno de los mensajes de error te ayuda a depurar el problema, trata de buscar en internet una solución a un problema similar. +- El 🤗 `huggingface_hub` de la librería proporciona un conjunto de herramientas que puedes utilizar para interactuar y depurar los repositorios en el Hub. + +Ahora que sabes cómo depurar un pipeline, vamos a ver un ejemplo más complicado en la pasada hacia delante (forward pass) del propio modelo. + +## Depurando la pasada hacia delante (forward pass) de tu modelo + +Aunque el `pipeline` es estupendo para la mayoría de las aplicaciones en las que necesitas generar predicciones rápidamente, a veces necesitarás acceder a los _logits_ del modelo (por ejemplo, si tienes algún postprocesamiento personalizado que te gustaría aplicar). Para ver lo que puede salir mal en este caso, vamos a coger primero el modelo y el tokenizador de nuestro `pipeline`: + +```python +tokenizer = reader.tokenizer +model = reader.model +``` + +A continuación, necesitamos una pregunta, así que veamos si nuestros frameworks son compatibles: + +```python +question = "Which frameworks can I use?" # ¿Qué frameworks puedo usar? +``` + +Como vimos en el [Capítulo 7](/course/chapter7), los pasos habituales que debemos seguir son tokenizar los inputs, extraer los _logits_ de los tokens de inicio y fin y luego decodificar el intervalo de la respuesta: + +```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 +# Obtiene el comienzo más probable de la respuesta con el argmax de la puntuación +answer_start = torch.argmax(answer_start_scores) +# Obtiene el final más probable de la respuesta con el argmax de la puntuación +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' +""" +``` + +Vaya, parece que tenemos un _bug_ en nuestro código. Pero no nos asusta un poco de depuración. Puedes usar el depurador de Python en una notebook: + + + +o en una terminal: + + + +Aquí la lectura del mensaje de error nos dice que el objeto `'list'` no tiene atributo `'size'`, y podemos ver una flecha `-->` apuntando a la línea donde el problema se originó en `model(**inputs)`. Puedes depurar esto interactivamente usando el _debugger_ de Python, pero por ahora simplemente imprimiremos un fragmento de `inputs` para ver qué obtenemos: + +```python +inputs["input_ids"][:5] +``` + +```python out +[101, 2029, 7705, 2015, 2064] +``` + +Esto sin duda parece una `lista` ordinaria de Python, pero vamos a comprobar el tipo: + +```python +type(inputs["input_ids"]) +``` + +```python out +list +``` + +Sí, es una lista de Python. Entonces, ¿qué salió mal? Recordemos del [Capítulo 2](/course/chapter2) que las clases `AutoModelForXxx` en 🤗 Transformers operan con _tensores_ (tanto en PyTorch como en TensorFlow), y una operación común es extraer las dimensiones de un tensor usando `Tensor.size()` en, por ejemplo, PyTorch. Volvamos a echar un vistazo al traceback, para ver qué línea desencadenó la excepción: + +``` +~/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' +``` + +Parece que nuestro código trata de llamar a la función `input_ids.size()`, pero esta claramente no funcionará con una lista de Python, la cual solo es un contenedor. ¿Cómo podemos resolver este problema? La búsqueda del mensaje de error en Stack Overflow da bastantes [resultados](https://stackoverflow.com/search?q=AttributeError%3A+%27list%27+object+has+no+attribute+%27size%27&s=c15ec54c-63cb-481d-a749-408920073e8f) relevantes. Al hacer clic en el primero, aparece una pregunta similar a la nuestra, con la respuesta que se muestra en la siguiente captura de pantalla: + +
+An answer from Stack Overflow. +
+ +La respuesta recomienda que adicionemos `return_tensors='pt'` al tokenizador, así que veamos si esto nos funciona: + +```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 +# Obtiene el comienzo más probable de la respuesta con el argmax de la puntuación +answer_start = torch.argmax(answer_start_scores) +# Obtiene el final más probable de la respuesta con el argmax de la puntuación +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? # ¿Qué frameworks puedo usar? +Answer: pytorch, tensorflow, and jax +""" +``` + +¡Excelente, funcionó! Este en un gran ejemplo de lo útil que puede ser Stack Overflow: al identificar un problema similar, fuimos capaces de beneficiarnos de la experiencia de otros en la comunidad. Sin embargo, una búsqueda como esta no siempre dará una respuesta relevante, así que ¿qué podemos hacer en esos casos? Afortunadamente hay una comunidad acogedora de desarrolladores en los [foros de Hugging Face](https://discuss.huggingface.co/) que pueden ayudarte. En la siguiente sección, veremos cómo elaborar buenas preguntas en el foro que tengan posibilidades de ser respondidas. diff --git a/chapters/fa/_toctree.yml b/chapters/fa/_toctree.yml index d45d23d0e..afe4a516f 100644 --- a/chapters/fa/_toctree.yml +++ b/chapters/fa/_toctree.yml @@ -26,12 +26,12 @@ - local: chapter4/2 title: بکارگیری مدل‌های از پیش تعلیم دیده -- title: 3. کوک کردن یک مدل از پیش تعلیم دیده # Translate this! +- title: 3. کوک کردن یک مدل از پیش تعلیم دیده sections: - local: chapter3/1 - title: مقدمه # Translate this! + title: مقدمه - local: chapter3/2 - title: پردازش داده # Translate this! + title: پردازش داده - title: واژه‌نامه sections: diff --git a/chapters/hi/chapter1/1.mdx b/chapters/hi/chapter1/1.mdx index 3107062cc..59b8aa076 100644 --- a/chapters/hi/chapter1/1.mdx +++ b/chapters/hi/chapter1/1.mdx @@ -1,10 +1,10 @@ # परिचय -## :hugs: पाठ्यक्रम में आपका स्वागत है! +## 🤗 पाठ्यक्रम में आपका स्वागत है! -यह पाठ्यक्रम आपको [Hugging Face](https://huggingface.co) पारिस्थितिकी तंत्र - [:hugs: ट्रान्सफ़ॉर्मर](https://github.com/huggingface/transformers), [:hugs: डेटासेट](https://github.com/huggingface/datasets), [:hugs: टोकनीज़र](https://github.com/huggingface/tokenizers), तथा [:hugs: एक्सेलेरेट](https://github.com/huggingface/accelerate) - इसके साथ ही [हगिंग फेस हब](https://huggingface.co/models) पुस्तकालयों का उपयोग करके प्राकृतिक भाषा प्रसंस्करण (एनएलपी) के बारे में सिखाएगा। यह पूरी तरह से मुफ़्त है और विज्ञापनों के बिना है। +यह पाठ्यक्रम आपको [Hugging Face](https://huggingface.co) पारिस्थितिकी तंत्र - [🤗 ट्रान्सफ़ॉर्मर](https://github.com/huggingface/transformers), [🤗 डेटासेट](https://github.com/huggingface/datasets), [🤗 टोकनीज़र](https://github.com/huggingface/tokenizers), तथा [🤗 एक्सेलेरेट](https://github.com/huggingface/accelerate) - इसके साथ ही [हगिंग फेस हब](https://huggingface.co/models) पुस्तकालयों का उपयोग करके प्राकृतिक भाषा प्रसंस्करण (एनएलपी) के बारे में सिखाएगा। यह पूरी तरह से मुफ़्त है और विज्ञापनों के बिना है। ## क्या उम्मीद करें? @@ -15,9 +15,9 @@
-- अध्याय 1 से 4 :hugs: ट्रान्सफ़ॉर्मर पुस्तकालय की मुख्य अवधारणाओं का परिचय प्रदान करते हैं। पाठ्यक्रम के इस भाग के अंत तक, आप इस बात से परिचित होंगे कि ट्रांसफार्मर मॉडल कैसे काम करते हैं और [हगिंग फेस हब](https://huggingface.co/models) से मॉडल का उपयोग करना जानते हैं, इसे ठीक करें। डेटासेट पर, और हब पर अपने परिणाम साझा करें! -- अध्याय 5 से 8 क्लासिक एनएलपी कार्यों में गोता लगाने से पहले :hugs: डेटासेट और :hugs: टोकनाइज़र की मूल बातें सिखाते हैं। इस भाग के अंत तक, आप सबसे आम एनएलपी समस्याओं से स्वयं निपटने में सक्षम होंगे। -- अध्याय 9 से 12 एनएलपी से आगे जाते हैं और यह पता लगाते हैं कि भाषा प्रसंस्करण और कंप्यूटर दृष्टि में कार्यों से निपटने के लिए ट्रांसफार्मर मॉडल का उपयोग कैसे किया जा सकता है। साथ ही, आप सीखेंगे कि अपने मॉडलों के डेमो कैसे बनाएं और साझा करें, और उन्हें उत्पादन परिवेशों के लिए अनुकूलित करें। इस भाग के अंत तक, आप (लगभग) किसी भी मशीन सीखने की समस्या के लिए :hugs: ट्रांसफॉर्मर लगाने के लिए तैयार होंगे! +- अध्याय 1 से 4 🤗 ट्रान्सफ़ॉर्मर पुस्तकालय की मुख्य अवधारणाओं का परिचय प्रदान करते हैं। पाठ्यक्रम के इस भाग के अंत तक, आप इस बात से परिचित होंगे कि ट्रांसफार्मर मॉडल कैसे काम करते हैं और [हगिंग फेस हब](https://huggingface.co/models) से मॉडल का उपयोग करना जानते हैं, इसे ठीक करें। डेटासेट पर, और हब पर अपने परिणाम साझा करें! +- अध्याय 5 से 8 क्लासिक एनएलपी कार्यों में गोता लगाने से पहले 🤗 डेटासेट और 🤗 टोकनाइज़र की मूल बातें सिखाते हैं। इस भाग के अंत तक, आप सबसे आम एनएलपी समस्याओं से स्वयं निपटने में सक्षम होंगे। +- अध्याय 9 से 12 एनएलपी से आगे जाते हैं और यह पता लगाते हैं कि भाषा प्रसंस्करण और कंप्यूटर दृष्टि में कार्यों से निपटने के लिए ट्रांसफार्मर मॉडल का उपयोग कैसे किया जा सकता है। साथ ही, आप सीखेंगे कि अपने मॉडलों के डेमो कैसे बनाएं और साझा करें, और उन्हें उत्पादन परिवेशों के लिए अनुकूलित करें। इस भाग के अंत तक, आप (लगभग) किसी भी मशीन सीखने की समस्या के लिए 🤗 ट्रांसफॉर्मर लगाने के लिए तैयार होंगे! यह पाठ्यक्रम के लिए: @@ -33,9 +33,9 @@ **मैथ्यू कैरिगन** हगिंग फेस में मशीन लर्निंग इंजीनियर हैं। वह डबलिन, आयरलैंड में रहता है, और पहले Parse.ly में एक एमएल इंजीनियर के रूप में काम करता था और उससे पहले ट्रिनिटी कॉलेज डबलिन में पोस्ट-डॉक्टरेट शोधकर्ता के रूप में काम करता था। वह विश्वास नहीं कर सकता कि हम मौजूदा आर्किटेक्चर को स्केल करके एजीआई तक पहुंचने जा रहे हैं, लेकिन रोबोट अमरता की परवाह किए बिना उच्च उम्मीदें हैं। -**लिसेंड्रे डेब्यू** हगिंग फेस में एक मशीन लर्निंग इंजीनियर है और बहुत प्रारंभिक विकास चरणों के बाद से :hugs: ट्रांसफॉर्मर्स लाइब्रेरी पर काम कर रहा है। उनका उद्देश्य एक बहुत ही सरल एपीआई के साथ उपकरण विकसित करके एनएलपी को सभी के लिए सुलभ बनाना है। +**लिसेंड्रे डेब्यू** हगिंग फेस में एक मशीन लर्निंग इंजीनियर है और बहुत प्रारंभिक विकास चरणों के बाद से 🤗 ट्रांसफॉर्मर्स लाइब्रेरी पर काम कर रहा है। उनका उद्देश्य एक बहुत ही सरल एपीआई के साथ उपकरण विकसित करके एनएलपी को सभी के लिए सुलभ बनाना है। -**सिल्वेन गुगर** हगिंग फेस में एक रिसर्च इंजीनियर हैं और :hugs: ट्रान्सफ़ॉर्मर्स लाइब्रेरी के मुख्य अनुरक्षकों में से एक हैं। पहले वे fast.ai में एक शोध वैज्ञानिक थे, और उन्होंने _[डीप लर्निंग फॉर कोडर्स विद फास्टाई और पायटॉर्च](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/) का सह-लेखन किया जेरेमी हॉवर्ड के साथ। उनके शोध का मुख्य फोकस तकनीकों को डिजाइन और सुधार करके गहन शिक्षण को और अधिक सुलभ बनाने पर है जो मॉडल को सीमित संसाधनों पर तेजी से प्रशिक्षित करने की अनुमति देता है। +**सिल्वेन गुगर** हगिंग फेस में एक रिसर्च इंजीनियर हैं और 🤗 ट्रान्सफ़ॉर्मर्स लाइब्रेरी के मुख्य अनुरक्षकों में से एक हैं। पहले वे fast.ai में एक शोध वैज्ञानिक थे, और उन्होंने _[डीप लर्निंग फॉर कोडर्स विद फास्टाई और पायटॉर्च](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/) का सह-लेखन किया जेरेमी हॉवर्ड के साथ। उनके शोध का मुख्य फोकस तकनीकों को डिजाइन और सुधार करके गहन शिक्षण को और अधिक सुलभ बनाने पर है जो मॉडल को सीमित संसाधनों पर तेजी से प्रशिक्षित करने की अनुमति देता है। **मर्व नोयान** हगिंग फेस में एक डेवलपर एडवोकेट है, जो सभी के लिए मशीन लर्निंग का लोकतंत्रीकरण करने के लिए टूल विकसित करने और उनके आसपास सामग्री बनाने पर काम कर रहे है। diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index 9ea9adab5..c9bb3fafb 100644 --- a/chapters/pt/_toctree.yml +++ b/chapters/pt/_toctree.yml @@ -52,3 +52,5 @@ title: Introdução - local: chapter5/2 title: E se o meu dataset não estiver no Hub? + - local: chapter5/3 + title: Hora de fatiar e dividir os dados diff --git a/chapters/pt/chapter4/3.mdx b/chapters/pt/chapter4/3.mdx index d0416b617..0a84b09dd 100644 --- a/chapters/pt/chapter4/3.mdx +++ b/chapters/pt/chapter4/3.mdx @@ -561,7 +561,7 @@ Objects not staged for commit: ``` -We can see that all files have `Git` as a handler, except *t5_model.h5*, which has `LFS`. Great! +Podemos ver que todos os arquivos têm `Git` como manipulador, exceto *t5_model.h5*, que tem `LFS`. Excelente! {/if} diff --git a/chapters/pt/chapter5/3.mdx b/chapters/pt/chapter5/3.mdx new file mode 100644 index 000000000..2c65a36c9 --- /dev/null +++ b/chapters/pt/chapter5/3.mdx @@ -0,0 +1,742 @@ +# Hora de fatiar e dividir os dados + + + +Na maioria das vezes, os dados com os quais você trabalha não estarão perfeitamente preparados para treinamento de modelos. Nesta seção vamos explorar as várias características que o 🤗 Datasets fornece para limpar seus conjuntos de dados. + + + +## Slicing and dicing our data + +Semelhante ao Pandas, 🤗 Datasets fornece várias funções para manipular o conteúdo dos objetos `Dataset` e `DatasetDict`. Já encontramos o método `Dataset.map()` no [Capítulo 3](/course/chapter3), e nesta seção vamos explorar algumas das outras funções à nossa disposição. + +Para este exemplo, usaremos o [Drug Review Dataset](https://archive.ics.uci.edu/ml/datasets/Drug+Review+Dataset+%28Drugs.com%29) que está hospedado na [UC Irvine Machine Learning Repository](https://archive.ics.uci.edu/ml/index.php), que contém avaliações de pacientes sobre vários medicamentos, juntamente com a condição a ser tratada e uma classificação de 10 estrelas da satisfação do paciente. + +Primeiro precisamos baixar e extrair os dados, o que pode ser feito com os comandos `wget` e `unzip`: + +```py +!wget "https://archive.ics.uci.edu/ml/machine-learning-databases/00462/drugsCom_raw.zip" +!unzip drugsCom_raw.zip +``` + +Como o TSV é apenas uma variante do CSV que usa tabulações em vez de vírgulas como separador, podemos carregar esses arquivos usando o script de carregamento `csv` e especificando o argumento `delimiter` na função `load_dataset()` da seguinte forma: + +```py +from datasets import load_dataset + +data_files = {"train": "drugsComTrain_raw.tsv", "test": "drugsComTest_raw.tsv"} +# \t is the tab character in Python +drug_dataset = load_dataset("csv", data_files=data_files, delimiter="\t") +``` + +Uma boa prática ao fazer qualquer tipo de análise de dados é pegar uma pequena amostra aleatória para ter uma ideia rápida do tipo de dados com os quais você está trabalhando. Em 🤗 Datasets, podemos criar uma amostra aleatória encadeando as funções `Dataset.shuffle()` e `Dataset.select()` juntas: + +```py +drug_sample = drug_dataset["train"].shuffle(seed=42).select(range(1000)) +# Peek at the first few examples +drug_sample[:3] +``` + +```python out +{'Unnamed: 0': [87571, 178045, 80482], + 'drugName': ['Naproxen', 'Duloxetine', 'Mobic'], + 'condition': ['Gout, Acute', 'ibromyalgia', 'Inflammatory Conditions'], + 'review': ['"like the previous person mention, I'm a strong believer of aleve, it works faster for my gout than the prescription meds I take. No more going to the doctor for refills.....Aleve works!"', + '"I have taken Cymbalta for about a year and a half for fibromyalgia pain. It is great\r\nas a pain reducer and an anti-depressant, however, the side effects outweighed \r\nany benefit I got from it. I had trouble with restlessness, being tired constantly,\r\ndizziness, dry mouth, numbness and tingling in my feet, and horrible sweating. I am\r\nbeing weaned off of it now. Went from 60 mg to 30mg and now to 15 mg. I will be\r\noff completely in about a week. The fibro pain is coming back, but I would rather deal with it than the side effects."', + '"I have been taking Mobic for over a year with no side effects other than an elevated blood pressure. I had severe knee and ankle pain which completely went away after taking Mobic. I attempted to stop the medication however pain returned after a few days."'], + 'rating': [9.0, 3.0, 10.0], + 'date': ['September 2, 2015', 'November 7, 2011', 'June 5, 2013'], + 'usefulCount': [36, 13, 128]} +``` + +Observe que corrigimos a seed em `Dataset.shuffle()` para fins de reprodutibilidade. `Dataset.select()` espera um iterável de índices, então passamos `range(1000)` para pegar os primeiros 1.000 exemplos do conjunto de dados embaralhado. A partir desta amostra já podemos ver algumas peculiaridades em nosso conjunto de dados: + +* A coluna `Unnamed: 0` se parece com um ID anônimo para cada paciente. +* A coluna `condition` inclui uma combinação de rótulos em maiúsculas e minúsculas. +* As revisões são de tamanho variável e contêm uma mistura de separadores de linha Python (`\r\n`), bem como códigos de caracteres HTML como `&\#039;`. + +Vamos ver como podemos usar 🤗 Datasets para lidar com cada um desses problemas. Para testar a hipótese de ID do paciente para a coluna `Unnamed: 0`, podemos usar a função `Dataset.unique()` para verificar se o número de IDs corresponde ao número de linhas em cada divisão: + +```py +for split in drug_dataset.keys(): + assert len(drug_dataset[split]) == len(drug_dataset[split].unique("Unnamed: 0")) +``` + +Isso parece confirmar nossa hipótese, então vamos limpar um pouco o conjunto de dados renomeando a coluna `Unnamed: 0` para algo um pouco mais interpretável. Podemos usar a função `DatasetDict.rename_column()` para renomear a coluna em ambas as divisões de uma só vez: + +```py +drug_dataset = drug_dataset.rename_column( + original_column_name="Unnamed: 0", new_column_name="patient_id" +) +drug_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'], + num_rows: 161297 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'], + num_rows: 53766 + }) +}) +``` + + + +✏️ **Experimente!** Use a função `Dataset.unique()` para encontrar o número de medicamentos e condições exclusivos nos conjuntos de treinamento e teste. + + + +Em seguida, vamos normalizar todos os rótulos `condition` usando `Dataset.map()`. Como fizemos com a tokenização no [Capítulo 3](/course/chapter3), podemos definir uma função simples que pode ser aplicada em todas as linhas de cada divisão em `drug_dataset`: + +```py +def lowercase_condition(example): + return {"condition": example["condition"].lower()} + + +drug_dataset.map(lowercase_condition) +``` + +```python out +AttributeError: 'NoneType' object has no attribute 'lower' +``` + +Oh não, tivemos um problema com nossa função de mapa! A partir do erro, podemos inferir que algumas das entradas na coluna `condition` são `None`, que não podem ser minúsculas, pois não são strings. Vamos eliminar essas linhas usando `Dataset.filter()`, que funciona de maneira semelhante a `Dataset.map()` e espera uma função que receba um único exemplo do conjunto de dados. Em vez de escrever uma função explícita como: + +```py +def filter_nones(x): + return x["condition"] is not None +``` + +e então executando `drug_dataset.filter(filter_nones)`, podemos fazer isso em uma linha usando uma _função lambda_. Em Python, funções lambda são pequenas funções que você pode definir sem nomeá-las explicitamente. Eles assumem a forma geral: + +``` +lambda : +``` + +onde `lambda` é uma das [palavras-chave] especiais do Python (https://docs.python.org/3/reference/lexical_analysis.html#keywords), `` é uma lista/conjunto de valores separados por vírgula que defina as entradas para a função, e `` representa as operações que você deseja executar. Por exemplo, podemos definir uma função lambda simples que eleva um número ao quadrado da seguinte forma: + +``` +lambda x : x * x +``` + +Para aplicar esta função a uma entrada, precisamos envolvê-la e a entrada entre parênteses: + +```py +(lambda x: x * x)(3) +``` + +```python out +9 +``` + +Da mesma forma, podemos definir funções lambda com vários argumentos, separando-os com vírgulas. Por exemplo, podemos calcular a área de um triângulo da seguinte forma: + +```py +(lambda base, height: 0.5 * base * height)(4, 8) +``` + +```python out +16.0 +``` + +As funções lambda são úteis quando você deseja definir funções pequenas e de uso único (para obter mais informações sobre elas, recomendamos a leitura do excelente [tutorial do Real Python](https://realpython.com/python-lambda/) de Andre Burgaud). No contexto 🤗 Datasets, podemos usar funções lambda para definir operações simples de mapa e filtro, então vamos usar este truque para eliminar as entradas `None` em nosso conjunto de dados: + +```py +drug_dataset = drug_dataset.filter(lambda x: x["condition"] is not None) +``` + +Com as entradas `None` removidas, podemos normalizar nossa coluna `condition`: + +```py +drug_dataset = drug_dataset.map(lowercase_condition) +# Check that lowercasing worked +drug_dataset["train"]["condition"][:3] +``` + +```python out +['left ventricular dysfunction', 'adhd', 'birth control'] +``` + +Funciona! Agora que limpamos os rótulos, vamos dar uma olhada na limpeza dos próprios comentários. + +## Criando novas colunas + +Sempre que estiver lidando com avaliações de clientes, uma boa prática é verificar o número de palavras em cada avaliação. Uma avaliação pode ser apenas uma única palavra como "Ótimo!" ou um ensaio completo com milhares de palavras e, dependendo do caso de uso, você precisará lidar com esses extremos de maneira diferente. Para calcular o número de palavras em cada revisão, usaremos uma heurística aproximada baseada na divisão de cada texto por espaços em branco. + +Vamos definir uma função simples que conta o número de palavras em cada revisão: + +```py +def compute_review_length(example): + return {"review_length": len(example["review"].split())} +``` + +Ao contrário de nossa função `lowercase_condition()`, `compute_review_length()` retorna um dicionário cuja chave não corresponde a um dos nomes de coluna no conjunto de dados. Nesse caso, quando `compute_review_length()` for passado para `Dataset.map()`, ele será aplicado a todas as linhas do conjunto de dados para criar uma nova coluna `review_length`: + +```py +drug_dataset = drug_dataset.map(compute_review_length) +# Inspect the first training example +drug_dataset["train"][0] +``` + +```python out +{'patient_id': 206461, + 'drugName': 'Valsartan', + 'condition': 'left ventricular dysfunction', + 'review': '"It has no side effect, I take it in combination of Bystolic 5 Mg and Fish Oil"', + 'rating': 9.0, + 'date': 'May 20, 2012', + 'usefulCount': 27, + 'review_length': 17} +``` + +Como esperado, podemos ver que uma coluna `review_length` foi adicionada ao nosso conjunto de treinamento. Podemos classificar essa nova coluna com `Dataset.sort()` para ver como são os valores extremos: + +```py +drug_dataset["train"].sort("review_length")[:3] +``` + +```python out +{'patient_id': [103488, 23627, 20558], + 'drugName': ['Loestrin 21 1 / 20', 'Chlorzoxazone', 'Nucynta'], + 'condition': ['birth control', 'muscle spasm', 'pain'], + 'review': ['"Excellent."', '"useless"', '"ok"'], + 'rating': [10.0, 1.0, 6.0], + 'date': ['November 4, 2008', 'March 24, 2017', 'August 20, 2016'], + 'usefulCount': [5, 2, 10], + 'review_length': [1, 1, 1]} +``` + +Como suspeitávamos, algumas revisões contêm apenas uma única palavra, que, embora possa ser boa para análise de sentimentos, não seria informativa se quisermos prever a condição. + + + +🙋 Uma forma alternativa de adicionar novas colunas a um conjunto de dados é com a função `Dataset.add_column()`. Isso permite que você forneça a coluna como uma lista Python ou array NumPy e pode ser útil em situações em que `Dataset.map()` não é adequado para sua análise. + + + +Vamos usar a função `Dataset.filter()` para remover comentários que contenham menos de 30 palavras. Da mesma forma que fizemos com a coluna "condição", podemos filtrar as reviews muito curtas exigindo que as reviews tenham um comprimento acima desse limite. + +```py +drug_dataset = drug_dataset.filter(lambda x: x["review_length"] > 30) +print(drug_dataset.num_rows) +``` + +```python out +{'train': 138514, 'test': 46108} +``` + +Como você pode ver, isso removeu cerca de 15% das avaliações de nossos conjuntos de treinamento e teste originais. + + + +✏️ **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. + + + +A última coisa com a qual precisamos lidar é a presença de códigos de caracteres HTML em nossas análises. Podemos usar o módulo `html` do Python para liberar esses caracteres, assim: + +```py +import html + +text = "I'm a transformer called BERT" +html.unescape(text) +``` + +```python out +"I'm a transformer called BERT" +``` + +Usaremos `Dataset.map()` para liberar todos os caracteres HTML em nosso corpus: + +```python +drug_dataset = drug_dataset.map(lambda x: {"review": html.unescape(x["review"])}) +``` + +Como você pode ver, o método `Dataset.map()` é bastante útil para o processamento de dados -- e ainda nem arranhamos a superfície de tudo o que ele pode fazer! + +## Os superpoderes do método `map()` + +O método `Dataset.map()` recebe um argumento `batched` que, se definido como `True`, faz com que ele envie um batch de exemplos para a função map de uma só vez (o tamanho do batch é configurável, mas o padrão é 1.000). Por exemplo, a função map anterior que não escapou de todo o HTML demorou um pouco para ser executada (você pode ler o tempo gasto nas barras de progresso). Podemos acelerar isso processando vários elementos ao mesmo tempo usando uma compreensão de lista. + +Quando você especifica `batched=True` a função recebe um dicionário com os campos do conjunto de dados, mas cada valor agora é uma _lista de valores_, e não apenas um valor único. O valor de retorno de `Dataset.map()` deve ser o mesmo: um dicionário com os campos que queremos atualizar ou adicionar ao nosso conjunto de dados e uma lista de valores. Por exemplo, aqui está outra maneira de fazer o scape de todos os caracteres HTML, mas usando `batched=True`: + +```python +new_drug_dataset = drug_dataset.map( + lambda x: {"review": [html.unescape(o) for o in x["review"]]}, batched=True +) +``` + +Se você estiver executando esse código em um jupyter notebook, verá que esse comando é executado muito mais rápido que o anterior. E não é porque nossas revisões já foram sem escape em HTML -- se você reexecutar a instrução da seção anterior (sem `batched=True`), levará o mesmo tempo que antes. Isso ocorre porque as compreensões de lista geralmente são mais rápidas do que executar o mesmo código em um loop `for`, e também ganhamos algum desempenho acessando muitos elementos ao mesmo tempo em vez de um por um. + +Usar `Dataset.map()` com `batched=True` será essencial para desbloquear a velocidade dos tokenizers "rápidos" que encontraremos no [Capítulo 6](/course/chapter6), que podem rapidamente tokenizar grandes listas de textos. Por exemplo, para tokenizar todas as análises de medicamentos com um tokenizer rápido, poderíamos usar uma função como esta: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") + + +def tokenize_function(examples): + return tokenizer(examples["review"], truncation=True) +``` + +Como você viu no [Capítulo 3](/course/chapter3), podemos passar um ou vários exemplos para o tokenizer, então podemos usar esta função com ou sem `batched=True`. Vamos aproveitar esta oportunidade para comparar o desempenho das diferentes opções. Em um notebook, você pode cronometrar uma instrução de uma linha adicionando `%time` antes da linha de código que deseja medir: + +```python no-format +%time tokenized_dataset = drug_dataset.map(tokenize_function, batched=True) +``` + +Você também pode cronometrar uma célula inteira colocando `%%time` no início da célula. No hardware em que executamos isso, ele mostrava 10,8s para esta instrução (é o número escrito depois de "Wall time"). + + + +✏️ **Experimente!** Execute a mesma instrução com e sem `batched=True`, então tente com um tokenizer lento (adicione `use_fast=False` no método `AutoTokenizer.from_pretrained()`) para que você possa veja quais números você obtém em seu hardware. + + + +Aqui estão os resultados que obtivemos com e sem batching, com um tokenizer rápido e lento: + +Opções | Tokenizador rápido | Tokenizador lento +:--------------:|:------------------:|:-------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s + +Isso significa que usar um tokenizer rápido com a opção `batched=True` é 30 vezes mais rápido do que seu equivalente lento sem batching -- isso é realmente incrível! Essa é a principal razão pela qual os tokenizers rápidos são o padrão ao usar o `AutoTokenizer` (e porque eles são chamados de "rápidos"). Eles são capazes de alcançar essa aceleração porque nos bastidores o código de tokenização é executado em Rust, que é uma linguagem que facilita a execução de código paralelizado. + +A paralelização também é a razão para a aceleração de quase 6x que o tokenizer rápido alcança com o batching: você não pode paralelizar uma única operação de tokenização, mas quando você deseja tokenizar muitos textos ao mesmo tempo, você pode simplesmente dividir a execução em vários processos, cada um responsável por seus próprios textos. + +`Dataset.map()` também possui alguns recursos de paralelização próprios. Como eles não são suportados pelo Rust, eles não permitem que um tokenizer lento alcance um rápido, mas ainda podem ser úteis (especialmente se você estiver usando um tokenizer que não possui uma versão rápida). Para ativar o multiprocessamento, use o argumento `num_proc` e especifique o número de processos a serem usados ​​em sua chamada para `Dataset.map()`: + +```py +slow_tokenizer = AutoTokenizer.from_pretrained("bert-base-cased", use_fast=False) + + +def slow_tokenize_function(examples): + return slow_tokenizer(examples["review"], truncation=True) + + +tokenized_dataset = drug_dataset.map(slow_tokenize_function, batched=True, num_proc=8) +``` + +Você pode experimentar um pouco o tempo para determinar o número ideal de processos a serem usados; no nosso caso, 8 pareceu produzir o melhor ganho de velocidade. Aqui estão os números que obtivemos com e sem multiprocessamento: + +Opções | Tokenizador rápido | Tokenizador lento +:------------------------------:|:------------------:|:-------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s +`batched=True`, `num_proc=8` | 6.52s | 41.3s +`batched=False`, `num_proc=8` | 9.49s | 45.2s + +Esses são resultados muito mais razoáveis ​​para o tokenizer lento, mas o desempenho do tokenizer rápido também foi substancialmente melhorado. Observe, no entanto, que nem sempre será o caso -- para valores de `num_proc` diferentes de 8, nossos testes mostraram que era mais rápido usar `batched=True` sem essa opção. Em geral, não recomendamos o uso de multiprocessamento Python para tokenizers rápidos com `batched=True`. + + + +Usar `num_proc` para acelerar seu processamento geralmente é uma ótima idéia, desde que a função que você está usando não esteja fazendo algum tipo de multiprocessamento próprio. + + + +Toda essa funcionalidade condensada em um único método já é incrível, mas tem mais! Com `Dataset.map()` e `batched=True` você pode alterar o número de elementos em seu conjunto de dados. Isso é super útil em muitas situações em que você deseja criar vários recursos de treinamento a partir de um exemplo, e precisaremos fazer isso como parte do pré-processamento de várias das tarefas de PNL que realizaremos no [Capítulo 7](/course/chapter7). + + + +💡 No aprendizado de máquina, um _exemplo_ geralmente é definido como o conjunto de _recursos_ que alimentamos o modelo. Em alguns contextos, esses recursos serão o conjunto de colunas em um `Dataset`, mas em outros (como aqui e para resposta a perguntas), vários recursos podem ser extraídos de um único exemplo e pertencer a uma única coluna. + + + +Vamos dar uma olhada em como funciona! Aqui vamos tokenizar nossos exemplos e truncá-los para um comprimento máximo de 128, mas pediremos ao tokenizer para retornar *todos* os pedaços dos textos em vez de apenas o primeiro. Isso pode ser feito com `return_overflowing_tokens=True`: +```py +def tokenize_and_split(examples): + return tokenizer( + examples["review"], + truncation=True, + max_length=128, + return_overflowing_tokens=True, + ) +``` + +Vamos testar isso em um exemplo antes de usar `Dataset.map()` em todo o conjunto de dados: + +```py +result = tokenize_and_split(drug_dataset["train"][0]) +[len(inp) for inp in result["input_ids"]] +``` + +```python out +[128, 49] +``` + +Assim, nosso primeiro exemplo no conjunto de treinamento se tornou dois recursos porque foi tokenizado para mais do que o número máximo de tokens que especificamos: o primeiro de comprimento 128 e o segundo de comprimento 49. Agora vamos fazer isso para todos os elementos do conjunto de dados! + +```py +tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) +``` + +```python out +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. + +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`: + +```py +tokenized_dataset = drug_dataset.map( + tokenize_and_split, batched=True, remove_columns=drug_dataset["train"].column_names +) +``` + +Agora isso funciona sem erro. Podemos verificar que nosso novo conjunto de dados tem muito mais elementos do que o conjunto de dados original comparando os comprimentos: + +```py +len(tokenized_dataset["train"]), len(drug_dataset["train"]) +``` + +```python out +(206772, 138514) +``` + +Mencionamos que também podemos lidar com o problema de comprimento incompatível tornando as colunas antigas do mesmo tamanho das novas. Para fazer isso, precisaremos do campo `overflow_to_sample_mapping` que o tokenizer retorna quando configuramos `return_overflowing_tokens=True`. Ele nos fornece um mapeamento de um novo índice de recurso para o índice da amostra da qual ele se originou. Usando isso, podemos associar cada chave presente em nosso conjunto de dados original a uma lista de valores do tamanho certo, repetindo os valores de cada exemplo quantas vezes ele gerar novos recursos: + +```py +def tokenize_and_split(examples): + result = tokenizer( + examples["review"], + truncation=True, + max_length=128, + return_overflowing_tokens=True, + ) + # Extract mapping between new and old indices + sample_map = result.pop("overflow_to_sample_mapping") + for key, values in examples.items(): + result[key] = [values[i] for i in sample_map] + return result +``` + +Podemos ver que funciona com `Dataset.map()` sem precisarmos remover as colunas antigas: + +```py +tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) +tokenized_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'], + num_rows: 206772 + }) + test: Dataset({ + features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'], + num_rows: 68876 + }) +}) +``` + +Obtemos o mesmo número de recursos de treinamento de antes, mas aqui mantivemos todos os campos antigos. Se você precisar deles para algum pós-processamento após aplicar seu modelo, convém usar essa abordagem. + +Agora você viu como 🤗 Datasets podem ser usados ​​para pré-processar um conjunto de dados de várias maneiras. Embora as funções de processamento de 🤗 Datasets cubram a maioria das suas necessidades de treinamento de modelo, pode haver momentos em que você precisará mudar para o Pandas para acessar recursos mais poderosos, como `DataFrame.groupby()` ou APIs de alto nível para visualização. Felizmente, 🤗 Datasets foi projetado para ser interoperável com bibliotecas como Pandas, NumPy, PyTorch, TensorFlow e JAX. Vamos dar uma olhada em como isso funciona. + +## De `Dataset`s para `DataFrame`s e vice-versa + + + +Para habilitar a conversão entre várias bibliotecas de terceiros, 🤗 Datasets fornece uma função `Dataset.set_format()`. Essa função altera apenas o _formato de saída_ do conjunto de dados, para que você possa alternar facilmente para outro formato sem afetar o _formato de dados_ subjacente, que é o Apache Arrow. A formatação é feita no local. Para demonstrar, vamos converter nosso conjunto de dados para Pandas: + +```py +drug_dataset.set_format("pandas") +``` + +Agora, quando acessamos os elementos do dataset, obtemos um `pandas.DataFrame` em vez de um dicionário: + +```py +drug_dataset["train"][:3] +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
patient_iddrugNameconditionreviewratingdateusefulCountreview_length
095260Guanfacineadhd"My son is halfway through his fourth week of Intuniv..."8.0April 27, 2010192141
192703Lybrelbirth control"I used to take another oral contraceptive, which had 21 pill cycle, and was very happy- very light periods, max 5 days, no other side effects..."5.0December 14, 200917134
2138000Ortho Evrabirth control"This is my first time using any form of birth control..."8.0November 3, 20151089
+ +Vamos criar um `pandas.DataFrame` para todo o conjunto de treinamento selecionando todos os elementos de `drug_dataset["train"]`: + +```py +train_df = drug_dataset["train"][:] +``` + + + +🚨 `Dataset.set_format()` altera o formato de retorno para o método dunder `__getitem__()` do conjunto de dados. Isso significa que quando queremos criar um novo objeto como `train_df` a partir de um `Dataset` no formato `"pandas"`, precisamos dividir todo o conjunto de dados para obter um `pandas.DataFrame`. Você pode verificar por si mesmo que o tipo de `drug_dataset["train"]` é `Dataset`, independentemente do formato de saída. + + + + +A partir daqui, podemos usar todas as funcionalidades do Pandas que queremos. Por exemplo, podemos fazer um encadeamento sofisticado para calcular a distribuição de classes entre as entradas `condition`: + +```py +frequencies = ( + train_df["condition"] + .value_counts() + .to_frame() + .reset_index() + .rename(columns={"index": "condition", "condition": "frequency"}) +) +frequencies.head() +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
conditionfrequency
0birth control27655
1depression8023
2acne5209
3anxiety4991
4pain4744
+ + +E uma vez que terminamos nossa análise de Pandas, sempre podemos criar um novo objeto `Dataset` usando a função `Dataset.from_pandas()` da seguinte forma: + + +```py +from datasets import Dataset + +freq_dataset = Dataset.from_pandas(frequencies) +freq_dataset +``` + +```python out +Dataset({ + features: ['condition', 'frequency'], + num_rows: 819 +}) +``` + + + +✏️ **Experimente!** Calcule a classificação média por medicamento e armazene o resultado em um novo `Dataset`. + + + +Isso encerra nosso tour pelas várias técnicas de pré-processamento disponíveis em 🤗 Datasets. Para completar a seção, vamos criar um conjunto de validação para preparar o conjunto de dados para treinar um classificador. Antes de fazer isso, vamos redefinir o formato de saída de `drug_dataset` de `"pandas"` para `"arrow"`: + +```python +drug_dataset.reset_format() +``` + +## Criando um conjunto de validação + +Embora tenhamos um conjunto de teste que poderíamos usar para avaliação, é uma boa prática deixar o conjunto de teste intocado e criar um conjunto de validação separado durante o desenvolvimento. Quando estiver satisfeito com o desempenho de seus modelos no conjunto de validação, você poderá fazer uma verificação final de sanidade no conjunto de teste. Esse processo ajuda a mitigar o risco de você se ajustar demais ao conjunto de teste e implantar um modelo que falha em dados do mundo real. + +🤗 Datasets fornece uma função `Dataset.train_test_split()` que é baseada na famosa funcionalidade do `scikit-learn`. Vamos usá-lo para dividir nosso conjunto de treinamento em divisões `train` e `validation` (definimos o argumento `seed` para reprodutibilidade): + +```py +drug_dataset_clean = drug_dataset["train"].train_test_split(train_size=0.8, seed=42) +# Rename the default "test" split to "validation" +drug_dataset_clean["validation"] = drug_dataset_clean.pop("test") +# Add the "test" set to our `DatasetDict` +drug_dataset_clean["test"] = drug_dataset["test"] +drug_dataset_clean +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 110811 + }) + validation: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 27703 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 46108 + }) +}) +``` + +Ótimo, agora preparamos um conjunto de dados pronto para treinar alguns modelos! Na [seção 5](/course/chapter5/5), mostraremos como fazer upload de conjuntos de dados para o Hugging Face Hub, mas, por enquanto, vamos encerrar nossa análise analisando algumas maneiras de salvar conjuntos de dados em sua máquina local . + +## Salvando um conjunto de dados + + + +Embora 🤗 Datasets armazene em cache todos os conjuntos de dados baixados e as operações realizadas nele, há momentos em que você deseja salvar um conjunto de dados em disco (por exemplo, caso o cache seja excluído). Conforme mostrado na tabela abaixo, 🤗 Datasets fornece três funções principais para salvar seu conjunto de dados em diferentes formatos: + +| Formato dos dados | Função | +| :---------: | :-----------------------------------: | +| Arrow | `Dataset.save_to_disk()` | +| CSV | `Dataset.to_csv()` | +| JSON | `Dataset.to_json()` | + +Por exemplo, vamos salvar nosso conjunto de dados limpo no formato Arrow: + +```py +drug_dataset_clean.save_to_disk("drug-reviews") +``` + +Isso criará um diretório com a seguinte estrutura: + +``` +drug-reviews/ +├── dataset_dict.json +├── test +│ ├── dataset.arrow +│ ├── dataset_info.json +│ └── state.json +├── train +│ ├── dataset.arrow +│ ├── dataset_info.json +│ ├── indices.arrow +│ └── state.json +└── validation + ├── dataset.arrow + ├── dataset_info.json + ├── indices.arrow + └── state.json +``` + +onde podemos ver que cada divisão está associada a sua própria tabela *dataset.arrow* e alguns metadados em *dataset_info.json* e *state.json*. Você pode pensar no formato Arrow como uma tabela sofisticada de colunas e linhas otimizada para criar aplicativos de alto desempenho que processam e transportam grandes conjuntos de dados. + +Uma vez que o conjunto de dados é salvo, podemos carregá-lo usando a função `load_from_disk()` da seguinte forma: + +```py +from datasets import load_from_disk + +drug_dataset_reloaded = load_from_disk("drug-reviews") +drug_dataset_reloaded +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 110811 + }) + validation: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 27703 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 46108 + }) +}) +``` + +Para os formatos CSV e JSON, temos que armazenar cada divisão como um arquivo separado. Uma maneira de fazer isso é iterando as chaves e os valores no objeto `DatasetDict`: + +```py +for split, dataset in drug_dataset_clean.items(): + dataset.to_json(f"drug-reviews-{split}.jsonl") +``` + +Isso salva cada divisão em [formato de linhas JSON](https://jsonlines.org), em que cada linha no conjunto de dados é armazenada como uma única linha de JSON. Veja como é o primeiro exemplo: + +```py +!head -n 1 drug-reviews-train.jsonl +``` + +```python out +{"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} +``` + +Podemos então usar as técnicas da [seção 2](/course/chapter5/2) para carregar os arquivos JSON da seguinte forma: + +```py +data_files = { + "train": "drug-reviews-train.jsonl", + "validation": "drug-reviews-validation.jsonl", + "test": "drug-reviews-test.jsonl", +} +drug_dataset_reloaded = load_dataset("json", data_files=data_files) +``` + +E é isso para nossa excursão em dados com 🤗 Datasets! Agora que temos um conjunto de dados limpo para treinar um modelo, aqui estão algumas ideias que você pode experimentar: + +1. Use as técnicas do [Capítulo 3](/course/chapter3) para treinar um classificador que possa prever a condição do paciente com base na revisão do medicamento. +2. Use o pipeline `summarization` do [Capítulo 1](/course/chapter1) para gerar resumos das revisões. + +A seguir, veremos como 🤗 Datasets pode permitir que você trabalhe com grandes conjuntos de dados sem explodir seu laptop! diff --git a/utils/generate_notebooks.py b/utils/generate_notebooks.py index 75676757e..87f2bb38b 100644 --- a/utils/generate_notebooks.py +++ b/utils/generate_notebooks.py @@ -60,7 +60,6 @@ def read_and_split_frameworks(fname): else: return "".join(content) - def extract_cells(content): """ Extract the code/output cells from content. From c5a178415f92e88b2d8a92bcd9db2a1b5dfcb8f8 Mon Sep 17 00:00:00 2001 From: lewtun Date: Mon, 30 May 2022 13:48:09 +0200 Subject: [PATCH 18/51] Bump release (#229) --- README.md | 3 + chapters/en/chapter9/2.mdx | 7 + chapters/en/chapter9/3.mdx | 7 + chapters/en/chapter9/4.mdx | 15 +- chapters/en/chapter9/5.mdx | 7 + chapters/en/chapter9/6.mdx | 9 +- chapters/en/chapter9/7.mdx | 7 + chapters/fr/chapter9/2.mdx | 7 + chapters/fr/chapter9/3.mdx | 7 + chapters/fr/chapter9/4.mdx | 7 + chapters/fr/chapter9/5.mdx | 7 + chapters/fr/chapter9/6.mdx | 7 + chapters/fr/chapter9/7.mdx | 7 + chapters/hi/_toctree.yml | 4 + chapters/hi/chapter1/3.mdx | 344 +++++++++++++++++++++++++++ chapters/hi/chapter1/4.mdx | 166 ++++++++++++++ chapters/pt/_toctree.yml | 7 + chapters/pt/chapter5/4.mdx | 287 +++++++++++++++++++++++ chapters/pt/chapter7/1.mdx | 33 +++ chapters/zh-CN/_toctree.yml | 23 +- chapters/zh-CN/chapter3/1.mdx | 21 ++ chapters/zh-CN/chapter3/2.mdx | 383 +++++++++++++++++++++++++++++++ chapters/zh-CN/chapter3/3.mdx | 172 ++++++++++++++ chapters/zh-CN/chapter3/3_tf.mdx | 190 +++++++++++++++ chapters/zh-CN/chapter3/4.mdx | 358 +++++++++++++++++++++++++++++ chapters/zh-CN/chapter3/5.mdx | 20 ++ chapters/zh-CN/chapter3/6.mdx | 284 +++++++++++++++++++++++ utils/code_formatter.py | 30 +-- utils/generate_notebooks.py | 98 ++++---- 29 files changed, 2454 insertions(+), 63 deletions(-) create mode 100644 chapters/hi/chapter1/3.mdx create mode 100644 chapters/hi/chapter1/4.mdx create mode 100644 chapters/pt/chapter5/4.mdx create mode 100644 chapters/pt/chapter7/1.mdx create mode 100644 chapters/zh-CN/chapter3/1.mdx create mode 100644 chapters/zh-CN/chapter3/2.mdx create mode 100644 chapters/zh-CN/chapter3/3.mdx create mode 100644 chapters/zh-CN/chapter3/3_tf.mdx create mode 100644 chapters/zh-CN/chapter3/4.mdx create mode 100644 chapters/zh-CN/chapter3/5.mdx create mode 100644 chapters/zh-CN/chapter3/6.mdx diff --git a/README.md b/README.md index 9f7590e27..9f46e8eef 100644 --- a/README.md +++ b/README.md @@ -9,14 +9,17 @@ This repo contains the content that's used to create the **[Hugging Face course] | [English](https://huggingface.co/course/en/chapter1/1) | [`chapters/en`](https://github.com/huggingface/course/tree/main/chapters/en) | [@sgugger](https://github.com/sgugger), [@lewtun](https://github.com/lewtun), [@LysandreJik](https://github.com/LysandreJik), [@Rocketknight1](https://github.com/Rocketknight1), [@sashavor](https://github.com/sashavor), [@osanseviero](https://github.com/osanseviero), [@SaulLu](https://github.com/SaulLu), [@lvwerra](https://github.com/lvwerra) | | [Chinese (simplified)](https://huggingface.co/course/zh/chapter1/1) (WIP) | [`chapters/zh`](https://github.com/huggingface/course/tree/main/chapters/zh) | [@zhlhyx](https://github.com/zhlhyx), [petrichor1122](https://github.com/petrichor1122), [@1375626371](https://github.com/1375626371) | | [French](https://huggingface.co/course/fr/chapter1/1) (WIP) | [`chapters/fr`](https://github.com/huggingface/course/tree/main/chapters/fr) | [@lbourdois](https://github.com/lbourdois), [@ChainYo](https://github.com/ChainYo), [@melaniedrevet](https://github.com/melaniedrevet), [@abdouaziz](https://github.com/abdouaziz) | +| [Gujarati](https://huggingface.co/course/gu/chapter1/1) (WIP) | [`chapters/gu`](https://github.com/huggingface/course/tree/main/chapters/gu) | [@pandyaved98](https://github.com/pandyaved98) | | [Hindi](https://huggingface.co/course/hi/chapter1/1) (WIP) | [`chapters/hi`](https://github.com/huggingface/course/tree/main/chapters/hi) | [@pandyaved98](https://github.com/pandyaved98) | | [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) | | [Persian](https://huggingface.co/course/fa/chapter1/1) (WIP) | [`chapters/fa`](https://github.com/huggingface/course/tree/main/chapters/fa) | [@jowharshamshiri](https://github.com/jowharshamshiri), [@schoobani](https://github.com/schoobani) | +| [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) | | [Spanish](https://huggingface.co/course/es/chapter1/1) (WIP) | [`chapters/es`](https://github.com/huggingface/course/tree/main/chapters/es) | [@camartinezbu](https://github.com/camartinezbu), [@munozariasjm](https://github.com/munozariasjm), [@fordaz](https://github.com/fordaz) | | [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) | + ### Translating the course into your language As part of our mission to democratise machine learning, we'd love to have the course available in many more languages! Please follow the steps below if you'd like to help translate the course into your language 🙏. diff --git a/chapters/en/chapter9/2.mdx b/chapters/en/chapter9/2.mdx index c2d57ef33..d6445763a 100644 --- a/chapters/en/chapter9/2.mdx +++ b/chapters/en/chapter9/2.mdx @@ -1,5 +1,12 @@ # Building your first demo + + Let's start by installing Gradio! Since it is a Python package, simply run: `$ pip install gradio ` diff --git a/chapters/en/chapter9/3.mdx b/chapters/en/chapter9/3.mdx index 33450ecae..7ce306c77 100644 --- a/chapters/en/chapter9/3.mdx +++ b/chapters/en/chapter9/3.mdx @@ -1,5 +1,12 @@ # Understanding the Interface class + + In this section, we will take a closer look at the `Interface` class, and understand the main parameters used to create one. diff --git a/chapters/en/chapter9/4.mdx b/chapters/en/chapter9/4.mdx index ec5b66915..ad8b4714a 100644 --- a/chapters/en/chapter9/4.mdx +++ b/chapters/en/chapter9/4.mdx @@ -1,5 +1,12 @@ # Sharing demos with others + + Now that you've built a demo, you'll probably want to share it with others. Gradio demos can be shared in two ways: using a ***temporary share link*** or ***permanent hosting on Spaces***. @@ -21,10 +28,9 @@ To add additional content to your demo, the `Interface` class supports some opti - `live`: if you want to make your demo "live", meaning that your model reruns every time the input changes, you can set `live=True`. This makes sense to use with quick models (we'll see an example at the end of this section) Using the options above, we end up with a more complete interface. Run the code below so you can chat with Rick and Morty: -```python out +```py title = "Ask Rick a Question" -description = -""" +description = """ The bot was trained to answer questions based on Rick and Morty dialogues. Ask Rick anything! """ @@ -38,7 +44,7 @@ gr.Interface( title=title, description=description, article=article, - examples=[["What are you doing?"], ["Where should we time travel to?"]] + examples=[["What are you doing?"], ["Where should we time travel to?"]], ).launch() ``` @@ -111,6 +117,7 @@ def predict(im): ``` Now that we have a `predict()` function. The next step is to define and launch our gradio interface: + ```py interface = gr.Interface( predict, diff --git a/chapters/en/chapter9/5.mdx b/chapters/en/chapter9/5.mdx index e2355dac0..31c796ce6 100644 --- a/chapters/en/chapter9/5.mdx +++ b/chapters/en/chapter9/5.mdx @@ -1,5 +1,12 @@ # Integrations with the Hugging Face Hub + + To make your life even easier, Gradio integrates directly with Hugging Face Hub and Hugging Face Spaces. You can load demos from the Hub and Spaces with only *one line of code*. diff --git a/chapters/en/chapter9/6.mdx b/chapters/en/chapter9/6.mdx index 95b412626..28a8c17f4 100644 --- a/chapters/en/chapter9/6.mdx +++ b/chapters/en/chapter9/6.mdx @@ -1,6 +1,13 @@ # Advanced Interface features -Now that we can build and share a basic interface, let's explore some more advanced features such as state and interpretation. + + +Now that we can build and share a basic interface, let's explore some more advanced features such as state, and interpretation. ### Using state to persist data diff --git a/chapters/en/chapter9/7.mdx b/chapters/en/chapter9/7.mdx index 76299fbbc..7d6de8c1b 100644 --- a/chapters/en/chapter9/7.mdx +++ b/chapters/en/chapter9/7.mdx @@ -1,5 +1,12 @@ # Introduction to Gradio Blocks + + In the previous sections we have explored and created demos using the `Interface` class. In this section we will introduce our **newly developed** low-level API called `gradio.Blocks`. Now, what's the difference between `Interface` and `Blocks`? diff --git a/chapters/fr/chapter9/2.mdx b/chapters/fr/chapter9/2.mdx index 339680935..813c3df3d 100644 --- a/chapters/fr/chapter9/2.mdx +++ b/chapters/fr/chapter9/2.mdx @@ -1,5 +1,12 @@ # Construire votre première démo + + Commençons par installer *Gradio* ! Comme il s'agit d'un *package* Python, il suffit de l'exécuter : `$ pip install gradio ` diff --git a/chapters/fr/chapter9/3.mdx b/chapters/fr/chapter9/3.mdx index 9c8e6dcca..3a6b97f70 100644 --- a/chapters/fr/chapter9/3.mdx +++ b/chapters/fr/chapter9/3.mdx @@ -1,5 +1,12 @@ # Comprendre la classe Interface + + Dans cette section, nous allons examiner de plus près la classe `Interface`, et comprendre les principaux paramètres utilisés pour en créer une. ## Comment créer une interface diff --git a/chapters/fr/chapter9/4.mdx b/chapters/fr/chapter9/4.mdx index 5e5f89510..ffea14513 100644 --- a/chapters/fr/chapter9/4.mdx +++ b/chapters/fr/chapter9/4.mdx @@ -1,5 +1,12 @@ # Partager ses démos avec les autres + + Maintenant que vous avez construit une démo, vous voudrez probablement la partager à d'autres personnes. Les démos *Gradio* peuvent être partagées de deux façons : en utilisant un lien de partage temporaire (***temporary share link***) ou un hébergement permanent (***permanent hosting on Spaces***). Nous aborderons ces deux approches sous peu. Mais avant de partager votre démo, vous voudrez peut-être la peaufiner 💅. diff --git a/chapters/fr/chapter9/5.mdx b/chapters/fr/chapter9/5.mdx index a30853b9c..e4a8c6f07 100644 --- a/chapters/fr/chapter9/5.mdx +++ b/chapters/fr/chapter9/5.mdx @@ -1,5 +1,12 @@ # Intégrations avec le Hub d'Hugging Face + + Pour vous rendre la vie encore plus facile, *Gradio* s'intègre directement avec *Hub* et *Spaces*. Vous pouvez charger des démos depuis le *Hub* et les *Spaces* avec seulement *une ligne de code*. diff --git a/chapters/fr/chapter9/6.mdx b/chapters/fr/chapter9/6.mdx index 06301e577..c3ca388e8 100644 --- a/chapters/fr/chapter9/6.mdx +++ b/chapters/fr/chapter9/6.mdx @@ -1,5 +1,12 @@ # Fonctionnalités avancées de l'interface + + Maintenant que nous pouvons construire et partager une interface de base, explorons quelques fonctionnalités plus avancées comme l'état, l'interprétation et l'authentification. ### Utilisation de l'état pour faire persister les données diff --git a/chapters/fr/chapter9/7.mdx b/chapters/fr/chapter9/7.mdx index c6cc7054f..8c66d2231 100644 --- a/chapters/fr/chapter9/7.mdx +++ b/chapters/fr/chapter9/7.mdx @@ -1,5 +1,12 @@ # Introduction à la classe Blocks + + Dans les sections précédentes, nous avons exploré et créé des démos en utilisant la classe `Interface`. Dans cette section, nous allons présenter une **nouvelle** API de bas niveau appelée `gradio.Blocks`. Quelle est la différence entre `Interface` et `Blocks` ? diff --git a/chapters/hi/_toctree.yml b/chapters/hi/_toctree.yml index f8f856b4b..c79b5c2c2 100644 --- a/chapters/hi/_toctree.yml +++ b/chapters/hi/_toctree.yml @@ -9,6 +9,10 @@ title: परिचय - local: chapter1/2 title: प्राकृतिक भाषा प्रसंस्करण + - local: chapter1/3 + title: ट्रांसफार्मर, वे क्या कर सकते हैं? + - local: chapter1/4 + title: ट्रांसफॉर्मर कैसे काम करते हैं? - title: 2. ट्रांसफॉर्मर का उपयोग करना sections: diff --git a/chapters/hi/chapter1/3.mdx b/chapters/hi/chapter1/3.mdx new file mode 100644 index 000000000..d40137645 --- /dev/null +++ b/chapters/hi/chapter1/3.mdx @@ -0,0 +1,344 @@ +# ट्रांसफार्मर, वे क्या कर सकते हैं? + + + +इस खंड में, हम देखेंगे कि ट्रांसफॉर्मर मॉडल क्या कर सकते हैं और 🤗 ट्रांसफॉर्मर्स लाइब्रेरी: `पाइपलाइन ()` फ़ंक्शन से हमारे पहले टूल का उपयोग कर सकते हैं। + + + +👀 ऊपर दाईं ओर *Colab में खोलें* बटन देखें? इस अनुभाग के सभी कोड नमूनों के साथ Google Colab नोटबुक खोलने के लिए उस पर क्लिक करें। यह बटन कोड उदाहरणों वाले किसी भी अनुभाग में मौजूद होगा। + +यदि आप उदाहरणों को स्थानीय रूप से चलाना चाहते हैं, तो हम सेटअप पर एक नज़र डालने की अनुशंसा करते हैं। + + + +## ट्रांसफॉर्मर हर जगह हैं! + +पिछले अनुभाग में उल्लिखित सभी प्रकार के एनएलपी कार्यों को हल करने के लिए ट्रांसफार्मर मॉडल का उपयोग किया जाता है। हगिंग फेस और ट्रांसफॉर्मर मॉडल का उपयोग करने वाली कुछ कंपनियां और संगठन यहां दिए गए हैं, जो अपने मॉडल साझा करके समुदाय में वापस योगदान करते हैं: + +Companies using Hugging Face + +[🤗 ट्रांसफॉर्मर्स लाइब्रेरी](https://github.com/huggingface/transformers) उन साझा मॉडलों को बनाने और उपयोग करने की कार्यक्षमता प्रदान करती है। [मॉडल हब](https://huggingface.co/models) में हजारों पूर्व-प्रशिक्षित मॉडल हैं जिन्हें कोई भी डाउनलोड और उपयोग कर सकता है। आप हब पर अपने स्वयं के मॉडल भी अपलोड कर सकते हैं! + + + ⚠️ हगिंग फेस हब ट्रांसफॉर्मर मॉडल तक सीमित नहीं है। कोई भी किसी भी प्रकार के मॉडल या डेटासेट साझा कर सकता है! सभी उपलब्ध सुविधाओं का लाभ उठाने के लिए एक हगिंगफेस खाता बनाएं! + + +ट्रांसफॉर्मर मॉडल हुड के तहत कैसे काम करते हैं, यह जानने से पहले, आइए कुछ उदाहरण देखें कि कुछ दिलचस्प प्राकृतिक भाषा प्रसंस्करण समस्याओं को हल करने के लिए उनका उपयोग कैसे किया जा सकता है। + +## पाइपलाइनों के साथ काम करना + + + +🤗 ट्रान्सफ़ॉर्मर्स लाइब्रेरी में सबसे बुनियादी वस्तु `पाइपलाइन ()` फ़ंक्शन है। यह एक मॉडल को इसके आवश्यक प्रीप्रोसेसिंग और पोस्टप्रोसेसिंग चरणों से जोड़ता है, जिससे हम किसी भी टेक्स्ट को सीधे इनपुट कर सकते हैं और एक समझदार उत्तर प्राप्त कर सकते हैं: + +```python +from transformers import pipeline + +classifier = pipeline("sentiment-analysis") +classifier("I've been waiting for a HuggingFace course my whole life.") +``` + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}] +``` + +हम कई वाक्य भी पास कर सकते हैं! + +```python +classifier( + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!"] +) +``` + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}, + {'label': 'NEGATIVE', 'score': 0.9994558095932007}] +``` + +डिफ़ॉल्ट रूप से, यह पाइपलाइन एक विशेष पूर्व-प्रशिक्षित मॉडल का चयन करती है जिसे अंग्रेजी में भावना विश्लेषण के लिए ठीक किया गया है। जब आप `क्लासिफायर` ऑब्जेक्ट बनाते हैं तो मॉडल डाउनलोड और कैश किया जाता है। यदि आप कमांड को फिर से चलाते हैं, तो इसके बजाय कैश्ड मॉडल का उपयोग किया जाएगा और मॉडल को फिर से डाउनलोड करने की कोई आवश्यकता नहीं है। + +जब आप किसी टेक्स्ट को पाइपलाइन में पास करते हैं तो इसमें तीन मुख्य चरण शामिल होते हैं: + +1. पाठ को एक प्रारूप में पूर्वसंसाधित किया जाता है जिसे मॉडल समझ सकता है। +2. प्रीप्रोसेस्ड इनपुट मॉडल को पास कर दिए जाते हैं। +3. मॉडल की भविष्यवाणियां पोस्ट-प्रोसेस की जाती हैं, इसलिए आप उन्हें समझ सकते हैं। + + +वर्तमान में कुछ [उपलब्ध पाइपलाइन](https://huggingface.co/transformers/main_classes/pipelines.html) हैं: + +- `feature-extraction` (पाठ का वेक्टर प्रतिनिधित्व प्राप्त करें) +- `fill-mask` +- `ner` (नामित इकाई मान्यता) +- `question-answering` +- `sentiment-analysis` +- `summarization` +- `text-generation` +- `translation` +- `zero-shot-classification` + +आइए इनमें से कुछ पर एक नजर डालते हैं! + +## जीरो-शॉट वर्गीकरण + +हम एक अधिक चुनौतीपूर्ण कार्य से निपटने के साथ शुरू करेंगे जहां हमें उन ग्रंथों को वर्गीकृत करने की आवश्यकता है जिन्हें लेबल नहीं किया गया है। वास्तविक दुनिया की परियोजनाओं में यह एक सामान्य परिदृश्य है क्योंकि व्याख्या पाठ आमतौर पर समय लेने वाला होता है और इसके लिए डोमेन विशेषज्ञता की आवश्यकता होती है। इस उपयोग के मामले के लिए, 'शून्य-शॉट-वर्गीकरण' पाइपलाइन बहुत शक्तिशाली है: यह आपको यह निर्दिष्ट करने की अनुमति देती है कि वर्गीकरण के लिए कौन से लेबल का उपयोग करना है, इसलिए आपको पूर्व-प्रशिक्षित मॉडल के लेबल पर भरोसा करने की आवश्यकता नहीं है। आप पहले ही देख चुके हैं कि कैसे मॉडल उन दो लेबलों का उपयोग करके एक वाक्य को सकारात्मक या नकारात्मक के रूप में वर्गीकृत कर सकता है - लेकिन यह आपके द्वारा पसंद किए जाने वाले लेबल के किसी अन्य सेट का उपयोग करके टेक्स्ट को वर्गीकृत भी कर सकता है। + +```python +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +classifier( + "This is a course about the Transformers library", + candidate_labels=["education", "politics", "business"], +) +``` + +```python out +{'sequence': 'This is a course about the Transformers library', + 'labels': ['education', 'business', 'politics'], + 'scores': [0.8445963859558105, 0.111976258456707, 0.043427448719739914]} +``` + +```python +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +classifier( + "This is a course about the Transformers library", + candidate_labels=["education", "politics", "business"], +) +``` + +```python out +{'sequence': 'This is a course about the Transformers library', + 'labels': ['education', 'business', 'politics'], + 'scores': [0.8445963859558105, 0.111976258456707, 0.043427448719739914]} +``` + +इस पाइपलाइन को _शून्य-शॉट_ कहा जाता है क्योंकि इसका उपयोग करने के लिए आपको अपने डेटा पर मॉडल को फ़ाइन-ट्यून करने की आवश्यकता नहीं है। यह आपके इच्छित लेबल की किसी भी सूची के लिए सीधे संभाव्यता स्कोर लौटा सकता है! + + + + ✏️ **कोशिश करके देखो!** अपने स्वयं के अनुक्रमों और लेबलों के साथ खेलें और देखें कि मॉडल कैसा व्यवहार करता है। + + + +## पाठ निर्माण + +अब देखते हैं कि कुछ पाठ उत्पन्न करने के लिए पाइपलाइन का उपयोग कैसे करें। यहां मुख्य विचार यह है कि आप एक संकेत प्रदान करते हैं और शेष पाठ उत्पन्न करके मॉडल इसे स्वतः पूर्ण कर देगा। यह प्रेडिक्टिव टेक्स्ट फीचर के समान है जो कई फोन पर पाया जाता है। पाठ निर्माण में यादृच्छिकता शामिल है, इसलिए यदि आपको नीचे दिखाए गए अनुसार समान परिणाम नहीं मिलते हैं तो यह सामान्य है। + +```python +from transformers import pipeline + +generator = pipeline("text-generation") +generator("In this course, we will teach you how to") +``` + +```python out +[{'generated_text': 'In this course, we will teach you how to understand and use ' + 'data flow and data interchange when handling user data. We ' + 'will be working with one or more of the most commonly used ' + 'data flows — data flows of various types, as seen by the ' + 'HTTP'}] +``` + +आप यह नियंत्रित कर सकते हैं कि `num_return_sequences` तर्क और `max_length` तर्क के साथ आउटपुट टेक्स्ट की कुल लंबाई के साथ कितने अलग-अलग क्रम उत्पन्न होते हैं। + + + +✏️ **कोशिश करके देखो!** 15 शब्दों के दो वाक्य बनाने के लिए `num_return_sequences` और `max_length` तर्कों का उपयोग करें। + + + +## हब से पाइपलाइन में किसी भी मॉडल का उपयोग करना + +पिछले उदाहरणों में कार्य के लिए डिफ़ॉल्ट मॉडल का उपयोग किया गया था, लेकिन आप किसी विशिष्ट कार्य के लिए पाइपलाइन में उपयोग करने के लिए हब से एक विशेष मॉडल भी चुन सकते हैं - जैसे, टेक्स्ट जनरेशन। [मॉडल हब](https://huggingface.co/models) पर जाएं और उस कार्य के लिए केवल समर्थित मॉडल प्रदर्शित करने के लिए बाईं ओर संबंधित टैग पर क्लिक करें। आपको [इस](https://huggingface.co/models?pipeline_tag=text-generation) जैसे पेज पर पहुंचना चाहिए। + +आइए [`distilgpt2`](https://huggingface.co/distilgpt2) मॉडल को आज़माएं! इसे पहले की तरह उसी पाइपलाइन में लोड करने का तरीका यहां दिया गया है: + +```python +from transformers import pipeline + +generator = pipeline("text-generation", model="distilgpt2") +generator( + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, +) +``` + +```python out +[{'generated_text': 'In this course, we will teach you how to manipulate the world and ' + 'move your mental and physical capabilities to your advantage.'}, + {'generated_text': 'In this course, we will teach you how to become an expert and ' + 'practice realtime, and with a hands on experience on both real ' + 'time and real'}] +``` + +आप भाषा टैग पर क्लिक करके और किसी अन्य भाषा में पाठ उत्पन्न करने वाला मॉडल चुनकर मॉडल के लिए अपनी खोज को परिष्कृत कर सकते हैं। मॉडल हब में बहुभाषी मॉडल के लिए चौकियां भी शामिल हैं जो कई भाषाओं का समर्थन करती हैं। + +एक बार जब आप उस पर क्लिक करके एक मॉडल का चयन करते हैं, तो आप देखेंगे कि एक विजेट है जो आपको इसे सीधे ऑनलाइन आज़माने में सक्षम बनाता है। इस प्रकार आप मॉडल को डाउनलोड करने से पहले उसकी क्षमताओं का शीघ्रता से परीक्षण कर सकते हैं। + + + + ✏️ **कोशिश करके देखो!** किसी अन्य भाषा के लिए टेक्स्ट जनरेशन मॉडल खोजने के लिए फ़िल्टर का उपयोग करें। विजेट के साथ खेलने के लिए स्वतंत्र महसूस करें और इसे पाइपलाइन में उपयोग करें! + + + +## अनुमान एपीआई + +हगिंग फेस [वेबसाइट](https://huggingface.co/) पर उपलब्ध इनफरेंस एपीआई का उपयोग करके सभी मॉडलों का सीधे आपके ब्राउज़र के माध्यम से परीक्षण किया जा सकता है। आप कस्टम टेक्स्ट इनपुट करके और इनपुट डेटा की मॉडल प्रक्रिया को देखकर सीधे इस पृष्ठ पर मॉडल के साथ खेल सकते हैं। + +विजेट को शक्ति प्रदान करने वाला अनुमान एपीआई एक सशुल्क उत्पाद के रूप में भी उपलब्ध है, जो आपके वर्कफ़्लो के लिए ज़रूरत पड़ने पर काम आता है। अधिक विवरण के लिए [मूल्य निर्धारण पृष्ठ](https://huggingface.co/pricing) देखें। + +## मास्क भरना + +अगली पाइपलाइन जो आप आजमाएंगे वह है `फिल-मास्क`। इस कार्य का विचार किसी दिए गए पाठ में रिक्त स्थान को भरना है: + +```python +from transformers import pipeline + +unmasker = pipeline("fill-mask") +unmasker("This course will teach you all about models.", top_k=2) +``` + +```python out +[{'sequence': 'This course will teach you all about mathematical models.', + 'score': 0.19619831442832947, + 'token': 30412, + 'token_str': ' mathematical'}, + {'sequence': 'This course will teach you all about computational models.', + 'score': 0.04052725434303284, + 'token': 38163, + 'token_str': ' computational'}] +``` + +`top_k` तर्क नियंत्रित करता है कि आप कितनी संभावनाएं प्रदर्शित करना चाहते हैं। ध्यान दें कि यहां मॉडल विशेष `` शब्द भरता है, जिसे अक्सर *मास्क टोकन* के रूप में संदर्भित किया जाता है। अन्य मुखौटा-भरने वाले मॉडलों में अलग-अलग मुखौटा टोकन हो सकते हैं, इसलिए अन्य मॉडलों की खोज करते समय उचित मुखौटा शब्द को सत्यापित करना हमेशा अच्छा होता है। इसे जांचने का एक तरीका विजेट में प्रयुक्त मुखौटा शब्द को देखकर है। + + + + ✏️ **कोशिश करके देखो!** हब पर `बर्ट-बेस-केस्ड` मॉडल खोजें और अनुमान एपीआई विजेट में इसके मुखौटा शब्द की पहचान करें। यह मॉडल उपरोक्त हमारे `पाइपलाइन` उदाहरण में वाक्य के लिए क्या भविष्यवाणी करता है? + + + +## नामित इकाई मान्यता + +नामांकित इकाई पहचान (एनईआर) एक ऐसा कार्य है जहां मॉडल को यह पता लगाना होता है कि इनपुट टेक्स्ट के कौन से हिस्से व्यक्तियों, स्थानों या संगठनों जैसी संस्थाओं से मेल खाते हैं। आइए एक उदाहरण देखें: + +```python +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +```python out +[{'entity_group': 'PER', 'score': 0.99816, 'word': 'Sylvain', 'start': 11, 'end': 18}, + {'entity_group': 'ORG', 'score': 0.97960, 'word': 'Hugging Face', 'start': 33, 'end': 45}, + {'entity_group': 'LOC', 'score': 0.99321, 'word': 'Brooklyn', 'start': 49, 'end': 57} +] +``` + +यहां मॉडल ने सही ढंग से पहचाना कि सिल्वेन एक व्यक्ति (पीईआर), हगिंग फेस एक संगठन (ओआरजी), और ब्रुकलिन एक स्थान (एलओसी) है। + +हम पाइपलाइन निर्माण फ़ंक्शन में विकल्प `grouped_entities=True` पास करते हैं ताकि पाइपलाइन को एक ही इकाई के अनुरूप वाक्य के हिस्सों को एक साथ फिर से समूहित करने के लिए कहा जा सके: यहां मॉडल ने एक ही संगठन के रूप में "हगिंग" और "फेस" को सही ढंग से समूहीकृत किया है, भले ही नाम में कई शब्द हों। वास्तव में, जैसा कि हम अगले अध्याय में देखेंगे, प्रीप्रोसेसिंग कुछ शब्दों को छोटे भागों में भी विभाजित करता है। उदाहरण के लिए, `सिल्वेन` को चार भागों में बांटा गया है: `S`, `##yl`, `##va`, और `##in`। प्रसंस्करण के बाद के चरण में, पाइपलाइन ने उन टुकड़ों को सफलतापूर्वक पुन: समूहित किया। + + + + ✏️ **कोशिश करके देखो!** अंग्रेजी में पार्ट-ऑफ-स्पीच टैगिंग (आमतौर पर पीओएस के रूप में संक्षिप्त) करने में सक्षम मॉडल के लिए मॉडल हब खोजें। यह मॉडल उपरोक्त उदाहरण में वाक्य के लिए क्या भविष्यवाणी करता है? + + + +## प्रश्न उत्तर + +'प्रश्न-उत्तर' पाइपलाइन किसी दिए गए संदर्भ से जानकारी का उपयोग करके प्रश्नों का उत्तर देती है: + +```python +from transformers import pipeline + +question_answerer = pipeline("question-answering") +question_answerer( + question="Where do I work?", + context="My name is Sylvain and I work at Hugging Face in Brooklyn", +) +``` + +```python out +{'score': 0.6385916471481323, 'start': 33, 'end': 45, 'answer': 'Hugging Face'} +``` + +ध्यान दें कि यह पाइपलाइन दिए गए संदर्भ से जानकारी निकालकर काम करती है; यह उत्तर उत्पन्न नहीं करता है। + +## संक्षिप्तीकरण + +पाठ में संदर्भित महत्वपूर्ण पहलुओं के सभी (या अधिकतर) को रखते हुए पाठ को छोटे पाठ में कम करने का कार्य सारांशीकरण है। यहाँ एक उदाहरण है: + +```python +from transformers import pipeline + +summarizer = pipeline("summarization") +summarizer( + """ + America has changed dramatically during recent years. Not only has the number of + graduates in traditional engineering disciplines such as mechanical, civil, + electrical, chemical, and aeronautical engineering declined, but in most of + the premier American universities engineering curricula now concentrate on + and encourage largely the study of engineering science. As a result, there + are declining offerings in engineering subjects dealing with infrastructure, + the environment, and related issues, and greater concentration on high + technology subjects, largely supporting increasingly complex scientific + developments. While the latter is important, it should not be at the expense + of more traditional engineering. + + Rapidly developing economies such as China and India, as well as other + industrial countries in Europe and Asia, continue to encourage and advance + the teaching of engineering. Both China and India, respectively, graduate + six and eight times as many traditional engineers as does the United States. + Other industrial countries at minimum maintain their output, while America + suffers an increasingly serious decline in the number of engineering graduates + and a lack of well-educated engineers. +""" +) +``` + +```python out +[{'summary_text': ' America has changed dramatically during recent years . The ' + 'number of engineering graduates in the U.S. has declined in ' + 'traditional engineering disciplines such as mechanical, civil ' + ', electrical, chemical, and aeronautical engineering . Rapidly ' + 'developing economies such as China and India, as well as other ' + 'industrial countries in Europe and Asia, continue to encourage ' + 'and advance engineering .'}] +``` + +टेक्स्ट जनरेशन की तरह, आप परिणाम के लिए `max_length` या `min_length` निर्दिष्ट कर सकते हैं। + +## अनुवाद + +अनुवाद के लिए, आप एक डिफ़ॉल्ट मॉडल का उपयोग कर सकते हैं यदि आप कार्य नाम में एक भाषा युग्म प्रदान करते हैं (जैसे `"translation_en_to_fr"`), लेकिन सबसे आसान तरीका है उस मॉडल को चुनना जिसे आप [मॉडल हब](https://huggingface.co/models) पर उपयोग करना चाहते हैं। यहाँ हम फ़्रेंच से अंग्रेज़ी में अनुवाद करने का प्रयास करेंगे: + +```python +from transformers import pipeline + +translator = pipeline("translation", model="Helsinki-NLP/opus-mt-fr-en") +translator("Ce cours est produit par Hugging Face.") +``` + +```python out +[{'translation_text': 'This course is produced by Hugging Face.'}] +``` + +पाठ निर्माण और संक्षेपण की तरह, आप परिणाम के लिए `max_length` या `min_length` निर्दिष्ट कर सकते हैं। + + + +✏️ **कोशिश करके देखो!** अन्य भाषाओं में अनुवाद मॉडल खोजें और पिछले वाक्य का कुछ भिन्न भाषाओं में अनुवाद करने का प्रयास करें। + + + +अब तक दिखाई गई पाइपलाइनें ज्यादातर प्रदर्शनकारी उद्देश्यों के लिए हैं। वे विशिष्ट कार्यों के लिए प्रोग्राम किए गए थे और उनमें से विविधताएं नहीं कर सकते। अगले अध्याय में, आप सीखेंगे कि 'पाइपलाइन ()' फ़ंक्शन के अंदर क्या है और इसके व्यवहार को कैसे अनुकूलित किया जाए। diff --git a/chapters/hi/chapter1/4.mdx b/chapters/hi/chapter1/4.mdx new file mode 100644 index 000000000..63dd3e619 --- /dev/null +++ b/chapters/hi/chapter1/4.mdx @@ -0,0 +1,166 @@ +# ट्रांसफॉर्मर कैसे काम करते हैं? + +इस खंड में, हम ट्रांसफॉर्मर मॉडल की वास्तुकला पर एक उच्च-स्तरीय नज़र डालेंगे। + +## ट्रांसफार्मर का थोड़ा सा इतिहास + +ट्रांसफॉर्मर मॉडल के (संक्षिप्त) इतिहास में कुछ संदर्भ बिंदु यहां दिए गए हैं: + +
+A brief chronology of Transformers models. + +
+ +[ट्रांसफॉर्मर आर्किटेक्चर](https://arxiv.org/abs/1706.03762) को जून 2017 में पेश किया गया था। मूल शोध का फोकस अनुवाद कार्यों पर था। इसके बाद कई प्रभावशाली मॉडल पेश किए गए, जिनमें शामिल हैं: + +- **जून 2018**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), पहला पूर्व प्रशिक्षित ट्रांसफॉर्मर मॉडल, जिसका उपयोग विभिन्न प्राकृतिक भाषा प्रसंस्करण कार्यों पर फाइन-ट्यूनिंग के लिए किया जाता है और राज्य का प्राप्त किया जाता है- कला परिणाम। +- **अक्टूबर 2018**: [BERT](https://arxiv.org/abs/1810.04805), एक और बड़ा पूर्व-प्रशिक्षित मॉडल, इसे वाक्यों के बेहतर सारांश तैयार करने के लिए डिज़ाइन किया गया है (इस पर अगले अध्याय में अधिक!) +- **फरवरी 2019**: [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), GPT का एक बेहतर (और बड़ा) संस्करण जिसे नैतिक चिंताओं के कारण तुरंत सार्वजनिक रूप से जारी नहीं किया गया था। +- **अक्टूबर 2019**: [DistilBERT](https://arxiv.org/abs/1910.01108), BERT का एक डिस्टिल्ड संस्करण जो 60% तेज, मेमोरी में 40% हल्का है, और अभी भी BERT के प्रदर्शन का 97% बरकरार रखता है। +- **अक्टूबर 2019**: [BART](https://arxiv.org/abs/1910.13461) और [T5](https://arxiv.org/abs/1910.10683), दो बड़े पूर्व-प्रशिक्षित मॉडल जो मूल ट्रांसफॉर्मर मॉडल के समान आर्किटेक्चर का उपयोग करते हैं ( ऐसा करने वाले पहले संस्करण)। +- **मई 2020**: [GPT-3](https://arxiv.org/abs/2005.14165), GPT-2 का और भी बड़ा संस्करण जो फाइन-ट्यूनिंग की आवश्यकता के बिना विभिन्न कार्यों पर अच्छा प्रदर्शन करने में सक्षम है (जिसे _जीरो शॉट लर्निंग_ कहा जाता है)। + +यह सूची व्यापक से बहुत दूर है और केवल कुछ विभिन्न प्रकार के ट्रांसफार्मर मॉडल को उजागर करने के लिए है। मोटे तौर पर उन्हें तीन श्रेणियों में बांटा जा सकता है: + +- GPT- जैसा (जिसे _auto-regressive_ Transformer मॉडल भी कहा जाता है) +- BERT- जैसा (जिसे _auto-encoding_ Transformer मॉडल भी कहा जाता है) +- BART/T5- जैस (जिसे _अनुक्रम-से-अनुक्रम_ट्रांसफॉर्मर मॉडल भी कहा जाता है) + +हम इन परिवारों के बारे में बाद में और गहराई से जानेंगे। + +## ट्रांसफॉर्मर भाषा मॉडल हैं + +ऊपर वर्णित सभी ट्रांसफार्मर मॉडल (जीपीटी, बीईआरटी, बार्ट, टी5, आदि) को *भाषा मॉडल* के रूप में प्रशिक्षित किया गया है। इसका मतलब है कि उन्हें स्व-निगरानी फैशन में बड़ी मात्रा में कच्चे पाठ पर प्रशिक्षित किया गया है। स्व-पर्यवेक्षित शिक्षण एक प्रकार का प्रशिक्षण है जिसमें मॉडल के इनपुट से उद्देश्य की स्वचालित रूप से गणना की जाती है। इसका मतलब है कि मनुष्यों को डेटा लेबल करने की आवश्यकता नहीं है! + +इस प्रकार का मॉडल उस भाषा की सांख्यिकीय समझ विकसित करता है जिस पर इसे प्रशिक्षित किया गया है, लेकिन यह विशिष्ट व्यावहारिक कार्यों के लिए बहुत उपयोगी नहीं है। इस वजह से, सामान्य पूर्व-प्रशिक्षित मॉडल तब *ट्रांसफर लर्निंग* नामक प्रक्रिया से गुजरता है। इस प्रक्रिया के दौरान, मॉडल को पर्यवेक्षित तरीके से ठीक-ठीक ट्यून किया जाता है - अर्थात, मानव-एनोटेटेड लेबल का उपयोग करके - किसी दिए गए कार्य पर। + +कार्य का एक उदाहरण *n* पिछले शब्दों को पढ़कर वाक्य में अगले शब्द की भविष्यवाणी करना है। इसे *कारण भाषा मॉडलिंग* कहा जाता है क्योंकि आउटपुट अतीत और वर्तमान इनपुट पर निर्भर करता है, लेकिन भविष्य के इनपुट पर नहीं। + +
+Example of causal language modeling in which the next word from a sentence is predicted. + +
+ +एक अन्य उदाहरण *मुखौटा भाषा मॉडलिंग* है, जिसमें मॉडल वाक्य में एक नकाबपोश शब्द की भविष्यवाणी करता है। + +
+Example of masked language modeling in which a masked word from a sentence is predicted. + +
+ +## ट्रांसफॉर्मर हैं बड़े मॉडल + +कुछ आउटलेयर (जैसे डिस्टिलबर्ट) के अलावा, बेहतर प्रदर्शन प्राप्त करने की सामान्य रणनीति मॉडल के आकार के साथ-साथ उन डेटा की मात्रा को बढ़ाकर है जिन पर वे पूर्व-प्रशिक्षित हैं। + +
+Number of parameters of recent Transformers models +
+ +दुर्भाग्य से, एक मॉडल को प्रशिक्षित करने के लिए, विशेष रूप से एक बड़े मॉडल के लिए बड़ी मात्रा में डेटा की आवश्यकता होती है। यह समय के लिहाज से बहुत महंगा हो जाता है और संसाधनों की गणना करता है। यह पर्यावरणीय प्रभाव का भी अनुवाद करता है, जैसा कि निम्नलिखित ग्राफ में देखा जा सकता है। + +
+The carbon footprint of a large language model. + +
+ + + +और यह पूर्व-प्रशिक्षण के पर्यावरणीय प्रभाव को कम करने की कोशिश कर रही एक टीम द्वारा तैयार किए गए (बहुत बड़े के लिए) एक परियोजना दिखा रहा है। सर्वोत्तम हाइपरपैरामीटर प्राप्त करने के लिए बहुत सारे परीक्षण चलाने का पदचिह्न और भी अधिक होगा। + +कल्पना कीजिए कि अगर हर बार एक शोध दल, एक छात्र संगठन, या कोई कंपनी किसी मॉडल को प्रशिक्षित करना चाहती है, तो उसने ऐसा शुरू से ही किया। इससे भारी, अनावश्यक वैश्विक लागत आएगी! + +यही कारण है कि भाषा मॉडल साझा करना सर्वोपरि है: पहले से प्रशिक्षित वजन के ऊपर प्रशिक्षित वजन और निर्माण को साझा करना समग्र गणना लागत और समुदाय के कार्बन पदचिह्न को कम करता है। + + +## स्थानांतरण सीखना + + + +*पूर्व-प्रशिक्षण* एक मॉडल को खरोंच से प्रशिक्षित करने का कार्य है: वज़न को बेतरतीब ढंग से आरंभ किया जाता है, और प्रशिक्षण बिना किसी पूर्व ज्ञान के शुरू होता है। + +
+The pretraining of a language model is costly in both time and money. + +
+ +यह पूर्व-प्रशिक्षण आमतौर पर बहुत बड़ी मात्रा में डेटा पर किया जाता है। इसलिए, इसके लिए बहुत अधिक डेटा की आवश्यकता होती है, और प्रशिक्षण में कई सप्ताह तक लग सकते हैं। + +दूसरी ओर, *फाइन-ट्यूनिंग*, किसी मॉडल के पूर्व-प्रशिक्षित **होने के बाद** किया जाने वाला प्रशिक्षण है। फ़ाइन-ट्यूनिंग करने के लिए, आप पहले एक पूर्व-प्रशिक्षित भाषा मॉडल प्राप्त करते हैं, फिर अपने कार्य के लिए विशिष्ट डेटासेट के साथ अतिरिक्त प्रशिक्षण करते हैं। रुको - क्यों न केवल अंतिम कार्य के लिए सीधे प्रशिक्षण दिया जाए? वहाँ के लिए बहुत कारण है: + +* पूर्व-प्रशिक्षित मॉडल को पहले से ही एक डेटासेट पर प्रशिक्षित किया गया था जिसमें फ़ाइन-ट्यूनिंग डेटासेट के साथ कुछ समानताएँ हैं। इस प्रकार फाइन-ट्यूनिंग प्रक्रिया प्रारंभिक मॉडल द्वारा पूर्व-प्रशिक्षण के दौरान प्राप्त ज्ञान का लाभ उठाने में सक्षम है (उदाहरण के लिए, प्राकृतिक भाषा प्रसंस्करण समस्याओं के साथ, पूर्व-प्रशिक्षित मॉडल को उस भाषा की किसी प्रकार की सांख्यिकीय समझ होगी जिसका आप उपयोग कर रहे हैं। आपका कार्य)। +* चूंकि पूर्व-प्रशिक्षित मॉडल पहले से ही बहुत सारे डेटा पर प्रशिक्षित था, इसलिए फाइन-ट्यूनिंग को अच्छे परिणाम प्राप्त करने के लिए बहुत कम डेटा की आवश्यकता होती है। +* उसी कारण से, अच्छे परिणाम प्राप्त करने के लिए आवश्यक समय और संसाधनों की मात्रा बहुत कम है। + +उदाहरण के लिए, कोई व्यक्ति अंग्रेजी भाषा में प्रशिक्षित एक पूर्व-प्रशिक्षित मॉडल का लाभ उठा सकता है और फिर उसे एक आर्क्सिव कॉर्पस पर ठीक कर सकता है, जिसके परिणामस्वरूप विज्ञान/अनुसंधान-आधारित मॉडल बन सकता है। फाइन-ट्यूनिंग के लिए केवल सीमित मात्रा में डेटा की आवश्यकता होगी: पूर्व-प्रशिक्षित मॉडल ने जो ज्ञान हासिल किया है वह "स्थानांतरित" है, इसलिए शब्द *ट्रांसफर लर्निंग*। + +
+The fine-tuning of a language model is cheaper than pretraining in both time and money. + +
+ +एक मॉडल को फाइन-ट्यूनिंग, इसलिए कम समय, डेटा, वित्तीय और पर्यावरणीय लागत है। विभिन्न फाइन-ट्यूनिंग योजनाओं पर पुनरावृति करना भी तेज और आसान है, क्योंकि प्रशिक्षण पूर्ण पूर्व-प्रशिक्षण की तुलना में कम विवश है। + +यह प्रक्रिया खरोंच से प्रशिक्षण की तुलना में बेहतर परिणाम प्राप्त करेगी (जब तक कि आपके पास बहुत अधिक डेटा न हो), यही कारण है कि आपको हमेशा पूर्व-प्रशिक्षित मॉडल का लाभ उठाने का प्रयास करना चाहिए - जो आपके हाथ में काम के जितना करीब हो सके - और इसे फाइन-ट्यून करें। + +## सामान्य वास्तुकला + +इस खंड में, हम ट्रान्सफ़ॉर्मर मॉडल की सामान्य संरचना के बारे में जानेंगे। यदि आप कुछ अवधारणाओं को नहीं समझते हैं, तो चिंता न करें; बाद में प्रत्येक घटक को कवर करने वाले विस्तृत खंड हैं। + + + +## परिचय + +मॉडल मुख्य रूप से दो ब्लॉकों से बना है: + +* **एनकोडर (बाएं)**: एन्कोडर इनपुट प्राप्त करता है और इसका प्रतिनिधित्व करता है (इसकी विशेषताएं)। इसका मतलब है कि इनपुट से समझ हासिल करने के लिए मॉडल को अनुकूलित किया गया है। +* **डिकोडर (दाएं)**: लक्ष्य अनुक्रम उत्पन्न करने के लिए डिकोडर अन्य इनपुट के साथ एन्कोडर के प्रतिनिधित्व (सुविधाओं) का उपयोग करता है। इसका मतलब है कि मॉडल आउटपुट उत्पन्न करने के लिए अनुकूलित है। + +
+Architecture of a Transformers models + +
+ +इनमें से प्रत्येक भाग का उपयोग कार्य के आधार पर स्वतंत्र रूप से किया जा सकता है: + +* **केवल-एनकोडर मॉडल**: उन कार्यों के लिए अच्छा है जिनके लिए इनपुट की समझ की आवश्यकता होती है, जैसे वाक्य वर्गीकरण और नामित इकाई पहचान। +* **केवल डिकोडर मॉडल**: पाठ निर्माण जैसे जनरेटिव कार्यों के लिए अच्छा है। +* **एनकोडर-डिकोडर मॉडल** or **अनुक्रम-से-अनुक्रम मॉडल**: Good for generative tasks that require an input, such as translation or summarization. + +हम बाद के खंडों में स्वतंत्र रूप से उन वास्तुकलाओं में गोता लगाएँगे। + +## ध्यान परतें + +ट्रांसफार्मर मॉडल की एक प्रमुख विशेषता यह है कि वे विशेष परतों के साथ निर्मित होते हैं जिन्हें *ध्यान परत* कहा जाता है। वास्तव में, ट्रांसफॉर्मर आर्किटेक्चर को पेश करने वाले पेपर का शीर्षक था ["अटेंशन इज़ ऑल यू नीड"](https://arxiv.org/abs/1706.03762)! हम पाठ्यक्रम में बाद में ध्यान परतों के विवरण का पता लगाएंगे; अभी के लिए, आपको केवल यह जानने की जरूरत है कि यह परत मॉडल को आपके द्वारा पारित वाक्य में कुछ शब्दों पर विशेष ध्यान देने के लिए कहेगी (और कमोबेश दूसरों की उपेक्षा करें) प्रत्येक शब्द के प्रतिनिधित्व के साथ व्यवहार करते समय। + +इसे संदर्भ में रखने के लिए, अंग्रेजी से फ्रेंच में पाठ का अनुवाद करने के कार्य पर विचार करें। इनपुट "आप इस पाठ्यक्रम को पसंद करते हैं" को देखते हुए, एक अनुवाद मॉडल को "पसंद" शब्द के लिए उचित अनुवाद प्राप्त करने के लिए आसन्न शब्द "यू" में भी भाग लेने की आवश्यकता होगी, क्योंकि फ्रेंच में क्रिया "पसंद" अलग-अलग संयुग्मित होती है पर निर्भर करता है विषय। हालाँकि, शेष वाक्य उस शब्द के अनुवाद के लिए उपयोगी नहीं है। उसी तरह, "इस" का अनुवाद करते समय मॉडल को "कोर्स" शब्द पर भी ध्यान देने की आवश्यकता होगी, क्योंकि "यह" अलग-अलग अनुवाद करता है जो इस बात पर निर्भर करता है कि संबंधित संज्ञा पुल्लिंग है या स्त्रीलिंग। फिर, वाक्य के दूसरे शब्द "इस" के अनुवाद के लिए कोई मायने नहीं रखेंगे। अधिक जटिल वाक्यों (और अधिक जटिल व्याकरण नियमों) के साथ, मॉडल को उन शब्दों पर विशेष ध्यान देने की आवश्यकता होगी जो प्रत्येक शब्द का ठीक से अनुवाद करने के लिए वाक्य में दूर दिखाई दे सकते हैं। + +प्राकृतिक भाषा से जुड़े किसी भी कार्य पर भी यही अवधारणा लागू होती है: एक शब्द का अपने आप में एक अर्थ होता है, लेकिन वह अर्थ संदर्भ से गहराई से प्रभावित होता है, जो शब्द के अध्ययन से पहले या बाद में कोई अन्य शब्द (या शब्द) हो सकता है। + +अब जब आपको पता चल गया है कि ध्यान की परतें क्या हैं, तो आइए ट्रांसफॉर्मर आर्किटेक्चर पर करीब से नज़र डालें। + +## मूल वास्तुकला + +ट्रांसफॉर्मर आर्किटेक्चर मूल रूप से अनुवाद के लिए डिज़ाइन किया गया था। प्रशिक्षण के दौरान, एनकोडर एक निश्चित भाषा में इनपुट (वाक्य) प्राप्त करता है, जबकि डिकोडर वांछित लक्ष्य भाषा में समान वाक्य प्राप्त करता है। एनकोडर में, ध्यान की परतें एक वाक्य में सभी शब्दों का उपयोग कर सकती हैं (चूंकि, जैसा कि हमने अभी देखा, किसी दिए गए शब्द का अनुवाद इस बात पर निर्भर हो सकता है कि वाक्य में क्या है और इसके पहले क्या है)। हालाँकि, डिकोडर क्रमिक रूप से काम करता है और केवल उस वाक्य में शब्दों पर ध्यान दे सकता है जिसका उसने पहले ही अनुवाद किया है (इसलिए, वर्तमान में उत्पन्न होने वाले शब्द से पहले के शब्द)। उदाहरण के लिए, जब हमने अनुवादित लक्ष्य के पहले तीन शब्दों की भविष्यवाणी की है, तो हम उन्हें डिकोडर को देते हैं जो चौथे शब्द की भविष्यवाणी करने के लिए एन्कोडर के सभी इनपुट का उपयोग करता है। + +प्रशिक्षण के दौरान चीजों को गति देने के लिए (जब मॉडल के पास लक्ष्य वाक्यों तक पहुंच होती है), डिकोडर को पूरे लक्ष्य को खिलाया जाता है, लेकिन भविष्य के शब्दों का उपयोग करने की अनुमति नहीं है (यदि भविष्यवाणी करने की कोशिश करते समय स्थिति 2 पर शब्द तक पहुंच थी) स्थिति 2 पर शब्द, समस्या बहुत कठिन नहीं होगी!)। उदाहरण के लिए, जब चौथे शब्द की भविष्यवाणी करने की कोशिश की जाती है, तो ध्यान परत के पास केवल 1 से 3 की स्थिति वाले शब्दों तक ही पहुंच होगी। + +मूल ट्रांसफॉर्मर आर्किटेक्चर इस तरह दिखता था, बाईं ओर एन्कोडर और दाईं ओर डिकोडर: + +
+Architecture of a Transformers models + +
+ +ध्यान दें कि डिकोडर ब्लॉक में पहली ध्यान परत डिकोडर के सभी (अतीत) इनपुट पर ध्यान देती है, लेकिन दूसरी ध्यान परत एन्कोडर के आउटपुट का उपयोग करती है। इस प्रकार यह वर्तमान शब्द का सर्वोत्तम अनुमान लगाने के लिए संपूर्ण इनपुट वाक्य तक पहुंच सकता है। यह बहुत उपयोगी है क्योंकि विभिन्न भाषाओं में व्याकरण संबंधी नियम हो सकते हैं जो शब्दों को अलग-अलग क्रम में रखते हैं, या वाक्य में बाद में दिए गए कुछ संदर्भ किसी दिए गए शब्द का सर्वोत्तम अनुवाद निर्धारित करने में सहायक हो सकते हैं। + +मॉडल को कुछ विशेष शब्दों पर ध्यान देने से रोकने के लिए *ध्यान मास्क* का उपयोग एन्कोडर/डिकोडर में भी किया जा सकता है - उदाहरण के लिए, विशेष पैडिंग शब्द जिसका उपयोग वाक्यों को एक साथ बैच करते समय सभी इनपुट को समान लंबाई बनाने के लिए किया जाता है। + +## आर्किटेक्चर बनाम चेकपॉइंट + +जैसे ही हम इस पाठ्यक्रम में ट्रांसफॉर्मर मॉडल में गोता लगाते हैं, आप *आर्किटेक्चर* और *चेकपॉइंट्स* के साथ-साथ *मॉडल* का उल्लेख देखेंगे। इन सभी शब्दों के थोड़े अलग अर्थ हैं: + +* **आर्किटेक्चर**: यह मॉडल का कंकाल है - प्रत्येक परत की परिभाषा और मॉडल के भीतर होने वाले प्रत्येक ऑपरेशन। +* **जांच की चौकी**: ये वे वज़न हैं जिन्हें किसी दिए गए आर्किटेक्चर में लोड किया जाएगा। +* **मॉडल**: यह एक छत्र शब्द है जो "आर्किटेक्चर" या "चेकपॉइंट" जितना सटीक नहीं है: इसका मतलब दोनों हो सकता है। अस्पष्टता को कम करने के लिए यह पाठ्यक्रम *वास्तुकला* या *चेकपॉइंट* निर्दिष्ट करेगा। + +उदाहरण के लिए, BERT एक आर्किटेक्चर है, जबकि `बर्ट-बेस-केस्ड`, BERT की पहली रिलीज़ के लिए Google टीम द्वारा प्रशिक्षित वज़न का एक सेट एक चेकपॉइंट है। हालांकि, कोई "बीईआरटी मॉडल" और "`बर्ट-बेस-केसेड` मॉडल" कह सकता है। diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index c9bb3fafb..7ba1a7cfa 100644 --- a/chapters/pt/_toctree.yml +++ b/chapters/pt/_toctree.yml @@ -54,3 +54,10 @@ title: E se o meu dataset não estiver no Hub? - local: chapter5/3 title: Hora de fatiar e dividir os dados + - local: chapter5/4 + title: Big data? 🤗 Datasets ao resgate + +- title: 7. Principais tarefas NLP + sections: + - local: chapter7/1 + title: Introdução diff --git a/chapters/pt/chapter5/4.mdx b/chapters/pt/chapter5/4.mdx new file mode 100644 index 000000000..fc1f3f92e --- /dev/null +++ b/chapters/pt/chapter5/4.mdx @@ -0,0 +1,287 @@ +# Big data? 🤗 Datasets ao resgate + + + + +Hoje em dia, não é incomum encontrar-se trabalhando com conjuntos de dados de vários gigabytes, especialmente se você planeja pré-treinar um transformer como BERT ou GPT-2 do zero. Nesses casos, até mesmo _carregar_ os dados pode ser um desafio. Por exemplo, o corpus WebText usado para pré-treinar o GPT-2 consiste em mais de 8 milhões de documentos e 40 GB de texto - carregar isso na RAM do seu laptop provavelmente lhe causará um ataque cardíaco! + +Felizmente, 🤗 Datasets foram projetados para superar essas limitações. Ele libera você de problemas de gerenciamento de memória tratando conjuntos de dados como arquivos _memory-mapped_ e de limites de disco rígido por _streaming_ das entradas em um corpus. + + + +Nesta seção, exploraremos esses recursos de 🤗 Conjuntos de dados com um enorme corpus de 825 GB conhecido como [the Pile](https://pile.eleuther.ai). Vamos começar! + +## O que é the Pile? + +O `The Pile` é um corpus de texto em inglês que foi criado por [EleutherAI](https://www.eleuther.ai) para treinar modelos de linguagem em larga escala. Ele inclui uma gama diversificada de conjuntos de dados, abrangendo artigos científicos, repositórios de código do GitHub e texto da web filtrado. O corpus de treinamento está disponível em [blocos de 14 GB](https://mystic.the-eye.eu/public/AI/pile/), e você também pode baixar vários dos [componentes individuais](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/). Vamos começar dando uma olhada no conjunto de dados PubMed Abstracts, que é um corpus de resumos de 15 milhões de publicações biomédicas no [PubMed](https://pubmed.ncbi.nlm.nih.gov/). O conjunto de dados está em [formato JSON Lines](https://jsonlines.org) e é compactado usando a biblioteca `zstandard`, então primeiro precisamos instalá-lo: + +```py +!pip install zstandard +``` + +Em seguida, podemos carregar o conjunto de dados usando o método para arquivos remotos que aprendemos na [seção 2](/course/chapter5/2): + +```py +from datasets import load_dataset + +# This takes a few minutes to run, so go grab a tea or coffee while you wait :) +data_files = "https://mystic.the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst" +pubmed_dataset = load_dataset("json", data_files=data_files, split="train") +pubmed_dataset +``` + +```python out +Dataset({ + features: ['meta', 'text'], + num_rows: 15518009 +}) +``` + +Podemos ver que há 15.518.009 linhas e 2 colunas em nosso conjunto de dados - isso é muito! + + + +✎ 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. + + + +Vamos inspecionar o conteúdo do primeiro exemplo: + +```py +pubmed_dataset[0] +``` + +```python out +{'meta': {'pmid': 11409574, 'language': 'eng'}, + '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 ...'} +``` + +Ok, isso parece o resumo de um artigo médico. Agora vamos ver quanta RAM usamos para carregar o conjunto de dados! + +## A magia do mapeamento de memória + +Uma maneira simples de medir o uso de memória em Python é com a biblioteca [`psutil`](https://psutil.readthedocs.io/en/latest/), que pode ser instalada com `pip` da seguinte forma: + +```python +!pip install psutil +``` + +Ele fornece uma classe `Process` que nos permite verificar o uso de memória do processo atual da seguinte forma: + +```py +import psutil + +# Process.memory_info is expressed in bytes, so convert to megabytes +print(f"RAM used: {psutil.Process().memory_info().rss / (1024 * 1024):.2f} MB") +``` + +```python out +RAM used: 5678.33 MB +``` + +Aqui o atributo `rss` refere-se ao _tamanho do conjunto residente_, que é a fração de memória que um processo ocupa na RAM. Essa medida também inclui a memória usada pelo interpretador Python e as bibliotecas que carregamos, portanto, a quantidade real de memória usada para carregar o conjunto de dados é um pouco menor. Para comparação, vamos ver o tamanho do conjunto de dados no disco, usando o atributo `dataset_size`. Como o resultado é expresso em bytes como antes, precisamos convertê-lo manualmente para gigabytes: + +```py +print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") +size_gb = pubmed_dataset.dataset_size / (1024**3) +print(f"Dataset size (cache file) : {size_gb:.2f} GB") +``` + +```python out +Number of files in dataset : 20979437051 +Dataset size (cache file) : 19.54 GB +``` + +Legal -- apesar de ter quase 20 GB de tamanho, podemos carregar e acessar o conjunto de dados com muito menos RAM! + + + +✏️ **Experimente!** Escolha um dos [subconjuntos](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/) da `The Pile` que é maior que a RAM do seu laptop ou desktop, carregue com 🤗 Datasets e meça a quantidade de RAM usada. Observe que, para obter uma medição precisa, você desejará fazer isso em um novo processo. Você pode encontrar os tamanhos descompactados de cada subconjunto na Tabela 1 do [artigo do `The Pile`](https://arxiv.org/abs/2101.00027). + + + +Se você estiver familiarizado com Pandas, esse resultado pode ser uma surpresa por causa da famosa [regra de ouro] de Wes Kinney (https://wesmckinney.com/blog/apache-arrow-pandas-internals/) de que você normalmente precisa de 5 para 10 vezes mais RAM do que o tamanho do seu conjunto de dados. Então, como 🤗 Datasets resolve esse problema de gerenciamento de memória? 🤗 Os conjuntos de dados tratam cada conjunto de dados como um [arquivo mapeado em memória](https://en.wikipedia.org/wiki/Memory-mapped_file), que fornece um mapeamento entre RAM e armazenamento do sistema de arquivos que permite que a biblioteca acesse e opere em elementos do conjunto de dados sem precisar carregá-lo totalmente na memória. + +Arquivos mapeados em memória também podem ser compartilhados em vários processos, o que permite que métodos como `Dataset.map()` sejam paralelizados sem a necessidade de mover ou copiar o conjunto de dados. Sob o capô, esses recursos são todos realizados pelo formato de memória [Apache Arrow](https://arrow.apache.org) e [`pyarrow`](https://arrow.apache.org/docs/python/index.html), que tornam o carregamento e o processamento de dados extremamente rápidos. (Para mais detalhes sobre o Apache Arrow e comparações com o Pandas, confira [post do blog de Dejan Simic](https://towardsdatascience.com/apache-arrow-read-dataframe-with-zero-memory-69634092b1a).) Para ver isso em ação, vamos executar um pequeno teste de velocidade iterando sobre todos os elementos no conjunto de dados PubMed Abstracts: + +```py +import timeit + +code_snippet = """batch_size = 1000 + +for idx in range(0, len(pubmed_dataset), batch_size): + _ = pubmed_dataset[idx:idx + batch_size] +""" + +time = timeit.timeit(stmt=code_snippet, number=1, globals=globals()) +print( + f"Iterated over {len(pubmed_dataset)} examples (about {size_gb:.1f} GB) in " + f"{time:.1f}s, i.e. {size_gb/time:.3f} GB/s" +) +``` + +```python out +'Iterated over 15518009 examples (about 19.5 GB) in 64.2s, i.e. 0.304 GB/s' +``` + +Aqui usamos o módulo `timeit` do Python para medir o tempo de execução do `code_snippet`. Normalmente, você poderá iterar em um conjunto de dados a uma velocidade de alguns décimos de GB/s a vários GB/s. Isso funciona muito bem para a grande maioria dos aplicativos, mas às vezes você terá que trabalhar com um conjunto de dados grande demais para ser armazenado no disco rígido do seu laptop. Por exemplo, se tentássemos baixar o Pile por completo, precisaríamos de 825 GB de espaço livre em disco! Para lidar com esses casos, 🤗 Datasets fornece um recurso de streaming que nos permite baixar e acessar elementos em tempo real, sem a necessidade de baixar todo o conjunto de dados. Vamos dar uma olhada em como isso funciona. + + + +💡 Nos notebooks Jupyter, você também pode cronometrar células usando a [`%%timeit` função mágica](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit). + + + +## Conjuntos de dados em streaming + +Para habilitar o streaming do conjunto de dados você só precisa passar o argumento `streaming=True` para a função `load_dataset()`. Por exemplo, vamos carregar o conjunto de dados PubMed Abstracts novamente, mas em modo streaming: + +```py +pubmed_dataset_streamed = load_dataset( + "json", data_files=data_files, split="train", streaming=True +) +``` + +Em vez do familiar `Dataset` que encontramos em outro lugar neste capítulo, o objeto retornado com `streaming=True` é um `IterableDataset`. Como o nome sugere, para acessar os elementos de um `IterableDataset` precisamos iterar sobre ele. Podemos acessar o primeiro elemento do nosso conjunto de dados transmitido da seguinte forma: + + +```py +next(iter(pubmed_dataset_streamed)) +``` + +```python out +{'meta': {'pmid': 11409574, 'language': 'eng'}, + '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 ...'} +``` + +Os elementos de um conjunto de dados transmitido podem ser processados dinamicamente usando `IterableDataset.map()`, o que é útil durante o treinamento se você precisar tokenizar as entradas. O processo é exatamente o mesmo que usamos para tokenizar nosso conjunto de dados no [Capítulo 3](/course/chapter3), com a única diferença de que as saídas são retornadas uma a uma: + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased") +tokenized_dataset = pubmed_dataset_streamed.map(lambda x: tokenizer(x["text"])) +next(iter(tokenized_dataset)) +``` + +```python out +{'input_ids': [101, 4958, 5178, 4328, 6779, ...], 'attention_mask': [1, 1, 1, 1, 1, ...]} +``` + + + +💡 Para acelerar a tokenização com streaming você pode passar `batched=True`, como vimos na última seção. Ele processará os exemplos lote por lote; o tamanho do lote padrão é 1.000 e pode ser especificado com o argumento `batch_size`. + + + +Você também pode embaralhar um conjunto de dados transmitido usando `IterableDataset.shuffle()`, mas, diferentemente de `Dataset.shuffle()`, isso apenas embaralha os elementos em um `buffer_size` predefinido: + +```py +shuffled_dataset = pubmed_dataset_streamed.shuffle(buffer_size=10_000, seed=42) +next(iter(shuffled_dataset)) +``` + +```python out +{'meta': {'pmid': 11410799, 'language': 'eng'}, + 'text': 'Randomized study of dose or schedule modification of granulocyte colony-stimulating factor in platinum-based chemotherapy for elderly patients with lung cancer ...'} +``` + +Neste exemplo, selecionamos um exemplo aleatório dos primeiros 10.000 exemplos no buffer. Uma vez que um exemplo é acessado, seu lugar no buffer é preenchido com o próximo exemplo no corpus (ou seja, o 10.001º exemplo no caso acima). Você também pode selecionar elementos de um conjunto de dados transmitido usando as funções `IterableDataset.take()` e `IterableDataset.skip()`, que agem de maneira semelhante a `Dataset.select()`. Por exemplo, para selecionar os primeiros 5 exemplos no conjunto de dados PubMed Abstracts, podemos fazer o seguinte: + +```py +dataset_head = pubmed_dataset_streamed.take(5) +list(dataset_head) +``` + +```python out +[{'meta': {'pmid': 11409574, 'language': 'eng'}, + 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'}, + {'meta': {'pmid': 11409575, 'language': 'eng'}, + 'text': 'Clinical signs of hypoxaemia in children with acute lower respiratory infection: indicators of oxygen therapy ...'}, + {'meta': {'pmid': 11409576, 'language': 'eng'}, + 'text': "Hypoxaemia in children with severe pneumonia in Papua New Guinea ..."}, + {'meta': {'pmid': 11409577, 'language': 'eng'}, + 'text': 'Oxygen concentrators and cylinders ...'}, + {'meta': {'pmid': 11409578, 'language': 'eng'}, + 'text': 'Oxygen supply in rural africa: a personal experience ...'}] +``` + +Da mesma forma, você pode usar a função `IterableDataset.skip()` para criar divisões de treinamento e validação de um conjunto de dados embaralhado da seguinte forma: + +```py +# Skip the first 1,000 examples and include the rest in the training set +train_dataset = shuffled_dataset.skip(1000) +# Take the first 1,000 examples for the validation set +validation_dataset = shuffled_dataset.take(1000) +``` + +Vamos completar nossa exploração de streaming de conjuntos de dados com um aplicativo comum: combinar vários conjuntos de dados para criar um único corpus. 🤗 Datasets fornece uma função `interleave_datasets()` que converte uma lista de objetos `IterableDataset` em um único `IterableDataset`, onde os elementos do novo conjunto de dados são obtidos alternando entre os exemplos de origem. Essa função é especialmente útil quando você está tentando combinar grandes conjuntos de dados, então, como exemplo, vamos transmitir o subconjunto FreeLaw do Pile, que é um conjunto de dados de 51 GB de pareceres jurídicos dos tribunais dos EUA: + +```py +law_dataset_streamed = load_dataset( + "json", + data_files="https://mystic.the-eye.eu/public/AI/pile_preliminary_components/FreeLaw_Opinions.jsonl.zst", + split="train", + streaming=True, +) +next(iter(law_dataset_streamed)) +``` + +```python out +{'meta': {'case_ID': '110921.json', + 'case_jurisdiction': 'scotus.tar.gz', + 'date_created': '2010-04-28T17:12:49Z'}, + 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'} +``` + +Esse conjunto de dados é grande o suficiente para sobrecarregar a RAM da maioria dos laptops, mas conseguimos carregá-lo e acessá-lo sem suar a camisa! Vamos agora combinar os exemplos dos conjuntos de dados FreeLaw e PubMed Abstracts com a função `interleave_datasets()`: + +```py +from itertools import islice +from datasets import interleave_datasets + +combined_dataset = interleave_datasets([pubmed_dataset_streamed, law_dataset_streamed]) +list(islice(combined_dataset, 2)) +``` + +```python out +[{'meta': {'pmid': 11409574, 'language': 'eng'}, + 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'}, + {'meta': {'case_ID': '110921.json', + 'case_jurisdiction': 'scotus.tar.gz', + 'date_created': '2010-04-28T17:12:49Z'}, + 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'}] +``` + +Aqui usamos a função `islice()` do módulo `itertools` do Python para selecionar os dois primeiros exemplos do conjunto de dados combinado e podemos ver que eles correspondem aos primeiros exemplos de cada um dos dois conjuntos de dados de origem. + +Por fim, se você quiser transmitir o Pile em sua totalidade de 825 GB, poderá pegar todos os arquivos preparados da seguinte maneira: + +```py +base_url = "https://mystic.the-eye.eu/public/AI/pile/" +data_files = { + "train": [base_url + "train/" + f"{idx:02d}.jsonl.zst" for idx in range(30)], + "validation": base_url + "val.jsonl.zst", + "test": base_url + "test.jsonl.zst", +} +pile_dataset = load_dataset("json", data_files=data_files, streaming=True) +next(iter(pile_dataset["train"])) +``` + +```python out +{'meta': {'pile_set_name': 'Pile-CC'}, + 'text': 'It is done, and submitted. You can play “Survival of the Tastiest” on Android, and on the web...'} +``` + + + +✏️ **Experimente!** Use um dos grandes corpora Common Crawl como [`mc4`](https://huggingface.co/datasets/mc4) ou [`oscar`](https://huggingface.co/datasets/oscar) para criar um conjunto de dados multilíngue de streaming que represente as proporções faladas de idiomas em um país de sua escolha. Por exemplo, as quatro línguas nacionais na Suíça são alemão, francês, italiano e romanche, então você pode tentar criar um corpus suíço amostrando os subconjuntos do Oscar de acordo com sua proporção falada. + + + +Agora você tem todas as ferramentas necessárias para carregar e processar conjuntos de dados de todas as formas e tamanhos, mas, a menos que tenha muita sorte, chegará um ponto em sua jornada de PNL em que você terá que criar um conjunto de dados para resolver o problema. problema em mãos. Esse é o tema da próxima seção! diff --git a/chapters/pt/chapter7/1.mdx b/chapters/pt/chapter7/1.mdx new file mode 100644 index 000000000..b95dc7580 --- /dev/null +++ b/chapters/pt/chapter7/1.mdx @@ -0,0 +1,33 @@ + + +# Introdução + +No [Capítulo 3](/course/chapter3), você viu como fazer o ajuste fino (fine-tune) de um modelo de classificação de texto. Neste capítulo, abordaremos as seguintes tarefas de NLP (também conhecido como PLN): + +- Classificação dos Tokens +- Modelagem de linguagem mascarada (como BERT) +- Sumarização +- Tradução +- Modelagem de linguagem causal pré-treinamento (como GPT-2) +- Responder perguntas + +{#if fw === 'pt'} + +Para fazer isso, terá de aproveitar tudo o que aprendeu sobre a API `Trainer` e a biblioteca 🤗 Accelerate no [Capítulo 3](/course/chapter3), a biblioteca 🤗 Datasets no [Capítulo 5](/course/chapter5), e a biblioteca 🤗 Tokenizers no [Capítulo 6](/course/chapter6). Também vamos fazer o upload dos nossos resultados para o Model Hub, assim como fizemos no [Capítulo 4](/course/chapter4), então realmente esse é o capítulo onde tudo se junta! + +Cada seção pode ser lida de forma independente e irá mostrar como treinar um modelo com a API `Trainer` ou com o seu próprio laço de treinamento, utilizando 🤗 Accelerate. Sinta-se à vontade para pular qualquer parte e se concentrar na que mais lhe interessa: a API `Trainer` é excelente para o ajuste fino ou para treinar o seu modelo sem se preocupar com o que se passa nos bastidores, enquanto que o laço de treinamento com `Accelerate` permite personalizar qualquer parte que queira com mais facilidade. + +{:else} + +Para fazer isso, terá de aproveitar tudo o que aprendeu sobre o treinamento de modelo com a API Keras no [Capítulo 3](/course/chapter3), a biblioteca 🤗 Datasets no [Capítulo 5](/course/chapter5), e a biblioteca 🤗 Tokenizers no [Capítulo 6](/course/chapter6). Também vamos fazer o upload dos nossos resultados para o Model Hub, assim como fizemos no [Capítulo 4](/course/chapter4), então realmente esse é o capítulo onde tudo se junta! + +Cada seção pode ser lida de forma independente. + +{/if} + + + + +Se ler as seções em sequência, notará que elas têm bastante código e texto em comum. Essa repetição é intencional para que possa mergulhar (ou voltar mais tarde) em qualquer tarefa que lhe interesse e encontrar um exemplo completo. + + diff --git a/chapters/zh-CN/_toctree.yml b/chapters/zh-CN/_toctree.yml index ea5134bf3..39883a89e 100644 --- a/chapters/zh-CN/_toctree.yml +++ b/chapters/zh-CN/_toctree.yml @@ -30,7 +30,7 @@ - title: 2. 使用 🤗 Transformers sections: - local: chapter2/1 - title: 介绍 + title: 章节简介 - local: chapter2/2 title: 管道的内部 - local: chapter2/3 @@ -44,5 +44,22 @@ - local: chapter2/7 title: 基本用法完成! - local: chapter2/8 - title: 章末小测试 - quiz: 2 \ No newline at end of file + title: 章末小测验 + quiz: 2 + +- title: 3. 微调一个预训练模型 + sections: + - local: chapter3/1 + title: 章节简介 + - local: chapter3/2 + title: 预处理数据 + - local: chapter3/3 + title: 使用 Trainer API 或者 Keras 微调一个模型 + local_fw: { pt: chapter3/3, tf: chapter3/3_tf } + - local: chapter3/4 + title: 一个完成的训练过程 + - local: chapter3/5 + title: 微调,章节回顾! + - local: chapter3/6 + title: 章末小测验 + quiz: 3 diff --git a/chapters/zh-CN/chapter3/1.mdx b/chapters/zh-CN/chapter3/1.mdx new file mode 100644 index 000000000..544b04149 --- /dev/null +++ b/chapters/zh-CN/chapter3/1.mdx @@ -0,0 +1,21 @@ + + +# 介绍 + +在 [第二章](/course/chapter2) 我们探索了如何使用标记器(Tokenizer)和预训练模型进行预测。但是,如果您想为自己的数据集微调预训练模型,该怎么做呢?这就是本章的主题!你将学到: + +{#if fw === 'pt'} +* 如何从模型中心(hub)准备大型数据集 +* 如何使用高级`训练`API微调一个模型 +* 如何使用自定义训练过程 +* 如何利用🤗 Accelerate库在任何分布式设备上轻松运行自定义训练过程 + +{:else} +* 如何从模型中心(hub)准备大型数据集 +* 如何使用 Keras 微调模型 +* 如何使用 Keras 进行预测 +* 如何使用自定义指标 + +{/if} + +为了将经过训练的参数上传到Hugging Face Hub,您需要一个huggingface.co帐户: [创建一个账户](https://huggingface.co/join) \ No newline at end of file diff --git a/chapters/zh-CN/chapter3/2.mdx b/chapters/zh-CN/chapter3/2.mdx new file mode 100644 index 000000000..4a92170fc --- /dev/null +++ b/chapters/zh-CN/chapter3/2.mdx @@ -0,0 +1,383 @@ + + +# 处理数据 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} +这一小节学习[第一小节](/course/chapter2)中提到的“如何使用模型中心(hub)大型数据集”,下面是我们用模型中心的数据在PyTorch上训练句子分类器的一个例子: + +```python +import torch +from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification + +# Same as before +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "This course is amazing!", +] +batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") + +# This is new +batch["labels"] = torch.tensor([1, 1]) + +optimizer = AdamW(model.parameters()) +loss = model(**batch).loss +loss.backward() +optimizer.step() +``` +{:else} +这一小节学习[第一小节](/course/chapter2)中提到的“如何使用模型中心(hub)大型数据集”,下面是我们用模型中心的数据在TensorFlow上训练句子分类器的一个例子: + +```python +import tensorflow as tf +import numpy as np +from transformers import AutoTokenizer, TFAutoModelForSequenceClassification + +# Same as before +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "This course is amazing!", +] +batch = dict(tokenizer(sequences, padding=True, truncation=True, return_tensors="tf")) + +# This is new +model.compile(optimizer="adam", loss="sparse_categorical_crossentropy") +labels = tf.convert_to_tensor([1, 1]) +model.train_on_batch(batch, labels) +``` +{/if} + +当然,仅仅用两句话训练模型不会产生很好的效果。为了获得更好的结果,您需要准备一个更大的数据集。 + +在本节中,我们将使用MRPC(微软研究释义语料库)数据集作为示例,该数据集由威廉·多兰和克里斯·布罗克特在[这篇文章](https://www.aclweb.org/anthology/I05-5002.pdf)发布。该数据集由5801对句子组成,每个句子对带有一个标签,指示它们是否为同义(即,如果两个句子的意思相同)。我们在本章中选择了它,因为它是一个小数据集,所以很容易对它进行训练。 + +### 从模型中心(Hub)加载数据集 + +{#if fw === 'pt'} + +{:else} + +{/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个不同文本分类任务中的性能。 + +🤗 Datasets库提供了一个非常便捷的命令,可以在模型中心(hub)上下载和缓存数据集。我们可以通过以下的代码下载MRPC数据集: + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("glue", "mrpc") +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 408 + }) + test: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 1725 + }) +}) +``` + +正如你所看到的,我们获得了一个**DatasetDict**对象,其中包含训练集、验证集和测试集。每一个集合都包含几个列(**sentence1**, **sentence2**, **label**, and **idx**)以及一个代表行数的变量,即每个集合中的行的个数(因此,训练集中有3668对句子,验证集中有408对,测试集中有1725对)。 + +默认情况下,此命令在下载数据集并缓存到 **~/.cache/huggingface/dataset**. 回想一下第2章,您可以通过设置**HF_HOME**环境变量来自定义缓存的文件夹。 + +我们可以访问我们数据集中的每一个**raw_train_dataset**对象,如使用字典: + +```py +raw_train_dataset = raw_datasets["train"] +raw_train_dataset[0] +``` + +```python out +{'idx': 0, + 'label': 1, + 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', + 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} +``` + +我们可以看到标签已经是整数了,所以我们不需要对标签做任何预处理。要知道哪个数字对应于哪个标签,我们可以查看**raw_train_dataset**的**features**. 这将告诉我们每列的类型: + +```py +raw_train_dataset.features +``` + +```python out +{'sentence1': Value(dtype='string', id=None), + 'sentence2': Value(dtype='string', id=None), + 'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None), + 'idx': Value(dtype='int32', id=None)} +``` + +在上面的例子之中,**Label(标签)** 是一种**ClassLabel(分类标签)**,使用整数建立起到类别标签的映射关系。**0**对应于**not_equivalent**,**1**对应于**equivalent**。 + + + +✏️ **试试看!** 查看训练集的第15行元素和验证集的87行元素。他们的标签是什么? + + + +### 预处理数据集 + +{#if fw === 'pt'} + +{:else} + +{/if} + +为了预处理数据集,我们需要将文本转换为模型能够理解的数字。正如你在[第二章](/course/chapter2)上看到的那样 + +```py +from transformers import AutoTokenizer + +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"]) +tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"]) +``` + +然而,在两句话传递给模型,预测这两句话是否是同义之前。我们需要这两句话依次进行适当的预处理。幸运的是,标记器不仅仅可以输入单个句子还可以输入一组句子,并按照我们的BERT模型所期望的输入进行处理: + +```py +inputs = tokenizer("This is the first sentence.", "This is the second one.") +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] +} +``` + +我们在[第二章](/course/chapter2) 讨论了**输入词id(input_ids)** 和 **注意力遮罩(attention_mask)** ,但我们在那个时候没有讨论**类型标记ID(token_type_ids)**。在这个例子中,**类型标记ID(token_type_ids)**的作用就是告诉模型输入的哪一部分是第一句,哪一部分是第二句。 + + + +✏️ ** 试试看!** 选取训练集中的第15个元素,将两句话分别标记为一对。结果和上方的例子有什么不同? + + + +如果我们将**input_ids**中的id转换回文字: + +```py +tokenizer.convert_ids_to_tokens(inputs["input_ids"]) +``` + +我们将得到: + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +``` + +所以我们看到模型需要输入的形式是 **[CLS] sentence1 [SEP] sentence2 [SEP]**。因此,当有两句话的时候。**类型标记ID(token_type_ids)** 的值是: + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +[ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] +``` + +如您所见,输入中 **[CLS] sentence1 [SEP]** 它们的类型标记ID均为**0**,而其他部分,对应于**sentence2 [SEP]**,所有的类型标记ID均为**1**. + +请注意,如果选择其他的检查点,则不一定具有**类型标记ID(token_type_ids)**(例如,如果使用DistilBERT模型,就不会返回它们)。只有当它在预训练期间使用过这一层,模型在构建时依赖它们,才会返回它们。 + +用类型标记ID对BERT进行预训练,并且使用[第一章](/course/chapter1)的遮罩语言模型,还有一个额外的应用类型,叫做下一句预测. 这项任务的目标是建立成对句子之间关系的模型。 + +在下一个句子预测任务中,会给模型输入成对的句子(带有随机遮罩的标记),并被要求预测第二个句子是否紧跟第一个句子。为了提高模型的泛化能力,数据集中一半的两个句子在原始文档中挨在一起,另一半的两个句子来自两个不同的文档。 + +一般来说,你不需要担心是否有**类型标记ID(token_type_ids)**。在您的标输入中:只要您对标记器和模型使用相同的检查点,一切都会很好,因为标记器知道向其模型提供什么。 + +现在我们已经了解了标记器如何处理一对句子,我们可以使用它对整个数据集进行处理:如[之前的章节](/course/chapter2),我们可以给标记器提供一组句子,第一个参数是它第一个句子的列表,第二个参数是第二个句子的列表。这也与我们在[第二章](/course/chapter2)中看到的填充和截断选项兼容. 因此,预处理训练数据集的一种方法是: + +```py +tokenized_dataset = tokenizer( + raw_datasets["train"]["sentence1"], + raw_datasets["train"]["sentence2"], + padding=True, + truncation=True, +) +``` + +这很有效,但它的缺点是返回字典(字典的键是**输入词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)方法,如果我们需要做更多的预处理而不仅仅是标记化,那么这也给了我们一些额外的自定义的方法。这个方法的工作原理是在数据集的每个元素上应用一个函数,因此让我们定义一个标记输入的函数: + +```py +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) +``` + +此函数的输入是一个字典(与数据集的项类似),并返回一个包含**输入词id(input_ids)** , **注意力遮罩(attention_mask)** 和 **类型标记ID(token_type_ids)** 键的新字典。请注意,如果像上面的**示例**一样,如果键所对应的值包含多个句子(每个键作为一个句子列表),那么它依然可以工作,就像前面的例子一样标记器可以处理成对的句子列表。这样的话我们可以在调用**map()**使用该选项 **batched=True** ,这将显著加快标记与标记的速度。这个**标记器**来自[🤗 Tokenizers](https://github.com/huggingface/tokenizers)库由Rust编写而成。当我们一次给它大量的输入时,这个标记器可以非常快。 + +请注意,我们现在在标记函数中省略了**padding**参数。这是因为在标记的时候将所有样本填充到最大长度的效率不高。一个更好的做法:在构建批处理时填充样本更好,因为这样我们只需要填充到该批处理中的最大长度,而不是整个数据集的最大长度。当输入长度变化很大时,这可以节省大量时间和处理能力! + +下面是我们如何在所有数据集上同时应用标记函数。我们在调用**map**时使用了**batch =True**,这样函数就可以同时应用到数据集的多个元素上,而不是分别应用到每个元素上。这将使我们的预处理快许多 + +```py +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) +tokenized_datasets +``` + +🤗Datasets库应用这种处理的方式是向数据集添加新的字段,每个字段对应预处理函数返回的字典中的每个键: + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 408 + }) + test: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 1725 + }) +}) +``` + +在使用预处理函数**map()**时,甚至可以通过传递**num_proc**参数使用并行处理。我们在这里没有这样做,因为🤗标记器库已经使用多个线程来更快地标记我们的样本,但是如果您没有使用该库支持的快速标记器,使用**num_proc**可能会加快预处理。 + +我们的**标记函数(tokenize_function)**返回包含**输入词id(input_ids)** , **注意力遮罩(attention_mask)** 和 **类型标记ID(token_type_ids)** 键的字典,所以这三个字段被添加到数据集的标记的结果中。注意,如果预处理函数**map()**为现有键返回一个新值,那将会修改原有键的值。 + +最后一件我们需要做的事情是,当我们一起批处理元素时,将所有示例填充到最长元素的长度——我们称之为动态填充。 + +### 动态填充 + + + +{#if fw === 'pt'} +负责在批处理中将数据整理为一个batch的函数称为*collate函数*。它是你可以在构建**DataLoader**时传递的一个参数,默认是一个函数,它将把你的数据集转换为PyTorch张量,并将它们拼接起来(如果你的元素是列表、元组或字典,则会使用递归)。这在我们的这个例子中下是不可行的,因为我们的输入不是都是相同大小的。我们故意在之后每个batch上进行填充,避免有太多填充的过长的输入。这将大大加快训练速度,但请注意,如果你在TPU上训练,这可能会导致问题——TPU喜欢固定的形状,即使这需要额外的填充。 + +{:else} + +负责在批处理中将数据整理为一个batch的函数称为*collate函数*。它只会将您的样本转换为 tf.Tensor并将它们拼接起来(如果你的元素是列表、元组或字典,则会使用递归)。这在我们的这个例子中下是不可行的,因为我们的输入不是都是相同大小的。我们故意在之后每个batch上进行填充,避免有太多填充的过长的输入。这将大大加快训练速度,但请注意,如果你在TPU上训练,这可能会导致问题——TPU喜欢固定的形状,即使这需要额外的填充。 + +{/if} + +为了解决句子长度统一的问题,我们必须定义一个collate函数,该函数会将每个batch句子填充到正确的长度。幸运的是,🤗transformer库通过**DataCollatorWithPadding**为我们提供了这样一个函数。当你实例化它时,需要一个标记器(用来知道使用哪个词来填充,以及模型期望填充在左边还是右边),并将做你需要的一切: + +{#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} + +为了测试这个新玩具,让我们从我们的训练集中抽取几个样本。这里,我们删除列**idx**, **sentence1**和**sentence2**,因为不需要它们,并查看一个batch中每个条目的长度: + +```py +samples = tokenized_datasets["train"][:8] +samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]} +[len(x) for x in samples["input_ids"]] +``` + +```python out +[50, 59, 47, 67, 59, 50, 62, 32] +``` + +毫无疑问,我们得到了不同长度的样本,从32到67。动态填充意味着该批中的所有样本都应该填充到长度为67,这是该批中的最大长度。如果没有动态填充,所有的样本都必须填充到整个数据集中的最大长度,或者模型可以接受的最大长度。让我们再次检查**data_collator**是否正确地动态填充了这批样本: + +```py: + +```py +batch = data_collator(samples) +{k: v.shape for k, v in batch.items()} +``` + +{#if fw === 'tf'} + +```python out +{'attention_mask': TensorShape([8, 67]), + 'input_ids': TensorShape([8, 67]), + 'token_type_ids': TensorShape([8, 67]), + 'labels': TensorShape([8])} +``` + +{:else} + +```python out +{'attention_mask': torch.Size([8, 67]), + 'input_ids': torch.Size([8, 67]), + 'token_type_ids': torch.Size([8, 67]), + 'labels': torch.Size([8])} +``` + +看起来不错!现在,我们已经将原始文本转化为了模型可以处理的数据,我们已准备好对其进行微调! + +{/if} + + + +✏️ ** 试试看!** 在GLUE SST-2数据集上应用预处理。它有点不同,因为它是由单个句子而不是成对的句子组成的,但是我们所做的其他事情看起来应该是一样的。另一个更难的挑战,请尝试编写一个可用于任何GLUE任务的预处理函数。 + + + +{#if fw === 'tf'} + +现在我们有了dataset和data collator,我们需要将dataset批量地应用data collator。 我们可以手动加载批次并整理它们,但这需要大量工作,而且可能性能也不是很好。 相反,有一个简单的方法可以为这个问题提供高效的解决方案:`to_tf_dataset()`。 这将在您的数据集上调用一个 `tf.data.Dataset`的方法,这个方法带有一个可选的data collator功能。 `tf.data.Dataset` 是 Keras 可用于 `model.fit()` 的原生 TensorFlow 格式,因此这种方法会立即将🤗 Dataset 转换为可用于训练的格式。 让我们看看它在我们的数据集上是如何使用的! + +```py +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, +) +``` + +就是这样! 我们可以将这些数据集带入下一节,在经过所有艰苦的数据预处理工作之后,训练将变得非常简单。 + +{/if} diff --git a/chapters/zh-CN/chapter3/3.mdx b/chapters/zh-CN/chapter3/3.mdx new file mode 100644 index 000000000..1d452b8fe --- /dev/null +++ b/chapters/zh-CN/chapter3/3.mdx @@ -0,0 +1,172 @@ + + +# 使用 Trainer API 微调模型 + + + + + +🤗 Transformers提供了一个 **Trainer** 类来帮助您在自己的数据集上微调任何预训练模型。完成上一节中的所有数据预处理工作后,您只需要执行几个步骤来创建 **Trainer** .最难的部分可能是为 **Trainer.train()**配置运行环境,因为它在 CPU 上运行速度会非常慢。如果您没有设置 GPU,您可以访问免费的 GPU 或 TPU[Google Colab](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) +``` + +### Training + +在我们定义我们的 **Trainer** 之前首先要定义一个 **TrainingArguments** 类,它将包含 **Trainer**用于训练和评估的所有超参数。您唯一必须提供的参数是保存训练模型的目录,以及训练过程中的检查点。对于其余的参数,您可以保留默认值,这对于基本微调应该非常有效。 + +```py +from transformers import TrainingArguments + +training_args = TrainingArguments("test-trainer") +``` + + + +💡 如果您想在训练期间自动将模型上传到 Hub,请将push_to_hub=True添加到TrainingArguments之中. 我们将在[第四章](/course/chapter4/3)中详细介绍这部分。 + + + +第二步是定义我们的模型。正如在[之前的章节](/2_Using Transformers/Introduction)一样,我们将使用 **AutoModelForSequenceClassification** 类,它有两个参数: + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +你会注意到,和[第二章](/course/chapter2)不一样的是,在实例化此预训练模型后会收到警告。这是因为 BERT 没有在句子对分类方面进行过预训练,所以预训练模型的头部已经被丢弃,而是添加了一个适合句子序列分类的新头部。警告表明一些权重没有使用(对应于丢弃的预训练头的那些),而其他一些权重被随机初始化(新头的那些)。最后鼓励您训练模型,这正是我们现在要做的。 + +一旦我们有了我们的模型,我们就可以定义一个 **Trainer** 通过将之前构造的所有对象传递给它——我们的**model** 、**training_args** ,训练和验证数据集,**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**后,默认 **Trainer**使用 的**data_collator**会使用之前预定义的 **DataCollatorWithPadding** ,因此您可以在这个例子中跳过 **data_collator=data_collator**。在第 2 节中向您展示这部分处理仍然很重要! + +为了让预训练模型在在我们的数据集上微调,我们只需要调用**Trainer**的**train()** 方法 : + +```py +trainer.train() +``` + +这将开始微调(在GPU上应该需要几分钟),并每500步报告一次训练损失。但是,它不会告诉您模型的性能如何(或质量如何)。这是因为: + +1. 我们没有通过将**evaluation_strategy**设置为“**steps**”(在每次更新参数的时候评估)或“**epoch**”(在每个epoch结束时评估)来告诉**Trainer**在训练期间进行评估。 +2. 我们没有为**Trainer**提供一个**compute_metrics()**函数来直接计算模型的好坏(否则评估将只输出loss,这不是一个非常直观的数字)。 + + +### 评估 + +让我们看看如何构建一个有用的 **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** 字段将只包含传递的数据集的loss,以及一些运行时间(预测所需的总时间和平均时间)。如果我们定义了自己的 **compute_metrics()** 函数并将其传递给 **Trainer** ,该字段还将包含**compute_metrics()**的结果。 + +**predict()** 方法是具有三个字段的命名元组: **predictions** , **label_ids** , 和 **metrics** .这 **metrics** 字段将只包含传递的数据集的loss,以及一些运行时间(预测所需的总时间和平均时间)。如果我们定义了自己的 **compute_metrics()** 函数并将其传递给 **Trainer** ,该字段还将包含**compute_metrics()** 的结果。如你看到的, **predictions** 是一个形状为 408 x 2 的二维数组(408 是我们使用的数据集中元素的数量)。这些是我们传递给**predict()**的数据集的每个元素的结果(logits)(正如你在[之前的章节](/course/chapter2)看到的情况)。要将我们的预测的可以与真正的标签进行比较,我们需要在第二个轴上取最大值的索引: + +```py +import numpy as np + +preds = np.argmax(predictions.predictions, axis=-1) +``` + +现在建立我们的 **compute_metric()** 函数来较为直观地评估模型的好坏,我们将使用 🤗 Datasets 库中的指标。我们可以像加载数据集一样轻松加载与 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} +``` + +您获得的确切结果可能会有所不同,因为模型头的随机初始化可能会影响最终建立的模型。在这里,我们可以看到我们的模型在验证集上的准确率为 85.78%,F1 分数为 89.97。这是用于评估 GLUE 基准的 MRPC 数据集结果的两个指标。而在[BERT 论文](https://arxiv.org/pdf/1810.04805.pdf)中展示的基础模型的 F1 分数为 88.9。那是 **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) +``` + +为了查看模型在每个训练周期结束的好坏,下面是我们如何使用**compute_metrics()**函数定义一个新的 **Trainer** : + +```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() +``` + +这一次,它将在训练loss之外,还会输出每个 epoch 结束时的验证loss和指标。同样,由于模型的随机头部初始化,您达到的准确率/F1 分数可能与我们发现的略有不同,但它应该在同一范围内。 + +这 **Trainer** 将在多个 GPU 或 TPU 上开箱即用,并提供许多选项,例如混合精度训练(在训练的参数中使用 **fp16 = True** )。我们将在第 10 章讨论它支持的所有内容。 + +使用**Trainer** API微调的介绍到此结束。对最常见的 NLP 任务执行此操作的示例将在第 7 章中给出,但现在让我们看看如何在纯 PyTorch 中执行相同的操作。 + + + +✏️ **试试看!** 使用您在第 2 节中进行的数据处理,在 GLUE SST-2 数据集上微调模型。 + + + diff --git a/chapters/zh-CN/chapter3/3_tf.mdx b/chapters/zh-CN/chapter3/3_tf.mdx new file mode 100644 index 000000000..911e12a92 --- /dev/null +++ b/chapters/zh-CN/chapter3/3_tf.mdx @@ -0,0 +1,190 @@ + + +# 使用 Keras 微调一个模型 + + + +完成上一节中的所有数据预处理工作后,您只剩下最后的几个步骤来训练模型。 但是请注意,`model.fit()` 命令在 CPU 上运行会非常缓慢。 如果您没有GPU,则可以在 [Google Colab](https://colab.research.google.com/) 上使用免费的 GPU 或 TPU(需要梯子)。 + +这一节的代码示例假设您已经执行了上一节中的代码示例。 下面一个简短的摘要,包含了在开始学习这一节之前您需要的执行的代码: + +```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, +) +``` + +### 训练模型 + +从🤗 Transformers 导入的 TensorFlow 模型已经是 Keras 模型。 下面的视频是对 Keras 的简短介绍。 + + + +这意味着,一旦我们有了数据,就需要很少的工作就可以开始对其进行训练。 + + + +和[第二章](/course/chapter2)使用的方法一样, 我们将使用二分类的 `TFAutoModelForSequenceClassification`类: + +```py +from transformers import TFAutoModelForSequenceClassification + +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +您会注意到,与 [第二章](/course/chapter2) 不同的是,您在实例化此预训练模型后会收到警告。 这是因为 BERT 没有对句子对进行分类进行预训练,所以预训练模型的 head 已经被丢弃,而是插入了一个适合序列分类的新 head。 警告表明一些权重没有使用(对应于丢弃的预训练头),而其他一些权重是随机初始化的(新头的权重)。 最后鼓励您训练模型,这正是我们现在要做的。 + +要在我们的数据集上微调模型,我们只需要在我们的模型上调用 `compile()` 方法,然后将我们的数据传递给 `fit()` 方法。 这将启动微调过程(在 GPU 上应该需要几分钟)并输出训练loss,以及每个 epoch 结束时的验证loss。 + + + +请注意🤗 Transformers 模型具有大多数 Keras 模型所没有的特殊能力——它们可以自动使用内部计算的loss。 如果您没有在 `compile()` 中设置损失函数,他们将默认使用内部计算的损失。 请注意,要使用内部损失,您需要将标签作为输入的一部分传递,而不是作为单独的标签(这是在 Keras 模型中使用标签的正常方式)。 您将在课程的第 2 部分中看到这方面的示例,其中定义正确的损失函数可能很棘手。 然而,对于序列分类,标准的 Keras 损失函数可以正常工作,所以我们将在这里使用它。 + + + +```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, +) +``` + + + +请注意这里有一个非常常见的陷阱——你只是*可以*将损失的名称作为字符串传递给 Keras,但默认情况下,Keras 会假设你已经对输出应用了 softmax。 然而,许多模型在应用 softmax 之前就输出,也称为 *logits*。 我们需要告诉损失函数我们的模型是否经过了softmax,唯一的方法是直接调用它,而不是用字符串的名称。 + + + + +### 提升训练的效果 + + + +如果您尝试上面的代码,它肯定会运行,但您会发现loss只是缓慢或零星地下降。 主要原因是*学习率*。 与loss一样,当我们将优化器的名称作为字符串传递给 Keras 时,Keras 会初始化该优化器具有所有参数的默认值,包括学习率。 但是,根据长期经验,我们知道Transformer 模型更适合使用比 Adam 的默认值(1e-3)也写成为 10 的 -3 次方,或 0.001,低得多的学习率。 5e-5 (0.00005) 比默认值大约低 20 倍,是一个更好的起点。 + +除了降低学习率,我们还有第二个技巧:我们可以慢慢降低学习率。在训练过程中。 在文献中,您有时会看到这被称为 *decaying* 或 *annealing*学习率。 在 Keras 中,最好的方法是使用 *learning rate scheduler*。 一个好用的是`PolynomialDecay`——尽管有这个名字,但在默认设置下,它只是简单地从初始值线性衰减学习率值在训练过程中的最终值,这正是我们想要的。但是, 为了正确使用调度程序,我们需要告诉它训练的次数。 我们将在下面为其计算“num_train_steps”。 + +```py +from tensorflow.keras.optimizers.schedules import PolynomialDecay + +batch_size = 8 +num_epochs = 3 +# 训练步数是数据集中的样本数除以batch size再乘以 epoch。 +# 注意这里的tf_train_dataset是一个转化为batch后的 tf.data.Dataset, +# 不是原来的 Hugging Face Dataset,所以它的 len() 已经是 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) +``` + + + +🤗 Transformers 库还有一个 `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"]) +``` + +现在,我们再次进行fit: + +```py +model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) +``` + + + +💡 如果您想在训练期间自动将模型上传到 Hub,您可以在 `model.fit()` 方法中传递 `PushToHubCallback`。 我们将在 [第四章](/course/chapter4/3) 中进行介绍 + + + +### 模型预测 + + + + +训练和观察的loss下降都非常好,但是如果我们想从训练后的模型中获得输出,或者计算一些指标,或者在生产中使用模型呢? 为此,我们可以使用`predict()` 方法。 这将返回模型的输出头的*logits*数值,每个类一个。 + +```py +preds = model.predict(tf_validation_dataset)["logits"] +``` + +我们可以将这些 logit 转换为模型的类别预测,方法是使用 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} +``` + +您获得的确切结果可能会有所不同,因为模型头的随机初始化可能会改变它获得的指标。 在这里,我们可以看到我们的模型在验证集上的准确率为 85.78%,F1 得分为 89.97。 这些是用于评估 GLUE 基准的 MRPC 数据集结果的两个指标。 [BERT 论文](https://arxiv.org/pdf/1810.04805.pdf) 中的表格报告了基本模型的 F1 分数为 88.9。 那是 `uncased` 模型,而我们目前使用的是 `cased` 模型,这解释了为什么我们会获得更好的结果。 + +使用 Keras API 进行微调的介绍到此结束。 第 7 章将给出对大多数常见 NLP 任务执行此操作的示例。如果您想在 Keras API 上磨练自己的技能,请尝试使第二节所使用的的数据处理在 GLUE SST-2 数据集上微调模型。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter3/4.mdx b/chapters/zh-CN/chapter3/4.mdx new file mode 100644 index 000000000..f1de4cc48 --- /dev/null +++ b/chapters/zh-CN/chapter3/4.mdx @@ -0,0 +1,358 @@ +# 一个完整的训练 + + + + + +现在,我们将了解如何在不使用`Trainer`类的情况下获得与上一节相同的结果。同样,我们假设您已经学习了第 2 节中的数据处理。下面是一个简短的总结,涵盖了您需要的所有内容: + +```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) +``` + +### 训练前的准备 + +在实际编写我们的训练循环之前,我们需要定义一些对象。第一个是我们将用于迭代批次的数据加载器。我们需要对我们的`tokenized_datasets`做一些处理,来处理`Trainer`自动为我们做的一些事情。具体来说,我们需要: + +- 删除与模型不期望的值相对应的列(如`sentence1`和`sentence2`列)。 +- 将列名`label`重命名为`labels`(因为模型期望参数是`labels`)。 +- 设置数据集的格式,使其返回 PyTorch 张量而不是列表。 + +针对上面的每个步骤,我们的 `tokenized_datasets` 都有一个方法: + +```py +tokenized_datasets = tokenized_datasets.remove_columns(["sentence1", "sentence2", "idx"]) +tokenized_datasets = tokenized_datasets.rename_column("label", "labels") +tokenized_datasets.set_format("torch") +tokenized_datasets["train"].column_names +``` + +然后,我们可以检查结果中是否只有模型能够接受的列: + +```python +["attention_mask", "input_ids", "labels", "token_type_ids"] +``` + +至此,我们可以轻松定义数据加载器: + +```py +from torch.utils.data import DataLoader + +train_dataloader = DataLoader( + tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator +) +eval_dataloader = DataLoader( + tokenized_datasets["validation"], batch_size=8, collate_fn=data_collator +) +``` + +为了快速检验数据处理中没有错误,我们可以这样检验其中的一个批次: + +```py +for batch in train_dataloader: + break +{k: v.shape for k, v in batch.items()} +``` + +```python out +{'attention_mask': torch.Size([8, 65]), + 'input_ids': torch.Size([8, 65]), + 'labels': torch.Size([8]), + 'token_type_ids': torch.Size([8, 65])} +``` + +请注意,实际的形状可能与您略有不同,因为我们为训练数据加载器设置了`shuffle=True`,并且模型会将句子填充到`batch`中的最大长度。 + +现在我们已经完全完成了数据预处理(对于任何 ML 从业者来说都是一个令人满意但难以实现的目标),让我们将注意力转向模型。我们完全像在上一节中所做的那样实例化它: + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` +为了确保训练过程中一切顺利,我们将`batch`传递给这个模型: + +```py +outputs = model(**batch) +print(outputs.loss, outputs.logits.shape) +``` + +```python out +tensor(0.5441, grad_fn=) torch.Size([8, 2]) +``` + +当我们提供 `labels` 时, 🤗 Transformers 模型都将返回这个`batch`的`loss`,我们还得到了 `logits`(`batch`中的每个输入有两个,所以张量大小为 8 x 2)。 + +我们几乎准备好编写我们的训练循环了!我们只是缺少两件事:优化器和学习率调度器。由于我们试图自行实现 `Trainer`的功能,我们将使用相同的优化器和学习率调度器。`Trainer` 使用的优化器是 `AdamW` , 与 `Adam` 相同,但在权重衰减正则化方面有所不同(参见[“Decoupled Weight Decay Regularization”](https://arxiv.org/abs/1711.05101)作者:Ilya Loshchilov 和 Frank Hutter): + +```py +from transformers import AdamW + +optimizer = AdamW(model.parameters(), lr=5e-5) +``` + +最后,默认使用的学习率调度器只是从最大值 (5e-5) 到 0 的线性衰减。 为了定义它,我们需要知道我们训练的次数,即所有数据训练的次数(epochs)乘以的数据量(这是我们所有训练数据的数量)。`Trainer`默认情况下使用三个`epochs`,因此我们定义训练过程如下: + +```py +from transformers import get_scheduler + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) +print(num_training_steps) +``` + +```python out +1377 +``` + +### 训练循环 + +最后一件事:如果我们可以访问 GPU,我们将希望使用 GPU(在 CPU 上,训练可能需要几个小时而不是几分钟)。为此,我们定义了一个 `device`,它在GPU可用的情况下指向GPU 我们将把我们的模型和`batche`放在`device`上: + +```py +import torch + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) +device +``` + +```python out +device(type='cuda') +``` + +我们现在准备好训练了!为了了解训练何时结束,我们使用 `tqdm` 库,在训练步骤数上添加了一个进度条: + +```py +from tqdm.auto import tqdm + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +您可以看到训练循环的核心与介绍中的非常相似。我们没有要求任何检验,所以这个训练循环不会告诉我们任何关于模型目前的状态。我们需要为此添加一个评估循环。 + + +### 评估循环 + +正如我们之前所做的那样,我们将使用 🤗 Datasets 库提供的指标。我们已经了解了 `metric.compute()` 方法,当我们使用 `add_batch()`方法进行预测循环时,实际上该指标可以为我们累积所有 `batch` 的结果。一旦我们累积了所有 `batch` ,我们就可以使用 `metric.compute()` 得到最终结果 .以下是在评估循环中实现所有这些的方法: + +```py +from datasets import load_metric + +metric = load_metric("glue", "mrpc") +model.eval() +for batch in eval_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + with torch.no_grad(): + outputs = model(**batch) + + logits = outputs.logits + predictions = torch.argmax(logits, dim=-1) + metric.add_batch(predictions=predictions, references=batch["labels"]) + +metric.compute() +``` + +```python out +{'accuracy': 0.8431372549019608, 'f1': 0.8907849829351535} +``` + +同样,由于模型头部初始化和数据改组的随机性,您的结果会略有不同,但它们应该在同一个范围内。 + + + +✏️ **试试看!** 修改之前的训练循环以在 SST-2 数据集上微调您的模型。 + + + +### S使用🤗 Accelerate加速您的训练循环 + + + +我们之前定义的训练循环在单个 CPU 或 GPU 上运行良好。但是使用[🤗 Accelerate](https://github.com/huggingface/accelerate)库,只需进行一些调整,我们就可以在多个 GPU 或 TPU 上启用分布式训练。从创建训练和验证数据加载器开始,我们的手动训练循环如下所示: + +```py +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +以下是变化: + +```diff ++ from accelerate import Accelerator + from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + ++ accelerator = Accelerator() + + model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) + optimizer = AdamW(model.parameters(), lr=3e-5) + +- device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +- model.to(device) + ++ train_dataloader, eval_dataloader, model, optimizer = accelerator.prepare( ++ train_dataloader, eval_dataloader, model, optimizer ++ ) + + num_epochs = 3 + num_training_steps = num_epochs * len(train_dataloader) + lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps + ) + + progress_bar = tqdm(range(num_training_steps)) + + model.train() + for epoch in range(num_epochs): + for batch in train_dataloader: +- batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss +- loss.backward() ++ accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +要添加的第一行是导入`Accelerator`。第二行实例化一个 `Accelerator`对象 ,它将查看环境并初始化适当的分布式设置。 🤗 Accelerate 为您处理数据在设备间的传递,因此您可以删除将模型放在设备上的那行代码(或者,如果您愿意,可使用 `accelerator.device` 代替 `device` )。 + +然后大部分工作会在将数据加载器、模型和优化器发送到的`accelerator.prepare()`中完成。这将会把这些对象包装在适当的容器中,以确保您的分布式训练按预期工作。要进行的其余更改是删除将`batch`放在 `device` 的那行代码(同样,如果您想保留它,您可以将其更改为使用 `accelerator.device` ) 并将 `loss.backward()` 替换为`accelerator.backward(loss)`。 + + +⚠️ 为了使云端 TPU 提供的加速发挥最大的效益,我们建议使用标记器(tokenizer)的 `padding=max_length` 和 `max_length` 参数将您的样本填充到固定长度。 + + +如果您想复制并粘贴来直接运行,以下是 🤗 Accelerate 的完整训练循环: + +```py +from accelerate import Accelerator +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +accelerator = Accelerator() + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +train_dl, eval_dl, model, optimizer = accelerator.prepare( + train_dataloader, eval_dataloader, model, optimizer +) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dl) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dl: + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +把这个放在 `train.py` 文件中,可以让它在任何类型的分布式设置上运行。要在分布式设置中试用它,请运行以下命令: + +```bash +accelerate config +``` + +这将询问您几个配置的问题并将您的回答转储到此命令使用的配置文件中: + +``` +accelerate launch train.py +``` + +这将启动分布式训练 + +这将启动分布式训练。如果您想在 Notebook 中尝试此操作(例如,在 Colab 上使用 TPU 进行测试),只需将代码粘贴到 `training_function()` 并使用以下命令运行最后一个单元格: + +```python +from accelerate import notebook_launcher + +notebook_launcher(training_function) +``` + +您可以在[🤗 Accelerate repo](https://github.com/huggingface/accelerate/tree/main/examples)找到更多的示例。 diff --git a/chapters/zh-CN/chapter3/5.mdx b/chapters/zh-CN/chapter3/5.mdx new file mode 100644 index 000000000..760741ec9 --- /dev/null +++ b/chapters/zh-CN/chapter3/5.mdx @@ -0,0 +1,20 @@ + + +# 微调,检查! + +这是非常令人高兴的! 在前两章中,您了解了模型和标记器(tokenizer),现在您知道如何针对您自己的数据对它们进行微调。回顾一下,在本章中,您: + +{#if fw === 'pt'} +* 了解了[Hub](https://huggingface.co/datasets)中的数据集 +* 学习了如何加载和预处理数据集,包括使用动态填充和整理器 +* 实现您自己的模型微调和评估 +* 实施了一个较为底层的训练循环 +* 使用 🤗 Accelerate 轻松调整您的训练循环,使其适用于多个 GPU 或 TPU + +{:else} +* 了解了[Hub](https://huggingface.co/datasets)中的数据集 +* 学习了如何加载和预处理数据集 +* 学习了如何使用 Keras 微调和评估模型 +* 实现了自定义指标 + +{/if} diff --git a/chapters/zh-CN/chapter3/6.mdx b/chapters/zh-CN/chapter3/6.mdx new file mode 100644 index 000000000..750bfd3cd --- /dev/null +++ b/chapters/zh-CN/chapter3/6.mdx @@ -0,0 +1,284 @@ + + + + +# End-of-chapter quiz + +Test what you learned in this chapter! + +### 1.“情绪”数据集包含标记有情绪的 Twitter 消息。在[ Hub ]( https://huggingface.co/datasets 集线器)中搜索它,然后读取数据集卡。哪一个不是它的基本情感? + + +### 2.在[ Hub ]( https://huggingface.co/datasets 集线器)中搜索‘ ar _ sarcasm’数据集,它支持哪个任务? + dataset card !" + }, + { + text: "命名实体识别", + explain: "不是这样的ーー再看看 < a href =’https://huggingface.co/datasets/ar _ sarcasm’> dataset card !" + }, + { + text: "回答问题", + explain: "Alas, this question was not answered correctly. 再试一次!" + } + ]} +/> + +### 3.BERT 模型期望如何处理一对句子? + [ CLS ] 特殊令牌在开始时是必需的,但是这不是唯一的事情!" + }, + { + text: "表示句子1[ SEP ]的符号表示句子2[ SEP ]", + explain: "没错!", + correct: true + }, + { + text: "表示句子1[ SEP ]的符号表示句子2", + explain: "开头需要一个 < code > [ CLS ] 特殊标记,还需要一个 < code > [ SEP ] 特殊标记来分隔两个句子,但这还不是全部!" + } + ]} +/> + +{#if fw === 'pt'} +### 4.‘ Dataset.map ()’方法的好处是什么? + + +### 5.什么是动态填充? + + +### 6.校对函数的用途是什么? + > 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.", + correct: true + }, + { + text: "它预处理整个数据集。", + explain: "这将是一个预处理函数,而不是校对函数。" + }, + { + text: "它截断数据集中的序列。", + explain: "校对函数用于处理单个批处理,而不是整个数据集。如果您对截断感兴趣,可以使用 < code > tokenizer 的 < truncate 参数。" + } + ]} +/> + +### 7.当你用一个预先训练过的语言模型(例如‘ bert-base-uncased’)实例化一个‘ AutoModelForXxx’类,这个类对应于一个不同于它所被训练的任务时会发生什么? + Trainer 所做的,而不是 Accelerate 库。再试一次!", + correct: true + }, + { + text: "丢弃预先训练好的模型头部。", + explain: "Something else needs to happen. 再试一次!" + }, + { + text: "没有,因为模型仍然可以针对不同的任务进行微调。", + explain: "这个经过训练的模特的头没有经过训练来解决这个问题,所以我们应该丢掉这个头!" + } + ]} +/> + +### 8.训练争论的目的是什么? + TrainingArguments 。" + }, + { + text: "它只包含用于评估的超参数。", + explain: "In the example, we specified where the model and its checkpoints will be saved. 再试一次!" + }, + { + text: "您可以轻松地计算与数据集相关的指标。", + explain: "In the example, we used an evaluation_strategy as well, so this impacts evaluation. 再试一次!" + } + ]} +/> + +### 9.为什么要使用 Accelerate 库? +Trainer, not the 🤗 Accelerate library. 再试一次!" + }, + { + text: "它使我们的训练循环工作在分布式策略上", + explain: "正确! 随着加速,你的训练循环将为多个 gpu 和 TPUs 工作。", + correct: true + }, + { + text: "它提供了更多的优化功能。", + explain: "不,Accelerate 库不提供任何优化功能。" + } + ]} +/> + +{:else} +### 4.当你用一个预先训练过的语言模型(例如‘ bert-base-uncased’)实例化一个‘ tfautoodelforxxx’类时,会发生什么? + + +### 5.来自“变压器”的 TensorFlow 模型已经是 Keras 模型,这有什么好处? + TPUStrategy scope 中的所有内容,包括模型的初始化。" + }, + { + text: "您可以利用现有的方法,如 < code > compile () 、 < code > fit () < c/ode > 和 < code > predict () 。", + explain: "正确! 一旦你有了这些数据,在这些数据上进行培训只需要很少的工作。", + correct: true + }, + { + text: "你可以学习 Keras 和变形金刚。", + explain: "没错,但我们要找的是别的东西:)", + correct: true + }, + { + text: "困惑", + explain: "Keras 帮助我们训练和评估模型,而不是计算与数据集相关的度量。" + } + ]} +/> + +### 6.如何定义自己的定制度量? + tfkeras.metrics. Metric 。", + explain: "太好了!", + correct: true + }, + { + text: "使用 Keras 函数 API。", + explain: "再试一次!" + }, + { + text: "通过使用带签名的可调用 < code > metric _ fn (y _ true,y _ pred) 。", + explain: "正确!", + correct: true + }, + { + text: "通过谷歌搜索。", + explain: "这不是我们要找的答案,但它应该能帮助你找到答案。", + correct: true + } + ]} +/> + +{/if} \ No newline at end of file diff --git a/utils/code_formatter.py b/utils/code_formatter.py index 2252bf340..dfff59a30 100644 --- a/utils/code_formatter.py +++ b/utils/code_formatter.py @@ -4,6 +4,7 @@ import re from pathlib import Path + def blackify(filename, check_only=False): # Read the content of the file with open(filename, "r", encoding="utf-8") as f: @@ -20,16 +21,12 @@ def blackify(filename, check_only=False): start_index = line_index while line_index < len(lines) and lines[line_index].strip() != "```": line_index += 1 - - code = "\n".join(lines[start_index: line_index]) + + code = "\n".join(lines[start_index:line_index]) # Deal with ! instructions code = re.sub(r"^!", r"## !", code, flags=re.MULTILINE) - - code_samples.append({ - "start_index": start_index, - "end_index": line_index - 1, - "code": code - }) + + code_samples.append({"start_index": start_index, "end_index": line_index - 1, "code": code}) line_index += 1 else: line_index += 1 @@ -39,29 +36,28 @@ def blackify(filename, check_only=False): full_code = delimiter.join([sample["code"] for sample in code_samples]) formatted_code = full_code.replace("\t", " ") formatted_code = black.format_str(formatted_code, mode=black.FileMode({black.TargetVersion.PY37}, line_length=90)) - + # Black adds last new lines we don't want, so we strip individual code samples. cells = formatted_code.split(delimiter) cells = [cell.strip() for cell in cells] formatted_code = delimiter.join(cells) - + if check_only: return full_code == formatted_code elif full_code == formatted_code: # Nothing to do, all is good return - + formatted_code = re.sub(r"^## !", r"!", formatted_code, flags=re.MULTILINE) print(f"Formatting {filename}") # Re-build the content with formatted code new_lines = [] start_index = 0 for sample, code in zip(code_samples, formatted_code.split(delimiter)): - new_lines.extend(lines[start_index:sample["start_index"]]) + new_lines.extend(lines[start_index : sample["start_index"]]) new_lines.append(code) start_index = sample["end_index"] + 1 new_lines.extend(lines[start_index:]) - with open(filename, "w", encoding="utf-8") as f: f.write("\n".join(new_lines)) @@ -77,14 +73,18 @@ def format_all_files(check_only=False): except Exception: print(f"Failed to format {filename}.") raise - + if check_only and len(failures) > 0: raise ValueError(f"{len(failures)} files need to be formatted, run `make style`.") if __name__ == "__main__": parser = argparse.ArgumentParser() - parser.add_argument("--check_only", action="store_true", help="Just check files are properly formatted.") + parser.add_argument( + "--check_only", + action="store_true", + help="Just check files are properly formatted.", + ) args = parser.parse_args() format_all_files(check_only=args.check_only) diff --git a/utils/generate_notebooks.py b/utils/generate_notebooks.py index 87f2bb38b..7e85013e1 100644 --- a/utils/generate_notebooks.py +++ b/utils/generate_notebooks.py @@ -22,15 +22,16 @@ frameworks = {"pt": "PyTorch", "tf": "TensorFlow"} + def read_and_split_frameworks(fname): """ Read the MDX in fname and creates two versions (if necessary) for each framework. """ with open(fname, "r") as f: content = f.readlines() - + contents = {"pt": [], "tf": []} - + differences = False current_content = [] line_idx = 0 @@ -54,7 +55,7 @@ def read_and_split_frameworks(fname): if len(current_content) > 0: for key in contents: contents[key].extend(current_content) - + if differences: return {k: "".join(content) for k, content in contents.items()} else: @@ -96,12 +97,16 @@ def convert_to_nb_cell(cell): nb_cell = {"cell_type": "code", "execution_count": None, "metadata": {}} if isinstance(cell, tuple): nb_cell["source"] = cell[0] - nb_cell["outputs"] = [nbformat.notebooknode.NotebookNode({ - 'data': {'text/plain': cell[1]}, - 'execution_count': None, - 'metadata': {}, - 'output_type': 'execute_result', - })] + nb_cell["outputs"] = [ + nbformat.notebooknode.NotebookNode( + { + "data": {"text/plain": cell[1]}, + "execution_count": None, + "metadata": {}, + "output_type": "execute_result", + } + ) + ] else: nb_cell["source"] = cell nb_cell["outputs"] = [] @@ -110,9 +115,7 @@ def convert_to_nb_cell(cell): def nb_cell(source, code=True): if not code: - return nbformat.notebooknode.NotebookNode( - {"cell_type": "markdown", "source": source, "metadata": {}} - ) + return nbformat.notebooknode.NotebookNode({"cell_type": "markdown", "source": source, "metadata": {}}) return nbformat.notebooknode.NotebookNode( {"cell_type": "code", "metadata": {}, "source": source, "execution_count": None, "outputs": []} ) @@ -152,11 +155,19 @@ def build_notebook(fname, title, output_dir="."): "What to do when you get an error", ] sections_with_faiss = ["Semantic search with FAISS (PyTorch)", "Semantic search with FAISS (TensorFlow)"] + sections_with_gradio = [ + "Building your first demo", + "Understanding the Interface class", + "Sharing demos with others", + "Integrations with the Hugging Face Hub", + "Advanced Interface features", + "Introduction to Blocks", + ] stem = Path(fname).stem if not isinstance(sections, dict): contents = [sections] titles = [title] - fnames = [f"{stem}.ipynb"] + fnames = [f"section{stem}.ipynb"] else: contents = [] titles = [] @@ -164,16 +175,16 @@ def build_notebook(fname, title, output_dir="."): for key, section in sections.items(): contents.append(section) titles.append(f"{title} ({frameworks[key]})") - fnames.append(f"{stem}_{key}.ipynb") - + fnames.append(f"section{stem}_{key}.ipynb") + for title, content, fname in zip(titles, contents, fnames): cells = extract_cells(content) if len(cells) == 0: continue - + nb_cells = [ nb_cell(f"# {title}", code=False), - nb_cell("Install the Transformers and Datasets libraries to run this notebook.", code=False) + nb_cell("Install the Transformers and Datasets libraries to run this notebook.", code=False), ] # Install cell @@ -181,21 +192,34 @@ def build_notebook(fname, title, output_dir="."): if title in sections_with_accelerate: installs.append("!pip install accelerate") installs.append("# To run the training on TPU, you will need to uncomment the followin line:") - installs.append("# !pip install cloud-tpu-client==0.10 torch==1.9.0 https://storage.googleapis.com/tpu-pytorch/wheels/torch_xla-1.9-cp37-cp37m-linux_x86_64.whl") + installs.append( + "# !pip install cloud-tpu-client==0.10 torch==1.9.0 https://storage.googleapis.com/tpu-pytorch/wheels/torch_xla-1.9-cp37-cp37m-linux_x86_64.whl" + ) if title in sections_with_hf_hub: installs.append("!apt install git-lfs") if title in sections_with_faiss: installs.append("!pip install faiss-gpu") - + if title in sections_with_gradio: + installs.append("!pip install gradio") + nb_cells.append(nb_cell("\n".join(installs))) if title in sections_with_hf_hub: - nb_cells.extend([ - nb_cell("You will need to setup git, adapt your email and name in the following cell.", code=False), - nb_cell("!git config --global user.email \"you@example.com\"\n!git config --global user.name \"Your Name\""), - nb_cell("You will also need to be logged in to the Hugging Face Hub. Execute the following and enter your credentials.", code=False), - nb_cell("from huggingface_hub import notebook_login\n\nnotebook_login()"), - ]) + nb_cells.extend( + [ + nb_cell( + "You will need to setup git, adapt your email and name in the following cell.", code=False + ), + nb_cell( + '!git config --global user.email "you@example.com"\n!git config --global user.name "Your Name"' + ), + nb_cell( + "You will also need to be logged in to the Hugging Face Hub. Execute the following and enter your credentials.", + code=False, + ), + nb_cell("from huggingface_hub import notebook_login\n\nnotebook_login()"), + ] + ) nb_cells += [convert_to_nb_cell(cell) for cell in cells] metadata = {"colab": {"name": title, "provenance": []}} nb_dict = {"cells": nb_cells, "metadata": metadata, "nbformat": 4, "nbformat_minor": 4} @@ -206,26 +230,20 @@ def build_notebook(fname, title, output_dir="."): def get_titles(): """ - Parse the yaml _chapters.yml to get the correspondence filename to title + Parse the _toctree.yml file to get the correspondence filename to title """ - table = yaml.safe_load(open(os.path.join(PATH_TO_COURSE, "_chapters.yml"), "r")) + table = yaml.safe_load(open(os.path.join(PATH_TO_COURSE, "_toctree.yml"), "r")) result = {} for entry in table: - chapter_name = entry["local"] - sections = [] - for i, section in enumerate(entry["sections"]): - if isinstance(section, str): - result[os.path.join(chapter_name, f"section{i+1}")] = section + for section in entry["sections"]: + section_title = section["title"] + if "local_fw" in section: + section_names = section["local_fw"] + result[section_names["pt"]] = section_title + result[section_names["tf"]] = section_title else: section_name = section["local"] - section_title = section["title"] - if isinstance(section_name, str): - result[os.path.join(chapter_name, section_name)] = section_title - else: - if isinstance(section_title, str): - section_title = {key: section_title for key in section_name.keys()} - for key in section_name.keys(): - result[os.path.join(chapter_name, section_name[key])] = section_title[key] + result[section_name] = section_title return {k: v for k, v in result.items() if "quiz" not in v} From 604e189efabc4e1b465af6134616f280b329ce4e Mon Sep 17 00:00:00 2001 From: lewtun Date: Thu, 9 Jun 2022 16:28:07 +0200 Subject: [PATCH 19/51] Bump release (#236) --- chapters/fr/chapter7/1.mdx | 8 +- chapters/fr/chapter7/2.mdx | 169 +++++----- chapters/fr/chapter7/3.mdx | 180 +++++------ chapters/fr/chapter7/4.mdx | 175 +++++----- chapters/fr/chapter7/5.mdx | 248 +++++++------- chapters/fr/chapter7/6.mdx | 137 ++++---- chapters/fr/chapter7/7.mdx | 208 ++++++------ chapters/fr/chapter7/8.mdx | 10 +- chapters/fr/chapter7/9.mdx | 112 +++---- chapters/it/_toctree.yml | 17 + chapters/it/chapter4/1.mdx | 17 + chapters/it/chapter4/2.mdx | 96 ++++++ chapters/it/chapter4/3.mdx | 643 +++++++++++++++++++++++++++++++++++++ chapters/it/chapter4/4.mdx | 83 +++++ chapters/it/chapter4/5.mdx | 7 + chapters/it/chapter4/6.mdx | 223 +++++++++++++ chapters/pt/_toctree.yml | 2 + chapters/pt/chapter5/5.mdx | 471 +++++++++++++++++++++++++++ chapters/ru/_toctree.yml | 10 +- chapters/ru/chapter2/2.mdx | 354 ++++++++++++++++++++ chapters/ru/chapter2/3.mdx | 227 +++++++++++++ chapters/ru/chapter2/7.mdx | 13 + chapters/th/_toctree.yml | 2 + chapters/th/chapter6/3.mdx | 524 ++++++++++++++++++++++++++++++ 24 files changed, 3320 insertions(+), 616 deletions(-) create mode 100644 chapters/it/chapter4/1.mdx create mode 100644 chapters/it/chapter4/2.mdx create mode 100644 chapters/it/chapter4/3.mdx create mode 100644 chapters/it/chapter4/4.mdx create mode 100644 chapters/it/chapter4/5.mdx create mode 100644 chapters/it/chapter4/6.mdx create mode 100644 chapters/pt/chapter5/5.mdx create mode 100644 chapters/ru/chapter2/2.mdx create mode 100644 chapters/ru/chapter2/3.mdx create mode 100644 chapters/ru/chapter2/7.mdx create mode 100644 chapters/th/chapter6/3.mdx diff --git a/chapters/fr/chapter7/1.mdx b/chapters/fr/chapter7/1.mdx index ca1d715b5..49c7a7a84 100644 --- a/chapters/fr/chapter7/1.mdx +++ b/chapters/fr/chapter7/1.mdx @@ -2,7 +2,7 @@ # Introduction -Dans le [Chapitre 3](/course/fr/chapter3), vous avez vu comment *finetuner* un modèle de classification de texte. Dans ce chapitre, nous nous attaquons aux tâches de NLP courantes suivantes : +Dans le [chapitre 3](/course/fr/chapter3), vous avez vu comment *finetuner* un modèle de classification de texte. Dans ce chapitre, nous nous attaquons aux tâches de NLP courantes suivantes : - la classification de *tokens*, - la modélisation du langage masqué (comme BERT), @@ -13,13 +13,13 @@ Dans le [Chapitre 3](/course/fr/chapter3), vous avez vu comment *finetuner* un m {#if fw === 'pt'} -Pour ce faire, vous devrez tirer parti de tout ce que vous avez appris sur l'API `Trainer` et la bibliothèque 🤗 *Accelerate* au [Chapitre 3](/course/fr/chapitre3), la bibliothèque 🤗 *Datasets* au [Chapitre 5](/course/fr/chapiter5), et la bibliothèque 🤗 *Tokenizers* au [Chapitre 6](/course/fr/chapiter6). Nous téléchargerons également nos résultats sur le *Hub*, comme nous l'avons fait dans le [Chapitre 4](/course/fr/chapiter4), donc c'est vraiment le chapitre où tout est réuni ! +Pour ce faire, vous devrez tirer parti de tout ce que vous avez appris sur l'API `Trainer`, sur la bibliothèque 🤗 *Accelerate* au [chapitre 3](/course/fr/chapitre3), sur la bibliothèque 🤗 *Datasets* au [chapitre 5](/course/fr/chapiter5) et sur la bibliothèque 🤗 *Tokenizers* au [chapitre 6](/course/fr/chapiter6). Nous téléchargerons également nos résultats sur le *Hub*, comme nous l'avons fait dans le [chapitre 4](/course/fr/chapiter4), donc c'est vraiment le chapitre où tout est réuni ! -Chaque section peut être lue indépendamment et vous montrera comment entraîner un modèle avec l'API `Trainer` ou avec votre propre boucle d'entraînement, en utilisant 🤗 *Accelerate*. N'hésitez pas à sauter l'une ou l'autre partie et à vous concentrer sur celle qui vous intéresse le plus : l'API `Trainer` est idéale pour affiner ou entraîner votre modèle sans vous soucier de ce qui se passe en coulisses, tandis que la boucle d'entraînement avec `Accelerate` vous permettra de personnaliser plus facilement toutes les parties que vous souhaitez. +Chaque section peut être lue indépendamment et vous montrera comment entraîner un modèle avec l'API `Trainer` ou avec 🤗 *Accelerate* et votre propre boucle d'entraînement. N'hésitez pas à sauter l'une ou l'autre partie et à vous concentrer sur celle qui vous intéresse le plus. L'API `Trainer` est idéale pour *finetuner* ou entraîner votre modèle sans vous soucier de ce qui se passe en coulisses, tandis que la boucle d'entraînement avec `Accelerate` vous permettra de personnaliser plus facilement toutes les parties que vous souhaitez. {:else} -Pour ce faire, vous devrez tirer parti de tout ce que vous avez appris sur l'entraînement des modèles avec l'API Keras dans le [Chapitre 3](/course/fr/chapiter3), la bibliothèque 🤗 *Datasets* dans le [Chapitre 5](/course/fr/chapiter5), et la bibliothèque 🤗 *Tokenizers* dans le [Chapitre 6](/course/fr/chapiter6). Nous téléchargerons également nos résultats sur le *Hub*, comme nous l'avons fait dans le [Chapitre 4](/course/fr/chapiter4), donc c'est vraiment le chapitre où tout est réuni ! +Pour ce faire, vous devrez tirer parti de tout ce que vous avez appris sur l'entraînement des modèles avec l'API Keras dans le [chapitre 3](/course/fr/chapiter3), sur la bibliothèque 🤗 *Accelerate* au [chapitre 3](/course/fr/chapitre3), sur la bibliothèque 🤗 *Datasets* au [chapitre 5](/course/fr/chapiter5) et sur la bibliothèque 🤗 *Tokenizers* au [chapitre 6](/course/fr/chapiter6). Nous téléchargerons également nos résultats sur le *Hub*, comme nous l'avons fait dans le [chapitre 4](/course/fr/chapiter4), donc c'est vraiment le chapitre où tout est réuni ! Chaque section peut être lue indépendamment. diff --git a/chapters/fr/chapter7/2.mdx b/chapters/fr/chapter7/2.mdx index 7fb7fae81..de780128e 100644 --- a/chapters/fr/chapter7/2.mdx +++ b/chapters/fr/chapter7/2.mdx @@ -1,6 +1,6 @@ -# Classification de *tokens* +# Classification de tokens {#if fw === 'pt'} @@ -22,15 +22,15 @@ {/if} -La première application que nous allons explorer est la classification de *tokens*. Cette tâche générique englobe tous les problèmes qui peuvent être formulés comme "l'attribution d'une étiquette à chaque *token* dans une phrase", tels que : +La première application que nous allons explorer est la classification de *tokens*. Cette tâche générique englobe tous les problèmes qui peuvent être formulés comme l'attribution d'une étiquette à chaque *token* d'une phrase, tels que : -- **reconnaissance d'entités nommées (NER)** : trouver les entités (telles que des personnes, des lieux ou des organisations) dans une phrase. Cela peut être formulé comme l'attribution d'une étiquette à chaque *token* en ayant une classe par entité et une classe pour "aucune entité". -- **part-of-speech tagging (POS)** : marquer chaque mot dans une phrase comme correspondant à une partie particulière du discours (comme un nom, un verbe, un adjectif, etc.). -- ***chunking*** : trouver les *tokens* qui appartiennent à la même entité. Cette tâche (qui peut être combinée avec le POS ou la NER) peut être formulée comme l'attribution d'une étiquette (habituellement `B-`) à tous les *tokens* qui sont au début d'un morceau, une autre étiquette (habituellement `I-`) aux *tokens* qui sont à l'intérieur d'un morceau, et une troisième étiquette (habituellement `O`) aux *tokens* qui n'appartiennent à aucun morceau. +- la **reconnaissance d'entités nommées (NER de l'anglais *Named Entity Recognition*)**, c'est-à-dire trouver les entités (telles que des personnes, des lieux ou des organisations) dans une phrase. Ce tâche peut être formulée comme l'attribution d'une étiquette à chaque *token* faisant parti d'une entité en ayant une classe spécifique par entité, et une classe pour les *tokens* ne faisant pas parti d'entité. +- le ***part-of-speech tagging* (POS)**, c'est-à-dire marquer chaque mot dans une phrase comme correspondant à une partie particulière (comme un nom, un verbe, un adjectif, etc.). +- le ***chunking***, c'est-à-dire trouver les *tokens* qui appartiennent à la même entité. Cette tâche (qui peut être combinée avec le POS ou la NER) peut être formulée comme l'attribution d'une étiquette (habituellement `B-`) à tous les *tokens* qui sont au début d'un morceau, une autre étiquette (habituellement `I-`) aux *tokens* qui sont à l'intérieur d'un morceau, et une troisième étiquette (habituellement `O`) aux *tokens* qui n'appartiennent à aucun morceau. -Bien sûr, il existe de nombreux autres types de problèmes de classification de *tokens* ; ce ne sont là que quelques exemples représentatifs. Dans cette section, nous allons affiner un modèle (BERT) sur une tâche NER, qui sera alors capable de calculer des prédictions comme celle-ci : +Bien sûr, il existe de nombreux autres types de problèmes de classification de *tokens*. Ce ne sont là que quelques exemples représentatifs. Dans cette section, nous allons *finetuner* un modèle (BERT) sur la tâche de NER. Il sera alors capable de calculer des prédictions comme celle-ci : @@ -40,7 +40,7 @@ Bien sûr, il existe de nombreux autres types de problèmes de classification de -Vous pouvez trouver le modèle que nous allons entraîner et télécharger sur le *Hub* et vérifier ses prédictions [ici](https://huggingface.co/huggingface-course/bert-finetuned-ner?text=My+nom+est+Sylvain+et+je+travaille+à+Hugging+Face+in+Brooklyn). +Vous pouvez trouver, télécharger et vérifier les précisions de ce modèle sur le [*Hub*](https://huggingface.co/huggingface-course/bert-finetuned-ner?text=My+nom+est+Sylvain+et+je+travaille+à+Hugging+Face+in+Brooklyn) les prédictions du modèle que nous allons entraîner. ## Préparation des données @@ -48,7 +48,7 @@ Tout d'abord, nous avons besoin d'un jeu de données adapté à la classificatio -💡 Tant que votre jeu de données consiste en des textes divisés en mots avec leurs étiquettes correspondantes, vous pourrez adapter les procédures de traitement des données décrites ici à votre propre jeu de données. Reportez-vous au [Chapitre 5](/course/fr/chapter5) si vous avez besoin d'un rafraîchissement sur la façon de charger vos propres données personnalisées dans un `Dataset`. +💡 Tant que votre jeu de données consiste en des textes divisés en mots avec leurs étiquettes correspondantes, vous pourrez adapter les procédures de traitement des données décrites ici à votre propre jeu de données. Reportez-vous au [chapitre 5](/course/fr/chapter5) si vous avez besoin d'un rafraîchissement sur la façon de charger vos propres données personnalisées dans un `Dataset`. @@ -62,7 +62,7 @@ from datasets import load_dataset raw_datasets = load_dataset("conll2003") ``` -Cela va télécharger et mettre en cache le jeu de données, comme nous l'avons vu dans [Chapitre 3](/course/fr/chapter3) pour le jeu de données GLUE MRPC. L'inspection de cet objet nous montre les colonnes présentes et la répartition entre les ensembles d'entraînement, de validation et de test : +Cela va télécharger et mettre en cache le jeu de données, comme nous l'avons vu dans [chapitre 3](/course/fr/chapter3) pour le jeu de données GLUE MRPC. L'inspection de cet objet nous montre les colonnes présentes dans ce jeu de données et la répartition entre les ensembles d'entraînement, de validation et de test : ```py raw_datasets @@ -85,7 +85,7 @@ DatasetDict({ }) ``` -En particulier, nous pouvons voir que le jeu de données contient des étiquettes pour les trois tâches que nous avons mentionnées précédemment : NER, POS, et *chunking*. Une grande différence avec les autres jeux de données est que les textes d'entrée ne sont pas présentés comme des phrases ou des documents, mais comme des listes de mots (la dernière colonne est appelée `tokens`, mais elle contient des mots dans le sens où ce sont des entrées pré-tokénisées qui doivent encore passer par le *tokenizer* pour la tokenisation des sous-mots). +En particulier, nous pouvons voir que le jeu de données contient des étiquettes pour les trois tâches que nous avons mentionnées précédemment : NER, POS et *chunking*. Une grande différence avec les autres jeux de données est que les entrées textuelles ne sont pas présentés comme des phrases ou des documents, mais comme des listes de mots (la dernière colonne est appelée `tokens`, mais elle contient des mots dans le sens où ce sont des entrées prétokénisées qui doivent encore passer par le *tokenizer* pour la tokenisation en sous-mots). Regardons le premier élément de l'ensemble d'entraînement : @@ -97,7 +97,7 @@ raw_datasets["train"][0]["tokens"] ['EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'lamb', '.'] ``` -Puisque nous voulons effectuer la reconnaissance des entités nommées, nous allons examiner les balises NER : +Puisque nous voulons effectuer reconnaître des entités nommées, nous allons examiner les balises NER : ```py raw_datasets["train"][0]["ner_tags"] @@ -107,7 +107,7 @@ raw_datasets["train"][0]["ner_tags"] [3, 0, 7, 0, 0, 0, 7, 0, 0] ``` -Ce sont les étiquettes sous forme d'entiers prêts pour l'entraînement, mais ils ne sont pas nécessairement utiles lorsque nous voulons inspecter les données. Comme pour la classification de texte, nous pouvons accéder à la correspondance entre ces entiers et les noms des étiquettes en regardant l'attribut `features` de notre jeu de données : +Ce sont les étiquettes sous forme d'entiers disponibles pour l'entraînement mais ne sont pas nécessairement utiles lorsque nous voulons inspecter les données. Comme pour la classification de texte, nous pouvons accéder à la correspondance entre ces entiers et les noms des étiquettes en regardant l'attribut `features` de notre jeu de données : ```py ner_feature = raw_datasets["train"].features["ner_tags"] @@ -118,7 +118,7 @@ ner_feature 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) ``` -Cette colonne contient donc des éléments qui sont des séquences de `ClassLabel`s. Le type des éléments de la séquence se trouve dans l'attribut `feature` de cette `ner_feature`, et nous pouvons accéder à la liste des noms en regardant l'attribut `names` de cette `feature` : +Cette colonne contient donc des éléments qui sont des séquences de `ClassLabel`. Le type des éléments de la séquence se trouve dans l'attribut `feature` de cette `ner_feature`, et nous pouvons accéder à la liste des noms en regardant l'attribut `names` de cette `feature` : ```py label_names = ner_feature.feature.names @@ -129,13 +129,13 @@ label_names ['O', 'B-PER', 'I-PER', 'B-ORG', 'I-ORG', 'B-LOC', 'I-LOC', 'B-MISC', 'I-MISC'] ``` -Nous avons déjà vu ces étiquettes en creusant dans le pipeline `token-classification` au [Chapitre 6](/course/fr/chapter6/3), mais pour un rapide rappel : +Nous avons déjà vu ces étiquettes au [chapitre 6](/course/fr/chapter6/3) lorsque nous nous sommes intéressés au pipeline `token-classification` mais nosu pouvons tout de même faire un rapide rappel : - `O` signifie que le mot ne correspond à aucune entité. -- `B-PER`/`I-PER` signifie que le mot correspond au début de/est à l'intérieur d'une entité *personne*. -- `B-ORG`/`I-ORG` signifie que le mot correspond au début/à l'intérieur d'une entité *organisation*. -- `B-LOC`/`I-LOC` signifie que le mot correspond au début/à l'intérieur d'une entité *location*. -- `B-MISC`/`I-MISC` signifie que le mot correspond au début/à l'intérieur d'une entité *divers*. +- `B-PER`/`I-PER` signifie que le mot correspond au début/est à l'intérieur d'une entité *personne*. +- `B-ORG`/`I-ORG` signifie que le mot correspond au début/est à l'intérieur d'une entité *organisation*. +- `B-LOC`/`I-LOC` signifie que le mot correspond au début/est à l'intérieur d'une entité *location*. +- `B-MISC`/`I-MISC` signifie que le mot correspond au début/est à l'intérieur d'une entité *divers*. Maintenant, le décodage des étiquettes que nous avons vues précédemment nous donne ceci : @@ -159,18 +159,18 @@ print(line2) 'B-ORG O B-MISC O O O B-MISC O O' ``` -Et pour un exemple mélangeant les étiquettes `B-` et `I-`, voici ce que le même code nous donne sur l'élément de l'ensemble d'entraînement à l'indice 4 : +Et pour un exemple mélangeant les étiquettes `B-` et `I-`, voici ce que le même code nous donne sur le quatrième élément du jeu d'entraînement : ```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' ``` -Comme on peut le voir, les entités couvrant deux mots, comme "Union européenne" et "Werner Zwingmann", se voient attribuer une étiquette "B-" pour le premier mot et une étiquette "I-" pour le second. +Comme on peut le voir, les entités couvrant deux mots, comme « European Union » et « Werner Zwingmann », se voient attribuer une étiquette `B-` pour le premier mot et une étiquette `I-` pour le second. -✏️ *Votre tour !* Affichez les deux mêmes phrases avec leurs étiquettes POS ou *chunking*. +✏️ *A votre tour !* Affichez les deux mêmes phrases avec leurs étiquettes POS ou *chunking*. @@ -178,9 +178,9 @@ Comme on peut le voir, les entités couvrant deux mots, comme "Union européenne -Comme d'habitude, nos textes doivent être convertis en identifiants de *tokens* avant que le modèle puisse leur donner un sens. Comme nous l'avons vu dans le [Chapitre 6](/course/fr/chapter6/), une grande différence dans le cas des tâches de classification de *tokens* est que nous avons des entrées pré-tokénisées. Heureusement, l'API tokenizer peut gérer cela assez facilement ; nous devons juste avertir le `tokenizer` avec un drapeau spécial. +Comme d'habitude, nos textes doivent être convertis en identifiants de *tokens* avant que le modèle puisse leur donner un sens. Comme nous l'avons vu au [chapitre 6](/course/fr/chapter6/), une grande différence dans le cas des tâches de classification de *tokens* est que nous avons des entrées prétokénisées. Heureusement, l'API `tokenizer` peut gérer cela assez facilement. Nous devons juste avertir le `tokenizer` avec un drapeau spécial. -Pour commencer, nous allons créer notre objet `tokenizer`. Comme nous l'avons dit précédemment, nous allons utiliser un modèle pré-entraîné BERT, donc nous allons commencer par télécharger et mettre en cache le tokenizer associé : +Pour commencer, nous allons créer notre objet `tokenizer`. Comme nous l'avons dit précédemment, nous allons utiliser un modèle BERT pré-entraîné, donc nous allons commencer par télécharger et mettre en cache le *tokenizer* associé : ```python from transformers import AutoTokenizer @@ -189,7 +189,7 @@ model_checkpoint = "bert-base-cased" tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) ``` -Vous pouvez remplacer le `model_checkpoint` par tout autre modèle que vous préférez à partir du [*Hub*]https://huggingface.co/models), ou par un dossier local dans lequel vous avez sauvegardé un modèle pré-entraîné et un *tokenizer*. La seule contrainte est que le *tokenizer* doit être soutenu par la bibliothèque 🤗 *Tokenizers*, il y a donc une version "rapide" disponible. Vous pouvez voir toutes les architectures qui ont une version rapide dans [ce grand tableau](https://huggingface.co/transformers/#supported-frameworks), et pour vérifier que l'objet `tokenizer` que vous utilisez est bien soutenu par 🤗 *Tokenizers* vous pouvez regarder son attribut `is_fast` : +Vous pouvez remplacer le `model_checkpoint` par tout autre modèle que vous préférez à partir du [*Hub*](https://huggingface.co/models), ou par un dossier local dans lequel vous avez sauvegardé un modèle pré-entraîné et un *tokenizer*. La seule contrainte est que le *tokenizer* doit être soutenu par la bibliothèque 🤗 *Tokenizers*. Il y a donc une version rapide disponible. Vous pouvez voir toutes les architectures qui ont une version rapide dans [ce tableau](https://huggingface.co/transformers/#supported-frameworks), et pour vérifier que l'objet `tokenizer` que vous utilisez est bien soutenu par 🤗 *Tokenizers* vous pouvez regarder son attribut `is_fast` : ```py tokenizer.is_fast @@ -199,7 +199,7 @@ tokenizer.is_fast True ``` -Pour tokeniser une entrée pré-tokenisée, nous pouvons utiliser notre `tokenizer` comme d'habitude et juste ajouter `is_split_into_words=True` : +Pour tokeniser une entrée prétokenisée, nous pouvons utiliser notre `tokenizer` comme d'habitude et juste ajouter `is_split_into_words=True` : ```py inputs = tokenizer(raw_datasets["train"][0]["tokens"], is_split_into_words=True) @@ -212,7 +212,7 @@ inputs.tokens() Comme on peut le voir, le *tokenizer* a ajouté les *tokens* spéciaux utilisés par le modèle (`[CLS]` au début et `[SEP]` à la fin) et n'a pas touché à la plupart des mots. Le mot `lamb`, cependant, a été tokenisé en deux sous-mots, `la` et `##mb`. Cela introduit un décalage entre nos entrées et les étiquettes : la liste des étiquettes n'a que 9 éléments, alors que notre entrée a maintenant 12 *tokens*. Il est facile de tenir compte des *tokens* spéciaux (nous savons qu'ils sont au début et à la fin), mais nous devons également nous assurer que nous alignons toutes les étiquettes avec les mots appropriés. -Heureusement, comme nous utilisons un *tokenizer* rapide, nous avons accès aux superpouvoirs des 🤗 *Tokenizers*, ce qui signifie que nous pouvons facilement faire correspondre chaque *token* au mot correspondant (comme on le voit au [Chapitre 6](/course/fr/chapter6/3)) : +Heureusement, comme nous utilisons un *tokenizer* rapide, nous avons accès aux superpouvoirs des 🤗 *Tokenizers*, ce qui signifie que nous pouvons facilement faire correspondre chaque *token* au mot correspondant (comme on le voit au [chapitre 6](/course/fr/chapter6/3)) : ```py inputs.word_ids() @@ -222,7 +222,7 @@ inputs.word_ids() [None, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, None] ``` -Avec un peu de travail, nous pouvons alors étendre notre liste d'étiquettes pour qu'elle corresponde aux *tokens*. La première règle que nous allons appliquer est que les *tokens* spéciaux reçoivent une étiquette de `-100`. En effet, par défaut, `-100` est un indice qui est ignoré dans la fonction de perte que nous allons utiliser (entropie croisée). Ensuite, chaque *token* reçoit la même étiquette que le *token* qui a commencé le mot dans lequel il se trouve, puisqu'ils font partie de la même entité. Pour les *tokens* à l'intérieur d'un mot mais pas au début, nous remplaçons le `B-` par `I-` (puisque le *token* ne commence pas l'entité) : +Avec un peu de travail, nous pouvons étendre notre liste d'étiquettes pour qu'elle corresponde aux *tokens*. La première règle que nous allons appliquer est que les *tokens* spéciaux reçoivent une étiquette de `-100`. En effet, par défaut, `-100` est un indice qui est ignoré dans la fonction de perte que nous allons utiliser (l'entropie croisée). Ensuite, chaque *token* reçoit la même étiquette que le *token* qui a commencé le mot dans lequel il se trouve puisqu'ils font partie de la même entité. Pour les *tokens* à l'intérieur d'un mot mais pas au début, nous remplaçons le `B-` par `I-` (puisque le *token* ne commence pas l'entité) : ```python def align_labels_with_tokens(labels, word_ids): @@ -230,17 +230,17 @@ def align_labels_with_tokens(labels, word_ids): current_word = None for word_id in word_ids: if word_id != current_word: - # Start of a new word! + # Début d'un nouveau mot ! current_word = word_id label = -100 if word_id is None else labels[word_id] new_labels.append(label) elif word_id is None: - # Special token + # Token spécial new_labels.append(-100) else: - # Same word as previous token + # Même mot que le token précédent label = labels[word_id] - # If the label is B-XXX we change it to I-XXX + # Si l'étiquette est B-XXX, nous la changeons en I-XXX if label % 2 == 1: label += 1 new_labels.append(label) @@ -262,14 +262,14 @@ print(align_labels_with_tokens(labels, word_ids)) [-100, 3, 0, 7, 0, 0, 0, 7, 0, 0, 0, -100] ``` -Comme nous pouvons le voir, notre fonction a ajouté le `-100` pour les deux *tokens* spéciaux au début et à la fin, et un nouveau `0` pour notre mot qui a été divisé en deux *tokens*. +Comme nous pouvons le voir, notre fonction a ajouté `-100` pour les deux *tokens* spéciaux du début et de fin, et un nouveau `0` pour notre mot qui a été divisé en deux *tokens*. -✏️ *Votre tour !* Certains chercheurs préfèrent n'attribuer qu'un seul label par mot, et attribuer `-100` aux autres sous-*tokens* dans un mot donné. Ceci afin d'éviter que les longs mots qui se divisent en plusieurs batchs ne contribuent fortement à la perte. Changez la fonction précédente pour aligner les étiquettes avec les ID d'entrée en suivant cette règle. +✏️ *A votre tour !* Certains chercheurs préfèrent n'attribuer qu'une seule étiquette par mot et attribuer `-100` aux autres sous-*tokens* dans un mot donné. Ceci afin d'éviter que les longs mots qui se divisent en plusieurs batchs ne contribuent fortement à la perte. Changez la fonction précédente pour aligner les étiquettes avec les identifiants d'entrée en suivant cette règle. -Pour prétraiter notre ensemble de données, nous devons tokeniser toutes les entrées et appliquer `align_labels_with_tokens()` sur toutes les étiquettes. Pour profiter de la vitesse de notre *tokenizer* rapide, il est préférable de tokeniser beaucoup de textes en même temps, donc nous allons écrire une fonction qui traite une liste d'exemples et utiliser la méthode `Dataset.map()` avec l'option `batched=True`. La seule chose qui diffère de notre exemple précédent est que la fonction `word_ids()` a besoin de récupérer l'index de l'exemple dont nous voulons les IDs de mots lorsque les entrées du *tokenizer* sont des listes de textes (ou dans notre cas, des listes de mots), donc nous l'ajoutons aussi : +Pour prétraiter notre jeu de données, nous devons tokeniser toutes les entrées et appliquer `align_labels_with_tokens()` sur toutes les étiquettes. Pour profiter de la vitesse de notre *tokenizer* rapide, il est préférable de tokeniser beaucoup de textes en même temps. Nous allons donc écrire une fonction qui traite une liste d'exemples et utiliser la méthode `Dataset.map()` avec l'option `batched=True`. La seule chose qui diffère de notre exemple précédent est que la fonction `word_ids()` a besoin de récupérer l'index de l'exemple dont nous voulons les identifiants de mots lorsque les entrées du *tokenizer* sont des listes de textes (ou dans notre cas, des listes de mots), donc nous l'ajoutons aussi : ```py def tokenize_and_align_labels(examples): @@ -286,7 +286,7 @@ def tokenize_and_align_labels(examples): return tokenized_inputs ``` -Notez que nous n'avons pas encore paddé nos entrées ; nous le ferons plus tard, lors de la création des lots avec un collateur de données. +Notez que nous n'avons pas encore rembourré nos entrées. Nous le ferons plus tard lors de la création des batchs avec un collateur de données. Nous pouvons maintenant appliquer tout ce prétraitement en une seule fois sur les autres divisions de notre jeu de données : @@ -298,26 +298,26 @@ tokenized_datasets = raw_datasets.map( ) ``` -Nous avons fait la partie la plus difficile ! Maintenant que les données ont été prétraitées, l'entraînement réel ressemblera beaucoup à ce que nous avons fait dans le [Chapitre 3](/course/fr/chapter3). +Nous avons fait la partie la plus difficile ! Maintenant que les données ont été prétraitées, l'entraînement ressemblera beaucoup à ce que nous avons fait dans le [chapitre 3](/course/fr/chapter3). {#if fw === 'pt'} -## *Finetuning* du modèle avec l'API `Trainer`. +## Finetuning du modèle avec l'API `Trainer` -Le code actuel utilisant le `Trainer` sera le même que précédemment ; les seuls changements sont la façon dont les données sont rassemblées dans un batch et la fonction de calcul de la métrique. +Le code utilisant `Trainer` sera le même que précédemment. Les seuls changements sont la façon dont les données sont rassemblées dans un batch ainsi que la fonction de calcul de la métrique. {:else} -## *Finetuning* fin du modèle avec Keras +## Finetuning du modèle avec Keras -Le code réel utilisant Keras sera très similaire au précédent ; les seuls changements sont la façon dont les données sont rassemblées dans un batch et la fonction de calcul de la métrique. +Le code utilisant Keras sera très similaire au précédent. Les seuls changements sont la façon dont les données sont rassemblées dans un batch ainsi que la fonction de calcul de la métrique. {/if} ### Collation des données -Nous ne pouvons pas simplement utiliser un `DataCollatorWithPadding` comme dans [Chapter 3](/course/fr/chapter3) parce que cela ne fait que rembourrer les entrées (IDs d'entrée, masque d'attention, et IDs de type de *token*). Ici, nos étiquettes doivent être remplies exactement de la même manière que les entrées afin qu'elles gardent la même taille, en utilisant `-100` comme valeur afin que les prédictions correspondantes soient ignorées dans le calcul de la perte. +Nous ne pouvons pas simplement utiliser un `DataCollatorWithPadding` comme dans le [chapitre 3](/course/fr/chapter3) car cela ne fait que rembourrer les entrées (identifiants d'entrée, masque d'attention et *token* de type identifiants). Ici, nos étiquettes doivent être rembourréés exactement de la même manière que les entrées afin qu'elles gardent la même taille, en utilisant `-100` comme valeur afin que les prédictions correspondantes soient ignorées dans le calcul de la perte. Tout ceci est fait par un [`DataCollatorForTokenClassification`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorfortokenclassification). Comme le `DataCollatorWithPadding`, il prend le `tokenizer` utilisé pour prétraiter les entrées : @@ -367,7 +367,7 @@ for i in range(2): {#if fw === 'pt'} -Comme nous pouvons le voir, le deuxième jeu d'étiquettes a été complété à la longueur du premier en utilisant `-100`s. +Comme nous pouvons le voir, le deuxième jeu d'étiquettes a été complété à la longueur du premier en utilisant des `-100`. {:else} @@ -390,7 +390,7 @@ tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( ``` - Prochain arrêt : le modèle lui-même. +Prochain arrêt : le modèle lui-même. {/if} @@ -398,16 +398,16 @@ tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( ### Définir le modèle -Puisque nous travaillons sur un problème de classification de *tokens*, nous allons utiliser la classe `TFAutoModelForTokenClassification`. La principale chose à retenir lors de la définition de ce modèle est de transmettre des informations sur le nombre de labels que nous avons. La façon la plus simple de le faire est de passer ce nombre avec l'argument `num_labels`, mais si nous voulons un joli *widget* d'inférence fonctionnant comme celui que nous avons vu au début de cette section, il est préférable de définir les correspondances correctes des étiquettes à la place. +Puisque nous travaillons sur un problème de classification de *tokens*, nous allons utiliser la classe `TFAutoModelForTokenClassification`. La principale chose à retenir lors de la définition de ce modèle est de transmettre des informations sur le nombre d'étiquettes que nous avons. La façon la plus simple de le faire est de passer ce nombre avec l'argument `num_labels`, mais si nous voulons un joli *widget* d'inférence fonctionnant comme celui que nous avons vu au début de cette section, il est préférable de définir les correspondances correctes des étiquettes à la place. -Elles devraient être définies par deux dictionnaires, `id2label` et `label2id`, qui contiennent la correspondance de l'ID au label et vice versa : +Elles devraient être définies par deux dictionnaires, `id2label` et `label2id`, qui contiennent la correspondance de l'identifiant à l'étiquette et vice versa : ```py id2label = {str(i): label for i, label in enumerate(label_names)} label2id = {v: k for k, v in id2label.items()} ``` -Maintenant, nous pouvons simplement les passer à la méthode `TFAutoModelForTokenClassification.from_pretrained()`, et ils seront définis dans la configuration du modèle, puis correctement enregistrés et téléchargés vers le *Hub* : +Maintenant, nous pouvons simplement les passer à la méthode `TFAutoModelForTokenClassification.from_pretrained()`, et ils seront définis dans la configuration du modèle puis correctement enregistrés et téléchargés vers le *Hub* : ```py from transformers import TFAutoModelForTokenClassification @@ -419,7 +419,7 @@ model = TFAutoModelForTokenClassification.from_pretrained( ) ``` -Comme lorsque nous avons défini notre `TFAutoModelForSequenceClassification` au [Chapitre 3](/course/fr/chapter3), la création du modèle émet un avertissement indiquant que certains poids n'ont pas été utilisés (ceux de la tête de pré-entraînement) et que d'autres poids ont été initialisés de manière aléatoire (ceux de la tête de classification des nouveaux *tokens*), et que ce modèle doit être entraîné. Nous ferons cela dans une minute, mais vérifions d'abord que notre modèle a le bon nombre d'étiquettes : +Comme lorsque nous avons défini notre `TFAutoModelForSequenceClassification` au [chapitre 3](/course/fr/chapter3), la création du modèle émet un avertissement indiquant que certains poids n'ont pas été utilisés (ceux de la tête de pré-entraînement) et que d'autres poids ont été initialisés de manière aléatoire (ceux de la tête de classification des nouveaux *tokens*), et que ce modèle doit être entraîné. Nous ferons cela dans une minute mais vérifions d'abord que notre modèle a le bon nombre d'étiquettes : ```python model.config.num_labels @@ -431,11 +431,11 @@ model.config.num_labels -⚠️ Si vous avez un modèle avec le mauvais nombre de labels, vous obtiendrez une erreur obscure en appelant `model.fit()` plus tard. Cela peut être ennuyeux à déboguer, donc assurez-vous de faire cette vérification pour confirmer que vous avez le nombre de labels attendu. +⚠️ Si vous avez un modèle avec le mauvais nombre d'étiquettes, vous obtiendrez plus tard une erreur obscure lors de l'appel de `model.fit()`. Cela peut être ennuyeux à déboguer donc assurez-vous de faire cette vérification pour confirmer que vous avez le nombre d'étiquettes attendu. -### *Finetuning* du modèle +### Finetuning du modèle Nous sommes maintenant prêts à entraîner notre modèle ! Mais nous devons d'abord faire un peu de ménage : nous devons nous connecter à Hugging Face et définir nos hyperparamètres d'entraînement. Si vous travaillez dans un *notebook*, il y a une fonction pratique pour vous aider à le faire : @@ -453,7 +453,7 @@ Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante huggingface-cli login ``` -Après s'être connecté, nous pouvons préparer tout ce dont nous avons besoin pour compiler notre modèle. 🤗 *Transformers* fournit une fonction pratique `create_optimizer()` qui vous donnera un optimiseur `AdamW` avec des paramètres appropriés pour la décroissance du taux des poids et la décroissance du taux d'apprentissage, les deux améliorant les performances de votre modèle par rapport à l'optimiseur `Adam` intégré : +Après s'être connecté, nous pouvons préparer tout ce dont nous avons besoin pour compiler notre modèle. 🤗 *Transformers* fournit une fonction pratique `create_optimizer()` qui vous donnera un optimiseur `AdamW` avec des paramètres appropriés pour le taux de décroissance des poids et le taux de décroissance de l'apprentissage, les deux améliorant les performances de votre modèle par rapport à l'optimiseur `Adam` : ```python from transformers import create_optimizer @@ -495,7 +495,7 @@ model.fit( ) ``` -Vous pouvez spécifier le nom complet du référentiel vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/bert-finetuned-ner"`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, par exemple `"cool_huggingface_user/bert-finetuned-ner"`. +Vous pouvez spécifier le nom complet du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/bert-finetuned-ner"`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, par exemple `"cool_huggingface_user/bert-finetuned-ner"`. @@ -514,25 +514,25 @@ A ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* pour test {#if fw === 'pt'} -Pour que le `Trainer` calcule une métrique à chaque époque, nous devrons définir une fonction `compute_metrics()` qui prend les tableaux de prédictions et de labels, et retourne un dictionnaire avec les noms et les valeurs des métriques. +Pour que le `Trainer` calcule une métrique à chaque époque, nous devrons définir une fonction `compute_metrics()` qui prend les tableaux de prédictions et d'étiquettes, et retourne un dictionnaire avec les noms et les valeurs des métriques. -Le cadre traditionnel utilisé pour évaluer la prédiction de la classification des *tokens* est [*seqeval*](https://github.com/chakki-works/seqeval). Pour utiliser cette métrique, nous devons d'abord installer la bibliothèque *seqeval* : +Le *framework* traditionnel utilisé pour évaluer la prédiction de la classification des *tokens* est [*seqeval*](https://github.com/chakki-works/seqeval). Pour utiliser cette métrique, nous devons d'abord installer la bibliothèque *seqeval* : ```py !pip install seqeval ``` -Nous pouvons ensuite le charger via la fonction `load_metric()` comme nous l'avons fait dans le [Chapitre 3](/course/fr/chapter3) : +Nous pouvons ensuite le charger via la fonction `load_metric()` comme nous l'avons fait dans le [chapitre 3](/course/fr/chapter3) : {:else} -Le cadre traditionnel utilisé pour évaluer la prédiction de la classification des *tokens* est [*seqeval*](https://github.com/chakki-works/seqeval). Pour utiliser cette métrique, nous devons d'abord installer la bibliothèque *seqeval* : +Le *framework* traditionnel utilisé pour évaluer la prédiction de la classification des *tokens* est [*seqeval*](https://github.com/chakki-works/seqeval). Pour utiliser cette métrique, nous devons d'abord installer la bibliothèque *seqeval* : ```py !pip install seqeval ``` -Nous pouvons ensuite le charger via la fonction `load_metric()` comme nous l'avons fait dans le [Chapitre 3](/course/fr/chapter3) : +Nous pouvons ensuite le charger via la fonction `load_metric()` comme nous l'avons fait dans le [chapitre 3](/course/fr/chapter3) : {/if} @@ -575,9 +575,9 @@ Notez que la métrique prend une liste de prédictions (pas seulement une) et un {#if fw === 'pt'} -Cela renvoie un batch d'informations ! Nous obtenons la précision, le rappel, et le score F1 pour chaque entité séparée, ainsi que le score global. Pour notre calcul de métrique, nous ne garderons que le score global, mais n'hésitez pas à modifier la fonction `compute_metrics()` pour retourner toutes les métriques que vous souhaitez. +Cela renvoie un batch d'informations ! Nous obtenons la précision, le rappel et le score F1 pour chaque entité séparée, ainsi que le score global. Pour notre calcul de métrique, nous ne garderons que le score global, mais n'hésitez pas à modifier la fonction `compute_metrics()` pour retourner toutes les métriques que vous souhaitez. -Cette fonction `compute_metrics()` prend d'abord l'argmax des logits pour les convertir en prédictions (comme d'habitude, les logits et les probabilités sont dans le même ordre, donc nous n'avons pas besoin d'appliquer le softmax). Ensuite, nous devons convertir les étiquettes et les prédictions des entiers en chaînes de caractères. Nous supprimons toutes les valeurs dont l'étiquette est `-100`, puis nous passons les résultats à la méthode `metric.compute()` : +Cette fonction `compute_metrics()` prend d'abord l'argmax des logits pour les convertir en prédictions (comme d'habitude, les logits et les probabilités sont dans le même ordre, donc nous n'avons pas besoin d'appliquer la fonction softmax). Ensuite, nous devons convertir les étiquettes et les prédictions des entiers en chaînes de caractères. Nous supprimons toutes les valeurs dont l'étiquette est `-100`, puis nous passons les résultats à la méthode `metric.compute()` : ```py import numpy as np @@ -602,13 +602,13 @@ def compute_metrics(eval_preds): } ``` -Maintenant que ceci est fait, nous sommes presque prêts à définir notre `Trainer`. Nous avons juste besoin d'un `modèle` pour *finetuner* ! +Maintenant que ceci est fait, nous sommes presque prêts à définir notre `Trainer`. Nous avons juste besoin d'un objet `model` pour *finetuner* ! {:else} Cela renvoie un batch d'informations ! Nous obtenons la précision, le rappel et le score F1 pour chaque entité séparée, ainsi que pour l'ensemble. Voyons maintenant ce qui se passe si nous essayons d'utiliser les prédictions de notre modèle pour calculer des scores réels. -TensorFlow n'aime pas concaténer nos prédictions ensemble, car elles ont des longueurs de séquence variables. Cela signifie que nous ne pouvons pas simplement utiliser `model.predict()`. Mais cela ne va pas nous arrêter. Nous obtiendrons des prédictions un batch à la fois et les concaténerons en une grande liste longue au fur et à mesure, en laissant tomber les *tokens* `-100` qui indiquent le masquage/le remplissage, puis nous calculerons les métriques sur la liste à la fin : +TensorFlow n'aime pas concaténer nos prédictions ensemble car elles ont des longueurs de séquence variables. Cela signifie que nous ne pouvons pas simplement utiliser `model.predict()`. Mais cela ne va pas nous arrêter. Nous obtiendrons des prédictions un batch à la fois et les concaténerons en une grande liste longue au fur et à mesure et en laissant de côté les *tokens* `-100` qui indiquent le masquage/le remplissage. Puis nous calculerons les métriques sur la liste à la fin : ```py import numpy as np @@ -648,16 +648,16 @@ Comment s'est comporté votre modèle, comparé au nôtre ? Si vous avez obtenu ### Définir le modèle -Puisque nous travaillons sur un problème de classification de *tokens*, nous allons utiliser la classe `AutoModelForTokenClassification`. La principale chose à retenir lors de la définition de ce modèle est de transmettre des informations sur le nombre de labels que nous avons. La façon la plus simple de le faire est de passer ce nombre avec l'argument `num_labels`, mais si nous voulons un joli *widget* d'inférence fonctionnant comme celui que nous avons vu au début de cette section, il est préférable de définir les correspondances correctes des étiquettes à la place. +Puisque nous travaillons sur un problème de classification de *tokens*, nous allons utiliser la classe `AutoModelForTokenClassification`. La principale chose à retenir lors de la définition de ce modèle est de transmettre des informations sur le nombre d'étiquettes que nous avons. La façon la plus simple de le faire est de passer ce nombre avec l'argument `num_labels`, mais si nous voulons un joli *widget* d'inférence fonctionnant comme celui que nous avons vu au début de cette section, il est préférable de définir les correspondances des étiquettes à la place. -Elles devraient être définies par deux dictionnaires, `id2label` et `label2id`, qui contiennent les correspondances entre ID et label et vice versa : +Elles devraient être définies par deux dictionnaires, `id2label` et `label2id`, qui contiennent les correspondances entre identifiants et étiquettes et vice versa : ```py id2label = {str(i): label for i, label in enumerate(label_names)} label2id = {v: k for k, v in id2label.items()} ``` -Maintenant nous pouvons simplement les passer à la méthode `AutoModelForTokenClassification.from_pretrained()`, et ils seront définis dans la configuration du modèle et ensuite correctement sauvegardés et téléchargés vers le *Hub* : +Maintenant nous pouvons simplement les passer à la méthode `AutoModelForTokenClassification.from_pretrained()`, ils seront définis dans la configuration du modèle puis correctement sauvegardés et téléchargés vers le *Hub* : ```py from transformers import AutoModelForTokenClassification @@ -669,7 +669,7 @@ model = AutoModelForTokenClassification.from_pretrained( ) ``` -Comme lorsque nous avons défini notre `AutoModelForSequenceClassification` au [Chapitre 3](/course/fr/chapter3), la création du modèle émet un avertissement indiquant que certains poids n'ont pas été utilisés (ceux de la tête de pré-entraînement) et que d'autres poids ont été initialisés de manière aléatoire (ceux de la tête de classification des nouveaux *tokens*), et que ce modèle doit être entraîné. Nous ferons cela dans une minute, mais vérifions d'abord que notre modèle a le bon nombre d'étiquettes : +Comme lorsque nous avons défini notre `AutoModelForSequenceClassification` au [chapitre 3](/course/fr/chapter3), la création du modèle émet un avertissement indiquant que certains poids n'ont pas été utilisés (ceux de la tête de pré-entraînement) et que d'autres poids ont été initialisés de manière aléatoire (ceux de la tête de classification des nouveaux *tokens*), et que ce modèle doit être entraîné. Nous ferons cela dans une minute, mais vérifions d'abord que notre modèle a le bon nombre d'étiquettes : ```python model.config.num_labels @@ -681,11 +681,11 @@ model.config.num_labels -⚠️ Si vous avez un modèle avec le mauvais nombre d'étiquettes, vous obtiendrez une erreur obscure lors de l'appel de la méthode `Trainer.train()` plus tard (quelque chose comme "CUDA error : device-side assert triggered"). C'est la première cause de bogues signalés par les utilisateurs pour de telles erreurs, donc assurez-vous de faire cette vérification pour confirmer que vous avez le nombre d'étiquettes attendu. +⚠️ Si vous avez un modèle avec le mauvais nombre d'étiquettes, vous obtiendrez une erreur obscure lors de l'appel de la méthode `Trainer.train()` (quelque chose comme "CUDA error : device-side assert triggered"). C'est la première cause de bogues signalés par les utilisateurs pour de telles erreurs, donc assurez-vous de faire cette vérification pour confirmer que vous avez le nombre d'étiquettes attendu. -### *Finetuning* du modèle +### Finetuning du modèle Nous sommes maintenant prêts à entraîner notre modèle ! Nous devons juste faire deux dernières choses avant de définir notre `Trainer` : se connecter à Hugging Face et définir nos arguments d'entraînement. Si vous travaillez dans un *notebook*, il y a une fonction pratique pour vous aider à le faire : @@ -719,7 +719,7 @@ args = TrainingArguments( ) ``` -Vous avez déjà vu la plupart d'entre eux : nous définissons quelques hyperparamètres (comme le taux d'apprentissage, le nombre d'époques à entraîner, et la décroissance du poids), et nous spécifions `push_to_hub=True` pour indiquer que nous voulons sauvegarder le modèle et l'évaluer à la fin de chaque époque, et que nous voulons télécharger nos résultats vers le *Hub*. Notez que vous pouvez spécifier le nom du référentiel vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/bert-finetuned-ner"``TrainingArguments`. Par défaut, le référentiel utilisé sera dans votre espace de noms et nommé d'après le répertoire de sortie que vous avez défini, donc dans notre cas ce sera `"sgugger/bert-finetuned-ner"`. +Vous avez déjà vu la plupart d'entre eux. Nous définissons quelques hyperparamètres (comme le taux d'apprentissage, le nombre d'époques à entraîner, et le taux de décroissance des poids), et nous spécifions `push_to_hub=True` pour indiquer que nous voulons sauvegarder le modèle, l'évaluer à la fin de chaque époque, et que nous voulons télécharger nos résultats vers le *Hub*. Notez que vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/bert-finetuned-ner"``TrainingArguments`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé d'après le répertoire de sortie que vous avez défini, donc dans notre cas ce sera `"sgugger/bert-finetuned-ner"`. @@ -764,11 +764,11 @@ Si vous voulez plonger un peu plus profondément dans la boucle d'entraînement, ## Une boucle d'entraînement personnalisée -Jetons maintenant un coup d'œil à la boucle d'entraînement complète, afin que vous puissiez facilement personnaliser les parties dont vous avez besoin. Elle ressemblera beaucoup à ce que nous avons fait dans le [Chapitre 3](/course/fr/chapter3/4), avec quelques changements pour l'évaluation. +Jetons maintenant un coup d'œil à la boucle d'entraînement complète afin que vous puissiez facilement personnaliser les parties dont vous avez besoin. Elle ressemblera beaucoup à ce que nous avons fait dans le [chapitre 3](/course/fr/chapter3/4) avec quelques changements pour l'évaluation. ### Préparer tout pour l'entraînement -D'abord nous devons construire le `DataLoader`s à partir de nos jeux de données. Nous allons réutiliser notre `data_collator` comme un `collate_fn` et mélanger l'ensemble d'entraînement, mais pas l'ensemble de validation : +D'abord nous devons construire le `DataLoader`s à partir de nos jeux de données. Nous réutilisons notre `data_collator` comme un `collate_fn` et mélanger l'ensemble d'entraînement, mais pas l'ensemble de validation : ```py from torch.utils.data import DataLoader @@ -784,7 +784,7 @@ eval_dataloader = DataLoader( ) ``` -Ensuite, nous réinstantifions notre modèle, pour nous assurer que nous ne continuons pas le réglage fin d'avant, mais que nous repartons du modèle pré-entraîné de BERT : +Ensuite, nous réinstantifions notre modèle pour nous assurer que nous ne continuons pas le *finetuning* d'avant et que nous repartons bien du modèle pré-entraîné de BERT : ```py model = AutoModelForTokenClassification.from_pretrained( @@ -794,7 +794,7 @@ model = AutoModelForTokenClassification.from_pretrained( ) ``` -Ensuite, nous aurons besoin d'un optimiseur. Nous allons utiliser le classique `AdamW`, qui est comme `Adam`, mais avec un correctif dans la façon dont la décroissance du taux des poids est appliquée : +Ensuite, nous avons besoin d'un optimiseur. Nous utilisons le classique `AdamW`, qui est comme `Adam`, mais avec un correctif dans la façon dont le taux de décroissance des poids est appliquée : ```py from torch.optim import AdamW @@ -815,11 +815,11 @@ model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( -🚨 Si vous vous entraînez sur un TPU, vous devrez déplacer tout le code à partir de la cellule ci-dessus dans une fonction d'entraînement dédiée. Voir le [Chapitre 3](/course/fr/chapter3) pour plus de détails. +🚨 Si vous entraînez sur un TPU, vous devrez déplacer tout le code à partir de la cellule ci-dessus dans une fonction d'entraînement dédiée. Voir le [chapitre 3](/course/fr/chapter3) pour plus de détails. -Maintenant que nous avons envoyé notre `train_dataloader` à `accelerator.prepare()`, nous pouvons utiliser sa longueur pour calculer le nombre d'étapes d'entraînement. Rappelez-vous que nous devrions toujours faire cela après avoir préparé le *dataloader*, car cette méthode modifiera sa longueur. Nous utilisons un programme linéaire classique du taux d'apprentissage à 0 : +Maintenant que nous avons envoyé notre `train_dataloader` à `accelerator.prepare()`, nous pouvons utiliser sa longueur pour calculer le nombre d'étapes d'entraînement. Rappelez-vous que nous devrions toujours faire cela après avoir préparé le *dataloader* car cette méthode modifiera sa longueur. Nous utilisons un programme linéaire classique du taux d'apprentissage à 0 : ```py from transformers import get_scheduler @@ -836,7 +836,7 @@ lr_scheduler = get_scheduler( ) ``` -Enfin, pour pousser notre modèle vers le Hub, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous à Hugging Face, si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'ID du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix ; il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `get_full_repo_name()`) : +Enfin, pour pousser notre modèle vers le *Hub*, nous avons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous à Hugging Face si vous n'êtes pas déjà connecté. Nous déterminons le nom du dépôt à partir de l'identifiant du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix, il doit juste contenir votre nom d'utilisateur et ce que fait la fonction `get_full_repo_name()`) : ```py from huggingface_hub import Repository, get_full_repo_name @@ -850,7 +850,7 @@ repo_name 'sgugger/bert-finetuned-ner-accelerate' ``` -Ensuite, nous pouvons cloner ce référentiel dans un dossier local. S'il existe déjà, ce dossier local doit être un clone existant du référentiel avec lequel nous travaillons : +Ensuite, nous pouvons cloner ce dépôt dans un dossier local. S'il existe déjà, ce dossier local doit être un clone existant du dépôt avec lequel nous travaillons : ```py output_dir = "bert-finetuned-ner-accelerate" @@ -861,14 +861,14 @@ Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output ### Boucle d'entraînement -Nous sommes maintenant prêts à écrire la boucle d'entraînement complète. Pour simplifier sa partie évaluation, nous définissons cette fonction `postprocess()` qui prend les prédictions et les étiquettes et les convertit en listes de chaînes de caractères, comme notre objet `metric` l'attend : +Nous sommes maintenant prêts à écrire la boucle d'entraînement complète. Pour simplifier sa partie évaluation, nous définissons cette fonction `postprocess()` qui prend les prédictions et les étiquettes, et les convertit en listes de chaînes de caractères comme notre objet `metric` l'attend : ```py def postprocess(predictions, labels): predictions = predictions.detach().cpu().clone().numpy() labels = labels.detach().cpu().clone().numpy() - # Remove ignored index (special tokens) and convert to labels + # Suppression de l'index ignoré (tokens spéciaux) et conversion en étiquettes 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] @@ -879,9 +879,9 @@ def postprocess(predictions, labels): Ensuite, nous pouvons écrire la boucle d'entraînement. Après avoir défini une barre de progression pour suivre l'évolution de l'entraînement, la boucle comporte trois parties : -- l'entraînement proprement dit, qui est l'itération classique sur le `train_dataloader`, passage en avant du modèle, puis passage en arrière et étape d'optimisation, -- l'évaluation, dans laquelle il y a une nouveauté après avoir obtenu les sorties de notre modèle sur un lot : puisque deux processus peuvent avoir paddé les entrées et les étiquettes à des formes différentes, nous devons utiliser `accelerator.pad_across_processes()` pour rendre les prédictions et les étiquettes de la même forme avant d'appeler la méthode `gather()`. Si nous ne le faisons pas, l'évaluation va soit se tromper, soit se bloquer pour toujours. Ensuite, nous envoyons les résultats à `metric.add_batch()` et appelons `metric.compute()` une fois que la boucle d'évaluation est terminée, -- sauvegarde et téléchargement, où nous sauvegardons d'abord le modèle et le tokenizer, puis appelons `repo.push_to_hub()`. Remarquez que nous utilisons l'argument `blocking=False` pour indiquer à la bibliothèque 🤗 *Hub* de pousser dans un processus asynchrone. De cette façon, l'entraînement continue normalement et cette (longue) instruction est exécutée en arrière-plan. +- L'entraînement proprement dit, qui est l'itération classique sur le `train_dataloader`, passage en avant, puis passage en arrière et étape d'optimisation. +- L'évaluation, dans laquelle il y a une nouveauté après avoir obtenu les sorties de notre modèle sur un batch : puisque deux processus peuvent avoir paddé les entrées et les étiquettes à des formes différentes, nous devons utiliser `accelerator.pad_across_processes()` pour rendre les prédictions et les étiquettes de la même forme avant d'appeler la méthode `gather()`. Si nous ne le faisons pas, l'évaluation va soit se tromper, soit se bloquer pour toujours. Ensuite, nous envoyons les résultats à `metric.add_batch()` et appelons `metric.compute()` une fois que la boucle d'évaluation est terminée. +- Sauvegarde et téléchargement, où nous sauvegardons d'abord le modèle et le *tokenizer*, puis appelons `repo.push_to_hub()`. Remarquez que nous utilisons l'argument `blocking=False` pour indiquer à la bibliothèque 🤗 *Hub* de pousser dans un processus asynchrone. De cette façon, l'entraînement continue normalement et cette (longue) instruction est exécutée en arrière-plan. Voici le code complet de la boucle d'entraînement : @@ -951,15 +951,15 @@ unwrapped_model = accelerator.unwrap_model(model) unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) ``` -La première ligne est explicite : elle indique à tous les processus d'attendre que tout le monde soit à ce stade avant de continuer. C'est pour s'assurer que nous avons le même modèle dans chaque processus avant de sauvegarder. Ensuite, nous prenons le `unwrapped_model`, qui est le modèle de base que nous avons défini. La méthode `accelerator.prepare()` modifie le modèle pour qu'il fonctionne dans l'entraînement distribué, donc il n'aura plus la méthode `save_pretrained()` ; la méthode `accelerator.unwrap_model()` annule cette étape. Enfin, nous appelons `save_pretrained()` mais nous disons à cette méthode d'utiliser `accelerator.save()` au lieu de `torch.save()`. +La première ligne est explicite : elle indique à tous les processus d'attendre que tout le monde soit à ce stade avant de continuer. C'est pour s'assurer que nous avons le même modèle dans chaque processus avant de sauvegarder. Ensuite, nous prenons le `unwrapped_model` qui est le modèle de base que nous avons défini. La méthode `accelerator.prepare()` modifie le modèle pour qu'il fonctionne dans l'entraînement distribué, donc il n'aura plus la méthode `save_pretrained()` ; la méthode `accelerator.unwrap_model()` annule cette étape. Enfin, nous appelons `save_pretrained()` mais nous disons à cette méthode d'utiliser `accelerator.save()` au lieu de `torch.save()`. -Une fois ceci fait, vous devriez avoir un modèle qui produit des résultats assez similaires à celui entraîné avec le `Trainer`. Vous pouvez vérifier le modèle que nous avons formé en utilisant ce code à [*huggingface-course/bert-finetuned-ner-accelerate*(https://huggingface.co/huggingface-course/bert-finetuned-ner-accelerate). Et si vous voulez tester des modifications de la boucle d'entraînement, vous pouvez les implémenter directement en modifiant le code ci-dessus ! +Une fois ceci fait, vous devriez avoir un modèle qui produit des résultats assez similaires à celui entraîné avec le `Trainer`. Vous pouvez vérifier le modèle que nous avons formé en utilisant ce code à [*huggingface-course/bert-finetuned-ner-accelerate*](https://huggingface.co/huggingface-course/bert-finetuned-ner-accelerate). Et si vous voulez tester des modifications de la boucle d'entraînement, vous pouvez les implémenter directement en modifiant le code ci-dessus ! {/if} -### Utilisation du modèle *finetuné* +### Utilisation du modèle finetuné -Nous vous avons déjà montré comment vous pouvez utiliser le modèle que nous avons affiné sur le *Hub* avec le *widget* d'inférence. Pour l'utiliser localement dans un `pipeline`, vous devez juste spécifier l'identifiant de modèle approprié : +Nous vous avons déjà montré comment vous pouvez utiliser le modèle *finetuné* sur le *Hub* avec le *widget* d'inférence. Pour l'utiliser localement dans un `pipeline`, vous devez juste spécifier l'identifiant de modèle approprié : ```py from transformers import pipeline @@ -979,3 +979,4 @@ token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.") ``` Super ! Notre modèle fonctionne aussi bien que le modèle par défaut pour ce pipeline ! + diff --git a/chapters/fr/chapter7/3.mdx b/chapters/fr/chapter7/3.mdx index 71c015327..738f9d59d 100644 --- a/chapters/fr/chapter7/3.mdx +++ b/chapters/fr/chapter7/3.mdx @@ -1,6 +1,6 @@ -# *Finetuner* un modèle de langage masqué +# Finetuner un modèle de langage masqué {#if fw === 'pt'} @@ -22,11 +22,11 @@ {/if} -Pour de nombreuses applications de NLP impliquant des *transformers*, vous pouvez simplement prendre un modèle pré-entraîné du *Hub* et l'ajuster directement sur vos données pour la tâche à accomplir. Pour autant que le corpus utilisé pour le pré-entraînement ne soit pas trop différent du corpus utilisé pour le *finetuning*, l'apprentissage par transfert produira généralement de bons résultats. +Pour de nombreuses applications de NLP impliquant des *transformers*, vous pouvez simplement prendre un modèle pré-entraîné du *Hub* et le *finetuner* directement sur vos données pour la tâche à accomplir. Pour autant que le corpus utilisé pour le pré-entraînement ne soit pas trop différent du corpus utilisé pour le *finetuning*. L'apprentissage par transfert produira généralement de bons résultats. -Cependant, il existe quelques cas où vous voudrez d'abord affiner les modèles de langue sur vos données, avant d'entraîner une tête spécifique à la tâche. Par exemple, si votre ensemble de données contient des contrats légaux ou des articles scientifiques, un modèle de transformation classique comme BERT traitera généralement les mots spécifiques au domaine dans votre corpus comme des *tokens* rares, et les performances résultantes peuvent être moins que satisfaisantes. En *finetunant* le modèle linguistique sur les données du domaine, vous pouvez améliorer les performances de nombreuses tâches en aval, ce qui signifie que vous ne devez généralement effectuer cette étape qu'une seule fois ! +Cependant, il existe quelques cas où vous voudrez d'abord *finetuner* les modèles de langue sur vos données, avant d'entraîner une tête spécifique à la tâche. Par exemple, si votre jeu de données contient des contrats légaux ou des articles scientifiques, un *transformer* classique comme BERT traitera généralement les mots spécifiques au domaine dans votre corpus comme des *tokens* rares et les performances résultantes peuvent être moins que satisfaisantes. En *finetunant* le modèle de langage sur les données du domaine, vous pouvez améliorer les performances de nombreuses tâches en aval, ce qui signifie que vous ne devez généralement effectuer cette étape qu'une seule fois ! -Ce processus d'ajustement fin d'un modèle de langage pré-entraîné sur des données *in-domain* est généralement appelé _adaptation au domaine_. Il a été popularisé en 2018 par [ULMFiT(https://arxiv.org/abs/1801.06146), qui a été l'une des premières architectures neuronales (basées sur les LSTM) à faire en sorte que l'apprentissage par transfert fonctionne réellement pour le NLP. Un exemple d'adaptation de domaine avec ULMFiT est présenté dans l'image ci-dessous ; dans cette section, nous ferons quelque chose de similaire, mais avec un *transformer* au lieu d'un LSTM ! +Ce processus de *finetuning* d'un modèle de langage pré-entraîné sur des données *dans le domaine* est généralement appelé _adaptation au domaine_. Il a été popularisé en 2018 par [ULMFiT](https://arxiv.org/abs/1801.06146) qui a été l'une des premières architectures neuronales (basées sur des LSTMs) à faire en sorte que l'apprentissage par transfert fonctionne réellement pour le NLP. Un exemple d'adaptation de domaine avec ULMFiT est présenté dans l'image ci-dessous. Dans cette section, nous ferons quelque chose de similaire mais avec un *transformer* au lieu d'une LSTM !
ULMFiT. @@ -38,26 +38,25 @@ Ce processus d'ajustement fin d'un modèle de langage pré-entraîné sur des do -Plongeons-y ! +Allons-y ! -🙋 Si les termes "modélisation du langage masqué" et "modèle pré-entraîné" ne vous sont pas familiers, consultez le [Chapitre 1](/course/fr/chapiter1), où nous expliquons tous ces concepts fondamentaux, vidéos à l'appui ! +🙋 Si les termes « modélisation du langage masqué » et « modèle pré-entraîné » ne vous sont pas familiers, consultez le [chapitre 1](/course/fr/chapiter1), où nous expliquons tous ces concepts fondamentaux, vidéos à l'appui ! ## Choix d'un modèle pré-entraîné pour la modélisation du langage masqué -Pour commencer, nous allons choisir un modèle pré-entraîné approprié pour la modélisation du langage masqué. Comme le montre la capture d'écran suivante, vous pouvez trouver une liste de candidats en appliquant le filtre "Fill-Mask" sur le [*Hub*] (https://huggingface.co/models?pipeline_tag=fill-mask&sort=downloads) : +Pour commencer, nous allons choisir un modèle pré-entraîné approprié pour la modélisation du langage masqué. Comme le montre la capture d'écran suivante, vous pouvez trouver une liste de candidats en appliquant le filtre « *Fill-Mask* » sur le [*Hub*](https://huggingface.co/models?pipeline_tag=fill-mask&sort=downloads) :
Hub models.
-Bien que les modèles de la famille BERT et RoBERTa soient les plus téléchargés, nous utiliserons un modèle appelé [DistilBERT](https://huggingface.co/distilbert-base-uncased) -qui peut être entraîné beaucoup plus rapidement avec peu ou pas de perte de performance en aval. Ce modèle a été entraîné à l'aide d'une technique spéciale appelée [_distillation de connaissances_](https://en.wikipedia.org/wiki/Knowledge_distillation), où un grand "modèle maître" comme BERT est utilisé pour guider l'entraînement d'un "modèle élève" qui a beaucoup moins de paramètres. Une explication des détails de la distillation de connaissances nous mènerait trop loin dans cette section, mais si vous êtes intéressé, vous pouvez lire tout cela dans [_Natural Language Processing with Transformers_](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/ch05.html) (familièrement connu comme le manuel Transformers). +Bien que les modèles de la famille BERT et RoBERTa soient les plus téléchargés, nous utiliserons un modèle appelé [DistilBERT](https://huggingface.co/distilbert-base-uncased) qui peut être entraîné beaucoup plus rapidement avec peu ou pas de perte de performance en aval. Ce modèle a été entraîné à l'aide d'une technique spéciale appelée [_distillation de connaissances_](https://en.wikipedia.org/wiki/Knowledge_distillation), où un grand modèle *enseignant* comme BERT est utilisé pour guider l'entraînement d'un modèle *étudiant* qui a beaucoup moins de paramètres. Une explication des détails de la distillation de connaissances nous mènerait trop loin dans cette section mais si vous êtes intéressé, vous pouvez lire tout cela dans le livre [_Natural Language Processing with Transformers_](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/ch05.html). {#if fw === 'pt'} @@ -74,13 +73,13 @@ Nous pouvons voir combien de paramètres ce modèle possède en appelant la mét ```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'") +print(f"'>>> DistilBERT nombre de paramètres : {round(distilbert_num_parameters)}M'") +print(f"'>>> BERT nombre de paramètres : 110M'") ``` ```python out -'>>> DistilBERT number of parameters: 67M' -'>>> BERT number of parameters: 110M' +'>>> DistilBERT nombre de paramètres : 67M' +'>>> BERT nombre de paramètres : 110M' ``` {:else} @@ -97,7 +96,7 @@ model = TFAutoModelForMaskedLM.from_pretrained(model_checkpoint) Nous pouvons voir combien de paramètres ce modèle possède en appelant la méthode `summary()` : ```python -model(model.dummy_inputs) # Build the model +model(model.dummy_inputs) # Construire le modèle model.summary() ``` @@ -122,13 +121,13 @@ _________________________________________________________________ {/if} -Avec environ 67 millions de paramètres, DistilBERT est environ deux fois plus petit que le modèle de base de BERT, ce qui se traduit approximativement par une accélération de l'entraînement d'un facteur deux - très bien ! Voyons maintenant quels types de *tokens* ce modèle prédit comme étant les compléments les plus probables d'un petit échantillon de texte : +Avec environ 67 millions de paramètres, DistilBERT est environ deux fois plus petit que le modèle de base de BERT, ce qui se traduit approximativement par une accélération de l'entraînement d'un facteur deux. Voyons maintenant quels types de *tokens* ce modèle prédit comme étant les compléments les plus probables d'un petit échantillon de texte : ```python text = "This is a great [MASK]." ``` -En tant qu'êtres humains, nous pouvons imaginer de nombreuses possibilités pour le *token* `[MASK]`, telles que "jour", "promenade" ou "peinture". Pour les modèles pré-entraînés, les prédictions dépendent du corpus sur lequel le modèle a été entraîné, puisqu'il apprend à détecter les modèles statistiques présents dans les données. Comme BERT, DistilBERT a été pré-entraîné sur les ensembles de données [English Wikipedia](https://huggingface.co/datasets/wikipedia) et [BookCorpus](https://huggingface.co/datasets/bookcorpus), nous nous attendons donc à ce que les prédictions pour `[MASK]` reflètent ces domaines. Pour prédire le masque, nous avons besoin du *tokenizer* de DistilBERT pour produire les entrées du modèle, alors téléchargeons-le également depuis le *Hub* : +En tant qu'êtres humains, nous pouvons imaginer de nombreuses possibilités pour le *token* `[MASK]`, telles que « jour », « promenade » ou « peinture ». Pour les modèles pré-entraînés, les prédictions dépendent du corpus sur lequel le modèle a été entraîné puisqu'il apprend à détecter les modèles statistiques présents dans les données. Comme BERT, DistilBERT a été pré-entraîné sur les jeux de données [*English Wikipedia*](https://huggingface.co/datasets/wikipedia) et [*BookCorpus*](https://huggingface.co/datasets/bookcorpus), nous nous attendons donc à ce que les prédictions pour `[MASK]` reflètent ces domaines. Pour prédire le masque, nous avons besoin du *tokenizer* de DistilBERT pour produire les entrées du modèle, alors téléchargeons-le également depuis le *Hub* : ```python from transformers import AutoTokenizer @@ -136,7 +135,7 @@ from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) ``` -Avec un *tokenizer* et un modèle, nous pouvons maintenant passer notre exemple de texte au modèle, extraire les logits, et imprimer les 5 meilleurs candidats : +Avec un *tokenizer* et un modèle, nous pouvons maintenant passer notre exemple de texte au modèle, extraire les logits, et afficher les 5 meilleurs candidats : {#if fw === 'pt'} @@ -145,7 +144,7 @@ import torch inputs = tokenizer(text, return_tensors="pt") token_logits = model(**inputs).logits -# Trouvez l'emplacement de [MASK] et extrayez ses logits +# Trouve l'emplacement de [MASK] et extrait ses logits mask_token_index = torch.where(inputs["input_ids"] == tokenizer.mask_token_id)[1] mask_token_logits = token_logits[0, mask_token_index, :] # Choisissez les candidats [MASK] avec les logits les plus élevés @@ -163,7 +162,7 @@ import tensorflow as tf inputs = tokenizer(text, return_tensors="np") token_logits = model(**inputs).logits -# Trouvez l'emplacement de [MASK] et extrayez ses logits +# Trouve l'emplacement de [MASK] et extrait ses logits mask_token_index = np.argwhere(inputs["input_ids"] == tokenizer.mask_token_id)[0, 1] mask_token_logits = token_logits[0, mask_token_index, :] # On choisit les candidats [MASK] avec les logits les plus élevés @@ -177,19 +176,19 @@ for token in top_5_tokens: {/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.' +'>>> This is a great deal.' # C'est une bonne affaire +'>>> This is a great success.' # C'est un grand succès +'>>> This is a great adventure.' # C'est une grande aventure +'>>> This is a great idea.' # C'est une bonne idée +'>>> This is a great feat.' # C'est un grand exploit ``` -Nous pouvons voir dans les sorties que les prédictions du modèle se réfèrent à des termes de tous les jours, ce qui n'est peut-être pas surprenant étant donné le fondement de la Wikipédia anglaise. Voyons comment nous pouvons changer ce domaine pour quelque chose d'un peu plus spécialisé : des critiques de films très polarisées ! +Nous pouvons voir dans les sorties que les prédictions du modèle se réfèrent à des termes de tous les jours, ce qui n'est peut-être pas surprenant étant donné le fondement de Wikipédia. Voyons comment nous pouvons changer ce domaine pour quelque chose d'un peu plus spécialisé : des critiques de films ! ## Le jeu de données -Pour illustrer l'adaptation au domaine, nous utiliserons le célèbre [Large Movie Review Dataset](https://huggingface.co/datasets/imdb) (ou IMDb en abrégé), qui est un corpus de critiques de films souvent utilisé pour évaluer les modèles d'analyse de sentiments. En affinant DistilBERT sur ce corpus, nous espérons que le modèle de langage adaptera son vocabulaire des données factuelles de Wikipédia sur lesquelles il a été pré-entraîné aux éléments plus subjectifs des critiques de films. Nous pouvons obtenir les données du *Hub* avec la fonction `load_dataset()` de 🤗 *Datasets* : +Pour illustrer l'adaptation au domaine, nous utiliserons le célèbre [*Large Movie Review Dataset*](https://huggingface.co/datasets/imdb) (ou IMDb en abrégé), qui est un corpus de critiques de films souvent utilisé pour évaluer les modèles d'analyse de sentiments. En *finetunant* DistilBERT sur ce corpus, nous espérons que le modèle de langage adaptera son vocabulaire des données factuelles de Wikipédia sur lesquelles il a été pré-entraîné aux éléments plus subjectifs des critiques de films. Nous pouvons obtenir les données du *Hub* avec la fonction `load_dataset()` de 🤗 *Datasets* : ```python from datasets import load_dataset @@ -215,7 +214,7 @@ DatasetDict({ }) ``` -Nous pouvons voir que les parties `train` et `test` sont chacune composées de 25 000 critiques, alors qu'il y a une partie non étiquetée appelée `unsupervised` qui contient 50 000 critiques. Jetons un coup d'œil à quelques échantillons pour avoir une idée du type de texte auquel nous avons affaire. Comme nous l'avons fait dans les chapitres précédents du cours, nous allons enchaîner les fonctions `Dataset.shuffle()` et `Dataset.select()` pour créer un échantillon aléatoire : +Nous pouvons voir que les parties `train` et `test` sont chacune composées de 25 000 critiques, alors qu'il y a une partie non étiquetée appelée `unsupervised` qui contient 50 000 critiques. Jetons un coup d'œil à quelques échantillons pour avoir une idée du type de texte auquel nous avons affaire. Comme nous l'avons fait dans les chapitres précédents du cours, nous allons enchaîner les fonctions `Dataset.shuffle()` et `Dataset.select()` pour créer un échantillon aléatoire : ```python sample = imdb_dataset["train"].shuffle(seed=42).select(range(3)) @@ -237,15 +236,15 @@ for row in sample: '>>> Label: 1' ``` -Oui, ce sont bien des critiques de films, et si vous êtes assez vieux, vous pouvez même comprendre le commentaire dans la dernière critique sur le fait de posséder une version VHS 😜 ! Bien que nous n'ayons pas besoin des étiquettes pour la modélisation du langage, nous pouvons déjà voir qu'un `0` dénote une critique négative, tandis qu'un `1` correspond à une critique positive. +Oui, ce sont bien des critiques de films, et si vous êtes assez âgés, vous pouvez même comprendre le commentaire dans la dernière critique sur le fait de posséder une version VHS 😜 ! Bien que nous n'ayons pas besoin des étiquettes pour la modélisation du langage, nous pouvons déjà voir qu'un `0` dénote une critique négative, tandis qu'un `1` correspond à une critique positive. -✏️ **Essayez !** Créez un échantillon aléatoire de la répartition `unsupervised` et vérifiez que les étiquettes ne sont ni `0` ni `1`. Pendant que vous y êtes, vous pouvez aussi vérifier que les étiquettes dans les fractions `train` et `test` sont bien `0` ou `1`. C'est un contrôle utile que tout praticien en NLP devrait effectuer au début d'un nouveau projet ! +✏️ **Essayez !** Créez un échantillon aléatoire de la répartition `unsupervised` et vérifiez que les étiquettes ne sont ni `0` ni `1`. Pendant que vous y êtes, vous pouvez aussi vérifier que les étiquettes dans les échantillons `train` et `test` sont bien `0` ou `1`. C'est un contrôle utile que tout praticien en NLP devrait effectuer au début d'un nouveau projet ! -Maintenant que nous avons jeté un coup d'œil rapide aux données, plongeons dans leur préparation pour la modélisation du langage masqué. Comme nous allons le voir, il y a quelques étapes supplémentaires à suivre par rapport aux tâches de classification de séquences que nous avons vues au [Chapitre 3](/course/fr/chapter3). Allons-y ! +Maintenant que nous avons jeté un coup d'œil rapide aux données, plongeons dans leur préparation pour la modélisation du langage masqué. Comme nous allons le voir, il y a quelques étapes supplémentaires à suivre par rapport aux tâches de classification de séquences que nous avons vues au [chapitre 3](/course/fr/chapter3). Allons-y ! ## Prétraitement des données @@ -253,7 +252,7 @@ Maintenant que nous avons jeté un coup d'œil rapide aux données, plongeons da Pour la modélisation autorégressive et la modélisation du langage masqué, une étape commune de prétraitement consiste à concaténer tous les exemples, puis à diviser le corpus entier en morceaux de taille égale. C'est très différent de notre approche habituelle, où nous nous contentons de *tokenizer* les exemples individuels. Pourquoi tout concaténer ? La raison est que les exemples individuels peuvent être tronqués s'ils sont trop longs, ce qui entraînerait la perte d'informations qui pourraient être utiles pour la tâche de modélisation du langage ! -Donc pour commencer, nous allons d'abord tokeniser notre corpus comme d'habitude, mais _sans_ mettre l'option `truncation=True` dans notre *tokenizer*. Nous allons aussi récupérer les IDs des mots s'ils sont disponibles (ce qui sera le cas si nous utilisons un *tokenizer* rapide, comme décrit dans [Chapter 6](/course/fr/chapter6/3)), car nous en aurons besoin plus tard pour faire le masquage des mots entiers. Nous allons envelopper cela dans une simple fonction, et pendant que nous y sommes, nous allons supprimer les colonnes `text` et `label` puisque nous n'en avons plus besoin : +Donc pour commencer, nous allons d'abord tokeniser notre corpus comme d'habitude, mais _sans_ mettre l'option `truncation=True` dans notre *tokenizer*. Nous allons aussi récupérer les identifiants des mots s'ils sont disponibles (ce qui sera le cas si nous utilisons un *tokenizer* rapide, comme décrit dans le [chapitre 6](/course/fr/chapter6/3)), car nous en aurons besoin plus tard pour faire le masquage de mots entiers. Nous allons envelopper cela dans une simple fonction, et pendant que nous y sommes, nous allons supprimer les colonnes `text` et `label` puisque nous n'en avons plus besoin : ```python def tokenize_function(examples): @@ -263,7 +262,7 @@ def tokenize_function(examples): return result -# Use batched=True to activate fast multithreading! +# Utilisation de batched=True pour activer le multithreading rapide ! tokenized_datasets = imdb_dataset.map( tokenize_function, batched=True, remove_columns=["text", "label"] ) @@ -295,19 +294,20 @@ Maintenant que nos critiques de films ont été tokenisées, l'étape suivante c tokenizer.model_max_length ``` + ```python out 512 ``` -Cette valeur est dérivée du fichier *tokenizer_config.json* associé à un point de contrôle ; dans ce cas, nous pouvons voir que la taille du contexte est de 512 *tokens*, tout comme avec BERT. +Cette valeur est dérivée du fichier *tokenizer_config.json* associé à un *checkpoint*. Dans ce cas, nous pouvons voir que la taille du contexte est de 512 *tokens*, tout comme avec BERT. -✏️ **Essayez !** Certains *transformers*, comme [BigBird](https://huggingface.co/google/bigbird-roberta-base) et [Longformer](hf.co/allenai/longformer-base-4096), ont une longueur de contexte beaucoup plus longue que BERT et les autres premiers *transformers*. Instanciez le *tokenizer* pour l'un de ces points de contrôle et vérifiez que le `model_max_length` correspond à ce qui est indiqué sur sa carte. +✏️ **Essayez !** Certains *transformers*, comme [BigBird](https://huggingface.co/google/bigbird-roberta-base) et [Longformer](hf.co/allenai/longformer-base-4096), ont une longueur de contexte beaucoup plus longue que BERT et les autres premiers *transformers*. Instanciez le *tokenizer* pour l'un de ces *checkpoints* et vérifiez que le `model_max_length` correspond à ce qui est indiqué sur sa carte. -Ainsi, pour réaliser nos expériences sur des GPU comme ceux de Google Colab, nous choisirons quelque chose d'un peu plus petit qui peut tenir en mémoire : +Ainsi, pour réaliser nos expériences sur des GPUs comme ceux disponibles sur Google Colab, nous choisirons quelque chose d'un peu plus petit qui peut tenir en mémoire : ```python chunk_size = 128 @@ -315,11 +315,11 @@ chunk_size = 128 -Notez que l'utilisation d'une petite taille de fragment peut être préjudiciable dans les scénarios du monde réel, vous devez donc utiliser une taille qui correspond au cas d'utilisation auquel vous appliquerez votre modèle. +Notez que l'utilisation d'une petite taille peut être préjudiciable dans les scénarios du monde réel. Vous devez donc utiliser une taille qui correspond au cas d'utilisation auquel vous appliquerez votre modèle. -Maintenant vient la partie amusante. Pour montrer comment la concaténation fonctionne, prenons quelques commentaires de notre ensemble d'entraînement et imprimons le nombre de *tokens* par commentaire : +Maintenant vient la partie amusante. Pour montrer comment la concaténation fonctionne, prenons quelques commentaires de notre ensemble d'entraînement et affichons le nombre de *tokens* par commentaire : ```python # Le découpage produit une liste de listes pour chaque caractéristique @@ -342,14 +342,14 @@ 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}'") +print(f"'>>> Longueur des critiques concaténées : {total_length}'") ``` ```python out -'>>> Concatenated reviews length: 951' +'>>> Longueur des critiques concaténées : 951' ``` -Super, la longueur totale est correcte. Donc maintenant, nous allons diviser les exemples concaténés en morceaux de la taille donnée par `block_size`. Pour ce faire, nous itérons sur les caractéristiques de `concatenated_examples` et utilisons une compréhension de liste pour créer des tranches de chaque caractéristique. Le résultat est un dictionnaire de *chunks* pour chaque caractéristique : +Super, la longueur totale est correcte. Donc maintenant, nous allons diviser les exemples concaténés en morceaux de la taille donnée par `block_size`. Pour ce faire, nous itérons sur les caractéristiques de `concatenated_examples` et utilisons une compréhension de liste pour créer des *chunks* de chaque caractéristique. Le résultat est un dictionnaire de *chunks* pour chaque caractéristique : ```python chunks = { @@ -374,8 +374,8 @@ for chunk in chunks["input_ids"]: Comme vous pouvez le voir dans cet exemple, le dernier *chunk* sera généralement plus petit que la taille maximale des morceaux. Il y a deux stratégies principales pour gérer cela : -* abandonner le dernier morceau s'il est plus petit que `chunk_size`. -* remplir le dernier morceau jusqu'à ce que sa longueur soit égale à `chunk_size`. +* Abandonner le dernier morceau s'il est plus petit que `chunk_size`. +* Rembourrer le dernier morceau jusqu'à ce que sa longueur soit égale à `chunk_size`. Nous adopterons la première approche ici, donc nous allons envelopper toute la logique ci-dessus dans une seule fonction que nous pouvons appliquer à nos jeux de données tokenisés : @@ -383,16 +383,16 @@ Nous adopterons la première approche ici, donc nous allons envelopper toute la def group_texts(examples): # Concaténation de tous les textes concatenated_examples = {k: sum(examples[k], []) for k in examples.keys()} - # Calculer la longueur des textes concaténés + # Calcule la longueur des textes concaténés total_length = len(concatenated_examples[list(examples.keys())[0]]) # Nous laissons tomber le dernier morceau s'il est plus petit que chunk_size total_length = (total_length // chunk_size) * chunk_size - # Split by chunks of max_len + # Fractionnement par chunk de 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() } - # Create a new labels column + # Créer une nouvelle colonne d'étiquettes result["labels"] = result["input_ids"].copy() return result ``` @@ -423,7 +423,7 @@ DatasetDict({ }) ``` -Vous pouvez voir que le regroupement puis le découpage des textes a produit beaucoup plus d'exemples que nos 25 000 exemples initiaux pour les divisions `train` et `test`. C'est parce que nous avons maintenant des exemples impliquant des "*tokens* contigus" qui s'étendent sur plusieurs exemples du corpus original. Vous pouvez le voir explicitement en cherchant les *tokens* spéciaux `[SEP]` et `[CLS]` dans l'un des *chunks* : +Vous pouvez voir que le regroupement puis le découpage des textes a produit beaucoup plus d'exemples que nos 25 000 exemples initiaux pour les divisions `train` et `test`. C'est parce que nous avons maintenant des exemples impliquant des *tokens* contigus qui s'étendent sur plusieurs exemples du corpus original. Vous pouvez le voir explicitement en cherchant les *tokens* spéciaux `[SEP]` et `[CLS]` dans l'un des *chunks* : ```python tokenizer.decode(lm_datasets["train"][1]["input_ids"]) @@ -443,11 +443,11 @@ tokenizer.decode(lm_datasets["train"][1]["labels"]) ".... 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" ``` -Comme prévu par notre fonction `group_texts()` ci-dessus, cela semble identique aux `input_ids` décodés - mais alors comment notre modèle peut-il apprendre quoi que ce soit ? Il nous manque une étape clé : insérer des *tokens* à des positions aléatoires dans les entrées ! Voyons comment nous pouvons le faire à la volée pendant le réglage fin en utilisant un collateur de données spécial. +Comme prévu par notre fonction `group_texts()` ci-dessus, cela semble identique aux `input_ids` décodés. Mais alors comment notre modèle peut-il apprendre quoi que ce soit ? Il nous manque une étape clé : insérer des *tokens* à des positions aléatoires dans les entrées ! Voyons comment nous pouvons le faire à la volée pendant le *finetuning* en utilisant un collateur de données spécial. -## *Finetuning* de DistilBERT avec l'API `Trainer`. +## Finetuning de DistilBERT avec l'API `Trainer` -Le *finetuning* d'un modèle de langage masqué est presque identique au *finetuning* d'un modèle de classification de séquences, comme nous l'avons fait dans le [Chapitre 3](/course/fr/chapter3). La seule différence est que nous avons besoin d'un collecteur de données spécial qui peut masquer de manière aléatoire certains des *tokens* dans chaque lot de textes. Heureusement, 🤗 *Transformers* est livré préparé avec un `DataCollatorForLanguageModeling` dédié à cette tâche. Nous devons juste lui passer le *tokenizer* et un argument `mlm_probability` qui spécifie quelle fraction des *tokens* à masquer. Nous choisirons 15%, qui est la quantité utilisée pour BERT et un choix commun dans la littérature : +Le *finetuning* d'un modèle de langage masqué est presque identique au *finetuning* d'un modèle de classification de séquences, comme nous l'avons fait dans le [chapitre 3](/course/fr/chapter3). La seule différence est que nous avons besoin d'un collecteur de données spécial qui peut masquer de manière aléatoire certains des *tokens* dans chaque batch de textes. Heureusement, 🤗 *Transformers* est livré préparé avec un `DataCollatorForLanguageModeling` dédié à cette tâche. Nous devons juste lui passer le *tokenizer* et un argument `mlm_probability` qui spécifie quelle fraction des *tokens* à masquer. Nous choisirons 15%, qui est la quantité utilisée pour BERT et un choix commun dans la littérature : ```python from transformers import DataCollatorForLanguageModeling @@ -455,7 +455,7 @@ from transformers import DataCollatorForLanguageModeling data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm_probability=0.15) ``` -Pour voir comment le masquage aléatoire fonctionne, nous allons donner quelques exemples au compilateur de données. Puisqu'il s'attend à une liste de `dict`s, où chaque `dict` représente un seul morceau de texte contigu, nous itérons d'abord sur le jeu de données avant de nourrir le lot au collateur. Nous supprimons la clé `"word_ids"` pour ce collateur de données car il ne l'attend pas : +Pour voir comment le masquage aléatoire fonctionne, nous allons donner quelques exemples au collateur de données. Puisqu'il s'attend à une liste de `dict` où chaque `dict` représente un seul morceau de texte contigu, nous itérons d'abord sur le jeu de données avant de donner le batch au collateur. Nous supprimons la clé `"word_ids"` pour ce collateur de données car il ne l'attend pas : ```python samples = [lm_datasets["train"][i] for i in range(2)] @@ -472,21 +472,21 @@ for chunk in data_collator(samples)["input_ids"]: '>>> .... 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' ``` -Super, ça a marché ! Nous pouvons voir que le *token* `[MASK]` a été inséré de façon aléatoire à différents endroits dans notre texte. Ce seront les *tokens* que notre modèle devra prédire pendant l'entraînement. Et la beauté du collecteur de données est qu'il va rendre aléatoire l'insertion du `[MASK]` à chaque lot ! +Super, ça a marché ! Nous pouvons voir que le *token* `[MASK]` a été inséré de façon aléatoire à différents endroits dans notre texte. Ce seront les *tokens* que notre modèle devra prédire pendant l'entraînement. Et la beauté du collecteur de données est qu'il va rendre aléatoire l'insertion du `[MASK]` à chaque batch ! -✏️ **Essayez** Exécutez le code ci-dessus plusieurs fois pour voir le masquage aléatoire se produire sous vos yeux ! Remplacez aussi la méthode `tokenizer.decode()` par `tokenizer.convert_ids_to_tokens()` pour voir que parfois un seul *token* d'un mot donné est masqué, et pas les autres. +✏️ **Essayez** Exécutez le code ci-dessus plusieurs fois pour voir le masquage aléatoire se produire sous vos yeux ! Remplacez aussi la méthode `tokenizer.decode()` par `tokenizer.convert_ids_to_tokens()` pour voir que parfois un seul *token* d'un mot donné est masqué et pas les autres. {#if fw === 'pt'} -Un effet secondaire du masquage aléatoire est que nos métriques d'évaluation ne seront pas déterministes lorsque nous utilisons le `Trainer`, puisque nous utilisons le même collateur de données pour les ensembles d'entraînement et de test. Nous verrons plus tard, lorsque nous examinerons le *finetuning* avec 🤗 *Accelerate*, comment nous pouvons utiliser la flexibilité d'une boucle d'évaluation personnalisée pour geler le caractère aléatoire. +Un effet secondaire du masquage aléatoire est que nos métriques d'évaluation ne seront pas déterministes lorsque nous utilisons la fonction `Trainer` puisque nous utilisons le même collateur de données pour les échantillons d'entraînement et de test. Nous verrons plus tard, lorsque nous examinerons le *finetuning* avec 🤗 *Accelerate*, comment nous pouvons utiliser la flexibilité d'une boucle d'évaluation personnalisée pour geler le caractère aléatoire. {/if} -Lors de l'entraînement des modèles pour la modélisation du langage masqué, une technique qui peut être utilisée est de masquer des mots entiers ensemble, et pas seulement des *tokens* individuels. Cette approche est appelée _masquage de mots entiers_. Si nous voulons utiliser le masquage de mots entiers, nous devons construire nous-mêmes un collateur de données. Un collateur de données est simplement une fonction qui prend une liste d'échantillons et les convertit en un lot, alors faisons-le maintenant ! Nous utiliserons les IDs des mots calculés plus tôt pour faire une correspondance entre les indices des mots et les *tokens* correspondants, puis nous déciderons aléatoirement quels mots masquer et appliquerons ce masque sur les entrées. Notez que les étiquettes sont toutes `-100` sauf celles qui correspondent aux mots masqués. +Lors de l'entraînement des modèles pour la modélisation du langage masqué, une technique qui peut être utilisée est de masquer des mots entiers ensemble et pas seulement des *tokens* individuels. Cette approche est appelée _masquage de mots entiers_. Si nous voulons utiliser le masquage de mots entiers, nous devons construire nous-mêmes un collateur de données. Un collateur de données est simplement une fonction qui prend une liste d'échantillons et les convertit en un batch. Faisons-le ! Nous utiliserons les identifiants des mots calculés plus tôt pour faire une correspondance entre les indices des mots et les *tokens*, puis nous déciderons aléatoirement quels mots masquer et appliquerons ce masque sur les entrées. Notez que les étiquettes sont toutes `-100` sauf celles qui correspondent aux mots masqués. {#if fw === 'pt'} @@ -503,7 +503,7 @@ def whole_word_masking_data_collator(features): for feature in features: word_ids = feature.pop("word_ids") - # Création d'une carte entre les mots et les indices des tokens correspondants. + # Création d'une correspondance entre les mots et les indices des tokens correspondants mapping = collections.defaultdict(list) current_word_index = -1 current_word = None @@ -543,7 +543,7 @@ def whole_word_masking_data_collator(features): for feature in features: word_ids = feature.pop("word_ids") - # Création d'une carte entre les mots et les indices des tokens correspondants. + # Création d'une correspondance entre les mots et les indices des tokens correspondants mapping = collections.defaultdict(list) current_word_index = -1 current_word = None @@ -592,7 +592,7 @@ for chunk in batch["input_ids"]: -Maintenant que nous avons deux collateurs de données, le reste des étapes de mise au point est standard. L'entraînement peut prendre un certain temps sur Google Colab si vous n'avez pas la chance d'avoir un mythique GPU P100 😭, donc nous allons d'abord réduire la taille de l'ensemble d'entraînement à quelques milliers d'exemples. Ne vous inquiétez pas, nous obtiendrons quand même un modèle de langage assez décent ! Un moyen rapide de réduire la taille d'un jeu de données dans 🤗 *Datasets* est la fonction `Dataset.train_test_split()` que nous avons vue au [Chapitre 5](/course/fr/chapter5) : +Maintenant que nous avons deux collateurs de données, les étapes restantes du *finetuning* sont standards. L'entraînement peut prendre un certain temps sur Google Colab si vous n'avez pas la chance de tomber sur un mythique GPU P100 😭. Ainsi nous allons d'abord réduire la taille du jeu d'entraînement à quelques milliers d'exemples. Ne vous inquiétez pas, nous obtiendrons quand même un modèle de langage assez décent ! Un moyen rapide de réduire la taille d'un jeu de données dans 🤗 *Datasets* est la fonction `Dataset.train_test_split()` que nous avons vue au [chapitre 5](/course/fr/chapter5) : ```python train_size = 10_000 @@ -617,7 +617,7 @@ DatasetDict({ }) ``` -Cela a automatiquement créé de nouvelles divisions `train` et `test`, avec la taille de l'ensemble d'entraînement fixée à 10.000 exemples et la validation fixée à 10% de cela. N'hésitez pas à augmenter cela si vous avez un GPU puissant ! La prochaine chose que nous devons faire est de nous connecter au *Hub*. Si vous exécutez ce code dans un *notebook*, vous pouvez le faire avec la fonction utilitaire suivante : +Cela a automatiquement créé de nouvelles divisions `train` et `test` avec la taille du jeu d'entraînement fixée à 10.000 exemples et la validation fixée à 10% de cela. N'hésitez pas à augmenter la taille si vous avez un GPU puissant ! La prochaine chose que nous devons faire est de nous connecter au *Hub*. Si vous exécutez ce code dans un *notebook*, vous pouvez le faire avec la fonction suivante : ```python from huggingface_hub import notebook_login @@ -653,9 +653,9 @@ tf_eval_dataset = downsampled_dataset["test"].to_tf_dataset( ) ``` -Ensuite, nous configurons nos hyperparamètres d'entraînement et compilons notre modèle. Nous utilisons la fonction `create_optimizer()` de la bibliothèque 🤗 *Transformers*, qui nous donne un optimiseur `AdamW` avec une décroissance linéaire du taux d'apprentissage. Nous utilisons également la perte intégrée au modèle, qui est la perte par défaut lorsqu'aucune perte n'est spécifiée comme argument de `compile()`, et nous définissons la précision d'entraînement à `"mixed_float16"`. Notez que si vous utilisez un GPU Colab ou un autre GPU qui n'a pas le support accéléré de float16, vous devriez probablement commenter cette ligne. +Ensuite, nous configurons nos hyperparamètres d'entraînement et compilons notre modèle. Nous utilisons la fonction `create_optimizer()` de la bibliothèque 🤗 *Transformers*, qui nous donne un optimiseur `AdamW` avec une décroissance linéaire du taux d'apprentissage. Nous utilisons également la perte intégrée au modèle, qui est la perte par défaut lorsqu'aucune perte n'est spécifiée comme argument de `compile()`, et nous définissons la précision d'entraînement à `"mixed_float16"`. Notez que si vous utilisez un GPU Colab ou un autre GPU qui n'a pas le support accéléré en float16, vous devriez probablement commenter cette ligne. -De plus, nous mettons en place un `PushToHubCallback` qui sauvegardera le modèle sur le Hub après chaque époque. Vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, pour pousser le modèle vers l'organisation [`huggingface-course`] (https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/distilbert-finetuned-imdb"`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, donc dans notre cas, ce sera `"lewtun/distilbert-finetuned-imdb"`. +De plus, nous mettons en place un `PushToHubCallback` qui sauvegardera le modèle sur le *Hub* après chaque époque. Vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, pour pousser le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/distilbert-finetuned-imdb"`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, donc dans notre cas, ce sera `"lewtun/distilbert-finetuned-imdb"`. ```python from transformers import create_optimizer @@ -671,7 +671,7 @@ optimizer, schedule = create_optimizer( ) model.compile(optimizer=optimizer) -# Train in mixed-precision float16 +# Entraîner en mixed-precision float16 tf.keras.mixed_precision.set_global_policy("mixed_float16") callback = PushToHubCallback( @@ -679,7 +679,7 @@ callback = PushToHubCallback( ) ``` -Nous sommes maintenant prêts à exécuter `model.fit()`. Mais avant de le faire, regardons brièvement la _perplexité_, qui est une métrique commune pour évaluer la performance des modèles de langage. +Nous sommes maintenant prêts à exécuter `model.fit()`. Mais avant, regardons brièvement la _perplexité_ qui est une métrique commune pour évaluer la performance des modèles de langage. {:else} @@ -689,7 +689,7 @@ Une fois que nous sommes connectés, nous pouvons spécifier les arguments pour from transformers import TrainingArguments batch_size = 64 -# Montrer la perte d'entraînement à chaque époque. +# Montrer la perte d'entraînement à chaque époque logging_steps = len(downsampled_dataset["train"]) // batch_size model_name = model_checkpoint.split("/")[-1] @@ -707,9 +707,9 @@ training_args = TrainingArguments( ) ``` -Ici, nous avons modifié quelques options par défaut, y compris `logging_steps` pour s'assurer que nous suivons la perte d'entraînement à chaque époque. Nous avons également utilisé `fp16=True` pour activer l'entraînement en précision mixte, ce qui nous donne un autre gain de vitesse. Par défaut, le `Trainer` va supprimer toutes les colonnes qui ne font pas partie de la méthode `forward()` du modèle. Cela signifie que si vous utilisez le collateur de masquage de mots entiers, vous devrez également définir `remove_unused_columns=False` pour vous assurer que nous ne perdons pas la colonne `word_ids` pendant l'entraînement. +Ici, nous avons modifié quelques options par défaut, y compris `logging_steps` pour s'assurer que nous suivons la perte d'entraînement à chaque époque. Nous avons également utilisé `fp16=True` pour activer l'entraînement en précision mixte, ce qui nous donne un autre gain de vitesse. Par défaut, `Trainer` va supprimer toutes les colonnes qui ne font pas partie de la méthode `forward()` du modèle. Cela signifie que si vous utilisez le collateur de masquage de mots entiers, vous devrez également définir `remove_unused_columns=False` pour vous assurer que nous ne perdons pas la colonne `word_ids` pendant l'entraînement. -Notez que vous pouvez spécifier le nom du référentiel vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/distilbert-finetuned-imdb"``TrainingArguments`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, donc dans notre cas ce sera `"lewtun/distilbert-finetuned-imdb"`. +Notez que vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/distilbert-finetuned-imdb"` `TrainingArguments`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, donc dans notre cas ce sera `"lewtun/distilbert-finetuned-imdb"`. Nous avons maintenant tous les ingrédients pour instancier le `Trainer`. Ici, nous utilisons juste le collateur standard `data_collator`, mais vous pouvez essayer le collateur de masquage de mots entiers et comparer les résultats comme exercice : @@ -725,7 +725,7 @@ trainer = Trainer( ) ``` -Nous sommes maintenant prêts à exécuter `trainer.train()` . Mais avant de le faire, regardons brièvement la _perplexité_, qui est une métrique commune pour évaluer la performance des modèles de langage. +Nous sommes maintenant prêts à exécuter `trainer.train()`. Mais avant, regardons brièvement la _perplexité_ qui est une métrique commune pour évaluer la performance des modèles de langage. {/if} @@ -733,11 +733,11 @@ Nous sommes maintenant prêts à exécuter `trainer.train()` . Mais avant de le -Contrairement à d'autres tâches, comme la classification de textes ou la réponse à des questions, sur lesquelles nous disposons d'un corpus étiqueté pour nous entraîner, la modélisation du langage ne s'appuie sur aucune étiquette explicite. Alors comment déterminer ce qui fait un bon modèle de langage ? Comme pour la fonction de correction automatique de votre téléphone, un bon modèle de langage est celui qui attribue des probabilités élevées aux phrases grammaticalement correctes et des probabilités faibles aux phrases absurdes. Pour vous donner une meilleure idée de ce à quoi cela ressemble, vous pouvez trouver en ligne des séries entières de "ratés d'autocorrection", où le modèle du téléphone d'une personne a produit des compléments plutôt amusants (et souvent inappropriés) ! +Contrairement à d'autres tâches, comme la classification de textes ou la réponse à des questions, sur lesquelles nous disposons d'un corpus étiqueté pour entraîner, la modélisation du langage ne s'appuie sur aucune étiquette explicite. Alors comment déterminer ce qui fait un bon modèle de langage ? Comme pour la fonction de correction automatique de votre téléphone, un bon modèle de langage est celui qui attribue des probabilités élevées aux phrases grammaticalement correctes et des probabilités faibles aux phrases absurdes. Pour vous donner une meilleure idée de ce à quoi cela ressemble, vous pouvez trouver en ligne des séries entières de « ratés d'autocorrection » où le modèle d'un téléphone produit des compléments plutôt amusants (et souvent inappropriés) ! {#if fw === 'pt'} -En supposant que notre ensemble de test se compose principalement de phrases grammaticalement correctes, une façon de mesurer la qualité de notre modèle de langage est de calculer les probabilités qu'il attribue au mot suivant dans toutes les phrases de l'ensemble de test. Des probabilités élevées indiquent que le modèle n'est pas "surpris" ou "perplexe" par les exemples non vus, et suggèrent qu'il a appris les modèles de base de la grammaire de la langue. Il existe plusieurs définitions mathématiques de la perplexité, mais celle que nous utiliserons la définit comme l'exponentielle de la perte d'entropie croisée. Ainsi, nous pouvons calculer la perplexité de notre modèle pré-entraîné en utilisant la fonction `Trainer.evaluate()` pour calculer la perte d'entropie croisée sur l'ensemble de test, puis en prenant l'exponentielle du résultat : +En supposant que notre ensemble de test se compose principalement de phrases grammaticalement correctes, une façon de mesurer la qualité de notre modèle de langage est de calculer les probabilités qu'il attribue au mot suivant dans toutes les phrases de l'ensemble de test. Des probabilités élevées indiquent que le modèle n'est pas « surpris » ou « perplexe » vis-à-vis des exemples non vus, et suggèrent qu'il a appris les modèles de base de la grammaire de la langue. Il existe plusieurs définitions mathématiques de la perplexité. Celle que nous utiliserons la définit comme l'exponentielle de la perte d'entropie croisée. Ainsi, nous pouvons calculer la perplexité de notre modèle pré-entraîné en utilisant la fonction `Trainer.evaluate()` pour calculer la perte d'entropie croisée sur l'ensemble de test, puis en prenant l'exponentielle du résultat : ```python import math @@ -748,22 +748,22 @@ print(f">>> Perplexity: {math.exp(eval_results['eval_loss']):.2f}") {:else} -En supposant que notre ensemble de test se compose principalement de phrases grammaticalement correctes, une façon de mesurer la qualité de notre modèle de langage est de calculer les probabilités qu'il attribue au mot suivant dans toutes les phrases de l'ensemble de test. Des probabilités élevées indiquent que le modèle n'est pas "surpris" ou "perplexe" par les exemples non vus, et suggèrent qu'il a appris les modèles de base de la grammaire de la langue. Il existe plusieurs définitions mathématiques de la perplexité, mais celle que nous utiliserons la définit comme l'exponentielle de la perte d'entropie croisée. Ainsi, nous pouvons calculer la perplexité de notre modèle pré-entraîné en utilisant la méthode `model.evaluate()` pour calculer la perte d'entropie croisée sur l'ensemble de test, puis en prenant l'exponentielle du résultat : +En supposant que notre ensemble de test se compose principalement de phrases grammaticalement correctes, une façon de mesurer la qualité de notre modèle de langage est de calculer les probabilités qu'il attribue au mot suivant dans toutes les phrases de l'ensemble de test. Des probabilités élevées indiquent que le modèle n'est pas « surpris » ou « perplexe » vis-à-vis des exemples non vus, et suggèrent qu'il a appris les modèles de base de la grammaire de la langue. Il existe plusieurs définitions mathématiques de la perplexité. Celle que nous utiliserons la définit comme l'exponentielle de la perte d'entropie croisée. Ainsi, nous pouvons calculer la perplexité de notre modèle pré-entraîné en utilisant la fonction `model.evaluate()` pour calculer la perte d'entropie croisée sur l'ensemble de test, puis en prenant l'exponentielle du résultat : ```python import math eval_loss = model.evaluate(tf_eval_dataset) -print(f"Perplexity: {math.exp(eval_loss):.2f}") +print(f"Perplexité : {math.exp(eval_loss):.2f}") ``` {/if} ```python out ->>> Perplexity: 21.75 +>>> Perplexité : 21.75 ``` -Un score de perplexité plus faible signifie un meilleur modèle de langue, et nous pouvons voir ici que notre modèle de départ a une valeur assez élevée. Voyons si nous pouvons la réduire en l'affinant ! Pour ce faire, nous commençons par exécuter la boucle d'entraînement : +Un score de perplexité faible signifie un meilleur modèle de langue. Nous pouvons voir ici que notre modèle de départ a une valeur assez élevée. Voyons si nous pouvons la réduire en l'affinant ! Pour ce faire, nous commençons par exécuter la boucle d'entraînement : {#if fw === 'pt'} @@ -785,27 +785,27 @@ et ensuite calculer la perplexité résultante sur l'ensemble de test comme pré ```python eval_results = trainer.evaluate() -print(f">>> Perplexity: {math.exp(eval_results['eval_loss']):.2f}") +print(f">>> Perplexité : {math.exp(eval_results['eval_loss']):.2f}") ``` {:else} ```python eval_loss = model.evaluate(tf_eval_dataset) -print(f"Perplexity: {math.exp(eval_loss):.2f}") +print(f"Perplexité : {math.exp(eval_loss):.2f}") ``` {/if} ```python out ->>> Perplexity: 11.32 +>>> Perplexité : 11.32 ``` Joli. C'est une réduction considérable de la perplexité, ce qui nous indique que le modèle a appris quelque chose sur le domaine des critiques de films ! {#if fw === 'pt'} -Une fois l'entraînement terminé, nous pouvons pousser la carte modèle avec les informations d'entraînement vers le Hub (les points de contrôle sont sauvegardés pendant l'entraînement lui-même) : +Une fois l'entraînement terminé, nous pouvons pousser la carte de modèle avec les informations d'entraînement vers le *Hub* (les *checkpoints* sont sauvegardés pendant l'entraînement lui-même) : ```python trainer.push_to_hub() @@ -815,7 +815,7 @@ trainer.push_to_hub() -✏️ **Votre tour !** Exécutez l'entraînement ci-dessus après avoir remplacé le collecteur de données par le collecteur de mots entiers masqués. Obtenez-vous de meilleurs résultats ? +✏️ **A votre tour !** Exécutez l'entraînement ci-dessus après avoir remplacé le collecteur de données par le collecteur de mots entiers masqués. Obtenez-vous de meilleurs résultats ? @@ -823,21 +823,21 @@ trainer.push_to_hub() Dans notre cas d'utilisation, nous n'avons pas eu besoin de faire quelque chose de spécial avec la boucle d'entraînement, mais dans certains cas, vous pourriez avoir besoin de mettre en œuvre une logique personnalisée. Pour ces applications, vous pouvez utiliser 🤗 *Accelerate*. Jetons un coup d'œil ! -## *Finetuning* de DistilBERT avec 🤗 Accelerate +## Finetuning de DistilBERT avec 🤗 Accelerate -Comme nous l'avons vu avec le `Trainer`, le réglage fin d'un modèle de langage masqué est très similaire à l'exemple de classification de texte du [Chapitre 3](/course/fr/chapter3). En fait, la seule subtilité est l'utilisation d'un collateur de données spécial, et nous l'avons déjà couvert plus tôt dans cette section ! +Comme nous l'avons vu, avec `Trainer` le *finetuning* d'un modèle de langage masqué est très similaire à l'exemple de classification de texte du [chapitre 3](/course/fr/chapter3). En fait, la seule subtilité est l'utilisation d'un collateur de données spécial, et nous l'avons déjà couvert plus tôt dans cette section ! -Cependant, nous avons vu que `DataCollatorForLanguageModeling` applique aussi un masquage aléatoire à chaque évaluation, donc nous verrons quelques fluctuations dans nos scores de perplexité à chaque entrainement. Une façon d'éliminer cette source d'aléatoire est d'appliquer le masquage _une fois_ sur l'ensemble de test, puis d'utiliser le collateur de données par défaut dans 🤗 *Transformers* pour collecter les lots pendant l'évaluation. Pour voir comment cela fonctionne, implémentons une fonction simple qui applique le masquage sur un lot, similaire à notre première rencontre avec `DataCollatorForLanguageModeling` : +Cependant, nous avons vu que `DataCollatorForLanguageModeling` applique aussi un masquage aléatoire à chaque évaluation. Nous verrons donc quelques fluctuations dans nos scores de perplexité à chaque entrainement. Une façon d'éliminer cette source d'aléat est d'appliquer le masquage _une fois_ sur l'ensemble de test, puis d'utiliser le collateur de données par défaut dans 🤗 *Transformers* pour collecter les batchs pendant l'évaluation. Pour voir comment cela fonctionne, implémentons une fonction simple qui applique le masquage sur un batch, similaire à notre première rencontre avec `DataCollatorForLanguageModeling` : ```python def insert_random_mask(batch): features = [dict(zip(batch, t)) for t in zip(*batch.values())] masked_inputs = data_collator(features) - # Create a new "masked" column for each column in the dataset + # Créer une nouvelle colonne "masquée" pour chaque colonne du jeu de données return {"masked_" + k: v.numpy() for k, v in masked_inputs.items()} ``` -Ensuite, nous allons appliquer cette fonction à notre jeu de test et laisser tomber les colonnes non masquées afin de les remplacer par les colonnes masquées. Vous pouvez utiliser le masquage de mots entiers en remplaçant le `data_collator` ci-dessus par celui qui est approprié, dans ce cas vous devez supprimer la première ligne ici : +Ensuite, nous allons appliquer cette fonction à notre jeu de test et laisser tomber les colonnes non masquées afin de les remplacer par les colonnes masquées. Vous pouvez utiliser le masquage de mots entiers en remplaçant le `data_collator` ci-dessus par celui qui est approprié. Dans ce cas vous devez supprimer la première ligne ici : ```py downsampled_dataset = downsampled_dataset.remove_columns(["word_ids"]) @@ -873,13 +873,13 @@ eval_dataloader = DataLoader( ) ``` -Forme ici, nous suivons les étapes standard avec 🤗 *Accelerate*. Le premier ordre du jour est de charger une version fraîche du modèle pré-entraîné : +Nous suivons les étapes standard avec 🤗 *Accelerate*. La première est de charger une version fraîche du modèle pré-entraîné : ``` model = AutoModelForMaskedLM.from_pretrained(model_checkpoint) ``` -Ensuite, nous devons spécifier l'optimiseur ; nous utiliserons le standard `AdamW` : +Ensuite, nous devons spécifier l'optimiseur. Nous utiliserons le standard `AdamW` : ```python from torch.optim import AdamW @@ -929,7 +929,7 @@ repo_name 'lewtun/distilbert-base-uncased-finetuned-imdb-accelerate' ``` -puis créer et cloner le référentiel en utilisant la classe `Repository` du 🤗 *Hub* : +puis créer et cloner le dépôt en utilisant la classe `Repository` du 🤗 *Hub* : ```python from huggingface_hub import Repository @@ -1000,9 +1000,9 @@ Cool, nous avons été en mesure d'évaluer la perplexité à chaque époque et {/if} -### Utilisation de notre modèle *finetuné* +### Utilisation de notre modèle finetuné -Vous pouvez interagir avec votre modèle affiné soit en utilisant son *widget* sur le *Hub*, soit localement avec le `pipeline` de 🤗 *Transformers*. Utilisons cette dernière pour télécharger notre modèle en utilisant le pipeline `fill-mask` : +Vous pouvez interagir avec votre modèle *finetuné* soit en utilisant son *widget* sur le *Hub*, soit localement avec le `pipeline` de 🤗 *Transformers*. Utilisons ce dernier pour télécharger notre modèle en utilisant le pipeline `fill-mask` : ```python from transformers import pipeline @@ -1012,7 +1012,7 @@ mask_filler = pipeline( ) ``` -Nous pouvons ensuite alimenter le pipeline avec notre exemple de texte "C'est un grand [MASK]" et voir quelles sont les 5 premières prédictions : +Nous pouvons ensuite donner au pipeline notre exemple de texte « this is a great [MASK] » et voir quelles sont les 5 premières prédictions : ```python preds = mask_filler(text) @@ -1033,10 +1033,10 @@ Notre modèle a clairement adapté ses pondérations pour prédire les mots qui -Ceci conclut notre première expérience d'entraînement d'un modèle de langage. Dans la [section 6](/course/fr/chapter7/section6), vous apprendrez comment entraîner un modèle autorégressif comme GPT-2 à partir de zéro ; allez-y si vous voulez voir comment vous pouvez pré-entraîner votre propre *transformer* ! +Ceci conclut notre première expérience d'entraînement d'un modèle de langage. Dans la [section 6](/course/fr/chapter7/section6), vous apprendrez comment entraîner à partir de zéro un modèle autorégressif comme GPT-2. Allez-y si vous voulez voir comment vous pouvez pré-entraîner votre propre *transformer* ! -✏️ **Essayez !** Pour quantifier les avantages de l'adaptation au domaine, ajustez un classificateur sur les étiquettes IMDb pour les points de contrôle DistilBERT pré-entraînés et ajustés. Si vous avez besoin d'un rafraîchissement sur la classification de texte, consultez le [Chapitre 3](/course/fr/chapter3). +✏️ **Essayez !** Pour quantifier les avantages de l'adaptation au domaine, finetunez un classifieur sur le jeu de données IMDb pour à la fois, le checkpoint de DistilBERT pré-entraîné et e checkpoint de DistilBERT finetuné. Si vous avez besoin d'un rafraîchissement sur la classification de texte, consultez le [chapitre 3](/course/fr/chapter3). diff --git a/chapters/fr/chapter7/4.mdx b/chapters/fr/chapter7/4.mdx index 48d83a6da..d7d868936 100644 --- a/chapters/fr/chapter7/4.mdx +++ b/chapters/fr/chapter7/4.mdx @@ -22,16 +22,16 @@ {/if} -Plongeons maintenant dans la traduction. Il s'agit d'une autre [tâche de séquence à séquence](/cours/fr/chapitre1/7), ce qui signifie que c'est un problème qui peut être formulé comme le passage d'une séquence à une autre. En ce sens, le problème est assez proche de la tâche de [résumé](/cours/fr/chapitre7/6) et vous pouvez adapter ce que nous allons voir ici à d'autres problèmes de séquence à séquence tels que : +Plongeons maintenant dans la traduction. Il s'agit d'une autre [tâche de séquence à séquence](/course/fr/chapitre1/7), ce qui signifie que c'est un problème qui peut être formulé comme le passage d'une séquence à une autre. En ce sens, le problème est assez proche de la tâche de [résumé](/course/fr/chapitre7/6) et vous pouvez adapter ce que nous allons voir ici à d'autres problèmes de séquence à séquence tels que : -- le **transfert de style** : créer un modèle qui *traduit* des textes écrits dans un certain style vers un autre (par exemple, du formel au décontracté ou de l'anglais shakespearien à l'anglais moderne). -- la **génération de réponse à des questions** : Création d'un modèle qui génère des réponses à des questions, compte tenu d'un contexte. +- Le **transfert de style** ? c'est-à-dire créer un modèle qui *traduit* des textes écrits dans un certain style vers un autre (par exemple, du formel au décontracté ou de l'anglais shakespearien à l'anglais moderne). +- La **génération de réponse à des questions** c'est-à-dire créer un modèle qui génère des réponses à des questions compte tenu d'un contexte. -Si vous disposez d'un corpus suffisamment important de textes en deux langues (ou plus), vous pouvez entraîner un nouveau modèle de traduction à partir de zéro, comme nous le ferons dans la section sur la [modélisation causale du langage](/cours/fr/chapitre7/6). Il est toutefois plus rapide de *finetuner* un modèle de traduction existant, qu'il s'agisse d'un modèle multilingue comme mT5 ou mBART que vous souhaitez adapter à une paire de langues spécifique, ou même d'un modèle spécialisé dans la traduction d'une langue vers une autre que vous souhaitez adapter à votre corpus spécifique. +Si vous disposez d'un corpus de textes suffisamment important en deux langues différentes (ou plus), vous pouvez entraîner un nouveau modèle de traduction à partir de zéro, comme nous le ferons dans la section sur la [modélisation causale du langage](/course/fr/chapitre7/6). Il est toutefois plus rapide de *finetuner* un modèle de traduction existant, qu'il s'agisse d'un modèle multilingue comme mT5 ou mBART que vous souhaitez adapter à une paire de langues spécifique, ou même d'un modèle spécialisé dans la traduction d'une langue vers une autre que vous souhaitez adapter à votre corpus spécifique. -Dans cette section, nous allons *finetuner* un modèle Marian pré-entraîné pour traduire de l'anglais au français (puisque de nombreux employés de Hugging Face parlent ces deux langues) sur le [KDE4 dataset](https://huggingface.co/datasets/kde4), qui est un jeu de données de fichiers localisés pour les [KDE apps](https://apps.kde.org/). Le modèle que nous utiliserons a été pré-entraîné sur un large corpus de textes français et anglais provenant du [jeu de données Opus](https://opus.nlpl.eu/), qui contient en fait le jeu de données KDE4. Mais même si le modèle pré-entraîné que nous utilisons a vu ces données pendant son pré-entraînement, nous verrons que nous pouvons obtenir une meilleure version de ce modèle après un *finetuning*. +Dans cette section, nous allons *finetuner* un modèle Marian pré-entraîné pour traduire de l'anglais au français (puisque de nombreux employés de Hugging Face parlent ces deux langues) sur le jeu de données [KDE4](https://huggingface.co/datasets/kde4) qui est un jeu de données de fichiers localisés pour les applications [KDE](https://apps.kde.org/). Le modèle que nous utiliserons a été pré-entraîné sur un large corpus de textes français et anglais provenant du jeu de données [Opus](https://opus.nlpl.eu/) qui contient en fait le jeu de données KDE4. A noter que même si le modèle pré-entraîné que nous utilisons a vu ces données pendant son pré-entraînement, nous verrons que nous pouvons obtenir une meilleure version de ce modèle après un *finetuning*. Une fois que nous aurons terminé, nous aurons un modèle capable de faire des prédictions comme celle-ci : @@ -43,11 +43,11 @@ Une fois que nous aurons terminé, nous aurons un modèle capable de faire des p -Comme dans les sections précédentes, vous pouvez trouver le modèle réel que nous allons entraîner et télécharger sur le *Hub* en utilisant le code ci-dessous et vérifier ses prédictions [ici](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr?text=This+plugin+allows+you+to+automatically+translate+web+pages+between+several+languages.). +Comme dans les sections précédentes, vous pouvez trouver, télécharger et vérifier les précisions de ce modèle sur le [*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.). ## Préparation des données -Pour affiner ou entraîner un modèle de traduction à partir de zéro, nous avons besoin d'un jeu de données adapté à cette tâche. Comme mentionné précédemment, nous utiliserons le jeu de données [KDE4](https://huggingface.co/datasets/kde4) dans cette section, mais vous pouvez adapter le code pour utiliser vos propres données assez facilement, tant que vous avez des paires de phrases dans les deux langues que vous voulez traduire de et vers. Reportez-vous au [Chapitre 5](/course/fr/chapter5) si vous avez besoin d'un rappel sur la façon de charger vos données personnalisées dans un `Dataset`. +Pour *finetuner* ou entraîner un modèle de traduction à partir de zéro, nous avons besoin d'un jeu de données adapté à cette tâche. Comme mentionné précédemment, nous utiliserons le jeu de données [KDE4](https://huggingface.co/datasets/kde4) dans cette section. Notez que vous pouvez adapter assez facilement le code pour utiliser vos propres données du moment que vous disposez de paires de phrases dans les deux langues que vous voulez traduire. Reportez-vous au [chapitre 5](/course/fr/chapter5) si vous avez besoin d'un rappel sur la façon de charger vos données personnalisées dans un `Dataset`. ### Le jeu de données KDE4 @@ -59,11 +59,11 @@ from datasets import load_dataset, load_metric raw_datasets = load_dataset("kde4", lang1="en", lang2="fr") ``` -Si vous souhaitez travailler avec une autre paire de langues, vous pouvez les spécifier par leurs codes. Au total, 92 langues sont disponibles pour cet ensemble de données ; vous pouvez les voir toutes en développant les étiquettes de langue sur sa [fiche](https://huggingface.co/datasets/kde4). +Si vous souhaitez travailler avec une autre paire de langues, 92 langues sont disponibles au total pour ce jeu de données. Vous pouvez les voir dans la [carte du jeu de données](https://huggingface.co/datasets/kde4). Language available for the KDE4 dataset. -Jetons un coup d'œil au jeu de données : +Jetons un coup d'œil au jeu de données : ```py raw_datasets @@ -78,7 +78,7 @@ DatasetDict({ }) ``` -Nous avons 210 173 paires de phrases, mais dans un seul split, donc nous devrons créer notre propre ensemble de validation. Comme nous l'avons vu dans le [Chapitre 5](/course/fr/chapter5), un `Dataset` possède une méthode `train_test_split()` qui peut nous aider. Nous allons fournir une graine pour la reproductibilité : +Nous avons 210 173 paires de phrases. Cependant regroupées dans un seul échantillon. Nous devrons donc créer notre propre jeu de validation. Comme nous l'avons vu dans le [chapitre 5](/course/fr/chapter5), un `Dataset` possède une méthode `train_test_split()` qui peut nous aider. Nous allons fournir une graine pour la reproductibilité : ```py split_datasets = raw_datasets["train"].train_test_split(train_size=0.9, seed=20) @@ -98,7 +98,7 @@ DatasetDict({ }) ``` -Nous pouvons renommer la clé "test" en "validation" comme ceci : +Nous pouvons renommer la clé `test` en `validation` comme ceci : ```py split_datasets["validation"] = split_datasets.pop("test") @@ -115,8 +115,8 @@ split_datasets["train"][1]["translation"] 'fr': 'Par défaut, développer les fils de discussion'} ``` -Nous obtenons un dictionnaire contenant deux phrases dans la paire de langues demandée. -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 par le plus correct "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 la plus facile de laisser le mot tel quel : +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 : ```py from transformers import pipeline @@ -130,8 +130,8 @@ translator("Default to expanded threads") [{'translation_text': 'Par défaut pour les threads élargis'}] ``` -Un autre exemple de ce comportement peut être observé avec le mot "*plugin*", qui n'est pas officiellement un mot français mais que la plupart des locuteurs natifs comprendront et ne prendront pas la peine de traduire. -Dans le jeu de données KDE4, ce mot a été traduit en français par le plus officiel "module d'extension" : +Un autre exemple de ce comportement peut être observé avec le mot « *plugin* » qui n'est pas officiellement un mot français mais que la plupart des francophones comprendront et ne prendront pas la peine de traduire. +Dans le jeu de données KDE4, ce mot a été traduit en français par le plus officiel « module d'extension » : ```py split_datasets["train"][172]["translation"] @@ -142,7 +142,7 @@ split_datasets["train"][172]["translation"] 'fr': "Impossible d'importer %1 en utilisant le module d'extension d'importation OFX. Ce fichier n'a pas un format correct."} ``` -Notre modèle pré-entraîné, cependant, s'en tient au mot anglais compact et familier : +Notre modèle pré-entraîné, lui, s'en tient au mot anglais : ```py translator( @@ -154,13 +154,13 @@ translator( [{'translation_text': "Impossible d'importer %1 en utilisant le plugin d'importateur OFX. Ce fichier n'est pas le bon format."}] ``` -Il sera intéressant de voir si notre modèle *finetuné* tient compte de ces particularités de l'ensemble de données (alerte *spoiler* : il le fera). +Il sera intéressant de voir si notre modèle *finetuné* tient compte de ces particularités (alerte *spoiler* : il le fera). -✏️ **Votre tour !** Un autre mot anglais souvent utilisé en français est "email". Trouvez le premier échantillon dans l'ensemble de données d'entraînement qui utilise ce mot. Comment est-il traduit ? Comment le modèle pré-entraîné traduit-il la même phrase anglaise ? +✏️ **A votre tour !** Un autre mot anglais souvent utilisé en français est « *email* ». Trouvez le premier échantillon dans l'échantillon d'entraînement qui utilise ce mot. Comment est-il traduit ? Comment le modèle pré-entraîné traduit-il cette même phrase ? @@ -177,11 +177,11 @@ model_checkpoint = "Helsinki-NLP/opus-mt-en-fr" tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, return_tensors="tf") ``` -Vous pouvez également remplacer le `model_checkpoint` par tout autre modèle que vous préférez à partir du [*Hub*](https://huggingface.co/models), ou un dossier local où vous avez sauvegardé un modèle pré-entraîné et un *tokenizer*. +Vous pouvez remplacer le `model_checkpoint` par un tout autre modèle disponible sur le [*Hub*](https://huggingface.co/models) qui aurait votre préférence, ou par un dossier en local où vous avez sauvegardé un modèle pré-entraîné et un *tokenizer*. -💡 Si vous utilisez un *tokenizer* multilingue tel que mBART, mBART-50, ou M2M100, vous devrez définir les codes de langue de vos entrées et cibles dans le *tokenizer* en définissant `tokenizer.src_lang` et `tokenizer.tgt_lang` aux bonnes valeurs. +💡 Si vous utilisez un *tokenizer* multilingue tel que mBART, mBART-50 ou M2M100, vous devrez définir les codes de langue de vos entrées et cibles dans le *tokenizer* en définissant `tokenizer.src_lang` et `tokenizer.tgt_lang` aux bonnes valeurs. @@ -194,7 +194,7 @@ with open(file_path) as f: content = f.read() ``` -Ici, les deux opérations connexes qui sont exécutées en paire sont les actions d'ouverture et de fermeture du fichier. L'objet correspondant au fichier ouvert `f` n'existe qu'à l'intérieur du bloc indenté sous le `with` ; l'ouverture se produit avant ce bloc et la fermeture à la fin du bloc. +Ici, les deux opérations connexes qui sont exécutées en paire sont les actions d'ouverture et de fermeture du fichier. L'objet correspondant au fichier ouvert `f` n'existe qu'à l'intérieur du bloc indenté sous le `with`. L'ouverture se produit avant ce bloc et la fermeture à la fin du bloc. Dans le cas présent, le gestionnaire de contexte `as_target_tokenizer()` va définir le *tokenizer* dans la langue de sortie (ici, le français) avant l'exécution du bloc indenté, puis le redéfinir dans la langue d'entrée (ici, l'anglais). @@ -209,7 +209,7 @@ with tokenizer.as_target_tokenizer(): targets = tokenizer(fr_sentence) ``` -Si nous oublions de tokeniser les cibles dans le gestionnaire de contexte, elles seront tokenisées par le *tokenizer* d'entrée, ce qui, dans le cas d'un modèle marial, ne va pas du tout bien se passer : +Si nous oublions de tokeniser les cibles dans le gestionnaire de contexte, elles seront tokenisées par le *tokenizer* d'entrée, ce qui dans le cas d'un modèle Marian, ne va pas du tout bien se passer : ```python wrong_targets = tokenizer(fr_sentence) @@ -222,9 +222,9 @@ print(tokenizer.convert_ids_to_tokens(targets["input_ids"])) ['▁Par', '▁défaut', ',', '▁développer', '▁les', '▁fils', '▁de', '▁discussion', ''] ``` -Comme on peut le voir, utiliser le *tokenizer* anglais pour prétraiter une phrase française donne un batch de *tokens* plus important, puisque le *tokenizer* ne connaît aucun mot français (sauf ceux qui apparaissent aussi en anglais, comme "discussion"). +Comme on peut le voir, utiliser le *tokenizer* anglais pour prétraiter une phrase française donne un batch de *tokens* plus important, puisque le *tokenizer* ne connaît aucun mot français (sauf ceux qui apparaissent aussi en anglais, comme « discussion »). -Les `inputs` et les `targets` sont des dictionnaires avec nos clés habituelles (identifiants d'entrée, masque d'attention, etc.), donc la dernière étape est de définir une clé `"labels"` dans les entrées. Nous faisons cela dans la fonction de prétraitement que nous allons appliquer sur les jeux de données : +Les `inputs` et les `targets` sont des dictionnaires avec nos clés habituelles (identifiants d'entrée, masque d'attention, etc.). La dernière étape est de définir une clé `"labels"` dans les entrées. Nous faisons cela dans la fonction de prétraitement que nous allons appliquer sur les jeux de données : ```python max_input_length = 128 @@ -236,7 +236,7 @@ def preprocess_function(examples): targets = [ex["fr"] for ex in examples["translation"]] model_inputs = tokenizer(inputs, max_length=max_input_length, truncation=True) - # Set up the tokenizer for targets + # Configurer le tokenizer pour les cibles. with tokenizer.as_target_tokenizer(): labels = tokenizer(targets, max_length=max_target_length, truncation=True) @@ -248,17 +248,17 @@ Notez que nous avons fixé des longueurs maximales similaires pour nos entrées -💡 Si vous utilisez un modèle T5 (plus précisément, un des points de contrôle `t5-xxx`), le modèle s'attendra à ce que les entrées de texte aient un préfixe indiquant la tâche à accomplir, comme Si vous utilisez un modèle T5 (plus précisément, un des points de contrôle `t5-xxx`), le modèle s'attendra à ce que les entrées de texte aient un préfixe indiquant la tâche à accomplir, comme `translate : Anglais vers Français:`.. +💡 Si vous utilisez un modèle T5 (plus précisément, un des *checkpoints* `t5-xxx`), le modèle s'attendra à ce que les entrées aient un préfixe indiquant la tâche à accomplir, comme `translate: English to French:`. -⚠️ Nous ne faisons pas attention au masque d'attention des cibles, car le modèle ne s'y attend pas. Au lieu de cela, les étiquettes correspondant à un *token* de *padding* doivent être mises à `-100` afin qu'elles soient ignorées dans le calcul de la perte. Cela sera fait par notre collateur de données plus tard puisque nous appliquons le *padding* dynamique, mais si vous utilisez le *padding* ici, vous devriez adapter la fonction de prétraitement pour mettre tous les labels qui correspondent au *token* de *padding* à `-100`. +⚠️ Nous ne faisons pas attention au masque d'attention des cibles car le modèle ne s'y attend pas. Au lieu de cela, les étiquettes correspondant à un *token* de *padding* doivent être mises à `-100` afin qu'elles soient ignorées dans le calcul de la perte. Cela sera fait par notre collateur de données plus tard puisque nous appliquons le *padding* dynamique, mais si vous utilisez le *padding* ici, vous devriez adapter la fonction de prétraitement pour mettre toutes les étiquettes qui correspondent au *token* de *padding* à `-100`. -Nous pouvons maintenant appliquer ce prétraitement en une seule fois sur toutes les divisions de notre jeu de données : +Nous pouvons maintenant appliquer ce prétraitement en une seule fois sur toutes les échantillons de notre jeu de données : ```py tokenized_datasets = split_datasets.map( @@ -272,11 +272,11 @@ Maintenant que les données ont été prétraitées, nous sommes prêts à *fine {#if fw === 'pt'} -## *Finetuner* le modèle avec l'API `Trainer`. +## Finetuner le modèle avec l'API `Trainer` -Le code actuel utilisant le `Trainer` sera le même que précédemment, avec juste un petit changement : nous utilisons ici un [`Seq2SeqTrainer`](https://huggingface.co/transformers/main_classes/trainer.html#seq2seqtrainer), qui est une sous-classe de `Trainer` qui nous permettra de traiter correctement l'évaluation, en utilisant la méthode `generate()` pour prédire les sorties à partir des entrées. Nous y reviendrons plus en détail lorsque nous parlerons du calcul de la métrique. +Le code actuel utilisant `Trainer` sera le même que précédemment, avec juste un petit changement : nous utilisons ici [`Seq2SeqTrainer`](https://huggingface.co/transformers/main_classes/trainer.html#seq2seqtrainer) qui est une sous-classe de `Trainer` qui nous permet de traiter correctement l'évaluation, en utilisant la méthode `generate()` pour prédire les sorties à partir des entrées. Nous y reviendrons plus en détail lorsque nous parlerons du calcul de la métrique. -Tout d'abord, nous avons besoin d'un modèle réel à affiner. Nous allons utiliser l'API habituelle `AutoModel` : +Tout d'abord, nous avons besoin d'un modèle à *finetuner*. Nous allons utiliser l'API habituelle `AutoModel` : ```py from transformers import AutoModelForSeq2SeqLM @@ -286,9 +286,9 @@ model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) {:else} -## *Finetuning* du modèle avec Keras +## Finetuner du modèle avec Keras -Tout d'abord, nous avons besoin d'un modèle réel à *finetuner*. Nous allons utiliser l'API habituelle `AutoModel` : +Tout d'abord, nous avons besoin d'un modèle à *finetuner*. Nous allons utiliser l'API habituelle `AutoModel` : ```py from transformers import TFAutoModelForSeq2SeqLM @@ -298,8 +298,7 @@ model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint, from_pt=True) -💡 Le *checkpoint* `Helsinki-NLP/opus-mt-en-fr` ne dispose que de poids PyTorch, donc vous aurez une erreur si vous essayez de charger le modèle sans utiliser l'argument -`from_pt=True` dans la méthode `from_pretrained()`. Lorsque vous spécifiez `from_pt=True`, la bibliothèque téléchargera et convertira automatiquement les poids PyTorch pour vous. Comme vous pouvez le constater, il est très simple de passer d'un framework à l'autre dans 🤗 *Transformers* ! +💡 Le *checkpoint* `Helsinki-NLP/opus-mt-en-fr` ne dispose que de poids PyTorch, vous aurez donc une erreur si vous essayez de charger le modèle sans utiliser l'argument `from_pt=True` dans la méthode `from_pretrained()`. Lorsque vous spécifiez `from_pt=True`, la bibliothèque téléchargera et convertira automatiquement les poids PyTorch pour vous. Comme vous pouvez le constater, c'est très simple de passer d'un *framework* à l'autre dans 🤗 *Transformers* ! @@ -309,9 +308,9 @@ Notez que cette fois-ci, nous utilisons un modèle qui a été entraîné sur un ### Collecte des données -Nous aurons besoin d'un assembleur de données pour gérer le rembourrage pour la mise en lots dynamique. Nous ne pouvons pas simplement utiliser un `DataCollatorWithPadding` comme dans [Chapter 3](/course/fr/chapter3) dans ce cas, parce que cela ne rembourre que les entrées (ID d'entrée, masque d'attention, et ID de type de jeton). Nos étiquettes doivent également être rembourrées à la longueur maximale rencontrée dans les étiquettes. Et, comme mentionné précédemment, la valeur de remplissage utilisée pour remplir les étiquettes doit être `-100` et non le jeton de remplissage du *tokenizer*, pour s'assurer que ces valeurs remplies sont ignorées dans le calcul de la perte. +Nous aurons besoin d'un assembleur de données pour gérer le rembourrage pour la mise en batchs dynamique. Ici, nous ne pouvons pas simplement utiliser un `DataCollatorWithPadding` comme dans le [chapitre 3](/course/fr/chapter3) car cela ne rembourre que les entrées (identifiants d'entrée, masque d'attention, et *token* de type identifiants). Nos étiquettes doivent également être rembourrées à la longueur maximale rencontrée dans les étiquettes. Et, comme mentionné précédemment, la valeur de remplissage utilisée pour remplir les étiquettes doit être `-100` et non le *token* de *padding* du *tokenizer* afin de s'assurer que ces valeurs soient ignorées dans le calcul de la perte. -Tout ceci est réalisé par un [`DataCollatorForSeq2Seq`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorforseq2seq). Comme le `DataCollatorWithPadding`, il prend le `tokenizer` utilisé pour prétraiter les entrées, mais il prend aussi le `model`. C'est parce que ce collateur de données sera également responsable de la préparation des ID d'entrée du décodeur, qui sont des versions décalées des étiquettes avec un jeton spécial au début. Comme ce décalage est effectué de manière légèrement différente selon les architectures, le `DataCollatorForSeq2Seq` a besoin de connaître l'objet `model` : +Tout ceci est réalisé par un [`DataCollatorForSeq2Seq`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorforseq2seq). Comme le `DataCollatorWithPadding`, il prend le `tokenizer` utilisé pour prétraiter les entrées, mais également le `model`. C'est parce que ce collateur de données est également responsable de la préparation des identifiants d'entrée du décodeur, qui sont des versions décalées des étiquettes avec un *token* spécial au début. Comme ce décalage est effectué de manière légèrement différente selon les architectures, le `DataCollatorForSeq2Seq` a besoin de connaître l'objet `model` : {#if fw === 'pt'} @@ -331,7 +330,7 @@ data_collator = DataCollatorForSeq2Seq(tokenizer, model=model, return_tensors="t {/if} -Pour le tester sur quelques échantillons, nous l'appelons simplement sur une liste d'exemples de notre ensemble d'entrainement tokénisé : +Pour le tester sur quelques échantillons, nous l'appelons simplement sur une liste d'exemples de notre échantillon d'entrainement tokénisé : ```py batch = data_collator([tokenized_datasets["train"][i] for i in range(1, 3)]) @@ -342,7 +341,7 @@ batch.keys() dict_keys(['attention_mask', 'input_ids', 'labels', 'decoder_input_ids']) ``` -Nous pouvons vérifier que nos étiquettes ont été paddées à la longueur maximale du lot, en utilisant `-100` : +Nous pouvons vérifier que nos étiquettes ont été rembourrées à la longueur maximale du batch, en utilisant `-100` : ```py batch["labels"] @@ -355,7 +354,7 @@ tensor([[ 577, 5891, 2, 3184, 16, 2542, 5, 1710, 0, -100, 550, 7032, 5821, 7907, 12649, 0]]) ``` -Et nous pouvons également jeter un coup d'œil aux ID d'entrée du décodeur, pour voir qu'il s'agit de versions décalées des étiquettes : +Nous pouvons aussi jeter un coup d'œil aux identifiants d'entrée du décodeur, pour voir qu'il s'agit de versions décalées des étiquettes : ```py batch["decoder_input_ids"] @@ -412,21 +411,21 @@ tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( {#if fw === 'pt'} -La fonctionnalité que `Seq2SeqTrainer` ajoute à sa superclasse `Trainer` est la possibilité d'utiliser la méthode `generate()` pendant l'évaluation ou la prédiction. Pendant l'entraînement, le modèle utilisera les `decoder_input_ids` avec un masque d'attention assurant qu'il n'utilise pas les *tokens* après le *token* qu'il essaie de prédire, pour accélérer l'entraînement. Pendant l'inférence, nous ne pourrons pas les utiliser puisque nous n'aurons pas d'étiquettes, donc c'est une bonne idée d'évaluer notre modèle avec la même configuration. +La fonctionnalité que `Seq2SeqTrainer` ajoute à sa superclasse `Trainer` est la possibilité d'utiliser la méthode `generate()` pendant l'évaluation ou la prédiction. Pendant l'entraînement, le modèle utilisera les `decoder_input_ids` avec un masque d'attention assurant qu'il n'utilise pas les *tokens* après le *token* qu'il essaie de prédire, pour accélérer l'entraînement. Pendant l'inférence, nous ne pourrons pas les utiliser puisque nous n'aurons pas d'étiquettes. Ainsi c'est une bonne idée d'évaluer notre modèle avec la même configuration. -Comme nous l'avons vu dans le [Chapitre 1](/course/fr/chapter1/6), le décodeur effectue l'inférence en prédisant les *tokens* un par un. Quelque chose qui est implémenté en coulisses dans les 🤗 Transformers par la méthode `generate()`. Le `Seq2SeqTrainer` nous laissera utiliser cette méthode pour l'évaluation si nous définissons `predict_with_generate=True`. +Comme nous l'avons vu dans le [chapitre 1](/course/fr/chapter1/6), le décodeur effectue l'inférence en prédisant les *tokens* un par un. C'est quelque chose qui est implémenté en coulisses dans 🤗 *Transformers* par la méthode `generate()`. Le `Seq2SeqTrainer` nous laissera utiliser cette méthode pour l'évaluation si nous indiquons `predict_with_generate=True`. {/if} -La métrique traditionnelle utilisée pour la traduction est le [score BLEU](https://en.wikipedia.org/wiki/BLEU), introduit dans [un article de 2002](https://aclanthology.org/P02-1040.pdf) par Kishore Papineni et al. Le score BLEU évalue dans quelle mesure les traductions sont proches de leurs étiquettes. Il ne mesure pas l'intelligibilité ou l'exactitude grammaticale des résultats générés par le modèle, mais utilise des règles statistiques pour garantir que tous les mots des résultats générés apparaissent également dans les cibles. En outre, il existe des règles qui pénalisent les répétitions des mêmes mots s'ils ne sont pas également répétés dans les cibles (pour éviter que le modèle ne produise des phrases telles que "the the the the the the the") et les phrases produites qui sont plus courtes que celles des cibles (pour éviter que le modèle ne produise des phrases telles que "the"). +La métrique traditionnelle utilisée pour la traduction est le [score BLEU](https://en.wikipedia.org/wiki/BLEU), introduit dans [un article de 2002](https://aclanthology.org/P02-1040.pdf) par Kishore Papineni et al. Le score BLEU évalue dans quelle mesure les traductions sont proches de leurs étiquettes. Il ne mesure pas l'intelligibilité ou l'exactitude grammaticale des résultats générés par le modèle, mais utilise des règles statistiques pour garantir que tous les mots des résultats générés apparaissent également dans les cibles. En outre, il existe des règles qui pénalisent les répétitions des mêmes mots s'ils ne sont pas également répétés dans les cibles (pour éviter que le modèle ne produise des phrases telles que « the the the the the the the ») et les phrases produites qui sont plus courtes que celles des cibles (pour éviter que le modèle ne produise des phrases telles que « the »). -L'une des faiblesses de BLEU est qu'il s'attend à ce que le texte soit déjà tokenisé, ce qui rend difficile la comparaison des scores entre les modèles qui utilisent différents *tokenizers*. Par conséquent, la mesure la plus couramment utilisée aujourd'hui pour évaluer les modèles de traduction est [SacreBLEU](https://github.com/mjpost/sacrebleu), qui remédie à cette faiblesse (et à d'autres) en standardisant l'étape de tokenisation. Pour utiliser cette métrique, nous devons d'abord installer la bibliothèque SacreBLEU : +L'une des faiblesses de BLEU est qu'il s'attend à ce que le texte soit déjà tokenisé, ce qui rend difficile la comparaison des scores entre les modèles qui utilisent différents *tokenizers*. Par conséquent, la mesure la plus couramment utilisée aujourd'hui pour évaluer les modèles de traduction est [SacreBLEU](https://github.com/mjpost/sacrebleu) qui remédie à cette faiblesse (et à d'autres) en standardisant l'étape de tokenisation. Pour utiliser cette métrique, nous devons d'abord installer la bibliothèque *SacreBLEU* : ```py !pip install sacrebleu ``` -Nous pouvons ensuite le charger via `load_metric()` comme nous l'avons fait dans le [Chapitre 3](/course/fr/chapter3) : +Nous pouvons ensuite charger ce score via `load_metric()` comme nous l'avons fait dans le [chapitre 3](/course/fr/chapter3) : ```py from datasets import load_metric @@ -434,7 +433,7 @@ from datasets import load_metric metric = load_metric("sacrebleu") ``` -Cette métrique prend des textes comme entrées et cibles. Elle est conçue pour accepter plusieurs cibles acceptables, car il y a souvent plusieurs traductions acceptables de la même phrase. Le jeu de données que nous utilisons n'en fournit qu'une seule, mais il n'est pas rare en NLP de trouver des jeux de données qui donnent plusieurs phrases comme étiquettes. Ainsi, les prédictions doivent être une liste de phrases, mais les références doivent être une liste de listes de phrases. +Cette métrique prend des textes comme entrées et cibles. Elle est conçue pour accepter plusieurs cibles acceptables car il y a souvent plusieurs traductions possibles d'une même phrase. Le jeu de données que nous utilisons n'en fournit qu'une seule, mais en NLP, il n'est pas rare de trouver des jeux de données ayant plusieurs phrases comme étiquettes. Ainsi, les prédictions doivent être une liste de phrases mais les références doivent être une liste de listes de phrases. Essayons un exemple : @@ -460,7 +459,7 @@ metric.compute(predictions=predictions, references=references) 'ref_len': 13} ``` -Cela donne un score BLEU de 46.75, ce qui est plutôt bon. Pour référence, le Transformer original dans l'article ["Attention Is All You Need"](https://arxiv.org/pdf/1706.03762.pdf) a obtenu un score BLEU de 41.8 sur une tâche de traduction similaire entre l'anglais et le français ! (Pour plus d'informations sur les métriques individuelles, comme `counts` et `bp`, voir le [Dépôt SacreBLEU](https://github.com/mjpost/sacrebleu/blob/078c440168c6adc89ba75fe6d63f0d922d42bcfe/sacrebleu/metrics/bleu.py#L74).) D'autre part, si nous essayons avec les deux mauvais types de prédictions (batchs de répétitions ou trop courts) qui sortent souvent des modèles de traduction, nous obtiendrons des scores BLEU plutôt mauvais : +Cela donne un score BLEU de 46.75, ce qui est plutôt bon. A titre de comparaison, le *Transformer* original dans l'article [*Attention Is All You Need*](https://arxiv.org/pdf/1706.03762.pdf) a obtenu un score BLEU de 41.8 sur une tâche de traduction similaire entre l'anglais et le français ! (Pour plus d'informations sur les métriques individuelles, comme `counts` et `bp`, voir le [dépôt SacreBLEU](https://github.com/mjpost/sacrebleu/blob/078c440168c6adc89ba75fe6d63f0d922d42bcfe/sacrebleu/metrics/bleu.py#L74). D'autre part, si nous essayons avec les deux mauvais types de prédictions (répétitions ou prédiction trop courte) qui sortent souvent des modèles de traduction, nous obtiendrons des scores BLEU plutôt mauvais : ```py predictions = ["This This This This"] @@ -502,11 +501,11 @@ metric.compute(predictions=predictions, references=references) 'ref_len': 13} ``` -Le score peut aller de 0 à 100, et plus il est élevé, mieux c'est. +Le score peut aller de 0 à 100. Plus il est élevé, mieux c'est. {#if fw === 'tf'} -Pour passer des sorties du modèle aux textes que la métrique peut utiliser, nous allons utiliser la méthode `tokenizer.batch_decode()`. Nous devons juste nettoyer tous les `-100` dans les étiquettes ; le *tokenizer* fera automatiquement la même chose pour le *token* de *padding*. Définissons une fonction qui prend notre modèle et un jeu de données et calcule des métriques sur ceux-ci. Comme la génération de longues séquences peut être lente, nous sous-échantillonnons l'ensemble de validation pour nous assurer que cela ne prend pas une éternité : +Pour passer des sorties du modèle aux textes que la métrique peut utiliser, nous allons utiliser la méthode `tokenizer.batch_decode()`. Nous devons juste nettoyer tous les `-100` dans les étiquettes. Le *tokenizer* fera automatiquement la même chose pour le *token* de *padding*. Définissons une fonction qui prend notre modèle et un jeu de données et calcule des métriques sur ceux-ci. Comme la génération de longues séquences peut être lente, nous sous-échantillonnons l'ensemble de validation pour nous assurer que cela ne prend pas une éternité : ```py import numpy as np @@ -541,7 +540,7 @@ def compute_metrics(): {:else} -Pour passer des sorties du modèle aux textes utilisables par la métrique, nous allons utiliser la méthode `tokenizer.batch_decode()`. Nous devons juste nettoyer tous les `-100`s dans les étiquettes (le tokenizer fera automatiquement la même chose pour le token de remplissage) : +Pour passer des sorties du modèle aux textes utilisables par la métrique, nous allons utiliser la méthode `tokenizer.batch_decode()`. Nous devons juste nettoyer tous les `-100` dans les étiquettes. Le *tokenizer* fera automatiquement la même chose pour le *token* de *padding* : ```py import numpy as np @@ -549,17 +548,17 @@ import numpy as np def compute_metrics(eval_preds): preds, labels = eval_preds - # In case the model returns more than the prediction logits + # Dans le cas où le modèle retourne plus que les logits de prédiction if isinstance(preds, tuple): preds = preds[0] decoded_preds = tokenizer.batch_decode(preds, skip_special_tokens=True) - # Replace -100s in the labels as we can't decode them + # Remplacer les -100 dans les étiquettes car nous ne pouvons pas les décoder labels = np.where(labels != -100, labels, tokenizer.pad_token_id) decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) - # Some simple post-processing + # Quelques post-traitements simples decoded_preds = [pred.strip() for pred in decoded_preds] decoded_labels = [[label.strip()] for label in decoded_labels] @@ -569,10 +568,10 @@ def compute_metrics(eval_preds): {/if} -Maintenant que c'est fait, nous sommes prêts à affiner notre modèle ! +Maintenant que c'est fait, nous sommes prêts à *finetuner* notre modèle ! -### *Finetuner* le modèle +### Finetuner le modèle La première étape consiste à se connecter à Hugging Face, afin de pouvoir télécharger vos résultats sur le *Hub*. Il y a une fonction pratique pour vous aider à le faire dans un *notebook* : @@ -582,7 +581,7 @@ from huggingface_hub import notebook_login notebook_login() ``` -Cela affichera un widget où vous pourrez entrer vos identifiants de connexion à Hugging Face. +Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face. Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : @@ -609,9 +608,9 @@ from transformers import create_optimizer from transformers.keras_callbacks import PushToHubCallback import tensorflow as tf -# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans l'ensemble de données, divisé par la taille du lot, puis multiplié par le nombre total d'époques. -# par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un lot tf.data.Dataset, -# et non le jeu de données original Hugging Face Dataset, donc son len() est déjà num_samples // batch_size. +# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans le jeu de données, divisé par la taille du batch, +# puis multiplié par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un tf.data.Dataset, +# et non le jeu de données original donc son len() est déjà num_samples // batch_size. num_epochs = 3 num_train_steps = len(tf_train_dataset) * num_epochs @@ -627,7 +626,7 @@ model.compile(optimizer=optimizer) tf.keras.mixed_precision.set_global_policy("mixed_float16") ``` -Ensuite, nous définissons un `PushToHubCallback` pour télécharger notre modèle sur le *Hub* pendant l'entraînement, comme nous l'avons vu dans [section 2]((/course/fr/chapter7/2)), et ensuite nous ajustons simplement le modèle avec ce callback : +Ensuite, nous définissons un `PushToHubCallback` pour télécharger notre modèle sur le *Hub* pendant l'entraînement, comme nous l'avons vu dans la [section 2](/course/fr/chapter7/2), puis nous entraînons simplement le modèle avec ce *callback* : ```python from transformers.keras_callbacks import PushToHubCallback @@ -644,7 +643,7 @@ model.fit( ) ``` -Notez que vous pouvez spécifier le nom du référentiel vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"``Seq2SeqTrainingArguments`. Par défaut, le référentiel utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, donc ici ce sera `"sgugger/marian-finetuned-kde4-en-to-fr"` (qui est le modèle que nous avons lié au début de cette section). +Notez que vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser le modèle avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"` dans `Seq2SeqTrainingArguments`. Par défaut, le dépôt utilisé sera dans votre espace et nommé après le répertoire de sortie que vous avez défini. Ici ce sera `"sgugger/marian-finetuned-kde4-en-to-fr"` (qui est le modèle que nous avons lié au début de cette section). @@ -652,7 +651,7 @@ Notez que vous pouvez spécifier le nom du référentiel vers lequel vous voulez -Enfin, voyons à quoi ressemblent nos mesures maintenant que l'entraînement est terminé : +Enfin, voyons à quoi ressemblent nos métriques maintenant que l'entraînement est terminé : ```py print(compute_metrics()) @@ -662,7 +661,7 @@ print(compute_metrics()) {'bleu': 57.334066271545865} ``` -À ce stade, vous pouvez utiliser le widget d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à *finetuner* un modèle sur une tâche de traduction. Félicitations ! +À ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à *finetuner* un modèle sur une tâche de traduction. Félicitations ! {:else} @@ -687,14 +686,14 @@ args = Seq2SeqTrainingArguments( ) ``` -En dehors des hyperparamètres habituels (comme le taux d'apprentissage, le nombre d'époques, la taille du lot et une certaine décroissance des poids), voici quelques changements par rapport à ce que nous avons vu dans les sections précédentes : +En dehors des hyperparamètres habituels (comme le taux d'apprentissage, le nombre d'époques, la taille des batchs et une le taux de décroissance des poids), voici quelques changements par rapport à ce que nous avons vu dans les sections précédentes : -- nous ne définissons pas d'évaluation régulière, car l'évaluation prend du temps ; nous allons juste évaluer notre modèle une fois avant l'entraînement et après, -- nous avons mis `fp16=True`, ce qui accélère l'entraînement sur les GPUs modernes, -- nous définissons `predict_with_generate=True`, comme discuté ci-dessus, -- nous utilisons `push_to_hub=True` pour télécharger le modèle sur le *Hub* à la fin de chaque époque. +- Nous ne définissons pas d'évaluation car elle prend du temps. Nous allons juste évaluer une fois notre modèle avant l'entraînement et après. +- Nous avons mis `fp16=True`, ce qui accélère l'entraînement sur les GPUs modernes. +- Nous définissons `predict_with_generate=True`, comme discuté ci-dessus. +- Nous utilisons `push_to_hub=True` pour télécharger le modèle sur le *Hub* à la fin de chaque époque. -Notez que vous pouvez spécifier le nom complet du référentiel vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"` `Seq2SeqTrainingArguments`. Par défaut, le référentiel utilisé sera dans votre espace de noms et nommé d'après le répertoire de sortie que vous avez défini, donc dans notre cas ce sera `"sgugger/marian-finetuned-kde4-en-to-fr"` (qui est le modèle que nous avons lié au début de cette section). +Notez que vous pouvez spécifier le nom complet du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"` à `Seq2SeqTrainingArguments`. Par défaut, le dépôt utilisé sera dans votre espace et nommé d'après le répertoire de sortie que vous avez défini. Dans notre cas ce sera `"sgugger/marian-finetuned-kde4-en-to-fr"` (qui est le modèle que nous avons lié au début de cette section). @@ -733,9 +732,9 @@ trainer.evaluate(max_length=max_target_length) 'eval_steps_per_second': 0.341} ``` -A BLEU score of 39 is not too bad, which reflects the fact that our model is already good at translating English sentences to French ones. +Un score BLEU de 39 n'est pas trop mauvais, ce qui reflète le fait que notre modèle est déjà bon pour traduire des phrases anglaises en phrases françaises. -Next is the training, which will also take a bit of time: +Vient ensuite l'entraînement, qui prendra également un peu de temps : ```python trainer.train() @@ -743,7 +742,7 @@ trainer.train() Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. De cette façon, vous serez en mesure de reprendre votre entraînement sur une autre machine si nécessaire. -Une fois l'entraînement terminé, nous évaluons à nouveau notre modèle - avec un peu de chance, nous verrons une amélioration du score BLEU ! +Une fois l'entraînement terminé, nous évaluons à nouveau notre modèle. Avec un peu de chance, nous verrons une amélioration du score BLEU ! ```py trainer.evaluate(max_length=max_target_length) @@ -760,7 +759,7 @@ trainer.evaluate(max_length=max_target_length) C'est une amélioration de près de 14 points, ce qui est formidable. -Enfin, nous utilisons la méthode `push_to_hub()` pour nous assurer que nous téléchargeons la dernière version du modèle. Le `Trainer` rédige également une carte modèle avec tous les résultats de l'évaluation et la télécharge. Cette carte de modèle contient des métadonnées qui aident le *Hub* à choisir le widget pour la démo d'inférence. Habituellement, il n'y a pas besoin de dire quoi que ce soit car il peut inférer le bon *widget* à partir de la classe du modèle, mais dans ce cas, la même classe de modèle peut être utilisée pour toutes sortes de problèmes de séquence à séquence, donc nous spécifions que c'est un modèle de traduction : +Enfin, nous utilisons la méthode `push_to_hub()` pour nous assurer que nous téléchargeons la dernière version du modèle. `Trainer` rédige également une carte de modèle avec tous les résultats de l'évaluation et la télécharge. Cette carte de modèle contient des métadonnées qui aident le *Hub* à choisir le *widget* pour l'inférence. Habituellement, il n'y a pas besoin de dire quoi que ce soit car il peut inférer le bon *widget* à partir de la classe du modèle, mais dans ce cas, la même classe de modèle peut être utilisée pour toutes sortes de problèmes de séquence à séquence. Ainsi nous spécifions que c'est un modèle de traduction : ```py trainer.push_to_hub(tags="translation", commit_message="Training complete") @@ -772,7 +771,7 @@ Cette commande renvoie l'URL du commit qu'elle vient de faire, si vous voulez l' 'https://huggingface.co/sgugger/marian-finetuned-kde4-en-to-fr/commit/3601d621e3baae2bc63d3311452535f8f58f6ef3' ``` -À ce stade, vous pouvez utiliser le widget d'inférence sur le Hub du modèle pour tester votre modèle et le partager avec vos amis. Vous avez réussi à *finetuner* un modèle sur une tâche de traduction. Félicitations ! +À ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à *finetuner* un modèle sur une tâche de traduction. Félicitations ! Si vous souhaitez vous plonger un peu plus profondément dans la boucle d'entraînement, nous allons maintenant vous montrer comment faire la même chose en utilisant 🤗 *Accelerate*. @@ -782,7 +781,7 @@ Si vous souhaitez vous plonger un peu plus profondément dans la boucle d'entra ## Une boucle d'entraînement personnalisée -Jetons maintenant un coup d'œil à la boucle d'entraînement complète, afin que vous puissiez facilement personnaliser les parties dont vous avez besoin. Elle ressemblera beaucoup à ce que nous avons fait dans la [section 2](/course/fr/chapter7/2) et le [chapitre 3](/course/fr/chapter3/4). +Jetons maintenant un coup d'œil à la boucle d'entraînement complète afin que vous puissiez facilement personnaliser les parties dont vous avez besoin. Elle ressemblera beaucoup à ce que nous avons fait dans la [section 2](/course/fr/chapter7/2) et dans le [chapitre 3](/course/fr/chapter3/4). ### Préparer le tout pour l'entraînement @@ -803,7 +802,7 @@ eval_dataloader = DataLoader( ) ``` -Ensuite, nous réinstantifions notre modèle, pour nous assurer que nous ne poursuivons pas l'affinage précédent, mais que nous repartons du modèle pré-entraîné : +Ensuite, nous réinstantifions notre modèle pour nous assurer que nous ne poursuivons pas le *finetuning* précédent et que nous repartons du modèle pré-entraîné : ```py model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) @@ -817,7 +816,7 @@ from transformers import AdamW optimizer = AdamW(model.parameters(), lr=2e-5) ``` -Une fois que nous avons tous ces objets, nous pouvons les envoyer à la méthode `accelerator.prepare()`. Rappelez-vous que si vous voulez vous entraîner sur des TPUs dans un *notebook* de Colab, vous devrez déplacer tout ce code dans une fonction d'entraînement, et qui ne devrait pas exécuter une cellule qui instancie un `Accelerator`. +Une fois que nous avons tous ces objets, nous pouvons les envoyer à la méthode `accelerator.prepare()`. Rappelez-vous que si vous voulez entraîner sur des TPUs dans un *notebook* de Colab, vous devez déplacer tout ce code dans une fonction d'entraînement et ne devrait pas exécuter une cellule qui instancie un `Accelerator`. ```py from accelerate import Accelerator @@ -828,7 +827,7 @@ model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( ) ``` -Maintenant que nous avons envoyé notre `train_dataloader` à `accelerator.prepare()`, nous pouvons utiliser sa longueur pour calculer le nombre d'étapes d'entraînement. Rappelez-vous que nous devrions toujours faire cela après avoir préparé le dataloader, car cette méthode va changer la longueur du `DataLoader`. Nous utilisons un programme linéaire classique du taux d'apprentissage à 0 : +Maintenant que nous avons envoyé notre `train_dataloader` à `accelerator.prepare()`, nous pouvons utiliser sa longueur pour calculer le nombre d'étapes d'entraînement. Rappelez-vous que nous devrions toujours faire cela après avoir préparé le chargeur de données car cette méthode va changer la longueur du `DataLoader`. Nous utilisons un programme linéaire classique du taux d'apprentissage à 0 : ```py from transformers import get_scheduler @@ -845,7 +844,7 @@ lr_scheduler = get_scheduler( ) ``` -Enfin, pour pousser notre modèle vers le Hub, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous au *Hub*, si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'ID du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix ; il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `get_full_repo_name()`) : +Enfin, pour pousser notre modèle vers le *Hub*, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous au *Hub* si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'identifiant du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix, il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `get_full_repo_name()`) : ```py from huggingface_hub import Repository, get_full_repo_name @@ -859,7 +858,7 @@ repo_name 'sgugger/marian-finetuned-kde4-en-to-fr-accelerate' ``` -Ensuite, nous pouvons cloner ce référentiel dans un dossier local. S'il existe déjà, ce dossier local doit être un clone du référentiel avec lequel nous travaillons : +Ensuite, nous pouvons cloner ce dépôt dans un dossier local. S'il existe déjà, ce dossier local doit être un clone du dépôt avec lequel nous travaillons : ```py output_dir = "marian-finetuned-kde4-en-to-fr-accelerate" @@ -879,7 +878,7 @@ def postprocess(predictions, labels): decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) - # Remplacer -100 dans les étiquettes car nous ne pouvons pas les décoder. + # Remplace -100 dans les étiquettes car nous ne pouvons pas les décoder labels = np.where(labels != -100, labels, tokenizer.pad_token_id) decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) @@ -889,11 +888,11 @@ def postprocess(predictions, labels): return decoded_preds, decoded_labels ``` -La boucle d'entraînement ressemble beaucoup à celles de [section 2](/course/fr/chapter7/2) et [chapitre 3](/course/fr/chapter3), avec quelques différences dans la partie évaluation -- alors concentrons-nous sur cela ! +La boucle d'entraînement ressemble beaucoup à celles de la [section 2](/course/fr/chapter7/2) et du [chapitre 3](/course/fr/chapter3), avec quelques différences dans la partie évaluation. Donc concentrons-nous sur cela ! -La première chose à noter est que nous utilisons la méthode `generate()` pour calculer les prédictions, mais c'est une méthode sur notre modèle de base, pas le modèle enveloppé 🤗 Accelerate créé dans la méthode `prepare()`. C'est pourquoi nous déballons d'abord le modèle, puis nous appelons cette méthode. +La première chose à noter est que nous utilisons la méthode `generate()` pour calculer les prédictions. C'est une méthode sur notre modèle de base et non pas le modèle enveloppé créé dans la méthode `prepare()`. C'est pourquoi nous déballons d'abord le modèle, puis nous appelons cette méthode. -La deuxième chose est que, comme avec [token classification](/course/fr/chapter7/2), deux processus peuvent avoir paddé les entrées et les étiquettes à des formes différentes, donc nous utilisons `accelerator.pad_across_processes()` pour rendre les prédictions et les étiquettes de la même forme avant d'appeler la méthode `gather()`. Si nous ne faisons pas cela, l'évaluation va soit se tromper, soit se bloquer pour toujours. +La deuxième chose est que, comme avec la classification de [*token*](/course/fr/chapter7/2), deux processus peuvent avoir rembourrés les entrées et les étiquettes à des formes différentes. Ainsi nous utilisons `accelerator.pad_across_processes()` pour rendre les prédictions et les étiquettes de la même forme avant d'appeler la méthode `gather()`. Si nous ne faisons pas cela, l'évaluation va soit se tromper, soit se bloquer pour toujours. ```py from tqdm.auto import tqdm @@ -957,11 +956,11 @@ epoch 1, BLEU score: 54.24 epoch 2, BLEU score: 54.44 ``` -Une fois que c'est fait, vous devriez avoir un modèle qui a des résultats assez similaires à celui entraîné avec le `Seq2SeqTrainer`. Vous pouvez vérifier celui que nous avons formé en utilisant ce code à [*huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate*](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate). Et si vous voulez tester des modifications de la boucle d'entraînement, vous pouvez les mettre en œuvre directement en modifiant le code ci-dessus ! +Une fois que c'est fait, vous devriez avoir un modèle qui a des résultats assez similaires à celui entraîné avec `Seq2SeqTrainer`. Vous pouvez vérifier celui que nous avons entraîné en utilisant ce code sur [*huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate*](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate). Et si vous voulez tester des modifications de la boucle d'entraînement, vous pouvez les mettre en œuvre directement en modifiant le code ci-dessus ! {/if} -### Utilisation du modèle *finetuné*. +### Utilisation du modèle finetuné Nous vous avons déjà montré comment vous pouvez utiliser le modèle que nous avons *finetuné* sur le *Hub* avec le *widget* d'inférence. Pour l'utiliser localement dans un `pipeline`, nous devons juste spécifier l'identifiant de modèle approprié : @@ -978,7 +977,7 @@ translator("Default to expanded threads") [{'translation_text': 'Par défaut, développer les fils de discussion'}] ``` -Comme prévu, notre modèle pré-entraîné a adapté ses connaissances au corpus sur lequel nous l'avons affiné, et au lieu de laisser le mot anglais "threads" seul, il le traduit maintenant par la version officielle française. Il en va de même pour "plugin" : +Comme prévu, notre modèle pré-entraîné a adapté ses connaissances au corpus sur lequel nous l'avons *finetuné*. Et au lieu de laisser le mot anglais « *threads* », le modèle le traduit maintenant par la version française officielle. Il en va de même pour « *plugin* » : ```py translator( @@ -994,6 +993,6 @@ Un autre excellent exemple d'adaptation au domaine ! -✏️ **Votre tour !** Que retourne le modèle sur l'échantillon avec le mot "email" que vous avez identifié plus tôt ? +✏️ **A votre tour !** Que retourne le modèle sur l'échantillon avec le mot « *email* » que vous avez identifié plus tôt ? diff --git a/chapters/fr/chapter7/5.mdx b/chapters/fr/chapter7/5.mdx index 0ba896f6d..47428e425 100644 --- a/chapters/fr/chapter7/5.mdx +++ b/chapters/fr/chapter7/5.mdx @@ -27,7 +27,7 @@ Dans cette section, nous allons voir comment les *transformers* peuvent être ut -Bien qu'il existe déjà plusieurs modèles *finetunés* pour le résumé sur le [Hugging Face Hub](https://huggingface.co/models?pipeline_tag=summarization&sort=downloads), la plupart d'entre eux ne sont adaptés qu'aux documents en anglais. Ainsi, pour ajouter une touche d'originalité à cette section, nous allons entraîner un modèle bilingue pour l'anglais et l'espagnol. À la fin de cette section, vous disposerez d'un [modèle](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es) capable de résumer les commentaires des clients comme celui présenté ici : +Bien qu'il existe déjà plusieurs modèles *finetunés* pour le résumé sur le [*Hub*](https://huggingface.co/models?pipeline_tag=summarization&sort=downloads), la plupart d'entre eux ne sont adaptés qu'aux documents en anglais. Ainsi, pour ajouter une touche d'originalité à cette section, nous allons entraîner un modèle bilingue pour l'anglais et l'espagnol. À la fin de cette section, vous disposerez d'un [modèle](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es) capable de résumer les commentaires des clients comme celui présenté ici : @@ -36,7 +36,7 @@ Comme nous allons le voir, ces résumés sont concis car ils sont appris à part ## Préparation d'un corpus multilingue -Nous allons utiliser le [Multilingual Amazon Reviews Corpus](https://huggingface.co/datasets/amazon_reviews_multi) pour créer notre résumeur bilingue. Ce corpus est constitué d'évaluations de produits Amazon en six langues et est généralement utilisé pour évaluer les classificateurs multilingues. Cependant, comme chaque critique est accompagnée d'un titre court, nous pouvons utiliser les titres comme résumés cibles pour l'apprentissage de notre modèle ! Pour commencer, téléchargeons les sous-ensembles anglais et espagnols depuis le *Hub* : +Nous allons utiliser le [*Multilingual Amazon Reviews Corpus*](https://huggingface.co/datasets/amazon_reviews_multi) pour créer notre résumeur bilingue. Ce corpus est constitué de critiques de produits Amazon en six langues et est généralement utilisé pour évaluer les classifieurs multilingues. Cependant, comme chaque critique est accompagnée d'un titre court, nous pouvons utiliser les titres comme résumés cibles pour l'apprentissage de notre modèle ! Pour commencer, téléchargeons les sous-ensembles anglais et espagnols depuis le *Hub* : ```python from datasets import load_dataset @@ -63,7 +63,7 @@ DatasetDict({ }) ``` -Comme vous pouvez le voir, pour chaque langue, il y a 200 000 évaluations pour la partie "entraînement", et 5 000 évaluations pour chacune des parties "validation" et "test". Les informations qui nous intéressent sont contenues dans les colonnes `review_body` et `review_title`. Voyons quelques exemples en créant une fonction simple qui prend un échantillon aléatoire de l'ensemble d'entraînement avec les techniques apprises au [Chapitre 5](/course/fr/chapter5) : +Comme vous pouvez le voir, pour chaque langue, il y a 200 000 critiques pour la partie entraînement et 5 000 critiques pour chacune des parties validation et test. Les informations qui nous intéressent sont contenues dans les colonnes `review_body` et `review_title`. Voyons quelques exemples en créant une fonction simple qui prend un échantillon aléatoire de l'ensemble d'entraînement avec les techniques apprises au [chapitre 5](/course/fr/chapter5) : ```python def show_samples(dataset, num_samples=3, seed=42): @@ -77,18 +77,19 @@ show_samples(english_dataset) ``` ```python out -'>> Title: Worked in front position, not rear' # Travaillé en position avant, pas arrière +'>> Title: Worked in front position, not rear' +# Travaillé en position avant, pas arrière '>> 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.' -# 3 étoiles car ce ne sont pas des freins arrière comme indiqué dans la description de l'article. Au moins, l'adaptateur de montage ne fonctionnait que sur la fourche avant du vélo pour lequel je l'ai acheté.'' +# 3 étoiles car ce ne sont pas des freins arrière comme indiqué dans la description de l'article. Au moins, l'adaptateur de montage ne fonctionnait que sur la fourche avant du vélo pour lequel je l'ai acheté. '>> 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' # Il fait son travail et il est magnifique mais le mien est en train de tomber en morceaux, j'ai dû le recoller avec de la colle chaude. -'>> Title: Can\'t beat these for the money' # On ne peut pas faire mieux pour le prix +'>> Title: Can\'t beat these for the money' +# On ne peut pas faire mieux pour le prix '>> 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.' # Je l'ai acheté pour manipuler diverses pièces d'avion et des "trucs" de hangar que je devais organiser ; il a vraiment fait l'affaire. L'unité est arrivée rapidement, était bien emballée et est arrivée intacte (toujours un bon signe). Il y a cinq supports muraux - trois sur le dessus et deux sur le dessous. Je voulais le monter sur le mur, alors tout ce que j'ai eu à faire était d'enlever les deux couches supérieures de tiroirs en plastique, ainsi que les tiroirs d'angle inférieurs, de le placer où je voulais et de le marquer ; j'ai ensuite utilisé quelques-uns des nouveaux ancrages muraux à vis en plastique (la variété de 50 livres) et il s'est facilement monté sur le mur. Certains ont fait remarquer qu'ils voulaient des séparateurs pour les tiroirs, et qu'ils les ont fabriqués. Bonne idée. Pour ma part, j'avais besoin de quelque chose dont je pouvais voir le contenu à hauteur des yeux, et je voulais donc des tiroirs plus grands. J'aime aussi le fait qu'il s'agisse du nouveau plastique qui ne se fragilise pas et ne se fend pas comme mes anciens tiroirs en plastique. J'aime la construction entièrement en plastique. Elle est suffisamment résistante pour contenir des pièces métalliques, mais étant en plastique, elle n'est pas aussi lourde qu'un cadre métallique, ce qui permet de la fixer facilement au mur et de la charger d'objets lourds ou légers. Aucun problème. Pour le prix, c'est imbattable. C'est le meilleur que j'ai acheté à ce jour, et j'utilise des versions de ce type depuis plus de quarante ans. - ``` @@ -97,40 +98,40 @@ show_samples(english_dataset) -Cet échantillon montre la diversité des critiques que l'on trouve généralement en ligne, allant du positif au négatif (et tout ce qui se trouve entre les deux !). Bien que l'exemple avec le titre "meh" ne soit pas très informatif, les autres titres semblent être des résumés décents des critiques elles-mêmes. Entraîner un modèle de résumé sur l'ensemble des 400 000 avis prendrait beaucoup trop de temps sur un seul GPU, nous allons donc nous concentrer sur la génération de résumés pour un seul domaine de produits. Pour avoir une idée des domaines parmi lesquels nous pouvons choisir, convertissons `english_dataset` en `pandas.DataFrame` et calculons le nombre d'avis par catégorie de produits : +Cet échantillon montre la diversité des critiques que l'on trouve généralement en ligne, allant du positif au négatif (et tout ce qui se trouve entre les deux !). Bien que l'exemple avec le titre « meh » ne soit pas très informatif, les autres titres semblent être des résumés décents des critiques. Entraîner un modèle de résumé sur l'ensemble des 400 000 avis prendrait beaucoup trop de temps sur un seul GPU, nous allons donc nous concentrer sur la génération de résumés pour un seul domaine de produits. Pour avoir une idée des domaines parmi lesquels nous pouvons choisir, convertissons `english_dataset` en `pandas.DataFrame` et calculons le nombre d'avis par catégorie de produits : ```python english_dataset.set_format("pandas") english_df = english_dataset["train"][:] -# Afficher les comptes des 20 premiers produits +# Afficher le compte des 20 premiers produits 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 +home 17679 # maison +apparel 15951 # vêtements +wireless 15717 # sans fil +other 13418 # autres +beauty 12091 # beauté +drugstore 11730 # pharmacie +kitchen 10382 # cuisine +toy 8745 # jouets +sports 8277 # sports +automotive 7506 # automobile +lawn_and_garden 7327 # pelouse_et_jardin +home_improvement 7136 # amélioration_de_la_maison +pet_products 7082 # produits_pour_animaux_de_compagnie +digital_ebook_purchase 6749 # achat_de_livres_numériques +pc 6401 # ordinateur_personnel +electronics 6186 # électronique +office_product 5521 # produits_de_bureau +shoes 5197 # chaussures +grocery 4730 # épicerie +book 3756 # livre Name: product_category, dtype: int64 ``` -Les produits les plus populaires dans l'ensemble de données anglaises concernent les articles ménagers, les vêtements et l'électronique sans fil. Pour rester dans le thème d'Amazon, nous allons nous concentrer sur le résumé des critiques de livres. Après tout, c'est la raison d'être de l'entreprise ! Nous pouvons voir deux catégories de produits qui correspondent à nos besoins (`book` et `digital_ebook_purchase`), nous allons donc filtrer les ensembles de données dans les deux langues pour ces produits uniquement. Comme nous l'avons vu dans le [Chapitre 5](/course/fr/chapter5), la fonction `Dataset.filter()` nous permet de découper un jeu de données de manière très efficace, nous pouvons donc définir une fonction simple pour le faire : +Les produits les plus populaires du jeu de données anglais concernent les articles ménagers, les vêtements et l'électronique sans fil. Pour rester dans le thème d'Amazon, nous allons nous concentrer sur le résumé des critiques de livres. Après tout, c'est la raison d'être de l'entreprise ! Nous pouvons voir deux catégories de produits qui correspondent à nos besoins (`book` et `digital_ebook_purchase`). Nous allons donc filtrer les jeux de données dans les deux langues pour ces produits uniquement. Comme nous l'avons vu dans le [chapitre 5](/course/fr/chapter5), la fonction `Dataset.filter()` nous permet de découper un jeu de données de manière très efficace. Nous pouvons donc définir une fonction simple pour le faire : ```python def filter_books(example): @@ -140,7 +141,7 @@ def filter_books(example): ) ``` -Maintenant, lorsque nous appliquons cette fonction à `english_dataset` et `spanish_dataset`, le résultat ne contiendra que les lignes impliquant les catégories de livres. Avant d'appliquer le filtre, changeons le format de `english_dataset` de `"pandas"` à `"arrow"` : +Maintenant, lorsque nous appliquons cette fonction à `english_dataset` et `spanish_dataset`, le résultat ne contient que les lignes impliquant les catégories de livres. Avant d'appliquer le filtre, changeons le format de `english_dataset` de `"pandas"` à `"arrow"` : ```python english_dataset.reset_format() @@ -155,15 +156,18 @@ show_samples(english_books) ``` ```python out -'>> Title: I\'m dissapointed.' # Je suis déçu +'>> Title: I\'m dissapointed.' +# Je suis déçu '>> 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.' # Je suppose que j'avais de plus grandes attentes pour ce livre d'après les critiques. Je pensais vraiment que j'allais au moins l'aimer. L'idée de l'intrigue était géniale. J'aimais Ash, mais ça n'allait nulle part. La plus grande partie du livre était consacrée à leur émission de radio et aux conversations avec les auditeurs. Je voulais que l'auteur creuse plus profondément pour que nous puissions vraiment connaître les personnages. Tout ce que nous savons de Grace, c'est qu'elle est séduisante, qu'elle est latino et qu'elle est une sorte de garce. Je suis déçue. -'>> Title: Good art, good price, poor design' # Un bon art, un bon prix, un mauvais design +'>> Title: Good art, good price, poor design' +# Un bon art, un bon prix, un mauvais 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' # J'ai eu le calendrier DC Vintage ces deux dernières années, mais il était en rupture de stock pour toujours cette année et j'ai vu qu'ils avaient réduit les dimensions sans raison valable. Celui-ci a de bons choix artistiques mais le design a le pli qui traverse l'image, donc c'est moins esthétique, surtout si vous voulez garder une image à accrocher. Pour le prix, c'est un bon calendrier. -'>> Title: Helpful' Utile +'>> Title: Helpful' +# Utile '>> Review: Nearly all the tips useful and. I consider myself an intermediate to advanced user of OneNote. I would highly recommend.' # Presque tous les conseils sont utiles et. Je me considère comme un utilisateur intermédiaire à avancé de OneNote. Je le recommande vivement. ``` @@ -186,16 +190,20 @@ show_samples(books_dataset) ``` ```python out -'>> Title: Easy to follow!!!!' # Facile à suivre!!!! +'>> Title: Easy to follow!!!!' +# Facile à suivre!!!! '>> 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.' # J'ai adoré The dash diet weight loss Solution. Jamais faim. Je recommande ce régime. Les menus sont également bien arrondis. Essayez-le. Il contient beaucoup d'informations, merci. -'>> Title: PARCIALMENTE DAÑADO' # PARTIELLEMENT ENDOMMAGÉ +'>> Title: PARCIALMENTE DAÑADO' +# PARTIELLEMENT ENDOMMAGÉ '>> 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).' # Il est arrivé le jour prévu, avec d'autres livres que j'avais commandés, mais la boîte est arrivée en mauvais état, ce qui a endommagé les coins des livres car ils étaient livrés sans protection (doublure). -'>> Title: no lo he podido descargar' # Je n'ai pas pu le télécharger -'>> Review: igual que el anterior' # même chose que ci-dessus +'>> Title: no lo he podido descargar' +# Je n'ai pas pu le télécharger +'>> Review: igual que el anterior' +# même chose que ci-dessus ``` Cela ressemble certainement à un mélange de critiques anglaises et espagnoles ! Maintenant que nous avons un corpus d'entraînement, une dernière chose à vérifier est la distribution des mots dans les critiques et leurs titres. Ceci est particulièrement important pour les tâches de résumé, où les résumés de référence courts dans les données peuvent biaiser le modèle pour qu'il ne produise qu'un ou deux mots dans les résumés générés. Les graphiques ci-dessous montrent les distributions de mots, et nous pouvons voir que les titres sont fortement biaisés vers seulement 1 ou 2 mots : @@ -215,32 +223,32 @@ Maintenant que nous avons préparé notre corpus, voyons quelques *transformers* ## Modèles pour le résumé de texte -Si vous y pensez, le résumé de texte est une tâche similaire à la traduction automatique : nous avons un corps de texte, comme une critique, que nous aimerions "traduire" en une version plus courte qui capture les caractéristiques saillantes de l'entrée. En conséquence, la plupart des modèles Transformer pour le résumé adoptent l'architecture encodeur-décodeur que nous avons rencontrée pour la première fois dans le [Chapitre 1](/course/fr/chapter1), bien qu'il y ait quelques exceptions comme la famille de modèles GPT qui peut également être utilisée pour le résumé dans des contextes peu complexes. Le tableau suivant présente quelques modèles pré-entraînés populaires qui peuvent être *finetunés* pour le résumé. +Si vous y pensez, le résumé de texte est une tâche similaire à la traduction automatique. Nous avons un corps de texte, comme une critique, que nous aimerions « traduire » en une version plus courte qui capture les caractéristiques saillantes de l'entrée. En conséquence, la plupart des *transformers* pour le résumé adoptent l'architecture encodeur-décodeur que nous avons rencontrée pour la première fois dans le [chapitre 1](/course/fr/chapter1), bien qu'il y ait quelques exceptions comme la famille de modèles GPT qui peut également être utilisée pour le résumé dans des contextes peu complexes. Le tableau suivant présente quelques modèles pré-entraînés populaires qui peuvent être *finetunés* pour le résumé. | *Transformers* | Description | Multilingue ? | | :---------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------: | -| [GPT-2](https://huggingface.co/gpt2-xl) | Bien qu'il soit entraîné comme un modèle de langage autorégressif, vous pouvez faire en sorte que GPT-2 génère des résumés en ajoutant "TL;DR" à la fin du texte d'entrée. | ❌ | -| [PEGASUS](https://huggingface.co/google/pegasus-large) | Utilise un objectif de pré-entraînement pour prédire les phrases masquées dans les textes à plusieurs phrases. Cet objectif de pré-entraînement est plus proche du résumé que de la modélisation du langage standard et obtient des scores élevés sur des benchmarks populaires. | ❌ | -| [T5](https://huggingface.co/t5-base) | Une architecture universelle de *transformer* qui formule toutes les tâches dans un cadre texte à texte ; par exemple, le format d'entrée du modèle pour résumer un document est `summarize : ARTICLE`. | ❌ | +| [GPT-2](https://huggingface.co/gpt2-xl) | Bien qu'il soit entraîné comme un modèle de langage autorégressif, vous pouvez faire en sorte que le GPT-2 génère des résumés en ajoutant `TL;DR` à la fin du texte d'entrée. | ❌ | +| [PEGASUS](https://huggingface.co/google/pegasus-large) | Utilise un objectif de pré-entraînement pour prédire les phrases masquées dans les textes à plusieurs phrases. Cet objectif de pré-entraînement est plus proche du résumé que de la modélisation du langage standard et obtient des scores élevés sur des *benchmarks* populaires. | ❌ | +| [T5](https://huggingface.co/t5-base) | Une architecture universelle de *transformer* qui formule toutes les tâches dans un cadre texte à texte. Par exemple, le format d'entrée du modèle pour résumer un document est `summarize: ARTICLE`. | ❌ | | [mT5](https://huggingface.co/google/mt5-base) | Une version multilingue de T5, pré-entraînée sur le corpus multilingue Common Crawl (mC4), couvrant 101 langues. | ✅ | | [BART](https://huggingface.co/facebook/bart-base) | Une architecture de *transformer* avec une pile d'encodeurs et de décodeurs entraînés pour reconstruire l'entrée corrompue qui combine les schémas de pré-entraînement de BERT et GPT-2. | ❌ | | [mBART-50](https://huggingface.co/facebook/mbart-large-50) | Une version multilingue de BART, pré-entraînée sur 50 langues. | ✅ | -Comme vous pouvez le voir dans ce tableau, la majorité des modèles Transformer pour le résumé (et en fait la plupart des tâches de NLP) sont monolingues. C'est une bonne chose si votre tâche se déroule dans une langue "à haute ressource" comme l'anglais ou l'allemand, mais moins pour les milliers d'autres langues utilisées dans le monde. Heureusement, il existe une catégorie de modèles Transformer multilingues, comme mT5 et mBART, qui viennent à la rescousse. Ces modèles sont pré-entraînés en utilisant la modélisation du langage, mais avec une particularité : au lieu de s'entraîner sur un corpus d'une seule langue, ils sont entraînés conjointement sur des textes dans plus de 50 langues à la fois ! +Comme vous pouvez le voir dans ce tableau, la majorité des *transformers* pour le résumé (et en fait la plupart des tâches de NLP) sont monolingues. C'est une bonne chose si votre tâche se déroule dans une langue « à haute ressource » comme l'anglais ou l'allemand, mais moins pour les milliers d'autres langues utilisées dans le monde. Heureusement, il existe une catégorie de *transformers* multilingues, comme mT5 et mBART, qui viennent à la rescousse. Ces modèles sont pré-entraînés en utilisant la modélisation du langage mais avec une particularité : au lieu d'être entraîné sur un corpus d'une seule langue, ils sont entraînés conjointement sur des textes dans plus de 50 langues ! -Nous allons nous concentrer sur mT5, une architecture intéressante basée sur T5 qui a été pré-entraînée dans un cadre texte à texte. Dans T5, chaque tâche NLP est formulée en termes d'un préfixe d'invite comme `summarize:` qui conditionne le modèle à adapter le texte généré à l'invite. Comme le montre la figure ci-dessous, cela rend T5 extrêmement polyvalent, car vous pouvez résoudre de nombreuses tâches avec un seul modèle ! +Nous allons nous concentrer sur mT5, une architecture intéressante basée sur T5 qui a été pré-entraînée dans un cadre texte à texte. Dans T5, chaque tâche de NLP est formulée en termes d'un préfixe de *prompt* comme `summarize:` qui conditionne le modèle à adapter le texte généré au *prompt*. Comme le montre la figure ci-dessous, cela rend le T5 extrêmement polyvalent car vous pouvez résoudre de nombreuses tâches avec un seul modèle !
Different tasks performed by the T5 architecture.
-mT5 n'utilise pas de préfixes, mais partage une grande partie de la polyvalence de T5 et a l'avantage d'être multilingue. Maintenant que nous avons choisi un modèle, voyons comment préparer nos données pour l'entraînement. +mT5 n'utilise pas de préfixes mais partage une grande partie de la polyvalence de T5 et a l'avantage d'être multilingue. Maintenant que nous avons choisi un modèle, voyons comment préparer nos données pour l'entraînement. -✏️ **Essayez** Une fois que vous avez travaillé sur cette section, voyez comment mT5 se compare à mBART en affinant ce dernier avec les mêmes techniques. Pour des points bonus, vous pouvez aussi essayer de *finetuner* le T5 uniquement sur les critiques anglaises. Puisque le T5 a un préfixe spécial, vous devrez ajouter `summarize:` aux exemples d'entrée dans les étapes de prétraitement ci-dessous. +✏️ **Essayez !** Une fois que vous aurez terminé cette section, comparez le mT5 à mBART en *finetunant* ce dernier avec les mêmes techniques. Pour des points bonus, vous pouvez aussi essayer de *finetuner* le T5 uniquement sur les critiques anglaises. Puisque le T5 a un préfixe spécial, vous devrez ajouter `summarize:` aux entrées dans les étapes de prétraitement ci-dessous. @@ -248,7 +256,7 @@ mT5 n'utilise pas de préfixes, mais partage une grande partie de la polyvalence -Notre prochaine tâche est de tokeniser et d'encoder nos critiques et leurs titres. Comme d'habitude, nous commençons par charger le *tokenizer* associé au point de contrôle du modèle pré-entraîné. Nous utiliserons `mt5-small` comme point de contrôle afin de pouvoir affiner le modèle en un temps raisonnable : +Notre prochaine tâche est de tokeniser et d'encoder nos critiques et leurs titres. Comme d'habitude, nous commençons par charger le *tokenizer* associé au *checkpoint* du modèle pré-entraîné. Nous utiliserons `mt5-small` comme *checkpoint* afin de pouvoir *finetuner* le modèle en un temps raisonnable : ```python from transformers import AutoTokenizer @@ -259,7 +267,7 @@ tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) -💡 Aux premiers stades de vos projets de NLP, une bonne pratique consiste à entraîner une classe de "petits" modèles sur un petit échantillon de données. Cela vous permet de déboguer et d'itérer plus rapidement vers un flux de travail de bout en bout. Une fois que vous avez confiance dans les résultats, vous pouvez toujours faire évoluer le modèle en changeant simplement le point de contrôle du modèle ! +💡 Aux premiers stades de vos projets de NLP, une bonne pratique consiste à entraîner une classe de « petits » modèles sur un petit échantillon de données. Cela vous permet de déboguer et d'itérer plus rapidement vers un flux de travail de bout en bout. Une fois que vous avez confiance dans les résultats, vous pouvez toujours faire évoluer le modèle en changeant simplement le *checkpoint* du modèle ! @@ -276,7 +284,7 @@ inputs {'input_ids': [336, 259, 28387, 11807, 287, 62893, 295, 12507, 1], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]} ``` -Ici nous pouvons voir les familiers `input_ids` et `attention_mask` que nous avons rencontrés dans nos premières expériences de fine-tuning au [Chapitre 3](/course/fr/chapter3). Décodons ces identifiants d'entrée avec la fonction `convert_ids_to_tokens()` du tokenizer pour voir à quel type de tokenizer nous avons affaire : +Ici nous pouvons voir les familiers `input_ids` et `attention_mask` que nous avons rencontrés dans nos premières expériences de *finetuning* au [chapitre 3](/course/fr/chapter3). Décodons ces identifiants d'entrée avec la fonction `convert_ids_to_tokens()` du *tokenizer* pour voir à quel type de *tokenizer* nous avons affaire : ```python tokenizer.convert_ids_to_tokens(inputs.input_ids) @@ -286,7 +294,7 @@ tokenizer.convert_ids_to_tokens(inputs.input_ids) ['▁I', '▁', 'loved', '▁reading', '▁the', '▁Hung', 'er', '▁Games', ''] ``` -Le caractère spécial Unicode `▁` et le *token* de fin de séquence `` indiquent que nous avons affaire au *tokenizer* de SentencePiece, qui est basé sur l'algorithme de segmentation Unigram discuté dans le [Chapitre 6](/course/chapter6). Unigram est particulièrement utile pour les corpus multilingues car il permet à SentencePiece d'être agnostique vis-à-vis des accents, de la ponctuation et du fait que de nombreuses langues, comme le japonais, n'ont pas de caractères d'espacement. +Le caractère Unicode spécial `▁` et le *token* de fin de séquence `` indiquent que nous avons affaire au *tokenizer* de SentencePiece, qui est basé sur l'algorithme de segmentation Unigram discuté dans le [chapitre 6](/course/chapter6). Unigram est particulièrement utile pour les corpus multilingues car il permet à SentencePiece d'être agnostique vis-à-vis des accents, de la ponctuation et du fait que de nombreuses langues, comme le japonais, n'ont pas de caractères d'espacement. Pour tokeniser notre corpus, nous devons faire face à une subtilité associée au résumé : comme nos étiquettes sont également du texte, il est possible qu'elles dépassent la taille maximale du contexte du modèle. Cela signifie que nous devons appliquer une troncature à la fois aux critiques et à leurs titres pour nous assurer de ne pas transmettre des entrées trop longues à notre modèle. Les tokenizers de 🤗 *Transformers* fournissent une fonction très pratique `as_target_tokenizer()` qui vous permet de tokeniser les étiquettes en parallèle avec les entrées. Ceci est typiquement fait en utilisant un gestionnaire de contexte à l'intérieur d'une fonction de prétraitement qui encode d'abord les entrées, et ensuite encode les étiquettes comme une colonne séparée. Voici un exemple d'une telle fonction pour mT5 : @@ -299,7 +307,7 @@ def preprocess_function(examples): model_inputs = tokenizer( examples["review_body"], max_length=max_input_length, truncation=True ) - # Configurer le *tokenizer* pour les cibles. + # Configurer le tokenizer pour les cibles with tokenizer.as_target_tokenizer(): labels = tokenizer( examples["review_title"], max_length=max_target_length, truncation=True @@ -321,7 +329,7 @@ Maintenant que le corpus a été prétraité, examinons certaines métriques cou -💡 Vous avez peut-être remarqué que nous avons utilisé `batched=True` dans notre fonction `Dataset.map()` ci-dessus. Cela permet de coder les exemples par lots de 1 000 (par défaut) et d'utiliser les capacités de multithreading des *tokenizers* rapides de 🤗 *Transformers*. Lorsque cela est possible, essayez d'utiliser `batched=True` pour tirer le meilleur parti de votre prétraitement ! +💡 Vous avez peut-être remarqué que nous avons utilisé `batched=True` dans notre fonction `Dataset.map()` ci-dessus. Cela permet de coder les exemples par lots de 1 000 (par défaut) et d'utiliser les capacités de *multithreading* des *tokenizers* rapides de 🤗 *Transformers*. Lorsque cela est possible, essayez d'utiliser `batched=True` pour tirer le meilleur parti de votre prétraitement ! @@ -330,32 +338,34 @@ Maintenant que le corpus a été prétraité, examinons certaines métriques cou -Par rapport à la plupart des autres tâches que nous avons abordées dans ce cours, la mesure des performances des tâches de génération de texte comme le résumé ou la traduction n'est pas aussi simple. Par exemple, pour une critique telle que "J'ai adoré lire les Hunger Games", il existe plusieurs résumés valides, comme "J'ai adoré Hunger Games" ou "Hunger Games est une excellente lecture". Il est clair que l'application d'une sorte de correspondance exacte entre le résumé généré et l'étiquette n'est pas une bonne solution - même les humains auraient de mauvais résultats avec une telle mesure, car nous avons tous notre propre style d'écriture. +Par rapport à la plupart des autres tâches que nous avons abordées dans ce cours, la mesure des performances des tâches de génération de texte comme le résumé ou la traduction n'est pas aussi simple. Par exemple, pour une critique telle que « J'ai adoré lire les Hunger Games », il existe plusieurs résumés valides, comme « J'ai adoré Hunger Games » ou « Hunger Games est une excellente lecture ». Il est clair que l'application d'une sorte de correspondance exacte entre le résumé généré et l'étiquette n'est pas une bonne solution. En effet, même les humains auraient de mauvais résultats avec une telle mesure, car nous avons tous notre propre style d'écriture. -Pour le résumé, l'une des métriques les plus couramment utilisées est le [score ROUGE](https://en.wikipedia.org/wiki/ROUGE_(metric)) (abréviation de Recall-Oriented Understudy for Gisting Evaluation). L'idée de base de cette métrique est de comparer un résumé généré avec un ensemble de résumés de référence qui sont généralement créés par des humains. Pour être plus précis, supposons que nous voulions comparer les deux résumés suivants : +Pour le résumé, l'une des métriques les plus couramment utilisées est le [score ROUGE](https://en.wikipedia.org/wiki/ROUGE_(metric)) (abréviation de *Recall-Oriented Understudy for Gisting Evaluation*). L'idée de base de cette métrique est de comparer un résumé généré avec un ensemble de résumés de référence qui sont généralement créés par des humains. Pour être plus précis, supposons que nous voulions comparer les deux résumés suivants : ```python generated_summary = "I absolutely loved reading the Hunger Games" +# "J'ai absolument adoré lire les Hunger Games" reference_summary = "I loved reading the Hunger Games" +# "J'ai adoré lire les Hunger Games" ``` Une façon de les comparer pourrait être de compter le nombre de mots qui se chevauchent, qui dans ce cas serait de 6. Cependant, cette méthode est un peu grossière, c'est pourquoi ROUGE se base sur le calcul des scores de _précision_ et de _rappel_ pour le chevauchement. -🙋 Ne vous inquiétez pas si c'est la première fois que vous entendez parler de précision et de rappel - nous allons parcourir ensemble quelques exemples explicites pour que tout soit clair. Ces métriques sont généralement rencontrées dans les tâches de classification, donc si vous voulez comprendre comment la précision et le rappel sont définis dans ce contexte, nous vous recommandons de consulter les [guides de `scikit-learn`](https://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html). +🙋 Ne vous inquiétez pas si c'est la première fois que vous entendez parler de précision et de rappel. Nous allons parcourir ensemble quelques exemples explicites pour que tout soit clair. Ces métriques sont généralement rencontrées dans les tâches de classification, donc si vous voulez comprendre comment la précision et le rappel sont définis dans ce contexte, nous vous recommandons de consulter les [guides de `scikit-learn`](https://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html). Pour ROUGE, le rappel mesure la proportion du résumé de référence qui est capturée par le résumé généré. Si nous ne faisons que comparer des mots, le rappel peut être calculé selon la formule suivante : -$$ \mathrm{Recall} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, reference\, summary}} $$ +$$ \mathrm{Recall} = \frac{\mathrm{Nombre\,de\,mots\,qui\,se\,chevauchent}}{\mathrm{Nombre\, total\, de\, mots\, dans\, le\, résumé\, de\, réference}} $$ -Pour notre exemple simple ci-dessus, cette formule donne un rappel parfait de 6/6 = 1 ; c'est-à-dire que tous les mots du résumé de référence ont été produits par le modèle. Cela peut sembler génial, mais imaginez que le résumé généré ait été "J'ai vraiment aimé lire les Hunger Games toute la nuit". Le rappel serait également parfait, mais le résumé serait sans doute moins bon puisqu'il serait verbeux. Pour traiter ces scénarios, nous calculons également la précision, qui, dans le contexte de ROUGE, mesure la proportion du résumé généré qui était pertinente : +Pour notre exemple simple ci-dessus, cette formule donne un rappel parfait de 6/6 = 1, c'est-à-dire que tous les mots du résumé de référence ont été produits par le modèle. Cela peut sembler génial, mais imaginez que le résumé généré ait été « J'ai vraiment aimé lire les Hunger Games toute la nuit ». Le rappel serait également parfait, mais le résumé serait sans doute moins bon puisqu'il serait verbeux. Pour traiter ces scénarios, nous calculons également la précision, qui dans le contexte de ROUGE, mesure la proportion du résumé généré qui est pertinente : -$$ \mathrm{Precision} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, generated\, summary}} $$ +$$ \mathrm{Precision} = \frac{\mathrm{Nombre\,de\,mots\,qui\,se\,chevauchent}}{\mathrm{Nombre\, total\, de\, mots\, dans\, le\, résumé\, généré}} $$ -En appliquant cela à notre résumé verbeux, on obtient une précision de 6/10 = 0,6, ce qui est considérablement moins bon que la précision de 6/7 = 0,86 obtenue par notre résumé plus court. En pratique, la précision et le rappel sont généralement calculés, puis le score F1 (la moyenne harmonique de la précision et du rappel) est indiqué. Nous pouvons le faire facilement dans 🤗 *Datasets* en installant d'abord le paquet `rouge_score` : +En appliquant cela à notre résumé verbeux, on obtient une précision de 6/10 = 0,6, ce qui est considérablement moins bon que la précision de 6/7 = 0,86 obtenue par notre résumé plus court. En pratique, la précision et le rappel sont généralement calculés, puis le score F1 (la moyenne harmonique de la précision et du rappel) est indiqué. Nous pouvons le faire facilement dans 🤗 *Datasets* en installant d'abord le *package* `rouge_score` : ```py !pip install rouge_score @@ -385,7 +395,7 @@ scores 'rougeLsum': AggregateScore(low=Score(precision=0.86, recall=1.0, fmeasure=0.92), mid=Score(precision=0.86, recall=1.0, fmeasure=0.92), high=Score(precision=0.86, recall=1.0, fmeasure=0.92))} ``` -Whoa, il y a un batch d'informations dans cette sortie. Qu'est-ce que ça veut dire ? Tout d'abord, 🤗 *Datasets* calcule en fait des intervalles de confiance pour la précision, le rappel et le score F1 ; ce sont les attributs `low`, `mid`, et `high` que vous pouvez voir ici. De plus, 🤗 *Datasets* calcule une variété de scores ROUGE qui sont basés sur différents types de granularité du texte lors de la comparaison des résumés générés et de référence. La variante `rouge1` est le chevauchement des unigrammes. C'est juste une façon fantaisiste de dire le chevauchement des mots et c'est exactement la métrique dont nous avons discuté ci-dessus. Pour vérifier cela, nous allons extraire la valeur "moyenne" de nos scores : +Whoa, il y a pas mal d'informations dans cette sortie. Qu'est-ce que ça veut dire ? Tout d'abord, 🤗 *Datasets* calcule des intervalles de confiance pour la précision, le rappel et le score F1. Ce sont les attributs `low`, `mid`, et `high` que vous pouvez voir ici. De plus, 🤗 *Datasets* calcule une variété de scores ROUGE qui sont basés sur différents types de granularité du texte lors de la comparaison des résumés générés et de référence. La variante `rouge1` est le chevauchement des unigrammes. C'est juste une façon fantaisiste de dire le chevauchement des mots et c'est exactement la métrique dont nous avons discuté ci-dessus. Pour vérifier cela, nous allons extraire la valeur `mid` de nos scores : ```python scores["rouge1"].mid @@ -395,19 +405,19 @@ scores["rouge1"].mid Score(precision=0.86, recall=1.0, fmeasure=0.92) ``` -Super, les chiffres de précision et de rappel correspondent ! Maintenant, qu'en est-il des autres scores ROUGE ? `rouge2` mesure le chevauchement entre les bigrammes (pensez au chevauchement des paires de mots), tandis que `rougeL` et `rougeLsum` mesurent les plus longues séquences de mots correspondants en recherchant les plus longues sous-souches communes dans les résumés générés et de référence. La "somme" dans `rougeLsum` fait référence au fait que cette métrique est calculée sur un résumé entier, alors que `rougeL` est calculée comme une moyenne sur des phrases individuelles. +Super, les chiffres de précision et de rappel correspondent ! Maintenant, qu'en est-il des autres scores ROUGE ? `rouge2` mesure le chevauchement entre les bigrammes (chevauchement des paires de mots), tandis que `rougeL` et `rougeLsum` mesurent les plus longues séquences de mots correspondants en recherchant les plus longues sous-souches communes dans les résumés générés et de référence. Le « sum » dans `rougeLsum` fait référence au fait que cette métrique est calculée sur un résumé entier, alors que `rougeL` est calculée comme une moyenne sur des phrases individuelles. -✏️ **Essayez** Créez votre propre exemple de résumé généré et de référence et voyez si les scores ROUGE obtenus correspondent à un calcul manuel basé sur les formules de précision et de rappel. Pour des points bonus, divisez le texte en bigrammes et comparez la précision et le rappel pour la métrique `rouge2`. +✏️ **Essayez !** Créez votre propre exemple de résumé généré et de référence et voyez si les scores ROUGE obtenus correspondent à un calcul manuel basé sur les formules de précision et de rappel. Pour des points bonus, divisez le texte en bigrammes et comparez la précision et le rappel pour la métrique `rouge2`. -Nous utiliserons ces scores ROUGE pour suivre les performances de notre modèle, mais avant cela, faisons ce que tout bon praticien de NLP devrait faire : créer une base de référence solide, mais simple ! +Nous utiliserons ces scores ROUGE pour suivre les performances de notre modèle, mais avant cela, faisons ce que tout bon praticien de NLP devrait faire : créer une *baseline* solide, mais simple ! ### Création d'une base de référence solide -Une base de référence commune pour le résumé de texte consiste à prendre simplement les trois premières phrases d'un article, souvent appelée la base de référence _lead-3_. Nous pourrions utiliser des points pour suivre les limites de la phrase, mais cela échouera avec des acronymes comme "U.S." ou "U.N.". -- Nous allons donc utiliser la bibliothèque `nltk`, qui inclut un meilleur algorithme pour gérer ces cas. Vous pouvez installer le paquetage en utilisant `pip` comme suit : +Une *baseline* commune pour le résumé de texte consiste à prendre simplement les trois premières phrases d'un article, souvent appelée la *baseline* _lead-3_. Nous pourrions utiliser les points pour tracker les limites des phrases mais cela échouera avec des acronymes comme « U.S. » ou « U.N. ». Nous allons donc utiliser la bibliothèque `nltk`, qui inclut un meilleur algorithme pour gérer ces cas. Vous pouvez installer le *package* en utilisant `pip` comme suit : ```python !pip install nltk @@ -421,7 +431,7 @@ import nltk nltk.download("punkt") ``` -Ensuite, nous importons le *tokenizer* de `nltk` et créons une fonction simple pour extraire les trois premières phrases d'une critique. La convention dans le résumé de texte est de séparer chaque résumé avec une nouvelle ligne, donc nous allons également inclure ceci et le tester sur un exemple d'entraînement : +Ensuite, nous importons le *tokenizer* de `nltk` et créons une fonction simple pour extraire les trois premières phrases d'une critique. La convention dans le résumé de texte est de séparer chaque résumé avec une nouvelle ligne, donc nous allons également inclure ceci et tester le tout sur un exemple d'entraînement : ```python from nltk.tokenize import sent_tokenize @@ -435,12 +445,15 @@ 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.' # J'ai grandi en lisant Koontz, et il y a des années, j'ai arrêté, convaincu que je l'avais "dépassé" -'Still,when a friend was looking for something suspenseful too read, I suggested Koontz.' " Pourtant, quand une amie cherchait un livre à suspense, je lui ai suggéré Koontz. -'She found Strangers.' # Elle a trouvé Strangers. +'I grew up reading Koontz, and years ago, I stopped,convinced i had "outgrown" him.' +# J'ai grandi en lisant Koontz, et il y a des années, j'ai arrêté, convaincu que je l'avais "dépassé" +'Still,when a friend was looking for something suspenseful too read, I suggested Koontz.' +# "Pourtant, quand une amie cherchait un livre à suspense, je lui ai suggéré Koontz." +'She found Strangers.' +# Elle a trouvé Strangers. ``` -Cela semble fonctionner, alors implémentons maintenant une fonction qui extrait ces "résumés" d'un ensemble de données et calcule les scores ROUGE pour la ligne de base : +Cela semble fonctionner, alors implémentons maintenant une fonction qui extrait ces résumés d'un jeu de données et calcule les scores ROUGE pour la ligne de base : ```python def evaluate_baseline(dataset, metric): @@ -463,13 +476,13 @@ rouge_dict {'rouge1': 16.74, 'rouge2': 8.83, 'rougeL': 15.6, 'rougeLsum': 15.96} ``` -Nous pouvons voir que le score de `rouge2` est significativement plus bas que le reste ; ceci reflète probablement le fait que les titres des revues sont typiquement concis et donc que la ligne de base de lead-3 est trop verbeuse. Maintenant que nous disposons d'une bonne base de travail, concentrons-nous sur le réglage fin de mT5 ! +Nous pouvons voir que le score de `rouge2` est significativement plus bas que le reste. Ceci reflète probablement le fait que les titres des critiques sont typiquement concis et donc que la *baseline* *lead-3* est trop verbeuse. Maintenant que nous disposons d'une bonne *baseline*, concentrons-nous sur le *finetuning* du mT5 ! {#if fw === 'pt'} -## *Finetuning* de mT5 avec l'API `Trainer`. +## Finetuning de mT5 avec l'API `Trainer` -Le *finetuning* d'un modèle pour le résumé est très similaire aux autres tâches que nous avons couvertes dans ce chapitre. La première chose à faire est de charger le modèle pré-entraîné depuis le checkpoint `mt5-small`. Puisque la compression est une tâche de séquence à séquence, nous pouvons charger le modèle avec la classe `AutoModelForSeq2SeqLM`, qui téléchargera automatiquement et mettra en cache les poids : +Le *finetuning* d'un modèle pour le résumé est très similaire aux autres tâches que nous avons couvertes dans ce chapitre. La première chose à faire est de charger le modèle pré-entraîné à partir du *checkpoint* `mt5-small`. Puisque la compression est une tâche de séquence à séquence, nous pouvons charger le modèle avec la classe `AutoModelForSeq2SeqLM`, qui téléchargera automatiquement et mettra en cache les poids : ```python from transformers import AutoModelForSeq2SeqLM @@ -479,9 +492,9 @@ model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) {:else} -## *Finetuning* de mT5 avec Keras +## Finetuning de mT5 avec Keras -Le *finetuning* d'un modèle pour le résumé est très similaire aux autres tâches que nous avons couvertes dans ce chapitre. La première chose à faire est de charger le modèle pré-entraîné à partir du point de contrôle `mt5-small`. Puisque la compression est une tâche de séquence à séquence, nous pouvons charger le modèle avec la classe `AutoModelForSeq2SeqLM`, qui téléchargera automatiquement et mettra en cache les poids : +Le *finetuning* d'un modèle pour le résumé est très similaire aux autres tâches que nous avons couvertes dans ce chapitre. La première chose à faire est de charger le modèle pré-entraîné à partir du *checkpoint* `mt5-small`. Puisque la compression est une tâche de séquence à séquence, nous pouvons charger le modèle avec la classe `TFAutoModelForSeq2SeqLM`, qui téléchargera automatiquement et mettra en cache les poids : ```python from transformers import TFAutoModelForSeq2SeqLM @@ -493,7 +506,7 @@ model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) -💡 Si vous vous demandez pourquoi vous ne voyez aucun avertissement concernant l'affinement du modèle sur une tâche en aval, c'est parce que pour les tâches de séquence à séquence, nous conservons tous les poids du réseau. Comparez cela à notre modèle de classification de texte dans [Chapitre 3](/course/fr/chapter3), où la tête du modèle pré-entraîné a été remplacée par un réseau initialisé de manière aléatoire. +💡 Si vous vous demandez pourquoi vous ne voyez aucun avertissement concernant le *finetuning* du modèle sur une tâche en aval, c'est parce que pour les tâches de séquence à séquence, nous conservons tous les poids du réseau. Comparez cela à notre modèle de classification de texte du [chapitre 3](/course/fr/chapter3) où la tête du modèle pré-entraîné a été remplacée par un réseau initialisé de manière aléatoire. @@ -539,11 +552,11 @@ args = Seq2SeqTrainingArguments( ) ``` -Ici, l'argument `predict_with_generate` a été défini pour indiquer que nous devons générer des résumés pendant l'évaluation afin de pouvoir calculer les scores ROUGE pour chaque époque. Comme discuté dans [Chapter 1](/course/fr/chapter1), le décodeur effectue l'inférence en prédisant les tokens un par un, et ceci est implémenté par la méthode `generate()` du modèle. Définir `predict_with_generate=True` indique au `Seq2SeqTrainer` d'utiliser cette méthode pour l'évaluation. Nous avons également ajusté certains des hyperparamètres par défaut, comme le taux d'apprentissage, le nombre d'époques, et le taux de décroissance des poids, et nous avons réglé l'option `save_total_limit` pour ne sauvegarder que jusqu'à 3 *checkpoints* pendant l'entraînement. C'est parce que même la "petite" version de mT5 utilise environ un Go d'espace disque, et nous pouvons gagner un peu de place en limitant le nombre de copies que nous sauvegardons. +Ici, l'argument `predict_with_generate` a été défini pour indiquer que nous devons générer des résumés pendant l'évaluation afin de pouvoir calculer les scores ROUGE pour chaque époque. Comme discuté au [chapitre 1](/course/fr/chapter1), le décodeur effectue l'inférence en prédisant les *tokens* un par un, et ceci est implémenté par la méthode `generate()`. Définir `predict_with_generate=True` indique au `Seq2SeqTrainer` d'utiliser cette méthode pour l'évaluation. Nous avons également ajusté certains des hyperparamètres par défaut, comme le taux d'apprentissage, le nombre d'époques, et le taux de décroissance des poids, et nous avons réglé l'option `save_total_limit` pour ne sauvegarder que jusqu'à trois *checkpoints* pendant l'entraînement. C'est parce que même la plus petite version de mT5 utilise environ 1 Go d'espace disque, et nous pouvons gagner un peu de place en limitant le nombre de copies que nous sauvegardons. -L'argument `push_to_hub=True` nous permettra de pousser le modèle vers le Hub après l'entraînement ; vous trouverez le dépôt sous votre profil utilisateur dans l'emplacement défini par `output_dir`. Notez que vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/mt5-finetuned-amazon-en-es"`à `Seq2SeqTrainingArguments`. +L'argument `push_to_hub=True` nous permettra de pousser le modèle vers le *Hub* après l'entraînement. Vous trouverez le dépôt sous votre profil utilisateur dans l'emplacement défini par `output_dir`. Notez que vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/mt5-finetuned-amazon-en-es"` à `Seq2SeqTrainingArguments`. -La prochaine chose que nous devons faire est de fournir à l'entraîneur une fonction `compute_metrics()` afin que nous puissions évaluer notre modèle pendant l'entraînement. Pour le résumé, c'est un peu plus compliqué que de simplement appeler `rouge_score.compute()` sur les prédictions du modèle, puisque nous devons _décoder_ les sorties et les étiquettes en texte avant de pouvoir calculer les scores ROUGE. La fonction suivante fait exactement cela, et utilise également la fonction `sent_tokenize()` de `nltk` pour séparer les phrases du résumé avec des nouvelles lignes : +La prochaine chose que nous devons faire est de fournir à `Seq2SeqTrainer` une fonction `compute_metrics()` afin que nous puissions évaluer notre modèle pendant l'entraînement. Pour le résumé, c'est un peu plus compliqué que de simplement appeler `rouge_score.compute()` sur les prédictions du modèle, puisque nous devons _décoder_ les sorties et les étiquettes en texte avant de pouvoir calculer les scores ROUGE. La fonction suivante fait exactement cela, et utilise également la fonction `sent_tokenize()` de `nltk` pour séparer les phrases du résumé avec des nouvelles lignes : ```python @@ -565,16 +578,16 @@ def compute_metrics(eval_pred): result = rouge_score.compute( predictions=decoded_preds, references=decoded_labels, use_stemmer=True ) - # Extract the median scores + # Extraire les scores médians result = {key: value.mid.fmeasure * 100 for key, value in result.items()} return {k: round(v, 4) for k, v in result.items()} ``` {/if} -Ensuite, nous devons définir un collateur de données pour notre tâche de séquence à séquence. Comme mT5 est un modèle Transformer encodeur-décodeur, une des subtilités de la préparation de nos lots est que, pendant le décodage, nous devons décaler les étiquettes d'une unité vers la droite. Ceci est nécessaire pour garantir que le décodeur ne voit que les étiquettes de vérité terrain précédentes et non les étiquettes actuelles ou futures, qui seraient faciles à mémoriser pour le modèle. Cela ressemble à la façon dont l'auto-attention masquée est appliquée aux entrées dans une tâche comme [la modélisation causale du langage](/course/fr/chapter7/6). +Ensuite, nous devons définir un collateur de données pour notre tâche de séquence à séquence. Comme mT5 est un *transformer* encodeur-décodeur, une des subtilités de la préparation de nos batchs est que, pendant le décodage, nous devons décaler les étiquettes d'une unité vers la droite. Ceci est nécessaire pour garantir que le décodeur ne voit que les étiquettes de vérité terrain précédentes et non les étiquettes actuelles ou futures, qui seraient faciles à mémoriser pour le modèle. Cela ressemble à la façon dont l'auto-attention masquée est appliquée aux entrées dans une tâche comme [la modélisation causale du langage](/course/fr/chapter7/6). -Heureusement, 🤗 *Transformers* fournit un collateur `DataCollatorForSeq2Seq` qui rembourrera dynamiquement les entrées et les étiquettes pour nous. Pour instancier ce collateur, nous devons simplement fournir le *tokenizer* et le `model` : +Heureusement, 🤗 *Transformers* fournit un collateur `DataCollatorForSeq2Seq` qui rembourrera dynamiquement les entrées et les étiquettes pour nous. Pour instancier ce collateur, nous devons simplement fournir le *tokenizer* et le *modèle* : {#if fw === 'pt'} @@ -594,7 +607,7 @@ data_collator = DataCollatorForSeq2Seq(tokenizer, model=model, return_tensors="t {/if} -Voyons ce que produit ce collateur lorsqu'on lui donne un petit lot d'exemples. Tout d'abord, nous devons supprimer les colonnes contenant des chaînes de caractères, car le collateur ne saura pas comment remplir ces éléments : +Voyons ce que produit ce collateur lorsqu'on lui donne un petit batch d'exemples. Tout d'abord, nous devons supprimer les colonnes contenant des chaînes de caractères, car le collateur ne saura pas comment remplir ces éléments : ```python tokenized_datasets = tokenized_datasets.remove_columns( @@ -602,7 +615,7 @@ tokenized_datasets = tokenized_datasets.remove_columns( ) ``` -Comme le collateur attend une liste de `dict`s, où chaque `dict` représente un seul exemple dans l'ensemble de données, nous devons également mettre les données dans le format attendu avant de les transmettre au collateur de données : +Comme le collateur attend une liste de `dict`, où chaque `dict` représente un seul exemple du jeu de données, nous devons également mettre les données dans le format attendu avant de les transmettre au collateur de données : ```python features = [tokenized_datasets["train"][i] for i in range(2)] @@ -625,11 +638,11 @@ data_collator(features) [ 0, 259, 27531, 13483, 259, 7505]])} ``` -La principale chose à remarquer ici est que le premier exemple est plus long que le second, donc les `input_ids` et `attention_mask` du second exemple ont été complétés sur la droite avec un jeton `[PAD]` (dont l'ID est `0`). De même, nous pouvons voir que les `labels` ont été complétés par des `-100`s, pour s'assurer que les *tokens* de remplissage sont ignorés par la fonction de perte. Et enfin, nous pouvons voir un nouveau `decoder_input_ids` qui a déplacé les étiquettes vers la droite en insérant un jeton `[PAD]` dans la première entrée. +La principale chose à remarquer ici est que le premier exemple est plus long que le second, donc les `input_ids` et `attention_mask` du second exemple ont été complétés sur la droite avec un *token* `[PAD]` (dont l'identifiant est `0`). De même, nous pouvons voir que les `labels` ont été complétés par des `-100`, pour s'assurer que les *tokens* de remplissage sont ignorés par la fonction de perte. Et enfin, nous pouvons voir un nouveau `decoder_input_ids` qui a déplacé les étiquettes vers la droite en insérant un *token* `[PAD]` dans la première entrée. {#if fw === 'pt'} -Nous avons enfin tous les ingrédients dont nous avons besoin pour nous entraîner ! Nous devons maintenant simplement instancier le formateur avec les arguments standards : +Nous avons enfin tous les ingrédients dont nous avons besoin pour l'entraînement ! Nous devons maintenant simplement instancier le `Seq2SeqTrainer` avec les arguments : ```python from transformers import Seq2SeqTrainer @@ -669,7 +682,7 @@ trainer.evaluate() 'eval_steps_per_second': 4.914} ``` -D'après les scores, nous pouvons voir que notre modèle a largement surpassé notre ligne de base lead-3. Bien ! La dernière chose à faire est de pousser les poids du modèle vers le *Hub*, comme suit : +D'après les scores, nous pouvons voir que notre modèle a largement surpassé notre *baseline* *lead-3*. Bien ! La dernière chose à faire est de pousser les poids du modèle vers le *Hub*, comme suit : ``` trainer.push_to_hub(commit_message="Training complete", tags="summarization") @@ -679,13 +692,13 @@ trainer.push_to_hub(commit_message="Training complete", tags="summarization") 'https://huggingface.co/huggingface-course/mt5-finetuned-amazon-en-es/commit/aa0536b829b28e73e1e4b94b8a5aacec420d40e0' ``` -Ceci sauvegardera le point de contrôle et les fichiers de configuration dans `output_dir`, avant de télécharger tous les fichiers sur le *Hub*. En spécifiant l'argument `tags`, nous nous assurons également que le widget sur le Hub sera celui d'un pipeline de résumé au lieu de celui de la génération de texte par défaut associé à l'architecture mT5 (pour plus d'informations sur les balises de modèle, voir la [🤗 documentation du *Hub*](https://huggingface.co/docs/hub/main#how-is-a-models-type-of-inference-api-and-widget-determined)). La sortie de `trainer.push_to_hub()` est une URL vers le hash du commit Git, donc vous pouvez facilement voir les changements qui ont été faits au dépôt de modèle ! +Ceci sauvegardera le *checkpoint* et les fichiers de configuration dans `output_dir`, avant de télécharger tous les fichiers sur le *Hub*. En spécifiant l'argument `tags`, nous nous assurons également que le *widget* sur le *Hub* sera celui d'un pipeline de résumé au lieu de celui de la génération de texte par défaut associé à l'architecture mT5 (pour plus d'informations sur les balises de modèle, voir la [documentation du *Hub*](https://huggingface.co/docs/hub/main#how-is-a-models-type-of-inference-api-and-widget-determined)). La sortie de `trainer.push_to_hub()` est une URL vers le hash du commit Git, donc vous pouvez facilement voir les changements qui ont été faits au dépôt de modèle ! -Pour conclure cette section, voyons comment nous pouvons également affiner mT5 en utilisant les fonctionnalités de bas niveau fournies par 🤗 *Accelerate*. +Pour conclure cette section, voyons comment nous pouvons également *finetuner* mT5 en utilisant les fonctionnalités de bas niveau fournies par 🤗 *Accelerate*. {:else} -Nous sommes presque prêts à nous entraîner ! Nous devons juste convertir nos jeux de données en `tf.data.Dataset`s en utilisant le collateur de données que nous avons défini ci-dessus, et ensuite `compile()` et `fit()` le modèle. D'abord, les jeux de données : +Nous sommes presque prêts à nous entraîner ! Nous devons juste convertir nos jeux de données en `tf.data.Dataset` en utilisant le collateur de données que nous avons défini ci-dessus, puis utiliser `compile()` et `fit()`. D'abord, les jeux de données : ```python tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( @@ -708,9 +721,9 @@ Maintenant, nous définissons nos hyperparamètres d'entraînement et nous compi from transformers import create_optimizer import tensorflow as tf -# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans l'ensemble de données, divisé par la taille du batch, puis multiplié par le nombre total d'époques. -# par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un batch tf.data.Dataset, -# et non le jeu de données original Hugging Face Dataset, donc son len() est déjà num_samples // batch_size. +# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans le jeu de données, divisé par la taille du batch, +# puis multiplié par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un tf.data.Dataset, +# et non le jeu de données original donc son len() est déjà num_samples // batch_size. num_train_epochs = 8 num_train_steps = len(tf_train_dataset) * num_train_epochs model_name = model_checkpoint.split("/")[-1] @@ -728,7 +741,7 @@ model.compile(optimizer=optimizer) tf.keras.mixed_precision.set_global_policy("mixed_float16") ``` -Et enfin, nous ajustons le modèle. Nous utilisons un `PushToHubCallback` pour sauvegarder le modèle sur le *Hub* après chaque époque, ce qui nous permettra de l'utiliser pour l'inférence plus tard : +Et enfin, nous *finetunons* le modèle. Nous utilisons un `PushToHubCallback` pour sauvegarder le modèle sur le *Hub* après chaque époque, ce qui nous permettra de l'utiliser pour l'inférence plus tard : ```python from transformers.keras_callbacks import PushToHubCallback @@ -781,13 +794,13 @@ result = {key: value.mid.fmeasure * 100 for key, value in result.items()} {#if fw === 'pt'} -## *Finetuning* de mT5 avec 🤗 *Accelerate* +## Finetuning de mT5 avec 🤗 Accelerate -Le *finetuning* de notre modèle avec 🤗 *Accelerate* est très similaire à l'exemple de classification de texte que nous avons rencontré dans [Chapitre 3](/course/fr/chapter3). Les principales différences seront la nécessité de générer explicitement nos résumés pendant l'Entraînement et de définir comment nous calculons les scores ROUGE (rappelons que le `Seq2SeqTrainer` s'est occupé de la génération pour nous). Voyons comment nous pouvons mettre en œuvre ces deux exigences dans 🤗 *Accelerate* ! +Le *finetuning* de notre modèle avec 🤗 *Accelerate* est très similaire à l'exemple de classification de texte que nous avons rencontré dans le [chapitre 3](/course/fr/chapter3). Les principales différences seront la nécessité de générer explicitement nos résumés pendant l'entraînement et de définir comment nous calculons les scores ROUGE (rappelons que le `Seq2SeqTrainer` s'est occupé de la génération pour nous). Voyons comment nous pouvons mettre en œuvre ces deux exigences dans 🤗 *Accelerate* ! ### Préparer tout pour l'entraînement -La première chose que nous devons faire est de créer un `DataLoader` pour chacun de nos splits. Puisque les chargeurs de données PyTorch attendent des batchs de tenseurs, nous devons définir le format à `"torch"` dans nos jeux de données : +La première chose que nous devons faire est de créer un `DataLoader` pour chacun de nos échantillons. Puisque les chargeurs de données PyTorch attendent des batchs de tenseurs, nous devons définir le format à `"torch"` dans nos jeux de données : ```python tokenized_datasets.set_format("torch") @@ -837,17 +850,17 @@ model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( -🚨 Si vous vous entraînez sur un TPU, vous devrez déplacer tout le code ci-dessus dans une fonction d'entraînement dédiée. Voir le [Chapitre 3](/course/fr/chapter3) pour plus de détails. +🚨 Si vous vous entraînez sur un TPU, vous devrez déplacer tout le code ci-dessus dans une fonction d'entraînement dédiée. Voir le [chapitre 3](/course/fr/chapter3) pour plus de détails. Maintenant que nous avons préparé nos objets, il reste trois choses à faire : -* définir le programme du taux d'apprentissage, +* définir le programmeur du taux d'apprentissage, * implémenter une fonction pour post-traiter les résumés pour l'évaluation, -* créer un référentiel sur le *Hub* vers lequel nous pouvons pousser notre modèle. +* créer un dépôt sur le *Hub* vers lequel nous pouvons pousser notre modèle. -Pour le programme de taux d'apprentissage, nous utiliserons le programme linéaire standard des sections précédentes : +Pour le programmeur de taux d'apprentissage, nous utiliserons le programmeur linéaire standard des sections précédentes : ```python from transformers import get_scheduler @@ -864,7 +877,7 @@ lr_scheduler = get_scheduler( ) ``` -Pour le post-traitement, nous avons besoin d'une fonction qui divise les résumés générés en phrases séparées par des nouvelles lignes. C'est le format attendu par la métrique ROUGE, et nous pouvons y parvenir avec le bout de code suivant : +Pour le post-traitement, nous avons besoin d'une fonction qui divise les résumés générés en phrases séparées par des nouvelles lignes. C'est le format attendu par la métrique ROUGE et nous pouvons y parvenir avec le bout de code suivant : ```python def postprocess_text(preds, labels): @@ -880,7 +893,7 @@ def postprocess_text(preds, labels): Cela devrait vous sembler familier si vous vous rappelez comment nous avons défini la fonction `compute_metrics()` du `Seq2SeqTrainer`. -Enfin, nous devons créer un dépôt de modèles sur le *Hub*. Pour cela, nous pouvons utiliser la bibliothèque 🤗 *Hub*, qui porte le nom approprié. Nous avons juste besoin de définir un nom pour notre référentiel, et la bibliothèque a une fonction utilitaire pour combiner l'ID du référentiel avec le profil de l'utilisateur : +Enfin, nous devons créer un dépôt de modèles sur le *Hub*. Pour cela, nous pouvons utiliser la bibliothèque 🤗 *Hub*, qui porte le nom approprié. Nous avons juste besoin de définir un nom pour notre dépôt, et la bibliothèque a une fonction utilitaire pour combiner l'identifiant du dépôt avec le profil de l'utilisateur : ```python from huggingface_hub import get_full_repo_name @@ -894,7 +907,7 @@ repo_name 'lewtun/mt5-finetuned-amazon-en-es-accelerate' ``` -Nous pouvons maintenant utiliser ce nom de référentiel pour cloner une version locale dans notre répertoire de résultats qui stockera les artefacts d'entraînement : +Nous pouvons maintenant utiliser ce nom de dépôt pour cloner une version locale dans notre répertoire de résultats qui stockera les artefacts d'entraînement : ```python from huggingface_hub import Repository @@ -903,7 +916,7 @@ output_dir = "results-mt5-finetuned-squad-accelerate" repo = Repository(output_dir, clone_from=repo_name) ``` -This will allow us to push the artifacts back to the Hub by calling the `repo.push_to_hub()` method during training! Let's now wrap up our analysis by writing out the training loop. +Cela nous permettra de pousser les artefacts vers le *Hub* en appelant la méthode `repo.push_to_hub()` pendant l'entraînement ! Concluons maintenant notre analyse en écrivant la boucle d'entraînement. ### Boucle d'entraînement @@ -912,7 +925,7 @@ La boucle d'entraînement pour le résumé est assez similaire aux autres exempl 1. entraîner le modèle en itérant sur tous les exemples dans `train_dataloader` pour chaque époque, 2. générer les résumés du modèle à la fin de chaque époque, en générant d'abord les *tokens* puis en les décodant (ainsi que les résumés de référence) en texte, 3. calculer les scores ROUGE en utilisant les mêmes techniques que nous avons vues précédemment, -4. sauvegarder les points de contrôle et pousser le tout vers le *Hub*. Ici, nous nous appuyons sur l'argument `blocking=False` de l'objet `Repository` afin de pouvoir pousser les points de contrôle par époque de manière _asynchrone_. Cela nous permet de poursuivre l'entraînement sans avoir à attendre le téléchargement quelque peu lent associé à un modèle de la taille d'un Go ! +4. sauvegarder les *checkpoints* et pousser le tout vers le *Hub*. Ici, nous nous appuyons sur l'argument `blocking=False` de l'objet `Repository` afin de pouvoir pousser les *checkpoints* par époque de manière _asynchrone_. Cela nous permet de poursuivre l'entraînement sans avoir à attendre le téléchargement quelque peu lent associé à un modèle de la taille d'1 Go ! Ces étapes peuvent être vues dans le bloc de code suivant : @@ -950,7 +963,7 @@ for epoch in range(num_train_epochs): ) labels = batch["labels"] - # Si nous n'avons pas rempli la longueur maximale, nous devons également remplir les étiquettes. + # Si nous n'avons pas rempli la longueur maximale, nous devons également remplir les étiquettes labels = accelerator.pad_across_processes( batch["labels"], dim=1, pad_index=tokenizer.pad_token_id ) @@ -958,7 +971,7 @@ for epoch in range(num_train_epochs): generated_tokens = accelerator.gather(generated_tokens).cpu().numpy() labels = accelerator.gather(labels).cpu().numpy() - # Remplacer -100 dans les étiquettes car nous ne pouvons pas les décoder. + # Remplacer -100 dans les étiquettes car nous ne pouvons pas les décoder labels = np.where(labels != -100, labels, tokenizer.pad_token_id) if isinstance(generated_tokens, tuple): generated_tokens = generated_tokens[0] @@ -1008,9 +1021,9 @@ Et c'est tout ! Une fois que vous l'aurez exécuté, vous aurez un modèle et de {/if} -## Utilisation de votre modèle *finetuné* +## Utilisation de votre modèle finetuné -Une fois que vous avez poussé le modèle vers le *Hub*, vous pouvez jouer avec lui soit via le widget d'inférence, soit avec un objet `pipeline`, comme suit : +Une fois que vous avez poussé le modèle vers le *Hub*, vous pouvez jouer avec lui soit via le *widget* d'inférence, soit avec un objet `pipeline`, comme suit : ```python from transformers import pipeline @@ -1041,9 +1054,11 @@ print_summary(100) '>>> Review: Nothing special at all about this product... the book is too small and stiff and hard to write in. The huge sticker on the back doesn’t come off and looks super tacky. I would not purchase this again. I could have just bought a journal from the dollar store and it would be basically the same thing. It’s also really expensive for what it is.' # Ce produit n'a rien de spécial... le livre est trop petit et rigide et il est difficile d'y écrire. L'énorme autocollant au dos ne se détache pas et a l'air super collant. Je n'achèterai plus jamais ce produit. J'aurais pu simplement acheter un journal dans un magasin à un dollar et ce serait à peu près la même chose. Il est également très cher pour ce qu'il est. -'>>> Title: Not impressed at all... buy something else' # Pas du tout impressionné... achetez autre chose. +'>>> Title: Not impressed at all... buy something else' +# Pas du tout impressionné... achetez autre chose. -'>>> Summary: Nothing special at all about this product' # Rien de spécial à propos de ce produit +'>>> Summary: Nothing special at all about this product' +# Rien de spécial à propos de ce produit ``` Ce n'est pas si mal ! Nous pouvons voir que notre modèle a été capable d'effectuer un résumé _abstractif_ en augmentant certaines parties de la critique avec de nouveaux mots. Et peut-être que l'aspect le plus cool de notre modèle est qu'il est bilingue, donc nous pouvons également générer des résumés de critiques en espagnol : @@ -1053,13 +1068,16 @@ 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' # C'est une trilogie qui se lit très facilement. J'ai aimé, je ne m'attendais pas du tout à la fin. +'>>> Review: Es una trilogia que se hace muy facil de leer. Me ha gustado, no me esperaba el final para nada' +# C'est une trilogie qui se lit très facilement. J'ai aimé, je ne m'attendais pas du tout à la fin. -'>>> Title: Buena literatura para adolescentes' # Bonne littérature pour les adolescents +'>>> Title: Buena literatura para adolescentes' +# Bonne littérature pour les adolescents -'>>> Summary: Muy facil de leer' # Très facile à lire +'>>> Summary: Muy facil de leer' +# Très facile à lire ``` -Le résumé se traduit par "Très facile à lire", ce qui, comme nous pouvons le constater, a été extrait directement de la critique. Néanmoins, cela montre la polyvalence du modèle mT5 et vous a donné un aperçu de ce que c'est que de traiter un corpus multilingue ! +Le résumé a été extrait directement de la critique. Néanmoins, cela montre la polyvalence du modèle mT5 et vous a donné un aperçu de ce que c'est que de traiter un corpus multilingue ! Ensuite, nous allons nous intéresser à une tâche un peu plus complexe : entraîner un modèle de langue à partir de zéro. diff --git a/chapters/fr/chapter7/6.mdx b/chapters/fr/chapter7/6.mdx index a4a4fa81d..a6c81f76d 100644 --- a/chapters/fr/chapter7/6.mdx +++ b/chapters/fr/chapter7/6.mdx @@ -22,25 +22,25 @@ {/if} -Jusqu'à présent, nous avons surtout utilisé des modèles pré-entraînés et les avons *finetunés* pour de nouveaux cas d'utilisation en réutilisant les poids du pré-entraînement. Comme nous l'avons vu dans le [Chapitre 1](/course/fr/chapter1), ceci est communément appelé _apprentissage par transfert_, et c'est une stratégie très efficace pour appliquer les modèles Transformer à la plupart des cas d'utilisation du monde réel où les données étiquetées sont rares. Dans ce chapitre, nous allons adopter une approche différente et entraîner un modèle complètement nouveau à partir de zéro. C'est une bonne approche à adopter si vous avez beaucoup de données et qu'elle est très différente des données de pré-entraînement utilisées pour les modèles disponibles. Cependant, le pré-entraînement d'un modèle de langue nécessite beaucoup plus de ressources informatiques que le simple affinage d'un modèle existant. Parmi les exemples où il peut être utile d'entraîner un nouveau modèle, citons les ensembles de données constitués de notes de musique, de séquences moléculaires telles que l'ADN ou de langages de programmation. Ces derniers ont récemment gagné en popularité grâce à des outils tels que TabNine et Copilot de GitHub, alimentés par le modèle Codex d'OpenAI, qui peuvent générer de longues séquences de code. Cette tâche de génération de texte est mieux abordée avec des modèles de langage autorégressifs ou causaux tels que GPT-2. +Jusqu'à présent, nous avons surtout réutilisé des modèles pré-entraînés et les avons *finetunés* sur de nouveaux cas d'usage. Comme nous l'avons vu dans le [chapitre 1](/course/fr/chapter1), ceci est communément appelé _apprentissage par transfert_, et il s'agit d'une stratégie très efficace pour appliquer les *transformers* à la plupart des applications du monde réel où les données étiquetées sont rares. Dans ce chapitre, nous allons adopter une approche différente consistant à entraîner un modèle complètement nouveau à partir de zéro. C'est une bonne démarche à adopter si vous avez beaucoup de données et qu'elles sont très différentes des données de pré-entraînement utilisées par les modèles disponibles. Cependant, le pré-entraînement d'un modèle de langue nécessite beaucoup plus de ressources informatiques que le simple *finetuning* d'un modèle existant. Parmi les exemples où il peut être utile d'entraîner un nouveau modèle, citons les jeux de données constitués de notes de musique, de séquences moléculaires telles que l'ADN, ou de langages de programmation. Ces derniers ont récemment gagné en popularité grâce à des outils tels que TabNine et Copilot de GitHub (alimentés par le modèle Codex d'OpenAI) qui peuvent générer de longues séquences de code. Cette tâche de génération de texte est mieux abordée avec des modèles de langage autorégressifs ou causaux tels que le GPT-2. -Dans cette section, nous allons construire une version réduite d'un modèle de génération de code : nous nous concentrerons sur les compléments d'une ligne au lieu des fonctions ou des classes complètes, en utilisant un sous-ensemble de code Python. Lorsque vous travaillez avec des données en Python, vous êtes souvent en contact avec la pile de données scientifiques Python, composée des bibliothèques `matplotlib`, `seaborn`, `pandas` et `scikit-learn`. Lors de l'utilisation de ces *frameworks*, il est fréquent d'avoir besoin de rechercher des commandes spécifiques, il serait donc bien d'utiliser un modèle pour compléter ces appels pour nous. +Dans cette section, nous allons construire une version réduite d'un modèle de génération de code Python. Nous nous concentrerons sur la complétion d'une ligne de code au lieu de fonctions ou de classes complètes. Lorsque vous travaillez sur des projets de science des données en Python, vous êtes souvent en contact avec les bibliothèques `matplotlib`, `seaborn`, `pandas` et `scikit-learn`. Lors de l'utilisation de ces *frameworks*, il est fréquent d'avoir besoin de rechercher des commandes spécifiques. Il serait donc bien d'utiliser un modèle pour compléter ces appels pour nous. -Dans le [Chapitre 6](/course/fr/chapter6), nous avons créé un *tokenizer* efficace pour traiter le code source Python, mais nous avons toujours besoin d'un ensemble de données à grande échelle pour pré-entraîner un modèle. Ici, nous allons appliquer notre *tokenizer* à un corpus de code Python provenant des dépôts GitHub. Nous utiliserons ensuite l'API `Trainer` et 🤗 *Accelerate* pour entraîner le modèle. C'est parti ! +Dans le [chapitre 6](/course/fr/chapter6), nous avons créé un *tokenizer* efficace pour traiter du code Python. Nous avons besoin d'un jeu de données à grande échelle pour pré-entraîner un modèle. Ici, nous allons appliquer notre *tokenizer* à un corpus de code Python provenant des dépôts GitHub. Nous utiliserons ensuite l'API `Trainer` et 🤗 *Accelerate* pour entraîner le modèle. C'est parti ! -Il s'agit en fait de la présentation du modèle qui a été entraîné et téléchargé sur le *Hub* à l'aide du code présenté dans cette section. Vous pouvez le trouver [ici](https://huggingface.co/huggingface-course/codeparrot-ds?text=plt.imshow%28). Notez qu'étant donné qu'il y a une certaine randomisation dans la génération du texte, vous obtiendrez probablement un résultat légèrement différent. +Il s'agit d'une présentation du modèle qui a été entraîné à l'aide du code présenté dans cette section et qui a ensuité été téléchargé sur le *Hub*. Vous pouvez le trouver [ici](https://huggingface.co/huggingface-course/codeparrot-ds?text=plt.imshow%28). Notez qu'étant donné qu'il y a un certains aléat dans la génération du texte, vous obtiendrez probablement un résultat légèrement différent. ## Collecte des données -Le code Python est disponible en abondance dans les dépôts de code tels que GitHub, que nous pouvons utiliser pour créer un ensemble de données en récupérant chaque dépôt Python. C'est l'approche adoptée dans le [manuel Transformers](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/) pour pré-entraîner un grand modèle GPT-2. En utilisant un dépôt GitHub d'environ 180 Go contenant approximativement 20 millions de fichiers Python appelé `codeparrot`, les auteurs ont construit un ensemble de données qu'ils ont ensuite partagé sur le [*Hub*](https://huggingface.co/datasets/transformersbook/codeparrot). +On peut trouver du code Python en abondance dans les dépôts de code tels que GitHub, que nous pouvons utiliser pour créer un jeu de données en récupérant chaque dépôt Python. C'est l'approche adoptée dans le [livre *Natural Language Processing with Transformers*](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/) pour pré-entraîner un grand GPT-2. En utilisant un dépôt GitHub d'environ 180 Go contenant approximativement 20 millions de fichiers Python, les auteurs du livre ont construit un jeu de données appelé `codeparrot` qu'ils ont ensuite partagé sur le [*Hub*](https://huggingface.co/datasets/transformersbook/codeparrot). -Cependant, s'entraîner sur l'ensemble du corpus prend beaucoup de temps et demande beaucoup de calculs, et nous n'avons besoin que du sous-ensemble du jeu de données concerné par la pile Python pour la science des données. Commençons donc par filtrer l'ensemble de données `codeparrot` pour tous les fichiers qui incluent l'une des bibliothèques de cette pile. En raison de la taille de l'ensemble de données, nous voulons éviter de le télécharger ; à la place, nous utiliserons la fonctionnalité de streaming pour le filtrer à la volée. Pour nous aider à filtrer les échantillons de code utilisant les bibliothèques que nous avons mentionnées précédemment, nous utiliserons la fonction suivante : +Cependant, entraîner sur l'ensemble du corpus prend beaucoup de temps et demande beaucoup de ressources de calculs. Dans notre cas, nous n'avons besoin que du sous-ensemble du jeu de données qui est relatif aux codes portant sur la science des données. Commençons donc par filtrer le jeu de données `codeparrot` en ne gardant que les fichiers incluant l'une des bibliothèques de science des données énumérées précédemment. En raison de la taille du jeu de données, nous voulons éviter de le télécharger. Nous utiliserons donc la fonctionnalité de *streaming* de 🤗 *Datasets* afin de le filtrer à la volée. Pour nous aider à filtrer les échantillons de code utilisant les bibliothèques que nous avons mentionnées précédemment, nous utilisons la fonction suivante : ```py def any_keyword_in_string(string, keywords): @@ -66,7 +66,7 @@ print( False True ``` -Nous pouvons l'utiliser pour créer une fonction qui diffusera l'ensemble de données et filtrera les éléments que nous voulons : +Nous pouvons l'utiliser pour créer une fonction qui va *streamer* le jeu de donner et filtrer les éléments que nous voulons : ```py def filter_streaming_dataset(dataset, filters): @@ -81,7 +81,7 @@ def filter_streaming_dataset(dataset, filters): return Dataset.from_dict(filtered_dict) ``` -Ensuite, nous pouvons simplement appliquer cette fonction à l'ensemble de données en continu : +Ensuite, nous pouvons simplement appliquer cette fonction : ```py # Cette cellule prendra beaucoup de temps à s'exécuter, donc vous devriez la sauter et aller à la suivante ! @@ -98,9 +98,9 @@ filtered_data = filter_streaming_dataset(data, filters) 3.26% of data after filtering. ``` -Cela nous laisse avec environ 3 % de l'ensemble de données original, ce qui est tout de même assez important. L'ensemble de données résultant est de 6 Go et se compose de 600 000 scripts Python ! +Cela nous laisse avec environ 3 % du jeu de données original, ce qui est tout de même assez important puisqu'il fait 6 Go et se compose de 600 000 scripts Python ! -Le filtrage de l'ensemble complet de données peut prendre de 2 à 3 heures, selon votre machine et votre bande passante. Si vous ne voulez pas passer par ce long processus vous-même, nous fournissons l'ensemble de données filtré sur le *Hub* pour que vous puissiez le télécharger : +Le filtrage peut prendre de 2 à 3 heures, selon votre machine et votre bande passante. Si vous ne voulez pas passer par ce long processus, nous fournissons sur le *Hub* le jeu de données filtré pour que vous puissiez le télécharger : ```py from datasets import load_dataset, DatasetDict @@ -133,11 +133,11 @@ DatasetDict({ -Le pré-entraînement du modèle de langue prendra un certain temps. Nous vous suggérons d'exécuter d'abord la boucle d'entraînement sur un échantillon des données en décommentant les deux lignes partielles ci-dessus, et de vous assurer que l'entraînement se termine avec succès et que les modèles sont stockés. Rien n'est plus frustrant qu'un entraînement qui échoue à la dernière étape parce que vous avez oublié de créer un dossier ou parce qu'il y a une faute de frappe à la fin de la boucle d'entraînement ! +Le pré-entraînement du modèle de langue prendra un certain temps. Nous vous suggérons donc d'exécuter d'abord la boucle d'entraînement sur un petit échantillon des données en décommentant les deux lignes dans le code ci-dessus. Assurez-vous alors que l'entraînement se termine avec succès et que les modèles sont stockés. Rien n'est plus frustrant qu'un entraînement qui échoue à la dernière étape car vous avez oublié de créer un dossier ou parce qu'il y a une faute de frappe à la fin de la boucle d'entraînement ! -Examinons un exemple tiré de l'ensemble de données. Nous ne montrerons que les 200 premiers caractères de chaque champ : +Examinons un exemple tiré du jeu de données. Nous ne montrerons que les 200 premiers caractères de chaque champ : ```py for key in raw_datasets["train"][0]: @@ -169,16 +169,16 @@ Nous pouvons voir que le champ `content` contient le code sur lequel nous voulon -La première étape sera de tokeniser les données, afin de pouvoir les utiliser pour l'entraînement. Puisque notre objectif est principalement d'autocompléter des appels de fonction courts, nous pouvons garder la taille du contexte relativement petite. L'avantage est que nous pouvons entraîner le modèle beaucoup plus rapidement et qu'il nécessite beaucoup moins de mémoire. S'il est important pour votre application d'avoir plus de contexte (par exemple, si vous voulez que le modèle écrive des tests unitaires basés sur un fichier avec la définition de la fonction), assurez-vous d'augmenter ce nombre, mais gardez également à l'esprit que cela s'accompagne d'une plus grande empreinte mémoire du GPU. Pour l'instant, fixons la taille du contexte à 128 *tokens*, par opposition aux 1 024 ou 2 048 utilisés dans GPT-2 ou GPT-3, respectivement. +La première étape est de tokeniser les données afin de pouvoir les utiliser pour l'entraînement. Puisque notre objectif est d'autocompléter de courts appels de fonctions, nous pouvons garder la taille du contexte relativement petite. L'avantage est que nous pouvons entraîner le modèle beaucoup plus rapidement et qu'il nécessite beaucoup moins de mémoire. Si c'est important pour votre application d'avoir davantage de contexte (par exemple, si vous voulez que le modèle écrive des tests unitaires basés sur un fichier avec la définition de la fonction), assurez-vous d'augmenter ce nombre. Gardez néanmoins à l'esprit que cela s'accompagne d'une plus grande empreinte mémoire du GPU. Pour l'instant, fixons la taille du contexte à 128 *tokens*, par opposition aux 1 024 ou 2 048 utilisés respectivement dans le GPT-2 et le GPT-3. -La plupart des documents contiennent beaucoup plus de 128 *tokens*, donc le fait de tronquer les entrées à la longueur maximale éliminerait une grande partie de notre jeu de données. A la place, nous utiliserons l'option `return_overflowing_tokens` pour tokeniser l'entrée entière et la diviser en plusieurs morceaux, comme nous l'avons fait dans [Chapter 6](/course/chapter6/4). Nous utiliserons également l'option `return_length` pour retourner automatiquement la longueur de chaque morceau créé. Souvent, le dernier morceau sera plus petit que la taille du contexte, et nous nous débarrasserons de ces morceaux pour éviter les problèmes de remplissage ; nous n'en avons pas vraiment besoin puisque nous avons beaucoup de données de toute façon. +La plupart des documents contiennent beaucoup plus de 128 *tokens*, donc le fait de tronquer les entrées à la longueur maximale éliminerait une grande partie de notre jeu de données. A la place, nous allons utiliser l'option `return_overflowing_tokens` pour tokeniser l'entrée entière et la diviser en plusieurs morceaux, comme nous l'avons fait dans le [chapitre 6](/course/fr/chapter6/4). Nous utiliserons également l'option `return_length` pour retourner automatiquement la longueur de chaque morceau créé. Souvent, le dernier morceau est plus petit que la taille du contexte et nous nous en débarrasserons pour éviter les problèmes de *padding*. Nous n'en avons pas vraiment besoin puisque de toute façon nous avons beaucoup de données.
Chunking a large texts in several pieces.
-Voyons exactement comment cela fonctionne en examinant les deux premiers exemples : +Voyons comment cela fonctionne en examinant les deux premiers exemples : ```py from transformers import AutoTokenizer @@ -205,9 +205,9 @@ Input chunk lengths: [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 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] ``` -Nous pouvons voir que nous obtenons 34 segments au total à partir de ces deux exemples. En regardant les longueurs des *chunks*, nous pouvons voir que les *chunks* à la fin des deux documents ont moins de 128 *tokens* (117 et 41, respectivement). Ils ne représentent qu'une petite fraction du total des *chunks* que nous avons, donc nous pouvons les jeter sans risque. Avec le champ `overflow_to_sample_mapping`, nous pouvons aussi reconstruire quels *chunks* appartenaient à quels échantillons d'entrée. +Nous pouvons voir que nous obtenons 34 morceaux à partir de ces deux exemples. En regardant leurs longueurs, nous pouvons voir qu'ils se terminent avec moins de 128 *tokens* (117 et 41, respectivement). Ils ne représentent qu'une petite fraction du total des morceaux que nous avons (2/34), donc nous pouvons les jeter sans risque. Avec le champ `overflow_to_sample_mapping`, nous pouvons aussi reconstruire quels morceaux appartenaient à quels échantillons d'entrée. -Avec cette opération, nous utilisons une fonctionnalité pratique de la fonction `Dataset.map()` dans 🤗 *Datasets*, qui est qu'elle ne nécessite pas de mappage un à un ; comme nous l'avons vu dans la [section 3](/course/fr/chapter7/3), nous pouvons créer des batchs avec plus ou moins d'éléments que le batchd'entrée. Ceci est utile lorsque l'on effectue des opérations telles que l'augmentation ou le filtrage des données qui modifient le nombre d'éléments. Dans notre cas, lors de la tokenisation de chaque élément en *chunks* de la taille de contexte spécifiée, nous créons de nombreux échantillons de chaque document. Nous devons juste nous assurer de supprimer les colonnes existantes, car elles ont une taille conflictuelle. Si nous voulions les garder, nous pourrions les répéter de manière appropriée et les retourner dans l'appel `Dataset.map()` : +Avec cette opération, nous utilisons une fonctionnalité pratique de la fonction `Dataset.map()` de 🤗 *Datasets*. En effet, celle-ci ne nécessite pas une correspondance un à un comme nous l'avons vu dans la [section 3](/course/fr/chapter7/3). Nous pouvons créer des batchs avec plus ou moins d'éléments que le batch d'entrée. C'est utile lorsque l'on effectue des opérations telles que l'augmentation ou le filtrage des données qui modifient le nombre d'éléments. Dans notre cas, lors de la tokenisation de chaque élément en morceaux de longeur de la taille de contexte spécifiée, nous créons de nombreux échantillons de chaque document. Nous devons juste nous assurer de supprimer les colonnes existantes, car elles ont une taille conflictuelle. Si nous voulions les garder, nous pourrions les répéter de manière appropriée et les retourner dans l'appel `Dataset.map()` : ```py def tokenize(element): @@ -244,20 +244,20 @@ DatasetDict({ }) ``` -Nous avons maintenant 16,7 millions d'exemples avec 128 *tokens* chacun, ce qui correspond à environ 2,1 milliards de *tokens* au total. Pour référence, les modèles GPT-3 et Codex d'OpenAI sont entraînés sur 300 et 100 milliards de *tokens*, respectivement, où les modèles Codex sont initialisés à partir des points de contrôle GPT-3. Notre objectif dans cette section n'est pas de rivaliser avec ces modèles, qui peuvent générer des textes longs et cohérents, mais de créer une version réduite fournissant une fonction d'autocomplétion rapide pour les scientifiques des données. +Nous avons maintenant 16,7 millions d'exemples avec 128 *tokens* chacun, ce qui correspond à environ 2,1 milliards de *tokens* au total. A titre de comparaison, les modèles GPT-3 et Codex d'OpenAI sont entraînés sur 300 et 100 milliards de *tokens*, respectivement. Les modèles Codex étant initialisés à partir des *checkpoints* GPT-3. Notre objectif dans cette section n'est pas de rivaliser avec ces modèles, qui peuvent générer des textes longs et cohérents, mais de créer une version réduite fournissant une fonction d'autocomplétion rapide. -Maintenant que l'ensemble de données est prêt, configurons le modèle ! +Maintenant que le jeu de données est prêt, configurons le modèle ! -✏️ **Essayez** Se débarrasser de tous les morceaux qui sont plus petits que la taille du contexte n'était pas un gros problème ici parce que nous utilisons de petites fenêtres de contexte. Si vous augmentez la taille du contexte (ou si vous avez un corpus de documents courts), la fraction des morceaux qui sont jetés augmentera également. Une façon plus efficace de préparer les données est de joindre tous les échantillons dans un batch avec un *token* `eos_token_id` entre les deux, puis d'effectuer le chunking sur les séquences concaténées. Comme exercice, modifiez la fonction `tokenize()` pour utiliser cette approche. Notez que vous devrez mettre `truncation=False` et enlever les autres arguments du *tokenizer* pour obtenir la séquence complète des IDs des *tokens*. +✏️ **Essayez !** Se débarrasser de tous les morceaux qui sont plus petits que la taille du contexte n'était pas un gros problème ici parce que nous utilisons de petites fenêtres de contexte. Si vous augmentez la taille du contexte (ou si vous avez un corpus de documents courts), la fraction des morceaux qui sont jetés augmentera. Une façon plus efficace de préparer les données est de joindre tous les échantillons dans un batch avec un *token* `eos_token_id` entre les deux, puis d'effectuer le découpage sur les séquences concaténées. Comme exercice, modifiez la fonction `tokenize()` pour utiliser cette approche. Notez que vous devrez mettre `truncation=False` et enlever les autres arguments du *tokenizer* pour obtenir la séquence complète des identifiants des *tokens*. ## Initialisation d'un nouveau modèle -Notre première étape consiste à initialiser fraîchement un modèle GPT-2. Nous utiliserons la même configuration pour notre modèle que pour le petit modèle GPT-2, donc nous chargeons la configuration pré-entraînée, nous nous assurons que la taille du *tokenizer* correspond à la taille du vocabulaire du modèle et nous passons les identifiants des *tokens* `bos` et `eos` (début et fin de séquence) : +Notre première étape consiste à initialiser un GPT-2. Pour notre modèle, nous utiliserons la même configuration que pour le petit modèle GPT-2. Ainsi nous chargeons la configuration pré-entraînée, nous nous assurons que la taille du *tokenizer* correspond à la taille du vocabulaire du modèle et nous passons les identifiants des *tokens* `bos` et `eos` (début et fin de séquence) : {#if fw === 'pt'} @@ -273,7 +273,7 @@ config = AutoConfig.from_pretrained( ) ``` -Avec cette configuration, nous pouvons charger un nouveau modèle. Notez que c'est la première fois que nous n'utilisons pas la fonction `from_pretrained()`, puisque nous initialisons nous-mêmes un modèle : +Avec cette configuration, nous pouvons charger un nouveau modèle. Notez que c'est la première fois que nous n'utilisons pas la fonction `from_pretrained()` puisque nous initialisons nous-mêmes un modèle : ```py model = GPT2LMHeadModel(config) @@ -299,11 +299,11 @@ config = AutoConfig.from_pretrained( ) ``` -Avec cette configuration, nous pouvons charger un nouveau modèle. Notez que c'est la première fois que nous n'utilisons pas la fonction `from_pretrained()`, puisque nous initialisons nous-mêmes un modèle : +Avec cette configuration, nous pouvons charger un nouveau modèle. Notez que c'est la première fois que nous n'utilisons pas la fonction `from_pretrained()` puisque nous initialisons nous-mêmes un modèle : ```py model = TFGPT2LMHeadModel(config) -model(model.dummy_inputs) # Builds the model +model(model.dummy_inputs) # Construit le modèle model.summary() ``` @@ -321,9 +321,9 @@ _________________________________________________________________ {/if} -Notre modèle comporte 124 millions de paramètres que nous devrons régler. Avant de commencer l'entraînement, nous devons configurer un collateur de données qui se chargera de créer les lots. Nous pouvons utiliser le collateur `DataCollatorForLanguageModeling`, qui est conçu spécifiquement pour la modélisation du langage (comme son nom le suggère subtilement). En plus de l'empilage et du remplissage des lots, il s'occupe aussi de la création des étiquettes du modèle de langage -- dans la modélisation causale du langage, les entrées servent aussi d'étiquettes (juste décalées d'un élément), et ce collateur de données les crée à la volée pendant l'entraînement pour ne pas avoir à dupliquer les `input_ids`. +Notre modèle comporte 124 millions de paramètres que nous devrons régler. Avant de commencer l'entraînement, nous devons configurer un collateur de données qui se chargera de créer les batchs. Nous pouvons utiliser le collateur `DataCollatorForLanguageModeling`, qui est conçu spécifiquement pour la modélisation du langage (comme son nom le suggère subtilement). En plus de l'empilage et du rembourrage des batchs, il s'occupe aussi de la création des étiquettes du modèle de langage. Dans la modélisation causale du langage, les entrées servent aussi d'étiquettes (juste décalées d'un élément) et que le collateur de données crée à la volée pendant l'entraînement pour ne pas avoir à dupliquer les `input_ids`. -Notez que `DataCollatorForLanguageModeling` supporte à la fois le *masked language modeling* (MLM) et le *causal language modeling* (CLM). Par défaut, il prépare les données pour MLM, mais nous pouvons passer à CLM en définissant l'argument `mlm=False` : +Notez que `DataCollatorForLanguageModeling` supporte à la fois la modélisation du langage masqué (MLM pour *masked language modeling*) et la modélisation du langage causal (CLM pour *causal language modeling*). Par défaut, il prépare les données pour la MLM mais nous pouvons passer à la CLM en définissant l'argument `mlm=False` : {#if fw === 'pt'} @@ -401,7 +401,7 @@ tf_eval_dataset = tokenized_dataset["valid"].to_tf_dataset(
-Nous avons maintenant tout ce qu'il faut pour former notre modèle - ce n'était pas si compliqué après tout ! Avant de commencer l'entraînement, nous devons nous connecter à Hugging Face. Si vous travaillez dans un *notebook*, vous pouvez le faire avec la fonction utilitaire suivante : +Nous avons maintenant tout ce qu'il faut pour entraîner notre modèle. Ce n'était pas si compliqué ! Avant de commencer l'entraînement, nous devons nous connecter à Hugging Face. Si vous travaillez dans un *notebook*, vous pouvez le faire avec la fonction utilitaire suivante : ```python from huggingface_hub import notebook_login @@ -409,7 +409,7 @@ from huggingface_hub import notebook_login notebook_login() ``` -Cela affichera un widget où vous pourrez entrer vos identifiants de connexion à Hugging Face. +Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face. Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : @@ -419,7 +419,7 @@ huggingface-cli login {#if fw === 'pt'} -Tout ce qu'il reste à faire est de configurer les arguments d'entraînement et de lancer le `Trainer`. Nous utiliserons un programme de taux d'apprentissage en cosinus avec un certain réchauffement et une taille de lot effective de 256 (`per_device_train_batch_size` * `gradient_accumulation_steps`). L'accumulation du gradient est utilisée lorsqu'un seul lot ne tient pas en mémoire, et construit le gradient de manière incrémentale à travers plusieurs passages avant/arrière. Nous verrons cela en action lorsque nous créerons la boucle d'entraînement avec 🤗 *Accelerate*. +Tout ce qu'il reste à faire est de configurer les arguments d'entraînement et de lancer la fonction `Trainer`. Nous utiliserons un programme de taux d'apprentissage de type cosinus avec un réchauffement et une taille de batch de 256 (`per_device_train_batch_size` x `gradient_accumulation_steps`). L'accumulation du gradient est utilisée lorsqu'un seul batch ne tient pas en mémoire, et construit le gradient de manière incrémentale à travers plusieurs passages en avant/en arrière. Nous verrons cela en action lorsque nous créerons la boucle d'entraînement avec 🤗 *Accelerate*. ```py from transformers import Trainer, TrainingArguments @@ -452,13 +452,13 @@ trainer = Trainer( ) ``` -Maintenant, nous pouvons simplement lancer le `Trainer` et attendre que l'entraînement se termine. Selon que vous l'exécutez sur la totalité ou sur un sous-ensemble de l'ensemble d'entraînement, cela prendra respectivement 20 ou 2 heures, alors prenez quelques cafés et un bon livre à lire ! +Maintenant, nous pouvons simplement lancer le `Trainer` et attendre que l'entraînement se termine. Selon que vous l'exécutez sur la totalité ou sur un sous-ensemble de l'échantillon d'entraînement, cela prendra respectivement 20 ou 2 heures. Alors prenez quelques cafés et un bon livre à lire ! ```py trainer.train() ``` -Une fois l'entraînement terminé, nous pouvons pousser le modèle et le *tokenizer* vers le Hub : +Une fois l'entraînement terminé, nous pouvons pousser le modèle et le *tokenizer* vers le *Hub* : ```py trainer.push_to_hub() @@ -466,7 +466,7 @@ trainer.push_to_hub() {:else} -Tout ce qu'il reste à faire est de configurer les hyperparamètres d'entraînement et d'appeler `compile()` et `fit()`. Nous utiliserons un programme de taux d'apprentissage avec un certain échauffement pour améliorer la stabilité de l'entraînement : +Tout ce qu'il reste à faire est de configurer les hyperparamètres d'entraînement et d'appeler `compile()` et `fit()`. Nous utiliserons un programme de taux d'apprentissage avec un réchauffement pour améliorer la stabilité de l'entraînement : ```py from transformers import create_optimizer @@ -481,11 +481,11 @@ optimizer, schedule = create_optimizer( ) model.compile(optimizer=optimizer) -# Train in mixed-precision float16 +# Entraîner en mixed-precision float16 tf.keras.mixed_precision.set_global_policy("mixed_float16") ``` -Maintenant, nous pouvons simplement appeler `model.fit()` et attendre que l'entraînement se termine. Selon que vous l'exécutez sur la totalité ou sur un sous-ensemble de l'ensemble d'entraînement, cela prendra respectivement 20 ou 2 heures, alors prenez quelques cafés et un bon livre à lire ! Une fois l'entraînement terminé, nous pouvons pousser le modèle et le *tokenizer* vers le *Hub* : +Maintenant, nous pouvons simplement appeler `model.fit()` et attendre que l'entraînement se termine. Selon que vous l'exécutez sur la totalité ou sur un sous-ensemble de l'échantillon d'entraînement, cela prendra respectivement 20 ou 2 heures. Alors prenez quelques cafés et un bon livre à lire ! Une fois l'entraînement terminé, nous pouvons pousser le modèle et le *tokenizer* vers le *Hub* : ```py from transformers.keras_callbacks import PushToHubCallback @@ -499,7 +499,7 @@ model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback -✏️ **Essayez** Il ne nous a fallu qu'une trentaine de lignes de code en plus des `TrainingArguments` pour passer des textes bruts à l'entraînement de GPT-2. Essayez-le avec votre propre jeu de données et voyez si vous pouvez obtenir de bons résultats ! +✏️ **Essayez !** Il ne nous a fallu qu'une trentaine de lignes de code en plus des `TrainingArguments` pour passer des textes bruts à l'entraînement du GPT-2. Essayez-le avec votre propre jeu de données et voyez si vous pouvez obtenir de bons résultats ! @@ -507,19 +507,19 @@ model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback {#if fw === 'pt'} -💡 Si vous avez accès à une machine avec plusieurs GPUs, essayez d'y exécuter le code. Le `Trainer` gère automatiquement plusieurs machines, et cela peut accélérer considérablement l'entraînement. +💡 Si vous avez accès à une machine avec plusieurs GPUs, essayez d'y exécuter le code. `Trainer` gère automatiquement plusieurs machines ce qui peut accélérer considérablement l'entraînement. {:else} -💡 Si vous avez accès à une machine avec plusieurs GPU, vous pouvez essayer d'utiliser un contexte `MirroredStrategy` pour accélérer considérablement l'entraînement. Vous devrez créer un objet `tf.distribute.MirroredStrategy`, et vous assurer que les commandes `to_tf_dataset` ainsi que la création du modèle et l'appel à `fit()` sont tous exécutés dans son contexte `scope()`. Vous pouvez consulter la documentation à ce sujet [ici](https://www.tensorflow.org/guide/distributed_training#use_tfdistributestrategy_with_keras_modelfit). +💡 Si vous avez accès à une machine avec plusieurs GPUs, vous pouvez essayer d'utiliser `MirroredStrategy` pour accélérer considérablement l'entraînement. Vous devrez créer un objet `tf.distribute.MirroredStrategy` et vous assurer que les commandes `to_tf_dataset` ainsi que la création du modèle et l'appel à `fit()` sont tous exécutés dans `scope()`. Vous pouvez consulter la documentation à ce sujet [ici](https://www.tensorflow.org/guide/distributed_training#use_tfdistributestrategy_with_keras_modelfit). {/if} -## Génération de code avec un pipeline +## Génération de code avec le pipeline -C'est maintenant le moment de vérité : voyons comment le modèle entraîné fonctionne réellement ! Nous pouvons voir dans les logs que la perte a diminué régulièrement, mais pour mettre le modèle à l'épreuve, regardons comment il fonctionne sur certains messages. Pour ce faire, nous allons envelopper le modèle dans une `pipeline` de génération de texte, et nous allons le mettre sur le GPU pour des générations rapides s'il y en a un de disponible : +C'est maintenant le moment de vérité : voyons comment le modèle entraîné fonctionne réellement ! Nous pouvons voir dans les logs que la perte a diminué régulièrement, mais pour mettre le modèle à l'épreuve, regardons comment il fonctionne sur certains messages. Pour ce faire, nous allons envelopper le modèle dans un `pipeline` de génération de texte et, s'il y en a un de disponible, utiliser un GPU pour avoir des générations rapidement : {#if fw === 'pt'} @@ -600,7 +600,8 @@ txt = """\ # tableau de données avec profession, revenu et nom df = pd.DataFrame({'profession': x, 'income':y, 'name': z}) -# calculer le revenu moyen par profession""" +# calculer le revenu moyen par profession +""" print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) ``` @@ -612,7 +613,7 @@ df = pd.DataFrame({'profession': x, 'income':y, 'name': z}) profession = df.groupby(['profession']).mean() ``` -Pas mal, c'est la bonne façon de faire. Enfin, voyons si nous pouvons aussi l'utiliser pour `scikit-learn` et mettre en place un modèle *Random Forest* : +Pas mal, c'est la bonne façon de faire. Enfin, voyons si nous pouvons aussi l'utiliser pour `scikit-learn` et utiliser un modèle *Random Forest* : ```py txt = """ @@ -636,23 +637,23 @@ rf {#if fw === 'tf'} -Au vu de ces quelques exemples, il semble que le modèle ait appris une partie de la syntaxe de la pile Python pour la science des données. Bien sûr, nous devrions évaluer le modèle de manière plus approfondie avant de le déployer dans le monde réel, mais il s'agit tout de même d'un prototype impressionnant. +Au vu de ces quelques exemples, il semble que le modèle ait appris une partie de la syntaxe des bibliothèques Python de science des données. Bien sûr, nous devrions évaluer le modèle de manière plus approfondie avant de le déployer dans le monde réel, mais il s'agit tout de même d'un prototype impressionnant. {:else} -Au vu de ces quelques exemples, il semble que le modèle ait appris une partie de la syntaxe de la pile de science des données Python (bien sûr, nous devrions l'évaluer de manière plus approfondie avant de déployer le modèle dans le monde réel). Cependant, il est parfois nécessaire de personnaliser davantage l'entraînement du modèle afin d'obtenir les performances nécessaires pour un cas d'utilisation donné. Par exemple, que se passe-t-il si l'on souhaite mettre à jour dynamiquement la taille du lot ou si l'on dispose d'une boucle d'entraînement conditionnelle qui ignore les mauvais exemples à la volée ? Une option serait de sous-classer le `Trainer` et d'ajouter les changements nécessaires, mais parfois il est plus simple d'écrire la boucle d'entraînement à partir de zéro. C'est là qu'intervient 🤗 *Accelerate*. +Au vu de ces quelques exemples, il semble que le modèle ait appris une partie de la syntaxe des bibliothèques Python de science des données. Bien sûr, nous devrions évaluer le modèle de manière plus approfondie avant de le déployer dans le monde réel, mais il s'agit tout de même d'un prototype impressionnant. Parfois, il est nécessaire de personnaliser davantage l'entraînement du modèle afin d'obtenir les performances nécessaires pour un cas d'utilisation donné. Par exemple, que se passe-t-il si l'on souhaite mettre à jour dynamiquement la taille du batch ou si l'on dispose d'une boucle d'entraînement conditionnelle qui ignore les mauvais exemples à la volée ? Une option serait de sous-classer le `Trainer` et d'ajouter les changements nécessaires, mais parfois il est plus simple d'écrire la boucle d'entraînement à partir de zéro. C'est là qu'intervient 🤗 *Accelerate*. {/if} {#if fw === 'pt'} -## Entraîner avec 🤗 *Accelerate* +## Entraîner avec 🤗 Accelerate -Nous avons vu comment entraîner un modèle avec le `Trainer`, qui peut permettre une certaine personnalisation. Cependant, parfois nous voulons un contrôle total sur la boucle d'entraînement, ou nous voulons faire quelques changements exotiques. Dans ce cas, 🤗 *Accelerate* est un excellent choix, et dans cette section, nous allons suivre les étapes pour l'utiliser pour entraîner notre modèle. Pour rendre les choses plus intéressantes, nous allons également ajouter une touche à la boucle d'entraînement. +Nous avons vu comment entraîner un modèle avec le `Trainer`, qui permet une certaine personnalisation. Cependant, parfois nous voulons un contrôle total sur la boucle d'entraînement ou nous souhaitons faire quelques changements exotiques. Dans ce cas, 🤗 *Accelerate* est un excellent choix, et dans cette section, nous allons suivre les étapes pour l'utiliser pour entraîner notre modèle. Pour rendre les choses plus intéressantes, nous allons également ajouter une touche à la boucle d'entraînement. -Puisque nous sommes principalement intéressés par l'autocomplétion sensible pour les bibliothèques de science des données, il est logique de donner plus de poids aux échantillons d'entraînement qui utilisent davantage ces bibliothèques. Nous pouvons facilement identifier ces exemples grâce à l'utilisation de mots-clés tels que `plt`, `pd`, `sk`, `fit`, et `predict`, qui sont les noms d'importation les plus fréquents pour `matplotlib.pyplot`, `pandas`, et `sklearn` ainsi que le modèle fit/predict de ce dernier. Si chacun d'entre eux est représenté par un seul token, nous pouvons facilement vérifier s'ils apparaissent dans la séquence d'entrée. Les *tokens* peuvent avoir un préfixe d'espacement, donc nous vérifierons aussi ces versions dans le vocabulaire du *tokenizer*. Pour vérifier que cela fonctionne, nous ajouterons un *token* de test qui devrait être divisé en plusieurs *tokens* : +Puisque nous sommes principalement intéressés par l'autocomplétion pour les bibliothèques de science des données, il est logique de donner plus de poids aux échantillons d'entraînement qui utilisent davantage ces bibliothèques. Nous pouvons facilement identifier ces exemples grâce à l'utilisation de mots-clés tels que `plt`, `pd`, `sk`, `fit`, et `predict`, qui sont les noms d'importation les plus fréquents pour `matplotlib.pyplot`, `pandas`, et `sklearn` ainsi que les fonctions `fit` et `predict` de cette dernière. Si chacun d'entre eux est représenté par un seul *token*, nous pouvons facilement vérifier s'ils apparaissent dans la séquence d'entrée. Les *tokens* peuvent avoir un préfixe d'espacement, donc nous vérifierons aussi ces versions dans le vocabulaire du *tokenizer*. Pour vérifier que cela fonctionne, nous ajouterons un *token* de test qui devrait être divisé en plusieurs *tokens* : ```py keytoken_ids = [] @@ -688,31 +689,31 @@ import torch def keytoken_weighted_loss(inputs, logits, keytoken_ids, alpha=1.0): - # Shift so that tokens < n predict n + # Décalage pour que tokens < n prédisent n shift_labels = inputs[..., 1:].contiguous() shift_logits = logits[..., :-1, :].contiguous() - # Calculate per-token loss + # Calcul de la perte par token loss_fct = CrossEntropyLoss(reduce=False) loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1)) - # Resize and average loss per sample + # Redimensionnement et perte moyenne par échantillon loss_per_sample = loss.view(shift_logits.size(0), shift_logits.size(1)).mean(axis=1) - # Calculate and scale weighting + # Calculer et échelonner la pondération weights = torch.stack([(inputs == kt).float() for kt in keytoken_ids]).sum( axis=[0, 2] ) weights = alpha * (1.0 + weights) - # Calculate weighted average + # Calculer la moyenne pondérée weighted_loss = (loss_per_sample * weights).mean() return weighted_loss ``` -Avant de commencer à s'entraîner avec cette nouvelle fonction de perte géniale, nous devons préparer quelques éléments : +Avant de commencer à entraîner avec cette nouvelle fonction de perte géniale, nous devons préparer quelques éléments : -- nous avons besoin de chargeurs de données pour charger les données par lots. -- nous devons définir les paramètres de décroissance du poids. -- de temps en temps, nous voulons évaluer, il est donc logique d'envelopper le code d'évaluation dans une fonction. +- Nous avons besoin de chargeurs de données pour charger les données par batch. +- Nous devons définir les paramètres de décroissance des poids. +- De temps en temps, nous voulons évaluer, il est donc logique d'envelopper le code d'évaluation dans une fonction. -Commençons par les chargeurs de données. Nous avons seulement besoin de définir le format du jeu de données à `"torch"`, et ensuite nous pouvons le passer à un PyTorch `DataLoader` avec la taille de lot appropriée : +Commençons par les chargeurs de données. Nous avons seulement besoin de définir le format du jeu de données à `"torch"` et ensuite nous pouvons le passer à un PyTorch `DataLoader` avec la taille de batch appropriée : ```py from torch.utils.data.dataloader import DataLoader @@ -722,7 +723,7 @@ train_dataloader = DataLoader(tokenized_dataset["train"], batch_size=32, shuffle eval_dataloader = DataLoader(tokenized_dataset["valid"], batch_size=32) ``` -Ensuite, nous regroupons les paramètres de façon à ce que l'optimiseur sache lesquels bénéficieront d'une décroissance de poids supplémentaire. Habituellement, tous les termes de biais et de poids LayerNorm en sont exemptés ; voici comment nous pouvons le faire : +Ensuite, nous regroupons les paramètres de façon à ce que l'optimiseur sache lesquels bénéficieront d'une décroissance de poids supplémentaire. Habituellement, tous les termes de biais et les poids de la *LayerNorm* en sont exemptés. Voici comment nous pouvons le faire : ```py weight_decay = 0.1 @@ -741,7 +742,7 @@ def get_grouped_params(model, no_decay=["bias", "LayerNorm.weight"]): ] ``` -Puisque nous voulons évaluer le modèle régulièrement sur l'ensemble de validation pendant l'entraînement, écrivons une fonction pour cela aussi. Elle passe simplement par le dataloader d'évaluation et rassemble toutes les pertes à travers les processus : +Puisque nous voulons évaluer le modèle régulièrement sur l'ensemble de validation pendant l'entraînement, écrivons une fonction pour cela aussi. Elle passe simplement par le *dataloader* d'évaluation et rassemble toutes les pertes à travers les processus : ```py def evaluate(): @@ -760,13 +761,13 @@ def evaluate(): return loss.item(), perplexity.item() ``` -Avec la fonction `evaluate()` nous pouvons rapporter la perte et la [perplexité](/course/fr/chapter7/3) à intervalles réguliers. Ensuite, nous redéfinissons notre modèle pour nous assurer que nous nous entraînons à nouveau à partir de zéro : +Avec la fonction `evaluate()` nous pouvons rapporter la perte et la [perplexité](/course/fr/chapter7/3) à intervalles réguliers. Ensuite, nous redéfinissons notre modèle pour nous assurer que nous entraînons à nouveau à partir de zéro : ```py model = GPT2LMHeadModel(config) ``` -Nous pouvons ensuite définir notre optimiseur, en utilisant la fonction précédente pour diviser les paramètres de la décroissance du poids : +Nous pouvons ensuite définir notre optimiseur, en utilisant la fonction précédente pour diviser les paramètres de décroissance des poids : ```py from torch.optim import AdamW @@ -788,11 +789,11 @@ model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( -🚨 Si vous vous entraînez sur un TPU, vous devrez déplacer tout le code commençant à la cellule ci-dessus dans une fonction d'entraînement dédiée. Voir le [Chapitre 3](/course/fr/chapter3) pour plus de détails. +🚨 Si vous vous entraînez sur un TPU, vous devrez déplacer tout le code commençant à la cellule ci-dessus dans une fonction d'entraînement dédiée. Voir le [chapitre 3](/course/fr/chapter3) pour plus de détails. -Maintenant que nous avons envoyé notre `train_dataloader` à `accelerator.prepare()`, nous pouvons utiliser sa longueur pour calculer le nombre d'étapes d'entraînement. Rappelez-vous que nous devons toujours faire cela après avoir préparé le dataloader, car cette méthode modifiera sa longueur. Nous utilisons un programme linéaire classique du taux d'apprentissage à 0 : +Maintenant que nous avons envoyé notre `train_dataloader` à `accelerator.prepare()`, nous pouvons utiliser sa longueur pour calculer le nombre d'étapes d'entraînement. Rappelez-vous que nous devons toujours faire cela après avoir préparé le *dataloader* car cette méthode modifiera sa longueur. Nous utilisons un programme linéaire classique du taux d'apprentissage à 0 : ```py num_train_epochs = 1 @@ -807,7 +808,7 @@ lr_scheduler = get_scheduler( ) ``` -Enfin, pour pousser notre modèle vers le Hub, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous au *Hub*, si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'ID du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix ; il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `get_full_repo_name()`) : +Enfin, pour pousser notre modèle vers le *Hub*, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous au *Hub*, si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'identifiant du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix. Il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `get_full_repo_name()`) : ```py from huggingface_hub import Repository, get_full_repo_name @@ -821,7 +822,7 @@ repo_name 'sgugger/codeparrot-ds-accelerate' ``` -Ensuite, nous pouvons cloner ce référentiel dans un dossier local. S'il existe déjà, ce dossier local doit être un clone existant du référentiel avec lequel nous travaillons : +Ensuite, nous pouvons cloner ce dépôt dans un dossier local. S'il existe déjà, ce dossier local doit être un clone existant du dépôt avec lequel nous travaillons : ```py output_dir = "codeparrot-ds-accelerate" @@ -840,7 +841,7 @@ evaluate() (10.934126853942871, 56057.14453125) ``` -Ce sont des valeurs très élevées pour la perte et la perplexité, mais ce n'est pas surprenant puisque nous n'avons pas encore entraîné le modèle. Avec cela, nous avons tout préparé pour écrire la partie principale du script d'entraînement : la boucle d'entraînement. Dans la boucle d'entraînement, nous itérons sur le chargeur de données et transmettons les lots au modèle. Avec les logits, nous pouvons alors évaluer notre fonction de perte personnalisée. Nous mettons à l'échelle la perte par le nombre d'étapes d'accumulation du gradient afin de ne pas créer de plus grandes pertes en agrégeant plus d'étapes. Avant de procéder à l'optimisation, nous découpons également les gradients pour une meilleure convergence. Enfin, tous les quelques pas, nous évaluons le modèle sur l'ensemble d'évaluation avec notre nouvelle fonction `evaluate()` : +Ce sont des valeurs très élevées pour la perte et la perplexité, mais ce n'est pas surprenant puisque nous n'avons pas encore entraîné le modèle. Avec cela, nous avons tout préparé pour écrire la partie principale du script d'entraînement : la boucle d'entraînement. Dans celle-ci, nous itérons sur le chargeur de données et transmettons les batchs au modèle. Avec les logits, nous pouvons alors évaluer notre fonction de perte personnalisée. Nous mettons à l'échelle la perte par le nombre d'étapes d'accumulation du gradient afin de ne pas créer de plus grandes pertes en agrégeant plus d'étapes. Avant de procéder à l'optimisation, nous découpons également les gradients pour une meilleure convergence. Enfin, tous les quelques pas, nous évaluons le modèle sur l'ensemble d'évaluation avec notre nouvelle fonction `evaluate()` : ```py from tqdm.notebook import tqdm @@ -887,17 +888,17 @@ for epoch in range(num_train_epochs): ) ``` -Et voilà, vous disposez maintenant de votre propre boucle d'entraînement personnalisée pour les modèles de langage causal tels que GPT-2, que vous pouvez encore adapter à vos besoins. +Et voilà, vous disposez maintenant de votre propre boucle d'entraînement personnalisée pour les modèles de langage causal tels que le GPT-2. Vous pouvez encore l'adapter à vos besoins. -✏️ **Essayez** Vous pouvez créer votre propre fonction de perte personnalisée, adaptée à votre cas d'utilisation, ou ajouter une autre étape personnalisée dans la boucle d'entraînement. +✏️ **Essayez !** Vous pouvez créer votre propre fonction de perte personnalisée, adaptée à votre cas d'utilisation, ou ajouter une autre étape personnalisée dans la boucle d'entraînement. -✏️ **Essayez** Lorsque vous effectuez de longues expériences d'entraînement, il est bon d'enregistrer les mesures importantes à l'aide d'outils tels que TensorBoard ou Weights & Biases. Ajoutez une journalisation appropriée à la boucle d'entraînement afin de pouvoir toujours vérifier comment se déroule l'entraînement. +✏️ **Essayez !** Lorsque vous effectuez de longues expériences d'entraînement, il est bon d'enregistrer les mesures importantes à l'aide d'outils tels que *TensorBoard* ou *Weights & Biases*. Ajoutez l'un d'eux à la boucle d'entraînement afin de pouvoir toujours vérifier comment se déroule l'entraînement. diff --git a/chapters/fr/chapter7/7.mdx b/chapters/fr/chapter7/7.mdx index e301b1bac..359e84211 100644 --- a/chapters/fr/chapter7/7.mdx +++ b/chapters/fr/chapter7/7.mdx @@ -22,26 +22,26 @@ {/if} -Il est temps de s'intéresser à la réponse aux questions ! Cette tâche peut prendre plusieurs formes, mais celle sur laquelle nous allons nous concentrer dans cette section est appelée réponse aux questions *extractives*. Il s'agit de poser des questions sur un document et d'identifier les réponses sous forme de "morceaux de texte" dans le document lui-même. +Il est temps de s'intéresser à la réponse aux questions ! Cette tâche peut prendre plusieurs formes mais celle sur laquelle nous allons nous concentrer dans cette section est appelée réponse aux questions *extractives*. Il s'agit de poser des questions sur un document et d'identifier les réponses sous forme de « d'étendue de texte » dans le document lui-même. -Nous allons affiner un modèle BERT sur le [jeu de données SQuAD](https://rajpurkar.github.io/SQuAD-explorer/), qui consiste en des questions posées par des *crowdworkers* sur un ensemble d'articles de Wikipedia. Cela nous donnera un modèle capable de calculer des prédictions comme celle-ci : +Nous allons *finetuner* un modèle BERT sur le [jeu de données SQuAD](https://rajpurkar.github.io/SQuAD-explorer/), qui consiste en des questions posées par des *crowdworkers* sur un ensemble d'articles de Wikipedia. Cela nous donnera un modèle capable de calculer des prédictions comme celui-ci : -Il s'agit en fait de la présentation du modèle qui a été entraîné et téléchargé sur le *Hub* à l'aide du code présenté dans cette section. Vous pouvez le trouver et vérifier les prédictions [ici](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) +Il s'agit d'une présentation du modèle qui a été entraîné à l'aide du code présenté dans cette section et qui a ensuité été téléchargé sur le *Hub*. Vous pouvez le trouver [ici](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) -💡 Les modèles à codeur unique comme BERT ont tendance à être excellents pour extraire les réponses à des questions factuelles comme "Qui a inventé l'architecture Transformer ?", mais ne sont pas très performants lorsqu'on leur pose des questions ouvertes comme "Pourquoi le ciel est-il bleu ?". Dans ces cas plus difficiles, les modèles encodeurs-décodeurs comme T5 et BART sont généralement utilisés pour synthétiser les informations d'une manière assez similaire au [résumé de texte](/cours/fr/chapter7/5). Si vous êtes intéressé par ce type de réponse aux questions *génératives*, nous vous recommandons de consulter notre [démo](https://yjernite.github.io/lfqa.html) basée sur le [jeu de données ELI5](https://huggingface.co/datasets/eli5). +💡 Les modèles basé que sur l'encodeur comme BERT ont tendance à être excellents pour extraire les réponses à des questions factuelles comme « Qui a inventé l'architecture Transformer ? » mais ne sont pas très performants lorsqu'on leur pose des questions ouvertes comme « Pourquoi le ciel est-il bleu ? ». Dans ces cas plus difficiles, les modèles encodeurs-décodeurs comme le T5 et BART sont généralement utilisés pour synthétiser les informations d'une manière assez similaire au [résumé de texte](/course/fr/chapter7/5). Si vous êtes intéressé par ce type de réponse aux questions *génératives*, nous vous recommandons de consulter notre [démo](https://yjernite.github.io/lfqa.html) basée sur le [jeu de données ELI5](https://huggingface.co/datasets/eli5). ## Préparation des données -Le jeu de données le plus utilisé comme référence académique pour la réponse extractive aux questions est [SQuAD](https://rajpurkar.github.io/SQuAD-explorer/), c'est donc celui que nous utiliserons ici. Il existe également une référence plus difficile [SQuAD v2](https://huggingface.co/datasets/squad_v2), qui comprend des questions sans réponse. Tant que votre propre jeu de données contient une colonne pour les contextes, une colonne pour les questions et une colonne pour les réponses, vous devriez être en mesure d'adapter les étapes ci-dessous. +Le jeu de données le plus utilisé comme référence académique pour la réponse extractive aux questions est [SQuAD](https://rajpurkar.github.io/SQuAD-explorer/). C'est donc celui que nous utiliserons ici. Il existe également une version plus difficile [SQuAD v2](https://huggingface.co/datasets/squad_v2), qui comprend des questions sans réponse. Tant que votre propre jeu de données contient une colonne pour les contextes, une colonne pour les questions et une colonne pour les réponses, vous devriez être en mesure d'adapter les étapes ci-dessous. ### Le jeu de données SQuAD @@ -53,7 +53,7 @@ from datasets import load_dataset raw_datasets = load_dataset("squad") ``` -Nous pouvons alors jeter un coup d'œil à cet objet pour en savoir plus sur le jeu de données SQuAD : +Nous pouvons jeter un coup d'œil à cet objet pour en savoir plus sur le jeu de données SQuAD : ```py raw_datasets @@ -72,7 +72,7 @@ DatasetDict({ }) ``` -On dirait que nous avons tout ce dont nous avons besoin avec les champs `context`, `question` et `answers`, alors imprimons-les pour le premier élément de notre ensemble d'entraînement : +On dirait que nous avons tout ce dont nous avons besoin avec les champs `context`, `question` et `answers`. Affichons-les pour le premier élément de notre ensemble d'entraînement : ```py print("Context: ", raw_datasets["train"][0]["context"]) @@ -83,11 +83,12 @@ 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.' # Sur le plan architectural, l'école a un caractère catholique. Au sommet du dôme doré du bâtiment principal se trouve une statue dorée de la Vierge Marie. Immédiatement devant le bâtiment principal et face à lui, se trouve une statue en cuivre du Christ, les bras levés, avec la légende "Venite Ad Me Omnes". À côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s'agit d'une réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette Soubirous en 1858. Au bout de l'allée principale (et dans une ligne directe qui passe par 3 statues et le Dôme d'or), se trouve une statue de pierre simple et moderne de Marie'. -Question: 'To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France?' # A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes, en France ? +Question: 'To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France?' +# A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes, en France ? Answer: {'text': ['Saint Bernadette Soubirous'], 'answer_start': [515]} ``` -Les champs `context` et `question` sont très simples à utiliser. Le champ `answers` est un peu plus délicat car il compile un dictionnaire avec deux champs qui sont tous deux des listes. C'est le format qui sera attendu par la métrique `squad` lors de l'évaluation ; si vous utilisez vos propres données, vous n'avez pas nécessairement besoin de vous soucier de mettre les réponses dans le même format. Le champ `text` est assez évident, et le champ `answer_start` contient l'indice du caractère de départ de chaque réponse dans le contexte. +Les champs `context` et `question` sont très simples à utiliser. Le champ `answers` est un peu plus délicat car il compile un dictionnaire avec deux champs qui sont tous deux des listes. C'est le format qui sera attendu par la métrique `squad` lors de l'évaluation. Si vous utilisez vos propres données, vous n'avez pas nécessairement besoin de vous soucier de mettre les réponses dans le même format. Le champ `text` est assez évident et le champ `answer_start` contient l'indice du caractère de départ de chaque réponse dans le contexte. Pendant l'entraînement, il n'y a qu'une seule réponse possible. Nous pouvons vérifier cela en utilisant la méthode `Dataset.filter()` : @@ -114,7 +115,7 @@ print(raw_datasets["validation"][2]["answers"]) {'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]} ``` -Nous ne nous plongerons pas dans le script d'évaluation car tout sera enveloppé par une métrique 🤗 *Datasets* pour nous, mais la version courte est que certaines des questions ont plusieurs réponses possibles, et ce script va comparer une réponse prédite à toutes les réponses acceptables et prendre le meilleur score. Si nous regardons l'échantillon de l'indice 2, par exemple : +Nous ne nous plongerons pas dans le script d'évaluation car tout sera enveloppé pour nous par une métrique de 🤗 *Datasets*. La version courte est que certaines des questions ont plusieurs réponses possibles, et ce script va comparer une réponse prédite à toutes les réponses acceptables et prendre le meilleur score. Par exemple, si nous regardons l'échantillon de l'indice 2 : ```py print(raw_datasets["validation"][2]["context"]) @@ -124,7 +125,8 @@ 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.' # Le Super Bowl 50 était un match de football américain visant à déterminer le champion de la National Football League (NFL) pour la saison 2015. Les Denver Broncos, champions de la Conférence de football américain (AFC), ont battu les Carolina Panthers, champions de la Conférence nationale de football (NFC), 24 à 10, pour remporter leur troisième titre de Super Bowl. Le match s'est déroulé le 7 février 2016 au Levi\'s Stadium, dans la baie de San Francisco, à Santa Clara, en Californie. Comme il s'agissait du 50e Super Bowl, la ligue a mis l'accent sur l'" anniversaire doré " avec diverses initiatives sur le thème de l'or, ainsi qu'en suspendant temporairement la tradition de nommer chaque match du Super Bowl avec des chiffres romains (en vertu de laquelle le match aurait été appelé " Super Bowl L "), afin que le logo puisse mettre en évidence les chiffres arabes 50.'' -'Where did Super Bowl 50 take place?' # Où a eu lieu le Super Bowl 50 ? +'Where did Super Bowl 50 take place?' +# Où a eu lieu le Super Bowl 50 ? ``` nous pouvons voir que la réponse peut effectivement être l'une des trois possibilités que nous avons vues précédemment. @@ -133,9 +135,9 @@ nous pouvons voir que la réponse peut effectivement être l'une des trois possi -Commençons par le prétraitement des données d'entraînement. La partie la plus difficile sera de générer des étiquettes pour la réponse à la question, qui seront les positions de début et de fin des *tokens* correspondant à la réponse dans le contexte. +Commençons par le prétraitement des données d'entraînement. La partie la plus difficile est de générer des étiquettes pour la réponse à la question, c'est-à-dire les positions de début et de fin des *tokens* correspondant à la réponse dans le contexte. -Mais ne nous emballons pas. Tout d'abord, nous devons convertir le texte de l'entrée en identifiants que le modèle peut comprendre, en utilisant un *tokenizer* : +Mais ne nous emballons pas. Tout d'abord, à l'aide d'un *tokenizer*, nous devons convertir le texte d'entrée en identifiants que le modèle peut comprendre : ```py from transformers import AutoTokenizer @@ -144,7 +146,7 @@ model_checkpoint = "bert-base-cased" tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) ``` -Comme mentionné précédemment, nous allons *finetuner* un modèle BERT, mais vous pouvez utiliser n'importe quel autre type de modèle tant qu'il a un *tokenizer* rapide implémenté. Vous pouvez voir toutes les architectures qui sont livrées avec une version rapide dans [ce grand tableau](https://huggingface.co/transformers/#supported-frameworks), et pour vérifier que l'objet `tokenizer` que vous utilisez est bien soutenu par des 🤗 *Tokenizers* vous pouvez regarder son attribut `is_fast` : +Comme mentionné précédemment, nous allons *finetuner* un modèle BERT, mais vous pouvez utiliser n'importe quel autre type de modèle tant qu'il a un *tokenizer* rapide implémenté. Vous pouvez voir toutes les architectures qui sont livrées avec un *tokenizer* rapide dans [ce tableau](https://huggingface.co/transformers/#supported-frameworks), et pour vérifier que l'objet `tokenizer` que vous utilisez est bien soutenu par 🤗 *Tokenizers* vous pouvez regarder son attribut `is_fast` : ```py tokenizer.is_fast @@ -154,7 +156,7 @@ tokenizer.is_fast True ``` -Nous pouvons transmettre à notre *tokenizer* la question et le contexte ensemble, et il insérera correctement les *tokens* spéciaux pour former une phrase comme celle-ci : +Nous pouvons transmettre à notre *tokenizer* la question et le contexte ensemble. Il insérera correctement les *tokens* spéciaux pour former une phrase comme celle-ci : ``` [CLS] question [SEP] context [SEP] @@ -181,30 +183,30 @@ tokenizer.decode(inputs["input_ids"]) 'and the Gold Dome ), is a simple, modern stone statue of Mary. [SEP]' '[CLS] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] Architecturalement, ' -l'école a un caractère catholique. Au sommet du dôme doré du bâtiment principal se trouve une statue dorée de la Vierge ' -Marie. Immédiatement devant le bâtiment principal et face à lui, se trouve une statue en cuivre du Christ, les bras ''levés''. -'levés avec la légende " Venite Ad Me Omnes ". A côté du bâtiment principal se trouve la basilique du Sacré-Cœur. +'l école a un caractère catholique. Au sommet du dôme doré du bâtiment principal se trouve une statue dorée de la Vierge ' +'Marie. Immédiatement devant le bâtiment principal et face à lui, se trouve une statue en cuivre du Christ, les bras ' +'levés avec la légende " Venite Ad Me Omnes ". A côté du bâtiment principal se trouve la basilique du Sacré ' 'Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s'agit d'une ' 'réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette ' -Soubirous en 1858. Au bout de l'allée principale ( et en ligne directe qui passe par 3 statues ' -'et le Dôme d'or), se trouve une statue de Marie en pierre, simple et moderne. [SEP]'' +'Soubirous en 1858. Au bout de l'allée principale ( et en ligne directe qui passe par 3 statues ' +'et le Dôme d'or), se trouve une statue de Marie en pierre, simple et moderne. [SEP]' ``` -Les étiquettes seront alors l'index des *tokens* de début et de fin de la réponse, et le modèle sera chargé de prédire un logit de début et de fin par *token* dans l'entrée, les étiquettes théoriques étant les suivantes : +Les étiquettes sont l'index des *tokens* de début et de fin de la réponse. Le modèle sera chargé de prédire dans l'entrée un logit de début et de fin par *token*, les étiquettes théoriques étant les suivantes :
One-hot encoded labels for question answering.
-Dans ce cas, le contexte n'est pas trop long, mais certains des exemples de l'ensemble de données ont des contextes très longs qui dépasseront la longueur maximale que nous avons fixée (qui est de 384 dans ce cas). Comme nous l'avons vu dans le [Chapitre 6](/course/fr/chapter6/4) lorsque nous avons exploré les internes du pipeline `question-answering`, nous allons traiter les contextes longs en créant plusieurs caractéristiques d'entraînement à partir d'un échantillon de notre jeu de données, avec une fenêtre glissante entre eux. +Dans ce cas, le contexte n'est pas trop long, mais certains des exemples du jeu de données ont des contextes très longs qui dépasseront la longueur maximale que nous avons fixée (qui est de 384 dans ce cas). Comme nous l'avons vu dans le [chapitre 6](/course/fr/chapter6/4) lorsque nous avons exploré le pipeline de `question-answering`, nous allons traiter les contextes longs en créant plusieurs caractéristiques d'entraînement à partir d'un échantillon de notre jeu de données et avec une fenêtre glissante entre eux. -Pour voir comment cela fonctionne en utilisant l'exemple actuel, nous pouvons limiter la longueur à 100 et utiliser une fenêtre glissante de 50 *tokens*. Pour rappel, nous utilisons +Pour voir comment cela fonctionne sur notre exemple, nous pouvons limiter la longueur à 100 et utiliser une fenêtre glissante de 50 *tokens*. Pour rappel, nous utilisons : - `max_length` pour définir la longueur maximale (ici 100) - `truncation="only_second"` pour tronquer le contexte (qui est en deuxième position) quand la question avec son contexte est trop longue - `stride` pour fixer le nombre de *tokens* se chevauchant entre deux morceaux successifs (ici 50) -- `return_overflowing_tokens=True` pour indiquer au tokenizer que l'on veut les *tokens* qui débordent +- `return_overflowing_tokens=True` pour indiquer au *tokenizer* que l'on veut les *tokens* qui débordent ```py inputs = tokenizer( @@ -222,21 +224,21 @@ for ids in 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 basi [SEP]' -'[CLS] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] Sur le plan architectural, l'école a un caractère catholique. Au sommet du dôme doré du bâtiment principal se trouve une statue dorée de la Vierge Marie. Immédiatement devant le bâtiment principal et face à lui, se trouve une statue en cuivre du Christ, les bras levés, avec la légende " Venite Ad Me Omnes ". À côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basi [SEP]''. +'[CLS] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] Sur le plan architectural, l école a un caractère catholique. Au sommet du dôme doré du bâtiment principal se trouve une statue dorée de la Vierge Marie. Immédiatement devant le bâtiment principal et face à lui, se trouve une statue en cuivre du Christ, les bras levés, avec la légende " Venite Ad Me Omnes ". À côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la 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] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [le bâtiment principal et face à lui, une statue en cuivre du Christ aux bras levés avec la légende " Venite Ad Me Omnes ". À côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s'agit d'une réplique de la grotte de Lourdes, en France, où la Vierge [SEP]''. +'[CLS] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] le bâtiment principal et face à lui, une statue en cuivre du Christ aux bras levés avec la légende " Venite Ad Me Omnes ". À côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s agit d'une réplique de la grotte de Lourdes, en France, où la Vierge [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] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [A côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s'agit d'une réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette Soubirous en 1858. Au bout de l'allée principale ( et dans une ligne directe qui relie par 3 [SEP]''. +'[CLS] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] A côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s agit d une réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette Soubirous en 1858. Au bout de l allée principale ( et dans une ligne directe qui relie par 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]' -'[CLS] A qui la Vierge Marie est-elle prétendument apparue en 1858 à Lourdes France ? [SEP]. Il s'agit d'une réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette Soubirous en 1858. Au bout de l'allée principale (et dans une ligne directe qui passe par 3 statues et le Dôme d'or), se trouve une simple statue de pierre moderne de Marie. [SEP]' +'[CLS] A qui la Vierge Marie est-elle prétendument apparue en 1858 à Lourdes France ? [SEP]. Il s agit d une réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette Soubirous en 1858. Au bout de l allée principale (et dans une ligne directe qui passe par 3 statues et le Dôme d or), se trouve une simple statue de pierre moderne de Marie. [SEP]' ``` -Comme nous pouvons le voir, notre exemple a été divisé en quatre entrées, chacune d'entre elles contenant la question et une partie du contexte. Notez que la réponse à la question ("Bernadette Soubirous") n'apparaît que dans la troisième et dernière entrée, donc en traitant les longs contextes de cette façon, nous allons créer quelques exemples d'entraînement où la réponse n'est pas incluse dans le contexte. Pour ces exemples, les étiquettes seront `start_position = end_position = 0` (donc nous prédisons le *token* `[CLS]`). Nous définirons également ces étiquettes dans le cas malheureux où la réponse a été tronquée de sorte que nous n'avons que le début (ou la fin) de celle-ci. Pour les exemples où la réponse est entièrement dans le contexte, les étiquettes seront l'index du *token* où la réponse commence et l'index du *token* où la réponse se termine. +Comme nous pouvons le voir, notre exemple a été divisé en quatre entrées, chacune d'entre elles contenant la question et une partie du contexte. Notez que la réponse à la question (« Bernadette Soubirous ») n'apparaît que dans la troisième et la dernière entrée. Donc en traitant les longs contextes de cette façon, nous allons créer quelques exemples d'entraînement où la réponse n'est pas incluse dans le contexte. Pour ces exemples, les étiquettes seront `start_position = end_position = 0` (donc nous prédisons le *token* `[CLS]`). Nous définirons également ces étiquettes dans le cas malheureux où la réponse a été tronquée de sorte que nous n'avons que le début (ou la fin) de celle-ci. Pour les exemples où la réponse est entièrement dans le contexte, les étiquettes seront l'index du *token* où la réponse commence et l'index du *token* où la réponse se termine. -L'ensemble de données nous fournit le caractère de début de la réponse dans le contexte, et en ajoutant la longueur de la réponse, nous pouvons trouver le caractère de fin dans le contexte. Pour faire correspondre ces indices aux *tokens*, nous devrons utiliser les mappages d'offset que nous avons étudiés au [Chapitre 6](/course/chapter6/4). Nous pouvons faire en sorte que notre *tokenizer* renvoie ces index en passant `return_offsets_mapping=True` : +Le jeu de données nous fournit le caractère de début de la réponse dans le contexte, et en ajoutant la longueur de la réponse, nous pouvons trouver le caractère de fin dans le contexte. Pour faire correspondre ces indices aux *tokens*, nous devrons utiliser les correspondances *offset* que nous avons étudiés au [chapitre 6](/course/fr/chapter6/4). Nous pouvons faire en sorte que notre *tokenizer* renvoie ces index en passant `return_offsets_mapping=True` : ```py inputs = tokenizer( @@ -255,7 +257,7 @@ inputs.keys() dict_keys(['input_ids', 'token_type_ids', 'attention_mask', 'offset_mapping', 'overflow_to_sample_mapping']) ``` -Comme nous pouvons le voir, nous récupérons les habituels ID d'entrée, ID de type de jeton, et masque d'attention, ainsi que le mappage d'offset dont nous avions besoin et une clé supplémentaire, `overflow_to_sample_mapping`. La valeur correspondante nous sera utile lorsque nous tokeniserons plusieurs textes en même temps (ce que nous devrions faire pour bénéficier du fait que notre *tokenizer* est soutenu par Rust). Puisqu'un échantillon peut donner plusieurs caractéristiques, il fait correspondre chaque caractéristique à l'exemple d'où elle provient. Parce qu'ici nous avons seulement tokenisé un exemple, nous obtenons une liste de `0`s : +Comme nous pouvons le voir, nous récupérons les identifiants d'entrée, les *tokens* de type identifiant, le masque d'attention, ainsi que la correspondance *offset* dont nous avions besoin et une clé supplémentaire, `overflow_to_sample_mapping`. La valeur correspondante nous sera utile lorsque nous tokeniserons plusieurs textes en même temps (ce que nous devrions faire pour bénéficier du fait que notre *tokenizer* est en Rust). Puisqu'un échantillon peut donner plusieurs caractéristiques, il fait correspondre chaque caractéristique à l'exemple d'où elle provient. Parce qu'ici nous avons seulement tokenisé un exemple, nous obtenons une liste de `0` : ```py inputs["overflow_to_sample_mapping"] @@ -265,7 +267,7 @@ inputs["overflow_to_sample_mapping"] [0, 0, 0, 0] ``` -Mais si nous tokenisons plus d'exemples, cela deviendra plus utile : +Mais si nous tokenisons davantage d'exemples, cela deviendra plus utile : ```py inputs = tokenizer( @@ -292,11 +294,11 @@ Comme nous pouvons le voir, les trois premiers exemples (aux indices 2, 3 et 4 d Ces informations seront utiles pour associer chaque caractéristique obtenue à son étiquette correspondante. Comme mentionné précédemment, ces étiquettes sont : - `(0, 0)` si la réponse n'est pas dans l'espace correspondant du contexte. -- `(start_position, end_position)` si la réponse est dans l'espace correspondant du contexte, avec `start_position` étant l'index du *token* (dans les IDs d'entrée) au début de la réponse et `end_position` étant l'index du *token* (dans les IDs d'entrée) où la réponse se termine. +- `(start_position, end_position)` si la réponse est dans l'espace correspondant du contexte, avec `start_position` étant l'index du *token* (dans les identifiants d'entrée) au début de la réponse et `end_position` étant l'index du *token* (dans les identifiants d'entrée) où la réponse se termine. -Pour déterminer ce qui est le cas et, le cas échéant, les positions des *tokens*, nous trouvons d'abord les indices qui commencent et finissent le contexte dans les IDs d'entrée. Nous pourrions utiliser les IDs du type de *token* pour le faire, mais puisque ceux-ci n'existent pas nécessairement pour tous les modèles (DistilBERT ne les requiert pas, par exemple), nous allons plutôt utiliser la méthode `sequence_ids()` du `BatchEncoding` que notre tokenizer retourne. +Pour déterminer ce qui est le cas et, le cas échéant, les positions des *tokens*, nous trouvons d'abord les indices qui commencent et finissent le contexte dans les identifiants d'entrée. Nous pourrions utiliser les *tokens* de type identifiants pour le faire, mais puisque ceux-ci n'existent pas nécessairement pour tous les modèles (DistilBERT ne les requiert pas par exemple), nous allons plutôt utiliser la méthode `sequence_ids()` du `BatchEncoding` que notre *tokenizer* retourne. -Une fois que nous avons ces indices de *tokens*, nous regardons les offsets correspondants, qui sont des tuples de deux entiers représentant l'étendue des caractères dans le contexte original. Nous pouvons ainsi détecter si le *chunk* du contexte dans cette fonctionnalité commence après la réponse ou se termine avant que la réponse ne commence (dans ce cas, l'étiquette est `(0, 0)`). Si ce n'est pas le cas, nous bouclons pour trouver le premier et le dernier *token* de la réponse : +Une fois que nous avons ces indices de *tokens*, nous regardons les *offsets* correspondants, qui sont des *tuples* de deux entiers représentant l'étendue des caractères dans le contexte original. Nous pouvons ainsi détecter si le morceau de contexte dans cette fonctionnalité commence après la réponse ou se termine avant que la réponse ne commence (dans ce cas, l'étiquette est `(0, 0)`). Si ce n'est pas le cas, nous bouclons pour trouver le premier et le dernier *token* de la réponse : ```py answers = raw_datasets["train"][2:6]["answers"] @@ -319,12 +321,12 @@ for i, offset in enumerate(inputs["offset_mapping"]): idx += 1 context_end = idx - 1 - # Si la réponse n'est pas entièrement dans le contexte, l'étiquette est (0, 0). + # Si la réponse n'est pas entièrement dans le contexte, l'étiquette est (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: - # Otherwise it's the start and end token positions + # Sinon, ce sont les positions de début et de fin du token idx = context_start while idx <= context_end and offset[idx][0] <= start_char: idx += 1 @@ -343,7 +345,7 @@ start_positions, end_positions [85, 53, 21, 0, 0, 70, 33, 0, 40, 0, 0, 0, 68, 35, 0, 0, 0, 0, 0]) ``` -Jetons un coup d'œil à quelques résultats pour vérifier que notre approche est correcte. Pour la première caractéristique, nous trouvons `(83, 85)` comme étiquettes, alors comparons la réponse théorique avec l'étendue décodée des *tokens* de 83 à 85 (inclus) : +Jetons un coup d'œil à quelques résultats pour vérifier que notre approche est correcte. Pour la première caractéristique, nous trouvons `(83, 85)` comme étiquettes. Comparons alors la réponse théorique avec l'étendue décodée des *tokens* de 83 à 85 (inclus) : ```py idx = 0 @@ -361,7 +363,7 @@ print(f"Theoretical answer: {answer}, labels give: {labeled_answer}") 'Theoretical answer: the Main Building, labels give: the Main Building' ``` -Donc, c'est une correspondance ! Maintenant, vérifions l'index 4, où nous avons mis les étiquettes à `(0, 0)`, ce qui signifie que la réponse n'est pas dans le *chunk* de contexte de cette caractéristique : +Cela correspond ! Maintenant vérifions l'index 4, où nous avons mis les étiquettes à `(0, 0)`, signifiant que la réponse n'est pas dans le morceau de contexte de cette caractéristique : ```py idx = 4 @@ -384,7 +386,7 @@ En effet, nous ne voyons pas la réponse dans le contexte. -Maintenant que nous avons vu étape par étape comment prétraiter nos données d'entraînement, nous pouvons les regrouper dans une fonction que nous appliquerons à l'ensemble des données d'entraînement. Nous allons rembourrer chaque caractéristique à la longueur maximale que nous avons définie, car la plupart des contextes seront longs (et les échantillons correspondants seront divisés en plusieurs caractéristiques), il n'y a donc pas de réel avantage à appliquer un rembourrage dynamique ici : +Maintenant que nous avons vu étape par étape comment prétraiter nos données d'entraînement, nous pouvons les regrouper dans une fonction que nous appliquerons à l'ensemble des données d'entraînement. Nous allons rembourrer chaque caractéristique à la longueur maximale que nous avons définie, car la plupart des contextes seront longs (et les échantillons correspondants seront divisés en plusieurs caractéristiques). Il n'y a donc pas de réel avantage à appliquer un rembourrage dynamique ici : ```py max_length = 384 @@ -426,12 +428,12 @@ def preprocess_training_examples(examples): idx += 1 context_end = idx - 1 - # Si la réponse n'est pas entièrement dans le contexte, l'étiquette est (0, 0). + # Si la réponse n'est pas entièrement dans le contexte, l'étiquette est (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: - # Otherwise it's the start and end token positions + # Sinon, ce sont les positions de début et de fin du token idx = context_start while idx <= context_end and offset[idx][0] <= start_char: idx += 1 @@ -447,9 +449,9 @@ def preprocess_training_examples(examples): return inputs ``` -Notez que nous avons défini deux constantes pour déterminer la longueur maximale utilisée ainsi que la longueur de la fenêtre glissante, et que nous avons ajouté un petit nettoyage avant la tokénisation : certaines des questions dans le jeu de données SQuAD ont des espaces supplémentaires au début et à la fin qui n'ajoutent rien (et prennent de la place lors de la tokénisation si vous utilisez un modèle comme RoBERTa), donc nous avons supprimé ces espaces supplémentaires. +Notez que nous avons défini deux constantes pour déterminer la longueur maximale utilisée ainsi que la longueur de la fenêtre glissante, et que nous avons ajouté un petit nettoyage avant la tokénisation : certaines des questions dans SQuAD ont des espaces supplémentaires au début et à la fin qui n'ajoutent rien (et prennent de la place lors de la tokénisation si vous utilisez un modèle comme RoBERTa), donc nous avons supprimé ces espaces supplémentaires. -Pour appliquer cette fonction à l'ensemble de l'entraînement, nous utilisons la méthode `Dataset.map()` avec le flag `batched=True`. C'est nécessaire ici car nous changeons la longueur de l'ensemble de données (puisqu'un exemple peut donner plusieurs caractéristiques d'entraînement) : +Pour appliquer cette fonction à l'ensemble de l'entraînement, nous utilisons la méthode `Dataset.map()` avec le flag `batched=True`. C'est nécessaire ici car nous changeons la longueur du jeu de données (puisqu'un exemple peut donner plusieurs caractéristiques d'entraînement) : ```py train_dataset = raw_datasets["train"].map( @@ -464,13 +466,13 @@ len(raw_datasets["train"]), len(train_dataset) (87599, 88729) ``` -Comme nous pouvons le voir, le prétraitement a ajouté environ 1 000 caractéristiques. Notre ensemble d'entraînement est maintenant prêt à être utilisé - passons au prétraitement de l'ensemble de validation ! +Comme nous pouvons le voir, le prétraitement a ajouté environ 1 000 caractéristiques. Notre ensemble d'entraînement est maintenant prêt à être utilisé. Passons au prétraitement de l'ensemble de validation ! ### Traitement des données de validation -Le prétraitement des données de validation sera légèrement plus facile car nous n'avons pas besoin de générer des étiquettes (sauf si nous voulons calculer une perte de validation, mais ce nombre ne nous aidera pas vraiment à comprendre la qualité du modèle). La vraie joie sera d'interpréter les prédictions du modèle dans des étendues du contexte original. Pour cela, il nous suffit de stocker les mappages de décalage et un moyen de faire correspondre chaque caractéristique créée à l'exemple original dont elle provient. Puisqu'il y a une colonne ID dans l'ensemble de données original, nous utiliserons cet ID. +Le prétraitement des données de validation sera légèrement plus facile car nous n'avons pas besoin de générer des étiquettes (sauf si nous voulons calculer une perte de validation, mais elle ne nous aidera pas vraiment à comprendre la qualité du modèle). Le réel plaisir sera d'interpréter les prédictions du modèle dans des étendues du contexte original. Pour cela, il nous suffit de stocker les correspondances d'*offset* et un moyen de faire correspondre chaque caractéristique créée à l'exemple original dont elle provient. Puisqu'il y a une colonne identifiant dans le jeu de données original, nous l'utiliserons. -La seule chose que nous allons ajouter ici est un petit nettoyage des mappages de décalage. Ils contiendront les offsets pour la question et le contexte, mais une fois que nous serons dans la phase de post-traitement, nous n'aurons aucun moyen de savoir quelle partie des IDs d'entrée correspondait au contexte et quelle partie était la question (la méthode `sequence_ids()` que nous avons utilisée n'est disponible que pour la sortie du *tokenizer*). Donc, nous allons mettre les offsets correspondant à la question à `None` : +La seule chose que nous allons ajouter ici est un petit nettoyage des correspondances d'*offset*. Elles contiendront les *offsets* pour la question et le contexte, mais une fois que nous serons à la phase de post-traitement, nous n'aurons aucun moyen de savoir quelle partie des identifiants d'entrée correspondait au contexte et quelle partie était la question (la méthode `sequence_ids()` que nous avons utilisée n'est disponible que pour la sortie du *tokenizer*). Donc, nous allons mettre les *offsets* correspondant à la question à `None` : ```py def preprocess_validation_examples(examples): @@ -503,7 +505,7 @@ def preprocess_validation_examples(examples): return inputs ``` -Nous pouvons appliquer cette fonction sur l'ensemble des données de validation comme précédemment : +Nous pouvons appliquer cette fonction sur l'ensemble de validation comme précédemment : ```py validation_dataset = raw_datasets["validation"].map( @@ -518,21 +520,21 @@ len(raw_datasets["validation"]), len(validation_dataset) (10570, 10822) ``` -Dans ce cas, nous n'avons ajouté que quelques centaines d'échantillons, il semble donc que les contextes dans l'ensemble de données de validation soient un peu plus courts. +Dans ce cas, nous n'avons ajouté que quelques centaines d'échantillons, il semble donc que les contextes dans l'ensemble de validation soient un peu plus courts. Maintenant que nous avons prétraité toutes les données, nous pouvons passer à l'entraînement. {#if fw === 'pt'} -## *Finetuner* le modèle avec l'API `Trainer` +## Finetuner le modèle avec l'API `Trainer` -Le code d'entraînement pour cet exemple ressemblera beaucoup au code des sections précédentes -- la chose la plus difficile sera d'écrire la fonction `compute_metrics()`. Puisque nous avons capitonné tous les échantillons à la longueur maximale que nous avons fixée, il n'y a pas de collateur de données à définir, donc ce calcul de métrique est vraiment la seule chose dont nous devons nous soucier. La partie la plus difficile sera de post-traiter les prédictions du modèle en travées de texte dans les exemples originaux ; une fois que nous aurons fait cela, la métrique de la bibliothèque 🤗 *Datasets* fera le gros du travail pour nous. +Le code d'entraînement pour cet exemple ressemblera beaucoup au code des sections précédentes mais le calcul de la métrique avec la fonction `compute_metrics()` sera un défi unique. Puisque nous avons rembourré tous les échantillons à la longueur maximale que nous avons définie, il n'y a pas de collateur de données à définir. Ainsi le calcul de la métrique est vraiment la seule chose dont nous devons nous soucier. La partie la plus difficile sera de post-traiter les prédictions du modèle en étendues de texte dans les exemples originaux. Une fois que nous aurons fait cela, la métrique de la bibliothèque 🤗 *Datasets* fera le gros du travail pour nous. {:else} -## *Finetuner* fin du modèle avec Keras +## Finetuner fin du modèle avec Keras -Le code d'entraînement de cet exemple ressemblera beaucoup au code des sections précédentes, mais le calcul des métriques sera un défi unique. Puisque nous avons capitonné tous les échantillons à la longueur maximale que nous avons définie, il n'y a pas de collateur de données à définir, donc le calcul de la métrique est vraiment la seule chose dont nous devons nous soucier. La partie la plus difficile sera de post-traiter les prédictions du modèle en travées de texte dans les exemples originaux ; une fois que nous aurons fait cela, la métrique de la bibliothèque 🤗 *Datasets* fera le gros du travail pour nous. +Le code d'entraînement de cet exemple ressemblera beaucoup au code des sections précédentes, mais le calcul de la métrique sera un défi unique. Puisque nous avons rembourré tous les échantillons à la longueur maximale que nous avons définie, il n'y a pas de collateur de données à définir. Ainsi le calcul de la métrique est vraiment la seule chose dont nous devons nous soucier. La partie la plus difficile sera de post-traiter les prédictions du modèle en étendues de texte dans les exemples originaux. Une fois que nous aurons fait cela, la métrique de la bibliothèque 🤗 *Datasets* fera le gros du travail pour nous. {/if} @@ -548,16 +550,16 @@ Le code d'entraînement de cet exemple ressemblera beaucoup au code des sections {/if} -Le modèle produira des logits pour les positions de début et de fin de la réponse dans les IDs d'entrée, comme nous l'avons vu lors de notre exploration du [`question-answering` pipeline](/course/chapter6/4). L'étape de post-traitement sera similaire à ce que nous avons fait là-bas, donc voici un rappel rapide des actions que nous avons prises : +Le modèle produira des logits pour les positions de début et de fin de la réponse dans les identifiants d'entrée, comme nous l'avons vu lors de notre exploration du pipeline de `question-answering` [au chapitre 6](/course/fr/chapter6/3b). L'étape de post-traitement sera similaire à ce que nous avons fait à ce chapitre là. Voici un rapide rappel des actions que nous avons prises : - nous avons masqué les logits de début et de fin correspondant aux *tokens* en dehors du contexte, -- nous avons ensuite converti les logits de début et de fin en probabilités en utilisant un softmax, +- nous avons ensuite converti les logits de début et de fin en probabilités en utilisant une fonction SoftMax, - nous avons attribué un score à chaque paire `(start_token, end_token)` en prenant le produit des deux probabilités correspondantes, - nous avons cherché la paire avec le score maximum qui donnait une réponse valide (par exemple, un `start_token` inférieur au `end_token`). -Ici, nous allons modifier légèrement ce processus car nous n'avons pas besoin de calculer les scores réels (juste la réponse prédite). Cela signifie que nous pouvons sauter l'étape du softmax. Pour aller plus vite, nous ne noterons pas non plus toutes les paires `(start_token, end_token)` possibles, mais seulement celles correspondant aux logits `n_best` les plus élevés (avec `n_best=20`). Puisque nous sauterons le softmax, ces scores seront des scores logit, et seront obtenus en prenant la somme des logits de début et de fin (au lieu du produit, à cause de la règle \\(\log(ab) = \log(a) + \log(b)\)). +Ici, nous allons modifier légèrement ce processus car nous n'avons pas besoin de calculer les scores réels (juste la réponse prédite). Cela signifie que nous pouvons sauter l'étape de la SoftMax. Pour aller plus vite, nous ne donnerons pas non plus un score à toutes les paires `(start_token, end_token)` possibles, mais seulement celles correspondant aux `n_best` logits les plus élevés (avec `n_best=20`). Puisque nous sautons la SoftMax, les scores seront des scores logi, et seront obtenus en prenant la somme des logits de début et de fin (au lieu du produit, à cause de la règle \\(\log(ab) = \log(a) + \log(b)\\)). -Pour démontrer tout cela, nous aurons besoin d'un certain type de prédictions. Puisque nous n'avons pas encore entraîné notre modèle, nous allons utiliser le modèle par défaut du pipeline d'assurance qualité pour générer quelques prédictions sur une petite partie de l'ensemble de validation. Nous pouvons utiliser la même fonction de traitement que précédemment ; parce qu'elle repose sur la constante globale `tokenizer`, nous devons juste changer cet objet pour le tokenizer du modèle que nous voulons utiliser temporairement : +Pour démontrer tout cela, nous aurons besoin d'un certain type de prédictions. Puisque nous n'avons pas encore entraîné notre modèle, nous allons utiliser le modèle par défaut du pipeline de `question-answering` pour générer quelques prédictions sur une petite partie de l'ensemble de validation. Nous pouvons utiliser la même fonction de traitement que précédemment car elle repose sur la constante globale `tokenizer`, nous devons juste changer cet objet par le *tokenizer* du modèle que nous voulons utiliser temporairement : ```python small_eval_set = raw_datasets["validation"].select(range(100)) @@ -577,7 +579,7 @@ Maintenant que le prétraitement est terminé, nous changeons le *tokenizer* pou tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) ``` -Nous supprimons ensuite les colonnes de notre `eval_set` qui ne sont pas attendues par le modèle, nous construisons un lot avec l'ensemble de ce petit ensemble de validation, et nous le passons au modèle. Si un GPU est disponible, nous l'utilisons pour aller plus vite : +Nous supprimons ensuite les colonnes de notre `eval_set` qui ne sont pas attendues par le modèle. Nous construisons un batch avec tout de ce petit ensemble de validation et le passons au modèle. Si un GPU est disponible, nous l'utilisons pour aller plus vite : {#if fw === 'pt'} @@ -598,7 +600,7 @@ with torch.no_grad(): outputs = trained_model(**batch) ``` -Puisque le `Trainer` nous donnera les prédictions sous forme de tableaux NumPy, nous récupérons les logits de début et de fin et les convertissons dans ce format : +Puisque `Trainer` nous donne les prédictions sous forme de tableaux NumPy, nous récupérons les logits de début et de fin et les convertissons dans ce format : ```python start_logits = outputs.start_logits.cpu().numpy() @@ -639,9 +641,9 @@ for idx, feature in enumerate(eval_set): example_to_features[feature["example_id"]].append(idx) ``` -Avec cela en main, nous pouvons vraiment nous mettre au travail en parcourant en boucle tous les exemples et, pour chaque exemple, toutes les caractéristiques associées. Comme nous l'avons dit précédemment, nous allons regarder les scores logit pour les `n_meilleurs` logits de début et logits de fin, en excluant les positions qui donnent : +Avec cela, nous pouvons vraiment nous mettre au travail en bouclant tous les exemples et, pour chaque exemple, toutes les caractéristiques associées. Comme nous l'avons dit précédemment, nous allons regarder les scores logit pour les `n_best` logits de début et logits de fin, en excluant les positions qui donnent : -- une réponse qui ne serait pas dans le contexte. +- une réponse qui ne serait pas dans le contexte - une réponse avec une longueur négative - une réponse qui est trop longue (nous limitons les possibilités à `max_answer_length=30`) @@ -671,7 +673,7 @@ for example in small_eval_set: # Ignore les réponses qui ne sont pas entièrement dans le contexte if offsets[start_index] is None or offsets[end_index] is None: continue - # Ignorer les réponses dont la longueur est soit < 0 soit > max_answer_length. + # Ignore les réponses dont la longueur est soit < 0 soit > max_answer_length if ( end_index < start_index or end_index - start_index + 1 > max_answer_length @@ -689,7 +691,7 @@ for example in small_eval_set: predicted_answers.append({"id": example_id, "prediction_text": best_answer["text"]}) ``` -Le format final des réponses prédites est celui qui sera attendu par la métrique que nous allons utiliser. Comme d'habitude, nous pouvons le charger à l'aide de la bibliothèque 🤗 *Datasets* : +Le format final des réponses prédites est celui qui sera attendu par la métrique que nous allons utiliser. Comme d'habitude, nous pouvons la charger à l'aide de la bibliothèque 🤗 *Datasets* : ```python from datasets import load_metric @@ -697,7 +699,7 @@ from datasets import load_metric metric = load_metric("squad") ``` -Cette métrique attend les réponses prédites dans le format que nous avons vu ci-dessus (une liste de dictionnaires avec une clé pour l'ID de l'exemple et une clé pour le texte prédit) et les réponses théoriques dans le format ci-dessous (une liste de dictionnaires avec une clé pour l'ID de l'exemple et une clé pour les réponses possibles) : +Cette métrique attend les réponses prédites dans le format que nous avons vu ci-dessus (une liste de dictionnaires avec une clé pour l'identifiant de l'exemple et une clé pour le texte prédit) et les réponses théoriques dans le format ci-dessous (une liste de dictionnaires avec une clé pour l'identifiant de l'exemple et une clé pour les réponses possibles) : ```python theoretical_answers = [ @@ -727,13 +729,13 @@ metric.compute(predictions=predicted_answers, references=theoretical_answers) {'exact_match': 83.0, 'f1': 88.25} ``` -Encore une fois, c'est plutôt bon si l'on considère que, selon [son article](https://arxiv.org/abs/1910.01108v2), DistilBERT *finetuné* sur SQuAD obtient 79,1 et 86,9 pour ces scores sur l'ensemble des données. +Encore une fois, c'est plutôt bon si l'on considère que, d'après [le papier](https://arxiv.org/abs/1910.01108v2) de DistilBERT, *finetuné* sur SQuAD, ce modèle obtient 79,1 et 86,9 pour ces scores sur l'ensemble du jeu de données. {#if fw === 'pt'} -Maintenant, mettons tout ce que nous venons de faire dans une fonction `compute_metrics()` que nous utiliserons dans le `Trainer`. Normalement, cette fonction `compute_metrics()` reçoit seulement un tuple `eval_preds` avec les logits et les labels. Ici, nous aurons besoin d'un peu plus, car nous devons chercher dans le jeu de données des caractéristiques pour le décalage et dans le jeu de données des exemples pour les contextes originaux, donc nous ne serons pas en mesure d'utiliser cette fonction pour obtenir des résultats d'évaluation réguliers pendant l'entraînement. Nous ne l'utiliserons qu'à la fin de l'entraînement pour vérifier les résultats. +Maintenant, mettons tout ce que nous venons de faire dans une fonction `compute_metrics()` que nous utiliserons dans le `Trainer`. Normalement, cette fonction `compute_metrics()` reçoit seulement un *tuple* `eval_preds` avec les logits et les étiquettes. Ici, nous aurons besoin d'un peu plus, car nous devons chercher dans le jeu de données des caractéristiques pour le décalage et dans le jeu de données des exemples pour les contextes originaux. Ainsi nous ne serons pas en mesure d'utiliser cette fonction pour obtenir des résultats d'évaluation standards pendant l'entraînement. Nous ne l'utiliserons qu'à la fin de l'entraînement pour vérifier les résultats. -La fonction `compute_metrics()` regroupe les mêmes étapes que précédemment ; nous ajoutons juste une petite vérification au cas où nous ne trouverions aucune réponse valide (dans ce cas nous prédisons une chaîne vide). +La fonction `compute_metrics()` regroupe les mêmes étapes que précédemment. Nous ajoutons juste une petite vérification au cas où nous ne trouverions aucune réponse valide (dans ce cas nous prédisons une chaîne vide). {:else} @@ -769,7 +771,7 @@ def compute_metrics(start_logits, end_logits, features, examples): # Ignore les réponses qui ne sont pas entièrement dans le contexte if offsets[start_index] is None or offsets[end_index] is None: continue - # Ignore les réponses dont la longueur est soit < 0, soit > max_answer_length. + # Ignore les réponses dont la longueur est soit < 0, soit > max_answer_length if ( end_index < start_index or end_index - start_index + 1 > max_answer_length @@ -805,13 +807,13 @@ compute_metrics(start_logits, end_logits, eval_set, small_eval_set) {'exact_match': 83.0, 'f1': 88.25} ``` -C'est bien ! Maintenant, utilisons ceci pour affiner notre modèle. +C'est bien ! Maintenant, utilisons ceci pour *finetuner* notre modèle. -### *Finetuning* du modèle +### Finetuning du modèle {#if fw === 'pt'} -Nous sommes maintenant prêts à entraîner notre modèle. Créons-le d'abord, en utilisant la classe `AutoModelForQuestionAnswering` comme précédemment : +Nous sommes maintenant prêts à entraîner notre modèle. Créons-le en utilisant la classe `AutoModelForQuestionAnswering` comme précédemment : ```python model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) @@ -819,7 +821,7 @@ model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) {:else} -Nous sommes maintenant prêts à entraîner notre modèle. Créons-le d'abord, en utilisant la classe `TFAutoModelForQuestionAnswering` comme précédemment : +Nous sommes maintenant prêts à entraîner notre modèle. Créons-le en utilisant la classe `TFAutoModelForQuestionAnswering` comme précédemment : ```python model = TFAutoModelForQuestionAnswering.from_pretrained(model_checkpoint) @@ -829,7 +831,7 @@ model = TFAutoModelForQuestionAnswering.from_pretrained(model_checkpoint) Comme d'habitude, nous recevons un avertissement indiquant que certains poids ne sont pas utilisés (ceux de la tête de pré-entraînement) et que d'autres sont initialisés de manière aléatoire (ceux de la tête de réponse aux questions). Vous devriez être habitué à cela maintenant, mais cela signifie que ce modèle n'est pas encore prêt à être utilisé et qu'il a besoin d'être *finetuné*. Une bonne chose que nous soyons sur le point de le faire ! -Pour pouvoir pousser notre modèle vers le *Hub*, nous devons nous connecter à Hugging Face. Si vous exécutez ce code dans un *notebook*, vous pouvez le faire avec la fonction utilitaire suivante, qui affiche un widget où vous pouvez entrer vos identifiants de connexion : +Pour pouvoir pousser notre modèle vers le *Hub*, nous devons nous connecter à Hugging Face. Si vous exécutez ce code dans un *notebook*, vous pouvez le faire avec la fonction utilitaire suivante, qui affiche un *widget* où vous pouvez entrer vos identifiants de connexion : ```python from huggingface_hub import notebook_login @@ -845,11 +847,11 @@ huggingface-cli login {#if fw === 'pt'} -Une fois ceci fait, nous pouvons définir nos `TrainingArguments`. Comme nous l'avons dit lorsque nous avons défini notre fonction pour calculer la métrique, nous ne serons pas en mesure d'avoir une boucle d'évaluation régulière à cause de la signature de la fonction `compute_metrics()`. Nous pourrions écrire notre propre sous-classe de `Trainer` pour faire cela (une approche que vous pouvez trouver dans le [script d'exemple de réponse aux questions](https://github.com/huggingface/transformers/blob/master/examples/pytorch/question-answering/trainer_qa.py)), mais c'est un peu trop long pour cette section. A la place, nous n'évaluerons le modèle qu'à la fin de l'entraînement et nous vous montrerons comment faire une évaluation régulière dans "Une boucle d'entraînement personnalisée" ci-dessous. +Une fois ceci fait, nous pouvons définir nos `TrainingArguments`. Comme nous l'avons dit lorsque nous avons défini notre fonction pour calculer la métrique, nous ne serons pas en mesure d'avoir une boucle d'évaluation standard à cause de la signature de la fonction `compute_metrics()`. Nous pourrions écrire notre propre sous-classe de `Trainer` pour faire cela (une approche que vous pouvez trouver dans le [script d'exemple de réponse aux questions](https://github.com/huggingface/transformers/blob/master/examples/pytorch/question-answering/trainer_qa.py)), mais c'est un peu trop long pour cette section. A la place, nous n'évaluerons le modèle qu'à la fin de l'entraînement et nous vous montrerons comment faire une évaluation cela dans le paragraphe « Une boucle d'entraînement personnalisée » ci-dessous. -C'est vraiment là que l'API `Trainer` montre ses limites et que la bibliothèque 🤗 *Accelerate* brille : personnaliser la classe pour un cas d'utilisation spécifique peut être pénible, mais modifier une boucle d'entraînement entièrement exposée est facile. +C'est là que l'API `Trainer` montre ses limites et que la bibliothèque 🤗 *Accelerate* brille : personnaliser la classe pour un cas d'utilisation spécifique peut être pénible, mais modifier une boucle d'entraînement est facile. -Jetons un coup d'œil à notre `TrainingArguments` : +Jetons un coup d'œil à notre `TrainingArguments` : ```python from transformers import TrainingArguments @@ -866,11 +868,11 @@ args = TrainingArguments( ) ``` -Nous avons déjà vu la plupart d'entre eux : nous définissons quelques hyperparamètres (comme le taux d'apprentissage, le nombre d'époques pour lesquelles nous nous entraînons, et une certaine décroissance de poids) et nous indiquons que nous voulons sauvegarder le modèle à la fin de chaque époque, sauter l'évaluation, et télécharger nos résultats vers le Model Hub. Nous activons également l'entraînement en précision mixte avec `fp16=True`, car cela peut accélérer l'entraînement sur un GPU récent. +Nous avons déjà vu la plupart d'entre eux. Nous définissons quelques hyperparamètres (comme le taux d'apprentissage, le nombre d'époques d'entraînement, un taux de décroissance des poids) et nous indiquons que nous voulons sauvegarder le modèle à la fin de chaque époque, sauter l'évaluation, et télécharger nos résultats vers le *Hub*. Nous activons également l'entraînement en précision mixte avec `fp16=True`, car cela peut accélérer l'entraînement sur un GPU récent. {:else} -Maintenant que c'est fait, nous pouvons créer nos ensembles de données TF. Nous pouvons utiliser le simple collateur de données par défaut cette fois-ci : +Maintenant que c'est fait, nous pouvons créer nos jeux de données TensorFlow. Nous pouvons utiliser le simple collateur de données par défaut cette fois-ci : ```python from transformers import DefaultDataCollator @@ -908,9 +910,9 @@ from transformers import create_optimizer from transformers.keras_callbacks import PushToHubCallback import tensorflow as tf -# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans le jeu de données, divisé par la taille du batch, puis multiplié par le nombre total d'époques. -# par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un lot tf.data.Dataset, -# et non le jeu de données original Hugging Face Dataset, donc son len() est déjà num_samples // batch_size. +# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans le jeu de données, divisé par la taille du batch, +# puis multiplié par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un tf.data.Dataset, +# et non le jeu de données original donc son len() est déjà num_samples // batch_size. num_train_epochs = 3 num_train_steps = len(tf_train_dataset) * num_train_epochs optimizer, schedule = create_optimizer( @@ -925,11 +927,11 @@ model.compile(optimizer=optimizer) tf.keras.mixed_precision.set_global_policy("mixed_float16") ``` -Enfin, nous sommes prêts à nous entraîner avec `model.fit()`. Nous utilisons un `PushToHubCallback` pour télécharger le modèle sur le *Hub* après chaque époque. +Enfin, nous sommes prêts à entraîner avec `model.fit()`. Nous utilisons un `PushToHubCallback` pour télécharger le modèle sur le *Hub* après chaque époque. {/if} -Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, donc dans notre cas il sera dans `"sgugger/bert-finetuned-squad"`. Nous pouvons passer outre en passant un `hub_model_id` ; par exemple, pour pousser le modèle dans l'organisation `huggingface_course` nous avons utilisé `hub_model_id= "huggingface_course/bert-finetuned-squad"` (qui est le modèle que nous avons lié au début de cette section). +Par défaut, le dépôt utilisé sera dans votre espace et nommé après le répertoire de sortie que vous avez défini. Donc dans notre cas il sera dans `"sgugger/bert-finetuned-squad"`. Nous pouvons passer outre en passant un `hub_model_id`, par exemple, pour pousser le modèle dans l'organisation `huggingface_course` nous avons utilisé `hub_model_id= "huggingface_course/bert-finetuned-squad"` (qui est le modèle que nous avons lié au début de cette section). {#if fw === 'pt'} @@ -967,11 +969,11 @@ model.fit(tf_train_dataset, callbacks=[callback], epochs=num_train_epochs) {/if} -Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le Hub en arrière-plan. Ainsi, vous pourrez reprendre votre entraînement sur une autre machine si nécessaire. L'ensemble de l'entraînement prend un certain temps (un peu plus d'une heure sur une Titan RTX), vous pouvez donc prendre un café ou relire les parties du cours qui vous ont semblé plus difficiles pendant qu'il se déroule. Notez également que dès que la première époque est terminée, vous verrez des poids téléchargés sur le Hub et vous pourrez commencer à jouer avec votre modèle sur sa page. +Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. Ainsi, vous pourrez reprendre votre entraînement sur une autre machine si nécessaire. L'ensemble de l'entraînement prend un certain temps (un peu plus d'une heure sur une Titan RTX), vous pouvez donc prendre un café ou relire les parties du cours qui vous ont semblé plus difficiles pendant qu'il se déroule. Notez également que dès que la première époque est terminée, vous verrez des poids téléchargés sur le *Hub* et vous pourrez commencer à jouer avec votre modèle sur sa page. {#if fw === 'pt'} -Une fois l'entraînement terminé, nous pouvons enfin évaluer notre modèle (et prier pour ne pas avoir dépensé tout ce temps de calcul pour rien). La méthode `predict()` du `Trainer` retournera un tuple où les premiers éléments seront les prédictions du modèle (ici une paire avec les logits de début et de fin). Nous envoyons ceci à notre fonction `compute_metrics()` : +Une fois l'entraînement terminé, nous pouvons enfin évaluer notre modèle (et prier pour ne pas avoir dépensé tout ce temps de calcul pour rien). La méthode `predict()` du `Trainer` retournera un *tuple* où les premiers éléments seront les prédictions du modèle (ici une paire avec les logits de début et de fin). Nous envoyons ceci à notre fonction `compute_metrics()` : ```python predictions, _ = trainer.predict(validation_dataset) @@ -999,7 +1001,7 @@ compute_metrics( {'exact_match': 81.18259224219489, 'f1': 88.67381321905516} ``` -Super ! À titre de comparaison, les scores de base indiqués dans l'article du BERT pour ce modèle sont de 80,8 et 88,5, donc nous sommes exactement là où nous devrions être. +Super ! À titre de comparaison, les scores indiqués dans l'article de BERT pour ce tâche sont de 80,8 et 88,5. Donc nous sommes exactement là où nous devrions être. {#if fw === 'pt'} @@ -1015,15 +1017,15 @@ Cela renvoie l'URL du commit qu'il vient de faire, si vous voulez l'inspecter : 'https://huggingface.co/sgugger/bert-finetuned-squad/commit/9dcee1fbc25946a6ed4bb32efb1bd71d5fa90b68' ``` -Le `Trainer` rédige également une fiche modèle avec tous les résultats de l'évaluation et la télécharge. +Le `Trainer` rédige également une carte de modèle avec tous les résultats de l'évaluation et la télécharge. {/if} -À ce stade, vous pouvez utiliser le widget d'inférence sur le *Hub* du modèle pour tester le modèle et le partager avec vos amis, votre famille et vos animaux préférés. Vous avez réussi à *finetuner* un modèle sur une tâche de réponse à une question - félicitations ! +À ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* du modèle pour tester le modèle et le partager avec vos amis, votre famille et vos animaux préférés. Vous avez réussi à *finetuner* un modèle sur une tâche de réponse à une question. Félicitations ! -✏️ **Votre tour** Essayez un autre modèle d'architecture pour voir s'il est plus performant dans cette tâche ! +✏️ **A votre tour** Essayez un autre modèle pour voir s'il est plus performant pour cette tâche ! @@ -1033,11 +1035,11 @@ Si vous voulez plonger un peu plus profondément dans la boucle d'entraînement, ## Une boucle d'entraînement personnalisée -Jetons maintenant un coup d'œil à la boucle d'entraînement complète, afin que vous puissiez facilement personnaliser les parties dont vous avez besoin. Elle ressemblera beaucoup à la boucle d'entraînement du [Chapitre 3](/course/fr/chapter3/4), à l'exception de la boucle d'évaluation. Nous serons en mesure d'évaluer le modèle régulièrement puisque nous ne sommes plus contraints par la classe `Trainer`. +Jetons maintenant un coup d'œil à la boucle d'entraînement complète, afin que vous puissiez facilement personnaliser les parties dont vous avez besoin. Elle ressemblera beaucoup à la boucle d'entraînement du [chapitre 3](/course/fr/chapter3/4), à l'exception de la boucle d'évaluation. Nous serons en mesure d'évaluer le modèle régulièrement puisque nous ne sommes plus contraints par la classe `Trainer`. ### Préparer tout pour l'entraînement -Tout d'abord, nous devons construire le `DataLoader`s à partir de nos jeux de données. Nous définissons le format de ces jeux de données à `"torch"`, et supprimons les colonnes dans le jeu de validation qui ne sont pas utilisées par le modèle. Ensuite, nous pouvons utiliser le `default_data_collator` fourni par Transformers comme `collate_fn` et mélanger l'ensemble d'entraînement, mais pas l'ensemble de validation : +Tout d'abord, nous devons construire le `DataLoader`s à partir de nos jeux de données. Nous définissons le format de ces jeux de données à `"torch"` et supprimons les colonnes dans le jeu de validation qui ne sont pas utilisées par le modèle. Ensuite, nous pouvons utiliser le `default_data_collator` fourni par 🤗 *Transformers* comme `collate_fn` et mélanger l'ensemble d'entraînement mais pas celui de validation : ```py from torch.utils.data import DataLoader @@ -1058,13 +1060,13 @@ eval_dataloader = DataLoader( ) ``` -Ensuite, nous réinstantifions notre modèle, afin de nous assurer que nous ne poursuivons pas les réglages fins précédents mais que nous repartons du modèle pré-entraîné de BERT : +Ensuite, nous réinstantifions notre modèle afin de nous assurer que nous ne poursuivons pas le *finetuning* précédent et que nous repartons du modèle BERT pré-entraîné : ```py model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) ``` -Ensuite, nous aurons besoin d'un optimiseur. Comme d'habitude, nous utilisons le classique `AdamW`, qui est comme Adam, mais avec une correction dans la façon dont la décroissance du poids est appliquée : +Ensuite, nous aurons besoin d'un optimiseur. Comme d'habitude, nous utilisons le classique `AdamW`, qui est comme Adam mais avec une correction dans la façon dont le taux de décroissance des poids est appliqué : ```py from torch.optim import AdamW @@ -1072,7 +1074,7 @@ from torch.optim import AdamW optimizer = AdamW(model.parameters(), lr=2e-5) ``` -Une fois que nous avons tous ces objets, nous pouvons les envoyer à la méthode `accelerator.prepare()`. Rappelez-vous que si vous voulez vous entraîner sur des TPUs dans un *notebook* de Colab, vous devrez déplacer tout ce code dans une fonction d'entraînement, et qui ne devrait pas exécuter une cellule qui instancie un `Accelerator`. Nous pouvons forcer l'entraînement en précision mixte en passant `fp16=True` à l'`Accelerator` (ou, si vous exécutez le code comme un script, assurez-vous de remplir la 🤗 *Accelerate* `config` de manière appropriée). +Une fois que nous avons tous ces objets, nous pouvons les envoyer à la méthode `accelerator.prepare()`. Rappelez-vous que si vous voulez entraîner sur des TPUs dans un *notebook* Colab, vous devrez déplacer tout ce code dans une fonction d'entraînement, et qui ne devrait pas exécuter une cellule qui instancie un `Accelerator`. Nous pouvons forcer l'entraînement en précision mixte en passant l'argument `fp16=True` à `Accelerator` (ou, si vous exécutez le code comme un script, assurez-vous de remplir la 🤗 *Accelerate* `config` de manière appropriée). ```py from accelerate import Accelerator @@ -1100,7 +1102,7 @@ lr_scheduler = get_scheduler( ) ``` -Pour pousser notre modèle vers le Hub, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous au Hugging Face Hub, si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'ID du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix ; il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `get_full_repo_name()`) : +Pour pousser notre modèle vers le *Hub*, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous au *Hub*, si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'identifiant du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix. Il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `get_full_repo_name()`) : ```py from huggingface_hub import Repository, get_full_repo_name @@ -1114,7 +1116,7 @@ repo_name 'sgugger/bert-finetuned-squad-accelerate' ``` -Ensuite, nous pouvons cloner ce référentiel dans un dossier local. S'il existe déjà, ce dossier local doit être un clone du référentiel avec lequel nous travaillons : +Ensuite, nous pouvons cloner ce dépôt dans un dossier local. S'il existe déjà, ce dossier local doit être un clone du dépôt avec lequel nous travaillons : ```py output_dir = "bert-finetuned-squad-accelerate" @@ -1127,8 +1129,8 @@ Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output Nous sommes maintenant prêts à écrire la boucle d'entraînement complète. Après avoir défini une barre de progression pour suivre l'évolution de l'entraînement, la boucle comporte trois parties : -- l'entraînement proprement dit, qui est l'itération classique sur le `train_dataloader`, passage en avant du modèle, puis passage en arrière et étape d'optimisation. -- l'évaluation, dans laquelle nous rassemblons toutes les valeurs pour `start_logits` et `end_logits` avant de les convertir en tableaux NumPy. Une fois la boucle d'évaluation terminée, nous concaténons tous les résultats. Notez que nous devons tronquer parce que l' `Accelerator` peut avoir ajouté quelques échantillons à la fin pour s'assurer que nous avons le même nombre d'exemples dans chaque processus. +- l'entraînement à proprement dit, qui est l'itération classique sur le `train_dataloader`, passage en avant du modèle, puis passage en arrière et étape d'optimisation. +- l'évaluation, dans laquelle nous rassemblons toutes les valeurs pour `start_logits` et `end_logits` avant de les convertir en tableaux NumPy. Une fois la boucle d'évaluation terminée, nous concaténons tous les résultats. Notez que nous devons tronquer car `Accelerator` peut avoir ajouté quelques échantillons à la fin pour s'assurer que nous avons le même nombre d'exemples dans chaque processus. - sauvegarde et téléchargement, où nous sauvegardons d'abord le modèle et le *tokenizer*, puis appelons `repo.push_to_hub()`. Comme nous l'avons fait auparavant, nous utilisons l'argument `blocking=False` pour dire à la bibliothèque 🤗 *Hub* de pousser dans un processus asynchrone. De cette façon, l'entraînement continue normalement et cette (longue) instruction est exécutée en arrière-plan. Voici le code complet de la boucle d'entraînement : @@ -1193,20 +1195,20 @@ unwrapped_model = accelerator.unwrap_model(model) unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) ``` -La première ligne est explicite : elle indique à tous les processus d'attendre que tout le monde soit à ce stade avant de continuer. C'est pour s'assurer que nous avons le même modèle dans chaque processus avant de sauvegarder. Ensuite, nous prenons le `unwrapped_model`, qui est le modèle de base que nous avons défini. La méthode `accelerator.prepare()` modifie le modèle pour qu'il fonctionne dans l'entraînement distribué, donc il n'aura plus la méthode `save_pretrained()` ; la méthode `accelerator.unwrap_model()` annule cette étape. Enfin, nous appelons `save_pretrained()` mais nous disons à cette méthode d'utiliser `accelerator.save()` au lieu de `torch.save()`. +La première ligne est explicite : elle indique à tous les processus d'attendre que tout le monde soit à ce stade avant de continuer. C'est pour s'assurer que nous avons le même modèle dans chaque processus avant de sauvegarder. Ensuite, nous prenons le `unwrapped_model`, qui est le modèle de base que nous avons défini. La méthode `accelerator.prepare()` modifie le modèle pour qu'il fonctionne dans l'entraînement distribué. Donc il n'aura plus la méthode `save_pretrained()` car la méthode `accelerator.unwrap_model()` annule cette étape. Enfin, nous appelons `save_pretrained()` mais nous disons à cette méthode d'utiliser `accelerator.save()` au lieu de `torch.save()`. -Une fois ceci fait, vous devriez avoir un modèle qui produit des résultats assez similaires à celui entraîné avec le `Trainer`. Vous pouvez vérifier le modèle que nous avons entraîné en utilisant ce code à [*huggingface-course/bert-finetuned-squad-accelerate*](https://huggingface.co/huggingface-course/bert-finetuned-squad-accelerate). Et si vous voulez tester des modifications de la boucle d'entraînement, vous pouvez les implémenter directement en modifiant le code ci-dessus ! +Une fois ceci fait, vous devriez avoir un modèle qui produit des résultats assez similaires à celui entraîné avec `Trainer`. Vous pouvez vérifier le modèle que nous avons entraîné en utilisant ce code à [*huggingface-course/bert-finetuned-squad-accelerate*](https://huggingface.co/huggingface-course/bert-finetuned-squad-accelerate). Et si vous voulez tester des modifications de la boucle d'entraînement, vous pouvez les implémenter directement en modifiant le code ci-dessus ! {/if} -### Utilisation du modèle *finetuné* +### Utilisation du modèle finetuné -Nous vous avons déjà montré comment vous pouvez utiliser le modèle que nous avons *finetuné* sur le *Hub* avec le widget d'inférence. Pour l'utiliser localement dans un `pipeline`, il suffit de spécifier l'identifiant du modèle : +Nous vous avons déjà montré comment vous pouvez utiliser le modèle que nous avons *finetuné* sur le *Hub* avec le *widget* d'inférence. Pour l'utiliser localement dans un `pipeline`, il suffit de spécifier l'identifiant du modèle : ```py from transformers import pipeline -# Replace this with your own checkpoint +# Remplacez par votre propre checkpoint model_checkpoint = "huggingface-course/bert-finetuned-squad" question_answerer = pipeline("question-answering", model=model_checkpoint) diff --git a/chapters/fr/chapter7/8.mdx b/chapters/fr/chapter7/8.mdx index 795ac72c3..aeea11abb 100644 --- a/chapters/fr/chapter7/8.mdx +++ b/chapters/fr/chapter7/8.mdx @@ -1,12 +1,12 @@ -# *Mastering NLP* +# Maîtriser le NLP -Si vous êtes arrivé jusqu'ici dans le cours, félicitations ! Vous avez maintenant toutes les connaissances et les outils nécessaires pour aborder (presque) n'importe quelle tâche de NLP avec 🤗 *Transformers* et l'écosystème Hugging Face ! +Si vous êtes arrivé jusqu'ici dans le cours, félicitations ! Vous avez maintenant toutes les connaissances et les outils nécessaires pour aborder (presque) n'importe quelle tâche de *NLP* avec 🤗 *Transformers* et l'écosystème d'*Hugging Face* ! -Nous avons vu beaucoup de batchs différents de collecteurs de données, donc nous avons fait cette petite vidéo pour vous aider à trouver lequel utiliser pour chaque tâche : +Nous avons vu beaucoup de collecteurs de données différents, c'est pourquoi nous avons fait cette petite vidéo pour vous aider à trouver lequel utiliser pour chaque tâche : -Après avoir terminé ce tour d'horizon des principales tâches NLP, vous devriez : +Après avoir terminé ce tour d'horizon des principales tâches de *NLP*, vous devriez : * savoir quelles architectures (encodeur, décodeur ou encodeur-décodeur) sont les mieux adaptées à chaque tâche, * comprendre la différence entre le pré-entraînement et le *finetuning* d'un modèle de langage, @@ -14,4 +14,4 @@ Après avoir terminé ce tour d'horizon des principales tâches NLP, vous devrie * comprendre la signification et les limites de métriques comme ROUGE et BLEU pour les tâches de génération de texte, * savoir comment interagir avec vos modèles *finetunés*, à la fois sur le *Hub* et en utilisant la `pipeline` de 🤗 *Transformers*. -Malgré toutes ces connaissances, il arrivera un moment où vous rencontrerez un bug difficile dans votre code ou aurez une question sur la façon de résoudre un problème NLP particulier. Heureusement, la communauté Hugging Face est là pour vous aider ! Dans le dernier chapitre de cette partie du cours, nous allons explorer comment vous pouvez déboguer vos *transformers* et demander de l'aide efficacement. \ No newline at end of file +Malgré toutes ces connaissances, il arrivera un moment où vous rencontrerez un *bug* difficile dans votre code ou aurez une question sur la façon de résoudre un problème de *NLP* particulier. Heureusement, la communauté d'*Hugging Face* est là pour vous aider ! Dans le dernier chapitre de cette partie du cours, nous allons explorer comment vous pouvez déboguer vos modèles et demander de l'aide efficacement. \ No newline at end of file diff --git a/chapters/fr/chapter7/9.mdx b/chapters/fr/chapter7/9.mdx index 56cb887aa..ae063e9d0 100644 --- a/chapters/fr/chapter7/9.mdx +++ b/chapters/fr/chapter7/9.mdx @@ -6,42 +6,42 @@ Testons ce que vous avez appris dans ce chapitre ! -### 1. Laquelle des tâches suivantes peut être considérée comme un problème de classification de *tokens* ? +### 1. Laquelle des tâches suivantes peut être considérée comme un problème de classification de tokens ? -### 2. Quelle partie du prétraitement pour la classification des *tokens* diffère des autres pipelines de prétraitement ? +### 2. Quelle partie du prétraitement pour la classification de tokens diffère des autres pipelines de prétraitement ? padding.", - explain: "En effet ! Mais ce n'est pas la seule différence.", + explain: "En effet mais ce n'est pas la seule différence.", correct: true } ]} /> -### 3. Quel problème se pose lorsque nous tokenisons les mots dans un problème de classification de *tokens* et que nous voulons étiqueter les *tokens* ? +### 3. Quel problème se pose lorsque nous tokenisons les mots dans un problème de classification de tokens et que nous voulons étiqueter les tokens ? tokenizer ajoute des tokens spéciaux et nous n'avons pas d'étiquettes pour eux.", - explain: "Nous étiquetons ces -100 ils sont donc ignorés dans la perte." + explain: "Nous les étiquetons par -100 ils sont donc ignorés dans la perte." }, { text: "Chaque mot peut produire plusieurs tokens, ce qui fait que nous nous retrouvons avec plus de tokens que d'étiquettes.", - explain: "C'est le problème principal, et nous devons aligner les étiquettes originales avec les tokens.", + explain: "C'est le problème principal et nous devons aligner les étiquettes originales avec les tokens.", correct: true }, { text: "Les tokens ajoutés n'ont pas d'étiquettes, il n'y a donc pas de problème.", - explain: "C'est incorrect. Nous avons besoin d'autant d'étiquettes que de tokens, sinon nos modèles se tromperont." + explain: "Nous avons besoin d'autant d'étiquettes que de tokens, sinon nos modèles se tromperont." } ]} /> -### 4. Que signifie "adaptation au domaine" ? +### 4. Que signifie « adaptation au domaine » ? finetunons un modèle pré-entraîné sur un nouveau jeu de données et qu'il donne des prédictions qui sont plus adaptées à ce nouveau jeu de données.", + explain: "Le modèle a adapté ses connaissances au nouveau jeu de données.", correct: true }, { text: "C'est lorsque nous ajoutons des échantillons mal classés à un jeu de données pour rendre notre modèle plus robuste.", - explain: "C'est certainement quelque chose que vous devriez faire si vous réentraînez votre modèle régulièrement, mais ce n'est pas une adaptation au domaine.." + explain: "C'est certainement quelque chose que vous devriez faire si vous réentraînez votre modèle régulièrement, mais ce n'est pas une adaptation au domaine." } ]} /> @@ -110,16 +110,16 @@ Testons ce que vous avez appris dans ce chapitre ! correct: true }, { - text: "Certains des tokens de la phrase d'entrée sont masqués aléatoirement et les étiquettes sont les tokens d'entrée originaux, décalés vers la gauche.", + text: "Certains des tokens de la phrase d'entrée sont masqués de manière aléatoire et les étiquettes sont les tokens d'entrée originaux, décalés vers la gauche.", explain: "Non, le déplacement des étiquettes vers la gauche correspond à la prédiction du mot suivant, ce qui est une modélisation causale du langage." }, { - text: "Certains des tokens de la phrase d'entrée sont masqués de manière aléatoire, et l'étiquette indique si la phrase est positive ou négative.", - explain: "Il s'agit d'un problème de classification de séquences avec une certaine augmentation des données, et non d'une modélisation du langage masqué." + text: "Certains des tokens de la phrase d'entrée sont masqués de manière aléatoire et l'étiquette indique si la phrase est positive ou négative.", + explain: "Il s'agit d'un problème de classification de séquences avec une certaine augmentation de données et non d'une modélisation du langage masqué." }, { - text: "Certains des tokens des deux phrases d'entrée sont masqués de manière aléatoire, et l'étiquette indique si les deux phrases sont similaires ou non.", - explain: "Il s'agit d'un problème de classification de séquences avec une certaine augmentation des données, et non d'une modélisation du langage masqué." + text: "Certains des tokens des deux phrases d'entrée sont masqués de manière aléatoire et l'étiquette indique si les deux phrases sont similaires ou non.", + explain: "Il s'agit d'un problème de classification de séquences avec une certaine augmentation de données et non d'une modélisation du langage masqué." } ]} /> @@ -129,23 +129,23 @@ Testons ce que vous avez appris dans ce chapitre ! tokenizer avec les éléments suivants inputs=... et targets=....", - explain: "Nous pourrions ajouter cette API à l'avenir, mais ce n'est pas possible pour le moment." + explain: "Nous pourrions ajouter cette API à l'avenir mais ce n'est pas possible pour le moment." }, { text: "Les entrées et les cibles doivent être prétraitées, en deux appels séparés au tokenizer.", @@ -177,22 +177,22 @@ Testons ce que vous avez appris dans ce chapitre ! {#if fw === 'pt'} -### 8. Pourquoi existe-t-il une sous-classe spécifique de `Trainer` pour les problèmes de séquence à séquence ? +### 8. Pourquoi existe-t-il une sous-classe spécifique de Trainer pour les problèmes de séquence à séquence ? -100", - explain: "Ce n'est pas du tout une perte personnalisée, mais la façon dont la perte est toujours calculée." + text: "Parce que les problèmes de séquence-à-séquence utilisent une perte personnalisée, pour ignorer les étiquettes définies à -100.", + explain: "Ce n'est pas du tout une perte personnalisée mais la façon dont la perte est toujours calculée." }, { - text: "Parce que les problèmes de séquence à séquence nécessitent une boucle d'évaluation spéciale", - explain: "C'est exact. Les prédictions des modèles de séquence à séquence sont souvent exécutées en utilisant la méthode generate().", + text: "Parce que les problèmes de séquence à séquence nécessitent une boucle d'évaluation spéciale.", + explain: "Les prédictions des modèles de séquence à séquence sont souvent exécutées en utilisant la méthode generate().", correct: true }, { - text: "Parce que les cibles sont des textes dans des problèmes de séquence à séquence", - explain: "Le Trainer ne se soucie pas vraiment de cela puisqu'ils ont été prétraités auparavant." + text: "Parce que les cibles sont des textes dans des problèmes de séquence à séquence.", + explain: "Trainer ne se soucie pas vraiment de cela puisqu'elles ont été prétraités auparavant." }, { text: "Parce que nous utilisons deux modèles dans les problèmes de séquence à séquence.", @@ -203,26 +203,26 @@ Testons ce que vous avez appris dans ce chapitre ! {:else} -### 9. Pourquoi est-il souvent inutile de spécifier une perte quand on appelle `compile()` sur un *transformer* ? +### 9. Pourquoi est-il souvent inutile de spécifier une perte quand on appelle compile() sur un transformer ? tranformers sont entraînés avec un apprentissage non supervisé.", - explain: "Pas tout à fait. Même l'apprentissage non supervisé a besoin d'une fonction de perte !" + text: "Parce que les tranformers sont entraînés avec un apprentissage autosupervisé.", + explain: "Pas tout à fait. Même l'apprentissage autosupervisé a besoin d'une fonction de perte !" }, { text: "Parce que la sortie de perte interne du modèle est utilisée par défaut.", - explain: "C'est exact !", + explain: " ", correct: true }, { text: "Parce que nous calculons les mesures après l'entraînement au lieu de le faire.", - explain: "Nous le faisons souvent, mais cela n'explique pas d'où vient la valeur de perte que nous optimisons dans l'entraînement." + explain: "Nous le faisons souvent mais cela n'explique pas d'où vient la valeur de perte que nous optimisons dans l'entraînement." }, { - text: "Parce que la perte est spécifiée dans `model.fit()`.", - explain: "Non, la fonction de perte est toujours fixée une fois que vous exécutez `model.compile()`, et ne peut pas être modifiée dans `model.fit()`." + text: "Parce que la perte est spécifiée dans model.fit().", + explain: "La fonction de perte est toujours fixée une fois que vous exécutez model.compile() et ne peut pas être modifiée dans model.fit()." } ]} /> @@ -235,16 +235,16 @@ Testons ce que vous avez appris dans ce chapitre ! choices={[ { text: "Lorsqu'il n'y a pas de modèle pré-entraîné disponible pour votre langue spécifique.", - explain: "C'est exact.", + explain: " ", correct: true }, { text: "Lorsque vous disposez d'un grand nombre de données, même s'il existe un modèle pré-entraîné qui pourrait fonctionner sur ces données.", - explain: "Dans ce cas, vous devriez probablement utiliser le modèle pré-entraîné et le finetuner sur vos données, afin d'éviter d'énormes coûts de calcul." + explain: "Dans ce cas, vous devriez probablement utiliser le modèle pré-entraîné et le finetuner sur vos données afin d'éviter d'énormes coûts de calcul." }, { text: "Lorsque vous avez des doutes sur le biais du modèle pré-entraîné que vous utilisez.", - explain: "C'est vrai, mais vous devez vous assurer que les données que vous utiliserez pour l'entraînement sont vraiment meilleures.", + explain: "C'est vrai mais vous devez vous assurer que les données que vous utiliserez pour l'entraînement sont vraiment meilleures.", correct: true }, { @@ -264,7 +264,7 @@ Testons ce que vous avez appris dans ce chapitre ! }, { text: "Parce que l'objectif de pré-entraînement ne nécessite pas que les humains étiquettent les données.", - explain: "C'est exact, la modélisation du langage est un problème autosupervisé.", + explain: "La modélisation du langage est un problème autosupervisé.", correct: true }, { @@ -280,7 +280,7 @@ Testons ce que vous avez appris dans ce chapitre ! choices={[ { text: "Vous devez tokeniser les entrées.", - explain: "C'est exact, mais est-ce vraiment un défi majeur ?" + explain: "Mais est-ce vraiment un défi majeur ?" }, { text: "Vous devez faire face à des contextes très longs, qui donnent plusieurs caractéristiques d'entraînement qui peuvent ou non contenir la réponse.", @@ -305,20 +305,20 @@ Testons ce que vous avez appris dans ce chapitre ! choices={[ { text: "Le modèle vous donne les positions de début et de fin de la réponse, et vous n'avez plus qu'à décoder la plage de tokens correspondant.", - explain: "Ce pourrait être une façon de faire, mais c'est un peu trop simpliste." + explain: "Ce pourrait être une façon de faire mais c'est un peu trop simpliste." }, { - text: "Le modèle vous donne les positions de début et de fin de la réponse pour chaque caractéristique créée par un exemple, et il vous suffit de décoder la plage de tokens correspondant dans celui qui a le meilleur score.", + text: "Le modèle vous donne les positions de début et de fin de la réponse pour chaque caractéristique créée par un exemple et il vous suffit de décoder la plage de tokens correspondant dans celui qui a le meilleur score.", explain: "C'est proche du post-traitement que nous avons étudié, mais ce n'est pas tout à fait exact." }, { - text: "Le modèle vous donne les positions de début et de fin de la réponse pour chaque caractéristique créée par un exemple, et vous n'avez plus qu'à les faire correspondre à la portée dans le contexte de celui qui a le meilleur score.", + text: "Le modèle vous donne les positions de début et de fin de la réponse pour chaque caractéristique créée par un exemple et vous n'avez plus qu'à les faire correspondre à la portée dans le contexte de celui qui a le meilleur score.", explain: "C'est ça en résumé !", correct: true }, { - text: "Le modèle génère une réponse, et il vous suffit de la décoder.", - explain: "Non, à moins que vous ne formuliez votre problème de réponse aux questions comme une tâche de séquence à séquence." + text: "Le modèle génère une réponse et il vous suffit de la décoder.", + explain: "A moins que vous ne formuliez votre problème de réponse aux questions comme une tâche de séquence à séquence." } ]} /> diff --git a/chapters/it/_toctree.yml b/chapters/it/_toctree.yml index d9c034499..6198bb32b 100644 --- a/chapters/it/_toctree.yml +++ b/chapters/it/_toctree.yml @@ -11,3 +11,20 @@ title: Natural Language Processing - local: chapter1/3 title: Cosa fanno i Transformer? + +- title: 4. Condividere modelli e tokenizers + sections: + - local: chapter4/1 + title: L'Hub di Hugging Face + - local: chapter4/2 + title: Usare modelli pre-addestrati + - local: chapter4/3 + title: Condividere modelli pre-addestrati + - local: chapter4/4 + title: Scrivere un cartellino del modello + - local: chapter4/5 + title: Fine della parte 1! + - local: chapter4/6 + title: Quiz di fine capitolo + quiz: 4 + diff --git a/chapters/it/chapter4/1.mdx b/chapters/it/chapter4/1.mdx new file mode 100644 index 000000000..b0d8f4f70 --- /dev/null +++ b/chapters/it/chapter4/1.mdx @@ -0,0 +1,17 @@ +# L'Hub di Hugging Face + +L'Hub di Hugging Face -- il nostro sito web principale -- è la piattaforma che permette a chiunque di scoprire, utilizzare, e proporre nuovi modelli e dataset. Contiene una vasta varietà di modelli, con più di 10.000 modelli pubblicamente disponibili. In questo capitolo ci concentreremo sui modelli, mentre approfondiremo i dataset nel capitolo 5. + +I modelli disponibili nell'Hub non sono limitati ai 🤗 Transformers, o ai modelli di analisi del linguaggio naturale (Natural Language Processing - NLP). Ci sono infatti modelli sviluppati da [Flair](https://github.com/flairNLP/flair) e [AllenNLP](https://github.com/allenai/allennlp) per l'NLP, ma anche modelli di [Asteroid](https://github.com/asteroid-team/asteroid) e [pyannote](https://github.com/pyannote/pyannote-audio) per la fonologia, e [timm](https://github.com/rwightman/pytorch-image-models) per l'analisi di immagini. + +Ognuno di questi modelli è disponibile come un repository Git, che permette di tracciarne le versioni e rendere reproducibili i risultati. Condividere un modello nell'Hub significa renderlo accessibile a tutta la comunità e consentire a chiunque di usarlo facilmente. Questo evita la necessità di addestrare un modello da soli e ne semplifica la diffusione e l'uso. + +In aggiunta, condividere un modello nell'Hub automaticamente pubblica una interfaccia programmatica (Application Programming Interface - API) di inferenza per quel modello. Qualsiasi utente della comunità può quindi provare il modello direttamente dalla sua pagina web con input personalizzati attraverso una interfaccia dedicata. + +La parte migliore è che condividere e usare qualsiasi modello pubblico nell'Hub è completamente gratuito! Esistono anche dei [piani a pagamento](https://huggingface.co/pricing) se si desidera condividere i modelli in maniera privata. + +Il video sottostante mostra come navigare l'Hub. + + + +Avere un account huggingface.co è necessario per seguire questo capitolo, poiché creeremo e gestiremo dei repository sull'Hub di Hugging Face: [crea un account](https://huggingface.co/join) \ No newline at end of file diff --git a/chapters/it/chapter4/2.mdx b/chapters/it/chapter4/2.mdx new file mode 100644 index 000000000..a95b9c9b0 --- /dev/null +++ b/chapters/it/chapter4/2.mdx @@ -0,0 +1,96 @@ + + +# Usare modelli pre-addestrati + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Usando l'Hub diventa molto facile selzionare il modello appropriato, così da poterlo usare in qualsiasi altro framework con solo poche righe di codice. Vediamo ora come usare un di questi modelli, e come contribuire allo sviluppo della comunità. + +Ad esempio assumiamo di stare cercando un modello francese sviluppato per ricostruire token mancanti (mask filling). + +
+Selecting the Camembert model. +
+ +Selezioniamo il checkpoint `camembert-base` per provarlo. L'identificatore `camembert-base` è tutto quello che serve per inizializzarlo! Come si è visto in precedenti capitoli, è possibile istanziare il modello usando la funzione `pipeline()`: + +```py +from transformers import pipeline + +camembert_fill_mask = pipeline("fill-mask", model="camembert-base") +results = camembert_fill_mask("Le camembert est :)") +``` + +```python out +[ + {'sequence': 'Le camembert est délicieux :)', 'score': 0.49091005325317383, 'token': 7200, 'token_str': 'délicieux'}, + {'sequence': 'Le camembert est excellent :)', 'score': 0.1055697426199913, 'token': 2183, 'token_str': 'excellent'}, + {'sequence': 'Le camembert est succulent :)', 'score': 0.03453313186764717, 'token': 26202, 'token_str': 'succulent'}, + {'sequence': 'Le camembert est meilleur :)', 'score': 0.0330314114689827, 'token': 528, 'token_str': 'meilleur'}, + {'sequence': 'Le camembert est parfait :)', 'score': 0.03007650189101696, 'token': 1654, 'token_str': 'parfait'} +] +``` + +Come potete vedere, caricare un modello all'interno di una pipeline è molto semplice. L'unico elemento da tenere in considerazione è che il checkpoint scelto sia adatto all'utilizzo che intendete farne. Ad esempio, noi abbiamo caricato il checkpoint `camembert-base` all'interno del pipeline `fill-mask`, che è corretto. Ma se dovessimo caricare questo checkpoint in un pipeline di classificazione del testo (`text-classification`), i risultati non avrebbero senso perché l'head di `camembert-base` non è adatto per questo obiettivo! Si consiglia di usare il filtro per obiettivi nell'interfaccia dell'Hub di Hugging Face per selezionare il checkpoint appropriato: + +
+The task selector on the web interface. +
+ +Potete anche istanziare il checkpoint usando direttamente l'architettura del modello: + +{#if fw === 'pt'} +```py +from transformers import CamembertTokenizer, CamembertForMaskedLM + +tokenizer = CamembertTokenizer.from_pretrained("camembert-base") +model = CamembertForMaskedLM.from_pretrained("camembert-base") +``` + +Tuttavia, noi consigliamo di usare le [classi `Auto*`](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes) quando possibile, poiché sono progettate per essere agnostiche rispetto al tipo di architettura del modello. Mentre il codice di esempio precedente limita gli utenti a caricare i checkpoint supportati dall'architettura CamemBERT, usare le classi `Auto*` rende facile il passaggio da un checkpoint ad un altro: + +```py +from transformers import AutoTokenizer, AutoModelForMaskedLM + +tokenizer = AutoTokenizer.from_pretrained("camembert-base") +model = AutoModelForMaskedLM.from_pretrained("camembert-base") +``` +{:else} +```py +from transformers import CamembertTokenizer, TFCamembertForMaskedLM + +tokenizer = CamembertTokenizer.from_pretrained("camembert-base") +model = TFCamembertForMaskedLM.from_pretrained("camembert-base") +``` + +Tuttavia, noi consigliamo di usare le [classi `TFAuto*`](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes) quando possibile, poiché sono progettate per essere agnostiche rispetto al tipo di architettura del modello. Mentre il codice di esempio precedente limita gli utenti a caricare i checkpoint supportati dall'architettura CamemBERT, usare le classi `TFAuto*` rende facile il passaggio da un checkpoint ad un altro: + +```py +from transformers import AutoTokenizer, TFAutoModelForMaskedLM + +tokenizer = AutoTokenizer.from_pretrained("camembert-base") +model = TFAutoModelForMaskedLM.from_pretrained("camembert-base") +``` +{/if} + + +Quando usate un modello pre-addestrato, assicuratevi di controllare come è stato addestrato, su quali dataset, i suoi limiti e i suoi bias. Tutte queste informazioni dovrebbero essere indicate sul cartellino del modello. + diff --git a/chapters/it/chapter4/3.mdx b/chapters/it/chapter4/3.mdx new file mode 100644 index 000000000..de96f65ab --- /dev/null +++ b/chapters/it/chapter4/3.mdx @@ -0,0 +1,643 @@ + + +# Condividere modelli pre-addestrati + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Nei passi seguenti illustreremo i modi più semplici e veloci per condividere modelli pre-addestrati sull'🤗 Hub. Vedremo degli strumenti e delle utility che rendono semplice condividere e aggiornare modelli direttamente sull'🤗 Hub. + + + +Incoraggiamo tutti gli utenti che addestrano un modello a contribuire alla comunità condividendolo -- anche se i vostri modelli sono addestrati su dati molto specifici, possono comunque aiutare gli altri a risparmiare tempo e risorse computazionali. A vostra volta, potrete beneficiare del lavoro che gli altri hanno fatto! + +Ci sono tre modi per creare un nuovo repository di un modello: + +- Usando la funzione `push_to_hub` dell'API +- Usando la libreria Python `huggingface_hub` +- Usando l'interfaccia web + +Una volta che avrete creato un repository, potrete caricarvi i file attraverso git e git-lfs. Nelle sezioni seguenti vedremo in dettaglio come creare un repository e aggiungervi i file. + + +## Utilizzando la funzione `push_to_hub` dell'API + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Il modo più semplice di caricare file sull'Hub è attraverso la funzione `push_to_hub` dell'API. + +Prima di continuare sarà necessario generare un token di autenticazione così che l'API `huggingface_hub` sappia chi siete e a quali namespace avete accesso in scrittura. Assicuratevi di essere in un ambiente in cui la libreria `transformers` è installata (vedi [Installazione](/course/chapter0)). Se state utilizzando un notebook, potete usare la seguente funzione per effettuare il login: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +In una finestra del terminale, potete eseguire: + +```bash +huggingface-cli login +``` + +In entrambi i casi, vi verrà chiesto di inserire il vostro nome utente e la password, che sono gli stessi che utilizzate per accedere all'Hub. Se non avete ancora un profilo sull'Hub, potete crearne uno [qui](https://huggingface.co/join). + +Perfetto! Ora il token di autenticazione è salvato nella cartella di cache, e possiamo creare dei nuovi repository! + +{#if fw === 'pt'} + +Se avete usato la API `Trainer` per addestrare un modello, il modo più semplice per caricarlo sull'Hub è impostare il parametro `push_to_hub=True` quando definite i `TrainingArguments` (parametri di addestramento): + +```py +from transformers import TrainingArguments + +training_args = TrainingArguments( + "bert-finetuned-mrpc", save_strategy="epoch", push_to_hub=True +) +``` + +Invocando la funzione `trainer.train()`, l'oggetto `Trainer` caricherà il modello sull'Hub ad ogni salvataggio (nell'esempio dopo ogni epoca) all'interno di un repository nel vostro namespace. Il repository avrà come nome la stessa stringa che avete scelto come nome per la cartella di output (qui `bert-finetuned-mrpc`), ma è possibile scegliere un nome diverso impostando il parametro `hub_model_id = "un_nome_diverso"`. + +Volendo caricare il modello nello spazio di una organizzazione di cui si è membri, sarà sufficiente impstare il parametro `hub_model_id = "nome_organizzazione/nome_repository"`. + +Alla fine dell'addestramento, sarà necessario invocare per l'ultima volta la funzione `trainer.push_to_hub()` per caricare la versione definitiva del modello. Questa azione genererà automaticamente anche un cartellino del modello, con tutti i metadati rilevanti, riportando anche gli iper-parametri utilizzati e i risultati della valutazione finale. Questo è un esempio del contenuto di uno di questi cartellini: + +
+ An example of an auto-generated model card. +
+ +{:else} + +Se si sta addestrando il modello con Keras, il modo più semplice per caricarlo sull'Hub è passare come parametro una funzione `PushToHubCallback` quando si invoca la funzione `model.fit()`: + +```py +from transformers import PushToHubCallback + +callback = PushToHubCallback( + "bert-finetuned-mrpc", save_strategy="epoch", tokenizer=tokenizer +) +``` + +Dovrete poi impostare il parametro `callbacks=[callback]` nella invocazione della funzione `model.fit()`. Questa funzione callback caricherà il modello sull'Hub ad ogni salvataggio (nell'esempio dopo ogni epoca) all'interno di un repository nel vostro namespace. Il repository avrà come nome la stessa stringa che avete scelto come nome per la cartella di output (qui `bert-finetuned-mrpc`), ma è possibile scegliere un nome diverso impostando il parametro `hub_model_id = "un_nome_diverso"`. + +Volendo caricare il modello nello spazio di una organizzazione di cui si è membri, sarà sufficiente impstare il parametro `hub_model_id = "nome_organizzazione/nome_repository"`. + +{/if} + +In ogni caso, quando si lavora con modelli, tokenizers, e oggetti di configurazione, è comunque possibile accedere all'Hub dei modelli direttamente ulizzando il rispettivo methodo `push_to_hub()`. Questo metodo si occupa di creare il repository e caricarvi i file del modello e tokenizer. Non è necessario gestire manualmente questa operazione, a differenza dell'API che vedremo più avanti. + +Per farvi una idea di come funziona questo processo, inizializzate un modello e un tokenizer: + +{#if fw === 'pt'} +```py +from transformers import AutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = AutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` +{:else} +```py +from transformers import TFAutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = TFAutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` +{/if} + +A questo punto, siete liberi di fare quello che volete con questi oggetti - aggiungere token diversi al tokenizer, addestrare il modello, affinarlo. Quando siete soddisfatti con il modello, i pesi e il tokenizer ottenuti, potrete usare il methodo `push_to_hub()` direttamente disponibile sul oggetto `model`: + +```py +model.push_to_hub("dummy-model") +``` + +Questo genererà un nuovo repository `dummy-model` nel vostro profilo, e lo popolerà con i file del modello. +Ripetete la stessa operazione con il tokenizer, così tutti i file saranno disponibili nel repository: + +```py +tokenizer.push_to_hub("dummy-model") +``` + +Se siete affiliati con una organizzazione, basterà specificare il parametro `organization` per caricare i file nel namespace dell'organizzazione: + +```py +tokenizer.push_to_hub("dummy-model", organization="huggingface") +``` + +Se desiderate utilizzare uno specifico token di autenticazione di Hugging Face, è possibile specificarlo durante l'invocazione del metodo `push_to_hub()`: + +```py +tokenizer.push_to_hub("dummy-model", organization="huggingface", use_auth_token="") +``` + +Ora potete dirigervi alla pagina del Model Hub per trovare il vostro nuovo modello appena caricato: *https://huggingface.co/user-or-organization/dummy-model*. + +Cliccando sulla scheda "Files and versions" dovreste vedere la lista dei file caricati, come nell'immagine sottostante: + +{#if fw === 'pt'} +
+Dummy model containing both the tokenizer and model files. +
+{:else} +
+Dummy model containing both the tokenizer and model files. +
+{/if} + + + +✏️ **Prova tu!** Prendi il modello e il tokenizer associati con il cehckpoint `bert-base-cased` e caricali in un repository nel tuo namespace usando il metodo `push_to_hub()`. Verifica che il repository appaia correttamente sulla tua pagina prima di cancellarlo. + + + +Come avete visto, il metodo `push_to_hub()` accetta numerosi parametri, rendendo possible caricare i file su uno specifico repository o in un namespace di una organizzazione, o utilizzare un qualunque API token. Consigliamo di leggere la documentazione disponibile alla pagina [🤗 Transformers documentation](https://huggingface.co/transformers/model_sharing.html) per farsi una idea di tutte le possibilità offerte dal metodo. + +`push_to_hub()` è supportato dal package [`huggingface_hub`](https://github.com/huggingface/huggingface_hub) di Python, che offre una API diretta per interagire con l'Hub di Hugging Face. È integrato in 🤗 Transformers e in numerosi altre librirerie di machine learning, come [`allenlp`](https://github.com/allenai/allennlp). In questo capitolo ci siamo soffermati sull'integrazione con 🤗 Transformers, ma integrarlo nel proprio codice o libreria è semplice. + +Saltate all'ultima sezione per vedere come caricare i file nel repository appena creato! + +## Utilizzando la libreria Python `huggingface_hub` + +La libreria Python `huggingface_hub` offre una varietà di strumenti per interfacciarsi con gli hub di modelli e dataset. Fornisce delle classi e dei metodi semplici per operazioni comuni come +ottenere informazioni e gestire repository sull'hub. Fornisce anche delle semplici API che sfruttano git per gestire i contenuti dei repository e integrare l'Hub +nei propri prgetti e librerie. + +Come per la funzione `push_to_hub`, anche questo approccio richiede di avere un API token salvato nella propria cartella di cache. Per ottenerlo, sarà necessario usare il comando `login` dalla interfaccia da riga di comando (CLI), come indicato nella sezione precedente (assicuratevi di inserire il carattere `!` prima di questi comandi se li state eseguendo in Google Colab): + +```bash +huggingface-cli login +``` + +La libreria `huggingface_hub` offre molte classi e metodi utili al nostro scopo. In primo luogo, ci sono alcuni metodi per gestire operazioni quali creazione e cancellazione di repository: + +```python no-format +from huggingface_hub import ( + # User management + login, + logout, + whoami, + + # Repository creation and management + create_repo, + delete_repo, + update_repo_visibility, + + # And some methods to retrieve/change information about the content + list_models, + list_datasets, + list_metrics, + list_repo_files, + upload_file, + delete_file, +) +``` + + +È inoltre offerta una classe `Repository` molto completa per gestire un repository locale. Nelle seguenti sezioni li esploreremo e capiremo come utilizzarli. + +Il metodo `create_repo` può essere utilizzato per creare un nuovo repository sull'hub: + +```py +from huggingface_hub import create_repo + +create_repo("dummy-model") +``` + +Questo genererà un nuovo repository `dummy-model` nel vostro namespace. Potete anche specificare un'organizzazione a cui il repository dovrebbe appartenere utilizzando il parametro `organization`: + +```py +from huggingface_hub import create_repo + +create_repo("dummy-model", organization="huggingface") +``` + +Che genererà il repository `dummy_model` all'interno del namespace `huggingface`, assumendo che apperteniate a questa organizzazione. +Altri parametri che possono tornare utili sono: + +- `private`, che permette di specificare se il repository dovrebbe essere visibile da altri oppure no. +- `token`, che permette di specificare un token di autenticazione diverso da quello salvato nella propria cartella di cache. +- `repo_type`, che permette di creare un `dataset` o un `space` (spazio) invece di un modello. I valori accettati sono `"dataset"` e `"space"`. + +Una volta creato il repository, dovremo aggiungere file al suo interno! Saltate alla sezione successiva per vedere tre modi per farlo. + + +## Usando l'interfaccia web + +L'interfaccia we offre strumenti per gestire i repository direttamente sull'Hub. Usando questa interfaccia potrete facilmente creare repository, aggiungere file (anche grandi!), esplorare modelli, visualizzare differenze tra file, e molto altro. + +Per creare un nuovo repository visitate la pagina [huggingface.co/new](https://huggingface.co/new): + +
+Page showcasing the model used for the creation of a new model repository. +
+ +Per prima cosa sarà necessario specificare chi sia il proprietario del repository: questi potete essere voi, o qualunque delle organizzazioni a cui siete affiliati. Se scegliete un'organizzazione, il modello sarà presente sulla pagina dell'organizzazione e tutti i membri dell'organizzazione avranno la possibilità di contribuire al repository. + +Ora potete inserire il nome del vostro modello. Questo sarà anche il nome del repository. Infine, potete specificare se volete che il vostro modello sia pubblico o privato. I modelli privati sono nascosti al pubblico. + +Dopo aver creato il repository del vostro modello, dovreste vedere una pagina come questa: + +
+An empty model page after creating a new repository. +
+ +Qui è dove il vostro modello sarà reso disponibile. Per iniziare a popolarlo, potete aggiungere un file README direttamente dalla interfaccia web. + +
+The README file showing the Markdown capabilities. +
+ +Il file README è in formato Markdown — sentitevi liberi di sbizzarrirvi col README! La terza parte di questo capitolo è dedicata alla generazione del cartellino del modello. Questi cartellini sono estremamente importanti nel valorizzare il vostro modello, poiché è qui che potrete comunicare agli altri le potenzialità del vostro modello. + +Nella scheda "Files and versions" (File e versioni), vedrete che non ci sono ancora molti file — solo il *README.md* che avete appena creato e il file *.gitattributes* che tiene traccia dei file grandi. + +
+The 'Files and versions' tab only shows the .gitattributes and README.md files. +
+ +Vedremo ora come aggiungere nuovi file. + +## Caricare i file del modello + +Il sistema di gestione dei file sull'Hub di Hugging Face è basato su git per file normali, e su git-lfs ([Git Large File Storage](https://git-lfs.github.com/)) per file più grandi. + +Nella sezione seguente, illustreremo tre diversi modi per caricare file sull'Hub: attraverso `huggingface_hub` e attraverso comandi git. + +### Usando `upload_file` + +Caricare file utilizzando `upload_file` non richiede di avere git e git-lfs installati sul proprio sistema. Infatti questo metodo trasferisce i file sul 🤗 Hub attraverso richieste HTTP POST. Una limitazione di questo approccio è che non può gestire file di dimensioni più grandi di 5GB. +Se i vostri file sono più grandi di 5GB, seguite gli altri due metodi dettagliati sotto. + +La API può essere usata in questo modo: + +```py +from huggingface_hub import upload_file + +upload_file( + "/config.json", + path_in_repo="config.json", + repo_id="/dummy-model", +) +``` + +Questo caricherà il file `config.json`, locato in ``, nella cartella di base (root) del repository come `config.json`, nel repository `dummy-model`. +Altri parametri che possono essere utili sono: + +- `token`, che permette di utilizzare un token di autenticazione specifico invece di quello salvato nella vostra cartella di cache. +- `repo_type`, che permette di caricare un file in un `dataset` o uno `space` invece di un modello. Valori accettati sono `"dataset"` e `"space"`. + + +### La classe `Repository` + +La classe `Repository` gestisce un repository locale in un modo simile a git. Elimina la maggior parte della complessità che un utente potrebbe incontrare con git, per fornire tutte le funzionalità di cui abbiamo bisogno. + +Questa classe necessità di git e git-lfs, quindi assicuratevi di averli installati (vedere [qui](https://git-lfs.github.com/) per le istruzioni di installazione) e di averli configurati prima di iniziare. + +In order to start playing around with the repository we have just created, we can start by initialising it into a local folder by cloning the remote repository: +Per iniziare a sperimentare con il repository appena creato, possiamo iniziallizzare il repository in una cartella locale clonando il repository remoto: + +```py +from huggingface_hub import Repository + +repo = Repository("", clone_from="/dummy-model") +``` + +Questa azione crea la cartella `` nella cartella di lavoro corrente (working directory). Questa cartella contiene solo il file `.gitattributes` poichè quello è l'unico file che viene creato quando si istanzia un repository attraverso il metodo `create_repo`. + +Da questo punto possiamo usare molti dei metodi classici di git. + +```py +repo.git_pull() +repo.git_add() +repo.git_commit() +repo.git_push() +repo.git_tag() +``` + +E molti altri! Consigliamo di leggere la documentazione della classe `Repository` disponibile [qui](https://github.com/huggingface/huggingface_hub/tree/main/src/huggingface_hub#advanced-programmatic-repository-management) per una panoramica dei metodi disponibili. + +In questo momento abbiamo un modello e un tokenizer che vorremmo caricare sull'hub. Avendo correttamente clonato il repository, possiamo salvare i file al suo interno. + +Assicuriamoci prima che il nostro clone locale sia aggiornato scaricando (pulling) gli ultimi cambiamenti: + +```py +repo.git_pull() +``` + +Fatto questo, salviamo i file del modello e del tokenizer: + +```py +model.save_pretrained("") +tokenizer.save_pretrained("") +``` + +La cartella `` ora conteine tutti i file del modello e del tokenizer. Possiamo seguire la sequenza di operazioni (workflow) standard di git, aggiungendo file alla staging area, utilizzando git commit e git push per caricarli sull'hub: + +```py +repo.git_add() +repo.git_commit("Add model and tokenizer files") +repo.git_push() +``` + +Congratulazioni! Avete appena caricato i vostri primi file sull'hub. + +### L'approccio basato su git + +Questo è un approccio molto minimalista al caricamento dei file: utilizzeremo git e git-lfs direttamente. Gli approcci precedenti rimuovevano la maggior parte della complessità utilizzando astrazioni. Siccome ci sono alcune limitazioni con questo metodo, mostreremo un caso di utilizzo più complesso. + +Questo metodo richeide git e git-lfs, quindi assicuratevi di averli installati (vedere [qui](https://git-lfs.github.com/) per le istruzioni di installazione) e di averli configurati prima di iniziare. + +Per prima cosa inizializziamo git-lfs: + +```bash +git lfs install +``` + +```bash +Updated git hooks. +Git LFS initialized. +``` + +Fatto questo, il primo passo è clonare il repository del proprio modello: + +```bash +git clone https://huggingface.co// +``` + +Il mio nome utente è `lysandre` e ho usato il nome `dummy` per il modello, quindi per me il comando da eseguire diventa: + +``` +git clone https://huggingface.co/lysandre/dummy +``` + +Adesso ho una cartella chiamata *dummy* nella mia cartella di lavoro corrente (working directory). Posso spostarmi nella cartella usando `cd` ed esaminare i contenuti: + +```bash +cd dummy && ls +``` + +```bash +README.md +``` + +Se avete appena creato la repository usando il metodo `create_repo` di Hugging Face Hub, questa cartella dovrebbe contenere solo un file nascosto `.gitattributes`. Se avete seguito le istruzioni nella sezione precedente per creare una repository usando l'interfaccia web, la cartella dovrebbe contenere un singolo file *README.md* assieme al file nascosto `.gitattributes`, come mostrato qui. + +Per aggiungere un file di taglia regolare, come un file di configurazione, un file vocabolario, o in genere qualsiasi file di taglia inferiore a qualche megabyte, si procede nello stesso modo di un qualunque systema basato su git. Tuttavia, i file più grandi devono essere registrati con git-lfs per poter essere caricati su *huggingface.co*. + +Tornando a Python per un momento, generiamo un modello e un tokenizer che vorremmo caricare sul nostro repository dummy: + +{#if fw === 'pt'} +```py +from transformers import AutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = AutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + +# Do whatever with the model, train it, fine-tune it... + +model.save_pretrained("") +tokenizer.save_pretrained("") +``` +{:else} +```py +from transformers import TFAutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = TFAutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + +# Do whatever with the model, train it, fine-tune it... + +model.save_pretrained("") +tokenizer.save_pretrained("") +``` +{/if} + +Adesso che abbiamo salvato gli artefatti del modello e del tokenizer, esaminiamo la cartella *dummy*: + +```bash +ls +``` + +{#if fw === 'pt'} +```bash +config.json pytorch_model.bin README.md sentencepiece.bpe.model special_tokens_map.json tokenizer_config.json tokenizer.json +``` + +Guardando le dimensioni dei file (ad esempio con `ls -lh`), possiamo vedere che il file contenente lo stato del modello (model state dict file) (*pytorch_model.bin*) è l'unico file anomalo, occupando più di 400 MB. + +{:else} +```bash +config.json README.md sentencepiece.bpe.model special_tokens_map.json tf_model.h5 tokenizer_config.json tokenizer.json +``` + +Guardando le dimensioni dei file (ad esempio con `ls -lh`), possiamo vedere che il file contenente lo stato del modello (model state dict file) (*t5_model.h5*) è l'unico file anomalo, occupando più di 400 MB. + +{/if} + + +✏️ When creating the repository from the web interface, the *.gitattributes* file is automatically set up to consider files with certain extensions, such as *.bin* and *.h5*, as large files, and git-lfs will track them with no necessary setup on your side. +✏️ Creando il reposiotry dall'interfaccia web, il file *.gitattributes* viene automaticamente configurato per considerare file con alcune estensioni, come *.bin* e *.h5*, come file grandi, e git-lfs li traccerà senza necessità di configurazione da parte dell'utente. + + +Possiamo quindi procedere come faremo per un repository Git tradizionale. Possiamo aggiungere tutti i file all'ambiente di staging di Git con il comando `git add`: + +```bash +git add . +``` + +Possiamo quindi vedere i file che sono attualmente in staging: + +```bash +git status +``` + +{#if fw === 'pt'} +```bash +On branch main +Your branch is up to date with 'origin/main'. + +Changes to be committed: + (use "git restore --staged ..." to unstage) + modified: .gitattributes + new file: config.json + new file: pytorch_model.bin + new file: sentencepiece.bpe.model + new file: special_tokens_map.json + new file: tokenizer.json + new file: tokenizer_config.json +``` +{:else} +```bash +On branch main +Your branch is up to date with 'origin/main'. + +Changes to be committed: + (use "git restore --staged ..." to unstage) + modified: .gitattributes + new file: config.json + new file: sentencepiece.bpe.model + new file: special_tokens_map.json + new file: tf_model.h5 + new file: tokenizer.json + new file: tokenizer_config.json +``` +{/if} + +Allo stesso modo possiamo assucurarci che git-lfs stia tenendo traccia dei file giusti utilizzando il comando `status`: + +```bash +git lfs status +``` + +{#if fw === 'pt'} +```bash +On branch main +Objects to be pushed to origin/main: + + +Objects to be committed: + + config.json (Git: bc20ff2) + pytorch_model.bin (LFS: 35686c2) + sentencepiece.bpe.model (LFS: 988bc5a) + special_tokens_map.json (Git: cb23931) + tokenizer.json (Git: 851ff3e) + tokenizer_config.json (Git: f0f7783) + +Objects not staged for commit: + + +``` + +Possiamo notare che tutti i file hanno `Git` come gestore (handler), ad eccezione di *pytorch_model.bin* e *sentencepiece.bpe.model*, che invece hanno `LFS`. Perfetto! + +{:else} +```bash +On branch main +Objects to be pushed to origin/main: + + +Objects to be committed: + + config.json (Git: bc20ff2) + sentencepiece.bpe.model (LFS: 988bc5a) + special_tokens_map.json (Git: cb23931) + tf_model.h5 (LFS: 86fce29) + tokenizer.json (Git: 851ff3e) + tokenizer_config.json (Git: f0f7783) + +Objects not staged for commit: + + +``` + +Possiamo notare che tutti i file hanno `Git` come gestore (handler), ad eccezione di *t5_model.h5*, che invece ha `LFS`. Perfetto! + +{/if} + +Possiamo quindi procedere al passo finale, utilizzando i comandi commit e push per caricare i file sul repository remoto *huggingface.co*: + +```bash +git commit -m "First model version" +``` + +{#if fw === 'pt'} +```bash +[main b08aab1] First model version + 7 files changed, 29027 insertions(+) + 6 files changed, 36 insertions(+) + create mode 100644 config.json + create mode 100644 pytorch_model.bin + create mode 100644 sentencepiece.bpe.model + create mode 100644 special_tokens_map.json + create mode 100644 tokenizer.json + create mode 100644 tokenizer_config.json +``` +{:else} +```bash +[main b08aab1] First model version + 6 files changed, 36 insertions(+) + create mode 100644 config.json + create mode 100644 sentencepiece.bpe.model + create mode 100644 special_tokens_map.json + create mode 100644 tf_model.h5 + create mode 100644 tokenizer.json + create mode 100644 tokenizer_config.json +``` +{/if} + +L'operazione di push può richiedere un po' di tempo, a seconda della velocità della connessione a internet e della dimensione dei file: + +```bash +git push +``` + +```bash +Uploading LFS objects: 100% (1/1), 433 MB | 1.3 MB/s, done. +Enumerating objects: 11, done. +Counting objects: 100% (11/11), done. +Delta compression using up to 12 threads +Compressing objects: 100% (9/9), done. +Writing objects: 100% (9/9), 288.27 KiB | 6.27 MiB/s, done. +Total 9 (delta 1), reused 0 (delta 0), pack-reused 0 +To https://huggingface.co/lysandre/dummy + 891b41d..b08aab1 main -> main +``` + +{#if fw === 'pt'} +Alla fine di questa operazione, possiamo controllare il repository e vedere tutti i file aggiunti di recente: + +
+The 'Files and versions' tab now contains all the recently uploaded files. +
+ +L'interfaccia permette di esplorare i file e le commit, e visualizzare le differenze (file diff) introdotte da ogni commit: + +
+The diff introduced by the recent commit. +
+{:else} +Alla fine di questa operazione, possiamo controllare il repository e vedere tutti i file aggiunti di recente: + +
+The 'Files and versions' tab now contains all the recently uploaded files. +
+ +L'interfaccia permette di esplorare i file e le commit, e visualizzare le differenze (file diff) introdotte da ogni commit: + +
+The diff introduced by the recent commit. +
+{/if} diff --git a/chapters/it/chapter4/4.mdx b/chapters/it/chapter4/4.mdx new file mode 100644 index 000000000..1b00e6946 --- /dev/null +++ b/chapters/it/chapter4/4.mdx @@ -0,0 +1,83 @@ +# Generare un cartellino del modello + +Il cartellino del modello (model card) è un file di importanza pari ai file del modello e del tokenizer in un repository. Contiene la definizione del modello, assicurando la possibilità di riutilizzarlo e riprodurre i risultati da parte dei membri della comunità, e facendo si che il modello sia una piattaforma su cui gli altri membri possono costruire i loro artefatti. + +Documentare il processo di addestramento e valutazione aiuta gli altri a capire cosa aspettarsi dal modello — inoltre, fornire informazioni accurate sui dati utilizzati e sulle operazioni di pre e post elaborazione (preprocessing e postprocessing), assicura che si possano identificare e comprenere le limitazioni, i bias, e i contesti in cui il modello è utile, e quelli in cui non lo è. + +Per questo creare un cartellino del modello che descriva chiaramente il modello, è un passo estremamente importante. Qui, forniamo alcuni suggerimenti per farlo. Il cartellino del modello viene creato tramite il file *README.md*, visto in precedenza, che è un file Markdown. + +Il concetto del cartellino trae origine dalla ricerca svolta a Google, e pubblicata per la prima volta nell'articolo ["Model Cards for Model Reporting"](https://arxiv.org/abs/1810.03993) di Margaret Mitchell et al. Molte informazioni contenute qui sono basate su quell'artictolo, e raccomandiamo a tutti di leggerlo per capire l'importanza del cartellino del modello in un mondo che valorizza la reproduzione, la riutilizzabilità e l'equità. + +Il cartellino solitamente inizia con una breve introduzione, che descrive ad alto livello per quale scopo il modello è stato creato, ed è seguita da informazioni aggiuntive nelle sezioni seguenti: + +- Descrizione del modello +- Usi previsti e limitazioni +- Istruzioni d'uso +- Limitazioni e bias +- Dati di addestramento +- Procedura di addestramento +- Risultati della valutazione + +Approfondiamo ora i contenuti di ciascuna sezione. + +### Descrizione del modello + +La descrizione del modello fornisce i dettagli di base. Questi includono l'architettura, la versione, informazioni sull'articolo scientifico in cui il modello è stato presentato (se disponibile), se sia disponibile una implementazione originale, l'autore, ed altre informazioni di carattere generale. Qualsiasi copyright deve essere attribuito qui. Informazioni generali sulle procedure di addestramento, i parametri, ed anche dichiarazioni di non responsabilità possono essere inserite in questa sezione. + +### Usi previsti e limitazioni + +In questa sezione vengono descritti gli utilizzi per cui il modello è inteso, inclusi i linguaggi e i domini di applicazione del modello. Questa sezione del cartellino puó anche descrivere situazioni che sono fuori dall'ambito previsto del modello, o dove é probabile che il modello non funzioni in maniera ottimale. + +### Istruzioni d'uso + +Questa sezione dovrebbe includere alcuni esempi che mostrino come usare il modello. Questi esempi possono includere l'utilizzo attraverso la funzione `pipeline()`, l'utilizzo delle classi modello e tokenizer, e qualsiasi altro esempio di codice che possa essere utile. + +### Dati di addestramento + +Questa parte dovrebbe indicare su quali dataset il modello è stato addestrato. È anche consigliabile aggiungere una breve descrizione dei dataset. + +### Procedura di addestramento + +In questa sezione dovreste descrivere tutti i dettagli del processo di addestramento rilevanti dal punto di vista della riproducibilitá. + +### Variabili e metriche di valutazione + +In questa sezione é opportuno descrivere le metriche utilizzate per la valutazione e differenti fattori che vengono misurati. Riportare quali metriche ssono state usate, e su quali dataset e relative partizioni (dataset split), rende facile comparare le performance del proprio modello con gli altri. Le informazioni in questa sezione dovrebbero coprire i casi d'uso riportati nelle sezioni precedenti. + +### Risultati della valutazione + +Per finire, si dovrebbero riportare i risultati della valutazione di come si comporta il modello sul dataset di valutazione. Se il modello utilizza una soglia di decisione (decision threshold), è opportuno riportare o la soglia di decisione utilizzata nella fase di valutazione, o riportare i risultati per differenti soglie di decisione per gli usi previsti. + +## Esempio + +Consigliamo di guardare i seguenti esempi di cartellini ben curati: + +- [`bert-base-cased`](https://huggingface.co/bert-base-cased) +- [`gpt2`](https://huggingface.co/gpt2) +- [`distilbert`](https://huggingface.co/distilbert-base-uncased) + +Esempi aggiuntivi, da parte di altre organizzazioni e compagnie, sono disponibili [qui](https://github.com/huggingface/model_card/blob/master/examples.md). + +## Nota + +Includere il cartellino del modello non è un requisito obbligatorio durante la pubblicazione di un modello, e inoltre non è necessario includere tutte le sezioni elencate in precedenza quando si crea un cartellino. Tuttavia, una documentazione esplicita del modello può solo portare benefici agli utilizzatori futuri, e per questo raccomandiamo di compilare quante più sezioni possibili, al meglio delle proprie conoscenze e capacità. + +## Metadati del cartellino del modello + +Se avete esplorato l'Hugging Face Hub, potreste aver notato che alcuni modelli appartengono a determinate categorie: è possibile filtrarli per task, lingue, librerie, ecc. Le categorie a cui appartiene un modello sono identificate in base ai metadati aggiunti nell'intestazione (header) del cartellino. + +Prendendo ad esempio [il cartellino di `camembert-base`](https://huggingface.co/camembert-base/blob/main/README.md), dovreste vedere le seguenti righe nell'intestazione del cartellino: + +``` +--- +language: fr +license: mit +datasets: +- oscar +--- +``` + +This metadata is parsed by the Hugging Face Hub, which then identifies this model as being a French model, with an MIT license, trained on the Oscar dataset. +Questi metadati vengono elaborati dall'Hub di Hugging Face, che identifica questo modello come un modello Francese, con una licenza MIT, addestrato sul dataset Oscar. + +La [specifica completa dei cartellini](https://github.com/huggingface/hub-docs/blame/main/modelcard.md) permette di riportare lingue, license, tags, datasets, metriche di valutazione, e anche i risultati della valutazione del modello ottenuti durante l'addestramento. diff --git a/chapters/it/chapter4/5.mdx b/chapters/it/chapter4/5.mdx new file mode 100644 index 000000000..68112b891 --- /dev/null +++ b/chapters/it/chapter4/5.mdx @@ -0,0 +1,7 @@ +# Fine della parte 1! + +Questa è la dine della prima parte del corso! La seconda parte sarà rilasciata il 15 Novembre durante un grande evento della comunità. Per maggiori informazioni [vedere qui](https://huggingface.co/blog/course-launch-event). + +A questo punto dovreste essere in grado di affinare un modello pre-addestrato per un problema di classificazione tesutale (a frase signola o doppia), e caricare il risultato sull'Hub dei Modelli (Model Hub). Per assicurarvi di padroneggiare i contenuti di questa prima sezione, dovreste provare a fare esattamente questo su un problema che vi interessi (e non necessariamente in Inglese se parlate un'altra lingua)! Potete trovare aiuto nel [forum di Hugging Face](https://discuss.huggingface.co/), e quando avete finito potete condividere il vostro progetto in [questo topic](https://discuss.huggingface.co/t/share-your-projects/6803). + +Non vediamo l'ora di scoprire cosa costruirete! diff --git a/chapters/it/chapter4/6.mdx b/chapters/it/chapter4/6.mdx new file mode 100644 index 000000000..c31b9fb76 --- /dev/null +++ b/chapters/it/chapter4/6.mdx @@ -0,0 +1,223 @@ + + + + +# Quiz di fine capitolo + +Mettiamo alla prova quello che avete imparato in questo capitolo! + +### 1. Quali modelli si possono caricare sull'Hub? + + + +### 2. Come si gestisce un modello sull'Hub? + +git-lfs per i file di grandi dimensioni.", + correct: true + } + ]} +/> + +### 3. Cosa si può fare attraverso l'interfacca web di Hugging Face Hub? + + + +### 4. Cos'è il cartellino del modello? + + + +### 5. QUali di questi oggetti della libreria 🤗 Transformers può essere direttamente condiviso sull'Hub con `push_to_hub()`? + +{#if fw === 'pt'} +push_to_hub, che carica tutti i file del tokenizer (vocabolario, architettura del tokenizer, ecc.) su un repository specificatoo. Questa non è l'unica risposta giusta però!", + correct: true + }, + { + text: "La configurazione di un modello", + explain: "Vero! Gli oggetti di contennti la configurazione di tutti i modelli hanno il metodo push_to_hub, che li carica su un repository specificato. Cosa altro si può condividere?", + correct: true + }, + { + text: "Un modello", + explain: "Corretto! Tutti i modelli hanno il metodo push_to_hub, e utilizzandolo si possono caricare, insieme ai loro file di configurazione, su un repository specificato. Si possono condividere anche altre cose.", + correct: true + }, + { + text: "Un Trainer", + explain: "Giusto — l'oggetto Trainer implementa il metodo push_to_hub, e utilizzandolo, si possono caricare modello, configurazione, tokenizer, e cartellino su un repository specificato. Prova un'altra risposta!", + correct: true + } + ]} +/> +{:else} +push_to_hub, che carica tutti i file del tokenizer (vocabolario, architettura del tokenizer, ecc.) su un repository specificatoo. Questa non è l'unica risposta giusta però!", + correct: true + }, + { + text: "La configurazione di un modello", + explain: "Vero! Gli oggetti di contennti la configurazione di tutti i modelli hanno il metodo push_to_hub, che li carica su un repository specificato. Cosa altro si può condividere?", + correct: true + }, + { + text: "Un modello", + explain: "Corretto! Tutti i modelli hanno il metodo push_to_hub, e utilizzandolo si possono caricare, insieme ai loro file di configurazione, su un repository specificato. Si possono condividere anche altre cose.", + correct: true + }, + { + text: "Tutti i precedenti, usando una callback dedicata", + explain: "Giusto — la callback PushToHubCallback caricherà tutti questi oggetti su un repository regolarmente durante l'addestramento.", + correct: true + } + ]} +/> +{/if} + +### 6. Qual è il primo passo da fare quando si usano il metodo `push_to_hub()` o gli strumenti da riga di comando (CLI)? + + + +### 7. Se state usando un modello e un tokenizer — come li caricate sull'Hub? + +huggingface_hub.", + explain: "Modelli e tokenizer beneficiano già delle utilities di huggingface_hub: non c'è bisogno di wrapping addizionale!" + }, + { + text: "Salvandoli su disco e invocando il comando transformers-cli upload-model", + explain: "Il commando upload-model non esiste." + } + ]} +/> + +### 8. Quali operazioni di git si possono fare con la classe `Repository`? + +git_commit() è li per questo.", + correct: true + }, + { + text: "git pull", + explain: "Questa è la funzione del metodo git_pull().", + correct: true + }, + { + text: "git push", + explain: "Il metodo git_push() fa esattamente questo.", + correct: true + }, + { + text: "git merge", + explain: "No, questa operazione non è possibile con questa API." + } + ]} +/> diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index 7ba1a7cfa..3c5b11bea 100644 --- a/chapters/pt/_toctree.yml +++ b/chapters/pt/_toctree.yml @@ -56,6 +56,8 @@ title: Hora de fatiar e dividir os dados - local: chapter5/4 title: Big data? 🤗 Datasets ao resgate + - local: chapter5/5 + title: Criando seu próprio dataset - title: 7. Principais tarefas NLP sections: diff --git a/chapters/pt/chapter5/5.mdx b/chapters/pt/chapter5/5.mdx new file mode 100644 index 000000000..be0219602 --- /dev/null +++ b/chapters/pt/chapter5/5.mdx @@ -0,0 +1,471 @@ +# Criando seu próprio dataset + + + +Às vezes, o conjunto de dados de que você precisa para criar um aplicativo de PLN não existe, portanto, você mesmo precisará criá-lo. Nesta seção, mostraremos como criar um corpus de [issues do GitHub](https://github.com/features/issues/), que são comumente usados ​​para rastrear bugs ou recursos nos repositórios do GitHub. Este corpus pode ser usado para vários fins, incluindo: + +* Explorar quanto tempo leva para fechar as issues abertos ou pull requests +* Treinar um _classificador multilabel_ que pode marcar issues com metadados com base na descrição da issue (por exemplo, "bug", "melhoria" ou "pergunta") +* Criando um mecanismo de pesquisa semântica para descobrir quais issues correspondem à consulta de um usuário + +Aqui nos concentraremos na criação do corpus e, na próxima seção, abordaremos o aplicativo de pesquisa semântica. Para manter a meta, usaremos as issues do GitHub associados a um projeto de código aberto popular: 🤗 Datasets! Vamos dar uma olhada em como obter os dados e explorar as informações contidas nessas edições. + +## Obtendo os dados + +Você pode encontrar todos as issues em 🤗 Datasets navegando até a [guia de issues](https://github.com/huggingface/datasets/issues) do repositório. Conforme mostrado na captura de tela a seguir, no momento da redação, havia 331 issues abertos e 668 fechados. + +
+The GitHub issues associated with 🤗 Datasets. +
+ +Se você clicar em uma dessas issues, verá que ele contém um título, uma descrição e um conjunto de rótulos que caracterizam a issue. Um exemplo é mostrado na captura de tela abaixo. + +
+A typical GitHub issue in the 🤗 Datasets repository. +
+ +Para baixar todos as issues do repositório, usaremos a [GitHub REST API](https://docs.github.com/en/rest) para pesquisar o [`Issues` endpoint](https://docs.github. com/en/rest/reference/issues#list-repository-issues). Esse endpoint retorna uma lista de objetos JSON, com cada objeto contendo um grande número de campos que incluem o título e a descrição, bem como metadados sobre o status da issue e assim por diante. + +Uma maneira conveniente de baixar as issues é por meio da biblioteca `requests`, que é a maneira padrão de fazer solicitações HTTP em Python. Você pode instalar a biblioteca executando: + +```python +!pip install requests +``` + +Uma vez que a biblioteca esteja instalada, você pode fazer solicitações GET para o endpoint `Issues` invocando a função `requests.get()`. Por exemplo, você pode executar o seguinte comando para recuperar a primeira issue na primeira página: + +```py +import requests + +url = "https://api.github.com/repos/huggingface/datasets/issues?page=1&per_page=1" +response = requests.get(url) +``` + +O objeto `response` contém muitas informações úteis sobre a solicitação, incluindo o código de status HTTP: + +```py +response.status_code +``` + +```python out +200 +``` + +onde um status `200` significa que a solicitação foi bem-sucedida (você pode encontrar uma lista de possíveis códigos de status HTTP [aqui](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes)). O que realmente nos interessa, porém, é o _payload_, que pode ser acessado em vários formatos como bytes, strings ou JSON. Como sabemos que nossas issues estão no formato JSON, vamos inspecionar o payload da seguinte forma: + +```py +response.json() +``` + +```python out +[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/2792', + 'repository_url': 'https://api.github.com/repos/huggingface/datasets', + 'labels_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/labels{/name}', + 'comments_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/comments', + 'events_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/events', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792', + 'id': 968650274, + 'node_id': 'MDExOlB1bGxSZXF1ZXN0NzEwNzUyMjc0', + 'number': 2792, + 'title': 'Update GooAQ', + 'user': {'login': 'bhavitvyamalik', + 'id': 19718818, + 'node_id': 'MDQ6VXNlcjE5NzE4ODE4', + 'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4', + 'gravatar_id': '', + 'url': 'https://api.github.com/users/bhavitvyamalik', + 'html_url': 'https://github.com/bhavitvyamalik', + 'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers', + 'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}', + 'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}', + 'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}', + 'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions', + 'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs', + 'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos', + 'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}', + 'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events', + 'type': 'User', + 'site_admin': False}, + 'labels': [], + 'state': 'open', + 'locked': False, + 'assignee': None, + 'assignees': [], + 'milestone': None, + 'comments': 1, + 'created_at': '2021-08-12T11:40:18Z', + 'updated_at': '2021-08-12T12:31:17Z', + 'closed_at': None, + 'author_association': 'CONTRIBUTOR', + 'active_lock_reason': None, + 'pull_request': {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/2792', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792', + 'diff_url': 'https://github.com/huggingface/datasets/pull/2792.diff', + 'patch_url': 'https://github.com/huggingface/datasets/pull/2792.patch'}, + 'body': '[GooAQ](https://github.com/allenai/gooaq) dataset was recently updated after splits were added for the same. This PR contains new updated GooAQ with train/val/test splits and updated README as well.', + 'performed_via_github_app': None}] +``` + +Uau, é muita informação! Podemos ver campos úteis como `title`, `body` e `number` que descrevem a issue, bem como informações sobre o usuário do GitHub que abriu a issue. + + + +✏️ **Experimente!** Clique em alguns dos URLs na carga JSON acima para ter uma ideia de que tipo de informação cada issue do GitHub está vinculado. + + + +Conforme descrito na [documentação] do GitHub (https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting), as solicitações não autenticadas são limitadas a 60 solicitações por hora. Embora você possa aumentar o parâmetro de consulta `per_page` para reduzir o número de solicitações feitas, você ainda atingirá o limite de taxa em qualquer repositório que tenha mais do que alguns milhares de issues. Então, em vez disso, você deve seguir as [instruções] do GitHub (https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) sobre como criar um _token de acesso pessoal_ para que você pode aumentar o limite de taxa para 5.000 solicitações por hora. Depois de ter seu token, você pode incluí-lo como parte do cabeçalho da solicitação: + +```py +GITHUB_TOKEN = xxx # Copy your GitHub token here +headers = {"Authorization": f"token {GITHUB_TOKEN}"} +``` + + + +⚠️ Não compartilhe um notebook com seu `GITHUB_TOKEN` colado nele. Recomendamos que você exclua a última célula depois de executá-la para evitar o vazamento dessas informações acidentalmente. Melhor ainda, armazene o token em um arquivo *.env* e use a [`python-dotenv` library](https://github.com/theskumar/python-dotenv) para carregá-lo automaticamente para você como uma variável de ambiente. + + + +Agora que temos nosso token de acesso, vamos criar uma função que possa baixar todas as issues de um repositório do GitHub: + +```py +import time +import math +from pathlib import Path +import pandas as pd +from tqdm.notebook import tqdm + + +def fetch_issues( + owner="huggingface", + repo="datasets", + num_issues=10_000, + rate_limit=5_000, + issues_path=Path("."), +): + if not issues_path.is_dir(): + issues_path.mkdir(exist_ok=True) + + batch = [] + all_issues = [] + per_page = 100 # Number of issues to return per page + num_pages = math.ceil(num_issues / per_page) + base_url = "https://api.github.com/repos" + + for page in tqdm(range(num_pages)): + # Query with state=all to get both open and closed issues + query = f"issues?page={page}&per_page={per_page}&state=all" + issues = requests.get(f"{base_url}/{owner}/{repo}/{query}", headers=headers) + batch.extend(issues.json()) + + if len(batch) > rate_limit and len(all_issues) < num_issues: + all_issues.extend(batch) + batch = [] # Flush batch for next time period + print(f"Reached GitHub rate limit. Sleeping for one hour ...") + time.sleep(60 * 60 + 1) + + all_issues.extend(batch) + df = pd.DataFrame.from_records(all_issues) + df.to_json(f"{issues_path}/{repo}-issues.jsonl", orient="records", lines=True) + print( + f"Downloaded all the issues for {repo}! Dataset stored at {issues_path}/{repo}-issues.jsonl" + ) +``` + +Agora, quando chamamos `fetch_issues()`, ele fará o download de todas as issues em lotes para evitar exceder o limite do GitHub no número de solicitações por hora; o resultado será armazenado em um arquivo _repository_name-issues.jsonl_, onde cada linha é um objeto JSON que representa uma issue. Vamos usar esta função para pegar todas as issues de 🤗 Datasets: + +```py +# Depending on your internet connection, this can take several minutes to run... +fetch_issues() +``` + +Depois que as issues forem baixadas, podemos carregá-las localmente usando nossas novas habilidades da [seção 2](/course/chaper5/2): + +```py +issues_dataset = load_dataset("json", data_files="datasets-issues.jsonl", split="train") +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'timeline_url', 'performed_via_github_app'], + num_rows: 3019 +}) +``` + +Ótimo, criamos nosso primeiro conjunto de dados do zero! Mas por que existem vários milhares de issues quando a [guia Issue](https://github.com/huggingface/datasets/issues) do repositório 🤗 Datasets mostra apenas cerca de 1.000 issues no total 🤔? Conforme descrito na [documentação] do GitHub (https://docs.github.com/en/rest/reference/issues#list-issues-assigned-to-the-authenticated-user), isso ocorre porque baixamos todos os pull request também: + +> A API REST v3 do GitHub considera cada pull request como uma issue, mas nem toda issue é um pull request. Por esse motivo, os endpoints de "issues" podem retornar issues e solicitações de pull na resposta. Você pode identificar solicitações de pull pela chave `pull_request`. Esteja ciente de que o `id` de uma solicitação pull retornada de endpoints "issues" será um ID de issue. + +Como o conteúdo das issues e dos pull request são bem diferentes, vamos fazer um pequeno pré-processamento para nos permitir distinguir entre eles. + +## Limpando os dados + +O trecho acima da documentação do GitHub nos diz que a coluna `pull_request` pode ser usada para diferenciar entre issues e solicitações de pull request. Vamos olhar para uma amostra aleatória para ver qual é a diferença. Como fizemos na [seção 3](/course/chapter5/3), vamos encadear `Dataset.shuffle()` e `Dataset.select()` para criar uma amostra aleatória e então compactar o `html_url` e ` pull_request` para que possamos comparar os vários URLs: + + +```py +sample = issues_dataset.shuffle(seed=666).select(range(3)) + +# Print out the URL and pull request entries +for url, pr in zip(sample["html_url"], sample["pull_request"]): + print(f">> URL: {url}") + print(f">> Pull request: {pr}\n") +``` + +```python out +>> URL: https://github.com/huggingface/datasets/pull/850 +>> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/850', 'html_url': 'https://github.com/huggingface/datasets/pull/850', 'diff_url': 'https://github.com/huggingface/datasets/pull/850.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/850.patch'} + +>> URL: https://github.com/huggingface/datasets/issues/2773 +>> Pull request: None + +>> URL: https://github.com/huggingface/datasets/pull/783 +>> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/783', 'html_url': 'https://github.com/huggingface/datasets/pull/783', 'diff_url': 'https://github.com/huggingface/datasets/pull/783.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/783.patch'} +``` + +Aqui podemos ver que cada pull request está associado a vários URLs, enquanto as issues comuns têm uma entrada `None`. Podemos usar essa distinção para criar uma nova coluna `is_pull_request` que verifica se o campo `pull_request` é `None` ou não: + +```py +issues_dataset = issues_dataset.map( + lambda x: {"is_pull_request": False if x["pull_request"] is None else True} +) +``` + + + +✏️ **Experimente!** Calcule o tempo médio que leva para fechar as issues em 🤗 Datasets. Você pode achar a função `Dataset.filter()` útil para filtrar os pull requests e as issues abertas, e você pode usar a função `Dataset.set_format()` para converter o conjunto de dados em um `DataFrame` para que você possa manipular facilmente os timestamps `created_at` e `closed_at`. Para pontos de bônus, calcule o tempo médio que leva para fechar os pull requests. + + + +Embora possamos continuar a limpar o conjunto de dados descartando ou renomeando algumas colunas, geralmente é uma boa prática manter o conjunto de dados o mais "bruto" possível neste estágio para que possa ser facilmente usado em vários aplicativos. + +Antes de enviarmos nosso conjunto de dados para o Hugging Face Hub, vamos lidar com uma coisa que está faltando: os comentários associados a cada issue e pull request. Vamos adicioná-los a seguir - você adivinhou - a API REST do GitHub! + +## Aumentando o conjunto de dados + +Conforme mostrado na captura de tela a seguir, os comentários associados a uma issue ou a pull request fornecem uma rica fonte de informações, especialmente se estivermos interessados ​​em criar um mecanismo de pesquisa para responder às consultas dos usuários sobre a biblioteca. + +
+Comments associated with an issue about 🤗 Datasets. +
+ +A API REST do GitHub fornece um [endpoint `Comments`](https://docs.github.com/en/rest/reference/issues#list-issue-comments) que retorna todos os comentários associados a uma issue. Vamos testar o endpoint para ver o que ele retorna: + +```py +issue_number = 2792 +url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments" +response = requests.get(url, headers=headers) +response.json() +``` + +```python out +[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/comments/897594128', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792#issuecomment-897594128', + 'issue_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792', + 'id': 897594128, + 'node_id': 'IC_kwDODunzps41gDMQ', + 'user': {'login': 'bhavitvyamalik', + 'id': 19718818, + 'node_id': 'MDQ6VXNlcjE5NzE4ODE4', + 'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4', + 'gravatar_id': '', + 'url': 'https://api.github.com/users/bhavitvyamalik', + 'html_url': 'https://github.com/bhavitvyamalik', + 'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers', + 'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}', + 'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}', + 'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}', + 'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions', + 'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs', + 'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos', + 'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}', + 'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events', + 'type': 'User', + 'site_admin': False}, + 'created_at': '2021-08-12T12:21:52Z', + 'updated_at': '2021-08-12T12:31:17Z', + 'author_association': 'CONTRIBUTOR', + 'body': "@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?", + 'performed_via_github_app': None}] +``` + +Podemos ver que o comentário está armazenado no campo `body`, então vamos escrever uma função simples que retorna todos os comentários associados a uma issue selecionando o conteúdo do `body` para cada elemento em `response.json()`: + +```py +def get_comments(issue_number): + url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments" + response = requests.get(url, headers=headers) + return [r["body"] for r in response.json()] + + +# Test our function works as expected +get_comments(2792) +``` + +```python out +["@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?"] +``` + +Isso parece certo, então vamos usar `Dataset.map()` para adicionar uma nova coluna `comments` para cada issue em nosso conjunto de dados: + +```py +# Depending on your internet connection, this can take a few minutes... +issues_with_comments_dataset = issues_dataset.map( + lambda x: {"comments": get_comments(x["number"])} +) +``` + +A etapa final é salvar o conjunto de dados aumentado junto com nossos dados brutos para que possamos enviá-los para o Hub: + +```py +issues_with_comments_dataset.to_json("issues-datasets-with-comments.jsonl") +``` + +## Carregando o conjunto de dados para o Hugging Face Hub + + + +Agora que temos nosso conjunto de dados aumentado, é hora de enviá-lo para o Hub para que possamos compartilhá-lo com a comunidade! Para fazer o upload do conjunto de dados, usaremos a [🤗 Hub library](https://github.com/huggingface/huggingface_hub), que nos permite interagir com o Hugging Face Hub por meio de uma API Python. 🤗 Hub vem pré-instalado com 🤗 Transformers, para que possamos usá-lo diretamente. Por exemplo, podemos usar a função `list_datasets()` para obter informações sobre todos os conjuntos de dados públicos atualmente hospedados no Hub: + +```py +from huggingface_hub import list_datasets + +all_datasets = list_datasets() +print(f"Number of datasets on Hub: {len(all_datasets)}") +print(all_datasets[0]) +``` + +```python out +Number of datasets on Hub: 1487 +Dataset Name: acronym_identification, Tags: ['annotations_creators:expert-generated', 'language_creators:found', 'languages:en', 'licenses:mit', 'multilinguality:monolingual', 'size_categories:10K + +✏️ **Experimente!** Use seu nome de usuário e senha do Hugging Face Hub para obter um token e criar um repositório vazio chamado `github-issues`. Lembre-se de **nunca salvar suas credenciais** no Colab ou em qualquer outro repositório, pois essas informações podem ser exploradas por agentes mal-intencionados. + + + +Em seguida, vamos clonar o repositório do Hub para nossa máquina local e copiar nosso arquivo de conjunto de dados para ele. O 🤗 Hub fornece uma classe `Repository` útil que envolve muitos dos comandos comuns do Git, portanto, para clonar o repositório remoto, basta fornecer o URL e o caminho local para o qual desejamos clonar: + + +```py +from huggingface_hub import Repository + +repo = Repository(local_dir="github-issues", clone_from=repo_url) +!cp datasets-issues-with-comments.jsonl github-issues/ +``` + +Por padrão, várias extensões de arquivo (como *.bin*, *.gz* e *.zip*) são rastreadas com o Git LFS para que arquivos grandes possam ser versionados no mesmo fluxo de trabalho do Git. Você pode encontrar uma lista de extensões de arquivos rastreados dentro do arquivo *.gitattributes* do repositório. Para incluir o formato JSON Lines na lista, podemos executar o seguinte comando: + +```py +repo.lfs_track("*.jsonl") +``` + +Então podemos usar `Repository.push_to_hub()` para enviar o conjunto de dados para o Hub: + +```py +repo.push_to_hub() +``` + +Se navegarmos para a URL contida em `repo_url`, veremos agora que nosso arquivo de conjunto de dados foi carregado. + +
+Our dataset repository on the Hugging Face Hub. +
+ +A partir daqui, qualquer um pode baixar o conjunto de dados simplesmente fornecendo `load_dataset()` com o ID do repositório como o argumento `path`: + +```py +remote_dataset = load_dataset("lewtun/github-issues", split="train") +remote_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 2855 +}) +``` + +Legal, nós enviamos nosso conjunto de dados para o Hub e está disponível para outros usarem! Há apenas uma coisa importante a fazer: adicionar um _cartão de conjunto de dados_ que explica como o corpus foi criado e fornece outras informações úteis para a comunidade. + + + +💡 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. + + + +## Criando um cartão do datasets + +Conjuntos de dados bem documentados são mais propensos a serem úteis para outras pessoas (incluindo você mesmo no futuro!), pois fornecem o contexto para permitir que os usuários decidam se o conjunto de dados é relevante para sua tarefa e avaliem possíveis vieses ou riscos associados ao uso o conjunto de dados. + +No Hugging Face Hub, essas informações são armazenadas no arquivo *README.md* de cada repositório de conjunto de dados. Há duas etapas principais que você deve seguir antes de criar este arquivo: + +1. Use a aplicação [`datasets-tagging`](https://huggingface.co/datasets/tagging/) para criar tags de metadados no formato YAML. Essas tags são usadas para uma variedade de recursos de pesquisa no Hugging Face Hub e garantem que seu conjunto de dados possa ser facilmente encontrado pelos membros da comunidade. Como criamos um conjunto de dados personalizado aqui, você precisará clonar o repositório `datasets-tagging` e executar o aplicativo localmente. Veja como é a interface: + +
+The `datasets-tagging` interface. +
+ +2. Leia o [guia do 🤗 datasets](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) sobre como criar cartões informativos de conjuntos de dados e use-os como modelo. + +Você pode criar o arquivo *README.md* diretamente no Hub e encontrar um cartão de conjunto de dados de modelo no repositório de conjunto de dados `lewtun/github-issues`. Uma captura de tela do cartão de conjunto de dados preenchido é mostrada abaixo. + +
+A dataset card. +
+ + + +✏️ **Experimente!** Use o aplicativo `dataset-tagging` e [guia do 🤗 datasets](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) para concluir o *Arquivo README.md* para o conjunto de dados de issues do GitHub. + + + +É isso! Vimos nesta seção que criar um bom conjunto de dados pode ser bastante complicado, mas felizmente carregá-lo e compartilhá-lo com a comunidade não é. Na próxima seção, usaremos nosso novo conjunto de dados para criar um mecanismo de pesquisa semântica com o 🤗 datasets que podem corresponder perguntas as issues e comentários mais relevantes. + + + +✏️ **Experimente!** Siga as etapas que seguimos nesta seção para criar um conjunto de dados de issues do GitHub para sua biblioteca de código aberto favorita (escolha algo diferente do 🤗 datasets, é claro!). Para pontos de bônus, ajuste um classificador multilabel para prever as tags presentes no campo `labels`. + + + + diff --git a/chapters/ru/_toctree.yml b/chapters/ru/_toctree.yml index 00b7f82bb..f3a4eaf6a 100644 --- a/chapters/ru/_toctree.yml +++ b/chapters/ru/_toctree.yml @@ -24,12 +24,16 @@ - local: chapter1/9 title: Итоги - - title: 2. Использование библиотеки 🤗 Transformers sections: - local: chapter2/1 title: Введение - + - local: chapter2/2 + title: Внутри конвейера + - local: chapter2/3 + title: Модели + - local: chapter2/7 + title: Базовое использование завершено! - title: 3. Fine-tuning предобученной модели sections: @@ -40,4 +44,4 @@ - local: chapter3/3 title: Fine-tuning модели с использованием Trainer API - local: chapter3/4 - title: Полное обучение модели + title: Полное обучение модели \ No newline at end of file diff --git a/chapters/ru/chapter2/2.mdx b/chapters/ru/chapter2/2.mdx new file mode 100644 index 000000000..85ce9cbd5 --- /dev/null +++ b/chapters/ru/chapter2/2.mdx @@ -0,0 +1,354 @@ + + +# Внутри конвейера + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + +Это первый раздел, содержание которого будет немного отличаться в зависимости от того, используете ли вы PyTorch или TensorFlow. Нажмите переключатель над заголовком, чтобы выбрать предпочитаемую платформу! + + +{#if fw === 'pt'} + +{:else} + +{/if} + +Давайте начнем с готового примера, взглянув на то, что происходило за кулисами, когда мы выполняли следующий код в [Главе 1](/course/chapter1): + +```python +from transformers import pipeline + +classifier = pipeline("sentiment-analysis") +classifier( + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] +) +``` + +и на выходе получали: + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}, + {'label': 'NEGATIVE', 'score': 0.9994558095932007}] +``` + +Как мы уже увидели в [Главе 1](/course/chapter1), данный конвейер включает в себя три шага: предварительная обработка, передача входных данных через модель и постобработка: + +
+Полный конвейер NLP: токенизация текста, преобразование в идентификаторы и вывод с помощью модели Transformer и слоя 'head' модели. + +
+ +Давайте вкратце рассмотрим каждый из этих этапов. + +## Предварительная обработка с помощью токенизатора + +Как и другие нейронные сети, модели Transformer не могут обрабатывать необработанный текст напрямую, поэтому первым шагом нашего конвейера является преобразование входных текстовых данных в числа, понятные модели. Для этого мы используем *токенизатор*, который будет отвечать за: + +- Разделение входных данных на слова, подслова или символы (например, знаки препинания), которые называются *токенами* +- Отображение каждого токена в целое число +- Добавление дополнительных входных данных, которые могут быть полезны для модели + +Всю эту предварительную обработку необходимо выполнять точно так же, как и при предварительном обучении модели, поэтому сначала нам нужно загрузить эту информацию из [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)), мы выполним следующие команды: + +```python +from transformers import AutoTokenizer + +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. + + +Чтобы указать тип тензоров, которые мы хотим получить (PyTorch, TensorFlow, или обычный NumPy), мы используем аргумент `return_tensors`: + +{#if fw === 'pt'} +```python +raw_inputs = [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", +] +inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="pt") +print(inputs) +``` +{:else} +```python +raw_inputs = [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", +] +inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="tf") +print(inputs) +``` +{/if} + +Не беспокойтесь пока о параметрах дополнения (padding) и усечения (truncation); мы объясним это позже. Здесь главное помнить, что вы можете передать одно предложение или список предложений, а также указать тип тензоров, которые вы хотите получить обратно (если тип не передан, в результате вы получите список из списков). + +{#if fw === 'pt'} + +Вот как результаты выглядят в виде тензоров PyTorch: + +```python out +{ + 'input_ids': tensor([ + [ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012, 102], + [ 101, 1045, 5223, 2023, 2061, 2172, 999, 102, 0, 0, 0, 0, 0, 0, 0, 0] + ]), + '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] + ]) +} +``` +{:else} + +Вот как результаты выглядят в виде тензоров TensorFlow: + +```python out +{ + 'input_ids': , + 'attention_mask': +} +``` +{/if} + +Сам вывод представляет собой словарь, содержащий два ключа, `input_ids` и `attention_mask`. `input_ids` содержит две строки целых чисел (по одному для каждого предложения), которые являются уникальными идентификаторами токенов в каждом предложении. Мы объясним, что такое `attention_mask` позже в этой главе. + +## Проходим через модель + +{#if fw === 'pt'} +Мы можем загрузить нашу предварительно обученную модель так же, как и наш токенизатор. Библиотека 🤗 Transformers предоставляет класс `AutoModel` который также имеет метод `from_pretrained()`: + +```python +from transformers import AutoModel + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = AutoModel.from_pretrained(checkpoint) +``` +{:else} +Мы можем загрузить нашу предварительно обученную модель так же, как и наш токенизатор. Библиотека 🤗 Transformers предоставляет класс `TFAutoModel` который также имеет метод `from_pretrained`: + +```python +from transformers import TFAutoModel + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = TFAutoModel.from_pretrained(checkpoint) +``` +{/if} + +В этом фрагменте кода мы загрузили ту же контрольную точку, которую использовали в нашем конвейере ранее (на самом деле она уже должна была быть закэширована) и создали с ее помощью экземпляр модели. + +Эта архитектура содержит только базовый модуль Transformer: при некоторых входных данных он выводит то, что мы будем называть *скрытыми состояниями*, также известными как *параметры*. Для каждого входного набора данных модели мы получим многомерный вектор, представляющий **контекстное понимание этого входного набора моделью Transformer**. + +Если вы пока не понимаете в чем смысл, не беспокойтесь об этом. Мы объясним все это позже. + +Хотя эти скрытые состояния могут быть полезны сами по себе, они обычно являются входными данными для другой части модели, известной как слой *head*. В [Главе 1](/course/chapter1) разные задачи могли бы выполняться с одной и той же архитектурой, но с каждой из этих задач будет связан отдельный слой "head". + +### Многомерный вектор, что это? + +Вектор, выводимый модулем Transformer, обычно является большим. И как правило, он имеет три параметра: + +- **Размер пакета**: Количество последовательностей, обрабатываемых одновременно (в нашем примере 2). +- **Длина последовательности**: Длина числового представления последовательности (в нашем примере 16). +- **Размер скрытого слоя сети**: Количество измерений вектора каждого входного параметра модели. + +Его называют "многомерный" из-за последнего значения. Размер скрытого слоя сети может быть очень большим (768 обычно используется для небольших моделей, а в больших моделях он может достигать 3072 или более). + +Мы можем увидеть это, если передадим входные данные, которые мы предварительно обработали, в нашу модель: + +{#if fw === 'pt'} +```python +outputs = model(**inputs) +print(outputs.last_hidden_state.shape) +``` + +```python out +torch.Size([2, 16, 768]) +``` +{:else} +```py +outputs = model(inputs) +print(outputs.last_hidden_state.shape) +``` + +```python out +(2, 16, 768) +``` +{/if} + +Обратите внимание, что выходные данные моделей 🤗 Transformers ведут себя как именованные кортежи или словари. Вы можете получить доступ к элементам по атрибутам (как это сделали мы) или по ключу (`outputs["last_hidden_state"]`), или даже по индексу, если вы точно знаете, где находится то, что вы ищете (`outputs[0]`). + +### Слои "head" модели: Разбираемся в цифрах + +Слои "head" модели принимают многомерный вектор скрытых состояний в качестве входных данных и проецируют их в другое измерение. Они обычно состоят из одного или нескольких линейных слоев: + +
+Нейронная сеть Transformer перед слоем 'head'. + +
+ +Выходные данные модели Transformer отправляются непосредственно в слой "head" модели для обработки. + +На данной диаграмме модель представлена слоем вложений и последующими слоями. Слой вложений преобразует каждый входной идентификатор полученный токенизатором, в вектор, который представляет собой связанный с ним токен. Последующие слои манипулируют этими векторами, используя механизм внимания, чтобы получить окончательное представление предложений. + +В 🤗 Transformersдоступно множество различных архитектур, каждая из которых предназначена для решения конкретной задачи. Вот неполный список: + +- `*Model` (извлекает скрытые состояния) +- `*ForCausalLM` +- `*ForMaskedLM` +- `*ForMultipleChoice` +- `*ForQuestionAnswering` +- `*ForSequenceClassification` +- `*ForTokenClassification` +- и другие 🤗 + +{#if fw === 'pt'} +Для нашего примера нам понадобится модель со слоем "head" для классификации последовательностей (чтобы иметь возможность классифицировать предложения как положительные или отрицательные). Итак, на самом деле мы будем использовать не класс `AutoModel`, а `AutoModelForSequenceClassification`: + +```python +from transformers import AutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +outputs = model(**inputs) +``` +{:else} +Для нашего примера нам понадобится модель со слоем "head" для классификации последовательностей (чтобы иметь возможность классифицировать предложения как положительные или отрицательные). Итак, на самом деле мы будем использовать не класс `TFAutoModel`, а `TFAutoModelForSequenceClassification`: + +```python +from transformers import TFAutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +outputs = model(inputs) +``` +{/if} + +Теперь, если мы посмотрим на форму наших входных данных, размерность будет намного ниже: слой "head" модели принимает в качестве входных данных многомерные векторы, которые мы видели ранее, и выводит векторы, содержащие всего два значения (по одному на метку): + +```python +print(outputs.logits.shape) +``` + +{#if fw === 'pt'} +```python out +torch.Size([2, 2]) +``` +{:else} +```python out +(2, 2) +``` +{/if} + +Поскольку у нас всего два предложения и две метки, результат, который мы получаем от нашей модели, имеет форму 2 x 2. + +## Постобработка выходных данных + +Значения, которые мы получаем в качестве выходных данных нашей модели, не обязательно имеют смысл сами по себе. Давайте посмотрим: + +```python +print(outputs.logits) +``` + +{#if fw === 'pt'} +```python out +tensor([[-1.5607, 1.6123], + [ 4.1692, -3.3464]], grad_fn=) +``` +{:else} +```python out + +``` +{/if} + +Наша модель предсказала `[-1.5607, 1.6123]` для первого предложения и `[ 4.1692, -3.3464]` для второго. Это не вероятности, а *логиты*, необработанные, ненормализованные оценки, выводимые последним слоем модели. Для преобразования в вероятности, они должны пройти через слой [SoftMax](https://en.wikipedia.org/wiki/Softmax_function) (все модели 🤗 Transformers выводят логиты, поскольку функция потерь для обучения обычно объединяет последнюю функцию активации, такую как SoftMax, с фактической функцией потерь, такой как перекрестная энтропия): + +{#if fw === 'pt'} +```py +import torch + +predictions = torch.nn.functional.softmax(outputs.logits, dim=-1) +print(predictions) +``` +{:else} +```py +import tensorflow as tf + +predictions = tf.math.softmax(outputs.logits, axis=-1) +print(predictions) +``` +{/if} + +{#if fw === 'pt'} +```python out +tensor([[4.0195e-02, 9.5980e-01], + [9.9946e-01, 5.4418e-04]], grad_fn=) +``` +{:else} +```python out +tf.Tensor( +[[4.01951671e-02 9.59804833e-01] + [9.9945587e-01 5.4418424e-04]], shape=(2, 2), dtype=float32) +``` +{/if} + +Теперь мы видим, что модель предсказала `[0.0402, 0.9598]` для первого предложения и `[0.9995, 0.0005]` для второго. Это легко узнаваемые оценки вероятности. + +Чтобы получить метки, соответствующие каждой позиции, мы можем проверить атрибут `id2label` в конфигурации модели (подробнее об этом в следующем разделе): + +```python +model.config.id2label +``` + +```python out +{0: 'NEGATIVE', 1: 'POSITIVE'} +``` + +Теперь мы можем сделать вывод, что модель предсказала следующее: + +- Первое предложение: NEGATIVE: 0.0402, POSITIVE: 0.9598 +- Второе предложение: NEGATIVE: 0.9995, POSITIVE: 0.0005 + +Мы успешно воспроизвели три этапа конвейера: предварительную обработку с помощью токенизаторов, передачу входных данных через модель и постобработку! Теперь давайте уделим некоторое время тому, чтобы углубиться в каждый из этих шагов. + + + +✏️ **Попробуйте это сделать!** Выберите два (или более) собственных текста и пропустите их через конвейер `sentiment-analysis`. Затем повторите шаги, которые вы видели здесь, и убедитесь, что вы получаете такие же результаты! + + diff --git a/chapters/ru/chapter2/3.mdx b/chapters/ru/chapter2/3.mdx new file mode 100644 index 000000000..080fd385f --- /dev/null +++ b/chapters/ru/chapter2/3.mdx @@ -0,0 +1,227 @@ + + +# Модели + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + +{:else} + +{/if} + +{#if fw === 'pt'} +В этом разделе мы подробнее рассмотрим создание и использование модели. Мы будем использовать класс `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` и все его родственники на самом деле являются простыми оболочками для большого количества моделей, доступных в библиотеке. Это умная оболочка, поскольку она может автоматически угадывать подходящую архитектуру модели для вашей контрольной точки, а затем создает экземпляр модели с этой архитектурой. + +{/if} + +Однако, если вы знаете тип модели, которую хотите использовать, вы можете использовать класс, который напрямую определяет ее архитектуру. Давайте посмотрим, как это работает с моделью BERT. + +## Создание модели Transformer + +Первое, что нам нужно будет сделать для инициализации модели 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) +``` + +```python out +BertConfig { + [...] + "hidden_size": 768, + "intermediate_size": 3072, + "max_position_embeddings": 512, + "num_attention_heads": 12, + "num_hidden_layers": 12, + [...] +} +``` + +Хотя вы еще не видели, что делают все эти атрибуты, вы должны узнать некоторые из них: атрибут `hidden_size` определяет размер вектора `hidden_states`, а `num_hidden_layers` определяет количество слоев, которые имеет модель. + +### Различные способы загрузки + +Создание модели из конфигурации по умолчанию инициализирует ее случайными значениями: + +{#if fw === 'pt'} +```py +from transformers import BertConfig, BertModel + +config = BertConfig() +model = BertModel(config) + +# Модель инициализируется случайным образом! +``` +{:else} +```py +from transformers import BertConfig, TFBertModel + +config = BertConfig() +model = TFBertModel(config) + +# Модель инициализируется случайным образом! +``` +{/if} + +Модель можно использовать в этом состоянии, но она будет выводить тарабарщину; сначала ее нужно обучить. Мы могли бы обучить модель с нуля для решения поставленной задачи, но, как вы видели в [Главе 1](/course/chapter1), это потребовало бы много времени и большого количества данных, а также имело бы значительное воздействие на окружающую среду. Чтобы избежать ненужных и дублирующих усилий, крайне важно иметь возможность делиться и повторно использовать модели, которые уже были обучены. + +Загрузить уже обученную модель Transformer очень просто — мы можем сделать это с помощью метода `from_pretrained()`: + +{#if fw === 'pt'} +```py +from transformers import BertModel + +model = BertModel.from_pretrained("bert-base-cased") +``` + +Как вы видели ранее, мы могли бы заменить `BertModel` эквивалентным классом `AutoModel`. С этого момента мы начнем делать так, поскольку таким образом создается код, не зависящий от контрольных точек; если ваш код работает для одной контрольной точки, он должен беспрепятственно работать с другой. Это применимо, даже если архитектура отличается, при условии, что контрольная точка была обучена для аналогичной задачи (например, задачи анализа тональности). + +{:else} +```py +from transformers import TFBertModel + +model = TFBertModel.from_pretrained("bert-base-cased") +``` + +Как вы видели ранее, мы могли бы заменить `TFBertModel` эквивалентным классом `TFAutoModel`. С этого момента мы начнем делать так, поскольку таким образом создается код, не зависящий от контрольных точек; если ваш код работает для одной контрольной точки, он должен беспрепятственно работать с другой. Это применимо, даже если архитектура отличается, при условии, что контрольная точка была обучена для аналогичной задачи (например, задачи анализа тональности). + +{/if} + +В приведенном выше примере кода мы не использовали `BertConfig`, а вместо этого загрузили предварительно обученную модель с помощью идентификатора `bert-base-cased`. Это контрольная точка модели, которую обучили сами авторы BERT; вы можете найти более подробную информацию о ней в её [карточке модели](https://huggingface.co/bert-base-cased). + +Теперь эта модель инициализирована со всеми весами контрольной точки. Её можно использовать непосредственно для логического вывода на задачах, для которых она обучалась, а также её можно точно донастроить для новой задачи. Тренируясь с предварительно обученными весами, а не с нуля, мы можем быстро добиться хороших результатов. + +Веса будут загружены и кэшированы (поэтому будущие вызовы метода `from_pretrained()` не будут загружать их повторно) в папку кеша, которая по умолчанию находится в *~/.cache/huggingface/transformers*. Вы можете настроить папку кэша, установив переменную среды `HF_HOME`. + +Идентификатор, используемый для загрузки модели, может быть идентификатором любой модели в Model Hub, если он совместим с архитектурой BERT. Полный список доступных контрольных точек моделей BERT можно найти [здесь](https://huggingface.co/models?filter=bert). + +### Способы сохранения + +Сохранить модель так же просто, как и загрузить - для этого мы используем метод `save_pretrained()`, аналогичный методу `from_pretrained()`: + +```py +model.save_pretrained("directory_on_my_computer") +``` + +Данный код сохранит два файла на вашем диске: + +{#if fw === 'pt'} +``` +ls directory_on_my_computer + +config.json pytorch_model.bin +``` +{:else} +``` +ls directory_on_my_computer + +config.json tf_model.h5 +``` +{/if} + +Если вы посмотрите на файл *config.json*, вы увидите атрибуты, необходимые для построения архитектуры модели. Этот файл также содержит некоторые метаданные, например, откуда появилась контрольная точка и какую версию 🤗 Transformers вы использовали, когда в последний раз сохраняли контрольную точку. + +{#if fw === 'pt'} +Файл *pytorch_model.bin* известен как *словарь состояний*; он содержит все веса вашей модели. Эти два файла идут рука об руку; конфигурация необходима, чтобы знать архитектуру вашей модели, в то время как веса модели являются параметрами вашей модели. + +{:else} +Файл *tf_model.h5* известен как *словарь состояний*; он содержит все веса вашей модели. Эти два файла идут рука об руку; конфигурация необходима, чтобы знать архитектуру вашей модели, в то время как веса модели являются параметрами вашей модели. + +{/if} + +## Использование модели Transformer для логического вывода + +Теперь, когда вы знаете, как загружать и сохранять модель, давайте попробуем использовать ее для построения некоторых предсказаний. Модели Transformer могут обрабатывать только числа — числа, которые генерирует токенизатор. Но прежде чем мы обсудим токенизаторы, давайте рассмотрим, какие входные данные принимает модель. + +Токенизаторы могут позаботиться о преобразовании входных данных в соответствующие тензоры фреймворка, но чтобы помочь вам понять, что происходит, мы кратко рассмотрим, что необходимо сделать, прежде чем отправлять входные данные в модель. + +Допустим, у нас есть несколько последовательностей: + +```py +sequences = ["Hello!", "Cool.", "Nice!"] +``` + +Токенизатор преобразует их в словарные индексы, которые обычно называются *входными идентификаторами*. Каждая последовательность теперь представляет собой список чисел! В результате получается: + +```py no-format +encoded_sequences = [ + [101, 7592, 999, 102], + [101, 4658, 1012, 102], + [101, 3835, 999, 102], +] +``` + +Это список закодированных последовательностей: список списков. Тензоры принимают только прямоугольные формы (например, матрицы). Этот "массив" уже имеет прямоугольную форму, поэтому преобразовать его в тензор несложно: + +{#if fw === 'pt'} +```py +import torch + +model_inputs = torch.tensor(encoded_sequences) +``` +{:else} +```py +import tensorflow as tf + +model_inputs = tf.constant(encoded_sequences) +``` +{/if} + +### Использование тензоров в качестве входных данных для модели + +Использовать тензоры с моделью чрезвычайно просто — мы просто вызываем модель с входными данными: + +```py +output = model(model_inputs) +``` + +В то время как модель принимает множество различных аргументов, необходимы только входные идентификаторы. Позже мы объясним, для чего применяются другие аргументы и когда они требуются, но сначала нам нужно поближе познакомиться с токенизаторами, которые используются для создания входных данных, понятных модели Transformer. diff --git a/chapters/ru/chapter2/7.mdx b/chapters/ru/chapter2/7.mdx new file mode 100644 index 000000000..99bf935fb --- /dev/null +++ b/chapters/ru/chapter2/7.mdx @@ -0,0 +1,13 @@ +# Базовое использование завершено! + +Отличная работа, вы прошли курс до текущего момента! Напомним, что в этой главе вы: + +- Изучил основные строительные блоки модели Transformer. +- Узнали, из чего состоит конвейер токенизации. +- Увидел, как использовать модель Transformer на практике. +- Научились использовать токенизатор для преобразования текста в тензоры, понятные модели. +- Настроили токенизатор и модель так, чтобы было возможно перейти от текста к прогнозированию. +- Изучили ограничения для входных идентификаторов и узнал о масках внимания. +- Поэкспериментировали с универсальными и настраиваемыми методами токенизатора. + +Теперь вы сможете свободно ориентироваться в документации 🤗 Transformers: словарный запас будет для вас знаком, и к тому уже вы видели методы, которые будете использовать большую часть времени. diff --git a/chapters/th/_toctree.yml b/chapters/th/_toctree.yml index e6452028a..7f7b0c13b 100644 --- a/chapters/th/_toctree.yml +++ b/chapters/th/_toctree.yml @@ -74,3 +74,5 @@ title: บทนำ - local: chapter6/2 title: การเทรน tokenizer จาก tokenizer ที่มีอยู่แล้ว + - local: chapter6/3 + title: ความสามารถพิเศษของตัวตัดคำแบบเร็ว (fast tokenizers) \ No newline at end of file diff --git a/chapters/th/chapter6/3.mdx b/chapters/th/chapter6/3.mdx new file mode 100644 index 000000000..c6c5ebd20 --- /dev/null +++ b/chapters/th/chapter6/3.mdx @@ -0,0 +1,524 @@ + + +# ความสามารถพิเศษของตัวตัดคำแบบเร็ว (fast tokenizers) + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +ในบทนี้ เราจะเรียนเกี่ยวกับความสามารถของ tokenizer ต่างๆ ใน 🤗 Transformers library กัน +ในบทก่อนๆ คุณได้ลองใช้ tokenizer เพื่อแยกข้อความให้เป็นคำๆ และเพื่อแปลง ID ของคำให้กลับไปเป็นข้อความแล้ว +จริงๆแล้ว tokenizer นั้นยังมีความสามารถอีกหลายอย่าง โดยเฉพาะ tokenizer จาก 🤗 Tokenizers library + +เพื่อให้คุณเห็นภาพได้อย่างชัดเจน เราจะมาลองคำนวนผลลัพธ์ (reproduce) ของ `token-classification` (ซึ่งเราจะเรียกสั้นๆว่า `ner`) และสร้าง pipeline สำหรับ `question-answering` อย่างที่คุณได้เรียนมาแล้ว[บทที่ 1](/course/chapter1)กัน + + + + +เราจะแยก tokenizer เป็นแบบช้า (slow) และแบบเร็ว (fast) ซึ่งแบบช้าหมายถึง tokenizer ที่เขียนด้วย Python และมาจาก 🤗 Transformers library ส่วนแบบเร็วหมายถึง tokenizer ที่เขียนด้วย Rust และมาจาก 🤗 Tokenizers library +ถ้าคุณยังจำตารางจาก[บทที่ 5](/course/chapter5/3)ได้ ซึ่งเป็นตารางเปรียบเทียบเวลา ที่ tokenizer แบบเร็วและช้า ใช้ในการตัดคำชุดข้อมูล Drug Review คุณก็จะเห็นว่า ทำไมเราจึงเรียกพวกมันว่า แบบช้าและเร็ว + + | Fast tokenizer | Slow tokenizer +:--------------:|:--------------:|:-------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s + + + +⚠️ ถ้าคุณเปรียบเทียบ tokenizer ทั้งสองแบบ โดยดูจากความเร็วในการตัดคำของประโยคเดียว คุณอาจจะไม่เห็นความแตกต่างมาก และบางที fast tokenizer อาจจะช้ากว่า slow tokenizer ด้วยซ้ำ คุณจะเห็นความแตกต่างที่แท้จริง ก็เมื่อลองรันกับ input ที่มีขนาดใหญ่ระดับหนึ่ง เพราะการทำแบบนี้ จะทำให้การประมวลผลแบบ parallel ถูกเรียกใช้งาน + + + +## Batch encoding + + + +output ที่ได้จากการตัดคำนั้นไม่ใช่ dictionary แต่เป็น Python object ที่เรียกว่า `BatchEncoding` ซึ่งเป็น subclass ของ dictionary อีกที ทำให้เราสามารถ index ตัว output ได้ `BatchEncoding` ต่างจาก dictionary ทั่วไปตรงที่ มันมี method เพิ่มเติม ที่ส่วนมากจะถูกใช้โดย fast tokenizer + +นอกจาก fast tokenizer จะสามารถประมวลผลแบบ parallel ได้แล้ว ความสามารถหลักของของมันก็คือ มันจะบันทึก span (ตำแหน่งเริ่มและจบ) ของแต่ละ token ไว้ด้วย ข้อมูลเกี่ยวกับ span นี้เราเรียกว่า *offset mapping* + +*offset mapping* สามารถช่วยให้เราโยง "คำ" ไปหา token ของมันได้ (ในที่นี้ "คำ" หมายถึง กลุ่มของตัวอักษรที่ถูกแบ่งด้วย space ซึ่งหนึ่งคำอาจจะถูกแบ่งออกเป็นหลาย token ได้) และนอกจากนั้น ก็ยังช่วยให้เราสามารถโยงตัวอักษร ไปหา token ได้ด้วยเช่นกัน + +มาดูตัวอย่างกัน: + +```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 + +``` + +เนื่องจาก class `AutoTokenizer` จะเรียกใช้ตัว fast tokenizer เป็นค่าเริ่มต้น (by default) ซึ่งแปลว่า output ที่ได้คือ `BatchEncoding` และเราก็จะสามารถใช้ method พิเศษของมันได้ +การจะเช็คว่า ตัวตัดคำเป็นแบบเร็วหรือช้า ทำได้สองวิธี วิธีแรกคือเช็คโดยการใช้ attribute `is_fast` ของ `tokenizer` : + +```python +tokenizer.is_fast +``` + +```python out +True +``` + +อีกวิธีคือเช็ค attribute `is_fast` ของ `encoding`: + +```python +encoding.is_fast +``` + +```python out +True +``` + +มาดูกันว่า fast tokenizer ทำอะไรได้บ้าง อย่างแรกคือ เราสามารถเรียกดู token ได้ โดยไม่ต้องแปลงแต่ละ ID กลับไปเป็น token + +```py +encoding.tokens() +``` + +```python out +['[CLS]', 'My', 'name', 'is', 'S', '##yl', '##va', '##in', 'and', 'I', 'work', 'at', 'Hu', '##gging', 'Face', 'in', + 'Brooklyn', '.', '[SEP]'] +``` + +ในตัวอย่างนี้ token ในตำแหน่งที่ 5 คือ `##yl` ซึ่งเป็นส่วนหนึ่งของคำว่า "Sylvain" + +นอกจากนั้น คุณยังสามารถใช้ method `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] +``` + +คุณจะเห็นว่า token พิเศษอย่าง `[CLS]` และ `[SEP]` จะถูกจับคู่กับค่า `None` ส่วน token อื่นๆก็จะถูกจับคู่กับคำต้นตอของมัน +ข้อมูลนี้ มีประโยชน์ในการเอาไว้เช็คว่า token ที่เราสนใจนั้น อยู่ในตำแหน่งที่เป็นจุดเริ่มต้นของคำหรือไม่ และเอาไว้เช็คว่า token สองตัว มาจากคำเดียวกันหรือไม่ +คุณสามารถเช็คข้อมูลพวกนี้ได้จากการดูที่สัญลักษณ์ `##` ที่อยู่ข้างหน้า token (ซึ่งแปลว่า token ตัวนี้ ไม่ได้อยู่ตรงจุดเริ่มต้นคำ) แต่วิธีนี้ ใช้ได้แค่กับตัวตัดคำที่มีโครงสร้างแบบโมเดล BERT เท่านั้น อย่างไรก็ตาม วิธีนี้ใช้ได้กับตัวตัดคำทุกประเภทที่เป็นแบบเร็ว + +ในบทหน้า เราจะมาดูกันว่า เราจะใช้ feature นี้เพื่อจับคู่ label กับ token ใน task เช่น entity recognition (NER) and part-of-speech (POS) tagging ได้อย่างไร +นอกจากนั้น คุณยังสามารถใช้ feature นี้ เพื่อทำการปกปิด(mask) token ทุกตัวที่มาจากคำเดียวกัน เวลาใช้ masked language modeling ได้อีกด้วย (การทำแบบนี้เราเรียกว่า _whole word masking_) + + + +นิยามของ "คำ" นั้นค่อนข้างยากที่จะกำหนด ตัวอย่างเช่น "I'll" (เป็นการเขียนแบบสั้นของ "I will" ) ควรนับเป็นหนึ่งหรือสองคำ ? +คำตอบของคำถามนี้นั้น ขึ้นกับว่า คุณใช้ตัวตัดคำแบบไหน และมีการปรับแต่งข้อความ input ก่อนที่จะทำการตัดคำหรือไม่ +ตัวตัดคำบางตัว อาจจะแยกคำด้วย space บางตัวอาจจะแยกคำด้วยเครื่องหมายวรรคตอน (punctuation) ก่อนแล้วจึงแบ่งด้วย space ในกรณีหลังนี้ "I'll" ก็จะถูกแบ่งเป็นสองคำ + +✏️ **ลองทำดู!** ให้คุณลองสร้างตัวตัดคำจาก checkpoint ของ `bert-base-cased` and `roberta-base` แล้วให้ลองตัดคำว่า "81s" คุณสังเกตเห็นอะไรบ้าง และ ID ของคำที่ได้คืออะไร + + + +นอกจากนั้นยังมี method คล้ายๆกัน ที่ชื่อ `sentence_ids()` ที่เอาไว้ใช้เพื่อโยง token ไปหาประโยคต้นตอ ในตัวอย่างของเรา คุณสามารถใช้ `token_type_ids` ซึ่งเป็นผลลัพธ์จากการรัน tokenizer แทน `sentence_ids()` ได้ เพราะทั้งสองให้ข้อมูลเดียวกัน + +ความสามารถสุดท้าย คือโยง token ไปหาแต่ละตัวอักษร หรือกลับกัน โดยการใช้ method `word_to_chars()` หรือ `token_to_chars()` และ `char_to_word()` หรือ `char_to_token()` + +ตัวอย่างเช่น method `word_ids()` สามารถบอกให้คุณรู้ว่า `##yl` เป็นส่วนหนึ่งของคำในตำแหน่งที่ 3 แต่ถ้าคุณอยากรู้ว่า เป็นคำไหนในประโยค คุณสามารถเช็คได้ดังนี้ : + +```py +start, end = encoding.word_to_chars(3) +example[start:end] +``` + +```python out +Sylvain +``` + +อย่างที่เราได้บอกข้างต้นแล้ว fast tokenizer สามารถทำแบบนี้ได้ เพราะมันเก็บข้อมูลเกี่ยวกับ span ของแต่ละ token เอาไว้ และบันทึกไว้ใน list ของ *offsets* +เพื่อที่จะอธิบายการใช้งานของ feature นี้ เรามาลองคำนวนผลลัพธ์ของ pipeline `token-classification` กัน + + + +✏️ **ลองทำดู!** ให้คุณลองคิดข้อความตัวอย่างขึ้นมา แล้วถามตัวเองว่า token ตัวไหนคู่กับ ID ของคำไหน และ คุณจะหา span ของแต่ละคำได้อย่างไร นอกจากนั้น ให้คุณลองสร้างสองประโยคเพื่อเป็น input ให้กับตัวตัดคำของคุณ แล้วดูว่า ID ของประโยคนั้นเหมาะสมหรือไม่ + + + + +## โครงสร้างภายในของ pipeline `token-classification` + +ใน[บทที่ 1](/course/chapter1) คุณได้เรียนเกี่ยวกับการสร้างระบบ NER ซึ่งเป้าหมายหลักของระบบนี้ คือการหาส่วนของข้อความที่เป็น entitiy เช่น ชื่อคน ชื่อสถานที่ หรือชื่อองค์กร โดยการใช้ฟังก์ชัน `pipeline()` จาก 🤗 Transformers +ส่วนใน[บทที่ 2](/course/chapter2) คุณได้เรียนรู้ว่า pipeline ประกอบไปด้วย 3 ขั้นตอนสำคัญ เริ่มจาก การตัดคำ จากนั้นผลลัพธ์ก็จะถูกส่งไปให้โมเดล และสุดท้ายคือ การการปรับแต่งผลลัพธ์ (post-processing) +สองขั้นตอนแรกใน pipeline `token-classification` นั้น จะเหมือนกันกับ pipeline อื่นๆ แต่ขั้นตอน post-processing จะค่อนข้างซับซ้อนกว่า เราจะมาดูรายละเอียดกัน + + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +### การคำนวนผลลัพธ์เบื้องต้นด้วยการใช้ pipeline + +อันดับแรก เราจะใช้ token classification pipeline เพื่อเปรียบเทียบกับ pipeline +ของเรา โมเดลที่ถูกตั้งเป็นค่าเบื้องต้นคือ [`dbmdz/bert-large-cased-finetuned-conll03-english`](https://huggingface.co/dbmdz/bert-large-cased-finetuned-conll03-english) ซึ่งมันจะคำนวน NER ของแต่ละ ข้อความ input: + +```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}] +``` + +คุณจะเห็นว่า โมเดลนี้สามารถบอกได้ว่า token ที่มาจาก คำว่า "Sylvain" นั้นเป็นชื่อคน และ token ที่มาจากคำว่า "Hugging Face" นั้นเป็นชื่อองค์กร และ "Brooklyn" เป็นชื่อสถานที่ +เราสามารถใช้ pipeline นี้เพื่อรวมรวม token ที่มี entity ประเภทเดียวกันได้ด้วย + +```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` ที่เราเลือก จะเปลี่ยนคำแนนของแต่ละกลุ่ม entity ด้วย +ถ้าเราตั้งค่าให้เป็นแบบ `"simple"` มันจะคำนวนคะแนน โดยการเฉลี่ยคะแนนของแต่ละ token ที่นับเป็น entity เดียวกัน ตัวอย่างเช่น คะแนนของคำว่า "Sylvain" คือค่าเฉลี่ยของคะแนนจาก token ย่อยซึ่งก็คือ `S`, `##yl`, `##va`, and `##in` + +วิธีคำนวนคะแนนรวมแบบอื่น : + +- `"first"` จะใช้คะแนนของ token แรกเท่านั้น เป็นคำแนนรวม (เช่น คะแนนรวมของคำว่า "Sylvain" ก็จะเป็น 0.993828 ซึ่งมาจากคะแนนของ `S`) +- `"max"` จะใช้คะแนนของ token ที่มีคะแนนมากที่สุด (เช่น คะแนนรวมของคำว่า "Hugging Face" ก็จะเป็น 0.98879766 ซึ่งมาจากคะแนนของ "Face") +- `"average"` จะใช้ค่าเฉลี่ยของแต่ละ token ที่เป็นส่วนของ entity นั้น เป็นคะแนนรวมของ entity (สำหรับคำว่า "Sylvain" คะแนนรวมแบบเฉลี่ยจะไม่ต่างจากคะแนนรวมแบบ `"simple"` แต่คำว่า "Hugging Face" จำได้คะแนน 0.9819 ซึ่งเป็นค่าเฉลี่ย ของ "Hugging" 0.975 และ "Face" 0.98879) + +มาดูกันว่า คุณจะสร้างผลลัพธ์แบบนี้ได้อย่างไร โดยไม่ใช้ฟังก์ชัน `pipeline()`! + +### จาก input สู่ ผลลัพธ์ + +{#if fw === 'pt'} + +อันดับแรก เราจะเอาข้อความ input มาตัดคำก่อน แล้วส่งต่อผลลัพธ์ที่ได้ไปให้กับโมเดล ขั้นตอนนี้จะเหมือนที่เราทำกันใน[บทที่ 2](/course/chapter2) โดยเริ่มจากสร้างตัวตัดคำและโมเดลขึ้นมา โดยใช้คลาส `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` หลังจากรัน เราจะได้ค่า logit (set of logits) สำหรับแต่ละ token ที่อยู่ในข้อความ input + +```py +print(inputs["input_ids"].shape) +print(outputs.logits.shape) +``` + +```python out +torch.Size([1, 19]) +torch.Size([1, 19, 9]) +``` + +{:else} + +อันดับแรก เราจะเอาข้อความ input มาตัดคำก่อน แล้วส่งต่อผลลัพธ์ที่ได้ไปให้กับโมเดล ขั้นตอนนี้จะเหมือนที่เราทำกันใน[บทที่ 2](/course/chapter2) โดยเริ่มจากสร้างตัวตัดคำและโมเดลขึ้นมา โดยใช้คลาส `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` หลังจากรัน เราจะได้ค่า logit (set of logits) สำหรับแต่ละ token ที่อยู่ในข้อความ input + +```py +print(inputs["input_ids"].shape) +print(outputs.logits.shape) +``` + +```python out +(1, 19) +(1, 19, 9) +``` + +{/if} + +แต่ละ batch ประกอบไปด้วย 1 ข้อความ ซึ่งมี token 19 ตัว และโมเดลที่เราใช้ สามารถทำนายได้ 9 หมวด (label) ดังนั้นขนาดของ output ที่ได้คือ 1 x 19 x 9 +เช่นเดียวกับตอนที่เราใช้ text classification pipeline คือเราจะใช้ฟังก์ชัน softmax เพื่อที่จะแปลงค่า logits ไปเป็นค่าความเป็นไปได้ (probabilities) จากนั้นเราจะคำนวนค่า argmax เพื่อคำนวนคำทำนายสุดท้าย (เราใช้ argmax ของค่า logits ตรงนี้ได้ ก็เพราะการคำนวน 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] +``` + +attribute `model.config.id2label` คือตัวที่เก็บข้อมูลเกี่ยวกับ mapping ของ index ไปหา label ที่เราเอาไว้ใช้เพื่อดูว่า ผลลัพธ์นั้นถูกต้องหรือไม่ + +```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'} +``` + +จาก output ข้างบน คุณจะเห็นว่า เรามีคำทำนายทั้งหมด 9 หมวด (label) โดยที่ label `O` หมายถึง token ที่ไม่ได้เป็น named entity (`O` มาจากคำว่า "outside") และ แต่ละประเภทของ entity จะมีอย่างละสอง label (เรามีทั้งหมด 4 entity: miscellaneous, person, organization, and location) + +ถ้า token ถูก label ด้วย `B-XXX` นั่นแปลว่า token นี้อยู่ข้างหน้าของ entity ประเภท `XXX` ส่วน label `I-XXX` หมายถึง token นี้อยู่ข้างใน entity ประเภท `XXX` +ถ้าดูจากข้อความตัวอย่างที่เราใช้ข้างบน โมเดลของเราก็ควรจะจับคู่ token `S` กับหมวด `B-PER` (ซึ่งแปลว่า `S` เป็นส่วนข้างหน้าของ entity ประเภทชื่อคน) ส่วน token `##yl`, `##va` และ `##in` ก็ควรจะถูกทำนายให้เป็นหมวด `I-PER` (ซึ่งหมายถึง token ที่อยู่ข้างใน entity ประเภทชื่อคน) + +คุณอาจจะคิดว่า โมเดลของเราทำนายผิดในตัวอย่างนี้ เพราะว่ามันทำนายทั้งสี่ token ให้เป็น `I-PER` แต่จริงๆแล้ว ทำนายแบบนี้ก็ไม่ได้ผิดไปซะทั้งหมด +เพราะว่า การทำนายโดยใช้ label `B-` และ `I-` ในงาน NER มีสองแบบ คือ *IOB1* and *IOB2* + +IOB1 (สีส้ม) เป็นแบบที่เราใช้ในตัวอย่างข้างบน ส่วน IOB2 (สีม่วง) แตกต่างตรงที่ `B-` เอาไว้ใช้แบ่ง entity สองตัวที่เป็นประเภทเดียว ให้ออกจากกัน +โมเดลที่เราใช้นั้นถูก fine-tune จากชุดข้อมูลที่มี label แบบ IOB2 ทำให้มันทำนาย `S` เป็น `I-PER` + + +
+IOB1 vs IOB2 format + +
+ +ตอนนี้เราก็พร้อมที่จะคำนวนผลลัพธ์ ให้ได้แบบเดียวกับ pipeline แรกแล้ว โดยที่เราจะใช้คะแนนและ label ของแต่ละ token ที่ไม่ใช่ `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'}] +``` + +จะเห็นว่าตอนนี้ เราได้ผลลัพธ์ที่คล้ายกับผลลัพธ์จาก pipeline ก่อนหน้านี้แล้ว ข้อแตกต่างเดียวก็คือ ผลลัพธ์จาก pipeline จะให้ข้อมูลเกี่ยวกับ ตำแหน่งเริ่มและจบในข้อความของแต่ละ entity ด้วย +ขั้นตอนต่อไป เราจะได้เรียกใช้ค่า offset mapping เพื่อตั้งค่าให้โมเดลคำนวนค่า offset เราจะเช็ต `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)] +``` + +แต่ละ tuple ในนี้ จะบันทึกค่า span ของแต่ละ token ไว้ โดยที่พวก token พิเศษ ก็จะมีค่า span เป็น `(0, 0)` +ก่อนหน้านี้ เราได้เห็นว่า token ในตำแหน่งที่ 5 คือ `##yl` มีค่า offset เป็น `(12, 14)` +ถ้าคุณลอง slice ข้อความ input ด้วย index สองค่านี้ + +```py +example[12:14] +``` + +คุณจะได้ส่วนของข้อความที่เป็น `yl` โดยที่ `##` จะถูกละออกจากผลลัพธ์: + +```python out +yl +``` + +เราจะใช้วิธีนี้ เพื่อคำนวนผลลัพธ์ให้ได้ผลลัพธ์เหมือนใน pipeline: + +```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}] +``` + +ตอนนี้ เราก็ได้ผลลัพธ์แบบเดียวกับผลลัพธ์จาก pipeline แล้ว! + +### การรวม entities + +ข้อมูลเกี่ยวกับ ตำแหน่งเริ่มและตำแหน่งจบของแต่ละ entity ที่เราได้จาก offset อาจจะไม่เป็นประโยชน์มากนัก +แต่ถ้าคุณต้องการจะรวม token ที่อยู่ในกลุ่ม entity เดียวกันเข้าด้วยกัน ข้อมูลจาก offset พวกนี้ จะมีโยชน์มาก และทำให้คุณไม่ต้องเขียนโค้ดเพิ่มเติมด้วย + +ตัวอย่างเช่น ถ้าคุณต้องการจะรวม `Hu`, `##gging`, และ `Face` เข้าด้วยกัน คุณอาจจะเขียนกฎขึ้นมาว่า ให้รวม token แรกกับ token ที่สองเข้าด้วยกัน โดยลบ `##` ออก +แล้วให้รวม token ที่สามโดยใช้ช่องว่างในการเชื่อม เพราะ `Face` ไม่ได้เริ่มต้นด้วย `##` อย่างไรก็ตามวิธีนี้ ใช้ได้แค่กับตัวตัดคำบางประเภทเท่านั้น +สำหรับตัวตัดคำอื่นๆเช่น แบบ SentencePiece หรือ Byte-Pair-Encoding เราก็จะต้องสร้างกฎขึ้นมาใหม่ + +การที่เราใช้ค่า offset ทำให้เราไม่จำเป็นต้องเขียนโค้ดเกี่ยวกับกฎพวกนี้ขึ้นมาเอง คุณสามารถใช้ตำแหน่งเริ่มของ token แรก และ ตำแหน่งจบของ token สุดท้าย เป็นค่าในการ slice ข้อความ input เพื่อที่จะคำนวนส่วนของข้อความของ entity ที่คุณสนใจ +ตัวอย่างเช่น ถ้าเรามี 3 token `Hu`, `##gging`, และ `Face` ซึ่งอยู่ในกลุ่ม entity เดียวกัน เราก็จะใช้ค่า 33 (ตำแหน่งเริ่มของ `Hu`) เป็นจุดเริ่มต้น และ 45 (ตำแหน่งจบของ `Hu`) เป็นจุดจบของ entity นี้ + + +```py +example[33:45] +``` + +```python out +Hugging Face +``` + +ขั้นตอนต่อไป เราจะมาเขียนโค้ดเพื่อปรับแต่งผลลัพธ์จากโมเดลกัน (post-processing) โดยจะทำไปพร้อมๆกับการรวมกลุ่ม entity ด้วยวิธีต่อไปนี้ + +เริ่มจาก token แรกของ entity ซึ่งเป็นได้ทั้ง `B-XXX` และ `I-XXX` จากนั้นให้รวม token ตัวถัดๆไป ที่เป็น `I-XXX` ทั้งหมดเข้าด้วยกัน จนกว่าจะเห็น `O` (แปลว่าเรากำลังอ่านถึง token ที่ไม่ใช่ entity ใดๆ ให้เราหยุดการรวม) หรือ `B-XXX` (ให้เราเริ่มต้นรวม entity ตัวใหม่) + +```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] + + # Grab all the tokens labeled with I-label + 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 + + # The score is the mean of all the scores of the tokens in that grouped entity + 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) +``` + +ตอนนี้ เราก็ได้ผลลัพธ์แบบเดียวกันกับ 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}] +``` + +อีกตัวอย่างหนึ่งของงานที่ค่า offset เป็นโยชน์มาก ก็คือ question answering +ในบทถัดไป คุณจะได้เรียนเกี่ยวกับ pipeline แบบเจาะลึกมากขึ้น ซึ่งคุณจะได้รู้จักกับ feature สุดท้ายของ tokenizers ใน 🤗 Transformers library ซึ่งก็คือ การจัดการกับ tokens ที่ถูกตัดออกจากข้อความ input \ No newline at end of file From 6d1fed22a5c9bc2f3c216d328dd562d0026d5871 Mon Sep 17 00:00:00 2001 From: lewtun Date: Tue, 21 Jun 2022 09:17:58 +0200 Subject: [PATCH 20/51] Bump release (#258) --- .github/workflows/build_documentation.yml | 2 +- .github/workflows/build_pr_documentation.yml | 4 +- chapters/en/chapter5/5.mdx | 2 +- chapters/en/chapter7/5.mdx | 2102 +++++++++--------- chapters/en/chapter9/3.mdx | 2 +- chapters/en/chapter9/8.mdx | 8 +- chapters/en/chapter9/9.mdx | 6 +- chapters/es/_toctree.yml | 6 +- chapters/es/chapter3/4.mdx | 357 +++ chapters/es/glossary/1.mdx | 141 +- chapters/fr/chapter9/8.mdx | 9 +- chapters/hi/_toctree.yml | 13 + chapters/hi/chapter1/10.mdx | 248 +++ chapters/hi/chapter1/5.mdx | 17 + chapters/hi/chapter1/6.mdx | 16 + chapters/hi/chapter1/7.mdx | 16 + chapters/hi/chapter1/8.mdx | 32 + chapters/hi/chapter1/9.mdx | 11 + chapters/it/_toctree.yml | 8 +- chapters/it/chapter1/4.mdx | 171 ++ chapters/it/chapter1/5.mdx | 17 + chapters/it/chapter1/6.mdx | 16 + chapters/ja/_toctree.yml | 23 +- chapters/ja/chapter4/1.mdx | 17 + chapters/ja/chapter4/2.mdx | 96 + chapters/ja/chapter4/3.mdx | 635 ++++++ chapters/ja/chapter4/4.mdx | 82 + chapters/ja/chapter4/5.mdx | 7 + chapters/ja/chapter4/6.mdx | 223 ++ chapters/ja/event/1.mdx | 204 ++ chapters/pt/_toctree.yml | 13 + chapters/pt/chapter5/6.mdx | 529 +++++ chapters/pt/chapter5/7.mdx | 11 + chapters/pt/chapter5/8.mdx | 223 ++ chapters/pt/event/1.mdx | 165 ++ chapters/ru/_toctree.yml | 14 +- chapters/ru/chapter0/1.mdx | 2 +- chapters/ru/chapter3/1.mdx | 2 +- chapters/ru/chapter3/2.mdx | 14 +- chapters/ru/chapter3/3_tf.mdx | 191 ++ chapters/ru/chapter3/5.mdx | 21 + chapters/ru/chapter3/6.mdx | 296 +++ chapters/ru/chapter4/1.mdx | 17 + chapters/ru/chapter4/2.mdx | 97 + chapters/th/_toctree.yml | 12 + chapters/th/chapter3/2.mdx | 381 ++++ chapters/th/chapter3/3.mdx | 172 ++ chapters/th/chapter3/3_tf.mdx | 197 ++ chapters/th/chapter3/4.mdx | 359 +++ chapters/th/chapter3/5.mdx | 20 + chapters/th/chapter3/6.mdx | 296 +++ chapters/zh-TW/_toctree.yml | 4 + chapters/zh-TW/chapter0/1.mdx | 111 + requirements.txt | 2 +- upcoming_chapters/en/chapter10.md | 22 - upcoming_chapters/en/chapter11.md | 16 - upcoming_chapters/en/chapter12.md | 23 - upcoming_chapters/en/chapter9.md | 24 - 58 files changed, 6490 insertions(+), 1235 deletions(-) create mode 100644 chapters/es/chapter3/4.mdx create mode 100644 chapters/hi/chapter1/10.mdx create mode 100644 chapters/hi/chapter1/5.mdx create mode 100644 chapters/hi/chapter1/6.mdx create mode 100644 chapters/hi/chapter1/7.mdx create mode 100644 chapters/hi/chapter1/8.mdx create mode 100644 chapters/hi/chapter1/9.mdx create mode 100644 chapters/it/chapter1/4.mdx create mode 100644 chapters/it/chapter1/5.mdx create mode 100644 chapters/it/chapter1/6.mdx create mode 100644 chapters/ja/chapter4/1.mdx create mode 100644 chapters/ja/chapter4/2.mdx create mode 100644 chapters/ja/chapter4/3.mdx create mode 100644 chapters/ja/chapter4/4.mdx create mode 100644 chapters/ja/chapter4/5.mdx create mode 100644 chapters/ja/chapter4/6.mdx create mode 100644 chapters/ja/event/1.mdx create mode 100644 chapters/pt/chapter5/6.mdx create mode 100644 chapters/pt/chapter5/7.mdx create mode 100644 chapters/pt/chapter5/8.mdx create mode 100644 chapters/pt/event/1.mdx create mode 100644 chapters/ru/chapter3/3_tf.mdx create mode 100644 chapters/ru/chapter3/5.mdx create mode 100644 chapters/ru/chapter3/6.mdx create mode 100644 chapters/ru/chapter4/1.mdx create mode 100644 chapters/ru/chapter4/2.mdx create mode 100644 chapters/th/chapter3/2.mdx create mode 100644 chapters/th/chapter3/3.mdx create mode 100644 chapters/th/chapter3/3_tf.mdx create mode 100644 chapters/th/chapter3/4.mdx create mode 100644 chapters/th/chapter3/5.mdx create mode 100644 chapters/th/chapter3/6.mdx create mode 100644 chapters/zh-TW/_toctree.yml create mode 100644 chapters/zh-TW/chapter0/1.mdx delete mode 100644 upcoming_chapters/en/chapter10.md delete mode 100644 upcoming_chapters/en/chapter11.md delete mode 100644 upcoming_chapters/en/chapter12.md delete mode 100644 upcoming_chapters/en/chapter9.md diff --git a/.github/workflows/build_documentation.yml b/.github/workflows/build_documentation.yml index caee4d661..2fc4430c2 100644 --- a/.github/workflows/build_documentation.yml +++ b/.github/workflows/build_documentation.yml @@ -14,6 +14,6 @@ jobs: package: course path_to_docs: course/chapters/ additional_args: --not_python_module - languages: ar bn de en es fa fr gj he hi it ja ko pt ru th tr zh-CN + languages: ar bn de en es fa fr gj he hi it ja ko pt ru th tr zh-CN zh-TW secrets: token: ${{ secrets.HUGGINGFACE_PUSH }} diff --git a/.github/workflows/build_pr_documentation.yml b/.github/workflows/build_pr_documentation.yml index bfff48611..0e3728c20 100644 --- a/.github/workflows/build_pr_documentation.yml +++ b/.github/workflows/build_pr_documentation.yml @@ -16,5 +16,5 @@ jobs: package: course path_to_docs: course/chapters/ additional_args: --not_python_module - languages: ar bn de en es fa fr gj he hi it ja ko pt ru th tr zh-CN - hub_base_path: https://moon-ci-docs.huggingface.co + languages: ar bn de en es fa fr gj he hi it ja ko pt ru th tr zh-CN zh-TW + hub_base_path: https://moon-ci-docs.huggingface.co \ No newline at end of file diff --git a/chapters/en/chapter5/5.mdx b/chapters/en/chapter5/5.mdx index eb27674bd..d5b0605ca 100644 --- a/chapters/en/chapter5/5.mdx +++ b/chapters/en/chapter5/5.mdx @@ -389,7 +389,7 @@ Next, let's clone the repository from the Hub to our local machine and copy our from huggingface_hub import Repository repo = Repository(local_dir="github-issues", clone_from=repo_url) -!cp datasets-issues-with-comments.jsonl github-issues/ +!cp issues-datasets-with-comments.jsonl github-issues/ ``` By default, various file extensions (such as *.bin*, *.gz*, and *.zip*) are tracked with Git LFS so that large files can be versioned within the same Git workflow. You can find a list of tracked file extensions inside the repository's *.gitattributes* file. To include the JSON Lines format in the list, we can run the following command: diff --git a/chapters/en/chapter7/5.mdx b/chapters/en/chapter7/5.mdx index 1f9280c15..958dc685d 100644 --- a/chapters/en/chapter7/5.mdx +++ b/chapters/en/chapter7/5.mdx @@ -1,1051 +1,1051 @@ - - -# Summarization - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - - -In this section we'll take a look at how Transformer models can be used to condense long documents into summaries, a task known as _text summarization_. This is one of the most challenging NLP tasks as it requires a range of abilities, such as understanding long passages and generating coherent text that captures the main topics in a document. However, when done well, text summarization is a powerful tool that can speed up various business processes by relieving the burden of domain experts to read long documents in detail. - - - -Although there already exist various fine-tuned models for summarization on the [Hugging Face Hub](https://huggingface.co/models?pipeline_tag=summarization&sort=downloads), almost all of these are only suitable for English documents. So, to add a twist in this section, we'll train a bilingual model for English and Spanish. By the end of this section, you'll have a [model](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es) that can summarize customer reviews like the one shown here: - - - -As we'll see, these summaries are concise because they're learned from the titles that customers provide in their product reviews. Let's start by putting together a suitable bilingual corpus for this task. - -## Preparing a multilingual corpus - -We'll use the [Multilingual Amazon Reviews Corpus](https://huggingface.co/datasets/amazon_reviews_multi) to create our bilingual summarizer. This corpus consists of Amazon product reviews in six languages and is typically used to benchmark multilingual classifiers. However, since each review is accompanied by a short title, we can use the titles as the target summaries for our model to learn from! To get started, let's download the English and Spanish subsets from the 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 - }) -}) -``` - -As you can see, for each language there are 200,000 reviews for the `train` split, and 5,000 reviews for each of the `validation` and `test` splits. The review information we are interested in is contained in the `review_body` and `review_title` columns. Let's take a look at a few examples by creating a simple function that takes a random sample from the training set with the techniques we learned in [Chapter 5](/course/chapter5): - -```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.' -``` - - - -✏️ **Try it out!** Change the random seed in the `Dataset.shuffle()` command to explore other reviews in the corpus. If you're a Spanish speaker, take a look at some of the reviews in `spanish_dataset` to see if the titles also seem like reasonable summaries. - - - -This sample shows the diversity of reviews one typically finds online, ranging from positive to negative (and everything in between!). Although the example with the "meh" title is not very informative, the other titles look like decent summaries of the reviews themselves. Training a summarization model on all 400,000 reviews would take far too long on a single GPU, so instead we'll focus on generating summaries for a single domain of products. To get a feel for what domains we can choose from, let's convert `english_dataset` to a `pandas.DataFrame` and compute the number of reviews per product category: - -```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 -``` - -The most popular products in the English dataset are about household items, clothing, and wireless electronics. To stick with the Amazon theme, though, let's focus on summarizing book reviews -- after all, this is what the company was founded on! We can see two product categories that fit the bill (`book` and `digital_ebook_purchase`), so let's filter the datasets in both languages for just these products. As we saw in [Chapter 5](/course/chapter5), the `Dataset.filter()` function allows us to slice a dataset very efficiently, so we can define a simple function to do this: - -```python -def filter_books(example): - return ( - example["product_category"] == "book" - or example["product_category"] == "digital_ebook_purchase" - ) -``` - -Now when we apply this function to `english_dataset` and `spanish_dataset`, the result will contain just those rows involving the book categories. Before applying the filter, let's switch the format of `english_dataset` from `"pandas"` back to `"arrow"`: - -```python -english_dataset.reset_format() -``` - -We can then apply the filter function, and as a sanity check let's inspect a sample of reviews to see if they are indeed about books: - -```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.' -``` - -Okay, we can see that the reviews are not strictly about books and might refer to things like calendars and electronic applications such as OneNote. Nevertheless, the domain seems about right to train a summarization model on. Before we look at various models that are suitable for this task, we have one last bit of data preparation to do: combining the English and Spanish reviews as a single `DatasetDict` object. 🤗 Datasets provides a handy `concatenate_datasets()` function that (as the name suggests) will stack two `Dataset` objects on top of each other. So, to create our bilingual dataset, we'll loop over each split, concatenate the datasets for that split, and shuffle the result to ensure our model doesn't overfit to a single language: - -```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) - -# Peek at a few examples -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' -``` - -This certainly looks like a mix of English and Spanish reviews! Now that we have a training corpus, one final thing to check is the distribution of words in the reviews and their titles. This is especially important for summarization tasks, where short reference summaries in the data can bias the model to only output one or two words in the generated summaries. The plots below show the word distributions, and we can see that the titles are heavily skewed toward just 1-2 words: - -
-Word count distributions for the review titles and texts. - -
- -To deal with this, we'll filter out the examples with very short titles so that our model can produce more interesting summaries. Since we're dealing with English and Spanish texts, we can use a rough heuristic to split the titles on whitespace and then use our trusty `Dataset.filter()` method as follows: - -```python -books_dataset = books_dataset.filter(lambda x: len(x["review_title"].split()) > 2) -``` - -Now that we've prepared our corpus, let's take a look at a few possible Transformer models that one might fine-tune on it! - -## Models for text summarization - -If you think about it, text summarization is a similar sort of task to machine translation: we have a body of text like a review that we'd like to "translate" into a shorter version that captures the salient features of the input. Accordingly, most Transformer models for summarization adopt the encoder-decoder architecture that we first encountered in [Chapter 1](/course/chapter1), although there are some exceptions like the GPT family of models which can also be used for summarization in few-shot settings. The following table lists some popular pretrained models that can be fine-tuned for summarization. - -| Transformer model | Description | Multilingual? | -| :---------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------: | -| [GPT-2](https://huggingface.co/gpt2-xl) | Although trained as an auto-regressive language model, you can make GPT-2 generate summaries by appending "TL;DR" at the end of the input text. | ❌ | -| [PEGASUS](https://huggingface.co/google/pegasus-large) | Uses a pretraining objective to predict masked sentences in multi-sentence texts. This pretraining objective is closer to summarization than vanilla language modeling and scores highly on popular benchmarks. | ❌ | -| [T5](https://huggingface.co/t5-base) | A universal Transformer architecture that formulates all tasks in a text-to-text framework; e.g., the input format for the model to summarize a document is `summarize: ARTICLE`. | ❌ | -| [mT5](https://huggingface.co/google/mt5-base) | A multilingual version of T5, pretrained on the multilingual Common Crawl corpus (mC4), covering 101 languages. | ✅ | -| [BART](https://huggingface.co/facebook/bart-base) | A novel Transformer architecture with both an encoder and a decoder stack trained to reconstruct corrupted input that combines the pretraining schemes of BERT and GPT-2. | ❌ | -| [mBART-50](https://huggingface.co/facebook/mbart-large-50) | A multilingual version of BART, pretrained on 50 languages. | ✅ | - -As you can see from this table, the majority of Transformer models for summarization (and indeed most NLP tasks) are monolingual. This is great if your task is in a "high-resource" language like English or German, but less so for the thousands of other languages in use across the world. Fortunately, there is a class of multilingual Transformer models, like mT5 and mBART, that come to the rescue. These models are pretrained using language modeling, but with a twist: instead of training on a corpus of one language, they are trained jointly on texts in over 50 languages at once! - -We'll focus on mT5, an interesting architecture based on T5 that was pretrained in a text-to-text framework. In T5, every NLP task is formulated in terms of a prompt prefix like `summarize:` which conditions the model to adapt the generated text to the prompt. As shown in the figure below, this makes T5 extremely versatile, as you can solve many tasks with a single model! - -
-Different tasks performed by the T5 architecture. - -
- -mT5 doesn't use prefixes, but shares much of the versatility of T5 and has the advantage of being multilingual. Now that we've picked a model, let's take a look at preparing our data for training. - - - - -✏️ **Try it out!** Once you've worked through this section, see how well mT5 compares to mBART by fine-tuning the latter with the same techniques. For bonus points, you can also try fine-tuning T5 on just the English reviews. Since T5 has a special prefix prompt, you'll need to prepend `summarize:` to the input examples in the preprocessing steps below. - - - -## Preprocessing the data - - - -Our next task is to tokenize and encode our reviews and their titles. As usual, we begin by loading the tokenizer associated with the pretrained model checkpoint. We'll use `mt5-small` as our checkpoint so we can fine-tune the model in a reasonable amount of time: - -```python -from transformers import AutoTokenizer - -model_checkpoint = "google/mt5-small" -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) -``` - - - -💡 In the early stages of your NLP projects, a good practice is to train a class of "small" models on a small sample of data. This allows you to debug and iterate faster toward an end-to-end workflow. Once you are confident in the results, you can always scale up the model by simply changing the model checkpoint! - - - -Let's test out the mT5 tokenizer on a small example: - -```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]} -``` - -Here we can see the familiar `input_ids` and `attention_mask` that we encountered in our first fine-tuning experiments back in [Chapter 3](/course/chapter3). Let's decode these input IDs with the tokenizer's `convert_ids_to_tokens()` function to see what kind of tokenizer we're dealing with: - -```python -tokenizer.convert_ids_to_tokens(inputs.input_ids) -``` - -```python out -['▁I', '▁', 'loved', '▁reading', '▁the', '▁Hung', 'er', '▁Games', ''] -``` - -The special Unicode character `▁` and end-of-sequence token `` indicate that we're dealing with the SentencePiece tokenizer, which is based on the Unigram segmentation algorithm discussed in [Chapter 6](/course/chapter6). Unigram is especially useful for multilingual corpora since it allows SentencePiece to be agnostic about accents, punctuation, and the fact that many languages, like Japanese, do not have whitespace characters. - -To tokenize our corpus, we have to deal with a subtlety associated with summarization: because our labels are also text, it is possible that they exceed the model's maximum context size. This means we need to apply truncation to both the reviews and their titles to ensure we don't pass excessively long inputs to our model. The tokenizers in 🤗 Transformers provide a nifty `as_target_tokenizer()` function that allows you to tokenize the labels in parallel to the inputs. This is typically done using a context manager inside a preprocessing function that first encodes the inputs, and then encodes the labels as a separate column. Here is an example of such a function for 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 - ) - # Set up the tokenizer for targets - with tokenizer.as_target_tokenizer(): - labels = tokenizer( - examples["review_title"], max_length=max_target_length, truncation=True - ) - - model_inputs["labels"] = labels["input_ids"] - return model_inputs -``` - -Let's walk through this code to understand what's happening. The first thing we've done is define values for `max_input_length` and `max_target_length`, which set the upper limits for how long our reviews and titles can be. Since the review body is typically much larger than the title, we've scaled these values accordingly. Then, in the `preprocess_function()` itself we can see the reviews are first tokenized, followed by the titles with `as_target_tokenizer()`. - -With `preprocess_function()`, it is then a simple matter to tokenize the whole corpus using the handy `Dataset.map()` function we've used extensively throughout this course: - -```python -tokenized_datasets = books_dataset.map(preprocess_function, batched=True) -``` - -Now that the corpus has been preprocessed, let's take a look at some metrics that are commonly used for summarization. As we'll see, there is no silver bullet when it comes to measuring the quality of machine-generated text. - - - -💡 You may have noticed that we used `batched=True` in our `Dataset.map()` function above. This encodes the examples in batches of 1,000 (the default) and allows you to make use of the multithreading capabilities of the fast tokenizers in 🤗 Transformers. Where possible, try using `batched=True` to get the most out of your preprocessing! - - - - -## Metrics for text summarization - - - -In comparison to most of the other tasks we've covered in this course, measuring the performance of text generation tasks like summarization or translation is not as straightforward. For example, given a review like "I loved reading the Hunger Games", there are multiple valid summaries, like "I loved the Hunger Games" or "Hunger Games is a great read". Clearly, applying some sort of exact match between the generated summary and the label is not a good solution -- even humans would fare poorly under such a metric, because we all have our own writing style. - -For summarization, one of the most commonly used metrics is the [ROUGE score](https://en.wikipedia.org/wiki/ROUGE_(metric)) (short for Recall-Oriented Understudy for Gisting Evaluation). The basic idea behind this metric is to compare a generated summary against a set of reference summaries that are typically created by humans. To make this more precise, suppose we want to compare the following two summaries: - -```python -generated_summary = "I absolutely loved reading the Hunger Games" -reference_summary = "I loved reading the Hunger Games" -``` - -One way to compare them could be to count the number of overlapping words, which in this case would be 6. However, this is a bit crude, so instead ROUGE is based on computing the _precision_ and _recall_ scores for the overlap. - - - -🙋 Don't worry if this is the first time you've heard of precision and recall -- we'll go through some explicit examples together to make it all clear. These metrics are usually encountered in classification tasks, so if you want to understand how precision and recall are defined in that context, we recommend checking out the `scikit-learn` [guides](https://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html). - - - -For ROUGE, recall measures how much of the reference summary is captured by the generated one. If we are just comparing words, recall can be calculated according to the following formula: - -$$ \mathrm{Recall} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, reference\, summary}} $$ - -For our simple example above, this formula gives a perfect recall of 6/6 = 1; i.e., all the words in the reference summary have been produced by the model. This may sound great, but imagine if our generated summary had been "I really really loved reading the Hunger Games all night". This would also have perfect recall, but is arguably a worse summary since it is verbose. To deal with these scenarios we also compute the precision, which in the ROUGE context measures how much of the generated summary was relevant: - -$$ \mathrm{Precision} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, generated\, summary}} $$ - -Applying this to our verbose summary gives a precision of 6/10 = 0.6, which is considerably worse than the precision of 6/7 = 0.86 obtained by our shorter one. In practice, both precision and recall are usually computed, and then the F1-score (the harmonic mean of precision and recall) is reported. We can do this easily in 🤗 Datasets by first installing the `rouge_score` package: - -```py -!pip install rouge_score -``` - -and then loading the ROUGE metric as follows: - -```python -from datasets import load_metric - -rouge_score = load_metric("rouge") -``` - -Then we can use the `rouge_score.compute()` function to calculate all the metrics at once: - -```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))} -``` - -Whoa, there's a lot of information in that output -- what does it all mean? First, 🤗 Datasets actually computes confidence intervals for precision, recall, and F1-score; these are the `low`, `mid`, and `high` attributes you can see here. Moreover, 🤗 Datasets computes a variety of ROUGE scores which are based on different types of text granularity when comparing the generated and reference summaries. The `rouge1` variant is the overlap of unigrams -- this is just a fancy way of saying the overlap of words and is exactly the metric we've discussed above. To verify this, let's pull out the `mid` value of our scores: - -```python -scores["rouge1"].mid -``` - -```python out -Score(precision=0.86, recall=1.0, fmeasure=0.92) -``` - -Great, the precision and recall numbers match up! Now what about those other ROUGE scores? `rouge2` measures the overlap between bigrams (think the overlap of pairs of words), while `rougeL` and `rougeLsum` measure the longest matching sequences of words by looking for the longest common substrings in the generated and reference summaries. The "sum" in `rougeLsum` refers to the fact that this metric is computed over a whole summary, while `rougeL` is computed as the average over individual sentences. - - - -✏️ **Try it out!** Create your own example of a generated and reference summary and see if the resulting ROUGE scores agree with a manual calculation based on the formulas for precision and recall. For bonus points, split the text into bigrams and compare the precision and recall for the `rouge2` metric. - - - -We'll use these ROUGE scores to track the performance of our model, but before doing that let's do something every good NLP practitioner should do: create a strong, yet simple baseline! - -### Creating a strong baseline - -A common baseline for text summarization is to simply take the first three sentences of an article, often called the _lead-3_ baseline. We could use full stops to track the sentence boundaries, but this will fail on acronyms like "U.S." or "U.N." -- so instead we'll use the `nltk` library, which includes a better algorithm to handle these cases. You can install the package using `pip` as follows: - -```python -!pip install nltk -``` - -and then download the punctuation rules: - -```python -import nltk - -nltk.download("punkt") -``` - -Next, we import the sentence tokenizer from `nltk` and create a simple function to extract the first three sentences in a review. The convention in text summarization is to separate each summary with a newline, so let's also include this and test it on a training example: - -```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.' -``` - -This seems to work, so let's now implement a function that extracts these "summaries" from a dataset and computes the ROUGE scores for the baseline: - -```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"]) -``` - -We can then use this function to compute the ROUGE scores over the validation set and prettify them a bit using 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} -``` - -We can see that the `rouge2` score is significantly lower than the rest; this likely reflects the fact that review titles are typically concise and so the lead-3 baseline is too verbose. Now that we have a good baseline to work from, let's turn our attention toward fine-tuning mT5! - -{#if fw === 'pt'} - -## Fine-tuning mT5 with the `Trainer` API - -Fine-tuning a model for summarization is very similar to the other tasks we've covered in this chapter. The first thing we need to do is load the pretrained model from the `mt5-small` checkpoint. Since summarization is a sequence-to-sequence task, we can load the model with the `AutoModelForSeq2SeqLM` class, which will automatically download and cache the weights: - -```python -from transformers import AutoModelForSeq2SeqLM - -model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) -``` - -{:else} - -## Fine-tuning mT5 with Keras - -Fine-tuning a model for summarization is very similar to the other tasks we've covered in this chapter. The first thing we need to do is load the pretrained model from the `mt5-small` checkpoint. Since summarization is a sequence-to-sequence task, we can load the model with the `AutoModelForSeq2SeqLM` class, which will automatically download and cache the weights: - -```python -from transformers import TFAutoModelForSeq2SeqLM - -model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) -``` - -{/if} - - - -💡 If you're wondering why you don't see any warnings about fine-tuning the model on a downstream task, that's because for sequence-to-sequence tasks we keep all the weights of the network. Compare this to our text classification model in [Chapter 3](/course/chapter3), where the head of the pretrained model was replaced with a randomly initialized network. - - - -The next thing we need to do is log in to the Hugging Face Hub. If you're running this code in a notebook, you can do so with the following utility function: - -```python -from huggingface_hub import notebook_login - -notebook_login() -``` - -which will display a widget where you can enter your credentials. Alternatively, you can run this command in your terminal and log in there: - -``` -huggingface-cli login -``` - -{#if fw === 'pt'} - -We'll need to generate summaries in order to compute ROUGE scores during training. Fortunately, 🤗 Transformers provides dedicated `Seq2SeqTrainingArguments` and `Seq2SeqTrainer` classes that can do this for us automatically! To see how this works, let's first define the hyperparameters and other arguments for our experiments: - -```python -from transformers import Seq2SeqTrainingArguments - -batch_size = 8 -num_train_epochs = 8 -# Show the training loss with every epoch -logging_steps = len(tokenized_datasets["train"]) // batch_size -model_name = model_checkpoint.split("/")[-1] - -args = Seq2SeqTrainingArguments( - output_dir=f"{model_name}-finetuned-amazon-en-es", - evaluation_strategy="epoch", - learning_rate=5.6e-5, - per_device_train_batch_size=batch_size, - per_device_eval_batch_size=batch_size, - weight_decay=0.01, - save_total_limit=3, - num_train_epochs=num_train_epochs, - predict_with_generate=True, - logging_steps=logging_steps, - push_to_hub=True, -) -``` - -Here, the `predict_with_generate` argument has been set to indicate that we should generate summaries during evaluation so that we can compute ROUGE scores for each epoch. As discussed in [Chapter 1](/course/chapter1), the decoder performs inference by predicting tokens one by one, and this is implemented by the model's `generate()` method. Setting `predict_with_generate=True` tells the `Seq2SeqTrainer` to use that method for evaluation. We've also adjusted some of the default hyperparameters, like the learning rate, number of epochs, and weight decay, and we've set the `save_total_limit` option to only save up to 3 checkpoints during training -- this is because even the "small" version of mT5 uses around a GB of hard drive space, and we can save a bit of room by limiting the number of copies we save. - -The `push_to_hub=True` argument will allow us to push the model to the Hub after training; you'll find the repository under your user profile in the location defined by `output_dir`. Note that you can specify the name of the repository you want to push to with the `hub_model_id` argument (in particular, you will have to use this argument to push to an organization). For instance, when we pushed the model to the [`huggingface-course` organization](https://huggingface.co/huggingface-course), we added `hub_model_id="huggingface-course/mt5-finetuned-amazon-en-es"` to `Seq2SeqTrainingArguments`. - -The next thing we need to do is provide the trainer with a `compute_metrics()` function so that we can evaluate our model during training. For summarization this is a bit more involved than simply calling `rouge_score.compute()` on the model's predictions, since we need to _decode_ the outputs and labels into text before we can compute the ROUGE scores. The following function does exactly that, and also makes use of the `sent_tokenize()` function from `nltk` to separate the summary sentences with newlines: - -```python -import numpy as np - - -def compute_metrics(eval_pred): - predictions, labels = eval_pred - # Decode generated summaries into text - decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) - # Replace -100 in the labels as we can't decode them - labels = np.where(labels != -100, labels, tokenizer.pad_token_id) - # Decode reference summaries into text - decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) - # ROUGE expects a newline after each sentence - decoded_preds = ["\n".join(sent_tokenize(pred.strip())) for pred in decoded_preds] - decoded_labels = ["\n".join(sent_tokenize(label.strip())) for label in decoded_labels] - # Compute ROUGE scores - result = rouge_score.compute( - predictions=decoded_preds, references=decoded_labels, use_stemmer=True - ) - # Extract the median scores - result = {key: value.mid.fmeasure * 100 for key, value in result.items()} - return {k: round(v, 4) for k, v in result.items()} -``` - -{/if} - -Next, we need to define a data collator for our sequence-to-sequence task. Since mT5 is an encoder-decoder Transformer model, one subtlety with preparing our batches is that during decoding we need to shift the labels to the right by one. This is required to ensure that the decoder only sees the previous ground truth labels and not the current or future ones, which would be easy for the model to memorize. This is similar to how masked self-attention is applied to the inputs in a task like [causal language modeling](/course/chapter7/6). - -Luckily, 🤗 Transformers provides a `DataCollatorForSeq2Seq` collator that will dynamically pad the inputs and the labels for us. To instantiate this collator, we simply need to provide the `tokenizer` and `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} - -Let's see what this collator produces when fed a small batch of examples. First, we need to remove the columns with strings because the collator won't know how to pad these elements: - -```python -tokenized_datasets = tokenized_datasets.remove_columns( - books_dataset["train"].column_names -) -``` - -Since the collator expects a list of `dict`s, where each `dict` represents a single example in the dataset, we also need to wrangle the data into the expected format before passing it to the data collator: - -```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]])} -``` - -The main thing to notice here is that the first example is longer than the second one, so the `input_ids` and `attention_mask` of the second example have been padded on the right with a `[PAD]` token (whose ID is `0`). Similarly, we can see that the `labels` have been padded with `-100`s, to make sure the padding tokens are ignored by the loss function. And finally, we can see a new `decoder_input_ids` which has shifted the labels to the right by inserting a `[PAD]` token in the first entry. - -{#if fw === 'pt'} - -We finally have all the ingredients we need to train with! We now simply need to instantiate the trainer with the standard arguments: - -```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, -) -``` - -and launch our training run: - -```python -trainer.train() -``` - -During training, you should see the training loss decrease and the ROUGE scores increase with each epoch. Once the training is complete, you can see the final ROUGE scores by running `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} -``` - -From the scores we can see that our model has handily outperformed our lead-3 baseline -- nice! The final thing to do is push the model weights to the Hub, as follows: - -``` -trainer.push_to_hub(commit_message="Training complete", tags="summarization") -``` - -```python out -'https://huggingface.co/huggingface-course/mt5-finetuned-amazon-en-es/commit/aa0536b829b28e73e1e4b94b8a5aacec420d40e0' -``` - -This will save the checkpoint and configuration files to `output_dir`, before uploading all the files to the Hub. By specifying the `tags` argument, we also ensure that the widget on the Hub will be one for a summarization pipeline instead of the default text generation one associated with the mT5 architecture (for more information about model tags, see the [🤗 Hub documentation](https://huggingface.co/docs/hub/main#how-is-a-models-type-of-inference-api-and-widget-determined)). The output from `trainer.push_to_hub()` is a URL to the Git commit hash, so you can easily see the changes that were made to the model repository! - -To wrap up this section, let's take a look at how we can also fine-tune mT5 using the low-level features provided by 🤗 Accelerate. - -{:else} - -We're almost ready to train! We just need to convert our datasets to `tf.data.Dataset`s using the data collator we defined above, and then `compile()` and `fit()` the model. First, the datasets: - -```python -tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=True, - batch_size=8, -) -tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=False, - batch_size=8, -) -``` - -Now, we define our training hyperparameters and compile: - -```python -from transformers import create_optimizer -import tensorflow as tf - -# 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_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) - -# Train in mixed-precision float16 -tf.keras.mixed_precision.set_global_policy("mixed_float16") -``` - -And finally, we fit the model. We use a `PushToHubCallback` to save the model to the Hub after each epoch, which will allow us to use it for inference later: - -```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 -) -``` - -We got some loss values during training, but really we'd like to see the ROUGE metrics we computed earlier. To get those metrics, we'll need to generate outputs from the model and convert them to strings. Let's build some lists of labels and predictions for the ROUGE metric to compare (note that if you get import errors for this section, you may need to`!pip install tqdm`): - -```python -from tqdm import tqdm -import numpy as np - -all_preds = [] -all_labels = [] -for batch in tqdm(tf_eval_dataset): - predictions = model.generate(**batch) - decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) - labels = batch["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) -``` - -Once we have our lists of label and prediction strings, computing the ROUGE score is easy: - -```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'} - -## Fine-tuning mT5 with 🤗 Accelerate - -Fine-tuning our model with 🤗 Accelerate is very similar to the text classification example we encountered in [Chapter 3](/course/chapter3). The main differences will be the need to explicitly generate our summaries during training and define how we compute the ROUGE scores (recall that the `Seq2SeqTrainer` took care of the generation for us). Let's take a look how we can implement these two requirements within 🤗 Accelerate! - -### Preparing everything for training - -The first thing we need to do is create a `DataLoader` for each of our splits. Since the PyTorch dataloaders expect batches of tensors, we need to set the format to `"torch"` in our datasets: - -```python -tokenized_datasets.set_format("torch") -``` - -Now that we've got datasets consisting of just tensors, the next thing to do is instantiate the `DataCollatorForSeq2Seq` again. For this we need to provide a fresh version of the model, so let's load it again from our cache: - -```python -model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) -``` - -We can then instantiate the data collator and use this to define our dataloaders: - -```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 -) -``` - -The next thing to do is define the optimizer we want to use. As in our other examples, we'll use `AdamW`, which works well for most problems: - -```python -from torch.optim import AdamW - -optimizer = AdamW(model.parameters(), lr=2e-5) -``` - -Finally, we feed our model, optimizer, and dataloaders to the `accelerator.prepare()` method: - -```python -from accelerate import Accelerator - -accelerator = Accelerator() -model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( - model, optimizer, train_dataloader, eval_dataloader -) -``` - - - -🚨 If you're training on a TPU, you'll need to move all the code above into a dedicated training function. See [Chapter 3](/course/chapter3) for more details. - - - -Now that we've prepared our objects, there are three remaining things to do: - -* Define the learning rate schedule. -* Implement a function to post-process the summaries for evaluation. -* Create a repository on the Hub that we can push our model to. - -For the learning rate schedule, we'll use the standard linear one from previous sections: - -```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, -) -``` - -For post-processing, we need a function that splits the generated summaries into sentences that are separated by newlines. This is the format the ROUGE metric expects, and we can achieve this with the following snippet of code: - -```python -def postprocess_text(preds, labels): - preds = [pred.strip() for pred in preds] - labels = [label.strip() for label in labels] - - # ROUGE expects a newline after each sentence - preds = ["\n".join(nltk.sent_tokenize(pred)) for pred in preds] - labels = ["\n".join(nltk.sent_tokenize(label)) for label in labels] - - return preds, labels -``` - -This should look familiar to you if you recall how we defined the `compute_metrics()` function of the `Seq2SeqTrainer`. - -Finally, we need to create a model repository on the Hugging Face Hub. For this, we can use the appropriately titled 🤗 Hub library. We just need to define a name for our repository, and the library has a utility function to combine the repository ID with the user profile: - -```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' -``` - -Now we can use this repository name to clone a local version to our results directory that will store the training artifacts: - -```python -from huggingface_hub import Repository - -output_dir = "results-mt5-finetuned-squad-accelerate" -repo = Repository(output_dir, clone_from=repo_name) -``` - -This will allow us to push the artifacts back to the Hub by calling the `repo.push_to_hub()` method during training! Let's now wrap up our analysis by writing out the training loop. - -### Training loop - -The training loop for summarization is quite similar to the other 🤗 Accelerate examples that we've encountered and is roughly split into four main steps: - -1. Train the model by iterating over all the examples in `train_dataloader` for each epoch. -2. Generate model summaries at the end of each epoch, by first generating the tokens and then decoding them (and the reference summaries) into text. -3. Compute the ROUGE scores using the same techniques we saw earlier. -4. Save the checkpoints and push everything to the Hub. Here we rely on the nifty `blocking=False` argument of the `Repository` object so that we can push the checkpoints per epoch _asynchronously_. This allows us to continue training without having to wait for the somewhat slow upload associated with a GB-sized model! - -These steps can be seen in the following block of code: - -```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): - # Training - model.train() - for step, batch in enumerate(train_dataloader): - outputs = model(**batch) - loss = outputs.loss - accelerator.backward(loss) - - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - progress_bar.update(1) - - # Evaluation - model.eval() - for step, batch in enumerate(eval_dataloader): - with torch.no_grad(): - generated_tokens = accelerator.unwrap_model(model).generate( - batch["input_ids"], - attention_mask=batch["attention_mask"], - ) - - generated_tokens = accelerator.pad_across_processes( - generated_tokens, dim=1, pad_index=tokenizer.pad_token_id - ) - labels = batch["labels"] - - # If we did not pad to max length, we need to pad the labels too - labels = accelerator.pad_across_processes( - batch["labels"], dim=1, pad_index=tokenizer.pad_token_id - ) - - generated_tokens = accelerator.gather(generated_tokens).cpu().numpy() - labels = accelerator.gather(labels).cpu().numpy() - - # Replace -100 in the labels as we can't decode them - labels = np.where(labels != -100, labels, tokenizer.pad_token_id) - if isinstance(generated_tokens, tuple): - generated_tokens = generated_tokens[0] - decoded_preds = tokenizer.batch_decode( - generated_tokens, skip_special_tokens=True - ) - decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) - - decoded_preds, decoded_labels = postprocess_text( - decoded_preds, decoded_labels - ) - - rouge_score.add_batch(predictions=decoded_preds, references=decoded_labels) - - # Compute metrics - result = rouge_score.compute() - # Extract the median ROUGE scores - result = {key: value.mid.fmeasure * 100 for key, value in result.items()} - result = {k: round(v, 4) for k, v in result.items()} - print(f"Epoch {epoch}:", result) - - # Save and upload - accelerator.wait_for_everyone() - unwrapped_model = accelerator.unwrap_model(model) - unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) - if accelerator.is_main_process: - tokenizer.save_pretrained(output_dir) - repo.push_to_hub( - commit_message=f"Training in progress epoch {epoch}", blocking=False - ) -``` - -```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} -``` - -And that's it! Once you run this, you'll have a model and results that are pretty similar to the ones we obtained with the `Trainer`. - -{/if} - -## Using your fine-tuned model - -Once you've pushed the model to the Hub, you can play with it either via the inference widget or with a `pipeline` object, as follows: - -```python -from transformers import pipeline - -hub_model_id = "huggingface-course/mt5-small-finetuned-amazon-en-es" -summarizer = pipeline("summarization", model=hub_model_id) -``` - -We can feed some examples from the test set (which the model has not seen) to our pipeline to get a feel for the quality of the summaries. First let's implement a simple function to show the review, title, and generated summary together: - -```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}'") -``` - -Let's take a look at one of the English examples we get: - -```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' -``` - -This is not too bad! We can see that our model has actually been able to perform _abstractive_ summarization by augmenting parts of the review with new words. And perhaps the coolest aspect of our model is that it is bilingual, so we can also generate summaries of Spanish reviews: - -```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' -``` - -The summary translates into "Very easy to read" in English, which we can see in this case was extracted directly from the review. Nevertheless, this shows the versatility of the mT5 model and has given you a taste of what it's like to deal with a multilingual corpus! - -Next, we'll turn our attention to a slightly more complex task: training a language model from scratch. + + +# Summarization + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + +In this section we'll take a look at how Transformer models can be used to condense long documents into summaries, a task known as _text summarization_. This is one of the most challenging NLP tasks as it requires a range of abilities, such as understanding long passages and generating coherent text that captures the main topics in a document. However, when done well, text summarization is a powerful tool that can speed up various business processes by relieving the burden of domain experts to read long documents in detail. + + + +Although there already exist various fine-tuned models for summarization on the [Hugging Face Hub](https://huggingface.co/models?pipeline_tag=summarization&sort=downloads), almost all of these are only suitable for English documents. So, to add a twist in this section, we'll train a bilingual model for English and Spanish. By the end of this section, you'll have a [model](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es) that can summarize customer reviews like the one shown here: + + + +As we'll see, these summaries are concise because they're learned from the titles that customers provide in their product reviews. Let's start by putting together a suitable bilingual corpus for this task. + +## Preparing a multilingual corpus + +We'll use the [Multilingual Amazon Reviews Corpus](https://huggingface.co/datasets/amazon_reviews_multi) to create our bilingual summarizer. This corpus consists of Amazon product reviews in six languages and is typically used to benchmark multilingual classifiers. However, since each review is accompanied by a short title, we can use the titles as the target summaries for our model to learn from! To get started, let's download the English and Spanish subsets from the 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 + }) +}) +``` + +As you can see, for each language there are 200,000 reviews for the `train` split, and 5,000 reviews for each of the `validation` and `test` splits. The review information we are interested in is contained in the `review_body` and `review_title` columns. Let's take a look at a few examples by creating a simple function that takes a random sample from the training set with the techniques we learned in [Chapter 5](/course/chapter5): + +```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.' +``` + + + +✏️ **Try it out!** Change the random seed in the `Dataset.shuffle()` command to explore other reviews in the corpus. If you're a Spanish speaker, take a look at some of the reviews in `spanish_dataset` to see if the titles also seem like reasonable summaries. + + + +This sample shows the diversity of reviews one typically finds online, ranging from positive to negative (and everything in between!). Although the example with the "meh" title is not very informative, the other titles look like decent summaries of the reviews themselves. Training a summarization model on all 400,000 reviews would take far too long on a single GPU, so instead we'll focus on generating summaries for a single domain of products. To get a feel for what domains we can choose from, let's convert `english_dataset` to a `pandas.DataFrame` and compute the number of reviews per product category: + +```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 +``` + +The most popular products in the English dataset are about household items, clothing, and wireless electronics. To stick with the Amazon theme, though, let's focus on summarizing book reviews -- after all, this is what the company was founded on! We can see two product categories that fit the bill (`book` and `digital_ebook_purchase`), so let's filter the datasets in both languages for just these products. As we saw in [Chapter 5](/course/chapter5), the `Dataset.filter()` function allows us to slice a dataset very efficiently, so we can define a simple function to do this: + +```python +def filter_books(example): + return ( + example["product_category"] == "book" + or example["product_category"] == "digital_ebook_purchase" + ) +``` + +Now when we apply this function to `english_dataset` and `spanish_dataset`, the result will contain just those rows involving the book categories. Before applying the filter, let's switch the format of `english_dataset` from `"pandas"` back to `"arrow"`: + +```python +english_dataset.reset_format() +``` + +We can then apply the filter function, and as a sanity check let's inspect a sample of reviews to see if they are indeed about books: + +```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.' +``` + +Okay, we can see that the reviews are not strictly about books and might refer to things like calendars and electronic applications such as OneNote. Nevertheless, the domain seems about right to train a summarization model on. Before we look at various models that are suitable for this task, we have one last bit of data preparation to do: combining the English and Spanish reviews as a single `DatasetDict` object. 🤗 Datasets provides a handy `concatenate_datasets()` function that (as the name suggests) will stack two `Dataset` objects on top of each other. So, to create our bilingual dataset, we'll loop over each split, concatenate the datasets for that split, and shuffle the result to ensure our model doesn't overfit to a single language: + +```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) + +# Peek at a few examples +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' +``` + +This certainly looks like a mix of English and Spanish reviews! Now that we have a training corpus, one final thing to check is the distribution of words in the reviews and their titles. This is especially important for summarization tasks, where short reference summaries in the data can bias the model to only output one or two words in the generated summaries. The plots below show the word distributions, and we can see that the titles are heavily skewed toward just 1-2 words: + +
+Word count distributions for the review titles and texts. + +
+ +To deal with this, we'll filter out the examples with very short titles so that our model can produce more interesting summaries. Since we're dealing with English and Spanish texts, we can use a rough heuristic to split the titles on whitespace and then use our trusty `Dataset.filter()` method as follows: + +```python +books_dataset = books_dataset.filter(lambda x: len(x["review_title"].split()) > 2) +``` + +Now that we've prepared our corpus, let's take a look at a few possible Transformer models that one might fine-tune on it! + +## Models for text summarization + +If you think about it, text summarization is a similar sort of task to machine translation: we have a body of text like a review that we'd like to "translate" into a shorter version that captures the salient features of the input. Accordingly, most Transformer models for summarization adopt the encoder-decoder architecture that we first encountered in [Chapter 1](/course/chapter1), although there are some exceptions like the GPT family of models which can also be used for summarization in few-shot settings. The following table lists some popular pretrained models that can be fine-tuned for summarization. + +| Transformer model | Description | Multilingual? | +| :---------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------: | +| [GPT-2](https://huggingface.co/gpt2-xl) | Although trained as an auto-regressive language model, you can make GPT-2 generate summaries by appending "TL;DR" at the end of the input text. | ❌ | +| [PEGASUS](https://huggingface.co/google/pegasus-large) | Uses a pretraining objective to predict masked sentences in multi-sentence texts. This pretraining objective is closer to summarization than vanilla language modeling and scores highly on popular benchmarks. | ❌ | +| [T5](https://huggingface.co/t5-base) | A universal Transformer architecture that formulates all tasks in a text-to-text framework; e.g., the input format for the model to summarize a document is `summarize: ARTICLE`. | ❌ | +| [mT5](https://huggingface.co/google/mt5-base) | A multilingual version of T5, pretrained on the multilingual Common Crawl corpus (mC4), covering 101 languages. | ✅ | +| [BART](https://huggingface.co/facebook/bart-base) | A novel Transformer architecture with both an encoder and a decoder stack trained to reconstruct corrupted input that combines the pretraining schemes of BERT and GPT-2. | ❌ | +| [mBART-50](https://huggingface.co/facebook/mbart-large-50) | A multilingual version of BART, pretrained on 50 languages. | ✅ | + +As you can see from this table, the majority of Transformer models for summarization (and indeed most NLP tasks) are monolingual. This is great if your task is in a "high-resource" language like English or German, but less so for the thousands of other languages in use across the world. Fortunately, there is a class of multilingual Transformer models, like mT5 and mBART, that come to the rescue. These models are pretrained using language modeling, but with a twist: instead of training on a corpus of one language, they are trained jointly on texts in over 50 languages at once! + +We'll focus on mT5, an interesting architecture based on T5 that was pretrained in a text-to-text framework. In T5, every NLP task is formulated in terms of a prompt prefix like `summarize:` which conditions the model to adapt the generated text to the prompt. As shown in the figure below, this makes T5 extremely versatile, as you can solve many tasks with a single model! + +
+Different tasks performed by the T5 architecture. + +
+ +mT5 doesn't use prefixes, but shares much of the versatility of T5 and has the advantage of being multilingual. Now that we've picked a model, let's take a look at preparing our data for training. + + + + +✏️ **Try it out!** Once you've worked through this section, see how well mT5 compares to mBART by fine-tuning the latter with the same techniques. For bonus points, you can also try fine-tuning T5 on just the English reviews. Since T5 has a special prefix prompt, you'll need to prepend `summarize:` to the input examples in the preprocessing steps below. + + + +## Preprocessing the data + + + +Our next task is to tokenize and encode our reviews and their titles. As usual, we begin by loading the tokenizer associated with the pretrained model checkpoint. We'll use `mt5-small` as our checkpoint so we can fine-tune the model in a reasonable amount of time: + +```python +from transformers import AutoTokenizer + +model_checkpoint = "google/mt5-small" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + + + +💡 In the early stages of your NLP projects, a good practice is to train a class of "small" models on a small sample of data. This allows you to debug and iterate faster toward an end-to-end workflow. Once you are confident in the results, you can always scale up the model by simply changing the model checkpoint! + + + +Let's test out the mT5 tokenizer on a small example: + +```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]} +``` + +Here we can see the familiar `input_ids` and `attention_mask` that we encountered in our first fine-tuning experiments back in [Chapter 3](/course/chapter3). Let's decode these input IDs with the tokenizer's `convert_ids_to_tokens()` function to see what kind of tokenizer we're dealing with: + +```python +tokenizer.convert_ids_to_tokens(inputs.input_ids) +``` + +```python out +['▁I', '▁', 'loved', '▁reading', '▁the', '▁Hung', 'er', '▁Games', ''] +``` + +The special Unicode character `▁` and end-of-sequence token `` indicate that we're dealing with the SentencePiece tokenizer, which is based on the Unigram segmentation algorithm discussed in [Chapter 6](/course/chapter6). Unigram is especially useful for multilingual corpora since it allows SentencePiece to be agnostic about accents, punctuation, and the fact that many languages, like Japanese, do not have whitespace characters. + +To tokenize our corpus, we have to deal with a subtlety associated with summarization: because our labels are also text, it is possible that they exceed the model's maximum context size. This means we need to apply truncation to both the reviews and their titles to ensure we don't pass excessively long inputs to our model. The tokenizers in 🤗 Transformers provide a nifty `as_target_tokenizer()` function that allows you to tokenize the labels in parallel to the inputs. This is typically done using a context manager inside a preprocessing function that first encodes the inputs, and then encodes the labels as a separate column. Here is an example of such a function for 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 + ) + # Set up the tokenizer for targets + with tokenizer.as_target_tokenizer(): + labels = tokenizer( + examples["review_title"], max_length=max_target_length, truncation=True + ) + + model_inputs["labels"] = labels["input_ids"] + return model_inputs +``` + +Let's walk through this code to understand what's happening. The first thing we've done is define values for `max_input_length` and `max_target_length`, which set the upper limits for how long our reviews and titles can be. Since the review body is typically much larger than the title, we've scaled these values accordingly. Then, in the `preprocess_function()` itself we can see the reviews are first tokenized, followed by the titles with `as_target_tokenizer()`. + +With `preprocess_function()`, it is then a simple matter to tokenize the whole corpus using the handy `Dataset.map()` function we've used extensively throughout this course: + +```python +tokenized_datasets = books_dataset.map(preprocess_function, batched=True) +``` + +Now that the corpus has been preprocessed, let's take a look at some metrics that are commonly used for summarization. As we'll see, there is no silver bullet when it comes to measuring the quality of machine-generated text. + + + +💡 You may have noticed that we used `batched=True` in our `Dataset.map()` function above. This encodes the examples in batches of 1,000 (the default) and allows you to make use of the multithreading capabilities of the fast tokenizers in 🤗 Transformers. Where possible, try using `batched=True` to get the most out of your preprocessing! + + + + +## Metrics for text summarization + + + +In comparison to most of the other tasks we've covered in this course, measuring the performance of text generation tasks like summarization or translation is not as straightforward. For example, given a review like "I loved reading the Hunger Games", there are multiple valid summaries, like "I loved the Hunger Games" or "Hunger Games is a great read". Clearly, applying some sort of exact match between the generated summary and the label is not a good solution -- even humans would fare poorly under such a metric, because we all have our own writing style. + +For summarization, one of the most commonly used metrics is the [ROUGE score](https://en.wikipedia.org/wiki/ROUGE_(metric)) (short for Recall-Oriented Understudy for Gisting Evaluation). The basic idea behind this metric is to compare a generated summary against a set of reference summaries that are typically created by humans. To make this more precise, suppose we want to compare the following two summaries: + +```python +generated_summary = "I absolutely loved reading the Hunger Games" +reference_summary = "I loved reading the Hunger Games" +``` + +One way to compare them could be to count the number of overlapping words, which in this case would be 6. However, this is a bit crude, so instead ROUGE is based on computing the _precision_ and _recall_ scores for the overlap. + + + +🙋 Don't worry if this is the first time you've heard of precision and recall -- we'll go through some explicit examples together to make it all clear. These metrics are usually encountered in classification tasks, so if you want to understand how precision and recall are defined in that context, we recommend checking out the `scikit-learn` [guides](https://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html). + + + +For ROUGE, recall measures how much of the reference summary is captured by the generated one. If we are just comparing words, recall can be calculated according to the following formula: + +$$ \mathrm{Recall} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, reference\, summary}} $$ + +For our simple example above, this formula gives a perfect recall of 6/6 = 1; i.e., all the words in the reference summary have been produced by the model. This may sound great, but imagine if our generated summary had been "I really really loved reading the Hunger Games all night". This would also have perfect recall, but is arguably a worse summary since it is verbose. To deal with these scenarios we also compute the precision, which in the ROUGE context measures how much of the generated summary was relevant: + +$$ \mathrm{Precision} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, generated\, summary}} $$ + +Applying this to our verbose summary gives a precision of 6/10 = 0.6, which is considerably worse than the precision of 6/7 = 0.86 obtained by our shorter one. In practice, both precision and recall are usually computed, and then the F1-score (the harmonic mean of precision and recall) is reported. We can do this easily in 🤗 Datasets by first installing the `rouge_score` package: + +```py +!pip install rouge_score +``` + +and then loading the ROUGE metric as follows: + +```python +from datasets import load_metric + +rouge_score = load_metric("rouge") +``` + +Then we can use the `rouge_score.compute()` function to calculate all the metrics at once: + +```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))} +``` + +Whoa, there's a lot of information in that output -- what does it all mean? First, 🤗 Datasets actually computes confidence intervals for precision, recall, and F1-score; these are the `low`, `mid`, and `high` attributes you can see here. Moreover, 🤗 Datasets computes a variety of ROUGE scores which are based on different types of text granularity when comparing the generated and reference summaries. The `rouge1` variant is the overlap of unigrams -- this is just a fancy way of saying the overlap of words and is exactly the metric we've discussed above. To verify this, let's pull out the `mid` value of our scores: + +```python +scores["rouge1"].mid +``` + +```python out +Score(precision=0.86, recall=1.0, fmeasure=0.92) +``` + +Great, the precision and recall numbers match up! Now what about those other ROUGE scores? `rouge2` measures the overlap between bigrams (think the overlap of pairs of words), while `rougeL` and `rougeLsum` measure the longest matching sequences of words by looking for the longest common substrings in the generated and reference summaries. The "sum" in `rougeLsum` refers to the fact that this metric is computed over a whole summary, while `rougeL` is computed as the average over individual sentences. + + + +✏️ **Try it out!** Create your own example of a generated and reference summary and see if the resulting ROUGE scores agree with a manual calculation based on the formulas for precision and recall. For bonus points, split the text into bigrams and compare the precision and recall for the `rouge2` metric. + + + +We'll use these ROUGE scores to track the performance of our model, but before doing that let's do something every good NLP practitioner should do: create a strong, yet simple baseline! + +### Creating a strong baseline + +A common baseline for text summarization is to simply take the first three sentences of an article, often called the _lead-3_ baseline. We could use full stops to track the sentence boundaries, but this will fail on acronyms like "U.S." or "U.N." -- so instead we'll use the `nltk` library, which includes a better algorithm to handle these cases. You can install the package using `pip` as follows: + +```python +!pip install nltk +``` + +and then download the punctuation rules: + +```python +import nltk + +nltk.download("punkt") +``` + +Next, we import the sentence tokenizer from `nltk` and create a simple function to extract the first three sentences in a review. The convention in text summarization is to separate each summary with a newline, so let's also include this and test it on a training example: + +```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.' +``` + +This seems to work, so let's now implement a function that extracts these "summaries" from a dataset and computes the ROUGE scores for the baseline: + +```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"]) +``` + +We can then use this function to compute the ROUGE scores over the validation set and prettify them a bit using 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} +``` + +We can see that the `rouge2` score is significantly lower than the rest; this likely reflects the fact that review titles are typically concise and so the lead-3 baseline is too verbose. Now that we have a good baseline to work from, let's turn our attention toward fine-tuning mT5! + +{#if fw === 'pt'} + +## Fine-tuning mT5 with the `Trainer` API + +Fine-tuning a model for summarization is very similar to the other tasks we've covered in this chapter. The first thing we need to do is load the pretrained model from the `mt5-small` checkpoint. Since summarization is a sequence-to-sequence task, we can load the model with the `AutoModelForSeq2SeqLM` class, which will automatically download and cache the weights: + +```python +from transformers import AutoModelForSeq2SeqLM + +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{:else} + +## Fine-tuning mT5 with Keras + +Fine-tuning a model for summarization is very similar to the other tasks we've covered in this chapter. The first thing we need to do is load the pretrained model from the `mt5-small` checkpoint. Since summarization is a sequence-to-sequence task, we can load the model with the `TFAutoModelForSeq2SeqLM` class, which will automatically download and cache the weights: + +```python +from transformers import TFAutoModelForSeq2SeqLM + +model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{/if} + + + +💡 If you're wondering why you don't see any warnings about fine-tuning the model on a downstream task, that's because for sequence-to-sequence tasks we keep all the weights of the network. Compare this to our text classification model in [Chapter 3](/course/chapter3), where the head of the pretrained model was replaced with a randomly initialized network. + + + +The next thing we need to do is log in to the Hugging Face Hub. If you're running this code in a notebook, you can do so with the following utility function: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +which will display a widget where you can enter your credentials. Alternatively, you can run this command in your terminal and log in there: + +``` +huggingface-cli login +``` + +{#if fw === 'pt'} + +We'll need to generate summaries in order to compute ROUGE scores during training. Fortunately, 🤗 Transformers provides dedicated `Seq2SeqTrainingArguments` and `Seq2SeqTrainer` classes that can do this for us automatically! To see how this works, let's first define the hyperparameters and other arguments for our experiments: + +```python +from transformers import Seq2SeqTrainingArguments + +batch_size = 8 +num_train_epochs = 8 +# Show the training loss with every epoch +logging_steps = len(tokenized_datasets["train"]) // batch_size +model_name = model_checkpoint.split("/")[-1] + +args = Seq2SeqTrainingArguments( + output_dir=f"{model_name}-finetuned-amazon-en-es", + evaluation_strategy="epoch", + learning_rate=5.6e-5, + per_device_train_batch_size=batch_size, + per_device_eval_batch_size=batch_size, + weight_decay=0.01, + save_total_limit=3, + num_train_epochs=num_train_epochs, + predict_with_generate=True, + logging_steps=logging_steps, + push_to_hub=True, +) +``` + +Here, the `predict_with_generate` argument has been set to indicate that we should generate summaries during evaluation so that we can compute ROUGE scores for each epoch. As discussed in [Chapter 1](/course/chapter1), the decoder performs inference by predicting tokens one by one, and this is implemented by the model's `generate()` method. Setting `predict_with_generate=True` tells the `Seq2SeqTrainer` to use that method for evaluation. We've also adjusted some of the default hyperparameters, like the learning rate, number of epochs, and weight decay, and we've set the `save_total_limit` option to only save up to 3 checkpoints during training -- this is because even the "small" version of mT5 uses around a GB of hard drive space, and we can save a bit of room by limiting the number of copies we save. + +The `push_to_hub=True` argument will allow us to push the model to the Hub after training; you'll find the repository under your user profile in the location defined by `output_dir`. Note that you can specify the name of the repository you want to push to with the `hub_model_id` argument (in particular, you will have to use this argument to push to an organization). For instance, when we pushed the model to the [`huggingface-course` organization](https://huggingface.co/huggingface-course), we added `hub_model_id="huggingface-course/mt5-finetuned-amazon-en-es"` to `Seq2SeqTrainingArguments`. + +The next thing we need to do is provide the trainer with a `compute_metrics()` function so that we can evaluate our model during training. For summarization this is a bit more involved than simply calling `rouge_score.compute()` on the model's predictions, since we need to _decode_ the outputs and labels into text before we can compute the ROUGE scores. The following function does exactly that, and also makes use of the `sent_tokenize()` function from `nltk` to separate the summary sentences with newlines: + +```python +import numpy as np + + +def compute_metrics(eval_pred): + predictions, labels = eval_pred + # Decode generated summaries into text + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + # Replace -100 in the labels as we can't decode them + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + # Decode reference summaries into text + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + # ROUGE expects a newline after each sentence + decoded_preds = ["\n".join(sent_tokenize(pred.strip())) for pred in decoded_preds] + decoded_labels = ["\n".join(sent_tokenize(label.strip())) for label in decoded_labels] + # Compute ROUGE scores + result = rouge_score.compute( + predictions=decoded_preds, references=decoded_labels, use_stemmer=True + ) + # Extract the median scores + result = {key: value.mid.fmeasure * 100 for key, value in result.items()} + return {k: round(v, 4) for k, v in result.items()} +``` + +{/if} + +Next, we need to define a data collator for our sequence-to-sequence task. Since mT5 is an encoder-decoder Transformer model, one subtlety with preparing our batches is that during decoding we need to shift the labels to the right by one. This is required to ensure that the decoder only sees the previous ground truth labels and not the current or future ones, which would be easy for the model to memorize. This is similar to how masked self-attention is applied to the inputs in a task like [causal language modeling](/course/chapter7/6). + +Luckily, 🤗 Transformers provides a `DataCollatorForSeq2Seq` collator that will dynamically pad the inputs and the labels for us. To instantiate this collator, we simply need to provide the `tokenizer` and `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} + +Let's see what this collator produces when fed a small batch of examples. First, we need to remove the columns with strings because the collator won't know how to pad these elements: + +```python +tokenized_datasets = tokenized_datasets.remove_columns( + books_dataset["train"].column_names +) +``` + +Since the collator expects a list of `dict`s, where each `dict` represents a single example in the dataset, we also need to wrangle the data into the expected format before passing it to the data collator: + +```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]])} +``` + +The main thing to notice here is that the first example is longer than the second one, so the `input_ids` and `attention_mask` of the second example have been padded on the right with a `[PAD]` token (whose ID is `0`). Similarly, we can see that the `labels` have been padded with `-100`s, to make sure the padding tokens are ignored by the loss function. And finally, we can see a new `decoder_input_ids` which has shifted the labels to the right by inserting a `[PAD]` token in the first entry. + +{#if fw === 'pt'} + +We finally have all the ingredients we need to train with! We now simply need to instantiate the trainer with the standard arguments: + +```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, +) +``` + +and launch our training run: + +```python +trainer.train() +``` + +During training, you should see the training loss decrease and the ROUGE scores increase with each epoch. Once the training is complete, you can see the final ROUGE scores by running `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} +``` + +From the scores we can see that our model has handily outperformed our lead-3 baseline -- nice! The final thing to do is push the model weights to the Hub, as follows: + +``` +trainer.push_to_hub(commit_message="Training complete", tags="summarization") +``` + +```python out +'https://huggingface.co/huggingface-course/mt5-finetuned-amazon-en-es/commit/aa0536b829b28e73e1e4b94b8a5aacec420d40e0' +``` + +This will save the checkpoint and configuration files to `output_dir`, before uploading all the files to the Hub. By specifying the `tags` argument, we also ensure that the widget on the Hub will be one for a summarization pipeline instead of the default text generation one associated with the mT5 architecture (for more information about model tags, see the [🤗 Hub documentation](https://huggingface.co/docs/hub/main#how-is-a-models-type-of-inference-api-and-widget-determined)). The output from `trainer.push_to_hub()` is a URL to the Git commit hash, so you can easily see the changes that were made to the model repository! + +To wrap up this section, let's take a look at how we can also fine-tune mT5 using the low-level features provided by 🤗 Accelerate. + +{:else} + +We're almost ready to train! We just need to convert our datasets to `tf.data.Dataset`s using the data collator we defined above, and then `compile()` and `fit()` the model. First, the datasets: + +```python +tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=8, +) +tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=8, +) +``` + +Now, we define our training hyperparameters and compile: + +```python +from transformers import create_optimizer +import tensorflow as tf + +# 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_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) + +# Train in mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +And finally, we fit the model. We use a `PushToHubCallback` to save the model to the Hub after each epoch, which will allow us to use it for inference later: + +```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 +) +``` + +We got some loss values during training, but really we'd like to see the ROUGE metrics we computed earlier. To get those metrics, we'll need to generate outputs from the model and convert them to strings. Let's build some lists of labels and predictions for the ROUGE metric to compare (note that if you get import errors for this section, you may need to`!pip install tqdm`): + +```python +from tqdm import tqdm +import numpy as np + +all_preds = [] +all_labels = [] +for batch in tqdm(tf_eval_dataset): + predictions = model.generate(**batch) + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + labels = batch["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) +``` + +Once we have our lists of label and prediction strings, computing the ROUGE score is easy: + +```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'} + +## Fine-tuning mT5 with 🤗 Accelerate + +Fine-tuning our model with 🤗 Accelerate is very similar to the text classification example we encountered in [Chapter 3](/course/chapter3). The main differences will be the need to explicitly generate our summaries during training and define how we compute the ROUGE scores (recall that the `Seq2SeqTrainer` took care of the generation for us). Let's take a look how we can implement these two requirements within 🤗 Accelerate! + +### Preparing everything for training + +The first thing we need to do is create a `DataLoader` for each of our splits. Since the PyTorch dataloaders expect batches of tensors, we need to set the format to `"torch"` in our datasets: + +```python +tokenized_datasets.set_format("torch") +``` + +Now that we've got datasets consisting of just tensors, the next thing to do is instantiate the `DataCollatorForSeq2Seq` again. For this we need to provide a fresh version of the model, so let's load it again from our cache: + +```python +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +We can then instantiate the data collator and use this to define our dataloaders: + +```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 +) +``` + +The next thing to do is define the optimizer we want to use. As in our other examples, we'll use `AdamW`, which works well for most problems: + +```python +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Finally, we feed our model, optimizer, and dataloaders to the `accelerator.prepare()` method: + +```python +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + + + +🚨 If you're training on a TPU, you'll need to move all the code above into a dedicated training function. See [Chapter 3](/course/chapter3) for more details. + + + +Now that we've prepared our objects, there are three remaining things to do: + +* Define the learning rate schedule. +* Implement a function to post-process the summaries for evaluation. +* Create a repository on the Hub that we can push our model to. + +For the learning rate schedule, we'll use the standard linear one from previous sections: + +```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, +) +``` + +For post-processing, we need a function that splits the generated summaries into sentences that are separated by newlines. This is the format the ROUGE metric expects, and we can achieve this with the following snippet of code: + +```python +def postprocess_text(preds, labels): + preds = [pred.strip() for pred in preds] + labels = [label.strip() for label in labels] + + # ROUGE expects a newline after each sentence + preds = ["\n".join(nltk.sent_tokenize(pred)) for pred in preds] + labels = ["\n".join(nltk.sent_tokenize(label)) for label in labels] + + return preds, labels +``` + +This should look familiar to you if you recall how we defined the `compute_metrics()` function of the `Seq2SeqTrainer`. + +Finally, we need to create a model repository on the Hugging Face Hub. For this, we can use the appropriately titled 🤗 Hub library. We just need to define a name for our repository, and the library has a utility function to combine the repository ID with the user profile: + +```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' +``` + +Now we can use this repository name to clone a local version to our results directory that will store the training artifacts: + +```python +from huggingface_hub import Repository + +output_dir = "results-mt5-finetuned-squad-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +This will allow us to push the artifacts back to the Hub by calling the `repo.push_to_hub()` method during training! Let's now wrap up our analysis by writing out the training loop. + +### Training loop + +The training loop for summarization is quite similar to the other 🤗 Accelerate examples that we've encountered and is roughly split into four main steps: + +1. Train the model by iterating over all the examples in `train_dataloader` for each epoch. +2. Generate model summaries at the end of each epoch, by first generating the tokens and then decoding them (and the reference summaries) into text. +3. Compute the ROUGE scores using the same techniques we saw earlier. +4. Save the checkpoints and push everything to the Hub. Here we rely on the nifty `blocking=False` argument of the `Repository` object so that we can push the checkpoints per epoch _asynchronously_. This allows us to continue training without having to wait for the somewhat slow upload associated with a GB-sized model! + +These steps can be seen in the following block of code: + +```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): + # Training + model.train() + for step, batch in enumerate(train_dataloader): + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) + + # Evaluation + model.eval() + for step, batch in enumerate(eval_dataloader): + with torch.no_grad(): + generated_tokens = accelerator.unwrap_model(model).generate( + batch["input_ids"], + attention_mask=batch["attention_mask"], + ) + + generated_tokens = accelerator.pad_across_processes( + generated_tokens, dim=1, pad_index=tokenizer.pad_token_id + ) + labels = batch["labels"] + + # If we did not pad to max length, we need to pad the labels too + labels = accelerator.pad_across_processes( + batch["labels"], dim=1, pad_index=tokenizer.pad_token_id + ) + + generated_tokens = accelerator.gather(generated_tokens).cpu().numpy() + labels = accelerator.gather(labels).cpu().numpy() + + # Replace -100 in the labels as we can't decode them + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + if isinstance(generated_tokens, tuple): + generated_tokens = generated_tokens[0] + decoded_preds = tokenizer.batch_decode( + generated_tokens, skip_special_tokens=True + ) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + decoded_preds, decoded_labels = postprocess_text( + decoded_preds, decoded_labels + ) + + rouge_score.add_batch(predictions=decoded_preds, references=decoded_labels) + + # Compute metrics + result = rouge_score.compute() + # Extract the median ROUGE scores + result = {key: value.mid.fmeasure * 100 for key, value in result.items()} + result = {k: round(v, 4) for k, v in result.items()} + print(f"Epoch {epoch}:", result) + + # Save and upload + accelerator.wait_for_everyone() + unwrapped_model = accelerator.unwrap_model(model) + unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) + if accelerator.is_main_process: + tokenizer.save_pretrained(output_dir) + repo.push_to_hub( + commit_message=f"Training in progress epoch {epoch}", blocking=False + ) +``` + +```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} +``` + +And that's it! Once you run this, you'll have a model and results that are pretty similar to the ones we obtained with the `Trainer`. + +{/if} + +## Using your fine-tuned model + +Once you've pushed the model to the Hub, you can play with it either via the inference widget or with a `pipeline` object, as follows: + +```python +from transformers import pipeline + +hub_model_id = "huggingface-course/mt5-small-finetuned-amazon-en-es" +summarizer = pipeline("summarization", model=hub_model_id) +``` + +We can feed some examples from the test set (which the model has not seen) to our pipeline to get a feel for the quality of the summaries. First let's implement a simple function to show the review, title, and generated summary together: + +```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}'") +``` + +Let's take a look at one of the English examples we get: + +```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' +``` + +This is not too bad! We can see that our model has actually been able to perform _abstractive_ summarization by augmenting parts of the review with new words. And perhaps the coolest aspect of our model is that it is bilingual, so we can also generate summaries of Spanish reviews: + +```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' +``` + +The summary translates into "Very easy to read" in English, which we can see in this case was extracted directly from the review. Nevertheless, this shows the versatility of the mT5 model and has given you a taste of what it's like to deal with a multilingual corpus! + +Next, we'll turn our attention to a slightly more complex task: training a language model from scratch. diff --git a/chapters/en/chapter9/3.mdx b/chapters/en/chapter9/3.mdx index 7ce306c77..be34af06d 100644 --- a/chapters/en/chapter9/3.mdx +++ b/chapters/en/chapter9/3.mdx @@ -20,7 +20,7 @@ These parameters are: - `fn`: the prediction function that is wrapped by the Gradio interface. This function can take one or more parameters and return one or more values - `inputs`: the input component type(s). Gradio provides many pre-built components such as`"image"` or `"mic"`. - - `outputs`: the output component type(s). Again, `gradio` provides many pre-built components e.g. `"image"` or `"label"`. + - `outputs`: the output component type(s). Again, Gradio provides many pre-built components e.g. `"image"` or `"label"`. For a complete list of components, [see the Gradio docs ](https://gradio.app/docs). Each pre-built component can be customized by instantiating the class corresponding to the component. diff --git a/chapters/en/chapter9/8.mdx b/chapters/en/chapter9/8.mdx index de661e346..9380d2e50 100644 --- a/chapters/en/chapter9/8.mdx +++ b/chapters/en/chapter9/8.mdx @@ -10,8 +10,10 @@ This wraps up the chapter on building cool ML demos with Gradio - we hope you en If you'd like to test your understanding of the concepts covered in this chapter, check out the quiz in the next section! -## Gradio blocks party 🥳 +## Where to next? -If you want to put the knowledge from this chapter to good use, come join the Gradio blocks party! This is a community event that's hosted by Hugging Face on **May 16-31**. During this event, you'll build cool machine learning demos with Gradio and be in the running to win Hugging Face swag and prizes! +If you want to learn more about Gradio you can -Check out the [event description](https://github.com/AK391/community-events/blob/main/gradio-blocks/README.md) for details on how to participate - we can't wait to see what you'll build 🤗! \ No newline at end of file +- Take a look at [Demos](https://github.com/gradio-app/gradio/tree/main/demo) in the repo, there are quite a lot of examples there. +- See the [Guides](https://gradio.app/guides/) page, where you can find guides about cool and advanced features. +- Check the [Docs](https://gradio.app/docs/) page to learn the details. diff --git a/chapters/en/chapter9/9.mdx b/chapters/en/chapter9/9.mdx index b5e73698b..7d2dfb8db 100644 --- a/chapters/en/chapter9/9.mdx +++ b/chapters/en/chapter9/9.mdx @@ -15,7 +15,7 @@ Let's test what you learned in this chapter! }, { text: "Share your machine learning model with others", - explain: "Using the share=True parameter in the launch method, you can generate a share link to send to anyone.", + explain: "Using the share=True parameter in the launch method, you can generate a share link to send to anyone.", correct: true }, { @@ -36,7 +36,7 @@ Let's test what you learned in this chapter! choices={[ { text: "True", - explain: "Gradio works with pytorch models, but also works for any type of machine learning model!" + explain: "Gradio works with PyTorch models, but also works for any type of machine learning model!" }, { text: "False", @@ -109,7 +109,7 @@ Let's test what you learned in this chapter! }, { text: "Loading a model from Hugging Face's model hub or Hugging Face Spaces", - explain: "Absolutely - load any Hugging Face model using the gr.Interface.load() method", + explain: "Absolutely - load any Hugging Face model using the gr.Interface.load() method", correct: true } ]} diff --git a/chapters/es/_toctree.yml b/chapters/es/_toctree.yml index 0250693da..5129356c0 100644 --- a/chapters/es/_toctree.yml +++ b/chapters/es/_toctree.yml @@ -40,8 +40,9 @@ title: Introducción - local: chapter3/2 title: Procesamiento de los datos - - + - local: chapter3/4 + title: Entrenamiento completo + - title: 8. ¿Cómo solicitar ayuda? sections: - local: chapter8/1 @@ -49,7 +50,6 @@ - local: chapter8/2 title: ¿Qué hacer cuando se produce un error? - - title: Glosario sections: - local: glossary/1 diff --git a/chapters/es/chapter3/4.mdx b/chapters/es/chapter3/4.mdx new file mode 100644 index 000000000..c16fa7d58 --- /dev/null +++ b/chapters/es/chapter3/4.mdx @@ -0,0 +1,357 @@ +# Un entrenamiento completo + + + + + +Ahora veremos como obtener los mismos resultados de la última sección sin hacer uso de la clase `Trainer`. De nuevo, asumimos que has hecho el procesamiento de datos en la sección 2. Aquí mostramos un resumen que cubre todo lo que necesitarás. + +```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) +``` + +### 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: + +- 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`). +- Configurar el formato de los conjuntos de datos para que retornen tensores PyTorch en lugar de listas. + +Nuestro `tokenized_datasets` tiene un método para cada uno de esos pasos: + +```py +tokenized_datasets = tokenized_datasets.remove_columns(["sentence1", "sentence2", "idx"]) +tokenized_datasets = tokenized_datasets.rename_column("label", "labels") +tokenized_datasets.set_format("torch") +tokenized_datasets["train"].column_names +``` + +Ahora podemos verificar que el resultado solo tiene columnas que nuestro modelo aceptará: + +```python +["attention_mask", "input_ids", "labels", "token_type_ids"] +``` + +Ahora que esto esta hecho, es fácil definir nuestros dataloaders: + +```py +from torch.utils.data import DataLoader + +train_dataloader = DataLoader( + tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator +) +eval_dataloader = DataLoader( + tokenized_datasets["validation"], batch_size=8, collate_fn=data_collator +) +``` + +Para verificar rápidamente que no hubo errores en el procesamiento de datos, podemos inspeccionar un lote de la siguiente manera: + +```py +for batch in train_dataloader: + break +{k: v.shape for k, v in batch.items()} +``` + +```python out +{'attention_mask': torch.Size([8, 65]), + 'input_ids': torch.Size([8, 65]), + 'labels': torch.Size([8]), + 'token_type_ids': torch.Size([8, 65])} +``` + +Nótese que los tamaños serán un poco distintos en tu caso ya que configuramos `shuffle=True` para el dataloader de entrenamiento y estamos rellenando a la máxima longitud dentro del lote. + +Ahora que hemos completado el preprocesamiento de datos (un objetivo gratificante y al mismo tiempo elusivo para cual cualquier practicante de ML), enfoquémonos en el modelo. Lo vamos a crear exactamente como lo hicimos en la sección anterior. + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +Para asegurarnos de que todo va a salir sin problems durante el entrenamiento, vamos a pasar un lote a este modelo: + +```py +outputs = model(**batch) +print(outputs.loss, outputs.logits.shape) +``` + +```python out +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). + +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): + +```py +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: + +```py +from transformers import get_scheduler + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) +print(num_training_steps) +``` + +```python out +1377 +``` + +### El bucle de entrenamiento + +Una última cosa: vamos a querer usar el GPU si tenemos acceso a uno (en un CPU, el entrenamiento puede tomar varias horas en lugar de unos pocos minutos). Para hacer esto, definimos un `device` sobre el que pondremos nuestro modelo y nuestros lotes: + +```py +import torch + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) +device +``` + +```python out +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`: + +```py +from tqdm.auto import tqdm + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Puedes ver que la parte central del bucle de entrenamiento luce bastante como el de la introducción. No se incluyó ningún tipo de reportes, asi que este bucle de entrenamiento no va a indicar como se esta desempeñando el modelo. Para eso necesitamos añadir un bucle de evaluación. + +### El bucle de evaluación + +Como lo hicimos anteriormente, usaremos una métrica ofrecida por la libreria Datasets 🤗. 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: + +```py +from datasets import load_metric + +metric = load_metric("glue", "mrpc") +model.eval() +for batch in eval_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + with torch.no_grad(): + outputs = model(**batch) + + logits = outputs.logits + predictions = torch.argmax(logits, dim=-1) + metric.add_batch(predictions=predictions, references=batch["labels"]) + +metric.compute() +``` + +```python out +{'accuracy': 0.8431372549019608, 'f1': 0.8907849829351535} +``` + +De nuevo, tus resultados serán un tanto diferente debido a la inicialización aleatoria en la cabeza del modelo y el mezclado de los datos, pero deberían tener valores similares. + + + +✏️ **Inténtalo!** Modifica el bucle de entrenamiento anterior para ajustar tu modelo en el conjunto de datos SST-2. + + + +### Repotencia tu bucle de entrenamiento con Accelerate 🤗 + + + +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: + +```py +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Y aqui están los cambios: + +```diff ++ from accelerate import Accelerator + from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + ++ accelerator = Accelerator() + + model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) + optimizer = AdamW(model.parameters(), lr=3e-5) + +- device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +- model.to(device) + ++ train_dataloader, eval_dataloader, model, optimizer = accelerator.prepare( ++ train_dataloader, eval_dataloader, model, optimizer ++ ) + + num_epochs = 3 + num_training_steps = num_epochs * len(train_dataloader) + lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps + ) + + progress_bar = tqdm(range(num_training_steps)) + + model.train() + for epoch in range(num_epochs): + for batch in train_dataloader: +- batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss +- loss.backward() ++ accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + 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`). + +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)`. + + +⚠️ 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 🤗: + +```py +from accelerate import Accelerator +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +accelerator = Accelerator() + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +train_dl, eval_dl, model, optimizer = accelerator.prepare( + train_dataloader, eval_dataloader, model, optimizer +) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dl) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dl: + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +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 +``` + +el cual hará algunas preguntas y guardará tus respuestas en un archivo de configuración usado por este comando: + +``` +accelerate launch train.py +``` + +el cual iniciará en entrenamiento distribuido. + +Si deseas ejecutar esto en un Notebook (por ejemplo, para probarlo con TPUs en Colab), solo pega el código en una `training_function()` y ejecuta la última celda con: + +```python +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). diff --git a/chapters/es/glossary/1.mdx b/chapters/es/glossary/1.mdx index 6879ce284..562cf2fe9 100644 --- a/chapters/es/glossary/1.mdx +++ b/chapters/es/glossary/1.mdx @@ -1,73 +1,78 @@ # Vocabulario -| Original | Spanish | -|-----------------------------|--------------------------------- | -| Abstraction | Abstracción | -| Accuracy | Exactitud | -| Backward Pass | Pasada en reverso | -| Batch | Lote | -| Benchmark | Punto de referencia | -| Cache | Almacenamiento | -| Caching | Almacenar | -| Chapter | Capítulo | -| Checkpoint | Punto de control | -| Class | Clase | -| Code | Código | -| Colab Notebook | Colab Notebook | -| Command | Comando | -| Configuration | Configuración | -| Course | Curso | -| Dependency | Dependencia | -| Deployment | Deployment | -| Development | Desarrollo | -| Dictionary | Diccionario | -| Distribution | Distribución | -| Download | Descargar | -| F1 score | F1 score | -| Feature | Feature | -| Field | Atributo | -| Fine-tuning | Ajustar | -| Folder | Carpeta | -| Forward Pass | Pasada hacia delante | -| Function | Función | -| Google | Google | -| Hugging Face | Hugging Face | -| Incompatibility | Incompatibilidad | -| Inference | Inferencia | -| Key (in a dictionary) | Llave | -| Library | Libreria | -| Linux | Linux | -| Load | Cargar | -| Loss function | Función de pérdida | -| Loop | Bucle | -| macOS | macOS | -| Model | Modelo | -| Model Hub | Hub de Modelos | -| Module | Módulo | -| Natural Language Processing | Procesamiento de Lenguaje Natural | -| Package | Paquete | -| Package Manager | Manejador de paquete | -| Padding | Relleno | -| Parameter | Parámetro | -| Python | Python | -| Pytorch | Pytorch | -| Save | Guardar | -| Script | Script | -| Self-Contained | Auto-contenido | -| Setup | Instalación | -| TensorFlow | Tensorflow | -| Terminal | Terminal | -| Tokenizer | Tokenizador | -| Train | Entrenar | -| Transformer | Transformer | -| Virtual Environment | Ambiente Virtual | -| Weight | Peso | -| Weights | Pesos | -| Windows | Windows | -| Working Environment | Ambiente de Trabajo | -| Workload | Carga de trabajo | -| Workspace | Workspace | - +| Original | Spanish | +|-----------------------------|--------------------------------- | +| Abstraction | Abstracción | +| Accuracy | Exactitud | +| Backward Pass | Pasada en reverso | +| Batch | Lote | +| Benchmark | Punto de referencia | +| Cache | Almacenamiento | +| Caching | Almacenar | +| Chapter | Capítulo | +| Checkpoint | Punto de control | +| Class | Clase | +| Code | Código | +| Colab Notebook | Colab Notebook | +| Command | Comando | +| Configuration | Configuración | +| Course | Curso | +| Dataloader | Dataloader | +| Dependency | Dependencia | +| Deployment | Deployment | +| Development | Desarrollo | +| Dictionary | Diccionario | +| Distribution | Distribución | +| Download | Descargar | +| F1 score | F1 score | +| Feature | Feature | +| Field | Atributo | +| Fine-tuning | Ajustar | +| Folder | Carpeta | +| Forward Pass | Pasada hacia delante | +| Function | Función | +| Google | Google | +| Hugging Face | Hugging Face | +| Incompatibility | Incompatibilidad | +| Inference | Inferencia | +| Key (in a dictionary) | Llave | +| Learning rate | Rata de aprendizaje | +| Library | Libreria | +| Linux | Linux | +| Load | Cargar | +| Loss | Pérdida | +| Loss function | Función de pérdida | +| Loop | Bucle | +| macOS | macOS | +| Model | Modelo | +| Model Hub | Hub de Modelos | +| Module | Módulo | +| Natural Language Processing | Procesamiento de Lenguaje Natural | +| Package | Paquete | +| Package Manager | Manejador de paquete | +| Padding | Relleno | +| Parameter | Parámetro | +| Python | Python | +| Pytorch | Pytorch | +| Samples | Muestras | +| Save | Guardar | +| Scheduler | Programador | +| Script | Script | +| Self-Contained | Auto-contenido | +| Setup | Instalación | +| TensorFlow | Tensorflow | +| Terminal | Terminal | +| Tokenizer | Tokenizador | +| Train | Entrenar | +| Transformer | Transformer | +| Virtual Environment | Ambiente Virtual | +| Weight | Peso | +| Weight decay regularization | Regularización de decremento de los pesos | +| Weights | Pesos | +| Windows | Windows | +| Working Environment | Ambiente de Trabajo | +| Workload | Carga de trabajo | +| Workspace | Workspace | ## Abbreviations diff --git a/chapters/fr/chapter9/8.mdx b/chapters/fr/chapter9/8.mdx index ee6017d04..418e389e6 100644 --- a/chapters/fr/chapter9/8.mdx +++ b/chapters/fr/chapter9/8.mdx @@ -10,8 +10,9 @@ Ceci conclut le chapitre sur la construction de démos d'apprentissage automatiq Si vous souhaitez tester votre compréhension des concepts abordés dans ce chapitre, consultez le quiz dans la section suivante ! -## La Gradio blocks party 🥳 +## Où aller ensuite ? -Si vous voulez mettre à profit les connaissances de ce chapitre, venez rejoindre la *Gradio blocks party* ! Il s'agit d'un événement communautaire organisé par Hugging Face du **16 au 31 mai**. Au cours de cet événement, vous construirez des démos d'apprentissage automatique avec *Gradio* et vous pourrez gagner des cadeaux et des prix de Hugging Face ! - -Consultez la [description de l'événement](https://github.com/AK391/community-events/blob/main/gradio-blocks/README.md) pour savoir comment participer. Nous sommes impatients de voir ce que vous allez construire 🤗 ! \ No newline at end of file +Si vous voulez en savoir plus à propos de Gradio, vous pouvez : +- Jeter un coup d'œil à la page [Demos](https://github.com/gradio-app/gradio/tree/main/demo) dans le dépôt GitHub pour consulter beaucoup d'exemples. +- Voir la page [Guides](https://gradio.app/guides/) où vous trouverez des guides sur les fonctionnalités avancées. +- Consulter la page [Docs](https://gradio.app/docs/) pour connaître les détails. \ No newline at end of file diff --git a/chapters/hi/_toctree.yml b/chapters/hi/_toctree.yml index c79b5c2c2..d4d2281cf 100644 --- a/chapters/hi/_toctree.yml +++ b/chapters/hi/_toctree.yml @@ -13,6 +13,19 @@ title: ट्रांसफार्मर, वे क्या कर सकते हैं? - local: chapter1/4 title: ट्रांसफॉर्मर कैसे काम करते हैं? + - local: chapter1/5 + title: एनकोडर मॉडल + - local: chapter1/6 + title: डिकोडर मॉडल + - local: chapter1/7 + title: अनुक्रम-से-अनुक्रम मॉडल + - local: chapter1/8 + title: पूर्वाग्रह और सीमाएं + - local: chapter1/9 + title: सारांश + - local: chapter1/10 + title: अध्याय के अंत की प्रश्नोत्तरी + quiz: 1 - title: 2. ट्रांसफॉर्मर का उपयोग करना sections: diff --git a/chapters/hi/chapter1/10.mdx b/chapters/hi/chapter1/10.mdx new file mode 100644 index 000000000..eeb67fdab --- /dev/null +++ b/chapters/hi/chapter1/10.mdx @@ -0,0 +1,248 @@ +# अध्याय के अंत की प्रश्नोत्तरी + +इस अध्याय में बहुत सारी जमीन शामिल है! यदि आप सभी विवरणों को नहीं समझ पाए हैं तो चिंता न करें; अगले अध्याय आपको यह समझने में मदद करेंगे कि चीजें हुड के तहत कैसे काम करती हैं। + +लेकिन, आइए पहले यह जाँचें कि आपने इस अध्याय में क्या सीखा! + +### 1. हब को एक्सप्लोर करें और `रॉबर्टा-लार्ज-एमएनली` चेकपॉइंट देखें। यह कौन सा कार्य करता है? + +roberta-large-mnli पेज पर फिर से देखें।" + }, + { + text: "पाठ वर्गीकरण", + explain: "अधिक सटीक रूप से, यह वर्गीकृत करता है कि क्या दो वाक्य तार्किक रूप से तीन लेबल (विरोधाभास, तटस्थ, प्रवेश) से जुड़े हुए हैं - एक कार्य जिसे प्राकृतिक भाषा अनुमान भी कहा जाता है।", + correct: true + }, + { + text: "पाठ निर्माण", + explain: "roberta-large-mnli पेज पर फिर से देखें।" + } + ]} +/> + +### 2. निम्नलिखित कोड क्या लौटाएगा? + +```py +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +भावना-विश्लेषण पाइपलाइन होगी।" + }, + { + text: "यह इस वाक्य को पूरा करने वाला एक उत्पन्न पाठ लौटाएगा।", + explain: "यह गलत है — यह एक टेक्स्ट-जनरेशन पाइपलाइन होगी।", + }, + { + text: "यह व्यक्तियों, संगठनों या स्थानों का प्रतिनिधित्व करने वाले शब्दों को वापस कर देगा।", + explain: "इसके अलावा, grouped_entities=True के साथ, यह एक ही इकाई से संबंधित शब्दों को एक साथ समूहित करेगा, जैसे \"हगिंग फेस\"।", + correct: true + } + ]} +/> + +### 3. क्या प्रतिस्थापित करना चाहिए ... इस कोड नमूने में? + +```py +from transformers import pipeline + +filler = pipeline("fill-mask", model="bert-base-cased") +result = filler("...") +``` + + आपका इंतजार कर रहा है।", + explain: "यह गलत है। bert-base-cased मॉडल कार्ड देखें और अपनी गलती का पता लगाने का प्रयास करें।" + }, + { + text: "यह [मास्क] आपका इंतजार कर रहा है।", + explain: "सही! इस मॉडल का मास्क टोकन [MASK] है।", + correct: true + }, + { + text: "यह आदमी तुम्हारा इंतजार कर रहा है।", + explain: "यह गलत है। यह पाइपलाइन नकाबपोश शब्दों में भरती है, इसलिए इसे कहीं न कहीं मास्क टोकन की जरूरत है।" + } + ]} +/> + +### 4. यह कोड विफल क्यों होगा? + +```py +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +result = classifier("This is a course about the Transformers library") +``` + +candidate_labels=[...] शामिल होना चाहिए।", + correct: true + }, + { + text: "इस पाइपलाइन के लिए केवल एक नहीं, बल्कि कई वाक्यों की आवश्यकता है।", + explain: "यह गलत है, हालांकि जब ठीक से उपयोग किया जाता है, तो यह पाइपलाइन प्रक्रिया के लिए वाक्यों की एक सूची ले सकती है (अन्य सभी पाइपलाइनों की तरह)।" + }, + { + text: "🤗 ट्रान्सफ़ॉर्मर पुस्तकालय हमेशा की तरह टूटा हुआ है।", + explain: "हम इस उत्तर को एक टिप्पणी के साथ सम्मानित नहीं करेंगे!!" + }, + { + text: "इस पाइपलाइन को लंबे समय तक इनपुट की आवश्यकता है; यह बहुत छोटा है।", + explain: "यह गलत है। ध्यान दें कि इस पाइपलाइन द्वारा संसाधित किए जाने पर एक बहुत लंबा टेक्स्ट छोटा कर दिया जाएगा।" + } + ]} +/> + +### 5. "ट्रांसफर लर्निंग" का क्या अर्थ है? + + + +### 6. सही या गलत? एक भाषा मॉडल को आमतौर पर इसके पूर्व-प्रशिक्षण के लिए लेबल की आवश्यकता नहीं होती है। + +स्व-पर्यवेक्षित होता है, जिसका अर्थ है कि लेबल स्वचालित रूप से इनपुट से बनाए जाते हैं (जैसे अगले शब्द की भविष्यवाणी करना या कुछ नकाबपोश शब्दों को भरना)।", + correct: true + }, + { + text: "गलत", + explain: "यह सही उत्तर नहीं है।" + } + ]} +/> + +### 7. उस वाक्य का चयन करें जो "मॉडल," "वास्तुकला," और "वजन" शब्दों का सबसे अच्छा वर्णन करता है। + + + +### 8. आप जनरेट किए गए टेक्स्ट के साथ संकेतों को पूरा करने के लिए इनमें से किस प्रकार के मॉडल का उपयोग करेंगे? + + + +### 9. पाठों को सारांशित करने के लिए आप इनमें से किस प्रकार के मॉडल का उपयोग करेंगे? + + + +### 10. कुछ लेबल के अनुसार टेक्स्ट इनपुट को वर्गीकृत करने के लिए आप इनमें से किस प्रकार के मॉडल का उपयोग करेंगे? + + + +### 11. एक मॉडल में देखे गए पूर्वाग्रह के संभावित स्रोत क्या हो सकते हैं? + + diff --git a/chapters/hi/chapter1/5.mdx b/chapters/hi/chapter1/5.mdx new file mode 100644 index 000000000..0e1957c48 --- /dev/null +++ b/chapters/hi/chapter1/5.mdx @@ -0,0 +1,17 @@ +# एनकोडर मॉडल + + + +एन्कोडर मॉडल केवल ट्रांसफ़ॉर्मर मॉडल के एन्कोडर का उपयोग करते हैं। प्रत्येक चरण में, ध्यान की परतें प्रारंभिक वाक्य में सभी शब्दों तक पहुंच सकती हैं। इन मॉडलों को अक्सर "द्वि-दिशात्मक" ध्यान देने के रूप में वर्णित किया जाता है, और इन्हें अक्सर *ऑटो-एन्कोडिंग मॉडल* कहा जाता है। + +इन मॉडलों का पूर्व-प्रशिक्षण आमतौर पर किसी दिए गए वाक्य को भ्रष्ट करने के लिए घूमता है (उदाहरण के लिए, इसमें यादृच्छिक शब्दों को मास्क करके) और प्रारंभिक वाक्य को खोजने या पुनर्निर्माण के साथ मॉडल को काम पर रखना। + +एनकोडर मॉडल उन कार्यों के लिए सबसे उपयुक्त होते हैं जिनमें पूर्ण वाक्य की समझ की आवश्यकता होती है, जैसे वाक्य वर्गीकरण, नामित इकाई पहचान (और अधिक सामान्य शब्द वर्गीकरण), और निकालने वाले प्रश्न उत्तर। + +मॉडल के इस परिवार के प्रतिनिधियों में शामिल हैं: + +- [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) diff --git a/chapters/hi/chapter1/6.mdx b/chapters/hi/chapter1/6.mdx new file mode 100644 index 000000000..9b0f245cd --- /dev/null +++ b/chapters/hi/chapter1/6.mdx @@ -0,0 +1,16 @@ +# डिकोडर मॉडल + + + +डिकोडर मॉडल केवल ट्रांसफॉर्मर मॉडल के डिकोडर का उपयोग करते हैं। प्रत्येक चरण में, किसी दिए गए शब्द के लिए ध्यान की परतें केवल वाक्य में उसके सामने स्थित शब्दों तक पहुंच सकती हैं। इन मॉडलों को अक्सर *स्वतः प्रतिगामी मॉडल* कहा जाता है। + +डिकोडर मॉडल का पूर्व-प्रशिक्षण आमतौर पर वाक्य में अगले शब्द की भविष्यवाणी करने के इर्द-गिर्द घूमता है। + +ये मॉडल टेक्स्ट जनरेशन से जुड़े कार्यों के लिए सबसे उपयुक्त हैं। + +मॉडल के इस परिवार के प्रतिनिधियों में शामिल हैं: + +- [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) +- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) +- [GPT-2](https://huggingface.co/transformers/model_doc/gpt2.html) +- [Transformer XL](https://huggingface.co/transformers/model_doc/transfo-xl.html) diff --git a/chapters/hi/chapter1/7.mdx b/chapters/hi/chapter1/7.mdx new file mode 100644 index 000000000..d4fc23ae3 --- /dev/null +++ b/chapters/hi/chapter1/7.mdx @@ -0,0 +1,16 @@ +# अनुक्रम-से-अनुक्रम मॉडल + + + +एनकोडर-डिकोडर मॉडल (जिसे *सीक्वेंस-टू-सीक्वेंस मॉडल* भी कहा जाता है) ट्रांसफॉर्मर आर्किटेक्चर के दोनों हिस्सों का उपयोग करते हैं। प्रत्येक चरण में, एन्कोडर की ध्यान परतें प्रारंभिक वाक्य में सभी शब्दों तक पहुंच सकती हैं, जबकि डिकोडर की ध्यान परतें केवल इनपुट में दिए गए शब्द से पहले स्थित शब्दों तक पहुंच सकती हैं। + +इन मॉडलों का पूर्व-प्रशिक्षण एन्कोडर या डिकोडर मॉडल के उद्देश्यों का उपयोग करके किया जा सकता है, लेकिन इसमें आमतौर पर कुछ अधिक जटिल होता है। उदाहरण के लिए, [T5](https://huggingface.co/t5-base) को टेक्स्ट के रैंडम स्पैन (जिसमें कई शब्द हो सकते हैं) को एक ही मास्क विशेष शब्द से बदलकर पूर्व-प्रशिक्षित किया जाता है, और इसका उद्देश्य भविष्यवाणी करना है वह पाठ जिसे यह मुखौटा शब्द बदल देता है। + +अनुक्रम-से-अनुक्रम मॉडल किसी दिए गए इनपुट के आधार पर नए वाक्यों को उत्पन्न करने के इर्द-गिर्द घूमने वाले कार्यों के लिए सबसे उपयुक्त हैं, जैसे कि सारांश, अनुवाद, या जनरेटिव प्रश्न उत्तर। + +मॉडल के इस परिवार के प्रतिनिधियों में शामिल हैं: + +- [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) diff --git a/chapters/hi/chapter1/8.mdx b/chapters/hi/chapter1/8.mdx new file mode 100644 index 000000000..52e778528 --- /dev/null +++ b/chapters/hi/chapter1/8.mdx @@ -0,0 +1,32 @@ +# पूर्वाग्रह और सीमाएं + + + +यदि आपका इरादा उत्पादन में एक पूर्व-प्रशिक्षित मॉडल या एक परिष्कृत संस्करण का उपयोग करना है, तो कृपया ध्यान रखें कि, हालांकि ये मॉडल शक्तिशाली उपकरण हैं, वे सीमाओं के साथ आते हैं। इनमें से सबसे बड़ी बात यह है कि बड़ी मात्रा में डेटा पर पूर्व-प्रशिक्षण को सक्षम करने के लिए, शोधकर्ता अक्सर उन सभी सामग्री को परिमार्जन करते हैं जो उन्हें मिल सकती हैं, जो कि इंटरनेट पर उपलब्ध सर्वोत्तम और साथ ही सबसे खराब है। + +एक त्वरित उदाहरण देने के लिए, आइए BERT मॉडल के साथ `फिल-मास्क` पाइपलाइन के उदाहरण पर वापस जाएं: + +```python +from transformers import pipeline + +unmasker = pipeline("fill-mask", model="bert-base-uncased") +result = unmasker("This man works as a [MASK].") +print([r["token_str"] for r in result]) + +result = unmasker("This woman works as a [MASK].") +print([r["token_str"] for r in result]) +``` + +```python out +['lawyer', 'carpenter', 'doctor', 'waiter', 'mechanic'] +['nurse', 'waitress', 'teacher', 'maid', 'prostitute'] +``` + +जब इन दो वाक्यों में छूटे हुए शब्द को भरने के लिए कहा जाता है, तो मॉडल केवल एक लिंग-मुक्त उत्तर (वेटर/वेट्रेस) देता है। अन्य कार्य व्यवसाय हैं जो आमतौर पर एक विशिष्ट लिंग से जुड़े होते हैं - और हाँ, वेश्या शीर्ष 5 संभावनाओं में समाप्त होती है जो मॉडल "महिला" और "काम" के साथ जुड़ती है। यह तब भी होता है जब BERT उन दुर्लभ ट्रांसफॉर्मर मॉडलों में से एक है जो पूरे इंटरनेट से डेटा को स्क्रैप करके नहीं बनाया गया है, बल्कि स्पष्ट रूप से तटस्थ डेटा का उपयोग करके बनाया गया है (यह [अंग्रेज़ी विकिपीडिया](https://huggingface.co/datasets/wikipedia) पर प्रशिक्षित है। ) और [बुककॉर्पस](https://huggingface.co/datasets/bookcorpus) डेटासेट)। + +जब आप इन उपकरणों का उपयोग करते हैं, तो आपको अपने दिमाग में यह याद रखना होगा कि आप जिस मूल मॉडल का उपयोग कर रहे हैं वह बहुत आसानी से सेक्सिस्ट, नस्लवादी या समलैंगिकतापूर्ण सामग्री उत्पन्न कर सकता है। अपने डेटा पर मॉडल को फाइन-ट्यूनिंग करने से यह आंतरिक पूर्वाग्रह गायब नहीं होगा। diff --git a/chapters/hi/chapter1/9.mdx b/chapters/hi/chapter1/9.mdx new file mode 100644 index 000000000..56649cb6b --- /dev/null +++ b/chapters/hi/chapter1/9.mdx @@ -0,0 +1,11 @@ +# सारांश + +इस अध्याय में, आपने देखा कि 🤗 ट्रांसफॉर्मर के उच्च-स्तरीय `पाइपलाइन ()` फ़ंक्शन का उपयोग करके विभिन्न प्राकृतिक भाषा प्रसंस्करण कार्यों को कैसे किया जाता है। आपने यह भी देखा कि हब में मॉडलों की खोज और उनका उपयोग कैसे करें, साथ ही सीधे अपने ब्राउज़र में मॉडलों का परीक्षण करने के लिए अनुमान API का उपयोग कैसे करें। + +हमने चर्चा की कि ट्रांसफॉर्मर मॉडल उच्च स्तर पर कैसे काम करते हैं और ट्रांसफर लर्निंग और फाइन-ट्यूनिंग के महत्व के बारे में बात की। एक महत्वपूर्ण पहलू यह है कि आप पूर्ण आर्किटेक्चर या केवल एन्कोडर या डिकोडर का उपयोग कर सकते हैं, यह इस बात पर निर्भर करता है कि आप किस प्रकार के कार्य को हल करना चाहते हैं। निम्न तालिका इसे सारांशित करती है: + +| मॉडल | उदाहरण | कार्य | +|-----------------|--------------------------------------------|----------------------------------------------------------------------------------| +| एनकोडर | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa | वाक्य वर्गीकरण, नामित इकाई मान्यता, प्रश्न उत्तर निकालने वाला | +| डिकोडर | CTRL, GPT, GPT-2, Transformer XL | पाठ निर्माण | +| एनकोडर-डिकोडर | BART, T5, Marian, mBART | संक्षिप्तीकरण, अनुवाद, प्रश्न उत्तर बनाना | diff --git a/chapters/it/_toctree.yml b/chapters/it/_toctree.yml index 6198bb32b..f30341dcc 100644 --- a/chapters/it/_toctree.yml +++ b/chapters/it/_toctree.yml @@ -11,7 +11,13 @@ title: Natural Language Processing - local: chapter1/3 title: Cosa fanno i Transformer? - + - local: chapter1/4 + title: Come funzionano i Transformer? + - local: chapter1/5 + title: Modelli encoder + - local: chapter1/6 + title: Modelli decoder + - title: 4. Condividere modelli e tokenizers sections: - local: chapter4/1 diff --git a/chapters/it/chapter1/4.mdx b/chapters/it/chapter1/4.mdx new file mode 100644 index 000000000..8aa824f14 --- /dev/null +++ b/chapters/it/chapter1/4.mdx @@ -0,0 +1,171 @@ +# Come funzionano i Transformer? + +In questa sezione, vedremo in maniera approfondita l'architettura dei modelli Transformer. + +## Un po' di storia dei Transformer + +Ecco alcuni punti di riferimento nella (breve) storia dei modelli Transformer: + +
+A brief chronology of Transformers models. + +
+ +L'[architettura Transformer](https://arxiv.org/abs/1706.03762) è stata introdotta in giugno 2017. Il focus della ricerca di partenza era sui compiti di traduzione. A questa seguì l'introduzione di numerosi modelli influenti, tra cui figurano: + +- **giugno 2018**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), il primo modello Transformer pre-addestrato, viene usato per affinare diversi compiti di NLP e ottiene risultati all'avanguardia + +- **ottobre 2018**: [BERT](https://arxiv.org/abs/1810.04805), un altro ampio modello pre-addestrato, questa volta progettato per produrre riassunti di frasi migliori (ne scopriremo di più nel prossimo capitolo!) + +- **febbraio 2019**: [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), una versione (migliorata e ingrandita) di GPT che non fu distribuita immediatamente al pubblico a causa di preoccupazioni etiche + +- **ottobre 2019**: [DistilBERT](https://arxiv.org/abs/1910.01108), una versione distillata di BERT che è il 60% più rapida e il 40% più leggera in memoria, pur conservando il 97% della performance di BERT + +- **ottobre 2019**: [BART](https://arxiv.org/abs/1910.13461) e [T5](https://arxiv.org/abs/1910.10683), due grossi modelli pre-addestrati che utilizzano la stessa architettura del modello Transformer originale (nonché i primi a farlo) + +- **maggio 2020**, [GPT-3](https://arxiv.org/abs/2005.14165), una versione ancora più ampia di GPT-2, con buone prestazioni in vari compiti e nessun bisogno di fine-tuning (il cosiddetto _zero-shot learning_) + +La lista è tutto fuorché esaustiva ed è volta solo a mettere in evidenza alcuni dei diversi tipi di modelli Transformer. In genere, questi possono essere raggruppati in tre categorie: + +- Modelli in stile GPT (detti anche modelli Transformer _auto-regressive_) +- Modelli in stile BERT (detti anche modelli Transformer _auto-encoding_) +- Modelli in stile BART/T5 (detti anche modelli Transformer _sequence-to-sequence_) + +Studieremo queste famiglie più nel dettaglio in seguito. + +## I Transformer sono modelli linguistici + +Tutti i modelli Transformer menzionati qui sopra (GPT, BERT, BART, T5, ecc.) sono stati addestrati come modelli linguistici (*language models*). Ciò significa che sono stati addestrati su grandi quantità di testo grezzo in stile auto-supervisionato (*self-supervising*). L'apprendimento auto-supervisionato è un tipo di apprendimento il cui obbiettivo viene computato direttamente dagli input del modello. Ciò significa che non è richiesto alcun intervento umano per etichettare i dati! + +Un modello di questo tipo sviluppa una comprensione statistica della lingua alla quale è stato addestrato, ma non è molto utile in compiti pratici e precisi. Per questa ragione, il modello pre-addestrato generale viene in seguito sottoposto a un processo detto *transfer learning*. Durante questo processo, il modello viene affinato per un determinato compito in maniera supervisionata (ossia utilizzando etichette generate da umani). + +Un esempio di compito è la previsione della parola seguente in una frase di cui sono state lette *n* parole precedenti. Quest'operazione si chiama *causal language modeling* perché il suo output dipende dagli input presenti e passati, ma non da quelli futuri. + +
+Example of causal language modeling in which the next word from a sentence is predicted. + +
+ +Un altro esempio è il *masked language modeling*, in cui il modello prevede una parola occultata della frase. + +
+Example of masked language modeling in which a masked word from a sentence is predicted. + +
+ +## I Transformers sono modelli enormi + +A parte per alcune eccezioni (come DistilBERT), la strategia generale per ottenere performance migliori consiste nell'aumentare la taglia dei modelli, nonché la quantità di dati utilizzati per il pre-addestramento. + +
+Number of parameters of recent Transformers models +
+ +Sfortunatamente, l'addestramento di un modello, e specialmente di un modello grosso, richiede grandi quantità di dati. Ciò si rivela molto costoso in termini di tempo, risorse informatiche e impatto ambientale, come mostrano i grafici qui sotto. + +
+The carbon footprint of a large language model. + +
+ + + +Questi dati si riferiscono a un progetto per un modello (molto grande) condotto da un team che provava consciamente a ridurre l'impatto ambientale del pre-addestramento. L'impronta di trials volti a ottenere i miglior iperparamenti possibili sarebbe ancora più importante. + +Immagina cosa succederebbe se ogni volta che un gruppo di ricerca, un'organizzazione studentesca o un'azienda vuole addestrare un modello lo facesse da zero! I costi globali sarebbero inutilmente enormi! + +Questo è il motivo per cui la condivisione di modelli linguistici è fondamentale: lavorare a partire da modelli già addestrati riduce i costi informatici complessivi e l'impatto ambientale della comunità. + + +## Transfer Learning + + + +Il pre-addestramento è l'atto di addestrare un modello da zero: i pesi sono inizializzati in maniera casuale, e l'addestramento inizia senza alcuna conoscenza pregressa. + +
+The pretraining of a language model is costly in both time and money. + +
+ +Questo pre-addestramento è solitamente fatto su enormi quantità di dati. Di conseguenza, l'addestramento richiede un corpus di dati molto ampio e può prendere diverse settimane. + +L'affinamento (*fine-tuning*), al contrario, è un addestramento che ha luogo **dopo** che il modello è stato pre-addestrato. Per poter effettuare un fine-tuning, è necessario acquisire un modello linguistico pre-addestrato e addestrarlo ulteriormente con una base dati adatta al compito in questione. Ma perché non addestrare direttamente al compito finale? Esistono alcune ragioni: + +* Il modello pre-addestrato è già addestrato su basi dati che contengono similarità con la base dati usata per il fine-tuning. Il processo di fine-tuning riesce quindi ad beneficiare della conoscenza acquisita dal modello iniziale durante il pre-addestramento (ad esempio, nei problemi di NLP, il modello pre-addestrato avrà già conoscenze statistiche della lingua utilizzata nel compito). +* Siccome il modello pre-addestrato è stato addestrato usando moltissimi dati, il fine-tuning richiede molto meno dati per ottenere buoni risultati. +* Per la stessa ragione, occorrono molto meno tempo e risorse per ottenere buoni risultati. + +Ad esempio, è possibile approfittare di un modello pre-addestrato per la lingua inglese e poi affinarlo usando un corpus arXiv, ottenendo così un modello specifico per la scienza/ricerca. L'affinamento non richiederà che una quantità limitata di dati: le conoscenze acquisite dal modello pre-addestrato sono "trasferite", come riflette il nome *transfer learning*. + +
+The fine-tuning of a language model is cheaper than pretraining in both time and money. + +
+ +Il fine-tuning di un modello ha quindi costi ridotti in termini di dati, finanze e impatto ambientale. Iterare su diversi schemi di fine-tuning è anche più rapido e semplice, in quanto l'addestramento è meno restrittivo di un pre-addestramento completo. + +Questo processo permette anche di ottenere risultati migliori di un addestramento da zero (a meno di non essere in possesso di moltissimi dati), motivo per cui bisognerebbe sempre partire da un modello pre-addestrato (quanto possibile compatibile con il compito da eseguire) e affinarlo. + +## Architettura generale + +In questa sezione, vedremo l'architettura generale del modello Transformer. Non preoccuparti se non capisci tutti i concetti: più avanti, troverai sezioni dettagliate per ogni componente. + + + +## Introduzione + +Il modello si compone principalmente di due blocchi: + +* **Encoder (sinistra)**: L'encoder riceve un input e ne costruisce una rappresentazione, le features. Ciò significa che il modello è ottimizzato per la comprensione dell'input. +* **Decoder (destra)**: Il decoder utilizza la rappresentazione dell'encoder (le features) assieme ad ulteriori input per generare la sequenza target. Ciò significa che il modello è ottimizzato per la generazione di output. + +
+Architecture of a Transformers models + +
+ +Ognuna di queste parti può essere utilizzata indipendentemente, in base al compito: + +* **Modelli Encoder-only**: Ottimi per compiti che richiedono una comprensione dell'input, come la classificazione frasale e il riconoscimento delle entità nominate. +* **Modelli Decoder-only**: Ottimi per compiti generativi come la generazione testuale. +* **Modelli Encoder-decoder** o **modelli sequence-to-sequence**: Ottimi per compiti generativi che richiedono un input, come la traduzione o il riassunto. + +Analizzeremo ciascuna di queste architetture indipendentemente più tardi nel corso. + +## Attention layers + +Una caratteristica chiave dei modelli Transformer è che sono basati su strati speciali detti *attention layers*. Non a caso, il titolo del paper che introdusse l'architettura Transformer era ["Attention Is All You Need"](https://arxiv.org/abs/1706.03762)! Esploreremo gli attention layer nel dettaglio più avanti in questo corso; per ora, tutto ciò che hai bisogno di sapere è che un layer dirà al modello di prestare particolare attenzione a certe parole nella frase input (ignorando praticamente le altre) quando si occupa della rappresentazione delle singole parole. + +Come esempio concreto, pensa ad un compito di traduzione testuale dall'inglese al francese. Dato l'input "You like this course", un modello di traduzione dovrà fare riferimento alla parola adiacente "You" per fornire la traduzione corretta della parola "like", perché in francese la coniugazione del verbo "like" cambia in base al soggetto. Diversamente, il resto della frase non è utile alla sua traduzione di quella precisa parola. In maniera simile, durante la traduzione di "this" il modello dovrà prestare attenzione alla parola "course", in quanto "this" ha traduzioni diverse se associato con nomi femminili o maschili. Di nuovo, il resto delle parole della frase non contribuiscono alla corretta traduzione di "this". Con frasi più complesse (e regole grammaticali più complesse), il modello potrebbe aver bisogno di prestare particolare attenzione a parole ben più lontane nella frase per tradurre correttamente ogni parola. + +Lo stesso concetto si applica a qualsiasi compito che ha a che fare con il linguaggio naturale: una parola ha un senso a sé stante, ma tale senso è profondamente influenzato dal contesto, il quale è costituito da una qualsiasi parola (o parole) che precede o segue la parola sotto osservazione. + +Ora che sai cosa sono gli attention layer, guardiamo un po' più nel dettaglio all'architettura Transformer. + +## L'architettura originale + +All'origine, l'architettura Transformer fu creata per la traduzione. In fase di addestramento, l'encoder riceve degli input (frasi) in una certa lingua, mentre il decoder riceve le stesse frasi nella lingua target d'elezione. Nell'encoder, gli attention layer sono in grado di utilizzare qualsiasi parola in una data frase (dato che, come abbiamo appena visto, la traduzione di una determinata parola può dipendere da ciò che la precede o segue nella frase). Diversamente, decoder procede in maniera sequenziale ed è capace di prestare attenzione solo alle parole della frase che ha già tradotto (ossia, solo le parole che precedono la parola che sta generando). Ad esempio, una volta predette le prime tre parole della frase target, le passiamo al decoder che utilizza tutti gli input dell'encoder per provare a predirre la quarta parola. + +Per accelerare il processo di addestramento (quando il modello ha accesso alle frasi target), l'intero target viene fornito al decoder, che però non è in grado di accedere alle parole future (se avesse accesso alla parola in seconda posizione mentre cerca di predirre la parola in seconda posizione, il problema cesserebbe di essere complesso). Ad esempio, mentre prova a predirre la quarta parola, l'attention layer avrà accesso solo alle posizioni tra la prima e la terza. + +L'architettura Transformer originale aveva la struttura qui sotto, con l'encoder a sinistra e il decoder a destra: + +
+Architecture of a Transformers models + +
+ +Nota che il primo attention layer in un *decoder block* presta attenzione a tutti gli input (passati) al decoder, mentre il secondo attention layer utilizza l'output del encoder. Gli è perciò possibile avere accesso a tutta la frase input per meglio prevedere la parola corrente. Questa caratteristica è molto utile in quanto lingue diverse possono avere regole grammaticali diverse piazzano le parole in ordini diversi, oppure perché il contesto che compare più tardi nella frase potrebbe essere utile nella determinazione della migliore traduzione di una data parola. + +L'*attention mask* può essere utilizzato anche nell'encoder/decoder per evitare che il modello presti attenzione a certe parole speciali, come ad esempio parole riempitive utilizzate per rendere tutti gli input della stessa lunghezza. + +## Architetture vs. checkpoint + +Durante questo viaggio nel mondo dei modelli Transformer, incontrerai menzioni di *architetture* e *checkpoint*, nonché di *modelli*. Questi termini hanno significati leggermente diversi: + +* **Architettura**: Lo scheletro del modello, ossia la definizione di ogni livello e operazione che compare nel modello. +* **Checkpoint**: I pesi che verranno caricati in una determinata architettura. +* **Modello**: Un termine generico meno preciso di "architettura" o "checkpoint", in quanto può significare entrambi. In questo corso faremo la distinzione tra *architettura* e *checkpoint* quando sarà necessario ridurre le ambiguità. + +Ad esempio, BERT è un'architettura, mentre `bert-base-cased`, un set di pesi (*weights*) addestrati dal team di Google per la prima versione di BERT, è un checkpoint. Ciononostante, è possibile dire "il modello BERT" e "il modello `bert-base-cased`." diff --git a/chapters/it/chapter1/5.mdx b/chapters/it/chapter1/5.mdx new file mode 100644 index 000000000..817c81463 --- /dev/null +++ b/chapters/it/chapter1/5.mdx @@ -0,0 +1,17 @@ +# Modelli encoder + + + +I modelli encoder utilizzano solo l'encoder di un modello Transformer. In ogni fase, gli attention layer hanno accesso a tutte le parole della frase di partenza. Questi modelli sono spesso caratterizzati come aventi attenzione "bi-direzionale" e chiamati *auto-encoding models*. + +Solitamente, il pre-addestramento di questi modelli consiste nel corrompere una determinata frase (ad esempio, nascondendone casualmente alcune parole) e incaricare il modello di ritrovare o ricostruire la frase di partenza. + +I modelli encoder sono particolarmente appropriati per compiti che richiedono la comprensione di frasi intere, quali la classificazione di frasi, riconoscimento delle entità nominate (e in senso più ampio, la classificazione di parole), e l'estrazione di risposte da un contesto. + +Alcuni esempi di modelli di questo tipo includono: + +- [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) diff --git a/chapters/it/chapter1/6.mdx b/chapters/it/chapter1/6.mdx new file mode 100644 index 000000000..c9a7296a4 --- /dev/null +++ b/chapters/it/chapter1/6.mdx @@ -0,0 +1,16 @@ +# Modelli decoder + + + +I modelli decoder utilizzano solo il decoder di un modello Transformer. Ad ogni passaggio e per una data parola, gli attention layer hanno accesso solo alle parole che la precedono nella frase. Questi modelli sono spesso detti *auto-regressive models*. + +Il pre-addestramento dei modelli decoder ha spesso a che fare con la previsione della parola successiva in un contesto frasale. + +Questi modelli sono particolarmente adatti a compiti di generazione testuale. + +Alcuni rappresentanti di questa famiglia includono: + +- [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) +- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) +- [GPT-2](https://huggingface.co/transformers/model_doc/gpt2.html) +- [Transformer XL](https://huggingface.co/transformers/model_doc/transfo-xl.html) diff --git a/chapters/ja/_toctree.yml b/chapters/ja/_toctree.yml index ce0970fad..0c72444af 100644 --- a/chapters/ja/_toctree.yml +++ b/chapters/ja/_toctree.yml @@ -6,4 +6,25 @@ - title: 1. Transformerモデルについて sections: - local: chapter1/1 - title: イントロダクション \ No newline at end of file + title: イントロダクション + +- title: 4. モデルとトークナイザーの共有 + sections: + - local: chapter4/1 + title: ハギングフェイスハブ + - local: chapter4/2 + title: 学習済みモデルを使う + - local: chapter4/3 + title: 学習済みモデルを共有する + - local: chapter4/4 + title: モデルカードを作成する + - local: chapter4/5 + title: パート1終了! + - local: chapter4/6 + title: チャプター修了クイズ + quiz: 4 + +- title: Hugging Faceコースのイベント + sections: + - local: event/1 + title: パート2公開記念イベント diff --git a/chapters/ja/chapter4/1.mdx b/chapters/ja/chapter4/1.mdx new file mode 100644 index 000000000..93effda6b --- /dev/null +++ b/chapters/ja/chapter4/1.mdx @@ -0,0 +1,17 @@ +# ハギングフェイスハブ + +[ハギングフェイスハブ](https://huggingface.co/)(Hugging Face Hub) –- 私たちのメインウェブサイト –- は、誰もが新しい最先端のモデルやデータセットを発見し、利用し、貢献することができるプラットフォームです。様々なモデルをホストしており、10,000以上のモデルが一般に公開されています。この章ではモデルに焦点を当て、第5章ではデータセットについて見ていきます。 + +ハブにあるモデルは、🤗 Transformersや、そもそもNLPモデルに限りません。例えば、[Flair](https://github.com/flairNLP/flair)や[AllenNLP](https://github.com/allenai/allennlp)からはNLPモデルが、[Asteroid](https://github.com/asteroid-team/asteroid)や[pyannote](https://github.com/pyannote/pyannote-audio)からは音声モデルが、そして[timm](https://github.com/rwightman/pytorch-image-models)からは画像モデルがそれぞれ寄贈されています。 + +これらのモデルはそれぞれGitリポジトリとしてホストされており、バージョン管理および再現性を担保しています。ハブでモデルを共有することは、モデルをコミュニティに開放し、誰でも簡単に利用できるようにすることであり、その結果、モデルを自分で学習する必要がなくなり、共有と利用が容易になります。 + +さらに、ハブ上でモデルを共有すると、そのモデルに対する推論APIが自動的にデプロイ・ホストされます。コミュニティの誰もが、カスタム入力と適切なウィジェットを使って、モデルのページで直接自由にテストすることができます。 + +ハブで公開されているモデルの共有や使用は、完全に無料です!非公開でモデルを共有したい場合は、[有料プラン](https://huggingface.co/pricing)も存在します。 + +以下のビデオでは、ハブの操作方法を紹介しています。 + + + +ハブでリポジトリの作成と管理を行うため、これ以降はハギングフェイスアカウントが必要になります:[アカウント作成](https://huggingface.co/join)。 \ No newline at end of file diff --git a/chapters/ja/chapter4/2.mdx b/chapters/ja/chapter4/2.mdx new file mode 100644 index 000000000..eeea7d497 --- /dev/null +++ b/chapters/ja/chapter4/2.mdx @@ -0,0 +1,96 @@ + + +# 学習済みモデルを使う + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +モデルハブは適切なモデルを簡単に選択できるようにし、どのライブラリからでも数行のコードで使用できるようにします。では、実際にこれらのモデルをどのように使用し、どのようにコミュニティに貢献するかを見ていきましょう。 + +例えば、マスクフィルを行えるフランス語のモデルを探しているとします。 + +
+Selecting the Camembert model. +
+ +試しに`camembert-base`チェックポイントを選択してみましょう。camembert-base`という識別子があれば、すぐに使い始めることができます。これまでの章で見てきたように、 `pipeline()` 関数を使用してインスタンスを作成することができます: + +```py +from transformers import pipeline + +camembert_fill_mask = pipeline("fill-mask", model="camembert-base") +results = camembert_fill_mask("Le camembert est :)") +``` + +```python out +[ + {'sequence': 'Le camembert est délicieux :)', 'score': 0.49091005325317383, 'token': 7200, 'token_str': 'délicieux'}, + {'sequence': 'Le camembert est excellent :)', 'score': 0.1055697426199913, 'token': 2183, 'token_str': 'excellent'}, + {'sequence': 'Le camembert est succulent :)', 'score': 0.03453313186764717, 'token': 26202, 'token_str': 'succulent'}, + {'sequence': 'Le camembert est meilleur :)', 'score': 0.0330314114689827, 'token': 528, 'token_str': 'meilleur'}, + {'sequence': 'Le camembert est parfait :)', 'score': 0.03007650189101696, 'token': 1654, 'token_str': 'parfait'} +] +``` + +ご覧の通り、パイプライン内でのモデルのロードは非常に簡単です。唯一気をつけなければならないのは、選択したチェックポイントが使用するタスクに適しているかということです。例えば、ここでは`camembert-base`というチェックポイントを`fill-mask`というパイプラインでロードしていますが、これは全く問題ありません。しかし、このチェックポイントを`text-classification`パイプラインでロードしたとすると、`camembert-base`の「ヘッド」がこのタスクに適していないため、結果が意味をなさないことになります!適切なチェックポイントを選択するために、ハギングフェイスハブインタフェースにあるタスクセレクタを使用することをお勧めします: + +
+The task selector on the web interface. +
+ +また、モデル・アーキテクチャを直接使用して、チェックポイントをインスタンス化することもできます: + +{#if fw === 'pt'} +```py +from transformers import CamembertTokenizer, CamembertForMaskedLM + +tokenizer = CamembertTokenizer.from_pretrained("camembert-base") +model = CamembertForMaskedLM.from_pretrained("camembert-base") +``` + +しかし、代わりに[`Auto*` classes](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes)を使用することをお勧めします。これらは設計上、(モデル)アーキテクチャに依存しないためです。先ほどのコードサンプルでは、CamemBERT アーキテクチャでロード可能なチェックポイントに限定していましたが、 `Auto*`クラスを使用すると、チェックポイントを簡単に切り替えることができます: + +```py +from transformers import AutoTokenizer, AutoModelForMaskedLM + +tokenizer = AutoTokenizer.from_pretrained("camembert-base") +model = AutoModelForMaskedLM.from_pretrained("camembert-base") +``` +{:else} +```py +from transformers import CamembertTokenizer, TFCamembertForMaskedLM + +tokenizer = CamembertTokenizer.from_pretrained("camembert-base") +model = TFCamembertForMaskedLM.from_pretrained("camembert-base") +``` + +しかし、代わりに[`TFAuto*` classes](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes)を使用することをお勧めします。これらは設計上、アーキテクチャに依存しないためです。先ほどのコードサンプルでは、CamemBERT アーキテクチャでロード可能なチェックポイントに限定していましたが、 `TFAuto*`クラスを使用すると、チェックポイントを簡単に切り替えることができます: + +```py +from transformers import AutoTokenizer, TFAutoModelForMaskedLM + +tokenizer = AutoTokenizer.from_pretrained("camembert-base") +model = TFAutoModelForMaskedLM.from_pretrained("camembert-base") +``` +{/if} + + +学習済みのモデルを使う場合は、どのように学習したのか、どのデータセットで学習したのか、その限界と偏りを必ず確認すること。これらの情報はすべて、モデルカードに記載されています。 + diff --git a/chapters/ja/chapter4/3.mdx b/chapters/ja/chapter4/3.mdx new file mode 100644 index 000000000..90e29c62b --- /dev/null +++ b/chapters/ja/chapter4/3.mdx @@ -0,0 +1,635 @@ + + +# 学習済みモデルを共有する + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +以下のステップでは、学習済みモデルを🤗ハブに共有する最も簡単な方法について見ていきます。ハブ上で直接モデルを共有し、更新できるツールやユーティリティが用意されていますので、以下、それを見ていきます。 + + + +たとえ非常に特殊なデータセットで学習させたとしても、モデルをコミュニティに共有することをお勧めします。他のユーザーの時間と計算資源を節約し、有用な学習済みモデルを提供することができるからです。代わりに、他の人の成果物の恩恵を受けることもできます! + +新しいモデルリポジトリを作成するには、次の3つの方法があります: + +- `push_to_hub` APIを使用する +- `huggingface_hub` Pythonライブラリを使用する +- ウェブインターフェイスを使用する + +リポジトリを作成したら、git と git-lfs を使ってリポジトリにファイルをアップロードすることができます。以下のセクションでは、モデルリポジトリを作成し、ファイルをアップロードする方法を説明します。 + +## `push_to_hub` APIを使用する + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +ハブにファイルをアップロードする最も簡単な方法は、`push_to_hub` API を使うことです。 + +先に進む前に、あなたが誰で、どのネームスペースに書き込み権限があるのかを通知するために、認証トークンを生成しましょう。`transformers`がインストールされている環境であることを確認してください([セットアップ](/course/chapter0)を参照のこと)。ノートブックの場合は、以下の関数を使ってログインすることができます: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +ターミナル上では次の通りです: + +```bash +huggingface-cli login +``` + +どちらの場合も、ユーザー名とパスワードの入力を求められますが、これはハブにログインするときに使用するものと同じです。まだハブのプロフィールをお持ちでない方は、[こちら](https://huggingface.co/join)から作成してください。 + +これで、認証トークンがキャッシュフォルダに保存されました。それでは、リポジトリを作成しましょう! + +{#if fw === 'pt'} + +`Trainer`API を使ってモデルを学習させたのであれば、 `TrainingArguments`において`push_to_hub=True`と設定することで、最も簡単にハブにアップロードすることができます: + +```py +from transformers import TrainingArguments + +training_args = TrainingArguments( + "bert-finetuned-mrpc", save_strategy="epoch", push_to_hub=True +) +``` + +`trainer.train()`を実行すると、モデルを保存する度に(ここではエポック毎に)`Trainer`はモデルをレポジトリにアップロードします。このリポジトリは出力ディレクトリと同じ名前になりますが(この例では`bert-finetuned-mrpc`)、`hub_model_id = "a_different_name"`とすることで別の名前を指定することができます。 + +あなたが所属する組織にモデルをアップロードするには、`hub_model_id = "my_organization/my_repo_name"`とすればよいです。 + +学習が終了したら、最後に `trainer.push_to_hub()` を実行して、モデルの最終版をアップロードしてください。この際、使用したハイパーパラメータと評価結果など、全ての関連するメタデータを含むモデルカードが生成されます!以下に、モデルカードに含まれる内容の例を示します。 + +
+ An example of an auto-generated model card. +
+ +{:else} + +モデルの学習にKerasを使用している場合、最も簡単にアップロードする方法は`PushToHubCallback`を`model.fit()`に渡すことです: + +```py +from transformers import PushToHubCallback + +callback = PushToHubCallback( + "bert-finetuned-mrpc", save_strategy="epoch", tokenizer=tokenizer +) +``` + +そして、`model.fit()`の呼び出しに`callbacks=[callback]`を追加してください。モデルを保存する度に(ここではエポック毎に)コールバックはモデルをリポジトリにアップロードします。このリポジトリは出力ディレクトリと同じ名前になりますが(この例では`bert-finetuned-mrpc`)、`hub_model_id = "a_different_name"`とすることで別の名前を指定することができます。 + +あなたが所属する組織にモデルをアップロードするには、`hub_model_id = "my_organization/my_repo_name"`とすればよいです。 + +{/if} + +より低いレベルでは、モデル、トークナイザー、および設定オブジェクトの `push_to_hub()` メソッドを通じて、モデルハブへのアクセスを直接行うことができます。このメソッドは、リポジトリの作成と、モデルやトークナイザーのリポジトリへのプッシュの両方を行います。後述するAPIとは異なり、手動で操作する必要はありません。 + +その仕組みを理解するために、まずモデルとトークナイザーを初期化してみましょう: + +{#if fw === 'pt'} +```py +from transformers import AutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = AutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` +{:else} +```py +from transformers import TFAutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = TFAutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` +{/if} + +これらを使って、トークナイザーにトークンを追加したり、モデルを学習させたり、微調整したりと、好きなことを自由に行うことができます。出来上がったモデル、重み、トークナイザーに満足したら、`model` オブジェクトから直接利用できる`push_to_hub()`メソッドを活用できます: + +```py +model.push_to_hub("dummy-model") +``` + +これであなたのプロファイルに新しいリポジトリ `dummy-model` が作成され、モデルファイルがそこに格納されます。すべてのファイルがこのリポジトリで利用できるよう、トークナイザーにも同様に実行してください: + +```py +tokenizer.push_to_hub("dummy-model") +``` + +組織に所属している場合、`organization`引数を指定することで当該組織のネームスペースにアップロードできます: + +```py +tokenizer.push_to_hub("dummy-model", organization="huggingface") +``` + +特定のHugging Faceトークンを使うこともできます: + +```py +tokenizer.push_to_hub("dummy-model", organization="huggingface", use_auth_token="") +``` + +さあ、新しくアップロードしたモデルをモデルハブで見てみましょう:*https://huggingface.co/user-or-organization/dummy-model*. + +"Files and versions"タブをクリックすると、これらのファイルが表示されるはずです: + +{#if fw === 'pt'} +
+Dummy model containing both the tokenizer and model files. +
+{:else} +
+Dummy model containing both the tokenizer and model files. +
+{/if} + + + +✏️ **やってみよう!** `bert-base-cased`チェックポイントに関連付けられたモデルとトークナイザーを、`push_to_hub()`メソッドを使って自分のネームスペースにあるリポジトリにアップロードします。レポジトリを削除する前に、レポジトリがあなたのページに正しく表示されることを確認してください。 + + + +これまで見てきたように、`push_to_hub()`メソッドはいくつかの引数をとるので、特定のリポジトリや組織のネームスペースにアップロードしたり、別のAPI トークンを使用したりすることが可能です。詳細については、[🤗 Transformers documentation](https://huggingface.co/transformers/model_sharing.html)で仕様を確認することをお勧めします。 + +この`push_to_hub()`メソッドは、ハギングフェイスハブに直接アクセスできる[`huggingface_hub`](https://github.com/huggingface/huggingface_hub) Pythonパッケージで実装されており、🤗 Transformersや、[`allenlp`](https://github.com/allenai/allennlp)といった、他の機械学習ライブラリに統合されています。この章では🤗 Transformersに焦点を当てますが、あなた自身のコードやライブラリに統合することは簡単です。 + +最後のセクションに移動して、新しく作成したリポジトリにファイルをアップロードする方法をご覧ください! + +## `huggingface_hub` Pythonライブラリを使用する + +`huggingface_hub` Pythonライブラリは、モデルとデータセットのハブのためのツールセットを提供するパッケージです。ハブ上のリポジトリに関する情報を取得し、それらを管理するような一般的なタスクのためのシンプルなメソッドとクラスを提供します。また、これらのリポジトリのコンテンツを管理し、あなたのプロジェクトやライブラリにハブを統合するために、gitの上で動作するシンプルなAPIを提供します。 + +`push_to_hub` API を使用する場合と同様に、APIトークンをキャッシュに保存しておく必要があります。これを行うには、前のセクションで説明したように、CLI から `login` コマンドを使用する必要があります (Google Colab で実行する場合は、これらのコマンドの前に `!` 文字を付加してください): + +```bash +huggingface-cli login +``` + +`huggingface_hub` パッケージには、便利なメソッドとクラスがいくつかあります。まず、リポジトリの作成と削除を管理するためのメソッドがいくつかあります: + +```python no-format +from huggingface_hub import ( + # ユーザー管理 + login, + logout, + whoami, + + # レポジトリの作成と管理 + create_repo, + delete_repo, + update_repo_visibility, + + # そして、コンテンツに関する情報を取得/変更するためのいくつかのメソッド + list_models, + list_datasets, + list_metrics, + list_repo_files, + upload_file, + delete_file, +) +``` + + +さらに、ローカルリポジトリを管理するための非常に強力な `Repository` クラスを提供しています。これらのメソッドやクラスをどのように活用するかについては、次のセクションで説明します。 + +`create_repo` メソッドを使用すると、ハブに新しいリポジトリを作成できます: + +```py +from huggingface_hub import create_repo + +create_repo("dummy-model") +``` + +これで、あなたのネームスペースに `dummy-model` というリポジトリが作成されます。もし必要なら、 `organization` 引数で所属する組織を指定することもできます: + +```py +from huggingface_hub import create_repo + +create_repo("dummy-model", organization="huggingface") +``` + +あなたがその組織に所属していると仮定して、これで`huggingface`ネームスペースに `dummy-model` リポジトリが作成されます。その他の便利な引数は以下の通りです。 + +- `private`:リポジトリを他から見えるようにするかどうかを指定します。 +- `token`:キャッシュに保存されているトークンではない、別のトークンを指定します。 +- `repo_type`:モデルではなく「データセット」や「スペース」のレポジトリを作成します。指定できる値は`"dataset"`と`"space"`です。 + +レポジトリが作成できたらファイルを追加してみましょう!次のセクションに移動して、3つの方法を見てみましょう。 + + +## ウェブインターフェイスを使う + +ウェブインタフェースでは、ハブのリポジトリを直接管理することができます。このインターフェイスを使って、リポジトリの作成、ファイル(大きなものも!)の追加、モデルの検索、差分の可視化など、さまざまなことが簡単にできます。 + +レポジトリを新しく作るには、[huggingface.co/new](https://huggingface.co/new)にアクセスして下さい: + +
+Page showcasing the model used for the creation of a new model repository. +
+ +まず、リポジトリの所有者を指定します。これはあなた自身か、あなたが所属する組織のいずれかになります。組織を選択した場合、モデルは組織のページで紹介され、組織の全メンバーがリポジトリに貢献することができるようになります。 + +次に、モデルの名前を入力します。これはリポジトリの名前にもなります。最後に、モデルをパブリックにするかプライベートにするかを指定します。プライベートモデルは、一般公開されないモデルです。 + +モデルリポジトリを作成すると、このようなページが表示されるはずです: + +
+An empty model page after creating a new repository. +
+ +これは、あなたのモデルがホストされる場所です。ウェブインターフェースから直接、モデルにREADMEファイルを追加してみましょう。 + +
+The README file showing the Markdown capabilities. +
+ +READMEファイルはMarkdownで書かれています - どうぞ自由に使ってください。この章の第三部は、モデルカードの作成に専念します。モデルカードはそのモデルができることを他の人に伝える場所であり、あなたのモデルに価値を与えるために最も重要なものです。 + +"Files and versions"タブを見ると、まだ多くのファイルがないことがわかります。先ほど作成した *README.md* と、大きなファイルを追跡するための *.gitattributes* ファイルがあるだけです。 + +
+The 'Files and versions' tab only shows the .gitattributes and README.md files. +
+ +次は、新しいファイルを追加する方法について見てみましょう。 + +## モデルファイルのアップロード + +ハギングフェイスハブでのファイル管理の仕組みは、通常のファイルはgit、大きなファイルはgit-lfs ([Git Large File Storage](https://git-lfs.github.com/)の略)をベースにしています。 + +次のセクションでは、ハブにファイルをアップロードする3つの方法について説明します: `huggingface_hub` と git コマンドです。 + +### `upload_file`を使ったアプローチ + +`upload_file` を使用する場合、git や git-lfs がシステムにインストールされている必要はありません。HTTP POST リクエストを使用して、ファイルを直接 🤗 ハブにプッシュします。この方法の制限は、5GB を超えるサイズのファイルを扱えないことです。5GB を超えるファイルを扱う場合は、以下に説明する他の2つの方法に従ってください。 + +本APIは次のように使用することができます: + +```py +from huggingface_hub import upload_file + +upload_file( + "/config.json", + path_in_repo="config.json", + repo_id="/dummy-model", +) +``` + +これは、リポジトリのルートである `` にある `config.json` というファイルを `dummy-model` リポジトリにアップロードすることになります。 +その他便利な引数は次の通りです: + +- `token`、キャッシュに保存されているトークンではない、別のトークンを指定します。 +- `repo_type`、モデルではなく「データセット」や「スペース」のレポジトリを作成します。指定できる値は`"dataset"`と`"space"`です。 + + +### `Repository`クラス + +`Repository` クラスは、gitに似た方法でローカルリポジトリを管理します。このクラスは、gitであれば苦労する点のほとんどを抽象化してくれます。 + +このクラスを使用するには、git と git-lfs がインストールされている必要があります。そのため、始める前に git-lfs をインストールし(インストール方法は[こちら](https://git-lfs.github.com/)を参照)、セットアップしておく必要があります。 + +作成したリポジトリでいろいろ試してみるために、リモートリポジトリをクローンしてローカルフォルダに初期化することから始めましょう: + +```py +from huggingface_hub import Repository + +repo = Repository("", clone_from="/dummy-model") +``` + +作業ディレクトリに `` というフォルダが作成されます。このフォルダには `.gitattributes` というファイルだけが存在しているはずです。これは、`create_repo` でリポジトリを作成する際に作成される唯一のファイルだからです。 + +これ以降、従来のgitのメソッドのいくつかを使用することができます: + +```py +repo.git_pull() +repo.git_add() +repo.git_commit() +repo.git_push() +repo.git_tag() +``` + +その他にも同様のメソッドがあります!利用可能なすべてのメソッドの概要については、[こちら](https://github.com/huggingface/huggingface_hub/tree/main/src/huggingface_hub#advanced-programmatic-repository-management)にある `Repository` のドキュメントをご覧になることをお勧めします。 + +現在、私たちはハブにプッシュしたいモデルとトークナイザーを持っていると仮定します。リポジトリのクローンには成功し、そのリポジトリ内にファイルを保存することができるはずです。 + +まず、ローカルのクローンから最新の変更を取り込み、最新の状態にします: + +```py +repo.git_pull() +``` + +それが終わったら、モデルファイルとトークナイザーファイルを保存します: + +```py +model.save_pretrained("") +tokenizer.save_pretrained("") +``` + +`` には、すべてのモデルファイルとトークナイザーファイルが格納されています。通常の git ワークフローに従って、ファイルをステージング・エリアに追加し、コミットしてハブにプッシュします。 + +```py +repo.git_add() +repo.git_commit("Add model and tokenizer files") +repo.git_push() +``` + +おめでとうございます!あなたは今、最初のファイルをハブにプッシュしました。 + +### gitベースのアプローチ + +これは、ファイルをアップロードするための必要最小限なアプローチで、gitとgit-lfsを直接使います。これまでのアプローチでは困難なことのほとんどは抽象化されていますが、このアプローチにはいくつかの注意点があります。より複雑なユースケースで説明していきます。 + +このクラスを使用するには、git と git-lfs がインストールされている必要があります。そのため、始める前に[git-lfs](https://git-lfs.github.com/)をインストールし(インストール方法はこちらを参照)、セットアップしておいてください。 + +まず最初に git-lfs を初期化することから始めます: + +```bash +git lfs install +``` + +```bash +Updated git hooks. +Git LFS initialized. +``` + +それが終わったら、まず最初にモデルリポジトリをクローンします: + +```bash +git clone https://huggingface.co// +``` + +私のユーザ名は `lysandre` で、モデル名は `dummy` としたので、私の場合、コマンドは以下のようになります: + +``` +git clone https://huggingface.co/lysandre/dummy +``` + +作業ディレクトリに*dummy*という名前のフォルダができました。このフォルダに `cd` して、中身を見ることができます: + +```bash +cd dummy && ls +``` + +```bash +README.md +``` + +ハギングフェイスハブの `create_repo` メソッドを使ってリポジトリを作成したばかりの場合、このフォルダーには `.gitattributes` という隠しファイルだけが存在しているはずです。前のセクションの指示に従ってウェブインターフェースを使用してリポジトリを作成した場合、このフォルダーには、ここに示すように、`.gitattributes` ファイルと一緒に*README.md* ファイルだけが存在しているはずです。 + +設定ファイルや語彙ファイルなど、基本的に数メガバイト以下の通常サイズのファイルを追加することは、gitベースのシステムで行うのとまったく同じように行われます。しかし、より大きなファイルを *huggingface.co* にプッシュするには、git-lfs を通して登録する必要があります。 + +Pythonに少し戻って、ダミーリポジトリにコミットするモデルとトークナイザを生成してみましょう: + +{#if fw === 'pt'} +```py +from transformers import AutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = AutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + +# モデルを使って、トレーニングしたり、微調整したり...。 + +model.save_pretrained("") +tokenizer.save_pretrained("") +``` +{:else} +```py +from transformers import TFAutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = TFAutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + +# モデルを使って、トレーニングしたり、微調整したり...。 + +model.save_pretrained("") +tokenizer.save_pretrained("") +``` +{/if} + +さて、モデルとトークナイザーのアーティファクトをいくつか保存したので、*dummy* フォルダーをもう一度見てみましょう。 + +```bash +ls +``` + +{#if fw === 'pt'} +```bash +config.json pytorch_model.bin README.md sentencepiece.bpe.model special_tokens_map.json tokenizer_config.json tokenizer.json +``` + +ファイルサイズを見ると(たとえば `ls -lh` で)、モデル状態のディクショナリファイル (*pytorch_model.bin*) が唯一、400 MB 以上あることがわかると思います。 + +{:else} +```bash +config.json README.md sentencepiece.bpe.model special_tokens_map.json tf_model.h5 tokenizer_config.json tokenizer.json +``` + +ファイルサイズを見ると(たとえば `ls -lh` で)、モデル状態のディクショナリファイル (*t5_model.h5*) が唯一、400 MB 以上あることがわかると思います。 + +{/if} + + +✏️ ウェブインターフェースからリポジトリを作成する場合、*.gitattributes* ファイルは自動的に *.bin* や *.h5* などの特定の拡張子を持つファイルを大きなファイルとみなすように設定され、git-lfs がそれらを追跡するようになります。ユーザー側で別途設定を行う必要はありません。 + + +これで、従来の Git リポジトリと同じように作業を進められるようになりました。すべてのファイルを Git のステージング環境に追加するには、`git add` コマンドを使います: + +```bash +git add . +``` + +そして、現在ステージングされているファイルを見ることができます: + +```bash +git status +``` + +{#if fw === 'pt'} +```bash +On branch main +Your branch is up to date with 'origin/main'. + +Changes to be committed: + (use "git restore --staged ..." to unstage) + modified: .gitattributes + new file: config.json + new file: pytorch_model.bin + new file: sentencepiece.bpe.model + new file: special_tokens_map.json + new file: tokenizer.json + new file: tokenizer_config.json +``` +{:else} +```bash +On branch main +Your branch is up to date with 'origin/main'. + +Changes to be committed: + (use "git restore --staged ..." to unstage) + modified: .gitattributes + new file: config.json + new file: sentencepiece.bpe.model + new file: special_tokens_map.json + new file: tf_model.h5 + new file: tokenizer.json + new file: tokenizer_config.json +``` +{/if} + +同様に、git-lfs が正しいファイルを追跡しているかどうかを `status` コマンドで確認することができます: + +```bash +git lfs status +``` + +{#if fw === 'pt'} +```bash +On branch main +Objects to be pushed to origin/main: + + +Objects to be committed: + + config.json (Git: bc20ff2) + pytorch_model.bin (LFS: 35686c2) + sentencepiece.bpe.model (LFS: 988bc5a) + special_tokens_map.json (Git: cb23931) + tokenizer.json (Git: 851ff3e) + tokenizer_config.json (Git: f0f7783) + +Objects not staged for commit: + + +``` + +`LFS`で処理される*pytorch_model.bin* と *sentencepiece.bpe.model* を除き、すべてのファイルが `Git` で処理されることが分かります。素晴らしい! + +{:else} +```bash +On branch main +Objects to be pushed to origin/main: + + +Objects to be committed: + + config.json (Git: bc20ff2) + sentencepiece.bpe.model (LFS: 988bc5a) + special_tokens_map.json (Git: cb23931) + tf_model.h5 (LFS: 86fce29) + tokenizer.json (Git: 851ff3e) + tokenizer_config.json (Git: f0f7783) + +Objects not staged for commit: + + +``` + +`LFS`で処理される*t5_model.h5*を除き、すべてのファイルが `Git` で処理されることが分かります。素晴らしい! + +{/if} + +最後のステップ、コミットと*huggingface.co*リモートリポジトリへのプッシュへと進みましょう: + +```bash +git commit -m "First model version" +``` + +{#if fw === 'pt'} +```bash +[main b08aab1] First model version + 7 files changed, 29027 insertions(+) + 6 files changed, 36 insertions(+) + create mode 100644 config.json + create mode 100644 pytorch_model.bin + create mode 100644 sentencepiece.bpe.model + create mode 100644 special_tokens_map.json + create mode 100644 tokenizer.json + create mode 100644 tokenizer_config.json +``` +{:else} +```bash +[main b08aab1] First model version + 6 files changed, 36 insertions(+) + create mode 100644 config.json + create mode 100644 sentencepiece.bpe.model + create mode 100644 special_tokens_map.json + create mode 100644 tf_model.h5 + create mode 100644 tokenizer.json + create mode 100644 tokenizer_config.json +``` +{/if} + +プッシュは、インターネットの接続速度やファイルの大きさによって、少し時間がかかることがあります: + +```bash +git push +``` + +```bash +Uploading LFS objects: 100% (1/1), 433 MB | 1.3 MB/s, done. +Enumerating objects: 11, done. +Counting objects: 100% (11/11), done. +Delta compression using up to 12 threads +Compressing objects: 100% (9/9), done. +Writing objects: 100% (9/9), 288.27 KiB | 6.27 MiB/s, done. +Total 9 (delta 1), reused 0 (delta 0), pack-reused 0 +To https://huggingface.co/lysandre/dummy + 891b41d..b08aab1 main -> main +``` + +{#if fw === 'pt'} +これが終了した時点でモデルリポジトリを見てみると、最近追加されたすべてのファイルを見ることができます: + +
+The 'Files and versions' tab now contains all the recently uploaded files. +
+ +このUIでは、モデルファイルやコミットを探索したり、各コミットでの差分を確認することができます: + +
+The diff introduced by the recent commit. +
+{:else} +これが終了した時点でモデルリポジトリを見てみると、最近追加されたすべてのファイルを見ることができます: + +
+The 'Files and versions' tab now contains all the recently uploaded files. +
+ +このUIでは、モデルファイルやコミットを探索したり、各コミットでの差分を確認することができます: + +
+The diff introduced by the recent commit. +
+{/if} diff --git a/chapters/ja/chapter4/4.mdx b/chapters/ja/chapter4/4.mdx new file mode 100644 index 000000000..82e25f2f5 --- /dev/null +++ b/chapters/ja/chapter4/4.mdx @@ -0,0 +1,82 @@ +# モデルカードを作成する + +モデルカードは、モデルリポジトリにおいて、モデルファイルやトークナイザーファイルと同じくらい重要なファイルです。モデルの主要な定義であり、コミュニティメンバーによる再利用と結果の再現性を保証し、さらには他のメンバーが成果物を構築するためのプラットフォームを提供します。 + +また、使用したデータや前処理・後処理に関する十分な情報を提供することで、モデルの限界、バイアス、有用となる場面の特定及び理解が可能になります。 + +そのため、モデルを明確に定義したモデルカードを作成することは、非常に重要なステップとなります。ここでは、これに役立ついくつかのヒントを提供します。モデルカードの作成は、先ほど見た*README.md*ファイル、つまりMarkdownファイルを通して行います。 + +「モデルカード」のコンセプトは、Googleの研究方針に端を発し、Margaret Mitchellらの論文["Model Cards for Model Reporting"](https://arxiv.org/abs/1810.03993)で初めて公開されました。ここに含まれる多くの情報は、その論文に基づいており、再現性、再利用性、公平性を重視する世界において、なぜモデルカードが重要であるかを理解するには、この論文をご覧になることをお勧めします。 + +モデルカードは通常、何のためのモデルなのかという非常に簡潔でハイレベルな概要から始まり、これらの追加の詳細が説明されます: + +- モデル概要 +- 使用目的・制限 +- 使用方法 +- 制限とバイアス +- 学習データ +- 学習手順 +- 評価結果 + +それでは、それぞれのセクションの内容について見ていきましょう。 + +### モデル概要 + +モデルの概要では、モデルに関する基本的な詳細を説明します。これには、アーキテクチャ、バージョン、論文で紹介されたかどうか、オリジナルの実装があるかどうか、作者、モデルに関する一般的な情報などが含まれます。著作権についてはここに記載すべきです。学習方法、パラメータ、重要な免責事項に関する一般的な情報もこのセクションに記載することができます。 + +### 使用目的・制限 + +ここでは、モデルが適用される言語、フィールド、ドメインなど、そのモデルが想定するユースケースを記述します。モデルカードのこのセクションは、モデルの適用範囲外であることが分かっている領域や、最適でない性能を発揮する可能性がある領域も記述することができます。 + +### 使用方法 + +このセクションでは、モデルの使い方の例をいくつか紹介します。これは`pipeline()`関数の使い方、モデルクラスとトークナイザークラスの使い方、そしてその他に役立つコードを紹介することができます。 + +### 学習データ + +この部分は、モデルがどのデータセットで学習されたかを示す必要があります。データセットの簡単な説明でも大丈夫です。 + +### 学習手順 + +このセクションでは、再現性の観点から有用となる、学習に関する全てを記述する必要があります。これには、データに対して行われた前処理や後処理、モデルの学習エポック数、バッチサイズ、学習レートなどの詳細が含まれます。 + +### 変数とメトリクス + +ここでは、評価のために使用したメトリクスと、測定しているさまざまな要因を記述します。どのデータセットで、どのデータセットを分割して、どのメトリクスを使用したかを記述することで、他のモデルの性能と比較することが容易になります。これらの情報は、前のセクション(想定されるユーザーやユースケースなど)から得たものであるべきです。 + +### 評価結果 + +最後に、評価用データセットにおいて、モデルがどの程度うまく機能するかの指標を提供します。モデルが閾値を使用する場合、評価に使用した閾値を提供するか、または想定する用途に応じた異なる閾値での評価に関する詳細を提供します。 + +## 例 + +よくできたモデルカードの例として、これらをご覧ください: + +- [`bert-base-cased`](https://huggingface.co/bert-base-cased) +- [`gpt2`](https://huggingface.co/gpt2) +- [`distilbert`](https://huggingface.co/distilbert-base-uncased) + +さまざまな組織や企業によるその他の事例は、[こちら](https://github.com/huggingface/model_card/blob/master/examples.md)からご覧いただけます。 + +## 注釈 + +モデルカードはモデルを公開する際の必須条件ではありませんし、モデルを作成する際に上記のセクションをすべて含める必要はありません。しかし、モデルを明確に文書化することは、必ず将来のユーザーのためになるので、できるだけ多くのセクションを埋めることをお勧めします。 + +## モデルカードメタデータ + +ハギングフェイスハブを少しみてみると、いくつかのモデルが特定のカテゴリに属していることがわかるはずです:タスク、言語、ライブラリなどでフィルタリングすることができます。モデルが属するカテゴリーは、モデルカードのヘッダーに追加されたメタデータによって識別されます。 + +例えば、[`camembert-base`モデルカード](https://huggingface.co/camembert-base/blob/main/README.md)を見てみると、モデルカードのヘッダに以下のような行があるはずです: + +``` +--- +language: fr +license: mit +datasets: +- oscar +--- +``` + +このメタデータはハギングフェイスハブによって解析され、このモデルがOscarデータセットで学習されたMITライセンスのフランス語のモデルであることが特定されます。 + +[モデルカード全仕様](https://github.com/huggingface/hub-docs/blame/main/modelcard.md)では、言語、ライセンス、タグ、データセット、メトリクス、および学習時にモデルが得た評価結果を記述することができます。 diff --git a/chapters/ja/chapter4/5.mdx b/chapters/ja/chapter4/5.mdx new file mode 100644 index 000000000..d4dd76891 --- /dev/null +++ b/chapters/ja/chapter4/5.mdx @@ -0,0 +1,7 @@ +# パート1終了! + +これでコースのパート1は終了です!パート2は11月15日に開催される、大きなコミュニティイベントで公開される予定です。詳しくは[こちら](https://huggingface.co/blog/course-launch-event)をご覧ください。 + +これで、テキスト分類問題(単一または文のペア)に対する学習済みモデルのファインチューニングを行い、結果をモデルハブにアップロードできるようになりました。この最初のセクションを確実にマスターするためには、自分の興味のある問題(英語でなくてもよい)をきっちりやっておくことが必要です。[Hugging Face forums](https://discuss.huggingface.co/)で助けを求めることもできますし、完成したら[this topic](https://discuss.huggingface.co/t/share-your-projects/6803)でプロジェクトを共有することもできます。 + +何ができるか楽しみです! diff --git a/chapters/ja/chapter4/6.mdx b/chapters/ja/chapter4/6.mdx new file mode 100644 index 000000000..22575d1b4 --- /dev/null +++ b/chapters/ja/chapter4/6.mdx @@ -0,0 +1,223 @@ + + + + +# チャプター修了クイズ + +この章で学んだことを確認してみましょう! + +### 1. ハブにアップロードできるモデルには何か制限があるでしょうか? + + + +### 2. ハブではどうやってモデルを管理すればよいでしょうか? + +git-lfsを活用しています。", + correct: true + } + ]} +/> + +### 3. ハギングフェイスハブのウェブインターフェイスを使うと何ができるでしょうか? + + + +### 4. モデルカードとは何でしょう? + + + +### 5. これらの🤗 Transformersライブラリのオブジェクトのうち、 `push_to_hub()` を使ってハブ上で直接共有できるものはどれでしょうか? + +{#if fw === 'pt'} +push_to_hubメソッドを備えており、全てのトークナイザーファイル(ボキャブラリー、トークナイザーのアーキテクチャ、等々)をレポジトリにプッシュすることができます。でもこれだけが正解ではありません。", + correct: true + }, + { + text: "モデルの設定", + explain: "正解です!全てのモデル設定はpush_to_hubメソッドを備えており、レポジトリにプッシュすることができます。その他に共有できるものは何でしょうか?", + correct: true + }, + { + text: "モデル", + explain: "正解です!全てのモデルはpush_to_hubメソッドを備えており、モデルとその設定ファイルをレポジトリにプッシュすることができます。でも他にも共有できるものがあります。", + correct: true + }, + { + text: "トレーナー", + explain: "正解です!Trainerpush_to_hubメソッドを備えており、モデル、モデル設定、トークナイザー、モデルカードの下書きを、レポジトリにプッシュすることができます。その他の正解も当ててみましょう!", + correct: true + } + ]} +/> +{:else} +push_to_hubメソッドを備えており、全てのトークナイザーファイル(ボキャブラリー、トークナイザーのアーキテクチャ、等々)をレポジトリにプッシュすることができます。でもこれだけが正解ではありません。", + correct: true + }, + { + text: "モデルの設定", + explain: "正解です!全てのモデル設定はpush_to_hubメソッドを備えており、レポジトリにプッシュすることができます。その他に共有できるものは何でしょうか?", + correct: true + }, + { + text: "モデル", + explain: "正解です!Trainerpush_to_hubメソッドを備えており、モデル、モデル設定、トークナイザー、モデルカードの下書きを、レポジトリにプッシュすることができます。その他の正解も当ててみましょう!", + correct: true + }, + { + text: "専用のコールバックを備えた上記の全て", + explain: "正解です!PushToHubCallbackは学習中、定期的にこれらのオブジェクトをレポジトリに送信します。", + correct: true + } + ]} +/> +{/if} + +### 6. `push_to_hub()`メソッドやCLIツールを使用する際の最初のステップは何でしょうか? + + + +### 7. モデルとトークナイザーはどうやってハブにアップロードすればよいですか? + +huggingface_hubユーティリティでラップする。", + explain: "モデルとトークナイザーは既にhuggingface_hubユーティリティの恩恵を受けています。追加のラッピングは必要はありません。" + }, + { + text: "ディスクに保存して、transformers-cli upload-modelを実行する。", + explain: "upload-modelというコマンドは存在しません。" + } + ]} +/> + +### 8. `Repository`クラスでできる git 操作はなんでしょう? + +git_commit()メソッドはそのためにあります。", + correct: true + }, + { + text: "プル", + explain: "それがgit_pull()メソッドの目的です。", + correct: true + }, + { + text: "プッシュ", + explain: "これを行うのがgit_push()メソッドです。", + correct: true + }, + { + text: "マージ", + explain: "このAPIを通してのマージは、未来永劫絶対にできません。" + } + ]} +/> diff --git a/chapters/ja/event/1.mdx b/chapters/ja/event/1.mdx new file mode 100644 index 000000000..6d38ad10b --- /dev/null +++ b/chapters/ja/event/1.mdx @@ -0,0 +1,204 @@ +# パート2公開記念イベント + +パート2のコース公開後、Fine-tuningのスプリントの前に、2日間のトークライブイベントが開催されました。 +詳細については以下のページから見ることが出来ます。 + + +## 1日目: Transformersライブラリとその学習方法を俯瞰する + +**Thomas Wolf:** *転移学習とTransformersライブラリの誕生* + +
+ +
+ +

+Thomasによるトークの概要図 +

+ +Thomas Wolfは、Hugging Faceの共同設立者であり、主任研究員です。 +彼とHugging Faceチームが作成したツールは、Facebook人工知能研究所、Googleリサーチ、DeepMind、Amazonリサーチ、Apple、アレン人工知能研究所、および多くの大学を含む5,000以上の研究機関に使用されています。 +Thomas Wolfは人工知能の分野における最大の研究機関の創始者であり、[BigScience](https://bigscience.huggingface.co)をはじめとする、世界で広く利用されている[ライブラリやツール](https://github.com/huggingface/)の開発者です。 +加えて、人工知能と自然言語処理の分野におけるリーダーであり、世界中のカンファレンスに定期的に招待されるスピーカーです。[https://thomwolf.io](https://thomwolf.io). + + +**Jay Alammar:** *Transformersモデルの学習方法の可視化* + +
+ +
+ +

+Jayによるトークの概要図 +

+ +Jay Alammarは機械学習ツールやコンセプトを基本的なもの(NumPyやPandasのドキュメント)から最先端のもの(Transformers、BERT、GPT-3)まで視覚的に理解できるようなブログを書いています。 + +**Margaret Mitchell:** *機械学習開発における価値観* + +
+ +
+ +

+Margaretによるトークの概要図 +

+ +Margaret Mitchellは、Ethical AIの研究者であり、現在、企業におけるAI開発の倫理的な観点に焦点をあてて研究しています。 +彼女は自然言語生成、支援技術、コンピュータビジョン、およびAI倫理に関する50以上の論文を発表し、会話生成と感情分類の分野で複数の特許を保有しています。 +以前はGoogle AIにリサーチサイエンティストとして勤務しており、Google's Ethical AIグループを設立、リーダーとしてAI倫理の基礎研究およびGoogle内部でのAI倫理の運用に注力していました。 +Google入社以前は、Microsoft Researchで画像からの言語生成に焦点を当てた研究員、ジョンズ・ホプキンズ大学でベイズモデリングと情報抽出に焦点を当てたポスドクを務めていました。 +アバディーン大学でコンピュータサイエンスの博士号を、ワシントン大学で計算言語学の修士号を取得しています。 +学位を取得する傍ら、2005年から2012年まで、オレゴン健康科学大学で機械学習、神経障害、支援技術に関する研究に従事していました。 +彼女は多様性やコンピュータサイエンス、倫理などの多くの分野でワークショップや活動を率先して行ってきました。 +彼女の研究は、アッシュ・カーター国防長官や米国盲人財団から表彰され、複数のテクノロジー企業で導入されています。 +ちなみに彼女はガーデニングと犬、猫が好きです。 + +**Matthew Watson and Chen Qian:** *Kerasによる自然言語処理のワークフロー* + +
+ +
+ +

+MatthewとChenによるトークの概要図 +

+ +Matthew Watsonは、Kerasチームの機械学習エンジニアで、ハイレベルのモデリングAPIの開発を行っています。 +スタンフォード大学でコンピュータグラフィックスを専攻し、修士号を取得しました。 +彼はもともと英語を専攻していましたが、コンピュータサイエンスに転向ました。 +分野を超えて仕事をし、より多くの人が自然言語処理にアクセスできるようにすることに情熱を傾けています。 + +Chen QianはKerasチームのソフトウェアエンジニアで、彼もハイレベルのモデリングAPIの開発を行っています。 +スタンフォード大学で電気工学の修士号を取得しました。 +機械学習タスクのコード実装の簡素化と大規模機械学習に特に興味を持っています。 + +**Mark Saroufim:** *Pytorchでモデルを学習させる方法* + +
+ +
+ +

+Markによるトークの概要図 +

+ +Mark SaroufimはPytorchのパートナーエンジニアで、TorchServeやPytorch Enterpriseを含むOSSの開発に携わっています。 +以前はGraphcore、[yuri.ai](http://yuri.ai/)、Microsoft、NASAのジェット推進研究所で応用科学者、プロダクトマネージャーを務めていました。 +プログラミングをもっと楽しくすることに情熱を注いでいます。 + + +**Jakob Uszkoreit:** *壊れてないものは直すな壊そう* + +
+ +
+ +

+Jakobによるトークの概要図 +

+ +Jakob Uszkoreitは、ディープラーニングを用いてワクチンや治療薬のためのRNA分子を設計している機関であるInceptiveの共同創設者です。 +InceptiveはRNAベースの医薬品をより入手しやすく、より効果的で、より広く適用できるようにすることを目標にしています。 +以前はGoogleに10年以上勤務し、Google Brain、 Research and Searchの研究開発チームを率いて、ディープラーニングの基礎、コンピュータビジョン、言語理解、機械翻訳に取り組んでいました。 + + +## 2日目: 使用するツールの紹介 + +**Lewis Tunstall:** *🤗 TransformersのTrainerを使ったシンプルな学習* + +
+ +
+ +Lewis TunstallはHugging Faceの機械学習エンジニアで、オープンソースのツールを開発し、より広いコミュニティで利用できるようにすることに注力しています。 +また、オライリーの書籍[Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/)の共著者でもあります。 +Twitter (@_lewtun) では、自然言語処理に関するtipsを紹介しています. + + +**Matthew Carrigan:** *TensorFlowの新機能として追加された🤗 Transformersと🤗 Datasets* + +
+ +
+ +Matthew CarriganはTransformersでTensorFlowのメンテナンスを担当しています。 +彼はいずれTwitterアカウント@carrigmatを通じて、PyTorch派に対するTensorflow派のリーダーとなるでしょう。 + +**Lysandre Debut:** *機械学習プロジェクトのコラボレーションと共有の手段としてのHugging Face Hub* + +
+ +
+ +

+Lysandreによるトークの概要図 +

+ +Lysandre DebutはHugging Faceの機械学習エンジニアで、多くのオープンソースプロジェクトに携わっています。 +彼の目的は、非常にシンプルなAPIを持つ強力なツールを開発することで、機械学習を誰にでもアクセスできるようにすることです。 + +**Lucile Saulnier:** *🤗 Transformersと🤗 Tokenizersで自分だけのトークナイザーを手に入れる* + +
+ +
+ +Lucile SaulnierはHugging Faceの機械学習エンジニアで、オープンソースツールの開発および使用支援を行っています。 +彼女は協調学習やBigScienceなど、自然言語処理分野の多くの研究プロジェクトにも積極的に参加しています。 + +**Sylvain Gugger:** *PyTorchの学習効率を高める🤗 Accelerate* + +
+ +
+ +Sylvain GuggerはHugging Faceのリサーチエンジニアで、🤗 Transformersのコアメンテナーの一人であり、🤗 Accelerateの開発者でもあります。 +彼はモデルの学習をより身近なものにすることが好きです。 + +**Merve Noyan:** *モデルのデモを展示する🤗 Spaces* + +
+ +
+ +Merve NoyanはHugging FaceのDeveloper Advocateで、誰もが機械学習を使うことが出来るように、ツールの開発とその周辺のコンテンツ構築に取り組んでいます。 + + +**Abubakar Abid:** *機械学習アプリケーションを素早く構築する* + +
+ +
+ +

+Abubakarによるトークの概要図 +

+ +Abubakar Abidは[Gradio](www.gradio.app)のCEOです。 +2015年にMITで電気工学とコンピュータサイエンスの理学士号を取得し、2021年にスタンフォードで応用機械学習の博士号を取得しました。 +Gradioでは機械学習モデルのデモ、デバッグ、デプロイを容易にすることに取り組んでいます。 + +**Mathieu Desvé:** *AWSの機械学習のビジョン: すべての顧客が機械学習にアクセスできるようにする* + +
+ +
+ +

+Mathieuによるトークの概要図 +

+ +Mathieu Desvéはテクノロジー好きで、暇さえあればものづくりをしています。 +クライアントとユーザーの問題解決に取り組むことが好きで、日々学び続けています。 +2004年以来、フロントエンド、バックエンド、インフラストラクチャー、オペレーション、マネジメントなど、さまざまなポジションを経験してきました。 +技術的、経営的に共通する問題を機敏に解決することを心がけています。 + +**Philipp Schmid:** *Amazon SageMakerと🤗 Transformersを使った学習管理* + +
+ +
+ +Philipp Schmidは、Hugging Faceの機械学習エンジニア兼テックリードで、Amazon SageMakerチームとの協業をリードしています。 +最先端の自然言語処理モデルを誰もが使えるように製品化し、ディープラーニングの使いやすさを向上することに情熱を注いでいます。 diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index 3c5b11bea..168333305 100644 --- a/chapters/pt/_toctree.yml +++ b/chapters/pt/_toctree.yml @@ -58,8 +58,21 @@ title: Big data? 🤗 Datasets ao resgate - local: chapter5/5 title: Criando seu próprio dataset + - local: chapter5/6 + title: Busca semântica com o FAISS + - local: chapter5/7 + title: Confira o 🤗 Datasets! + - local: chapter5/8 + title: Questionário de fim de capítulo + quiz: 5 - title: 7. Principais tarefas NLP sections: - local: chapter7/1 title: Introdução + +- title: Evento do curso Hugging Face + sections: + - local: event/1 + title: Evento de lançamento da Parte 2 + \ No newline at end of file diff --git a/chapters/pt/chapter5/6.mdx b/chapters/pt/chapter5/6.mdx new file mode 100644 index 000000000..3ced95ff6 --- /dev/null +++ b/chapters/pt/chapter5/6.mdx @@ -0,0 +1,529 @@ + + +# Busca semântica com o FAISS + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Na [seção 5](/course/chapter5/5), criamos um conjunto de dados de issues e comentários do GitHub do repositório 🤗 Datasets. Nesta seção, usaremos essas informações para construir um mecanismo de pesquisa que pode nos ajudar a encontrar respostas para nossas perguntas mais urgentes sobre a biblioteca! + + + +## Usando embeddings para pesquisa semântica + +Como vimos no [Capítulo 1](/course/chapter1), os modelos de linguagem baseados em Transformer representam cada token em um intervalo de texto como um _vetor de incorporação_. Acontece que é possível "agrupar" as incorporações individuais para criar uma representação vetorial para frases inteiras, parágrafos ou (em alguns casos) documentos. Essas incorporações podem ser usadas para encontrar documentos semelhantes no corpus calculando a similaridade do produto escalar (ou alguma outra métrica de similaridade) entre cada incorporação e retornando os documentos com maior sobreposição. + +Nesta seção, usaremos embeddings para desenvolver um mecanismo de pesquisa semântica. Esses mecanismos de pesquisa oferecem várias vantagens sobre as abordagens convencionais que se baseiam na correspondência de palavras-chave em uma consulta com os documentos. + +
+Semantic search. + +
+ +## Carregando e preparando o conjunto de dados + +A primeira coisa que precisamos fazer é baixar nosso conjunto de dados de issues do GitHub, então vamos usar a biblioteca 🤗 Hub para resolver a URL onde nosso arquivo está armazenado no Hugging Face Hub: + +```py +from huggingface_hub import hf_hub_url + +data_files = hf_hub_url( + repo_id="lewtun/github-issues", + filename="datasets-issues-with-comments.jsonl", + repo_type="dataset", +) +``` + +Com a URL armazenada em `data_files`, podemos carregar o conjunto de dados remoto usando o método apresentado na [seção 2](/course/chapter5/2): + +```py +from datasets import load_dataset + +issues_dataset = load_dataset("json", data_files=data_files, split="train") +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 2855 +}) +``` + +Aqui nós especificamos a divisão padrão `train` em `load_dataset()`, então ele retorna um `Dataset` em vez de um `DatasetDict`. A primeira ordem de negócios é filtrar os pull request, pois elas tendem a ser raramente usadas para responder a consultas de usuários e introduzirão ruído em nosso mecanismo de pesquisa. Como já deve ser familiar, podemos usar a função `Dataset.filter()` para excluir essas linhas em nosso conjunto de dados. Enquanto estamos nisso, também vamos filtrar as linhas sem comentários, pois elas não fornecem respostas às consultas dos usuários: + +```py +issues_dataset = issues_dataset.filter( + lambda x: (x["is_pull_request"] == False and len(x["comments"]) > 0) +) +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 771 +}) +``` +Podemos ver que há muitas colunas em nosso conjunto de dados, a maioria das quais não precisamos para construir nosso mecanismo de pesquisa. De uma perspectiva de pesquisa, as colunas mais informativas são `title`, `body` e `comments`, enquanto `html_url` nos fornece um link de volta para a issue de origem. Vamos usar a função `Dataset.remove_columns()` para descartar o resto: + +```py +columns = issues_dataset.column_names +columns_to_keep = ["title", "body", "html_url", "comments"] +columns_to_remove = set(columns_to_keep).symmetric_difference(columns) +issues_dataset = issues_dataset.remove_columns(columns_to_remove) +issues_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body'], + num_rows: 771 +}) +``` + +Para criar nossos embeddings, aumentaremos cada comentário com o título e o corpo da issue, pois esses campos geralmente incluem informações contextuais úteis. Como nossa coluna `comments` é atualmente uma lista de comentários para cada issue, precisamos "explodir" a coluna para que cada linha consista em uma tupla `(html_url, title, body, comment)`. No Pandas podemos fazer isso com a função [`DataFrame.explode()`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html), que cria uma nova linha para cada elemento em uma coluna semelhante a uma lista, enquanto replica todos os outros valores de coluna. Para ver isso em ação, vamos primeiro mudar para o formato `DataFrame` do Pandas: + +```py +issues_dataset.set_format("pandas") +df = issues_dataset[:] +``` + +Se inspecionarmos a primeira linha neste `DataFrame`, podemos ver que há quatro comentários associados a esta issue: + +```py +df["comments"][0].tolist() +``` + +```python out +['the bug code locate in :\r\n if data_args.task_name is not None:\r\n # Downloading and loading a dataset from the hub.\r\n datasets = load_dataset("glue", data_args.task_name, cache_dir=model_args.cache_dir)', + 'Hi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com\r\n\r\nNormally, it should work if you wait a little and then retry.\r\n\r\nCould you please confirm if the problem persists?', + 'cannot connect,even by Web browser,please check that there is some problems。', + 'I can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...'] +``` + +Quando explodimos `df`, esperamos obter uma linha para cada um desses comentários. Vamos verificar se é o caso: + +```py +comments_df = df.explode("comments", ignore_index=True) +comments_df.head(4) +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
html_urltitlecommentsbody
0https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comthe bug code locate in :\r\n if data_args.task_name is not None...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
1https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comHi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
2https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comcannot connect,even by Web browser,please check that there is some problems。Hello,\r\nI am trying to run run_glue.py and it gives me this error...
3https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comI can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
+ +Ótimo, podemos ver que as linhas foram replicadas, com a coluna `comments` contendo os comentários individuais! Agora que terminamos com o Pandas, podemos voltar rapidamente para um `Dataset` carregando o `DataFrame` na memória + +```py +from datasets import Dataset + +comments_dataset = Dataset.from_pandas(comments_df) +comments_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body'], + num_rows: 2842 +}) +``` + +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. + + + +Agora que temos um comentário por linha, vamos criar uma nova coluna `comments_length` que contém o número de palavras por comentário: + +```py +comments_dataset = comments_dataset.map( + lambda x: {"comment_length": len(x["comments"].split())} +) +``` + +Podemos usar essa nova coluna para filtrar comentários curtos, que normalmente incluem coisas como "cc @lewtun" ou "Obrigado!" que não são relevantes para o nosso motor de busca. Não há um número preciso para selecionar o filtro, mas cerca de 15 palavras parece um bom começo: + +```py +comments_dataset = comments_dataset.filter(lambda x: x["comment_length"] > 15) +comments_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body', 'comment_length'], + num_rows: 2098 +}) +``` + +Depois de limpar um pouco nosso conjunto de dados, vamos concatenar o título, a descrição e os comentários da issue em uma nova coluna `text`. Como de costume, escreveremos uma função simples que podemos passar para `Dataset.map()`: + +```py +def concatenate_text(examples): + return { + "text": examples["title"] + + " \n " + + examples["body"] + + " \n " + + examples["comments"] + } + + +comments_dataset = comments_dataset.map(concatenate_text) +``` + +Finalmente estamos prontos para criar alguns embeddings! Vamos dar uma olhada. + +## Criando embeddings de texto + +Vimos no [Capítulo 2](/course/chapter2) que podemos obter tokens embeddings usando a classe `AutoModel`. Tudo o que precisamos fazer é escolher um checkpoint adequado para carregar o modelo. Felizmente, existe uma biblioteca chamada `sentence-transformers` dedicada à criação de embeddings. Conforme descrito na [documentação da biblioteca](https://www.sbert.net/examples/applications/semantic-search/README.html#symmetric-vs-asymmetric-semantic-search), nosso caso de uso é um exemplo de _asymmetric semantic search_ porque temos uma consulta curta cuja resposta gostaríamos de encontrar em um documento mais longo, como um comentário da issue. A útil [tabela de visão geral do modelo](https://www.sbert.net/docs/pretrained_models.html#model-overview) na documentação indica que o checkpoint `multi-qa-mpnet-base-dot-v1` tem o melhor desempenho para pesquisa semântica, então usaremos isso para nosso aplicativo. Também carregaremos o tokenizer usando o mesmo checkpoint: + +{#if fw === 'pt'} + +```py +from transformers import AutoTokenizer, AutoModel + +model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1" +tokenizer = AutoTokenizer.from_pretrained(model_ckpt) +model = AutoModel.from_pretrained(model_ckpt) +``` + +Para acelerar o processo de embedding, é útil colocar o modelo e as entradas em um dispositivo GPU, então vamos fazer isso agora: + +```py +import torch + +device = torch.device("cuda") +model.to(device) +``` + +{:else} + +```py +from transformers import AutoTokenizer, TFAutoModel + +model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1" +tokenizer = AutoTokenizer.from_pretrained(model_ckpt) +model = TFAutoModel.from_pretrained(model_ckpt, from_pt=True) +``` + +Observe que definimos `from_pt=True` como um argumento do método `from_pretrained()`. Isso ocorre porque o checkpoint `multi-qa-mpnet-base-dot-v1` só tem pesos PyTorch, portanto, definir `from_pt=True` irá convertê-los automaticamente para o formato TensorFlow para nós. Como você pode ver, é muito simples alternar entre frameworks no 🤗 Transformers! + +{/if} + +Como mencionamos anteriormente, gostaríamos de representar cada entrada em nosso corpus de issues do GitHub como um único vetor, portanto, precisamos "pool" ou calcular a média de nossas incorporações de token de alguma forma. Uma abordagem popular é realizar *CLS pooling* nas saídas do nosso modelo, onde simplesmente coletamos o último estado oculto para o token especial `[CLS]`. A função a seguir faz o truque para nós: + +```py +def cls_pooling(model_output): + return model_output.last_hidden_state[:, 0] +``` + +Em seguida, criaremos uma função auxiliar que tokenizará uma lista de documentos, colocará os tensores na GPU, os alimentará no modelo e, finalmente, aplicará o agrupamento CLS às saídas: + +{#if fw === 'pt'} + +```py +def get_embeddings(text_list): + encoded_input = tokenizer( + text_list, padding=True, truncation=True, return_tensors="pt" + ) + encoded_input = {k: v.to(device) for k, v in encoded_input.items()} + model_output = model(**encoded_input) + return cls_pooling(model_output) +``` + +Podemos testar o funcionamento da função alimentando-a com a primeira entrada de texto em nosso corpus e inspecionando a forma de saída: + +```py +embedding = get_embeddings(comments_dataset["text"][0]) +embedding.shape +``` + +```python out +torch.Size([1, 768]) +``` + +Ótimo, convertemos a primeira entrada em nosso corpus em um vetor de 768 dimensões! Podemos usar `Dataset.map()` para aplicar nossa função `get_embeddings()` a cada linha em nosso corpus, então vamos criar uma nova coluna `embeddings` da seguinte forma: + +```py +embeddings_dataset = comments_dataset.map( + lambda x: {"embeddings": get_embeddings(x["text"]).detach().cpu().numpy()[0]} +) +``` + +{:else} + +```py +def get_embeddings(text_list): + encoded_input = tokenizer( + text_list, padding=True, truncation=True, return_tensors="tf" + ) + encoded_input = {k: v for k, v in encoded_input.items()} + model_output = model(**encoded_input) + return cls_pooling(model_output) +``` + +Podemos testar o funcionamento da função alimentando-a com a primeira entrada de texto em nosso corpus e inspecionando a forma de saída: + +```py +embedding = get_embeddings(comments_dataset["text"][0]) +embedding.shape +``` + +```python out +TensorShape([1, 768]) +``` + +Ótimo, convertemos a primeira entrada em nosso corpus em um vetor de 768 dimensões! Podemos usar `Dataset.map()` para aplicar nossa função `get_embeddings()` a cada linha em nosso corpus, então vamos criar uma nova coluna `embeddings` da seguinte forma: + +```py +embeddings_dataset = comments_dataset.map( + lambda x: {"embeddings": get_embeddings(x["text"]).numpy()[0]} +) +``` + +{/if} + +Observe que convertemos os embeddings em arrays NumPy -- isso porque 🤗 Datasets requer esse formato quando tentamos indexá-los com FAISS, o que faremos a seguir. + + +## Usando FAISS para busca de similaridade + +Agora que temos um conjunto de dados de embeddings, precisamos de alguma maneira de pesquisá-los. Para fazer isso, usaremos uma estrutura de dados especial em 🤗 Datasets chamada _FAISS index_. [FAISS](https://faiss.ai/) (abreviação de Facebook AI Similarity Search) é uma biblioteca que fornece algoritmos eficientes para pesquisar rapidamente e agrupar vetores de incorporação. + +A idéia básica por trás do FAISS é criar uma estrutura de dados especial chamada _index_ que permite descobrir quais embeddings são semelhantes a um embedding de entrada. Criar um índice FAISS em 🤗 Datasets é simples -- usamos a função `Dataset.add_faiss_index()` e especificamos qual coluna do nosso conjunto de dados gostaríamos de indexar: + +```py +embeddings_dataset.add_faiss_index(column="embeddings") +``` + +Agora podemos realizar consultas neste índice fazendo uma pesquisa do vizinho mais próximo com a função `Dataset.get_nearest_examples()`. Vamos testar isso primeiro incorporando uma pergunta da seguinte forma: + + +{#if fw === 'pt'} + +```py +question = "How can I load a dataset offline?" +question_embedding = get_embeddings([question]).cpu().detach().numpy() +question_embedding.shape +``` + +```python out +torch.Size([1, 768]) +``` + +{:else} + +```py +question = "How can I load a dataset offline?" +question_embedding = get_embeddings([question]).numpy() +question_embedding.shape +``` + +```python out +(1, 768) +``` + +{/if} + +Assim como com os documentos, agora temos um vetor de 768 dimensões representando a consulta, que podemos comparar com todo o corpus para encontrar os embeddings mais semelhantes: + +```py +scores, samples = embeddings_dataset.get_nearest_examples( + "embeddings", question_embedding, k=5 +) +``` + +A função `Dataset.get_nearest_examples()` retorna uma tupla de pontuações que classificam a sobreposição entre a consulta e o documento e um conjunto correspondente de amostras (aqui, as 5 melhores correspondências). Vamos coletá-los em um `pandas.DataFrame` para que possamos classificá-los facilmente: + +```py +import pandas as pd + +samples_df = pd.DataFrame.from_dict(samples) +samples_df["scores"] = scores +samples_df.sort_values("scores", ascending=False, inplace=True) +``` + +Agora podemos iterar nas primeiras linhas para ver como nossa consulta correspondeu aos comentários disponíveis: + +```py +for _, row in samples_df.iterrows(): + print(f"COMMENT: {row.comments}") + print(f"SCORE: {row.scores}") + print(f"TITLE: {row.title}") + print(f"URL: {row.html_url}") + print("=" * 50) + print() +``` + +```python out +""" +COMMENT: Requiring online connection is a deal breaker in some cases unfortunately so it'd be great if offline mode is added similar to how `transformers` loads models offline fine. + +@mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like? +SCORE: 25.505046844482422 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: The local dataset builders (csv, text , json and pandas) are now part of the `datasets` package since #1726 :) +You can now use them offline +\`\`\`python +datasets = load_dataset("text", data_files=data_files) +\`\`\` + +We'll do a new release soon +SCORE: 24.555509567260742 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: I opened a PR that allows to reload modules that have already been loaded once even if there's no internet. + +Let me know if you know other ways that can make the offline mode experience better. I'd be happy to add them :) + +I already note the "freeze" modules option, to prevent local modules updates. It would be a cool feature. + +---------- + +> @mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like? + +Indeed `load_dataset` allows to load remote dataset script (squad, glue, etc.) but also you own local ones. +For example if you have a dataset script at `./my_dataset/my_dataset.py` then you can do +\`\`\`python +load_dataset("./my_dataset") +\`\`\` +and the dataset script will generate your dataset once and for all. + +---------- + +About I'm looking into having `csv`, `json`, `text`, `pandas` dataset builders already included in the `datasets` package, so that they are available offline by default, as opposed to the other datasets that require the script to be downloaded. +cf #1724 +SCORE: 24.14896583557129 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: > here is my way to load a dataset offline, but it **requires** an online machine +> +> 1. (online machine) +> +> ``` +> +> import datasets +> +> data = datasets.load_dataset(...) +> +> data.save_to_disk(/YOUR/DATASET/DIR) +> +> ``` +> +> 2. copy the dir from online to the offline machine +> +> 3. (offline machine) +> +> ``` +> +> import datasets +> +> data = datasets.load_from_disk(/SAVED/DATA/DIR) +> +> ``` +> +> +> +> HTH. + + +SCORE: 22.893993377685547 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: here is my way to load a dataset offline, but it **requires** an online machine +1. (online machine) +\`\`\` +import datasets +data = datasets.load_dataset(...) +data.save_to_disk(/YOUR/DATASET/DIR) +\`\`\` +2. copy the dir from online to the offline machine +3. (offline machine) +\`\`\` +import datasets +data = datasets.load_from_disk(/SAVED/DATA/DIR) +\`\`\` + +HTH. +SCORE: 22.406635284423828 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== +""" +``` + +Nada mal! Nosso segundo resultado parece corresponder à consulta. + + + +✏️ **Experimente!** Crie sua própria consulta e veja se consegue encontrar uma resposta nos documentos recuperados. Você pode ter que aumentar o parâmetro `k` em `Dataset.get_nearest_examples()` para ampliar a pesquisa. + + \ No newline at end of file diff --git a/chapters/pt/chapter5/7.mdx b/chapters/pt/chapter5/7.mdx new file mode 100644 index 000000000..e83ba6a81 --- /dev/null +++ b/chapters/pt/chapter5/7.mdx @@ -0,0 +1,11 @@ +# Confira o 🤗 Datasets! + +Bem, esse foi um belo passeio pela biblioteca 🤗 Datasets - parabéns por chegar até aqui! Com o conhecimento que você adquiriu neste capítulo, você deve ser capaz de: + +- Carregue conjuntos de dados de qualquer lugar, seja o Hugging Face Hub, seu laptop ou um servidor remoto em sua empresa. +- Organize seus dados usando uma combinação das funções `Dataset.map()` e `Dataset.filter()`. +- Alterne rapidamente entre formatos de dados como Pandas e NumPy usando `Dataset.set_format()`. +- Crie seu próprio conjunto de dados e envie-o para o Hugging Face Hub. +- Incorpore seus documentos usando um modelo Transformer e construa um mecanismo de pesquisa semântica usando o FAISS. + +No [Capítulo 7](/course/chapter7), usaremos tudo isso para nos aprofundarmos nas principais tarefas de PNL para as quais os modelos Transformer são ótimos. Antes de avançar, no entanto, teste seu conhecimento de 🤗 Datasets com um teste rápido! \ No newline at end of file diff --git a/chapters/pt/chapter5/8.mdx b/chapters/pt/chapter5/8.mdx new file mode 100644 index 000000000..152c191e5 --- /dev/null +++ b/chapters/pt/chapter5/8.mdx @@ -0,0 +1,223 @@ + + +# Questionário de fim de capítulo +Este capítulo cobriu muita coisa! Não se preocupe se você não entendeu todos os detalhes; os próximos capítulos o ajudarão a entender como as coisas funcionam. + +Antes de prosseguir, vamos testar o que você aprendeu neste capítulo. + +### 1. A função `load_dataset()` em 🤗 Datasets permite carregar um dataset de qual dos seguintes locais? + +data_files de load_dataset() para carregar conjuntos de dados localmente.", + correct: true + }, + { + text: "Do Hugging Face Hub", + explain: "Correto! Você pode carregar conjuntos de dados no Hub fornecendo o ID do conjunto de dados, por exemplo, load_dataset('emotion').", + correct: true + }, + { + text: "De um servidor remoto", + explain: "Correto! Você pode passar URLs para o argumento data_files de load_dataset() para carregar arquivos remotos.", + correct: true + }, + ]} +/> +### 2. Suponha que você carregue uma das tarefas GLUE da seguinte forma: + +```py +from datasets import load_dataset + +dataset = load_dataset("glue", "mrpc", split="train") +``` + +Qual dos seguintes comandos produzirá uma amostra aleatória de 50 elementos do `conjunto de dados`? + +dataset.sample(50)", + explain: "Isso está incorreto -- não há método Dataset.sample()." + }, + { + text: "dataset.shuffle().select(range(50))", + explain: "Correto! Como você viu neste capítulo, você primeiro embaralha o conjunto de dados e depois seleciona as amostras dele.", + correct: true + }, + { + text: "dataset.select(range(50)).shuffle()", + explain: "Isso está incorreto - embora o código seja executado, ele embaralha apenas os primeiros 50 elementos do conjunto de dados." + } + ]} +/> + +### 3. Suponha que você tenha um conjunto de dados sobre animais domésticos chamado `pets_dataset`, que tem uma coluna `name` que denota o nome de cada animal. Qual das seguintes abordagens permitiria filtrar o conjunto de dados para todos os animais de estimação cujos nomes começam com a letra "L"? + +pets_dataset.filter(lambda x : x['name'].startswith('L'))", + explain: "Correto! Usar uma função lambda do Python para esses filtros rápidos é uma ótima ideia. Você consegue pensar em outra solução?", + correct: true + }, + { + text: "pets_dataset.filter(lambda x['name'].startswith('L'))", + explain: "Isso está incorreto -- uma função lambda assume a forma geral lambda *arguments* : *expression*, então você precisa fornecer argumentos neste caso." + }, + { + text: "Criar uma função assim def filter_names(x): return x['name'].startswith('L') e executa-la pets_dataset.filter(filter_names).", + explain: "Correto! Assim como com Dataset.map(), você pode passar funções explícitas para Dataset.filter(). Isso é útil quando você tem alguma lógica complexa que não é adequado para uma função lambda curta. Qual das outras soluções funcionaria?", + correct: true + } + ]} +/> + +### 4. O que é mapeamento de memória? + + + +### 5. Quais dos seguintes são os principais benefícios do mapeamento de memória? + + + +### 6. Por que o código a seguir falha? + +```py +from datasets import load_dataset + +dataset = load_dataset("allocine", streaming=True, split="train") +dataset[0] +``` + +IterableDataset.", + explain: "Correto! Um IterableDataset é um gerador, não um contêiner, então você deve acessar seus elementos usando next(iter(dataset)).", + correct: true + }, + { + text: "O conjunto de dados allocine não tem uma divisão train.", + explain: "Isso está incorreto - confira o cartão de conjunto de dados [allocine](https://huggingface.co/datasets/allocine) no Hub para ver quais divisões ele contém." + } + ]} +/> + +### 7. Quais dos seguintes são os principais benefícios de criar um cartão de conjunto de dados? + + + +### 8. O que é pesquisa semântica? + + + +### 9. Para pesquisa semântica assimétrica, você geralmente tem: + + + +### 10. Posso usar 🤗 Datasets para carregar dados para uso em outros domínios, como processamento de fala (audios)? + +conjunto de dados MNIST no Hub para um exemplo de visão computacional." + }, + { + text: "Sim", + explain: "Correto! Confira os desenvolvimentos interessantes com fala e visão na biblioteca 🤗 Transformers para ver como 🤗 Datasets é usado nesses domínios.", + correct : true + }, + ]} +/> diff --git a/chapters/pt/event/1.mdx b/chapters/pt/event/1.mdx new file mode 100644 index 000000000..3eb60cc4f --- /dev/null +++ b/chapters/pt/event/1.mdx @@ -0,0 +1,165 @@ +# Evento de lançamento da Parte 2 + +Para o lançamento da parte 2 do curso, organizamos um evento ao vivo com dois dias de palestras antes de um sprint de ajuste. Se você perdeu, pode acompanhar as palestras que estão listadas abaixo! + +## Dia 1: Uma visão de alto nível dos Transformers e como treiná-los + +**Thomas Wolf:** *Transfer Learning and the birth of the Transformers library* + +
+ +
+ +

+A visual summary of Thom's talk +

+ +Thomas Wolf é cofundador e Chief Science Officer da Hugging Face. As ferramentas criadas por Thomas Wolf e a equipe Hugging Face são usadas em mais de 5.000 organizações de pesquisa, incluindo Facebook Artificial Intelligence Research, Google Research, DeepMind, Amazon Research, Apple, Allen Institute for Artificial Intelligence e na maioria dos departamentos universitários. Thomas Wolf é o iniciador e presidente sênior da maior colaboração de pesquisa que já existiu em Inteligência Artificial: [“BigScience”](https://bigscience.huggingface.co), bem como um conjunto de [bibliotecas e ferramentas amplamente utilizadas ](https://github.com/huggingface/). Thomas Wolf também é um educador prolífico, um líder de pensamento no campo de Inteligência Artificial e Processamento de Linguagem Natural e um orador convidado regular para conferências em todo o mundo [https://thomwolf.io](https://thomwolf.io ). + +**Jay Alammar:** *A gentle visual intro to Transformers models* + +
+ +
+ +

+A visual summary of Jay's talk +

+ +Por meio de seu popular blog de ML, Jay ajudou milhões de pesquisadores e engenheiros a entender visualmente ferramentas e conceitos de aprendizado de máquina desde o básico (terminando em NumPy, Pandas docs) até o de ponta (Transformers, BERT, GPT-3). + +**Margaret Mitchell:** *On Values in ML Development* + +
+ +
+ +

+A visual summary of Margaret's talk +

+ +Margaret Mitchell é uma pesquisadora que trabalha em IA ética, atualmente focada nos meandros do desenvolvimento de IA informada pela ética em tecnologia. Ela publicou mais de 50 artigos sobre geração de linguagem natural, tecnologia assistiva, visão computacional e ética em IA, e possui várias patentes nas áreas de geração de conversas e classificação de sentimentos. Ela trabalhou anteriormente no Google AI como Staff Research Scientist, onde fundou e co-liderou o grupo Ethical AI do Google, focado na pesquisa básica de ética em IA e na operacionalização da ética de IA internamente no Google. Antes de ingressar no Google, ela foi pesquisadora da Microsoft Research, focada na geração de visão computacional para linguagem; e fez pós-doutorado na Johns Hopkins, com foco em modelagem bayesiana e extração de informações. Ela possui doutorado em Ciência da Computação pela Universidade de Aberdeen e mestrado em linguística computacional pela Universidade de Washington. Enquanto se formava, ela também trabalhou de 2005 a 2012 em aprendizado de máquina, distúrbios neurológicos e tecnologia assistiva na Oregon Health and Science University. Ela liderou uma série de workshops e iniciativas nas interseções de diversidade, inclusão, ciência da computação e ética. Seu trabalho recebeu prêmios do Secretário de Defesa Ash Carter e da Fundação Americana para Cegos e foi implementado por várias empresas de tecnologia. Ela gosta de jardinagem, cães e gatos. + +**Matthew Watson and Chen Qian:** *NLP workflows with Keras* + +
+ +
+ +

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

+ +Matthew Watson é engenheiro de aprendizado de máquina na equipe Keras, com foco em APIs de modelagem de alto nível. Ele estudou Computação Gráfica durante a graduação e mestrado na Universidade de Stanford. Um quase graduado em inglês que se voltou para a ciência da computação, ele é apaixonado por trabalhar em várias disciplinas e tornar a PNL acessível a um público mais amplo. + +Chen Qian é engenheiro de software da equipe Keras, com foco em APIs de modelagem de alto nível. Chen obteve um mestrado em Engenharia Elétrica pela Universidade de Stanford e está especialmente interessado em simplificar as implementações de código de tarefas de ML e ML em larga escala. + +**Mark Saroufim:** *How to Train a Model with Pytorch* + +
+ +
+ +

+A visual summary of Mark's talk +

+ +Mark Saroufim é um engenheiro parceiro do Pytorch trabalhando em ferramentas de produção OSS, incluindo TorchServe e Pytorch Enterprise. Em suas vidas passadas, Mark foi Cientista Aplicado e Gerente de Produto na Graphcore, [yuri.ai](http://yuri.ai/), Microsoft e JPL da NASA. Sua principal paixão é tornar a programação mais divertida. + +**Jakob Uszkoreit:** *It Ain't Broke So Don't Fix Let's Break It* + +
+ +
+ +

+A visual summary of Jakob's talk +

+ +Jakob Uszkoreit é o cofundador da Inceptive. A Inceptive projeta moléculas de RNA para vacinas e terapias usando aprendizado profundo em larga escala em um circuito fechado com experimentos de alto rendimento com o objetivo de tornar os medicamentos baseados em RNA mais acessíveis, mais eficazes e mais amplamente aplicáveis. Anteriormente, Jakob trabalhou no Google por mais de uma década, liderando equipes de pesquisa e desenvolvimento no Google Brain, Research and Search trabalhando em fundamentos de aprendizado profundo, visão computacional, compreensão de idiomas e tradução automática. + +## Dia 2: As ferramentas a serem usadas + +**Lewis Tunstall:** *Simple Training with the 🤗 Transformers Trainer* + +
+ +
+ +Lewis é machine learning engineer no Hugging Face, focado em desenvolver ferramentas de código aberto e torná-las acessíveis para a comunidade em geral. Ele também é coautor do livro de O'Reilly [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). Você pode segui-lo no Twitter (@_lewtun) para dicas e truques de PNL! + +**Matthew Carrigan:** *New TensorFlow Features for 🤗 Transformers and 🤗 Datasets* + +
+ +
+ +Matt é responsável pela manutenção do TensorFlow em Transformers e, eventualmente, liderará um golpe contra a facção PyTorch, que provavelmente será coordenada por meio de sua conta no Twitter @carrigmat. + +**Lysandre Debut:** *The Hugging Face Hub as a means to collaborate on and share Machine Learning projects* + +
+ +
+ +

+A visual summary of Lysandre's talk +

+ +Lysandre é machine learning engineer no Hugging Face, onde está envolvido em muitos projetos de código aberto. Seu objetivo é tornar o Machine Learning acessível a todos, desenvolvendo ferramentas poderosas com uma API muito simples. + +**Lucile Saulnier:** *Get your own tokenizer with 🤗 Transformers & 🤗 Tokenizers* + +
+ +
+ +Lucile é engenheira de aprendizado de máquina na Hugging Face, desenvolvendo e dando suporte ao uso de ferramentas de código aberto. Ela também está ativamente envolvida em muitos projetos de pesquisa na área de Processamento de Linguagem Natural, como treinamento colaborativo e BigScience. + +**Sylvain Gugger:** *Supercharge your PyTorch training loop with 🤗 Accelerate* + +
+ +
+ +Sylvain é um Research Engineer no Hugging Face e um dos principais mantenedores do 🤗 Transformers e o desenvolvedor por trás do 🤗 Accelerate. Ele gosta de tornar o treinamento de modelo mais acessível. + +**Merve Noyan:** *Showcase your model demos with 🤗 Spaces* + +
+ +
+ +Merve é um desenvolvedor defensor da Hugging Face, trabalhando no desenvolvimento de ferramentas e na criação de conteúdo em torno delas para democratizar o aprendizado de máquina para todos. + +**Abubakar Abid:** *Building Machine Learning Applications Fast* + +
+ +
+ +

+A visual summary of Abubakar's talk +

+ +Abubakar Abid é o CEO da [Gradio](www.gradio.app). Ele recebeu seu bacharelado em Engenharia Elétrica e Ciência da Computação do MIT em 2015 e seu PhD em Aprendizado de Máquina Aplicado de Stanford em 2021. Em seu papel como CEO da Gradio, Abubakar trabalha para tornar os modelos de aprendizado de máquina mais fáceis de demonstrar, debugar, e implantar. + +**Mathieu Desvé:** *AWS ML Vision: Making Machine Learning Accessible to all Customers* + +
+ +
+ +

+A visual summary of Mathieu's talk +

+ +Entusiasta da tecnologia, maker nas horas vagas. Gosto de desafios e resolução de problemas de clientes e usuários, e trabalho com pessoas talentosas para aprender todos os dias. Desde 2004, atuo em várias posições alternando entre frontend, backend, infraestrutura, operações e gerenciamentos. Tente resolver problemas técnicos e gerenciais comuns de maneira ágil. + +**Philipp Schmid:** *Managed Training with Amazon SageMaker and 🤗 Transformers* + +
+ +
+ +Philipp Schmid é Machine Learning Engineer and Tech Lead no Hugging Face, onde lidera a colaboração com a equipe do Amazon SageMaker. Ele é apaixonado por democratizar e produzir modelos de PNL de ponta e melhorar a facilidade de uso do Deep Learning. diff --git a/chapters/ru/_toctree.yml b/chapters/ru/_toctree.yml index f3a4eaf6a..edfd8d605 100644 --- a/chapters/ru/_toctree.yml +++ b/chapters/ru/_toctree.yml @@ -43,5 +43,17 @@ title: Предобработка данных - local: chapter3/3 title: Fine-tuning модели с использованием Trainer API + local_fw: { pt: chapter3/3, tf: chapter3/3_tf } - local: chapter3/4 - title: Полное обучение модели \ No newline at end of file + title: Полное обучение модели + - local: chapter3/5 + title: Fine-tuning, итоги! + - local: chapter3/6 + title: Итоговый тест по главе + quiz: 3 +- title: 4. Hugging Face Hub + sections: + - local: chapter4/1 + title: Hugging Face Hub + - local: chapter4/2 + title: Использование предобученных моделей diff --git a/chapters/ru/chapter0/1.mdx b/chapters/ru/chapter0/1.mdx index e8cf0938f..52aa76502 100644 --- a/chapters/ru/chapter0/1.mdx +++ b/chapters/ru/chapter0/1.mdx @@ -1,6 +1,6 @@ # Введение -Добро пожаловать на курс от Hugging Face! Это введение поможет настроить рабочее окружение. Если вы только начинаете курс, мы рекомендуем сначала заглянуть в [Главу 1](/course/chapter1), затем вернуться и настроить среду, чтобы попробовать запустить код самостоятельно. +Добро пожаловать на курс от Hugging Face! Это введение поможет настроить рабочее окружение. Если вы только начинаете курс, мы рекомендуем сначала заглянуть в [Главу 1](/course/ru/chapter1), затем вернуться и настроить среду, чтобы попробовать запустить код самостоятельно. Все библиотеки, которые мы будем использовать в этом курсе доступны как Python-пакеты, мы покажем, как установить окружение и необходимые библиотеки. diff --git a/chapters/ru/chapter3/1.mdx b/chapters/ru/chapter3/1.mdx index a4fc5d0f1..10ed44252 100644 --- a/chapters/ru/chapter3/1.mdx +++ b/chapters/ru/chapter3/1.mdx @@ -2,7 +2,7 @@ # Введение -В [главе 2](/course/chapter2) мы увидели, как можно использовать токенизаторы и предобученные модели для построения предсказаний. Но что если мы хотим дообучить предобученную модель на собственном датасете? Это и есть тема данной главы! Мы изучим: +В [главе 2](/course/ru/chapter2) мы увидели, как можно использовать токенизаторы и предобученные модели для построения предсказаний. Но что если мы хотим дообучить предобученную модель на собственном датасете? Это и есть тема данной главы! Мы изучим: {#if fw === 'pt'} * Как подготовить большой датасет из Model Hub diff --git a/chapters/ru/chapter3/2.mdx b/chapters/ru/chapter3/2.mdx index c3932bd16..4d8ed067e 100644 --- a/chapters/ru/chapter3/2.mdx +++ b/chapters/ru/chapter3/2.mdx @@ -23,8 +23,8 @@ {/if} {#if fw === 'pt'} -Продолжим с примером из [предыдущей главы](/course/chapter2) -Continuing with the example from the [previous chapter](/course/chapter2), вот как мы будем обучать классификатор последовательности на одном батче с помощью PyTorch: +Продолжим с примером из [предыдущей главы](/course/ru/chapter2) +Continuing with the example from the [previous chapter](/course/ru/chapter2), вот как мы будем обучать классификатор последовательности на одном батче с помощью PyTorch: ```python import torch @@ -49,7 +49,7 @@ loss.backward() optimizer.step() ``` {:else} -Continuing with the example from the [previous chapter](/course/chapter2), вот как мы будем обучать классификатор последовательности на одном батче с помощью TensorFlow: +Continuing with the example from the [previous chapter](/course/ru/chapter2), вот как мы будем обучать классификатор последовательности на одном батче с помощью TensorFlow: ```python import tensorflow as tf @@ -160,7 +160,7 @@ raw_train_dataset.features {/if} -Чтобы предобработать датасет, нам необходимо конвертировать текст в числа, которые может обработать модель. Как вы видели в [предыдущей главе](/course/chapter2), это делается с помощью токенайзера. Мы можем подать на вход токенайзеру одно или список предложений, т.е. можно токенизировать предложения попарно таким образом: +Чтобы предобработать датасет, нам необходимо конвертировать текст в числа, которые может обработать модель. Как вы видели в [предыдущей главе](/course/ru/chapter2), это делается с помощью токенайзера. Мы можем подать на вход токенайзеру одно или список предложений, т.е. можно токенизировать предложения попарно таким образом: ```py from transformers import AutoTokenizer @@ -186,7 +186,7 @@ inputs } ``` -Мы уже обсуждали ключи `input_ids` и `attention_mask` в [главе 2](/course/chapter2), но не упоминали о `token_type_ids`. В этом примере мы указываем модели какая часть входных данных является первым предложением, а какая вторым. +Мы уже обсуждали ключи `input_ids` и `attention_mask` в [главе 2](/course/ru/chapter2), но не упоминали о `token_type_ids`. В этом примере мы указываем модели какая часть входных данных является первым предложением, а какая вторым. @@ -217,13 +217,13 @@ tokenizer.convert_ids_to_tokens(inputs["input_ids"]) Обратите внимание, что если вы выберете другой чекпоинт, `token_type_ids` необязательно будут присутствовать в ваших токенизированных входных данных (например, они не возвращаются, если вы используете модель DistilBERT). Они возвращаются только тогда, когда модель будет знать, что с ними делать, потому что она видела их во время предобучения. -В данном случае BERT был обучен с информацией о идентификаторах типов токенов, и помимо задачи маскированной языковой модели, о которой мы говорили в [главе 1](/course/chapter1), он может решать еще одну задачу: предсказание следующего предложения (_next sentence prediction_). Суть этой задачи - смоделировать связь между предложениями. +В данном случае BERT был обучен с информацией о идентификаторах типов токенов, и помимо задачи маскированной языковой модели, о которой мы говорили в [главе 1](/course/ru/chapter1), он может решать еще одну задачу: предсказание следующего предложения (_next sentence prediction_). Суть этой задачи - смоделировать связь между предложениями. В этой задаче модели на вход подаются пары предложений (со случайно замаскированными токенами), от модели требуется предсказать, является ли следующее предложение продолжением текущего. Чтобы задача не была слишком тривиальной, половина времени модель обучается на соседних предложениях из одного документа, другую половину на парах предложений, взятых из разных источников. В общем случае вам не нужно беспокоиться о наличии `token_type_ids` в ваших токенизированных данных: пока вы используете одинаковый чекпоинт и для токенизатора, и для модели – токенизатор будет знать, как нужно обработать данные. -Теперь мы знаем, что токенизатор может подготовить сразу пару предложений, а значит мы можем использовать его для целого датасета: так же как и в [предыдущей главе](/course/chapter2) можно подать на вход токенизатору список первых предложений и список вторых предложений. Это также сработает и для механизмов дополнения (padding) и усечения до максимальной длины (truncation) - об этом мы говорили в [главе 2](/course/chapter2). Итак, один из способов предобработать обучающий датасет такой: +Теперь мы знаем, что токенизатор может подготовить сразу пару предложений, а значит мы можем использовать его для целого датасета: так же как и в [предыдущей главе](/course/ru/chapter2) можно подать на вход токенизатору список первых предложений и список вторых предложений. Это также сработает и для механизмов дополнения (padding) и усечения до максимальной длины (truncation) - об этом мы говорили в [главе 2](/course/chapter2). Итак, один из способов предобработать обучающий датасет такой: ```py tokenized_dataset = tokenizer( diff --git a/chapters/ru/chapter3/3_tf.mdx b/chapters/ru/chapter3/3_tf.mdx new file mode 100644 index 000000000..01f73e339 --- /dev/null +++ b/chapters/ru/chapter3/3_tf.mdx @@ -0,0 +1,191 @@ + + +# Fine-tuning модели с использованием Keras + + + +После того, как вы выполнили всю работу по предварительной обработке данных в последнем разделе, у вас осталось всего несколько шагов для обучения модели. Обратите внимание, однако, что команда `model.fit()` будет работать очень медленно на CPU. Если у вас нет настроенного графического процессора, вы можете получить доступ к бесплатным графическим процессорам или TPU на[Google Colab](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, +) +``` + +### Обучение + +Модели TensorFlow, импортированные из 🤗 Transformers, уже являются моделями Keras. Вот краткое введение в Keras. + + + +Это означает, что когда у нас есть данные, остается совсем немного до начала обучения. + + + +Как и в [предыдущей главе](/course/ru/chapter2), мы будем использовать класс `TFAutoModelForSequenceClassification` с двумя метками: + +```py +from transformers import TFAutoModelForSequenceClassification + +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +Вы заметите, что в отличие от [Главы 2](/course/ru/chapter2), вы получаете предупреждение после создания экземпляра этой предварительно обученной модели. Это связано с тем, что BERT не был предварительно обучен классификации пар предложений, поэтому последний слой предварительно обученной модели был отброшен, а вместо него был вставлен новый слой, подходящий для классификации последовательностей. Предупреждения указывают на то, что некоторые веса не использовались (те, которые соответствуют удаленным слоям), а некоторые другие были инициализированы случайным образом (те, что для новых слоев). В заключение предлагается обучить модель, что мы и собираемся сделать сейчас. + +Чтобы точно настроить модель в нашем наборе данных, нам просто нужно вызвать `compile()` у нашей модели, а затем передать наши данные в метод `fit()`. Это запустит процесс fine tuning (который должен занять пару минут на графическом процессоре) и сообщит о значениях функции потерь при обучении, а также о значениях функции потерь на валидации. + + + +Обратите внимание, что у моделей 🤗 Transformers есть особая способность, которой нет у большинства моделей Keras — они могут автоматически использовать соответствующие функции потерь. Они будут использовать эти потерю по умолчанию, если вы не установите аргумент `loss` в `compile()`. Обратите внимание, что для использования внутренней функции вам нужно будет передать свои метки классов как часть обучающих данных, а не как отдельную метку, что является обычным способом использования меток с моделями Keras. Вы увидите примеры этого во второй части курса, где определение правильной функции потерь может быть сложным. Однако для классификации последовательностей стандартная функция потерь Keras отлично работает, поэтому мы будем использовать ее здесь. + + + +```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, +) +``` + + + +Обратите внимание на очень распространенную ошибку — в Keras функцию потерь можно задать просто текстовым значением, но по умолчанию Keras будет считать, что вы уже применили softmax к своим выходам. Однако многие модели выводят значения непосредственно перед применением softmax, так называемые *логиты*. Нам нужно указать это в функции потерь, а единственный способ сделать это — вызвать ее напрямую, а не по имени в виде строки. + + + + +### Повышение производительности обучения + + + +Если вы запустите приведенный выше код, он, конечно, заработает, но вы обнаружите, что потери снижаются медленно или спорадически. Основная причина это *скорость обучения* (*learning rate*). Как и в случае потери, когда мы передаем Keras имя оптимизатора в виде строки, Keras инициализирует этот оптимизатор со значениями по умолчанию для всех параметров, включая скорость обучения. Однако из многолетнего опыта мы знаем, что модели-трансформеры выигрывают от гораздо более низкой скорости обучения, чем по умолчанию для Adam - 1e-3 (1e-3 = 0.001). Значение 5e-5 (0.00005) примерно в двадцать раз ниже, и это гораздо более эффективное изначальное значение. + +В дополнение к снижению скорости обучения у нас есть еще одна хитрость: мы можем медленно снижать скорость обучения процессе обучения. В литературе вы иногда можете встретить термин «убывание» или «отжиг» скорости обучения. В Keras лучший способ сделать это — использовать планировщик скорости обучения. Хороший вариант для использования `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) +``` + + + +В библиотеке 🤗 Transformers также есть функция `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) +``` + + + +💡 Если вы хотите автоматически загружать свою модель на Hub во время обучения, вы можете передать `PushToHubCallback` в метод `model.fit()`. Мы узнаем об этом больше в [Chapter 4](/course/chapter4/3). + + + +### Применение модели для классификации + + + +Обучение и наблюдение за снижением значений функции потерь — это очень хорошо, но что, если мы действительно хотим получить результаты от обученной модели, либо для вычисления некоторых показателей, либо для использования модели в производстве? Для этого мы можем просто использовать метод `predict()`. Это вернет *логиты* из модели, по одному на класс. + +```py +preds = model.predict(tf_validation_dataset)["logits"] +``` + +Мы можем сконвертировать логиты в значение класса с помощью функции `argmax` для поиска максимального значения логита, которое соответствует наиболее правдоподобному классу. + + +```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} +``` + +Точные результаты, которые вы получите, могут отличаться, так как случайная инициализация параметров выходных слоев модели может изменить показатели. Здесь мы видим, что наша модель имеет точность 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. diff --git a/chapters/ru/chapter3/5.mdx b/chapters/ru/chapter3/5.mdx new file mode 100644 index 000000000..eb571700d --- /dev/null +++ b/chapters/ru/chapter3/5.mdx @@ -0,0 +1,21 @@ + + +# Fine-tuning, итоги! + +Это было весело! В первых двух главах вы узнали о моделях и токенизаторах, и теперь вы знаете как применить fine-tuning на собственных данных. +Напомним, в этой главе вы: + +{#if fw === 'pt'} +* Узнали о датасетах из [Hub](https://huggingface.co/datasets) +* Узнали как загрузить и предобработать данные (включая динамический padding и collator) +* Реализовали свой fine-tuning и валидировали модель +* Реализовали низко-уровневый обучающий цикл +* Использовали 🤗 Accelerate для легкой адаптации обучающего цикла к нескольким GPU или TPU + +{:else} +* Узнали о датасетах из [Hub](https://huggingface.co/datasets) +* Узнали как загрузить и предобработать данные +* Узнали как реализовать fine-tuning и валидировать модель с исползованием keras +* Реализовали собственную метрику + +{/if} diff --git a/chapters/ru/chapter3/6.mdx b/chapters/ru/chapter3/6.mdx new file mode 100644 index 000000000..b4a492a51 --- /dev/null +++ b/chapters/ru/chapter3/6.mdx @@ -0,0 +1,296 @@ + + + + +# Итоговый тест по главе + +Тест по результатам изучения главы 3. + +### Датасет `emotion` содержит сообщения из Твиттера, каждое сообщение помечено какой-либо эмоцией. Найдите его на [Hub](https://huggingface.co/datasets) и изучить карточку датасета. Какая из этих эмоцией не является базовой? + + + +### 2. Найдите датасет `ar_sarcasm` в [Hub](https://huggingface.co/datasets). Какую задачу можно решить с использованием этого датасета? + +карточку датасета!" + }, + { + text: "Named entity recognition / Распознавание именованных сущностей", + explain: "Нет, изучите еще раз карточку датасета!" + }, + { + text: "Question answering / Ответы на вопросы", + explain: "Увы! Неправильный ответ. " + } + ]} +/> + +### 3. В каком формате модель BERT ожидает на вход пару предложений? + +[SEP] – специальный токен для разделения двух предложений, однако этого недостаточно." + }, + { + text: "[CLS] Токены_предложения_1 Токены_предложения_2", + explain: "Токен [CLS] – специальный токен, обозначающий начало последовательнсти, однако этого недостаточно." + }, + { + text: "[CLS] Токены_предложения_1 [SEP] Токены_предложения_2 [SEP]", + explain: "Правильно!", + correct: true + }, + { + text: "[CLS] Токены_предложения_1 [SEP] Токены_предложения_2", + explain: "Токен [CLS] – специальный токен, обозначающий начало последовательнсти, Токен [SEP] – специальный токен для разделения двух предложений. Но это не всё!" + } + ]} +/> + +{#if fw === 'pt'} +### 4. Какие преимущества есть у метода `Dataset.map()?` + + + +### 5. Что такое dynamic padding? + + + +### 6. Какова цель функции сопоставления (collate function)? + +DataCollatorWithPadding." + }, + { + text: "Она соединяет вместе все элементы батча.", + explain: "Верно! Вы можете передать функцию сопоставления в качестве аргумента для DataLoader. Мы использовали функцию DataCollatorWithPadding, которая дополняет все элементы в батче до одинаковой длины.", + correct: true + }, + { + text: "Она обрабатывает весь датасет. ", + explain: "Тогда она называлась быть функцией препроцессинга, а не функцией сопоставления." + }, + { + text: "Она обрезает предложения в датасете.", + explain: "Collate-функция используется для одного батча, а не всего датасета. Если вам необходимо обрезать датасет, вы можете использовать аргумент truncate в tokenizer." + } + ]} +/> + +### 7. Что происходит, когда вы создаете экземпляр одного из классов `AutoModelForXxx` с предварительно обученной языковой моделью (например, `bert-base-uncased`), которая соответствует задаче, отличной от той, для которой она была обучена? + +AutoModelForSequenceClassification с bert-base-uncased чекпоинтом, распечатывается предупреждение при инициализации модели. Предобученная «голова» модели не используется для классификации предложений, так что она заменяется другим слоем со случайно инициализированными весами.", + correct: true + }, + { + text: "Последний слой модели игнорируется.", + explain: "Должно произойти что-то еще! Попробуй еще раз!" + }, + { + text: "Ничего, модель по-прежнему можно будет настроить на решение другой задачи.", + explain: "Последний слой модели был обучен решать другую задачу, значит с ним должно что-то произойти!" + } + ]} +/> + +### 8. Зачем нужен `TrainingArguments`? + +Trainer", + explain: "Верно!", + correct: true + }, + { + text: "Задает размер модели.", + explain: "Размер модели определяется ее структурой, а не классом TrainingArguments." + }, + { + text: "Содержит гиперпараметры для этапа валидации модели.", + explain: "В примере мы задавали, где будут сохраняться модель и её веса. Попробуй еще раз!" + }, + { + text: "Он содержит гиперпараметры этапа обучения.", + explain: "В примере мы использовали evaluation_strategy, что также влияет на валидацию. Попробуй еще раз!" + } + ]} +/> + +### 9. Зачем нужна библиотека 🤗 Accelerate? + +Trainer, а не 🤗 Accelerate. Попробуй еще раз!" + }, + { + text: "Позволяет исполнить наш цикл обучения на распределенных системах.", + explain: "Праивльно! С помощью 🤗 Accelerate обучающий цикл будет исполняться на нескольких GPU или TPU.", + correct: true + }, + { + text: "Предоставляет больше оптимизационных функций.", + explain: "Нет, 🤗 Accelerate не предоставляет оптимизационных функций." + } + ]} +/> + +{:else} +### 4. Что происходит, когда вы создаете экземпляр одного из классов `TFAutoModelForXxx` с предварительно обученной языковой моделью (например, `bert-base-uncased`), которая соответствует задаче, отличной от той, для которой она была обучена? + +AutoModelForSequenceClassification с bert-base-uncased чекпоинтом, распечатывается предупреждение при инициализации модели. Предобученная «голова» модели не используется для классификации предложений, так что она заменяется другим слоем со случайно инициализированными весами.", + correct: true + }, + { + text: "Последний слой модели игнорируется.", + explain: "Должно произойти что-то еще! Попробуй еще раз!" + }, + { + text: "Ничего, модель по-прежнему можно будет настроить на решение другой задачи.", + explain: "Последний слой модели был обучен решать другую задачу, значит с ним должно что-то произойти!" + } + ]} +/> + +### 5. TensorFlow-модели из `transformers` уже можно рассматривать как Keras-модели. Какие преимущества это дает? + +TPUStrategy (включая инициализацию модели)." + }, + { + text: "Вы сможете испольовать существующие методы, такие как compile(), fit() и predict().", + explain: "Верно! Данные у вас уже есть, дело осталось за малым – обучить модель. ", + correct: true + }, + { + text: "Вы сможете изучить и Keras, и transformers.", + explain: "Верно! Но ответ все же немного другой :)", + correct: true + }, + { + text: "Вы можете просто вычислить метрики, связанные с датасетом.", + explain: "Keras помогает в обучении и валидации модели, а не с вычислением метрик." + } + ]} +/> + +### 6. Как мы можем задать собственную метрику? + +tf.keras.metrics.Metric.", + explain: "Великолепно!", + correct: true + }, + { + text: "С использованием функционального API Keras.", + explain: "Try again!" + }, + { + text: "С использованием вызываемого модуля metric_fn(y_true, y_pred).", + explain: "Верно!", + correct: true + }, + { + text: "Загуглив её!", + explain: "Это не тот ответ, который мы ожидаем, однако это должно помочь вам!", + correct: true + } + ]} +/> + +{/if} diff --git a/chapters/ru/chapter4/1.mdx b/chapters/ru/chapter4/1.mdx new file mode 100644 index 000000000..8a5829bce --- /dev/null +++ b/chapters/ru/chapter4/1.mdx @@ -0,0 +1,17 @@ +# Hugging Face Hub + +[Hugging Face Hub](https://huggingface.co/) -- наш главный сайт -- центральная платформа, позволяющая всем изучить, применить и внести свой вклад в SOTA (state-of=the-art) модели и наборы данных. Здесь хранится множество разнообразных моделей, среди которых больше 10000 доступны для использования. В этой главе мы сконцентрируемся на моделях, а в главе 5 обратим внимание на датасеты. + +Модели из Hugging Face Hub - это не только трансформеры или модели из области NLP. Здесь присутствуют модели [Flair](https://github.com/flairNLP/flair) и [AllenNLP](https://github.com/allenai/allennlp) для NLP, речевые модели [Asteroid](https://github.com/asteroid-team/asteroid) и [pyannote](https://github.com/pyannote/pyannote-audio), [timm](https://github.com/rwightman/pytorch-image-models) для компьютерного зрения. + +Каждая из этих моделей размещается в виде git-репозитория, что позволяет управлять версиями и воспроизводить их. Совместное использование модели в Hub означает ее доступность для сообщества и предоставление доступа к ней всем, кто хочет легко ее использовать, что, в свою очередь, избавляет их от необходимости обучать модель самостоятельно. + +Кроме того, совместное использование модели в Hub автоматически развертывает размещенный API применения для этой модели. Любой участник сообщества может протестировать его прямо на странице модели с пользовательскими входными данными и соответствующими виджетами. + +Самое приятное то, что делиться и использовать любую общедоступную модель в Hub можно совершенно бесплатно! [Платные варианты] (https://huggingface.co/pricing) также существуют, если вы хотите поделиться моделями в частном порядке. + +В видео ниже показано, как сориентироваться на Hub. + + + +Чтобы завершить изучение этой главы, необходимо иметь учетную запись Huggingface.co, так как мы будем создавать и управлять репозиториями в Hugging Face Hub: [создать учетную запись](https://huggingface.co/join) \ No newline at end of file diff --git a/chapters/ru/chapter4/2.mdx b/chapters/ru/chapter4/2.mdx new file mode 100644 index 000000000..df15ba630 --- /dev/null +++ b/chapters/ru/chapter4/2.mdx @@ -0,0 +1,97 @@ + + +# Использование предобученных моделей + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Hub упрощает выбор подходящей модели, поэтому ее использование в любой задаче заключается в запуске нескольких строк кода. Давайте посмотрим, как это сделать и как внести свой вклад в сообщество. + +Допустим, мы ищем модель для французского языка, которая может выполнять заполнение пропущенных слов в предложении. + +
+Selecting the Camembert model. +
+ +Мы выберем для этой задачи чекпоинт `camembert-base`. Идентификатор `camembert-base` – все, что нам нужно, чтобы начать использовать модель! Как вы видели в предыдущих главах, мы можем инициализировать модель с использованием функции `pipeline()`: + +```py +from transformers import pipeline + +camembert_fill_mask = pipeline("fill-mask", model="camembert-base") +results = camembert_fill_mask("Le camembert est :)") +``` + +```python out +[ + {'sequence': 'Le camembert est délicieux :)', 'score': 0.49091005325317383, 'token': 7200, 'token_str': 'délicieux'}, + {'sequence': 'Le camembert est excellent :)', 'score': 0.1055697426199913, 'token': 2183, 'token_str': 'excellent'}, + {'sequence': 'Le camembert est succulent :)', 'score': 0.03453313186764717, 'token': 26202, 'token_str': 'succulent'}, + {'sequence': 'Le camembert est meilleur :)', 'score': 0.0330314114689827, 'token': 528, 'token_str': 'meilleur'}, + {'sequence': 'Le camembert est parfait :)', 'score': 0.03007650189101696, 'token': 1654, 'token_str': 'parfait'} +] +``` + +Как видите, загрузить модель в пайплайн очень просто. Единственное, на что вам нужно обратить внимание, это чтобы выбранный чекпоинт подходил для задачи, для которой он будет использоваться. Например, здесь мы загружаем чекпоинт `camembert-base` в пайплайн `fill-mask`, что совершенно нормально. Но если бы мы загрузили эту контрольную точку в пайплайн `text-classification`, результаты не имели бы никакого смысла, потому что выходной слой `camembert-base` не подходит для этой задачи! Мы рекомендуем использовать селектор задач в интерфейсе Hugging Face Hub, чтобы выбрать соответствующие чекпоинты: + +
+The task selector on the web interface. +
+ +Вы также можете инициализировать модель не через пайплайн, а путем создания экземпляра класса модели: + +{#if fw === 'pt'} +```py +from transformers import CamembertTokenizer, CamembertForMaskedLM + +tokenizer = CamembertTokenizer.from_pretrained("camembert-base") +model = CamembertForMaskedLM.from_pretrained("camembert-base") +``` + +Однако вместо этого мы рекомендуем использовать [`Auto*` классы](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes), так как они по своей конструкции не зависят от архитектуры используемой модели. В то время как предыдущий пример кода ограничивает пользователей чекпоинтами, загружаемыми в архитектуре CamemBERT, использование классов `Auto*` упрощает переключение между чекпоинтами: + +```py +from transformers import AutoTokenizer, AutoModelForMaskedLM + +tokenizer = AutoTokenizer.from_pretrained("camembert-base") +model = AutoModelForMaskedLM.from_pretrained("camembert-base") +``` +{:else} +```py +from transformers import CamembertTokenizer, TFCamembertForMaskedLM + +tokenizer = CamembertTokenizer.from_pretrained("camembert-base") +model = TFCamembertForMaskedLM.from_pretrained("camembert-base") +``` + +Однако вместо этого мы рекомендуем использовать [`TFAuto*` classes](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes) так как они по своей конструкции не зависят от архитектуры используемой модели. В то время как предыдущий пример кода ограничивает пользователей чекпоинтами, загружаемыми в архитектуре CamemBERT, использование классов `TFAuto*` упрощает переключение между чекпоинтами: + +```py +from transformers import AutoTokenizer, TFAutoModelForMaskedLM + +tokenizer = AutoTokenizer.from_pretrained("camembert-base") +model = TFAutoModelForMaskedLM.from_pretrained("camembert-base") +``` +{/if} + + + +При использовании предварительно обученной модели обязательно проверьте: как она была обучена, на каких наборах данных, ее ограничениях и смещениях. Вся эта информация должна быть указана в карточке модели. + diff --git a/chapters/th/_toctree.yml b/chapters/th/_toctree.yml index 7f7b0c13b..d1f0a8f45 100644 --- a/chapters/th/_toctree.yml +++ b/chapters/th/_toctree.yml @@ -51,6 +51,18 @@ sections: - local: chapter3/1 title: บทนำ + - local: chapter3/2 + title: การประมวลผลข้อมูล + - local: chapter3/3 + title: การ Fine-tune โมเดลด้วย Trainer API หรือ Keras + local_fw: { pt: chapter3/3, tf: chapter3/3_tf } + - local: chapter3/4 + title: การเทรนโมเดลฉบับสมบูรณ์ + - local: chapter3/5 + title: Fine-tune โมเดลสำเร็จแล้ว! + - local: chapter3/6 + title: คำถามท้ายบท + quiz: 3 - title: 4. การแบ่งปันโมเดลและ tokenizers sections: diff --git a/chapters/th/chapter3/2.mdx b/chapters/th/chapter3/2.mdx new file mode 100644 index 000000000..61efcd854 --- /dev/null +++ b/chapters/th/chapter3/2.mdx @@ -0,0 +1,381 @@ + + +# การประมวลผลข้อมูล + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} +เราจะยังคงใช้ตัวอย่างจากบทที่แล้ว [previous chapter](/course/chapter2) โค้ดข้างล่างนี้คือวิธีการเทรนโมเดลสำหรับจำแนกลำดับ (sequence classifier) โดยใช้ข้อมูล 1 batch ใน Pytorch: + +```python +import torch +from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification + +# Same as before +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "This course is amazing!", +] +batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") + +# This is new +batch["labels"] = torch.tensor([1, 1]) + +optimizer = AdamW(model.parameters()) +loss = model(**batch).loss +loss.backward() +optimizer.step() +``` +{:else} +เราจะยังคงใช้ตัวอย่างจากบทที่แล้ว [previous chapter](/course/chapter2) โค้ดข้างล่างนี้คือวิธีการเทรนโมเดลสำหรับจำแนกลำดับ (sequence classifier) โดยใช้ข้อมูล 1 batch ใน TensorFlow: + +```python +import tensorflow as tf +import numpy as np +from transformers import AutoTokenizer, TFAutoModelForSequenceClassification + +# Same as before +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "This course is amazing!", +] +batch = dict(tokenizer(sequences, padding=True, truncation=True, return_tensors="tf")) + +# This is new +model.compile(optimizer="adam", loss="sparse_categorical_crossentropy") +labels = tf.convert_to_tensor([1, 1]) +model.train_on_batch(batch, labels) +``` +{/if} + +เป็นที่แน่นอนว่า ถ้าเราเทรนโมเดลโดยใช้ข้อมูลเพียง 2 ประโยคก็คงไม่ได้ผลลัพธ์ที่ดีเท่าไรนัก ถ้าคุณต้องการผลลัพธ์ที่ดีขึ้น คุณจะต้องเตรียมชุดข้อมูล (dataset) ที่มีขนาดใหญ่ขึ้น + +ใน section นี้ เราจะใช้ชุดข้อมูล MRPC (Microsoft Research Paraphrase Corpus) มารันให้ดูเป็นตัวอย่าง ชุดข้อมูลนี้มีการนำเสนอใน [paper](https://www.aclweb.org/anthology/I05-5002.pdf) โดย William B. Dolan and Chris Brockett โดยชุดข้อมูลนี้ประกอบด้วยคู่ของประโยคจำนวน 5,801 คู่ โดยมีข้อมูล label บ่งบอกว่าประโยคแต่ละคู่เกิดจากการถอความ (paraphrase) หรือไม่ (ประโยคคู่นี้มีความหมายเดียวกันหรือไม่) เหตุผลที่เราเลือกชุดข้อมูลนี้ เนื่องจากมันเป็นชุดข้อมูลที่มีขนาดเล็ก จึงง่ายต่อการนำไปทดลองเทรนโมเดล + +### วิธีการโหลดชุดข้อมูลจาก Hub + +{#if fw === 'pt'} + +{:else} + +{/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 งาน + +ไลบรารี่ 🤗 Datasets library มีคำสั่งที่ใช้งานได้ง่ายมากในการดาวโหลดและ cache ชุดข้อมูลที่อยู่บน Hub เราสามารถดาวโหลดชุดข้อมูล MRPC ได้ดังนี้: + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("glue", "mrpc") +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 408 + }) + test: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 1725 + }) +}) +``` + +คุณจะเห็นว่า เราจะได้อ็อบเจกต์ `DatasetDict` ซึ่งเก็บข้อมูลของ training set (ชุดข้อมูลที่ใช้เทรน) validation set (ชุดข้อมูลที่ใช้ตรวจสอบ) และ test set (ชุดข้อมูลที่ใช้ทดสอบ) ซึ่งในแต่ละชุดก็ประกอบด้วยหลายคอลัมน์ (`sentence1`, `sentence2`, `label`, and `idx`) และมีตัวแปร num_rows เก็บจำนวนข้อมูลของแต่ละชุด (ใน training set มีคู่ประโยคจำนวน 3,668 คู่ ส่วนใน validation set มี 408 คู่ และใน test set มี 1,725 คู่) + +คำสั่งนี้จะดาวโหลดและเก็บ cache ของชุดข้อมูลไว้ โดยค่าเริ่มต้น (by default) จะเก็บ cache ไว้ที่ *~/.cache/huggingface/dataset* โดยใน Chapter 2 เราได้บอกวิธีไว้แล้วว่า คุณสามารถเปลี่ยนโฟลเดอร์ที่จะเก็บ cache ได้โดยการตั้งค่าตัวแปร environment ที่ชื่อ `HF_HOME` + +เราสามารถเข้าถึงข้อมูลประโยคแต่ละคู่ในอ็อบเจกต์ `raw_datasets` ของเราได้โดยการใช้ indexing แบบเดียวกับที่ใช้กับ dictionary: + +```py +raw_train_dataset = raw_datasets["train"] +raw_train_dataset[0] +``` + +```python out +{'idx': 0, + 'label': 1, + 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', + 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} +``` + +เราจะเห็นได้ว่าข้อมูล labels นั้นอยู่ในรูป integers อยู่แล้ว จึงไม่ได้ต้องทำการประมวลผลใด ๆ เพิ่มเติมกับ label ถ้าอยากรู้ว่า integer ตัวไหนตรงกับ label ตัวไหน เราสามารถเข้าไปดูได้ที่ `features` ของอ็อพเจกต์ `raw_train_dataset` ของเรา ซึ่งจะบอกชนิดของข้อมูลในแต่ละคอลัมน์: + +```py +raw_train_dataset.features +``` + +```python out +{'sentence1': Value(dtype='string', id=None), + 'sentence2': Value(dtype='string', id=None), + 'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None), + 'idx': Value(dtype='int32', id=None)} +``` + +เราจะเห็นเบื้องหลังของ `label` ว่าเป็นข้อมูลชนิด `ClassLabel` โดยข้อมูลการ mapping integers เข้ากับชื่อ label นั้นเก็บอยู่ในโฟลเดอร์ *names* โดย `0` จะตรงกับ `not_equivalent` และ `1` ตรงกับ `equivalent` + + + +✏️ **ลองเลย!** ลองดูที่ element 15 ของ training set และ element 87 ของ validation set ว่ามี label เป็นอะไร? + + + +### การประมวลผลชุดข้อมูล + +{#if fw === 'pt'} + +{:else} + +{/if} + +ในขั้นตอนการประมวลผลชุดข้อมูล เราจะต้องแปลงตัวอักษรให้กลายเป็นตัวเลข เพื่อให้โมเดลสามารถทำความเข้าใจได้ ดังที่คุณได้เห็นแล้วใน [previous chapter](/course/chapter2) ขั้นตอนการแปลงนี้สามารถทำได้โดยใช้ tokenizer โดยเราสามารถป้อนข้อมูลเข้า tokenizer เพียงแค่หนึ่งประโยค หรือจะป้อนข้อมูลเป็น list ของประโยคทั้งหมดเลยก็ได้ เราสามารถ tokenize ทั้งประโยคแรกและประโยคที่สองในแต่ละคู่ประโยคทุกคู่ได้ดังนี้: + +```py +from transformers import AutoTokenizer + +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"]) +tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"]) +``` + +อย่างไรก็ตาม การส่งเพียงข้อมูลสองลำดับ (sequences) ในลักษณะนี้เข้าไปยังไม่เพียงพอที่จะทำให้โมเดลสามารถเรียนรู้และทำนายว่าประโยคทั้งสองนี้เป็นประโยคที่เกิดจากการถอดความ (paraphrase) หรือไม่ เราจะต้องจัดการให้ประโยคทั้งสองเป็นคู่กันก่อนแล้วค่อยทำการประมวลผลให้เหมาะสม ซึ่งโชคดีมากที่ tokenizer สามารถรับข้อมูลคู่ของลำดับแล้วเตรียมข้อมูลให้อยู่ในรูปแบบที่เหมาะสมกับการป้อนเข้าโมเดล BERT ของเรา: + +```py +inputs = tokenizer("This is the first sentence.", "This is the second one.") +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] +} +``` + +เราได้อธิบายเกี่ยวกับ keys ที่ชื่อ `input_ids` และ `attention_mask` ไปแล้วใน [Chapter 2](/course/chapter2) แต่เรายังไม่ได้พูดถึง `token_type_ids` ซึ่งในตัวอย่างนี้ ตัว token_type_ids นี่เองที่เป็นตัวบอกโมเดลว่าส่วนไหนของ input ที่เป็นประโยคแรก และส่วนไหนที่เป็นประโยคที่สอง + + + +✏️ **ลองเลย!** ลองเลือก element 15 ของ training set มาลอง tokenize ประโยคทั้งสองแยกกันทีละประโยค และลอง tokenize เป็นคู่มาเทียบกันดู การ tokenize สองแบบนี้ให้ผลลัพธ์ที่ต่างกันอย่างไร? + + + +ถ้าเรา decode ข้อมูล IDs ที่อยู่ใน `input_ids` กลับไปเป็นคำ: + +```py +tokenizer.convert_ids_to_tokens(inputs["input_ids"]) +``` + +เราจะได้ผลลัพธ์: + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +``` + +เราจะเห็นได้ว่าถ้าเราจะป้อนข้อมูลเข้าไปทีละสองประโยค โมเดลจะต้องการรับข้อมูลในรูปของ `[CLS] ประโยคที่หนึ่ง [SEP] ประโยคที่สอง [SEP]` ซึ่งถ้าเราไปเรียงให้ตรงกับ `token_type_ids` เราจะได้: + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +[ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] +``` + +คุณจะเห็นได้ว่า input ในส่วนที่ตรงกับ `[CLS] ประโยคที่หนึ่ง [SEP]` จะมี token type ID มีค่าเป็น 0 ทั้งหมด ในขณะที่ input ส่วนที่เหลือซึ่งตรงกับ `ประโยคที่สอง [SEP]` จะมี token type ID มีค่าเป็น 1 ทั้งหมด + +ควรระวังไว้ว่า ถ้าคุณเลือก checkpoint อื่น ผลลัพธ์จากการ tokenize อาจจะไม่มี token_type_ids อยู่ด้วยก็ได้ (ยกตัวอย่างเช่น ถ้าคุณเลือกโมเดล DistilBERT ผลลัพธ์จากการ tokenize จะไม่มี token_type_ids) การ tokenize จะให้ token_type_ids ออกมาก็ต่อเมื่อโมเดลนั้นรู้ว่าต้องจัดการกับมันอย่างไร เพราะโมเดลเคยเห็นข้อมูลนี้มาแล้วในช่วง pretraining + +ในตัวอย่างนี้ โมเดล BERT ผ่านการ pretrain มาด้วย token type IDs แล้ว และนอกเหนือไปจากเป้าหมายในการเทรนให้โมเดลสามารถเติมคำที่ถูกปิดไว้ (masked langauge modeling objective) ที่เราได้คุยกันใน [Chapter 1](/course/chapter1) โมเดล BERT ยังมีอีกเป้าหมายหนึ่งที่เรียกว่า _next sentence prediction_ (การทำนายประโยคถัดไป) โดยมีเป้าหมายในการทำแบบจำลองความสัมพันธ์ระหว่างคู่ของประโยคต่าง ๆ + +ในการทำให้โมเดลสามารถบรรลุเป้าหมายการทำนายประโยคถัดไป ได้มีการป้อนคู่ของประโยคที่ถูกปิดไว้อย่างสุ่มจำนวนมาก (pairs of sentences with randomly masked tokens) เข้าไปในโมเดล แล้วให้โมเดลทำนายว่าประโยคที่สองเป็นประโยคที่ตามหลังประโยคแรกหรือไม่ เพื่อไม่ให้โมเดลเรียนรู้เฉพาะประโยคที่เรียงตามกันเพียงอย่างเดียว จึงมีการแบ่งข้อมูลให้ครึ่งหนึ่งของคู่ประโยคทั้งหมด เป็นประโยคที่เรียงตามกันจริง ๆ เหมือนในเอกสารต้นฉบับ และอีกครึ่งหนึ่งเป็นคู่ประโยคที่เกิดจากสองประโยคที่มาจากเอกสารคนละชิ้นกัน + +โดยทั่วไปแล้ว คุณไม่ต้องกังวลว่าจะมีข้อมูล `token_type_ids` ในผลลัพธ์จากการ toknize หรือไม่ ตราบเท่าที่คุณเลือกให้ tokenizer และโมเดลใช้ checkpoint ตัวเดียวกัน เพราะถ้า tokenizer รู้ว่าต้องป้อนข้อมูลอะไรเข้าโมเดล ก็จะไม่เกิดปัญหาใด ๆ + +ตอนนี้เราก็ได้เห็นแล้วว่า tokenizer ของเราสามารถรับข้อมูลคู่ประโยคเพียงคู่เดียวก็ได้ หรือสามารถ tokenize คู่ประโยคทั้งหมดที่มีอยู่ในชุดข้อมูลของเราเลยก็ได้: เหมือนกับที่เราทำใน [previous chapter](/course/chapter2) เราสามารถป้อนข้อมูลเป็น list ของคู่ประโยคต่าง ๆ เข้าไปใน tokenizer ได้ โดยป้อนข้อมูล list ของประโยคแรก แล้วตามด้วย list ของประโยคที่สอง และยังสามารถทำการเติมและตัด (padding and truncation) เหมือนกับที่เราทำใน [Chapter 2](/course/chapter2) ได้ เราอาจจะเขียนคำสั่งในการประมวลผลชุดข้อมูล training set ได้ดังนี้: + +```py +tokenized_dataset = tokenizer( + raw_datasets["train"]["sentence1"], + raw_datasets["train"]["sentence2"], + padding=True, + truncation=True, +) +``` + +ซึ่งการเขียนคำสั่งแบบนี้ก็ได้ผลลัพธ์ที่ถูกต้อง แต่จะมีจุดด้อยคือการทำแบบนี้จะได้ผลลัพธ์ออกมาเป็น 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 ข้อมูลของเรากันก่อน: + +```py +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) +``` + +ฟังก์ชั่นนี้จะรับ dictionary (เหมือนกับแต่ละ item ของชุดข้อมูลของเรา) และให้ผลลัพธ์เป็น dictionary ตัวใหม่ที่มี keys เป็น `input_ids`, `attention_mask` และ `token_type_ids` ควรสังเกตว่าถึงแม้ `example` dictionary จะประกอบไปด้วยข้อมูลหลายชุด (แต่ละ key เป็น list ของประโยคต่าง ๆ ) ฟังก์ชั่นนี้ก็ยังทำงานได้ เนื่องจาก `tokenizer` สามารถรับข้อมูลเป็น list ของคู่ประโยคต่าง ๆ ได้ดังที่ได้เห็นแล้วข้างต้น และการเขียนฟังก์ชั่นแบบนี้ยังทำให้เราสามารถใช้ตัวเลือก `batched=True` ตอนที่เราเรียกใช้เมธอด `map()` ได้อีกด้วย ซึ่งจะช่วยให้การ tokenize เร็วขึ้นอย่างมาก เนื่องจาก tokenizer ในไลบรารี่ [🤗 Tokenizers](https://github.com/huggingface/tokenizers) นั้นเขียนโดยใช้ภาษา Rust ซึ่งจะทำงานได้รวดเร็วมากหากคุณป้อนข้อมูลเข้าไปจำนวนมากพร้อม ๆ กัน + +ควรสังเกตว่าเรายังไม่ได้ใส่อากิวเมนต์ `padding` เข้ามาในฟังก์ชั่น tokenize ของเราตอนนี้ เนื่องจากการเติม (padding) ข้อมูลทุก ๆ ตัวอย่างให้มีความยาวเท่ากับประโยคที่มีความยาวมากสุดนั้นไม่ค่อยมีประสิทธิภาพเท่าไรนัก วิธีการที่ดีกว่าคือให้เราเติม (pad) ข้อมูลเมื่อเรากำลังสร้าง batch ขึ้นมา ซึ่งเราก็จะต้องเติมให้ข้อมูลมีความยาวเท่ากับประโยคที่ยาวที่สุดใน batch นั้น ๆ ก็พอ ไม่จำเป็นต้องเติมให้ยาวเท่ากับประโยคที่ยาวที่สุดในทั้งชุดข้อมูล การทำเช่นนี้จะช่วยประหยัดเวลาและพลังในการประมวลผลได้อย่างมาก แม้ input ของเราจะมีความยาวที่แตกต่างกันมากก็ตาม! + +ต่อไปนี้คือวิธีการใช้ฟังก์ชั่น tokenize ให้ทำงานกับข้อมูลใน dataset ทุกชุดของเราในคราวเดียว โดยเราจะใส่ `batched=True` ตอนที่ call เมธอด `map` เพื่อให้ฟังก์ชั่นทำงานกับ elements หลาย ๆ ตัวใน dataset ของเราในคราวเดียว (ไม่ได้ทำทีละ element แยกกัน) ซึ่งการทำเช่นนี้จะช่วยให้เราประมวลผลข้อมูลได้เร็วขึ้นมาก + +```py +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) +tokenized_datasets +``` + +ไลบรารี่ 🤗 Datasets จะทำการประมวลผลนี้โดยการเพิ่ม fields ใหม่เข้าไปยัง datasets ของเรา โดยเพิ่ม field ให้กับแต่ละ key ของ dictionary ที่ได้ออกมาจากฟังก์ชั่นประมวลผลของเรา + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 408 + }) + test: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 1725 + }) +}) +``` + +นอกจากนี้คุณยังสามารถใช้ multiprocessing ตอนที่คุณใช้ฟังก์ชั่น preprocess ของคุณกับ `map()` ได้โดยการใส่อากิวเมนต์ `num_proc` แต่ที่เราไม่ได้ทำให้ดูตรงนี้ เนื่องจากไลบรารี่ 🤗 Tokenizers นั้นมีการใช้ multiple threads เพื่อให้การ tokenize ตัวอย่างของเราเร็วขึ้นอยู่แล้ว แต่ถ้าคุณไม่ได้ใช้ fast tokenizer ที่เขียนไว้ในไลบรารี่นี้ การใช้ multiprocessing ก็อาจจะช่วยให้การประมวลผลชุดข้อมูลของคุณเร็วขึ้นได้ + +`tokenize_function` ของเราให้ผลลัพธ์เป็น dictionary โดยมี keys ต่าง ๆ ได้แก่ `input_ids`, `attention_mask` และ `token_type_ids` เพื่อให้ทั้งสาม field นี้ถูกเพิ่มเข้าไปใน dataset ทั้งสาม split คุณควรจำไว้ว่าเราอาจจะเปลี่ยน filed ที่มีอยู่แล้วก็ได้ ถ้าหากว่าคุณเลือกเขียนฟังก์ชั่นให้เปลี่ยนค่าใน key เดิมใน dataset ที่เราจะทำการ map และให้ฟังก์ชั่น return ค่าใหม่ออกมา + +ขั้นตอนสุดท้ายที่ต้องทำก็คือการเติมชุดข้อมูลตัวอย่างของเราให้มีความยาวเท่ากับข้อมูลตัวที่มีความยาวมากที่สุดใน batch ซึ่งเทคนิคเราจะเรียกว่า *dynamic padding* (การเติมแบบพลวัต) + +### Dynamic padding (การเติมแบบพลวัต) + + + +{#if fw === 'pt'} +ฟังก์ชั่นที่ทำหน้าที่เก็บข้อมูลตัวอย่างเข้ามาทำเป็น batch เรียกว่า *collate function* ซึ่งเป็นอากิวเมนต์ที่คุณสามารถใส่เพิ่มได้เมื่อคุณสร้าง `DataLoader` โดยการตั้งค่าเริ่มต้นจะเป็นฟังก์ชั่นที่ทำหน้าที่เพียงแปลงข้อมูลตัวอย่างของคุณให้เป็น Pytorch tensors และนำมา concatenate ต่อกัน (แบบ recursive ถ้าหากคุณป้อนข้อมูลเป็น lists, tuples หรือ dictionaries) ซึ่งในกรณีตัวอย่างของเรานี้จะทำแบบนั้นไม่ได้ เนื่องจากข้อมูลป้อนเข้าแต่ละตัวของเรามีขนาดไม่เท่ากัน ซึ่งเราก็ได้จงใจที่ยังไม่ทำการเติม (padding) มาจนถึงตอนนี้ เพื่อที่จะทำการเติมเท่าที่จำเป็นต้องทำในแต่ละ batch เพื่อหลีกเลี่ยงการเติมข้อมูลให้มีความยาวเกินจำเป็น ซึ่งการทำแบบนี้จะช่วยให้การ training เร็วขึ้นค่อนข้างมาก แต่ควรระวังไว้ว่าถ้าคุณ train บน TPU การทำแบบนี้อาจสร้างปัญหาได้ เนื่องจาก TPUs นั้นชอบข้อมูลที่มี shape คงที่มากกว่า แม้ว่าจะต้องเติมข้อมูลให้ยาวมากก็ตาม + +{:else} + +ฟังก์ชั่นที่ทำหน้าที่เก็บข้อมูลตัวอย่างเข้ามาทำเป็น batch เรียกว่า *collate function* ซึ่งมีการตั้งค่าเริ่มต้นเป็นฟังก์ชั่นที่ทำหน้าที่เพียงแปลงข้อมูลตัวอย่างของคุณให้เป็น tf.Tensor และนำมา concatenate ต่อกัน (แบบ recursive ถ้าหากคุณป้อนข้อมูลเป็น lists, tuples หรือ dictionaries) ซึ่งในกรณีตัวอย่างของเรานี้จะทำแบบนั้นไม่ได้ เนื่องจากข้อมูลป้อนเข้าแต่ละตัวของเรามีขนาดไม่เท่ากัน ซึ่งเราก็ได้จงใจที่ยังไม่ทำการเติม (padding) มาจนถึงตอนนี้ เพื่อที่จะทำการเติมเท่าที่จำเป็นต้องทำในแต่ละ batch เพื่อหลีกเลี่ยงการเติมข้อมูลให้มีความยาวเกินจำเป็น ซึ่งการทำแบบนี้จะช่วยให้การ training เร็วขึ้นค่อนข้างมาก แต่ควรระวังไว้ว่าถ้าคุณ train บน TPU การทำแบบนี้อาจสร้างปัญหาได้ เนื่องจาก TPUs นั้นชอบข้อมูลที่มี shape คงที่มากกว่า แม้ว่าจะต้องเติมข้อมูลให้ยาวมากก็ตาม + +{/if} + +ในทางปฏิบัติแล้ว เราจะต้องสร้างฟังก์ชั่น collate ที่จะทำการเติมข้อมูลในแต่ละ batch ของ dataset ด้วยจำนวนที่ถูกต้อง ซึ่งโชคดีที่ไลบรารี่ 🤗 Transformers ได้เตรียมฟังก์ชั่นนี้ไว้ให้แล้วในโมดูล `DataCollatorWithPadding` โดยจะรับข้อมูลเป็น tokenier (เพื่อให้รู้ว่าจะต้องเติมด้วย paddin token อะไร และเพื่อให้รู้ว่าโมเดลคาดหวังว่าจะต้องเติมไปทางซ้ายหรือทางขวามือของข้อมูล) และจะทำขั้นตอนทุกอย่างที่คุณต้องการ: + +{#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} + +เพื่อจะทดสอบของเล่นชิ้นใหม่นี้ ลองเลือกข้อมูลบางส่วนจากชุดข้อมูล training ของเรามาทดลองสร้างเป็น batch ซึ่งตรงนี้เราจะเอาคอลัมน์ idx, sentence1 และ sentence2 ออกไปเนื่องจากเราไม่จำเป็นต้องใช้ อีกทั้งคอลัมน์เหล่านี้ยังมี strings (ซึ่งเราไม่สามารถใช้ strings ในการสร้าง tensor ได้) แล้วลองดูความยาวของข้อมูลแต่ละตัวใน batch ของเรา: + +```py +samples = tokenized_datasets["train"][:8] +samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]} +[len(x) for x in samples["input_ids"]] +``` + +```python out +[50, 59, 47, 67, 59, 50, 62, 32] +``` + +เราเลือกได้ข้อมูลที่มีความยาวต่าง ๆ กัน ตั้งแต่ 32 ไปถึง 67 (ซึ่งก็ไม่น่าประหลาดใจอะไร) การทำ Dynamic padding ควรที่จะเติมข้อมูลทุกตัวใน batch นี้ให้มีความยาวเท่ากันเท่ากับ 67 (ซึ่งเป็นความยาวของข้อมูลที่ยาวที่สุดใน batch นี้) ถ้าไม่มีการทำ dynamic padding เราก็จะต้องเติมข้อมูลให้ยาวเท่ากับข้อมูลที่ยาวที่สุดใน dataset หรือไม่ก็เท่ากับความยาวสูงสุดที่โมเดลจะรับได้ ลองมาตรวจสอบกันดูว่า `data_collator` ของเรานั้นได้ทำการเติมแบบพลวัตให้กับข้อมูลใน batch ของเราอย่างถูกต้องเหมาะสม: + +```py +batch = data_collator(samples) +{k: v.shape for k, v in batch.items()} +``` + +{#if fw === 'tf'} + +```python out +{'attention_mask': TensorShape([8, 67]), + 'input_ids': TensorShape([8, 67]), + 'token_type_ids': TensorShape([8, 67]), + 'labels': TensorShape([8])} +``` + +{:else} + +```python out +{'attention_mask': torch.Size([8, 67]), + 'input_ids': torch.Size([8, 67]), + 'token_type_ids': torch.Size([8, 67]), + 'labels': torch.Size([8])} +``` + +ผลลัพธ์ออกมาดูดีเลย! ตอนนี้เราก็จัดการข้อมูลจาก raw text ให้เป็นชุดของ batch ที่โมเดลทำความเข้าใจได้แล้ว เราพร้อมที่จะ fine-tune แล้ว! + +{/if} + + + +✏️ **ลองเลย!** ลองทำการประมวลผลแบบนี้กับชุดข้อมูล GLUE SST-2 ดู มันจะต่างจากตัวอย่างนี้เล็กน้อย เนื่องจากชุดข้อมูลนั้นประกอบไปด้วยประโยคเดียวแทนที่จะเป็นคู่ประโยค แต่ส่วนที่เหลือก็เหมือนกัน ถ้าอยากลองความท้าทายที่ยากขึ้นไปอีก ให้ลองเขียนฟังก์ชั่นประมวลผลที่ใช้กับ GLUE tasks ได้ทุก task ดูสิ + + + +{#if fw === 'tf'} + +ตอนนี้เราก็ได้ dataset และ data collator แล้ว เราจะต้องนำมันมาต่อเข้าด้วยกัน โดยเราอาจจะโหลด batch และ collate มันแบบ manual ก็ได้ แต่นั่นเป็นงานที่หนักมากและไม่ค่อยมีประสิทธิภาพนัก เราอาจเลือกใช้เมธอด `to_tf_dataset()` ในการแก้ปัญหานี้อย่างมีประสิทธิภาพ โดยเมธอดนี้จะ wrap `tf.data.Dataset` เข้ากับ dataset ของคุณและคุณสามารถใส่ collation function เข้าไปด้วยได้ โดย `tf.data.Dataset` นั้นเป็น native TensorFlow format ซึ่ง Keras สามารถใช้ร่วมกับ `model.fit()` ได้ ดังนั้นเมธอดนี้จะแปลง 🤗 Dataset ให้เป็น format ที่พร้อมสำหรับการ training แล้ว ลองมาดูการเทรนโมเดลด้วย dataset ของเรากันเลย! + +```py +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, +) +``` + +เสร็จเรียบร้อย! เราสามารถนำ datasets พวกนี้ไปใช้ในบทเรียนต่อไปของเราได้เลย โดยการ training นั้นค่อนข้างตรงไปตรงมาไม่ซับซ้อนหลังจากที่เราทำงานอย่างหนักไปกกับการประมวลผลข้อมูลแล้ว + +{/if} diff --git a/chapters/th/chapter3/3.mdx b/chapters/th/chapter3/3.mdx new file mode 100644 index 000000000..783b9325b --- /dev/null +++ b/chapters/th/chapter3/3.mdx @@ -0,0 +1,172 @@ + + +# การ Fine-tune โมเดลด้วย Trainer API + + + + + +🤗 Transformers มี `Trainer` class เพื่อช่วยให้คุณสามารถ fine-tune โมเดลที่ผ่านการเทรนมาแล้วด้วย dataset ของคุณเองได้ หลังจากที่คุณได้ทำการประมวลผลข้อมูลใน section ที่แล้ว ก็เหลืองานอีกไม่กี่ขั้นตอนเท่านั้นในการกำหนดตัว `Trainer` ซึ่งงานส่วนที่ยากที่สุดน่าจะเป็นการเตรียม environment ในการ run `Trainer.train()` เนื่องจากมันจะ run ได้ช้ามากบน CPU ถ้าคุณไม่มีการติดตั้ง GPU คุณก็สามารถเข้าถึง free GPUs หรือ TPUs ได้บน [Google Colab](https://colab.research.google.com/) + +โค้ดตัวอย่างข้างล่างนี้สันนิษฐานไว้ว่าคุณได้ทำตัวอย่างใน section ที่แล้วมาเรียบร้อยแล้ว นี่คือการสรุปสั้น ๆ เพื่อทบทวนสิ่งที่คุณต้องเตรียมให้พร้อม: + +```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) +``` + +### การ Train โมเดล + +ขั้นตอนแรกก่อนที่เราจะกำหนด `Trainer` ของเราก็คือการกำหนด `TrainingArguments` class ที่จะมีข้อมูลของ hyperparameters ทุกตัวที่ `Trainer` จะใช้ในการ train และการ evaluate โดยอากิวเมนต์เดียวที่คุณต้องใส่คือ directory ที่จะเซฟข้อมูลโมเดลที่เทรนแล้ว รวมถึง checkpoints ระหว่างการเทรน ที่เหลือนั้นคุณสามารถปล่อยให้เป็นไปตามค่าเริ่มต้นได้ ซึ่งน่าจะทำงานได้ดีสำหรับการ fine-tune แบบพื้นฐาน + +```py +from transformers import TrainingArguments + +training_args = TrainingArguments("test-trainer") +``` + + + +💡 ถ้าคุณต้องการจะอัพโหลดโมเดลของคุณขึ้น Hub ระหว่างที่ทำการเทรนโดยอัตโนมัติ ให้ใส่ `push_to_hub=True` เข้าไปใน `TrainingArguments` ด้วย โดยเราจะเรียนรู้เพิ่มเติมใน [Chapter 4](/course/chapter4/3) + + + +ขั้นตอนที่สองคือการกำหนดโมเดลของพวกเรา เหมือนกับใน [previous chapter](/course/chapter2) เราจะใช้ `AutoModelForSequenceClassification` class โดยมี 2 labels: + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +คุณจะสังเกตได้ว่าคุณจะได้รับคำเตือนหลังจากที่สร้างโมเดลขึ้นมา ไม่เหมือนกับใน [Chapter 2](/course/chapter2) ที่เป็นเช่นนี้เนื่องจาก BERT ยังไม่ได้มีการ pretrained ให้สามารถจำแนกคู่ประโยค ดังนั้น head ของโมเดลที่เทรนไว้แล้วจะถูกตัดทิ้งไป และจะใส่ head ใหม่ที่เหมาะกับการจำแนกลำดับ (sequence classification) เข้ามาแทน คำเตือนนี้เป็นการแจ้งว่า weights บางส่วนจะไม่ถูกนำมาใช้ (weights ของ head ที่ถูกตัดทิ้งไป) และ weights บางส่วน (ของ head ใหม่) จะถูกสร้างขึ้นแบบสุ่ม (randomly initialized) และจบคำเตือนโดยการส่งเสริมให้เราเทรนโมเดล ซึ่งก็เป็นสิ่งที่เรากำลังจะทำตอนนี้ + +หลังจากที่เราได้โมเดลแล้ว เราสามารถกำหนด `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` ในการ call นี้ไปเลยก็ได้ ซึ่งคุณได้เรียนรู้กระบวนการนี้มาแล้วใน section 2! + +เพื่อจะทำการ fine-tune โมเดลด้วย dataset ของเรา เราก็แค่ต้องเรียกเมธอด `train()` จาก `Trainer` ของเรา: + +```py +trainer.train() +``` + +การรันโค้ดนี้จะเป็นการเริ่มต้น fine-tune โมเดล (ซึ่งจะใช้เวลาไม่กี่นาที ถ้าทำบน GPU) และจะรายงาน training loss ทุก ๆ 500 steps แต่มันจะไม่บอกว่าโมเดลของคุณทำงานได้ดีหรือแย่แค่ไหน เนื่องจาก: + +1. เราไม่ได้บอก `Trainer` ให้ evaluate ระหว่างการเทรนโดยตั้งค่า `evaluation_strategy` ให้เป็น `"steps"` (เพื่อ evaluate ทุก ๆ `eval_steps`) หรือ `"epoch"` (เพื่อ evaluate เมื่อจบแต่ละ epoch) +2. เราไม่ได้ใส่ฟังก์ชั่น `compute_metrics()` เข้าไปใน `Trainer` ของเรา โดยฟังก์ชั่นนี้จะคำนวณ metric เมื่อมีการ evaluate เกิดขึ้น (ไม่เช่นนั้นเมื่อมีการ evaluate เกิดขึ้น ก็จะรายงานแค่ค่า loss ซึ่งเป็นตัวเลขที่ทำความเข้าใจได้ยาก) + + +### Evaluation (การประเมินผลโมเดล) + +มาดูกันว่าเราจะสามารถสร้างฟังก์ชั่น `compute_metrics()` และนำไปใช้งานในการเทรนครั้งหน้าได้อย่างไร ฟังก์ชั่นนี้จะต้องรับออพเจกต์ `EvalPrediction` (ซึ่งเป็น named tuple ที่มี filed เป็น `predictions` และ `label_ids`) และจะให้ผลลัพธ์เป็น dictionary ที่มีการ map strings เข้ากับ floats (โดย strings เป็นชื่อของ metrics ผลลัพธ์ และ floats เป็นค่าของ metrics เหล่านั้น) เพื่อจะดูผลการทำนายของโมเดลของเรา เราสามารถใช้คำสั่ง `Trainer.predict()`: + +```py +predictions = trainer.predict(tokenized_datasets["validation"]) +print(predictions.predictions.shape, predictions.label_ids.shape) +``` + +```python out +(408, 2) (408,) +``` + +ผลลัพธ์จากเมธอด `predict()` จะเป็น named tuple อีกตัวหนึ่งซึ่งประกอบด้วย 3 fields ได้แก่ `predictions`, `label_ids` และ `metrics` โดย field `metrics` จะเก็บข้อมูล loss บน dataset ที่ป้อนเข้ามา รวมถึง metrics ที่เกี่ยวข้องกับเวลาบางตัว (ใช้เวลาในการทำนายเท่าไร โดยคิดทั้งเวลาทั้งหมดและเวลาโดยเฉลี่ย) เมื่อเราสร้างฟังก์ชั่น `compute_metrics()` เสร็จแล้วและใส่เข้าไปใน `Trainer` ตัว field metric ก็จะมีข้อมูล metrics ต่าง ๆ ที่ได้จากฟังก์ชั่น `compute_metrics()` ด้วย + +ดังที่คุณเห็น `predictions` เป็น array 2 มิติ ที่มี shape 408 x 2 (408 เป็นจำนวนของ element ใน dataset ที่เราใช้) ซึ่งข้อมูลเหล่านี้คือ logits สำหรับแต่ละ element ของ dataset ที่เราส่งเข้าไปให้เมธอด `predict()` (เหมือนที่คุณเห็นมาแล้วใน [previous chapter](/course/chapter2) ว่าโมเดล Transformers ทุกตัวจะให้ผลลัพธ์เป็น logits) เพื่อจะแปลง logits ให้เป็นการทำนายที่เราสามารถเปรียบเทียบกับ label ของเราได้ เราจะต้องหา index ของค่าที่มีค่าสูงสุดใน axis ที่สอง: + +```py +import numpy as np + +preds = np.argmax(predictions.predictions, axis=-1) +``` + +ตอนนี้เราก็สามารถเปรียบเทียบ `preds` เหล่านี้กับ labels ของเราได้แล้ว เพื่อจะสร้างฟังก์ชั่น `compute_metric()` ของเรา เราจะยืม metrics จากไลบรารี่ 🤗 Datasets มาใช้ เราสามารถโหลด metrics ที่เกี่ยวข้องกับ MRPC dataset ได้อย่างง่ายดายเหมือนกับที่เราโหลดชุดข้อมูล โดยการใช้ฟังก์ชั่น `load_metric()` โดยจะได้ผลลัพธ์เป็นออพเจ็กต์ที่มีเมธอด `compute()` ที่เราสามารถนำไปใช้ในการคำนวณ metric ได้: + +```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} +``` + +ผลลัพธ์ที่ได้อาจแตกต่างไปเล็กน้อย เนื่องจากมีการกำหนดค่า weight ของ model head ขึ้นมาแบบสุ่ม และอาจเปลี่ยนผลลัพธ์ใน metrics ได้ ซึ่งตรงนี้เราจะเห็นได้ว่าโมเดลของเราได้ accuracy ที่ 85.78% เมื่อทดสอบด้วย validation set และได้ค่า F1 score ที่ 89.97 ซึ่ง metrics ทั้งสองตัวนี้เป็น metrics ที่ใช้วัดผล MRPC dataset สำหรับ GLUE benchmark โดยตารางในรายงาน [BERT paper](https://arxiv.org/pdf/1810.04805.pdf) ได้รายงานค่า F1 score ไว้ที่ 88.9 สำหรับ base model ซึ่งเป็นโมเดล `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) +``` + +และเพื่อให้มันรายงาน metrics เมื่อจบ epoch แต่ละ epoch เราสามารกำหนด `Trainer` ตัวใหม่ โดยใช้ฟังก์ชั่น `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, +) +``` + +ควรสังเกตว่าเราได้สร้าง `TrainingArguments` ชุดใหม่ โดยกำหนนด `evaluation_strategy` ให้เป็น `"epoch"` และสร้างโมเดลขึ้นใหม่ด้วย มิฉะนั้นเราก็จะเทรนโมเดลตัวเดิมของเราต่อ เพื่อจะเริ่มการ training รอบใหม่ เราจะใช้คำสั่ง: + +``` +trainer.train() +``` + +คราวนี้มันจะรายงาน validation loss และ metrics ต่าง ๆ ทุก ๆ ครั้งที่จบแต่ละ epoch นอกจาก training loss ซึ่ง accuracy และ F1 score ที่คุณได้อาจจะต่างจากนี้ไปเล็กน้อยเนื่องจากการสุ่ม แต่มันก็ควรจะได้ค่าที่ใกล้เคียงกัน + +`Trainer` จะสามารถทำงานได้ดีกับการเทรนด้วย GPUs หรือ TPUs หลายตัวโดยไม่ต้องปรับแต่งอะไรมาก และยังมี options มากมายให้เลือกใช้ เช่น mixed-precision training (เลือกได้โดยใส่ `fp16 = True` เข้าไปใน training arguments ของคุณ) เราจะอธิบายทุกอย่างที่มันทำได้ใน Chapter 10 + +ก็เป็นอันเสร็จสิ้นวิธีการ fine-tune โดยใช้ `Trainer` API ซึ่งตัวอย่างการ fine-tune กับ NLP tasks ส่วนใหญ่ที่ใช้บ่อยจะอยู่ใน Chapter 7 แต่ตอนนี้เรามาดูการทำแบบเดียวกันนี้โดยใช้ PyTorch เพียงอย่างเดียวกัน + + + +✏️ **ลองเลย!** Fine-tune โมเดลโดยใช้ GLUE SST-2 dataset โดยใช้การประมวลผลข้อมูลแบบที่คุณทำไว้ใน section 2 + + + diff --git a/chapters/th/chapter3/3_tf.mdx b/chapters/th/chapter3/3_tf.mdx new file mode 100644 index 000000000..6e6941754 --- /dev/null +++ b/chapters/th/chapter3/3_tf.mdx @@ -0,0 +1,197 @@ + + +# การ Fine-tune โมเดลด้วย Keras + + + +หลังจากที่คุณได้ทำการประมวลผลข้อมูลใน section ที่แล้ว ก็เหลืองานอีกไม่กี่ขั้นตอนเท่านั้นในการเทรนโมเดล ควรระวังไว้ว่าคำสั่ง `model.fit()` จะรันบน CPU ได้ช้ามาก ๆ ถ้าคุณไม่มีการติดตั้ง GPU คุณสามารถเข้าถึง free GPUs หรือ TPUs ได้บน [Google Colab](https://colab.research.google.com/) + +โค้ดตัวอย่างข้างล่างนี้สันนิษฐานไว้ว่าคุณได้ทำตัวอย่างใน section ที่แล้วเรียบร้อยแล้ว นี่คือการสรุปสั้น ๆ เพื่อทบทวนสิ่งที่คุณต้องเตรียมให้พร้อม: + +```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, +) +``` + +### การ Train โมเดล + +โมเดล TensorFlow ที่ import มาจากไลบรารี่ 🤗 Transformers นั้นเป็นโมเดล Keras อยู่แล้ว นี่คือการแนะนำสั้น ๆ ให้รู้จักกับ Keras + + + +นั่นหมายความว่า เมื่อเรามีข้อมูลแล้ว ก็เหลืองานแค่เล็กน้อยที่ต้องทำก่อนจะเริ่มเทรนโมเดลด้วยข้อมูลของเรา + + + +เหมือนกับใน [previous chapter](/course/chapter2) เราจะใช้ `AutoModelForSequenceClassification` class โดยมี 2 labels: + +```py +from transformers import TFAutoModelForSequenceClassification + +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +คุณจะสังเกตได้ว่าคุณจะได้รับคำเตือนหลังจากที่สร้างโมเดลขึ้นมา ไม่เหมือนกับใน [Chapter 2](/course/chapter2) ที่เป็นเช่นนี้เนื่องจาก BERT ยังไม่ได้มีการ pretrained ให้สามารถจำแนกคู่ประโยค ดังนั้น head ของโมเดลที่เทรนไว้แล้วจะถูกตัดทิ้งไป และจะใส่ head ใหม่ที่เหมาะกับการจำแนกลำดับ (sequence classification) เข้ามาแทน คำเตือนนี้เป็นการแจ้งว่า weights บางส่วนจะไม่ถูกนำมาใช้ (weights ของ head ที่ถูกตัดทิ้งไป) และ weights บางส่วน (ของ head ใหม่) จะถูกสร้างขึ้นแบบสุ่ม (randomly initialized) และจบคำเตือนโดยการส่งเสริมให้เราเทรนโมเดล ซึ่งก็เป็นสิ่งที่เรากำลังจะทำตอนนี้ + +เพื่อจะ fine-tune โมเดลด้วย dataset ของเรา เราแค่ต้องทำการ `compile()` โมเดลของเราแล้วส่งข้อมูลเข้าโดยใช้เมธอด `fit()` ซึ่งจะเป็นการเริ่ม fine-tuning (ซึ่งจะใช้เวลาไม่กี่นาทีบน GPU) และรายงาน training loss ระหว่างการเทรน รวมถึง validation loss เมื่อจบแต่ละ epoch +To fine-tune the model on our dataset, we just have to `compile()` our model and then pass our data to the `fit()` method. This will start the fine-tuning process (which should take a couple of minutes on a GPU) and report training loss as it goes, plus the validation loss at the end of each epoch. + + + +ควรสังเกตว่าโมเดล 🤗 Transformers มีความสามารถพิเศษที่โมเดล Keras ส่วนใหญ่ไม่มี นั่นก็คือ พวกมันสามารถเลือก loss ที่เหมาะสมได้เอง โดยมันจะใช้ค่า loss นี้เป็นค่าเริ่มต้นหากคุณไม่ได้ใส่อากิวเมนต์ loss ในเมธอด `compile()` นอกจากนี้ควรระวังว่า การจะใช้ internal loss คุณจะต้องส่ง labels ของคุณเข้าไปเป็นส่วนหนึ่งของ input ด้วย ห้ามส่งแยกกัน ซึ่งเป็นวิธีการปกติที่ใช้จัดการกับ labels กับโมเดล Keras คุณจะเป็นตัวอย่างนี้ใน Part 2 ของคอร์ส ซึ่งการจะกำหนด loss function ให้ถูกต้องนั้นจะยุ่งยากเล็กน้อย อย่างไรก็ตาม สำหรับงาน sequence classification สามารถใช้ standard Keras loss function ได้โดยไม่มีปัญหา ซึ่งเราก็จะใช้แบบนั้นในตัวอย่างนี้ + + + +```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, +) +``` + + + +ให้ระวังข้อผิดพลาดที่เกิดขึ้นบ่อยตรงนี้ - คุณ *สามารถ* แค่ใส่ชื่อของ loss เป็น string เข้าไปใน Keras แต่โดยค่าเริ่มต้นแล้ว Keras จะสันนิษฐานว่าคุณได้ใช้ softmax กับ outputs ของคุณไปแล้ว อย่างไรก็ตามมีโมเดลจำนวนมากที่ให้ผลลัพธ์เป็นค่าก่อนที่จะใช้ softmax ซึ่งเรียกว่า logits เราจะต้องบอก loss function ว่าโมเดลของเราทำอะไร และวิธีการเดียวที่จะทำได้คือการ call โดยตรง ไม่ใช่การส่งชื่อที่เป็น string เข้าไป + + + + +### การปรับปรุงประสิทธิภาพในการเทรน + + + +ถ้าคุณลองโค้ดข้างต้น มันก็จะรันได้ แต่คุณจะพบว่า loss จะลดลงได้ช้ามาก หรือลดลงเพียงบางช่วง ซึ่งสาเหตุหลักมาจาก *learning rate* +ซึ่งก็เหมือนกับ loss ถ้าเราส่งชื่อของ optimizer เป็น string เข้าไป Keras จะ initialize ตัว optimizer นั้นด้วยค่าเริ่มต้นสำหรับทุก ๆ parameters รวมถึง learning rate ด้วย +แต่จากประสบการณ์ที่ยาวนาน เรารู้ว่าโมเดล transformer จะได้ประโยชน์จาก learning rate ที่มีค่าต่ำมากกว่าค่าเริ่มต้นของ Adam (ซึ่งมีค่าเริ่มต้นคือ 1e-3 หรือ 10 ยกกำลัง -3 หรือ 0.001.) +ค่า learning rate ที่ 5e-5 (0.00005) ซึ่งน้อยกว่าค่าเริ่มต้นของ Adam ถึง 20 เท่า เป็นค่าที่เหมาะกับการเริ่มต้นมากกว่า + +นอกเหนือไปจากการลด learning rate เรายังมี trick อีกอย่างหนึ่งคือ เราสามารถลด learning rate ลงอย่างช้า ๆ ได้ ซึ่งในงานวิจัยคุณมักจะเห็นการทำเช่นนี้ถูกเรียกว่า +*decaying* หรือ *annealing* the learning rate ซึ่งใน Keras วิธีการที่ดีที่สุดก็คือการใช้ *learning rate scheduler* โดยตัวที่น่าใช้คือ +`PolynomialDecay` โดยมีการตั้งค่าเริ่มต้นให้ลด learning rate ลงแบบ linearly จากค่าเริ่มต้นไปจนถึงค่าสุดท้ายเมื่อจบการเทรน ซึ่งเป็นสิ่งที่เราต้องการ +เพื่อที่จะใช้งาน scheduler ได้อย่างถูกต้อง เราจะต้องระบุว่าจะเทรนนานเท่าไร เราจะคำนวณเวลาในการเทรนเป็น `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) +``` + + + +ไลบรารี่ 🤗 Transformers ก็มีฟังก์ชั่น `create_optimizer()` ซึ่งจะสร้าง `AdamW` optimizer โดยใช้ learning rate decay ซึ่งเป็นทางลัดที่สะดวก และคุณจะได้เห็นรายละเอียดใน section ต่อ ๆ ไปในคอร์ส + + + +ตอนนี้เราก็มี optimizer ตัวใหม่เอี่ยม และสามารถนำไปลองเทรนได้เลย ขั้นแรก เรามาโหลดโมเดลขึ้นมากใหม่ เพื่อ reset weights จากการเทรนก่อนหน้านี้ จากนั้นเราก็จะ compile โมเดลโดยใช้ optimizer ตัวใหม่ + +```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"]) +``` + +ตอนนี้เราก็ fit อีกรอบได้เลย: + +```py +model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) +``` + + + +💡 ถ้าคุณต้องการจะอัพโหลดโมเดลของคุณขึ้น Hub ระหว่างที่ทำการเทรนโดยอัตโนมัติ ให้ใส่ `push_to_hub=True` เข้าไปใน `TrainingArguments` ด้วย โดยเราจะเรียนรู้เพิ่มเติมใน [Chapter 4](/course/chapter4/3) + + + +### การทำนายผลของโมเดล + + + + +การเทรน และการมองดู loss ค่อย ๆ ลดลงนั้นเยี่ยมไปเลย แต่ถ้าสิ่งที่เราต้องการจริง ๆ คือการเอาผลลัพธ์จากโมเดลไปประเมินผล หรือนำไปใช้งานจริงใน production ล่ะ? เพื่อจะทำอย่างนั้น เราก็แค่ใช้เมธอด `predict()` ก็จะได้ผลลัพธ์เป็น *logits* จาก output head ของโมเดล (หนึ่งตัวต่อหนึ่งคลาส) + +```py +preds = model.predict(tf_validation_dataset)["logits"] +``` + +เราสามารถแปลง logits เหล่านี้เป็นการทำนาย class ได้โดยการใช้ `argmax` เพื่อหา logit ที่มีค่าสูงสุด ซึ่งจะตรงกับ class ที่มีความน่าจะเป็นมากที่สุด: + +```py +class_preds = np.argmax(preds, axis=1) +print(preds.shape, class_preds.shape) +``` + +```python out +(408, 2) (408,) +``` + +ตอนนี้เรามาใช้ `preds` เพื่อคำนวณ metrics บางอย่างกันดีกว่า! เราสามารถโหลด metrics ที่เกี่ยวข้องกับ MRPC dataset ได้อย่างง่ายดายเหมือนกับที่เราโหลดชุดข้อมูล โดยการใช้ฟังก์ชั่น `load_metric()` โดยจะได้ผลลัพธ์เป็นออพเจ็กต์ที่มีเมธอด `compute()` ที่เราสามารถนำไปใช้ในการคำนวณ metric ได้: + +```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} +``` + +ผลลัพธ์ที่ได้อาจแตกต่างไปเล็กน้อย เนื่องจากมีการกำหนดค่า weight ของ model head ขึ้นมาแบบสุ่ม และอาจเปลี่ยนผลลัพธ์ใน metrics ได้ ซึ่งตรงนี้เราจะเห็นได้ว่าโมเดลของเราได้ accuracy ที่ 85.78% เมื่อทดสอบด้วย validation set และได้ค่า F1 score ที่ 89.97 ซึ่ง metrics ทั้งสองตัวนี้เป็น metrics ที่ใช้วัดผล MRPC dataset สำหรับ GLUE benchmark โดยตารางในรายงาน [BERT paper](https://arxiv.org/pdf/1810.04805.pdf) ได้รายงานค่า F1 score ไว้ที่ 88.9 สำหรับ base model ซึ่งเป็นโมเดล `uncased` ในขณะที่โมเดลของเราเป็นโมเดล `cased` จึงเป็นเหตุให้มีผลลัพธ์ที่ดีกว่า + +ก็เป็นอันเสร็จสิ้นวิธีการ fine-tune โดยใช้ `Trainer` API ซึ่งตัวอย่างการ fine-tune กับ NLP tasks ส่วนใหญ่ที่ใช้บ่อยจะอยู่ใน Chapter 7 ถ้าคุณอยากฝึกทักษะการใช้ Keras API เพิ่มเติม ให้ลอง fine-tune โมเดลโดยใช้ GLUE SST-2 dataset โดยใช้การประมวลผลข้อมูลแบบที่คุณทำไว้ใน section 2 diff --git a/chapters/th/chapter3/4.mdx b/chapters/th/chapter3/4.mdx new file mode 100644 index 000000000..c8fcec348 --- /dev/null +++ b/chapters/th/chapter3/4.mdx @@ -0,0 +1,359 @@ +# การเทรนโมเดลฉบับสมบูรณ์ + + + + + +คราวนี้เราจะมาดูกันว่าถ้าเราอยากเขียนโค้ดให้ได้ผลลัพธ์แบบเดียวกันกับใน section ที่แล้วโดยไม่ต้องเรียกใช้ `Trainer` class จะต้องทำอย่างไร เราสันนิษฐานว่าคุณได้ทำกระบวนการประมวลผลข้อมูลใน section 2 มาแล้ว โค้ดข้างล่างนี้เป็นการสรุปอย่างย่อครอบคลุมถึงกระบวนการทุกอย่างที่คุณจำเป็นต้องทำ: + +```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) +``` + +### เตรียมพร้อมก่อนเทรน + +ก่อนที่เราจะเริ่มเขียนลูปในการเทรนโมเดล เราจะต้องกำหนดออพเจ็กต์บางตัวก่อน โดยออพเจ็กต์ชุดแรกที่เราต้องกำหนดก็คือ dataloaders (ออพเจ็กต์สำหรับโหลดข้อมูล) ที่เราจะใช้ในการโหลดข้อมูล โดยการทำซ้ำกับหลาย ๆ batch ของข้อมูล แต่ก่อนที่คุณจะกำหนด dataloaders เหล่านี้ เราจะต้องทำกระบวนการ postprocessing บางอย่างกับ `tokenized_datasets` ของเราก่อน เพื่อทำกระบวนการบางอย่างที่ `Trainer` ได้จัดการให้เราโดยอัตโนมัติ ซึ่งกระบวนการเหล่านั้นได้แก่: + +- ลบคอลัมน์ที่มีข้อมูลที่โมเดลไม่ต้องการใช้ (เช่น คอลัมน์ `sentence1` และ `sentence2`) +- เปลี่ยนชื่อคอลัมน์ `label` เป็น `labels` (เพราะว่าโมเดลคาดหวังอากิวเมนต์ชื่อว่า `labels`) +- กำหนดรูปแบบของ datasets ให้ส่งผลลัพธ์ออกมาเป็น PyTorach tensors แทนที่จะเป็น lists + +`tokenized_datasets` ของเรามีเมธอดสำหรับการจัดการแต่ละขั้นตอนดังนี้: + +```py +tokenized_datasets = tokenized_datasets.remove_columns(["sentence1", "sentence2", "idx"]) +tokenized_datasets = tokenized_datasets.rename_column("label", "labels") +tokenized_datasets.set_format("torch") +tokenized_datasets["train"].column_names +``` + +จากนั้นเราก็สามารถตรวจสอบผลลัพธ์ได้ว่ามีเฉพาะคอลัมน์ที่โมเดลต้องการใช้: + +```python +["attention_mask", "input_ids", "labels", "token_type_ids"] +``` + +เมื่อเราทำขั้นตอนนี้เสร็จแล้ว เราก็สามารถกำหนด dataloaders ของเราได้อย่างง่ายดาย ดังนี้: + +```py +from torch.utils.data import DataLoader + +train_dataloader = DataLoader( + tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator +) +eval_dataloader = DataLoader( + tokenized_datasets["validation"], batch_size=8, collate_fn=data_collator +) +``` + +เพื่อจะตรวจสอบอย่างรวดเร็วว่าไม่มีข้อผิดพลาดจากการประมวลผลข้อมูล เราสามารถลองเรียกข้อมูล batch หนึ่งมาดูได้ดังนี้: + +```py +for batch in train_dataloader: + break +{k: v.shape for k, v in batch.items()} +``` + +```python out +{'attention_mask': torch.Size([8, 65]), + 'input_ids': torch.Size([8, 65]), + 'labels': torch.Size([8]), + 'token_type_ids': torch.Size([8, 65])} +``` + +ควรระวังไว้ว่า shape ที่คุณได้อาจจะแตกต่างไปจากนี้เล็กน้อย เนื่องจากเราได้กำหนดค่าให้ training dataloader มีการทำ `shuffle=True` และเราได้เติมข้อมูลให้ยาวเท่ากับข้อมูลตัวที่ยาวที่สุดใน batch + +ตอนนี้เราก็ทำกระบวนการประมวลผลข้อมูลเสร็จแล้ว (สำหรับนักปฏิบัติ ML แล้วนี่เป็นเป้าหมายที่น่าพึงพอใจทีเดียว แต่ยังไม่ได้ให้ผลลัพธ์อะไรออกมาเป็นรูปธรรมนะ) ลองกลับมาดูที่โมเดลกัน เราจะสร้างโมเดลขึ้นมาแบบเดียวกับที่เราทำใน section ที่แล้ว: + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +เพื่อให้แน่ใจว่าทุกอย่างจะทำงานได้ราบรื่นตลอดการเทรน เราจึงลองส่ง batch ของเราเข้าไปในโมเดลนี้ดู: + +```py +outputs = model(**batch) +print(outputs.loss, outputs.logits.shape) +``` + +```python out +tensor(0.5441, grad_fn=) torch.Size([8, 2]) +``` + +โมเดล 🤗 Transformers ทุกตัวจะให้ผลลัพธ์ค่า loss ออกมาด้วยถ้าหากเราใส่ข้อมูล `labels` เข้าไปด้วย และเรายังได้ผลลัพธ์ออกมาเป็น logits (ได้ออกมาเป็น 2 ค่าสำหรับแต่ละ input ใน batch ของเรา ดังนั้น logits จะเป็น tensor ที่มีขนาด 8 x 2) + +เราเกือบจะพร้อมสำหรับการเขียนลูปในการเทรนแล้ว! เราแค่ต้องการอีกสองสิ่งเท่านั้นเอง: optimizer (ตัวปรับปรุงให้การเทรนราบรื่นขึ้น) และ learning rate scheduler (ตัวกำหนดค่า learning rate ตามเวลา) เนื่องจากตอนนี้เราพยายามจะเลียนแบบสิ่งที่ `Trainer` ทำไว้ เราก็จะใช้ค่าเริ่มต้นที่เหมือนกัน โดยตัว optimizer ที่ `Trainer` ใช้คือ `AdamW` ซึ่งเป็นตัวเดียวกันกับ Adam แต่มีการพลิกแพลงในส่วนของ weight decay regularization (ดูเพิ่มเติมที่ ["Decoupled Weight Decay Regularization"](https://arxiv.org/abs/1711.05101) โดย Ilya Loshchilov and Frank Hutter): + +```py +from transformers import AdamW + +optimizer = AdamW(model.parameters(), lr=5e-5) +``` + +เราก็มาถึงขั้นตอนสุดท้าย เราจะต้องกำหนดตัว learning rate scheduler ซึ่งมีค่าเริ่มต้นให้ learningrate มีการ decay แบบเชิงเส้น โดยมีการลดค่าจากค่า learning rate ที่สูงที่สุด (5e-5) ไปเรื่อย ๆ จนมีค่าเป็น 0 เพื่อจะกำหนดค่าให้ถูกต้อง เราจะต้องรู้ว่าการเทรนครั้งนี้มีการเทรนจำนวนทั้งสิ้นกี่ step ซึ่งคำนวณได้จากจำนวน epochs ที่เราจะเทรน คูณด้วยจำนวน training batches (ซึ่งก็คือความยาวของ training dataloader ของเรา) โดยตัว `Trainer` มีค่าเริ่มต้นในการเทรนอยู่ที่ 3 epochs เราก็จะยึดตามค่านี้: + +```py +from transformers import get_scheduler + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) +print(num_training_steps) +``` + +```python out +1377 +``` + +### ลูปในการเทรน + +ข้อควรคำนึงถึงข้อสุดท้าย: เราจะต้องการใช้ GPU ถ้าหากเรามีการติดตั้งไว้ (ถ้าเราเทรนบน CPU จะต้องใช้เวลาหลายชั่วโมงแทนที่จะเป็นเวลาไม่กี่นาที) เพื่อกำหนดให้มีการใช้ GPU ทุกครั้งที่เป็นไปได้ เราสามารถกำหนด `device` ที่เราจะใส่โมเดลและ batches ของข้อมูลของเราลงไปได้ดังนี้: + +```py +import torch + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) +device +``` + +```python out +device(type='cuda') +``` + +ตอนนี้เราก็พร้อมจะเทรนโมเดลแล้ว! เพื่อให้เราพอคาดการณ์ได้ว่าการเทรนจะใช้เวลานานเท่าไร เราจึงเพิ่มแถบแสดงสถานะความคืบหน้าตามจำนวน step ในการเทรน โดยใช้ไลบรารี่ `tqdm` ดังนี้: + +```py +from tqdm.auto import tqdm + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +คุณจะสามารถเห็นได้ว่าแก่นของลูปในการเทรนนั้นก็เหมือนกับตัวที่เราแสดงให้ดูในบทนำ เรายังไม่ได้กำหนดให้มีการรายงานค่าใด ๆ ออกมา ดังนั้นลูปในการเทรนตัวนี้จึงไม่ได้บอกอะไรเราเลยว่าโมเดลมีประสิทธิภาพเป็นอย่างไร เราจึงจำเป็นต้องเขียนลูปในการประเมินผลโมเดล (evaluation loop) ด้วย + + +### ลูปในการประเมินผลโมเดล (evaluation loop) + +เหมือนกับที่เราได้ทำไว้ก่อนหน้านี้ เราสามารถเรียกใช้ metric จากไลบรารี่ 🤗 Datasets ได้เลย เราได้เห็นเมธอด `metric.compute() มาแล้ว แต่ metrics ยังสามารถรวบรวมผลมาเป็น batches ให้เราได้ด้วย โดยใช้เมธอด `add_batch()` โดยเมื่อเรารวบรวมผลมาจากทุก batches แล้ว เราก็จะคำนวณผลลัพธ์สุดท้ายได้โดยใช้เมธอด `metric.compute()` โค้ดข้างล่างนี้เป็นตัวอย่างการทำทุกอย่างที่เรากล่าวมานี้ในลูปสำหรับประเมินผลโมเดล: + +```py +from datasets import load_metric + +metric = load_metric("glue", "mrpc") +model.eval() +for batch in eval_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + with torch.no_grad(): + outputs = model(**batch) + + logits = outputs.logits + predictions = torch.argmax(logits, dim=-1) + metric.add_batch(predictions=predictions, references=batch["labels"]) + +metric.compute() +``` + +```python out +{'accuracy': 0.8431372549019608, 'f1': 0.8907849829351535} +``` + +ผลลัพธ์ที่ได้อาจแตกต่างไปเล็กน้อยเนื่องจากมีการสุ่มค่า weight ตอนสร้าง model head และมีการสลับข้อมูลแบบสุ่ม แต่ผลที่ได้ก็ควรจะใกล้เคียงกัน + + + +✏️ **ลองเลย!** แก้ไขลูปในการเทรนก่อนหน้านี้เพื่อทำการ fine-tune โมเดลของคุณด้วย SST-2 dataset. + + + +### เร่งความเร็วให้ลูปในการเทรนของคุณด้วย 🤗 Accelerate + + + +ลูปในการเทรนที่เรากำหนดขึ้นก่อนหน้านี้ทำงานได้ดีบน CPU หรือ GPU ตัวเดียว แต่การใช้ไลบรารี่ [🤗 Accelerate](https://github.com/huggingface/accelerate) และเพิ่มการปรับค่าอีกเพียงเล็กน้อย จะช่วยให้เราสามารถเทรนบน distributed setup ที่มีการใช้ GPUs หรือ TPUs หลายตัวได้ โดยเริ่มต้นจากการสร้าง training และ validation dataloaders ลูปในการเทรนแบบ manual ของเรามีลักษณะดังนี้: + +```py +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +และต่อไปนี้คือสิ่งที่ต้องปรับแก้: + +```diff ++ from accelerate import Accelerator + from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + ++ accelerator = Accelerator() + + model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) + optimizer = AdamW(model.parameters(), lr=3e-5) + +- device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +- model.to(device) + ++ train_dataloader, eval_dataloader, model, optimizer = accelerator.prepare( ++ train_dataloader, eval_dataloader, model, optimizer ++ ) + + num_epochs = 3 + num_training_steps = num_epochs * len(train_dataloader) + lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps + ) + + progress_bar = tqdm(range(num_training_steps)) + + model.train() + for epoch in range(num_epochs): + for batch in train_dataloader: +- batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss +- loss.backward() ++ accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +โค้ดบรรทัดแรกที่ต้องเพิ่มเข้ามาเป็นส่วนของการ import โดยบรรทัดที่สองเป็นการสร้างออพเจ็กต์ `Accelerator` ที่จะตรวจสอบ environment ของคุณและสร้าง distributed setup ที่เหมาะสมขึ้นมาให้ โดย 🤗 Accelerate จะช่วยจัดการ device ให้คุณ คุณจึงสามารถเอาโค้ดส่วนที่คุณใส่โมเดลเข้าไปใน device ออกได้ (หรือถ้าคุณอยากคงไว้ ก็เปลี่ยนจาก `device` เป็น `accelerator.device`) + +จากนัั้นก็มีการทำงานหลัก ๆ ในบรรทัดที่ส่ง dataloaders, โมเดล และ optimizer เข้าไปที่ `accelerator.prepare()` ซึ่งเป็นการ wrap ออพเจ็กต์เหล่านี้ให้อยู่ใน container ที่เหมาะสม และทำให้แน่ใจว่า distributed training ของคุณทำงานได้ตามที่ตั้งใจไว้ การเปลี่ยนแปลงส่วนที่เหลือคือการเอาโค้ดส่วนที่คุณใส่ batch เข้าไปใน `device` ออก (และอีกครั้ง ถ้าคุณอยากคงไว้ ก็เปลี่ยนจาก `device` เป็น `accelerator.device` และแก้จาก `loss.backward()` เป็น `accelerator.backward(loss)`) + + +⚠️ เพื่อที่จะได้ประโยชน์จากความเร็วที่เพิ่มขึ้นจากการใช้ Cloud TPUs เราแนะนำให้คุณเติมข้อมูลของคุณด้วยความยาวที่คงที่โดยการกำหนดอากิวเมนต์ `padding="max_length"` และ `max_length` ให้กับ tokenizer + + +ถ้าคุณอยากคัดลองและวางโค้ดเพื่อทดลองดู โค้ดข้างล่างนี้คือตัวอย่างของลูปในการเทรนโดยใช้ 🤗 Accelerate แบบสมบูรณ์: + +```py +from accelerate import Accelerator +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +accelerator = Accelerator() + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +train_dl, eval_dl, model, optimizer = accelerator.prepare( + train_dataloader, eval_dataloader, model, optimizer +) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dl) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dl: + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +การใส่โค้ดข้างบนนี้เข้าไปในสคริปต์ `train.py` จะทำให้สคริปต์ของคุณรันได้ไม่ว่าจะมี distributed setup เป็นแบบใดก็ตาม เพื่อจะลองบน distributed setup ของคุณ ให้รันคำสั่งนี้: + +```bash +accelerate config +``` + +ซึ่งจะให้คุณตอบคำถาม 2-3 ข้อ และใส่คำตอบของคุณลงไปในไฟล์ configuration ที่ใช้ในคำสั่งนี้: + +``` +accelerate launch train.py +``` + +ซึ่งจะเริ่มการเทรนโมเดลแบบ distributed + +ถ้าคุณอยากลองโค้ดนี้บน Notebook (เช่น ทดลองกับ TPUs บน Colab) แค่วางโค้ดนี้ลงไปใน `training_function()` และรัน cell สุดท้ายด้วยโค้ดนี้: + +```python +from accelerate import notebook_launcher + +notebook_launcher(training_function) +``` + +คุณสามารถศึกษาจากตัวอย่างอื่น ๆ เพิ่มเติม ได้ใน [🤗 Accelerate repo](https://github.com/huggingface/accelerate/tree/main/examples) diff --git a/chapters/th/chapter3/5.mdx b/chapters/th/chapter3/5.mdx new file mode 100644 index 000000000..60d9a9fe8 --- /dev/null +++ b/chapters/th/chapter3/5.mdx @@ -0,0 +1,20 @@ + + +# Fine-tune โมเดลสำเร็จแล้ว! + +สนุกจังเลย! ในสองบทแรกคุณได้เรียนรู้เกี่ยวกับโมเดลและ tokenizers และตอนนี้คุณก็รู้วิธีการ fine-tune โมเดลด้วยข้อมูลของคุณเองแล้ว มาทบทวนกันว่าคุณได้ทำอะไรไปบ้างในบทนี้: + +{#if fw === 'pt'} +* เรียนรู้การใช้งาน datasets ผ่าน [Hub](https://huggingface.co/datasets) +* เรียนรู้วิธีการโหลดและประมวลผล datasets รวมถึงการใช้งาน dynamic padding และ collators +* เขียนโค้ดการ fine-tuning และการประเมินประสิทธิภาพของโมเดลในแบบของคุณเอง +* เขียนโค้ดลูปการเทรนโดยไม่ใช้ class Trainer +* ใช้ไลบรารี่ 🤗 Accelerate เพื่อปรับลูปการเทรนของคุณให้ใช้การได้กับการเทรนโดยใช้ GPUs หรือ TPUs หลายตัวได้อย่างง่ายดาย + +{:else} +* เรียนรู้การใช้งาน datasets ผ่าน [Hub](https://huggingface.co/datasets) +* เรียนรู้วิธีการโหลดและประมวลผล datasets +* เรียนรู้วิธีการใช้ Keras ในการ fine-tune และประเมินประสิทธิภาพของโมเดล +* เขียนโค้ดสร้าง metric ในแบบของคุณเอง + +{/if} diff --git a/chapters/th/chapter3/6.mdx b/chapters/th/chapter3/6.mdx new file mode 100644 index 000000000..521ac7f5a --- /dev/null +++ b/chapters/th/chapter3/6.mdx @@ -0,0 +1,296 @@ + + + + +# คำถามท้ายบท + +ทดสอบความรู้ที่คุณได้เรียนมาจากบทนี้กัน! + +### 1. ใน `emotion` dataset ซึ่งได้รวบรวมข้อความ Twitter ที่มีการ labeled ว่าแต่ละข้อความนั้นเป็นข้อความที่มีอารมณ์แบบใด ลองค้นข้อมูลดูจาก [Hub](https://huggingface.co/datasets)และอ่าน dataset card ดูแล้วตอบว่า ข้อใดไม่ใช่หนึ่งในอารมณ์พื้นฐานของ dataset นี้? + + + +### 2. ลองหาข้อมูล `ar_sarcasm` dataset ใน [Hub](https://huggingface.co/datasets) ดูว่ามันสามารถทำ Task อะไรได้บ้าง? + +dataset card!" + }, + { + text: "Named entity recognition (การจำแนกหน่วยย่อยของประโยค)", + explain: "ยังไม่ถูกนะ — ลองดูข้อมูลใหม่อีกครั้งที่ dataset card!" + }, + { + text: "Question answering (การตอบคำถาม)", + explain: "ตอบคำถามได้ดีแต่ยังไม่ถูกนะ ลองใหม่อีกครั้ง!" + } + ]} +/> + +### 3. โมเดล BERT ต้องการข้อมูลนำเข้า เป็นคู่ประโยคในลักษณะใด? + +[SEP] เพื่อแยกระหว่างคู่ประโยคด้วย แต่แค่นี้ยังไม่ครบถ้วนนะ!" + }, + { + text: "[CLS] Tokens_of_sentence_1 Tokens_of_sentence_2", + explain: "คุณต้องใส่ Token พิเศษชื่อ [CLS] ไว้ที่ต้นประโยคแรกด้วย แต่แค่นี้ยังไม่ครบถ้วนนะ!" + }, + { + text: "[CLS] Tokens_of_sentence_1 [SEP] Tokens_of_sentence_2 [SEP]", + explain: "ถูกต้อง!", + correct: true + }, + { + text: "[CLS] Tokens_of_sentence_1 [SEP] Tokens_of_sentence_2", + explain: "คุณต้องใส่ Token พิเศษชื่อ [CLS] ไว้ที่ต้นประโยคแรก รวมถึง Token พิเศษชื่อ [SEP] เพื่อแยกระหว่างคู่ประโยค แต่แค่นี้ยังไม่ครบถ้วนนะ!" + } + ]} +/> + +{#if fw === 'pt'} +### 4. ข้อใดเป็นประโยชน์ที่ได้จากการใช้เมธอด `Dataset.map()`? + + + +### 5. dynamic padding หมายถึงอะไร? + + + +### 6. ข้อใดคือหน้าที่ของฟังก์ชั่น collate? + +DataCollatorWithPadding" + }, + { + text: "เพื่อเก็บข้อมูลเข้ามาทำเป็น batch อย่างเหมาะสม", + explain: "ถูกต้อง! คุณสามารถใส่ฟังก์ชั่น collate เป็นอากิวเมนต์ของ DataLoader เราได้ใช้ฟังก์ชั่น DataCollatorWithPadding ในการเติมข้อมูลทุกตัวใน batch ให้มีความยาวเท่ากัน", + correct: true + }, + { + text: "เพื่อประมวลผลข้อมูลทั้ง dataset.", + explain: "นั่นเป็นหน้าที่ของฟังก์ชั่นประมวลผล (preprocessing) ไม่ใช่หน้าที่ของฟังก์ชั่น collate" + }, + { + text: "เพื่อตัด sequences ทุกตัวใน dataset.", + explain: "ฟังก์ชั่น collate นั้นมีการจัดการเพียงในแต่ละ batch ไม่ได้จัดการทั้ง dataset และถ้าคุณต้องการตัด (truncating) คุณสามารถใช้อากิวเมนต์ truncate ของ tokenizer." + } + ]} +/> + +### 7. จะเกิดอะไรขึ้นถ้าคุณสร้างออพเจ็กต์ของคลาส `AutoModelForXxx` ตัวหนึ่งซึ่งมี pretrained language model (เช่น `bert-base-uncased`) เพื่อนำไปทำ task ที่แตกต่างไปจาก task ที่เคยเทรนไว้? + +AutoModelForSequenceClassification กับ bert-base-uncased เราจะได้ข้อความ warnings เพราะ pretrained head นั้นไม่ใช้ในการทำ sequence classification มันจึงถูกตัดทิ้งและมีการสร้าง head ใหม่ขึ้นมาแทน โดยกำหนดค่า weights ขึ้นแบบสุ่ม", + correct: true + }, + { + text: "head ของ pretrained model จะถูกตัดทิ้งไป", + explain: "ยังมีอีกสิ่งหนึ่งที่จะต้องเกิดขึ้นด้วย ลองใหม่อีกครั้งนะ!" + }, + { + text: "ไม่มีอะไรเกิดขึ้น เนื่องจากเราสามารถ fine tune โมเดลให้ทำ task ที่ต่างออกไปได้", + explain: "head ของ pretrained model ไม่ได้ถูกเทรนมาให้ทำ task นี้ เราจึงต้องตัดมันทิ้งไป!" + } + ]} +/> + +### 8. ข้อใดคือหน้าที่ของ `TrainingArguments`? + +Trainer", + explain: "ถูกต้อง!", + correct: true + }, + { + text: "เพื่อกำหนดขนาดของโมเดล", + explain: "ขนาดของโมเดลนั้นจะถูกกำหนดโดย model configuration ไม่ใช่ TrainingArguments" + }, + { + text: "เพื่อเก็บ hyperparameters ที่ใช้ในการประเมินผลโมเดล", + explain: "ในตัวอย่างเราได้กำหนดว่าจะเก็บโมเดลและ checkpoints ไว้ที่ไหนด้วย ลองใหม่อีกครั้ง!" + }, + { + text: "เพื่อเก็บ hyperparameters ที่ใช้ในการเทรนโมเดล", + explain: "ในตัวอย่างเราได้ใช้ evaluation_strategy ด้วย มันจึงส่งผลต่อการประเมินผลโมเดลด้วย ลองใหม่อีกครั้ง!" + } + ]} +/> + +### 9. ทำไมคุณจึงควรใช้ไลบรารี่ 🤗 Accelerate? + +Trainer ไม่ใช่ไลบรารี่ 🤗 Accelerate ลองใหม่อีกครั้ง!" + }, + { + text: "ช่วยให้ลูปในการเทรนของคุณใช้การได้กับ distributed setup", + explain: "ถูกต้อง! การใช้ 🤗 Accelerate จะช่วยให้ลูปในการเทรนของคุณใช้การได้เมื่อต้องเทรนด้วย GPUs หรือ TPUs หลาย ๆ ตัว", + correct: true + }, + { + text: "มันมีฟังก์ชั่น optimization ให้เลือกใช้มากกว่า", + explain: "ไม่ใช่นะ ไลบรารี่ 🤗 Accelerate ไม่มีฟังก์ชั่น optimization เลย" + } + ]} +/> + +{:else} +### 4. จะเกิดอะไรขึ้นถ้าคุณสร้างออพเจ็กต์ของคลาส `AutoModelForXxx` ตัวหนึ่งซึ่งมี pretrained language model (เช่น `bert-base-uncased`) เพื่อนำไปทำ task ที่แตกต่างไปจาก task ที่เคยเทรนไว้? + +AutoModelForSequenceClassification กับ bert-base-uncased เราจะได้ข้อความ warnings เพราะ pretrained head นั้นไม่ใช้ในการทำ sequence classification มันจึงถูกตัดทิ้งและมีการสร้าง head ใหม่ขึ้นมาแทน โดยกำหนดค่า weights ขึ้นแบบสุ่ม", + correct: true + }, + { + text: "head ของ pretrained model จะถูกตัดทิ้งไป", + explain: "ยังมีอีกสิ่งหนึ่งที่จะต้องเกิดขึ้นด้วย ลองใหม่อีกครั้งนะ!" + }, + { + text: "ไม่มีอะไรเกิดขึ้น เนื่องจากเราสามารถ fine tune โมเดลให้ทำ task ที่ต่างออกไปได้", + explain: "head ของ pretrained model ไม่ได้ถูกเทรนมาให้ทำ task นี้ เราจึงต้องตัดมันทิ้งไป!" + } + ]} +/> + +### 5. โมเดล TensorFlow จาก `transformers` นั้นเป็นโมเดล Keras อยู่แล้ว การที่เป็นแบบนี้นั้นมีประโยชน์อะไรบ้าง? + +TPUStrategy scope รวมถึงการ initialize โมเดลด้วย" + }, + { + text: "คุณสามารถใช้ประโยชน์จากเมธอดที่มีอยู่แล้วอย่างเช่น compile(), fit() และ predict()", + explain: "ถูกต้อง! เมื่อคุณมีข้อมูล การเทรนก็ไม่ยาก แค่ต้องทำงานเพิ่มอีกนิดเดียวเท่านั้น", + correct: true + }, + { + text: "คุณจะได้เรียนวิธีใช้ Keras และ transformers.", + explain: "ก็ถูกนะ แต่เราอยากได้คำตอบอื่น :)", + correct: true + }, + { + text: "คุณจะสามารถคำนวณ metrics ที่เกี่ยวข้องกับ dataset ได้โดยง่าย", + explain: "Keras นั้นช่วยในการเทรนและประเมินผลโมเดล แต่ไม่ได้ช่วยคำนวณ metrics ที่เกี่ยวข้องกับ dataset" + } + ]} +/> + +### 6. คุณสามารถสร้าง metric ของคุณเองได้อย่างไร? + +tf.keras.metrics.Metric.", + explain: "เยี่ยมเลย!", + correct: true + }, + { + text: "ใช้ Keras functional API.", + explain: "ลองใหม่อีกครั้ง!" + }, + { + text: "โดยการใช้ callable ด้วย signature metric_fn(y_true, y_pred).", + explain: "ถูกต้อง!", + correct: true + }, + { + text: "ใช้ google ค้นหาวิธี", + explain: "นั่นไม่ใช่คำตอบที่เราต้องการนะ แต่มันก็น่าจะช่วยคุณหาวิธีได้จริง ๆ", + correct: true + } + ]} +/> + +{/if} \ No newline at end of file diff --git a/chapters/zh-TW/_toctree.yml b/chapters/zh-TW/_toctree.yml new file mode 100644 index 000000000..b19551f42 --- /dev/null +++ b/chapters/zh-TW/_toctree.yml @@ -0,0 +1,4 @@ +- title: 0. 設置 + sections: + - local: chapter0/1 + title: 簡介 \ No newline at end of file diff --git a/chapters/zh-TW/chapter0/1.mdx b/chapters/zh-TW/chapter0/1.mdx new file mode 100644 index 000000000..8fd4fbb39 --- /dev/null +++ b/chapters/zh-TW/chapter0/1.mdx @@ -0,0 +1,111 @@ +# 簡介 + +歡迎來到Hugging Face的教學!本篇介紹將會帶著你設置運行環境。如果你正開始學的話,不妨先看看[第一章](/course/chapter1)再回來,這樣就能直接開始試著執行裡面的程式碼了。 + +我們會用到的所有函式庫都將會以Python資源包的方式被取得,所以這邊我們會教你如何設置Python環境並安裝你所需要的函式庫。 + +本篇將會涵蓋兩種設置環境的方法 - 使用Colab notebook或是Python虛擬環境。選你自己覺得合適的方式就好,但是對於初學者我們強烈推薦先從使用Colab notebook開始。 + +我們不會提到Windows系統,如果你是Windows的使用者,我們建議使用Colab notebook。如果你用的是Linux或是macOS,你可以任意選擇上述的兩種方法。 + +大部分的教學都會需要一個Hugging Face的帳號。我們建議現在就[創一個](https://huggingface.co/join)。 + +## 使用Google Colab notebook + +用Colab notebook是最簡單容易的方法;在瀏覽器開一頁Colab notebook就能直接開始寫程式了! + +如果你對Colab notebook不熟悉的話,我們建議你從[這篇介紹](https://colab.research.google.com/notebooks/intro.ipynb)開始。在Colab上你可以使用一些加速硬體,像是GPU或TPU,而且工作量不大的話也不收錢。 + +當你開始熟悉Colab後,建立新的筆記本然後開始進行設置: + +
+An empty colab notebook +
+ +接下來就是安裝我們將會用到的函式庫。我們會使用 `pip` 這個Python的資源管理工具來安裝。在Colab notebook裡,你可以用 `!` 來執行系統指令,所以你可以用以下的指令來安裝 🤗 Transformers函式庫: + +``` +!pip install transformers +``` + +把函式庫導入到Python runtime可以確認你的資源包有被正確地安裝: + +``` +import transformers +``` + +
+A gif showing the result of the two commands above: installation and import +
+ +這會安裝一個非常輕量的🤗 Transformers。裡面沒有安裝任何像是PyTorch或TensorFlow等的機器學習框架。因為我們會用到很多函式庫裡的不同功能,所以我們建議安裝包含了大部分使用情境所需資源的開發用版本: + + +``` +!pip install transformers[sentencepiece] +``` + +這會花一點時間,不過裝完你就已經完全準備好面對剩下的教學了! + + +## 使用Python虛擬環境 + +如果你比較想用Python虛擬環境的話,第一步就是安裝Python。我們建議跟著[這篇教學](https://realpython.com/installing-python/)做為起手式。 + + +當你安裝好Python後,你應該就能從終端機執行Python指令了。在進行下一步之前你可以先執行以下指令來確認Python有沒有安裝好:`python --version` 這條指令會讓終端機顯示你所安裝的Python版本。 + + +在終端機執行像是`python --version`的Python指令時,你應該把你的指令想成是用你系統上主要的Python版本來執行。我們建議不要在這個版本上安裝任何資源包,讓每個專案在各自獨立的環境裡運行就可以了。這樣每個專案都可以有各自的相依性跟資源包,你也不用擔心不同專案之間使用同一個環境時潛在的相容性問題。 + + +在Python我們可以用[*虛擬環境*](https://docs.python.org/3/tutorial/venv.html)來做這件事。虛擬環境是一個獨立包裝的樹狀目錄,每一個目錄下都有安裝特定版本的Python跟它需要的所有資源包。創建這樣的虛擬環境可以用很多不同的工具,不過我們會用一個叫做[`venv`](https://docs.python.org/3/library/venv.html#module-venv)的Python官方資源包。 + +首先,創建你希望你的程式執行時所在的目錄 - 舉例來說,你可能想要在你的家目錄下新增一個叫*transformers-course*的目錄: + +``` +mkdir ~/transformers-course +cd ~/transformers-course +``` +從這個目錄裡面,你可以用Python的`venv`模組建立一個虛擬環境: + +``` +python -m venv .env +``` +你現在應該在你的空資料夾裡找到一個叫做*.env*的目錄,這個目錄就是你的虛擬環境。 +You should now have a directory called *.env* in your otherwise empty folder: + +``` +ls -a +``` + +```out +. .. .env +``` +你可用 `activate` 和 `deactivate` 這兩個腳本來啟用或關閉你的虛擬環境: + +``` +# Activate the virtual environment +source .env/bin/activate + +# Deactivate the virtual environment +source .env/bin/deactivate +``` +你可以執行 `which python` 指令來確認你的虛擬環境是否有被啟用:如果它指向虛擬環境的目錄,那表示你的虛擬環境已經啟用了! + +``` +which python +``` + +```out +/home//transformers-course/.env/bin/python +``` + +### 安裝相依性資源包 + +在之前的段落中提到的使用Google Colab的情況裡,你會需要安裝相依性資源包才能繼續。你可以用 `pip` 這個資源管理工具來安裝開發版的🤗 Transformers: + +``` +pip install "transformers[sentencepiece]" +``` +你現在已經準備就緒,可以開始了! diff --git a/requirements.txt b/requirements.txt index 1b0269246..8f94be377 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ nbformat>=5.1.3 PyYAML>=5.4.1 -black \ No newline at end of file +black==22.3.0 \ No newline at end of file diff --git a/upcoming_chapters/en/chapter10.md b/upcoming_chapters/en/chapter10.md deleted file mode 100644 index 9444f2333..000000000 --- a/upcoming_chapters/en/chapter10.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: 'Chapter 10: Speeding up training' -description: - 'We need to go faster.' -prev: /chapter9 -next: /chapter11 -type: chapter -id: 10 ---- - - - - - - - - - - - - - diff --git a/upcoming_chapters/en/chapter11.md b/upcoming_chapters/en/chapter11.md deleted file mode 100644 index 0849529ae..000000000 --- a/upcoming_chapters/en/chapter11.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: 'Chapter 11: A custom training loop' -description: - 'But what about my own specific problems?' -prev: /chapter10 -next: /chapter12 -type: chapter -id: 11 ---- - - - - - - - diff --git a/upcoming_chapters/en/chapter12.md b/upcoming_chapters/en/chapter12.md deleted file mode 100644 index d698e4654..000000000 --- a/upcoming_chapters/en/chapter12.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -title: 'Chapter 12: Contribute to Transformers' -description: - 'Giving back' -prev: /chapter11 -next: null -type: chapter -id: 11 ---- - - - - -loprtin rte miondjfnjfs - - - - - - - - - diff --git a/upcoming_chapters/en/chapter9.md b/upcoming_chapters/en/chapter9.md deleted file mode 100644 index a5553fd1b..000000000 --- a/upcoming_chapters/en/chapter9.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -title: 'Chapter 09: Specialized architectures' -description: - 'Become an expert at transformer models.' -prev: /chapter8 -next: /chapter10 -type: chapter -id: 9 ---- - - - - - - - - - - - - - - - From f0f81230ababe2225a68a3d6ef34009cb195cec7 Mon Sep 17 00:00:00 2001 From: lewtun Date: Thu, 30 Jun 2022 15:21:42 +0200 Subject: [PATCH 21/51] Bump release (#270) --- chapters/it/_toctree.yml | 6 + chapters/it/chapter1/7.mdx | 16 + chapters/it/chapter1/8.mdx | 32 ++ chapters/it/chapter1/9.mdx | 11 + chapters/pt/_toctree.yml | 12 +- chapters/pt/chapter1/3.mdx | 331 ++++++++++++++++++ chapters/pt/chapter8/1.mdx | 12 + chapters/pt/chapter8/2.mdx | 366 ++++++++++++++++++++ chapters/pt/chapter8/3.mdx | 163 +++++++++ chapters/ru/_toctree.yml | 9 + chapters/ru/chapter4/3.mdx | 634 +++++++++++++++++++++++++++++++++++ chapters/ru/chapter4/4.mdx | 82 +++++ chapters/ru/chapter4/5.mdx | 7 + chapters/ru/chapter4/6.mdx | 223 +++++++++++++ chapters/th/_toctree.yml | 19 +- chapters/th/chapter6/10.mdx | 278 ++++++++++++++++ chapters/th/chapter6/2.mdx | 12 +- chapters/th/chapter6/3.mdx | 26 +- chapters/th/chapter6/3b.mdx | 648 ++++++++++++++++++++++++++++++++++++ chapters/th/chapter6/4.mdx | 129 +++++++ chapters/th/chapter6/5.mdx | 371 +++++++++++++++++++++ chapters/th/chapter6/6.mdx | 394 ++++++++++++++++++++++ chapters/th/chapter6/7.mdx | 404 ++++++++++++++++++++++ chapters/th/chapter6/8.mdx | 597 +++++++++++++++++++++++++++++++++ chapters/th/chapter6/9.mdx | 11 + 25 files changed, 4772 insertions(+), 21 deletions(-) create mode 100644 chapters/it/chapter1/7.mdx create mode 100644 chapters/it/chapter1/8.mdx create mode 100644 chapters/it/chapter1/9.mdx create mode 100644 chapters/pt/chapter1/3.mdx create mode 100644 chapters/pt/chapter8/1.mdx create mode 100644 chapters/pt/chapter8/2.mdx create mode 100644 chapters/pt/chapter8/3.mdx create mode 100644 chapters/ru/chapter4/3.mdx create mode 100644 chapters/ru/chapter4/4.mdx create mode 100644 chapters/ru/chapter4/5.mdx create mode 100644 chapters/ru/chapter4/6.mdx create mode 100644 chapters/th/chapter6/10.mdx create mode 100644 chapters/th/chapter6/3b.mdx create mode 100644 chapters/th/chapter6/4.mdx create mode 100644 chapters/th/chapter6/5.mdx create mode 100644 chapters/th/chapter6/6.mdx create mode 100644 chapters/th/chapter6/7.mdx create mode 100644 chapters/th/chapter6/8.mdx create mode 100644 chapters/th/chapter6/9.mdx diff --git a/chapters/it/_toctree.yml b/chapters/it/_toctree.yml index f30341dcc..670ecb291 100644 --- a/chapters/it/_toctree.yml +++ b/chapters/it/_toctree.yml @@ -17,6 +17,12 @@ title: Modelli encoder - local: chapter1/6 title: Modelli decoder + - local: chapter1/7 + title: Modelli sequence-to-sequence + - local: chapter1/8 + title: Bias e limiti + - local: chapter1/9 + title: Riassunto - title: 4. Condividere modelli e tokenizers sections: diff --git a/chapters/it/chapter1/7.mdx b/chapters/it/chapter1/7.mdx new file mode 100644 index 000000000..a8c616c64 --- /dev/null +++ b/chapters/it/chapter1/7.mdx @@ -0,0 +1,16 @@ +# Modelli sequence-to-sequence + + + +I modelli encoder-decoder (detti anche modelli *sequence-to-sequence*) utilizzano entrambi i componenti dell'architettura Transformer. Ad ogni passaggio, gli attention layer dell'encoder hanno accesso a tutte le parole della frase iniziale, mentre gli attention layer del decoder possono solo accedere alle parole che precedono linearmente una data parola nell'input. + +Il pre-addestramento di questi modelli può essere fatto utilizzando gli obiettivi dei modelli encoder o decoder, anche se solitamente include un livello di complessità maggiore. Ad esempio, [T5](https://huggingface.co/t5-base) è pre-addestrato rimpiazzando porzioni random di testo (che possono contenere più di una parola) con una speciale mask word, con l'obiettivo di predirre il testo rimpiazzato dalla mask word stessa. + +I modelli sequence-to-sequence sono più adatti ai compiti che hanno a che fare con la generazione di nuove frasi sulla base di un input preciso, come il riassunto, la traduzione, o la generazione di risposte a domande. + +Tra i rappresentanti di questa famiglia di modelli ci sono: + +- [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) diff --git a/chapters/it/chapter1/8.mdx b/chapters/it/chapter1/8.mdx new file mode 100644 index 000000000..9a68fed34 --- /dev/null +++ b/chapters/it/chapter1/8.mdx @@ -0,0 +1,32 @@ +# Bias e limiti + + + +Se intendi utilizzare un modello pre-addestrato o una versione affinata in produzione, sii consapevole che i modelli sono degli strumenti potenti, ma hanno dei limiti. Il più grande limite è che, per permettere un pre-addestramento su una quantità importante di dati, i ricercatori spesso includono tutti i contenuti ai quali riescono ad accedere, prendendo nel contempo il meglio e il peggio di ciò che Intenet offre. + +Per vederne una rappresentazione rapida, torniamo all'esempio della pipeline `fill-mask` con il modello BERT: + +```python +from transformers import pipeline + +unmasker = pipeline("fill-mask", model="bert-base-uncased") +result = unmasker("This man works as a [MASK].") +print([r["token_str"] for r in result]) + +result = unmasker("This woman works as a [MASK].") +print([r["token_str"] for r in result]) +``` + +```python out +['lawyer', 'carpenter', 'doctor', 'waiter', 'mechanic'] +['nurse', 'waitress', 'teacher', 'maid', 'prostitute'] +``` + +Quando domandiamo al modello di trovare la parola mancante in queste due frasi, questo produce solo una risposta senza genere predeterminato ('waiter/waitress'). Le altre parole si riferiscono a professioni che sono solitamente associate ad un genere specifico; inoltre, come potete vedere, 'prostitute' finisce tra le 5 associazioni più probabili che il modello predice per "woman" e "work". Ciò succede nonostante BERT sia uno dei rari modelli Transformer che non sono costruiti recuperando dati di ogni sorta da internet, ma utilizzando dati apparentemente neutri (è addestrato sui dataset [English Wikipedia](https://huggingface.co/datasets/wikipedia) e [BookCorpus](https://huggingface.co/datasets/bookcorpus)). + +Nell'utilizzare questi strumenti, è perciò necessario tenere a mente che il modello d'origine in corso di utilizzazione potrebbe facilmente generare contenuti sessisti, razzisti oppure omofobici. Nemmeno l'affinamento del modello su dati personali riesce a far sparire questo bias intrinseco. diff --git a/chapters/it/chapter1/9.mdx b/chapters/it/chapter1/9.mdx new file mode 100644 index 000000000..3b11de19c --- /dev/null +++ b/chapters/it/chapter1/9.mdx @@ -0,0 +1,11 @@ +# Riassunto + +In questo capitolo, hai scoperto come approcciare diversi compiti di NLP utilizzando la funzione di alto livello `pipeline()` degli 🤗 Transformer. Abbiamo anche visto come cercare e utilizzare i modelli dell'Hub, nonché come usare l'Inference API per testare i modelli direttamente nel tuo browser. + +Abbiamo discusso di come i modelli Transformer lavorino a livello alto, e parlato dell'importanza del transfer learning e dell'affinamento. Un aspetto chiave è che è possibile utilizzare l'architettuta completa oppure solo l'encoder o il decoder, dipendentemente dal compito a cui desideri lavorare. La tabella seguente riordina questi concetti: + +| Modello | Esempi | Compiti | +|-----------------|--------------------------------------------|----------------------------------------------------------------------------------| +| Encoder | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa | Classificazione frasale, riconoscimento delle entità nominate, estrazione di risposte a domande | +| Decoder | CTRL, GPT, GPT-2, Transformer XL | Generazione di testi | +| Encoder-decoder | BART, T5, Marian, mBART | Riassunti, traduzione, generazione di risposte a domande | diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index 168333305..47d726cff 100644 --- a/chapters/pt/_toctree.yml +++ b/chapters/pt/_toctree.yml @@ -9,6 +9,8 @@ title: Introdução - local: chapter1/2 title: Processamento de Linguagem Natural + - local: chapter1/3 + title: Transformers, o que eles podem fazer? - title: 2. Usando 🤗 Transformers sections: @@ -71,8 +73,16 @@ - local: chapter7/1 title: Introdução +- title: 8. Como pedir ajuda 🤗 + sections: + - local: chapter8/1 + title: Introdução + - local: chapter8/2 + title: O que fazer quando ocorrer um erro + - local: chapter8/3 + title: Pedindo ajuda nos fóruns + - title: Evento do curso Hugging Face sections: - local: event/1 title: Evento de lançamento da Parte 2 - \ No newline at end of file diff --git a/chapters/pt/chapter1/3.mdx b/chapters/pt/chapter1/3.mdx new file mode 100644 index 000000000..254d83372 --- /dev/null +++ b/chapters/pt/chapter1/3.mdx @@ -0,0 +1,331 @@ +# Transformers, o que eles podem fazer? + + + +Nessa seção, observaremos sobre o que os modelos Transformers podem fazer e usar nossa primeira ferramenta da biblioteca 🤗 Transformers: a função `pipeline()` . + + +👀 Tá vendo o botão Open in Colab no topo direito? Clique nele e abra um notebook Google Colab notebook com todas as amostras de códigos dessa seção. Esse botão estará presente em cada seção contendo exemplos de códigos. + +Se você deseja rodar os exemplos localmente, nós recomendamos dar uma olhada no setup. + + +## Transformers estão por toda parte! + +Os modelos Transformers são usados para resolver todos os tipos de tarefas de NLP, como algumas já mencionadas na seção anterior. Aqui estão algumas empresas e organizações usando a Hugging Face e os modelos Transformers, que também contribuem de volta para a comunidade compartilhando seus modelos: + +Empresas usando a Hugging Face + +A [biblioteca 🤗 Transformers](https://github.com/huggingface/transformers) oferece a funcionalidade para criar e usar esses modelos compartilhados. O [Model Hub](https://huggingface.co/models) contém milhares de modelos pré-treinados que qualquer um pode baixar e usar. Você pode também dar upload nos seus próprios modelos no Hub! + + +⚠️ O Hugging Face Hub não é limitado aos modelos Transformers. Qualquer um pode compartilhar quaisquer tipos de modelos ou datasets que quiserem! Crie uma conta na huggingface.co para se beneficiar de todos os recursos disponíveis! + + +Antes de aprofundarmos sobre como os modelos Transformers funcionam por debaixo dos panos, vamos olhar alguns exemplos de como eles podem ser usados para solucionar alguns problemas de NLP interessantes. + +## Trabalhando com pipelines + + + +O objeto mais básico na biblioteca 🤗 Transformers é a função `pipeline()` . Ela conecta o modelo com seus passos necessários de pré e pós-processamento, permitindo-nos a diretamente inserir qualquer texto e obter uma resposta inteligível: + +```python +from transformers import pipeline + +classifier = pipeline("sentiment-analysis") +classifier("I've been waiting for a HuggingFace course my whole life.") +``` + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}] +``` + +Nós até podemos passar várias sentenças! + +```python +classifier( + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!"] +) +``` + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}, + {'label': 'NEGATIVE', 'score': 0.9994558095932007}] +``` + +Por padrão, esse pipeline seleciona particularmente um modelo pré-treinado que tem sido *ajustado* (fine-tuned) para análise de sentimentos em Inglês. O modelo é baixado e cacheado quando você criar o objeto `classifier`. Se você rodar novamente o comando, o modelo cacheado irá ser usado no lugar e não haverá necessidade de baixar o modelo novamente. + +Há três principais passos envolvidos quando você passa algum texto para um pipeline: + +1. O texto é pré-processado para um formato que o modelo consiga entender. +2. As entradas (*inputs*) pré-processados são passadas para o modelo. +3. As predições do modelo são pós-processadas, para que então você consiga atribuir sentido a elas. + + +Alguns dos [pipelines disponíveis](https://huggingface.co/transformers/main_classes/pipelines.html) atualmente, são: + +- `feature-extraction` (pega a representação vetorial do texto) +- `fill-mask` (preenchimento de máscara) +- `ner` (reconhecimento de entidades nomeadas) +- `question-answering` (responder perguntas) +- `sentiment-analysis` (análise de sentimentos) +- `summarization` (sumarização) +- `text-generation` (geração de texto) +- `translation` (tradução) +- `zero-shot-classification` (classificação "zero-shot") + +Vamos dar uma olhada em alguns desses! + +## Classificação Zero-shot + +Nós começaremos abordando uma tarefa mais desafiadora da qual nós precisamos classificar texto que não tenham sido rotulados. Esse é um cenário comum nos projetos reais porque anotar texto geralmente consome bastante do nosso tempo e requer expertise no domínio. Para esse caso, o pipeline `zero-shot-classification` é muito poderoso: permite você especificar quais rótulos usar para a classificação, desse modo você não precisa "confiar" nos rótulos dos modelos pré-treinados. Você já viu como um modelo pode classificar uma sentença como positiva ou negativa usando esses dois rótulos - mas também pode ser classificado usando qualquer outro conjunto de rótulos que você quiser. + +```python +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +classifier( + "This is a course about the Transformers library", + candidate_labels=["education", "politics", "business"], +) +``` + +```python out +{'sequence': 'This is a course about the Transformers library', + 'labels': ['education', 'business', 'politics'], + 'scores': [0.8445963859558105, 0.111976258456707, 0.043427448719739914]} +``` + +Esse pipeline é chamado de _zero-shot_ porque você não precisa fazer o ajuste fino do modelo nos dados que você o utiliza. Pode diretamente retornar scores de probabilidade para qualquer lista de rótulos que você quiser! + + + +✏️ **Experimente!** Brinque com suas próprias sequências e rótulos e veja como o modelo se comporta. + + + + +## Geração de Texto + +Agora vamos ver como usar um pipeline para gerar uma porção de texto. A principal ideia aqui é que você coloque um pedaço de texto e o modelo irá autocompletá-lo ao gerar o texto restante. Isso é similar ao recurso de predição textual que é encontrado em inúmeros celulares. A geração de texto envolve aleatoriedade, então é normal se você não obter o mesmo resultado obtido mostrado abaixo. + +```python +from transformers import pipeline + +generator = pipeline("text-generation") +generator( + "In this course, we will teach you how to" +) # nesse curso, nós te mostraremos como você +``` + +```python out +[{'generated_text': 'In this course, we will teach you how to understand and use ' + 'data flow and data interchange when handling user data. We ' + 'will be working with one or more of the most commonly used ' + 'data flows — data flows of various types, as seen by the ' + 'HTTP'}] # nesse curso, nós te mostraremos como você pode entender e usar o fluxo de dados e a troca de dados quando for lidar com dados do usuário. Nós estaremos trabalhando com um ou um dos mais comuns fluxos de dados utilizados - fluxo de dados de vários tipos, como visto pelo 'HTTP' +``` + +Você pode controlar quão diferentes sequências são geradas com o argumento `num_return_sequences` e o tamanho total da saída de texto (*output*) com o argumento `max_length`. + + + +✏️ **Experimente!** Use os argumentos `num_return_sequences` e `max_length` para gerar 2 textos com 15 palavras cada. + + + + +## Usando qualquer modelo do Hub em um pipeline + +Nos exemplos passados, usamos o modelo padrão para a tarefa que executamos, mas você pode usar um modelo particular do Hub para usá-lo no pipeline em uma tarefa específica — exemplo, geração de texto. Vá ao [Model Hub](https://huggingface.co/models) e clique na tag correspondente na esquerda para mostrar apenas os modelos suportáveis para aquela tarefa. Você deverá ir a uma página como [essa](https://huggingface.co/models?pipeline_tag=text-generation). + +Vamos tentar o modelo [`distilgpt2`](https://huggingface.co/distilgpt2)! Aqui está como carrega-lo no mesmo pipeline como antes: + +```python +from transformers import pipeline + +generator = pipeline("text-generation", model="distilgpt2") +generator( + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, +) +``` + +```python out +[{'generated_text': 'In this course, we will teach you how to manipulate the world and ' + 'move your mental and physical capabilities to your advantage.'}, + {'generated_text': 'In this course, we will teach you how to become an expert and ' + 'practice realtime, and with a hands on experience on both real ' + 'time and real'}] +``` + +Você pode refinar sua pesquisa por um modelo clicando nas tags de linguagem, e pegando o modelo que gerará o texto em outra lingua. O Model Hub até mesmo contém checkpoints para modelos multilinguais que suportem várias linguas. + +Uma vez que você seleciona o modelo clicando nele, você irá ver que há um widget que permite que você teste-o diretamente online. Desse modo você pode rapidamente testar as capacidades do modelo antes de baixa-lo. + + + +✏️ **Experimente!** Use os filtros para encontrar um modelo de geração de texto em outra lingua. Fique à vontade para brincar com o widget e usa-lo em um pipeline! + + + +### A API de Inferência + +Todos os modelos podem ser testados diretamente de seu navegador usando a API de InferênciaI, que está disponível no website da [Hugging Face](https://huggingface.co/). Você pode brincar com o modelo diretamente pela página colocando textos customizados e observando como o modelo processa os dados inseridos. + +A API de Inferência que alimenta o widget também está disponível como um produto pago, que serve como uma "mão na roda" se você precisa dela para seus workflows. Olhe a [página de preços](https://huggingface.co/pricing) para mais detalhes. + +## Preenchimento de máscara (*Mask filling*) + +O próximo pipeline que você irá testar é o `fill-mask`. A ideia dessa tarefa é preencher os espaços em branco com um texto dado: + +```python +from transformers import pipeline + +unmasker = pipeline("fill-mask") +unmasker("This course will teach you all about models.", top_k=2) +``` + +```python out +[{'sequence': 'This course will teach you all about mathematical models.', + 'score': 0.19619831442832947, + 'token': 30412, + 'token_str': ' mathematical'}, + {'sequence': 'This course will teach you all about computational models.', + 'score': 0.04052725434303284, + 'token': 38163, + 'token_str': ' computational'}] +``` + +O argumento `top_k` controla quantas possibilidades você quer que sejam geradas. Note que aqui o modelo preenche com uma palavra `` especial, que é frequentemente referida como *mask token*. Outros modelos de preenchimento de máscara podem ter diferentes *mask tokens*, então é sempre bom verificar uma palavra máscara apropriada quando explorar outros modelos. Um modo de checar isso é olhando para a palavra máscara usada no widget. + + + +✏️ **Experimente!** Pesquise pelo modelo `bert-base-cased` no Hub e identifique suas palavras máscara no widget da API de inferência. O que esse modelo prediz para a sentença em nosso `pipeline` no exemplo acima? + + + +## Reconhecimento de entidades nomeadas + +Reconhecimento de Entidades Nomeadas (NER) é uma tarefa onde o modelo tem de achar quais partes do texto correspondem a entidades como pessoas, locais, organizações. Vamos olhar em um exemplo: + +```python +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +```python out +[{'entity_group': 'PER', 'score': 0.99816, 'word': 'Sylvain', 'start': 11, 'end': 18}, + {'entity_group': 'ORG', 'score': 0.97960, 'word': 'Hugging Face', 'start': 33, 'end': 45}, + {'entity_group': 'LOC', 'score': 0.99321, 'word': 'Brooklyn', 'start': 49, 'end': 57} +] +``` + +Aqui o modelo corretamente identificou que Sylvain é uma pessoa (PER), Hugging Face é uma organização (ORG), e Brooklyn é um local (LOC). + +Nós passamos a opção `grouped_entities=True` na criação da função do pipelina para dize-lo para reagrupar juntos as partes da sentença que correspondem à mesma entidade: aqui o modelo agrupou corretamente "Hugging" e "Face" como única organização, ainda que o mesmo nome consista em múltiplas palavras. Na verdade, como veremos no próximo capítulo, o pré-processamento até mesmo divide algumas palavras em partes menores. Por exemplo, `Sylvain` é dividido em 4 pedaços: `S`, `##yl`, `##va`, e `##in`. No passo de pós-processamento, o pipeline satisfatoriamente reagrupa esses pedaços. + + + +✏️ **Experimente!** Procure no Model Hub por um modelo capaz de fazer o tageamento de partes do discurso (usualmente abreviado como POS) em inglês. O que o modelo prediz para a sentença no exemplo acima? + + + +## Responder perguntas + +O pipeline `question-answering` responde perguntas usando informações dado um contexto: + +```python +from transformers import pipeline + +question_answerer = pipeline("question-answering") +question_answerer( + question="Where do I work?", + context="My name is Sylvain and I work at Hugging Face in Brooklyn", +) +``` + +```python out +{'score': 0.6385916471481323, 'start': 33, 'end': 45, 'answer': 'Hugging Face'} +``` + +Note que o pipeline funciona através da extração da informação dado um contexto; não gera uma resposta. + +## Sumarização + +Sumarização é uma tarefa de reduzir um texto em um texto menor enquanto pega toda (ou boa parte) dos aspectos importantes do texto referenciado. Aqui um exemplo: + +```python +from transformers import pipeline + +summarizer = pipeline("summarization") +summarizer( + """ + America has changed dramatically during recent years. Not only has the number of + graduates in traditional engineering disciplines such as mechanical, civil, + electrical, chemical, and aeronautical engineering declined, but in most of + the premier American universities engineering curricula now concentrate on + and encourage largely the study of engineering science. As a result, there + are declining offerings in engineering subjects dealing with infrastructure, + the environment, and related issues, and greater concentration on high + technology subjects, largely supporting increasingly complex scientific + developments. While the latter is important, it should not be at the expense + of more traditional engineering. + + Rapidly developing economies such as China and India, as well as other + industrial countries in Europe and Asia, continue to encourage and advance + the teaching of engineering. Both China and India, respectively, graduate + six and eight times as many traditional engineers as does the United States. + Other industrial countries at minimum maintain their output, while America + suffers an increasingly serious decline in the number of engineering graduates + and a lack of well-educated engineers. +""" +) +``` + +```python out +[{'summary_text': ' America has changed dramatically during recent years . The ' + 'number of engineering graduates in the U.S. has declined in ' + 'traditional engineering disciplines such as mechanical, civil ' + ', electrical, chemical, and aeronautical engineering . Rapidly ' + 'developing economies such as China and India, as well as other ' + 'industrial countries in Europe and Asia, continue to encourage ' + 'and advance engineering .'}] +``` + +Como a geração de texto, você pode especificar o tamanho máximo `max_length` ou mínimo `min_length` para o resultado. + + +## Tradução + +Para tradução, você pode usar o modelo default se você der um par de idiomas no nome da tarefa (tal como `"translation_en_to_fr"`, para traduzir inglês para francês), mas a maneira mais fácil é pegar o moddelo que você quiser e usa-lo no [Model Hub](https://huggingface.co/models). Aqui nós iremos tentar traduzir do Francês para o Inglês: + +```python +from transformers import pipeline + +translator = pipeline("translation", model="Helsinki-NLP/opus-mt-fr-en") +translator("Ce cours est produit par Hugging Face.") +``` + +```python out +[{'translation_text': 'This course is produced by Hugging Face.'}] +``` + +Como a geração de texto e a sumarização, você pode especificar o tamanho máximo `max_length` e mínimo `min_length` para o resultado. + + + +✏️ **Experimente!** Pesquise por modelos de tradução em outras línguas e experimente traduzir a sentença anterior em idiomas diferentes. + + + +Os pipelines mostrados até agora são em sua maioria para propósitos demonstrativos. Eles foram programados para tarefas específicas e não podem performar variações delas. No próximo capítulo, você aprenderá o que está por dentro da função `pipeline()` e como customizar seu comportamento. diff --git a/chapters/pt/chapter8/1.mdx b/chapters/pt/chapter8/1.mdx new file mode 100644 index 000000000..2500a85c8 --- /dev/null +++ b/chapters/pt/chapter8/1.mdx @@ -0,0 +1,12 @@ +# Introdução + +Agora que você sabe como lidar com as tarefas mais comuns de PNL com 🤗 Transformers, você deve ser capaz de começar seus próprios projetos! Neste capítulo, exploraremos o que fazer quando você encontrar um problema. Você aprenderá como debugar com sucesso seu código ou seu treino e como pedir ajuda à comunidade se não conseguir resolver o problema sozinho. E se você acha que encontrou um bug em uma das bibliotecas do Hugging Face, mostraremos a melhor maneira de informa-lo para que o problema seja resolvido o mais rápido possível. + +Mais precisamente, neste capítulo você aprenderá: + +- A primeira coisa a fazer quando você recebe um erro +- Como pedir ajuda nos [fóruns](https://discuss.huggingface.co/) +- Como debugar sua pipeline de treinamento +- Como escrever uma boa issue + +Nada disso está especificamente relacionado a 🤗 Transformers ou ao ecossistema Hugging Face, é claro; os tópicos deste capítulo são aplicáveis à maioria dos projetos de código aberto! \ No newline at end of file diff --git a/chapters/pt/chapter8/2.mdx b/chapters/pt/chapter8/2.mdx new file mode 100644 index 000000000..75a68bb0d --- /dev/null +++ b/chapters/pt/chapter8/2.mdx @@ -0,0 +1,366 @@ +# O que fazer quando ocorrer um erro + + + +Nesta seção, veremos alguns erros comuns que podem ocorrer ao tentar gerar previsões de seu modelo Transformer recém treinado. Isso irá prepará-lo para a [seção 4](/course/chapter8/section4), onde exploraremos como debugar a própria fase de treinamento. + + + +Preparamos um [repositório modelo](https://huggingface.co/lewtun/distilbert-base-uncased-finetuned-squad-d5716d28) para esta seção e, se você quiser executar o código neste capítulo, Primeiro, você precisará copiar o modelo para sua conta no [Hugging Face Hub](https://huggingface.co). Para fazer isso, primeiro faça login executando o seguinte em um notebook Jupyter: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +ou usando seu terminal favorito: + +```bash +huggingface-cli login +``` + +Isso solicitará que você insira seu nome de usuário e senha e salvará um token em *~/.cache/huggingface/*. Depois de fazer login, você pode copiar o repositório de modelos com a seguinte função: + +```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(): + # Clone the repo and extract the local path + template_repo_id = "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28" + commit_hash = "be3eaffc28669d7932492681cd5f3e8905e358b4" + template_repo_dir = snapshot_download(template_repo_id, revision=commit_hash) + # Create an empty repo on the Hub + model_name = template_repo_id.split("/")[1] + create_repo(model_name, exist_ok=True) + # Clone the empty repo + new_repo_id = get_full_repo_name(model_name) + new_repo_dir = model_name + repo = Repository(local_dir=new_repo_dir, clone_from=new_repo_id) + # Copy files + copy_tree(template_repo_dir, new_repo_dir) + # Push to Hub + repo.push_to_hub() +``` + +Agora, quando você chamar `copy_repository_template()`, ele criará uma cópia do repositório de modelos em sua conta. + +## Debugando o pipeline de 🤗 Transformers + +Para iniciar nossa jornada no maravilhoso mundo de debug de modelos Transformer, considere o seguinte cenário: você está trabalhando com um colega em um projeto de resposta a perguntas para ajudar os clientes de um site de comércio eletrônico a encontrar respostas sobre produtos de consumo. Seu colega lhe envia uma mensagem como: + +> Bom dia! Acabei de fazer um experimento usando as técnicas do [Capítulo 7](/course/chapter7/7) do curso Hugging Face e obtive ótimos resultados no SQuAD! Acho que podemos usar esse modelo como checkpoint para o nosso projeto. O ID do modelo no Hub é "lewtun/distillbert-base-uncased-finetuned-squad-d5716d28". Fique a vontade para testar :) + +e a primeira coisa que você pensa é carregar o modelo usando o `pipeline` de 🤗 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 +""" +``` + +Oh não, algo parece ter dado errado! Se você é novo em programação, esse tipo de erro pode parecer um pouco enigmático no começo (o que é mesmo um `OSError`?!). O erro exibido aqui é apenas a última parte de um relatório de erros muito maior chamado _Python traceback_ (também conhecido como **stack trace**). Por exemplo, se você estiver executando este código no Google Colab, deverá ver algo como a captura de tela a seguir: + +
+A Python traceback. +
+ +Há muitas informações contidas nesses relatórios, então vamos percorrer as partes principais juntos. A primeira coisa a notar é que os tracebacks devem ser lidos _de baixo para cima_. Isso pode soar estranho se você está acostumado a ler texto em inglês de cima para baixo, mas reflete o fato de que o traceback mostra a sequência de chamadas de função que o `pipeline` faz ao baixar o modelo e o tokenizer. (Confira o [Capítulo 2](/course/chapter2) para mais detalhes sobre como o `pipeline` funciona nos bastidores.) + + + +🚨 Está vendo aquela caixa azul em torno de "6 frames" no traceback do Google Colab? Esse é um recurso especial do Colab, que compacta o traceback em "quadros". Se você não conseguir encontrar a fonte de um erro, certifique-se de expandir o rastreamento completo clicando nessas duas pequenas setas. + + + +Isso significa que a última linha do traceback indica a última mensagem de erro e fornece o nome da exceção que foi gerada. Nesse caso, o tipo de exceção é `OSError`, que indica um erro relacionado ao sistema. Se lermos a mensagem de erro que a acompanha, veremos que parece haver um problema com o arquivo *config.json* do modelo e recebemos duas sugestões para corrigi-lo: + +```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 +""" +``` + + + +💡 Se você encontrar uma mensagem de erro difícil de entender, basta copiar e colar a mensagem na barra de pesquisa do Google ou [Stack Overflow](https://stackoverflow.com/) (sim, sério!). Há uma boa chance de você não ser a primeira pessoa a encontrar o erro, e essa é uma boa maneira de encontrar soluções que outras pessoas da comunidade postaram. Por exemplo, pesquisar por `OSError: Can't load config for` no Stack Overflow fornece vários [hits](https://stackoverflow.com/search?q=OSError%3A+Can%27t+load+config+for+) que poderia ser usado como ponto de partida para resolver o problema. + + + +A primeira sugestão é nos pedir para verificar se o ID do modelo está realmente correto, então a primeira ordem do dia é copiar o identificador e colá-lo na barra de pesquisa do Hub: + +
+The wrong model name. +
+ +Hmm, realmente parece que o modelo do nosso colega não está no Hub... aha, mas há um erro de digitação no nome do modelo! DistilBERT tem apenas um "l" em seu nome, então vamos corrigir isso e procurar por "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28": + +
+The right model name. +
+ +Ok, isso teve sucesso. Agora vamos tentar baixar o modelo novamente com o ID do modelo correto: + +```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 +""" +``` + +Argh, frustrado novamente - bem-vindo ao cotidiano de um engenheiro de aprendizado de máquina! Como corrigimos o ID do modelo, o problema deve estar no próprio repositório. Uma maneira rápida de acessar o conteúdo de um repositório no 🤗 Hub é através da função `list_repo_files()` da biblioteca `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'] +``` + +Interessante -- não parece haver um arquivo *config.json* no repositório! Não é à toa que nosso `pipeline` não conseguiu carregar o modelo; nosso colega deve ter esquecido de enviar este arquivo para o Hub depois de ajustá-lo. Nesse caso, o problema parece bem simples de corrigir: poderíamos pedir para adicionar o arquivo ou, como podemos ver no ID do modelo, que o modelo pré-treinado usado foi [`distilbert-base-uncased`](https:/ /huggingface.co/distilbert-base-uncased), podemos baixar a configuração para este modelo e enviá-la para nosso repositório para ver se isso resolve o problema. Vamos tentar isso. Usando as técnicas que aprendemos no [Capítulo 2](/course/chapter2), podemos baixar a configuração do modelo com a classe `AutoConfig`: + + +```python +from transformers import AutoConfig + +pretrained_checkpoint = "distilbert-base-uncased" +config = AutoConfig.from_pretrained(pretrained_checkpoint) +``` + + + +🚨 A abordagem que estamos tomando aqui não é infalível, já que nosso colega pode ter ajustado a configuração de `distilbert-base-uncased` antes de ajustar o modelo. Na vida real, gostaríamos de verificar com eles primeiro, mas para os propósitos desta seção, vamos supor que eles usaram a configuração padrão. + + + +Podemos então enviar isso para o nosso repositório de modelos com a função `push_to_hub()` da configuração: + +```python +config.push_to_hub(model_checkpoint, commit_message="Add config.json") +``` + +Agora podemos testar se funcionou carregando o modelo do último commit no branch `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'} +``` + +Uhuuul, funcionou! Vamos recapitular o que você acabou de aprender: + +- As mensagens de erro em Python são conhecidas como _tracebacks_ e são lidas de baixo para cima. A última linha da mensagem de erro geralmente contém as informações necessárias para localizar a origem do problema. +- Se a última linha não contiver informações suficientes, suba o traceback e veja se você consegue identificar onde no código-fonte o erro ocorre. +- Se nenhuma das mensagens de erro puder ajudá-lo a debugar o problema, tente pesquisar online uma solução para um problema semelhante. +- O `huggingface_hub` +// 🤗 Hub? +esta biblioteca fornece um conjunto de ferramentas que você pode usar para interagir e debugar repositórios no Hub. + +Agora que você sabe como debugar um pipeline, vamos dar uma olhada em um exemplo mais complicado no forward pass do próprio modelo. + +## Debugando o forward pass do seu modelo + +Embora o `pipeline` seja ótimo para a maioria dos aplicativos em que você precisa gerar previsões rapidamente, às vezes você precisará acessar os logits do modelo (digamos, se você tiver algum pós-processamento personalizado que gostaria de aplicar). Para ver o que pode dar errado neste caso, vamos primeiro pegar o modelo e o tokenizer do nosso `pipeline`: + +```python +tokenizer = reader.tokenizer +model = reader.model +``` + +Em seguida, precisamos de uma pergunta, então vamos ver se nossos frameworks favoritos são suportados: + +```python +question = "Which frameworks can I use?" +``` + +Como vimos no [Capítulo 7](/course/chapter7), as etapas usuais que precisamos seguir são tokenizar as entradas, extrair os logits dos tokens de início e fim e, em seguida, decodificar o intervalo de resposta: + +```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 +# Get the most likely beginning of answer with the argmax of the score +answer_start = torch.argmax(answer_start_scores) +# Get the most likely end of answer with the argmax of the score +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' +""" +``` + +Oxii, parece que temos um bug em nosso código! Mas não temos medo de debugar um pouco. Você pode usar o debugger do Python em um notebook: + + + +ou em um terminal: + + + +Aqui, a leitura da mensagem de erro nos diz que o objeto `'list' não tem atributo 'size'`, e podemos ver uma seta `-->` apontando para a linha onde o problema foi levantado em `model(**inputs) `. Você pode debugar isso interativamente usando o debugger Python, mas por enquanto vamos simplesmente imprimir uma fatia de `entradas` para ver o que temos: + +```python +inputs["input_ids"][:5] +``` + +```python out +[101, 2029, 7705, 2015, 2064] +``` + +Isso certamente se parece com uma `lista` comum do Python, mas vamos verificar novamente o tipo: + +```python +type(inputs["input_ids"]) +``` + +```python out +list +``` + +Sim, isso é uma `lista` do Python com certeza. Então o que deu errado? Lembre-se do [Capítulo 2](/course/chapter2) que as classes `AutoModelForXxx` em 🤗 Transformers operam em _tensors_ (em PyTorch ou TensorFlow), e uma operação comum é extrair as dimensões de um tensor usando `Tensor.size( )` em, digamos, PyTorch. Vamos dar outra olhada no traceback, para ver qual linha acionou a exceção: + +``` +~/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' +``` + +Parece que nosso código tentou chamar `input_ids.size()`, mas isso claramente não funcionará para uma `list` Python, que é apenas um contêiner. Como podemos resolver este problema? Pesquisar a mensagem de erro no Stack Overflow fornece alguns [hits] relevantes (https://stackoverflow.com/search?q=AttributeError%3A+%27list%27+object+has+no+attribute+%27size%27&s=c15ec54c-63cb-481d-a749-408920073e8f). Clicar no primeiro exibe uma pergunta semelhante à nossa, com a resposta mostrada na captura de tela abaixo: + + +
+An answer from Stack Overflow. +
+ +A resposta recomenda que adicionemos `return_tensors='pt'` ao tokenizer, então vamos ver se isso funciona para nós: + +```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 +# Get the most likely beginning of answer with the argmax of the score +answer_start = torch.argmax(answer_start_scores) +# Get the most likely end of answer with the argmax of the score +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 +""" +``` + +Legal, funcionou! Este é um ótimo exemplo de como o Stack Overflow pode ser útil: ao identificar um problema semelhante, pudemos nos beneficiar da experiência de outras pessoas da comunidade. No entanto, uma pesquisa como essa nem sempre produz uma resposta relevante, então o que você pode fazer nesses casos? Felizmente, há uma comunidade acolhedora de desenvolvedores nos [fóruns do Hugging Face](https://discuss.huggingface.co/) que pode ajudá-lo! Na próxima seção, veremos como você pode criar boas perguntas no fórum que provavelmente serão respondidas. \ No newline at end of file diff --git a/chapters/pt/chapter8/3.mdx b/chapters/pt/chapter8/3.mdx new file mode 100644 index 000000000..c738682e5 --- /dev/null +++ b/chapters/pt/chapter8/3.mdx @@ -0,0 +1,163 @@ +# Pedindo ajuda nos fóruns + + + + +Os [fóruns do Hugging Face](https://discuss.huggingface.co) são um ótimo lugar para obter ajuda da equipe de código aberto e da comunidade Hugging Face. Aqui está a aparência da página principal em um determinado dia: + +
+The Hugging Face forums. +
+ +No lado esquerdo você pode ver todas as categorias em que os vários tópicos estão agrupados, enquanto o lado direito mostra os tópicos mais recentes. Um tópico é uma postagem que contém um título, categoria e descrição; é bastante semelhante ao formato de problemas do GitHub que vimos ao criar nosso próprio conjunto de dados no [Capítulo 5](/course/chapter5). Como o nome sugere, a categoria [Beginners](https://discuss.huggingface.co/c/beginners/5) destina-se principalmente a pessoas que estão começando com as bibliotecas e o ecossistema Hugging Face. Qualquer dúvida sobre qualquer uma das bibliotecas é bem-vinda, seja para depurar algum código ou pedir ajuda sobre como fazer algo. (Dito isso, se sua pergunta diz respeito a uma biblioteca em particular, você provavelmente deve ir para a categoria de biblioteca correspondente no fórum.) + +Da mesma forma, as categorias [Intermediário](https://discuss.huggingface.co/c/intermediate/6) e [Pesquisa](https://discuss.huggingface.co/c/research/7) são para perguntas mais avançadas, por exemplo, sobre as bibliotecas ou alguma nova pesquisa interessante sobre PNL que você gostaria de discutir. + +E, naturalmente, também devemos mencionar a categoria [Curso](https://discuss.huggingface.co/c/course/20), onde você pode tirar todas as suas dúvidas relacionadas ao curso Hugging Face! + +Depois de selecionar uma categoria, você estará pronto para escrever seu primeiro tópico. Você pode encontrar algumas [diretrizes](https://discuss.huggingface.co/t/how-to-request-support/3128) no fórum sobre como fazer isso, e nesta seção veremos algumas características que compõem um bom tópico. + +## Escrevendo uma boa postagem no fórum + +Como exemplo de execução, vamos supor que estamos tentando gerar embeddings de artigos da Wikipedia para criar um mecanismo de pesquisa personalizado. Como de costume, carregamos o tokenizer e o model da seguinte forma: + +```python +from transformers import AutoTokenizer, AutoModel + +model_checkpoint = "distilbert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +model = AutoModel.from_pretrained(model_checkpoint) +``` + +Agora suponha que tentamos incorporar uma seção inteira do [artigo da Wikipédia](https://en.wikipedia.org/wiki/Transformers) em Transformers (a franquia, não a biblioteca!): + +```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 +``` + +Uh-oh, encontramos um problema -- e a mensagem de erro é muito mais enigmática do que as que vimos na [seção 2](/curso/chapter8/section2)! Não podemos fazer cara ou coroa do traceback completo, então decidimos recorrer aos fóruns do Hugging Face para obter ajuda. Como podemos elaborar o tema? + +Para começar, precisamos clicar no botão "Novo Tópico" (em inglês, **new topic**) no canto superior direito (observe que para criar um tópico, precisamos estar logados): + +
+Creating a new forum topic. +
+ +Isso traz uma interface de escrita onde podemos inserir o título do nosso tópico, selecionar uma categoria e redigir o conteúdo: + +
+The interface for creating a forum topic. +
+ +Como o erro parece ser exclusivamente sobre 🤗 Transformers, selecionaremos isso para a categoria. Nossa primeira tentativa de explicar o problema pode ser algo assim: + +
+Drafting the content for a new forum topic. +
+ +Embora este tópico contenha a mensagem de erro para a qual precisamos de ajuda, há alguns problemas com a forma como ela está escrita: + +1. O título não é muito descritivo, então quem estiver navegando no fórum não poderá dizer do que se trata o tópico sem ler também o corpo. +2. O corpo não fornece informações suficientes sobre _onde_ o erro está vindo e _como_ reproduzi-lo. +3. O tópico marca algumas pessoas diretamente com um tom um tanto exigente. + +Tópicos como este provavelmente não terão uma resposta rápida (se conseguirem), então vamos ver como podemos melhorá-lo. Vamos começar com a primeira questão de escolher um bom título. + +### Escolhendo um título descritivo + +Se você estiver tentando obter ajuda com um bug em seu código, uma boa regra geral é incluir informações suficientes no título para que outras pessoas possam determinar rapidamente se acham que podem responder à sua pergunta ou não. Em nosso exemplo em execução, sabemos o nome da exceção que está sendo levantada e temos algumas dicas de que ela é acionada na passagem direta do modelo, onde chamamos `model(**inputs)`. Para comunicar isso, um título possível poderia ser: + +> Source of IndexError in the AutoModel forward pass? + +Este título diz ao leitor _onde_ você acha que o bug está vindo, e se eles encontraram um `IndexError` antes, há uma boa chance de que eles saibam como depurá-lo. Claro, o título pode ser o que você quiser, e outras variações como: + +> Why does my model produce an IndexError? + +também pode ficar bem. Agora que temos um título descritivo, vamos dar uma olhada em como melhorar o corpo. + +### Formatando seus trechos de código + +Ler o código-fonte é bastante difícil em um IDE, mas é ainda mais difícil quando o código é copiado e colado como texto simples! Felizmente, os fóruns do Hugging Face suportam o uso de Markdown, então você deve sempre colocar seus blocos de código com três acentos graves (```) para que seja mais fácil de ler. Vamos fazer isso para embelezar a mensagem de erro - e enquanto estaomos nisso, vamos tornar o corpo um pouco mais educado do que a nossa versão original: + +
+Our revised forum topic, with proper code formatting. +
+ +Como você pode ver na captura de tela, colocar os blocos de código em acentos graves converte o texto bruto em código formatado, completo com estilo de cores! Observe também que backticks simples podem ser usados ​​para formatar variáveis ​​inline, como fizemos para `distilbert-base-uncased`. Este tópico parece muito melhor e, com um pouco de sorte, podemos encontrar alguém na comunidade que possa adivinhar do que se trata o erro. No entanto, em vez de confiar na sorte, vamos facilitar a vida incluindo o rastreamento em todos os seus detalhes sangrentos! + +### Incluindo o rastreamento completo + +Como a última linha do traceback geralmente é suficiente para depurar seu próprio código, pode ser tentador apenas fornecer isso em seu tópico para "economizar espaço". Embora bem intencionado, isso na verdade torna _mais difícil_ para outros depurar o problema, já que as informações que estão mais acima no traceback também podem ser muito úteis. Portanto, uma boa prática é copiar e colar o traceback _inteiro_, certificando-se de que está bem formatado. Como esses rastreamentos podem ser bastante longos, algumas pessoas preferem mostrá-los depois de explicar o código-fonte. Vamos fazer isso. Agora, nosso tópico do fórum se parece com o seguinte: + +
+Our example forum topic, with the complete traceback. +
+ +Isso é muito mais informativo, e um leitor cuidadoso pode apontar que o problema parece ser devido à passagem de uma entrada longa por causa desta linha no traceback: + +> Token indices sequence length is longer than the specified maximum sequence length for this model (583 > 512). + +No entanto, podemos tornar as coisas ainda mais fáceis para eles fornecendo o código real que acionou o erro. Vamos fazer isso agora. + +### Fornecendo um exemplo reproduzível + +Se você já tentou depurar o código de outra pessoa, provavelmente tentou primeiro recriar o problema relatado para poder começar a trabalhar no rastreamento para identificar o erro. Não é diferente quando se trata de obter (ou dar) assistência nos fóruns, então realmente ajuda se você puder fornecer um pequeno exemplo que reproduza o erro. Na metade do tempo, simplesmente fazer este exercício o ajudará a descobrir o que está acontecendo de errado. De qualquer forma, a parte que falta em nosso exemplo é mostrar as _inputs_ que fornecemos ao modelo. Fazer isso nos dá algo como o seguinte exemplo concluído: + +
+The final version of our forum topic. +
+ +Este tópico agora contém muitas informações e foi escrito de uma forma que é muito mais provável de atrair a atenção da comunidade e obter uma resposta útil. Com essas diretrizes básicas, agora você pode criar ótimos tópicos para encontrar as respostas para suas dúvidas sobre 🤗 Transformers! + diff --git a/chapters/ru/_toctree.yml b/chapters/ru/_toctree.yml index edfd8d605..9aef5a09d 100644 --- a/chapters/ru/_toctree.yml +++ b/chapters/ru/_toctree.yml @@ -57,3 +57,12 @@ title: Hugging Face Hub - local: chapter4/2 title: Использование предобученных моделей + - local: chapter4/3 + title: Публикация предобученных моделей в общий доступ + - local: chapter4/4 + title: Создание карточки модели + - local: chapter4/5 + title: Первая часть завершена! + - local: chapter4/6 + title: Итоговый тест по главе + quiz: 4 \ No newline at end of file diff --git a/chapters/ru/chapter4/3.mdx b/chapters/ru/chapter4/3.mdx new file mode 100644 index 000000000..601aa06b5 --- /dev/null +++ b/chapters/ru/chapter4/3.mdx @@ -0,0 +1,634 @@ + + +# Публикация обученых моделей в общий доступ + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Далее мы рассмотрим самые простые способы публикации предварительно обученных моделей в 🤗 Hub: доступные инструменты и утилиты, упрощающие совместное использование и обновление моделей непосредственно в Hub. + + + +Мы призываем всех пользователей, которые обучают модели, вносить свой вклад, делясь ими с сообществом — совместное использование моделей, даже если они обучены на очень конкретных наборах данных, поможет другим, сэкономив им время и вычислительные ресурсы! В свою очередь, вы можете извлечь выгоду из работы, которую проделали другие! + +Есть три пути создания репозитория с моделью: + +- С использованием API `push_to_hub` +- С использованием python-библиотеки `huggingface_hub` +- С использованием веб-интерфейса + +После создания репозитория вы можете загружать в него файлы через git и git-lfs. В следующих разделах мы познакомим вас с созданием репозиториев моделей и загрузкой в них файлов. + +## Использование API `push_to_hub` + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Простейший путь загрузки файлов на Hub – `push_to_hub` API. + +Перед тем, как пойдем дальше, необходимо сгенерировать токен аутентификации. Это необходимо сделать для того, чтобы `huggingface_hub` API «узнал» вас и предоставил вам необходимые права на запись. Убедитесь, что вы находитесь в окружении, в котором установлена библиотека `transformers` (см. [Установка](/course/ru/chapter0)). Если вы работаете в Jupyter'е, вы можете использовать следующую функцию для авторизации: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +В терминале можно запустить: + +```bash +huggingface-cli login +``` + +В обоих случаях вам будет предложено ввести свой логин и пароль (это должны быть те же данные, которые вы используете для входа на Hub). Если у вас нет учетной записи HuggingFace, вы можете создать ее [здесь](https://huggingface.co/join). + +Отлично! После авторизации ваш токен сохранится во временную папку. Давайте создадим репозитории! + +{#if fw === 'pt'} + +Если вы уже пользовались `Trainer` API для обучения модели, то самый простой способ загрузить модель на Hub – установить аргумент `push_to_hub=True` во время инициализации `TrainingArguments`: + +```py +from transformers import TrainingArguments + +training_args = TrainingArguments( + "bert-finetuned-mrpc", save_strategy="epoch", push_to_hub=True +) +``` + +При вызове `trainer.train()` `Trainer` будет загружать модель на Hub каждый раз, когда она будет сохраняться (в данном примере каждую эпоху). Репозиторий будет назван так же, как вы назовете папку для сохранения (в примере это `bert-finetuned-mrpc`), но вы можете задать имя репозитория самостоятельно: задайте аргумент `hub_model_id = "a_different_name"`. + +Для загрузки модели в репозиторий организации, представитем которой вы являетесь, укажите `hub_model_id = "my_organization/my_repo_name"`. + +После окончания обучения следует вызвать метод `trainer.push_to_hub()`, который загрузит последнюю версию вашей модели в репозиторий. Также он сгенерирует карточку модели со всей необходимой информацией, использованными гиперпараметрами и результатами валидации. Ниже приведено то, что вы можете найти в подобной карточке модели: + +
+ An example of an auto-generated model card. +
+ +{:else} + +Если вы используете Keras для обучения моделей, простейший способ загрузить модель на Hub – вызвать callback `PushToHubCallback` при вызове `model.fit()`: + +```py +from transformers import PushToHubCallback + +callback = PushToHubCallback( + "bert-finetuned-mrpc", save_strategy="epoch", tokenizer=tokenizer +) +``` + +Затем необходимо добавить `callbacks=[callback]` в вызов `model.fit()`. Callback будет загружать модель на Hub каждый раз, когда она будет сохраняться (в данном примере каждую эпоху). Репозиторий будет назван так же, как вы назовете папку для сохранения (в примере это `bert-finetuned-mrpc`), но вы можете задать имя репозитория самостоятельно: задайте аргумент `hub_model_id = "a_different_name"`. + +Для загрузки модели в репозиторий организации, представитем которой вы являетесь, укажите `hub_model_id = "my_organization/my_repo_name"`. + +{/if} + +На более низком уровне доступ к Model Hub может быть осуществлен прямо через метод `push_to_hub()`, который можно вызвать у моделей, токенизаторов и конфигурационных объектов. Этот метод отвечает сразу и за создание репозитория, и за загрузку файлов моделей и токенизаторов. Никак + +At a lower level, accessing the Model Hub can be done directly on models, tokenizers, and configuration objects via their `push_to_hub()` method. This method takes care of both the repository creation and pushing the model and tokenizer files directly to the repository. В отличие от API, который мы рассмотрим ниже, здесь никакая ручная обработка не применяется. + +Чтобы понять, как это работает, давайте инициализируем модель и токенизатор: + +{#if fw === 'pt'} +```py +from transformers import AutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = AutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` +{:else} +```py +from transformers import TFAutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = TFAutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` +{/if} + +Вы можете сделать с этими объектами что угодно – измените настройки токенизатора, обучите модель, дообучите предобученную и т.д. После того, как вы получите приемлемый результат, вы можете вызвать `push_to_hub()` прямо у экземпляра модели: + +```py +model.push_to_hub("dummy-model") +``` + +Эта операция создаст новый репозиторий `dummy-model` в вашем профиле и загрузит все необходимые файлы. Сделайте это же с токенизатором: + +```py +tokenizer.push_to_hub("dummy-model") +``` + +Если вы являетесь членом организации, просто укажите `organization` и данные будут загружены в профиль организации: + +```py +tokenizer.push_to_hub("dummy-model", organization="huggingface") +``` + +Если вы хотите использовать какой-то конкретный токен, вы можете указать его в методе `push_to_hub()`: + +```py +tokenizer.push_to_hub("dummy-model", organization="huggingface", use_auth_token="") +``` + +Теперь перейдите в Model Hub и найдите свою модель: *https://huggingface.co/user-or-organization/dummy-model*. + +Выберите вкладку "Files and versions", вы должны увидеть файлы, похожие на приведенные на скриншоте ниже: + +{#if fw === 'pt'} +
+Dummy model containing both the tokenizer and model files. +
+{:else} +
+Dummy model containing both the tokenizer and model files. +
+{/if} + + +✏️ **Попробуйте!** Используйте модель и токенайзер чекпоинта `bert-base-cased` и загрузите их в собственный профиль с помощью метода `push_to_hub()`. Проверьте, что репозиторий корректно создался перед его удалением. + + +Как мы увидели выше, метод `push_to_hub()` поддерживает несколько аргументов, позволяющих загрузить данные в конкретный профиль или профиль организации или использовать конкретный токен. Мы рекомендуем обратить внимание на спецификацию метода, доступную по ссылке [🤗 Transformers documentation](https://huggingface.co/transformers/model_sharing.html), и ознакомиться с остальными возможностями метода. + +Метод `push_to_hub()` реализован с использованием python-пакета [`huggingface_hub`](https://github.com/huggingface/huggingface_hub), который напрямую использует API Hugging Face Hub. Этот пакет интегрирован в 🤗 Transformers и несколько других библиотек машинного обучения, например [`allenlp`](https://github.com/allenai/allennlp). Хотя в этой главе мы сосредоточимся на интеграции с 🤗 Transformers, интегрировать его в ваш собственный код или библиотеку очень просто. + +Перейдите к последнему разделу, чтобы узнать, как загружать файлы в только что созданный репозиторий! + +## Использование библиотеки `huggingface_hub` + +Библиотека `huggingface_hub` - это инструмент, который предлагает наборы различных моделей и датасетов. В ней есть возможность использования простых методов и классов для общих задач, таких как получение информации о репозиториях на хабе и управление ими. Также доступны простые API-интерфейсы, которые работают поверх git для управления содержимым этих репозиториев и интеграции Hub в ваших проектах и библиотеках. + +Как и при использовании API `push_to_hub` необходимо, чтобы ваш токен API был сохранен в кэше. Для этого вам нужно будет использовать команду `login` из CLI, как упоминалось в предыдущем разделе (опять же, убедитесь, что перед этими командами стоит символ `!`, если вы работаете в Google Colab): + +```bash +huggingface-cli login +``` + +Пакет `huggingface_hub` предлагает несколько методов и классов, полезных для наших целей. Во-первых, есть несколько способов управления созданием, удалением и прочего: + +```python no-format +from huggingface_hub import ( + # Пользовательские настройки + login, + logout, + whoami, + + # Создание и управление репозиториями + create_repo, + delete_repo, + update_repo_visibility, + + # И несколько способов для получения или изменения информации о содержимом + list_models, + list_datasets, + list_metrics, + list_repo_files, + upload_file, + delete_file, +) +``` +Кроме того, `huggingface_hub` предлагает очень мощный класс `Repository` для управления локальным хранилищем. Мы рассмотрим эти методы и этот класс в следующих нескольких разделах, чтобы понять, как их использовать. + +Метод `create_repo` можно использовать для создания нового репозитория на хабе: + +```py +from huggingface_hub import create_repo + +create_repo("dummy-model") +``` +Это создаст репозиторий `dummy-model` в вашем пространстве. Если хотите, вы можете указать, какой организации должен принадлежать репозиторий, используя аргумент `organization`: + +```py +from huggingface_hub import create_repo + +create_repo("dummy-model", organization="huggingface") +``` + +Это создаст репозиторий `dummy-model` в пространстве `huggingface`, предполагая, что вы принадлежите к этой организации. +Другие аргументы, которые могут быть полезны: + +- `private`, чтобы указать, должен ли репозиторий быть видимым для других или нет. +- `token`, если вы хотите переопределить токен, хранящийся в вашем кэше, указанным токеном. +- `repo_type`, если вы хотите создать `dataset` или `space` вместо модели. Допустимые значения: `"dataset"` и `"space"`. + +Как только репозиторий создан, мы должны добавить в него файлы! Перейдите к следующему разделу, чтобы увидеть три способа сделать это. + + +## Использование веб-интерфейса + +Веб-интерфейс предлагает инструменты для управления репозиториями прямо в хабе. Используя интерфейс, вы можете легко создавать репозитории, добавлять файлы (даже большие!), исследовать модели, визуализировать различия и многое другое. + +Для создания нового репозитория перейдите по ссылке: [huggingface.co/new](https://huggingface.co/new): + +
+Page showcasing the model used for the creation of a new model repository. +
+ +Во-первых, укажите владельца репозитория: это можете быть как вы, так и любая из организаций, с которыми вы связаны. Если вы выберете организацию, модель будет размещена на странице организации, и каждый член организации сможет внести свой вклад в репозиторий. + +Затем введите название вашей модели. Это также будет имя репозитория. Наконец, вы можете указать, хотите ли вы, чтобы ваша модель была общедоступной или приватной. Приватные модели скрыты от посторонних глаз. + +После создания репозитория моделей вы должны увидеть страницу, подобную этой: + +
+An empty model page after creating a new repository. +
+ +Здесь будет размещена ваша модель. Чтобы начать заполнение репозитория, вы можете добавить файл README прямо из веб-интерфейса. + +
+The README file showing the Markdown capabilities. +
+ +Файл README хранится в формате Markdown! Третья часть этой главы посвящена заполнению карточки модели. Она имеет первостепенное значение для повышения ценности вашей модели, поскольку именно здесь вы рассказываете другим, на что способна модель. + +Если вы посмотрите на вкладку «Файлы и версии», то увидите, что там пока не так много файлов — только только что созданный *README.md* и файл *.gitattributes*, который отслеживает большие файлы. + +
+The 'Files and versions' tab only shows the .gitattributes and README.md files. +
+ +Позже мы посмотрим, как добавить новые файлы. + +## Загрузка файлов модели + +Система управления файлами в Hugging Face Hub основана на git для обычных файлов и git-lfs (что означает [Git Large File Storage](https://git-lfs.github.com/)) для больших файлов. + +В следующем разделе мы рассмотрим три различных способа загрузки файлов в Hub: через `huggingface_hub` и через команды git. + +### Функция `upload_file` + +Использование `upload_file` не требует установки git и git-lfs в вашей системе. Функция отправляет файлы напрямую в 🤗 Hub с помощью HTTP-запросов POST. Ограничение этого подхода заключается в том, что он не обрабатывает файлы размером более 5 ГБ. +Если размер ваших файлов превышает 5 ГБ, воспользуйтесь двумя другими способами, описанными ниже. + +API можно использовать следующим образом: + +```py +from huggingface_hub import upload_file + +upload_file( + "/config.json", + path_in_repo="config.json", + repo_id="/dummy-model", +) +``` + +Это загрузит файл `config.json`, доступный по адресу ``, в корень как `config.json` в репозиторий `dummy-model`. +Другие аргументы, которые могут быть полезны: + +- `token`, если вы хотите переопределить токен, хранящийся в вашем кеше, указанным токеном. +- `repo_type`, если вы хотите загрузить в `dataset` или `space`. Допустимые значения: `"dataset"` и `"space"`. + + +### Класс `Repository` + +Класс `Repository` управляет локальным репозиторием подобно git. Он абстрагирует большинство проблемных моментов, которые могут возникнуть с git, чтобы предоставить все функции, которые нам нужны. + +Использование этого класса требует наличия установленных git и git-lfs, поэтому убедитесь, что у вас установлен и настроен git-lfs (см. [здесь] (https://git-lfs.github.com/) для инструкций по установке), прежде чем начать . + +Чтобы начать использование только что созданного репозитория, его нужно инициализировать в локальной папке путем клонирования удаленного репозитория: + +```py +from huggingface_hub import Repository + +repo = Repository("", clone_from="/dummy-model") +``` + +Этот код создал папку `` в нашем рабочем каталоге. Эта папка содержит только файл `.gitattributes`, поскольку это единственный файл, созданный при создании экземпляра репозитория с помощью `create_repo`. + +С этого момента мы можем использовать несколько традиционных методов git: + +```py +repo.git_pull() +repo.git_add() +repo.git_commit() +repo.git_push() +repo.git_tag() +``` + +и другие. Мы рекомендуем ознакомиться с доступной документацией `Repository` [здесь] (https://github.com/huggingface/huggingface_hub/tree/main/src/huggingface_hub#advanced-programmatic-repository-management) для обзора всех доступных методов. + +В настоящее время у нас есть модель и токенизатор, которые мы хотели бы отправить в хаб. Мы успешно клонировали репозиторий, поэтому мы можем сохранить файлы в этом репозитории. + +Сначала мы убедимся, что наш локальный репозиторий обновлен: запросим оттуда все изменения: + +```py +repo.git_pull() +``` +После того, как это сделано, мы сохраним файлы модели и токенизатора: + +```py +model.save_pretrained("") +tokenizer.save_pretrained("") +``` +`` сейчас содержит пути к модели и токенизатору. Мы последуем обычной процедуре добавления файлов с помощью git, зафиксируем изменения и отправим их в удаленный репозиторий: + +```py +repo.git_add() +repo.git_commit("Add model and tokenizer files") +repo.git_push() +``` + +Поздравляем! Вы только что сделали первый коммит в хаб! + +### git-подход + +Это очень простой подход к загрузке файлов: мы сделаем это напрямую с помощью git и git-lfs. + +Использование этого подхода требует наличия установленных git и git-lfs, поэтому убедитесь, что у вас установлен и настроен [git-lfs](https://git-lfs.github.com/) (см. здесь инструкции по установке), прежде чем начать. + +Начните с инициализации git-lfs: + +```bash +git lfs install +``` + +```bash +Updated git hooks. +Git LFS initialized. +``` + +После инициализации клонируйте репозиторий с моделью. + +Once that's done, the first step is to clone your model repository: + +```bash +git clone https://huggingface.co// +``` + +Мое имя пользователя `lysandre` и я использую модель под названием `dummy`, поэтому команда выглядит следующим образом: + +``` +git clone https://huggingface.co/lysandre/dummy +``` + +Теперь у меня есть папка *dummy* в моей рабочей директории. Командной `cd` я могу перейти в эту директорию и посмотреть на ее содержимое: + +```bash +cd dummy && ls +``` + +```bash +README.md +``` + +Если вы только что создали репозиторий с помощью метода `create_repo` в Hugging Face Hub, эта папка должна содержать только скрытый файл `.gitattributes`. Если вы следовали инструкциям из предыдущего раздела для создания репозитория с помощью веб-интерфейса, папка должна содержать один файл *README.md* вместе со скрытым файлом `.gitattributes`, как показано здесь. + +Добавление файла обычного размера, такого как файл конфигурации, файл словаря или практически любого файла размером менее нескольких мегабайт, выполняется точно так же, как это делается в любой системе на основе git. Однако файлы большего размера должны быть зарегистрированы через git-lfs, тогда появится возможность отправить их на *huggingface.co*. + +Давайте ненадолго вернемся к Python, чтобы сгенерировать модель и токенизатор, которые мы хотели бы зафиксировать в нашем демонстрацинном репозитории: + +{#if fw === 'pt'} +```py +from transformers import AutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = AutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + +# Сделайте с моделью что угодно: обучите с нуля, дообучите и тд + +model.save_pretrained("") +tokenizer.save_pretrained("") +``` +{:else} +```py +from transformers import TFAutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = TFAutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + +# Do whatever with the model, train it, fine-tune it... + +model.save_pretrained("") +tokenizer.save_pretrained("") +``` +{/if} + +Давайте взглянем на нашу директорию после выполненных выше шагов: + +```bash +ls +``` + +{#if fw === 'pt'} +```bash +config.json pytorch_model.bin README.md sentencepiece.bpe.model special_tokens_map.json tokenizer_config.json tokenizer.json +``` + +If you look at the file sizes (for example, with `ls -lh`), you should see that the model state dict file (*pytorch_model.bin*) is the only outlier, at more than 400 MB. + +{:else} +```bash +config.json README.md sentencepiece.bpe.model special_tokens_map.json tf_model.h5 tokenizer_config.json tokenizer.json +``` + +Если вы обратите внимание на размеры файлов (например, с помощью команды `ls -lh`), вы заметите, что файл модели (*t5_model.h5*) является единственным большим файлом, он занимает более 400 Мб. + +{/if} + + +✏️ При создании репозитория с помощью веб-интерфейса, файл *.gitattributes* автоматически фиксирует файлы с определенными расширениями (*.bin* и *.h5*) как большие файлы, и git-lfs отследит их без необходимости делать это вручную. + + +Теперь мы можем продолжить и продолжить, как обычно делаем с традиционными репозиториями Git. Мы можем добавить все файлы в промежуточную среду Git с помощью команды `git add`: + +```bash +git add . +``` + +Затем мы можем взглянуть на файлы, которые в настоящее время размещены: + +```bash +git status +``` + +{#if fw === 'pt'} +```bash +On branch main +Your branch is up to date with 'origin/main'. + +Changes to be committed: + (use "git restore --staged ..." to unstage) + modified: .gitattributes + new file: config.json + new file: pytorch_model.bin + new file: sentencepiece.bpe.model + new file: special_tokens_map.json + new file: tokenizer.json + new file: tokenizer_config.json +``` +{:else} +```bash +On branch main +Your branch is up to date with 'origin/main'. + +Changes to be committed: + (use "git restore --staged ..." to unstage) + modified: .gitattributes + new file: config.json + new file: sentencepiece.bpe.model + new file: special_tokens_map.json + new file: tf_model.h5 + new file: tokenizer.json + new file: tokenizer_config.json +``` +{/if} + +Точно так же мы можем убедиться, что git-lfs отслеживает правильные файлы, используя команду `status`: + +```bash +git lfs status +``` + +{#if fw === 'pt'} +```bash +On branch main +Objects to be pushed to origin/main: + + +Objects to be committed: + + config.json (Git: bc20ff2) + pytorch_model.bin (LFS: 35686c2) + sentencepiece.bpe.model (LFS: 988bc5a) + special_tokens_map.json (Git: cb23931) + tokenizer.json (Git: 851ff3e) + tokenizer_config.json (Git: f0f7783) + +Objects not staged for commit: + + +``` + +Мы видим, что все файлы имеют `Git` в качестве обработчика, кроме *pytorch_model.bin* и *sentencepiece.bpe.model*, у которых есть `LFS`. Отлично! + +{:else} +```bash +On branch main +Objects to be pushed to origin/main: + + +Objects to be committed: + + config.json (Git: bc20ff2) + sentencepiece.bpe.model (LFS: 988bc5a) + special_tokens_map.json (Git: cb23931) + tf_model.h5 (LFS: 86fce29) + tokenizer.json (Git: 851ff3e) + tokenizer_config.json (Git: f0f7783) + +Objects not staged for commit: + + +``` + +Мы видим, что все файлы имеют `Git` в качестве обработчика, кроме *t5_model.h5*, у которых есть `LFS`. Отлично! + +{/if} + +Перейдем к последним шагам - коммиту и отправке в удаленный репозиторий *huggingface.co*: + +```bash +git commit -m "First model version" +``` + +{#if fw === 'pt'} +```bash +[main b08aab1] First model version + 7 files changed, 29027 insertions(+) + 6 files changed, 36 insertions(+) + create mode 100644 config.json + create mode 100644 pytorch_model.bin + create mode 100644 sentencepiece.bpe.model + create mode 100644 special_tokens_map.json + create mode 100644 tokenizer.json + create mode 100644 tokenizer_config.json +``` +{:else} +```bash +[main b08aab1] First model version + 6 files changed, 36 insertions(+) + create mode 100644 config.json + create mode 100644 sentencepiece.bpe.model + create mode 100644 special_tokens_map.json + create mode 100644 tf_model.h5 + create mode 100644 tokenizer.json + create mode 100644 tokenizer_config.json +``` +{/if} + +Отправка может занять некоторое время, в зависимости от скорости вашего интернет-соединения и размера ваших файлов: + +```bash +git push +``` + +```bash +Uploading LFS objects: 100% (1/1), 433 MB | 1.3 MB/s, done. +Enumerating objects: 11, done. +Counting objects: 100% (11/11), done. +Delta compression using up to 12 threads +Compressing objects: 100% (9/9), done. +Writing objects: 100% (9/9), 288.27 KiB | 6.27 MiB/s, done. +Total 9 (delta 1), reused 0 (delta 0), pack-reused 0 +To https://huggingface.co/lysandre/dummy + 891b41d..b08aab1 main -> main +``` + +{#if fw === 'pt'} +Если мы посмотрим на репозиторий модели после завершения отправки, мы увидим все недавно добавленные файлы: + +
+The 'Files and versions' tab now contains all the recently uploaded files. +
+ +Интерфейс позволяет вам исследовать файлы моделей и коммиты, а также видеть разницу, представленную каждым коммитом: + +
+The diff introduced by the recent commit. +
+{:else} +Если мы посмотрим на репозиторий модели после завершения отправки, мы увидим все недавно добавленные файлы: + +
+The 'Files and versions' tab now contains all the recently uploaded files. +
+ +Интерфейс позволяет вам исследовать файлы моделей и коммиты, а также видеть разницу, представленную каждым коммитом: + +
+The diff introduced by the recent commit. +
+{/if} diff --git a/chapters/ru/chapter4/4.mdx b/chapters/ru/chapter4/4.mdx new file mode 100644 index 000000000..ba506976f --- /dev/null +++ b/chapters/ru/chapter4/4.mdx @@ -0,0 +1,82 @@ +# Создание карточки модели + +Карточка модели — это файл, который, возможно, так же важен, как файлы модели и токенизатора в репозитории моделей. Это центральное описание модели, обеспечивающее возможность повторного использования другими членами сообщества и воспроизводимость результатов, а также предоставляющее платформу для других участников. + +Документирование процесса обучения и оценки помогает другим понять, чего ожидать от модели, а предоставление достаточной информации об использованных данных, а также о проведенной предварительной и постобработке. По карточке модели ясны ограничения, предубеждения и контексты, в которых модель может быть полезна, а в каких случаях окажется бесполезной. + +Поэтому создание карточки модели, которая четко определяет вашу модель, является очень важным шагом. Здесь мы даем несколько советов, которые помогут вам в этом. Карточка модели создается с помощью файла *README.md*, который вы видели ранее, который представляет собой файл Markdown. + +Концепция «карточки модели» возникла в результате исследовательского направления Google, впервые представленного в статье ["Model Cards for Model Reporting"] (https://arxiv.org/abs/1810.03993) Маргарет Митчелл и др. Большая часть информации, содержащейся здесь, основана на этом документе, и мы рекомендуем вам ознакомиться с ним, чтобы понять, почему карточки с моделями так важны в мире, который ценит воспроизводимость, возможность повторного использования и честность. + +Карточка модели обычно начинается с очень краткого общего обзора того, для чего предназначена модель, за которым следуют дополнительные сведения в следующих разделах: + +- Описание модели +- Предполагаемое использование и ограничения +- Как использовать +- Ограничения и предубеждения +- Тренировочные данные +- Процедура обучения +- Результаты оценки + +Давайте посмотрим, что должен содержать каждый из этих разделов. + +### Описание модели + +Описание содержит основные сведения о модели. Оно включает в себя архитектуру, версию, если модель была представлена в статье - автора, ссылку на оригинальную реализацию (если доступна), автора и общую информацию о модели. Любые авторские права должны быть указаны здесь. В этом разделе также можно упомянуть общую информацию о процедурах обучения, параметрах и важных отказах от ответственности. + +### Предполагаемое использование и ограничения + +Здесь вы описываете варианты использования, для которых предназначена модель, включая языки, области и домены, в которых она может применяться. В этом разделе карты модели также можно документировать те области, которые являются неподходящими для модели. + +### Как использовать + +Этот раздел должен включать несколько примеров того, как использовать модель. Это может быть продемонстрировано с использованием функции `pipeline()`, использованием классов модели и токенизатора, а также любым другим способом, удобным на ваш взгляд. + +### Обучающие данные + +В этой части должно быть указано, на каком наборе данных обучалась модель. Также приветствуется краткое описание набора(ов) данных. + +### Процедура обучения + +В этом разделе вы должны описать все важные аспекты обучения, которые полезны с точки зрения воспроизводимости. Раздел включает в себя любую предварительную и постобработку данных, а также такие детали, как количество эпох, на которых была обучена модель, размер батча, скорость обучения и т. д. + +### Variable and metrics + +Здесь вы должны описать метрики, которые вы используете для оценки, и прочие величины, которые вы замеряете. Напишите, какие метрики использовались, в каком датасете и какое разделение разбиение датасета позволяет легко сравнивать производительность вашей модели с другими моделями. + +### Результаты валидации + +Наконец, укажите, насколько хорошо модель работает с набором данных для оценки. Если в модели используется порог принятия решения (threshold)– укажите его, либо предоставьте подробные сведения об оценке при различных порогах для предполагаемого использования. + +## Пример + +Ознакомьтесь с несколькими примерами хорошо сделанных карточек моделей: + +- [`bert-base-cased`](https://huggingface.co/bert-base-cased) +- [`gpt2`](https://huggingface.co/gpt2) +- [`distilbert`](https://huggingface.co/distilbert-base-uncased) + +Больше примеров от других организаций и компаний доступны: [здесь](https://github.com/huggingface/model_card/blob/master/examples.md). + +## Примечание + +Карточки моделей не являются обязательным требованием при публикации моделей, и вам не нужно включать все разделы, описанные выше, при их создании. Однако подробное документирование модели может принести только пользу будущим пользователям, поэтому мы рекомендуем вам заполнить как можно больше разделов в меру своих знаний и способностей. + +## Метаданные карточки модели + +Если вы немного изучили Hugging Face Hub, вы должны были заметить, что некоторые модели относятся к определенным категориям: вы можете фильтровать их по задачам, языкам, библиотекам и т. д. Категории, к которым принадлежит модель, идентифицируются в соответствии с метаданными, которые вы добавляете в заголовок карточки модели. + +Например, если вы посмотрите на [карточку модели `camembert-base`](https://huggingface.co/camembert-base/blob/main/README.md), вы должны увидеть следующие строки в заголовке карточки модели: + +``` +--- +language: fr +license: mit +datasets: +- oscar +--- +``` + +Эти метаданные анализируются Hugging Face Hub, который затем идентифицирует эту модель как французскую модель с лицензией MIT, обученную на наборе данных Oscar. + +[Полная спецификация карточки модели](https://github.com/huggingface/hub-docs/blame/main/modelcard.md) позволяет указать языкы, лицензии, теги, датасеты, метрики, а также результаты валидации модели. diff --git a/chapters/ru/chapter4/5.mdx b/chapters/ru/chapter4/5.mdx new file mode 100644 index 000000000..58ab3e1ac --- /dev/null +++ b/chapters/ru/chapter4/5.mdx @@ -0,0 +1,7 @@ +# Первая часть завершена! + +Вот и закончилась первая часть курса! Часть 2 будет выпущена 15 ноября вместе с большим событием для сообщества, дополнительную информацию см. [здесь] (https://huggingface.co/blog/course-launch-event). + +Теперь вы сможете точно настроить предварительно обученную модель для задачи классификации текста (одно предложение или пара предложений) и загрузить результат в Model Hub. Чтобы убедиться, что вы усвоили этот первый раздел, вы должны сделать именно это по интересующей вас проблеме (и не обязательно на английском языке, если вы говорите на другом языке)! Вы можете найти помощь на [форумах Hugging Face](https://discuss.huggingface.co/) и поделиться своим проектом в [этой теме](https://discuss.huggingface.co/t/share-your-projects /6803)! + +Нам не терпится увидеть, как вы используете свои знания! diff --git a/chapters/ru/chapter4/6.mdx b/chapters/ru/chapter4/6.mdx new file mode 100644 index 000000000..ccbf6425f --- /dev/null +++ b/chapters/ru/chapter4/6.mdx @@ -0,0 +1,223 @@ + + + + +# Итоговый тест по главе + +Проверим, что вы усвоили в результате изучения данной главы! + +### 1. Чем ограничиваются модели с Hub? + + + +### 2. Как можно управлять моделями на Hub? + +git-lfs для больших файлов.", + correct: true + } + ]} +/> + +### 3. Что вы можете сделать, используя веб-интерфейс Hugging Face Hub? + + + +### 4. Что такое карточка модели? + + + +### 5. Какие из этих объектов библиотеки 🤗 Transformers могут быть напрямую выложены на Hub с помощью функции `push_to_hub()`? + +{#if fw === 'pt'} +push_to_hub, его применение отправит все файлы токенизатора (словарь, архитектуру и пр.) в указанный репозиторий. Тем не менее, это не единственный верный ответ!", + correct: true + }, + { + text: "Конфигурация модели", + explain: "Верно! Все конфигурации моделей обладают методом push_to_hub, его применение отправит необходимые файлы в указанный репозиторий. Тем не менее, это не единственный верный ответ!", + correct: true + }, + { + text: "Модель", + explain: "Верно! Все модели обладают методом push_to_hub, его применение отправит сответствующие файлы и конфигурации в указанный репозиторий. Но это не всё, чем вы можете поделиться!", + correct: true + }, + { + text: "Экземпляр Trainer", + explain: "Правильно: Trainer также обладает методом push_to_hub, его применение загрузит модель, конфигурацию, токенизатор и черновик карточки модели в указанный репозиторий. Попробуйте и другие ответы!", + correct: true + } + ]} +/> +{:else} +push_to_hub, его применение отправит все файлы токенизатора (словарь, архитектуру и пр.) в указанный репозиторий. Тем не менее, это не единственный верный ответ!", + correct: true + }, + { + text: "Конфигурация модели", + explain: "Верно! Все конфигурации моделей обладают методом push_to_hub, его применение отправит необходимые файлы в указанный репозиторий. Тем не менее, это не единственный верный ответ!", + correct: true + }, + { + text: "Модель", + explain: "Верно! Все модели обладают методом push_to_hub, его применение отправит сответствующие файлы и конфигурации в указанный репозиторий. Но это не всё, чем вы можете поделиться!", + correct: true + }, + { + text: "Все вышеперечисленной с помощью специального callback", + explain: "Верно: PushToHubCallback будет регулярно отсылать все объекты в репозиторий во время обучения.", + correct: true + } + ]} +/> +{/if} + +### 6. Какой первый шаг при использовани `push_to_hub()` метода или инструментов командной строки? + + + +### 7.Если вы используете модель и токенизатор – как вы можете загрузить их на Hub? + +huggingface_hub утилиту.", + explain: "Модели и токенизаторы уже используют утилиты huggingface_hub: нет необходимости в дополнительных утилитах!" + }, + { + text: "Сохранив их на диск и вызвав transformers-cli upload-model", + explain: "Команды upload-model не существует." + } + ]} +/> + +### 8. Какие операции git вы можете проводить с экземпляром класса `Repository`? + +git_commit() метод как раз для этого.", + correct: true + }, + { + text: "Pull", + explain: "Это предназначение метода git_pull().", + correct: true + }, + { + text: "Push", + explain: "Метод git_push() делает это.", + correct: true + }, + { + text: "Merge", + explain: "Нет, такая операция невозможная с данным API." + } + ]} +/> diff --git a/chapters/th/_toctree.yml b/chapters/th/_toctree.yml index d1f0a8f45..5139c38b7 100644 --- a/chapters/th/_toctree.yml +++ b/chapters/th/_toctree.yml @@ -87,4 +87,21 @@ - local: chapter6/2 title: การเทรน tokenizer จาก tokenizer ที่มีอยู่แล้ว - local: chapter6/3 - title: ความสามารถพิเศษของตัวตัดคำแบบเร็ว (fast tokenizers) \ No newline at end of file + title: ความสามารถพิเศษของตัวตัดคำแบบเร็ว (fast tokenizers) + - local: chapter6/3b + title: การใช้งานตัวตัดคำแบบเร็ว (Fast tokenizers) ใน QA pipeline + - local: chapter6/4 + title: Normalization และ pre-tokenization + - local: chapter6/5 + title: Byte-Pair Encoding tokenization + - local: chapter6/6 + title: WordPiece tokenization + - local: chapter6/7 + title: Unigram tokenization + - local: chapter6/8 + title: การสร้าง tokenizer ทีละขั้นตอน + - local: chapter6/9 + title: เรียนจบเรื่อง tokenizer แล้ว! + - local: chapter6/10 + title: คำถามท้ายบท + quiz: 6 \ No newline at end of file diff --git a/chapters/th/chapter6/10.mdx b/chapters/th/chapter6/10.mdx new file mode 100644 index 000000000..82890bc9b --- /dev/null +++ b/chapters/th/chapter6/10.mdx @@ -0,0 +1,278 @@ + + +# คำถามท้ายบท + +มาทดสอบความรู้ที่คุณได้เรียนในบทนี้กันเถอะ! + +### 1. สถานการณ์ไหนที่คุณควรจะเทรน tokenizer ขึ้นมาใหม่? + + + +### 2. เวลาใช้ `train_new_from_iterator()` อะไรคือข้อดีของการใช้ generator of lists of texts เทียบกับการใช้ list of lists of texts? + +train_new_from_iterator() สามารถใช้ได้", + explain: "list of lists of texts เป็น generator ประเภทหนึ่ง ดังนั้น method นี้สามารถใช้มันได้เช่นกัน ลองดูใหม่นะ!" + }, + { + text: "เพื่อป้องกันไม่ให้คุณต้องโหลดชุดข้อมูลทั้งหมด ลงไปใน memory ภายในครั้งเดียว", + explain: "ถูกต้อง! แต่ละ batch ของข้อความ จะถูกปล่อยออกจาก memory เวลาที่คุณ iterate มัน คุณจะเห็นประโยชน์ของการทำแบบนี้ได้ชัดเจนยิ่งขึ้น เวลาที่คุณใช้ 🤗 Datasets เพื่อเก็บข้อความ", + correct: true + }, + { + text: "ทำให้ 🤗 Tokenizers library สามารถใช้ multiprocessing ได้", + explain: "ไม่ถูก เพราะมันจะใช้ multiprocessing ในทั้งสองกรณี" + }, + { + text: "tokenizer จะสามารถผลิตข้อความได้ดีขึ้น", + explain: "tokenizer ไม่สามารถผลิตข้อความได้ -- คุณอาจจะกำลังสับสนมันกับ language model หรือเปล่า" + } + ]} +/> + +### 3. อะไรคือข้อดีของ "fast" tokenizer? + + + +### 4. `token-classification` pipeline มีวิธีจัดการกับ entity ที่ประกอบไปด้วยหลายๆ token ได้อย่างไร? + + + +### 5. `question-answering` pipeline มีวิธีจัดการกับข้อความส่วนบริบท(context)ที่มีขนาดยาวอย่างไร? + + + +### 6. อะไรคือ normalization? + + + +### 7. อะไรคือขั้นตอนการ pre-tokenization ของ subword tokenizer? + + + +### 8. เลือกข้อความที่ถูกต้อง เกี่ยวกับ BPE model? + + + +### 9. เลือกข้อความที่ถูกต้อง เกี่ยวกับ WordPiece model? + + + +### 10. เลือกข้อความที่ถูกต้อง เกี่ยวกับ Unigram model? + + diff --git a/chapters/th/chapter6/2.mdx b/chapters/th/chapter6/2.mdx index ff21da829..f36d7bb1b 100644 --- a/chapters/th/chapter6/2.mdx +++ b/chapters/th/chapter6/2.mdx @@ -166,7 +166,7 @@ old_tokenizer = AutoTokenizer.from_pretrained("gpt2") ``` ถึงแม้ว่าเป้าหมายของเราคือการเทรน tokenizer ใหม่ เราจะเริ่มต้นด้วยการโหลด tokenizer ที่ถูกเทรนมาแล้ว เพื่อที่เราจะได้ไม่ต้องเริ่มกระบวนการทั้งหมดตั้งแต่แรก -ข้อดีของการทำแบบนี้ก็คือ คุณไม่ต้องเสียเวลาตั้งค่าต่างๆ เช่น ประเภทอัลกอรึทึมของ tokenizer หรือ token พิเศษต่างๆ tokenizer ตัวใหม่ของเราจะมีโครงสร้างเหมือนกับตัวที่ใช้ใน GPT-2 สิ่งเดียวที่แตกต่างคือชุดคำศัพท์(vocabulary) ซึ่งจะเปลี่ยนไปตามชุดข้อมูลใหม่ที่เราจะใช้ +ข้อดีของการทำแบบนี้ก็คือ คุณไม่ต้องเสียเวลาตั้งค่าต่างๆ เช่น ประเภทอัลกอริทึมของ tokenizer หรือ token พิเศษต่างๆ tokenizer ตัวใหม่ของเราจะมีโครงสร้างเหมือนกับตัวที่ใช้ใน GPT-2 สิ่งเดียวที่แตกต่างคือชุดคำศัพท์(vocabulary) ซึ่งจะเปลี่ยนไปตามชุดข้อมูลใหม่ที่เราจะใช้ ก่อนอื่นมาดูกันว่า tokenizer ที่เราเพิ่งโหลดมา จะแบ่งข้อความตัวอย่างข้างล่างอย่างไร : @@ -185,7 +185,7 @@ tokens ``` tokenizer นี้มีการใช้สัญลักษณ์พิเศษ เช่น `Ġ` ซึ่งเอาไว้แทนช่องว่าง (space) และ `Ċ` ซึ่งแทนการเริ่มบรรทัดใหม่ (newline) -เราจะเห็นว่า ผลลัพธ์ของการตัดคำไม่ค่อยจะดีนัก เพราะว่าช่องว่างที่อยู่ต่อกันนั้น ถูกแบ่งออกเป็นอย่างละ token ซึ่งจริงๆแล้วการแบ่งที่ดีกว่านี้คือ ช่องว่างที่อยู่ติดกันควรจะถูกรวมให้เป็น token เดียว (เพราะว่าการพิมช่องว่าง 4 หรือ 8 ครั้ง เป็นสิ่งที่พบได้ทั่วไปในการเขียนโค้ด) +เราจะเห็นว่า ผลลัพธ์ของการตัดคำไม่ค่อยจะดีนัก เพราะว่าช่องว่างที่อยู่ต่อกันนั้น ถูกแบ่งออกเป็นอย่างละ token ซึ่งจริงๆแล้วการแบ่งที่ดีกว่านี้คือ ช่องว่างที่อยู่ติดกันควรจะถูกรวมให้เป็น token เดียว (เพราะว่าการพิมพ์ช่องว่าง 4 หรือ 8 ครั้ง เป็นสิ่งที่พบได้ทั่วไปในการเขียนโค้ด) นอกจากนั้น tokenizer นี้ยังแบ่งชื่อฟังก์ชันได้ไม่ดีเท่าไหร่ เหมือนกับว่ามันไม่คุ้นเคยกับสัญลักษณ์ `_` ทำให้ชื่อฟังก์ชันถูกแยกออกเป็นสี่ส่วน @@ -202,8 +202,8 @@ tokenizer = old_tokenizer.train_new_from_iterator(training_corpus, 52000) คุณจะได้เห็นในบทต่อไปว่า 🤗 Transformers library มี tokenizer สองประเภท ประเภทแรกคือตัวที่เขียนด้วย Python ล้วน และประเภทที่สอง(แบบเร็ว)ที่สร้างจาก 🤗 Tokenizers library ซึ่งใช้ภาษา [Rust](https://www.rust-lang.org) ในการเขียน แม้ว่า Python จะเป็นภาษาที่ได้รับความนิยมมากที่สุดในงานด้าน data science และ deep learning แต่ถ้าเราต้องการประมวลผลข้อมูลให้รวดเร็วมากขึ้น โดยใช้การประมวลผลแบบ parallel (หมายถึง ประมวลผลหลายๆงานพร้อมๆกัน) เราจำเป็นต้องเขียนโปรแกรมด้วยภาษาอื่น -ตัวอย่างเช่น การคูณเมทริกซ์ ซึ่งถือเป็นการคำนวนหลักในการประมวลผลของโมเดลประเภท neural network โค้ดส่วนนี้จะถูกเขียนด้วยภาษา CUDA ซึ่งเป็น C library ที่ถูกพัฒนาให้เหมาะกับการใช้งานร่วมกับ GPU -หากเราเขียนโปรแกรมสำหรับเทรน tokenizer ด้วย Python อย่างเดียว จะทำให้การคำนวนช้ามาก นี่คือเหตุผลที่ Huggingface สร้าง 🤗 Tokenizers library ขึ้นมา +ตัวอย่างเช่น การคูณเมทริกซ์ ซึ่งถือเป็นการคำนวณหลักในการประมวลผลของโมเดลประเภท neural network โค้ดส่วนนี้จะถูกเขียนด้วยภาษา CUDA ซึ่งเป็น C library ที่ถูกพัฒนาให้เหมาะกับการใช้งานร่วมกับ GPU +หากเราเขียนโปรแกรมสำหรับเทรน tokenizer ด้วย Python อย่างเดียว จะทำให้การคำนวณช้ามาก นี่คือเหตุผลที่ Huggingface สร้าง 🤗 Tokenizers library ขึ้นมา แต่ไม่ต้องกังวลกับส่วนนี้ เพราะคุณไม่จำเป็นต้องรู้ภาษา Rust เพื่อจะใช้งานตัวตัดคำแบบเร็วนี้ เหมือนกับที่คุณไม่จำเป็นต้องรู้ภาษา CUDA เพื่อจะรันโมเดลบน GPU @@ -292,7 +292,7 @@ notebook_login() ``` หลังจากคุณรันโค้ดข้างบน คุณจะเห็น widget ให้ล็อกอินเข้าบัญชี Hugging Face -แต่หากคุณไม่ได้ใช้ notebook ให้พิมคำสั่งข้างล่างนี้ใน terminal +แต่หากคุณไม่ได้ใช้ notebook ให้พิมพ์คำสั่งข้างล่างนี้ใน terminal ```bash huggingface-cli login @@ -310,4 +310,4 @@ tokenizer.push_to_hub("code-search-net-tokenizer") tokenizer = AutoTokenizer.from_pretrained("huggingface-course/code-search-net-tokenizer") ``` -มาถึงตอนนี้คุณก็พร้อมแล้วที่จะเทรน และ fine-tune language model สำหรับงานที่คุณต้องการ เราจะเรียนเรื่องกันนี้ใน[บทที่ 7](/course/chapter7) แต่ในบทนี้ เราจะเรียนเกี่ยวกับ fast tokenizer ให้ละเอียดมากขึ้นและมาดูกันว่า เวลาคุณรัน `train_new_from_iterator()` มีการคำนวนอะไรเกิดขึ้นบ้าง \ No newline at end of file +มาถึงตอนนี้คุณก็พร้อมแล้วที่จะเทรน และ fine-tune language model สำหรับงานที่คุณต้องการ เราจะเรียนเรื่องกันนี้ใน[บทที่ 7](/course/chapter7) แต่ในบทนี้ เราจะเรียนเกี่ยวกับ fast tokenizer ให้ละเอียดมากขึ้นและมาดูกันว่า เวลาคุณรัน `train_new_from_iterator()` มีการคำนวณอะไรเกิดขึ้นบ้าง \ No newline at end of file diff --git a/chapters/th/chapter6/3.mdx b/chapters/th/chapter6/3.mdx index c6c5ebd20..8f7f6c52f 100644 --- a/chapters/th/chapter6/3.mdx +++ b/chapters/th/chapter6/3.mdx @@ -26,7 +26,7 @@ ในบทก่อนๆ คุณได้ลองใช้ tokenizer เพื่อแยกข้อความให้เป็นคำๆ และเพื่อแปลง ID ของคำให้กลับไปเป็นข้อความแล้ว จริงๆแล้ว tokenizer นั้นยังมีความสามารถอีกหลายอย่าง โดยเฉพาะ tokenizer จาก 🤗 Tokenizers library -เพื่อให้คุณเห็นภาพได้อย่างชัดเจน เราจะมาลองคำนวนผลลัพธ์ (reproduce) ของ `token-classification` (ซึ่งเราจะเรียกสั้นๆว่า `ner`) และสร้าง pipeline สำหรับ `question-answering` อย่างที่คุณได้เรียนมาแล้ว[บทที่ 1](/course/chapter1)กัน +เพื่อให้คุณเห็นภาพได้อย่างชัดเจน เราจะมาลองคำนวณผลลัพธ์ (reproduce) ของ `token-classification` (ซึ่งเราจะเรียกสั้นๆว่า `ner`) และสร้าง pipeline สำหรับ `question-answering` อย่างที่คุณได้เรียนมาแล้ว[บทที่ 1](/course/chapter1)กัน @@ -149,7 +149,7 @@ Sylvain ``` อย่างที่เราได้บอกข้างต้นแล้ว fast tokenizer สามารถทำแบบนี้ได้ เพราะมันเก็บข้อมูลเกี่ยวกับ span ของแต่ละ token เอาไว้ และบันทึกไว้ใน list ของ *offsets* -เพื่อที่จะอธิบายการใช้งานของ feature นี้ เรามาลองคำนวนผลลัพธ์ของ pipeline `token-classification` กัน +เพื่อที่จะอธิบายการใช้งานของ feature นี้ เรามาลองคำนวณผลลัพธ์ของ pipeline `token-classification` กัน @@ -175,10 +175,10 @@ Sylvain {/if} -### การคำนวนผลลัพธ์เบื้องต้นด้วยการใช้ pipeline +### การคำนวณผลลัพธ์เบื้องต้นด้วยการใช้ pipeline อันดับแรก เราจะใช้ token classification pipeline เพื่อเปรียบเทียบกับ pipeline -ของเรา โมเดลที่ถูกตั้งเป็นค่าเบื้องต้นคือ [`dbmdz/bert-large-cased-finetuned-conll03-english`](https://huggingface.co/dbmdz/bert-large-cased-finetuned-conll03-english) ซึ่งมันจะคำนวน NER ของแต่ละ ข้อความ input: +ของเรา โมเดลที่ถูกตั้งเป็นค่าเบื้องต้นคือ [`dbmdz/bert-large-cased-finetuned-conll03-english`](https://huggingface.co/dbmdz/bert-large-cased-finetuned-conll03-english) ซึ่งมันจะคำนวณ NER ของแต่ละ ข้อความ input: ```py from transformers import pipeline @@ -214,12 +214,12 @@ token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.") {'entity_group': 'LOC', 'score': 0.99321055, 'word': 'Brooklyn', 'start': 49, 'end': 57}] ``` -`aggregation_strategy` ที่เราเลือก จะเปลี่ยนคำแนนของแต่ละกลุ่ม entity ด้วย -ถ้าเราตั้งค่าให้เป็นแบบ `"simple"` มันจะคำนวนคะแนน โดยการเฉลี่ยคะแนนของแต่ละ token ที่นับเป็น entity เดียวกัน ตัวอย่างเช่น คะแนนของคำว่า "Sylvain" คือค่าเฉลี่ยของคะแนนจาก token ย่อยซึ่งก็คือ `S`, `##yl`, `##va`, and `##in` +`aggregation_strategy` ที่เราเลือก จะเปลี่ยนคะแนนของแต่ละกลุ่ม entity ด้วย +ถ้าเราตั้งค่าให้เป็นแบบ `"simple"` มันจะคำนวณคะแนน โดยการเฉลี่ยคะแนนของแต่ละ token ที่นับเป็น entity เดียวกัน ตัวอย่างเช่น คะแนนของคำว่า "Sylvain" คือค่าเฉลี่ยของคะแนนจาก token ย่อยซึ่งก็คือ `S`, `##yl`, `##va`, and `##in` -วิธีคำนวนคะแนนรวมแบบอื่น : +วิธีคำนวณคะแนนรวมแบบอื่น : -- `"first"` จะใช้คะแนนของ token แรกเท่านั้น เป็นคำแนนรวม (เช่น คะแนนรวมของคำว่า "Sylvain" ก็จะเป็น 0.993828 ซึ่งมาจากคะแนนของ `S`) +- `"first"` จะใช้คะแนนของ token แรกเท่านั้น เป็นคะแนนรวม (เช่น คะแนนรวมของคำว่า "Sylvain" ก็จะเป็น 0.993828 ซึ่งมาจากคะแนนของ `S`) - `"max"` จะใช้คะแนนของ token ที่มีคะแนนมากที่สุด (เช่น คะแนนรวมของคำว่า "Hugging Face" ก็จะเป็น 0.98879766 ซึ่งมาจากคะแนนของ "Face") - `"average"` จะใช้ค่าเฉลี่ยของแต่ละ token ที่เป็นส่วนของ entity นั้น เป็นคะแนนรวมของ entity (สำหรับคำว่า "Sylvain" คะแนนรวมแบบเฉลี่ยจะไม่ต่างจากคะแนนรวมแบบ `"simple"` แต่คำว่า "Hugging Face" จำได้คะแนน 0.9819 ซึ่งเป็นค่าเฉลี่ย ของ "Hugging" 0.975 และ "Face" 0.98879) @@ -285,7 +285,7 @@ print(outputs.logits.shape) {/if} แต่ละ batch ประกอบไปด้วย 1 ข้อความ ซึ่งมี token 19 ตัว และโมเดลที่เราใช้ สามารถทำนายได้ 9 หมวด (label) ดังนั้นขนาดของ output ที่ได้คือ 1 x 19 x 9 -เช่นเดียวกับตอนที่เราใช้ text classification pipeline คือเราจะใช้ฟังก์ชัน softmax เพื่อที่จะแปลงค่า logits ไปเป็นค่าความเป็นไปได้ (probabilities) จากนั้นเราจะคำนวนค่า argmax เพื่อคำนวนคำทำนายสุดท้าย (เราใช้ argmax ของค่า logits ตรงนี้ได้ ก็เพราะการคำนวน softmax จากคะแนนของแต่ละหมวด ไม่ได้ทำให้ลำดับของหมวดเปลี่ยน) +เช่นเดียวกับตอนที่เราใช้ text classification pipeline คือเราจะใช้ฟังก์ชัน softmax เพื่อที่จะแปลงค่า logits ไปเป็นค่าความเป็นไปได้ (probabilities) จากนั้นเราจะคำนวณค่า argmax เพื่อคำนวณคำทำนายสุดท้าย (เราใช้ argmax ของค่า logits ตรงนี้ได้ ก็เพราะการคำนวณ softmax จากคะแนนของแต่ละหมวด ไม่ได้ทำให้ลำดับของหมวดเปลี่ยน) {#if fw === 'pt'} @@ -350,7 +350,7 @@ IOB1 (สีส้ม) เป็นแบบที่เราใช้ในต
-ตอนนี้เราก็พร้อมที่จะคำนวนผลลัพธ์ ให้ได้แบบเดียวกับ pipeline แรกแล้ว โดยที่เราจะใช้คะแนนและ label ของแต่ละ token ที่ไม่ใช่ `O`เท่านั้น +ตอนนี้เราก็พร้อมที่จะคำนวณผลลัพธ์ ให้ได้แบบเดียวกับ pipeline แรกแล้ว โดยที่เราจะใช้คะแนนและ label ของแต่ละ token ที่ไม่ใช่ `O`เท่านั้น ```py results = [] @@ -378,7 +378,7 @@ print(results) ``` จะเห็นว่าตอนนี้ เราได้ผลลัพธ์ที่คล้ายกับผลลัพธ์จาก pipeline ก่อนหน้านี้แล้ว ข้อแตกต่างเดียวก็คือ ผลลัพธ์จาก pipeline จะให้ข้อมูลเกี่ยวกับ ตำแหน่งเริ่มและจบในข้อความของแต่ละ entity ด้วย -ขั้นตอนต่อไป เราจะได้เรียกใช้ค่า offset mapping เพื่อตั้งค่าให้โมเดลคำนวนค่า offset เราจะเช็ต `return_offsets_mapping=True` ในตอนที่เราใช้รันตัวตัดคำ +ขั้นตอนต่อไป เราจะได้เรียกใช้ค่า offset mapping เพื่อตั้งค่าให้โมเดลคำนวณค่า offset เราจะเช็ต `return_offsets_mapping=True` ในตอนที่เราใช้รันตัวตัดคำ ```py @@ -405,7 +405,7 @@ example[12:14] yl ``` -เราจะใช้วิธีนี้ เพื่อคำนวนผลลัพธ์ให้ได้ผลลัพธ์เหมือนใน pipeline: +เราจะใช้วิธีนี้ เพื่อคำนวณผลลัพธ์ให้ได้ผลลัพธ์เหมือนใน pipeline: ```py results = [] @@ -452,7 +452,7 @@ print(results) แล้วให้รวม token ที่สามโดยใช้ช่องว่างในการเชื่อม เพราะ `Face` ไม่ได้เริ่มต้นด้วย `##` อย่างไรก็ตามวิธีนี้ ใช้ได้แค่กับตัวตัดคำบางประเภทเท่านั้น สำหรับตัวตัดคำอื่นๆเช่น แบบ SentencePiece หรือ Byte-Pair-Encoding เราก็จะต้องสร้างกฎขึ้นมาใหม่ -การที่เราใช้ค่า offset ทำให้เราไม่จำเป็นต้องเขียนโค้ดเกี่ยวกับกฎพวกนี้ขึ้นมาเอง คุณสามารถใช้ตำแหน่งเริ่มของ token แรก และ ตำแหน่งจบของ token สุดท้าย เป็นค่าในการ slice ข้อความ input เพื่อที่จะคำนวนส่วนของข้อความของ entity ที่คุณสนใจ +การที่เราใช้ค่า offset ทำให้เราไม่จำเป็นต้องเขียนโค้ดเกี่ยวกับกฎพวกนี้ขึ้นมาเอง คุณสามารถใช้ตำแหน่งเริ่มของ token แรก และ ตำแหน่งจบของ token สุดท้าย เป็นค่าในการ slice ข้อความ input เพื่อที่จะคำนวณส่วนของข้อความของ entity ที่คุณสนใจ ตัวอย่างเช่น ถ้าเรามี 3 token `Hu`, `##gging`, และ `Face` ซึ่งอยู่ในกลุ่ม entity เดียวกัน เราก็จะใช้ค่า 33 (ตำแหน่งเริ่มของ `Hu`) เป็นจุดเริ่มต้น และ 45 (ตำแหน่งจบของ `Hu`) เป็นจุดจบของ entity นี้ diff --git a/chapters/th/chapter6/3b.mdx b/chapters/th/chapter6/3b.mdx new file mode 100644 index 000000000..dd9aad386 --- /dev/null +++ b/chapters/th/chapter6/3b.mdx @@ -0,0 +1,648 @@ + + +# การใช้งานตัวตัดคำแบบเร็ว (Fast tokenizers) ใน QA pipeline + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +ในบทนี้ เราจะเรียนเกี่ยวกับการใช้งาน pipeline เพื่อทำ `question-answering` และดูว่าเราจะสามารถใช้ข้อมูลจาก offset เพื่อเอาไว้หาคำตอบให้กับคำถาม input จากบริบทรอบๆ (context) ได้อย่างไร +ขั้นตอนนี้จะคล้ายๆกับตอนที่เราใช้ offset เพื่อรวมรวม entity ประเภทเดียวกันเข้าด้วยกัน ในบทที่แล้ว +จากนั้น เราจะมาดูกันว่าเราจะจัดการกับ context ที่ยาวมากๆ จนบางส่วนต้องถูกตัดทอนออกได้อย่างไร คุณสามารถข้ามส่วนนี้หากคุณไม่สนใจ question answering + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +## การใช้ `question-answering` pipeline + +อย่างที่คุณได้เรียนใน[บทที่ 1](/course/chapter1) เราสามารถใช้ `question-answering` pipeline เพื่อคำนวณคำตอบของคำถาม input ได้ : + +```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'} +``` + +ใน pipeline อื่นๆ เราไม่สามารถตัดทอนและแยกข้อความที่ยาวเกินกว่าความยาวสูงสุดที่โมเดลกำหนดได้ (และอาจพลาดตัดข้อมูลที่ส่วนท้ายของเอกสารได้ด้วย) แต่ pipeline ที่เราจะเรียนกันนี้ สามารถจัดการกับ context ที่ยาวมากได้ และจะ return คำตอบให้กับคำถาม แม้ว่าจะอยู่ในตอนท้าย: + +```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'} +``` + +เรามาดูกันว่ามันทำงานอย่างไร! + +## การใช้งานโมเดลสำหรับงาน question answering + +เช่นเดียวกับ pipeline อื่นๆ เราจะเริ่มต้นด้วยการ tokenize ข้อความ input ของเรา แล้วส่งผลลัพธ์ที่ได้ต่อไปยังตัวโมเดล +ค่าเริ่มต้นของ checkpoint สำหรับ `question-answering` pipeline คือ [`distilbert-base-cased-distilled-squad`](https://huggingface.co/distilbert-base-cased-distilled-squad) +(คำว่า "squad" มาจากชื่อของชุดข้อมูลที่โมเดลใช้เพื่อ fine-tune ซึ่งก็คือ SQuAD dataset เราจะพูดถึงชุดข้อมูลนี้เพิ่มเติมใน[บทที่ 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} + +เราจะทำการตัดคำให้กับส่วนที่เป็นคำถามและส่วน context ไปด้วยกัน โดยจะตัดคำให้กับส่วนคำถามก่อน + +
+An example of tokenization of question and context + +
+ +โมเดลสำหรับ question answering นั้นทำงานแตกต่างไปจากโมเดลอื่น ที่คุณเคยเห็นมาแล้วเล็กน้อย จากภาพด้านบน +โมเดลจะถูกการเทรนให้ทำนาย index ของ token ที่เป็นจุดเริ่มต้นของข้อความคำตอบ (ซึ่งก็คือ index ที่ 21) และ index ของ token สุดท้ายของข้อความคำตอบ +(ซึ่งก็คือ index ที่ 24) นี่คือสาเหตุที่โมเดลเหล่านั้นไม่ return tensor ของ logit หนึ่งตัว แต่สองตัว: tensor แรก คือ logits สำหรับ token เริ่มต้นของคำตอบ +และอีก tensor เป็น logits สำหรับ token สุดท้ายของคำตอบ เนื่องจากในกรณีนี้ เรามีเพียง input เดียว ซึ่งมี 66 token เราจะได้ผลลัพธ์ดังนี้: + +```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} + +ในการแปลงค่า logits ให้เป็นค่าความน่าจะเป็น (probabilities) เราจะใช้ฟังก์ชัน softmax แต่ก่อนอื่น เราจะต้องทำการปกปิด (mask) index ที่ไม่ได้เป็นส่วนหนึ่งของ context ก่อน +อินพุตของเราคือ `[CLS] question [SEP] context [SEP]` ดังนั้น เราจะ mask แต่ละ token ในส่วนที่เป็นคำถาม รวมถึง token `[SEP]` ด้วย อย่างไรก็ตาม เราจะเก็บ `[CLS]` ไว้ +เนื่องจากโมเดลบางตัวอาจจะใช้มัน เพื่อระบุว่าคำตอบไม่อยู่ใน context +เนื่องจากเราจะใช้ softmax ในภายหลัง เราจึงเพียงแค่ต้องแทนที่ค่า logits ที่เราต้องการ mask ด้วยตัวเลขติดลบจำนวนมาก ในตัวอย่างนี้ เราใช้ `-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} + +หลังจากที่ เราได้ mask ค่า logits ตำแหน่งที่เราไม่ต้องการจะทำนาย เรียบร้อยแล้ว ตอนนี้เราก็สามารถคำนวณ 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 ของ probabilities ของจุดเริ่มต้นและจุดสิ้นสุดได้แล้ว +แต่ปัญหาที่อาจจะเกิดขึ้นก็คือ index ของจุดเริ่มต้น นั้นอยู่เกิน index ของจุดสิ้นสุด ดังนั้นเราจึงต้องหาวิธีจัดการปัญหานี้ เราจะคำนวณ probabilities ของ `start_index` และ `end_index` ที่เป็นไปได้จริง ซึ่งหมายถึง `start_index <= end_index` จากนั้นเราจะเลือกใช้แค่ tuple `(start_index, end_index)` ที่มีความเป็นไปได้สูงสุด +สมมติว่า เหตุการณ์ที่ "คำตอบเริ่มต้นที่ `start_index`" และ "คำตอบสิ้นสุดที่ `end_index`" ไม่มีความเกี่ยวข้องกัน (independent) ความน่าจะเป็นที่ คำตอบจะเริ่มต้นที่ `start_index` และสิ้นสุดที่ `end_index` คือ: + +$$\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]$$ + +การคำนวณ score ทำได้โดยคำนวณผลคูณทั้งหมดของ \\(\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'} + +จากนั้นเราจะ mask ค่าตรงที่ `start_index > end_index` ให้เป็น `0` (ค่า probabilities อื่นๆ เป็นจำนวนบวกทั้งหมด) ฟังก์ชัน `torch.triu()` จะคำนวณ ส่วนสามเหลี่ยมบนของ tensor 2 มิติ ที่เราใส่ไปเป็น argument ดังนั้นมันจะทำการ mask ให้เรา: + +```py +scores = torch.triu(scores) +``` + +{:else} + +จากนั้นเราจะ mask ค่าตรงที่ `start_index > end_index` ให้เป็น `0` (ค่า probabilities อื่นๆ เป็นจำนวนบวกทั้งหมด) ฟังก์ชัน `np.triu()` จะคำนวณ ส่วนสามเหลี่ยมบนของ tensor 2 มิติ ที่เราใส่ไปเป็น argument ดังนั้นมันจะทำการ mask ให้เรา: +```py +scores = np.triu(scores) +``` + +{/if} + +ตอนนี้เราแค่ต้องหา index ที่มีค่า probability สูงสุด เนื่องจาก PyTorch จะ return ค่าในรูป flattened tensor เราจึงต้องใช้การหารแล้วปัดลง (floor division) `//` และโมดูลัส `%` เพื่อคำนวณ `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]) +``` +ตอนนี้ เราก็ได้ score ที่ถูกต้องสำหรับคำตอบแล้ว (คุณสามารถตรวจสอบได้โดยเปรียบเทียบกับผลลัพธ์แรกในส่วนก่อนหน้า): + +```python out +0.97773 +``` + + + +✏️ **ลองทำดู!** คำนวณ index เริ่มต้นและสิ้นสุด เพื่อหาคำตอบที่น่าจะเป็นไปได้มากที่สุด 5 คำตอบ + + + +เรามี `start_index` และ `end_index` ของ token ที่จะเอามาเป็นคำตอบได้แล้ว ดังนั้นตอนนี้เราเพียงแค่ต้องแปลงเป็น index ของตัวอักษร ใน context เท่านั้น นี่คือจุดที่ offsets จะมีประโยชน์มาก เราสามารถใช้งานมันได้เหมือนที่เราทำใน 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} +``` + +ยอดเยี่ยม! เราได้คำตอบเหมือนกับในตัวอย่างแรกของเรา! + + + +✏️ **ลองดูสิ!** ใช้คะแนนที่ดีที่สุดที่คุณคำนวณไว้ก่อนหน้านี้ เพื่อคำนวณคำตอบที่น่าจะเป็นไปได้มากที่สุดห้าลำดับ ในการตรวจสอบผลลัพธ์ของคุณ ให้กลับไปที่ pipeline แรกแล้วตั้งค่า `top_k=5` ตอนที่รัน pipeline + + +## การจัดการกับบริบทยาว (long contexts) + +หากคุณต้องการ tokenize คำถามและบริบทที่ค่อยข้างยาว ที่เราใช้เป็นตัวอย่างก่อนหน้านี้ คุณจะได้ token ที่มีความยาวสูงกว่าความยาวสูงสุดที่จำกัดไว้ใน pipeline `question-answering` (ซึ่งคือ 384): + +```py +inputs = tokenizer(question, long_context) +print(len(inputs["input_ids"])) +``` + +```python out +461 +``` + +ดังนั้น เราจึงจำเป็นจะต้องตัดทอน input ของเราให้ความยาวเท่ากับความยาวสูงสุด มีหลายวิธีที่เราสามารถทำได้ อย่างไรก็ตาม เราไม่ต้องการตัดคำถามให้สั้นลง เราต้องการตัดเฉพาะตัวบริบทเท่านั้น +เนื่องจากบริบทอยู่ในตำแหน่งของประโยคที่สอง เราจะใช้กลยุทธ์การตัดทอนที่เรียกว่า `"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` จะแบ่งบริบทออกเป็นส่วนย่อยๆ ที่ไม่ยาวเกินความยาวสูงสุด + +เพื่อให้แน่ใจว่า เราจะไม่แบ่งบริบทผิดตำแหน่งจนโมเดลไม่สามารถค้นหาคำตอบได้ เราจะแบ่งโดย ให้บริบทย่อยแต่ละส่วนมีส่วนที่ทับซ้อนกันด้วย +เราสามารถใช้ tokenizer (ทั้งแบบเร็วและช้า) ทำสิ่งนี้ให้เราได้ โดยคุณจะต้องตั้งค่า `return_overflowing_tokens=True` นอกจากนั้น เพื่อกำหนดว่าเราจะให้ข้อความทับซ้อนกันมากแค่ไหน เราจำกำหนดค่าให้กับ argument `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]' +``` + +คุณจะเห็นว่า ตอนนี้ประโยคถูกแบ่งออกเป็นส่วนๆ โดยแต่ละส่วนจะมีไม่เกิน 6 token และมี token ที่ทับซ้อนกัน 2 token (สังเกตว่า ประโยคสุดท้ายมีเพียง 4 token ในกรณี เราจะต้องเพิ่ม padding token ทีหลังเพื่อให้มันยาวเท่ากับส่วนอื่นๆ) + +มาดูผลลัพธ์ของการ tokenization อย่างละเอียดยิ่งขึ้น: + +```py +print(inputs.keys()) +``` + +```python out +dict_keys(['input_ids', 'attention_mask', 'overflow_to_sample_mapping']) +``` + +ผลลัพธ์จากการแบ่งประโยคนี้ คือ `input_ids` และ `attention_mask` ส่วนคีย์สุดท้าย `overflow_to_sample_mapping` เป็น map ที่บอกเราว่าแต่ละประโยคย่อยมาจากประโยค input ตำแหน่งที่เท่าไร ในตัวอย่างของเรา เราใช้แค่ประโยคเดียวเป็น input และเราได้ 7 ประโยคย่อยเป็น output แปลว่าทุกประโยคย่อยก็จะถูก map ไปหาประโยคหลักที่มี ID เดียวกัน : +```py +print(inputs["overflow_to_sample_mapping"]) +``` + +```python out +[0, 0, 0, 0, 0, 0, 0] +``` + +feature นี้จะมีประโยชน์เมื่อเราใช้ประโยคหลายเป็น input ตัวอย่างเช่น: + +```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"]) +``` + +เราจะได้ : + +```python out +[0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1] +``` + +ซึ่งหมายความว่า ประโยคแรกถูกแบ่งออกเป็น 7 ส่วน และ ประโยคที่สองถูกแบ่งออกเป็น 4 ส่วน + +กลับมาดูกันว่า เราจะจัดการกับบริบทยาวๆได้อย่างไร ไปป์ไลน์ `question-answering` จำกัดความยาวสูงสุดไว้ที่ 384 และค่า stride ถูกตั้งไว้ที่ 128 ซึ่งสอดคล้องกับค่าที่ใช้ตอนที่โมเดลถูก fine-tune (คุณสามารถปรับ parameters เหล่านั้นได้ โดยตั้งค่า `max_seq_len` และ `stride` เมื่อเรียกไปป์ไลน์) เราจะใช้ค่าเริ่มต้นพวกนี้ในการแบ่งบริบทเป็นส่วนย่อยๆ นอกจากนี้ เราจะตั้งค่า padding (เพื่อให้มีแต่ละส่วนที่มีความยาวเท่ากัน และเพื่อที่เราจะได้นำมันไปสร้าง tensor ได้) และค่า 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, +) +``` + +`inputs` เหล่านั้นจะมี input ID และ attention masks เช่นเดียวกับ offsets และ `overflow_to_sample_mapping` ที่เราเพิ่งพูดถึง + เนื่องจากทั้งสองอย่างหลังนี้ไม่ใช่ parameters ที่ใช้โดยโมเดล เราจะเอามันออกจาก `inputs` ก่อนที่จะแปลง `inputs` เป็น 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} + +บริบทแบบยาวของเรา ตอนนี้ถูกแบ่งออกเป็นสองส่วน ซึ่งหมายความว่า หลังจากเราใส่มันเข้าไปในโมเดลแล้ว เราจะได้ค่า start logits และ end logits อย่างละ 2 เซ็ต : + +```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} + +เช่นเดียวกับตัวอย่างก่อน ก่อนอื่นเราจะปิด(mask) token ที่ไม่ได้เป็นส่วนหนึ่งของบริบท ก่อนที่จะใช้ softmax นอกจากนี้เราจะปิด padding tokens ทั้งหมดด้วย (ตามการตั้งค่าใน attention mask): + +{#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} + +จากนั้นเราจะใช้ softmax เพื่อแปลง logits เป็นความน่าจะเป็น: + +{#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} + +ขั้นตอนต่อไปนั้น คล้ายกับสิ่งที่เราทำกับบริบทแบบสั้นก่อนหน้านี้ เราจะรัน process เดียวกันนี้กับประโยคย่อยทั้งสองส่วนที่เราได้มา จากนั้น เราจะแจกจ่าย score ไปให้กับทุกๆ span ของคำตอบที่เป็นไปได้ และสุดท้ายเราจะเลือก span ที่มี score สูงที่สุด + +{#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[0] + end_idx = idx % scores.shape[0] + 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[0] + end_idx = idx % scores.shape[0] + 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)] +``` + +output ที่เราได้คือ span คำตอบที่ดีที่สุดของแต่ละประโยคย่อย ที่โมเดลคำนวณได้ เราจะเห็นว่าโมเดลให้ค่าความมั่นใจที่สูงมากๆกับ span คำตอบในประโยคที่สองมากกว่าประโยคแรก (ซึ่งเป็นสัญญาณที่ดี!) สิ่งที่เราต้องทำหลังจากนี้ก็คือ map ค่า span ไปสู่ตัวอักษร เพื่อดูว่า คำตอบที่โมเดลคำนวณได้คืออะไร + + +✏️ **ลองดูสิ!** ปรับโค้ดด้านบนเพื่อให้มัน return score และ span ของคำตอบที่น่าจะเป็นไปได้มากที่สุด 5 ลำดับ (โดยเปรียบเทียบ score ของทุกประโยคย่อย) + + + +ค่า `offsets` ที่เราใช้ก่อนหน้านี้ เป็น list ของ offsets โดยที่แต่ละประโยคย่อยจะมีหนึ่ง list : + +```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} +``` + +ถ้าไม่นับผลลัพธ์แรกที่เรา print ออกมาด้วย เราก็จะได้ผลลัพธ์เดียวกันกับผลลัพธ์จากไปป์ไลน์ -- เย้! + + + +✏️ **ลองดูสิ!** ใช้ score ที่ดีที่สุดที่คุณคำนวณได้ก่อนหน้านี้ เพื่อแสดงคำตอบที่น่าจะเป็นไปได้มากที่สุด 5 ลำดับ (สำหรับบริบททั้งหมด ไม่ใช่แต่ละส่วน) เพื่อตรวจสอบผลลัพธ์ของคุณ ให้กลับไปที่ไปป์ไลน์แรกแล้วตั้งค่า `top_k=5` เวลารัน + + + +บทนี้ถือว่าเป็น การสรุปจบการเรียนรู้ความสามารถของ tokenizer แบบละเอียด ในบทต่อไปคุณจะได้ใช้ความรู้ที่เรียนมานี้ เพื่อฝึกฝนอีก โดยคุณจะได้ฝึก fine-tune โมเดลเพื่อ task ทั่วๆไป ของ NLP \ No newline at end of file diff --git a/chapters/th/chapter6/4.mdx b/chapters/th/chapter6/4.mdx new file mode 100644 index 000000000..1d763e715 --- /dev/null +++ b/chapters/th/chapter6/4.mdx @@ -0,0 +1,129 @@ +# Normalization และ pre-tokenization + + + +ก่อนที่เราจะเจาะลึกเกี่ยวกับอัลกอริทึม 3 แบบ ของ subword tokenization ที่ใช้กับโมเดล Transformer (Byte-Pair Encoding [BPE], WordPiece, และ Unigram) +อันดับแรก เราจะมาเรียนเกี่ยวกับขั้นตอน preprocessing ที่ tokenizer ใช้เพื่อจัดแต่งข้อความก่อนการ tokenize หลักกันก่อน + +บทนี้จะเป็นภาพรวมระดับสูงของขั้นตอนต่างๆในไปป์ไลน์ tokenization: + +
+The tokenization pipeline. + +
+ +ก่อนแยกข้อความออกเป็น subtokens ตัว tokenizer จะดำเนินการสองขั้นตอน คือ _normalization_ และ _pre-tokenization_ + +## Normalization + + + +ขั้นตอน normalization เกี่ยวข้องกับทำความสะอาดข้อมูลทั่วไป เช่น การลบช่องว่างที่ไม่จำเป็นแปลงข้อความเป็นตัวพิมพ์เล็ก และ/หรือ การลบเครื่องหมายเน้นเสียงออก (accents) หากคุณคุ้นเคยกับ [Unicode normalization](http://www.unicode.org/reports/tr15/) (เช่น NFC หรือ NFKC) นี่ก็เป็นสิ่งที่ tokenizer อาจใช้เช่นกัน + +🤗 Transformers `tokenizer` มี attribute ที่เรียกว่า `backend_tokenizer` ที่เราสามารถเรียกใช้ได้ เพื่อเข้าถึง tokenizer พื้นฐานของ 🤗 Tokenizers library: + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased") +print(type(tokenizer.backend_tokenizer)) +``` + +```python out + +``` + +attribute ชื่อ `normalizer` ของ `tokenizer` object มี method ชื่อ `normalize_str()` ที่เราสามารถใช้เพื่อดูผลลัพธ์ของการ normalization ได้: + +```py +print(tokenizer.backend_tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) +``` + +```python out +'hello how are u?' +``` + +ในตัวอย่างนี้ เนื่องจากเราเลือกใช้ checkpoint `bert-base-uncased` การ normalization จึงแปลงข้อความเป็นตัวพิมพ์เล็กและลบเครื่องหมายเน้นเสียงออก + + + +✏️ **ลองดูสิ!** โหลด tokenizer จาก checkpoint `bert-base-cased` และใช้มันกับ input เดียวกันกับข้างบนนี้ แล้วดูว่าผลลัพธ์ต่างกันอย่างไร ระหว่าง tokenizer เวอร์ชัน cased และ uncased + + + +## Pre-tokenization + + + +ในหัวข้อถัดไปคุณจะได้เรียนรู้ว่า เราไม่สามารถเทรน tokenizer จาก raw text โดยตรงได้ ก่อนอื่นเราจะต้องแยกข้อความเป็น entity เล็กๆ เช่นแยกออกเป็น คำ ขั้นตอนพวกนี้คือการ pre-tokenization ดังที่คุณเห็นใน[บทที่ 2](/course/chapter2) tokenizer แบบ word-based จะแบ่งข้อความเป็นคำ โดยการแบ่งตรงช่องว่าง และ เครื่องหมายวรรคตอน คำที่ได้จะถูกนำมาใช้เป็นขอบเขตของ subtokens ที่ tokenizer เอาไว้ใช้ในการเทรน + +สำหรับ fast tokenizer ถ้าหากเราอยากจะดูว่ามันทำอะไรบ้างในขั้นตอน pre-tokenization เราจะใช้ method ชื่อ `pre_tokenize_str()` ของ attribute ชื่อ `pre_tokenizer` จาก `tokenizer` object: + +```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))] +``` + +คุณจะเห็นว่าตัว tokenizer มีการเก็บข้อมูลเกี่ยวกับ offsets ด้วย ซึ่งทำให้มันสามารถสร้าง offsets mapping ให้เราได้อย่างที่เห็นในบทที่แล้ว สำหรับข้อความ input ในตัวอย่างนี้ ช่องว่างสองช่อง(หลังคำว่า are) ถูกแทนที่ด้วยหนึ่งช่องว่างเท่านั้น แต่เราจะเห็นว่าค่า offsets ยังนับช่องว่างพวกนี้อยู่ สังเกตค่า offsets ตรง `are` และ `you` + +เนื่องจากเราใช้ BERT tokenizer ขั้นตอน pre-tokenization คือการตัดข้อความตรงช่องว่างและเครื่องหมายวรรคตอนเท่านั้น ส่วน tokenizer อื่นๆ อาจจะมีการหลักการตัดคำแบบอื่นได้ ตัวอย่างเช่น ถ้าเราใช้ tokenizer ของ GPT-2: + +```py +tokenizer = AutoTokenizer.from_pretrained("gpt2") +tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") +``` + +มันจะแบ่งข้อความตรงช่องว่างและเครื่องหมายวรรคตอนเช่นเดียวกัน แต่มันจะยังเก็บข้อมูลเกี่ยวกับช่องว่างไว้และใช้เครื่องหมาย `Ġ` เพื่อแทนช่องว่างพวกนี้ การทำแบบนี้ทำให้เราสามารถกู้คืนช่องว่างพวกนี้ได้ตอนที่เรา decode token เหล่านี้ + +```python out +[('Hello', (0, 5)), (',', (5, 6)), ('Ġhow', (6, 10)), ('Ġare', (10, 14)), ('Ġ', (14, 15)), ('Ġyou', (15, 19)), + ('?', (19, 20))] +``` + +สังเกตว่า ช่องว่างสองช่องจะไม่ถูกรวมเป็นหนึ่งช่องแบบใน BERT tokenizer + +ในตัวอย่างสุดท้ายนี้ เราจะมาดู T5 tokenizer กัน ซึ่งใช้อัลกอริทึมที่ชื่อ 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 tokenizer T5 tokenizer จะเก็บข้อมูลเกี่ยวกับช่องว่าง และแทนที่พวกมันด้วยเครื่องหมายพิเศษ (`_`) แต่มันจะแบ่งตรงช่องว่างเท่านั้น และจะไม่แบ่งตรงเครื่องหมายวรรคตอน +สังเกตว่า มันจะเพิ่มช่องว่างตรงต้นประโยคด้วย (ก่อนคำว่า `Hello`) และมันจะไม่นับช่องว่างสองช่องที่อยู่ระหว่าง `are` และ `you` + +คุณได้เห็นแล้วว่า tokenizers ต่างๆ ประมวลผลข้อความอย่างไร ตอนนี้เราจะมาดูอัลกอริทึมต่างๆกัน เริ่มที่ SentencePiece ซึ่งเป็นอัลกอริทึมที่ถูกนำมาใช้อย่างกว้างขวาง จากนั้นในอีกสามหัวข้อต่อไป เราจะมาดูเกี่ยวกับอัลกอริทึม 3 แบบ ของ subword tokenization + +## SentencePiece + +[SentencePiece](https://github.com/google/sentencepiece) คืออัลกอริทึมสำหรับการ preprocessing ข้อความ เพื่อนำข้อความพวกนี้ไปใช้ในโมเดลต่างๆที่คุณจะได้เรียนในอีกสามบทถัดจากนี้ จะมันมองข้อความเป็นอักขระ Unicode และแทนที่ช่องว่างด้วยสัญลักษณ์พิเศษ `▁` ถ้าใช้งานร่วมกับ Unigram algorithm (ดู[บทที่ 7](/course/chapter7/7)) มันจะไม่จำเป็นต้องทำขั้นตอน pre-tokenization เลยด้วย ซึ่งมีประโยชน์สำหรับภาษาที่ไม่ได้ใช้ช่องว่างในการแบ่งคำเช่น ภาษาจีนหรือญี่ปุ่น + +ความสามารถหลักอีกอย่างของ SentencePiece คือ *reversible tokenization* (การตัดคำที่แปลงกลับได้): เนื่องจากมันไม่ได้ treat พวกช่องว่างแบบพิเศษ เวลา decode ประโยคที่ตัดแล้วกลับคืน เราสามารถเชื่อม (concatenate)แต่ละ token ได้เลยและ และแทนที่ `_` ด้วยช่องว่าง ผลลัพธ์ก็คือ ข้อความที่ ถูก normalized + +อย่างที่คุณได้เห็นก่อนหน้านี้ BERT tokenizer จะลบช่องว่างที่ต่อกันออก ทำให้ตอนรวม token กลับ เราจะไม่ได้ข้อความแบบเดิม + +## ภาพรวมของแต่ละอัลกอริทึม + +ในบทถัดไป เราจะมาเรียนรู้อย่างละเอียด เกี่ยวกับอัลกอริทึมสามแบบ สำหรับ subword tokenization ได้แก่ BPE (ใช้กับ GPT-2 และ โมเดลอื่นๆ), WordPiece (ใช้กับ BERT), และ Unigram (ใช้กับ T5 และโมเดลอื่นๆ) +ก่อนที่จะไปเริ่มกัน เรามาดูภาพรวมของแต่ละอัลกอริทึมกันก่อน คุณสามารถกลับมาดูตารางนี้ใหม่ได้หลังจากที่อ่านบทถัดไปแล้ว เพื่อจะได้เข้าใจมากขึ้น + +โมเดล | BPE | WordPiece | Unigram +:----:|:---:|:---------:|:------: +การเทรน | เริ่มจาก vocabulary ขนาดเล็ก และเรียนกฎในการรวม token เข้าด้วยกัน | เริ่มจาก vocabulary ขนาดเล็ก และเรียนกฎในการรวม token เข้าด้วยกัน | เริ่มจาก vocabulary ขนาดใหญ่ เรียนกฎเพื่อลบ token ออกจาก vocabulary +ขั้นตอนการเทรน | รวม token ถ้ามันเป็นคู่ที่พบบ่อย | รวม token ถ้ามันเป็นคู่ที่มี score ที่ดีที่สุด โดย score คำนวณจากความถี่ของคู่ token นั้น และให้คะแนนสูงถ้าแต่ละ token มีความถี่ต่ำ | ลบ token ออกจาก vocabulary เพื่อทำให้ค่า loss ลดลง โดยที่ค่า loss คำนวณจาก training corpus +สิ่งที่เรียน | กฎในการรวม token (merge rules) และ vocabulary | เรียนแค่ vocabulary | เรียน vocabulary และ score ของแต่ละ token +Encoding | แยกคำออกเป็นตัวอักษร และทำการรวมโดยใช้กฎที่เรียนระหว่างการเทรน | หาคำย่อยที่ยาวที่สุดที่อยู่ใน vocabulary เริ่มจากต้นคำและทำต่อไปเรื่อยๆจนหมดคำ | หาการแบ่งคำที่เหมาะสมที่สุดโดยใช้ score ที่เรียนระหว่างการเทรน + +ในบทต่อไปเรามาเรียนเกี่ยวกับ BPE อย่างละเอียดกัน! \ No newline at end of file diff --git a/chapters/th/chapter6/5.mdx b/chapters/th/chapter6/5.mdx new file mode 100644 index 000000000..ad05d3e21 --- /dev/null +++ b/chapters/th/chapter6/5.mdx @@ -0,0 +1,371 @@ +# Byte-Pair Encoding tokenization + + + +ดั้งเดิมแล้ว Byte-Pair Encoding (BPE) เป็นอัลกอริทึมที่ถูกสร้างเพื่อใช้บีบอัดข้อความให้เล็กลง (compress texts) ภายหลัง OpenAI ได้นำอัลกอริทึมนี้มาใช้ในการตัดคำ ในขั้นตอนเตรียมข้อมูลเพื่อเทรน GPT อัลกอริทึมตัวนี้ยังถูกนำมาใช้อย่างกว้างขวางกับโมเดลประเภท Transformer เช่น GPT, GPT-2, RoBERTa, BART, และ DeBERTa + + + + + +💡 บทนี้จะพูดถึง BPE อย่างละเอียด เราจะเจาะลึกถึงไปถึงการ implement อัลกอริทึมนี้ คุณสามารถข้ามไปตอนท้ายได้ ถ้าคุณสนใจเพียงแค่ภาพรวมคร่าวๆเท่านั้น + + + +## อัลกอริทึมที่ใช้ในการเทรน + +BPE เริ่มการเทรนด้วยการคำนวณรายการของคำที่อยู่ในคลังข้อมูล (คลังข้อมูลจะต้องผ่านการ normalization และ pre-tokenization มาแล้ว) จากนั้นมันจะเริ่มสร้างชุดคำศัพท์ (vocabulary) จากตัวอักษรที่อยู่ในแต่ละคำ มาดูตัวอย่างง่ายๆกัน เราจะสมมติว่าคลังข้อมูลของเรามีเพียงห้าคำเท่านั้น : + +``` +"hug", "pug", "pun", "bun", "hugs" +``` + +vocabulary ตั้งต้นสำหรับชุดข้อมูลนี้คือ `["b", "g", "h", "n", "p", "s", "u"]` ในการใช้งานจริง vocabulary ตั้งต้นจะประกอบด้วยตัวอักษร ASCII เป็นอย่างต่ำ หรืออาจจะมีตัวอักษร Unicode ได้ด้วย + +ถ้าข้อความใหม่ที่คุณต้องการจะตัดคำ มีสัญลักษณ์ที่ไม่ได้อยู่ใน training corpus สัญลักษณ์พวกนี้จะถูกแปลงเป็น unknown token นี่เป็นเหตุผลว่าทำไมโมเดล NLP จึงประมวลผลข้อความที่มีอีโมจิได้ไม่ดีนัก + + + +Tokenizer ของ GPT-2 และ RoBERTa (ซึ่งค่อนข้างคล้ายกัน) มีวิธีการจัดการกับปัญหานี้ได้อย่างประสิทธิภาพ มันจะไม่มองแต่ละคำเป็น Unicode แต่จะมองว่าเป็น byte การทำแบบนี้ทำให้ vocabulary ตั้งต้น มีขนาดที่เล็ก (256) แต่ยังสามารถบันทึกทุกๆสัญลักษณ์ได้ โดยไม่ต้องแปลงสัญลักษณ์พิเศษต่างๆเป็น unknown token เทคนิคนี้เรียกว่า *byte-level BPE* + + + +หลังจากสร้าง vocabulary ตั้งต้นแล้ว เราจะเพิ่ม token ใหม่ๆ เข้าไปจนว่าจะได้ vocabulary ขนาดใหญ่พอกับที่เราต้องการ โดยเราจะเทรน BPE ให้เรียน กฎที่เรียกว่า *merges* ซึ่งเป็นกฎสำหรับการรวมสองหน่วยใน vocabulary เข้าด้วยกัน +ตอนช่วงเริ่มต้น กฎ merges พวกนี้จะสร้างคำย่อยที่ประกอบด้วยตัวอักษรสองตัว ระหว่างที่เราเทรนต่อไปเรื่อยๆ คำย่อยที่ได้ก็จะยาวขึ้น +ในแต่ละรอบของการเทรน BPE จะคำนวณหาคู่ของคำย่อยที่พบบ่อยที่สุด (คู่ของคำย่อย ในที่นี้เราหมายถึง token ที่อยู่ติดกัน) +คู่ที่มีความถี่มากที่สุดจะถูกรวมเข้าด้วยกัน จากนั้นโมเดลจะทำแบบเดิมอีกในการเทรนรอบต่อไป + +กลับมาที่ตัวอย่างของเรา สมมติว่าแต่ละคำมีความถี่ดังต่อไปนี้ : + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +ซึ่งแปลว่า `"hug"` พบ 10 ครั้งใน corpus, `"pug"` พบ 5 ครั้ง, `"pun"` พบ 12 ครั้ง, `"bun"` พบ 4 ครั้ง, และ `"hugs"` พบ 5 ครั้ง + +เราจะเริ่มการเทรน โดยแยกแต่ละคำออกเป็นตัวอักษร (ตัวอักษรจะต้องมาจาก vocabulary ตั้งต้นที่เราสร้างมาก่อนหน้านี้แล้ว) ตอนนี้คุณจะเห็นว่าแต่ละคำถูกแปลงเป็น list ที่ประกอบไปด้วยหลายๆ token + +``` +("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 ครั้งใน corpus +อย่างไรก็ตาม คู่นี้ไม่ใช่คู่ที่พบบ่อยที่สุด คู่ที่พบบ่อยที่สุดคือ `("u", "g")` ซึ่งพบใน คำว่า `"hug"`, `"pug"`, และ `"hugs"` ซึ่งความถี่รวมของมันคือ 20 ครั้ง +ดังนั้น กฎแรกของการ merge ที่ tokenizer เรียนคือ `("u", "g") -> "ug"` แปลว่ามันจะเพิ่ม `"ug"` เข้าไปใน vocabulary และใน corpus คู่นี้ก็จะถูกรวมเป็น token เดียวด้วย + +หลังจากขั้นตอนนี้ vocabulary และ corpus จะมีค่าดังนี้ : + +``` +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) +``` + +ตอนนี้ จะเห็นว่าเรามีคู่ที่เมื่อรวมกันจะได้ token ที่ยาวกว่าสองตัวอักษร ตัวอย่างเช่น คู่ `("h", "ug")` ซึ่งพบ 15 ครั้งใน corpus +อย่างไรก็ตาม คู่ที่พบบ่อยที่สุดคือ `("u", "n")` ซึ่งพบ 16 ครั้ง ดังนั้นกฎที่สองก็คือ `("u", "n") -> "un"` หลังจากที่เราเพิ่มคู่นี้ไปใน vocabulary และ merge token ใน corpus เข้าด้วยกันแล้ว เราจะได้ : + +``` +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"` ซึ่งจะทำให้เราได้ token ที่มีสามตัวอักษร หลังจากการ merge เราจะได้ : + +``` +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) +``` + +เราจะทำแบบนี้ต่อไปเรื่อยๆ จนกว่าจะได้ขนาดของ vocabulary ที่ต้องการ + + + +✏️ **ตาคุณแล้ว!** คุณคิดว่ากฎ merge ต่อไปคืออะไร + + + +## Tokenization algorithm + +การ tokenization เป็นขั้นตอนหลังจากการเทรน โดย input ใหม่จะถูก tokenize ด้วยขั้นตอนดังต่อไปนี้ + +1. Normalization (การปรับข้อความให้เป็นมาตรฐาน) +2. Pre-tokenization (การเตรียมข้อความให้พร้อมสำหรับการ tokenize จริง) +3. แยกคำออกเป็นตัวอักษรเดี่ยว +4. ใช้กฎ merge ที่ได้จากการเทรนเพื่อรวมตัวอักษรที่เราได้จากขั้นตอนก่อนหน้า + +มาดูกฎสามตัวที่เราได้จากการเทรนก่อนหน้านี้ : + +``` +("u", "g") -> "ug" +("u", "n") -> "un" +("h", "ug") -> "hug" +``` + +คำว่า`"bug"` จะถูกแยกเป็น `["b", "ug"]` ส่วนคำว่า `"mug"` จะถูกแยกเป็น `["[UNK]", "ug"]` เพราะว่า `"m"` ไม่ได้อยู่ใน vocabulary ของเรา +้เช่นเดียวกัน คำว่า `"thug"` จะถูกแยกเป็น `["[UNK]", "hug"]` เพราะว่า `"t"` ไม่ได้อยู่ใน vocabulary กฎแรกจะรวม `"u"` และ `"g"` เข้าด้วยกัน จากนั้น `"hu"` และ `"g"` ก็จะถูกรวมเข้าด้วยกัน + + + +✏️ **ตาคุณแล้ว!** คุณคิดว่าคำว่า `"unhug"` จะถูกแยกอย่างไร + + + +## การสร้าง BPE (Implementing BPE) + +ตอนนี้เราจะมาดูกันว่า คุณจะสามารถ implement อัลกอริทึม BPE ได้อย่างไร สิ่งที่เราจะเรียนต่อไปนี้ไม่ใช่ implementation ที่ดีที่สุด เราเพียงต้องการให้คุณเข้าใจโค้ดและเข้าใจว่า BPE ทำงานอย่างไร + +อันดับแรก เราต้องการ corpus ดังนั้น เราจะสร้าง corpus แบบง่ายๆขึ้นมา โดยประกอบไปด้วยไม่กี่ประโยค : + +```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.", +] +``` + +จากนั้นเราจะทำการ pre-tokenize corpus นี้ เพื่อแยกข้อความออกเป็นคำๆ เนื่องจากเราจะสร้าง BPE tokenizer ตามตัวที่ใช้ใน GPT-2 เราจึงต้องใช้ `gpt2` tokenizer ในการ pre-tokenize + +```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}) +``` + +ขั้นตอนต่อไป คือการคำนวณ vocabulary ตั้งต้น ซึ่งสร้างจากแต่ละตัวอักษรใน 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', 'Ġ'] +``` + +เราจะเพิ่ม token พิเศษเข้าไปในข้างหน้า list นี้ด้วย GPT-2 ใช้ token พิเศษคือ `"<|endoftext|>"` : + +```python +vocab = ["<|endoftext|>"] + alphabet.copy() +``` + +จากนั้นเราจะแยกแต่ละคำใน corpus ให้เป็นตัวอักษร เพื่อที่เราจะได้เริ่มการเทรน : + +```python +splits = {word: [c for c in word] for word in word_freqs.keys()} +``` + +ตอนนี้เราก็พร้อมที่จะเทรนแล้ว เราจะเริ่มด้วยการเขียนฟังก์ชันที่คำนวณความถี่ของแต่ละคู่ token : + +```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 +``` + +มาดูส่วนผลลัพธ์ (ซึ่งเป็น dictionary) กัน : + +```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'` เข้าไปใน vocabulary : + +```python +merges = {("Ġ", "t"): "Ġt"} +vocab.append("Ġt") +``` + +จากนั้น เราจะต้องทำการ merge คำย่อยที่อยู่ใน dictionary `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 +``` + +และนี่ก็คือผลลัพธ์จากการ merge ครั้งแรก : + +```py +splits = merge_pair("Ġ", "t", splits) +print(splits["Ġtrained"]) +``` + +```python out +['Ġt', 'r', 'a', 'i', 'n', 'e', 'd'] +``` + +ตอนนี้เราก็มีทุกอย่างพร้อมสำหรับการเทรนแล้ว เราจะเทรนจนกว่าขนาดของ vocabulary จะเท่ากับ 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]) +``` + +ผลลัพธ์ที่ได้คือ tokenizer ของเราได้เรียน 19 กฎ (vocabulary ตั้งต้นมี 31 token ซึ่งมาจากตัวอักษรที่เรามี 30 ตัวและ token พิเศษอีกหนึ่งตัว) : + +```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'} +``` + +ส่วน vocabulary ที่ได้จะประกอบไปด้วย token พิเศษ, ตัวอักษรตั้งต้น, และผลลัพธ์จากการ merge แต่ละครั้ง : + +```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()` กับ corpus เดียวกันนี้ คุณจะไม่ได้ vocabulary เดียวกัน เพราะว่าอาจจะมีหลายคู่ token ที่มีความถี่สูงสุดเท่ากัน ในตัวอย่างของเรา เราเลือกคู่แรกที่โค้ดของเราอ่านเจอ ส่วน 🤗 Tokenizers library เลือกคู่แรกโดยเรียงตาม ID + + + +หากเราต้องการ tokenize ข้อความใดข้อความหนึ่ง สิ่งที่ต้องทำคือ pre-tokenize จากนั้นจึงทำการ tokenize และสุดท้าย apply กฎ merge : + +```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', '.'] +``` + + + +⚠️ การ implementation ในตัวอย่างของเราจะ return error ถ้าโปรแกรมอ่านเจอตัวอักษรที่ไม่มีใน vocabulary นั่นเพราะว่าเราไม่ได้เขียนโค้ดเพื่อจัดการกับกรณีแบบนี้ +ใน GPT-2 ปกติจะไม่มี unknown token แบบนี้ เพราะว่า ถ้าเราใช้ byte-level BPE เราจะไม่มีทางได้ตัวอักษรที่ unknown อย่างไรก็ตามในตัวอย่างของเรา เราไม่ได้ใช้ทุกๆ byte เพื่อสร้าง vocabulary ตั้งต้น +อย่างไรก็ตาม หัวข้อนี้นั้นค่อนข้างลึก เราจึงจะไม่ขอพูดถึงรายละเอียดไปมากกว่านี้ + + + +นี่ก็คือ อัลกอริทึม BPE ในบทต่อไป เราจะมาดู WordPiece กัน \ No newline at end of file diff --git a/chapters/th/chapter6/6.mdx b/chapters/th/chapter6/6.mdx new file mode 100644 index 000000000..e19f40410 --- /dev/null +++ b/chapters/th/chapter6/6.mdx @@ -0,0 +1,394 @@ +# WordPiece tokenization + + + +WordPiece เป็นอัลกอริทึมสำหรับ tokenization ที่สร้างโดย Google เพื่อ pretrain โมเดล BERT หลังจากนั้นมันได้ถูกนำมาใช้กับโมเดลประเภท Transformer หลายตัวที่เป็นประเภทเดียวกับ BERT เช่น DistilBERT, MobileBERT, Funnel Transformers, และ MPNET + +WordPiece มีความคล้ายกับ BPE ในวิธีการเทรน แต่วิธีการแยกคำนั้นแตกต่างกัน + + + + + +💡 บทนี้จะพูดถึง WordPiece อย่างละเอียด เราจะเจาะลึกถึงไปถึงการ implement อัลกอริทึมนี้ คุณสามารถข้ามไปตอนท้ายได้ ถ้าคุณสนใจเพียงแค่ภาพรวมคร่าวๆเท่านั้น + + + +## Training algorithm + + + +⚠️ เนื่องจาก Google ไม่เปิดเผยโค้ดสำหรับการเทรน WordPiece ดังนั้นโค้ดที่เราจะสอนคุณต่อจากนี้ มาจากการพยายามทำตามข้อมูลที่บอกไว้ใน paper แปลว่าโค้ดอาจจะไม่แม่นยำ 100% + + + +เช่นเดียวกับ BPE อัลกอริทึม WordPiece เริ่มจาก vocabulary ขนาดเล็ก ที่ประกอบไปด้วย token พิเศษที่โมเดลใช้ และตัวอักษรตั้งต้น +เพื่อที่โมเดลจะได้รู้ว่าคำไหนเป็นคำย่อย มันจะเขียน prefix เช่น `##` (ใช้ใน BERT) ไว้ข้างหน้าของแต่ละคำย่อย ในขั้นตอนแรก แต่ละคำจะถูกแบ่งออกเป็นตัวอักษร โดยตัวอักษรที่ไม่ใช่ตัวแรกจะมี prefix นี้ + +ตัวอย่างเช่น คำว่า `"word"` จะถูกแบ่งดังนี้ : + +``` +w ##o ##r ##d +``` + +ดังนั้น vocabulary ตั้งต้น จะประกอบไปด้วยทุกๆตัวอักษรที่อยู่เริ่มต้นของแต่ละคำ และตัวอักษรอื่นๆที่อยู่ข้างในคำนั้น ซึ่งนำหน้าด้วย prefix พิเศษ + +เช่นเดียวกันกับ BPE เป้าหมายในการเทรน WordPiece คือเรียนกฎเพื่อการ merge แต่ความแตกต่างคือหลักการในการเลือกคู่ token ที่จะนำมา merge แทนที่จะเลือกคู่ที่พบบ่อยที่สุด WordPiece จะคำนวณ score ให้แต่ละคู่ โดยใช้สูตรต่อไปนี้ + +$$\mathrm{score} = (\mathrm{freq\_of\_pair}) / (\mathrm{freq\_of\_first\_element} \times \mathrm{freq\_of\_second\_element})$$ + +การที่เราหารความถี่ของคู่ token ด้วยผลคูณของความถี่ของแต่ละ token ในคู่ จะทำให้อัลกอริทึมให้คะแนนคู่ ที่แต่ละ token มีความถี่ไม่สูง +ตัวอย่างเช่น เราไม่จำเป็นจำต้อง merge `("un", "##able")` ถึงแม้ว่าคู่นี้จะมีจำนวนมากที่สุดใน vocabulary เพราะว่าทั้ง `"un"` และ `"##able"` ต่างพบได้กับคำอื่นๆด้วย และแต่ละตัวก็มีจำนวนค่อนข้างสูง +ตรงกันข้ามกับ คู่เช่น `("hu", "##gging")` ซึ่งอาจจะถูกรวมเร็วกว่า (ในกรณีที่ "hugging" มีจำนวนสูงใน vocabulary ) เพราะว่า ทั้ง`"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) +``` + +ดังนั้น vocabulary ตั้งต้น คือ `["b", "h", "p", "##g", "##n", "##s", "##u"]` (เราขอละไม่พูดถึง token พิเศษในที่นี้) +คู่ที่พบบ่อยที่สุดคือ `("##u", "##g")` ซึ่งพบ 20 ครั้ง แต่ว่าถ้านับจำนวนของแต่ละ token `"##u"` จะมีจำนวนค่อนข้างสูง ทำให้ score ของคู่นี้ไม่ได้สูงที่สุด (1 / 36) +ทุกๆคู่ที่ประกอบด้วย `"##u"` จะได้ score เดียวกันซึ่งคือ (1 / 36) ดังนั้น score ที่สูงที่สุดจึงมาจากคู่ `("##g", "##s")` เพราะว่ามันไม่มี `"##u"` ซึ่งมี score เป็น 1 / 20 และกฎแรกที่เราได้ก็คือ `("##g", "##s") -> ("##gs")` + +โปรดสังเกตว่า เวลาที่เรา merge เราจะลบ ตัว `##` ออกระหว่าง token สองตัวที่เราต้องการจะ merge แปลว่าเราจะได้ `"##gs"` และเราจะเพิ่ม token นี้เข้าไปใน vocabulary จากนั้นเราก็จะใช้กฎนี้กับทุกๆคำใน 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) +``` + +ตอนนี้ `"##u"` มีอยู่ในทุกๆคู่ แปลว่า ทุกคู่จะมี score เท่ากัน + +ในกรณีนี้ เราจะเลือกกฎใดกฎหนึ่งเพื่อ merge ต่อไป เราจะเลือก `("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) +``` + +ตอนนี้คู่ที่มี score สูงที่สุดคือ `("hu", "##g")` และ `("hu", "##gs")` ซึ่งทั้งสองมี score เท่ากับ 1/15 (ส่วนตัวอื่นๆที่เหลือ มี score เท่ากับ 1/21) เราจะเลือกคู่แรกใน list มาใช้ เพื่อ merge : + +``` +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) +``` + +เราจะทำแบบนี้จนกว่าจะได้ vocabulary ที่มีขนาดใหญ่มากพอ + + + +✏️ **ตาคุณบ้างแล้ว!** กฎ merge ต่อไปคืออะไร + + + +## Tokenization algorithm + +การ tokenization ใน WordPiece แตกต่างจาก BPE ตรงที่ WordPiece จะบันทึกเฉพาะ vocabulary สุดท้ายเท่านั้น และไม่ได้บันทึก กฎ merge +หากเราจะ tokenize คำใดคำหนึ่ง WordPiece จะหาคำย่อยที่ยาวที่สุด ที่พบใน vocabulary จากนั้นจะแยกคำออกตามนั้น +ตัวอย่างเช่น ถ้าเราใช้ vocabulary ที่เทรนแล้วจากตัวอย่างด้านบน และต้องการ tokenize คำว่า `"hugs"` คำย่อยที่ยาวที่สุดก็คือ `"hug"` ดังนั้น เราจะแบ่งมันออกเป็น `["hug", "##s"]` +จากนั้นเราก็จะดูที่ `"##s"` เราพบว่าเป็น token ที่อยู่ใน vocabulary ดังนั้น เราจึงได้ `["hug", "##s"]` +ถ้าเราใช้ BPE เราจะใช้กฎที่เทรนมาตามลำดับ ซึ่งมันจะ tokenize ตัวอย่างของเราออกเป็น `["hu", "##gs"]` + +มาดูอีกตัวอย่างกัน เช่นคำว่า `"bugs"` +เราจะเริ่มอ่านจากข้างหน้าของคำไปข้างหลัง คุณจะเห็นว่า `"b"` เป็นคำย่อยที่ยาวที่สุดที่พบใน vocabulary ดังนั้น เราจะแยกคำตรงนี้ และเราจะได้ `["b", "##ugs"]` +จากนั้นเราจะดูคำว่า `"##ugs"` สำหรับคำนี้เราพบว่า `"##u"` คือคำย่อยที่ยาวที่สุดที่พบใน vocabulary ดังนั้น เราจึงจะแยกคำตรงนี้ และได้ผลลัพธ์เป็น `["b", "##u, "##gs"]` +สุดท้ายเราจะดูที่คำว่า `"##gs"` ซึ่งเราพบว่า มันอยู่ใน vocabulary แล้ว ดังนั้นเราไม่ต้องแบ่งมันอีก + +ในกรณีที่เราไม่สามารถหาคำย่อยที่อยู่ใน vocabulary ได้เลย คำหลักที่เรากำลังอ่านนั้นจะถูกแปลงเป็นคำ unknown +ตัวอย่างเช่นคำว่า `"mug"` จะถูก tokenize ให้เป็น `["[UNK]"]` เช่นเดียวกันกับคำว่า `"bum"` ถึงแม้ว่าเราจะเจอ `"b"` และ `"##u"` ใน vocabulary แต่ว่า `"##m"` ไม่ได้อยู่ใน vocabulary เราจะแปลงทั้งคำเป็น `["[UNK]"]` และจะไม่แยกมันเป็น `["b", "##u", "[UNK]"]` +นี่เป็นสิ่งหนึ่งที่แตกต่างจาก BPE โดย BPE จะดูที่แต่ละตัวอักษร และถ้าตัวไหนไม่พบใน vocabulary ก็จะถูกคัดว่าเป็น unknown + + + +✏️ **ถึงตาคุณแล้ว!** คำว่า `"pugs"` จะถูก tokenize อย่างไร? + + + +## Implementing WordPiece + +มาดูกันว่า เราจะ implement อัลกอริทึม WordPiece ได้อย่างไร +เช่นเดียวกับตอนที่เราสอนเรื่อง BPE สิ่งที่เราจะสอนต่อไปนี้เป็นเพียงตัวอย่าง เพื่อให้คุณเข้าใจการทำงานของอัลกอริทึม โค้ดที่ได้อาจจะไม่สามารถใช้ได้กับ corpus ใหญ่ๆ +เราจะใช้ corpus ตัวอย่างเดียวกับที่ใช้ในบท 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.", +] +``` + +ก่อนอื่นคุณจะต้อง pre-tokenize corpus เพื่อแยกข้อความเป็นคำๆ เนื่องจากเราจะจำลองการทำงานของ WordPiece tokenizer (เช่น BERT) เราจะใช้ `bert-base-cased` tokenizer ในการ pre-tokenize + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +``` + +จากนั้นคำนวณความถี่ของแต่ละคำใน 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 +``` + +```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}) +``` + +เราจะมาสร้างเซ็ตของ alphabet กัน ซึ่งคือเซ็ตที่ประกอบไปด้วยตัวอักษรแรกของแต่ละคำ และอักษรอื่นๆที่ไม่ใช่ตัวแรกจะมีการใส่ `##` ไว้ข้างหน้า : + +```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'] +``` + +เราจะเพิ่ม token พิเศษ เข้าไปด้านหน้าของ list นี้ด้วย สำหรับ 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() +} +``` + +ตอนนี้เราก็พร้อมที่จะเทรนแล้ว เราจะมาเขียนฟังก์ชันเพื่อคำนวณ score ให้แต่ละคู่ tokenกัน : + +```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 +``` + +จากนั้น เราจะหาคู่ที่มี score สูงที่สุด โดยใช้ loop ง่ายๆ ดังนี้ : + +```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'` เข้าไปใน vocabulary : + +```python +vocab.append("ab") +``` + +ก่อนที่จะทำต่อ เราจะต้องเพิ่มตัวที่ถูก merge เข้าไปใน dictionary `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 +``` + +นี่คือผลลัพธ์ของการ merge ครั้งแรก : + +```py +splits = merge_pair("a", "##b", splits) +splits["about"] +``` + +```python out +['ab', '##o', '##u', '##t'] +``` + +ตอนนี้เราก็มีทุกฟังก์ชันที่จำเป็นสำหรับการเทรนแล้ว เราจะเทรนจนกว่า tokenizer ได้เรียนเกี่ยวกับทุกๆ merge ที่เราต้องการ เราจะตั้งค่าขนาด vocabulary เป็น 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) +``` + +มาดูผลลัพธ์ของ vocabulary กัน : + +```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', '##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 คุณจะเห็นว่า tokenizer ตัวนี้สามารถเรียนเกี่ยวกับคำย่อยได้เร็วกว่านิดหน่อย + + + +💡 ถ้าคุณใช้ `train_new_from_iterator()` กับ corpus ตัวอย่างนี้ คุณอาจจะไม่ได้ vocabulary เดียวกัน นั่นก็เพราะ 🤗 Tokenizers library ไม่ได้ใช้ WordPiece ในการเทรน แต่เราใช้ BPE + + + + +เมื่อคุณต้องการ tokenize ข้อความใหม่ คุณจะต้องทำการ pre-tokenize ข้อความแล้วจากนั้นจึง tokenize แต่ละคำ ตามหลักการของอัลกอริทึมนี้ +เราจะมองหาคำย่อยที่ยาวที่สุด โดยอ่านจากข้างหน้าคำไปข้างหลัง จากนั้นเราจะแยกคำหลักออกตรงคำย่อยนี้ จากนั้นทำขั้นตอนนี้ซ้ำกับส่วนต่อๆไปของคำนั้น แล้วทำเช่นเดียวกันกับคำต่อไป + +```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 +``` + +มาทดลอง tokenize คำที่มีใน vocabulary และอีกคำที่ไม่ได้อยู่ใน vocabulary กัน : + +```python +print(encode_word("Hugging")) +print(encode_word("HOgging")) +``` + +```python out +['Hugg', '##i', '##n', '##g'] +['[UNK]'] +``` + +ตอนนี้เราจะต้องเขียนฟังก์ชันเพื่อ tokenize ข้อความกัน : + +```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/th/chapter6/7.mdx b/chapters/th/chapter6/7.mdx new file mode 100644 index 000000000..a19df558c --- /dev/null +++ b/chapters/th/chapter6/7.mdx @@ -0,0 +1,404 @@ +# Unigram tokenization + + + +อัลกอริทึม Unigram มักจะถูกใช้บ่อยใน SentencePiece ซึ่งเป็นอีกอัลกอริทึมสำหรับการ tokenization ที่ใช้ในโมเดลเช่น AlBERT, T5, mBART, Big Bird, และ XLNet + + + + + +💡 บทนี้จะพูดถึง Unigram อย่างละเอียด เราจะเจาะลึกถึงไปถึงการ implement อัลกอริทึมนี้ คุณสามารถข้ามไปตอนท้ายได้ ถ้าคุณสนใจเพียงแค่ภาพรวมคร่าวๆเท่านั้น + + + +## Training algorithm + +เมื่อเทียบกับ BPE และ WordPiece การตัดคำแบบ Unigram ทำงานกลับกันคือ เริ่มจาก vocabulary ตั้งต้นขนาดใหญ่ แล้วอัลกอริทึมจะพยายามลบ token ออกจาก vocabulary จนกว่าจะได้ขนาด vocabulary ที่เราต้องการ +การสร้าง vocabulary ตั้งต้นทำได้หลายวิธี คุณอาจจะใช้คำย่อยที่พบบ่อยที่สุด หรือคุณอาจจะใช้ BPE เพื่อสร้าง vocabulary ตั้งต้น โดยตั้งค่าขนาด vocabulary ให้มีขนาดค่อนข้างใหญ่ +ในการเทรนแต่ละครั้ง อัลกอริทึม Unigram จะคำนวณค่า loss ของ training corpus ซึ่งขึ้นกับ vocabulary ที่มีในขณะนั้น +จากนั้น มันจะลองลบ แต่ละ token ออกจาก vocabulary แล้วคำนวณค่า loss อีกที + +เป้าหมายคือการค้นหา token ที่เมื่อลบออกแล้วจะทำให้ค่า loss เพิ่มน้อยที่สุด +token พวกนี้คือตัวที่ไม่มีผลต่อค่า loss มาก แปลว่า มันไม่มีประโยชน์มาก ทำให้เราสามารถลบมันออกได้ +การคำนวณแบบนี้ค่อนข้างใช้การประมวลผลสูง เราไม่เพียงแค่ลบสัญลักษณ์ออกหนึ่งตัวเท่านั้น แต่เราลบ \\(p\\) เปอร์เซ็นของ token ที่เพิ่มค่าloss น้อยที่สุด (\\(p\\) คือ hyperparameter ที่เราสามารถตั้งค่าได้ ปกติค่าจะอยู่ที่ 10 หรือ 20) +เราจะรันขั้นตอนนี้จนกว่าจะได้ขนาด vocabulary ที่ต้องการ +อย่างไรก็ตาม เราจะไม่ลบตัวอักษรตั้งต้น (base characters) เพื่อที่จะได้มั่นใจว่าเราจะยังสามารถ tokenize ทุกคำได้ +ส่วนหลักของอัลกอริทึมนี้คือการคำนวณค่า loss ของ corpus และดูว่าค่า loss มีการเปลี่ยนแปลงอย่างไรถ้าเราลบ token ตัวใดตัวหนึ่งออกจาก vocabulary เราจะมาอธิบายว่ามันทำงานอย่างไรกัน + +ขั้นตอนนี้จะใช้อัลกอริทึมสำหรับ tokenization ของโมเดล Unigram +เราจะใช้ corpus เดียวกันกับในตัวอย่างก่อนๆ : + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +โดยที่เราจะใช้ทุกๆคำย่อยของแต่ละคำ มาสร้าง vocabulary ตั้งต้น : + +``` +["h", "u", "g", "hu", "ug", "p", "pu", "n", "un", "b", "bu", "s", "hug", "gs", "ugs"] +``` + +## Tokenization algorithm + +โมเดล Unigram เป็น language model ประเภทหนึ่งที่ประมวลผลแต่ละ token ในข้อความ โดยมองว่ามันไม่มีส่วนเกี่ยวข้องกับ token ตัวอื่นๆ (not dependent) +Unigram ถือว่าเป็น language model ประเภทที่ซับซ้อนน้อยที่สุด เพราะว่าเวลาที่เราคำนวณความน่าจะเป็น(probability)ของ token ที่อยู่ในข้อความใดข้อความหนึ่ง เราไม่ต้องพิจารณา token ตัวอื่นๆในข้อความด้วย +ดังนั้น ถ้าเราใช้ Unigram language model เพื่อผลิตข้อความ มันก็จะผลิตคำที่พบบ่อยที่สุดทุกๆครั้ง (language model จะ predict คำที่มีความน่าจะเป็นสูงที่สุดเวลาที่มันผลิตข้อความ) + +ความน่าจะเป็นของ token หนึ่งจะเท่ากับความถี่ของคำนั้นๆที่เราพบใน corpus หารกับ ผลรวมของความถี่ของ token ทุกตัวใน vocabulary (ส่วนหารนี้จะช่วยทำให้ค่าความน่าจะเป็นของแต่ละ token รวมกันได้ 1) + +ตัวอย่างเช่น `"ug"` เป็นคำย่อย (subword) ที่อยู่ใน `"hug"`, `"pug"`, และ `"hugs"` ดังนั้นความถี่ของมันก็คือ 20 + +ข้างล่างนี้คือความถี่ของคำย่อยทุกๆตัวใน vocabulary ของเรา : + +``` +("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 + + + +✏️ **ตาคุณบ้างแล้ว!** ลองเขียนโค้ดเพื่อคำนวณความถี่ของแต่ละ token แบบตัวอย่างข้างบน และคำนวณผลรวมของทุกความถี่ด้วย แล้วเช็คว่าผลลัพธ์ของคุณถูกหรือไม่ + + + +ในการ tokenize คำๆหนึ่งนั้น เราจะคำนวณทุกๆการตัดคำที่เป็นไปได้ (segmentation) และคำนวณความน่าจะเป็นของแต่ละ segmentation ด้วย โดยใช้วิธีการคำนวณตามโมเดล Unigram +เนื่องจากแต่ละ token ไม่ได้ขึ้นกับ token ตัวอื่น ค่าความน่าจะเป็นของแต่ละ segmentation สามารถคำนวณได้โดย นำค่าความน่าจะเป็นของแต่ละ token ย่อยใน segmentation นั้นมาคูณกัน + +ตัวอย่างเช่น ถ้าเรา tokenize คำว่า `"pug"` แล้วได้ `["p", "u", "g"]` ความน่าจะเป็นของ segmentation นี้ก็คือ + +$$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$$ + +ปกติแล้วถ้า segmentation มีจำนวนคำย่อยน้อย มันจะมีค่าความน่าจะเป็นที่สูง (เพราะว่า ในการคำนวณความน่าจะเป็นของแต่ละ token ทุกตัวจะถูกหารด้วยค่าเดียวกันคือ 210) +ผลลัพธ์แบบนี้ถือว่าดี เพราะสอดคล้องกับสิ่งที่เราต้องการ นั่นคือเราต้องการแยกคำออกเป็นคำย่อยๆ โดยให้มีจำนวนคำย่อยน้อยที่สุดเท่าที่จะเป็นไปได้ + +สำหรับตัวอย่าง `"pug"` เราจะได้ความน่าจะเป็นของแต่ละ segmentation ดังนี้ : + +``` +["p", "u", "g"] : 0.000389 +["p", "ug"] : 0.0022676 +["pu", "g"] : 0.0022676 +``` + +จะเห็นว่า `["p", "ug"]` หรือ `["pu", "g"]` มีความน่าจะเป็นเท่ากัน ดังนั้นโปรแกรมของเราจะเลือก segmentation ใดก็ได้ ขึ้นกับว่าโปรแกรมของเราจะอ่านเจอผลลัพธ์ใดก่อน (อย่างไรก็ตามใน corpus ใหญ่ๆ เราจะไม่ค่อยเห็นกรณีแบบนี้ ที่หลาย segmentation มีความน่าจะเป็นเท่ากัน) +เนื่องจากเราใช้ตัวอย่างสั้นๆง่ายๆ อาจจะทำให้ดูเหมือนการคำนวณ segmentation ทั้งหมด รวมถึงค่าความน่าจะเป็น ทำได้อย่างง่ายดาย แต่ปกติแล้วการคำนวณจะยากกว่านี้ + +อัลกอริทึมหนึ่งที่จะช่วยหา segmentation ที่ดีที่สุดให้เรา ก็คือ *Viterbi algorithm* +มันจะสร้างกราฟที่สามารถคำนวณหา segmentation ที่เป็นไปได้ทั้งหมดของคำที่เราต้องการจะ tokenize ตัวอย่างเช่น ถ้า _a_ และ _b_ เป็นคำย่อยที่มีอยู่ใน vocabulary อัลกอริทึมก็จะสร้างกราฟเชื่อมจาก _a_ ไปหา _b_ และมันก็จะคำนวณค่าความน่าจะเป็นของคำย่อยพวกนี้และบันทึกลงไปในกราฟเพื่อการคำนวณต่อไป +เป้าหมายของเราคือการหาเส้นทางในกราฟที่แสดงถึง segmentation ที่ดีที่สุด ซึ่งก็คือ segmentation ที่มี score สูงที่สุด +เราจะคำนวณ score จากต้นกราฟไปยังปลายกราฟ ในแต่ละตำแหน่งเราจะ loop ตัวอักษรสุดท้ายของแต่ละคำย่อยทุกๆตัวที่เป็นไปได้ในตำแหน่งนั้น และเลือกคำย่อยที่มี score สูงที่สุด +และต่อแบบนี้ไปเรื่อยๆจนถึงตำแหน่งสุดท้าย + +มาดูตัวอย่างกับคำว่า `"unhug"` กัน ในแต่ละตำแหน่ง เราได้คำนวณเพื่อหาคำย่อยที่ตัวอักษรสุดท้ายมี score สูงที่สุด : + +``` +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"`และ score ของมัน + + + +## กลับมาสู่การเทรน + +หลังจากที่คุณได้รู้แล้วว่าอัลกอริทึมนี้ tokenize คำอย่างไร ในหัวข้อนี้ เราจะมาดูกันอย่างละเอียดว่า เราจะคำนวณค่า loss เพื่อการเทรนได้อย่างไร +ในทุกๆรอบของการเทรน เราจะคำนวณค่า loss โดยเราจะ tokenize แต่ละคำใน corpus ตามข้อมูลใน vocabulary และโมเดล Unigram ที่เราสร้างจากการคำนวณความถี่ของแต่ละ token ใน corpus (อย่างที่เราได้เรียนกันแล้วข้างบน) +แต่ละคำใน corpus จะมีค่า score ของมัน ส่วนค่า loss เราจะคำนวณจาก negative log likelihood ของ score พวกนี้ ซึ่งเท่ากับ ผลรวมของ `-log(P(word))` ของทุกๆคำ + +กลับมาดูตัวอย่างกัน นี่คือ corpus ของเรา : + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +ข้างล่างนี้คือ segmentation ที่ดีที่สุดของแต่ละคำ และ score ของมัน ที่เราใช้โมเดล Ngram คำนวณ : + +``` +"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) +``` + +ดังนั้นค่า loss ก็คือ : + +``` +10 * (-log(0.071428)) + 5 * (-log(0.007710)) + 12 * (-log(0.006168)) + 4 * (-log(0.001451)) + 5 * (-log(0.001701)) = 169.8 +``` +จากนั้นเราจะคำนวณว่า ถ้าเราลบคำใดคำหนึ่งออก มันจะกระทบค่า loss อย่างไร +ขั้นตอนนี้ค่อนข้างซับซ้อน ดังนั้นเราใช้แค่ token สองตัวเป็นตัวอย่าง และจะเขียนโค้ดเพื่อช่วยคำนวณภายหลัง +ถ้าคุณยังจำได้ เรามีสอง segmentation ที่มี score เท่ากัน ตัวอย่างเช่น `"pug"` สามารถถูกแบ่งเป็น `["p", "ug"]` ได้ด้วย +ดังนั้น ถ้าเราลบ `"pu"` ออกจาก vocabulary เราก็จะยังได้ค่า loss เท่าเดิม +ในทางกลับกัน ถ้าเราลบ `"hug"` ออก ค่า loss จะเพิ่มสูงขึ้นมาก นั่นก็เพราะว่า ผลลัพธ์ของ `"hug"` และ `"hugs"` จะเปลี่ยนเป็น + +``` +"hug": ["hu", "g"] (score 0.006802) +"hugs": ["hu", "gs"] (score 0.001701) +``` + +การเปลี่ยนแปลงนี้ทำให้ score เปลี่ยนไป และค่า loss รวมก็จะสูงขึ้น : + +``` +- 10 * (-log(0.071428)) + 10 * (-log(0.006802)) = 23.5 +``` + +ดังนั้นเราจึงควรจะลบ `"pu"` ออก แต่เก็บ `"hug"` ไว้ + +## Implementing Unigram + +ในหัวข้อนี้ เราจะมา implement ทุกอย่างกัน +เช่นเดียวกับในตัวอย่างของ BPE และ WordPiece โค้ดที่เราจะสอนต่อไปนี้ไม่ใช่โค้ดที่มีประสิทธิภาพที่สุด เราเพียงต้องการให้คุณเข้าในการทำงานของอัลกอริทึมเท่านั้น +เราจะใช้ corpus เดียวกับตัวอย่างก่อนๆ : + +```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 เราเริ่มจากการนับจำนวน(ความถี่)ของแต่ละคำใน 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 +``` + +จากนั้น เราจะต้องสร้าง vocabulary ตั้งต้นให้ใหญ่กว่า ขนาด vocabulary ที่เราต้องการ + +vocabulary จะต้องมีตัวอักษรพื้นฐาน ไม่เช่นนั้นเราจะ tokenize ไม่ได้เลย ส่วน token ที่ใหญ่ขึ้น(subwords) เราจะใช้เฉพาะตัวที่พบบ่อยๆเท่านั้น ซึ่งเราจะเรียงลำดับพวกมันตามความถี่ ดังนี้ : + +```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)] +``` + +เราจะนำตัวอักษรและ subwords ที่มีความถี่สูงพวกนี้ มารวมกันเพื่อสร้าง vocabulary ตั้งต้น ขนาด 300 token : + +```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) ซึ่งมีประสิทธิภาพมากกว่า Ngram ในการสร้าง vocabulary ตั้งต้น + + + +ขั้นตอนต่อไป เราจะคำนวณผลรวมของความถี่ของทุกๆคำ เพื่อแปลงความถี่เป็นค่าความน่าจะเป็น + +สำหรับโมเดลของเรา เราจะคำนวณค่าลอการิทึมของความน่าจะเป็น แทนที่จะใช้ความน่าจะเป็นเท่านั้น เพราะว่ามันจะทำให้การคำนวณมีความเสถียรมากกว่า เมื่อเทียบกับการคูณจำนวนน้อยๆหลายๆจำนวน และมันยังช่วยทำให้การคำนวณค่า loss ทำได้ง่ายขึ้นด้วย : + +```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()} +``` + +ฟังก์ชันหลักของ tokenizer นี้คือการ tokenize คำ โดยใช้อัลกอริทึม Viterbi ช่วย +อย่างที่คุณได้เห็นมาแล้ว อัลกอริทึม Viterbi จะคำนวณหาการแบ่งคำที่ดีที่สุดให้กับแต่ละคำใน corpus ซึ่ง segmentation พวกนี้จะถูกเซฟไว้ใน list ชื่อ `best_segmentations` +สำหรับคำที่เราต้องการจะ tokenize ในแต่ละตำแหน่ง (เริ่มจาก 0 ถึง ความยาวของคำ) เราจะสร้าง dictionary ขึ้นมา ซึ่งมีคีย์สองตัว ตัวแรกคือ index ของตัวอักษรแรกของ token สุดท้ายใน segmentation ที่ดีที่สุดของคำนั้น และคีย์ตัวที่สองคือค่า score ของ segmentation ที่ดีที่สุด +คีย์ที่เก็บ index นี้ จะช่วยให้เราสามารถคำนวณ segmentation เต็มๆได้ หลังจากที่เราเพิ่มข้อมูลลงใน list แล้ว + +เริ่มจากเราจะ for loop สองครั้ง เพื่อค้นหาคำย่อยในคำหลักที่อาจจะอยู่ใน vocabulary โดย loop แรกเอาไว้หาตำแหน่งเริ่มต้นของคำย่อย ส่วน loop ที่สองเอาไว้หาตำแหน่งจบของคำย่อยนั้น +ถ้าเราเจอคำย่อยที่อยู่ใน vocabulary เราจะสร้าง segmentation ใหม่ขึ้นมาโดยแบ่งคำหลักตรงคำย่อยนี้ และก่อนที่จะบันทึก segmentation นี้ลงไป เราจะเทียบกับ score ของมันกับ score ของ segmentation ที่มีอยู่แล้วใน `best_segmentations` +หลังจาก loop จบ เราจะคำนวณหา segmentation ที่ดีที่สุดของคำ input โดยอ่านจากตำแหน่งสุดท้ายของคำไปหาต้นคำ และบันทึกคำย่อยที่ดีที่สุดเอาไว้สำหรับทุกๆตำแหน่ง : + +```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 +``` +ตอนนี้เราก็สามารถลองใช้โมเดลได้แล้ว : + +```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) +``` + +และเราก็ยังสามารถคำนวณค่า loss ได้อย่างง่ายดายอีกด้วย : + +```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 +``` + +มาเช็คผลลัพธ์การคำนวณ loss จากโมเดลของเรากัน : + +```python +compute_loss(model) +``` + +```python out +413.10377642940875 +``` + +การคำนวณ score ให้แต่ละ token ก็ไม่ยากเช่นกัน โดยเราจะคำนวณค่า loss หลังจากที่เราลบ 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 +``` + +ลองรันโค้ดกับ token ตัวอย่างดังนี้ : + +```python +scores = compute_scores(model) +print(scores["ll"]) +print(scores["his"]) +``` + +เนื่องจาก `"ll"` ถูกใช้ในการ tokenize คำว่า `"Hopefully"` ถ้าเราลบมันออก เราจะต้องใช้ `"l"` สองครั้งแทน ในกรณีนี้ ค่า loss จะสูงขึ้น +ส่วน `"his"` ถูกใช้แค่ในคำว่า `"This"` ซึ่งคำนี้ถูก tokenize ให้เป็นตัวมันเอง(ไม่มีการแบ่ง) หากเราลบมันออก `"his"` ก็จะไม่มีผลต่อค่า loss มาดูผลลัพธ์กัน : + +```python out +6.376412403623874 +0.0 +``` + + + +💡 วิธีการคำนวณแบบข้างบนนี้ถือว่าไม่มีประสิทธิภาพนัก ดังนั้น SentencePiece จะคำนวณค่า loss แบบคร่าวๆเท่านั้น เวลาที่เราลองลบ token แต่ละตัวออก โดยมันจะแทนที่ token นั้นด้วย segmentation ของมันแทนที่จะใช้ token เต็มๆ +การทำแบบนี้ช่วยให้เราสามารถคำนวณ score ของทุกๆตัวได้ภายในครั้งเดียว และยังสามารถคำนวณไปพร้อมๆกับค่า loss ได้อีกด้วย + + + +สิ่งสุดท้ายที่เราจะต้องทำก็คือ เพิ่ม token พิเศษที่โมเดลใช้ลงไปใน vocabulary จากนั้น loop จนกว่าเราจะลบ token ออกจาก vocabulary จนได้ขนาดที่เราพอใจ : + +```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()} +``` + +เมื่อเราต้องการจะ tokenize ประโยคหรือข้อความ สิ่งที่เราต้องทำก็คือทำการ pre-tokenization ข้อความนั้น แล้วรันฟังก์ชัน `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 หวังว่าถึงตอนนี้คุณจะรู้สึกว่าคุณเป็นผู้เชี่ยวชาญด้าน tokenizer ในระดับหนึ่งแล้ว ในบทถัดไปเราจะพูดถึงส่วนประกอบสำคัญของ 🤗 Tokenizers library และมาดูกันว่าคุณจะใช้มันเพื่อสร้าง tokenizer ของคุณเองได้อย่างไร diff --git a/chapters/th/chapter6/8.mdx b/chapters/th/chapter6/8.mdx new file mode 100644 index 000000000..8b8d62072 --- /dev/null +++ b/chapters/th/chapter6/8.mdx @@ -0,0 +1,597 @@ +# การสร้าง tokenizer ทีละขั้นตอน + + + +จากบทก่อนๆ คุณจะเห็นว่า การตัดคำ ประกอบไปด้วยหลายขั้นตอน : + +- Normalization (หรือ การปรับข้อความให้เป็นมาตรฐาน หมายถึงการทำความสะอาดข้อความ เช่น ลบช่องว่างหรือเครื่องหมายเน้นเสียง รวมถึงการทำ Unicode normalization และอื่นๆ) +- Pre-tokenization (ขั้นตอนก่อนตัดคำ หมายถึง การแยกข้อความออกเป็นคำๆ) +- ส่ง input เข้าไปในโมเดล (แยกคำที่ได้จากขั้นตอน pre-tokenization ออกเป็นคำย่อยหลายๆคำ) +- Post-processing (ขั้นตอนปรับแต่งผลลัพธ์ เช่น การใส่ token พิเศษของ tokenizer เข้าไปในผลลัพธ์, การสร้าง attention mask และ token type IDs) + +เพื่อเป็นการเตือนความจำ มาดูกระบวนการโดยรวมอีกครั้ง : + +
+The tokenization pipeline. + +
+ + +🤗 Tokenizers library เป็นเครื่องมือสำหรับช่วยดำเนินการขั้นตอนพวกนี้ โดยคุณสามารถผสมผสานเครื่องมือพวกนี้ตามความต้องการได้ +ในบทนี้เราจะมาเรียนวิธีสร้าง tokenizer ขึ้นมาตั้งแต่ต้น แทนที่จะเทรนจากตัวที่ถูก implement แล้วด้วยข้อมูลใหม่เท่านั้น อย่างที่เราได้ลองทำกันใน[บทที่ 2](/course/chapter6/2) เมื่อจบบทนี้ คุณจะสามารถสร้าง tokenizer แบบใดก็ได้ตามที่คุณต้องการ + + + +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) + +## การโหลด corpus + +ในการเทรน tokenizer ตัวใหม่ เราจะใช้ corpus เล็กๆ เพื่อที่การคำนวณจะได้รวดเร็ว +การเรียกใช้งาน corpus จะทำคล้ายๆกับวิธีที่เราใช้ใน[ตอนต้นของบทนี้](/course/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()` เป็น generator ที่จะ yield ข้อมูลในรูปแบบ batch โดยแต่ละ batch ประกอบไปด้วย 1,000 ข้อความ +🤗 Tokenizers สามารถเทรนได้จากไฟล์ข้อความโดยตรง ดังนั้นเราจะสร้างไฟล์ข้อความ ที่ประกอบไปด้วย ข้อความทั้งหมดจาก 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") +``` + +ต่อไปเราจะพาคุณสร้าง tokenizer แบบ BERT, GPT-2, and XLNet ของคุณเอง ทีละขั้นตอน +ซึ่งคุณก็จะได้ลองใช้งาน tokenization algorithm ต่างๆที่ได้เรียนมาแล้ว เช่น WordPiece, BPE, and Unigram มาเริ่มจาก BERT กัน + +## สร้าง WordPiece tokenizer ตั้งแต่เริ่มต้น + +ในการสร้าง tokenizer โดยใช้ 🤗 Tokenizers library เราจะเริ่มจากการสร้าง `Tokenizer` object จาก `model` และตั้งค่า attribute ต่างๆ ได้แก่ `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` เพื่อบอกโมเดลว่าให้มัน return ค่าอะไรหากมันเจอตัวอักษรที่มันไม่รู้จัก +ส่วน argument อื่นๆที่เราสามารถตั้งค่าได้ก็คือ `vocab` (แต่เนื่องจากเราจะเทรนโมเดล ทำให้ตอนนี้เรายังไม่ต้องตั้งค่านี้) และ `max_input_chars_per_word` ซึ่งหมายถึง ความยาวสูงสุดของแต่ละคำ (คำที่ยาวกว่าค่านี้จะถูกแบ่งเป็นหลายๆส่วน) + +ขั้นตอนแรกคือ normalization เนื่องจาก BERT เป็นโมเดลที่ได้รับความนิยมมาก เราจึงมี `BertNormalizer` เฉพาะ ซึ่งมี option ต่างๆดังนี้: `lowercase`, `strip_accents`, `clean_text` (ลบ control characters และแทนที่ช่องว่างที่อยู่ต่อกันหลายๆอันด้วยช่องว่างเดียว), และ `handle_chinese_chars` (เพิ่มช่องว่างในตัวอักษรจีน) + +เราจะมาเลียนแบบ `bert-base-uncased` tokenizer โดยตั้งค่า normalizer ดังนี้ : + +```python +tokenizer.normalizer = normalizers.BertNormalizer(lowercase=True) +``` + +แต่ปกติแล้ว สำหรับ tokenizer ใหม่ คุณอาจจะไม่สามารถใช้ normalizer ที่จาก 🤗 Tokenizers library ได้ ดังนั้นเราจะมาเรียนวิธีการสร้าง BERT normalizer เองกัน + +library นี้ มี normalizer เพื่อ `Lowercase` และเพื่อ `StripAccents` ซึ่งคุณสามารถรวมทั้งสองตัวได้โดยใช้ `Sequence` : + +```python +tokenizer.normalizer = normalizers.Sequence( + [normalizers.NFD(), normalizers.Lowercase(), normalizers.StripAccents()] +) +``` + +เราจะใช้ `NFD` Unicode normalizer ด้วย เพื่อที่จะให้ `StripAccents` สามารถหาสัญลักษณ์ accents ได้ และจะได้ลบพวกมันออก + +เพื่อเช็คผลลัพธ์ของ normalizer เราจะใช้ `normalize_str()` method จาก `normalizer` : + +```python +print(tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) +``` + +```python out +hello how are u? +``` + + + +**รายละเอียดเพิ่มเติม** ถ้าคุณทดลองใช้งาน normalizer ทั้งสองเวอร์ชันกับข้อความที่มีตัวอักษร unicode `u"\u0085"` คุณจะได้ผลลัพธ์ที่แตกต่างกัน +อย่างไรก็ตาม เราไม่อยากทำให้เวอร์ชันที่สร้างจาก `normalizers.Sequence` ของเรานั้นซับซ้อนเกินไป เราจึงไม่ใช้ Regex ที่ `BertNormalizer` ใช้เวลาที่ `clean_text` ถูกตั้งค่าเป็น `True` ซึ่งเป็นค่าตั้งต้น +แต่คุณไม่ต้องกังวลไป เพราะมันยังมีวิธีที่จะทำให้ผลลัพธ์ออกมาเป็นเหมือนกันโดยที่ไม่ต้องใช้ `BertNormalizer` นั่นคือโดยการเพิ่ม `normalizers.Replace` สองครั้ง เข้าไปใน `normalizers.Sequence` + + + +ขั้นตอนต่อไปคือ การ pre-tokenization เราจะใช้ `BertPreTokenizer` ที่ถูกสร้างมาแล้ว : + +```python +tokenizer.pre_tokenizer = pre_tokenizers.BertPreTokenizer() +``` + +หรือจะใช้ตัวที่เราสร้างขึ้นมาเองก็ได้ : + +```python +tokenizer.pre_tokenizer = pre_tokenizers.Whitespace() +``` + +โปรดทราบว่า `Whitespace` pre-tokenizer จะตัดข้อความตรงที่มีช่องว่าง และ รวมถึงตัวสัญลักษณ์ที่ไม่ใช้ตัวอักษร ตัวเลข หรือ underscore หรือพูดอีกแบบก็คือ มันจะแบ่งข้อความตรงที่มีช่องว่างและเครื่องหมายวรรคตอน นั่นเอง + +```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` pre-tokenizer : + +```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))] +``` + +เหมือนกับใน normalizer เราสามารถใช้ `Sequence` เพื่อรวมหลายๆ pre-tokenizers เข้าด้วยกัน : + +```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))] +``` + +ขั้นตอนต่อไปใน tokenization pipeline คือการใส่ input เข้าไปในโมเดล +เราได้สร้างโมเดลขึ้นมาแล้ว แต่เรายังคงต้องเทรนมัน ซึ่งเราจะใช้ `WordPieceTrainer` + +สิ่งสำคัญที่คุณต้องจำคือ เวลาสร้าง (instantiate) trainer ใน 🤗 Tokenizers คุณจะต้องเพิ่ม token พิเศษต่างๆ เข้าไปในเทรนเนอร์เอง ไม่เช่นนั้น มันจะไม่เพิ่ม token พวกนี้เข้าไปใน vocabulary เพราะว่า token พิเศษไม่ได้อยู่ใน training corpus : + +```python +special_tokens = ["[UNK]", "[PAD]", "[CLS]", "[SEP]", "[MASK]"] +trainer = trainers.WordPieceTrainer(vocab_size=25000, special_tokens=special_tokens) +``` + +นอกจากเราจะสามารถตั้งค่า `vocab_size` และ `special_tokens` แล้ว เรายังสามารถตั้งค่า `min_frequency` (ความถี่ขั้นต่ำของคำที่ต้องมี เพื่อที่จะได้ถูกเพิ่มลงไปใน vocabulary) หรือ `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) +``` + +สำหรับทั้งสองกรณี คุณสามารถทดลองใช้ tokenizer กับข้อความได้ โดยเรียกใช้ `encode()` method : + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.'] +``` + +`encoding` ที่เราได้คือ `Encoding` ที่ประกอบไปด้วยข้อมูลจำเป็นต่างๆสำหรับ tokenizer ได้แก่ `ids`, `type_ids`, `tokens`, `offsets`, `attention_mask`, `special_tokens_mask`, และ `overflowing` + +ขั้นตอนสุดท้ายของ pipeline คือ post-processing +เราจะเพิ่ม `[CLS]` ไว้ที่ตอนต้นของผลลัพธ์จากการ tokenize และ `[SEP]` ไว้ที่ตอนท้าย หรือหลังสิ้นสุดประโยค ถ้า input ของเรามีสองประโยค + +เราจะใช้ `TemplateProcessor` เพื่อช่วยเราทำ แต่ก่อนอื่นเราต้องหา ID ของ `[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) +``` + +เราจะมาสร้าง template ให้กับ `TemplateProcessor` โดยจะต้องกำหนดว่าเราต้องการให้มันประมวลผล input ที่เป็น ประโยคเดี่ยว และ input ที่มีสองประโยคอย่างไร +สำหรับทั้งสองกรณี เราจะต้องกำหนด token พิเศษ เพื่อแทนประโยคขึ้นมา สำหรับประโยคเดี่ยวหรือประโยคแรก เราจะใช้ `$A` ส่วนสำหรับประโยคที่สอง เราจะใช้ `$B` +สำหรับ token พิเศษพวกนี้และประโยค เราจะสร้าง token type ID ขึ้นมา โดยจะเขียน ID นี้หลังเครื่องหมาย colon + +template ของ 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)], +) +``` + +อย่าลืมว่า เราจะต้องให้ข้อมูลโมเดลเกี่ยวกับ ID ของ token พิเศษด้วย เพื่อที่โมเดลจะได้แปลงมันได้อย่างถูกต้อง + +เราจะกลับมาดูตัวอย่างก่อนหน้ากัน : + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.', '[SEP]'] +``` + +ถ้าเราใส่ input ที่เป็นสองประโยค เราจะได้ : + +```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] +``` + +ขั้นตอนสุดท้าย เราจะเพิ่ม decoder เข้าไปใน pipeline : + +```python +tokenizer.decoder = decoders.WordPiece(prefix="##") +``` + +มาดูผลลัพธ์ของ `encoding` กัน : + +```python +tokenizer.decode(encoding.ids) +``` + +```python out +"let's test this tokenizer... on a pair of sentences." +``` + +เยี่ยมมาก! ตอนนี้เราสามารถบันทึก tokenizer นี้เป็นไฟล์ JSON ได้แล้ว ดังนี้ : + +```python +tokenizer.save("tokenizer.json") +``` + +คุณสามารถโหลดไฟล์นี้ให้เป็น `Tokenizer` object ได้โดยใช้ `from_file()` method : + +```python +new_tokenizer = Tokenizer.from_file("tokenizer.json") +``` + +การจะนำ tokenizer นี้มาใช้ใน 🤗 Transformers เราจะต้อง wrap มันให้เป็น `PreTrainedTokenizerFast` ก่อน +โดยเราใช้ class ปกติ (ถ้า tokenizer ของเรามีโครงสร้างสอดคล้องกันโมเดลหลักเราที่จะใช้งาน) หรือ class ที่ถูกสร้างขึ้นมาแล้ว เช่น `BertTokenizerFast` +ในกรณีที่คุณสร้าง tokenizer ขึ้นมาเองอย่างที่เราได้สอนไว้ข้างต้น คุณจะต้องใช้ตัวเลือกแรก + +การจะ wrap tokenizer ของเราให้เป็น `PreTrainedTokenizerFast` เราจะต้องส่งผ่าน tokenizer ของเราเข้าไปเป็น `tokenizer_object` หรือ ส่งผ่านไฟล์ของ tokenizer เป็น `tokenizer_file` +สิ่งสำคัญอีกอย่างคือ คุณจะต้องส่งผ่านข้อมูลเกี่ยวกับ token พิเศษ ต่างๆให้โมเดลเอง เพราะว่า class นี้จะไม่สามารถ infer จาก `tokenizer` object ได้เอง ว่า token ตัวไหนเป็น mask token เช่น `[CLS]` : + +```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]", +) +``` + +ถ้าคุณใช้ class ที่เฉพาะเจาะจง เช่น `BertTokenizerFast` คุณเพียงแค่ต้องเพิ่มข้อมูลเกี่ยวกับ token พิเศษที่ต่างจากตัวที่โมเดลใช้อยู่แล้ว ในตัวอย่างของเรา เราไม่ได้ใช้ token พิเศษอื่น จึงไม่ต้องเพิ่มอะไร : + +```python +from transformers import BertTokenizerFast + +wrapped_tokenizer = BertTokenizerFast(tokenizer_object=tokenizer) +``` + +ตอนนี้เราก็สามารถใช้ tokenizer ที่เราได้สร้างขึ้นมาเอง เหมือนกับ tokenizer ตัวอื่นๆจาก 🤗 Transformers ได้แล้ว นอกจากนั้นคุณยังสามารถเซฟมันได้ โดยใช้ `save_pretrained()` หรืออัพโหลดมันไปที่ Hub โดยใช้ `push_to_hub()` + +หลังจากที่เราได้ดูกันแล้วว่าจะสร้าง WordPiece tokenizer อย่างไร เราจะมาดูกันว่า จะทำแบบเดียวกันกับ BPE tokenizer ได้อย่างไร เราจะอธิบายคร่าวๆเท่านั้น เพราะคุณได้เรียนรายละเอียดมาแล้ว และจะพูดถึงแค่ข้อแตกต่างเท่านั้น + +## การสร้าง BPE tokenizer ตั้งแต่เริ่มต้น + +เราจะมาสร้าง GPT-2 tokenizer กัน เช่นเดียวกับตัวอย่างของ BERT tokenizer เราจะเริ่มด้วยกัน initialize `Tokenizer` ด้วยโมเดล BPE : + +```python +tokenizer = Tokenizer(models.BPE()) +``` + +เราสามารถสร้างโมเดลนี้ด้วย vocabulary ที่มีอยู่ได้ โดยส่งผ่าน `vocab` และ `merges` แต่เราจะเทรนตั้งแต่ต้น แปลว่าเราไม่จำเป็นต้องทำขั้นตอนนี้ +เราไม่จำเป็นต้องกำหนด `unk_token` เพราะ GPT-2 ใช้ byte-level BPE + +นอกจากนั้น GPT-2 ยังไม่ใช้ normalizer อีกด้วย เราจึงจะข้ามขั้นตอนนี้ไปทำ pre-tokenization เลย : + +```python +tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False) +``` + +`add_prefix_space=False` หมายถึงเราจะไม่เพิ่มช่องว่างตรงต้นประโยค (ค่า default จะมีการเพิ่มช่องว่างนี้) + +มาดูผลลัพธ์ตัวอย่างของการ pre-tokenization กัน : + +```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 มันจะมี token พิเศษตัวเดียวคือ end-of-text token (อยู่ตรงท้ายของข้อความ) : + +```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 (เช่น ``) คุณก็สามารถตั้งค่ามันได้ด้วย `end_of_word_suffix` + +คุณสามารถเทรนด้วยไฟล์ข้อความได้ด้วย : + +```python +tokenizer.model = models.BPE() +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +มาดูผลลัพธ์ของการ tokenize ข้อความตัวอย่างกัน : + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['L', 'et', "'", 's', 'Ġtest', 'Ġthis', 'Ġto', 'ken', 'izer', '.'] +``` + +เราจะทำการ post-processing แบบ byte-level ให้กับ GPT-2 tokenizer ดังนี้ : + +```python +tokenizer.post_processor = processors.ByteLevel(trim_offsets=False) +``` + +`trim_offsets = False` แปลว่าเราจะไม่เปลี่ยนแปลงค่า offset ของ token ที่ขึ้นต้นด้วย `Ġ` ซึ่งแปลว่าตำแหน่งเริ่มต้นของ offset จะหมายถึง ช่องว่าง และไม่ใช่ตัวอักษรแรกของ token นั้น + +มาดูผลลัพธ์ของข้อความที่เราเพิ่งจะ encode กันไป ในตัวอย่างนี้ token ในตำแหน่งที่ 4 คือ `'Ġtest'` : + +```python +sentence = "Let's test this tokenizer." +encoding = tokenizer.encode(sentence) +start, end = encoding.offsets[4] +sentence[start:end] +``` + +```python out +' test' +``` + +สุดท้ายเราจะเพิ่มส่วนที่เป็น byte-level decoder : + +```python +tokenizer.decoder = decoders.ByteLevel() +``` + +เช็คดูอีกทีว่าผลลัพธ์ถูกต้องหรือไม่ : + +```python +tokenizer.decode(encoding.ids) +``` + +```python out +"Let's test this tokenizer." +``` + +เยี่ยมมาก! ตอนนี้คุณสามารถเซฟโมเดลได้ ส่วนขั้นต่อไปคือ เราจะ wrap 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 tokenizer บ้าง + +## การสร้าง Unigram tokenizer ตั้งแต่เริ่มต้น + +เราจะสร้าง XLNet tokenizer โดยเริ่มจาก initialize `Tokenizer` ด้วย Unigram model ! + +```python +tokenizer = Tokenizer(models.Unigram()) +``` + +คุณสามารถ initialize มันโดยส่งผ่าน vocabulary ที่มีอยู่เข้าไปด้วย + +ในขั้นตอน normalization โมเดล XLNet จะมีการแทนที่ตัวอักษรต่างๆ : + +```python +from tokenizers import Regex + +tokenizer.normalizer = normalizers.Sequence( + [ + normalizers.Replace("``", '"'), + normalizers.Replace("''", '"'), + normalizers.NFKD(), + normalizers.StripAccents(), + normalizers.Replace(Regex(" {2,}"), " "), + ] +) +``` + +โค้ดข้างบนนี้จะแทนที่ `` และ '' ด้วย " +และถ้ามีช่องว่างที่อยู่ต่อๆกัน มันจะถูกแปลงให้เป็นช่องว่างเดียว และสุดท้ายมันจะลบสัญลักษณ์ accent ออกด้วย + +pre-tokenizer ที่ใช้ใน SentencePiece tokenizer คือ `Metaspace` : + +```python +tokenizer.pre_tokenizer = pre_tokenizers.Metaspace() +``` + +มาดูผลลัพธ์ของการ pre-tokenization กับข้อความตัวอย่างกัน : + +```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 มีการใช้ token พิเศษอยู่จำนวนหนึ่ง : + +```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` คือ อย่าลืมกำหนด argument `unk_token` +นอกจากนั้นคุณยังสามารถตั้งค่า argument อื่นๆที่ต้องใช้กับ Unigram algorithm ได้ด้วย เช่น `shrinking_factor` (ค่าเริ่มต้นคือ 0.75) หรือ `max_piece_length` (ค่าเริ่มต้นคือ 16) + +เราสามารถเทรนจากไฟล์ข้อความได้ด้วย : + +```python +tokenizer.model = models.Unigram() +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +ลอง tokenize ตัวอย่างง่ายๆดู : + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.'] +``` + +XLNet จะเพิ่ม token พิเศษ `` ใส่ในตอนท้ายของประโยค และตั้งค่า type ID มันเป็น 2 เพื่อให้มันแตกต่างจาก token อื่น +การทำแบบนี้ถือว่าเป็นการ padding ทางด้ายซ้าย + +เพื่อจัดการกับ token พิเศษ เราจะต้องสร้าง template ขึ้นมา ก่อนอื่นเราต้องดูว่า ID ของ `` และ `` คืออะไร : + +```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 +``` + +นี่คือตัวอย่างการสร้าง template : + +```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)], +) +``` + +เราจะทดลองใช้งานมันโดยการ encode ประโยค input สองประโยค : + +```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] +``` + +จากนั้นตั้งค่า decoder เป็น `Metaspace` : + +```python +tokenizer.decoder = decoders.Metaspace() +``` + +ตอนนี้เราก็เสร็จแล้ว คุณสามารถเซฟ tokenizer และ wrap มันให้เป็น `PreTrainedTokenizerFast` หรือ `XLNetTokenizerFast` ก็ได้ ถ้าคุณต้องการใช้มันใน 🤗 Transformers + +สิ่งสำคัญอีกอย่างเวลาใช้ `PreTrainedTokenizerFast` ก็คือเราจะต้องบอก 🤗 Transformers library ว่าเราได้ทำการ 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", +) +``` + +อีกวิธีก็คือ : + +```python +from transformers import XLNetTokenizerFast + +wrapped_tokenizer = XLNetTokenizerFast(tokenizer_object=tokenizer) +``` + +ตอนนี้ คุณก็ได้เรียนรู้ขั้นตอนในการสร้าง tokenizer ขึ้นมาเองแล้ว โดยการใช้เครื่องมือจาก 🤗 Tokenizers library และได้เรียนวิธีการนำ tokenizer ของคุณไปใช้ใน 🤗 Transformers อีกด้วย diff --git a/chapters/th/chapter6/9.mdx b/chapters/th/chapter6/9.mdx new file mode 100644 index 000000000..2b0716c2d --- /dev/null +++ b/chapters/th/chapter6/9.mdx @@ -0,0 +1,11 @@ +# เรียนจบเรื่อง tokenizer แล้ว! + +เยี่ยมมาก คุณเรียนจบบทนี้แล้ว! + +หลังจากที่ได้เรียนเกี่ยวกับ tokenizer อย่างละเอียดแล้ว คุณจะ : + +- สามารถเทรน tokenizer ตัวใหม่ จาก tokenizer อีกตัวที่มีโครงสร้างอยู่แล้ว +- เข้าใจวิธีการใช้ค่า offsets เพื่อ map ตำแหน่งของ token ไปหาค่าช่วงตำแหน่งของมัน(span)ในข้อความหลัก +- รู้ความแตกต่างระหว่าง BPE, WordPiece, และ Unigram +- สามารถผสมผสานแต่ละเครื่องมือจาก 🤗 Tokenizers library เพื่อสร้าง tokenizer ของคุณเองได้ +- สามารถนำ tokenizer นั้นไปใช้ใน 🤗 Transformers library ได้ From aeaef1f12bd10735eb2c2f20a0d6aafd95361637 Mon Sep 17 00:00:00 2001 From: lewtun Date: Wed, 6 Jul 2022 14:57:56 +0200 Subject: [PATCH 22/51] Bump release (#274) --- chapters/en/chapter6/10.mdx | 2 +- chapters/ja/_toctree.yml | 22 + chapters/ja/chapter7/1.mdx | 32 + chapters/ja/chapter7/2.mdx | 1020 +++++++++++++++++++++++++++++ chapters/ja/chapter7/3.mdx | 1066 ++++++++++++++++++++++++++++++ chapters/ja/chapter7/4.mdx | 1023 +++++++++++++++++++++++++++++ chapters/ja/chapter7/5.mdx | 1063 ++++++++++++++++++++++++++++++ chapters/ja/chapter7/6.mdx | 927 ++++++++++++++++++++++++++ chapters/ja/chapter7/7.mdx | 1221 +++++++++++++++++++++++++++++++++++ chapters/ja/chapter7/8.mdx | 19 + chapters/ja/chapter7/9.mdx | 324 ++++++++++ chapters/pt/_toctree.yml | 5 + chapters/pt/chapter6/1.mdx | 14 + 13 files changed, 6737 insertions(+), 1 deletion(-) create mode 100644 chapters/ja/chapter7/1.mdx create mode 100644 chapters/ja/chapter7/2.mdx create mode 100644 chapters/ja/chapter7/3.mdx create mode 100644 chapters/ja/chapter7/4.mdx create mode 100644 chapters/ja/chapter7/5.mdx create mode 100644 chapters/ja/chapter7/6.mdx create mode 100644 chapters/ja/chapter7/7.mdx create mode 100644 chapters/ja/chapter7/8.mdx create mode 100644 chapters/ja/chapter7/9.mdx create mode 100644 chapters/pt/chapter6/1.mdx diff --git a/chapters/en/chapter6/10.mdx b/chapters/en/chapter6/10.mdx index 1c9514eea..6d488332d 100644 --- a/chapters/en/chapter6/10.mdx +++ b/chapters/en/chapter6/10.mdx @@ -96,7 +96,7 @@ Let's test what you learned in this chapter! correct: true }, { - text: "When a token is has the label of a given entity, any other following token with the same label is considered part of the same entity, unless it's labeled as the start of a new entity.", + text: "When a token has the label of a given entity, any other following token with the same label is considered part of the same entity, unless it's labeled as the start of a new entity.", explain: "That's the most common way to group entities together -- it's not the only right answer, though.", correct: true } diff --git a/chapters/ja/_toctree.yml b/chapters/ja/_toctree.yml index 0c72444af..af556d6b3 100644 --- a/chapters/ja/_toctree.yml +++ b/chapters/ja/_toctree.yml @@ -24,6 +24,28 @@ title: チャプター修了クイズ quiz: 4 +- title: 7. 主要な自然言語処理タスク + 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: Hugging Faceコースのイベント sections: - local: event/1 diff --git a/chapters/ja/chapter7/1.mdx b/chapters/ja/chapter7/1.mdx new file mode 100644 index 000000000..5856697ae --- /dev/null +++ b/chapters/ja/chapter7/1.mdx @@ -0,0 +1,32 @@ + + +# イントロダクション + +[第3章](/course/ja/chapter3)では、テキスト分類のためにモデルを微調整する方法を学びました。この章では、以下のような一般的な自然言語処理タスクに取り組みます。 + +- トークン分類 +- マスク言語モデリング(BERTのような) +- 要約 +- 翻訳 +- 因果言語モデリング事前学習(GPT-2など) +- 質問応答 + +{#if fw === 'pt'} + +これを行うには、第3章で学んだTrainer APIと🤗 Accelerateライブラリ、5章で学んだ🤗 Datasetsライブラリ、第6章で学んだ🤗 Tokenizersライブラリについて、すべて活用する必要があります。また、第4章で行ったように、結果をModel Hubにアップロードします。したがって、この章は本当にすべてが集約された章です。 + +各セクションは独立して読むことができ、Trainer APIや🤗 Accelerateを使った独自の学習ループでモデルを学習する方法が紹介されています。どのパートも自由にスキップできるので、最も興味のあるパートに集中してください。Trainer APIは裏で何が起こっているかを気にせずにモデルを微調整したりトレーニングしたりするのに最適です。一方、Accelerateを使ったトレーニングループでは、必要な部分をより簡単にカスタマイズすることができます。 + +{:else} + +これを行うには、第3章のKeras API、第5章の🤗 Datasetsライブラリ、第6章の🤗 Tokenizersライブラリでモデルのトレーニングについて学んだことをすべて活用する必要があります。また、第4章で行ったように、結果をモデルハブにアップロードすることになるので、この章はまさにすべてが集約された章と言えます。 + +各セクションは独立して読むことができます。 + +{/if} + + + +各セクションを順番に読んでいくと、共通するコードや文章がかなりあることに気がつくと思います。この繰り返しは意図的なもので、興味のあるタスクに飛び込んで(あるいは後で戻って)、完全な動作例を見つけることができるようにするためのものです。 + + diff --git a/chapters/ja/chapter7/2.mdx b/chapters/ja/chapter7/2.mdx new file mode 100644 index 000000000..c6fad9d03 --- /dev/null +++ b/chapters/ja/chapter7/2.mdx @@ -0,0 +1,1020 @@ + + +# トークン分類 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +最初に紹介するアプリケーションは、トークン分類です。この汎用的なタスクは「文中の各トークンにラベルを付ける」と定義可能な、以下のような問題を含みます。 + +- **固有表現認識(NER)**: 文中に含まれる人名、地名、組織名などの固有のエンティティを検出します。これは、固有エンティティを1クラス、固有エンティティなしを1クラスとして、各トークンにラベルを付与するタスクと定義できます。 +- **品詞タグ付け(POS)**: 文中の各単語を特定の品詞(名詞、動詞、形容詞など)に対応するものとしてマークします。 +- **チャンキング(chunking)**: 同じエンティティに属するトークンを見つけます。このタスク(POSやNERと組み合わせることができます)は、チャンクの先頭にあるトークンには一つのラベル(通常 `B-`)、チャンクの中にあるトークンには別のラベル(通常 `I-`)、どのチャンクにも属さないトークンには三つ目のラベル(通常 `O`)を付けることと定義できます。。 + + + +もちろん、トークン分類問題には他にも多くの問題があり、これらは代表的な例に過ぎません。このセクションでは、NERタスクでモデル(BERT)を微調整し、以下のような予測計算ができるようにします。 + + + + +One-hot encoded labels for question answering. + + + +あなたは学習済みモデルをHubで探したり、Hubにアップロードし、その予測値を[ここで](https://huggingface.co/huggingface-course/bert-finetuned-ner?text=My+name+is+Sylvain+and+I+work+at+Hugging+Face+in+Brooklyn)再確認することができます 。 + +## データの準備 + +まず最初に、トークン分類に適したデータセットが必要です。このセクションでは、[CoNLL-2003 dataset](https://huggingface.co/datasets/conll2003)を使います。このデータセットはロイターが配信するニュース記事を含みます。 + + + +💡 単語とそれに対応するラベルに分割されたテキストからなるデータセットであれば、ここで説明するデータ処理を自分のデータセットに適用することができます。独自のデータを `Dataset` にロードする方法について復習が必要な場合は、[第5章](/course/ja/chapter5) を参照してください。 + + + +### The CoNLL-2003 dataset + +CoNLL-2003のデータセットをロードするために、🤗 Datasetsライブラリの `load_dataset()` メソッドを使用します。 + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("conll2003") +``` + +これはデータセットをダウンロードしキャッシュします。[第3章](/course/ja/chapter3)でGLUE MRPC datasetを扱ったときと同じです。このオブジェクトを調べると、定義された列と、トレーニングセット、検証セット、テストセットの3つに分割されている事がわかります。 + +```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 + }) +}) +``` + +特に、このデータセットには、先に述べた3つのタスク用のラベル、NER、POS、チャンキングが含まれていることがわかります。他のデータセットとの大きな違いは、入力テキストが文や文書としてではなく、単語のリストとして表示されていることです(最後の列は`tokens`呼ばれていますが、これはトークン化前の入力で、まだサブワード トークン化のためにtokenizer処理する必要があるという意味で単語を含んでいます)。 + +それでは、学習セットの最初の要素を見てみましょう。 + +```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` の要素を含んでいます。各要素の型は、この `ner_feature` の `feature` 属性にあり、その `feature` の `names` 属性を見ることで名前のリストを確認する事ができます。 + + +```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'] +``` + +これらのラベルは[第6章](/course/ja/chapter6/3)で、`token-classification`パイプラインを学んだときに掘り下げましたが、簡単に復習しておきましょう。 + +- `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" のように2つの単語にまたがるエンティティは、最初の単語には `B-` ラベルが、2番目の単語には `I-` ラベルが付与されます。 + + + +✏️ **あなたの番です!** 同じ2つの文をPOSラベルまたはチャンキングラベルと一緒に出力してください。 + + + +### データの処理 + + + +いつものように、モデルが意味を理解できるようにするために、テキストはトークンIDに変換される必要があります。[第6章](/course/ja/chapter6/)で見たように、トークン分類タスクの場合の大きな違いは、入力があらかじめトークン化されていると言う事です。幸いなことに、tokenizer API はこの点をかなり簡単に処理できます。特別なフラグを指定して `tokenizer` に警告するだけです。 + +まず最初に、`tokenizer` オブジェクトを作成しましょう。前に述べたように、事前学習済みBERTモデルを使用する予定なので、関連するtokenizerをダウンロードしてキャッシュすることから始めます。 + +```python +from transformers import AutoTokenizer + +model_checkpoint = "bert-base-cased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +あなたは`model_checkpoint` を自由に置き換える事ができます。[Hub](https://huggingface.co/models) にある好きなモデルや、自分の端末に保存した事前学習済みモデルやtokenizerをで置き換えることができます。 + +唯一の制約は、tokenizerが 🤗 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`という2つのサブワードにトークン化されました。このため、入力とラベルの間にミスマッチが生じます。ラベルのリストには9つの要素しかありませんが、入力のリストには12のトークンがあります。特殊なトークンを考慮するのは簡単ですが(最初と最後にあることが分かっています)、すべてのラベルを適切な単語に揃えることも必要です。 + +幸い、高速なtokenizerを使っているので、🤗 Tokenizers のスーパーパワーにアクセスすることができ、それぞれのトークンを対応する単語に簡単にマッピングすることができます (これは [第6章](/course/ja/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: + # Start of a new 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: + # Special token + new_labels.append(-100) + else: + # Same word as previous token + label = labels[word_id] + # If the label is B-XXX we change it to 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] +``` + +見てわかるように、この関数は最初と最後の2つの特別なトークンに対して `-100` を追加し、2つのトークンに分割された単語に対して新たに `0` を追加しています。 + + + +✏️ **あなたの番です!** 研究者の中には、1つの単語には1つのラベルしか付けず、与えられた単語内の他のサブトークンに`-100`を割り当てることを好む人もいます。これは、多くのサブトークンに分割される長い単語が学習時の損失に大きく寄与するのを避けるためです。このルールに従って、ラベルと入力IDを一致させるように、前の関数を変更してみましょう。 + + + +データセット全体の前処理として、すべての入力をトークン化し、すべてのラベルに対して `align_labels_with_tokens()` を適用する必要があります。高速なtokenizerの速度を活かすには、たくさんのテキストを同時にトークン化するのがよいでしょう。そこで、サンプルのリストを処理する関数を書いて、 `Dataset.map()` メソッドに `batched=True` オプションを付けて使用することにしましょう。以前の例と唯一違うのは、tokenizerへの入力がテキストのリスト(この場合は単語のリストのリスト)である場合、 `word_ids()` 関数は単語IDが欲しいリストのインデックスを必要とするので、これも追加します。 + +```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章](/course/ja/chapter3)でやったような感じになりますね。 + +{#if fw === 'pt'} + +## Trainer API でモデルを微調整する + +実際に `Trainer` を使用するコードは、これまでと同じです。変更点は、データをバッチ化する方法と、指標を計算する関数だけです。 + +{:else} + +## Keras を使ってモデルを微調整する + +Kerasを使った実際のコードは、これまでとほとんど同じで、データをバッチ化する方法と、指標計算の関数が変わるだけです。 + +{/if} + + +### データ照合 + +[第3章](/course/ja/chapter3) にあるような `DataCollatorWithPadding` は入力 (入力 ID、アテンションマスク、トークンタイプ ID) のみをパディングするので使えません。ここでは、ラベルのサイズが変わらないように、入力と全く同じ方法でパディングを行います。値として `-100` を使用し、対応する予測値が損失計算で無視されるようにします。 + +これは全て [`DataCollatorForTokenClassification`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorfortokenclassification) によって行われます。DataCollatorWithPadding` と同様に、入力の前処理に使用される `tokenizer` を受け取ります。 + +{#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]]) +``` + +これをデータセットの1番目と2番目の要素のラベルと比較してみましょう。 + +```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'} + +見ての通り、2つ目のラベルのセットは最初のラベルの長さに `-100`s を使ってパディングされています. + +{:else} + +データ照合の準備ができました! +では、これを使って `tf.data.Dataset` を `to_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'} + +### モデルの定義 + +今回はトークン分類の問題を扱うので、 `TFAutoModelForTokenClassification` クラスを使用します。このモデルを定義する際に覚えておくべきことは、ラベルの数に関する情報を渡すことです。最も簡単な方法は `num_labels` 引数でその数を渡すことですが、このセクションの最初に見たような素敵な推論ウィジェットを動作させたい場合は、代わりに正しいラベルの対応関係を設定した方が良いでしょう。 + +id2label` と `label2id` という 2 つの辞書型データがあり、ID からラベル、ラベルから ID へのマッピングを設定することができます。 + +```py +id2label = {str(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, +) +``` + +[第3章](/course/ja/chapter3) で `TFAutoModelForSequenceClassification` を定義したときのように、モデルを作成すると、いくつかの重みが使われていない(事前学習済みモデルのヘッド部の重み)、他のいくつかの重みがランダムに初期化されている(新しく接続したトークン分類ヘッドの重み)、このモデルはトレーニングする必要があるという警告が表示されます。トレーニングはすぐにでも実行できますが、まずはこのモデルが正しい数のラベルを持つことを再確認しましょう。 + +```python +model.config.num_labels +``` + +```python out +9 +``` + + + +⚠️ ラベルの数が間違っているモデルがあると、後で `model.fit()` を呼び出すときによくわからないエラーが発生します。このエラーはデバッグの際に厄介なので、このチェックを必ず行い、期待通りのラベル数であることを確認してください。 + + + +### モデルの微調整 + +これでモデルを学習する準備が整いました! +しかし、その前にもう少しだけやるべきことがあります。Hugging Faceにログインし、学習用ハイパーパラメータを定義する必要があります。もしNotebookで作業しているなら、これを助ける便利な関数があります。 + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +これにより、Hugging Faceのログイン情報を入力するウィジェットが表示されます。 + +Notebookで作業していない場合は、ターミナルに次の行を入力するだけです。 + +```bash +huggingface-cli login +``` + +ログインしたら、モデルをコンパイルするために必要なものをすべて準備します。 + +🤗 Transformers は便利な `create_optimizer()` 関数を提供しており、重みの減衰と学習率の減衰を適切に設定した `AdamW` オプティマイザが得られます。この2つの設定は、組み込みの `Adam` オプティマイザと比較してあなたのモデルの性能を向上させるでしょう。 + +```python +from transformers import create_optimizer +import tensorflow as tf + +# Train in mixed-precision float16 +# Comment this line out if you're using a GPU that will not benefit from this +tf.keras.mixed_precision.set_global_policy("mixed_float16") + +# 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_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) +``` + +また、`compile()`に `loss`引数を与えないことにも注意してください。これは、モデルが内部的に損失を計算することができるからです。損失なしでコンパイルして、入力辞書にラベルを指定すると(私たちのデータセットで行っているように)、モデルはその内部損失を使用して学習し、それは選んだタスクとモデルタイプに適したものになるでしょう。 + +次に、学習中にモデルをHubにアップロードするための`PushToHubCallback`を定義し、そのコールバックでモデルをフィットさせます。 + +```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` 引数には、プッシュしたいリポジトリのフルネームを指定できます。 (特に、特定の組織(organization)にプッシュする場合はこの引数を使用する必要があります)。例えば、モデルを [`huggingface-course` organization](https://huggingface.co/huggingface-course) にプッシュする場合、 `hub_model_id="huggingface-course/bert-finetuned-ner"` を追加します。デフォルトでは、使用されるリポジトリはあなたの名前が使われ、設定した出力ディレクトリにちなんだ名前、例えば `"cool_huggingface_user/bert-finetuned-ner"` となります。 + + + +💡 使用している出力ディレクトリがすでに存在する場合は、プッシュしたいリポジトリのローカルクローンである必要があります。そうでない場合は、`model.fit()` を呼び出すときにエラーが発生し、新しい名前を設定する必要があります。 + + + +学習が行われている間、モデルが保存されるたびに(今回の例ではエポックごとに)バックグラウンドでHubにアップロードされることに注意してください。このようにしておけば、必要に応じて別のマシンで学習を再開することができます。この段階で、Model Hub上の推論ウィジェットを使ってモデルをテストし、友人と共有することができます。 + +これでトークン分類タスクのモデル微調整に成功しました。おめでとうございます! + +しかし、このモデルの実力はいかほどでしょうか?それを知るために、いくつかの指標を使って評価する必要があります。 + +{/if} + + +### 指標 + +{#if fw === 'pt'} + +`Trainer` にエポック毎に指標を計算させるためには、`compute_metrics()` 関数を定義する必要があります。これは予測とラベルの配列を受け取り、指標の名前と値を含む辞書を返す関数です。 + +トークン分類予測の評価に使われる伝統的な枠組みは[*seqeval*](https://github.com/chakki-works/seqeval)です。この指標を使うには、まず *seqeval* ライブラリをインストールする必要があります。 + + +```py +!pip install seqeval +``` + +そして、[第3章](/course/ja/chapter3) で行ったように `load_metric()` 関数で読み込むことができるようになります。 + +{:else} + +トークン分類予測の評価に使われる伝統的な枠組みは[*seqeval*](https://github.com/chakki-works/seqeval)です。この評価指標を使うには、まず*seqeval*ライブラリをインストールする必要があります。 + +```py +!pip install seqeval +``` + +そして、[第3章](/course/ja/chapter3) で行ったように `load_metric()` 関数で読み込むことができるようになります。 + +{/if} + +```py +from datasets import load_metric + +metric = load_metric("seqeval") +``` + +この指標は標準的な精度指標のように動作しません:実際にはラベルのリストを整数ではなく文字列として受け取るので、予測値とラベルを指標に渡す前に完全にデコードする必要があります。 + +それでは、どのように動作するか見てみましょう。まず、最初の学習サンプルに対するラベルを取得します。 + +```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]) +``` + +この指標は予測値のリスト(1つだけではない)とラベルのリストを受け取ることに注意してください。以下はその出力です。 + + +```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'} + +とても多くの情報を取得しています! +各エンティティの精度、再現率、F1スコア、そして総合的なスコアです。 + +私達の指標計算では、総合的なスコアのみを保持することにします。 +しかし、お望みなら`compute_metrics()`関数を微調整して、報告させたいすべての指標を返すこともできます。 + +この `compute_metrics()` 関数は、まず 最終レイヤーが出力するベクトルの最大値を予測値に変換します(最終レイヤーが出力する生の値は通常は確率に変換されますが、最大値は確率に変換しなくとも同じなので、softmax で確率に変換させる必要はありません)。次に、ラベルと予測値の両方を整数から文字列に変換する必要があります。ラベルが `-100` である値をすべて削除し、その結果を `metric.compute()` メソッドに渡します。 + +```py +import numpy as np + + +def compute_metrics(eval_preds): + logits, labels = eval_preds + predictions = np.argmax(logits, axis=-1) + + # Remove ignored index (special tokens) and convert to labels + 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} + +とても多くの情報を取得しています! +各エンティティの精度、再現率、F1スコア、そして総合的なスコアです。 +では、実際にモデルの予測値を使ってスコアを計算してみるとどうなるか見てみましょう。 + +TensorFlowは予測値を連結することを好みません。なぜなら、予測値は可変長であるからです。つまり、`model.predict()`をそのまま使うことはできないのです。しかし、だからといってここで止めるつもりはありません。一度にいくつかの予測をバッチで実行して取得し、それらを一つの大きな長いリストに連結し、マスキングやパディングを示す `-100` トークンを削除します。 + +```py +import numpy as np + +all_predictions = [] +all_labels = [] +for batch in tf_eval_dataset: + logits = model.predict(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'} + +### モデルを定義 + +今回はトークン分類の問題を扱うので、 `AutoModelForTokenClassification` クラスを使用します。このモデルを定義する際に覚えておくべきことは、ラベルの数に関する情報を渡すことです。最も簡単な方法は `num_labels` 引数でその数を渡すことですが、このセクションの最初に見たような素敵な推論ウィジェットを動作させたい場合は、代わりに正しいラベルの対応関係を設定した方が良いでしょう。 + +id2label` と `label2id` という 2 つの辞書型データがあり、ID からラベル、ラベルから ID へのマッピングを設定することができます。 + +```py +id2label = {str(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, +) +``` + +[第3章](/course/ja/chapter3) で `AutoModelForSequenceClassification` を定義したときのように、モデルを作成すると、いくつかの重みが使われていない(事前学習済みモデルのヘッド部の重み)、他のいくつかの重みがランダムに初期化されている(新しく接続したトークン分類ヘッドの重み)、このモデルはトレーニングする必要があるという警告が表示されます。トレーニングはすぐにでも実行できますが、まずはこのモデルが正しい数のラベルを持つことを再確認しましょう。 + +```python +model.config.num_labels +``` + +```python out +9 +``` + + + +⚠️ ラベルの数が間違っているモデルがあると、後で `model.fit()` を呼び出すときによくわからないエラー("CUDA error: device-side assert triggered"のようなエラー)が発生します。このようなエラーはユーザーから報告されるバグの原因として一番多いものです。このチェックを必ず行い、期待通りのラベル数であることを確認してください。 + + + +### モデルの微調整 + +これでモデルを学習する準備が整いました! +しかし、`Trainer`を定義する前に、最後に2つのことをする必要があります。 + +Hugging Faceにログインし、学習用ハイパーパラメータを定義する必要があります。もしNotebookで作業しているなら、これを助ける便利な関数があります。 + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +これにより、Hugging Faceのログイン情報を入力するウィジェットが表示されます。 + +Notebookで作業していない場合は、ターミナルに次の行を入力するだけです。 + +```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` を指定して、モデルを保存して各エポック終了時に評価し、その結果をモデルハブにアップロードすることを指示します。 + + +なお、 `hub_model_id` 引数でプッシュ先のリポジトリ名を指定できます (特に、特定の組織(organization)にプッシュする場合は、この引数を使用する必要があります)。例えば、[`huggingface-course` organization](https://huggingface.co/huggingface-course) にモデルをプッシュする場合、`TrainingArguments` に `hub_model_id="huggingface-course/bert-finetuned-ner"` を追加しました。 + +デフォルトでは、使用されるリポジトリはあなたの名前が使われ、設定した出力ディレクトリちなんだ名前、例えば今回の例では `"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 を返すので、それを検査したい場合は、このコマンドを使用します。 + +```python out +'https://huggingface.co/sgugger/bert-finetuned-ner/commit/26ab21e5b1568f9afeccdaed2d8715f571d786ed' +``` + +また、`Trainer`はすべての評価結果を含むモデルカードを起草し、アップロードします。この段階で、Model Hub上の推論ウィジェットを使ってモデルをテストし、友人と共有することができます。これで、トークン分類タスクのモデル微調整に成功しました。 +おめでとうございます! + +もう少し深く学習ループについて学びたい場合は、🤗 Accelerate を使って同じことをする方法を紹介します。 + +## カスタムトレーニングループ + +それでは、必要な部分を簡単にカスタマイズできるように、トレーニングループの全体像を見てみましょう。これは、[第3章](/course/ja/chapter3/4) で行ったこととよく似ていますが、評価のために少し変更が加えられています。 + +### トレーニングのための準備 + +まず、データセットから `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章](/course/ja/chapter3)を参照してください。 + + + +これで `train_dataloader` を `accelerator.prepare()` に送ったので、そのデータ長を用いて学習ステップ数を計算することができます。このメソッドはdataloaderの長さを変更するので、常に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` オブジェクトを作業フォルダに作成する必要があります。まず、まだログインしていなければHugging Faceにログインしてください。モデルに付与したいモデルIDからリポジトリ名を決定します。(`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) +``` + +これで `repo.push_to_hub()` メソッドを呼び出すことで、`output_dir` に保存したものをアップロードできるようになりました。これにより、各エポック終了時に中間モデルをアップロードすることができます。 + +### 学習ループ + +これで学習ループを書く準備ができました。 +評価部分を簡略化するため、`postprocess()` 関数を簡単に定義します。 +この関数は予測値とラベルを受け取って `metric` オブジェクトが期待するような文字列のリストに変換します。 + +```py +def postprocess(predictions, labels): + predictions = predictions.detach().cpu().clone().numpy() + labels = labels.detach().cpu().clone().numpy() + + # Remove ignored index (special tokens) and convert to labels + 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 +``` + +次に、トレーニングループを書きます。トレーニングの進捗を確認するためのプログレスバーを定義した後、ループは3つのパートに分かれます。 + +- 学習そのもの。`train_dataloader`に対する古典的な繰り返しで、モデルを前方に伝播させ、後方に逆伝播させ、最適化のステップを行います。 + +- 評価。モデルの出力をバッチで取得した後に、新しい事をします。2つのプロセスで入力とラベルを異なる形状にパディングしているかもしれないので、`gather()`メソッドを呼ぶ前に `accelerator.pad_across_processes()` を使って予測値とラベルを同じ形状にする必要があるのです。これを行わないと、評価がエラーになるか、永遠にハングアップします。そして、結果を `metric.add_batch()` に送り、評価ループが終了したら `metric.compute()` を呼び出します。 + +- 保存とアップロード。まずモデルとtokenizerを保存し、次に `repo.push_to_hub()` を呼び出します。引数 `blocking=False` を使って、🤗 Hub libraryに非同期処理でプッシュするように指示していることに注意してください。この指定をすると、トレーニングは通常通り行われ、この(長い時間のかかる)命令はバックグラウンドで実行されます。 + +以下は、トレーニングループの完全なコードです。 + + +```py +from tqdm.auto import tqdm +import torch + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Training + 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) + + # Evaluation + model.eval() + for batch in eval_dataloader: + with torch.no_grad(): + outputs = model(**batch) + + predictions = outputs.logits.argmax(dim=-1) + labels = batch["labels"] + + # Necessary to pad predictions and labels for being gathered + 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"] + }, + ) + + # Save and upload + accelerator.wait_for_everyone() + unwrapped_model = accelerator.unwrap_model(model) + unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) + if accelerator.is_main_process: + tokenizer.save_pretrained(output_dir) + repo.push_to_hub( + commit_message=f"Training in progress epoch {epoch}", blocking=False + ) +``` + +今回初めて🤗 Accelerateで保存されたモデルをご覧になる方のために、それに付随する3行のコードを少し点検してみましょう。 + +```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()` を呼び出しますが、このメソッドには `torch.save()` の代わりに `accelerator.save()` を使用するように指示します。 + +これが完了すると、`Trainer` で学習したものとほぼ同じ結果を得ることができるモデルができあがります。このコードを使って学習したモデルは [*huggingface-course/bert-finetuned-ner-accelerate*](https://huggingface.co/huggingface-course/bert-finetuned-ner-accelerate) で確認することができます。また、学習ループの微調整を試したい場合は、上に示したコードを編集することで直接実装することができます! + +{/if} + +## 微調整したモデルを使う + +Model Hubで微調整したモデルを推論ウィジェットで使用する方法は既に紹介しました。ローカル環境の`pipeline`で使用する場合は、モデル識別子を指定します。 + + +```py +from transformers import pipeline + +# Replace this with your own checkpoint +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/ja/chapter7/3.mdx b/chapters/ja/chapter7/3.mdx new file mode 100644 index 000000000..bbb115be2 --- /dev/null +++ b/chapters/ja/chapter7/3.mdx @@ -0,0 +1,1066 @@ + + +# マスク言語モデルの微調整 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Transformerモデルを含む多くのNLPアプリケーションでは、ハギング フェイス ハブから事前学習済みのモデルを取り出し、自分が実行したいタスク用のデータを直接使って微調整を行うだけでよいのです。事前学習に使われたコーパスと微調整に使うコーパスがあまり違わない限り、転移学習は通常良い結果を生み出します。 + +しかし、モデルのヘッド部だけを対象にタスクに特化したトレーニング行う前に、まずデータを使って言語モデルを微調整したいケースもいくつかあります。例えば、データセットに法的契約や科学論文が含まれている場合、BERTのような素のTransformerモデルは通常、コーパス内のドメイン特有の単語を稀なトークンとして扱うため、結果として満足のいく性能が得られない可能性があります。ドメイン内データで言語モデルを微調整することで、多くの下流タスクのパフォーマンスを向上させることができ、このステップは通常一度だけ行えばよいことになります。 + +このように、事前に学習した言語モデルをドメイン内データで微調整するプロセスは、通常_ドメイン適応_と呼ばれます。これは2018年に[ULMFiT](https://arxiv.org/abs/1801.06146)によって普及しました。転移学習をNLPで本当に使えるようにした最初のニューラルアーキテクチャ(LSTMがベース)の1つです。ULMFiTによるドメイン適応の例を下の画像に示します。このセクションでは、LSTMの代わりにTransformerを使って、同様のことを行います! + + +
+ULMFiT. + +
+ +このセクションの終わりには、以下のような文章を自動補完できる[マスク言語モデル](https://huggingface.co/huggingface-course/distilbert-base-uncased-finetuned-imdb?text=This+is+a+great+%5BMASK%5D.)がHub上にできていることでしょう。 + + + +それでは始めましょう! + + + + + +🙋 「マスク言語モデリング」や「事前学習済みモデル」という言葉に聞き覚えがない方は、[第1章](/course/ja/chapter1)でこれらの主要な概念をすべて動画付きで説明していますので、ぜひご覧になってください。 + + + +## マスク言語モデリング用の事前学習済みモデルの選択 + +まず、マスク言語モデリングに適した事前学習済みモデルを選びましょう。以下のスクリーンショットのように、[ハギング フェイス ハブ](https://huggingface.co/models?pipeline_tag=fill-mask&sort=downloads)の "Fill-Mask "フィルタを適用すると、候補のリストが表示されます。 + +
+Hub models. +
+ +BERT と RoBERTa モデルのファミリーが最もダウンロードされていますが、ここでは [DistilBERT](https://huggingface.co/distilbert-base-uncased) と呼ばれるモデルを使用することにします。このモデルは、下流タスクの性能をほとんど損なうことなく、より高速に学習させることができます。 + +このモデルは、[_知識蒸留_](https://en.wikipedia.org/wiki/Knowledge_distillation)と呼ばれる特別な技術を使用して訓練されました。この手法は、BERTのような大きな「教師モデル」が、それよりはるかに少ないパラメータを持つ「生徒モデル」の訓練を導くために使用されています。 + +知識蒸溜の詳細を説明すると、この章の内容から離れすぎてしまいますが、もし興味があれば、[_Natural Language Processing with Transformers_](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/ch05.html) (通称Transformers教科書)でそれについてすべて読むことができます。 + +{#if fw === 'pt'} + +それでは、`AutoModelForMaskedLM`クラスを使ってDistilBERTをダウンロードしてみましょう。 + +```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} + +それでは、`AutoModelForMaskedLM`クラスを使ってDistilBERTをダウンロードしてみましょう。 + +```python +from transformers import TFAutoModelForMaskedLM + +model_checkpoint = "distilbert-base-uncased" +model = TFAutoModelForMaskedLM.from_pretrained(model_checkpoint) +``` +このモデルがいくつのパラメータを持っているかは、`summary()` メソッドを呼び出すことで確認することができます。 + +```python +model(model.dummy_inputs) # Build the model +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} + +約6,700万パラメータを持つDistilBERTは、BERTの基本モデルよりも約2倍小さく、これは、学習時に約2倍のスピードアップに相当します(素晴らしい!)。それでは、このモデルが予測する、小さなテキストサンプルの最も可能性の高い完成形はどのような種類のトークンであるかを見てみましょう。 + +```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のtokenizerが必要なので、これもHubからダウンロードしましょう。 + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +tokenizerモデルがあれば、テキストサンプルをモデルに渡し、ロジットを抽出し、上位5つの候補を出力することができます。 + +{#if fw === 'pt'} + +```python +import torch + +inputs = tokenizer(text, return_tensors="pt") +token_logits = model(**inputs).logits +# Find the location of [MASK] and extract its logits +mask_token_index = torch.where(inputs["input_ids"] == tokenizer.mask_token_id)[1] +mask_token_logits = token_logits[0, mask_token_index, :] +# Pick the [MASK] candidates with the highest logits +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 +# Find the location of [MASK] and extract its logits +mask_token_index = np.argwhere(inputs["input_ids"] == tokenizer.mask_token_id)[0, 1] +mask_token_logits = token_logits[0, mask_token_index, :] +# Pick the [MASK] candidates with the highest logits +# We negate the array before argsort to get the largest, not the smallest, logits +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.' +``` + +出力から、モデルの予測は日常的な用語に言及していることがわかりますが、これは英語版ウィキペディアが基盤となっている事を考えれば驚くことではありません。では、この領域をもう少しニッチなもの、つまり、分裂している映画評価データセットに変えてみましょう。 + +## データセット + +ドメイン適応の例を示すために、私たちは有名な[Large Movie Review Dataset](https://huggingface.co/datasets/imdb) (略してIMDb)を使用します。これは、感情分析モデルのベンチマークによく使われる、映画のレビューのコーパスです。このコーパスでDistilBERTを微調整することで、言語モデルが事前学習したWikipediaの事実に基づくデータから、映画レビューのより主観的な要素に語彙を適応させることが期待されます。ハギング フェイス ハブのデータは、🤗 Datasetsの `load_dataset()` 関数で取得することができます。 + +```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' +``` + +そう、これらは確かに映画のレビューです。もしあなたが十分に年を取っているなら、最後のレビューにあるVHS版を所有しているというコメントさえ理解できるかもしれません😜! 言語モデリングにはラベルは必要ありませんが、`0`は否定的なレビュー、`1`は肯定的なレビューに対応することがもうわかりました。 + + + +✏️ **挑戦してみましょう!** `unsupervised` のラベルがついた分割データのランダムサンプルを作成し、ラベルが `0` や `1` でないことを確認してみましょう。また、`train` と `test` 用の分割データのラベルが本当に `0` か `1` のみかを確認することもできます。これはすべての自然言語処理の実践者が新しいプロジェクトの開始時に実行すべき、有用なサニティチェックです! + + + +さて、データをざっと見たところで、マスク言語モデリングのための準備に取りかかりましょう。[第3章](/course/ja/chapter3)で見たシーケンス分類のタスクと比較すると、いくつかの追加ステップが必要であることがわかるでしょう。さあ、始めましょう! + +## データの前処理 + + + +自己回帰言語モデリングでもマスク言語モデリングでも、共通の前処理として、すべての用例を連結し、コーパス全体を同じ大きさの断片に分割することが行われます。これは、個々のサンプルを単純にトークン化するという、私達の通常のアプローチとは全く異なるものです。なぜすべてを連結するのでしょうか?それは、個々の例文が長すぎると切り捨てられる可能性があり、言語モデリングタスクに役立つかもしれない情報が失われてしまうからです! + +そこで、まずいつものようにコーパスをトークン化します。ただし、トークン化する際に `truncation=True` オプションをtokenizerに_設定しない_ようにします。また、単語IDがあればそれを取得します([6章](/course/ja/chapter6/3)で説明した高速tokenizerを使用している場合はそうなります)。後で単語全体をマスキングするために必要になるからです。これをシンプルな関数にまとめ、ついでに `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 + + +# Use batched=True to activate fast multithreading! +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 メモリの量によって決まりますが、良い出発点はモデルの最大コンテキストサイズが何であるかを見ることです。これはtokenizerの `model_max_length` 属性を調べることで推測することができます。 + +```python +tokenizer.model_max_length +``` + +```python out +512 +``` + +この値は、チェックポイントに関連付けられた *tokenizer_config.json* ファイルから取得します。この場合、BERT と同様に、コンテキストサイズが 512 トークンであることが分かります。 + + + +✏️ ** あなたの番です! ** [BigBird](https://huggingface.co/google/bigbird-roberta-base) や [Longformer](hf.co/allenai/longformer-base-4096) などのいくつかの Transformer モデルは、BERT や他の初期の Transformer モデルよりずっと長いコンテキスト長を持っています。これらのチェックポイントのトークナイザーをインスタンス化して、`model_max_length` がそのモデルカード内に記載されているものと一致することを検証してください。 + + + +され、Google Colab内で利用可能なGPUで実験を行うために、メモリに収まるような少し小さめのものを選ぶことにします。 + +```python +chunk_size = 128 +``` + + + +なお、小さな断片サイズを使用すると、実際のシナリオでは不利になることがあるので、モデルを適用するユースケースに対応したサイズを使用する必要があります。 + + + +さて、ここからが楽しいところです。連結がどのように機能するかを示すために、トークン化されたトレーニングセットからいくつかのレビューを取り出し、レビュー毎のトークン数を出力してみましょう。 + +```python +# Slicing produces a list of lists for each feature +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' +``` + +そして、これらの例をすべてをシンプルな辞書内包表記を使って連結すると、次のようになります。 + +```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' +``` + +素晴らしい!全体の長さの裏付けがとれました。 + +では、連結されたレビューを `block_size` で指定されたサイズの断片に分割してみましょう。そのために、 `concatenated_examples` を繰り返し処理し、リスト内包表記を使用して各特徴のスライスを作成します。その結果、断片の辞書ができあがります。 + +```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' +``` + +この例でわかるように、一般的に最後の断片は最大断片サイズより小さくなります。これを扱うには、主に 2 つの方法があります。 + +* 最後の断片が `chunk_size` よりも小さければ削除する +* 最後の断片の長さが `chunk_size` と等しくなるまで、最後の断片にダミーデータを詰め込む + +ここでは、最初のアプローチをとります。上記のロジックをすべてひとつの関数にまとめ、トークン化されたデータセットに適用してみましょう。 + +```python +def group_texts(examples): + # Concatenate all texts + concatenated_examples = {k: sum(examples[k], []) for k in examples.keys()} + # Compute length of concatenated texts + total_length = len(concatenated_examples[list(examples.keys())[0]]) + # We drop the last chunk if it's smaller than chunk_size + total_length = (total_length // chunk_size) * chunk_size + # Split by chunks of 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() + } + # Create a new labels column + result["labels"] = result["input_ids"].copy() + return result +``` + +`group_texts()` の最後のステップでは、 `input_ids` 列のコピーである新しい `labels` 列を作成していることに注意してください。これから説明するように、マスク言語モデリングでは、入力部に含まれるランダムなマスクトークンを予測することが目的です。 `labels` 列は、言語モデルが学習の際に参考する真実の値を提供するために使用します。 + +それでは、信頼できる `Dataset.map()` 関数を使って、トークン化されたデータセットに `group_texts()` を適用してみましょう。 + +```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 + }) +}) +``` + +テキストをグループ化し、断片に分けたことで`train`と`test`のデータセットで25,000よりも多くのサンプルが生成されていることがわかります。これは、元のコーパスに複数のレビューを含むものがあるため _連続トークン_ を含むサンプルができたからです。このことは、断片の1つにある特別な `[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" +``` + +上の例では、高校に関する映画とホームレスに関する映画のレビューが2つ重複しているのがわかります。また、マスク言語モデリングのラベルがどのように見えるか確認してみましょう。 + +```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を`Trainer`APIで微調整する + +マスク言語モデルの微調整は、[第3章](/course/ja/chapter3)で行ったようなシーケンス分類モデルの微調整とほぼ同じです。唯一の違いは、各バッチのテキストに含まれるいくつかのトークンをランダムにマスクすることができる特別なデータコレーターが必要であることです。幸いなことに、🤗 Transformersにはこのタスクのために専用の `DataCollatorForLanguageModeling` が用意されています。私たちはtokenizerと、マスクするトークンの割合を指定する `mlm_probability` 引数を渡すだけでよいのです。ここでは、BERTで使用され、文献上でも一般的な選択である15%を選びます。 + +```python +from transformers import DataCollatorForLanguageModeling + +data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm_probability=0.15) +``` + +ランダムマスクがどのように機能するかを見るために、いくつかの例を data_collator に与えてみましょう。コレーターは辞書型 のリストを想定しており、各 辞書は連続したテキストの塊を表すので、まずデータセットを繰り返し処理してからコレーターにバッチを渡します。このデータコレーターは `"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_ と呼ばれます。単語全体をマスキングする場合、データコレーターを自作する必要があります。データコレーターとは、サンプルのリストを受け取り、それをバッチ変換する関数のことです。 + +今からやってみましょう! + +先ほど計算した単語IDを使って、単語のインデックスと対応するトークンのマップを作る事にします。どの単語をマスクするかをランダムに決めて、そのマスクを入力に適用します。なお、ラベルはマスクする単語を除いて全て`-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") + + # Create a map between words and corresponding token indices + 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) + + # Randomly mask words + 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 + + return default_data_collator(features) +``` + +{:else} + +```py +import collections +import numpy as np + +from transformers.data 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") + + # Create a map between words and corresponding token indices + 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) + + # Randomly mask words + 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 + + 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()` に置き換えると、与えられた単語からのトークンが常に一緒にマスクされることを確認できます。 + + + +これで2つのデータコレーターが揃いましたので、残りの微調整ステップは標準的なものです。Google Colabで、神話に出てくるP100 GPUを運良く割り当てられなかった場合、学習に時間がかかることがあります😭そこで、まず学習セットのサイズを数千事例までダウンサンプルします。心配しないでください、それでもかなりまともな言語モデルができますよ。🤗 Datasets 内のデータセットは[第5章](/course/ja/chapter5)で紹介した `Dataset.train_test_split()` 関数で簡単にダウンサンプリングすることができます。 + +```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をお持ちなら、この値を自由に増やしてください! + +次に必要なことは、ハギング フェイス ハブにログインすることです。このコードをnotebookで実行する場合は、次のユーティリティ関数で実行できます。 + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +上記を実行すると、入力ウィジェットが表示され、認証情報を入力することができます。また実行することもできます。 + +``` +huggingface-cli login +``` + +好みに応じて、ターミナルを起動し、そこからログインしてください。 + +{#if fw === 'tf'} + +一度ログインしたら、`tf.data`データセットを作成する事ができます。ここでは標準的なデータコレーターを使いますが、練習として全単語マスキングのコレーターを試して結果を比較することも可能です。 + +```python +tf_train_dataset = downsampled_dataset["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) + +tf_eval_dataset = downsampled_dataset["test"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=32, +) +``` + +次に、学習用ハイパーパラメータを設定し、モデルをコンパイルします。Transformersライブラリの `create_optimizer()` 関数を使用し、学習率が線形に減衰する性質を持つ `AdamW` オプティマイザを使用します。また、モデルの組み込みの損失を使用します。これは `compile()` の引数に損失が指定されていない場合のデフォルトであり、学習精度は `"mixed_float16"` に設定されます。Colabで割り当てられたGPUやお使いのGPUがfloat16のサポートをしていない場合は、この行をコメントアウトする必要があります。 + +さらに、各エポック後にモデルをハブに保存する `PushToHubCallback` をセットアップします。`hub_model_id` 引数で、プッシュしたいリポジトリの名前を指定します。(特に、組織を指定してプッシュする場合はこの引数を使用する必要があります)。例えば、モデルを [`huggingface-course` organization](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) + +# Train in mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") + +callback = PushToHubCallback( + output_dir=f"{model_name}-finetuned-imdb", tokenizer=tokenizer +) +``` + +これで `model.fit()` を実行する準備ができました。 +しかし、これを実行する前に言語モデルの性能を評価する一般的な指標である _パープレキシティ_ について簡単に見ておきましょう。 + +{:else} + +一度ログインしたら、`Trainer` の引数を指定できます。 + +```python +from transformers import TrainingArguments + +batch_size = 64 +# Show the training loss with every epoch +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` organization](https://huggingface.co/huggingface-course) にプッシュする場合、 `TrainingArguments` に `hub_model_id="huggingface-course/distilbert-finetuned-imdb"` を追加しています。デフォルトでは、使用するリポジトリはあなたの名前空間内にあり、設定した出力ディレクトリにちなんだ名前になるので、私たちの場合は `"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, +) +``` + +これで `trainer.train()` を実行する準備ができました。しかしその前に、言語モデルの性能を評価する一般的な指標である _パープレキシティ_ について簡単に見ておきましょう。 + +{/if} + +### 言語モデルのパープレキシティ + + + +テキストの分類や質問応答のように、ラベル付けされたコーパスを用いて学習する他のタスクとは異なり、言語モデリングでは明示的なラベルを一切持ちません。では、何が良い言語モデルなのか、どのように判断すればよいのでしょうか? + +携帯電話の自動補正機能のように、文法的に正しい文には高い確率で、無意味な文には低い確率で割り当てることができるものが良い言語モデルです。自動補正の失敗例として、携帯電話に搭載された自動補正モデルが、おかしな(そして往々にして不適切な)文章を生成している例がネット上に多数紹介されています。 + +{#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を使って微調整する + +`Trainer`で見たように、マスクされた言語モデルの微調整は[第3章](/course/ja/chapter3)のテキスト分類の例と非常によく似ています。実際、唯一のわずかな違いは特別なデータコレーターを使うことで、それはこのセクションの前半ですでに取り上げました! + +しかし、`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) + # Create a new "masked" column for each column in the dataset + 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", + } +) +``` + +あとは通常通りデータローダーをセットアップしますが、ここでは評価セットに 🤗 Transformers の `default_data_collator` を使用します。 + +```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, +) +``` + +トレーニングの前に最後にすることがあります。ハギング フェイス ハブにモデルリポジトリを作成することです。🤗 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' +``` + +それから、🤗 Hub の `Repository` クラスを使用してリポジトリを作成し、クローンします。 + +```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): + # Training + 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) + + # Evaluation + 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}") + + # Save and upload + accelerator.wait_for_everyone() + unwrapped_model = accelerator.unwrap_model(model) + unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) + if accelerator.is_main_process: + tokenizer.save_pretrained(output_dir) + repo.push_to_hub( + commit_message=f"Training in progress epoch {epoch}", blocking=False + ) +``` + +```python out +>>> Epoch 0: Perplexity: 11.397545307900472 +>>> Epoch 1: Perplexity: 10.904909330983092 +>>> Epoch 2: Perplexity: 10.729503505340409 +``` + +イケてる! +エポックごとにパープレキシティを評価し、複数回のトレーニングを実行した際の再現性を確保することができました + +{/if} + +## 微調整したモデルを使う + +微調整したモデルは、Hub上のウィジェットを使うか、🤗 Transformersの `pipeline` を使ってローカル環境で操作することができます。それでは後者に挑戦してみましょう。`fill-mask`パイプラインを使用してモデルをダウンロードします。 + +```python +from transformers import pipeline + +mask_filler = pipeline( + "fill-mask", model="huggingface-course/distilbert-base-uncased-finetuned-imdb" +) +``` + +そして「これは素晴らしい[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](/course/ja/chapter7/section6)では、GPT-2のような自己回帰モデルをゼロから学習する方法を学びます。もし、あなた自身のTransformerモデルをどうやって事前学習するか見たいなら、そちらに向かってください + + + +✏️ ** あなたの番です! ** ドメイン適応の利点を定量化するために、IMDbラベルの分類器を、訓練前と微調整したDistilBERTチェックポイントの両方で微調整してください。テキスト分類について復習が必要な場合は、[第3章](/course/ja/chapter3)をチェックしてみてください。 + + diff --git a/chapters/ja/chapter7/4.mdx b/chapters/ja/chapter7/4.mdx new file mode 100644 index 000000000..969647748 --- /dev/null +++ b/chapters/ja/chapter7/4.mdx @@ -0,0 +1,1023 @@ + + +# 翻訳 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +では、翻訳タスクに飛び込んでみましょう。これも[シーケンス間タスク](/course/chapter1/ja/7)で、ある配列から別の配列へという定式化が可能な問題ということです。その意味では、この問題は[要約](/course/chapter7/ja/6)にかなり近く、今回得る知識は、以下のような他のシーケンス間問題に適応させることができます。 + +- **スタイル転送**: あるスタイルで書かれた文章を別のスタイルに*翻訳*するモデルの作成(例:フォーマルな文章からカジュアルな文章、シェイクスピア英語から現代英語など) + +- **質問応答生成**: 与えられた文脈に沿って、質問に対する答えを生成するモデルを作成する。 + + + +2言語(またはそれ以上)のテキストの十分な大きさのコーパスがあれば、[因果言語モデリング](/course/chapter7/ja/6)で説明するように、新しい翻訳モデルをゼロから学習させることができます。しかし、mT5やmBARTのような多言語翻訳モデルを特定の言語ペアに合うように微調整したり、ある言語から別の言語への翻訳に特化したモデルを特定のコーパスに合うように微調整する方が、より速く翻訳モデルを作成できます。 + +このセクションでは、(多くのHugging Face社員は両方の言語を話すため)英語からフランス語に翻訳するように事前に学習したMarianモデルを、[KDE4データセット](https://huggingface.co/datasets/kde4)で微調整します。このデータセットは [KDE apps](https://apps.kde.org/) のローカライズファイルのデータセットです。 + +私たちが使うモデルは、実際にKDE4データセットを含む[Opus dataset](https://opus.nlpl.eu/) から取得したフランス語と英語のテキストの大規模なコーパスで事前学習されています。しかし、私たちが使う事前学習済みモデルは、事前学習中にそのデータを見ていたとしても、微調整の後、より良いバージョンを得ることができることが分かるでしょう。 + +これが終われば、以下のような予測が可能なモデルが完成します。 + + + + +One-hot encoded labels for question answering. + + + +前のセクションと同様に、以下のコードで学習してハブにアップロードする実際のモデルを[ここ](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr?text=This+plugin+allows+you+to+automatically+translate+web+pages+between+several+languages.)で見つけ、その予測結果をダブルチェックする事ができます。 + +## データの準備 + +翻訳モデルをゼロから調整・学習するためには、そのタスクに適したデータセットが必要です。前述したように、このセクションでは [KDE4 dataset](https://huggingface.co/datasets/kde4) を使用しますが、翻訳したい2つの言語の文のペアがあれば、自分のデータを使用するようにコードを適応させることは非常に簡単です。カスタムデータを `Dataset` にロードする方法を思い出したい場合は、[5章](/course/ja/chapter5) を参照してください。 + +### KDE4データセット + +いつものように、 `load_dataset()` 関数を使用してデータセットをダウンロードします。 + +```py +from datasets import load_dataset, load_metric + +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組の文がありますが、1回の分割で、独自の検証セットを作成する必要があります。[第5章](/course/ja/chapter5) で見たように、 `Dataset` には `train_test_split()` メソッドがあり、これを利用して検証セットの作成を行うことができます。再現性を高めるためにシードを用意します。 + +```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") +``` + +では、データセットの1つの要素を見てみましょう。 + +```py +split_datasets["train"][1]["translation"] +``` + +```python out +{'en': 'Default to expanded threads', + 'fr': 'Par défaut, développer les fils de discussion'} +``` + +必要な言語のペアで構成される2つの文の辞書を得ることができました。コンピュータサイエンスの専門用語を集めたこのデータセットの特殊性は、すべてフランス語で完全に翻訳されていることです。しかし、フランスのエンジニアは怠惰なので、コンピュータサイエンス特有の単語はほとんど英語単語のままで会話していることが多いです。例えば、"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."}] +``` + +このようなデータセットの特殊性を、微調整したモデルが拾い上げてくれるかどうか、興味深いところです。(ネタバレ注意:拾ってくれます) + + + + + +✏️ **あなたの番です!** フランス語でよく使われるもう1つの英単語は "email "です。学習データセットから、この単語を使った最初のサンプルを見つけてください。どのように翻訳されますか?同じ英文を学習済みモデルはどのように翻訳しているでしょうか? + + + +### データを加工する + + + +もうお分かりだと思いますが、テキストを全てトークンIDのセットに変換し、モデルが意味を理解できるようにする必要があります。このタスクのために、入力とターゲットの両方をトークン化する必要があります。最初のタスクは `tokenizer` オブジェクトを作成することです。 + +先に述べたように、今回は英語からフランス語に翻訳するように事前学習したMarianモデルを使用します。もしこのコードを他の言語のペアで試す場合は、モデルのチェックポイントを適応させてください。[Helsinki-NLP](https://huggingface.co/Helsinki-NLP) という組織が多言語で1000以上のモデルを提供しています。 + +```python +from transformers import AutoTokenizer + +model_checkpoint = "Helsinki-NLP/opus-mt-en-fr" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, return_tensors="tf") +``` + +また、`model_checkpoint` を [Hub](https://huggingface.co/models) にある好きなモデルや、事前学習したモデルやトークナイザーを保存したローカルフォルダに置き換えることができます。 + + + +💡 mBART、mBART-50、M2M100 などの多言語トークナイザーを使用している場合は、トークナイザーの `tokenizer.src_lang` と `tokenizer.tgt_lang` に入力元となる言語とターゲットとなる言語の正しい言語コード値を設定する必要があります。 + + + +データの準備はとても簡単です。入力言語は通常通り処理しますが、ターゲット言語についてはトークナイザーをコンテキストマネージャー `as_target_tokenizer()` の中にラップする必要があります。 + +Python のコンテキストマネージャーは `with` 文で導入され、2つの関連する操作をペアで実行するときに便利です。最も一般的な例は、ファイルを書き込んだり読み込んだりするときで、次のような命令の内部で実行されることがよくあります。 + +``` +with open(file_path) as f: + content = f.read() +``` + +上の例では、関連する2つの操作をペアとして実行することで、ファイルを開く動作と閉じる動作を実現しています。オープンされたファイル `f` に対応するオブジェクトは、 `with` の下にあるインデントされたブロックの中にのみ存在し、ファイルのオープンはそのブロックの前に、フェイルのクローズはブロックの最後に行われます。 + +私達のケースでは、コンテキストマネージャー `as_target_tokenizer()` は、インデントされたブロックが実行される前にトークナイザーを出力言語 (ここではフランス語) に設定し、その後、入力言語 (ここでは英語) に設定しなおします。 + +つまり、サンプルの前処理は次のようになります。 + +```python +en_sentence = split_datasets["train"][1]["translation"]["en"] +fr_sentence = split_datasets["train"][1]["translation"]["fr"] + +inputs = tokenizer(en_sentence) +with tokenizer.as_target_tokenizer(): + targets = tokenizer(fr_sentence) +``` + +もし、コンテキスト・マネージャの内部でターゲット言語をトークン化する処理を忘れると、入力トークナイザーによってトークン化されてしまい、Marian モデルの場合、全くうまくいきません。 + +```python +wrong_targets = tokenizer(fr_sentence) +print(tokenizer.convert_ids_to_tokens(wrong_targets["input_ids"])) +print(tokenizer.convert_ids_to_tokens(targets["input_ids"])) +``` + +```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`と`targets`はどちらも通常のキー(入力ID、アテンションマスクなど)を持つ辞書なので、最後のステップは入力の中に `"labels"` キーを設定することです。これはデータセットに適用する前処理関数で行います。 + +```python +max_input_length = 128 +max_target_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, max_length=max_input_length, truncation=True) + + # Set up the tokenizer for targets + with tokenizer.as_target_tokenizer(): + labels = tokenizer(targets, max_length=max_target_length, truncation=True) + + model_inputs["labels"] = labels["input_ids"] + return model_inputs +``` + +入力と出力に同様な最大長を設定していることに注意してください。扱うテキストはかなり短いと思われるので、128を使用しています。 + + + +💡 T5モデル(具体的には `t5-xxx` チェックポイントの1つ)を使用している場合、モデルはテキスト入力にタスクを示すプレフィックス、例えば `translate: English to French:` のような、タスクを示す接頭辞を持つテキスト入力であることを期待します。 + + + + + +⚠️私達はターゲット文のアテンションマスクはモデルが期待していないので、注意を払っていません。その代わり、パディングトークンに対応するラベルに`-100`に設定し、損失計算で無視されるようにします。私達は動的パディングを使用するのでこの処理は後でデータコレーターが行います。しかし、ここでパディングを使用する場合は、パディングトークンに対応する全てのラベルを`-100`に設定するように前処理関数を適応させる必要があります。 + + + +これで、データセットのすべての分割に対して、この前処理を一度に適用することができるようになりました。 + +```py +tokenized_datasets = split_datasets.map( + preprocess_function, + batched=True, + remove_columns=split_datasets["train"].column_names, +) +``` + +データの前処理が終わったので、次は学習済みモデルの微調整を行います! + +{#if fw === 'pt'} + +## Trainer API を用いてモデルを微調整する + + +実際に `Trainer` を使用するコードは、これまでと同じですが、1つだけ少し変更点があります。 +ここでは [`Seq2SeqTrainer`](https://huggingface.co/transformers/main_classes/trainer.html#seq2seqtrainer) を使用します。 +これは `Trainer` のサブクラスであり、評価を適切に処理するためで、`generate()` メソッドを使用して入力から出力を予測します。詳細は、指標計算の話をするときに詳しく掘り下げます。 + +まず最初に、微調整を行うための実際のモデルが必要です。ここでは、通常の `AutoModel` API を使用します。 + +```py +from transformers import AutoModelForSeq2SeqLM + +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{:else} + +## Kerasを使ったモデルの微調整 + +まず最初に、微調整を行うための実際のモデルが必要です。ここでは、通常の `AutoModel` API を使用します。 + +```py +from transformers import TFAutoModelForSeq2SeqLM + +model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint, from_pt=True) +``` + + + +💡 `Helsinki-NLP/opus-mt-en-fr`のチェックポイントにはPyTorch用の重みしかありません。 +`from_pretrained()` メソッドで `from_pt=True` という引数を指定せずにモデルをロードしようとするとエラーが発生します。 + +`from_pt=True` を指定すると、ライブラリはPyTorch の重みを自動的にダウンロードし、変換します。 +このように、🤗 Transformersではフレームワークの切り替えが非常に簡単です! + + + +{/if} + +なお、今回は翻訳タスクで学習したモデルを使用しており、実際にすでに使用することができますので、重みの欠落や新たに初期化されたものについての警告は出ていません。 + +### データの照合 + +動的バッチ処理用のパディングを行うために、データコレーターが必要になります。この場合、[第3章](/course/ja/chapter3) のような `DataCollatorWithPadding` を使うわけにはいきません。 + +なぜなら、それは入力(入力ID、アテンションマスク、トークンタイプID)だけをパディングするものだからです。私たちのラベルも、ラベルで遭遇する最大長にパディングされるべきです。そして、前述したように、これらのパディングされた値が損失計算で無視されるように、ラベルをパディングするために使用されるパディング値は、トークナイザーのパディングトークンではなく、`-100`であるべきです。 + +これはすべて [`DataCollatorForSeq2Seq`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorforseq2seq) によって行われます。`DataCollatorWithPadding` と同様に、入力の前処理に使用した `tokenizer` を受け取りますが、 `model` も受け取ります。これは、このデータコレーターがデコーダーの入力 ID を準備する役割も担うからです。この ID は、ラベルをシフトしたもので、先頭に特別なトークンが付加されています。このシフトはアーキテクチャによって若干異なるので、 `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]]) +``` + +また、デコーダーの入力IDを見ると、ラベルをシフトさせたものであることがわかります。 + +```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]]) +``` + +以下は、データセットの1番目と2番目の要素に対するラベルです。 + +```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 = tokenized_datasets["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) +tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=16, +) +``` + +{/if} + + +### 指標 + + + +{#if fw === 'pt'} + +`Seq2SeqTrainer` がスーパークラス `Trainer` に追加した機能は、評価や予測の際に `generate()` メソッドを使用することです。学習時には、モデルは `decoder_input_ids` を使用し、予測しようとするトークンの後ろのトークンを使用しないようにアテンションマスクをして、学習を高速化することができます。推論実行時には同様にこれらを使用することはできませんので、同じ設定でモデルを評価するのは良いアイデアです。 + +[第1章](/course/ja/chapter1/6) で見たように、デコーダはトークンを1つずつ予測して推論を行います。🤗 Transformersでは `generate()` メソッドが裏で動いています。`Seq2SeqTrainer` では、 `predict_with_generate=True` を設定すると、この手法を使って評価を行うことができるようになります。 + +{/if} + +翻訳に使われる伝統的な指標は、[BLEU score](https://en.wikipedia.org/wiki/BLEU) です。これはKishore Papineniらによって、[a 2002 article](https://aclanthology.org/P02-1040.pdf) で紹介された指標で、翻訳文がそのラベルにどれだけ近いかを評価するものです。 + +モデルの生成した出力文の明瞭度や文法的な正しさは測定しませんが、生成した出力に含まれるすべての単語がターゲットにも現れるように、統計的なルールを使用します。また、同じ単語の繰り返しが元文章にない場合はペナルティを与えるルール(モデルが「the the the the」のような文章を出力しないように)や、ターゲットより短い文章を出力するとペナルティを与えるルール(モデルが「the」のような文章を出力しないように)などがあります。 + +BLEUの弱点は、テキストがすでにトークン化されていることを前提としているため、異なるトークナイザーを使用するモデル間でスコアを比較することが困難な点です。そこで、現在翻訳モデルのベンチマークとして最もよく使われているのが[SacreBLEU](https://github.com/mjpost/sacrebleu)です。トークン化ステップを標準化することで、この弱点(およびその他の弱点)を解決しています。この指標を使うには、まずSacreBLEUライブラリをインストールする必要があります。 + +```py +!pip install sacrebleu +``` + +そして、[第3章](/course/ja/chapter3) で行ったように `load_metric()` で読み込むことができるようになります。 + +```py +from datasets import load_metric + +metric = load_metric("sacrebleu") +``` + +この指標はテキストを入力とターゲットとして受け取ります。同じ文でも複数の翻訳があることが多いので、複数の翻訳を受け入れるように設計されています。私たちが使っているデータセットは1つしか提供していませんが、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となりました。これはかなり良い結果です。 + +参考までに、["Attention Is All You Need" 論文](https://arxiv.org/pdf/1706.03762.pdf) のオリジナルのTransformerモデルは、英語とフランス語間の同様の翻訳タスクでBLEUスコア41.8を達成しました!(`count`や`bp`などの個々の指標の詳細は[SacreBLEUリポジトリ](https://github.com/mjpost/sacrebleu/blob/078c440168c6adc89ba75fe6d63f0d922d42bcfe/sacrebleu/metrics/bleu.py#L74)を参照してください)。 + +一方、翻訳モデルからよく出てくる2つの悪いタイプの予測(単語の繰り返しが多い、または短すぎる)で試すと、かなり悪い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` をすべて削除する必要があります。トークナイザーはパディングトークンに対しても自動的に同じ処理を行います。 + +モデルとデータセットを受け取り、それに対して指標を計算する関数を定義しましょう。長いシーケンスの生成には時間がかかるので、検証セットをサブサンプリングして、時間がかからないようにします。 + +```py +import numpy as np + + +def compute_metrics(): + all_preds = [] + all_labels = [] + sampled_dataset = tokenized_datasets["validation"].shuffle().select(range(200)) + tf_generate_dataset = sampled_dataset.to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=4, + ) + for batch in tf_generate_dataset: + predictions = model.generate( + input_ids=batch["input_ids"], attention_mask=batch["attention_mask"] + ) + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + labels = batch["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 + # In case the model returns more than the prediction logits + if isinstance(preds, tuple): + preds = preds[0] + + decoded_preds = tokenizer.batch_decode(preds, skip_special_tokens=True) + + # Replace -100s in the labels as we can't decode them + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + # Some simple post-processing + 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} + +これで、モデルを微調整する準備が整いました! + +### モデルの微調整 + +最初のステップは、ハギング フェイスにログインして、結果をModel Hubにアップロードすることです。そのための便利な機能がノートブックにあります。 + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +これは、ハギング フェイスのログイン情報を入力するウィジェットが表示されます。 +ノートブックで作業していない場合は、ターミナルで次の行を入力するだけです。 + +```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 RTX 20xx/V100またはそれ以降)で大幅なスピードアップを得ることができます。 + +```python +from transformers import create_optimizer +from transformers.keras_callbacks import PushToHubCallback +import tensorflow as tf + +# 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_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) + +# Train in mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +次に、[セクション2](/course/ja/chapter7/2)で見たように、学習中にモデルをHubにアップロードするための`PushToHubCallback`を定義し、そのコールバックとしモデルを単純にフィトするようにします。 + +```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` organization](https://huggingface.co/huggingface-course) にプッシュする場合、`Seq2SeqTrainingArguments` に `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"` を追加しています。 + +デフォルトでは、使用するリポジトリはあなたの名前空間内にあり、設定した出力ディレクトリの名前になります。ここでは `"sgugger/marian-finetuned-kde4-en-to-fr"` (このセクションの最初にリンクしたモデルです)となります。 + + + +💡 使用している出力ディレクトリがすでに存在する場合、プッシュしたいリポジトリのローカルクローンである必要があります。そうでない場合は、`model.fit()` を呼び出すときにエラーが発生するので、新しい名前を設定する必要があります。 + + + +最後に、トレーニングが終了した後の指標を見てみましょう。 + +```py +print(compute_metrics()) +``` + +``` +{'bleu': 57.334066271545865} +``` + +この段階で、モデルハブ上の推論ウィジェットを使って、モデルをテストしたり、友人と共有したりすることができます。これで、翻訳タスクのモデルの微調整が完了です。 + +おめでとうございます! + +{: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_model_id` 引数には、プッシュしたいリポジトリのフルネームを指定できます。(特に、組織にプッシュする場合は、この引数を使用する必要があります)。例えば、モデルを [`huggingface-course` organization`](https://huggingface.co/huggingface-course) にプッシュする場合、`Seq2SeqTrainingArguments` に `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"` を追加しています。デフォルトでは、使用するリポジトリはあなたの名前空間内にあり、設定した出力ディレクトリにちなんだ名前になります。 +この例では `"sgugger/marian-finetuned-kde4-en-to-fr"` となります。(このセクションの冒頭でリンクしたモデルです) + + + +💡 出力先ディレクトリがすでに存在する場合は、プッシュしたいリポジトリのローカルクローンである必要があります。そうでない場合は、`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_target_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_target_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()` メソッドを使って、最新版のモデルをアップロードしていることを確認します。また、`Trainer`はすべての評価結果を含むモデルカードを起草し、アップロードします。このモデルカードには、モデルハブが推論デモ用のウィジェットを選ぶのに役立つメタデータが含まれています。通常はモデルクラスから正しいウィジェットを推論できるので何も言う必要はありませんが、今回は同じモデルクラスがあらゆるシーケンス間問題に使えるので、翻訳モデルであることを指定しています。 + +```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' +``` + +この段階で、モデルハブ上の推論ウィジェットを使って、モデルをテストしたり、友人と共有したりすることができます。これで、翻訳タスクのモデルの微調整が完了しました。おめでとうございます! + +もう少し深く学習ループを学びたい場合に、🤗 Accelerateを使って同じことをする方法を紹介します。 + +{/if} + +{#if fw === 'pt'} + +## カスタムトレーニングループ + +それでは、必要な部分を簡単にカスタマイズできるように、トレーニングループの全体像を見てみましょう。これは、[セクション 2](/course/ja/chapter7/2) と [第3章](/course/chapter3/4) で行ったことと同じように見えます。 + + +### トレーニングのためのすべての準備 + +何度か見たことがあるはずなので、かなり手短にコードを見ていきましょう。まず、データセットから `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()` メソッドに送ることができます。もし、ColabノートブックでTPUの学習をしたい場合は、このコードを全てトレーニング関数に移動する必要があります。そしてトレーニング関数は、`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, +) +``` + +最後に、私たちのモデルをハブにプッシュするために、作業フォルダに `Repository` オブジェクトを作成する必要があります。まず、ハギングフェイスハブにログインしてください(まだログインしていない場合)。モデルに付与したいモデル ID からリポジトリ名を決定します。(`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) +``` + +これで `repo.push_to_hub()` メソッドを呼び出すことで、`output_dir` に保存したものをアップロードできるようになりました。これにより、各エポック終了時に中間モデルをアップロードすることができます。 + +### トレーニングループ + +これで完全なトレーニングループを書く準備ができました。評価パートを簡略化するため、この`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) + + # Replace -100 in the labels as we can't decode them. + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + # Some simple post-processing + decoded_preds = [pred.strip() for pred in decoded_preds] + decoded_labels = [[label.strip()] for label in decoded_labels] + return decoded_preds, decoded_labels +``` + +学習ループは [セクション 2](/course/ja/chapter7/2) や [第3章](/course/ja/chapter3) のものとよく似ていますが、評価パートで少し違いがあります。では、その部分に注目してみましょう! + +まず、予測の計算には `generate()` メソッドを使用していますが、これはベースモデルに対するメソッドです。🤗 Accelerateが`prepare()` メソッドで作成したラップモデルではありません。これがまずモデルをアンラップしてから、このメソッドを呼び出している理由です。 + +もう一つは、[トークン分類](/course/ja/chapter7/2) のように、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): + # Training + 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) + + # Evaluation + 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"] + + # Necessary to pad predictions and labels for being gathered + 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}") + + # Save and upload + accelerator.wait_for_everyone() + unwrapped_model = accelerator.unwrap_model(model) + unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) + if accelerator.is_main_process: + tokenizer.save_pretrained(output_dir) + repo.push_to_hub( + commit_message=f"Training in progress epoch {epoch}", blocking=False + ) +``` + +```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} + +## 微調整したモデルを使う + +モデルハブで微調整したモデルを推論ウィジェットで使用する方法は既に紹介しました。パイプラインで使用する場合は、モデル識別子を指定します。 + +```py +from transformers import pipeline + +# Replace this with your own checkpoint +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/ja/chapter7/5.mdx b/chapters/ja/chapter7/5.mdx new file mode 100644 index 000000000..3f83c6dad --- /dev/null +++ b/chapters/ja/chapter7/5.mdx @@ -0,0 +1,1063 @@ + + +# 要約 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +このセクションでは、Transformerモデルを使用して、長いドキュメントを要約する方法を見ていきます。これは、_文章要約_ として知られるタスクです。 これは、長い文章を理解したり、ドキュメントの主要なトピックを補足する一貫性のあるテキストを生成したりするなど、さまざまな能力を必要とするため、最も困難なNLPタスクの1つです。 ただし、テキストの要約は、うまく行けば、領域の専門家が長いドキュメントを詳細に読む負担を軽減することで、さまざまなビジネスプロセスをスピードアップできる強力なツールになります。 + + + +[ハギングフェイス ハブ](https://huggingface.co/models?pipeline_tag=summarization&sort=downloads)には、要約用に微調整されたさまざまなモデルがすでに存在しますが、これらのほとんどは英語のドキュメントにのみ適しています。 したがって、このセクションにひねりを加えるために、英語とスペイン語のバイリンガルモデルをトレーニングします。 このセクションの終わりまでに、ここに示すようなカスタマーレビューを要約できる[モデル](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es)ができあがります。 + + + +これから説明するように、これらの要約は、顧客が製品レビュー投稿時につけたタイトル文を使って学習されているため、簡潔です。 このタスクに適した多言語コーパスをまとめることから始めましょう。 + +## 多言語コーパスの準備 + +[Multilingual Amazon Reviews Corpus](https://huggingface.co/datasets/amazon_reviews_multi)を使用して、多言語要約器を作成します。このコーパスは、6つの言語でのAmazon製品レビューで構成されており、通常、多言語分類子のベンチマークに使用されます。 ただし、各レビューには短いタイトルが付いているため、モデルが学習対象とする要約文としてタイトルを使用できます。 開始するには、ハギングフェイス ハブから英語とスペイン語のサブセットをダウンロードしましょう。 + +```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 + }) +}) +``` + +ご覧の通り、各言語の `train` 分割には 200,000 件のレビューがあり、 `validation` と `test` 分割にはそれぞれ 5,000 件のレビューがあります。私達が内容を知りたいレビュー情報は `review_body` と `review_title` カラムに含まれています。[第5章](/course/ja/chapter5) で学んだ手法で、トレーニングセットからランダムにサンプルを取得する簡単な関数を作成し、いくつかの例を見てみましょう。 + +```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.' +``` + + + +✏️ ** あなたの番です! ** `Dataset.shuffle()` コマンドのランダムシードを変更して、コーパスの他のレビューも調べてみてください。もしあなたがスペイン語を話せるなら、`spanish_dataset` にあるいくつかのレビューを見て、タイトルも妥当な要約に見えるかどうか確かめてみてください。 + + + +このサンプルは、肯定的なレビューから否定的なレビューまで(そしてその中間にある全てのレビュー!)、一般的にオンラインで見られるレビューの多様性を示しています。 「meh」というタイトルはあまり有益な情報を示すタイトルではありませんが、他のタイトルはレビュー自体の適切な要約のように見えます。40万件のレビューすべてについて要約モデルをトレーニングすることは、単一の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本来のテーマに沿って、書評の要約に焦点を当てましょう。結局のところ、書籍はこの会社が設立された際の商品なのです! 2つの製品カテゴリ(`book` と `digital_ebook_purchase`) が当てはまるので、これらの製品について両言語でデータセットをフィルタリングしてみましょう。[第5章](/course/ja/chapter5) で見たように、 `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のような電子アプリケーションのようなものを参照している可能性があることがわかります。それでも、このドメインは要約モデルを学習させるのに適していると思われます。このタスクに適した様々なモデルを見る前に、最後のデータ準備として、英語とスペイン語のレビューを1つの `DatasetDict` オブジェクトとして結合する必要があります。🤗 Datasetsには便利な `concatenate_datasets()` 関数があり、(その名の通り)2つの `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) + +# Peek at a few examples +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つの単語しか出力しないようモデルを偏らせる可能性があります。下のプロットは単語の分布を示しており、タイトルが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のモデルを微調整してみましょう。 + +## 文章要約用モデル + + +考えてみれば、文章の要約は機械翻訳と似たような種類のタスクです。レビューのようなテキスト入力があり、それを入力文内の顕著な特徴をとらえた短いバージョンに「翻訳」したいのです。したがって、要約のためのほとんどのTransformerモデルは[第1章](/course/ja/chapter1)で最初に出会ったエンコーダとデコーダのアーキテクチャを採用しています。しかし、GPTモデル群のような例外もあり、少数ショット学習設定で使用することも可能です。以下の表は、要約のために微調整が可能な、よく使われる事前学習済みモデルの一覧です。 + +| Transformer model | Description | Multilingual? | +| :---------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------: | +| [GPT-2](https://huggingface.co/gpt2-xl) | Although trained as an auto-regressive language model, you can make GPT-2 generate summaries by appending "TL;DR" at the end of the input text. | ❌ | +| [PEGASUS](https://huggingface.co/google/pegasus-large) | Uses a pretraining objective to predict masked sentences in multi-sentence texts. This pretraining objective is closer to summarization than vanilla language modeling and scores highly on popular benchmarks. | ❌ | +| [T5](https://huggingface.co/t5-base) | A universal Transformer architecture that formulates all tasks in a text-to-text framework; e.g., the input format for the model to summarize a document is `summarize: ARTICLE`. | ❌ | +| [mT5](https://huggingface.co/google/mt5-base) | A multilingual version of T5, pretrained on the multilingual Common Crawl corpus (mC4), covering 101 languages. | ✅ | +| [BART](https://huggingface.co/facebook/bart-base) | A novel Transformer architecture with both an encoder and a decoder stack trained to reconstruct corrupted input that combines the pretraining schemes of BERT and GPT-2. | ❌ | +| [mBART-50](https://huggingface.co/facebook/mbart-large-50) | A multilingual version of BART, pretrained on 50 languages. | ✅ | + +この表からわかるように、要約のためのTransformerモデルの大半は(そして実際、ほとんどのNLPタスクも)単言語版です。これはタスクが英語やドイツ語のような利用可能なデータの多い「高リソース」言語である場合は良いのですが、世界中で使われている何千もの他の言語ではそうではありません。幸いなことに、mT5やmBARTのような多言語Transformerモデルもあります。これらのモデルは言語モデリングを使って事前に学習されますが、ひねりが加えられています。1つの言語のコーパスで学習するのではなく、50以上の言語のテキストで一度に共同学習しているのです! + +ここでは、T5をベースにテキストからテキストへのフレームワークで事前学習された興味深いアーキテクチャであるmT5に焦点を当てます。T5では、すべての自然言語処理タスクは「要約:」のようなプロンプト接頭辞で定式化され、生成されたテキストをプロンプトに適応させるようモデルに条件付けされます。下図に示すように、T5は非常に汎用性が高く、1つのモデルで多くのタスクを解決することができます! + +
+Different tasks performed by the T5 architecture. + +
+ +mT5は接頭辞を使用しませんが、T5の多用途性を共有し、多言語であるという利点があります。さて、モデルを選んだところで、学習用のデータの準備に取りかかりましょう。 + + + +✏️ ** あなたの番です! ** このセクションを終えたら、同じ手法でmBARTを微調整して、mT5がmBARTと比較してどの程度優れているかを見てみましょう。ボーナスポイントとして、英語のレビューだけでT5を微調整してみることもできます。T5には特別な接頭辞プロンプトがあるので、以下の前処理ステップでは入力例の前に`summarize:`を付ける必要があります。 + + + +## データの前処理 + + + +次のタスクはレビューとそのタイトルをトークン化しエンコードすることです。いつものように、事前に学習したモデルのチェックポイントに関連付けられたトークナイザーをロードすることから始めます。ここではチェックポイントとして `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]} +``` + +ここで、[第3章](/course/ja/chapter3) の最初の微調整の実験で遭遇した、おなじみの `input_ids` と `attention_mask` を見ることができます。これらの入力IDをトークナイザーの `convert_ids_to_tokens()` 関数でデコードして、どのようなトークナイザーなのかを見てみましょう。 + +```python +tokenizer.convert_ids_to_tokens(inputs.input_ids) +``` + +```python out +['▁I', '▁', 'loved', '▁reading', '▁the', '▁Hung', 'er', '▁Games', ''] +``` + +Unicodeの特殊文字 `▁` とシーケンスの終わりを意味するトークン `` は、SentencePieceトークナイザーを扱っていることを示しています。これは [第6章](/course/ja/chapter6) で説明したユニグラムセグメント化アルゴリズムに基づいています。ユニグラムは多言語コーパスに特に有効です。ユニグラムによりSentencePieceは口調、句読点、空白などに依存しなくなるので、日本語のように空白文字を持たない多くの言語に対して効果的になります。 + +このコーパスをトークン化するために、要約に関連する些細な問題に対処する必要があります。ラベルもテキストなので、モデルの最大コンテキストサイズを超える可能性があります。これは、レビューとそのタイトルの両方に切り詰めを適用して、過度に長い入力をモデルに渡さないようにする必要があることを意味します。🤗 Transformers のトークナイザーは、入力と並行してラベルをトークン化することができる便利な `as_target_tokenizer()` 関数を提供します。これは通常、まず入力をエンコードし、次にラベルを別の列としてエンコードする前処理関数の内部で、コンテキストマネージャーを使用して行われます。 + +以下は、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 + ) + # Set up the tokenizer for targets + with tokenizer.as_target_tokenizer(): + 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()` 自身で、レビューが最初にトークン化され、次に `as_target_tokenizer()` でタイトルがトークン化されていることがわかります。 + +`preprocess_function()` があれば、あとはこのコースで散々使ってきた便利な `Dataset.map()` 関数を使ってコーパス全体をトークン化するのは簡単なことです。 + +```python +tokenized_datasets = books_dataset.map(preprocess_function, batched=True) +``` + +さて、コーパスの前処理が終わったところで、要約によく使われるいくつかの指標を見てみましょう。これから見るように、機械が生成した文章の品質を測る際に、万能の手法は存在しません。 + + + +💡 上の `Dataset.map()` 関数で `batched=True` を使っていることにお気づきかもしれません。これはサンプルを1,000のバッチ(デフォルト)でエンコードし、🤗 Transformersの高速トークナイザーが持つマルチスレッド機能を利用できるようにするものです。可能であれば、前処理を最大限に活用するために `batched=True` を使ってみてください! + + + + +## 文章要約のための指標 + + + +このコースで取り上げた他のほとんどのタスクと比較して、要約や翻訳のようなテキスト生成タスクの性能測定はそれほど簡単ではありません。例えば、「ハンガーゲームを読むのが好きだ」というレビューがあったとして、「ハンガーゲームが大好きだ」「ハンガーゲームは素晴らしい読み物だ」など、有効な要約が複数存在します。明らかに、生成された要約とラベルの間にある種の完全な一致を適用することは良い解決策ではありません。私たちは皆、独自の文体を持っているので、そのような測定指標を用いては人間でさえうまくいかないでしょう。 + +要約のために、最もよく使われる指標の1つが[ROUGE score](https://en.wikipedia.org/wiki/ROUGE_(metric)) (Recall-Oriented Understudy for Gisting Evaluationの略)です。この指標の背後にある基本的な考え方は、生成された要約を、通常人間が作成するした参照要約のセットと比較することです。これをより正確にするために、次の2つの要約を比較したいとします。 + +```python +generated_summary = "I absolutely loved reading the Hunger Games" +reference_summary = "I loved reading the Hunger Games" +``` + +比較する一つの方法として、重複している単語の数を数えることが考えられますが、この場合、6個となります。しかし、これは少し粗いので、代わりにROUGEは重なり合った部分の _適合率_ と _再現率_ のスコアを計算することを基本としています。 + + + +🙋 もしあなたが適合率や再現率について初めて聞いたとしても心配しないでください。すべてを明らかにするために、いくつかの明確な例を一緒に見ていきましょう。これらの指標は通常分類タスクで遭遇するので、その分類タスクの場合に適合率と再現率がどのように定義されているかを理解したい場合は、 `scikit-learn` [guides](https://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html) をチェックアウトすることをお勧めします。 + + + +ROUGEでは、生成した要約に参照元の要約がどれだけ取り込まれたかを再現率で測定します。単語を比較するだけであれば、以下の式によって再現率を計算することができます。 + +$$ \mathrm{Recall} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, reference\, summary}} $$ + +上記の簡単な例では、この式は6/6 = 1の完全な再現率を与えます。つまり、参照要約のすべての単語がモデルによって生成されたことになります。これは素晴らしいことだと思うかもしれませんが、もし私達のモデルが生成した要約が「ハンガーゲームを一晩中読むのが本当に本当に好きだった」であったとしたらどうでしょう。この場合も完璧な再現率が得られますが、冗長であるため、間違いなくより悪い要約となります。このようなシナリオに対処するために、我々は私達は適合率も計算します。これはROUGEの文脈において、生成された要約がどれだけ関連していたかを測定するものです。 + +$$ \mathrm{Precision} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, generated\, summary}} $$ + +これを冗長な要約に適用すると、適合率は6/10 = 0.6となり、短い要約で得られた6/7 = 0.86よりもかなり悪くなります。実際には、通常、適合率と再現率の両方が計算され、そして、F1スコア(精度とリコールの調和平均)が報告されます。これは🤗 Datasetsで、まず `rouge_score` パッケージをインストールすることで簡単に行うことができます。 + +```py +!pip install rouge_score +``` + +そして、ROUGE指標を読み込みます。 + +```python +from datasets import load_metric + +rouge_score = load_metric("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は適合率、再現率、F1スコアの信頼区間を計算します。これらはここに表示されている `low`、`mid`、`high` の属性です。さらに、🤗 Datasetsは生成された要約と参照された要約を比較する際に、異なるタイプのテキストの粒度に基づいた様々なROUGEスコアを計算します。`rouge1`のバリエーションはユニグラムの重なり具合です。これは単語のオーバーラップを言い換えただけのもので、まさに上で説明したような指標です。これを確認するために、スコアの `mid` 値を引き出してみましょう。 + +```python +scores["rouge1"].mid +``` + +```python out +Score(precision=0.86, recall=1.0, fmeasure=0.92) +``` + +素晴らしい!適合率と再現率の数値が一致しました。では、他のROUGEスコアについてはどうでしょうか? +`rouge2` はビッグラム(単語のペアの重なり)の重なりを測定し、 `rougeL` と `rougeLsum` は生成されたサマリーと参照サマリーで最も長い共通部分文字列を探して、最も長くマッチする単語列を測定します。`rougeLsum` の "sum" は、 `rougeL` が個々の文の平均値として計算されるのに対し、この指標は要約全体に対して計算されるという事実を表している。 + + + +✏️ **あなたの番です! ** 生成と参照要約の独自の例を作成し、結果のROUGEスコアが精度とリコールの公式を基にした手動計算と一致するかどうかを確認することができます。ボーナスポイントとして、テキストをビッグラムに分割し、`rouge2` 指標の適合率と制限率を比較します。 + + + +このROUGEスコアを使ってモデルのパフォーマンスを追跡していきますが、その前に優れたNLP実践者がすべきこと、それは強力かつシンプルなベースラインを作成することです。 + +### 強力なベースラインの作成 + +テキスト要約の一般的なベースラインは、単純に記事の最初の3つのセンテンスを取ることで、しばしば _lead-3_ ベースラインと呼ばれます。文の境界を追跡するためにピリオドを使うこともできますが、このやり方は "U.S." や "U.N." のような頭字語では失敗します。そこで、このようなケースを処理するための優れたアルゴリズムを含む `nltk` ライブラリを使用することにします。このパッケージは、以下のように `pip` を用いてインストールすることができます。 + +```python +!pip install nltk +``` + +そして、句読点規則をダウンロードしてください。 + +```python +import nltk + +nltk.download("punkt") +``` + +次に、`nltk`からセンテンストークナイザーをインポートし、レビューの最初の3文を抽出する簡単な関数を作成します。テキストの要約では、各要約を改行で区切るのが慣例なので、これも含めて学習例でテストしてみましょう。 + +```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'} + +## Trainer API を使って mT5 を微調整する + +要約のためのモデルの微調整は、この章で取り上げた他のタスクと非常によく似ています。まず最初に行うべきことは、`mt5-small` チェックポイントから事前学習したモデルをロードすることです。要約はシーケンス間タスクなので、`AutoModelForSeq2SeqLM` クラスを使用してモデルをロードすることができます。これは自動的に重みをダウンロードし、キャッシュします。 + +```python +from transformers import AutoModelForSeq2SeqLM + +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{:else} + +## KerasでmT5を微調整する + +要約のためのモデルの微調整は、この章で取り上げた他のタスクと非常によく似ています。まず最初に行うべきことは、`mt5-small`チェックポイントから事前に学習したモデルをロードすることです。要約はシーケンス間タスクなので、`AutoModelForSeq2SeqLM`クラスでモデルをロードすれば、自動的に重みをダウンロードし、キャッシュすることができます。 + +```python +from transformers import TFAutoModelForSeq2SeqLM + +model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{/if} + + + +💡 下流のタスクでモデルを微調整に関する警告が表示されないことを不思議に思うかもしれませんが、それはシーケンス間タスクでは、ネットワークのすべての重みが保持されるからです。これを[第3章](/course/ja/chapter3)のテキスト分類モデルと比較してみましょう。テキスト分類モデルでは、事前学習したモデルの先頭をランダムに初期化したネットワークに置き換えています。 + + + +次に必要なのは、ハンギングフェイス ハブにログインすることです。このコードをノートブックで実行する場合は、次のユーティリティ関数で実行できます。 + +```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 +# Show the training loss with every epoch +logging_steps = len(tokenized_datasets["train"]) // batch_size +model_name = model_checkpoint.split("/")[-1] + +args = Seq2SeqTrainingArguments( + output_dir=f"{model_name}-finetuned-amazon-en-es", + evaluation_strategy="epoch", + learning_rate=5.6e-5, + per_device_train_batch_size=batch_size, + per_device_eval_batch_size=batch_size, + weight_decay=0.01, + save_total_limit=3, + num_train_epochs=num_train_epochs, + predict_with_generate=True, + logging_steps=logging_steps, + push_to_hub=True, +) +``` + +ここでは、 `predict_with_generate` 引数を設定し、各エポックの ROUGE スコアを計算できるように、評価中に要約を生成するように指示しました。[第1章](/course/ja/chapter1) で説明したように、デコーダはトークンを一つずつ予測して推論を行いますが、これはモデルの `generate()` メソッドによって実装されています。`predict_with_generate=True` を設定すると、 `Seq2SeqTrainer` がそのメソッドを使用して評価を行うようになります。また、学習率、エポック回数、重み減衰などのデフォルトのハイパーパラメータを調整し、 `save_total_limit` オプションを設定して、学習中のチェックポイントを3つまでしか保存しないようにしました。これはmT5の「小さい」バージョンでさえ、ハードディスクの容量を約1GB使用しており、保存するコピーを制限すれば、少し容量を節約することができるからです。 + +`push_to_hub=True` を指定すると、学習後にモデルを Hub にプッシュすることができます。ユーザープロファイルの下の、 `output_dir` で定義された場所にリポジトリが作成されます。なお、 `hub_model_id` 引数で、プッシュしたいリポジトリの名前を指定することができます。(特に、組織にプッシュする場合はこの引数を使用する必要があります)。例えば、モデルを [`huggingface-course` organization](https://huggingface.co/huggingface-course) にプッシュする場合、`Seq2SeqTrainingArguments` に `hub_model_id="huggingface-course/mt5-finetuned-amazon-en-es"` を追加しています。 + +次に必要なことは、学習中にモデルを評価できるように、 `compute_metrics()` 関数をトレーナーに提供することです。 +要約タスクでは、予測タスクのようにシンプルに `rouge_score.compute()` を呼ぶのと少し異なります。なぜなら、ROUGE スコアを計算する前に、出力とラベルをテキストにデコードする必要があるからです。以下の関数はまさにそれを行うもので、さらに `nltk` の `sent_tokenize()` 関数を利用して、要約文章を改行で区切るようにしています。 + +```python +import numpy as np + + +def compute_metrics(eval_pred): + predictions, labels = eval_pred + # Decode generated summaries into text + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + # Replace -100 in the labels as we can't decode them + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + # Decode reference summaries into text + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + # ROUGE expects a newline after each sentence + decoded_preds = ["\n".join(sent_tokenize(pred.strip())) for pred in decoded_preds] + decoded_labels = ["\n".join(sent_tokenize(label.strip())) for label in decoded_labels] + # Compute ROUGE scores + result = rouge_score.compute( + predictions=decoded_preds, references=decoded_labels, use_stemmer=True + ) + # Extract the median scores + result = {key: value.mid.fmeasure * 100 for key, value in result.items()} + return {k: round(v, 4) for k, v in result.items()} +``` + +{/if} + +次に、シーケンス間タスクのためのデータコレーターを定義する必要があります。mT5はエンコーダ・デコーダのTransformerモデルなので、バッチを準備する際の一つのちょっとした差異は、デコード中にラベルを右に1つシフトする必要があることです。これは、デコーダが以前の真実のラベルしか見ないようにするためで、現在や将来のラベルをモデルに記憶させないようにしうます。これは[因果言語モデリング](/course/ja/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` はデータセット内の 1 つの例を表している事を期待しています。したがって、データをコレーターに渡す前に期待通りの形式に変換する必要があります。 + +```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]])} +``` + +ここで注目すべきは、最初の例は2番目の例よりも長いので、2番目の例の `input_ids` と `attention_mask` は右側に `[PAD]` トークン (ID は `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 スコアが増加するのが分かるはずです。学習が完了したら、`Trainer.evaluate()`を実行して最終的な ROUGE スコアを確認することができます。 + +```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のベースラインを見事に上回ったことがわかります。いいですね! +最後に、以下のようにモデルの重みをハブにプッシュします。 + +``` +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` を指定することで、Hub 上のウィジェットが mT5 アーキテクチャに関連付けられたデフォルトのテキスト生成用ではなく、要約パイプライン用のものになることも確認できます (モデルタグに関する詳細については、 [🤗 Hub documentation](https://huggingface.co/docs/hub/main#how-is-a-models-type-of-inference-api-and-widget-determined)を参照してください)。 +`trainer.push_to_hub()` の出力は Git のコミットハッシュへの URL で、モデルリポジトリに加えられた変更を簡単に確認することができます! + +このセクションの最後に、🤗 Accelerate が提供する低レベルの機能を使って mT5 を微調整することもできる方法を見てみましょう。 + +{:else} + +トレーニングの準備はほぼ整いました。あとは上で定義したデータコレーターを使ってデータセットを `tf.data.Dataset`s に変換し、モデルを `compile()` と `fit()` するだけです。まず、データセットです。 + +```python +tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=8, +) +tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=8, +) +``` + +ここで、学習用ハイパーパラメータを定義し、コンパイルします。 + +```python +from transformers import create_optimizer +import tensorflow as tf + +# 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_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) + +# Train in mixed-precision 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`を実行する必要があるかもしれませんので注意してください)。 + +```python +from tqdm import tqdm +import numpy as np + +all_preds = [] +all_labels = [] +for batch in tqdm(tf_eval_dataset): + predictions = model.generate(**batch) + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + labels = batch["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 を使って微調整する + +🤗 Accelerateを使ったモデルの微調整は、[第3章](/course/ja/chapter3)で行ったテキスト分類の例と非常によく似ています。主な違いは、学習時に要約を明示的に生成する必要があることと、ROUGEスコアの計算方法を定義することです(`Seq2SeqTrainer`が生成の面倒をみてくれたことを思い出してください)。では、この2つの要件を🤗 Accelerateでどのように実装するか見てみましょう。 + +### トレーニングのための準備 + +まず最初に行うべきことは、各分割に対して `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章](/course/ja/chapter3)を参照してください。 + + + +さて、オブジェクトの準備ができたので、残すは3つです。 + + +* 学習率のスケジュールを定義する。 +* 評価用の要約を後処理する関数を実装する。 +* ハブ上にモデルをプッシュできるリポジトリを作成する。 + +学習率のスケジュールには、前節までの標準的な線形なものを使うことにします。 + +```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 expects a newline after each sentence + preds = ["\n".join(nltk.sent_tokenize(pred)) for pred in preds] + labels = ["\n".join(nltk.sent_tokenize(label)) for label in labels] + + return preds, labels +``` + +これは、 `Seq2SeqTrainer` の `compute_metrics()` 関数をどのように定義したかを思い出せば、見覚えがあるはずです。 + +最後に、ハギングフェイス ハブにモデルリポジトリを作成する必要があります。これには、適切なタイトルの🤗 ハブ ライブラリを使用します。 +私たちは、リポジトリの名前を定義する必要があるだけです。このライブラリには、リポジトリ ID とユーザプロファイルを組み合わせるユーティリティ関数があります。 + +```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' +``` + +このリポジトリ名を使って、resultsディレクトリにローカルバージョンをクローンし、学習用成果物を格納します。 + +```python +from huggingface_hub import Repository + +output_dir = "results-mt5-finetuned-squad-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +これにより、トレーニング中に `repo.push_to_hub()` メソッドを呼び出すことで、成果物をハブにプッシュバックすることができます! +それでは、トレーニングループを書き出し、分析を終えましょう。 + +### 学習ループ + +要約のためのトレーニングループは、私たちが遭遇した他の🤗 Accelerateの例と非常によく似ており、大きく4つの主要なステップに分かれています。 + +1. 各エポックごとに `train_dataloader` にあるすべての例に対して繰り返し処理を行い、モデルを学習させる。 +2. 各エポック終了時に、まずトークンを生成し、それをデコードしてテキストにすることでモデルの要約を生成する。(参考要約も)。 +3. 先に見たのと同じ手法でROUGEスコアを計算する。 +4. チェックポイントを保存し、すべてをハブにプッシュする。ここでは、エポック毎にチェックポイントを _非同期_ にプッシュできるように、`Repository` オブジェクトの `blocking=False` という便利な引数に頼っています。これにより、GBサイズのモデルで発生する遅いアップロードを待つことなく、学習を継続することができるようになりました。 + +これらの手順は、以下のコードブロックのようになります。 + +```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): + # Training + model.train() + for step, batch in enumerate(train_dataloader): + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) + + # Evaluation + model.eval() + for step, batch in enumerate(eval_dataloader): + with torch.no_grad(): + generated_tokens = accelerator.unwrap_model(model).generate( + batch["input_ids"], + attention_mask=batch["attention_mask"], + ) + + generated_tokens = accelerator.pad_across_processes( + generated_tokens, dim=1, pad_index=tokenizer.pad_token_id + ) + labels = batch["labels"] + + # If we did not pad to max length, we need to pad the labels too + labels = accelerator.pad_across_processes( + batch["labels"], dim=1, pad_index=tokenizer.pad_token_id + ) + + generated_tokens = accelerator.gather(generated_tokens).cpu().numpy() + labels = accelerator.gather(labels).cpu().numpy() + + # Replace -100 in the labels as we can't decode them + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + if isinstance(generated_tokens, tuple): + generated_tokens = generated_tokens[0] + decoded_preds = tokenizer.batch_decode( + generated_tokens, skip_special_tokens=True + ) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + decoded_preds, decoded_labels = postprocess_text( + decoded_preds, decoded_labels + ) + + rouge_score.add_batch(predictions=decoded_preds, references=decoded_labels) + + # Compute metrics + result = rouge_score.compute() + # Extract the median ROUGE scores + result = {key: value.mid.fmeasure * 100 for key, value in result.items()} + result = {k: round(v, 4) for k, v in result.items()} + print(f"Epoch {epoch}:", result) + + # Save and upload + accelerator.wait_for_everyone() + unwrapped_model = accelerator.unwrap_model(model) + unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) + if accelerator.is_main_process: + tokenizer.save_pretrained(output_dir) + repo.push_to_hub( + commit_message=f"Training in progress epoch {epoch}", blocking=False + ) +``` + +```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} + +## あなたの微調整したモデルを使用する + +モデルをハブにプッシュしたら、推論ウィジェットか `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/ja/chapter7/6.mdx b/chapters/ja/chapter7/6.mdx new file mode 100644 index 000000000..ad4dccae9 --- /dev/null +++ b/chapters/ja/chapter7/6.mdx @@ -0,0 +1,927 @@ + + +# 因果言語モデルを一から学習 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +今までは、事前学習したモデルを使い、事前学習時の重みを再利用して新しい用途向けに微調整を行うことがほとんどでした。[第1章](/course/ja/chapter1)で見たように、これは一般的に _転移学習_ と呼ばれ、ラベル付きデータがあまりない実世界のほとんどの用途でTransformerモデルを適用するための非常に成功した戦略です。この章では、別のアプローチで、全く新しいモデルをゼロから学習します。これは多くのデータを持っている場合に取るべき良いアプローチで、利用可能なモデルに使われる事前学習データとは全く異なります。しかし、言語モデルの事前学習には、既存のモデルを微調整するよりも、かなり多くの計算リソースが必要になります。例えば、音符やDNAなどの分子配列、プログラミング言語などのデータセットに新しいモデルを学習させることが有効な場合があります。後者については、OpenAIのCodexモデルを搭載したTabNineやGitHubのCopilotのような、長いコード列を生成できるツールが最近人気を集めています。このテキスト生成のタスクは、GPT-2のような自己回帰型言語モデルや因果関係言語モデルで対応するのが最適です。 + +このセクションでは、コード生成モデルの縮小版を構築します。Pythonコードのサブセットを使用して、完全な関数やクラスではなく、1行の補完に焦点を当てます。Pythonでデータを扱うとき、`matplotlib`, `seaborn`, `pandas`, `scikit-learn` ライブラリからなるPythonデータサイエンススタックと頻繁に接触することになります。これらのフレームワークを使うとき、特定のコマンドを調べる必要があるのはよくあることです。そこで、これらの呼び出しを補完するためにモデルを使うことができれば素敵です。 + + + +[第6章](/course/ja/chapter6)では、Pythonソースコードを処理するための効率的なトークナイザーを作成しましたが、モデルを事前学習するためには、やはり大規模なデータセットが必要です。ここでは、GitHub リポジトリから得た Python コードのコーパスにトークナイザを適用します。そして、`Trainer` API と 🤗 Accelerate を使ってモデルを学習します。さあ、始めましょう + + + +これは実際に、このセクションで示したコードを使って学習し、ハブにアップロードしたモデルを紹介しているものです。[こちら](https://huggingface.co/huggingface-course/codeparrot-ds?text=plt.imshow%28)をご覧ください。なお、テキスト生成の際にランダム化が行われているので、おそらく少し異なる結果が得られると思います。 + +## データを収集する + +PythonのコードはGitHubなどのコードリポジトリから豊富に提供されており、これを利用してPythonのリポジトリごとにスクレイピングすることでデータセットを作成することができます。これは[トランスフォーマーの教科書](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/)で大規模なGPT-2モデルを事前学習させるために取られたアプローチです。著者らは`codeparrot`と呼ばれる約2000万のPythonファイルを含む約180GBのGitHubダンプを使ってデータセットを作り、それを[ハギング フェイス ハブ](https://huggingface.co/datasets/transformersbook/codeparrot)で共有しました。 + +しかし、コーパス全体に対する学習は時間と計算がかかるので、Pythonを使用したデータサイエンスに関連するデータだけが必要です。そこで、まず`codeparrot`データセットから、データサイエンスに使われるライブラリのいずれかを含むすべてのファイルをフィルタリングしてみましょう。データセットのサイズが大きいので、ダウンロードは避けたいです。その代わりに、ストリーミング機能を使って、その場でフィルタリングすることにしましょう。先ほど紹介したライブラリを使ったコードサンプルをフィルタリングするために、次の関数を使います。 + +```py +def any_keyword_in_string(string, keywords): + for keyword in keywords: + if keyword in string: + return True + return False +``` + +2つの例でテストしてみましょう。 + +```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 +# This cell will take a very long time to execute, so you should skip it and go to +# the next one! +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%が残されましたが、それでもかなり大きなサイズです。このデータセットは6GBで、60万のPythonスクリプトから構成されています! + +データセット全体のフィルタリングには、マシンや帯域幅にもよりますが、2〜3時間かかると思われます。もし、この長いプロセスを自分でやりたくない場合、私達は既にフィルタリングされたデータセットをハブで提供し、ダウンロードできるようにしています。 + +```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 + }) +}) +``` + + + +言語モデルのプリトレーニングにはしばらく時間がかかります。まず、上記の2つのデータセットに関する部分を一旦コメント化し、サンプルデータに対して学習ループを実行し、学習が一通り正常に終了してモデルが保存されたことを確認することをお勧めします。フォルダを作り忘れたり、学習ループの最後にタイプミスがあったりして、最後のステップで学習が失敗してしまうことほど悔しいことはありません! + + + +データセット内の例を見てみましょう。ここでは、各フィールドの最初の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` フィールドに、モデルに学習させたいコードが含まれていることがわかります。データセットができたので、テキストを準備し、事前学習に適した形式にする必要があります。 + +## データセットの準備 + + + +まず最初に、データをトークン化し、学習に利用できるようにします。私達の目標は主に短い関数呼び出しを自動補完することなので、コンテキストのサイズを比較的小さく保つことができます。これにより、モデルをより速く学習させることができ、必要なメモリ量も大幅に少なくなるという利点があります。もしあなたのアプリケーションにとってより多くのコンテキストを持つことが重要であれば(例えば、関数定義を含むファイルに基づいてユニットテストを書くようにモデルをしたい場合)、この数を増やした事を確認してください。GPT-2 のコンテキストサイズは 1,024、GPT-3 では 2,048 ですが、現在のところ、私達のコンテキストサイズは 128 トークンに固定しましょう。 + +ほとんどの文書は128トークンより多いので、単純に入力を最大長に切り詰めると、データセットの大部分を除去してしまうことになります。その代わりに、[第6章](/course/ja/chapter6/4) で行ったように、 `return_overflowing_tokens` オプションを使って入力全体をトークン化し、いくつかの断片に分割してみます。また、`return_length`オプションを使用して、作成された各断片の長さを自動的に返します。多くの場合、最後の断片はコンテキストのサイズよりも小さくなるので、パディングの問題を避けるためにこれらの断片を取り除きます。 + +
+Chunking a large texts in several pieces. + +
+ +最初の2つの例で、この仕組みを具体的に見てみましょう。 + +```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] +``` + +これらの 2 つのサンプルから、合計で 34 の断片が得られることがわかります。断片の長さを見ると、両方のドキュメントの末尾にある断片は 128 トークンより短いことがわかります。(それぞれ 117 と 41)これらは全断片のほんの一部なので、安全に捨てることができます。`overflow_to_sample_mapping` フィールドを使うと、どの断片がどの入力サンプルに属していたかを再構築することもできます。 + +この操作では、🤗 Datasetsの `Dataset.map()` 関数の便利な機能を使っています。それは、一対一の対応を必要としないことです。[セクション 3](/course/ja/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 + }) +}) +``` + +現在、各トークンが128個の1670万サンプルがあり、これは合計で約21億トークンに相当します。参考までに、OpenAIのGPT-3とCodexモデルはそれぞれ3000億、1000億のトークンで学習されており、CodexモデルはGPT-3のチェックポイントから初期化されています。このセクションの目的は、長くて一貫性のあるテキストを生成できるこれらのモデルと競合することではなく、データサイエンティストのための迅速な自動補完機能を提供する縮小版を作成することです。 + +さて、データセットの準備ができたので、モデルをセットアップしてみましょう! + + + +✏️ **あなたの番です!** + +コンテキストサイズより小さい断片を全て取り除くことは、今回は小さなコンテキストウィンドウを使っているので大きな問題ではありませんでした。コンテキストサイズを大きくすると(あるいは短いドキュメントのコーパスがある場合)、捨てられる断片の割合も大きくなります。より効率的なデータの準備方法としては、トークン化されたサンプルを `eos_token_id` トークンを挟んで一括で連結し、連結したデータに対して断片分割を実行することです。練習として、その方法を利用するために `tokenize()` 関数を修正してください。トークン ID の完全なシーケンスを取得するために、 `truncation=False` を設定し、トークナイザーの他の引数を削除する必要があることに注意してください。 + + + +## 新しいモデルを初期化する + +最初のステップは GPT-2 モデルを新しく初期化することです。このモデルには小型のGPT-2モデルと同じ設定を使用します。そのため、事前学習済みの設定をロードし、トークナイザーのサイズがモデルの語彙サイズと一致することを確認し、`bos`と`eos`(シーケンスの開始と終了を意味します)のトークンIDを渡します。 + +{#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) # Builds the model +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} + +このモデルには1億2400万のパラメータがあり、これを調整する必要があります。トレーニングを開始する前に、バッチを作成するためのデータコレーターをセットアップする必要があります。私達は`DataCollatorForLanguageModeling`を使う事ができます。 + +これは言語モデリング用に特別に設計されたものです(その名前が示すとおり)。バッチのスタックとパディングの他にまた、言語モデルのラベルを作成することもできます。因果言語モデリングでは、入力もラベルの役割を果たしますが(要素を1つずらすだけです)、このデータコレーターは学習中にラベルを作成するので、 `input_ids` を重複させる必要がありません。 + +`DataCollatorForLanguageModeling` はマスク言語モデリング (MLM) と因果言語モデリング (CLM) の両方をサポートすることに注意してください。デフォルトでは MLM 用のデータが用意されていますが、引数 `mlm=False` を設定することでCLMに切り替えることができます。 + +{#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'} + +あとは `to_tf_dataset()` メソッドを使って、上で作成したデータコレーターでデータセットをTensorFlowのデータセットに変換すればよいでしょう。 + +```python +tf_train_dataset = tokenized_dataset["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) +tf_eval_dataset = tokenized_dataset["valid"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=32, +) +``` + +{/if} + + + +⚠️ 入力とラベルの位置をずらすのはモデル内部で行われるので、データコレーターは入力をコピーしてラベルを作成するだけです。 + + + +これで、実際にモデルを訓練するための準備が整いました。 + +結局のところ、それほど大変な作業ではありませんでしたね。トレーニングを始める前に、ハギング フェイスにログインする必要があります。もしノートブックで作業しているなら、次のユーティリティ関数でログインできます。 + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +これにより、ハギング フェイスのログイン情報を入力するウィジェットが表示されます。 + +ノートブックで作業していない場合は、ターミナルで次の行を入力するだけです。 + +```bash +huggingface-cli login +``` + +{#if fw === 'pt'} + +あとは学習用の引数を設定し、`Trainer` を起動するだけです。ここでは、いくつかのウォームアップを伴う cosine 学習率のスケジュールと、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) + +# Train in mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` +あとは `model.fit()` を呼び出して、学習が終了するのを待つだけです。トレーニングセット全体かその一部分だけかにもよりますが、それぞれ20時間、2時間かかりますので、コーヒーでも飲みながらお好きな本を読んでゆっくり待ちましょう。学習が完了したら、モデルとトークナイザーをハブにプッシュします。 + +```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} + + + +✏️ **あなたの番です!** 生のテキストからGPT-2の学習まで、`TrainingArguments`に加えて、約30行のコードを作成するだけで済みました。あなた自身のデータセットで試してみて、良い結果が得られるかどうか確認してみてください! + + + + + +{#if fw === 'pt'} + +💡 もし、複数のGPUを搭載したマシンを利用できるのであれば、そこでコードを実行してみてください。トレーナー`は自動的に複数のマシンを管理するため、学習速度が飛躍的に向上します。 + +{:else} + +💡 もし、複数のGPUを搭載したマシンを利用できるのであれば、`MirroredStrategy`コンテキストを使って、学習を大幅にスピードアップさせることができます。そのためには `tf.distribute.MirroredStrategy` オブジェクトを作成し、 `to_tf_dataset` コマンド、モデルの作成、 `fit()` の呼び出しがすべて `scope()` コンテキストで実行されることを確認する必要があります。これに関するドキュメントは[こちら](https://www.tensorflow.org/guide/distributed_training#use_tfdistributestrategy_with_keras_modelfit)で見ることができます。 + +{/if} + + + +## パイプラインによるコード生成 + +さて、いよいよ本番です!学習したモデルが実際にどの程度機能するのか見てみましょう。ログを見ると損失が着実に減っていることがわかりますが、モデルをテストするために、いくつかのプロンプトに対してどの程度効果があるのか見てみましょう。そのために、テキスト生成の `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} + +まずは散布図を作るという簡単な作業から始めてみましょう。 + +```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` オペレーションでも動作するのでしょうか?2つの配列から `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を使ったデータサイエンス関連の構文の一部を学習したようです。もちろん、このモデルを実世界に展開する前に、もっと徹底的に評価する必要がありますが、それでもこれは印象的なプロトタイプです。 + +{:else} + +これらのいくつかの例を見ると、モデルはPythonデータサイエンス関連の構文の一部を学習したようです(もちろん、実世界にモデルを展開する前にもっと徹底的に評価する必要があるでしょう)。しかし、あるユースケースに必要なパフォーマンスを達成するために、モデルの学習をよりカスタマイズする必要がある場合もあります。例えば、バッチサイズを動的に更新したい場合や、適切でないサンプルをその場でスキップする条件付き学習ループを持ちたい場合はどうすればよいでしょうか。一つの選択肢は `Trainer` をサブクラス化して必要な変更を加えることですが、時には学習ループを一から書いた方がシンプルな場合もあります。そこで🤗 Accelerateの出番です。 + +{/if} + +{#if fw === 'pt'} + +## 🤗 Accelerate を使ったトレーニング + +これまで `Trainer` を使ってモデルを学習する方法を見てきました。これはある程度カスタマイズすることができますが、時には学習ループを完全に制御したい場合や、派手な変更を加えたい場合があります。この場合、🤗 Accelerateは素晴らしい選択肢です。このセクションでは、それを使ってモデルを訓練する手順を説明します。さらに面白くするために、学習ループに一工夫してみましょう。 + + + +私達は主にデータサイエンスライブラリの自動補完に興味があるので、これらのライブラリをより多く使用する学習サンプルに重きを置くことは理にかなっています。これらのサンプルは `plt`, `pd`, `sk`, `fit`, `predict` といったキーワードで簡単に識別できます。これらは `matplotlib.pyplot`, `pandas`, `sklearn` で最も頻繁に使用される import 名で、後者の fit/predict のパターンも同様です。これらをそれぞれ1つのトークンとして表現すれば、入力列の中にそれらがあるかどうかを簡単にチェックすることができます。トークンは半角スペースを前に持つことができるので、トークナイザーの語彙の中にそれらのがあるかどうかもチェックすることになります。動作確認のため、複数のトークンに分割されるはずのテストトークンを1つ追加してみます。 + +```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つシフトしたものがラベルとなり、次のトークンが現在のトークンのラベルとなります。これは入力シーケンスの2番目のトークンからラベルを開始することで実現できます。なぜなら、モデルは最初のトークンに対していずれにしても予測を行わないからです。そして、最後のロジットを切り捨てます。なぜなら、全入力シーケンスの後には対応するラベルがないからです。これでサンプルごとの損失を計算し、各サンプルにおける全てのキーワードの出現をカウントすることができます。最後に、出現回数を重みとして、全サンプルの加重平均を計算します。キーワードを持たないサンプルを全て捨てたくないので、重みに1を加えます。 + +```py +from torch.nn import CrossEntropyLoss +import torch + + +def keytoken_weighted_loss(inputs, logits, keytoken_ids, alpha=1.0): + # Shift so that tokens < n predict n + shift_labels = inputs[..., 1:].contiguous() + shift_logits = logits[..., :-1, :].contiguous() + # Calculate per-token loss + loss_fct = CrossEntropyLoss(reduce=False) + loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1)) + # Resize and average loss per sample + loss_per_sample = loss.view(shift_logits.size(0), shift_logits.size(1)).mean(axis=1) + # Calculate and scale weighting + weights = torch.stack([(inputs == kt).float() for kt in keytoken_ids]).sum( + axis=[0, 2] + ) + weights = alpha * (1.0 + weights) + # Calculate weighted average + weighted_loss = (loss_per_sample * weights).mean() + return weighted_loss +``` + +この素晴らしい新しい損失関数を使った学習を始める前に、いくつかのことを準備する必要があります。 + +- データをロードしてバッチにするためのデータローダーが必要です。 +- 重み減衰のパラメータを設定する必要があります。 +- 時折、評価を行いたいので、評価コードを関数でラップするのは理にかなっています。 + +まずはデータローダーから始めましょう。 +データセットのフォーマットを `"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) +``` + +次に、パラメータをグループ化し、オプティマイザがどのパラメータが追加の重み減衰を得るかを知ることができるようにします。通常、すべてのバイアスとLayerNormの重み項は、この対象から除外されます。 +以下のようになります。 + +```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()`関数により、損失と[パープレキシティ](/course/ja/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章](/course/ja/chapter3)を参照してください。 + + + +これで `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, +) +``` + +最後に、私たちのモデルをハブにプッシュするために、作業フォルダに `Repository` オブジェクトを作成する必要があります。まず、ハギング フェイス ハブ にログインしてください(まだログインしていない場合)。モデルに付与したいモデル ID からリポジトリ名を決定します(`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) +``` + +これで `repo.push_to_hub()` メソッドを呼び出すことで、`output_dir` に保存したものをアップロードできるようになりました。これにより、各エポック終了時に中間モデルをアップロードすることができます。 + +学習する前に、評価関数が正しく動作するかどうか、簡単なテストを実行してみましょう。 + +```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( + { + "lr": get_lr(), + "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} \ No newline at end of file diff --git a/chapters/ja/chapter7/7.mdx b/chapters/ja/chapter7/7.mdx new file mode 100644 index 000000000..e54482205 --- /dev/null +++ b/chapters/ja/chapter7/7.mdx @@ -0,0 +1,1221 @@ + + +# 質問応答 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +質問応答について考える時が来ました。このタスクには多くの種類がありますが、このセクションで取り上げるのは「 *抽出的* な質問応答」です。これは、特定の文書についての質問を投げかけ、文書内の答えの書かれている範囲を特定することを含みます。 + + + +私達は、Wikipediaの記事に対してクラウドワーカーによって作成された質問からなる[SQuADデータセット](https://rajpurkar.github.io/SQuAD-explorer/)のBERTモデルを微調整する予定です。これにより、以下のような予測を実行できるモデルができるでしょう。 + + + +これは実際にこのセクションで示したコードを使って学習し、ハブにアップロードしたモデルを紹介しているものです。 +貴方は[ここ](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/ja/chapter7/5)に非常に似た典型的な方法で情報を合成するために使用されます。このタイプの *生成的* な質問回答に興味がある場合は、[ELI5データセット](https://huggingface.co/datasets/eli5)に基づく私たちの[デモ](https://yjernite.github.io/lfqa.html)をチェックすることをお勧めします。 + + + +## データの準備 + +抽出的質問応答の学術的なベンチマークとして最も利用されているデータセットが [SQuAD](https://rajpurkar.github.io/SQuAD-explorer/) ですので、ここでもこれを利用します。また、より難しい[SQuAD v2](https://huggingface.co/datasets/squad_v2)ベンチマークもあり、これは答えのない質問を含んでいます。あなたのデータセットが文脈の列、質問の列、答えの列を含んでいる限り、以下のステップを適用することができるはずです。 + +### SQuAD データセット + +いつものように、`load_dataset()`のおかげで、たった1ステップでデータセットをダウンロードし、キャッシュすることができます。 + +```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` フィールドは少し手が込んでおり、リストである2つのフィールドを持つ辞書型データを作成します。これは、 `squad` 指標が評価時に期待する形式です。もし、あなた自身のデータを使用しているのであれば、必ずしも同じ形式の答えを置くことを心配する必要はありません。また、 `answer_start` フィールドには、コンテキスト内の各解答の開始文字インデックスが格納されます。 + +トレーニングの間、可能な答えは1つだけです。このことは `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?' +``` + +答えは、3つの可能性のうちの1つであることがわかります。 + +### 学習データの処理 + + + +まず、学習データの前処理から始めましょう。難しいのは質問の答えのラベルを生成することで、これはコンテキスト内の答えに対応するトークンの開始位置と終了位置となります。 + +しかし、先を急がないようにしましょう。まず、トークナイザーを使って、入力のテキストをモデルが理解できるようなIDに変換する必要があります。 + +```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] +``` + +2重チェックしてみましょう。 + +```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]' +``` + +ラベルは回答の開始と終了のトークンのインデックスとなり、モデルは入力のトークンごとに1つの開始と終了のロジットを予測するよう課され、理論上のラベルは次のようになります。 + +
+One-hot encoded labels for question answering. + +
+ +この場合、コンテキストはそれほど長くありませんが、データセットの中には非常に長い文脈を持つ例もあり、設定した最大長(この場合は384)を超えてしまいます。[第6章](/course/ja/chapter6/4)で `質問応答`パイプラインの内部を調べたときに見たように、長いコンテキストに対しては、データセットの1つのサンプルから複数の学習特徴を作成し、それらの間にスライディングウィンドウを設けて対処することになります。 + +今回の例では、長さを100に制限し、50トークンのスライディングウィンドウを使用することで、どのように動作するかを確認します。注意点として、使用するのは + +- 最大長を設定する `max_length` (ここでは100) +- `truncation="only_second"` は、質問とそのコンテキストが長すぎる場合に、(2番目の位置にある)コンテキストを切り詰める +- `stride` は2つの連続した断片間で重複するトークンの数を設定します (ここでは50) +- `return_overflowing_tokens=True` でトークナイザーにオーバーフロー用トークンが必要なことを知らせます。 + +```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]' +``` + +見てわかるように、この例は4つの入力に分割され、それぞれが質問とコンテキストの一部を含んでいます。質問に対する答え("Bernadette Soubirous")は3番目と最後の入力にのみ現れることに注意してください。このように長い文脈を扱うことで、答えが文脈に含まれない学習サンプルをいくつか作成することができます。これらの例では、ラベルは `start_position = end_position = 0` となります(つまり、`[CLS]` トークンを予測することになります)。また、不幸にも答えが切り捨てられ、始まり(または終わり)しかない場合にも、これらのラベルを設定します。答えがコンテキストに完全に含まれている例では、ラベルは答えが始まるトークンのインデックスと答えが終わるトークンのインデックスになります。 + +データセットからコンテキスト内の答えの開始文字が得られ、答えの長さを追加することで、コンテキスト内の終了文字が見つかります。これらをトークン インデックスにマップするには、[第6章](/course/ja/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']) +``` + +見てのとおり、通常の入力 ID、トークンタイプ ID、アテンションマスクに加え、必要なオフセットマッピング、さらに `overflow_to_sample_mapping` というキーが返されます。この値は、複数のテキストを同時にトークン化するときに役に立ちます (このトークナイザーがRustに支えられているという事実を利用するために、そうする必要があります)。1 つのサンプルは複数の特徴を与えることができるので、各特徴をその元となるサンプルにマップします。ここでは 1 つの例をトークン化しただけなので、`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].' +``` + +見ての通り、最初の3例(トレーニングセットのインデックス2、3、4)はそれぞれ4つの特徴を与え、最後の例(トレーニングセットのインデックス5)は7つの特徴を与えています。 + +この情報は、得られた各特徴を対応するラベルに対応付けるために有用です。前述したように、それらのラベルは + +- 答えがコンテキストが対応する範囲内にない場合、 `(0, 0)` となります +- `(start_position, end_position)` もし答えがコンテキストの対応する範囲内にあれば、 `start_position` は答えの始まりのトークン(入力IDの中)のインデックス、 `end_position` は答えの終わりのトークン(入力IDの中)のインデックスです + +これらのどちらであるか、また関連する場合はトークンの位置を決定するために、まず入力IDの中で文脈を開始するインデックスと終了するインデックスを見つけます。これを行うにはトークンタイプ ID を使用することもできますが、それはすべてのモデルに存在するとは限らないので (例えば DistilBERT はそれを要求しません)、代わりにトークナイザーが返す `BatchEncoding` の `sequence_ids()` メソッドを使用することにします。 + +トークンのインデックスが得られたら、対応するオフセットを調べます。オフセットとは、元のコンテキスト内の文字の範囲を表す2つの整数の組のことです。このようにして、この特徴におけるコンテキストの断片が、答えが終わった後に始まっているのか、答えが始まる前に終わっているのか(その場合のラベルは `(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) + + # Find the start and end of the context + idx = 0 + while sequence_ids[idx] != 1: + idx += 1 + context_start = idx + while sequence_ids[idx] == 1: + idx += 1 + context_end = idx - 1 + + # If the answer is not fully inside the context, label is (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: + # Otherwise it's the start and end token positions + 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' +``` + +ということで、一致しました ここで、ラベルを `(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) + + # Find the start and end of the context + idx = 0 + while sequence_ids[idx] != 1: + idx += 1 + context_start = idx + while sequence_ids[idx] == 1: + idx += 1 + context_end = idx - 1 + + # If the answer is not fully inside the context, label is (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: + # Otherwise it's the start and end token positions + 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 +``` + +スライディングウィンドウの長さと最大長を決定するために2つの定数を定義したことと、トークン化の前に小さなクリーンアップを追加したことに注意してください。SQuADデータセットのいくつかの質問には最初と最後に何も追加しない余計なスペース(RoBERTaなどのモデルを使用するとトークン化するときにスペースを取ってしまいます)があるので、それらの余計なスペースを除去しています。 + +この関数を訓練セット全体に適用するために、 `Dataset.map()` メソッドに `batched=True` フラグを付けて使います。これはデータセットの長さを変更するために必要です(1つの例で複数の学習特徴を与えることができるため)。 + +```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個の特徴が追加されました。これで訓練セットの準備は整いました。次は検証セットの前処理です。 + +### 検証セットの処理 + +検証データの前処理は、ラベルを生成する必要がないため、若干簡単になります。(検証損失を計算したい場合は別ですが、この数値はモデルがどれだけ優れているかを理解するのにあまり役立ちません)。本当の喜びは、モデルの予測を元のコンテキストの範囲として解釈する事にあります。このためには、オフセットマッピングと、作成された各特徴を元の例文とマッチングさせる方法の両方を保存する必要があるだけです。元のデータセットにIDカラムがあるので、その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'} + +## Trainer API でモデルの微調整を行う + +この例の学習コードは、前のセクションのコードとよく似ています。最も難しいのは `compute_metrics()` 関数を書くことです。すべてのサンプルを設定した最大長になるようにパディングしているので、データコレーターを定義する必要はありません。したがって、この指標の計算だけが本当に心配しなければならないことです。難しいのは、後処理でモデルの予測を元の例文のテキストの範囲にすることです。一旦これを行えば、🤗 Datasetsライブラリの指標が私達のためにほとんどの作業をしてくれるでしょう。 + +{:else} + +## Kerasを使ってモデルの微調整を行う + +この例の学習コードは、前のセクションのコードとよく似ていますが、指標の計算に独自の難しさがあります。全てのサンプルを設定した最大長にパディングしたので、定義すべきデータコレーターはなく、この指標の計算だけが本当に心配しなければならないことなのです。難しい事は、後処理として、モデルの予測を元の例のテキストの範囲にすることです。一度それを行えば、🤗 Datasetsライブラリのメトリックが私たちのために仕事の大部分を行ってくれます。 + +{/if} + +### 後処理 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +このモデルは、[`question-answering` pipeline](/course/ja/chapter6/3b) の探索で見たように、入力IDにおける答えの開始位置と終了位置のロジットを出力することになります。後処理のステップは、その際に行ったことと同じようなものになりますので、以下でやった事を思い出してください。 + +- コンテキスト外のトークンに対応する開始と終了のロジットをマスクしました。 +- 次に、ソフトマックスを使用して開始ロジットと終了ロジットを確率に変換しました。 +- 各ペア `(start_token, end_token)` には、対応する二つの確率の積を取ることでスコアを付与しました。 +- 私達は有効な答え(例えば、`start_token`が`end_token`より前にある)をもたらす最大のスコアを持つペアを探しました。 + +ここでは、実際のスコアを計算する必要がないため、このプロセスを少し変更します(予測された答えだけが欲しいのです)。つまり、ソフトマックスのステップをスキップすることができます。また、より高速に処理するために、可能性のある全ての `(start_token, end_token)` ペアをスコアリングせず、最も高い `n_best` ロジットに対応するものだけをスコアリングします(`n_best=20` とします)。ソフトマックスをスキップするので、これらのスコアはlogitスコアとなり、開始と終了のロジットの和を取ることで得られます。\\(\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` で可能性を制限しています) + +1つの例に対して採点されたすべての可能な答えがあったら、最高のロジットスコアから1つを選びます。 + +```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: + # Skip answers that are not fully in the context + if offsets[start_index] is None or offsets[end_index] is None: + continue + # Skip answers with a length that is either < 0 or > 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"]}) +``` + +予測された答えの最終的なフォーマットは、私たちが使用する指標によって期待されるものです。いつものように、🤗 Datasetsライブラリの助けを借りて読み込むことができます。 + +```python +from datasets import load_metric + +metric = load_metric("squad") +``` + +この指標は、上で見た形式の予測された答え(サンプルのIDと予測されたテキストの1つのキーを持つ辞書のリスト)と、下の形式の理論的な答え(サンプルのIDと可能な答えの1つのキーを持つ辞書のリスト)を期待するものです。 + +```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)によると、SQuADで微調整したDistilBERTは、全データセットで79.1点と86.9点を獲得していることを考えると、これはむしろ良いことだと言えます。 + +{#if fw === 'pt'} + +ここで、先ほど行ったことをすべて `compute_metrics()` 関数にまとめて、 `Trainer` で使用することにしましょう。通常、 `compute_metrics()` 関数は logits と labels を含むタプル `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 = [] + + # Loop through all features associated with that example + 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: + # Skip answers that are not fully in the context + if offsets[start_index] is None or offsets[end_index] is None: + continue + # Skip answers with a length that is either < 0 or > 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) + + # Select the answer with the best score + 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} +``` + +いい感じです!では、これを使ってモデルを微調整してみましょう。 + +### モデルの微調整 + +{#if fw === 'pt'} + +これでモデルを学習する準備ができました。先程と同じように `AutoModelForQuestionAnswering` クラスを使用して、まずモデルを作成しましょう。 + +```python +model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) +``` + +{:else} + +これでモデルを学習する準備ができました。まず、前と同じように `TFAutoModelForQuestionAnswering` クラスを使用してモデルを作成しましょう。 + +```python +model = TFAutoModelForQuestionAnswering.from_pretrained(model_checkpoint) +``` + +{/if} + +いつものように、いくつかの重みが使われていない(事前学習時のヘッドのもの)、他のいくつかの重みがランダムに初期化されている(質問応答用ヘッドのもの)という警告が表示されます。もう慣れたと思いますが、これはこのモデルがまだ使える状態ではなく、微調整が必要であることを意味します。 + +このモデルをHubにプッシュするためには、Hugging Faceにログインする必要があります。もしこのコードをnotebookで実行しているなら、次のユーティリティ関数でログインすることができます。 + +```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) にあります)、このセクションにはちょっと長すぎますね。その代わり、ここでは学習の最後にモデルを評価することだけを行い、通常の評価の方法は後述の「カスタム学習ループ」で紹介します。 + +これは `Trainer` API の限界であり、🤗 Accelerate ライブラリが輝くところです。特定の用例に合わせてクラスをカスタマイズするのは大変ですが、完全に公開された学習ループを調整するのは簡単です。 + +それでは、`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, +) +``` + +これらのほとんどは以前にも見たことがあると思います。 + +ハイパーパラメータ(学習率、学習エポック数、ウェイト減衰など)を設定し、エポック終了ごとにモデルを保存し、評価を省略し、結果をモデルハブにアップロードすることを指定します。また、`fp16=True`で混合精度学習を有効にします。最近のGPUでは、混合精度学習がうまくスピードアップするからです。 + +{:else} + +これで、TFデータセットを作成することができます。今回はシンプルなデフォルトのデータコレーターを使用します。 + +```python +from transformers import DefaultDataCollator + +data_collator = DefaultDataCollator(return_tensors="tf") +``` + +そして、今度はいつも通りデータセットを作成します。 + +```python +tf_train_dataset = train_dataset.to_tf_dataset( + columns=[ + "input_ids", + "start_positions", + "end_positions", + "attention_mask", + "token_type_ids", + ], + collate_fn=data_collator, + shuffle=True, + batch_size=16, +) +tf_eval_dataset = validation_dataset.to_tf_dataset( + columns=["input_ids", "attention_mask", "token_type_ids"], + 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 + +# 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_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) + +# Train in mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +最後に、`model.fit()`で学習する準備ができました。`PushToHubCallback`を使用して、各エポック後にモデルをハブにアップロードします。 + +{/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) + +# We're going to do validation afterwards, so no validation mid-training +model.fit(tf_train_dataset, callbacks=[callback], epochs=num_train_epochs) +``` + +{/if} + +学習が行われている間、モデルが保存されるたびに(ここではエポックごとに)バックグラウンドでハブにアップロードされることに注意してください。こうすることで、必要に応じて別のマシンでトレーニングを再開することができます。トレーニング全体にはしばらく時間がかかるので(Titan RTXで1時間強)、その間コーヒーを飲んだり、コースの難しい部分を読み直したりすることができます。また、最初のエポックが終了するとすぐに、いくつかの重みがハブにアップロードされ、そのページであなたのモデルで遊び始めることができることに留意してください。 + +{#if fw === 'pt'} + +学習が完了したら、最後にモデルを評価することができます(そして、無駄に計算時間を費やさないように祈ることになります)。`Trainer` の `predict()` メソッドはタプルを返し、その最初の要素はモデルの予測値(ここでは開始ロジットと終了ロジットのペア)となります。これを `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} + +学習が完了したら、最後にモデルを評価することができます(そして、無駄に計算時間を費やしていないことを祈ります)。予測値の取得は `model` の `predict()` メソッドで行います。また、先ほど `compute_metrics()` 関数を定義して苦労したので、1行で結果を得ることができます。 + +```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 を返します。もしそれを検査したいのであれば、その URL をチェックします。 + +```python out +'https://huggingface.co/sgugger/bert-finetuned-squad/commit/9dcee1fbc25946a6ed4bb32efb1bd71d5fa90b68' +``` + +また、`Trainer`は評価結果をまとめたモデルカードを起案し、アップロードします。 + +{/if} + +この段階で、モデルハブ上の推論ウィジェットを使ってモデルをテストし、友人、家族、お気に入りのペットと共有することができます。あなたは、質問応答タスクでモデルの微調整に成功しました。おめでとうございます! + + + +✏️ **あなたの番です!** このタスクでより良いパフォーマンスが得られるかどうか、別のモデルアーキテクチャを試してみてください! + + + +{#if fw === 'pt'} + +もう少し深くトレーニングループを極めたい方は、今度は🤗Accelerateを使って同じことをする方法を紹介します。 + +## カスタムトレーニングループ + +それでは、必要な部分を簡単にカスタマイズできるように、トレーニングループの全体像を見てみましょう。[第3章](/course/ja/chapter3/4)の学習ループとほぼ同じですが、評価ループは例外です。もう `Trainer` クラスの制約を受けないので、定期的にモデルを評価することができるようになります。 + +### トレーニングのためのすべてを準備する + +まず、datasetsから `DataLoader` を構築します。それらのdatasetsのフォーマットを `"torch"` に設定し、検証用セットの中からモデルで使用しないカラムを削除します。次に、Transformers が提供する `default_data_collator` を `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()` メソッドに送ることができます。もし、ColabノートブックでTPUを使ったトレーニングをしたいのであれば、このコードを全てトレーニング関数に移動する必要があることに注意してください。トレーニング関数は`Accelerator`をインスタンス化するセルを実行するべきではありません。`Accelerator`に `fp16=True` を渡すことで、強制的に混合精度のトレーニングを行うことができます (または、コードをスクリプトとして実行する場合は、🤗 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, +) +``` + +モデルをハブにプッシュするには、作業フォルダに `Repository` オブジェクトを作成する必要があります。まず、ハギング フェイス ハブにログインしてください(まだログインしていない場合)。モデルに付与したいモデル ID からリポジトリ名を決定します(`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) +``` + +これで `repo.push_to_hub()` メソッドを呼び出すことで、`output_dir` に保存したものをアップロードできるようになりました。これにより、各エポック終了時に中間モデルをアップロードすることができます。 + +## トレーニングループ + +これでトレーニングループの全体を記述する準備が整いました。トレーニングの進捗を確認するためのプログレスバーを定義した後、ループは3つの部分に分かれます。 + +- 訓練自体は、`train_dataloader`に対する古典的な繰り返しで、モデルを前方に通過させ、後方に通過させ、オプティマイザーのステップを行います。 + +- 評価では、`start_logits` と `end_logits` の値をすべて収集し、NumPy の配列に変換します。評価ループが終了したら、すべての結果を連結します。各処理で同じ数のサンプルが得られるように、`Accelerator`が最後にいくつかのサンプルを追加している可能性があるため、切り捨てる必要があることに注意してください。 + +- 保存とアップロードでは、まずモデルとトークナイザーを保存し、次に `repo.push_to_hub()` を呼び出します。前回と同様に、引数 `blocking=False` を使って🤗 Hubライブラリに非同期処理でプッシュするように指示します。こうすることで、トレーニングは通常通り行われ、この(長い時間のかかる)命令はバックグラウンドで実行されます。 + +以下は、トレーニングループの完全なコードです。 + +```py +from tqdm.auto import tqdm +import torch + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Training + model.train() + for step, batch in enumerate(train_dataloader): + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) + + # Evaluation + model.eval() + 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) + + # Save and upload + accelerator.wait_for_everyone() + unwrapped_model = accelerator.unwrap_model(model) + unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) + if accelerator.is_main_process: + tokenizer.save_pretrained(output_dir) + repo.push_to_hub( + commit_message=f"Training in progress epoch {epoch}", blocking=False + ) +``` + +🤗Accelerateで保存されたモデルを初めて表示する場合は、それに付随する3行のコードを調べてみましょう。 + +```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()`を呼び出しますが、そのメソッドに `torch.save()`の代わりに `accelerator.save()`を使用するように指示します。 + +これが完了すると、`Trainer`でトレーニングされたものと非常によく似た結果を生成するモデルができあがります。 このコードを使用してトレーニングしたモデルは、[*huggingface-course/bert-finetuned-squad-accelerate*](https://huggingface.co/huggingface-course/bert-finetuned-squad-accelerate)で確認できます。 また、トレーニングループの微調整をテストする場合は、上記のコードを編集して直接実装できます! + +{/if} + +## 微調整したモデルを使用する + +モデルハブで微調整したモデルを推論ウィジェットで使用する方法は既に紹介しました。`pipeline`で利用する場合は、モデル識別子を指定します。 + +```py +from transformers import pipeline + +# Replace this with your own checkpoint +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'} +``` + +素晴らしいです! 私たちのモデルは、このパイプラインのデフォルトのものと同じように動作しています! \ No newline at end of file diff --git a/chapters/ja/chapter7/8.mdx b/chapters/ja/chapter7/8.mdx new file mode 100644 index 000000000..7426fcc25 --- /dev/null +++ b/chapters/ja/chapter7/8.mdx @@ -0,0 +1,19 @@ +# NLPをマスター + +このコースでここまで進んだなら、おめでとうございます! +あなたは今、🤗トランスフォーマーとハギング フェイス エコシステムを使って(ほとんど)どんなNLPタスクにも取り組むために必要なすべての知識とツールを手にしています。 + +様々なデータコレーターを見てきましたので、各タスクにどのコレーターを使えばいいのかがわかるように、この小さなビデオを作りました。 + + + +このライトニング・ツアーでNLPの主要タスクを学んだ後、次のことを行ってください。 +* 各タスクに最適なアーキテクチャ(エンコーダ、デコーダ、またはエンコーダ-デコーダ)を把握する +* 言語モデルの事前トレーニングと微調整の違いを理解する +* フォローしているトラックに応じて、TrainerAPIと分散トレーニング機能の🤗AccelerateまたはTensorFlowとKerasのいずれかを使用してTransformerモデルをトレーニング +する方法を知る +* テキスト生成タスクのROUGEやBLEUなどの指標の意味と制限を理解する +* ハブを使用する場合と🤗Transformersのパイプラインの使用する場合の両方で、微調整されたモデルを使う方法を知る + +このすべての知識にもかかわらず、コードで難しいバグに遭遇したり、特定のNLP問題を解決する方法について質問したりするときが来るでしょう。 幸いなことに、ハギング + フェイスコミュニティがお手伝いします。 コースのこの部分の最後の章では、Transformerモデルをデバッグし、効果的に支援を求める方法を探ります。 \ No newline at end of file diff --git a/chapters/ja/chapter7/9.mdx b/chapters/ja/chapter7/9.mdx new file mode 100644 index 000000000..4553fca3e --- /dev/null +++ b/chapters/ja/chapter7/9.mdx @@ -0,0 +1,324 @@ + + + + +# 章末クイズ + +この章で学んだことをテストしてみましょう! + +### 1. 次のタスクのうち、トークン分類問題として組み立てられるものはどれでしょうか? + + + +### 2. トークン分類のための前処理は、他の前処理パイプラインとどの部分が違うのでしょうか? + +-100 を使ってラベル付けしています。", + explain: "これはトークン分類に限ったことではありません。損失で無視したいトークンのラベルには、常に-100を使用します。" + }, + { + text: "単語を切り捨て/パディングを適用する際に、ラベルも入力と同じサイズに切り捨てるかパディングする必要があります。", + explain: "確かに! しかし、それだけが違いではありません。", + correct: true + } + ]} +/> + +### 3.トークン分類問題で単語をトークン化し、そのトークンにラベルを付けたい場合、どのような問題がありますか? + +-100の特別なラベルを付ける事ができます。" + }, + { + text: "各単語は複数のトークンを生成できるため、ラベルの数よりも多くのトークンを持つことになります。", + explain: "これが主な問題になります。元のラベルとトークンを揃える必要があります。", + correct: true + }, + { + text: "追加されたトークンはラベルを持たないので、問題はありません。", + explain: "それは正しくありません。トークンと同じ数のラベルが必要です。そうしないと、モデルがエラーになります。" + } + ]} +/> + +### 4. 「ドメイン適応」とはどういう意味ですか? + + + +### 5. マスク言語モデリング問題におけるラベルとは何ですか? + +らすことは次の単語を予測することに相当し、これは因果言語モデリングです。" + }, + { + text: "入力文の一部のトークンをランダムにマスクし、その文が肯定的か否定的かをラベルとします。", + explain: "これはデータ拡張を伴うシーケンス分類の問題で、マスク言語モデリングではありません。" + }, + { + text: "2つの入力文のトークンの一部がランダムにマスクされた後、ラベルとは2つの文が類似しているかどうかになります。", + explain: "これはデータ拡張を伴うシーケンス分類の問題であり>、マスク言語モデリングではありません。" + } + ]} +/> + +### 6. 以下の作業のうち、シーケンス間問題と見なせるものはどれですか? + + + +### 7. シーケンス間問題におけるデータの前処理はどのように行うのが適切でしょうか? + +inputs=...とtargets=...で一緒にトークナイザーに送らなければなりません。", + explain: "これは将来的に>追加するAPIかもしれませんが、今はまだ不可能です。" + }, + { + text: "入力とターゲットの両方を前処理する必要があり、トークナイザーを2回別々に呼び出す必要があります。", + explain: "その通りですが、不完全です。トークナイザーが両方を適切に処理できるようにするために必要なことがあります。" + }, + { + text: "いつも通り、入力をトークン化すればいいだけです。", + explain: "シーケンス分類の問題ではありません。ターゲットも数値に変換する必要があるテキストです!" + }, + { + text: "入力はトークナイザーに送られなければなりません。ターゲットもですが、特別なコンテキストマネージャーの下で送らなければなりません。", + explain: "その通りです。トークナイザは、そのコンテキストマネージャによってターゲットモードにする必要があります。", + correct: true + } + ]} +/> + +{#if fw === 'pt'} + +### 8. なぜシーケンス間問題には `Trainer` の特定のサブクラスが存在するのでしょうか? + +-100に設定されたラベルを無視するためです。", + explain: "それはカスタム損失固有の問題ではなく、損失が計算される際は常にそうなります。" + }, + { + text: "シーケンス間問題には特別な評価ループが必要だからです。", + explain: "その通りです。シーケンス間モデルの予測は generate()メソッドで実行されることが多いです。", + correct: true + }, + { + text: "シーケンス間問題ではターゲットがテキストになるためです。", + explain: "事前に前処理されているので、Trainerはあまり気にしません。" + }, + { + text: "シーケンス間問題では2つのモデルを使用するためです。", + explain: "ある意味2つのモデルを使用しています。エンコーダーとデコーダーですが、1つのモデルにまとめられています。" + } + ]} +/> + +{:else} + +### 9. Transformerモデルで `compile()` を呼び出す際に、損失を指定する必要がないことが多いのはなぜですか? + + + +{/if} + +### 10. 新しいモデルの事前学習はいつ行うべきですか? + +て、データ上で微調整を行うべきでしょう。" + }, + { + text: "使用している事前学習済みモデルのバイアスに懸念がある場合", + explain: "その通りですが、学習に使用するデータが本当に良いものであることをよく確認する必要があります。", + correct: true + }, + { + text: "利用できる事前学習済みモデルが十分に良い性能ではない場合", + explain: "学習時にきちんとデバッグができていますか?" + } + ]} +/> + +### 11. なぜ、たくさんのテキストで言語モデルを事前学習する事は簡単なのですか? + + + +### 12. 質問応答タスクのためにデータを前処理する際の主な課題は何ですか? + + + +### 13. 質問応答では通常どのように後処理が行われますか? + + diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index 47d726cff..9d7266369 100644 --- a/chapters/pt/_toctree.yml +++ b/chapters/pt/_toctree.yml @@ -68,6 +68,11 @@ title: Questionário de fim de capítulo quiz: 5 +- title: 6. A biblioteca Tokenizers 🤗 + sections: + - local: chapter6/1 + title: Introdução + - title: 7. Principais tarefas NLP sections: - local: chapter7/1 diff --git a/chapters/pt/chapter6/1.mdx b/chapters/pt/chapter6/1.mdx new file mode 100644 index 000000000..0fb3099dc --- /dev/null +++ b/chapters/pt/chapter6/1.mdx @@ -0,0 +1,14 @@ +# Introdução + +No [Capítulo 3](/course/chapter3), nós estudamos como realizar o ajuste fino em um modelo para uma dada tarefa. Quando nós fazemos isso, usamos o mesmo tokenizador utilizado pelo modelo pré-treinado -- mas o que podemos fazer quando queremos treinar um modelo do início? Nestes casos, utilizar um tokenizador que foi pré-treinado em um corpus de outro domínio ou linguagem é tipicamente subótimo. Por exemplo, um tokenizador que é treinado em um corpus de lingua inglesa terá um desempenho ruim em um corpus de textos em japonês, visto que o uso de espaços e pontuações é muito diferente nestes dois idiomas. + +Neste capítulo, você aprenderá como treinar um novo tokenizador em um corpus de textos, para então ser usado no treinamento de um modelo de linguagem. Isto tudo será feito com ajuda da biblioteca [🤗 Tokenizers](https://github.com/huggingface/tokenizers), que provê o tokenizador rápido na biblioteca [🤗 Transformers](https://github.com/huggingface/transformers). Daremos uma olhada a fundo sobre as funcionalidades oferecidas pela biblioteca, e explorar como os tokenizadores rápidos diferem das versões "lentas". + +Os tópicos que iremos cobrir incluem: + +* Como treinar um novo tokenizador semelhante ao usado por um determinado checkpoint em um novo corpus de textos +* Os recursos especiais dos tokenizadores rápidos +* As diferenças entre os três principais algoritmos de tokenização de subpalavras usados ​​no processamento de linguagem natural hoje +* Como construir um tokenizador do zero com a biblioteca 🤗 Tokenizers e treiná-lo em alguns dados + +As técnicas introduzidas neste capítulo irão te preparar para a seção no [Capítulo 7](/course/chapter7/6) onde iremos analisar a criação de um modelo de linguagem para a linguagem Python. Primeiramente, vamos começar analisando o que significa "treinar" um tokenizador. From 0730a3c9b1ae146eca2eb09df2775e27d531edcd Mon Sep 17 00:00:00 2001 From: lewtun Date: Mon, 25 Jul 2022 11:20:16 +0200 Subject: [PATCH 23/51] Bump release (#286) --- chapters/de/chapter3/3.mdx | 8 ++++---- chapters/de/chapter3/3_tf.mdx | 6 +++--- chapters/de/chapter3/4.mdx | 6 +++--- chapters/en/chapter3/3.mdx | 8 ++++---- chapters/en/chapter3/3_tf.mdx | 6 +++--- chapters/en/chapter3/4.mdx | 6 +++--- chapters/en/chapter7/2.mdx | 8 ++++---- chapters/en/chapter7/4.mdx | 8 ++++---- chapters/en/chapter7/5.mdx | 4 ++-- chapters/en/chapter7/7.mdx | 6 +++--- chapters/en/chapter8/4.mdx | 25 +++++++++++++++---------- chapters/en/chapter8/4_tf.mdx | 3 ++- chapters/es/chapter3/4.mdx | 6 +++--- chapters/fr/chapter3/3.mdx | 8 ++++---- chapters/fr/chapter3/3_tf.mdx | 6 +++--- chapters/fr/chapter3/4.mdx | 6 +++--- chapters/fr/chapter7/2.mdx | 8 ++++---- chapters/fr/chapter7/4.mdx | 8 ++++---- chapters/fr/chapter7/5.mdx | 4 ++-- chapters/fr/chapter7/7.mdx | 6 +++--- chapters/fr/chapter8/4.mdx | 25 +++++++++++++++---------- chapters/fr/chapter8/4_tf.mdx | 3 ++- chapters/hi/chapter3/3.mdx | 8 ++++---- chapters/hi/chapter3/3_tf.mdx | 6 +++--- chapters/hi/chapter3/4.mdx | 6 +++--- chapters/ja/chapter7/2.mdx | 8 ++++---- chapters/ja/chapter7/4.mdx | 8 ++++---- chapters/ja/chapter7/5.mdx | 4 ++-- chapters/ja/chapter7/7.mdx | 6 +++--- chapters/ru/chapter3/3.mdx | 8 ++++---- chapters/ru/chapter3/3_tf.mdx | 6 +++--- chapters/ru/chapter3/4.mdx | 6 +++--- chapters/th/chapter3/3.mdx | 8 ++++---- chapters/th/chapter3/3_tf.mdx | 6 +++--- chapters/th/chapter3/4.mdx | 6 +++--- chapters/zh-CN/chapter3/3.mdx | 8 ++++---- chapters/zh-CN/chapter3/3_tf.mdx | 6 +++--- chapters/zh-CN/chapter3/4.mdx | 6 +++--- utils/generate_notebooks.py | 4 ++-- 39 files changed, 148 insertions(+), 136 deletions(-) diff --git a/chapters/de/chapter3/3.mdx b/chapters/de/chapter3/3.mdx index 3189863c2..ee38b9c67 100644 --- a/chapters/de/chapter3/3.mdx +++ b/chapters/de/chapter3/3.mdx @@ -110,12 +110,12 @@ import numpy as np preds = np.argmax(predictions.predictions, axis=-1) ``` -Jetzt können wir diese Vorhersagen in `preds` mit den Labels vergleichen. Wir greifen auf die Metriken aus der 🤗 Bibliothek Datasets zurück, um unsere Funktion `compute_metric()` zu erstellen. Die mit dem MRPC-Datensatz verbundenen Metriken können genauso einfach geladen werden, wie wir den Datensatz geladen haben, diesmal mit der Funktion `load_metric()`. Das zurückgegebene Objekt verfügt über eine Berechnungsmethode, mit der wir die Metrik auswerten können: +Jetzt können wir diese Vorhersagen in `preds` mit den Labels vergleichen. Wir greifen auf die Metriken aus der 🤗 Bibliothek [Evaluate](https://github.com/huggingface/evaluate/) zurück, um unsere Funktion `compute_metric()` zu erstellen. Die mit dem MRPC-Datensatz verbundenen Metriken können genauso einfach geladen werden, wie wir den Datensatz geladen haben, diesmal mit der Funktion `evaluate.load()`. Das zurückgegebene Objekt verfügt über eine Berechnungsmethode, mit der wir die Metrik auswerten können: ```py -from datasets import load_metric +import evaluate -metric = load_metric("glue", "mrpc") +metric = evaluate.load("glue", "mrpc") metric.compute(predictions=preds, references=predictions.label_ids) ``` @@ -129,7 +129,7 @@ Zusammenfassend ergibt das unsere Funktion `compute_metrics()`: ```py def compute_metrics(eval_preds): - metric = load_metric("glue", "mrpc") + metric = evaluate.load("glue", "mrpc") logits, labels = eval_preds predictions = np.argmax(logits, axis=-1) return metric.compute(predictions=predictions, references=labels) diff --git a/chapters/de/chapter3/3_tf.mdx b/chapters/de/chapter3/3_tf.mdx index dd1be7835..6290506eb 100644 --- a/chapters/de/chapter3/3_tf.mdx +++ b/chapters/de/chapter3/3_tf.mdx @@ -172,12 +172,12 @@ print(preds.shape, class_preds.shape) (408, 2) (408,) ``` -Nun können wir diese Vorhersagen in `preds` nutzen, um einige Metriken zu berechnen! Wir können die Metriken, die mit dem MRPC-Datensatz verbunden sind, genauso einfach laden, wie wir den Datensatz geladen haben, in diesem Fall mit der Funktion "load_metric()". Das zurückgegebene Objekt verfügt über eine Berechnungsmethode, mit der wir die Metrik berechnen können: +Nun können wir diese Vorhersagen in `preds` nutzen, um einige Metriken zu berechnen! Wir können die Metriken, die mit dem MRPC-Datensatz verbunden sind, genauso einfach laden, wie wir den Datensatz geladen haben, in diesem Fall mit der Funktion "evaluate.load()". Das zurückgegebene Objekt verfügt über eine Berechnungsmethode, mit der wir die Metrik berechnen können: ```py -from datasets import load_metric +import evaluate -metric = load_metric("glue", "mrpc") +metric = evaluate.load("glue", "mrpc") metric.compute(predictions=class_preds, references=raw_datasets["validation"]["label"]) ``` diff --git a/chapters/de/chapter3/4.mdx b/chapters/de/chapter3/4.mdx index 1888bf2fa..c940e4030 100644 --- a/chapters/de/chapter3/4.mdx +++ b/chapters/de/chapter3/4.mdx @@ -171,12 +171,12 @@ Der Kern der Trainingsschleife sieht ähnlich aus wie in der Einleitung. Da wir ### Die Evaluationsschleife -Wie schon zuvor verwenden wir eine Metrik, die von der 🤗 Datasets-Bibliothek bereitgestellt wird. Wir haben bereits die Methode `metric.compute()` gesehen, aber Metriken können auch Batches für uns akkumulieren, wenn wir die Vorhersageschleife mit der Methode `add_batch()` durchlaufen. Sobald wir alle Batches gesammelt haben, können wir das Endergebnis mit der Methode `metric.compute()` ermitteln. So implementierst du all das in eine Evaluationsschleife: +Wie schon zuvor verwenden wir eine Metrik, die von der 🤗 Evaluate-Bibliothek bereitgestellt wird. Wir haben bereits die Methode `metric.compute()` gesehen, aber Metriken können auch Batches für uns akkumulieren, wenn wir die Vorhersageschleife mit der Methode `add_batch()` durchlaufen. Sobald wir alle Batches gesammelt haben, können wir das Endergebnis mit der Methode `metric.compute()` ermitteln. So implementierst du all das in eine Evaluationsschleife: ```py -from datasets import load_metric +import evaluate -metric = load_metric("glue", "mrpc") +metric = evaluate.load("glue", "mrpc") model.eval() for batch in eval_dataloader: batch = {k: v.to(device) for k, v in batch.items()} diff --git a/chapters/en/chapter3/3.mdx b/chapters/en/chapter3/3.mdx index fb1665370..ebd301469 100644 --- a/chapters/en/chapter3/3.mdx +++ b/chapters/en/chapter3/3.mdx @@ -110,12 +110,12 @@ import numpy as np preds = np.argmax(predictions.predictions, axis=-1) ``` -We can now compare those `preds` to the labels. To build our `compute_metric()` function, we will rely on the metrics from the 🤗 Datasets library. We can load the metrics associated with the MRPC dataset as easily as we loaded the dataset, this time with the `load_metric()` function. The object returned has a `compute()` method we can use to do the metric calculation: +We can now compare those `preds` to the labels. To build our `compute_metric()` function, we will rely on the metrics from the 🤗 [Evaluate](https://github.com/huggingface/evaluate/) library. We can load the metrics associated with the MRPC dataset as easily as we loaded the dataset, this time with the `evaluate.load()` function. The object returned has a `compute()` method we can use to do the metric calculation: ```py -from datasets import load_metric +import evaluate -metric = load_metric("glue", "mrpc") +metric = evaluate.load("glue", "mrpc") metric.compute(predictions=preds, references=predictions.label_ids) ``` @@ -129,7 +129,7 @@ Wrapping everything together, we get our `compute_metrics()` function: ```py def compute_metrics(eval_preds): - metric = load_metric("glue", "mrpc") + metric = evaluate.load("glue", "mrpc") logits, labels = eval_preds predictions = np.argmax(logits, axis=-1) return metric.compute(predictions=predictions, references=labels) diff --git a/chapters/en/chapter3/3_tf.mdx b/chapters/en/chapter3/3_tf.mdx index 2252a9613..6357be0b2 100644 --- a/chapters/en/chapter3/3_tf.mdx +++ b/chapters/en/chapter3/3_tf.mdx @@ -181,12 +181,12 @@ print(preds.shape, class_preds.shape) (408, 2) (408,) ``` -Now, let's use those `preds` to compute some metrics! We can load the metrics associated with the MRPC dataset as easily as we loaded the dataset, this time with the `load_metric()` function. The object returned has a `compute()` method we can use to do the metric calculation: +Now, let's use those `preds` to compute some metrics! We can load the metrics associated with the MRPC dataset as easily as we loaded the dataset, this time with the `evaluate.load()` function. The object returned has a `compute()` method we can use to do the metric calculation: ```py -from datasets import load_metric +import evaluate -metric = load_metric("glue", "mrpc") +metric = evaluate.load("glue", "mrpc") metric.compute(predictions=class_preds, references=raw_datasets["validation"]["label"]) ``` diff --git a/chapters/en/chapter3/4.mdx b/chapters/en/chapter3/4.mdx index 54563ea7c..a515ce2af 100644 --- a/chapters/en/chapter3/4.mdx +++ b/chapters/en/chapter3/4.mdx @@ -172,12 +172,12 @@ You can see that the core of the training loop looks a lot like the one in the i ### The evaluation loop -As we did earlier, we will use a metric provided by the 🤗 Datasets library. We've already seen the `metric.compute()` method, but metrics can actually accumulate batches for us as we go over the prediction loop with the method `add_batch()`. Once we have accumulated all the batches, we can get the final result with `metric.compute()`. Here's how to implement all of this in an evaluation loop: +As we did earlier, we will use a metric provided by the 🤗 Evaluate library. We've already seen the `metric.compute()` method, but metrics can actually accumulate batches for us as we go over the prediction loop with the method `add_batch()`. Once we have accumulated all the batches, we can get the final result with `metric.compute()`. Here's how to implement all of this in an evaluation loop: ```py -from datasets import load_metric +import evaluate -metric = load_metric("glue", "mrpc") +metric = evaluate.load("glue", "mrpc") model.eval() for batch in eval_dataloader: batch = {k: v.to(device) for k, v in batch.items()} diff --git a/chapters/en/chapter7/2.mdx b/chapters/en/chapter7/2.mdx index 9d1ccc3b9..3eaba62c8 100644 --- a/chapters/en/chapter7/2.mdx +++ b/chapters/en/chapter7/2.mdx @@ -522,7 +522,7 @@ The traditional framework used to evaluate token classification prediction is [* !pip install seqeval ``` -We can then load it via the `load_metric()` function like we did in [Chapter 3](/course/chapter3): +We can then load it via the `evaluate.load()` function like we did in [Chapter 3](/course/chapter3): {:else} @@ -532,14 +532,14 @@ The traditional framework used to evaluate token classification prediction is [* !pip install seqeval ``` -We can then load it via the `load_metric()` function like we did in [Chapter 3](/course/chapter3): +We can then load it via the `evaluate.load()` function like we did in [Chapter 3](/course/chapter3): {/if} ```py -from datasets import load_metric +import evaluate -metric = load_metric("seqeval") +metric = evaluate.load("seqeval") ``` This metric does not behave like the standard accuracy: it will actually take the lists of labels as strings, not integers, so we will need to fully decode the predictions and labels before passing them to the metric. Let's see how it works. First, we'll get the labels for our first training example: diff --git a/chapters/en/chapter7/4.mdx b/chapters/en/chapter7/4.mdx index 5aa654ceb..e68bb376b 100644 --- a/chapters/en/chapter7/4.mdx +++ b/chapters/en/chapter7/4.mdx @@ -53,7 +53,7 @@ To fine-tune or train a translation model from scratch, we will need a dataset s As usual, we download our dataset using the `load_dataset()` function: ```py -from datasets import load_dataset, load_metric +from datasets import load_dataset raw_datasets = load_dataset("kde4", lang1="en", lang2="fr") ``` @@ -428,12 +428,12 @@ One weakness with BLEU is that it expects the text to already be tokenized, whic !pip install sacrebleu ``` -We can then load it via `load_metric()` like we did in [Chapter 3](/course/chapter3): +We can then load it via `evaluate.load()` like we did in [Chapter 3](/course/chapter3): ```py -from datasets import load_metric +import evaluate -metric = load_metric("sacrebleu") +metric = evaluate.load("sacrebleu") ``` This metric will take texts as inputs and targets. It is designed to accept several acceptable targets, as there are often multiple acceptable translations of the same sentence -- the dataset we're using only provides one, but it's not uncommon in NLP to find datasets that give several sentences as labels. So, the predictions should be a list of sentences, but the references should be a list of lists of sentences. diff --git a/chapters/en/chapter7/5.mdx b/chapters/en/chapter7/5.mdx index 958dc685d..e6df6fc31 100644 --- a/chapters/en/chapter7/5.mdx +++ b/chapters/en/chapter7/5.mdx @@ -352,9 +352,9 @@ Applying this to our verbose summary gives a precision of 6/10 = 0.6, which is and then loading the ROUGE metric as follows: ```python -from datasets import load_metric +import evaluate -rouge_score = load_metric("rouge") +rouge_score = evaluate.load("rouge") ``` Then we can use the `rouge_score.compute()` function to calculate all the metrics at once: diff --git a/chapters/en/chapter7/7.mdx b/chapters/en/chapter7/7.mdx index d8e1942e4..d32fc7d8d 100644 --- a/chapters/en/chapter7/7.mdx +++ b/chapters/en/chapter7/7.mdx @@ -670,12 +670,12 @@ for example in small_eval_set: predicted_answers.append({"id": example_id, "prediction_text": best_answer["text"]}) ``` -The final format of the predicted answers is the one that will be expected by the metric we will use. As usual, we can load it with the help of the 🤗 Datasets library: +The final format of the predicted answers is the one that will be expected by the metric we will use. As usual, we can load it with the help of the 🤗 Evaluate library: ```python -from datasets import load_metric +import evaluate -metric = load_metric("squad") +metric = evaluate.load("squad") ``` This metric expects the predicted answers in the format we saw above (a list of dictionaries with one key for the ID of the example and one key for the predicted text) and the theoretical answers in the format below (a list of dictionaries with one key for the ID of the example and one key for the possible answers): diff --git a/chapters/en/chapter8/4.mdx b/chapters/en/chapter8/4.mdx index 1cc9e4e51..54232cdc9 100644 --- a/chapters/en/chapter8/4.mdx +++ b/chapters/en/chapter8/4.mdx @@ -22,7 +22,8 @@ The best way to debug an error that arises in `trainer.train()` is to manually g To demonstrate this, we will use the following script that (tries to) fine-tune a DistilBERT model on the [MNLI dataset](https://huggingface.co/datasets/glue): ```py -from datasets import load_dataset, load_metric +from datasets import load_dataset +import evaluate from transformers import ( AutoTokenizer, AutoModelForSequenceClassification, @@ -52,7 +53,7 @@ args = TrainingArguments( weight_decay=0.01, ) -metric = load_metric("glue", "mnli") +metric = evaluate.load("glue", "mnli") def compute_metrics(eval_pred): @@ -98,7 +99,8 @@ Do you notice something wrong? This, in conjunction with the error message about Why wasn't the data processed? We did use the `Dataset.map()` method on the datasets to apply the tokenizer on each sample. But if you look closely at the code, you will see that we made a mistake when passing the training and evaluation sets to the `Trainer`. Instead of using `tokenized_datasets` here, we used `raw_datasets` 🤦. So let's fix this! ```py -from datasets import load_dataset, load_metric +from datasets import load_dataset +import evaluate from transformers import ( AutoTokenizer, AutoModelForSequenceClassification, @@ -128,7 +130,7 @@ args = TrainingArguments( weight_decay=0.01, ) -metric = load_metric("glue", "mnli") +metric = evaluate.load("glue", "mnli") def compute_metrics(eval_pred): @@ -291,7 +293,8 @@ So this is the `default_data_collator`, but that's not what we want in this case The answer is because we did not pass the `tokenizer` to the `Trainer`, so it couldn't create the `DataCollatorWithPadding` we want. In practice, you should never hesitate to explicitly pass along the data collator you want to use, to make sure you avoid these kinds of errors. Let's adapt our code to do exactly that: ```py -from datasets import load_dataset, load_metric +from datasets import load_dataset +import evaluate from transformers import ( AutoTokenizer, AutoModelForSequenceClassification, @@ -322,7 +325,7 @@ args = TrainingArguments( weight_decay=0.01, ) -metric = load_metric("glue", "mnli") +metric = evaluate.load("glue", "mnli") def compute_metrics(eval_pred): @@ -416,7 +419,8 @@ trainer.model.config.num_labels With two labels, only 0s and 1s are allowed as targets, but according to the error message we got a 2. Getting a 2 is actually normal: if we remember the label names we extracted earlier, there were three, so we have indices 0, 1, and 2 in our dataset. The problem is that we didn't tell that to our model, which should have been created with three labels. So let's fix that! ```py -from datasets import load_dataset, load_metric +from datasets import load_dataset +import evaluate from transformers import ( AutoTokenizer, AutoModelForSequenceClassification, @@ -447,7 +451,7 @@ args = TrainingArguments( weight_decay=0.01, ) -metric = load_metric("glue", "mnli") +metric = evaluate.load("glue", "mnli") def compute_metrics(eval_pred): @@ -626,7 +630,8 @@ For reference, here is the completely fixed script: ```py import numpy as np -from datasets import load_dataset, load_metric +from datasets import load_dataset +import evaluate from transformers import ( AutoTokenizer, AutoModelForSequenceClassification, @@ -657,7 +662,7 @@ args = TrainingArguments( weight_decay=0.01, ) -metric = load_metric("glue", "mnli") +metric = evaluate.load("glue", "mnli") def compute_metrics(eval_pred): diff --git a/chapters/en/chapter8/4_tf.mdx b/chapters/en/chapter8/4_tf.mdx index 4ba2f3b1c..6a241216d 100644 --- a/chapters/en/chapter8/4_tf.mdx +++ b/chapters/en/chapter8/4_tf.mdx @@ -22,7 +22,8 @@ The best way to debug an error that arises in `model.fit()` is to manually go th To demonstrate this, we will use the following script that (tries to) fine-tune a DistilBERT model on the [MNLI dataset](https://huggingface.co/datasets/glue): ```py -from datasets import load_dataset, load_metric +from datasets import load_dataset +import evaluate from transformers import ( AutoTokenizer, TFAutoModelForSequenceClassification, diff --git a/chapters/es/chapter3/4.mdx b/chapters/es/chapter3/4.mdx index c16fa7d58..b5bd3f7cf 100644 --- a/chapters/es/chapter3/4.mdx +++ b/chapters/es/chapter3/4.mdx @@ -171,12 +171,12 @@ 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 Datasets 🤗. 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 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: ```py -from datasets import load_metric +import evaluate -metric = load_metric("glue", "mrpc") +metric = evaluate.load("glue", "mrpc") model.eval() for batch in eval_dataloader: batch = {k: v.to(device) for k, v in batch.items()} diff --git a/chapters/fr/chapter3/3.mdx b/chapters/fr/chapter3/3.mdx index 13563fb0d..2624cdd5a 100644 --- a/chapters/fr/chapter3/3.mdx +++ b/chapters/fr/chapter3/3.mdx @@ -110,12 +110,12 @@ import numpy as np preds = np.argmax(predictions.predictions, axis=-1) ``` -Nous pouvons maintenant comparer ces `preds` aux étiquettes. Pour construire notre fonction `compute_metric()`, nous allons nous appuyer sur les métriques de la bibliothèque 🤗 *Datasets*. Nous pouvons charger les métriques associées au jeu de données MRPC aussi facilement que nous avons chargé le jeu de données, cette fois avec la fonction `load_metric()`. L'objet retourné possède une méthode `compute()` que nous pouvons utiliser pour effectuer le calcul de la métrique : +Nous pouvons maintenant comparer ces `preds` aux étiquettes. Pour construire notre fonction `compute_metric()`, nous allons nous appuyer sur les métriques de la bibliothèque 🤗 [*Evaluate*](https://github.com/huggingface/evaluate/). Nous pouvons charger les métriques associées au jeu de données MRPC aussi facilement que nous avons chargé le jeu de données, cette fois avec la fonction `evaluate.load()`. L'objet retourné possède une méthode `compute()` que nous pouvons utiliser pour effectuer le calcul de la métrique : ```py -from datasets import load_metric +import evaluate -metric = load_metric("glue", "mrpc") +metric = evaluate.load("glue", "mrpc") metric.compute(predictions=preds, references=predictions.label_ids) ``` @@ -129,7 +129,7 @@ En regroupant le tout, nous obtenons notre fonction `compute_metrics()` : ```py def compute_metrics(eval_preds): - metric = load_metric("glue", "mrpc") + metric = evaluate.load("glue", "mrpc") logits, labels = eval_preds predictions = np.argmax(logits, axis=-1) return metric.compute(predictions=predictions, references=labels) diff --git a/chapters/fr/chapter3/3_tf.mdx b/chapters/fr/chapter3/3_tf.mdx index 6be37c3a3..9a84d533d 100644 --- a/chapters/fr/chapter3/3_tf.mdx +++ b/chapters/fr/chapter3/3_tf.mdx @@ -172,12 +172,12 @@ print(preds.shape, class_preds.shape) (408, 2) (408,) ``` -Maintenant, utilisons ces `preds` pour calculer des métriques ! Nous pouvons charger les métriques associées au jeu de données MRPC aussi facilement que nous avons chargé le jeu de données, cette fois avec la fonction `load_metric()`. L'objet retourné a une méthode `compute()` que nous pouvons utiliser pour faire le calcul de la métrique : +Maintenant, utilisons ces `preds` pour calculer des métriques ! Nous pouvons charger les métriques associées au jeu de données MRPC aussi facilement que nous avons chargé le jeu de données, cette fois avec la fonction `evaluate.load()`. L'objet retourné a une méthode `compute()` que nous pouvons utiliser pour faire le calcul de la métrique : ```py -from datasets import load_metric +import evaluate -metric = load_metric("glue", "mrpc") +metric = evaluate.load("glue", "mrpc") metric.compute(predictions=class_preds, references=raw_datasets["validation"]["label"]) ``` diff --git a/chapters/fr/chapter3/4.mdx b/chapters/fr/chapter3/4.mdx index ce42e9b7f..e66caa6db 100644 --- a/chapters/fr/chapter3/4.mdx +++ b/chapters/fr/chapter3/4.mdx @@ -172,12 +172,12 @@ Vous pouvez voir que le cœur de la boucle d'entraînement ressemble beaucoup à ### La boucle d'évaluation -Comme nous l'avons fait précédemment, nous allons utiliser une métrique fournie par la bibliothèque 🤗 *Datasets*. Nous avons déjà vu la méthode `metric.compute()`, mais les métriques peuvent en fait accumuler des batchs pour nous au fur et à mesure que nous parcourons la boucle de prédiction avec la méthode `add_batch()`. Une fois que nous avons accumulé tous les batchs, nous pouvons obtenir le résultat final avec `metric.compute()`. Voici comment implémenter tout cela dans une boucle d'évaluation : +Comme nous l'avons fait précédemment, nous allons utiliser une métrique fournie par la bibliothèque 🤗 *Evaluate*. Nous avons déjà vu la méthode `metric.compute()`, mais les métriques peuvent en fait accumuler des batchs pour nous au fur et à mesure que nous parcourons la boucle de prédiction avec la méthode `add_batch()`. Une fois que nous avons accumulé tous les batchs, nous pouvons obtenir le résultat final avec `metric.compute()`. Voici comment implémenter tout cela dans une boucle d'évaluation : ```py -from datasets import load_metric +import evaluate -metric = load_metric("glue", "mrpc") +metric = evaluate.load("glue", "mrpc") model.eval() for batch in eval_dataloader: batch = {k: v.to(device) for k, v in batch.items()} diff --git a/chapters/fr/chapter7/2.mdx b/chapters/fr/chapter7/2.mdx index de780128e..921f9629f 100644 --- a/chapters/fr/chapter7/2.mdx +++ b/chapters/fr/chapter7/2.mdx @@ -522,7 +522,7 @@ Le *framework* traditionnel utilisé pour évaluer la prédiction de la classifi !pip install seqeval ``` -Nous pouvons ensuite le charger via la fonction `load_metric()` comme nous l'avons fait dans le [chapitre 3](/course/fr/chapter3) : +Nous pouvons ensuite le charger via la fonction `evaluate.load()` comme nous l'avons fait dans le [chapitre 3](/course/fr/chapter3) : {:else} @@ -532,14 +532,14 @@ Le *framework* traditionnel utilisé pour évaluer la prédiction de la classif !pip install seqeval ``` -Nous pouvons ensuite le charger via la fonction `load_metric()` comme nous l'avons fait dans le [chapitre 3](/course/fr/chapter3) : +Nous pouvons ensuite le charger via la fonction `evaluate.load()` comme nous l'avons fait dans le [chapitre 3](/course/fr/chapter3) : {/if} ```py -from datasets import load_metric +import evaluate -metric = load_metric("seqeval") +metric = evaluate.load("seqeval") ``` Cette métrique ne se comporte pas comme la précision standard : elle prend les listes d'étiquettes comme des chaînes de caractères et non comme des entiers. Nous devrons donc décoder complètement les prédictions et les étiquettes avant de les transmettre à la métrique. Voyons comment cela fonctionne. Tout d'abord, nous allons obtenir les étiquettes pour notre premier exemple d'entraînement : diff --git a/chapters/fr/chapter7/4.mdx b/chapters/fr/chapter7/4.mdx index d7d868936..e28cf05d5 100644 --- a/chapters/fr/chapter7/4.mdx +++ b/chapters/fr/chapter7/4.mdx @@ -54,7 +54,7 @@ Pour *finetuner* ou entraîner un modèle de traduction à partir de zéro, nous Comme d'habitude, nous téléchargeons notre jeu de données en utilisant la fonction `load_dataset()` : ```py -from datasets import load_dataset, load_metric +from datasets import load_dataset raw_datasets = load_dataset("kde4", lang1="en", lang2="fr") ``` @@ -425,12 +425,12 @@ L'une des faiblesses de BLEU est qu'il s'attend à ce que le texte soit déjà t !pip install sacrebleu ``` -Nous pouvons ensuite charger ce score via `load_metric()` comme nous l'avons fait dans le [chapitre 3](/course/fr/chapter3) : +Nous pouvons ensuite charger ce score via `evaluate.load()` comme nous l'avons fait dans le [chapitre 3](/course/fr/chapter3) : ```py -from datasets import load_metric +import evaluate -metric = load_metric("sacrebleu") +metric = evaluate.load("sacrebleu") ``` Cette métrique prend des textes comme entrées et cibles. Elle est conçue pour accepter plusieurs cibles acceptables car il y a souvent plusieurs traductions possibles d'une même phrase. Le jeu de données que nous utilisons n'en fournit qu'une seule, mais en NLP, il n'est pas rare de trouver des jeux de données ayant plusieurs phrases comme étiquettes. Ainsi, les prédictions doivent être une liste de phrases mais les références doivent être une liste de listes de phrases. diff --git a/chapters/fr/chapter7/5.mdx b/chapters/fr/chapter7/5.mdx index 47428e425..c2177fb07 100644 --- a/chapters/fr/chapter7/5.mdx +++ b/chapters/fr/chapter7/5.mdx @@ -374,9 +374,9 @@ En appliquant cela à notre résumé verbeux, on obtient une précision de 6/10 et ensuite charger la métrique ROUGE comme suit : ```python -from datasets import load_metric +import evaluate -rouge_score = load_metric("rouge") +rouge_score = evaluate.load("rouge") ``` Ensuite, nous pouvons utiliser la fonction `rouge_score.compute()` pour calculer toutes les métriques en une seule fois : diff --git a/chapters/fr/chapter7/7.mdx b/chapters/fr/chapter7/7.mdx index 359e84211..b703523bd 100644 --- a/chapters/fr/chapter7/7.mdx +++ b/chapters/fr/chapter7/7.mdx @@ -691,12 +691,12 @@ for example in small_eval_set: predicted_answers.append({"id": example_id, "prediction_text": best_answer["text"]}) ``` -Le format final des réponses prédites est celui qui sera attendu par la métrique que nous allons utiliser. Comme d'habitude, nous pouvons la charger à l'aide de la bibliothèque 🤗 *Datasets* : +Le format final des réponses prédites est celui qui sera attendu par la métrique que nous allons utiliser. Comme d'habitude, nous pouvons la charger à l'aide de la bibliothèque 🤗 *Evaluate* : ```python -from datasets import load_metric +import evaluate -metric = load_metric("squad") +metric = evaluate.load("squad") ``` Cette métrique attend les réponses prédites dans le format que nous avons vu ci-dessus (une liste de dictionnaires avec une clé pour l'identifiant de l'exemple et une clé pour le texte prédit) et les réponses théoriques dans le format ci-dessous (une liste de dictionnaires avec une clé pour l'identifiant de l'exemple et une clé pour les réponses possibles) : diff --git a/chapters/fr/chapter8/4.mdx b/chapters/fr/chapter8/4.mdx index 2ea4a9ece..7ac1272c0 100644 --- a/chapters/fr/chapter8/4.mdx +++ b/chapters/fr/chapter8/4.mdx @@ -22,7 +22,8 @@ La meilleure façon de déboguer une erreur qui survient dans `trainer.train()` Pour le démontrer, nous utiliserons le script suivant qui tente de *finetuner* un modèle DistilBERT sur le [jeu de données MNLI](https://huggingface.co/datasets/glue) : ```py -from datasets import load_dataset, load_metric +from datasets import load_dataset +import evaluate from transformers import ( AutoTokenizer, AutoModelForSequenceClassification, @@ -52,7 +53,7 @@ args = TrainingArguments( weight_decay=0.01, ) -metric = load_metric("glue", "mnli") +metric = evaluate.load("glue", "mnli") def compute_metrics(eval_pred): @@ -98,7 +99,8 @@ Vous remarquez quelque chose d'anormal ? Ceci, en conjonction avec le message d' Pourquoi les données n'ont-elles pas été traitées ? Nous avons utilisé la méthode `Dataset.map()` sur les jeux de données pour appliquer le *tokenizer* sur chaque échantillon. Mais si vous regardez attentivement le code, vous verrez que nous avons fait une erreur en passant les ensembles d'entraînement et d'évaluation au `Trainer`. Au lieu d'utiliser `tokenized_datasets` ici, nous avons utilisé `raw_datasets` 🤦. Alors corrigeons ça ! ```py -from datasets import load_dataset, load_metric +from datasets import load_dataset +import evaluate from transformers import ( AutoTokenizer, AutoModelForSequenceClassification, @@ -128,7 +130,7 @@ args = TrainingArguments( weight_decay=0.01, ) -metric = load_metric("glue", "mnli") +metric = evaluate.load("glue", "mnli") def compute_metrics(eval_pred): @@ -291,7 +293,8 @@ C'est donc `default_data_collator`, mais ce n'est pas ce que nous voulons dans c La réponse est que nous n'avons pas passé le `tokenizer` au `Trainer`, donc il ne pouvait pas créer le `DataCollatorWithPadding` que nous voulons. En pratique, il ne faut jamais hésiter à transmettre explicitement l'assembleur de données que l'on veut utiliser pour être sûr d'éviter ce genre d'erreurs. Adaptons notre code pour faire exactement cela : ```py -from datasets import load_dataset, load_metric +from datasets import load_dataset +import evaluate from transformers import ( AutoTokenizer, AutoModelForSequenceClassification, @@ -322,7 +325,7 @@ args = TrainingArguments( weight_decay=0.01, ) -metric = load_metric("glue", "mnli") +metric = evaluate.load("glue", "mnli") def compute_metrics(eval_pred): @@ -417,7 +420,8 @@ trainer.model.config.num_labels Avec deux étiquettes, seuls les 0 et les 1 sont autorisés comme cibles, mais d'après le message d'erreur, nous avons obtenu un 2. Obtenir un 2 est en fait normal : si nous nous souvenons des noms des étiquettes que nous avons extraits plus tôt, il y en avait trois, donc nous avons les indices 0, 1 et 2 dans notre jeu de données. Le problème est que nous n'avons pas indiqué cela à notre modèle, qui aurait dû être créé avec trois étiquettes. Alors, corrigeons cela ! ```py -from datasets import load_dataset, load_metric +from datasets import load_dataset +import evaluate from transformers import ( AutoTokenizer, AutoModelForSequenceClassification, @@ -448,7 +452,7 @@ args = TrainingArguments( weight_decay=0.01, ) -metric = load_metric("glue", "mnli") +metric = evaluate.load("glue", "mnli") def compute_metrics(eval_pred): @@ -627,7 +631,8 @@ Pour référence, voici le script complètement corrigé : ```py import numpy as np -from datasets import load_dataset, load_metric +from datasets import load_dataset +import evaluate from transformers import ( AutoTokenizer, AutoModelForSequenceClassification, @@ -658,7 +663,7 @@ args = TrainingArguments( weight_decay=0.01, ) -metric = load_metric("glue", "mnli") +metric = evaluate.load("glue", "mnli") def compute_metrics(eval_pred): diff --git a/chapters/fr/chapter8/4_tf.mdx b/chapters/fr/chapter8/4_tf.mdx index e178f6842..257dafe26 100644 --- a/chapters/fr/chapter8/4_tf.mdx +++ b/chapters/fr/chapter8/4_tf.mdx @@ -22,7 +22,8 @@ La meilleure façon de déboguer une erreur qui survient dans `trainer.train()` Pour le démontrer, nous utiliserons le script suivant qui tente de *finetuner* un modèle DistilBERT sur le [jeu de données MNLI](https://huggingface.co/datasets/glue) : ```py -from datasets import load_dataset, load_metric +from datasets import load_dataset +import evaluate from transformers import ( AutoTokenizer, TFAutoModelForSequenceClassification, diff --git a/chapters/hi/chapter3/3.mdx b/chapters/hi/chapter3/3.mdx index 93bcdeb97..748824180 100644 --- a/chapters/hi/chapter3/3.mdx +++ b/chapters/hi/chapter3/3.mdx @@ -110,12 +110,12 @@ import numpy as np preds = np.argmax(predictions.predictions, axis=-1) ``` -अब हम उन `preds` की तुलना लेबल से कर सकते हैं। हमारे `compute_metric()` फ़ंक्शन को बनाने के लिए, हम 🤗 डेटासेट लाइब्रेरी के मेट्रिक्स पर निर्भर है। हम MRPC डेटासेट से जुड़े मेट्रिक्स को उतनी ही आसानी से लोड कर सकते हैं, जितनी आसानी से हमने डेटासेट लोड किया, इस बार `load_metric()` फ़ंक्शन के साथ। इसने एक वस्तु लौटाया जिसमे एक `compute()` विधि है जिसका उपयोग हम मीट्रिक गणना करने के लिए कर सकते हैं: +अब हम उन `preds` की तुलना लेबल से कर सकते हैं। हमारे `compute_metric()` फ़ंक्शन को बनाने के लिए, हम 🤗 [मूल्यांकन करना](https://github.com/huggingface/evaluate/) लाइब्रेरी के मेट्रिक्स पर निर्भर है। हम MRPC डेटासेट से जुड़े मेट्रिक्स को उतनी ही आसानी से लोड कर सकते हैं, जितनी आसानी से हमने डेटासेट लोड किया, इस बार `evaluate.load()` फ़ंक्शन के साथ। इसने एक वस्तु लौटाया जिसमे एक `compute()` विधि है जिसका उपयोग हम मीट्रिक गणना करने के लिए कर सकते हैं: ```py -from datasets import load_metric +import evaluate -metric = load_metric("glue", "mrpc") +metric = evaluate.load("glue", "mrpc") metric.compute(predictions=preds, references=predictions.label_ids) ``` @@ -129,7 +129,7 @@ metric.compute(predictions=preds, references=predictions.label_ids) ```py def compute_metrics(eval_preds): - metric = load_metric("glue", "mrpc") + metric = evaluate.load("glue", "mrpc") logits, labels = eval_preds predictions = np.argmax(logits, axis=-1) return metric.compute(predictions=predictions, references=labels) diff --git a/chapters/hi/chapter3/3_tf.mdx b/chapters/hi/chapter3/3_tf.mdx index 84f022ead..837983be3 100644 --- a/chapters/hi/chapter3/3_tf.mdx +++ b/chapters/hi/chapter3/3_tf.mdx @@ -181,12 +181,12 @@ print(preds.shape, class_preds.shape) (408, 2) (408,) ``` -अब, कुछ मेट्रिक्स की गणना करने के लिए उन `preds` का उपयोग करते हैं! हम MRPC डेटासेट से जुड़े मेट्रिक्स को उतनी ही आसानी से लोड कर सकते हैं, जितनी आसानी से हमने डेटासेट लोड किया, इस बार `load_metric()` फ़ंक्शन के साथ। इसने एक वस्तु लौटाया जिसमे एक `compute()` विधि है जिसका उपयोग हम मीट्रिक गणना करने के लिए कर सकते हैं: +अब, कुछ मेट्रिक्स की गणना करने के लिए उन `preds` का उपयोग करते हैं! हम MRPC डेटासेट से जुड़े मेट्रिक्स को उतनी ही आसानी से लोड कर सकते हैं, जितनी आसानी से हमने डेटासेट लोड किया, इस बार `evaluate.load()` फ़ंक्शन के साथ। इसने एक वस्तु लौटाया जिसमे एक `compute()` विधि है जिसका उपयोग हम मीट्रिक गणना करने के लिए कर सकते हैं: ```py -from datasets import load_metric +import evaluate -metric = load_metric("glue", "mrpc") +metric = evaluate.load("glue", "mrpc") metric.compute(predictions=class_preds, references=raw_datasets["validation"]["label"]) ``` diff --git a/chapters/hi/chapter3/4.mdx b/chapters/hi/chapter3/4.mdx index 10e690a1b..181e3e0e5 100644 --- a/chapters/hi/chapter3/4.mdx +++ b/chapters/hi/chapter3/4.mdx @@ -172,12 +172,12 @@ for epoch in range(num_epochs): ### मूल्यांकन लूप -जैसा कि हमने पहले किया था, हम 🤗 डेटासेट लाइब्रेरी द्वारा प्रदान किए गए मीट्रिक का उपयोग करेंगे। हम पहले ही `metric.compute()` विधि देख चुके हैं, लेकिन मेट्रिक्स वास्तव में हमारे लिए बैच जमा कर सकते हैं जब हम भविष्यवाणी लूप पर जाते हैं `add_batch()` विधि के साथ । एक बार जब हम सभी बैचों को जमा कर लेते हैं, तो हम `metric.compute()` के साथ अंतिम परिणाम प्राप्त कर सकते हैं। मूल्यांकन लूप में इन सभी को कार्यान्वित करने का तरीका यहां दिया गया है: +जैसा कि हमने पहले किया था, हम 🤗 मूल्यांकन करना लाइब्रेरी द्वारा प्रदान किए गए मीट्रिक का उपयोग करेंगे। हम पहले ही `metric.compute()` विधि देख चुके हैं, लेकिन मेट्रिक्स वास्तव में हमारे लिए बैच जमा कर सकते हैं जब हम भविष्यवाणी लूप पर जाते हैं `add_batch()` विधि के साथ । एक बार जब हम सभी बैचों को जमा कर लेते हैं, तो हम `metric.compute()` के साथ अंतिम परिणाम प्राप्त कर सकते हैं। मूल्यांकन लूप में इन सभी को कार्यान्वित करने का तरीका यहां दिया गया है: ```py -from datasets import load_metric +import evaluate -metric = load_metric("glue", "mrpc") +metric = evaluate.load("glue", "mrpc") model.eval() for batch in eval_dataloader: batch = {k: v.to(device) for k, v in batch.items()} diff --git a/chapters/ja/chapter7/2.mdx b/chapters/ja/chapter7/2.mdx index c6fad9d03..efd90ad71 100644 --- a/chapters/ja/chapter7/2.mdx +++ b/chapters/ja/chapter7/2.mdx @@ -534,7 +534,7 @@ model.fit( !pip install seqeval ``` -そして、[第3章](/course/ja/chapter3) で行ったように `load_metric()` 関数で読み込むことができるようになります。 +そして、[第3章](/course/ja/chapter3) で行ったように `evaluate.load()` 関数で読み込むことができるようになります。 {:else} @@ -544,14 +544,14 @@ model.fit( !pip install seqeval ``` -そして、[第3章](/course/ja/chapter3) で行ったように `load_metric()` 関数で読み込むことができるようになります。 +そして、[第3章](/course/ja/chapter3) で行ったように `evaluate.load()` 関数で読み込むことができるようになります。 {/if} ```py -from datasets import load_metric +import evaluate -metric = load_metric("seqeval") +metric = evaluate.load("seqeval") ``` この指標は標準的な精度指標のように動作しません:実際にはラベルのリストを整数ではなく文字列として受け取るので、予測値とラベルを指標に渡す前に完全にデコードする必要があります。 diff --git a/chapters/ja/chapter7/4.mdx b/chapters/ja/chapter7/4.mdx index 969647748..cadd8c24c 100644 --- a/chapters/ja/chapter7/4.mdx +++ b/chapters/ja/chapter7/4.mdx @@ -56,7 +56,7 @@ いつものように、 `load_dataset()` 関数を使用してデータセットをダウンロードします。 ```py -from datasets import load_dataset, load_metric +from datasets import load_dataset raw_datasets = load_dataset("kde4", lang1="en", lang2="fr") ``` @@ -439,12 +439,12 @@ BLEUの弱点は、テキストがすでにトークン化されていること !pip install sacrebleu ``` -そして、[第3章](/course/ja/chapter3) で行ったように `load_metric()` で読み込むことができるようになります。 +そして、[第3章](/course/ja/chapter3) で行ったように `evaluate.load()` で読み込むことができるようになります。 ```py -from datasets import load_metric +import evaluate -metric = load_metric("sacrebleu") +metric = evaluate.load("sacrebleu") ``` この指標はテキストを入力とターゲットとして受け取ります。同じ文でも複数の翻訳があることが多いので、複数の翻訳を受け入れるように設計されています。私たちが使っているデータセットは1つしか提供していませんが、NLPでは複数の文をラベルとして与えるデータセットが珍しくありません。つまり、予測は文のリストであるべきですが、その参照は文のリストのリストであるべきなのです。 diff --git a/chapters/ja/chapter7/5.mdx b/chapters/ja/chapter7/5.mdx index 3f83c6dad..13232100e 100644 --- a/chapters/ja/chapter7/5.mdx +++ b/chapters/ja/chapter7/5.mdx @@ -354,9 +354,9 @@ $$ \mathrm{Precision} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{ そして、ROUGE指標を読み込みます。 ```python -from datasets import load_metric +import evaluate -rouge_score = load_metric("rouge") +rouge_score = evaluate.load("rouge") ``` そして、`rouge_score.compute()`関数を使って、すべての指標を一度に計算することができます。 diff --git a/chapters/ja/chapter7/7.mdx b/chapters/ja/chapter7/7.mdx index e54482205..8ee20d14f 100644 --- a/chapters/ja/chapter7/7.mdx +++ b/chapters/ja/chapter7/7.mdx @@ -671,12 +671,12 @@ for example in small_eval_set: predicted_answers.append({"id": example_id, "prediction_text": best_answer["text"]}) ``` -予測された答えの最終的なフォーマットは、私たちが使用する指標によって期待されるものです。いつものように、🤗 Datasetsライブラリの助けを借りて読み込むことができます。 +予測された答えの最終的なフォーマットは、私たちが使用する指標によって期待されるものです。いつものように、🤗 Evaluateライブラリの助けを借りて読み込むことができます。 ```python -from datasets import load_metric +import evaluate -metric = load_metric("squad") +metric = evaluate.load("squad") ``` この指標は、上で見た形式の予測された答え(サンプルのIDと予測されたテキストの1つのキーを持つ辞書のリスト)と、下の形式の理論的な答え(サンプルのIDと可能な答えの1つのキーを持つ辞書のリスト)を期待するものです。 diff --git a/chapters/ru/chapter3/3.mdx b/chapters/ru/chapter3/3.mdx index b68dbfa01..5ff79c1ed 100644 --- a/chapters/ru/chapter3/3.mdx +++ b/chapters/ru/chapter3/3.mdx @@ -113,12 +113,12 @@ import numpy as np preds = np.argmax(predictions.predictions, axis=-1) ``` -Теперь мы можем сравнить эти предсказания с лейблами. Для создания функции `compute_metric()` мы воспользуемся метриками из библиотеки 🤗 Datasets. Мы можем загрузить подходящие для датасета MRPC метрики так же просто, как мы загрузили датасет, но на этот раз с помощью функции `load_metric()`. Возвращаемый объект имеет метод `compute()`, который мы можем использовать для вычисления метрики: +Теперь мы можем сравнить эти предсказания с лейблами. Для создания функции `compute_metric()` мы воспользуемся метриками из библиотеки 🤗 [Evaluate](https://github.com/huggingface/evaluate/). Мы можем загрузить подходящие для датасета MRPC метрики так же просто, как мы загрузили датасет, но на этот раз с помощью функции `evaluate.load()`. Возвращаемый объект имеет метод `compute()`, который мы можем использовать для вычисления метрики: ```py -from datasets import load_metric +import evaluate -metric = load_metric("glue", "mrpc") +metric = evaluate.load("glue", "mrpc") metric.compute(predictions=preds, references=predictions.label_ids) ``` @@ -132,7 +132,7 @@ metric.compute(predictions=preds, references=predictions.label_ids) ```py def compute_metrics(eval_preds): - metric = load_metric("glue", "mrpc") + metric = evaluate.load("glue", "mrpc") logits, labels = eval_preds predictions = np.argmax(logits, axis=-1) return metric.compute(predictions=predictions, references=labels) diff --git a/chapters/ru/chapter3/3_tf.mdx b/chapters/ru/chapter3/3_tf.mdx index 01f73e339..a3b1f7ef6 100644 --- a/chapters/ru/chapter3/3_tf.mdx +++ b/chapters/ru/chapter3/3_tf.mdx @@ -173,12 +173,12 @@ print(preds.shape, class_preds.shape) (408, 2) (408,) ``` -Теперь давайте используем эти `preds` для вычисления некоторых метрик! Мы можем загрузить метрики, связанные с датасетом MRPC, так же легко, как мы загрузили этот датасет, на этот раз с помощью функции `load_metric()`. Возвращаемый объект имеет метод `compute()`, который мы можем использовать для вычисления метрики: +Теперь давайте используем эти `preds` для вычисления некоторых метрик! Мы можем загрузить метрики, связанные с датасетом MRPC, так же легко, как мы загрузили этот датасет, на этот раз с помощью функции `evaluate.load()`. Возвращаемый объект имеет метод `compute()`, который мы можем использовать для вычисления метрики: ```py -from datasets import load_metric +import evaluate -metric = load_metric("glue", "mrpc") +metric = evaluate.load("glue", "mrpc") metric.compute(predictions=class_preds, references=raw_datasets["validation"]["label"]) ``` diff --git a/chapters/ru/chapter3/4.mdx b/chapters/ru/chapter3/4.mdx index c267ca864..15568df81 100644 --- a/chapters/ru/chapter3/4.mdx +++ b/chapters/ru/chapter3/4.mdx @@ -172,12 +172,12 @@ for epoch in range(num_epochs): ### Валидационный цикл -Ранее мы использовали метрику, которую нам предоставляла библиотека 🤗 Datasets. Мы уже знаем, что есть метод `metric.compute()`, однако метрики могут накапливать значения в процессе итерирования по батчу, для этого есть метод `add_batch()`. После того, как мы пройдемся по всем батчам, мы сможем вычислить финальный результат с помощью `metric.compute()`. Вот пример того, как это можно сделать в цикле валидации: +Ранее мы использовали метрику, которую нам предоставляла библиотека 🤗 Evaluate. Мы уже знаем, что есть метод `metric.compute()`, однако метрики могут накапливать значения в процессе итерирования по батчу, для этого есть метод `add_batch()`. После того, как мы пройдемся по всем батчам, мы сможем вычислить финальный результат с помощью `metric.compute()`. Вот пример того, как это можно сделать в цикле валидации: ```py -from datasets import load_metric +import evaluate -metric = load_metric("glue", "mrpc") +metric = evaluate.load("glue", "mrpc") model.eval() for batch in eval_dataloader: batch = {k: v.to(device) for k, v in batch.items()} diff --git a/chapters/th/chapter3/3.mdx b/chapters/th/chapter3/3.mdx index 783b9325b..e510e443d 100644 --- a/chapters/th/chapter3/3.mdx +++ b/chapters/th/chapter3/3.mdx @@ -110,12 +110,12 @@ import numpy as np preds = np.argmax(predictions.predictions, axis=-1) ``` -ตอนนี้เราก็สามารถเปรียบเทียบ `preds` เหล่านี้กับ labels ของเราได้แล้ว เพื่อจะสร้างฟังก์ชั่น `compute_metric()` ของเรา เราจะยืม metrics จากไลบรารี่ 🤗 Datasets มาใช้ เราสามารถโหลด metrics ที่เกี่ยวข้องกับ MRPC dataset ได้อย่างง่ายดายเหมือนกับที่เราโหลดชุดข้อมูล โดยการใช้ฟังก์ชั่น `load_metric()` โดยจะได้ผลลัพธ์เป็นออพเจ็กต์ที่มีเมธอด `compute()` ที่เราสามารถนำไปใช้ในการคำนวณ metric ได้: +ตอนนี้เราก็สามารถเปรียบเทียบ `preds` เหล่านี้กับ labels ของเราได้แล้ว เพื่อจะสร้างฟังก์ชั่น `compute_metric()` ของเรา เราจะยืม metrics จากไลบรารี่ 🤗 [Evaluate](https://github.com/huggingface/evaluate/) มาใช้ เราสามารถโหลด metrics ที่เกี่ยวข้องกับ MRPC dataset ได้อย่างง่ายดายเหมือนกับที่เราโหลดชุดข้อมูล โดยการใช้ฟังก์ชั่น `evaluate.load()` โดยจะได้ผลลัพธ์เป็นออพเจ็กต์ที่มีเมธอด `compute()` ที่เราสามารถนำไปใช้ในการคำนวณ metric ได้: ```py -from datasets import load_metric +import evaluate -metric = load_metric("glue", "mrpc") +metric = evaluate.load("glue", "mrpc") metric.compute(predictions=preds, references=predictions.label_ids) ``` @@ -129,7 +129,7 @@ metric.compute(predictions=preds, references=predictions.label_ids) ```py def compute_metrics(eval_preds): - metric = load_metric("glue", "mrpc") + metric = evaluate.load("glue", "mrpc") logits, labels = eval_preds predictions = np.argmax(logits, axis=-1) return metric.compute(predictions=predictions, references=labels) diff --git a/chapters/th/chapter3/3_tf.mdx b/chapters/th/chapter3/3_tf.mdx index 6e6941754..e9ccd4611 100644 --- a/chapters/th/chapter3/3_tf.mdx +++ b/chapters/th/chapter3/3_tf.mdx @@ -179,12 +179,12 @@ print(preds.shape, class_preds.shape) (408, 2) (408,) ``` -ตอนนี้เรามาใช้ `preds` เพื่อคำนวณ metrics บางอย่างกันดีกว่า! เราสามารถโหลด metrics ที่เกี่ยวข้องกับ MRPC dataset ได้อย่างง่ายดายเหมือนกับที่เราโหลดชุดข้อมูล โดยการใช้ฟังก์ชั่น `load_metric()` โดยจะได้ผลลัพธ์เป็นออพเจ็กต์ที่มีเมธอด `compute()` ที่เราสามารถนำไปใช้ในการคำนวณ metric ได้: +ตอนนี้เรามาใช้ `preds` เพื่อคำนวณ metrics บางอย่างกันดีกว่า! เราสามารถโหลด metrics ที่เกี่ยวข้องกับ MRPC dataset ได้อย่างง่ายดายเหมือนกับที่เราโหลดชุดข้อมูล โดยการใช้ฟังก์ชั่น `evaluate.load()` โดยจะได้ผลลัพธ์เป็นออพเจ็กต์ที่มีเมธอด `compute()` ที่เราสามารถนำไปใช้ในการคำนวณ metric ได้: ```py -from datasets import load_metric +import evaluate -metric = load_metric("glue", "mrpc") +metric = evaluate.load("glue", "mrpc") metric.compute(predictions=class_preds, references=raw_datasets["validation"]["label"]) ``` diff --git a/chapters/th/chapter3/4.mdx b/chapters/th/chapter3/4.mdx index c8fcec348..d5f9f2f94 100644 --- a/chapters/th/chapter3/4.mdx +++ b/chapters/th/chapter3/4.mdx @@ -172,12 +172,12 @@ for epoch in range(num_epochs): ### ลูปในการประเมินผลโมเดล (evaluation loop) -เหมือนกับที่เราได้ทำไว้ก่อนหน้านี้ เราสามารถเรียกใช้ metric จากไลบรารี่ 🤗 Datasets ได้เลย เราได้เห็นเมธอด `metric.compute() มาแล้ว แต่ metrics ยังสามารถรวบรวมผลมาเป็น batches ให้เราได้ด้วย โดยใช้เมธอด `add_batch()` โดยเมื่อเรารวบรวมผลมาจากทุก batches แล้ว เราก็จะคำนวณผลลัพธ์สุดท้ายได้โดยใช้เมธอด `metric.compute()` โค้ดข้างล่างนี้เป็นตัวอย่างการทำทุกอย่างที่เรากล่าวมานี้ในลูปสำหรับประเมินผลโมเดล: +เหมือนกับที่เราได้ทำไว้ก่อนหน้านี้ เราสามารถเรียกใช้ metric จากไลบรารี่ 🤗 Evaluate ได้เลย เราได้เห็นเมธอด `metric.compute()` มาแล้ว แต่ metrics ยังสามารถรวบรวมผลมาเป็น batches ให้เราได้ด้วย โดยใช้เมธอด `add_batch()` โดยเมื่อเรารวบรวมผลมาจากทุก batches แล้ว เราก็จะคำนวณผลลัพธ์สุดท้ายได้โดยใช้เมธอด `metric.compute()` โค้ดข้างล่างนี้เป็นตัวอย่างการทำทุกอย่างที่เรากล่าวมานี้ในลูปสำหรับประเมินผลโมเดล: ```py -from datasets import load_metric +import evaluate -metric = load_metric("glue", "mrpc") +metric = evaluate.load("glue", "mrpc") model.eval() for batch in eval_dataloader: batch = {k: v.to(device) for k, v in batch.items()} diff --git a/chapters/zh-CN/chapter3/3.mdx b/chapters/zh-CN/chapter3/3.mdx index 1d452b8fe..de8018344 100644 --- a/chapters/zh-CN/chapter3/3.mdx +++ b/chapters/zh-CN/chapter3/3.mdx @@ -110,12 +110,12 @@ import numpy as np preds = np.argmax(predictions.predictions, axis=-1) ``` -现在建立我们的 **compute_metric()** 函数来较为直观地评估模型的好坏,我们将使用 🤗 Datasets 库中的指标。我们可以像加载数据集一样轻松加载与 MRPC 数据集关联的指标,这次使用 **load_metric()** 函数。返回的对象有一个 **compute()**方法我们可以用来进行度量计算的方法: +现在建立我们的 **compute_metric()** 函数来较为直观地评估模型的好坏,我们将使用 🤗 [Evaluate](https://github.com/huggingface/evaluate/) 库中的指标。我们可以像加载数据集一样轻松加载与 MRPC 数据集关联的指标,这次使用 **evaluate.load()** 函数。返回的对象有一个 **compute()**方法我们可以用来进行度量计算的方法: ```py -from datasets import load_metric +import evaluate -metric = load_metric("glue", "mrpc") +metric = evaluate.load("glue", "mrpc") metric.compute(predictions=preds, references=predictions.label_ids) ``` @@ -129,7 +129,7 @@ metric.compute(predictions=preds, references=predictions.label_ids) ```py def compute_metrics(eval_preds): - metric = load_metric("glue", "mrpc") + metric = evaluate.load("glue", "mrpc") logits, labels = eval_preds predictions = np.argmax(logits, axis=-1) return metric.compute(predictions=predictions, references=labels) diff --git a/chapters/zh-CN/chapter3/3_tf.mdx b/chapters/zh-CN/chapter3/3_tf.mdx index 911e12a92..be3953a8c 100644 --- a/chapters/zh-CN/chapter3/3_tf.mdx +++ b/chapters/zh-CN/chapter3/3_tf.mdx @@ -172,12 +172,12 @@ print(preds.shape, class_preds.shape) (408, 2) (408,) ``` -现在,让我们使用这些 `preds` 来计算一些指标! 我们可以像加载数据集一样轻松地加载与 MRPC 数据集相关的指标,这次使用的是 `load_metric()` 函数。 返回的对象有一个 `compute()` 方法,我们可以使用它来进行度量计算: +现在,让我们使用这些 `preds` 来计算一些指标! 我们可以像加载数据集一样轻松地加载与 MRPC 数据集相关的指标,这次使用的是 `evaluate.load()` 函数。 返回的对象有一个 `compute()` 方法,我们可以使用它来进行度量计算: ```py -from datasets import load_metric +import evaluate -metric = load_metric("glue", "mrpc") +metric = evaluate.load("glue", "mrpc") metric.compute(predictions=class_preds, references=raw_datasets["validation"]["label"]) ``` diff --git a/chapters/zh-CN/chapter3/4.mdx b/chapters/zh-CN/chapter3/4.mdx index f1de4cc48..aab5f40a6 100644 --- a/chapters/zh-CN/chapter3/4.mdx +++ b/chapters/zh-CN/chapter3/4.mdx @@ -171,12 +171,12 @@ for epoch in range(num_epochs): ### 评估循环 -正如我们之前所做的那样,我们将使用 🤗 Datasets 库提供的指标。我们已经了解了 `metric.compute()` 方法,当我们使用 `add_batch()`方法进行预测循环时,实际上该指标可以为我们累积所有 `batch` 的结果。一旦我们累积了所有 `batch` ,我们就可以使用 `metric.compute()` 得到最终结果 .以下是在评估循环中实现所有这些的方法: +正如我们之前所做的那样,我们将使用 🤗 Evaluate 库提供的指标。我们已经了解了 `metric.compute()` 方法,当我们使用 `add_batch()`方法进行预测循环时,实际上该指标可以为我们累积所有 `batch` 的结果。一旦我们累积了所有 `batch` ,我们就可以使用 `metric.compute()` 得到最终结果 .以下是在评估循环中实现所有这些的方法: ```py -from datasets import load_metric +import evaluate -metric = load_metric("glue", "mrpc") +metric = evaluate.load("glue", "mrpc") model.eval() for batch in eval_dataloader: batch = {k: v.to(device) for k, v in batch.items()} diff --git a/utils/generate_notebooks.py b/utils/generate_notebooks.py index 7e85013e1..e64100810 100644 --- a/utils/generate_notebooks.py +++ b/utils/generate_notebooks.py @@ -184,11 +184,11 @@ def build_notebook(fname, title, output_dir="."): nb_cells = [ nb_cell(f"# {title}", code=False), - nb_cell("Install the Transformers and Datasets libraries to run this notebook.", code=False), + nb_cell("Install the Transformers, Datasets, and Evaluate libraries to run this notebook.", code=False), ] # Install cell - installs = ["!pip install datasets transformers[sentencepiece]"] + installs = ["!pip install datasets evaluate transformers[sentencepiece]"] if title in sections_with_accelerate: installs.append("!pip install accelerate") installs.append("# To run the training on TPU, you will need to uncomment the followin line:") From 76b1abe077bbe169876f640f5e28a767ef1d2438 Mon Sep 17 00:00:00 2001 From: lewtun Date: Tue, 26 Jul 2022 10:40:06 +0200 Subject: [PATCH 24/51] Bump release (#288) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Chapter 2 Section 1 Bengali Translation (huggingface#72) (#168) * [TH] Chapter 6 Section 1 and 2 (#171) Co-authored-by: Suteera * [FA] CH1 / P1-2 (#142) * Spanish Chapter 3: sections 1 & 2 (#162) * fix typos in bpe, wordpiece, unigram (#166) * [FR] French Review (#186) * Part 7: Training a causal... fixes (#179) * typo & error mitigation * consistency * Trainer.predict() returns 3 fields * ran make style * [TR] Translated Chapter 1.6 🤗 (#185) * added chapter 1/6 to _toctree.yml * [TR] Translated Chapter 1.6 🤗 * [PT][Chapter 01 - 2.mdx] - issue #51 (#170) * Fix Gradio ToC (#193) * Add Gradio authors and Blocks event (#189) * Update 6.mdx (#188) Correct link to Transformer XL doc * Add translating notes and glossary to Spanish (#192) * Add translating notes and glosary to Spanish * Adding glossary to the toc * add pt 4.3 (#191) * [FR] Visual corrections (#190) * [PT] add chapter 4.4 and 4.5 (#196) * fix invite discord link (#197) * [FA] Second draft of CH2/P1-2 (#139) * added chapter3 in hindi (#198) * [TR] Chapter 3/1 (#165) * [RU] Ch3-1/2/3 (#200) * [PT] add 5.1 and 5.2 (#204) * [FA] - Ch3 - P1 and P2 (#199) * [PT] add `end-of-chapter quiz` for chapter 4 (4.6) (#201) Co-authored-by: lewtun * Chapter1: 2.mdx Translated. (#206) * Remove comments from Persian ToC (#210) * Fix CI URL for PRs (#211) * code fragment & english syntax and meaning (#203) * Updated Ch1/1 with Emoji (#214) * Add missing numpy import (#217) * [ES] translate sections 8.1 and 8.2 (#215) * Fix path to datasets (#216) * [PT] add 5.3 (#218) * fix 4.3 (#223) * Fix notebook generation (#227) * Add Gradio nb links * add 5.4 (#226) * add pt wip (#225) * Added Gujarati List. (#221) * Add Gradio nbs links to fr (#228) * Chinese - Chapter 3finished (#219) * add ch7 at _toctree and translate 7.1 (#222) * add 5.5 (#235) * [FR] Review of chapter 7 (#233) * Italian translation - chapter 4 (#230) * Added Thai translation of chapters 3 (#231) * [Ru] Add part 2, chapter 2 (#234) * Update 8.mdx (#237) - Remove Gradio Blocks Party - Add, Where to next? section * Created HI/Chapter1/5.mdx (#232) * Add Spanish chaper3/section4, update toc and glossary (#238) * [RU] Chapter 3 finished (#239) * [PT] add 5.6 and 5.7 (#240) * [EN] Visual corrections (#245) * Translation for 1/4, 1/5 and 1/6. (#247) * add event in PT (#250) * Pin version of black (#252) * Translate ja event (#241) * [PT] add quiz chapter 5 (#243) * Update 5.mdx (#253) inconsistent naming with line 327 * Translation for Traditional Chinese (zh-tw) chapter0 (#251) Co-authored-by: Lewis Tunstall * Translated the whole Chapter 3 to Thai (#255) * Japanese chapter 4 (#244) * Translation of 1/7, 1/8, and 1/9. (#263) * [PT] add chapter 8.1 and 8.2 (#265) * [RU] Chapter 4 (#269) * Add Thai translation for chapter 6.3b to 6.10 (#268) * add 8.3 (#266) * 3.mdx of chapter 01 (#260) Co-authored-by: Lewis Tunstall * Fix typo (#271) * [PT] add chapter 6.1 (#273) * add Japanese chapter7 (#267) * replace `load_metric` with `evaluate.load` (#285) * update `load_metric` refs to `evaluate.load` Co-authored-by: lewtun * [GJ] Translation to Gujarati - Ch0 Setup (#287) * [PT] add chapter 6.2 and 6.3 (#279) * zh-CN - Chapter 4,5finished (#281) Co-authored-by: Lewis Tunstall * Chapter 01 - Done [PT] #51 (#280) Co-authored-by: Lewis Tunstall Co-authored-by: Avishek Das Co-authored-by: Suteera Seeha <33692408+meanna@users.noreply.github.com> Co-authored-by: Suteera Co-authored-by: Saeed Choobani Co-authored-by: Fermin Ordaz Co-authored-by: Kerem Turgutlu Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Sebastian Sosa <37946988+CakeCrusher@users.noreply.github.com> Co-authored-by: tanersekmen <56790802+tanersekmen@users.noreply.github.com> Co-authored-by: Victor Costa <54755870+victorescosta@users.noreply.github.com> Co-authored-by: Camille Couturier Co-authored-by: João Gustavo A. Amorim Co-authored-by: Bahram Shamshiri Co-authored-by: Kavya <36916536+robotjellyzone@users.noreply.github.com> Co-authored-by: Batuhan Ayhan Co-authored-by: Pavel <60391448+pdumin@users.noreply.github.com> Co-authored-by: Kambiz Ghoorchian Co-authored-by: Vedant Pandya Co-authored-by: Diego Vargas <91356068+dzarkV@users.noreply.github.com> Co-authored-by: Thomas O'Brien Co-authored-by: Lincoln V Schreiber Co-authored-by: 1375626371 <40328311+1375626371@users.noreply.github.com> Co-authored-by: Giorgio Severi Co-authored-by: svv73 <88366711+svv73@users.noreply.github.com> Co-authored-by: Ömer Faruk Özdemir Co-authored-by: Caterina Bonan <97481648+CaterinaBi@users.noreply.github.com> Co-authored-by: Hiromu Hota Co-authored-by: trtd56 <5toda6@gmail.com> Co-authored-by: Mehrdad Nezamdoost Co-authored-by: Wolvz Co-authored-by: a-krirk <56425947+a-krirk@users.noreply.github.com> Co-authored-by: atgctg <105969161+atgctg@users.noreply.github.com> Co-authored-by: Thiago Medeiros Co-authored-by: webbigdata-jp <87654083+webbigdata-jp@users.noreply.github.com> Co-authored-by: Leandro von Werra Co-authored-by: Bhadresh Savani --- chapters/gj/_toctree.yml | 5 +- chapters/gj/chapter0/1.mdx | 99 ++++- chapters/pt/_toctree.yml | 21 + chapters/pt/chapter1/10.mdx | 252 ++++++++++++ chapters/pt/chapter1/4.mdx | 171 ++++++++ chapters/pt/chapter1/5.mdx | 17 + chapters/pt/chapter1/6.mdx | 14 + chapters/pt/chapter1/7.mdx | 14 + chapters/pt/chapter1/8.mdx | 24 ++ chapters/pt/chapter1/9.mdx | 11 + chapters/pt/chapter6/2.mdx | 252 ++++++++++++ chapters/pt/chapter6/3.mdx | 471 +++++++++++++++++++++ chapters/zh-CN/_toctree.yml | 44 +- chapters/zh-CN/chapter0/1.mdx | 2 +- chapters/zh-CN/chapter1/1.mdx | 2 +- chapters/zh-CN/chapter2/1.mdx | 2 +- chapters/zh-CN/chapter3/1.mdx | 2 +- chapters/zh-CN/chapter4/1.mdx | 15 + chapters/zh-CN/chapter4/2.mdx | 97 +++++ chapters/zh-CN/chapter4/3.mdx | 648 +++++++++++++++++++++++++++++ chapters/zh-CN/chapter4/4.mdx | 82 ++++ chapters/zh-CN/chapter4/5.mdx | 7 + chapters/zh-CN/chapter4/6.mdx | 215 ++++++++++ chapters/zh-CN/chapter5/1.mdx | 17 + chapters/zh-CN/chapter5/2.mdx | 167 ++++++++ chapters/zh-CN/chapter5/3.mdx | 743 ++++++++++++++++++++++++++++++++++ chapters/zh-CN/chapter5/4.mdx | 287 +++++++++++++ chapters/zh-CN/chapter5/5.mdx | 461 +++++++++++++++++++++ chapters/zh-CN/chapter5/6.mdx | 526 ++++++++++++++++++++++++ chapters/zh-CN/chapter5/7.mdx | 11 + chapters/zh-CN/chapter5/8.mdx | 216 ++++++++++ 31 files changed, 4883 insertions(+), 12 deletions(-) create mode 100644 chapters/pt/chapter1/10.mdx create mode 100644 chapters/pt/chapter1/4.mdx create mode 100644 chapters/pt/chapter1/5.mdx create mode 100644 chapters/pt/chapter1/6.mdx create mode 100644 chapters/pt/chapter1/7.mdx create mode 100644 chapters/pt/chapter1/8.mdx create mode 100644 chapters/pt/chapter1/9.mdx create mode 100644 chapters/pt/chapter6/2.mdx create mode 100644 chapters/pt/chapter6/3.mdx create mode 100644 chapters/zh-CN/chapter4/1.mdx create mode 100644 chapters/zh-CN/chapter4/2.mdx create mode 100644 chapters/zh-CN/chapter4/3.mdx create mode 100644 chapters/zh-CN/chapter4/4.mdx create mode 100644 chapters/zh-CN/chapter4/5.mdx create mode 100644 chapters/zh-CN/chapter4/6.mdx create mode 100644 chapters/zh-CN/chapter5/1.mdx create mode 100644 chapters/zh-CN/chapter5/2.mdx create mode 100644 chapters/zh-CN/chapter5/3.mdx create mode 100644 chapters/zh-CN/chapter5/4.mdx create mode 100644 chapters/zh-CN/chapter5/5.mdx create mode 100644 chapters/zh-CN/chapter5/6.mdx create mode 100644 chapters/zh-CN/chapter5/7.mdx create mode 100644 chapters/zh-CN/chapter5/8.mdx diff --git a/chapters/gj/_toctree.yml b/chapters/gj/_toctree.yml index 5c7be7a95..04b120fba 100644 --- a/chapters/gj/_toctree.yml +++ b/chapters/gj/_toctree.yml @@ -1,5 +1,4 @@ -- title: 0. સ્થાપના +- title: 0. સિસ્ટમ સેટઅપ sections: - local: chapter0/1 - title: પરિચય - + title: પરિચય \ No newline at end of file diff --git a/chapters/gj/chapter0/1.mdx b/chapters/gj/chapter0/1.mdx index 55bb3d7db..de32bfdb2 100644 --- a/chapters/gj/chapter0/1.mdx +++ b/chapters/gj/chapter0/1.mdx @@ -4,10 +4,107 @@ આ કોર્સમાં આપણે જે લાઈબ્રેરીનો ઉપયોગ કરીશું તે Python Package તરીકે ઉપલબ્ધ છે, તેથી અહીં અમે તમને બતાવીશું કે Python Environment કેવી રીતે સેટ કરવું અને તમને જોઈતી વિશિષ્ટ લાઈબ્રેરીઓ કેવી રીતે ઇન્સ્ટોલ કરવી. -Colab Notebook અથવા Python Virtual એન્વાયર્નમેન્ટનો ઉપયોગ કરીને અમે તમારા કામનું વાતાવરણ સેટ કરવાની બે રીતે આવરી લઈશું. તમને જે સૌથી વધુ પસંદ હોય તે ઉપયોગ કરો. નવા નિશાળિયા માટે, અમે ભલામણ કરીએ છીએ કે તમે Colab નોટબુકથી શરૂઆત કરો. +Colab Notebook અથવા Python Virtual એન્વાયર્નમેન્ટનો ઉપયોગ કરીને અમે તમારા કામનું વાતાવરણ સેટ કરવાની બે રીતે આવરી લઈશું. તમને જે સૌથી વધુ પસંદ હોય તે ઉપયોગ કરો. નવા નિશાળિયા માટે, અમે ભલામણ કરીએ છીએ કે તમે Colab નોટબુકથી શરૂઆત કરો. નોંધ કરો કે અમે Windows System ને આવરી શું નહીં. જો તમે Windows ચલાવી રહ્યાં હોવ, તો અમે તેને અનુસરવા માટે Colab Notebook નો ઉપયોગ કરવાનો સુઝાવ આપીએ છીએ. જો તમે Linux વિતરણ અથવા MacOS નો ઉપયોગ કરી રહ્યાં છો, તો તમે અહીં વર્ણવેલ કોઈપણ અભિગમનો ઉપયોગ કરી શકો છો. અલબત્ત મોટાભાગનો આધાર તમારા હગિંગ ફેસ એકાઉન્ટ પર છે. અમે હમણાં એક ખાતું બનાવવાની ભલામણ કરીએ છીએ: [ખાતું અહીં બનાવો](https://huggingface.co/join) +## Google Colab Notebook(ગૂગલ કોલાબ નોટબુક) ની મદદ થી +હુગિંગફેસ(huggingface) નું સૌથી આસાન સેટઅપ Google Colab નોટબુક થી કરી શકાય. તમારા વેબ બ્રાઉઝર માં colab ઓપન કરો. + +જો તમે પેહલા colab થી પરિચિત ના હોવ, તો [પરિચય](https://colab.research.google.com/notebooks/intro.ipynb). થી શરૂઆત કરવી. Colab તમને advanced hardware જેમકે GPU અથવા TPU આપશે, જે નાના prototype માટે વિના મૂલ્યે વાપરી શકાય. + +જો તમને એક વાર colab ફાવી જાય તો નવી નોટબુક open કરી જરૂરી પેકેજ install કરી શકાય જે setup કરવા માટે અત્યંત જરૂરી છે.: + +
+An empty colab notebook +
+ +હવે આપણે libraries install કરીશું જે આખા course ma વપરાશે. આપણે વાપરીશું installation માટે, જે python ma પેકેજ મેનેજર છે. Notebook ના cells માં તમે કમાંડ run કરી શકો જો તમે એને થી શરૂ કરો. તમે ને આ રીતે કરી શકો: + +``` +!pip install transformers +``` + +જો આપને ચકાસવું હોય કે પેકેજ બરાબર install થયું છે કે નહિ તો આ રીતે કરી શકાય: + +``` +import transformers +``` + +
+A gif showing the result of the two commands above: installation and import +
+ +આ આગળ નો command અમુક જ પેકેજ install કરશે. એ મશીનલનિંગ ના ફ્રેમવર્ક જેમકે (Tensorflow અને Pytorch) ઇન્સ્ટોલ નઈ કરે. આ course માં આપણે ઘણાં ફિચર્સ જોઈશું એટલે હું development વર્સન install કરવાની સલાહ આપીશ, કે જેમાં બધા પેકેજ અને જરૂરી લાઇબ્રેરી install એક સાથે આવશે: + +``` +!pip install transformers[sentencepiece] +``` + +આ સ્ટેપ run થતાં થોડો ટાઈમ લાગશે, પણ એનાથી આગળ ના પ્રકરણ માં સારું પડશે! + +## Python Virtual Environment ની મદદ થી + +જો તમને python virtual environment અનુકૂળ આવતું હોય તો પેહલું તબકું એ તમારા system માં python install છે. અમે આ [guide](https://realpython.com/installing-python/) અનુસરવાનું કહીશું. + +એકવાર python install થઈ જાય એટલે તમારા system ના terminal માં python command run કરી શકવા જોઈએ. જો તને તપાસવા માંગતા હોવ તો આ રન કરી શકો. આ command python નું version આપશે. + +જ્યારે તમે python command run કરો, જેમકે `python --version`, ત્યારે એ તમારી કમ્પ્યુટર સિસ્ટમ ના મુખ્ય python environment માં run થશે. અમે મુખ્ય python environment માં કઈ પણ install ન કરવાનો સુજાવ કરીએ છીએ. દરેક application માટે, અલગ environment વાપરવું, એમ કરવાથી દરેક application ને પેકેજ અને dependency અલગ અલગ રહેશે. આમ કરવાથી બીજી application સાથે compatibility ની સમસ્યા નઈ આવે. + +Python માં આ બધું [*virtual environments*](https://docs.python.org/3/tutorial/venv.html) થી થાય છે, આ virtual environment તમને અલગ ફોલ્ડર બનાઈ આપશે જેવા જરૂરી python version સાથે packages હશે જે તમને application માટે જરૂરી હશે. આ રીતનું virtual environment ઘણી રીતે બનાવી શકાય. આપણે python નું official tool [`venv`](https://docs.python.org/3/library/venv.html#module-venv) વાપરીશું. + +સૌથી પેહલા એક ફોલ્ડર બનાવો કે જેમાં તમારી application નો code રેહશે.દાખલા તરીકે, તમે તમરી હોમ ફોલ્ડર માં *transformers-course* નામનું ફોલ્ડર બનાવો છો: + +``` +mkdir ~/transformers-course +cd ~/transformers-course +``` + +એ ફોલ્ડર માં, python ના `venv` મોડ્યુલ ની મદદ થી virtual environment બનાવો: + +``` +python -m venv .env +``` + +તમને પેહલા ના ખાલી ફોલ્ડર માં *.env* નામનું ફોલ્ડર દેખાશે: + +``` +ls -a +``` + +```out +. .. .env +``` + +તમેં તમારા virtual environment ને ઉસ કરવા `activate` અને `deactivate` ના વાપરવું હોય તો સ્ક્રિપ્ટ વાપરી શકો: + +``` +# Activate the virtual environment +source .env/bin/activate + +# Deactivate the virtual environment +source .env/bin/deactivate +``` + +જો તમે verify કરવા માંગતા હોવ તો `which python` command run કરો. એ તમરા virtual environment ના ફોલ્ડર ને આઉટપુટ માં આપશે. આ એવું સાબિત કરે છે કે virtual environment સફળાપૂર્વક active છે.! + +``` +which python +``` + +```out +/home//transformers-course/.env/bin/python +``` + +### Installing dependencies + +જેમ આપણે પેહલા ના colab વાળા સેકશન માં કરેલું એમ, આપણે પેકેજ ઇન્સ્ટોલ કરીશું. આપણે `pip` પેકેજ મેનેજર ની મદદ થી 🤗 `transformers` નું ડેવલપમેન્ટ વર્સન ઇન્સ્ટોલ કરીશું: + +``` +pip install "transformers[sentencepiece]" +``` + +હવે તમારું સિસ્ટમ સેટઅપ થઈ ગયું છે અને તમે આગળ વધવા માટે સક્ષમ છો! \ No newline at end of file diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index 9d7266369..425eb7d94 100644 --- a/chapters/pt/_toctree.yml +++ b/chapters/pt/_toctree.yml @@ -11,6 +11,22 @@ title: Processamento de Linguagem Natural - local: chapter1/3 title: Transformers, o que eles podem fazer? + - local: chapter1/4 + title: Como os Transformers trabalham? + - local: chapter1/5 + title: Modelos decodificadores + - local: chapter1/6 + title: Modelos codificadores + - local: chapter1/7 + title: Modelos sequência a sequência + - local: chapter1/8 + title: Vieses e limitações + - local: chapter1/9 + title: Resumo + - local: chapter1/10 + title: Questionário de fim de capítulo + quiz: 1 + - title: 2. Usando 🤗 Transformers sections: @@ -72,6 +88,11 @@ sections: - local: chapter6/1 title: Introdução + - local: chapter6/2 + title: Treinando um novo tokenizador + - local: chapter6/3 + title: Os poderes especiais dos tokenizadores rápidos + - title: 7. Principais tarefas NLP sections: diff --git a/chapters/pt/chapter1/10.mdx b/chapters/pt/chapter1/10.mdx new file mode 100644 index 000000000..e1a79c2ce --- /dev/null +++ b/chapters/pt/chapter1/10.mdx @@ -0,0 +1,252 @@ + + +# Questionário de fim de capítulo + +Este capítulo cobriu muito terreno! Não se preocupe se você não entendeu todos os detalhes; os próximos capítulos o ajudarão a entender como as coisas funcionam debaixo do capô. + +Primeiro, porém, vamos testar o que você aprendeu neste capítulo! + +### 1. Explore o Hub e olhe para o checkpoint `roberta-large-mnli` . Que tarefa ele executa? + +roberta-large-mnli." + }, + { + text: "Classificação de texto", + explain: "Mais precisamente, ele classifica se duas ou mais sentenças estão logicamente conectadas entre três rótulos (contradição, neutro, vinculação) — uma tarefa também chamada de inferência de linguagem natural.", + correct: true + }, + { + text: "Geração de texto", + explain: "Olhe novamente na página roberta-large-mnli." + } + ]} +/> + +### 2. O que o código a seguir retornará? + +```py +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + + + +### 3. O que deverá substituir ... nesse trecho de código? + +```py +from transformers import pipeline + +filler = pipeline("fill-mask", model="bert-base-cased") +result = filler("...") +``` + + + está esperando por você.", + explain: "Isso está incorreto. Confira o cartão modelo `bert-base-cased` e tente identificar seu erro." + }, + { + text: "Esta [MASK] está esperando por você.", + explain: "Correto! O token de máscara deste modelo é [MASK]", + correct: true + }, + { + text: "Este homem está esperando por você.", + explain: "Isso está incorreto. Esse pipeline preenche palavras mascaradas, portanto, precisa de um token de máscara em algum lugar." + } + ]} +/> + +### 4. Por que esse código irá dar erro? + +```py +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +result = classifier("This is a course about the Transformers library") +``` + + + + +### 5. O que "transfer learning" significa? + + + +### 6. Verdadeiro ou Falso? Um modelo de linguagem geralmente não precisa de rótulos para seu pré-treino. + + + +### 7. Selecione a sentença que melhor descreve os termos "modelo", "arquitetura" e "pesos". + + + +### 8. Quais desses tipos de modelos você usaria para completar comandos com textos gerados? + + + +### 9. Quais desses tipos de modelos você usaria para resumir textos? + + + +### 10. Quais desses tipos de modelos você usaria para classificar entradas de texto de acordo com determinados rótulos? + + + +### 11. Que possível fonte o viés observado em um modelo pode ter? + + diff --git a/chapters/pt/chapter1/4.mdx b/chapters/pt/chapter1/4.mdx new file mode 100644 index 000000000..f9b9e1ce8 --- /dev/null +++ b/chapters/pt/chapter1/4.mdx @@ -0,0 +1,171 @@ +# Como os Transformers trabalham? + +Nessa seção, nós olharemos para o alto nível de arquitetura dos modelos Transformers. + +## Um pouco da história dos Transformers + +Aqui alguns pontos de referência na (pequena) história dos modelos Transformers: + +
+A brief chronology of Transformers models. + +
+ +A [arquitetura Transformer](https://arxiv.org/abs/1706.03762) foi introduzida em Junho de 2017. O foco de pesquisa original foi para tarefas de tradução. Isso foi seguido pela introdução de muitos modelos influentes, incluindo: + +- **Junho de 2018**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), o primeiro modelo Transformer pré-treinado, usado para ajuste-fino em várias tarefas de NLP e obtendo resultados estado-da-arte + +- **Outubro de 2018**: [BERT](https://arxiv.org/abs/1810.04805), outro grande modelo pré-treinado, esse outro foi designado para produzir melhores resumos de sentenças(mais sobre isso no próximo capítulo!) + +- **Fevereiro de 2019**: [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), uma melhor (e maior) versão da GPT que não foi imediatamente publicizado o seu lançamento devido a preocupações éticas [N.T.: não apenas por isso] + +- **Outubro de 2019**: [DistilBERT](https://arxiv.org/abs/1910.01108), uma versão destilada do BERT que é 60% mais rápidam 40% mais leve em memória, e ainda retém 97% da performance do BERT + +- **Outubro de 2019**: [BART](https://arxiv.org/abs/1910.13461) e [T5](https://arxiv.org/abs/1910.10683), dois grandes modelos pré-treinados usando a mesma arquitetura do modelo original Transformer (os primeiros a fazerem até então) + +- **Maio de 2020**, [GPT-3](https://arxiv.org/abs/2005.14165), uma versão ainda maior da GPT-2 que é capaz de performar bem em uma variedade de tarefas sem a necessidade de ajuste-fino (chamado de aprendizagem_zero-shot_) + +Esta lista está longe de ser abrangente e destina-se apenas a destacar alguns dos diferentes tipos de modelos de Transformers. Em linhas gerais, eles podem ser agrupados em três categorias: + +- GPT-like (também chamados de modelos Transformers _auto-regressivos_) +- BERT-like (também chamados de modelos Transformers _auto-codificadores_) +- BART/T5-like (também chamados de modelos Transformers _sequence-to-sequence_) + +Vamos mergulhar nessas famílias com mais profundidade mais adiante + +## Transformers são modelos de linguagem + +Todos os modelos de Transformer mencionados acima (GPT, BERT, BART, T5, etc.) foram treinados como *modelos de linguagem*. Isso significa que eles foram treinados em grandes quantidades de texto bruto de forma auto-supervisionada. O aprendizado autossupervisionado é um tipo de treinamento no qual o objetivo é calculado automaticamente a partir das entradas do modelo. Isso significa que os humanos não são necessários para rotular os dados! + +Este tipo de modelo desenvolve uma compreensão estatística da linguagem em que foi treinado, mas não é muito útil para tarefas práticas específicas. Por causa disso, o modelo geral pré-treinado passa por um processo chamado *aprendizagem de transferência*. Durante esse processo, o modelo é ajustado de maneira supervisionada - ou seja, usando rótulos anotados por humanos - em uma determinada tarefa. + +Um exemplo de tarefa é prever a próxima palavra em uma frase depois de ler as *n* palavras anteriores. Isso é chamado de *modelagem de linguagem causal* porque a saída depende das entradas passadas e presentes, mas não das futuras. + +
+Example of causal language modeling in which the next word from a sentence is predicted. + +
+ +Outro exemplo é a *modelagem de linguagem mascarada*, na qual o modelo prevê uma palavra mascarada na frase. + +
+Example of masked language modeling in which a masked word from a sentence is predicted. + +
+ +## Transformers são modelos grandes + +Além de alguns outliers (como o DistilBERT), a estratégia geral para obter melhor desempenho é aumentar os tamanhos dos modelos, bem como a quantidade de dados em que são pré-treinados. + +
+Number of parameters of recent Transformers models +
+ +Infelizmente, treinar um modelo, especialmente um grande, requer uma grande quantidade de dados. Isso se torna muito caro em termos de tempo e recursos de computação. Até se traduz em impacto ambiental, como pode ser visto no gráfico a seguir. + +
+The carbon footprint of a large language model. + +
+ + + +E isso mostra um projeto para um modelo (muito grande) liderado por uma equipe que tenta conscientemente reduzir o impacto ambiental do pré-treinamento. Os gastos de executar muitos testes para obter os melhores hiperparâmetros seria ainda maior. + +Imagine se cada vez que uma equipe de pesquisa, uma organização estudantil ou uma empresa quisesse treinar um modelo, o fizesse do zero. Isso levaria a custos globais enormes e desnecessários! + +É por isso que compartilhar modelos de linguagem é fundamental: compartilhar os pesos treinados e construir em cima dos pesos já treinados reduz o custo geral de computação e os gastos de carbono da comunidade. + + +## Transferência de Aprendizagem + + + +*Pré-treinamento* é o ato de treinar um modelo do zero: os pesos são inicializados aleatoriamente e o treinamento começa sem nenhum conhecimento prévio. + +
+The pretraining of a language model is costly in both time and money. + +
+ +Esse pré-treinamento geralmente é feito em grandes quantidades de dados. Portanto, requer um corpus de dados muito grande e o treinamento pode levar várias semanas. + +*Ajuste fino*, por outro lado, é o treinamento feito **após** um modelo ter sido pré-treinado. Para realizar o ajuste fino, primeiro você adquire um modelo de linguagem pré-treinado e, em seguida, realiza treinamento adicional com um conjunto de dados específico para sua tarefa. Espere - por que não simplesmente treinar diretamente para a tarefa final? Existem algumas razões: + +* O modelo pré-treinado já foi treinado em um conjunto de dados que possui algumas semelhanças com o conjunto de dados de ajuste fino. O processo de ajuste fino é, portanto, capaz de aproveitar o conhecimento adquirido pelo modelo inicial durante o pré-treinamento (por exemplo, com problemas de NLP, o modelo pré-treinado terá algum tipo de compreensão estatística da linguagem que você está usando para sua tarefa). +* Como o modelo pré-treinado já foi treinado com muitos dados, o ajuste fino requer muito menos dados para obter resultados decentes. +* Pela mesma razão, a quantidade de tempo e recursos necessários para obter bons resultados são muito menores. + +Por exemplo, pode-se alavancar um modelo pré-treinado treinado no idioma inglês e depois ajustá-lo em um corpus arXiv, resultando em um modelo baseado em ciência/pesquisa. O ajuste fino exigirá apenas uma quantidade limitada de dados: o conhecimento que o modelo pré-treinado adquiriu é "transferido", daí o termo *aprendizagem de transferência*. + +
+The fine-tuning of a language model is cheaper than pretraining in both time and money. + +
+ +O ajuste fino de um modelo, portanto, tem menores custos de tempo, dados, financeiros e ambientais. Também é mais rápido e fácil iterar em diferentes esquemas de ajuste fino, pois o treinamento é menos restritivo do que um pré-treinamento completo. + +Esse processo também alcançará melhores resultados do que treinar do zero (a menos que você tenha muitos dados), e é por isso que você deve sempre tentar alavancar um modelo pré-treinado - um o mais próximo possível da tarefa que você tem em mãos - e então fazer seu ajuste fino. + +## Arquitetura geral + +Nesta seção, veremos a arquitetura geral do modelo Transformer. Não se preocupe se você não entender alguns dos conceitos; há seções detalhadas posteriormente cobrindo cada um dos componentes. + + + +## Introdução + +O modelo é principalmente composto por dois blocos: + +* **Codificador (esquerda)**: O codificador recebe uma entrada e constrói uma representação dela (seus recursos). Isso significa que o modelo é otimizado para adquirir entendimento da entrada. +* **Decodificador (à direita)**: O decodificador usa a representação do codificador (recursos) junto com outras entradas para gerar uma sequência de destino. Isso significa que o modelo é otimizado para gerar saídas. + +
+Architecture of a Transformers models + +
+ +Cada uma dessas partes pode ser usada de forma independente, dependendo da tarefa: + +* **Modelos somente de codificador**: bom para tarefas que exigem compreensão da entrada, como classificação de sentença e reconhecimento de entidade nomeada. +* **Modelos somente decodificadores**: bom para tarefas generativas, como geração de texto. +* **Modelos de codificador-decodificador** ou **modelos de sequência a sequência**: bom para tarefas generativas que exigem uma entrada, como tradução ou resumo. (corrigit sequence to sequence) + +Vamos mergulhar nessas arquiteturas de forma independente em seções posteriores. + +## Camadas de Atenção + +Uma característica chave dos modelos Transformer é que eles são construídos com camadas especiais chamadas *camadas de atenção*. Na verdade, o título do artigo que apresenta a arquitetura do Transformer era ["Atenção é tudo que você precisa"](https://arxiv.org/abs/1706.03762)! Exploraremos os detalhes das camadas de atenção posteriormente no curso; por enquanto, tudo o que você precisa saber é que essa camada dirá ao modelo para prestar atenção específica a certas palavras na frase que você passou (e mais ou menos ignorar as outras) ao lidar com a representação de cada palavra. + +Para contextualizar, considere a tarefa de traduzir o texto do português para o francês. Dada a entrada "Você gosta deste curso", um modelo de tradução precisará atender também à palavra adjacente "Você" para obter a tradução adequada para a palavra "gosta", pois em francês o verbo "gostar" é conjugado de forma diferente dependendo o sujeito. O resto da frase, no entanto, não é útil para a tradução dessa palavra. Na mesma linha, ao traduzir "deste" o modelo também precisará prestar atenção à palavra "curso", pois "deste" traduz-se de forma diferente dependendo se o substantivo associado é masculino ou feminino. Novamente, as outras palavras na frase não importarão para a tradução de "deste". Com frases mais complexas (e regras gramaticais mais complexas), o modelo precisaria prestar atenção especial às palavras que podem aparecer mais distantes na frase para traduzir adequadamente cada palavra. + +O mesmo conceito se aplica a qualquer tarefa associada à linguagem natural: uma palavra por si só tem um significado, mas esse significado é profundamente afetado pelo contexto, que pode ser qualquer outra palavra (ou palavras) antes ou depois da palavra que está sendo estudada. + +Agora que você tem uma ideia do que são as camadas de atenção, vamos dar uma olhada mais de perto na arquitetura do Transformer. + +## A arquitetura original + +A arquitetura Transformer foi originalmente projetada para tradução. Durante o treinamento, o codificador recebe entradas (frases) em um determinado idioma, enquanto o decodificador recebe as mesmas frases no idioma de destino desejado. No codificador, as camadas de atenção podem usar todas as palavras em uma frase (já que, como acabamos de ver, a tradução de uma determinada palavra pode ser dependente do que está depois e antes dela na frase). O decodificador, no entanto, funciona sequencialmente e só pode prestar atenção nas palavras da frase que ele já traduziu (portanto, apenas as palavras anteriores à palavra que está sendo gerada no momento). Por exemplo, quando previmos as três primeiras palavras do alvo traduzido, as entregamos ao decodificador que então usa todas as entradas do codificador para tentar prever a quarta palavra. + +Para acelerar as coisas durante o treinamento (quando o modelo tem acesso às frases alvo), o decodificador é alimentado com todo o alvo, mas não é permitido usar palavras futuras (se teve acesso à palavra na posição 2 ao tentar prever a palavra na posição 2, o problema não seria muito difícil!). Por exemplo, ao tentar prever a quarta palavra, a camada de atenção só terá acesso às palavras nas posições 1 a 3. + +A arquitetura original do Transformer ficou assim, com o codificador à esquerda e o decodificador à direita: + +
+Architecture of a Transformers models + +
+ +Observe que a primeira camada de atenção em um bloco decodificador presta atenção a todas as entradas (passadas) do decodificador, mas a segunda camada de atenção usa a saída do codificador. Ele pode, assim, acessar toda a frase de entrada para melhor prever a palavra atual. Isso é muito útil, pois diferentes idiomas podem ter regras gramaticais que colocam as palavras em ordens diferentes, ou algum contexto fornecido posteriormente na frase pode ser útil para determinar a melhor tradução de uma determinada palavra. + +A *máscara de atenção* também pode ser usada no codificador/decodificador para evitar que o modelo preste atenção a algumas palavras especiais - por exemplo, a palavra de preenchimento especial usada para fazer com que todas as entradas tenham o mesmo comprimento ao agrupar frases. + +## Arquiteturas vs. checkpoints + +À medida que nos aprofundarmos nos modelos do Transformer neste curso, você verá menções a *arquiteturas* e *checkpoints*, bem como *modelos*. Todos esses termos têm significados ligeiramente diferentes: + +* **Arquitetura**: Este é o esqueleto do modelo -- a definição de cada camada e cada operação que acontece dentro do modelo. +* **Checkpoints**: Esses são os pesos que serão carregados em uma determinada arquitetura. +* **Modelos**: Este é um termo abrangente que não é tão preciso quanto "arquitetura" ou "checkpoint": pode significar ambos. Este curso especificará *arquitetura* ou *checkpoint* quando for necessário reduzir a ambiguidade. + +Por exemplo, BERT é uma arquitetura enquanto `bert-base-cased`, um conjunto de pesos treinados pela equipe do Google para a primeira versão do BERT, é um checkpoint. No entanto, pode-se dizer "o modelo BERT" e "o modelo `bert-base-cased`". diff --git a/chapters/pt/chapter1/5.mdx b/chapters/pt/chapter1/5.mdx new file mode 100644 index 000000000..4ab25d749 --- /dev/null +++ b/chapters/pt/chapter1/5.mdx @@ -0,0 +1,17 @@ +# Modelos decodificadores + + + +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*. + +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. + +Os modelos de codificador são mais adequados para tarefas que exigem uma compreensão da sentença completa, como classificação de sentença, reconhecimento de entidade nomeada (e, mais geralmente, classificação de palavras) e resposta extrativa de perguntas. + +Os representantes desta família de modelos incluem: + +- [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) diff --git a/chapters/pt/chapter1/6.mdx b/chapters/pt/chapter1/6.mdx new file mode 100644 index 000000000..0d6e1cb15 --- /dev/null +++ b/chapters/pt/chapter1/6.mdx @@ -0,0 +1,14 @@ +# Modelos decodificadores + +Os modelos de decodificador usam apenas o decodificador de um modelo Transformer. Em cada etapa, para uma determinada palavra, as camadas de atenção só podem acessar as palavras posicionadas antes dela na frase. Esses modelos geralmente são chamados de _modelos auto-regressivos_. + +O pré-treinamento de modelos de decodificadores geralmente gira em torno de prever a próxima palavra na frase. + +Esses modelos são mais adequados para tarefas que envolvem geração de texto. + +Os representantes desta família de modelos incluem: + +- [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) +- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) +- [GPT-2](https://huggingface.co/transformers/model_doc/gpt2.html) +- [Transformer XL](https://huggingface.co/transformers/model_doc/transfo-xl.html) diff --git a/chapters/pt/chapter1/7.mdx b/chapters/pt/chapter1/7.mdx new file mode 100644 index 000000000..695359a16 --- /dev/null +++ b/chapters/pt/chapter1/7.mdx @@ -0,0 +1,14 @@ +# Modelos sequência a sequência + +Modelos encoder-decoder (também chamados de modelos _sequence-to-sequence_) usam ambas as partes da arquitetura Transformer. Em cada estágio, as camadas de atenção do codificador podem acessar todas as palavras da frase inicial, enquanto as camadas de atenção do decodificador podem acessar apenas as palavras posicionadas antes de uma determinada palavra na entrada. + +O pré-treinamento desses modelos pode ser feito usando os objetivos dos modelos de codificador ou decodificador, mas geralmente envolve algo um pouco mais complexo. Por exemplo, [T5](https://huggingface.co/t5-base) é pré-treinado substituindo trechos aleatórios de texto (que podem conter várias palavras) por uma única palavra especial de máscara, e o objetivo é prever o texto que esta palavra de máscara substitui. + +Os modelos de sequência a sequência são mais adequados para tarefas que envolvem a geração de novas frases dependendo de uma determinada entrada, como resumo, tradução ou resposta a perguntas generativas. + +Os representantes desta família de modelos incluem: + +- [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) diff --git a/chapters/pt/chapter1/8.mdx b/chapters/pt/chapter1/8.mdx new file mode 100644 index 000000000..e01fd445a --- /dev/null +++ b/chapters/pt/chapter1/8.mdx @@ -0,0 +1,24 @@ +# Vieses e limitações + + + +Se sua intenção é usar um modelo pré-treinado ou uma versão ajustada em produção, esteja ciente de que, embora esses modelos sejam ferramentas poderosas, eles vêm com limitações. A maior delas é que, para possibilitar o pré-treinamento em grandes quantidades de dados, os pesquisadores muitas vezes raspam todo o conteúdo que encontram, tirando o melhor e o pior do que está disponível na internet. + +Para dar uma ilustração rápida, vamos voltar ao exemplo de um pipeline `fill-mask` com o modelo BERT: +```py +from transformers import pipeline + +unmasker = pipeline("fill-mask", model="bert-base-uncased") +result = unmasker("This man works as a [MASK].") +print([r["token_str"] for r in result]) + +result = unmasker("This woman works as a [MASK].") +print([r["token_str"] for r in result]) + +["lawyer", "carpenter", "doctor", "waiter", "mechanic"] +["nurse", "waitress", "teacher", "maid", "prostitute"] +``` + +Quando solicitado a preencher a palavra que falta nessas duas frases, o modelo dá apenas uma resposta livre de gênero (garçom/garçonete). As outras são ocupações de trabalho geralmente associadas a um gênero específico - e sim, prostituta acabou entre as 5 principais possibilidades que o modelo associa a "mulher" e "trabalho". Isso acontece mesmo que o BERT seja um dos raros modelos de Transformer não construídos por meio de coleta de dados de toda a Internet, mas usando dados aparentemente neutros (ele é treinado com datasets da [Wikipedia em inglês](https://huggingface.co/datasets/wikipedia ) e [BookCorpus](https://huggingface.co/datasets/bookcorpus)). + +Quando você usa essas ferramentas, você precisa ter em mente que o modelo original que você está usando pode facilmente gerar conteúdo sexista, racista ou homofóbico. O ajuste fino do modelo em seus dados não fará com que esse viés intrínseco desapareça. diff --git a/chapters/pt/chapter1/9.mdx b/chapters/pt/chapter1/9.mdx new file mode 100644 index 000000000..7398a7fc6 --- /dev/null +++ b/chapters/pt/chapter1/9.mdx @@ -0,0 +1,11 @@ +# Resumo + +Nesse capítulo, você viu como abordar diferentes tarefas de NLP usando a função de alto nível `pipeline()` da biblioteca 🤗 Transformers. Você também viu como pesquisar e usar modelos no Hub, bem como usar a API de inferência para testar os modelos diretamente em seu navegador. + +Discutimos como os modelos Transformers funcionam em alto nível e falamos sobre a importância do aprendizado de transferência (transfer learning) e do ajuste fino. Um aspecto chave é que você pode usar a arquitetura completa ou apenas o codificador ou decodificador, dependendo do tipo de tarefa que você pretende resolver. A tabela a seguir resume isso: + +| Modelo | Exemplos | Tarefas | +|-----------------|--------------------------------------------|----------------------------------------------------------------------------------| +| Encoder | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa | Classificação de sentenças, reconhecimento de entidades nomeadas, Q&A | +| Decoder | CTRL, GPT, GPT-2, Transformer XL | Geração de texto | +| Encoder-decoder | BART, T5, Marian, mBART | Sumarização, tradução, perguntas e respostas gerativas | diff --git a/chapters/pt/chapter6/2.mdx b/chapters/pt/chapter6/2.mdx new file mode 100644 index 000000000..049ca184d --- /dev/null +++ b/chapters/pt/chapter6/2.mdx @@ -0,0 +1,252 @@ +# Treinando um novo tokenizador + + + +Se um modelo de linguagem não estiver disponível no idioma que você estiver interessado, ou se o seu corpus for muito diferente do que o seu modelo de linguagem foi treinado, você muito provavelmente desejará retreinar o modelo do zero usando um tokenizador adaptado para seus dados. Isto exigirá um treinamento de um novo tokenizador para seu conjunto de dados. Mas o que isso exatamente significa? Quando observamos os tokenizadores pela primeira vez no [Capítulo 2](/course/chapter2), nós vimos que a maioria dos modelos Transformer usa um algoritmo de tokenização de subpalavras. Para identificar quais subpalavras são de interesse e que ocorrem mais frequentemente no corpus em questão, o tokenizador precisa dar uma boa olhada em todos os textos no corpus -- processo que chamamos de *treinamento*. As regras exatas que governam o treinamento dependem do tipo de tokenizador usado, e veremos os três algoritmos principais mais adiante neste capítulo. + + + + + +⚠️ Treinar um tokenizador não é o mesmo que treinar um modelo! O treinamento de um modelo usa o gradiente descendente estocástico para fazer a perda um pouquinho menor a cada batch. Portanto, é aleatório por natureza (o que significa que você deve definir seeds para obter o mesmo resultado quando estiver fazendo o mesmo treino novamente). Treinar um tokenizador é um processo estatístico que tenta identificar que subpalavras são as melhores para escolher dependendo do algoritmo de tokenização. Portanto, este processo é determinístico, o que significa que você terá sempre o mesmo resultado quando for treinar com o mesmo algoritmo no mesmo corpus. + + + +## Montando um corpus + +Existe uma API muito simples em 🤗 Transformers que você pode usar para treinar um novo tokenizador com as mesmas características de um já existente: `AutoTokenizer.train_new_from_iterator()`. Para ver isso em ação, vamos supor que queremos treinar o GPT-2 do zero, mas em um idioma diferente do inglês. Nossa primeira tarefa será obter muitos dados de um idioma em um corpus de treinamento. Para prover exemplos que todo mundo será capaz de entender, não usaremos um idioma como russo ou chinês aqui, mas sim in idiome inglês especializado: código Python. + +A biblioteca [🤗 Datasets](https://github.com/huggingface/datasets) pode nos ajudar a montar um corpus de códigos em Python. Nós usaremos a função usual `load_dataset()` para baixar e armazenar em cache o dataset [CodeSearchNet](https://huggingface.co/datasets/code_search_net). Este dataset foi criado para o [CodeSearchNet challenge](https://wandb.ai/github/CodeSearchNet/benchmark) e contém milhões de funções de bibliotecas de código aberto no GitHub em diferentes linguagens de programação. Aqui, nós iremos carregar a parte Python deste dataset: + +```py +from datasets import load_dataset + +# Isto pode levar alguns minutos para carregar, então pegue um copo de café enquanto espera! +raw_datasets = load_dataset("code_search_net", "python") +``` + +Podemos dar uma olhada na divisão de treinamento para ver a quais colunas temos acesso: + +```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 o conjunto de dados separa as docstrings do código e sugere uma tokenização de ambos. Aqui, usaremos apenas a coluna `whole_func_string` para treinar o nosso tokenizador. Podemos observar um exemplo de uma dessas funções indexando na divisão `train`: + +```py +print(raw_datasets["train"][123456]["whole_func_string"]) +``` + +Que deve resultar na seguinte saída: + +```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) +``` + +A primeira coisa que precisamos fazer é transformar o dataset em um iterador) de listas de textos -- por exemplo, uma lista de lista de textos. Usando lista de textos irá habilitar o nosso tokenizador para funcionar mais rapidamente (treinando em lotes de textos ao invés de processar individualmente os textos, um por vez), e deve ser um iterador se quisermos evitar ter tudo na memória de uma vez. Se o teu corpus for grande, você vai querer aproveitar o fato de que 🤗 Datasets não carrega tudo na memória RAM, mas armazena os elementos do dataset no disco. + +Executar o trecho abaixo criaria uma lista de listas de 1000 textos cada, mas carregaria tudo na memória: + +```py +# Não remova o comentário da linha abaixo, a menos que o teu dataset seja pequeno! +# training_corpus = [raw_datasets["train"][i: i + 1000]["whole_func_string"] for i in range(0, len(raw_datasets["train"]), 1000)] +``` + +Usando um Python generator, nós podemos evitar que o Python carregue tudo na memória até que realmente seja necessário. Para criar tal generator, você precisa apenas substituir os colchetes por parênteses: + +```py +training_corpus = ( + raw_datasets["train"][i : i + 1000]["whole_func_string"] + for i in range(0, len(raw_datasets["train"]), 1000) +) +``` + +Esta linha de código não busca nenhum elemento no dataset; ele apenas cria um objeto que você pode usar em um o loop `for` do Python. Os textos só serão carregados quando você precisar deles (ou seja, quando você estiver na etapa do loop `for` que os requer), e apenas 1000 textos por vez serão carregados. Desse modo, você não esgotará toda a sua memória, mesmo se você estiver processando um grande dataset. + +O problema com um objeto gerador é que ele só pode ser usado uma vez. Então, em vez de nos dar a lista dos primeiros 10 dígitos duas vezes: + +```py +gen = (i for i in range(10)) +print(list(gen)) +print(list(gen)) +``` + +nós obtemos uma vez e, em seguida, uma lista vazia: + +```python out +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +[] +``` + +É por isso que definomos uma função que retorna um gerador: + +```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() +``` + +Você também pode definir o seu gerador dentro de um loop `for` ao usar o 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"] +``` + +que irá produzir exatamente o mesmo gerador de antes, mas permite que você use uma lógica mais complexa do que você pode em um list comprehension. + +## Treinando um novo tokenizador + +Agora que nós temos o nosso corpus na forma de um iterador de lotes de texto, estamos prontos para treinar um novo tokenizador. Para fazer isso, primeiramente nós precisamos carregar o tokenizador que queremos emparelhar com o nosso modelo (neste caso, GPT-2): + +```py +from transformers import AutoTokenizer + +old_tokenizer = AutoTokenizer.from_pretrained("gpt2") +``` +Por mais que iremos treinar um novo tokenizador, é uma boa ideia fazer isso para evitar começar do zero. Dessa forma, não precisaremos especificar nada sobre o algoritmo de tokenização ou tokens especiais que queremos usar; nosso novo tokenizador será exatamente igual ao GPT-2, e a única coisa que irá mudar é o vocabulário, que será determinado pelo treinamento em nosso corpus. + +Primeiramente, vamos dar uma olhada em como o tokenizador trataria um exemplo de função: + +```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'] +``` + +O tokenizador possui alguns símbolos especiais, como `Ġ` e `Ċ`, que denotam espaços e novas linhas, respectivamente. Como podemos observar, isso não é tão eficiente: o tokenizador retorna tokens individuais para cada espaço, quando poderia agrupar níveis de indentação (já que ter conjuntos de quatro ou oito espaços será muito comum no código). O tokenizador também dividiu o nome da função de uma forma um pouco estranha, não sendo usado para ver palavras com o caractere `_`. + +Vamos treinar um novo tokenizador e ver se isso resolve esses problemas. Para isso, iremos utilizar o método `train_new_from_iterator()`: + +```py +tokenizer = old_tokenizer.train_new_from_iterator(training_corpus, 52000) +``` + +Este comando pode demorar um pouco se o seu corpus for muito grande, mas para este dataset contendo 1.6 GB de textos é extremamente rápido (1 minuto e 16 segundos em uma CPU AMD Ryzen 9 3900X com 12 núcleos). + +Observe que `AutoTokenizer.train_new_from_iterator()` funciona apenas se o tokenizador que você estiver usando é um tokenizador "rápido". Como você verá na próxima seção, a biblioteca 🤗 Transformers contém dois tipos de tokenizers: alguns são escritos puramente em Python e outros (os mais rápidos) são apoiados pela biblioteca 🤗 Tokenizers, que é escrita na linguagem de programação [Rust](https://www.rust-lang.org). Python é a linguagem de programação mais utilizada para Ciência de Dados e aplicações em Deep Learning, mas quando algo precisa ser paralelizado para ser rápido, é preciso ser escrito em uma outra linguagem. Por exemplo, as multiplicações de matrizes que estão na base de modelos de computação são escritos em CUDA, uma biblioteca em C otimizada para GPUs. + +Treinar um tokenizador totalmente novo usando apenas Python seria terrivelmente lento, e é por isso que nós desenvolvemos a biblioteca 🤗 Tokenizers. Observe que, assim como você não precisou aprender a linguagem CUDA para ser capaz de executar seu modelo em um lote de entradas em uma GPU, você não precisará aprender Rust para usar o tokenizador rápido. A biblioteca 🤗 Tokenizers fornece ligaçções para muitos métodos que internamente chamam algum trecho de código em Rust; por exemplo, para paralelizar o treinamento do seu novo tokenizador ou, como vimos no [Chapter 3](/course/chapter3), a tokenização de um lote de entradas. + +A maioria dos modelos Transformer possui um tokenizador rápido disponível (existem algumas exceções que você pode checar [aqui](https://huggingface.co/transformers/#supported-frameworks)), e a API `AutoTokenizer` sempre seleciona o tokenizador rápido para você se estiver disponível. Na próxima seção, veremos alguns dos outros recursos especiais que os tokenizers rápidos possuem, que serão realmente úteis para tarefas como classificação de tokens e resposta a perguntas. Antes de aprofundarmos nisso, no entanto, vamos experimentar o nosso novo tokenizador no exemplo anterior: + +```py +tokens = tokenizer.tokenize(example) +tokens +``` + +```python out +['def', 'Ġadd', '_', 'numbers', '(', 'a', ',', 'Ġb', '):', 'ĊĠĠĠ', 'Ġ"""', 'Add', 'Ġthe', 'Ġtwo', 'Ġnumbers', 'Ġ`', + 'a', '`', 'Ġand', 'Ġ`', 'b', '`."""', 'ĊĠĠĠ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb'] +``` + +Aqui vemos novamente os símbolos especiais `Ġ` and `Ċ` que denotam espaços e novas linhas, mas também podemos observar que o nosso tokenizador aprendeu alguns tokens que são altamente específicos em um corpus de funções em Python: por exemplo, existe um token `ĊĠĠĠ` que representa uma indentação, e um token `Ġ"""` que representa as três aspas que começam uma docstring. O tokenizador também divide corretamente o nome da função em `_`. Esta é uma representação bastante compacta; comparativamente, usando o tokenizador em inglês no mesmo exemplo nos dará uma frase mais longa: + +```py +print(len(tokens)) +print(len(old_tokenizer.tokenize(example))) +``` + +```python out +27 +36 +``` +Vejamos outro exemplo: + +```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', 'ĊĠĠĠĠ'] +``` + +Além do token correpondente a uma indentação, aqui podemos ver um token para uma indentação dupla: `ĊĠĠĠĠĠĠĠ`. Palavras especiais em Python, como `class`, `init`, `call`, `self`, e `return` são tokenizadas como um token, e podemos ver que além de dividir em `_` e `.`, o tokenizador divide corretamente até mesmo nomes em CamelCase: `LinearLayer` é tokenizado como `["ĠLinear", "Layer"]` + +## Salvando o tokenizador + +Para garantir que podemos usá-lo mais tarde, precisamos salvar nosso novo tokenizador. Assim como é utilizado para modelos, isso é feito com o método `save_pretrained()`: + +```py +tokenizer.save_pretrained("code-search-net-tokenizer") +``` +Isso irá criar uma nova pasta chamada *code-search-net-tokenizer*, que irá conter todos os arquivos que o tokenizador precisa para ser recarregado. Se você quiser compartilhar este tokenizador com outros colegas e amigos, você pode carregá-lo no Hub fazendo login em sua conta. Se você estiver trabalhando em um notebook, há uma função conveniente para ajudá-lo com isso: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` +Isso exibirá um widget onde você pode inserir suas credenciais de login do Hugging Face. Se você não estiver trabalhando em um notebook, basta digitar a seguinte linha em seu terminal: + +```bash +huggingface-cli login +``` + +Depois de você logar, você pode enviar seu tokenizador executando o seguinte comando: + +```py +tokenizer.push_to_hub("code-search-net-tokenizer") +``` + +Isso criará um novo repositório em seu namespace com o nome `code-search-net-tokenizer`, contendo o arquivo do tokenizador. Você pode então carregar o tokenizador de qualquer lugar com o método `from_pretrained()`: + +```py +# Substitua "huggingface-course" abaixo pelo seu namespace real para usar seu próprio tokenizador +tokenizer = AutoTokenizer.from_pretrained("huggingface-course/code-search-net-tokenizer") +``` + +Agora você está pronto para treinar um modelo de linguagem do zero e ajustá-lo para sua tarefa! Chegaremos a isso no [Chapter 7](/course/chapter7), mas primeiro, no resto do capítulo daremos uma olhada sobre tokenizers rápidos e explorar em detalhes o que realmente acontece quando chamamos o método `train_new_from_iterator()`. diff --git a/chapters/pt/chapter6/3.mdx b/chapters/pt/chapter6/3.mdx new file mode 100644 index 000000000..b79732841 --- /dev/null +++ b/chapters/pt/chapter6/3.mdx @@ -0,0 +1,471 @@ + + +# Os poderes especiais dos tokenizadores rápidos + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Nesta seção, examinaremos mais de perto os recursos dos tokenizadores em 🤗 Transformers. Até agora, só os usamos para tokenizar entradas ou decodificar IDs de volta em texto, mas tokenizadores - especialmente aqueles apoiados pela biblioteca 🤗 Tokenizers - podem fazer muito mais. Para ilustrar esses recursos adicionais, exploraremos como reproduzir os resultados dos pipelines `token-classification` (que chamamos de `ner`) e `question-answering` que encontramos pela primeira vez no [Capítulo 1](/course/chapter1). + + +Na discussão a seguir, muitas vezes faremos a distinção entre tokenizadores "lentos" e "rápidos". Tokenizadores lentos são aqueles escritos em Python dentro da biblioteca 🤗 Transformers, enquanto as versões rápidas são aquelas fornecidas por 🤗 Tokenizers, que são escritos em Rust. Se você se lembrar da tabela do [Capítulo 5](/course/chapter5/3) que informava quanto tempo levou um tokenizador rápido e um lento para tokenizar o conjunto de dados de revisão de medicamentos, você deve ter uma ideia do motivo pelo qual os chamamos de rápido e lento: + + | Fast tokenizer | Slow tokenizer +:--------------:|:--------------:|:-------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s + + + +⚠️ Ao tokenizar uma única frase, você nem sempre verá uma diferença de velocidade entre as versões lenta e rápida do mesmo tokenizador. Na verdade, a versão rápida pode ser mais lenta! É somente ao tokenizar muitos textos em paralelo ao mesmo tempo que você poderá ver a diferença com maior nitidez. + + +## Codificação em lote + + + +A saída de um tokenizador não é um simples dicionário em Python; o que obtemos é, na verdade, um objeto especial chamado `BatchEncoding`. Este objeto é uma subclasse de um dicionário (e é por isso que conseguimos indexar esse resultado sem nenhum problema antes), mas com métodos adicionais que são usados ​​principalmente por tokenizadores rápidos. + +Além de seus recursos de paralelização, uma funcionalidade importante dos tokenizadores rápidos é que eles sempre acompanham o intervalo original de textos dos quais os tokens finais vêm - um recurso que chamamos de *mapeamento de offset*. Isso, por sua vez, desbloqueia recursos como o mapeamento de cada palavra para os tokens gerados ou mapeamento de cada caractere do texto original para o token que está dentro e vice-versa. + +Vamos analisar um exemplo: + +```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 mencionado anteriormente, nós obtemos um objeto `BatchEncoding` na saída do tokenizador: + +```python out + +``` + +Como a classe `AutoTokenizer` escolhe o tokenizador rápido como padrão, podemos usar os métodos adicionais que o objeto `BatchEncoding` fornece. Temos duas formas de verificar se o nosso tokenizador é rápido ou lento. Podemos, por exemplo, avaliar o atributo `is_fast` do tokenizador: + +```python +tokenizer.is_fast +``` + +```python out +True +``` + +ou checar o mesmo atributo do nosso `encoding`: + +```python +encoding.is_fast +``` + +```python out +True +``` + +Vejamos o que um tokenizador rápido nos permite fazer. Primeiro, podemos acessar os tokens sem precisar converter os IDs de volta em tokens: + +```py +encoding.tokens() +``` + +```python out +['[CLS]', 'My', 'name', 'is', 'S', '##yl', '##va', '##in', 'and', 'I', 'work', 'at', 'Hu', '##gging', 'Face', 'in', + 'Brooklyn', '.', '[SEP]'] +``` + +No caso, o token no índice 5 é `##yl`, que faz parte da palavra "Sylvain" na sentença original. Nós podemos também usar o metodo `words_ids()` para obter o índice da palavra de onde cada palavra vem: + +```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 observar que as palavras especiais do tokenizador `[CLS]` e `[SEP]` são mapeados para `None`, e então cada token é mapeada para a palavra de onde se origina. Isso é especialmente útil para determinar se um token está no início da palavra ou se dois tokens estão em uma mesma palavra. Poderíamos contar com o prefix `##` para isso, mas apenas para tokenizadores do tipo BERT; este método funciona para qualquer tipo de tokenizador, desde que seja do tipo rápido. No próximo capítulo, nós veremos como podemos usar esse recurso para aplicar os rótulos que temos para cada palavra adequadamente aos tokens em tarefas como reconhecimento de entidade nomeada (em inglês, Named Entity Recognition, ou NER) e marcação de parte da fala (em inglês, part-of-speech, ou POS). Também podemos usá-lo para mascarar todos os tokens provenientes da mesma palavra na modelagem de linguagem mascarada (uma técnica chamada _mascaramento da palavra inteira_) + + + +A noção do que é uma palavra é complicada. Por exemplo, "d'água" (uma contração de "da água") conta como uma ou duas palavras? Na verdade, depende do tokenizador e da operação de pré-tokenização que é aplicada. Alguns tokenizadores apenas dividem em espaços, então eles considerarão isso como uma palavra. Outros usam pontuação em cima dos espaços, então considerarão duas palavras. + +✏️ **Experimente!** Crie um tokenizador a partir dos checkpoints de `bert-base-cased `e `roberta-base` e tokenize "81s" com eles. O que você observa? Quais são os IDs das palavras? + + + +Da mesma forma, existe um método `sentence_ids()` que podemos usar para mapear um token para a sentença de onde veio (embora, neste caso, o `token_type_ids` retornado pelo tokenizador possa nos dar a mesma informação). + +Por fim, podemos mapear qualquer palavra ou token para caracteres no texto original (e vice-versa) através dos métodos `word_to_chars()` ou `token_to_chars()` e `char_to_word()` ou `char_to_token()`. Por exemplo, o método `word_ids()` nos diz que `##yl` é parte da palavra no índice 3, mas qual palavra está na frase? Podemos descobrir da seguinte forma: + +```py +start, end = encoding.word_to_chars(3) +example[start:end] +``` + +```python out +Sylvain +``` + +Como mencionamos anteriormente, isso é apoiado pelo fato de que o tokenizador rápido acompanha o intervalo de texto de cada token em uma lista de *offsets*. Para ilustrar seu uso, mostraremos a seguir como replicar manualmente os resultados do pipeline `token-classification`. + + + +✏️ **Experimente!** Crie seu próprio texto de exemplo e veja se você consegue entender quais tokens estão associados ao ID da palavra e também como extrair os intervalos de caracteres para uma única palavra. Como bônus, tente usar duas frases como entrada e veja se os IDs das frases fazem sentido para você. + + + +## Dentro do pipeline `token-classification` + +No [Capítulo 1](/course/chapter1) tivemos o primeiro gosto de aplicar o NER -- onde a tarefa é identificar quais partes do texto correspondem a entidades como pessoas, locais ou organizações -- com a função do 🤗 Transformers `pipeline()`. Então, no [Capítulo 2](/course/chapter2), vimos como um pipeline agrupa os três estágios necessários para obter as previsões de um texto: tokenização, passagem das entradas pelo modelo e pós-processamento. As duas primeiras etapas do pipeline `token-classification` são as mesmas de qualquer outro pipeline, mas o pós-processamento é um pouco mais complexo -- vejamos como! + + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +### Obtendo os resultados básicos com o pipeline + +Primeiro, vamos usar um pipeline de classificação de token para que possamos obter alguns resultados para comparar manualmente. O modelo usado por padrão é [`dbmdz/bert-large-cased-finetuned-conll03-english`](https://huggingface.co/dbmdz/bert-large-cased-finetuned-conll03-english); ele executa NER em frases: + +```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}] +``` + +O modelo identificou corretamente cada token gerado por "Sylvain" como uma pessoa, cada token gerado por "Hugging Face" como uma organização e o token "Brooklyn" como um local. Também podemos pedir ao pipeline para agrupar os tokens que correspondem à mesma entidade: + +```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}] +``` + +O parâmetro `aggregation_strategy` escolhido mudará as pontuações calculadas para cada entidade agrupada. Com o valor `"simple"`, a pontuação é apenas a média das pontuações de cada token na entidade dada: por exemplo, a pontuação de "Sylvain" é a média das pontuações que vimos no exemplo anterior para os tokens `S`, `##yl`, `##va`, e `##in`. Outras estratégias disponíveis são: + +- `"first"`, onde a pontuação de cada entidade é a pontuação do primeiro token dessa entidade (portanto, para "Sylvain" seria 0.993828, a pontuação do token `S`) +- `"max"`, onde a pontuação de cada entidade é a pontuação máxima dos tokens naquela entidade (portanto, para "Hugging Face" seria 0.98879766, a pontuação do token `"Face"`) +- `"average"`, onde a pontuação de cada entidade é a média das pontuações das palavras que compõem aquela entidade (assim para "Sylvain" não haveria diferença da estratégia `"simple"`, mas `"Hugging Face"` teria uma pontuação de 0.9819, a média das pontuações para `"Hugging"`, 0.975, e `"Face"`, 0.98879) + +Agora vejamos como obter esses resultados sem usar a função `pipeline()`! + +### Das entradas às previsões + +{#if fw === 'pt'} + +Primeiro, precisamos tokenizar nossa entrada e passá-la pelo modelo. Isso é feito exatamente como no [Capítulo 2](/course/chapter3); instanciamos o tokenizador e o modelo usando as classes `AutoXxx` e depois as usamos em nosso exemplo: + +```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) +``` + +Como estamos usando `AutoModelForTokenClassification` neste caso, obtemos um conjunto de logits para cada token na sequência de entrada: + +```py +print(inputs["input_ids"].shape) +print(outputs.logits.shape) +``` + +```python out +torch.Size([1, 19]) +torch.Size([1, 19, 9]) +``` + +{:else} + +Primeiro, precisamos tokenizar nossa entrada e passá-la pelo modelo. Isso é feito exatamente como no [Capítulo 2](/course/chapter2); instanciamos o tokenizador e o modelo usando as classes `TFAutoXxx` e depois as usamos em nosso exemplo: + +```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) +``` + +Como estamos usando `TFAutoModelForTokenClassification` neste caso, obtemos um conjunto de logits para cada token na sequência de entrada: + +```py +print(inputs["input_ids"].shape) +print(outputs.logits.shape) +``` + +```python out +(1, 19) +(1, 19, 9) +``` + +{/if} + +Temos um lote com 1 sequência de 19 tokens e o modelo tem 9 rótulos diferentes, então a saída do modelo tem um tamanho de 1 x 19 x 9. Assim como para o pipeline de classificação de texto, usamos uma função softmax para converter esses logits para probabilidades, e pegamos o argmax para obter previsões (note que podemos pegar o argmax nos logits porque o softmax não altera a ordem): + +{#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] +``` + +O atributo `model.config.id2label` contém o mapeamento de índices para rótulos que podemos usar para entender as previsões: + +```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 anteriormente, existem 9 rótulos: `O` é o rótulo para os tokens que não estão em nenhuma entidade nomeada, e então temos dois rótulos para cada tipo de entidade (miscelânia, pessoa, organização e localização). O rótulo `B-XXX` indica que o token está no início de uma entidade `XXX` e o rótulo `I-XXX` indica que o token está dentro da entidade `XXX`. No caso do exemplo atual, esperaríamos que o nosso modelo classificasse o token `S` como `B-PER` (início de uma entidade pessoa) e os tokens `##yl`, `##va` e `##in` como `I-PER` (dentro da entidade pessoa). + +Você pode pensar que o modelo estava errado neste caso, pois deu o rótulo `I-PER` a todos esses quatro tokens, mas isso não é totalmente verdade. Na realidade, existem dois formatos para esses rótulos: `B-` e `I-`: *IOB1* e *IOB2*. O formato IOB2 (em rosa abaixo), é o que introduzimos, enquanto que no formato IOB1 (em azul), os rótulos que começam com `B-` são usados apenas para separar duas entidades adjacentes do mesmo tipo. O modelo que estamos usando foi ajustado em um conjunto de dados usando esse formato, e é por isso que ele atribui o rótulo `I-PER` ao token `S`. + +
+IOB1 vs IOB2 format + +
+ +Com este mapa, estamos prontos para reproduzir (quase inteiramente) os resultados do primeiro pipeline -- podemos apenas pegar a pontuação e o rótulo de cada token que não foi classificado como `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'}] +``` + +Isso é muito parecido com o que tínhamos antes, com uma exceção: o pipeline também nos dava informações sobre o `start` e `end` de cada entidade na frase original. É aqui que nosso mapeamento de offset entrará em ação. Para obter tais offsets, basta definir `return_offsets_mapping=True` quando aplicamos o tokenizador às nossas entradas: + +```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 é o intervalo de texto correspondente a cada token, onde `(0, 0)` é reservado para os tokens especiais. Vimos antes que o token no índice 5 é `##yl`, que tem `(12, 14)` como offset aqui. Se pegarmos a fatia correspondente em nosso exemplo: + +```py +example[12:14] +``` + +obtemos o intervalo adequado de texto sem o `##`: + +```python out +yl +``` + +Usando isso, agora podemos completar os resultados anteriores: + +```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}] +``` + +Este é o mesmo resultado que obtivemos no primeiro pipeline! + +### Agrupando entidades + +Usar os offsets para determinar as chaves inicial e final de cada entidade é útil, mas essa informação não é estritamente necessária. Quando queremos agrupar as entidades, no entanto, os offsets nos pouparão muito código confuso. Por exemplo, se quisermos agrupar os tokens `Hu`, `##gging` e `Face`, podemos fazer regras especiais que digam que os dois primeiros devem ser anexados e removido o `##`, e o `Face` deve ser adicionado com um espaço, pois não começa com `##` -- mas isso só funcionaria para esse tipo específico de tokenizador. Teríamos que escrever outro conjunto de regras para um tokenizador SentencePiece ou Byte-Pair-Encoding (discutido mais adiante neste capítulo). + +Com os offsets, todo esse código personalizado desaparece: podemos apenas pegar o intervalo no texto original que começa com o primeiro token e termina com o último token. Então, no caso dos tokens `Hu`, `##ging` e `Face`, devemos começar no caractere 33 (o início de `Hu`) e terminar antes do caractere 45 (o final de `Face`): + +```py +example[33:45] +``` + +```python out +Hugging Face +``` + +Para escrever o código para o pós-processamento das previsões ao agrupar entidades, agruparemos entidades consecutivas e rotuladas com `I-XXX`, excento a primeira, que pode ser rotulada como `B-XXX` ou `I-XXX` (portanto, paramos de agrupar uma entidade quando obtemos um `O`, um novo tipo de entidade ou um `B-XXX` que nos informa que uma entidade do mesmo tipo está iniciando): + +```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": + # Removendo o B- ou I- + label = label[2:] + start, _ = offsets[idx] + + # Vamos pegar todos os tokens rotulados com 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 + + # A pontuação é a média de todas as pontuações dos tokens da entidade 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) +``` + +E obtemos os mesmos resultados do nosso 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}] +``` + +Outro exemplo de uma tarefa onde esses offsets são extremamente úteis é a resposta a perguntas. O conhecimento deste pipeline, que faremos na próxima seção, também nos permitirá dar uma olhada em um último recurso dos tokenizadores na biblioteca 🤗 Transformers: lidar com tokens em excesso quando truncamos uma entrada em um determinado comprimento. diff --git a/chapters/zh-CN/_toctree.yml b/chapters/zh-CN/_toctree.yml index 39883a89e..499368392 100644 --- a/chapters/zh-CN/_toctree.yml +++ b/chapters/zh-CN/_toctree.yml @@ -1,12 +1,12 @@ - title: 0. 安装 sections: - local: chapter0/1 - title: 简介 + title: 课程简介 - title: 1. Transformer 模型 sections: - local: chapter1/1 - title: 章节简介 + title: 本章简介 - local: chapter1/2 title: 自然语言处理 - local: chapter1/3 @@ -30,7 +30,7 @@ - title: 2. 使用 🤗 Transformers sections: - local: chapter2/1 - title: 章节简介 + title: 本章简介 - local: chapter2/2 title: 管道的内部 - local: chapter2/3 @@ -50,7 +50,7 @@ - title: 3. 微调一个预训练模型 sections: - local: chapter3/1 - title: 章节简介 + title: 本章简介 - local: chapter3/2 title: 预处理数据 - local: chapter3/3 @@ -63,3 +63,39 @@ - local: chapter3/6 title: 章末小测验 quiz: 3 + +- title: 4. 共享 models 和 tokenizers + sections: + - local: chapter4/1 + title: The Hugging Face Hub + - local: chapter4/2 + title: 使用预训练的模型 + - local: chapter4/3 + title: 共享预训练模型 + - local: chapter4/4 + title: 构建模型卡片 + - local: chapter4/5 + title: Part 1 完结! + - local: chapter4/6 + title: 章末小测验 + quiz: 4 + +- title: 5. The 🤗 Datasets library + sections: + - local: chapter5/1 + title: 本章简介 + - local: chapter5/2 + title: 如果我的数据集不在 Hub 上怎么办? + - local: chapter5/3 + title: 是时候来学一下切片了 + - local: chapter5/4 + title: 大数据? 🤗 Datasets 来救援! + - local: chapter5/5 + title: 创建自己的数据集 + - local: chapter5/6 + title: 使用 FAISS 进行语义搜索 + - local: chapter5/7 + title: 🤗 Datasets,回顾! + - local: chapter5/8 + title: 章末小测验 + quiz: 5 diff --git a/chapters/zh-CN/chapter0/1.mdx b/chapters/zh-CN/chapter0/1.mdx index ca2294a22..5e46295d1 100644 --- a/chapters/zh-CN/chapter0/1.mdx +++ b/chapters/zh-CN/chapter0/1.mdx @@ -1,4 +1,4 @@ -# 简介 +# 课程简介 欢迎来到拥抱脸课程!本介绍将指导您设置工作环境。如果您刚开始学习本课程,我们建议您先阅读[第一章](/course/chapter1), 然后再回来设置您的环境,以便您可以自己尝试运行代码。 diff --git a/chapters/zh-CN/chapter1/1.mdx b/chapters/zh-CN/chapter1/1.mdx index 68e6a14c7..4ab545a0c 100644 --- a/chapters/zh-CN/chapter1/1.mdx +++ b/chapters/zh-CN/chapter1/1.mdx @@ -1,4 +1,4 @@ -# 简介 +# 本章简介 ## 欢迎来到🤗课程 diff --git a/chapters/zh-CN/chapter2/1.mdx b/chapters/zh-CN/chapter2/1.mdx index a24c162da..d0ab0e0d9 100644 --- a/chapters/zh-CN/chapter2/1.mdx +++ b/chapters/zh-CN/chapter2/1.mdx @@ -1,4 +1,4 @@ -# 介绍 +# 本章简介 正如你在 [Chapter 1](/course/chapter1),中看到的那样,Transformers模型通常非常大。对于数以百万计到数千万计数十亿的参数,训练和部署这些模型是一项复杂的任务。此外,由于几乎每天都在发布新模型,而且每种模型都有自己的实现,因此尝试它们绝非易事。 diff --git a/chapters/zh-CN/chapter3/1.mdx b/chapters/zh-CN/chapter3/1.mdx index 544b04149..f441c3f21 100644 --- a/chapters/zh-CN/chapter3/1.mdx +++ b/chapters/zh-CN/chapter3/1.mdx @@ -1,6 +1,6 @@ -# 介绍 +# 本章简介 在 [第二章](/course/chapter2) 我们探索了如何使用标记器(Tokenizer)和预训练模型进行预测。但是,如果您想为自己的数据集微调预训练模型,该怎么做呢?这就是本章的主题!你将学到: diff --git a/chapters/zh-CN/chapter4/1.mdx b/chapters/zh-CN/chapter4/1.mdx new file mode 100644 index 000000000..167f46f9d --- /dev/null +++ b/chapters/zh-CN/chapter4/1.mdx @@ -0,0 +1,15 @@ +# The Hugging Face Hub + +[Hugging Face Hub](https://huggingface.co/)---我们的主网站,是一个中央平台,在这个网站上任何人都可以查找、使用和贡献新的最先进的模型和数据集。它拥有各种各样的模型,公开可用的模型超过 10,000个。我们在本章去探索Hub中的模型,并在第 5 章中探索Hub中的数据集。 + +Hub 中的模型不仅限于🤗 Transformers 甚至 NLP。有用于自然语言处理的[Flair](https://github.com/flairNLP/flair),[AllenNLP](https://github.com/allenai/allennlp),[Asteroid](https://github.com/asteroid-team/asteroid)和用于音频检测的[pyannote](https://github.com/pyannote/pyannote-audio),以及对于视觉的[timm](https://github.com/rwightman/pytorch-image-models),这些例子只是Hub中冰山一角,更多的模型。可以由你去探索。 + +这些模型中的每一个都作为 Git 存储库托管,这允许进行版本控制和重现。在 Hub 上共享模型意味着将其向社区开放,让任何希望使用它的人都可以轻松访问它,从而使其他人不用为训练模型而苦恼就可以直接使用模型。 + +此外,在 Hub 上共享模型会自动为该模型部署托管的推理 API。社区中的任何人都可以直接在模型页面上自由地测试它,使用自定义输入和适当的小部件。 + +最棒的是是在 Hub 上共享和使用任何公共模型是完全免费的!如果您不想公开模型,也存在[付费计划](https://huggingface.co/pricing)。下面的视频显示了如何使用 Hub。 + + + +这部分需要有一个 Huggingface.co 帐户,因为我们将在 Hugging Face Hub 上创建和管理存储库:[创建一个账户](https://huggingface.co/join) \ No newline at end of file diff --git a/chapters/zh-CN/chapter4/2.mdx b/chapters/zh-CN/chapter4/2.mdx new file mode 100644 index 000000000..696767537 --- /dev/null +++ b/chapters/zh-CN/chapter4/2.mdx @@ -0,0 +1,97 @@ + + +# 使用预训练的模型 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +模型中心使选择合适的模型变得简单,因此只需几行代码即可在任何下游库中使用它。让我们来看看如何实际使用这些模型之一,以及如何回馈社区。 + +假设我们正在寻找一种可以执行**mask**填充的French-based模型。 + +
+Selecting the Camembert model. +
+ +我们选择 **camembert-base** 检查点来尝试一下。我们需要做的仅仅是输入 `camembert-base`标识符!正如您在前几章中看到的,我们可以使用 **pipeline()** 功能: + +```py +from transformers import pipeline + +camembert_fill_mask = pipeline("fill-mask", model="camembert-base") +results = camembert_fill_mask("Le camembert est :)") +``` + +```python out +[ + {'sequence': 'Le camembert est délicieux :)', 'score': 0.49091005325317383, 'token': 7200, 'token_str': 'délicieux'}, + {'sequence': 'Le camembert est excellent :)', 'score': 0.1055697426199913, 'token': 2183, 'token_str': 'excellent'}, + {'sequence': 'Le camembert est succulent :)', 'score': 0.03453313186764717, 'token': 26202, 'token_str': 'succulent'}, + {'sequence': 'Le camembert est meilleur :)', 'score': 0.0330314114689827, 'token': 528, 'token_str': 'meilleur'}, + {'sequence': 'Le camembert est parfait :)', 'score': 0.03007650189101696, 'token': 1654, 'token_str': 'parfait'} +] +``` + +如您所见,在管道中加载模型非常简单。您唯一需要注意的是所选检查点是否适合它将用于的任务。例如,这里我们正在加载 **camembert-base** 检查点在 **fill-mask** 管道,这完全没问题。但是如果我们要在 **text-classification** 管道,结果没有任何意义,因为 **camembert-base** 不适合这个任务!我们建议使用 Hugging Face Hub 界面中的任务选择器来选择合适的检查点: + +
+The task selector on the web interface. +
+ +您还可以直接使用模型架构实例化检查点: + +{#if fw === 'pt'} +```py +from transformers import CamembertTokenizer, CamembertForMaskedLM + +tokenizer = CamembertTokenizer.from_pretrained("camembert-base") +model = CamembertForMaskedLM.from_pretrained("camembert-base") +``` + +然而,我们建议使用[Auto* 类](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes),因为Auto* 类设计与架构无关。前面的代码示例将只能在 CamemBERT 架构中加载可用的检查点,但使用 **Auto*** 类使切换检查点变得简单: + +```py +from transformers import AutoTokenizer, AutoModelForMaskedLM + +tokenizer = AutoTokenizer.from_pretrained("camembert-base") +model = AutoModelForMaskedLM.from_pretrained("camembert-base") +``` +{:else} +```py +from transformers import CamembertTokenizer, TFCamembertForMaskedLM + +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: +然而,我们建议使用[`TFAuto*` 类](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes),因为`TFAuto*`类设计与架构无关。前面的代码示例将只能在 CamemBERT 架构中加载可用的检查点,但使用 `TFAuto*` 类使切换检查点变得简单: + +```py +from transformers import AutoTokenizer, TFAutoModelForMaskedLM + +tokenizer = AutoTokenizer.from_pretrained("camembert-base") +model = TFAutoModelForMaskedLM.from_pretrained("camembert-base") +``` +{/if} + + +使用预训练模型时,一定要检查它是如何训练的,在哪些数据集上,它的限制和它的偏差。所有这些信息都应在其模型卡片上注明。 + diff --git a/chapters/zh-CN/chapter4/3.mdx b/chapters/zh-CN/chapter4/3.mdx new file mode 100644 index 000000000..4e40bcc68 --- /dev/null +++ b/chapters/zh-CN/chapter4/3.mdx @@ -0,0 +1,648 @@ + + +# 共享预训练模型 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +在下面的步骤中,我们将看看将预训练模型分享到 🤗 Hub 的最简单方法。有可用的工具和实用程序可以让直接在 Hub 上共享和更新模型变得简单,我们将在下面进行探讨。 + + + +我们鼓励所有训练模型的用户通过与社区共享来做出贡献——共享模型,即使是在非常特定的数据集上进行训练,也将帮助他人,节省他们的时间和计算资源,并提供对有用的训练工件的访问。反过来,您可以从其他人所做的工作中受益! + +创建新模型存储库的方法有以下三种: + +- 使用 push_to_hub API 接口 +- 使用 huggingface_hub Python 库 +- 使用 web 界面 + +创建存储库后,您可以通过 git 和 git-lfs 将文件上传到其中。我们将在以下部分引导您创建模型存储库并将文件上传到它们 + + +## 使用 push_to_hub API + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +将文件上传到集线器的最简单方法是利用 **push_to_hub** API 接口。 + +在继续之前,您需要生成一个身份验证令牌,以便 **huggingface_hub** API 知道您是谁以及您对哪些名称空间具有写入权限。确保你在一个环境中 **transformers** 已安装(见[Setup](/course/chapter0))。如果您在笔记本中,可以使用以下功能登录: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +在终端中,您可以运行: + +```bash +huggingface-cli login +``` + +在这两种情况下,系统都会提示您输入用户名和密码,这与您用于登录 Hub 的用户名和密码相同。如果您还没有 Hub 配置文件,则应该创建一个[here](https://huggingface.co/join)。 + +好的!您现在已将身份验证令牌存储在缓存文件夹中。让我们创建一些存储库! + +{#if fw === 'pt'} + +如果你玩过 **Trainer** 用于训练模型的 API,将其上传到 Hub 的最简单方法是设置 **push_to_hub=True** 当你定义你的 **TrainingArguments** : + +```py +from transformers import TrainingArguments + +training_args = TrainingArguments( + "bert-finetuned-mrpc", save_strategy="epoch", push_to_hub=True +) +``` + +你声明 **trainer.train()** 的时候, 这 **Trainer** 然后每次将您的模型保存到您的命名空间中的存储库中时(这里是每个时代),它将上传到集线器。该存储库将命名为您选择的输出目录(此处 **bert-finetuned-mrpc** ) 但您可以选择不同的名称 **hub_model_id = a_different_name** 。 + +要将您的模型上传到您所属的组织,只需将其传递给 **hub_model_id = my_organization/my_repo_name** 。 + +训练结束后,你应该做最后的 **trainer.push_to_hub()** 上传模型的最新版本。它还将生成包含所有相关元数据的模型卡,报告使用的超参数和评估结果!以下是您可能会在此类模型卡中找到的内容示例: + +
+ An example of an auto-generated model card. +
+ + +{:else} + + +如果您使用Keras来训练您的模型,则将其上传到Hub的最简单方法是在调用 **model.fit()** 时传递**PushToHubCallback**: + +```py +from transformers import PushToHubCallback + +callback = PushToHubCallback( + "bert-finetuned-mrpc", save_strategy="epoch", tokenizer=tokenizer +) +``` + +然后,您应该在对**model.fit()**的调用中添加**callbacks=[callback]**。然后,每次将模型保存在命名空间的存储库中(此处为每个 epoch)时,回调都会将模型上传到 Hub。该存储库的名称将类似于您选择的输出目录(此处为**bert-finetuned-mrpc**),但您可以选择另一个名称,名称为**hub_model_id = a_different_name**。 + +要将您的模型上传到您所属的组织,只需将其传递给 **hub_model_id = my_organization/my_repo_name** 。 + +{/if} + +在较低级别,可以通过模型、标记器和配置对象直接访问模型中心 **push_to_hub()** 方法。此方法负责创建存储库并将模型和标记器文件直接推送到存储库。与我们将在下面看到的 API 不同,不需要手动处理。 + +为了了解它是如何工作的,让我们首先初始化一个模型和一个标记器: + +{#if fw === 'pt'} + +```py +from transformers import AutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = AutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` + +{:else} + +```py +from transformers import TFAutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = TFAutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` + +{/if} + +你可以自由地用这些做任何你想做的事情——向标记器添加标记,训练模型,微调它。一旦您对生成的模型、权重和标记器感到满意,您就可以利用 **push_to_hub()** 方法直接在 **model** 中: + +```py +model.push_to_hub("dummy-model") +``` + +这将创建新的存储库 **dummy-model** 在您的个人资料中,并用您的模型文件填充它。 +对标记器执行相同的操作,以便所有文件现在都可以在此存储库中使用: + +```py +tokenizer.push_to_hub("dummy-model") +``` + +如果您属于一个组织,只需指定 **organization** 上传到该组织的命名空间的参数: + +```py +tokenizer.push_to_hub("dummy-model", organization="huggingface") +``` + +如果您希望使用特定的 Hugging Face 令牌,您可以自由地将其指定给 **push_to_hub()** 方法也是: + +```py +tokenizer.push_to_hub("dummy-model", organization="huggingface", use_auth_token="") +``` + +现在前往模型中心找到您新上传的模型:*https://huggingface.co/user-or-organization/dummy-model*。 + +单击“文件和版本”选项卡,您应该会在以下屏幕截图中看到可见的文件: + +{#if fw === 'pt'} +
+Dummy model containing both the tokenizer and model files. +
+{:else} +
+Dummy model containing both the tokenizer and model files. +
+{/if} + + + +✏️ **试试看**!获取与检查点关联的模型和标记器,并使用该方法将它们上传到您的命名空间中的存储库。在删除之前,请仔细检查该存储库是否正确显示在您的页面上。 + + + +如您所见, **push_to_hub()** 方法接受多个参数,从而可以上传到特定的存储库或组织命名空间,或使用不同的 API 令牌。我们建议您查看直接在[🤗 Transformers documentation](https://huggingface.co/transformers/model_sharing.html)了解什么是可能的 + +这 **push_to_hub()** 方法由[huggingface_hub](https://github.com/huggingface/huggingface_hub)Python 包,为 Hugging Face Hub 提供直接 API。它集成在 🤗 Transformers 和其他几个机器学习库中,例如[allenlp](https://github.com/allenai/allennlp).虽然我们在本章中专注于 🤗 Transformers 集成,但将其集成到您自己的代码或库中很简单。 + +跳到最后一部分,了解如何将文件上传到新创建的存储库! + +## 使用 huggingface_hub python库 + +这 **huggingface_hub** Python 库是一个包,它为模型和数据集中心提供了一组工具。它为常见任务提供了简单的方法和类,例如 +获取有关集线器上存储库的信息并对其进行管理。它提供了在 git 之上工作的简单 API 来管理这些存储库的内容并集成 Hub +在您的项目和库中。 + +类似于使用 **push_to_hub** API,这将要求您将 API 令牌保存在缓存中。为此,您需要使用 **login** 来自 CLI 的命令,如上一节所述(同样,确保在这些命令前面加上 **!** 字符(如果在 Google Colab 中运行): + +```bash +huggingface-cli login +``` + +这 **huggingface_hub** 包提供了几种对我们有用的方法和类。首先,有几种方法可以管理存储库的创建、删除等: + +```python no-format +from huggingface_hub import ( + # User management + login, + logout, + whoami, + + # Repository creation and management + create_repo, + delete_repo, + update_repo_visibility, + + # And some methods to retrieve/change information about the content + list_models, + list_datasets, + list_metrics, + list_repo_files, + upload_file, + delete_file, +) +``` + + +此外,它还提供了非常强大的 **Repository** 用于管理本地存储库的类。我们将在接下来的几节中探讨这些方法和该类,以了解如何利用它们。 + +这 **create_repo** 方法可用于在集线器上创建新存储库: + + +```py +from huggingface_hub import create_repo + +create_repo("dummy-model") +``` + +这将创建存储库 **dummy-model** 在您的命名空间中。如果愿意,您可以使用 **organization** 争论: + +```py +from huggingface_hub import create_repo + +create_repo("dummy-model", organization="huggingface") +``` + +这将创建 **dummy-model** 存储库中的 **huggingface** 命名空间,假设您属于该组织。 +其他可能有用的参数是: + +- private 以指定存储库是否应对其他人可见。 +- token 如果您想用给定的令牌覆盖存储在缓存中的令牌。 +- repo_type 如果你想创建一个或一个替代一个的而不是模型。接受的值和 datasetspace "dataset""space"。 + +创建存储库后,我们应该向其中添加文件!跳到下一部分以查看可以处理此问题的三种方法。 + + +## 使用网络界面 + +Web 界面提供了直接在 Hub 中管理存储库的工具。使用该界面,您可以轻松创建存储库、添加文件(甚至是大文件!)、探索模型、可视化差异等等。 + +要创建新的存储库,请访问[huggingface.co/new](https://huggingface.co/new): + +
+Page showcasing the model used for the creation of a new model repository. +
+ +首先,指定存储库的所有者:这可以是您或您所属的任何组织。如果您选择一个组织,该模型将出现在该组织的页面上,并且该组织的每个成员都可以为存储库做出贡献。 + +接下来,输入您的模型名称。这也将是存储库的名称。最后,您可以指定您的模型是公开的还是私有的。私人模特要求您拥有付费 Hugging Face 帐户,并允许您将模特隐藏在公众视野之外。 + +创建模型存储库后,您应该看到如下页面: + +
+An empty model page after creating a new repository. +
+ +这是您的模型将被托管的地方。要开始填充它,您可以直接从 Web 界面添加 README 文件。 + +
+The README file showing the Markdown capabilities. +
+ +README 文件在 Markdown 中 - 随意使用它!本章的第三部分致力于构建模型卡。这些对于为您的模型带来价值至关重要,因为它们是您告诉其他人它可以做什么的地方。 + +如果您查看“文件和版本”选项卡,您会发现那里还没有很多文件——只有自述文件你刚刚创建和.git 属性跟踪大文件的文件。 + +
+The 'Files and versions' tab only shows the .gitattributes and README.md files. +
+ +接下来我们将看看如何添加一些新文件。 + +## 上传模型文件 + +Hugging Face Hub 上的文件管理系统基于用于常规文件的 git 和 git-lfs(代表[Git Large File Storage](https://git-lfs.github.com/)) 对于较大的文件。 + +在下一节中,我们将介绍将文件上传到 Hub 的三种不同方式:通过 **huggingface_hub** 并通过 git 命令。 + +### The `upload_file` approach + +使用 **upload_file** 不需要在您的系统上安装 git 和 git-lfs。它使用 HTTP POST 请求将文件直接推送到 🤗 Hub。这种方法的一个限制是它不能处理大于 5GB 的文件。 +如果您的文件大于 5GB,请按照下面详述的另外两种方法进行操作。API 可以按如下方式使用: + +```py +from huggingface_hub import upload_file + +upload_file( + "/config.json", + path_in_repo="config.json", + repo_id="/dummy-model", +) +``` + +这将上传文件 **config.json** 可在 **path_to_file** 到存储库的根目录 **config.json** , 到 **dummy-model** 存储库。 +其他可能有用的参数是: + +- token,如果要通过给定的令牌覆盖缓存中存储的令牌。 +- repo_type, 如果你想要上传一个 `dataset` 或一个 `space` 而不是模型。 接受的值为 `"dataset"` 和 `"space"`. + + +### The `Repository` class + +以类似 git 的方式管理本地存储库。它抽象了 git 可能遇到的大部分痛点,以提供我们需要的所有功能。 + +使用这个类需要安装 git 和 git-lfs,所以确保你已经安装了 git-lfs(参见[here](https://git-lfs.github.com/)安装说明)并在开始之前进行设置。 + +为了开始使用我们刚刚创建的存储库,我们可以通过克隆远程存储库将其初始化到本地文件夹开始: + +```py +from huggingface_hub import Repository + +repo = Repository("", clone_from="/dummy-model") +``` + +这创建了文件夹 **path_to_dummy_folder** 在我们的工作目录中。该文件夹仅包含 **.gitattributes** 文件,因为这是通过实例化存储库时创建的唯一文件 **create_repo**。 + +从现在开始,我们可以利用几种传统的 git 方法: + +```py +repo.git_pull() +repo.git_add() +repo.git_commit() +repo.git_push() +repo.git_tag() +``` + +另外!我们建议您查看 **Repository** 可用文件[here](https://github.com/huggingface/huggingface_hub/tree/main/src/huggingface_hub#advanced-programmatic-repository-management)有关所有可用方法的概述。 + +目前,我们有一个模型和一个标记器,我们希望将其推送到集线器。我们已经成功克隆了存储库,因此我们可以将文件保存在该存储库中。 + +我们首先通过拉取最新更改来确保我们的本地克隆是最新的: + +```py +repo.git_pull() +``` + +完成后,我们保存模型和标记器文件: + +```py +model.save_pretrained("") +tokenizer.save_pretrained("") +``` + +这 **path_to_dummy_folder** 现在包含所有模型和标记器文件。我们遵循通常的 git 工作流程,将文件添加到暂存区,提交它们并将它们推送到集线器: + +```py +repo.git_add() +repo.git_commit("Add model and tokenizer files") +repo.git_push() +``` + +恭喜!您刚刚将第一个文件推送到hub上。 + +### The git-based approach + +这是上传文件的非常简单的方法:我们将直接使用 git 和 git-lfs 来完成。大多数困难都被以前的方法抽象掉了,但是下面的方法有一些警告,所以我们将遵循一个更复杂的用例。 + +使用这个类需要安装 git 和 git-lfs,所以请确保你有[git-lfs](https://git-lfs.github.com/)安装(请参阅此处了解安装说明)并在开始之前进行设置。 + +首先从初始化 git-lfs 开始: + +```bash +git lfs install +``` + +```bash +Updated git hooks. +Git LFS initialized. +``` + +完成后,第一步是克隆您的模型存储库: + +```bash +git clone https://huggingface.co// +``` + +我的用户名是 **lysandre** 我使用了模型名称 **dummy** ,所以对我来说,命令最终如下所示: + +``` +git clone https://huggingface.co/lysandre/dummy +``` + +我现在有一个名为的文件夹假在我的工作目录中。我能 **cd** 进入文件夹并查看内容: + +```bash +cd dummy && ls +``` + +```bash +README.md +``` + +如果您刚刚使用 Hugging Face Hub 创建了您的存储库 **create_repo** 方法,这个文件夹应该只包含一个隐藏的 **.gitattributes** 文件。如果您按照上一节中的说明使用 Web 界面创建存储库,则该文件夹应包含一个自述文件文件旁边的隐藏 **.gitattributes** 文件,如图所示。 + +添加一个常规大小的文件,例如配置文件、词汇文件,或者基本上任何几兆字节以下的文件,就像在任何基于 git 的系统中所做的一样。但是,更大的文件必须通过 git-lfs 注册才能将它们推送到拥抱脸。 + +让我们回到 Python 来生成我们想要提交到我们的虚拟存储库的模型和标记器: + +{#if fw === 'pt'} +```py +from transformers import AutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = AutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + +# Do whatever with the model, train it, fine-tune it... + +model.save_pretrained("") +tokenizer.save_pretrained("") +``` +{:else} +```py +from transformers import TFAutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = TFAutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + +# Do whatever with the model, train it, fine-tune it... + +model.save_pretrained("") +tokenizer.save_pretrained("") +``` +{/if} + +现在我们已经保存了一些模型和标记器工件,让我们再看看假文件夹: + +```bash +ls +``` + +{#if fw === 'pt'} +```bash +config.json pytorch_model.bin README.md sentencepiece.bpe.model special_tokens_map.json tokenizer_config.json tokenizer.json +``` + +If you look at the file sizes (for example, with `ls -lh`), you should see that the model state dict file (*pytorch_model.bin*) is the only outlier, at more than 400 MB. + +{:else} +```bash +config.json README.md sentencepiece.bpe.model special_tokens_map.json tf_model.h5 tokenizer_config.json tokenizer.json +``` + +如果您查看文件大小(例如, **ls -lh** ),您应该会看到模型状态 dict 文件 (pytorch_model.bin) 是唯一的异常值,超过 400 MB。 + +{/if} + + +✏️ 从 web 界面创建存储库时,*.gitattributes* 文件会自动设置为将具有某些扩展名的文件,例如 *.bin* 和 *.h5* 视为大文件,git-lfs 会对其进行跟踪您无需进行必要的设置。 + + +我们现在可以继续进行,就像我们通常使用传统 Git 存储库一样。我们可以使用以下命令将所有文件添加到 Git 的暂存环境中 **git add** 命令: + +```bash +git add . +``` + +然后我们可以查看当前暂存的文件: + +```bash +git status +``` + +{#if fw === 'pt'} +```bash +On branch main +Your branch is up to date with 'origin/main'. + +Changes to be committed: + (use "git restore --staged ..." to unstage) + modified: .gitattributes + new file: config.json + new file: pytorch_model.bin + new file: sentencepiece.bpe.model + new file: special_tokens_map.json + new file: tokenizer.json + new file: tokenizer_config.json +``` +{:else} +```bash +On branch main +Your branch is up to date with 'origin/main'. + +Changes to be committed: + (use "git restore --staged ..." to unstage) + modified: .gitattributes + new file: config.json + new file: sentencepiece.bpe.model + new file: special_tokens_map.json + new file: tf_model.h5 + new file: tokenizer.json + new file: tokenizer_config.json +``` +{/if} + +同样,我们可以确保 git-lfs 使用其跟踪正确的文件 **status** 命令: + +```bash +git lfs status +``` + +{#if fw === 'pt'} +```bash +On branch main +Objects to be pushed to origin/main: + + +Objects to be committed: + + config.json (Git: bc20ff2) + pytorch_model.bin (LFS: 35686c2) + sentencepiece.bpe.model (LFS: 988bc5a) + special_tokens_map.json (Git: cb23931) + tokenizer.json (Git: 851ff3e) + tokenizer_config.json (Git: f0f7783) + +Objects not staged for commit: + + +``` + +我们可以看到所有文件都有 **Git** 作为处理程序,除了其中有 **LFS**的*pytorch_model.bin* 和 *sentencepiece.bpe.model*。 + +{:else} +```bash +On branch main +Objects to be pushed to origin/main: + + +Objects to be committed: + + config.json (Git: bc20ff2) + sentencepiece.bpe.model (LFS: 988bc5a) + special_tokens_map.json (Git: cb23931) + tf_model.h5 (LFS: 86fce29) + tokenizer.json (Git: 851ff3e) + tokenizer_config.json (Git: f0f7783) + +Objects not staged for commit: + + +``` + +我们可以看到所有文件都有 **Git** 作为处理程序,除了其中有 **LFS**的*t5_model.h5*。 + +{/if} + +Let's proceed to the final steps, committing and pushing to 让我们继续最后的步骤,提交并推动拥抱脸远程仓库: + +```bash +git commit -m "First model version" +``` + +{#if fw === 'pt'} +```bash +[main b08aab1] First model version + 7 files changed, 29027 insertions(+) + 6 files changed, 36 insertions(+) + create mode 100644 config.json + create mode 100644 pytorch_model.bin + create mode 100644 sentencepiece.bpe.model + create mode 100644 special_tokens_map.json + create mode 100644 tokenizer.json + create mode 100644 tokenizer_config.json +``` +{:else} +```bash +[main b08aab1] First model version + 6 files changed, 36 insertions(+) + create mode 100644 config.json + create mode 100644 sentencepiece.bpe.model + create mode 100644 special_tokens_map.json + create mode 100644 tf_model.h5 + create mode 100644 tokenizer.json + create mode 100644 tokenizer_config.json +``` +{/if} + +推送可能需要一些时间,具体取决于您的互联网连接速度和文件大小: + +```bash +git push +``` + +```bash +Uploading LFS objects: 100% (1/1), 433 MB | 1.3 MB/s, done. +Enumerating objects: 11, done. +Counting objects: 100% (11/11), done. +Delta compression using up to 12 threads +Compressing objects: 100% (9/9), done. +Writing objects: 100% (9/9), 288.27 KiB | 6.27 MiB/s, done. +Total 9 (delta 1), reused 0 (delta 0), pack-reused 0 +To https://huggingface.co/lysandre/dummy + 891b41d..b08aab1 main -> main +``` + +{#if fw === 'pt'} +If we take a look at the model repository when this is finished, we can see all the recently added files: + +
+The 'Files and versions' tab now contains all the recently uploaded files. +
+ +UI 允许您浏览模型文件和提交,并查看每个提交引入的差异: + +
+The diff introduced by the recent commit. +
+ +{:else} + +如果我们在完成后查看模型存储库,我们可以看到所有最近添加的文件: + +
+The 'Files and versions' tab now contains all the recently uploaded files. +
+ +UI 允许您浏览模型文件和提交,并查看每个提交引入的差异: + +
+The diff introduced by the recent commit. +
+{/if} diff --git a/chapters/zh-CN/chapter4/4.mdx b/chapters/zh-CN/chapter4/4.mdx new file mode 100644 index 000000000..a6c698e13 --- /dev/null +++ b/chapters/zh-CN/chapter4/4.mdx @@ -0,0 +1,82 @@ +# 构建模型卡片 + +模型卡片是一个配置文件,可以说与模型存储库中的模型和 tokenizer 文件一样重要。它包含了模型的核心定义,确保了社区成员可以复现模型的结果,并提供一个其他成员可以在这个模型基础上构建他们的组件的平台。 + +记录训练和评估过程并提供有关使用的数据以及已完成的预处理和后续处理的足够信息,有助于其他人了解对模型的能力——确保模型存在和目前的限制、偏差可以识别和理解。 + +因此,创建清晰定义模型的模型卡片是非常重要的一步。在这里,我们提供了一些可以帮助您解决此问题的方法。创建模型卡片是通过您之前看到的 Markdown 文件:README.md 。 + +“模型卡片”的概念源于谷歌的一个研究方向, Margaret Mitchell 等人在论文[“Model Cards for Model Reporting”](https://arxiv.org/abs/1810.03993)中首次提出,此处包含的许多信息均基于该论文,我们建议您查看这篇论文以了解为什么模型卡片在重视可重复性、可重用性和公平性的时候中如此重要。 + +模型卡通常以非常简短的概述开始,说明模型的用途,然后是模型卡片需要的其他信息: + +- 模型描述 +- 预期用途和限制 +- 如何使用 +- 局限性和偏见 +- 训练数据 +- 训练程序 +- 评价结果 + +让我们来看看每个部分应该包含什么。 + +### 模型描述: + +提供了有关模型的基本详细信息。这包括架构、版本、如果它是在论文中介绍的,是否有原始的实现可用?作者以及有关模型的一般信息、任何版权都应归于此处。这一部分还可以提及有关训练程序、参数和重要免责声明的一般信息。 + +### 预期用途和限制: + +在此描述模型可以适用的例子,包括可以应用它的语言、领域。模型卡的这一部分还可以记录已知超出模型范围的区域,或者可能表现不佳的区域。 + +### 使用方法: + +此部分应包括一些有关如何使用模型的示例。这可以展示使用 **pipeline()** 函数、模型和标记器类的使用以及其他任何您认为可能有帮助的代码。 + +### 训练数据: + +这部分应该指出模型是在哪个数据集上训练的。也欢迎对数据集进行简要描述。 + +### 训练过程: + +此部分中,您应该描述从再现性角度来看有用的训练的所有相关方面。这包括对数据进行的任何预处理和后处理,以及模型训练的批量数、批量大小、学习率等细节。 + +### 变量和指标: + +在这里,您应该描述您用于评估的指标,以及您测量的不同因素。提及使用了哪些指标、在哪个数据集上以及哪个数据集部分,可以轻松地将您的模型的性能与其他模型的性能进行比较。 + +### 评价结果: + +这些应该提前在前面的部分告知,例如预期的使用效果和示例。最后,提供模型在评估数据集上的表现的指示。如果模型使用决策阈值,要么提供评估中使用的决策阈值,要么提供在不同阈值下针对预期用途进行评估的详细信息。 + +## 例子 + +查看以下几个精心制作的模型卡的例子: + +* [bert-base-cased](https://huggingface.co/bert-base-cased) +* [gpt2](https://huggingface.co/gpt2) +* [distilbert](https://huggingface.co/distilbert-base-uncased) + +更多来自于不同组织和公司的示例可以在[这里](https://github.com/huggingface/model_card/blob/master/examples.md)查阅. + +## 提示 + +发布模型时不需要模型卡,制作一个模型时不需要包含上述所有部分。但是,模型的文档会使未来的用户受益,因此我们建议您尽自己的知识和能力填写尽可能多的部分。 + +## 模型卡片元数据 + +如果您对 Hugging Face Hub 进行了一些探索,您应该已经看到某些模型属于某些类别:您可以按任务、语言、库等对其进行过滤。模型所属的类别来自于您在模型卡片标题中添加的元数据。 + +例如,如果你看一下[`camembert-base` 模型卡片](https://huggingface.co/camembert-base/blob/main/README.md),您应该在模型卡标题中看到以下几行: + +``` +--- +language: fr +license: mit +datasets: +- oscar +--- +``` + +该元数据由 Hugging Face Hub 解析,然后将这个模型识别为法语模型,拥有 MIT 许可证,在 Oscar 数据集上训练。 + +允许的指定语言、许可证、标签、数据集、指标以及模型在训练时获得的评估结果在[全部模型卡片的规格](https://raw.githubusercontent.com/huggingface/huggingface_hub/main/modelcard.md)可以查阅。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter4/5.mdx b/chapters/zh-CN/chapter4/5.mdx new file mode 100644 index 000000000..87f7e5241 --- /dev/null +++ b/chapters/zh-CN/chapter4/5.mdx @@ -0,0 +1,7 @@ +# Part 1 完结! + +这是课程第一部分的结尾!第 2 部分将在 11 月 15 日与大型社区活动一起发布,[点击这里](https://huggingface.co/blog/course-launch-event)查看更多信息. + +您现在应该能够针对文本分类问题(单个或成对句子)对预训练模型进行微调,并将结果上传到模型中心。为确保您掌握了第一部分的内容,您应该针对您感兴趣的想法进行尝试(不一定是英语)!一旦你完成,您可以在[Hugging Face 社区](https://discuss.huggingface.co/)的[这个话题](https://discuss.huggingface.co/t/share-your-projects/6803)分享您的项目。 + +我们迫不及待地想看看您将用它构建什么! \ No newline at end of file diff --git a/chapters/zh-CN/chapter4/6.mdx b/chapters/zh-CN/chapter4/6.mdx new file mode 100644 index 000000000..6f29d6c17 --- /dev/null +++ b/chapters/zh-CN/chapter4/6.mdx @@ -0,0 +1,215 @@ + + + + +# 章末小测试 + +让我们测试一下你在本章所学的知识! + +### 1.Hub上的模型有什么限制? + + +### 2.如何管理Hub上的模型? + + +### 3.你能使用Hugging Face Hub网页接口做什么? + + +### 4.模型卡是什么? + + +### 5.哪些🤗 Transformers 库的对象可以直接在 Hub 上通过push _ to _ Hub ()共享? +{#if fw === 'pt'} + +{:else} + +{/if} + +### 6.当使用push _ to _ hub ()方法或 CLI 工具时,第一步是什么? + + +### 7.您正在使用一个模型和一个标记器————如何将它们上传到 Hub? + huggingface _ hub 实用程序: 不需要额外的包装!" + }, + { + text: "将它们保存到磁盘并调用 < code > transformers-cli upload-model ", + explain: "命令 < code > upload-model 不存在。" + } + ]} +/> + +### 8.您可以使用'Repository'类执行哪些 git 操作? +git _ commit () 方法就是为此而存在的。", + correct: true + }, + { + text: "拉一下", + explain: "这就是 < code > git _ pull () 方法的目的。", + correct: true + }, + { + text: "推一下", + explain: "方法 < code > git _ push () 可以做到这一点。", + correct: true + }, + { + text: "合并", + explain: "不,这个操作在这个 API 中是不可能的。" + } + ]} +/> diff --git a/chapters/zh-CN/chapter5/1.mdx b/chapters/zh-CN/chapter5/1.mdx new file mode 100644 index 000000000..20fe40dd0 --- /dev/null +++ b/chapters/zh-CN/chapter5/1.mdx @@ -0,0 +1,17 @@ +# 本章简介 + +在[第三章](/course/chapter3)第一次体验了🤗Datasets 库,并发现在微调模型时有三个主要步骤: + +1. 从hugs Face Hub加载一个数据集。 +2. 使用Dataset.map()对数据进行预处理。 +3. 加载和计算指标(特征)。 + +但这只是🤗 Datasets的表面功能而已!在本章中,我们将深入了解这个库。在此过程中,我们将找到以下问题的答案: + +* 当数据集不在hub上时,您该怎么做? +* 如何对数据集进行切片?(如果你真正的特别需要使用pandas的时候该怎么办?) +* 当你的数据集很大,会撑爆你笔记本电脑的RAM时,你会怎么做? +* “内存映射”和Apache Arrow到底是什么? +* 如何创建自己的数据集并将其推送到中心? + +您在这里学到的技术将为您在[第6章](/course/chapter6)和[第7章](/course/chapter7)中的高级标记化和微调任务做好准备——所以,喝杯咖啡,让我们开始吧! \ No newline at end of file diff --git a/chapters/zh-CN/chapter5/2.mdx b/chapters/zh-CN/chapter5/2.mdx new file mode 100644 index 000000000..ce3d11dae --- /dev/null +++ b/chapters/zh-CN/chapter5/2.mdx @@ -0,0 +1,167 @@ +# 如果我的数据集不在 Hub 上怎么办? + + + +你知道如何使用[Hugging Face Hub](https://huggingface.co/datasets)下载数据集, 但你经常会发现自己正在处理存储在笔记本电脑或远程服务器上的数据。在本节中,我们将向您展示如何使用 🤗 Datasets来加载 Hugging Face Hub 上不可用的数据集。 + + + +## 使用本地和远程数据集 + +🤗 Datasets 提供了加载脚本来加载本地和远程数据集。它支持几种常见的数据格式,例如: + +| Data format | Loading script | Example | +| :----------------: | :------------: | :-----------------------------------------------------: | +| CSV & TSV | `csv` | `load_dataset("csv", data_files="my_file.csv")` | +| Text files | `text` | `load_dataset("text", data_files="my_file.txt")` | +| JSON & JSON Lines | `json` | `load_dataset("json", data_files="my_file.jsonl")` | +| Pickled DataFrames | `pandas` | `load_dataset("pandas", data_files="my_dataframe.pkl")` | + +如表所示, 对于每种数据格式, 我们只需要使用 `load_dataset()` 函数, 使用 `data_files` 指定一个或多个文件的路径的参数。 让我们从本地文件加载数据集开始;稍后我们将看到如何对远程文件执行相同的操作。 + +## 加载本地数据集 + +对于这个例子,我们将使用 [SQuAD-it dataset](https://github.com/crux82/squad-it/), 这是一个大规模的意大利语问答数据集。 + +训练和测试都托管在 GitHub 上, 因此我们可以通过`wget`命令非常简单地下载它们: + +```python +!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-train.json.gz +!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-test.json.gz +``` + +这将下载两个名为*SQuAD_it-train.json.gz* 和 *SQuAD_it-test.json.gz*的压缩文件, 我们可以用Linux的解压命令 `gzip`: + +```python +!gzip -dkv SQuAD_it-*.json.gz +``` + +```bash +SQuAD_it-test.json.gz: 87.4% -- replaced with SQuAD_it-test.json +SQuAD_it-train.json.gz: 82.2% -- replaced with SQuAD_it-train.json +``` + +我们可以看到压缩文件已经被替换为SQuAD_it-train.json和SQuAD_it-text.json,并且数据以 JSON 格式存储。 + + + +✎ 如果你想知道为什么上面的shell命令中哟与一个字符`!`,那是因为我们是在 Jupyter notebook 中运行它们。如果您想在终端中下载和解压缩数据集,只需删除前缀!即可。 + + + +使用`load_dataset()`函数来加载JSON文件, 我们只需要知道我们是在处理普通的 JSON(类似于嵌套字典)还是 JSON 行(行分隔的 JSON)。像许多问答数据集一样, SQuAD-it 使用嵌套格式,所有文本都存储在 `data`文件中。这意味着我们可以通过指定参数`field`来加载数据集,如下所示: + +```py +from datasets import load_dataset + +squad_it_dataset = load_dataset("json", data_files="SQuAD_it-train.json", field="data") +``` + +默认情况下, 加载本地文件会创建一个带有`train`的`DatasetDict` 对象。 我们可以通过 `squad_it_dataset`查看: + +```py +squad_it_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 442 + }) +}) +``` + +这向我们显示了与训练集相关联的行数和列名。我们可以通过索引到 `train` 查看示例,如下所示: + +```py +squad_it_dataset["train"][0] +``` + +```python out +{ + "title": "Terremoto del Sichuan del 2008", + "paragraphs": [ + { + "context": "Il terremoto del Sichuan del 2008 o il terremoto...", + "qas": [ + { + "answers": [{"answer_start": 29, "text": "2008"}], + "id": "56cdca7862d2951400fa6826", + "question": "In quale anno si è verificato il terremoto nel Sichuan?", + }, + ... + ], + }, + ... + ], +} +``` + +很好, 我们已经加载了我们的第一个本地数据集! 但是, 虽然这对训练集有效, 但是我们真正想要的是包括 `train` 和 `test` 的 `DatasetDict` 对象。这样的话就可以使用 `Dataset.map()` 函数同时处理训练集和测试集。 为此, 我们提供参数`data_files`的字典,将每个分割名称映射到与该分割相关联的文件: + +```py +data_files = {"train": "SQuAD_it-train.json", "test": "SQuAD_it-test.json"} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +squad_it_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 442 + }) + test: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 48 + }) +}) +``` + +这正是我们想要的。现在, 现在,我们可以应用各种预处理技术来清理数据、标记评论等。 + + + +`load_dataset()`函数的`data_files`参数非常灵活并且可以是单个文件路径、文件路径列表或将分割后的名称映射到文件路径的字典。您还可以根据Unix shell使用的规则对与指定模式匹配的文件进行全局定位(例如,您可以通过设置'data_files=“*.JSON”'将目录中的所有JSON文件作为单个拆分进行全局定位)。有关更多详细信息,请参阅🤗Datasets 文档。 + + + +🤗 Datasets实际上支持输入文件的自动解压,所以我们可以跳过使用`gzip`,直接设置 `data_files`参数传递压缩文件: + +```py +data_files = {"train": "SQuAD_it-train.json.gz", "test": "SQuAD_it-test.json.gz"} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +``` + +如果您不想手动解压缩许多 GZIP 文件,这会很有用。自动解压也适用于其他常见格式,如 ZIP 和 TAR,因此您只需将 `data_files` 设置为压缩文件所在的路径,你就可以开始了! + +现在你知道如何在笔记本电脑或台式机上加载本地文件,让我们来看看加载远程文件。 + +## 加载远程数据集 + +如果你在公司担任数据研究员或编码员,那么你要分析的数据集很有可能存储在某个远程服务器上。幸运的是,加载远程文件就像加载本地文件一样简单!我们没有提供本地文件的路径, 而是将`load_dataset()`的`data_files`参数指向存储远程文件的一个或多个URL。例如, 对于托管在 GitHub 上的 SQuAD-it 数据集, 我们可以将 `data_files` 指向 _SQuAD_it-*.json.gz_ 的网址,如下所示: + +```py +url = "https://github.com/crux82/squad-it/raw/master/" +data_files = { + "train": url + "SQuAD_it-train.json.gz", + "test": url + "SQuAD_it-test.json.gz", +} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +``` + +这将返回和上面的本地例子相同的 `DatasetDict` 对象, 但省去了我们手动下载和解压 _SQuAD_it-*.json.gz_ 文件的步骤。这是我们对加载未托管在Hugging Face Hub的数据集的各种方法的总结。既然我们已经有了一个可以使用的数据集,让我们开始大展身手吧! + + + +✏️ **试试看!** 选择托管在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))。 + + + + diff --git a/chapters/zh-CN/chapter5/3.mdx b/chapters/zh-CN/chapter5/3.mdx new file mode 100644 index 000000000..f4b7eb5d1 --- /dev/null +++ b/chapters/zh-CN/chapter5/3.mdx @@ -0,0 +1,743 @@ +# 是时候来学一下切片了 + + + +大多数情况下,您使用的数据都需根据模型所要求的输入进行清洗。在本节中,我们将探索 🤗 Datasets 提供的用于数据集清洗的各种功能。 + + + +## 切片与切分我们的数据 + +与 Pandas 类似,🤗 Datasets 提供了几个函数来操作 **Dataset** 和 **DatasetDict** 对象。我们在[第三章](/course/chapter3)已经遇到了 **Dataset.map()** 方法,在本节中,我们将探索我们可以使用的其他功能。 + +对于这个例子,我们将使用托管在[加州大学欧文分校机器学习存储库](https://archive.ics.uci.edu/ml/index.php)的[药物审查数据集](https://archive.ics.uci.edu/ml/datasets/Drug+Review+Dataset+%28Drugs.com%29),其中包含患者对各种药物的评论,以及正在治疗的病情和患者满意度的 10 星评级。 + +首先我们需要下载并提取数据,这可以通过 **wget** 和 **unzip** 命令: + +```py +!wget "https://archive.ics.uci.edu/ml/machine-learning-databases/00462/drugsCom_raw.zip" +!unzip drugsCom_raw.zip +``` + +由于 TSV 只是使用制表符而不是逗号作为分隔符的 CSV 变体,我们可以使用加载**csv**文件的**load_dataset()**函数并指定分隔符 示例如下: + +```py +from datasets import load_dataset + +data_files = {"train": "drugsComTrain_raw.tsv", "test": "drugsComTest_raw.tsv"} +# \t is the tab character in Python +drug_dataset = load_dataset("csv", data_files=data_files, delimiter="\t") +``` + +在进行任何类型的数据分析时,一个好的做法是抽取一个小的随机样本,以快速了解您正在处理的数据类型。在🤗数据集中,我们可以通过链接 **Dataset.shuffle()** 和 **Dataset.select()** 共同来完成抽取: + +```py +drug_sample = drug_dataset["train"].shuffle(seed=42).select(range(1000)) +# Peek at the first few examples +drug_sample[:3] +``` + +```python out +{'Unnamed: 0': [87571, 178045, 80482], + 'drugName': ['Naproxen', 'Duloxetine', 'Mobic'], + 'condition': ['Gout, Acute', 'ibromyalgia', 'Inflammatory Conditions'], + 'review': ['"like the previous person mention, I'm a strong believer of aleve, it works faster for my gout than the prescription meds I take. No more going to the doctor for refills.....Aleve works!"', + '"I have taken Cymbalta for about a year and a half for fibromyalgia pain. It is great\r\nas a pain reducer and an anti-depressant, however, the side effects outweighed \r\nany benefit I got from it. I had trouble with restlessness, being tired constantly,\r\ndizziness, dry mouth, numbness and tingling in my feet, and horrible sweating. I am\r\nbeing weaned off of it now. Went from 60 mg to 30mg and now to 15 mg. I will be\r\noff completely in about a week. The fibro pain is coming back, but I would rather deal with it than the side effects."', + '"I have been taking Mobic for over a year with no side effects other than an elevated blood pressure. I had severe knee and ankle pain which completely went away after taking Mobic. I attempted to stop the medication however pain returned after a few days."'], + 'rating': [9.0, 3.0, 10.0], + 'date': ['September 2, 2015', 'November 7, 2011', 'June 5, 2013'], + 'usefulCount': [36, 13, 128]} +``` + +请注意,出于可以复现的目的,我们已将在**Dataset.shuffle()**选取了固定的随机数种子。 **Dataset.select()** 需要一个可迭代的索引,所以我们已经通过了 **range(1000)** 从随机打乱的数据集中选取前 1,000 个示例。从抽取的数据中,我们已经可以看到我们数据集的一些特点: + +* **Unnamed: 0**这列看起来很像每个患者的匿名 ID。 +* **condition** 这列包含有描述健康状况的标签。 +* 评论长短不一,混合有 Python 行分隔符 (**\r\n**) 以及 HTML 字符代码,如** &\#039;**。 + +让我们看看我们如何使用 🤗 Datasets 来处理这些问题。为了验证**Unnamed: 0** 列存储的是患者 ID的猜想,我们可以使用 **Dataset.unique()** 函数来验证匿名ID 的数量是否与拆分后每部分中的行数匹配: + +```py +for split in drug_dataset.keys(): + assert len(drug_dataset[split]) == len(drug_dataset[split].unique("Unnamed: 0")) +``` + +这似乎证实了我们的假设,所以让我们把 **Unnamed: 0** 列重命名为患者的id。我们可以使用 **DatasetDict.rename_column()**函数一次性重命名DatasetDict中共有的列: + +```py +drug_dataset = drug_dataset.rename_column( + original_column_name="Unnamed: 0", new_column_name="patient_id" +) +drug_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'], + num_rows: 161297 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'], + num_rows: 53766 + }) +}) +``` + + + +✏️ **试试看!** 使用 `Dataset.unique()` 函数查找训练和测试集中满足某个条件的药物经过去重之后的数量。 + + + +接下来,让我们使用 **Dataset.map()**标准化所有 **condition** 标签 .正如我们在[第三章](/course/chapter3)中所做的那样,我们可以定义一个简单的函数,可以将该函数应用于**drug_dataset** 拆分后每部分的所有行: + +```py +def lowercase_condition(example): + return {"condition": example["condition"].lower()} + + +drug_dataset.map(lowercase_condition) +``` + +```python out +AttributeError: 'NoneType' object has no attribute 'lower' +``` + +哦不,我们的map功能遇到了问题!从错误中我们可以推断出 **condition** 列存在 **None** , 不能转换为小写,因为它们不是字符串。让我们使用 **Dataset.filter()** 删除这些行 ,其工作方式类似于 **Dataset.map()** 。例如: + +```py +def filter_nones(x): + return x["condition"] is not None +``` + +然后运行 **drug_dataset.filter(filter_nones)** ,我们可以在一行中使用lambda 函数.在 Python 中,lambda 函数是您无需明确命名即可使用的微函数(匿名函数)。它们一般采用如下形式: + +``` +lambda : +``` + +其中**lambda** 是 Python 的特殊[关键字](https://docs.python.org/3/reference/lexical_analysis.html#keywords), **arguments** 是以逗号进行分隔的函数输入的列表/集合, **expression** 代表您希望执行的操作。例如,我们可以定义一个简单的 lambda 函数来对一个数字进行平方,如下所示: + +``` +lambda x : x * x +``` + +我们需要将要输入给这个函数值括在括号中: + +```py +(lambda x: x * x)(3) +``` + +```python out +9 +``` + +类似地,我们可以通过用逗号分隔多个参数来定义 lambda 函数。例如,我们可以按如下方式计算三角形的面积: + +```py +(lambda base, height: 0.5 * base * height)(4, 8) +``` + +```python out +16.0 +``` + +当您想定义小型、一次性使用的函数时,Lambda 函数非常方便(有关它们的更多信息,我们建议阅读安德烈·布尔高写的[真正的Python教程](https://realpython.com/python-lambda/))。在🤗 Datasets 中,我们可以使用 lambda 函数来定义简单的映射和过滤操作,所以让我们使用这个技巧来消除我们数据集中的 **None** 条目: + +```py +drug_dataset = drug_dataset.filter(lambda x: x["condition"] is not None) +``` + +当 **None** 条目已删除,我们可以标准化我们的 **condition** 列: + +```py +drug_dataset = drug_dataset.map(lowercase_condition) +# Check that lowercasing worked +drug_dataset["train"]["condition"][:3] +``` + +```python out +['left ventricular dysfunction', 'adhd', 'birth control'] +``` + +有用!现在我们已经清理了标签,让我们来看看清洗后的评论文本。 + +## Creating new columns + +每当您处理客户评论时,一个好的做法是检查每个评论中的字数。评论可能只是一个词,比如“太棒了!”或包含数千字的完整文章,根据实际的情况,您需要以不同的方式处理这些极端情况。为了计算每条评论中的单词数,我们将使用基于空格分割每个文本的粗略方法。 + +让我们定义一个简单的函数来计算每条评论中的单词数: + +```py +def compute_review_length(example): + return {"review_length": len(example["review"].split())} +``` + +与我们的 `lowercase_condition()` 函数不同,`compute_review_length()` 返回一个字典,其键与数据集中的列名之一不对应。 在这种情况下,当 `compute_review_length()` 传递给 `Dataset.map()` 时,它将应用于数据集中的所有行以创建新的 `review_length` 列: + +```py +drug_dataset = drug_dataset.map(compute_review_length) +# Inspect the first training example +drug_dataset["train"][0] +``` + +```python out +{'patient_id': 206461, + 'drugName': 'Valsartan', + 'condition': 'left ventricular dysfunction', + 'review': '"It has no side effect, I take it in combination of Bystolic 5 Mg and Fish Oil"', + 'rating': 9.0, + 'date': 'May 20, 2012', + 'usefulCount': 27, + 'review_length': 17} +``` + +正如预期的那样,我们可以看到一个 **review_length** 列已添加到我们的训练集中。我们可以使用 **Dataset.sort()**对这个新列进行排序,然后查看极端长度的评论的样子: + +```py +drug_dataset["train"].sort("review_length")[:3] +``` + +```python out +{'patient_id': [103488, 23627, 20558], + 'drugName': ['Loestrin 21 1 / 20', 'Chlorzoxazone', 'Nucynta'], + 'condition': ['birth control', 'muscle spasm', 'pain'], + 'review': ['"Excellent."', '"useless"', '"ok"'], + 'rating': [10.0, 1.0, 6.0], + 'date': ['November 4, 2008', 'March 24, 2017', 'August 20, 2016'], + 'usefulCount': [5, 2, 10], + 'review_length': [1, 1, 1]} +``` + +正如我们所猜想的那样,一些评论只包含一个词,虽然这对于情感分析来说可能没问题,但如果我们想要预测病情,这些评论可能并不适合。 + + + +🙋向数据集添加新列的另一种方法是使用函数Dataset.add_column() 。这允许您输入Python 列表或 NumPy,在不适合使用Dataset.map()情况下可以很方便。 + + + +让我们使用 **Dataset.filter()** 功能来删除包含少于 30 个单词的评论。与我们对 **condition** 列的处理相似,我们可以通过选取评论的长度高于此阈值来过滤掉非常短的评论: + +```py +drug_dataset = drug_dataset.filter(lambda x: x["review_length"] > 30) +print(drug_dataset.num_rows) +``` + +```python out +{'train': 138514, 'test': 46108} +``` + +如您所见,这已经从我们的原始训练和测试集中删除了大约 15% 的评论。 + + + +✏️ 试试看!使用 Dataset.sort() 函数查看单词数最多的评论。请参阅文档以了解您需要使用哪个参数按长度降序对评论进行排序。 + + + +我们需要处理的最后一件事是评论中是否存在 HTML 字符代码。我们可以使用 Python 的**html**模块取消这些字符的转义,如下所示: + +```py +import html + +text = "I'm a transformer called BERT" +html.unescape(text) +``` + +```python out +"I'm a transformer called BERT" +``` + +我们将使用 **Dataset.map()** 对我们语料库中的所有 HTML 字符进行转义: + +```python +drug_dataset = drug_dataset.map(lambda x: {"review": html.unescape(x["review"])}) +``` + +如您所见, **Dataset.map()** 方法对于处理数据非常有用——在示例中仅仅是浅尝辄止就有很大的收获! + +## map() 方法的超级加速 + +**Dataset.map()** 方法有一个 **batched** 参数,如果设置为 **True** , map 函数将会分批执行所需要进行的操作(批量大小是可配置的,但默认为 1,000)。例如,之前对所有 HTML 进行转义的 map 函数运行需要一些时间(您可以从进度条中读取所用时间)。我们可以通过使用列表推导同时处理多个元素来加快速度。 + +当您在使用 **Dataset.map()**函数时指定 **batched=True**。该函数会接收一个包含数据集字段的字典,每个值都是一个列表,而不仅仅是单个值。**Dataset.map()** 的返回值应该是相同的:一个包含我们想要更新或添加到数据集中的字段的字典,字典的键是要添加的字段,字典的值是结果的列表。例如,这是使用 **batched=True**对所有 HTML 字符进行转义的方法 : + +```python +new_drug_dataset = drug_dataset.map( + lambda x: {"review": [html.unescape(o) for o in x["review"]]}, batched=True +) +``` + +如果您在笔记本中运行此代码,您会看到此命令的执行速度比前一个命令快得多。这不是因为我们的评论已经是处理过的——如果你重新执行上一节的指令(没有 **batched=True** ),它将花费与以前相同的时间。这是因为列表推导式通常比在同一代码中用 **for** 循环执行相同的代码更快,并且我们还通过同时访问多个元素而不是一个一个来处理来提高处理的速度。 + +在[第六章](/course/chapter6)我们将遇到的“快速”标记器,它可以快速标记大文本列表。使用 **Dataset.map()** 和 **batched=True** 是加速的关键。例如,要使用快速标记器标记所有药物评论,我们可以使用这样的函数: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") + + +def tokenize_function(examples): + return tokenizer(examples["review"], truncation=True) +``` + +正如你在[第三章](/course/chapter3)所看到的,我们原本就可以将一个或多个示例传递给分词器,因此在**batched=True**是一个非必须的选项.让我们借此机会比较不同选项的性能。在笔记本中,您可以在您要测量的代码行之前添加 **%time**来测试改行运行所消耗的时间: + +```python no-format +%time tokenized_dataset = drug_dataset.map(tokenize_function, batched=True) +``` + +您还可以通过将整个单元格计时 **%%time** 在单元格的开头。在我们执行此操作的硬件上,该指令显示 10.8 秒(这是写在“Wall time”之后的数字)。 + + + +✏️ **试试看!** 使用和不使用 `batched=True` 执行相同的指令,然后使用慢速标记器尝试(在 `AutoTokenizer.from_pretrained()` 方法中添加 `use_fast=False`),这样你就可以看看在你的电脑上它需要多长的时间。 + + + +以下是我们在使用和不使用批处理时使用快速和慢速分词器获得的结果: + +Options | Fast tokenizer | Slow tokenizer +:--------------:|:--------------:|:-------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s + +这意味着使用带有 **batched=True** 选项比没有批处理的慢选项快 30 倍——这真是太棒了!这就是为什么**AutoTokenizer** 的默认设置是**use_fast=True**的主要原因 (以及为什么它们被称为“快速”)。他们能够实现这样的加速,因为在底层的标记化代码是在 Rust 中执行的,Rust 是一种可以轻松并行化执行的语言。 + +并行化也是快速标记器通过批处理实现近 6 倍加速的原因:单个标记化操作是不能并行的,但是当您想同时标记大量文本时,您可以将执行拆分为多个进程,每个进程都对自己的文本负责。 + +**Dataset.map()** 也有一些自己的并行化能力。由于它们不受 Rust 的支持,因此慢速分词器的速度赶不上快速分词器,但它们仍然会更快一些(尤其是当您使用没有快速版本的分词器时)。要启用多处理,请在**Dataset.map()**时使用 **num_proc** 参数并指定要在调用中使用的进程数 : + +```py +slow_tokenizer = AutoTokenizer.from_pretrained("bert-base-cased", use_fast=False) + + +def slow_tokenize_function(examples): + return slow_tokenizer(examples["review"], truncation=True) + + +tokenized_dataset = drug_dataset.map(slow_tokenize_function, batched=True, num_proc=8) +``` + +您可以对处理的时间进行一些试验,以确定要使用的最佳进程数;在我们的例子中,8 似乎产生了最好的速度增益。以下是我们在使用和不使用多处理时所需要的时间: + +Options | Fast tokenizer | Slow tokenizer +:--------------:|:--------------:|:-------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s +`batched=True`, `num_proc=8` | 6.52s | 41.3s +`batched=False`, `num_proc=8` | 9.49s | 45.2s + +对于慢速分词器来说,这些结果要合理得多,但快速分词器的性能也得到了显着提高。但是请注意,情况并非总是如此——除了 **num_proc=8**,我们的测试表明,使用**batched=True**而不带有**num_proc**参数的选项处理起来更快。通常,我们不建议将 Python 多线程处理用于具有**batched=True**功能的快速标记器 . + + + +使用num_proc以加快处理速度通常是一个好主意,只要您使用的函数还没有自己带有的进行某种多进程处理的方法。 + + + +将所有这些功能浓缩到一个方法中已经非常了不起,但还有更多!使用 **Dataset.map()** 和 **batched=True** 您可以更改数据集中的元素数量。当你想从一个例子中创建几个训练特征时,这是非常有用的。我们将在[第七章](/course/chapter7).中进行的几个NLP任务的预处理中使用到这个功能,它非常便利。 + + + +💡在机器学习中,一个例子通常可以为我们的模型提供一组特征。在某些情况下,这些特征会储存在数据集的几个列,但在其他情况下(例如此处的例子和用于问答的数据),可以从单个示例的一列中提取多个特征 + + + +让我们来看看它是如何工作的!在这里,我们将标记化我们的示例并将最大截断长度设置128,但我们将要求标记器返回全部文本块,而不仅仅是第一个。这可以用 **return_overflowing_tokens=True** : + +```py +def tokenize_and_split(examples): + return tokenizer( + examples["review"], + truncation=True, + max_length=128, + return_overflowing_tokens=True, + ) +``` + +在使用**Dataset.map()** 正式在整个数据集上开始处理之前让我们先在一个例子上测试一下: + +```py +result = tokenize_and_split(drug_dataset["train"][0]) +[len(inp) for inp in result["input_ids"]] +``` + +```python out +[128, 49] +``` + +瞧!我们在训练集中的第一个示例变成了两个特征,因为它被标记为超过我们指定的最大截断长度,因此结果被截成了两段:第一段长度为 128 ,第二段长度为 49 。现在让我们对所有元素执行此操作数据集! + +```py +tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) +``` + +```python out +ArrowInvalid: Column 1 named condition expected length 1463 but got length 1000 +``` + +不好了!它没有起作用!为什么呢?查看错误消息会给我们一个线索:列的长度不匹配,一列长度为 1,463,另一列长度为 1,000。1,000行的"review"给出了 1,463 行的新特征,导致和原本的1000行数据不匹配。 + +问题出在我们试图混合两个不同大小的不同数据集: **drug_dataset** 列将有一定数量的元素(我们错误中的 1,000),但是我们正在构建**tokenized_dataset** 将有更多的元素(错误消息中的 1,463)。这不适用于 **Dataset** ,因此我们需要从旧数据集中删除列或使它们的大小与新数据集中的大小相同。我们可以用 **remove_columns** 参数: + +```py +tokenized_dataset = drug_dataset.map( + tokenize_and_split, batched=True, remove_columns=drug_dataset["train"].column_names +) +``` + +现在这个过程没有错误。我们可以通过比较长度来检查新数据集的元素是否比原始数据集多得多: + +```py +len(tokenized_dataset["train"]), len(drug_dataset["train"]) +``` + +```python out +(206772, 138514) +``` + +我们提到我们还可以通过使旧列与新列的大小相同来处理长度不匹配的问题。为此,我们可以使用 **overflow_to_sample_mapping** 字段,当我们设置**return_overflowing_tokens=True** .它为我们提供了特征到它所产生的样本的映射。使用这个,我们可以将原始数据集中的每个键关联到一个合适大小的值列表中,通过遍历所有的数据来生成新特性: + +```py +def tokenize_and_split(examples): + result = tokenizer( + examples["review"], + truncation=True, + max_length=128, + return_overflowing_tokens=True, + ) + # Extract mapping between new and old indices + sample_map = result.pop("overflow_to_sample_mapping") + for key, values in examples.items(): + result[key] = [values[i] for i in sample_map] + return result +``` + +我们可以使用**Dataset.map()**来进行批处理,这样无需我们删除旧列: + +```py +tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) +tokenized_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'], + num_rows: 206772 + }) + test: Dataset({ + features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'], + num_rows: 68876 + }) +}) +``` + +我们获得了与以前相同数量的训练特征,但在这里我们保留了所有旧字段。如果您在使用模型计算之后需要它们进行一些后处理,您可能需要使用这种方法。 + +您现在已经了解了 🤗 Datasets如何以各种方式用于预处理数据集。虽然🤗 Datasets 的处理功能会覆盖你大部分的模型训练需求,有时您可能需要切换到 Pandas 以使用更强大的功能,例如 **DataFrame.groupby()** 或用于可视化的高级 API。幸运的是,🤗 Datasets旨在与 Pandas、NumPy、PyTorch、TensorFlow 和 JAX 等库可以相互转换。让我们来看看这是如何工作的。 + +## `🤗 Datasets 和 DataFrames 的相互转换 + + + +为了实现各种第三方库之间的转换,🤗 Datasets 提供了一个 **Dataset.set_format()** 功能。此功能可以通过仅更改输出格式的,轻松切换到另一种格式,而不会影响底层数据格式,即 Apache Arrow。格式化会在数据本身上进行。为了演示,让我们将数据集转换为 Pandas: + +```py +drug_dataset.set_format("pandas") +``` + +现在,当我们访问数据集的元素时,我们会得到一个 **pandas.DataFrame** 而不是字典: + +```py +drug_dataset["train"][:3] +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
patient_iddrugNameconditionreviewratingdateusefulCountreview_length
095260Guanfacineadhd"My son is halfway through his fourth week of Intuniv..."8.0April 27, 2010192141
192703Lybrelbirth control"I used to take another oral contraceptive, which had 21 pill cycle, and was very happy- very light periods, max 5 days, no other side effects..."5.0December 14, 200917134
2138000Ortho Evrabirth control"This is my first time using any form of birth control..."8.0November 3, 20151089
+ +让我们创建一个 **pandas.DataFrame** 来选择 **drug_dataset[train]** 的所有元素: + +```py +train_df = drug_dataset["train"][:] +``` + + + +🚨 在底层,`Dataset.set_format()` 改变了数据集的 `__getitem__()` dunder 方法的返回格式。 这意味着当我们想从 `"pandas"` 格式的 `Dataset` 中创建像 `train_df` 这样的新对象时,我们需要对整个数据集进行切片以获得 `pandas.DataFrame`。 无论输出格式如何,您都可以自己验证 `drug_dataset["train"]` 的类型依然还是 `Dataset`。 + + + + +从这里我们可以使用我们想要的所有 Pandas 功能。例如,我们可以通过花式链接来计算 **condition**类之间的分布 : + +```py +frequencies = ( + train_df["condition"] + .value_counts() + .to_frame() + .reset_index() + .rename(columns={"index": "condition", "condition": "frequency"}) +) +frequencies.head() +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
conditionfrequency
0birth control27655
1depression8023
2acne5209
3anxiety4991
4pain4744
+ + +一旦我们完成了 Pandas 分析,我们总是通过使用对象 **Dataset.from_pandas()**方法可以创建一个新的 **Dataset** 如下: + + +```py +from datasets import Dataset + +freq_dataset = Dataset.from_pandas(frequencies) +freq_dataset +``` + +```python out +Dataset({ + features: ['condition', 'frequency'], + num_rows: 819 +}) +``` + + + +✏️ **试试看!** 计算每种药物的平均评级并将结果存储在一个新的Dataset. + + + +我们对 🤗 Datasets中可用的各种预处理技术的介绍到此结束。在最后一部分,让我们创建一个验证集来准备用于训练分类器的数据集。在此之前,我们将输出格式 **drug_dataset** 从 **pandas**重置到 **arrow** : + +```python +drug_dataset.reset_format() +``` + +## 创建验证集 + +尽管我们有一个可以用于评估的测试集,但在开发过程中保持测试集不变并创建一个单独的验证集是一个很好的做法。一旦您对模型在测试集上的表现感到满意,您就可以对验证集进行最终的检查。此过程有助于降低您过拟合测试集并部署在现实世界数据上失败的模型的风险。 + +🤗 Datasets提供了一个基于**scikit-learn**的经典方法**Dataset.train_test_split()** .让我们用它把我们的训练集分成 **train** 和 **validation** (为了可以复现,我们将设置**seed**的值为一个常量): + +```py +drug_dataset_clean = drug_dataset["train"].train_test_split(train_size=0.8, seed=42) +# Rename the default "test" split to "validation" +drug_dataset_clean["validation"] = drug_dataset_clean.pop("test") +# Add the "test" set to our `DatasetDict` +drug_dataset_clean["test"] = drug_dataset["test"] +drug_dataset_clean +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 110811 + }) + validation: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 27703 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 46108 + }) +}) +``` + +太好了,我们现在已经准备好了一个数据集,可以用来训练一些模型了!在[第五节]](/course/chapter5/5)我们将向您展示如何将数据集上传到 Hugging Face Hub,但现在让我们查看在本地计算机上保存数据集的几种方法。 + +## 保存数据集 + + + +虽然 🤗 Datasets 会缓存每个下载的数据集和对它执行的操作,但有时你会想要将数据集保存到磁盘(例如,以防缓存被删除)。如下表所示,🤗 Datasets 提供了三个主要功能来以不同的格式保存您的数据集: + +| 数据格式 | 对应的方法 | +| :---------: | :--------------------: | +| Arrow | `Dataset.save_to_disk()` | +| CSV | `Dataset.to_csv()` | +| JSON | `Dataset.to_json()` | + +例如,让我们以 Arrow 格式保存我们清洗过的数据集: + +```py +drug_dataset_clean.save_to_disk("drug-reviews") +``` + +这将创建一个具有以下结构的目录: + +``` +drug-reviews/ +├── dataset_dict.json +├── test +│ ├── dataset.arrow +│ ├── dataset_info.json +│ └── state.json +├── train +│ ├── dataset.arrow +│ ├── dataset_info.json +│ ├── indices.arrow +│ └── state.json +└── validation + ├── dataset.arrow + ├── dataset_info.json + ├── indices.arrow + └── state.json +``` + +在那里我们可以看到每个部分.arrow表,以及一些元数据数据集信息.json和状态文件保存在一起.您可以将 Arrow 格式视为一个精美的列和行的表格,它针对构建处理和传输大型数据集的高性能应用程序进行了优化。 + +保存数据集后,我们可以使用 **load_from_disk()** 功能从磁盘读取数据如下: + +```py +from datasets import load_from_disk + +drug_dataset_reloaded = load_from_disk("drug-reviews") +drug_dataset_reloaded +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 110811 + }) + validation: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 27703 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 46108 + }) +}) +``` + +对于 CSV 和 JSON 格式,我们必须将每个部分存储为单独的文件。一种方法是迭代**DatasetDict**中的键和值 : + +```py +for split, dataset in drug_dataset_clean.items(): + dataset.to_json(f"drug-reviews-{split}.jsonl") +``` + +这将保存每个拆分都是[JSON的标准格式](https://jsonlines.org),其中数据集中的每一行都存储为一行 JSON。这是第一个示例: + +```py +!head -n 1 drug-reviews-train.jsonl +``` + +```python out +{"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} +``` + +然后我们可以使用[第二节](/course/chapter5/2)学过的技术加载 JSON 文件如下: + +```py +data_files = { + "train": "drug-reviews-train.jsonl", + "validation": "drug-reviews-validation.jsonl", + "test": "drug-reviews-test.jsonl", +} +drug_dataset_reloaded = load_dataset("json", data_files=data_files) +``` + +这就是我们探索 🤗 Datasets 的旅程!现在我们有了一个清洗过的数据集,以下是您可以尝试的一些想法: + +1. 使用[第3章](/course/chapter3)的技术来训练一个分类器,它可以根据药物评论预测病人的情况。 +2. 使用 [Chapter 1](/course/chapter1) 中的“summarization”管道生成评论摘要。 + +接下来,我们将看看 🤗 Datasets如何使您能够在不撑爆笔记本电脑内存的情况下处理庞大的数据集! \ No newline at end of file diff --git a/chapters/zh-CN/chapter5/4.mdx b/chapters/zh-CN/chapter5/4.mdx new file mode 100644 index 000000000..d8224b3bd --- /dev/null +++ b/chapters/zh-CN/chapter5/4.mdx @@ -0,0 +1,287 @@ +# 大数据? 🤗 Datasets 来救援! + + + + +如今,不难发现我们经常使用数GB的数据集, 特别是如果你打算从头开始预训练像 BERT 或者 GPT-2 这样的转换器。 在这种情况下, _加载_ 数据集就是一个挑战。例如, 用于预训练 GPT-2 的 WebText 语料库包含超过 800 万个文档和 40 GB 的文本 -- 将其加载到笔记本电脑的 RAM 中可能会让它抓狂! + +幸运的是, 🤗 Datasets 旨在克服这些限制。它通过将数据集作为内存映射文件来处理,并通过在语料库中流化条目来摆脱硬盘限制, 从而使你避免内存管理问题。 + + + +在本节中, 我们将探索🤗 Datasets 的特性。它有一个称为 [the Pile](https://pile.eleuther.ai)的825 GB的语料库。 让我们开始吧! + +## 什么是Pile? + +The Pile 是由[EleutherAI](https://www.eleuther.ai)创建的一个英语文本语料库, 用于训练大规模语言模型。它包含各种各样的数据集, 涵盖科学文章, GitHub 代码库以及过滤的Web文本。训练语料库在[14 GB chunks](https://mystic.the-eye.eu/public/AI/pile/), 并且你也可以下载几个[单独的组件](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/)。 让我们先来看看 PubMed Abstracts 数据集, 它是[PubMed](https://pubmed.ncbi.nlm.nih.gov/)上的1500万篇生物医学出版物的摘要的语料库。 数据集采用[JSON行格式](https://jsonlines.org) 并使用`zstandard`库进行压缩, 所以我们首先需要先安装`zstandard`库: + +```py +!pip install zstandard +``` + +接下来, 我们可以使用[第二节](/course/chapter5/2)中所学的加载远程数据集的方法加载数据集: + +```py +from datasets import load_dataset + +# This takes a few minutes to run, so go grab a tea or coffee while you wait :) +data_files = "https://mystic.the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst" +pubmed_dataset = load_dataset("json", data_files=data_files, split="train") +pubmed_dataset +``` + +```python out +Dataset({ + features: ['meta', 'text'], + num_rows: 15518009 +}) +``` + +我们可以看到我们的数据集中有 15,518,009 行和 2 列 -- 这是非常多的! + + + +✎ 默认情况下, 🤗 Datasets 会自动解压加载数据集所需的文件。 如果你想保留硬盘空间, 你可以传递 `DownloadConfig(delete_extracted=True)` 到 `download_config` 的 `load_dataset()`参数. 有关更多详细信息, 请参阅文档](https://huggingface.co/docs/datasets/package_reference/builder_classes.html?#datasets.utils.DownloadConfig)。 + + + +让我们看看数据集的第一个元素的内容: + +```py +pubmed_dataset[0] +``` + +```python out +{'meta': {'pmid': 11409574, 'language': 'eng'}, + '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 ...'} +``` + +可以看到, 这看起来像是医学文章的摘要。 现在让我们看看我们使用了RAM的多少存储空间来加载数据集! + +## 内存映射的魔力 + +在 Python 中测量内存使用情况的一个简单的方法是使用[`psutil`](https://psutil.readthedocs.io/en/latest/)库,它可以使用 `pip`安装, 如下所示: + +```python +!pip install psutil +``` + +它提供了一个 `Process` 类,这个类允许我们检查当前进程的内存使用情况, 如下所示: + +```py +import psutil + +# Process.memory_info is expressed in bytes, so convert to megabytes +print(f"RAM used: {psutil.Process().memory_info().rss / (1024 * 1024):.2f} MB") +``` + +```python out +RAM used: 5678.33 MB +``` + +这里的`rss`属性是指 _常驻集_ 的大小, 它是进程在RAM中占用的内存比例。 这个测量结果也包括了 Python 编译器和我们加载的库所使用的内存, 所以实际上用于加载数据集的内存会更小一些。为了比较, 让我们使用 `dataset_size` 属性看看数据集在磁盘上有多大。 由于结果像之前一样用字节表示, 我们需要手动将其转换为GB: + +```py +print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") +size_gb = pubmed_dataset.dataset_size / (1024**3) +print(f"Dataset size (cache file) : {size_gb:.2f} GB") +``` + +```python out +Number of files in dataset : 20979437051 +Dataset size (cache file) : 19.54 GB +``` + +非常棒 -- 尽管它将近20GB, 但我们能够占用很少的RAM空间加载和访问数据集! + + + +✏️ **试试看!** 从[subsets](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/)中选择一个大于你的笔记本或者台式机的RAM大小的子集, 用 🤗 Datasets加载这个数据集, 并且测量RAM的使用量。 请注意, 要获得准确的测量结果, 你需要在另一个进程中执行这个操作。你可以在 [the Pile paper](https://arxiv.org/abs/2101.00027)的表一中找到每个子集解压后的大小。 + + + +如果你熟悉 Pandas, 这个结果可能会让人感到很意外。因为 Wes Kinney 的著名的[经验法则](https://wesmckinney.com/blog/apache-arrow-pandas-internals/) 是你需要的RAM应该是数据集的大小的5倍到10倍。 那么 🤗 Datasets 是如何解决这个内存管理问题的呢? 🤗 Datasets 将每一个数据集看作一个[内存映射文件](https://en.wikipedia.org/wiki/Memory-mapped_file), 它提供了RAM和文件系统存储之间的映射, 该映射允许库访问和操作数据集的元素, 而且无需将其完全加载到内存中。 + +内存映射文件也一个在多个进程之间共享, 这使得像 `Dataset.map()`之类的方法可以并行化, 并且无需移动或者赋值数据集。在底层, 这些功能都是由[Apache Arrow](https://arrow.apache.org)内存格式和[`pyarrow`](https://arrow.apache.org/docs/python/index.html)库提供的支持, 使得数据加载和处理速度快如闪电。 (更多有关Apache Arrow的详细信息以及与Pandas的比较, 请查看[Dejan Simic's blog post](https://towardsdatascience.com/apache-arrow-read-dataframe-with-zero-memory-69634092b1a).) 为了更清晰地看到这个过程, 让我们通过迭代PubMed Abstracts数据集中的所有元素来运行一个速度测试小程序: + +```py +import timeit + +code_snippet = """batch_size = 1000 + +for idx in range(0, len(pubmed_dataset), batch_size): + _ = pubmed_dataset[idx:idx + batch_size] +""" + +time = timeit.timeit(stmt=code_snippet, number=1, globals=globals()) +print( + f"Iterated over {len(pubmed_dataset)} examples (about {size_gb:.1f} GB) in " + f"{time:.1f}s, i.e. {size_gb/time:.3f} GB/s" +) +``` + +```python out +'Iterated over 15518009 examples (about 19.5 GB) in 64.2s, i.e. 0.304 GB/s' +``` + +这里我们使用了 Python的 `timeit` 模块来测量执行 `code_snippet`所耗的时间。 你通常能以十分之几GB/s到几GB/s的速度迭代数据集。通过上述的方法就已经能够解决大多数大数据集加载的限制, 但是有时候你不得不使用一个很大的数据集, 它甚至都不能存储在笔记本电脑的硬盘上。例如, 如果我们尝试下载整个 Pile, 我们需要825GB的可用磁盘空间! 为了处理这种情况, 🤗 Datasets 提供了一个流式功能, 这个功能允许我们动态下载和访问元素, 并且不需要下载整个数据集。让我们来看看这个功能是如何工作的。 + + + +💡在 Jupyter 笔记中你还可以使用[`%%timeit` magic function](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit)为单元格计时。 + + + +## 流式数据集 + +要使用数据集流, 你只需要将 `streaming=True` 参数传递给 `load_dataset()` 函数。接下来, 让我们再次加载 PubMed Abstracts 数据集, 但是采用流模式: + +```py +pubmed_dataset_streamed = load_dataset( + "json", data_files=data_files, split="train", streaming=True +) +``` + +与我们在本章其他地方遇到的熟悉的 `Dataset` 不同, `streaming=True` 返回的对象是一个 `IterableDataset`。 顾名思义, 要访问 `IterableDataset` , 我们需要迭代它。我们可以按照如下方式访问流式数据集的第一个元素: + + +```py +next(iter(pubmed_dataset_streamed)) +``` + +```python out +{'meta': {'pmid': 11409574, 'language': 'eng'}, + '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()`进行动态处理。该过程与我们在[第三章](/course/chapter3)中标记数据集的过程完全相同, 唯一的区别是输出是逐个返回的: + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased") +tokenized_dataset = pubmed_dataset_streamed.map(lambda x: tokenizer(x["text"])) +next(iter(tokenized_dataset)) +``` + +```python out +{'input_ids': [101, 4958, 5178, 4328, 6779, ...], 'attention_mask': [1, 1, 1, 1, 1, ...]} +``` + + + +💡 你可以传递 `batched=True` 来通过流式加速标记化, 如同我们在上一节看到的那样。它将逐批处理示例; 默认的批量大小为 1,000, 可以使用 `batch_size` 参数指定批量大小。 + + + +你还可以使用 `IterableDataset.shuffle()` 打乱流式数据集, 但与 `Dataset.shuffle()` 不同的是这只会打乱预定义 `buffer_size` 中的元素: + +```py +shuffled_dataset = pubmed_dataset_streamed.shuffle(buffer_size=10_000, seed=42) +next(iter(shuffled_dataset)) +``` + +```python out +{'meta': {'pmid': 11410799, 'language': 'eng'}, + 'text': 'Randomized study of dose or schedule modification of granulocyte colony-stimulating factor in platinum-based chemotherapy for elderly patients with lung cancer ...'} +``` + +在这个示例中, 我们从缓冲区的前 10,000 个示例中随机选择了一个示例。一旦访问了一个示例, 它在缓冲区中的位置就会被语料库中的下一个示例填充 (即, 上述案例中的第 10,001个示例)。你还可以使用 `IterableDataset.take()` 和 `IterableDataset.skip()` 函数从流式数据集中选择元素, 它的作用类似于 `Dataset.select()`。例如, 要选择 PubMed Abstracts 数据集的前5个示例, 我们可以执行以下操作: + +```py +dataset_head = pubmed_dataset_streamed.take(5) +list(dataset_head) +``` + +```python out +[{'meta': {'pmid': 11409574, 'language': 'eng'}, + 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'}, + {'meta': {'pmid': 11409575, 'language': 'eng'}, + 'text': 'Clinical signs of hypoxaemia in children with acute lower respiratory infection: indicators of oxygen therapy ...'}, + {'meta': {'pmid': 11409576, 'language': 'eng'}, + 'text': "Hypoxaemia in children with severe pneumonia in Papua New Guinea ..."}, + {'meta': {'pmid': 11409577, 'language': 'eng'}, + 'text': 'Oxygen concentrators and cylinders ...'}, + {'meta': {'pmid': 11409578, 'language': 'eng'}, + 'text': 'Oxygen supply in rural africa: a personal experience ...'}] +``` + +同样, 你可以使用 `IterableDataset.skip()` 函数将打乱的数据集拆分为训练集和验证集, 如下所示: + +```py +# Skip the first 1,000 examples and include the rest in the training set +train_dataset = shuffled_dataset.skip(1000) +# Take the first 1,000 examples for the validation set +validation_dataset = shuffled_dataset.take(1000) +``` + +让我们用一个常见的任务来进行我们对数据集流的最后探索: 将多个数据集组合在一起创建一个心得语料库。 🤗 Datasets 提供了一个 `interleave_datasets()` 函数, 它将一个 `IterableDataset` 对象列表组合为单个的 `IterableDataset`, 其中新数据集的元素是通过在列表中的对象交替获得的。当你试图组合大型数据集时, 这个函数特别有用, 让我们通过下面这个例子来试着组合 Pile的自由法律数据集,它是来自美国法院的51 GB的法律意见数据集: + +```py +law_dataset_streamed = load_dataset( + "json", + data_files="https://mystic.the-eye.eu/public/AI/pile_preliminary_components/FreeLaw_Opinions.jsonl.zst", + split="train", + streaming=True, +) +next(iter(law_dataset_streamed)) +``` + +```python out +{'meta': {'case_ID': '110921.json', + 'case_jurisdiction': 'scotus.tar.gz', + 'date_created': '2010-04-28T17:12:49Z'}, + 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'} +``` + +这个数据集足够大, 可以对大多数笔记本电脑的RAM有足够的压力, 但是我们已经能够毫不费力地加载和访问它! 现在我们使用 `interleave_datasets()` 函数加载来自 FreeLaw 和 PubMed Abstracts 的数据集: + +```py +from itertools import islice +from datasets import interleave_datasets + +combined_dataset = interleave_datasets([pubmed_dataset_streamed, law_dataset_streamed]) +list(islice(combined_dataset, 2)) +``` + +```python out +[{'meta': {'pmid': 11409574, 'language': 'eng'}, + 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'}, + {'meta': {'case_ID': '110921.json', + 'case_jurisdiction': 'scotus.tar.gz', + 'date_created': '2010-04-28T17:12:49Z'}, + 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'}] +``` + +这里我们使用了来自Python的 `itertools` 模块的 `islice()` 函数从合并的数据集中选择前两个示例, 并且我们可以看到它们实际上就是两个源数据集中的前两个示例拼在一起形成的: + +最后, 如果你想流式传输整个825GB的 Pile, 你可以按照如下方式获取所有准备好的文件: + +```py +base_url = "https://mystic.the-eye.eu/public/AI/pile/" +data_files = { + "train": [base_url + "train/" + f"{idx:02d}.jsonl.zst" for idx in range(30)], + "validation": base_url + "val.jsonl.zst", + "test": base_url + "test.jsonl.zst", +} +pile_dataset = load_dataset("json", data_files=data_files, streaming=True) +next(iter(pile_dataset["train"])) +``` + +```python out +{'meta': {'pile_set_name': 'Pile-CC'}, + 'text': 'It is done, and submitted. You can play “Survival of the Tastiest” on Android, and on the web...'} +``` + + + +✏️ **试试看!** 使用像[`mc4`](https://huggingface.co/datasets/mc4) 或者 [`oscar`](https://huggingface.co/datasets/oscar)这样的大型 Common Crawl 语料库来创建一个流式多语言数据集, 该数据集代表你选择的国家/地区语言的口语比例。例如, 瑞士的四种民族语言分别是德语、法语、意大利语和罗曼什语, 因此你可以尝试根据根据口语比例对Oscar子集进行采用来创建瑞士语料库。 + + + +你现在拥有加载和处理各种类型和大小的数据集的所需的所有工具 -- 但是除非你非常幸运, 否则在你的NLP之旅中会有一个难题, 你将不得不创建一个数据集来解决手头的问题。这就是下一节的主题! diff --git a/chapters/zh-CN/chapter5/5.mdx b/chapters/zh-CN/chapter5/5.mdx new file mode 100644 index 000000000..b97bb7542 --- /dev/null +++ b/chapters/zh-CN/chapter5/5.mdx @@ -0,0 +1,461 @@ +# 创建自己的数据集 + + + +有时,不存在合适的数据集适用于您构建 NLP 应用,因此您需要自己创建。在本节中,我们将向您展示如何创建一个[GitHub issues](https://github.com/features/issues/)的语料库,GitHub issues通常用于跟踪 GitHub 存储库中的错误或功能。该语料库可用于各种目的,包括: +* 探索关闭未解决的issue或拉取请求需要多长时间 +* 训练一个*多标签分类器*可以根据issue的描述(例如,“错误”、“增强”或“issue”)用元数据标记issue +* 创建语义搜索引擎以查找与用户查询匹配的issue + +在这里,我们将专注于创建语料库,在下一节中,我们将探索语义搜索。我们将使用与流行的开源项目相关的 GitHub issue:🤗 Datasets!接下来让我们看看如何获取数据并探索这些issue中包含的信息。 + +## 获取数据 + +您可以浏览 🤗 Datasets 中的所有issue[Issues tab](https://github.com/huggingface/datasets/issues).如以下屏幕截图所示,在撰写本文时,有 331 个未解决的issue和 668 个已关闭的issue。 + +
+The GitHub issues associated with 🤗 Datasets. +
+ +如果您单击其中一个issue,您会发现它包含一个标题、一个描述和一组表征该issue的标签。下面的屏幕截图显示了一个示例. + +
+A typical GitHub issue in the 🤗 Datasets repository. +
+ +要下载所有存储库的issue,我们将使用[GitHub REST API](https://docs.github.com/en/rest)投票[Issues endpoint](https://docs.github.com/en/rest/reference/issues#list-repository-issues).此节点返回一个 JSON 对象列表,每个对象包含大量字段,其中包括标题和描述以及有关issue状态的元数据等。 + +下载issue的一种便捷方式是通过 **requests** 库,这是用 Python 中发出 HTTP 请求的标准方式。您可以通过运行以下的代码来安装库: + +```python +!pip install requests +``` + +安装库后,您通过调用 **requests.get()** 功能来获取**Issues**节点。例如,您可以运行以下命令来获取第一页上的第一个Issues: + +```py +import requests + +url = "https://api.github.com/repos/huggingface/datasets/issues?page=1&per_page=1" +response = requests.get(url) +``` + +这 **response** 对象包含很多关于请求的有用信息,包括 HTTP 状态码: + +```py +response.status_code +``` + +```python out +200 +``` + +其中一个状态码 **200** 表示请求成功(您可以[在这里](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes)找到可能的 HTTP 状态代码列表)。然而,我们真正感兴趣的是有效的信息,由于我们知道我们的issues是 JSON 格式,让我们按如下方式查看所有的信息: + +```py +response.json() +``` + +```python out +[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/2792', + 'repository_url': 'https://api.github.com/repos/huggingface/datasets', + 'labels_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/labels{/name}', + 'comments_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/comments', + 'events_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/events', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792', + 'id': 968650274, + 'node_id': 'MDExOlB1bGxSZXF1ZXN0NzEwNzUyMjc0', + 'number': 2792, + 'title': 'Update GooAQ', + 'user': {'login': 'bhavitvyamalik', + 'id': 19718818, + 'node_id': 'MDQ6VXNlcjE5NzE4ODE4', + 'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4', + 'gravatar_id': '', + 'url': 'https://api.github.com/users/bhavitvyamalik', + 'html_url': 'https://github.com/bhavitvyamalik', + 'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers', + 'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}', + 'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}', + 'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}', + 'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions', + 'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs', + 'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos', + 'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}', + 'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events', + 'type': 'User', + 'site_admin': False}, + 'labels': [], + 'state': 'open', + 'locked': False, + 'assignee': None, + 'assignees': [], + 'milestone': None, + 'comments': 1, + 'created_at': '2021-08-12T11:40:18Z', + 'updated_at': '2021-08-12T12:31:17Z', + 'closed_at': None, + 'author_association': 'CONTRIBUTOR', + 'active_lock_reason': None, + 'pull_request': {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/2792', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792', + 'diff_url': 'https://github.com/huggingface/datasets/pull/2792.diff', + 'patch_url': 'https://github.com/huggingface/datasets/pull/2792.patch'}, + 'body': '[GooAQ](https://github.com/allenai/gooaq) dataset was recently updated after splits were added for the same. This PR contains new updated GooAQ with train/val/test splits and updated README as well.', + 'performed_via_github_app': None}] +``` + +哇,这是很多信息!我们可以看到有用的字段,例如 **标题** , **内容** , **参与的成员**, **issue的描述信息**,以及打开issue的GitHub 用户的信息。 + + + +✏️ 试试看!单击上面 JSON 中的几个 URL,以了解每个 GitHub issue中我url链接到的实际的地址。 + + +如 GitHub[文档](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting) 中所述,未经身份验证的请求限制为每小时 60 个请求。虽然你可以增加 **per_page** 查询参数以减少您发出的请求数量,您仍然会遭到任何超过几千个issue的存储库的速率限制。因此,您应该关注 GitHub 的[创建个人身份令牌](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token),创建一个个人访问令牌这样您就可以将速率限制提高到每小时 5,000 个请求。获得令牌后,您可以将其包含在请求标头中: + +```py +GITHUB_TOKEN = xxx # Copy your GitHub token here +headers = {"Authorization": f"token {GITHUB_TOKEN}"} +``` + + + +⚠️ 不要与陌生人共享存在GITHUB令牌的笔记本。我们建议您在使用完后将GITHUB令牌删除,以避免意外泄漏此信息。一个更好的做法是,将令牌存储在.env文件中,并使用 [`python-dotenv` library](https://github.com/theskumar/python-dotenv) 为您自动将其作为环境变量加载。 + + + +现在我们有了访问令牌,让我们创建一个可以从 GitHub 存储库下载所有issue的函数: + +```py +import time +import math +from pathlib import Path +import pandas as pd +from tqdm.notebook import tqdm + + +def fetch_issues( + owner="huggingface", + repo="datasets", + num_issues=10_000, + rate_limit=5_000, + issues_path=Path("."), +): + if not issues_path.is_dir(): + issues_path.mkdir(exist_ok=True) + + batch = [] + all_issues = [] + per_page = 100 # Number of issues to return per page + num_pages = math.ceil(num_issues / per_page) + base_url = "https://api.github.com/repos" + + for page in tqdm(range(num_pages)): + # Query with state=all to get both open and closed issues + query = f"issues?page={page}&per_page={per_page}&state=all" + issues = requests.get(f"{base_url}/{owner}/{repo}/{query}", headers=headers) + batch.extend(issues.json()) + + if len(batch) > rate_limit and len(all_issues) < num_issues: + all_issues.extend(batch) + batch = [] # Flush batch for next time period + print(f"Reached GitHub rate limit. Sleeping for one hour ...") + time.sleep(60 * 60 + 1) + + all_issues.extend(batch) + df = pd.DataFrame.from_records(all_issues) + df.to_json(f"{issues_path}/{repo}-issues.jsonl", orient="records", lines=True) + print( + f"Downloaded all the issues for {repo}! Dataset stored at {issues_path}/{repo}-issues.jsonl" + ) +``` + +现在我们可以调用 **fetch_issues()** 批量下载所有issue,避免超过GitHub每小时的请求数限制;结果将存储在repository_name-issues.jsonl文件,其中每一行都是一个 JSON 对象,代表一个issue。让我们使用这个函数从 🤗 Datasets中抓取所有issue: + +```py +# Depending on your internet connection, this can take several minutes to run... +fetch_issues() +``` + +下载issue后,我们可以使用我们 [section 2](/course/chaper5/2)新学会的方法在本地加载它们: + +```py +issues_dataset = load_dataset("json", data_files="datasets-issues.jsonl", split="train") +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'timeline_url', 'performed_via_github_app'], + num_rows: 3019 +}) +``` + +太好了,我们已经从头开始创建了我们的第一个数据集!但是为什么会有几千个issue,而🤗 Datasets存储库中的[Issues 选项卡](https://github.com/huggingface/datasets/issues)总共却只显示了大约 1,000 个issue🤔?如 GitHub [文档](https://docs.github.com/en/rest/reference/issues#list-issues-assigned-to-the-authenticated-user)中所述,那是因为我们也下载了所有的拉取请求: + +>Git Hub的REST API v3认为每个pull请求都是一个issue,但并不是每个issue都是一个pull请求。因此,“Issues”节点可能在响应中同时返回issue和拉取请求。你可以通过pull_request 的 key来辨别pull请求。请注意,从“Issues”节点返回的pull请求的id将是一个issue id。 + +由于issue和pull request的内容有很大的不同,我们先做一些小的预处理,让我们能够区分它们。 + +## 清理数据 + +上面来自 GitHub 文档的片段告诉我们, **pull_request** 列可用于区分issue和拉取请求。让我们随机挑选一些样本,看看有什么不同。我们将使用在[第三节](/course/chapter5/3), 学习的方法,使用 **Dataset.shuffle()** 和 **Dataset.select()** 抽取一个随机样本,然后将 **html_url** 和 **pull_request** 列使用zip函数打包,以便我们可以比较各种 URL: + +```py +sample = issues_dataset.shuffle(seed=666).select(range(3)) + +# Print out the URL and pull request entries +for url, pr in zip(sample["html_url"], sample["pull_request"]): + print(f">> URL: {url}") + print(f">> Pull request: {pr}\n") +``` + +```python out +>> URL: https://github.com/huggingface/datasets/pull/850 +>> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/850', 'html_url': 'https://github.com/huggingface/datasets/pull/850', 'diff_url': 'https://github.com/huggingface/datasets/pull/850.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/850.patch'} + +>> URL: https://github.com/huggingface/datasets/issues/2773 +>> Pull request: None + +>> URL: https://github.com/huggingface/datasets/pull/783 +>> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/783', 'html_url': 'https://github.com/huggingface/datasets/pull/783', 'diff_url': 'https://github.com/huggingface/datasets/pull/783.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/783.patch'} +``` + +这里我们可以看到,每个pull请求都与各种url相关联,而普通issue只有一个None条目。我们可以使用这一点不同来创建一个新的is_pull_request列通过检查pull_request字段是否为None来区分它们: + +```py +issues_dataset = issues_dataset.map( + lambda x: {"is_pull_request": False if x["pull_request"] is None else True} +) +``` + + + +✏️ 试试看!计算在 🤗 Datasets中解决issue所需的平均时间。您可能会发现 Dataset.filter()函数对于过滤拉取请求和未解决的issue很有用,并且您可以使用Dataset.set_format()函数将数据集转换为DataFrame,以便您可以轻松地按照需求修改创建和关闭的时间的格式(以时间戳格式)。 + + + +尽管我们可以通过删除或重命名某些列来进一步清理数据集,但在此阶段尽可能保持数据集“原始”状态通常是一个很好的做法,以便它可以在多个应用程序中轻松使用。在我们将数据集推送到 Hugging Face Hub 之前,让我们再添加一些缺少的数据:与每个issue和拉取请求相关的评论。我们接下来将添加它们——你猜对了——我们将依然使用GitHub REST API! + +## 扩充数据集 + +如以下屏幕截图所示,与issue或拉取请求相关的评论提供了丰富的信息,特别是如果我们有兴趣构建搜索引擎来回答用户对这个项目的疑问。 + +
+Comments associated with an issue about 🤗 Datasets. +
+ +GitHub REST API 提供了一个 [评论节点](https://docs.github.com/en/rest/reference/issues#list-issue-comments) 返回与issue编号相关的所有评论。让我们测试节点以查看它返回的内容: + +```py +issue_number = 2792 +url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments" +response = requests.get(url, headers=headers) +response.json() +``` + +```python out +[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/comments/897594128', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792#issuecomment-897594128', + 'issue_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792', + 'id': 897594128, + 'node_id': 'IC_kwDODunzps41gDMQ', + 'user': {'login': 'bhavitvyamalik', + 'id': 19718818, + 'node_id': 'MDQ6VXNlcjE5NzE4ODE4', + 'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4', + 'gravatar_id': '', + 'url': 'https://api.github.com/users/bhavitvyamalik', + 'html_url': 'https://github.com/bhavitvyamalik', + 'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers', + 'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}', + 'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}', + 'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}', + 'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions', + 'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs', + 'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos', + 'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}', + 'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events', + 'type': 'User', + 'site_admin': False}, + 'created_at': '2021-08-12T12:21:52Z', + 'updated_at': '2021-08-12T12:31:17Z', + 'author_association': 'CONTRIBUTOR', + 'body': "@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?", + 'performed_via_github_app': None}] +``` + +我们可以看到注释存储在body字段中,所以让我们编写一个简单的函数,通过在response.json()中为每个元素挑选body内容来返回与某个issue相关的所有评论: + +```py +def get_comments(issue_number): + url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments" + response = requests.get(url, headers=headers) + return [r["body"] for r in response.json()] + + +# Test our function works as expected +get_comments(2792) +``` + +```python out +["@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?"] +``` + +这看起来不错,所以让我们使用 **Dataset.map()** 方法在我们数据集中每个issue的添加一个**comments**列: + +```py +# Depending on your internet connection, this can take a few minutes... +issues_with_comments_dataset = issues_dataset.map( + lambda x: {"comments": get_comments(x["number"])} +) +``` + +最后一步是将增强数据集与原始数据保存在一起,以便我们可以将它们都推送到 Hub: + +```py +issues_with_comments_dataset.to_json("issues-datasets-with-comments.jsonl") +``` + +## 将数据集上传到 Hugging Face Hub + + + +现在我们有了我们的增强数据集,是时候将它推送到 Hub 并且与社区共享它!要上传数据集,我们将使用[🤗 Hub 库](https://github.com/huggingface/huggingface_hub),它允许我们通过 Python API 与 Hugging Face Hub 进行交互。 🤗 Hub 预装了🤗 Transformers,所以我们可以直接使用它。例如,我们可以使用 **list_datasets()** 获取有关当前托管在 Hub 上的所有公共数据集的信息的函数: + +```py +from huggingface_hub import list_datasets + +all_datasets = list_datasets() +print(f"Number of datasets on Hub: {len(all_datasets)}") +print(all_datasets[0]) +``` + +```python out +Number of datasets on Hub: 1487 +Dataset Name: acronym_identification, Tags: ['annotations_creators:expert-generated', 'language_creators:found', 'languages:en', 'licenses:mit', 'multilinguality:monolingual', 'size_categories:10K + +✏️ 试试看!使用您的 Hugging Face Hub 用户名和密码获取令牌并创建一个名为 github-issues.请记住永远不要将您的凭据保存在 Colab 或任何其他存储库中,因为这些信息可能会被不法分子利用。 + +
+ +接下来,让我们将存储库从 Hub 克隆到我们的本地机器,并将我们的数据集文件复制到其中。 🤗 Hub 提供了一个方便的 **Repository** 类,它包含许多常见 Git 命令的类,因此要克隆远程存储库,我们只需要提供我们要克隆的 URL 和本地路径: + +```py +from huggingface_hub import Repository + +repo = Repository(local_dir="github-issues", clone_from=repo_url) +!cp datasets-issues-with-comments.jsonl github-issues/ +``` + +默认情况下,使用Git LFS跟踪各种文件扩展名(如.bin、.gz和.zip),以便在同一Git工作流中对大型文件进行版本控制。您可以在存储库的.gitattributes文件找到跟踪文件扩展名的列表。要在列表中包含JSON行格式,我们可以运行以下命令: + +```py +repo.lfs_track("*.jsonl") +``` + +然后我们可以使用 **Repository.push_to_hub()** 将数据集推送到 Hub: + +```py +repo.push_to_hub() +``` + +如果我们导航到包含在 **repo_url** ,我们现在应该看到我们的数据集文件已经上传。 + +
+Our dataset repository on the Hugging Face Hub. +
+ +从这里,任何人都可以通过简单地提供来下载数据集 **load_dataset()** 以存储库 ID 作为 **path** 争论: + +```py +remote_dataset = load_dataset("lewtun/github-issues", split="train") +remote_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 2855 +}) +``` + +很酷,我们已经将我们的数据集推送到 Hub,其他人可以使用它!只剩下一件重要的事情要做:添加一个数据卡这解释了语料库是如何创建的,并为使用数据集的其他提供一些其他有用的信息。 + + + +💡 您还可以使用一些 Git 魔法直接从终端将数据集上传到 Hugging Face Hub。有关如何执行此操作的详细信息,请参阅 [🤗 Datasets guide](https://huggingface.co/docs/datasets/share.html#add-a-community-dataset) 指南。 + + + +## 创建数据集卡片 + +有据可查的数据集更有可能对其他人(包括你未来的自己!)有用,因为它们提供了上下文,使用户能够决定数据集是否与他们的任务相关,并评估任何潜在的偏见或与使用相关的风险。在 Hugging Face Hub 上,此信息存储在每个数据集存储库的自述文件文件。在创建此文件之前,您应该执行两个主要步骤: + +1. 使用[数据集标签应用程序](https://huggingface.co/datasets/tagging/) 创建YAML格式的元数据标签。这些标签用于各种各样的搜索功能,并确保您的数据集可以很容易地被社区成员找到。因为我们已经在这里创建了一个自定义数据集,所以您需要克隆数据集标签存储库并在本地运行应用程序。它的界面是这样的: + +
+The `datasets-tagging` interface. +
+ +2.阅读[🤗 Datasets guide](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) 关于创建信息性数据集卡片的指南,并将其作为模板使用。 + +您可以创建自述文件文件直接在Hub上,你可以在里面找到模板数据集卡片 **lewtun/github-issues** 数据集存储库。填写好的数据集卡片的屏幕截图如下所示。! + +
+A dataset card. +
+ + + +✏️试试看!使用应用程序和 [🤗 Datasets guide](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) 指南来完成 GitHub issue数据集的 README.md 文件。 + + + +很好! 我们在本节中看到,创建一个好的数据集可能非常复杂,但幸运的是,将其上传并与社区共享会很容易实现。在下一节中,我们将使用我们的新数据集创建一个带有 🤗 Datasets的语义搜索引擎,该数据集可以将issue与最相关的issue和评论进行匹配。 + + + +✏️ 试试看!按照我们在本节中采取的步骤为您最喜欢的开源库创建一个 GitHub issue数据集(当然,选择 🤗 Datasets以外的其他东西!)。对于奖励积分,微调多标签分类器以预测该领域中存在的标签。 + + + diff --git a/chapters/zh-CN/chapter5/6.mdx b/chapters/zh-CN/chapter5/6.mdx new file mode 100644 index 000000000..4e6411a98 --- /dev/null +++ b/chapters/zh-CN/chapter5/6.mdx @@ -0,0 +1,526 @@ + + +# 使用 FAISS 进行语义搜索 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +在[第五小节](/course/chapter5/5), 我们从 🤗 Datasets 存储库创建了一个包含 GitHub 问题和评论的数据集。在本节中,我们将使用这些信息来构建一个搜索引擎,它可以帮助我们找到这个库最紧迫问题的答案! + + + +## 使用嵌入进行语义搜索 + +正如我们在[第一章](/course/chapter1),学习的, 基于 Transformer 的语言模型会将文本中的每个标记转换为嵌入向量.事实证明,可以“汇集”各个嵌入向量来创建整个句子、段落或文档(在某些情况下)的向量表示。然后,通过计算每个嵌入之间的点积相似度(或其他一些相似度度量)并返回相似度最大的文档,这些嵌入可用于在语料库中找到相似的文档。在本节中,我们将使用嵌入来开发语义搜索引擎。与基于将查询中的关键字的传统方法相比,这些搜索引擎具有多种优势。 + +
+Semantic search. + +
+ +## ## 加载和准备数据集 + +我们需要做的第一件事是下载我们的 GitHub 问题数据集,所以让我们使用 🤗 Hub 库来解析我们的文件在 Hugging Face Hub 上存储的数据: + +```py +from huggingface_hub import hf_hub_url + +data_files = hf_hub_url( + repo_id="lewtun/github-issues", + filename="datasets-issues-with-comments.jsonl", + repo_type="dataset", +) +``` + +将 URL 存储在 **data_files** ,然后我们可以使用[第二小节](/course/chapter5/2)介绍的方法加载远程数据集: + +```py +from datasets import load_dataset + +issues_dataset = load_dataset("json", data_files=data_files, split="train") +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 2855 +}) +``` + +这里我们在load_dataset()中使用了默认的训练集分割,所以它返回一个数据集而不是数据集字典。第一项任务是过滤掉pull请求,因为这些请求很少用于回答用户提出的问题,而且会给我们的搜索引擎带来噪声。现在应该很熟悉了,我们可以使用dataset.filter()函数来排除数据集中的这些行。同时,让我们也过滤掉没有注释的行,因为这些行不会是用户提问的答案: + +```py +issues_dataset = issues_dataset.filter( + lambda x: (x["is_pull_request"] == False and len(x["comments"]) > 0) +) +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 771 +}) +``` + +我们可以看到我们的数据集中有很多列,其中大部分我们不需要构建我们的搜索引擎。从搜索的角度来看,信息量最大的列是 **title** , **body** , 和 **comments** ,而 **html_url** 为我们提供了一个回到源问题的链接。让我们使用 **Dataset.remove_columns()** 删除其余部分的功能: + +```py +columns = issues_dataset.column_names +columns_to_keep = ["title", "body", "html_url", "comments"] +columns_to_remove = set(columns_to_keep).symmetric_difference(columns) +issues_dataset = issues_dataset.remove_columns(columns_to_remove) +issues_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body'], + num_rows: 771 +}) +``` + +为了创建我们的嵌入,我们将用问题的标题和正文来扩充每条评论,因为这些字段通常包含有用的上下文信息。因为我们的 **comments** 列当前是每个问题的评论列表,我们需要“重新组合”列,以便每一条评论都包含一个 **(html_url, title, body, comment)** 元组。在 Pandas 中,我们可以使用 [DataFrame.explode() 函数](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html), 它为类似列表的列中的每个元素创建一个新行,同时复制所有其他列值。为了看到它的实际效果,让我们首先切换到 Pandas的**DataFrame** 格式: + +```py +issues_dataset.set_format("pandas") +df = issues_dataset[:] +``` + +如果我们检查这里的第一行 **DataFrame** 我们可以看到有四个评论与这个问题相关: + +```py +df["comments"][0].tolist() +``` + +```python out +['the bug code locate in :\r\n if data_args.task_name is not None:\r\n # Downloading and loading a dataset from the hub.\r\n datasets = load_dataset("glue", data_args.task_name, cache_dir=model_args.cache_dir)', + 'Hi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com\r\n\r\nNormally, it should work if you wait a little and then retry.\r\n\r\nCould you please confirm if the problem persists?', + 'cannot connect,even by Web browser,please check that there is some problems。', + 'I can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...'] +``` + +我们希望这些评论中的每一条都得到一行。让我们检查是否是这种情况: + +```py +comments_df = df.explode("comments", ignore_index=True) +comments_df.head(4) +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
html_urltitlecommentsbody
0https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comthe bug code locate in :\r\n if data_args.task_name is not None...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
1https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comHi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
2https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comcannot connect,even by Web browser,please check that there is some problems。Hello,\r\nI am trying to run run_glue.py and it gives me this error...
3https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comI can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
+ +太好了,我们可以看到评论成功被扩充, **comments** 是包含个人评论的列!现在我们已经完成了 Pandas要完成的部分功能,我们可以快速切换回 **Dataset** 通过加载 **DataFrame** 在内存中: + +```py +from datasets import Dataset + +comments_dataset = Dataset.from_pandas(comments_df) +comments_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body'], + num_rows: 2842 +}) +``` + +太好了,我们获取到了几千条的评论! + + + + +✏️ **Try it out!** 看看能不能不用pandas就可以完成列的扩充; 这有点棘手; 你可能会发现 🤗 Datasets 文档的 ["Batch mapping"](https://huggingface.co/docs/datasets/v1.12.1/about_map_batch.html?batch-mapping#batch-mapping) 对这个任务很有用。 + + + +现在我们每行有一个评论,让我们创建一个新的 **comments_length** 列来存放每条评论的字数: + +```py +comments_dataset = comments_dataset.map( + lambda x: {"comment_length": len(x["comments"].split())} +) +``` + +我们可以使用这个新列来过滤掉简短的评论,其中通常包括“cc @lewtun”或“谢谢!”之类与我们的搜索引擎无关的内容。虽然无法为过滤器选择的精确数字,但大约大于15 个单词似乎是一个不错的选择: + +```py +comments_dataset = comments_dataset.filter(lambda x: x["comment_length"] > 15) +comments_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body', 'comment_length'], + num_rows: 2098 +}) +``` + +稍微清理了我们的数据集后,让我们将问题标题、描述和评论连接到一个新的 **text** 列。像往常一样,我们可以编写一个简单的函数,并将其传递给 **Dataset.map()**来做到这些 : + +```py +def concatenate_text(examples): + return { + "text": examples["title"] + + " \n " + + examples["body"] + + " \n " + + examples["comments"] + } + + +comments_dataset = comments_dataset.map(concatenate_text) +``` + +我们终于准备好创建一些嵌入了!让我们来看看。 + +## 创建文本嵌入 + +我们在[第二章](/course/chapter2) 学过,我们可以通过使用 **AutoModel** 类来完成词嵌入。我们需要做的就是选择一个合适的检查点来加载模型。幸运的是,有一个名为 **sentence-transformers** 专门用于创建词嵌入。如库中[文档](https://www.sbert.net/examples/applications/semantic-search/README.html#symmetric-vs-asymmetric-semantic-search), 所述的,我们这次要实现的是非对称语义搜索,因为我们有一个简短的查询,我们希望在比如问题评论等更长的文档中找到其答案。通过查看[模型概述表](https://www.sbert.net/docs/pretrained_models.html#model-overview) 我们可以发现 **multi-qa-mpnet-base-dot-v1** 检查点在语义搜索方面具有最佳性能,因此我们将在我们的应用程序中使用它。我们还将使用相同的检查点加载标记器: + +{#if fw === 'pt'} + +```py +from transformers import AutoTokenizer, AutoModel + +model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1" +tokenizer = AutoTokenizer.from_pretrained(model_ckpt) +model = AutoModel.from_pretrained(model_ckpt) +``` + +为了加快嵌入过程,将模型和输入放在 GPU 设备上,所以现在让我们这样做: + +```py +import torch + +device = torch.device("cuda") +model.to(device) +``` + +{:else} + +```py +from transformers import AutoTokenizer, TFAutoModel + +model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1" +tokenizer = AutoTokenizer.from_pretrained(model_ckpt) +model = TFAutoModel.from_pretrained(model_ckpt, from_pt=True) +``` + +请注意,我们已将 from_pt=True 设置为 from_pretrained() 方法的参数。这是因为 multi-qa-mpnet-base-dot-v1 检查点只有PyTorch权重,因此设置 from_pt=True 会自动将它们转换为TensorFlow格式。如您所见,在Transformers中的🤗框架之间切换非常简单! + +{/if} + +正如我们之前提到的,我们希望将 GitHub 问题语料库中的每个条目表示为单个向量,因此我们需要以某种方式“池化”或平均化我们的标记嵌入。一种流行的方法是在我们模型的输出上执行CLS 池化,我们只获取**[CLS]** 令牌的最后一个隐藏状态。以下函数为我们提供了这样的方法: + +```py +def cls_pooling(model_output): + return model_output.last_hidden_state[:, 0] +``` + +接下来,我们将创建一个辅助函数,该函数将标记文档列表,将tensor放在 GPU 上,然后提供给模型,最后对输出使用CLS 池化: + +{#if fw === 'pt'} + +```py +def get_embeddings(text_list): + encoded_input = tokenizer( + text_list, padding=True, truncation=True, return_tensors="pt" + ) + encoded_input = {k: v.to(device) for k, v in encoded_input.items()} + model_output = model(**encoded_input) + return cls_pooling(model_output) +``` + +我们可以通过在我们的语料库中输入第一个文本条目并检查输出维度来测试该函数是否有效: + +```py +embedding = get_embeddings(comments_dataset["text"][0]) +embedding.shape +``` + +```python out +torch.Size([1, 768]) +``` + +太好了,我们已经将语料库中的第一个条目转换为 768 维向量!我们可以用 **Dataset.map()** 应用我们的 **get_embeddings()** 函数到我们语料库中的每一行,所以让我们创建一个新的 **embeddings** 列如下: + +```py +embeddings_dataset = comments_dataset.map( + lambda x: {"embeddings": get_embeddings(x["text"]).detach().cpu().numpy()[0]} +) +``` + +{:else} + +```py +def get_embeddings(text_list): + encoded_input = tokenizer( + text_list, padding=True, truncation=True, return_tensors="tf" + ) + encoded_input = {k: v for k, v in encoded_input.items()} + model_output = model(**encoded_input) + return cls_pooling(model_output) +``` + +我们可以通过在我们的语料库中输入第一个文本条目并检查输出维度来测试该函数是否有效: + +```py +embedding = get_embeddings(comments_dataset["text"][0]) +embedding.shape +``` + +```python out +TensorShape([1, 768]) +``` + +太好了,我们已经将语料库中的第一个条目转换为 768 维向量!我们可以用 **Dataset.map()** 应用我们的 **get_embeddings()** 函数到我们语料库中的每一行,所以让我们创建一个新的 **embeddings** 列如下: + +```py +embeddings_dataset = comments_dataset.map( + lambda x: {"embeddings": get_embeddings(x["text"]).numpy()[0]} +) +``` + +{/if} + +请注意,我们已经将嵌入转换为 NumPy 数组——这是因为当我们尝试使用 FAISS 索引它们时,🤗 Datasets需要这种格式,我们接下来会这样做。 + + +## 使用 FAISS 进行高效的相似性搜索 + +现在我们有了一个词嵌入数据集,我们需要一些方法来搜索它们。为此,我们将在 🤗 Datasets中使用一种特殊的数据结构,称为 FAISS指数.[FAISS](https://faiss.ai/) (short for Facebook AI Similarity Search) (Facebook AI Similarity Search 的缩写)是一个提供高效算法来快速搜索和聚类嵌入向量的库。FAISS 背后的基本思想是创建一个特殊的数据结构,称为指数。这允许人们找到哪些嵌入词与输入的词嵌入相似。在 🤗 Datasets中创建一个 FAISS 索引很简单——我们使用 **Dataset.add_faiss_index()** 函数并指定我们要索引的数据集的哪一列: + +```py +embeddings_dataset.add_faiss_index(column="embeddings") +``` + +现在,我们可以使用**Dataset.get_nearest_examples()**函数进行最近邻居查找。让我们通过首先嵌入一个问题来测试这一点,如下所示: + +{#if fw === 'pt'} + +```py +question = "How can I load a dataset offline?" +question_embedding = get_embeddings([question]).cpu().detach().numpy() +question_embedding.shape +``` + +```python out +torch.Size([1, 768]) +``` + +{:else} + +```py +question = "How can I load a dataset offline?" +question_embedding = get_embeddings([question]).numpy() +question_embedding.shape +``` + +```python out +(1, 768) +``` + +{/if} + +就像文档一样,我们现在有一个 768 维向量表示查询,我们可以将其与整个语料库进行比较以找到最相似的嵌入: + +```py +scores, samples = embeddings_dataset.get_nearest_examples( + "embeddings", question_embedding, k=5 +) +``` + + **Dataset.get_nearest_examples()** 函数返回一个分数元组,对查询和文档之间的相似度进行排序,以及一组最佳匹配的结果(这里是 5 个)。让我们把这些收集到一个 **pandas.DataFrame** 以便我们可以轻松地对它们进行排序: + +```py +import pandas as pd + +samples_df = pd.DataFrame.from_dict(samples) +samples_df["scores"] = scores +samples_df.sort_values("scores", ascending=False, inplace=True) +``` + +现在我们可以遍历前几行来查看我们的查询与评论的匹配程度: + +```py +for _, row in samples_df.iterrows(): + print(f"COMMENT: {row.comments}") + print(f"SCORE: {row.scores}") + print(f"TITLE: {row.title}") + print(f"URL: {row.html_url}") + print("=" * 50) + print() +``` + +```python out +""" +COMMENT: Requiring online connection is a deal breaker in some cases unfortunately so it'd be great if offline mode is added similar to how `transformers` loads models offline fine. + +@mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like? +SCORE: 25.505046844482422 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: The local dataset builders (csv, text , json and pandas) are now part of the `datasets` package since #1726 :) +You can now use them offline +\`\`\`python +datasets = load_dataset("text", data_files=data_files) +\`\`\` + +We'll do a new release soon +SCORE: 24.555509567260742 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: I opened a PR that allows to reload modules that have already been loaded once even if there's no internet. + +Let me know if you know other ways that can make the offline mode experience better. I'd be happy to add them :) + +I already note the "freeze" modules option, to prevent local modules updates. It would be a cool feature. + +---------- + +> @mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like? + +Indeed `load_dataset` allows to load remote dataset script (squad, glue, etc.) but also you own local ones. +For example if you have a dataset script at `./my_dataset/my_dataset.py` then you can do +\`\`\`python +load_dataset("./my_dataset") +\`\`\` +and the dataset script will generate your dataset once and for all. + +---------- + +About I'm looking into having `csv`, `json`, `text`, `pandas` dataset builders already included in the `datasets` package, so that they are available offline by default, as opposed to the other datasets that require the script to be downloaded. +cf #1724 +SCORE: 24.14896583557129 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: > here is my way to load a dataset offline, but it **requires** an online machine +> +> 1. (online machine) +> +> ``` +> +> import datasets +> +> data = datasets.load_dataset(...) +> +> data.save_to_disk(/YOUR/DATASET/DIR) +> +> ``` +> +> 2. copy the dir from online to the offline machine +> +> 3. (offline machine) +> +> ``` +> +> import datasets +> +> data = datasets.load_from_disk(/SAVED/DATA/DIR) +> +> ``` +> +> +> +> HTH. + + +SCORE: 22.893993377685547 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: here is my way to load a dataset offline, but it **requires** an online machine +1. (online machine) +\`\`\` +import datasets +data = datasets.load_dataset(...) +data.save_to_disk(/YOUR/DATASET/DIR) +\`\`\` +2. copy the dir from online to the offline machine +3. (offline machine) +\`\`\` +import datasets +data = datasets.load_from_disk(/SAVED/DATA/DIR) +\`\`\` + +HTH. +SCORE: 22.406635284423828 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== +""" +``` + +我们的第二个搜索结果似乎与查询相符。 + + + +✏️ 试试看!创建您自己的查询并查看您是否可以在检索到的文档中找到答案。您可能需要增加参数k以扩大搜索范围。 + + \ No newline at end of file diff --git a/chapters/zh-CN/chapter5/7.mdx b/chapters/zh-CN/chapter5/7.mdx new file mode 100644 index 000000000..a4bece254 --- /dev/null +++ b/chapters/zh-CN/chapter5/7.mdx @@ -0,0 +1,11 @@ +# 🤗 Datasets,回顾! + +这是对 🤗 Datasets 库的一次完整游览——祝贺你走到这一步!凭借从本章中获得的知识,您应该能够: + +- 从任何地方加载数据集,无论是 Hugging Face Hub、您的笔记本电脑还是您公司的远程服务器。 +- 混合使用Dataset.map()和Dataset.filter()函数来整理数据。 +- 使用`Dataset.set_format()`在 Pandas 和 NumPy 等数据格式之间快速切换. +- 创建您自己的数据集并将其推送到 Hugging Face Hub。. +- 使用 Transformer 模型为您的文档创建词嵌入,并使用 FAISS 构建语义搜索引擎。. + +在[第七章](/course/chapter7),当我们深入研究 Transformer 模型非常适合的核心 NLP 任务时,我们将充分利用所有这些。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter5/8.mdx b/chapters/zh-CN/chapter5/8.mdx new file mode 100644 index 000000000..428d5bf1d --- /dev/null +++ b/chapters/zh-CN/chapter5/8.mdx @@ -0,0 +1,216 @@ + + +# 章末小测试 + +本章涵盖了很多方面! 如果你没有掌握所有细节, 不用担心; 在下一章将帮助你了解内部的事情是如何工作的。 + +不过, 在继续下一章之前, 让我们测试一下你在本章学到的内容。 + +### 1.🤗 Datasets中的 `load_dataset ()` 函数允许你从下列哪个位置加载数据集? +load_dataset() 函数的 data_files 参数来加载本地数据集。", + correct: true + }, + { + text: "Hugging Face Hub", + explain: "正确! 你可以通过提供数据集 ID 在 Hub 上加载数据集, 例如 < code > load _ dataset ('em otion') 。", + correct: true + }, + { + text: "远程服务器", + explain: "正确! 你可以将URL传递给 load_dataset() 函数的 data_files 参数来加载远程文件。", + correct: true + }, + ]} +/> + +### 2.假设您加载了 GLUE 任务,如下所示: +```py +from datasets import load_dataset + +dataset = load_dataset("glue", "mrpc", split="train") +``` + +以下哪个命令将从 `dataset` 中生成50个元素的随机样本? + + dataset.sample (50) ", + explain: "这是不正确的——没有 < code > Dataset.sample () 方法。" + }, + { + text: "dataset.shuffle().select(range(50))", + explain: "正确! 正如你在本章中看待的, 你首先打乱了数据集, 然后从中选择样本。", + correct: true + }, + { + text: "dataset.select(range(50)).shuffle()", + explain: "这是不正确的——尽管代码会运行, 但它只会随机处理数据集中的前50个元素。" + } + ]} +/> + +### 3.假设你有一个叫做宠物数据集的家庭宠物数据集,它有一个名字列表示每个宠物的名字。下列哪种方法可以让你过滤所有名字以字母"L"开头的宠物的数据? + 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: "创建一个类似于 < code > def filter _ names (x) : return x ['name'] . startswith ('L') 的函数并运行 < code > pets _ dataset. filter (filter _ names) 。", + explain: "正确!就像使用 < code > Dataset.map () 一样,你可以将显式函数传递给 < code > Dataset.filter () 。当你有一些不适合于简短 lambda 函数的复杂逻辑时,这是非常有用的。其他解决方案中还有哪一个可行?", + correct: true + } + ]} +/> + +### 4.什么是内存映射? + + +### 5.下列哪一项是内存映射的主要好处? + + +### 6.为什么下面的代码是错误的? +```py +from datasets import load_dataset + +dataset = load_dataset("allocine", streaming=True, split="train") +dataset[0] +``` + + IterableDataset 。", + explain: "正确! < code > IterableDataset 是一个生成器, 而不是一个容器, 因此你应该使用 < code > next (iter (dataset)) 来访问它的元素。", + correct: true + }, + { + text: "数据集 < code > allocine 没有分割< code >训练集。", + explain: "这是不正确的---- 查看 Hub 上的[ < code > allocine dataset card ]( https://huggingface.co/datasets/allocine ), 看看它包含哪些拆分。" + } + ]} +/> + +### 7.创建数据集卡的主要好处是什么? + + + +### 8.什么是语义搜索? + + +### 9.对于非对称语义搜索,通常有: + + +### 10.我可以使用数据集加载数据用于其他领域,如语音处理? + From 240a8ad445fb173dad3ff288dbade0bf0f819a0c Mon Sep 17 00:00:00 2001 From: lewtun Date: Mon, 15 Aug 2022 12:35:37 +0100 Subject: [PATCH 25/51] Bump release (#295) --- chapters/en/chapter7/3.mdx | 2 +- chapters/fr/chapter7/3.mdx | 2 +- chapters/it/_toctree.yml | 19 + chapters/it/chapter5/1.mdx | 17 + chapters/it/chapter5/2.mdx | 167 ++++++++ chapters/it/chapter5/3.mdx | 740 +++++++++++++++++++++++++++++++++ chapters/it/chapter5/4.mdx | 288 +++++++++++++ chapters/it/chapter5/5.mdx | 470 +++++++++++++++++++++ chapters/it/chapter5/6.mdx | 531 +++++++++++++++++++++++ chapters/it/chapter5/7.mdx | 10 + chapters/it/chapter5/8.mdx | 225 ++++++++++ chapters/ja/chapter7/3.mdx | 2 +- chapters/zh-CN/_toctree.yml | 27 +- chapters/zh-CN/chapter6/1.mdx | 14 + chapters/zh-CN/chapter6/10.mdx | 268 ++++++++++++ chapters/zh-CN/chapter6/2.mdx | 256 ++++++++++++ chapters/zh-CN/chapter6/3.mdx | 473 +++++++++++++++++++++ chapters/zh-CN/chapter6/3b.mdx | 639 ++++++++++++++++++++++++++++ chapters/zh-CN/chapter6/4.mdx | 124 ++++++ chapters/zh-CN/chapter6/5.mdx | 360 ++++++++++++++++ chapters/zh-CN/chapter6/6.mdx | 373 +++++++++++++++++ chapters/zh-CN/chapter6/7.mdx | 381 +++++++++++++++++ chapters/zh-CN/chapter6/8.mdx | 564 +++++++++++++++++++++++++ chapters/zh-CN/chapter6/9.mdx | 11 + 24 files changed, 5959 insertions(+), 4 deletions(-) create mode 100644 chapters/it/chapter5/1.mdx create mode 100644 chapters/it/chapter5/2.mdx create mode 100644 chapters/it/chapter5/3.mdx create mode 100644 chapters/it/chapter5/4.mdx create mode 100644 chapters/it/chapter5/5.mdx create mode 100644 chapters/it/chapter5/6.mdx create mode 100644 chapters/it/chapter5/7.mdx create mode 100644 chapters/it/chapter5/8.mdx create mode 100644 chapters/zh-CN/chapter6/1.mdx create mode 100644 chapters/zh-CN/chapter6/10.mdx create mode 100644 chapters/zh-CN/chapter6/2.mdx create mode 100644 chapters/zh-CN/chapter6/3.mdx create mode 100644 chapters/zh-CN/chapter6/3b.mdx create mode 100644 chapters/zh-CN/chapter6/4.mdx create mode 100644 chapters/zh-CN/chapter6/5.mdx create mode 100644 chapters/zh-CN/chapter6/6.mdx create mode 100644 chapters/zh-CN/chapter6/7.mdx create mode 100644 chapters/zh-CN/chapter6/8.mdx create mode 100644 chapters/zh-CN/chapter6/9.mdx diff --git a/chapters/en/chapter7/3.mdx b/chapters/en/chapter7/3.mdx index 3d8eba45f..1ea3d6ee5 100644 --- a/chapters/en/chapter7/3.mdx +++ b/chapters/en/chapter7/3.mdx @@ -533,7 +533,7 @@ def whole_word_masking_data_collator(features): import collections import numpy as np -from transformers.data import tf_default_data_collator +from transformers.data.data_collator import tf_default_data_collator wwm_probability = 0.2 diff --git a/chapters/fr/chapter7/3.mdx b/chapters/fr/chapter7/3.mdx index 738f9d59d..89cd6a843 100644 --- a/chapters/fr/chapter7/3.mdx +++ b/chapters/fr/chapter7/3.mdx @@ -534,7 +534,7 @@ def whole_word_masking_data_collator(features): import collections import numpy as np -from transformers.data import tf_default_data_collator +from transformers.data.data_collator import tf_default_data_collator wwm_probability = 0.2 diff --git a/chapters/it/_toctree.yml b/chapters/it/_toctree.yml index 670ecb291..40c971827 100644 --- a/chapters/it/_toctree.yml +++ b/chapters/it/_toctree.yml @@ -40,3 +40,22 @@ title: Quiz di fine capitolo quiz: 4 +- title: 5. La libreria 🤗 Datasets + sections: + - local: chapter5/1 + title: Introduzione + - local: chapter5/2 + title: E se il mio dataset non è sull'Hub? + - local: chapter5/3 + title: È arrivato il momento di tagliuzzare + - local: chapter5/4 + title: Big data? Ci pensa 🤗 Datasets! + - local: chapter5/5 + title: Creare il proprio dataset + - local: chapter5/6 + title: Ricerca semantica con FAISS + - local: chapter5/7 + title: 🤗 Datasets, check! + - local: chapter5/8 + title: Quiz di fine capitolo + quiz: 5 diff --git a/chapters/it/chapter5/1.mdx b/chapters/it/chapter5/1.mdx new file mode 100644 index 000000000..e23cf502e --- /dev/null +++ b/chapters/it/chapter5/1.mdx @@ -0,0 +1,17 @@ +# Introduzione + +Nel [Capitolo 3](/course/chapter3) hai mosso i primi passi nella libreria 🤗 Datasets, e hai scoperto i tre passaggi fondamentali nell'ottimizzazione dei modelli: + +1. Si carica un dataset dell'Hub Hugging Face. +2. Si processano i dati con `Dataset.map()`. +3. Si caricano e si elaborano le metriche. + +Ma questo non è che un assaggio di ciò che 🤗 Datasets è in grado di fare! In questo capitolo approfondiremo le potenzialità della libreria. Durante questo percorso, troverai risposta alle seguenti domande: + +* Cosa fare quando un dataset non è presente nell'Hub? +* Come fare a tagliuzzare il dataset? (E cosa succede se devi _proprio_ usare Pandas?) +* Cosa fare quando un dataset è tanto grande da sciogliere la RAM del tuo portatile? +* Cosa cavolo sono il "mappamento di memoria" e Apache Arrow? +* Come fare per creare il proprio dataset e pubblicarlo sull'Hub? + +Le tecniche che imparerai ti prepareranno a compiti più avanzati di tokenizzazione e fine-tuning che troverai nei capitoli [Chapter 6](/course/chapter6) e [Chapter 7](/course/chapter7) -- quindi preparati una tazza di caffè e iniziamo! diff --git a/chapters/it/chapter5/2.mdx b/chapters/it/chapter5/2.mdx new file mode 100644 index 000000000..93a7d01f6 --- /dev/null +++ b/chapters/it/chapter5/2.mdx @@ -0,0 +1,167 @@ +# E se il mio dataset non è sull'Hub? + + + +Sai come usare l'[Hub Hugging Face](https://huggingface.co/datasets) per scaricare i dataset, ma spessa dovrai lavorare con dati che si trovano sul tuo computer, o so un server remoto. In questa sezione vederemo come usare 🤗 Datasets per caricare dataset che non sono disponibile nell'Hub Hugging Face. + + + +## Lavorare con dataset locali e in remoto + +🤗 Datasets mette a tua disposizione diversi script per caricare dataset in locale e in remoto. Sono supportati diversi formati di dati, tra cui: + +| Formato dati | Script | Esempio | +| :----------------: | :------------: | :-----------------------------------------------------: | +| CSV & TSV | `csv` | `load_dataset("csv", data_files="my_file.csv")` | +| File di testo | `text` | `load_dataset("text", data_files="my_file.txt")` | +| JSON & JSON Lines | `json` | `load_dataset("json", data_files="my_file.jsonl")` | +| DataFrame serializzati in Pickle | `pandas` | `load_dataset("pandas", data_files="my_dataframe.pkl")` | + +Come mostrato nella tabella, per ogni formato di dati abbiamo bisogno di specificare, all'interno della funzione `load_dataset()`, il tipo di script da utilizzare, assieme a `data_files`, che specifica il percorso verso uno o più file. Iniziamo a caricare un dataset proveniente da file locali; più tardi vederemo come fare la stessa cosa con file in remoto. + +## Caricare un dataset locale + +Per questo esempio useremo il [dataset SQuAD-it](https://github.com/crux82/squad-it/), un ampio dataset per il question answering in italiano + +Le sezioni di addestramento e di test si trovano su GitHub, quindi possiamo scaricarle con un semplice comando `wget`: + +```python +!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-train.json.gz +!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-test.json.gz +``` + +Questo scaricherà due file compressi chiamati *SQuAD_it-train.json.gz* e *SQuAD_it-test.json.gz*, che possiamo decomprimere con il comandi Linux `gzip`: + +```python +!gzip -dkv SQuAD_it-*.json.gz +``` + +```bash +SQuAD_it-test.json.gz: 87.4% -- replaced with SQuAD_it-test.json +SQuAD_it-train.json.gz: 82.2% -- replaced with SQuAD_it-train.json +``` + +Vediamo che i dati compressi sono stati sostituiti da _SQuAD_it-train.json_ e _SQuAD_it-text.json_, e che i dati sono archiviati in formato JSON. + + + +✎ Se ti stai chiedendo perché c'è un `!` nei comandi di shell precedenti, è perché li stiamo eseguendo da un notebook Jupyter. Se vuoi scaricare e decomprimere i dataset da un terminale, non devi fare altro che rimuovere il prefisso. + + + +Per caricare un file JSON con la funzione `load_dataset()`, ci serve solo sapere se abbiamo a che fare con un normale JSON (simile a un dizionario annidato) o con un JSON Lines (JSON separato da righe). Come molti dataset per il question asnwring, SQuAD-it usa il formato annidato, con tutto il testo immagazzinato nel campo `data`. Questo significa che possiamo caricare il dataset specificando l'argomento `field` come segue: + +```py +from datasets import load_dataset + +squad_it_dataset = load_dataset("json", data_files="SQuAD_it-train.json", field="data") +``` + +Di default, caricare file locali create un oggetto `DatasetDict` con una sezione `train`. Possiamo vederlo ispezionando l'oggetto `squad_it_dataset`: + +```py +squad_it_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 442 + }) +}) +``` + +Questo ci mostra il numero di righe e i nomi delle colonne associate con il set di addestraento. Possiamo vedere uno degli esempi indicizzando la sezione `train`, come segue: + +```py +squad_it_dataset["train"][0] +``` + +```python out +{ + "title": "Terremoto del Sichuan del 2008", + "paragraphs": [ + { + "context": "Il terremoto del Sichuan del 2008 o il terremoto...", + "qas": [ + { + "answers": [{"answer_start": 29, "text": "2008"}], + "id": "56cdca7862d2951400fa6826", + "question": "In quale anno si è verificato il terremoto nel Sichuan?", + }, + ... + ], + }, + ... + ], +} +``` + +Benissimo, abbiamo caricare il nostro primo dataset locale! Ma anche se questo ha funzionato per la sezione di addestramento, vogliamo includere entrambe le sezioni `train` e `test` in un unico oggetto `DatasetDict` così da poter applicare le funzioni `Dataset.map()` su entrambi i dataset simultaneamente. Per fare questo, possiamo dare un dizionaro all'argomento `data_files`, per mappare ogni sezione a un file associato con quella sezione: + +```py +data_files = {"train": "SQuAD_it-train.json", "test": "SQuAD_it-test.json"} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +squad_it_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 442 + }) + test: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 48 + }) +}) +``` + +Questo è proprio ciò che volevamo. Ora possiamo applicare diverse tecniche di preprocessamento per pulire i dati, tokenizzare le revisioni, e altro. + + + +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. + + + +Gli script presenti in 🤗 Datasets supportano la decompressione atuomatica dei file in input, quindi possiamo saltare l'uso di `gzip` puntando `data_files` direttamente ai file compressi: + +```py +data_files = {"train": "SQuAD_it-train.json.gz", "test": "SQuAD_it-test.json.gz"} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +``` + +Questo può essere utile se non vuoi decomprimere manualmente molti file GZIP. La decompressione automatica si applica inoltre ad altri formati comuni, come ZIP e TAR, basta solo puntare `data_files` ai file compressi ed è fatta! + +Ora che sai come caricare i file locali dal tuo computer, guardiamo come caricare i file remoti. + +## Caricare un dataset in remoto + +Se lavori come data scientist o come programmatore per un'azienda, ci sono buone probabilità che i dataset da analizzare sono archiaviati su un qualche server in remoto. Per fortuna, caricare file remoti è semplice come caricare quelli locali! Invece di dare un percorso a file locali, puntiamo l'argomento `data_files` di `load_dataset()` a uno o più URL dove si trovano i file in remoto. Ad esempio, per il dataset SQuAD-it presente su GitHub, possiamo puntare `data_files` agli URL _SQuAD_it-*.json.gz_ come segue: + +```py +url = "https://github.com/crux82/squad-it/raw/master/" +data_files = { + "train": url + "SQuAD_it-train.json.gz", + "test": url + "SQuAD_it-test.json.gz", +} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +``` + +Questo codice restituisce lo stesso oggetto `DatasetDict` visto in precedenza, ma ci risparmia il passaggio manuale di scaricare e decomprimere i file _SQuAD_it-*.json.gz_. Questo conclude la nostra incursione nei diversi modi di caricare dataset che non sono presenti nell'Hub Hugging Face. Ora che abbiamo un dataset con cui giocare, sporchiamoci le mani con diverse tecniche di data-wrangling! + + + +✏️ **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). + + + + diff --git a/chapters/it/chapter5/3.mdx b/chapters/it/chapter5/3.mdx new file mode 100644 index 000000000..8c44362ed --- /dev/null +++ b/chapters/it/chapter5/3.mdx @@ -0,0 +1,740 @@ +# È arrivato il momento di tagliuzzare + + + +La maggior parte delle volte, i dati su cui lavorerai non saranno perfettamente pronti a essere usati per l'addestramento. In questa sezione esploreremo alcune funzionalità di 🤗 Datasets per pulire i tuoi dataset. + + + +## Tagliuzzare i tuoi dati + +Proprio come Pandas, 🤗 Datasets offre diverse funzionalità per manipolare il contenuto degli oggetti `Dataset` e `DatasetDict`. Abbiamo già visto il metodo `Dataset.map()` nel [Capitolo 3](/course/chapter3), e in questa sezione esploreremo altre funzioni a nostra disposizione. + +Ai fini di quest'esempio useremo il [Drug Review Dataset](https://archive.ics.uci.edu/ml/datasets/Drug+Review+Dataset+%28Drugs.com%29), che raccoglie le recensioni di pazienti su vari medicinali, assieme alla condizione curata e a una valutazione da 0 a 10 del grado di soddisfazione del paziente. +Prima di tutto scarichiamo ed estraiamo i dati, utilizzando i comandi `wget` e `unzip`: + +```py +!wget "https://archive.ics.uci.edu/ml/machine-learning-databases/00462/drugsCom_raw.zip" +!unzip drugsCom_raw.zip +``` + +Poiché TSV non è altro che una variante di CSV che usa come separatore tabulatori al posto delle virgole, caricheremo questi file utilizzando lo script `csv` e specificando l'argomento `delimiter` nella funzione `load_dataset()`, come segue: + +```py +from datasets import load_dataset + +data_files = {"train": "drugsComTrain_raw.tsv", "test": "drugsComTest_raw.tsv"} +# \t rappresenta il tabulatore in Python +drug_dataset = load_dataset("csv", data_files=data_files, delimiter="\t") +``` + +È buona prassi nell'analisi dati recuperare un piccolo campione casuale per farsi un'idea del tipo di dati con cui si sta lavorando. Utilizzando 🤗 Datasets, possiamo crare un campione casuale concatenando le funzioni `Dataset.shuffle()` e `Dataset.select()`: + +```py +drug_sample = drug_dataset["train"].shuffle(seed=42).select(range(1000)) +# Diamo un'occhiata ai primi esempi +drug_sample[:3] +``` + +```python out +{'Unnamed: 0': [87571, 178045, 80482], + 'drugName': ['Naproxen', 'Duloxetine', 'Mobic'], + 'condition': ['Gout, Acute', 'ibromyalgia', 'Inflammatory Conditions'], + 'review': ['"like the previous person mention, I'm a strong believer of aleve, it works faster for my gout than the prescription meds I take. No more going to the doctor for refills.....Aleve works!"', + '"I have taken Cymbalta for about a year and a half for fibromyalgia pain. It is great\r\nas a pain reducer and an anti-depressant, however, the side effects outweighed \r\nany benefit I got from it. I had trouble with restlessness, being tired constantly,\r\ndizziness, dry mouth, numbness and tingling in my feet, and horrible sweating. I am\r\nbeing weaned off of it now. Went from 60 mg to 30mg and now to 15 mg. I will be\r\noff completely in about a week. The fibro pain is coming back, but I would rather deal with it than the side effects."', + '"I have been taking Mobic for over a year with no side effects other than an elevated blood pressure. I had severe knee and ankle pain which completely went away after taking Mobic. I attempted to stop the medication however pain returned after a few days."'], + 'rating': [9.0, 3.0, 10.0], + 'date': ['September 2, 2015', 'November 7, 2011', 'June 5, 2013'], + 'usefulCount': [36, 13, 128]} +``` + +Da notare che abbiamo impostato il seed in `Dataset.shuffle()` per motivi di riproducibilità. `Dataset.select()` ha bisogno di un iterabile di indici, per cui abbiamo utilizzato `range(1000)` per recuperare i primi 1.000 esempi dal dataset mescolato. Da questo campione possiamo già vedere alcune particolarità del nostor dataset: + +* La colonna `Unnamed: 0` assomiglia molto a un ID anonimizzato per ognuno dei pazienti. +* La colonna `condizione` include un mix di etichette maiuscole e minuscole. +* Le recensioni sono di diversa lunghezza e contengono un mix di separatori di riga Python (`\r\n`) e di codici di caratteri HTML come `&\#039`. + +Ora vediamo come utilizzare 🤗 Datasets per risolvere alcuni di questi problemi. Per confermare l'ipotesi che la colonna `Unnamed: 0` rappresenti gli ID dei pazienti, possiamo usare la funzione `Dataset.unique()` per verificare che il numero di ID corrisponda al numero delle righe in ognuna delle sezioni: + + +```py +for split in drug_dataset.keys(): + assert len(drug_dataset[split]) == len(drug_dataset[split].unique("Unnamed: 0")) +``` + +Questo sembra confermare la nostra ipotesi, quindi puliamo un po' il nostro dataset cambiando il nome della colonna `Unnamed: 0` in qualcosa di un po' più comprensibile. Possiamo usare la funzione `DatasetDict.rename_column()` per rinominare la colonna in entrambe le sezioni: + + +```py +drug_dataset = drug_dataset.rename_column( + original_column_name="Unnamed: 0", new_column_name="patient_id" +) +drug_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'], + num_rows: 161297 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'], + num_rows: 53766 + }) +}) +``` + + + +✏️ **Prova tu!** Usa la funzione `Dataset.unique()` per trovare il numero di medicine diverse e condizioni nelle sezioni di addestramento e di test. + + + +Ora, normaliziamo le etichette in `condition` utilizzando `Dataset.map()`. Così come abbiamo fatto con la tokenizzazione nel [Capitolo 3](/course/chapter3), possiamo definire una semplice funzione che può essere applicata a tutte le righe di ogni sezione nel `drug_dataset`: + +```py +def lowercase_condition(example): + return {"condition": example["condition"].lower()} + + +drug_dataset.map(lowercase_condition) +``` + +```python out +AttributeError: 'NoneType' object has no attribute 'lower' +``` + +Oh no, abbiamo incontrato un problema con la nostra funzione! Dall'errore possiamo dedurre che alcuni dei valori nella colonna `condition` sono `None`, che non essendo stringhe non possono essere convertiti in lettere minuscole. Eliminiamo queste righe utilizzando `Dataset.filter()`, che funziona come `Dataset.map()` e accetta una funziona che riceve un singolo esempio del dataset. Invece di scrivere una funzione esplicita come: + + +```py +def filter_nones(x): + return x["condition"] is not None +``` + +e utilizzare `drug_dataset.filter(filter_nones)`, possiamo utilizzare una _funzione lambda_ e completare tutto in un'unica riga. In Python, le funzioni lambda sono funzioni che possiamo definire senza nominarle esplicitamente. Hanno la forma generale: + +``` +lambda : +``` + +dove `lambda' è una delle [keyword](https://docs.python.org/3/reference/lexical_analysis.html#keywords) speciali di Python, `` è una lista/set di valori separati da virgole che definisce l'input della funzione, e `` rappresenta le operazioni che vogliamo eseguire. Ad esempio, posiamo definire una semplice funzione lamda che calcola il quadrato di un numero: + +``` +lambda x : x * x +``` + +Per applicare questa funzione a un input, dobbiamo includere sia la funzione che l'input in parentesi: + +```py +(lambda x: x * x)(3) +``` + +```python out +9 +``` + +Allo stesso modo, possiamo definire funzioni lmabda con argomenti multipli separandoli con virgoli. Ad esempio, possiamo calcolare l'area di un triangolo come segue: + +```py +(lambda base, altezza: 0.5 * base * altezza)(4, 8) +``` + +```python out +16.0 +``` + +Le funzioni lambda sono utili quando vogliamo definire piccole funzioni monouso (per maggiori informazioni, invitiamo alla lettura dell'ottimo [tutorial di Real Python](https://realpython.com/python-lambda/) di Andre Burgaud). In 🤗 Datasets, possiamo usare le funzioni lambda per definire semplici operazioni di mappatura e filtraggio. Utilizziamo questo trucchetto per eliminare i valori `None` nel nostro dataset: + +```py +drug_dataset = drug_dataset.filter(lambda x: x["condition"] is not None) +``` + +Una volta rimosse le voci `None`, possiamo normalizzare la colonna `condition`: + +```py +drug_dataset = drug_dataset.map(lowercase_condition) +# Check that lowercasing worked +drug_dataset["train"]["condition"][:3] +``` + +```python out +['left ventricular dysfunction', 'adhd', 'birth control'] +``` + +Funziona! Or ache abbiamo pulito le nostre etichette, diamo un'occhiata a come pulire le recensioni. + +## Creare nuove colonne + +Quando abbiamo a che fare con le recensioni di clienti, è buona pratica controllare il numero di parole in ogni recensione. Una recensione potrebbe contenere solo una parola com "Ottimo!" o un vero e proprio saggio di migliaia di parole, e a seconda dell'uso che ne farai dovrai affrontare queste situazioni in maniera diversa. Per calculare il numero di parole in ogni recensione, useremo un'euristica grezza basata sulla divisione dei testi sugli spazi. + + +Definiamo una semplice funzione che conta il numero di parole in ogni recensione: + +```py +def compute_review_length(example): + return {"review_length": len(example["review"].split())} +``` + +A differenza della nostra funzione `lowercase_condition()`, `compute_review_length()` ritorna un dizionario le cui chiavi non corrispondono a nessuna delle colonne nel dataset. In questo caso, quando `compute_review_length()` è passata a `Dataset.map()`, si applicherà a tutte le righe nel dataset per creare una nuova colonna `review_lenght`; + +```py +drug_dataset = drug_dataset.map(compute_review_length) +# Inspect the first training example +drug_dataset["train"][0] +``` + +```python out +{'patient_id': 206461, + 'drugName': 'Valsartan', + 'condition': 'left ventricular dysfunction', + 'review': '"It has no side effect, I take it in combination of Bystolic 5 Mg and Fish Oil"', + 'rating': 9.0, + 'date': 'May 20, 2012', + 'usefulCount': 27, + 'review_length': 17} +``` + +Come previsto, una colonna `review_length` è stata aggiunta al nostro set di addestramento. Possiamo ordinare questa nuova colonna utilizzando `Dataset.sort()` per dare un'occhiata ai valori estremi: + +```py +drug_dataset["train"].sort("review_length")[:3] +``` + +```python out +{'patient_id': [103488, 23627, 20558], + 'drugName': ['Loestrin 21 1 / 20', 'Chlorzoxazone', 'Nucynta'], + 'condition': ['birth control', 'muscle spasm', 'pain'], + 'review': ['"Excellent."', '"useless"', '"ok"'], + 'rating': [10.0, 1.0, 6.0], + 'date': ['November 4, 2008', 'March 24, 2017', 'August 20, 2016'], + 'usefulCount': [5, 2, 10], + 'review_length': [1, 1, 1]} +``` + +Come sospettato, alcune revisioni contengono una sola parola che, benché potrebbe essere utile per la sentiment analysis, non dà informazioni utili per predirre la condizione. + + + +🙋Un altro modo per aggiungere nuove colonne a un dataset è attraverso la funzione `Dataset.add_column()`. Questo ti permette di inserire le colonne come una lista Python o unarray NumPy, e può tornare utile in situazioni in cui `Dataset.map()` non è indicata per le tue analisi. + + + +Usiamo la funzione `Dataset.filter()` per rimuovere le recensioni che contengono meno di 30 parole. Proprio come abbiamo fatto per la colonna `condizione`, possiamo eliminare le recensioni più brevi aggiungendo un filtro che lascia passare solo le recensioni più lunghe di una certa soglia: + +```py +drug_dataset = drug_dataset.filter(lambda x: x["review_length"] > 30) +print(drug_dataset.num_rows) +``` + +```python out +{'train': 138514, 'test': 46108} +``` + +Come puoi vedere, questo ha rimosso circa il 15% delle recensioni nelle sezioni di training e di test. + + + +✏️ **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. + + + +L'ultima cosa che ci resta da risolvere è la presenza di codici HTML di caratteri nelle nostre recensioni. Possiamo usare il modulo Python `html` per sostituirli, così: + +```py +import html + +text = "I'm a transformer called BERT" +html.unescape(text) +``` + +```python out +"I'm a transformer called BERT" +``` + +We'll use `Dataset.map()` to unescape all the HTML characters in our corpus: + +```python +drug_dataset = drug_dataset.map(lambda x: {"review": html.unescape(x["review"])}) +``` +Come puoi vedere, il metodo `Dataset.map()` è molto utile per processare i dati -- e questo non è che la punta dell'iceberg di ciò che è in grado di fare! + +## I superpoteri del metodo `map()` + +Il metodo `Dataset.map()` accetta un argomento `batched` che, se impostato su `True`, gli fa inviare un batch di esempi alla funzione map in una sola volta (la grandezza del batch è configurabile, ma di default è impostta a 1.000). Ad esempio, l'esecuzione delle funzione map precedente che ha sostituito tutti i caratteri HTML è stata un po' lenta (puoi leggere il tempo impiegato dalle barre di progresso). Possiamo accelerare questo processo processando diversi elementi in contemporanea usando una comprensione di lista. + +Quando si specifica `batched=True` la funzione riceva un dizionario con i campi del dataset, ma ogni valore è ora una _lista di valori_, e non un valore singolo. Il valore ritornato da `Dataset.map()` dovrebbe essere lo stesso: un dizionario con i campi che vogliano aggiornare o aggiungere al nostro dataset, e una lista di valori. Ad esempio, ecco un altro modo per sostituire tutti i carattere HTML, ma utilizzando `batched=True`: + +```python +new_drug_dataset = drug_dataset.map( + lambda x: {"review": [html.unescape(o) for o in x["review"]]}, batched=True +) +``` + +Se utilizzi questo codice in un notebook, noterai che questo comando è molto più veloce del precedente. E non perché le nostre recensioni già state preprocessate, se esegui nuovamente le istruzioni della sezione precedente (senza `batched=True'), ci metterà lo stesso tempo di prima. Questo è perchè le comprensioni di lista sono solitamente più veloci delle loro controparti con ciclo `for`, e inoltre abbiamo guadagnato performance permettendo l'accesso a molti elementi in contemporanea invece di uno per volta. + +Utilizzare `Dataset.map()` con `batched=True` sarà essenziale per sbloccare la velocità dei tokenizzatori "fast" che incontreremo nel [Capitolo 6](/course/chapter6), che permettono di tokenizzare velocemente grandi liste di testi. Ad esempio, per tokenizzare tutte le recensioni di medicinali con un tokenizzatore veloce, potremmo usare una funzione come questa: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") + + +def tokenize_function(examples): + return tokenizer(examples["review"], truncation=True) +``` + +Come visto nel [Capitolo 3](/course/chapter3), possiamo passare uno o più esempi al tokenizzatore. Le funzione può essere usata con o senza `batched=True`. Approfittiamo di quest'occasione per paragonare la performance delle diverse opzioni. In un notebook, possiamo cronomotrare un'istruzione su una singola riga aggiungendo `%time` prima della riga di codice che desideri cronometrare: + +```python no-format +%time tokenized_dataset = drug_dataset.map(tokenize_function, batched=True) +``` + +Possiamo cronometrare anche un'intera cella inserento `%%time` all'inizio della cella. Sull'hardware che stiamo utilizzando, mostrava 10.8s pe rquest'istruzione (è il numero scritto dopo "Wall time"). + + + +✏️ **Prova tu!** Esegui la stessa istruzione con e senza `batched=True`, poi prova con un tokenizzatore lento (aggiungi `add_fast=False` al metodo `AutoTokenizer.from_pretrained()`) così che puoi controllare i tempi sul tuo hardware. + + +Ecco i risultati che otteniamo con e senza utilizzare batch, con un tokenizzatore lento e uno veloce: + +Opzioni | Tokenizzatore veloce |Tokenizzatore lento +:--------------:|:--------------------:|:-------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s + +Questo significa che utilizzare un tokenizzatore veloce con l'opzione `batched=True` è 30 volte più veloce della sua controparte lenta con `batched=False` -- ottimo! Questa è la ragione principale per cui i tokenizzatori veloci sono di default utilizzando `AutoTokenizer` (e il motivo per cui vengono chiamati "fast"). Sono in grado di raggiungere certe velocità perché dietro le quinte il codice di tokenizzazione è eseguito in Rust, un linguaggio che rende semplice l'esecuzione di codici in parallelo. + +L'esecuzione in parallelo è anche il motivo per l'aumento di velocità x6 che il tokenizzatore veloce ottiene con `batched=True`: non è possibile eseguire in parallelo una sola operazione di tokenizzazione, ma quando vuoi tokenizzare molti testi contemporaneamente puoi dividere l'esecuzione su vari processi, ognuno responsabile dei propri testi. + +`Dataset.map()` possiede inoltre alcune capacità di parallelizzazione per conto proprio. Non avendo però Rust alle proprie spalle, non può permettere a un tokenizzatore lento di raggiungere uno veloce, ma possono comunque tornare utili (soprattutto se stai utilizzando un tokenizatore che non possiede una versione veloce). Per abilitare il multiprocessing, usa l'argomenti `num_proc` e specifica il numero di processi da utilizzare quando evoci `Dataset.map()`: + +```py +slow_tokenizer = AutoTokenizer.from_pretrained("bert-base-cased", use_fast=False) + + +def slow_tokenize_function(examples): + return slow_tokenizer(examples["review"], truncation=True) + + +tokenized_dataset = drug_dataset.map(slow_tokenize_function, batched=True, num_proc=8) +``` + +Puoi sperimentare con le tempistiche per determinare il numero ottimale di processi da utilizzare; nel nostro caso 8 sembra produrre i risultati migliori. Ecco i numeri che abbiamo ottenuto con e senza multiprocessing: + +Opzioni | Tokenizzatore veloce | Tokenizzatore lento +:----------------------------:|:--------------------:|:-------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s +`batched=True`, `num_proc=8` | 6.52s | 41.3s +`batched=False`, `num_proc=8` | 9.49s | 45.2s + +Questi sono dei risultati molto più accettabili per il tokenizzatore lento, ma anche la performance dei tokenizzatori veloci è notevolmente migliorata. Notare, comunque, che non è sempre questo il caso: per valori di `num_proc` diversi da 8, i nostri test hanno mostrato che è più veloce utilizzare `batched=True` senza l'opzione `num_proc`. In generale, non raccomandiamo l'utilizzo di multiprocessing Python per tokenizzatori veloci con `batched=True`. + + + +Utilizzare `num_proc` per accelerare i processi è generalmente una buona idea, a patto che la funzione che stai utilizzando non stia già usando un qualche tipo di multiprocessing per conto proprio. + + + +Tutte queste funzionalità condensate in un unico metodo sono già molto utili, ma c'è altro! Con `Dataset.map()` e `batched=True`, è possibile modificare il numero di elementi nel tuo dataset. È particolarmente utile quando vuoi creare diverse feature di addestramento da un unico esempio, e ne avremo bisogno come parte di preprocessing per molti dei task NLP che affronteremo nel [Capitolo 7](/course/chapter7). + + + +💡 Nel machine learning, un _esempio_ è solitamente definito come un insieme di _feature_ che diamo in pasto al modello. In alcuni contesti, queste feature saranno l'insieme delle colonne in un `Dataset`, ma in altri casi (come ad esempio questo, o per il question answering), molte feature possono essere estratte da un singolo esempio, e appartenere a una sola colonna. + + + +Diamo un'occhiata a come funziona! Tokenizziamo i nostri esempi e tronchiamoli a una lunghezza massima di 128, ma chiediamo al tokenizzatore di restituire *tutti* i pezzi di testo e non solo il primo. Questo può essere fatto con `return_overflowing_tokens=True`: + +```py +def tokenize_and_split(examples): + return tokenizer( + examples["review"], + truncation=True, + max_length=128, + return_overflowing_tokens=True, + ) +``` + +Testiamo questa funzione su un esempio prima di utilizzare `Dataset.map()` sull'intero dataset: + +```py +result = tokenize_and_split(drug_dataset["train"][0]) +[len(inp) for inp in result["input_ids"]] +``` + +```python out +[128, 49] +``` + +Quindi, il nostro primo esempio nel set di train è stato trasformaro in due feature perché tokenizzato in un numero maggiore di token di quelli specificati: il primo gruppo di lunghezza 128 token e il secondo di lunghezza 49. Facciamo la stessa cosa per tutti gli elementi del dataset! + +```py +tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) +``` + +```python out +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. + +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`: + + +```py +tokenized_dataset = drug_dataset.map( + tokenize_and_split, batched=True, remove_columns=drug_dataset["train"].column_names +) +``` + +Ora funziona senza errori. Possiamo controllare che il nostro nuovo dataset contiene più elementi del dataset originale paragonando le lunghezze: + +```py +len(tokenized_dataset["train"]), len(drug_dataset["train"]) +``` + +```python out +(206772, 138514) +``` +Abbiamo già menzionato che possiamo risolvere il problema delle lunghezze discordanti cambiando la dimenzione delle vecchie colonne. Per far ciò, abbiamo bisogno del campo `overflow_to_sample_mapping` restituito dal tokenizzatore quando impostiamo `return_overflowing_tokens=True`. Così facendo avremo una mappatura degli indici delle nuove feature all'indice di campioni da cui sono state generate. Usando questa mappatura, possiamo associare a ogni chiava presente nel nostro dataset originale una lista di valori delle dimensioni giuste, ripetendo il valore di ogni esempio finché genera nuove feature: + +```py +def tokenize_and_split(examples): + result = tokenizer( + examples["review"], + truncation=True, + max_length=128, + return_overflowing_tokens=True, + ) + # Estraiamo la mappatura tra gli indici vecchi e quelli nuovi + sample_map = result.pop("overflow_to_sample_mapping") + for key, values in examples.items(): + result[key] = [values[i] for i in sample_map] + return result +``` + +Possiamo vedere come funziona con `Dataset.map()` senza aver bisogno di rimuovere le colonne vecchie: + +```py +tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) +tokenized_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'], + num_rows: 206772 + }) + test: Dataset({ + features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'], + num_rows: 68876 + }) +}) +``` + +Otteniamo lo stesso numero di feature di addestramento di prima, ma qui abbiamo conservato i campi originali. Se ti servono per un post-processing dopo aver applicato il tuo modello, potresti usare quest'approccio. + +Ora abbiamo visto come usare 🤗 Datasets per preprocessare un dataset in diversi modi. Benché le funzioni di processamento di 🤗 Datasets soddisferà la maggior parte delle esigenze del modello che vuoi addestrare, ci saranno momenti in cui avrai bisogno di utilizzare Pandas per avere funzionalità ancora più potenti, come `DataFrame.groupby()` o API di alto livello per visualizzazione. Per fortuna, 🤗 Datasets è progettato per essere utilizzato con librerie come Pandas, NumPy, PyTorch, TensorFlow e JAX. Diamo un'occhiata a come funziona. + +## Da `Dataset` a `DataFrame` e ritorno + + + +Per permettere la conversione tra librerie terze, 🤗 Datasets fornisce una funzione `Dataset.set_format()`. Questa funzione cambia il _formato di output_ del dataset, così che puoi passare a un altro formato senza modificare il _formato di dati_ soggiacente, che è Apache Arrow. La formattazione avviene direttamente _in place_. Per provare, convertiamo il nostro dataset per Pandas: + +```py +drug_dataset.set_format("pandas") +``` + +Ora quando accediamo agli elementi del dataset otteniamo un `pandas.DataFrame` e non un dizionario: + +```py +drug_dataset["train"][:3] +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
patient_iddrugNameconditionreviewratingdateusefulCountreview_length
095260Guanfacineadhd"My son is halfway through his fourth week of Intuniv..."8.0April 27, 2010192141
192703Lybrelbirth control"I used to take another oral contraceptive, which had 21 pill cycle, and was very happy- very light periods, max 5 days, no other side effects..."5.0December 14, 200917134
2138000Ortho Evrabirth control"This is my first time using any form of birth control..."8.0November 3, 20151089
+ +Creiamo un `pandas.DataFrame` per l'intero set di addestramento selezionando tutti gli elementi di `drug_dataset["train"]`: + +```py +train_df = drug_dataset["train"][:] +``` + + + +🚨 Dietro le quinte, `Dataset.set_format()` modifica il formato di restituzione del meteodo dunder `__getitem__()` del dataset. Questo significa che quando vogliamo creare un nuovo oggetto come ad esempio `train_df` da un `Dataset` in formato `"pandas"`, abbiamo bisogno di suddividere l'intero dataset per ottenere un `pandas.DataFrame`. Puoi verificare da te che `drug_dataset["train"]` ha come tipo `Dataset`, a prescindere dal formato di output. + + + +Da qui possiamo usare tutte le funzionalità Pandas che vogliamo. Ad esempio, possiamo creare un concatenamento per calcolare la distribuzione delle classi nelle voci `condition`: + +```py +frequencies = ( + train_df["condition"] + .value_counts() + .to_frame() + .reset_index() + .rename(columns={"index": "condition", "condition": "frequency"}) +) +frequencies.head() +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
conditionfrequency
0birth control27655
1depression8023
2acne5209
3anxiety4991
4pain4744
+ + +E una volta che abbiamo finito con le nostre analisi su Pandas, possiamo sempre creare un nuovo oggetto `Dataset` utilizzando la funzione `Dataset.from_pandas()`: + +```py +from datasets import Dataset + +freq_dataset = Dataset.from_pandas(frequencies) +freq_dataset +``` + +```python out +Dataset({ + features: ['condition', 'frequency'], + num_rows: 819 +}) +``` + + + +✏️ **Prova tu!** Calcola la valutazione media per i medicinali, e salviamo i risultati in un nuovo `Dataset`. + + + +Questo conclude il nostro tour delle diverse tecniche di prepocessamento disponibile in 🤗 Datasets. Per riepilogare la sezione, creiamo un set di validazione per preparare il dataset su cui addestreremo un classificatore. Prima di far ciò, resettiamo il formato di output di `drug_dataset` da `"pandas"` a `"arrow"`: + +```python +drug_dataset.reset_format() +``` + +## Creare un set di validazione + +Pur avendo un set di test che potremmo usare per la valutazione, è buona prassi lasciare il set di test intatto e creare un set di validazione sepearato durante lo sviluppo de lmodello. Una volta che sei soddisfatto della performance del tuo modello sul set di validazione, puoi proseguire con un ultimo check sul set di test. Questo processo aiuta a ridurre i rischi di overfitting sul set di test e di creare un modello che fallisce sui dati del mondo reale. + +🤗 Datasets possiede una funzione `Dataset.train_test_split()`, basata sulla famosa funzionalità da `scikit-learn`. Proviamo a utilizzarla per dividere il nostro set di addestramento in sezioni di `addestramento` e di `validazione` (impostiamo l'argomento `seed` per motivi di riproducibilità): + +```py +drug_dataset_clean = drug_dataset["train"].train_test_split(train_size=0.8, seed=42) +# Rinominare la sezione di "test" in "validazione" +drug_dataset_clean["validation"] = drug_dataset_clean.pop("test") +# Aggiungere il set "test" al nostor `DatasetDict` +drug_dataset_clean["test"] = drug_dataset["test"] +drug_dataset_clean +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 110811 + }) + validation: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 27703 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 46108 + }) +}) +``` + +Bene! Abbiamo preparato un dataset che è pronto per l'addestramento di modelli! Nella [sezione 5](/course/chapter5/5) ti mostreremo come caricare i dataset nell'Hub di Hugging Face, ma per ora concludiamo la nostra analisi esplorando alcuni modi per salvare i dataset sulla tua macchina locale. + +## Salvare un dataset + + + +Benché 🤗 Datasets memorizzi in cache tutti i dataset scaricati e le operazioni effettuate, ci sono momenti in cui vorrai salvare un dataset su disco (ad esempio, nel caso la cache venga eliminata). Come mostrato nella tabella successiva, 🤗 Datasets fornisce tre funzioni principali per salvare il tuo dataset in diversi formati: + +| Formato dati | Funzione | +| :---------: | :--------------------: | +| Arrow | `Dataset.save_to_disk()` | +| CSV | `Dataset.to_csv()` | +| JSON | `Dataset.to_json()` | + +Ad esempio, salviamo il nostro dataset pulito in formato Arrow: + +```py +drug_dataset_clean.save_to_disk("drug-reviews") +``` + +Questo creerà un dizionario con la seguente struttura: + +``` +drug-reviews/ +├── dataset_dict.json +├── test +│ ├── dataset.arrow +│ ├── dataset_info.json +│ └── state.json +├── train +│ ├── dataset.arrow +│ ├── dataset_info.json +│ ├── indices.arrow +│ └── state.json +└── validation + ├── dataset.arrow + ├── dataset_info.json + ├── indices.arrow + └── state.json +``` + +dove possiamo vedere che ogni sezione è associata alla propria tavola *dataset.arrow*, e alcuni metadata sono salvati in *dataset_info.json* e *state.json*. Puoi pensare al formato Arrow come a una tavola sofisticata di colonne e righe, ottimizzata per costruire applicazioni ad alte prestazioni che processano e trasportanto grandi dataset. + +Una volta che il dataset è stato salvato, possiamo caricarlo utilizzando la funzione `load_from_disk()`: + +```py +from datasets import load_from_disk + +drug_dataset_reloaded = load_from_disk("drug-reviews") +drug_dataset_reloaded +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 110811 + }) + validation: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 27703 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 46108 + }) +}) +``` +Per i formati CSV e JSON, dobbiamo salvare ogni sezione come file separato. Un modo per farlo è iterando sulle chiavi e i valori dell'oggetti `DatasetDict`: + +```py +for split, dataset in drug_dataset_clean.items(): + dataset.to_json(f"drug-reviews-{split}.jsonl") +``` + +Questo salva ogni sezione in [formato JSON Lines](https://jsonlines.org), in cui ogni riga del dataset è salvata come una singola riga di JSON. Ecco come appare il primo esempio: + +```py +!head -n 1 drug-reviews-train.jsonl +``` + +```python out +{"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} +``` + +Possiamo usare le tecniche studiate nella [sezione 2](/course/chapter5/2) per caricare i file JSON come segue: + +```py +data_files = { + "train": "drug-reviews-train.jsonl", + "validation": "drug-reviews-validation.jsonl", + "test": "drug-reviews-test.jsonl", +} +drug_dataset_reloaded = load_dataset("json", data_files=data_files) +``` +E questo è tutto per la nostra visita nel data wrangling con 🤗 Datasets! Ora che abbiamo un dataset pulito su cui addestrare un modello, ecco alcune idee che potresti testare: + +1. Usa le tecniche studiate nel [Capitolo 3](/course/chapter3) per addestrare un classificatore che può predirre la condizione del pazionte sulla base della recensione del medicinale. +2. Usa la pipeline `summarization` del [Capitolo 1](/course/chapter1) per generare riassunti delle recensioni. + +In seguito, daremo un'occhiata a come 🤗 Datasets ti permette di lavorare su enormi dataset senza far scoppiare il tuo portatile! diff --git a/chapters/it/chapter5/4.mdx b/chapters/it/chapter5/4.mdx new file mode 100644 index 000000000..682ef8719 --- /dev/null +++ b/chapters/it/chapter5/4.mdx @@ -0,0 +1,288 @@ +# Big data? Ci pensa 🤗 Datasets! + + + + +Al giorno d'oggi non è raro trovarsi a lavorare con dataset grandi diversi gigabyte, soprattutto quando si vuole addestrare un transformer come BERT o GPT-2 da zero. In questi casi, persino _caricare_ i dati può essere un'impresa difficile. Ad esempio, il corpus WebText utilizzato per preaddestrare GPT-2 contiente più di 8 milioni di documenti e 40gb di testo -- caricare un dataset del genere sulla RAM del tuo portatile gli farebbe venire un colpo! + +Per fortuna, 🤗 Datasets è stato sviluppato per superare queste limitazioni, e può risolvere i problemi relativi alla gestione della memoria trattando i dataset come file _memory-mapped_, e quelli relativi ai limiti del disco rigido attraverso lo _stream processing_ delle voci del corpus. + + + +In questa sezione esploreremo queste funzionalità di 🤗 Datasets con un enorme corpus di 825 GB conosciuto come [Pile](https://pile.eleuther.ai). Iniziamo! + +## Cos'è Pile? + +The Pile è un corpus testuale creato da [EleutherAI](https://www.eleuther.ai) per addestrare modelli di linguaggio su grande scala. Include un grande varietà di dataset, a partire da articoli scientifici, repository di codici da GitHub, e testi dal web filtrati. Il corpus di addestramento è disponibili in [frammenti da 14 GB](https://mystic.the-eye.eu/public/AI/pile/), ed è possibile scaricare diverse delle [componenti singole](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/). Iniziamo dando uno sguardo al dataset PubMed Abstracts, un corpus di abstract da 15 milioni di pubblicazioni in ambito biomedico da [PubMed](https://pubmed.ncbi.nlm.nih.gov/). Il dataset è in [formato JSON Lines](https://jsonlines.org) ed è stato compressato usando la libreria `zstandard`, per cui dobbiamo prima installarla: + + +```py +!pip install zstandard +``` + +Ora, possiamo caricare il dataset utilizzando il meotodo per file remoti che abbiamo visto nella [sezione 2](/course/chapter5/2): + +```py +from datasets import load_dataset + +# Ci vuole qualche minuto per l'esecuzione, quindi preparati un tè o un caffè nell'attesa :) +data_files = "https://mystic.the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst" +pubmed_dataset = load_dataset("json", data_files=data_files, split="train") +pubmed_dataset +``` + +```python out +Dataset({ + features: ['meta', 'text'], + num_rows: 15518009 +}) +``` + +Possiamo vedere che ci sono 15.518.009 righe e 2 colonne nel nostro dataset -- un bel po'! + + + +✎ 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). + + + +Ispezioniamo i contenuti del primo esempio: + +```py +pubmed_dataset[0] +``` + +```python out +{'meta': {'pmid': 11409574, 'language': 'eng'}, + '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 ...'} +``` + +Okay, questo sembra proprio l'abstract di un articolo di medicina. Ora vediamo quanta RAM è stata usata per caricare il dataset! + +## La magia del memory mapping + +Un modo semplice per calcolare l'uso di memoria su Python è utilizzando la libreria [`psutil`](https://psutil.readthedocs.io/en/latest/), che può essere installata con `pip` come segue: + +```python +!pip install psutil +``` + +`psutil` offre una classe `Process` che permette di controllare l'utilizzo della memoria del processo attuale come segue:: + +```py +import psutil + +# Process.memory_info mostra i dati in byte, quindi convertiamo in megabyte +print(f"RAM used: {psutil.Process().memory_info().rss / (1024 * 1024):.2f} MB") +``` + +```python out +RAM used: 5678.33 MB +``` + +L'attributo `rss` qui fa riferimento alla _grandezza del resident set_, che equivale alla frazione di memoria che il processo occupa nella RAM. Questo valore include inoltre la memoria utilizzata dall'interprete Python e dalle librerie caricate, per cui l'ammontare effettivo utilizzato per caricare il dataset è un po' più piccolo. Per fare un confronto, vediamo quant'è grande il dataset su disco utilizzando l'attributo `dataset_size`. Come prima, il risultato è espresso in byte, e abbiamo bisogno di convertirlo in gigabyte: + +```py +print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") +size_gb = pubmed_dataset.dataset_size / (1024**3) +print(f"Dataset size (cache file) : {size_gb:.2f} GB") +``` + +```python out +Number of files in dataset : 20979437051 +Dataset size (cache file) : 19.54 GB +``` + +Bene -- nonostante sia grande quasi 30 GB, siamo in grado di caricare e accedere al dataset utilizzando molta meno RAM! + + + +✏️ **Provaci tu!** Scegli uno dei [subset](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/) di Pile che è più grande della RAM del tuo PC o del tuo portatile, caricalo utilizzando 🤗 Datasets e calcola la quantità di RAM utilizzata. Nota che per avere un valore preciso, dovrai creare un nuovo processo. Puoi trovare le grandezze decompresse di ogni subset nella Tavola 1 dell'[articolo su Pile](https://arxiv.org/abs/2101.00027) + + + +Se hai dimestichezza con Pandas, questo risultato potrebbe sorprenderti, vista la famosa [regola di Wes Kinney](https://wesmckinney.com/blog/apache-arrow-pandas-internals/), ovvero che, in linea di massima, serve una RAM 5-10 volte più grande del dataset che vuoi caricare. Come fa 🤗 Datasets a risolvere questo problema di gestione della memoria? 🤗 Datasets tratta ogni dataset come un [file mappato in memoria](https://it.wikipedia.org/wiki/File_mappato_in_memoria), il che permette di avere un mapping tra la RAM e l'archiviazione dei file di sistema, che permette alla librera di accedere e operare su elementi del dataset senza doverli caricare completamente in memoria. + +I file mappati in memoria possono inoltre essre condivisi su più processi, il che permette a metodi come `Dataset.map()` di poter essere eseguiti in parallelo senza bisogno di spostare o copiare il dataset. Dietro le quinte, tutto ciò è realizzato dal formato di memoria [Apache Arrow](https://arrow.apache.org) e dalla libreria [`pyarrow`](https://arrow.apache.org/docs/python/index.html), che rendono più veloci il caricamento e il processamento dei dati. (per maggiori dettagli su Apache Arrow, e per un confronto con Pandas, dai un'occhiata al [post di Dejan Simic](https://towardsdatascience.com/apache-arrow-read-dataframe-with-zero-memory-69634092b1a).) Per vederlo in azione, eseguiamo un piccolo test di velocità con un loop su tutti gli elementi nel dataset PubMed Abstracts: + +```py +import timeit + +code_snippet = """batch_size = 1000 + +for idx in range(0, len(pubmed_dataset), batch_size): + _ = pubmed_dataset[idx:idx + batch_size] +""" + +time = timeit.timeit(stmt=code_snippet, number=1, globals=globals()) +print( + f"Iterated over {len(pubmed_dataset)} examples (about {size_gb:.1f} GB) in " + f"{time:.1f}s, i.e. {size_gb/time:.3f} GB/s" +) +``` + +```python out +'Iterated over 15518009 examples (about 19.5 GB) in 64.2s, i.e. 0.304 GB/s' +``` + +Abbiamo usato il modulo di Python `timeit` per calcolare il tempo di esecuzione impiegato da `code_snippet`. Tipicamente l'iterazione su un dataset impiega un tempo che va da un decimo di GB al secondo, a diversi GB al secondo. Questo funziona perfettamente per la maggior parte delle applicazioni, ma a volte avrai bisogno di lavorare con un dataset che è troppo grande persino per essere salvato sul tuo portatile. Ad esempio, se cercassimo di scaricare Pile per intero, avremo bisogno di 825 GB di spazio libero su disko! In questi casi, 🤗 Datasets permette di utilizzare processi di streaming che ci permettono di scaricare e accedere al volo ai dati, senza bisogno di scaricare l'intero dataset. Diamo un'occhiata a come funziona. + + + +💡 Nei notebook Jupyter, puoi cronometrare le celle utilizzando la [funzione magica `%%timeit`](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit) + + + +## Streaming di dataset + +Per abilitare lo streaming dei dataset devi semplicemente passare l'argomento `streaming=True` alla funzione `load_dataset()`. Ad esempio, carichiamo un'altra volta il dataset PubMed Abstract, ma in modalità streaming: + +```py +pubmed_dataset_streamed = load_dataset( + "json", data_files=data_files, split="train", streaming=True +) +``` + +Invece del solito `Dataset` che abbiamo incontrato in precedenza in questo capitolo, l'oggetto ritornato con `streaming=True' è un `IterableDataset`. Come suggerito dal nome, per accedere agli elementi di un `IterableDataset`, dobbiamo iterare di esso. Possiamo accedere al primo elemento del nostro dataset in streaming come segue: + +```py +next(iter(pubmed_dataset_streamed)) +``` + +```python out +{'meta': {'pmid': 11409574, 'language': 'eng'}, + '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 ...'} +``` + +Gli elementi di un dataset in streaming possono essere processati al volo utilizzando `IterableDataset.map()`, che è utile durante l'addestramento se hai bisogno di tokenizzare gli input. Il processo è uguale a quello che abbiamo utilizzato per tokenizzare il nostro dataset nel [Capitolo 3](/course/chapter3), con l'unica differenza che ora ritorneremo gli output uno alla volta: + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased") +tokenized_dataset = pubmed_dataset_streamed.map(lambda x: tokenizer(x["text"])) +next(iter(tokenized_dataset)) +``` + +```python out +{'input_ids': [101, 4958, 5178, 4328, 6779, ...], 'attention_mask': [1, 1, 1, 1, 1, ...]} +``` + + + +💡 Per velocizzare la tokenizzazione con lo streaming puoi passare `batchet=True`, come abbiamo visto nell'ultima sezione. Questo processerà gli esempi per batch. Di default, la grandezza di un batch è 1.000, e può essere specificata attraverso l'argomento `batch_size`. + + + +È anche possibile mescolare un dataset in streaming utilizzato `Iterabledataset.shuffle()`, ma a differenza di `Dataset.shuffle()`, questo metodo mescola solo gli elementi in un `buffer_size` predefinito: + +```py +shuffled_dataset = pubmed_dataset_streamed.shuffle(buffer_size=10_000, seed=42) +next(iter(shuffled_dataset)) +``` + +```python out +{'meta': {'pmid': 11410799, 'language': 'eng'}, + 'text': 'Randomized study of dose or schedule modification of granulocyte colony-stimulating factor in platinum-based chemotherapy for elderly patients with lung cancer ...'} +``` + +In questo esempio, abbiamo selezionato un esempio casuale dai primi 10.000 esempi nel buffer. Una volta che accediamo a un esempio, il suo posto nel buffer è subito occupato dall'esempio successivo nel corpus (in questo caso l'esempio 10.0001). Puoi inoltre selezionare gli elementi da un dataset in streaming utilizzando le funzioni `IterableDataset.take()` a `IterableDataset.skip()`, che funzionano un po' come `Dataset.select()`. Ad esempio, per selezionare i primi 5 esempi nel dataset PubMed Abstract dovremmo fare come segue: + +```py +dataset_head = pubmed_dataset_streamed.take(5) +list(dataset_head) +``` + +```python out +[{'meta': {'pmid': 11409574, 'language': 'eng'}, + 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'}, + {'meta': {'pmid': 11409575, 'language': 'eng'}, + 'text': 'Clinical signs of hypoxaemia in children with acute lower respiratory infection: indicators of oxygen therapy ...'}, + {'meta': {'pmid': 11409576, 'language': 'eng'}, + 'text': "Hypoxaemia in children with severe pneumonia in Papua New Guinea ..."}, + {'meta': {'pmid': 11409577, 'language': 'eng'}, + 'text': 'Oxygen concentrators and cylinders ...'}, + {'meta': {'pmid': 11409578, 'language': 'eng'}, + 'text': 'Oxygen supply in rural africa: a personal experience ...'}] +``` + +Allo stesso modo, è possibile utilizzare la funzione `IterableDataset.skip()` per creare sezioni di addestramento e di validazione da un dataset mescolato, come segue: + +```py +# Salta i primi 1.000 esempi, il resto viene incluso nell'insieme di addestramento +train_dataset = shuffled_dataset.skip(1000) +# Includi i primi 1.000 esempi nell'insieme di validazione +validation_dataset = shuffled_dataset.take(1000) +``` + +Concludiamo la nostra ricognizione dello streaming di dataset con un'applicazione comune: la combinazione di più dataset per creare un unico corpus. 🤗 Datasets fornisce una funzione `interleave_datasets()`, che converte una lista di oggetti `IterableDataset` in un unico `IterableDataset`, dove gli elementi del nuovo dataset sono ottenuti alternando tra gli esempi forniti. Questa funzione è particolarmente utile quando cerchiamo di combinare dataset di grandi dimensioni, come esempio possiamo utilizzare in streaming la sezione FreeLaw del Pile, un dataset di 51 GB di pareri legali dai tribunali statunitensi: + + +```py +law_dataset_streamed = load_dataset( + "json", + data_files="https://mystic.the-eye.eu/public/AI/pile_preliminary_components/FreeLaw_Opinions.jsonl.zst", + split="train", + streaming=True, +) +next(iter(law_dataset_streamed)) +``` + +```python out +{'meta': {'case_ID': '110921.json', + 'case_jurisdiction': 'scotus.tar.gz', + 'date_created': '2010-04-28T17:12:49Z'}, + 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'} +``` + +Questo dataset è abbastanza grande da mettere sotto sforzo la RAM di molto portatili, ma siamo riusciti a caricarlo e accedervi senza alcun problema! Ora cominiamo gli esempi di FreeLaw e di PubMed Abstracts con la funzione `interleave_datasets()`: + +```py +from itertools import islice +from datasets import interleave_datasets + +combined_dataset = interleave_datasets([pubmed_dataset_streamed, law_dataset_streamed]) +list(islice(combined_dataset, 2)) +``` + +```python out +[{'meta': {'pmid': 11409574, 'language': 'eng'}, + 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'}, + {'meta': {'case_ID': '110921.json', + 'case_jurisdiction': 'scotus.tar.gz', + 'date_created': '2010-04-28T17:12:49Z'}, + 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'}] +``` + +Abbiamo utilizzato la funzione `islice()` del modulo Python `itertools` per selezionare i primi due esempi dai dataset combinati, e abbiamo visto che corrispondono ai primi esempi di ognuno dei due dataset originali. + +Infine, se vuoi processare il Pile in streaming, in tutti i suoi 825 GB, puoi recuperare tutti i file preparati, come segue: + +```py +base_url = "https://mystic.the-eye.eu/public/AI/pile/" +data_files = { + "train": [base_url + "train/" + f"{idx:02d}.jsonl.zst" for idx in range(30)], + "validation": base_url + "val.jsonl.zst", + "test": base_url + "test.jsonl.zst", +} +pile_dataset = load_dataset("json", data_files=data_files, streaming=True) +next(iter(pile_dataset["train"])) +``` + +```python out +{'meta': {'pile_set_name': 'Pile-CC'}, + 'text': 'It is done, and submitted. You can play “Survival of the Tastiest” on Android, and on the web...'} +``` + + + +✏️ **Prova tu!** Usa uno dei corpora Common Crawl come [`mc4`](https://huggingface.co/datasets/mc4) oppure [`oscar`](https://huggingface.co/datasets/oscar) per crare un dataset multilingue in streaming, che rappresenta le proporzioni delle lingue parlate in un paese a tua scelta. Ad esempio, le quattro lingue ufficiali in Svizzera sono il tedesco, il francesce, l'italiano e il romancio, per cui potresti creare un corpus della Svizzera raccogliendo i campioni da Oscar, secondo la percentuale di parlanti di ognuna. + + + +Ora hai a tua disposizione tutti gli strumenti per caricare e processare dataset di ogni tipo -- ma a meno che tu non sia estremamente fortunato, arriverà un momento nel tuo cammino in cui dovrai effettivamente creare un dataset per risolvere i tuoi problemi. Questo sarà argomento della prossima sezione! diff --git a/chapters/it/chapter5/5.mdx b/chapters/it/chapter5/5.mdx new file mode 100644 index 000000000..be262d885 --- /dev/null +++ b/chapters/it/chapter5/5.mdx @@ -0,0 +1,470 @@ +# Creare il proprio dataset + + + +A volte il dataset che ti serve per la tua applicazione NLP non esiste, per cui dovrai crearlo da te. In questa sezione ti mostreremo come creare un corpus di [issue da GitHub](https://github.com/features/issues), usate solitamente per tenere traccia dei bug e delle feature nelle repository su GitHub. Questo corpus può essere usato in diversi modi, ad esempio: + +* Esplorare il tempo impiegato per chiudere un issue, o per effettuare dei pull +* Addestrare un _classificatore multiclasse_ che assegna a ogni issue dei metadati sulla base della descrizione dell'issue (ad esempio, "bug", "enhancement", "question") +* Creare un motore di ricerca semantico per trovare quale issue corrisponde a una richiesta dell'utente + +Ci focalizzeremo sulla creazione del corpus, e nella prossima sezione affronteremo la creazione di un motore di ricerca semantico. Useremo gli issue GitHub associate a un progetto open source molto popolare: 🤗 Datasets! Diamo un'occhiata a come recuperare i dati e come esplorare le informazioni contenute negli issue. + +## Recuperare i dati + +Puoi trovare tutte gli issue in 🤗 Datasets navigando nella [sezione Issues della repository](https://github.com/huggingface/datasets/issues). Come si vede dallo screenshot, al momento della scrittura c'erano 331 issue aperti e 668 issue chiusi. + +
+The GitHub issues associated with 🤗 Datasets. +
+ +Se clicchi su una di questi issue vedrai che contiene un titolo, una descrizione, e un set di etichette che caratterizzano l'issue. Un esempio è mostrato nello screenshot successivo. + +
+A typical GitHub issue in the 🤗 Datasets repository. +
+ +Per scaricare gli issue della repository, useremo la [REST API di GitHub](https://docs.github.com/en/rest) per interrogare l'[endpoint `Issues`](https://docs.github.com/en/rest/reference/issues#list-repository-issues). Questo endpoint restituisce una lista di oggetti JSON, e ogni oggetto contiene un gran numero di campi, tra cui il titolo e la descrizione, così come dei metadati circo lo status dell'issue e altro ancora. + +Una maniera conveniente di scaricare gli issue è attraverso la libreria `requests`, che rappresenta il metodo standard di fare richieste HTTP su Python. Puoi installa la libreria attraverso il codice: + +```python +!pip install requests +``` + +Una volta che la libreria è stata installata, puoi effettuare una richiesta GET all'endpoint `Issues` utilizzando la funzione `requests.get()`. Ad esempio, puoi eseguire il comando mostrato di seguito per recuperare il primo issue nella prima pagina: + +```py +import requests + +url = "https://api.github.com/repos/huggingface/datasets/issues?page=1&per_page=1" +response = requests.get(url) +``` + +L'oggetto `response` contiene un sacco di informazioni utili sulla richiesta, compreso il codice di stato HTTP: + +```py +response.status_code +``` + +```python out +200 +``` + +Lo status `200` indica che la richiesta ha avuto buon fine (puoi trovare una lista di codici di stato HTTTP [qui](https://it.wikipedia.org/wiki/Codici_di_stato_HTTP)). Ma ciò che ci interessa davvero è il _payload_, a cui è possibile accedere utilizzando diversi formati come byte, stringh, o JSON. Visto che sappiamo che i nostri issue sono in formato JSON, diamo un'occhiata al payload come segue: + +```py +response.json() +``` + +```python out +[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/2792', + 'repository_url': 'https://api.github.com/repos/huggingface/datasets', + 'labels_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/labels{/name}', + 'comments_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/comments', + 'events_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/events', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792', + 'id': 968650274, + 'node_id': 'MDExOlB1bGxSZXF1ZXN0NzEwNzUyMjc0', + 'number': 2792, + 'title': 'Update GooAQ', + 'user': {'login': 'bhavitvyamalik', + 'id': 19718818, + 'node_id': 'MDQ6VXNlcjE5NzE4ODE4', + 'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4', + 'gravatar_id': '', + 'url': 'https://api.github.com/users/bhavitvyamalik', + 'html_url': 'https://github.com/bhavitvyamalik', + 'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers', + 'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}', + 'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}', + 'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}', + 'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions', + 'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs', + 'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos', + 'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}', + 'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events', + 'type': 'User', + 'site_admin': False}, + 'labels': [], + 'state': 'open', + 'locked': False, + 'assignee': None, + 'assignees': [], + 'milestone': None, + 'comments': 1, + 'created_at': '2021-08-12T11:40:18Z', + 'updated_at': '2021-08-12T12:31:17Z', + 'closed_at': None, + 'author_association': 'CONTRIBUTOR', + 'active_lock_reason': None, + 'pull_request': {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/2792', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792', + 'diff_url': 'https://github.com/huggingface/datasets/pull/2792.diff', + 'patch_url': 'https://github.com/huggingface/datasets/pull/2792.patch'}, + 'body': '[GooAQ](https://github.com/allenai/gooaq) dataset was recently updated after splits were added for the same. This PR contains new updated GooAQ with train/val/test splits and updated README as well.', + 'performed_via_github_app': None}] +``` + +Wow, quante informazioni! Possiamo vedere alcuni campi utili come `title`, `body` e `number` che descrivono l'issue, così come informazioni sull'utente che l'ha aperto. + + + +✏️ **Prova tu!** Clicca su alcuni degli URL nel payload JSON per farti un'idea del tipo di informazione a cui è collegato ogni issue GitHub. + + + +Come descritto nella [documentazione di GitHub](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting), le richieste senza autenticazione sono limitate a 60 ogni ora. Benché possiamo aumentare il parametro della query `per_page` per ridurre il numero di richieste, raggiungerai comunque il limite su qualunque repository che ha qualche migliaio di issue. Quindi, dovresti seguire le [istruzioni](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) su come creare un _token di accesso personale_ così che puoi aumentare il limite a 5.000 richieste ogni ora. Una volta che hai ottenuto il tuo token, puoi includerlo come parte dell'header della richiesta: + +```py +GITHUB_TOKEN = xxx # inserisci qui il tuo token GitHub +headers = {"Authorization": f"token {GITHUB_TOKEN}"} +``` + + + +⚠️ Fai attenzione a non condividere un notebook con il tuo `GITHUB_TOKEN` al suo interno. Ti consigliamo di cancellare l'ultima cella una volta che l'hai eseguita per evitare di far trapelare quest'informazione accidentalmente. Meglio ancora, salva il tuo token in un file *.env* e usa la [libreria `python-dotenv`](https://github.com/theskumar/python-dotenv) per caricarlo automaticamente come una variabile d'ambiente. + + + +Ora che abbiamo il nostro token di accesso, creiamo una funzione che scarichi tutti gli issue da una repository GitHub: + +```py +import time +import math +from pathlib import Path +import pandas as pd +from tqdm.notebook import tqdm + + +def fetch_issues( + owner="huggingface", + repo="datasets", + num_issues=10_000, + rate_limit=5_000, + issues_path=Path("."), +): + if not issues_path.is_dir(): + issues_path.mkdir(exist_ok=True) + + batch = [] + all_issues = [] + per_page = 100 # Numero di issue da restituire per pagina + num_pages = math.ceil(num_issues / per_page) + base_url = "https://api.github.com/repos" + + for page in tqdm(range(num_pages)): + # La query ha state=all per ottenere sia gli issue aperti che quelli chiusi + query = f"issues?page={page}&per_page={per_page}&state=all" + issues = requests.get(f"{base_url}/{owner}/{repo}/{query}", headers=headers) + batch.extend(issues.json()) + + if len(batch) > rate_limit and len(all_issues) < num_issues: + all_issues.extend(batch) + batch = [] # puliamo la batch per il termine successivo + print(f"Reached GitHub rate limit. Sleeping for one hour ...") + time.sleep(60 * 60 + 1) + + all_issues.extend(batch) + df = pd.DataFrame.from_records(all_issues) + df.to_json(f"{issues_path}/{repo}-issues.jsonl", orient="records", lines=True) + print( + f"Downloaded all the issues for {repo}! Dataset stored at {issues_path}/{repo}-issues.jsonl" + ) +``` + +Ora quando eseguiremo `fetch_issues()`, scaricherà tutti gli issue in batch per evitare di superare il limite di GitHub del numero di richieste per ora; il risultato sarà conservato in un file _repository_name-issues.jsonl_, in cui ogni linea è un oggetto JSON che rappresenta un issue. Usiamo questa funzione per recuperare tutti gli issue da 🤗 Datasets: + +```py +# A seconda della tua connessione internet, ci potrebbe volere qualche secondo... +fetch_issues() +``` + +Una volta che gli issue sono stati scaricati, possiamo caricarli in locale usando le nuove abilità imparate nella [sezione 2](/course/chaper5/2): + +```py +issues_dataset = load_dataset("json", data_files="datasets-issues.jsonl", split="train") +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'timeline_url', 'performed_via_github_app'], + num_rows: 3019 +}) +``` + +Benissimo, abbiamo creato il nostro primo dataset da zero! Ma perché ci sono migliaia di issue quando la [sezione Issues](https://github.com/huggingface/datasets/issues) della repository 🤗 Datasets mostra circa 1,000 issue in totale 🤔? Come indicato nella [documentazione di GitHub](https://docs.github.com/en/rest/reference/issues#list-issues-assigned-to-the-authenticated-user), è perché abbiamo scaricato anche le richieste di pull: + +> GitHub's REST API v3 considers every pull request an issue, but not every issue is a pull request. For this reason, "Issues" endpoints may return both issues and pull requests in the response. You can identify pull requests by the `pull_request` key. Be aware that the `id` of a pull request returned from "Issues" endpoints will be an issue id. + +(_La REST API v3 di GitHub considera ogni richiesta di pull un issue, ma non ogni issue è una richiesta di pull. Per questa ragione, gli endpoint "Issues" potrebbe tornare sia gli issue che le richieste di pull. È possibile identificare le richieste di pull utilizzando la chiave `pull_request`. Tieni presente che l'`id` di una richiesta di pull resituita dagli endpoint `Issues` sarà un id di un issue._) + +Poichè i contenuti degli issue e delle richieste di pull sono molto diversi, facciamo un po' di preprocessing per permetterci di distinguere tra i due. + +## Pulire i dati + +Il frammento precedente della documentazione di GitHub ci dice che la colonna `pull_request` può essere utilizzata per distinguere gli issue e le richieste di pull. Diamo uno sguardo a un esempio casuale per vedere qual è la differenza. Come abbiamo fatto nella [sezione 3](/course/chapter5/3), concateneremo `Dataset.shuffle()` e `Dataset.select()` per creare un campione random, e poi zipperemo le colonne `html_url` e `pull_request` così da poter paragonare i diversi URL: + +```py +sample = issues_dataset.shuffle(seed=666).select(range(3)) + +# Stampiamo le entrate `URL` e `pull_request` +for url, pr in zip(sample["html_url"], sample["pull_request"]): + print(f">> URL: {url}") + print(f">> Pull request: {pr}\n") +``` + +```python out +>> URL: https://github.com/huggingface/datasets/pull/850 +>> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/850', 'html_url': 'https://github.com/huggingface/datasets/pull/850', 'diff_url': 'https://github.com/huggingface/datasets/pull/850.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/850.patch'} + +>> URL: https://github.com/huggingface/datasets/issues/2773 +>> Pull request: None + +>> URL: https://github.com/huggingface/datasets/pull/783 +>> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/783', 'html_url': 'https://github.com/huggingface/datasets/pull/783', 'diff_url': 'https://github.com/huggingface/datasets/pull/783.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/783.patch'} +``` + +Possiamo vedere che ogni richiesta di pull è associata a diversi URL, mentre i comuni issue hanno un'entrata `None`. Possiamo usare questa distinzione per crare una nuova colonna `is_pull_request` che controlla se il campo `pull_request` sia `None` o meno: + +```py +issues_dataset = issues_dataset.map( + lambda x: {"is_pull_request": False if x["pull_request"] is None else True} +) +``` + + + +✏️ **Prova tu!** Calcola il tempo medio che ci vuole a chiudere un issue su 🤗 Datasets. Potrebbe essere utile usare la funzione `Dataset.filter()` per eliminare le richieste di pull e gli issue aperti, e puoi usare la funzione `Dataset.set_format()` per convertire il dataset in un `DataFrame` così che puoi facilmente manipolare i timestamp `created_at` e `closed_at`. Per dei punti bonus, calcola il tempo medio che ci vuole a chiudere le richieste di pull. + + + +Benché potremmo procedere e pulire ulteriormente il dataset eliminando o rinominando alcune colonne, è solitamente buona prassi lasciare il dataset quando più intatto è possibile in questo stadio, così che può essere utilizzato facilmente in più applicazioni. + +Prima di caricare il nostro dataset sull'Hub Hugging Face, dobbiamo occuparci di una cosa che manca: i commenti associati a ogni issue e richiesta di pull. Hai indovinato, li aggiungeremo utilizzando la REST API di GitHub! + +## Estendere il dataset + +Come mostrato negli screenshot di seguito, i commenti associati a un issue o una richiesta di pull offrono una fonte molto ricca di informazioni, soprattutto se siamo interessati a costruire un motore di ricerca per rispondere alle richieste degli utenti sulla libreria. + +
+Comments associated with an issue about 🤗 Datasets. +
+ +La REST API di GitHub offre un [endpoint `Comments`](https://docs.github.com/en/rest/reference/issues#list-issue-comments) che restituisce tutti i commenti associati con un numero di issue. Testiamo quest'endpoint per vedere cosa restituisce: + +```py +issue_number = 2792 +url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments" +response = requests.get(url, headers=headers) +response.json() +``` + +```python out +[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/comments/897594128', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792#issuecomment-897594128', + 'issue_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792', + 'id': 897594128, + 'node_id': 'IC_kwDODunzps41gDMQ', + 'user': {'login': 'bhavitvyamalik', + 'id': 19718818, + 'node_id': 'MDQ6VXNlcjE5NzE4ODE4', + 'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4', + 'gravatar_id': '', + 'url': 'https://api.github.com/users/bhavitvyamalik', + 'html_url': 'https://github.com/bhavitvyamalik', + 'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers', + 'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}', + 'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}', + 'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}', + 'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions', + 'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs', + 'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos', + 'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}', + 'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events', + 'type': 'User', + 'site_admin': False}, + 'created_at': '2021-08-12T12:21:52Z', + 'updated_at': '2021-08-12T12:31:17Z', + 'author_association': 'CONTRIBUTOR', + 'body': "@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?", + 'performed_via_github_app': None}] +``` + +Possiamo vedere che il commento è archiviato nel campo `body`, quindi possiamo scvrivere una semplice funzione che restituisce tutti i commenti associati con un issue estraendo i contenuti di `body` per ogni elemento in `response.json()`: + +```py +def get_comments(issue_number): + url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments" + response = requests.get(url, headers=headers) + return [r["body"] for r in response.json()] + + +# Testiamo la nostra funzione +get_comments(2792) +``` + +```python out +["@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?"] +``` + +Sembra andar bene, quindi possiamo usare `Dataset.map()` per aggiungere una nuova colonna `comments` a ogni usse nel nostro dataset: + +```py +# A seconda della tua connessione, potrebbe volerci qualche secondo... +issues_with_comments_dataset = issues_dataset.map( + lambda x: {"comments": get_comments(x["number"])} +) +``` + +Come passaggio finale, salviamo il dataset esteso assieme ai nostri dati non processati, così da poter caricare entrambi sull'Hub: + +```py +issues_with_comments_dataset.to_json("issues-datasets-with-comments.jsonl") +``` + +## Caricare il dataset sull'Hub Hugging Face + + + +Ora che abbiamo il nostro dataset esteso, è arrivato il momento di caricarlo sull'Hub, così da poterlo condividere con la community! Per caricare il dataset useremo la [libreria 🤗 Hub](https://github.com/huggingface/huggingface_hub), che ci permette di interagire con l'Hub di Hugging Face attraverso un'API di Python. 🤗 Hub è preinstallato con 🤗 Transformers, così possiamo usarlo da subito. Ad esempio, possiamo usare la funzione `list_datastes()` per avere informazioni su tutti i dataset pubblici attualmente presenti sull'Hub: + +```py +from huggingface_hub import list_datasets + +all_datasets = list_datasets() +print(f"Number of datasets on Hub: {len(all_datasets)}") +print(all_datasets[0]) +``` + +```python out +Number of datasets on Hub: 1487 +Dataset Name: acronym_identification, Tags: ['annotations_creators:expert-generated', 'language_creators:found', 'languages:en', 'licenses:mit', 'multilinguality:monolingual', 'size_categories:10K + +✏️ **Prova tu!** Usa le tue credenziali dell'Hub Hugging Face per ottenere un token e creare una repository vuota chiamata `github-issues`. Ricordati di **non salvere mai le tue credenziali** su Colab o qualunque altra repository, perché potrebbero essere recuperate da malintenzionati. + +
+ +Ora, cloniamo la repository dall'Hub alla nostra macchina e copiamo al suo interno i file del nostro dataset. 🤗 Hub contiene una classe `Repository` che ha al suo interno molti dei comandi più comuni di Git, per cui per clonare la repository in remoto dobbiamo semplicemente fornire l'URL e il percorso locale in cui desideriamo clonare: + +```py +from huggingface_hub import Repository + +repo = Repository(local_dir="github-issues", clone_from=repo_url) +!cp issues-datasets-with-comments.jsonl github-issues/ +``` + +Di default, diverse estensioni file (ad esempio *.bin*, *.gz* e *.zip*) sono registrate da Git LFS, così che i file di grandi dimensioni possono essere gestiti all'interno dello stesso workflow. Puoi trovare una lista delle estensioni di file monitorati nel file *.gitattributes* della repository. Per includere il formato JSON Lines a questa lista, possiamo utilizzare il comando: + +```py +repo.lfs_track("*.jsonl") +``` + +Ora possiamo usare `Repository.push_to_hub()` per caricare il dataset sull'Hub: + +```py +repo.push_to_hub() +``` + +Se navighiamo fino all'URL contenuto in `repo_url`, vedremo che il file del nostro dataset è stato caricato. + +
+Our dataset repository on the Hugging Face Hub. +
+ +Da qui, chiunque può scaricare il dataset semplicemente inserendo l'ID della repository come argomento `path` di `load_dataset()`: + +```py +remote_dataset = load_dataset("lewtun/github-issues", split="train") +remote_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 2855 +}) +``` + +Bene, abbiamo caricato il nostro dataset sull'Hub, e può essere utilizzato da tutti! C'è un'altra cosa importante che dobbiamo fare: aggiungere una _dataset card_ che spiega come è stato creato il corpus, e offre altre informazioni utili per la community. + + + +💡 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. + + + +## Creare una dataset card + +I dataset ben-documentati sono più utili agli altri utenti (compreso il futuro te!), poiché spiegano il contesto per permettere agli utenti di decidere se un dataset può essere utile, e valutare gli eventuali bias o rischi associati nell'utilizzo del dataset. + +Sull'Hug di Hugging Face, queste informazioni si trovano nel file *README.md* della repository. Ci sono due passaggi principali che dovresti seguire prima di creare questo file: + +1. Usa l'[applicatione `datasets-tagging`](https://huggingface.co/datasets/tagging/) per creare tag di metadati in formato YAML. Questi tag sono usato per una serie di funzioni di ricerca sull'Hub di Hugging Face, e assicurano che il tuo dataset possa essere facilmente trovato dai membri della community. Poichè abbiamo creato un nostro dataset, dovrai clonare la repository `datasets-tagging`, ed eseguire l'applicazione in locale. Ecco com'è l'interfaccia: + +
+The `datasets-tagging` interface. +
+ +2. Leggi la [guida 🤗 Datasets](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) sulla creazione di dataset card informative, e usala come template. + +Puoi creare il file *README.md* direttamente sull'Hub, e puoi trovare un modello per una dataset card nella repository `lewtun/github-issues`. Di seguito è mostrato uno screenshot di una dataset card già compilata. + +
+A dataset card. +
+ + + +✏️ **Prova tu!** Usa l'applicazione `dataset-tagging` e la [guida 🤗 Datasets](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) per completare il file *README.md* per il tuo dataset di issue di GitHub. + + +È tutto! Abbiamo visto in questa sezione che creare un buon dataset può essere un'impresa, ma per fortuna caricarlo e condividerlo con la community è molto più semplice. Nella prossima sezione useremo il nostro nuovo dataset per creare un motore di ricerca semantico con 🤗 Datasets, che abbina alle domande gli issue e i commenti più rilevanti. + + + +✏️ **Prova tu!** Segui i passi che abbiamo eseguito in questa sezione per creare un dataset di issue GitHub per la tua libreria open source preferita (ovviamente scegli qualcosa di diverso da 🤗 Datasets!). Per punti bonus, esegui il fine-tuning di un classificatore multiclasse per predirre i tag presenti nel campo `labels`. + + + + diff --git a/chapters/it/chapter5/6.mdx b/chapters/it/chapter5/6.mdx new file mode 100644 index 000000000..087e457c6 --- /dev/null +++ b/chapters/it/chapter5/6.mdx @@ -0,0 +1,531 @@ + + +# Ricerca semantica con FAISS + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Nella [sezione 5](/course/chapter5/5) abbiamo creato un dataset di issue e commenti dalla repository GitHub di 🤗 Datasets. In questa sezione useremo queste informazioni per costrure un motore di ricerca semantico che ci può aiutare a trovare risposte alle nostre domande urgenti sulla libreria! + + + +## Usare gli embedding per la ricerca semantica + +Come abbiamo visto nel [Capitolo 1](/course/chapter1), i language model basati su Transformer rappresentano ogni token in un testo come un _vettore_, detto _embedding_. È possibile "mettere insieme" i diversi embedding per creare una rappresentazione vettoriale di un'intera frase, paragrafo o (in alcuni casi) documento. Questi embedding possono essere usati per trovare documenti simili in un corpus calcolandone la similarità, ad esempio usando il prodotto scalere (o altre misure di similarità) tra ogni embedding, e restituendo i documenti più simili. + +In questa sezione useremo gli embedding per sviluppare un motore di ricerca semantico. Questi motori di ricerca offrono diversi vantagig rispetto ai metodo convenzionali, basati sulla ricerca, all'interno dei documenti, delle parole chiavi presente in una query. + +
+Semantic search. + +
+ +## Caricare e preparare il dataset + +La prima cosa che dobbiamo fare è scaricare il nostro dataset di issue, quindi utilizziamo la libreria 🤗 Hub per scaricare i file usando l'URL dell'Hub Hugging Face: + +```py +from huggingface_hub import hf_hub_url + +data_files = hf_hub_url( + repo_id="lewtun/github-issues", + filename="datasets-issues-with-comments.jsonl", + repo_type="dataset", +) +``` + +Se conseriamo l'URL iin `data_files`, possiamo caricare il dataset utilizzando il metodo introdotto nella [sezione 2](/course/chapter5/2): + +```py +from datasets import load_dataset + +issues_dataset = load_dataset("json", data_files=data_files, split="train") +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 2855 +}) +``` + +Qui abbiamo specificato la sezione di defaul `train` in `load_dataset()`, così che questa funzione resituisce un `Dataset` invece di un `DatasetDict`. La prima cosa da fare è filtrare le richieste di pull, poichè queste tendono a essere usate raramente come risposta alle domande degli utenti, e introdurrebbero rumore nel nostro motore di ricerca. Come dovrebbe esser enoto, possiamo usare la funzione `Dataset.filter()` per escludere questi dati dal nostro dataset. Già che ci siamo, eliminiamo anche le righe senza commenti, poiché queste non presentano nessuna risposta alle domande degli utenti: + +```py +issues_dataset = issues_dataset.filter( + lambda x: (x["is_pull_request"] == False and len(x["comments"]) > 0) +) +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 771 +}) +``` + +Possiamo vedere che ci sono molte colonne nel nostro dataset, molte delle quali non servono alla costruzione del nostro motore di ricerca. Da una prospettiva di ricerca, le colonne maggiormente informative sono `title`, `body`, e `comments`, mentre `html_url` ci fornisce un link all'issue originale. Usiamo la funzione `Dataset.remove_columns()` per eliminare le colonne rimanenti: + +```py +columns = issues_dataset.column_names +columns_to_keep = ["title", "body", "html_url", "comments"] +columns_to_remove = set(columns_to_keep).symmetric_difference(columns) +issues_dataset = issues_dataset.remove_columns(columns_to_remove) +issues_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body'], + num_rows: 771 +}) +``` + +Per crare i nostri embedding arricchiremo ognu commento con il titolo e il corpo dell'issue, visto che questi campi spesso includono informazioni utili sul contesto. Poiché la nostra colonna `comment` è al momento una lista di commenti per ogni issue, dobbiamo "farla esplodere" così che ogni riga consista in una tupla `(html_url, title, body, comment)`. In panda è possibile farlo utilizzando la [funzione `Dataframe.explode()`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html), che crea una nuova riga per ogni elemento in una colonna in formato di lista, ripetendo i valori di tutte le altre colonne. Per vederlo in azione, prima di tutto passiamo al formato `DataFrame`: + +```py +issues_dataset.set_format("pandas") +df = issues_dataset[:] +``` + +Se diamo un'occhiata alla prima riga di questo `DataFrame`, possiamo vedere che ci sono quattro commenti associati con quest'issue: + +```py +df["comments"][0].tolist() +``` + +```python out +['the bug code locate in :\r\n if data_args.task_name is not None:\r\n # Downloading and loading a dataset from the hub.\r\n datasets = load_dataset("glue", data_args.task_name, cache_dir=model_args.cache_dir)', + 'Hi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com\r\n\r\nNormally, it should work if you wait a little and then retry.\r\n\r\nCould you please confirm if the problem persists?', + 'cannot connect,even by Web browser,please check that there is some problems。', + 'I can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...'] +``` + +Quando "esplodiamo" `df`, ci aspettiamo di avere una riga per ognuno di questi commenti. Controlliamo se è così: + +```py +comments_df = df.explode("comments", ignore_index=True) +comments_df.head(4) +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
html_urltitlecommentsbody
0https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comthe bug code locate in :\r\n if data_args.task_name is not None...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
1https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comHi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
2https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comcannot connect,even by Web browser,please check that there is some problems。Hello,\r\nI am trying to run run_glue.py and it gives me this error...
3https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comI can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
+ +bene, possiamo vedere che le righe sono state duplicate, e che la colonna `comment` contiene i diversi comment! Ora che abbiamo finito con Pandas, possiamo passare velocemente a `Dataset` caricando il `DataFrame` in memoria: + +```py +from datasets import Dataset + +comments_dataset = Dataset.from_pandas(comments_df) +comments_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body'], + num_rows: 2842 +}) +``` + +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. + + + +Ora che abbiamo un commento per riga, creiamo una nuova colonna `comments_length` che contiene il numero di parole per ogni commento: + +```py +comments_dataset = comments_dataset.map( + lambda x: {"comment_length": len(x["comments"].split())} +) +``` + +Possiamo usare questa nuova colonna per eliminare i commenti brevi, che solitamente includono cose del tipo "cc @lewtun" o "Grazie!", che non sono pertinenti per il nostro motore di ricerca. Non abbiamo un numero preciso da selezionare per questo filtro, ma 15 parole dovrebbero andare bene: + +```py +comments_dataset = comments_dataset.filter(lambda x: x["comment_length"] > 15) +comments_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body', 'comment_length'], + num_rows: 2098 +}) +``` + +Una volta data una pulizia al nostro dataset, possiamo concatenare il titolo, la descrizione e i commenti delle issue in una nuova colonna `text`. Come al solito , scriveremo una semplice funzione che possiamo passare a `Dataset.map()`: + +```py +def concatenate_text(examples): + return { + "text": examples["title"] + + " \n " + + examples["body"] + + " \n " + + examples["comments"] + } + + +comments_dataset = comments_dataset.map(concatenate_text) +``` + +Siamo finalmente pronti a creare degli embedding! Diamo un'occhiata. + +## Creare i text embedding + +Abbiamo visto nel [Capitolo 2](/course/chapter2) che possiamo ottenere i token embedding utilizando la classe `AutoModel`. Dobbiamo solo scegliere un checkpoint valido da cui caricare il modell. Per fortuna, esiste una libreria chiamata `sentence-transformers`, dedicata alla creazione di embedding. Seguendo la descrizione nella [documentazione](https://www.sbert.net/examples/applications/semantic-search/README.html#symmetric-vs-asymmetric-semantic-search)della libreria, il nostro caso d'uso è un esempio di _asymmetric semantic search_ perché abbiamo una breve query per cui vogliamo trovare risposte in un documento lungo, come ad esempio un commento a un issue. La [scheda di riepilogo dei modelli](https://www.sbert.net/docs/pretrained_models.html#model-overview) nella documentazione ci indica che il checkpoint `multi-qa-mpnet-base-dot-v1` ha mostrato la performance migliore per la ricerca semantica, quindi è quello che useremo per la nostra applicazione. Caricheremo anche il tokenizzatore usando lo stesso checkpoint: + +{#if fw === 'pt'} + +```py +from transformers import AutoTokenizer, AutoModel + +model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1" +tokenizer = AutoTokenizer.from_pretrained(model_ckpt) +model = AutoModel.from_pretrained(model_ckpt) +``` + +Per accelerare il processo di embedding, è bene usare la GPU per il modello e gli input, quindi: + +```py +import torch + +device = torch.device("cuda") +model.to(device) +``` + +{:else} + +```py +from transformers import AutoTokenizer, TFAutoModel + +model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1" +tokenizer = AutoTokenizer.from_pretrained(model_ckpt) +model = TFAutoModel.from_pretrained(model_ckpt, from_pt=True) +``` + +Nota che abbiamo impostato `from_pt=True` come argomento del metodo `from_pretrained()`. Questo perchè il checkpoint `multi-qa-mpnet-base-dot-v1` ha solo pesi PyTorch, quindi impostare `from_pt=True` li convertirà automaticamente in formato TensorFlow. Come puoi vedere, è molto facile passare dall'uno all'altro su 🤗 Transformers! + +{/if} + +Come abbiamo già detto prima, vorremmo rappresentare ogni entrata nel nostro corpus di issue GitHub come un vettore singolo, per cui avremo bisogno di calcolare la media, o il "pool" dei nostri token embedding. Un metodo comune è di effettuare un *CLS pooling* sull'output del nostro modello: questa tecnica su basa sul recuperare semplicemente l'ultimo stato nascosto del token speciale `[CLS]`. La funzione seguente fa proprio questo: + +```py +def cls_pooling(model_output): + return model_output.last_hidden_state[:, 0] +``` + +Poi, creeremo una funzione di supporto che: tokenizza una lista di documenti, inserire i tensori sulla GPU, li usa come input per il modello, e infine applica il CLS pooling agli output: + +{#if fw === 'pt'} + +```py +def get_embeddings(text_list): + encoded_input = tokenizer( + text_list, padding=True, truncation=True, return_tensors="pt" + ) + encoded_input = {k: v.to(device) for k, v in encoded_input.items()} + model_output = model(**encoded_input) + return cls_pooling(model_output) +``` + +Possiamo testare la funzione sul primo testo nel nostro corpus, e ispezionandone le dimensioni dell'ouput: + +```py +embedding = get_embeddings(comments_dataset["text"][0]) +embedding.shape +``` + +```python out +torch.Size([1, 768]) +``` + +Bene, abbiamo convertito la prima voce del nostro corpus in un vettore a 768 dimensioni! Possiamo usare `Dataset.map()` per applicare la nostra funzione `get_embedding()` a ogni riga del nostro corpus, quindi creiamo una nuova colonna `embedding` così: + +```py +embeddings_dataset = comments_dataset.map( + lambda x: {"embeddings": get_embeddings(x["text"]).detach().cpu().numpy()[0]} +) +``` + +{:else} + +```py +def get_embeddings(text_list): + encoded_input = tokenizer( + text_list, padding=True, truncation=True, return_tensors="tf" + ) + encoded_input = {k: v for k, v in encoded_input.items()} + model_output = model(**encoded_input) + return cls_pooling(model_output) +``` + +Possiamo testare la funzione dandole in input la prima voce testuale del nostro corpus e studiando le dimensioni dell'output: + +```py +embedding = get_embeddings(comments_dataset["text"][0]) +embedding.shape +``` + +```python out +TensorShape([1, 768]) +``` + +Bene, abbiamo convertito la prima voce del nostro corpus in un vettore a 768 dimensioni! Possiamo usare `Dataset.map()` per applicare la nostra funzione `get_embedding()` a ogni riga del nostro corpus, quindi creiamo una nuova colonna `embedding` così: + +```py +embeddings_dataset = comments_dataset.map( + lambda x: {"embeddings": get_embeddings(x["text"]).numpy()[0]} +) +``` + +{/if} + +Node che abbiamo convertito gli embedding in array NumPy -- questo perchè 🤗 Datasets ha bisogno di questo formato per indicizzare gli embedding con FAISS, che è ciò che faremo nella prossima sezione. + + +## Usare FAISS per ricerca di similarità efficiente + +Ora che abbiamo un dataset di embedding, abbiamo bisogno di un modo per effettuare una ricerca. Per far ciò, useremo una struttura specialie di 🤗 Datasets +chiamato _indice FAISS_. [FAISS](https://faiss.ai/) (Facebook AI Similarity Search) è una libreria che permette di utilizzare algoritmi efficient per ricercare e raggruppare gli embedding. + +L'idea di base dietro FAISS è di creare un formato speciale di dati chiamato _indice_ che permette di trovare quali embedding sono simili a un embedding in input. Creare un indice FAISS su 🤗 Datasets è semplice -- usiamo la funzione `Dataset.add_faiss_index()` e specificare quale colonna nel nostro dataset vorremmo indicizzare: + +```py +embeddings_dataset.add_faiss_index(column="embeddings") +``` + +Ora possiamo eseguire dele query su questo indice effettuando una ricerca degli elementi più vicini usando la funzione `Dataset.get_nearest_examples()`. Testiamolo creando un embedding per una domanda. + +{#if fw === 'pt'} + +```py +question = "How can I load a dataset offline?" +question_embedding = get_embeddings([question]).cpu().detach().numpy() +question_embedding.shape +``` + +```python out +torch.Size([1, 768]) +``` + +{:else} + +```py +question = "How can I load a dataset offline?" +question_embedding = get_embeddings([question]).numpy() +question_embedding.shape +``` + +```python out +(1, 768) +``` + +{/if} + +Proprio come con i documenti, ora abbiamo un vettore di 768 dimensioni che rappresenta la query, che possiamo confrontare con l'intero corpus per trovare gli embedding più simili: + +```py +scores, samples = embeddings_dataset.get_nearest_examples( + "embeddings", question_embedding, k=5 +) +``` + +La funzione `Dataset.get_nearest_examples()` restituisce una tupla di valori che valutano la sovrapposizione tra la query e il documento, e un set corrispondente di campioni (in questo caso, le 5 corrispondenze migliori). Salviamole in un `pandas.DataFrame`, così che possiamo ordinarle facilmente: + +```py +import pandas as pd + +samples_df = pd.DataFrame.from_dict(samples) +samples_df["scores"] = scores +samples_df.sort_values("scores", ascending=False, inplace=True) +``` + +Ora possiamo iterare sulle prime righe per vedere quanto bene la nostra query corrisponde ai commenti disponibili: + +```py +for _, row in samples_df.iterrows(): + print(f"COMMENT: {row.comments}") + print(f"SCORE: {row.scores}") + print(f"TITLE: {row.title}") + print(f"URL: {row.html_url}") + print("=" * 50) + print() +``` + +```python out +""" +COMMENT: Requiring online connection is a deal breaker in some cases unfortunately so it'd be great if offline mode is added similar to how `transformers` loads models offline fine. + +@mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like? +SCORE: 25.505046844482422 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: The local dataset builders (csv, text , json and pandas) are now part of the `datasets` package since #1726 :) +You can now use them offline +\`\`\`python +datasets = load_dataset("text", data_files=data_files) +\`\`\` + +We'll do a new release soon +SCORE: 24.555509567260742 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: I opened a PR that allows to reload modules that have already been loaded once even if there's no internet. + +Let me know if you know other ways that can make the offline mode experience better. I'd be happy to add them :) + +I already note the "freeze" modules option, to prevent local modules updates. It would be a cool feature. + +---------- + +> @mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like? + +Indeed `load_dataset` allows to load remote dataset script (squad, glue, etc.) but also you own local ones. +For example if you have a dataset script at `./my_dataset/my_dataset.py` then you can do +\`\`\`python +load_dataset("./my_dataset") +\`\`\` +and the dataset script will generate your dataset once and for all. + +---------- + +About I'm looking into having `csv`, `json`, `text`, `pandas` dataset builders already included in the `datasets` package, so that they are available offline by default, as opposed to the other datasets that require the script to be downloaded. +cf #1724 +SCORE: 24.14896583557129 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: > here is my way to load a dataset offline, but it **requires** an online machine +> +> 1. (online machine) +> +> ``` +> +> import datasets +> +> data = datasets.load_dataset(...) +> +> data.save_to_disk(/YOUR/DATASET/DIR) +> +> ``` +> +> 2. copy the dir from online to the offline machine +> +> 3. (offline machine) +> +> ``` +> +> import datasets +> +> data = datasets.load_from_disk(/SAVED/DATA/DIR) +> +> ``` +> +> +> +> HTH. + + +SCORE: 22.893993377685547 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: here is my way to load a dataset offline, but it **requires** an online machine +1. (online machine) +\`\`\` +import datasets +data = datasets.load_dataset(...) +data.save_to_disk(/YOUR/DATASET/DIR) +\`\`\` +2. copy the dir from online to the offline machine +3. (offline machine) +\`\`\` +import datasets +data = datasets.load_from_disk(/SAVED/DATA/DIR) +\`\`\` + +HTH. +SCORE: 22.406635284423828 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== +""" +``` + +Non male! Il nostro secondo risultato sembra soddisfare la nostra richiesta. + + + +✏️ **Prova tu!** Crea la tua query e prova a trovare una risposta tra i documenti raccolti. Potresti aver bisogno di aumentare il parametro `k` in `Dataset.get_nearest_examples()` per allargare la ricerca. + + diff --git a/chapters/it/chapter5/7.mdx b/chapters/it/chapter5/7.mdx new file mode 100644 index 000000000..118b04511 --- /dev/null +++ b/chapters/it/chapter5/7.mdx @@ -0,0 +1,10 @@ +# 🤗 Datasets, check! + +Beh, abbiamo fatto un bel giro nella libreria 🤗 Datasets: complimenti per aver raggiunto quest'obiettivo! Con le conoscenze che hai ottenuto da questo capitolo, sei in grado di: +- Caricare dataset da ogni luogo, sia esso l'Hub di Hugging Face, il tuo portatile, o un server in remoto della tua compagnia. +- Fare data-wrangling usando un mix delle funzioni `Dataset.map()` e `Dataset.filter()`. +- Passare velocemente tra diversi formati di dati domce Pandas e NumPy usando `Dataset.set_format()`. +- Creare il tuo dataset e condividerlo sull'Hub Hugging Face. +- Creare embedding dei tuoi documenti usando un modello Transformer, e costruire un motore di ricerca semantico usando FAISS. + +Nel [Capitolo 7](/course/chapter7), faremo buon uso di tutto questo mentre ci avventureremo nei task principali NLP, per i quali i modelli Transformer sono un'ottima soluzione. Prima di andare oltre, però, metti alla prova la tua conoscenza di 🤗 Datasets con un quiz! diff --git a/chapters/it/chapter5/8.mdx b/chapters/it/chapter5/8.mdx new file mode 100644 index 000000000..5fece0b6a --- /dev/null +++ b/chapters/it/chapter5/8.mdx @@ -0,0 +1,225 @@ + + +# Quiz di fine capitolo + +In questo capitolo abbiamo fatto un bel po' di strada! Non preoccuparti se non hai colto tutti i dettagli; i capitoli successivi ti aiuteranno a capire come funzionano le cose dietro le quinte! + +Prima di andare oltre, mettiamo alla prova ciò che hai imparato in questo capitolo. + +### 1. Usando la funzione `load_dataset()` in 🤗 Datasets, da dove puoi caricare un dataset? + +data_files di load_dataset() per caricare dataset locali.", + correct: true + }, + { + text: "L'Hub Hugging Face.", + explain: "Corretto! Puoi caricare i dataset presenti sull'Hub fornendo l'ID del dataset, ad esempio load_dataset('emotion').", + correct: true + }, + { + text: "Un server remoto", + explain: "Corretto! Puoi passare un URL nell'argomento data_files di load_dataset() per caricare file in remoto.", + correct: true + }, + ]} +/> + +### 2. Immagina di caricare uno dei task GLUE come segue: + +```py +from datasets import load_dataset + +dataset = load_dataset("glue", "mrpc", split="train") +``` + +Quale dei comandi seguenti produce un campione di 50 elementi casuali da `dataset`? + +dataset.sample(50)", + explain: "Questa risposta è sbagliata -- non esiste nessun metodo Dataset.sample()." + }, + { + text: "dataset.shuffle().select(range(50))", + explain: "Corretto! Come hai visto in questo capitolo, puoi mescolare il dataset e selezionarne i campioni.", + correct: true + }, + { + text: "dataset.select(range(50)).shuffle()", + explain: "Questa risposta è sbagliata -- anche se il codice verrebbe eseguito, mescolerebbe solo i primi 50 elementi del dataset" + } + ]} +/> + +### 3. Immagina di avere un dataset sugli animali domestici, chiamto `pets_dataset`, che ha una colonna `name` che denota il nome di ogni animale. Quale degli approcci ci permetterebbe di filtrare il dataset e lasciare solo gli animali il cui nome inizia con la lettera "L"? +pets_dataset.filter(lambda x : x['name'].startswith('L'))", + explain: "Corretto! Usare una funzione lambda di Python per questi filtri veloci è un'ottima idea. Riesci a pensare a un'altra soluzione?", + correct: true + }, + { + text: "pets_dataset.filter(lambda x['name'].startswith('L'))", + explain: "Questa risposta è sbagliata: una funzione lambda ha la forma generica lambda *argomenti* : *espressione*, per cui devi esplicitare gli argomenti in questo caso." + }, + { + text: "Creare una funzione come def filter_names(x): return x['name'].startswith('L') ed eseguire pets_dataset.filter(filter_names).", + explain: "Corretto! Proprio come Dataset.map(), puoi passare delle funzioni esplicite a Dataset.filter(). Quest'opzione è utile quando hai un'espressione complessa che non è adatta a una funzione lambda. Quale altra soluzione potrebbe funzionare?", + correct: true + } + ]} +/> + +### 4. Cos'è il memory mapping? + + + +### 5. Quali dei seguenti sono i principali vantaggi del memory mapping? + + + +### 6. Cosa causa un errore nel codice seguente? + +```py +from datasets import load_dataset + +dataset = load_dataset("allocine", streaming=True, split="train") +dataset[0] +``` + +IterableDataset.", + explain: "Corretto! Un IterableDataset è un generatore e non un contenitore, per cui puoi accedere ai suoi elementi solo usando next(iter(dataset)).", + correct: true + }, + { + text: "Il dataset allocine non ha una sezione train.", + explain: "Questa risposta è sbagliata -- controlla le [informazioni sul dataset allocine](https://huggingface.co/datasets/allocine) sull'Hub per vedere quali sezioni contiente." + } + ]} +/> + +### 7. Quali dei seguenti sono i vantaggi principali di creare una dataset card? + + + + +### 8. Cos'è la ricerca semantica? + + + +### 9. Nelle ricerche semantiche asimmetriche, solitamente si hanno: + + + +### 10. Posso usare 🤗 Datasets per caricare dati utilizzabili in altri domini, come processamento del parlato? + +dataset MNIST sull'Hub per un esempio di dati per visione artificiale." + }, + { + text: "Sì", + explain: "Questa risposta è corretta! Controlla gli eccitanti sviluppi per il parlato e la visione artificiale nella libreria 🤗 Transformers per vedere come è utilizzato 🤗 Datasets in questi domini.", + correct : true + }, + ]} +/> diff --git a/chapters/ja/chapter7/3.mdx b/chapters/ja/chapter7/3.mdx index bbb115be2..faa27116d 100644 --- a/chapters/ja/chapter7/3.mdx +++ b/chapters/ja/chapter7/3.mdx @@ -545,7 +545,7 @@ def whole_word_masking_data_collator(features): import collections import numpy as np -from transformers.data import tf_default_data_collator +from transformers.data.data_collator import tf_default_data_collator wwm_probability = 0.2 diff --git a/chapters/zh-CN/_toctree.yml b/chapters/zh-CN/_toctree.yml index 499368392..b23fbbc78 100644 --- a/chapters/zh-CN/_toctree.yml +++ b/chapters/zh-CN/_toctree.yml @@ -80,7 +80,7 @@ title: 章末小测验 quiz: 4 -- title: 5. The 🤗 Datasets library +- title: 5. 🤗 Datasets库 sections: - local: chapter5/1 title: 本章简介 @@ -99,3 +99,28 @@ - local: chapter5/8 title: 章末小测验 quiz: 5 +- title: 6. 🤗 Tokenizers库 + sections: + - local: chapter6/1 + title: 本章简介 + - local: chapter6/2 + title: 根据已有的tokenizer训练新的tokenizer + - local: chapter6/3 + title: 快速标记器的特殊能力 + - local: chapter6/3b + title: QA 管道中的快速标记器 + - local: chapter6/4 + title: 标准化和预标记化 + - local: chapter6/5 + title: 字节对编码标记化 + - local: chapter6/6 + title: WordPiece 标记化 + - local: chapter6/7 + title: Unigram标记化 + - local: chapter6/8 + title: 逐块地构建标记器 + - local: chapter6/9 + title: 标记器,回顾! + - local: chapter6/10 + title: 章末小测验 + quiz: 6 \ No newline at end of file diff --git a/chapters/zh-CN/chapter6/1.mdx b/chapters/zh-CN/chapter6/1.mdx new file mode 100644 index 000000000..a13faa5a1 --- /dev/null +++ b/chapters/zh-CN/chapter6/1.mdx @@ -0,0 +1,14 @@ +# 本章简介 + +在 [第三章] (/course/chapter3) 中,我们研究了如何在给定任务上微调模型。 当我们这样做时,我们需要使用与模型预训练相同的标记器——但是当我们想从头开始训练模型时该怎么办? 不过,使用在来自其他领域或语言的语料库上预训练的标记器通常不是最理想的。 例如,在英语语料库上训练的标记器在日语文本语料库上表现不佳,因为两种语言中空格和标点符号的使用非常不同。 + +在本章中,您将学习如何在文本语料库上训练一个全新的标记器,然后将其用于预训练语言模型。 这一切都将在 [🤗 Tokenizers](https://github.com/huggingface/tokenizers) 库的帮助下完成,该库在 [🤗 Transformers](https://github.com /huggingface/transformers) 库之内。 我们将仔细研究这个库提供的功能,并探讨快速标记器与“慢”版本的区别。 + +我们将涵盖的主题包括: + +* 如何训练一个新的标记器,类似于给定检查点在新的文本语料库上使用的标记器 +* 快速标记器的特殊功能 +* 目前 NLP 中使用的三种主要子词标记化算法之间的差异 +* 如何使用🤗 Tokenizers 库从头开始构建标记器并在一些数据上对其进行训练 + +本章介绍的技术将使您为 [第 7 章](/course/chapter7/6) 中的部分做好准备,在那部分中,我们着眼于为 Python 源代码创建语言模型。 让我们首先看一下什么是“训练”标记器? \ No newline at end of file diff --git a/chapters/zh-CN/chapter6/10.mdx b/chapters/zh-CN/chapter6/10.mdx new file mode 100644 index 000000000..703459a3d --- /dev/null +++ b/chapters/zh-CN/chapter6/10.mdx @@ -0,0 +1,268 @@ + + +# 章末小测验 + +让我们测试一下您在本章中学到了什么! + +### 1.你应该什么时候训练一个新的标记器? + + +### 2.当使用“ train_new_from_iterator()”时,使用文本列表生成器与文本列表相比有什么优点? + train_new_from_iterator() 接受的唯一类型。", + explain: "文本列表是一种特殊的文本列表生成器,因此该方法也会接受这种方法。再试一次!" + }, + { + text: "您将避免立即将整个数据集载入内存中。", + explain: "没错!每一批文本都会在你迭代的时候从内存中释放出来,如果你使用数据集存储文本的话,增益将尤其明显。", + correct: true + }, + { + text: "这将允许 Tokenizers 库使用并行处理。", + explain: "不,无论如何它都将使用并行处理。" + }, + { + text: "你训练的标记器将产生更好的文本。", + explain: "Tokenizer 不生成文本——您是否将其与语言模型混淆了?" + } + ]} +/> + +### 3.使用“快速”标记器的优点是什么? + + +### 4.“token-classification”管道如何处理跨多个标记的实体? + + +### 5.“question-answering”流水线如何处理长上下文? + + +### 6.什么是标准化? + + +### 7.什么是子词标记化的前标记化? + + +### 8.选择描述标记化 BPE 模式最准确的句子。 + + +### 9.选择适用于 WordPiece 标记模型的句子。 + + +### 10.选择适用于 Unigram 标记模式的句子。 + diff --git a/chapters/zh-CN/chapter6/2.mdx b/chapters/zh-CN/chapter6/2.mdx new file mode 100644 index 000000000..ffac12aa8 --- /dev/null +++ b/chapters/zh-CN/chapter6/2.mdx @@ -0,0 +1,256 @@ +# 根据已有的tokenizer训练新的tokenizer + + + +如果您感兴趣的语言中没有可用的语言模型,或者如果您的语料库与您的语言模型所训练的语料库有很大不同,您很可能希望从适合您的数据的标记器从头开始重新训练模型 . 这将需要在您的数据集上训练一个新的标记器。 但这究竟是什么意思? 当我们在 [第二章](/course/chapter2) 中第一次查看标记器时,我们看到大多数 Transformer 模型使用_子词分词算法_。 为了识别哪些子词是感兴趣的并且在手头的语料库中最常出现,标记器需要仔细查看语料库中的所有文本——我们称之为*training*的过程。 这种训练的确切规则取决于所使用的标记器的类型,我们将在本章后面讨论三种主要算法。 + + + + + +⚠️ 训练标记器与训练模型不同!模型训练使用随机梯度下降使每个batch的loss小一点。它本质上是随机的(这意味着在进行两次相同的训练时,您必须设置一些随机数种子才能获得相同的结果)。训练标记器是一个统计过程,它试图确定哪些子词最适合为给定的语料库选择,用于选择它们的确切规则取决于分词算法。它是确定性的,这意味着在相同的语料库上使用相同的算法进行训练时,您总是会得到相同的结果。 + + + +## 准备语料库 + +🤗 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 部分: + +```py +from datasets import load_dataset + +# This can take a few minutes to load, so grab a coffee or tea while you wait! +raw_datasets = load_dataset("code_search_net", "python") +``` + +我们可以查看训练集的部分,以查看我们数据集中有哪些列: + +```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 +}) +``` + +我们可以看到数据集将文档字符串与代码分开,并且有他们各自的标记化后的结果。 这里。 我们将只使用 `whole_func_string` 列来训练我们的标记器。 我们可以通过指定到 `train` 中的一部分来查看这些函数的一个示例: + +```py +print(raw_datasets["train"][123456]["whole_func_string"]) +``` + +应该打印以下内容: + +```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) +``` + +我们需要做的第一件事是将数据集转换为迭代器文本列表 - 例如,文本列表。使用文本列表将使我们的标记器运行得更快(训练成批文本而不是一个接一个地处理单个文本),如果我们想避免一次将所有内容都放在内存中,它应该是一个迭代器。如果你的语料库很大,你会想要利用这样一个特性:🤗 Datasets 不会将所有内容都加载到 RAM 中,而是将数据集的元素存储在磁盘上。 + +执行以下操作将创建一个包含 1,000 个文本的列表的列表,但会将所有内容加载到内存中: + +```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)] +``` + +使用 Python 生成器,我们可以避免 Python 将任何内容加载到内存中,直到真正需要为止。要创建这样的生成器,您只需要将括号替换为圆括号: + +```py +training_corpus = ( + raw_datasets["train"][i : i + 1000]["whole_func_string"] + for i in range(0, len(raw_datasets["train"]), 1000) +) +``` + +这行代码不会获取数据集的任何元素;它只是创建了一个可以在 Python 中使用的对象 **for** 环形。文本只会在您需要时加载(即,当您处于 **for** 需要它们的循环),并且一次只会加载 1,000 个文本。这样,即使您正在处理庞大的数据集,也不会耗尽所有内存。 + +生成器对象的问题在于它只能使用一次,每次访问它将给出下一个值。 下面是一个例子: + +```py +gen = (i for i in range(10)) +print(list(gen)) +print(list(gen)) +``` + +我们第一次得到了这个列表,然后是一个空列表: + +```python out +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +[] +``` + +这就是我们定义一个返回生成器的函数的原因: + +```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() +``` + +您还可以在一个 **for** 循环内部使用 **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"] +``` + +这将产生与以前完全相同的生成器,但允许您使用比列表生成式中更复杂的逻辑。 + +## 训练一个新的标记器 + +现在我们的语料库是文本批量迭代器的形式,我们准备训练一个新的标记器。为此,我们首先需要加载要与模型配对的标记器(此处为 GPT-2): + +```py +from transformers import AutoTokenizer + +old_tokenizer = AutoTokenizer.from_pretrained("gpt2") +``` + +即使我们要训练一个新的标记器,最好还是这样做以避免完全从头开始。这样,我们就不必指定任何关于标记化算法或我们想要使用的特殊标记;我们的新标记器将与 GPT-2 完全相同,唯一会改变的是输入的数据,这将取决于我们训练的语料。 + +首先让我们看看这个标记器将如何处理示例的数据: + +```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'] +``` + +这个标记器有一些特殊的符号,比如 **Ċ** 和 **Ġ** ,分别表示空格和换行符。正如我们所看到的,这不是太有效:标记器为每个空格返回单独的标记,当它可以将缩进级别组合在一起时(因为在代码中具有四个或八个空格的集合将非常普遍)。它也有点奇怪地拆分了函数名称,而习惯使用**_**的函数命名的方法。 + +让我们训练一个新的标记器,看看它是否能解决这些问题。为此,我们将使用 **train_new_from_iterator()** 方法: + +```py +tokenizer = old_tokenizer.train_new_from_iterator(training_corpus, 52000) +``` +如果您的语料库非常大,此命令可能需要一些时间,但对于这个 1.6 GB 文本数据集,它的速度非常快(在具有 12 个内核的 AMD Ryzen 9 3900X CPU 上为 1 分 16 秒)。 + +注意 **AutoTokenizer.train_new_from_iterator()** 仅当您使用的标记器是“快速(fast)”标记器时才有效。正如您将在下一节中看到的,🤗 Transformers 库包含两种类型的标记器:一些完全用 Python 编写,而另一些(快速的)由 🤗 Tokenizers 库支持,该库用[Rust](https://www.rust-lang.org)编程语言编写。 Python 是最常用于数据科学和深度学习应用程序的语言,但是当需要并行化以提高速度时,必须用另一种语言编写。例如,模型计算核心的矩阵乘法是用 CUDA 编写的,CUDA 是一个针对 GPU 的优化 C 库。 + +用纯 Python 训练一个全新的标记器会非常缓慢,这就是我们开发 🤗 Tokenizers库的原因。请注意,正如您无需学习 CUDA 语言即可在 GPU 上执行您的模型一样,您也无需学习 Rust 即可使用快速标记器。 🤗 Tokenizers 库为许多内部调用 Rust 代码的方法提供 Python 绑定;例如,并行化新标记器的训练,或者,正如我们在[第三章](/course/chapter3)中看到的,对一批输入进行标记化。 + +大多数 Transformer 模型都有可用的快速标记器(您可以[在这里](https://huggingface.co/transformers/#supported-frameworks)检查一些例外情况),如果 **AutoTokenizer** 可用,API 总是为您选择快速标记器。在下一节中,我们将看看快速标记器具有的其他一些特殊功能,这些功能对于标记分类和问答等任务非常有用。然而,在深入研究之前,让我们在上一个示例中尝试我们全新的标记器: + +```py +tokens = tokenizer.tokenize(example) +tokens +``` + +```python out +['def', 'Ġadd', '_', 'numbers', '(', 'a', ',', 'Ġb', '):', 'ĊĠĠĠ', 'Ġ"""', 'Add', 'Ġthe', 'Ġtwo', 'Ġnumbers', 'Ġ`', + 'a', '`', 'Ġand', 'Ġ`', 'b', '`."""', 'ĊĠĠĠ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb'] +``` + +在这里我们再次看到特殊符号 **Ċ** 和 **Ġ** 表示空格和换行符,但我们也可以看到我们的标记器学习了一些高度特定于 Python 函数语料库的标记:例如,有一个 **ĊĠĠĠ** 表示缩进的标记,以及 **Ġ** 表示开始文档字符串的三个引号的标记。标记器还正确使用**_**命名的规范将函数名称拆分为 .这是一个非常紧凑的表示;相比之下,在同一个例子中使用简单的英语标记器会给我们一个更长的句子: + +```py +print(len(tokens)) +print(len(old_tokenizer.tokenize(example))) +``` + +```python out +27 +36 +``` + +让我们再看一个例子: + +```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', 'ĊĠĠĠĠ'] +``` + +除了一个缩进对应的token,这里我们还可以看到一个双缩进的token: **ĊĠĠĠĠĠĠĠ** .特殊的 Python 词如 **class** , **init** , **call** , **self** , 和 **return** 每个都被标记为一个标记,我们可以看到,以及分裂 **_** 和 **.** 标记器甚至可以正确拆分驼峰式名称: **LinearLayer** 被标记为 **[ĠLinear, Layer]** . + +## 保存标记器 + +为了确保我们以后可以使用它,我们需要保存我们的新标记器。就像模型一样,是通过 **save_pretrained()** 方法: + +```py +tokenizer.save_pretrained("code-search-net-tokenizer") +``` + +这将创建一个名为的*code-search-net-tokenizer*的新文件夹,它将包含重新加载标记器所需要的所有文件。如果您想与您的同事和朋友分享这个标记器,您可以通过登录您的帐户将其上传到 Hub。如果您在notebook上工作,有一个方便的功能可以帮助您: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +这将显示一个小部件,您可以在其中输入您的 Hugging Face 登录凭据。如果您不是在notebook上工作,只需在终端中输入以下行: + +```bash +huggingface-cli login +``` + +登录后,您可以通过执行以下命令来推送您的标记器: + +```py +tokenizer.push_to_hub("code-search-net-tokenizer") +``` + +这将在您的命名空间中创建一个名为**code-search-net-tokenizer**的新存储库 ,包含标记器文件。然后,您可以使用以下命令从任何地方加载标记器的 **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") +``` + +您现在已准备好从头开始训练语言模型并根据您手头的任务对其进行微调!我们将在[第七章](/course/chapter7)进行这部分。但首先,在本章的其余部分,我们将仔细研究快速标记器,并详细探讨调用 **train_new_from_iterator()** 方法时实际发生的情况 . diff --git a/chapters/zh-CN/chapter6/3.mdx b/chapters/zh-CN/chapter6/3.mdx new file mode 100644 index 000000000..f1ed19153 --- /dev/null +++ b/chapters/zh-CN/chapter6/3.mdx @@ -0,0 +1,473 @@ + + +# 快速标记器的特殊能力 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +在本节中,我们将仔细研究 🤗 Transformers 中标记器的功能。到目前为止,我们只使用它们来标记输入或将 ID 解码回文本,但是标记器——尤其是那些由 🤗 Tokenizers 库支持的——可以做更多的事情。为了说明这些附加功能,我们将探索如何重现结果 **token-classification** (我们称之为 **ner** ) 和 **question-answering** 我们第一次在[Chapter 1](/course/chapter1)中遇到的管道. + + + +在接下来的讨论中,我们会经常区分“慢”和“快”分词器。慢速分词器是在 🤗 Transformers 库中用 Python 编写的,而快速版本是由 🤗 分词器提供的,它们是用 Rust 编写的。如果你还记得在[Chapter 5](/course/chapter5/3)中报告了快速和慢速分词器对药物审查数据集进行分词所需的时间的这张表,您应该知道为什么我们称它们为“快”和“慢”: + + | Fast tokenizer | Slow tokenizer +:--------------:|:--------------:|:-------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s + + + +⚠️ 对单个句子进行分词时,您不会总是看到相同分词器的慢速和快速版本之间的速度差异。事实上,快速版本实际上可能更慢!只有同时对大量文本进行标记时,您才能清楚地看到差异。 + + + +## 批量编码 + + + +分词器的输出不是简单的 Python 字典;我们得到的实际上是一个特殊的 **BatchEncoding** 目的。它是字典的子类(这就是为什么我们之前能够毫无问题地索引到该结果中的原因),但具有主要由快速标记器使用的附加方法。 + +除了它们的并行化能力之外,快速标记器的关键功能是它们始终跟踪最终标记来自的原始文本范围——我们称之为偏移映射.这反过来又解锁了诸如将每个单词映射到它生成的标记或将原始文本的每个字符映射到它内部的标记等功能,反之亦然。让我们看一个例子: + +```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** 类默认选择快速标记器,我们可以使用附加方法 this **BatchEncoding** 对象提供。我们有两种方法来检查我们的分词器是快的还是慢的。我们可以检查 **is_fast** 的属性 **tokenizer** : + +```python +tokenizer.is_fast +``` + +```python out +True +``` + +或检查我们的相同属性 **encoding** : + +```python +encoding.is_fast +``` + +```python out +True +``` + +让我们看看快速标记器使我们能够做什么。首先,我们可以访问令牌而无需将 ID 转换回令牌: + +```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) 和词性 (POS) 标记等任务中的标记。我们还可以使用它来屏蔽来自屏蔽语言建模中来自同一单词的所有标记(一种称为全词掩码)。 + + + +一个词是什么的概念很复杂。例如,“I'll”(“I will”的缩写)算一两个词吗?它实际上取决于分词器和它应用的预分词操作。一些标记器只是在空格上拆分,因此他们会将其视为一个词。其他人在空格顶部使用标点符号,因此将其视为两个词。 + +✏️ 试试看!从bert base cased和roberta base检查点创建一个标记器,并用它们标记“81s”。你观察到了什么?ID这个词是什么? + + + +同样,有一个 **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 +``` + +正如我们之前提到的,这一切都是由快速标记器跟踪每个标记来自列表中的文本跨度这一事实提供支持的抵消.为了说明它们的用途,接下来我们将向您展示如何复制结果 **token-classification** 手动管道。 + + + +✏️ 试试看!创建您自己的示例文本,看看您是否能理解哪些标记与单词 ID 相关联,以及如何提取单个单词的字符跨度。对于奖励积分,请尝试使用两个句子作为输入,看看句子 ID 是否对您有意义。 + + + +## 在令牌分类管道内 + +在[Chapter 1](/course/chapter1)我们第一次尝试使用 NER——任务是识别文本的哪些部分对应于个人、地点或组织等实体——使用 🤗 Transformers **pipeline()** 功能。然后,在[Chapter 2](/course/chapter2),我们看到了管道如何将从原始文本中获取预测所需的三个阶段组合在一起:标记化、通过模型传递输入和后处理。前两步 **token-classification** 管道与任何其他管道相同,但后处理稍微复杂一些 - 让我们看看如何! + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +### 通过管道获得基本结果 + +首先,让我们获取一个标记分类管道,以便我们可以手动比较一些结果。默认使用的模型是[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,标记的分数) + +- `"max"`,其中每个实体的分数是该实体中标记的最大分数(因此对于“Hugging Face”,它将是 0.98879766,即“Face”的分数) + +- `"average"`, 其中每个实体的分数是组成该实体的单词分数的平均值(因此对于“Sylvain”,与“simple”策略,但“Hugging Face”的得分为 0.9819,“Hugging”得分的平均值为 0.975,“Face”得分为 0.98879) + +现在让我们看看如何在不使用pipeline()函数的情况下获得这些结果! + +### 从输入到预测 + +{#if fw === 'pt'} + +首先,我们需要标记我们的输入并将其传递给模型。这是完全按照[Chapter 2](/course/chapter2);我们使用 **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** 在这里,我们为输入序列中的每个标记获得一组 logits: + +```py +print(inputs["input_ids"].shape) +print(outputs.logits.shape) +``` + +```python out +torch.Size([1, 19]) +torch.Size([1, 19, 9]) +``` + +{:else} + +首先,我们需要标记我们的输入并将其传递给模型。这是完全按照[Chapter 2](/course/chapter2);我们使用 **AutoXxx** 类,然后在我们的示例中使用它们: + +```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) +``` + +于我们正在使用 **AutoModelForTokenClassification** 在这里,我们为输入序列中的每个标记获得一组 logits: + +```py +print(inputs["input_ids"].shape) +print(outputs.logits.shape) +``` + +```python out +(1, 19) +(1, 19, 9) +``` + +{/if} + +我们有一个包含 19 个标记的 1 个序列的批次,模型有 9 个不同的标签,因此模型的输出具有 1 x 19 x 9 的形状。与文本分类管道一样,我们使用 softmax 函数来转换这些 logits到概率,我们采用 argmax 来获得预测(请注意,我们可以在 logits 上采用 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** 是不在任何命名实体中的标记的标签(它代表“外部”),然后我们为每种类型的实体(杂项、人员、组织和位置)提供两个标签。标签 **B-XXX** 表示令牌在实体的开头 **XXX** 和标签 **I-XXX** 表示令牌在实体内 **XXX** .例如,在当前示例中,我们希望我们的模型对令牌进行分类 **S** 作为 **B-PER** (一个人实体的开始)和令牌 **##yl** , **##va** 和 **##in** 作为 **I-PER** (在个人实体内) + +在这种情况下,您可能认为模型是错误的,因为它给出了标签 **I-PER** 对所有这四个令牌,但这并不完全正确。实际上有两种格式 **B-** 和 **I-** 标签:IOB1和IOB2. IOB2 格式(下面粉红色)是我们介绍的格式,而在 IOB1 格式(蓝色)中,标签以 **B-** 仅用于分隔相同类型的两个相邻实体。我们使用的模型在使用该格式的数据集上进行了微调,这就是它分配标签的原因 **I-PER** 到 **S** 令牌。 + +
+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}] +``` + +这和我们从第一个管道中得到的一样! + +### 分组实体 + +使用偏移量来确定每个实体的开始和结束键很方便,但该信息并不是绝对必要的。然而,当我们想要将实体组合在一起时,偏移量将为我们节省大量混乱的代码。例如,如果我们想将令牌组合在一起 **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": + # Remove the B- or I- + label = label[2:] + start, _ = offsets[idx] + + # Grab all the tokens labeled with I-label + 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 + + # The score is the mean of all the scores of the tokens in that grouped entity + 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}] +``` + +这些偏移量非常有用的另一个任务示例是问答。深入研究这个管道,我们将在下一节中进行,也将使我们能够了解 🤗 Transformers 库中标记器的最后一个功能:当我们将输入截断为给定长度时处理溢出的标记。 diff --git a/chapters/zh-CN/chapter6/3b.mdx b/chapters/zh-CN/chapter6/3b.mdx new file mode 100644 index 000000000..c6012e419 --- /dev/null +++ b/chapters/zh-CN/chapter6/3b.mdx @@ -0,0 +1,639 @@ + + +# QA 管道中的快速标记器 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +我们现在将深入研究 **question-answering** 管道,看看如何利用偏移量从上下文中获取手头问题的答案,有点像我们在上一节中对分组实体所做的。然后我们将看到我们如何处理最终被截断的非常长的上下文。如果您对问答任务不感兴趣,可以跳过此部分。 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +## 使用 `question-answering` 管道 + +正如我们在[Chapter 1](/course/chapter1),我们可以使用 **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'} +``` + +让我们看看它是如何做到这一切的! + +## 使用模型进行问答 + +与任何其他管道一样,我们首先对输入进行标记化,然后通过模型将其发送。默认情况下用于的检查点 **question-answering** 管道是[distilbert-base-cased-distilled-squad](https://huggingface.co/distilbert-base-cased-distilled-squad)(名称中的“squad”来自模型微调的数据集;我们将在[Chapter 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} + +请注意,我们将问题和上下文标记为一对,首先是问题 + +
+An example of tokenization of question and context + +
+ +问答模型的工作方式与我们迄今为止看到的模型略有不同。以上图为例,该模型已经过训练,可以预测答案开始的标记的索引(此处为 21)和答案结束处的标记的索引(此处为 24)。这就是为什么这些模型不返回一个 logits 的张量,而是返回两个:一个用于对应于答案的开始标记的 logits,另一个用于对应于答案的结束标记的 logits。由于在这种情况下我们只有一个包含 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} + +为了将这些 logits 转换为概率,我们将应用一个 softmax 函数——但在此之前,我们需要确保我们屏蔽了不属于上下文的索引。我们的输入是 **[CLS] question [SEP] context [SEP]** ,所以我们需要屏蔽问题的标记以及 **[SEP]** 令牌。我们将保留 **[CLS]** 然而,因为某些模型使用它来表示答案不在上下文中。 + +由于我们将在之后应用 softmax,我们只需要用一个大的负数替换我们想要屏蔽的 logits。在这里,我们使用 **-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} + +现在我们已经正确屏蔽了与我们不想预测的位置相对应的 logits,我们可以应用 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}]\\) where `start_index <= end_index`. + +首先让我们计算所有可能的产品: +```py +scores = start_probabilities[:, None] * end_probabilities[None, :] +``` + +{#if fw === 'pt'} + +然后我们将屏蔽这些值 **start_index > end_index** 通过将它们设置为 **0** (其他概率都是正数)。这 **torch.triu()** 函数返回作为参数传递的 2D 张量的上三角部分,因此它会为我们做屏蔽: + +```py +scores = torch.triu(scores) +``` + +{:else} +然后我们将屏蔽这些值 **start_index > end_index** 通过将它们设置为 **0** (其他概率都是正数)。这 **torch.triu()** 函数返回作为参数传递的 2D 张量的上三角部分,因此它会为我们做屏蔽: + +```py +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** 就标记而言的答案,所以现在我们只需要转换为上下文中的字符索引。这是偏移量非常有用的地方。我们可以像在令牌分类任务中一样抓住它们并使用它们: + +```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} +``` + +太棒了!这和我们的第一个例子一样! + + + +✏️ **试试看!** 使用您之前计算的最佳分数来显示五个最可能的答案。要检查您的结果,请返回到第一个管道并在调用它时传入。 + + + +## 处理长上下文 + +如果我们尝试对我们之前作为示例使用的问题和长上下文进行标记化,我们将获得比在 **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 个标记(我们需要添加填充以使最后一个条目与其他条目的大小相同)并且每个条目之间有 2 个标记的重叠。 + +让我们仔细看看标记化的结果: + +```py +print(inputs.keys()) +``` + +```python out +dict_keys(['input_ids', 'attention_mask', 'overflow_to_sample_mapping']) +``` + +正如预期的那样,我们得到了输入 ID 和一个注意力掩码。最后一个键, **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"]) +``` + +让我们: + +```python out +[0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1] +``` + +这意味着第一个句子像以前一样分成 7 个块,接下来的 4 个块来自第二个句子。 + + +现在让我们回到我们的长期背景。默认情况下 **question-answering** 管道使用的最大长度为 384,正如我们之前提到的,步长为 128,这对应于模型微调的方式(您可以通过传递 **max_seq_len** 和 **stride** 调用管道时的参数)。因此,我们将在标记化时使用这些参数。我们还将添加填充(具有相同长度的样本,因此我们可以构建张量)以及请求偏移量: + +```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** 将包含模型期望的输入 ID 和注意力掩码,以及偏移量和 **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} + +我们的长上下文被分成两部分,这意味着在它通过我们的模型后,我们将有两组开始和结束 logits: + +```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 之前首先屏蔽不属于上下文的标记。我们还屏蔽了所有填充标记(由注意掩码标记): + +{#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} + +然后我们可以使用 softmax 将我们的 logits 转换为概率: + +{#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[0] + end_idx = idx % scores.shape[0] + 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[0] + end_idx = idx % scores.shape[0] + 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} +``` + +如果我们忽略第一个结果,我们会得到与这个长上下文的管道相同的结果——是的! + + + +✏️ **试试看!** 使用您之前计算的最佳分数来显示五个最可能的答案(对于整个上下文,而不是每个块)。要检查您的结果,请返回到第一个管道并在调用它时传入。 + + + +我们对分词器功能的深入研究到此结束。我们将在下一章再次将所有这些付诸实践,届时我们将向您展示如何在一系列常见的 NLP 任务上微调模型。 diff --git a/chapters/zh-CN/chapter6/4.mdx b/chapters/zh-CN/chapter6/4.mdx new file mode 100644 index 000000000..5e58d2747 --- /dev/null +++ b/chapters/zh-CN/chapter6/4.mdx @@ -0,0 +1,124 @@ +# 标准化和预标记化 + + + +在我们更深入地研究与 Transformer 模型(字节对编码 [BPE]、WordPiece 和 Unigram)一起使用的三种最常见的子词标记化算法之前,我们将首先看一下每个标记器应用于文本的预处理。以下是标记化管道中步骤的高级概述: + +
+The tokenization pipeline. + +
+ +在将文本拆分为子标记之前(根据其模型),分词器执行两个步骤: _normalization_ 和 _pre-tokenization_. + +## 正常化 + + + +标准化步骤涉及一些常规清理,例如删除不必要的空格、小写和/或删除重音符号。如果你熟悉[Unicode normalization](http://www.unicode.org/reports/tr15/)(例如 NFC 或 NFKC),这也是 tokenizer 可能应用的东西。 + +🤗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** 检查点,标准化应用小写并删除重音。 + + + +✏️ **试试看!** 从检查点加载标记器并将相同的示例传递给它。您可以看到分词器的带壳和无壳版本之间的主要区别是什么? + + + + +## 预标记化 + + + +正如我们将在下一节中看到的,分词器不能单独在原始文本上进行训练。相反,我们首先需要将文本拆分为小实体,例如单词。这就是预标记化步骤的用武之地。 正如我们在[Chapter 2](/course/chapter2), 基于单词的标记器可以简单地将原始文本拆分为空白和标点符号的单词。这些词将是分词器在训练期间可以学习的子标记的边界。 + +要查看快速分词器如何执行预分词,我们可以使用 **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 分词器不同,此分词器不会忽略双空格 + +最后一个例子,让我们看一下基于 SentencePiece 算法的 T5 分词器: + +```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](https://github.com/google/sentencepiece) 是一种用于文本预处理的标记化算法,您可以将其与我们将在接下来的三个部分中看到的任何模型一起使用。它将文本视为 Unicode 字符序列,并用特殊字符替换空格, **▁** .与 Unigram 算法结合使用(参见[section 7](/course/chapter7/7)), 它甚至不需要预标记化步骤,这对于不使用空格字符的语言(如中文或日语)非常有用。 + +SentencePiece 的另一个主要特点是可逆标记化:由于没有对空格进行特殊处理,因此只需通过将它们连接起来并替换 **_** s 带空格——这会导致标准化的文本。正如我们之前看到的,BERT 分词器删除了重复的空格,因此它的分词是不可逆的。 + +## 算法概述 + +在下面的部分中,我们将深入研究三种主要的子词标记化算法:BPE(由 GPT-2 和其他人使用)、WordPiece(例如由 BERT 使用)和 Unigram(由 T5 和其他人使用)。在我们开始之前,这里是它们各自工作原理的快速概述。如果您还没有理解,请在阅读下一节后立即回到此表。 + + +Model | BPE | WordPiece | Unigram +:----:|:---:|:---------:|:------: +Training | Starts from a small vocabulary and learns rules to merge tokens | Starts from a small vocabulary and learns rules to merge tokens | Starts from a large vocabulary and learns rules to remove tokens +Training step | Merges the tokens corresponding to the most common pair | Merges the tokens corresponding to the pair with the best score based on the frequency of the pair, privileging pairs where each individual token is less frequent | Removes all the tokens in the vocabulary that will minimize the loss computed on the whole corpus +Learns | Merge rules and a vocabulary | Just a vocabulary | A vocabulary with a score for each token +Encoding | Splits a word into characters and applies the merges learned during training | Finds the longest subword starting from the beginning that is in the vocabulary, then does the same for the rest of the word | Finds the most likely split into tokens, using the scores learned during training + +现在让我们深入了解 BPE! \ No newline at end of file diff --git a/chapters/zh-CN/chapter6/5.mdx b/chapters/zh-CN/chapter6/5.mdx new file mode 100644 index 000000000..272210fad --- /dev/null +++ b/chapters/zh-CN/chapter6/5.mdx @@ -0,0 +1,360 @@ +# 字节对编码标记化 + + + +字节对编码(BPE)最初被开发为一种压缩文本的算法,然后在预训练 GPT 模型时被 OpenAI 用于标记化。许多 Transformer 模型都使用它,包括 GPT、GPT-2、RoBERTa、BART 和 DeBERTa。 + + + + + +💡 本节深入介绍了BPE,甚至展示了一个完整的实现。如果你只想大致了解标记化算法,可以跳到最后。 + + + +## 训练算法 + +BPE 训练首先计算语料库中使用的唯一单词集(在完成标准化和预标记化步骤之后),然后通过获取用于编写这些单词的所有符号来构建词汇表。一个非常简单的例子,假设我们的语料库使用了这五个词: + +``` +"hug", "pug", "pun", "bun", "hugs" +``` + +基础词汇将是 `["b", "g", "h", "n", "p", "s", "u"]`。对于实际情况,基本词汇表将包含所有 ASCII 字符,至少,可能还包含一些 Unicode 字符。如果您正在标记的示例使用不在训练语料库中的字符,则该字符将转换为未知标记。这就是为什么许多 NLP 模型在分析带有表情符号的内容方面非常糟糕的原因之一。 + + + +TGPT-2 和 RoBERTa 标记器(非常相似)有一个聪明的方法来处理这个问题: 他们不把单词看成是用 Unicode 字符写的,而是用字节写的。这样,基本词汇表的大小很小(256),但你能想到的每个字符仍将被包含在内,而不会最终转换为未知标记。这个技巧被称为 *字节级 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) +``` + +我们继续这样合并,直到达到我们所需的词汇量。 + + + +✏️ **现在轮到你了!**你认为下一个合并规则是什么? + + + +## 标记化算法 + +标记化紧跟训练过程,从某种意义上说,通过应用以下步骤对新输入进行标记: + +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"` 被合并,然后是 `"hu"` 和 `"g"` 被合并。 + + + +✏️ **现在轮到你了!** 你认为这个词 `"unhug"` 将如何被标记? + + + +## 实现 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") +``` + +要继续接下来的步骤,我们需要在我们的`分词`字典中应用该合并。让我们为此编写另一个函数: + +```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/zh-CN/chapter6/6.mdx b/chapters/zh-CN/chapter6/6.mdx new file mode 100644 index 000000000..c93b41898 --- /dev/null +++ b/chapters/zh-CN/chapter6/6.mdx @@ -0,0 +1,373 @@ +# WordPiece 标记化 + + + +WordPiece 是 Google 为预训练 BERT 而开发的标记化算法。此后,它在不少基于 BERT 的 Transformer 模型中得到重用,例如 DistilBERT、MobileBERT、Funnel Transformers 和 MPNET。它在训练方面与 BPE 非常相似,但实际标记化的方式不同。 + + + + + +💡 本节深入介绍 WordPiece,甚至展示完整的实现。如果您只想大致了解标记化算法,可以跳到最后。 + + + +## 训练算法 + + + +⚠️ 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) +``` + +我们继续这样处理,直到达到我们所需的词汇量。 + + + +✏️ **现在轮到你了!** 下一个合并规则是什么? + + +## 标记化算法 + +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 的另一个区别,BPE 只会将不在词汇表中的单个字符分类为未知。 + + + +✏️ **现在轮到你了!** `"pugs"` 将被如何标记? + + + +## 实现 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") +``` + +要继续接下来的步骤,我们需要在我们的 `拆分` 字典中应用该合并。让我们为此编写另一个函数: + +```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', '##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/zh-CN/chapter6/7.mdx b/chapters/zh-CN/chapter6/7.mdx new file mode 100644 index 000000000..4303d8e58 --- /dev/null +++ b/chapters/zh-CN/chapter6/7.mdx @@ -0,0 +1,381 @@ +# Unigram标记化 + + + +在 SentencePiece 中经常使用 Unigram 算法,该算法是 AlBERT、T5、mBART、Big Bird 和 XLNet 等模型使用的标记化算法。 + + + + + +💡 本节深入介绍了 Unigram,甚至展示了一个完整的实现。如果你只想大致了解标记化算法,可以跳到最后。 + + + +## 训练算法 + +与 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"] +``` + +## 标记化算法 + +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 模型计算每个分割的概率。由于所有标记都被认为是独立的,所以这个概率只是每个标记概率的乘积。例如, `"pug"` 的标记化 `["p", "u", "g"]` 的概率为: + +$$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)算法*。本质上,我们可以构建一个图来检测给定单词的可能分割,如果从_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"` 的标记化及其分数。 + + + +## 回到训练 + +现在我们已经了解了标记化的工作原理,我们可以更深入地研究训练期间使用的损失。在任何给定的阶段,这个损失是通过对语料库中的每个单词进行标记来计算的,使用当前词汇表和由语料库中每个标记的频率确定的 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 + +现在让我们在代码中实现我们迄今为止看到的所有内容。与 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 + # 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)] +``` + +我们用最优的子词对字符进行分组,以获得大小为 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 使用一种称为增强后缀数组(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()} +``` + +N现在主要功能是使用 Viterbi 算法标记单词的功能。正如我们之前看到的,该算法计算单词的每个子串的最佳分段,我们将其存储在名为 `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)): + # 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 +``` + +我们已经可以在一些词上尝试我们的初始模型: + +```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(): + # 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 +``` + +我们可以在给定的标记上尝试: + +```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]) + # 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()} +``` + +然后,为了标记一些文本,我们只需要应用预标记化,然后使用我们的 `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/zh-CN/chapter6/8.mdx b/chapters/zh-CN/chapter6/8.mdx new file mode 100644 index 000000000..c7922a72f --- /dev/null +++ b/chapters/zh-CN/chapter6/8.mdx @@ -0,0 +1,564 @@ +# 逐块地构建标记器 + + + +正如我们在前几节中看到的,标记化包括几个步骤: + +- 规范化(任何认为必要的文本清理,例如删除空格或重音符号、Unicode 规范化等) +- 预标记化(将输入拆分为单词) +- 通过模型处理输入(使用预先拆分的词来生成一系列标记) +- 后处理(添加标记器的特殊标记,生成注意力掩码和标记类型 ID) + +提醒一下,这里再看一下整个过程 + +
+The tokenization pipeline. + +
+ +🤗 Tokenizers 库旨在为每个步骤提供多个选项,您可以将它们混合和匹配在一起。在本节中,我们将看到如何从头开始构建标记器,而不是像我们[第二节 2](/course/chapter6/2)那样从旧的标记器训练新的标记器.然后,您将能够构建您能想到的任何类型的标记器! + + + +更准确地说,该库是围绕一个中央“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)找到完整的模块列表。 + +## 获取语​​料库 + +为了训练我们的新标记器,我们将使用一个小的文本语料库(因此示例运行得很快)。获取语​​料库的步骤与我们在[在这章的开始]((/course/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()** 函数是一个生成器,每次调用的时候将产生 1,000 个文本,我们将用它来训练标记器。 + +🤗 Tokenizers 也可以直接在文本文件上进行训练。以下是我们如何生成一个文本文件,其中包含我们可以在本地使用的来自 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 标记器 + +要使用 🤗 Tokenizers 库构建标记器,我们首先使用**model**实例化一个 **Tokenizer** 对象与 ,然后将 **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库中实现的非常方便的normalizer——所以让我们看看如何手动创建 BERT normalizer。 该库提供了一个“Lowercase(小写)”的normalizer和一个“StripAccents”的normalizer,您可以使用“序列”组合多个normalizer: + +```python +tokenizer.normalizer = normalizers.Sequence( + [normalizers.NFD(), normalizers.Lowercase(), normalizers.StripAccents()] +) +``` + +我们也在使用 **NFD** Unicode normalizer,否则 **StripAccents** normalizer 无法正确识别带重音的字符,因此没办法删除它们。 + +正如我们之前看到的,我们可以使用 **normalize** 的 **normalize_str()** 方法查看它对给定文本的影响: + +```python +print(tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) +``` + +```python out +hello how are u? +``` + + + +**更进一步**如果您在包含 unicode 字符的字符串上测试先前normalizers的两个版本,您肯定会注意到这两个normalizers并不完全等效。 +为了不过度使用 `normalizers.Sequence` 使版本过于复杂,我们没有包含当 `clean_text` 参数设置为 `True` 时 `BertNormalizer` 需要的正则表达式替换 - 这是默认行为。 但不要担心:通过在normalizer序列中添加两个 `normalizers.Replace` 可以在不使用方便的 `BertNormalizer` 的情况下获得完全相同的规范化。 + + + +接下来是预标记步骤。 同样,我们可以使用一个预构建的“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))] +``` + +像normalizers一样,您可以使用 **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]** 在词汇表中的ID: + +```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** .对于这些特殊标记和句子,我们还需要使用在冒号后指定相应的标记类型 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)], +) +``` + +请注意,我们需要传递特殊标记的 ID,以便标记器可以正确地将特殊标记转换为它们的 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") +``` + +然后我们可以使用**from_file()** 方法从该文件里重新加载 **Tokenizer** 对象: + +```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", # You can load from the tokenizer file, alternatively + 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 标记器 + +现在让我们构建一个 GPT-2 标记器。与 BERT 标记器一样,我们首先使用 **Tokenizer** 初始化一个BPE 模型: + +```python +tokenizer = Tokenizer(models.BPE()) +``` + +和 BERT 一样,如果我们有一个词汇表,我们可以用一个词汇表来初始化这个模型(在这种情况下,我们需要传递 `vocab` 和 `merges`),但是由于我们将从头开始训练,所以我们不需要这样去做。 我们也不需要指定“unk_token”,因为 GPT-2 使用的字节级 BPE,不需要“unk_token”。 + +GPT-2 不使用归一化器,因此我们跳过该步骤并直接进入预标记化: + +```python +tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False) +``` + +我们在此处添加到 `ByteLevel` 的选项是不在句子开头添加空格(默认为ture)。 我们可以看一下使用这个标记器对之前示例文本的预标记: + +```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 标记器 + +现在让我们构建一个 XLNet 标记器。与之前的标记器一样,我们首先使用 Unigram 模型初始化一个 **Tokenizer** : + +```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,}"), " "), + ] +) +``` + +这会取代 **“** 和 **”** 和 **”** 以及任何两个或多个空格与单个空格的序列,以及删除文本中的重音以进行标记。 + +用于任何 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', '.'] +``` + +A peculiarity of XLNet is that it puts the `` token at the end of the sentence, with a type ID of 2 (to distinguish it from the other tokens). It's padding on the left, as a result. We can deal with all the special tokens and token type IDs with a template, like for BERT, but first we have to get the IDs of the `` and `` tokens: +XLNet 的一个特点是它将`` 标记放在句子的末尾,类型ID 为2(以将其与其他标记区分开来)。它会将结果填充在左侧。 我们可以使用模板处理所有特殊标记和标记类型 ID,例如 BERT,但首先我们必须获取 `` 和 `` 标记的 ID: + +```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() +``` + +我们完成了这个标记器! 我们可以像以前一样保存标记器,如果我们想在 🤗 Transformers 中使用它,可以将它包装在 `PreTrainedTokenizerFast` 或 `XLNetTokenizerFast` 中。 使用 `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) +``` + +现在您已经了解了如何使用各种构建块来构建现有的标记器,您应该能够使用 🤗 tokenizer库编写您想要的任何标记器,并能够在 🤗 Transformers中使用它。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter6/9.mdx b/chapters/zh-CN/chapter6/9.mdx new file mode 100644 index 000000000..b2f5ea052 --- /dev/null +++ b/chapters/zh-CN/chapter6/9.mdx @@ -0,0 +1,11 @@ +# 标记器,回顾! + +完成这一章,辛苦了! + +在深入研究标记器之后,您应该: + +- 能够使用旧的标记器作为模板来训练新的标记器 +- 了解如何使用偏移量将标记的位置映射到其原始文本范围 +- 了解 BPE、WordPiece 和 Unigram 之间的区别 +- 能够混合和匹配 🤗 Tokenizers 库提供的块来构建您自己的标记器 +- 能够在 🤗 Transformers 库中使用该标记器 \ No newline at end of file From abeb4309b7994926501ebaa127173f189012d09c Mon Sep 17 00:00:00 2001 From: lewtun Date: Mon, 22 Aug 2022 08:55:06 +0100 Subject: [PATCH 26/51] Bump release (#296) --- .github/workflows/build_documentation.yml | 2 +- .github/workflows/build_pr_documentation.yml | 4 +- chapters/it/_toctree.yml | 19 + chapters/it/chapter8/1.mdx | 12 + chapters/it/chapter8/2.mdx | 364 +++++++++ chapters/it/chapter8/3.mdx | 164 ++++ chapters/it/chapter8/4.mdx | 787 +++++++++++++++++++ chapters/it/chapter8/4_tf.mdx | 485 ++++++++++++ chapters/it/chapter8/5.mdx | 91 +++ chapters/it/chapter8/6.mdx | 7 + chapters/it/chapter8/7.mdx | 199 +++++ chapters/ja/_toctree.yml | 9 + chapters/ja/chapter8/1.mdx | 12 + chapters/ja/chapter8/2.mdx | 359 +++++++++ chapters/ja/chapter8/6.mdx | 7 + chapters/vi/_toctree.yml | 86 ++ chapters/vi/chapter0/1.mdx | 118 +++ chapters/vi/chapter1/1.mdx | 56 ++ chapters/vi/chapter1/10.mdx | 278 +++++++ chapters/vi/chapter1/2.mdx | 21 + chapters/vi/chapter1/3.mdx | 327 ++++++++ chapters/vi/chapter1/4.mdx | 170 ++++ chapters/vi/chapter1/5.mdx | 17 + chapters/vi/chapter1/6.mdx | 16 + chapters/vi/chapter1/7.mdx | 16 + chapters/vi/chapter1/8.mdx | 41 + chapters/vi/chapter1/9.mdx | 11 + chapters/vi/chapter2/1.mdx | 19 + chapters/vi/chapter2/2.mdx | 353 +++++++++ chapters/vi/chapter2/3.mdx | 265 +++++++ chapters/vi/chapter2/4.mdx | 238 ++++++ chapters/vi/chapter2/5.mdx | 338 ++++++++ chapters/vi/chapter2/6.mdx | 165 ++++ chapters/vi/chapter2/7.mdx | 13 + chapters/vi/chapter2/8.mdx | 307 ++++++++ chapters/vi/chapter3/1.mdx | 21 + chapters/vi/chapter3/2.mdx | 381 +++++++++ chapters/vi/chapter3/3.mdx | 170 ++++ chapters/vi/chapter3/3_tf.mdx | 189 +++++ chapters/vi/chapter3/4.mdx | 358 +++++++++ chapters/vi/chapter3/5.mdx | 20 + chapters/vi/chapter3/6.mdx | 296 +++++++ chapters/vi/chapter4/1.mdx | 17 + chapters/vi/chapter4/2.mdx | 129 +++ chapters/vi/chapter4/3.mdx | 702 +++++++++++++++++ chapters/vi/chapter4/4.mdx | 82 ++ chapters/vi/chapter4/5.mdx | 7 + chapters/vi/chapter4/6.mdx | 231 ++++++ chapters/vi/event/1.mdx | 165 ++++ 49 files changed, 8141 insertions(+), 3 deletions(-) create mode 100644 chapters/it/chapter8/1.mdx create mode 100644 chapters/it/chapter8/2.mdx create mode 100644 chapters/it/chapter8/3.mdx create mode 100644 chapters/it/chapter8/4.mdx create mode 100644 chapters/it/chapter8/4_tf.mdx create mode 100644 chapters/it/chapter8/5.mdx create mode 100644 chapters/it/chapter8/6.mdx create mode 100644 chapters/it/chapter8/7.mdx create mode 100644 chapters/ja/chapter8/1.mdx create mode 100644 chapters/ja/chapter8/2.mdx create mode 100644 chapters/ja/chapter8/6.mdx create mode 100644 chapters/vi/_toctree.yml create mode 100644 chapters/vi/chapter0/1.mdx create mode 100644 chapters/vi/chapter1/1.mdx create mode 100644 chapters/vi/chapter1/10.mdx create mode 100644 chapters/vi/chapter1/2.mdx create mode 100644 chapters/vi/chapter1/3.mdx create mode 100644 chapters/vi/chapter1/4.mdx create mode 100644 chapters/vi/chapter1/5.mdx create mode 100644 chapters/vi/chapter1/6.mdx create mode 100644 chapters/vi/chapter1/7.mdx create mode 100644 chapters/vi/chapter1/8.mdx create mode 100644 chapters/vi/chapter1/9.mdx create mode 100644 chapters/vi/chapter2/1.mdx create mode 100644 chapters/vi/chapter2/2.mdx create mode 100644 chapters/vi/chapter2/3.mdx create mode 100644 chapters/vi/chapter2/4.mdx create mode 100644 chapters/vi/chapter2/5.mdx create mode 100644 chapters/vi/chapter2/6.mdx create mode 100644 chapters/vi/chapter2/7.mdx create mode 100644 chapters/vi/chapter2/8.mdx create mode 100644 chapters/vi/chapter3/1.mdx create mode 100644 chapters/vi/chapter3/2.mdx create mode 100644 chapters/vi/chapter3/3.mdx create mode 100644 chapters/vi/chapter3/3_tf.mdx create mode 100644 chapters/vi/chapter3/4.mdx create mode 100644 chapters/vi/chapter3/5.mdx create mode 100644 chapters/vi/chapter3/6.mdx create mode 100644 chapters/vi/chapter4/1.mdx create mode 100644 chapters/vi/chapter4/2.mdx create mode 100644 chapters/vi/chapter4/3.mdx create mode 100644 chapters/vi/chapter4/4.mdx create mode 100644 chapters/vi/chapter4/5.mdx create mode 100644 chapters/vi/chapter4/6.mdx create mode 100644 chapters/vi/event/1.mdx diff --git a/.github/workflows/build_documentation.yml b/.github/workflows/build_documentation.yml index 2fc4430c2..b730778f2 100644 --- a/.github/workflows/build_documentation.yml +++ b/.github/workflows/build_documentation.yml @@ -14,6 +14,6 @@ jobs: package: course path_to_docs: course/chapters/ additional_args: --not_python_module - languages: ar bn de en es fa fr gj he hi it ja ko pt ru th tr zh-CN zh-TW + languages: ar bn de en es fa fr gj he hi it ja ko pt ru th tr vi zh-CN zh-TW secrets: token: ${{ secrets.HUGGINGFACE_PUSH }} diff --git a/.github/workflows/build_pr_documentation.yml b/.github/workflows/build_pr_documentation.yml index 0e3728c20..51347210f 100644 --- a/.github/workflows/build_pr_documentation.yml +++ b/.github/workflows/build_pr_documentation.yml @@ -16,5 +16,5 @@ jobs: package: course path_to_docs: course/chapters/ additional_args: --not_python_module - languages: ar bn de en es fa fr gj he hi it ja ko pt ru th tr zh-CN zh-TW - hub_base_path: https://moon-ci-docs.huggingface.co \ No newline at end of file + languages: ar bn de en es fa fr gj he hi it ja ko pt ru th tr vi zh-CN zh-TW + hub_base_path: https://moon-ci-docs.huggingface.co diff --git a/chapters/it/_toctree.yml b/chapters/it/_toctree.yml index 40c971827..4174fa0f2 100644 --- a/chapters/it/_toctree.yml +++ b/chapters/it/_toctree.yml @@ -59,3 +59,22 @@ - local: chapter5/8 title: Quiz di fine capitolo quiz: 5 + +- title: 8. Come chiedere un aiuto + sections: + - local: chapter8/1 + title: Introduzione + - local: chapter8/2 + title: Cosa fare quando si riceve un errore + - local: chapter8/3 + title: Chiedere aiuto sui forum + - local: chapter8/4 + title: Fare il debug della training pipeline + local_fw: { pt: chapter8/4, tf: chapter8/4_tf } + - local: chapter8/5 + title: Come scrivere un issue correttamente + - local: chapter8/6 + title: Parte 2 completata! + - local: chapter8/7 + title: Quiz di fine capitolo + quiz: 8 diff --git a/chapters/it/chapter8/1.mdx b/chapters/it/chapter8/1.mdx new file mode 100644 index 000000000..7b34c6a70 --- /dev/null +++ b/chapters/it/chapter8/1.mdx @@ -0,0 +1,12 @@ +# Introduzione + +Ora che sai come affrontare i problemi più comuni nel campo del NLP con i 🤗 Transformers, dovresti essere in grado d'iniziare a lavorare sui tuoi progetti personali! In questo capitolo descriveremo cosa fare quando si incontra un problema. Imparerai come eseguire il debug del tuo codice o del tuo training e come chiedere aiuto alla community se non riesci a risolvere il problema in autonomia. E, se pensi di aver trovato un bug in una delle librerie di Hugging Face, ti mostreremo il modo migliore per segnalarlo, in modo che il problema venga risolto il più rapidamente possibile. + +Più precisamente, in questo capitolo impareremo: + +- Cosa fare come prima cosa quando si riceve un errore +- Come chiedere aiuto nei [forum](https://discuss.huggingface.co/) +- Come eseguire il debug della training pipeline +- Come scrivere un issue adeguatamente + +Ovviamente, niente di tutto ciò è specifico a 🤗 Transformers o all'ecosistema di Hugging Face; le lezioni di questo capitolo sono applicabili alla maggior parte dei progetti open source! diff --git a/chapters/it/chapter8/2.mdx b/chapters/it/chapter8/2.mdx new file mode 100644 index 000000000..75b43ed9a --- /dev/null +++ b/chapters/it/chapter8/2.mdx @@ -0,0 +1,364 @@ +# Cosa fare quando si riceve un errore + + + +In questa sezione esamineremo alcuni errori comuni che possono verificarsi quando si cerca di generare previsioni dal modello Transformer appena affinato. Questo ti preparerà alla [sezione 4](/course/chapter8/section4), in cui esploreremo come eseguire il debug della fase di training. + + + +Per questa sezione, abbiamo preparato un [template repository del modello](https://huggingface.co/lewtun/distilbert-base-uncased-finetuned-squad-d5716d28) e, se vuoi eseguire il codice di questo capitolo, dovrai prima copiare il modello nel tuo account su [Hugging Face Hub](https://huggingface.co). Per farlo, occorre innanzitutto effettuare il login eseguendo una delle seguenti operazioni in un Jupyter notebook: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +o il seguente nel tuo terminale preferito: + +```bash +huggingface-cli login +``` + +Questo chiederà di inserire il nome utente e la password e salverà un token in *~/.cache/huggingface/*. Una volta effettuato l'accesso, è possibile copiare il template repository con la seguente funzione: + +```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(): + # Clone the repo and extract the local path + template_repo_id = "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28" + commit_hash = "be3eaffc28669d7932492681cd5f3e8905e358b4" + template_repo_dir = snapshot_download(template_repo_id, revision=commit_hash) + # Create an empty repo on the Hub + model_name = template_repo_id.split("/")[1] + create_repo(model_name, exist_ok=True) + # Clone the empty repo + new_repo_id = get_full_repo_name(model_name) + new_repo_dir = model_name + repo = Repository(local_dir=new_repo_dir, clone_from=new_repo_id) + # Copy files + copy_tree(template_repo_dir, new_repo_dir) + # Push to Hub + repo.push_to_hub() +``` + +A questo punto, quando si esegue `copy_repository_template()`, verrà creata una copia del template repository nel proprio account. + +## Fare il debug della pipeline di 🤗 Transformers + +Per iniziare il nostro viaggio nel fantastico mondo del debug dei modelli Transformer, considera lo scenario seguente: stai lavorando con un/a collega a un progetto di risposta alle domande per aiutare i clienti di un sito web di e-commerce a trovare risposte sui prodotti di consumo. Il/La collega ti invia un messaggio del tipo: + +> Ciao! Ho appena fatto un esperimento utilizzando le tecniche del [Capitolo 7](/course/chapter7/7) del corso di Hugging Face e ho ottenuto ottimi risultati su SQuAD! Penso che possiamo usare questo modello come punto di partenza per il nostro progetto. L'ID del modello sull'Hub è "lewtun/distillbert-base-uncased-finetuned-squad-d5716d28". Provalo pure :) + +e la prima cosa che pensi è di caricare il modello usando la `pipeline` di 🤗 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 +""" +``` + +Oh no, sembra che qualcosa sia andato storto! Se sei alle prime armi con la programmazione, questo tipo di errori può sembrare un po' criptico all'inizio (cos'è un `OSError`?!). L'errore visualizzato qui è solo l'ultima parte di un messaggio di errore molto più ampio, chiamato _Python traceback_ (anche detto stack trace). Per esempio, se si esegue questo codice su Google Colab, si dovrebbe vedere qualcosa di simile alla seguente schermata: + +
+A Python traceback. +
+ +Questi messaggi contengono molte informazioni, quindi analizziamo insieme le parti principali. La prima cosa da notare è che i traceback devono essere letti _dal basso verso l'alto_. Questo può sembrare strano se si è abituati a leggere dall'alto verso il basso, ma riflette il fatto che il traceback mostra la sequenza di chiamate delle funzioni che la `pipeline` effettua quando scarica il modello e il tokenizer. (Dai un'occhiata al [Capitolo 2](/course/chapter2) per maggiori dettagli su come funziona la `pipeline`.) + + + +🚨 Hai notato quel riquadro blu intorno a "6 frames" nel traceback di Google Colab? È una funzionalità speciale di Colab, che comprime il traceback in "frame". Se non riesci a trovare l'origine di un errore, assicurati di espandere l'intero traceback facendo clic su quelle due piccole frecce. + + + +Ciò significa che l'ultima riga del traceback indica l'ultimo messaggio di errore e fornisce il nome dell'eccezione sollevata. In questo caso, il tipo di eccezione è `OSError`, che indica un errore legato al sistema. Leggendo il messaggio di errore, si può notare che sembra esserci un problema con il file *config.json* del modello e vengono forniti due suggerimenti per risolverlo: + +```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 +""" +``` + + + +💡 Se vedi un messaggio di errore che è difficile da capire, copia e incolla il messaggio nella barra di ricerca di Google o di [Stack Overflow](https://stackoverflow.com/) (sì, davvero!). C'è una buona probabilità che non sei la prima persona a riscontrare l'errore, e questo è un buon modo per trovare le soluzioni pubblicate da altri utenti della community. Ad esempio, cercando `OSError: Can't load config for` su Stack Overflow si ottengono diversi [risultati](https://stackoverflow.com/search?q=OSError%3A+Can%27t+load+config+for+) che possono essere usati come punto di partenza per risolvere il problema. + + + +Il primo suggerimento ci chiede di verificare se l'ID del modello è effettivamente corretto, quindi la prima cosa da fare è copiare l'identificativo e incollarlo nella barra di ricerca dell'Hub: + +
+The wrong model name. +
+ +Mmm, sembra proprio che il modello del/la nostro/a collega non sia sull'Hub... aha, ma c'è un errore di battitura nel nome del modello! DistilBERT ha solo una "l" nel suo nome, quindi correggiamolo e cerchiamo invece "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28": + +
+The right model name. +
+ +Ok, questo c'è. Ora proviamo a scaricare di nuovo il modello con l'ID corretto: + +```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 +""" +``` + +Argh, è fallito ancora - benvenuti nella routine quotidiana di un machine learning engineer! Poiché abbiamo aggiustato l'ID del modello, il problema deve essere nel repository stesso. Un modo rapido per accedere al contenuto di un repository sul 🤗 Hub è la funzione `list_repo_files()` della libreria `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'] +``` + +Interessante: non sembra esserci un file *config.json* nel repository! Non c'è da stupirsi che la nostra `pipeline` non riesca a caricare il modello; il/la nostro/a collega deve aver dimenticato di inserire questo file nell'Hub dopo averlo affinato. In questo caso, il problema sembra abbastanza semplice da risolvere: potremmo chiedere loro di aggiungere il file, oppure, dato che possiamo vedere dall'ID del modello che il modello pre-addestrato usato è [`distilbert-base-uncased`](https://huggingface.co/distilbert-base-uncased), possiamo scaricare la configurazione di questo modello e inserirla nel nostro repository per vedere se questo risolve il problema. Proviamo. Utilizzando le tecniche apprese nel [Capitolo 2](/course/chapter2), possiamo scaricare la configurazione del modello con la classe `AutoConfig`: + +```python +from transformers import AutoConfig + +pretrained_checkpoint = "distilbert-base-uncased" +config = AutoConfig.from_pretrained(pretrained_checkpoint) +``` + + + +🚨 L'approccio che stiamo adottando non è infallibile, poiché il/la nostro/a collega potrebbe aver modificato la configurazione di `distilbert-base-uncased` prima di affinare il modello. Nella vita reale, dovremmo verificare prima con loro, ma per lo scopo di questa sezione assumeremo che abbiano usato la configurazione predefinita. + + + +Possiamo quindi inviarlo al nostro repository del modello con la funzione `push_to_hub()` della configurazione: + +```python +config.push_to_hub(model_checkpoint, commit_message="Add config.json") +``` + +Ora possiamo verificare se ha funzionato, caricando il modello dall'ultimo commit del ramo `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'} +``` + +Woohoo, ha funzionato! Riassumiamo quello che hai appena imparato: + +- I messaggi di errore in Python sono noti come _traceback_ e vengono letti dal basso verso l'alto. L'ultima riga del messaggio di errore di solito contiene le informazioni necessarie per individuare l'origine del problema. +- Se l'ultima riga non contiene informazioni sufficienti, risali il traceback e vedi se riesci a identificare in quale punto del codice sorgente si verifica l'errore. +- Se nessuno dei messaggi di errore può aiutare a individuare il problema, provare a cercare online una soluzione a un problema simile. +- Il `huggingface_hub' +// 🤗 Hub? +fornisce una serie di strumenti che si possono usare per interagire e fare il debug dei repository su Hub. + +Ora che sappiamo come eseguire il debug di una pipeline, diamo un'occhiata a un esempio più complicato nel forward pass del modello stesso. + +## Debug del forward pass del modello + +Sebbene la `pipeline` sia ottima per la maggior parte delle applicazioni in cui è necessario generare previsioni rapidamente, a volte è necessario accedere ai logit del modello (ad esempio, se si desidera applicare un post-processing personalizzato). Per vedere cosa potrebbe andare storto in questo caso, iniziamo prendendo il modello e il tokenizer dalla nostra `pipeline`: + +```python +tokenizer = reader.tokenizer +model = reader.model +``` + +Poi abbiamo bisogno di una domanda, quindi vediamo se i nostri framework preferiti sono supportati: + +```python +question = "Which frameworks can I use?" +``` + +Come abbiamo visto nel [Capitolo 7](/course/chapter7), i passaggi tipici da svolgere sono la tokenizzazione degli input, l'estrazione dei logit dei token iniziali e finali e la decodifica dell'intervallo di risposta: + +```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 +# Get the most likely beginning of answer with the argmax of the score +answer_start = torch.argmax(answer_start_scores) +# Get the most likely end of answer with the argmax of the score +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' +""" +``` + +Oh cielo, sembra che ci sia un bug nel nostro codice! Ma non avere paura di un po' di debug. Puoi usare il debugger di Python in un notebook: + + + +o dal terminale: + + + +Qui, leggendo il messaggio di errore vediamo che l'oggetto `'list'` non ha attributo `'size'`, e possiamo vedere una `-->` freccia che punta alla riga in cui il problema è stato sollevato in `model(**inputs)`. Possiamo eseguire il debug interattivo utilizzando il debugger di Python, ma per ora ci limiteremo a stampare una parte di `input` per vedere cosa abbiamo: + +```python +inputs["input_ids"][:5] +``` + +```python out +[101, 2029, 7705, 2015, 2064] +``` + +Questo sembra certamente una normale `list` di Python, ma ricontrolliamo il `type`: + +```python +type(inputs["input_ids"]) +``` + +```python out +list +``` + +Sì, questa è sicuramente una `list` di Python. Cos'è andato storto? Ricordiamo dal [Capitolo 2](/course/chapter2) che le classi `AutoModelForXxx` in 🤗 Transformers operano su _tensori_ (sia in PyTorch che in TensorFlow), e un'operazione comune è quella di estrarre le dimensioni di un tensore usando `Tensor.size()` in PyTorch, per esempio. Diamo un'altra occhiata al traceback, per vedere quale riga ha causato l'eccezione: + +``` +~/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' +``` + +Sembra che il nostro codice abbia provato a chiamare `input_ids.size()`, ma questo chiaramente non funziona per una `list`di Python, che è solo un _container_. Come possiamo risolvere questo problema? Cercando il messaggio di errore su Stack Overflow si ottengono alcuni [risultati](https://stackoverflow.com/search?q=AttributeError%3A+%27list%27+object+has+no+attribute+%27size%27&s=c15ec54c-63cb-481d-a749-408920073e8f) pertinenti. Cliccando sul primo, viene visualizzata una domanda simile alla nostra, con la risposta mostrata nello screenshot seguente: + +
+An answer from Stack Overflow. +
+ +La risposta raccomanda di aggiungere `return_tensors='pt'` al tokenizer, quindi proviamo se funziona: + +```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 +# Get the most likely beginning of answer with the argmax of the score +answer_start = torch.argmax(answer_start_scores) +# Get the most likely end of answer with the argmax of the score +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 +""" +``` + +Bene, ha funzionato! Questo è un ottimo esempio di quanto possa essere utile Stack Overflow: identificando un problema simile, abbiamo potuto beneficiare dell'esperienza di altri membri della community. Tuttavia, una ricerca come questa non sempre produce una risposta pertinente, quindi cosa si può fare in questi casi? Fortunatamente, sul [forum di Hugging Face](https://discuss.huggingface.co/) c'è un'accogliente community di sviluppatori che può aiutarti! Nella prossima sezione, vedremo come creare bene delle domande sul forum che abbiano buona probabilità di ricevere una risposta. \ No newline at end of file diff --git a/chapters/it/chapter8/3.mdx b/chapters/it/chapter8/3.mdx new file mode 100644 index 000000000..d7cc632ba --- /dev/null +++ b/chapters/it/chapter8/3.mdx @@ -0,0 +1,164 @@ +# Asking for help on the forums + + + + + +Il [forum di Hugging Face](https://discuss.huggingface.co) è un posto ideale per ricevere aiuto dal team open source e dalla più ampia community di Hugging Face. Ecco come appare la pagina principale in un dato giorno: + +
+The Hugging Face forums. +
+ +Sul lato sinistro si possono vedere tutte le categorie in cui sono raggruppati i vari _topic_, mentre il lato destro mostra i _topic_ più recenti. Un _topic_ è un post che contiene un titolo, una categoria e una descrizione; è abbastanza simile al formato degli _issues_ di GitHub che abbiamo visto quando abbiamo creato il nostro dataset nel [Capitolo 5](/course/chapter5). Come suggerisce il nome, la categoria [Beginners](https://discuss.huggingface.co/c/beginners/5) è diretta principalmente alle persone che iniziano a lavorare con le librerie e l'ecosistema di Hugging Face. Tutte le domande sulle librerie sono benvenute qui, sia che siano per fare debug di codice sia che siano per chiedere aiuto su come fare qualcosa. (Detto questo, se la domanda riguarda una libreria in particolare, è meglio rivolgersi alla categoria corrispondente del forum). + +Allo stesso modo, le categorie [Intermediate](https://discuss.huggingface.co/c/intermediate/6) e [Research](https://discuss.huggingface.co/c/research/7) sono per domande più avanzate, ad esempio sulle librerie o su novità nel campo della ricerca del NLP, di cui si vuole discutere. + +E naturalmente va menzionata anche la categoria [Course](https://discuss.huggingface.co/c/course/20), dove potrai porre tutte le tue domande relative al corso Hugging Face! + +Una volta selezionata la categoria, sarai pronto/a a scrivere il tuo primo _topic_. Nel forum troverai alcune [linee guida](https://discuss.huggingface.co/t/how-to-request-support/3128) su come farlo, e in questa sezione daremo un'occhiata ad alcune delle caratteristiche che contraddistinguono un buon _topic_. + +## Scrivere un post nel forum correttamente + +Come esempio, supponiamo di voler generare embeddings da articoli di Wikipedia per creare un motore di ricerca personalizzato. Come al solito, carichiamo il tokenizer e il modello come segue: + +```python +from transformers import AutoTokenizer, AutoModel + +model_checkpoint = "distilbert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +model = AutoModel.from_pretrained(model_checkpoint) +``` + +Ora supponiamo di provare a creare embeddings per un'intera sezione dell'[articolo di Wikipedia](https://en.wikipedia.org/wiki/Transformers) sui Transformers (il franchise, non la libreria!): + +```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 +``` + +Oh-oh, abbiamo un problema -- e il messaggio di errore è molto più criptico di quelli visti nella [sezione 2](/course/chapter8/section2)! Non riusciamo a capire il traceback completo, quindi decidiamo di rivolgerci ai forum di Hugging Face per chiedere aiuto. Come potremmo creare il _topic_? + +Per iniziare, dobbiamo cliccare sul pulsante "New Topic" nell'angolo in alto a destra (nota che per creare un _topic_ è necessario aver effettuato il login): + +
+Creating a new forum topic. +
+ +Si apre un'interfaccia di scrittura in cui è possibile inserire il titolo dell'argomento, selezionare una categoria e abbozzare il contenuto: + +
+The interface for creating a forum topic. +
+ +Poiché l'errore sembra riguardare esclusivamente 🤗 Transformers, selezioneremo questa categoria. Il nostro primo tentativo di spiegare il problema potrebbe essere simile a questo: + +
+Drafting the content for a new forum topic. +
+ +Sebbene questo _topic_ contenga il messaggio di errore per il quale abbiamo bisogno di aiuto, ci sono alcuni problemi nel modo in cui è scritto: + +1. Il titolo non è molto descrittivo, quindi chiunque navighi nel forum non sarà in grado di capire di cosa si tratta senza leggere anche il testo completo del _topic_. +2. Il corpo del testo non fornisce abbastanza informazioni su _da dove_ proviene l'errore e _come_ riprodurlo. +3. Nel _topic_ alcune persone sono taggate direttamente con un tono un po' esigente. + +_Topic_ come questo non riceveranno probabilmente una risposta rapida (se la otterranno), quindi cerchiamo di capire come possiamo migliorarli. Cominciamo con il primo problema: la scelta di un buon titolo. + +### Scegliere un titolo descrittivo + +Se si sta cercando di ottenere aiuto per un bug nel codice, una buona regola è quella di includere abbastanza informazioni nel titolo, in modo che gli altri possano determinare rapidamente se pensano di poter rispondere alla domanda o meno. Nel nostro esempio, conosciamo il nome dell'eccezione che viene sollevata e abbiamo qualche indizio sul fatto che viene attivata nel _forward pass_ del modello, dove chiamiamo `model(**inputs)`. Per comunicare tutto ciò, un possibile titolo potrebbe essere: + +> Source of IndexError in the AutoModel forward pass? + +Questo titolo dice al lettore _da dove_ si pensa che provenga il bug e, se ha già incontrato un `IndexError`, è molto probabile che sappia come risolverlo. Naturalmente, il titolo può essere qualsiasi cosa si voglia, e altre varianti come: + +> Why does my model produce an IndexError? + +potrebbe anche andare bene. Ora che abbiamo un titolo descrittivo, vediamo di migliorare il corpo del testo. + +### Formattare gli snippet di codice + +Leggere il codice sorgente è già abbastanza difficile in un IDE, ma lo è ancora di più quando il codice viene copiato e incollato come testo normale! Fortunatamente, i forum di Hugging Face supportano l'uso di Markdown, quindi dovresti sempre racchiudere i vostri blocchi di codice con tre backtick (```) in modo da renderli più facilmente leggibili. Facciamo così per abbellire il messaggio di errore e, già che ci siamo, rendiamo il testo un po' più educato della nostra versione originale: + +
+Our revised forum topic, with proper code formatting. +
+ +Come si può vedere nello screeshot, racchiudendo i blocchi di codice tra i backtick si converte il testo grezzo in codice formattato, completo di colore! Si noti anche che i singoli backtick possono essere usati per formattare le variabili inline, come abbiamo fatto per `distilbert-base-uncased`. Questo _topic_ sembra molto migliorato e con un po' di fortuna potremmo trovare qualcuno nella community in grado di indovinare la causa dell'errore. Tuttavia, invece di affidarci alla fortuna, rendiamo la vita più facile includendo il traceback in tutti i suoi dettagli! + +### Includere il traceback completo + +Poiché l'ultima riga del traceback è spesso sufficiente per il debug del proprio codice, si può essere tentati di fornire solo quella nel proprio _topic_ per "risparmiare spazio". Anche se fatto con buone intenzioni, in realtà questo rende per gli altri il debug del problema _più difficile_, poiché anche le informazioni che si trovano più in alto nel traceback possono essere molto utili. Quindi, è buona pratica copiare e incollare l'_intero_ traceback, assicurandosi che sia ben formattato. Poiché questi traceback possono diventare piuttosto lunghi, alcuni preferiscono mostrarli dopo aver spiegato il codice sorgente. Facciamo così. Ora, il nostro _topic_ del forum ha questo aspetto: + +
+Our example forum topic, with the complete traceback. +
+ +Questo è molto più esplicativo e un lettore attento potrebbe notare che il problema sembra essere dovuto al passaggio di un input lungo, grazie a questa riga nel traceback: + +> Token indices sequence length is longer than the specified maximum sequence length for this model (583 > 512). + +Tuttavia, possiamo rendere le cose ancora più semplici fornendo il codice effettivo che ha generato l'errore. Facciamolo ora. + +### Fornire un esempio riproducibile + +Se hai mai provato a fare il debug del codice di qualcun altro, probabilmente hai prima cercato di ricreare il problema segnalato, in modo da poter iniziare a lavorare dal traceback per individuare l'errore. Non è diverso quando si tratta di ricevere (o dare) supporto sui forum, quindi è davvero utile poter fornire un piccolo esempio che riproduca l'errore. La metà delle volte, semplicemente facendo questo ti aiuterà a capire cosa non funziona. In ogni caso, il pezzo mancante del nostro esempio è mostrare gli _input_ che abbiamo fornito al modello. In questo modo si ottiene un esempio completo come il seguente: + +
+The final version of our forum topic. +
+ +Questo _topic_ contiene ora molte informazioni ed è scritto in modo da attirare l'attenzione della community e ottenere una risposta utile. Con queste linee guida basilari, puoi ora creare ottimi _topic_ per trovare le risposte alle tue domande su 🤗 Transformers! + diff --git a/chapters/it/chapter8/4.mdx b/chapters/it/chapter8/4.mdx new file mode 100644 index 000000000..c57190b58 --- /dev/null +++ b/chapters/it/chapter8/4.mdx @@ -0,0 +1,787 @@ + + +# Fare il debug della training pipeline + + + +Hai scritto un bello script per addestrare o affinare un modello su un determinato compito, seguendo scrupolosamente i consigli del [Capitolo 7](/course/chapter7). Ma quando lanci il comando `trainer.train()`, succede qualcosa di orribile: si ottiene un errore 😱! O peggio, tutto sembra andare bene e il training viene eseguito senza errori, ma il modello che ne risulta fa schifo. In questa sezione mostreremo cosa è possibile fare per eseguire il debug di questo tipo di problemi. + +## Fare il debug della training pipeline + + + +Il problema quando si ha un errore da `trainer.train()` è che potrebbe provenire da più fonti, poiché il `Trainer` di solito mette insieme molte cose. Converte i dataset in _dataloader_, quindi l'errore potrebbe essere dato da qualcosa di sbagliato nel dataset stesso, o da un problema qualche problema nel provare a raggruppare in un batch elementi del dataset. Poi prende un batch di dati e lo invia al modello, quindi il problema potrebbe anche essere nel codice del modello. Successivamente, calcola i gradienti ed esegue la fase di ottimizzazione, quindi il problema potrebbe essere nel tuo _optimizer_. E anche se tutto va bene per il training, qualcosa potrebbe andare storto durante la valutazione se c'è un problema con la metrica selezionata. + +Il modo migliore per eseguire il debug di un errore che si verifica in `trainer.train()` è quello di esaminare manualmente l'intera pipeline per vedere dove le cose sono andate storte. L'errore è spesso molto facile da risolvere. + +Per dimostrarlo, useremo il seguente script che ha lo scopo di affinare un modello DistilBERT sul [dataset MNLI](https://huggingface.co/datasets/glue): + +```py +from datasets import load_dataset, load_metric +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 = load_metric("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() +``` + +Se provi a eseguirlo, otterrai un errore piuttosto criptico: + +```python out +'ValueError: You have to specify either input_ids or inputs_embeds' +``` + +### Controlla i dati + +Non c'è bisogno di dirlo, ma se i dati sono corrotti, il `Trainer` non sarà in grado di formare i batch e tanto meno di addestrare il modello. Quindi, per prima cosa, è necessario dare un'occhiata a cosa c'è nel training set(_insieme di addestramento_). + +Per evitare di passare infinite ore a cercare di risolvere qualcosa che non è la fonte del bug, consigliamo di usare `trainer.train_dataset` per controllare l'insieme di dati e nient'altro. Quindi facciamo così: + +```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.'} +``` + +Hai notato qualcosa di sbagliato? Questo, insieme al messaggio di errore sulla mancanza di `input_ids`, dovrebbe farci capire che qui abbiamo testo, non numeri che invece il modello può interpretare. In questo caso, l'errore originale è molto fuorviante, perché il `Trainer` rimuove automaticamente le colonne che non corrispondono alla firma del modello (cioè i parametri che il modello si aspetta). Ciò significa che in questo caso tutto, a parte _label_, è stato scartato. Non c'è stato quindi nessun problema nel creare i batch di dati e poi inviarli al modello, invece è il modello che a sua volta si è lamentato di non aver ricevuto l'input corretto. + +Perché i dati non sono stati processati? Abbiamo usato il metodo `Dataset.map()` sui set di dati per applicare il tokenizer a ogni campione. Ma se si osserva attentamente il codice, si noterà che abbiamo commesso un errore nel passare i training set e il validation set (_insieme di valutazione_) al `Trainer`. Qui invece di usare `tokenized_datasets`, abbiamo usato `raw_datasets` 🤦. Quindi correggiamo questo errore! + +```py +from datasets import load_dataset, load_metric +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 = load_metric("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() +``` + +Questo nuovo codice ora darà un errore diverso (un miglioramento!): + +```python out +'ValueError: expected sequence of length 43 at dim 1 (got 37)' +``` + +Osservando il traceback, si nota che l'errore si verifica nel punto in cui i dati vengono raccolti: + +```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 +``` + +Quindi, bisogna concentrarsi su questo. Prima di farlo, però, finiamo d'ispezionare i nostri dati, per essere sicuri al 100% che siano corretti. + +Una cosa da fare sempre quando si esegue il debug di una sessione di addestramento è dare un'occhiata agli input del modello decodificati. Non possiamo dare un senso ai numeri che gli diamo direttamente in pasto, quindi dobbiamo guardare cosa rappresentano quei numeri. Nella computer vision, ad esempio, ciò significa guardare le immagini decodificate dei pixel passati, nel campo del riconoscimento vocale significa ascoltare i campioni audio decodificati e per il nostro esempio di NLP significa usare il nostro tokenizer per decodificare gli input: + +```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]' +``` + +Questo sembra corretto. Si dovrebbe fare così per tutte le chiavi degli input: + +```py +trainer.train_dataset[0].keys() +``` + +```python out +dict_keys(['attention_mask', 'hypothesis', 'idx', 'input_ids', 'label', 'premise']) +``` + +Si noti che le chiavi che non corrispondono a input accettati dal modello saranno automaticamente scartate, quindi qui terremo solo `input_ids`, `attention_mask` e `label` (che sarà rinominata `labels`). Per ricontrollare la firma del modello, si può stampare la classe del modello e poi controllare la sua documentazione: + +```py +type(trainer.model) +``` + +```python out +transformers.models.distilbert.modeling_distilbert.DistilBertForSequenceClassification +``` + +Quindi, nel nostro caso, possiamo controllare i parametri accettati in [questa pagina](https://huggingface.co/transformers/model_doc/distilbert.html#distilbertforsequenceclassification). Il `Trainer` registrerà anche le colonne che sta scartando. + +Abbiamo controllato che gli ID in ingresso siano corretti decodificandoli. Il prossimo passo è la `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] +``` + +Poiché non abbiamo applicato il padding nel nostro preprocessing, questo sembra perfettamente naturale. Per essere sicuri che non ci siano problemi con la attention mask (_maschera di attenzione_), controlliamo che sia della stessa lunghezza dei nostri ID di input: + +```py +len(trainer.train_dataset[0]["attention_mask"]) == len( + trainer.train_dataset[0]["input_ids"] +) +``` + +```python out +True +``` + +Bene! Infine, controlliamo la nostra label: + +```py +trainer.train_dataset[0]["label"] +``` + +```python out +1 +``` + +Come gli ID degli input, si tratta di un numero che non ha senso di per sé. Come abbiamo visto prima, la mappa tra gli interi e i nomi delle label è memorizzata all'interno dell'attributo `names` della corrispondente *feature* del dataset: + +```py +trainer.train_dataset.features["label"].names +``` + +```python out +['entailment', 'neutral', 'contradiction'] +``` + +Quindi `1` significa `neutral` (_neutro_), il che significa che le due frasi viste sopra non sono in contraddizione e che la prima non implica la seconda. Sembra corretto! + +Non abbiamo token type ID (_ID del tipo di token_) qui, perché DistilBERT non li prevede; se li hai nel tuo modello, devi anche assicurarti che corrispondano correttamente alla posizione della prima e della seconda frase nell'input. + + + +✏️ **Prova tu!** Controlla che tutto sia corretto nel secondo elemento del training set. + + + +In questo caso, il controllo viene effettuato solo sul training set, ma è necessario ricontrollare allo stesso modo anche il validation set e il test set. + +Ora che sappiamo che i nostri set di dati sono corretti, è il momento di verificare la fase successiva della pipeline di addestramento. + +### Dai dataset ai dataloader + +La prossima cosa che può andare storta nella pipeline di addestramento è quando il `Trainer` cerca di formare dei batch dal training o dal validation set. Una volta che si è sicuri che i set di dati del `Trainer` sono corretti, si può provare a formare manualmente un batch eseguendo quanto segue (sostituire `train` con `eval` per il dataloader di validazione): + +```py +for batch in trainer.get_train_dataloader(): + break +``` + +Questo codice crea il training dataloader (_caricatore di dati di addestramento_), quindi lo itera, fermandosi alla prima iterazione. Se il codice viene eseguito senza errori, si ha il primo batch di addestramento che può essere ispezionato; se il codice dà errore, si sa con certezza che il problema è nel dataloader, come in questo caso: + +```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) +``` + +L'ispezione dell'ultimo frame del traceback dovrebbe essere sufficiente a fornire un indizio, ma cerchiamo di scavare un po' più a fondo. La maggior parte dei problemi durante la creazione dei batch si verifica a causa del raggruppamento degli esempi in un singolo batch, quindi la prima cosa da controllare in caso di dubbio è quale `collate_fn` il tuo `DataLoader` sta usando: + +```py +data_collator = trainer.get_train_dataloader().collate_fn +data_collator +``` + +```python out + Dict[str, Any]> +``` + +È il `default_data_collator`, ma non è quello che vogliamo in questo caso. Vogliamo che i nostri esempi siano espansi fino ad essere come la frase più lunga del batch, cosa che viene fatta dal collettore `DataCollatorWithPadding`. Questo collatore di dati dovrebbe essere usato di default da `Trainer`, quindi perché non viene usato qui? + +La risposta è che non abbiamo passato il `tokenizer` al `Trainer`, quindi non ha potuto creare il `DataCollatorWithPadding` che volevamo. In pratica, non si dovrebbe mai esitare a passare esplicitamente il collettore di dati che si vuole usare, per essere sicuri di evitare questo tipo di errori. Adattiamo il nostro codice per fare esattamente questo: + +```py +from datasets import load_dataset, load_metric +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 = load_metric("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() +``` + +La buona notizia? Non riceviamo più lo stesso errore di prima, il che è sicuramente un miglioramento. La cattiva notizia? Otteniamo invece un famigerato errore CUDA: + +```python out +RuntimeError: CUDA error: CUBLAS_STATUS_ALLOC_FAILED when calling `cublasCreate(handle)` +``` + +Questo è un male perché gli errori di CUDA sono estremamente difficili da debuggare in generale. Vedremo tra poco come risolvere questo problema, ma prima terminiamo l'analisi della creazione di batch. + +Se siete sicuri che il tuo collettore di dati è quello giusto, dovresti provare ad applicarlo su un paio di campioni del tuo set di dati: + +```py +data_collator = trainer.get_train_dataloader().collate_fn +batch = data_collator([trainer.train_dataset[i] for i in range(4)]) +``` + +Questo codice fallirà perché il `train_dataset` contiene colonne di tipo stringa, che il `Trainer` solitamente rimuove. È possibile rimuoverle manualmente o, se si vuole replicare esattamente ciò che il `Trainer` fa dietro le quinte, si può chiamare il metodo privato `Trainer._remove_unused_columns()` che fa questo: + +```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)]) +``` + +Se l'errore persiste, si potrebbe eseguire manualmente il debug di ciò che accade all'interno del collettore di dati. + +Ora che abbiamo eseguito il debug del processo di creazione del batch, è il momento di passarne uno attraverso il modello! + +### Passaggio attraverso il modello + +Dovrebbe essere possibile ottenere un batch eseguendo il seguente comando: + +```py +for batch in trainer.get_train_dataloader(): + break +``` + +Se si esegue questo codice in un notebook, è possibile che si verifichi un errore CUDA simile a quello visto in precedenza, nel qual caso è necessario riavviare il notebook e rieseguire l'ultimo snippet senza la riga `trainer.train()`. Questa è la seconda cosa più fastidiosa degli errori CUDA: rompono irrimediabilmente il kernel. La cosa più fastidiosa è che sono difficili da debuggare. + +Perché? Questo ha a che fare con il modo in cui funzionano le GPU. Sono estremamente efficienti nell'eseguire molte operazioni in parallelo, ma l'inconveniente è che quando una di queste istruzioni produce un errore, non lo si sa immediatamente. È solo quando il programma chiama una sincronizzazione dei processi multipli sulla GPU che esso si accorge che qualcosa è andato storto, quindi l'errore viene effettivamente sollevato in un punto che non ha niente a che fare con ciò che lo ha creato. Per esempio, se guardiamo il nostro traceback precedente, l'errore è stato sollevato durante il backward pass (_percorso discendente_), ma vedremo tra un minuto che in realtà deriva da qualcosa nel forward pass (_percorso ascendente_). + +Come si fa a fare il debug di questi errori? La risposta è semplice: non lo facciamo. A meno che l'errore CUDA non sia un errore out-of-memory (il che significa che la memoria della GPU non è sufficiente), si dovrebbe sempre tornare alla CPU per eseguire il debug. + +Per fare questo nel nostro caso, dobbiamo semplicemente rimettere il modello sulla CPU e chiamarlo sul nostro batch -- il batch restituito dal `DataLoader` non è ancora stato spostato sulla 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. +``` + +Quindi, il quadro si fa più chiaro. Invece di avere un errore CUDA, ora abbiamo un `IndexError` nel calcolo della loss (_funzione di perdita_) (quindi niente a che fare con il backward pass, come abbiamo detto prima). Più precisamente, possiamo vedere che è il target 2 a creare l'errore, quindi questo è un ottimo momento per controllare il numero di label del nostro modello: + +```python +trainer.model.config.num_labels +``` + +```python out +2 +``` + +Con due label, solo gli 0 e gli 1 sono ammessi come target, ma secondo il messaggio di errore abbiamo ottenuto un 2. Ottenere un 2 è in realtà normale: se ricordiamo i nomi delle etichette che abbiamo estratto in precedenza, ce n'erano tre, quindi abbiamo gli indici 0, 1 e 2 nel nostro dataset. Il problema è che non l'abbiamo detto al nostro modello, il quale si sarebbe dovuto creare con tre label. Quindi, risolviamo il problema! + +```py +from datasets import load_dataset, load_metric +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 = load_metric("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, +) +``` + +Non abbiamo ancora incluso la riga `trainer.train()`, per prendere tempo e verificare che tutto sia a posto. Se richiediamo un batch e lo passiamo al nostro modello, ora funziona senza errori! + +```py +for batch in trainer.get_train_dataloader(): + break + +outputs = trainer.model.cpu()(**batch) +``` + +Il passo successivo consiste nel tornare a usare la GPU e verificare che tutto funzioni ancora: + +```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) +``` + +Se si verifica ancora un errore, assicurarsi di riavviare il notebook ed eseguire solo l'ultima versione dello script. + +### Esecuzione di un passaggio di ottimizzazione + +Ora che sappiamo che possiamo costruire batch che passano effettivamente attraverso il modello, siamo pronti per la fase successiva della pipeline di addestramento: calcolare i gradienti ed eseguire una fase di ottimizzazione. + +La prima parte consiste nel richiamare il metodo `backward()` sulla loss: + +```py +loss = outputs.loss +loss.backward() +``` + +È abbastanza raro che si verifichi un errore in questa fase, ma se si verifica, assicurati di tornare ad usare la CPU per ottenere un messaggio di errore più utile. + +Per eseguire la fase di ottimizzazione, è sufficiente creare l'oggetto `optimizer` e richiamare il suo metodo `step()`: + +```py +trainer.create_optimizer() +trainer.optimizer.step() +``` + +Anche in questo caso, se si utilizza l'ottimizzatore predefinito nel `Trainer`, non si dovrebbe ottenere un errore in questa fase, ma se hai un ottimizzatore personalizzato, potrebbero esserci dei problemi da risolvere. Non dimenticare di tornare alla CPU se ottieni uno strano errore CUDA in questa fase. A proposito di errori CUDA, prima abbiamo menzionato un caso speciale. Vediamo ora questo caso. + +### Come gestire gli errori out-of-memory di CUDA + +Ogni volta che si riceve un messaggio di errore che inizia con `RuntimeError: CUDA out of memory`, indica che la memoria della GPU è esaurita. Questo errore non è direttamente collegato al codice e può verificarsi anche con uno script che funziona perfettamente. Questo errore significa che si è tentato di mettere troppe cose nella memoria interna della GPU e che si è verificato un errore. Come per altri errori di CUDA, è necessario riavviare il kernel per poter eseguire nuovamente l'allenamento. + +Per risolvere questo problema, è sufficiente utilizzare meno spazio sulla GPU, cosa che spesso è più facile a dirsi che a farsi. Per prima cosa, assicuratevi di non avere due modelli sulla GPU contemporaneamente (a meno che non sia necessario per il vostro problema, ovviamente). Poi, è probabile che si debba ridurre la dimensione del batch, in quanto influisce direttamente sulle dimensioni di tutti gli output intermedi del modello e dei loro gradienti. Se il problema persiste, si può considerare di utilizzare una versione più piccola del modello. + + + +Nella prossima parte del corso, esamineremo tecniche più avanzate che possono aiutare a ridurre l'impatto sulla memoria e ad affinare i modelli più grandi. + + + +### Valutazione del modello + +Ora che abbiamo risolto tutti i problemi con il nostro codice, tutto è perfetto e l'addestramento dovrebbe girare senza intoppi, giusto? Non così veloce! Se si esegue il comando `trainer.train()`, all'inizio sembrerà tutto a posto, ma dopo un po' si otterrà il seguente risultato: + +```py +# This will take a long time and error out, so you shouldn't run this cell +trainer.train() +``` + +```python out +TypeError: only size-1 arrays can be converted to Python scalars +``` + +Ti accorgerai che questo errore compare durante la fase di valutazione, quindi è l'ultima cosa che dobbiamo debuggare. + +È possibile eseguire il ciclo di valutazione del `Trainer` indipendentemente dall'addestramento, in questo modo: + +```py +trainer.evaluate() +``` + +```python out +TypeError: only size-1 arrays can be converted to Python scalars +``` + + + +💡 Bisogna sempre assicurarsi di poter eseguire `trainer.evaluate()` prima di lanciare `trainer.train()`, per evitare di sprecare molte risorse di calcolo prima di incorrere in un errore. + + + +Prima di tentare il debug di un problema nel ciclo di valutazione, è necessario assicurarsi di aver dato un'occhiata ai dati, di essere in grado di generare correttamente un batch e di poter eseguire il modello su di esso. Abbiamo completato tutti questi passaggi, quindi il codice seguente può essere eseguito senza errori: + +```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) +``` + +L'errore arriva più tardi, alla fine della fase di valutazione, e se guardiamo il traceback vediamo questo: + +```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() +``` + +Questo ci dice che l'errore ha origine nel modulo `datasets/metric.py`, quindi si tratta di un problema con la nostra funzione `compute_metrics()`. La funzione accetta una tupla con i logit e le label come array NumPy, quindi proviamo a dargliela in pasto: + +```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 +``` + +Otteniamo lo stesso errore, quindi il problema risiede sicuramente in quella funzione. Se guardiamo al suo codice, vediamo che sta solo trasferendo le `predictions` e le `labels` a `metric.compute()`. C'è quindi un problema con questo metodo? Non proprio. Diamo una rapida occhiata alle dimensioni: + +```py +predictions.shape, labels.shape +``` + +```python out +((8, 3), (8,)) +``` + +Le nostre previsioni sono ancora dei logit, non le vere previsioni, ed è per questo che la metrica restituisce questo errore (un po' oscuro). La soluzione è abbastanza semplice: basta aggiungere un argmax nella funzione `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} +``` + +Ora il nostro errore è stato risolto! Questo era l'ultimo, quindi il nostro script ora addestrerà correttamente un modello. + +Per riferimento, ecco lo script completamente corretto: + +```py +import numpy as np +from datasets import load_dataset, load_metric +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 = load_metric("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() +``` + +In questo caso, non ci sono più problemi e il nostro script affinerà un modello che dovrebbe dare risultati ragionevoli. Ma cosa possiamo fare quando l'addestramento procede senza errori e il modello addestrato non funziona affatto bene? Questa è la parte più difficile di machine learning e ti mostreremo alcune tecniche che possono aiutarti. + + + +💡 Se si utilizza un ciclo di addestramento manuale, per il debug della pipeline di addestramento valgono gli stessi passaggi, ma è più facile separarli. Assicurati però di non aver dimenticato il `model.eval()` o il `model.train()` nei punti giusti, o lo `zero_grad()` a ogni passo! + + + +## Debug degli errori silenziosi durante l'addestramento + +Cosa possiamo fare per eseguire il debug di un addestramento che viene completato senza errori, ma che non produce buoni risultati? Qui ti daremo alcuni suggerimenti, ma sappi che questo tipo di debugging è la parte più difficile di machine learning e non esiste una soluzione magica. + +### Controllare i dati (di nuovo!) + +Il tuo modello imparerà qualcosa solo se è effettivamente possibile imparare qualcosa dai tuoi dati. Se c'è un bug che corrompe i dati o le label sono assegnate in modo casuale, è molto probabile che non si riesca ad addestrare il modello sul dataset. Quindi, inizia sempre con un doppio controllo degli input e delle label decodificate e poniti le seguenti domande: + +- I dati decodificati sono comprensibili? +- Sei d'accordo con le label? +- C'è una label più comune delle altre? +- Quale dovrebbe essere la funzione di perdita/metrica se il modello predicesse una risposta a caso/sempre la stessa risposta? + + + +⚠️ Se effettui un addestramento in modo distribuito, stampa campioni del set di dati in ogni processo e controlla molto attentamente che ottieni la stessa cosa. Un bug comune è la presenza di una qualche fonte di casualità nella creazione dei dati che fa sì che ogni processo abbia una versione diversa del set di dati. + + + +Dopo aver esaminato i dati, esamina alcune previsioni del modello e decodificale. Se il modello prevede sempre la stessa cosa, potrebbe essere perché il tuo set di dati è influenzato verso una categoria (per i problemi di classificazione); tecniche come fare oversampling (_sovra-campionamento_) delle classi rare potrebbero aiutare. + +Se la funzione di perdita/metrica ottenuta con il tuo modello iniziale è molto diversa da quella che ci si aspetterebbe per le previsioni casuali, ricontrolla il modo in cui viene calcolata la funzione o la metrica, perché probabilmente c'è un bug. Se si utilizzano diverse funzioni che aggiungi alla fine, assicurati che siano della stessa grandezza. + +Quando sei sicuro/a che i dati sono perfetti, puoi verificare se il modello è in grado di addestrarsi su di essi con un semplice test. + +### Fare overfitting del modello su un batch + +L'overfitting è di solito qualcosa che cerchiamo di evitare durante l'addestramento, poiché significa che il modello non sta imparando a riconoscere le proprietà generali che vogliamo, ma sta invece memorizzando i campioni di addestramento. Tuttavia, provare ad addestrare il modello su un batch più e più volte è un buon test per verificare se il problema così come è stato inquadrato può essere risolto dal modello che si sta cercando di addestrare. Inoltre, ti aiuterà a capire se il learning rate (_tasso di apprendimento_) iniziale è troppo alta. + +Una volta definito il `Trainer`, è molto semplice: basta prendere un batch dal training set, ed eseguire un piccolo ciclo di addestramento manuale utilizzando solo quel batch per qualcosa come 20 step: + +```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() +``` + + + +💡 Se i dati di addestramento sono sbilanciati, assicurati di creare un batch di dati di addestramento contenente tutte le label. + + + +Il modello risultante dovrebbe avere risultati quasi perfetti sullo stesso `batch`. Calcoliamo la metrica sulle previsioni risultanti: + +```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% di accuratezza, questo è un bell'esempio di overfitting (il che significa che se provi il tuo modello su qualsiasi altra frase, molto probabilmente ti darà una risposta sbagliata)! + +Se non si riesci a far sì che il modello ottenga risultati perfetti come questo, significa che c'è qualcosa di sbagliato nel modo in cui si è impostato il problema o con i dati, e quindi dovresti risolvere questa cosa. Solo quando riesci a superare il test di overfitting puoi essere sicuro/a che il tuo modello possa effettivamente imparare qualcosa. + + + +⚠️ Sarà necessario ricreare il modello e il `Trainer` dopo questo test, poiché il modello ottenuto probabilmente non sarà in grado di recuperare e imparare qualcosa di utile sul set di dati completo. + + + +### Non calibrare niente prima di avere una prima baseline + +Hyperparameter tuning (_calibrazione degli iperparametri_) è sempre considerato come la parte più difficile di machine learning, ma è solo l'ultimo passo per aiutarti a migliorare un po' la metrica. Nella maggior parte dei casi, gli iperparametri predefiniti del `Trainer` funzionano bene per dare buoni risultati, quindi non ci si deve lanciare in una ricerca di iperparametri dispendiosa in termini di tempo e di costi, finché non si è ottenuto qualcosa che batta la baseline (_base di partenza_) che si ha sul dataset. + +Una volta ottenuto un modello sufficientemente buono, si può iniziare a modificarlo un po'. Non provare a eseguire l'addestramento un migliaio di volte con iperparametri diversi, ma confronta un paio di esecuzioni che hanno valori diversi per un iperparametro così da avere un'idea di quale abbia il maggiore impatto. + +Se stai modificando il modello stesso, mantieni le cose semplici e non provare nulla che non possa essere ragionevolmente giustificato. Assicurati sempre di rifare il test di overfitting per verificare che la modifica non abbia avuto conseguenze indesiderate. + +### Chiedere aiuto + +Speriamo che in questa sezione tu abbia trovato qualche consiglio utile a risolvere il tuo problema, ma se così non fosse, ricordati che puoi sempre chiedere aiuto alla community nei [forum](https://discuss.huggingface.co/). + +Qui di seguito sono riportate alcune risorse aggiuntive che potrebbero rivelarsi utili: + +- ["Reproducibility as a vehicle for engineering best practices"](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p) di Joel Grus +- ["Checklist for debugging neural networks"](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) di Cecelia Shao +- ["How to unit test machine learning code"](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) di Chase Roberts +- ["A Recipe for Training Neural Networks"](http://karpathy.github.io/2019/04/25/recipe/) di Andrej Karpathy + +Naturalmente, non tutti i problemi che incontrerai durante l'addestramento delle reti neurali sono colpa tua! Se si incontra qualcosa nella libreria 🤗 Transformers o 🤗 Datasets che non sembra corretto, è possibile che si sia trovato un bug. Dovresti assolutamente segnalarcelo e nella prossima sezione ti spiegheremo esattamente come fare. diff --git a/chapters/it/chapter8/4_tf.mdx b/chapters/it/chapter8/4_tf.mdx new file mode 100644 index 000000000..998000149 --- /dev/null +++ b/chapters/it/chapter8/4_tf.mdx @@ -0,0 +1,485 @@ + + +# Fare il debug di una training pipeline + + + +Hai scritto un bello script per addestrare o affinare un modello su un determinato compito, seguendo scrupolosamente i consigli del [Capitolo 7](/course/chapter7). Ma quando lanci il comando `model.fit()`, succede qualcosa di orribile: si ottiene un errore 😱! O peggio, tutto sembra andare bene e il training viene eseguito senza errori, ma il modello che ne risulta fa schifo. In questa sezione mostreremo cosa è possibile fare per eseguire il debug di questo tipo di problemi. + +## Debugging the training pipeline + + + +Il problema quando si ha un errore da `model.fit()` è che potrebbe provenire da più fonti, poichè la fase di training di solito mette insieme molte cose su cui si è lavorato fino a quel momento. Il problema potrebbe essere qualcosa di sbagliato nel tuo dataset, o qualche problema nel provare a raggruppare in un batch elementi del dataset. E anche se tutto va bene per il training, qualcosa potrebbe andare storto durante la valutazione se c'è un problema con la metrica selezionata. + +Il modo migliore per eseguire il debug di un errore che si verifica in `model.fit()` è quello di esaminare manualmente l'intera pipeline per vedere dove le cose sono andate storte. L'errore è spesso molto facile da risolvere. + +Per dimostrarlo, useremo il seguente script che ha lo scopo di affinare un modello DistilBERT sul [dataset MNLI](https://huggingface.co/datasets/glue): + +```py +from datasets import load_dataset, load_metric +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) +``` + +Se si tenta di eseguirlo, si potrebbero riscontrare alcuni `VisibleDeprecationWarning` durante la conversione del dataset -- si tratta di un problema UX noto, quindi si prega di ignorarlo. Se stai leggendo il corso dopo, diciamo, novembre 2021 e il problema si ripresenta ancora, invia dei tweet di disappunto a @carrigmat finché non lo risolve. + +Il problema più grave, però, è che riceviamo un vero e proprio errore. Ed è davvero terribilmente lungo: + +```python out +ValueError: No gradients provided for any variable: ['tf_distil_bert_for_sequence_classification/distilbert/embeddings/word_embeddings/weight:0', '...'] +``` + +Che cosa significa? Abbiamo provato ad dare training sui nostri dati, ma non abbiamo ottenuto alcun gradiente? Questo è piuttosto preoccupante; come possiamo iniziare a fare il debug di una cosa del genere? Quando l'errore che si ottiene non suggerisce immediatamente dove sia il problema, la soluzione migliore è spesso quella di procedere in ordine, assicurandosi in ogni fase che tutto sia corretto. Naturalmente, il punto di partenza è sempre... + +### Controllare i dati + +Non c'è bisogno di dirlo, ma se i dati sono danneggiati, Keras non sarà in grado di risolverli per te. Quindi, per prima cosa, è necessario dare un'occhiata a cosa c'è nel training set. + +Anche se c'è la tentazione di guardare in `raw_datasets` e `tokenized_datasets`, si consiglia vivamente di esaminare i dati proprio nel punto in cui entrano nel modello. Ciò significa leggere un output dal `tf.data.Dataset` creato con la funzione `to_tf_dataset()`! Come si fa? Gli oggetti `tf.data.Dataset` ci forniscono volta per volta interi batch e non supportano l'indicizzazione, quindi non possiamo semplicemente chiedere `train_dataset[0]`. Possiamo però chiedere gentilmente un batch: + +```py +for batch in train_dataset: + break +``` + +`break` termina il ciclo dopo un'iterazione, quindi prende il primo batch che esce da `train_dataset` e lo salva come `batch`. Ora, diamo un'occhiata a ciò che c'è dentro: + +```python out +{'attention_mask': , + 'label': , + 'input_ids': } +``` + +Sembra corretto, non è vero? Stiamo passando al modello le `labels`, le `attention_mask` e gli `input_ids`, che dovrebbero essere tutto ciò di cui ha bisogno per calcolare gli output e la loss (_funzione di perdita_). Perché non abbiamo un gradiente? Guarda meglio: stiamo passando un singolo dizionario come input, ma un batch di addestramento è di solito un tensore o un dizionario di input, più un tensore di label. Le label sono solo una chiave del dizionario di input. + +È un problema? Non sempre, in realtà! Ma è uno dei problemi più comuni che si incontrano quando si addestrano modelli Transformer con TensorFlow. Tutti i nostri modelli possono calcolare la loss internamente, ma per farlo è necessario passare le label nel dizionario di input. Questa è la funzione che viene utilizzata quando non si specifica un valore di loss in `compile()`. Keras, invece, di solito si aspetta che le label siano passate separatamente dal dizionario di input e le computazioni delle loss di solito falliscono se non lo si fa. + +Il problema è ora più chiaro: abbiamo usato un argomento `loss`, il che significa che stiamo chiedendo a Keras di calcolare le loss per noi, ma abbiamo passato le nostre label come input al modello, non come label nel posto in cui Keras se le aspetta! Dobbiamo scegliere l'una o l'altra soluzione: o usiamo la funzione interna del modello e manteniamo le label dove sono, oppure continuiamo a usare le loss di Keras, ma spostiamo le label nel punto in cui Keras se le aspetta. Per semplicità, adottiamo il primo approccio. Cambia la chiamata a `compile()` in: + +```py +model.compile(optimizer="adam") +``` + +Ora utilizzeremo la funzione di perdita interna del modello e il problema dovrebbe essere risolto! + + + +✏️ **Prova tu!** Come sfida opzionale, dopo aver risolto gli altri problemi, puoi provare a tornare a questo passaggio e a far funzionare il modello con la loss originale calcolata da Keras invece che con la loss interna. È necessario aggiungere `"labels"` all'argomento `label_cols` di `to_tf_dataset()` per assicurarsi che le label siano fornite correttamente, in modo da ottenere i gradienti, ma c'è un altro problema con la loss che abbiamo specificato. L'addestramento continuerà a funzionare con questo problema, ma l'apprendimento sarà molto lento e si bloccherà a una loss di addestramento elevata. Riesci a capire di cosa si tratta? + +Un suggerimento codificato in ROT13, se sei bloccato/a: Vs lbh ybbx ng gur bhgchgf bs FrdhraprPynffvsvpngvba zbqryf va Genafsbezref, gurve svefg bhgchg vf `ybtvgf`. Jung ner ybtvgf? + +E un secondo indizio: 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? + + + +Ora proviamo ad avviare l'addestramento. Ora dovremmo ottenere i gradienti, quindi, se tutto va bene (musica minacciosa), possiamo chiamare `model.fit()` e tutto funzionerà bene! + +```python out + 246/24543 [..............................] - ETA: 15:52 - loss: nan +``` + +Oh no. + +`nan` non è un valore di loss molto incoraggiante. Tuttavia, abbiamo controllato i nostri dati e sembrano abbastanza buoni. Se il problema non è questo, come possiamo procedere? Il passo successivo più ovvio è... + +### Controllare il modello + +`model.fit()` è un'ottima funzionionalità di Keras, ma fa un sacco di cose per te e questo può rendere più difficile trovare esattamente dove si è generato un problema. Se stai facendo il debug del modello, una strategia che può essere molto utile è quella di passare un solo batch al modello e di esaminare in dettaglio gli output di quel batch. Un altro suggerimento molto utile se il modello produce errori è quello di `compile()` il modello con `run_eagerly=True`. Questo lo renderà molto più lento, ma renderà i messaggi di errore molto più comprensibili, perché indicheranno esattamente in quale punto del codice del modello si è verificato il problema. + +Per ora, però, non abbiamo bisogno di `run_eagerly`. Passiamo il `batch` che abbiamo ottenuto prima attraverso il modello e vediamo come sono gli output: + +```py +model(batch) +``` + +```python out +TFSequenceClassifierOutput(loss=, logits=, hidden_states=None, attentions=None) +``` + +Beh, questo è insidioso. Tutto è `nan`! Ma è strano, non è vero? Come farebbero tutti i nostri logit a diventare `nan`? `nan` significa "not a number" (_"non un numero"_). I valori `nan` si verificano spesso quando si esegue un'operazione vietata, come la divisione per zero. Ma una cosa molto importante da sapere su `nan` in machine learning è che questo valore tende a *propagarsi*. Se si moltiplica un numero per `nan`, anche il risultato sarà `nan`. E se si ottiene un `nan` in un punto qualsiasi dell'output, della loss o del gradiente, questo si diffonderà rapidamente in tutto il modello, perché quando quel valore `nan` si propagherà attraverso la rete, si otterranno gradienti `nan`, e quando gli aggiornamenti dei pesi saranno calcolati con quei gradienti, si otterranno pesi `nan`, e quei pesi calcoleranno ancora più output `nan`! Presto l'intera rete sarà solo un grande blocco di `nan`. Una volta che ciò accade, è piuttosto difficile capire dove sia iniziato il problema. Come possiamo isolare il punto in cui `nan` si è insinuato per la prima volta? + +La risposta è provare a *reinizializzare* il nostro modello. Una volta iniziato l'addestramento, abbiamo avuto un `nan` da qualche parte e questo si è rapidamente propagato all'intero modello. Quindi, carichiamo il modello da un checkpoint e non eseguiamo alcun aggiornamento dei pesi, e vediamo dove otteniamo un valore `nan`: + +```py +model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint) +model(batch) +``` + +Quando lo si esegue, si ottiene: + +```py out +TFSequenceClassifierOutput(loss=, logits=, hidden_states=None, attentions=None) +``` + +*Adesso* sì che ci capiamo! Non ci sono valori `nan` nei nostri logit, il che è rassicurante. Ma vediamo alcuni valori `nan` nella nostra loss! C'è qualcosa in quei campioni in particolare che sta causando questo problema? Vediamo quali sono (nota che se esegui questo codice da solo/a, potresti ottenere indici diversi perché il dataset è stato rimescolato): + +```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]) +``` + +Visualizziamo i campioni associati a questi indici: + +```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]]) +``` + +Beh, ci sono tante cose qui dentro, ma non c'è nulla che si distingua come insolito. Diamo un'occhiata alle label: + +```python out +labels = batch['labels'].numpy() +labels[indices] +``` + +```python out +array([2, 2, 2, 2, 2, 2, 2, 2, 2]) +``` + +I campioni `nan` hanno tutti la stessa label, ed è la classe 2. Questo è un indizio molto chiaro. Il fatto che si abbia una loss di `nan` solo quando la label è 2 suggerisce che questo è un ottimo momento per verificare il numero di label nel nostro modello: + +```python +model.config.num_labels +``` + +```python out +2 +``` + +Ora vediamo il problema: il modello pensa che ci siano solo due classi, ma le label arrivano a 2, il che significa che in realtà ci sono tre classi (perché anche lo 0 è una classe). Ecco come abbiamo ottenuto un `nan`: cercando di calcolare la loss per una classe inesistente! Proviamo a cambiare il modello e ad adattarlo di nuovo: + +``` +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 +``` + +Staimo addestrando! Non ci sono più `nan` e la nostra loss sta diminuendo... più o meno. Se la si osserva per un po', si potrebbe iniziare a spazientirsi, perché il valore della loss rimane ostinatamente alto. Interrompiamo il training e cerchiamo di capire quale potrebbe essere la causa di questo problema. A questo punto, siamo abbastanza sicuri che sia i dati che il modello siano a posto, ma il nostro modello non sta imparando bene. Cos'altro rimane? È ora di... + +### Controllare gli iperparametri + +Se si guarda al codice precedente, è possibile che non si riesca a vedere alcun iperparametro, a parte forse il `batch_size`, e questo non sembra un possibile problema. Non lasciarti ingannare, però: gli iperparametri ci sono sempre e se non li vedi significa che non conosci il valore a cui sono impostati. In particolare, ricorda una cosa fondamentale di Keras: se imposti una loss, un optimizer (_ottimizzatore_) o una funzione di attivazione con una stringa, _tutti i suoi argomenti saranno impostati ai loro valori predefiniti_. Ciò significa che, anche se usare le stringhe è molto comodo, bisogna fare molta attenzione, perché questa cosa potrebbe facilmente nascondere alcuni aspetti importanti. (Chiunque si cimenti nella sfida opzionale qui sopra dovrebbe prendere nota di questo fatto). + +In questo caso, dove abbiamo impostato un argomento con una stringa? Inizialmente settavamo la loss con una stringa, ma ora non lo facciamo più. Tuttavia, impostiamo l'optimizer usando una stringa. Potrebbe nasconderci qualcosa? Diamo un'occhiata ai [suoi argomenti](https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/Adam). + +C'è qualcosa che balza all'occhio? Esatto: il learning rate (_tasso di apprendimento_)! Quando usiamo semplicemente la stringa `'adam'`, otterremo il tasso di apprendimento predefinito, che è 0,001, o 1e-3. Questo è decisamente troppo alto per un modello Transformer! In generale, si consiglia di provare learning rate tra 1e-5 e 1e-4 per i modelli; si tratta di un valore tra 10 e 100 volte inferiore a quello che stiamo usando qui. Questo sembra essere un problema importante, quindi proviamo a ridurlo. Per farlo, dobbiamo importare l'oggetto `optimizer`. Già che ci siamo, reinizializziamo il modello dal checkpoint, nel caso in cui il training con un learning rate elevato abbia compromesso i suoi pesi: + +```python +from tensorflow.keras.optimizers import Adam + +model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint) +model.compile(optimizer=Adam(5e-5)) +``` + + + +💡 È anche possibile importare la funzione `create_optimizer()` da 🤗 Transformers, che fornirà un optimizer AdamW con un corretto weight decay insieme a un learning rate warmup e decay. Questo ottimizzatore spesso produce risultati leggermente migliori di quelli ottenuti con l'ottimizzatore Adam predefinito. + + + +Adess, possiamo tentarde di fare training del modell con il nuovo learning rate migliorato: + +```python +model.fit(train_dataset) +``` + +```python out +319/24543 [..............................] - ETA: 16:07 - loss: 0.9718 +``` + +Ora la nostra loss sta davvero andando da qualche parte! L'addestramento sembra finalmente funzionare. C'è una lezione da imparare: quando il modello funziona, ma la loss non diminuisce, e si è sicuri che i dati siano corretti, è una buona idea controllare gli iperparametri come il learning rate e il weight decay. Impostando uno di questi parametri troppo alto, è molto probabile che l'addestramento si "blocchi" a un valore di loss elevato. + +## Altri potenziali problemi + +Abbiamo trattato i problemi dello script di cui sopra, ma ci sono molti altri errori comuni che si possono incontrare. Vediamo un elenco (molto incompleto). + +### Gestire gli errori out-of-memory + +Il segnale che indica che la memoria è esaurita è un errore del tipo "OOM when allocating tensor" (OOM è l'abbreviazione di "out of memory"). Si tratta di un rischio molto comune quando si ha a che fare con modelli linguistici di grandi dimensioni. In questo caso, una buona strategia è quella di dimezzare le dimensioni del batch e riprovare. Tenete presente, però, che alcuni modelli sono *molto* grandi. Ad esempio, il modello GPT-2 completo ha 1,5B parametri, il che significa che sono necessari 6 GB di memoria solo per memorizzare il modello e altri 6 GB per i suoi gradienti! L'addestramento del modello GPT-2 completo richiede di solito oltre 20 GB di VRAM, indipendentemente dalla dimensione del batch utilizzato, che solo poche GPU hanno. Modelli più leggeri come `distilbert-base-cased` sono molto più facili da eseguire e si addestrano molto più rapidamente. + + + +Nella prossima parte del corso, esamineremo tecniche più avanzate che possono aiutare a ridurre l'impatto sulla memoria e ad affinare i modelli più grandi. + + + +### TensorFlow è molto affamato 🦛 + +Una particolarità di TensorFlow di cui bisogna essere consapevoli è che alloca *tutta* la memoria della GPU su se stesso non appena si carica un modello o si esegue un addestramento, e poi divide la memoria in base alle esigenze. Questo comportamento è diverso da quello di altri framework, come PyTorch, che allocano la memoria come richiesto con CUDA invece di farlo internamente. Un vantaggio dell'approccio di TensorFlow è che spesso può produrre errori utili quando esaurisci la memoria e può recuperare da questo stato senza mandare in crash l'intero kernel CUDA. Ma c'è anche un importante aspetto negativo: se si eseguono due processi TensorFlow contemporaneamente, allora **sarà un bel guaio**. + +Se si esegue su Colab non ci si deve preoccupare di questo, ma se si lavora in locale è sicuramente qualcosa a cui si deve fare attenzione. In particolare, è bene ricordare che la chiusura della scheda di un notebook non comporta necessariamente la chiusura del notebook stesso! Potresti dover selezionare i notebook in esecuzione (quelli con l'icona verde) e chiuderli manualmente nell'elenco della directory. Qualsiasi notebook in esecuzione che utilizzava TensorFlow potrebbe ancora conservare una buona parte della memoria della GPU e ciò significa che qualsiasi nuovo notebook avviato potrebbe presentare problemi molto strani. + +Se inizi a ricevere errori relativi a CUDA, BLAS o cuBLAS in un codice che prima funzionava, questa è molto spesso la ragione. Si può usare un comando come `nvidia-smi` per controllare: quando si spegne o si riavvia il notebook usato, la maggior parte della memoria è libera o è ancora in uso? Se è ancora in uso, c'è qualcos'altro che la sta occupando! + + +### Check your data (again!) + +Il tuo modello imparerà qualcosa solo se è effettivamente possibile imparare qualcosa dai tuoi dati. Se c'è un bug che corrompe i dati o le label sono assegnate in modo casuale, è molto probabile che non si riesca ad addestrare il modello sul dataset. In questo caso uno strumento utile è `tokenizer.decode()`. Questo trasformerà gli `input_ids` in stringhe, in modo da poter visualizzare i dati e vedere se i dati di training stanno addestrando ciò che si vuole. Per esempio, dopo aver ottenuto un `batch` dal proprio `tf.data.Dataset` come abbiamo fatto sopra, si può decodificare il primo elemento in questo modo: + +```py +input_ids = batch["input_ids"].numpy() +tokenizer.decode(input_ids[0]) +``` + +Poi si può confrontare con la prima label, in questo modo: + +```py +labels = batch["labels"].numpy() +label = labels[0] +``` + +Una volta visualizzati i dati in questo modo, puoi porti le seguenti domande: + +- I dati decodificati sono comprensibili? +- Sei d'accordo con le label? +- C'è una label più comune delle altre? +- Quale dovrebbe essere la funzione di perdita/metrica se il modello predicesse una risposta a caso/sempre la stessa risposta? + +Dopo aver osservato i dati, esamina alcune previsioni del modello: se il modello produce dei token, prova a decodificare anche quelli! Se il modello prevede sempre la stessa cosa, potrebbe essere perché il tuo set di dati è influenzato verso una categoria (per i problemi di classificazione); tecniche come fare oversampling (_sovra-campionamento_) delle classi rare potrebbero aiutare. In alternativa, ciò può essere causato da problemi di addestramento, come ad esempio una scorretta impostazione degli iperparametri. + +Se la funzione di perdita/metrica ottenuta con il tuo modello iniziale è molto diversa da quella che ci si aspetterebbe per le previsioni casuali, ricontrolla il modo in cui viene calcolata la funzione o la metrica, perché probabilmente c'è un bug. Se si utilizzano diverse funzioni che aggiungi alla fine, assicurati che siano della stessa grandezza. + +Quando sei sicuro/a che i dati sono perfetti, puoi verificare se il modello è in grado di addestrarsi su di essi con un semplice test. + +### Fare overfitting del modello su un batch + +L'overfitting è di solito qualcosa che cerchiamo di evitare durante l'addestramento, poiché significa che il modello non sta imparando a riconoscere le proprietà generali che vogliamo, ma sta invece memorizzando i campioni di addestramento. Tuttavia, provare ad addestrare il modello su un batch più e più volte è un buon test per verificare se il problema così come è stato inquadrato può essere risolto dal modello che si sta cercando di addestrare. Inoltre, ti aiuterà a capire se il learning rate iniziale è troppo alta. + +Una volta definito il `Trainer`, è molto semplice: basta prendere un batch dal training set, ed eseguire un piccolo ciclo di addestramento manuale utilizzando solo quel `batch` per qualcosa come 20 step: + +```py +for batch in train_dataset: + break + +# Make sure you have run model.compile() and set your optimizer, +# and your loss/metrics if you're using them + +model.fit(batch, epochs=20) +``` + + + +💡 Se i dati di addestramento sono sbilanciati, assicurati di creare un batch di dati di addestramento contenente tutte le label. + + + +Il modello risultante dovrebbe avere risultati quasi perfetti sul `batch`, con una loss che diminuisce rapidamente verso lo 0 (o il valore minimo per la loss che si sta utilizzando). + +Se non si riesci a far sì che il modello ottenga risultati perfetti come questo, significa che c'è qualcosa di sbagliato nel modo in cui si è impostato il problema o con i dati, e quindi dovresti risolvere questa cosa. Solo quando riesci a superare il test di overfitting puoi essere sicuro/a che il tuo modello possa effettivamente imparare qualcosa. + + + +⚠️ Sarà necessario ricreare il modello e ricompilarlo dopo questo test, poiché il modello ottenuto probabilmente non sarà in grado di recuperare e imparare qualcosa di utile sul set di dati completo. + + + +### Non calibrare niente prima di avere una prima baseline + +Hyperparameter tuning (_calibrazione degli iperparametri_) è sempre considerato come la parte più difficile di machine learning, ma è solo l'ultimo passo per aiutarti a migliorare un po' la metrica. Valori *molto* sbagliati di iperparametri, come l'uso del learning rate predefinito di Adam di 1e-3 con un modello Transformer, faranno sì che l'apprendimento proceda molto lentamente o si blocchi completamente, naturalmente, ma la maggior parte delle volte iperparametri "ragionevoli", come un learning rate da 1e-5 a 5e-5, funzioneranno bene per darti buoni risultati. Quindi, non ci si deve lanciare in una ricerca di iperparametri dispendiosa in termini di tempo e di costi, finché non si è ottenuto qualcosa che batta la baseline (_base di partenza_) che si ha sul dataset. + +Una volta ottenuto un modello sufficientemente buono, si può iniziare a modificarlo un po'. Non provare a eseguire l'addestramento un migliaio di volte con iperparametri diversi, ma confronta un paio di esecuzioni che hanno valori diversi per un iperparametro così da avere un'idea di quale abbia il maggiore impatto. + +Se stai modificando il modello stesso, mantieni le cose semplici e non provare nulla che non possa essere ragionevolmente giustificato. Assicurati sempre di rifare il test di overfitting per verificare che la modifica non abbia avuto conseguenze indesiderate. + +### Chiedere aiuto + +Speriamo che in questa sezione tu abbia trovato qualche consiglio utile a risolvere il tuo problema, ma se così non fosse, ricordati che puoi sempre chiedere aiuto alla community nei [forum](https://discuss.huggingface.co/). + +Qui di seguito sono riportate alcune risorse aggiuntive che potrebbero rivelarsi utili: + +- ["Reproducibility as a vehicle for engineering best practices"](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p) di Joel Grus +- ["Checklist for debugging neural networks"](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) di Cecelia Shao +- ["How to unit test machine learning code"](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) di Chase Roberts +- ["A Recipe for Training Neural Networks"](http://karpathy.github.io/2019/04/25/recipe/) di Andrej Karpathy + +Naturalmente, non tutti i problemi che incontrerai durante l'addestramento delle reti neurali sono colpa tua! Se si incontra qualcosa nella libreria 🤗 Transformers o 🤗 Datasets che non sembra corretto, è possibile che si sia trovato un bug. Dovresti assolutamente segnalarcelo e nella prossima sezione ti spiegheremo esattamente come fare. diff --git a/chapters/it/chapter8/5.mdx b/chapters/it/chapter8/5.mdx new file mode 100644 index 000000000..683738e94 --- /dev/null +++ b/chapters/it/chapter8/5.mdx @@ -0,0 +1,91 @@ +# Come scrivere un issue correttamente + + + +Quando si riscontra una cosa che non va in una delle librerie di Hugging Face, dovresti assolutamente farcelo sapere così possiamo correggerla (lo stesso vale per qualsiasi libreria open source, se è per questo). Se non si è del tutto sicuri se il bug risieda nel proprio codice o in una delle nostre librerie, il primo posto da controllare è il [forum](https://discuss.huggingface.co/). La community ti aiuterà a capirlo e anche il team di Hugging Face segue da vicino le discussioni. + + + +Quando si è sicuri di avere un bug tra le mani, il primo passo è creare un minimo esempio riproducibile. + +## Creare un minimo esempio riproducibile + +È molto importante isolare il pezzo di codice che produce il bug, poiché nessuno del team di Hugging Face è un mago (ancora) e non possono risolvere ciò che non vedono. Un minimo esempio riproducibile dovrebbe, come indica il nome, essere riproducibile. Ciò significa che non deve fare affidamento su file o dati esterni. Prova a sostituire i dati che stai usando con alcuni valori fittizi che assomigliano a quelli reali e producono lo stesso errore. + + + +🚨 Molti issue presenti nel repository di 🤗 Transformers sono irrisolti perché i dati utilizzati per riprodurli non sono accessibili. + + + +Una volta che si ha qualcosa di autocontenuto, si può cercare di ridurlo in un numero ancora minore di righe di codice, costruendo quello che chiamiamo un _minimo esempio riproducibile_. Sebbene questo richieda un po' più di lavoro da parte tua, se fornisci un breve e chiaro esempio di bug, avrai quasi la garanzia di ricevere aiuto e una correzione. + +Se ti senti abbastanza a tuo agio, vai a ispezionare il codice sorgente in cui si verifica il tuo bug. Potresti trovare una soluzione al tuo problema (nel qual caso puoi anche fare una pull request per risolverlo), ma più in generale, questo può aiutare i maintainer a capire meglio il codice quando leggono la tua segnalazione. + +## Compilare il template di un issue + +Quando si segnala un problema, si noterà che c'è un template da compilare. Qui seguiremo quello per [🤗 Transformers issues](https://github.com/huggingface/transformers/issues/new/choose), ma lo stesso tipo di informazioni sarà richiesto se segnali un problema in un altro repository. Non lasciate il template in bianco: prendersi il tempo di compilarlo massimizzerà le possibilità di ottenere una risposta e di risolvere il problema. + +In generale, quando si segnala un problema, bisogna sempre essere cortesi. Questo è un progetto open source, quindi state usando software libero e nessuno ha l'obbligo di aiutarvi. Si possono inserire nel problema critiche giustificate, ma i maintainer potrebbero prenderle male e non avere fretta di aiutarvi. Assicuratevi di leggere il [code of conduct](https://github.com/huggingface/transformers/blob/master/CODE_OF_CONDUCT.md) del progetto. + +### Includere le informazioni sul tuo ambiente di sviluppo + +🤗 Transformers fornisce un'utilità per ottenere tutte le informazioni necessarie sul tuo ambiente di sviluppo. Basta digitare quanto segue nel terminale: + +``` +transformers-cli env +``` + +e si dovrebbe ottenere qualcosa di simile: + +```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?: +``` + +Si può anche aggiungere un `!` all'inizio del comando `transformers-cli env` per eseguirlo da una cella del notebook e poi copiare e incollare il risultato all'inizio dell'issue. + +### Taggare persone + +Taggare le persone digitando una `@` seguita dal loro handle GitHub invierà loro una notifica, in modo che vedano il problema e possano rispondere più rapidamente. Usa questo metodo con moderazione, perché le persone che tagghi potrebbero non apprezzare di ricevere una notifica per qualcosa a cui non hanno un collegamento diretto. Se hai esaminato il codice sorgente relativo al tuo bug, dovresti taggare l'ultima persona che ha apportato modifiche alla riga che ritieni responsabile del tuo problema (puoi trovare questa informazione guardando la riga in questione su GitHub, selezionandola e facendo clic su "View git blame"). + +Altrimenti, il template offre suggerimenti sulle persone da taggare. In generale, non taggare mai più di tre persone! + +### Includere un esempio riproducibile + +Se sei riuscito a creare un esempio autocontenuto che produce il bug, è il momento di includerlo! Scrivi una riga con tre backtick seguiti da `python`, come questa: + +``` +```python +``` + +quindi incolla il tuo minimo esempio riproducibile e digita una nuova riga con tre backtick. In questo modo il codice sarà formattato correttamente. + +Se non sei riuscito/a a creare un esempio riproducibile, spiega in modo chiaro come sei arrivato/a al tuo problema. Se possibile, includi un link al notebook di Google Colab in cui hai riscontrato l'errore. Più informazioni si condividono, più i maintainer saranno in grado di rispondere. + +In ogni caso, è necessario copiare e incollare l'intero messaggio di errore ricevuto. Se lavori in Colab, ricorda che alcuni frame potrebbero essere automaticamente compressi nella stack trace, quindi assicurati di espanderli prima di copiarli. Come nel caso dell'esempio di codice, inserisci il messaggio di errore tra due righe con tre backtick, in modo che sia formattato correttamente. + +### Descrivere il funzionamento atteso + +Spiega in poche righe cosa ti aspettavi di ottenere, in modo che i maintainer abbiano una visione completa del problema. Questa parte è generalmente abbastanza ovvia, quindi dovrebbe essere contenuta in una frase, ma in alcuni casi si può avere molto da dire. + +## E poi? + +Una volta inviato l'issue, assicurati di controllare rapidamente che tutto sia a posto. Puoi modificare l'issue se hai commesso un errore, o anche cambiare il titolo se ti rendi conto che il problema è diverso da quello che pensavi all'inizio. + +È inutile sollecitare le persone se non si ottiene una risposta. Se nessuno ti aiuta in pochi giorni, è probabile che nessuno sia in grado di risolvere il tuo problema. Non esitare a rivedere l'esempio riproducibile. Puoi renderlo più breve e più dritto al punto? Se non ricevi una risposta entro una settimana, puoi aggiungere un messaggio in cui chiedi gentilmente aiuto, soprattutto se hai modificato il tuo problema per includere ulteriori informazioni sul problema. diff --git a/chapters/it/chapter8/6.mdx b/chapters/it/chapter8/6.mdx new file mode 100644 index 000000000..f0792a85a --- /dev/null +++ b/chapters/it/chapter8/6.mdx @@ -0,0 +1,7 @@ +# Parte 2 completata! + +Congratulazioni, hai completato la seconda parte del corso! Stiamo lavorando attivamente alla terza parte, quindi iscrivetevi alla nostra [newsletter](https://huggingface.curated.co/) per essere sicuro/a di non perdere il suo rilascio. + +Ora dovresti essere in grado di risolvere una serie di problemi di NLP e di affinare o preaddestrare un modello su di essi. Non dimenticare di condividere i tuoi risultati con la community su [Model Hub](https://huggingface.co/models). + +Non vediamo l'ora di vedere cosa svilupperai con le conoscenze acquisite! diff --git a/chapters/it/chapter8/7.mdx b/chapters/it/chapter8/7.mdx new file mode 100644 index 000000000..467e4943a --- /dev/null +++ b/chapters/it/chapter8/7.mdx @@ -0,0 +1,199 @@ + + +# Quiz di fine capitolo + +Mettiamo alla prova quello che hai imparato in questo capitolo! + +### 1. In quale ordine si deve leggere un traceback di Python? + + + +### 2. Che cos'è un minimo esempio riproducibile? + + + +### 3. Supponiamo di provare a eseguire il codice seguente, il quale produce un errore: + +```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) +``` + +Quale dei seguenti potrebbe essere una buona scelta per il titolo di un topic del forum per chiedere aiuto? + +ImportError: cannot import name 'GPT3ForSequenceClassification' from 'transformers' (/Users/lewtun/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/__init__.py)", + explain: "Includere l'ultima riga del traceback può essere esplicativo, ma è meglio riservarlo al corpo principale del topic. Riprov!" + }, + { + text: "Problema con from transformers import GPT3ForSequenceClassification", + explain: "Riprova -- sebbene questo fornisca informazioni utili, è probabilmente meglio riservarle al corpo principale del testo.", + }, + { + text: "Perché non posso importare GPT3ForSequenceClassification?", + explain: "Ottima scelta! Questo titolo è conciso e dà al lettore un indizio su ciò che potrebbe essere sbagliato (ad esempio, che il GPT-3 non è supportato nei 🤗 Transformers).", + correct: true + }, + { + text: "GPT-3 è supportato in 🤗 Transformers?", + explain: "Buona questa! Usare domande come titoli dei topic è un ottimo modo per comunicare il problema alla community.", + correct: true + } + ]} +/> + +### 4. Supponiamo di aver provato a eseguire `trainer.train()` e di trovarci di fronte a un errore criptico che non ci dice esattamente da dove proviene. Quale dei seguenti è il primo posto in cui cercare gli errori nella training pipeline? + + + +### 5. Qual è il modo migliore per fare il debug di un errore CUDA? + + + +### 6. Qual è il modo migliore per far risolvere un problema su GitHub? + + + +### 7. Perché l'overfitting di un batch è di solito una buona tecnica di debugging? + + + +### 8. Perché è una buona idea includere dettagli sul proprio ambiente di sviluppo con `transformers-cli env` quando si crea un nuovo issue nel repo di 🤗 Transformers? + + diff --git a/chapters/ja/_toctree.yml b/chapters/ja/_toctree.yml index af556d6b3..07e20fe21 100644 --- a/chapters/ja/_toctree.yml +++ b/chapters/ja/_toctree.yml @@ -46,6 +46,15 @@ title: チャプター修了クイズ quiz: 7 +- title: 8. 助けの求め方 + sections: + - local: chapter8/1 + title: イントロダクション + - local: chapter8/2 + title: エラーを見つけた時に最初にすること + - local: chapter8/6 + title: 概要 + - title: Hugging Faceコースのイベント sections: - local: event/1 diff --git a/chapters/ja/chapter8/1.mdx b/chapters/ja/chapter8/1.mdx new file mode 100644 index 000000000..5c45820d8 --- /dev/null +++ b/chapters/ja/chapter8/1.mdx @@ -0,0 +1,12 @@ +# イントロダクション + +🤗 Transformers を使いながらNLPタスクに取り組む方法がわかった後、自分自身のプロジェクトを簡単に始めることができます! この章では、エラーにぶつかったときにどうすればよいかを深く探ります。自分のコードやトレーニングを正確にデバッグする方法、そして自分でエラーを解決できない場合にコミュニティに助けを求める方法を一緒に学びましょう。また、HuggingFace (ハギングフェイス) ライブラリのひとつにバグを見つけたと思ったら、その問題をできるだけ早く解決するため報告する方法を紹介します。 + +この章では、一緒に次のことを学びます: + +- エラーを見つけた時に最初にすること +- [ハギングフェイス フォーラム](https://discuss.huggingface.co/)の中で助けの求め方 +- トレーニングパイプラインのデバグ方法 +- 良いGitHubイシューの書き方 + +もちろん、このどれもが🤗 Transformersやハギングフェイスのエコシステムと特別な関係はありません。この章から得られる教訓は、ほとんどのオープンソースプロジェクトに適用可能です \ No newline at end of file diff --git a/chapters/ja/chapter8/2.mdx b/chapters/ja/chapter8/2.mdx new file mode 100644 index 000000000..c6d73057c --- /dev/null +++ b/chapters/ja/chapter8/2.mdx @@ -0,0 +1,359 @@ +# エラーを見つけた時に最初にすること + + + +このセクションでは、新しくチューニングされたTransformerモデルから予測を生成しようとするときに起こる事ができる、いくつかの一般的なエラーについて見ていきましょう。これは[セクション4](/course/chapter8/section4)の準備となり、学習段階をデバッグする方法を見ていきましょう。 + + + +この章の為に[テンプレートレポジトリー](https://huggingface.co/lewtun/distilbert-base-uncased-finetuned-squad-d5716d28)を用意しました。この章のコードを実行したい場合は、まずモデルを[ハギングフェイスハブ(Hugging Face Hub)](https://huggingface.co)のアカウントにコピーする必要があります。まずJupyterNotebookでログインしましょう + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +それとも、ターミナルを使いながら: + +```bash +huggingface-cli login +``` + +ユーザー名とパスワードを入力する画面が表示されます。Authentification token が *~/.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(): + # Clone the repo and extract the local path + template_repo_id = "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28" + commit_hash = "be3eaffc28669d7932492681cd5f3e8905e358b4" + template_repo_dir = snapshot_download(template_repo_id, revision=commit_hash) + # Create an empty repo on the Hub + model_name = template_repo_id.split("/")[1] + create_repo(model_name, exist_ok=True) + # Clone the empty repo + new_repo_id = get_full_repo_name(model_name) + new_repo_dir = model_name + repo = Repository(local_dir=new_repo_dir, clone_from=new_repo_id) + # Copy files + copy_tree(template_repo_dir, new_repo_dir) + # Push to Hub + repo.push_to_hub() +``` + +`copy_repository_template()`機能はテンプレートレポジトリーをアカウントにコピーします。 + +## 🤗 Transformers パイプラインのデバグ + +Transformerモデルとパイプラインのデバッグという素晴らしい世界への旅を始めるにあたり、次のようなシナリオを考えてみましょう。あなたは同僚と一緒に、あるeコマースサイトに関するお客さんの答えを見つけられるように、'Question Answering'のプロジェクトに取り組んでいます。あなたの同僚はこんなメッセージを送りました。 + +> どうも! 先ほど、【第7章】(/course/chapter7/7) のテクニックを使って実験をしたところ、SQuADデータセットで素晴らしい結果が得られました!Hub上のモデルIDは"lewtun/distilbert-base-uncased-finetuned-squad-d5716d28"です。このモデルをぜひテストしてみましょう! + +さて、🤗 Transformers でモデルをロードするために  `pipeline` を使いましょう! + +```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` が行う一連の関数呼び出しを反映していますいます(詳しくは[第2章](/course/chapter2))をご覧下さい。 + + + +🚨 Google Colabのトレースバックで、「6frames」のあたりに青い枠があるのが見えますか?これはColabの特別な機能で、トレースバックを "フレーム "に圧縮しているのです。もしエラーの原因が詳しく見つからないようであれば、この2つの小さな矢印をクリックして、トレースバックの全容を拡大してみてください。 + + + +これは、トレースバックの最後の行が、発生したエラーの名前を与えることをします。エラーのタイプは `OSError` で、これはシステム関連のエラーを示しています。付属のエラーメッセージを読むと、モデルの *config.json* ファイルに問題があるようで、それを修正するための2つの提案を与えられています。 +```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+) が見付けられます。これは、問題解決の出発点として使うことができます。 + + +最初の提案は、モデルIDが実際に正しいかどうかを確認するよう求めているので、まず、識別子をコピーしてHubの検索バーに貼り付けましょう。 + +
+The wrong model name. +
+ +うーん、確かに同僚のモデルはハブにないようだ...しかし、モデルの名前にタイプミスがある! DistilBERTの名前には「l」が1つしかないので、それを直して、代わりに「lewtun/distilbert-base-uncased-finetuned-squad-d5716d28」を探そう! +
+The right model name. +
+ +さて、これでヒットしました。では、正しいモデルIDで再度ダウンロードをしてみましょう。 + +```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 +""" +``` + +ああ、また失敗だ。機械学習エンジニアの日常へようこそ! モデルIDは修正できたので、問題はリポジトリ自体にあるはずです。🤗 Hub上のリポジトリの内容にアクセスする簡単な方法は、`huggingface_hub`ライブラリの `list_repo_files()` 関数を使用することです。 + +```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にプッシュするのを忘れたに違いありません。この場合、問題を解決するのは簡単です。ファイルを追加するように依頼するか、モデルIDから使用された事前学習済みモデルが[`distilbert-base-uncased`](https://huggingface.co/distilbert-base-uncased)であることがわかるので、そのモデルのconfig(設定)をダウンロードし、我々のリポジトリにプッシュして問題が解決するか確認することができます。それでは試してみましょう。[第2章](/course/chapter2)で学んだテクニックを使って、`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のエラーメッセージは __traceback__ と呼ばれ、下から上へと読み上げられます。エラーメッセージの最後の行は一番必要な情報を含んでいます。 +- エラーメッセージが複雑な場合は、__traceback__ を読み上げながらエラーが発生したソースコードファイル名や行番号を指定して、エラーの原因を読み取ることができます。 +- その場合でもデバグすることができないなら、インターネット上にを検索してみましょう。 +- `huggingface_hub` ライブラリは、Hubのリポジトリを使用するため一連のツールを提供します。デバッグするために使用できるのツールも含めています。 + +パイプラインのデバッグ方法がわかったところで、モデルのフォワードパスで難しい例を見てみましょう。 + +## モデルのフォワードパスをデバッグ + +時にはモデルのロジットにアクセスする必要があります(例えば、カスタムなパイプラインを使いたい場合で)。このような場合、何が問題になるかを知るために、まず `pipeline` からモデルとトークナイザーを取得してみましょう。 + +```python +tokenizer = reader.tokenizer +model = reader.model +``` + +次に必要なのは、お気に入りのフレームワークがサポートされているかどうかという質問です。 + +```python +question = "Which frameworks can I use?" +``` + +[第7章](/course/chapter7)で見たように、通常必要なステップは、入力のトークン、開始と終了トークンのロジット、そして解答スパンのデコードです。 + +```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 +# Get the most likely beginning of answer with the argmax of the score +answer_start = torch.argmax(answer_start_scores) +# Get the most likely end of answer with the argmax of the score +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' object has no attribute 'size'` と、 `-->` 矢印が `model(**inputs)` の中で問題が発生した行を指していることが分かります。Pythonデバッガを使ってインタラクティブにデバッグできますが、今は単に `inputs` のスライスを表示して何があるか見てみましょう。 + +```python +inputs["input_ids"][:5] +``` + +```python out +[101, 2029, 7705, 2015, 2064] +``` + +これは確かに普通のPythonの `list` のように見えますが、再確認してみましょう。 +```python +type(inputs["input_ids"]) +``` + +```python out +list +``` + +これは確かにPythonの`list`ですね。では何がいけなかったのか?[第2章](/course/chapter2)で、🤗 Transformersの `AutoModelForXxx` クラスは _tensor_ (PyTorch または TensorFlow のいずれか)を使いながら、例えばPyTorchの `Tensor.size()`機能を呼び出しています。トレースバックをもう一度見て、どの行で例外が発生したかを確認しましょう。 + +``` +~/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 +# Get the most likely beginning of answer with the argmax of the score +answer_start = torch.argmax(answer_start_scores) +# Get the most likely end of answer with the argmax of the score +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/ja/chapter8/6.mdx b/chapters/ja/chapter8/6.mdx new file mode 100644 index 000000000..c644a5c50 --- /dev/null +++ b/chapters/ja/chapter8/6.mdx @@ -0,0 +1,7 @@ +# パート2終了! + +お疲れ様でした。第2部を無事に完了しました!今は第3部について働いてるので情報を見逃せないように我々の[ニュースレター](https://huggingface.curated.co/)に応募して下さい! + +今は、様々なNLPタスクに取り組み、その上でモデルを微調整またはプリトレーニングできるようになったはずです!その結果を [モデルハブ] (https://huggingface.co/models) でコミュニティと共有することを忘れないでください。 + +皆さんがコースのお陰に得た知識で何を作るのか、楽しみです! \ No newline at end of file diff --git a/chapters/vi/_toctree.yml b/chapters/vi/_toctree.yml new file mode 100644 index 000000000..6bf3d27eb --- /dev/null +++ b/chapters/vi/_toctree.yml @@ -0,0 +1,86 @@ +- title: 0. Cài đặt + sections: + - local: chapter0/1 + title: Giới thiệu + +- title: 1. Mô hình Transformer + sections: + - local: chapter1/1 + title: Giới thiệu + - local: chapter1/2 + title: Xử lý Ngôn Ngữ Tự nhiên + - local: chapter1/3 + title: Transformers có thể làm những gì? + - local: chapter1/4 + title: Cơ chế hoạt động của Transformer? + - local: chapter1/5 + title: Các mô hình mã hóa + - local: chapter1/6 + title: Các mô hình giải mã + - local: chapter1/7 + title: Các mô hình mã hoá-giải mã + - local: chapter1/8 + title: Thiên kiến và hạn chế + - local: chapter1/9 + title: Tổng kết + - local: chapter1/10 + title: Đố vui cuối chương + quiz: 1 + +- title: 2. Sử dụng 🤗 Transformers + sections: + - local: chapter2/1 + title: Giới thiệu + - local: chapter2/2 + title: Đằng sau pipeline + - local: chapter2/3 + title: Các mô hình + - local: chapter2/4 + title: Tokenizers + - local: chapter2/5 + title: Xử lý đa chuỗi + - local: chapter2/6 + title: Kết hợp lại + - local: chapter2/7 + title: Hoàn thành cách sử dụng cơ bản! + - local: chapter2/8 + title: Đố vui cuối chương + quiz: 2 + +- title: 3. Tinh chỉnh một mô hình huấn luyện trước + sections: + - local: chapter3/1 + title: Giới thiệu + - local: chapter3/2 + title: Xử lý dữ liệu + - local: chapter3/3 + title: Tinh chỉnh một mô hình với Trainer API hoặc Keras + local_fw: { pt: chapter3/3, tf: chapter3/3_tf } + - local: chapter3/4 + title: Bản huấn luyện hoàn chỉnh + - local: chapter3/5 + title: Tỉnh chỉnh, thử xem! + - local: chapter3/6 + title: Đố vui cuối chương + quiz: 3 + +- title: 4. Chia sẻ các mô hình và tokenizer + sections: + - local: chapter4/1 + title: Hugging Face Hub + - local: chapter4/2 + title: Sử dụng các mô hình huấn luyện trước + - local: chapter4/3 + title: Chia sẻ các mô hình huấn luyện trước + - local: chapter4/4 + title: Xây dựng các thẻ mô hình + - local: chapter4/5 + title: Hoàn thành phần 1! + - local: chapter4/6 + title: Đố vui cuối chương + quiz: 4 + +- title: Sự kiện Khoá học Hugging Face + sections: + - local: event/1 + title: Sự kiện Phát hành Phần 2 diff --git a/chapters/vi/chapter0/1.mdx b/chapters/vi/chapter0/1.mdx new file mode 100644 index 000000000..6dbefb5ca --- /dev/null +++ b/chapters/vi/chapter0/1.mdx @@ -0,0 +1,118 @@ +# Giới thiệu + +Chào mừng các bạn đến với khoá học Hugging Face. Trong phần Giới thiệu này, chúng tôi sẽ hướng dẫn các bạn cách thiết lập môi trường làm việc. Nếu bạn mới bắt đầu khoá học, chúng tôi khuyến khích các bạn xem [Chương 1](/course/chapter1) trước rồi sau đó quay lại và cài đặt môi trường làm việc để bạn có thể tự thử nghiệm các đoạn mã nguồn. + +Tất cả các thư viện mà chúng ta sẽ sử dụng ở khóa học này đều được đóng gói sẵn trong Python, vì vậy ở đây chúng tôi sẽ chỉ cho các bạn cách thiết lập môi trường Python và cài đặt các thư viện cụ thể mà bạn cần. + +Chúng tôi sẽ đề cập đến hai cách thiết lập môi trường làm việc, sử dụng sổ ghi chép Colab hoặc môi trường ảo Python. Hãy thoải mái chọn phương pháp phù hợp và thuận tiện với bạn nhất. Đối với người mới học, chúng tôi khuyến khích các bạn nên bắt đầu bằng cách sử dụng sổ ghi chép Colab. + +Lưu ý rằng chúng tôi sẽ không đề cập đến hệ thống Windows. Nếu bạn đang sử dụng Windows, chúng tôi khuyên bạn nên dùng sổ ghi chép Colab. Nếu bạn đang sử dụng Linux hoặc macOS, bạn có thể chọn một trong hai cách tiếp cận được mô tả trong phần này. + +Hầu hết nội dung khóa học phụ thuộc vào việc bạn có một tài khoản Hugging Face. Chúng tôi khuyến khích bạn tạo một tài khoản ngay bây giờ: [tạo tài khoản](https://huggingface.co/join). + +## Sử dụng sổ ghi chép Google Colab + +Sử dụng sổ ghi chép Colab có thể coi là cách thiết lập đơn giản nhất; khởi động sổ ghi chép trong trình duyệt của bạn và bắt đầu viết mã thôi! + +Nếu bạn không quen thuộc với Colab, chúng tôi khuyên bạn nên bắt đầu bằng cách làm theo phần [giới thiệu](https://colab.research.google.com/notebooks/intro.ipynb). Colab cho phép bạn sử dụng một số phần cứng tăng tốc, như GPU hoặc TPU, và nó miễn phí cho các khối lượng công việc nhỏ hơn. + +Khi bạn cảm thấy thoải mái với các thao tác trong Colab, hãy tạo một sổ ghi chép mới và bắt đầu phần cài đặt: + +
+ An empty colab notebook +
+ +Bước tiếp theo là cài đặt các thư viện mà chúng ta sẽ sử dụng trong khóa học này. Chúng ta sẽ sử dụng `pip`, một trình quản lý gói cho Python, để cài đặt. Trong sổ ghi chép, bạn có thể chạy các lệnh hệ thống bằng cách đặt trước chúng ký tự `!`, từ đó, bạn có thể cài đặt thư viện 🤗 Transformers như sau: + +``` +!pip install transformers +``` + +Bạn có thể đảm bảo rằng gói đã được cài đặt chính xác bằng cách nhập nó trong thời gian chạy Python của bạn: + +``` +import transformers +``` + +
+ A gif showing the result of the two commands above: installation and import +
+ +Câu lệnh trên cài đặt một phiên bản rất nhẹ của 🤗 Transformers. Đặc biệt, không có khung học máy cụ thể nào (như PyTorch hoặc TensorFlow) được cài đặt. Vì chúng ta sẽ sử dụng nhiều tính năng khác nhau của thư viện, chúng tôi khuyên bạn nên cài đặt phiên bản phát triển, đi kèm với tất cả các thư viện phụ thuộc bắt buộc cho nhiều trường hợp có thể nghĩ tới: + +``` +!pip install transformers[sentencepiece] +``` + +Câu lệnh này sẽ mất một chút thời gian để thực thi, nhưng sau đó, bạn sẽ sẵn sàng tiếp tục toàn bộ phần còn lại của khóa học! + +## Sử dụng môi trường ảo Python + +Nếu bạn thích sử dụng môi trường ảo Python, đầu tiên, bạn cần cài đặt Python trên hệ thống của bạn. Chúng tôi khuyên bạn nên làm theo [hướng dẫn này](https://realpython.com/installing-python/) để bắt đầu. + +Khi bạn đã cài đặt Python xong, bạn sẽ có thể chạy các lệnh Python trên giao diện dòng lệch (terminal) của mình. Bạn có thể bắt đầu bằng cách chạy lệnh sau để đảm bảo rằng Python được cài đặt chính xác trước khi tiếp tục các bước tiếp theo: `python --version`. Câu lệnh này sẽ in ra phiên bản Python hiện có trên hệ thống của bạn. + +Khi chạy một lệnh Python trên terminal của bạn, chẳng hạn như `python --version`, bạn có thể coi chương trình chạy lệnh là chương trình Python chính trên hệ thống của bạn. Chúng tôi khuyên bạn nên giữ bản cài đặt chính này khỏi bất kỳ gói thư viện nào và sử dụng nó để tạo môi trường riêng biệt cho từng ứng dụng bạn làm việc - với cách này, mỗi ứng dụng có thể có các gói và thư viện phụ thuộc riêng và bạn sẽ không cần phải lo lắng về các vấn đề tiềm ẩn về tương thích với các ứng dụng khác. + +Trong Python, điều này được thực hiện với [_virtual environments_](https://docs.python.org/3/tutorial/venv.html), một cây thư mục độc lập chứa một bản cài đặt Python với một phiên bản Python cụ thể cùng với tất cả các gói ứng dụng cần thiết. Việc tạo một môi trường ảo như vậy có thể được thực hiện bằng một số công cụ khác nhau, nhưng chúng ta sẽ sử dụng gói Python chính thức cho mục đích đó, được gọi là [`venv`](https://docs.python.org/3/library/venv.html#module-venv). + +Trước tiên, hãy tạo ra thư mục mà bạn muốn chứa ứng dụng của mình - ví dụ: bạn có thể tạo một thư mục mới có tên _transformers-course_ ở gốc của thư mục chính: + +``` +mkdir ~/transformers-course +cd ~/transformers-course +``` + +Từ bên trong thư mục này, chúng ta tạo một môi trường ảo bằng cách sử dụng mô-đun Python `venv`: + +``` +python -m venv .env +``` + +Bây giờ bạn sẽ có một thư mục được gọi là _.env_ trong thư mục trống của bạn: + +``` +ls -a +``` + +```out +. .. .env +``` + +Bạn có thể vào và thoát ra khỏi môi trường ảo của mình bằng câu lệnh `activate` và `deactivate`: + +``` +# Kích hoạt môi trường ảo +source .env/bin/activate + +# Huỷ kích hoạt môi trường ảo +source .env/bin/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! + +``` +which python +``` + +```out +/home//transformers-course/.env/bin/python +``` + +### Cài đặt các thư viện phụ thuộc + +Tương tự với cách sử dụng các phiên bản Google Colab như trong phần trước, bạn sẽ cần cài đặt các gói thư viện cần thiết. Một lần nữa, các bạn có thể cài đặt phiên bản phát triển của 🤗 Transformers bằng trình quản lý gói `pip`: + +``` +pip install "transformers[sentencepiece]" +``` + +Bây giờ bạn đã thiết lập xong và sẵn sàng để bắt đầu khám phá nội dung khoá học này! diff --git a/chapters/vi/chapter1/1.mdx b/chapters/vi/chapter1/1.mdx new file mode 100644 index 000000000..5741d0ed6 --- /dev/null +++ b/chapters/vi/chapter1/1.mdx @@ -0,0 +1,56 @@ +# Giới thiệu + +## Chào mừng tới 🤗 Khoá học! + + + +Khóa học này sẽ dạy bạn về Xử lý Ngôn ngữ Tự nhiên (NLP) sử dụng các thư viện từ hệ sinh thái [Hugging Face](https://huggingface.co/) — [🤗 Transformers](https://github.com/huggingface/transformers), [🤗 Datasets](https://github.com/huggingface/datasets), [🤗 Tokenizers](https://github.com/huggingface/tokenizers), và [🤗 Accelerate](https://github.com/huggingface/accelerate) — cũng như [Hugging Face Hub](https://huggingface.co/models). Khoá học hoàn toàn miễn phí và không có quảng cáo. + +## Khóa học có gì? + +Dưới đây là tổng quan ngắn gọn về khóa học: + +
+Brief overview of the chapters of the course. + +
+ +- Các chương từ 1 đến 4 giới thiệu các khái niệm chính của thư viện 🤗 Transformers. Đến cuối học phần này, bạn sẽ quen thuộc với cách hoạt động của các mô hình Transformer và sẽ biết cách sử dụng mô hình từ [Hugging Face Hub](https://huggingface.co/models), tinh chỉnh nó trên một tập dữ liệu cụ thể, và chia sẻ kết quả của bạn lên Hub! +- Các chương từ 5 đến 8 dạy các kiến thức cơ bản về 🤗 Datasets và 🤗 Tokenizers trước khi đi sâu vào các tác vụ NLP kinh điển. Đến cuối học phần này, bạn sẽ có thể tự mình giải quyết các vấn đề NLP phổ biến nhất. +- Các chương từ 9 đến 12 vượt ra ngoài phạm vi NLP và khám phá cách sử dụng các mô hình Transformer để giải quyết các tác vụ trong xử lý giọng nói và thị giác máy tính. Trong quá trình này, bạn sẽ học cách xây dựng và chia sẻ các bản demo về mô hình của mình cũng như cách tối ưu hóa chúng cho môi trường sản xuất. Đến cuối học phần này, bạn sẽ sẵn sàng áp dụng 🤗 Transformers cho (hầu hết) bất kỳ vấn đề học máy nào! + +Khoá học: + +- Yêu cầu có kiến thức tốt về Python. +- Nên tìm hiểu sau khi đã hoàn thành một khóa nhập môn về Học sâu, chẳng hạn như [Practical Deep Learning for Coders](https://course.fast.ai/) của [fast.ai](https://www.fast.ai/) hoặc một trong những chương trình được phát triển bởi [DeepLearning.AI](https://www.deeplearning.ai/). +- Không yêu cầu biết trước các kiến thức về [PyTorch](https://pytorch.org/) hoặc [TensorFlow](https://www.tensorflow.org/), mặc dù quen thuộc với một số kiến thức này sẽ hữu ích. + +Sau khi bạn hoàn thành khóa học này, chúng tôi khuyến khích bạn xem thêm khoá [Natural Language Processing Specialization](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh) của DeepLearning.AI với nội dung bao gồm một loạt các mô hình NLP truyền thống đáng để biết như Naive Bayes và LSTM! + +## Chúng ta là ai? + +Giới thiệu về tác giả: + +**Abubakar Abid** đã hoàn thành chương trình Tiến sĩ về học máy ứng dụng tại Stanford. Trong thời gian học tiến sĩ, anh ấy đã tạo ra [Gradio](https://github.com/gradio-app/gradio), một thư viện Python mã nguồn mở được sử dụng để xây dựng hơn 600,000 bản demo học máy. Gradio được mua lại bởi Hugging Face, nơi Abubakar hiện đóng vai trò là trưởng nhóm học máy. + +**Matthew Carrigan** là một Kỹ sư Học máy tại Hugging Face. Anh ấy sống ở Dublin, Ireland, trước đây là kỹ sư Học máy tại Parse.ly và trước đó là nhà nghiên cứu sau tiến sĩ tại Trinity College Dublin. Anh ấy không tin rằng chúng ta sẽ đạt được AGI bằng cách mở rộng các kiến ​​trúc hiện có, nhưng có niềm tin vào sự bất tử của robot. + +**Lysandre Debut** là một Kỹ sư Học máy tại Hugging Face và đã làm việc với thư viện 🤗 Transformers từ những giai đoạn đầu phát triển. Mục tiêu của anh ấy là làm cho NLP có thể dễ dàng truy cập được từ tất cả mọi người bằng cách phát triển các công cụ với một API rất đơn giản. + +**Sylvain Gugger** là Kỹ sư nghiên cứu tại Hugging Face và là một trong những thành viên cốt lõi của thư viện 🤗 Transformers. Trước đây, anh ấy là Nhà nghiên cứu khoa học tại fast.ai và anh ấy là đồng sáng tác đầu sách _[Deep Learning for Coders with fastai and PyTorch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)_ cùng với Jeremy Howard. Hướng nghiên cứu chính của anh ấy là làm cho việc học sâu trở nên dễ tiếp cận hơn, bằng cách thiết kế và cải tiến các kỹ thuật cho phép các mô hình huấn luyện nhanh trên các tài nguyên hạn chế. + +**Dawood Khan** là một Kỹ sư Học máy tại Hugging Face. Anh ấy đến từ New York và tốt nghiệp Đại học New York chuyên ngành Khoa học máy tính. Sau khi làm việc với tư cách là Kỹ sư iOS trong một vài năm, Dawood đã nghỉ việc để bắt đầu phát triển Gradio cùng với những người đồng sáng lập của mình. Gradio cuối cùng đã được mua lại bởi Hugging Face. + +**Merve Noyan** là Chuyên gia về Quan hệ lập trình viên tại Hugging Face, hiện đang phát triển các công cụ và xây dựng nội dung xung quanh chúng để tất cả mọi người có thể tiếp cận học máy dễ dàng hơn. + +**Lucile Saulnier** là một Kỹ sư Học máy tại Hugging Face, phát triển và hỗ trợ việc sử dụng các công cụ mã nguồn mở. Cô cũng tích cực tham gia vào nhiều dự án nghiên cứu trong lĩnh vực Xử lý Ngôn ngữ Tự nhiên như huấn luyện cộng tác và BigScience. + +**Lewis Tunstall** là một Kỹ sư Học máy tại Hugging Face, tập trung vào việc phát triển các công cụ mã nguồn mở và giúp chúng có thể tiếp cận được với cộng đồng rộng lớn hơn. Anh cũng là đồng tác giả của cuốn sách O’Reilly [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). + +**Leandro von Werra** là một Kỹ sư Học máy trong nhóm mã nguồn mở tại Hugging Face và cũng là đồng tác giả của cuốn sách O'Reilly [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). Anh ấy có nhiều năm kinh nghiệm thực tế triển khai các dự án NLP vào sản xuất bằng cách làm việc trên toàn bộ hệ thống học máy. + +Bạn đã sẵn sàng chưa? Trong chương này, bạn sẽ học: + +- Cách sử dụng hàm `pipeline()` để giải quyết các tác vụ NLP như tạo và phân loại văn bản. +- Về cấu trúc của mạng Transformer. +- Làm thế nào để phân biệt giữa các kiến trúc encoder, decoder, và encoder-decoder cũng như các trường hợp sử dụng. diff --git a/chapters/vi/chapter1/10.mdx b/chapters/vi/chapter1/10.mdx new file mode 100644 index 000000000..1e529bd97 --- /dev/null +++ b/chapters/vi/chapter1/10.mdx @@ -0,0 +1,278 @@ + + +# Đố vui cuối chương + +Chương này bao gồm rất nhiều mặt! Đừng lo lắng nếu bạn không nắm được tất cả các chi tiết; các chương tiếp theo sẽ giúp bạn hiểu mọi thứ hoạt động như thế nào. + +Tuy nhiên, trước tiên, hãy kiểm tra những gì bạn đã học được trong chương này! + +### 1. Khám phá Hub và tìm `roberta-large-mnli`. Nó phục vụ cho tác vụ gì? + +roberta-large-mnli page.', + }, + { + text: "Phân loại văn bản", + explain: + "Chính xác hơn, nó phân loại nếu hai câu được liên kết một cách hợp lý qua ba nhãn (mâu thuẫn, trung lập, vướng mắc) — hay tác vụ còn được gọi là luận suy ngôn ngữ tự nhiên.", + correct: true, + }, + { + text: "Tạo văn bản", + explain: + 'Xem lại tại roberta-large-mnli page.', + }, + ]} +/> + +### 2. Đoạn mã sau sẽ trả về cái gì? + +```py +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +sentiment-analysis.", + }, + { + text: "Nó sẽ trả về một văn bản được tạo để hoàn thành câu này.", + explain: + "Không chính xác - đây là mô tả của pipelinetext-generation.", + }, + { + text: "Nó sẽ trả về các từ đại diện cho người, tổ chức hoặc địa điểm.", + explain: + 'Hơn nữa, với grouped_entities=True, nó sẽ nhóm các từ thuộc cùng một thực thể lại với nhau, ví dụ như "Hugging Face".', + correct: true, + }, + ]} +/> + +### 3. Từ nào có thể thay thế ... trong đoạn mã dưới đây? + +```py +from transformers import pipeline + +filler = pipeline("fill-mask", model="bert-base-cased") +result = filler("...") +``` + + has been waiting for you.", + explain: + "Không chính xác. Kiểm tra thẻ mô hình bert-base-cased và cố gắng phát hiện lỗi của bạn.", + }, + { + text: "This [MASK] has been waiting for you.", + explain: "Chính xác! Đáp án là [MASK].", + correct: true, + }, + { + text: "This man has been waiting for you.", + explain: + "Không chính xác. Pipeline này sẽ điền vào các từ bị che đi, vì vậy nó cần một [MASK] token ở đâu đó.", + }, + ]} +/> + +### 4. Tại sao đoạn mã này sẽ lỗi? + +```py +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +result = classifier("This is a course about the Transformers library") +``` + +candidate_labels=[...].", + correct: true, + }, + { + text: "Pipeline này yêu cầu nhiều câu thay vì một câu.", + explain: + "Không chính xác, mặc dù khi được sử dụng đúng cách, pipeline này có thể lấy một danh sách các câu để xử lý (giống như tất cả các pipeline khác).", + }, + { + text: "Thư viện 🤗 Transformers bị hỏng, như thường lệ.", + explain: + "Chúng tôi sẽ không đánh giá cao câu trả lời này với bất kỳ bình luận nào!", + }, + { + text: "Pipeline yêu cầu đầu vào dài hơn; pipeline này quá ngắn.", + explain: + "Không chính xác. Lưu ý rằng một văn bản rất dài sẽ bị cắt bớt khi xử lý bằng pipeline này.", + }, + ]} +/> + +### 5. "Học chuyển giao" nghĩa là gì? + + + +### 6. Đúng hay sai? Một mô hình ngôn ngữ thường không cần nhãn cho quá trình huấn luyện trước của nó. + +tự giám sát, có nghĩa là các nhãn được tạo ra tự động từ các đầu vào (như dự đoán từ tiếp theo hoặc điền vào một số từ bị che).", + correct: true, + }, + { + text: "Sai", + explain: "Đây không phải đáp án chính xác.", + }, + ]} +/> + +### 7. Chọn câu mô tả đúng nhất các thuật ngữ "mô hình", "kiến trúc" và "trọng số". + + + +### 8. Bạn sẽ sử dụng loại mô hình nào trong số những loại mô hình này để hoàn thành lời nhắc với văn bản được tạo ra? + + + +### 9. Bạn sẽ sử dụng kiểu mô hình nào để tóm tắt văn bản? + + + +### 10. Bạn sẽ sử dụng kiểu mô hình nào trong số những kiểu mô hình này để phân loại đầu vào văn bản theo các nhãn nhất định? + + + +### 11. Sự sai lệch quan sát thấy trong một mô hình có thể bắt nguồn nào? + + diff --git a/chapters/vi/chapter1/2.mdx b/chapters/vi/chapter1/2.mdx new file mode 100644 index 000000000..b5a27a1b7 --- /dev/null +++ b/chapters/vi/chapter1/2.mdx @@ -0,0 +1,21 @@ +# Xử lý Ngôn Ngữ Tự nhiên + +Trước khi chuyển sang mô hình Transformer, chúng ta hãy cùng tìm hiểu nhanh tổng quan về Xử lý Ngôn ngữ Tự nhiên là gì và tại sao chúng ta quan tâm đến lĩnh vực này. + +## Xử lý Ngôn ngữ Tự nhiên (NLP) là gì? + +NLP là một lĩnh vực kết hợp giữa ngôn ngữ học và học máy, tập trung vào việc hiểu mọi thứ liên quan đến ngôn ngữ của con người. Mục đích của các tác vụ NLP không chỉ dừng ở hiểu từng từ đơn lẻ mà còn có thể hiểu ngữ cảnh của những từ đó. + +Dưới đây là danh sách các tác vụ NLP phổ biến, với một số ví dụ về mỗi tác vụ: + +- **Phân loại toàn bộ câu**: Nhận biết cảm xúc của bài đánh giá, phát hiện xem một bức thư điện tử có phải thư rác hay không, xác định xem một câu có đúng ngữ pháp hay không hoặc hai câu có liên quan về mặt logic hay không. +- **Phân loại từng từ trong câu**: Xác định các thành phần ngữ pháp của câu (danh từ, động từ, tính từ), hoặc các thực thể được đặt tên (người, vị trí, tổ chức). +- **Tạo nội dung văn bản**: Hoàn thành lời nhắc với văn bản được tạo tự động, điền vào chỗ trống trong văn bản có các từ bị che. +- **Trích xuất câu trả lời từ văn bản**: Cho một câu hỏi và ngữ cảnh, trích xuất câu trả lời cho câu hỏi dựa trên thông tin được cung cấp trong ngữ cảnh +- **Tạo câu mới từ văn bản đầu vào**: Dịch văn bản sang ngôn ngữ khác, tóm tắt văn bản. + +NLP không giới hạn chỉ trong văn bản viết. Nó cũng giải quyết những thách thức phức tạp trong nhận dạng giọng nói và thị giác máy tính, chẳng hạn như tạo bản ghi chép từ âm thanh hoặc mô tả hình ảnh. + +## Vì sao lĩnh vực này đầy thách thức? + +Máy tính không xử lý thông tin theo cách giống như con người. Ví dụ, khi đọc câu “Tôi đói”, chúng ta có thể dễ dàng hiểu được ý nghĩa của nó. Tương tự, với hai câu như "Tôi đói" và "Tôi buồn", chúng ta có thể dễ dàng xác định xem chúng giống nhau như thế nào. Đối với mô hình học máy (ML), các tác vụ như vậy khó hơn nhiều. Văn bản cần được xử lý theo cách cho phép mô hình học hỏi từ nó. Và bởi vì ngôn ngữ phức tạp, chúng ta cần phải suy nghĩ cẩn thận về cách xử lý này cần thực hiện. Đã có rất nhiều nghiên cứu được thực hiện về cách biểu diễn văn bản, và chúng ta sẽ xem xét một số phương pháp trong chương tiếp theo. diff --git a/chapters/vi/chapter1/3.mdx b/chapters/vi/chapter1/3.mdx new file mode 100644 index 000000000..677b4f48b --- /dev/null +++ b/chapters/vi/chapter1/3.mdx @@ -0,0 +1,327 @@ +# Transformers có thể làm những gì? + + + +Trong phần này, chúng ta sẽ xem các mô hình Transformer có thể làm được những gì và sử dụng công cụ đầu tiên từ thư viện 🤗 Transformers: hàm `pipeline()`. + + +Bạn có thấy nút Mở trong Colab ở trên cùng bên phải không? Bấm vào nó để mở sổ ghi chép Google Colab với tất cả các đoạn mã của phần này. Nút này sẽ xuất hiện trong bất kỳ phần nào có chứa các mã ví dụ. + +Nếu bạn muốn chạy các ví dụ ở máy cá nhân, các bạn có thể tham khảo phần cài đặt. + + +## Transformers ở muôn nơi! + +Các mô hình Transformers được sử dụng để giải quyết tất cả các kiểu tác vụ NLP, giống như các mô hình đề cập trong phần trước. Dưới đây là một số công ty và tổ chức sử dụng Hugging Face và các mô hình Transformer, đồng thời đóng góp lại cho cộng đồng bằng cách chia sẻ các mô hình của họ: + +Companies using Hugging Face + +Thư viện [🤗 Transformers](https://github.com/huggingface/transformers) cung cấp tính năng tạo và sử dụng các mô hình được chia sẻ đó. [Model Hub](https://huggingface.co/models) chứa hàng nghìn mô hình được huấn luyện trước mà bất kỳ ai cũng có thể tải xuống và sử dụng. Bạn cũng có thể tải các mô hình của riêng mình lên Hub! + + +⚠️ Hugging Face Hub không giới hạn ở các mô hình Transformer. Bất kỳ ai cũng có thể chia sẻ bất kỳ loại mô hình hoặc bộ dữ liệu nào họ muốn! Tạo tài khoản huggingface.co để hưởng lợi từ tất cả các tính năng có sẵn này! + + +Trước khi đi sâu vào cách hoạt động của các mô hình Transformer, hãy cùng xem một vài ví dụ về cách sử dụng chúng để giải quyết một số vấn đề NLP thú vị. + +## Làm việc với pipelines + + + +Đối tượng cơ bản nhất trong thư viện 🤗 Transformers là hàm `pipeline()`. Hàm kết nối một mô hình với các bước tiền xử lý và hậu xử lý cần thiết, cho phép chúng ta nhập trực tiếp bất kỳ văn bản nào và nhận được câu trả lời dễ hiểu: + +```python +from transformers import pipeline + +classifier = pipeline("sentiment-analysis") +classifier("I've been waiting for a HuggingFace course my whole life.") +``` + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}] +``` + +Chúng tôi thậm chí có thể truyền vào nhiều câu! + +```python +classifier( + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!"] +) +``` + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}, + {'label': 'NEGATIVE', 'score': 0.9994558095932007}] +``` + +Theo mặc định, quy trình này chọn một mô hình cụ thể được huấn luyện trước và đã được tinh chỉnh để phân tích cảm xúc văn bản tiếng Anh. Mô hình được tải xuống và lưu vào bộ nhớ cache khi bạn tạo đối tượng `classifier`. Nếu bạn chạy lại lệnh, mô hình đã lưu trong bộ nhớ cache sẽ được sử dụng thay thế và không cần tải lại mô hình một lần nữa. + +Có ba bước chính khi bạn chuyển một số văn bản vào một pipeline: + +1. Văn bản được tiền xử lý thành một định dạng mà mô hình có thể hiểu được. +2. Các đầu vào đã tiền xử lý được đưa vào mô hình. +3. Các dự đoán của mô hình được hậu xử lý để bạn có thể hiểu được chúng. + +Một số [pipeline](https://huggingface.co/transformers/main_classes/pipelines.html) hiện có có thể kể đến: + +- `feature-extraction` (trích xuất biểu diễn vectơ của một văn bản) +- `fill-mask` +- `ner` (nhận dạng thực thể) +- `question-answering` +- `sentiment-analysis` +- `summarization` +- `text-generation` +- `translation` +- `zero-shot-classification` + +Chúng ta hãy cùng xem một vài ví dụ trong số kể trên! + +## Phân loại không mẫu (Zero-shot) + +Chúng ta sẽ bắt đầu với việc giải quyết một tác vụ khó nhằn hơn: chúng ta cần phân loại các văn bản chưa được dán nhãn. Đây là một tình huống phổ biến trong các dự án thực tế vì việc đánh nhãn văn bản thường tốn nhiều thời gian và yêu cầu kiến thức chuyên môn. Đối với trường hợp này, `zero-shot-classification` là một phương án mạnh mẽ: nó cho phép bạn chỉ định nhãn nào sẽ sử dụng để phân loại, vì vậy bạn không cần phải dựa vào các nhãn của mô hình được huấn luyện trước. + +Bạn đã thấy cách mô hình có thể phân loại một câu là tích cực hay tiêu cực bằng cách sử dụng hai nhãn đó - nhưng nó cũng có thể phân loại văn bản bằng cách sử dụng bất kỳ bộ nhãn nào khác mà bạn thích. + +```python +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +classifier( + "This is a course about the Transformers library", + candidate_labels=["education", "politics", "business"], +) +``` + +```python out +{'sequence': 'This is a course about the Transformers library', + 'labels': ['education', 'business', 'politics'], + 'scores': [0.8445963859558105, 0.111976258456707, 0.043427448719739914]} +``` + +Quy trình này được gọi là _zero-shot_ (không mẫu) vì bạn không cần tinh chỉnh mô hình trên dữ liệu của bạn để sử dụng. Nó có thể trực tiếp trả về xác suất cho bất kỳ danh sách nhãn nào bạn muốn! + + + +✏️ **Thử nghiệm thôi!** Cùng thử các chuỗi văn bản và các nhãn riêng của bạn để xem mô hình hoạt động như thế nào. + + + +## Tạo văn bản + +Giờ chúng ta hãy cùng xem cách sử dụng một pipeline để tạo ra văn bản. Ý tưởng chính ở đây là bạn cung cấp một lời gợi ý và mô hình sẽ tự động hoàn thành nó bằng cách tạo ra phần văn bản còn lại. Điều này tương tự như tính năng gợi ý văn bản được tìm thấy trên điện thoại. Việc tạo văn bản liên quan đến sự ngẫu nhiên, vì vậy nếu bạn không nhận được kết quả như hình dưới đây cũng là điều dễ hiểu. + +```python +from transformers import pipeline + +generator = pipeline("text-generation") +generator("In this course, we will teach you how to") +``` + +```python out +[{'generated_text': 'In this course, we will teach you how to understand and use ' + 'data flow and data interchange when handling user data. We ' + 'will be working with one or more of the most commonly used ' + 'data flows — data flows of various types, as seen by the ' + 'HTTP'}] +``` + +Bạn có thể kiểm soát số lượng chuỗi khác nhau được tạo với tham số `num_return_sequences` và tổng độ dài của văn bản đầu ra với tham số `max_length`. + + + +✏️ **Thử nghiệm thôi!** Sử dụng `num_return_sequences` và `max_length` để tạo ra hai câu, mỗi câu chứa 15 từ. + + + +## Sử dụng một mô hình bất kỳ từ Hub trong pipeline + +Các ví dụ trước đã sử dụng mô hình mặc định cho các tác vụ, nhưng bạn cũng có thể chọn một mô hình cụ thể từ Hub để sử dụng trong pipeline cho một tác vụ cụ thể - ví dụ, tạo văn bản. Truy cập [Model Hub](https://huggingface.co/models) và bấm vào thẻ tương ứng ở bên trái để chỉ hiển thị các mô hình được hỗ trợ cho tác vụ đó. Bạn sẽ đến một trang như [trang này](https://huggingface.co/models?pipeline_tag=text-generation). + +Hãy thử mô hình [`distilgpt2`](https://huggingface.co/distilgpt2)! Đây là cách tải nó vào cùng một pipeline như phần trước: + +```python +from transformers import pipeline + +generator = pipeline("text-generation", model="distilgpt2") +generator( + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, +) +``` + +```python out +[{'generated_text': 'In this course, we will teach you how to manipulate the world and ' + 'move your mental and physical capabilities to your advantage.'}, + {'generated_text': 'In this course, we will teach you how to become an expert and ' + 'practice realtime, and with a hands on experience on both real ' + 'time and real'}] +``` + +Bạn có thể tinh chỉnh việc tìm kiếm cho một mô hình của mình bằng cách nhấp vào các thẻ ngôn ngữ và chọn một mô hình sẽ tạo văn bản bằng ngôn ngữ khác. Model Hub thậm chí còn chứa các checkpoints cho các mô hình đa ngôn ngữ hỗ trợ một số ngôn ngữ khác nhau. + +Sau khi bạn chọn một mô hình bằng cách bấm vào nó, bạn sẽ thấy rằng có một tiện ích cho phép bạn dùng thử trực tuyến. Bằng cách này, bạn có thể nhanh chóng kiểm tra khả năng của mô hình trước khi tải xuống. + + + +✏️ **Thử nghiệm thôi!** Sử dụng bộ lọc để tìm mô hình tạo văn bản cho ngôn ngữ khác. Hãy thoải mái chơi với tiện ích này và sử dụng nó theo một pipeline! + + + +### Inference API + +Tất cả các mô hình có thể được kiểm tra trực tiếp thông qua trình duyệt của bạn bằng cách sử dụng Inference API, có sẵn trên [trang web Hugging Face](https://huggingface.co/). Bạn có thể chơi với mô hình trực tiếp trên trang này bằng cách nhập văn bản tùy chỉnh và xem mô hình xử lý dữ liệu đầu vào. + +Inference API hỗ trợ tiện ích này cũng có sẵn dưới dạng sản phẩm trả phí, rất hữu ích nếu bạn cần nó cho quy trình công việc của mình. Xem [trang giá](https://huggingface.co/pricing) để biết thêm chi tiết. + +## Điền vào chỗ trống + +Pipeline tiếp theo bạn sẽ thử nghiệm, đó là `fill-mask`. Ý tưởng của tác vụ này là điền vào chỗ trống trong một văn bản nhất định: + +```python +from transformers import pipeline + +unmasker = pipeline("fill-mask") +unmasker("This course will teach you all about models.", top_k=2) +``` + +```python out +[{'sequence': 'This course will teach you all about mathematical models.', + 'score': 0.19619831442832947, + 'token': 30412, + 'token_str': ' mathematical'}, + {'sequence': 'This course will teach you all about computational models.', + 'score': 0.04052725434303284, + 'token': 38163, + 'token_str': ' computational'}] +``` + +Tham số `top_k` kiểm soát số lượng khả năng bạn muốn được hiển thị. Lưu ý rằng ở đây mô hình điền từ vào vị trí bị che bởi từ ``, thường được gọi là *mask token*. Các mô hình điền khác có thể có các kiểu che từ khác nhau, vì vậy, tốt nhất nên xác minh từ bị che phù hợp khi khám phá các mô hình khác. Một cách để kiểm tra đó là xem từ bị che được sử dụng trong tiện ích con. + + + +✏️ **Thử nghiệm thôi!** Tìm kiếm mô hình `bert-base-cased` trên Hub và xác định từ bị che của nó trong tiện ích Inference API. Mô hình này dự đoán điều gì cho câu trong ví dụ về `pipeline` của chúng ta ở trên? + + + +## Nhận dạng thực thể + +Nhận dạng thực thể được đặt tên (NER) là một tác vụ trong đó mô hình phải tìm ra những phần của văn bản đầu vào tương ứng với các thực thể như người, địa điểm, hoặc tổ chức. Hãy xem một ví dụ: + +```python +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +```python out +[{'entity_group': 'PER', 'score': 0.99816, 'word': 'Sylvain', 'start': 11, 'end': 18}, + {'entity_group': 'ORG', 'score': 0.97960, 'word': 'Hugging Face', 'start': 33, 'end': 45}, + {'entity_group': 'LOC', 'score': 0.99321, 'word': 'Brooklyn', 'start': 49, 'end': 57} +] +``` + +Ở đây, mô hình đã xác định chính xác rằng Sylvain là một người (PER), Hugging Face là một tổ chức (ORG) và Brooklyn là một địa điểm (LOC). + +Chúng ta truyền `grouped_entities = True` vào trong hàm pipeline để yêu cầu pipeline nhóm lại các phần thuộc cùng một thực thể trong câu với nhau: ở đây mô hình đã nhóm chính xác "Hugging" và "Face" thành một tổ chức duy nhất, mặc dù tên bao gồm nhiều từ. Trên thực tế, như chúng ta sẽ thấy trong chương tiếp theo, quá trình tiền xử lý thậm chí còn chia một số từ thành các phần nhỏ hơn. Ví dụ: `Sylvain` được chia thành bốn phần: `S`, `##yl`, `##va`, và `##in`. Trong bước hậu xử lý, pipeline đã tập hợp lại thành công các phần đó. + + + +✏️ **Thử nghiệm thôi!** Tìm kiếm trên Model Hub để tìm một mô hình có thể thực hiện gán nhãn từ loại (thường được viết tắt là POS) bằng tiếng Anh. Mô hình này dự đoán điều gì cho câu trong ví dụ trên? + + + +## Hỏi đáp + +Pipeline `question-answering` trả lời các câu hỏi sử dụng thông tin ngữ cảnh cho trước: + +```python +from transformers import pipeline + +question_answerer = pipeline("question-answering") +question_answerer( + question="Where do I work?", + context="My name is Sylvain and I work at Hugging Face in Brooklyn", +) +``` + +```python out +{'score': 0.6385916471481323, 'start': 33, 'end': 45, 'answer': 'Hugging Face'} +``` + +Lưu ý rằng pipeline này hoạt động bằng cách trích xuất thông tin từ ngữ cảnh được cung cấp; nó không tạo ra câu trả lời. + +## Tóm tắt + +Tóm tắt là tác vụ thu gọn một văn bản thành một văn bản ngắn hơn trong khi vẫn giữ tất cả (hoặc hầu hết) các ý quan trọng được đề cập trong văn bản. Dưới đây là một ví dụ: + +```python +from transformers import pipeline + +summarizer = pipeline("summarization") +summarizer( + """ + America has changed dramatically during recent years. Not only has the number of + graduates in traditional engineering disciplines such as mechanical, civil, + electrical, chemical, and aeronautical engineering declined, but in most of + the premier American universities engineering curricula now concentrate on + and encourage largely the study of engineering science. As a result, there + are declining offerings in engineering subjects dealing with infrastructure, + the environment, and related issues, and greater concentration on high + technology subjects, largely supporting increasingly complex scientific + developments. While the latter is important, it should not be at the expense + of more traditional engineering. + + Rapidly developing economies such as China and India, as well as other + industrial countries in Europe and Asia, continue to encourage and advance + the teaching of engineering. Both China and India, respectively, graduate + six and eight times as many traditional engineers as does the United States. + Other industrial countries at minimum maintain their output, while America + suffers an increasingly serious decline in the number of engineering graduates + and a lack of well-educated engineers. +""" +) +``` + +```python out +[{'summary_text': ' America has changed dramatically during recent years . The ' + 'number of engineering graduates in the U.S. has declined in ' + 'traditional engineering disciplines such as mechanical, civil ' + ', electrical, chemical, and aeronautical engineering . Rapidly ' + 'developing economies such as China and India, as well as other ' + 'industrial countries in Europe and Asia, continue to encourage ' + 'and advance engineering .'}] +``` + +Tương tự như tạo văn bản, bạn có thể tuỳ chỉnh `max_length` và `min_length` cho kết quả trả về. + +## Dịch máy + +Đối với dịch máy, bạn có thể sử dụng mô hình mặc định nếu bạn cung cấp một cặp ngôn ngữ trong tên tác vụ (chẳng hạn như `"translation_en_to_fr"`), nhưng cách dễ nhất là chọn mô hình bạn muốn sử dụng trên [Model Hub](https://huggingface.co/models). Tại đây, chúng ta sẽ thử dịch từ tiếng Pháp sang tiếng Anh: + +```python +from transformers import pipeline + +translator = pipeline("translation", model="Helsinki-NLP/opus-mt-fr-en") +translator("Ce cours est produit par Hugging Face.") +``` + +```python out +[{'translation_text': 'This course is produced by Hugging Face.'}] +``` + +Giống như tạo và tóm tắt văn bản, bạn có thể chỉ định giá trị `max_length` hoặc `min_length` cho kết quả trả về. + + + +✏️ **Thử nghiệm thôi!** Tìm kiếm các mô hình dịch ở các ngôn ngữ khác và cố gắng dịch câu trước đó sang một vài ngôn ngữ khác nhau. + + + +Các pipeline ở trên hầu hết phục vụ mục đích trình diễn. Chúng được lập trình cho các tác vụ cụ thể và không thể thực hiện các biến thể của chúng. Trong chương tiếp theo, bạn sẽ tìm hiểu những gì bên trong một hàm `pipeline()` và cách tinh chỉnh hành vi của nó. diff --git a/chapters/vi/chapter1/4.mdx b/chapters/vi/chapter1/4.mdx new file mode 100644 index 000000000..b21901fd4 --- /dev/null +++ b/chapters/vi/chapter1/4.mdx @@ -0,0 +1,170 @@ +# Cơ chế hoạt động của Transformer? + +Trong phần này, chúng ta sẽ tìm hiểu kiến trúc của các mô hình Transformer. + +## Lịch sử phát triển của Transformers + +Dưới đây là một số mốc tham khảo trong lịch sử (ngắn) phát triển của các mô hìnhn Transformer: + +
+A brief chronology of Transformers models. + +
+ +[Kiến trúc Transformer](https://arxiv.org/abs/1706.03762) được giới thiệu vào tháng 6 năm 2017. Trọng tâm của nghiên cứu ban đầu hướng tới các tác vụ dịch thuật. Tiếp theo là sự ra đời của một số mô hình có ảnh hưởng, bao gồm: + +- **06/2018**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), mô hình Transformer được huấn luyện trước đầu tiên, được sử dụng để tinh chỉnh các tác vụ NLP khác nhau và thu được kết quả tốt nhất lúc bấy giờ. + +- **10/2018**: [BERT](https://arxiv.org/abs/1810.04805), một mô hình lớn được huấn luyện trước khác, mô hình này được thiết kế để tạo ra các bản tóm tắt câu tốt hơn (sẽ đề cập thêm trong chương tiếp theo!). + +- **02/2019**: [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), một phiên bản GPT cải tiến (và lớn hơn) nhưng không được phát hành công khai ngay lập tức do lo ngại về vấn đề đạo đức. + +- **10/2019**: [DistilBERT](https://arxiv.org/abs/1910.01108), phiên bản nhẹ của BERT với tốc độ nhanh hơn 60%, bộ nhớ nhẹ hơn 40%, và vẫn giữ được 97% hiệu suất của BERT. + +- **10/2019**: [BART](https://arxiv.org/abs/1910.13461) and [T5](https://arxiv.org/abs/1910.10683), hai mô hình lớn được đào tạo trước sử dụng cùng một kiến trúc với mô hình Transformer gốc (mô hình đầu tiên làm như vậy). + +- **05/2020**, [GPT-3](https://arxiv.org/abs/2005.14165), phiên bản thậm chí còn lớn hơn của GPT-2, có thể thực hiện tốt nhiều tác vụ khác nhau mà không cần tinh chỉnh (còn được gọi là _zero-shot learning_) + +Danh sách này vẫn chưa đầy đủ và chỉ nhằm mục đích làm nổi bật một vài số mô hình Transformer khác nhau. Nhìn chung, chúng có thể được chia thành ba loại: + +- Giống GPT (còn được gọi là _auto-regression_ Transformer) +- Giống BERT (còn được gọi là _auto-encoding_ Transformer) +- Giống BART/T5 (còn được gọi là _sequence-to-sequence_ Transformer) + +Chúng ta sẽ đi sâu hơn vào các nhóm này ở các phần sau. + +## Transformers là mô hình ngôn ngữ + +Tất cả các mô hình Transformer được đề cập ở trên (GPT, BERT, BART, T5, v.v.) được huấn luyện thành các _mô hình ngôn ngữ_. Điều này có nghĩa là chúng đã được huấn luyện trên một lượng lớn văn bản thô theo phương pháp tự giám sát. Học tự giám sát là một loại hình huấn luyện trong đó mục tiêu được tính toán tự động từ các đầu vào của mô hình. Điều đó có nghĩa là con người không cần thiết phải gắn nhãn dữ liệu! + +Loại mô hình này phát triển theo sự hiểu biết thống kê về ngôn ngữ mà nó đã được huấn luyện, nhưng nó không hữu ích lắm cho các tác vụ cụ thể trong thực tế. Do đó, mô hình được huấn luyện chung chung trước sau đó sẽ trải qua một quá trình được gọi là _học chuyển giao_. Trong quá trình này, mô hình được tinh chỉnh theo cách có giám sát - nghĩa là sử dụng các nhãn do con người gán - trên một tác vụ nhất định. + +Ví dụ về một tác vụ có thể kể đến dự đoán từ tiếp theo trong một câu đã đọc cho trước _n_ từ trước đó. Đây được gọi là _mô hình ngôn ngữ nhân quả_ bởi vì đầu ra phụ thuộc vào các đầu vào trong quá khứ và hiện tại, nhưng không phụ thuộc vào các đầu vào trong tương lai. + +
+Example of causal language modeling in which the next word from a sentence is predicted. + +
+ +Another example is _masked language modeling_, in which the model predicts a masked word in the sentence. + +
+Example of masked language modeling in which a masked word from a sentence is predicted. + +
+ +## Transformers là mô hình lớn + +Ngoài một số ngoại lệ (như DistilBERT), chiến lược chung để đạt được hiệu suất tốt hơn là tăng kích thước của các mô hình cũng như lượng dữ liệu mà chúng được huấn luyện trước. + +
+Number of parameters of recent Transformers models +
+ +Tiếc thay, việc huấn luyện một mô hình, đặc biệt là một mô hình lớn, đòi hỏi một lượng lớn dữ liệu. Điều này trở nên rất tốn kém về mặt thời gian và tài nguyên tính toán. Nó thậm chí còn chuyển thành tác động môi trường, như có thể thấy trong biểu đồ sau. + +
+The carbon footprint of a large language model. + +
+ + + +Và điều này cho thấy một dự án cho một mô hình (rất lớn) được dẫn dắt bởi một nhóm có ý thức cố gắng giảm tác động môi trường của quá trình huấn luyện trước. Dấu chân của việc chạy nhiều thử nghiệm để có được siêu thông số tốt nhất sẽ còn cao hơn. + +Hãy tưởng tượng nếu mỗi lần một nhóm nghiên cứu, một tổ chức sinh viên hoặc một công ty muốn huấn luyện một mô hình, họ sẽ thực hiện như vậy từ đầu. Điều này sẽ dẫn đến chi phí khổng lồ, không cần thiết! + +Đây là lý do tại sao việc chia sẻ các mô hình ngôn ngữ là điều tối quan trọng: chia sẻ các trọng số đã được huấn luyện và xây dựng trên các trọng số đã được huấn luyện giúp giảm chi phí tính toán tổng thể và lượng khí thải carbon tới cộng đồng. + +## Học chuyển giao + + + +_Huấn luyện trước_ là hành động huấn luyện một mô hình từ đầu: các trọng số được khởi tạo ngẫu nhiên và quá trình huấn luyện bắt đầu mà không cần biết trước bất kỳ điều gì. + +
+The pretraining of a language model is costly in both time and money. + +
+ +Việc huấn luyện trước này thường được thực hiện trên một lượng dữ liệu rất lớn. Do đó, nó yêu cầu một kho dữ liệu rất lớn và quá trình huấn luyện có thể mất đến vài tuần. + +Mặt khác, _tinh chỉnh_ là quá trình huấn luyện được thực hiện **sau khi** một mô hình đã được huấn luyện trước. Để thực hiện việc tinh chỉnh, trước tiên bạn cần có một mô hình ngôn ngữ đã huấn luyện trước, sau đó thực hiện huấn luyện bổ sung với một tập dữ liệu cụ thể cho tác vụ của bạn. Khoan - tại sao không đơn giản là huấn luyện trực tiếp cho tác vụ cuối cùng? Có một vài lý do như sau: + +- Mô hình được huấn luyện trước đã được huấn luyện trên một bộ dữ liệu có một số điểm tương đồng với bộ dữ liệu tinh chỉnh. Do đó, quá trình tinh chỉnh có thể tận dụng kiến thức có được bởi mô hình ban đầu trong quá trình huấn luyện trước (ví dụ: với các vấn đề NLP, mô hình được huấn luyện trước sẽ có một số hiểu biết thống kê về ngôn ngữ bạn đang sử dụng cho tác vụ của mình). +- Vì mô hình được huấn luyện trước đã được đào tạo trên nhiều dữ liệu, nên việc tinh chỉnh yêu cầu ít dữ liệu hơn để có được kết quả tốt. +- Với lý do tương tự, lượng thời gian và nguồn lực cần thiết để đạt được kết quả tốt thấp hơn nhiều. + +Ví dụ: người ta có thể tận dụng một mô hình huấn luyện trước được huấn luyện trên ngôn ngữ tiếng Anh và sau đó tinh chỉnh nó trên kho ngữ liệu arXiv, trả về một mô hình dựa trên khoa học/nghiên cứu. Việc tinh chỉnh sẽ chỉ yêu cầu một lượng dữ liệu hạn chế: kiến thức mà mô hình được huấn luyện trước được "chuyển giao", do đó ta có thuật ngữ _học chuyển giao_. + +
+The fine-tuning of a language model is cheaper than pretraining in both time and money. + +
+ +Do đó, việc tinh chỉnh một mô hình có chi phí thời gian, dữ liệu, tài chính và môi trường thấp hơn. Việc lặp lại các bước tinh chỉnh khác nhau cũng nhanh hơn và dễ dàng hơn, vì quá trình huấn luyện ít bị ràng buộc hơn so với huấn luyện trước từ đầu. + +Quá trình này cũng sẽ đạt được kết quả tốt hơn so với huấn luyện từ đầu (trừ khi bạn có nhiều dữ liệu), đó là lý do tại sao bạn nên luôn cố gắng tận dụng một mô hình được huấn luyện trước - một mô hình càng gần với tác vụ bạn có trong tay càng tốt - và điều chỉnh nó. + +## Kiến trúc tổng quan + +Trong phần này, chúng ta sẽ xem xét kiến trúc chung của mô hình Transformer. Đừng lo lắng nếu bạn không hiểu một số khái niệm; có các phần sau bao gồm chi tiết từng thành phần. + + + +## Giới thiệu + +Về cơ bản, mô hình bao gồm hai khối: + +- **Bộ mã hóa (bên trái)**: Bộ mã hóa nhận đầu vào và xây dựng biểu diễn (các đặc trưng của nó). Điều này có nghĩa là mô hình được tối ưu hóa để có được sự hiểu biết từ đầu vào. +- **Bộ giải mã (bên phải)**: Bộ giải mã sử dụng biểu diễn (đặc trưng) của bộ mã hóa cùng với các đầu vào khác để tạo chuỗi đích. Điều này có nghĩa là mô hình được tối ưu hóa để tạo ra kết quả đầu ra. + +
+Architecture of a Transformers models + +
+ +Mỗi phần có thể được sử dụng độc lập, tùy thuộc vào tác vụ: + +- **Các mô hình chỉ dùng bộ mã hóa**: Phù hợp với các tác vụ yêu cầu hiểu rõ về đầu vào, chẳng hạn như phân loại câu và nhận dạng thực thể được đặt tên. +- **Các mô hình chỉ dùng bộ giải mã**: Tốt cho các tác vụ tổng hợp như tạo văn bản. +- **Các mô hình bộ mã hóa-giải mã** hoặc **mô hình chuỗi-sang-chuỗi**: Tốt cho các tác vụ tổng hợp yêu cầu đầu vào, chẳng hạn như dịch máy hoặc tóm tắt. + +Chúng ta sẽ đi sâu vào các kiến trúc đó một cách độc lập trong các phần sau. + +## Các lớp Attention + +Một tính năng chính của các mô hình Transformer là chúng được xây dựng với các lớp đặc biệt được gọi là _Attention_. Trên thực tế, tiêu đề của bài báo giới thiệu kiến trúc Transformer là ["Attention is all you need"](https://arxiv.org/abs/1706.03762) hay "Sự chú ý là tất cả những gì bạn cần"! Chúng ta sẽ khám phá chi tiết về các lớp Attention ở phần sau của khóa học; hiện tại, tất cả những gì bạn cần biết là lớp này sẽ yêu cầu mô hình chú ý cụ thể đến các từ nhất định trong câu bạn đã chuyển nó (và ít nhiều bỏ qua những từ khác) khi xử lý biểu diễn của từng từ. + +Để đặt điều này vào ngữ cảnh, cùng xem tác vụ dịch văn bản từ tiếng Anh sang tiếng Pháp. Với đầu vào là "You like this course" ("Bạn thích khóa học này"), một mô hình dịch cũng sẽ cần phải chú ý vào từ liền kề "You" để có được bản dịch thích hợp cho từ "like", bởi vì trong tiếng Pháp, động từ "like" được chia khác nhau tùy thuộc vào chủ ngữ. Tuy nhiên, phần còn lại của câu không hữu ích cho việc dịch từ đó. Tương tự như vậy, khi dịch "this", mô hình cũng sẽ cần chú ý đến từ "course", bởi vì "this" dịch khác nhau tùy thuộc vào việc danh từ liên quan là giống đực hay giống cái. Một lần nữa, các từ khác trong câu sẽ không thành vấn đề đối với bản dịch của "this". Với các câu phức tạp hơn (và các quy tắc ngữ pháp phức tạp hơn), mô hình sẽ cần đặc biệt chú ý đến các từ có thể xuất hiện ở xa hơn trong câu để dịch đúng từng từ. + +Khái niệm tương tự cũng áp dụng cho bất kỳ tác vụ nào liên quan đến ngôn ngữ tự nhiên: một từ tự nó đã có nghĩa, nhưng nghĩa đó bị ảnh hưởng sâu sắc bởi ngữ cảnh, có thể là bất kỳ từ nào khác (hoặc các từ) trước hoặc sau từ được học. + +Giờ bạn đã nắm được ý tưởng về tất cả các lớp Attention, chúng ta hãy xem xét kỹ hơn về kiến trúc Transformer. + +## Kiến trúc gốc + +Kiến trúc Transformer ban đầu được thiết kế phục vụ cho dịch máy. Trong quá trình huấn luyện, bộ mã hóa nhận đầu vào (câu) bằng một ngôn ngữ nhất định, trong khi bộ giải mã nhận các câu tương tự bằng ngôn ngữ đích mong muốn. Trong bộ mã hóa, các lớp Attention có thể sử dụng tất cả các từ trong một câu (vì, như chúng ta vừa thấy, bản dịch của một từ nhất định có thể phụ thuộc vào những gì đứng sau cũng như trước nó trong câu). Tuy nhiên, bộ giải mã hoạt động tuần tự và chỉ có thể chú ý đến các từ trong câu mà nó đã được dịch (vì vậy, chỉ những từ trước từ hiện đang được tạo). Ví dụ: khi chúng ta dự đoán ba từ đầu tiên của mục tiêu đã dịch, chúng ta đưa chúng cho bộ giải mã, sau đó sử dụng tất cả các đầu vào của bộ mã hóa để cố gắng dự đoán từ thứ tư. + +Để tăng tốc độ mọi thứ trong quá trình huấn luyện (khi mô hình có quyền truy cập vào các câu đích), bộ giải mã được cung cấp toàn bộ nhãn, nhưng nó không được phép sử dụng các từ trong tương lai (nếu nó có quyền truy cập vào từ ở vị trí 2 khi cố gắng dự đoán từ ở vị trí 2, vấn đề sẽ không khó lắm!). Ví dụ: khi cố gắng dự đoán từ thứ tư, lớp Attention sẽ chỉ có quyền truy cập vào các từ ở vị trí 1 đến 3. + +Kiến trúc Transformer gốc trông như sau, với bộ mã hóa ở bên trái và bộ giải mã ở bên phải: + +
+Architecture of a Transformers models + +
+ +Lưu ý rằng lớp Attention đầu tiên trong bộ giải mã chú ý đến tất cả đầu vào (quá khứ) của bộ giải mã, nhưng lớp Attention thứ hai sử dụng đầu ra của bộ mã hóa. Do đó, nó có thể truy cập toàn bộ câu đầu vào để dự đoán tốt nhất từ hiện tại. Điều này rất hữu ích vì các ngôn ngữ khác nhau có thể có các quy tắc ngữ pháp đặt các từ theo thứ tự khác nhau hoặc một số ngữ cảnh được cung cấp sau trong câu có thể hữu ích để xác định bản dịch tốt nhất của một từ nhất định. + +_Attention mask_ cũng có thể được sử dụng trong bộ mã hóa/ giải mã để ngăn mô hình chú ý đến một số từ đặc biệt - ví dụ: từ đệm đặc biệt được sử dụng để làm cho tất cả các đầu vào có cùng độ dài khi ghép các câu lại với nhau. + +## Các kiến trúc và checkpoints + +Khi chúng ta đi sâu và các mô hình Transformer trong hoá học này, bạn sẽ bắt gặp những cụm từ như _kiến trúc_, _checkpoint_ cũng như _mô hình_. Các thuật ngữ này mang ý nghĩa hơi khác nhau: + +- **Kiến trúc**: Đây là khung của mô hình -- định nghĩa từng lớp và từng hoạt động xảy ra trong mô hình. +- **Checkpoints**: Đây là những trọng số sẽ được sử dụng trong một kiến trúc nhất định. +- **Mô hình**: Đây là một thuật ngữ bao trùm và không thể giải thích chính xác như "kiến trúc" hay "checkpoint": nó có thể mang cả hai nghĩa. Khoá học này sẽ chỉ ra khi nào là _kiến trúc_ và _checkpoint_ để giảm bớt sự mơ hồ khi cần thiết. + +Ví dụ, BERT là 1 kiến trúc trong khi `bert-base-cased`,tập hợp các trọng số được huấn luyện bởi đội ngũ Google cho phiên bản đầu tiên của BERT, là 1 chekcpoint. Tuy nhiên, ta có thể nói "mô hình BERT" và "mô hình `bert-base-cased`". diff --git a/chapters/vi/chapter1/5.mdx b/chapters/vi/chapter1/5.mdx new file mode 100644 index 000000000..f2cb94448 --- /dev/null +++ b/chapters/vi/chapter1/5.mdx @@ -0,0 +1,17 @@ +# Các mô hình mã hóa + + + +Các mô hình mã hóa chỉ sử dụng phần mã hóa của mô hình Transformer. Ở mỗi bước, các lớp attention có thể truy cập tất cả các từ trong câu ban đầu. Những mô hình này thường có đặc trưng là chú ý "hai chiều" và thường được gọi là mô hình _auto-encoding_ hay _mã hóa tự động_. + +Việc huấn luyện trước các mô hình này thường xoay quanh việc phá vỡ một câu đã cho bằng cách nào đó (ví dụ: bằng cách che các từ ngẫu nhiên trong đó) và yêu cầu mô hình tìm hoặc tái tạo lại câu ban đầu. + +Mô hình mã hóa phù hợp nhất cho các tác vụ yêu cầu hiểu toàn bộ câu, chẳng hạn như phân loại câu, nhận dạng thực thể được đặt tên (và nói chung là phân loại từ) và trả lời câu hỏi chiết xuất. + +Một số mô hình tiêu biểu của nhóm này bao gồm: + +- [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) diff --git a/chapters/vi/chapter1/6.mdx b/chapters/vi/chapter1/6.mdx new file mode 100644 index 000000000..fa577ea9e --- /dev/null +++ b/chapters/vi/chapter1/6.mdx @@ -0,0 +1,16 @@ +# CCác mô hình giải mã + + + +Mô hình giải mã chỉ sử dụng phần giải mã của mô hình Transformer. Ở mỗi bước, đối với một từ nhất định, các lớp attention chỉ có thể truy cập các từ được đặt trước nó trong câu. Những mô hình này thường được gọi là mô hình _auto-regressive_ hay _hồi quy tự động_. + +Việc huấn luyện trước các mô hình giải mã thường xoay quanh việc dự đoán từ tiếp theo trong câu. + +Các mô hình này phù hợp nhất cho các tác vụ liên quan đến việc tạo văn bản. + +Một số mô hình tiêu biểu của nhóm này bao gồm: + +- [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) +- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) +- [GPT-2](https://huggingface.co/transformers/model_doc/gpt2.html) +- [Transformer XL](https://huggingface.co/transformers/model_doc/transfo-xl.html) diff --git a/chapters/vi/chapter1/7.mdx b/chapters/vi/chapter1/7.mdx new file mode 100644 index 000000000..d2c894cfb --- /dev/null +++ b/chapters/vi/chapter1/7.mdx @@ -0,0 +1,16 @@ +# Các mô hình mã hoá-giải mã + + + +Các mô hình mã hóa-giải mã (còn được gọi là _mô hình chuỗi-sang-chuỗi_) sử dụng cả hai phần của kiến trúc Transformer. Ở mỗi bước, các lớp attention của phần mã hóa có thể truy cập tất cả các từ trong câu ban đầu, trong khi các lớp attention của phần giải mã chỉ có thể truy cập các từ được đặt trước một từ nhất định trong đầu vào. + +Việc huấn luyện trước các mô hình này có thể được thực hiện bằng cách sử dụng các hàm mục tiêu của mô hình mã hóa hoặc giải mã, nhưng thường liên quan đến một thứ phức tạp hơn một chút. Ví dụ: [T5](https://huggingface.co/t5-base) được huấn luyện trước bằng cách thay thế các khoảng văn bản ngẫu nhiên (có thể chứa một số từ) bằng cách che lại bằng một từ đặc biệt và mục tiêu sau đó là dự đoán phần bị che lại bởi một từ đặc biệt đó. + +Mô hình chuỗi-sang-chuỗi phù hợp nhất cho các tác vụ xoay quanh việc tạo ra các câu mới tùy thuộc vào đầu vào nhất định, chẳng hạn như tóm tắt, dịch hoặc hỏi đáp chung. + +Một số mô hình tiêu biểu của nhóm này bao gồm: + +- [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) diff --git a/chapters/vi/chapter1/8.mdx b/chapters/vi/chapter1/8.mdx new file mode 100644 index 000000000..3ce04e0fd --- /dev/null +++ b/chapters/vi/chapter1/8.mdx @@ -0,0 +1,41 @@ +# Thiên kiến và hạn chế + + + +Nếu mục đích của bạn là sử dụng một mô hình được huấn luyện trước hoặc một phiên bản được tinh chỉnh trong quá trình sản xuất, xin lưu ý rằng mặc dù những mô hình này là những công cụ mạnh mẽ nhưng chúng cũng có những hạn chế. Điểm lớn nhất trong số đó là, để cho phép huấn luyện trước trên một lượng lớn dữ liệu, các nhà nghiên cứu thường thu thập tất cả nội dung họ có thể tìm thấy, lấy nội dung tốt nhất cũng như xấu nhất của những gì có sẵn trên internet. + +Để đưa ra một minh họa nhanh, hãy quay lại ví dụ về pipeline `fill-mask` với mô hình BERT: + +```python +from transformers import pipeline + +unmasker = pipeline("fill-mask", model="bert-base-uncased") +result = unmasker("This man works as a [MASK].") +print([r["token_str"] for r in result]) + +result = unmasker("This woman works as a [MASK].") +print([r["token_str"] for r in result]) +``` + +```python out +['lawyer', 'carpenter', 'doctor', 'waiter', 'mechanic'] +['nurse', 'waitress', 'teacher', 'maid', 'prostitute'] +``` + +Khi được yêu cầu điền từ còn thiếu trong hai câu này, mô hình chỉ đưa ra một câu trả lời không phân biệt giới tính (waiter/waitress hay bồi bàn nam/bồi bàn nữ). Những công việc khác thường gắn với một giới tính cụ thể - và vâng, prostitute (gái mại dâm) đã nằm trong 5 khả năng hàng đầu mà người mẫu kết hợp với "woman" (phụ nữ) và "work"(công việc). Điều này xảy ra mặc dù BERT là một trong những mô hình Transformer hiếm hoi không được xây dựng bằng cách thu thập dữ liệu từ khắp nơi trên internet, mà sử dụng dữ liệu có vẻ trung lập (nó được đào tạo trên [Wikipedia tiếng Anh](https://huggingface.co/datasets/wikipedia) và bộ dữ liệu [BookCorpus](https://huggingface.co/datasets/bookcorpus). + +Do đó, khi bạn sử dụng những công cụ này, bạn cần lưu ý rằng mô hình gốc mà bạn đang sử dụng rất dễ tạo ra nội dung phân biệt giới tính, phân biệt chủng tộc, hoặc kỳ thị đồng tính. Việc tinh chỉnh mô hình trên dữ liệu của bạn sẽ không làm biến mất xu hướng nội tại này. diff --git a/chapters/vi/chapter1/9.mdx b/chapters/vi/chapter1/9.mdx new file mode 100644 index 000000000..fffa994b6 --- /dev/null +++ b/chapters/vi/chapter1/9.mdx @@ -0,0 +1,11 @@ +# Tổng kết + +Trong chương này, bạn đã biết cách tiếp cận các tác vụ NLP khác nhau bằng cách sử dụng hàm `pipeline()` cấp cao từ 🤗 Transformers. Bạn cũng đã biết cách tìm kiếm và sử dụng các mô hình trong Hub, cũng như cách sử dụng Inference API để kiểm tra các mô hình trực tiếp trong trình duyệt của mình. + +Chúng ta đã thảo luận về cách các mô hình Transformer hoạt động ở cấp độ cao và nói về tầm quan trọng của việc học chuyển giao và tinh chỉnh. Một khía cạnh quan trọng, đó là bạn có thể sử dụng kiến trúc đầy đủ hoặc chỉ phần mã hóa hoặc giải mã, tùy thuộc vào loại tác vụ bạn muốn giải quyết. Bảng dưới đây tóm tắt khía cạnh này: + +| Mô hình | Ví dụ | Tác vụ | +| -------------- | ------------------------------------------ | ------------------------------------------------------------ | +| Mã hoá | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa | Phân loại câu, Nhận dạng thực thể có tên, hỏi đáp chích xuất | +| Giải mã | CTRL, GPT, GPT-2, Transformer XL | Tạo văn bản | +| Mã hoá-giải mã | BART, T5, Marian, mBART | Tóm tắt, dịch máy, trả lời câu hỏi tổng hợp | diff --git a/chapters/vi/chapter2/1.mdx b/chapters/vi/chapter2/1.mdx new file mode 100644 index 000000000..e3fd44f8b --- /dev/null +++ b/chapters/vi/chapter2/1.mdx @@ -0,0 +1,19 @@ +# Giới thiệu + +Như bạn đã thấy trong [Chương 1](/course/chapter1), các mô hình Transformer thường rất lớn. Với hàng triệu đến hàng chục *tỷ* thông số, việc huấn luyện và triển khai các mô hình này là một công việc phức tạp. Hơn nữa, với việc các mô hình mới được phát hành gần như hàng ngày và mỗi mô hình có cách triển khai riêng, việc thử tất cả chúng không phải là nhiệm vụ dễ dàng. + +Thư viện 🤗 Transformers được tạo ra để giải quyết vấn đề này. Mục tiêu của nó là cung cấp một API duy nhất mà qua đó bất kỳ mô hình Transformer nào cũng có thể được tải, huấn luyện, và lưu. Các tính năng chính của thư viện gồm: + +- **Tính dễ sử dụng**: Việc tải xuống, tải và sử dụng mô hình NLP tối tân để luận suy có thể được thực hiện chỉ trong hai dòng mã. +- **Tính linh hoạt**: Về cốt lõi, tất cả các mô hình đều là các lớp PyTorch `nn.Module` hoặc TensorFlow` tf.keras.Model` đơn giản và có thể được xử lý giống như bất kỳ mô hình nào khác trong khuôn khổ học máy (ML) tương ứng của chúng. +- **Tính đơn giản**: Hầu như không có bất kỳ sự trừu tượng nào được thực hiện trên toàn bộ thư viện. "All in one file" ("Tất cả trong một tệp") là khái niệm cốt lõi: bước lan truyền thẳng của một mô hình được xác định hoàn toàn trong một tệp duy nhất giúp bản thân đoạn mã dễ hiểu và có thể hack được. + +Tính năng cuối cùng này làm cho 🤗 Transformers khá khác biệt so với các thư viện ML khác. Các mô hình không được xây dựng trên các mô-đun được chia sẻ trên các tệp; thay vào đó, mỗi mô hình có các lớp riêng của nó. Ngoài việc làm cho các mô hình dễ tiếp cận và dễ hiểu hơn, điều này cho phép bạn dễ dàng thử nghiệm trên một mô hình mà không ảnh hưởng đến các mô hình khác. + +Chương này sẽ bắt đầu với một ví dụ từ đầu đến cuối, trong đó chúng ta sử dụng một mô hình và một tokenizer cùng nhau để sao chép hàm `pipeline()` được giới thiệu trong [Chapter 1](/course/chapter1). Tiếp theo, chúng ta sẽ thảo luận về API mô hình: chúng ta sẽ đi sâu vào các lớp cấu hình và mô hình, đồng thời chỉ cho bạn cách tải một mô hình và cách nó xử lý các đầu vào dạng số để đưa ra các dự đoán đầu ra. + +Sau đó, chúng ta sẽ xem xét API tokenizer, một thành phần chính khác của hàm `pipeline()`. Tokenizers thực hiện các bước xử lý đầu tiên và cuối cùng, xử lý việc chuyển đổi từ văn bản đầu vào thành dạng số cho mạng nơ-ron và chuyển đổi trở lại văn bản khi cần. Cuối cùng, chúng tôi sẽ chỉ cho bạn cách xử lý việc gửi nhiều câu vào một mô hình trong một batch (lô) đã chuẩn bị, sau đó tóm tắt tất cả bằng cách xem xét kỹ hơn hàm `tokenizer()` ở bậc cao. + + +⚠️ Để có thể tận dụng tất cả các tính năng có sẵn với Model Hub và 🤗 Transformers, chúng tôi khuyến khích bạn tạo tài khoản . + diff --git a/chapters/vi/chapter2/2.mdx b/chapters/vi/chapter2/2.mdx new file mode 100644 index 000000000..b27e6365f --- /dev/null +++ b/chapters/vi/chapter2/2.mdx @@ -0,0 +1,353 @@ + + +# Đằng sau pipeline + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + +Đây là phần đầu tiên có nội dung hơi khác một chút tùy thuộc vào việc bạn sử dụng PyTorch hay TensorFlow. Chuyển đổi công tắc trên đầu tiêu đề để chọn nền tảng bạn thích! + + +{#if fw === 'pt'} + +{:else} + +{/if} + +Hãy bắt đầu với một ví dụ hoàn chỉnh, cùng xem những gì xảy ra phía sau khi chúng tôi thực thi đoạn mã sau trong [Chương 1](/course/chapter1): + +```python +from transformers import pipeline + +classifier = pipeline("sentiment-analysis") +classifier( + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] +) +``` + +và thu được: + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}, + {'label': 'NEGATIVE', 'score': 0.9994558095932007}] +``` + +Như chúng ta đã thấy trong [Chương 1](/course/chapter1), pipeline này nhóm ba bước lại với nhau: tiền xử lý, đưa các đầu vào qua mô hình và hậu xử lý: + +
+The full NLP pipeline: tokenization of text, conversion to IDs, and inference through the Transformer model and the model head. + +
+ +Hãy cùng đi qua từng phần này. + +## Tiền xử lý với một tokenizer + +Giống như các mạng nơ-ron khác, các mô hình Transformers không thể xử lý trực tiếp văn bản thô, vì vậy bước đầu tiên trong quy trình của chúng ta là chuyển các đầu vào văn bản thành dạng số mà mô hình có thể hiểu được. Để làm điều này, chúng ta sử dụng *tokenizer*, hàm sẽ chịu trách nhiệm về: + +- Tách đầu vào thành các từ, từ phụ, hoặc ký hiệu (như dấu chấm câu) được gọi là *tokens* +- Ánh xạ mỗi token thành một số nguyên +- Thêm đầu vào bổ sung có thể hữu ích cho mô hình + +Tất cả quá trình tiền xử lý này cần được thực hiện giống hệt như khi mô hình được huấn luyện trước, vì vậy trước tiên chúng ta cần tải xuống thông tin đó từ [Model Hub](https://huggingface.co/models). Để làm điều này, chúng tôi sử dụng lớp `AutoTokenizer` và phương thức `from_pretrained()` của nó. Sử dụng tên checkpoint mô hình của chúng ta, nó sẽ tự động tìm nạp dữ liệu được liên kết với tokenizer của mô hình và lưu vào bộ nhớ cache (vì vậy nó chỉ được tải xuống lần đầu tiên bạn chạy mã bên dưới). + +Vì checkpoint mặc định của `sentiment-analysis` là `distilbert-base-unsased-finetuned-sst-2-english` (bạn có thể xem thẻ mô hình của nó [tại đây](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english)), chúng ta chạy như sau: + +```python +from transformers import AutoTokenizer + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` + +Khi có tokenizer rồi, chúng ta có thể truyền trực tiếp các câu của mình vào bên trong và nhận lại một từ điển đã sẵn sàng để cung cấp cho mô hình! Việc duy nhất cần làm là chuyển đổi danh sách các ID đầu vào thành các tensor. + +Bạn có thể sử dụng 🤗 Transformers mà không phải lo lắng về khung ML nào được sử dụng phía dưới; nó có thể là PyTorch hoặc TensorFlow hoặc Flax đối với một số mô hình. Tuy nhiên, các mô hình Transformer chỉ chấp nhận *tensor* làm đầu vào. Nếu đây là lần đầu tiên bạn nghe về tensor, bạn có thể nghĩ chúng như là mảng NumPy. Mảng NumPy có thể là giá trị vô hướng (0D), vectơ (1D), ma trận (2D) hoặc có nhiều kích thước hơn. Nó thực sự là một tensor; Các tensor của các khung ML khác hoạt động tương tự và thường khởi tạo đơn giản như các mảng NumPy. + +Để chỉ định loại tensors mà chúng ta muốn trả về (PyTorch, TensorFlow hoặc thuần NumPy), ta sử dụng tham số `return_tensors`: + +{#if fw === 'pt'} +```python +raw_inputs = [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", +] +inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="pt") +print(inputs) +``` +{:else} +```python +raw_inputs = [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", +] +inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="tf") +print(inputs) +``` +{/if} + +Đừng lo lắng về padding (đệm) và truncation (cắt bớt) vội; chúng tôi sẽ giải thích những điều đó sau. Những điều chính cần nhớ ở đây là bạn có thể chuyển một câu hoặc một danh sách các câu, cũng như chỉ định loại tensors bạn muốn lấy lại (nếu không có loại nào được truyền vào, mặc định bạn sẽ nhận được kết quả trả về là một danh sách). + +{#if fw === 'pt'} + +Đây là kết quả tương ứng tensor PyTorch: + +```python out +{ + 'input_ids': tensor([ + [ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012, 102], + [ 101, 1045, 5223, 2023, 2061, 2172, 999, 102, 0, 0, 0, 0, 0, 0, 0, 0] + ]), + '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] + ]) +} +``` +{:else} + +Đây là kết quả tương ứng tensor Tensorflow: + +```python out +{ + 'input_ids': , + 'attention_mask': +} +``` +{/if} + +Bản thân kết quả đầu ra là một từ điển có chứa hai khóa, `input_ids` và `attention_mask`. `input_ids` chứa hai hàng số nguyên (một cho mỗi câu) là số nhận dạng duy nhất của token trong mỗi câu. Chúng tôi sẽ giải thích `attention_mask` là gì ở phần sau của chương này. + +## Đi qua mô hình + +{#if fw === 'pt'} +Chúng ta có thể tải xuống mô hình được huấn luyện trước của mình giống như cách đã làm với tokenizer. 🤗 Transformers cung cấp một lớp `AutoModel` cũng có phương thức `from_pretrained()`: + +```python +from transformers import AutoModel + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = AutoModel.from_pretrained(checkpoint) +``` +{:else} +Chúng ta có thể tải xuống mô hình được huấn luyện trước của mình giống như cách đã làm với tokenizer. 🤗 Transformers cung cấp một lớp `TFAutoModel` cũng có phương thức `from_pretrained()`: + +```python +from transformers import TFAutoModel + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = TFAutoModel.from_pretrained(checkpoint) +``` +{/if} + +Trong đoạn mã này, chúng ta đã tải xuống cùng một checkpoint đã sử dụng trong pipeline của mình trước đây (nó được lưu vào bộ nhớ đệm rồi) và khởi tạo một mô hình với nó. + +Kiến trúc này chỉ chứa mô-đun Transformer cơ sở: với một số đầu vào, nó xuất ra cái mà chúng ta sẽ gọi là *hidden states* (*trạng thái ẩn*), còn được gọi là *đặc trưng*. Đối với mỗi đầu vào mô hình, chúng ta sẽ truy xuất một vectơ đa chiều đại diện cho **sự hiểu theo ngữ cảnh của đầu vào đó bằng mô hình Transformer**. + +Nếu điều này không hợp lý, đừng lo lắng về nó. Chúng tôi sẽ giải thích tất cả sau. + +Mặc dù những trạng thái ẩn này có thể tự hữu ích, nhưng chúng thường là đầu vào cho một phần khác của mô hình, được gọi là *head* (*đầu*). Trong [Chapter 1](/course/chapter1), các tác vụ khác nhau có thể được thực hiện với cùng một kiến trúc, nhưng mỗi tác vụ này sẽ có một phần đầu khác nhau được liên kết với nó. + +### Một vectơ đa chiều + +Đầu ra vectơ của mô-đun Transformer thường lớn với ba chiều: + +- **Kích thước batch (lô)**: Số chuỗi được xử lý tại một thời điểm (trong ví dụ của chúng tôi là 2). +- **Độ dài chuỗi**: Độ dài biểu diễn số của chuỗi (trong ví dụ của chúng tôi là 16). +- **Kích thước ẩn**: Kích thước vectơ của mỗi đầu vào mô hình. + +Nó được cho là "có số chiều cao" vì giá trị cuối cùng. Kích thước ẩn có thể rất lớn (768 là giá trị phổ biến cho các mô hình nhỏ hơn và trong các mô hình lớn hơn, con số này có thể đạt tới 3072 hoặc hơn). + +Có thể thấy điều này nếu chúng ta cung cấp các đầu vào đã xử lý trước cho mô hình của mình: + +{#if fw === 'pt'} +```python +outputs = model(**inputs) +print(outputs.last_hidden_state.shape) +``` + +```python out +torch.Size([2, 16, 768]) +``` +{:else} +```py +outputs = model(inputs) +print(outputs.last_hidden_state.shape) +``` + +```python out +(2, 16, 768) +``` +{/if} + +Lưu ý rằng đầu ra của các mô hình 🤗 Transformers hoạt động giống như các `namedtuple` hoặc từ điển. Bạn có thể truy cập các phần tử theo thuộc tính (như chúng ta đã làm) hoặc theo khóa (`outputs["last_hidden_state"]`), hoặc thậm chí theo chỉ mục nếu bạn biết chính xác nơi bạn đang tìm kiếm (`outputs[0]`). + +### Đầu mô hình: Hợp lý tời từng con số + +Các đầu mô hình lấy vector đa chiều của các trạng thái ẩn làm đầu vào và chiếu chúng lên một chiều khác. Chúng thường bao gồm một hoặc một vài lớp tuyến tính: + +
+A Transformer network alongside its head. + +
+ +Đầu ra của mô hình Transformer được gửi trực tiếp đến đầu mô hình để được xử lý. + +Trong biểu đồ này, mô hình được biểu diễn bằng lớp nhúng của nó và các lớp tiếp theo. Lớp nhúng chuyển đổi mỗi ID trong đầu vào được mã hóa thành một vectơ đại diện cho token được liên kết. Các lớp tiếp theo thao tác các vectơ đó bằng cách sử dụng cơ chế chú ý để tạo ra biểu diễn cuối cùng của các câu. + +Có nhiều kiến trúc khác nhau có sẵn trong 🤗 Transformers, với mỗi kiến trúc được thiết kế xoay quanh một tác vụ cụ thể. Đây là danh sách không đầy đủ: + +- `*Model` (truy xuất các trạng thái ẩn) +- `*ForCausalLM` +- `*ForMaskedLM` +- `*ForMultipleChoice` +- `*ForQuestionAnswering` +- `*ForSequenceClassification` +- `*ForTokenClassification` +- and others 🤗 + +{#if fw === 'pt'} +Với ví dụ của mình, chúng ta sẽ cần một mô hình có đầu phân loại tuần tự (để có thể phân loại các câu là khẳng định hoặc phủ định). Vì vậy, ta sẽ không sử dụng lớp `AutoModel` mà là `AutoModelForSequenceClassification`: + +```python +from transformers import AutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +outputs = model(**inputs) +``` +{:else} +Với ví dụ của mình, chúng ta sẽ cần một mô hình có đầu phân loại tuần tự (để có thể phân loại các câu là khẳng định hoặc phủ định). Vì vậy, ta sẽ không sử dụng lớp `TFAutoModel` mà là `TFAutoModelForSequenceClassification`: + +```python +from transformers import TFAutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +outputs = model(inputs) +``` +{/if} + +Giờ thì nếu chúng ta nhìn vào hình dạng các đầu vào của mình, kích thước sẽ thấp hơn nhiều: đầu mô hình lấy các vectơ đa chiều mà chúng ta đã thấy trước đây và xuất ra các vectơ có chứa hai giá trị (mỗi giá trị tương ứng một nhãn): + +```python +print(outputs.logits.shape) +``` + +{#if fw === 'pt'} +```python out +torch.Size([2, 2]) +``` +{:else} +```python out +(2, 2) +``` +{/if} + +Vì chúng ta chỉ có hai câu và hai nhãn, kết quả nhận được từ mô hình của chúng ta là dạng 2 x 2. + +## Hậu xử lý đầu ra + +Các giá trị chúng ta nhận được dưới dạng đầu ra từ mô hình không nhất thiết phải tự có nghĩa. Hãy cùng xem: + +```python +print(outputs.logits) +``` + +{#if fw === 'pt'} +```python out +tensor([[-1.5607, 1.6123], + [ 4.1692, -3.3464]], grad_fn=) +``` +{:else} +```python out + +``` +{/if} + +Mô hình đã dự đoán `[-1.5607, 1.6123]` cho câu đầu tiên và `[4.1692, -3.3464]` cho câu thứ hai. Đó không phải là xác suất mà là *logits*, điểm số thô, chưa chuẩn hóa được xuất ra bởi lớp cuối cùng của mô hình. Để được chuyển đổi thành xác suất, chúng cần phải trải qua lớp [SoftMax](https://en.wikipedia.org/wiki/Softmax_function) (tất cả các mô hình 🤗 Transformers đều xuất ra logits, vì hàm mất mát cho việc huấn luyện thường sẽ kết hợp hàm kích hoạt cuối cùng, chẳng hạn như SoftMax, với hàm mất mát thực tế, chẳng hạn như entropy chéo): + +{#if fw === 'pt'} +```py +import torch + +predictions = torch.nn.functional.softmax(outputs.logits, dim=-1) +print(predictions) +``` +{:else} +```py +import tensorflow as tf + +predictions = tf.math.softmax(outputs.logits, axis=-1) +print(predictions) +``` +{/if} + +{#if fw === 'pt'} +```python out +tensor([[4.0195e-02, 9.5980e-01], + [9.9946e-01, 5.4418e-04]], grad_fn=) +``` +{:else} +```python out +tf.Tensor( +[[4.01951671e-02 9.59804833e-01] + [9.9945587e-01 5.4418424e-04]], shape=(2, 2), dtype=float32) +``` +{/if} + +Bây giờ chúng ta có thể thấy rằng mô hình đã dự đoán `[0.0402, 0.9598]` cho câu đầu tiên và `[0.9995, 0.0005]` cho câu thứ hai. Đây là những điểm xác suất dễ nhận biết. + +Để lấy các nhãn tương ứng với từng vị trí, chúng ta có thể kiểm tra thuộc tính `id2label` của cấu hình mô hình (tìm hiểu thêm về điều này trong phần tiếp theo): + +```python +model.config.id2label +``` + +```python out +{0: 'NEGATIVE', 1: 'POSITIVE'} +``` + +Bây giờ chúng ta có thể kết luận rằng mô hình đã dự đoán như sau: + +- Câu đầu tiên: TIÊU CỰC: 0,0402, TÍCH CỰC: 0,9598 +- Câu thứ hai: TIÊU CỰC: 0,9995, TÍCH CỰC: 0,0005 + +Chúng tôi đã tái tạo thành công ba bước của quy trình: tiền xử lý bằng tokenizers, đưa đầu vào qua mô hình và hậu xử lý! Giờ thì chúng ta hãy dành một chút thời gian để đi sâu hơn vào từng bước đó. + + + +✏️ **Thử nghiệm thôi!** Chọn hai (hoặc nhiều) văn bản của riêng bạn và chạy chúng thông qua `sentiment-analysis`. Sau đó, tự mình lặp lại các bước bạn đã thấy ở đây và kiểm tra xem bạn có thu được kết quả tương tự không! + + diff --git a/chapters/vi/chapter2/3.mdx b/chapters/vi/chapter2/3.mdx new file mode 100644 index 000000000..4d7021969 --- /dev/null +++ b/chapters/vi/chapter2/3.mdx @@ -0,0 +1,265 @@ + + +# Các mô hình + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + + +{:else} + +{/if} + +{#if fw === 'pt'} +Trong phần này, chúng ta sẽ xem xét kỹ hơn về việc tạo và sử dụng một mô hình. Chúng tôi sẽ sử dụng `AutoModel`, rất tiện lợi khi bạn muốn khởi tạo bất kỳ mô hình nào từ một checkpoint. + +`AutoModel` và tất cả các lớp họ hàng của nó thực ra là các hàm đóng gói đơn giản trên nhiều loại mô hình có sẵn trong thư viện. Đó là một hàm đóng gói thông minh vì nó có thể tự động đoán kiến trúc mô hình thích hợp cho checkpoint của bạn và sau đó khởi tạo một mô hình với kiến trúc này. + +{:else} +Trong phần này, chúng ta sẽ xem xét kỹ hơn về việc tạo và sử dụng một mô hình. Chúng tôi sẽ sử dụng `TFAutoModel`, rất tiện lợi khi bạn muốn khởi tạo bất kỳ mô hình nào từ một checkpoint. + +`TFAutoModel` và tất cả các lớp họ hàng của nó thực ra là các hàm đóng gói đơn giản trên nhiều loại mô hình có sẵn trong thư viện. Đó là một hàm đóng gói thông minh vì nó có thể tự động đoán kiến trúc mô hình thích hợp cho checkpoint của bạn và sau đó khởi tạo một mô hình với kiến trúc này. + +{/if} + +Tuy nhiên, nếu bạn biết loại mô hình bạn muốn sử dụng, bạn có thể sử dụng trực tiếp lớp định nghĩa kiến trúc của nó. Chúng ta hãy xem cách này hoạt động với mô hình BERT. + +## Tạo ra một Transformer + +Điều đầu tiên ta cần làm để khởi tạo mô hình BERT là tải một cấu hình: + +{#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} + +Cấu hình chứa nhiều thuộc tính được sử dụng để xây dựng mô hình: + +```py +print(config) +``` + +```python out +BertConfig { + [...] + "hidden_size": 768, + "intermediate_size": 3072, + "max_position_embeddings": 512, + "num_attention_heads": 12, + "num_hidden_layers": 12, + [...] +} +``` + +Mặc dù bạn chưa thấy tất cả các thuộc tính này có tác dụng gì, nhưng bạn nên nhận ra một số trong số chúng: thuộc tính `hidden_size` xác định kích thước của vectơ `hidden_states` và `num_hidden_layers` xác định số lớp mà mô hình Transformer có. + +### Các phương pháp tải khác nhau + +Việc tạo mô hình từ cấu hình mặc định sẽ khởi tạo mô hình đó với các giá trị ngẫu nhiên: + +{#if fw === 'pt'} + +```py +from transformers import BertConfig, BertModel + +config = BertConfig() +model = BertModel(config) + +# Model is randomly initialized! +``` + +{:else} + +```py +from transformers import BertConfig, TFBertModel + +config = BertConfig() +model = TFBertModel(config) + +# Model is randomly initialized! +``` + +{/if} +Mô hình có thể được sử dụng ở trạng thái này, nhưng nó sẽ trả ra vô nghĩa; nó cần được huấn luyện trước. Chúng ta có thể huấn luyện mô hình từ đầu tác vụ này, nhưng như bạn đã thấy trong [Chương 1](/course/chapter1), điều này sẽ đòi hỏi một thời gian dài và nhiều dữ liệu, và nó sẽ tác động đáng kể tới môi trường. Để tránh nỗ lực không cần thiết và trùng lặp, khả năng chia sẻ và sử dụng lại các mô hình đã được huấn luyện trở nên bắt buộc. + +Việc tải một mô hình Transformer đã được huấn luyện trước rất đơn giản - chúng ta có thể thực hiện việc này bằng cách sử dụng phương thức `from_pretrained()`: + +{#if fw === 'pt'} + +```py +from transformers import BertModel + +model = BertModel.from_pretrained("bert-base-cased") +``` + +Như bạn đã thấy trước đó, chúng ta có thể thay thế `BertModel` bằng `AutoModel` tương đương. Chúng ta sẽ làm điều này từ bây giờ vì điều này tạo ra các đoạn mã checkpoint bất khả tri; nếu mã của bạn hoạt động cho một checkpoint, nó sẽ hoạt động liền mạch với một checkpoint khác. Điều này áp dụng ngay cả khi kiến trúc khác nhau, miễn là checkpoint đã được huấn luyện cho một tác vụ tương tự (ví dụ: một tác vụ phân tích cảm xúc). + +{:else} + +```py +from transformers import TFBertModel + +model = TFBertModel.from_pretrained("bert-base-cased") +``` + +Như bạn đã thấy trước đó, chúng ta có thể thay thế `BertModel` bằng `TFAutoModel` tương đương. Chúng ta sẽ làm điều này từ bây giờ vì điều này tạo ra các đoạn mã checkpoint bất khả tri; nếu mã của bạn hoạt động cho một checkpoint, nó sẽ hoạt động liền mạch với một checkpoint khác. Điều này áp dụng ngay cả khi kiến trúc khác nhau, miễn là checkpoint đã được huấn luyện cho một tác vụ tương tự (ví dụ: một tác vụ phân tích cảm xúc). + +{/if} + +Trong đoạn mã ở trên, ta không sử dụng `BertConfig` và thay vào đó, tải một mô hình được đào tạo trước thông qua mã định danh `bert-base-cased`. Đây là một checkpoint mô hình do chính các tác giả của BERT huấn luyện; bạn có thể tìm thêm thông tin chi tiết về nó trong [thẻ mô hình](https://huggingface.co/bert-base-cased). + +Mô hình này hiện đã được khởi tạo với tất cả các trọng số của checkpoint. Nó có thể được sử dụng trực tiếp để luận suy về các tác vụ mà nó đã được huấn luyện, và nó cũng có thể được tinh chỉnh trên một tác vụ mới. Bằng cách huấn luyện với trọng số đã được huấn luyện trước chứ không phải từ đầu, chúng ta có thể nhanh chóng đạt được kết quả tốt. + +Các trọng số đã được tải xuống và lưu vào bộ nhớ cache (vì vậy các lệnh gọi tới phương thức `from_pretrained()` trong tương lai sẽ không tải xuống lại chúng) trong thư mục bộ nhớ cache, mặc định là _~/.cache/huggingface/transformers_. Bạn có thể tùy chỉnh thư mục bộ nhớ cache của mình bằng cách đặt biến môi trường `HF_HOME`. + +Số định danh được sử dụng để tải mô hình có thể là số định danh của bất kỳ mô hình nào trên Model Hub, miễn là nó tương thích với kiến ​​trúc BERT. Toàn bộ danh sách các checkpoint BERT hiện có có thể được tìm thấy [tại đây](https://huggingface.co/models?filter=bert). + +### Phương pháp lưu trữ checkpoint + +Lưu một mô hình cũng dễ dàng như tải một mô hình - chúng ta sử dụng phương thức `save_pretrained()`, tương tự với phương thức `from_pretrained()`: + +```py +model.save_pretrained("directory_on_my_computer") +``` + +Thao tác này sẽ lưu hai tệp vào đĩa của bạn: + +{#if fw === 'pt'} + +``` +ls directory_on_my_computer + +config.json pytorch_model.bin +``` + +{:else} + +``` +ls directory_on_my_computer + +config.json tf_model.h5 +``` + +{/if} + +Nếu bạn xem tệp _config.json_, bạn sẽ nhận ra các thuộc tính cần thiết để xây dựng kiến trúc mô hình. Tệp này cũng chứa một số siêu dữ liệu, chẳng hạn như điểm bắt nguồn của checkpoint và phiên bản 🤗 Transformers bạn đang sử dụng khi bạn lưu checkpoint lần cuối. + +{#if fw === 'pt'} + +Tệp _pytorch_model.bin_ được gọi là _state dictionary_ (_từ điển trạng thái_); nó chứa tất cả các trọng số mô hình của bạn. Hai tập tin đi đôi với nhau; cấu hình là cần thiết để biết kiến trúc mô hình của bạn, trong khi trọng số mô hình là thông số của mô hình của bạn. + +{:else} + +Tệp _tf_model.h5_ được gọi là _state dictionary_ (_từ điển trạng thái_); nó chứa tất cả các trọng số mô hình của bạn. Hai tập tin đi đôi với nhau; cấu hình là cần thiết để biết kiến trúc mô hình của bạn, trong khi trọng số mô hình là thông số của mô hình của bạn. + +{/if} + +## Sử dụng mô hình Transformer để luận suy + +Giờ bạn đã biết cách tải và lưu một mô hình, hãy thử sử dụng nó để đưa ra một số dự đoán. Các mô hình Transfomer chỉ có thể xử lý số - các số mà tokenizer tạo ra. Nhưng trước khi chúng ta thảo luận về tokenizer, chúng ta hãy khám phá những yếu tố đầu vào mà mô hình chấp nhận. + +Tokenizer có thể đảm nhận việc truyền các đầu vào đến các tensor của khung thích hợp, nhưng để giúp bạn hiểu những gì đang xảy ra, chúng ta sẽ xem xét nhanh những gì phải thực hiện trước khi gửi đầu vào cho mô hình. + +Giả sử chúng ta có một vài chuỗi như sau: + +```py +sequences = ["Hello!", "Cool.", "Nice!"] +``` + +Tokenizer chuyển đổi các chỉ số này thành các chỉ mục từ vựng thường được gọi là _ID đầu vào_. Mỗi chuỗi giờ là một danh sách các số! Kết quả đầu ra là: + +```py no-format +encoded_sequences = [ + [101, 7592, 999, 102], + [101, 4658, 1012, 102], + [101, 3835, 999, 102], +] +``` + +Đây là danh sách các chuỗi được mã hóa: danh sách các danh sách. Tensor chỉ chấp nhận dạng hình chữ nhật (hãy nghĩ tới ma trận). "Mảng" này đã có dạng hình chữ nhật, vì vậy việc chuyển đổi nó thành một tensor rất dễ dàng: + +{#if fw === 'pt'} + +```py +import torch + +model_inputs = torch.tensor(encoded_sequences) +``` + +{:else} + +```py +import tensorflow as tf + +model_inputs = tf.constant(encoded_sequences) +``` + +{/if} + +### Sử dụng tensor làm đầu vào mô hình + +Việc sử dụng các tensors với mô hình cực kỳ đơn giản - chúng ta chỉ cần gọi mô hình với các đầu vào: + +```py +output = model(model_inputs) +``` + +Mặc dù mô hình chấp nhận rất nhiều tham số khác nhau, nhưng chỉ các ID đầu vào là cần thiết. Chúng tôi sẽ giải thích những gì các tham số khác làm và khi nào chúng được yêu cầu sau, nhưng trước tiên, chúng ta cần xem xét kỹ hơn các bộ tokenizer tạo ra các đầu vào mà một mô hình Transformer có thể hiểu được. diff --git a/chapters/vi/chapter2/4.mdx b/chapters/vi/chapter2/4.mdx new file mode 100644 index 000000000..34e1fcc22 --- /dev/null +++ b/chapters/vi/chapter2/4.mdx @@ -0,0 +1,238 @@ + + +# Tokenizers + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + + +Tokenizer là một trong những thành phần cốt lõi của pipeline NLP. Chúng phục vụ một mục đích: dịch văn bản thành dữ liệu có thể được xử lý bởi mô hình. Mô hình chỉ có thể xử lý dạng số, do đó, các tokenizer cần phải chuyển đổi đầu vào văn bản của chúng ta thành dữ liệu số. Trong phần này, chúng ta sẽ khám phá chính xác những gì xảy ra trong đường dẫn mã hóa. + +Trong các tác vụ NLP, dữ liệu thường được xử lý là văn bản thô. Đây là một ví dụ về văn bản như vậy: + +``` +Jim Henson was a puppeteer +``` + +Tuy nhiên, các mô hình chỉ có thể xử lý số, vì vậy chúng ta cần tìm cách chuyển văn bản thô thành số. Đó là những gì mà tokenizer làm, và có rất nhiều cách để thực hiện điều này. Mục tiêu đề ra là tìm ra cách biểu diễn có ý nghĩa nhất - nghĩa là cái có ý nghĩa nhất đối với mô hình - và, nếu có thể, là cách biểu diễn nhỏ nhất. + +Hãy cùng xem một số ví dụ về thuật toán tokenize và cố gắng trả lời một số câu hỏi bạn có thể có về tokenize. + +## Dựa trên từ + + + +Loại tokenizer đầu tiên ta nghĩ đến đó là _dựa trên từ vựng_. Nó thường rất dễ thiết lập và sử dụng chỉ với một số quy tắc và nó thường mang lại kết quả tốt. Ví dụ: trong hình ảnh bên dưới, mục tiêu là tách văn bản thô thành các từ và tìm biểu diễn số cho mỗi từ: + +
+ An example of word-based tokenization. + +
+ +Có nhiều cách khác nhau để tách văn bản. Ví dụ: chúng ta có thể sử dụng khoảng trắng để tokenize văn bản thành các từ bằng cách áp dụng hàm `split()` của Python: + +```py +tokenized_text = "Jim Henson was a puppeteer".split() +print(tokenized_text) +``` + +```python out +['Jim', 'Henson', 'was', 'a', 'puppeteer'] +``` + +Ngoài ra còn có các biến thể của tokenize mức từ với các quy tắc bổ sung cho dấu câu. Với loại tokenizer này, chúng ta có thể đúc kết với một bộ "từ vựng" khá lớn, trong đó từ vựng được xác định bằng tổng số token độc lập mà chúng ta có trong corpus (kho ngữ liệu) của mình. + +Mỗi từ được gán một ID, bắt đầu từ 0 và tăng dần theo kích thước của bộ từ vựng. Mô hình sử dụng các ID này để xác định từng từ. + +Nếu chúng ta muốn bao phủ hoàn toàn một ngôn ngữ bằng tokenize mức từ, chúng ta sẽ cần phải có một chỉ số nhận dạng cho mỗi từ trong ngôn ngữ, điều này sẽ tạo ra một lượng lớn token. Ví dụ: có hơn 500,000 từ trong tiếng Anh, vì vậy để xây dựng bản đồ nối mỗi từ đến một ID đầu vào, chúng ta cần theo dõi ngần đó ID. Hơn nữa, các từ như "dog" được biểu diễn khác với các từ như "dogs", và ban đầu mô hình sẽ không có cách nào để biết rằng "dog" (chó) và "dogs" là tương tự nhau: nó sẽ xác định hai từ này không liên quan. Điều này cũng áp dụng cho các từ tương tự khác, như "run" (chạy) và "running", mà ban đầu mô hình sẽ không thấy là tương tự. + +Cuối cùng, chúng ta cần một token tùy chỉnh để đại diện cho các từ không có trong vốn từ vựng của chúng ta. Mã này được gọi là token "không xác định", thường được biểu thị là "[UNK]" hoặc "<unk>". Nói chung, đó là một dấu hiệu xấu nếu bạn thấy trình tokenize đang tạo ra rất nhiều token này, vì nó không thể truy xuất một biểu hiện hợp lý của một từ và bạn đang mất thông tin trong suốt quá trình. Mục tiêu khi tạo từ vựng là làm sao cho trình tokenize mã hóa càng ít từ thành token không xác định càng tốt. + +Một cách để giảm số lượng mã thông báo không xác định là đi sâu hơn xuống một cấp, sử dụng tokenize _mức kí tự_. + +## Dựa trên kí tự + + + +- Vốn từ vựng ít hơn nhiều. +- Có ít token ngoài bộ từ vựng (không xác định) hơn nhiều, vì mọi từ đều có thể được xây dựng từ các ký tự. + +Nhưng ở đây cũng có một số câu hỏi nảy sinh liên quan đến dấu cách và các dấu câu: + +
+ An example of character-based tokenization. + +
+ +Cách tiếp cận này cũng không hoàn hảo. Vì biểu diễn bây giờ dựa trên các ký tự chứ không phải từ, người ta có thể lập luận rằng, theo trực giác, nó ít ý nghĩa hơn: mỗi ký tự không có nhiều ý nghĩa riêng so với trường hợp của các từ. Tuy nhiên, điều này lại khác nhau tùy theo ngôn ngữ; trong tiếng Trung, chẳng hạn, mỗi ký tự mang nhiều thông tin hơn một ký tự trong ngôn ngữ Latinh. + +Một điều khác cần xem xét là chúng ta sẽ có một lượng rất lớn token sẽ được xử lý bởi mô hình của chúng ta: trong khi một từ chỉ là một token duy nhất khi tokenize dựa trên từ, nó có thể dễ dàng chuyển thành 10 token trở lên khi chuyển đổi thành các ký tự. + +Để tận dụng tối đa cả hai, chúng ta có thể sử dụng kỹ thuật thứ ba kết hợp hai cách tiếp cận: _tokenize theo từ phụ_. + +## Tokenize theo từ phụ + + + +Các thuật toán token theo từ phụ dựa trên nguyên tắc rằng các từ được sử dụng thường xuyên không được chia thành các từ phụ nhỏ hơn, nhưng các từ hiếm phải được phân tách thành các từ phụ có ý nghĩa. + +Ví dụ: "annoyingly" (khó chịu) có thể được coi là một từ hiếm và có thể được chuyển thành "annoying" và "ly". Cả hai đều có khả năng xuất hiện thường xuyên hơn dưới dạng các từ phụ độc lập, đồng thời nghĩa của "annoying" được giữ nguyên bởi nghĩa kết hợp của "annoying" và "ly". + +Dưới đây là một ví dụ cho thấy cách một thuật toán tokenize theo từ phụ sẽ tokenize chuỗi "Let's do tokenization!" (Hãy thực hiện tokenize!): + +
+ A subword tokenization algorithm. + +
+ +Những từ phụ này cung cấp rất nhiều ý nghĩa về mặt ngữ nghĩa: ví dụ: trong ví dụ ở trên "tokenization" được chia thành "token" và "ization", hai token đều có ý nghĩa về mặt ngữ nghĩa đồng thời tiết kiệm không gian (chỉ cần hai token để biểu thị một từ dài). Điều này cho phép chúng ta có thể bao quát tương đối tốt với các từ vựng nhỏ và gần như không có token nào không xác định. + +Cách tiếp cận này đặc biệt hữu ích trong các ngôn ngữ tổng hợp như tiếng Thổ Nhĩ Kỳ, nơi bạn có thể tạo (gần như) các từ phức dài tùy ý bằng cách xâu chuỗi các từ phụ lại với nhau. + +### Và hơn thế nữa! + +Không có gì đáng ngạc nhiên, có rất nhiều kỹ thuật khác, có thể kể đến: + +- Byte-level BPE (BPE cấp byte), như được sử dụng trong GPT-2 +- WordPiece, như được sử dụng trong BERT +- SentencePiece hoặc Unigram, như được sử dụng trong một số mô hình đa ngôn ngữ + +Bây giờ, bạn đã có đủ kiến thức về cách thức hoạt động của tokenize để bắt đầu với API. + +## Tải và lưu + +Việc tải và lưu tokenizer cũng đơn giản như với các mô hình. Trên thực tế, nó dựa trên hai phương thức giống nhau: `from_pretrained()` và `save_pretrained()`. Các phương thức này sẽ tải hoặc lưu thuật toán được sử dụng bởi tokenizer (hơi giống với _kiến trúc_ của mô hình) cũng như từ vựng của nó (hơi giống với _trọng số_ của mô hình). + +Việc tải BERT tokenizer được huấn luyện với cùng một checkpoint với BERT được thực hiện giống như cách tải mô hình, ngoại trừ việc chúng ta sử dụng lớp `BertTokenizer`: + +```py +from transformers import BertTokenizer + +tokenizer = BertTokenizer.from_pretrained("bert-base-cased") +``` + +{#if fw === 'pt'} +Tương tự `AutoModel`, lớp `AutoTokenizer` sẽ lấy lớp tokenizer thích hợp trong thư viện dựa trên tên checkpoint và có thể được sử dụng trực tiếp với bất kỳ checkpoint nào: + +{:else} +Tương tự `TFAutoModel`, lớp `AutoTokenizer` sẽ lấy lớp tokenizer thích hợp trong thư viện dựa trên tên checkpoint và có thể được sử dụng trực tiếp với bất kỳ checkpoint nào: + +{/if} + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +``` + +Giờ chúng ta có thể sử dụng tokenizer như trong đoạn dưới đây: + +```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]} +``` + +Lưu một tokenizer giống như khi lưu một mô hình vậy: + +```py +tokenizer.save_pretrained("directory_on_my_computer") +``` + +Chúng ta sẽ trao đổi thêm về `token_type_ids` trong [Chương 3](/course/chapter3), và chúng ta sẽ giải thích cơ chế của từ khoá `attention_mask` sau đó. Đầu tiênm hãy cùng xem cách `input_ids` được tạo ra. Để làm điều này, chúng ta sẽ cần xem xét các phương thức trung gian của tokenizer. + +## Mã hoá + + + +Dịch văn bản sang số được gọi là _encoding_ hay _mã hoá_. Việc mã hóa được thực hiện theo quy trình gồm hai bước: tokenize, tiếp theo là chuyển đổi sang ID đầu vào. + +Như chúng ta đã thấy, bước đầu tiên là chia văn bản thành các từ (hoặc các phần của từ,theo ký hiệu dấu câu, v.v.), thường được gọi là _token_. Có nhiều quy tắc có thể chi phối quá trình đó, đó là lý do tại sao chúng ta cần khởi tạo trình token bằng cách sử dụng tên của mô hình, để đảm bảo rằng chúng tôi sử dụng cùng các quy tắc đã được sử dụng khi mô hình được huấn luyện trước. + +Bước thứ hai là chuyển đổi các token đó thành số để chúng ta có thể xây dựng một tensor từ chúng và đưa chúng vào mô hình. Để làm điều này, tokenizer có _từ vựng_, là phần chúng ta tải xuống khi khởi tạo nó bằng phương thức `from_pretrained()`. Một lần nữa, chúng ta cần sử dụng cùng một bộ từ vựng được sử dụng khi mô hình được huấn luyện trước. + +Để hiểu rõ hơn về hai bước, chúng ta sẽ khám phá chúng một cách riêng biệt. Lưu ý rằng chúng tôi sẽ sử dụng một số phương pháp thực hiện các phần của pipeline tokenize riêng biệt để hiển thị cho bạn kết quả trung gian của các bước đó, nhưng trên thực tế, bạn nên gọi tokenize trực tiếp trên đầu vào của mình (như được hiển thị trong phần 2). + +### Tokenize + +Quá trình tokenize được thực hiện bởi phương thức `tokenize()` của tokenizer: + +```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) +``` + +Kết quả của phương thức này là một danh sách các chuỗi văn bản hoặc tokens: + +```python out +['Using', 'a', 'transform', '##er', 'network', 'is', 'simple'] +``` + +Tokenizer này là một tokenizer dự theo từ phụ: nó chia các từ cho đến khi lấy được các tokens được biểu diễn bởi bộ từ vựng của nó. Ví dụ, `transformer` sẽ được chia thành hai token: `transform` và `##er`. + +### Từ token tới ID đầu vào + +Quá tình chuyển đổi sang ID đầu vào được thực hiện bởi `convert_tokens_to_ids()` của tokenizer: + +```py +ids = tokenizer.convert_tokens_to_ids(tokens) + +print(ids) +``` + +```python out +[7993, 170, 11303, 1200, 2443, 1110, 3014] +``` + +Các đầu ra này, sau khi được chuyển đổi sang khung tensor thích hợp, có thể được sử dụng làm đầu vào cho một mô hình như đã thấy ở phần trước trong chương này. + + + +✏️ **Thử nghiệm thôi!** Sao chép hai bước cuối cùng (tokenize và chuyển đổi sang ID đầu vào) trên các câu đầu vào mà chúng ta đã sử dụng trong phần 2 ("I've been waiting for a HuggingFace course my whole life." và "I hate this so much!"). Kiểm tra xem bạn có nhận được các ID đầu vào giống như chúng tôi đã nhận trước đó không! + + + +## Giải mã + +_Decoding_ hay _giải mã_ thì ngược lại: từ các chỉ số từ vựng, ta muốn trả về một chuỗi văn bản. Điều này có thể được thực hiện với phương thức `decode()` như sau: + +```py +decoded_string = tokenizer.decode([7993, 170, 11303, 1200, 2443, 1110, 3014]) +print(decoded_string) +``` + +```python out +'Using a Transformer network is simple' +``` + +Lưu ý rằng phương pháp `giải mã` không chỉ chuyển đổi các chỉ số trở lại thành token, mà còn nhóm các token là một phần của cùng một từ lại với nhau để tạo ra một câu có thể đọc được. Hành vi này sẽ cực kỳ hữu ích khi chúng ta sử dụng các mô hình dự đoán văn bản mới (văn bản được tạo từ lời nhắc hoặc đối với các bài toán chuỗi-sang-chuỗi như dịch hoặc tóm tắt văn bản). + +Bây giờ bạn đã hiểu các hoạt động nguyên tử mà một tokenizer có thể xử lý: tokenize, chuyển đổi sang ID và chuyển đổi ID trở lại một chuỗi. Tuy nhiên, tất cả chỉ mới là sự bắt đầu. Trong phần sau, chúng ta sẽ tiếp cận các giới hạn của nó và xem cách vượt qua chúng. diff --git a/chapters/vi/chapter2/5.mdx b/chapters/vi/chapter2/5.mdx new file mode 100644 index 000000000..40c583154 --- /dev/null +++ b/chapters/vi/chapter2/5.mdx @@ -0,0 +1,338 @@ + + +# Xử lý đa chuỗi + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + +{:else} + +{/if} + +Trong phần trước, chúng ta đã khám phá các trường hợp sử dụng đơn giản nhất: thực hiện luận suy trên một dãy đơn có độ dài nhỏ. Tuy nhiên, một số câu hỏi được đề cập như: + +- Làm thế nào để chúng ta xử lý nhiều chuỗi? +- Làm thế nào để chúng ta xử lý nhiều chuỗi *có độ dài khác nhau*? +- Các chỉ số từ vựng có phải là đầu vào duy nhất cho phép một mô hình hoạt động tốt không? +- Nếu như một chuỗi quá dài thì sao? + +Hãy xem những câu hỏi này đặt ra những loại vấn đề nào và cách chúng tôi có thể giải quyết chúng bằng cách sử dụng API 🤗 Transformers. + +## Mô hình kì vọng một lô các đầu vào + +Trong bài tập trước, bạn đã thấy cách các chuỗi được chuyển thành danh sách các số. Hãy chuyển đổi danh sách các số này thành một tensor và gửi nó đến mô hình: + +{#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) +# This line will fail. +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) +# This line will fail. +model(input_ids) +``` + +```py out +InvalidArgumentError: Input to reshape is a tensor with 14 values, but the requested shape has 196 [Op:Reshape] +``` +{/if} + +Ôi không! Tại sao đoạn mã lại không thành công? Chúng ta đã làm theo các bước từ pipeline trong phần 2. + +Vấn đề ở đây đó là chúng ta đã gửi một chuỗi đơn cho mô hình, trong khi mô hình 🤗 Transformers mong đợi nhiều câu theo mặc định. Ở đây, chúng ta đã cố gắng thực hiện mọi thứ mà tokenizer đã làm ở phía sau khi áp dụng nó vào một `chuỗi`, nhưng nếu bạn nhìn kỹ, bạn sẽ thấy rằng nó không chỉ chuyển đổi danh sách ID đầu vào thành một tensor, nó còn thêm một chiều lên trên: + +{#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} + +Hãy cũng thử lại và thêm một chiều mới: + +{#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} + +Ta in ra các ID đầu vào cũng như kết quả logit như sau: + +{#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} + +*Batching* hay *Lô* là hành động gửi nhiều câu qua mô hình, tất cả cùng một lúc. Nếu bạn chỉ có một câu, bạn chỉ có thể xây dựng một lô với một chuỗi duy nhất: + +``` +batched_ids = [ids, ids] +``` + +Đây là một lô chứa hai chuỗi giống nhau! + + + +✏️ **Thử nghiệm thôi!** Chuyển đổi danh sách `batch_ids` này thành một tensor và chuyển nó qua mô hình của bạn. Kiểm tra để đảm bảo rằng bạn có được logit giống như trước đây (nhưng hai lần)! + + + +Việc phân phối lô cho phép mô hình hoạt động khi bạn đưa vào nhiều câu. Việc sử dụng nhiều chuỗi cũng đơn giản như xây dựng một lô với một chuỗi duy nhất. Tuy nhiên, có một vấn đề thứ hai. Khi bạn cố gắng ghép hai (hoặc nhiều) câu lại với nhau, chúng có thể có độ dài khác nhau. Nếu bạn đã từng làm việc với tensor trước đây, bạn biết rằng chúng cần có dạng hình chữ nhật, vì vậy bạn sẽ không thể chuyển đổi trực tiếp danh sách ID đầu vào thành tensor. Để giải quyết vấn đề này, chúng tôi thường *đệm* các đầu vào. + +## Đêm thêm vào đầu vào + +Danh sách các danh sách dưới đây không thể chuyển đổi thành một tensor: + +```py no-format +batched_ids = [ + [200, 200, 200], + [200, 200] +] +``` + +Để giải quyết vấn đề này, chúng ta sẽ sử dụng *đệm* để làm cho các tensor của chúng ta có hình chữ nhật. Đệm đảm bảo tất cả các câu của chúng ta có cùng độ dài bằng cách thêm một từ đặc biệt được gọi là *padding token* hay *token được đệm thêm* vào các câu có ít giá trị hơn. Ví dụ: nếu bạn có 10 câu 10 từ và 1 câu 20 từ, phần đệm sẽ đảm bảo tất cả các câu có 20 từ. Trong ví dụ của chúng tôi, tensor kết quả trông giống như sau: + +```py no-format +padding_id = 100 + +batched_ids = [ + [200, 200, 200], + [200, 200, padding_id], +] +``` + +ID của token đệm có thể tìm thấy ở `tokenizer.pad_token_id`. Hãy sử dụng nó và gửi hai câu của chúng ta thông qua mô hình riêng lẻ và theo lô với nhau: + +{#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} + +Có điều gì đó không ổn với các logit trong các dự đoán theo lô của chúng ta: hàng thứ hai phải giống với logit cho câu thứ hai, nhưng chúng ta có các giá trị hoàn toàn khác nhau! + +Điều này là do tính năng chính của các mô hình Transformer là các lớp attention đã *ngữ cảnh hóa* mỗi token. Chúng sẽ tính đến các padding token vì chúng tham gia vào tất cả các token của một chuỗi. Để có được kết quả tương tự khi chuyển các câu riêng lẻ có độ dài khác nhau qua mô hình hoặc khi chuyển một lô với các câu và phần đệm giống nhau được áp dụng, chúng ta cần yêu cầu các lớp attention đó bỏ qua các thẻ đệm. Điều này được thực hiện bằng cách sử dụng attention mask. + +## Attention masks + +*Attention masks* là các tensor có hình dạng chính xác như tensor ID đầu vào, được lấp đầy bởi 0 và 1: 1 cho biết các tokenn tương ứng nên được tham gia và các số 0 cho biết các token tương ứng không được tham gia (tức là chúng phải bị bỏ qua bởi các lớp attention của mô hình). + +Hãy hoàn thành ví dụ trước với một attention mask: + +{#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} + +Bây giờ chúng ta nhận được các logit tương tự cho câu thứ hai trong lô. + +Lưu ý cách giá trị cuối cùng của chuỗi thứ hai là ID đệm, là giá trị 0 trong attention mask. + + + +✏️ **Thử nghiệm thôi!** Áp dụng thủ công tokenize cho hai câu được sử dụng trong phần 2 ("I've been waiting for a HuggingFace course my whole life." và "I hate this so much!"). Đưa chúng vào mô hình và kiểm tra xem bạn có nhận được các logit giống như trong phần 2 không. Bây giờ, gộp chúng lại với nhau bằng cách sử dụng token đệm, sau đó tạo attention mask thích hợp. Kiểm tra xem bạn có đạt được kết quả tương tự khi đưa qua mô hình không! + + + +## Những chuỗi dài hơn + +Với các mô hình Transformer, có một giới hạn về độ dài của các chuỗi mà chúng tôi có thể vượt qua các mô hình. Hầu hết các mô hình xử lý chuỗi lên đến 512 hoặc 1024 token và sẽ bị lỗi khi được yêu cầu xử lý chuỗi dài hơn. Có hai giải pháp cho vấn đề này: + +- Sử dụng mô hình có độ dài chuỗi được hỗ trợ dài hơn. +- Cắt ngắn chuỗi của bạn. + +Các mô hình có độ dài chuỗi được hỗ trợ khác nhau và một số mô hình chuyên xử lý các trình tự rất dài. [Longformer](https://huggingface.co/transformers/model_doc/longformer.html) là một ví dụ và một ví dụ khác là [LED](https://huggingface.co/transformers/model_doc/led.html). Nếu bạn đang thực hiện một công việc đòi hỏi trình tự rất dài, chúng tôi khuyên bạn nên xem các mô hình đó. + +Nếu không, chúng tôi khuyên bạn nên cắt bớt các chuỗi của mình bằng cách chỉ định tham số `max_sequence_length`: + +```py +sequence = sequence[:max_sequence_length] +``` diff --git a/chapters/vi/chapter2/6.mdx b/chapters/vi/chapter2/6.mdx new file mode 100644 index 000000000..8ffd62368 --- /dev/null +++ b/chapters/vi/chapter2/6.mdx @@ -0,0 +1,165 @@ + + +# Kết hợp lại + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Trong vài phần trước, chúng ta đã cố gắng hết sức để làm hầu hết các tác vụ bằng tay. Chúng ta đã khám phá cách thức hoạt động của các công cụ tokenize và xem xét quá trình tokenize, chuyển đổi dữ liệu sang ID đầu vào, đệm, cắt bớt và các lớp che attention. + +Tuy nhiên, như chúng ta đã thấy trong phần 2, API 🤗 Transformers có thể xử lý tất cả những điều này cho chúng ta bằng một chức năng cấp cao mà chúng ta sẽ đi sâu vào đây. Khi bạn gọi trực tiếp `tokenizer` trên câu, bạn sẽ nhận lại được các thông tin đầu vào sẵn sàng chuyển qua mô hình của bạn: + +```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) +``` + +Ở đây, biến `model_inputs` chứa mọi thứ cần thiết để một mô hình hoạt động tốt. Đối với DistilBERT, điều đó bao gồm các ID đầu vào cũng như lớp che attention. Các mô hình khác chấp nhận đầu vào bổ sung cũng sẽ có đầu ra đó từ đối tượng `tokenizer`. + +Như chúng ta sẽ thấy trong một số ví dụ bên dưới, phương pháp này rất mạnh mẽ. Đầu tiên, nó có thể mã hóa một chuỗi duy nhất: + +```py +sequence = "I've been waiting for a HuggingFace course my whole life." + +model_inputs = tokenizer(sequence) +``` + +Nó cũng xử lý nhiều chuỗi cùng một lúc mà không cần thay đổi trong API: + +```py +sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] + +model_inputs = tokenizer(sequences) +``` + +Nó có thể đệm thêm tuỳ theo một số mục tiêu như sau: + +```py +# Sẽ đệm thêm vào chuỗi sao cho độ dài bằng độ dài tối đa của chuỗi +model_inputs = tokenizer(sequences, padding="longest") + +# Sẽ đệm thêm vào chuỗi sao cho độ dài bằng độ dài tối đa của mô hình +# (512 cho BERT hoặc DistilBERT) +model_inputs = tokenizer(sequences, padding="max_length") + +# Sẽ đệm thêm vào chuỗi sao cho độ dài bằng độ dài tối đa được chỉ định +model_inputs = tokenizer(sequences, padding="max_length", max_length=8) +``` + +Nó cũng có thể cắt bớt các chuỗi: + +```py +sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] + +# Sẽ cắt bớt chuỗi cho bằng độ dài tối đa của mô hình +# (512 cho BERT hoặc DistilBERT) +model_inputs = tokenizer(sequences, truncation=True) + +# Sẽ cắt bớt chuỗi có độ dài dài hơn độ dài tối đa được chỉ định +model_inputs = tokenizer(sequences, max_length=8, truncation=True) +``` + +Đối tượng `tokenizer` có thể xử lý việc chuyển đổi sang các tensor cụ thể, sau đó có thể được gửi trực tiếp đến mô hình. Ví dụ: trong đoạn mã sau, chúng tôi đang nhắc tokenizer trả về tensors từ các khung khác nhau - `"pt"` trả về tensors PyTorch, `"tf"` trả về tensors TensorFlow và `"np"` trả về mảng NumPy: + +```py +sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] + +# Trả về tensor PyTorch +model_inputs = tokenizer(sequences, padding=True, return_tensors="pt") + +# Trả về tensor TensorFlow +model_inputs = tokenizer(sequences, padding=True, return_tensors="tf") + +# Trả về mảng NumPy +model_inputs = tokenizer(sequences, padding=True, return_tensors="np") +``` + +## Các token đặc biệt + +Nếu chúng ta xem xét các ID đầu vào được trả về bởi tokenizer, chúng ta sẽ thấy chúng hơi khác một chút so với những gì chúng ta đã có trước đó: + +```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] +``` + +Một token ID đã được thêm vào vị trí đầu và cuối. Hãy giải mã hai chuỗi ID ở trên để xem nó là gì: + + +```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." +``` + +Tokenizer đã thêm từ đặc biệt `[CLS]` vào đầu và từ đặc biệt `[SEP]` ở cuối. Điều này là do mô hình đã được huấn luyện trước với chúng, vì vậy để có được kết quả tương tự để luận suy, chúng ta cũng cần thêm chúng vào. Lưu ý rằng một số mô hình không thêm các từ đặc biệt hoặc thêm các từ khác; mô hình cũng có thể chỉ thêm những từ đặc biệt này vào đầu hoặc chỉ ở cuối. Trong mọi trường hợp, tokenizer biết cái nào được mong đợi và sẽ giải quyết việc này cho bạn. + +## Tổng kết: Từ tokenizer đến mô hình + +Giờ chúng ta đã thấy tất cả các bước riêng lẻ mà `tokenizer` sử dụng khi áp dụng lên văn bản, chúng ta hãy xem lần cuối cách nó có thể xử lý nhiều chuỗi (đệm thêm!), chuỗi rất dài (cắt ngắn!) Và nhiều kiểu tensor với API chính của nó: + +{#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/vi/chapter2/7.mdx b/chapters/vi/chapter2/7.mdx new file mode 100644 index 000000000..a96ff4fdf --- /dev/null +++ b/chapters/vi/chapter2/7.mdx @@ -0,0 +1,13 @@ +# Hoàn thành cách sử dụng cơ bản! + +Thật tuyệt vời khi theo dõi khóa học đến đây! Tổng kết lại, trong chương này bạn: + +- Đã học các khối cơ bản của mô hình Transformer. +- Đã học những gì tạo nên pipeline cho việc tokenize. +- Biết cách sử dụng mô hình Transformer trong thực tế. +- Đã học cách sử dụng tokenizer để chuyển đổi văn bản thành tensors mà mô hình có thể hiểu được. +- Thiết lập tokenizer và một mô hình cùng nhau để chuyển từ văn bản đầu vào thành dự đoán đầu ra. +- Tìm hiểu những hạn chế của ID đầu vào và tìm hiểu về lớp attantion mask. +- Nghịch các phương pháp tokenizer linh hoạt và có thể định cấu hình. + +Từ bây giờ, bạn sẽ có thể tự do khám phá các tài liệu 🤗 Transformers: từ vựng sẽ nghe có vẻ quen thuộc và bạn đã thấy các phương pháp bạn sẽ sử dụng phần lớn thời gian. diff --git a/chapters/vi/chapter2/8.mdx b/chapters/vi/chapter2/8.mdx new file mode 100644 index 000000000..daf41d7ef --- /dev/null +++ b/chapters/vi/chapter2/8.mdx @@ -0,0 +1,307 @@ + + + + +# Đố vui cuối chương + +### 1. Thứ tự của một quy trình mô hình hóa ngôn ngữ là gì? + + + +### 2. Đầu ra tensor của mô hình Transformer cơ sở có bao nhiêu chiều, và chúng là gì? + + + +### 3. Trường hợp nào dưới đây không phải là ví dụ về tokenize theo từ phụ? + + + +### 4. Model head (Đầu mô hình) là gì? + + + +{#if fw === 'pt'} + +### 5. AutoModel là gì? + +AutoTrain của chúng tôi không?" + }, + { + text: "Một đối tượng trả về kiến trúc chính xác dựa trên checkpoint", + explain: "Chính xác: AutoModel chỉ cần biết checkpoint từ đó khởi tạo để trả về kiến trúc chính xác.", + correct: true + }, + { + text: "Một mô hình tự động phát hiện ngôn ngữ được sử dụng cho đầu vào của nó để tải các trọng số chính xác", + explain: "Không chính xác; trong khi một số checkpoint và mô hình có khả năng xử lý đa ngôn ngữ, không có công cụ tích hợp nào để lựa chọn checkpoint tự động theo ngôn ngữ. Bạn nên truy cập Model Hub để tìm checkpoint tốt nhất cho tác vụ của bạn!" + } + ]} +/> + +{:else} +### 5. TFAutoModel là gì? + +AutoTrain của chúng tôi không?" + }, + { + text: "Một đối tượng trả về kiến trúc chính xác dựa trên checkpoint", + explain: "Chính xác: TFAutoModel chỉ cần biết checkpoint từ đó khởi tạo để trả về kiến trúc chính xác.", + correct: true + }, + { + text: "Một mô hình tự động phát hiện ngôn ngữ được sử dụng cho đầu vào của nó để tải các trọng số chính xác", + explain: "Không chính xác; trong khi một số checkpoint và mô hình có khả năng xử lý đa ngôn ngữ, không có công cụ tích hợp nào để lựa chọn checkpoint tự động theo ngôn ngữ. Bạn nên truy cập Model Hub để tìm checkpoint tốt nhất cho tác vụ của bạn!" + } + ]} +/> + +{/if} + +### 6. Các kỹ thuật cần lưu ý khi ghép các chuỗi có độ dài khác nhau với nhau là gì? + + + +### 7. Mục đích của việc áp dụng hàm SoftMax vào đầu ra logit của mô hình phân loại là gì? + + + +### 8. Phần lớn API tokenizer tập trung vào phương pháp nào? + +encode, vì nó có thể mã hóa văn bản thành ID và ID thành dự đoán", + explain: "Sai! Mặc dù phương thứcencode tồn tại trên tokenizers, nhưng nó không tồn tại trên các mô hình." + }, + { + text: "Gọi trực tiếp đối tượng tokenizer.", + explain: "Chính xác! Phương thức __call__ của tokenizer là một phương pháp rất mạnh có thể xử lý khá nhiều thứ. Nó cũng là phương pháp được sử dụng để truy xuất các dự đoán từ một mô hình.", + correct: true + }, + { + text: "Đệm thêm", + explain: "Sai! Đệm thêm rất hữu ích, nhưng nó chỉ là một phần của tokenizer API." + }, + { + text: "tokenize", + explain: "Phương thức tokenize được cho là một trong những phương pháp hữu ích nhất, nhưng nó không phải là cốt lõi của API tokenizer." + } + ]} +/> + +### 9. Biến `result` chứa gì trong đoạn mã dưới đây? + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +result = tokenizer.tokenize("Hello!") +``` + +__call__ hoặc convert_tokens_to_ids làm!" + }, + { + text: "Một chuỗi chứa tất cả các token", + explain: "Điều này sẽ là không tối ưu, vì mục tiêu là chia chuỗi thành nhiều token." + } + ]} +/> + +{#if fw === 'pt'} + +### 10. Có điều gì đó sai với đoạn mã sau đây? + +```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. Có điều gì đó sai với đoạn mã sau đây? + +```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/vi/chapter3/1.mdx b/chapters/vi/chapter3/1.mdx new file mode 100644 index 000000000..d69c48dcf --- /dev/null +++ b/chapters/vi/chapter3/1.mdx @@ -0,0 +1,21 @@ + + +# Giới thiệu + +Trong [Chương 2](/course/chapter2), chúng ta đã khám phá cách sử dụng tokenizer và các mô hình huấn luyện trước để đưa ra dự đoán. Nhưng nếu bạn muốn tinh chỉnh một mô hình được huấn luyện trước cho tập dữ liệu của riêng mình thì sao? Đó là chủ đề của chương này! Bạn sẽ học: + +{#if fw === 'pt'} +* Cách chuẩn bị một tập dữ liệu lớn từ Hub +* Cách sử dụng API `Trainer` cấp cao để tinh chỉnh mô hình +* Cách sử dụng vòng lặp huấn luyện tùy chỉnh +* Cách tận dụng thư viện 🤗 Accelerate để dễ dàng chạy vòng huấn luyện tùy chỉnh đó trên bất kỳ thiết lập phân tán nào + +{:else} +* Cách chuẩn bị một tập dữ liệu lớn từ Hub +* Cách sử dụng Keras để tinh chỉnh mô hình +* Cách sử dụng Keras để đưa ra dự đoán +* Cách sử dụng thước đo tùy chỉnh + +{/if} + +Để tải các checkpoint được huấn luyện của bạn lên Hugging Face Hub, bạn sẽ cần có tài khoản huggingface.co: [tạo tài khoản](https://huggingface.co/join) diff --git a/chapters/vi/chapter3/2.mdx b/chapters/vi/chapter3/2.mdx new file mode 100644 index 000000000..dd3d633bb --- /dev/null +++ b/chapters/vi/chapter3/2.mdx @@ -0,0 +1,381 @@ + + +# Xử lý dữ liệu + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} +Tiếp tục với ví dụ từ [chương trước](/course/chapter2), đây là cách chúng ta sẽ huấn luyện một bộ phân loại chuỗi trên một lô trong PyTorch: + +```python +import torch +from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification + +# Tương tự như ví dụ trước +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "This course is amazing!", +] +batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") + +# Đây là phần mới +batch["labels"] = torch.tensor([1, 1]) + +optimizer = AdamW(model.parameters()) +loss = model(**batch).loss +loss.backward() +optimizer.step() +``` +{:else} +Tiếp tục với ví dụ từ [chương trước](/course/chapter2), đây là cách chúng ta sẽ huấn luyện một bộ phân loại chuỗi trên một lô trong TensorFlow: + +```python +import tensorflow as tf +import numpy as np +from transformers import AutoTokenizer, TFAutoModelForSequenceClassification + +# Tương tự như ví dụ trước +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "This course is amazing!", +] +batch = dict(tokenizer(sequences, padding=True, truncation=True, return_tensors="tf")) + +# Đây là phần mới +model.compile(optimizer="adam", loss="sparse_categorical_crossentropy") +labels = tf.convert_to_tensor([1, 1]) +model.train_on_batch(batch, labels) +``` +{/if} + +Tất nhiên, chỉ huấn luyện mô hình trên hai câu sẽ không mang lại kết quả tốt. Để có được kết quả tốt hơn, bạn sẽ cần chuẩn bị một bộ dữ liệu lớn hơn. + +Trong phần này, chúng tôi sẽ sử dụng tập dữ liệu MRPC (Microsoft Research Paraphrase Corpus) làm ví dụ, được giới thiệu trong [bài báo](https://www.aclweb.org/anthology/I05-5002.pdf) của William B. Dolan và Chris Brockett. Tập dữ liệu bao gồm 5,801 cặp câu, với nhãn cho biết chúng có phải là câu diễn giải hay không (tức là nếu cả hai câu đều có nghĩa giống nhau). Chúng tôi đã chọn nó cho chương này vì nó là một tập dữ liệu nhỏ, vì vậy thật dễ dàng để thử nghiệm với việc huấn luyện về nó. + +### Tải bộ dữ liệu từ Hub + +{#if fw === 'pt'} + +{:else} + +{/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. + +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: + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("glue", "mrpc") +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 408 + }) + test: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 1725 + }) +}) +``` + +Như bạn có thể thấy, chúng ta nhận được một đối tượng `DatasetDict` chứa tập huấn luyện, tập kiểm định và tập kiểm thử. Mỗi tập chứa một số cột (`sentence1`, `sentence2`, `label`, và `idx`) và một số hàng thay đổi, là số phần tử trong mỗi tập (vì vậy, có 3,668 cặp câu trong tập huấn luyện, 408 trong tập kiểm chứng và 1,725 trong tập kiểm định). + +Lệnh này tải xuống và lưu vào bộ nhớ cache các tập dữ liệu, mặc định lưu trong *~/.cache/huggingface/datasets*. Nhớ lại từ Chương 2 rằng bạn có thể tùy chỉnh thư mục bộ nhớ cache của mình bằng cách đặt biến môi trường `HF_HOME`. + +Chúng ta có thể truy cập từng cặp câu trong đối tượng `raw_datasets` của mình bằng cách lập chỉ mục, giống như với từ điển: + +```py +raw_train_dataset = raw_datasets["train"] +raw_train_dataset[0] +``` + +```python out +{'idx': 0, + 'label': 1, + 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', + 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} +``` + +Chúng ta có thể thấy các nhãn vốn là số nguyên, vì vậy chúng ta không phải thực hiện bất kỳ bước xử lý trước nào ở đó. Để biết số nguyên nào tương ứng với nhãn nào, chúng ta có thể kiểm tra `features` của `raw_train_dataset`. Điều này sẽ cho chúng tôi biết loại của mỗi cột: + +```py +raw_train_dataset.features +``` + +```python out +{'sentence1': Value(dtype='string', id=None), + 'sentence2': Value(dtype='string', id=None), + 'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None), + 'idx': Value(dtype='int32', id=None)} +``` + +Phía sau, `label` thuộc loại `ClassLabel` và ánh xạ các số nguyên thành tên nhãn được lưu trữ trong thư mục *names*. `0` tương ứng với `không tương đương`, và `1` tương ứng với `tương đương`. + + + +✏️ **Thử nghiệm thôi!** Nhìn vào phần tử thứ 15 của tập huấn luyện và phần tử 87 của tập kiểm định. Nhãn của chúng là gì? + + + +### Tiền xử lý một bộ dữ liệu + +{#if fw === 'pt'} + +{:else} + +{/if} + +Để tiền xử lý bộ dữ liệu, chúng ta cần chuyển văn bản thành các số mà mô hình có thể hiểu được. Như bạn đã thấy trong [chương trước](/course/chapter2), điều này được thực hiện với một tokenizer. Chúng ta có thể cung cấp cho tokenizer một câu hoặc một danh sách các câu, vì vậy chúng ta có thể tokenizer trực tiếp tất cả các câu đầu tiên và tất cả các câu thứ hai của mỗi cặp như sau: + +```py +from transformers import AutoTokenizer + +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"]) +tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"]) +``` + +Tuy nhiên, chúng ta không thể chỉ chuyển hai chuỗi vào mô hình và nhận được dự đoán liệu hai câu có phải là diễn giải hay không. Chúng ta cần xử lý hai chuỗi như một cặp và áp dụng tiền xử lý thích hợp. May mắn thay, tokenizer cũng có thể nhận một cặp chuỗi và chuẩn bị nó theo cách mà mô hình BERT của ta mong đợi: + +```py +inputs = tokenizer("This is the first sentence.", "This is the second one.") +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] +} +``` + +Chúng ta đã thảo luận về `input_ids` và `attention_mask` trong [Chương 2](/course/chapter2), nhưng chúng ta tạm dừng để nói về `token_type_ids`. Trong ví dụ này, đây là phần cho mô hình biết phần nào của đầu vào là câu đầu tiên và phần nào là câu thứ hai. + + + +✏️ **Thử nghiệm thôi!** Lấy phần tử 15 của tập huấn luyện và tokenize hai câu riêng biệt và như một cặp. Sự khác biệt giữa hai kết quả là gì? + + + +Nếu chúng ta giải mã các ID bên trong `input_ids` trở lại các từ: + +```py +tokenizer.convert_ids_to_tokens(inputs["input_ids"]) +``` + +ta sẽ nhận được: + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +``` + +Có thể thấy mô hình kì vọng các đầu vào có dạng `[CLS] câu1 [SEP] câu2 [SEP]` khi có hai câu. Căn chỉnh điều này với `token_type_ids` cho ta kết quả: + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +[ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] +``` + +Như bạn có thể thấy, các phần của đầu vào tương ứng với `[CLS] câu1 [SEP]` đều có loại token ID là `0`, trong khi các phần khác, tương ứng với `câu2 [SEP]`, tất cả đều có loại token ID là `1`. + +Lưu ý rằng nếu bạn chọn một checkpoint khác, bạn sẽ không nhất thiết phải có `token_type_ids` trong đầu vào được tokenize của mình (ví dụ: chúng sẽ không được trả lại nếu bạn sử dụng mô hình DistilBERT). Chúng chỉ được trả lại khi mô hình biết phải làm gì với chúng, bởi vì nó đã nhìn thấy chúng trong quá trình huấn luyện trước. + +Ở đây, BERT được huấn luyện trước với các token ID và trên đầu mục tiêu mô hình ngôn ngữ được che mà chúng ta đã đề cập trong [Chương 1](/course/chapter1), nó có một mục tiêu bổ sung được gọi là _dự đoán câu tiếp theo_. Mục tiêu của tác vụ này là mô hình hóa mối quan hệ giữa các cặp câu. + +Với dự đoán câu tiếp theo, mô hình được cung cấp các cặp câu (với các token được che ngẫu nhiên) và được yêu cầu dự đoán liệu câu thứ hai có theo sau câu đầu tiên hay không. Để làm cho tác vụ trở nên không tầm thường, một nửa là các câu tiếp nối nhau trong tài liệu gốc mà chúng được trích xuất, và nửa còn lại là hai câu đến từ hai tài liệu khác nhau. + +Nói chung, bạn không cần phải lo lắng về việc có hay không có `token_type_ids` trong đầu vào được tokenize của mình: miễn là bạn sử dụng cùng một checkpoint cho trình tokenize và mô hình, mọi thứ sẽ ổn vì trình tokenize nhận biết cần cung cấp những gì với mô hình của nó. + +Bây giờ chúng ta đã thấy cách trình tokenize của chúng ta có thể xử lý một cặp câu, chúng ta có thể sử dụng nó để mã hóa toàn bộ tập dữ liệu của mình: giống như trong [chương trước](/course/chapter2), chúng ta có thể cung cấp cho trình tokenize danh sách các cặp bằng cách đưa cho nó danh sách các câu đầu tiên, sau đó là danh sách các câu thứ hai. Điều này cũng tương thích với các tùy chọn đệm và cắt bớt mà chúng ta đã thấy trong [Chương 2](/course/chapter2). Vì vậy, một cách để tiền xử lý trước tập dữ liệu huấn luyện là: + +```py +tokenized_dataset = tokenizer( + raw_datasets["train"]["sentence1"], + raw_datasets["train"]["sentence2"], + padding=True, + truncation=True, +) +``` + +Đ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: + +```py +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) +``` + +Hàm này lấy một từ điển (giống như các mục trong tập dữ liệu của chúng ta) và trả về một từ điển mới với các khóa `input_ids`, `attention_mask` và `token_type_ids`. Lưu ý rằng nó cũng hoạt động nếu từ điển `example` chứa một số mẫu (mỗi khóa là một danh sách các câu) vì `tokenizer` hoạt động trên danh sách các cặp câu, như đã thấy trước đây. Điều này sẽ cho phép chúng ta sử dụng tùy chọn `batch = True` trong lệnh gọi `map()`, từ đó sẽ tăng tốc đáng kể quá trình tokenize. `Tokenizer` được hỗ trợ bởi một tokenizer được viết bằng Rust từ thư viện [🤗 Tokenizer](https://github.com/huggingface/tokenizers). Tokenizer này có thể rất nhanh, nhưng chỉ khi chúng ta cung cấp nhiều đầu vào cùng một lúc. + +Lưu ý rằng chúng ta đã để tạm bỏ qua tham số `padding` trong hàm tokenize của ta. Điều này là do việc đệm tất cả các mẫu đến chiều dài tối đa không hiệu quả: tốt hơn nên đệm các mẫu khi chúng ta đang tạo một lô, vì khi đó chúng ta chỉ cần đệm đến chiều dài tối đa trong lô đó chứ không phải chiều dài tối đa trong toàn bộ tập dữ liệu. Điều này có thể tiết kiệm rất nhiều thời gian và công suất xử lý khi các đầu vào có độ dài rất thay đổi! + +Đây là cách chúng ta áp dụng chức năng mã hóa trên tất cả các tập dữ liệu của ta cùng một lúc. Chúng ta đang sử dụng `batch = True` trong lệnh gọi tới `map`, vì vậy, hàm được áp dụng cho nhiều phần tử của tập dữ liệu cùng một lúc, chứ không phải trên từng phần tử riêng biệt. Điều này cho phép việc tiền xử lý nhanh hơn. + +```py +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) +tokenized_datasets +``` + +Cách thư viện 🤗 Datasets áp dụng bước xử lý này là thêm các trường mới vào bộ dữ liệu, mỗi khóa trong từ điển được trả về bởi hàm tiền xử lý một trường: + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 408 + }) + test: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 1725 + }) +}) +``` + +Bạn thậm chí có thể sử dụng đa xử lý khi áp dụng chức năng tiền xử lý của mình với `map()` bằng cách truyền tham số `num_proc`. Chúng ta không làm điều này ở đây vì thư viện 🤗 Tokenizers đã sử dụng nhiều chuỗi để tokenize ác mẫu của nhanh hơn, nhưng nếu bạn không sử dụng trình tokenize nhanh được thư viện này hỗ trợ, bước trên có thể tăng tốc quá trình xử lý trước của bạn. + +`Tokenize_function` của chúng ta trả về một từ điển với các khóa `input_ids`, `attention_mask` và `token_type_ids`, vì vậy ba trường đó được thêm vào tất cả các phần bộ dữ liệu của chúng ta. Lưu ý rằng ta cũng có thể đã thay đổi các trường hiện có nếu hàm tiền xử lý trả về một giá trị mới cho một khóa hiện có trong tập dữ liệu mà ta đã áp dụng `map()`. + +Điều cuối cùng chúng ta sẽ cần làm là đệm tất cả các ví dụ để có độ dài của phần tử dài nhất khi chúng tôi gộp các phần tử lại với nhau - một kỹ thuật mà chúng tôi gọi là *đệm động*. + +### Phần đệm động + + + +{#if fw === 'pt'} +Hàm chịu trách nhiệm tập hợp các mẫu lại với nhau trong một lô được gọi là *collate function* hay *hàm đối chiếu*. Đó là một tham số bạn có thể đưa vào khi xây dựng một `DataLoader`, mặc định đây là một hàm sẽ chỉ chuyển đổi các mẫu của bạn thành các tensors PyTorch và nối chúng (đệ quy nếu các phần tử của bạn là list, tuple hoặc dict). Điều này sẽ không thể xảy ra trong trường hợp của chúng ta vì tất cả các đầu vào ta có sẽ không có cùng kích thước. Chúng ta đã cố tình hoãn việc bổ sung đệm, để chỉ áp dụng nó khi cần thiết trên mỗi lô và tránh để các đầu vào quá dài với nhiều đệm. Điều này sẽ đẩy nhanh quá trình huấn luyện lên một chút, nhưng lưu ý rằng nếu bạn đang huấn luyện trên TPU thì nó có thể gây ra vấn đề - TPU thích các hình dạng cố định, ngay cả khi điều đó yêu cầu thêm đệm. + +{:else} + +Hàm chịu trách nhiệm tập hợp các mẫu lại với nhau trong một lô được gọi là *collate function* hay *hàm đối chiếu*. Đó là một tham số bạn có thể đưa vào khi xây dựng một `DataLoader`, mặc định đây là một hàm sẽ chỉ chuyển đổi các mẫu của bạn thành các tensors PyTorch và nối chúng (đệ quy nếu các phần tử của bạn là list, tuple hoặc dict). Điều này sẽ không thể xảy ra trong trường hợp của chúng ta vì tất cả các đầu vào ta có sẽ không có cùng kích thước. Chúng ta đã cố tình hoãn việc bổ sung đệm, để chỉ áp dụng nó khi cần thiết trên mỗi lô và tránh để các đầu vào quá dài với nhiều đệm. Điều này sẽ đẩy nhanh quá trình huấn luyện lên một chút, nhưng lưu ý rằng nếu bạn đang huấn luyện trên TPU thì nó có thể gây ra vấn đề - TPU thích các hình dạng cố định, ngay cả khi điều đó yêu cầu thêm đệm. + +{/if} + +Để thực hiện điều này trong thực tế, chúng ta phải định nghĩa một hàm đối chiếu sẽ áp dụng đúng số lượng đệm cho các mục của tập dữ liệu mà chúng ta muốn gộp hàng loạt lại với nhau. May mắn thay, thư viện 🤗 Transformers cung cấp cho chúng ta một chức năng như vậy thông qua `DataCollatorWithPadding`. Cần có trình tokenize khi bạn khởi tạo nó (để biết cần sử dụng token đệm nào và liệu mô hình mong đợi đệm ở bên trái hay bên phải của các đầu vào) và sẽ thực hiện mọi thứ bạn cần: + +{#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} + +Để kiểm tra món mới này, chúng ta hãy lấy một vài mẫu từ tập huấn luyện mà chúng ta muốn ghép lại với nhau. Ở đây, chúng ta xóa các cột `idx`, `sentence1`, và `sentence2` vì chúng không cần thiết và chứa các chuỗi (và chúng ta không thể tạo tensor bằng chuỗi) và xem độ dài của mỗi mục trong lô: + +```py +samples = tokenized_datasets["train"][:8] +samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]} +[len(x) for x in samples["input_ids"]] +``` + +```python out +[50, 59, 47, 67, 59, 50, 62, 32] +``` + +Không có gì ngạc nhiên, ta nhận được các mẫu có độ dài khác nhau, từ 32 đến 67. Đệm động có nghĩa là tất cả các mẫu trong lô này phải được đệm đến chiều dài 67, chiều dài tối đa bên trong lô. Nếu không có đệm động, tất cả các mẫu sẽ phải được đệm đến độ dài tối đa trong toàn bộ tập dữ liệu hoặc độ dài tối đa mà mô hình có thể chấp nhận. Hãy kiểm tra kỹ xem `data_collator` của chúng ta có tự động đệm lô đúng cách hay không: + +```py +batch = data_collator(samples) +{k: v.shape for k, v in batch.items()} +``` + +{#if fw === 'tf'} + +```python out +{'attention_mask': TensorShape([8, 67]), + 'input_ids': TensorShape([8, 67]), + 'token_type_ids': TensorShape([8, 67]), + 'labels': TensorShape([8])} +``` + +{:else} + +```python out +{'attention_mask': torch.Size([8, 67]), + 'input_ids': torch.Size([8, 67]), + 'token_type_ids': torch.Size([8, 67]), + 'labels': torch.Size([8])} +``` + +Trông khá ổn! Giờ ta đã chuyển từ văn bản thô sang các lô mà mô hình có thể xử lý, và ta đã sẵn sàng tinh chỉnh nó! + +{/if} + + + +✏️ **Thử nghiệm thôi!** Sao chép tiền xử lý trên tập dữ liệu GLUE SST-2. Nó hơi khác một chút vì nó bao gồm các câu đơn thay vì các cặp, nhưng phần còn lại của những gì ta đã làm sẽ tương tự nhau. Với một thử thách khó hơn, hãy cố gắng viết một hàm tiền xử lý hoạt động trên bất kỳ tác vụ GLUE nào. + + + +{#if fw === 'tf'} + +Bây giờ chúng ta đã có bộ dữ liệu và bộ đối chiếu dữ liệu, ta cần phải kết hợp chúng lại với nhau. Chúng ta có thể tải các lô và đối chiếu theo cách thủ công, nhưng cách này rất tốn công sức và có lẽ cũng không hiệu quả lắm. Thay vào đó, có một phương pháp đơn giản cung cấp giải pháp hiệu quả cho vấn đề này: `to_tf_dataset()`. Nso được bao một `tf.data.Dataset` xung quanh tập dữ liệu của bạn, với một chức năng đối chiếu tùy chọn. `tf.data.Dataset` là một định dạng TensorFlow gốc mà Keras có thể sử dụng cho `model.fit()`, vì vậy phương pháp này ngay lập tức chuyển đổi một 🤗 Dataset sang một định dạng sẵn sàng để huấn luyện. Hãy xem nó hoạt động với tập dữ liệu của chúng tôi! + +```py +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, +) +``` + +Và nó đó! Chúng ta có thể chuyển những bộ dữ liệu đó sang bài giảng tiếp theo, nơi việc huấn luyện sẽ trở nên đơn giản một cách dễ chịu sau tất cả những công việc khó khăn của việc xử lý trước dữ liệu. + +{/if} diff --git a/chapters/vi/chapter3/3.mdx b/chapters/vi/chapter3/3.mdx new file mode 100644 index 000000000..980165a91 --- /dev/null +++ b/chapters/vi/chapter3/3.mdx @@ -0,0 +1,170 @@ + + +# Tinh chỉnh một mô hình với Trainer API + + + + + +🤗 Transformers cung cấp lớp `Trainer` để giúp bạn tinh chỉnh bất kỳ mô hình huấn luyện trước nào mà nó cung cấp trên tập dữ liệu của bạn. Khi bạn đã hoàn thành tất cả công việc tiền xử lý dữ liệu trong phần cuối cùng, bạn chỉ còn một vài bước để định nghĩa `Trainer`. Phần khó nhất có thể là chuẩn bị môi trường để chạy `Trainer.train()`, vì nó sẽ chạy rất chậm trên CPU. Nếu bạn chưa thiết lập GPU, bạn có thể có quyền truy cập vào GPU hoặc TPU miễn phí trên [Google Colab](https://colab.research.google.com/). + +Các ví dụ mã bên dưới giả sử bạn đã thực hiện các ví dụ trong phần trước. Dưới đây là một bản tóm tắt ngắn tóm tắt lại những gì bạn cần: + +```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) +``` + +### Huấn luyện + +Bước đầu tiên trước khi chúng ta có thể định nghĩa `Trainer` của mình là định nghĩa một lớp `TrainingArguments` sẽ chứa tất cả các siêu tham số mà `Trainer` sẽ sử dụng để huấn luyện và đánh giá. Tham số duy nhất bạn phải cung cấp là một thư mục nơi mô hình được huấn luyện sẽ được lưu, cũng như các checkpoint đi kèm. Đối với tất cả phần còn lại, bạn có thể để mặc định, nó sẽ hoạt động khá tốt với tinh chỉnh cơ bản. + +```py +from transformers import TrainingArguments + +training_args = TrainingArguments("test-trainer") +``` + + + +💡 Nếu bạn muốn tự động tải mô hình của mình lên Hub trong quá trình huấn luyện, hãy chuyển sang phần `push_to_hub=True` trong phần `TrainingArguments`. Chúng ta sẽ tìm hiểu thêm về điều này trong [Chương 4](/course/chapter4/3) + + + +Bước thứ hai là xác định mô hình của chúng ta. Như trong [chương trước](/course/chapter2), chúng ta sẽ sử dụng lớp `AutoModelForSequenceClassification`, với hai nhãn: + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +Bạn sẽ nhận thấy rằng không như trong [Chương 2](/course/chapter2), bạn nhận được một cảnh báo sau khi khởi tạo mô hình được huấn luyện trước này. Đây là do BERT chưa được huấn luyện trước về phân loại các cặp câu, vì vậy phần đầu của mô hình được huấn luyện trước đã bị loại bỏ và phần đầu mới phù hợp để phân loại chuỗi đã được chèn vào thay thế. Các cảnh báo chỉ ra rằng một số trọng số đã không được sử dụng (những trọng số tương ứng với đầu huấn luyện trước bị rụng) và một số trọng số khác khác được khởi tạo ngẫu nhiên (những trọng số dành cho đầu mới). Nó kết thúc bằng cách khuyến khích bạn huấn luyện mô hình, đó chính xác là những gì chúng ta sẽ làm bây giờ. + +Khi chúng ta có mô hình của mình, chúng ta có thể xác định một `Trainer` bằng cách truyền vào tất cả các đối tượng được xây dựng từ trước đến nay - `model`, `training_args`, tập huấn luyện và kiểm định,`data_collator` và `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, +) +``` + +Lưu ý rằng khi bạn truyền `tokenizer` như chúng ta đã làm ở đây, mặc định `data_collator` được sử dụng bởi `Trainer` sẽ là `DataCollatorWithPadding` như đã định nghĩa trước đó, vì vậy bạn có thể bỏ qua dòng `data_collator = data_collator` trong lệnh gọi này. Điều quan trọng là phải cho bạn thấy phần này của quá trình trong phần 2! + +Để tinh chỉnh mô hình trên tập dữ liệu, chúng ta chỉ cần gọi phương thức `train()` của `Trainer`: + +```py +trainer.train() +``` + +Thao tác này sẽ bắt đầu quá trình tinh chỉnh (sẽ mất vài phút trên GPU) và báo cáo lỗi đào tạo sau mỗi 500 bước. Tuy nhiên, nó sẽ không cho bạn biết mô hình của bạn đang hoạt động tốt (hoặc tồi tệ như thế nào). Điều này là do: + +1. Chúng ta đã không yêu cầu `Trainer` đánh giá trong quá trình huấn luyện bằng cách cài đặt `eval_strategy` thành `"steps"` (đánh giá mọi `eval_steps`) hoặc `"epoch"` (đánh giá vào cuối mỗi epoch). +2. Chúng ta đã không cung cấp cho `Trainer` một hàm `compute_metrics()` để tính toán chỉ số trong quá trình đánh giá nói trên (nếu không, đánh giá sẽ chỉ in ra lỗ, đây không phải là một chỉ số trực quan cho lắm). + +### Đánh giá + +Hãy xem cách chúng ta có thể xây dựng một hàm `compute_metrics()` hữu ích và sử dụng nó trong lần huấn luyện tiếp theo. Hàm phải nhận một đối tượng `EvalPrediction` (là một tuple được đặt tên với trường `predictions` và trường `label_ids`) và sẽ trả về một chuỗi ánh xạ từ thành số thực (các chuỗi là tên của các chỉ số được trả về và các giá trị của chúng ép về kiểu số thực). Để nhận được dự đoán từ mô hình, chúng ta có thể sử dụng lệnh `Trainer.predict()`: + +```py +predictions = trainer.predict(tokenized_datasets["validation"]) +print(predictions.predictions.shape, predictions.label_ids.shape) +``` + +```python out +(408, 2) (408,) +``` +Đầu ra của phương thức `predict()` là một tuple có tên khác với ba trường: `predictions`, `label_ids`, và `metrics`. Trường `metrics` sẽ chỉ chứa sự mất mát trên tập dữ liệu đã truyền vào, cũng như một số chỉ số thời gian (tổng cộng và trung bình mất bao lâu để dự đoán). Sau khi chúng ta hoàn thành hàm `compute_metrics()` và truyền nó vào `Trainer`, trường đó cũng sẽ chứa các chỉ số được trả về bởi` compute_metrics()`. + +Như bạn có thể thấy, `predictions` là một mảng hai chiều có hình dạng 408 x 2 (408 là số phần tử trong tập dữ liệu ta đã sử dụng). Đó là các logit cho từng phần tử của tập dữ liệu mà chúng ta đã truyền vào cho`predict()` ( như bạn đã thấy trong [chương trước](/course/chapter2), tất cả các mô hình Transformer đều trả về logit). Để chuyển đổi chúng thành các dự đoán mà chúng ta có thể so sánh với các nhãn của mình, chúng ta cần lấy chỉ số có giá trị lớn nhất trên trục thứ hai: + +```py +import numpy as np + +preds = np.argmax(predictions.predictions, axis=-1) +``` + +Giờ chúng ta có thể so sánh các `preds` đó với các nhãn. Để xây dựng hàm `compute_metric()`, chúng ta sẽ dựa vào các chỉ số từ thư viện 🤗 [Đánh giá](https://github.com/huggingface/evaluate/). Chúng ta có thể tải các chỉ số được liên kết với tập dữ liệu MRPC dễ dàng như khi chúng ta tải tập dữ liệu, lần này là với hàm `evaluate.load()`. Đối tượng được trả về có phương thức `compute()` mà chúng ta có thể sử dụng để thực hiện tính toán số liệu: + +```py +import evaluate + +metric = evaluate.load("glue", "mrpc") +metric.compute(predictions=preds, references=predictions.label_ids) +``` + +```python out +{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542} +``` + +Kết quả chính xác bạn nhận được có thể khác nhau, vì việc khởi tạo ngẫu nhiên phần đầu mô hình có thể thay đổi các chỉ số mà nó đạt được. Ở đây, chúng ta có thể thấy mô hình có độ chính xác 85.78% trên tập kiểm định và điểm F1 là 89.97. Đó là hai chỉ số được sử dụng để đánh giá kết quả trên tập dữ liệu MRPC theo điểm chuẩn GLUE. Bảng trong [bài báo BERT](https://arxiv.org/pdf/1810.04805.pdf) báo cáo điểm F1 là 88.9 cho mô hình cơ sở. Đó là mô hình `không phân biệt` viết hoa viết thường trong khi chúng ta hiện đang sử dụng mô hình `có phân biệt`, điều này giải thích kết quả tốt hơn. + +Kết hợp mọi thứ lại với nhau, chúng ta nhận được hàm `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) +``` + +Và để xem nó được sử dụng trong thực tiễn để báo cáo các chỉ số ở cuối mỗi epoch như thế nào, đây là cách chúng tôi định nghĩa một `Trainer` mới với hàm `compute_metrics()` này: + + +```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, +) +``` + +Lưu ý rằng chúng ta tạo một `TrainingArguments` mới với `eval_strategy` của nó được đặt thành `"epoch"` và một mô hình mới - nếu không, chúng ta sẽ tiếp tục huấn luyện mô hình ta đã huấn luyện. Để khởi chạy một đợt huấn luyện mới, chúng ta thực hiện: + +``` +trainer.train() +``` + +Lần này, nó sẽ báo cáo thông số mất mát kiểm định và chỉ số ở cuối mỗi epoch ên cạnh thông số mất mát trên tập huấn luyện. Một lần nữa, độ chính xác tuyệt đối/điểm F1 mà bạn đạt được có thể hơi khác so với những gì chúng tôi tìm thấy, do việc khởi tạo đầu ngẫu nhiên của mô hình, nhưng nó phải ở trong cùng một khoảng. + +`Trainer` sẽ hoạt động hiệu quả trên nhiều GPU hoặc TPU và cung cấp nhiều tùy chọn, chẳng hạn như huấn luyện về độ chính xác hỗn hợp (sử dụng `fp16=True` trong tham số huấn luyện của bạn). Chúng ta sẽ xem xét mọi thứ mà nó hỗ trợ trong Chương 10. + +Phần này kết thúc phần giới thiệu về cách tinh chỉnh bằng API `Trainer`. Một ví dụ về việc thực hiện điều này đối với hầu hết các tác vụ NLP phổ biến sẽ được đưa ra trong [Chương 7](/course/chapter7), nhưng ở thời điểm này chúng ta hãy xem cách thực hiện điều tương tự trong PyTorch thuần túy. + + + +✏️ **Thử nghiệm thôi!** Tinh chỉnh mô hình trên tập dữ liệu GLUE SST-2, sử dụng quá trình xử lý dữ liệu bạn đã thực hiện trong phần 2. + + diff --git a/chapters/vi/chapter3/3_tf.mdx b/chapters/vi/chapter3/3_tf.mdx new file mode 100644 index 000000000..a451985d1 --- /dev/null +++ b/chapters/vi/chapter3/3_tf.mdx @@ -0,0 +1,189 @@ + + +# Tinh chỉnh một mô hình với Keras + + + +Khi bạn đã hoàn thành tất cả công việc tiền xử lý dữ liệu trong phần trước, bạn chỉ còn một vài bước nữa để huấn luyện mô hình. Tuy nhiên, lưu ý rằng lệnh `model.fit()` sẽ chạy rất chậm trên CPU. Nếu bạn chưa thiết lập GPU, bạn có thể có quyền truy cập vào GPU hoặc TPU miễn phí trên [Google Colab](https://colab.research.google.com/). + +Các đoạn mã ví dụ bên dưới giả sử bạn đã thực thi các ví dụ trong phần trước. Dưới đây là một bản tóm tắt ngắn gọn tóm tắt lại những gì bạn cần: + +```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, +) +``` + +### Huấn luyện + +Các mô hình TensorFlow nhập từ 🤗 Transformers vốn là các mô hình Keras. Đây là phần giới thiệu ngắn về Keras. + + + +Điều đó có nghĩa là một khi chúng tôi có dữ liệu riêng mình, chúng ta chỉ cần thao tác ít bước nữa thôi để bắt đầu huấn luyện. + + + +Như trong [chương trước](/course/chapter2), chúng ta sẽ sử dụng lớp `TFAutoModelForSequenceClassification`, với hai nhãn: + +```py +from transformers import TFAutoModelForSequenceClassification + +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +Bạn sẽ nhận thấy rằng không như trong [Chương 2](/course/chapter2), bạn nhận được một cảnh báo sau khi khởi tạo mô hình được huấn luyện trước này. Đây là do BERT chưa được huấn luyện trước về phân loại các cặp câu, vì vậy phần đầu của mô hình được huấn luyện trước đã bị loại bỏ và phần đầu mới phù hợp để phân loại chuỗi đã được chèn vào thay thế. Các cảnh báo chỉ ra rằng một số trọng số đã không được sử dụng (những trọng số tương ứng với đầu huấn luyện trước bị rụng) và một số trọng số khác khác được khởi tạo ngẫu nhiên (những trọng số dành cho đầu mới). Nó kết thúc bằng cách khuyến khích bạn huấn luyện mô hình, đó chính xác là những gì chúng ta sẽ làm bây giờ. + +Để tinh chỉnh mô hình trên tập dữ liệu của mình, chúng ta chỉ cần `compile()` mô hình và sau đó chuyển dữ liệu của ta đến phương thức `fit()`. Thao tác này sẽ bắt đầu quá trình tinh chỉnh (sẽ mất vài phút trên GPU) và báo cáo sự mất mát ở tập huấn luyện khi nó diễn ra, cộng với mất mát ở tập kiểm định ở cuối mỗi epoch. + + + +Lưu ý rằng 🤗 các mô hình Transformers có một khả năng đặc biệt mà hầu hết các mô hình Keras không có - chúng có thể tự động sử dụng một lượng mất mát thích hợp mà chúng tính toán bên trong. Chúng sẽ sử dụng sự mất mát này theo mặc định nếu bạn không đặt tham số mất mát bên trong `compile()`. Lưu ý rằng để sử dụng hàm mất mát trong nội bộ, bạn sẽ cần truyền các nhãn của mình như một phần của đầu vào, không phải dưới dạng nhãn riêng biệt, đây là cách thông thường để sử dụng nhãn với các mô hình Keras. Bạn sẽ thấy các ví dụ về điều này trong Phần 2 của khóa học, trong đó việc xác định hàm mất mát chính xác có thể khó khăn. Tuy nhiên, đối với phân loại chuỗi, một hàm mất mát Keras tiêu chuẩn hoạt động khá tốt, vì vậy đó là những gì chúng ta sẽ sử dụng ở đây. + + + +```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, +) +``` + + + +Lưu ý một lỗi rất phổ biến ở đây - bạn *có thể* chỉ cần truyền tên của hàm mất mát dưới dạng chuỗi cho Keras, nhưng theo mặc định, Keras sẽ cho rằng bạn đã áp dụng softmax cho đầu ra của mình. Tuy nhiên, nhiều mô hình xuất ra các giá trị ngay trước khi áp dụng softmax, còn được gọi là *logit*. Chúng ta cần nói với hàm mất mát rằng đó là những gì mô hình của chúng ta làm và cách duy nhất để làm điều đó là gọi nó trực tiếp, thay vì đặt tên bằng một chuỗi. + + + +### Cải thiện hiệu suất huấn luyện + + + +Nếu bạn thử đoạn mã trên, nó chắc chắn chạy, nhưng bạn sẽ thấy rằng hàm mất mát chỉ giảm từ từ hoặc không thường xuyên. Nguyên nhân chính là do *learning rate* hay *tốc độ học*. Với hàm mất mát, khi ta truyền cho Keras tên của trình tối ưu hóa dưới dạng một chuỗi, Keras sẽ khởi tạo trình tối ưu hóa đó với các giá trị mặc định cho tất cả các tham số, bao gồm cả tốc độ học. Tuy nhiên, từ kinh nghiệm lâu năm, chúng tôi biết +rằng các mô hình Transformer được hưởng lợi từ tốc độ học thấp hơn nhiều so với tỷ lệ mặc định cho Adam, là 1e-3, cũng được viết bằng 10 lũy thừa của -3, hoặc 0,001. 5e-5 (0,00005), thấp hơn khoảng hai mươi lần, là một điểm khởi đầu tốt hơn nhiều. + +Ngoài việc giảm tốc độ học, chúng tôi có một mẹo thứ hai: Ta có thể từ từ giảm tốc độ học trong quá trình huấn luyện. Trong tài liệu, đôi khi bạn sẽ thấy điều này được gọi là *phân rã* hoặc *ủ* tốc độ học. Ở Keras, cách tốt nhất để làm điều này là sử dụng *learning rate scheduler* hay *công cụ lập lịch trình tốc độ học*. Một cái hay để sử dụng là `PolynomialDecay` - với cài đặt mặc định, nó chỉ đơn giản là giảm độ tuyến tính tốc độ học từ giá trị ban đầu đến giá trị cuối cùng trong quá trình huấn luyện, đó chính xác là những gì ta muốn. Tuy nhiên, để sử dụng bộ lập lịch một cách chính xác, chúng ta cần cho nó biết thời gian huấn luyện sẽ kéo dài. Chúng ta tính giá trị đó dưới dạng `num_train_steps` như sau. + +```py +from tensorflow.keras.optimizers.schedules import PolynomialDecay + +batch_size = 8 +num_epochs = 3 +# Số bước huấn luyện là số lượng mẫu trong tập dữ liệu, chia cho kích thước lô sau đó nhân +# với tổng số epoch. Lưu ý rằng tf_train_dataset ở đây là tf.data.Dataset theo lô, +# không phải là Hugging Face Dataset, vì vậy len() của nó đã là 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) +``` + + + +Thư viện 🤗 Transformers cũng có một hàm `create_optimizer()` sẽ tạo ra một trình tối ưu hóa `AdamW` với sự giảm tốc độ học. Đây là một phím tắt thuận tiện mà bạn sẽ thấy chi tiết trong các phần sau của khóa học. + + + +Bây giờ chúng ta đã có trình tối ưu hóa hoàn toàn mới và ta có thể thử huấn luyện với nó. Đầu tiên, hãy tải lại mô hình, để đặt lại các thay đổi đối với trọng số từ lần chạy huấn luyện mà chúng ta vừa thực hiện và sau đó ta có thể biên dịch nó bằng trình tối ưu hóa mới: + +```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"]) +``` + +Giờ ta sẽ fit lại 1 lần nữa: + +```py +model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) +``` + + + +💡 Nếu bạn muốn tự động tải mô hình của mình lên Hub trong quá trình huấn luyện, bạn có thể truyền `PushToHubCallback` vào trong phương thức `model.fit()`. Chúng ta sẽ tìm hiểu thêm về điều này trong [Chương 4](/course/chapter4/3) + + + +### Các dự đoán của mô hình + + + +Việc huấn luyện và theo dõi sự mất mát giảm xuống đều rất tốt, nhưng nếu chúng ta muốn thực sự có được kết quả đầu ra từ mô hình được huấn luyện, để tính toán một số chỉ số hoặc sử dụng mô hình đó trong sản xuất thì sao? Để làm điều đó, chúng ta chỉ có thể sử dụng phương thức `predict()`. Điều này sẽ trả về *logit* từ đầu ra của mô hình, một cho mỗi lớp. + +```py +preds = model.predict(tf_validation_dataset)["logits"] +``` + +Chúng ta có thể chuyển đổi các logit này thành các dự đoán lớp của mô hình bằng cách sử dụng `argmax` để tìm logit cao nhất, tương ứng với lớp có nhiều khả năng nhất: + +```py +class_preds = np.argmax(preds, axis=1) +print(preds.shape, class_preds.shape) +``` + +```python out +(408, 2) (408,) +``` + +Bây giờ, hãy sử dụng các `preds` đó để tính toán một số chỉ số! Chúng ta có thể tải các chỉ số được liên kết với tập dữ liệu MRPC dễ dàng như khi ta tải tập dữ liệu, lần này là với hàm `eval.load())`. Đối tượng được trả về có phương thức `compute()` mà chúng ta có thể sử dụng để thực hiện phép tính số liệu: + +```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} +``` + +Kết quả chính xác bạn nhận được có thể khác nhau, vì việc khởi tạo ngẫu nhiên phần đầu mô hình có thể thay đổi các chỉ số mà nó đạt được. Ở đây, chúng ta có thể thấy mô hình có độ chính xác 85.78% trên tập kiểm định và điểm F1 là 89.97. Đó là hai chỉ số được sử dụng để đánh giá kết quả trên tập dữ liệu MRPC theo điểm chuẩn GLUE. Bảng trong [bài báo BERT](https://arxiv.org/pdf/1810.04805.pdf) báo cáo điểm F1 là 88.9 cho mô hình cơ sở. Đó là mô hình `không phân biệt` viết hoa viết thường trong khi chúng ta hiện đang sử dụng mô hình `có phân biệt`, điều này giải thích kết quả tốt hơn. + +Phần này kết thúc phần giới thiệu về cách tinh chỉnh bằng Keras API. Một ví dụ về cách làm này đối với hầu hết các tác vụ NLP phổ biến sẽ được đưa ra trong [Chương 7](/course/chapter7). Nếu bạn muốn trau dồi kỹ năng của mình trên API Keras, hãy cố gắng tinh chỉnh một mô hình trên tập dữ liệu GLUE SST-2, bằng cách sử dụng xử lý dữ liệu bạn đã thực hiện trong phần 2. diff --git a/chapters/vi/chapter3/4.mdx b/chapters/vi/chapter3/4.mdx new file mode 100644 index 000000000..24e47a91f --- /dev/null +++ b/chapters/vi/chapter3/4.mdx @@ -0,0 +1,358 @@ +# Bản huấn luyện hoàn chỉnh + + + + + +Bây giờ chúng ta sẽ xem cách đạt được kết quả tương tự như chúng ta đã làm trong phần trước mà không cần sử dụng lớp `Trainer`. Một lần nữa, chúng tôi giả sử bạn đã thực hiện bước xử lý dữ liệu trong phần 2. Dưới đây là một bản tóm tắt ngắn bao gồm mọi thứ bạn cần: + +```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) +``` + +### Chuẩn bị cho huấn luyện + +Trước khi thực sự viết vòng lặp huấn luyện của mình, chúng ta sẽ cần xác định một vài đối tượng. Đầu tiên là bộ dữ liệu dataloader mà chúng tôi sẽ sử dụng để lặp qua các lô. Nhưng trước khi chúng ta có thể xác định các bộ dữ liệu đó, chúng ta cần áp dụng một chút hậu xử lý cho `tokenized_datasets` của mình, để xử lý một số thứ mà `Trainer` đã làm cho chúng ta một cách tự động. Cụ thể, chúng ta cần: + +- Loại bỏ các cột tương ứng với các giá trị mà mô hình không mong đợi (như cột `sentence1` và `sentence2`). +- Đổi tên cột `label` thành `labels` (vì mô hình mong đợi đối số được đặt tên là `labels`). +- Đặt định dạng của bộ dữ liệu để chúng trả về các tensor PyTorch thay vì danh sách. + +`Tokenized_datasets` của chúng ta có phương thức cho mỗi bước đó: + +```py +tokenized_datasets = tokenized_datasets.remove_columns(["sentence1", "sentence2", "idx"]) +tokenized_datasets = tokenized_datasets.rename_column("label", "labels") +tokenized_datasets.set_format("torch") +tokenized_datasets["train"].column_names +``` + +Sau đó, chúng ta có thể kiểm tra xem kết quả có chỉ có các cột mà mô hình của chúng ta sẽ chấp nhận không: + +```python +["attention_mask", "input_ids", "labels", "token_type_ids"] +``` + +Xong rồi, chúng ta có thể dễ dàng định nghĩa các bộ dữ liệu của mình: + +```py +from torch.utils.data import DataLoader + +train_dataloader = DataLoader( + tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator +) +eval_dataloader = DataLoader( + tokenized_datasets["validation"], batch_size=8, collate_fn=data_collator +) +``` + +Để nhanh chóng kiểm tra không có sai sót trong quá trình xử lý dữ liệu, chúng ta có thể kiểm tra một lô như sau: + +```py +for batch in train_dataloader: + break +{k: v.shape for k, v in batch.items()} +``` + +```python out +{'attention_mask': torch.Size([8, 65]), + 'input_ids': torch.Size([8, 65]), + 'labels': torch.Size([8]), + 'token_type_ids': torch.Size([8, 65])} +``` + +Lưu ý rằng các hình dạng thực tế có thể sẽ hơi khác đối với bạn vì chúng tôi đặt `shuffle = True` cho dataloader huấn luyện và chúng tôi đang đệm đến độ dài tối đa bên trong lô. + +Bây giờ chúng ta đã hoàn thành việc xử lý trước dữ liệu (một mục tiêu thỏa mãn nhưng khó nắm bắt đối với bất kỳ người thực hành ML nào), hãy chuyển sang mô hình thôi. Chúng ta khởi tạo nó chính xác như đã làm trong phần trước: + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +To make sure that everything will go smoothly during training, we pass our batch to this model: + +```py +outputs = model(**batch) +print(outputs.loss, outputs.logits.shape) +``` + +```python out +tensor(0.5441, grad_fn=) torch.Size([8, 2]) +``` + +Tất cả các mô hình 🤗 Transformers sẽ trả về lượng mất mát khi `labels` được cung cấp và chúng ta cũng nhận được logit (hai cho mỗi đầu vào trong lô, do đó, một tensor có kích thước 8 x 2). + +Chúng ta gần như đã sẵn sàng để viết vòng lặp huấn luyện của mình! Chúng ta chỉ thiếu hai thứ: một trình tối ưu hóa và một công cụ lập lịch tốc độ học tập. Vì chúng ta đang cố gắng tái tạo những gì mà `Trainer` đã làm bằng tay, nên ta sẽ sử dụng các giá trị mặc định tương tự. Trình tối ưu hóa được sử dụng bởi `Trainer` là `AdamW`, tương tự như Adam, nhưng có một bước ngoặt để điều chỉnh phân rã trọng số (xem ["Decoupled Weight Decay Regularization"](https://arxiv.org/abs/1711.05101) của Ilya Loshchilov và Frank Hutter): + +```py +from transformers import AdamW + +optimizer = AdamW(model.parameters(), lr=5e-5) +``` + +Cuối cùng, bộ lập lịch tốc độ học được sử dụng theo mặc định chỉ là một phân rã tuyến tính từ giá trị lớn nhất (5e-5) xuống 0. Để xác định đúng, chúng ta cần biết số bước huấn luyện sẽ thực hiện, đó là số epoch muốn chạy nhân với số lô huấn luyện (là độ dài của bộ dữ liệu huấn luyện). `Trainer` sử dụng ba epoch theo mặc định, vì vậy chúng tôi sẽ tuân theo điều đó: + +```py +from transformers import get_scheduler + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) +print(num_training_steps) +``` + +```python out +1377 +``` + +### Vòng lặp huấn luyện + +Một điều cuối cùng: chúng ta sẽ muốn sử dụng GPU nếu có quyền truy cập vào một GPU (trên CPU, quá trình huấn luyện có thể mất vài giờ thay vì vài phút). Để làm điều này, chúng ta xác định một `device`, ta sẽ đặt mô hình và các lô của ta trên đó: + +```py +import torch + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) +device +``` + +```python out +device(type='cuda') +``` + +Giờ thì ta đã sẵn sàng để huấn luyện rồi! Để biết khi nào quá trình huấn luyện sẽ kết thúc, ta thêm thanh tiến trình qua số bước huấn luyện, sử dụng thư viện `tqdm`: + +```py +from tqdm.auto import tqdm + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Bạn có thể thấy rằng cốt lõi của vòng lặp huấn luyện trông rất giống như trong phần giới thiệu. Chúng ta đã không yêu cầu bất kỳ báo cáo nào, vì vậy vòng huấn luyện này sẽ không cho ta biết bất kỳ điều gì về cái giá của mô hình. Chúng ta cần thêm một vòng lặp đánh giá cho điều đó. + +### Vòng lặp đánh giá + +Như đã làm trước đó, chúng ta sẽ sử dụng một chỉ số được cung cấp bởi thư viện 🤗 Evaluate. Chúng ta đã thấy phương thức `metric.compute()`, các chỉ số thực sự có thể tích lũy các lô cho ta khi xem qua vòng dự đoán với phương thức `add_batch()`. Khi ta đã tích lũy tất cả các lô, chúng ta có thể nhận được kết quả cuối cùng với `metric.compute()`. Dưới đây là cách thực hiện tất cả những điều này trong một vòng lặp đánh giá: + +```py +import evaluate + +metric = evaluate.load("glue", "mrpc") +model.eval() +for batch in eval_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + with torch.no_grad(): + outputs = model(**batch) + + logits = outputs.logits + predictions = torch.argmax(logits, dim=-1) + metric.add_batch(predictions=predictions, references=batch["labels"]) + +metric.compute() +``` + +```python out +{'accuracy': 0.8431372549019608, 'f1': 0.8907849829351535} +``` + +Một lần nữa, kết quả của bạn sẽ hơi khác một chút vì sự ngẫu nhiên trong quá trình khởi tạo đầu mô hình và xáo trộn dữ liệu, nhưng chúng phải ở trong cùng một khoảng. + + + +✏️ **Thử nghiệm thôi!** Sửa đổi vòng lặp huấn luyện trước đó để tinh chỉnh mô hình của bạn trên tập dữ liệu SST-2. + + + +### Tăng cường trí thông minh của vòng huấn luyện với 🤗 Accelerate + + + +Vòng lặp huấn luyện mà ta đã định nghĩa trước đó hoạt động tốt trên một CPU hoặc GPU. Nhưng bằng cách sử dụng thư viện [🤗 Accelerate](https://github.com/huggingface/accelerate), chỉ với một vài điều chỉnh, chúng ta có thể huấn luyện phân tán trên nhiều GPU hoặc TPU. Bắt đầu từ việc tạo bộ dữ liệu huấn luyện và kiểm định, đây là vòng lặp huấn luyện thủ công thực thi: + +```py +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Và đây là một số thay đổi: + +```diff ++ from accelerate import Accelerator + from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + ++ accelerator = Accelerator() + + model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) + optimizer = AdamW(model.parameters(), lr=3e-5) + +- device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +- model.to(device) + ++ train_dataloader, eval_dataloader, model, optimizer = accelerator.prepare( ++ train_dataloader, eval_dataloader, model, optimizer ++ ) + + num_epochs = 3 + num_training_steps = num_epochs * len(train_dataloader) + lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps + ) + + progress_bar = tqdm(range(num_training_steps)) + + model.train() + for epoch in range(num_epochs): + for batch in train_dataloader: +- batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss +- loss.backward() ++ accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Dòng đầu tiên cần thêm là dòng nhập. Dòng thứ hai khởi tạo một đối tượng `Accelerator` sẽ xem xét môi trường và khởi tạo thiết lập phân tán thích hợp. 🤗 Accelerate xử lý vị trí đặt thiết bị cho bạn, vì vậy bạn có thể xóa các dòng đặt mô hình trên thiết bị (hoặc, nếu bạn thích, hãy thay đổi chúng để sử dụng `accelerator.device` thay vì `device`). + +Sau đó, phần lớn công việc chính được thực hiện trong dòng gửi bộ lưu dữ liệu, mô hình và trình tối ưu hóa đến `accelerator.prepare()`. Thao tác này sẽ bọc các đối tượng đó trong hộp chứa thích hợp để đảm bảo việc huấn luyện được phân phối hoạt động như dự định. Các thay đổi còn lại cần thực hiện là loại bỏ dòng đặt lô trên `device` (một lần nữa, nếu bạn muốn giữ lại điều này, bạn chỉ cần thay đổi nó thành sử dụng `accelerator.device`) và thay thế `loss.backward()` bằng `accelerator.backward(loss)`. + + +⚠️ Để hưởng lợi từ việc tăng tốc độ do Cloud TPUs cung cấp, chúng tôi khuyên bạn nên đệm các mẫu của mình theo độ dài cố định bằng các tham số `padding="max_length"` và `max_length` của tokenizer. + + +Nếu bạn muốn sao chép và dán nó để mày mò, đây là giao diện của vòng huấn luyện hoàn chỉnh với 🤗 Accelerate: + +```py +from accelerate import Accelerator +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +accelerator = Accelerator() + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +train_dl, eval_dl, model, optimizer = accelerator.prepare( + train_dataloader, eval_dataloader, model, optimizer +) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dl) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dl: + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Đặt điều này trong `train.py` sẽ làm cho tập lệnh đó có thể chạy được trên bất kỳ loại thiết lập phân tán nào. Để dùng thử trong thiết lập phân tán của bạn, hãy chạy lệnh: + +```bash +accelerate config +``` + +điều này sẽ nhắc bạn trả lời một số câu hỏi và trích xuất câu trả lời của bạn vào tệp cấu hình bởi lệnh sau: + +``` +accelerate launch train.py +``` + +và nó sẽ khởi chạy chương trình huấn luyện phân tán. + +Nếu bạn muốn thử điều này trong Notebook (ví dụ: để kiểm tra nó với TPU trên Colab), chỉ cần dán đoạn mã vào `training_function()` và chạy ô cuối cùng với: + +```python +from accelerate import notebook_launcher + +notebook_launcher(training_function) +``` + +Bạn có thể tìm thêm các ví dụ tại [🤗 Accelerate repo](https://github.com/huggingface/accelerate/tree/main/examples). diff --git a/chapters/vi/chapter3/5.mdx b/chapters/vi/chapter3/5.mdx new file mode 100644 index 000000000..d50018fda --- /dev/null +++ b/chapters/vi/chapter3/5.mdx @@ -0,0 +1,20 @@ + + +# Tỉnh chỉnh, thử xem! + +Trong hai chương đầu tiên, bạn đã học về các mô hình và tokenizer, và bây giờ bạn biết cách tinh chỉnh chúng cho dữ liệu của riêng bạn. Tóm lại, trong chương này bạn: + +{#if fw === 'pt'} +* Đã tìm hiểu về tập dữ liệu trong [Hub](https://huggingface.co/datasets) +* Đã học cách tải và tiền xử lý bộ dữ liệu, bao gồm cả việc sử dụng đệm động và trình đối chiếu +* Thực hiện tinh chỉnh và đánh giá mô hình của riêng bạn +* Đã thực hiện một vòng huấn luyện cấp thấp hơn +* Được sử dụng 🤗 Accelerate để dễ dàng điều chỉnh vòng lặp huấn luyện của bạn để nó hoạt động với nhiều GPU hoặc TPU + +{:else} +* Đã tìm hiểu về các bộ dữ liệu trong [Hub](https://huggingface.co/datasets) +* Đã học cách tải và tiền xử lý bộ dữ liệu +* Đã học cách tinh chỉnh và đánh giá một mô hình với Keras +* Đã triển khai các thước đo tùy chỉnh + +{/if} diff --git a/chapters/vi/chapter3/6.mdx b/chapters/vi/chapter3/6.mdx new file mode 100644 index 000000000..2a87303de --- /dev/null +++ b/chapters/vi/chapter3/6.mdx @@ -0,0 +1,296 @@ + + + + +# Đố vui cuối chương + +Kiểm tra những gì bạn đã học trong chương này! + +### 1. Tập dữ liệu `emotion` chứa các tin nhắn Twitter được gắn nhãn cảm xúc. Tìm kiếm nó trong [Hub](https://huggingface.co/datasets) và đọc thẻ tập dữ liệu. Cảm xúc nào trong số này không phải là một trong những cảm xúc cơ bản của nó? + + + +### 2. Tìm kiếm tập dữ liệu `ar_sarcasm` trong [Hub](https://huggingface.co/datasets). Nó hỗ trợ tác vụ nào? + +thẻ dữ liệu nha!" + }, + { + text: "Nhận dạng thực thể", + explain: "Không phải rồi - thử xem lại tại thẻ dữ liệu nha!" + }, + { + text: "Hỏi đáp", + explain: "Tiếc ghê, không chính xác rồi. Thử lại nha!" + } + ]} +/> + +### 3. Mô hình BERT mong đợi một cặp câu được xử lý như thế nào? + +[SEP] để phân cách hai câu, nhưng đây không phải thứ duy nhất!" + }, + { + text: "[CLS] Tokens_of_sentence_1 Tokens_of_sentence_2", + explain: "Cần token đặc biệt [CLS] ở đầu, nhưng đây không phải thứ duy nhất!" + }, + { + text: "[CLS] Tokens_of_sentence_1 [SEP] Tokens_of_sentence_2 [SEP]", + explain: "Chính xác!", + correct: true + }, + { + text: "[CLS] Tokens_of_sentence_1 [SEP] Tokens_of_sentence_2", + explain: "Cần token đặc biệt [CLS] ở đầu cũng như [SEP] để phân cách hai câu, nhưng đây không phải thứ duy nhất!" + } + ]} +/> + +{#if fw === 'pt'} +### 4. Lợi ích của phương thức `Dataset.map()` là gì? + + + +### 5. Đệm động nghĩa là sao? + + + +### 6. Mục đích của hàm đối chiếu là gì? + +DataCollatorWithPadding." + }, + { + text: "Nó tập hợp tất cả các mẫu lại trong một lô.", + explain: "Đúng! Bạn có thể truyền hàm đối chiếu như một tham số của DataLoader. Chúng tôi đã sử dụng hàm DataCollatorWithPadding, một hàm đệm tất cả các mục trong một lô để chúng giống nhau về chiều dài.", + correct: true + }, + { + text: "Nó tiền xử lý toàn bộ tập dữ liệu.", + explain: "Đó là một hàm tiền xử lý, không phải là một hàm đối chiếu." + }, + { + text: "Nó cắt bớt các chuỗi trong tập dữ liệu.", + explain: "Một hàm đối chiếu liên quan đến việc xử lý các lô riêng lẻ, không phải toàn bộ tập dữ liệu. Nếu bạn muốn cắt ngắn, bạn có thể sử dụng tham số truncate của tokenizer." + } + ]} +/> + +### 7. Điều gì xảy ra khi bạn khởi tạo một trong các lớp `AutoModelForXxx` với một mô hình ngôn ngữ huấn luyện trước( ví dụ như `bert-base-uncased`) mà liên quan tới một tác vụ khác hơn là tác vụ mà nó được huấn luyện sẵn? + +AutoModelForSequenceClassification với bert-base-uncased, ta nhận được cảnh báo khi khởi tạo mô hình. Phần đầu được huấn luyện trước không được sử dụng cho chuỗi tác vụ phân loại, vì vậy nó bị loại bỏ và một phần đầu mới được khởi tạo với các trọng số ngẫu nhiên.", + correct: true + }, + { + text: "Phần đầu của mô hình được huấn luyện trước bị loại bỏ.", + explain: "Một điều gì đó khác cần phải xảy ra. Hãy thử lại!" + }, + { + text: "Không có gì, vì mô hình vẫn có thể được tinh chỉnh cho các tác vụ khác.", + explain: "Phần đầu của mô hình được huấn luyện trước không được huấn luyện để giải quyết tác vụ này, vì vậy chúng ta nên loại bỏ phần đầu!" + } + ]} +/> + +### 8. Mục đích của `TrainingArguments` là gì? + +Trainer.", + explain: "Chính xác!", + correct: true + }, + { + text: "Nó chỉ định kích thước của mô hình.", + explain: "Kích thước mô hình được xác định bởi cấu hình mô hình, không phải lớp TrainingArguments." + }, + { + text: "Nó chỉ chứa các siêu tham số được sử dụng để đánh giá.", + explain: "Trong ví dụ này, chúng tôi đã chỉ định nơi mô hình và các checkpoint của nó sẽ được lưu. Hãy thử lại!" + }, + { + text: "Nó chỉ chứa các siêu tham số được sử dụng để huấn luyện.", + explain: "Trong ví dụ này, chúng tôi cũng đã sử dụng evaluation_strategy, vì vậy điều này ảnh hưởng đến việc đánh giá mô hình. Hãy thử lại!" + } + ]} +/> + +### 9. Vì sao bạn nên sử dụng thư viện 🤗 Accelerate? + +Trainer, không phải với thư viện 🤗 Accelerate. Hãy thử lại!" + }, + { + text: "Nó làm cho các vòng huấn luyện hoạt động dựa trên các chiến lược phân tán", + explain: "Đúng! Với 🤗 Accelerate, các vòng huấn luyện của bạn sẽ hoạt động cho nhiều GPU và TPU.", + correct: true + }, + { + text: "Nó cung cấp nhiều hàm tối ưu hơn.", + explain: "Không, thư viện 🤗 Accelerate không cung cấp bất kỳ hàm tối ưu nào." + } + ]} +/> + +{:else} +### 4. Điều gì xảy ra khi bạn khởi tạo một trong các lớp `TFAutoModelForXxx` với một mô hình ngôn ngữ huấn luyện trước( ví dụ như `bert-base-uncased`) mà liên quan tới một tác vụ khác hơn là tác vụ mà nó được huấn luyện sẵn? + +TFAutoModelForSequenceClassification với bert-base-uncased, ta nhận được cảnh báo khi khởi tạo mô hình. Phần đầu được huấn luyện trước không được sử dụng cho chuỗi tác vụ phân loại, vì vậy nó bị loại bỏ và một phần đầu mới được khởi tạo với các trọng số ngẫu nhiên.", + correct: true + }, + { + text: "Phần đầu của mô hình được huấn luyện trước bị loại bỏ.", + explain: "Một điều gì đó khác cần phải xảy ra. Hãy thử lại!" + }, + { + text: "Không có gì, vì mô hình vẫn có thể được tinh chỉnh cho các tác vụ khác.", + explain: "Phần đầu của mô hình được huấn luyện trước không được huấn luyện để giải quyết tác vụ này, vì vậy chúng ta nên loại bỏ phần đầu!" + } + ]} +/> + +### 5. Các mô hình TensorFlow từ `transformers` vốn đã là các mô hình Keras. Lợi ích của việc này là gì? + +TPUStrategy , bao gồm cả việc khởi tạo mô hình." + }, + { + text: "Bạn có thể tận dụng các phương thức hiện có như compile(), fit(), và predict().", + explain: "Đúng! Sau khi bạn có dữ liệu, việc đào tạo về dữ liệu đó cần rất ít công việc.", + correct: true + }, + { + text: "Bạn có thể học Keras cũng như transformers.", + explain: "Đúng, nhưng chúng tôi đang tìm kiếm thứ khác :)", + correct: true + }, + { + text: "Bạn có thể dễ dàng tính toán các chỉ số liên quan đến tập dữ liệu.", + explain: "Keras giúp chúng ta huấn luyện và đánh giá mô hình, không phải tính toán các số liệu liên quan đến tập dữ liệu." + } + ]} +/> + +### 6. Làm thế nào bạn có thể định nghĩa thước đo tuỳ chỉnh của riêng bạn? + +tf.keras.metrics.Metric.", + explain: "Tuyệt vời!", + correct: true + }, + { + text: "Sử dụng API chức năng của Keras.", + explain: "Thử lại!" + }, + { + text: "Thông qua sử dụng metric_fn(y_true, y_pred).", + explain: "Chính xác!", + correct: true + }, + { + text: "Sử dụng Googling.", + explain: "Đó không phải là câu trả lời mà chúng tôi đang tìm kiếm, nhưng nó sẽ giúp bạn tìm thấy nó.", + correct: true + } + ]} +/> + +{/if} diff --git a/chapters/vi/chapter4/1.mdx b/chapters/vi/chapter4/1.mdx new file mode 100644 index 000000000..952b5193a --- /dev/null +++ b/chapters/vi/chapter4/1.mdx @@ -0,0 +1,17 @@ +# Hugging Face Hub + +[Hugging Face Hub](https://huggingface.co/) –- trang web chính của chúng tôi –- là một nền tảng tập trung cho phép mọi người khám phá, sử dụng và đóng góp các mô hình và bộ dữ liệu hiện đại nhất. Nó lưu trữ nhiều mô hình khác nhau, với hơn 10,000 mô hình được công bố rộng rãi. Chúng ta sẽ tập trung vào các mô hình trong chương này và xem xét các tập dữ liệu trong Chương 5. + +Các mô hình trong Hub không giới hạn ở 🤗 Transformer hoặc thậm chí là NLP. Có các mô hình từ [Flair](https://github.com/flairNLP/flair) và [AllenNLP](https://github.com/allenai/allennlp) cho NLP, [Asteroid](https://github.com/asteroid-team/asteroid) và [pyannote](https://github.com/pyannote/pyannote-audio) cho âm thanh và [timm](https://github.com/rwightman/pytorch-image-models) cho hình ảnh. + +Mỗi mô hình được lưu trữ ở kho lưu trữ Git, cho phép tạo phiên bản và khả năng tái tạo. Chia sẻ mô hình trên Hub có nghĩa là mở rộng mô hình đó với cộng đồng và giúp bất kỳ ai muốn sử dụng mô hình đó đều có thể dễ dàng truy cập, do đó loại bỏ nhu cầu tự huấn luyện mô hình của họ và đơn giản hóa việc chia sẻ và sử dụng. + +Ngoài ra, việc chia sẻ một mô hình trên Hub sẽ tự động triển khai một API luận suy được lưu trữ cho mô hình đó. Bất kỳ ai trong cộng đồng đều có thể tự do kiểm tra nó trực tiếp trên trang của mô hình, với các đầu vào tùy chỉnh và các vật dụng thích hợp. + +Phần tốt nhất là việc chia sẻ và sử dụng bất kỳ mô hình công khai nào trên Hub là hoàn toàn miễn phí! [Gói trả phí](https://huggingface.co/pricing) cũng tồn tại nếu bạn muốn chia sẻ mô hình một cách riêng tư. + +Video dưới đây hướng dẫn cách dùng Hub. + + + +Bạn cần có tài khoản huggingface.co để làm theo phần này, vì chúng ta sẽ tạo và quản lý kho lưu trữ trên Hugging Face Hub: [tạo tài khoản](https://huggingface.co/join) diff --git a/chapters/vi/chapter4/2.mdx b/chapters/vi/chapter4/2.mdx new file mode 100644 index 000000000..d094cb86b --- /dev/null +++ b/chapters/vi/chapter4/2.mdx @@ -0,0 +1,129 @@ + + +# Sử dụng các mô hình huấn luyện trước + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Model Hub làm cho việc chọn mô hình thích hợp trở nên đơn giản, vì vậy việc sử dụng mô hình đó trong bất kỳ thư viện nào dưới đây có thể được thực hiện trong một vài dòng mã. Hãy cùng xem cách thực sự sử dụng một trong những mô hình này và cách đóng góp lại cho cộng đồng. + +Giả sử chúng tôi đang tìm kiếm một mô hình cho tiếng Pháp có thể thực hiện tác vụ diền vào phần bị che đi. + +
+ Selecting the Camembert model. +
+ +Chúng tôi chọn checkpoint `camembert-base` để dùng thử. Từ `camembert-base` là tất cả những gì chúng ta cần để bắt đầu sử dụng nó! Như bạn đã thấy trong các chương trước, chúng ta có thể khởi tạo nó bằng cách sử dụng hàm `pipeline()`: + +```py +from transformers import pipeline + +camembert_fill_mask = pipeline("fill-mask", model="camembert-base") +results = camembert_fill_mask("Le camembert est :)") +``` + +```python out +[ + {'sequence': 'Le camembert est délicieux :)', 'score': 0.49091005325317383, 'token': 7200, 'token_str': 'délicieux'}, + {'sequence': 'Le camembert est excellent :)', 'score': 0.1055697426199913, 'token': 2183, 'token_str': 'excellent'}, + {'sequence': 'Le camembert est succulent :)', 'score': 0.03453313186764717, 'token': 26202, 'token_str': 'succulent'}, + {'sequence': 'Le camembert est meilleur :)', 'score': 0.0330314114689827, 'token': 528, 'token_str': 'meilleur'}, + {'sequence': 'Le camembert est parfait :)', 'score': 0.03007650189101696, 'token': 1654, 'token_str': 'parfait'} +] +``` + +Như bạn có thể thấy, việc tải một mô hình trong một pipeline cực kỳ đơn giản. Điều duy nhất bạn cần chú ý là checkpoint đã chọn có phù hợp với tác vụ mà nó sẽ được sử dụng hay không. Ví dụ: ở đây chúng tôi đang tải checkpoint `camembert-base` trong pipeline `fill-mask`, điều này hoàn toàn ổn. Nhưng nếu chúng tôi tải checkpoint này trong pipeline phân loại văn bản, kết quả sẽ không có ý nghĩa gì vì phần đầu của `camembert-base` không phù hợp với tác vụ này! Chúng tôi khuyên bạn nên sử dụng công cụ chọn tác vụ trong giao diện Hugging Face Hub để chọn các checkpoint thích hợp: + +
+ The task selector on the web interface. +
+ +Bạn cũng có thể khởi tạo checkpoint bằng cách sử dụng kiến trúc mô hình trực tiếp: + +{#if fw === 'pt'} + +```py +from transformers import CamembertTokenizer, CamembertForMaskedLM + +tokenizer = CamembertTokenizer.from_pretrained("camembert-base") +model = CamembertForMaskedLM.from_pretrained("camembert-base") +``` + +Tuy nhiên, chúng tôi khuyên bạn nên sử dụng [`Auto*` classes](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes) vì đây là của kiến trúc thiết kế-bất khả tri. Trong khi đoạn mã trước đó giới hạn người dùng ở các checkpoint chỉ có thể tải được trong kiến trúc CamemBERT, việc sử dụng các lớp `Auto*` giúp việc chuyển đổi các checkpoint trở nên đơn giản: + + +```py +from transformers import AutoTokenizer, AutoModelForMaskedLM + +tokenizer = AutoTokenizer.from_pretrained("camembert-base") +model = AutoModelForMaskedLM.from_pretrained("camembert-base") +``` + +{:else} + +```py +from transformers import CamembertTokenizer, TFCamembertForMaskedLM + +tokenizer = CamembertTokenizer.from_pretrained("camembert-base") +model = TFCamembertForMaskedLM.from_pretrained("camembert-base") +``` + +Tuy nhiên, chúng tôi khuyên bạn nên sử dụng [`TFAuto*` classes](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes) vì đây là của kiến trúc thiết kế-bất khả tri. Trong khi đoạn mã trước đó giới hạn người dùng ở các checkpoint chỉ có thể tải được trong kiến trúc CamemBERT, việc sử dụng các lớp `TFAuto*` giúp việc chuyển đổi các checkpoint trở nên đơn giản: + +```py +from transformers import AutoTokenizer, TFAutoModelForMaskedLM + +tokenizer = AutoTokenizer.from_pretrained("camembert-base") +model = TFAutoModelForMaskedLM.from_pretrained("camembert-base") +``` + +{/if} + + + +Khi sử dụng một mô hình được huấn luyện trước, hãy đảm bảo kiểm tra xem nó được huấn luyện như thế nào, dựa trên tập dữ liệu nào, các giới hạn và độ sai lệch của nó. Tất cả thông tin này phải được ghi trên thẻ mô hình của nó. + + diff --git a/chapters/vi/chapter4/3.mdx b/chapters/vi/chapter4/3.mdx new file mode 100644 index 000000000..944d8c9f7 --- /dev/null +++ b/chapters/vi/chapter4/3.mdx @@ -0,0 +1,702 @@ + + +# Chia sẻ các mô hình huấn luyện trước + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Trong các bước bên dưới, chúng ta sẽ xem xét các cách dễ nhất để chia sẻ các mô hình được huấn luyện trước với 🤗 Hub. Ta có sẵn các công cụ và tiện ích giúp việc chia sẻ và cập nhật mô hình trực tiếp trên Hub trở nên đơn giản, và chúng ta sẽ cùng nhau khám phá bên dưới. + + + +Chúng tôi khuyến khích tất cả người dùng huấn luyện mô hình đóng góp bằng cách chia sẻ chúng với cộng đồng - chia sẻ mô hình, ngay cả khi được huấn luyện trên các bộ dữ liệu rất cụ thể, sẽ giúp ích cho những người khác, giúp họ tiết kiệm thời gian và tính toán tài nguyên và cung cấp quyền truy cập vào các hiện vật hữu ích được huấn luyện. Đổi lại, bạn có thể hưởng lợi từ công việc mà những người khác đã làm! + +Có ba cách để tạo kho lưu trữ mô hình mới: + +- Sử dụng API `push_to_hub` +- Sử dụng thư viện Python `huggingface_hub` +- Sử dụng giao diện web + +Khi bạn đã tạo một kho lưu trữ, bạn có thể tải tệp lên đó qua git và git-lfs. Chúng tôi sẽ hướng dẫn bạn cách tạo kho lưu trữ mô hình và tải tệp lên chúng trong các phần sau. + +## Sử dụng API `push_to_hub` + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Cách đơn giản nhất để tải tệp lên Hub là tận dụng API `push_to_hub`. + +Trước khi đi xa hơn, bạn sẽ cần tạo token xác thực để API `huggingface_hub` biết bạn là ai và bạn có quyền ghi vào không gian tên nào. Đảm bảo rằng bạn đang ở trong môi trường mà bạn đã cài đặt `transformers` (xem [Thiết lập](/course/chapter0)). Nếu bạn đang ở trong notebook, bạn có thể sử dụng chức năng sau để đăng nhập: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Trên terminal, bạn có thể: + +```bash +huggingface-cli login +``` + +Trong cả hai trường hợp, bạn sẽ được nhắc nhập tên người dùng và mật khẩu của mình, đó là những mật khẩu mà bạn sử dụng để đăng nhập vào Hub. Nếu bạn chưa có hồ sơ Hub, bạn nên tạo một hồ sơ [tại đây](https://huggingface.co/join). + +Tuyệt vời! Bây giờ bạn có token xác thực được lưu trữ trong thư mục bộ nhớ cache của mình. Hãy tạo một số kho lưu trữ thôi! + +{#if fw === 'pt'} + +Nếu bạn đã thử với API `Trainer` để huấn luyện một mô hình, thì cách dễ nhất để tải nó lên Hub là đặt `push_to_hub=True` khi bạn định nghĩa `TrainingArguments`: + +```py +from transformers import TrainingArguments + +training_args = TrainingArguments( + "bert-finetuned-mrpc", save_strategy="epoch", push_to_hub=True +) +``` + +Khi bạn gọi `trainer.train()`, `Trainer` sau đó sẽ tải mô hình của bạn lên Hub mỗi khi nó được lưu (ở đây là mỗi epoch) trong một kho lưu trữ trong không gian tên của bạn. Kho lưu trữ đó sẽ được đặt tên giống như thư mục đầu ra bạn đã chọn (ở đây là `bert-finetuned-mrpc`) nhưng bạn có thể chọn một tên khác với `hub_model_id = "a_different_name"`. + +Để tải mô hình của bạn lên tổ chức mà bạn là thành viên, chỉ cần truyền nó vào qua `hub_model_id = "my_organization/my_repo_name"`. + +Sau khi quá trình huấn luyện của bạn kết thúc, bạn nên thực hiện một `trainer.push_to_hub()` cuối cùng để tải lên phiên bản cuối cùng của mô hình của bạn. Nó cũng sẽ tạo ra một thẻ mô hình với tất cả các siêu dữ liệu liên quan, báo cáo các siêu tham số được sử dụng và kết quả đánh giá! Dưới đây là một ví dụ về nội dung bạn có thể tìm thấy trong một thẻ mô hình như vậy: + +
+ An example of an auto-generated model card. +
+ +{:else} + +Nếu bạn sử dụng Keras để huấn luyện mô hình, cách dễ nhất để tải nó lên Hub là sử dụng `PushToHubCallback` khi bạn gọi `model.fit()`: + +```py +from transformers import PushToHubCallback + +callback = PushToHubCallback( + "bert-finetuned-mrpc", save_strategy="epoch", tokenizer=tokenizer +) +``` + +Sau đó, bạn nên thêm `callbacks=[callback]` trong lệnh gọi của mình tới `model.fit()`. Sau đó, lệnh này sẽ tải mô hình của bạn lên Hub mỗi khi nó được lưu (ở đây là mỗi epoch) trong một kho lưu trữ trên không gian tên của bạn. Kho lưu trữ đó sẽ được đặt tên giống như thư mục đầu ra bạn đã chọn (ở đây là `bert-finetuned-mrpc`) nhưng bạn có thể chọn một tên khác với `hub_model_id = "a_different_name"`. + +Để tải mô hình của bạn lên tổ chức mà bạn là thành viên, chỉ cần truyền nó vào qua `hub_model_id = "my_organization/my_repo_name"`. + +{/if} + +Ở cấp độ thấp hơn, việc truy cập Model Hub có thể được thực hiện trực tiếp trên các mô hình, tokenizer và các đối tượng cấu hình thông qua phương thức `push_to_hub()`. Phương pháp này xử lý cả việc tạo kho lưu trữ và đẩy các tệp mô hình và tệp tokenizer trực tiếp đến kho lưu trữ. Không cần xử lý thủ công, không giống như với API mà chúng ta sẽ thấy bên dưới. + +Để có ý tưởng về cách nó hoạt động, trước tiên chúng ta hãy khởi tạo một mô hình và một tokenizer: + +{#if fw === 'pt'} + +```py +from transformers import AutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = AutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` + +{:else} + +```py +from transformers import TFAutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = TFAutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` + +{/if} + +Bạn có thể tự do làm bất cứ điều gì bạn muốn với những thứ này - thêm token vào trình tokenize, huấn luyện mô hình, tinh chỉnh nó. Khi bạn hài lòng với kết quả mô hình, trọng số, và tokenizer, bạn có thể tận dụng phương thức `push_to_hub()` có sẵn trực tiếp trên đối tượng `model`: + +```py +model.push_to_hub("dummy-model") +``` + +Điều này sẽ tạo kho lưu trữ mới `dummy-model` trong hồ sơ của bạn và điền nó vào các tệp mô hình của bạn. +Làm tương tự với tokenizer để tất cả các tệp có sẵn trong kho lưu trữ này: + +```py +tokenizer.push_to_hub("dummy-model") +``` + +Nếu bạn thuộc một tổ chức, chỉ cần chỉ định tham số `organization` để tải lên không gian tên của tổ chức đó: + +```py +tokenizer.push_to_hub("dummy-model", organization="huggingface") +``` + +Nếu bạn muốn sử dụng một token Hugging Face cụ thể, bạn cũng có thể chỉ định nó thông qua `push_to_hub()`: + +```py +tokenizer.push_to_hub("dummy-model", organization="huggingface", use_auth_token="") +``` + +Giờ hãy đi tới Model Hub để tìm mô hình mới được tải lên của bạn: *https://huggingface.co/user-or-organization/dummy-model*. + +Nhấp vào tab "Files and versions" ("Tệp và phiên bản") và bạn sẽ thấy các tệp hiển thị trong ảnh chụp màn hình sau: + +{#if fw === 'pt'} + +
+Dummy model containing both the tokenizer and model files. +
+{:else} +
+Dummy model containing both the tokenizer and model files. +
+{/if} + + + +✏️ **Thử nghiệm thôi!** Lấy mô hình và trình tokenize được liên kết với checkpoint `bert-base-cased` và tải chúng lên kho lưu trữ trong không gian tên của bạn bằng phương thức `push_to_hub()`. Kiểm tra kỹ xem repo có xuất hiện chính xác trên trang của bạn hay không trước khi xóa nó. + + + +Như bạn đã thấy, phương thức `push_to_hub()` nhận một vài tham số, giúp bạn có thể tải lên không gian tên tổ chức hoặc kho lưu trữ cụ thể hoặc sử dụng token API khác. Chúng tôi khuyên bạn nên xem thông số kỹ thuật phương pháp có sẵn trực tiếp trong [🤗 tài liệu về Transformers](https://huggingface.co/transformers/model_sharing.html) để biết những gì ta có thể làm. + +Phương thức `push_to_hub()` được hỗ trợ bởi gói Python [`huggingface_hub`](https://github.com/huggingface/huggingface_hub), cung cấp một API trực tiếp đến Hugging Face Hub. Nó được tích hợp trong 🤗 Transformers và một số thư viện học máy khác, như [`allenlp`](https://github.com/allenai/allennlp). Mặc dù chúng tôi tập trung vào tích hợp 🤗 Transformers trong chương này, việc tích hợp nó vào mã hoặc thư viện của riêng bạn rất đơn giản. + +Chuyển đến phần cuối cùng để xem cách tải tệp lên kho lưu trữ mới tạo của bạn! + +## Sử dụng thư viện Python `huggingface_hub` + +Thư viện Python `huggingface_hub` là một gói cung cấp một bộ công cụ cho các hub mô hình và tập dữ liệu. Nó cung cấp các phương thức và lớp đơn giản cho các tác vụ phổ biến như tiếp nhận thông tin về kho lưu trữ trên hub và quản lý chúng. Nó cung cấp các API đơn giản hoạt động trên git để quản lý nội dung của các kho đó và tích hợp Hub trong các dự án và thư viện của bạn. + +Tương tự như việc sử dụng API `push_to_hub`, điều này sẽ yêu cầu bạn lưu token API vào bộ nhớ cache của mình. Để thực hiện việc này, bạn sẽ cần sử dụng lệnh `login` từ CLI, như đã đề cập trong phần trước (một lần nữa, hãy đảm bảo thêm các lệnh này với ký tự `!` nếu chạy trên Google Colab): + +```bash +huggingface-cli login +``` + +Gói `huggingface_hub` cung cấp một số phương thức và lớp hữu ích cho mục đích của chúng ta. Thứ nhất, có một số phương pháp để quản lý việc tạo, xóa kho lưu trữ và các phương pháp khác: + +```python no-format +from huggingface_hub import ( + # Quản lý người dùng + login, + logout, + whoami, + + # Tạo và quản lý kho dữ liệu + create_repo, + delete_repo, + update_repo_visibility, + + # Và một số phương thức truy xuất/thay đổi thông tin về mặt nội dung + list_models, + list_datasets, + list_metrics, + list_repo_files, + upload_file, + delete_file, +) +``` + +Ngoài ra, nó cung cấp lớp `Repository` rất mạnh mẽ để quản lý một kho lưu trữ cục bộ. Chúng ta sẽ khám phá các phương thức này và lớp đó trong phần tiếp theo để hiểu cách tận dụng chúng. + +Phương thức `create_repo` có thể được sử dụng để tạo một kho lưu trữ mới trên hub: + +```py +from huggingface_hub import create_repo + +create_repo("dummy-model") +``` + +Thao tác này sẽ tạo kho lưu trữ `dummy-model` trong không gian tên của bạn. Nếu muốn, bạn có thể chỉ định tổ chức nào mà kho lưu trữ sẽ thuộc về bằng cách sử dụng tham số `organization`: + +```py +from huggingface_hub import create_repo + +create_repo("dummy-model", organization="huggingface") +``` + +Thao tác này sẽ tạo kho lưu trữ `dummy-model` trong không gian tên `huggingface`, giả sử bạn thuộc tổ chức đó. +Các tham số có thể hữu ích khác là: + +- `private`, để chỉ định xem liệu kho lưu trữ có nên hiển thị với những người khác hay không. +- `token`, nếu bạn muốn ghi đè token được lưu trữ trong bộ nhớ cache của mình bằng một token nhất định. +- `repo_type`, nếu bạn muốn tạo `dataset` hoặc `space` thay vì một mô hình. Các giá trị được chấp nhận là `"dataset"` và `"space"`. + +Khi kho lưu trữ được tạo, chúng ta nên thêm tệp vào đó! Chuyển sang phần tiếp theo để xem ba cách có thể xử lý vấn đề này. + +## Sử dụng giao diện web + +Giao diện web cung cấp các công cụ để quản lý kho lưu trữ trực tiếp trong Hub. Sử dụng giao diện này, bạn có thể dễ dàng tạo kho lưu trữ, thêm tệp (thậm chí cả tệp lớn!), Khám phá các mô hình, trực quan hóa các điểm khác biệt và hơn thế nữa. + +Để tạo một kho lưu trữ mới, hãy truy cập [huggingface.co/new](https://huggingface.co/new): + +
+ Page showcasing the model used for the creation of a new model repository. +
+ +Đầu tiên, chỉ định chủ sở hữu của kho lưu trữ: đây có thể là bạn hoặc bất kỳ tổ chức nào mà bạn liên kết. Nếu bạn chọn một tổ chức, mô hình sẽ được giới thiệu trên trang của tổ chức và mọi thành viên của tổ chức sẽ có khả năng đóng góp vào kho lưu trữ. + +Tiếp theo, nhập tên mô hình của bạn. Đây cũng sẽ là tên của kho lưu trữ. Cuối cùng, bạn có thể chỉ định xem bạn muốn mô hình của mình là công khai hay riêng tư. Các mô hình tư nhân được ẩn khỏi chế độ xem công khai. + +Sau khi tạo kho mô hình, bạn sẽ thấy một trang như sau: + +
+ An empty model page after creating a new repository. +
+ +Đây là nơi mô hình của bạn sẽ được lưu trữ. Để bắt đầu điền nó, bạn có thể thêm tệp README trực tiếp từ giao diện web. + +
+ The README file showing the Markdown capabilities. +
+ +Tệp README nằm trong Markdown - hãy thoải mái sử dụng nó! Phần thứ ba của chương này dành riêng cho việc xây dựng một thẻ mô hình. Đây là những điều quan trọng hàng đầu trong việc mang lại giá trị cho mô hình của bạn, vì chúng là nơi bạn nói cho người khác biết nó có thể làm gì. + +Nếu bạn nhìn vào tab "Files and versions" hay "Tệp và phiên bản", bạn sẽ thấy rằng chưa có nhiều tệp ở đó - chỉ có _README.md_ bạn vừa tạo và tệp _.gitattributes_ theo dõi các tệp lớn. + +
+ The 'Files and versions' tab only shows the .gitattributes and README.md files. +
+ +Tiếp theo, chúng ta sẽ xem xét cách thêm một số tệp mới. + +## Tải các tệp mô hình + +Hệ thống quản lý tệp trên Hugging Face Hub dựa trên git cho các tệp thông thường và git-lfs (viết tắt của [Git Large File Storage](https://git-lfs.github.com/)) cho các tệp lớn hơn . + +Trong phần tiếp theo, chúng ta sẽ xem xét ba cách khác nhau để tải tệp lên Hub: thông qua `huggingface_hub` và thông qua lệnh git. + +### Phương pháp `upload_file` + +Sử dụng `upload_file` không yêu cầu cài đặt git và git-lfs trên hệ thống của bạn. Nó đẩy các tệp trực tiếp đến 🤗 Hub bằng cách sử dụng các yêu cầu HTTP POST. Một hạn chế của phương pháp này là nó không xử lý các tệp có kích thước lớn hơn 5GB. +Nếu tệp của bạn lớn hơn 5GB, vui lòng làm theo hai phương pháp khác được nêu chi tiết bên dưới. + +API có thể được sử dụng như sau: + +```py +from huggingface_hub import upload_file + +upload_file( + "/config.json", + path_in_repo="config.json", + repo_id="/dummy-model", +) +``` + +Thao tác này sẽ tải tệp `config.json` có sẵn tại `` vào thư mục gốc của kho lưu trữ là `config.json`, vào kho lưu trữ `dummy-model`. +Các tham số có thể hữu ích khác là: + +- `token`, nếu bạn muốn ghi đè token được lưu trữ trong bộ nhớ cache của mình bằng một token nhất định. +- `repo_type`, nếu bạn muốn tải lên `dataset` hoặc `space` thay vì một mô hình. Các giá trị được chấp nhận là `"dataset"` và `"space"`. + +### Lớp `Repository` + +Lớp `Repository` quản lý một kho lưu trữ cục bộ theo cách giống như git. Nó tóm tắt hầu hết các điểm khó khăn mà người ta có thể có với git để cung cấp tất cả các tính năng mà chúng tôi yêu cầu. + +Sử dụng lớp này yêu cầu phải cài đặt git và git-lfs, vì vậy hãy đảm bảo rằng bạn đã cài đặt git-lfs (xem [tại đây](https://git-lfs.github.com/) để biết hướng dẫn cài đặt) và thiết lập trước khi bắt đầu. + +Để bắt đầu chơi với kho lưu trữ chúng ta vừa tạo, chúng ta có thể bắt đầu bằng cách khởi tạo nó vào một thư mục cục bộ bằng cách sao chép kho lưu trữ từ xa: + +```py +from huggingface_hub import Repository + +repo = Repository("", clone_from="/dummy-model") +``` + +Thao tác này đã tạo thư mục `` trong thư mục làm việc của chúng ta. Thư mục này chỉ chứa tệp `.gitattributes` vì đó là tệp duy nhất được tạo khi khởi tạo kho lưu trữ thông qua `create_repo`. + +Từ thời điểm này, chúng ta có thể tận dụng một số phương pháp git truyền thống: + +```py +repo.git_pull() +repo.git_add() +repo.git_commit() +repo.git_push() +repo.git_tag() +``` + +Và những cái khác! Chúng tôi khuyên bạn nên xem tài liệu về `Repository` hay `Kho lưu trữ` có sẵn [tại đây](https://github.com/huggingface/huggingface_hub/tree/main/src/huggingface_hub#advanced-programmatic-repository-management) để biết tổng quan về tất cả các phương pháp. + +Hiện tại, chúng ta có một mô hình và một tokenizer mà ta muốn đưa vào Hub. Chúng ta đã nhân bản thành công kho lưu trữ, do đó chúng tôi có thể lưu các tệp trong kho lưu trữ đó. + +Trước tiên, chúng tôi đảm bảo rằng bản sao cục bộ được cập nhật bằng cách kéo về những thay đổi mới nhất: + +```py +repo.git_pull() +``` + +Sau đó, ta lưu mô hình và tệp tokenizer: + +```py +model.save_pretrained("") +tokenizer.save_pretrained("") +``` + +`` bây giờ chứa tất cả các tệp mô hình và tokenizer. Chúng ta thực hiện theo quy trình làm việc git thông thường bằng cách thêm tệp vào khu vực lưu trữ thay đổi, cam kết chúng và đẩy chúng vào hub: + +```py +repo.git_add() +repo.git_commit("Thêm mô hình và tệp tokenizer") +repo.git_push() +``` + +Xin chúc mừng! Bạn vừa đẩy các tệp đầu tiên của mình lên Hub. + +### Phương pháp dựa trên git + +Đây là cách tiếp cận rất đơn giản để tải tệp lên: chúng ta sẽ làm trực tiếp với git và git-lfs. Hầu hết khó khăn đã được loại bỏ bởi các cách tiếp cận trước đây, nhưng có một số lưu ý với phương pháp tiếp theo, vì vậy chúng ta sẽ theo một trường hợp sử dụng phức tạp hơn. + +Sử dụng lớp này yêu cầu phải cài đặt git và git-lfs, vì vậy hãy đảm bảo bạn đã cài đặt [git-lfs](https://git-lfs.github.com/) (xem hướng dẫn cài đặt tại đây) và cài đặt trước khi bắt đầu . + +Trước tiên, hãy bắt đầu bằng cách khởi tạo git-lfs: + +```bash +git lfs install +``` + +```bash +Updated git hooks. +Git LFS initialized. +``` + +Sau khi hoàn tất, bước đầu tiên là sao chép kho lưu trữ mô hình của bạn: + +```bash +git clone https://huggingface.co// +``` + +Tên người dùng của tôi là `lysandre` và ta đã sử dụng tên mô hình là `dummy`, vì vậy lệnh kết thúc như sau: + +``` +git clone https://huggingface.co/lysandre/dummy +``` + +Bây giờ ta có một thư mục tên _dummy_ trong thư mục làm việc của mình. Ta có thể `cd` vào thư mục và xem nội dung: + +```bash +cd dummy && ls +``` + +```bash +README.md +``` + +Nếu bạn vừa tạo kho lưu trữ của mình bằng phương pháp `create_repo` của Hugging Face Hub, thì thư mục này chỉ nên chứa tệp `.gitattributes` ẩn. Nếu bạn đã làm theo hướng dẫn trong phần trước để tạo kho lưu trữ bằng giao diện web, thì thư mục phải chứa một tệp _README.md_ duy nhất cùng với tệp `.gitattributes` ẩn, như được hiển thị ở đây. + +Việc thêm một tệp có kích thước thông thường, chẳng hạn như tệp cấu hình, tệp từ vựng hoặc về cơ bản là bất kỳ tệp nào dưới vài megabyte, được thực hiện chính xác như cách người ta làm trong bất kỳ hệ thống dựa trên git nào. Tuy nhiên, các tệp lớn hơn phải được đăng ký thông qua git-lfs để đẩy chúng lên _huggingface.co_. + +Hãy quay lại Python một chút để tạo một mô hình và trình tokenize mà chúng ta muốn cam kết với kho lưu trữ dummy của chúng ta: + +{#if fw === 'pt'} + +```py +from transformers import AutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = AutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + +# Làm bất cứ điều gì với mô hình, huấn luyện nó, tinh chỉnh nó ... + +model.save_pretrained("") +tokenizer.save_pretrained("") +``` + +{:else} + +```py +from transformers import TFAutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = TFAutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + +# Làm bất cứ điều gì với mô hình, huấn luyện nó, tinh chỉnh nó ... + +model.save_pretrained("") +tokenizer.save_pretrained("") +``` + +{/if} + +Bây giờ chúng ta đã lưu một số tạo tác mô hình và tokenizer, hãy xem xét lại thư mục _dummy_: + +```bash +ls +``` + +{#if fw === 'pt'} + +```bash +config.json pytorch_model.bin README.md sentencepiece.bpe.model special_tokens_map.json tokenizer_config.json tokenizer.json +``` + +Nếu bạn nhìn vào kích thước tệp (ví dụ: với `ls -lh`), bạn sẽ thấy rằng tệp dict trạng thái mô hình (_pytorch_model.bin_) là ngoại lệ duy nhất, với hơn 400 MB. + +{:else} + +```bash +config.json README.md sentencepiece.bpe.model special_tokens_map.json tf_model.h5 tokenizer_config.json tokenizer.json +``` + +Nếu bạn nhìn vào kích thước tệp (ví dụ: với `ls -lh`), bạn sẽ thấy rằng tệp dict trạng thái mô hình (_t5_model.h5_) là ngoại lệ duy nhất, với hơn 400 MB. + +{/if} + + + ✏️ Khi tạo kho lưu trữ từ giao diện web, tệp *.gitattributes* được tự động thiết lập để xem xét các tệp có phần mở rộng nhất định, chẳng hạn như *.bin* và *.h5*, là tệp lớn và git-lfs sẽ theo dõi chúng mà không có thiết lập cần thiết về phía bạn. +{" "} + +Bây giờ chúng ta có thể tiếp tục và tiến hành như chúng ta thường làm với các kho lưu trữ Git truyền thống. Chúng ta có thể thêm tất cả các tệp vào môi trường dàn dựng của Git bằng lệnh `git add`: + +```bash +git add . +``` + +Sau đó, chúng ta có thể xem xét các tệp hiện đang được sắp xếp: + +```bash +git status +``` + +{#if fw === 'pt'} + +```bash +On branch main +Your branch is up to date with 'origin/main'. + +Changes to be committed: + (use "git restore --staged ..." to unstage) + modified: .gitattributes + new file: config.json + new file: pytorch_model.bin + new file: sentencepiece.bpe.model + new file: special_tokens_map.json + new file: tokenizer.json + new file: tokenizer_config.json +``` + +{:else} + +```bash +On branch main +Your branch is up to date with 'origin/main'. + +Changes to be committed: + (use "git restore --staged ..." to unstage) + modified: .gitattributes + new file: config.json + new file: sentencepiece.bpe.model + new file: special_tokens_map.json + new file: tf_model.h5 + new file: tokenizer.json + new file: tokenizer_config.json +``` + +{/if} + +Tương tự, chúng ta có thể đảm bảo rằng git-lfs đang theo dõi các tệp chính xác bằng cách sử dụng lệnh `status`: + +```bash +git lfs status +``` + +{#if fw === 'pt'} + +```bash +On branch main +Objects to be pushed to origin/main: + + +Objects to be committed: + + config.json (Git: bc20ff2) + pytorch_model.bin (LFS: 35686c2) + sentencepiece.bpe.model (LFS: 988bc5a) + special_tokens_map.json (Git: cb23931) + tokenizer.json (Git: 851ff3e) + tokenizer_config.json (Git: f0f7783) + +Objects not staged for commit: + + +``` + +Chúng ta có thể thấy rằng tất cả các tệp đều có `Git` làm trình xử lý, ngoại trừ _pytorch_model.bin_ và _sentencepiece.bpe.model_, có` LFS`. Tuyệt vời! + +{:else} + +```bash +On branch main +Objects to be pushed to origin/main: + + +Objects to be committed: + + config.json (Git: bc20ff2) + sentencepiece.bpe.model (LFS: 988bc5a) + special_tokens_map.json (Git: cb23931) + tf_model.h5 (LFS: 86fce29) + tokenizer.json (Git: 851ff3e) + tokenizer_config.json (Git: f0f7783) + +Objects not staged for commit: + + +``` + +Chúng ta có thể thấy rằng tất cả các tệp đều có `Git` làm trình xử lý, ngoại trừ _t5_model.h5_, có `LFS`. Tuyệt vời! + +{/if} + +Hãy tiến hành các bước cuối cùng, cam kết và đẩy đến kho lưu trữ từ xa _huggingface.co_: + +```bash +git commit -m "First model version" +``` + +{#if fw === 'pt'} + +```bash +[main b08aab1] First model version + 7 files changed, 29027 insertions(+) + 6 files changed, 36 insertions(+) + create mode 100644 config.json + create mode 100644 pytorch_model.bin + create mode 100644 sentencepiece.bpe.model + create mode 100644 special_tokens_map.json + create mode 100644 tokenizer.json + create mode 100644 tokenizer_config.json +``` + +{:else} + +```bash +[main b08aab1] First model version + 6 files changed, 36 insertions(+) + create mode 100644 config.json + create mode 100644 sentencepiece.bpe.model + create mode 100644 special_tokens_map.json + create mode 100644 tf_model.h5 + create mode 100644 tokenizer.json + create mode 100644 tokenizer_config.json +``` + +{/if} + +Việc đẩy có thể mất một chút thời gian, tùy thuộc vào tốc độ kết nối internet và kích thước tệp của bạn: + +```bash +git push +``` + +```bash +Uploading LFS objects: 100% (1/1), 433 MB | 1.3 MB/s, done. +Enumerating objects: 11, done. +Counting objects: 100% (11/11), done. +Delta compression using up to 12 threads +Compressing objects: 100% (9/9), done. +Writing objects: 100% (9/9), 288.27 KiB | 6.27 MiB/s, done. +Total 9 (delta 1), reused 0 (delta 0), pack-reused 0 +To https://huggingface.co/lysandre/dummy + 891b41d..b08aab1 main -> main +``` + +{#if fw === 'pt'} +Nếu chúng ta xem qua kho lưu trữ mô hình khi quá trình này kết thúc, chúng ta có thể thấy tất cả các tệp được thêm gần đây: + +
+ The 'Files and versions' tab now contains all the recently uploaded files. +
+ +Giao diện người dùng cho phép bạn khám phá các tệp mô hình và các cam kết cũng như xem sự khác biệt được giới thiệu bởi mỗi cam kết: + +
+The diff introduced by the recent commit. +
+{:else} +Nếu chúng ta xem qua kho lưu trữ mô hình khi quá trình này kết thúc, chúng ta có thể thấy tất cả các tệp được thêm gần đây: + +
+ The 'Files and versions' tab now contains all the recently uploaded files. +
+ +Giao diện người dùng cho phép bạn khám phá các tệp mô hình và các cam kết cũng như xem sự khác biệt được giới thiệu bởi mỗi cam kết: + +
+The diff introduced by the recent commit. +
+{/if} diff --git a/chapters/vi/chapter4/4.mdx b/chapters/vi/chapter4/4.mdx new file mode 100644 index 000000000..57b703e36 --- /dev/null +++ b/chapters/vi/chapter4/4.mdx @@ -0,0 +1,82 @@ +# Xây dựng các thẻ mô hình + +Thẻ mô hình là một tệp được cho là quan trọng như mô hình và tệp tokenizer trong kho lưu trữ mô hình. Đây là định nghĩa chủ đạo của mô hình, đảm bảo khả năng tái sử dụng của các thành viên trong cộng đồng và khả năng tái tạo kết quả, đồng thời cung cấp một nền tảng mà các thành viên khác có thể xây dựng các tác phẩm của họ. + +Việc ghi lại quá trình huấn luyện và đánh giá giúp những người khác hiểu những gì mong đợi ở một mô hình - và cung cấp đầy đủ thông tin liên quan đến dữ liệu đã được sử dụng và quá trình tiền xử lý và hậu xử lý đã được thực hiện đảm bảo rằng các hạn chế, thành kiến ​​và bối cảnh trong đó mô hình đang và không hữu ích có thể được xác định và hiểu. + +Vì vậy, tạo một thẻ mô hình xác định rõ ràng mô hình của bạn là một bước rất quan trọng. Ở đây, chúng tôi cung cấp một số mẹo sẽ giúp bạn điều này. Việc tạo thẻ mô hình được thực hiện thông qua tệp _README.md_ mà bạn đã thấy trước đó, đây là một tệp Markdown. + +Khái niệm "thẻ mô hình" bắt nguồn từ một hướng nghiên cứu của Google, lần đầu tiên được chia sẻ trong bài báo ["Model Cards for Model Reporting"](https://arxiv.org/abs/1810.03993) của Margaret Mitchell và cộng sự. Nhiều thông tin ở đây dựa trên bài báo đó và chúng tôi khuyên bạn nên xem qua để hiểu tại sao thẻ mô hình lại quan trọng như vậy trong một thế giới coi trọng khả năng tái tạo, khả năng tái sử dụng và tính công bằng. + +Thẻ mô hình thường bắt đầu với tổng quan rất ngắn gọn, cấp cao về mục đích của mô hình, tiếp theo là các chi tiết bổ sung trong các phần sau: + +- Mô tả về mô hình +- Mục đích sử dụng & giới hạn +- Cách sử dụng +- Hạn chế và sai lệch +- Dữ liệu huấn luyện +- Quy trình huấn luyện +- Những kết quả đánh giá + +Chúng ta hãy xem mỗi phần này nên chứa những gì. + +### Mô tả về mô hình + +Mô tả về mô hình cung cấp các chi tiết cơ bản về mô hình. Điều này bao gồm kiến ​​trúc, phiên bản, nếu nó được giới thiệu trong một bài báo, nếu có sẵn bản triển khai gốc, tác giả và thông tin chung về mô hình. Bất kỳ bản quyền nào cũng nên được ghi nhận ở đây. Thông tin chung về quy trình huấn luyện, các thông số và tuyên bố từ chối trách nhiệm quan trọng cũng có thể được đề cập trong phần này. + +### Mục đích sử dụng & giới hạn + +Ở đây, bạn mô tả các trường hợp sử dụng mà mô hình, bao gồm các ngôn ngữ, trường và mảng chuyên môn mà nó có thể được áp dụng. Phần này của thẻ mô hình cũng có thể ghi lại các khu vực được biết là nằm ngoài phạm vi của mô hình hoặc nơi nó có khả năng hoạt động dưới mức tối ưu. + +### Cách sử dụng + +Phần này nên bao gồm một số ví dụ về cách sử dụng mô hình. Điều này có thể giới thiệu cách sử dụng hàm `pipeline()`, cách sử dụng mô hình và tokenizer, và bất kỳ đoạn mã nào khác mà bạn nghĩ có thể hữu ích. + +### Dữ liệu huấn luyện + +Phần này phải chỉ ra (các) tập dữ liệu nào mà mô hình đã được huấn luyện. Một mô tả ngắn gọn về (các) tập dữ liệu cũng được hoan nghênh. + +### Quy trình huấn luyện + +Trong phần này, bạn nên mô tả tất cả các khía cạnh liên quan của việc huấn luyện mà hữu ích từ góc độ khả năng tái tạo. Điều này bao gồm bất kỳ quá trình tiền xử lý và hậu xử lý nào đã được thực hiện trên dữ liệu, cũng như các chi tiết như số epoch mà mô hình được huấn luyện, kích thước lô, tốc độ học, v.v. + +### Biến và chỉ số thước đo + +Ở đây, bạn nên mô tả các số liệu bạn sử dụng để đánh giá, và các yếu tố khác nhau mà bạn đang đo lường. Đề cập đến (các) chỉ số nào đã được sử dụng, tập dữ liệu nào được sử dụng và tập dữ liệu được phân chia như thế nào, giúp dễ dàng so sánh hiệu suất của mô hình của bạn so với hiệu suất của các mô hình khác. Những điều này phải được thông báo bởi các phần trước, chẳng hạn như đối tượng người dùng và các trường hợp sử dụng. + +### Những kết quả đánh giá + +Cuối cùng, cung cấp chỉ báo về mức độ hoạt động của mô hình trên tập dữ liệu đánh giá. Nếu mô hình sử dụng ngưỡng quyết định, hãy cung cấp ngưỡng quyết định được sử dụng trong đánh giá hoặc cung cấp thông tin chi tiết về đánh giá ở các ngưỡng khác nhau phục vụ cho các mục đích sử dụng. + +## Ví dụ + +Hãy xem phần sau để biết một vài ví dụ về thẻ mô hình được chế tạo tốt: + +- [`bert-base-cased`](https://huggingface.co/bert-base-cased) +- [`gpt2`](https://huggingface.co/gpt2) +- [`distilbert`](https://huggingface.co/distilbert-base-uncased) + +Tham khảo thêm các ví dụ từ các tổ chức và công ty khác nhau [tại đây](https://github.com/huggingface/model_card/blob/master/examples.md). + +## Lưu ý + +Thẻ mô hình không phải là ràng buộc khi xuất bản mô hình và bạn không cần phải bao gồm tất cả các phần được mô tả ở trên khi tạo thẻ mô hình. Tuy nhiên, tài liệu rõ ràng về mô hình có thể mang lại lợi ích cho người dùng trong tương lai, vì vậy chúng tôi khuyên bạn nên điền vào nhiều phần nhất có thể theo khả năng và kiến ​​thức của mình. + +## Siêu dữ liệu thẻ mô hình + +Nếu bạn đã khám phá một chút về Hugging Face Hub, bạn sẽ thấy rằng một số kiểu mô hình thuộc một số nhóm nhất định: bạn có thể lọc chúng theo tác vụ, ngôn ngữ, thư viện, v.v. Các nhóm mà một mô hình thuộc về được xác định theo siêu dữ liệu bạn thêm vào tiêu đề thẻ mô hình. + +Ví dụ: nếu bạn xem [thẻ mô hình camembert-base`](https://huggingface.co/camembert-base/blob/main/README.md), bạn sẽ thấy các dòng sau trong tiêu đề thẻ mô hình: + +``` +--- +language: fr +license: mit +datasets: +- oscar +--- +``` + +Siêu dữ liệu này được phân tích bởi Hugging Face Hub, sau đó xác định mô hình này là cho tiếng Pháp, có giấy phép MIT, được huấn luyện trên tập dữ liệu Oscar. + +[Thông số kỹ thuật thẻ mô hình bản đầy đủ](https://github.com/huggingface/hub-docs/blame/main/modelcard.md) cho phép chỉ định ngôn ngữ, giấy phép, thẻ, bộ dữ liệu, số liệu cũng như kết quả đánh giá mô hình thu được khi huấn luyện. diff --git a/chapters/vi/chapter4/5.mdx b/chapters/vi/chapter4/5.mdx new file mode 100644 index 000000000..15f1a2ef2 --- /dev/null +++ b/chapters/vi/chapter4/5.mdx @@ -0,0 +1,7 @@ +# Hoàn thành phần 1! + +Đây là mục cuối của phần đầu tiên trong khóa học! Phần 2 sẽ ra mắt vào ngày 15/11 tới đây với một sự kiện cộng đồng lớn, xem thêm thông tin [tại đây](https://huggingface.co/blog/course-launch-event). + +Giờ đây, bạn có thể tinh chỉnh mô hình được huấn luyện trước về vấn đề phân loại văn bản (đơn hoặc cặp câu) và tải kết quả lên Model Hub. Để đảm bảo bạn thành thạo phần đầu tiên này, bạn nên làm chính xác phần đó đối với một vấn đề mà bạn quan tâm (và không nhất thiết phải bằng tiếng Anh nếu bạn nói một ngôn ngữ khác)! Bạn có thể tìm trợ giúp trong [diễn đàn Hugging Face](https://discuss.huggingface.co/) và chia sẻ dự án của mình trong [chủ đề này](https://discuss.huggingface.co/t/share-your-projects/6803) sau khi bạn hoàn thành. + +Chúng tôi háo hức chờ đợi để xem bạn sẽ xây dựng những gì với điều này! diff --git a/chapters/vi/chapter4/6.mdx b/chapters/vi/chapter4/6.mdx new file mode 100644 index 000000000..ab9c55658 --- /dev/null +++ b/chapters/vi/chapter4/6.mdx @@ -0,0 +1,231 @@ + + + + +# Đố vui cuối chương + +Hãy kiểm tra những gì bạn đã học được trong chương này! + +### 1. Các mô hình tải lên trên Hub có giới hạn gì? + + + +### 2. Bạn có thể quản lý các mô hình trên Hub bằng cách nào? + +git-lfs cho các tệp lớn.", + correct: true, + }, + ]} +/> + +### 3. Bạn có thể làm những gì khi sử dụng giao diện web Hugging Face Hub? + + + +### 4. Thẻ mô hình là gì? + + + +### 5. Đối tượng nào sau đây của thư viện 🤗 Transformers có thể được chia sẻ trực tiếp trên Hub với `push_to_hub()`? + +{#if fw === 'pt'} + +push_to_hub giúp đẩy tất cả các tệp tokenizer (từ vựng, kiến ​​trúc của tokenizer, v.v.) đến một repo nhất định. Tuy nhiên, đó không phải là câu trả lời đúng duy nhất!", + correct: true + }, + { + text: "Một tệp cấu hình mô hình", + explain: "Đúng vậy! Tất cả các tệp cấu hình mô hình đều có phương thức push_to_hub giúp đẩy chúng đến một repo. Bạn có thể chia sẻ điều gì khác nữa không?", + correct: true + }, + { + text: "Một mô hình", + explain: "Chính xác! Tất cả các mô hình đều có phương thức push_to_hub giúp đẩy mô hình và các tệp cấu hình đến một repo nhất định.Tuy nhiên, đó không phải là câu trả lời đúng duy nhất!", + correct: true + }, + { + text: "Một Trainer", + explain: "Đúng vậy— Trainer cũng triển khai phương thức push_to_hub giúp tải mô hình, cấu hình, tokenizer và thẻ mô hình của chúng đến một repo nhất định. Thử thêm đáp án khác nữa xem!", + correct: true + } + ]} +/> +{:else} +push_to_hub giúp đẩy tất cả các tệp tokenizer (từ vựng, kiến ​​trúc của tokenizer, v.v.) đến một repo nhất định. Tuy nhiên, đó không phải là câu trả lời đúng duy nhất!", + correct: true + }, + { + text: "Một tệp cấu hình mô hình", + explain: "Đúng vậy! Tất cả các tệp cấu hình mô hình đều có phương thức push_to_hub giúp đẩy chúng đến một repo. Bạn có thể chia sẻ điều gì khác nữa không?", + correct: true + }, + { + text: "Một mô hình", + explain: "Chính xác! Tất cả các mô hình đều có phương thức push_to_hub giúp đẩy mô hình và các tệp cấu hình đến một repo nhất định.Tuy nhiên, đó không phải là câu trả lời đúng duy nhất!", + correct: true + }, + { + text: "Tất cả những điều trên với một callback đặc thù", + explain: "Đúng vậy - PushToHubCallback sẽ thường xuyên gửi tất cả các đối tượng đó đến một repo trong quá trình huấn luyện.", + correct: true + } + ]} +/> +{/if} + +### 6. Bước đầu tiên khi sử dụng phương thức `push_to_hub()` hoặc các công cụ CLI là gì? + + + +### 7. Bạn đang sử dụng một mô hình và một tokenizer - làm cách nào bạn có thể tải chúng lên Hub? + +huggingface_hub.", + explain: + "Các mô hình và tokenizer đã hưởng lợi sẵn từ tiện ích huggingface_hub: không cần gói thêm!", + }, + { + text: "Bằng cách lưu chúng vào ổ đĩa và gọi lệnh transformers-cli upload-model", + explain: "Lệnh upload-model không tồn tại.", + }, + ]} +/> + +### 8. Bạn có thể thực hiện các thao tác git nào với `Repository`? + +git_commit() có sẵn cho điều đó.", + correct: true, + }, + { + text: "Pull (Kéo lại)", + explain: "Đó là mục đích của phương thức git_pull().", + correct: true, + }, + { + text: "Push (Đẩy lên)", + explain: "Phương thức git_push() thực hiện điều này.", + correct: true, + }, + { + text: "Merge (Gộp)", + explain: "Không, thao tác đó sẽ không bao giờ có thể thực hiện được với API này.", + }, + ]} +/> diff --git a/chapters/vi/event/1.mdx b/chapters/vi/event/1.mdx new file mode 100644 index 000000000..992b50bfe --- /dev/null +++ b/chapters/vi/event/1.mdx @@ -0,0 +1,165 @@ +# Sự kiện Phát hành Phần 2 + +Để phát hành phần 2 của khóa học, chúng tôi đã tổ chức một sự kiện trực tiếp với hai ngày chia sẻ. Nếu bạn đã bỏ lỡ nó, bạn có thể theo dõi các bài nói chuyện được liệt kê dưới đây! + +## Ngày 1: Một cái nhìn cấp cao về Transformer và cách huấn luyện chúng + +**Thomas Wolf:** *Học chuyển giao và sự ra đời của thư viện Transformers* + +
+ +
+ +

+Tóm tắt hình ảnh về bài chia sẻ của Thom +

+ +Thomas Wolf là đồng sáng lập và Giám đốc Khoa học của Hugging Face. Các công cụ do Thomas Wolf và nhóm Hugging Face tạo ra được sử dụng trên hơn 5,000 tổ chức nghiên cứu bao gồm Facebook Artificial Intelligence Research, Google Research, DeepMind, Amazon Research, Apple, Allen Institute for Artificial Intelligence cũng như hầu hết các khoa của trường đại học. Thomas Wolf là người khởi xướng và là chủ tịch cấp cao của sự hợp tác nghiên cứu lớn nhất từng tồn tại trong Trí tuệ nhân tạo: [“BigScience”](https://bigscience.huggingface.co), cũng như một bộ [các thư viện và công cụ được sử dụng rộng rãi](https://github.com/huggingface/). Thomas Wolf cũng là một nhà giáo dục xuất sắc, một nhà lãnh đạo tư tưởng trong lĩnh vực Trí tuệ Nhân tạo và Xử lý Ngôn ngữ Tự nhiên, và là một diễn giả thường xuyên được mời tham dự các hội nghị trên toàn thế giới [https://thomwolf.io](https://thomwolf.io ). + +**Jay Alammar:** *Phần giới thiệu trực quan nhẹ nhàng về các mô hình Transformer* + +
+ +
+ +

+Tóm tắt hình ảnh về bài chia sẻ của Jay +

+ +Thông qua blog Học máy (ML) nổi tiếng của mình, Jay đã giúp hàng triệu nhà nghiên cứu và kỹ sư hiểu trực quan các công cụ và khái niệm ML từ cơ bản (với các tài liệu về NumPy, Pandas) đến tiên tiến (Transformers, BERT, GPT-3). + +**Margaret Mitchell:** *Về giá trị trong phát triển Học máy* + +
+ +
+ +

+Tóm tắt hình ảnh về bài chia sẻ của Margaret +

+ +Margaret Mitchell là một nhà nghiên cứu làm việc về Đạo đức Trí tuệ nhân tạo, hiện đang tập trung vào những điểm cần thiết của sự phát triển AI có nhận thức về đạo đức trong công nghệ. Cô đã xuất bản hơn 50 bài báo về tạo ngôn ngữ tự nhiên, công nghệ hỗ trợ, thị giác máy tính, và đạo đức AI, đồng thời nắm giữ nhiều bằng sáng chế trong các lĩnh vực tạo hội thoại và phân loại cảm xúc. Trước đây, cô đã làm việc tại Google AI với tư cách là Chuyên viên nghiên cứu khoa học, nơi cô thành lập và đồng lãnh đạo nhóm đạo đức AI của Google, tập trung vào nghiên cứu nền tảng đạo đức AI và vận hành đạo đức AI trong nội bộ Google. Trước khi gia nhập Google, cô ấy là nhà nghiên cứu tại Microsoft Research, tập trung vào việc chuyển đổi hình ảnh sang ngôn ngữ; và là một hậu nghiên cứu sinh tại Johns Hopkins, tập trung vào mô hình Bayes và trích xuất thông tin. Cô có bằng Tiến sĩ Khoa học Máy tính tại Đại học Aberdeen và Thạc sĩ ngôn ngữ học máy tính của Đại học Washington. Trong lúc chờ lấy bằng, cô cũng đã làm việc từ năm 2005-2012 về học máy, rối loạn thần kinh, và công nghệ hỗ trợ tại Đại học Khoa học và Sức khỏe Oregon. Cô ấy đã dẫn đầu một số hội thảo và sáng kiến ​​về giao điểm của sự đa dạng, hòa nhập, khoa học máy tính, và đạo đức. Công việc của cô đã nhận được giải thưởng từ Bộ trưởng Quốc phòng Ash Carter và Quỹ Người mù Hoa Kỳ, đồng thời được thực hiện bởi nhiều công ty công nghệ. Cô ấy thích làm vườn, nuôi chó và mèo. + +**Matthew Watson and Chen Qian:** *Quy trình NLP với Keras* + +
+ +
+ +

+Tóm tắt hình ảnh về bài chia sẻ của Matt và Chen +

+ +Matthew Watson là một kỹ sư học máy trong nhóm Keras, tập trung vào các API mô hình hóa cấp cao. Anh học Đồ họa máy tính ở đại học và có bằng Thạc sĩ tại Đại học Stanford. Là một sinh viên gần như chuyên ngành tiếng Anh chuyển sang ngành khoa học máy tính, anh ấy đam mê làm việc trên nhiều lĩnh vực và giúp cho NLP có thể tiếp cận với nhiều đối tượng hơn. + +Chen Qian là kỹ sư phần mềm từ nhóm Keras, tập trung vào các API mô hình hóa cấp cao. Chen có bằng Thạc sĩ Kỹ thuật Điện tại Đại học Stanford và anh ấy đặc biệt quan tâm đến việc đơn giản hóa việc triển khai mã của các tác vụ ML và ML quy mô lớn. + +**Mark Saroufim:** *Cách Huấn luyện một Mô hình với Pytorch* + +
+ +
+ +

+Tóm tắt hình ảnh về bài chia sẻ của Mark +

+ +Mark Saroufim là Kỹ sư đối tác tại Pytorch làm việc trên các công cụ sản xuất OSS bao gồm TorchServe và Pytorch Enterprise. Trước đó, Mark là Nhà khoa học ứng dụng và Giám đốc sản phẩm tại Graphcore, [yuri.ai](http://yuri.ai/), Microsoft và NASA's JPL. Niềm đam mê chính của anh ấy là làm cho việc lập trình trở nên thú vị hơn. + +**Jakob Uszkoreit:** *Nó không hỏng nên Đừng sửa Hãy phá nó đi* + +
+ +
+ +

+Tóm tắt hình ảnh về bài chia sẻ của Jakob +

+ +Jakob Uszkoreit là đồng sáng lập của Inception. Inception thiết kế các phân tử RNA cho vắc-xin và liệu pháp điều trị bằng cách sử dụng học sâu quy mô lớn trong một vòng lặp chặt chẽ với các thí nghiệm thông lượng cao với mục tiêu làm cho các loại thuốc dựa trên RNA trở nên dễ tiếp cận hơn, hiệu quả hơn và có thể áp dụng rộng rãi hơn. Trước đây, Jakob đã làm việc tại Google hơn một thập kỷ, lãnh đạo các nhóm nghiên cứu và phát triển trong Google Brain, Nghiên cứu và Tìm kiếm, làm việc về các nguyên tắc cơ bản về học sâu, thị giác máy tính, và hiểu ngôn ngữ và dịch máy. + +## Ngày 2: Các công cụ sử dụng + +**Lewis Tunstall:** *Huấn luyện đơn giản với 🤗 Transformers Trainer* + +
+ +
+ +Lewis là một kỹ sư máy học tại Hugging Face, tập trung vào việc phát triển các công cụ mã nguồn mở và giúp chúng có thể tiếp cận với cộng đồng rộng lớn hơn. Anh cũng là đồng tác giả của cuốn sách O’Reilly [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). Bạn có thể theo dõi anh ấy trên Twitter (@_lewtun) để biết các mẹo và thủ thuật NLP! + +**Matthew Carrigan:** * Các tính năng TensorFlow mới cho 🤗 Transformers và 🤗 Datasets* + +
+ +
+ +Matt chịu trách nhiệm bảo trì TensorFlow tại Transformers, và cuối cùng sẽ dẫn đầu một cuộc đảo chính chống lại phe PyTorch đương nhiệm, có khả năng thông qua tài khoản Twitter @carrigmat của anh ta. + +** Lysandre Debut: ** *Hugging Face Hub như một phương tiện để cộng tác và chia sẻ các dự án Học máy* + +
+ +
+ +

+Tóm tắt hình ảnh về bài chia sẻ của Lysandre +

+ +Lysandre là Kỹ sư Học máy tại Hugging Face, nơi anh ấy tham gia vào nhiều dự án mã nguồn mở. Mục đích của ông là làm cho Học máy có thể truy cập được với tất cả mọi người bằng cách phát triển các công cụ mạnh mẽ với một API rất đơn giản. + +**Lucile Saulnier:** *Tạo ra tokenizer của riêng bạn🤗 Transformers & 🤗 Tokenizers* + +
+ +
+ +Lucile là một kỹ sư học máy tại Hugging Face, phát triển và hỗ trợ việc sử dụng các công cụ mã nguồn mở. Cô cũng tích cực tham gia vào nhiều dự án nghiên cứu trong lĩnh vực Xử lý ngôn ngữ tự nhiên như huấn luyện hợp tác và BigScience. + +**Sylvain Gugger:** *Tăng cường vòng lặp huấn luyện PyTorch của bạn với 🤗 Accelerate* + +
+ +
+ +Sylvain là Kỹ sư nghiên cứu tại Hugging Face và là một trong những người bảo trì cốt lõi của 🤗 Transformers và là nhà phát triển đằng sau 🤗 Accelerate. Anh ấy thích làm cho những mô hình huấn luyện trở nên dễ tiếp cận hơn. + +**Merve Noyan:** *Giới thiệu các bản demo mô hình của bạn với 🤗 Spaces* + +
+ +
+ +Merve là chuyên gia về quan hệ lập trình viên tại Hugging Face, đang làm việc để phát triển các công cụ và xây dựng nội dung xung quanh chúng để giúp học máy có thể tiếp cận tới tất cả mọi người. + +**Abubakar Abid:** *Xây dựng Ứng dụng Học máy nhanh chóng* + +
+ +
+ +

+Tóm tắt hình ảnh về bài chia sẻ của Abubakar +

+ +Abubakar Abid là Giám đốc điều hành của [Gradio](www.gradio.app). Anh ấy nhận bằng Cử nhân Khoa học về Kỹ thuật Điện và Khoa học Máy tính từ MIT vào năm 2015 và Tiến sĩ về Máy học Ứng dụng từ Stanford vào năm 2021. Với vai trò là Giám đốc điều hành của Gradio, Abubakar làm việc để làm cho các mô hình học máy dễ dàng demo, gỡ lỗi và triển khai hơn. + +**Mathieu Desvé:** *AWS ML Vision: Làm cho Máy học có thể dễ dàng truy cập được bởi tất cả khách hàng* + +
+ +
+ +

+Tóm tắt hình ảnh về bài chia sẻ của Mathieu +

+ +Mathieu Desvé là người đam mê công nghệ, nhà sản xuất vào thời gian rảnh. Ạnh thích thử thách và giải quyết vấn đề của khách hàng và người dùng, đồng thời làm việc với những người tài năng để học hỏi mỗi ngày. Kể từ năm 2004, anh làm việc ở nhiều vị trí chuyển đổi từ frontend, backend, cơ sở hạ tầng, hoạt động và quản lý. Anh cố gắng giải quyết các vấn đề liên quan đến kỹ thuật và quản lý theo cách nhanh nhẹn. + +**Philipp Schmid:** *Quản lý huấn luyện với Amazon SageMaker và 🤗 Transformers* + +
+ +
+ +Philipp Schmid là Kỹ sư Máy học và Trưởng nhóm Công nghệ tại Hugging Face, nơi anh lãnh đạo sự hợp tác với nhóm Amazon SageMaker. Anh ấy đam mê sản xuất các mô hình NLP tiên tiến và cải thiện tính dễ sử dụng cho Học sâu. From 3b7f5f027e84bdca16aef3e8a8313e3959fc8c66 Mon Sep 17 00:00:00 2001 From: lewtun Date: Tue, 23 Aug 2022 17:09:25 +0100 Subject: [PATCH 27/51] Bump release (#299) --- chapters/vi/_toctree.yml | 46 +++ chapters/vi/chapter5/1.mdx | 17 + chapters/vi/chapter5/2.mdx | 165 +++++++++ chapters/vi/chapter5/3.mdx | 739 +++++++++++++++++++++++++++++++++++++ chapters/vi/chapter5/4.mdx | 294 +++++++++++++++ chapters/vi/chapter5/5.mdx | 474 ++++++++++++++++++++++++ chapters/vi/chapter5/6.mdx | 529 ++++++++++++++++++++++++++ chapters/vi/chapter5/7.mdx | 11 + chapters/vi/chapter5/8.mdx | 249 +++++++++++++ chapters/vi/chapter6/1.mdx | 14 + chapters/vi/chapter6/10.md | 278 ++++++++++++++ chapters/vi/chapter6/2.mdx | 257 +++++++++++++ chapters/vi/chapter6/3.md | 473 ++++++++++++++++++++++++ chapters/vi/chapter6/3b.md | 642 ++++++++++++++++++++++++++++++++ chapters/vi/chapter6/4.md | 123 ++++++ chapters/vi/chapter6/5.md | 360 ++++++++++++++++++ chapters/vi/chapter6/6.md | 374 +++++++++++++++++++ chapters/vi/chapter6/7.md | 381 +++++++++++++++++++ chapters/vi/chapter6/8.md | 565 ++++++++++++++++++++++++++++ chapters/vi/chapter6/9.mdx | 11 + 20 files changed, 6002 insertions(+) create mode 100644 chapters/vi/chapter5/1.mdx create mode 100644 chapters/vi/chapter5/2.mdx create mode 100644 chapters/vi/chapter5/3.mdx create mode 100644 chapters/vi/chapter5/4.mdx create mode 100644 chapters/vi/chapter5/5.mdx create mode 100644 chapters/vi/chapter5/6.mdx create mode 100644 chapters/vi/chapter5/7.mdx create mode 100644 chapters/vi/chapter5/8.mdx create mode 100644 chapters/vi/chapter6/1.mdx create mode 100644 chapters/vi/chapter6/10.md create mode 100644 chapters/vi/chapter6/2.mdx create mode 100644 chapters/vi/chapter6/3.md create mode 100644 chapters/vi/chapter6/3b.md create mode 100644 chapters/vi/chapter6/4.md create mode 100644 chapters/vi/chapter6/5.md create mode 100644 chapters/vi/chapter6/6.md create mode 100644 chapters/vi/chapter6/7.md create mode 100644 chapters/vi/chapter6/8.md create mode 100644 chapters/vi/chapter6/9.mdx diff --git a/chapters/vi/_toctree.yml b/chapters/vi/_toctree.yml index 6bf3d27eb..46a3c7d40 100644 --- a/chapters/vi/_toctree.yml +++ b/chapters/vi/_toctree.yml @@ -80,6 +80,52 @@ title: Đố vui cuối chương quiz: 4 +- title: 5. Thư viện 🤗 Datasets + sections: + - local: chapter5/1 + title: Giới thiệu + - local: chapter5/2 + title: Nếu như dữ liệu của ta không trên Hub thì sao? + - local: chapter5/3 + title: Sắp xếp dữ liệu + - local: chapter5/4 + title: Dữ liệu lớn? 🤗 Bộ dữ liệu để giải cứu! + - local: chapter5/5 + title: Tạo tập dữ liệu của riêng bạn + - local: chapter5/6 + title: Tìm kiếm ngữ nghĩa với FAISS + - local: chapter5/7 + title: 🤗 Datasets, kiểm tra nào! + - local: chapter5/8 + title: Đố vui cuối chương + quiz: 5 + +- title: 6. Thư viện 🤗 Tokenizers + sections: + - local: chapter6/1 + title: Giới thiệu + - local: chapter6/2 + title: Huấn luyện một tokenizer mới từ cái cũ + - local: chapter6/3 + title: Sức mạnh đặc biệt của tokenizer nhanh + - local: chapter6/3b + title: Tokenizer nhanh trong pipeline QA + - local: chapter6/4 + title: Chuẩn hoá và tiền tokenize + - local: chapter6/5 + title: Byte-Pair Encoding tokenization + - local: chapter6/6 + title: WordPiece tokenization + - local: chapter6/7 + title: Unigram tokenization + - local: chapter6/8 + title: Xây dựng từng khối tokenizer + - local: chapter6/9 + title: Tokenizers, kiểm tra nào! + - local: chapter6/10 + title: Đố vui cuối chương + quiz: 6 + - title: Sự kiện Khoá học Hugging Face sections: - local: event/1 diff --git a/chapters/vi/chapter5/1.mdx b/chapters/vi/chapter5/1.mdx new file mode 100644 index 000000000..d918cb67e --- /dev/null +++ b/chapters/vi/chapter5/1.mdx @@ -0,0 +1,17 @@ +# Giới thiệu + +Trong [Chương 3](/course/chapter3), bạn sẽ lần đầu được trải nghiệm thư viện 🤗 Datasets và thấy rằng có ba bước chính khi tinh chỉnh một mô hình: + +1. Tải tập dữ liệu từ Hugging Face Hub. +2. Tiền xử lý dữ liệu với `Dataset.map()`. +3. Tải và tính toán các chỉ số. + +Nhưng đây chỉ là bề nổi của những gì 🤗 Datasets có thể làm! Trong chương này, chúng ta sẽ đi sâu vào thư viện. Trong hành trình này, chúng ta sẽ tìm câu trả lời cho những câu hỏi sau: + +* Bạn làm gì khi bộ dữ liệu của bạn không có trên Hub? +* Làm thế nào bạn có thể chia một bộ dữ liệu? (Và điều gì sẽ xảy ra nếu bạn _thực sự_ cần sử dụng Pandas?) +* Bạn sẽ làm gì khi bộ dữ liệu của bạn rất lớn và sẽ làm tràn RAM của máy tính xách tay của bạn? +* "Bản đồ bộ nhớ" và Apache Arrow là cái quái gì vậy? +* Làm cách nào bạn có thể tạo bộ dữ liệu của riêng mình và đẩy nó lên Hub? + +Các kỹ thuật bạn học được ở đây sẽ giúp bạn chuẩn bị cho các tác vụ tinh chỉnh và tokenize nâng cao trong [Chương 6](/course/chapter6) và [Chương 7](/course/chapter7) - vì vậy hãy uống một ly cà phê và bắt đầu thôi! diff --git a/chapters/vi/chapter5/2.mdx b/chapters/vi/chapter5/2.mdx new file mode 100644 index 000000000..eea04d929 --- /dev/null +++ b/chapters/vi/chapter5/2.mdx @@ -0,0 +1,165 @@ +# Nếu như dữ liệu của ta không trên Hub thì sao? + + + +Bạn biết cách sử dụng [Hugging Face Hub](https://huggingface.co/datasets) để tải xuống bộ dữ liệu, nhưng bạn sẽ thấy mình thường làm việc với dữ liệu được lưu trữ trên máy tính xách tay hoặc trên máy chủ từ xa. Trong phần này, chúng tôi sẽ chỉ cho bạn cách 🤗 Datasets có thể được sử dụng để tải các tập dữ liệu không có sẵn trên Hugging Face Hub. + + + +## Làm việc với bộ dữ liệu cục bộ và từ xa + +🤗 Datasets cung cấp các tập lệnh để xử lý việc tải các tập dữ liệu cục bộ và từ xa. Nó hỗ trợ một số định dạng dữ liệu phổ biến, chẳng hạn như: + +| Định dạng dữ liệu | +Tập lệnh | Ví dụ | +| :----------------: | :------------: | :-----------------------------------------------------: | +| CSV & TSV | `csv` | `load_dataset("csv", data_files="my_file.csv")` | +| Text files | `text` | `load_dataset("text", data_files="my_file.txt")` | +| JSON & JSON Lines | `json` | `load_dataset("json", data_files="my_file.jsonl")` | +| Pickled DataFrames | `pandas` | `load_dataset("pandas", data_files="my_dataframe.pkl")` | + +Như được hiển thị trong bảng, đối với mỗi định dạng dữ liệu, chúng ta chỉ cần chỉ định loại tập lệnh tải dữ liệu trong hàm `load_dataset()`, cùng với tham số `data_files` chỉ định đường dẫn đến một hoặc nhiều tệp. Hãy bắt đầu bằng cách tải một tập dữ liệu từ các tệp cục bộ; Sau đó, chúng ta sẽ xem cách thực hiện tương tự với các tệp từ xa. + +## Tải tập dữ liệu cục bộ + +Đối với ví dụ này, chúng ta sẽ sử dụng [bộ dữ liệu SQuAD-it](https://github.com/crux82/squad-it/), là một tập dữ liệu quy mô lớn cho tác vụ hỏi đáp bằng tiếng Ý. + +Phần dữ liệu huấn luyện và kiểm thử được lưu trữ trên GitHub, vì vậy chúng tôi có thể tải chúng xuống bằng lệnh `wget` đơn giản: + +```python +!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-train.json.gz +!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-test.json.gz +``` + +Thao tác này sẽ tải xuống hai tệp nén có tên *SQuAD_it-train.json.gz* và *SQuAD_it-test.json.gz*, chúng ta có thể giải nén bằng lệnh Linux `gzip`: + +```python +!gzip -dkv SQuAD_it-*.json.gz +``` + +```bash +SQuAD_it-test.json.gz: 87.4% -- replaced with SQuAD_it-test.json +SQuAD_it-train.json.gz: 82.2% -- replaced with SQuAD_it-train.json +``` + +Chúng ta có thể thấy rằng các tệp nén đã được thay thế bằng _SQuAD_it-train.json_ và _SQuAD_it-text.json_, và dữ liệu được lưu trữ ở định dạng JSON. + + + +✎ Nếu bạn đang thắc mắc tại sao lại có ký tự`!` trong các lệnh trên, đó là bởi vì chúng ta đang chạy chúng trong một sổ ghi chép Jupyter. Chỉ cần xóa tiền tố này nếu bạn muốn tải xuống và giải nén tập dữ liệu trên terminal. + + + +Để tải tệp JSON bằng hàm `load_dataset()`, chúng ta chỉ cần biết liệu chúng ta đang xử lý JSON thông thường (tương tự như từ điển lồng nhau) hay JSON dòng (JSON được phân tách bằng dòng). Giống như nhiều bộ dữ liệu hỏi đáp, SQuAD-it sử dụng định dạng lồng nhau, với tất cả văn bản được lưu trữ trong trường `data`. Điều này có nghĩa là chúng ta có thể tải tập dữ liệu bằng cách chỉ định tham số `field` như sau: + +```py +from datasets import load_dataset + +squad_it_dataset = load_dataset("json", data_files="SQuAD_it-train.json", field="data") +``` + +Theo mặc định, việc tải các tệp cục bộ sẽ tạo ra một đối tượng `DatasetDict` với sự phân chia của `train`. Chúng ta có thể thấy điều này bằng cách kiểm tra đối tượng `squad_it_dataset`: + +```py +squad_it_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 442 + }) +}) +``` + +Điều này cho chúng ta thấy số hàng và cột được liên kết với tập huấn luyện. Chúng ta có thể xem một trong các ví dụ bằng cách lập chỉ mục vào phần tập `train` như sau: + +```py +squad_it_dataset["train"][0] +``` + +```python out +{ + "title": "Terremoto del Sichuan del 2008", + "paragraphs": [ + { + "context": "Il terremoto del Sichuan del 2008 o il terremoto...", + "qas": [ + { + "answers": [{"answer_start": 29, "text": "2008"}], + "id": "56cdca7862d2951400fa6826", + "question": "In quale anno si è verificato il terremoto nel Sichuan?", + }, + ... + ], + }, + ... + ], +} +``` + +Tuyệt, chúng ta đã tải tập dữ liệu cục bộ đầu tiên của mình! Nhưng trong khi điều này hoạt động cho tập huấn luyện, những gì chúng tôi thực sự muốn là bao gồm cả hai tập `train` và `test` trong một đối tượng `DatasetDict` duy nhất để ta có thể áp dụng `Dataset.map()` trên cả hai phần dữ liệu cùng một lúc. Để thực hiện việc này, chúng ta có thể cung cấp một từ điển cho tham số `data_files` ánh xạ từng tên phần dữ liệu với một tệp được liên kết với các phần đó: + +```py +data_files = {"train": "SQuAD_it-train.json", "test": "SQuAD_it-test.json"} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +squad_it_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 442 + }) + test: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 48 + }) +}) +``` +Đây chính xác là những gì chúng ta muốn. Giờ đây, ta có thể áp dụng nhiều kỹ thuật tiền xử lý khác nhau để làm sạch dữ liệu, mã hóa các bài đánh giá, v.v. + + + +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. + + + +Các tập lệnh tải trong 🤗 Datasets thực sự hỗ trợ giải nén tự động các tệp đầu vào, vì vậy chúng ta có thể bỏ qua việc sử dụng `gzip` bằng cách trỏ trực tiếp tham số `data_files` vào các tệp nén: + +```py +data_files = {"train": "SQuAD_it-train.json.gz", "test": "SQuAD_it-test.json.gz"} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +``` + +Điều này có thể hữu ích nếu bạn không muốn giải nén nhiều tệp GZIP theo cách thủ công. Tính năng giải nén tự động cũng áp dụng cho các định dạng phổ biến khác như ZIP và TAR, vì vậy bạn chỉ cần trỏ `data_files` đến các tệp nén và bạn đã sẵn sàng rồi! + +Bây giờ bạn đã biết cách tải các tệp cục bộ trên máy tính xách tay hoặc máy tính để bàn của mình, hãy cùng xem cách tải các tệp từ xa. + +## Tải tập dữ liệu từ xa + +Nếu bạn đang làm việc với tư cách là nhà khoa học dữ liệu hoặc lập trình viên trong một công ty, thì rất có thể các bộ dữ liệu bạn muốn phân tích được lưu trữ trên một máy chủ từ xa nào đó. May mắn thay, việc tải các tệp từ xa cũng đơn giản như tải các tệp cục bộ! Thay vì cung cấp một đường dẫn đến các tệp cục bộ, chúng ta trỏ tham số `data_files` của `load_dataset()` đến một hoặc nhiều URL nơi các tệp từ xa được lưu trữ. Ví dụ: đối với tập dữ liệu SQuAD-it được lưu trữ trên GitHub, chúng ta chỉ cần trỏ `data_files` đến các URL _SQuAD_it-*.json.gz_ như sau: + +```py +url = "https://github.com/crux82/squad-it/raw/master/" +data_files = { + "train": url + "SQuAD_it-train.json.gz", + "test": url + "SQuAD_it-test.json.gz", +} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +``` + +Điều này trả về cùng một đối tượng `DatasetDict` như ở trên, nhưng giúp ta tiết kiệm bước tải xuống và giải nén thủ công các tệp _SQuAD_it-*.json.gz_. Điều này tổng kết bước đột phá của chúng ta vào các cách khác nhau để tải các tập dữ liệu không được lưu trữ trên Hugging Face Hub. Giờ ta đã có một tập dữ liệu để nghịch, hãy bắt tay vào các kỹ thuật sắp xếp dữ liệu khác nhau thôi! + + + +✏️ **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). + + diff --git a/chapters/vi/chapter5/3.mdx b/chapters/vi/chapter5/3.mdx new file mode 100644 index 000000000..eee6bb891 --- /dev/null +++ b/chapters/vi/chapter5/3.mdx @@ -0,0 +1,739 @@ +# Sắp xếp dữ liệu + + + +Hầu hết thời gian, dữ liệu bạn làm việc sẽ chưa được chuẩn bị hoàn hảo cho các mô hình huấn luyện. Trong phần này, chúng ta sẽ khám phá các tính năng khác nhau mà 🤗 Datasets cung cấp để làm sạch các tập dữ liệu của bạn. + + + +## Sắp xếp dữ liệu của chúng ta + +Tương tự như Pandas, 🤗 Datasets cung cấp một số tính năng để thao túng nội dung của `Dataset` và `DatasetDict`. Chúng ta đã gặp phương thức `Dataset.map()` trong [Chương 3](/course/chapter3) và trong phần này, chúng ta sẽ khám phá một số hàm khác theo ý của chúng ta. + +Đối với ví dụ này, chúng tôi sẽ sử dụng [Bộ dữ liệu đánh giá thuốc](https://archive.ics.uci.edu/ml/datasets/Drug+Review+Dataset+%28Drugs.com%29) được lưu trữ trên [Kho lưu trữ Học máy UC Irvine](https://archive.ics.uci.edu/ml/index.php), chứa các đánh giá của bệnh nhân về các loại thuốc khác nhau, cùng với tình trạng đang được điều trị và xếp hạng 10 sao về mức độ hài lòng của bệnh nhân. + +Trước tiên, chúng ta cần tải xuống và giải nén dữ liệu, có thể thực hiện bằng lệnh `wget` và `unzip`: + +```py +!wget "https://archive.ics.uci.edu/ml/machine-learning-databases/00462/drugsCom_raw.zip" +!unzip drugsCom_raw.zip +``` + +Vì TSV chỉ là một biến thể của CSV sử dụng dấu tab thay vì dấu phẩy làm dấu phân cách, chúng ta có thể tải các tệp này bằng cách sử dụng tập lệnh tải `csv` và chỉ định đối số `delimiter` trong hàm `load_dataset()` như sau: + +```py +from datasets import load_dataset + +data_files = {"train": "drugsComTrain_raw.tsv", "test": "drugsComTest_raw.tsv"} +# \t is the tab character in Python +drug_dataset = load_dataset("csv", data_files=data_files, delimiter="\t") +``` + +Một thực tiễn khi thực hiện bất kỳ loại phân tích dữ liệu nào là lấy một mẫu ngẫu nhiên nhỏ để có thể cảm nhận nhanh về loại dữ liệu bạn đang làm việc. Trong 🤗 Datasets, chúng ta có thể tạo một mẫu ngẫu nhiên bằng cách xâu chuỗi các hàm `Dataset.shuffle()` và `Dataset.select()` với nhau: + +```py +drug_sample = drug_dataset["train"].shuffle(seed=42).select(range(1000)) +# Xem qua một số ví dụ đầu tiên +drug_sample[:3] +``` + +```python out +{'Unnamed: 0': [87571, 178045, 80482], + 'drugName': ['Naproxen', 'Duloxetine', 'Mobic'], + 'condition': ['Gout, Acute', 'ibromyalgia', 'Inflammatory Conditions'], + 'review': ['"like the previous person mention, I'm a strong believer of aleve, it works faster for my gout than the prescription meds I take. No more going to the doctor for refills.....Aleve works!"', + '"I have taken Cymbalta for about a year and a half for fibromyalgia pain. It is great\r\nas a pain reducer and an anti-depressant, however, the side effects outweighed \r\nany benefit I got from it. I had trouble with restlessness, being tired constantly,\r\ndizziness, dry mouth, numbness and tingling in my feet, and horrible sweating. I am\r\nbeing weaned off of it now. Went from 60 mg to 30mg and now to 15 mg. I will be\r\noff completely in about a week. The fibro pain is coming back, but I would rather deal with it than the side effects."', + '"I have been taking Mobic for over a year with no side effects other than an elevated blood pressure. I had severe knee and ankle pain which completely went away after taking Mobic. I attempted to stop the medication however pain returned after a few days."'], + 'rating': [9.0, 3.0, 10.0], + 'date': ['September 2, 2015', 'November 7, 2011', 'June 5, 2013'], + 'usefulCount': [36, 13, 128]} +``` + +Lưu ý rằng chúng ta đã sửa seed trong `Dataset.shuffle()` cho mục đích tái tạo. `Dataset.select()` mong đợi một chỉ số có thể lặp lại, vì vậy chúng ta truyền vào khoảng `range(1000)` để lấy 1,000 mẫu đầu tiên từ tập dữ liệu đã xáo trộn. Từ mẫu này, ta đã có thể thấy một số điều kỳ quặc trong tập dữ liệu: + +* Cột `Unnamed: 0` trông đáng ngờ giống như một ID ẩn danh cho mỗi bệnh nhân. +* Cột `condition` bao gồm sự kết hợp giữa các nhãn chữ hoa và chữ thường. +* Các bài đánh giá có độ dài khác nhau và chứa hỗn hợp các dấu phân tách dòng Python (`\r\n`) cũng như các mã ký tự HTML như `&\#039;`. + +Hãy xem cách chúng ta có thể sử dụng 🤗 Datasets để giải quyết từng vấn đề này. Để kiểm tra giả thuyết ID bệnh nhân cho cột `Unnamed: 0`, ta có thể sử dụng hàm `Dataset.unique()` để xác minh rằng số lượng ID khớp với số hàng trong mỗi lần tách: + +```py +for split in drug_dataset.keys(): + assert len(drug_dataset[split]) == len(drug_dataset[split].unique("Unnamed: 0")) +``` +Điều này dường như xác nhận giả thuyết của chúng tôi, vì vậy hãy dọn dẹp tập dữ liệu một chút bằng cách đổi tên cột `Unname: 0` thành một cái gì đó dễ hiểu hơn một chút. Chúng ta có thể sử dụng hàm `DatasetDict.rename_column()` để đổi tên cột trên cả hai tập con trong một lần: + +```py +drug_dataset = drug_dataset.rename_column( + original_column_name="Unnamed: 0", new_column_name="patient_id" +) +drug_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'], + num_rows: 161297 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'], + num_rows: 53766 + }) +}) +``` + + + +✏️ **Thử nghiệm thôi!** Sử dụng hàm `Dataset.unique()` để tìm số lượng thuốc độc nhất và điều kiện trong tập huấn luyện và kiểm thử. + + + +Tiếp theo, hãy chuẩn hóa tất cả các nhãn `condition` bằng cách sử dụng `Dataset.map()`. Như chúng ta đã làm với tokenize trong [Chương 3](/course/chapter3), chúng ta có thể xác định một hàm đơn giản có thể được áp dụng trên tất cả các hàng của mỗi tập trong `drug_dataset`: + +```py +def lowercase_condition(example): + return {"condition": example["condition"].lower()} + + +drug_dataset.map(lowercase_condition) +``` + +```python out +AttributeError: 'NoneType' object has no attribute 'lower' +``` + +Ồ không, chúng ta đã gặp sự cố với chức năng nối của mình! Từ lỗi, chúng ta có thể suy ra rằng một số mục nhập trong cột `condition` là `None`, không thể viết thường vì chúng không phải là chuỗi. Hãy bỏ các hàng này bằng cách sử dụng `Dataset.filter()`, hoạt động theo cách tương tự như `Dataset.map()` và mong đợi một hàm nhận được một mẫu về tập dữ liệu. Thay vì viết một hàm rõ ràng như: + +```py +def filter_nones(x): + return x["condition"] is not None +``` + +và sau đó chạy `drug_dataset.filter(filter_nones)`, chúng ta có thể thực hiện việc này trong một dòng bằng cách sử dụng _hàm lambda_. Trong Python, các hàm lambda là các hàm nhỏ mà bạn có thể định nghĩa mà không cần đặt tên rõ ràng. Chúng có dạng chung: + +``` +lambda : +``` + +ở đây `lambda` là một trong những [từ khóa](https://docs.python.org/3/reference/lexical_analysis.html#keywords) đặc biệt của Python, `` là danh sách / tập hợp các giá trị được phân tách bằng dấu phẩy xác định các đầu vào cho hàm và `` đại diện cho các hoạt động bạn muốn thực hiện. Ví dụ, chúng ta có thể định nghĩa một hàm lambda đơn giản bình phương một số như sau: + +``` +lambda x : x * x +``` + +Để áp dụng hàm này cho một đầu vào, chúng ta cần đặt nó và đầu vào trong dấu ngoặc đơn: + +```py +(lambda x: x * x)(3) +``` + +```python out +9 +``` + +Tương tự, chúng ta có thể định nghĩa các hàm lambda với nhiều tham số bằng cách phân tách chúng bằng dấu phẩy. Ví dụ, chúng ta có thể tính diện tích của một tam giác như sau: + +```py +(lambda base, height: 0.5 * base * height)(4, 8) +``` + +```python out +16.0 +``` + +Các hàm Lambda rất hữu ích khi bạn muốn định nghĩa các hàm nhỏ, sử dụng một lần (để biết thêm thông tin về chúng, chúng tôi khuyên bạn nên đọc [Hướng dẫn Python đích thực](https://realpython.com/python-lambda/) của Andre Burgaud). Trong ngữ cảnh 🤗 Datasets, chúng ta có thể sử dụng các hàm lambda để xác định các hoạt động nối và lọc đơn giản, vì vậy hãy sử dụng thủ thuật này để loại bỏ các phần `None` trong tập dữ liệu: + +```py +drug_dataset = drug_dataset.filter(lambda x: x["condition"] is not None) +``` + +Với `None` đã bị xóa, chúng ta có thể chuẩn hóa cột `condition`: + +```py +drug_dataset = drug_dataset.map(lowercase_condition) +# Kiểm tra xem chữ viết thường đã hoạt động chưa +drug_dataset["train"]["condition"][:3] +``` + +```python out +['left ventricular dysfunction', 'adhd', 'birth control'] +``` + +Nó hoạt động! Vậy là chúng ta đã làm sạch các nhãn, giờ chúng ta hãy xem xét việc làm sạch các bài đánh giá. + +## Tạo ra các cột mới + +Bất cứ khi nào bạn xử lý các bài đánh giá của khách hàng, một phương pháp hay đó là kiểm tra số lượng từ trong mỗi bài đánh giá. Bài đánh giá có thể chỉ là một từ duy nhất như "Tuyệt vời!" hoặc một bài luận đầy đủ với hàng nghìn từ, và tùy thuộc vào trường hợp sử dụng, bạn sẽ cần xử lý những thái cực này theo cách khác nhau. Để tính toán số lượng từ trong mỗi bài đánh giá, chúng tôi sẽ sử dụng phương pháp phỏng đoán sơ bộ dựa trên việc tách từng văn bản theo khoảng trắng. + +Hãy định nghĩa một hàm đơn giản đếm số từ trong mỗi bài đánh giá: + +```py +def compute_review_length(example): + return {"review_length": len(example["review"].split())} +``` + +Không giống như hàm `lowercase_condition()`, `compute_review_length()` trả về một từ điển có khóa không tương ứng với một trong các tên cột trong tập dữ liệu. Trong trường hợp này, khi `compute_review_length()` được truyền vào `Dataset.map()`, nó sẽ được áp dụng cho tất cả các hàng trong tập dữ liệu để tạo cột mới `review_length`: + +```py +drug_dataset = drug_dataset.map(compute_review_length) +# Kiểm tra mẫu huấn luyện đầu tiên +drug_dataset["train"][0] +``` + +```python out +{'patient_id': 206461, + 'drugName': 'Valsartan', + 'condition': 'left ventricular dysfunction', + 'review': '"It has no side effect, I take it in combination of Bystolic 5 Mg and Fish Oil"', + 'rating': 9.0, + 'date': 'May 20, 2012', + 'usefulCount': 27, + 'review_length': 17} +``` + +Như mong đợi, chúng ta có thể thấy cột `review_length` đã được thêm vào tập huấn luyện của chúng ta. Chúng ta có thể sắp xếp cột mới này với `Dataset.sort()` để xem các giá trị cực đại trông như thế nào: + +```py +drug_dataset["train"].sort("review_length")[:3] +``` + +```python out +{'patient_id': [103488, 23627, 20558], + 'drugName': ['Loestrin 21 1 / 20', 'Chlorzoxazone', 'Nucynta'], + 'condition': ['birth control', 'muscle spasm', 'pain'], + 'review': ['"Excellent."', '"useless"', '"ok"'], + 'rating': [10.0, 1.0, 6.0], + 'date': ['November 4, 2008', 'March 24, 2017', 'August 20, 2016'], + 'usefulCount': [5, 2, 10], + 'review_length': [1, 1, 1]} +``` + +Như ta đã nghi vấn, một số đánh giá chỉ chứa một từ duy nhất, mặc dù có thể ổn để phân tích sắc thái, nhưng sẽ không có nhiều thông tin nếu chúng tôi muốn dự đoán tình trạng bệnh. + + + +🙋 Một cách thay thế để thêm các cột mới vào tập dữ liệu là sử dụng hàm `Dataset.add_column()`. Điều này cho phép bạn cung cấp cột dưới dạng danh sách Python hoặc mảng NumPy và có thể hữu ích trong các trường hợp mà `Dataset.map()` không phù hợp cho phân tích của bạn. + + + +Hãy sử dụng hàm `Dataset.filter()` để xóa các bài đánh giá có ít hơn 30 từ. Tương tự như những gì chúng ta đã làm với cột `condition`, chúng ta có thể lọc ra các bài đánh giá rất ngắn bằng cách yêu cầu các bài đánh giá có độ dài trên ngưỡng này: + +```py +drug_dataset = drug_dataset.filter(lambda x: x["review_length"] > 30) +print(drug_dataset.num_rows) +``` + +```python out +{'train': 138514, 'test': 46108} +``` + +Như bạn có thể thấy, điều này đã loại bỏ khoảng 15% bài đánh giá khỏi bộ huấn luyện và kiểm thử ban đầu. + + + +✏️ **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. + + + +Điều cuối cùng chúng ta cần giải quyết là sự hiện diện của ký tự HTML trong các bài đánh giá của chúng ta. Chúng ta có thể sử dụng mô-đun `html` của Python để loại bỏ qua các ký tự này, như sau: + +```py +import html + +text = "I'm a transformer called BERT" +html.unescape(text) +``` + +```python out +"I'm a transformer called BERT" +``` + +Ta sẽ sử dụng `Dataset.map()` để hủy tất cả các ký tự HTML trong kho tài liệu của mình: + +```python +drug_dataset = drug_dataset.map(lambda x: {"review": html.unescape(x["review"])}) +``` + +Như bạn có thể thấy, phương thức `Dataset.map()` khá hữu ích để xử lý dữ liệu - và chúng ta thậm chí còn chưa rõ tất mọi thứ mà nó có thể làm! + +## Siêu sức mạnh của hàm `map()` + +Phương thức `Dataset.map ()` nhận tham số `batched`, nếu được đặt thành `True`, nó sẽ gửi một loạt các mẫu đến hàm map cùng một lúc (ta có thể cấu hình kích thước lô nhưng mặc định là 1,000). Ví dụ: hàm map trước đó loại bỏ tất cả HTML đã mất một chút thời gian để chạy (bạn có thể đọc thời gian thực hiện từ các thanh tiến trình). Chúng ta có thể tăng tốc độ này bằng cách xử lý một số phần tử cùng lúc thông qua sử dụng bao hàm. + +Khi bạn chỉ định `batched=True`, hàm sẽ nhận một từ điển với các trường của tập dữ liệu, nhưng mỗi giá trị bây giờ là một _danh sách các giá trị_ và không chỉ là một giá trị duy nhất. Giá trị trả về của `Dataset.map()` phải giống nhau: một từ điển với các trường ta muốn cập nhật hoặc thêm vào tập dữ liệu của mình và một danh sách các giá trị. Ví dụ: đây là một cách khác để hủy tất cả các ký tự HTML, nhưng sử dụng `batched=True`: + +```python +new_drug_dataset = drug_dataset.map( + lambda x: {"review": [html.unescape(o) for o in x["review"]]}, batched=True +) +``` + +Nếu bạn đang chạy đoạn mã này trên notebook, bạn sẽ thấy rằng lệnh này thực thi nhanh hơn lệnh trước đó. Và đó không phải là do các bài đánh giá của chúng tôi đã được loại đi HTML - nếu bạn thực hiện lại hướng dẫn từ phần trước (không có `batch = True`), nó sẽ mất cùng một khoảng thời gian như trước. Điều này là do việc bao hàm thường nhanh hơn việc thực thi cùng một đoạn mã trong vòng lặp `for` và chúng ta cũng đạt được một số hiệu suất bằng cách truy cập nhiều phần tử cùng một lúc thay vì từng phần tử một. + +Sử dụng `Dataset.map()` với `batched=True` sẽ là điều cần thiết để mở khóa tốc độ của các trình tokenize "nhanh" mà chúng ta sẽ gặp trong [Chương 6](/course/chap6), có thể nhanh chóng tokenize các danh sách lớn các văn bản. Ví dụ: để tokenize tất cả các đánh giá thuốc bằng trình tokenize nhanh, ta có thể sử dụng một hàm như sau: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") + + +def tokenize_function(examples): + return tokenizer(examples["review"], truncation=True) +``` + +Như bạn đã thấy trong [Chương 3](/course/chapter3), chúng ta có thể truyền vào một hoặc nhiều mẫu cho tokenizer, vì vậy ta có thể sử dụng hàm này với `batched=True` hoặc không. Hãy cũng coi đây là một cơ hội để so sánh hiệu năng của hai tuỳ chọn này. Trong một notebook, bạn có thể bấm giờ chỉ với một dòng lệnh `%time` trước dòng mã bạn muốn tình thời gian: + +```python no-format +%time tokenized_dataset = drug_dataset.map(tokenize_function, batched=True) +``` + +Bạn cũng có thể tính thời gian cho toàn bộ ô bằng cách đặt `%%time` ở đầu của ô mã. Trên phần cứng mà chúng ta thực hiện, nó hiển thị 10.8 giây cho lệnh này (đó là số được viết sau "Wall time"). + + + +✏️ **Thử nghiệm thôi!** Thực hiện cùng một hướng dẫn có và không có `batched=True`, sau đó thử nó với tokenizer chậm (thêm `use_fast=False` vào `AutoTokenizer.from_pretrained()`) để bạn có thể thấy giá trị bạn nhận được trên phần cứng của mình. + + + +Dưới đây là kết quả thu được khi có và không có tính năng phân lô, với tokenizer nhanh và chậm: + +Tuỳ chọn | Tokenizer nhanh | Tokenizer chậm +:--------------:|:--------------:|:-------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s + +Điều này có nghĩa là việc sử dụng một tokenizer nhanh với tùy chọn `batched=True` sẽ nhanh hơn 30 lần so với phiên bản chậm mà không có lô - điều này thực sự tuyệt vời! Đó là lý do chính tại sao tokenizer nhanh là mặc định khi sử dụng `AutoTokenizer` (và tại sao chúng được gọi là "nhanh"). Chúng có thể đạt được tốc độ như vậy bởi vì phía sau, đoạn mã token hóa được thực thi bằng Rust, đây là một ngôn ngữ giúp dễ dàng thực hiện đoạn mã song song. + +Song song hóa cũng là lý do giải thích cho tốc độ tăng gần gấp 6 lần mà trình tokenize nhanh đạt được với việc phân lô: bạn không thể song song một thao tác tokenize đơn lẻ, nhưng khi bạn muốn tokenize nhiều văn bản cùng một lúc, bạn có thể chỉ cần chia nhỏ việc thực thi trên nhiều quy trình, mỗi người chịu trách nhiệm về các văn bản của riêng mình. + +`Dataset.map()` cũng tự có một số khả năng tính toán song song. Vì chúng không được hỗ trợ bởi Rust, nên chúng sẽ không để một trình tokenizer chậm bắt kịp với một tokenizer nhanh, nhưng chúng vẫn có thể hữu ích (đặc biệt nếu bạn đang sử dụng một tokenizer không có phiên bản nhanh). Để bật xử lý đa luồng, hãy sử dụng tham số `num_proc` và chỉ định số lượng quy trình sẽ sử dụng trong lệnh gọi của bạn tới `Dataset.map()`: + +```py +slow_tokenizer = AutoTokenizer.from_pretrained("bert-base-cased", use_fast=False) + + +def slow_tokenize_function(examples): + return slow_tokenizer(examples["review"], truncation=True) + + +tokenized_dataset = drug_dataset.map(slow_tokenize_function, batched=True, num_proc=8) +``` + +Bạn có thể thử nghiệm một chút với thời gian để xác định số lượng quy trình tối ưu để sử dụng; trong trường hợp của chúng ta, 8 dường như tạo ra tốc độ tăng tốt nhất. Dưới đây là những con số chúng tôi nhận được khi có và không có xử lý đa luồng: + +Tuỳ chọn | Tokenizer nhanh | Tokenizer chậm +:--------------:|:--------------:|:-------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s +`batched=True`, `num_proc=8` | 6.52s | 41.3s +`batched=False`, `num_proc=8` | 9.49s | 45.2s + +Đó là những kết quả hợp lý hơn nhiều đối với tokenizer chậm, nhưng hiệu suất của tokenizer nhanh cũng đã được cải thiện đáng kể. Tuy nhiên, lưu ý rằng điều đó không phải lúc nào cũng đúng - đối với các giá trị của `num_proc` khác 8, các thử nghiệm của chúng tôi cho thấy rằng sử dụng `batched=True` mà không có tùy chọn này sẽ nhanh hơn. Nói chung, chúng tôi khuyên bạn không nên sử dụng xử lý đa luồng Python cho các trình tokenize nhanh với `batched=True`. + + + +Sử dụng `num_proc` để tăng tốc quá trình xử lý của bạn thường là một ý tưởng tuyệt vời, miễn là hàm bạn đang sử dụng chưa thực hiện một số kiểu xử lý đa xử lý của riêng nó. + + + +Tất cả các chức năng này được cô đọng trong một phương pháp đã khá tuyệt vời, nhưng còn nhiều hơn thế nữa! Với `Dataset.map()` và `batched=True`, bạn có thể thay đổi số lượng phần tử trong tập dữ liệu của mình. Điều này cực kỳ hữu ích trong nhiều trường hợp mà bạn muốn tạo một số đặc trưng huấn luyện từ một mẫu và chúng ta sẽ cần thực hiện điều này như một phần của quá trình tiền xử lý cho một số tác vụ NLP sẽ thực hiện trong [Chương 7](/course/chapter7). + + + +💡 Trong học máy, một _mẫu_ thường được định nghĩa là tập hợp _đặc trưng_ mà chúng ta cung cấp cho mô hình. Trong một số ngữ cảnh, các đặc trưng này sẽ là tập hợp thành các cột trong `Dataset`, nhưng trong các trường hợp khác (như ở đây và để phục vụ hỏi đáp), nhiều đặc trưng có thể được trích xuất từ một mẫu và thuộc về một cột duy nhất. + + + +Chúng ta hãy xem nó hoạt động như thế nào! Ở đây, ta sẽ tokenize các mẫu của mình và cắt chúng về độ dài tối đa là 128, nhưng ta sẽ yêu cầu trình tokenize trả về *tất cả* các đoạn văn bản thay vì chỉ đoạn văn bản đầu tiên. Điều này có thể được thực hiện với `return_overflowing_tokens=True`: + +```py +def tokenize_and_split(examples): + return tokenizer( + examples["review"], + truncation=True, + max_length=128, + return_overflowing_tokens=True, + ) +``` + +Hãy kiểm tra điều này trên một mẫu trước khi sử dụng `Dataset.map()` trên toàn bộ tập dữ liệu: + +```py +result = tokenize_and_split(drug_dataset["train"][0]) +[len(inp) for inp in result["input_ids"]] +``` + +```python out +[128, 49] +``` + +Vì vậy, mẫu đầu tiên trong tập huấn luyện đã trở thành hai đặc trưng vì nó đã được tokenize nhiều hơn số lượng token tối đa mà chúng tôi đã chỉ định: cái đầu tiên có độ dài 128 và cái thứ hai có độ dài 49. Bây giờ hãy làm điều này cho tất cả các phần tử của tập dữ liệu! + +```py +tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) +``` + +```python out +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. + +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`: + +```py +tokenized_dataset = drug_dataset.map( + tokenize_and_split, batched=True, remove_columns=drug_dataset["train"].column_names +) +``` + +Bây giờ nó hoạt động mà không có lỗi. Chúng ta có thể kiểm tra xem tập dữ liệu mới của mình có nhiều phần tử hơn tập dữ liệu gốc hay không bằng cách so sánh độ dài: + +```py +len(tokenized_dataset["train"]), len(drug_dataset["train"]) +``` + +```python out +(206772, 138514) +``` + +Chúng tôi đã đề cập rằng chúng ta cũng có thể giải quyết vấn đề chiều dài không khớp bằng cách làm cho các cột cũ có cùng kích thước với các cột mới. Để thực hiện việc này, chúng ta sẽ cần trường `overflow_to_sample_mapping` mà tokenizer trả về khi chúng ta đặt `return_overflowing_tokens=True`. Nó cung cấp cho chúng ta một ánh xạ từ một chỉ mục đặc trưng mới đến chỉ mục của mẫu mà nó bắt nguồn từ đó. Sử dụng điều này, chúng ta có thể liên kết mỗi khóa có trong tập dữ liệu ban đầu với một danh sách các giá trị có kích thước phù hợp bằng cách lặp lại các giá trị của mỗi ví dụ nhiều lần khi nó tạo ra các đặc trưng mới: + +```py +def tokenize_and_split(examples): + result = tokenizer( + examples["review"], + truncation=True, + max_length=128, + return_overflowing_tokens=True, + ) + # Extract mapping between new and old indices + sample_map = result.pop("overflow_to_sample_mapping") + for key, values in examples.items(): + result[key] = [values[i] for i in sample_map] + return result +``` + +Chúng ta có thể thấy nó hoạt động với `Dataset.map()` mà chúng ta không cần xóa các cột cũ: + +```py +tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) +tokenized_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'], + num_rows: 206772 + }) + test: Dataset({ + features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'], + num_rows: 68876 + }) +}) +``` + +Chúng ta nhận được cùng số đặc trưng huấn luyện như trước đó, nhưng ở đây ta đã giữ lại tất cả các trường cũ. Nếu bạn cần chúng để hậu xử lý sau khi áp dụng mô hình của mình, bạn có thể muốn sử dụng phương pháp này. + +Bây giờ bạn đã thấy cách 🤗 Datasets có thể được sử dụng để tiền xử lý một tập dữ liệu theo nhiều cách khác nhau. Mặc dù các chức năng xử lý của 🤗 Datasets sẽ đáp ứng hầu hết các nhu cầu huấn luyện mô hình của bạn, +có thể đôi khi bạn cần chuyển sang Pandas để truy cập các tính năng mạnh mẽ hơn, chẳng hạn như `DataFrame.groupby()` hoặc các API cấp cao để trực quan hóa. May mắn thay, 🤗 Datasets được thiết kế để có thể tương tác với các thư viện như Pandas, NumPy, PyTorch, TensorFlow và JAX. Chúng ta hãy xem cách này hoạt động như thế nào. + +## Từ `Dataset` tới `DataFrame` và ngược lại + + + +Để cho phép chuyển đổi giữa các thư viện bên thứ ba khác nhau, 🤗 Datasets cung cấp hàm `Dataset.set_format()`. Hàm này chỉ thay đổi _định dạng đầu ra_ của tập dữ liệu, vì vậy bạn có thể dễ dàng chuyển sang định dạng khác mà không ảnh hưởng đến _định dạng đầu ra_ bên dưới, đó là Apache Arrow. Việc định dạng được thực hiện tại chỗ. Để chứng minh, hãy chuyển đổi tập dữ liệu của chúng tôi thành Pandas: + +```py +drug_dataset.set_format("pandas") +``` + +Giờ khi chúng ta truy cập các phần tử của tập dữ liệu, ta nhận được `pandas.DataFrame` thay vì từ điển: + +```py +drug_dataset["train"][:3] +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
patient_iddrugNameconditionreviewratingdateusefulCountreview_length
095260Guanfacineadhd"My son is halfway through his fourth week of Intuniv..."8.0April 27, 2010192141
192703Lybrelbirth control"I used to take another oral contraceptive, which had 21 pill cycle, and was very happy- very light periods, max 5 days, no other side effects..."5.0December 14, 200917134
2138000Ortho Evrabirth control"This is my first time using any form of birth control..."8.0November 3, 20151089
+ +Hãy tạo ra một `pandas.DataFrame` cho toàn bộ tập huấn luyện bằng cách chọn tất cả các phần tử trong `drug_dataset["train"]`: + +```py +train_df = drug_dataset["train"][:] +``` + + + +🚨 Bên dưới `Dataset.set_format()` thay đổi định dạng trả về cho phương thức `__getitem __()` của tập dữ liệu. Điều này có nghĩa là khi chúng ta muốn tạo một đối tượng mới như `train_df` từ `Dataset` ở định dạng `"pandas"`, chúng ta cần cắt toàn bộ tập dữ liệu để có được một `pandas.DataFrame`. Bạn có thể tự xác minh xem kiểu dữ liệu của `drug_dataset["train"]` có phải là `Dataset`, bất kể định dạng đầu ra là gì. + + + +Từ đây, ta có thể sử dụng tất cả các chức năng của Pandas mà ta muốn. Ví dụ, chúng ta có thể thực hiện chuỗi lạ mắt để tính toán phân phối lớp giữa các `condition`: + +```py +frequencies = ( + train_df["condition"] + .value_counts() + .to_frame() + .reset_index() + .rename(columns={"index": "condition", "condition": "frequency"}) +) +frequencies.head() +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
conditionfrequency
0birth control27655
1depression8023
2acne5209
3anxiety4991
4pain4744
+ +Và khi chúng ta hoàn thành phân tích Pandas của mình, chúng ta luôn có thể tạo một đối tượng `Dataset` mới bằng cách sử dụng hàm `Dataset.from_pandas()` như sau: + +```py +from datasets import Dataset + +freq_dataset = Dataset.from_pandas(frequencies) +freq_dataset +``` + +```python out +Dataset({ + features: ['condition', 'frequency'], + num_rows: 819 +}) +``` + + + +✏️ **Thử nghiệm thôi!** Tính xếp hạng trung bình cho mỗi loại thuốc và lưu trữ kết quả ở dạng `Dataset` mới. + + + +Phần này kết thúc chuyến tham quan của chúng ta về các kỹ thuật tiền xử lý khác nhau có sẵn trong 🤗 Datasets. Để hoàn thiện phần này, hãy tạo một tệp kiểm định để chuẩn bị tập dữ liệu cho việc huấn luyện một trình phân loại. Trước khi làm như vậy, chúng ta sẽ đặt lại định dạng đầu ra của `drug_dataset` từ `"pandas"` thành `"arrow"`: + +```python +drug_dataset.reset_format() +``` + +## Tạo ra một tệp kiểm định + +Mặc dù chúng ta có một bộ dữ liệu kiểm thử có thể sử dụng để đánh giá, nhưng bạn nên giữ nguyên bộ kiểm thử và tạo một bộ kiểm định riêng trong quá trình phát triển. Khi bạn hài lòng với hiệu suất của các mô hình của mình trên bộ kiểm định, bạn có thể thực hiện kiểm tra lần cuối đối với bộ kiểm thử. Quy trình này giúp giảm thiểu rủi ro rằng bạn sẽ trang bị quá mức cho bộ kiểm thử và triển khai một mô hình không thành công trên dữ liệu trong thế giới thực. + +🤗 Datasets cung cấp một hàm `Dataset.train_test_split()` dựa trên tính năng nổi tiếng từ `scikit-learn`. Hãy cùng dùng nó để chia tập huấn luyện thành các tập `train` và `validation` (ta đặt tham số `seed` cho mục đính tái tạo): + +```py +drug_dataset_clean = drug_dataset["train"].train_test_split(train_size=0.8, seed=42) +# Thay đổi tên mặc định "test" thành "validation" +drug_dataset_clean["validation"] = drug_dataset_clean.pop("test") +# Thêm "test" vào `DatasetDict` +drug_dataset_clean["test"] = drug_dataset["test"] +drug_dataset_clean +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 110811 + }) + validation: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 27703 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 46108 + }) +}) +``` + +Tuyệt vời, ta hiện đã chuẩn bị một tập dữ liệu sẵn sàng để huấn luyện một số mô hình! Trong [phần 5](/course/chapter5/5), chúng tôi sẽ chỉ cho bạn cách tải tập dữ liệu lên Hugging Face Hub, nhưng bây giờ hãy quen với phân tích của chúng tôi bằng cách xem xét một số cách bạn có thể lưu tập dữ liệu trên máy cục bộ của mình. + +## Lưu một bộ dữ liệu + + + +Mặc dù 🤗 Datasets sẽ lưu vào bộ nhớ cache mọi tập dữ liệu đã tải xuống và các hoạt động được thực hiện trên nó, nhưng đôi khi bạn sẽ muốn lưu tập dữ liệu vào đĩa (ví dụ: trong trường hợp bộ nhớ cache bị xóa). Như thể hiện trong bảng bên dưới, 🤗 Datasets cung cấp ba chức năng chính để lưu tập dữ liệu của bạn ở các định dạng khác nhau: + +| Định dạng dữ liệu | Hàm | +| :---------: | :--------------------: | +| Arrow | `Dataset.save_to_disk()` | +| CSV | `Dataset.to_csv()` | +| JSON | `Dataset.to_json()` | + +Ví dụ, hãy cùng lưu dữ liệu sạch của chúng ta về định dạng Arrow: + +```py +drug_dataset_clean.save_to_disk("drug-reviews") +``` + +Nó sẽ tạo ra một kho lưu trữ với cấu trúc như sau: + +``` +drug-reviews/ +├── dataset_dict.json +├── test +│ ├── dataset.arrow +│ ├── dataset_info.json +│ └── state.json +├── train +│ ├── dataset.arrow +│ ├── dataset_info.json +│ ├── indices.arrow +│ └── state.json +└── validation + ├── dataset.arrow + ├── dataset_info.json + ├── indices.arrow + └── state.json +``` + +nơi chúng ta có thể thấy rằng mỗi phần tách ra được liên kết với bảng *dataset.arrow* của riêng nó và một số siêu dữ liệu trong *dataset_info.json* và *state.json*. Bạn có thể coi định dạng Arrow như một bảng gồm các cột và hàng ưa thích được tối ưu hóa để xây dựng các ứng dụng hiệu suất cao xử lý và vận chuyển các tập dữ liệu lớn. + +Sau khi tập dữ liệu được lưu, chúng ta có thể tải nó bằng cách sử dụng hàm `load_from_disk()` như sau: + +```py +from datasets import load_from_disk + +drug_dataset_reloaded = load_from_disk("drug-reviews") +drug_dataset_reloaded +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 110811 + }) + validation: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 27703 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 46108 + }) +}) +``` +Đối với định dạng CSV và JSON, chúng ta phải lưu trữ từng phần thành một tệp riêng biệt. Một cách để làm điều này là lặp lại các khóa và giá trị trong đối tượng `DatasetDict`: + +```py +for split, dataset in drug_dataset_clean.items(): + dataset.to_json(f"drug-reviews-{split}.jsonl") +``` + +Nó sẽ lưu mỗi phần dữ liệu vào[định dạng JSON Lines](https://jsonlines.org), nơi mỗi dòng trong bộ dữ liệu được lưu trữ trên một dòng JSON. Đây là một ví dụ về hình hài cua nó: + +```py +!head -n 1 drug-reviews-train.jsonl +``` + +```python out +{"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} +``` + +Chúng ta sau đó có thể sử dụng các kỹ thuật trong [phần 2](/course/chapter5/2) để tải tệp JSON như sau: + +```py +data_files = { + "train": "drug-reviews-train.jsonl", + "validation": "drug-reviews-validation.jsonl", + "test": "drug-reviews-test.jsonl", +} +drug_dataset_reloaded = load_dataset("json", data_files=data_files) +``` + +Và đó là nó cho chuyến du ngoạn của chúng ta với sắp xếp dữ liệu sử dụng 🤗 Datasets! Giờ ta đã có một tập dữ liệu đã được làm sạch để huấn luyện mô hình, đây là một vài ý tưởng mà bạn có thể thử: + +1. Sử dụng các kỹ thuật từ [Chương 3](/course/chapter3) để huấn luyện một bộ phân loại có thể dự đoán tình trạng bệnh nhân dựa trên các phản hồi về thuốc. +2. Sử dụng pipeline `summarization` từ [Chương 1](/course/chapter1) để tạo các bản tóm tắt các bài đánh giá. + +Tiếp theo, chúng ta sẽ xem xét cách 🤗 Datasets có thể cho phép bạn làm việc với những tập dữ liệu khổng lồ mà không làm hỏng máy tính xách tay của bạn! diff --git a/chapters/vi/chapter5/4.mdx b/chapters/vi/chapter5/4.mdx new file mode 100644 index 000000000..b5a2b2348 --- /dev/null +++ b/chapters/vi/chapter5/4.mdx @@ -0,0 +1,294 @@ +# Dữ liệu lớn? 🤗 Bộ dữ liệu để giải cứu! + + + +Ngày nay, không có gì lạ khi bạn đang làm việc với các bộ dữ liệu nhiều gigabyte, đặc biệt nếu bạn đang có kế hoạch huấn luyện trước một mô hình Transformer như BERT hoặc GPT-2 từ đầu. Trong những trường hợp này, thậm chí _tải_ dữ liệu có thể là một thách thức. Ví dụ: kho dữ liệu WebText được sử dụng để huấn luyện trước GPT-2 bao gồm hơn 8 triệu tài liệu và 40 GB văn bản - việc tải dữ liệu này vào RAM của máy tính xách tay của bạn có thể khiến bạn bị đau tim! + +May mắn thay, 🤗 Datasets đã được thiết kế để khắc phục những hạn chế này. Nó giải phóng bạn khỏi các vấn đề về quản lý bộ nhớ bằng cách coi các tập dữ liệu là tệp _ánh xạ bộ nhớ_ và thoát khỏi giới hạn ổ cứng bằng cách _truyền tải trực tiếp_ các mục trong một kho ngữ liệu. + + + +Trong phần này, chúng ta sẽ khám phá các tính năng này của 🤗 Datasets với kho dữ liệu 825 GB khổng lồ được gọi là [Pile](https://pile.eleuther.ai). Bắt đầu thôi! + +## Pile là gì? + +The Pile là một kho ngữ liệu tiếng Anh được tạo ra bởi [EleutherAI](https://www.eleuther.ai) để huấn luyện các mô hình ngôn ngữ quy mô lớn. Nó bao gồm một loạt các bộ dữ liệu, các bài báo khoa học trải dài, kho mã GitHub và văn bản web được lọc. Kho tài liệu huấn luyện có sẵn trong [khối 14GB](https://mystic.the-eye.eu/public/AI/pile/) và bạn cũng có thể tải xuống một số [thành phần riêng lẻ](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/). Hãy bắt đầu bằng cách xem qua tập dữ liệu PubMed Abstracts, tập dữ liệu tóm tắt từ 15 triệu ấn phẩm y sinh trên [PubMed](https://pubmed.ncbi.nlm.nih.gov/). Tập dữ liệu ở [định dạng JSON Lines](https://jsonlines.org) và được nén bằng thư viện `zstandard`, vì vậy trước tiên chúng ta cần cài đặt: + +```py +!pip install zstandard +``` + +Tiếp theo, chúng ta có thể tải tập dữ liệu bằng phương pháp cho các tệp từ xa mà chúng ta đã học trong [phần 2](/course/chapter5/2): + +```py +from datasets import load_dataset + +# Quá trình này mất một vài phút để chạy, vì vậy hãy làm cốc trà hoặc cà phê trong khi chờ đợi :) +data_files = "https://mystic.the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst" +pubmed_dataset = load_dataset("json", data_files=data_files, split="train") +pubmed_dataset +``` + +```python out +Dataset({ + features: ['meta', 'text'], + num_rows: 15518009 +}) +``` + +Chúng ta có thể thấy rằng có 15,518,009 hàng và 2 cột trong tập dữ liệu của chúng tôi - đó là rất nhiều! + + + +✎ 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. + + + +Hãy kiểm tra nội dung của mẫu đầu tiên: + +```py +pubmed_dataset[0] +``` + +```python out +{'meta': {'pmid': 11409574, 'language': 'eng'}, + '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 ...'} +``` + +Được rồi, đây giống như phần tóm tắt từ một bài báo y khoa. Bây giờ chúng ta hãy xem chúng ta đã sử dụng bao nhiêu RAM để tải tập dữ liệu! + +## Sự kỳ diệu của ánh xạ bộ nhớ + +Một cách đơn giản để đo mức sử dụng bộ nhớ trong Python là sử dụng thư viện [`psutil`](https://psutil.readthedocs.io/en/latest/), có thể được cài đặt bằng `pip` như sau: + +```python +!pip install psutil +``` + +Nó cung cấp một lớp `Process` cho phép chúng ta kiểm tra việc sử dụng bộ nhớ của tiến trình hiện tại như sau: + +```py +import psutil + +# Process.memory_info được biểu thị bằng bytes, sau đó chuyển sang megabytes +print(f"RAM used: {psutil.Process().memory_info().rss / (1024 * 1024):.2f} MB") +``` + +```python out +RAM used: 5678.33 MB +``` + +Ở đây thuộc tính `rss` đề cập đến _resident set size_, là phần bộ nhớ mà một tiến trình chiếm trong RAM. Phép đo này cũng bao gồm bộ nhớ được sử dụng bởi trình thông dịch Python và các thư viện mà chúng tôi đã tải, do đó, lượng bộ nhớ thực tế được sử dụng để tải tập dữ liệu nhỏ hơn một chút. Để so sánh, hãy xem tập dữ liệu trên đĩa lớn như thế nào, sử dụng thuộc tính `dataset_size`. Vì kết quả được thể hiện bằng byte như trước đây, chúng tôi cần chuyển đổi thủ công nó thành gigabyte: + +```py +print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") +size_gb = pubmed_dataset.dataset_size / (1024**3) +print(f"Dataset size (cache file) : {size_gb:.2f} GB") +``` + +```python out +Number of files in dataset : 20979437051 +Dataset size (cache file) : 19.54 GB +``` + +Tuyệt vời - mặc dù nó gần 20 GB, chúng ta có thể tải và truy cập tập dữ liệu với RAM ít hơn nhiều! + + + +✏️ **Thử nghiệm thôi!** Chọn một trong các [tập hợp con](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/) từ Pile sao cho lớn hơn RAM của máy tính xách tay hoặc máy tính để bàn của bạn, tải nó với 🤗 Datasets, và đo dung lượng RAM được sử dụng. Lưu ý rằng để có được một phép đo chính xác, bạn sẽ muốn thực hiện việc này trong một quy trình mới. Bạn có thể tìm thấy các kích thước đã giải nén của từng tập hợp con trong Bảng 1 của [bài báo về Pile](https://arxiv.org/abs/2101.00027). + + + +Nếu bạn đã quen thuộc với Pandas, kết quả này có thể gây bất ngờ vì theo [quy tắc ngón tay cái](https://wesmckinney.com/blog/apache-arrow-pandas-internals/) nổi tiếng của Wes Kinney, bạn thường cần gấp 5 gấp 10 lần RAM so với kích thước của tập dữ liệu của bạn. Vậy 🤗 Datasets giải quyết vấn đề quản lý bộ nhớ này như thế nào? 🤗 Datasets coi mỗi tập dữ liệu như một [tệp ánh xạ bộ nhớ](https://en.wikipedia.org/wiki/Memory-mapped_file), cung cấp ánh xạ giữa RAM và bộ nhớ hệ thống tệp cho phép thư viện truy cập và hoạt động trên các phần tử của tập dữ liệu mà không cần tải đầy đủ vào bộ nhớ. + +Các tệp được ánh xạ bộ nhớ cũng có thể được chia sẻ trên nhiều quy trình, cho phép các phương thức như `Dataset.map()` được thực thi song song mà không cần di chuyển hoặc sao chép tập dữ liệu. Bên cạnh đó, tất cả các khả năng này đều được thực hiện bởi định dạng bộ nhớ [Apache Arrow](https://arrow.apache.org) và thư viện[`pyarrow`](https://arrow.apache.org/docs/python/index.html), giúp tải và xử lý dữ liệu nhanh như chớp. (Để biết thêm chi tiết về Apache Arrow và so sánh với Pandas, hãy xem [Bài đăng trên blog của Dejan Simic](https://towardsdatascience.com/apache-arrow-read-dataframe-with-zero-memory-69634092b1a).) trong thực tế, hãy chạy một bài kiểm tra tốc độ nhỏ bằng cách lặp lại tất cả các phần tử trong tập dữ liệu PubMed Abstracts: + +```py +import timeit + +code_snippet = """batch_size = 1000 + +for idx in range(0, len(pubmed_dataset), batch_size): + _ = pubmed_dataset[idx:idx + batch_size] +""" + +time = timeit.timeit(stmt=code_snippet, number=1, globals=globals()) +print( + f"Iterated over {len(pubmed_dataset)} examples (about {size_gb:.1f} GB) in " + f"{time:.1f}s, i.e. {size_gb/time:.3f} GB/s" +) +``` + +```python out +'Iterated over 15518009 examples (about 19.5 GB) in 64.2s, i.e. 0.304 GB/s' +``` + +Ở đây chúng ta đã sử dụng mô-đun `timeit` của Python để đo thời gian thực thi được thực hiện bởi `code_snippet`. Thông thường, bạn sẽ có thể lặp lại tập dữ liệu với tốc độ từ vài phần mười GB/s đến vài GB/s. Điều này hoạt động hiệu quả với đại đa số các ứng dụng, nhưng đôi khi bạn sẽ phải làm việc với một tập dữ liệu quá lớn, thậm chí không thể lưu trữ trên ổ cứng của máy tính xách tay của bạn. Ví dụ: nếu chúng tôi cố gắng tải xuống toàn bộ Pile, chúng tôi sẽ cần 825 GB dung lượng đĩa trống! Để xử lý những trường hợp này, 🤗 Datasets cung cấp tính năng phát trực tuyến cho phép chúng tôi tải xuống và truy cập các phần tử một cách nhanh chóng mà không cần tải xuống toàn bộ tập dữ liệu. Chúng ta hãy xem cách này hoạt động như thế nào. + + + +💡 Trong sổ ghi chép Jupyter, bạn có thể định thời gian cho các ô bằng cách sử dụng[hàm ma thuật `%%timeit`](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit). + + + +## Truyền trực tuyến tập dữ liệu + +Để bật tính năng phát trực tuyến tập dữ liệu, bạn chỉ cần truyền tham số `streaming=True` vào hàm `load_dataset()`. Ví dụ: hãy tải lại tập dữ liệu PubMed Abstracts, nhưng ở chế độ phát trực tuyến: + +```py +pubmed_dataset_streamed = load_dataset( + "json", data_files=data_files, split="train", streaming=True +) +``` + +Thay vì `Dataset` quen thuộc mà chúng ta đã gặp ở những nơi khác trong chương này, đối tượng được trả về với `streaming=True` là một `IterableDataset`. Như cái tên cho thấy, để truy cập các phần tử của một `IterableDataset`, chúng ta cần phải lặp lại nó. Chúng tôi có thể truy cập phần tử đầu tiên của tập dữ liệu được phát trực tuyến của chúng tôi như sau: + +```py +next(iter(pubmed_dataset_streamed)) +``` + +```python out +{'meta': {'pmid': 11409574, 'language': 'eng'}, + '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 ...'} +``` + +Các phần tử từ một tập dữ liệu được truyền trực tuyến có thể được xử lý nhanh chóng bằng cách sử dụng `IterableDataset.map()`, rất hữu ích trong quá trình huấn luyện nếu bạn cần tokenize các đầu vào. Quy trình hoàn toàn giống với quy trình chúng ta đã sử dụng để tokenize tập dữ liệu của mình trong [Chương 3](/course/chapter3), với sự khác biệt duy nhất là các đầu ra được trả về từng cái một: + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased") +tokenized_dataset = pubmed_dataset_streamed.map(lambda x: tokenizer(x["text"])) +next(iter(tokenized_dataset)) +``` + +```python out +{'input_ids': [101, 4958, 5178, 4328, 6779, ...], 'attention_mask': [1, 1, 1, 1, 1, ...]} +``` + + + +💡 Để tăng tốc độ trình tokenize với tính năng phát trực tuyến, bạn có thể vượt qua `batched=True`, như chúng ta đã thấy trong phần trước. Nó sẽ xử lý hàng loạt các ví dụ; kích thước lô mặc định là 1,000 và có thể được chỉ định bằng tham số `batch_size`. + + + +Bạn cũng có thể xáo trộn một tập dữ liệu được phát trực tuyến bằng cách sử dụng `IterableDataset.shuffle()`, nhưng không giống như `Dataset.shuffle()` điều này chỉ xáo trộn các phần tử trong một `buffer_size` được định trước: + +```py +shuffled_dataset = pubmed_dataset_streamed.shuffle(buffer_size=10_000, seed=42) +next(iter(shuffled_dataset)) +``` + +```python out +{'meta': {'pmid': 11410799, 'language': 'eng'}, + 'text': 'Randomized study of dose or schedule modification of granulocyte colony-stimulating factor in platinum-based chemotherapy for elderly patients with lung cancer ...'} +``` + +Trong ví dụ này, chúng ta đã chọn một mẫu ngẫu nhiên từ 10,000 mẫu đầu tiên trong bộ đệm. Khi một mẫu được truy cập, vị trí của nó trong bộ đệm sẽ được lấp đầy bằng ví dụ tiếp theo trong kho tài liệu (tức là ví dụ thứ 10,001 trong trường hợp trên). Bạn cũng có thể chọn các phần tử từ một tập dữ liệu được truyền trực tuyến bằng cách sử dụng các hàm `IterableDataset.take()` và `IterableDataset.skip()`, hoạt động theo cách tương tự như `Dataset.select()`. Ví dụ, để chọn 5 mẫu đầu tiên trong tập dữ liệu PubMed Abstracts, chúng ta có thể làm như sau: + +```py +dataset_head = pubmed_dataset_streamed.take(5) +list(dataset_head) +``` + +```python out +[{'meta': {'pmid': 11409574, 'language': 'eng'}, + 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'}, + {'meta': {'pmid': 11409575, 'language': 'eng'}, + 'text': 'Clinical signs of hypoxaemia in children with acute lower respiratory infection: indicators of oxygen therapy ...'}, + {'meta': {'pmid': 11409576, 'language': 'eng'}, + 'text': "Hypoxaemia in children with severe pneumonia in Papua New Guinea ..."}, + {'meta': {'pmid': 11409577, 'language': 'eng'}, + 'text': 'Oxygen concentrators and cylinders ...'}, + {'meta': {'pmid': 11409578, 'language': 'eng'}, + 'text': 'Oxygen supply in rural africa: a personal experience ...'}] +``` + +Tương tự, bạn có thể sử dụng hàm `IterableDataset.skip()` để tạo các tập huấn luyện và kiểm định từ một tập dữ liệu xáo trộn như sau: + +```py +# Bỏ qua 1,000 mẫu đầu tiên và đưa phần còn lại vào tập huấn luyện +train_dataset = shuffled_dataset.skip(1000) +# Lấy 1,000 ví dụ đầu tiên cho tập kiểm định +validation_dataset = shuffled_dataset.take(1000) +``` + +Hãy hoàn thành việc khám phá của chúng ta về việc truyền trực tuyến tập dữ liệu với một ứng dụng phổ biến: kết hợp nhiều tập dữ liệu với nhau để tạo ra một kho dữ liệu duy nhất. 🤗 Datasets cung cấp một hàm `interleave_datasets()` để chuyển đổi danh sách các đối tượng `IterableDataset` thành một `IterableDataset` duy nhất, trong đó các phần tử của tập dữ liệu mới được lấy bằng cách xen kẽ giữa các mẫu gốc. Hàm này đặc biệt hữu ích khi bạn đang cố gắng kết hợp các tập dữ liệu lớn, vì vậy, để làm ví dụ, hãy truyền trực tuyến tập con FreeLaw của Pile, là tập dữ liệu 51 GB về các ý kiến pháp lý từ các tòa án Hoa Kỳ: + +```py +law_dataset_streamed = load_dataset( + "json", + data_files="https://mystic.the-eye.eu/public/AI/pile_preliminary_components/FreeLaw_Opinions.jsonl.zst", + split="train", + streaming=True, +) +next(iter(law_dataset_streamed)) +``` + +```python out +{'meta': {'case_ID': '110921.json', + 'case_jurisdiction': 'scotus.tar.gz', + 'date_created': '2010-04-28T17:12:49Z'}, + 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'} +``` + +Tập dữ liệu này đủ lớn để kích hoạt RAM của hầu hết các máy tính xách tay, nhưng chúng ta vẫn có thể tải và truy cập nó mà không phải đổ mồ hôi! Bây giờ chúng ta hãy kết hợp các mẫu từ bộ dữ liệu FreeLaw và PubMed Abstracts với hàm `interleave_datasets()`: + +```py +from itertools import islice +from datasets import interleave_datasets + +combined_dataset = interleave_datasets([pubmed_dataset_streamed, law_dataset_streamed]) +list(islice(combined_dataset, 2)) +``` + +```python out +[{'meta': {'pmid': 11409574, 'language': 'eng'}, + 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'}, + {'meta': {'case_ID': '110921.json', + 'case_jurisdiction': 'scotus.tar.gz', + 'date_created': '2010-04-28T17:12:49Z'}, + 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'}] +``` + +Ở đây chúng ta đã sử dụng hàm `islice()` từ mô-đun `itertools` của Python để chọn hai mẫu đầu tiên từ tập dữ liệu kết hợp và chúng ta có thể thấy rằng chúng khớp với các ví dụ đầu tiên từ mỗi trong hai tập dữ liệu nguồn. + +Cuối cùng, nếu bạn muốn phát trực tuyến toàn bộ 825 GB của Pile, bạn có thể lấy tất cả các tệp đã chuẩn bị như sau: + +```py +base_url = "https://mystic.the-eye.eu/public/AI/pile/" +data_files = { + "train": [base_url + "train/" + f"{idx:02d}.jsonl.zst" for idx in range(30)], + "validation": base_url + "val.jsonl.zst", + "test": base_url + "test.jsonl.zst", +} +pile_dataset = load_dataset("json", data_files=data_files, streaming=True) +next(iter(pile_dataset["train"])) +``` + +```python out +{'meta': {'pile_set_name': 'Pile-CC'}, + 'text': 'It is done, and submitted. You can play “Survival of the Tastiest” on Android, and on the web...'} +``` + + + +✏️ **Thử nghiệm thôi!** Sử dụng một trong những kho tài liệu Common Crawl lớn như [`mc4`](https://huggingface.co/datasets/mc4) hoặc [`oscar`](https://huggingface.co/datasets/oscar) để tạo tập dữ liệu đa ngôn ngữ trực tuyến thể hiện tỷ lệ nói của các ngôn ngữ ở quốc gia bạn chọn. Ví dụ: bốn ngôn ngữ quốc gia ở Thụy Sĩ là tiếng Đức, tiếng Pháp, tiếng Ý và tiếng La Mã, vì vậy bạn có thể thử tạo một kho ngữ liệu tiếng Thụy Sĩ bằng cách lấy mẫu các tập hợp con Oscar theo tỷ lệ nói của chúng. + + + +Giờ đây, bạn có tất cả các công cụ cần thiết để tải và xử lý các tập dữ liệu ở mọi hình dạng và kích thước - nhưng trừ khi bạn đặc biệt may mắn, sẽ đến một thời điểm trong hành trình NLP của bạn, nơi bạn sẽ phải thực sự tạo một tập dữ liệu để giải quyết vấn đề vấn đề trong tầm tay. Đó là chủ đề của phần tiếp theo! diff --git a/chapters/vi/chapter5/5.mdx b/chapters/vi/chapter5/5.mdx new file mode 100644 index 000000000..d93d3dc1f --- /dev/null +++ b/chapters/vi/chapter5/5.mdx @@ -0,0 +1,474 @@ +# Tạo tập dữ liệu của riêng bạn + + + +Đôi khi tập dữ liệu mà bạn cần để xây dựng một ứng dụng NLP không tồn tại, vì vậy bạn sẽ cần phải tự tạo. Trong phần này, chúng tôi sẽ hướng dẫn bạn cách tạo một kho tài liệu gồm [Các vấn đề về GitHub](https://github.com/features/issues/), thường được sử dụng để theo dõi các lỗi hoặc tính năng trong kho lưu trữ GitHub. Kho tài liệu này có thể được sử dụng cho các mục đích khác nhau, bao gồm: + +* Khám phá mất bao lâu để đóng các vấn đề đang mở hoặc yêu cầu kéo về (pull requests) +* Đào tạo _multilabel classifier_ hay _trình phân loại đa nhãn_ có thể gắn thẻ các vấn đề với siêu dữ liệu dựa trên mô tả của vấn đề (ví dụ: "lỗi", "cải tiến" hoặc "câu hỏi") +* Tạo công cụ tìm kiếm ngữ nghĩa để tìm những vấn đề nào phù hợp với truy vấn của người dùng + +Ở đây chúng ta sẽ tập trung vào việc tạo kho ngữ liệu và trong phần tiếp theo chúng ta sẽ giải quyết ứng dụng tìm kiếm ngữ nghĩa. Để giữ mọi thứ đúng meta, chúng ta sẽ sử dụng các vấn đề GitHub liên quan đến một dự án nguồn mở phổ biến: 🤗 Datasets! Chúng ta hãy xem cách lấy dữ liệu và khám phá thông tin có trong những vấn đề này. + +## Lấy dữ liệu + +Bạn có thể tìm thấy tất cả các vấn đề trong 🤗 Datasets bằng cách điều hướng đến [tab Issues](https://github.com/huggingface/datasets/issues). Như thể hiện trong ảnh chụp màn hình bên dưới, tại thời điểm viết bài, có 331 vấn đề đang mở và 668 vấn đề đã đóng. + +
+The GitHub issues associated with 🤗 Datasets. +
+ +Nếu bạn nhấp vào một trong những vấn đề này, bạn sẽ thấy nó chứa tiêu đề, mô tả và một bộ nhãn mô tả đặc trưng cho vấn đề. Một ví dụ được hiển thị trong ảnh chụp màn hình bên dưới. + +
+A typical GitHub issue in the 🤗 Datasets repository. +
+ +Để tải xuống tất cả các vấn đề của kho lưu trữ, chúng tôi sẽ sử dụng [GitHub REST API](https://docs.github.com/en/rest) để thăm dò điểm cuối [`Issues`](https://docs.github.com/en/rest/reference/issues#list-repository-issues). Điểm cuối này trả về danh sách các đối tượng JSON, với mỗi đối tượng chứa một số lượng lớn các trường bao gồm tiêu đề và mô tả cũng như siêu dữ liệu về trạng thái của vấn đề, v.v. + +Một cách thuận tiện để tải các vấn đề xuống là thông qua thư viện `requests`, đây là cách tiêu chuẩn để thực hiện các yêu cầu HTTP trong Python. Bạn có thể cài đặt thư viện bằng cách chạy: + + +```python +!pip install requests +``` + +Sau khi thư viện được cài đặt, bạn có thể thực hiện các yêu cầu GET tới điểm cuối `Issues` bằng cách gọi hàm `requests.get()`. Ví dụ: bạn có thể chạy lệnh sau để truy xuất vấn đề đầu tiên trên trang đầu tiên: + +```py +import requests + +url = "https://api.github.com/repos/huggingface/datasets/issues?page=1&per_page=1" +response = requests.get(url) +``` + +Đối tượng `response` chứa nhiều thông tin hữu ích về yêu cầu, bao gồm mã trạng thái HTTP: + +```py +response.status_code +``` + +```python out +200 +``` + +trong đó trạng thái `200` có nghĩa là yêu cầu đã thành công (bạn có thể tìm thấy danh sách các mã trạng thái HTTP có thể có [tại đây](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes)). Tuy nhiên, điều chúng ta thực sự quan tâm là _payload_ hay _tải trọng_, có thể được truy cập ở nhiều định dạng khác nhau như byte, chuỗi hoặc JSON. Vì chúng ta biết các vấn đề của ta ở định dạng JSON, hãy kiểm tra tải trọng như sau: + +```py +response.json() +``` + +```python out +[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/2792', + 'repository_url': 'https://api.github.com/repos/huggingface/datasets', + 'labels_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/labels{/name}', + 'comments_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/comments', + 'events_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/events', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792', + 'id': 968650274, + 'node_id': 'MDExOlB1bGxSZXF1ZXN0NzEwNzUyMjc0', + 'number': 2792, + 'title': 'Update GooAQ', + 'user': {'login': 'bhavitvyamalik', + 'id': 19718818, + 'node_id': 'MDQ6VXNlcjE5NzE4ODE4', + 'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4', + 'gravatar_id': '', + 'url': 'https://api.github.com/users/bhavitvyamalik', + 'html_url': 'https://github.com/bhavitvyamalik', + 'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers', + 'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}', + 'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}', + 'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}', + 'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions', + 'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs', + 'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos', + 'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}', + 'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events', + 'type': 'User', + 'site_admin': False}, + 'labels': [], + 'state': 'open', + 'locked': False, + 'assignee': None, + 'assignees': [], + 'milestone': None, + 'comments': 1, + 'created_at': '2021-08-12T11:40:18Z', + 'updated_at': '2021-08-12T12:31:17Z', + 'closed_at': None, + 'author_association': 'CONTRIBUTOR', + 'active_lock_reason': None, + 'pull_request': {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/2792', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792', + 'diff_url': 'https://github.com/huggingface/datasets/pull/2792.diff', + 'patch_url': 'https://github.com/huggingface/datasets/pull/2792.patch'}, + 'body': '[GooAQ](https://github.com/allenai/gooaq) dataset was recently updated after splits were added for the same. This PR contains new updated GooAQ with train/val/test splits and updated README as well.', + 'performed_via_github_app': None}] +``` + +Ồ, đó là rất nhiều thông tin! Chúng ta có thể thấy các trường hữu ích như `title`, `body`, và `number` mô tả sự cố cũng như thông tin về người dùng GitHub đã mở sự cố. + + + +✏️ **Thử nghiệm thôi!** Nhấp vào một vài URL trong tải trọng JSON ở trên để biết loại thông tin mà mỗi vấn đề GitHub được liên kết với. + + + +Như đã mô tả trong [tài liệu](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting) GitHub, các yêu cầu chưa được xác thực được giới hạn ở 60 yêu cầu mỗi giờ. Mặc dù bạn có thể tăng tham số truy vấn `per_page` để giảm số lượng yêu cầu bạn thực hiện, nhưng bạn vẫn sẽ đạt đến giới hạn tỷ lệ trên bất kỳ kho lưu trữ nào có nhiều hơn một vài nghìn vấn đề. Vì vậy, thay vào đó, bạn nên làm theo [hướng dẫn](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) của GitHub về cách tạo _personal access token_ hay _token truy cập cá nhân_ để bạn có thể tăng giới hạn tốc độ lên 5,000 yêu cầu mỗi giờ. Khi bạn có token của riêng mình, bạn có thể bao gồm nó như một phần của tiêu đề yêu cầu: + +```py +GITHUB_TOKEN = xxx # Sao chép token GitHub của bạn tại đây +headers = {"Authorization": f"token {GITHUB_TOKEN}"} +``` + + + +⚠️ Không dùng chung notebook có dán `GITHUB_TOKEN` của bạn trong đó. Chúng tôi khuyên bạn nên xóa ô cuối cùng sau khi bạn đã thực thi nó để tránh vô tình làm rò rỉ thông tin này. Tốt hơn nữa, hãy lưu trữ token trong tệp *.env* và sử dụng [thư viện `python-dotenv`](https://github.com/theskumar/python-dotenv) để tải tự động cho bạn dưới dạng biến môi trường. + + + +Bây giờ chúng ta đã có token truy cập của mình, hãy tạo một hàm có thể tải xuống tất cả các vấn đề từ kho lưu trữ GitHub: + +```py +import time +import math +from pathlib import Path +import pandas as pd +from tqdm.notebook import tqdm + + +def fetch_issues( + owner="huggingface", + repo="datasets", + num_issues=10_000, + rate_limit=5_000, + issues_path=Path("."), +): + if not issues_path.is_dir(): + issues_path.mkdir(exist_ok=True) + + batch = [] + all_issues = [] + per_page = 100 # Số vấn đề phải trả về trên mỗi trang + num_pages = math.ceil(num_issues / per_page) + base_url = "https://api.github.com/repos" + + for page in tqdm(range(num_pages)): + # Truy vấn với trạng thái state=all để nhận được cả vấn đề mở và đóng + query = f"issues?page={page}&per_page={per_page}&state=all" + issues = requests.get(f"{base_url}/{owner}/{repo}/{query}", headers=headers) + batch.extend(issues.json()) + + if len(batch) > rate_limit and len(all_issues) < num_issues: + all_issues.extend(batch) + batch = [] # Xả lô cho khoảng thời gian tiếp theo + print(f"Reached GitHub rate limit. Sleeping for one hour ...") + time.sleep(60 * 60 + 1) + + all_issues.extend(batch) + df = pd.DataFrame.from_records(all_issues) + df.to_json(f"{issues_path}/{repo}-issues.jsonl", orient="records", lines=True) + print( + f"Downloaded all the issues for {repo}! Dataset stored at {issues_path}/{repo}-issues.jsonl" + ) +``` + +Bây giờ khi ta gọi `fetch_issues ()`, nó sẽ tải xuống tất cả các vấn đề theo lô để tránh vượt quá giới hạn của GitHub về số lượng yêu cầu mỗi giờ; kết quả sẽ được lưu trữ trong tệp _repository_name-issues.jsonl_, trong đó mỗi dòng là một đối tượng JSON đại diện cho một vấn đề. Hãy sử dụng chức năng này để lấy tất cả các vấn đề từ 🤗 Datasets: + +```py +# Tùy thuộc vào kết nối internet của bạn, quá trình này có thể mất vài phút để chạy ... +fetch_issues() +``` + +Sau khi các vấn đề được tải xuống, chúng tôi có thể lôi chúng cục bộ bằng cách sử dụng các kỹ năng mới khai phá từ [phần 2](/course/chaper5/2): + +```py +issues_dataset = load_dataset("json", data_files="datasets-issues.jsonl", split="train") +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'timeline_url', 'performed_via_github_app'], + num_rows: 3019 +}) +``` + +Tuyệt vời, chúng ta đã tạo tập dữ liệu đầu tiên của mình từ đầu! Nhưng tại sao lại có vài nghìn vấn đề khi [tab Sự cố](https://github.com/huggingface/datasets/issues) của kho lưu trữ 🤗 Datasets chỉ hiển thị tổng số 1,000 vấn đề 🤔? Như được mô tả trong [tài liệu](https://docs.github.com/en/rest/reference/issues#list-issues-assigned-to-the-authenticated-user) GitHub, đó là vì chúng ta đã tải xuống tất cả kéo các yêu cầu: + +> REST API v3 của GitHub coi mọi yêu cầu kéo về là một vấn đề, nhưng không phải mọi vấn đề đều là yêu cầu kéo. Vì lý do này, điểm cuối "Issues" có thể trả về cả hai sự cố và kéo các yêu cầu trong phản hồi. Bạn có thể xác định các yêu cầu kéo bằng phím `pull_request`. Lưu ý rằng `id` của một yêu cầu kéo được trả về từ các điểm cuối "Issues" sẽ là một id vấn đề. + +Vì nội dung của các vấn đề và yêu cầu kéo khá khác nhau, chúng ta hãy thực hiện một số xử lý trước nhỏ để cho phép chúng ta phân biệt giữa chúng. + +## Làm sạch dữ liệu + +Đoạn mã trên từ tài liệu của GitHub cho chúng ta biết rằng cột `pull_request` có thể được sử dụng để phân biệt giữa các vấn đề và các yêu cầu kéo. Hãy xem xét một mẫu ngẫu nhiên để xem sự khác biệt là gì. Như chúng ta đã làm trong [phần 3](/course/chapter5/3), chúng ta sẽ xâu chuỗi `Dataset.shuffle()` và `Dataset.select()` để tạo một mẫu ngẫu nhiên và sau đó nén cột `html_url` và `pull_request` để chúng tôi có thể so sánh các URL khác nhau: + +```py +sample = issues_dataset.shuffle(seed=666).select(range(3)) + +# In ra URL và kéo về các mục yêu cầu +for url, pr in zip(sample["html_url"], sample["pull_request"]): + print(f">> URL: {url}") + print(f">> Pull request: {pr}\n") +``` + +```python out +>> URL: https://github.com/huggingface/datasets/pull/850 +>> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/850', 'html_url': 'https://github.com/huggingface/datasets/pull/850', 'diff_url': 'https://github.com/huggingface/datasets/pull/850.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/850.patch'} + +>> URL: https://github.com/huggingface/datasets/issues/2773 +>> Pull request: None + +>> URL: https://github.com/huggingface/datasets/pull/783 +>> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/783', 'html_url': 'https://github.com/huggingface/datasets/pull/783', 'diff_url': 'https://github.com/huggingface/datasets/pull/783.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/783.patch'} +``` + +Ở đây, chúng ta có thể thấy rằng mỗi yêu cầu kéo được liên kết với các URL khác nhau, trong khi các vấn đề thông thường có mục nhập `None`. Chúng ta có thể sử dụng sự phân biệt này để tạo một cột `is_pull_request` mới để kiểm tra xem trường `pull_request` có phải là `None` hay không: + +```py +issues_dataset = issues_dataset.map( + lambda x: {"is_pull_request": False if x["pull_request"] is None else True} +) +``` + + + +✏️ **Thử nghiệm thôi!** Tính thời gian trung bình cần để đóng các vấn đề trong 🤗 Datasets. Bạn có thể thấy hàm `Dataset.filter()` hữu ích để lọc ra các yêu cầu kéo và các vấn đề đang mở, đồng thời bạn có thể sử dụng hàm `Dataset.set_format()` để chuyển đổi tập dữ liệu thành `DataFrame` để bạn có thể dễ dàng thao tác dấu thời gian `create_at` và `closed_at`. Đối với điểm thưởng, hãy tính thời gian trung bình cần để đóng các yêu cầu kéo. + + + +Mặc dù chúng ta có thể tiếp tục dọn dẹp tập dữ liệu bằng cách loại bỏ hoặc đổi tên một số cột, nhưng thông thường tốt nhất là giữ tập dữ liệu ở trạng thái "thô" nhất có thể ở giai đoạn này để có thể dễ dàng sử dụng trong nhiều ứng dụng. + +Trước khi chúng tôi đẩy tập dữ liệu của mình sang Hugging Face Hub, hãy giải quyết một thứ còn thiếu trong nó: các nhận xét liên quan đến từng vấn đề và yêu cầu kéo. Chúng ta sẽ thêm chúng vào phầi tiếp theo với - bạn đoán được không - chính là API GitHub REST! + +## Bổ sung tập dữ liệu + +Mặc dù chúng tôi có thể tiếp tục dọn dẹp tập dữ liệu bằng cách loại bỏ hoặc đổi tên một số cột, nhưng thông thường tốt nhất là giữ tập dữ liệu ở trạng thái "thô" nhất có thể ở giai đoạn này để có thể dễ dàng sử dụng trong nhiều ứng dụng. + +Trước khi chúng tôi đẩy tập dữ liệu của mình sang Trung tâm khuôn mặt ôm, hãy giải quyết một thứ còn thiếu trong nó: các nhận xét liên quan đến từng vấn đề và yêu cầu kéo. Chúng tôi sẽ thêm chúng vào lần tiếp theo với - bạn đoán không - API GitHub REST! + +## Bổ sung tập dữ liệu + +Như được hiển thị trong ảnh chụp màn hình sau, các nhận xét liên quan đến vấn đề hoặc yêu cầu kéo cung cấp nguồn thông tin phong phú, đặc biệt nếu chúng ta quan tâm đến việc xây dựng một công cụ tìm kiếm để trả lời các truy vấn của người dùng về thư viện. + +
+Comments associated with an issue about 🤗 Datasets. +
+ +API GitHub REST cung cấp điểm cuối [`Comments`](https://docs.github.com/en/rest/reference/issues#list-issue-comments) trả về tất cả các nhận xét được liên kết với số vấn đề. Hãy kiểm tra điểm cuối để xem nó trả về những gì: + +```py +issue_number = 2792 +url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments" +response = requests.get(url, headers=headers) +response.json() +``` + +```python out +[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/comments/897594128', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792#issuecomment-897594128', + 'issue_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792', + 'id': 897594128, + 'node_id': 'IC_kwDODunzps41gDMQ', + 'user': {'login': 'bhavitvyamalik', + 'id': 19718818, + 'node_id': 'MDQ6VXNlcjE5NzE4ODE4', + 'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4', + 'gravatar_id': '', + 'url': 'https://api.github.com/users/bhavitvyamalik', + 'html_url': 'https://github.com/bhavitvyamalik', + 'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers', + 'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}', + 'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}', + 'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}', + 'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions', + 'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs', + 'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos', + 'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}', + 'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events', + 'type': 'User', + 'site_admin': False}, + 'created_at': '2021-08-12T12:21:52Z', + 'updated_at': '2021-08-12T12:31:17Z', + 'author_association': 'CONTRIBUTOR', + 'body': "@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?", + 'performed_via_github_app': None}] +``` + +Chúng ta có thể thấy rằng nhận xét được lưu trữ trong trường `body`, vì vậy hãy viết một hàm đơn giản trả về tất cả các nhận xét liên quan đến một vấn đề bằng cách chọn nội dung `body` cho mỗi phần tử trong `response.json()`: + +```py +def get_comments(issue_number): + url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments" + response = requests.get(url, headers=headers) + return [r["body"] for r in response.json()] + + +# Kiểm tra hàm có hoạt động như mong đợi không +get_comments(2792) +``` + +```python out +["@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?"] +``` + +Điều này có vẻ ổn, vì vậy hãy sử dụng `Dataset.map()` để thêm cột `comments` mới cho từng vấn đề trong tập dữ liệu của mình: + +```py +# Tùy thuộc vào kết nối internet của bạn, quá trình này có thể mất vài phút ... +issues_with_comments_dataset = issues_dataset.map( + lambda x: {"comments": get_comments(x["number"])} +) +``` + +Bước cuối cùng là lưu tập dữ liệu tăng cường cùng với dữ liệu thô của chúng ta để ta có thể đẩy cả hai vào Hub: + +```py +issues_with_comments_dataset.to_json("issues-datasets-with-comments.jsonl") +``` + +## Tải tập dữ liệu lên Hugging Face Hub + + + +Bây giờ chúng ta đã có tập dữ liệu tăng cường của mình, đã đến lúc chuyển nó vào Hub để chúng ta có thể chia sẻ nó với cộng đồng! Để tải tập dữ liệu lên, chúng tôi sẽ sử dụng [🤗 thư viện Hub](https://github.com/huggingface/huggingface_hub), cho phép chúng ta tương tác với Hugging Face Hub thông qua API Python. 🤗 Hub được cài đặt sẵn với 🤗 Transformers, vì vậy chúng ta có thể sử dụng trực tiếp. Ví dụ: chúng ta có thể sử dụng hàm `list_datasets()` để lấy thông tin về tất cả các tập dữ liệu công khai hiện được lưu trữ trên Hub: + +```py +from huggingface_hub import list_datasets + +all_datasets = list_datasets() +print(f"Number of datasets on Hub: {len(all_datasets)}") +print(all_datasets[0]) +``` + +```python out +Number of datasets on Hub: 1487 +Dataset Name: acronym_identification, Tags: ['annotations_creators:expert-generated', 'language_creators:found', 'languages:en', 'licenses:mit', 'multilinguality:monolingual', 'size_categories:10K + +✏️ **Thử nghiệm thôi!** Sử dụng tên người dùng và mật khẩu Hugging Face Hub của bạn để lấy token và tạo một kho lưu trữ trống có tên là `github-issue`. Hãy nhớ **không bao giờ lưu thông tin đăng nhập của bạn** trong Colab hoặc bất kỳ kho lưu trữ nào khác, vì thông tin này có thể bị kẻ xấu lợi dụng. + + + +Tiếp theo, hãy sao chép kho lưu trữ từ Hub vào máy cục bộ của chúng ta và sao chép tệp tập dữ liệu của chúng ta vào đó. 🤗 Hub cung cấp một lớp `Repository` tiện dụng bao bọc nhiều lệnh Git phổ biến, do đó, để sao chép kho lưu trữ từ xa, chúng ta chỉ cần cung cấp URL và đường dẫn cục bộ mà ta muốn sao chép tới: + +```py +from huggingface_hub import Repository + +repo = Repository(local_dir="github-issues", clone_from=repo_url) +!cp issues-datasets-with-comments.jsonl github-issues/ +``` + +Theo mặc định, các phần mở rộng tệp khác nhau (chẳng hạn như *.bin*, *.gz*, và *.zip*) được theo dõi bằng Git LFS để các tệp lớn có thể được tạo phiên bản trong cùng một quy trình làm việc của Git. Bạn có thể tìm thấy danh sách các phần mở rộng tệp được theo dõi bên trong tệp *.gitattributes* của kho lưu trữ. Để đưa định dạng JSON Lines vào danh sách, chúng ta có thể chạy lệnh sau: + +```py +repo.lfs_track("*.jsonl") +``` + +Sau đó ta có thể dùng `Repository.push_to_hub()` để đẩy dữ liệu lên Hub: + +```py +repo.push_to_hub() +``` + +Nếu chúng ta điều hướng đến URL có trong `repo_url`, bây giờ chúng ta sẽ thấy rằng tệp tập dữ liệu của chúng ta đã được tải lên. + +
+Our dataset repository on the Hugging Face Hub. +
+ +Từ đây, bất kỳ ai cũng có thể tải xuống tập dữ liệu bằng cách chỉ cần cung cấp `load_dataset()` với ID kho lưu trữ dưới dạng tham số `path`: + +```py +remote_dataset = load_dataset("lewtun/github-issues", split="train") +remote_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 2855 +}) +``` + +Tuyệt vời, chúng ta đã đưa tập dữ liệu của mình vào Hub và nó có sẵn cho những người khác sử dụng! Chỉ còn một việc quan trọng cần làm: thêm _dataset card_ hay _thẻ dữ liệu_ giải thích cách tạo kho tài liệu và cung cấp thông tin hữu ích khác cho cộng đồng. + + + +💡 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. + + + +## Tạo thẻ dữ liệu + +Tập dữ liệu được ghi chép đầy đủ có nhiều khả năng hữu ích hơn cho người khác (bao gồm cả tương lai của bạn!), vì chúng cung cấp bối cảnh để cho phép người dùng quyết định xem tập dữ liệu có liên quan đến tác vụ của họ hay không và để đánh giá bất kỳ sai lệch hoặc rủi ro tiềm ẩn nào liên quan đến việc sử dụng tập dữ liệu. + +Trên Hugging Face Hub, thông tin này được lưu trữ trong mỗi tệp *README.md* của kho lưu trữ tập dữ liệu. Có hai bước chính bạn nên thực hiện trước khi tạo tệp này: + +1. Sử dụng ứng dụng [`datasets-tagging`](https://huggingface.co/datasets/tagging/) để tạo thẻ siêu dữ liệu ở định dạng YAML. Các thẻ này được sử dụng cho nhiều tính năng tìm kiếm trên Hugging Face Hub và đảm bảo các thành viên của cộng đồng có thể dễ dàng tìm thấy tập dữ liệu của bạn. Vì chúng ta đã tạo tập dữ liệu tùy chỉnh ở đây, bạn sẽ cần sao chép kho lưu trữ `datasets-tagging` và chạy ứng dụng cục bộ. Đây là giao diện trông như thế nào: + +
+The `datasets-tagging` interface. +
+ +2.Đọc [Hướng dẫn 🤗 Datasets](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) về cách tạo thẻ tập dữ liệu thông tin và sử dụng nó làm mẫu. + +Bạn có thể tạo tệp *README.md* trực tiếp trên Hub và bạn có thể tìm thấy mẫu thẻ dữ liệu trong kho lưu trữ dữ liệu `lewtun/github-issues`. Ảnh chụp màn hình của thẻ dữ liệu đã điền đầy đủ thông tin được hiển thị bên dưới. + +
+A dataset card. +
+ + + +✏️ **Thử nghiệm thôi!** Sử dụng ứng dụng `dataset-tagging` và [hướng dẫn 🤗 Datasets](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) để hoàn thành tệp *README.md* cho vấn đề về dữ liệu trên Github của bạn. + + + +Vậy đó! Trong phần này, chúng ta đã thấy rằng việc tạo một tập dữ liệu tốt có thể khá liên quan, nhưng may mắn thay, việc tải nó lên và chia sẻ nó với cộng đồng thì không. Trong phần tiếp theo, chúng ta sẽ sử dụng bộ dữ liệu mới của mình để tạo một công cụ tìm kiếm ngữ nghĩa với 🤗 Datasets có thể so khớp các câu hỏi với các vấn đề và nhận xét có liên quan nhất. + + + +✏️ **Thử nghiệm thôi!** Thực hiện theo các bước chúng ta đã thực hiện trong phần này để tạo tập dữ liệu về các vấn đề GitHub cho thư viện mã nguồn mở yêu thích của bạn (tất nhiên là chọn thứ khác ngoài 🤗 Datasets!). Để có điểm thưởng, hãy tinh chỉnh bộ phân loại đa nhãn để dự đoán các thẻ có trong trường `labels`. + + diff --git a/chapters/vi/chapter5/6.mdx b/chapters/vi/chapter5/6.mdx new file mode 100644 index 000000000..e6803fb27 --- /dev/null +++ b/chapters/vi/chapter5/6.mdx @@ -0,0 +1,529 @@ + + +# Tìm kiếm ngữ nghĩa với FAISS + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Trong [phần 5](/course/chapter5/5), chúng ta đã tạo tập dữ liệu về các vấn đề GitHub và nhận xét từ kho lưu trữ 🤗 Datasets. Trong phần này, chúng ta sẽ sử dụng thông tin này để xây dựng một công cụ tìm kiếm có thể giúp ta tìm câu trả lời cho những câu hỏi cấp bách nhất về thư viện! + + + +## Sử dụng nhúng biểu diễn từ cho tìm kiếm ngữ nghĩa + +Như chúng ta đã thấy trong [Chương 1](/course/chapter1), các mô hình ngôn ngữ dựa trên Transformer đại diện cho mỗi token trong một khoảng văn bản dưới dạng một _vector nhugns biểu diễn từ_. Hóa ra người ta có thể "gộp" các biểu diễn riêng lẻ để tạo biểu diễn vectơ cho toàn bộ câu, đoạn văn hoặc toàn bộ (trong một số trường hợp) tài liệu. Sau đó, các phép nhúng này có thể được sử dụng để tìm các tài liệu tương tự trong kho tài liệu bằng cách tính toán độ tương tự của sản phẩm (hoặc một số chỉ số tương tự khác) giữa mỗi biễu diễn và trả về các tài liệu có độ tương đồng lớn nhất. + +Trong phần này, chúng ta sẽ sử dụng các biểu diễn từ để phát triển một công cụ tìm kiếm ngữ nghĩa. Các công cụ tìm kiếm này cung cấp một số lợi thế so với các phương pháp tiếp cận thông thường dựa trên việc kết hợp các từ khóa trong một truy vấn với các tài liệu. + +
+Semantic search. + +
+ +## Tải và chuẩn bị tập dữ liệu + +Điều đầu tiên chúng ta cần làm là tải xuống tập dữ liệu về các sự cố GitHub, vì vậy hãy sử dụng thư viện 🤗 Hub để giải quyết URL nơi tệp của chúng ta được lưu trữ trên Hugging Face Hub: + +```py +from huggingface_hub import hf_hub_url + +data_files = hf_hub_url( + repo_id="lewtun/github-issues", + filename="datasets-issues-with-comments.jsonl", + repo_type="dataset", +) +``` + +Với URL được lưu trữ trong `data_files`, sau đó chúng ta có thể tải tập dữ liệu từ xa bằng phương pháp đã được giới thiệu trong [phần 2](/course/chapter5/2): + +```py +from datasets import load_dataset + +issues_dataset = load_dataset("json", data_files=data_files, split="train") +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 2855 +}) +``` + +Ở đây chúng ta đã chỉ định tách `train` mặc định trong `load_dataset()`, vì vậy nó trả về một `Dataset` thay vì `DatasetDict`. Trình tự đầu tiên của doanh nghiệp là lọc ra các yêu cầu kéo, vì những yêu cầu này có xu hướng hiếm khi được sử dụng để trả lời các truy vấn của người dùng và sẽ tạo ra nhiễu trong công cụ tìm kiếm mình. Chúng ta có thể sử dụng hàm `Dataset.filter()` đã quen thuộc với bạn để loại trừ các hàng này trong tập dữ liệu của mình. Cùng lúc đó, hãy cùng lọc ra các hàng không có nhận xét, vì những hàng này không cung cấp câu trả lời cho các truy vấn của người dùng: + +```py +issues_dataset = issues_dataset.filter( + lambda x: (x["is_pull_request"] == False and len(x["comments"]) > 0) +) +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 771 +}) +``` + +Chúng ta có thể thấy rằng có rất nhiều cột trong tập dữ liệu của chúng ta, hầu hết trong số đó chúng ta không cần phải xây dựng công cụ tìm kiếm của mình. Từ góc độ tìm kiếm, các cột chứa nhiều thông tin nhất là `title`, `body`, và `comments`, trong khi `html_url` cung cấp cho chúng ta một liên kết trỏ về nguồn. Hãy sử dụng hàm `Dataset.remove_columns()` để xóa phần còn lại: + +```py +columns = issues_dataset.column_names +columns_to_keep = ["title", "body", "html_url", "comments"] +columns_to_remove = set(columns_to_keep).symmetric_difference(columns) +issues_dataset = issues_dataset.remove_columns(columns_to_remove) +issues_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body'], + num_rows: 771 +}) +``` + +Để tạo các biểu diễn, chúng ta sẽ bổ sung mỗi nhận xét với tiêu đề và nội dung của vấn đề, vì các trường này thường bao gồm thông tin ngữ cảnh hữu ích. Vì cột `comments` của hiện là danh sách các nhận xét cho từng vấn đề, chúng tôi cần khám phá các cột để mỗi hàng bao gồm một tuple `(html_url, title, body, comment)`. Trong Pandas, chúng ta có thể thực hiện việc này bằng hàm [hàm `DataFrame.explode()`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html), tạo một hàng mới cho mỗi phần tử trong cột giống như danh sách, trong khi sao chép tất cả các giá trị cột khác. Để xem điều này hoạt động, trước tiên chúng ta hãy chúng chuyển thành định dạng Pandas `DataFrame`: + +```py +issues_dataset.set_format("pandas") +df = issues_dataset[:] +``` + +Nếu ta kiểm tra hàng đầu tiên trong `DataFrame` này, chúng ta có thể thấy có bốn nhận xét liên quan đến vấn đề này: + +```py +df["comments"][0].tolist() +``` + +```python out +['the bug code locate in :\r\n if data_args.task_name is not None:\r\n # Downloading and loading a dataset from the hub.\r\n datasets = load_dataset("glue", data_args.task_name, cache_dir=model_args.cache_dir)', + 'Hi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com\r\n\r\nNormally, it should work if you wait a little and then retry.\r\n\r\nCould you please confirm if the problem persists?', + 'cannot connect,even by Web browser,please check that there is some problems。', + 'I can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...'] +``` + +Khi chúng ta khám phá `df`, chúng tôi mong đợi nhận được một hàng cho mỗi nhận xét này. Hãy kiểm tra xem nó đã đúng chưa: + +```py +comments_df = df.explode("comments", ignore_index=True) +comments_df.head(4) +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
html_urltitlecommentsbody
0https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comthe bug code locate in :\r\n if data_args.task_name is not None...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
1https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comHi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
2https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comcannot connect,even by Web browser,please check that there is some problems。Hello,\r\nI am trying to run run_glue.py and it gives me this error...
3https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comI can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
+ +Tuyệt vời, chúng ta có thể thấy các hàng đã được nhân rộng, với cột `comments` chứa các nhận xét riêng lẻ! Bây giờ chúng ta đã hoàn thành với Pandas, chúng ta có thể nhanh chóng chuyển trở lại `Dataset` bằng cách tải `DataFrame` vào bộ nhớ: + +```py +from datasets import Dataset + +comments_dataset = Dataset.from_pandas(comments_df) +comments_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body'], + num_rows: 2842 +}) +``` + +Được rồi, điều này đã cho chúng ta vài nghìn nhận xét để làm việc cùng! + + + + +✏️ **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. + + + +Bây giờ chúng ta đã có một nhận xét trên mỗi hàng, hãy tạo một cột `comments_length` mới chứa số từ trên mỗi nhận xét: + +```py +comments_dataset = comments_dataset.map( + lambda x: {"comment_length": len(x["comments"].split())} +) +``` + +Chúng ta có thể sử dụng cột mới này để lọc ra các nhận xét ngắn, thường bao gồm những thứ như "cc @lewtun" hoặc "Thanks!" không liên quan đến công cụ tìm kiếm của mình. Không có con số chính xác để chọn cho bộ lọc, nhưng khoảng 15 từ có vẻ như là một khởi đầu tốt: + +```py +comments_dataset = comments_dataset.filter(lambda x: x["comment_length"] > 15) +comments_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body', 'comment_length'], + num_rows: 2098 +}) +``` + +Sau khi dọn dẹp tập dữ liệu một chút, hãy ghép tiêu đề, mô tả và nhận xét của vấn đề với nhau trong một cột `text` mới. Như thường lệ, chúng ta sẽ viết một hàm đơn giản mà chúng ta có thể truyền vào `Dataset.map()`: + +```py +def concatenate_text(examples): + return { + "text": examples["title"] + + " \n " + + examples["body"] + + " \n " + + examples["comments"] + } + + +comments_dataset = comments_dataset.map(concatenate_text) +``` + +Cuối cùng thì chúng ta cũng đã sẵn sàng để tạo một số biểu điễn! Chúng ta hãy xem nào. + +## Tạo ra biểu diễn văn bản + +Chúng ta đã thấy trong [Chương 2](/course/chapter2) rằng ta có thể nhận được token biễu diễn nhúng bằng cách sử dụng lớp `AutoModel`. Tất cả những gì chúng ta cần làm là chọn một checkpoint phù hợp để tải mô hình từ đó. May mắn thay, có một thư viện tên là `sentence-transformers` dành riêng cho việc tạo các biểu diễn này. Như được mô tả trong [tài liệu](https://www.sbert.net/examples/appices/semantic-search/README.html#symmetric-vs-asymmetric-semantic-search) của thư viện, trường hợp sử dụng của ta là một ví dụ về _tìm kiếm ngữ nghĩa phi đối xứng_ bởi vì chúng ta có một truy vấn ngắn có câu trả lời ta muốn tìm thấy trong một tài liệu lại dài hơn nhiều, chẳng hạn như một nhận xét về vấn đề. [Bảng tổng quan về mô hình](https://www.sbert.net/docs/pretrained_models.html#model-overview) trong phần tài liệu chỉ ra rằng checkpoint `multi-qa-mpnet-base-dot-v1` có hiệu suất tốt nhất cho tìm kiếm ngữ nghĩa, vì vậy chúng ta sẽ sử dụng nó cho ứng dụng của mình. Chúng ta cũng sẽ tải tokenizer bằng cách sử dụng cùng một checkpoint: + +{#if fw === 'pt'} + +```py +from transformers import AutoTokenizer, AutoModel + +model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1" +tokenizer = AutoTokenizer.from_pretrained(model_ckpt) +model = AutoModel.from_pretrained(model_ckpt) +``` + +Để tăng tốc quá trình biểu diễn, ta sẽ giúp đặt mô hình và đầu vào trên thiết bị GPU, vì vậy hãy làm điều đó ngay bây giờ thôi: + +```py +import torch + +device = torch.device("cuda") +model.to(device) +``` + +{:else} + +```py +from transformers import AutoTokenizer, TFAutoModel + +model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1" +tokenizer = AutoTokenizer.from_pretrained(model_ckpt) +model = TFAutoModel.from_pretrained(model_ckpt, from_pt=True) +``` + +Lưu ý rằng chúng ta đặt `from_pt=True` như một tham số của phương thức `from_pretrained()`. Điều này là bởi checkpoint `multi-qa-mpnet-base-dot-v1` chỉ có trọng số Pytorch, vì vậy thiết lập `from_pt=True` sẽ tự động chuyển chúng về định dạng TensorFlow cho chúng ta. Như có thể thấy, nó rất đơn giản để chuyển giữa hai khung này trong 🤗 Transformers! + +{/if} + +Như đã đề cập trước đó, chúng ta muốn biểu diễn mỗi mục trong kho dữ liệu các vấn đề GitHub của mình dưới dạng một vectơ duy nhất, vì vậy chúng ta cần "gộp" hoặc tính trung bình các lần biễu diễn token theo một cách nào đó. Một cách tiếp cận phổ biến là thực hiện *CLS pooling* trên đầu ra của mô hình, nơi ta chỉ cần thu thập trạng thái ẩn cuối cùng cho token đặc biệt `[CLS]`. Hàm sau thực hiện thủ thuật này cho chúng ta: + +```py +def cls_pooling(model_output): + return model_output.last_hidden_state[:, 0] +``` + +Tiếp theo, chúng tôi sẽ tạo một chức năng trợ giúp sẽ tokanize danh sách các tài liệu, đặt các tensor trên GPU, đưa chúng vào mô hình và cuối cùng áp dụng CLS gộp cho các đầu ra: + +{#if fw === 'pt'} + +```py +def get_embeddings(text_list): + encoded_input = tokenizer( + text_list, padding=True, truncation=True, return_tensors="pt" + ) + encoded_input = {k: v.to(device) for k, v in encoded_input.items()} + model_output = model(**encoded_input) + return cls_pooling(model_output) +``` + +Chúng ta có thể kiểm tra chức năng hoạt động bằng cách cung cấp cho nó đoạn văn bản đầu tiên trong kho tài liệu và kiểm tra hình dạng đầu ra: + +```py +embedding = get_embeddings(comments_dataset["text"][0]) +embedding.shape +``` + +```python out +torch.Size([1, 768]) +``` + +Tuyệt vời, chúng ta đã chuyển đổi mục nhập đầu tiên trong kho tài liệu của mình thành một vectơ 768 chiều! Chúng ta có thể sử dụng `Dataset.map()` để áp dụng hàm `get_embeddings()` cho mỗi hàng trong kho tài liệu của mình, vì vậy hãy tạo một cột `embeddings` mới như sau: + +```py +embeddings_dataset = comments_dataset.map( + lambda x: {"embeddings": get_embeddings(x["text"]).detach().cpu().numpy()[0]} +) +``` + +{:else} + +```py +def get_embeddings(text_list): + encoded_input = tokenizer( + text_list, padding=True, truncation=True, return_tensors="tf" + ) + encoded_input = {k: v for k, v in encoded_input.items()} + model_output = model(**encoded_input) + return cls_pooling(model_output) +``` + +Chúng tôi có thể kiểm tra hàm có hoạt động không bằng cách cung cấp cho nó văn bản đầu tiên trong kho tài liệu và kiểm tra hình dạng đầu ra: + +```py +embedding = get_embeddings(comments_dataset["text"][0]) +embedding.shape +``` + +```python out +TensorShape([1, 768]) +``` + +Tuyệt vời, chúng ta đã chuyển đổi mục nhập đầu tiên trong kho tài liệu của mình thành một vectơ 768 chiều! Chúng ta có thể sử dụng `Dataset.map()` để áp dụng hàm `get_embeddings()` cho mỗi hàng trong kho tài liệu của mình, vì vậy hãy tạo một cột `embeddings` mới như sau: + +```py +embeddings_dataset = comments_dataset.map( + lambda x: {"embeddings": get_embeddings(x["text"]).numpy()[0]} +) +``` + +{/if} + +Lưu ý rằng chúng ta đã chuyển đổi các biểu diễn sang thành mảng NumPy - đó là vì 🤗 Datasets yêu cầu định dạng này khi ta cố gắng lập chỉ mục chúng bằng FAISS, điều mà ta sẽ thực hiện tiếp theo. + +## Sử dụng FAISS để tìm kiếm điểm tương đồng hiệu quả + +Bây giờ chúng ta đã có một tập dữ liệu về các biểu diễn, chúng ta cần một số cách để tìm kiếm chúng. Để làm điều này, chúng ta sẽ sử dụng một cấu trúc dữ liệu đặc biệt trong 🤗 Datasets được gọi là _FAISS index_. [FAISS](https://faiss.ai/) (viết tắt của Facebook AI Similarity Search) là một thư viện cung cấp các thuật toán hiệu quả để nhanh chóng tìm kiếm và phân cụm các vectơ nhúng biểu diễn. + +Ý tưởng cơ bản đằng sau FAISS là tạo ra một cấu trúc dữ liệu đặc biệt được gọi là _index_ hay _chỉ mục_ cho phép người ta tìm thấy các biểu diễn nhúng nào tương tự như biểu diễn nhúng đầu vào. Tạo chỉ mục FAISS trong 🤗 Datasets rất đơn giản - ta sử dụng hàm `Dataset.add_faiss_index()` và chỉ định cột nào trong tập dữ liệu mà ta muốn lập chỉ mục: + +```py +embeddings_dataset.add_faiss_index(column="embeddings") +``` + +Bây giờ chúng ta có thể thực hiện các truy vấn trên chỉ mục này bằng cách thực hiện tra cứu những mẫu lân cận nhất thông qua hàm `Dataset.get_nearest_examples()`. Hãy kiểm tra điều này bằng cách biểu diễn một câu hỏi như sau: + +{#if fw === 'pt'} + +```py +question = "How can I load a dataset offline?" +question_embedding = get_embeddings([question]).cpu().detach().numpy() +question_embedding.shape +``` + +```python out +torch.Size([1, 768]) +``` + +{:else} + +```py +question = "How can I load a dataset offline?" +question_embedding = get_embeddings([question]).numpy() +question_embedding.shape +``` + +```python out +(1, 768) +``` + +{/if} + +Cũng giống như các tài liệu, giờ đây chúng ta có một vectơ 768 chiều đại diện cho truy vấn, mà chúng ta có thể so sánh với toàn bộ kho dữ liệu để tìm ra các cách biểu diễn tương tự nhất: + +```py +scores, samples = embeddings_dataset.get_nearest_examples( + "embeddings", question_embedding, k=5 +) +``` + +Hàm `Dataset.get_nearest_examples()` trả về một loạt điểm xếp hạng sự tương đồng giữa truy vấn và tài liệu và một tập hợp các mẫu tương ứng (ở đây, là 5 kết quả phù hợp nhất). Hãy thu thập những thứ này vào một `pandas.DataFrame` để chúng ta có thể dễ dàng sắp xếp chúng: + +```py +import pandas as pd + +samples_df = pd.DataFrame.from_dict(samples) +samples_df["scores"] = scores +samples_df.sort_values("scores", ascending=False, inplace=True) +``` + +Bây giờ chúng ta có thể lặp lại một vài hàng đầu tiên để xem truy vấn của chúng ta khớp với các nhận xét có sẵn như thế nào: + +```py +for _, row in samples_df.iterrows(): + print(f"COMMENT: {row.comments}") + print(f"SCORE: {row.scores}") + print(f"TITLE: {row.title}") + print(f"URL: {row.html_url}") + print("=" * 50) + print() +``` + +```python out +""" +COMMENT: Requiring online connection is a deal breaker in some cases unfortunately so it'd be great if offline mode is added similar to how `transformers` loads models offline fine. + +@mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like? +SCORE: 25.505046844482422 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: The local dataset builders (csv, text , json and pandas) are now part of the `datasets` package since #1726 :) +You can now use them offline +\`\`\`python +datasets = load_dataset("text", data_files=data_files) +\`\`\` + +We'll do a new release soon +SCORE: 24.555509567260742 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: I opened a PR that allows to reload modules that have already been loaded once even if there's no internet. + +Let me know if you know other ways that can make the offline mode experience better. I'd be happy to add them :) + +I already note the "freeze" modules option, to prevent local modules updates. It would be a cool feature. + +---------- + +> @mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like? + +Indeed `load_dataset` allows to load remote dataset script (squad, glue, etc.) but also you own local ones. +For example if you have a dataset script at `./my_dataset/my_dataset.py` then you can do +\`\`\`python +load_dataset("./my_dataset") +\`\`\` +and the dataset script will generate your dataset once and for all. + +---------- + +About I'm looking into having `csv`, `json`, `text`, `pandas` dataset builders already included in the `datasets` package, so that they are available offline by default, as opposed to the other datasets that require the script to be downloaded. +cf #1724 +SCORE: 24.14896583557129 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: > here is my way to load a dataset offline, but it **requires** an online machine +> +> 1. (online machine) +> +> ``` +> +> import datasets +> +> data = datasets.load_dataset(...) +> +> data.save_to_disk(/YOUR/DATASET/DIR) +> +> ``` +> +> 2. copy the dir from online to the offline machine +> +> 3. (offline machine) +> +> ``` +> +> import datasets +> +> data = datasets.load_from_disk(/SAVED/DATA/DIR) +> +> ``` +> +> +> +> HTH. + + +SCORE: 22.893993377685547 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: here is my way to load a dataset offline, but it **requires** an online machine +1. (online machine) +\`\`\` +import datasets +data = datasets.load_dataset(...) +data.save_to_disk(/YOUR/DATASET/DIR) +\`\`\` +2. copy the dir from online to the offline machine +3. (offline machine) +\`\`\` +import datasets +data = datasets.load_from_disk(/SAVED/DATA/DIR) +\`\`\` + +HTH. +SCORE: 22.406635284423828 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== +""" +``` + +Không tệ! Lần truy cập thứ hai của chúng ta dường như phù hợp với truy vấn. + + + +✏️ **Thử nghiệm thôi!** Tạo truy vấn của riêng bạn và xem liệu bạn có thể tìm thấy câu trả lời trong các tài liệu đã truy xuất hay không. Bạn có thể phải tăng tham số `k` trong `Dataset.get_nearest_examples()` để mở rộng tìm kiếm. + + diff --git a/chapters/vi/chapter5/7.mdx b/chapters/vi/chapter5/7.mdx new file mode 100644 index 000000000..02ae4bd7e --- /dev/null +++ b/chapters/vi/chapter5/7.mdx @@ -0,0 +1,11 @@ +# 🤗 Datasets, kiểm tra nào! + +Chà, đó là một chuyến tham quan khá thú vị qua thư viện 🤗 Datasets - chúc mừng bạn đã đi được xa như vậy! Với kiến thức bạn đã thu được từ chương này, bạn sẽ có thể: + +- Tải bộ dữ liệu từ bất kỳ đâu, có thể là Hugging Face Hub, máy tính xách tay của bạn hoặc máy chủ từ xa tại công ty của bạn. +- Xoá dữ liệu của bạn bằng cách sử dụng kết hợp các hàm `Dataset.map()` và `Dataset.filter()`. +- Chuyển đổi nhanh chóng giữa các định dạng dữ liệu như Pandas và NumPy bằng cách sử dụng `Dataset.set_format()`. +- Tạo tập dữ liệu của riêng bạn và đẩy nó vào Hugging Face Hub. +- Nhúng tài liệu của bạn bằng mô hình Transformer và xây dựng công cụ tìm kiếm ngữ nghĩa bằng FAISS. + +Trong [Chương 7](/course/chapter7), chúng ta sẽ sử dụng tốt tất cả những điều này khi ta đi sâu vào các tác vụ NLP cốt lõi mà các mô hình Transformer rất phù hợp. Tuy nhiên, trước khi vượt lên phía trước, hãy đưa kiến thức của bạn về 🤗 Datasets vào một bài kiểm tra nhanh! diff --git a/chapters/vi/chapter5/8.mdx b/chapters/vi/chapter5/8.mdx new file mode 100644 index 000000000..40fafb58d --- /dev/null +++ b/chapters/vi/chapter5/8.mdx @@ -0,0 +1,249 @@ + + +# Đố vui cuối chương + +Chương này bao gồm rất nhiều nội dung! Đừng lo lắng nếu bạn không nắm được tất cả các chi tiết; các chương tiếp theo sẽ giúp bạn hiểu mọi thứ hoạt động như thế nào. + +Tuy nhiên, trước khi tiếp tục, hãy kiểm tra những gì bạn đã học được trong chương này. + +### 1. Hàm `load_dataset()` trong 🤗 Datasets cho phép bạn tải tập dữ liệu từ vị trí nào sau đây? + +data_files của load_dataset() để tải các tập dữ liệu cục bộ.", + correct: true, + }, + { + text: "The Hugging Face Hub", + explain: + "Đúng! Bạn có thể tải tập dữ liệu trên Hub bằng cách cung cấp ID tập dữ liệu, ví dụ: load_dataset('emotion').", + correct: true, + }, + { + text: "Máy chủ từ xa", + explain: + "Đúng! Bạn có thể truyền URL đến tham số data_files của load_dataset() để tải các tệp từ xa.", + correct: true, + }, + ]} +/> + +### 2. Giả sử bạn đã tải một trong số các tác vụ GLUE như sau: + +```py +from datasets import load_dataset + +dataset = load_dataset("glue", "mrpc", split="train") +``` + +Đâu là một trong số những câu lệnh sẽ tạo ra một tập mẫu ngẫu nhiên 50 phần tử từ `dataset`? + +dataset.sample(50)", + explain: + "Điều này không chính xác - không có phương thức Dataset.sample().", + }, + { + text: "dataset.shuffle().select(range(50))", + explain: + "Chính xác! Như bạn đã thấy trong chương này, trước tiên bạn xáo trộn tập dữ liệu và sau đó chọn các mẫu từ nó.", + correct: true, + }, + { + text: "dataset.select(range(50)).shuffle()", + explain: + "Điều này không chính xác - mặc dù đoạn mã sẽ chạy, nó sẽ chỉ xáo trộn 50 phần tử đầu tiên trong tập dữ liệu.", + }, + ]} +/> + +### 3. Giả sử bạn có một tập dữ liệu về vật nuôi trong nhà được gọi là `pets_dataset`, có cột `name` biểu thị tên của từng vật nuôi. Phương pháp tiếp cận nào sau đây sẽ cho phép bạn lọc tập dữ liệu cho tất cả vật nuôi có tên bắt đầu bằng chữ cái "L"? + +pets_dataset.filter(lambda x : x['name'].startswith('L'))", + explain: + "Đúng! Sử dụng hàm lambda của Python cho các bộ lọc nhanh này là một ý tưởng tuyệt vời. Bạn có thể nghĩ ra giải pháp khác không?", + correct: true, + }, + { + text: "pets_dataset.filter(lambda x['name'].startswith('L'))", + explain: + "Điều này không chính xác - một hàm lambda có dạng chung lambda *arguments* : *expression*, vì vậy bạn cần cung cấp các tham số trong trường hợp này.", + }, + { + text: "Tạo ra một hàm def filter_names(x): return x['name'].startswith('L') và chạy pets_dataset.filter(filter_names).", + explain: + "Chính xác! Cũng giống như với Dataset.map(), bạn có thể truyền các hàm tường minhDataset.filter(). Điều này rất hữu ích khi bạn có một số logic phức tạp không phù hợp với một hàm lambda ngắn. Giải pháp nào khác sẽ hiệu quả?", + correct: true, + }, + ]} +/> + +### 4. Ánh xạ bộ nhớ là gì? + + + +### 5. Lợi ích chính của ánh xạ bộ nhớ là gì? + + + +### 6. Tại sao đoạn mã sau không thành công? + +```py +from datasets import load_dataset + +dataset = load_dataset("allocine", streaming=True, split="train") +dataset[0] +``` + +IterableDataset.", + explain: + "Đúng! Một IterableDataset là một trình tạo, không phải là một vùng chứa, nên bạn có thể truy cập các phần tử của nó sử dụng next(iter(dataset)).", + correct: true, + }, + { + text: "Tập dữ liệu allocine không có phần tách huấn luyện (train split).", + explain: + "Không chính xác -- tham khảo [thẻ dữ liệu allocine](https://huggingface.co/datasets/allocine) trên Hub để xem có những phần tách dữ liệu nào.", + }, + ]} +/> + +### 7. Lợi ích chính của việc tạo thẻ tập dữ liệu là gì? + + + +### 8. Tìm kiếm ngữ nghĩa là gì? + + + +### 9. Đối với tìm kiếm ngữ nghĩa phi đối xứng, bạn thường có: + + + +### 10. Tôi có thể sử dụng 🤗 Datasets để tải dữ liệu sử dụng cho các mảng khác như xử lý âm thanh được không? + +dữ liệu MNIST trên Hub cho ví dụ về xử lý hình ảnh.", + }, + { + text: "Có", + explain: + "Chính xác! Hãy xem những diễn biến thú vị với giọng nói và hình ảnh trong thư viện 🤗 Transformers để xem cách 🤗 Datasets được sử dụng như thế nào trong các lĩnh vực này.", + correct: true, + }, + ]} +/> diff --git a/chapters/vi/chapter6/1.mdx b/chapters/vi/chapter6/1.mdx new file mode 100644 index 000000000..6540876b7 --- /dev/null +++ b/chapters/vi/chapter6/1.mdx @@ -0,0 +1,14 @@ +# Giới thiệu + +Trong [Chương 3](/course/chapter3), chúng ta đã xem xét cách tinh chỉnh một mô hình trong một tác vụ nhất định. Khi làm điều đó, chúng ta sử dụng cùng một trình tokenizer mà mô hình đã được huấn luyện trước - nhưng chúng ra phải làm gì khi muốn huấn luyện một mô hình từ đầu? Trong những trường hợp này, việc sử dụng trình tokenizer đã được huấn luyện trước trên một kho ngữ liệu từ một lĩnh vực hoặc ngôn ngữ khác thường là không tối ưu. Ví dụ: một tokenizer được huấn luyện trên ngữ liệu tiếng Anh sẽ hoạt động kém trên ngữ liệu văn bản tiếng Nhật vì việc sử dụng dấu cách và dấu câu trong hai ngôn ngữ rất khác nhau. + +Trong chương này, bạn sẽ học cách huấn luyện một trình tokenize hoàn toàn mới trên kho ngữ liệu văn bản, do đó, nó có thể được sử dụng để huấn luyện trước một mô hình ngôn ngữ. Tất cả điều này sẽ được thực hiện với sự trợ giúp của thư viện [🤗 Tokenizers](https://github.com/huggingface/tokenizers), nơi cung cấp các tokenizer "nhanh" trong thư viện [🤗 Transformers](https://github.com/huggingface/transformers). Chúng ta sẽ xem xét kỹ các tính năng mà thư viện này cung cấp và khám phá cách các bản tokenizer nhanh khác so với các phiên bản "chậm". + +Các chủ đề chúng ta sẽ đề cập bao gồm: + +- Cách huấn luyện một trình tokenize mới tương tự như một trình được sử dụng bởi một checkpoint nhất định trên một kho văn bản mới +- Các tính năng đặc biệt của tokenizer nhanh +- Sự khác biệt giữa ba thuật toán tokenize từ phụ được sử dụng trong NLP ngày nay +- Cách xây dựng một tokenizer từ đầu với thư viện 🤗 Tokenizer và huấn luyện nó trên một số dữ liệu + +Các kỹ thuật được giới thiệu trong chương này sẽ giúp bạn chuẩn bị cho phần trong [Chương 7](/course/chapter7/6), nơi chúng ta xem xét việc tạo mô hình ngôn ngữ cho mã nguồn Python. Hãy bắt đầu bằng cách xem xét ý nghĩa của việc "huấn luyện" một tokenizer ngay từ đầu. diff --git a/chapters/vi/chapter6/10.md b/chapters/vi/chapter6/10.md new file mode 100644 index 000000000..0a678358a --- /dev/null +++ b/chapters/vi/chapter6/10.md @@ -0,0 +1,278 @@ + + +# Đố vui cuối chương + +Let's test what you learned in this chapter! + +### 1. When should you train a new tokenizer? + + + +### 2. What is the advantage of using a generator of lists of texts compared to a list of lists of texts when using `train_new_from_iterator()`? + +train_new_from_iterator() accepts.", + explain: "A list of lists of texts is a particular kind of generator of lists of texts, so the method will accept this too. Try again!" + }, + { + text: "You will avoid loading the whole dataset into memory at once.", + explain: "Right! Each batch of texts will be released from memory when you iterate, and the gain will be especially visible if you use 🤗 Datasets to store your texts.", + correct: true + }, + { + text: "This will allow the 🤗 Tokenizers library to use multiprocessing.", + explain: "No, it will use multiprocessing either way." + }, + { + text: "The tokenizer you train will generate better texts.", + explain: "The tokenizer does not generate text -- are you confusing it with a language model?" + } + ]} +/> + +### 3. What are the advantages of using a "fast" tokenizer? + + + +### 4. How does the `token-classification` pipeline handle entities that span over several tokens? + + + +### 5. How does the `question-answering` pipeline handle long contexts? + + + +### 6. What is normalization? + + + +### 7. What is pre-tokenization for a subword tokenizer? + + + +### 8. Select the sentences that apply to the BPE model of tokenization. + + + +### 9. Select the sentences that apply to the WordPiece model of tokenization. + + + +### 10. Select the sentences that apply to the Unigram model of tokenization. + + diff --git a/chapters/vi/chapter6/2.mdx b/chapters/vi/chapter6/2.mdx new file mode 100644 index 000000000..2f0287bcd --- /dev/null +++ b/chapters/vi/chapter6/2.mdx @@ -0,0 +1,257 @@ +# Huấn luyện một tokenizer mới từ cái cũ + + + +Nếu mô hình ngôn ngữ không có sẵn ngôn ngữ bạn quan tâm hoặc nếu kho tài liệu của bạn rất khác với kho mà mô hình ngôn ngữ của bạn đã huấn luyện, bạn rất có thể sẽ muốn huấn luyện lại mô hình từ đầu bằng cách sử dụng trình tokenize phù hợp với dữ liệu của bạn. Điều đó sẽ yêu cầu huấn luyện một trình tokenize mới trên tập dữ liệu của bạn. Nhưng chính xác thì điều đó có nghĩa là gì? Khi chúng ta lần đầu xem xét các tokenizer trong [Chương 2](/course/chapter2), chúng ta thấy rằng hầu hết các mô hình Transformer sử dụng thuật toán tokenize _từ phụ_. Để xác định những từ phụ nào được quan tâm và xuất hiện thường xuyên nhất trong kho ngữ liệu hiện có, trình tokenize cần phải xem xét kỹ tất cả các văn bản trong kho ngữ liệu - một quá trình mà chúng ta gọi là *huấn luyện*. Các quy tắc chi phối việc huấn luyện này phụ thuộc vào loại tokenizer được sử dụng và chúng ta sẽ xem xét ba thuật toán chính ở phần sau của chương này. + + + + + +⚠️ Huấn luyện một tokenizer không giống như huấn luyện một mô hình! Huấn luyện mô hình sử dụng giảm độ dốc ngẫu nhiên để làm cho tổn thất nhỏ hơn một chút cho mỗi đợt. Nó được ngẫu nhiên hóa bởi tự nhiên (có nghĩa là bạn phải đặt một giá trị seed để có được kết quả tương tự khi thực hiện cùng thực hiện huấn luyện hai lần). Huấn luyện một trình tokenize là một quy trình thống kê cố gắng xác định những từ phụ nào tốt nhất để chọn cho một kho dữ liệu nhất định, và các quy tắc được sử dụng để chọn chúng dựa trên thuật toán tokenize. Nó mang tính cố định, nghĩa là bạn luôn nhận được cùng một kết quả khi huấn luyện với cùng một thuật toán trên cùng một kho tài liệu. + + + +## Tập hợp một kho ngữ liệu + +Có một API rất đơn giản trong 🤗 Transformers mà bạn có thể sử dụng để huấn luyện một tokenizer mới có cùng đặc điểm với cái hiện có: `AutoTokenizer.train_new_from_iterator()`. Để thấy điều này trong thực tế, giả sử chúng ta muốn huấn luyện GPT-2 từ đầu, nhưng bằng một ngôn ngữ khác ngoài tiếng Anh. Nhiệm vụ đầu tiên của chúng ta sẽ là thu thập nhiều dữ liệu bằng ngôn ngữ đó trong một kho dữ liệu huấn luyện. Để cung cấp các mẫu mà mọi người có hiểu được, chúng ta sẽ không sử dụng ngôn ngữ như tiếng Nga hoặc tiếng Trung ở đây, mà là ngôn ngữ tiếng Anh chuyên dụng: đoạn mã Python. + +Thư viện [🤗 Datasets](https://github.com/huggingface/datasets) có thể giúp chúng ta tập hợp một kho dữ liệu mã nguồn Python. Chúng ta sẽ sử dụng hàm `load_dataset()` thông thường để tải xuống và lưu vào bộ nhớ cache của tập dữ liệu [CodeSearchNet](https://huggingface.co/datasets/code_search_net). Tập dữ liệu này được tạo cho [thử thách CodeSearchNet](https://wandb.ai/github/CodeSearchNet/benchmark) và chứa hàng triệu hàm từ các thư viện mã nguồn mở trên GitHub bằng một số ngôn ngữ lập trình. Ở đây, chúng ta sẽ tải phần Python của tập dữ liệu này: + +```py +from datasets import load_dataset + +# Quá trình này có thể mất một vài phút để tải, vì vậy hãy lấy cà phê hoặc trà trong khi chờ đợi! +raw_datasets = load_dataset("code_search_net", "python") +``` + +Chúng ta có thể xem xét phần tách huấn luyện để xem ta có quyền truy cập vào những cột nào: + +```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 +}) +``` + +Chúng ta có thể thấy tập dữ liệu tách chuỗi tài liệu mô tả khỏi đoạn mã và đề xuất tokenize cả hai. Ở đây, chúng ta sẽ chỉ sử dụng cột `whole_func_string` để huấn luyện trình tokenize. Chúng ta có thể xem xét mẫu một trong những hàm này bằng cách lập chỉ mục vào phần `train`: + +```py +print(raw_datasets["train"][123456]["whole_func_string"]) +``` + +nó nên trả về kết quả như dưới đây: + +```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) +``` + +Điều đầu tiên chúng ta cần làm là chuyển đổi tập dữ liệu thành một _iterator_ danh sách các văn bản - ví dụ, một danh sách các văn bản. Việc sử dụng danh sách văn bản sẽ cho phép tokenizer hoạt động nhanh hơn (huấn luyện hàng loạt văn bản thay vì xử lý từng văn bản riêng lẻ) và nó phải là một trình lặp nếu chúng ta muốn tránh có mọi thứ trong bộ nhớ cùng một lúc. Nếu kho dữ liệu của bạn lớn, bạn sẽ muốn tận dụng lợi thế thực tiễn là 🤗 Datasets không tải mọi thứ vào RAM mà lưu trữ các phần tử của tập dữ liệu trên đĩa. + +Làm như sau sẽ tạo một danh sách các danh sách với mỗi danh sách gồm 1,000 văn bản, nhưng sẽ tải mọi thứ vào bộ nhớ: + +```py +# Đừng bỏ ghi chú dòng bên dưới trừ khi tập dữ liệu của bạn nhỏ! +# training_corpus = [raw_datasets["train"][i: i + 1000]["whole_func_string"] for i in range(0, len(raw_datasets["train"]), 1000)] +``` + +Sử dụng trình tạo Python, chúng ta có thể tránh việc Python tải bất kỳ thứ gì vào bộ nhớ cho đến khi nó thực sự cần thiết. Để tạo một trình tạo như vậy, bạn chỉ cần thay dấu ngoặc vuông bằng dấu ngoặc đơn: + +```py +training_corpus = ( + raw_datasets["train"][i : i + 1000]["whole_func_string"] + for i in range(0, len(raw_datasets["train"]), 1000) +) +``` + +Dòng mã này không tìm nạp bất kỳ phần tử nào của tập dữ liệu; nó chỉ tạo một đối tượng mà bạn có thể sử dụng trong vòng lặp Python `for`. Các văn bản sẽ chỉ được tải khi bạn cần (nghĩa là khi bạn đang ở bước của vòng lặp `for` mà yêu cầu chúng) và chỉ 1,000 văn bản sẽ được tải mỗi lần. Bằng cách này, bạn sẽ không sử dụng hết bộ nhớ của mình ngay cả khi bạn đang xử lý một tập dữ liệu lớn. + +Vấn đề với một đối tượng tạo là nó chỉ có thể được sử dụng một lần. Vì vậy, thay vì điều này cho ta danh sách 10 chữ số đầu tiên hai lần: + +```py +gen = (i for i in range(10)) +print(list(gen)) +print(list(gen)) +``` + +chúng ta có thể lấy chúng trong một lần và sau đó danh sáng sẽ trống: + +```python out +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +[] +``` + +Đó là lí do chúng ta định nghĩa một hàm thay vào đó trả về một trình tạo: + +```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() +``` + +Ta có thể định nghĩa trình tạo bên trong vòng lặp `for` sử dụng `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"] +``` + +sẽ tạo ra trình tạo hoàn toàn giống như trước đây, nhưng cho phép bạn sử dụng logic phức tạp hơn bạn có thể trong một bao hàm. + +## Huấn luyện một tokenizer mới + +Bây giờ chúng ta đã có kho văn bản của mình dưới dạng một trình lặp các loạt văn bản, chúng ta đã sẵn sàng để huấn luyện một trình tokenize mới. Để thực hiện việc này, trước tiên chúng ta cần tải tokenizer mà chúng ta muốn ghép nối với mô hình của mình (ở đây, GPT-2): + +```py +from transformers import AutoTokenizer + +old_tokenizer = AutoTokenizer.from_pretrained("gpt2") +``` + +Mặc dù chúng ta sẽ huấn luyện một tokenizer, nhưng bạn nên làm điều này để tránh bắt đầu hoàn toàn từ đầu. Bằng cách này, chúng ta sẽ không phải chỉ định bất kỳ điều gì về thuật toán tokenize hoặc các token đặc biệt mà ta muốn sử dụng; tokenizer mới sẽ giống hệt như GPT-2 và điều duy nhất sẽ thay đổi là từ vựng, sẽ được xác định bởi quá trình huấn luyện trên kho ngữ liệu của chúng tôi. + +Đầu tiên, chúng ta hãy xem cách mà tokenizer này sẽ xử lý một hàm mẫu thế nào: + +```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'] +``` + +Tokenizer này có một số ký hiệu đặc biệt, như `Ġ` và `Ċ`, tương ứng biểu thị dấu cách và dòng mới. Như chúng ta có thể thấy, điều này không quá hiệu quả: tokenizer trả về các mã thông báo riêng lẻ cho từng khoảng trắng, khi nó có thể nhóm các mức thụt lề lại với nhau (vì có bộ bốn hoặc tám dấu cách sẽ rất phổ biến trong mã). Nó cũng tách tên hàm hơi kỳ lạ, nhìn không quen các từ có ký tự `_`. + +Hãy huấn luyện một tokenizer mới và xem liệu nó có giải quyết được những vấn đề đó không. Đối với điều này, chúng ta sẽ sử dụng phương thức `train_new_from_iterator()`: + +```py +tokenizer = old_tokenizer.train_new_from_iterator(training_corpus, 52000) +``` + +Lệnh này có thể mất một chút thời gian nếu kho dữ liệu của bạn rất lớn, nhưng đối với tập dữ liệu 1.6GB văn bản này, nó rất nhanh (1 phút 16 giây trên CPU AMD Ryzen 9 3900X với 12 lõi). + +Lưu ý rằng `AutoTokenizer.train_new_from_iterator()` chỉ hoạt động nếu tokenizer bạn đang sử dụng là tokenizer "nhanh". Như bạn sẽ thấy trong phần tiếp theo, thư viện 🤗 Transformers chứa hai loại tokenizers: một số được viết hoàn toàn bằng Python và những loại khác (loại nhanh) được hỗ trợ bởi thư viện 🤗 Tokenizers, được viết bằng ngôn ngữ lập trình [Rust](https://www.rust-lang.org). Python là ngôn ngữ thường được sử dụng nhất cho các ứng dụng khoa học dữ liệu và học sâu, nhưng khi bất kỳ thứ gì cần được song song hóa cho nhanh, nó phải được viết bằng một ngôn ngữ khác. Ví dụ, các phép nhân ma trận là cốt lõi của tính toán mô hình được viết bằng CUDA, một thư viện C được tối ưu hóa cho GPU. + +Việc huấn luyện một tokenizer hoàn toàn mới bằng Python thuần túy sẽ rất chậm, đó là lý do tại sao chúng tôi đã phát triển thư viện 🤗 Tokenizer. Lưu ý rằng cũng giống như bạn không phải học ngôn ngữ CUDA để có thể thực thi mô hình của mình trên một loạt đầu vào trên GPU, bạn sẽ không cần phải học Rust để sử dụng trình tokenizer nhanh. Thư viện 🤗 Tokenizers cung cấp các liên kết Python cho nhiều phương thức gọi nội bộ một số đoạn mã trong Rust; ví dụ: để song song huấn luyện trình tokenize mới của bạn hoặc, như chúng ta đã thấy trong [Chương 3](/course/chapter3), tokenize một loạt đầu vào. + +Hầu hết các mô hình Transformer đều có sẵn công cụ tokenize nhanh (có một số ngoại lệ mà bạn có thể kiểm tra [tại đây](https://huggingface.co/transformers/#supported-frameworks)) và API `AutoTokenizer` luôn chọn tốc tokenizer nhanh cho bạn nếu nó có sẵn. Trong phần tiếp theo, chúng ta sẽ xem xét một số tính năng đặc biệt khác mà các tokenize nhanh có mà thực sự hữu ích cho các tác vụ như phân loại token và hỏi đáp. Tuy nhiên, trước khi đi sâu vào vấn đề đó, chúng ta hãy thử tokenizer hoàn toàn mới của chúng ta trên mẫu trước: + +```py +tokens = tokenizer.tokenize(example) +tokens +``` + +```python out +['def', 'Ġadd', '_', 'numbers', '(', 'a', ',', 'Ġb', '):', 'ĊĠĠĠ', 'Ġ"""', 'Add', 'Ġthe', 'Ġtwo', 'Ġnumbers', 'Ġ`', + 'a', '`', 'Ġand', 'Ġ`', 'b', '`."""', 'ĊĠĠĠ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb'] +``` + +Ở đây chúng ta lại thấy các ký hiệu đặc biệt `Ġ` và `Ċ` biểu thị dấu cách và dòng mới, nhưng chúng ta cũng có thể thấy rằng trình tokenize đã học được một số token rất cụ thể cho một kho các hàm Python: ví dụ: có một token `ĊĠĠĠ` đại diện cho một thụt lề và token `Ġ"""` đại diện cho ba dấu ngoặc kép bắt đầu một chuỗi tài liệu. Tokenizer cũng phân chia chính xác tên hàm trên `_`. Đây là một biễu diễn khá nhỏ gọn; tương đối, sử dụng tokenizer đơn giản bằng tiếng Anh trên cùng một mẫu sẽ cho ta một câu dài hơn: + +```py +print(len(tokens)) +print(len(old_tokenizer.tokenize(example))) +``` + +```python out +27 +36 +``` + +Hãy cùng nhìn vào ví dụ sau: + +```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', 'ĊĠĠĠĠ'] +``` + +Ngoài token tương ứng với thụt lề, ở đây chúng ta cũng có thể thấy token cho thụt lề kép:`ĊĠĠĠĠĠĠĠ`. Các từ đặc biệt trong Python như `class`, `init`, `call`, `self`, và `return`, mỗi từ được tokenize thành một token và chúng ta có thể thấy cũng như tách `_` và `.`, tokenizer phân chia chính xác các tên: `LinearLayer` được tokenize là `["ĠLinear", "Layer"]`. + +## Lưu tokenizer + +Để đảm bảo rằng chúng ta có thể sử dụng nó sau này, chúng ta cần phải lưu tokenizer mới của mình. Giống như đối với các mô hình, điều này được thực hiện với phương thức `save_pretrained()`: + +```py +tokenizer.save_pretrained("code-search-net-tokenizer") +``` + +Thao tác này sẽ tạo một thư mục mới có tên *code-search-net-tokenizer*, sẽ chứa tất cả các tệp mà tokenizer cần được tải lại. Nếu bạn muốn chia sẻ tokenizer này với đồng nghiệp và bạn bè của mình, bạn có thể tải nó lên Hub bằng cách đăng nhập vào tài khoản của mình. Nếu bạn đang làm việc trên notebook, có một hàm tiện ích giúp bạn làm điều này: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Thao tác này sẽ hiển thị một tiện ích mà bạn có thể nhập thông tin đăng nhập Hugging Face của mình. Nếu bạn không làm việc trong notebook, chỉ cần nhập dòng sau vào thiết bị đầu cuối của bạn: + +```bash +huggingface-cli login +``` + +Khi bạn đã đăng nhập, bạn có thể đẩy tokenizer của mình bằng cách thực hiện lệnh sau: + +```py +tokenizer.push_to_hub("code-search-net-tokenizer") +``` + +Thao tác này sẽ tạo một kho lưu trữ mới trong không gian tên của bạn với tên `code-search-net-tokenizer`, chứa tệp tokenizer. Sau đó bạn có thể tải tokenizer từ bất kì đâu với phương thức `from_pretrained()`: + +```py +# Thay "huggingface-course" dưới đấy với tên không gian thực sự sử dụng tokenizer riêng của bạn +tokenizer = AutoTokenizer.from_pretrained("huggingface-course/code-search-net-tokenizer") +``` + +Giờ bạn đã sẵn sàng để huấn luyện một mô hình ngôn ngữ từ đầu và việc tinh chỉnh nó trong tầm tay của bạn! Chúng ta sẽ tìm hiểu điều đó trong [Chương 7](/course/chap7), nhưng trước tiên, trong phần còn lại của chương này, chúng ta sẽ xem xét kỹ hơn về các trình tokenize nhanh và khám phá chi tiết những gì thực sự xảy ra khi chúng ta gọi phương thức `train_new_from_iterator()`. diff --git a/chapters/vi/chapter6/3.md b/chapters/vi/chapter6/3.md new file mode 100644 index 000000000..c43fead52 --- /dev/null +++ b/chapters/vi/chapter6/3.md @@ -0,0 +1,473 @@ + + +# Sức mạnh đặc biệt của tokenizer nhanh + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +In this section we will take a closer look at the capabilities of the tokenizers in 🤗 Transformers. Up to now we have only used them to tokenize inputs or decode IDs back into text, but tokenizers -- especially those backed by the 🤗 Tokenizers library -- can do a lot more. To illustrate these additional features, we will explore how to reproduce the results of the `token-classification` (that we called `ner`) and `question-answering` pipelines that we first encountered in [Chapter 1](/course/chapter1). + + + +In the following discussion, we will often make the distinction between "slow" and "fast" tokenizers. Slow tokenizers are those written in Python inside the 🤗 Transformers library, while the fast versions are the ones provided by 🤗 Tokenizers, which are written in Rust. If you remember the table from [Chapter 5](/course/chapter5/3) that reported how long it took a fast and a slow tokenizer to tokenize the Drug Review Dataset, you should have an idea of why we call them fast and slow: + + | Fast tokenizer | Slow tokenizer +:--------------:|:--------------:|:-------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s + + + +⚠️ When tokenizing a single sentence, you won't always see a difference in speed between the slow and fast versions of the same tokenizer. In fact, the fast version might actually be slower! It's only when tokenizing lots of texts in parallel at the same time that you will be able to clearly see the difference. + + + +## Batch encoding + + + +The output of a tokenizer isn't a simple Python dictionary; what we get is actually a special `BatchEncoding` object. It's a subclass of a dictionary (which is why we were able to index into that result without any problem before), but with additional methods that are mostly used by fast tokenizers. + +Besides their parallelization capabilities, the key functionality of fast tokenizers is that they always keep track of the original span of texts the final tokens come from -- a feature we call *offset mapping*. This in turn unlocks features like mapping each word to the tokens it generated or mapping each character of the original text to the token it's inside, and vice versa. + +Let's take a look at an example: + +```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)) +``` + +As mentioned previously, we get a `BatchEncoding` object in the tokenizer's output: + +```python out + +``` + +Since the `AutoTokenizer` class picks a fast tokenizer by default, we can use the additional methods this `BatchEncoding` object provides. We have two ways to check if our tokenizer is a fast or a slow one. We can either check the attribute `is_fast` of the `tokenizer`: + +```python +tokenizer.is_fast +``` + +```python out +True +``` + +or check the same attribute of our `encoding`: + +```python +encoding.is_fast +``` + +```python out +True +``` + +Let's see what a fast tokenizer enables us to do. First, we can access the tokens without having to convert the IDs back to tokens: + +```py +encoding.tokens() +``` + +```python out +['[CLS]', 'My', 'name', 'is', 'S', '##yl', '##va', '##in', 'and', 'I', 'work', 'at', 'Hu', '##gging', 'Face', 'in', + 'Brooklyn', '.', '[SEP]'] +``` + +In this case the token at index 5 is `##yl`, which is part of the word "Sylvain" in the original sentence. We can also use the `word_ids()` method to get the index of the word each token comes from: + +```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] +``` + +We can see that the tokenizer's special tokens `[CLS]` and `[SEP]` are mapped to `None`, and then each token is mapped to the word it originates from. This is especially useful to determine if a token is at the start of a word or if two tokens are in the same word. We could rely on the `##` prefix for that, but it only works for BERT-like tokenizers; this method works for any type of tokenizer as long as it's a fast one. In the next chapter, we'll see how we can use this capability to apply the labels we have for each word properly to the tokens in tasks like named entity recognition (NER) and part-of-speech (POS) tagging. We can also use it to mask all the tokens coming from the same word in masked language modeling (a technique called _whole word masking_). + + + +The notion of what a word is is complicated. For instance, does "I'll" (a contraction of "I will") count as one or two words? It actually depends on the tokenizer and the pre-tokenization operation it applies. Some tokenizers just split on spaces, so they will consider this as one word. Others use punctuation on top of spaces, so will consider it two words. + +✏️ **Try it out!** Create a tokenizer from the `bert-base-cased` and `roberta-base` checkpoints and tokenize "81s" with them. What do you observe? What are the word IDs? + + + +Similarly, there is a `sentence_ids()` method that we can use to map a token to the sentence it came from (though in this case, the `token_type_ids` returned by the tokenizer can give us the same information). + +Lastly, we can map any word or token to characters in the original text, and vice versa, via the `word_to_chars()` or `token_to_chars()` and `char_to_word()` or `char_to_token()` methods. For instance, the `word_ids()` method told us that `##yl` is part of the word at index 3, but which word is it in the sentence? We can find out like this: + +```py +start, end = encoding.word_to_chars(3) +example[start:end] +``` + +```python out +Sylvain +``` + +As we mentioned previously, this is all powered by the fact the fast tokenizer keeps track of the span of text each token comes from in a list of *offsets*. To illustrate their use, next we'll show you how to replicate the results of the `token-classification` pipeline manually. + + + +✏️ **Try it out!** Create your own example text and see if you can understand which tokens are associated with word ID, and also how to extract the character spans for a single word. For bonus points, try using two sentences as input and see if the sentence IDs make sense to you. + + + +## Inside the `token-classification` pipeline + +In [Chapter 1](/course/chapter1) we got our first taste of applying NER -- where the task is to identify which parts of the text correspond to entities like persons, locations, or organizations -- with the 🤗 Transformers `pipeline()` function. Then, in [Chapter 2](/course/chapter2), we saw how a pipeline groups together the three stages necessary to get the predictions from a raw text: tokenization, passing the inputs through the model, and post-processing. The first two steps in the `token-classification` pipeline are the same as in any other pipeline, but the post-processing is a little more complex -- let's see how! + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +### Getting the base results with the pipeline + +First, let's grab a token classification pipeline so we can get some results to compare manually. The model used by default is [`dbmdz/bert-large-cased-finetuned-conll03-english`](https://huggingface.co/dbmdz/bert-large-cased-finetuned-conll03-english); it performs NER on sentences: + +```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}] +``` + +The model properly identified each token generated by "Sylvain" as a person, each token generated by "Hugging Face" as an organization, and the token "Brooklyn" as a location. We can also ask the pipeline to group together the tokens that correspond to the same entity: + +```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}] +``` + +The `aggregation_strategy` picked will change the scores computed for each grouped entity. With `"simple"` the score is just the mean of the scores of each token in the given entity: for instance, the score of "Sylvain" is the mean of the scores we saw in the previous example for the tokens `S`, `##yl`, `##va`, and `##in`. Other strategies available are: + +- `"first"`, where the score of each entity is the score of the first token of that entity (so for "Sylvain" it would be 0.993828, the score of the token `S`) +- `"max"`, where the score of each entity is the maximum score of the tokens in that entity (so for "Hugging Face" it would be 0.98879766, the score of "Face") +- `"average"`, where the score of each entity is the average of the scores of the words composing that entity (so for "Sylvain" there would be no difference from the `"simple"` strategy, but "Hugging Face" would have a score of 0.9819, the average of the scores for "Hugging", 0.975, and "Face", 0.98879) + +Now let's see how to obtain these results without using the `pipeline()` function! + +### From inputs to predictions + +{#if fw === 'pt'} + +First we need to tokenize our input and pass it through the model. This is done exactly as in [Chapter 2](/course/chapter2); we instantiate the tokenizer and the model using the `AutoXxx` classes and then use them on our example: + +```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) +``` + +Since we're using `AutoModelForTokenClassification` here, we get one set of logits for each token in the input sequence: + +```py +print(inputs["input_ids"].shape) +print(outputs.logits.shape) +``` + +```python out +torch.Size([1, 19]) +torch.Size([1, 19, 9]) +``` + +{:else} + +First we need to tokenize our input and pass it through the model. This is done exactly as in [Chapter 2](/course/chapter2); we instantiate the tokenizer and the model using the `TFAutoXxx` classes and then use them on our example: + +```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) +``` + +Since we're using `TFAutoModelForTokenClassification` here, we get one set of logits for each token in the input sequence: + +```py +print(inputs["input_ids"].shape) +print(outputs.logits.shape) +``` + +```python out +(1, 19) +(1, 19, 9) +``` + +{/if} + +We have a batch with 1 sequence of 19 tokens and the model has 9 different labels, so the output of the model has a shape of 1 x 19 x 9. Like for the text classification pipeline, we use a softmax function to convert those logits to probabilities, and we take the argmax to get predictions (note that we can take the argmax on the logits because the softmax does not change the order): + +{#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] +``` + +The `model.config.id2label` attribute contains the mapping of indexes to labels that we can use to make sense of the predictions: + +```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'} +``` + +As we saw earlier, there are 9 labels: `O` is the label for the tokens that are not in any named entity (it stands for "outside"), and we then have two labels for each type of entity (miscellaneous, person, organization, and location). The label `B-XXX` indicates the token is at the beginning of an entity `XXX` and the label `I-XXX` indicates the token is inside the entity `XXX`. For instance, in the current example we would expect our model to classify the token `S` as `B-PER` (beginning of a person entity) and the tokens `##yl`, `##va` and `##in` as `I-PER` (inside a person entity). + +You might think the model was wrong in this case as it gave the label `I-PER` to all four of these tokens, but that's not entirely true. There are actually two formats for those `B-` and `I-` labels: *IOB1* and *IOB2*. The IOB2 format (in pink below), is the one we introduced whereas in the IOB1 format (in blue), the labels beginning with `B-` are only ever used to separate two adjacent entities of the same type. The model we are using was fine-tuned on a dataset using that format, which is why it assigns the label `I-PER` to the `S` token. + +
+IOB1 vs IOB2 format + +
+ +With this map, we are ready to reproduce (almost entirely) the results of the first pipeline -- we can just grab the score and label of each token that was not classified as `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'}] +``` + +This is very similar to what we had before, with one exception: the pipeline also gave us information about the `start` and `end` of each entity in the original sentence. This is where our offset mapping will come into play. To get the offsets, we just have to set `return_offsets_mapping=True` when we apply the tokenizer to our 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)] +``` + +Each tuple is the span of text corresponding to each token, where `(0, 0)` is reserved for the special tokens. We saw before that the token at index 5 is `##yl`, which has `(12, 14)` as offsets here. If we grab the corresponding slice in our example: + + +```py +example[12:14] +``` + +we get the proper span of text without the `##`: + +```python out +yl +``` + +Using this, we can now complete the previous results: + +```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}] +``` + +This is the same as what we got from the first pipeline! + +### Grouping entities + +Using the offsets to determine the start and end keys for each entity is handy, but that information isn't strictly necessary. When we want to group the entities together, however, the offsets will save us a lot of messy code. For example, if we wanted to group together the tokens `Hu`, `##gging`, and `Face`, we could make special rules that say the first two should be attached while removing the `##`, and the `Face` should be added with a space since it does not begin with `##` -- but that would only work for this particular type of tokenizer. We would have to write another set of rules for a SentencePiece or a Byte-Pair-Encoding tokenizer (discussed later in this chapter). + +With the offsets, all that custom code goes away: we just can take the span in the original text that begins with the first token and ends with the last token. So, in the case of the tokens `Hu`, `##gging`, and `Face`, we should start at character 33 (the beginning of `Hu`) and end before character 45 (the end of `Face`): + +```py +example[33:45] +``` + +```python out +Hugging Face +``` + +To write the code that post-processes the predictions while grouping entities, we will group together entities that are consecutive and labeled with `I-XXX`, except for the first one, which can be labeled as `B-XXX` or `I-XXX` (so, we stop grouping an entity when we get a `O`, a new type of entity, or a `B-XXX` that tells us an entity of the same type is starting): + +```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] + + # Grab all the tokens labeled with I-label + 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 + + # The score is the mean of all the scores of the tokens in that grouped entity + 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) +``` + +And we get the same results as with our second 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}] +``` + +Another example of a task where these offsets are extremely useful is question answering. Diving into that pipeline, which we'll do in the next section, will also enable us to take a look at one last feature of the tokenizers in the 🤗 Transformers library: dealing with overflowing tokens when we truncate an input to a given length. diff --git a/chapters/vi/chapter6/3b.md b/chapters/vi/chapter6/3b.md new file mode 100644 index 000000000..595235c8b --- /dev/null +++ b/chapters/vi/chapter6/3b.md @@ -0,0 +1,642 @@ + + +# Tokenizer nhanh trong pipeline QA + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +We will now dive into the `question-answering` pipeline and see how to leverage the offsets to grab the answer to the question at hand from the context, a bit like we did for the grouped entities in the previous section. Then we will see how we can deal with very long contexts that end up being truncated. You can skip this section if you're not interested in the question answering task. + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +## Using the `question-answering` pipeline + +As we saw in [Chapter 1](/course/chapter1), we can use the `question-answering` pipeline like this to get the answer to a question: + +```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'} +``` + +Unlike the other pipelines, which can't truncate and split texts that are longer than the maximum length accepted by the model (and thus may miss information at the end of a document), this pipeline can deal with very long contexts and will return the answer to the question even if it's at the end: + +```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'} +``` + +Let's see how it does all of this! + +## Using a model for question answering + +Like with any other pipeline, we start by tokenizing our input and then send it through the model. The checkpoint used by default for the `question-answering` pipeline is [`distilbert-base-cased-distilled-squad`](https://huggingface.co/distilbert-base-cased-distilled-squad) (the "squad" in the name comes from the dataset on which the model was fine-tuned; we'll talk more about the SQuAD dataset in [Chapter 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} + +Note that we tokenize the question and the context as a pair, with the question first. + +
+An example of tokenization of question and context + +
+ +Models for question answering work a little differently from the models we've seen up to now. Using the picture above as an example, the model has been trained to predict the index of the token starting the answer (here 21) and the index of the token where the answer ends (here 24). This is why those models don't return one tensor of logits but two: one for the logits corresponding to the start token of the answer, and one for the logits corresponding to the end token of the answer. Since in this case we have only one input containing 66 tokens, we get: + +```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} + +To convert those logits into probabilities, we will apply a softmax function -- but before that, we need to make sure we mask the indices that are not part of the context. Our input is `[CLS] question [SEP] context [SEP]`, so we need to mask the tokens of the question as well as the `[SEP]` token. We'll keep the `[CLS]` token, however, as some models use it to indicate that the answer is not in the context. + +Since we will apply a softmax afterward, we just need to replace the logits we want to mask with a large negative number. Here, we use `-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} + +Now that we have properly masked the logits corresponding to positions we don't want to predict, we can apply the 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} + +At this stage, we could take the argmax of the start and end probabilities -- but we might end up with a start index that is greater than the end index, so we need to take a few more precautions. We will compute the probabilities of each possible `start_index` and `end_index` where `start_index <= end_index`, then take the tuple `(start_index, end_index)` with the highest probability. + +Assuming the events "The answer starts at `start_index`" and "The answer ends at `end_index`" to be independent, the probability that the answer starts at `start_index` and ends at `end_index` is: + +$$\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]$$ + +So, to compute all the scores, we just need to compute all the products \\(\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]\\) where `start_index <= end_index`. + +First let's compute all the possible products: + +```py +scores = start_probabilities[:, None] * end_probabilities[None, :] +``` + +{#if fw === 'pt'} + +Then we'll mask the values where `start_index > end_index` by setting them to `0` (the other probabilities are all positive numbers). The `torch.triu()` function returns the upper triangular part of the 2D tensor passed as an argument, so it will do that masking for us: + +```py +scores = torch.triu(scores) +``` + +{:else} + +Then we'll mask the values where `start_index > end_index` by setting them to `0` (the other probabilities are all positive numbers). The `np.triu()` function returns the upper triangular part of the 2D tensor passed as an argument, so it will do that masking for us: + +```py +import numpy as np + +scores = np.triu(scores) +``` + +{/if} + +Now we just have to get the index of the maximum. Since PyTorch will return the index in the flattened tensor, we need to use the floor division `//` and modulus `%` operations to get the `start_index` and `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]) +``` + +We're not quite done yet, but at least we already have the correct score for the answer (you can check this by comparing it to the first result in the previous section): + +```python out +0.97773 +``` + + + +✏️ **Try it out!** Compute the start and end indices for the five most likely answers. + + + +We have the `start_index` and `end_index` of the answer in terms of tokens, so now we just need to convert to the character indices in the context. This is where the offsets will be super useful. We can grab them and use them like we did in the token classification task: + +```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] +``` + +Now we just have to format everything to get our result: + +```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} +``` + +Great! That's the same as in our first example! + + + +✏️ **Try it out!** Use the best scores you computed earlier to show the five most likely answers. To check your results, go back to the first pipeline and pass in `top_k=5` when calling it. + + + +## Handling long contexts + +If we try to tokenize the question and long context we used as an example previously, we'll get a number of tokens higher than the maximum length used in the `question-answering` pipeline (which is 384): + +```py +inputs = tokenizer(question, long_context) +print(len(inputs["input_ids"])) +``` + +```python out +461 +``` + +So, we'll need to truncate our inputs at that maximum length. There are several ways we can do this, but we don't want to truncate the question, only the context. Since the context is the second sentence, we'll use the `"only_second"` truncation strategy. The problem that arises then is that the answer to the question may not be in the truncated context. Here, for instance, we picked a question where the answer is toward the end of the context, and when we truncate it that answer is not present: + +```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] +""" +``` + +This means the model will have a hard time picking the correct answer. To fix this, the `question-answering` pipeline allows us to split the context into smaller chunks, specifying the maximum length. To make sure we don't split the context at exactly the wrong place to make it possible to find the answer, it also includes some overlap between the chunks. + +We can have the tokenizer (fast or slow) do this for us by adding `return_overflowing_tokens=True`, and we can specify the overlap we want with the `stride` argument. Here is an example, using a smaller sentence: + +```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]' +``` + +As we can see, the sentence has been split into chunks in such a way that each entry in `inputs["input_ids"]` has at most 6 tokens (we would need to add padding to have the last entry be the same size as the others) and there is an overlap of 2 tokens between each of the entries. + +Let's take a closer look at the result of the tokenization: + +```py +print(inputs.keys()) +``` + +```python out +dict_keys(['input_ids', 'attention_mask', 'overflow_to_sample_mapping']) +``` + +As expected, we get input IDs and an attention mask. The last key, `overflow_to_sample_mapping`, is a map that tells us which sentence each of the results corresponds to -- here we have 7 results that all come from the (only) sentence we passed the tokenizer: + +```py +print(inputs["overflow_to_sample_mapping"]) +``` + +```python out +[0, 0, 0, 0, 0, 0, 0] +``` + +This is more useful when we tokenize several sentences together. For instance, this: + +```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] +``` + +which means the first sentence is split into 7 chunks as before, and the next 4 chunks come from the second sentence. + +Now let's go back to our long context. By default the `question-answering` pipeline uses a maximum length of 384, as we mentioned earlier, and a stride of 128, which correspond to the way the model was fine-tuned (you can adjust those parameters by passing `max_seq_len` and `stride` arguments when calling the pipeline). We will thus use those parameters when tokenizing. We'll also add padding (to have samples of the same length, so we can build tensors) as well as ask for the 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, +) +``` + +Those `inputs` will contain the input IDs and attention masks the model expects, as well as the offsets and the `overflow_to_sample_mapping` we just talked about. Since those two are not parameters used by the model, we'll pop them out of the `inputs` (and we won't store the map, since it's not useful here) before converting it to a 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} + +Our long context was split in two, which means that after it goes through our model, we will have two sets of start and end logits: + +```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} + +Like before, we first mask the tokens that are not part of the context before taking the softmax. We also mask all the padding tokens (as flagged by the attention mask): + +{#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} + +Then we can use the softmax to convert our logits to probabilities: + +{#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} + +The next step is similar to what we did for the small context, but we repeat it for each of our two chunks. We attribute a score to all possible spans of answer, then take the span with the best score: + +{#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[0] + end_idx = idx % scores.shape[0] + 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[0] + end_idx = idx % scores.shape[0] + 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)] +``` + +Those two candidates correspond to the best answers the model was able to find in each chunk. The model is way more confident the right answer is in the second part (which is a good sign!). Now we just have to map those two token spans to spans of characters in the context (we only need to map the second one to have our answer, but it's interesting to see what the model has picked in the first chunk). + + + +✏️ **Try it out!** Adapt the code above to return the scores and spans for the five most likely answers (in total, not per chunk). + + + +The `offsets` we grabbed earlier is actually a list of offsets, with one list per chunk of text: + +```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} +``` + +If we ignore the first result, we get the same result as our pipeline for this long context -- yay! + + + +✏️ **Try it out!** Use the best scores you computed before to show the five most likely answers (for the whole context, not each chunk). To check your results, go back to the first pipeline and pass in `top_k=5` when calling it. + + + +This concludes our deep dive into the tokenizer's capabilities. We will put all of this in practice again in the next chapter, when we show you how to fine-tune a model on a range of common NLP tasks. diff --git a/chapters/vi/chapter6/4.md b/chapters/vi/chapter6/4.md new file mode 100644 index 000000000..6710ed42a --- /dev/null +++ b/chapters/vi/chapter6/4.md @@ -0,0 +1,123 @@ +# Chuẩn hoá và tiền tokenize + + + +Before we dive more deeply into the three most common subword tokenization algorithms used with Transformer models (Byte-Pair Encoding [BPE], WordPiece, and Unigram), we'll first take a look at the preprocessing that each tokenizer applies to text. Here's a high-level overview of the steps in the tokenization pipeline: + +
+The tokenization pipeline. + +
+ +Before splitting a text into subtokens (according to its model), the tokenizer performs two steps: _normalization_ and _pre-tokenization_. + +## Normalization + + + +The normalization step involves some general cleanup, such as removing needless whitespace, lowercasing, and/or removing accents. If you're familiar with [Unicode normalization](http://www.unicode.org/reports/tr15/) (such as NFC or NFKC), this is also something the tokenizer may apply. + +The 🤗 Transformers `tokenizer` has an attribute called `backend_tokenizer` that provides access to the underlying tokenizer from the 🤗 Tokenizers library: + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased") +print(type(tokenizer.backend_tokenizer)) +``` + +```python out + +``` + +The `normalizer` attribute of the `tokenizer` object has a `normalize_str()` method that we can use to see how the normalization is performed: + +```py +print(tokenizer.backend_tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) +``` + +```python out +'hello how are u?' +``` + +In this example, since we picked the `bert-base-uncased` checkpoint, the normalization applied lowercasing and removed the accents. + + + +✏️ **Try it out!** Load a tokenizer from the `bert-base-cased` checkpoint and pass the same example to it. What are the main differences you can see between the cased and uncased versions of the tokenizer? + + + +## Pre-tokenization + + + +As we will see in the next sections, a tokenizer cannot be trained on raw text alone. Instead, we first need to split the texts into small entities, like words. That's where the pre-tokenization step comes in. As we saw in [Chapter 2](/course/chapter2), a word-based tokenizer can simply split a raw text into words on whitespace and punctuation. Those words will be the boundaries of the subtokens the tokenizer can learn during its training. + +To see how a fast tokenizer performs pre-tokenization, we can use the `pre_tokenize_str()` method of the `pre_tokenizer` attribute of the `tokenizer` object: + +```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))] +``` + +Notice how the tokenizer is already keeping track of the offsets, which is how it can give us the offset mapping we used in the previous section. Here the tokenizer ignores the two spaces and replaces them with just one, but the offset jumps between `are` and `you` to account for that. + +Since we're using a BERT tokenizer, the pre-tokenization involves splitting on whitespace and punctuation. Other tokenizers can have different rules for this step. For example, if we use the GPT-2 tokenizer: + +```py +tokenizer = AutoTokenizer.from_pretrained("gpt2") +tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") +``` + +it will split on whitespace and punctuation as well, but it will keep the spaces and replace them with a `Ġ` symbol, enabling it to recover the original spaces if we decode the tokens: + +```python out +[('Hello', (0, 5)), (',', (5, 6)), ('Ġhow', (6, 10)), ('Ġare', (10, 14)), ('Ġ', (14, 15)), ('Ġyou', (15, 19)), + ('?', (19, 20))] +``` + +Also note that unlike the BERT tokenizer, this tokenizer does not ignore the double space. + +For a last example, let's have a look at the T5 tokenizer, which is based on the SentencePiece algorithm: + +```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))] +``` + +Like the GPT-2 tokenizer, this one keeps spaces and replaces them with a specific token (`_`), but the T5 tokenizer only splits on whitespace, not punctuation. Also note that it added a space by default at the beginning of the sentence (before `Hello`) and ignored the double space between `are` and `you`. + +Now that we've seen a little of how some different tokenizers process text, we can start to explore the underlying algorithms themselves. We'll begin with a quick look at the broadly widely applicable SentencePiece; then, over the next three sections, we'll examine how the three main algorithms used for subword tokenization work. + +## SentencePiece + +[SentencePiece](https://github.com/google/sentencepiece) is a tokenization algorithm for the preprocessing of text that you can use with any of the models we will see in the next three sections. It considers the text as a sequence of Unicode characters, and replaces spaces with a special character, `▁`. Used in conjunction with the Unigram algorithm (see [section 7](/course/chapter7/7)), it doesn't even require a pre-tokenization step, which is very useful for languages where the space character is not used (like Chinese or Japanese). + +The other main feature of SentencePiece is *reversible tokenization*: since there is no special treatment of spaces, decoding the tokens is done simply by concatenating them and replacing the `_`s with spaces -- this results in the normalized text. As we saw earlier, the BERT tokenizer removes repeating spaces, so its tokenization is not reversible. + +## Algorithm overview + +In the following sections, we'll dive into the three main subword tokenization algorithms: BPE (used by GPT-2 and others), WordPiece (used for example by BERT), and Unigram (used by T5 and others). Before we get started, here's a quick overview of how they each work. Don't hesitate to come back to this table after reading each of the next sections if it doesn't make sense to you yet. + + +Model | BPE | WordPiece | Unigram +:----:|:---:|:---------:|:------: +Training | Starts from a small vocabulary and learns rules to merge tokens | Starts from a small vocabulary and learns rules to merge tokens | Starts from a large vocabulary and learns rules to remove tokens +Training step | Merges the tokens corresponding to the most common pair | Merges the tokens corresponding to the pair with the best score based on the frequency of the pair, privileging pairs where each individual token is less frequent | Removes all the tokens in the vocabulary that will minimize the loss computed on the whole corpus +Learns | Merge rules and a vocabulary | Just a vocabulary | A vocabulary with a score for each token +Encoding | Splits a word into characters and applies the merges learned during training | Finds the longest subword starting from the beginning that is in the vocabulary, then does the same for the rest of the word | Finds the most likely split into tokens, using the scores learned during training + +Now let's dive into BPE! diff --git a/chapters/vi/chapter6/5.md b/chapters/vi/chapter6/5.md new file mode 100644 index 000000000..a9f070b0e --- /dev/null +++ b/chapters/vi/chapter6/5.md @@ -0,0 +1,360 @@ +# Byte-Pair Encoding tokenization + + + +Byte-Pair Encoding (BPE) was initially developed as an algorithm to compress texts, and then used by OpenAI for tokenization when pretraining the GPT model. It's used by a lot of Transformer models, including GPT, GPT-2, RoBERTa, BART, and DeBERTa. + + + + + +💡 This section covers BPE in depth, going as far as showing a full implementation. You can skip to the end if you just want a general overview of the tokenization algorithm. + + + +## Training algorithm + +BPE training starts by computing the unique set of words used in the corpus (after the normalization and pre-tokenization steps are completed), then building the vocabulary by taking all the symbols used to write those words. As a very simple example, let's say our corpus uses these five words: + +``` +"hug", "pug", "pun", "bun", "hugs" +``` + +The base vocabulary will then be `["b", "g", "h", "n", "p", "s", "u"]`. For real-world cases, that base vocabulary will contain all the ASCII characters, at the very least, and probably some Unicode characters as well. If an example you are tokenizing uses a character that is not in the training corpus, that character will be converted to the unknown token. That's one reason why lots of NLP models are very bad at analyzing content with emojis, for instance. + + + +The GPT-2 and RoBERTa tokenizers (which are pretty similar) have a clever way to deal with this: they don't look at words as being written with Unicode characters, but with bytes. This way the base vocabulary has a small size (256), but every character you can think of will still be included and not end up being converted to the unknown token. This trick is called *byte-level BPE*. + + + +After getting this base vocabulary, we add new tokens until the desired vocabulary size is reached by learning *merges*, which are rules to merge two elements of the existing vocabulary together into a new one. So, at the beginning these merges will create tokens with two characters, and then, as training progresses, longer subwords. + +At any step during the tokenizer training, the BPE algorithm will search for the most frequent pair of existing tokens (by "pair," here we mean two consecutive tokens in a word). That most frequent pair is the one that will be merged, and we rinse and repeat for the next step. + +Going back to our previous example, let's assume the words had the following frequencies: + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +meaning `"hug"` was present 10 times in the corpus, `"pug"` 5 times, `"pun"` 12 times, `"bun"` 4 times, and `"hugs"` 5 times. We start the training by splitting each word into characters (the ones that form our initial vocabulary) so we can see each word as a list of tokens: + +``` +("h" "u" "g", 10), ("p" "u" "g", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "u" "g" "s", 5) +``` + +Then we look at pairs. The pair `("h", "u")` is present in the words `"hug"` and `"hugs"`, so 15 times total in the corpus. It's not the most frequent pair, though: that honor belongs to `("u", "g")`, which is present in `"hug"`, `"pug"`, and `"hugs"`, for a grand total of 20 times in the vocabulary. + +Thus, the first merge rule learned by the tokenizer is `("u", "g") -> "ug"`, which means that `"ug"` will be added to the vocabulary, and the pair should be merged in all the words of the corpus. At the end of this stage, the vocabulary and corpus look like this: + +``` +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) +``` + +Now we have some pairs that result in a token longer than two characters: the pair `("h", "ug")`, for instance (present 15 times in the corpus). The most frequent pair at this stage is `("u", "n")`, however, present 16 times in the corpus, so the second merge rule learned is `("u", "n") -> "un"`. Adding that to the vocabulary and merging all existing occurrences leads us to: + +``` +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) +``` + +Now the most frequent pair is `("h", "ug")`, so we learn the merge rule `("h", "ug") -> "hug"`, which gives us our first three-letter token. After the merge, the corpus looks like this: + +``` +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) +``` + +And we continue like this until we reach the desired vocabulary size. + + + +✏️ **Now your turn!** What do you think the next merge rule will be? + + + +## Tokenization algorithm + +Tokenization follows the training process closely, in the sense that new inputs are tokenized by applying the following steps: + +1. Normalization +2. Pre-tokenization +3. Splitting the words into individual characters +4. Applying the merge rules learned in order on those splits + +Let's take the example we used during training, with the three merge rules learned: + +``` +("u", "g") -> "ug" +("u", "n") -> "un" +("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. + + + +✏️ **Now your turn!** How do you think the word `"unhug"` will be tokenized? + + + +## Implementing BPE + +Now let's take a look at an implementation of the BPE algorithm. This won't be an optimized version you can actually use on a big corpus; we just want to show you the code so you can understand the algorithm a little bit better. + +First we need a corpus, so let's create a simple one with a few sentences: + +```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.", +] +``` + +Next, we need to pre-tokenize that corpus into words. Since we are replicating a BPE tokenizer (like GPT-2), we will use the `gpt2` tokenizer for the pre-tokenization: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("gpt2") +``` + +Then we compute the frequencies of each word in the corpus as we do the pre-tokenization: + +```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}) +``` + +The next step is to compute the base vocabulary, formed by all the characters used in the 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', 'Ġ'] +``` + +We also add the special tokens used by the model at the beginning of that vocabulary. In the case of GPT-2, the only special token is `"<|endoftext|>"`: + +```python +vocab = ["<|endoftext|>"] + alphabet.copy() +``` + +We now need to split each word into individual characters, to be able to start training: + +```python +splits = {word: [c for c in word] for word in word_freqs.keys()} +``` + +Now that we are ready for training, let's write a function that computes the frequency of each pair. We'll need to use this at each step of the training: + +```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 +``` + +Let's have a look at a part of this dictionary after the initial splits: + +```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 +``` + +Now, finding the most frequent pair only takes a quick loop: + +```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 +``` + +So the first merge to learn is `('Ġ', 't') -> 'Ġt'`, and we add `'Ġt'` to the vocabulary: + +```python +merges = {("Ġ", "t"): "Ġt"} +vocab.append("Ġt") +``` + +To continue, we need to apply that merge in our `splits` dictionary. Let's write another function for this: + +```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 +``` + +And we can have a look at the result of the first merge: + +```py +splits = merge_pair("Ġ", "t", splits) +print(splits["Ġtrained"]) +``` + +```python out +['Ġt', 'r', 'a', 'i', 'n', 'e', 'd'] +``` + +Now we have everything we need to loop until we have learned all the merges we want. Let's aim for a vocab size of 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]) +``` + +As a result, we've learned 19 merge rules (the initial vocabulary had a size of 31 -- 30 characters in the alphabet, plus the special token): + +```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'] +``` + + + +💡 Using `train_new_from_iterator()` on the same corpus won't result in the exact same vocabulary. This is because when there is a choice of the most frequent pair, we selected the first one encountered, while the 🤗 Tokenizers library selects the first one based on its inner IDs. + + + +To tokenize a new text, we pre-tokenize it, split it, then apply all the merge rules learned: + +```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, []) +``` + +We can try this on any text composed of characters in the alphabet: + +```py +tokenize("This is not a token.") +``` + +```python out +['This', 'Ġis', 'Ġ', 'n', 'o', 't', 'Ġa', 'Ġtoken', '.'] +``` + + + +⚠️ Our implementation will throw an error if there is an unknown character since we didn't do anything to handle them. GPT-2 doesn't actually have an unknown token (it's impossible to get an unknown character when using byte-level BPE), but this could happen here because we did not include all the possible bytes in the initial vocabulary. This aspect of BPE is beyond the scope of this section, so we've left the details out. + + + +That's it for the BPE algorithm! Next, we'll have a look at WordPiece. \ No newline at end of file diff --git a/chapters/vi/chapter6/6.md b/chapters/vi/chapter6/6.md new file mode 100644 index 000000000..d4152cd5e --- /dev/null +++ b/chapters/vi/chapter6/6.md @@ -0,0 +1,374 @@ +# WordPiece tokenization + + + +WordPiece is the tokenization algorithm Google developed to pretrain BERT. It has since been reused in quite a few Transformer models based on BERT, such as DistilBERT, MobileBERT, Funnel Transformers, and MPNET. It's very similar to BPE in terms of the training, but the actual tokenization is done differently. + + + + + +💡 This section covers WordPiece in depth, going as far as showing a full implementation. You can skip to the end if you just want a general overview of the tokenization algorithm. + + + +## Training algorithm + + + +⚠️ Google never open-sourced its implementation of the training algorithm of WordPiece, so what follows is our best guess based on the published literature. It may not be 100% accurate. + + + +Like BPE, WordPiece starts from a small vocabulary including the special tokens used by the model and the initial alphabet. Since it identifies subwords by adding a prefix (like `##` for BERT), each word is initially split by adding that prefix to all the characters inside the word. So, for instance, `"word"` gets split like this: + +``` +w ##o ##r ##d +``` + +Thus, the initial alphabet contains all the characters present at the beginning of a word and the characters present inside a word preceded by the WordPiece prefix. + +Then, again like BPE, WordPiece learns merge rules. The main difference is the way the pair to be merged is selected. Instead of selecting the most frequent pair, WordPiece computes a score for each pair, using the following formula: + +$$\mathrm{score} = (\mathrm{freq\_of\_pair}) / (\mathrm{freq\_of\_first\_element} \times \mathrm{freq\_of\_second\_element})$$ + +By dividing the frequency of the pair by the product of the frequencies of each of its parts, the algorithm prioritizes the merging of pairs where the individual parts are less frequent in the vocabulary. For instance, it won't necessarily merge `("un", "##able")` even if that pair occurs very frequently in the vocabulary, because the two pairs `"un"` and `"##able"` will likely each appear in a lot of other words and have a high frequency. In contrast, a pair like `("hu", "##gging")` will probably be merged faster (assuming the word "hugging" appears often in the vocabulary) since `"hu"` and `"##gging"` are likely to be less frequent individually. + +Let's look at the same vocabulary we used in the BPE training example: + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +The splits here will be: + +``` +("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##g" "##s", 5) +``` + +so the initial vocabulary will be `["b", "h", "p", "##g", "##n", "##s", "##u"]` (if we forget about special tokens for now). The most frequent pair is `("##u", "##g")` (present 20 times), but the individual frequency of `"##u"` is very high, so its score is not the highest (it's 1 / 36). All pairs with a `"##u"` actually have that same score (1 / 36), so the best score goes to the pair `("##g", "##s")` -- the only one without a `"##u"` -- at 1 / 20, and the first merge learned is `("##g", "##s") -> ("##gs")`. + +Note that when we merge, we remove the `##` between the two tokens, so we add `"##gs"` to the vocabulary and apply the merge in the words of the 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) +``` + +At this point, `"##u"` is in all the possible pairs, so they all end up with the same score. Let's say that in this case, the first pair is merged, so `("h", "##u") -> "hu"`. This takes us to: + +``` +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) +``` + +Then the next best score is shared by `("hu", "##g")` and `("hu", "##gs")` (with 1/15, compared to 1/21 for all the other pairs), so the first pair with the biggest score is merged: + +``` +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) +``` + +and we continue like this until we reach the desired vocabulary size. + + + +✏️ **Now your turn!** What will the next merge rule be? + + + +## Tokenization algorithm + +Tokenization differs in WordPiece and BPE in that WordPiece only saves the final vocabulary, not the merge rules learned. Starting from the word to tokenize, WordPiece finds the longest subword that is in the vocabulary, then splits on it. For instance, if we use the vocabulary learned in the example above, for the word `"hugs"` the longest subword starting from the beginning that is inside the vocabulary is `"hug"`, so we split there and get `["hug", "##s"]`. We then continue with `"##s"`, which is in the vocabulary, so the tokenization of `"hugs"` is `["hug", "##s"]`. + +With BPE, we would have applied the merges learned in order and tokenized this as `["hu", "##gs"]`, so the encoding is different. + +As another example, let's see how the word `"bugs"` would be tokenized. `"b"` is the longest subword starting at the beginning of the word that is in the vocabulary, so we split there and get `["b", "##ugs"]`. Then `"##u"` is the longest subword starting at the beginning of `"##ugs"` that is in the vocabulary, so we split there and get `["b", "##u, "##gs"]`. Finally, `"##gs"` is in the vocabulary, so this last list is the tokenization of `"bugs"`. + +When the tokenization gets to a stage where it's not possible to find a subword in the vocabulary, the whole word is tokenized as unknown -- so, for instance, `"mug"` would be tokenized as `["[UNK]"]`, as would `"bum"` (even if we can begin with `"b"` and `"##u"`, `"##m"` is not the vocabulary, and the resulting tokenization will just be `["[UNK]"]`, not `["b", "##u", "[UNK]"]`). This is another difference from BPE, which would only classify the individual characters not in the vocabulary as unknown. + + + +✏️ **Now your turn!** How will the word `"pugs"` be tokenized? + + + +## Implementing WordPiece + +Now let's take a look at an implementation of the WordPiece algorithm. Like with BPE, this is just pedagogical, and you won't able to use this on a big corpus. + +We will use the same corpus as in the BPE example: + +```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.", +] +``` + +First, we need to pre-tokenize the corpus into words. Since we are replicating a WordPiece tokenizer (like BERT), we will use the `bert-base-cased` tokenizer for the pre-tokenization: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +``` + +Then we compute the frequencies of each word in the corpus as we do the pre-tokenization: + +```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}) +``` + +As we saw before, the alphabet is the unique set composed of all the first letters of words, and all the other letters that appear in words prefixed by `##`: + +```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'] +``` + +We also add the special tokens used by the model at the beginning of that vocabulary. In the case of BERT, it's the list `["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]`: + +```python +vocab = ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"] + alphabet.copy() +``` + +Next we need to split each word, with all the letters that are not the first prefixed by `##`: + +```python +splits = { + word: [c if i == 0 else f"##{c}" for i, c in enumerate(word)] + for word in word_freqs.keys() +} +``` + +Now that we are ready for training, let's write a function that computes the score of each pair. We'll need to use this at each step of the training: + +```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 +``` + +Let's have a look at a part of this dictionary after the initial splits: + +```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 +``` + +Now, finding the pair with the best score only takes a quick loop: + +```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 +``` + +So the first merge to learn is `('a', '##b') -> 'ab'`, and we add `'ab'` to the vocabulary: + +```python +vocab.append("ab") +``` + +To continue, we need to apply that merge in our `splits` dictionary. Let's write another function for this: + +```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 +``` + +And we can have a look at the result of the first merge: + +```py +splits = merge_pair("a", "##b", splits) +splits["about"] +``` + +```python out +['ab', '##o', '##u', '##t'] +``` + +Now we have everything we need to loop until we have learned all the merges we want. Let's aim for a vocab size of 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) +``` + +We can then look at the generated vocabulary: + +```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'] +``` + +As we can see, compared to BPE, this tokenizer learns parts of words as tokens a bit faster. + + + +💡 Using `train_new_from_iterator()` on the same corpus won't result in the exact same vocabulary. This is because the 🤗 Tokenizers library does not implement WordPiece for the training (since we are not completely sure of its internals), but uses BPE instead. + + + +To tokenize a new text, we pre-tokenize it, split it, then apply the tokenization algorithm on each word. That is, we look for the biggest subword starting at the beginning of the first word and split it, then we repeat the process on the second part, and so on for the rest of that word and the following words in the text: + +```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 +``` + +Let's test it on one word that's in the vocabulary, and another that isn't: + +```python +print(encode_word("Hugging")) +print(encode_word("HOgging")) +``` + +```python out +['Hugg', '##i', '##n', '##g'] +['[UNK]'] +``` + +Now, let's write a function that tokenizes a text: + +```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, []) +``` + +We can try it on any text: + +```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]'] +``` + +That's it for the WordPiece algorithm! Now let's take a look at Unigram. diff --git a/chapters/vi/chapter6/7.md b/chapters/vi/chapter6/7.md new file mode 100644 index 000000000..e23d6f473 --- /dev/null +++ b/chapters/vi/chapter6/7.md @@ -0,0 +1,381 @@ +# Unigram tokenization + + + +The Unigram algorithm is often used in SentencePiece, which is the tokenization algorithm used by models like AlBERT, T5, mBART, Big Bird, and XLNet. + + + + + +💡 This section covers Unigram in depth, going as far as showing a full implementation. You can skip to the end if you just want a general overview of the tokenization algorithm. + + + +## Training algorithm + +Compared to BPE and WordPiece, Unigram works in the other direction: it starts from a big vocabulary and removes tokens from it until it reaches the desired vocabulary size. There are several options to use to build that base vocabulary: we can take the most common substrings in pre-tokenized words, for instance, or apply BPE on the initial corpus with a large vocabulary size. + +At each step of the training, the Unigram algorithm computes a loss over the corpus given the current vocabulary. Then, for each symbol in the vocabulary, the algorithm computes how much the overall loss would increase if the symbol was removed, and looks for the symbols that would increase it the least. Those symbols have a lower effect on the overall loss over the corpus, so in a sense they are "less needed" and are the best candidates for removal. + +This is all a very costly operation, so we don't just remove the single symbol associated with the lowest loss increase, but the \\(p\\) (\\(p\\) being a hyperparameter you can control, usually 10 or 20) percent of the symbols associated with the lowest loss increase. This process is then repeated until the vocabulary has reached the desired size. + +Note that we never remove the base characters, to make sure any word can be tokenized. + +Now, this is still a bit vague: the main part of the algorithm is to compute a loss over the corpus and see how it changes when we remove some tokens from the vocabulary, but we haven't explained how to do this yet. This step relies on the tokenization algorithm of a Unigram model, so we'll dive into this next. + +We'll reuse the corpus from the previous examples: + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +and for this example, we will take all strict substrings for the initial vocabulary : + +``` +["h", "u", "g", "hu", "ug", "p", "pu", "n", "un", "b", "bu", "s", "hug", "gs", "ugs"] +``` + +## Tokenization algorithm + +A Unigram model is a type of language model that considers each token to be independent of the tokens before it. It's the simplest language model, in the sense that the probability of token X given the previous context is just the probability of token X. So, if we used a Unigram language model to generate text, we would always predict the most common token. + +The probability of a given token is its frequency (the number of times we find it) in the original corpus, divided by the sum of all frequencies of all tokens in the vocabulary (to make sure the probabilities sum up to 1). For instance, `"ug"` is present in `"hug"`, `"pug"`, and `"hugs"`, so it has a frequency of 20 in our corpus. + +Here are the frequencies of all the possible subwords in the vocabulary: + +``` +("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) +``` + +So, the sum of all frequencies is 210, and the probability of the subword `"ug"` is thus 20/210. + + + +✏️ **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, to tokenize a given word, we look at all the possible segmentations into tokens and compute the probability of each according to the Unigram model. Since all tokens are considered independent, this probability is just the product of the probability of each token. For instance, the tokenization `["p", "u", "g"]` of `"pug"` has the probability: + +$$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$$ + +Comparatively, the tokenization `["pu", "g"]` has the probability: + +$$P([``pu", ``g"]) = P(``pu") \times P(``g") = \frac{5}{210} \times \frac{20}{210} = 0.0022676$$ + +so that one is way more likely. In general, tokenizations with the least tokens possible will have the highest probability (because of that division by 210 repeated for each token), which corresponds to what we want intuitively: to split a word into the least number of tokens possible. + +The tokenization of a word with the Unigram model is then the tokenization with the highest probability. In the example of `"pug"`, here are the probabilities we would get for each possible segmentation: + +``` +["p", "u", "g"] : 0.000389 +["p", "ug"] : 0.0022676 +["pu", "g"] : 0.0022676 +``` + +So, `"pug"` would be tokenized as `["p", "ug"]` or `["pu", "g"]`, depending on which of those segmentations is encountered first (note that in a larger corpus, equality cases like this will be rare). + +In this case, it was easy to find all the possible segmentations and compute their probabilities, but in general it's going to be a bit harder. There is a classic algorithm used for this, called the *Viterbi algorithm*. Essentially, we can build a graph to detect the possible segmentations of a given word by saying there is a branch from character _a_ to character _b_ if the subword from _a_ to _b_ is in the vocabulary, and attribute to that branch the probability of the subword. + +To find the path in that graph that is going to have the best score the Viterbi algorithm determines, for each position in the word, the segmentation with the best score that ends at that position. Since we go from the beginning to the end, that best score can be found by looping through all subwords ending at the current position and then using the best tokenization score from the position this subword begins at. Then, we just have to unroll the path taken to arrive at the end. + +Let's take a look at an example using our vocabulary and the word `"unhug"`. For each position, the subwords with the best scores ending there are the following: + +``` +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) +``` + +Thus `"unhug"` would be tokenized as `["un", "hug"]`. + + + +✏️ **Now your turn!** Determine the tokenization of the word `"huggun"`, and its score. + + + +## Back to training + +Now that we have seen how the tokenization works, we can dive a little more deeply into the loss used during training. At any given stage, this loss is computed by tokenizing every word in the corpus, using the current vocabulary and the Unigram model determined by the frequencies of each token in the corpus (as seen before). + +Each word in the corpus has a score, and the loss is the negative log likelihood of those scores -- that is, the sum for all the words in the corpus of all the `-log(P(word))`. + +Let's go back to our example with the following corpus: + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +The tokenization of each word with their respective scores is: + +``` +"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) +``` + +So the loss is: + +``` +10 * (-log(0.071428)) + 5 * (-log(0.007710)) + 12 * (-log(0.006168)) + 4 * (-log(0.001451)) + 5 * (-log(0.001701)) = 169.8 +``` + +Now we need to compute how removing each token affects the loss. This is rather tedious, so we'll just do it for two tokens here and save the whole process for when we have code to help us. In this (very) particular case, we had two equivalent tokenizations of all the words: as we saw earlier, for example, `"pug"` could be tokenized `["p", "ug"]` with the same score. Thus, removing the `"pu"` token from the vocabulary will give the exact same loss. + +On the other hand, removing `"hug"` will make the loss worse, because the tokenization of `"hug"` and `"hugs"` will become: + +``` +"hug": ["hu", "g"] (score 0.006802) +"hugs": ["hu", "gs"] (score 0.001701) +``` + +These changes will cause the loss to rise by: + +``` +- 10 * (-log(0.071428)) + 10 * (-log(0.006802)) = 23.5 +``` + +Therefore, the token `"pu"` will probably be removed from the vocabulary, but not `"hug"`. + +## Implementing Unigram + +Now let's implement everything we've seen so far in code. Like with BPE and WordPiece, this is not an efficient implementation of the Unigram algorithm (quite the opposite), but it should help you understand it a bit better. + +We will use the same corpus as before as an example: + +```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.", +] +``` + +This time, we will use `xlnet-base-cased` as our model: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("xlnet-base-cased") +``` + +Like for BPE and WordPiece, we begin by counting the number of occurrences of each word in the 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 +``` + +Then, we need to initialize our vocabulary to something larger than the vocab size we will want at the end. We have to include all the basic characters (otherwise we won't be able to tokenize every word), but for the bigger substrings we'll only keep the most common ones, so we sort them by frequency: + +```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)] +``` + +We group the characters with the best subwords to arrive at an initial vocabulary of size 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 uses a more efficient algorithm called Enhanced Suffix Array (ESA) to create the initial vocabulary. + + + +Next, we compute the sum of all frequencies, to convert the frequencies into probabilities. For our model we will store the logarithms of the probabilities, because it's more numerically stable to add logarithms than to multiply small numbers, and this will simplify the computation of the loss of the model: + +```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()} +``` + +Now the main function is the one that tokenizes words using the Viterbi algorithm. As we saw before, that algorithm computes the best segmentation of each substring of the word, which we will store in a variable named `best_segmentations`. We will store one dictionary per position in the word (from 0 to its total length), with two keys: the index of the start of the last token in the best segmentation, and the score of the best segmentation. With the index of the start of the last token, we will be able to retrieve the full segmentation once the list is completely populated. + +Populating the list is done with just two loops: the main loop goes over each start position, and the second loop tries all substrings beginning at that start position. If the substring is in the vocabulary, we have a new segmentation of the word up until that end position, which we compare to what is in `best_segmentations`. + +Once the main loop is finished, we just start from the end and hop from one start position to the next, recording the tokens as we go, until we reach the start of the word: + +```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 +``` + +We can already try our initial model on some words: + +```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) +``` + +Now it's easy to compute the loss of the model on the 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 +``` + +We can check it works on the model we have: + +```python +compute_loss(model) +``` + +```python out +413.10377642940875 +``` + +Computing the scores for each token is not very hard either; we just have to compute the loss for the models obtained by deleting each 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 +``` + +We can try it on a given token: + +```python +scores = compute_scores(model) +print(scores["ll"]) +print(scores["his"]) +``` + +Since `"ll"` is used in the tokenization of `"Hopefully"`, and removing it will probably make us use the token `"l"` twice instead, we expect it will have a positive loss. `"his"` is only used inside the word `"This"`, which is tokenized as itself, so we expect it to have a zero loss. Here are the results: + +```python out +6.376412403623874 +0.0 +``` + + + +💡 This approach is very inefficient, so SentencePiece uses an approximation of the loss of the model without token X: instead of starting from scratch, it just replaces token X by its segmentation in the vocabulary that is left. This way, all the scores can be computed at once at the same time as the model loss. + + + +With all of this in place, the last thing we need to do is add the special tokens used by the model to the vocabulary, then loop until we have pruned enough tokens from the vocabulary to reach our desired size: + +```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()} +``` + +Then, to tokenize some text, we just need to apply the pre-tokenization and then use our `encode_word()` function: + +```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', '.'] +``` + +That's it for Unigram! Hopefully by now you're feeling like an expert in all things tokenizer. In the next section, we will delve into the building blocks of the 🤗 Tokenizers library, and show you how you can use them to build your own tokenizer. diff --git a/chapters/vi/chapter6/8.md b/chapters/vi/chapter6/8.md new file mode 100644 index 000000000..9e54d568a --- /dev/null +++ b/chapters/vi/chapter6/8.md @@ -0,0 +1,565 @@ +# Xây dựng từng khối tokenizer + + + +As we've seen in the previous sections, tokenization comprises several steps: + +- Normalization (any cleanup of the text that is deemed necessary, such as removing spaces or accents, Unicode normalization, etc.) +- Pre-tokenization (splitting the input into words) +- Running the input through the model (using the pre-tokenized words to produce a sequence of tokens) +- Post-processing (adding the special tokens of the tokenizer, generating the attention mask and token type IDs) + +As a reminder, here's another look at the overall process: + +
+The tokenization pipeline. + +
+ +The 🤗 Tokenizers library has been built to provide several options for each of those steps, which you can mix and match together. In this section we'll see how we can build a tokenizer from scratch, as opposed to training a new tokenizer from an old one as we did in [section 2](/course/chapter6/2). You'll then be able to build any kind of tokenizer you can think of! + + + +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). + +## Acquiring a corpus + +To train our new tokenizer, we will use a small corpus of text (so the examples run fast). The steps for acquiring the corpus are similar to the ones we took at the [beginning of this chapter](/course/chapter6/2), but this time we'll use the [WikiText-2](https://huggingface.co/datasets/wikitext) dataset: + +```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"] +``` + +The function `get_training_corpus()` is a generator that will yield batches of 1,000 texts, which we will use to train the tokenizer. + +🤗 Tokenizers can also be trained on text files directly. Here's how we can generate a text file containing all the texts/inputs from WikiText-2 that we can use locally: + +```python +with open("wikitext-2.txt", "w", encoding="utf-8") as f: + for i in range(len(dataset)): + f.write(dataset[i]["text"] + "\n") +``` + +Next we'll show you how to build your own BERT, GPT-2, and XLNet tokenizers, block by block. That will give us an example of each of the three main tokenization algorithms: WordPiece, BPE, and Unigram. Let's start with BERT! + +## Building a WordPiece tokenizer from scratch + +To build a tokenizer with the 🤗 Tokenizers library, we start by instantiating a `Tokenizer` object with a `model`, then set its `normalizer`, `pre_tokenizer`, `post_processor`, and `decoder` attributes to the values we want. + +For this example, we'll create a `Tokenizer` with a WordPiece model: + +```python +from tokenizers import ( + decoders, + models, + normalizers, + pre_tokenizers, + processors, + trainers, + Tokenizer, +) + +tokenizer = Tokenizer(models.WordPiece(unk_token="[UNK]")) +``` + +We have to specify the `unk_token` so the model knows what to return when it encounters characters it hasn't seen before. Other arguments we can set here include the `vocab` of our model (we're going to train the model, so we don't need to set this) and `max_input_chars_per_word`, which specifies a maximum length for each word (words longer than the value passed will be split). + +The first step of tokenization is normalization, so let's begin with that. Since BERT is widely used, there is a `BertNormalizer` with the classic options we can set for BERT: `lowercase` and `strip_accents`, which are self-explanatory; `clean_text` to remove all control characters and replace repeating spaces with a single one; and `handle_chinese_chars`, which places spaces around Chinese characters. To replicate the `bert-base-uncased` tokenizer, we can just set this normalizer: + +```python +tokenizer.normalizer = normalizers.BertNormalizer(lowercase=True) +``` + +Generally speaking, however, when building a new tokenizer you won't have access to such a handy normalizer already implemented in the 🤗 Tokenizers library -- so let's see how to create the BERT normalizer by hand. The library provides a `Lowercase` normalizer and a `StripAccents` normalizer, and you can compose several normalizers using a `Sequence`: + +```python +tokenizer.normalizer = normalizers.Sequence( + [normalizers.NFD(), normalizers.Lowercase(), normalizers.StripAccents()] +) +``` + +We're also using an `NFD` Unicode normalizer, as otherwise the `StripAccents` normalizer won't properly recognize the accented characters and thus won't strip them out. + +As we've seen before, we can use the `normalize_str()` method of the `normalizer` to check out the effects it has on a given text: + +```python +print(tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) +``` + +```python out +hello how are u? +``` + + + +**To go further** If you test the two versions of the previous normalizers on a string containing the unicode character `u"\u0085"` you will surely notice that these two normalizers are not exactly equivalent. +To not over-complicate the version with `normalizers.Sequence` too much , we haven't included the Regex replacements that the `BertNormalizer` requires when the `clean_text` argument is set to `True` - which is the default behavior. But don't worry: it is possible to get exactly the same normalization without using the handy `BertNormalizer` by adding two `normalizers.Replace`'s to the normalizers sequence. + + + +Next is the pre-tokenization step. Again, there is a prebuilt `BertPreTokenizer` that we can use: + +```python +tokenizer.pre_tokenizer = pre_tokenizers.BertPreTokenizer() +``` + +Or we can build it from scratch: + +```python +tokenizer.pre_tokenizer = pre_tokenizers.Whitespace() +``` + +Note that the `Whitespace` pre-tokenizer splits on whitespace and all characters that are not letters, digits, or the underscore character, so it technically splits on whitespace and punctuation: + +```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))] +``` + +If you only want to split on whitespace, you should use the `WhitespaceSplit` pre-tokenizer instead: + +```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))] +``` + +Like with normalizers, you can use a `Sequence` to compose several pre-tokenizers: + +```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))] +``` + +The next step in the tokenization pipeline is running the inputs through the model. We already specified our model in the initialization, but we still need to train it, which will require a `WordPieceTrainer`. The main thing to remember when instantiating a trainer in 🤗 Tokenizers is that you need to pass it all the special tokens you intend to use -- otherwise it won't add them to the vocabulary, since they are not in the training corpus: + +```python +special_tokens = ["[UNK]", "[PAD]", "[CLS]", "[SEP]", "[MASK]"] +trainer = trainers.WordPieceTrainer(vocab_size=25000, special_tokens=special_tokens) +``` + +As well as specifying the `vocab_size` and `special_tokens`, we can set the `min_frequency` (the number of times a token must appear to be included in the vocabulary) or change the `continuing_subword_prefix` (if we want to use something different from `##`). + +To train our model using the iterator we defined earlier, we just have to execute this command: + +```python +tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) +``` + +We can also use text files to train our tokenizer, which would look like this (we reinitialize the model with an empty `WordPiece` beforehand): + +```python +tokenizer.model = models.WordPiece(unk_token="[UNK]") +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +In both cases, we can then test the tokenizer on a text by calling the `encode()` method: + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.'] +``` + +The `encoding` obtained is an `Encoding`, which contains all the necessary outputs of the tokenizer in its various attributes: `ids`, `type_ids`, `tokens`, `offsets`, `attention_mask`, `special_tokens_mask`, and `overflowing`. + +The last step in the tokenization pipeline is post-processing. We need to add the `[CLS]` token at the beginning and the `[SEP]` token at the end (or after each sentence, if we have a pair of sentences). We will use a `TemplateProcessor` for this, but first we need to know the IDs of the `[CLS]` and `[SEP]` tokens in the vocabulary: + +```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) +``` + +To write the template for the `TemplateProcessor`, we have to specify how to treat a single sentence and a pair of sentences. For both, we write the special tokens we want to use; the first (or single) sentence is represented by `$A`, while the second sentence (if encoding a pair) is represented by `$B`. For each of these (special tokens and sentences), we also specify the corresponding token type ID after a colon. + +The classic BERT template is thus defined as follows: + +```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)], +) +``` + +Note that we need to pass along the IDs of the special tokens, so the tokenizer can properly convert them to their IDs. + +Once this is added, going back to our previous example will give: + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.', '[SEP]'] +``` + +And on a pair of sentences, we get the proper result: + +```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] +``` + +We've almost finished building this tokenizer from scratch -- the last step is to include a decoder: + +```python +tokenizer.decoder = decoders.WordPiece(prefix="##") +``` + +Let's test it on our previous `encoding`: + +```python +tokenizer.decode(encoding.ids) +``` + +```python out +"let's test this tokenizer... on a pair of sentences." +``` + +Great! We can save our tokenizer in a single JSON file like this: + +```python +tokenizer.save("tokenizer.json") +``` + +We can then reload that file in a `Tokenizer` object with the `from_file()` method: + +```python +new_tokenizer = Tokenizer.from_file("tokenizer.json") +``` + +To use this tokenizer in 🤗 Transformers, we have to wrap it in a `PreTrainedTokenizerFast`. We can either use the generic class or, if our tokenizer corresponds to an existing model, use that class (here, `BertTokenizerFast`). If you apply this lesson to build a brand new tokenizer, you will have to use the first option. + +To wrap the tokenizer in a `PreTrainedTokenizerFast`, we can either pass the tokenizer we built as a `tokenizer_object` or pass the tokenizer file we saved as `tokenizer_file`. The key thing to remember is that we have to manually set all the special tokens, since that class can't infer from the `tokenizer` object which token is the mask token, the `[CLS]` token, 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]", +) +``` + +If you are using a specific tokenizer class (like `BertTokenizerFast`), you will only need to specify the special tokens that are different from the default ones (here, none): + +```python +from transformers import BertTokenizerFast + +wrapped_tokenizer = BertTokenizerFast(tokenizer_object=tokenizer) +``` + +You can then use this tokenizer like any other 🤗 Transformers tokenizer. You can save it with the `save_pretrained()` method, or upload it to the Hub with the `push_to_hub()` method. + +Now that we've seen how to build a WordPiece tokenizer, let's do the same for a BPE tokenizer. We'll go a bit faster since you know all the steps, and only highlight the differences. + +## Building a BPE tokenizer from scratch + +Let's now build a GPT-2 tokenizer. Like for the BERT tokenizer, we start by initializing a `Tokenizer` with a BPE model: + +```python +tokenizer = Tokenizer(models.BPE()) +``` + +Also like for BERT, we could initialize this model with a vocabulary if we had one (we would need to pass the `vocab` and `merges` in this case), but since we will train from scratch, we don't need to do that. We also don't need to specify an `unk_token` because GPT-2 uses byte-level BPE, which doesn't require it. + +GPT-2 does not use a normalizer, so we skip that step and go directly to the pre-tokenization: + +```python +tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False) +``` + +The option we added to `ByteLevel` here is to not add a space at the beginning of a sentence (which is the default otherwise). We can have a look at the pre-tokenization of an example text like before: + +```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))] +``` + +Next is the model, which needs training. For GPT-2, the only special token is the end-of-text token: + +```python +trainer = trainers.BpeTrainer(vocab_size=25000, special_tokens=["<|endoftext|>"]) +tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) +``` + +Like with the `WordPieceTrainer`, as well as the `vocab_size` and `special_tokens`, we can specify the `min_frequency` if we want to, or if we have an end-of-word suffix (like ``), we can set it with `end_of_word_suffix`. + +This tokenizer can also be trained on text files: + +```python +tokenizer.model = models.BPE() +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +Let's have a look at the tokenization of a sample text: + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['L', 'et', "'", 's', 'Ġtest', 'Ġthis', 'Ġto', 'ken', 'izer', '.'] +``` + +We apply the byte-level post-processing for the GPT-2 tokenizer as follows: + +```python +tokenizer.post_processor = processors.ByteLevel(trim_offsets=False) +``` + +The `trim_offsets = False` option indicates to the post-processor that we should leave the offsets of tokens that begin with 'Ġ' as they are: this way the start of the offsets will point to the space before the word, not the first character of the word (since the space is technically part of the token). Let's have a look at the result with the text we just encoded, where `'Ġtest'` is the token at index 4: + +```python +sentence = "Let's test this tokenizer." +encoding = tokenizer.encode(sentence) +start, end = encoding.offsets[4] +sentence[start:end] +``` + +```python out +' test' +``` + +Finally, we add a byte-level decoder: + +```python +tokenizer.decoder = decoders.ByteLevel() +``` + +and we can double-check it works properly: + +```python +tokenizer.decode(encoding.ids) +``` + +```python out +"Let's test this tokenizer." +``` + +Great! Now that we're done, we can save the tokenizer like before, and wrap it in a `PreTrainedTokenizerFast` or `GPT2TokenizerFast` if we want to use it in 🤗 Transformers: + +```python +from transformers import PreTrainedTokenizerFast + +wrapped_tokenizer = PreTrainedTokenizerFast( + tokenizer_object=tokenizer, + bos_token="<|endoftext|>", + eos_token="<|endoftext|>", +) +``` + +or: + +```python +from transformers import GPT2TokenizerFast + +wrapped_tokenizer = GPT2TokenizerFast(tokenizer_object=tokenizer) +``` + +As the last example, we'll show you how to build a Unigram tokenizer from scratch. + +## Building a Unigram tokenizer from scratch + +Let's now build an XLNet tokenizer. Like for the previous tokenizers, we start by initializing a `Tokenizer` with a Unigram model: + +```python +tokenizer = Tokenizer(models.Unigram()) +``` + +Again, we could initialize this model with a vocabulary if we had one. + +For the normalization, XLNet uses a few replacements (which come from SentencePiece): + +```python +from tokenizers import Regex + +tokenizer.normalizer = normalizers.Sequence( + [ + normalizers.Replace("``", '"'), + normalizers.Replace("''", '"'), + normalizers.NFKD(), + normalizers.StripAccents(), + normalizers.Replace(Regex(" {2,}"), " "), + ] +) +``` + +This replaces `` and '' with " and any sequence of two or more spaces with a single space, as well as removing the accents in the texts to tokenize. + +The pre-tokenizer to use for any SentencePiece tokenizer is `Metaspace`: + +```python +tokenizer.pre_tokenizer = pre_tokenizers.Metaspace() +``` + +We can have a look at the pre-tokenization of an example text like before: + +```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))] +``` + +Next is the model, which needs training. XLNet has quite a few special tokens: + +```python +special_tokens = ["", "", "", "", "", "", ""] +trainer = trainers.UnigramTrainer( + vocab_size=25000, special_tokens=special_tokens, unk_token="" +) +tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) +``` + +A very important argument not to forget for the `UnigramTrainer` is the `unk_token`. We can also pass along other arguments specific to the Unigram algorithm, such as the `shrinking_factor` for each step where we remove tokens (defaults to 0.75) or the `max_piece_length` to specify the maximum length of a given token (defaults to 16). + +This tokenizer can also be trained on text files: + +```python +tokenizer.model = models.Unigram() +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +Let's have a look at the tokenization of a sample text: + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.'] +``` + +A peculiarity of XLNet is that it puts the `` token at the end of the sentence, with a type ID of 2 (to distinguish it from the other tokens). It's padding on the left, as a result. We can deal with all the special tokens and token type IDs with a template, like for BERT, but first we have to get the IDs of the `` and `` tokens: + +```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 +``` + +The template looks like this: + +```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)], +) +``` + +And we can test it works by encoding a pair of sentences: + +```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] +``` + +Finally, we add a `Metaspace` decoder: + +```python +tokenizer.decoder = decoders.Metaspace() +``` + +and we're done with this tokenizer! We can save the tokenizer like before, and wrap it in a `PreTrainedTokenizerFast` or `XLNetTokenizerFast` if we want to use it in 🤗 Transformers. One thing to note when using `PreTrainedTokenizerFast` is that on top of the special tokens, we need to tell the 🤗 Transformers library to pad on the left: + +```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", +) +``` + +Or alternatively: + +```python +from transformers import XLNetTokenizerFast + +wrapped_tokenizer = XLNetTokenizerFast(tokenizer_object=tokenizer) +``` + +Now that you have seen how the various building blocks are used to build existing tokenizers, you should be able to write any tokenizer you want with the 🤗 Tokenizers library and be able to use it in 🤗 Transformers. diff --git a/chapters/vi/chapter6/9.mdx b/chapters/vi/chapter6/9.mdx new file mode 100644 index 000000000..cc16eeeec --- /dev/null +++ b/chapters/vi/chapter6/9.mdx @@ -0,0 +1,11 @@ +# Tokenizers, kiểm tra nào! + +Chúc mừng bạn đã hoàn thành chương này! + +Sau khi tìm hiểu sâu về tokenizer, bạn nên: + +- Có thể huấn luyện một tokenizer mới bằng cách sử dụng một cái cũ làm mẫu +- Hiểu cách sử dụng hiệu số để ánh xạ vị trí của token với khoảng văn bản ban đầu của chúng +- Biết sự khác biệt giữa BPE, WordPiece và Unigram +- Có thể trộn và kết hợp các khối được cung cấp bởi thư viện 🤗 Tokenizers để xây dựng tokenizer của riêng bạn +- Có thể sử dụng tokenizer đó trong thư viện 🤗 Transformers From bd8e271c16dfe4dd3732e4d6b4b15325aeafb5d0 Mon Sep 17 00:00:00 2001 From: lewtun Date: Fri, 2 Sep 2022 11:55:47 +0200 Subject: [PATCH 28/51] Bump release (#305) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Chinese - Chapter 1 finished * Add zh to the languages field Add zh to the languages field in the build_documentation.yml and build_pr_documentation.yml files * Remove untranslated chapters in _toctree.yml Remove all these sections that haven't been translated yet Remove Chapter 0 from the table of contents since it hasn't been translated yet * Fixed an error in the translation format Fixed an error in the translation format of Chapter 1, Section 3 * Added a small part of the missing content * Fix style * Complete the translation of Chapters 0 and 2 * Fixed some bugs ·Fixed some formatting errors ·Moved Chapters 0 and 2 to Simplified Chinese * Add files via upload Formatting revisions and some translation corrections * run make style to format chapter1 session3 * run make style to format code * run make style to format code * Fix style * Chapter 2 Section 1 Bengali Translation (huggingface#72) (#168) * [TH] Chapter 6 Section 1 and 2 (#171) Co-authored-by: Suteera * [FA] CH1 / P1-2 (#142) * Spanish Chapter 3: sections 1 & 2 (#162) * fix typos in bpe, wordpiece, unigram (#166) * [FR] French Review (#186) * Part 7: Training a causal... fixes (#179) * typo & error mitigation * consistency * Trainer.predict() returns 3 fields * ran make style * [TR] Translated Chapter 1.6 🤗 (#185) * added chapter 1/6 to _toctree.yml * [TR] Translated Chapter 1.6 🤗 * [PT][Chapter 01 - 2.mdx] - issue #51 (#170) * Fix Gradio ToC (#193) * Add Gradio authors and Blocks event (#189) * Update 6.mdx (#188) Correct link to Transformer XL doc * Add translating notes and glossary to Spanish (#192) * Add translating notes and glosary to Spanish * Adding glossary to the toc * add pt 4.3 (#191) * [FR] Visual corrections (#190) * [PT] add chapter 4.4 and 4.5 (#196) * fix invite discord link (#197) * [FA] Second draft of CH2/P1-2 (#139) * added chapter3 in hindi (#198) * [TR] Chapter 3/1 (#165) * [RU] Ch3-1/2/3 (#200) * [PT] add 5.1 and 5.2 (#204) * Add placeholders for audio chapters (#208) * [FA] - Ch3 - P1 and P2 (#199) * [PT] add `end-of-chapter quiz` for chapter 4 (4.6) (#201) Co-authored-by: lewtun * Chapter1: 2.mdx Translated. (#206) * Remove comments from Persian ToC (#210) * Fix CI URL for PRs (#211) * code fragment & english syntax and meaning (#203) * Updated Ch1/1 with Emoji (#214) * Add missing numpy import (#217) * Updata chapter3 * Code format for chapter3 * Updata yml file of chapter3 * Uptata yml file of chapter3 * Fix yml file bug * [ES] translate sections 8.1 and 8.2 (#215) * Fix path to datasets (#216) * [PT] add 5.3 (#218) * fix 4.3 (#223) * Run make style * Fix notebook generation (#227) * Add Gradio nb links * add 5.4 (#226) * add pt wip (#225) * Added Gujarati List. (#221) * Fix quality * Add Gradio nbs links to fr (#228) * Fix ToC tree * Remove audio templates * Fix fr section * Fix fr chapter * Chinese - Chapter 3finished (#219) * add ch7 at _toctree and translate 7.1 (#222) * add 5.5 (#235) * [FR] Review of chapter 7 (#233) * Italian translation - chapter 4 (#230) * Added Thai translation of chapters 3 (#231) * [Ru] Add part 2, chapter 2 (#234) * Update 8.mdx (#237) - Remove Gradio Blocks Party - Add, Where to next? section * Created HI/Chapter1/5.mdx (#232) * Add Spanish chaper3/section4, update toc and glossary (#238) * [RU] Chapter 3 finished (#239) * [PT] add 5.6 and 5.7 (#240) * [EN] Visual corrections (#245) * Translation for 1/4, 1/5 and 1/6. (#247) * add event in PT (#250) * Pin version of black (#252) * Translate ja event (#241) * [PT] add quiz chapter 5 (#243) * Update 5.mdx (#253) inconsistent naming with line 327 * Translation for Traditional Chinese (zh-tw) chapter0 (#251) Co-authored-by: Lewis Tunstall * Translated the whole Chapter 3 to Thai (#255) * Japanese chapter 4 (#244) * Translation of 1/7, 1/8, and 1/9. (#263) * [PT] add chapter 8.1 and 8.2 (#265) * [RU] Chapter 4 (#269) * Add Thai translation for chapter 6.3b to 6.10 (#268) * add 8.3 (#266) * 3.mdx of chapter 01 (#260) Co-authored-by: Lewis Tunstall * Fix typo (#271) * [PT] add chapter 6.1 (#273) * add Japanese chapter7 (#267) * zh-CN - Chapter 4,5finished * replace `load_metric` with `evaluate.load` (#285) * update `load_metric` refs to `evaluate.load` Co-authored-by: lewtun * [GJ] Translation to Gujarati - Ch0 Setup (#287) * [PT] add chapter 6.2 and 6.3 (#279) * Fix formatting * Debug formatting * Debug FR formatting * zh-CN - Chapter 4,5finished (#281) Co-authored-by: Lewis Tunstall * Chapter 01 - Done [PT] #51 (#280) Co-authored-by: Lewis Tunstall * tf_default_data_collator seems to have moved * zh-CN - Chapter 6finished * Revert "Merge branch 'huggingface:main' into main" This reverts commit aebb46e12f9f87a4303f8bb4f0f2cf545eb83b21, reversing changes made to 69187a3789e8d3d2d0de821ebe495f111d1cc73d. * Revert "zh-CN - Chapter 6finished" This reverts commit e69fce28d3a7b35b76c4f768a6cedf295b37d8c9. * zh-CN - Chapter 6finished * fix style * undo bad commit * Chapter5it (#278) * added the italian translation for unit 1 chapter5 Co-authored-by: Leandro von Werra * Vietnamese translation (#293) * Update .github/workflows/build_pr_documentation.yml Co-authored-by: lewtun * Translate JP chapter 8 (#249) * Italian translation - Chapter 8 (#272) * Translation to Vietnamese - chapter 5 (#297) * Add course contributors (#298) * Add CourseFloatingBanner component * DocNotebookDropdown -> CourseFloatingBanner * Italian translation Ch 2/1, 2/2 (#300) * Add contributors (#304) * Add forum button (#306) Co-authored-by: 1375626371 <40328311+1375626371@users.noreply.github.com> Co-authored-by: 1375626371 <1375626371@qq.com> Co-authored-by: Avishek Das Co-authored-by: Suteera Seeha <33692408+meanna@users.noreply.github.com> Co-authored-by: Suteera Co-authored-by: Saeed Choobani Co-authored-by: Fermin Ordaz Co-authored-by: Kerem Turgutlu Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Sebastian Sosa <37946988+CakeCrusher@users.noreply.github.com> Co-authored-by: tanersekmen <56790802+tanersekmen@users.noreply.github.com> Co-authored-by: Victor Costa <54755870+victorescosta@users.noreply.github.com> Co-authored-by: Camille Couturier Co-authored-by: João Gustavo A. Amorim Co-authored-by: Bahram Shamshiri Co-authored-by: Kavya <36916536+robotjellyzone@users.noreply.github.com> Co-authored-by: Batuhan Ayhan Co-authored-by: Pavel <60391448+pdumin@users.noreply.github.com> Co-authored-by: Kambiz Ghoorchian Co-authored-by: Vedant Pandya Co-authored-by: Diego Vargas <91356068+dzarkV@users.noreply.github.com> Co-authored-by: Thomas O'Brien Co-authored-by: Lincoln V Schreiber Co-authored-by: Giorgio Severi Co-authored-by: svv73 <88366711+svv73@users.noreply.github.com> Co-authored-by: Ömer Faruk Özdemir Co-authored-by: Caterina Bonan <97481648+CaterinaBi@users.noreply.github.com> Co-authored-by: Hiromu Hota Co-authored-by: trtd56 <5toda6@gmail.com> Co-authored-by: Mehrdad Nezamdoost Co-authored-by: Wolvz Co-authored-by: a-krirk <56425947+a-krirk@users.noreply.github.com> Co-authored-by: atgctg <105969161+atgctg@users.noreply.github.com> Co-authored-by: Thiago Medeiros Co-authored-by: webbigdata-jp <87654083+webbigdata-jp@users.noreply.github.com> Co-authored-by: Leandro von Werra Co-authored-by: Bhadresh Savani Co-authored-by: Andreas Ehrencrona Co-authored-by: leandro Co-authored-by: Matt Co-authored-by: Nolanogenn <52080100+Nolanogenn@users.noreply.github.com> Co-authored-by: Hồng Hạnh Co-authored-by: Younes Belkada <49240599+younesbelkada@users.noreply.github.com> Co-authored-by: Edoardo Abati <29585319+EdAbati@users.noreply.github.com> Co-authored-by: Mishig Davaadorj Co-authored-by: Acciaro Gennaro Daniele --- README.md | 35 +- chapters/bn/chapter1/1.mdx | 5 + chapters/bn/chapter2/1.mdx | 5 + chapters/de/chapter3/1.mdx | 5 + chapters/de/chapter3/2.mdx | 8 +- chapters/de/chapter3/3.mdx | 4 +- chapters/de/chapter3/3_tf.mdx | 4 +- chapters/de/chapter3/4.mdx | 4 +- chapters/de/chapter3/5.mdx | 5 + chapters/de/chapter3/6.mdx | 5 + chapters/en/chapter1/1.mdx | 5 + chapters/en/chapter1/10.mdx | 5 + chapters/en/chapter1/2.mdx | 5 + chapters/en/chapter1/3.mdx | 4 +- chapters/en/chapter1/4.mdx | 5 + chapters/en/chapter1/5.mdx | 5 + chapters/en/chapter1/6.mdx | 5 + chapters/en/chapter1/7.mdx | 5 + chapters/en/chapter1/8.mdx | 4 +- chapters/en/chapter1/9.mdx | 5 + chapters/en/chapter2/1.mdx | 5 + chapters/en/chapter2/2.mdx | 8 +- chapters/en/chapter2/3.mdx | 8 +- chapters/en/chapter2/4.mdx | 8 +- chapters/en/chapter2/5.mdx | 8 +- chapters/en/chapter2/6.mdx | 8 +- chapters/en/chapter2/7.mdx | 5 + chapters/en/chapter2/8.mdx | 5 + chapters/en/chapter3/1.mdx | 5 + chapters/en/chapter3/2.mdx | 8 +- chapters/en/chapter3/3.mdx | 4 +- chapters/en/chapter3/3_tf.mdx | 4 +- chapters/en/chapter3/4.mdx | 4 +- chapters/en/chapter3/5.mdx | 5 + chapters/en/chapter3/6.mdx | 5 + chapters/en/chapter4/1.mdx | 5 + chapters/en/chapter4/2.mdx | 8 +- chapters/en/chapter4/3.mdx | 8 +- chapters/en/chapter4/4.mdx | 5 + chapters/en/chapter4/5.mdx | 5 + chapters/en/chapter4/6.mdx | 5 + chapters/en/chapter5/1.mdx | 5 + chapters/en/chapter5/2.mdx | 4 +- chapters/en/chapter5/3.mdx | 4 +- chapters/en/chapter5/4.mdx | 4 +- chapters/en/chapter5/5.mdx | 4 +- chapters/en/chapter5/6.mdx | 8 +- chapters/en/chapter5/7.mdx | 5 + chapters/en/chapter5/8.mdx | 5 + chapters/en/chapter6/1.mdx | 5 + chapters/en/chapter6/10.mdx | 5 + chapters/en/chapter6/2.mdx | 4 +- chapters/en/chapter6/3.mdx | 8 +- chapters/en/chapter6/3b.mdx | 8 +- chapters/en/chapter6/4.mdx | 4 +- chapters/en/chapter6/5.mdx | 4 +- chapters/en/chapter6/6.mdx | 4 +- chapters/en/chapter6/7.mdx | 4 +- chapters/en/chapter6/8.mdx | 4 +- chapters/en/chapter6/9.mdx | 5 + chapters/en/chapter7/1.mdx | 5 + chapters/en/chapter7/2.mdx | 8 +- chapters/en/chapter7/3.mdx | 8 +- chapters/en/chapter7/4.mdx | 8 +- chapters/en/chapter7/5.mdx | 2102 ++++++++++++------------- chapters/en/chapter7/6.mdx | 8 +- chapters/en/chapter7/7.mdx | 8 +- chapters/en/chapter7/8.mdx | 5 + chapters/en/chapter7/9.mdx | 5 + chapters/en/chapter8/1.mdx | 5 + chapters/en/chapter8/2.mdx | 4 +- chapters/en/chapter8/3.mdx | 4 +- chapters/en/chapter8/4.mdx | 4 +- chapters/en/chapter8/4_tf.mdx | 4 +- chapters/en/chapter8/5.mdx | 4 +- chapters/en/chapter8/6.mdx | 5 + chapters/en/chapter8/7.mdx | 5 + chapters/en/chapter9/1.mdx | 5 + chapters/en/chapter9/2.mdx | 4 +- chapters/en/chapter9/3.mdx | 4 +- chapters/en/chapter9/4.mdx | 4 +- chapters/en/chapter9/5.mdx | 4 +- chapters/en/chapter9/6.mdx | 4 +- chapters/en/chapter9/7.mdx | 4 +- chapters/en/chapter9/8.mdx | 5 + chapters/en/chapter9/9.mdx | 5 + chapters/es/chapter1/1.mdx | 5 + chapters/es/chapter1/10.mdx | 5 + chapters/es/chapter1/2.mdx | 5 + chapters/es/chapter1/3.mdx | 4 +- chapters/es/chapter1/4.mdx | 5 + chapters/es/chapter1/5.mdx | 5 + chapters/es/chapter1/6.mdx | 5 + chapters/es/chapter1/8.mdx | 4 +- chapters/es/chapter1/9.mdx | 5 + chapters/es/chapter2/4.mdx | 8 +- chapters/es/chapter2/5.mdx | 8 +- chapters/es/chapter3/1.mdx | 5 + chapters/es/chapter3/2.mdx | 8 +- chapters/es/chapter3/4.mdx | 4 +- chapters/es/chapter8/1.mdx | 5 + chapters/es/chapter8/2.mdx | 4 +- chapters/fa/chapter1/1.mdx | 5 + chapters/fa/chapter1/2.mdx | 5 + chapters/fa/chapter2/1.mdx | 5 + chapters/fa/chapter2/2.mdx | 8 +- chapters/fa/chapter2/3.mdx | 8 +- chapters/fa/chapter3/1.mdx | 5 + chapters/fa/chapter3/2.mdx | 8 +- chapters/fa/chapter4/1.mdx | 5 + chapters/fa/chapter4/2.mdx | 8 +- chapters/fr/chapter1/1.mdx | 115 +- chapters/fr/chapter1/10.mdx | 521 +++---- chapters/fr/chapter1/2.mdx | 47 +- chapters/fr/chapter1/3.mdx | 762 ++++----- chapters/fr/chapter1/4.mdx | 343 +++-- chapters/fr/chapter1/5.mdx | 39 +- chapters/fr/chapter1/6.mdx | 37 +- chapters/fr/chapter1/7.mdx | 37 +- chapters/fr/chapter1/8.mdx | 68 +- chapters/fr/chapter1/9.mdx | 25 +- chapters/fr/chapter2/1.mdx | 53 +- chapters/fr/chapter2/2.mdx | 698 ++++----- chapters/fr/chapter2/3.mdx | 462 +++--- chapters/fr/chapter2/4.mdx | 506 +++--- chapters/fr/chapter2/5.mdx | 708 ++++----- chapters/fr/chapter2/6.mdx | 376 ++--- chapters/fr/chapter2/7.mdx | 29 +- chapters/fr/chapter2/8.mdx | 619 ++++---- chapters/fr/chapter3/1.mdx | 49 +- chapters/fr/chapter3/2.mdx | 770 +++++----- chapters/fr/chapter3/3.mdx | 342 ++--- chapters/fr/chapter3/3_tf.mdx | 380 ++--- chapters/fr/chapter3/4.mdx | 718 ++++----- chapters/fr/chapter3/5.mdx | 43 +- chapters/fr/chapter3/6.mdx | 597 ++++---- chapters/fr/chapter4/1.mdx | 37 +- chapters/fr/chapter4/2.mdx | 194 +-- chapters/fr/chapter4/3.mdx | 1276 ++++++++-------- chapters/fr/chapter4/4.mdx | 173 ++- chapters/fr/chapter4/5.mdx | 17 +- chapters/fr/chapter4/6.mdx | 449 +++--- chapters/fr/chapter5/1.mdx | 37 +- chapters/fr/chapter5/2.mdx | 334 ++-- chapters/fr/chapter5/3.mdx | 1504 +++++++++--------- chapters/fr/chapter5/4.mdx | 592 +++---- chapters/fr/chapter5/5.mdx | 934 ++++++------ chapters/fr/chapter5/6.mdx | 1060 ++++++------- chapters/fr/chapter5/7.mdx | 25 +- chapters/fr/chapter5/8.mdx | 457 +++--- chapters/fr/chapter6/1.mdx | 29 +- chapters/fr/chapter6/10.mdx | 561 +++---- chapters/fr/chapter6/2.mdx | 528 +++---- chapters/fr/chapter6/3.mdx | 954 ++++++------ chapters/fr/chapter6/3b.mdx | 1438 ++++++++--------- chapters/fr/chapter6/4.mdx | 246 +-- chapters/fr/chapter6/5.mdx | 726 ++++----- chapters/fr/chapter6/6.mdx | 756 ++++----- chapters/fr/chapter6/7.mdx | 770 +++++----- chapters/fr/chapter6/8.mdx | 1132 +++++++------- chapters/fr/chapter6/9.mdx | 27 +- chapters/fr/chapter7/1.mdx | 71 +- chapters/fr/chapter7/2.mdx | 1964 ++++++++++++------------ chapters/fr/chapter7/3.mdx | 2084 ++++++++++++------------- chapters/fr/chapter7/4.mdx | 1996 ++++++++++++------------ chapters/fr/chapter7/5.mdx | 2166 +++++++++++++------------- chapters/fr/chapter7/6.mdx | 1810 +++++++++++----------- chapters/fr/chapter7/7.mdx | 2460 +++++++++++++++--------------- chapters/fr/chapter7/8.mdx | 37 +- chapters/fr/chapter7/9.mdx | 653 ++++---- chapters/fr/chapter8/1.mdx | 29 +- chapters/fr/chapter8/2.mdx | 750 ++++----- chapters/fr/chapter8/3.mdx | 414 ++--- chapters/fr/chapter8/4.mdx | 1586 +++++++++---------- chapters/fr/chapter8/4_tf.mdx | 978 ++++++------ chapters/fr/chapter8/5.mdx | 184 +-- chapters/fr/chapter8/6.mdx | 19 +- chapters/fr/chapter8/7.mdx | 403 ++--- chapters/fr/chapter9/1.mdx | 69 +- chapters/fr/chapter9/2.mdx | 232 +-- chapters/fr/chapter9/3.mdx | 332 ++-- chapters/fr/chapter9/4.mdx | 292 ++-- chapters/fr/chapter9/5.mdx | 148 +- chapters/fr/chapter9/6.mdx | 274 ++-- chapters/fr/chapter9/7.mdx | 478 +++--- chapters/fr/chapter9/8.mdx | 5 + chapters/fr/chapter9/9.mdx | 469 +++--- chapters/hi/chapter1/1.mdx | 5 + chapters/hi/chapter1/10.mdx | 5 + chapters/hi/chapter1/2.mdx | 5 + chapters/hi/chapter1/3.mdx | 4 +- chapters/hi/chapter1/4.mdx | 5 + chapters/hi/chapter1/5.mdx | 5 + chapters/hi/chapter1/6.mdx | 5 + chapters/hi/chapter1/7.mdx | 5 + chapters/hi/chapter1/8.mdx | 4 +- chapters/hi/chapter1/9.mdx | 5 + chapters/hi/chapter2/1.mdx | 5 + chapters/hi/chapter3/1.mdx | 5 + chapters/hi/chapter3/2.mdx | 8 +- chapters/hi/chapter3/3.mdx | 4 +- chapters/hi/chapter3/3_tf.mdx | 4 +- chapters/hi/chapter3/4.mdx | 4 +- chapters/hi/chapter3/5.mdx | 5 + chapters/hi/chapter3/6.mdx | 5 + chapters/it/_toctree.yml | 9 +- chapters/it/chapter1/1.mdx | 5 + chapters/it/chapter1/2.mdx | 5 + chapters/it/chapter1/3.mdx | 4 +- chapters/it/chapter1/4.mdx | 5 + chapters/it/chapter1/5.mdx | 5 + chapters/it/chapter1/6.mdx | 5 + chapters/it/chapter1/7.mdx | 5 + chapters/it/chapter1/8.mdx | 4 +- chapters/it/chapter1/9.mdx | 5 + chapters/it/chapter2/1.mdx | 26 + chapters/it/chapter2/2.mdx | 351 +++++ chapters/it/chapter4/1.mdx | 5 + chapters/it/chapter4/2.mdx | 8 +- chapters/it/chapter4/3.mdx | 8 +- chapters/it/chapter4/4.mdx | 5 + chapters/it/chapter4/5.mdx | 5 + chapters/it/chapter4/6.mdx | 5 + chapters/it/chapter5/1.mdx | 5 + chapters/it/chapter5/2.mdx | 4 +- chapters/it/chapter5/3.mdx | 4 +- chapters/it/chapter5/4.mdx | 4 +- chapters/it/chapter5/5.mdx | 4 +- chapters/it/chapter5/6.mdx | 8 +- chapters/it/chapter5/7.mdx | 5 + chapters/it/chapter5/8.mdx | 5 + chapters/it/chapter8/1.mdx | 5 + chapters/it/chapter8/2.mdx | 4 +- chapters/it/chapter8/3.mdx | 4 +- chapters/it/chapter8/4.mdx | 4 +- chapters/it/chapter8/4_tf.mdx | 4 +- chapters/it/chapter8/5.mdx | 4 +- chapters/it/chapter8/6.mdx | 5 + chapters/it/chapter8/7.mdx | 5 + chapters/ja/chapter1/1.mdx | 5 + chapters/ja/chapter4/1.mdx | 5 + chapters/ja/chapter4/2.mdx | 8 +- chapters/ja/chapter4/3.mdx | 8 +- chapters/ja/chapter4/4.mdx | 5 + chapters/ja/chapter4/5.mdx | 5 + chapters/ja/chapter4/6.mdx | 5 + chapters/ja/chapter7/1.mdx | 5 + chapters/ja/chapter7/2.mdx | 8 +- chapters/ja/chapter7/3.mdx | 8 +- chapters/ja/chapter7/4.mdx | 8 +- chapters/ja/chapter7/5.mdx | 8 +- chapters/ja/chapter7/6.mdx | 8 +- chapters/ja/chapter7/7.mdx | 8 +- chapters/ja/chapter7/8.mdx | 5 + chapters/ja/chapter7/9.mdx | 5 + chapters/ja/chapter8/1.mdx | 5 + chapters/ja/chapter8/2.mdx | 4 +- chapters/ja/chapter8/6.mdx | 5 + chapters/ko/chapter1/1.mdx | 5 + chapters/ko/chapter1/10.mdx | 5 + chapters/ko/chapter1/2.mdx | 5 + chapters/ko/chapter1/3.mdx | 4 +- chapters/ko/chapter1/4.mdx | 5 + chapters/ko/chapter1/5.mdx | 5 + chapters/ko/chapter1/6.mdx | 5 + chapters/ko/chapter1/7.mdx | 5 + chapters/ko/chapter1/8.mdx | 4 +- chapters/ko/chapter1/9.mdx | 5 + chapters/pt/chapter1/1.mdx | 5 + chapters/pt/chapter1/10.mdx | 5 + chapters/pt/chapter1/2.mdx | 5 + chapters/pt/chapter1/3.mdx | 4 +- chapters/pt/chapter1/4.mdx | 5 + chapters/pt/chapter1/5.mdx | 5 + chapters/pt/chapter1/6.mdx | 5 + chapters/pt/chapter1/7.mdx | 5 + chapters/pt/chapter1/8.mdx | 2 +- chapters/pt/chapter1/9.mdx | 5 + chapters/pt/chapter2/1.mdx | 5 + chapters/pt/chapter2/2.mdx | 8 +- chapters/pt/chapter2/3.mdx | 8 +- chapters/pt/chapter2/4.mdx | 8 +- chapters/pt/chapter2/5.mdx | 8 +- chapters/pt/chapter2/6.mdx | 8 +- chapters/pt/chapter2/7.mdx | 5 + chapters/pt/chapter2/8.mdx | 5 + chapters/pt/chapter4/1.mdx | 5 + chapters/pt/chapter4/2.mdx | 8 +- chapters/pt/chapter4/3.mdx | 8 +- chapters/pt/chapter4/4.mdx | 5 + chapters/pt/chapter4/5.mdx | 5 + chapters/pt/chapter4/6.mdx | 5 + chapters/pt/chapter5/1.mdx | 5 + chapters/pt/chapter5/2.mdx | 4 +- chapters/pt/chapter5/3.mdx | 4 +- chapters/pt/chapter5/4.mdx | 4 +- chapters/pt/chapter5/5.mdx | 4 +- chapters/pt/chapter5/6.mdx | 8 +- chapters/pt/chapter5/7.mdx | 5 + chapters/pt/chapter5/8.mdx | 5 + chapters/pt/chapter6/1.mdx | 5 + chapters/pt/chapter6/2.mdx | 4 +- chapters/pt/chapter6/3.mdx | 8 +- chapters/pt/chapter7/1.mdx | 5 + chapters/pt/chapter8/1.mdx | 5 + chapters/pt/chapter8/2.mdx | 4 +- chapters/pt/chapter8/3.mdx | 4 +- chapters/ru/chapter1/1.mdx | 5 + chapters/ru/chapter1/2.mdx | 5 + chapters/ru/chapter1/3.mdx | 4 +- chapters/ru/chapter1/4.mdx | 5 + chapters/ru/chapter1/5.mdx | 5 + chapters/ru/chapter1/6.mdx | 5 + chapters/ru/chapter1/7.mdx | 5 + chapters/ru/chapter1/8.mdx | 4 +- chapters/ru/chapter1/9.mdx | 5 + chapters/ru/chapter2/1.mdx | 5 + chapters/ru/chapter2/2.mdx | 8 +- chapters/ru/chapter2/3.mdx | 8 +- chapters/ru/chapter2/7.mdx | 5 + chapters/ru/chapter3/1.mdx | 5 + chapters/ru/chapter3/2.mdx | 8 +- chapters/ru/chapter3/3.mdx | 4 +- chapters/ru/chapter3/3_tf.mdx | 4 +- chapters/ru/chapter3/4.mdx | 4 +- chapters/ru/chapter3/5.mdx | 5 + chapters/ru/chapter3/6.mdx | 5 + chapters/ru/chapter4/1.mdx | 5 + chapters/ru/chapter4/2.mdx | 8 +- chapters/ru/chapter4/3.mdx | 8 +- chapters/ru/chapter4/4.mdx | 5 + chapters/ru/chapter4/5.mdx | 5 + chapters/ru/chapter4/6.mdx | 5 + chapters/th/chapter1/1.mdx | 5 + chapters/th/chapter1/10.mdx | 5 + chapters/th/chapter1/2.mdx | 5 + chapters/th/chapter1/3.mdx | 4 +- chapters/th/chapter1/4.mdx | 5 + chapters/th/chapter1/5.mdx | 5 + chapters/th/chapter1/6.mdx | 5 + chapters/th/chapter1/7.mdx | 5 + chapters/th/chapter1/8.mdx | 4 +- chapters/th/chapter1/9.mdx | 5 + chapters/th/chapter2/1.mdx | 5 + chapters/th/chapter2/2.mdx | 8 +- chapters/th/chapter2/3.mdx | 8 +- chapters/th/chapter2/4.mdx | 8 +- chapters/th/chapter2/5.mdx | 8 +- chapters/th/chapter2/6.mdx | 8 +- chapters/th/chapter2/7.mdx | 5 + chapters/th/chapter2/8.mdx | 5 + chapters/th/chapter3/1.mdx | 5 + chapters/th/chapter3/2.mdx | 8 +- chapters/th/chapter3/3.mdx | 4 +- chapters/th/chapter3/3_tf.mdx | 4 +- chapters/th/chapter3/4.mdx | 4 +- chapters/th/chapter3/5.mdx | 5 + chapters/th/chapter3/6.mdx | 5 + chapters/th/chapter4/1.mdx | 5 + chapters/th/chapter4/2.mdx | 8 +- chapters/th/chapter4/3.mdx | 8 +- chapters/th/chapter4/4.mdx | 5 + chapters/th/chapter4/5.mdx | 5 + chapters/th/chapter4/6.mdx | 5 + chapters/th/chapter6/1.mdx | 5 + chapters/th/chapter6/10.mdx | 5 + chapters/th/chapter6/2.mdx | 4 +- chapters/th/chapter6/3.mdx | 8 +- chapters/th/chapter6/3b.mdx | 8 +- chapters/th/chapter6/4.mdx | 4 +- chapters/th/chapter6/5.mdx | 4 +- chapters/th/chapter6/6.mdx | 4 +- chapters/th/chapter6/7.mdx | 4 +- chapters/th/chapter6/8.mdx | 4 +- chapters/th/chapter6/9.mdx | 5 + chapters/tr/chapter1/1.mdx | 111 +- chapters/tr/chapter1/2.mdx | 5 + chapters/tr/chapter1/5.mdx | 41 +- chapters/tr/chapter1/6.mdx | 5 + chapters/tr/chapter2/1.mdx | 5 + chapters/tr/chapter3/1.mdx | 5 + chapters/vi/chapter1/1.mdx | 5 + chapters/vi/chapter1/10.mdx | 5 + chapters/vi/chapter1/2.mdx | 5 + chapters/vi/chapter1/3.mdx | 4 +- chapters/vi/chapter1/4.mdx | 5 + chapters/vi/chapter1/5.mdx | 5 + chapters/vi/chapter1/6.mdx | 5 + chapters/vi/chapter1/7.mdx | 5 + chapters/vi/chapter1/8.mdx | 4 +- chapters/vi/chapter1/9.mdx | 5 + chapters/vi/chapter2/1.mdx | 5 + chapters/vi/chapter2/2.mdx | 8 +- chapters/vi/chapter2/3.mdx | 8 +- chapters/vi/chapter2/4.mdx | 8 +- chapters/vi/chapter2/5.mdx | 8 +- chapters/vi/chapter2/6.mdx | 8 +- chapters/vi/chapter2/7.mdx | 5 + chapters/vi/chapter2/8.mdx | 5 + chapters/vi/chapter3/1.mdx | 5 + chapters/vi/chapter3/2.mdx | 8 +- chapters/vi/chapter3/3.mdx | 4 +- chapters/vi/chapter3/3_tf.mdx | 4 +- chapters/vi/chapter3/4.mdx | 4 +- chapters/vi/chapter3/5.mdx | 5 + chapters/vi/chapter3/6.mdx | 5 + chapters/vi/chapter4/1.mdx | 5 + chapters/vi/chapter4/2.mdx | 8 +- chapters/vi/chapter4/3.mdx | 8 +- chapters/vi/chapter4/4.mdx | 5 + chapters/vi/chapter4/5.mdx | 5 + chapters/vi/chapter4/6.mdx | 5 + chapters/vi/chapter5/1.mdx | 5 + chapters/vi/chapter5/2.mdx | 4 +- chapters/vi/chapter5/3.mdx | 4 +- chapters/vi/chapter5/4.mdx | 4 +- chapters/vi/chapter5/5.mdx | 4 +- chapters/vi/chapter5/6.mdx | 8 +- chapters/vi/chapter5/7.mdx | 5 + chapters/vi/chapter5/8.mdx | 5 + chapters/vi/chapter6/1.mdx | 5 + chapters/vi/chapter6/10.md | 5 + chapters/vi/chapter6/2.mdx | 4 +- chapters/vi/chapter6/3.md | 8 +- chapters/vi/chapter6/3b.md | 8 +- chapters/vi/chapter6/4.md | 4 +- chapters/vi/chapter6/5.md | 4 +- chapters/vi/chapter6/6.md | 4 +- chapters/vi/chapter6/7.md | 4 +- chapters/vi/chapter6/8.md | 4 +- chapters/vi/chapter6/9.mdx | 5 + chapters/zh-CN/chapter1/1.mdx | 109 +- chapters/zh-CN/chapter1/10.mdx | 5 + chapters/zh-CN/chapter1/2.mdx | 43 +- chapters/zh-CN/chapter1/3.mdx | 4 +- chapters/zh-CN/chapter1/4.mdx | 349 ++--- chapters/zh-CN/chapter1/5.mdx | 5 + chapters/zh-CN/chapter1/6.mdx | 5 + chapters/zh-CN/chapter1/7.mdx | 37 +- chapters/zh-CN/chapter1/8.mdx | 4 +- chapters/zh-CN/chapter1/9.mdx | 25 +- chapters/zh-CN/chapter2/1.mdx | 39 +- chapters/zh-CN/chapter2/2.mdx | 8 +- chapters/zh-CN/chapter2/3.mdx | 528 +++---- chapters/zh-CN/chapter2/4.mdx | 478 +++--- chapters/zh-CN/chapter2/5.mdx | 710 ++++----- chapters/zh-CN/chapter2/6.mdx | 330 ++-- chapters/zh-CN/chapter2/7.mdx | 59 +- chapters/zh-CN/chapter2/8.mdx | 591 +++---- chapters/zh-CN/chapter3/1.mdx | 45 +- chapters/zh-CN/chapter3/2.mdx | 766 +++++----- chapters/zh-CN/chapter3/3.mdx | 344 ++--- chapters/zh-CN/chapter3/3_tf.mdx | 4 +- chapters/zh-CN/chapter3/4.mdx | 716 ++++----- chapters/zh-CN/chapter3/5.mdx | 45 +- chapters/zh-CN/chapter3/6.mdx | 571 +++---- chapters/zh-CN/chapter4/1.mdx | 5 + chapters/zh-CN/chapter4/2.mdx | 8 +- chapters/zh-CN/chapter4/3.mdx | 8 +- chapters/zh-CN/chapter4/4.mdx | 5 + chapters/zh-CN/chapter4/5.mdx | 5 + chapters/zh-CN/chapter4/6.mdx | 5 + chapters/zh-CN/chapter5/1.mdx | 5 + chapters/zh-CN/chapter5/2.mdx | 4 +- chapters/zh-CN/chapter5/3.mdx | 4 +- chapters/zh-CN/chapter5/4.mdx | 4 +- chapters/zh-CN/chapter5/5.mdx | 4 +- chapters/zh-CN/chapter5/6.mdx | 8 +- chapters/zh-CN/chapter5/7.mdx | 5 + chapters/zh-CN/chapter5/8.mdx | 5 + chapters/zh-CN/chapter6/1.mdx | 5 + chapters/zh-CN/chapter6/10.mdx | 5 + chapters/zh-CN/chapter6/2.mdx | 4 +- chapters/zh-CN/chapter6/3.mdx | 8 +- chapters/zh-CN/chapter6/3b.mdx | 8 +- chapters/zh-CN/chapter6/4.mdx | 4 +- chapters/zh-CN/chapter6/5.mdx | 4 +- chapters/zh-CN/chapter6/6.mdx | 4 +- chapters/zh-CN/chapter6/7.mdx | 4 +- chapters/zh-CN/chapter6/8.mdx | 4 +- chapters/zh-CN/chapter6/9.mdx | 5 + chapters/zh/_toctree.yml | 23 - chapters/zh/chapter1/1.mdx | 52 - chapters/zh/chapter1/10.mdx | 253 --- chapters/zh/chapter1/2.mdx | 20 - chapters/zh/chapter1/3.mdx | 287 ---- chapters/zh/chapter1/4.mdx | 172 --- chapters/zh/chapter1/5.mdx | 17 - chapters/zh/chapter1/6.mdx | 17 - chapters/zh/chapter1/7.mdx | 16 - chapters/zh/chapter1/8.mdx | 31 - chapters/zh/chapter1/9.mdx | 11 - utils/generate_notebooks.py | 1 + 493 files changed, 27300 insertions(+), 26587 deletions(-) create mode 100644 chapters/it/chapter2/1.mdx create mode 100644 chapters/it/chapter2/2.mdx delete mode 100644 chapters/zh/_toctree.yml delete mode 100644 chapters/zh/chapter1/1.mdx delete mode 100644 chapters/zh/chapter1/10.mdx delete mode 100644 chapters/zh/chapter1/2.mdx delete mode 100644 chapters/zh/chapter1/3.mdx delete mode 100644 chapters/zh/chapter1/4.mdx delete mode 100644 chapters/zh/chapter1/5.mdx delete mode 100644 chapters/zh/chapter1/6.mdx delete mode 100644 chapters/zh/chapter1/7.mdx delete mode 100644 chapters/zh/chapter1/8.mdx delete mode 100644 chapters/zh/chapter1/9.mdx diff --git a/README.md b/README.md index 9f46e8eef..49654517c 100644 --- a/README.md +++ b/README.md @@ -4,20 +4,27 @@ This repo contains the content that's used to create the **[Hugging Face course] ## 🌎 Languages and translations -| Language | Source | Authors | -|:-------------------------------------------------------|:-----------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [English](https://huggingface.co/course/en/chapter1/1) | [`chapters/en`](https://github.com/huggingface/course/tree/main/chapters/en) | [@sgugger](https://github.com/sgugger), [@lewtun](https://github.com/lewtun), [@LysandreJik](https://github.com/LysandreJik), [@Rocketknight1](https://github.com/Rocketknight1), [@sashavor](https://github.com/sashavor), [@osanseviero](https://github.com/osanseviero), [@SaulLu](https://github.com/SaulLu), [@lvwerra](https://github.com/lvwerra) | -| [Chinese (simplified)](https://huggingface.co/course/zh/chapter1/1) (WIP) | [`chapters/zh`](https://github.com/huggingface/course/tree/main/chapters/zh) | [@zhlhyx](https://github.com/zhlhyx), [petrichor1122](https://github.com/petrichor1122), [@1375626371](https://github.com/1375626371) | -| [French](https://huggingface.co/course/fr/chapter1/1) (WIP) | [`chapters/fr`](https://github.com/huggingface/course/tree/main/chapters/fr) | [@lbourdois](https://github.com/lbourdois), [@ChainYo](https://github.com/ChainYo), [@melaniedrevet](https://github.com/melaniedrevet), [@abdouaziz](https://github.com/abdouaziz) | -| [Gujarati](https://huggingface.co/course/gu/chapter1/1) (WIP) | [`chapters/gu`](https://github.com/huggingface/course/tree/main/chapters/gu) | [@pandyaved98](https://github.com/pandyaved98) | -| [Hindi](https://huggingface.co/course/hi/chapter1/1) (WIP) | [`chapters/hi`](https://github.com/huggingface/course/tree/main/chapters/hi) | [@pandyaved98](https://github.com/pandyaved98) | -| [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) | -| [Persian](https://huggingface.co/course/fa/chapter1/1) (WIP) | [`chapters/fa`](https://github.com/huggingface/course/tree/main/chapters/fa) | [@jowharshamshiri](https://github.com/jowharshamshiri), [@schoobani](https://github.com/schoobani) | -| [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) | -| [Spanish](https://huggingface.co/course/es/chapter1/1) (WIP) | [`chapters/es`](https://github.com/huggingface/course/tree/main/chapters/es) | [@camartinezbu](https://github.com/camartinezbu), [@munozariasjm](https://github.com/munozariasjm), [@fordaz](https://github.com/fordaz) | -| [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) | +| Language | Source | Authors | +|:------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [English](https://huggingface.co/course/en/chapter1/1) | [`chapters/en`](https://github.com/huggingface/course/tree/main/chapters/en) | [@sgugger](https://github.com/sgugger), [@lewtun](https://github.com/lewtun), [@LysandreJik](https://github.com/LysandreJik), [@Rocketknight1](https://github.com/Rocketknight1), [@sashavor](https://github.com/sashavor), [@osanseviero](https://github.com/osanseviero), [@SaulLu](https://github.com/SaulLu), [@lvwerra](https://github.com/lvwerra) | +| [Bengali](https://huggingface.co/course/bn/chapter1/1) (WIP) | [`chapters/bn`](https://github.com/huggingface/course/tree/main/chapters/bn) | [@avishek-018](https://github.com/avishek-018), [@eNipu](https://github.com/eNipu) | +| [German](https://huggingface.co/course/de/chapter1/1) (WIP) | [`chapters/de`](https://github.com/huggingface/course/tree/main/chapters/de) | [@JesperDramsch](https://github.com/JesperDramsch), [@MarcusFra](https://github.com/MarcusFra) | +| [Spanish](https://huggingface.co/course/es/chapter1/1) (WIP) | [`chapters/es`](https://github.com/huggingface/course/tree/main/chapters/es) | [@camartinezbu](https://github.com/camartinezbu), [@munozariasjm](https://github.com/munozariasjm), [@fordaz](https://github.com/fordaz) | +| [Persian](https://huggingface.co/course/fa/chapter1/1) (WIP) | [`chapters/fa`](https://github.com/huggingface/course/tree/main/chapters/fa) | [@jowharshamshiri](https://github.com/jowharshamshiri), [@schoobani](https://github.com/schoobani) | +| [French](https://huggingface.co/course/fr/chapter1/1) | [`chapters/fr`](https://github.com/huggingface/course/tree/main/chapters/fr) | [@lbourdois](https://github.com/lbourdois), [@ChainYo](https://github.com/ChainYo), [@melaniedrevet](https://github.com/melaniedrevet), [@abdouaziz](https://github.com/abdouaziz) | +| [Gujarati](https://huggingface.co/course/gu/chapter1/1) (WIP) | [`chapters/gu`](https://github.com/huggingface/course/tree/main/chapters/gu) | [@pandyaved98](https://github.com/pandyaved98) | +| [Hebrew](https://huggingface.co/course/he/chapter1/1) (WIP) | [`chapters/he`](https://github.com/huggingface/course/tree/main/chapters/he) | [@omer-dor](https://github.com/omer-dor) | +| [Hindi](https://huggingface.co/course/hi/chapter1/1) (WIP) | [`chapters/hi`](https://github.com/huggingface/course/tree/main/chapters/hi) | [@pandyaved98](https://github.com/pandyaved98) | +| [Italian](https://huggingface.co/course/it/chapter1/1) (WIP) | [`chapters/it`](https://github.com/huggingface/course/tree/main/chapters/it) | [@CaterinaBi](https://github.com/CaterinaBi), [@ClonedOne](https://github.com/ClonedOne), [@Nolanogenn](https://github.com/Nolanogenn), [@EdAbati](https://github.com/EdAbati), [@gdacciaro](https://github.com/gdacciaro) | +| [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) | +| [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) | +| [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) (WIP) | [`chapters/vi`](https://github.com/huggingface/course/tree/main/chapters/vi) | [@honghanhh](https://github.com/honghanhh) | +| [Chinese (simplified)](https://huggingface.co/course/zh-CN/chapter1/1) (WIP) | [`chapters/zh-CN`](https://github.com/huggingface/course/tree/main/chapters/zh-CN) | [@zhlhyx](https://github.com/zhlhyx), [petrichor1122](https://github.com/petrichor1122), [@1375626371](https://github.com/1375626371) | +| [Chinese (traditional)](https://huggingface.co/course/zh-TW/chapter1/1) (WIP) | [`chapters/zh-TW`](https://github.com/huggingface/course/tree/main/chapters/zh-TW) | [@davidpeng86](https://github.com/davidpeng86) | ### Translating the course into your language diff --git a/chapters/bn/chapter1/1.mdx b/chapters/bn/chapter1/1.mdx index 339d066d5..c40753b53 100644 --- a/chapters/bn/chapter1/1.mdx +++ b/chapters/bn/chapter1/1.mdx @@ -1,5 +1,10 @@ # ভূমিকা + + ## 🤗 কোর্সে স্বাগতম! diff --git a/chapters/bn/chapter2/1.mdx b/chapters/bn/chapter2/1.mdx index 3fbb16aa2..358c1f474 100644 --- a/chapters/bn/chapter2/1.mdx +++ b/chapters/bn/chapter2/1.mdx @@ -1,5 +1,10 @@ # ভূমিকা + + [অধ্যায় ১](/course/bn/chapter1) এ আমরা দেখে এসেছি যে Transformer মডেলগুলো সাধারণত অনেক বড় হয়। লাখ-লাখ কোটি-কোটি প্যারামিটার সম্বলিত এই মডেল গুলো কে ট্রেনিং এবং ডেপ্লয় করা বেশ জটিল ও কষ্টসাধ্য একটা কাজ। তাছাড়াও প্রায় প্রতিদিনই নতুন নতুন মডেল রিলিজ হচ্ছে এবং সবগুলোরই নিজস্ব বাস্তবায়ন রয়েছে। এই সবকিছু একসাথে এপ্লাই করা খুব সহজ একটা কাজ নয়। এই 🤗 Transformers লাইব্রেরিটা বানানো হয়েছে এই সমস্যাগুলো সমাধান করার জন্য। এর আসল উদ্দেশ্য হলো এমন একটি API প্রদান করা যার মাধ্যমে যেকোনো Transformer মডেলকে লোড করা, ট্রেইন করা কিংবা সেভ করা যাবে। লাইব্রেরিটির আসল ফিচারগুলো হলঃ diff --git a/chapters/de/chapter3/1.mdx b/chapters/de/chapter3/1.mdx index 0f1c7041c..7d275b917 100644 --- a/chapters/de/chapter3/1.mdx +++ b/chapters/de/chapter3/1.mdx @@ -2,6 +2,11 @@ # Einführung + + In [Kapitel 2](/course/chapter2) haben wir behandelt, wie man Tokenizer und vortrainierte Modelle verwendet, um Vorhersagen zu treffen. Was passiert aber, wenn wir ein vortrainiertes Modell für unseren eigenen Datensatz optimieren möchten? Das ist das Thema dieses Kapitels! Folgendes wirst du lernen: {#if fw === 'pt'} diff --git a/chapters/de/chapter3/2.mdx b/chapters/de/chapter3/2.mdx index 21c6df41a..9248e9b0f 100644 --- a/chapters/de/chapter3/2.mdx +++ b/chapters/de/chapter3/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/de/chapter3/3.mdx b/chapters/de/chapter3/3.mdx index ee38b9c67..466c25d29 100644 --- a/chapters/de/chapter3/3.mdx +++ b/chapters/de/chapter3/3.mdx @@ -2,9 +2,9 @@ # Fine-tuning eine Modells mit der Trainer API - diff --git a/chapters/de/chapter3/3_tf.mdx b/chapters/de/chapter3/3_tf.mdx index 6290506eb..686d8bb71 100644 --- a/chapters/de/chapter3/3_tf.mdx +++ b/chapters/de/chapter3/3_tf.mdx @@ -2,9 +2,9 @@ # Modell mit Keras fein-tunen - diff --git a/chapters/de/chapter3/4.mdx b/chapters/de/chapter3/4.mdx index c940e4030..2eaca08e8 100644 --- a/chapters/de/chapter3/4.mdx +++ b/chapters/de/chapter3/4.mdx @@ -1,8 +1,8 @@ # Komplettes Training - diff --git a/chapters/de/chapter3/5.mdx b/chapters/de/chapter3/5.mdx index 944442f6a..a017a1189 100644 --- a/chapters/de/chapter3/5.mdx +++ b/chapters/de/chapter3/5.mdx @@ -2,6 +2,11 @@ # Fein-tunen, Check! + + Das hat Spaß gemacht! In den ersten beiden Kapiteln hast du etwas über Modelle und Tokenizer gelernt, und jetzt weißt du, wie du sie auf deine eigenen Daten fein-tunen kannst. Rekapitulieren wir, was du in diesem Kapitel gelernt hast: {#if fw === 'pt'} diff --git a/chapters/de/chapter3/6.mdx b/chapters/de/chapter3/6.mdx index c031089f0..cd82492e7 100644 --- a/chapters/de/chapter3/6.mdx +++ b/chapters/de/chapter3/6.mdx @@ -4,6 +4,11 @@ # Quiz am Ende des Kapitels + + Teste, was du in diesem Kapitel gelernt hast! ### 1. Der Datensatz `emotion` enthält Twitter-Nachrichten, die mit Emotionen gelabelt sind. Suche im [Hub](https://huggingface.co/datasets) nach dem Datensatz und lies die Datensatzkarte. Welche der folgenden Emotionen gehört nicht zu den grundlegenden Emotionen? diff --git a/chapters/en/chapter1/1.mdx b/chapters/en/chapter1/1.mdx index cd1851129..b5e8bd360 100644 --- a/chapters/en/chapter1/1.mdx +++ b/chapters/en/chapter1/1.mdx @@ -1,5 +1,10 @@ # Introduction + + ## Welcome to the 🤗 Course! diff --git a/chapters/en/chapter1/10.mdx b/chapters/en/chapter1/10.mdx index 355cade7f..7c1b22080 100644 --- a/chapters/en/chapter1/10.mdx +++ b/chapters/en/chapter1/10.mdx @@ -2,6 +2,11 @@ # End-of-chapter quiz + + This chapter covered a lot of ground! Don't worry if you didn't grasp all the details; the next chapters will help you understand how things work under the hood. First, though, let's test what you learned in this chapter! diff --git a/chapters/en/chapter1/2.mdx b/chapters/en/chapter1/2.mdx index 4e4aecc1a..548e65d63 100644 --- a/chapters/en/chapter1/2.mdx +++ b/chapters/en/chapter1/2.mdx @@ -1,5 +1,10 @@ # Natural Language Processing + + Before jumping into Transformer models, let's do a quick overview of what natural language processing is and why we care about it. ## What is NLP? diff --git a/chapters/en/chapter1/3.mdx b/chapters/en/chapter1/3.mdx index ac22e7e8f..d25513cd4 100644 --- a/chapters/en/chapter1/3.mdx +++ b/chapters/en/chapter1/3.mdx @@ -1,8 +1,8 @@ # Transformers, what can they do? - diff --git a/chapters/en/chapter1/4.mdx b/chapters/en/chapter1/4.mdx index 6d286a42e..6792a8a57 100644 --- a/chapters/en/chapter1/4.mdx +++ b/chapters/en/chapter1/4.mdx @@ -1,5 +1,10 @@ # How do Transformers work? + + In this section, we will take a high-level look at the architecture of Transformer models. ## A bit of Transformer history diff --git a/chapters/en/chapter1/5.mdx b/chapters/en/chapter1/5.mdx index 1c707033b..59c9d3a5a 100644 --- a/chapters/en/chapter1/5.mdx +++ b/chapters/en/chapter1/5.mdx @@ -1,5 +1,10 @@ # Encoder models + + Encoder models use only the encoder of a Transformer model. At each stage, the attention layers can access all the words in the initial sentence. These models are often characterized as having "bi-directional" attention, and are often called *auto-encoding models*. diff --git a/chapters/en/chapter1/6.mdx b/chapters/en/chapter1/6.mdx index d86cea9e5..2ec974012 100644 --- a/chapters/en/chapter1/6.mdx +++ b/chapters/en/chapter1/6.mdx @@ -1,5 +1,10 @@ # Decoder models + + Decoder models use only the decoder of a Transformer model. At each stage, for a given word the attention layers can only access the words positioned before it in the sentence. These models are often called *auto-regressive models*. diff --git a/chapters/en/chapter1/7.mdx b/chapters/en/chapter1/7.mdx index 3639c2a81..0474bcf76 100644 --- a/chapters/en/chapter1/7.mdx +++ b/chapters/en/chapter1/7.mdx @@ -1,5 +1,10 @@ # Sequence-to-sequence models + + Encoder-decoder models (also called *sequence-to-sequence models*) use both parts of the Transformer architecture. At each stage, the attention layers of the encoder can access all the words in the initial sentence, whereas the attention layers of the decoder can only access the words positioned before a given word in the input. diff --git a/chapters/en/chapter1/8.mdx b/chapters/en/chapter1/8.mdx index 90c80665d..08135867b 100644 --- a/chapters/en/chapter1/8.mdx +++ b/chapters/en/chapter1/8.mdx @@ -1,8 +1,8 @@ # Bias and limitations - diff --git a/chapters/en/chapter1/9.mdx b/chapters/en/chapter1/9.mdx index 4cd91feac..b9f6b5d99 100644 --- a/chapters/en/chapter1/9.mdx +++ b/chapters/en/chapter1/9.mdx @@ -1,5 +1,10 @@ # Summary + + In this chapter, you saw how to approach different NLP tasks using the high-level `pipeline()` function from 🤗 Transformers. You also saw how to search for and use models in the Hub, as well as how to use the Inference API to test the models directly in your browser. We discussed how Transformer models work at a high level, and talked about the importance of transfer learning and fine-tuning. A key aspect is that you can use the full architecture or only the encoder or decoder, depending on what kind of task you aim to solve. The following table summarizes this: diff --git a/chapters/en/chapter2/1.mdx b/chapters/en/chapter2/1.mdx index 9ab184b82..ce9c18224 100644 --- a/chapters/en/chapter2/1.mdx +++ b/chapters/en/chapter2/1.mdx @@ -1,5 +1,10 @@ # Introduction + + As you saw in [Chapter 1](/course/chapter1), Transformer models are usually very large. With millions to tens of *billions* of parameters, training and deploying these models is a complicated undertaking. Furthermore, with new models being released on a near-daily basis and each having its own implementation, trying them all out is no easy task. The 🤗 Transformers library was created to solve this problem. Its goal is to provide a single API through which any Transformer model can be loaded, trained, and saved. The library's main features are: diff --git a/chapters/en/chapter2/2.mdx b/chapters/en/chapter2/2.mdx index d1304d737..d65e4983e 100644 --- a/chapters/en/chapter2/2.mdx +++ b/chapters/en/chapter2/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter2/3.mdx b/chapters/en/chapter2/3.mdx index c9100c42c..61e60b564 100644 --- a/chapters/en/chapter2/3.mdx +++ b/chapters/en/chapter2/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter2/4.mdx b/chapters/en/chapter2/4.mdx index ccebe04ec..9699ef2fc 100644 --- a/chapters/en/chapter2/4.mdx +++ b/chapters/en/chapter2/4.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter2/5.mdx b/chapters/en/chapter2/5.mdx index 5a692aa19..a268b4ce5 100644 --- a/chapters/en/chapter2/5.mdx +++ b/chapters/en/chapter2/5.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter2/6.mdx b/chapters/en/chapter2/6.mdx index 974123515..49e9e0bca 100644 --- a/chapters/en/chapter2/6.mdx +++ b/chapters/en/chapter2/6.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter2/7.mdx b/chapters/en/chapter2/7.mdx index 122728d08..d5306c56f 100644 --- a/chapters/en/chapter2/7.mdx +++ b/chapters/en/chapter2/7.mdx @@ -1,5 +1,10 @@ # Basic usage completed! + + Great job following the course up to here! To recap, in this chapter you: - Learned the basic building blocks of a Transformer model. diff --git a/chapters/en/chapter2/8.mdx b/chapters/en/chapter2/8.mdx index 861aacb39..96dfd107e 100644 --- a/chapters/en/chapter2/8.mdx +++ b/chapters/en/chapter2/8.mdx @@ -4,6 +4,11 @@ # End-of-chapter quiz + + ### 1. What is the order of the language modeling pipeline? + In [Chapter 2](/course/chapter2) we explored how to use tokenizers and pretrained models to make predictions. But what if you want to fine-tune a pretrained model for your own dataset? That's the topic of this chapter! You will learn: {#if fw === 'pt'} diff --git a/chapters/en/chapter3/2.mdx b/chapters/en/chapter3/2.mdx index d6d4ceb49..dd340b738 100644 --- a/chapters/en/chapter3/2.mdx +++ b/chapters/en/chapter3/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter3/3.mdx b/chapters/en/chapter3/3.mdx index ebd301469..4420d705b 100644 --- a/chapters/en/chapter3/3.mdx +++ b/chapters/en/chapter3/3.mdx @@ -2,9 +2,9 @@ # Fine-tuning a model with the Trainer API - diff --git a/chapters/en/chapter3/3_tf.mdx b/chapters/en/chapter3/3_tf.mdx index 6357be0b2..72f84ba20 100644 --- a/chapters/en/chapter3/3_tf.mdx +++ b/chapters/en/chapter3/3_tf.mdx @@ -2,9 +2,9 @@ # Fine-tuning a model with Keras - diff --git a/chapters/en/chapter3/4.mdx b/chapters/en/chapter3/4.mdx index a515ce2af..53550b40c 100644 --- a/chapters/en/chapter3/4.mdx +++ b/chapters/en/chapter3/4.mdx @@ -1,8 +1,8 @@ # A full training - diff --git a/chapters/en/chapter3/5.mdx b/chapters/en/chapter3/5.mdx index dda8cb7fe..bdbea1c52 100644 --- a/chapters/en/chapter3/5.mdx +++ b/chapters/en/chapter3/5.mdx @@ -2,6 +2,11 @@ # Fine-tuning, Check! + + That was fun! In the first two chapters you learned about models and tokenizers, and now you know how to fine-tune them for your own data. To recap, in this chapter you: {#if fw === 'pt'} diff --git a/chapters/en/chapter3/6.mdx b/chapters/en/chapter3/6.mdx index 63e6c7052..75a7cf9f5 100644 --- a/chapters/en/chapter3/6.mdx +++ b/chapters/en/chapter3/6.mdx @@ -4,6 +4,11 @@ # End-of-chapter quiz + + Test what you learned in this chapter! ### 1. The `emotion` dataset contains Twitter messages labeled with emotions. Search for it in the [Hub](https://huggingface.co/datasets), and read the dataset card. Which of these is not one of its basic emotions? diff --git a/chapters/en/chapter4/1.mdx b/chapters/en/chapter4/1.mdx index 5b3a7f706..264326231 100644 --- a/chapters/en/chapter4/1.mdx +++ b/chapters/en/chapter4/1.mdx @@ -1,5 +1,10 @@ # The Hugging Face Hub + + The [Hugging Face Hub](https://huggingface.co/) –- our main website –- is a central platform that enables anyone to discover, use, and contribute new state-of-the-art models and datasets. It hosts a wide variety of models, with more than 10,000 publicly available. We'll focus on the models in this chapter, and take a look at the datasets in Chapter 5. The models in the Hub are not limited to 🤗 Transformers or even NLP. There are models from [Flair](https://github.com/flairNLP/flair) and [AllenNLP](https://github.com/allenai/allennlp) for NLP, [Asteroid](https://github.com/asteroid-team/asteroid) and [pyannote](https://github.com/pyannote/pyannote-audio) for speech, and [timm](https://github.com/rwightman/pytorch-image-models) for vision, to name a few. diff --git a/chapters/en/chapter4/2.mdx b/chapters/en/chapter4/2.mdx index 9519cddac..bca54f883 100644 --- a/chapters/en/chapter4/2.mdx +++ b/chapters/en/chapter4/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter4/3.mdx b/chapters/en/chapter4/3.mdx index a2dbc7ee8..3fb6e0a5c 100644 --- a/chapters/en/chapter4/3.mdx +++ b/chapters/en/chapter4/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter4/4.mdx b/chapters/en/chapter4/4.mdx index 8d9a75817..b609b479a 100644 --- a/chapters/en/chapter4/4.mdx +++ b/chapters/en/chapter4/4.mdx @@ -1,5 +1,10 @@ # Building a model card + + The model card is a file which is arguably as important as the model and tokenizer files in a model repository. It is the central definition of the model, ensuring reusability by fellow community members and reproducibility of results, and providing a platform on which other members may build their artifacts. Documenting the training and evaluation process helps others understand what to expect of a model — and providing sufficient information regarding the data that was used and the preprocessing and postprocessing that were done ensures that the limitations, biases, and contexts in which the model is and is not useful can be identified and understood. diff --git a/chapters/en/chapter4/5.mdx b/chapters/en/chapter4/5.mdx index 9edff8a97..341f9f348 100644 --- a/chapters/en/chapter4/5.mdx +++ b/chapters/en/chapter4/5.mdx @@ -1,5 +1,10 @@ # Part 1 completed! + + This is the end of the first part of the course! Part 2 will be released on November 15th with a big community event, see more information [here](https://huggingface.co/blog/course-launch-event). You should now be able to fine-tune a pretrained model on a text classification problem (single or pairs of sentences) and upload the result to the Model Hub. To make sure you mastered this first section, you should do exactly that on a problem that interests you (and not necessarily in English if you speak another language)! You can find help in the [Hugging Face forums](https://discuss.huggingface.co/) and share your project in [this topic](https://discuss.huggingface.co/t/share-your-projects/6803) once you're finished. diff --git a/chapters/en/chapter4/6.mdx b/chapters/en/chapter4/6.mdx index 6e968972f..4317fdb4f 100644 --- a/chapters/en/chapter4/6.mdx +++ b/chapters/en/chapter4/6.mdx @@ -4,6 +4,11 @@ # End-of-chapter quiz + + Let's test what you learned in this chapter! ### 1. What are models on the Hub limited to? diff --git a/chapters/en/chapter5/1.mdx b/chapters/en/chapter5/1.mdx index f65c89609..04fbbed52 100644 --- a/chapters/en/chapter5/1.mdx +++ b/chapters/en/chapter5/1.mdx @@ -1,5 +1,10 @@ # Introduction + + In [Chapter 3](/course/chapter3) you got your first taste of the 🤗 Datasets library and saw that there were three main steps when it came to fine-tuning a model: 1. Load a dataset from the Hugging Face Hub. diff --git a/chapters/en/chapter5/2.mdx b/chapters/en/chapter5/2.mdx index 853595311..9b9c84d05 100644 --- a/chapters/en/chapter5/2.mdx +++ b/chapters/en/chapter5/2.mdx @@ -1,8 +1,8 @@ # What if my dataset isn't on the Hub? - diff --git a/chapters/en/chapter5/3.mdx b/chapters/en/chapter5/3.mdx index f47d5abbb..075a88ceb 100644 --- a/chapters/en/chapter5/3.mdx +++ b/chapters/en/chapter5/3.mdx @@ -1,8 +1,8 @@ # Time to slice and dice - diff --git a/chapters/en/chapter5/4.mdx b/chapters/en/chapter5/4.mdx index cb90067f4..7de820546 100644 --- a/chapters/en/chapter5/4.mdx +++ b/chapters/en/chapter5/4.mdx @@ -1,8 +1,8 @@ # Big data? 🤗 Datasets to the rescue! - diff --git a/chapters/en/chapter5/5.mdx b/chapters/en/chapter5/5.mdx index d5b0605ca..363afacbc 100644 --- a/chapters/en/chapter5/5.mdx +++ b/chapters/en/chapter5/5.mdx @@ -1,8 +1,8 @@ # Creating your own dataset - diff --git a/chapters/en/chapter5/6.mdx b/chapters/en/chapter5/6.mdx index 1db80cc9e..b2aa21057 100644 --- a/chapters/en/chapter5/6.mdx +++ b/chapters/en/chapter5/6.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter5/7.mdx b/chapters/en/chapter5/7.mdx index 7b67dbe10..d4f42a6a7 100644 --- a/chapters/en/chapter5/7.mdx +++ b/chapters/en/chapter5/7.mdx @@ -1,5 +1,10 @@ # 🤗 Datasets, check! + + Well, that was quite a tour through the 🤗 Datasets library -- congratulations on making it this far! With the knowledge that you've gained from this chapter, you should be able to: - Load datasets from anywhere, be it the Hugging Face Hub, your laptop, or a remote server at your company. diff --git a/chapters/en/chapter5/8.mdx b/chapters/en/chapter5/8.mdx index 69888b348..ae049731e 100644 --- a/chapters/en/chapter5/8.mdx +++ b/chapters/en/chapter5/8.mdx @@ -2,6 +2,11 @@ # End-of-chapter quiz + + This chapter covered a lot of ground! Don't worry if you didn't grasp all the details; the next chapters will help you understand how things work under the hood. Before moving on, though, let's test what you learned in this chapter. diff --git a/chapters/en/chapter6/1.mdx b/chapters/en/chapter6/1.mdx index 5e5c63c9a..778883488 100644 --- a/chapters/en/chapter6/1.mdx +++ b/chapters/en/chapter6/1.mdx @@ -1,5 +1,10 @@ # Introduction + + In [Chapter 3](/course/chapter3), we looked at how to fine-tune a model on a given task. When we do that, we use the same tokenizer that the model was pretrained with -- but what do we do when we want to train a model from scratch? In these cases, using a tokenizer that was pretrained on a corpus from another domain or language is typically suboptimal. For example, a tokenizer that's trained on an English corpus will perform poorly on a corpus of Japanese texts because the use of spaces and punctuation is very different in the two languages. In this chapter, you will learn how to train a brand new tokenizer on a corpus of texts, so it can then be used to pretrain a language model. This will all be done with the help of the [🤗 Tokenizers](https://github.com/huggingface/tokenizers) library, which provides the "fast" tokenizers in the [🤗 Transformers](https://github.com/huggingface/transformers) library. We'll take a close look at the features that this library provides, and explore how the fast tokenizers differ from the "slow" versions. diff --git a/chapters/en/chapter6/10.mdx b/chapters/en/chapter6/10.mdx index 6d488332d..3a2fec5dd 100644 --- a/chapters/en/chapter6/10.mdx +++ b/chapters/en/chapter6/10.mdx @@ -2,6 +2,11 @@ # End-of-chapter quiz + + Let's test what you learned in this chapter! ### 1. When should you train a new tokenizer? diff --git a/chapters/en/chapter6/2.mdx b/chapters/en/chapter6/2.mdx index 7eba91c92..4effa7320 100644 --- a/chapters/en/chapter6/2.mdx +++ b/chapters/en/chapter6/2.mdx @@ -1,8 +1,8 @@ # Training a new tokenizer from an old one - diff --git a/chapters/en/chapter6/3.mdx b/chapters/en/chapter6/3.mdx index 85b24c195..4f9bd30db 100644 --- a/chapters/en/chapter6/3.mdx +++ b/chapters/en/chapter6/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter6/3b.mdx b/chapters/en/chapter6/3b.mdx index 61fad33d0..6b8ccd02f 100644 --- a/chapters/en/chapter6/3b.mdx +++ b/chapters/en/chapter6/3b.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter6/4.mdx b/chapters/en/chapter6/4.mdx index 741e57e8c..58a68f23b 100644 --- a/chapters/en/chapter6/4.mdx +++ b/chapters/en/chapter6/4.mdx @@ -1,8 +1,8 @@ # Normalization and pre-tokenization - diff --git a/chapters/en/chapter6/5.mdx b/chapters/en/chapter6/5.mdx index a9f070b0e..18b189bc5 100644 --- a/chapters/en/chapter6/5.mdx +++ b/chapters/en/chapter6/5.mdx @@ -1,8 +1,8 @@ # Byte-Pair Encoding tokenization - diff --git a/chapters/en/chapter6/6.mdx b/chapters/en/chapter6/6.mdx index d4152cd5e..7e60dabb0 100644 --- a/chapters/en/chapter6/6.mdx +++ b/chapters/en/chapter6/6.mdx @@ -1,8 +1,8 @@ # WordPiece tokenization - diff --git a/chapters/en/chapter6/7.mdx b/chapters/en/chapter6/7.mdx index e23d6f473..2a4d6f2f3 100644 --- a/chapters/en/chapter6/7.mdx +++ b/chapters/en/chapter6/7.mdx @@ -1,8 +1,8 @@ # Unigram tokenization - diff --git a/chapters/en/chapter6/8.mdx b/chapters/en/chapter6/8.mdx index 301648c7e..d831eb239 100644 --- a/chapters/en/chapter6/8.mdx +++ b/chapters/en/chapter6/8.mdx @@ -1,8 +1,8 @@ # Building a tokenizer, block by block - diff --git a/chapters/en/chapter6/9.mdx b/chapters/en/chapter6/9.mdx index 6d2e0f36e..132206d25 100644 --- a/chapters/en/chapter6/9.mdx +++ b/chapters/en/chapter6/9.mdx @@ -1,5 +1,10 @@ # Tokenizers, check! + + Great job finishing this chapter! After this deep dive into tokenizers, you should: diff --git a/chapters/en/chapter7/1.mdx b/chapters/en/chapter7/1.mdx index 58215cb40..020d7ea5d 100644 --- a/chapters/en/chapter7/1.mdx +++ b/chapters/en/chapter7/1.mdx @@ -2,6 +2,11 @@ # Introduction + + In [Chapter 3](/course/chapter3), you saw how to fine-tune a model for text classification. In this chapter, we will tackle the following common NLP tasks: - Token classification diff --git a/chapters/en/chapter7/2.mdx b/chapters/en/chapter7/2.mdx index 3eaba62c8..b052dc207 100644 --- a/chapters/en/chapter7/2.mdx +++ b/chapters/en/chapter7/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter7/3.mdx b/chapters/en/chapter7/3.mdx index 1ea3d6ee5..63eeba0a9 100644 --- a/chapters/en/chapter7/3.mdx +++ b/chapters/en/chapter7/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter7/4.mdx b/chapters/en/chapter7/4.mdx index e68bb376b..cc0a41c49 100644 --- a/chapters/en/chapter7/4.mdx +++ b/chapters/en/chapter7/4.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter7/5.mdx b/chapters/en/chapter7/5.mdx index e6df6fc31..f66a4f429 100644 --- a/chapters/en/chapter7/5.mdx +++ b/chapters/en/chapter7/5.mdx @@ -1,1051 +1,1051 @@ - - -# Summarization - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - - -In this section we'll take a look at how Transformer models can be used to condense long documents into summaries, a task known as _text summarization_. This is one of the most challenging NLP tasks as it requires a range of abilities, such as understanding long passages and generating coherent text that captures the main topics in a document. However, when done well, text summarization is a powerful tool that can speed up various business processes by relieving the burden of domain experts to read long documents in detail. - - - -Although there already exist various fine-tuned models for summarization on the [Hugging Face Hub](https://huggingface.co/models?pipeline_tag=summarization&sort=downloads), almost all of these are only suitable for English documents. So, to add a twist in this section, we'll train a bilingual model for English and Spanish. By the end of this section, you'll have a [model](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es) that can summarize customer reviews like the one shown here: - - - -As we'll see, these summaries are concise because they're learned from the titles that customers provide in their product reviews. Let's start by putting together a suitable bilingual corpus for this task. - -## Preparing a multilingual corpus - -We'll use the [Multilingual Amazon Reviews Corpus](https://huggingface.co/datasets/amazon_reviews_multi) to create our bilingual summarizer. This corpus consists of Amazon product reviews in six languages and is typically used to benchmark multilingual classifiers. However, since each review is accompanied by a short title, we can use the titles as the target summaries for our model to learn from! To get started, let's download the English and Spanish subsets from the 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 - }) -}) -``` - -As you can see, for each language there are 200,000 reviews for the `train` split, and 5,000 reviews for each of the `validation` and `test` splits. The review information we are interested in is contained in the `review_body` and `review_title` columns. Let's take a look at a few examples by creating a simple function that takes a random sample from the training set with the techniques we learned in [Chapter 5](/course/chapter5): - -```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.' -``` - - - -✏️ **Try it out!** Change the random seed in the `Dataset.shuffle()` command to explore other reviews in the corpus. If you're a Spanish speaker, take a look at some of the reviews in `spanish_dataset` to see if the titles also seem like reasonable summaries. - - - -This sample shows the diversity of reviews one typically finds online, ranging from positive to negative (and everything in between!). Although the example with the "meh" title is not very informative, the other titles look like decent summaries of the reviews themselves. Training a summarization model on all 400,000 reviews would take far too long on a single GPU, so instead we'll focus on generating summaries for a single domain of products. To get a feel for what domains we can choose from, let's convert `english_dataset` to a `pandas.DataFrame` and compute the number of reviews per product category: - -```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 -``` - -The most popular products in the English dataset are about household items, clothing, and wireless electronics. To stick with the Amazon theme, though, let's focus on summarizing book reviews -- after all, this is what the company was founded on! We can see two product categories that fit the bill (`book` and `digital_ebook_purchase`), so let's filter the datasets in both languages for just these products. As we saw in [Chapter 5](/course/chapter5), the `Dataset.filter()` function allows us to slice a dataset very efficiently, so we can define a simple function to do this: - -```python -def filter_books(example): - return ( - example["product_category"] == "book" - or example["product_category"] == "digital_ebook_purchase" - ) -``` - -Now when we apply this function to `english_dataset` and `spanish_dataset`, the result will contain just those rows involving the book categories. Before applying the filter, let's switch the format of `english_dataset` from `"pandas"` back to `"arrow"`: - -```python -english_dataset.reset_format() -``` - -We can then apply the filter function, and as a sanity check let's inspect a sample of reviews to see if they are indeed about books: - -```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.' -``` - -Okay, we can see that the reviews are not strictly about books and might refer to things like calendars and electronic applications such as OneNote. Nevertheless, the domain seems about right to train a summarization model on. Before we look at various models that are suitable for this task, we have one last bit of data preparation to do: combining the English and Spanish reviews as a single `DatasetDict` object. 🤗 Datasets provides a handy `concatenate_datasets()` function that (as the name suggests) will stack two `Dataset` objects on top of each other. So, to create our bilingual dataset, we'll loop over each split, concatenate the datasets for that split, and shuffle the result to ensure our model doesn't overfit to a single language: - -```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) - -# Peek at a few examples -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' -``` - -This certainly looks like a mix of English and Spanish reviews! Now that we have a training corpus, one final thing to check is the distribution of words in the reviews and their titles. This is especially important for summarization tasks, where short reference summaries in the data can bias the model to only output one or two words in the generated summaries. The plots below show the word distributions, and we can see that the titles are heavily skewed toward just 1-2 words: - -
-Word count distributions for the review titles and texts. - -
- -To deal with this, we'll filter out the examples with very short titles so that our model can produce more interesting summaries. Since we're dealing with English and Spanish texts, we can use a rough heuristic to split the titles on whitespace and then use our trusty `Dataset.filter()` method as follows: - -```python -books_dataset = books_dataset.filter(lambda x: len(x["review_title"].split()) > 2) -``` - -Now that we've prepared our corpus, let's take a look at a few possible Transformer models that one might fine-tune on it! - -## Models for text summarization - -If you think about it, text summarization is a similar sort of task to machine translation: we have a body of text like a review that we'd like to "translate" into a shorter version that captures the salient features of the input. Accordingly, most Transformer models for summarization adopt the encoder-decoder architecture that we first encountered in [Chapter 1](/course/chapter1), although there are some exceptions like the GPT family of models which can also be used for summarization in few-shot settings. The following table lists some popular pretrained models that can be fine-tuned for summarization. - -| Transformer model | Description | Multilingual? | -| :---------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------: | -| [GPT-2](https://huggingface.co/gpt2-xl) | Although trained as an auto-regressive language model, you can make GPT-2 generate summaries by appending "TL;DR" at the end of the input text. | ❌ | -| [PEGASUS](https://huggingface.co/google/pegasus-large) | Uses a pretraining objective to predict masked sentences in multi-sentence texts. This pretraining objective is closer to summarization than vanilla language modeling and scores highly on popular benchmarks. | ❌ | -| [T5](https://huggingface.co/t5-base) | A universal Transformer architecture that formulates all tasks in a text-to-text framework; e.g., the input format for the model to summarize a document is `summarize: ARTICLE`. | ❌ | -| [mT5](https://huggingface.co/google/mt5-base) | A multilingual version of T5, pretrained on the multilingual Common Crawl corpus (mC4), covering 101 languages. | ✅ | -| [BART](https://huggingface.co/facebook/bart-base) | A novel Transformer architecture with both an encoder and a decoder stack trained to reconstruct corrupted input that combines the pretraining schemes of BERT and GPT-2. | ❌ | -| [mBART-50](https://huggingface.co/facebook/mbart-large-50) | A multilingual version of BART, pretrained on 50 languages. | ✅ | - -As you can see from this table, the majority of Transformer models for summarization (and indeed most NLP tasks) are monolingual. This is great if your task is in a "high-resource" language like English or German, but less so for the thousands of other languages in use across the world. Fortunately, there is a class of multilingual Transformer models, like mT5 and mBART, that come to the rescue. These models are pretrained using language modeling, but with a twist: instead of training on a corpus of one language, they are trained jointly on texts in over 50 languages at once! - -We'll focus on mT5, an interesting architecture based on T5 that was pretrained in a text-to-text framework. In T5, every NLP task is formulated in terms of a prompt prefix like `summarize:` which conditions the model to adapt the generated text to the prompt. As shown in the figure below, this makes T5 extremely versatile, as you can solve many tasks with a single model! - -
-Different tasks performed by the T5 architecture. - -
- -mT5 doesn't use prefixes, but shares much of the versatility of T5 and has the advantage of being multilingual. Now that we've picked a model, let's take a look at preparing our data for training. - - - - -✏️ **Try it out!** Once you've worked through this section, see how well mT5 compares to mBART by fine-tuning the latter with the same techniques. For bonus points, you can also try fine-tuning T5 on just the English reviews. Since T5 has a special prefix prompt, you'll need to prepend `summarize:` to the input examples in the preprocessing steps below. - - - -## Preprocessing the data - - - -Our next task is to tokenize and encode our reviews and their titles. As usual, we begin by loading the tokenizer associated with the pretrained model checkpoint. We'll use `mt5-small` as our checkpoint so we can fine-tune the model in a reasonable amount of time: - -```python -from transformers import AutoTokenizer - -model_checkpoint = "google/mt5-small" -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) -``` - - - -💡 In the early stages of your NLP projects, a good practice is to train a class of "small" models on a small sample of data. This allows you to debug and iterate faster toward an end-to-end workflow. Once you are confident in the results, you can always scale up the model by simply changing the model checkpoint! - - - -Let's test out the mT5 tokenizer on a small example: - -```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]} -``` - -Here we can see the familiar `input_ids` and `attention_mask` that we encountered in our first fine-tuning experiments back in [Chapter 3](/course/chapter3). Let's decode these input IDs with the tokenizer's `convert_ids_to_tokens()` function to see what kind of tokenizer we're dealing with: - -```python -tokenizer.convert_ids_to_tokens(inputs.input_ids) -``` - -```python out -['▁I', '▁', 'loved', '▁reading', '▁the', '▁Hung', 'er', '▁Games', ''] -``` - -The special Unicode character `▁` and end-of-sequence token `` indicate that we're dealing with the SentencePiece tokenizer, which is based on the Unigram segmentation algorithm discussed in [Chapter 6](/course/chapter6). Unigram is especially useful for multilingual corpora since it allows SentencePiece to be agnostic about accents, punctuation, and the fact that many languages, like Japanese, do not have whitespace characters. - -To tokenize our corpus, we have to deal with a subtlety associated with summarization: because our labels are also text, it is possible that they exceed the model's maximum context size. This means we need to apply truncation to both the reviews and their titles to ensure we don't pass excessively long inputs to our model. The tokenizers in 🤗 Transformers provide a nifty `as_target_tokenizer()` function that allows you to tokenize the labels in parallel to the inputs. This is typically done using a context manager inside a preprocessing function that first encodes the inputs, and then encodes the labels as a separate column. Here is an example of such a function for 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 - ) - # Set up the tokenizer for targets - with tokenizer.as_target_tokenizer(): - labels = tokenizer( - examples["review_title"], max_length=max_target_length, truncation=True - ) - - model_inputs["labels"] = labels["input_ids"] - return model_inputs -``` - -Let's walk through this code to understand what's happening. The first thing we've done is define values for `max_input_length` and `max_target_length`, which set the upper limits for how long our reviews and titles can be. Since the review body is typically much larger than the title, we've scaled these values accordingly. Then, in the `preprocess_function()` itself we can see the reviews are first tokenized, followed by the titles with `as_target_tokenizer()`. - -With `preprocess_function()`, it is then a simple matter to tokenize the whole corpus using the handy `Dataset.map()` function we've used extensively throughout this course: - -```python -tokenized_datasets = books_dataset.map(preprocess_function, batched=True) -``` - -Now that the corpus has been preprocessed, let's take a look at some metrics that are commonly used for summarization. As we'll see, there is no silver bullet when it comes to measuring the quality of machine-generated text. - - - -💡 You may have noticed that we used `batched=True` in our `Dataset.map()` function above. This encodes the examples in batches of 1,000 (the default) and allows you to make use of the multithreading capabilities of the fast tokenizers in 🤗 Transformers. Where possible, try using `batched=True` to get the most out of your preprocessing! - - - - -## Metrics for text summarization - - - -In comparison to most of the other tasks we've covered in this course, measuring the performance of text generation tasks like summarization or translation is not as straightforward. For example, given a review like "I loved reading the Hunger Games", there are multiple valid summaries, like "I loved the Hunger Games" or "Hunger Games is a great read". Clearly, applying some sort of exact match between the generated summary and the label is not a good solution -- even humans would fare poorly under such a metric, because we all have our own writing style. - -For summarization, one of the most commonly used metrics is the [ROUGE score](https://en.wikipedia.org/wiki/ROUGE_(metric)) (short for Recall-Oriented Understudy for Gisting Evaluation). The basic idea behind this metric is to compare a generated summary against a set of reference summaries that are typically created by humans. To make this more precise, suppose we want to compare the following two summaries: - -```python -generated_summary = "I absolutely loved reading the Hunger Games" -reference_summary = "I loved reading the Hunger Games" -``` - -One way to compare them could be to count the number of overlapping words, which in this case would be 6. However, this is a bit crude, so instead ROUGE is based on computing the _precision_ and _recall_ scores for the overlap. - - - -🙋 Don't worry if this is the first time you've heard of precision and recall -- we'll go through some explicit examples together to make it all clear. These metrics are usually encountered in classification tasks, so if you want to understand how precision and recall are defined in that context, we recommend checking out the `scikit-learn` [guides](https://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html). - - - -For ROUGE, recall measures how much of the reference summary is captured by the generated one. If we are just comparing words, recall can be calculated according to the following formula: - -$$ \mathrm{Recall} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, reference\, summary}} $$ - -For our simple example above, this formula gives a perfect recall of 6/6 = 1; i.e., all the words in the reference summary have been produced by the model. This may sound great, but imagine if our generated summary had been "I really really loved reading the Hunger Games all night". This would also have perfect recall, but is arguably a worse summary since it is verbose. To deal with these scenarios we also compute the precision, which in the ROUGE context measures how much of the generated summary was relevant: - -$$ \mathrm{Precision} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, generated\, summary}} $$ - -Applying this to our verbose summary gives a precision of 6/10 = 0.6, which is considerably worse than the precision of 6/7 = 0.86 obtained by our shorter one. In practice, both precision and recall are usually computed, and then the F1-score (the harmonic mean of precision and recall) is reported. We can do this easily in 🤗 Datasets by first installing the `rouge_score` package: - -```py -!pip install rouge_score -``` - -and then loading the ROUGE metric as follows: - -```python -import evaluate - -rouge_score = evaluate.load("rouge") -``` - -Then we can use the `rouge_score.compute()` function to calculate all the metrics at once: - -```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))} -``` - -Whoa, there's a lot of information in that output -- what does it all mean? First, 🤗 Datasets actually computes confidence intervals for precision, recall, and F1-score; these are the `low`, `mid`, and `high` attributes you can see here. Moreover, 🤗 Datasets computes a variety of ROUGE scores which are based on different types of text granularity when comparing the generated and reference summaries. The `rouge1` variant is the overlap of unigrams -- this is just a fancy way of saying the overlap of words and is exactly the metric we've discussed above. To verify this, let's pull out the `mid` value of our scores: - -```python -scores["rouge1"].mid -``` - -```python out -Score(precision=0.86, recall=1.0, fmeasure=0.92) -``` - -Great, the precision and recall numbers match up! Now what about those other ROUGE scores? `rouge2` measures the overlap between bigrams (think the overlap of pairs of words), while `rougeL` and `rougeLsum` measure the longest matching sequences of words by looking for the longest common substrings in the generated and reference summaries. The "sum" in `rougeLsum` refers to the fact that this metric is computed over a whole summary, while `rougeL` is computed as the average over individual sentences. - - - -✏️ **Try it out!** Create your own example of a generated and reference summary and see if the resulting ROUGE scores agree with a manual calculation based on the formulas for precision and recall. For bonus points, split the text into bigrams and compare the precision and recall for the `rouge2` metric. - - - -We'll use these ROUGE scores to track the performance of our model, but before doing that let's do something every good NLP practitioner should do: create a strong, yet simple baseline! - -### Creating a strong baseline - -A common baseline for text summarization is to simply take the first three sentences of an article, often called the _lead-3_ baseline. We could use full stops to track the sentence boundaries, but this will fail on acronyms like "U.S." or "U.N." -- so instead we'll use the `nltk` library, which includes a better algorithm to handle these cases. You can install the package using `pip` as follows: - -```python -!pip install nltk -``` - -and then download the punctuation rules: - -```python -import nltk - -nltk.download("punkt") -``` - -Next, we import the sentence tokenizer from `nltk` and create a simple function to extract the first three sentences in a review. The convention in text summarization is to separate each summary with a newline, so let's also include this and test it on a training example: - -```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.' -``` - -This seems to work, so let's now implement a function that extracts these "summaries" from a dataset and computes the ROUGE scores for the baseline: - -```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"]) -``` - -We can then use this function to compute the ROUGE scores over the validation set and prettify them a bit using 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} -``` - -We can see that the `rouge2` score is significantly lower than the rest; this likely reflects the fact that review titles are typically concise and so the lead-3 baseline is too verbose. Now that we have a good baseline to work from, let's turn our attention toward fine-tuning mT5! - -{#if fw === 'pt'} - -## Fine-tuning mT5 with the `Trainer` API - -Fine-tuning a model for summarization is very similar to the other tasks we've covered in this chapter. The first thing we need to do is load the pretrained model from the `mt5-small` checkpoint. Since summarization is a sequence-to-sequence task, we can load the model with the `AutoModelForSeq2SeqLM` class, which will automatically download and cache the weights: - -```python -from transformers import AutoModelForSeq2SeqLM - -model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) -``` - -{:else} - -## Fine-tuning mT5 with Keras - -Fine-tuning a model for summarization is very similar to the other tasks we've covered in this chapter. The first thing we need to do is load the pretrained model from the `mt5-small` checkpoint. Since summarization is a sequence-to-sequence task, we can load the model with the `TFAutoModelForSeq2SeqLM` class, which will automatically download and cache the weights: - -```python -from transformers import TFAutoModelForSeq2SeqLM - -model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) -``` - -{/if} - - - -💡 If you're wondering why you don't see any warnings about fine-tuning the model on a downstream task, that's because for sequence-to-sequence tasks we keep all the weights of the network. Compare this to our text classification model in [Chapter 3](/course/chapter3), where the head of the pretrained model was replaced with a randomly initialized network. - - - -The next thing we need to do is log in to the Hugging Face Hub. If you're running this code in a notebook, you can do so with the following utility function: - -```python -from huggingface_hub import notebook_login - -notebook_login() -``` - -which will display a widget where you can enter your credentials. Alternatively, you can run this command in your terminal and log in there: - -``` -huggingface-cli login -``` - -{#if fw === 'pt'} - -We'll need to generate summaries in order to compute ROUGE scores during training. Fortunately, 🤗 Transformers provides dedicated `Seq2SeqTrainingArguments` and `Seq2SeqTrainer` classes that can do this for us automatically! To see how this works, let's first define the hyperparameters and other arguments for our experiments: - -```python -from transformers import Seq2SeqTrainingArguments - -batch_size = 8 -num_train_epochs = 8 -# Show the training loss with every epoch -logging_steps = len(tokenized_datasets["train"]) // batch_size -model_name = model_checkpoint.split("/")[-1] - -args = Seq2SeqTrainingArguments( - output_dir=f"{model_name}-finetuned-amazon-en-es", - evaluation_strategy="epoch", - learning_rate=5.6e-5, - per_device_train_batch_size=batch_size, - per_device_eval_batch_size=batch_size, - weight_decay=0.01, - save_total_limit=3, - num_train_epochs=num_train_epochs, - predict_with_generate=True, - logging_steps=logging_steps, - push_to_hub=True, -) -``` - -Here, the `predict_with_generate` argument has been set to indicate that we should generate summaries during evaluation so that we can compute ROUGE scores for each epoch. As discussed in [Chapter 1](/course/chapter1), the decoder performs inference by predicting tokens one by one, and this is implemented by the model's `generate()` method. Setting `predict_with_generate=True` tells the `Seq2SeqTrainer` to use that method for evaluation. We've also adjusted some of the default hyperparameters, like the learning rate, number of epochs, and weight decay, and we've set the `save_total_limit` option to only save up to 3 checkpoints during training -- this is because even the "small" version of mT5 uses around a GB of hard drive space, and we can save a bit of room by limiting the number of copies we save. - -The `push_to_hub=True` argument will allow us to push the model to the Hub after training; you'll find the repository under your user profile in the location defined by `output_dir`. Note that you can specify the name of the repository you want to push to with the `hub_model_id` argument (in particular, you will have to use this argument to push to an organization). For instance, when we pushed the model to the [`huggingface-course` organization](https://huggingface.co/huggingface-course), we added `hub_model_id="huggingface-course/mt5-finetuned-amazon-en-es"` to `Seq2SeqTrainingArguments`. - -The next thing we need to do is provide the trainer with a `compute_metrics()` function so that we can evaluate our model during training. For summarization this is a bit more involved than simply calling `rouge_score.compute()` on the model's predictions, since we need to _decode_ the outputs and labels into text before we can compute the ROUGE scores. The following function does exactly that, and also makes use of the `sent_tokenize()` function from `nltk` to separate the summary sentences with newlines: - -```python -import numpy as np - - -def compute_metrics(eval_pred): - predictions, labels = eval_pred - # Decode generated summaries into text - decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) - # Replace -100 in the labels as we can't decode them - labels = np.where(labels != -100, labels, tokenizer.pad_token_id) - # Decode reference summaries into text - decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) - # ROUGE expects a newline after each sentence - decoded_preds = ["\n".join(sent_tokenize(pred.strip())) for pred in decoded_preds] - decoded_labels = ["\n".join(sent_tokenize(label.strip())) for label in decoded_labels] - # Compute ROUGE scores - result = rouge_score.compute( - predictions=decoded_preds, references=decoded_labels, use_stemmer=True - ) - # Extract the median scores - result = {key: value.mid.fmeasure * 100 for key, value in result.items()} - return {k: round(v, 4) for k, v in result.items()} -``` - -{/if} - -Next, we need to define a data collator for our sequence-to-sequence task. Since mT5 is an encoder-decoder Transformer model, one subtlety with preparing our batches is that during decoding we need to shift the labels to the right by one. This is required to ensure that the decoder only sees the previous ground truth labels and not the current or future ones, which would be easy for the model to memorize. This is similar to how masked self-attention is applied to the inputs in a task like [causal language modeling](/course/chapter7/6). - -Luckily, 🤗 Transformers provides a `DataCollatorForSeq2Seq` collator that will dynamically pad the inputs and the labels for us. To instantiate this collator, we simply need to provide the `tokenizer` and `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} - -Let's see what this collator produces when fed a small batch of examples. First, we need to remove the columns with strings because the collator won't know how to pad these elements: - -```python -tokenized_datasets = tokenized_datasets.remove_columns( - books_dataset["train"].column_names -) -``` - -Since the collator expects a list of `dict`s, where each `dict` represents a single example in the dataset, we also need to wrangle the data into the expected format before passing it to the data collator: - -```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]])} -``` - -The main thing to notice here is that the first example is longer than the second one, so the `input_ids` and `attention_mask` of the second example have been padded on the right with a `[PAD]` token (whose ID is `0`). Similarly, we can see that the `labels` have been padded with `-100`s, to make sure the padding tokens are ignored by the loss function. And finally, we can see a new `decoder_input_ids` which has shifted the labels to the right by inserting a `[PAD]` token in the first entry. - -{#if fw === 'pt'} - -We finally have all the ingredients we need to train with! We now simply need to instantiate the trainer with the standard arguments: - -```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, -) -``` - -and launch our training run: - -```python -trainer.train() -``` - -During training, you should see the training loss decrease and the ROUGE scores increase with each epoch. Once the training is complete, you can see the final ROUGE scores by running `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} -``` - -From the scores we can see that our model has handily outperformed our lead-3 baseline -- nice! The final thing to do is push the model weights to the Hub, as follows: - -``` -trainer.push_to_hub(commit_message="Training complete", tags="summarization") -``` - -```python out -'https://huggingface.co/huggingface-course/mt5-finetuned-amazon-en-es/commit/aa0536b829b28e73e1e4b94b8a5aacec420d40e0' -``` - -This will save the checkpoint and configuration files to `output_dir`, before uploading all the files to the Hub. By specifying the `tags` argument, we also ensure that the widget on the Hub will be one for a summarization pipeline instead of the default text generation one associated with the mT5 architecture (for more information about model tags, see the [🤗 Hub documentation](https://huggingface.co/docs/hub/main#how-is-a-models-type-of-inference-api-and-widget-determined)). The output from `trainer.push_to_hub()` is a URL to the Git commit hash, so you can easily see the changes that were made to the model repository! - -To wrap up this section, let's take a look at how we can also fine-tune mT5 using the low-level features provided by 🤗 Accelerate. - -{:else} - -We're almost ready to train! We just need to convert our datasets to `tf.data.Dataset`s using the data collator we defined above, and then `compile()` and `fit()` the model. First, the datasets: - -```python -tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=True, - batch_size=8, -) -tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=False, - batch_size=8, -) -``` - -Now, we define our training hyperparameters and compile: - -```python -from transformers import create_optimizer -import tensorflow as tf - -# 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_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) - -# Train in mixed-precision float16 -tf.keras.mixed_precision.set_global_policy("mixed_float16") -``` - -And finally, we fit the model. We use a `PushToHubCallback` to save the model to the Hub after each epoch, which will allow us to use it for inference later: - -```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 -) -``` - -We got some loss values during training, but really we'd like to see the ROUGE metrics we computed earlier. To get those metrics, we'll need to generate outputs from the model and convert them to strings. Let's build some lists of labels and predictions for the ROUGE metric to compare (note that if you get import errors for this section, you may need to`!pip install tqdm`): - -```python -from tqdm import tqdm -import numpy as np - -all_preds = [] -all_labels = [] -for batch in tqdm(tf_eval_dataset): - predictions = model.generate(**batch) - decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) - labels = batch["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) -``` - -Once we have our lists of label and prediction strings, computing the ROUGE score is easy: - -```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'} - -## Fine-tuning mT5 with 🤗 Accelerate - -Fine-tuning our model with 🤗 Accelerate is very similar to the text classification example we encountered in [Chapter 3](/course/chapter3). The main differences will be the need to explicitly generate our summaries during training and define how we compute the ROUGE scores (recall that the `Seq2SeqTrainer` took care of the generation for us). Let's take a look how we can implement these two requirements within 🤗 Accelerate! - -### Preparing everything for training - -The first thing we need to do is create a `DataLoader` for each of our splits. Since the PyTorch dataloaders expect batches of tensors, we need to set the format to `"torch"` in our datasets: - -```python -tokenized_datasets.set_format("torch") -``` - -Now that we've got datasets consisting of just tensors, the next thing to do is instantiate the `DataCollatorForSeq2Seq` again. For this we need to provide a fresh version of the model, so let's load it again from our cache: - -```python -model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) -``` - -We can then instantiate the data collator and use this to define our dataloaders: - -```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 -) -``` - -The next thing to do is define the optimizer we want to use. As in our other examples, we'll use `AdamW`, which works well for most problems: - -```python -from torch.optim import AdamW - -optimizer = AdamW(model.parameters(), lr=2e-5) -``` - -Finally, we feed our model, optimizer, and dataloaders to the `accelerator.prepare()` method: - -```python -from accelerate import Accelerator - -accelerator = Accelerator() -model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( - model, optimizer, train_dataloader, eval_dataloader -) -``` - - - -🚨 If you're training on a TPU, you'll need to move all the code above into a dedicated training function. See [Chapter 3](/course/chapter3) for more details. - - - -Now that we've prepared our objects, there are three remaining things to do: - -* Define the learning rate schedule. -* Implement a function to post-process the summaries for evaluation. -* Create a repository on the Hub that we can push our model to. - -For the learning rate schedule, we'll use the standard linear one from previous sections: - -```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, -) -``` - -For post-processing, we need a function that splits the generated summaries into sentences that are separated by newlines. This is the format the ROUGE metric expects, and we can achieve this with the following snippet of code: - -```python -def postprocess_text(preds, labels): - preds = [pred.strip() for pred in preds] - labels = [label.strip() for label in labels] - - # ROUGE expects a newline after each sentence - preds = ["\n".join(nltk.sent_tokenize(pred)) for pred in preds] - labels = ["\n".join(nltk.sent_tokenize(label)) for label in labels] - - return preds, labels -``` - -This should look familiar to you if you recall how we defined the `compute_metrics()` function of the `Seq2SeqTrainer`. - -Finally, we need to create a model repository on the Hugging Face Hub. For this, we can use the appropriately titled 🤗 Hub library. We just need to define a name for our repository, and the library has a utility function to combine the repository ID with the user profile: - -```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' -``` - -Now we can use this repository name to clone a local version to our results directory that will store the training artifacts: - -```python -from huggingface_hub import Repository - -output_dir = "results-mt5-finetuned-squad-accelerate" -repo = Repository(output_dir, clone_from=repo_name) -``` - -This will allow us to push the artifacts back to the Hub by calling the `repo.push_to_hub()` method during training! Let's now wrap up our analysis by writing out the training loop. - -### Training loop - -The training loop for summarization is quite similar to the other 🤗 Accelerate examples that we've encountered and is roughly split into four main steps: - -1. Train the model by iterating over all the examples in `train_dataloader` for each epoch. -2. Generate model summaries at the end of each epoch, by first generating the tokens and then decoding them (and the reference summaries) into text. -3. Compute the ROUGE scores using the same techniques we saw earlier. -4. Save the checkpoints and push everything to the Hub. Here we rely on the nifty `blocking=False` argument of the `Repository` object so that we can push the checkpoints per epoch _asynchronously_. This allows us to continue training without having to wait for the somewhat slow upload associated with a GB-sized model! - -These steps can be seen in the following block of code: - -```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): - # Training - model.train() - for step, batch in enumerate(train_dataloader): - outputs = model(**batch) - loss = outputs.loss - accelerator.backward(loss) - - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - progress_bar.update(1) - - # Evaluation - model.eval() - for step, batch in enumerate(eval_dataloader): - with torch.no_grad(): - generated_tokens = accelerator.unwrap_model(model).generate( - batch["input_ids"], - attention_mask=batch["attention_mask"], - ) - - generated_tokens = accelerator.pad_across_processes( - generated_tokens, dim=1, pad_index=tokenizer.pad_token_id - ) - labels = batch["labels"] - - # If we did not pad to max length, we need to pad the labels too - labels = accelerator.pad_across_processes( - batch["labels"], dim=1, pad_index=tokenizer.pad_token_id - ) - - generated_tokens = accelerator.gather(generated_tokens).cpu().numpy() - labels = accelerator.gather(labels).cpu().numpy() - - # Replace -100 in the labels as we can't decode them - labels = np.where(labels != -100, labels, tokenizer.pad_token_id) - if isinstance(generated_tokens, tuple): - generated_tokens = generated_tokens[0] - decoded_preds = tokenizer.batch_decode( - generated_tokens, skip_special_tokens=True - ) - decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) - - decoded_preds, decoded_labels = postprocess_text( - decoded_preds, decoded_labels - ) - - rouge_score.add_batch(predictions=decoded_preds, references=decoded_labels) - - # Compute metrics - result = rouge_score.compute() - # Extract the median ROUGE scores - result = {key: value.mid.fmeasure * 100 for key, value in result.items()} - result = {k: round(v, 4) for k, v in result.items()} - print(f"Epoch {epoch}:", result) - - # Save and upload - accelerator.wait_for_everyone() - unwrapped_model = accelerator.unwrap_model(model) - unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) - if accelerator.is_main_process: - tokenizer.save_pretrained(output_dir) - repo.push_to_hub( - commit_message=f"Training in progress epoch {epoch}", blocking=False - ) -``` - -```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} -``` - -And that's it! Once you run this, you'll have a model and results that are pretty similar to the ones we obtained with the `Trainer`. - -{/if} - -## Using your fine-tuned model - -Once you've pushed the model to the Hub, you can play with it either via the inference widget or with a `pipeline` object, as follows: - -```python -from transformers import pipeline - -hub_model_id = "huggingface-course/mt5-small-finetuned-amazon-en-es" -summarizer = pipeline("summarization", model=hub_model_id) -``` - -We can feed some examples from the test set (which the model has not seen) to our pipeline to get a feel for the quality of the summaries. First let's implement a simple function to show the review, title, and generated summary together: - -```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}'") -``` - -Let's take a look at one of the English examples we get: - -```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' -``` - -This is not too bad! We can see that our model has actually been able to perform _abstractive_ summarization by augmenting parts of the review with new words. And perhaps the coolest aspect of our model is that it is bilingual, so we can also generate summaries of Spanish reviews: - -```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' -``` - -The summary translates into "Very easy to read" in English, which we can see in this case was extracted directly from the review. Nevertheless, this shows the versatility of the mT5 model and has given you a taste of what it's like to deal with a multilingual corpus! - -Next, we'll turn our attention to a slightly more complex task: training a language model from scratch. + + +# Summarization + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + +In this section we'll take a look at how Transformer models can be used to condense long documents into summaries, a task known as _text summarization_. This is one of the most challenging NLP tasks as it requires a range of abilities, such as understanding long passages and generating coherent text that captures the main topics in a document. However, when done well, text summarization is a powerful tool that can speed up various business processes by relieving the burden of domain experts to read long documents in detail. + + + +Although there already exist various fine-tuned models for summarization on the [Hugging Face Hub](https://huggingface.co/models?pipeline_tag=summarization&sort=downloads), almost all of these are only suitable for English documents. So, to add a twist in this section, we'll train a bilingual model for English and Spanish. By the end of this section, you'll have a [model](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es) that can summarize customer reviews like the one shown here: + + + +As we'll see, these summaries are concise because they're learned from the titles that customers provide in their product reviews. Let's start by putting together a suitable bilingual corpus for this task. + +## Preparing a multilingual corpus + +We'll use the [Multilingual Amazon Reviews Corpus](https://huggingface.co/datasets/amazon_reviews_multi) to create our bilingual summarizer. This corpus consists of Amazon product reviews in six languages and is typically used to benchmark multilingual classifiers. However, since each review is accompanied by a short title, we can use the titles as the target summaries for our model to learn from! To get started, let's download the English and Spanish subsets from the 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 + }) +}) +``` + +As you can see, for each language there are 200,000 reviews for the `train` split, and 5,000 reviews for each of the `validation` and `test` splits. The review information we are interested in is contained in the `review_body` and `review_title` columns. Let's take a look at a few examples by creating a simple function that takes a random sample from the training set with the techniques we learned in [Chapter 5](/course/chapter5): + +```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.' +``` + + + +✏️ **Try it out!** Change the random seed in the `Dataset.shuffle()` command to explore other reviews in the corpus. If you're a Spanish speaker, take a look at some of the reviews in `spanish_dataset` to see if the titles also seem like reasonable summaries. + + + +This sample shows the diversity of reviews one typically finds online, ranging from positive to negative (and everything in between!). Although the example with the "meh" title is not very informative, the other titles look like decent summaries of the reviews themselves. Training a summarization model on all 400,000 reviews would take far too long on a single GPU, so instead we'll focus on generating summaries for a single domain of products. To get a feel for what domains we can choose from, let's convert `english_dataset` to a `pandas.DataFrame` and compute the number of reviews per product category: + +```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 +``` + +The most popular products in the English dataset are about household items, clothing, and wireless electronics. To stick with the Amazon theme, though, let's focus on summarizing book reviews -- after all, this is what the company was founded on! We can see two product categories that fit the bill (`book` and `digital_ebook_purchase`), so let's filter the datasets in both languages for just these products. As we saw in [Chapter 5](/course/chapter5), the `Dataset.filter()` function allows us to slice a dataset very efficiently, so we can define a simple function to do this: + +```python +def filter_books(example): + return ( + example["product_category"] == "book" + or example["product_category"] == "digital_ebook_purchase" + ) +``` + +Now when we apply this function to `english_dataset` and `spanish_dataset`, the result will contain just those rows involving the book categories. Before applying the filter, let's switch the format of `english_dataset` from `"pandas"` back to `"arrow"`: + +```python +english_dataset.reset_format() +``` + +We can then apply the filter function, and as a sanity check let's inspect a sample of reviews to see if they are indeed about books: + +```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.' +``` + +Okay, we can see that the reviews are not strictly about books and might refer to things like calendars and electronic applications such as OneNote. Nevertheless, the domain seems about right to train a summarization model on. Before we look at various models that are suitable for this task, we have one last bit of data preparation to do: combining the English and Spanish reviews as a single `DatasetDict` object. 🤗 Datasets provides a handy `concatenate_datasets()` function that (as the name suggests) will stack two `Dataset` objects on top of each other. So, to create our bilingual dataset, we'll loop over each split, concatenate the datasets for that split, and shuffle the result to ensure our model doesn't overfit to a single language: + +```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) + +# Peek at a few examples +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' +``` + +This certainly looks like a mix of English and Spanish reviews! Now that we have a training corpus, one final thing to check is the distribution of words in the reviews and their titles. This is especially important for summarization tasks, where short reference summaries in the data can bias the model to only output one or two words in the generated summaries. The plots below show the word distributions, and we can see that the titles are heavily skewed toward just 1-2 words: + +
+Word count distributions for the review titles and texts. + +
+ +To deal with this, we'll filter out the examples with very short titles so that our model can produce more interesting summaries. Since we're dealing with English and Spanish texts, we can use a rough heuristic to split the titles on whitespace and then use our trusty `Dataset.filter()` method as follows: + +```python +books_dataset = books_dataset.filter(lambda x: len(x["review_title"].split()) > 2) +``` + +Now that we've prepared our corpus, let's take a look at a few possible Transformer models that one might fine-tune on it! + +## Models for text summarization + +If you think about it, text summarization is a similar sort of task to machine translation: we have a body of text like a review that we'd like to "translate" into a shorter version that captures the salient features of the input. Accordingly, most Transformer models for summarization adopt the encoder-decoder architecture that we first encountered in [Chapter 1](/course/chapter1), although there are some exceptions like the GPT family of models which can also be used for summarization in few-shot settings. The following table lists some popular pretrained models that can be fine-tuned for summarization. + +| Transformer model | Description | Multilingual? | +| :---------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------: | +| [GPT-2](https://huggingface.co/gpt2-xl) | Although trained as an auto-regressive language model, you can make GPT-2 generate summaries by appending "TL;DR" at the end of the input text. | ❌ | +| [PEGASUS](https://huggingface.co/google/pegasus-large) | Uses a pretraining objective to predict masked sentences in multi-sentence texts. This pretraining objective is closer to summarization than vanilla language modeling and scores highly on popular benchmarks. | ❌ | +| [T5](https://huggingface.co/t5-base) | A universal Transformer architecture that formulates all tasks in a text-to-text framework; e.g., the input format for the model to summarize a document is `summarize: ARTICLE`. | ❌ | +| [mT5](https://huggingface.co/google/mt5-base) | A multilingual version of T5, pretrained on the multilingual Common Crawl corpus (mC4), covering 101 languages. | ✅ | +| [BART](https://huggingface.co/facebook/bart-base) | A novel Transformer architecture with both an encoder and a decoder stack trained to reconstruct corrupted input that combines the pretraining schemes of BERT and GPT-2. | ❌ | +| [mBART-50](https://huggingface.co/facebook/mbart-large-50) | A multilingual version of BART, pretrained on 50 languages. | ✅ | + +As you can see from this table, the majority of Transformer models for summarization (and indeed most NLP tasks) are monolingual. This is great if your task is in a "high-resource" language like English or German, but less so for the thousands of other languages in use across the world. Fortunately, there is a class of multilingual Transformer models, like mT5 and mBART, that come to the rescue. These models are pretrained using language modeling, but with a twist: instead of training on a corpus of one language, they are trained jointly on texts in over 50 languages at once! + +We'll focus on mT5, an interesting architecture based on T5 that was pretrained in a text-to-text framework. In T5, every NLP task is formulated in terms of a prompt prefix like `summarize:` which conditions the model to adapt the generated text to the prompt. As shown in the figure below, this makes T5 extremely versatile, as you can solve many tasks with a single model! + +
+Different tasks performed by the T5 architecture. + +
+ +mT5 doesn't use prefixes, but shares much of the versatility of T5 and has the advantage of being multilingual. Now that we've picked a model, let's take a look at preparing our data for training. + + + + +✏️ **Try it out!** Once you've worked through this section, see how well mT5 compares to mBART by fine-tuning the latter with the same techniques. For bonus points, you can also try fine-tuning T5 on just the English reviews. Since T5 has a special prefix prompt, you'll need to prepend `summarize:` to the input examples in the preprocessing steps below. + + + +## Preprocessing the data + + + +Our next task is to tokenize and encode our reviews and their titles. As usual, we begin by loading the tokenizer associated with the pretrained model checkpoint. We'll use `mt5-small` as our checkpoint so we can fine-tune the model in a reasonable amount of time: + +```python +from transformers import AutoTokenizer + +model_checkpoint = "google/mt5-small" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + + + +💡 In the early stages of your NLP projects, a good practice is to train a class of "small" models on a small sample of data. This allows you to debug and iterate faster toward an end-to-end workflow. Once you are confident in the results, you can always scale up the model by simply changing the model checkpoint! + + + +Let's test out the mT5 tokenizer on a small example: + +```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]} +``` + +Here we can see the familiar `input_ids` and `attention_mask` that we encountered in our first fine-tuning experiments back in [Chapter 3](/course/chapter3). Let's decode these input IDs with the tokenizer's `convert_ids_to_tokens()` function to see what kind of tokenizer we're dealing with: + +```python +tokenizer.convert_ids_to_tokens(inputs.input_ids) +``` + +```python out +['▁I', '▁', 'loved', '▁reading', '▁the', '▁Hung', 'er', '▁Games', ''] +``` + +The special Unicode character `▁` and end-of-sequence token `` indicate that we're dealing with the SentencePiece tokenizer, which is based on the Unigram segmentation algorithm discussed in [Chapter 6](/course/chapter6). Unigram is especially useful for multilingual corpora since it allows SentencePiece to be agnostic about accents, punctuation, and the fact that many languages, like Japanese, do not have whitespace characters. + +To tokenize our corpus, we have to deal with a subtlety associated with summarization: because our labels are also text, it is possible that they exceed the model's maximum context size. This means we need to apply truncation to both the reviews and their titles to ensure we don't pass excessively long inputs to our model. The tokenizers in 🤗 Transformers provide a nifty `as_target_tokenizer()` function that allows you to tokenize the labels in parallel to the inputs. This is typically done using a context manager inside a preprocessing function that first encodes the inputs, and then encodes the labels as a separate column. Here is an example of such a function for 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 + ) + # Set up the tokenizer for targets + with tokenizer.as_target_tokenizer(): + labels = tokenizer( + examples["review_title"], max_length=max_target_length, truncation=True + ) + + model_inputs["labels"] = labels["input_ids"] + return model_inputs +``` + +Let's walk through this code to understand what's happening. The first thing we've done is define values for `max_input_length` and `max_target_length`, which set the upper limits for how long our reviews and titles can be. Since the review body is typically much larger than the title, we've scaled these values accordingly. Then, in the `preprocess_function()` itself we can see the reviews are first tokenized, followed by the titles with `as_target_tokenizer()`. + +With `preprocess_function()`, it is then a simple matter to tokenize the whole corpus using the handy `Dataset.map()` function we've used extensively throughout this course: + +```python +tokenized_datasets = books_dataset.map(preprocess_function, batched=True) +``` + +Now that the corpus has been preprocessed, let's take a look at some metrics that are commonly used for summarization. As we'll see, there is no silver bullet when it comes to measuring the quality of machine-generated text. + + + +💡 You may have noticed that we used `batched=True` in our `Dataset.map()` function above. This encodes the examples in batches of 1,000 (the default) and allows you to make use of the multithreading capabilities of the fast tokenizers in 🤗 Transformers. Where possible, try using `batched=True` to get the most out of your preprocessing! + + + + +## Metrics for text summarization + + + +In comparison to most of the other tasks we've covered in this course, measuring the performance of text generation tasks like summarization or translation is not as straightforward. For example, given a review like "I loved reading the Hunger Games", there are multiple valid summaries, like "I loved the Hunger Games" or "Hunger Games is a great read". Clearly, applying some sort of exact match between the generated summary and the label is not a good solution -- even humans would fare poorly under such a metric, because we all have our own writing style. + +For summarization, one of the most commonly used metrics is the [ROUGE score](https://en.wikipedia.org/wiki/ROUGE_(metric)) (short for Recall-Oriented Understudy for Gisting Evaluation). The basic idea behind this metric is to compare a generated summary against a set of reference summaries that are typically created by humans. To make this more precise, suppose we want to compare the following two summaries: + +```python +generated_summary = "I absolutely loved reading the Hunger Games" +reference_summary = "I loved reading the Hunger Games" +``` + +One way to compare them could be to count the number of overlapping words, which in this case would be 6. However, this is a bit crude, so instead ROUGE is based on computing the _precision_ and _recall_ scores for the overlap. + + + +🙋 Don't worry if this is the first time you've heard of precision and recall -- we'll go through some explicit examples together to make it all clear. These metrics are usually encountered in classification tasks, so if you want to understand how precision and recall are defined in that context, we recommend checking out the `scikit-learn` [guides](https://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html). + + + +For ROUGE, recall measures how much of the reference summary is captured by the generated one. If we are just comparing words, recall can be calculated according to the following formula: + +$$ \mathrm{Recall} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, reference\, summary}} $$ + +For our simple example above, this formula gives a perfect recall of 6/6 = 1; i.e., all the words in the reference summary have been produced by the model. This may sound great, but imagine if our generated summary had been "I really really loved reading the Hunger Games all night". This would also have perfect recall, but is arguably a worse summary since it is verbose. To deal with these scenarios we also compute the precision, which in the ROUGE context measures how much of the generated summary was relevant: + +$$ \mathrm{Precision} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, generated\, summary}} $$ + +Applying this to our verbose summary gives a precision of 6/10 = 0.6, which is considerably worse than the precision of 6/7 = 0.86 obtained by our shorter one. In practice, both precision and recall are usually computed, and then the F1-score (the harmonic mean of precision and recall) is reported. We can do this easily in 🤗 Datasets by first installing the `rouge_score` package: + +```py +!pip install rouge_score +``` + +and then loading the ROUGE metric as follows: + +```python +import evaluate + +rouge_score = evaluate.load("rouge") +``` + +Then we can use the `rouge_score.compute()` function to calculate all the metrics at once: + +```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))} +``` + +Whoa, there's a lot of information in that output -- what does it all mean? First, 🤗 Datasets actually computes confidence intervals for precision, recall, and F1-score; these are the `low`, `mid`, and `high` attributes you can see here. Moreover, 🤗 Datasets computes a variety of ROUGE scores which are based on different types of text granularity when comparing the generated and reference summaries. The `rouge1` variant is the overlap of unigrams -- this is just a fancy way of saying the overlap of words and is exactly the metric we've discussed above. To verify this, let's pull out the `mid` value of our scores: + +```python +scores["rouge1"].mid +``` + +```python out +Score(precision=0.86, recall=1.0, fmeasure=0.92) +``` + +Great, the precision and recall numbers match up! Now what about those other ROUGE scores? `rouge2` measures the overlap between bigrams (think the overlap of pairs of words), while `rougeL` and `rougeLsum` measure the longest matching sequences of words by looking for the longest common substrings in the generated and reference summaries. The "sum" in `rougeLsum` refers to the fact that this metric is computed over a whole summary, while `rougeL` is computed as the average over individual sentences. + + + +✏️ **Try it out!** Create your own example of a generated and reference summary and see if the resulting ROUGE scores agree with a manual calculation based on the formulas for precision and recall. For bonus points, split the text into bigrams and compare the precision and recall for the `rouge2` metric. + + + +We'll use these ROUGE scores to track the performance of our model, but before doing that let's do something every good NLP practitioner should do: create a strong, yet simple baseline! + +### Creating a strong baseline + +A common baseline for text summarization is to simply take the first three sentences of an article, often called the _lead-3_ baseline. We could use full stops to track the sentence boundaries, but this will fail on acronyms like "U.S." or "U.N." -- so instead we'll use the `nltk` library, which includes a better algorithm to handle these cases. You can install the package using `pip` as follows: + +```python +!pip install nltk +``` + +and then download the punctuation rules: + +```python +import nltk + +nltk.download("punkt") +``` + +Next, we import the sentence tokenizer from `nltk` and create a simple function to extract the first three sentences in a review. The convention in text summarization is to separate each summary with a newline, so let's also include this and test it on a training example: + +```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.' +``` + +This seems to work, so let's now implement a function that extracts these "summaries" from a dataset and computes the ROUGE scores for the baseline: + +```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"]) +``` + +We can then use this function to compute the ROUGE scores over the validation set and prettify them a bit using 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} +``` + +We can see that the `rouge2` score is significantly lower than the rest; this likely reflects the fact that review titles are typically concise and so the lead-3 baseline is too verbose. Now that we have a good baseline to work from, let's turn our attention toward fine-tuning mT5! + +{#if fw === 'pt'} + +## Fine-tuning mT5 with the `Trainer` API + +Fine-tuning a model for summarization is very similar to the other tasks we've covered in this chapter. The first thing we need to do is load the pretrained model from the `mt5-small` checkpoint. Since summarization is a sequence-to-sequence task, we can load the model with the `AutoModelForSeq2SeqLM` class, which will automatically download and cache the weights: + +```python +from transformers import AutoModelForSeq2SeqLM + +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{:else} + +## Fine-tuning mT5 with Keras + +Fine-tuning a model for summarization is very similar to the other tasks we've covered in this chapter. The first thing we need to do is load the pretrained model from the `mt5-small` checkpoint. Since summarization is a sequence-to-sequence task, we can load the model with the `TFAutoModelForSeq2SeqLM` class, which will automatically download and cache the weights: + +```python +from transformers import TFAutoModelForSeq2SeqLM + +model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{/if} + + + +💡 If you're wondering why you don't see any warnings about fine-tuning the model on a downstream task, that's because for sequence-to-sequence tasks we keep all the weights of the network. Compare this to our text classification model in [Chapter 3](/course/chapter3), where the head of the pretrained model was replaced with a randomly initialized network. + + + +The next thing we need to do is log in to the Hugging Face Hub. If you're running this code in a notebook, you can do so with the following utility function: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +which will display a widget where you can enter your credentials. Alternatively, you can run this command in your terminal and log in there: + +``` +huggingface-cli login +``` + +{#if fw === 'pt'} + +We'll need to generate summaries in order to compute ROUGE scores during training. Fortunately, 🤗 Transformers provides dedicated `Seq2SeqTrainingArguments` and `Seq2SeqTrainer` classes that can do this for us automatically! To see how this works, let's first define the hyperparameters and other arguments for our experiments: + +```python +from transformers import Seq2SeqTrainingArguments + +batch_size = 8 +num_train_epochs = 8 +# Show the training loss with every epoch +logging_steps = len(tokenized_datasets["train"]) // batch_size +model_name = model_checkpoint.split("/")[-1] + +args = Seq2SeqTrainingArguments( + output_dir=f"{model_name}-finetuned-amazon-en-es", + evaluation_strategy="epoch", + learning_rate=5.6e-5, + per_device_train_batch_size=batch_size, + per_device_eval_batch_size=batch_size, + weight_decay=0.01, + save_total_limit=3, + num_train_epochs=num_train_epochs, + predict_with_generate=True, + logging_steps=logging_steps, + push_to_hub=True, +) +``` + +Here, the `predict_with_generate` argument has been set to indicate that we should generate summaries during evaluation so that we can compute ROUGE scores for each epoch. As discussed in [Chapter 1](/course/chapter1), the decoder performs inference by predicting tokens one by one, and this is implemented by the model's `generate()` method. Setting `predict_with_generate=True` tells the `Seq2SeqTrainer` to use that method for evaluation. We've also adjusted some of the default hyperparameters, like the learning rate, number of epochs, and weight decay, and we've set the `save_total_limit` option to only save up to 3 checkpoints during training -- this is because even the "small" version of mT5 uses around a GB of hard drive space, and we can save a bit of room by limiting the number of copies we save. + +The `push_to_hub=True` argument will allow us to push the model to the Hub after training; you'll find the repository under your user profile in the location defined by `output_dir`. Note that you can specify the name of the repository you want to push to with the `hub_model_id` argument (in particular, you will have to use this argument to push to an organization). For instance, when we pushed the model to the [`huggingface-course` organization](https://huggingface.co/huggingface-course), we added `hub_model_id="huggingface-course/mt5-finetuned-amazon-en-es"` to `Seq2SeqTrainingArguments`. + +The next thing we need to do is provide the trainer with a `compute_metrics()` function so that we can evaluate our model during training. For summarization this is a bit more involved than simply calling `rouge_score.compute()` on the model's predictions, since we need to _decode_ the outputs and labels into text before we can compute the ROUGE scores. The following function does exactly that, and also makes use of the `sent_tokenize()` function from `nltk` to separate the summary sentences with newlines: + +```python +import numpy as np + + +def compute_metrics(eval_pred): + predictions, labels = eval_pred + # Decode generated summaries into text + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + # Replace -100 in the labels as we can't decode them + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + # Decode reference summaries into text + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + # ROUGE expects a newline after each sentence + decoded_preds = ["\n".join(sent_tokenize(pred.strip())) for pred in decoded_preds] + decoded_labels = ["\n".join(sent_tokenize(label.strip())) for label in decoded_labels] + # Compute ROUGE scores + result = rouge_score.compute( + predictions=decoded_preds, references=decoded_labels, use_stemmer=True + ) + # Extract the median scores + result = {key: value.mid.fmeasure * 100 for key, value in result.items()} + return {k: round(v, 4) for k, v in result.items()} +``` + +{/if} + +Next, we need to define a data collator for our sequence-to-sequence task. Since mT5 is an encoder-decoder Transformer model, one subtlety with preparing our batches is that during decoding we need to shift the labels to the right by one. This is required to ensure that the decoder only sees the previous ground truth labels and not the current or future ones, which would be easy for the model to memorize. This is similar to how masked self-attention is applied to the inputs in a task like [causal language modeling](/course/chapter7/6). + +Luckily, 🤗 Transformers provides a `DataCollatorForSeq2Seq` collator that will dynamically pad the inputs and the labels for us. To instantiate this collator, we simply need to provide the `tokenizer` and `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} + +Let's see what this collator produces when fed a small batch of examples. First, we need to remove the columns with strings because the collator won't know how to pad these elements: + +```python +tokenized_datasets = tokenized_datasets.remove_columns( + books_dataset["train"].column_names +) +``` + +Since the collator expects a list of `dict`s, where each `dict` represents a single example in the dataset, we also need to wrangle the data into the expected format before passing it to the data collator: + +```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]])} +``` + +The main thing to notice here is that the first example is longer than the second one, so the `input_ids` and `attention_mask` of the second example have been padded on the right with a `[PAD]` token (whose ID is `0`). Similarly, we can see that the `labels` have been padded with `-100`s, to make sure the padding tokens are ignored by the loss function. And finally, we can see a new `decoder_input_ids` which has shifted the labels to the right by inserting a `[PAD]` token in the first entry. + +{#if fw === 'pt'} + +We finally have all the ingredients we need to train with! We now simply need to instantiate the trainer with the standard arguments: + +```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, +) +``` + +and launch our training run: + +```python +trainer.train() +``` + +During training, you should see the training loss decrease and the ROUGE scores increase with each epoch. Once the training is complete, you can see the final ROUGE scores by running `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} +``` + +From the scores we can see that our model has handily outperformed our lead-3 baseline -- nice! The final thing to do is push the model weights to the Hub, as follows: + +``` +trainer.push_to_hub(commit_message="Training complete", tags="summarization") +``` + +```python out +'https://huggingface.co/huggingface-course/mt5-finetuned-amazon-en-es/commit/aa0536b829b28e73e1e4b94b8a5aacec420d40e0' +``` + +This will save the checkpoint and configuration files to `output_dir`, before uploading all the files to the Hub. By specifying the `tags` argument, we also ensure that the widget on the Hub will be one for a summarization pipeline instead of the default text generation one associated with the mT5 architecture (for more information about model tags, see the [🤗 Hub documentation](https://huggingface.co/docs/hub/main#how-is-a-models-type-of-inference-api-and-widget-determined)). The output from `trainer.push_to_hub()` is a URL to the Git commit hash, so you can easily see the changes that were made to the model repository! + +To wrap up this section, let's take a look at how we can also fine-tune mT5 using the low-level features provided by 🤗 Accelerate. + +{:else} + +We're almost ready to train! We just need to convert our datasets to `tf.data.Dataset`s using the data collator we defined above, and then `compile()` and `fit()` the model. First, the datasets: + +```python +tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=8, +) +tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=8, +) +``` + +Now, we define our training hyperparameters and compile: + +```python +from transformers import create_optimizer +import tensorflow as tf + +# 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_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) + +# Train in mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +And finally, we fit the model. We use a `PushToHubCallback` to save the model to the Hub after each epoch, which will allow us to use it for inference later: + +```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 +) +``` + +We got some loss values during training, but really we'd like to see the ROUGE metrics we computed earlier. To get those metrics, we'll need to generate outputs from the model and convert them to strings. Let's build some lists of labels and predictions for the ROUGE metric to compare (note that if you get import errors for this section, you may need to`!pip install tqdm`): + +```python +from tqdm import tqdm +import numpy as np + +all_preds = [] +all_labels = [] +for batch in tqdm(tf_eval_dataset): + predictions = model.generate(**batch) + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + labels = batch["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) +``` + +Once we have our lists of label and prediction strings, computing the ROUGE score is easy: + +```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'} + +## Fine-tuning mT5 with 🤗 Accelerate + +Fine-tuning our model with 🤗 Accelerate is very similar to the text classification example we encountered in [Chapter 3](/course/chapter3). The main differences will be the need to explicitly generate our summaries during training and define how we compute the ROUGE scores (recall that the `Seq2SeqTrainer` took care of the generation for us). Let's take a look how we can implement these two requirements within 🤗 Accelerate! + +### Preparing everything for training + +The first thing we need to do is create a `DataLoader` for each of our splits. Since the PyTorch dataloaders expect batches of tensors, we need to set the format to `"torch"` in our datasets: + +```python +tokenized_datasets.set_format("torch") +``` + +Now that we've got datasets consisting of just tensors, the next thing to do is instantiate the `DataCollatorForSeq2Seq` again. For this we need to provide a fresh version of the model, so let's load it again from our cache: + +```python +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +We can then instantiate the data collator and use this to define our dataloaders: + +```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 +) +``` + +The next thing to do is define the optimizer we want to use. As in our other examples, we'll use `AdamW`, which works well for most problems: + +```python +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Finally, we feed our model, optimizer, and dataloaders to the `accelerator.prepare()` method: + +```python +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + + + +🚨 If you're training on a TPU, you'll need to move all the code above into a dedicated training function. See [Chapter 3](/course/chapter3) for more details. + + + +Now that we've prepared our objects, there are three remaining things to do: + +* Define the learning rate schedule. +* Implement a function to post-process the summaries for evaluation. +* Create a repository on the Hub that we can push our model to. + +For the learning rate schedule, we'll use the standard linear one from previous sections: + +```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, +) +``` + +For post-processing, we need a function that splits the generated summaries into sentences that are separated by newlines. This is the format the ROUGE metric expects, and we can achieve this with the following snippet of code: + +```python +def postprocess_text(preds, labels): + preds = [pred.strip() for pred in preds] + labels = [label.strip() for label in labels] + + # ROUGE expects a newline after each sentence + preds = ["\n".join(nltk.sent_tokenize(pred)) for pred in preds] + labels = ["\n".join(nltk.sent_tokenize(label)) for label in labels] + + return preds, labels +``` + +This should look familiar to you if you recall how we defined the `compute_metrics()` function of the `Seq2SeqTrainer`. + +Finally, we need to create a model repository on the Hugging Face Hub. For this, we can use the appropriately titled 🤗 Hub library. We just need to define a name for our repository, and the library has a utility function to combine the repository ID with the user profile: + +```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' +``` + +Now we can use this repository name to clone a local version to our results directory that will store the training artifacts: + +```python +from huggingface_hub import Repository + +output_dir = "results-mt5-finetuned-squad-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +This will allow us to push the artifacts back to the Hub by calling the `repo.push_to_hub()` method during training! Let's now wrap up our analysis by writing out the training loop. + +### Training loop + +The training loop for summarization is quite similar to the other 🤗 Accelerate examples that we've encountered and is roughly split into four main steps: + +1. Train the model by iterating over all the examples in `train_dataloader` for each epoch. +2. Generate model summaries at the end of each epoch, by first generating the tokens and then decoding them (and the reference summaries) into text. +3. Compute the ROUGE scores using the same techniques we saw earlier. +4. Save the checkpoints and push everything to the Hub. Here we rely on the nifty `blocking=False` argument of the `Repository` object so that we can push the checkpoints per epoch _asynchronously_. This allows us to continue training without having to wait for the somewhat slow upload associated with a GB-sized model! + +These steps can be seen in the following block of code: + +```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): + # Training + model.train() + for step, batch in enumerate(train_dataloader): + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) + + # Evaluation + model.eval() + for step, batch in enumerate(eval_dataloader): + with torch.no_grad(): + generated_tokens = accelerator.unwrap_model(model).generate( + batch["input_ids"], + attention_mask=batch["attention_mask"], + ) + + generated_tokens = accelerator.pad_across_processes( + generated_tokens, dim=1, pad_index=tokenizer.pad_token_id + ) + labels = batch["labels"] + + # If we did not pad to max length, we need to pad the labels too + labels = accelerator.pad_across_processes( + batch["labels"], dim=1, pad_index=tokenizer.pad_token_id + ) + + generated_tokens = accelerator.gather(generated_tokens).cpu().numpy() + labels = accelerator.gather(labels).cpu().numpy() + + # Replace -100 in the labels as we can't decode them + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + if isinstance(generated_tokens, tuple): + generated_tokens = generated_tokens[0] + decoded_preds = tokenizer.batch_decode( + generated_tokens, skip_special_tokens=True + ) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + decoded_preds, decoded_labels = postprocess_text( + decoded_preds, decoded_labels + ) + + rouge_score.add_batch(predictions=decoded_preds, references=decoded_labels) + + # Compute metrics + result = rouge_score.compute() + # Extract the median ROUGE scores + result = {key: value.mid.fmeasure * 100 for key, value in result.items()} + result = {k: round(v, 4) for k, v in result.items()} + print(f"Epoch {epoch}:", result) + + # Save and upload + accelerator.wait_for_everyone() + unwrapped_model = accelerator.unwrap_model(model) + unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) + if accelerator.is_main_process: + tokenizer.save_pretrained(output_dir) + repo.push_to_hub( + commit_message=f"Training in progress epoch {epoch}", blocking=False + ) +``` + +```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} +``` + +And that's it! Once you run this, you'll have a model and results that are pretty similar to the ones we obtained with the `Trainer`. + +{/if} + +## Using your fine-tuned model + +Once you've pushed the model to the Hub, you can play with it either via the inference widget or with a `pipeline` object, as follows: + +```python +from transformers import pipeline + +hub_model_id = "huggingface-course/mt5-small-finetuned-amazon-en-es" +summarizer = pipeline("summarization", model=hub_model_id) +``` + +We can feed some examples from the test set (which the model has not seen) to our pipeline to get a feel for the quality of the summaries. First let's implement a simple function to show the review, title, and generated summary together: + +```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}'") +``` + +Let's take a look at one of the English examples we get: + +```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' +``` + +This is not too bad! We can see that our model has actually been able to perform _abstractive_ summarization by augmenting parts of the review with new words. And perhaps the coolest aspect of our model is that it is bilingual, so we can also generate summaries of Spanish reviews: + +```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' +``` + +The summary translates into "Very easy to read" in English, which we can see in this case was extracted directly from the review. Nevertheless, this shows the versatility of the mT5 model and has given you a taste of what it's like to deal with a multilingual corpus! + +Next, we'll turn our attention to a slightly more complex task: training a language model from scratch. diff --git a/chapters/en/chapter7/6.mdx b/chapters/en/chapter7/6.mdx index 27dc8cbbd..e113b9423 100644 --- a/chapters/en/chapter7/6.mdx +++ b/chapters/en/chapter7/6.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter7/7.mdx b/chapters/en/chapter7/7.mdx index d32fc7d8d..2dd81123a 100644 --- a/chapters/en/chapter7/7.mdx +++ b/chapters/en/chapter7/7.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/en/chapter7/8.mdx b/chapters/en/chapter7/8.mdx index 0ac5ccc02..dcda455a5 100644 --- a/chapters/en/chapter7/8.mdx +++ b/chapters/en/chapter7/8.mdx @@ -1,5 +1,10 @@ # Mastering NLP + + If you've made it this far in the course, congratulations -- you now have all the knowledge and tools you need to tackle (almost) any NLP task with 🤗 Transformers and the Hugging Face ecosystem! We have seen a lot of different data collators, so we made this little video to help you find which one to use for each task: diff --git a/chapters/en/chapter7/9.mdx b/chapters/en/chapter7/9.mdx index 1763e56ed..e6da1f801 100644 --- a/chapters/en/chapter7/9.mdx +++ b/chapters/en/chapter7/9.mdx @@ -4,6 +4,11 @@ # End-of-chapter quiz + + Let's test what you learned in this chapter! ### 1. Which of the following tasks can be framed as a token classification problem? diff --git a/chapters/en/chapter8/1.mdx b/chapters/en/chapter8/1.mdx index 5a14a567f..644e438a1 100644 --- a/chapters/en/chapter8/1.mdx +++ b/chapters/en/chapter8/1.mdx @@ -1,5 +1,10 @@ # Introduction + + Now that you know how to tackle the most common NLP tasks with 🤗 Transformers, you should be able to get started on your own projects! In this chapter we will explore what to do when you hit a problem. You'll learn how to successfully debug your code or your training, and how to ask the community for help if you don't manage to solve the problem by yourself. And if you think you've found a bug in one of the Hugging Face libraries, we'll show you the best way to report it so that the issue is resolved as quickly as possible. More precisely, in this chapter you will learn: diff --git a/chapters/en/chapter8/2.mdx b/chapters/en/chapter8/2.mdx index b366666fc..20dac3a77 100644 --- a/chapters/en/chapter8/2.mdx +++ b/chapters/en/chapter8/2.mdx @@ -1,8 +1,8 @@ # What to do when you get an error - diff --git a/chapters/en/chapter8/3.mdx b/chapters/en/chapter8/3.mdx index 46a1564d0..0dd7c2ec3 100644 --- a/chapters/en/chapter8/3.mdx +++ b/chapters/en/chapter8/3.mdx @@ -1,8 +1,8 @@ # Asking for help on the forums - diff --git a/chapters/en/chapter8/4.mdx b/chapters/en/chapter8/4.mdx index 54232cdc9..8d2306321 100644 --- a/chapters/en/chapter8/4.mdx +++ b/chapters/en/chapter8/4.mdx @@ -2,9 +2,9 @@ # Debugging the training pipeline - diff --git a/chapters/en/chapter8/4_tf.mdx b/chapters/en/chapter8/4_tf.mdx index 6a241216d..a9f595b48 100644 --- a/chapters/en/chapter8/4_tf.mdx +++ b/chapters/en/chapter8/4_tf.mdx @@ -2,9 +2,9 @@ # Debugging the training pipeline - diff --git a/chapters/en/chapter8/5.mdx b/chapters/en/chapter8/5.mdx index f58cc94b9..51f71c15e 100644 --- a/chapters/en/chapter8/5.mdx +++ b/chapters/en/chapter8/5.mdx @@ -1,8 +1,8 @@ # How to write a good issue - diff --git a/chapters/en/chapter8/6.mdx b/chapters/en/chapter8/6.mdx index 3db86f7ae..2c8b97712 100644 --- a/chapters/en/chapter8/6.mdx +++ b/chapters/en/chapter8/6.mdx @@ -1,5 +1,10 @@ # Part 2 completed! + + Congratulations, you've made it through the second part of the course! We're actively working on the third one, so subscribe to our [newsletter](https://huggingface.curated.co/) to make sure you don't miss its release. You should now be able to tackle a range of NLP tasks, and fine-tune or pretrain a model on them. Don't forget to share your results with the community on the [Model Hub](https://huggingface.co/models). diff --git a/chapters/en/chapter8/7.mdx b/chapters/en/chapter8/7.mdx index 9d29e8fcb..219328edd 100644 --- a/chapters/en/chapter8/7.mdx +++ b/chapters/en/chapter8/7.mdx @@ -2,6 +2,11 @@ # End-of-chapter quiz + + Let's test what you learned in this chapter! ### 1. In which order should you read a Python traceback? diff --git a/chapters/en/chapter9/1.mdx b/chapters/en/chapter9/1.mdx index edfcb13f9..74ea8a0a3 100644 --- a/chapters/en/chapter9/1.mdx +++ b/chapters/en/chapter9/1.mdx @@ -1,5 +1,10 @@ # Introduction to Gradio + + In this chapter we will be learning about how to build **interactive demos** for your machine learning models. Why build a demo or a GUI for your machine learning model in the first place? Demos allow: diff --git a/chapters/en/chapter9/2.mdx b/chapters/en/chapter9/2.mdx index d6445763a..3cf23d896 100644 --- a/chapters/en/chapter9/2.mdx +++ b/chapters/en/chapter9/2.mdx @@ -1,8 +1,8 @@ # Building your first demo - diff --git a/chapters/en/chapter9/3.mdx b/chapters/en/chapter9/3.mdx index be34af06d..bd829ec95 100644 --- a/chapters/en/chapter9/3.mdx +++ b/chapters/en/chapter9/3.mdx @@ -1,8 +1,8 @@ # Understanding the Interface class - diff --git a/chapters/en/chapter9/4.mdx b/chapters/en/chapter9/4.mdx index ad8b4714a..fc31404d0 100644 --- a/chapters/en/chapter9/4.mdx +++ b/chapters/en/chapter9/4.mdx @@ -1,8 +1,8 @@ # Sharing demos with others - diff --git a/chapters/en/chapter9/5.mdx b/chapters/en/chapter9/5.mdx index 31c796ce6..b7c727a7f 100644 --- a/chapters/en/chapter9/5.mdx +++ b/chapters/en/chapter9/5.mdx @@ -1,8 +1,8 @@ # Integrations with the Hugging Face Hub - diff --git a/chapters/en/chapter9/6.mdx b/chapters/en/chapter9/6.mdx index 28a8c17f4..ce4ee3ecc 100644 --- a/chapters/en/chapter9/6.mdx +++ b/chapters/en/chapter9/6.mdx @@ -1,8 +1,8 @@ # Advanced Interface features - diff --git a/chapters/en/chapter9/7.mdx b/chapters/en/chapter9/7.mdx index 7d6de8c1b..eb0fd3f61 100644 --- a/chapters/en/chapter9/7.mdx +++ b/chapters/en/chapter9/7.mdx @@ -1,8 +1,8 @@ # Introduction to Gradio Blocks - diff --git a/chapters/en/chapter9/8.mdx b/chapters/en/chapter9/8.mdx index 9380d2e50..1c6dd04c2 100644 --- a/chapters/en/chapter9/8.mdx +++ b/chapters/en/chapter9/8.mdx @@ -1,5 +1,10 @@ # Gradio, check! + + This wraps up the chapter on building cool ML demos with Gradio - we hope you enjoyed it! To recap, in this chapter we learned: - How to create Gradio demos with the high-level `Interface` API, and how to configure different input and output modalities. diff --git a/chapters/en/chapter9/9.mdx b/chapters/en/chapter9/9.mdx index 7d2dfb8db..0bb98fca8 100644 --- a/chapters/en/chapter9/9.mdx +++ b/chapters/en/chapter9/9.mdx @@ -2,6 +2,11 @@ # End-of-chapter quiz + + Let's test what you learned in this chapter! ### 1. What can you use Gradio to do? diff --git a/chapters/es/chapter1/1.mdx b/chapters/es/chapter1/1.mdx index fc53d0700..5c82e6bba 100644 --- a/chapters/es/chapter1/1.mdx +++ b/chapters/es/chapter1/1.mdx @@ -1,5 +1,10 @@ # Introducción + + ## ¡Te damos la bienvenida al curso de 🤗! diff --git a/chapters/es/chapter1/10.mdx b/chapters/es/chapter1/10.mdx index 18e0262ae..6749eeee5 100644 --- a/chapters/es/chapter1/10.mdx +++ b/chapters/es/chapter1/10.mdx @@ -2,6 +2,11 @@ # Quiz de final de capítulo + + ¡Este capítulo cubrió una gran variedad de temas! No te preocupes si no entendiste todos los detalles; los siguientes capítulos te ayudarán a entender cómo funcionan las cosas detrás de cámaras. Por ahora, ¡revisemos lo que aprendiste en este capítulo! diff --git a/chapters/es/chapter1/2.mdx b/chapters/es/chapter1/2.mdx index 662379538..e799de435 100644 --- a/chapters/es/chapter1/2.mdx +++ b/chapters/es/chapter1/2.mdx @@ -1,5 +1,10 @@ # Procesamiento de Lenguaje Natural + + Antes de ver los Transformadores, hagamos una revisión rápida de qué es el procesamiento de lenguaje natural y por qué nos interesa. ## ¿Qué es PLN? diff --git a/chapters/es/chapter1/3.mdx b/chapters/es/chapter1/3.mdx index c725bb68d..539f62dca 100644 --- a/chapters/es/chapter1/3.mdx +++ b/chapters/es/chapter1/3.mdx @@ -1,8 +1,8 @@ # Transformadores, ¿qué pueden hacer? - diff --git a/chapters/es/chapter1/4.mdx b/chapters/es/chapter1/4.mdx index 3f28b352d..7d6b958b0 100644 --- a/chapters/es/chapter1/4.mdx +++ b/chapters/es/chapter1/4.mdx @@ -1,5 +1,10 @@ # ¿Cómo funcionan los Transformadores? + + En esta sección, daremos una mirada de alto nivel a la arquitectura de los Transformadores. ## Un poco de historia sobre los Transformadores diff --git a/chapters/es/chapter1/5.mdx b/chapters/es/chapter1/5.mdx index 0d587219f..b06cb7b75 100644 --- a/chapters/es/chapter1/5.mdx +++ b/chapters/es/chapter1/5.mdx @@ -1,5 +1,10 @@ # Modelos de codificadores + + 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*. diff --git a/chapters/es/chapter1/6.mdx b/chapters/es/chapter1/6.mdx index 803ba35bb..b4462cd8e 100644 --- a/chapters/es/chapter1/6.mdx +++ b/chapters/es/chapter1/6.mdx @@ -1,5 +1,10 @@ # Modelos de decodificadores + + 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*. diff --git a/chapters/es/chapter1/8.mdx b/chapters/es/chapter1/8.mdx index de17dfed0..aae4180a9 100644 --- a/chapters/es/chapter1/8.mdx +++ b/chapters/es/chapter1/8.mdx @@ -1,8 +1,8 @@ # Sesgos y limitaciones - diff --git a/chapters/es/chapter1/9.mdx b/chapters/es/chapter1/9.mdx index 71eb8b4f6..49228f34f 100644 --- a/chapters/es/chapter1/9.mdx +++ b/chapters/es/chapter1/9.mdx @@ -1,5 +1,10 @@ # Resumen + + En este capítulo viste cómo abordar diferentes tareas de PLN usando la función de alto nivel `pipeline()` de 🤗 Transformers. También viste como buscar modelos en el Hub, así como usar la API de Inferencia para probar los modelos directamente en tu navegador. Discutimos brevemente el funcionamiento de los Transformadores y hablamos sobre la importancia de la transferencia de aprendizaje y el ajuste. Un aspecto clave es que puedes usar la arquitectura completa o sólo el codificador o decodificador, dependiendo de qué tipo de tarea quieres resolver. La siguiente tabla resume lo anterior: diff --git a/chapters/es/chapter2/4.mdx b/chapters/es/chapter2/4.mdx index 7a3b40160..b53535f03 100644 --- a/chapters/es/chapter2/4.mdx +++ b/chapters/es/chapter2/4.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/es/chapter2/5.mdx b/chapters/es/chapter2/5.mdx index 606f9bc89..366b53839 100644 --- a/chapters/es/chapter2/5.mdx +++ b/chapters/es/chapter2/5.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/es/chapter3/1.mdx b/chapters/es/chapter3/1.mdx index 16ae3c5d2..2f0398087 100644 --- a/chapters/es/chapter3/1.mdx +++ b/chapters/es/chapter3/1.mdx @@ -2,6 +2,11 @@ # Introducción + + 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 ? {#if fw === 'pt'} diff --git a/chapters/es/chapter3/2.mdx b/chapters/es/chapter3/2.mdx index 3e7e3d91a..df59b8a2c 100644 --- a/chapters/es/chapter3/2.mdx +++ b/chapters/es/chapter3/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/es/chapter3/4.mdx b/chapters/es/chapter3/4.mdx index b5bd3f7cf..96e07b050 100644 --- a/chapters/es/chapter3/4.mdx +++ b/chapters/es/chapter3/4.mdx @@ -1,8 +1,8 @@ # Un entrenamiento completo - diff --git a/chapters/es/chapter8/1.mdx b/chapters/es/chapter8/1.mdx index 4ebde8120..262a46d4f 100644 --- a/chapters/es/chapter8/1.mdx +++ b/chapters/es/chapter8/1.mdx @@ -1,5 +1,10 @@ # Introducción + + Ahora sabes cómo abordar las tareas de PLN más comunes con la librería 🤗 Transformers, ¡deberías ser capaz de iniciar tus propios proyectos! En este capítulo exploraremos qué debes hacer cuando te encuentras con un problema. Aprenderás a cómo depurar (debug) exitosamente tu código o tu entrenamiento, y cómo solicitar ayuda si no consigues resolver el problema por ti mismo. Además, si crees que has encontrado un error (bug) en una de las librerías de Hugging Face, te indicaremos la mejor manera de reportarlo para que se resuelva tan pronto como sea posible. Más precisamente, en este capítulo aprenderás: diff --git a/chapters/es/chapter8/2.mdx b/chapters/es/chapter8/2.mdx index 34a4e9392..0e7553eae 100644 --- a/chapters/es/chapter8/2.mdx +++ b/chapters/es/chapter8/2.mdx @@ -1,8 +1,8 @@ # ¿Qué hacer cuando se produce un error? - diff --git a/chapters/fa/chapter1/1.mdx b/chapters/fa/chapter1/1.mdx index 5b826a280..88d60c612 100644 --- a/chapters/fa/chapter1/1.mdx +++ b/chapters/fa/chapter1/1.mdx @@ -1,6 +1,11 @@
# مقدمه + + ## به دوره‌ آموزشی هاگینگ‌فِیس خوش آمدید diff --git a/chapters/fa/chapter1/2.mdx b/chapters/fa/chapter1/2.mdx index 3342d6e47..9b3962c1c 100644 --- a/chapters/fa/chapter1/2.mdx +++ b/chapters/fa/chapter1/2.mdx @@ -1,6 +1,11 @@
# پردازش زبان طبیعی + + قبل از اینکه به سراغ مدل‌های ترنسفومر برویم، بیایید نگاهی سریع بیاندازیم به اینکه پردازش زبان طبیعی[^1] چیست و چرا برای ما حائز اهمیت است. ## NLP چیست؟ diff --git a/chapters/fa/chapter2/1.mdx b/chapters/fa/chapter2/1.mdx index 5d61827cf..ad6bdd22e 100644 --- a/chapters/fa/chapter2/1.mdx +++ b/chapters/fa/chapter2/1.mdx @@ -2,6 +2,11 @@
# مقدمه + + همان طور که در [فصل اول](/course/chapter1) دیدید، مدل‌های ترنسفورمر معمولا بسیار بزرگ هستند. با داشتن میلیون‌ها یا حتی ده‌ها میلیارد پارامتر، تعلیم و بکارگیری این مدل‌ها کار بسیار پیچیده‌ای است. علاوه بر این،‌ تقریبا هر روز مدل‌های جدیدی عرضه می‌شوند که هرکدام شیوه پیاده‌سازی خود را دارند و امتحان کردن تمام آن‌ها کار آسانی نیست. کتابخانه ترنسفومرهای هاگینگ‌فِیس برای حل این مشکل تولید شده است. هدف آن، ارائه یک API واحد برای بارگذاری، تعلیم و ذخیره‌سازی مدل‌های ترنسفورمر است. ویژگی های اصلی این کتابخانه از این قرار است: diff --git a/chapters/fa/chapter2/2.mdx b/chapters/fa/chapter2/2.mdx index 71abc5e16..9ae1d64e2 100644 --- a/chapters/fa/chapter2/2.mdx +++ b/chapters/fa/chapter2/2.mdx @@ -5,18 +5,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/fa/chapter2/3.mdx b/chapters/fa/chapter2/3.mdx index 0155c5634..eace2dc12 100644 --- a/chapters/fa/chapter2/3.mdx +++ b/chapters/fa/chapter2/3.mdx @@ -5,18 +5,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/fa/chapter3/1.mdx b/chapters/fa/chapter3/1.mdx index b3f520423..cf96cdbf8 100644 --- a/chapters/fa/chapter3/1.mdx +++ b/chapters/fa/chapter3/1.mdx @@ -4,6 +4,11 @@ # مقدمه + + در [فصل ۲](/course/chapter2) نحوه استفاده از توکِنایزرها و مدل‌های از پیش تعلیم دیده را جهت انجام پیش‌بینی‌های جدید بررسی کردیم. اما چگونه می‌توانید یک مدل از پیش‌ تعلیم دیده را خودتان کوک‌ کنید؟ {#if fw === 'pt'} diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 092a92b30..deabbc5f0 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -6,18 +6,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/fa/chapter4/1.mdx b/chapters/fa/chapter4/1.mdx index b071b63df..7f6995ac0 100644 --- a/chapters/fa/chapter4/1.mdx +++ b/chapters/fa/chapter4/1.mdx @@ -1,6 +1,11 @@
# هاب هاگینگ‌فِیس + + [هاب هاگینگ‌فِیس](https://huggingface.co/) –- وب‌سایت اصلی ما –- پلتفرمی مرکزی است که به همه امکان مشارکت، کشف و استفاده از جدیدترین و بهترین مدل‌ها و دیتاسِت‌ها را می‌دهد. این هاب طیف گسترده‌ای از مدل‌ها را در خود جای داده، که بالغ بر ۱۰ هزار مورد از آن‌ها در دسترس عموم قرار دارد. در این فصل بر روی مدل‌ها متمرکز شده و در فصل ۵ نیز نگاهی به دیتاسِت‌ها خواهیم داشت. مدل‌های موجود در هاب، محدود به ترنسفورمرهای هاگینگ‌فِیس یا حتی NLP نیستند. از جمله آنها می‌توان مدل‌هایی از قبیل [Flair](https://github.com/flairNLP/flair) و [AllenNLP](https://github.com/allenai/allennlp) برای پردازش زبان طبیعی، [Asteroid](https://github.com/asteroid-team/asteroid) و [pyannote](https://github.com/pyannote/pyannote-audio) برای پردازش گفتار، و [timm](https://github.com/rwightman/pytorch-image-models) را برای پردازش تصویر برشمرد. diff --git a/chapters/fa/chapter4/2.mdx b/chapters/fa/chapter4/2.mdx index 01960357d..2514c1849 100644 --- a/chapters/fa/chapter4/2.mdx +++ b/chapters/fa/chapter4/2.mdx @@ -5,18 +5,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/fr/chapter1/1.mdx b/chapters/fr/chapter1/1.mdx index a0ff68898..6dd1423c5 100644 --- a/chapters/fr/chapter1/1.mdx +++ b/chapters/fr/chapter1/1.mdx @@ -1,55 +1,60 @@ -# Introduction - -## Bienvenue au cours 🤗 ! - - - -Ce cours vous apprendra à utiliser les bibliothèques de NLP de l'écosystème [Hugging Face](https://huggingface.co/) : [🤗 *Transformers*](https://github.com/huggingface/transformers), [🤗 *Datasets*](https://github.com/huggingface/datasets), [🤗 *Tokenizers*](https://github.com/huggingface/tokenizers) et [🤗 *Accelerate*](https://github.com/huggingface/accelerate), ainsi que le [*Hub*](https://huggingface.co/models). C'est totalement gratuit et sans publicité. - -## À quoi s'attendre ? - -Voici un bref aperçu du cours : - -
-Bref aperçu du contenu du cours. - -
- -- Les chapitres 1 à 4 présentent les principaux concepts de la bibliothèque 🤗 *Transformers*. À la fin de ce chapitre, vous serez familier avec le fonctionnement des *transformers* et vous saurez comment utiliser un modèle présent sur le [*Hub*](https://huggingface.co/models), le *finetuner* sur un jeu de données, et partager vos résultats sur le *Hub* ! -- Les chapitres 5 à 8 présentent les bases des librairies 🤗 *Datasets* et 🤗 *Tokenizers* ainsi qu'une découverte des problèmes classiques de NLP. À la fin de ce chapitre, vous serez capable de résoudre les problèmes de NLP les plus communs par vous-même. -- Les chapitres 9 à 12 proposent d'aller plus loin et d'explorer comment les *transformers* peuvent être utilisés pour résoudre des problèmes de traitement de la parole et de vision par ordinateur. En suivant ces chapitres, vous apprendrez à construire et à partager vos modèles via des démonstrateurs, et vous serez capable d'optimiser ces modèles pour des environnements de production. Enfin, vous serez prêt à appliquer 🤗 *Transformers* à (presque) n'importe quel problème d'apprentissage automatique ! - -Ce cours : - -* requiert un bon niveau en Python, -* se comprend mieux si vous avez déjà suivi un cours d'introduction à l'apprentissage profond comme [fast.ai's](https://www.fast.ai/), [*Practical Deep Learning for Coders*](https://course.fast.ai/) ou un des cours développés par [*DeepLearning.AI*](https://www.deeplearning.ai/), -* n'attend pas une connaissance appronfondie de [PyTorch](https://pytorch.org/) ou de [TensorFlow](https://www.tensorflow.org/), bien qu'être familiarisé avec l'un d'entre eux peut aider. - -Après avoir terminé ce cours, nous vous recommandons de suivre la [Spécialisation en NLP](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh) dispensée par DeepLearning.AI, qui couvre une grande partie des modèles traditionnels de NLP comme le Bayésien naïf et les LSTMs qui sont importants à connaître! - -## Qui sommes-nous ? - -À propos des auteurs de ce cours : - -*Abubakar Abid** a obtenu son doctorat à Stanford en apprentissage automatique appliqué. Pendant son doctorat, il a fondé [Gradio](https://github.com/gradio-app/gradio), une bibliothèque Python open-source qui a été utilisée pour construire plus de 600 000 démos d'apprentissage automatique. Gradio a été rachetée par Hugging Face, où Abubakar occupe désormais le poste de responsable de l'équipe d'apprentissage automatique. - -**Matthew Carrigan** est ingénieur en apprentissage machine chez Hugging Face. Il vit à Dublin en Irlande. Il a travaillé auparavant comme ingénieur en apprentissage machine chez Parse.ly et avant cela comme chercheur postdoctoral au Trinity College Dublin. Il ne croit pas que nous arrivions à l'*AGI* en mettant à l'échelle les architectures existantes mais a tout de même beaucoup d'espoir dans l'immortalité des robots. - -**Lysandre Debut** est ingénieur en apprentissage machine chez Hugging Face et a travaillé sur la bibliothèque 🤗 *Transformers* depuis les premières phases de développement. Son but est de rendre le NLP accessible à tous en développant des outils disposant d'une API très simple. - -**Sylvain Gugger** est ingénieur recherche chez Hugging Face et un des principaux responsables de la bibliothèque 🤗 *Transformers*. Avant cela, il était chercheur en en apprentissage machine chez fast.ai et a écrit le livre [*Deep Learning for Coders with fastai and PyTorch*](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/) avec Jeremy Howard. Son but est de rendre l'apprentissage profond plus accessible, en développant et en améliorant des techniques permettant aux modèles d'apprendre rapidement sur des ressources limitées. - -**Dawood Khan** est un ingénieur en apprentissage automatique chez Hugging Face. Il vient de New York et est diplômé de l'Université de New York en informatique. Après avoir travaillé comme ingénieur iOS pendant quelques années, Dawood a quitté son poste pour créer Gradio avec ses cofondateurs. Gradio a finalement été acquis par Hugging Face. - -**Merve Noyan** est développeuse *advocate* chez Hugging Face et travaille à la création d'outils et de contenus visant à démocratiser l'apprentissage machine pour tous. - -**Lucile Saulnier** est ingénieure en apprentissage machine chez Hugging Face et travaille au développement et à l'implémentation de nombreux outils *open source*. Elle est également activement impliquée dans de nombreux projets de recherche dans le domaine du NLP comme l'entraînement collaboratif de modèles et le projet BigScience. - -**Lewis Tunstall** est ingénieur en apprentissage machine chez Hugging Face et dévoué au développement d'outils open source avec la volonté de les rendre accessibles à une communauté plus large. Il est également co-auteur du livre [*Natural Language Processing with Transformers*](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). - -**Leandro von Werra** est ingénieur en apprentissage machine dans l'équipe *open source* d'Hugging Face et également co-auteur du livre [*Natural Language Processing with Transformers*](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). Il a plusieurs années d'expérience dans l'industrie où il a pu déployer des projets de NLP en production et travailler sur toutes les étapes clefs du déploiement. - -Êtes-vous prêt à commencer ? Dans ce chapitre, vous apprendrez : -* à utiliser la fonction `pipeline()` pour résoudre des problèmes de NLP comme la génération de texte et la classification, -* l'architecture d'un *transformer*, -* comment faire la distinction entre les différentes architectures d'encodeur, de décodeur et d'encodeur-décodeur ainsi que leurs différents cas d'usage. +# Introduction + + + +## Bienvenue au cours 🤗 ! + + + +Ce cours vous apprendra à utiliser les bibliothèques de NLP de l'écosystème [Hugging Face](https://huggingface.co/) : [🤗 *Transformers*](https://github.com/huggingface/transformers), [🤗 *Datasets*](https://github.com/huggingface/datasets), [🤗 *Tokenizers*](https://github.com/huggingface/tokenizers) et [🤗 *Accelerate*](https://github.com/huggingface/accelerate), ainsi que le [*Hub*](https://huggingface.co/models). C'est totalement gratuit et sans publicité. + +## À quoi s'attendre ? + +Voici un bref aperçu du cours : + +
+Bref aperçu du contenu du cours. + +
+ +- Les chapitres 1 à 4 présentent les principaux concepts de la bibliothèque 🤗 *Transformers*. À la fin de ce chapitre, vous serez familier avec le fonctionnement des *transformers* et vous saurez comment utiliser un modèle présent sur le [*Hub*](https://huggingface.co/models), le *finetuner* sur un jeu de données, et partager vos résultats sur le *Hub* ! +- Les chapitres 5 à 8 présentent les bases des librairies 🤗 *Datasets* et 🤗 *Tokenizers* ainsi qu'une découverte des problèmes classiques de NLP. À la fin de ce chapitre, vous serez capable de résoudre les problèmes de NLP les plus communs par vous-même. +- Les chapitres 9 à 12 proposent d'aller plus loin et d'explorer comment les *transformers* peuvent être utilisés pour résoudre des problèmes de traitement de la parole et de vision par ordinateur. En suivant ces chapitres, vous apprendrez à construire et à partager vos modèles via des démonstrateurs, et vous serez capable d'optimiser ces modèles pour des environnements de production. Enfin, vous serez prêt à appliquer 🤗 *Transformers* à (presque) n'importe quel problème d'apprentissage automatique ! + +Ce cours : + +* requiert un bon niveau en Python, +* se comprend mieux si vous avez déjà suivi un cours d'introduction à l'apprentissage profond comme [fast.ai's](https://www.fast.ai/), [*Practical Deep Learning for Coders*](https://course.fast.ai/) ou un des cours développés par [*DeepLearning.AI*](https://www.deeplearning.ai/), +* n'attend pas une connaissance appronfondie de [PyTorch](https://pytorch.org/) ou de [TensorFlow](https://www.tensorflow.org/), bien qu'être familiarisé avec l'un d'entre eux peut aider. + +Après avoir terminé ce cours, nous vous recommandons de suivre la [Spécialisation en NLP](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh) dispensée par DeepLearning.AI, qui couvre une grande partie des modèles traditionnels de NLP comme le Bayésien naïf et les LSTMs qui sont importants à connaître! + +## Qui sommes-nous ? + +À propos des auteurs de ce cours : + +*Abubakar Abid** a obtenu son doctorat à Stanford en apprentissage automatique appliqué. Pendant son doctorat, il a fondé [Gradio](https://github.com/gradio-app/gradio), une bibliothèque Python open-source qui a été utilisée pour construire plus de 600 000 démos d'apprentissage automatique. Gradio a été rachetée par Hugging Face, où Abubakar occupe désormais le poste de responsable de l'équipe d'apprentissage automatique. + +**Matthew Carrigan** est ingénieur en apprentissage machine chez Hugging Face. Il vit à Dublin en Irlande. Il a travaillé auparavant comme ingénieur en apprentissage machine chez Parse.ly et avant cela comme chercheur postdoctoral au Trinity College Dublin. Il ne croit pas que nous arrivions à l'*AGI* en mettant à l'échelle les architectures existantes mais a tout de même beaucoup d'espoir dans l'immortalité des robots. + +**Lysandre Debut** est ingénieur en apprentissage machine chez Hugging Face et a travaillé sur la bibliothèque 🤗 *Transformers* depuis les premières phases de développement. Son but est de rendre le NLP accessible à tous en développant des outils disposant d'une API très simple. + +**Sylvain Gugger** est ingénieur recherche chez Hugging Face et un des principaux responsables de la bibliothèque 🤗 *Transformers*. Avant cela, il était chercheur en en apprentissage machine chez fast.ai et a écrit le livre [*Deep Learning for Coders with fastai and PyTorch*](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/) avec Jeremy Howard. Son but est de rendre l'apprentissage profond plus accessible, en développant et en améliorant des techniques permettant aux modèles d'apprendre rapidement sur des ressources limitées. + +**Dawood Khan** est un ingénieur en apprentissage automatique chez Hugging Face. Il vient de New York et est diplômé de l'Université de New York en informatique. Après avoir travaillé comme ingénieur iOS pendant quelques années, Dawood a quitté son poste pour créer Gradio avec ses cofondateurs. Gradio a finalement été acquis par Hugging Face. + +**Merve Noyan** est développeuse *advocate* chez Hugging Face et travaille à la création d'outils et de contenus visant à démocratiser l'apprentissage machine pour tous. + +**Lucile Saulnier** est ingénieure en apprentissage machine chez Hugging Face et travaille au développement et à l'implémentation de nombreux outils *open source*. Elle est également activement impliquée dans de nombreux projets de recherche dans le domaine du NLP comme l'entraînement collaboratif de modèles et le projet BigScience. + +**Lewis Tunstall** est ingénieur en apprentissage machine chez Hugging Face et dévoué au développement d'outils open source avec la volonté de les rendre accessibles à une communauté plus large. Il est également co-auteur du livre [*Natural Language Processing with Transformers*](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). + +**Leandro von Werra** est ingénieur en apprentissage machine dans l'équipe *open source* d'Hugging Face et également co-auteur du livre [*Natural Language Processing with Transformers*](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). Il a plusieurs années d'expérience dans l'industrie où il a pu déployer des projets de NLP en production et travailler sur toutes les étapes clefs du déploiement. + +Êtes-vous prêt à commencer ? Dans ce chapitre, vous apprendrez : +* à utiliser la fonction `pipeline()` pour résoudre des problèmes de NLP comme la génération de texte et la classification, +* l'architecture d'un *transformer*, +* comment faire la distinction entre les différentes architectures d'encodeur, de décodeur et d'encodeur-décodeur ainsi que leurs différents cas d'usage. diff --git a/chapters/fr/chapter1/10.mdx b/chapters/fr/chapter1/10.mdx index d48d06003..889612715 100644 --- a/chapters/fr/chapter1/10.mdx +++ b/chapters/fr/chapter1/10.mdx @@ -1,258 +1,263 @@ - - -# Quiz de fin de chapitre - -Ce chapitre a couvert un grand nombre de notions ! Ne vous inquiétez pas si vous n'avez pas compris tous les détails, les chapitres suivants vous aideront à comprendre comment les choses fonctionnent concrètement. - -Mais avant d'aller plus loin, prenons un instant pour voir ce que vous avez appris dans ce chapitre ! - - -### 1. Explorez le *Hub* et cherchez le modèle `roberta-large-mnli`. Quelle tâche accomplit-il ? - - -page roberta-large-mnli." - }, - { - text: "Classification de texte", - explain: "Pour être plus précis, il classifie si deux phrases sont logiquement liées entre elles parmis trois possibilités (contradiction, neutre, lien). Il s'agit d'une tâche aussi appelée inference de langage naturel.", - correct: true - }, - { - text: "Génération de texte", - explain: "Regardez à nouveau sur la page roberta-large-mnli." - } - ]} -/> - -### 2. Que renvoie le code suivant ? - -```py -from transformers import pipeline - -ner = pipeline("ner", grouped_entities=True) -ner( - "My name is Sylvain and I work at Hugging Face in Brooklyn." -) # Je m'appelle Sylvain et je travaille à Hugging Face à Brooklyn. -``` - -d'analyse de sentiment (sentiment-analysis dans la documentation d'Hugging-Face)." - }, - { - text: "Il renvoie un texte généré qui complète cette phrase.", - explain: "Cela correspondrait au pipeline de génération de texte (text-generation dans la documentation d'Hugging-Face)." - }, - { - text: "Il renvoie les entités nommées dans cette phrase, telles que les personnes, les organisations ou lieux.", - explain: "De plus, avec grouped_entities=True, cela regroupe les mots appartenant à la même entité, comme par exemple \"Hugging Face\".", - correct: true - } - ]} -/> - -### 3. Que remplace « ... » dans ce code ? - -```py -from transformers import pipeline - -filler = pipeline("fill-mask", model="bert-base-cased") -result = filler("...") -``` - - has been waiting for you. # Ce <mask> vous attend.", - explain: "Regardez la description du modèle bert-base-cased et essayez de trouver votre erreur." - }, - { - text: "This [MASK] has been waiting for you. # Ce [MASK] vous attend.", - explain: "Le modèle utilise [MASK] comme mot-masque.", - correct: true - }, - { - text: "This man has been waiting for you. # Cet homme vous attend.", - explain: "Ce pipeline permet de remplacer les mot manquants donc il a besoin d'un mot-masque." - } - ]} -/> - -### 4. Pourquoi ce code ne fonctionne-t-il pas ? - -```py -from transformers import pipeline - -classifier = pipeline("zero-shot-classification") -result = classifier( - "This is a course about the Transformers library" -) # C'est un cours sur la bibliothèque Transformers -``` - -candidate_labels=[...].", - correct: true - }, - { - text: "Ce pipeline nécessite que des phrases soient données, pas juste une phrase.", - explain: "Bien que ce pipeline puisse prendre une liste de phrases à traiter (comme tous les autres pipelines)." - }, - { - text: "La bibliothèque 🤗 Transformers est cassée, comme d'habitude.", - explain: "Nous n'avons aucun commentaire pour cette réponse !", - }, - { - text: "Ce pipeline nécessite des phrases plus longues, celle-ci est trop courte.", - explain: "Notez que si un texte est très long, il est tronqué par le pipeline." - } - ]} -/> - -### 5. Que signifie « apprentissage par transfert » ? - - - -### 6. Vrai ou faux ? Un modèle de langage n'a généralement pas besoin d'étiquettes pour son pré-entraînement. - - -autosupervisé, ce qui signifie que les étiquettes sont créées automatiquement à partir des données d'entrée (comme prédire le mot suivant ou remplacer des mots masqués).", - correct: true - }, - { - text: "Faux", - explain: "Ce n'est pas la bonne réponse." - } - ]} -/> - -### 7. Sélectionnez la phrase qui décrit le mieux les termes « modèle », « architecture » et « poids ». - - - - -### 8. Parmi ces types de modèles, quel est le plus approprié pour générer du texte à partir d'une instruction (*prompt*) ? - - - -### 9. Parmi ces types de modèles, quel est le plus approprié pour le résumé de texte ? - - - -### 10. Quel type de modèle utiliseriez-vous pour classifier des entrées de texte en fonction de certains labels ? - - - -### 11. De quelle source possible peut être le biais observé dans un modèle ? - -finetunée d'un modèle pré-entraîné et il a conservé ses biais.", - explain: "Avec l'apprentissage par transfert, les biais du modèle pré-entraîné perdurent dans le modèle finetuné.", - correct: true - }, - { - text: "Le modèle a été entraîné sur des données qui sont biaisées.", - explain: "Ceci représente la source de biais la plus évidente mais n'est pas la seule possible.", - correct: true - }, - { - text: "La métrique optimisée lors de l'entraînement du modèle est biaisée.", - explain: "Une source moins évidente est la façon dont le modèle est entraîné. Votre modèle va de façon aveugle optimiser la métrique que vous avez sélectionnée, sans prendre aucun recul.", - correct: true - } - ]} -/> + + +# Quiz de fin de chapitre + + + +Ce chapitre a couvert un grand nombre de notions ! Ne vous inquiétez pas si vous n'avez pas compris tous les détails, les chapitres suivants vous aideront à comprendre comment les choses fonctionnent concrètement. + +Mais avant d'aller plus loin, prenons un instant pour voir ce que vous avez appris dans ce chapitre ! + + +### 1. Explorez le *Hub* et cherchez le modèle `roberta-large-mnli`. Quelle tâche accomplit-il ? + + +page roberta-large-mnli." + }, + { + text: "Classification de texte", + explain: "Pour être plus précis, il classifie si deux phrases sont logiquement liées entre elles parmis trois possibilités (contradiction, neutre, lien). Il s'agit d'une tâche aussi appelée inference de langage naturel.", + correct: true + }, + { + text: "Génération de texte", + explain: "Regardez à nouveau sur la page roberta-large-mnli." + } + ]} +/> + +### 2. Que renvoie le code suivant ? + +```py +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner( + "My name is Sylvain and I work at Hugging Face in Brooklyn." +) # Je m'appelle Sylvain et je travaille à Hugging Face à Brooklyn. +``` + +d'analyse de sentiment (sentiment-analysis dans la documentation d'Hugging-Face)." + }, + { + text: "Il renvoie un texte généré qui complète cette phrase.", + explain: "Cela correspondrait au pipeline de génération de texte (text-generation dans la documentation d'Hugging-Face)." + }, + { + text: "Il renvoie les entités nommées dans cette phrase, telles que les personnes, les organisations ou lieux.", + explain: "De plus, avec grouped_entities=True, cela regroupe les mots appartenant à la même entité, comme par exemple \"Hugging Face\".", + correct: true + } + ]} +/> + +### 3. Que remplace « ... » dans ce code ? + +```py +from transformers import pipeline + +filler = pipeline("fill-mask", model="bert-base-cased") +result = filler("...") +``` + + has been waiting for you. # Ce <mask> vous attend.", + explain: "Regardez la description du modèle bert-base-cased et essayez de trouver votre erreur." + }, + { + text: "This [MASK] has been waiting for you. # Ce [MASK] vous attend.", + explain: "Le modèle utilise [MASK] comme mot-masque.", + correct: true + }, + { + text: "This man has been waiting for you. # Cet homme vous attend.", + explain: "Ce pipeline permet de remplacer les mot manquants donc il a besoin d'un mot-masque." + } + ]} +/> + +### 4. Pourquoi ce code ne fonctionne-t-il pas ? + +```py +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +result = classifier( + "This is a course about the Transformers library" +) # C'est un cours sur la bibliothèque Transformers +``` + +candidate_labels=[...].", + correct: true + }, + { + text: "Ce pipeline nécessite que des phrases soient données, pas juste une phrase.", + explain: "Bien que ce pipeline puisse prendre une liste de phrases à traiter (comme tous les autres pipelines)." + }, + { + text: "La bibliothèque 🤗 Transformers est cassée, comme d'habitude.", + explain: "Nous n'avons aucun commentaire pour cette réponse !", + }, + { + text: "Ce pipeline nécessite des phrases plus longues, celle-ci est trop courte.", + explain: "Notez que si un texte est très long, il est tronqué par le pipeline." + } + ]} +/> + +### 5. Que signifie « apprentissage par transfert » ? + + + +### 6. Vrai ou faux ? Un modèle de langage n'a généralement pas besoin d'étiquettes pour son pré-entraînement. + + +autosupervisé, ce qui signifie que les étiquettes sont créées automatiquement à partir des données d'entrée (comme prédire le mot suivant ou remplacer des mots masqués).", + correct: true + }, + { + text: "Faux", + explain: "Ce n'est pas la bonne réponse." + } + ]} +/> + +### 7. Sélectionnez la phrase qui décrit le mieux les termes « modèle », « architecture » et « poids ». + + + + +### 8. Parmi ces types de modèles, quel est le plus approprié pour générer du texte à partir d'une instruction (*prompt*) ? + + + +### 9. Parmi ces types de modèles, quel est le plus approprié pour le résumé de texte ? + + + +### 10. Quel type de modèle utiliseriez-vous pour classifier des entrées de texte en fonction de certains labels ? + + + +### 11. De quelle source possible peut être le biais observé dans un modèle ? + +finetunée d'un modèle pré-entraîné et il a conservé ses biais.", + explain: "Avec l'apprentissage par transfert, les biais du modèle pré-entraîné perdurent dans le modèle finetuné.", + correct: true + }, + { + text: "Le modèle a été entraîné sur des données qui sont biaisées.", + explain: "Ceci représente la source de biais la plus évidente mais n'est pas la seule possible.", + correct: true + }, + { + text: "La métrique optimisée lors de l'entraînement du modèle est biaisée.", + explain: "Une source moins évidente est la façon dont le modèle est entraîné. Votre modèle va de façon aveugle optimiser la métrique que vous avez sélectionnée, sans prendre aucun recul.", + correct: true + } + ]} +/> diff --git a/chapters/fr/chapter1/2.mdx b/chapters/fr/chapter1/2.mdx index ef5803d79..c3b8db6ac 100644 --- a/chapters/fr/chapter1/2.mdx +++ b/chapters/fr/chapter1/2.mdx @@ -1,21 +1,26 @@ -# Traitement du langage naturel (NLP pour Natural Language Processing) - -Avant de commencer avec les *transformers*, voyons succinctement ce qu'est le traitement du langage naturel et pourquoi il est important. - -## Le NLP, qu'est-ce que c'est ? - -Le traitement du langage naturel est un domaine de linguistique et d'apprentissage automatique se concentrant sur la compréhension de tout ce qui est lié à la langue humaine. L'objectif des tâches de NLP est non seulement de comprendre individuellement chaque mot, mais aussi de comprendre le contexte associé à l'utilisation de ces mots. - -La liste suivante regroupe les tâches de NLP les plus courantes, avec pour chacune quelques exemples : - -- **Classification de phrases entières** : analyser le sentiment d'un avis, détecter si un email est un spam, déterminer si une phrase est grammaticalement correcte, déterminer si deux phrases sont logiquement reliées ou non, etc. -- **Classification de chaque mot d'une phrase** : identifier les composants grammaticaux d'une phrase (nom, verbe, adjectif), identifier les entités nommées (personne, lieu, organisation), etc. -- **Génération de texte** : compléter le début d'un texte avec un texte généré automatiquement, remplacer les mots manquants ou masqués dans un texte, etc. -- **Extraction d'une réponse à partir d'un texte** : étant donné une question et un contexte extraire la réponse à la question en fonction des informations fournies par le contexte, etc. -- **Génération de nouvelles phrases à partir d'un texte** : traduire un texte dans une autre langue, faire le résumé d'un texte, etc. - -Le traitement du langage naturel ne se limite pas qu'à la compréhension du texte. Il s'intéresse aussi aux problèmes complexes de reconnaissance de la parole et de vision par ordinateur tels que la génération d'une transcription à partir d'un échantillon audio ou la description d'une image. - -## Pourquoi est-ce difficile ? - -Les ordinateurs ne traitent pas les informations de la même manière que les humains. Par exemple, lorsque nous lisons la phrase « j'ai faim », nous comprenons très facilement son sens. De même, lorsque nous lisons deux phrases telles que « j'ai faim » et « je suis triste », nous pouvons facilement déterminer s'il existe des similitudes entre elles. Pour les modèles d'apprentissage automatique, ces tâches sont plus difficiles. Le texte doit être traité de manière à permettre au modèle d'apprendre. Et parce que le langage est complexe, nous devons prendre soin de réfléchir à la meilleure façon de faire ce traitement. Il y a eu beaucoup de recherches sur la façon de représenter le texte et nous allons voir quelques-unes de ces méthodes dans le chapitre suivant. +# Traitement du langage naturel (NLP pour Natural Language Processing) + + + +Avant de commencer avec les *transformers*, voyons succinctement ce qu'est le traitement du langage naturel et pourquoi il est important. + +## Le NLP, qu'est-ce que c'est ? + +Le traitement du langage naturel est un domaine de linguistique et d'apprentissage automatique se concentrant sur la compréhension de tout ce qui est lié à la langue humaine. L'objectif des tâches de NLP est non seulement de comprendre individuellement chaque mot, mais aussi de comprendre le contexte associé à l'utilisation de ces mots. + +La liste suivante regroupe les tâches de NLP les plus courantes, avec pour chacune quelques exemples : + +- **Classification de phrases entières** : analyser le sentiment d'un avis, détecter si un email est un spam, déterminer si une phrase est grammaticalement correcte, déterminer si deux phrases sont logiquement reliées ou non, etc. +- **Classification de chaque mot d'une phrase** : identifier les composants grammaticaux d'une phrase (nom, verbe, adjectif), identifier les entités nommées (personne, lieu, organisation), etc. +- **Génération de texte** : compléter le début d'un texte avec un texte généré automatiquement, remplacer les mots manquants ou masqués dans un texte, etc. +- **Extraction d'une réponse à partir d'un texte** : étant donné une question et un contexte extraire la réponse à la question en fonction des informations fournies par le contexte, etc. +- **Génération de nouvelles phrases à partir d'un texte** : traduire un texte dans une autre langue, faire le résumé d'un texte, etc. + +Le traitement du langage naturel ne se limite pas qu'à la compréhension du texte. Il s'intéresse aussi aux problèmes complexes de reconnaissance de la parole et de vision par ordinateur tels que la génération d'une transcription à partir d'un échantillon audio ou la description d'une image. + +## Pourquoi est-ce difficile ? + +Les ordinateurs ne traitent pas les informations de la même manière que les humains. Par exemple, lorsque nous lisons la phrase « j'ai faim », nous comprenons très facilement son sens. De même, lorsque nous lisons deux phrases telles que « j'ai faim » et « je suis triste », nous pouvons facilement déterminer s'il existe des similitudes entre elles. Pour les modèles d'apprentissage automatique, ces tâches sont plus difficiles. Le texte doit être traité de manière à permettre au modèle d'apprendre. Et parce que le langage est complexe, nous devons prendre soin de réfléchir à la meilleure façon de faire ce traitement. Il y a eu beaucoup de recherches sur la façon de représenter le texte et nous allons voir quelques-unes de ces méthodes dans le chapitre suivant. diff --git a/chapters/fr/chapter1/3.mdx b/chapters/fr/chapter1/3.mdx index 5428aff2b..d10b714e2 100644 --- a/chapters/fr/chapter1/3.mdx +++ b/chapters/fr/chapter1/3.mdx @@ -1,381 +1,381 @@ -# Que peuvent faire les transformers ? - - - -Dans cette section, nous allons voir ce que peuvent faire les *transformers* et utiliser notre premier outil de la bibliothèque 🤗 *Transformers* : la fonction `pipeline()`. - - -👀 Vous voyez ce bouton Open in Colab en haut à droite ? Cliquez dessus pour ouvrir un notebook Colab avec tous les exemples de code de cette section. Ce bouton sera présent dans n'importe quelle section contenant des exemples de code. - -Si vous souhaitez exécuter les codes en local, nous vous recommandons de jeter un œil au chapitre configuration. - - -## Les transformers sont partout ! - -Les *transformers* sont utilisés pour résoudre toute sorte de tâches de NLP comme celles mentionnées dans la section précédente. Voici quelques-unes des entreprises et organisations qui utilisent Hugging Face, les *transformers* et qui contribuent aussi à la communauté en partageant leurs modèles : - -Companies using Hugging Face - -La bibliothèque [🤗 *Transformers*](https://github.com/huggingface/transformers) fournit toutes les fonctionnalités nécessaires pour créer et utiliser les modèles partagés. Le [*Hub*](https://huggingface.co/models) contient des milliers de modèles pré-entraînés que n'importe qui peut télécharger et utiliser. Vous pouvez également transférer vos propres modèles vers le Hub ! - - - ⚠️ Le Hub n'est pas limité aux transformers. Tout le monde peut partager n'importe quel modèle ou jeu de données s'il le souhaite ! Créez un compte sur huggingface.co pour bénéficier de toutes les fonctionnalités disponibles ! - - -Avant de découvrir en détail comment les *transformers* fonctionnent, nous allons voir quelques exemples de comment ils peuvent être utilisés pour résoudre des problèmes intéressants de NLP. - -## Travailler avec les pipelines - - - -L'outil le plus basique de la bibliothèque 🤗 *Transformers* est la fonction `pipeline()`. Elle relie un modèle avec ses étapes de pré-traitement et de post-traitement, permettant d'entrer n'importe quel texte et d'obtenir une réponse compréhensible : - -```python -from transformers import pipeline - -classifier = pipeline("sentiment-analysis") -classifier( - "I've been waiting for a HuggingFace course my whole life." -) # J'ai attendu un cours d'HuggingFace toute ma vie. -``` - -```python out -[{'label': 'POSITIVE', 'score': 0.9598047137260437}] -``` - -On peut même passer plusieurs phrases ! - -```python -classifier( - [ - "I've been waiting for a HuggingFace course my whole life.", - "I hate this so much!", - ] # « J'ai attendu un cours d'HuggingFace toute ma vie. », « Je déteste tellement ça ! » -) -``` - -```python out -[{'label': 'POSITIVE', 'score': 0.9598047137260437}, - {'label': 'NEGATIVE', 'score': 0.9994558095932007}] -``` - -Par défaut, ce pipeline sélectionne un modèle pré-entraîné qui a été spécifiquement entraîné pour l'analyse de sentiment en anglais. Le modèle est téléchargé et mis en cache lorsque vous créez l'objet `classifier`. Si vous réexécutez la commande, c'est le modèle mis en cache qui sera utilisé et il n'y a pas besoin de télécharger le modèle à nouveau. - -Il y a trois étapes principales lorsque vous passez du texte à un pipeline : - -1. le texte est prétraité pour qu'il ait un format compréhensible par le modèle, -2. les données prétraitées sont passées au modèle, -3. les prédictions du modèle sont post-traitées de sorte que vous puissiez les comprendre. - - -Voici une liste non-exhaustive des [pipelines disponibles](https://huggingface.co/transformers/main_classes/pipelines.html) : - -- `feature-extraction` (pour obtenir la représentation vectorielle d'un texte) -- `fill-mask` -- `ner` (*named entity recognition* ou reconnaissance d'entités nommées en français) -- `question-answering` -- `sentiment-analysis` -- `summarization` -- `text-generation` -- `translation` -- `zero-shot-classification` - -Regardons de plus près certains d'entre eux ! - -## Zero-shot classification - -Nous allons commencer par nous attaquer à une tâche plus difficile où nous devons classer des textes qui n'ont pas été annotés. C'est un scénario très répandu dans les projets réels car l'annotation de textes est généralement longue et nécessite parfois une expertise dans un domaine. Pour ce cas d'usage, le pipeline `zero-shot-classification` est très puissant : il vous permet de spécifier les labels à utiliser pour la classification, de sorte que vous n'ayez pas à vous soucier des labels du modèle pré-entraîné. Nous avons déjà vu comment le modèle peut classer un texte comme positif ou négatif en utilisant ces deux labels mais il peut également classer le texte en utilisant n'importe quel autre ensemble de labels que vous souhaitez. - -```python -from transformers import pipeline - -classifier = pipeline("zero-shot-classification") -classifier( - "This is a course about the Transformers library", - # C'est un cours sur la bibliothèque Transformers - candidate_labels=["education", "politics", "business"], -) -``` - -```python out -{'sequence': 'This is a course about the Transformers library', -# C'est un cours sur la bibliothèque Transformers - 'labels': ['education', 'business', 'politics'], - 'scores': [0.8445963859558105, 0.111976258456707, 0.043427448719739914]} -``` - -Ce pipeline est appelé _zero-shot_ car vous n'avez pas besoin d'entraîner spécifiquement le modèle sur vos données pour l'utiliser. Il peut directement retourner des scores de probabilité pour n'importe quel ensemble de labels que vous choisissez ! - - - -✏️ **Essayez !** Jouez avec vos propres séquences et labels et voyez comment le modèle fonctionne. - - - - -## Génération de texte - -Maintenant, nous allons voir comment utiliser un pipeline pour générer du texte. L'idée principale ici est que vous fournissez seulement un extrait de texte qui va être complété par du texte généré automatiquement par le modèle. Cette fonction est similaire à la fonction de texte prédictif que l'on trouve sur de nombreux téléphones portables. La génération de texte implique de l'aléatoire, donc il est normal que vous n'obteniez pas les mêmes résultats que ceux présentés ci-dessous. - -```python -from transformers import pipeline - -generator = pipeline("text-generation") -generator( - "In this course, we will teach you how to" -) # Dans ce cours, nous vous enseignerons comment -``` - -```python out -[{'generated_text': 'In this course, we will teach you how to understand and use ' - # Dans ce cours, nous vous enseignerons comment comprendre et utiliser - 'data flow and data interchange when handling user data. We ' - # flux de données et l'échange de données lors de la manipulation des données utilisateur. Nous - 'will be working with one or more of the most commonly used ' - # travailleront avec un ou plusieurs des plus couramment utilisés - 'data flows — data flows of various types, as seen by the ' - # flux de données - flux de données de différents types, tels qu'ils sont vus par - 'HTTP'}] # HTTP -``` - -Il est possible de contrôler le nombre de séquences générées avec l'argument `num_return_sequences` et la longueur totale du texte généré avec l'argument `max_length`. - - - -✏️ **Essayez !** Utilisez les arguments `num_return_sequences` et `max_length` pour générer deux phrases de 15 mots chacune. - - - - -## Utiliser n'importe quel modèle du Hub dans un pipeline - -Les exemples précédents utilisaient le modèle par défaut pour la tâche en question mais vous pouvez aussi choisir un modèle particulier du *Hub* et l'utiliser dans un pipeline pour une tâche spécifique comme par exemple la génération de texte. Rendez-vous sur le [*Hub*](https://huggingface.co/models) et cliquez sur le *filtre* correspondant sur la gauche pour afficher seulement les modèles supportés pour cette tâche. Vous devriez arriver sur une page comme [celle-ci](https://huggingface.co/models?pipeline_tag=text-generation). - -Essayons le modèle [`distilgpt2`](https://huggingface.co/distilgpt2) ! Voici comment charger le modèle dans le même pipeline que précédemment : - -```python -from transformers import pipeline - -generator = pipeline("text-generation", model="distilgpt2") -generator( - "In this course, we will teach you how to", - # Dans ce cours, nous vous enseignerons comment - max_length=30, - num_return_sequences=2, -) -``` - -```python out -[{'generated_text': 'In this course, we will teach you how to manipulate the world and ' - # Dans ce cours, nous vous enseignerons comment manipuler le monde et - 'move your mental and physical capabilities to your advantage.'}, - # utiliser vos capacités mentales et physiques à votre avantage. - {'generated_text': 'In this course, we will teach you how to become an expert and ' - # Dans ce cours, nous vous apprendrons comment devenir un expert et - 'practice realtime, and with a hands on experience on both real ' - # pratique en temps réel, et avec une expérience pratique à la fois sur de vrais - 'time and real'}] - # temps et réel -``` - -Vous pouvez améliorer votre recherche de modèle en cliquant sur les *filtres* de langue et choisir un modèle qui génère du texte dans une autre langue. Le *Hub* contient également des *checkpoints* pour des modèles multilingues qui supportent plusieurs langues. - -Une fois que vous avez choisi un modèle, vous verrez que vous pouvez tester son fonctionnement en ligne directement. Cela vous permet de tester rapidement les capacités du modèle avant de le télécharger. - - - -✏️ **Essayez !** Utilisez les filtres pour trouver un modèle de génération de texte pour une autre langue. N'hésitez pas à jouer avec le *widget* et l'utiliser dans un pipeline ! - - - -### L'API d'inférence - -Tous les modèles peuvent être testé directement depuis votre navigateur en utilisant l'API d'inférence qui est disponible sur le site [Hugging Face](https://huggingface.co/). Vous pouvez jouer avec le modèle directement sur sa page en entrant du texte personnalisé et en regardant le modèle traiter les données d'entrée. - -L'API d'inférence qui est utilisée par le *widget* est également disponible en tant que produit payant si vous avez besoin de l'API pour votre travail. Consultez la [page des prix](https://huggingface.co/pricing) pour plus de détails. - -## Remplacement des mots manquants - -Le prochain pipeline que vous allez essayer est celui de `fill-mask`. L'idée de cette tâche est de remplir les mots manquants d'un texte donné : - -```python -from transformers import pipeline - -unmasker = pipeline("fill-mask") -unmasker("This course will teach you all about models.", top_k=2) -``` - -```python out -[{'sequence': 'This course will teach you all about mathematical models.', -# Ce cours vous apprendra tout sur les modèles mathématiques. - 'score': 0.19619831442832947, - 'token': 30412, - 'token_str': ' mathematical'}, - {'sequence': 'This course will teach you all about computational models.', - # Ce cours vous apprendra tout sur les modèles mathématiques. - 'score': 0.04052725434303284, - 'token': 38163, - 'token_str': ' computational'}] -``` - -L'argument `top_k` permet de contrôler le nombre de possibilités que vous souhaitez afficher. Notez que dans ce cas, le modèle remplace le mot spécial ``, qui est souvent appelé un *mot masqué*. D'autres modèles permettant de remplacer les mots manquants peuvent avoir des mots masqués différents, donc il est toujours bon de vérifier le mot masqué approprié lorsque vous comparez d'autres modèles. Une façon de le vérifier est de regarder le mot masqué utilisé dans l'outil de test de la page du modèle. - - - -✏️ **Essayez !** Recherchez le modèle `bert-base-cased` sur le *Hub* et identifiez le mot masqué dans l'outil d'inférence. Que prédit le modèle pour la phrase dans notre exemple de pipeline au-dessus ? - - - -## Reconnaissance d'entités nommées - -La reconnaissance d'entités nommées ou NER (pour *Named Entity Recognition*) est une tâche où le modèle doit trouver les parties du texte d'entrée qui correspondent à des entités telles que des personnes, des lieux ou des organisations. Voyons un exemple : - -```python -from transformers import pipeline - -ner = pipeline("ner", grouped_entities=True) -ner( - "My name is Sylvain and I work at Hugging Face in Brooklyn." -) # Je m'appelle Sylvain et je travaille à Hugging Face à Brooklyn. -``` - -```python out -[{'entity_group': 'PER', 'score': 0.99816, 'word': 'Sylvain', 'start': 11, 'end': 18}, - {'entity_group': 'ORG', 'score': 0.97960, 'word': 'Hugging Face', 'start': 33, 'end': 45}, - {'entity_group': 'LOC', 'score': 0.99321, 'word': 'Brooklyn', 'start': 49, 'end': 57} -] -``` - -Nous pouvons voir que le modèle a correctement identifié Sylvain comme une personne (PER), Hugging Face comme une organisation (ORG) et Brooklyn comme un lieu (LOC). - -Il est possible d'utiliser l'option `grouped_entities=True` lors de la création du pipeline pour regrouper les parties du texte qui correspondent à la même entité : ici le modèle à correctement regroupé `Hugging` et `Face` comme une seule organisation, même si le nom comporte plusieurs mots. En effet, comme nous allons voir dans le prochain chapitre, la prétraitement du texte sépare parfois certains mots en plus petites parties. Par exemple, `Sylvain` est séparé en quatre morceaux : `S`, `##yl`, `##va`, et `##in`. Dans l'étape de post-traitement, le pipeline a réussi à regrouper ces morceaux. - - - -✏️ **Essayez !** Recherchez sur le *Hub* un modèle capable de reconnaître les différentes parties du langage (généralement abrégé en POS pour *Part-of-speech*) en anglais. Que prédit le modèle pour la phrase dans notre exemple du pipeline au-dessus ? - - - -## Réponse à des questions - -Le pipeline `question-answering` répond à des questions en utilisant des informations données en contexte : - -```python -from transformers import pipeline - -question_answerer = pipeline("question-answering") -question_answerer( - question="Where do I work?", # Où est-ce que je travaille ? - context="My name is Sylvain and I work at Hugging Face in Brooklyn", - # Je m'appelle Sylvain et je travaille à Hugging Face à Brooklyn. -) -``` - -```python out -{'score': 0.6385916471481323, 'start': 33, 'end': 45, 'answer': 'Hugging Face'} -``` - -Notez que ce pipeline fonctionne par extraction d'information depuis le contexte fourni, il ne génère pas la réponse. - -## Résumé - -Le résumé est une tâche de réduction d'un texte en un texte plus court, tout en gardant tous (ou presque tous) les aspects importants référencés dans le texte. Voici un exemple : - -```python -from transformers import pipeline - -summarizer = pipeline("summarization") -summarizer( - """ - America has changed dramatically during recent years. Not only has the number of - graduates in traditional engineering disciplines such as mechanical, civil, - electrical, chemical, and aeronautical engineering declined, but in most of - the premier American universities engineering curricula now concentrate on - and encourage largely the study of engineering science. As a result, there - are declining offerings in engineering subjects dealing with infrastructure, - the environment, and related issues, and greater concentration on high - technology subjects, largely supporting increasingly complex scientific - developments. While the latter is important, it should not be at the expense - of more traditional engineering. - - Rapidly developing economies such as China and India, as well as other - industrial countries in Europe and Asia, continue to encourage and advance - the teaching of engineering. Both China and India, respectively, graduate - six and eight times as many traditional engineers as does the United States. - Other industrial countries at minimum maintain their output, while America - suffers an increasingly serious decline in the number of engineering graduates - and a lack of well-educated engineers. -""" -) - -""" - L'Amérique a changé de façon spectaculaire au cours des dernières années. Non seulement le nombre de - diplômés dans les disciplines traditionnelles de l'ingénierie telles que le génie mécanique, civil, - l'électricité, la chimie et l'aéronautique a diminué, mais dans la plupart - des grandes universités américaines, les programmes d'études d'ingénierie se concentrent désormais sur - et encouragent largement l'étude des sciences de l'ingénieur. Par conséquent, il y a - de moins en moins d'offres dans les sujets d'ingénierie traitant de l'infrastructure, - l'environnement et les questions connexes, et une plus grande concentration sur les sujets de haute - technologie, qui soutiennent en grande partie des développements scientifiques de plus en plus - complexes. Si cette dernière est importante, elle ne doit pas se faire au détriment - de l'ingénierie plus traditionnelle. - - Les économies en développement rapide telles que la Chine et l'Inde, ainsi que d'autres - pays industrialisés d'Europe et d'Asie, continuent d'encourager et de promouvoir - l'enseignement de l'ingénierie. La Chine et l'Inde, respectivement, diplôment - six et huit fois plus d'ingénieurs traditionnels que les États-Unis. - Les autres pays industriels maintiennent au minimum leur production, tandis que l'Amérique - souffre d'une baisse de plus en plus importante du nombre de diplômés en ingénierie - et un manque d'ingénieurs bien formés. -""" -``` - -```python out -[{'summary_text': ' America has changed dramatically during recent years . The ' - # L'Amérique a changé de façon spectaculaire au cours des dernières années. Le - 'number of engineering graduates in the U.S. has declined in ' - # nombre de diplômés en ingénierie aux États-Unis a diminué dans - 'traditional engineering disciplines such as mechanical, civil ' - # dans les disciplines traditionnelles de l'ingénierie, telles que le génie mécanique, civil - ', electrical, chemical, and aeronautical engineering . Rapidly ' - # l'électricité, la chimie et l'aéronautique. Les économies - 'developing economies such as China and India, as well as other ' - # en développement rapide comme la Chine et l'Inde, ainsi que d'autres - 'industrial countries in Europe and Asia, continue to encourage ' - # pays industriels d'Europe et d'Asie, continuent d'encourager - 'and advance engineering.'}] - # et à faire progresser l'ingénierie. -``` - -Comme pour la génération de texte, vous pouvez spécifier une `max_length` (longueur maximale) ou une `min_length` (longueur minimale) pour le résultat. - - -## Traduction - -Pour la traduction, vous pouvez utiliser un modèle par défaut si vous fournissez un couple de langues dans le nom de la tâche (comme `"translation_en_to_fr"`), mais le plus simple reste d'utiliser un modèle adéquat disponible sur le [*Hub*](https://huggingface.co/models). Ici, nous allons essayer de traduire du français en anglais : - -```python -from transformers import pipeline - -translator = pipeline("translation", model="Helsinki-NLP/opus-mt-fr-en") -translator("Ce cours est produit par Hugging Face.") -``` - -```python out -[{'translation_text': 'This course is produced by Hugging Face.'}] -``` - -Comme pour la génération de texte et le résumé de texte, il est possible de spécifier une `max_length` (longueur maximale) ou une `min_length` (longueur minimale) pour le résultat. - - - -✏️ **Essayez !** Recherchez d'autres modèles de traduction sur le Hub et essayez de traduire la phrase précédente en plusieurs langues différentes. - - - -Les pipelines présentés jusqu'ici sont principalement destinés à des fins de démonstration. Ils ont été programmés pour des tâches spécifiques et ne peuvent pas effectuer de variations de celles-ci. Dans le chapitre suivant, vous apprendrez ce qu'il y a dans un `pipeline()` et comment modifier son comportement. +# Que peuvent faire les transformers ? + + + +Dans cette section, nous allons voir ce que peuvent faire les *transformers* et utiliser notre premier outil de la bibliothèque 🤗 *Transformers* : la fonction `pipeline()`. + + +👀 Vous voyez ce bouton Open in Colab en haut à droite ? Cliquez dessus pour ouvrir un notebook Colab avec tous les exemples de code de cette section. Ce bouton sera présent dans n'importe quelle section contenant des exemples de code. + +Si vous souhaitez exécuter les codes en local, nous vous recommandons de jeter un œil au chapitre configuration. + + +## Les transformers sont partout ! + +Les *transformers* sont utilisés pour résoudre toute sorte de tâches de NLP comme celles mentionnées dans la section précédente. Voici quelques-unes des entreprises et organisations qui utilisent Hugging Face, les *transformers* et qui contribuent aussi à la communauté en partageant leurs modèles : + +Companies using Hugging Face + +La bibliothèque [🤗 *Transformers*](https://github.com/huggingface/transformers) fournit toutes les fonctionnalités nécessaires pour créer et utiliser les modèles partagés. Le [*Hub*](https://huggingface.co/models) contient des milliers de modèles pré-entraînés que n'importe qui peut télécharger et utiliser. Vous pouvez également transférer vos propres modèles vers le Hub ! + + + ⚠️ Le Hub n'est pas limité aux transformers. Tout le monde peut partager n'importe quel modèle ou jeu de données s'il le souhaite ! Créez un compte sur huggingface.co pour bénéficier de toutes les fonctionnalités disponibles ! + + +Avant de découvrir en détail comment les *transformers* fonctionnent, nous allons voir quelques exemples de comment ils peuvent être utilisés pour résoudre des problèmes intéressants de NLP. + +## Travailler avec les pipelines + + + +L'outil le plus basique de la bibliothèque 🤗 *Transformers* est la fonction `pipeline()`. Elle relie un modèle avec ses étapes de pré-traitement et de post-traitement, permettant d'entrer n'importe quel texte et d'obtenir une réponse compréhensible : + +```python +from transformers import pipeline + +classifier = pipeline("sentiment-analysis") +classifier( + "I've been waiting for a HuggingFace course my whole life." +) # J'ai attendu un cours d'HuggingFace toute ma vie. +``` + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}] +``` + +On peut même passer plusieurs phrases ! + +```python +classifier( + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] # « J'ai attendu un cours d'HuggingFace toute ma vie. », « Je déteste tellement ça ! » +) +``` + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}, + {'label': 'NEGATIVE', 'score': 0.9994558095932007}] +``` + +Par défaut, ce pipeline sélectionne un modèle pré-entraîné qui a été spécifiquement entraîné pour l'analyse de sentiment en anglais. Le modèle est téléchargé et mis en cache lorsque vous créez l'objet `classifier`. Si vous réexécutez la commande, c'est le modèle mis en cache qui sera utilisé et il n'y a pas besoin de télécharger le modèle à nouveau. + +Il y a trois étapes principales lorsque vous passez du texte à un pipeline : + +1. le texte est prétraité pour qu'il ait un format compréhensible par le modèle, +2. les données prétraitées sont passées au modèle, +3. les prédictions du modèle sont post-traitées de sorte que vous puissiez les comprendre. + + +Voici une liste non-exhaustive des [pipelines disponibles](https://huggingface.co/transformers/main_classes/pipelines.html) : + +- `feature-extraction` (pour obtenir la représentation vectorielle d'un texte) +- `fill-mask` +- `ner` (*named entity recognition* ou reconnaissance d'entités nommées en français) +- `question-answering` +- `sentiment-analysis` +- `summarization` +- `text-generation` +- `translation` +- `zero-shot-classification` + +Regardons de plus près certains d'entre eux ! + +## Zero-shot classification + +Nous allons commencer par nous attaquer à une tâche plus difficile où nous devons classer des textes qui n'ont pas été annotés. C'est un scénario très répandu dans les projets réels car l'annotation de textes est généralement longue et nécessite parfois une expertise dans un domaine. Pour ce cas d'usage, le pipeline `zero-shot-classification` est très puissant : il vous permet de spécifier les labels à utiliser pour la classification, de sorte que vous n'ayez pas à vous soucier des labels du modèle pré-entraîné. Nous avons déjà vu comment le modèle peut classer un texte comme positif ou négatif en utilisant ces deux labels mais il peut également classer le texte en utilisant n'importe quel autre ensemble de labels que vous souhaitez. + +```python +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +classifier( + "This is a course about the Transformers library", + # C'est un cours sur la bibliothèque Transformers + candidate_labels=["education", "politics", "business"], +) +``` + +```python out +{'sequence': 'This is a course about the Transformers library', +# C'est un cours sur la bibliothèque Transformers + 'labels': ['education', 'business', 'politics'], + 'scores': [0.8445963859558105, 0.111976258456707, 0.043427448719739914]} +``` + +Ce pipeline est appelé _zero-shot_ car vous n'avez pas besoin d'entraîner spécifiquement le modèle sur vos données pour l'utiliser. Il peut directement retourner des scores de probabilité pour n'importe quel ensemble de labels que vous choisissez ! + + + +✏️ **Essayez !** Jouez avec vos propres séquences et labels et voyez comment le modèle fonctionne. + + + + +## Génération de texte + +Maintenant, nous allons voir comment utiliser un pipeline pour générer du texte. L'idée principale ici est que vous fournissez seulement un extrait de texte qui va être complété par du texte généré automatiquement par le modèle. Cette fonction est similaire à la fonction de texte prédictif que l'on trouve sur de nombreux téléphones portables. La génération de texte implique de l'aléatoire, donc il est normal que vous n'obteniez pas les mêmes résultats que ceux présentés ci-dessous. + +```python +from transformers import pipeline + +generator = pipeline("text-generation") +generator( + "In this course, we will teach you how to" +) # Dans ce cours, nous vous enseignerons comment +``` + +```python out +[{'generated_text': 'In this course, we will teach you how to understand and use ' + # Dans ce cours, nous vous enseignerons comment comprendre et utiliser + 'data flow and data interchange when handling user data. We ' + # flux de données et l'échange de données lors de la manipulation des données utilisateur. Nous + 'will be working with one or more of the most commonly used ' + # travailleront avec un ou plusieurs des plus couramment utilisés + 'data flows — data flows of various types, as seen by the ' + # flux de données - flux de données de différents types, tels qu'ils sont vus par + 'HTTP'}] # HTTP +``` + +Il est possible de contrôler le nombre de séquences générées avec l'argument `num_return_sequences` et la longueur totale du texte généré avec l'argument `max_length`. + + + +✏️ **Essayez !** Utilisez les arguments `num_return_sequences` et `max_length` pour générer deux phrases de 15 mots chacune. + + + + +## Utiliser n'importe quel modèle du Hub dans un pipeline + +Les exemples précédents utilisaient le modèle par défaut pour la tâche en question mais vous pouvez aussi choisir un modèle particulier du *Hub* et l'utiliser dans un pipeline pour une tâche spécifique comme par exemple la génération de texte. Rendez-vous sur le [*Hub*](https://huggingface.co/models) et cliquez sur le *filtre* correspondant sur la gauche pour afficher seulement les modèles supportés pour cette tâche. Vous devriez arriver sur une page comme [celle-ci](https://huggingface.co/models?pipeline_tag=text-generation). + +Essayons le modèle [`distilgpt2`](https://huggingface.co/distilgpt2) ! Voici comment charger le modèle dans le même pipeline que précédemment : + +```python +from transformers import pipeline + +generator = pipeline("text-generation", model="distilgpt2") +generator( + "In this course, we will teach you how to", + # Dans ce cours, nous vous enseignerons comment + max_length=30, + num_return_sequences=2, +) +``` + +```python out +[{'generated_text': 'In this course, we will teach you how to manipulate the world and ' + # Dans ce cours, nous vous enseignerons comment manipuler le monde et + 'move your mental and physical capabilities to your advantage.'}, + # utiliser vos capacités mentales et physiques à votre avantage. + {'generated_text': 'In this course, we will teach you how to become an expert and ' + # Dans ce cours, nous vous apprendrons comment devenir un expert et + 'practice realtime, and with a hands on experience on both real ' + # pratique en temps réel, et avec une expérience pratique à la fois sur de vrais + 'time and real'}] + # temps et réel +``` + +Vous pouvez améliorer votre recherche de modèle en cliquant sur les *filtres* de langue et choisir un modèle qui génère du texte dans une autre langue. Le *Hub* contient également des *checkpoints* pour des modèles multilingues qui supportent plusieurs langues. + +Une fois que vous avez choisi un modèle, vous verrez que vous pouvez tester son fonctionnement en ligne directement. Cela vous permet de tester rapidement les capacités du modèle avant de le télécharger. + + + +✏️ **Essayez !** Utilisez les filtres pour trouver un modèle de génération de texte pour une autre langue. N'hésitez pas à jouer avec le *widget* et l'utiliser dans un pipeline ! + + + +### L'API d'inférence + +Tous les modèles peuvent être testé directement depuis votre navigateur en utilisant l'API d'inférence qui est disponible sur le site [Hugging Face](https://huggingface.co/). Vous pouvez jouer avec le modèle directement sur sa page en entrant du texte personnalisé et en regardant le modèle traiter les données d'entrée. + +L'API d'inférence qui est utilisée par le *widget* est également disponible en tant que produit payant si vous avez besoin de l'API pour votre travail. Consultez la [page des prix](https://huggingface.co/pricing) pour plus de détails. + +## Remplacement des mots manquants + +Le prochain pipeline que vous allez essayer est celui de `fill-mask`. L'idée de cette tâche est de remplir les mots manquants d'un texte donné : + +```python +from transformers import pipeline + +unmasker = pipeline("fill-mask") +unmasker("This course will teach you all about models.", top_k=2) +``` + +```python out +[{'sequence': 'This course will teach you all about mathematical models.', +# Ce cours vous apprendra tout sur les modèles mathématiques. + 'score': 0.19619831442832947, + 'token': 30412, + 'token_str': ' mathematical'}, + {'sequence': 'This course will teach you all about computational models.', + # Ce cours vous apprendra tout sur les modèles mathématiques. + 'score': 0.04052725434303284, + 'token': 38163, + 'token_str': ' computational'}] +``` + +L'argument `top_k` permet de contrôler le nombre de possibilités que vous souhaitez afficher. Notez que dans ce cas, le modèle remplace le mot spécial ``, qui est souvent appelé un *mot masqué*. D'autres modèles permettant de remplacer les mots manquants peuvent avoir des mots masqués différents, donc il est toujours bon de vérifier le mot masqué approprié lorsque vous comparez d'autres modèles. Une façon de le vérifier est de regarder le mot masqué utilisé dans l'outil de test de la page du modèle. + + + +✏️ **Essayez !** Recherchez le modèle `bert-base-cased` sur le *Hub* et identifiez le mot masqué dans l'outil d'inférence. Que prédit le modèle pour la phrase dans notre exemple de pipeline au-dessus ? + + + +## Reconnaissance d'entités nommées + +La reconnaissance d'entités nommées ou NER (pour *Named Entity Recognition*) est une tâche où le modèle doit trouver les parties du texte d'entrée qui correspondent à des entités telles que des personnes, des lieux ou des organisations. Voyons un exemple : + +```python +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner( + "My name is Sylvain and I work at Hugging Face in Brooklyn." +) # Je m'appelle Sylvain et je travaille à Hugging Face à Brooklyn. +``` + +```python out +[{'entity_group': 'PER', 'score': 0.99816, 'word': 'Sylvain', 'start': 11, 'end': 18}, + {'entity_group': 'ORG', 'score': 0.97960, 'word': 'Hugging Face', 'start': 33, 'end': 45}, + {'entity_group': 'LOC', 'score': 0.99321, 'word': 'Brooklyn', 'start': 49, 'end': 57} +] +``` + +Nous pouvons voir que le modèle a correctement identifié Sylvain comme une personne (PER), Hugging Face comme une organisation (ORG) et Brooklyn comme un lieu (LOC). + +Il est possible d'utiliser l'option `grouped_entities=True` lors de la création du pipeline pour regrouper les parties du texte qui correspondent à la même entité : ici le modèle à correctement regroupé `Hugging` et `Face` comme une seule organisation, même si le nom comporte plusieurs mots. En effet, comme nous allons voir dans le prochain chapitre, la prétraitement du texte sépare parfois certains mots en plus petites parties. Par exemple, `Sylvain` est séparé en quatre morceaux : `S`, `##yl`, `##va`, et `##in`. Dans l'étape de post-traitement, le pipeline a réussi à regrouper ces morceaux. + + + +✏️ **Essayez !** Recherchez sur le *Hub* un modèle capable de reconnaître les différentes parties du langage (généralement abrégé en POS pour *Part-of-speech*) en anglais. Que prédit le modèle pour la phrase dans notre exemple du pipeline au-dessus ? + + + +## Réponse à des questions + +Le pipeline `question-answering` répond à des questions en utilisant des informations données en contexte : + +```python +from transformers import pipeline + +question_answerer = pipeline("question-answering") +question_answerer( + question="Where do I work?", # Où est-ce que je travaille ? + context="My name is Sylvain and I work at Hugging Face in Brooklyn", + # Je m'appelle Sylvain et je travaille à Hugging Face à Brooklyn. +) +``` + +```python out +{'score': 0.6385916471481323, 'start': 33, 'end': 45, 'answer': 'Hugging Face'} +``` + +Notez que ce pipeline fonctionne par extraction d'information depuis le contexte fourni, il ne génère pas la réponse. + +## Résumé + +Le résumé est une tâche de réduction d'un texte en un texte plus court, tout en gardant tous (ou presque tous) les aspects importants référencés dans le texte. Voici un exemple : + +```python +from transformers import pipeline + +summarizer = pipeline("summarization") +summarizer( + """ + America has changed dramatically during recent years. Not only has the number of + graduates in traditional engineering disciplines such as mechanical, civil, + electrical, chemical, and aeronautical engineering declined, but in most of + the premier American universities engineering curricula now concentrate on + and encourage largely the study of engineering science. As a result, there + are declining offerings in engineering subjects dealing with infrastructure, + the environment, and related issues, and greater concentration on high + technology subjects, largely supporting increasingly complex scientific + developments. While the latter is important, it should not be at the expense + of more traditional engineering. + + Rapidly developing economies such as China and India, as well as other + industrial countries in Europe and Asia, continue to encourage and advance + the teaching of engineering. Both China and India, respectively, graduate + six and eight times as many traditional engineers as does the United States. + Other industrial countries at minimum maintain their output, while America + suffers an increasingly serious decline in the number of engineering graduates + and a lack of well-educated engineers. +""" +) + +""" + L'Amérique a changé de façon spectaculaire au cours des dernières années. Non seulement le nombre de + diplômés dans les disciplines traditionnelles de l'ingénierie telles que le génie mécanique, civil, + l'électricité, la chimie et l'aéronautique a diminué, mais dans la plupart + des grandes universités américaines, les programmes d'études d'ingénierie se concentrent désormais sur + et encouragent largement l'étude des sciences de l'ingénieur. Par conséquent, il y a + de moins en moins d'offres dans les sujets d'ingénierie traitant de l'infrastructure, + l'environnement et les questions connexes, et une plus grande concentration sur les sujets de haute + technologie, qui soutiennent en grande partie des développements scientifiques de plus en plus + complexes. Si cette dernière est importante, elle ne doit pas se faire au détriment + de l'ingénierie plus traditionnelle. + + Les économies en développement rapide telles que la Chine et l'Inde, ainsi que d'autres + pays industrialisés d'Europe et d'Asie, continuent d'encourager et de promouvoir + l'enseignement de l'ingénierie. La Chine et l'Inde, respectivement, diplôment + six et huit fois plus d'ingénieurs traditionnels que les États-Unis. + Les autres pays industriels maintiennent au minimum leur production, tandis que l'Amérique + souffre d'une baisse de plus en plus importante du nombre de diplômés en ingénierie + et un manque d'ingénieurs bien formés. +""" +``` + +```python out +[{'summary_text': ' America has changed dramatically during recent years . The ' + # L'Amérique a changé de façon spectaculaire au cours des dernières années. Le + 'number of engineering graduates in the U.S. has declined in ' + # nombre de diplômés en ingénierie aux États-Unis a diminué dans + 'traditional engineering disciplines such as mechanical, civil ' + # dans les disciplines traditionnelles de l'ingénierie, telles que le génie mécanique, civil + ', electrical, chemical, and aeronautical engineering . Rapidly ' + # l'électricité, la chimie et l'aéronautique. Les économies + 'developing economies such as China and India, as well as other ' + # en développement rapide comme la Chine et l'Inde, ainsi que d'autres + 'industrial countries in Europe and Asia, continue to encourage ' + # pays industriels d'Europe et d'Asie, continuent d'encourager + 'and advance engineering.'}] + # et à faire progresser l'ingénierie. +``` + +Comme pour la génération de texte, vous pouvez spécifier une `max_length` (longueur maximale) ou une `min_length` (longueur minimale) pour le résultat. + + +## Traduction + +Pour la traduction, vous pouvez utiliser un modèle par défaut si vous fournissez un couple de langues dans le nom de la tâche (comme `"translation_en_to_fr"`), mais le plus simple reste d'utiliser un modèle adéquat disponible sur le [*Hub*](https://huggingface.co/models). Ici, nous allons essayer de traduire du français en anglais : + +```python +from transformers import pipeline + +translator = pipeline("translation", model="Helsinki-NLP/opus-mt-fr-en") +translator("Ce cours est produit par Hugging Face.") +``` + +```python out +[{'translation_text': 'This course is produced by Hugging Face.'}] +``` + +Comme pour la génération de texte et le résumé de texte, il est possible de spécifier une `max_length` (longueur maximale) ou une `min_length` (longueur minimale) pour le résultat. + + + +✏️ **Essayez !** Recherchez d'autres modèles de traduction sur le Hub et essayez de traduire la phrase précédente en plusieurs langues différentes. + + + +Les pipelines présentés jusqu'ici sont principalement destinés à des fins de démonstration. Ils ont été programmés pour des tâches spécifiques et ne peuvent pas effectuer de variations de celles-ci. Dans le chapitre suivant, vous apprendrez ce qu'il y a dans un `pipeline()` et comment modifier son comportement. diff --git a/chapters/fr/chapter1/4.mdx b/chapters/fr/chapter1/4.mdx index dc996234e..28de8aa80 100644 --- a/chapters/fr/chapter1/4.mdx +++ b/chapters/fr/chapter1/4.mdx @@ -1,169 +1,174 @@ -# Comment fonctionnent les transformers ? - -Dans cette partie, nous allons jeter un coup d'œil à l'architecture des *transformers*. - -## Court historique des transformers - -Voici quelques dates clefs dans la courte histoire des *transformers* : - -
-A brief chronology of Transformers models. - -
- -[L'architecture *Transformer*](https://arxiv.org/abs/1706.03762) a été présentée en juin 2017. Initialement, la recherche portait sur la tâche de traduction. Elle a été suivie par l'introduction de plusieurs modèles influents, notamment : - -- **Juin 2018** : [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), le premier *transformer* pré-entraîné et *finetuné* sur différentes tâches de NLP et ayant obtenu des résultats à l'état de l'art, - -- **Octobre 2018** : [BERT](https://arxiv.org/abs/1810.04805), autre grand modèle pré-entraîné ayant été construit pour produire de meilleurs résumés de texte (plus de détails dans le chapitre suivant !), - -- **Février 2019** : [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), une version améliorée (et plus grande) de GPT qui n'a pas été directement rendu publique pour cause de raisons éthiques, - -- **Octobre 2019** : [DistilBERT](https://arxiv.org/abs/1910.01108), une version distillée de BERT étant 60% plus rapide, 40% plus légère en mémoire et conservant tout de même 97% des performances initiales de BERT, - -- **Octobre 2019** : [BART](https://arxiv.org/abs/1910.13461) et [T5](https://arxiv.org/abs/1910.10683), deux modèles pré-entraînés utilisant la même architecture que le *transformer* original (les premiers à faire cela), - -- **Mai 2020** : [GPT-3](https://arxiv.org/abs/2005.14165), une version encore plus grande que GPT-2 ayant des performances très bonnes sur une variété de tâches ne nécessitant pas de *finetuning* (appelé _zero-shot learning_). - -Cette liste est loin d'être exhaustive et met en lumière certains *transformers*. Plus largement, ces modèles peuvent être regroupés en trois catégories : - -- ceux de type GPT (aussi appelés *transformers* _autorégressifs_) -- ceux de type BERT (aussi appelés *transformers* _auto-encodeurs_) -- ceux de type BART/T5 (aussi appelés *transformers* _séquence-à-séquence_) - -Nous verrons plus en profondeur ces familles de modèles plus tard. - -## Les transformers sont des modèles de langage - -Tous les *transformers* mentionnés ci-dessus (GPT, BERT, BART, T5, etc.) ont été entraînés comme des *modèles de langage*. Cela signifie qu'ils ont été entraînés sur une large quantité de textes bruts de manière autosupervisée. L'apprentissage autosupervisé est un type d'entraînement dans lequel l'objectif est automatiquement calculé à partir des entrées du modèle. Cela signifie que les humains ne sont pas nécessaires pour étiqueter les données ! - -Ce type de modèle développe une compréhension statistique de la langue sur laquelle il a été entraîné, mais il n'est pas très utile pour des tâches pratiques spécifiques. Pour cette raison, le modèle pré-entraîné passe ensuite par un processus appelé apprentissage par transfert. Au cours de ce processus, le modèle est *finetuné* de manière supervisée (c'est-à-dire en utilisant des étiquettes annotées par des humains) pour une tâche donnée. - -Un exemple de tâche consiste à prédire le mot suivant dans une phrase après avoir lu les *n* mots précédents. Cette tâche est appelée *modélisation causale du langage* car la sortie dépend des entrées passées et présentes, mais pas des entrées futures. -
-Example of causal language modeling in which the next word from a sentence is predicted. - -
- -Un autre exemple est la *modélisation du langage masqué*, dans laquelle le modèle prédit un mot masqué dans la phrase. - -
-Example of masked language modeling in which a masked word from a sentence is predicted. - -
- -## Les transformers sont énormes - -En dehors de quelques exceptions (comme DistilBERT), la stratégie générale pour obtenir de meilleure performance consiste à augmenter la taille des modèles ainsi que la quantité de données utilisées pour l'entraînement de ces derniers. - -
-Number of parameters of recent Transformers models -
- -Malheureusement, entraîner un modèle et particulièrement un très grand modèle, nécessite une importante quantité de données. Cela devient très coûteux en termes de temps et de ressources de calcul. Cela se traduit même par un impact environnemental comme le montre le graphique suivant. - -
-The carbon footprint of a large language model. - -
- - - -L'image montre l'empreinte carbone pour un projet d'entraînement d'un (très grand) modèle mené par une équipe qui pourtant essaie consciemment de réduire l'impact environnemental du pré-entraînement. L'empreinte de l'exécution de nombreux essais pour obtenir les meilleurs hyperparamètres serait encore plus élevée. - -Imaginez qu'à chaque fois qu'une équipe de recherche, une association d'étudiants ou une entreprise souhaite entraîner un modèle, elle le fasse en partant de zéro. Cela entraînerait des coûts globaux énormes et inutiles ! - -C'est pourquoi le partage des modèles du langage est primordial : partager les poids d'entraînement et construire à partir de ces poids permet de réduire les coûts de calcul globaux ainsi que l'empreinte carbone de toute la communauté. - -## L'apprentissage par transfert - - - -Le pré-entraînement consiste à entraîner un modèle à partir de zéro : les poids sont initialisés de manière aléatoire et l'entraînement commence sans aucune connaissance préalable. - -
-The pretraining of a language model is costly in both time and money. - -
- -Ce pré-entraînement est généralement effectué sur de très grandes quantités de données. Il nécessite donc un très grand corpus de données et l'entraînement peut prendre jusqu'à plusieurs semaines. - -Le *finetuning*, quant à lui, est l'entrainement effectué après qu'un modèle ait été pré-entraîné. Pour effectuer un *finetuning*, vous devez d'abord acquérir un modèle de langue pré-entraîné, puis effectuer un entraînement supplémentaire avec un jeu de données spécifiques. Mais pourquoi ne pas entraîner directement pour la tâche finale ? Il y a plusieurs raisons à cela : - -* Le modèle pré-entraîné a déjà été entraîné sur un jeu de données qui présente certaines similitudes avec le jeu de données de *finetuning*. Le processus de *finetuning* est donc en mesure de tirer parti des connaissances acquises par le modèle initial lors du pré-entraînement (par exemple, pour les problèmes de langage naturel, le modèle pré-entraîné aura une certaine compréhension statistique de la langue que vous utilisez pour votre tâche) -* Comme le modèle pré-entraîné a déjà été entraîné sur de nombreuses données, le *finetuning* nécessite beaucoup moins de données pour obtenir des résultats décents. -* Pour la même raison, le temps et les ressources nécessaires pour obtenir de bons résultats sont beaucoup moins importants. - -Par exemple, il est possible d'exploiter un modèle pré-entraîné entraîné sur la langue anglaise, puis de le *finetuner* sur un corpus arXiv, pour obtenir un modèle basé sur la science et la recherche. Le *finetuning* ne nécessitera qu'une quantité limitée de données : les connaissances acquises par le modèle pré-entraîné sont « transférées », d'où le terme d'apprentissage par transfert. - -
-The fine-tuning of a language model is cheaper than pretraining in both time and money. - -
- -Le *finetuning* d'un modèle a donc un coût moindre en termes de temps, de données, de finances et d'environnement. Il est aussi plus rapide et plus facile d'itérer sur différents schémas de *finetuning* car l'entraînement est moins contraignant qu'un pré-entraînement complet. - -Ce processus permet également d'obtenir de meilleurs résultats que l'entraînement à partir de zéro (à moins que vous ne disposiez d'un grand nombre de données). C'est pourquoi vous devez toujours essayer de tirer parti d'un modèle pré-entraîné, c'est-à-dire un modèle aussi proche que possible de la tâche que vous avez à accomplir, et de le *finetuner*. - -## Architecture générale - -Dans cette section, nous allons voir l'architecture générale des *transformers*. Pas d'inquiétudes si vous ne comprenez pas tous les concepts, des sections détaillées qui couvrent chaque composant seront abordées plus tard. - - - -## Introduction - -Le modèle est principalement composé de deux blocs : - -* **Encodeur (à gauche)** : l'encodeur reçoit une entrée et construit une représentation de celle-ci (ses caractéristiques). Cela signifie que le modèle est optimisé pour acquérir une compréhension venant de ces entrées. -* **Décodeur (à droite)** : le décodeur utilise la représentation de l'encodeur (les caractéristiques) en plus des autres entrées pour générer une séquence cible. Cela signifie que le modèle est optimisé pour générer des sorties. - -
-Architecture of a Transformers models - -
- -Chacun de ces blocs peuvent être utilisés indépendamment en fonction de la tâche que l'on souhaite traiter : - -* **Modèles uniquement encodeurs** : adaptés pour des tâches qui nécessitent une compréhension de l'entrée, comme la classification de phrases et la reconnaissance d'entités nommées. -* **Modèles uniquement décodeurs** : adaptés pour les tâches génératives telles que la génération de texte. -* **Modèles encodeurs-décodeurs** (ou **modèles de séquence-à-séquence**) : adaptés aux tâches génératives qui nécessitent une entrée, telles que la traduction ou le résumé de texte. - -Nous verrons plus en détails chacune de ces architectures plus tard. - -## Les couches d'attention - -Une caractéristique clé des *transformers* est qu'ils sont construits avec des couches spéciales appelées couches d'attention. En fait, le titre du papier introduisant l'architecture *transformer* se nomme [*Attention Is All You Need*](https://arxiv.org/abs/1706.03762) ! Nous explorerons les détails des couches d'attention plus tard dans le cours. Pour l'instant, tout ce que vous devez savoir est que cette couche indique au modèle de prêter une attention spécifique à certains mots de la phrase que vous lui avez passée (et d'ignorer plus ou moins les autres) lors du traitement de la représentation de chaque mot. - -Pour mettre cela en contexte, considérons la tâche de traduire un texte de l'anglais au français. Étant donné l'entrée « *You like this course* », un modèle de traduction devra également s'intéresser au mot adjacent « *You* » pour obtenir la traduction correcte du mot « *like* », car en français le verbe « *like* » se conjugue différemment selon le sujet. Le reste de la phrase n'est en revanche pas utile pour la traduction de ce mot. Dans le même ordre d'idées, pour traduire « *this* », le modèle devra également faire attention au mot « *course* » car « *this* » se traduit différemment selon que le nom associé est masculin ou féminin. Là encore, les autres mots de la phrase n'auront aucune importance pour la traduction de « *this* ». Avec des phrases plus complexes (et des règles de grammaire plus complexes), le modèle devra prêter une attention particulière aux mots qui pourraient apparaître plus loin dans la phrase pour traduire correctement chaque mot. - -Le même concept s'applique à toute tâche associée au langage naturel : un mot en lui-même a un sens, mais ce sens est profondément affecté par le contexte, qui peut être n'importe quel autre mot (ou mots) avant ou après le mot étudié. - -Maintenant que vous avez une idée plus précise des couches d'attentions, nous allons regarder de plus près l'architecture des *transformers*. - -## L'architecture originale - -L'architecture du *transformer* a initialement été construite pour la tâche de traduction. Pendant l'entraînement, l'encodeur reçoit des entrées (des phrases) dans une certaine langue, tandis que le décodeur reçoit la même phrase traduite dans la langue cible. Pour l'encodeur, les couches d'attention peuvent utiliser tous les mots d'une phrase (puisque comme nous venons de le voir, la traduction d'un mot donné peut dépendre de ce qui le suit ou le précède dans la phrase). Le décodeur, quant à lui, fonctionne de façon séquentielle et ne peut porter son attention qu'aux mots déjà traduits dans la phrase (donc uniquement les mots générés avant le mot en cours). Par exemple, lorsqu'on a prédit les trois premiers mots de la phrase cible, on les donne au décodeur qui utilise alors toutes les entrées de l'encodeur pour essayer de prédire le quatrième mot. - -Pour accélérer les choses pendant l'apprentissage (lorsque le modèle a accès aux phrases cibles), le décodeur est alimenté avec la cible entière, mais il n'est pas autorisé à utiliser les mots futurs (s'il avait accès au mot en position 2 lorsqu'il essayait de prédire le mot en position 2, le problème ne serait pas très difficile !). Par exemple, en essayant de prédire le quatrième mot, la couche d'attention n'aura accès qu'aux mots des positions 1 à 3. - -L'architecture originale du *transformer* ressemble à ceci, avec l'encodeur à gauche et le décodeur à droite : - -
-Architecture of a Transformers models - -
- -Notez que la première couche d'attention dans un bloc décodeur prête attention à toutes les entrées (passées) du décodeur, mais que la deuxième couche d'attention utilise la sortie de l'encodeur. Elle peut donc accéder à l'ensemble de la phrase d'entrée pour prédire au mieux le mot actuel. C'est très utile, car différentes langues peuvent avoir des règles grammaticales qui placent les mots dans un ordre différent, ou un contexte fourni plus tard dans la phrase peut être utile pour déterminer la meilleure traduction d'un mot donné. - -Le *masque d'attention* peut également être utilisé dans l'encodeur/décodeur pour empêcher le modèle de prêter attention à certains mots spéciaux. Par exemple, le mot de remplissage spécial (le *padding*) utilisé pour que toutes les entrées aient la même longueur lors du regroupement de phrases. - -## Architectures contre checkpoints - -En approfondissant l'étude des transformers dans ce cours, vous verrez des mentions d'architectures et de checkpoints ainsi que de modèles. Ces termes ont tous des significations légèrement différentes : - -* **Architecture** : c'est le squelette du modèle, la définition de chaque couche et chaque opération qui se produit au sein du modèle. -* **Checkpoints** : ce sont les poids qui seront chargés dans une architecture donnée. -* **Modèle** : c'est un mot valise n'étant pas aussi précis que les mots « architecture » ou « *checkpoint* ». Il peut désigner l'un comme l'autre. Dans ce cours, il sera spécifié *architecture* ou *checkpoint* lorsqu'il sera essentiel de réduire toute ambiguïté. - -Par exemple, BERT est une architecture alors que `bert-base-cased` (un ensemble de poids entraîné par l'équipe de Google lors de la première sortie de BERT) est un *checkpoint*. Cependant, il est possible de dire « le modèle BERT » et « le modèle `bert-base-cased` ». +# Comment fonctionnent les transformers ? + + + +Dans cette partie, nous allons jeter un coup d'œil à l'architecture des *transformers*. + +## Court historique des transformers + +Voici quelques dates clefs dans la courte histoire des *transformers* : + +
+A brief chronology of Transformers models. + +
+ +[L'architecture *Transformer*](https://arxiv.org/abs/1706.03762) a été présentée en juin 2017. Initialement, la recherche portait sur la tâche de traduction. Elle a été suivie par l'introduction de plusieurs modèles influents, notamment : + +- **Juin 2018** : [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), le premier *transformer* pré-entraîné et *finetuné* sur différentes tâches de NLP et ayant obtenu des résultats à l'état de l'art, + +- **Octobre 2018** : [BERT](https://arxiv.org/abs/1810.04805), autre grand modèle pré-entraîné ayant été construit pour produire de meilleurs résumés de texte (plus de détails dans le chapitre suivant !), + +- **Février 2019** : [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), une version améliorée (et plus grande) de GPT qui n'a pas été directement rendu publique pour cause de raisons éthiques, + +- **Octobre 2019** : [DistilBERT](https://arxiv.org/abs/1910.01108), une version distillée de BERT étant 60% plus rapide, 40% plus légère en mémoire et conservant tout de même 97% des performances initiales de BERT, + +- **Octobre 2019** : [BART](https://arxiv.org/abs/1910.13461) et [T5](https://arxiv.org/abs/1910.10683), deux modèles pré-entraînés utilisant la même architecture que le *transformer* original (les premiers à faire cela), + +- **Mai 2020** : [GPT-3](https://arxiv.org/abs/2005.14165), une version encore plus grande que GPT-2 ayant des performances très bonnes sur une variété de tâches ne nécessitant pas de *finetuning* (appelé _zero-shot learning_). + +Cette liste est loin d'être exhaustive et met en lumière certains *transformers*. Plus largement, ces modèles peuvent être regroupés en trois catégories : + +- ceux de type GPT (aussi appelés *transformers* _autorégressifs_) +- ceux de type BERT (aussi appelés *transformers* _auto-encodeurs_) +- ceux de type BART/T5 (aussi appelés *transformers* _séquence-à-séquence_) + +Nous verrons plus en profondeur ces familles de modèles plus tard. + +## Les transformers sont des modèles de langage + +Tous les *transformers* mentionnés ci-dessus (GPT, BERT, BART, T5, etc.) ont été entraînés comme des *modèles de langage*. Cela signifie qu'ils ont été entraînés sur une large quantité de textes bruts de manière autosupervisée. L'apprentissage autosupervisé est un type d'entraînement dans lequel l'objectif est automatiquement calculé à partir des entrées du modèle. Cela signifie que les humains ne sont pas nécessaires pour étiqueter les données ! + +Ce type de modèle développe une compréhension statistique de la langue sur laquelle il a été entraîné, mais il n'est pas très utile pour des tâches pratiques spécifiques. Pour cette raison, le modèle pré-entraîné passe ensuite par un processus appelé apprentissage par transfert. Au cours de ce processus, le modèle est *finetuné* de manière supervisée (c'est-à-dire en utilisant des étiquettes annotées par des humains) pour une tâche donnée. + +Un exemple de tâche consiste à prédire le mot suivant dans une phrase après avoir lu les *n* mots précédents. Cette tâche est appelée *modélisation causale du langage* car la sortie dépend des entrées passées et présentes, mais pas des entrées futures. +
+Example of causal language modeling in which the next word from a sentence is predicted. + +
+ +Un autre exemple est la *modélisation du langage masqué*, dans laquelle le modèle prédit un mot masqué dans la phrase. + +
+Example of masked language modeling in which a masked word from a sentence is predicted. + +
+ +## Les transformers sont énormes + +En dehors de quelques exceptions (comme DistilBERT), la stratégie générale pour obtenir de meilleure performance consiste à augmenter la taille des modèles ainsi que la quantité de données utilisées pour l'entraînement de ces derniers. + +
+Number of parameters of recent Transformers models +
+ +Malheureusement, entraîner un modèle et particulièrement un très grand modèle, nécessite une importante quantité de données. Cela devient très coûteux en termes de temps et de ressources de calcul. Cela se traduit même par un impact environnemental comme le montre le graphique suivant. + +
+The carbon footprint of a large language model. + +
+ + + +L'image montre l'empreinte carbone pour un projet d'entraînement d'un (très grand) modèle mené par une équipe qui pourtant essaie consciemment de réduire l'impact environnemental du pré-entraînement. L'empreinte de l'exécution de nombreux essais pour obtenir les meilleurs hyperparamètres serait encore plus élevée. + +Imaginez qu'à chaque fois qu'une équipe de recherche, une association d'étudiants ou une entreprise souhaite entraîner un modèle, elle le fasse en partant de zéro. Cela entraînerait des coûts globaux énormes et inutiles ! + +C'est pourquoi le partage des modèles du langage est primordial : partager les poids d'entraînement et construire à partir de ces poids permet de réduire les coûts de calcul globaux ainsi que l'empreinte carbone de toute la communauté. + +## L'apprentissage par transfert + + + +Le pré-entraînement consiste à entraîner un modèle à partir de zéro : les poids sont initialisés de manière aléatoire et l'entraînement commence sans aucune connaissance préalable. + +
+The pretraining of a language model is costly in both time and money. + +
+ +Ce pré-entraînement est généralement effectué sur de très grandes quantités de données. Il nécessite donc un très grand corpus de données et l'entraînement peut prendre jusqu'à plusieurs semaines. + +Le *finetuning*, quant à lui, est l'entrainement effectué après qu'un modèle ait été pré-entraîné. Pour effectuer un *finetuning*, vous devez d'abord acquérir un modèle de langue pré-entraîné, puis effectuer un entraînement supplémentaire avec un jeu de données spécifiques. Mais pourquoi ne pas entraîner directement pour la tâche finale ? Il y a plusieurs raisons à cela : + +* Le modèle pré-entraîné a déjà été entraîné sur un jeu de données qui présente certaines similitudes avec le jeu de données de *finetuning*. Le processus de *finetuning* est donc en mesure de tirer parti des connaissances acquises par le modèle initial lors du pré-entraînement (par exemple, pour les problèmes de langage naturel, le modèle pré-entraîné aura une certaine compréhension statistique de la langue que vous utilisez pour votre tâche) +* Comme le modèle pré-entraîné a déjà été entraîné sur de nombreuses données, le *finetuning* nécessite beaucoup moins de données pour obtenir des résultats décents. +* Pour la même raison, le temps et les ressources nécessaires pour obtenir de bons résultats sont beaucoup moins importants. + +Par exemple, il est possible d'exploiter un modèle pré-entraîné entraîné sur la langue anglaise, puis de le *finetuner* sur un corpus arXiv, pour obtenir un modèle basé sur la science et la recherche. Le *finetuning* ne nécessitera qu'une quantité limitée de données : les connaissances acquises par le modèle pré-entraîné sont « transférées », d'où le terme d'apprentissage par transfert. + +
+The fine-tuning of a language model is cheaper than pretraining in both time and money. + +
+ +Le *finetuning* d'un modèle a donc un coût moindre en termes de temps, de données, de finances et d'environnement. Il est aussi plus rapide et plus facile d'itérer sur différents schémas de *finetuning* car l'entraînement est moins contraignant qu'un pré-entraînement complet. + +Ce processus permet également d'obtenir de meilleurs résultats que l'entraînement à partir de zéro (à moins que vous ne disposiez d'un grand nombre de données). C'est pourquoi vous devez toujours essayer de tirer parti d'un modèle pré-entraîné, c'est-à-dire un modèle aussi proche que possible de la tâche que vous avez à accomplir, et de le *finetuner*. + +## Architecture générale + +Dans cette section, nous allons voir l'architecture générale des *transformers*. Pas d'inquiétudes si vous ne comprenez pas tous les concepts, des sections détaillées qui couvrent chaque composant seront abordées plus tard. + + + +## Introduction + +Le modèle est principalement composé de deux blocs : + +* **Encodeur (à gauche)** : l'encodeur reçoit une entrée et construit une représentation de celle-ci (ses caractéristiques). Cela signifie que le modèle est optimisé pour acquérir une compréhension venant de ces entrées. +* **Décodeur (à droite)** : le décodeur utilise la représentation de l'encodeur (les caractéristiques) en plus des autres entrées pour générer une séquence cible. Cela signifie que le modèle est optimisé pour générer des sorties. + +
+Architecture of a Transformers models + +
+ +Chacun de ces blocs peuvent être utilisés indépendamment en fonction de la tâche que l'on souhaite traiter : + +* **Modèles uniquement encodeurs** : adaptés pour des tâches qui nécessitent une compréhension de l'entrée, comme la classification de phrases et la reconnaissance d'entités nommées. +* **Modèles uniquement décodeurs** : adaptés pour les tâches génératives telles que la génération de texte. +* **Modèles encodeurs-décodeurs** (ou **modèles de séquence-à-séquence**) : adaptés aux tâches génératives qui nécessitent une entrée, telles que la traduction ou le résumé de texte. + +Nous verrons plus en détails chacune de ces architectures plus tard. + +## Les couches d'attention + +Une caractéristique clé des *transformers* est qu'ils sont construits avec des couches spéciales appelées couches d'attention. En fait, le titre du papier introduisant l'architecture *transformer* se nomme [*Attention Is All You Need*](https://arxiv.org/abs/1706.03762) ! Nous explorerons les détails des couches d'attention plus tard dans le cours. Pour l'instant, tout ce que vous devez savoir est que cette couche indique au modèle de prêter une attention spécifique à certains mots de la phrase que vous lui avez passée (et d'ignorer plus ou moins les autres) lors du traitement de la représentation de chaque mot. + +Pour mettre cela en contexte, considérons la tâche de traduire un texte de l'anglais au français. Étant donné l'entrée « *You like this course* », un modèle de traduction devra également s'intéresser au mot adjacent « *You* » pour obtenir la traduction correcte du mot « *like* », car en français le verbe « *like* » se conjugue différemment selon le sujet. Le reste de la phrase n'est en revanche pas utile pour la traduction de ce mot. Dans le même ordre d'idées, pour traduire « *this* », le modèle devra également faire attention au mot « *course* » car « *this* » se traduit différemment selon que le nom associé est masculin ou féminin. Là encore, les autres mots de la phrase n'auront aucune importance pour la traduction de « *this* ». Avec des phrases plus complexes (et des règles de grammaire plus complexes), le modèle devra prêter une attention particulière aux mots qui pourraient apparaître plus loin dans la phrase pour traduire correctement chaque mot. + +Le même concept s'applique à toute tâche associée au langage naturel : un mot en lui-même a un sens, mais ce sens est profondément affecté par le contexte, qui peut être n'importe quel autre mot (ou mots) avant ou après le mot étudié. + +Maintenant que vous avez une idée plus précise des couches d'attentions, nous allons regarder de plus près l'architecture des *transformers*. + +## L'architecture originale + +L'architecture du *transformer* a initialement été construite pour la tâche de traduction. Pendant l'entraînement, l'encodeur reçoit des entrées (des phrases) dans une certaine langue, tandis que le décodeur reçoit la même phrase traduite dans la langue cible. Pour l'encodeur, les couches d'attention peuvent utiliser tous les mots d'une phrase (puisque comme nous venons de le voir, la traduction d'un mot donné peut dépendre de ce qui le suit ou le précède dans la phrase). Le décodeur, quant à lui, fonctionne de façon séquentielle et ne peut porter son attention qu'aux mots déjà traduits dans la phrase (donc uniquement les mots générés avant le mot en cours). Par exemple, lorsqu'on a prédit les trois premiers mots de la phrase cible, on les donne au décodeur qui utilise alors toutes les entrées de l'encodeur pour essayer de prédire le quatrième mot. + +Pour accélérer les choses pendant l'apprentissage (lorsque le modèle a accès aux phrases cibles), le décodeur est alimenté avec la cible entière, mais il n'est pas autorisé à utiliser les mots futurs (s'il avait accès au mot en position 2 lorsqu'il essayait de prédire le mot en position 2, le problème ne serait pas très difficile !). Par exemple, en essayant de prédire le quatrième mot, la couche d'attention n'aura accès qu'aux mots des positions 1 à 3. + +L'architecture originale du *transformer* ressemble à ceci, avec l'encodeur à gauche et le décodeur à droite : + +
+Architecture of a Transformers models + +
+ +Notez que la première couche d'attention dans un bloc décodeur prête attention à toutes les entrées (passées) du décodeur, mais que la deuxième couche d'attention utilise la sortie de l'encodeur. Elle peut donc accéder à l'ensemble de la phrase d'entrée pour prédire au mieux le mot actuel. C'est très utile, car différentes langues peuvent avoir des règles grammaticales qui placent les mots dans un ordre différent, ou un contexte fourni plus tard dans la phrase peut être utile pour déterminer la meilleure traduction d'un mot donné. + +Le *masque d'attention* peut également être utilisé dans l'encodeur/décodeur pour empêcher le modèle de prêter attention à certains mots spéciaux. Par exemple, le mot de remplissage spécial (le *padding*) utilisé pour que toutes les entrées aient la même longueur lors du regroupement de phrases. + +## Architectures contre checkpoints + +En approfondissant l'étude des transformers dans ce cours, vous verrez des mentions d'architectures et de checkpoints ainsi que de modèles. Ces termes ont tous des significations légèrement différentes : + +* **Architecture** : c'est le squelette du modèle, la définition de chaque couche et chaque opération qui se produit au sein du modèle. +* **Checkpoints** : ce sont les poids qui seront chargés dans une architecture donnée. +* **Modèle** : c'est un mot valise n'étant pas aussi précis que les mots « architecture » ou « *checkpoint* ». Il peut désigner l'un comme l'autre. Dans ce cours, il sera spécifié *architecture* ou *checkpoint* lorsqu'il sera essentiel de réduire toute ambiguïté. + +Par exemple, BERT est une architecture alors que `bert-base-cased` (un ensemble de poids entraîné par l'équipe de Google lors de la première sortie de BERT) est un *checkpoint*. Cependant, il est possible de dire « le modèle BERT » et « le modèle `bert-base-cased` ». diff --git a/chapters/fr/chapter1/5.mdx b/chapters/fr/chapter1/5.mdx index 0f1fe9d3a..92d30c2a9 100644 --- a/chapters/fr/chapter1/5.mdx +++ b/chapters/fr/chapter1/5.mdx @@ -1,17 +1,22 @@ -# Les modèles basés sur l'encodeur - - - -Les modèles basés sur l'encodeur utilisent uniquement l'encodeur d'un *transformer*. À chaque étape, les couches d'attention peuvent accéder à tous les mots de la phrase initiale. Ces modèles sont souvent caractérisés comme ayant une attention bidirectionnelle et sont souvent appelés *modèles d'auto-encodage*. - -Le pré-entraînement de ces modèles se concentre généralement sur la modification d'une phrase donnée (par exemple, en masquant des mots aléatoires dans celle-ci) et en demandant au modèle de trouver ou de reconstruire la phrase initiale. - -Ces modèles sont les plus adaptés pour des tâches qui requièrent une compréhension complète de la phrase, telles que la classification de phrases, la reconnaissance d'entités nommées (et plus généralement la classification de mots) et les questions-réponses extractives. - -Les modèles les plus représentatifs de cette famille sont : - -- [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) +# Les modèles basés sur l'encodeur + + + + + +Les modèles basés sur l'encodeur utilisent uniquement l'encodeur d'un *transformer*. À chaque étape, les couches d'attention peuvent accéder à tous les mots de la phrase initiale. Ces modèles sont souvent caractérisés comme ayant une attention bidirectionnelle et sont souvent appelés *modèles d'auto-encodage*. + +Le pré-entraînement de ces modèles se concentre généralement sur la modification d'une phrase donnée (par exemple, en masquant des mots aléatoires dans celle-ci) et en demandant au modèle de trouver ou de reconstruire la phrase initiale. + +Ces modèles sont les plus adaptés pour des tâches qui requièrent une compréhension complète de la phrase, telles que la classification de phrases, la reconnaissance d'entités nommées (et plus généralement la classification de mots) et les questions-réponses extractives. + +Les modèles les plus représentatifs de cette famille sont : + +- [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) diff --git a/chapters/fr/chapter1/6.mdx b/chapters/fr/chapter1/6.mdx index 338d6d25c..b2fbf236c 100644 --- a/chapters/fr/chapter1/6.mdx +++ b/chapters/fr/chapter1/6.mdx @@ -1,16 +1,21 @@ -# Les modèles basés sur le décodeur - - - -Les modèles basés sur le décodeur utilisent seulement le décodeur d'un *transformer*. À chaque étape, pour un mot donné, les couches d'attention ne peuvent strictement accéder qu'aux mots situés avant dans la phrase. Ces modèles sont souvent appelés *modèles autorégressifs*. - -Le pré-entraînement des modèles basés sur le décodeur se concentre généralement sur la prédiction du prochain mot dans la phrase. - -Ces modèles sont vraiment adaptés aux tâches qui impliquent la génération de texte. - -Les modèles qui représentent le mieux la famille des modèles décodeurs sont : - -- [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) -- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) -- [GPT-2](https://huggingface.co/transformers/model_doc/gpt2.html) -- [Transformer XL](https://huggingface.co/transformers/model_doc/transformerxl.html) +# Les modèles basés sur le décodeur + + + + + +Les modèles basés sur le décodeur utilisent seulement le décodeur d'un *transformer*. À chaque étape, pour un mot donné, les couches d'attention ne peuvent strictement accéder qu'aux mots situés avant dans la phrase. Ces modèles sont souvent appelés *modèles autorégressifs*. + +Le pré-entraînement des modèles basés sur le décodeur se concentre généralement sur la prédiction du prochain mot dans la phrase. + +Ces modèles sont vraiment adaptés aux tâches qui impliquent la génération de texte. + +Les modèles qui représentent le mieux la famille des modèles décodeurs sont : + +- [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) +- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) +- [GPT-2](https://huggingface.co/transformers/model_doc/gpt2.html) +- [Transformer XL](https://huggingface.co/transformers/model_doc/transformerxl.html) diff --git a/chapters/fr/chapter1/7.mdx b/chapters/fr/chapter1/7.mdx index be4c527dc..7a94a5be3 100644 --- a/chapters/fr/chapter1/7.mdx +++ b/chapters/fr/chapter1/7.mdx @@ -1,16 +1,21 @@ -# Les modèles de séquence-à-séquence - - - -Les modèles encodeur-décodeur (également appelés modèles de séquence-à-séquence) utilisent les deux parties du *transformer*. À chaque étape, les couches d'attention de l'encodeur peuvent accéder à tous les mots de la phrase initiale, tandis que les couches d'attention du décodeur n'ont accès qu'aux mots positionnés avant un mot donné en entrée de ces couches d'attention. - -Le pré-entraînement de ces modèles peut être réalisé en utilisant les objectifs des modèles basés sur l'encodeur ou des modèles basés sur le décodeur. En général cela implique quelque chose de plus complexe. Par exemple, le modèle [T5](https://huggingface.co/t5-base) est pré-entraîné en remplaçant des zones aléatoires de texte (qui peuvent contenir plusieurs mots) par un masque spécial et l'objectif est alors de prédire le texte que ce masque cache. - -Les modèles de séquence-à-séquence sont les plus adaptés pour les tâches liées à la génération de nouvelles phrases en fonction d'une entrée donnée, comme le résumé de texte, la traduction ou la génération de questions-réponses. - -Les modèles qui représentent le mieux cette famille sont : - -- [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) +# Les modèles de séquence-à-séquence + + + + + +Les modèles encodeur-décodeur (également appelés modèles de séquence-à-séquence) utilisent les deux parties du *transformer*. À chaque étape, les couches d'attention de l'encodeur peuvent accéder à tous les mots de la phrase initiale, tandis que les couches d'attention du décodeur n'ont accès qu'aux mots positionnés avant un mot donné en entrée de ces couches d'attention. + +Le pré-entraînement de ces modèles peut être réalisé en utilisant les objectifs des modèles basés sur l'encodeur ou des modèles basés sur le décodeur. En général cela implique quelque chose de plus complexe. Par exemple, le modèle [T5](https://huggingface.co/t5-base) est pré-entraîné en remplaçant des zones aléatoires de texte (qui peuvent contenir plusieurs mots) par un masque spécial et l'objectif est alors de prédire le texte que ce masque cache. + +Les modèles de séquence-à-séquence sont les plus adaptés pour les tâches liées à la génération de nouvelles phrases en fonction d'une entrée donnée, comme le résumé de texte, la traduction ou la génération de questions-réponses. + +Les modèles qui représentent le mieux cette famille sont : + +- [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) diff --git a/chapters/fr/chapter1/8.mdx b/chapters/fr/chapter1/8.mdx index e3e3c2310..9ab2ccbb3 100644 --- a/chapters/fr/chapter1/8.mdx +++ b/chapters/fr/chapter1/8.mdx @@ -1,34 +1,34 @@ -# Biais et limitations - - - -Si vous souhaitez utiliser un modèle pré-entraîné ou une version *finetunée* de celui-ci en production, il est important d'avoir conscience que, bien que ces modèles soient puissants, ils ont des limites. La plus importante de ces limitations est que, pour permettre le pré-entraînement des modèles sur de grandes quantités de données, les chercheurs récupèrent souvent tout le contenu qu'ils peuvent trouver et donc en prenant le meilleur et le pire de ce qui est disponible sur internet. - -Pour illustrer cela rapidement, revenons au pipeline *fill-mask* avec le modèle BERT : - -```python -from transformers import pipeline - -unmasker = pipeline("fill-mask", model="bert-base-uncased") -result = unmasker("This man works as a [MASK].") # Cet homme travaille comme [MASQUE] -print([r["token_str"] for r in result]) - -result = unmasker("This woman works as a [MASK].") # Cette femme travaille comme [MASQUE] -print([r["token_str"] for r in result]) -``` - -```python out -['lawyer', 'carpenter', 'doctor', 'waiter', 'mechanic'] -# [avocat, charpentier, médecin, serveur, mécanicien] -['nurse', 'waitress', 'teacher', 'maid', 'prostitute'] -# ["infirmière", "serveuse", "professeur", "femme de chambre", "prostituée"] -``` - -Lorsque l'on demande au modèle de remplacer le mot manquant dans ces deux phrases, il ne propose qu'un seul métier ne portant pas la marque du genre (*waiter*/*waitress* → serveur/serveuse). Les autres sont des métiers habituellement associés à un genre spécifique : et oui malheureusement, prostituée a été retenu dans les 5 premiers choix du modèle, mot associé à « femme » et à « travail » par le modèle. Cela se produit même si BERT est l'un des rare *transformers* qui n'a pas été construit avec des données récupérées par *scrapping* sur internet, mais à l'aide de données en apparence neutres. En effet, il est entraîné sur les jeux de donnés [Wikipédia Anglais](https://huggingface.co/datasets/wikipedia) et [BookCorpus](https://huggingface.co/datasets/bookcorpus)). - -Donc lorsque vous utilisez ce genre d'outils, il est important de garder en tête que le modèle que vous utilisez peut rapidement générer du contenu sexiste, raciste ou homophobe. Le *finetuning* du modèle sur vos données ne fera en aucun cas disparaître ce biais intrinsèque. +# Biais et limitations + + + +Si vous souhaitez utiliser un modèle pré-entraîné ou une version *finetunée* de celui-ci en production, il est important d'avoir conscience que, bien que ces modèles soient puissants, ils ont des limites. La plus importante de ces limitations est que, pour permettre le pré-entraînement des modèles sur de grandes quantités de données, les chercheurs récupèrent souvent tout le contenu qu'ils peuvent trouver et donc en prenant le meilleur et le pire de ce qui est disponible sur internet. + +Pour illustrer cela rapidement, revenons au pipeline *fill-mask* avec le modèle BERT : + +```python +from transformers import pipeline + +unmasker = pipeline("fill-mask", model="bert-base-uncased") +result = unmasker("This man works as a [MASK].") # Cet homme travaille comme [MASQUE] +print([r["token_str"] for r in result]) + +result = unmasker("This woman works as a [MASK].") # Cette femme travaille comme [MASQUE] +print([r["token_str"] for r in result]) +``` + +```python out +['lawyer', 'carpenter', 'doctor', 'waiter', 'mechanic'] +# [avocat, charpentier, médecin, serveur, mécanicien] +['nurse', 'waitress', 'teacher', 'maid', 'prostitute'] +# ["infirmière", "serveuse", "professeur", "femme de chambre", "prostituée"] +``` + +Lorsque l'on demande au modèle de remplacer le mot manquant dans ces deux phrases, il ne propose qu'un seul métier ne portant pas la marque du genre (*waiter*/*waitress* → serveur/serveuse). Les autres sont des métiers habituellement associés à un genre spécifique : et oui malheureusement, prostituée a été retenu dans les 5 premiers choix du modèle, mot associé à « femme » et à « travail » par le modèle. Cela se produit même si BERT est l'un des rare *transformers* qui n'a pas été construit avec des données récupérées par *scrapping* sur internet, mais à l'aide de données en apparence neutres. En effet, il est entraîné sur les jeux de donnés [Wikipédia Anglais](https://huggingface.co/datasets/wikipedia) et [BookCorpus](https://huggingface.co/datasets/bookcorpus)). + +Donc lorsque vous utilisez ce genre d'outils, il est important de garder en tête que le modèle que vous utilisez peut rapidement générer du contenu sexiste, raciste ou homophobe. Le *finetuning* du modèle sur vos données ne fera en aucun cas disparaître ce biais intrinsèque. diff --git a/chapters/fr/chapter1/9.mdx b/chapters/fr/chapter1/9.mdx index 502bf459d..efe33bfc8 100644 --- a/chapters/fr/chapter1/9.mdx +++ b/chapters/fr/chapter1/9.mdx @@ -1,11 +1,16 @@ -# Résumé du chapitre - -Au cours de ce chapitre, vous avez vu comment approcher différents problèmes de NLP en utilisant la fonction `pipeline()` de la bibliothèque 🤗 *Transformers*. Vous avez également vu comment rechercher et utiliser des modèles dans le *Hub* ainsi que comment utiliser l'API d'inférence pour tester les modèles directement dans votre navigateur. - -Nous avons pu aborder le fonctionnement des *transformers* de façon générale et parler de l'importance de l'apprentissage par transfert et du *finetuning*. Un point important est que vous pouvez utiliser l'architecture complète ou seulement l'encodeur ou le décodeur, selon le type de tâche que vous souhaitez résoudre. Le tableau suivant résume ceci : - -| Modèle | Exemples | Tâches | -|-------------------|--------------------------------------------|----------------------------------------------------------------------------------------------| -| Encodeur | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa | Classification de phrase, reconnaissance d'entités nommées, extraction de question-réponse | -| Décodeur | CTRL, GPT, GPT-2, Transformer XL | Génération de texte | +# Résumé du chapitre + + + +Au cours de ce chapitre, vous avez vu comment approcher différents problèmes de NLP en utilisant la fonction `pipeline()` de la bibliothèque 🤗 *Transformers*. Vous avez également vu comment rechercher et utiliser des modèles dans le *Hub* ainsi que comment utiliser l'API d'inférence pour tester les modèles directement dans votre navigateur. + +Nous avons pu aborder le fonctionnement des *transformers* de façon générale et parler de l'importance de l'apprentissage par transfert et du *finetuning*. Un point important est que vous pouvez utiliser l'architecture complète ou seulement l'encodeur ou le décodeur, selon le type de tâche que vous souhaitez résoudre. Le tableau suivant résume ceci : + +| Modèle | Exemples | Tâches | +|-------------------|--------------------------------------------|----------------------------------------------------------------------------------------------| +| Encodeur | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa | Classification de phrase, reconnaissance d'entités nommées, extraction de question-réponse | +| Décodeur | CTRL, GPT, GPT-2, Transformer XL | Génération de texte | | Encodeur-décodeur | BART, T5, Marian, mBART | Résumé, traduction, génération de question-réponse | \ No newline at end of file diff --git a/chapters/fr/chapter2/1.mdx b/chapters/fr/chapter2/1.mdx index 77a53ca12..aa72cc82f 100644 --- a/chapters/fr/chapter2/1.mdx +++ b/chapters/fr/chapter2/1.mdx @@ -1,24 +1,29 @@ -# Introduction - -Comme vous l'avez vu dans le [chapitre 1](/course/fr/chapter1), les *transformers* sont généralement très grands. Pouvant aller de plusieurs millions à des dizaines de milliards de paramètres, l'entraînement et le déploiement de ces modèles est une entreprise compliquée. De plus, avec de nouveaux modèles publiés presque quotidiennement et ayant chacun sa propre implémentation, les essayer tous n'est pas une tâche facile. - -La bibliothèque 🤗 *Transformers* a été créée pour résoudre ce problème. Son objectif est de fournir une API unique à travers laquelle tout modèle de *transformers* peut être chargé, entraîné et sauvegardé. Les principales caractéristiques de la bibliothèque sont : - -- **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. - -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. -En plus de rendre les modèles plus accessibles et compréhensibles, cela vous permet d'expérimenter des choses facilement sur un modèle sans affecter les autres. - -Ce chapitre commence par un exemple de bout en bout où nous utilisons un modèle et un *tokenizer* ensemble pour reproduire la fonction `pipeline()` introduite dans le [chapitre 1](/course/fr/chapter1). -Ensuite, nous aborderons l'API *model* : nous nous plongerons dans les classes de modèle et de configuration, nous verrons comment charger un modèle et enfin comment il traite les entrées numériques pour produire des prédictions. - -Nous examinerons ensuite l'API *tokenizer* qui est l'autre composant principal de la fonction `pipeline()`. -Les *tokenizers* s'occupent de la première et de la dernière étape du traitement en gérant la conversion du texte en entrées numériques pour le réseau neuronal et la reconversion en texte lorsqu'elle est nécessaire. -Enfin, nous montrerons comment gérer l'envoi de plusieurs phrases à travers un modèle dans un batch préparé et nous conclurons le tout en examinant de plus près la fonction `tokenizer()`. - - - ⚠️ Afin de bénéficier de toutes les fonctionnalités disponibles avec le Hub et la bibliothèque 🤗 Transformers, nous vous recommandons de créer un compte. - +# Introduction + + + +Comme vous l'avez vu dans le [chapitre 1](/course/fr/chapter1), les *transformers* sont généralement très grands. Pouvant aller de plusieurs millions à des dizaines de milliards de paramètres, l'entraînement et le déploiement de ces modèles est une entreprise compliquée. De plus, avec de nouveaux modèles publiés presque quotidiennement et ayant chacun sa propre implémentation, les essayer tous n'est pas une tâche facile. + +La bibliothèque 🤗 *Transformers* a été créée pour résoudre ce problème. Son objectif est de fournir une API unique à travers laquelle tout modèle de *transformers* peut être chargé, entraîné et sauvegardé. Les principales caractéristiques de la bibliothèque sont : + +- **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. + +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. +En plus de rendre les modèles plus accessibles et compréhensibles, cela vous permet d'expérimenter des choses facilement sur un modèle sans affecter les autres. + +Ce chapitre commence par un exemple de bout en bout où nous utilisons un modèle et un *tokenizer* ensemble pour reproduire la fonction `pipeline()` introduite dans le [chapitre 1](/course/fr/chapter1). +Ensuite, nous aborderons l'API *model* : nous nous plongerons dans les classes de modèle et de configuration, nous verrons comment charger un modèle et enfin comment il traite les entrées numériques pour produire des prédictions. + +Nous examinerons ensuite l'API *tokenizer* qui est l'autre composant principal de la fonction `pipeline()`. +Les *tokenizers* s'occupent de la première et de la dernière étape du traitement en gérant la conversion du texte en entrées numériques pour le réseau neuronal et la reconversion en texte lorsqu'elle est nécessaire. +Enfin, nous montrerons comment gérer l'envoi de plusieurs phrases à travers un modèle dans un batch préparé et nous conclurons le tout en examinant de plus près la fonction `tokenizer()`. + + + ⚠️ Afin de bénéficier de toutes les fonctionnalités disponibles avec le Hub et la bibliothèque 🤗 Transformers, nous vous recommandons de créer un compte. + diff --git a/chapters/fr/chapter2/2.mdx b/chapters/fr/chapter2/2.mdx index baaf88030..6aceac55a 100644 --- a/chapters/fr/chapter2/2.mdx +++ b/chapters/fr/chapter2/2.mdx @@ -1,349 +1,349 @@ - - -# Derrière le pipeline - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - - -Il s'agit de la première section dont le contenu est légèrement différent selon que vous utilisez PyTorch ou TensorFlow. Cliquez sur le bouton situé au-dessus du titre pour sélectionner la plateforme que vous préférez ! - - -{#if fw === 'pt'} - -{:else} - -{/if} - -Commençons par un exemple complet en regardant ce qui s'est passé en coulisses lorsque nous avons exécuté le code suivant dans le [chapitre 1](/course/chapter1) : - -```python -from transformers import pipeline - -classifier = pipeline("sentiment-analysis") -classifier( - [ - "I've been waiting for a HuggingFace course my whole life.", - # J'ai attendu un cours de HuggingFace toute ma vie. - "I hate this so much!", # Je déteste tellement ça ! - ] -) -``` - -la sortie : - -```python out -[{'label': 'POSITIVE', 'score': 0.9598047137260437}, - {'label': 'NEGATIVE', 'score': 0.9994558095932007}] -``` - -Comme nous l'avons vu dans le [chapitre 1](/course/fr/chapter1), ce pipeline regroupe trois étapes : le prétraitement, le passage des entrées dans le modèle et le post-traitement. - -
-The full NLP pipeline: tokenization of text, conversion to IDs, and inference through the Transformer model and the model head. - -
- -Passons rapidement en revue chacun de ces éléments. - -## Prétraitement avec un tokenizer - -Comme d'autres réseaux de neurones, les *transformers* ne peuvent pas traiter directement le texte brut, donc la première étape de notre pipeline est de convertir les entrées textuelles en nombres afin que le modèle puisse les comprendre. Pour ce faire, nous utilisons un *tokenizer*, qui sera responsable de : -- diviser l'entrée en mots, sous-mots, ou symboles (comme la ponctuation) qui sont appelés *tokens*, -- associer chaque *token* à un nombre entier, -- ajouter des entrées supplémentaires qui peuvent être utiles au modèle. - -Tout ce prétraitement doit être effectué exactement de la même manière que celui appliqué lors du pré-entraînement du modèle. Nous devons donc d'abord télécharger ces informations depuis le [*Hub*](https://huggingface.co/models). Pour ce faire, nous utilisons la classe `AutoTokenizer` et sa méthode `from_pretrained()`. En utilisant le nom du *checkpoint* de notre modèle, elle va automatiquement récupérer les données associées au *tokenizer* du modèle et les mettre en cache (afin qu'elles ne soient téléchargées que la première fois que vous exécutez le code ci-dessous). - -Puisque le *checkpoint* par défaut du pipeline `sentiment-analysis` (analyse de sentiment) est `distilbert-base-uncased-finetuned-sst-2-english` (vous pouvez voir la carte de ce modèle [ici](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english)), nous exécutons ce qui suit : - -```python -from transformers import AutoTokenizer - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -``` - -Une fois que nous avons le *tokenizer* nous pouvons lui passer directement nos phrases et obtenir un dictionnaire prêt à être donné à notre modèle ! La seule chose qui reste à faire est de convertir en tenseurs la liste des identifiants d'entrée. - -Vous pouvez utiliser 🤗 *Transformers* sans avoir à vous soucier du *framework* utilisé comme *backend*. Il peut s'agir de PyTorch, de TensorFlow ou de Flax pour certains modèles. Cependant, les *transformers* n'acceptent que les *tenseurs* en entrée. Si c'est la première fois que vous entendez parler de tenseurs, vous pouvez les considérer comme des tableaux NumPy. Un tableau NumPy peut être un scalaire (0D), un vecteur (1D), une matrice (2D), ou avoir davantage de dimensions. Les tenseurs des autres *frameworks* d'apprentissage machine se comportent de manière similaire et sont généralement aussi simples à instancier que les tableaux NumPy. - -Pour spécifier le type de tenseurs que nous voulons récupérer (PyTorch, TensorFlow, ou simplement NumPy), nous utilisons l'argument `return_tensors` : - -{#if fw === 'pt'} -```python -raw_inputs = [ - "I've been waiting for a HuggingFace course my whole life.", - # J'ai attendu un cours de HuggingFace toute ma vie. - "I hate this so much!", # Je déteste tellement ça ! -] -inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="pt") -print(inputs) -``` -{:else} -```python -raw_inputs = [ - "I've been waiting for a HuggingFace course my whole life.", - # J'ai attendu un cours de HuggingFace toute ma vie. - "I hate this so much!", # Je déteste tellement ça ! -] -inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="tf") -print(inputs) -``` -{/if} - -Ne vous préoccupez pas encore du remplissage (*padding*) et de la troncature, nous les expliquerons plus tard. Les principales choses à retenir ici sont que vous pouvez passer une phrase ou une liste de phrases, ainsi que spécifier le type de tenseurs que vous voulez récupérer (si aucun type n'est passé, par défaut vous obtiendrez une liste de listes comme résultat). - -{#if fw === 'pt'} - -Voici à quoi ressemblent les résultats sous forme de tenseurs PyTorch : - -```python out -{ - 'input_ids': tensor([ - [ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012, 102], - [ 101, 1045, 5223, 2023, 2061, 2172, 999, 102, 0, 0, 0, 0, 0, 0, 0, 0] - ]), - '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] - ]) -} -``` -{:else} - -Voici à quoi ressemblent les résultats sous forme de tenseurs TensorFlow : - -```python out -{ - 'input_ids': , - 'attention_mask': -} -``` -{/if} - -La sortie elle-même est un dictionnaire contenant deux clés : `input_ids` et `attention_mask`. `input_ids` contient deux lignes d'entiers (une pour chaque phrase) qui sont les identifiants uniques des *tokens* dans chaque phrase. Nous expliquerons ce qu'est l'`attention_mask` plus tard dans ce chapitre. - -## Passage au modèle - -{#if fw === 'pt'} -Nous pouvons télécharger notre modèle pré-entraîné de la même manière que nous l'avons fait avec notre *tokenizer*. 🤗 *Transformers* fournit une classe `AutoModel` qui possède également une méthode `from_pretrained()` : - -```python -from transformers import AutoModel - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -model = AutoModel.from_pretrained(checkpoint) -``` -{:else} -Nous pouvons télécharger notre modèle prétraîné de la même manière que nous l'avons fait avec notre *tokenizer*. 🤗 *Transformers* fournit une classe `TFAutoModel` qui possède également une méthode `from_pretrained()` : - -```python -from transformers import TFAutoModel - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -model = TFAutoModel.from_pretrained(checkpoint) -``` -{/if} - -Dans cet extrait de code, nous avons téléchargé le même *checkpoint* que nous avons utilisé dans notre pipeline auparavant (il devrait en fait avoir déjà été mis en cache) et instancié un modèle avec lui. - -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. - -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. - -### Un vecteur de grande dimension ? - -Le vecteur produit en sortie par le *transformer* est généralement de grande dimension. Il a généralement trois dimensions : - -- **la taille du lot** : le nombre de séquences traitées à la fois (2 dans notre exemple), -- **la longueur de la séquence** : la longueur de la représentation numérique de la séquence (16 dans notre exemple), -- **la taille cachée** : la dimension du vecteur de chaque entrée du modèle. - -On dit qu'il est de « grande dimension » en raison de la dernière valeur. La taille cachée peut être très grande (généralement 768 pour les petits modèles et pour les grands modèles cela peut atteindre 3072 voire plus). - -Nous pouvons le constater si nous alimentons notre modèle avec les entrées que nous avons prétraitées : - - -{#if fw === 'pt'} -```python -outputs = model(**inputs) -print(outputs.last_hidden_state.shape) -``` - -```python out -torch.Size([2, 16, 768]) -``` -{:else} -```py -outputs = model(inputs) -print(outputs.last_hidden_state.shape) -``` - -```python out -(2, 16, 768) -``` -{/if} - -Notez que les sorties des modèles de la bibliothèque 🤗 *Transformers* se comportent comme des `namedtuples` ou des dictionnaires. Vous pouvez accéder aux éléments par attributs (comme nous l'avons fait), par clé (`outputs["last_hidden_state"]`), ou même par l’index si vous savez exactement où se trouve la chose que vous cherchez (`outputs[0]`). - -### Les têtes des modèles : donner du sens aux chiffres -Les têtes des modèles prennent en entrée le vecteur de grande dimension des états cachés et le projettent sur une autre dimension. Elles sont généralement composées d'une ou de quelques couches linéaires : -
-A Transformer network alongside its head. - -
- -La sortie du *transformer* est envoyée directement à la tête du modèle pour être traitée. -Dans ce diagramme, le modèle est représenté par sa couche d’enchâssement et les couches suivantes. La couche d’enchâssement convertit chaque identifiant d'entrée dans l'entrée tokenisée en un vecteur qui représente le *token* associé. Les couches suivantes manipulent ces vecteurs en utilisant le mécanisme d'attention pour produire la représentation finale des phrases. -Il existe de nombreuses architectures différentes disponibles dans la bibliothèque 🤗 *Transformers*, chacune étant conçue autour de la prise en charge d'une tâche spécifique. En voici une liste non exhaustive : -- `*Model` (récupérer les états cachés) -- `*ForCausalLM` -- `*ForMaskedLM` -- `*ForMultipleChoice` -- `*ForQuestionAnswering` -- `*ForSequenceClassification` -- `*ForTokenClassification` -- et autres 🤗 - -{#if fw === 'pt'} -Pour notre exemple, nous avons besoin d'un modèle avec une tête de classification de séquence (pour pouvoir classer les phrases comme positives ou négatives). Donc, nous n'utilisons pas réellement la classe `AutoModel` mais plutôt `AutoModelForSequenceClassification` : -```python -from transformers import AutoModelForSequenceClassification - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -model = AutoModelForSequenceClassification.from_pretrained(checkpoint) -outputs = model(**inputs) -``` -{:else} -Pour notre exemple, nous avons besoin d'un modèle avec une tête de classification de séquence (pour pouvoir classer les phrases comme positives ou négatives). Donc, nous n'utilisons pas réellement la classe ` TFAutoModel` mais plutôt ` TFAutoModelForSequenceClassification` : -```python -from transformers import TFAutoModelForSequenceClassification - -checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" -model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) -outputs = model(inputs) -``` -{/if} - -Maintenant, si nous examinons la forme de nos entrées, la dimensionnalité est beaucoup plus faible. La tête du modèle prend en entrée les vecteurs de grande dimension que nous avons vus précédemment et elle produit des vecteurs contenant deux valeurs (une par étiquette) : -```python -print(outputs.logits.shape) -``` - -{#if fw === 'pt'} -```python out -torch.Size([2, 2]) -``` -{:else} -```python out -(2, 2) -``` -{/if} - -Comme nous n'avons que deux phrases et deux étiquettes, le résultat que nous obtenons est de forme 2 x 2 - -## Post-traitement de la sortie - -Les valeurs que nous obtenons en sortie de notre modèle n'ont pas nécessairement de sens en elles-mêmes. Jetons-y un coup d’œil : - -```python -print(outputs.logits) -``` - -{#if fw === 'pt'} -```python out -tensor([[-1.5607, 1.6123], - [ 4.1692, -3.3464]], grad_fn=) -``` -{:else} -```python out - -``` -{/if} - -Notre modèle a prédit `[-1.5607, 1.6123]` pour la première phrase et `[ 4.1692, -3.3464]` pour la seconde. Ce ne sont pas des probabilités mais des *logits*, les scores bruts, non normalisés, produits par la dernière couche du modèle. Pour être convertis en probabilités, ils doivent passer par une couche [SoftMax](https://fr.wikipedia.org/wiki/Fonction_softmax) (tous les modèles de la bibliothèque 🤗 *Transformers* sortent les logits car la fonction de perte de l'entraînement fusionne généralement la dernière fonction d'activation, comme la SoftMax, avec la fonction de perte réelle, comme l'entropie croisée) : - -{#if fw === 'pt'} -```py -import torch - -predictions = torch.nn.functional.softmax(outputs.logits, dim=-1) -print(predictions) -``` -{:else} -```py -import tensorflow as tf - -predictions = tf.math.softmax(outputs.logits, axis=-1) -print(predictions) -``` -{/if} - -{#if fw === 'pt'} -```python out -tensor([[4.0195e-02, 9.5980e-01], - [9.9946e-01, 5.4418e-04]], grad_fn=) -``` -{:else} -```python out -tf.Tensor( -[[4.01951671e-02 9.59804833e-01] - [9.9945587e-01 5.4418424e-04]], shape=(2, 2), dtype=float32) -``` -{/if} - -Maintenant nous pouvons voir que le modèle a prédit `[0.0402, 0.9598]` pour la première phrase et `[0.9995, 0.0005]` pour la seconde. Ce sont des scores de probabilité reconnaissables. - -Pour obtenir les étiquettes correspondant à chaque position, nous pouvons inspecter l'attribut `id2label` de la configuration du modèle (plus de détails dans la section suivante) : - -```python -model.config.id2label -``` - -```python out -{0: 'NEGATIVE', 1: 'POSITIVE'} -``` - -Nous pouvons maintenant conclure que le modèle a prédit ce qui suit : - -- première phrase : NEGATIVE: 0.0402, POSITIVE: 0.9598 -- deuxième phrase : NEGATIVE: 0.9995, POSITIVE: 0.0005 - -Nous avons reproduit avec succès les trois étapes du pipeline : prétraitement avec les *tokenizers*, passage des entrées dans le modèle et post-traitement ! Prenons maintenant le temps de nous plonger plus profondément dans chacune de ces étapes. - - - -✏️ **Essayez !** Choisissez deux (ou plus) textes de votre choix (en anglais) et faites-les passer par le pipeline `sentiment-analysis`. Reproduisez ensuite vous-même les étapes vues ici et vérifiez que vous obtenez les mêmes résultats ! - - + + +# Derrière le pipeline + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + +Il s'agit de la première section dont le contenu est légèrement différent selon que vous utilisez PyTorch ou TensorFlow. Cliquez sur le bouton situé au-dessus du titre pour sélectionner la plateforme que vous préférez ! + + +{#if fw === 'pt'} + +{:else} + +{/if} + +Commençons par un exemple complet en regardant ce qui s'est passé en coulisses lorsque nous avons exécuté le code suivant dans le [chapitre 1](/course/chapter1) : + +```python +from transformers import pipeline + +classifier = pipeline("sentiment-analysis") +classifier( + [ + "I've been waiting for a HuggingFace course my whole life.", + # J'ai attendu un cours de HuggingFace toute ma vie. + "I hate this so much!", # Je déteste tellement ça ! + ] +) +``` + +la sortie : + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}, + {'label': 'NEGATIVE', 'score': 0.9994558095932007}] +``` + +Comme nous l'avons vu dans le [chapitre 1](/course/fr/chapter1), ce pipeline regroupe trois étapes : le prétraitement, le passage des entrées dans le modèle et le post-traitement. + +
+The full NLP pipeline: tokenization of text, conversion to IDs, and inference through the Transformer model and the model head. + +
+ +Passons rapidement en revue chacun de ces éléments. + +## Prétraitement avec un tokenizer + +Comme d'autres réseaux de neurones, les *transformers* ne peuvent pas traiter directement le texte brut, donc la première étape de notre pipeline est de convertir les entrées textuelles en nombres afin que le modèle puisse les comprendre. Pour ce faire, nous utilisons un *tokenizer*, qui sera responsable de : +- diviser l'entrée en mots, sous-mots, ou symboles (comme la ponctuation) qui sont appelés *tokens*, +- associer chaque *token* à un nombre entier, +- ajouter des entrées supplémentaires qui peuvent être utiles au modèle. + +Tout ce prétraitement doit être effectué exactement de la même manière que celui appliqué lors du pré-entraînement du modèle. Nous devons donc d'abord télécharger ces informations depuis le [*Hub*](https://huggingface.co/models). Pour ce faire, nous utilisons la classe `AutoTokenizer` et sa méthode `from_pretrained()`. En utilisant le nom du *checkpoint* de notre modèle, elle va automatiquement récupérer les données associées au *tokenizer* du modèle et les mettre en cache (afin qu'elles ne soient téléchargées que la première fois que vous exécutez le code ci-dessous). + +Puisque le *checkpoint* par défaut du pipeline `sentiment-analysis` (analyse de sentiment) est `distilbert-base-uncased-finetuned-sst-2-english` (vous pouvez voir la carte de ce modèle [ici](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english)), nous exécutons ce qui suit : + +```python +from transformers import AutoTokenizer + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` + +Une fois que nous avons le *tokenizer* nous pouvons lui passer directement nos phrases et obtenir un dictionnaire prêt à être donné à notre modèle ! La seule chose qui reste à faire est de convertir en tenseurs la liste des identifiants d'entrée. + +Vous pouvez utiliser 🤗 *Transformers* sans avoir à vous soucier du *framework* utilisé comme *backend*. Il peut s'agir de PyTorch, de TensorFlow ou de Flax pour certains modèles. Cependant, les *transformers* n'acceptent que les *tenseurs* en entrée. Si c'est la première fois que vous entendez parler de tenseurs, vous pouvez les considérer comme des tableaux NumPy. Un tableau NumPy peut être un scalaire (0D), un vecteur (1D), une matrice (2D), ou avoir davantage de dimensions. Les tenseurs des autres *frameworks* d'apprentissage machine se comportent de manière similaire et sont généralement aussi simples à instancier que les tableaux NumPy. + +Pour spécifier le type de tenseurs que nous voulons récupérer (PyTorch, TensorFlow, ou simplement NumPy), nous utilisons l'argument `return_tensors` : + +{#if fw === 'pt'} +```python +raw_inputs = [ + "I've been waiting for a HuggingFace course my whole life.", + # J'ai attendu un cours de HuggingFace toute ma vie. + "I hate this so much!", # Je déteste tellement ça ! +] +inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="pt") +print(inputs) +``` +{:else} +```python +raw_inputs = [ + "I've been waiting for a HuggingFace course my whole life.", + # J'ai attendu un cours de HuggingFace toute ma vie. + "I hate this so much!", # Je déteste tellement ça ! +] +inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="tf") +print(inputs) +``` +{/if} + +Ne vous préoccupez pas encore du remplissage (*padding*) et de la troncature, nous les expliquerons plus tard. Les principales choses à retenir ici sont que vous pouvez passer une phrase ou une liste de phrases, ainsi que spécifier le type de tenseurs que vous voulez récupérer (si aucun type n'est passé, par défaut vous obtiendrez une liste de listes comme résultat). + +{#if fw === 'pt'} + +Voici à quoi ressemblent les résultats sous forme de tenseurs PyTorch : + +```python out +{ + 'input_ids': tensor([ + [ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012, 102], + [ 101, 1045, 5223, 2023, 2061, 2172, 999, 102, 0, 0, 0, 0, 0, 0, 0, 0] + ]), + '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] + ]) +} +``` +{:else} + +Voici à quoi ressemblent les résultats sous forme de tenseurs TensorFlow : + +```python out +{ + 'input_ids': , + 'attention_mask': +} +``` +{/if} + +La sortie elle-même est un dictionnaire contenant deux clés : `input_ids` et `attention_mask`. `input_ids` contient deux lignes d'entiers (une pour chaque phrase) qui sont les identifiants uniques des *tokens* dans chaque phrase. Nous expliquerons ce qu'est l'`attention_mask` plus tard dans ce chapitre. + +## Passage au modèle + +{#if fw === 'pt'} +Nous pouvons télécharger notre modèle pré-entraîné de la même manière que nous l'avons fait avec notre *tokenizer*. 🤗 *Transformers* fournit une classe `AutoModel` qui possède également une méthode `from_pretrained()` : + +```python +from transformers import AutoModel + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = AutoModel.from_pretrained(checkpoint) +``` +{:else} +Nous pouvons télécharger notre modèle prétraîné de la même manière que nous l'avons fait avec notre *tokenizer*. 🤗 *Transformers* fournit une classe `TFAutoModel` qui possède également une méthode `from_pretrained()` : + +```python +from transformers import TFAutoModel + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = TFAutoModel.from_pretrained(checkpoint) +``` +{/if} + +Dans cet extrait de code, nous avons téléchargé le même *checkpoint* que nous avons utilisé dans notre pipeline auparavant (il devrait en fait avoir déjà été mis en cache) et instancié un modèle avec lui. + +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. + +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. + +### Un vecteur de grande dimension ? + +Le vecteur produit en sortie par le *transformer* est généralement de grande dimension. Il a généralement trois dimensions : + +- **la taille du lot** : le nombre de séquences traitées à la fois (2 dans notre exemple), +- **la longueur de la séquence** : la longueur de la représentation numérique de la séquence (16 dans notre exemple), +- **la taille cachée** : la dimension du vecteur de chaque entrée du modèle. + +On dit qu'il est de « grande dimension » en raison de la dernière valeur. La taille cachée peut être très grande (généralement 768 pour les petits modèles et pour les grands modèles cela peut atteindre 3072 voire plus). + +Nous pouvons le constater si nous alimentons notre modèle avec les entrées que nous avons prétraitées : + + +{#if fw === 'pt'} +```python +outputs = model(**inputs) +print(outputs.last_hidden_state.shape) +``` + +```python out +torch.Size([2, 16, 768]) +``` +{:else} +```py +outputs = model(inputs) +print(outputs.last_hidden_state.shape) +``` + +```python out +(2, 16, 768) +``` +{/if} + +Notez que les sorties des modèles de la bibliothèque 🤗 *Transformers* se comportent comme des `namedtuples` ou des dictionnaires. Vous pouvez accéder aux éléments par attributs (comme nous l'avons fait), par clé (`outputs["last_hidden_state"]`), ou même par l’index si vous savez exactement où se trouve la chose que vous cherchez (`outputs[0]`). + +### Les têtes des modèles : donner du sens aux chiffres +Les têtes des modèles prennent en entrée le vecteur de grande dimension des états cachés et le projettent sur une autre dimension. Elles sont généralement composées d'une ou de quelques couches linéaires : +
+A Transformer network alongside its head. + +
+ +La sortie du *transformer* est envoyée directement à la tête du modèle pour être traitée. +Dans ce diagramme, le modèle est représenté par sa couche d’enchâssement et les couches suivantes. La couche d’enchâssement convertit chaque identifiant d'entrée dans l'entrée tokenisée en un vecteur qui représente le *token* associé. Les couches suivantes manipulent ces vecteurs en utilisant le mécanisme d'attention pour produire la représentation finale des phrases. +Il existe de nombreuses architectures différentes disponibles dans la bibliothèque 🤗 *Transformers*, chacune étant conçue autour de la prise en charge d'une tâche spécifique. En voici une liste non exhaustive : +- `*Model` (récupérer les états cachés) +- `*ForCausalLM` +- `*ForMaskedLM` +- `*ForMultipleChoice` +- `*ForQuestionAnswering` +- `*ForSequenceClassification` +- `*ForTokenClassification` +- et autres 🤗 + +{#if fw === 'pt'} +Pour notre exemple, nous avons besoin d'un modèle avec une tête de classification de séquence (pour pouvoir classer les phrases comme positives ou négatives). Donc, nous n'utilisons pas réellement la classe `AutoModel` mais plutôt `AutoModelForSequenceClassification` : +```python +from transformers import AutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +outputs = model(**inputs) +``` +{:else} +Pour notre exemple, nous avons besoin d'un modèle avec une tête de classification de séquence (pour pouvoir classer les phrases comme positives ou négatives). Donc, nous n'utilisons pas réellement la classe ` TFAutoModel` mais plutôt ` TFAutoModelForSequenceClassification` : +```python +from transformers import TFAutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +outputs = model(inputs) +``` +{/if} + +Maintenant, si nous examinons la forme de nos entrées, la dimensionnalité est beaucoup plus faible. La tête du modèle prend en entrée les vecteurs de grande dimension que nous avons vus précédemment et elle produit des vecteurs contenant deux valeurs (une par étiquette) : +```python +print(outputs.logits.shape) +``` + +{#if fw === 'pt'} +```python out +torch.Size([2, 2]) +``` +{:else} +```python out +(2, 2) +``` +{/if} + +Comme nous n'avons que deux phrases et deux étiquettes, le résultat que nous obtenons est de forme 2 x 2 + +## Post-traitement de la sortie + +Les valeurs que nous obtenons en sortie de notre modèle n'ont pas nécessairement de sens en elles-mêmes. Jetons-y un coup d’œil : + +```python +print(outputs.logits) +``` + +{#if fw === 'pt'} +```python out +tensor([[-1.5607, 1.6123], + [ 4.1692, -3.3464]], grad_fn=) +``` +{:else} +```python out + +``` +{/if} + +Notre modèle a prédit `[-1.5607, 1.6123]` pour la première phrase et `[ 4.1692, -3.3464]` pour la seconde. Ce ne sont pas des probabilités mais des *logits*, les scores bruts, non normalisés, produits par la dernière couche du modèle. Pour être convertis en probabilités, ils doivent passer par une couche [SoftMax](https://fr.wikipedia.org/wiki/Fonction_softmax) (tous les modèles de la bibliothèque 🤗 *Transformers* sortent les logits car la fonction de perte de l'entraînement fusionne généralement la dernière fonction d'activation, comme la SoftMax, avec la fonction de perte réelle, comme l'entropie croisée) : + +{#if fw === 'pt'} +```py +import torch + +predictions = torch.nn.functional.softmax(outputs.logits, dim=-1) +print(predictions) +``` +{:else} +```py +import tensorflow as tf + +predictions = tf.math.softmax(outputs.logits, axis=-1) +print(predictions) +``` +{/if} + +{#if fw === 'pt'} +```python out +tensor([[4.0195e-02, 9.5980e-01], + [9.9946e-01, 5.4418e-04]], grad_fn=) +``` +{:else} +```python out +tf.Tensor( +[[4.01951671e-02 9.59804833e-01] + [9.9945587e-01 5.4418424e-04]], shape=(2, 2), dtype=float32) +``` +{/if} + +Maintenant nous pouvons voir que le modèle a prédit `[0.0402, 0.9598]` pour la première phrase et `[0.9995, 0.0005]` pour la seconde. Ce sont des scores de probabilité reconnaissables. + +Pour obtenir les étiquettes correspondant à chaque position, nous pouvons inspecter l'attribut `id2label` de la configuration du modèle (plus de détails dans la section suivante) : + +```python +model.config.id2label +``` + +```python out +{0: 'NEGATIVE', 1: 'POSITIVE'} +``` + +Nous pouvons maintenant conclure que le modèle a prédit ce qui suit : + +- première phrase : NEGATIVE: 0.0402, POSITIVE: 0.9598 +- deuxième phrase : NEGATIVE: 0.9995, POSITIVE: 0.0005 + +Nous avons reproduit avec succès les trois étapes du pipeline : prétraitement avec les *tokenizers*, passage des entrées dans le modèle et post-traitement ! Prenons maintenant le temps de nous plonger plus profondément dans chacune de ces étapes. + + + +✏️ **Essayez !** Choisissez deux (ou plus) textes de votre choix (en anglais) et faites-les passer par le pipeline `sentiment-analysis`. Reproduisez ensuite vous-même les étapes vues ici et vérifiez que vous obtenez les mêmes résultats ! + + diff --git a/chapters/fr/chapter2/3.mdx b/chapters/fr/chapter2/3.mdx index 9fce3d02f..a16dc192d 100644 --- a/chapters/fr/chapter2/3.mdx +++ b/chapters/fr/chapter2/3.mdx @@ -1,231 +1,231 @@ - - -# Les modèles - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -{#if fw === 'pt'} - -{:else} - -{/if} - -{#if fw === 'pt'} -Dans cette section, nous allons examiner de plus près la création et l'utilisation d'un modèle. Nous utiliserons la classe `AutoModel` qui est pratique lorsque vous voulez instancier n'importe quel modèle à partir d'un checkpoint. - -La classe `AutoModel` et toutes les classes apparentées sont en fait de simples *wrappers* sur la grande variété de modèles disponibles dans la bibliothèque. C'est une enveloppe intelligente car elle peut automatiquement deviner l'architecture appropriée pour votre *checkpoint* et ensuite instancier un modèle avec cette architecture. - -{:else} -Dans cette section, nous allons examiner de plus près la création et l'utilisation d'un modèle. Nous utiliserons la classe `TFAutoModel` qui est pratique lorsque vous voulez instancier n'importe quel modèle à partir d'un checkpoint. - -La classe `TFAutoModel` et toutes les classes apparentées sont en fait de simples *wrappers* sur la grande variété de modèles disponibles dans la bibliothèque. C'est une enveloppe intelligente car elle peut automatiquement deviner l'architecture appropriée pour votre *checkpoint* et ensuite instancier un modèle avec cette architecture. - -{/if} - -Cependant, si vous connaissez le type de modèle que vous voulez utiliser, vous pouvez utiliser directement la classe qui définit son architecture. Voyons comment cela fonctionne avec un modèle BERT. - -## Création d’un transformer - -La première chose que nous devons faire pour initialiser un modèle BERT est de charger un objet configuration : - -{#if fw === 'pt'} -```py -from transformers import BertConfig, BertModel - -# Construire la configuration -config = BertConfig() - -# Construire le modèle à partir de la configuration -model = BertModel(config) -``` -{:else} -```py -from transformers import BertConfig, TFBertModel - -# Construire la configuration -config = BertConfig() - -# Construire le modèle à partir de la configuration -model = TFBertModel(config) -``` -{/if} - -La configuration contient de nombreux attributs qui sont utilisés pour construire le modèle : -```py -print(config) -``` - -```python out -BertConfig { - [...] - "hidden_size": 768, - "intermediate_size": 3072, - "max_position_embeddings": 512, - "num_attention_heads": 12, - "num_hidden_layers": 12, - [...] -} -``` - -Bien que vous n'ayez pas encore vu ce que font tous ces attributs, vous devriez en reconnaître certains : l'attribut `hidden_size` définit la taille du vecteur `hidden_states`, et `num_hidden_layers` définit le nombre de couches que le *transformer* possède. - -### Différentes méthodes de chargement - -Un modèle créé à partir de la configuration par défaut est initialisé avec des valeurs aléatoires : - - -{#if fw === 'pt'} -```py -from transformers import BertConfig, BertModel - -config = BertConfig() -model = BertModel(config) - -# Le modèle est initialisé de façon aléatoire ! -``` -{:else} -```py -from transformers import BertConfig, TFBertModel - -config = BertConfig() -model = TFBertModel(config) - -# Le modèle est initialisé de façon aléatoire ! -``` -{/if} - -Le modèle peut être utilisé tel quel mais il produira du charabia. En effet, il doit d'abord être entraîné. Nous pourrions entraîner le modèle à partir de zéro sur la tâche qui nous intéresse mais comme vous l'avez vu dans le [chapitre 1](/course/fr/chapter1) cela nécessiterait beaucoup de temps et de données. De plus cela aurait un impact environnemental non négligeable. Pour éviter les efforts inutiles et redondants, il est impératif de pouvoir partager et réutiliser les modèles qui ont déjà été entraînés. - -Charger un *transformer* qui a déjà été entraîné est simple : nous pouvons le faire en utilisant la méthode `from_pretrained()` : - - -{#if fw === 'pt'} -```py -from transformers import BertModel - -model = BertModel.from_pretrained("bert-base-cased") -``` - -Comme vu précédemment, nous pouvons remplacer `BertModel` par la classe équivalente `AutoModel`. A partir de maintenant nous ferons cela car cela produit un code agnostique *checkpoint*, c’est-à-dire que si votre code fonctionne pour un *checkpoint* donné, il devrait fonctionner sans problème avec un autre. Cela s'applique même si l'architecture est différente du moment que le *checkpoint* a été entraîné pour une tâche similaire (par exemple, une tâche d'analyse de sentiments). - -{:else} -```py -from transformers import TFBertModel - -model = TFBertModel.from_pretrained("bert-base-cased") -``` - -Comme vu précédemment, nous pouvons remplacer `TFBertModel` par la classe équivalente `TFAutoModel`. A partir de maintenant nous ferons cela car cela produit un code agnostique *checkpoint*, c’est-à-dire que si votre code fonctionne pour un *checkpoint* donné, il devrait fonctionner sans problème avec un autre. Cela s'applique même si l'architecture est différente du moment que le *checkpoint* a été entraîné pour une tâche similaire (par exemple, une tâche d'analyse de sentiments). - -{/if} - -Dans l'exemple de code ci-dessus, nous n'avons pas utilisé `BertConfig` et avons à la place chargé un modèle pré-entraîné via l'identifiant `bert-base-cased`. Il s'agit d'un *checkpoint* qui a été entraîné par les auteurs de BERT eux-mêmes. Vous pouvez trouver davantage de détails à son sujet dans la [fiche du modèle](https://huggingface.co/bert-base-cased). - -Ce modèle est maintenant initialisé avec tous les poids du *checkpoint*. Il peut être utilisé directement pour l'inférence sur les tâches sur lesquelles il a été entraîné. Il peut également être *finetuné* sur une nouvelle tâche. En entraînant avec des poids pré-entraînés plutôt qu'à partir de zéro, nous pouvons rapidement obtenir de bons résultats. - -Les poids ont été téléchargés et mis en cache (afin que les futurs appels à la méthode `from_pretrained()` ne les retéléchargent pas) dans le dossier cache, qui est par défaut *~/.cache/huggingface/transformers*. Vous pouvez personnaliser votre dossier de cache en définissant la variable d'environnement `HF_HOME`. - -L'identifiant utilisé pour charger le modèle peut être l'identifiant de n'importe quel modèle sur le *Model Hub* du moment qu'il est compatible avec l'architecture BERT. La liste complète des *checkpoints* de BERT disponibles peut être trouvée [ici](https://huggingface.co/models?filter=bert). - -### Méthodes de sauvegarde - -Sauvegarder un modèle est aussi facile que d'en charger un. Nous utilisons la méthode `save_pretrained()`, qui est analogue à la méthode `from_pretrained()` : - - -```py -model.save_pretrained("directory_on_my_computer") -``` - -Cela enregistre deux fichiers sur votre disque : - -{#if fw === 'pt'} -``` -ls directory_on_my_computer - -config.json pytorch_model.bin -``` -{:else} -``` -ls directory_on_my_computer - -config.json tf_model.h5 -``` -{/if} - -Si vous jetez un coup d'œil au fichier *config.json*, vous reconnaîtrez les attributs nécessaires pour construire l'architecture du modèle. Ce fichier contient également certaines métadonnées, comme l'origine du *checkpoint* et la version de la bibliothèque 🤗 *Transformers* que vous utilisiez lors du dernier enregistrement du point *checkpoint*. - -{#if fw === 'pt'} -Le fichier *pytorch_model.bin* est connu comme le *dictionnaire d'état*. Il contient tous les poids de votre modèle. Les deux fichiers vont de pair : la configuration est nécessaire pour connaître l'architecture de votre modèle, tandis que les poids du modèle sont les paramètres de votre modèle. - -{:else} -Le fichier *tf_model.h5* est connu comme le *dictionnaire d'état*. Il contient tous les poids de votre modèle. Les deux fichiers vont de pair : la configuration est nécessaire pour connaître l'architecture de votre modèle, tandis que les poids du modèle sont les paramètres de votre modèle. - -{/if} - -### Utilisation d'un transformer pour l'inférence - -Maintenant que vous savez comment charger et sauvegarder un modèle, essayons de l'utiliser pour faire quelques prédictions. Les *transformers* ne peuvent traiter que des nombres. Des nombres que le *tokenizer* génère. Mais avant de parler des *tokenizers*, explorons les entrées que le modèle accepte. - -Les *tokenizers* se chargent de passer les entrées vers les tenseurs du *framework* approprié. Pour vous aider à comprendre ce qui se passe, jetons un coup d'œil rapide à ce qui doit être fait avant d'envoyer les entrées au modèle. - -Disons que nous avons les séquences suivantes : - - -```py -sequences = ["Hello!", "Cool.", "Nice!"] -``` - -Le *tokenizer* les convertit en indices de vocabulaire qui sont généralement appelés *input IDs*. Chaque séquence est maintenant une liste de nombres ! La sortie résultante est : - -```py no-format -encoded_sequences = [ - [101, 7592, 999, 102], - [101, 4658, 1012, 102], - [101, 3835, 999, 102], -] -``` - -Il s'agit d'une liste de séquences encodées : une liste de listes. Les tenseurs n'acceptent que des formes rectangulaires (pensez aux matrices). Ce « tableau » est déjà de forme rectangulaire, donc le convertir en tenseur est facile : - -{#if fw === 'pt'} -```py -import torch - -model_inputs = torch.tensor(encoded_sequences) -``` -{:else} -```py -import tensorflow as tf - -model_inputs = tf.constant(encoded_sequences) -``` -{/if} - -### Utilisation des tenseurs comme entrées du modèle - -L'utilisation des tenseurs avec le modèle est extrêmement simple, il suffit d'appeler le modèle avec les entrées : - - -```py -output = model(model_inputs) -``` - -Bien que le modèle accepte un grand nombre d'arguments différents, seuls les identifiants d'entrée sont nécessaires. Nous expliquerons plus tard ce que font les autres arguments et quand ils sont nécessaires. Avant cela, regardons de plus près les *tokenizers*, cet outil qui construit les entrées qu'un *transformer* peut comprendre. + + +# Les modèles + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + +{:else} + +{/if} + +{#if fw === 'pt'} +Dans cette section, nous allons examiner de plus près la création et l'utilisation d'un modèle. Nous utiliserons la classe `AutoModel` qui est pratique lorsque vous voulez instancier n'importe quel modèle à partir d'un checkpoint. + +La classe `AutoModel` et toutes les classes apparentées sont en fait de simples *wrappers* sur la grande variété de modèles disponibles dans la bibliothèque. C'est une enveloppe intelligente car elle peut automatiquement deviner l'architecture appropriée pour votre *checkpoint* et ensuite instancier un modèle avec cette architecture. + +{:else} +Dans cette section, nous allons examiner de plus près la création et l'utilisation d'un modèle. Nous utiliserons la classe `TFAutoModel` qui est pratique lorsque vous voulez instancier n'importe quel modèle à partir d'un checkpoint. + +La classe `TFAutoModel` et toutes les classes apparentées sont en fait de simples *wrappers* sur la grande variété de modèles disponibles dans la bibliothèque. C'est une enveloppe intelligente car elle peut automatiquement deviner l'architecture appropriée pour votre *checkpoint* et ensuite instancier un modèle avec cette architecture. + +{/if} + +Cependant, si vous connaissez le type de modèle que vous voulez utiliser, vous pouvez utiliser directement la classe qui définit son architecture. Voyons comment cela fonctionne avec un modèle BERT. + +## Création d’un transformer + +La première chose que nous devons faire pour initialiser un modèle BERT est de charger un objet configuration : + +{#if fw === 'pt'} +```py +from transformers import BertConfig, BertModel + +# Construire la configuration +config = BertConfig() + +# Construire le modèle à partir de la configuration +model = BertModel(config) +``` +{:else} +```py +from transformers import BertConfig, TFBertModel + +# Construire la configuration +config = BertConfig() + +# Construire le modèle à partir de la configuration +model = TFBertModel(config) +``` +{/if} + +La configuration contient de nombreux attributs qui sont utilisés pour construire le modèle : +```py +print(config) +``` + +```python out +BertConfig { + [...] + "hidden_size": 768, + "intermediate_size": 3072, + "max_position_embeddings": 512, + "num_attention_heads": 12, + "num_hidden_layers": 12, + [...] +} +``` + +Bien que vous n'ayez pas encore vu ce que font tous ces attributs, vous devriez en reconnaître certains : l'attribut `hidden_size` définit la taille du vecteur `hidden_states`, et `num_hidden_layers` définit le nombre de couches que le *transformer* possède. + +### Différentes méthodes de chargement + +Un modèle créé à partir de la configuration par défaut est initialisé avec des valeurs aléatoires : + + +{#if fw === 'pt'} +```py +from transformers import BertConfig, BertModel + +config = BertConfig() +model = BertModel(config) + +# Le modèle est initialisé de façon aléatoire ! +``` +{:else} +```py +from transformers import BertConfig, TFBertModel + +config = BertConfig() +model = TFBertModel(config) + +# Le modèle est initialisé de façon aléatoire ! +``` +{/if} + +Le modèle peut être utilisé tel quel mais il produira du charabia. En effet, il doit d'abord être entraîné. Nous pourrions entraîner le modèle à partir de zéro sur la tâche qui nous intéresse mais comme vous l'avez vu dans le [chapitre 1](/course/fr/chapter1) cela nécessiterait beaucoup de temps et de données. De plus cela aurait un impact environnemental non négligeable. Pour éviter les efforts inutiles et redondants, il est impératif de pouvoir partager et réutiliser les modèles qui ont déjà été entraînés. + +Charger un *transformer* qui a déjà été entraîné est simple : nous pouvons le faire en utilisant la méthode `from_pretrained()` : + + +{#if fw === 'pt'} +```py +from transformers import BertModel + +model = BertModel.from_pretrained("bert-base-cased") +``` + +Comme vu précédemment, nous pouvons remplacer `BertModel` par la classe équivalente `AutoModel`. A partir de maintenant nous ferons cela car cela produit un code agnostique *checkpoint*, c’est-à-dire que si votre code fonctionne pour un *checkpoint* donné, il devrait fonctionner sans problème avec un autre. Cela s'applique même si l'architecture est différente du moment que le *checkpoint* a été entraîné pour une tâche similaire (par exemple, une tâche d'analyse de sentiments). + +{:else} +```py +from transformers import TFBertModel + +model = TFBertModel.from_pretrained("bert-base-cased") +``` + +Comme vu précédemment, nous pouvons remplacer `TFBertModel` par la classe équivalente `TFAutoModel`. A partir de maintenant nous ferons cela car cela produit un code agnostique *checkpoint*, c’est-à-dire que si votre code fonctionne pour un *checkpoint* donné, il devrait fonctionner sans problème avec un autre. Cela s'applique même si l'architecture est différente du moment que le *checkpoint* a été entraîné pour une tâche similaire (par exemple, une tâche d'analyse de sentiments). + +{/if} + +Dans l'exemple de code ci-dessus, nous n'avons pas utilisé `BertConfig` et avons à la place chargé un modèle pré-entraîné via l'identifiant `bert-base-cased`. Il s'agit d'un *checkpoint* qui a été entraîné par les auteurs de BERT eux-mêmes. Vous pouvez trouver davantage de détails à son sujet dans la [fiche du modèle](https://huggingface.co/bert-base-cased). + +Ce modèle est maintenant initialisé avec tous les poids du *checkpoint*. Il peut être utilisé directement pour l'inférence sur les tâches sur lesquelles il a été entraîné. Il peut également être *finetuné* sur une nouvelle tâche. En entraînant avec des poids pré-entraînés plutôt qu'à partir de zéro, nous pouvons rapidement obtenir de bons résultats. + +Les poids ont été téléchargés et mis en cache (afin que les futurs appels à la méthode `from_pretrained()` ne les retéléchargent pas) dans le dossier cache, qui est par défaut *~/.cache/huggingface/transformers*. Vous pouvez personnaliser votre dossier de cache en définissant la variable d'environnement `HF_HOME`. + +L'identifiant utilisé pour charger le modèle peut être l'identifiant de n'importe quel modèle sur le *Model Hub* du moment qu'il est compatible avec l'architecture BERT. La liste complète des *checkpoints* de BERT disponibles peut être trouvée [ici](https://huggingface.co/models?filter=bert). + +### Méthodes de sauvegarde + +Sauvegarder un modèle est aussi facile que d'en charger un. Nous utilisons la méthode `save_pretrained()`, qui est analogue à la méthode `from_pretrained()` : + + +```py +model.save_pretrained("directory_on_my_computer") +``` + +Cela enregistre deux fichiers sur votre disque : + +{#if fw === 'pt'} +``` +ls directory_on_my_computer + +config.json pytorch_model.bin +``` +{:else} +``` +ls directory_on_my_computer + +config.json tf_model.h5 +``` +{/if} + +Si vous jetez un coup d'œil au fichier *config.json*, vous reconnaîtrez les attributs nécessaires pour construire l'architecture du modèle. Ce fichier contient également certaines métadonnées, comme l'origine du *checkpoint* et la version de la bibliothèque 🤗 *Transformers* que vous utilisiez lors du dernier enregistrement du point *checkpoint*. + +{#if fw === 'pt'} +Le fichier *pytorch_model.bin* est connu comme le *dictionnaire d'état*. Il contient tous les poids de votre modèle. Les deux fichiers vont de pair : la configuration est nécessaire pour connaître l'architecture de votre modèle, tandis que les poids du modèle sont les paramètres de votre modèle. + +{:else} +Le fichier *tf_model.h5* est connu comme le *dictionnaire d'état*. Il contient tous les poids de votre modèle. Les deux fichiers vont de pair : la configuration est nécessaire pour connaître l'architecture de votre modèle, tandis que les poids du modèle sont les paramètres de votre modèle. + +{/if} + +### Utilisation d'un transformer pour l'inférence + +Maintenant que vous savez comment charger et sauvegarder un modèle, essayons de l'utiliser pour faire quelques prédictions. Les *transformers* ne peuvent traiter que des nombres. Des nombres que le *tokenizer* génère. Mais avant de parler des *tokenizers*, explorons les entrées que le modèle accepte. + +Les *tokenizers* se chargent de passer les entrées vers les tenseurs du *framework* approprié. Pour vous aider à comprendre ce qui se passe, jetons un coup d'œil rapide à ce qui doit être fait avant d'envoyer les entrées au modèle. + +Disons que nous avons les séquences suivantes : + + +```py +sequences = ["Hello!", "Cool.", "Nice!"] +``` + +Le *tokenizer* les convertit en indices de vocabulaire qui sont généralement appelés *input IDs*. Chaque séquence est maintenant une liste de nombres ! La sortie résultante est : + +```py no-format +encoded_sequences = [ + [101, 7592, 999, 102], + [101, 4658, 1012, 102], + [101, 3835, 999, 102], +] +``` + +Il s'agit d'une liste de séquences encodées : une liste de listes. Les tenseurs n'acceptent que des formes rectangulaires (pensez aux matrices). Ce « tableau » est déjà de forme rectangulaire, donc le convertir en tenseur est facile : + +{#if fw === 'pt'} +```py +import torch + +model_inputs = torch.tensor(encoded_sequences) +``` +{:else} +```py +import tensorflow as tf + +model_inputs = tf.constant(encoded_sequences) +``` +{/if} + +### Utilisation des tenseurs comme entrées du modèle + +L'utilisation des tenseurs avec le modèle est extrêmement simple, il suffit d'appeler le modèle avec les entrées : + + +```py +output = model(model_inputs) +``` + +Bien que le modèle accepte un grand nombre d'arguments différents, seuls les identifiants d'entrée sont nécessaires. Nous expliquerons plus tard ce que font les autres arguments et quand ils sont nécessaires. Avant cela, regardons de plus près les *tokenizers*, cet outil qui construit les entrées qu'un *transformer* peut comprendre. diff --git a/chapters/fr/chapter2/4.mdx b/chapters/fr/chapter2/4.mdx index 2015763fb..6445f167e 100644 --- a/chapters/fr/chapter2/4.mdx +++ b/chapters/fr/chapter2/4.mdx @@ -1,253 +1,253 @@ - - -# Les tokenizers - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - - - -Les *tokenizers* sont l'un des principaux composants du pipeline de NLP. Ils ont un seul objectif : traduire le texte en données pouvant être traitées par le modèle. Les modèles ne pouvant traiter que des nombres, les *tokenizers* doivent convertir nos entrées textuelles en données numériques. Dans cette section, nous allons explorer ce qui se passe exactement dans le pipeline de tokénisation. - -Dans les tâches de NLP, les données traitées sont généralement du texte brut. Voici un exemple de ce type de texte : - - -``` -Jim Henson was a puppeteer # Jim Henson était un marionnettiste -``` - -Les modèles ne pouvant traiter que des nombres, nous devons trouver un moyen de convertir le texte brut en nombres. C'est ce que font les *tokenizers* et il existe de nombreuses façons de procéder. L'objectif est de trouver la représentation la plus significative, c'est-à-dire celle qui a le plus de sens pour le modèle, et si possible qui soit la plus petite. - -Voyons quelques exemples d'algorithmes de tokénisation et essayons de répondre à certaines des questions que vous pouvez vous poser à ce sujet. - -## Tokenizer basé sur les mots - - - - -Le premier type de *tokenizer* qui vient à l'esprit est celui basé sur les mots. Il est généralement très facile à utiliser et configurable avec seulement quelques règles. Il donne souvent des résultats décents. Par exemple, dans l'image ci-dessous, l'objectif est de diviser le texte brut en mots et de trouver une représentation numérique pour chacun d'eux : - -
- An example of word-based tokenization. - -
- -Il existe différentes façons de diviser le texte. Par exemple, nous pouvons utiliser les espaces pour segmenter le texte en mots en appliquant la fonction `split()` de Python : - -```py -tokenized_text = "Jim Henson was a puppeteer".split() -print(tokenized_text) -``` - -```python out -['Jim', 'Henson', 'was', 'a', 'puppeteer'] # ['Jim', 'Henson', était, 'un', 'marionnettiste'] -``` - -Il existe également des variantes des *tokenizers* basés sur les mots qui ont des règles supplémentaires pour la ponctuation. Avec ce type de *tokenizers* nous pouvons nous retrouver avec des « vocabulaires » assez larges, où un vocabulaire est défini par le nombre total de *tokens* indépendants que nous avons dans notre corpus. - -Un identifiant est attribué à chaque mot, en commençant par 0 et en allant jusqu'à la taille du vocabulaire. Le modèle utilise ces identifiants pour identifier chaque mot. - -Si nous voulons couvrir complètement une langue avec un *tokenizer* basé sur les mots, nous devons avoir un identifiant pour chaque mot de la langue que nous traitons, ce qui génère une énorme quantité de *tokens*. Par exemple, il y a plus de 500 000 mots dans la langue anglaise. Ainsi pour associer chaque mot à un identifiant, nous devrions garder la trace d'autant d'identifiants. De plus, des mots comme « chien » sont représentés différemment de mots comme « chiens ». Le modèle n'a initialement aucun moyen de savoir que « chien » et « chiens » sont similaires : il identifie les deux mots comme non apparentés. Il en va de même pour d'autres mots similaires, comme « maison » et « maisonnette » que le modèle ne considérera pas comme similaires au départ. - -Enfin, nous avons besoin d'un *token* personnalisé pour représenter les mots qui ne font pas partie de notre vocabulaire. C'est ce qu'on appelle le *token* « inconnu » souvent représenté par « [UNK] » (de l’anglais « unknown ») ou « <unk> ; ». C'est généralement un mauvais signe si vous constatez que le *tokenizer* produit un nombre important de ce jeton spécial. Cela signifie qu’il n'a pas été en mesure de récupérer une représentation sensée d'un mot et que vous perdez des informations en cours de route. L'objectif de l'élaboration du vocabulaire est de faire en sorte que le *tokenizer* transforme le moins de mots possible en *token* inconnu. - -Une façon de réduire la quantité de *tokens* inconnus est d'aller un niveau plus profond, en utilisant un *tokenizer* basé sur les caractères. - - -## Tokenizer basé sur les caractères - - - -Les *tokenizers* basés sur les caractères divisent le texte en caractères, plutôt qu'en mots. Cela présente deux avantages principaux : - -- le vocabulaire est beaucoup plus petit -- il y a beaucoup moins de *tokens* hors vocabulaire (inconnus) puisque chaque mot peut être construit à partir de caractères. - -Mais là aussi, des questions se posent concernant les espaces et la ponctuation : - - -
- An example of character-based tokenization. - -
- -Cette approche n'est pas non plus parfaite. Puisque la représentation est maintenant basée sur des caractères plutôt que sur des mots, on pourrait dire intuitivement qu’elle est moins significative : chaque caractère ne signifie pas grand-chose en soi, alors que c'est le cas pour les mots. Toutefois, là encore, cela diffère selon la langue. En chinois, par exemple, chaque caractère est porteur de plus d'informations qu'un caractère dans une langue latine. - -Un autre élément à prendre en compte est que nous nous retrouverons avec une très grande quantité de *tokens* à traiter par notre modèle. Alors qu’avec un *tokenizer* basé sur les mots, pour un mot donné on aurait qu'un seul *token*, avec un *tokenizer* basé sur les caractères, cela peut facilement se transformer en 10 *tokens* voire plus. - -Pour obtenir le meilleur des deux mondes, nous pouvons utiliser une troisième technique qui combine les deux approches : la *tokénisation en sous-mots*. - - -## Tokénisation en sous-mots - - - -Les algorithmes de tokenisation en sous-mots reposent sur le principe selon lequel les mots fréquemment utilisés ne doivent pas être divisés en sous-mots plus petits, mais les mots rares doivent être décomposés en sous-mots significatifs. - -Par exemple, le mot « maisonnette » peut être considéré comme un mot rare et peut être décomposé en « maison » et « ette ». Ces deux mots sont susceptibles d'apparaître plus fréquemment en tant que sous-mots autonomes, alors qu'en même temps le sens de « maison » est conservé par le sens composite de « maison » et « ette ». - -Voici un exemple montrant comment un algorithme de tokenisation en sous-mots tokeniserait la séquence « *Let's do tokenization* ! » : - - -
- A subword tokenization algorithm. - -
- -Ces sous-mots finissent par fournir beaucoup de sens sémantique. Par exemple, ci-dessus, « *tokenization* » a été divisé en « *token* » et « *ization* » : deux *tokens* qui ont un sens sémantique tout en étant peu encombrants (seuls deux *tokens* sont nécessaires pour représenter un long mot). Cela nous permet d'avoir une couverture relativement bonne avec de petits vocabulaires et presque aucun *token* inconnu. - -Cette approche est particulièrement utile dans les langues agglutinantes comme le turc, où l'on peut former des mots complexes (presque) arbitrairement longs en enchaînant des sous-mots. - - -### Et plus encore ! - -Il existe de nombreuses autres techniques. Pour n'en citer que quelques-unes : - -- le *Byte-level BPE* utilisé par exemple dans le GPT-2 -- le *WordPiece* utilisé par exemple dans BERT -- *SentencePiece* ou *Unigram*, utilisés dans plusieurs modèles multilingues. - -Vous devriez maintenant avoir une connaissance suffisante du fonctionnement des tokenizers pour commencer à utiliser l'API. - - -## Chargement et sauvegarde - -Le chargement et la sauvegarde des *tokenizers* est aussi simple que pour les modèles. En fait, c'est basé sur les deux mêmes méthodes : `from_pretrained()` et `save_pretrained()`. Ces méthodes vont charger ou sauvegarder l'algorithme utilisé par le *tokenizer* (un peu comme l'*architecture* du modèle) ainsi que son vocabulaire (un peu comme les *poids* du modèle). - -Le chargement du *tokenizer* de BERT entraîné avec le même *checkpoint* que BERT se fait de la même manière que le chargement du modèle, sauf que nous utilisons la classe `BertTokenizer` : - - -```py -from transformers import BertTokenizer - -tokenizer = BertTokenizer.from_pretrained("bert-base-cased") -``` - -{#if fw === 'pt'} -Similaire à `AutoModel`, la classe `AutoTokenizer` récupère la classe de *tokenizer* appropriée dans la bibliothèque basée sur le nom du *checkpoint*. Elle peut être utilisée directement avec n'importe quel *checkpoint* : - -{:else} -Similaire à `TFAutoModel`, la classe `AutoTokenizer` récupère la classe de *tokenizer* appropriée dans la bibliothèque basée sur le nom du *checkpoint*. Elle peut être utilisée directement avec n'importe quel *checkpoint* : - -{/if} - -```py -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") -``` - -Nous pouvons à présent utiliser le *tokenizer* comme indiqué dans la section précédente : - -```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]} -``` - -La sauvegarde d'un tokenizer est identique à celle d'un modèle : - -```py -tokenizer.save_pretrained("directory_on_my_computer") -``` - -Nous parlerons plus en détail des `token_type_ids` au [chapitre 3](/course/fr/chapter3) et nous expliquerons la clé `attention_mask` un peu plus tard. Tout d'abord, voyons comment les `input_ids` sont générés. Pour ce faire, nous devons examiner les méthodes intermédiaires du *tokenizer*. - -## Encodage - - - - -La traduction d'un texte en chiffres est connue sous le nom d’*encodage*. L'encodage se fait en deux étapes : la tokenisation, suivie de la conversion en identifiants d'entrée. - -Comme nous l'avons vu, la première étape consiste à diviser le texte en mots (ou parties de mots, symboles de ponctuation, etc.), généralement appelés *tokens*. De nombreuses règles peuvent régir ce processus. C'est pourquoi nous devons instancier le *tokenizer* en utilisant le nom du modèle afin de nous assurer que nous utilisons les mêmes règles que celles utilisées lors du pré-entraînement du modèle. - -La deuxième étape consiste à convertir ces *tokens* en nombres afin de construire un tenseur à partir de ceux-ci ainsi que de les transmettre au modèle. Pour ce faire, le *tokenizer* possède un *vocabulaire*, qui est la partie que nous téléchargeons lorsque nous l'instancions avec la méthode `from_pretrained()`. Encore une fois, nous devons utiliser le même vocabulaire que celui utilisé lors du pré-entraînement du modèle. - -Pour mieux comprendre les deux étapes, nous allons les explorer séparément. A noter que nous utilisons des méthodes effectuant séparément des parties du pipeline de tokenisation afin de montrer les résultats intermédiaires de ces étapes. Néanmoins, en pratique, il faut appeler le *tokenizer* directement sur vos entrées (comme indiqué dans la section 2). - -### Tokenisation - -Le processus de tokenisation est effectué par la méthode `tokenize()` du *tokenizer* : - - -```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) -``` - -La sortie de cette méthode est une liste de chaînes de caractères ou de *tokens* : - -```python out -['Using', 'a', 'transform', '##er', 'network', 'is', 'simple'] -``` - -Ce *tokenizer* est un *tokenizer* de sous-mots : il découpe les mots jusqu'à obtenir des *tokens* qui peuvent être représentés par son vocabulaire. C'est le cas ici avec `transformer` qui est divisé en deux *tokens* : `transform` et `##er`. - -### De tokens aux identifiants d'entrée - -La conversion en identifiants d'entrée est gérée par la méthode `convert_tokens_to_ids()` du *tokenizer* : - - -```py -ids = tokenizer.convert_tokens_to_ids(tokens) - -print(ids) -``` - -```python out -[7993, 170, 11303, 1200, 2443, 1110, 3014] -``` - -Une fois converties en tenseur dans le *framework* approprié, ces sorties peuvent ensuite être utilisées comme entrées d'un modèle, comme nous l'avons vu précédemment dans ce chapitre. - - - -✏️ **Essayez !** Reproduisez les deux dernières étapes (tokénisation et conversion en identifiants d'entrée) sur les phrases des entrées que nous avons utilisées dans la section 2 (« I've been waiting for a HuggingFace course my whole life. » et « I hate this so much! »). Vérifiez que vous obtenez les mêmes identifiants d'entrée que nous avons obtenus précédemment ! - - - -## Décodage - -Le *décodage* va dans l'autre sens : à partir d'indices du vocabulaire nous voulons obtenir une chaîne de caractères. Cela peut être fait avec la méthode `decode()` comme suit : - - -```py -decoded_string = tokenizer.decode([7993, 170, 11303, 1200, 2443, 1110, 3014]) -print(decoded_string) -``` - -```python out -'Using a Transformer network is simple' -``` - -Notez que la méthode `decode` non seulement reconvertit les indices en *tokens* mais regroupe également les *tokens* faisant partie des mêmes mots. Le but étant de produire une phrase lisible. Ce comportement sera extrêmement utile lorsque dans la suite du cours nous utiliserons des modèles pouvant produire du nouveau texte (soit du texte généré à partir d'un *prompt*, soit pour des problèmes de séquence à séquence comme la traduction ou le résumé de texte). - -Vous devriez maintenant comprendre les opérations atomiques qu'un *tokenizer* peut gérer : tokenisation, conversion en identifiants, et reconversion des identifiants en chaîne de caractères. Cependant, nous n'avons fait qu'effleurer la partie émergée de l'iceberg. Dans la section suivante, nous allons pousser notre approche jusqu'à ses limites et voir comment les surmonter. + + +# Les tokenizers + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + + +Les *tokenizers* sont l'un des principaux composants du pipeline de NLP. Ils ont un seul objectif : traduire le texte en données pouvant être traitées par le modèle. Les modèles ne pouvant traiter que des nombres, les *tokenizers* doivent convertir nos entrées textuelles en données numériques. Dans cette section, nous allons explorer ce qui se passe exactement dans le pipeline de tokénisation. + +Dans les tâches de NLP, les données traitées sont généralement du texte brut. Voici un exemple de ce type de texte : + + +``` +Jim Henson was a puppeteer # Jim Henson était un marionnettiste +``` + +Les modèles ne pouvant traiter que des nombres, nous devons trouver un moyen de convertir le texte brut en nombres. C'est ce que font les *tokenizers* et il existe de nombreuses façons de procéder. L'objectif est de trouver la représentation la plus significative, c'est-à-dire celle qui a le plus de sens pour le modèle, et si possible qui soit la plus petite. + +Voyons quelques exemples d'algorithmes de tokénisation et essayons de répondre à certaines des questions que vous pouvez vous poser à ce sujet. + +## Tokenizer basé sur les mots + + + + +Le premier type de *tokenizer* qui vient à l'esprit est celui basé sur les mots. Il est généralement très facile à utiliser et configurable avec seulement quelques règles. Il donne souvent des résultats décents. Par exemple, dans l'image ci-dessous, l'objectif est de diviser le texte brut en mots et de trouver une représentation numérique pour chacun d'eux : + +
+ An example of word-based tokenization. + +
+ +Il existe différentes façons de diviser le texte. Par exemple, nous pouvons utiliser les espaces pour segmenter le texte en mots en appliquant la fonction `split()` de Python : + +```py +tokenized_text = "Jim Henson was a puppeteer".split() +print(tokenized_text) +``` + +```python out +['Jim', 'Henson', 'was', 'a', 'puppeteer'] # ['Jim', 'Henson', était, 'un', 'marionnettiste'] +``` + +Il existe également des variantes des *tokenizers* basés sur les mots qui ont des règles supplémentaires pour la ponctuation. Avec ce type de *tokenizers* nous pouvons nous retrouver avec des « vocabulaires » assez larges, où un vocabulaire est défini par le nombre total de *tokens* indépendants que nous avons dans notre corpus. + +Un identifiant est attribué à chaque mot, en commençant par 0 et en allant jusqu'à la taille du vocabulaire. Le modèle utilise ces identifiants pour identifier chaque mot. + +Si nous voulons couvrir complètement une langue avec un *tokenizer* basé sur les mots, nous devons avoir un identifiant pour chaque mot de la langue que nous traitons, ce qui génère une énorme quantité de *tokens*. Par exemple, il y a plus de 500 000 mots dans la langue anglaise. Ainsi pour associer chaque mot à un identifiant, nous devrions garder la trace d'autant d'identifiants. De plus, des mots comme « chien » sont représentés différemment de mots comme « chiens ». Le modèle n'a initialement aucun moyen de savoir que « chien » et « chiens » sont similaires : il identifie les deux mots comme non apparentés. Il en va de même pour d'autres mots similaires, comme « maison » et « maisonnette » que le modèle ne considérera pas comme similaires au départ. + +Enfin, nous avons besoin d'un *token* personnalisé pour représenter les mots qui ne font pas partie de notre vocabulaire. C'est ce qu'on appelle le *token* « inconnu » souvent représenté par « [UNK] » (de l’anglais « unknown ») ou « <unk> ; ». C'est généralement un mauvais signe si vous constatez que le *tokenizer* produit un nombre important de ce jeton spécial. Cela signifie qu’il n'a pas été en mesure de récupérer une représentation sensée d'un mot et que vous perdez des informations en cours de route. L'objectif de l'élaboration du vocabulaire est de faire en sorte que le *tokenizer* transforme le moins de mots possible en *token* inconnu. + +Une façon de réduire la quantité de *tokens* inconnus est d'aller un niveau plus profond, en utilisant un *tokenizer* basé sur les caractères. + + +## Tokenizer basé sur les caractères + + + +Les *tokenizers* basés sur les caractères divisent le texte en caractères, plutôt qu'en mots. Cela présente deux avantages principaux : + +- le vocabulaire est beaucoup plus petit +- il y a beaucoup moins de *tokens* hors vocabulaire (inconnus) puisque chaque mot peut être construit à partir de caractères. + +Mais là aussi, des questions se posent concernant les espaces et la ponctuation : + + +
+ An example of character-based tokenization. + +
+ +Cette approche n'est pas non plus parfaite. Puisque la représentation est maintenant basée sur des caractères plutôt que sur des mots, on pourrait dire intuitivement qu’elle est moins significative : chaque caractère ne signifie pas grand-chose en soi, alors que c'est le cas pour les mots. Toutefois, là encore, cela diffère selon la langue. En chinois, par exemple, chaque caractère est porteur de plus d'informations qu'un caractère dans une langue latine. + +Un autre élément à prendre en compte est que nous nous retrouverons avec une très grande quantité de *tokens* à traiter par notre modèle. Alors qu’avec un *tokenizer* basé sur les mots, pour un mot donné on aurait qu'un seul *token*, avec un *tokenizer* basé sur les caractères, cela peut facilement se transformer en 10 *tokens* voire plus. + +Pour obtenir le meilleur des deux mondes, nous pouvons utiliser une troisième technique qui combine les deux approches : la *tokénisation en sous-mots*. + + +## Tokénisation en sous-mots + + + +Les algorithmes de tokenisation en sous-mots reposent sur le principe selon lequel les mots fréquemment utilisés ne doivent pas être divisés en sous-mots plus petits, mais les mots rares doivent être décomposés en sous-mots significatifs. + +Par exemple, le mot « maisonnette » peut être considéré comme un mot rare et peut être décomposé en « maison » et « ette ». Ces deux mots sont susceptibles d'apparaître plus fréquemment en tant que sous-mots autonomes, alors qu'en même temps le sens de « maison » est conservé par le sens composite de « maison » et « ette ». + +Voici un exemple montrant comment un algorithme de tokenisation en sous-mots tokeniserait la séquence « *Let's do tokenization* ! » : + + +
+ A subword tokenization algorithm. + +
+ +Ces sous-mots finissent par fournir beaucoup de sens sémantique. Par exemple, ci-dessus, « *tokenization* » a été divisé en « *token* » et « *ization* » : deux *tokens* qui ont un sens sémantique tout en étant peu encombrants (seuls deux *tokens* sont nécessaires pour représenter un long mot). Cela nous permet d'avoir une couverture relativement bonne avec de petits vocabulaires et presque aucun *token* inconnu. + +Cette approche est particulièrement utile dans les langues agglutinantes comme le turc, où l'on peut former des mots complexes (presque) arbitrairement longs en enchaînant des sous-mots. + + +### Et plus encore ! + +Il existe de nombreuses autres techniques. Pour n'en citer que quelques-unes : + +- le *Byte-level BPE* utilisé par exemple dans le GPT-2 +- le *WordPiece* utilisé par exemple dans BERT +- *SentencePiece* ou *Unigram*, utilisés dans plusieurs modèles multilingues. + +Vous devriez maintenant avoir une connaissance suffisante du fonctionnement des tokenizers pour commencer à utiliser l'API. + + +## Chargement et sauvegarde + +Le chargement et la sauvegarde des *tokenizers* est aussi simple que pour les modèles. En fait, c'est basé sur les deux mêmes méthodes : `from_pretrained()` et `save_pretrained()`. Ces méthodes vont charger ou sauvegarder l'algorithme utilisé par le *tokenizer* (un peu comme l'*architecture* du modèle) ainsi que son vocabulaire (un peu comme les *poids* du modèle). + +Le chargement du *tokenizer* de BERT entraîné avec le même *checkpoint* que BERT se fait de la même manière que le chargement du modèle, sauf que nous utilisons la classe `BertTokenizer` : + + +```py +from transformers import BertTokenizer + +tokenizer = BertTokenizer.from_pretrained("bert-base-cased") +``` + +{#if fw === 'pt'} +Similaire à `AutoModel`, la classe `AutoTokenizer` récupère la classe de *tokenizer* appropriée dans la bibliothèque basée sur le nom du *checkpoint*. Elle peut être utilisée directement avec n'importe quel *checkpoint* : + +{:else} +Similaire à `TFAutoModel`, la classe `AutoTokenizer` récupère la classe de *tokenizer* appropriée dans la bibliothèque basée sur le nom du *checkpoint*. Elle peut être utilisée directement avec n'importe quel *checkpoint* : + +{/if} + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +``` + +Nous pouvons à présent utiliser le *tokenizer* comme indiqué dans la section précédente : + +```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]} +``` + +La sauvegarde d'un tokenizer est identique à celle d'un modèle : + +```py +tokenizer.save_pretrained("directory_on_my_computer") +``` + +Nous parlerons plus en détail des `token_type_ids` au [chapitre 3](/course/fr/chapter3) et nous expliquerons la clé `attention_mask` un peu plus tard. Tout d'abord, voyons comment les `input_ids` sont générés. Pour ce faire, nous devons examiner les méthodes intermédiaires du *tokenizer*. + +## Encodage + + + + +La traduction d'un texte en chiffres est connue sous le nom d’*encodage*. L'encodage se fait en deux étapes : la tokenisation, suivie de la conversion en identifiants d'entrée. + +Comme nous l'avons vu, la première étape consiste à diviser le texte en mots (ou parties de mots, symboles de ponctuation, etc.), généralement appelés *tokens*. De nombreuses règles peuvent régir ce processus. C'est pourquoi nous devons instancier le *tokenizer* en utilisant le nom du modèle afin de nous assurer que nous utilisons les mêmes règles que celles utilisées lors du pré-entraînement du modèle. + +La deuxième étape consiste à convertir ces *tokens* en nombres afin de construire un tenseur à partir de ceux-ci ainsi que de les transmettre au modèle. Pour ce faire, le *tokenizer* possède un *vocabulaire*, qui est la partie que nous téléchargeons lorsque nous l'instancions avec la méthode `from_pretrained()`. Encore une fois, nous devons utiliser le même vocabulaire que celui utilisé lors du pré-entraînement du modèle. + +Pour mieux comprendre les deux étapes, nous allons les explorer séparément. A noter que nous utilisons des méthodes effectuant séparément des parties du pipeline de tokenisation afin de montrer les résultats intermédiaires de ces étapes. Néanmoins, en pratique, il faut appeler le *tokenizer* directement sur vos entrées (comme indiqué dans la section 2). + +### Tokenisation + +Le processus de tokenisation est effectué par la méthode `tokenize()` du *tokenizer* : + + +```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) +``` + +La sortie de cette méthode est une liste de chaînes de caractères ou de *tokens* : + +```python out +['Using', 'a', 'transform', '##er', 'network', 'is', 'simple'] +``` + +Ce *tokenizer* est un *tokenizer* de sous-mots : il découpe les mots jusqu'à obtenir des *tokens* qui peuvent être représentés par son vocabulaire. C'est le cas ici avec `transformer` qui est divisé en deux *tokens* : `transform` et `##er`. + +### De tokens aux identifiants d'entrée + +La conversion en identifiants d'entrée est gérée par la méthode `convert_tokens_to_ids()` du *tokenizer* : + + +```py +ids = tokenizer.convert_tokens_to_ids(tokens) + +print(ids) +``` + +```python out +[7993, 170, 11303, 1200, 2443, 1110, 3014] +``` + +Une fois converties en tenseur dans le *framework* approprié, ces sorties peuvent ensuite être utilisées comme entrées d'un modèle, comme nous l'avons vu précédemment dans ce chapitre. + + + +✏️ **Essayez !** Reproduisez les deux dernières étapes (tokénisation et conversion en identifiants d'entrée) sur les phrases des entrées que nous avons utilisées dans la section 2 (« I've been waiting for a HuggingFace course my whole life. » et « I hate this so much! »). Vérifiez que vous obtenez les mêmes identifiants d'entrée que nous avons obtenus précédemment ! + + + +## Décodage + +Le *décodage* va dans l'autre sens : à partir d'indices du vocabulaire nous voulons obtenir une chaîne de caractères. Cela peut être fait avec la méthode `decode()` comme suit : + + +```py +decoded_string = tokenizer.decode([7993, 170, 11303, 1200, 2443, 1110, 3014]) +print(decoded_string) +``` + +```python out +'Using a Transformer network is simple' +``` + +Notez que la méthode `decode` non seulement reconvertit les indices en *tokens* mais regroupe également les *tokens* faisant partie des mêmes mots. Le but étant de produire une phrase lisible. Ce comportement sera extrêmement utile lorsque dans la suite du cours nous utiliserons des modèles pouvant produire du nouveau texte (soit du texte généré à partir d'un *prompt*, soit pour des problèmes de séquence à séquence comme la traduction ou le résumé de texte). + +Vous devriez maintenant comprendre les opérations atomiques qu'un *tokenizer* peut gérer : tokenisation, conversion en identifiants, et reconversion des identifiants en chaîne de caractères. Cependant, nous n'avons fait qu'effleurer la partie émergée de l'iceberg. Dans la section suivante, nous allons pousser notre approche jusqu'à ses limites et voir comment les surmonter. diff --git a/chapters/fr/chapter2/5.mdx b/chapters/fr/chapter2/5.mdx index fb85ff966..3e9bc09e4 100644 --- a/chapters/fr/chapter2/5.mdx +++ b/chapters/fr/chapter2/5.mdx @@ -1,354 +1,354 @@ - - -# Manipulation de plusieurs séquences - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -{#if fw === 'pt'} - -{:else} - -{/if} - -Dans la section précédente, nous avons exploré le cas d'utilisation le plus simple : faire une inférence sur une seule séquence de petite longueur. Cependant, certaines questions émergent déjà : - -- comment gérer de plusieurs séquences ? -- comment gérer de plusieurs séquences *de longueurs différentes* ? -- les indices du vocabulaire sont-ils les seules entrées qui permettent à un modèle de bien fonctionner ? -- existe-t-il une séquence trop longue ? - -Voyons quels types de problèmes ces questions posent et comment nous pouvons les résoudre en utilisant l'API 🤗 *Transformers*. - -## Les modèles attendent un batch d'entrées - -Dans l'exercice précédent, vous avez vu comment les séquences sont traduites en listes de nombres. -Convertissons cette liste de nombres en un tenseur et envoyons-le au modèle : - -{#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." -# J'ai attendu un cours d’HuggingFace toute ma vie. - -tokens = tokenizer.tokenize(sequence) -ids = tokenizer.convert_tokens_to_ids(tokens) -input_ids = torch.tensor(ids) -# Cette ligne va échouer. -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." -# J'ai attendu un cours d’HuggingFace toute ma vie. - -tokens = tokenizer.tokenize(sequence) -ids = tokenizer.convert_tokens_to_ids(tokens) -input_ids = tf.constant(ids) -# This line will fail. -model(input_ids) -``` - -```py out -InvalidArgumentError: Input to reshape is a tensor with 14 values, but the requested shape has 196 [Op:Reshape] -``` -{/if} - -Pourquoi cela a échoué ? Nous avons suivi les étapes du pipeline de la section 2. - -Le problème est que nous avons envoyé une seule séquence au modèle, alors que les modèles de l’API 🤗 *Transformers* attendent plusieurs phrases par défaut. Ici, nous avons essayé de faire ce que le *tokenizer* fait en coulisses lorsque nous l'avons appliqué à une `séquence`. Cependant si vous regardez de près, vous verrez qu'il n'a pas seulement converti la liste des identifiants d'entrée en un tenseur mais aussi ajouté une dimension par-dessus : - - -{#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} - -Essayons à nouveau en ajoutant une nouvelle dimension : - -{#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." -# J'ai attendu un cours d’HuggingFace toute ma vie. - - -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." -# J'ai attendu un cours d’HuggingFace toute ma vie. - - -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} - -Nous affichons les identifiants d'entrée ainsi que les logits résultants. Voici la sortie : - -{#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} - -Le « *batching* » est l'acte d'envoyer plusieurs phrases à travers le modèle, toutes en même temps. Si vous n'avez qu'une seule phrase, vous pouvez simplement construire un batch avec une seule séquence : - -``` -batched_ids = [ids, ids] -``` - -Il s'agit d'un batch de deux séquences identiques ! - - - -✏️ **Essayez !** Convertissez cette liste `batched_ids` en un tenseur et passez-la dans votre modèle. Vérifiez que vous obtenez les mêmes logits que précédemment (mais deux fois) ! - - - -Utiliser des *batchs* permet au modèle de fonctionner lorsque vous lui donnez plusieurs séquences. Utiliser plusieurs séquences est aussi simple que de construire un batch avec une seule séquence. Il y a cependant un deuxième problème. Lorsque vous essayez de regrouper deux phrases (ou plus), elles peuvent être de longueurs différentes. Si vous avez déjà travaillé avec des tenseurs, vous savez qu'ils doivent être de forme rectangulaire. Vous ne pourrez donc pas convertir directement la liste des identifiants d'entrée en un tenseur. Pour contourner ce problème, nous avons l'habitude de *rembourrer*/*remplir* (le *padding* en anglais) les entrées. - -## Padding des entrées - -La liste de listes suivante ne peut pas être convertie en un tenseur : - - -```py no-format -batched_ids = [ - [200, 200, 200], - [200, 200] -] -``` - -Afin de contourner ce problème, nous utilisons le *padding* pour que nos tenseurs aient une forme rectangulaire. Le *padding* permet de s'assurer que toutes nos phrases ont la même longueur en ajoutant un mot spécial appelé *padding token* aux phrases ayant moins de valeurs. Par exemple, si vous avez 10 phrases de 10 mots et 1 phrase de 20 mots, le *padding* fait en sorte que toutes les phrases aient 20 mots. Dans notre exemple, le tenseur résultant ressemble à ceci : - -```py no-format -padding_id = 100 - -batched_ids = [ - [200, 200, 200], - [200, 200, padding_id], -] -``` - -L'identifiant du jeton de *padding* peut être trouvé dans `tokenizer.pad_token_id`. Utilisons-le et envoyons nos deux phrases à travers le modèle premièrement individuellement puis en étant mises dans un même batch : - -{#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} - -Il y a quelque chose qui ne va pas avec les logits de notre prédiction avec les séquences mises dans un même batch. La deuxième ligne devrait être la même que les logits pour la deuxième phrase, mais nous avons des valeurs complètement différentes ! - -C'est parce que dans un *transformer* les couches d’attention *contextualisent* chaque *token*. Celles-ci prennent en compte les *tokens* de *padding* puisqu'elles analysent tous les *tokens* d'une séquence. Pour obtenir le même résultat lorsque l'on passe dans notre modèle des phrases individuelles de différentes longueurs ou un batch composé de mêmes phrases avec *padding*, nous devons dire à ces couches d'attention d'ignorer les jetons de *padding*. Ceci est fait en utilisant un masque d'attention. - - -## Masques d'attention - -Les masques d'attention sont des tenseurs ayant exactement la même forme que le tenseur d'identifiants d'entrée, remplis de 0 et de 1 : -- 1 indique que les *tokens* correspondants doivent être analysés -- 0 indique que les *tokens* correspondants ne doivent pas être analysés (c'est-à-dire qu'ils doivent être ignorés par les couches d'attention du modèle). - -Complétons l'exemple précédent avec un masque d'attention : - - -{#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} - -Nous obtenons maintenant les mêmes logits pour la deuxième phrase du batch. - -Remarquez comment la dernière valeur de la deuxième séquence est un identifiant de *padding* valant 0 dans le masque d'attention. - - - - -✏️ **Essayez !** Appliquez la tokenisation manuellement sur les deux phrases utilisées dans la section 2 (« I've been waiting for a HuggingFace course my whole life. » et « I hate this so much! »). Passez-les dans le modèle et vérifiez que vous obtenez les mêmes logits que dans la section 2. Ensuite regroupez-les en utilisant le jeton de *padding* et créez le masque d'attention approprié. Vérifiez que vous obtenez les mêmes résultats qu’en passant par le modèle ! - - - - -## Séquences plus longues - -Les *transformers* acceptent en entrée que des séquences d’une longueur limitée. La plupart des modèles traitent des séquences allant jusqu'à 512 ou 1024 *tokens* et plantent lorsqu'on leur demande de traiter des séquences plus longues. Il existe deux solutions à ce problème : - -- utiliser un modèle avec une longueur de séquence supportée plus longue, -- tronquer les séquences. - -Certains modèles sont spécialisés dans le traitement de très longues séquences comme par exemple le [Longformer](https://huggingface.co/transformers/model_doc/longformer.html) ou le [LED](https://huggingface.co/transformers/model_doc/led.html). Si vous travaillez sur une tâche qui nécessite de très longues séquences, nous vous recommandons de jeter un coup d'œil à ces modèles. - -Sinon, nous vous recommandons de tronquer vos séquences en spécifiant le paramètre `max_sequence_length` : - - -```py -sequence = sequence[:max_sequence_length] -``` + + +# Manipulation de plusieurs séquences + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + +{:else} + +{/if} + +Dans la section précédente, nous avons exploré le cas d'utilisation le plus simple : faire une inférence sur une seule séquence de petite longueur. Cependant, certaines questions émergent déjà : + +- comment gérer de plusieurs séquences ? +- comment gérer de plusieurs séquences *de longueurs différentes* ? +- les indices du vocabulaire sont-ils les seules entrées qui permettent à un modèle de bien fonctionner ? +- existe-t-il une séquence trop longue ? + +Voyons quels types de problèmes ces questions posent et comment nous pouvons les résoudre en utilisant l'API 🤗 *Transformers*. + +## Les modèles attendent un batch d'entrées + +Dans l'exercice précédent, vous avez vu comment les séquences sont traduites en listes de nombres. +Convertissons cette liste de nombres en un tenseur et envoyons-le au modèle : + +{#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." +# J'ai attendu un cours d’HuggingFace toute ma vie. + +tokens = tokenizer.tokenize(sequence) +ids = tokenizer.convert_tokens_to_ids(tokens) +input_ids = torch.tensor(ids) +# Cette ligne va échouer. +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." +# J'ai attendu un cours d’HuggingFace toute ma vie. + +tokens = tokenizer.tokenize(sequence) +ids = tokenizer.convert_tokens_to_ids(tokens) +input_ids = tf.constant(ids) +# This line will fail. +model(input_ids) +``` + +```py out +InvalidArgumentError: Input to reshape is a tensor with 14 values, but the requested shape has 196 [Op:Reshape] +``` +{/if} + +Pourquoi cela a échoué ? Nous avons suivi les étapes du pipeline de la section 2. + +Le problème est que nous avons envoyé une seule séquence au modèle, alors que les modèles de l’API 🤗 *Transformers* attendent plusieurs phrases par défaut. Ici, nous avons essayé de faire ce que le *tokenizer* fait en coulisses lorsque nous l'avons appliqué à une `séquence`. Cependant si vous regardez de près, vous verrez qu'il n'a pas seulement converti la liste des identifiants d'entrée en un tenseur mais aussi ajouté une dimension par-dessus : + + +{#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} + +Essayons à nouveau en ajoutant une nouvelle dimension : + +{#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." +# J'ai attendu un cours d’HuggingFace toute ma vie. + + +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." +# J'ai attendu un cours d’HuggingFace toute ma vie. + + +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} + +Nous affichons les identifiants d'entrée ainsi que les logits résultants. Voici la sortie : + +{#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} + +Le « *batching* » est l'acte d'envoyer plusieurs phrases à travers le modèle, toutes en même temps. Si vous n'avez qu'une seule phrase, vous pouvez simplement construire un batch avec une seule séquence : + +``` +batched_ids = [ids, ids] +``` + +Il s'agit d'un batch de deux séquences identiques ! + + + +✏️ **Essayez !** Convertissez cette liste `batched_ids` en un tenseur et passez-la dans votre modèle. Vérifiez que vous obtenez les mêmes logits que précédemment (mais deux fois) ! + + + +Utiliser des *batchs* permet au modèle de fonctionner lorsque vous lui donnez plusieurs séquences. Utiliser plusieurs séquences est aussi simple que de construire un batch avec une seule séquence. Il y a cependant un deuxième problème. Lorsque vous essayez de regrouper deux phrases (ou plus), elles peuvent être de longueurs différentes. Si vous avez déjà travaillé avec des tenseurs, vous savez qu'ils doivent être de forme rectangulaire. Vous ne pourrez donc pas convertir directement la liste des identifiants d'entrée en un tenseur. Pour contourner ce problème, nous avons l'habitude de *rembourrer*/*remplir* (le *padding* en anglais) les entrées. + +## Padding des entrées + +La liste de listes suivante ne peut pas être convertie en un tenseur : + + +```py no-format +batched_ids = [ + [200, 200, 200], + [200, 200] +] +``` + +Afin de contourner ce problème, nous utilisons le *padding* pour que nos tenseurs aient une forme rectangulaire. Le *padding* permet de s'assurer que toutes nos phrases ont la même longueur en ajoutant un mot spécial appelé *padding token* aux phrases ayant moins de valeurs. Par exemple, si vous avez 10 phrases de 10 mots et 1 phrase de 20 mots, le *padding* fait en sorte que toutes les phrases aient 20 mots. Dans notre exemple, le tenseur résultant ressemble à ceci : + +```py no-format +padding_id = 100 + +batched_ids = [ + [200, 200, 200], + [200, 200, padding_id], +] +``` + +L'identifiant du jeton de *padding* peut être trouvé dans `tokenizer.pad_token_id`. Utilisons-le et envoyons nos deux phrases à travers le modèle premièrement individuellement puis en étant mises dans un même batch : + +{#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} + +Il y a quelque chose qui ne va pas avec les logits de notre prédiction avec les séquences mises dans un même batch. La deuxième ligne devrait être la même que les logits pour la deuxième phrase, mais nous avons des valeurs complètement différentes ! + +C'est parce que dans un *transformer* les couches d’attention *contextualisent* chaque *token*. Celles-ci prennent en compte les *tokens* de *padding* puisqu'elles analysent tous les *tokens* d'une séquence. Pour obtenir le même résultat lorsque l'on passe dans notre modèle des phrases individuelles de différentes longueurs ou un batch composé de mêmes phrases avec *padding*, nous devons dire à ces couches d'attention d'ignorer les jetons de *padding*. Ceci est fait en utilisant un masque d'attention. + + +## Masques d'attention + +Les masques d'attention sont des tenseurs ayant exactement la même forme que le tenseur d'identifiants d'entrée, remplis de 0 et de 1 : +- 1 indique que les *tokens* correspondants doivent être analysés +- 0 indique que les *tokens* correspondants ne doivent pas être analysés (c'est-à-dire qu'ils doivent être ignorés par les couches d'attention du modèle). + +Complétons l'exemple précédent avec un masque d'attention : + + +{#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} + +Nous obtenons maintenant les mêmes logits pour la deuxième phrase du batch. + +Remarquez comment la dernière valeur de la deuxième séquence est un identifiant de *padding* valant 0 dans le masque d'attention. + + + + +✏️ **Essayez !** Appliquez la tokenisation manuellement sur les deux phrases utilisées dans la section 2 (« I've been waiting for a HuggingFace course my whole life. » et « I hate this so much! »). Passez-les dans le modèle et vérifiez que vous obtenez les mêmes logits que dans la section 2. Ensuite regroupez-les en utilisant le jeton de *padding* et créez le masque d'attention approprié. Vérifiez que vous obtenez les mêmes résultats qu’en passant par le modèle ! + + + + +## Séquences plus longues + +Les *transformers* acceptent en entrée que des séquences d’une longueur limitée. La plupart des modèles traitent des séquences allant jusqu'à 512 ou 1024 *tokens* et plantent lorsqu'on leur demande de traiter des séquences plus longues. Il existe deux solutions à ce problème : + +- utiliser un modèle avec une longueur de séquence supportée plus longue, +- tronquer les séquences. + +Certains modèles sont spécialisés dans le traitement de très longues séquences comme par exemple le [Longformer](https://huggingface.co/transformers/model_doc/longformer.html) ou le [LED](https://huggingface.co/transformers/model_doc/led.html). Si vous travaillez sur une tâche qui nécessite de très longues séquences, nous vous recommandons de jeter un coup d'œil à ces modèles. + +Sinon, nous vous recommandons de tronquer vos séquences en spécifiant le paramètre `max_sequence_length` : + + +```py +sequence = sequence[:max_sequence_length] +``` diff --git a/chapters/fr/chapter2/6.mdx b/chapters/fr/chapter2/6.mdx index 9602b2a96..4752f7bf0 100644 --- a/chapters/fr/chapter2/6.mdx +++ b/chapters/fr/chapter2/6.mdx @@ -1,188 +1,188 @@ - - -# Tout assembler - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -Dans les dernières sections, nous avons fait de notre mieux pour effectuer la plupart du travail manuellement. Nous avons exploré le fonctionnement des *tokenizers* et examiné la tokenisation, la conversion en identifiants d'entrée, le *padding*, la troncature et les masques d'attention. - -Cependant, comme nous l'avons vu dans la section 2, l'API 🤗 *Transformers* peut gérer tout cela pour nous via une fonction dans laquelle nous allons nous plonger ici. Lorsque vous appelez votre `tokenizer` directement sur la phrase, vous récupérez des entrées qui sont prêtes à être passées dans votre modèle : - - -```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." -# J'ai attendu un cours d’HuggingFace toute ma vie. - -model_inputs = tokenizer(sequence) -``` - -Ici, la variable `model_inputs` contient tout ce qui est nécessaire au bon fonctionnement d'un modèle. Pour DistilBERT, cela inclut les identifiants d'entrée ainsi que le masque d'attention. D'autres modèles qui acceptent des entrées supplémentaires sont également fournis par l'objet `tokenizer`. - -Comme nous allons le voir dans les quelques exemples ci-dessous, cette méthode est très puissante. Premièrement, elle peut tokeniser une seule séquence : - - -```py -sequence = "I've been waiting for a HuggingFace course my whole life." -# J'ai attendu un cours d’HuggingFace toute ma vie. - - -model_inputs = tokenizer(sequence) -``` - -Elle gère également plusieurs séquences à la fois, sans modification de l'API : - - -```py -sequences = [ - "I've been waiting for a HuggingFace course my whole life.", - "So have I!", -] # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! » - -model_inputs = tokenizer(sequences) -``` - -Il est possible de faire du *padding* selon plusieurs objectifs : - -```py -# Remplit les séquences jusqu'à la longueur maximale de la séquence -model_inputs = tokenizer(sequences, padding="longest") - -# Remplit les séquences jusqu'à la longueur maximale du modèle (512 pour BERT ou DistilBERT) -model_inputs = tokenizer(sequences, padding="max_length") - -# Remplit les séquences jusqu'à la longueur maximale spécifiée -model_inputs = tokenizer(sequences, padding="max_length", max_length=8) -``` - -La fonction peut également tronquer les séquences : - -```py -sequences = [ - "I've been waiting for a HuggingFace course my whole life.", - "So have I!", -] # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! » - -# Tronque les séquences qui sont plus longues que la longueur maximale du modèle -# (512 pour BERT ou DistilBERT) -model_inputs = tokenizer(sequences, truncation=True) - -# Tronque les séquences qui sont plus longues que la longueur maximale spécifiée -model_inputs = tokenizer(sequences, max_length=8, truncation=True) -``` - -L'objet `tokenizer` peut gérer la conversion en des tenseurs de *frameworks* spécifiques. Ils peuvent ensuite être directement envoyés au modèle. Par exemple, dans le code suivant, nous demandons au *tokenizer* de retourner des tenseurs PyTorch lorsque l’on spécifie `"pt"`, de retourner des tenseurs TensorFlow lorsque l’on spécifie `"tf"` et des tableaux NumPy lorsque l’on indique `"np"` : - -```py -sequences = [ - "I've been waiting for a HuggingFace course my whole life.", - "So have I!", -] # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! » - -# Retourne des tenseurs PyTorch -model_inputs = tokenizer(sequences, padding=True, return_tensors="pt") - -# Retourne des tenseurs TensorFlow -model_inputs = tokenizer(sequences, padding=True, return_tensors="tf") - -# Retourne des tableaux NumPy -model_inputs = tokenizer(sequences, padding=True, return_tensors="np") -``` - -## Jetons spéciaux - -Si nous jetons un coup d'œil aux identifiants d'entrée renvoyés par le *tokenizer*, nous verrons qu'ils sont un peu différents de ceux que nous avions précédemment : - - -```py -sequence = "I've been waiting for a HuggingFace course my whole life." -# « J'ai attendu un cours de HuggingFace toute ma vie. » - -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] -``` - -Un identifiant symbolique a été ajouté au début ainsi qu’un autre à la fin. Décodons les deux séquences d'identifiants ci-dessus pour voir de quoi il s'agit : - -```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." -``` - -Le *tokenizer* a ajouté le mot spécial `[CLS]` au début et le mot spécial `[SEP]` à la fin. C'est parce que le modèle a été pré-entraîné avec ces mots, donc pour obtenir les mêmes résultats pour l'inférence, nous devons également les ajouter. Notez que certains modèles n'ajoutent pas de mots spéciaux, ou en ajoutent des différents. Les modèles peuvent aussi ajouter ces mots spéciaux seulement au début, ou seulement à la fin. Dans tous les cas, le *tokenizer* sait lesquels sont attendus et s'en occupe pour vous. - -## Conclusion : du tokenizer au modèle - -Maintenant que nous avons vu toutes les étapes individuelles que l'objet `tokenizer` utilise lorsqu'il est appliqué sur des textes, voyons une dernière fois comment il peut gérer plusieurs séquences (*padding*), de très longues séquences (*troncation*) et plusieurs types de tenseurs avec son API principale : - - -{#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!", -] # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! » - - -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!", -] # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! » - -tokens = tokenizer(sequences, padding=True, truncation=True, return_tensors="tf") -output = model(**tokens) -``` -{/if} + + +# Tout assembler + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Dans les dernières sections, nous avons fait de notre mieux pour effectuer la plupart du travail manuellement. Nous avons exploré le fonctionnement des *tokenizers* et examiné la tokenisation, la conversion en identifiants d'entrée, le *padding*, la troncature et les masques d'attention. + +Cependant, comme nous l'avons vu dans la section 2, l'API 🤗 *Transformers* peut gérer tout cela pour nous via une fonction dans laquelle nous allons nous plonger ici. Lorsque vous appelez votre `tokenizer` directement sur la phrase, vous récupérez des entrées qui sont prêtes à être passées dans votre modèle : + + +```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." +# J'ai attendu un cours d’HuggingFace toute ma vie. + +model_inputs = tokenizer(sequence) +``` + +Ici, la variable `model_inputs` contient tout ce qui est nécessaire au bon fonctionnement d'un modèle. Pour DistilBERT, cela inclut les identifiants d'entrée ainsi que le masque d'attention. D'autres modèles qui acceptent des entrées supplémentaires sont également fournis par l'objet `tokenizer`. + +Comme nous allons le voir dans les quelques exemples ci-dessous, cette méthode est très puissante. Premièrement, elle peut tokeniser une seule séquence : + + +```py +sequence = "I've been waiting for a HuggingFace course my whole life." +# J'ai attendu un cours d’HuggingFace toute ma vie. + + +model_inputs = tokenizer(sequence) +``` + +Elle gère également plusieurs séquences à la fois, sans modification de l'API : + + +```py +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "So have I!", +] # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! » + +model_inputs = tokenizer(sequences) +``` + +Il est possible de faire du *padding* selon plusieurs objectifs : + +```py +# Remplit les séquences jusqu'à la longueur maximale de la séquence +model_inputs = tokenizer(sequences, padding="longest") + +# Remplit les séquences jusqu'à la longueur maximale du modèle (512 pour BERT ou DistilBERT) +model_inputs = tokenizer(sequences, padding="max_length") + +# Remplit les séquences jusqu'à la longueur maximale spécifiée +model_inputs = tokenizer(sequences, padding="max_length", max_length=8) +``` + +La fonction peut également tronquer les séquences : + +```py +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "So have I!", +] # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! » + +# Tronque les séquences qui sont plus longues que la longueur maximale du modèle +# (512 pour BERT ou DistilBERT) +model_inputs = tokenizer(sequences, truncation=True) + +# Tronque les séquences qui sont plus longues que la longueur maximale spécifiée +model_inputs = tokenizer(sequences, max_length=8, truncation=True) +``` + +L'objet `tokenizer` peut gérer la conversion en des tenseurs de *frameworks* spécifiques. Ils peuvent ensuite être directement envoyés au modèle. Par exemple, dans le code suivant, nous demandons au *tokenizer* de retourner des tenseurs PyTorch lorsque l’on spécifie `"pt"`, de retourner des tenseurs TensorFlow lorsque l’on spécifie `"tf"` et des tableaux NumPy lorsque l’on indique `"np"` : + +```py +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "So have I!", +] # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! » + +# Retourne des tenseurs PyTorch +model_inputs = tokenizer(sequences, padding=True, return_tensors="pt") + +# Retourne des tenseurs TensorFlow +model_inputs = tokenizer(sequences, padding=True, return_tensors="tf") + +# Retourne des tableaux NumPy +model_inputs = tokenizer(sequences, padding=True, return_tensors="np") +``` + +## Jetons spéciaux + +Si nous jetons un coup d'œil aux identifiants d'entrée renvoyés par le *tokenizer*, nous verrons qu'ils sont un peu différents de ceux que nous avions précédemment : + + +```py +sequence = "I've been waiting for a HuggingFace course my whole life." +# « J'ai attendu un cours de HuggingFace toute ma vie. » + +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] +``` + +Un identifiant symbolique a été ajouté au début ainsi qu’un autre à la fin. Décodons les deux séquences d'identifiants ci-dessus pour voir de quoi il s'agit : + +```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." +``` + +Le *tokenizer* a ajouté le mot spécial `[CLS]` au début et le mot spécial `[SEP]` à la fin. C'est parce que le modèle a été pré-entraîné avec ces mots, donc pour obtenir les mêmes résultats pour l'inférence, nous devons également les ajouter. Notez que certains modèles n'ajoutent pas de mots spéciaux, ou en ajoutent des différents. Les modèles peuvent aussi ajouter ces mots spéciaux seulement au début, ou seulement à la fin. Dans tous les cas, le *tokenizer* sait lesquels sont attendus et s'en occupe pour vous. + +## Conclusion : du tokenizer au modèle + +Maintenant que nous avons vu toutes les étapes individuelles que l'objet `tokenizer` utilise lorsqu'il est appliqué sur des textes, voyons une dernière fois comment il peut gérer plusieurs séquences (*padding*), de très longues séquences (*troncation*) et plusieurs types de tenseurs avec son API principale : + + +{#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!", +] # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! » + + +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!", +] # « J'ai attendu un cours de HuggingFace toute ma vie. », « Moi aussi ! » + +tokens = tokenizer(sequences, padding=True, truncation=True, return_tensors="tf") +output = model(**tokens) +``` +{/if} diff --git a/chapters/fr/chapter2/7.mdx b/chapters/fr/chapter2/7.mdx index 2b6c11798..31ea012a5 100644 --- a/chapters/fr/chapter2/7.mdx +++ b/chapters/fr/chapter2/7.mdx @@ -1,12 +1,17 @@ -# Utilisation de base terminée ! - -Bravo à vous pour avoir suivi le cours jusqu'ici ! Pour récapituler, dans ce chapitre vous avez : -- appris les blocs de construction de base d'un *transformer*, -- appris ce qui constitue un pipeline de tokenisation, -- vu comment utiliser un *transformer* en pratique, -- appris comment tirer parti d'un *tokenizer* pour convertir du texte en tenseurs compréhensibles par le modèle, -- configurer ensemble un *tokenizer* et un modèle afin de passer du texte aux prédictions, -- appris les limites des identifiants d'entrée et ce que sont que les masques d'attention, -- joué avec des méthodes de *tokenizer* polyvalentes et configurables. - -À partir de maintenant, vous devriez être en mesure de naviguer librement dans la documentation 🤗 *Transformers*. Le vocabulaire vous semblera familier et vous avez vu les méthodes que vous utiliserez la plupart du temps. +# Utilisation de base terminée ! + + + +Bravo à vous pour avoir suivi le cours jusqu'ici ! Pour récapituler, dans ce chapitre vous avez : +- appris les blocs de construction de base d'un *transformer*, +- appris ce qui constitue un pipeline de tokenisation, +- vu comment utiliser un *transformer* en pratique, +- appris comment tirer parti d'un *tokenizer* pour convertir du texte en tenseurs compréhensibles par le modèle, +- configurer ensemble un *tokenizer* et un modèle afin de passer du texte aux prédictions, +- appris les limites des identifiants d'entrée et ce que sont que les masques d'attention, +- joué avec des méthodes de *tokenizer* polyvalentes et configurables. + +À partir de maintenant, vous devriez être en mesure de naviguer librement dans la documentation 🤗 *Transformers*. Le vocabulaire vous semblera familier et vous avez vu les méthodes que vous utiliserez la plupart du temps. diff --git a/chapters/fr/chapter2/8.mdx b/chapters/fr/chapter2/8.mdx index a3fa30228..9b55bd866 100644 --- a/chapters/fr/chapter2/8.mdx +++ b/chapters/fr/chapter2/8.mdx @@ -1,307 +1,312 @@ - - - - -# Quiz de fin de chapitre - -### 1. Quel est l'ordre du pipeline de modélisation du langage ? - - -tokenizer donne un sens à ces prédictions et les reconvertit en texte si nécessaire.", - explain: " Le modèle ne peut pas comprendre le texte ! Le tokenizer doit d'abord tokeniser le texte et le convertir en identifiants afin qu'il soit compréhensible par le modèle."}, - { - text: " Tout d'abord, le tokenizer, qui traite le texte et renvoie des identifiants. Puis le modèle traite ces identifiants et produit une prédiction, qui peut être du texte.", - explain: " La prédiction du modèle ne peut pas être du texte immédiatement. Le tokenizer doit être utilisé afin de reconvertir la prédiction en texte !"}, - { - text: " Le tokenizer traite le texte et renvoie des identifiants. Le modèle traite ces identifiants et produit une prédiction. Le tokenizer peut alors être utilisé à nouveau pour reconvertir ces prédictions en texte.", - explain: " Le tokenizer peut être utilisé à la fois pour la tokenisation et la dé-tokénisation.", - correct: true - } - ]} -/> - -### 2. Combien de dimensions le tenseur produit par le transformer de base possède-t-il et quelles sont-elles ? - - -transformers gèrent les batchs, même avec une seule séquence ce serait une taille de batch de 1 !" - }, - { - text: "3: la longueur de la séquence, la taille du batch et la taille cachée.", - explain: "", - correct: true - } - ]} -/> - -### 3. Lequel des éléments suivants est un exemple de tokenisation en sous-mots ? - - - -### 4. Qu'est-ce qu'une tête de modèle ? - -transformer de base qui redirige les tenseurs vers leurs couches correctes.", - explain: "Il n'y a pas de tel composant." - }, - { - text: "Également connu sous le nom de mécanisme d'auto-attention, il adapte la représentation d'un token en fonction des autres tokens de la séquence.", - explain: "La couche d'auto-attention contient des têtes d'attention mais ce ne sont pas des têtes d'adaptation." - }, - { - text: "Un composant supplémentaire, généralement constitué d'une ou plusieurs couches, pour convertir les prédictions du transformer en une sortie spécifique à la tâche.", - explain: "Les têtes d'adaptation, aussi appelées simplement têtes, se présentent sous différentes formes : têtes de modélisation du langage, têtes de réponse aux questions, têtes de classification des séquences, etc.", - correct: true - } - ]} -/> - -{#if fw === 'pt'} -### 5. Qu'est-ce qu'un AutoModel? - -checkpoints et modèles soient capables de gérer plusieurs langues, il n'existe pas d'outils intégrés pour la sélection automatique des checkpoints en fonction de la langue. Vous devez vous rendre sur le Hub des modèles pour trouver le meilleur checkpoint pour votre tâche !" - } - ]} -/> - -{:else} -### 5. What is an AutoModel? - -checkpoints et modèles soient capables de gérer plusieurs langues, il n'existe pas d'outils intégrés pour la sélection automatique des checkpoints en fonction de la langue. Vous devez vous rendre sur le Hub des modèles pour trouver le meilleur checkpoint pour votre tâche !" - } - ]} -/> - -{/if} - -### 6. Quelles sont les techniques à connaître lors de la mise en batch de séquences de longueurs différentes ? - - -padding", - explain: "Le padding est une façon correcte d'égaliser les séquences pour qu'elles tiennent dans une forme rectangulaire. Mais est-ce le seul moyen ?", - correct: true - }, - { - text: "Les masques d'attention ", - explain: "Les masques d'attention sont d'une importance capitale lorsqu'on manipule des séquences de longueurs différentes. Ce n'est cependant pas la seule technique à laquelle il faut faire attention.", - correct: true - } - ]} -/> - -### 7. Quel est l'intérêt d'appliquer une fonction SoftMax aux logits produits par un modèle de classification de séquences ? - - - -### 8. Autour de quelle méthode s'articule la majeure partie de l'API tokenizer ? - -encode, car elle peut encoder du texte en identifiants et des identifiants en prédictions.", - explain: "Bien que la méthode encode existe sur les tokenizer, elle n'existe pas sur les modèles." - }, - { - text: "Appeler directement l'objet tokenizer", - explain: "La méthode __call__ du tokenizer est une méthode très puissante qui peut traiter à peu près tout. C'est également la méthode utilisée pour récupérer les prédictions d'un modèle.", - correct: true - }, - { - text: "pad", - explain: "Le padding est très utile mais ce n'est qu'une partie de l'API tokenizer." - }, - { - text: "tokenize", - explain: "La méthode tokenize est est sans doute l'une des méthodes les plus utiles, mais elle ne constitue pas le cœur de l'API tokenizer." - } - ]} -/> - -### 9. Que contient la variable `result` dans cet exemple de code ? - -```py -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") -result = tokenizer.tokenize("Hello!") -``` - -token.", - explain: "Convertissez cela en identifiants, et donnez-les à un modèle !", - correct: true - }, - { - text: "Une liste d'identifiants", - explain: "C'est à cela que la méthode __call__ ou la méthode convert_tokens_to_ids sert !" - }, - { - text: "Une chaîne contenant tous les tokens", - explain: "Ce serait sous-optimal car le but est de diviser la chaîne de caractères en plusieurs éléments." - } - ]} -/> - -{#if fw === 'pt'} -### 10. Y a-t-il un problème avec le code suivant ? - - -```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) -``` - -tokenizer qui a été entraîné avec un checkpoint différent est rarement une bonne idée. Le modèle n'a pas été entraîné pour donner du sens à la sortie de ce tokenizer donc la sortie du modèle (s'il peut même fonctionner !) n'aura aucun sens." - }, - { - text: " Le tokenizer et le modèle doivent toujours provenir du même checkpoint.", - explain: "", - correct: true - }, - { - text: " C'est une bonne pratique de faire du padding et de troncage avec le tokenizer car chaque entrée est un batch.", - explain: "Il est vrai que chaque entrée de modèle doit être un batch. Cependant, tronquer ou compléter cette séquence n'aurait pas nécessairement de sens puisqu'il n'y en a qu'une seule. Il s'agit là de techniques permettant de mettre en batch une liste de phrases." - } - ]} -/> - -{:else} -### 10. Y a-t-il un problème avec le code suivant ? - -```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) -``` - -tokenizer qui a été entraîné avec un checkpoint différent est rarement une bonne idée. Le modèle n'a pas été entraîné pour donner du sens à la sortie de ce tokenizer donc la sortie du modèle (s'il peut même fonctionner !) n'aura aucun sens." - }, - { - text: " Le tokenizer et le modèle doivent toujours provenir du même checkpoint.", - explain: "", - correct: true - }, - { - text: " C'est une bonne pratique de faire du padding et de troncage avec le tokenizer car chaque entrée est un batch.", - explain: "Il est vrai que chaque entrée de modèle doit être un batch. Cependant, tronquer ou compléter cette séquence n'aurait pas nécessairement de sens puisqu'il n'y en a qu'une seule. Il s'agit là de techniques permettant de mettre en batch une liste de phrases." - } - ]} -/> - -{/if} + + + + +# Quiz de fin de chapitre + + + +### 1. Quel est l'ordre du pipeline de modélisation du langage ? + + +tokenizer donne un sens à ces prédictions et les reconvertit en texte si nécessaire.", + explain: " Le modèle ne peut pas comprendre le texte ! Le tokenizer doit d'abord tokeniser le texte et le convertir en identifiants afin qu'il soit compréhensible par le modèle."}, + { + text: " Tout d'abord, le tokenizer, qui traite le texte et renvoie des identifiants. Puis le modèle traite ces identifiants et produit une prédiction, qui peut être du texte.", + explain: " La prédiction du modèle ne peut pas être du texte immédiatement. Le tokenizer doit être utilisé afin de reconvertir la prédiction en texte !"}, + { + text: " Le tokenizer traite le texte et renvoie des identifiants. Le modèle traite ces identifiants et produit une prédiction. Le tokenizer peut alors être utilisé à nouveau pour reconvertir ces prédictions en texte.", + explain: " Le tokenizer peut être utilisé à la fois pour la tokenisation et la dé-tokénisation.", + correct: true + } + ]} +/> + +### 2. Combien de dimensions le tenseur produit par le transformer de base possède-t-il et quelles sont-elles ? + + +transformers gèrent les batchs, même avec une seule séquence ce serait une taille de batch de 1 !" + }, + { + text: "3: la longueur de la séquence, la taille du batch et la taille cachée.", + explain: "", + correct: true + } + ]} +/> + +### 3. Lequel des éléments suivants est un exemple de tokenisation en sous-mots ? + + + +### 4. Qu'est-ce qu'une tête de modèle ? + +transformer de base qui redirige les tenseurs vers leurs couches correctes.", + explain: "Il n'y a pas de tel composant." + }, + { + text: "Également connu sous le nom de mécanisme d'auto-attention, il adapte la représentation d'un token en fonction des autres tokens de la séquence.", + explain: "La couche d'auto-attention contient des têtes d'attention mais ce ne sont pas des têtes d'adaptation." + }, + { + text: "Un composant supplémentaire, généralement constitué d'une ou plusieurs couches, pour convertir les prédictions du transformer en une sortie spécifique à la tâche.", + explain: "Les têtes d'adaptation, aussi appelées simplement têtes, se présentent sous différentes formes : têtes de modélisation du langage, têtes de réponse aux questions, têtes de classification des séquences, etc.", + correct: true + } + ]} +/> + +{#if fw === 'pt'} +### 5. Qu'est-ce qu'un AutoModel? + +checkpoints et modèles soient capables de gérer plusieurs langues, il n'existe pas d'outils intégrés pour la sélection automatique des checkpoints en fonction de la langue. Vous devez vous rendre sur le Hub des modèles pour trouver le meilleur checkpoint pour votre tâche !" + } + ]} +/> + +{:else} +### 5. What is an AutoModel? + +checkpoints et modèles soient capables de gérer plusieurs langues, il n'existe pas d'outils intégrés pour la sélection automatique des checkpoints en fonction de la langue. Vous devez vous rendre sur le Hub des modèles pour trouver le meilleur checkpoint pour votre tâche !" + } + ]} +/> + +{/if} + +### 6. Quelles sont les techniques à connaître lors de la mise en batch de séquences de longueurs différentes ? + + +padding", + explain: "Le padding est une façon correcte d'égaliser les séquences pour qu'elles tiennent dans une forme rectangulaire. Mais est-ce le seul moyen ?", + correct: true + }, + { + text: "Les masques d'attention ", + explain: "Les masques d'attention sont d'une importance capitale lorsqu'on manipule des séquences de longueurs différentes. Ce n'est cependant pas la seule technique à laquelle il faut faire attention.", + correct: true + } + ]} +/> + +### 7. Quel est l'intérêt d'appliquer une fonction SoftMax aux logits produits par un modèle de classification de séquences ? + + + +### 8. Autour de quelle méthode s'articule la majeure partie de l'API tokenizer ? + +encode, car elle peut encoder du texte en identifiants et des identifiants en prédictions.", + explain: "Bien que la méthode encode existe sur les tokenizer, elle n'existe pas sur les modèles." + }, + { + text: "Appeler directement l'objet tokenizer", + explain: "La méthode __call__ du tokenizer est une méthode très puissante qui peut traiter à peu près tout. C'est également la méthode utilisée pour récupérer les prédictions d'un modèle.", + correct: true + }, + { + text: "pad", + explain: "Le padding est très utile mais ce n'est qu'une partie de l'API tokenizer." + }, + { + text: "tokenize", + explain: "La méthode tokenize est est sans doute l'une des méthodes les plus utiles, mais elle ne constitue pas le cœur de l'API tokenizer." + } + ]} +/> + +### 9. Que contient la variable `result` dans cet exemple de code ? + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +result = tokenizer.tokenize("Hello!") +``` + +token.", + explain: "Convertissez cela en identifiants, et donnez-les à un modèle !", + correct: true + }, + { + text: "Une liste d'identifiants", + explain: "C'est à cela que la méthode __call__ ou la méthode convert_tokens_to_ids sert !" + }, + { + text: "Une chaîne contenant tous les tokens", + explain: "Ce serait sous-optimal car le but est de diviser la chaîne de caractères en plusieurs éléments." + } + ]} +/> + +{#if fw === 'pt'} +### 10. Y a-t-il un problème avec le code suivant ? + + +```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) +``` + +tokenizer qui a été entraîné avec un checkpoint différent est rarement une bonne idée. Le modèle n'a pas été entraîné pour donner du sens à la sortie de ce tokenizer donc la sortie du modèle (s'il peut même fonctionner !) n'aura aucun sens." + }, + { + text: " Le tokenizer et le modèle doivent toujours provenir du même checkpoint.", + explain: "", + correct: true + }, + { + text: " C'est une bonne pratique de faire du padding et de troncage avec le tokenizer car chaque entrée est un batch.", + explain: "Il est vrai que chaque entrée de modèle doit être un batch. Cependant, tronquer ou compléter cette séquence n'aurait pas nécessairement de sens puisqu'il n'y en a qu'une seule. Il s'agit là de techniques permettant de mettre en batch une liste de phrases." + } + ]} +/> + +{:else} +### 10. Y a-t-il un problème avec le code suivant ? + +```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) +``` + +tokenizer qui a été entraîné avec un checkpoint différent est rarement une bonne idée. Le modèle n'a pas été entraîné pour donner du sens à la sortie de ce tokenizer donc la sortie du modèle (s'il peut même fonctionner !) n'aura aucun sens." + }, + { + text: " Le tokenizer et le modèle doivent toujours provenir du même checkpoint.", + explain: "", + correct: true + }, + { + text: " C'est une bonne pratique de faire du padding et de troncage avec le tokenizer car chaque entrée est un batch.", + explain: "Il est vrai que chaque entrée de modèle doit être un batch. Cependant, tronquer ou compléter cette séquence n'aurait pas nécessairement de sens puisqu'il n'y en a qu'une seule. Il s'agit là de techniques permettant de mettre en batch une liste de phrases." + } + ]} +/> + +{/if} diff --git a/chapters/fr/chapter3/1.mdx b/chapters/fr/chapter3/1.mdx index 3ce9c58f7..3b9d16505 100644 --- a/chapters/fr/chapter3/1.mdx +++ b/chapters/fr/chapter3/1.mdx @@ -1,23 +1,28 @@ - - -# Introduction - -Dans le [chapitre 2](/course/fr/chapter2) nous avons étudié comment utiliser les *tokenizers* et les modèles pré-entraînés pour faire des prédictions. -Mais que faire si vous souhaitez *finetuner* un modèle pré-entraîné pour votre propre jeu de données ? C'est le sujet de ce chapitre ! Vous allez apprendre à : - -{#if fw === 'pt'} -* savoir comment préparer un très grand jeu de données à partir du *Hub*, -* savoir comment utiliser l'API de haut niveau `Trainer` pour *finetuner* un modèle, -* savoir comment utiliser une boucle d'entraînement personnalisée, -* savoir comment tirer parti de la bibliothèque 🤗 *Accelerate* pour exécuter facilement cette boucle d'entraînement personnalisée sur n'importe quelle configuration distribuée. -configuration distribuée - -{:else} -* savoir comment préparer un très grand jeu de données à partir du *Hub*, -* savoir comment utiliser Keras pour *finetuner* un modèle, -* savoir comment utiliser Keras pour obtenir des prédictions, -* savoir comment utiliser des métriques personnalisées. - -{/if} - + + +# Introduction + + + +Dans le [chapitre 2](/course/fr/chapter2) nous avons étudié comment utiliser les *tokenizers* et les modèles pré-entraînés pour faire des prédictions. +Mais que faire si vous souhaitez *finetuner* un modèle pré-entraîné pour votre propre jeu de données ? C'est le sujet de ce chapitre ! Vous allez apprendre à : + +{#if fw === 'pt'} +* savoir comment préparer un très grand jeu de données à partir du *Hub*, +* savoir comment utiliser l'API de haut niveau `Trainer` pour *finetuner* un modèle, +* savoir comment utiliser une boucle d'entraînement personnalisée, +* savoir comment tirer parti de la bibliothèque 🤗 *Accelerate* pour exécuter facilement cette boucle d'entraînement personnalisée sur n'importe quelle configuration distribuée. +configuration distribuée + +{:else} +* savoir comment préparer un très grand jeu de données à partir du *Hub*, +* savoir comment utiliser Keras pour *finetuner* un modèle, +* savoir comment utiliser Keras pour obtenir des prédictions, +* savoir comment utiliser des métriques personnalisées. + +{/if} + Afin de télécharger vos *checkpoints* entraînés sur le *Hub* Hugging Face, vous aurez besoin d'un compte huggingface.co : [créer un compte](https://huggingface.co/join) \ No newline at end of file diff --git a/chapters/fr/chapter3/2.mdx b/chapters/fr/chapter3/2.mdx index 297c260f5..5a3186a74 100644 --- a/chapters/fr/chapter3/2.mdx +++ b/chapters/fr/chapter3/2.mdx @@ -1,385 +1,385 @@ - - -# Préparer les données - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -{#if fw === 'pt'} - -En continuant avec l'exemple du [chapitre précédent](/course/fr/chapter2), voici comment entraîner un classifieur de séquences sur un batch avec PyTorch : - -```python -import torch -from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification - -# Même chose que précédemment -checkpoint = "bert-base-uncased" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -model = AutoModelForSequenceClassification.from_pretrained(checkpoint) -sequences = [ - "I've been waiting for a HuggingFace course my whole life.", - # J'ai attendu un cours de HuggingFace toute ma vie. - "This course is amazing!", # Ce cours est incroyable ! -] -batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") - -# Ceci est nouveau -batch["labels"] = torch.tensor([1, 1]) - -optimizer = AdamW(model.parameters()) -loss = model(**batch).loss -loss.backward() -optimizer.step() -``` -{:else} - -En continuant avec l'exemple du [chapitre précédent](/course/fr/chapter2), voici comment entraîner un classifieur de séquences sur un batch avec TensorFlow : - -```python -import tensorflow as tf -import numpy as np -from transformers import AutoTokenizer, TFAutoModelForSequenceClassification - -# Même chose que précédemment -checkpoint = "bert-base-uncased" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) -sequences = [ - "I've been waiting for a HuggingFace course my whole life.", - # J'ai attendu un cours de HuggingFace toute ma vie. - "This course is amazing!", # Ce cours est incroyable ! -] -batch = dict(tokenizer(sequences, padding=True, truncation=True, return_tensors="tf")) - -# Ceci est nouveau -model.compile(optimizer="adam", loss="sparse_categorical_crossentropy") -labels = tf.convert_to_tensor([1, 1]) -model.train_on_batch(batch, labels) -``` -{/if} -Evidemment, entraîner un modèle avec seulement deux phrases ne va pas donner de bons résultats. Pour obtenir de meilleurs résultats, vous allez avoir à préparer un plus grand jeu de données. - -Dans cette section, nous allons utiliser comme exemple le jeu de données MRPC (*Microsoft Research Paraphrase Corpus*) présenté dans un [papier](https://www.aclweb.org/anthology/I05-5002.pdf) par William B. Dolan et Chris Brockett. Ce jeu de données contient 5801 paires de phrases avec un label indiquant si ces paires sont des paraphrases ou non (i.e. si elles ont la même signification). Nous l'avons choisi pour ce chapitre parce que c'est un petit jeu de données et cela rend donc simples les expériences d'entraînement sur ce jeu de données. - -### Charger un jeu de données depuis le Hub - -{#if fw === 'pt'} - -{:else} - -{/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. - -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 : - -```py -from datasets import load_dataset - -raw_datasets = load_dataset("glue", "mrpc") -raw_datasets -``` - -```python out -DatasetDict({ - train: Dataset({ - features: ['sentence1', 'sentence2', 'label', 'idx'], - num_rows: 3668 - }) - validation: Dataset({ - features: ['sentence1', 'sentence2', 'label', 'idx'], - num_rows: 408 - }) - test: Dataset({ - features: ['sentence1', 'sentence2', 'label', 'idx'], - num_rows: 1725 - }) -}) -``` - -Comme vous le voyez, on obtient un objet de type `DatasetDict` qui contient le jeu de données d'entraînement, celui de validation et celui de test. Chacun d'eux contient plusieurs colonnes (`sentence1`, `sentence2`, `label` et `idx`) et une variable nombre de lignes qui contient le nombre d'éléments dans chaque jeu de données (il y a donc 3.668 paires de phrases dans le jeu d'entraînement, 408 dans celui de validation et 1.725 dans celui de test). - -Cette commande télécharge et met en cache le jeu de données dans *~/.cache/huggingface/dataset*. Rappelez-vous que comme vu au chapitre 2, vous pouvez personnaliser votre dossier cache en modifiant la variable d'environnement `HF_HOME`. - -Nous pouvons accéder à chaque paire de phrase de notre objet `raw_datasets` par les indices, comme avec un dictionnaire : - -```py -raw_train_dataset = raw_datasets["train"] -raw_train_dataset[0] -``` - -```python out -{'idx': 0, - 'label': 1, - 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', - # Amrozi a accusé son frère, qu'il a appelé « le témoin », de déformer délibérément son témoignage. - 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} - # Se référant à lui uniquement comme « le témoin », Amrozi a accusé son frère de déformer délibérément son témoignage. -``` - -Nous pouvons voir que les étiquettes sont déjà des entiers, donc nous n'aurons pas à faire de prétraitement ici. Pour savoir quel entier correspond à quel label, nous pouvons inspecter les `features` de notre `raw_train_dataset`. Cela nous indiquera le type de chaque colonne : - -```py -raw_train_dataset.features -``` - -```python out -{'sentence1': Value(dtype='string', id=None), - 'sentence2': Value(dtype='string', id=None), - 'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None), - 'idx': Value(dtype='int32', id=None)} -``` - -En réalité, `label` est de type `ClassLabel` et la correspondance des entiers aux noms des labels est enregistrée le dossier *names*. `0` correspond à `not_equivalent` et `1` correspond à `equivalent`. - - - -✏️ **Essayez !** Regardez l'élément 15 de l'ensemble d'entraînement et l'élément 87 de l'ensemble de validation. Quelles sont leurs étiquettes ? - - -### Prétraitement d'un jeu de données - -{#if fw === 'pt'} - -{:else} - -{/if} - -Pour prétraiter le jeu de données, nous devons convertir le texte en chiffres compréhensibles par le modèle. Comme vous l'avez vu dans le [chapitre précédent](/course/fr/chapter2), cette conversion est effectuée par un *tokenizer*. Nous pouvons fournir au *tokenizer* une phrase ou une liste de phrases, de sorte que nous pouvons directement tokeniser toutes les premières phrases et toutes les secondes phrases de chaque paire comme ceci : - -```py -from transformers import AutoTokenizer - -checkpoint = "bert-base-uncased" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"]) -tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"]) -``` - -Cependant, nous ne pouvons pas simplement passer deux séquences au modèle et obtenir une prédiction pour savoir si les deux phrases sont des paraphrases ou non. Nous devons traiter les deux séquences comme une paire, et appliquer le prétraitement approprié. Heureusement, le *tokenizer* peut également prendre une paire de séquences et la préparer de la manière attendue par notre modèle BERT : - -```py -inputs = tokenizer( - "This is the first sentence.", "This is the second one." -) # "C'est la première phrase.", "C'est la deuxième." -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] -} -``` - -Nous avons discuté des clés `input_ids` et `attention_mask` dans le [chapitre 2](/course/fr/chapter2), mais nous avons laissé de côté les `token_type_ids`. Dans cet exemple, c'est ce qui indique au modèle quelle partie de l'entrée est la première phrase et quelle partie est la deuxième phrase. - - - -✏️ **Essayez !** Prenez l'élément 15 de l'ensemble d'entraînement et tokenisez les deux phrases séparément et par paire. Quelle est la différence entre les deux résultats ? - - - -Si on décode les IDs dans `input_ids` en mots : - -```py -tokenizer.convert_ids_to_tokens(inputs["input_ids"]) -``` - -nous aurons : - -```python out -['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] -``` - -Nous voyons donc que le modèle s'attend à ce que les entrées soient de la forme `[CLS] phrase1 [SEP] phrase2 [SEP]` lorsqu'il y a deux phrases. En alignant cela avec les `token_type_ids`, on obtient : - -```python out -['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] -[ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] -``` - -Comme vous pouvez le voir, les parties de l'entrée correspondant à `[CLS] sentence1 [SEP]` ont toutes un *token* de type ID de `0`, tandis que les autres parties, correspondant à `sentence2 [SEP]`, ont toutes un *token* de type ID de `1`. - -Notez que si vous choisissez un autre *checkpoint*, vous n'aurez pas nécessairement les `token_type_ids` dans vos entrées tokenisées (par exemple, ils ne sont pas retournés si vous utilisez un modèle DistilBERT). Ils ne sont retournés que lorsque le modèle sait quoi faire avec eux, parce qu'il les a vus pendant son pré-entraînement. - -Ici, BERT est pré-entraîné avec les *tokens* de type ID et en plus de l'objectif de modélisation du langage masqué dont nous avons abordé dans [chapitre 1](/course/fr/chapter1), il a un objectif supplémentaire appelé _prédiction de la phrase suivante_. Le but de cette tâche est de modéliser la relation entre des paires de phrases. - -Avec la prédiction de la phrase suivante, on fournit au modèle des paires de phrases (avec des *tokens* masqués de manière aléatoire) et on lui demande de prédire si la deuxième phrase suit la première. Pour rendre la tâche non triviale, la moitié du temps, les phrases se suivent dans le document d'origine dont elles ont été extraites, et l'autre moitié du temps, les deux phrases proviennent de deux documents différents. - -En général, vous n'avez pas besoin de vous inquiéter de savoir s'il y a ou non des `token_type_ids` dans vos entrées tokenisées : tant que vous utilisez le même *checkpoint* pour le *tokenizer* et le modèle, tout ira bien puisque le *tokenizer* sait quoi fournir à son modèle. - -Maintenant que nous avons vu comment notre *tokenizer* peut traiter une paire de phrases, nous pouvons l'utiliser pour tokeniser l'ensemble de notre jeu de données : comme dans le [chapitre précédent](/course/fr/chapter2), nous pouvons fournir au *tokenizer* une liste de paires de phrases en lui donnant la liste des premières phrases, puis la liste des secondes phrases. Ceci est également compatible avec les options de remplissage et de troncature que nous avons vues dans le [chapitre 2](/course/fr/chapter2). Voici donc une façon de prétraiter le jeu de données d'entraînement : - -```py -tokenized_dataset = tokenizer( - raw_datasets["train"]["sentence1"], - raw_datasets["train"]["sentence2"], - padding=True, - truncation=True, -) -``` - -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 : - -```py -def tokenize_function(example): - return tokenizer(example["sentence1"], example["sentence2"], truncation=True) -``` - -Cette fonction prend un dictionnaire (comme les éléments de notre jeu de données) et retourne un nouveau dictionnaire avec les clés `input_ids`, `attention_mask`, et `token_type_ids`. Notez que cela fonctionne également si le dictionnaire `example` contient plusieurs échantillons (chaque clé étant une liste de phrases) puisque le `tokenizer` travaille sur des listes de paires de phrases, comme vu précédemment. Cela nous permettra d'utiliser l'option `batched=True` dans notre appel à `map()`, ce qui accélérera grandement la tokénisation. Le `tokenizer` est soutenu par un *tokenizer* écrit en Rust à partir de la bibliothèque [🤗 *Tokenizers*](https://github.com/huggingface/tokenizers). Ce *tokenizer* peut être très rapide, mais seulement si on lui donne beaucoup d'entrées en même temps. - -Notez que nous avons laissé l'argument `padding` hors de notre fonction de *tokenizer* pour le moment. C'est parce que le *padding* de tous les échantillons à la longueur maximale n'est pas efficace : il est préférable de remplir les échantillons lorsque nous construisons un batch, car alors nous avons seulement besoin de remplir à la longueur maximale dans ce batch, et non la longueur maximale dans l'ensemble des données. Cela peut permettre de gagner beaucoup de temps et de puissance de traitement lorsque les entrées ont des longueurs très variables ! - -Voici comment nous appliquons la fonction de tokenization sur tous nos jeux de données en même temps. Nous utilisons `batched=True` dans notre appel à `map` pour que la fonction soit appliquée à plusieurs éléments de notre jeu de données en une fois, et non à chaque élément séparément. Cela permet un prétraitement plus rapide. - -```py -tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) -tokenized_datasets -``` - -La façon dont la bibliothèque 🤗 *Datasets* applique ce traitement consiste à ajouter de nouveaux champs aux jeux de données, un pour chaque clé du dictionnaire renvoyé par la fonction de prétraitement : - -```python out -DatasetDict({ - train: Dataset({ - features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], - num_rows: 3668 - }) - validation: Dataset({ - features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], - num_rows: 408 - }) - test: Dataset({ - features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], - num_rows: 1725 - }) -}) -``` - -Vous pouvez même utiliser le multitraitement lorsque vous appliquez votre fonction de prétraitement avec `map()` en passant un argument `num_proc`. Nous ne l'avons pas fait ici parce que la bibliothèque 🤗 *Tokenizers* utilise déjà plusieurs *threads* pour tokeniser nos échantillons plus rapidement, mais si vous n'utilisez pas un *tokenizer* rapide soutenu par cette bibliothèque, cela pourrait accélérer votre prétraitement. - -Notre `tokenize_function` retourne un dictionnaire avec les clés `input_ids`, `attention_mask`, et `token_type_ids`, donc ces trois champs sont ajoutés à toutes les divisions de notre jeu de données. Notez que nous aurions également pu modifier des champs existants si notre fonction de prétraitement avait retourné une nouvelle valeur pour une clé existante dans l'ensemble de données auquel nous avons appliqué `map()`. - -La dernière chose que nous devrons faire est de remplir tous les exemples à la longueur de l'élément le plus long lorsque nous regroupons les éléments, une technique que nous appelons le *padding dynamique*. - -### Padding dynamique - - - -{#if fw === 'pt'} -La fonction qui est responsable de l'assemblage des échantillons dans un batch est appelée *fonction de rassemblement*. C'est un argument que vous pouvez passer quand vous construisez un `DataLoader`, la valeur par défaut étant une fonction qui va juste convertir vos échantillons en tenseurs PyTorch et les concaténer (récursivement si vos éléments sont des listes, des *tuples* ou des dictionnaires). Cela ne sera pas possible dans notre cas puisque les entrées que nous avons ne seront pas toutes de la même taille. Nous avons délibérément reporté le *padding*, pour ne l'appliquer que si nécessaire sur chaque batch et éviter d'avoir des entrées trop longues avec beaucoup de remplissage. Cela accélère considérablement l'entraînement, mais notez que si vous vous entraînez sur un TPU, cela peut poser des problèmes. En effet, les TPU préfèrent les formes fixes, même si cela nécessite un *padding* supplémentaire. - -{:else} -La fonction qui est responsable de l'assemblage des échantillons dans un batch est appelée *fonction de rassemblement*. C'est un argument que vous pouvez passer quand vous construisez un `DataLoader`, la valeur par défaut étant une fonction qui va juste convertir vos échantillons en type tf.Tensor et les concaténer (récursivement si les éléments sont des listes, des *tuples* ou des dictionnaires). Cela ne sera pas possible dans notre cas puisque les entrées que nous avons ne seront pas toutes de la même taille. Nous avons délibérément reporté le *padding*, pour ne l'appliquer que si nécessaire sur chaque batch et éviter d'avoir des entrées trop longues avec beaucoup de remplissage. Cela accélère considérablement l'entraînement, mais notez que si vous vous entraînez sur un TPU, cela peut poser des problèmes. En effet, les TPU préfèrent les formes fixes, même si cela nécessite un *padding* supplémentaire. - -{/if} -Pour faire cela en pratique, nous devons définir une fonction de rassemblement qui appliquera la bonne quantité de *padding* aux éléments du jeu de données que nous voulons regrouper. Heureusement, la bibliothèque 🤗 *Transformers* nous fournit une telle fonction via `DataCollatorWithPadding`. Elle prend un *tokenizer* lorsque vous l'instanciez (pour savoir quel *token* de *padding* utiliser et si le modèle s'attend à ce que le *padding* soit à gauche ou à droite des entrées) et fera tout ce dont vous avez besoin : - -{#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} - -Pour tester notre nouveau jouet, prenons quelques éléments de notre jeu d'entraînement avec lesquels nous allons former un batch. Ici, on supprime les colonnes `idx`, `sentence1` et `sentence2` puisque nous n'en aurons pas besoin et qu'elles contiennent des *strings* (et nous ne pouvons pas créer des tenseurs avec des *strings*) et on regarde la longueur de chaque entrée du batch : - -```py -samples = tokenized_datasets["train"][:8] -samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]} -[len(x) for x in samples["input_ids"]] -``` - -```python out -[50, 59, 47, 67, 59, 50, 62, 32] -``` - -Sans surprise, nous obtenons des échantillons de longueur variable, de 32 à 67. Le *padding* dynamique signifie que les échantillons de ce batch doivent tous être rembourrés à une longueur de 67, la longueur maximale dans le batch. Sans le *padding* dynamique, tous les échantillons devraient être rembourrés à la longueur maximale du jeu de données entier, ou à la longueur maximale que le modèle peut accepter. Vérifions à nouveau que notre `data_collator` rembourre dynamiquement le batch correctement : - -```py -batch = data_collator(samples) -{k: v.shape for k, v in batch.items()} -``` - -{#if fw === 'tf'} - -```python out -{'attention_mask': TensorShape([8, 67]), - 'input_ids': TensorShape([8, 67]), - 'token_type_ids': TensorShape([8, 67]), - 'labels': TensorShape([8])} -``` - -{:else} - -```python out -{'attention_mask': torch.Size([8, 67]), - 'input_ids': torch.Size([8, 67]), - 'token_type_ids': torch.Size([8, 67]), - 'labels': torch.Size([8])} -``` - -C'est beau ! Maintenant que nous sommes passés du texte brut à des batchs que notre modèle peut traiter, nous sommes prêts à le *finetuner* ! - -{/if} - - - -✏️ **Essayez !** Reproduisez le prétraitement sur le jeu de données GLUE SST-2. C'est un peu différent puisqu'il est composé de phrases simples au lieu de paires, mais le reste de ce que nous avons fait devrait être identique. Pour un défi plus difficile, essayez d'écrire une fonction de prétraitement qui fonctionne sur toutes les tâches GLUE. - - - -{#if fw === 'tf'} - -Maintenant que nous disposons de notre jeu de données et d'un collecteur de données, nous devons les assembler. Nous pourrions charger manuellement des batchs et les assembler mais c'est beaucoup de travail et probablement pas très performant non plus. A la place, il existe une méthode simple qui offre une solution performante à ce problème : `to_tf_dataset()`. Cela va envelopper un `tf.data.Dataset` autour de votre jeu de données, avec une fonction de collation optionnelle. `tf.data.Dataset` est un format natif de TensorFlow que Keras peut utiliser pour `model.fit()`, donc cette seule méthode convertit immédiatement un *dataset* en un format prêt pour l'entraînement. Voyons cela en action avec notre jeu de données ! - -```py -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, -) -``` - -Et c'est tout ! Nous pouvons utiliser ces jeux de données dans le prochain cours, où l'entraînement sera agréablement simple après tout le dur travail de prétraitement des données. - -{/if} + + +# Préparer les données + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + +En continuant avec l'exemple du [chapitre précédent](/course/fr/chapter2), voici comment entraîner un classifieur de séquences sur un batch avec PyTorch : + +```python +import torch +from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification + +# Même chose que précédemment +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + # J'ai attendu un cours de HuggingFace toute ma vie. + "This course is amazing!", # Ce cours est incroyable ! +] +batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") + +# Ceci est nouveau +batch["labels"] = torch.tensor([1, 1]) + +optimizer = AdamW(model.parameters()) +loss = model(**batch).loss +loss.backward() +optimizer.step() +``` +{:else} + +En continuant avec l'exemple du [chapitre précédent](/course/fr/chapter2), voici comment entraîner un classifieur de séquences sur un batch avec TensorFlow : + +```python +import tensorflow as tf +import numpy as np +from transformers import AutoTokenizer, TFAutoModelForSequenceClassification + +# Même chose que précédemment +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + # J'ai attendu un cours de HuggingFace toute ma vie. + "This course is amazing!", # Ce cours est incroyable ! +] +batch = dict(tokenizer(sequences, padding=True, truncation=True, return_tensors="tf")) + +# Ceci est nouveau +model.compile(optimizer="adam", loss="sparse_categorical_crossentropy") +labels = tf.convert_to_tensor([1, 1]) +model.train_on_batch(batch, labels) +``` +{/if} +Evidemment, entraîner un modèle avec seulement deux phrases ne va pas donner de bons résultats. Pour obtenir de meilleurs résultats, vous allez avoir à préparer un plus grand jeu de données. + +Dans cette section, nous allons utiliser comme exemple le jeu de données MRPC (*Microsoft Research Paraphrase Corpus*) présenté dans un [papier](https://www.aclweb.org/anthology/I05-5002.pdf) par William B. Dolan et Chris Brockett. Ce jeu de données contient 5801 paires de phrases avec un label indiquant si ces paires sont des paraphrases ou non (i.e. si elles ont la même signification). Nous l'avons choisi pour ce chapitre parce que c'est un petit jeu de données et cela rend donc simples les expériences d'entraînement sur ce jeu de données. + +### Charger un jeu de données depuis le Hub + +{#if fw === 'pt'} + +{:else} + +{/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. + +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 : + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("glue", "mrpc") +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 408 + }) + test: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 1725 + }) +}) +``` + +Comme vous le voyez, on obtient un objet de type `DatasetDict` qui contient le jeu de données d'entraînement, celui de validation et celui de test. Chacun d'eux contient plusieurs colonnes (`sentence1`, `sentence2`, `label` et `idx`) et une variable nombre de lignes qui contient le nombre d'éléments dans chaque jeu de données (il y a donc 3.668 paires de phrases dans le jeu d'entraînement, 408 dans celui de validation et 1.725 dans celui de test). + +Cette commande télécharge et met en cache le jeu de données dans *~/.cache/huggingface/dataset*. Rappelez-vous que comme vu au chapitre 2, vous pouvez personnaliser votre dossier cache en modifiant la variable d'environnement `HF_HOME`. + +Nous pouvons accéder à chaque paire de phrase de notre objet `raw_datasets` par les indices, comme avec un dictionnaire : + +```py +raw_train_dataset = raw_datasets["train"] +raw_train_dataset[0] +``` + +```python out +{'idx': 0, + 'label': 1, + 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', + # Amrozi a accusé son frère, qu'il a appelé « le témoin », de déformer délibérément son témoignage. + 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} + # Se référant à lui uniquement comme « le témoin », Amrozi a accusé son frère de déformer délibérément son témoignage. +``` + +Nous pouvons voir que les étiquettes sont déjà des entiers, donc nous n'aurons pas à faire de prétraitement ici. Pour savoir quel entier correspond à quel label, nous pouvons inspecter les `features` de notre `raw_train_dataset`. Cela nous indiquera le type de chaque colonne : + +```py +raw_train_dataset.features +``` + +```python out +{'sentence1': Value(dtype='string', id=None), + 'sentence2': Value(dtype='string', id=None), + 'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None), + 'idx': Value(dtype='int32', id=None)} +``` + +En réalité, `label` est de type `ClassLabel` et la correspondance des entiers aux noms des labels est enregistrée le dossier *names*. `0` correspond à `not_equivalent` et `1` correspond à `equivalent`. + + + +✏️ **Essayez !** Regardez l'élément 15 de l'ensemble d'entraînement et l'élément 87 de l'ensemble de validation. Quelles sont leurs étiquettes ? + + +### Prétraitement d'un jeu de données + +{#if fw === 'pt'} + +{:else} + +{/if} + +Pour prétraiter le jeu de données, nous devons convertir le texte en chiffres compréhensibles par le modèle. Comme vous l'avez vu dans le [chapitre précédent](/course/fr/chapter2), cette conversion est effectuée par un *tokenizer*. Nous pouvons fournir au *tokenizer* une phrase ou une liste de phrases, de sorte que nous pouvons directement tokeniser toutes les premières phrases et toutes les secondes phrases de chaque paire comme ceci : + +```py +from transformers import AutoTokenizer + +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"]) +tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"]) +``` + +Cependant, nous ne pouvons pas simplement passer deux séquences au modèle et obtenir une prédiction pour savoir si les deux phrases sont des paraphrases ou non. Nous devons traiter les deux séquences comme une paire, et appliquer le prétraitement approprié. Heureusement, le *tokenizer* peut également prendre une paire de séquences et la préparer de la manière attendue par notre modèle BERT : + +```py +inputs = tokenizer( + "This is the first sentence.", "This is the second one." +) # "C'est la première phrase.", "C'est la deuxième." +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] +} +``` + +Nous avons discuté des clés `input_ids` et `attention_mask` dans le [chapitre 2](/course/fr/chapter2), mais nous avons laissé de côté les `token_type_ids`. Dans cet exemple, c'est ce qui indique au modèle quelle partie de l'entrée est la première phrase et quelle partie est la deuxième phrase. + + + +✏️ **Essayez !** Prenez l'élément 15 de l'ensemble d'entraînement et tokenisez les deux phrases séparément et par paire. Quelle est la différence entre les deux résultats ? + + + +Si on décode les IDs dans `input_ids` en mots : + +```py +tokenizer.convert_ids_to_tokens(inputs["input_ids"]) +``` + +nous aurons : + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +``` + +Nous voyons donc que le modèle s'attend à ce que les entrées soient de la forme `[CLS] phrase1 [SEP] phrase2 [SEP]` lorsqu'il y a deux phrases. En alignant cela avec les `token_type_ids`, on obtient : + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +[ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] +``` + +Comme vous pouvez le voir, les parties de l'entrée correspondant à `[CLS] sentence1 [SEP]` ont toutes un *token* de type ID de `0`, tandis que les autres parties, correspondant à `sentence2 [SEP]`, ont toutes un *token* de type ID de `1`. + +Notez que si vous choisissez un autre *checkpoint*, vous n'aurez pas nécessairement les `token_type_ids` dans vos entrées tokenisées (par exemple, ils ne sont pas retournés si vous utilisez un modèle DistilBERT). Ils ne sont retournés que lorsque le modèle sait quoi faire avec eux, parce qu'il les a vus pendant son pré-entraînement. + +Ici, BERT est pré-entraîné avec les *tokens* de type ID et en plus de l'objectif de modélisation du langage masqué dont nous avons abordé dans [chapitre 1](/course/fr/chapter1), il a un objectif supplémentaire appelé _prédiction de la phrase suivante_. Le but de cette tâche est de modéliser la relation entre des paires de phrases. + +Avec la prédiction de la phrase suivante, on fournit au modèle des paires de phrases (avec des *tokens* masqués de manière aléatoire) et on lui demande de prédire si la deuxième phrase suit la première. Pour rendre la tâche non triviale, la moitié du temps, les phrases se suivent dans le document d'origine dont elles ont été extraites, et l'autre moitié du temps, les deux phrases proviennent de deux documents différents. + +En général, vous n'avez pas besoin de vous inquiéter de savoir s'il y a ou non des `token_type_ids` dans vos entrées tokenisées : tant que vous utilisez le même *checkpoint* pour le *tokenizer* et le modèle, tout ira bien puisque le *tokenizer* sait quoi fournir à son modèle. + +Maintenant que nous avons vu comment notre *tokenizer* peut traiter une paire de phrases, nous pouvons l'utiliser pour tokeniser l'ensemble de notre jeu de données : comme dans le [chapitre précédent](/course/fr/chapter2), nous pouvons fournir au *tokenizer* une liste de paires de phrases en lui donnant la liste des premières phrases, puis la liste des secondes phrases. Ceci est également compatible avec les options de remplissage et de troncature que nous avons vues dans le [chapitre 2](/course/fr/chapter2). Voici donc une façon de prétraiter le jeu de données d'entraînement : + +```py +tokenized_dataset = tokenizer( + raw_datasets["train"]["sentence1"], + raw_datasets["train"]["sentence2"], + padding=True, + truncation=True, +) +``` + +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 : + +```py +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) +``` + +Cette fonction prend un dictionnaire (comme les éléments de notre jeu de données) et retourne un nouveau dictionnaire avec les clés `input_ids`, `attention_mask`, et `token_type_ids`. Notez que cela fonctionne également si le dictionnaire `example` contient plusieurs échantillons (chaque clé étant une liste de phrases) puisque le `tokenizer` travaille sur des listes de paires de phrases, comme vu précédemment. Cela nous permettra d'utiliser l'option `batched=True` dans notre appel à `map()`, ce qui accélérera grandement la tokénisation. Le `tokenizer` est soutenu par un *tokenizer* écrit en Rust à partir de la bibliothèque [🤗 *Tokenizers*](https://github.com/huggingface/tokenizers). Ce *tokenizer* peut être très rapide, mais seulement si on lui donne beaucoup d'entrées en même temps. + +Notez que nous avons laissé l'argument `padding` hors de notre fonction de *tokenizer* pour le moment. C'est parce que le *padding* de tous les échantillons à la longueur maximale n'est pas efficace : il est préférable de remplir les échantillons lorsque nous construisons un batch, car alors nous avons seulement besoin de remplir à la longueur maximale dans ce batch, et non la longueur maximale dans l'ensemble des données. Cela peut permettre de gagner beaucoup de temps et de puissance de traitement lorsque les entrées ont des longueurs très variables ! + +Voici comment nous appliquons la fonction de tokenization sur tous nos jeux de données en même temps. Nous utilisons `batched=True` dans notre appel à `map` pour que la fonction soit appliquée à plusieurs éléments de notre jeu de données en une fois, et non à chaque élément séparément. Cela permet un prétraitement plus rapide. + +```py +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) +tokenized_datasets +``` + +La façon dont la bibliothèque 🤗 *Datasets* applique ce traitement consiste à ajouter de nouveaux champs aux jeux de données, un pour chaque clé du dictionnaire renvoyé par la fonction de prétraitement : + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 408 + }) + test: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 1725 + }) +}) +``` + +Vous pouvez même utiliser le multitraitement lorsque vous appliquez votre fonction de prétraitement avec `map()` en passant un argument `num_proc`. Nous ne l'avons pas fait ici parce que la bibliothèque 🤗 *Tokenizers* utilise déjà plusieurs *threads* pour tokeniser nos échantillons plus rapidement, mais si vous n'utilisez pas un *tokenizer* rapide soutenu par cette bibliothèque, cela pourrait accélérer votre prétraitement. + +Notre `tokenize_function` retourne un dictionnaire avec les clés `input_ids`, `attention_mask`, et `token_type_ids`, donc ces trois champs sont ajoutés à toutes les divisions de notre jeu de données. Notez que nous aurions également pu modifier des champs existants si notre fonction de prétraitement avait retourné une nouvelle valeur pour une clé existante dans l'ensemble de données auquel nous avons appliqué `map()`. + +La dernière chose que nous devrons faire est de remplir tous les exemples à la longueur de l'élément le plus long lorsque nous regroupons les éléments, une technique que nous appelons le *padding dynamique*. + +### Padding dynamique + + + +{#if fw === 'pt'} +La fonction qui est responsable de l'assemblage des échantillons dans un batch est appelée *fonction de rassemblement*. C'est un argument que vous pouvez passer quand vous construisez un `DataLoader`, la valeur par défaut étant une fonction qui va juste convertir vos échantillons en tenseurs PyTorch et les concaténer (récursivement si vos éléments sont des listes, des *tuples* ou des dictionnaires). Cela ne sera pas possible dans notre cas puisque les entrées que nous avons ne seront pas toutes de la même taille. Nous avons délibérément reporté le *padding*, pour ne l'appliquer que si nécessaire sur chaque batch et éviter d'avoir des entrées trop longues avec beaucoup de remplissage. Cela accélère considérablement l'entraînement, mais notez que si vous vous entraînez sur un TPU, cela peut poser des problèmes. En effet, les TPU préfèrent les formes fixes, même si cela nécessite un *padding* supplémentaire. + +{:else} +La fonction qui est responsable de l'assemblage des échantillons dans un batch est appelée *fonction de rassemblement*. C'est un argument que vous pouvez passer quand vous construisez un `DataLoader`, la valeur par défaut étant une fonction qui va juste convertir vos échantillons en type tf.Tensor et les concaténer (récursivement si les éléments sont des listes, des *tuples* ou des dictionnaires). Cela ne sera pas possible dans notre cas puisque les entrées que nous avons ne seront pas toutes de la même taille. Nous avons délibérément reporté le *padding*, pour ne l'appliquer que si nécessaire sur chaque batch et éviter d'avoir des entrées trop longues avec beaucoup de remplissage. Cela accélère considérablement l'entraînement, mais notez que si vous vous entraînez sur un TPU, cela peut poser des problèmes. En effet, les TPU préfèrent les formes fixes, même si cela nécessite un *padding* supplémentaire. + +{/if} +Pour faire cela en pratique, nous devons définir une fonction de rassemblement qui appliquera la bonne quantité de *padding* aux éléments du jeu de données que nous voulons regrouper. Heureusement, la bibliothèque 🤗 *Transformers* nous fournit une telle fonction via `DataCollatorWithPadding`. Elle prend un *tokenizer* lorsque vous l'instanciez (pour savoir quel *token* de *padding* utiliser et si le modèle s'attend à ce que le *padding* soit à gauche ou à droite des entrées) et fera tout ce dont vous avez besoin : + +{#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} + +Pour tester notre nouveau jouet, prenons quelques éléments de notre jeu d'entraînement avec lesquels nous allons former un batch. Ici, on supprime les colonnes `idx`, `sentence1` et `sentence2` puisque nous n'en aurons pas besoin et qu'elles contiennent des *strings* (et nous ne pouvons pas créer des tenseurs avec des *strings*) et on regarde la longueur de chaque entrée du batch : + +```py +samples = tokenized_datasets["train"][:8] +samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]} +[len(x) for x in samples["input_ids"]] +``` + +```python out +[50, 59, 47, 67, 59, 50, 62, 32] +``` + +Sans surprise, nous obtenons des échantillons de longueur variable, de 32 à 67. Le *padding* dynamique signifie que les échantillons de ce batch doivent tous être rembourrés à une longueur de 67, la longueur maximale dans le batch. Sans le *padding* dynamique, tous les échantillons devraient être rembourrés à la longueur maximale du jeu de données entier, ou à la longueur maximale que le modèle peut accepter. Vérifions à nouveau que notre `data_collator` rembourre dynamiquement le batch correctement : + +```py +batch = data_collator(samples) +{k: v.shape for k, v in batch.items()} +``` + +{#if fw === 'tf'} + +```python out +{'attention_mask': TensorShape([8, 67]), + 'input_ids': TensorShape([8, 67]), + 'token_type_ids': TensorShape([8, 67]), + 'labels': TensorShape([8])} +``` + +{:else} + +```python out +{'attention_mask': torch.Size([8, 67]), + 'input_ids': torch.Size([8, 67]), + 'token_type_ids': torch.Size([8, 67]), + 'labels': torch.Size([8])} +``` + +C'est beau ! Maintenant que nous sommes passés du texte brut à des batchs que notre modèle peut traiter, nous sommes prêts à le *finetuner* ! + +{/if} + + + +✏️ **Essayez !** Reproduisez le prétraitement sur le jeu de données GLUE SST-2. C'est un peu différent puisqu'il est composé de phrases simples au lieu de paires, mais le reste de ce que nous avons fait devrait être identique. Pour un défi plus difficile, essayez d'écrire une fonction de prétraitement qui fonctionne sur toutes les tâches GLUE. + + + +{#if fw === 'tf'} + +Maintenant que nous disposons de notre jeu de données et d'un collecteur de données, nous devons les assembler. Nous pourrions charger manuellement des batchs et les assembler mais c'est beaucoup de travail et probablement pas très performant non plus. A la place, il existe une méthode simple qui offre une solution performante à ce problème : `to_tf_dataset()`. Cela va envelopper un `tf.data.Dataset` autour de votre jeu de données, avec une fonction de collation optionnelle. `tf.data.Dataset` est un format natif de TensorFlow que Keras peut utiliser pour `model.fit()`, donc cette seule méthode convertit immédiatement un *dataset* en un format prêt pour l'entraînement. Voyons cela en action avec notre jeu de données ! + +```py +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, +) +``` + +Et c'est tout ! Nous pouvons utiliser ces jeux de données dans le prochain cours, où l'entraînement sera agréablement simple après tout le dur travail de prétraitement des données. + +{/if} diff --git a/chapters/fr/chapter3/3.mdx b/chapters/fr/chapter3/3.mdx index 2624cdd5a..eba84e1b1 100644 --- a/chapters/fr/chapter3/3.mdx +++ b/chapters/fr/chapter3/3.mdx @@ -1,171 +1,171 @@ - - -# Finetuner un modèle avec l'API Trainer - - - - - -La bibliothèque 🤗 *Transformers* fournit une classe `Trainer` pour vous aider à *finetuner* n'importe lequel des modèles pré-entraînés qu'elle met à disposition sur votre jeu de données. Une fois que vous avez fait tout le travail de prétraitement des données dans la dernière section, il ne vous reste que quelques étapes pour définir le `Trainer`. La partie la plus difficile sera probablement de préparer l'environnement pour exécuter `Trainer.train()`, car elle fonctionnera très lentement sur un CPU. Si vous n'avez pas de GPU, vous pouvez avoir accès à des GPUs ou TPUs gratuits sur [Google Colab](https://colab.research.google.com/). - -Les exemples de code ci-dessous supposent que vous avez déjà exécuté les exemples de la section précédente. Voici un bref résumé de ce dont vous avez besoin : - -```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) -``` - -### Entraînement - -La première étape avant de pouvoir définir notre `Trainer` est de définir une classe `TrainingArguments` qui contiendra tous les hyperparamètres que le `Trainer` utilisera pour l'entraînement et l'évaluation. Le seul argument que vous devez fournir est un répertoire où le modèle entraîné sera sauvegardé, ainsi que les *checkpoints*. Pour tout le reste, vous pouvez laisser les valeurs par défaut, qui devraient fonctionner assez bien pour un *finetuning* de base. - -```py -from transformers import TrainingArguments - -training_args = TrainingArguments("test-trainer") -``` - - - -💡 Si vous voulez télécharger automatiquement votre modèle sur le *Hub* pendant l'entraînement, passez `push_to_hub=True` dans le `TrainingArguments`. Nous en apprendrons plus à ce sujet au [chapitre 4](/course/fr/chapter4/3). - - - -La deuxième étape consiste à définir notre modèle. Comme dans le [chapitre précédent](/course/fr/chapter2), nous utiliserons la classe `AutoModelForSequenceClassification`, avec deux labels : - -```py -from transformers import AutoModelForSequenceClassification - -model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) -``` - -Vous remarquerez que contrairement au [chapitre 2](/course/fr/chapter2), vous obtenez un message d'avertissement après l'instanciation de ce modèle pré-entraîné. C'est parce que BERT n'a pas été pré-entraîné à la classification de paires de phrases, donc la tête du modèle pré-entraîné a été supprimée et une nouvelle tête adaptée à la classification de séquences a été ajoutée à la place. Les messages d'avertissement indiquent que certains poids n'ont pas été utilisés (ceux correspondant à la tête de pré-entraînement abandonnée) et que d'autres ont été initialisés de manière aléatoire (ceux pour la nouvelle tête). Il conclut en vous encourageant à entraîner le modèle, ce qui est exactement ce que nous allons faire maintenant. - -Une fois que nous avons notre modèle, nous pouvons définir un `Trainer` en lui passant tous les objets construits jusqu'à présent : le `model`, le `training_args`, les jeux de données d'entraînement et de validation, notre `data_collator`, et notre `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, -) -``` - -Notez que lorsque vous passez le `tokenizer` comme nous l'avons fait ici, le `data_collator` par défaut utilisé par le `Trainer` sera un `DataCollatorWithPadding` comme défini précédemment. Ainsi, vous pouvez sauter la ligne `data_collator=data_collator` dans cet appel. Il était quand même important de vous montrer cette partie du traitement dans la section 2 ! - -Pour *finetuner* le modèle sur notre jeu de données, il suffit d'appeler la méthode `train()` de notre `Trainer` : - -```py -trainer.train() -``` - -Cela lancera le *finetuning* (qui devrait prendre quelques minutes sur un GPU) et indiquera la perte d'entraînement tous les 500 pas. Cependant, elle ne vous dira pas si votre modèle fonctionne bien (ou mal). Ceci est dû au fait que : - -1. nous n'avons pas dit au `Trainer` d'évaluer pendant l'entraînement en réglant `evaluation_strategy` à soit `"steps"` (évaluer chaque `eval_steps`) ou `"epoch"` (évaluer à la fin de chaque *epoch*). -2. nous n'avons pas fourni au `Trainer` une fonction `compute_metrics()` pour calculer une métrique pendant ladite évaluation (sinon l'évaluation aurait juste affiché la perte, qui n'est pas un nombre très intuitif). - - -### Evaluation - -Voyons comment nous pouvons construire une fonction `compute_metrics()` utile et l'utiliser la prochaine fois que nous entraînons. La fonction doit prendre un objet `EvalPrediction` (qui est un *tuple* nommé avec un champ `predictions` et un champ `label_ids`) et retournera un dictionnaire de chaînes de caractères vers des flottants (les chaînes de caractères étant les noms des métriques retournées, et les flottants leurs valeurs). Pour obtenir des prédictions de notre modèle, nous pouvons utiliser la commande `Trainer.predict()` : - -```py -predictions = trainer.predict(tokenized_datasets["validation"]) -print(predictions.predictions.shape, predictions.label_ids.shape) -``` - -```python out -(408, 2) (408,) -``` - -La sortie de la méthode `predict()` est un autre *tuple* nommé avec trois champs : `predictions`, `label_ids`, et `metrics`. Le champ `metrics` contiendra juste la perte sur le jeu de données passé, ainsi que quelques mesures de temps (combien de temps il a fallu pour prédire, au total et en moyenne). Une fois que nous aurons complété notre fonction `compute_metrics()` et que nous l'aurons passé au `Trainer`, ce champ contiendra également les métriques retournées par `compute_metrics()`. - -Comme vous pouvez le voir, `predictions` est un tableau bidimensionnel de forme 408 x 2 (408 étant le nombre d'éléments dans le jeu de données que nous avons utilisé). Ce sont les logits pour chaque élément du jeu de données que nous avons passé à `predict()` (comme vous l'avez vu dans le [chapitre précédent](/course/fr/chapter2), tous les *transformers* retournent des logits). Pour les transformer en prédictions que nous pouvons comparer à nos étiquettes, nous devons prendre l'indice avec la valeur maximale sur le second axe : - -```py -import numpy as np - -preds = np.argmax(predictions.predictions, axis=-1) -``` - -Nous pouvons maintenant comparer ces `preds` aux étiquettes. Pour construire notre fonction `compute_metric()`, nous allons nous appuyer sur les métriques de la bibliothèque 🤗 [*Evaluate*](https://github.com/huggingface/evaluate/). Nous pouvons charger les métriques associées au jeu de données MRPC aussi facilement que nous avons chargé le jeu de données, cette fois avec la fonction `evaluate.load()`. L'objet retourné possède une méthode `compute()` que nous pouvons utiliser pour effectuer le calcul de la métrique : - -```py -import evaluate - -metric = evaluate.load("glue", "mrpc") -metric.compute(predictions=preds, references=predictions.label_ids) -``` - -```python out -{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542} -``` - -Les résultats exacts que vous obtiendrez peuvent varier, car l'initialisation aléatoire de la tête du modèle peut modifier les métriques obtenues. Ici, nous pouvons voir que notre modèle a une précision de 85,78% sur l'ensemble de validation et un score F1 de 89,97. Ce sont les deux métriques utilisées pour évaluer les résultats sur le jeu de données MRPC pour le benchmark GLUE. Le tableau du papier de [BERT](https://arxiv.org/pdf/1810.04805.pdf) indique un score F1 de 88,9 pour le modèle de base. Il s'agissait du modèle `uncased` alors que nous utilisons actuellement le modèle `cased`, ce qui explique le meilleur résultat. - -En regroupant le tout, nous obtenons notre fonction `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) -``` - -Et pour le voir utilisé en action pour rapporter les métriques à la fin de chaque époque, voici comment nous définissons un nouveau `Trainer` avec cette fonction `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, -) -``` - -Notez que nous créons un nouveau `TrainingArguments` avec sa `evaluation_strategy` définie sur `"epoch"` et un nouveau modèle. Sinon, nous ne ferions que continuer l'entraînement du modèle que nous avons déjà entraîné. Pour lancer un nouveau cycle d'entraînement, nous exécutons : - -``` -trainer.train() -``` - -Cette fois, il indiquera la perte et les mesures de validation à la fin de chaque époque, en plus de la perte d'entraînement. Encore une fois, le score exact de précision/F1 que vous atteignez peut être un peu différent de ce que nous avons trouvé, en raison de l'initialisation aléatoire de la tête du modèle, mais il devrait être dans la même fourchette. - -Le `Trainer` fonctionnera sur plusieurs GPUs ou TPUs et fournit beaucoup d'options, comme l'entraînement en précision mixte (utilisez `fp16 = True` dans vos arguments d'entraînement). Nous passerons en revue tout ce qu'il supporte dans le chapitre 10. - -Ceci conclut l'introduction au *fine-tuning* en utilisant l'API `Trainer`. Un exemple d'utilisation pour les tâches de NLP les plus communes es donné dans le [chapitre 7](/course/fr/chapter7), mais pour l'instant regardons comment faire la même chose en PyTorch pur. - - - -✏️ **Essayez !** *Finetunez* un modèle sur le jeu de données GLUE SST-2, en utilisant le traitement des données que vous avez fait dans la section 2. - - + + +# Finetuner un modèle avec l'API Trainer + + + + + +La bibliothèque 🤗 *Transformers* fournit une classe `Trainer` pour vous aider à *finetuner* n'importe lequel des modèles pré-entraînés qu'elle met à disposition sur votre jeu de données. Une fois que vous avez fait tout le travail de prétraitement des données dans la dernière section, il ne vous reste que quelques étapes pour définir le `Trainer`. La partie la plus difficile sera probablement de préparer l'environnement pour exécuter `Trainer.train()`, car elle fonctionnera très lentement sur un CPU. Si vous n'avez pas de GPU, vous pouvez avoir accès à des GPUs ou TPUs gratuits sur [Google Colab](https://colab.research.google.com/). + +Les exemples de code ci-dessous supposent que vous avez déjà exécuté les exemples de la section précédente. Voici un bref résumé de ce dont vous avez besoin : + +```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) +``` + +### Entraînement + +La première étape avant de pouvoir définir notre `Trainer` est de définir une classe `TrainingArguments` qui contiendra tous les hyperparamètres que le `Trainer` utilisera pour l'entraînement et l'évaluation. Le seul argument que vous devez fournir est un répertoire où le modèle entraîné sera sauvegardé, ainsi que les *checkpoints*. Pour tout le reste, vous pouvez laisser les valeurs par défaut, qui devraient fonctionner assez bien pour un *finetuning* de base. + +```py +from transformers import TrainingArguments + +training_args = TrainingArguments("test-trainer") +``` + + + +💡 Si vous voulez télécharger automatiquement votre modèle sur le *Hub* pendant l'entraînement, passez `push_to_hub=True` dans le `TrainingArguments`. Nous en apprendrons plus à ce sujet au [chapitre 4](/course/fr/chapter4/3). + + + +La deuxième étape consiste à définir notre modèle. Comme dans le [chapitre précédent](/course/fr/chapter2), nous utiliserons la classe `AutoModelForSequenceClassification`, avec deux labels : + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +Vous remarquerez que contrairement au [chapitre 2](/course/fr/chapter2), vous obtenez un message d'avertissement après l'instanciation de ce modèle pré-entraîné. C'est parce que BERT n'a pas été pré-entraîné à la classification de paires de phrases, donc la tête du modèle pré-entraîné a été supprimée et une nouvelle tête adaptée à la classification de séquences a été ajoutée à la place. Les messages d'avertissement indiquent que certains poids n'ont pas été utilisés (ceux correspondant à la tête de pré-entraînement abandonnée) et que d'autres ont été initialisés de manière aléatoire (ceux pour la nouvelle tête). Il conclut en vous encourageant à entraîner le modèle, ce qui est exactement ce que nous allons faire maintenant. + +Une fois que nous avons notre modèle, nous pouvons définir un `Trainer` en lui passant tous les objets construits jusqu'à présent : le `model`, le `training_args`, les jeux de données d'entraînement et de validation, notre `data_collator`, et notre `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, +) +``` + +Notez que lorsque vous passez le `tokenizer` comme nous l'avons fait ici, le `data_collator` par défaut utilisé par le `Trainer` sera un `DataCollatorWithPadding` comme défini précédemment. Ainsi, vous pouvez sauter la ligne `data_collator=data_collator` dans cet appel. Il était quand même important de vous montrer cette partie du traitement dans la section 2 ! + +Pour *finetuner* le modèle sur notre jeu de données, il suffit d'appeler la méthode `train()` de notre `Trainer` : + +```py +trainer.train() +``` + +Cela lancera le *finetuning* (qui devrait prendre quelques minutes sur un GPU) et indiquera la perte d'entraînement tous les 500 pas. Cependant, elle ne vous dira pas si votre modèle fonctionne bien (ou mal). Ceci est dû au fait que : + +1. nous n'avons pas dit au `Trainer` d'évaluer pendant l'entraînement en réglant `evaluation_strategy` à soit `"steps"` (évaluer chaque `eval_steps`) ou `"epoch"` (évaluer à la fin de chaque *epoch*). +2. nous n'avons pas fourni au `Trainer` une fonction `compute_metrics()` pour calculer une métrique pendant ladite évaluation (sinon l'évaluation aurait juste affiché la perte, qui n'est pas un nombre très intuitif). + + +### Evaluation + +Voyons comment nous pouvons construire une fonction `compute_metrics()` utile et l'utiliser la prochaine fois que nous entraînons. La fonction doit prendre un objet `EvalPrediction` (qui est un *tuple* nommé avec un champ `predictions` et un champ `label_ids`) et retournera un dictionnaire de chaînes de caractères vers des flottants (les chaînes de caractères étant les noms des métriques retournées, et les flottants leurs valeurs). Pour obtenir des prédictions de notre modèle, nous pouvons utiliser la commande `Trainer.predict()` : + +```py +predictions = trainer.predict(tokenized_datasets["validation"]) +print(predictions.predictions.shape, predictions.label_ids.shape) +``` + +```python out +(408, 2) (408,) +``` + +La sortie de la méthode `predict()` est un autre *tuple* nommé avec trois champs : `predictions`, `label_ids`, et `metrics`. Le champ `metrics` contiendra juste la perte sur le jeu de données passé, ainsi que quelques mesures de temps (combien de temps il a fallu pour prédire, au total et en moyenne). Une fois que nous aurons complété notre fonction `compute_metrics()` et que nous l'aurons passé au `Trainer`, ce champ contiendra également les métriques retournées par `compute_metrics()`. + +Comme vous pouvez le voir, `predictions` est un tableau bidimensionnel de forme 408 x 2 (408 étant le nombre d'éléments dans le jeu de données que nous avons utilisé). Ce sont les logits pour chaque élément du jeu de données que nous avons passé à `predict()` (comme vous l'avez vu dans le [chapitre précédent](/course/fr/chapter2), tous les *transformers* retournent des logits). Pour les transformer en prédictions que nous pouvons comparer à nos étiquettes, nous devons prendre l'indice avec la valeur maximale sur le second axe : + +```py +import numpy as np + +preds = np.argmax(predictions.predictions, axis=-1) +``` + +Nous pouvons maintenant comparer ces `preds` aux étiquettes. Pour construire notre fonction `compute_metric()`, nous allons nous appuyer sur les métriques de la bibliothèque 🤗 [*Evaluate*](https://github.com/huggingface/evaluate/). Nous pouvons charger les métriques associées au jeu de données MRPC aussi facilement que nous avons chargé le jeu de données, cette fois avec la fonction `evaluate.load()`. L'objet retourné possède une méthode `compute()` que nous pouvons utiliser pour effectuer le calcul de la métrique : + +```py +import evaluate + +metric = evaluate.load("glue", "mrpc") +metric.compute(predictions=preds, references=predictions.label_ids) +``` + +```python out +{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542} +``` + +Les résultats exacts que vous obtiendrez peuvent varier, car l'initialisation aléatoire de la tête du modèle peut modifier les métriques obtenues. Ici, nous pouvons voir que notre modèle a une précision de 85,78% sur l'ensemble de validation et un score F1 de 89,97. Ce sont les deux métriques utilisées pour évaluer les résultats sur le jeu de données MRPC pour le benchmark GLUE. Le tableau du papier de [BERT](https://arxiv.org/pdf/1810.04805.pdf) indique un score F1 de 88,9 pour le modèle de base. Il s'agissait du modèle `uncased` alors que nous utilisons actuellement le modèle `cased`, ce qui explique le meilleur résultat. + +En regroupant le tout, nous obtenons notre fonction `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) +``` + +Et pour le voir utilisé en action pour rapporter les métriques à la fin de chaque époque, voici comment nous définissons un nouveau `Trainer` avec cette fonction `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, +) +``` + +Notez que nous créons un nouveau `TrainingArguments` avec sa `evaluation_strategy` définie sur `"epoch"` et un nouveau modèle. Sinon, nous ne ferions que continuer l'entraînement du modèle que nous avons déjà entraîné. Pour lancer un nouveau cycle d'entraînement, nous exécutons : + +``` +trainer.train() +``` + +Cette fois, il indiquera la perte et les mesures de validation à la fin de chaque époque, en plus de la perte d'entraînement. Encore une fois, le score exact de précision/F1 que vous atteignez peut être un peu différent de ce que nous avons trouvé, en raison de l'initialisation aléatoire de la tête du modèle, mais il devrait être dans la même fourchette. + +Le `Trainer` fonctionnera sur plusieurs GPUs ou TPUs et fournit beaucoup d'options, comme l'entraînement en précision mixte (utilisez `fp16 = True` dans vos arguments d'entraînement). Nous passerons en revue tout ce qu'il supporte dans le chapitre 10. + +Ceci conclut l'introduction au *fine-tuning* en utilisant l'API `Trainer`. Un exemple d'utilisation pour les tâches de NLP les plus communes es donné dans le [chapitre 7](/course/fr/chapter7), mais pour l'instant regardons comment faire la même chose en PyTorch pur. + + + +✏️ **Essayez !** *Finetunez* un modèle sur le jeu de données GLUE SST-2, en utilisant le traitement des données que vous avez fait dans la section 2. + + diff --git a/chapters/fr/chapter3/3_tf.mdx b/chapters/fr/chapter3/3_tf.mdx index 9a84d533d..bace781f0 100644 --- a/chapters/fr/chapter3/3_tf.mdx +++ b/chapters/fr/chapter3/3_tf.mdx @@ -1,190 +1,190 @@ - - -# Finetuner un modèle avec Keras - - - -Une fois que vous avez fait tout le travail de prétraitement des données dans la dernière section, il ne vous reste que quelques étapes pour entraîner le modèle. Notez, cependant, que la commande `model.fit()` s'exécutera très lentement sur un CPU. Si vous n'avez pas de GPU, vous pouvez avoir accès à des GPUs ou TPUs gratuits sur [Google Colab](https://colab.research.google.com/). - -Les exemples de code ci-dessous supposent que vous avez déjà exécuté les exemples de la section précédente. Voici un bref résumé de ce dont vous avez besoin : - -```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, -) -``` - -### Entraînement - -Les modèles TensorFlow importés depuis 🤗 *Transformers* sont déjà des modèles Keras. Voici une courte introduction à Keras. - - - -Cela signifie qu'une fois que nous disposons de nos données, très peu de travail est nécessaire pour commencer à entraîner sur celles-ci. - - - -Comme dans le [chapitre précédent](/course/fr/chapter2), nous allons utiliser la classe `TFAutoModelForSequenceClassification`, avec deux étiquettes : - -```py -from transformers import TFAutoModelForSequenceClassification - -model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) -``` - -Vous remarquerez que, contrairement au [chapitre 2](/course/fr/chapter2), vous obtenez un message d'avertissement après l'instanciation de ce modèle pré-entraîné. Ceci est dû au fait que BERT n'a pas été pré-entraîné à la classification de paires de phrases, donc la tête du modèle pré-entraîné a été supprimée et une nouvelle tête adaptée à la classification de séquences a été insérée à la place. Les messages d'avertissement indiquent que certains poids n'ont pas été utilisés (ceux correspondant à la tête de pré-entraînement abandonnée) et que d'autres ont été initialisés de manière aléatoire (ceux pour la nouvelle tête). Il conclut en vous encourageant à entraîner le modèle, ce qui est exactement ce que nous allons faire maintenant. - -Pour *finetuner* le modèle sur notre jeu de données, nous devons simplement `compiler()` notre modèle et ensuite passer nos données à la méthode `fit()`. Cela va démarrer le processus de *finetuning* (qui devrait prendre quelques minutes sur un GPU) et rapporter la perte d'entraînement au fur et à mesure, plus la perte de validation à la fin de chaque époque. - - - -Notez que les modèles 🤗 *Transformers* ont une capacité spéciale que la plupart des modèles Keras n'ont pas. Ils peuvent automatiquement utiliser une perte appropriée qu'ils calculent en interne. Ils utiliseront cette perte par défaut si vous ne définissez pas un argument de perte dans `compile()`. Notez que pour utiliser la perte interne, vous devrez passer vos labels comme faisant partie de l'entrée, et non pas comme un label séparé, ce qui est la façon normale d'utiliser les labels avec les modèles Keras. Vous verrez des exemples de cela dans la partie 2 du cours, où la définition de la fonction de perte correcte peut être délicate. Pour la classification des séquences, cependant, une fonction de perte standard de Keras fonctionne bien, et c'est donc ce que nous utiliserons ici. - - - -```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, -) -``` - - - -Notez un piège très commun ici. Vous *pouvez* simplement passer le nom de la perte comme une chaîne à Keras, mais par défaut Keras supposera que vous avez déjà appliqué une fonction softmax à vos sorties. Cependant, de nombreux modèles produisent les valeurs juste avant l'application de la softmax, que l'on appelle aussi les *logits*. Nous devons indiquer à la fonction de perte que c'est ce que fait notre modèle, et la seule façon de le faire est de l'appeler directement, plutôt que par son nom avec une chaîne. - - - - -### Améliorer les performances d'entraînement - - - -Si vous essayez le code ci-dessus, il fonctionne certainement, mais vous constaterez que la perte ne diminue que lentement ou sporadiquement. La cause principale est le *taux d'apprentissage*. Comme pour la perte, lorsque nous transmettons à Keras le nom d'un optimiseur sous forme de chaîne de caractères, Keras initialise cet optimiseur avec des valeurs par défaut pour tous les paramètres, y compris le taux d'apprentissage. Cependant, nous savons depuis longtemps que les *transformers* bénéficient d'un taux d'apprentissage beaucoup plus faible que celui par défaut d'Adam, qui est de 1e-3, également écrit comme 10 à la puissance -3, ou 0,001. 5e-5 (0,00005), qui est environ vingt fois inférieur, est un bien meilleur point de départ. - -En plus de réduire le taux d'apprentissage, nous avons une deuxième astuce dans notre manche : nous pouvons réduire lentement le taux d'apprentissage au cours de l'entraînement. Dans la littérature, on parle parfois de *décroissance* ou d'*annulation* du taux d'apprentissage.le taux d'apprentissage. Dans Keras, la meilleure façon de le faire est d'utiliser un *planificateur du taux d'apprentissage*. Un bon planificateur à utiliser est `PolynomialDecay`. Malgré son nom, avec les paramètres par défaut, il diminue simplement de façon linéaire le taux d'apprentissage de la valeur initiale à la valeur finale au cours de l'entraînement, ce qui est exactement ce que nous voulons. Afin d'utiliser correctement un planificateur, nous devons lui dire combien de temps l'entraînement va durer. Nous calculons cela comme `num_train_steps` ci-dessous. - -```py -from tensorflow.keras.optimizers.schedules import PolynomialDecay - -batch_size = 8 -num_epochs = 3 -# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans l'ensemble de données, divisé par la taille du batch puis multiplié -# par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un lot tf.data.Dataset -# et non le jeu de données original Hugging Face Dataset, donc son len() est déjà 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 bibliothèque 🤗 *Transformers* possède également une fonction `create_optimizer()` qui créera un optimiseur `AdamW` avec un taux d'apprentissage décroissant. Il s'agit d'un raccourci pratique que vous verrez en détail dans les prochaines sections du cours. - - - -Nous avons maintenant notre tout nouvel optimiseur et nous pouvons essayer de nous entraîner avec lui. Tout d'abord, rechargeons le modèle pour réinitialiser les modifications apportées aux poids lors de l'entraînement que nous venons d'effectuer, puis nous pouvons le compiler avec le nouvel optimiseur : - -```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"]) -``` - -Maintenant, on *fit* : - -```py -model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) -``` - - - -💡 Si vous voulez télécharger automatiquement votre modèle sur le *Hub* pendant l'entraînement, vous pouvez passer un `PushToHubCallback` dans la méthode `model.fit()`. Nous en apprendrons davantage à ce sujet au [chapitre 4](/course/fr/chapter4/3). - - - -### Prédictions du modèle - - - - -Entraîner et regarder la perte diminuer, c'est très bien, mais que faire si l'on veut réellement obtenir des résultats du modèle entraîné, soit pour calculer des métriques, soit pour utiliser le modèle en production ? Pour ce faire, nous pouvons simplement utiliser la méthode `predict()`. Ceci retournera les *logits* de la tête de sortie du modèle, un par classe. - -```py -preds = model.predict(tf_validation_dataset)["logits"] -``` - -Nous pouvons convertir ces logits en prédictions de classe du modèle en utilisant `argmax` pour trouver le logit le plus élevé, qui correspond à la classe la plus probable : - -```py -class_preds = np.argmax(preds, axis=1) -print(preds.shape, class_preds.shape) -``` - -```python out -(408, 2) (408,) -``` - -Maintenant, utilisons ces `preds` pour calculer des métriques ! Nous pouvons charger les métriques associées au jeu de données MRPC aussi facilement que nous avons chargé le jeu de données, cette fois avec la fonction `evaluate.load()`. L'objet retourné a une méthode `compute()` que nous pouvons utiliser pour faire le calcul de la métrique : - -```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} -``` - -Les résultats exacts que vous obtiendrez peuvent varier, car l'initialisation aléatoire de la tête du modèle peut modifier les métriques obtenues. Ici, nous pouvons voir que notre modèle a une précision de 85,78% sur l'ensemble de validation et un score F1 de 89,97. Ce sont les deux métriques utilisées pour évaluer les résultats sur le jeu de données MRPC pour le benchmark GLUE. Le tableau du papier de [BERT](https://arxiv.org/pdf/1810.04805.pdf) indique un score F1 de 88,9 pour le modèle de base. Il s'agissait du modèle `uncased` alors que nous utilisons actuellement le modèle `cased`, ce qui explique le meilleur résultat. - -Ceci conclut l'introduction à le *finetuning* en utilisant l'API Keras. Un exemple d'application de cette méthode aux tâches les plus courantes du traitement automatique des langues sera présenté au [chapitre 7](/course/fr/chapter7). Si vous souhaitez affiner vos connaissances de l'API Keras, essayez *finetuner* un modèle sur le jeu de données GLUE SST-2, en utilisant le traitement des données que vous avez effectué dans la section 2. + + +# Finetuner un modèle avec Keras + + + +Une fois que vous avez fait tout le travail de prétraitement des données dans la dernière section, il ne vous reste que quelques étapes pour entraîner le modèle. Notez, cependant, que la commande `model.fit()` s'exécutera très lentement sur un CPU. Si vous n'avez pas de GPU, vous pouvez avoir accès à des GPUs ou TPUs gratuits sur [Google Colab](https://colab.research.google.com/). + +Les exemples de code ci-dessous supposent que vous avez déjà exécuté les exemples de la section précédente. Voici un bref résumé de ce dont vous avez besoin : + +```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, +) +``` + +### Entraînement + +Les modèles TensorFlow importés depuis 🤗 *Transformers* sont déjà des modèles Keras. Voici une courte introduction à Keras. + + + +Cela signifie qu'une fois que nous disposons de nos données, très peu de travail est nécessaire pour commencer à entraîner sur celles-ci. + + + +Comme dans le [chapitre précédent](/course/fr/chapter2), nous allons utiliser la classe `TFAutoModelForSequenceClassification`, avec deux étiquettes : + +```py +from transformers import TFAutoModelForSequenceClassification + +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +Vous remarquerez que, contrairement au [chapitre 2](/course/fr/chapter2), vous obtenez un message d'avertissement après l'instanciation de ce modèle pré-entraîné. Ceci est dû au fait que BERT n'a pas été pré-entraîné à la classification de paires de phrases, donc la tête du modèle pré-entraîné a été supprimée et une nouvelle tête adaptée à la classification de séquences a été insérée à la place. Les messages d'avertissement indiquent que certains poids n'ont pas été utilisés (ceux correspondant à la tête de pré-entraînement abandonnée) et que d'autres ont été initialisés de manière aléatoire (ceux pour la nouvelle tête). Il conclut en vous encourageant à entraîner le modèle, ce qui est exactement ce que nous allons faire maintenant. + +Pour *finetuner* le modèle sur notre jeu de données, nous devons simplement `compiler()` notre modèle et ensuite passer nos données à la méthode `fit()`. Cela va démarrer le processus de *finetuning* (qui devrait prendre quelques minutes sur un GPU) et rapporter la perte d'entraînement au fur et à mesure, plus la perte de validation à la fin de chaque époque. + + + +Notez que les modèles 🤗 *Transformers* ont une capacité spéciale que la plupart des modèles Keras n'ont pas. Ils peuvent automatiquement utiliser une perte appropriée qu'ils calculent en interne. Ils utiliseront cette perte par défaut si vous ne définissez pas un argument de perte dans `compile()`. Notez que pour utiliser la perte interne, vous devrez passer vos labels comme faisant partie de l'entrée, et non pas comme un label séparé, ce qui est la façon normale d'utiliser les labels avec les modèles Keras. Vous verrez des exemples de cela dans la partie 2 du cours, où la définition de la fonction de perte correcte peut être délicate. Pour la classification des séquences, cependant, une fonction de perte standard de Keras fonctionne bien, et c'est donc ce que nous utiliserons ici. + + + +```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, +) +``` + + + +Notez un piège très commun ici. Vous *pouvez* simplement passer le nom de la perte comme une chaîne à Keras, mais par défaut Keras supposera que vous avez déjà appliqué une fonction softmax à vos sorties. Cependant, de nombreux modèles produisent les valeurs juste avant l'application de la softmax, que l'on appelle aussi les *logits*. Nous devons indiquer à la fonction de perte que c'est ce que fait notre modèle, et la seule façon de le faire est de l'appeler directement, plutôt que par son nom avec une chaîne. + + + + +### Améliorer les performances d'entraînement + + + +Si vous essayez le code ci-dessus, il fonctionne certainement, mais vous constaterez que la perte ne diminue que lentement ou sporadiquement. La cause principale est le *taux d'apprentissage*. Comme pour la perte, lorsque nous transmettons à Keras le nom d'un optimiseur sous forme de chaîne de caractères, Keras initialise cet optimiseur avec des valeurs par défaut pour tous les paramètres, y compris le taux d'apprentissage. Cependant, nous savons depuis longtemps que les *transformers* bénéficient d'un taux d'apprentissage beaucoup plus faible que celui par défaut d'Adam, qui est de 1e-3, également écrit comme 10 à la puissance -3, ou 0,001. 5e-5 (0,00005), qui est environ vingt fois inférieur, est un bien meilleur point de départ. + +En plus de réduire le taux d'apprentissage, nous avons une deuxième astuce dans notre manche : nous pouvons réduire lentement le taux d'apprentissage au cours de l'entraînement. Dans la littérature, on parle parfois de *décroissance* ou d'*annulation* du taux d'apprentissage.le taux d'apprentissage. Dans Keras, la meilleure façon de le faire est d'utiliser un *planificateur du taux d'apprentissage*. Un bon planificateur à utiliser est `PolynomialDecay`. Malgré son nom, avec les paramètres par défaut, il diminue simplement de façon linéaire le taux d'apprentissage de la valeur initiale à la valeur finale au cours de l'entraînement, ce qui est exactement ce que nous voulons. Afin d'utiliser correctement un planificateur, nous devons lui dire combien de temps l'entraînement va durer. Nous calculons cela comme `num_train_steps` ci-dessous. + +```py +from tensorflow.keras.optimizers.schedules import PolynomialDecay + +batch_size = 8 +num_epochs = 3 +# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans l'ensemble de données, divisé par la taille du batch puis multiplié +# par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un lot tf.data.Dataset +# et non le jeu de données original Hugging Face Dataset, donc son len() est déjà 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 bibliothèque 🤗 *Transformers* possède également une fonction `create_optimizer()` qui créera un optimiseur `AdamW` avec un taux d'apprentissage décroissant. Il s'agit d'un raccourci pratique que vous verrez en détail dans les prochaines sections du cours. + + + +Nous avons maintenant notre tout nouvel optimiseur et nous pouvons essayer de nous entraîner avec lui. Tout d'abord, rechargeons le modèle pour réinitialiser les modifications apportées aux poids lors de l'entraînement que nous venons d'effectuer, puis nous pouvons le compiler avec le nouvel optimiseur : + +```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"]) +``` + +Maintenant, on *fit* : + +```py +model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) +``` + + + +💡 Si vous voulez télécharger automatiquement votre modèle sur le *Hub* pendant l'entraînement, vous pouvez passer un `PushToHubCallback` dans la méthode `model.fit()`. Nous en apprendrons davantage à ce sujet au [chapitre 4](/course/fr/chapter4/3). + + + +### Prédictions du modèle + + + + +Entraîner et regarder la perte diminuer, c'est très bien, mais que faire si l'on veut réellement obtenir des résultats du modèle entraîné, soit pour calculer des métriques, soit pour utiliser le modèle en production ? Pour ce faire, nous pouvons simplement utiliser la méthode `predict()`. Ceci retournera les *logits* de la tête de sortie du modèle, un par classe. + +```py +preds = model.predict(tf_validation_dataset)["logits"] +``` + +Nous pouvons convertir ces logits en prédictions de classe du modèle en utilisant `argmax` pour trouver le logit le plus élevé, qui correspond à la classe la plus probable : + +```py +class_preds = np.argmax(preds, axis=1) +print(preds.shape, class_preds.shape) +``` + +```python out +(408, 2) (408,) +``` + +Maintenant, utilisons ces `preds` pour calculer des métriques ! Nous pouvons charger les métriques associées au jeu de données MRPC aussi facilement que nous avons chargé le jeu de données, cette fois avec la fonction `evaluate.load()`. L'objet retourné a une méthode `compute()` que nous pouvons utiliser pour faire le calcul de la métrique : + +```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} +``` + +Les résultats exacts que vous obtiendrez peuvent varier, car l'initialisation aléatoire de la tête du modèle peut modifier les métriques obtenues. Ici, nous pouvons voir que notre modèle a une précision de 85,78% sur l'ensemble de validation et un score F1 de 89,97. Ce sont les deux métriques utilisées pour évaluer les résultats sur le jeu de données MRPC pour le benchmark GLUE. Le tableau du papier de [BERT](https://arxiv.org/pdf/1810.04805.pdf) indique un score F1 de 88,9 pour le modèle de base. Il s'agissait du modèle `uncased` alors que nous utilisons actuellement le modèle `cased`, ce qui explique le meilleur résultat. + +Ceci conclut l'introduction à le *finetuning* en utilisant l'API Keras. Un exemple d'application de cette méthode aux tâches les plus courantes du traitement automatique des langues sera présenté au [chapitre 7](/course/fr/chapter7). Si vous souhaitez affiner vos connaissances de l'API Keras, essayez *finetuner* un modèle sur le jeu de données GLUE SST-2, en utilisant le traitement des données que vous avez effectué dans la section 2. diff --git a/chapters/fr/chapter3/4.mdx b/chapters/fr/chapter3/4.mdx index e66caa6db..b04812639 100644 --- a/chapters/fr/chapter3/4.mdx +++ b/chapters/fr/chapter3/4.mdx @@ -1,359 +1,359 @@ -# Un entraînement complet - - - - - -Maintenant nous allons voir comment obtenir les mêmes résultats que dans la dernière section sans utiliser la classe `Trainer`. Encore une fois, nous supposons que vous avez fait le traitement des données dans la section 2. Voici un court résumé couvrant tout ce dont vous aurez besoin : - -```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) -``` - -### Préparer l'entraînement - -Avant d'écrire réellement notre boucle d'entraînement, nous devons définir quelques objets. Les premiers sont les *dataloaders* que nous utiliserons pour itérer sur les batchs. Mais avant de pouvoir définir ces chargeurs de données, nous devons appliquer un peu de post-traitement à nos `tokenized_datasets`, pour prendre soin de certaines choses que le `Trainer` fait pour nous automatiquement. Spécifiquement, nous devons : - -- supprimer les colonnes correspondant aux valeurs que le modèle n'attend pas (comme les colonnes `sentence1` et `sentence2`), -- renommer la colonne `label` en `labels` (parce que le modèle s'attend à ce que l'argument soit nommé `labels`), -- définir le format des jeux de données pour qu'ils retournent des tenseurs PyTorch au lieu de listes. - -Notre `tokenized_datasets` a une méthode pour chacune de ces étapes : - -```py -tokenized_datasets = tokenized_datasets.remove_columns(["sentence1", "sentence2", "idx"]) -tokenized_datasets = tokenized_datasets.rename_column("label", "labels") -tokenized_datasets.set_format("torch") -tokenized_datasets["train"].column_names -``` - -Nous pouvons alors vérifier que le résultat ne comporte que des colonnes que notre modèle acceptera : - -```python -["attention_mask", "input_ids", "labels", "token_type_ids"] -``` - -Maintenant que cela est fait, nous pouvons facilement définir nos *dataloaders* : - -```py -from torch.utils.data import DataLoader - -train_dataloader = DataLoader( - tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator -) -eval_dataloader = DataLoader( - tokenized_datasets["validation"], batch_size=8, collate_fn=data_collator -) -``` - -Pour vérifier rapidement qu'il n'y a pas d'erreur dans le traitement des données, nous pouvons inspecter un batch comme celui-ci : - -```py -for batch in train_dataloader: - break -{k: v.shape for k, v in batch.items()} -``` - -```python out -{'attention_mask': torch.Size([8, 65]), - 'input_ids': torch.Size([8, 65]), - 'labels': torch.Size([8]), - 'token_type_ids': torch.Size([8, 65])} -``` - -Notez que les formes réelles seront probablement légèrement différentes pour vous puisque nous avons défini `shuffle=True` pour le chargeur de données d'entraînement et que nous *paddons* à la longueur maximale dans le batch. - -Maintenant que nous en avons terminé avec le prétraitement des données (un objectif satisfaisant mais difficile à atteindre pour tout praticien d'apprentissage automatique), passons au modèle. Nous l'instancions exactement comme nous l'avons fait dans la section précédente : - -```py -from transformers import AutoModelForSequenceClassification - -model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) -``` - -Pour s'assurer que tout se passera bien pendant l'entraînement, nous transmettons notre batch à ce modèle : - -```py -outputs = model(**batch) -print(outputs.loss, outputs.logits.shape) -``` - -```python out -tensor(0.5441, grad_fn=) torch.Size([8, 2]) -``` - -Tous les modèles 🤗 *Transformers* renvoient la perte lorsque les `labels` sont fournis. Nous obtenons également les logits (deux pour chaque entrée de notre batch, donc un tenseur de taille 8 x 2). - -Nous sommes presque prêts à écrire notre boucle d'entraînement ! Il nous manque juste deux choses : un optimiseur et un planificateur de taux d'apprentissage. Puisque nous essayons de reproduire à la main ce que fait la fonction `Trainer`, utilisons les mêmes paramètres par défaut. L'optimiseur utilisé par `Trainer` est `AdamW`, qui est le même qu'Adam, mais avec une torsion pour la régularisation par décroissance de poids (voir [*Decoupled Weight Decay Regularization*](https://arxiv.org/abs/1711.05101) par Ilya Loshchilov et Frank Hutter) : - -```py -from transformers import AdamW - -optimizer = AdamW(model.parameters(), lr=5e-5) -``` - -Enfin, le planificateur du taux d'apprentissage utilisé par défaut est juste une décroissance linéaire de la valeur maximale (5e-5) à 0. Pour le définir correctement, nous devons connaître le nombre d'étapes d'entraînement que nous prendrons, qui est le nombre d'époques que nous voulons exécuter multiplié par le nombre de batch d'entraînement (qui est la longueur de notre *dataloader* d'entraînement). Le `Trainer` utilise trois époques par défaut, nous allons donc suivre ça : - -```py -from transformers import get_scheduler - -num_epochs = 3 -num_training_steps = num_epochs * len(train_dataloader) -lr_scheduler = get_scheduler( - "linear", - optimizer=optimizer, - num_warmup_steps=0, - num_training_steps=num_training_steps, -) -print(num_training_steps) -``` - -```python out -1377 -``` - -### La boucle d'entraînement - -Une dernière chose : nous voulons utiliser le GPU si nous en avons un (sur un CPU, l'entraînement peut prendre plusieurs heures au lieu de quelques minutes). Pour ce faire, nous définissons un `device` sur lequel nous allons placer notre modèle et nos batchs : - -```py -import torch - -device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") -model.to(device) -device -``` - -```python out -device(type='cuda') -``` - -Nous sommes maintenant prêts à entraîner ! Pour avoir une idée du moment où l'entraînement sera terminé, nous ajoutons une barre de progression sur le nombre d'étapes d'entraînement, en utilisant la bibliothèque `tqdm` : - -```py -from tqdm.auto import tqdm - -progress_bar = tqdm(range(num_training_steps)) - -model.train() -for epoch in range(num_epochs): - for batch in train_dataloader: - batch = {k: v.to(device) for k, v in batch.items()} - outputs = model(**batch) - loss = outputs.loss - loss.backward() - - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - progress_bar.update(1) -``` - -Vous pouvez voir que le cœur de la boucle d'entraînement ressemble beaucoup à celui de l'introduction. Nous n'avons pas demandé de rapport, donc cette boucle d'entraînement ne nous dira rien sur les résultats du modèle. Pour cela, nous devons ajouter une boucle d'évaluation. - - -### La boucle d'évaluation - -Comme nous l'avons fait précédemment, nous allons utiliser une métrique fournie par la bibliothèque 🤗 *Evaluate*. Nous avons déjà vu la méthode `metric.compute()`, mais les métriques peuvent en fait accumuler des batchs pour nous au fur et à mesure que nous parcourons la boucle de prédiction avec la méthode `add_batch()`. Une fois que nous avons accumulé tous les batchs, nous pouvons obtenir le résultat final avec `metric.compute()`. Voici comment implémenter tout cela dans une boucle d'évaluation : - -```py -import evaluate - -metric = evaluate.load("glue", "mrpc") -model.eval() -for batch in eval_dataloader: - batch = {k: v.to(device) for k, v in batch.items()} - with torch.no_grad(): - outputs = model(**batch) - - logits = outputs.logits - predictions = torch.argmax(logits, dim=-1) - metric.add_batch(predictions=predictions, references=batch["labels"]) - -metric.compute() -``` - -```python out -{'accuracy': 0.8431372549019608, 'f1': 0.8907849829351535} -``` - -Une fois encore, vos résultats seront légèrement différents en raison du caractère aléatoire de l'initialisation de la tête du modèle et du mélange des données, mais ils devraient se situer dans la même fourchette. - - - -✏️ **Essayez** Modifiez la boucle d'entraînement précédente pour *finetuner* votre modèle sur le jeu de données SST-2. - - - -### Optimisez votre boucle d'entraînement avec 🤗 Accelerate - - - -La boucle d'entraînement que nous avons définie précédemment fonctionne bien sur un seul CPU ou GPU. Mais en utilisant la bibliothèque [🤗 *Accelerate*](https://github.com/huggingface/accelerate), il suffit de quelques ajustements pour permettre un entraînement distribué sur plusieurs GPUs ou TPUs. En partant de la création des *dataloaders* d'entraînement et de validation, voici à quoi ressemble notre boucle d'entraînement manuel : - -```py -from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler - -model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) -optimizer = AdamW(model.parameters(), lr=3e-5) - -device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") -model.to(device) - -num_epochs = 3 -num_training_steps = num_epochs * len(train_dataloader) -lr_scheduler = get_scheduler( - "linear", - optimizer=optimizer, - num_warmup_steps=0, - num_training_steps=num_training_steps, -) - -progress_bar = tqdm(range(num_training_steps)) - -model.train() -for epoch in range(num_epochs): - for batch in train_dataloader: - batch = {k: v.to(device) for k, v in batch.items()} - outputs = model(**batch) - loss = outputs.loss - loss.backward() - - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - progress_bar.update(1) -``` - -Et voici les changements : - -```diff -+ from accelerate import Accelerator - from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler - -+ accelerator = Accelerator() - - model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) - optimizer = AdamW(model.parameters(), lr=3e-5) - -- device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") -- model.to(device) - -+ train_dataloader, eval_dataloader, model, optimizer = accelerator.prepare( -+ train_dataloader, eval_dataloader, model, optimizer -+ ) - - num_epochs = 3 - num_training_steps = num_epochs * len(train_dataloader) - lr_scheduler = get_scheduler( - "linear", - optimizer=optimizer, - num_warmup_steps=0, - num_training_steps=num_training_steps - ) - - progress_bar = tqdm(range(num_training_steps)) - - model.train() - for epoch in range(num_epochs): - for batch in train_dataloader: -- batch = {k: v.to(device) for k, v in batch.items()} - outputs = model(**batch) - loss = outputs.loss -- loss.backward() -+ accelerator.backward(loss) - - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - progress_bar.update(1) -``` - -La première ligne à ajouter est la ligne d'importation. La deuxième ligne instancie un objet `Accelerator` qui va regarder l'environnement et initialiser la bonne configuration distribuée. 🤗 *Accelerate* gère le placement des périphériques pour vous, donc vous pouvez enlever les lignes qui placent le modèle sur le périphérique (ou, si vous préférez, les changer pour utiliser `accelerator.device` au lieu de `device`). - -Ensuite, le gros du travail est fait dans la ligne qui envoie les *dataloaders*, le modèle, et l'optimiseur à `accelerator.prepare()`. Cela va envelopper ces objets dans le conteneur approprié pour s'assurer que votre entraînement distribué fonctionne comme prévu. Les changements restants à faire sont la suppression de la ligne qui met le batch sur le `device` (encore une fois, si vous voulez le garder, vous pouvez juste le changer pour utiliser `accelerator.device`) et le remplacement de `loss.backward()` par `accelerator.backward(loss)`. - - -⚠️ Afin de bénéficier de la rapidité offerte par les TPUs du Cloud, nous vous recommandons de rembourrer vos échantillons à une longueur fixe avec les arguments `padding="max_length"` et `max_length` du tokenizer. - - -Si vous souhaitez faire un copier-coller pour jouer, voici à quoi ressemble la boucle d'entraînement complète avec 🤗 Accelerate : - -```py -from accelerate import Accelerator -from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler - -accelerator = Accelerator() - -model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) -optimizer = AdamW(model.parameters(), lr=3e-5) - -train_dl, eval_dl, model, optimizer = accelerator.prepare( - train_dataloader, eval_dataloader, model, optimizer -) - -num_epochs = 3 -num_training_steps = num_epochs * len(train_dl) -lr_scheduler = get_scheduler( - "linear", - optimizer=optimizer, - num_warmup_steps=0, - num_training_steps=num_training_steps, -) - -progress_bar = tqdm(range(num_training_steps)) - -model.train() -for epoch in range(num_epochs): - for batch in train_dl: - outputs = model(**batch) - loss = outputs.loss - accelerator.backward(loss) - - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - progress_bar.update(1) -``` - -En plaçant ceci dans un script `train.py`, cela sera exécutable sur n'importe quel type d'installation distribuée. Pour l'essayer dans votre installation distribuée, exécutez la commande : - -```bash -accelerate config -``` - -qui vous demandera de répondre à quelques questions et enregistrera vos réponses dans un fichier de configuration utilisé par cette commande : - -``` -accelerate launch train.py -``` - -qui lancera l'entraînement distribué. - -Si vous voulez essayer ceci dans un *notebook* (par exemple, pour le tester avec des TPUs sur Colab), collez simplement le code dans une `training_function()` et lancez une dernière cellule avec : - -```python -from accelerate import notebook_launcher - -notebook_launcher(training_function) -``` - -Vous trouverez d'autres exemples dans le dépôt d'[🤗 *Accelerate*](https://github.com/huggingface/accelerate/tree/main/examples). +# Un entraînement complet + + + + + +Maintenant nous allons voir comment obtenir les mêmes résultats que dans la dernière section sans utiliser la classe `Trainer`. Encore une fois, nous supposons que vous avez fait le traitement des données dans la section 2. Voici un court résumé couvrant tout ce dont vous aurez besoin : + +```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) +``` + +### Préparer l'entraînement + +Avant d'écrire réellement notre boucle d'entraînement, nous devons définir quelques objets. Les premiers sont les *dataloaders* que nous utiliserons pour itérer sur les batchs. Mais avant de pouvoir définir ces chargeurs de données, nous devons appliquer un peu de post-traitement à nos `tokenized_datasets`, pour prendre soin de certaines choses que le `Trainer` fait pour nous automatiquement. Spécifiquement, nous devons : + +- supprimer les colonnes correspondant aux valeurs que le modèle n'attend pas (comme les colonnes `sentence1` et `sentence2`), +- renommer la colonne `label` en `labels` (parce que le modèle s'attend à ce que l'argument soit nommé `labels`), +- définir le format des jeux de données pour qu'ils retournent des tenseurs PyTorch au lieu de listes. + +Notre `tokenized_datasets` a une méthode pour chacune de ces étapes : + +```py +tokenized_datasets = tokenized_datasets.remove_columns(["sentence1", "sentence2", "idx"]) +tokenized_datasets = tokenized_datasets.rename_column("label", "labels") +tokenized_datasets.set_format("torch") +tokenized_datasets["train"].column_names +``` + +Nous pouvons alors vérifier que le résultat ne comporte que des colonnes que notre modèle acceptera : + +```python +["attention_mask", "input_ids", "labels", "token_type_ids"] +``` + +Maintenant que cela est fait, nous pouvons facilement définir nos *dataloaders* : + +```py +from torch.utils.data import DataLoader + +train_dataloader = DataLoader( + tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator +) +eval_dataloader = DataLoader( + tokenized_datasets["validation"], batch_size=8, collate_fn=data_collator +) +``` + +Pour vérifier rapidement qu'il n'y a pas d'erreur dans le traitement des données, nous pouvons inspecter un batch comme celui-ci : + +```py +for batch in train_dataloader: + break +{k: v.shape for k, v in batch.items()} +``` + +```python out +{'attention_mask': torch.Size([8, 65]), + 'input_ids': torch.Size([8, 65]), + 'labels': torch.Size([8]), + 'token_type_ids': torch.Size([8, 65])} +``` + +Notez que les formes réelles seront probablement légèrement différentes pour vous puisque nous avons défini `shuffle=True` pour le chargeur de données d'entraînement et que nous *paddons* à la longueur maximale dans le batch. + +Maintenant que nous en avons terminé avec le prétraitement des données (un objectif satisfaisant mais difficile à atteindre pour tout praticien d'apprentissage automatique), passons au modèle. Nous l'instancions exactement comme nous l'avons fait dans la section précédente : + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +Pour s'assurer que tout se passera bien pendant l'entraînement, nous transmettons notre batch à ce modèle : + +```py +outputs = model(**batch) +print(outputs.loss, outputs.logits.shape) +``` + +```python out +tensor(0.5441, grad_fn=) torch.Size([8, 2]) +``` + +Tous les modèles 🤗 *Transformers* renvoient la perte lorsque les `labels` sont fournis. Nous obtenons également les logits (deux pour chaque entrée de notre batch, donc un tenseur de taille 8 x 2). + +Nous sommes presque prêts à écrire notre boucle d'entraînement ! Il nous manque juste deux choses : un optimiseur et un planificateur de taux d'apprentissage. Puisque nous essayons de reproduire à la main ce que fait la fonction `Trainer`, utilisons les mêmes paramètres par défaut. L'optimiseur utilisé par `Trainer` est `AdamW`, qui est le même qu'Adam, mais avec une torsion pour la régularisation par décroissance de poids (voir [*Decoupled Weight Decay Regularization*](https://arxiv.org/abs/1711.05101) par Ilya Loshchilov et Frank Hutter) : + +```py +from transformers import AdamW + +optimizer = AdamW(model.parameters(), lr=5e-5) +``` + +Enfin, le planificateur du taux d'apprentissage utilisé par défaut est juste une décroissance linéaire de la valeur maximale (5e-5) à 0. Pour le définir correctement, nous devons connaître le nombre d'étapes d'entraînement que nous prendrons, qui est le nombre d'époques que nous voulons exécuter multiplié par le nombre de batch d'entraînement (qui est la longueur de notre *dataloader* d'entraînement). Le `Trainer` utilise trois époques par défaut, nous allons donc suivre ça : + +```py +from transformers import get_scheduler + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) +print(num_training_steps) +``` + +```python out +1377 +``` + +### La boucle d'entraînement + +Une dernière chose : nous voulons utiliser le GPU si nous en avons un (sur un CPU, l'entraînement peut prendre plusieurs heures au lieu de quelques minutes). Pour ce faire, nous définissons un `device` sur lequel nous allons placer notre modèle et nos batchs : + +```py +import torch + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) +device +``` + +```python out +device(type='cuda') +``` + +Nous sommes maintenant prêts à entraîner ! Pour avoir une idée du moment où l'entraînement sera terminé, nous ajoutons une barre de progression sur le nombre d'étapes d'entraînement, en utilisant la bibliothèque `tqdm` : + +```py +from tqdm.auto import tqdm + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Vous pouvez voir que le cœur de la boucle d'entraînement ressemble beaucoup à celui de l'introduction. Nous n'avons pas demandé de rapport, donc cette boucle d'entraînement ne nous dira rien sur les résultats du modèle. Pour cela, nous devons ajouter une boucle d'évaluation. + + +### La boucle d'évaluation + +Comme nous l'avons fait précédemment, nous allons utiliser une métrique fournie par la bibliothèque 🤗 *Evaluate*. Nous avons déjà vu la méthode `metric.compute()`, mais les métriques peuvent en fait accumuler des batchs pour nous au fur et à mesure que nous parcourons la boucle de prédiction avec la méthode `add_batch()`. Une fois que nous avons accumulé tous les batchs, nous pouvons obtenir le résultat final avec `metric.compute()`. Voici comment implémenter tout cela dans une boucle d'évaluation : + +```py +import evaluate + +metric = evaluate.load("glue", "mrpc") +model.eval() +for batch in eval_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + with torch.no_grad(): + outputs = model(**batch) + + logits = outputs.logits + predictions = torch.argmax(logits, dim=-1) + metric.add_batch(predictions=predictions, references=batch["labels"]) + +metric.compute() +``` + +```python out +{'accuracy': 0.8431372549019608, 'f1': 0.8907849829351535} +``` + +Une fois encore, vos résultats seront légèrement différents en raison du caractère aléatoire de l'initialisation de la tête du modèle et du mélange des données, mais ils devraient se situer dans la même fourchette. + + + +✏️ **Essayez** Modifiez la boucle d'entraînement précédente pour *finetuner* votre modèle sur le jeu de données SST-2. + + + +### Optimisez votre boucle d'entraînement avec 🤗 Accelerate + + + +La boucle d'entraînement que nous avons définie précédemment fonctionne bien sur un seul CPU ou GPU. Mais en utilisant la bibliothèque [🤗 *Accelerate*](https://github.com/huggingface/accelerate), il suffit de quelques ajustements pour permettre un entraînement distribué sur plusieurs GPUs ou TPUs. En partant de la création des *dataloaders* d'entraînement et de validation, voici à quoi ressemble notre boucle d'entraînement manuel : + +```py +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Et voici les changements : + +```diff ++ from accelerate import Accelerator + from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + ++ accelerator = Accelerator() + + model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) + optimizer = AdamW(model.parameters(), lr=3e-5) + +- device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +- model.to(device) + ++ train_dataloader, eval_dataloader, model, optimizer = accelerator.prepare( ++ train_dataloader, eval_dataloader, model, optimizer ++ ) + + num_epochs = 3 + num_training_steps = num_epochs * len(train_dataloader) + lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps + ) + + progress_bar = tqdm(range(num_training_steps)) + + model.train() + for epoch in range(num_epochs): + for batch in train_dataloader: +- batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss +- loss.backward() ++ accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +La première ligne à ajouter est la ligne d'importation. La deuxième ligne instancie un objet `Accelerator` qui va regarder l'environnement et initialiser la bonne configuration distribuée. 🤗 *Accelerate* gère le placement des périphériques pour vous, donc vous pouvez enlever les lignes qui placent le modèle sur le périphérique (ou, si vous préférez, les changer pour utiliser `accelerator.device` au lieu de `device`). + +Ensuite, le gros du travail est fait dans la ligne qui envoie les *dataloaders*, le modèle, et l'optimiseur à `accelerator.prepare()`. Cela va envelopper ces objets dans le conteneur approprié pour s'assurer que votre entraînement distribué fonctionne comme prévu. Les changements restants à faire sont la suppression de la ligne qui met le batch sur le `device` (encore une fois, si vous voulez le garder, vous pouvez juste le changer pour utiliser `accelerator.device`) et le remplacement de `loss.backward()` par `accelerator.backward(loss)`. + + +⚠️ Afin de bénéficier de la rapidité offerte par les TPUs du Cloud, nous vous recommandons de rembourrer vos échantillons à une longueur fixe avec les arguments `padding="max_length"` et `max_length` du tokenizer. + + +Si vous souhaitez faire un copier-coller pour jouer, voici à quoi ressemble la boucle d'entraînement complète avec 🤗 Accelerate : + +```py +from accelerate import Accelerator +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +accelerator = Accelerator() + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +train_dl, eval_dl, model, optimizer = accelerator.prepare( + train_dataloader, eval_dataloader, model, optimizer +) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dl) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dl: + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +En plaçant ceci dans un script `train.py`, cela sera exécutable sur n'importe quel type d'installation distribuée. Pour l'essayer dans votre installation distribuée, exécutez la commande : + +```bash +accelerate config +``` + +qui vous demandera de répondre à quelques questions et enregistrera vos réponses dans un fichier de configuration utilisé par cette commande : + +``` +accelerate launch train.py +``` + +qui lancera l'entraînement distribué. + +Si vous voulez essayer ceci dans un *notebook* (par exemple, pour le tester avec des TPUs sur Colab), collez simplement le code dans une `training_function()` et lancez une dernière cellule avec : + +```python +from accelerate import notebook_launcher + +notebook_launcher(training_function) +``` + +Vous trouverez d'autres exemples dans le dépôt d'[🤗 *Accelerate*](https://github.com/huggingface/accelerate/tree/main/examples). diff --git a/chapters/fr/chapter3/5.mdx b/chapters/fr/chapter3/5.mdx index db244e545..782e3f5d8 100644 --- a/chapters/fr/chapter3/5.mdx +++ b/chapters/fr/chapter3/5.mdx @@ -1,20 +1,25 @@ - - -# Finetuning, coché ! - -C'était amusant ! Dans les deux premiers chapitres, vous avez appris à connaître les modèles et les *tokenizers*, et vous savez maintenant comment les *finetuner* pour vos propres données. Pour récapituler, dans ce chapitre vous : - -{#if fw === 'pt'} -* avez appris à connaître les jeux de données dans le [*Hub*](https://huggingface.co/datasets), -* avez appris à charger et à prétraiter des jeux de données, notamment en utilisant le remplissage dynamique et les assembleurs, -* avez implémenté votre propre *finetuning* et évaluation d'un modèle, -* avez implémenté une boucle d'entraînement de niveau inférieur, -* avez utilisé 🤗 *Accelerate* pour adapter facilement votre boucle d'entraînement afin qu'elle fonctionne pour plusieurs GPUs ou TPUs. - -{:else} -* avez appris à connaître les jeux de données dans le [*Hub*](https://huggingface.co/datasets), -* avez appris comment charger et prétraiter les jeux de données, -* avez appris comment *finetuner* et évaluer un modèle avec Keras, -* avez implémenté une métrique personnalisée. - + + +# Finetuning, coché ! + + + +C'était amusant ! Dans les deux premiers chapitres, vous avez appris à connaître les modèles et les *tokenizers*, et vous savez maintenant comment les *finetuner* pour vos propres données. Pour récapituler, dans ce chapitre vous : + +{#if fw === 'pt'} +* avez appris à connaître les jeux de données dans le [*Hub*](https://huggingface.co/datasets), +* avez appris à charger et à prétraiter des jeux de données, notamment en utilisant le remplissage dynamique et les assembleurs, +* avez implémenté votre propre *finetuning* et évaluation d'un modèle, +* avez implémenté une boucle d'entraînement de niveau inférieur, +* avez utilisé 🤗 *Accelerate* pour adapter facilement votre boucle d'entraînement afin qu'elle fonctionne pour plusieurs GPUs ou TPUs. + +{:else} +* avez appris à connaître les jeux de données dans le [*Hub*](https://huggingface.co/datasets), +* avez appris comment charger et prétraiter les jeux de données, +* avez appris comment *finetuner* et évaluer un modèle avec Keras, +* avez implémenté une métrique personnalisée. + {/if} \ No newline at end of file diff --git a/chapters/fr/chapter3/6.mdx b/chapters/fr/chapter3/6.mdx index 44c0fdf44..5379a1fbe 100644 --- a/chapters/fr/chapter3/6.mdx +++ b/chapters/fr/chapter3/6.mdx @@ -1,296 +1,301 @@ - - - - -# Quiz de fin de chapitre - -Testez ce que vous avez appris dans ce chapitre ! - -### 1. Le jeu de données `emotion` contient des messages Twitter étiquetés avec des émotions. Cherchez-le dans le [*Hub*](https://huggingface.co/datasets) et lisez la carte du jeu de données. Laquelle de ces émotions n'est pas une de ses émotions de base ? - - - -### 2. Cherchez le jeu de données `ar_sarcasme` dans le [*Hub*](https://huggingface.co/datasets). Quelle tâche prend-il en charge ? - -tags.", - correct: true - }, - { - text: "Traduction automatique", - explain: "Ce n'est pas ça. Jetez un autre coup d'œil à la carte du jeu de données !" - }, - { - text: "Reconnaissance des entités nommées", - explain: "Ce n'est pas ça. Jetez un autre coup d'œil à la carte du jeu de données !" - }, - { - text: "Réponse aux questions", - explain: "Hélas, cette question n'a pas reçu de réponse correcte. Essayez à nouveau !" - } - ]} -/> - -### 3. Comment le modèle BERT attend-il qu'une paire de phrases soit traitée ? - -[SEP] est nécessaire pour séparer les deux phrases, mais ce n'est pas tout !" - }, - { - text: "[CLS] Tokens_de_la_phrase_1 Tokens_de_la_phrase_2", - explain: "Un jeton spécial [CLS] est requis au début, mais ce n'est pas la seule chose !" - }, - { - text: "[CLS] Tokens_de_la_phrase_1 [SEP] Tokens_de_la_phrase_2 [SEP]", - explain: "C'est exact !", - correct: true - }, - { - text: "[CLS] Tokens_de_la_phrase_1 [SEP] Tokens_de_la_phrase_2", - explain: "Un jeton spécial [CLS] est nécessaire au début, ainsi qu'un jeton spécial [SEP] pour séparer les deux phrases, mais ce n'est pas tout !" - } - ]} -/> - -{#if fw === 'pt'} -### 4. Quels sont les avantages de la méthode `Dataset.map()` ? - - - -### 5. Que signifie le remplissage (*padding*) dynamique ? - -tokens que la précédente dans le jeu de données.", - explain: "Cela n'a pas de sens de regarder l'ordre dans le jeu de données puisque nous le mélangeons pendant l'entraînement." - }, - ]} -/> - -### 6. Quel est le but d'une fonction de rassemblement ? - -DataCollatorWithPadding." - }, - { - text: "Elle rassemble tous les échantillons dans un batch.", - explain: "Vous pouvez passer la fonction de rassemblement comme argument d'une fonction DataLoader. Nous avons utilisé la fonction DataCollatorWithPadding qui remplit tous les éléments d'un batch pour qu'ils aient la même longueur.", - correct: true - }, - { - text: "Elle pré-traite tout le jeu de données.", - explain: "Ce serait une fonction de prétraitement, pas une fonction de rassemblement." - }, - { - text: "Elle tronque les séquences dans le jeu de données.", - explain: "Une fonction de rassemblement est impliquée dans le traitement des batchs individuels, et non de tout le jeu de données. Si vous êtes intéressé par la troncature, vous pouvez utiliser la fonction truncate en argument du tokenizer." - } - ]} -/> - -### 7. Que se passe-t-il lorsque vous instanciez une des classes `AutoModelForXxx` avec un modèle de langage pré-entraîné (tel que `bert-base-uncased`) qui correspond à une tâche différente de celle pour laquelle il a été entraîné ? - -AutoModelForSequenceClassification avec bert-base-uncased, nous avons eu des messages d'avertissement lors de l'instanciation du modèle. La tête pré-entraînée n'est pas utilisée pour la tâche de classification de séquences, elle est donc supprimée et une nouvelle tête est instanciée avec des poids aléatoires..", - correct: true - }, - { - text: "La tête du modèle pré-entraîné est supprimée.", - explain: "Quelque chose d'autre doit se produire. Essayez encore !" - }, - { - text: "Rien, puisque le modèle peut encore être finetuné pour les différentes tâches.", - explain: "La tête du modèle pré-entraîné n'a pas été entraînée à résoudre cette tâche, nous devons donc la supprimer !" - } - ]} -/> - -### 8. Quel est le but de `TrainingArguments` ? - -Trainer.", - explain: "", - correct: true - }, - { - text: "Préciser la taille du modèle.", - explain: "La taille du modèle est définie par la configuration du modèle, et non par la classe TrainingArguments." - }, - { - text: "Juste contenir les hyperparamètres utilisés pour l'évaluation.", - explain: "Dans l'exemple, nous avons spécifié où le modèle et ses checkpoints seront sauvegardés. Essayez à nouveau !" - }, - { - text: "Contenir seulement les hyperparamètres utilisés pour l'entraînement.", - explain: "Dans l'exemple, nous avons utilisé une evaluation_strategy également, ce qui a un impact sur l'évaluation. Essayez à nouveau !" - } - ]} -/> - -### 9. Pourquoi devriez-vous utiliser la librairie 🤗 *Accelerate* ? - -Accelerate ne fournit aucun modèles." - }, - { - text: "Elle fournit une API de haut niveau qui évite d'avoir à mettre en place sa propre boucle d'entraînement.", - explain: "C'est ce que nous avons fait avec le Trainer mais pas avec la librairie 🤗 Accelerate. Essayez à nouveau !" - }, - { - text: "Elle permet à nos boucles d'entraînement de fonctionner avec des stratégies distribuées.", - explain: "Avec 🤗 Accelerate, vos boucles d'entraînement fonctionneront pour plusieurs GPUs et TPUs.", - correct: true - }, - { - text: "Elle offre davantage de fonctions d'optimisation.", - explain: "Non, la librairie 🤗 Accelerate ne fournit pas de fonctions d'optimisation." - } - ]} -/> - -{:else} -### 4. Que se passe-t-il lorsque vous instanciez une des classes `TFAutoModelForXxx` avec un modèle de langage pré-entraîné (tel que `bert-base-uncased`) qui correspond à une tâche différente de celle pour laquelle il a été entraîné ? - -TFAutoModelForSequenceClassification avec bert-base-uncased, nous avons eu des messages d'avertissement lors de l'instanciation du modèle. La tête pré-entraînée n'est pas utilisée pour la tâche de classification de séquences, elle est donc supprimée et une nouvelle tête est instanciée avec des poids aléatoires..", - correct: true - }, - { - text: "La tête du modèle pré-entraîné est supprimée.", - explain: "Quelque chose d'autre doit se produire. Essayez encore !" - }, - { - text: "Rien, puisque le modèle peut encore être finetuné pour les différentes tâches.", - explain: "La tête du modèle pré-entraîné n'a pas été entraînée à résoudre cette tâche, nous devons donc la supprimer !" - } - ]} -/> - -### 5. Les modèles TensorFlow de `transformers` sont déjà des modèles Keras. Quel avantage cela offre-t-il ? - -TPUStrategy, y compris l'initialisation du modèle." - }, - { - text: "Vous pouvez tirer parti des méthodes existantes telles que compile(), fit() et predict().", - explain: "Une fois que vous disposez des données, l'entraînement sur celles-ci ne demande que très peu de travail.", - correct: true - }, - { - text: "Vous apprendrez à connaître Keras ainsi que transformers.", - explain: "Mais nous cherchons quelque chose d'autre :)", - correct: true - }, - { - text: "Vous pouvez facilement calculer les métriques liées au jeu de données.", - explain: "Keras nous aide à entraîner et à évaluer le modèle, et non à calculer les paramètres liés aux jeux de données." - } - ]} -/> - -### 6. Comment pouvez-vous définir votre propre métrique personnalisée ? - -tf.keras.metrics.Metric.", - explain: "Excellent !", - correct: true - }, - { - text: "Utilisation de l'API fonctionnelle de Keras.", - explain: "Essayez à nouveau !" - }, - { - text: "En utilisant un callable avec la signature metric_fn(y_true, y_pred).", - explain: " ", - correct: true - }, - { - text: "En le googlant.", - explain: "Ce n'est pas la réponse que nous cherchons, mais cela devrait vous aider à la trouver.", - correct: true - } - ]} -/> - -{/if} + + + + +# Quiz de fin de chapitre + + + +Testez ce que vous avez appris dans ce chapitre ! + +### 1. Le jeu de données `emotion` contient des messages Twitter étiquetés avec des émotions. Cherchez-le dans le [*Hub*](https://huggingface.co/datasets) et lisez la carte du jeu de données. Laquelle de ces émotions n'est pas une de ses émotions de base ? + + + +### 2. Cherchez le jeu de données `ar_sarcasme` dans le [*Hub*](https://huggingface.co/datasets). Quelle tâche prend-il en charge ? + +tags.", + correct: true + }, + { + text: "Traduction automatique", + explain: "Ce n'est pas ça. Jetez un autre coup d'œil à la carte du jeu de données !" + }, + { + text: "Reconnaissance des entités nommées", + explain: "Ce n'est pas ça. Jetez un autre coup d'œil à la carte du jeu de données !" + }, + { + text: "Réponse aux questions", + explain: "Hélas, cette question n'a pas reçu de réponse correcte. Essayez à nouveau !" + } + ]} +/> + +### 3. Comment le modèle BERT attend-il qu'une paire de phrases soit traitée ? + +[SEP] est nécessaire pour séparer les deux phrases, mais ce n'est pas tout !" + }, + { + text: "[CLS] Tokens_de_la_phrase_1 Tokens_de_la_phrase_2", + explain: "Un jeton spécial [CLS] est requis au début, mais ce n'est pas la seule chose !" + }, + { + text: "[CLS] Tokens_de_la_phrase_1 [SEP] Tokens_de_la_phrase_2 [SEP]", + explain: "C'est exact !", + correct: true + }, + { + text: "[CLS] Tokens_de_la_phrase_1 [SEP] Tokens_de_la_phrase_2", + explain: "Un jeton spécial [CLS] est nécessaire au début, ainsi qu'un jeton spécial [SEP] pour séparer les deux phrases, mais ce n'est pas tout !" + } + ]} +/> + +{#if fw === 'pt'} +### 4. Quels sont les avantages de la méthode `Dataset.map()` ? + + + +### 5. Que signifie le remplissage (*padding*) dynamique ? + +tokens que la précédente dans le jeu de données.", + explain: "Cela n'a pas de sens de regarder l'ordre dans le jeu de données puisque nous le mélangeons pendant l'entraînement." + }, + ]} +/> + +### 6. Quel est le but d'une fonction de rassemblement ? + +DataCollatorWithPadding." + }, + { + text: "Elle rassemble tous les échantillons dans un batch.", + explain: "Vous pouvez passer la fonction de rassemblement comme argument d'une fonction DataLoader. Nous avons utilisé la fonction DataCollatorWithPadding qui remplit tous les éléments d'un batch pour qu'ils aient la même longueur.", + correct: true + }, + { + text: "Elle pré-traite tout le jeu de données.", + explain: "Ce serait une fonction de prétraitement, pas une fonction de rassemblement." + }, + { + text: "Elle tronque les séquences dans le jeu de données.", + explain: "Une fonction de rassemblement est impliquée dans le traitement des batchs individuels, et non de tout le jeu de données. Si vous êtes intéressé par la troncature, vous pouvez utiliser la fonction truncate en argument du tokenizer." + } + ]} +/> + +### 7. Que se passe-t-il lorsque vous instanciez une des classes `AutoModelForXxx` avec un modèle de langage pré-entraîné (tel que `bert-base-uncased`) qui correspond à une tâche différente de celle pour laquelle il a été entraîné ? + +AutoModelForSequenceClassification avec bert-base-uncased, nous avons eu des messages d'avertissement lors de l'instanciation du modèle. La tête pré-entraînée n'est pas utilisée pour la tâche de classification de séquences, elle est donc supprimée et une nouvelle tête est instanciée avec des poids aléatoires..", + correct: true + }, + { + text: "La tête du modèle pré-entraîné est supprimée.", + explain: "Quelque chose d'autre doit se produire. Essayez encore !" + }, + { + text: "Rien, puisque le modèle peut encore être finetuné pour les différentes tâches.", + explain: "La tête du modèle pré-entraîné n'a pas été entraînée à résoudre cette tâche, nous devons donc la supprimer !" + } + ]} +/> + +### 8. Quel est le but de `TrainingArguments` ? + +Trainer.", + explain: "", + correct: true + }, + { + text: "Préciser la taille du modèle.", + explain: "La taille du modèle est définie par la configuration du modèle, et non par la classe TrainingArguments." + }, + { + text: "Juste contenir les hyperparamètres utilisés pour l'évaluation.", + explain: "Dans l'exemple, nous avons spécifié où le modèle et ses checkpoints seront sauvegardés. Essayez à nouveau !" + }, + { + text: "Contenir seulement les hyperparamètres utilisés pour l'entraînement.", + explain: "Dans l'exemple, nous avons utilisé une evaluation_strategy également, ce qui a un impact sur l'évaluation. Essayez à nouveau !" + } + ]} +/> + +### 9. Pourquoi devriez-vous utiliser la librairie 🤗 *Accelerate* ? + +Accelerate ne fournit aucun modèles." + }, + { + text: "Elle fournit une API de haut niveau qui évite d'avoir à mettre en place sa propre boucle d'entraînement.", + explain: "C'est ce que nous avons fait avec le Trainer mais pas avec la librairie 🤗 Accelerate. Essayez à nouveau !" + }, + { + text: "Elle permet à nos boucles d'entraînement de fonctionner avec des stratégies distribuées.", + explain: "Avec 🤗 Accelerate, vos boucles d'entraînement fonctionneront pour plusieurs GPUs et TPUs.", + correct: true + }, + { + text: "Elle offre davantage de fonctions d'optimisation.", + explain: "Non, la librairie 🤗 Accelerate ne fournit pas de fonctions d'optimisation." + } + ]} +/> + +{:else} +### 4. Que se passe-t-il lorsque vous instanciez une des classes `TFAutoModelForXxx` avec un modèle de langage pré-entraîné (tel que `bert-base-uncased`) qui correspond à une tâche différente de celle pour laquelle il a été entraîné ? + +TFAutoModelForSequenceClassification avec bert-base-uncased, nous avons eu des messages d'avertissement lors de l'instanciation du modèle. La tête pré-entraînée n'est pas utilisée pour la tâche de classification de séquences, elle est donc supprimée et une nouvelle tête est instanciée avec des poids aléatoires..", + correct: true + }, + { + text: "La tête du modèle pré-entraîné est supprimée.", + explain: "Quelque chose d'autre doit se produire. Essayez encore !" + }, + { + text: "Rien, puisque le modèle peut encore être finetuné pour les différentes tâches.", + explain: "La tête du modèle pré-entraîné n'a pas été entraînée à résoudre cette tâche, nous devons donc la supprimer !" + } + ]} +/> + +### 5. Les modèles TensorFlow de `transformers` sont déjà des modèles Keras. Quel avantage cela offre-t-il ? + +TPUStrategy, y compris l'initialisation du modèle." + }, + { + text: "Vous pouvez tirer parti des méthodes existantes telles que compile(), fit() et predict().", + explain: "Une fois que vous disposez des données, l'entraînement sur celles-ci ne demande que très peu de travail.", + correct: true + }, + { + text: "Vous apprendrez à connaître Keras ainsi que transformers.", + explain: "Mais nous cherchons quelque chose d'autre :)", + correct: true + }, + { + text: "Vous pouvez facilement calculer les métriques liées au jeu de données.", + explain: "Keras nous aide à entraîner et à évaluer le modèle, et non à calculer les paramètres liés aux jeux de données." + } + ]} +/> + +### 6. Comment pouvez-vous définir votre propre métrique personnalisée ? + +tf.keras.metrics.Metric.", + explain: "Excellent !", + correct: true + }, + { + text: "Utilisation de l'API fonctionnelle de Keras.", + explain: "Essayez à nouveau !" + }, + { + text: "En utilisant un callable avec la signature metric_fn(y_true, y_pred).", + explain: " ", + correct: true + }, + { + text: "En le googlant.", + explain: "Ce n'est pas la réponse que nous cherchons, mais cela devrait vous aider à la trouver.", + correct: true + } + ]} +/> + +{/if} diff --git a/chapters/fr/chapter4/1.mdx b/chapters/fr/chapter4/1.mdx index 7a0420461..b27ba639e 100644 --- a/chapters/fr/chapter4/1.mdx +++ b/chapters/fr/chapter4/1.mdx @@ -1,17 +1,22 @@ -# Le Hub d'Hugging Face - -Le [*Hub* d'Hugging Face](https://huggingface.co/), notre site internet principal, est une plateforme centrale qui permet à quiconque de découvrir, d'utiliser et de contribuer à de nouveaux modèles et jeux de données de pointe. Il héberge une grande variété de modèles, dont plus de 10 000 sont accessibles au public. Nous nous concentrerons sur les modèles dans ce chapitre, et nous examinerons les jeux de données au chapitre 5. - -Les modèles présents dans le *Hub* ne sont pas limités à 🤗 *Transformers* ou même au NLP. Il existe des modèles de [Flair](https://github.com/flairNLP/flair) et [AllenNLP](https://github.com/allenai/allennlp) pour le NLP, [Asteroid](https://github.com/asteroid-team/asteroid) et [pyannote](https://github.com/pyannote/pyannote-audio) pour l'audio, et [timm](https://github.com/rwightman/pytorch-image-models) pour la vision, pour n'en citer que quelques-uns. - -Chacun de ces modèles est hébergé sous forme de dépôt Git, ce qui permet le suivi des versions et la reproductibilité. Partager un modèle sur le *Hub*, c'est l'ouvrir à la communauté et le rendre accessible à tous ceux qui souhaitent l'utiliser facilement, ce qui leur évite d'avoir à entraîner eux-mêmes un modèle et simplifie le partage et l'utilisation. - -En outre, le partage d'un modèle sur le *Hub* déploie automatiquement une API d'inférence hébergée pour ce modèle. Toute personne de la communauté est libre de la tester directement sur la page du modèle, avec des entrées personnalisées et des *widgets* appropriés. - -La meilleure partie est que le partage ainsi que l'utilisation de n'importe quel modèle public sur le *Hub* sont totalement gratuits ! [Des plans payants](https://huggingface.co/pricing) existent également si vous souhaitez partager des modèles en privé. - -La vidéo ci-dessous montre comment naviguer sur le *Hub* : - - - +# Le Hub d'Hugging Face + + + +Le [*Hub* d'Hugging Face](https://huggingface.co/), notre site internet principal, est une plateforme centrale qui permet à quiconque de découvrir, d'utiliser et de contribuer à de nouveaux modèles et jeux de données de pointe. Il héberge une grande variété de modèles, dont plus de 10 000 sont accessibles au public. Nous nous concentrerons sur les modèles dans ce chapitre, et nous examinerons les jeux de données au chapitre 5. + +Les modèles présents dans le *Hub* ne sont pas limités à 🤗 *Transformers* ou même au NLP. Il existe des modèles de [Flair](https://github.com/flairNLP/flair) et [AllenNLP](https://github.com/allenai/allennlp) pour le NLP, [Asteroid](https://github.com/asteroid-team/asteroid) et [pyannote](https://github.com/pyannote/pyannote-audio) pour l'audio, et [timm](https://github.com/rwightman/pytorch-image-models) pour la vision, pour n'en citer que quelques-uns. + +Chacun de ces modèles est hébergé sous forme de dépôt Git, ce qui permet le suivi des versions et la reproductibilité. Partager un modèle sur le *Hub*, c'est l'ouvrir à la communauté et le rendre accessible à tous ceux qui souhaitent l'utiliser facilement, ce qui leur évite d'avoir à entraîner eux-mêmes un modèle et simplifie le partage et l'utilisation. + +En outre, le partage d'un modèle sur le *Hub* déploie automatiquement une API d'inférence hébergée pour ce modèle. Toute personne de la communauté est libre de la tester directement sur la page du modèle, avec des entrées personnalisées et des *widgets* appropriés. + +La meilleure partie est que le partage ainsi que l'utilisation de n'importe quel modèle public sur le *Hub* sont totalement gratuits ! [Des plans payants](https://huggingface.co/pricing) existent également si vous souhaitez partager des modèles en privé. + +La vidéo ci-dessous montre comment naviguer sur le *Hub* : + + + Avoir un compte huggingface.co est nécessaire pour suivre cette partie car nous allons créer et gérer des répertoires sur le *Hub* : [créer un compte](https://huggingface.co/join) \ No newline at end of file diff --git a/chapters/fr/chapter4/2.mdx b/chapters/fr/chapter4/2.mdx index 86579685f..6f86e8f4f 100644 --- a/chapters/fr/chapter4/2.mdx +++ b/chapters/fr/chapter4/2.mdx @@ -1,97 +1,97 @@ - - -# Utilisation de modèles pré-entraînés - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -Le *Hub* rend simple la sélection d'un modèle et permet alors que celui-ci puisse être utilisé dans toute bibliothèque en aval en seulement quelques lignes de code. Voyons comment utiliser concrètement l'un de ces modèles et comment contribuer au développement de la communauté. - -Supposons que nous recherchions un modèle basé sur le français, capable de remplir des masques. - -
-Selecting the Camembert model. -
- -Nous choisissons le *checkpoint* `camembert-base` pour essayer. L'identifiant `camembert-base` est tout ce dont nous avons besoin pour commencer à utiliser le modèle ! Comme vous l'avez vu dans les chapitres précédents, nous pouvons l'instancier en utilisant la fonction `pipeline()` : - -```py -from transformers import pipeline - -camembert_fill_mask = pipeline("fill-mask", model="camembert-base") -results = camembert_fill_mask("Le camembert est :)") -``` - -```python out -[ - {'sequence': 'Le camembert est délicieux :)', 'score': 0.49091005325317383, 'token': 7200, 'token_str': 'délicieux'}, - {'sequence': 'Le camembert est excellent :)', 'score': 0.1055697426199913, 'token': 2183, 'token_str': 'excellent'}, - {'sequence': 'Le camembert est succulent :)', 'score': 0.03453313186764717, 'token': 26202, 'token_str': 'succulent'}, - {'sequence': 'Le camembert est meilleur :)', 'score': 0.0330314114689827, 'token': 528, 'token_str': 'meilleur'}, - {'sequence': 'Le camembert est parfait :)', 'score': 0.03007650189101696, 'token': 1654, 'token_str': 'parfait'} -] -``` - -Comme vous pouvez le constater, le chargement d'un modèle dans un pipeline est extrêmement simple. La seule chose à laquelle vous devez faire attention est que le *checkpoint* choisi soit adapté à la tâche pour laquelle il va être utilisé. Par exemple, ici nous chargeons le *checkpoint* `camembert-base` dans le pipeline `fill-mask`, ce qui est tout à fait correct. Mais si nous chargerions ce *checkpoint* dans le pipeline `text-classification`, les résultats n'auraient aucun sens car la tête de `camembert-base` n'est pas adaptée à cette tâche ! Nous recommandons d'utiliser le sélecteur de tâche dans l'interface du *Hub* afin de sélectionner les *checkpoints* appropriés : - -
-The task selector on the web interface. -
- -Vous pouvez également instancier le *checkpoint* en utilisant directement l'architecture du modèle : - -{#if fw === 'pt'} -```py -from transformers import CamembertTokenizer, CamembertForMaskedLM - -tokenizer = CamembertTokenizer.from_pretrained("camembert-base") -model = CamembertForMaskedLM.from_pretrained("camembert-base") -``` - -Cependant, nous recommandons d'utiliser les classes [`Auto*`](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes) à la place, car elles sont par conception indépendantes de l'architecture. Alors que l'exemple de code précédent limite les utilisateurs aux *checkpoints* chargeables dans l'architecture CamemBERT, l'utilisation des classes `Auto*` facilite le changement de *checkpoint* : - -```py -from transformers import AutoTokenizer, AutoModelForMaskedLM - -tokenizer = AutoTokenizer.from_pretrained("camembert-base") -model = AutoModelForMaskedLM.from_pretrained("camembert-base") -``` -{:else} -```py -from transformers import CamembertTokenizer, TFCamembertForMaskedLM - -tokenizer = CamembertTokenizer.from_pretrained("camembert-base") -model = TFCamembertForMaskedLM.from_pretrained("camembert-base") -``` - -Cependant, nous recommandons d'utiliser les classes [`TFAuto*`](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes) à la place, car elles sont par conception indépendantes de l'architecture. Alors que l'exemple de code précédent limite les utilisateurs aux *checkpoints* chargeables dans l'architecture CamemBERT, l'utilisation des classes `TFAuto*` facilite le changement de *checkpoint* : - - -```py -from transformers import AutoTokenizer, TFAutoModelForMaskedLM - -tokenizer = AutoTokenizer.from_pretrained("camembert-base") -model = TFAutoModelForMaskedLM.from_pretrained("camembert-base") -``` -{/if} - - -Lorsque vous utilisez un modèle pré-entraîné, assurez-vous de vérifier comment il a été entraîné, sur quels jeux de données, ses limites et ses biais. Toutes ces informations doivent être indiquées dans sa carte. - + + +# Utilisation de modèles pré-entraînés + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Le *Hub* rend simple la sélection d'un modèle et permet alors que celui-ci puisse être utilisé dans toute bibliothèque en aval en seulement quelques lignes de code. Voyons comment utiliser concrètement l'un de ces modèles et comment contribuer au développement de la communauté. + +Supposons que nous recherchions un modèle basé sur le français, capable de remplir des masques. + +
+Selecting the Camembert model. +
+ +Nous choisissons le *checkpoint* `camembert-base` pour essayer. L'identifiant `camembert-base` est tout ce dont nous avons besoin pour commencer à utiliser le modèle ! Comme vous l'avez vu dans les chapitres précédents, nous pouvons l'instancier en utilisant la fonction `pipeline()` : + +```py +from transformers import pipeline + +camembert_fill_mask = pipeline("fill-mask", model="camembert-base") +results = camembert_fill_mask("Le camembert est :)") +``` + +```python out +[ + {'sequence': 'Le camembert est délicieux :)', 'score': 0.49091005325317383, 'token': 7200, 'token_str': 'délicieux'}, + {'sequence': 'Le camembert est excellent :)', 'score': 0.1055697426199913, 'token': 2183, 'token_str': 'excellent'}, + {'sequence': 'Le camembert est succulent :)', 'score': 0.03453313186764717, 'token': 26202, 'token_str': 'succulent'}, + {'sequence': 'Le camembert est meilleur :)', 'score': 0.0330314114689827, 'token': 528, 'token_str': 'meilleur'}, + {'sequence': 'Le camembert est parfait :)', 'score': 0.03007650189101696, 'token': 1654, 'token_str': 'parfait'} +] +``` + +Comme vous pouvez le constater, le chargement d'un modèle dans un pipeline est extrêmement simple. La seule chose à laquelle vous devez faire attention est que le *checkpoint* choisi soit adapté à la tâche pour laquelle il va être utilisé. Par exemple, ici nous chargeons le *checkpoint* `camembert-base` dans le pipeline `fill-mask`, ce qui est tout à fait correct. Mais si nous chargerions ce *checkpoint* dans le pipeline `text-classification`, les résultats n'auraient aucun sens car la tête de `camembert-base` n'est pas adaptée à cette tâche ! Nous recommandons d'utiliser le sélecteur de tâche dans l'interface du *Hub* afin de sélectionner les *checkpoints* appropriés : + +
+The task selector on the web interface. +
+ +Vous pouvez également instancier le *checkpoint* en utilisant directement l'architecture du modèle : + +{#if fw === 'pt'} +```py +from transformers import CamembertTokenizer, CamembertForMaskedLM + +tokenizer = CamembertTokenizer.from_pretrained("camembert-base") +model = CamembertForMaskedLM.from_pretrained("camembert-base") +``` + +Cependant, nous recommandons d'utiliser les classes [`Auto*`](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes) à la place, car elles sont par conception indépendantes de l'architecture. Alors que l'exemple de code précédent limite les utilisateurs aux *checkpoints* chargeables dans l'architecture CamemBERT, l'utilisation des classes `Auto*` facilite le changement de *checkpoint* : + +```py +from transformers import AutoTokenizer, AutoModelForMaskedLM + +tokenizer = AutoTokenizer.from_pretrained("camembert-base") +model = AutoModelForMaskedLM.from_pretrained("camembert-base") +``` +{:else} +```py +from transformers import CamembertTokenizer, TFCamembertForMaskedLM + +tokenizer = CamembertTokenizer.from_pretrained("camembert-base") +model = TFCamembertForMaskedLM.from_pretrained("camembert-base") +``` + +Cependant, nous recommandons d'utiliser les classes [`TFAuto*`](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes) à la place, car elles sont par conception indépendantes de l'architecture. Alors que l'exemple de code précédent limite les utilisateurs aux *checkpoints* chargeables dans l'architecture CamemBERT, l'utilisation des classes `TFAuto*` facilite le changement de *checkpoint* : + + +```py +from transformers import AutoTokenizer, TFAutoModelForMaskedLM + +tokenizer = AutoTokenizer.from_pretrained("camembert-base") +model = TFAutoModelForMaskedLM.from_pretrained("camembert-base") +``` +{/if} + + +Lorsque vous utilisez un modèle pré-entraîné, assurez-vous de vérifier comment il a été entraîné, sur quels jeux de données, ses limites et ses biais. Toutes ces informations doivent être indiquées dans sa carte. + diff --git a/chapters/fr/chapter4/3.mdx b/chapters/fr/chapter4/3.mdx index fabdd4a36..b9db79e6e 100644 --- a/chapters/fr/chapter4/3.mdx +++ b/chapters/fr/chapter4/3.mdx @@ -1,638 +1,638 @@ - - -# Partage de modèles pré-entraînés - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -Dans les étapes ci-dessous, nous allons examiner les moyens les plus simples de partager des modèles pré-entraînés sur le 🤗 *Hub*. Il existe des outils et des services disponibles qui permettent de simplifier le partage et la mise à jour des modèles directement sur le *Hub*, que nous allons explorer ci-dessous. - - - -Nous encourageons tous les utilisateurs qui entraînent des modèles à contribuer en les partageant avec la communauté. Le partage des modèles, même s'ils ont été entraînés sur des jeux de données très spécifiques, aidera les autres, en leur faisant gagner du temps, des ressources de calcul et en leur donnant accès à des artefacts entraînés utiles. À votre tour, vous pourrez bénéficier du travail effectué par les autres ! - -Il y a trois façons de créer de nouveaux dépôts de modèles : - -- en utilisant l'API `push_to_hub`, -- en utilisant la bibliothèque Python `huggingface_hub`, -- en utilisant l'interface web. - -Une fois que vous avez créé un dépôt, vous pouvez y charger des fichiers via git et git-lfs. Nous allons vous guider dans la création de dépôts de modèles et le téléchargement de fichiers dans les sections suivantes. - - -## Utilisation de l'API `push_to_hub` - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -La façon la plus simple de télécharger des fichiers vers le *Hub* est d'utiliser l'API `push_to_hub`. - -Avant d'aller plus loin, vous devrez générer un jeton d'authentification afin que l'API `huggingface_hub` sache qui vous êtes et à quels espaces de noms vous avez accès en écriture. Assurez-vous que vous êtes dans un environnement où vous avez installé `transformers` (voir la [Configuration](/course/fr/chapter0)). Si vous êtes dans un *notebook*, vous pouvez utiliser la fonction suivante pour vous connecter : - -```python -from huggingface_hub import notebook_login - -notebook_login() -``` - -Dans un terminal, vous pouvez exécuter : - -```bash -huggingface-cli login -``` - -Dans les deux cas, vous serez invité à saisir votre nom d'utilisateur et votre mot de passe, qui sont les mêmes que ceux que vous utilisez pour vous connecter au *Hub*. Si vous n'avez pas encore de profil pour le Hub, vous devez en créer un [ici](https://huggingface.co/join). - -Super ! Votre jeton d'authentification est maintenant stocké dans votre dossier de cache. Créons quelques dépôts ! - -{#if fw === 'pt'} - -Si vous avez joué avec l'API `Trainer` pour entraîner un modèle, le moyen le plus simple de le télécharger sur le *Hub* est de définir `push_to_hub=True` lorsque vous définissez vos `TrainingArguments` : - -```py -from transformers import TrainingArguments - -training_args = TrainingArguments( - "bert-finetuned-mrpc", save_strategy="epoch", push_to_hub=True -) -``` - -Lorsque vous appelez `trainer.train()`, le `Trainer` téléchargera alors votre modèle vers le *Hub* à chaque fois qu'il sera sauvegardé (ici à chaque époque) dans un dépôt dans votre espace personnel. Ce dépôt sera nommé comme le répertoire de sortie que vous avez choisi (ici `bert-finetuned-mrpc`) mais vous pouvez choisir un nom différent avec `hub_model_id = "a_different_name"`. - -Pour télécharger votre modèle vers une organisation dont vous êtes membre, passez-le simplement avec `hub_model_id = "my_organization/my_repo_name"`. - -Une fois que votre entraînement est terminé, vous devriez faire un dernier `trainer.push_to_hub()` pour télécharger la dernière version de votre modèle. Cela générera également une carte pour le modèle avec toutes les métadonnées pertinentes, rapportant les hyperparamètres utilisés et les résultats d'évaluation ! Voici un exemple du contenu que vous pourriez trouver dans une telle carte de modèle : -
- An example of an auto-generated model card. -
- -{:else} - -Si vous utilisez Keras pour entraîner votre modèle, le moyen le plus simple de le télécharger sur le *Hub* est de passer un `PushToHubCallback` lorsque vous appelez `model.fit()` : - -```py -from transformers import PushToHubCallback - -callback = PushToHubCallback( - "bert-finetuned-mrpc", save_strategy="epoch", tokenizer=tokenizer -) -``` - -Ensuite, vous devez ajouter `callbacks=[callback]` dans votre appel à `model.fit()`. Le *callback* téléchargera alors votre modèle vers le *Hub* à chaque fois qu'il sera sauvegardé (ici à chaque époque) dans un dépôt dans votre espace de noms. Ce dépôt sera nommé comme le répertoire de sortie que vous avez choisi (ici `bert-finetuned-mrpc`) mais vous pouvez choisir un nom différent avec `hub_model_id = "a_different_name"`. - -Pour télécharger votre modèle dans une organisation dont vous êtes membre, passez-le simplement avec `hub_model_id = "my_organization/my_repo_name"`. - -{/if} - -A un niveau inférieur, l'accès au *Hub* peut être fait directement sur les modèles, les *tokenizers* et les objets de configuration via leur méthode `push_to_hub()`. Cette méthode s'occupe à la fois de la création du dépôt et de l'envoi les fichiers du modèle et du *tokenizer* directement dans le dépôt. Aucune manipulation manuelle n'est nécessaire, contrairement à l'API que nous verrons plus loin. - -Pour avoir une idée de son fonctionnement, commençons par initialiser un modèle et un *tokenizer* : - -{#if fw === 'pt'} -```py -from transformers import AutoModelForMaskedLM, AutoTokenizer - -checkpoint = "camembert-base" - -model = AutoModelForMaskedLM.from_pretrained(checkpoint) -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -``` -{:else} -```py -from transformers import TFAutoModelForMaskedLM, AutoTokenizer - -checkpoint = "camembert-base" - -model = TFAutoModelForMaskedLM.from_pretrained(checkpoint) -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -``` -{/if} - -Vous êtes libre de faire ce que vous voulez avec ces objets : ajouter des *tokens* au *tokenizer*, entraîner le modèle, le *finetuner*. Une fois que vous êtes satisfait du modèle, des poids et du *tokenizer* obtenus, vous pouvez utiliser la méthode `push_to_hub()` directement disponible sur l'objet `model` : - -```py -model.push_to_hub("dummy-model") -``` - -Cela va créer le nouveau dépôt `dummy-model` dans votre profil et le remplir avec les fichiers du modèle. -Faites la même chose avec le *tokenizer*, de sorte que tous les fichiers sont maintenant disponibles dans ce dépôt : - -```py -tokenizer.push_to_hub("dummy-model") -``` - -Si vous appartenez à une organisation, il suffit de spécifier l'argument `organization` pour télécharger dans l'espace de cette organisation : - -```py -tokenizer.push_to_hub("dummy-model", organization="huggingface") -``` - -Si vous souhaitez utiliser un jeton Hugging Face spécifique, vous pouvez également le spécifier à la méthode `push_to_hub()` : - -```py -tokenizer.push_to_hub("dummy-model", organization="huggingface", use_auth_token="") -``` - -Maintenant, dirigez-vous sur *Hub* pour trouver votre modèle nouvellement téléchargé : *https://huggingface.co/user-or-organization/dummy-model*. - -Cliquez sur l'onglet « Fichiers et versions » et vous devriez voir les fichiers visibles dans la capture d'écran suivante : - -{#if fw === 'pt'} -
-Dummy model containing both the tokenizer and model files. -
-{:else} -
-Dummy model containing both the tokenizer and model files. -
-{/if} - - - -✏️ **Essayez** Prenez le modèle et le *tokenizer* associés au *checkpoint* `bert-base-cased` et téléchargez-les vers un dépôt dans votre espace en utilisant la méthode `push_to_hub()`. Vérifiez que le dépôt apparaît correctement sur votre page avant de le supprimer. - - - -Comme vous l'avez vu, la méthode `push_to_hub()` accepte plusieurs arguments, ce qui permet de télécharger vers un dépôt ou un espace d'organisation spécifique, ou d'utiliser un jeton d'API différent. Nous vous recommandons de jeter un coup d'œil à la spécification de la méthode disponible directement dans la documentation de [🤗 *Transformers*](https://huggingface.co/transformers/model_sharing.html) pour avoir une idée de ce qui est possible. - -La méthode `push_to_hub()` est soutenue par le *package* Python [`huggingface_hub`](https://github.com/huggingface/huggingface_hub), qui offre une API directe au *Hub*. C'est intégré à 🤗 *Transformers* et à plusieurs autres bibliothèques d'apprentissage automatique, comme [`allenlp`](https://github.com/allenai/allennlp). Bien que nous nous concentrions sur l'intégration via 🤗 *Transformers* dans ce chapitre, son intégration dans votre propre code ou bibliothèque est simple. - -Passez à la dernière section pour voir comment télécharger des fichiers dans votre dépôt nouvellement créé ! - -## Utilisation de la bibliothèque Python `huggingface_hub` - -La bibliothèque Python `huggingface_hub` est un *package* qui offre un ensemble d'outils pour les hubs des modèles et des jeux de données. Elle fournit des méthodes et des classes simples pour des tâches courantes telles qu'obtenir et gérer des informations à propos des dépôts sur le *Hub*. Elle fournit des APIs simples qui fonctionnent au-dessus de git pour gérer le contenu de ces dépôts et pour intégrer le *Hub* dans vos projets et bibliothèques. - -De la même manière que pour l'utilisation de l'API `push_to_hub`, vous devrez avoir votre jeton d'API enregistré dans votre cache. Pour ce faire, vous devrez utiliser la commande `login` de la CLI, comme mentionné dans la section précédente (encore une fois, assurez-vous de faire précéder ces commandes du caractère `!` si vous les exécutez dans Google Colab) : - -```bash -huggingface-cli login -``` - -Le *package* `huggingface_hub` offre plusieurs méthodes et classes qui sont utiles pour notre objectif. Tout d'abord, il y a quelques méthodes pour gérer la création, la suppression des dépôts, et autres : - -```python no-format -from huggingface_hub import ( - # Gestion des utilisateurs - login, - logout, - whoami, - - # Création et gestion du dépôt - create_repo, - delete_repo, - update_repo_visibility, - - # Et quelques méthodes pour récupérer/changer des informations sur le contenu - list_models, - list_datasets, - list_metrics, - list_repo_files, - upload_file, - delete_file, -) -``` - - -De plus, elle offre la très puissante classe `Repository` pour gérer un dépôt local. Nous allons explorer ces méthodes et cette classe dans les prochaines sections pour comprendre comment les exploiter. - -La méthode `create_repo` peut être utilisée pour créer un nouveau dépôt sur le *Hub* : - -```py -from huggingface_hub import create_repo - -create_repo("dummy-model") -``` - -Ceci créera le dépôt `dummy-model` dans votre espace. Si vous le souhaitez, vous pouvez spécifier à quelle organisation le dépôt doit appartenir en utilisant l'argument `organization` : - -```py -from huggingface_hub import create_repo - -create_repo("dummy-model", organization="huggingface") -``` - -Cela créera le dépôt `dummy-model` dans l'espace de nom `huggingface`, en supposant que vous appartenez à cette organisation. -D'autres arguments qui peuvent être utiles sont : - -- `private`, afin de spécifier si le dépôt doit être visible des autres ou non, -- `token`, si vous voulez remplacer le jeton stocké dans votre cache par un jeton donné, -- `repo_type`, si vous souhaitez créer un `dataset` ou un `space` au lieu d'un modèle. Les valeurs acceptées sont `"dataset"` et `"space"`. - -Une fois que le dépôt est créé, nous devons y ajouter des fichiers ! Passez à la section suivante pour voir les trois façons dont cela peut être géré. - - -## Utilisation de l'interface web - -L'interface web offre des outils pour gérer les dépôts directement dans le *Hub*. En utilisant l'interface, vous pouvez facilement créer des dépôts, ajouter des fichiers (même de grande taille !), explorer des modèles, visualiser les différences, et bien plus encore. - -Pour créer un nouveau dépôt, visitez [huggingface.co/new](https://huggingface.co/new) : - -
-Page showcasing the model used for the creation of a new model repository. -
- -Tout d'abord, indiquez le propriétaire du dépôt : il peut s'agir de vous ou de l'une des organisations auxquelles vous êtes affilié. Si vous choisissez une organisation, le modèle sera présenté sur la page de l'organisation et chaque membre de l'organisation aura la possibilité de contribuer au dépôt. - -Ensuite, saisissez le nom de votre modèle. Ce sera également le nom du dépôt. Enfin, vous pouvez préciser si vous souhaitez que votre modèle soit public ou privé. Les modèles privés sont cachés de la vue du public. - -Après avoir créé votre dépôt de modèles, vous devriez voir une page comme celle-ci : - -
-An empty model page after creating a new repository. -
- -C'est là que votre modèle sera hébergé. Pour commencer à le remplir, vous pouvez ajouter un fichier README directement depuis l'interface web. - -
-The README file showing the Markdown capabilities. -
- -Le fichier README est en Markdown. N'hésitez pas à vous lâcher avec lui ! La troisième partie de ce chapitre est consacrée à la construction d'une carte de modèle. Celles-ci sont d'une importance capitale pour valoriser votre modèle, car c'est par elles que vous indiquez aux autres ce qu'il peut faire. - -Si vous regardez l'onglet « *Files and versions* », vous verrez qu'il n'y a pas encore beaucoup de fichiers : juste le *README.md* que vous venez de créer et le fichier *.gitattributes* qui garde la trace des gros fichiers. - -
-The 'Files and versions' tab only shows the .gitattributes and README.md files. -
- -Nous allons maintenant voir comment ajouter de nouveaux fichiers. - -## Téléchargement des fichiers du modèle - -Le système de gestion des fichiers sur le *Hub* est basé sur git pour les fichiers ordinaires et git-lfs (qui signifie [Git Large File Storage](https://git-lfs.github.com/)) pour les fichiers plus importants. - -Dans la section suivante, nous passons en revue trois façons différentes de télécharger des fichiers sur le *Hub* : par `huggingface_hub` et par des commandes git. - -### L'approche `upload_file' - -L'utilisation de `upload_file` ne nécessite pas que git et git-lfs soient installés sur votre système. Il pousse les fichiers directement vers le 🤗 *Hub* en utilisant des requêtes HTTP POST. Une limitation de cette approche est qu'elle ne gère pas les fichiers dont la taille est supérieure à 5 Go. -Si vos fichiers ont une taille supérieure à 5 Go, veuillez suivre les deux autres méthodes détaillées ci-dessous. - -L'API peut être utilisée comme suit : - -```py -from huggingface_hub import upload_file - -upload_file( - "/config.json", - path_in_repo="config.json", - repo_id="/dummy-model", -) -``` - -Ceci téléchargera le fichier `config.json` disponible à `` à la racine du dépôt en tant que `config.json`, vers le dépôt `dummy-model`. -D'autres arguments qui peuvent être utiles sont : - -- `token`, si vous souhaitez remplacer le jeton stocké dans votre cache par un jeton donné, -- `repo_type`, si vous souhaitez télécharger vers un `dataset` ou un `space` au lieu d'un modèle. Les valeurs acceptées sont `"dataset"` et `"space"`. - - -### La classe `Repository` - -La classe `Repository` gère un dépôt local d'une manière similaire à git. Elle abstrait la plupart des problèmes que l'on peut rencontrer avec git pour fournir toutes les fonctionnalités dont nous avons besoin. - -L'utilisation de cette classe nécessite l'installation de git et de git-lfs, donc assurez-vous que git-lfs est installé (voir [ici](https://git-lfs.github.com/) pour les instructions d'installation) et configuré avant de commencer. - -Afin de commencer à jouer avec le dépôt que nous venons de créer, nous pouvons commencer par l'initialiser dans un dossier local en clonant le dépôt distant : - -```py -from huggingface_hub import Repository - -repo = Repository("", clone_from="/dummy-model") -``` - -Cela a créé le dossier `` dans notre répertoire de travail. Ce dossier ne contient que le fichier `.gitattributes` car c'est le seul fichier créé lors de l'instanciation du dépôt par `create_repo`. - -A partir de maintenant, nous pouvons utiliser plusieurs des méthodes traditionnelles de git : - -```py -repo.git_pull() -repo.git_add() -repo.git_commit() -repo.git_push() -repo.git_tag() -``` - -Et d'autres encore ! Nous vous recommandons de jeter un coup d’œil à la documentation de `Repository` disponible [ici](https://github.com/huggingface/huggingface_hub/tree/main/src/huggingface_hub#advanced-programmatic-repository-management) pour une vue d'ensemble de toutes les méthodes disponibles. - -Actuellement, nous avons un modèle et un *tokenizer* que nous voulons pousser vers le *Hub*. Nous avons réussi à cloner le dépôt, nous pouvons donc enregistrer les fichiers dans ce dépôt. - -Nous nous assurons d'abord que notre clone local est à jour en récupérant les dernières modifications : - -```py -repo.git_pull() -``` - -Une fois que c'est fait, nous sauvegardons les fichiers du modèle et du *tokenizer* : - -```py -model.save_pretrained("") -tokenizer.save_pretrained("") -``` - -Le `` contient maintenant tous les fichiers du modèle et du *tokenizer*. Nous suivons le flux de travail git habituel en ajoutant des fichiers à la zone de transit, en les validant et en les poussant vers le *Hub* : - -```py -repo.git_add() -repo.git_commit("Add model and tokenizer files") -repo.git_push() -``` - -Félicitations ! Vous venez de pousser vos premiers fichiers sur le *Hub*. - -### L'approche basée sur git - -Il s'agit de l'approche la plus basique pour télécharger des fichiers : nous le ferons directement avec git et git-lfs. La plupart des difficultés sont abstraites par les approches précédentes, mais il y a quelques réserves avec la méthode suivante, nous allons donc suivre un cas d'utilisation plus complexe. - -L'utilisation de cette classe nécessite l'installation de git et de git-lfs, donc assurez-vous d'avoir [git-lfs](https://git-lfs.github.com/) installé et configuré avant de commencer. - -Commencez par initialiser git-lfs : - -```bash -git lfs install -``` - -```bash -Updated git hooks. -Git LFS initialized. -``` - -Une fois que c'est fait, la première étape consiste à cloner votre dépôt de modèles : - -```bash -git clone https://huggingface.co// -``` - -Mon nom d'utilisateur est `lysandre` et j'ai utilisé le nom de modèle `dummy`, donc pour moi la commande ressemble à ce qui suit : - -``` -git clone https://huggingface.co/lysandre/dummy -``` - -J'ai maintenant un dossier nommé *dummy* dans mon répertoire de travail. Je peux `cd` dans ce dossier et jeter un coup d'oeil à son contenu : - -```bash -cd dummy && ls -``` - -```bash -README.md -``` - -Si vous venez de créer votre dépôt en utilisant la méthode `create_repo` du *Hub*, ce dossier devrait seulement contenir un fichier caché `.gitattributes`. Si vous avez suivi les instructions de la section précédente pour créer un dépôt en utilisant l'interface web, le dossier devrait contenir un seul fichier *README.md* à côté du fichier caché `.gitattributes`, comme indiqué ici. - -L'ajout d'un fichier de taille normale, comme un fichier de configuration, un fichier de vocabulaire, ou tout autre fichier de moins de quelques mégaoctets, est fait exactement comme on le ferait dans n'importe quel système basé sur git. Cependant, les fichiers plus volumineux doivent être enregistrés via git-lfs afin de les pousser vers *huggingface.co*. - -Revenons un peu à Python pour générer un modèle et un *tokenizer* que nous souhaitons « commiter » dans notre dépôt fictif : - -{#if fw === 'pt'} -```py -from transformers import AutoModelForMaskedLM, AutoTokenizer - -checkpoint = "camembert-base" - -model = AutoModelForMaskedLM.from_pretrained(checkpoint) -tokenizer = AutoTokenizer.from_pretrained(checkpoint) - -# Faites ce que vous voulez avec le modèle, entraînez-le, finetunez-le... - -model.save_pretrained("") -tokenizer.save_pretrained("") -``` -{:else} -```py -from transformers import TFAutoModelForMaskedLM, AutoTokenizer - -checkpoint = "camembert-base" - -model = TFAutoModelForMaskedLM.from_pretrained(checkpoint) -tokenizer = AutoTokenizer.from_pretrained(checkpoint) - -# Faites ce que vous voulez avec le modèle, entraînez-le, finetunez-le... - -model.save_pretrained("") -tokenizer.save_pretrained("") -``` -{/if} - -Maintenant que nous avons sauvegardé quelques artefacts de modèle et de *tokenizer*, regardons à nouveau le dossier *dummy* : - -```bash -ls -``` - -{#if fw === 'pt'} -```bash -config.json pytorch_model.bin README.md sentencepiece.bpe.model special_tokens_map.json tokenizer_config.json tokenizer.json -``` - -Si vous regardez la taille des fichiers (par exemple, avec `ls -lh`), vous devriez voir que le fichier d'état du modèle (*pytorch_model.bin*) est la seule exception, avec plus de 400 Mo. - -{:else} -```bash -config.json README.md sentencepiece.bpe.model special_tokens_map.json tf_model.h5 tokenizer_config.json tokenizer.json -``` - -Si vous regardez la taille des fichiers (par exemple, avec `ls -lh`), vous devriez voir que le fichier dict de l'état du modèle (*t5_model.h5*) est la seule aberration, avec plus de 400 Mo. - -{/if} - - -✏️ Lors de la création du dépôt à partir de l'interface web, le fichier .gitattributes est automatiquement configuré pour considérer les fichiers avec certaines extensions, comme .bin et .h5, comme des fichiers volumineux, et git-lfs les suivra sans aucune configuration nécessaire de votre part. - - -Nous pouvons maintenant aller de l'avant et procéder comme nous le ferions habituellement avec des dépôts Git traditionnels. Nous pouvons ajouter tous les fichiers à l'environnement Git en utilisant la commande `git add` : - -```bash -git add . -``` - -Nous pouvons alors jeter un coup d'œil aux fichiers : - -```bash -git status -``` - -{#if fw === 'pt'} -```bash -On branch main -Your branch is up to date with 'origin/main'. - -Changes to be committed: - (use "git restore --staged ..." to unstage) - modified: .gitattributes - new file: config.json - new file: pytorch_model.bin - new file: sentencepiece.bpe.model - new file: special_tokens_map.json - new file: tokenizer.json - new file: tokenizer_config.json -``` -{:else} -```bash -On branch main -Your branch is up to date with 'origin/main'. - -Changes to be committed: - (use "git restore --staged ..." to unstage) - modified: .gitattributes - new file: config.json - new file: sentencepiece.bpe.model - new file: special_tokens_map.json - new file: tf_model.h5 - new file: tokenizer.json - new file: tokenizer_config.json -``` -{/if} - -De même, nous pouvons nous assurer que git-lfs suit les bons fichiers en utilisant sa commande `status` : - -```bash -git lfs status -``` - -{#if fw === 'pt'} -```bash -On branch main -Objects to be pushed to origin/main: - - -Objects to be committed: - - config.json (Git: bc20ff2) - pytorch_model.bin (LFS: 35686c2) - sentencepiece.bpe.model (LFS: 988bc5a) - special_tokens_map.json (Git: cb23931) - tokenizer.json (Git: 851ff3e) - tokenizer_config.json (Git: f0f7783) - -Objects not staged for commit: - - -``` - -Nous pouvons voir que tous les fichiers ont `Git` comme gestionnaire, sauf *pytorch_model.bin* et *sentencepiece.bpe.model*, qui ont `LFS`. Super ! - -{:else} -```bash -On branch main -Objects to be pushed to origin/main: - - -Objects to be committed: - - config.json (Git: bc20ff2) - sentencepiece.bpe.model (LFS: 988bc5a) - special_tokens_map.json (Git: cb23931) - tf_model.h5 (LFS: 86fce29) - tokenizer.json (Git: 851ff3e) - tokenizer_config.json (Git: f0f7783) - -Objects not staged for commit: - - -``` - -Nous pouvons voir que tous les fichiers ont `Git` comme gestionnaire, sauf *t5_model.h5* qui a `LFS`. Super ! - -{/if} - -Passons aux étapes finales, *committing* et *pushing* vers le dépôt distant *huggingface.co* : - -```bash -git commit -m "First model version" -``` - -{#if fw === 'pt'} -```bash -[main b08aab1] First model version - 7 files changed, 29027 insertions(+) - 6 files changed, 36 insertions(+) - create mode 100644 config.json - create mode 100644 pytorch_model.bin - create mode 100644 sentencepiece.bpe.model - create mode 100644 special_tokens_map.json - create mode 100644 tokenizer.json - create mode 100644 tokenizer_config.json -``` -{:else} -```bash -[main b08aab1] First model version - 6 files changed, 36 insertions(+) - create mode 100644 config.json - create mode 100644 sentencepiece.bpe.model - create mode 100644 special_tokens_map.json - create mode 100644 tf_model.h5 - create mode 100644 tokenizer.json - create mode 100644 tokenizer_config.json -``` -{/if} - -Le chargement peut prendre un peu de temps, en fonction de la vitesse de votre connexion Internet et de la taille de vos fichiers : - -```bash -git push -``` - -```bash -Uploading LFS objects: 100% (1/1), 433 MB | 1.3 MB/s, done. -Enumerating objects: 11, done. -Counting objects: 100% (11/11), done. -Delta compression using up to 12 threads -Compressing objects: 100% (9/9), done. -Writing objects: 100% (9/9), 288.27 KiB | 6.27 MiB/s, done. -Total 9 (delta 1), reused 0 (delta 0), pack-reused 0 -To https://huggingface.co/lysandre/dummy - 891b41d..b08aab1 main -> main -``` - -{#if fw === 'pt'} -Si nous jetons un coup d'œil au dépôt du modèle, lorsque cette opération est terminée, nous pouvons voir tous les fichiers récemment ajoutés : - -
-The 'Files and versions' tab now contains all the recently uploaded files. -
- -L'interface utilisateur vous permet d'explorer les fichiers du modèle et les *commits* et de voir la différence introduite par chaque *commit* : - -
-The diff introduced by the recent commit. -
-{:else} -Si nous jetons un coup d'œil au dépôt du modèle, lorsque cette opération est terminée, nous pouvons voir tous les fichiers récemment ajoutés : - -
-The 'Files and versions' tab now contains all the recently uploaded files. -
- -L'interface utilisateur vous permet d'explorer les fichiers du modèle et les *commits* et de voir la différence introduite par chaque *commit* : - -
-The diff introduced by the recent commit. -
-{/if} + + +# Partage de modèles pré-entraînés + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Dans les étapes ci-dessous, nous allons examiner les moyens les plus simples de partager des modèles pré-entraînés sur le 🤗 *Hub*. Il existe des outils et des services disponibles qui permettent de simplifier le partage et la mise à jour des modèles directement sur le *Hub*, que nous allons explorer ci-dessous. + + + +Nous encourageons tous les utilisateurs qui entraînent des modèles à contribuer en les partageant avec la communauté. Le partage des modèles, même s'ils ont été entraînés sur des jeux de données très spécifiques, aidera les autres, en leur faisant gagner du temps, des ressources de calcul et en leur donnant accès à des artefacts entraînés utiles. À votre tour, vous pourrez bénéficier du travail effectué par les autres ! + +Il y a trois façons de créer de nouveaux dépôts de modèles : + +- en utilisant l'API `push_to_hub`, +- en utilisant la bibliothèque Python `huggingface_hub`, +- en utilisant l'interface web. + +Une fois que vous avez créé un dépôt, vous pouvez y charger des fichiers via git et git-lfs. Nous allons vous guider dans la création de dépôts de modèles et le téléchargement de fichiers dans les sections suivantes. + + +## Utilisation de l'API `push_to_hub` + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +La façon la plus simple de télécharger des fichiers vers le *Hub* est d'utiliser l'API `push_to_hub`. + +Avant d'aller plus loin, vous devrez générer un jeton d'authentification afin que l'API `huggingface_hub` sache qui vous êtes et à quels espaces de noms vous avez accès en écriture. Assurez-vous que vous êtes dans un environnement où vous avez installé `transformers` (voir la [Configuration](/course/fr/chapter0)). Si vous êtes dans un *notebook*, vous pouvez utiliser la fonction suivante pour vous connecter : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Dans un terminal, vous pouvez exécuter : + +```bash +huggingface-cli login +``` + +Dans les deux cas, vous serez invité à saisir votre nom d'utilisateur et votre mot de passe, qui sont les mêmes que ceux que vous utilisez pour vous connecter au *Hub*. Si vous n'avez pas encore de profil pour le Hub, vous devez en créer un [ici](https://huggingface.co/join). + +Super ! Votre jeton d'authentification est maintenant stocké dans votre dossier de cache. Créons quelques dépôts ! + +{#if fw === 'pt'} + +Si vous avez joué avec l'API `Trainer` pour entraîner un modèle, le moyen le plus simple de le télécharger sur le *Hub* est de définir `push_to_hub=True` lorsque vous définissez vos `TrainingArguments` : + +```py +from transformers import TrainingArguments + +training_args = TrainingArguments( + "bert-finetuned-mrpc", save_strategy="epoch", push_to_hub=True +) +``` + +Lorsque vous appelez `trainer.train()`, le `Trainer` téléchargera alors votre modèle vers le *Hub* à chaque fois qu'il sera sauvegardé (ici à chaque époque) dans un dépôt dans votre espace personnel. Ce dépôt sera nommé comme le répertoire de sortie que vous avez choisi (ici `bert-finetuned-mrpc`) mais vous pouvez choisir un nom différent avec `hub_model_id = "a_different_name"`. + +Pour télécharger votre modèle vers une organisation dont vous êtes membre, passez-le simplement avec `hub_model_id = "my_organization/my_repo_name"`. + +Une fois que votre entraînement est terminé, vous devriez faire un dernier `trainer.push_to_hub()` pour télécharger la dernière version de votre modèle. Cela générera également une carte pour le modèle avec toutes les métadonnées pertinentes, rapportant les hyperparamètres utilisés et les résultats d'évaluation ! Voici un exemple du contenu que vous pourriez trouver dans une telle carte de modèle : +
+ An example of an auto-generated model card. +
+ +{:else} + +Si vous utilisez Keras pour entraîner votre modèle, le moyen le plus simple de le télécharger sur le *Hub* est de passer un `PushToHubCallback` lorsque vous appelez `model.fit()` : + +```py +from transformers import PushToHubCallback + +callback = PushToHubCallback( + "bert-finetuned-mrpc", save_strategy="epoch", tokenizer=tokenizer +) +``` + +Ensuite, vous devez ajouter `callbacks=[callback]` dans votre appel à `model.fit()`. Le *callback* téléchargera alors votre modèle vers le *Hub* à chaque fois qu'il sera sauvegardé (ici à chaque époque) dans un dépôt dans votre espace de noms. Ce dépôt sera nommé comme le répertoire de sortie que vous avez choisi (ici `bert-finetuned-mrpc`) mais vous pouvez choisir un nom différent avec `hub_model_id = "a_different_name"`. + +Pour télécharger votre modèle dans une organisation dont vous êtes membre, passez-le simplement avec `hub_model_id = "my_organization/my_repo_name"`. + +{/if} + +A un niveau inférieur, l'accès au *Hub* peut être fait directement sur les modèles, les *tokenizers* et les objets de configuration via leur méthode `push_to_hub()`. Cette méthode s'occupe à la fois de la création du dépôt et de l'envoi les fichiers du modèle et du *tokenizer* directement dans le dépôt. Aucune manipulation manuelle n'est nécessaire, contrairement à l'API que nous verrons plus loin. + +Pour avoir une idée de son fonctionnement, commençons par initialiser un modèle et un *tokenizer* : + +{#if fw === 'pt'} +```py +from transformers import AutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = AutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` +{:else} +```py +from transformers import TFAutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = TFAutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` +{/if} + +Vous êtes libre de faire ce que vous voulez avec ces objets : ajouter des *tokens* au *tokenizer*, entraîner le modèle, le *finetuner*. Une fois que vous êtes satisfait du modèle, des poids et du *tokenizer* obtenus, vous pouvez utiliser la méthode `push_to_hub()` directement disponible sur l'objet `model` : + +```py +model.push_to_hub("dummy-model") +``` + +Cela va créer le nouveau dépôt `dummy-model` dans votre profil et le remplir avec les fichiers du modèle. +Faites la même chose avec le *tokenizer*, de sorte que tous les fichiers sont maintenant disponibles dans ce dépôt : + +```py +tokenizer.push_to_hub("dummy-model") +``` + +Si vous appartenez à une organisation, il suffit de spécifier l'argument `organization` pour télécharger dans l'espace de cette organisation : + +```py +tokenizer.push_to_hub("dummy-model", organization="huggingface") +``` + +Si vous souhaitez utiliser un jeton Hugging Face spécifique, vous pouvez également le spécifier à la méthode `push_to_hub()` : + +```py +tokenizer.push_to_hub("dummy-model", organization="huggingface", use_auth_token="") +``` + +Maintenant, dirigez-vous sur *Hub* pour trouver votre modèle nouvellement téléchargé : *https://huggingface.co/user-or-organization/dummy-model*. + +Cliquez sur l'onglet « Fichiers et versions » et vous devriez voir les fichiers visibles dans la capture d'écran suivante : + +{#if fw === 'pt'} +
+Dummy model containing both the tokenizer and model files. +
+{:else} +
+Dummy model containing both the tokenizer and model files. +
+{/if} + + + +✏️ **Essayez** Prenez le modèle et le *tokenizer* associés au *checkpoint* `bert-base-cased` et téléchargez-les vers un dépôt dans votre espace en utilisant la méthode `push_to_hub()`. Vérifiez que le dépôt apparaît correctement sur votre page avant de le supprimer. + + + +Comme vous l'avez vu, la méthode `push_to_hub()` accepte plusieurs arguments, ce qui permet de télécharger vers un dépôt ou un espace d'organisation spécifique, ou d'utiliser un jeton d'API différent. Nous vous recommandons de jeter un coup d'œil à la spécification de la méthode disponible directement dans la documentation de [🤗 *Transformers*](https://huggingface.co/transformers/model_sharing.html) pour avoir une idée de ce qui est possible. + +La méthode `push_to_hub()` est soutenue par le *package* Python [`huggingface_hub`](https://github.com/huggingface/huggingface_hub), qui offre une API directe au *Hub*. C'est intégré à 🤗 *Transformers* et à plusieurs autres bibliothèques d'apprentissage automatique, comme [`allenlp`](https://github.com/allenai/allennlp). Bien que nous nous concentrions sur l'intégration via 🤗 *Transformers* dans ce chapitre, son intégration dans votre propre code ou bibliothèque est simple. + +Passez à la dernière section pour voir comment télécharger des fichiers dans votre dépôt nouvellement créé ! + +## Utilisation de la bibliothèque Python `huggingface_hub` + +La bibliothèque Python `huggingface_hub` est un *package* qui offre un ensemble d'outils pour les hubs des modèles et des jeux de données. Elle fournit des méthodes et des classes simples pour des tâches courantes telles qu'obtenir et gérer des informations à propos des dépôts sur le *Hub*. Elle fournit des APIs simples qui fonctionnent au-dessus de git pour gérer le contenu de ces dépôts et pour intégrer le *Hub* dans vos projets et bibliothèques. + +De la même manière que pour l'utilisation de l'API `push_to_hub`, vous devrez avoir votre jeton d'API enregistré dans votre cache. Pour ce faire, vous devrez utiliser la commande `login` de la CLI, comme mentionné dans la section précédente (encore une fois, assurez-vous de faire précéder ces commandes du caractère `!` si vous les exécutez dans Google Colab) : + +```bash +huggingface-cli login +``` + +Le *package* `huggingface_hub` offre plusieurs méthodes et classes qui sont utiles pour notre objectif. Tout d'abord, il y a quelques méthodes pour gérer la création, la suppression des dépôts, et autres : + +```python no-format +from huggingface_hub import ( + # Gestion des utilisateurs + login, + logout, + whoami, + + # Création et gestion du dépôt + create_repo, + delete_repo, + update_repo_visibility, + + # Et quelques méthodes pour récupérer/changer des informations sur le contenu + list_models, + list_datasets, + list_metrics, + list_repo_files, + upload_file, + delete_file, +) +``` + + +De plus, elle offre la très puissante classe `Repository` pour gérer un dépôt local. Nous allons explorer ces méthodes et cette classe dans les prochaines sections pour comprendre comment les exploiter. + +La méthode `create_repo` peut être utilisée pour créer un nouveau dépôt sur le *Hub* : + +```py +from huggingface_hub import create_repo + +create_repo("dummy-model") +``` + +Ceci créera le dépôt `dummy-model` dans votre espace. Si vous le souhaitez, vous pouvez spécifier à quelle organisation le dépôt doit appartenir en utilisant l'argument `organization` : + +```py +from huggingface_hub import create_repo + +create_repo("dummy-model", organization="huggingface") +``` + +Cela créera le dépôt `dummy-model` dans l'espace de nom `huggingface`, en supposant que vous appartenez à cette organisation. +D'autres arguments qui peuvent être utiles sont : + +- `private`, afin de spécifier si le dépôt doit être visible des autres ou non, +- `token`, si vous voulez remplacer le jeton stocké dans votre cache par un jeton donné, +- `repo_type`, si vous souhaitez créer un `dataset` ou un `space` au lieu d'un modèle. Les valeurs acceptées sont `"dataset"` et `"space"`. + +Une fois que le dépôt est créé, nous devons y ajouter des fichiers ! Passez à la section suivante pour voir les trois façons dont cela peut être géré. + + +## Utilisation de l'interface web + +L'interface web offre des outils pour gérer les dépôts directement dans le *Hub*. En utilisant l'interface, vous pouvez facilement créer des dépôts, ajouter des fichiers (même de grande taille !), explorer des modèles, visualiser les différences, et bien plus encore. + +Pour créer un nouveau dépôt, visitez [huggingface.co/new](https://huggingface.co/new) : + +
+Page showcasing the model used for the creation of a new model repository. +
+ +Tout d'abord, indiquez le propriétaire du dépôt : il peut s'agir de vous ou de l'une des organisations auxquelles vous êtes affilié. Si vous choisissez une organisation, le modèle sera présenté sur la page de l'organisation et chaque membre de l'organisation aura la possibilité de contribuer au dépôt. + +Ensuite, saisissez le nom de votre modèle. Ce sera également le nom du dépôt. Enfin, vous pouvez préciser si vous souhaitez que votre modèle soit public ou privé. Les modèles privés sont cachés de la vue du public. + +Après avoir créé votre dépôt de modèles, vous devriez voir une page comme celle-ci : + +
+An empty model page after creating a new repository. +
+ +C'est là que votre modèle sera hébergé. Pour commencer à le remplir, vous pouvez ajouter un fichier README directement depuis l'interface web. + +
+The README file showing the Markdown capabilities. +
+ +Le fichier README est en Markdown. N'hésitez pas à vous lâcher avec lui ! La troisième partie de ce chapitre est consacrée à la construction d'une carte de modèle. Celles-ci sont d'une importance capitale pour valoriser votre modèle, car c'est par elles que vous indiquez aux autres ce qu'il peut faire. + +Si vous regardez l'onglet « *Files and versions* », vous verrez qu'il n'y a pas encore beaucoup de fichiers : juste le *README.md* que vous venez de créer et le fichier *.gitattributes* qui garde la trace des gros fichiers. + +
+The 'Files and versions' tab only shows the .gitattributes and README.md files. +
+ +Nous allons maintenant voir comment ajouter de nouveaux fichiers. + +## Téléchargement des fichiers du modèle + +Le système de gestion des fichiers sur le *Hub* est basé sur git pour les fichiers ordinaires et git-lfs (qui signifie [Git Large File Storage](https://git-lfs.github.com/)) pour les fichiers plus importants. + +Dans la section suivante, nous passons en revue trois façons différentes de télécharger des fichiers sur le *Hub* : par `huggingface_hub` et par des commandes git. + +### L'approche `upload_file' + +L'utilisation de `upload_file` ne nécessite pas que git et git-lfs soient installés sur votre système. Il pousse les fichiers directement vers le 🤗 *Hub* en utilisant des requêtes HTTP POST. Une limitation de cette approche est qu'elle ne gère pas les fichiers dont la taille est supérieure à 5 Go. +Si vos fichiers ont une taille supérieure à 5 Go, veuillez suivre les deux autres méthodes détaillées ci-dessous. + +L'API peut être utilisée comme suit : + +```py +from huggingface_hub import upload_file + +upload_file( + "/config.json", + path_in_repo="config.json", + repo_id="/dummy-model", +) +``` + +Ceci téléchargera le fichier `config.json` disponible à `` à la racine du dépôt en tant que `config.json`, vers le dépôt `dummy-model`. +D'autres arguments qui peuvent être utiles sont : + +- `token`, si vous souhaitez remplacer le jeton stocké dans votre cache par un jeton donné, +- `repo_type`, si vous souhaitez télécharger vers un `dataset` ou un `space` au lieu d'un modèle. Les valeurs acceptées sont `"dataset"` et `"space"`. + + +### La classe `Repository` + +La classe `Repository` gère un dépôt local d'une manière similaire à git. Elle abstrait la plupart des problèmes que l'on peut rencontrer avec git pour fournir toutes les fonctionnalités dont nous avons besoin. + +L'utilisation de cette classe nécessite l'installation de git et de git-lfs, donc assurez-vous que git-lfs est installé (voir [ici](https://git-lfs.github.com/) pour les instructions d'installation) et configuré avant de commencer. + +Afin de commencer à jouer avec le dépôt que nous venons de créer, nous pouvons commencer par l'initialiser dans un dossier local en clonant le dépôt distant : + +```py +from huggingface_hub import Repository + +repo = Repository("", clone_from="/dummy-model") +``` + +Cela a créé le dossier `` dans notre répertoire de travail. Ce dossier ne contient que le fichier `.gitattributes` car c'est le seul fichier créé lors de l'instanciation du dépôt par `create_repo`. + +A partir de maintenant, nous pouvons utiliser plusieurs des méthodes traditionnelles de git : + +```py +repo.git_pull() +repo.git_add() +repo.git_commit() +repo.git_push() +repo.git_tag() +``` + +Et d'autres encore ! Nous vous recommandons de jeter un coup d’œil à la documentation de `Repository` disponible [ici](https://github.com/huggingface/huggingface_hub/tree/main/src/huggingface_hub#advanced-programmatic-repository-management) pour une vue d'ensemble de toutes les méthodes disponibles. + +Actuellement, nous avons un modèle et un *tokenizer* que nous voulons pousser vers le *Hub*. Nous avons réussi à cloner le dépôt, nous pouvons donc enregistrer les fichiers dans ce dépôt. + +Nous nous assurons d'abord que notre clone local est à jour en récupérant les dernières modifications : + +```py +repo.git_pull() +``` + +Une fois que c'est fait, nous sauvegardons les fichiers du modèle et du *tokenizer* : + +```py +model.save_pretrained("") +tokenizer.save_pretrained("") +``` + +Le `` contient maintenant tous les fichiers du modèle et du *tokenizer*. Nous suivons le flux de travail git habituel en ajoutant des fichiers à la zone de transit, en les validant et en les poussant vers le *Hub* : + +```py +repo.git_add() +repo.git_commit("Add model and tokenizer files") +repo.git_push() +``` + +Félicitations ! Vous venez de pousser vos premiers fichiers sur le *Hub*. + +### L'approche basée sur git + +Il s'agit de l'approche la plus basique pour télécharger des fichiers : nous le ferons directement avec git et git-lfs. La plupart des difficultés sont abstraites par les approches précédentes, mais il y a quelques réserves avec la méthode suivante, nous allons donc suivre un cas d'utilisation plus complexe. + +L'utilisation de cette classe nécessite l'installation de git et de git-lfs, donc assurez-vous d'avoir [git-lfs](https://git-lfs.github.com/) installé et configuré avant de commencer. + +Commencez par initialiser git-lfs : + +```bash +git lfs install +``` + +```bash +Updated git hooks. +Git LFS initialized. +``` + +Une fois que c'est fait, la première étape consiste à cloner votre dépôt de modèles : + +```bash +git clone https://huggingface.co// +``` + +Mon nom d'utilisateur est `lysandre` et j'ai utilisé le nom de modèle `dummy`, donc pour moi la commande ressemble à ce qui suit : + +``` +git clone https://huggingface.co/lysandre/dummy +``` + +J'ai maintenant un dossier nommé *dummy* dans mon répertoire de travail. Je peux `cd` dans ce dossier et jeter un coup d'oeil à son contenu : + +```bash +cd dummy && ls +``` + +```bash +README.md +``` + +Si vous venez de créer votre dépôt en utilisant la méthode `create_repo` du *Hub*, ce dossier devrait seulement contenir un fichier caché `.gitattributes`. Si vous avez suivi les instructions de la section précédente pour créer un dépôt en utilisant l'interface web, le dossier devrait contenir un seul fichier *README.md* à côté du fichier caché `.gitattributes`, comme indiqué ici. + +L'ajout d'un fichier de taille normale, comme un fichier de configuration, un fichier de vocabulaire, ou tout autre fichier de moins de quelques mégaoctets, est fait exactement comme on le ferait dans n'importe quel système basé sur git. Cependant, les fichiers plus volumineux doivent être enregistrés via git-lfs afin de les pousser vers *huggingface.co*. + +Revenons un peu à Python pour générer un modèle et un *tokenizer* que nous souhaitons « commiter » dans notre dépôt fictif : + +{#if fw === 'pt'} +```py +from transformers import AutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = AutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + +# Faites ce que vous voulez avec le modèle, entraînez-le, finetunez-le... + +model.save_pretrained("") +tokenizer.save_pretrained("") +``` +{:else} +```py +from transformers import TFAutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = TFAutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + +# Faites ce que vous voulez avec le modèle, entraînez-le, finetunez-le... + +model.save_pretrained("") +tokenizer.save_pretrained("") +``` +{/if} + +Maintenant que nous avons sauvegardé quelques artefacts de modèle et de *tokenizer*, regardons à nouveau le dossier *dummy* : + +```bash +ls +``` + +{#if fw === 'pt'} +```bash +config.json pytorch_model.bin README.md sentencepiece.bpe.model special_tokens_map.json tokenizer_config.json tokenizer.json +``` + +Si vous regardez la taille des fichiers (par exemple, avec `ls -lh`), vous devriez voir que le fichier d'état du modèle (*pytorch_model.bin*) est la seule exception, avec plus de 400 Mo. + +{:else} +```bash +config.json README.md sentencepiece.bpe.model special_tokens_map.json tf_model.h5 tokenizer_config.json tokenizer.json +``` + +Si vous regardez la taille des fichiers (par exemple, avec `ls -lh`), vous devriez voir que le fichier dict de l'état du modèle (*t5_model.h5*) est la seule aberration, avec plus de 400 Mo. + +{/if} + + +✏️ Lors de la création du dépôt à partir de l'interface web, le fichier .gitattributes est automatiquement configuré pour considérer les fichiers avec certaines extensions, comme .bin et .h5, comme des fichiers volumineux, et git-lfs les suivra sans aucune configuration nécessaire de votre part. + + +Nous pouvons maintenant aller de l'avant et procéder comme nous le ferions habituellement avec des dépôts Git traditionnels. Nous pouvons ajouter tous les fichiers à l'environnement Git en utilisant la commande `git add` : + +```bash +git add . +``` + +Nous pouvons alors jeter un coup d'œil aux fichiers : + +```bash +git status +``` + +{#if fw === 'pt'} +```bash +On branch main +Your branch is up to date with 'origin/main'. + +Changes to be committed: + (use "git restore --staged ..." to unstage) + modified: .gitattributes + new file: config.json + new file: pytorch_model.bin + new file: sentencepiece.bpe.model + new file: special_tokens_map.json + new file: tokenizer.json + new file: tokenizer_config.json +``` +{:else} +```bash +On branch main +Your branch is up to date with 'origin/main'. + +Changes to be committed: + (use "git restore --staged ..." to unstage) + modified: .gitattributes + new file: config.json + new file: sentencepiece.bpe.model + new file: special_tokens_map.json + new file: tf_model.h5 + new file: tokenizer.json + new file: tokenizer_config.json +``` +{/if} + +De même, nous pouvons nous assurer que git-lfs suit les bons fichiers en utilisant sa commande `status` : + +```bash +git lfs status +``` + +{#if fw === 'pt'} +```bash +On branch main +Objects to be pushed to origin/main: + + +Objects to be committed: + + config.json (Git: bc20ff2) + pytorch_model.bin (LFS: 35686c2) + sentencepiece.bpe.model (LFS: 988bc5a) + special_tokens_map.json (Git: cb23931) + tokenizer.json (Git: 851ff3e) + tokenizer_config.json (Git: f0f7783) + +Objects not staged for commit: + + +``` + +Nous pouvons voir que tous les fichiers ont `Git` comme gestionnaire, sauf *pytorch_model.bin* et *sentencepiece.bpe.model*, qui ont `LFS`. Super ! + +{:else} +```bash +On branch main +Objects to be pushed to origin/main: + + +Objects to be committed: + + config.json (Git: bc20ff2) + sentencepiece.bpe.model (LFS: 988bc5a) + special_tokens_map.json (Git: cb23931) + tf_model.h5 (LFS: 86fce29) + tokenizer.json (Git: 851ff3e) + tokenizer_config.json (Git: f0f7783) + +Objects not staged for commit: + + +``` + +Nous pouvons voir que tous les fichiers ont `Git` comme gestionnaire, sauf *t5_model.h5* qui a `LFS`. Super ! + +{/if} + +Passons aux étapes finales, *committing* et *pushing* vers le dépôt distant *huggingface.co* : + +```bash +git commit -m "First model version" +``` + +{#if fw === 'pt'} +```bash +[main b08aab1] First model version + 7 files changed, 29027 insertions(+) + 6 files changed, 36 insertions(+) + create mode 100644 config.json + create mode 100644 pytorch_model.bin + create mode 100644 sentencepiece.bpe.model + create mode 100644 special_tokens_map.json + create mode 100644 tokenizer.json + create mode 100644 tokenizer_config.json +``` +{:else} +```bash +[main b08aab1] First model version + 6 files changed, 36 insertions(+) + create mode 100644 config.json + create mode 100644 sentencepiece.bpe.model + create mode 100644 special_tokens_map.json + create mode 100644 tf_model.h5 + create mode 100644 tokenizer.json + create mode 100644 tokenizer_config.json +``` +{/if} + +Le chargement peut prendre un peu de temps, en fonction de la vitesse de votre connexion Internet et de la taille de vos fichiers : + +```bash +git push +``` + +```bash +Uploading LFS objects: 100% (1/1), 433 MB | 1.3 MB/s, done. +Enumerating objects: 11, done. +Counting objects: 100% (11/11), done. +Delta compression using up to 12 threads +Compressing objects: 100% (9/9), done. +Writing objects: 100% (9/9), 288.27 KiB | 6.27 MiB/s, done. +Total 9 (delta 1), reused 0 (delta 0), pack-reused 0 +To https://huggingface.co/lysandre/dummy + 891b41d..b08aab1 main -> main +``` + +{#if fw === 'pt'} +Si nous jetons un coup d'œil au dépôt du modèle, lorsque cette opération est terminée, nous pouvons voir tous les fichiers récemment ajoutés : + +
+The 'Files and versions' tab now contains all the recently uploaded files. +
+ +L'interface utilisateur vous permet d'explorer les fichiers du modèle et les *commits* et de voir la différence introduite par chaque *commit* : + +
+The diff introduced by the recent commit. +
+{:else} +Si nous jetons un coup d'œil au dépôt du modèle, lorsque cette opération est terminée, nous pouvons voir tous les fichiers récemment ajoutés : + +
+The 'Files and versions' tab now contains all the recently uploaded files. +
+ +L'interface utilisateur vous permet d'explorer les fichiers du modèle et les *commits* et de voir la différence introduite par chaque *commit* : + +
+The diff introduced by the recent commit. +
+{/if} diff --git a/chapters/fr/chapter4/4.mdx b/chapters/fr/chapter4/4.mdx index 4f2086aea..3bd79c348 100644 --- a/chapters/fr/chapter4/4.mdx +++ b/chapters/fr/chapter4/4.mdx @@ -1,84 +1,89 @@ -# Construire une carte de modèle - -La carte de modèle est un fichier qui est sans doute aussi important que les fichiers du modèle et du *tokenizer* dans un dépôt de modèles. Il s'agit de la définition centrale du modèle, qui garantit la réutilisation par les autres membres de la communauté, la reproductibilité des résultats, et une plateforme sur laquelle les autres membres peuvent construire leurs artefacts. - -Documenter le processus d'entraînement et d'évaluation aide les autres à comprendre ce qu'ils peuvent attendre d'un modèle. Fournir suffisamment d'informations concernant les données utilisées, les prétraitements et post-traitements effectués permet d'identifier et de comprendre les limites, les biais et les contextes dans lesquels le modèle est ou n'est pas utile. - -Par conséquent, la création d'une carte de modèle définissant clairement votre modèle est une étape très importante. Nous vous donnons ici quelques conseils qui vous aideront à le faire. La création de la fiche de modèle se fait par le biais du fichier *README.md* que vous avez vu précédemment, qui est un fichier Markdown. - -Le concept de carte de modèle provient d'une direction de recherche de Google, partagée pour la première fois dans l'article [« *Model Cards for Model Reporting* »](https://arxiv.org/abs/1810.03993) par Margaret Mitchell et al. De nombreuses informations contenues dans ce document sont basées sur cet article et nous vous recommandons d'y jeter un coup d'œil pour comprendre pourquoi les cartes de modèles sont si importantes dans un monde qui valorise la reproductibilité, la réutilisation et l'équité. - -La carte de modèle commence généralement par une très brève présentation de haut niveau de l'objet du modèle, suivie de détails supplémentaires dans les sections suivantes : - -- description du modèle -- utilisations et limites prévues -- comment utiliser le modèle -- limites et biais -- données d'entraînement -- procédure d'entraînement -- résultats de l'évaluation - -Voyons ce que chacune de ces sections doit contenir. - - -### Description du modèle - -La description du modèle fournit des détails de base sur le modèle. Cela inclut l'architecture, la version, s'il a été présenté dans un article, si une implémentation originale est disponible, l'auteur et des informations générales sur le modèle. Tout droit d'auteur doit être attribué ici. Des informations générales sur les procédures d'entraînement, les paramètres et les avertissements importants peuvent également être mentionnés dans cette section. - -### Utilisations et limitations prévues - -Vous décrivez ici les cas d'utilisation auxquels le modèle est destiné, y compris les langues, les domaines et les champs où il peut être appliqué. Cette section de la fiche de modèle peut également documenter les domaines qui sont connus pour être hors de portée du modèle, ou dans lesquels il est susceptible de fonctionner de manière sous-optimale. - -### Comment utiliser - -Cette section doit inclure des exemples d'utilisation du modèle. Cela peut montrer l'utilisation de la fonction `pipeline()`, l'utilisation des classes du modèle et du *tokenizer*, et tout autre code que vous pensez être utile. - -### Données d'entraînement - -Cette partie doit indiquer sur quel(s) jeu(x) de données le modèle a été entraîné. Une brève description du ou des jeux de données est également la bienvenue. - -### Procédure d'entraînement - -Dans cette section, vous devez décrire tous les aspects pertinents de l'entraînement qui sont utiles du point de vue de la reproductibilité. Cela inclut tout prétraitement et post-traitement effectué sur les données, ainsi que des détails tels que le nombre d'époques pour lesquelles le modèle a été entraîné, la taille du batch, le taux d'apprentissage, etc. - -### Variable et métriques - -Décrivez ici les métriques que vous utilisez pour l'évaluation et les différents facteurs que vous mesurez. En mentionnant la ou les métriques utilisées, sur quel jeu de données et quelle division du jeu de données, il est plus facile de comparer les performances de votre modèle à celles d'autres modèles. Les sections précédentes, telles que les utilisateurs prévus et les cas d'utilisation, doivent être prises en compte. - -### Résultats de l'évaluation - -Enfin, fournissez une indication de la performance du modèle sur l'ensemble de données d'évaluation. Si le modèle utilise un seuil de décision, indiquez le seuil de décision utilisé dans l'évaluation ou fournissez des détails sur l'évaluation à différents seuils pour les utilisations prévues. - - -## Exemple - -Voici quelques exemples de cartes de modèles bien conçues : - -- [`bert-base-case`](https://huggingface.co/bert-base-cased) -- [`gpt2`](https://huggingface.co/gpt2) -- [`distilbert`](https://huggingface.co/distilbert-base-uncased) - -D'autres exemples provenant de différentes organisations et entreprises sont disponibles [ici](https://github.com/huggingface/model_card/blob/master/examples.md). - -## Note - -Les fiches de modèle ne sont pas une exigence lors de la publication de modèles, et vous n'avez pas besoin d'inclure toutes les sections décrites ci-dessus lorsque vous en faites une. Cependant, une documentation explicite du modèle ne peut qu'être bénéfique aux futurs utilisateurs. Nous vous recommandons donc de remplir autant de sections que possible, au mieux de vos connaissances et de vos capacités. - -## Métadonnées de la carte de modèle - -Si vous avez exploré un peu le *Hub*, vous devriez avoir vu que certains modèles appartiennent à certaines catégories : vous pouvez les filtrer par tâches, langues, bibliothèques, et plus encore. Les catégories auxquelles appartient un modèle sont identifiées en fonction des métadonnées que vous ajoutez dans l'en-tête de la fiche du modèle. - -Par exemple, si vous regardez la fiche de modèle de [`camembert-base`](https://huggingface.co/camembert-base/blob/main/README.md), vous devriez voir les lignes suivantes dans l'en-tête de la fiche de modèle : - -``` ---- -language: fr -license: mit -datasets: -- oscar ---- -``` - -Ces métadonnées sont analysées par le *Hub* qui identifie alors ce modèle comme étant un modèle français, avec une licence MIT, entraîné sur le jeu de données Oscar. - -La [spécification complète de la carte du modèle](https://github.com/huggingface/hub-docs/blame/main/modelcard.md) permet de spécifier les langues, les licences, les balises, les jeux de données, les mesures, ainsi que les résultats d'évaluation obtenus par le modèle lors de l'entraînement. +# Construire une carte de modèle + + + +La carte de modèle est un fichier qui est sans doute aussi important que les fichiers du modèle et du *tokenizer* dans un dépôt de modèles. Il s'agit de la définition centrale du modèle, qui garantit la réutilisation par les autres membres de la communauté, la reproductibilité des résultats, et une plateforme sur laquelle les autres membres peuvent construire leurs artefacts. + +Documenter le processus d'entraînement et d'évaluation aide les autres à comprendre ce qu'ils peuvent attendre d'un modèle. Fournir suffisamment d'informations concernant les données utilisées, les prétraitements et post-traitements effectués permet d'identifier et de comprendre les limites, les biais et les contextes dans lesquels le modèle est ou n'est pas utile. + +Par conséquent, la création d'une carte de modèle définissant clairement votre modèle est une étape très importante. Nous vous donnons ici quelques conseils qui vous aideront à le faire. La création de la fiche de modèle se fait par le biais du fichier *README.md* que vous avez vu précédemment, qui est un fichier Markdown. + +Le concept de carte de modèle provient d'une direction de recherche de Google, partagée pour la première fois dans l'article [« *Model Cards for Model Reporting* »](https://arxiv.org/abs/1810.03993) par Margaret Mitchell et al. De nombreuses informations contenues dans ce document sont basées sur cet article et nous vous recommandons d'y jeter un coup d'œil pour comprendre pourquoi les cartes de modèles sont si importantes dans un monde qui valorise la reproductibilité, la réutilisation et l'équité. + +La carte de modèle commence généralement par une très brève présentation de haut niveau de l'objet du modèle, suivie de détails supplémentaires dans les sections suivantes : + +- description du modèle +- utilisations et limites prévues +- comment utiliser le modèle +- limites et biais +- données d'entraînement +- procédure d'entraînement +- résultats de l'évaluation + +Voyons ce que chacune de ces sections doit contenir. + + +### Description du modèle + +La description du modèle fournit des détails de base sur le modèle. Cela inclut l'architecture, la version, s'il a été présenté dans un article, si une implémentation originale est disponible, l'auteur et des informations générales sur le modèle. Tout droit d'auteur doit être attribué ici. Des informations générales sur les procédures d'entraînement, les paramètres et les avertissements importants peuvent également être mentionnés dans cette section. + +### Utilisations et limitations prévues + +Vous décrivez ici les cas d'utilisation auxquels le modèle est destiné, y compris les langues, les domaines et les champs où il peut être appliqué. Cette section de la fiche de modèle peut également documenter les domaines qui sont connus pour être hors de portée du modèle, ou dans lesquels il est susceptible de fonctionner de manière sous-optimale. + +### Comment utiliser + +Cette section doit inclure des exemples d'utilisation du modèle. Cela peut montrer l'utilisation de la fonction `pipeline()`, l'utilisation des classes du modèle et du *tokenizer*, et tout autre code que vous pensez être utile. + +### Données d'entraînement + +Cette partie doit indiquer sur quel(s) jeu(x) de données le modèle a été entraîné. Une brève description du ou des jeux de données est également la bienvenue. + +### Procédure d'entraînement + +Dans cette section, vous devez décrire tous les aspects pertinents de l'entraînement qui sont utiles du point de vue de la reproductibilité. Cela inclut tout prétraitement et post-traitement effectué sur les données, ainsi que des détails tels que le nombre d'époques pour lesquelles le modèle a été entraîné, la taille du batch, le taux d'apprentissage, etc. + +### Variable et métriques + +Décrivez ici les métriques que vous utilisez pour l'évaluation et les différents facteurs que vous mesurez. En mentionnant la ou les métriques utilisées, sur quel jeu de données et quelle division du jeu de données, il est plus facile de comparer les performances de votre modèle à celles d'autres modèles. Les sections précédentes, telles que les utilisateurs prévus et les cas d'utilisation, doivent être prises en compte. + +### Résultats de l'évaluation + +Enfin, fournissez une indication de la performance du modèle sur l'ensemble de données d'évaluation. Si le modèle utilise un seuil de décision, indiquez le seuil de décision utilisé dans l'évaluation ou fournissez des détails sur l'évaluation à différents seuils pour les utilisations prévues. + + +## Exemple + +Voici quelques exemples de cartes de modèles bien conçues : + +- [`bert-base-case`](https://huggingface.co/bert-base-cased) +- [`gpt2`](https://huggingface.co/gpt2) +- [`distilbert`](https://huggingface.co/distilbert-base-uncased) + +D'autres exemples provenant de différentes organisations et entreprises sont disponibles [ici](https://github.com/huggingface/model_card/blob/master/examples.md). + +## Note + +Les fiches de modèle ne sont pas une exigence lors de la publication de modèles, et vous n'avez pas besoin d'inclure toutes les sections décrites ci-dessus lorsque vous en faites une. Cependant, une documentation explicite du modèle ne peut qu'être bénéfique aux futurs utilisateurs. Nous vous recommandons donc de remplir autant de sections que possible, au mieux de vos connaissances et de vos capacités. + +## Métadonnées de la carte de modèle + +Si vous avez exploré un peu le *Hub*, vous devriez avoir vu que certains modèles appartiennent à certaines catégories : vous pouvez les filtrer par tâches, langues, bibliothèques, et plus encore. Les catégories auxquelles appartient un modèle sont identifiées en fonction des métadonnées que vous ajoutez dans l'en-tête de la fiche du modèle. + +Par exemple, si vous regardez la fiche de modèle de [`camembert-base`](https://huggingface.co/camembert-base/blob/main/README.md), vous devriez voir les lignes suivantes dans l'en-tête de la fiche de modèle : + +``` +--- +language: fr +license: mit +datasets: +- oscar +--- +``` + +Ces métadonnées sont analysées par le *Hub* qui identifie alors ce modèle comme étant un modèle français, avec une licence MIT, entraîné sur le jeu de données Oscar. + +La [spécification complète de la carte du modèle](https://github.com/huggingface/hub-docs/blame/main/modelcard.md) permet de spécifier les langues, les licences, les balises, les jeux de données, les mesures, ainsi que les résultats d'évaluation obtenus par le modèle lors de l'entraînement. diff --git a/chapters/fr/chapter4/5.mdx b/chapters/fr/chapter4/5.mdx index 4365a6733..cf0b3b764 100644 --- a/chapters/fr/chapter4/5.mdx +++ b/chapters/fr/chapter4/5.mdx @@ -1,7 +1,12 @@ -# Fin de la première partie du cours ! - -C'est la fin de la première partie du cours ! La partie 2 sera publiée le 15 novembre 2021 avec un grand événement communautaire, pour plus d'informations voir [ici](https://huggingface.co/blog/course-launch-event). - -Vous devriez maintenant être capable de *finetuner* un modèle pré-entraîné sur un problème de classification de texte (phrases simples ou paires de phrases) et de télécharger le résultat sur le *Hub*. Pour vous assurer que vous maîtrisez cette première section, vous devriez refaire ça sur un problème qui vous intéresse (et pas nécessairement en anglais si vous parlez une autre langue) ! Vous pouvez trouver de l'aide dans les [forums d'Hugging Face](https://discuss.huggingface.co/) et partager votre projet dans [ce sujet](https://discuss.huggingface.co/t/share-your-projects/6803) une fois que vous avez terminé. - +# Fin de la première partie du cours ! + + + +C'est la fin de la première partie du cours ! La partie 2 sera publiée le 15 novembre 2021 avec un grand événement communautaire, pour plus d'informations voir [ici](https://huggingface.co/blog/course-launch-event). + +Vous devriez maintenant être capable de *finetuner* un modèle pré-entraîné sur un problème de classification de texte (phrases simples ou paires de phrases) et de télécharger le résultat sur le *Hub*. Pour vous assurer que vous maîtrisez cette première section, vous devriez refaire ça sur un problème qui vous intéresse (et pas nécessairement en anglais si vous parlez une autre langue) ! Vous pouvez trouver de l'aide dans les [forums d'Hugging Face](https://discuss.huggingface.co/) et partager votre projet dans [ce sujet](https://discuss.huggingface.co/t/share-your-projects/6803) une fois que vous avez terminé. + Nous sommes impatients de voir ce que vous allez construire avec cet outil ! \ No newline at end of file diff --git a/chapters/fr/chapter4/6.mdx b/chapters/fr/chapter4/6.mdx index a180d67fa..d0220522e 100644 --- a/chapters/fr/chapter4/6.mdx +++ b/chapters/fr/chapter4/6.mdx @@ -1,223 +1,228 @@ - - - - -# Quiz de fin de chapitre - -Testons ce que vous avez appris dans ce chapitre ! - -### 1. A quoi sont limités les modèles du *Hub* ? - -Transformers.", - explain: "Si les modèles de la bibliothèque 🤗 Transformers sont pris en charge sur le Hub, ils ne sont pas les seuls !" - }, - { - text: "Tous les modèles avec une interface similaire à 🤗 Transformers.", - explain: "Aucune exigence d'interface n'est fixée lors du téléchargement de modèles vers le Hub." - }, - { - text: "Il n'y a pas de limites.", - explain: "Il n'y a pas de limites au téléchargement de modèles sur le Hub.", - correct: true - }, - { - text: "Des modèles qui sont d'une certaine manière liés au NLP.", - explain: "Aucune exigence n'est fixée concernant le domaine d'application !" - } - ]} -/> - -### 2. Comment pouvez-vous gérer les modèles sur le *Hub* ? - -Hub sont de simples dépôts Git exploitant git-lfs pour les fichiers volumineux.", - correct: true - } - ]} -/> - -### 3. Que pouvez-vous faire en utilisant l'interface web du *Hub* ? - -Forker » un dépôt existant.", - explain: "« Forker » un dépôt n'est pas possible sur le Hub." - }, - { - text: "Créer un nouveau dépôt de modèles.", - explain: "Ce n'est pas tout ce que vous pouvez faire, cependant.", - correct: true - }, - { - text: "Gérer et modifier des fichiers.", - explain: "Ce n'est pas la seule bonne réponse, cependant.", - correct: true - }, - { - text: "Télécharger des fichiers.", - explain: "Mais ce n'est pas tout.", - correct: true - }, - { - text: "Voir les différences entre les versions.", - explain: "Ce n'est pas tout ce que vous pouvez faire.", - correct: true - } - ]} -/> - -### 4. Qu'est-ce qu'une carte de modèle ? - -tokenizer.", - explain: "Il s'agit bien d'une description du modèle, mais c'est un élément important : s'il est incomplet ou absent, l'utilité du modèle est considérablement réduite." - }, - { - text: "Un moyen d'assurer la reproductibilité, la réutilisation et l'équité..", - explain: "Le fait de partager les bonnes informations dans la fiche du modèle aidera les utilisateurs à tirer parti de votre modèle et à être conscients de ses limites et de ses biais.", - correct: true - }, - { - text: "Un fichier Python qui peut être exécuté pour récupérer des informations sur le modèle.", - explain: "Les cartes de modèle sont de simples fichiers Markdown." - } - ]} -/> - -### 5. Lesquels de ces objets de la bibliothèque 🤗 *Transformers* peuvent être directement partagés sur le Hub avec `push_to_hub()` ? - -{#if fw === 'pt'} -tokenizer", - explain: "Tous les tokenizers ont la méthode push_to_hub et l'utiliser poussera tous les fichiers du tokenizer (vocabulaire, architecture du tokenizer, etc.) vers un dépôt donné. Ce n'est pas la seule bonne réponse, cependant !", - correct: true - }, - { - text: "Une configuration de modèle", - explain: "Toutes les configurations de modèles ont la méthode push_to_hub et son utilisation les poussera vers un dépôt donné. Que pouvez-vous partager d'autre ?", - correct: true - }, - { - text: "Un modèle", - explain: "Tous les modèles ont la méthode push_to_hub qui le pushra ainsi que leurs fichiers de configuration, vers un dépôt donné. Ce n'est pas tout ce que vous pouvez partager, cependant.", - correct: true - }, - { - text: "Trainer", - explain: "Le Trainer implémente aussi la méthode push_to_hub. L'utiliser téléchargera le modèle, sa configuration, le tokenizer et une ébauche de carte de modèle vers un dépôt donné. Essayez une autre réponse !", - correct: true - } - ]} -/> -{:else} -tokenizer", - explain: "Toutes les configurations de modèles ont la méthode push_to_hub et son utilisation les poussera vers un dépôt donné. Que pouvez-vous partager d'autre ?", - correct: true - }, - { - text: "Une configuration de modèle", - explain: "Toutes les configurations de modèles ont la méthode push_to_hub et son utilisation les poussera vers un dépôt donné. Que pouvez-vous partager d'autre ?", - correct: true - }, - { - text: "Un modèle", - explain: "Tous les modèles ont la méthode push_to_hub qui le pushra ainsi que leurs fichiers de configuration, vers un dépôt donné. Ce n'est pas tout ce que vous pouvez partager, cependant.", - correct: true - }, - { - text: "Tout ce qui précède avec un callback dédié", - explain: "Le PushToHubCallback enverra régulièrement tous ces objets à un dépôt pendant l'entraînement.", - correct: true - } - ]} -/> -{/if} - -### 6. Quelle est la première étape lorsqu'on utilise la méthode `push_to_hub()` ou les outils CLI ? - -notebook.", - explain: "Cela affichera un widget pour vous permettre de vous authentifier.", - correct: true - }, - ]} -/> - -### 7. Vous utilisez un modèle et un *tokenizer*, comment pouvez-vous les télécharger sur le *Hub* ? - -tokenizer.", - explain: " ", - correct: true - }, - { - text: "Au sein du moteur d'exécution Python, en les enveloppant dans une balise huggingface_hub.", - explain: "Les modèles et les tokenizers bénéficient déjà de huggingface_hub : pas besoin d'emballage supplémentaire !" - }, - { - text: "En les sauvegardant sur le disque et en appelant transformers-cli upload-model.", - explain: "La commande upload-model n'existe pas." - } - ]} -/> - -### 8. Quelles opérations git pouvez-vous faire avec la classe `Repository` ? - -commit.", - explain: "La méthode git_commit() est là pour ça.", - correct: true - }, - { - text: "Un pull.", - explain: "C'est le but de la méthode git_pull().", - correct: true - }, - { - text: "Un push.", - explain: "La méthode git_push() fait ça.", - correct: true - }, - { - text: "Un merge.", - explain: "Cette opération ne sera jamais possible avec cette API." - } - ]} + + + + +# Quiz de fin de chapitre + + + +Testons ce que vous avez appris dans ce chapitre ! + +### 1. A quoi sont limités les modèles du *Hub* ? + +Transformers.", + explain: "Si les modèles de la bibliothèque 🤗 Transformers sont pris en charge sur le Hub, ils ne sont pas les seuls !" + }, + { + text: "Tous les modèles avec une interface similaire à 🤗 Transformers.", + explain: "Aucune exigence d'interface n'est fixée lors du téléchargement de modèles vers le Hub." + }, + { + text: "Il n'y a pas de limites.", + explain: "Il n'y a pas de limites au téléchargement de modèles sur le Hub.", + correct: true + }, + { + text: "Des modèles qui sont d'une certaine manière liés au NLP.", + explain: "Aucune exigence n'est fixée concernant le domaine d'application !" + } + ]} +/> + +### 2. Comment pouvez-vous gérer les modèles sur le *Hub* ? + +Hub sont de simples dépôts Git exploitant git-lfs pour les fichiers volumineux.", + correct: true + } + ]} +/> + +### 3. Que pouvez-vous faire en utilisant l'interface web du *Hub* ? + +Forker » un dépôt existant.", + explain: "« Forker » un dépôt n'est pas possible sur le Hub." + }, + { + text: "Créer un nouveau dépôt de modèles.", + explain: "Ce n'est pas tout ce que vous pouvez faire, cependant.", + correct: true + }, + { + text: "Gérer et modifier des fichiers.", + explain: "Ce n'est pas la seule bonne réponse, cependant.", + correct: true + }, + { + text: "Télécharger des fichiers.", + explain: "Mais ce n'est pas tout.", + correct: true + }, + { + text: "Voir les différences entre les versions.", + explain: "Ce n'est pas tout ce que vous pouvez faire.", + correct: true + } + ]} +/> + +### 4. Qu'est-ce qu'une carte de modèle ? + +tokenizer.", + explain: "Il s'agit bien d'une description du modèle, mais c'est un élément important : s'il est incomplet ou absent, l'utilité du modèle est considérablement réduite." + }, + { + text: "Un moyen d'assurer la reproductibilité, la réutilisation et l'équité..", + explain: "Le fait de partager les bonnes informations dans la fiche du modèle aidera les utilisateurs à tirer parti de votre modèle et à être conscients de ses limites et de ses biais.", + correct: true + }, + { + text: "Un fichier Python qui peut être exécuté pour récupérer des informations sur le modèle.", + explain: "Les cartes de modèle sont de simples fichiers Markdown." + } + ]} +/> + +### 5. Lesquels de ces objets de la bibliothèque 🤗 *Transformers* peuvent être directement partagés sur le Hub avec `push_to_hub()` ? + +{#if fw === 'pt'} +tokenizer", + explain: "Tous les tokenizers ont la méthode push_to_hub et l'utiliser poussera tous les fichiers du tokenizer (vocabulaire, architecture du tokenizer, etc.) vers un dépôt donné. Ce n'est pas la seule bonne réponse, cependant !", + correct: true + }, + { + text: "Une configuration de modèle", + explain: "Toutes les configurations de modèles ont la méthode push_to_hub et son utilisation les poussera vers un dépôt donné. Que pouvez-vous partager d'autre ?", + correct: true + }, + { + text: "Un modèle", + explain: "Tous les modèles ont la méthode push_to_hub qui le pushra ainsi que leurs fichiers de configuration, vers un dépôt donné. Ce n'est pas tout ce que vous pouvez partager, cependant.", + correct: true + }, + { + text: "Trainer", + explain: "Le Trainer implémente aussi la méthode push_to_hub. L'utiliser téléchargera le modèle, sa configuration, le tokenizer et une ébauche de carte de modèle vers un dépôt donné. Essayez une autre réponse !", + correct: true + } + ]} +/> +{:else} +tokenizer", + explain: "Toutes les configurations de modèles ont la méthode push_to_hub et son utilisation les poussera vers un dépôt donné. Que pouvez-vous partager d'autre ?", + correct: true + }, + { + text: "Une configuration de modèle", + explain: "Toutes les configurations de modèles ont la méthode push_to_hub et son utilisation les poussera vers un dépôt donné. Que pouvez-vous partager d'autre ?", + correct: true + }, + { + text: "Un modèle", + explain: "Tous les modèles ont la méthode push_to_hub qui le pushra ainsi que leurs fichiers de configuration, vers un dépôt donné. Ce n'est pas tout ce que vous pouvez partager, cependant.", + correct: true + }, + { + text: "Tout ce qui précède avec un callback dédié", + explain: "Le PushToHubCallback enverra régulièrement tous ces objets à un dépôt pendant l'entraînement.", + correct: true + } + ]} +/> +{/if} + +### 6. Quelle est la première étape lorsqu'on utilise la méthode `push_to_hub()` ou les outils CLI ? + +notebook.", + explain: "Cela affichera un widget pour vous permettre de vous authentifier.", + correct: true + }, + ]} +/> + +### 7. Vous utilisez un modèle et un *tokenizer*, comment pouvez-vous les télécharger sur le *Hub* ? + +tokenizer.", + explain: " ", + correct: true + }, + { + text: "Au sein du moteur d'exécution Python, en les enveloppant dans une balise huggingface_hub.", + explain: "Les modèles et les tokenizers bénéficient déjà de huggingface_hub : pas besoin d'emballage supplémentaire !" + }, + { + text: "En les sauvegardant sur le disque et en appelant transformers-cli upload-model.", + explain: "La commande upload-model n'existe pas." + } + ]} +/> + +### 8. Quelles opérations git pouvez-vous faire avec la classe `Repository` ? + +commit.", + explain: "La méthode git_commit() est là pour ça.", + correct: true + }, + { + text: "Un pull.", + explain: "C'est le but de la méthode git_pull().", + correct: true + }, + { + text: "Un push.", + explain: "La méthode git_push() fait ça.", + correct: true + }, + { + text: "Un merge.", + explain: "Cette opération ne sera jamais possible avec cette API." + } + ]} /> \ No newline at end of file diff --git a/chapters/fr/chapter5/1.mdx b/chapters/fr/chapter5/1.mdx index 2817734c9..3b1d9718f 100644 --- a/chapters/fr/chapter5/1.mdx +++ b/chapters/fr/chapter5/1.mdx @@ -1,17 +1,22 @@ -# Introduction - -Dans le [chapitre 3](/course/fr/chapter3) vous avez eu un premier aperçu de la bibliothèque 🤗 *Datasets* et des trois étapes principales pour *finetuner* un modèle : - -1. chargement d'un jeu de données à partir du *Hub* d’Hugging Face, -2. prétraitement des données avec `Dataset.map()`, -3. chargement et calcul des métriques. - -Mais ce n'est qu'effleurer la surface de ce que 🤗 *Datasets* peut faire ! Dans ce chapitre, nous allons plonger profondément dans cette bibliothèque. En cours de route, nous trouverons des réponses aux questions suivantes : - -* que faire lorsque votre jeu de données n'est pas sur le *Hub* ? -* comment découper et trancher un jeu de données ? (Et si on a _vraiment_ besoin d'utiliser Pandas ?) -* que faire lorsque votre jeu de données est énorme et va monopoliser la RAM de votre ordinateur portable ? -* qu'est-ce que c'est que le « *memory mapping* » et Apache Arrow ? -* comment créer votre propre jeu de données et le pousser sur le *Hub* ? - +# Introduction + + + +Dans le [chapitre 3](/course/fr/chapter3) vous avez eu un premier aperçu de la bibliothèque 🤗 *Datasets* et des trois étapes principales pour *finetuner* un modèle : + +1. chargement d'un jeu de données à partir du *Hub* d’Hugging Face, +2. prétraitement des données avec `Dataset.map()`, +3. chargement et calcul des métriques. + +Mais ce n'est qu'effleurer la surface de ce que 🤗 *Datasets* peut faire ! Dans ce chapitre, nous allons plonger profondément dans cette bibliothèque. En cours de route, nous trouverons des réponses aux questions suivantes : + +* que faire lorsque votre jeu de données n'est pas sur le *Hub* ? +* comment découper et trancher un jeu de données ? (Et si on a _vraiment_ besoin d'utiliser Pandas ?) +* que faire lorsque votre jeu de données est énorme et va monopoliser la RAM de votre ordinateur portable ? +* qu'est-ce que c'est que le « *memory mapping* » et Apache Arrow ? +* comment créer votre propre jeu de données et le pousser sur le *Hub* ? + Les techniques apprises dans ce chapitre vous prépareront aux tâches avancées de tokenisation du [chapitre 6](/course/fr/chapter6) et de *finetuning* du [chapitre 7](/course/fr/chapter7). Alors prenez un café et commençons ! \ No newline at end of file diff --git a/chapters/fr/chapter5/2.mdx b/chapters/fr/chapter5/2.mdx index f05424005..ee20d7800 100644 --- a/chapters/fr/chapter5/2.mdx +++ b/chapters/fr/chapter5/2.mdx @@ -1,167 +1,167 @@ -# Que faire si mon jeu de données n'est pas sur le Hub ? - - - -Vous savez comment utiliser le [*Hub*](https://huggingface.co/datasets) pour télécharger des jeux de données mais en pratique vous vous retrouverez souvent à travailler avec des données stockées sur votre ordinateur portable ou sur un serveur distant. Dans cette section, nous allons vous montrer comment 🤗 *Datasets* peut être utilisé pour charger des jeux de données qui ne sont pas disponibles sur le *Hub* d’Hugging Face. - - - -## Travailler avec des jeux de données locaux et distants - -🤗 *Datasets* fournit des scripts de chargement de jeux de données locaux et distants. La bibliothèque prend en charge plusieurs formats de données courants, tels que : - -| Format des données | Script de chargement | Exemple | -| :----------------: | :------------------: | :-----------------------------------------------------: | -| CSV & TSV | `csv` | `load_dataset("csv", data_files="my_file.csv")` | -| Fichiers texte | `text` | `load_dataset("text", data_files="my_file.txt")` | -| JSON & JSON Lines | `json` | `load_dataset("json", data_files="my_file.jsonl")` | -| DataFrames en Pickle | `pandas` | `load_dataset("pandas", data_files="my_dataframe.pkl")` | - -Comme indiqué dans le tableau, pour chaque format de données, nous avons juste besoin de spécifier le type de script de chargement dans la fonction `load_dataset()`, ainsi qu'un argument `data_files` qui spécifie le chemin vers un ou plusieurs fichiers. Commençons par charger un jeu de données à partir de fichiers locaux puis plus tard comment faire la même chose avec des fichiers distants. - -## Charger un jeu de données local - -Pour cet exemple, nous utilisons le jeu de données [SQuAD-it](https://github.com/crux82/squad-it/) qui est un grand jeu de données pour la tâche de *Question Awnswering* en italien. - -Les échantillons d’entraînement et de test sont hébergés sur GitHub, nous pouvons donc les télécharger avec une simple commande `wget` : - -```python -!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-train.json.gz -!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-test.json.gz -``` - -Cela télécharge deux fichiers compressés appelés *SQuAD_it-train.json.gz* et *SQuAD_it-test.json.gz* que nous pouvons décompresser avec la commande Linux `gzip` : - -```python -!gzip -dkv SQuAD_it-*.json.gz -``` - -```bash -SQuAD_it-test.json.gz: 87.4% -- replaced with SQuAD_it-test.json -SQuAD_it-train.json.gz: 82.2% -- replaced with SQuAD_it-train.json -``` - -Nous pouvons voir que les fichiers compressés ont été remplacés par _SQuAD_it-train.json_ et _SQuAD_it-text.json_, et que les données sont stockées au format JSON. - - - -✎ Si vous vous demandez pourquoi il y a un caractère `!` dans les commandes *shell* ci-dessus, c'est parce que nous les exécutons dans un *notebook* Jupyter. Supprimez simplement le préfixe si vous souhaitez télécharger et décompresser le jeu de données dans un terminal. - - - -Pour charger un fichier JSON avec la fonction `load_dataset()`, nous avons juste besoin de savoir si nous avons affaire à du JSON ordinaire (similaire à un dictionnaire imbriqué) ou à des lignes JSON (JSON séparé par des lignes). Comme de nombreux jeux de données de questions-réponses, SQuAD-it utilise le format imbriqué où tout le texte est stocké dans un champ `data`. Cela signifie que nous pouvons charger le jeu de données en spécifiant l'argument `field` comme suit : - -```py -from datasets import load_dataset - -squad_it_dataset = load_dataset("json", data_files="SQuAD_it-train.json", field="data") -``` - -Par défaut, le chargement de fichiers locaux crée un objet `DatasetDict` avec un échantillon `train`. Nous pouvons le voir en inspectant l'objet `squad_it_dataset` : - -```py -squad_it_dataset -``` - -```python out -DatasetDict({ - train: Dataset({ - features: ['title', 'paragraphs'], - num_rows: 442 - }) -}) -``` - -Cela nous montre le nombre de lignes et les noms de colonnes associés à l’échantillon d’entraînement. Nous pouvons afficher l'un des exemples en indexant l’échantillon `train` comme suit : - -```py -squad_it_dataset["train"][0] -``` - -```python out -{ - "title": "Terremoto del Sichuan del 2008", # Séisme du Sichuan 2008 - "paragraphs": [ - { - "context": "Il terremoto del Sichuan del 2008 o il terremoto...", - # Le tremblement de terre du Sichuan de 2008 ou le... - "qas": [ - { - "answers": [{"answer_start": 29, "text": "2008"}], - "id": "56cdca7862d2951400fa6826", - "question": "In quale anno si è verificato il terremoto nel Sichuan?", - # En quelle année le tremblement de terre du Sichuan a-t-il eu lieu ? - }, - ... - ], - }, - ... - ], -} -``` - -Super, nous avons chargé notre premier jeu de données local ! Mais ce que nous voulons vraiment, c'est inclure à la fois les échantillons `train` et `test` dans un seul objet `DatasetDict` afin que nous puissions appliquer les fonctions `Dataset.map()` sur les deux à la fois . Pour ce faire, nous pouvons fournir un dictionnaire à l'argument `data_files` qui associe chaque nom des échantillons à un fichier associé à cet échantillon : - -```py -data_files = {"train": "SQuAD_it-train.json", "test": "SQuAD_it-test.json"} -squad_it_dataset = load_dataset("json", data_files=data_files, field="data") -squad_it_dataset -``` - -```python out -DatasetDict({ - train: Dataset({ - features: ['title', 'paragraphs'], - num_rows: 442 - }) - test: Dataset({ - features: ['title', 'paragraphs'], - num_rows: 48 - }) -}) -``` - -C'est exactement ce que nous voulions. Désormais, nous pouvons appliquer diverses techniques de prétraitement pour nettoyer les données, tokeniser les avis, etc. - - - -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. - - - -Les scripts de chargement de 🤗 *Datasets* prennent en charge la décompression automatique des fichiers d'entrée. Nous aurions donc pu ignorer l'utilisation de `gzip` en pointant l'argument `data_files` directement sur les fichiers compressés : - -```py -data_files = {"train": "SQuAD_it-train.json.gz", "test": "SQuAD_it-test.json.gz"} -squad_it_dataset = load_dataset("json", data_files=data_files, field="data") -``` - -Cela peut être utile si vous ne souhaitez pas décompresser manuellement de nombreux fichiers GZIP. La décompression automatique s'applique également à d'autres formats courants tels que ZIP et TAR. Il vous suffit donc de pointer `data_files` vers les fichiers compressés et vous êtes prêt à partir ! - -Maintenant que vous savez comment charger des fichiers locaux sur votre ordinateur portable ou de bureau, examinons le chargement de fichiers distants. - -## Charger un jeu de données distant - -Si vous travaillez en tant que *data scientist* ou codeur dans une entreprise, il y a de fortes chances que les jeux de données que vous souhaitez analyser soient stockés sur un serveur distant. Heureusement, charger des fichiers distants est aussi simple que de charger des fichiers locaux ! Au lieu de fournir un chemin vers les fichiers locaux, nous pointons l'argument `data_files` de `load_dataset()` vers une ou plusieurs URL où les fichiers distants sont stockés. Par exemple, pour le jeu de données SQuAD-it hébergé sur GitHub, nous pouvons simplement faire pointer `data_files` vers les URL _SQuAD_it-*.json.gz_ comme suit : - -```py -url = "https://github.com/crux82/squad-it/raw/master/" -data_files = { - "train": url + "SQuAD_it-train.json.gz", - "test": url + "SQuAD_it-test.json.gz", -} -squad_it_dataset = load_dataset("json", data_files=data_files, field="data") -``` - -Cela renvoie le même objet `DatasetDict` obtenu ci-dessus mais nous évite de télécharger et de décompresser manuellement les fichiers _SQuAD_it-*.json.gz_. Ceci conclut notre incursion dans les différentes façons de charger des jeux de données qui ne sont pas hébergés sur le *Hub*. Maintenant que nous avons un jeu de données avec lequel jouer, mettons la main à la pâte avec diverses techniques de gestion des données ! - - - -✏️ **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). - - +# Que faire si mon jeu de données n'est pas sur le Hub ? + + + +Vous savez comment utiliser le [*Hub*](https://huggingface.co/datasets) pour télécharger des jeux de données mais en pratique vous vous retrouverez souvent à travailler avec des données stockées sur votre ordinateur portable ou sur un serveur distant. Dans cette section, nous allons vous montrer comment 🤗 *Datasets* peut être utilisé pour charger des jeux de données qui ne sont pas disponibles sur le *Hub* d’Hugging Face. + + + +## Travailler avec des jeux de données locaux et distants + +🤗 *Datasets* fournit des scripts de chargement de jeux de données locaux et distants. La bibliothèque prend en charge plusieurs formats de données courants, tels que : + +| Format des données | Script de chargement | Exemple | +| :----------------: | :------------------: | :-----------------------------------------------------: | +| CSV & TSV | `csv` | `load_dataset("csv", data_files="my_file.csv")` | +| Fichiers texte | `text` | `load_dataset("text", data_files="my_file.txt")` | +| JSON & JSON Lines | `json` | `load_dataset("json", data_files="my_file.jsonl")` | +| DataFrames en Pickle | `pandas` | `load_dataset("pandas", data_files="my_dataframe.pkl")` | + +Comme indiqué dans le tableau, pour chaque format de données, nous avons juste besoin de spécifier le type de script de chargement dans la fonction `load_dataset()`, ainsi qu'un argument `data_files` qui spécifie le chemin vers un ou plusieurs fichiers. Commençons par charger un jeu de données à partir de fichiers locaux puis plus tard comment faire la même chose avec des fichiers distants. + +## Charger un jeu de données local + +Pour cet exemple, nous utilisons le jeu de données [SQuAD-it](https://github.com/crux82/squad-it/) qui est un grand jeu de données pour la tâche de *Question Awnswering* en italien. + +Les échantillons d’entraînement et de test sont hébergés sur GitHub, nous pouvons donc les télécharger avec une simple commande `wget` : + +```python +!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-train.json.gz +!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-test.json.gz +``` + +Cela télécharge deux fichiers compressés appelés *SQuAD_it-train.json.gz* et *SQuAD_it-test.json.gz* que nous pouvons décompresser avec la commande Linux `gzip` : + +```python +!gzip -dkv SQuAD_it-*.json.gz +``` + +```bash +SQuAD_it-test.json.gz: 87.4% -- replaced with SQuAD_it-test.json +SQuAD_it-train.json.gz: 82.2% -- replaced with SQuAD_it-train.json +``` + +Nous pouvons voir que les fichiers compressés ont été remplacés par _SQuAD_it-train.json_ et _SQuAD_it-text.json_, et que les données sont stockées au format JSON. + + + +✎ Si vous vous demandez pourquoi il y a un caractère `!` dans les commandes *shell* ci-dessus, c'est parce que nous les exécutons dans un *notebook* Jupyter. Supprimez simplement le préfixe si vous souhaitez télécharger et décompresser le jeu de données dans un terminal. + + + +Pour charger un fichier JSON avec la fonction `load_dataset()`, nous avons juste besoin de savoir si nous avons affaire à du JSON ordinaire (similaire à un dictionnaire imbriqué) ou à des lignes JSON (JSON séparé par des lignes). Comme de nombreux jeux de données de questions-réponses, SQuAD-it utilise le format imbriqué où tout le texte est stocké dans un champ `data`. Cela signifie que nous pouvons charger le jeu de données en spécifiant l'argument `field` comme suit : + +```py +from datasets import load_dataset + +squad_it_dataset = load_dataset("json", data_files="SQuAD_it-train.json", field="data") +``` + +Par défaut, le chargement de fichiers locaux crée un objet `DatasetDict` avec un échantillon `train`. Nous pouvons le voir en inspectant l'objet `squad_it_dataset` : + +```py +squad_it_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 442 + }) +}) +``` + +Cela nous montre le nombre de lignes et les noms de colonnes associés à l’échantillon d’entraînement. Nous pouvons afficher l'un des exemples en indexant l’échantillon `train` comme suit : + +```py +squad_it_dataset["train"][0] +``` + +```python out +{ + "title": "Terremoto del Sichuan del 2008", # Séisme du Sichuan 2008 + "paragraphs": [ + { + "context": "Il terremoto del Sichuan del 2008 o il terremoto...", + # Le tremblement de terre du Sichuan de 2008 ou le... + "qas": [ + { + "answers": [{"answer_start": 29, "text": "2008"}], + "id": "56cdca7862d2951400fa6826", + "question": "In quale anno si è verificato il terremoto nel Sichuan?", + # En quelle année le tremblement de terre du Sichuan a-t-il eu lieu ? + }, + ... + ], + }, + ... + ], +} +``` + +Super, nous avons chargé notre premier jeu de données local ! Mais ce que nous voulons vraiment, c'est inclure à la fois les échantillons `train` et `test` dans un seul objet `DatasetDict` afin que nous puissions appliquer les fonctions `Dataset.map()` sur les deux à la fois . Pour ce faire, nous pouvons fournir un dictionnaire à l'argument `data_files` qui associe chaque nom des échantillons à un fichier associé à cet échantillon : + +```py +data_files = {"train": "SQuAD_it-train.json", "test": "SQuAD_it-test.json"} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +squad_it_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 442 + }) + test: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 48 + }) +}) +``` + +C'est exactement ce que nous voulions. Désormais, nous pouvons appliquer diverses techniques de prétraitement pour nettoyer les données, tokeniser les avis, etc. + + + +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. + + + +Les scripts de chargement de 🤗 *Datasets* prennent en charge la décompression automatique des fichiers d'entrée. Nous aurions donc pu ignorer l'utilisation de `gzip` en pointant l'argument `data_files` directement sur les fichiers compressés : + +```py +data_files = {"train": "SQuAD_it-train.json.gz", "test": "SQuAD_it-test.json.gz"} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +``` + +Cela peut être utile si vous ne souhaitez pas décompresser manuellement de nombreux fichiers GZIP. La décompression automatique s'applique également à d'autres formats courants tels que ZIP et TAR. Il vous suffit donc de pointer `data_files` vers les fichiers compressés et vous êtes prêt à partir ! + +Maintenant que vous savez comment charger des fichiers locaux sur votre ordinateur portable ou de bureau, examinons le chargement de fichiers distants. + +## Charger un jeu de données distant + +Si vous travaillez en tant que *data scientist* ou codeur dans une entreprise, il y a de fortes chances que les jeux de données que vous souhaitez analyser soient stockés sur un serveur distant. Heureusement, charger des fichiers distants est aussi simple que de charger des fichiers locaux ! Au lieu de fournir un chemin vers les fichiers locaux, nous pointons l'argument `data_files` de `load_dataset()` vers une ou plusieurs URL où les fichiers distants sont stockés. Par exemple, pour le jeu de données SQuAD-it hébergé sur GitHub, nous pouvons simplement faire pointer `data_files` vers les URL _SQuAD_it-*.json.gz_ comme suit : + +```py +url = "https://github.com/crux82/squad-it/raw/master/" +data_files = { + "train": url + "SQuAD_it-train.json.gz", + "test": url + "SQuAD_it-test.json.gz", +} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +``` + +Cela renvoie le même objet `DatasetDict` obtenu ci-dessus mais nous évite de télécharger et de décompresser manuellement les fichiers _SQuAD_it-*.json.gz_. Ceci conclut notre incursion dans les différentes façons de charger des jeux de données qui ne sont pas hébergés sur le *Hub*. Maintenant que nous avons un jeu de données avec lequel jouer, mettons la main à la pâte avec diverses techniques de gestion des données ! + + + +✏️ **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). + + diff --git a/chapters/fr/chapter5/3.mdx b/chapters/fr/chapter5/3.mdx index 1962c4dad..443681291 100644 --- a/chapters/fr/chapter5/3.mdx +++ b/chapters/fr/chapter5/3.mdx @@ -1,752 +1,752 @@ -# Il est temps de trancher et de découper - - - -La plupart du temps, les données avec lesquelles vous travaillez ne sont pas parfaitement préparées pour l’entraînements de modèles. Dans cette section, nous allons explorer les différentes fonctionnalités fournies par 🤗 *Datasets* pour nettoyer vos jeux de données. - - - -## Trancher et découper nos données - -Semblable à Pandas, 🤗 *Datasets* fournit plusieurs fonctions pour manipuler le contenu des objets `Dataset` et `DatasetDict`. Nous avons déjà rencontré la méthode `Dataset.map()` dans le [chapitre 3](/course/fr/chapter3) et dans cette section nous allons explorer certaines des autres fonctions à notre disposition. - -Pour cet exemple, nous utiliserons le [*Drug Review Dataset*](https://archive.ics.uci.edu/ml/datasets/Drug+Review+Dataset+%28Drugs.com%29) qui est hébergé sur [*UC Irvine Machine Learning Repository*](https://archive.ics.uci.edu/ml/index.php) et contenant des avis de patients sur divers médicaments ainsi que la condition traitée et une note de 10 étoiles sur la satisfaction du patient. - -Nous devons d'abord télécharger et extraire les données, ce qui peut être fait avec les commandes `wget` et `unzip` : - -```py -!wget "https://archive.ics.uci.edu/ml/machine-learning-databases/00462/drugsCom_raw.zip" -!unzip drugsCom_raw.zip -``` - -Étant donné que TSV n'est qu'une variante de CSV qui utilise des tabulations au lieu de virgules comme séparateurs, nous pouvons charger ces fichiers en utilisant le script de chargement `csv` et en spécifiant l'argument `delimiter` dans la fonction `load_dataset()` comme suit : - -```py -from datasets import load_dataset - -data_files = {"train": "drugsComTrain_raw.tsv", "test": "drugsComTest_raw.tsv"} -# \t is the tab character in Python -drug_dataset = load_dataset("csv", data_files=data_files, delimiter="\t") -``` - -Une bonne pratique lors de toute sorte d'analyse de données consiste à prélever un petit échantillon aléatoire pour avoir une idée rapide du type de données avec lesquelles vous travaillez. Dans 🤗 *Datasets*, nous pouvons créer un échantillon aléatoire en enchaînant les fonctions `Dataset.shuffle()` et `Dataset.select()` : - -```py -drug_sample = drug_dataset["train"].shuffle(seed=42).select(range(1000)) -# Un coup d'œil sur les premiers exemples -drug_sample[:3] -``` - -```python out -{'Unnamed: 0': [87571, 178045, 80482], - 'drugName': ['Naproxen', 'Duloxetine', 'Mobic'], - 'condition': ['Gout, Acute', 'ibromyalgia', 'Inflammatory Conditions'], - #['Goutte aiguë', 'ibromyalgie', 'Affections inflammatoires'] - 'review': ['"like the previous person mention, I'm a strong believer of aleve, it works faster for my gout than the prescription meds I take. No more going to the doctor for refills.....Aleve works!"', - # comme la personne précédente l'a mentionné, je suis un fervent partisan de l'aleve, il fonctionne plus rapidement pour ma goutte que les médicaments sur ordonnance que je prends. Je n'ai plus besoin d'aller chez le médecin pour des renouvellements.....Aleve fonctionne !" - '"I have taken Cymbalta for about a year and a half for fibromyalgia pain. It is great\r\nas a pain reducer and an anti-depressant, however, the side effects outweighed \r\nany benefit I got from it. I had trouble with restlessness, being tired constantly,\r\ndizziness, dry mouth, numbness and tingling in my feet, and horrible sweating. I am\r\nbeing weaned off of it now. Went from 60 mg to 30mg and now to 15 mg. I will be\r\noff completely in about a week. The fibro pain is coming back, but I would rather deal with it than the side effects."', - # J'ai pris du Cymbalta pendant environ un an et demi pour des douleurs de la fibromyalgie. C'est un excellent analgésique et un antidépresseur, mais les effets secondaires l'ont emporté sur tous les avantages que j'en ai tirés. J'ai eu des problèmes d'agitation, de fatigue constante, de vertiges, de bouche sèche, d'engourdissement, de picotements dans les pieds, et de transpiration horrible. Je suis en train de m'en sevrer maintenant. Je suis passée de 60 mg à 30 mg et maintenant à 15 mg. Je l'arrêterai complètement dans environ une semaine. La douleur de la fibrose revient, mais je préfère la supporter plutôt que les effets secondaires. - '"I have been taking Mobic for over a year with no side effects other than an elevated blood pressure. I had severe knee and ankle pain which completely went away after taking Mobic. I attempted to stop the medication however pain returned after a few days."'], - # J'ai pris Mobic pendant plus d'un an sans effets secondaires autres qu'une pression sanguine élevée. J'avais de fortes douleurs au genou et à la cheville qui ont complètement disparu après avoir pris Mobic. J'ai essayé d'arrêter le médicament mais la douleur est revenue après quelques jours." - 'rating': [9.0, 3.0, 10.0], - 'date': ['September 2, 2015', 'November 7, 2011', 'June 5, 2013'], - #['2 septembre 2015', '7 novembre 2011', '5 juin 2013'] - 'usefulCount': [36, 13, 128]} -``` - -Notez que nous avons corrigé la graine dans `Dataset.shuffle()` à des fins de reproductibilité. `Dataset.select()` attend un itérable d'indices, nous avons donc passé `range(1000)` pour récupérer les 1 000 premiers exemples du jeu de données mélangé. À partir de cet échantillon, nous pouvons déjà voir quelques bizarreries dans notre jeu de données : - -* la colonne `Unnamed: 0` ressemble étrangement à un identifiant anonyme pour chaque patient, -* la colonne `condition` comprend un mélange d'étiquettes en majuscules et en minuscules, -* les avis sont de longueur variable et contiennent un mélange de séparateurs de lignes Python (`\r\n`) ainsi que des codes de caractères HTML comme `&\#039;`. - -Voyons comment nous pouvons utiliser 🤗 *Datasets* pour traiter chacun de ces problèmes. Pour tester l'hypothèse de l'ID patient pour la colonne `Unnamed : 0`, nous pouvons utiliser la fonction `Dataset.unique()` pour vérifier que le nombre d'ID correspond au nombre de lignes dans chaque division : - -```py -for split in drug_dataset.keys(): - assert len(drug_dataset[split]) == len(drug_dataset[split].unique("Unnamed: 0")) -``` - -Cela semble confirmer notre hypothèse, alors nettoyons un peu en renommant la colonne `Unnamed: 0` en quelque chose d'un peu plus interprétable. Nous pouvons utiliser la fonction `DatasetDict.rename_column()` pour renommer la colonne sur les deux divisions en une seule fois : - -```py -drug_dataset = drug_dataset.rename_column( - original_column_name="Unnamed: 0", new_column_name="patient_id" -) -drug_dataset -``` - -```python out -DatasetDict({ - train: Dataset({ - features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'], - num_rows: 161297 - }) - test: Dataset({ - features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'], - num_rows: 53766 - }) -}) -``` - - - -✏️ **Essayez !** Utilisez la fonction ` Dataset.unique()` pour trouver le nombre de médicaments et de conditions uniques dans les échantillons d'entraînement et de test. - - - -Ensuite, normalisons toutes les étiquettes `condition` en utilisant `Dataset.map()`. Comme nous l'avons fait avec la tokenisation dans le [chapitre 3](/course/fr/chapter3), nous pouvons définir une fonction simple qui peut être appliquée sur toutes les lignes de chaque division dans `drug_dataset` : - -```py -def lowercase_condition(example): - return {"condition": example["condition"].lower()} - - -drug_dataset.map(lowercase_condition) -``` - -```python out -AttributeError: 'NoneType' object has no attribute 'lower' -``` - -Oh non, nous rencontrons un problème avec notre fonction ! À partir de l'erreur, nous pouvons déduire que certaines des entrées de la colonne `condition` sont `None` ne pouvant donc pas être mises en minuscules car ce ne sont pas des chaînes. Supprimons ces lignes en utilisant `Dataset.filter()`, qui fonctionne de manière similaire à `Dataset.map()` et attend une fonction qui reçoit un seul exemple issu du jeu de données. Au lieu d'écrire une fonction explicite comme : - -```py -def filter_nones(x): - return x["condition"] is not None -``` - -puis exécuter `drug_dataset.filter(filter_nones)`, nous pouvons le faire en une seule ligne en utilisant une _fonction lambda_. En Python, les fonctions lambda sont de petites fonctions que vous pouvez définir sans les nommer explicitement. Ils prennent la forme générale : - -``` -lambda : -``` - -où `lambda` est l'un des [mots clés](https://docs.python.org/3/reference/lexical_analysis.html#keywords) spéciaux de Python, `` est une liste/ensemble de valeurs séparées par des virgules qui définissent les entrées de la fonction et `` représente les opérations que vous souhaitez exécuter. Par exemple, nous pouvons définir une simple fonction lambda qui met au carré un nombre comme suit : - -``` -lambda x : x * x -``` - -Pour appliquer cette fonction à une entrée, nous devons l'envelopper ainsi que l'entrée entre parenthèses : - -```py -(lambda x: x * x)(3) -``` - -```python out -9 -``` - -De même, nous pouvons définir des fonctions lambda avec plusieurs arguments en les séparant par des virgules. Par exemple, nous pouvons calculer l'aire d'un triangle comme suit : - -```py -(lambda base, height: 0.5 * base * height)(4, 8) -``` - -```python out -16.0 -``` - -Les fonctions lambda sont pratiques lorsque vous souhaitez définir de petites fonctions à usage unique (pour plus d'informations à leur sujet, nous vous recommandons de lire l'excellent [tutoriel Real Python](https://realpython.com/python-lambda/) d'André Burgaud) . Dans le contexte de la bibliothèque 🤗 *Datasets*, nous pouvons utiliser des fonctions lambda pour définir des opérations simples de « mappage » et de filtrage. Utilisons cette astuce pour éliminer les entrées `None` dans notre jeu de données : - -```py -drug_dataset = drug_dataset.filter(lambda x: x["condition"] is not None) -``` - -Avec les entrées `None` supprimées, nous pouvons normaliser notre colonne `condition` : - -```py -drug_dataset = drug_dataset.map(lowercase_condition) -# Vérification que la mise en minuscule a fonctionné -drug_dataset["train"]["condition"][:3] -``` - -```python out -['left ventricular dysfunction', 'adhd', 'birth control'] -``` - -Ça marche ! Maintenant que nous avons nettoyé les étiquettes, examinons le nettoyage des avis eux-mêmes. - -## Création de nouvelles colonnes - -Chaque fois que vous avez affaire à des avis de clients, une bonne pratique consiste à vérifier le nombre de mots dans chaque avis. Une critique peut être un simple mot comme « Génial ! » ou un essai complet avec des milliers de mots. Selon le cas d'usage, vous devrez gérer ces extrêmes différemment. Pour calculer le nombre de mots dans chaque révision, nous utiliserons une heuristique approximative basée sur la division de chaque texte par des espaces. - -Définissons une fonction simple qui compte le nombre de mots dans chaque avis : - -```py -def compute_review_length(example): - return {"review_length": len(example["review"].split())} -``` - -Contrairement à notre fonction `lowercase_condition()`, `compute_review_length()` renvoie un dictionnaire dont la clé ne correspond pas à l'un des noms de colonne du jeu de données. Dans ce cas, lorsque `compute_review_length()` est passé à `Dataset.map()`, il est appliqué à toutes les lignes du jeu de données pour créer une nouvelle colonne `review_length` : - -```py -drug_dataset = drug_dataset.map(compute_review_length) -# Inspecter le premier exemple d'entraînement -drug_dataset["train"][0] -``` - -```python out -{'patient_id': 206461, - 'drugName': 'Valsartan', - 'condition': 'left ventricular dysfunction', # dysfonctionnement du ventricule gauche - 'review': '"It has no side effect, I take it in combination of Bystolic 5 Mg and Fish Oil"', - # Il n'a aucun effet secondaire, je le prends en combinaison avec Bystolic 5 mg et de l'huile de poisson. - 'rating': 9.0, - 'date': 'May 20, 2012', # 20 mai 2012 - 'usefulCount': 27, - 'review_length': 17} -``` - -Comme prévu, nous pouvons voir qu'une colonne `review_length` a été ajoutée à notre jeu d'entraînement. Nous pouvons trier cette nouvelle colonne avec `Dataset.sort()` pour voir à quoi ressemblent les valeurs extrêmes : - -```py -drug_dataset["train"].sort("review_length")[:3] -``` - -```python out -{'patient_id': [103488, 23627, 20558], - 'drugName': ['Loestrin 21 1 / 20', 'Chlorzoxazone', 'Nucynta'], - 'condition': ['birth control', 'muscle spasm', 'pain'], - # contraception, spasme musculaire, douleur. - 'review': ['"Excellent."', '"useless"', '"ok"'], # Excellent, inutile, ok - 'rating': [10.0, 1.0, 6.0], - 'date': ['November 4, 2008', 'March 24, 2017', 'August 20, 2016'], - # 4 novembre 2008, 24 mars 2017, 20 août 2016 - 'usefulCount': [5, 2, 10], - 'review_length': [1, 1, 1]} -``` - -Comme nous le soupçonnions, certaines critiques ne contiennent qu'un seul mot, ce qui, bien que cela puisse convenir à l'analyse des sentiments, n’est pas informatif si nous voulons prédire la condition. - - - -🙋 Une autre façon d'ajouter de nouvelles colonnes à un jeu de données consiste à utiliser la fonction `Dataset.add_column()`. Cela vous permet de donner la colonne sous forme de liste Python ou de tableau NumPy et peut être utile dans les situations où `Dataset.map()` n'est pas bien adapté à votre analyse. - - - -Utilisons la fonction `Dataset.filter()` pour supprimer les avis contenant moins de 30 mots. De la même manière que nous l'avons fait avec la colonne `condition`, nous pouvons filtrer les avis très courts en exigeant que les avis aient une longueur supérieure à ce seuil : - -```py -drug_dataset = drug_dataset.filter(lambda x: x["review_length"] > 30) -print(drug_dataset.num_rows) -``` - -```python out -{'train': 138514, 'test': 46108} -``` - -Comme vous pouvez le constater, cela a supprimé environ 15 % des avis de nos jeux d'entraînement et de test d'origine. - - - -✏️ **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. - - - -La dernière chose à laquelle nous devons faire face est la présence de caractères HTML dans nos avis. Nous pouvons utiliser le module `html` de Python pour supprimer ces caractères, comme ceci : - -```py -import html - -text = "I'm a transformer called BERT" -html.unescape(text) -``` - -```python out -"I'm a transformer called BERT" -``` - -Nous utilisons `Dataset.map()` pour démasquer tous les caractères HTML de notre corpus : - -```python -drug_dataset = drug_dataset.map(lambda x: {"review": html.unescape(x["review"])}) -``` - -Comme vous pouvez le voir, la méthode `Dataset.map()` est très utile pour le traitement des données. Et nous n'avons même pas effleuré la surface de tout ce qu'elle peut faire ! - -## Les superpouvoirs de la méthode `map()` - -La méthode `Dataset.map()` prend un argument `batched` qui, s'il est défini sur `True`, l'amène à envoyer un batch d'exemples à la fonction *map* en une seule fois (la taille du batch est configurable mais est fixé par défaut à 1 000). Par exemple, la fonction `map()` précédente qui supprime tout le code HTML prend un peu de temps à s'exécuter (vous pouvez lire le temps pris dans les barres de progression). On peut accélérer cela en traitant plusieurs éléments en même temps à l'aide d'une compréhension de liste. - -Lorsque vous spécifiez `batched=True`, la fonction reçoit un dictionnaire avec les champs du jeu de données mais chaque valeur est maintenant une _liste de valeurs_ et non plus une seule valeur. La valeur retournée par `Dataset.map()` devrait être la même : un dictionnaire avec les champs que nous voulons mettre à jour ou ajouter à notre jeu de données, et une liste de valeurs. Par exemple, voici une autre façon de supprimer tous les caractères HTML, mais en utilisant `batched=True` : - -```python -new_drug_dataset = drug_dataset.map( - lambda x: {"review": [html.unescape(o) for o in x["review"]]}, batched=True -) -``` - -Si vous exécutez ce code dans un *notebook*, vous verrez que cette commande s'exécute beaucoup plus rapidement que la précédente. Et ce n'est pas parce que nos critiques ont déjà été scannées au format HTML. Si vous ré-exécutez l'instruction de la section précédente (sans `batched=True`), cela prendra le même temps qu'avant. En effet, les compréhensions de liste sont généralement plus rapides que l'exécution du même code dans une boucle `for` et nous gagnons également en performances en accédant à de nombreux éléments en même temps au lieu d'un par un. - -L'utilisation de `Dataset.map()` avec `batched=True` est essentielle pour les *tokenizers rapides* que nous rencontrerons dans le [chapitre 6](/course/fr/chapter6) et qui peuvent rapidement tokeniser de grandes listes de textes. Par exemple, pour tokeniser toutes les critiques de médicaments avec un *tokenizer* rapide nous pouvons utiliser une fonction comme celle-ci : - -```python -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") - - -def tokenize_function(examples): - return tokenizer(examples["review"], truncation=True) -``` - -Comme vous l'avez vu dans le [chapitre 3](/course/fr/chapter3), nous pouvons passer un ou plusieurs exemples au *tokenizer*. Nous pouvons donc utiliser cette fonction avec ou sans `batched=True`. Profitons-en pour comparer les performances des différentes options. Dans un *notebook*, vous pouvez chronométrer une instruction d'une ligne en ajoutant `%time` avant la ligne de code que vous souhaitez mesurer : - -```python no-format -%time tokenized_dataset = drug_dataset.map(tokenize_function, batched=True) -``` - -Vous pouvez également chronométrer une cellule entière en mettant `%%time` au début de la cellule. Sur le matériel sur lequel nous avons exécuté cela, cela affichait 10,8 s pour cette instruction (c'est le nombre écrit après "Wall time"). - - - -✏️ **Essayez !** Exécutez la même instruction avec et sans `batched=True`, puis essayez-le avec un *tokenizer* lent (ajoutez `use_fast=False` dans la méthode `AutoTokenizer.from_pretrained()`) afin que vous puissiez voir quels temps vous obtenez sur votre matériel. - - - -Voici les résultats que nous avons obtenus avec et sans *batching*, avec un *tokenizer* rapide et un lent : - -Options | *Tokenizer* rapide | *Tokenizer* lent -:--------------:|:----------------:|:-----------------: -`batched=True` | 10.8s | 4min41s -`batched=False` | 59.2s | 5min3s - -Cela signifie que l'utilisation d'un *tokenizer* rapide avec l'option `batched=True` est 30 fois plus rapide que son homologue lent sans batch. C'est vraiment incroyable ! C'est la raison principale pour laquelle les *tokenizers* rapides sont la valeur par défaut lors de l'utilisation de `AutoTokenizer` (et pourquoi ils sont appelés « rapides »). Ils sont capables d'atteindre une telle vitesse car en coulisses le code de tokenisation est exécuté en Rust qui est un langage facilitant la parallélisation de l'exécution du code. - -La parallélisation est également la raison du gain de vitesse de près de 6 fois obtenue par le *tokenizer* rapide avec batch. Vous ne pouvez pas paralléliser une seule opération de tokenisation, mais lorsque vous souhaitez tokeniser de nombreux textes en même temps, vous pouvez simplement répartir l'exécution sur plusieurs processus. Chacun responsable de ses propres textes. - -`Dataset.map()` possède aussi ses propres capacités de parallélisation. Comme elles ne sont pas soutenus par Rust, un *tokenizer* lent ne peut pas rattraper un rapide mais cela peut toujours être utile (surtout si vous utilisez un *tokenizer* qui n'a pas de version rapide). Pour activer le multitraitement, utilisez l'argument `num_proc` et spécifiez le nombre de processus à utiliser dans votre appel à `Dataset.map()` : - -```py -slow_tokenizer = AutoTokenizer.from_pretrained("bert-base-cased", use_fast=False) - - -def slow_tokenize_function(examples): - return slow_tokenizer(examples["review"], truncation=True) - - -tokenized_dataset = drug_dataset.map(slow_tokenize_function, batched=True, num_proc=8) -``` - -Vous pouvez faire des tests pour déterminer le nombre optimal de processus à utiliser. Dans notre cas 8 semble produire le meilleur gain de vitesse. Voici les chiffres que nous avons obtenus avec et sans multitraitement : - -Options | *Tokenizer* rapide | *Tokenizer* lent -:----------------------------:|:----------------:|:---------------: -`batched=True` | 10.8s | 4min41s -`batched=False` | 59.2s | 5min3s -`batched=True`, `num_proc=8` | 6.52s | 41.3s -`batched=False`, `num_proc=8` | 9.49s | 45.2s - -Ce sont des résultats beaucoup plus raisonnables pour le *tokenizer* lent mais les performances du *tokenizer* rapide ont également été considérablement améliorées. Notez, cependant, que ce ne sera pas toujours le cas : pour des valeurs de `num_proc` autres que 8, nos tests ont montré qu'il était plus rapide d'utiliser `batched=True` sans cette option. En général, nous ne recommandons pas d'utiliser le multitraitement pour les *tokenizers* rapides avec `batched=True`. - - - -Utiliser `num_proc` pour accélérer votre traitement est généralement une bonne idée tant que la fonction que vous utilisez n'effectue pas déjà une sorte de multitraitement. - - - -Toutes ces fonctionnalités condensées en une seule méthode sont déjà assez étonnantes, mais il y a plus ! Avec `Dataset.map()` et `batched=True` vous pouvez modifier le nombre d'éléments dans votre jeu de données. Ceci est très utile dans de nombreuses situations où vous souhaitez créer plusieurs fonctionnalités d'entraînement à partir d'un exemple. Nous devrons le faire dans le cadre du prétraitement de plusieurs des tâches de traitement du langage naturel que nous entreprendrons dans le [chapitre 7](/course/fr/chapter7). - - - -💡 En apprentissage automatique, un _exemple_ est généralement défini comme l'ensemble de _features_ que nous donnons au modèle. Dans certains contextes, ces caractéristiques seront l'ensemble des colonnes d'un `Dataset`, mais dans d'autres (comme ici et pour la réponse aux questions), plusieurs caractéristiques peuvent être extraites d'un seul exemple et appartenir à une seule colonne. - - - -Voyons comment cela fonctionne ! Ici, nous allons tokeniser nos exemples et les tronquer à une longueur maximale de 128 mais nous demanderons au *tokenizer* de renvoyer *tous* les morceaux des textes au lieu du premier. Cela peut être fait avec `return_overflowing_tokens=True` : - -```py -def tokenize_and_split(examples): - return tokenizer( - examples["review"], - truncation=True, - max_length=128, - return_overflowing_tokens=True, - ) -``` - -Testons cela sur un exemple avant d'utiliser `Dataset.map()` sur le jeu de données : - -```py -result = tokenize_and_split(drug_dataset["train"][0]) -[len(inp) for inp in result["input_ids"]] -``` - -```python out -[128, 49] -``` - -Notre premier exemple du jeu d’entraînement est devenu deux caractéristiques car il a été segmenté à plus que le nombre maximum de *tokens* que nous avons spécifié : le premier de longueur 128 et le second de longueur 49. Faisons maintenant cela pour tous les éléments du jeu de données ! - -```py -tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) -``` - -```python out -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. - -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` : - -```py -tokenized_dataset = drug_dataset.map( - tokenize_and_split, batched=True, remove_columns=drug_dataset["train"].column_names -) -``` - -Maintenant, cela fonctionne sans erreur. Nous pouvons vérifier que notre nouveau jeu de données contient beaucoup plus d'éléments que le jeu de données d'origine en comparant les longueurs : - -```py -len(tokenized_dataset["train"]), len(drug_dataset["train"]) -``` - -```python out -(206772, 138514) -``` - -Nous avons mentionné que nous pouvions également résoudre le problème de longueur non concordante en donnant aux anciennes colonnes la même taille que les nouvelles. Pour ce faire, nous avons besoin du champ `overflow_to_sample_mapping` que le *tokenizer* renvoie lorsque nous définissons `return_overflowing_tokens=True`. Il nous donne une correspondance entre un nouvel index de caractéristique et l'index de l'échantillon dont il est issu. Grâce à cela, nous pouvons associer chaque clé présente dans notre jeu de données d'origine à une liste de valeurs de la bonne taille en répétant les valeurs de chaque exemple autant de fois qu'il génère de nouvelles caractéristiques : - -```py -def tokenize_and_split(examples): - result = tokenizer( - examples["review"], - truncation=True, - max_length=128, - return_overflowing_tokens=True, - ) - # Extract mapping between new and old indices - sample_map = result.pop("overflow_to_sample_mapping") - for key, values in examples.items(): - result[key] = [values[i] for i in sample_map] - return result -``` - -Nous pouvons voir que cela fonctionne avec `Dataset.map()` sans que nous ayons besoin de supprimer les anciennes colonnes : - -```py -tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) -tokenized_dataset -``` - -```python out -DatasetDict({ - train: Dataset({ - features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'], - num_rows: 206772 - }) - test: Dataset({ - features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'], - num_rows: 68876 - }) -}) -``` - -Nous obtenons le même nombre de caractéristiques d'entraînement qu'auparavant, mais ici nous avons conservé tous les anciens champs. Si vous en avez besoin pour un post-traitement après l'application de votre modèle, vous pouvez utiliser cette approche. - -Vous avez maintenant vu comment 🤗 *Datasets* peut être utilisé pour prétraiter un jeu de données de différentes manières. Bien que les fonctions de traitement de 🤗 *Datasets* couvrent la plupart de vos besoins, il peut arriver que vous deviez passer à Pandas pour accéder à des fonctionnalités plus puissantes, telles que `DataFrame.groupby()` ou des API de haut niveau pour la visualisation. Heureusement, 🤗 *Datasets* est conçu pour être interopérable avec des bibliothèques telles que Pandas, NumPy, PyTorch, TensorFlow et JAX. Voyons comment cela fonctionne. - -## De `Dataset` à `DataFrame` et vice versa - - - -Pour permettre la conversion entre diverses bibliothèques tierces, 🤗 *Datasets* fournit une fonction `Dataset.set_format()`. Cette fonction ne modifie que le _format de sortie_ du jeu de données. Vous pouvez donc facilement passer à un autre format sans affecter le _format de données_ sous-jacent, qui est Apache Arrow. Le formatage se fait sur place. Pour démontrer, convertissons notre jeu de données vers Pandas : - -```py -drug_dataset.set_format("pandas") -``` - -Maintenant, lorsque nous accédons aux éléments du jeu de données, nous obtenons un `pandas.DataFrame` au lieu d'un dictionnaire : - -```py -drug_dataset["train"][:3] -``` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
patient_iddrugNameconditionreviewratingdateusefulCountreview_length
095260Guanfacineadhd"My son is halfway through his fourth week of Intuniv..."8.0April 27, 2010192141
192703Lybrelbirth control"I used to take another oral contraceptive, which had 21 pill cycle, and was very happy- very light periods, max 5 days, no other side effects..."5.0December 14, 200917134
2138000Ortho Evrabirth control"This is my first time using any form of birth control..."8.0November 3, 20151089
- -Créons un `pandas.DataFrame` pour l'ensemble d'entraînement en sélectionnant tous les éléments de `drug_dataset["train"]` : - -```py -train_df = drug_dataset["train"][:] -``` - - - -🚨 Sous le capot, `Dataset.set_format()` change le format de retour pour la méthode `__getitem__()`. Cela signifie que lorsque nous voulons créer un nouvel objet comme `train_df` à partir d'un `Dataset` au format `"pandas"`, nous devons découper tout le jeu de données pour obtenir un `pandas.DataFrame`. Vous pouvez vérifier par vous-même que le type de `drug_dataset["train"]` est `Dataset`, quel que soit le format de sortie. - - - - -De là, nous pouvons utiliser toutes les fonctionnalités Pandas que nous voulons. Par exemple, nous pouvons faire un chaînage sophistiqué pour calculer la distribution de classe parmi les entrées `condition` : - -```py -frequencies = ( - train_df["condition"] - .value_counts() - .to_frame() - .reset_index() - .rename(columns={"index": "condition", "condition": "frequency"}) -) -frequencies.head() -``` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
conditionfrequency
0birth control27655
1depression8023
2acne5209
3anxiety4991
4pain4744
- - -Et une fois que nous avons terminé notre analyse Pandas, nous pouvons toujours créer un nouvel objet `Dataset` en utilisant la fonction `Dataset.from_pandas()` comme suit : - - -```py -from datasets import Dataset - -freq_dataset = Dataset.from_pandas(frequencies) -freq_dataset -``` - -```python out -Dataset({ - features: ['condition', 'frequency'], - num_rows: 819 -}) -``` - - - -✏️ **Essayez !** Calculez la note moyenne par médicament et stockez le résultat dans un nouveau jeu de données. - - - -Ceci conclut notre visite des différentes techniques de prétraitement disponibles dans 🤗 *Datasets*. Pour compléter la section, créons un ensemble de validation pour préparer le jeu de données à l’entraînement d'un classifieur. Avant cela, nous allons réinitialiser le format de sortie de `drug_dataset` de `"pandas"` à `"arrow"` : - -```python -drug_dataset.reset_format() -``` - -## Création d'un ensemble de validation - -Bien que nous ayons un jeu de test que nous pourrions utiliser pour l'évaluation, il est recommandé de ne pas toucher au jeu de test et de créer un jeu de validation séparé pendant le développement. Une fois que vous êtes satisfait des performances de vos modèles sur l'ensemble de validation, vous pouvez effectuer une dernière vérification d'intégrité sur l'ensemble test. Ce processus permet d'atténuer le risque de surentraînement sur le jeu de test et de déployer un modèle qui échoue sur des données du monde réel. - -🤗 *Datasets* fournit une fonction `Dataset.train_test_split()` basée sur la célèbre fonctionnalité de `scikit-learn`. Utilisons-la pour diviser notre ensemble d'entraînement `train` et `validation` (nous définissons l'argument `seed` pour la reproductibilité) : - -```py -drug_dataset_clean = drug_dataset["train"].train_test_split(train_size=0.8, seed=42) -# Rename the default "test" split to "validation" -drug_dataset_clean["validation"] = drug_dataset_clean.pop("test") -# Add the "test" set to our `DatasetDict` -drug_dataset_clean["test"] = drug_dataset["test"] -drug_dataset_clean -``` - -```python out -DatasetDict({ - train: Dataset({ - features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], - num_rows: 110811 - }) - validation: Dataset({ - features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], - num_rows: 27703 - }) - test: Dataset({ - features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], - num_rows: 46108 - }) -}) -``` - -Génial, nous avons maintenant préparé un jeu de données prêt pour l'entraînement de certains modèles ! Dans la [section 5](/course/fr/chapter5/5), nous vous montrerons comment télécharger des jeux de données sur le *Hub*. Mais pour l'instant, terminons notre analyse en examinant quelques façons d'enregistrer des jeux de données sur votre ordinateur local. - -## Enregistrer un jeu de données - - - -Bien que 🤗 *Datasets* mette en cache chaque jeu de données téléchargé et les opérations qui y sont effectuées, il y a des moments où vous voudrez enregistrer un jeu de données sur le disque (par exemple, au cas où le cache serait supprimé). Comme indiqué dans le tableau ci-dessous, 🤗 *Datasets* fournit trois fonctions principales pour enregistrer votre jeu de données dans différents formats : - -| Format de données | Fonction | -| :---------------: | :----------------------: | -| Arrow | `Dataset.save_to_disk()` | -| CSV | `Dataset.to_csv()` | -| JSON | `Dataset.to_json()` | - -Par exemple, enregistrons notre jeu de données nettoyé au format Arrow : - -```py -drug_dataset_clean.save_to_disk("drug-reviews") -``` - -Cela créera un répertoire avec la structure suivante : - -``` -drug-reviews/ -├── dataset_dict.json -├── test -│ ├── dataset.arrow -│ ├── dataset_info.json -│ └── state.json -├── train -│ ├── dataset.arrow -│ ├── dataset_info.json -│ ├── indices.arrow -│ └── state.json -└── validation - ├── dataset.arrow - ├── dataset_info.json - ├── indices.arrow - └── state.json -``` - -où nous pouvons voir que chaque division est associée à sa propre table *dataset.arrow* et à certaines métadonnées dans *dataset_info.json* et *state.json*. Vous pouvez considérer le format Arrow comme un tableau sophistiqué de colonnes et de lignes optimisé pour la création d'applications hautes performances qui traitent et transportent de grands ensembles de données. - -Une fois le jeu de données enregistré, nous pouvons le charger en utilisant la fonction `load_from_disk()` comme suit : - -```py -from datasets import load_from_disk - -drug_dataset_reloaded = load_from_disk("drug-reviews") -drug_dataset_reloaded -``` - -```python out -DatasetDict({ - train: Dataset({ - features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], - num_rows: 110811 - }) - validation: Dataset({ - features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], - num_rows: 27703 - }) - test: Dataset({ - features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], - num_rows: 46108 - }) -}) -``` - -Pour les formats CSV et JSON, nous devons stocker chaque fractionnement dans un fichier séparé. Pour ce faire, vous pouvez parcourir les clés et les valeurs de l'objet `DatasetDict` : - -```py -for split, dataset in drug_dataset_clean.items(): - dataset.to_json(f"drug-reviews-{split}.jsonl") -``` - -Cela enregistre chaque fractionnement au [format JSON Lines](https://jsonlines.org), où chaque ligne du jeu de données est stockée sous la forme d'une seule ligne de JSON. Voici à quoi ressemble le premier exemple : - -```py -!head -n 1 drug-reviews-train.jsonl -``` - -```python out -{"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} - # Il semble que je ressente les effets secondaires habituels de LEXAPRO : insomnie, baisse de la libido, somnolence pendant la journée. Je le prends le soir parce que mon médecin m'a dit de le prendre le soir s'il me fatiguait. J'ai supposé que ce serait le cas et j'ai commencé à le prendre la nuit. Rêves étranges, certains agréables. On m'a diagnostiqué une fibromyalgie. Il semble que ce médicament aide à soulager la douleur. J'ai eu de l'anxiété et de la dépression dans ma famille, et j'ai essayé plusieurs autres médicaments qui n'ont pas fonctionné. Cela ne fait que deux semaines que je prends ce médicament, mais je me sens plus positif dans mon esprit et je veux accomplir davantage dans ma vie. J'espère que les effets secondaires vont s'estomper, cela vaut la peine de s'y tenir d'après les réponses des autres. C'est un excellent médicament. -``` - -Nous pouvons ensuite utiliser les techniques de [section 2](/course/fr/chapter5/2) pour charger les fichiers JSON comme suit : - -```py -data_files = { - "train": "drug-reviews-train.jsonl", - "validation": "drug-reviews-validation.jsonl", - "test": "drug-reviews-test.jsonl", -} -drug_dataset_reloaded = load_dataset("json", data_files=data_files) -``` - -Et c'est tout pour notre excursion dans la manipulation des données avec 🤗 *Datasets* ! Maintenant que nous disposons d'un ensemble de données nettoyé pour entraîner un modèle, voici quelques idées que vous pouvez essayer : - -1. Utilisez les techniques du [chapitre 3](/course/fr/chapter3) pour entraîner un classifieur capable de prédire l'état du patient en fonction de l'examen du médicament. -2. Utilisez le pipeline `summarization` du [chapitre 1](/course/fr/chapter1) pour générer des résumés des révisions. - -Ensuite, nous verrons comment 🤗 *Datasets* peut vous permettre de travailler avec d'énormes jeux de données sans faire exploser votre ordinateur portable ! +# Il est temps de trancher et de découper + + + +La plupart du temps, les données avec lesquelles vous travaillez ne sont pas parfaitement préparées pour l’entraînements de modèles. Dans cette section, nous allons explorer les différentes fonctionnalités fournies par 🤗 *Datasets* pour nettoyer vos jeux de données. + + + +## Trancher et découper nos données + +Semblable à Pandas, 🤗 *Datasets* fournit plusieurs fonctions pour manipuler le contenu des objets `Dataset` et `DatasetDict`. Nous avons déjà rencontré la méthode `Dataset.map()` dans le [chapitre 3](/course/fr/chapter3) et dans cette section nous allons explorer certaines des autres fonctions à notre disposition. + +Pour cet exemple, nous utiliserons le [*Drug Review Dataset*](https://archive.ics.uci.edu/ml/datasets/Drug+Review+Dataset+%28Drugs.com%29) qui est hébergé sur [*UC Irvine Machine Learning Repository*](https://archive.ics.uci.edu/ml/index.php) et contenant des avis de patients sur divers médicaments ainsi que la condition traitée et une note de 10 étoiles sur la satisfaction du patient. + +Nous devons d'abord télécharger et extraire les données, ce qui peut être fait avec les commandes `wget` et `unzip` : + +```py +!wget "https://archive.ics.uci.edu/ml/machine-learning-databases/00462/drugsCom_raw.zip" +!unzip drugsCom_raw.zip +``` + +Étant donné que TSV n'est qu'une variante de CSV qui utilise des tabulations au lieu de virgules comme séparateurs, nous pouvons charger ces fichiers en utilisant le script de chargement `csv` et en spécifiant l'argument `delimiter` dans la fonction `load_dataset()` comme suit : + +```py +from datasets import load_dataset + +data_files = {"train": "drugsComTrain_raw.tsv", "test": "drugsComTest_raw.tsv"} +# \t is the tab character in Python +drug_dataset = load_dataset("csv", data_files=data_files, delimiter="\t") +``` + +Une bonne pratique lors de toute sorte d'analyse de données consiste à prélever un petit échantillon aléatoire pour avoir une idée rapide du type de données avec lesquelles vous travaillez. Dans 🤗 *Datasets*, nous pouvons créer un échantillon aléatoire en enchaînant les fonctions `Dataset.shuffle()` et `Dataset.select()` : + +```py +drug_sample = drug_dataset["train"].shuffle(seed=42).select(range(1000)) +# Un coup d'œil sur les premiers exemples +drug_sample[:3] +``` + +```python out +{'Unnamed: 0': [87571, 178045, 80482], + 'drugName': ['Naproxen', 'Duloxetine', 'Mobic'], + 'condition': ['Gout, Acute', 'ibromyalgia', 'Inflammatory Conditions'], + #['Goutte aiguë', 'ibromyalgie', 'Affections inflammatoires'] + 'review': ['"like the previous person mention, I'm a strong believer of aleve, it works faster for my gout than the prescription meds I take. No more going to the doctor for refills.....Aleve works!"', + # comme la personne précédente l'a mentionné, je suis un fervent partisan de l'aleve, il fonctionne plus rapidement pour ma goutte que les médicaments sur ordonnance que je prends. Je n'ai plus besoin d'aller chez le médecin pour des renouvellements.....Aleve fonctionne !" + '"I have taken Cymbalta for about a year and a half for fibromyalgia pain. It is great\r\nas a pain reducer and an anti-depressant, however, the side effects outweighed \r\nany benefit I got from it. I had trouble with restlessness, being tired constantly,\r\ndizziness, dry mouth, numbness and tingling in my feet, and horrible sweating. I am\r\nbeing weaned off of it now. Went from 60 mg to 30mg and now to 15 mg. I will be\r\noff completely in about a week. The fibro pain is coming back, but I would rather deal with it than the side effects."', + # J'ai pris du Cymbalta pendant environ un an et demi pour des douleurs de la fibromyalgie. C'est un excellent analgésique et un antidépresseur, mais les effets secondaires l'ont emporté sur tous les avantages que j'en ai tirés. J'ai eu des problèmes d'agitation, de fatigue constante, de vertiges, de bouche sèche, d'engourdissement, de picotements dans les pieds, et de transpiration horrible. Je suis en train de m'en sevrer maintenant. Je suis passée de 60 mg à 30 mg et maintenant à 15 mg. Je l'arrêterai complètement dans environ une semaine. La douleur de la fibrose revient, mais je préfère la supporter plutôt que les effets secondaires. + '"I have been taking Mobic for over a year with no side effects other than an elevated blood pressure. I had severe knee and ankle pain which completely went away after taking Mobic. I attempted to stop the medication however pain returned after a few days."'], + # J'ai pris Mobic pendant plus d'un an sans effets secondaires autres qu'une pression sanguine élevée. J'avais de fortes douleurs au genou et à la cheville qui ont complètement disparu après avoir pris Mobic. J'ai essayé d'arrêter le médicament mais la douleur est revenue après quelques jours." + 'rating': [9.0, 3.0, 10.0], + 'date': ['September 2, 2015', 'November 7, 2011', 'June 5, 2013'], + #['2 septembre 2015', '7 novembre 2011', '5 juin 2013'] + 'usefulCount': [36, 13, 128]} +``` + +Notez que nous avons corrigé la graine dans `Dataset.shuffle()` à des fins de reproductibilité. `Dataset.select()` attend un itérable d'indices, nous avons donc passé `range(1000)` pour récupérer les 1 000 premiers exemples du jeu de données mélangé. À partir de cet échantillon, nous pouvons déjà voir quelques bizarreries dans notre jeu de données : + +* la colonne `Unnamed: 0` ressemble étrangement à un identifiant anonyme pour chaque patient, +* la colonne `condition` comprend un mélange d'étiquettes en majuscules et en minuscules, +* les avis sont de longueur variable et contiennent un mélange de séparateurs de lignes Python (`\r\n`) ainsi que des codes de caractères HTML comme `&\#039;`. + +Voyons comment nous pouvons utiliser 🤗 *Datasets* pour traiter chacun de ces problèmes. Pour tester l'hypothèse de l'ID patient pour la colonne `Unnamed : 0`, nous pouvons utiliser la fonction `Dataset.unique()` pour vérifier que le nombre d'ID correspond au nombre de lignes dans chaque division : + +```py +for split in drug_dataset.keys(): + assert len(drug_dataset[split]) == len(drug_dataset[split].unique("Unnamed: 0")) +``` + +Cela semble confirmer notre hypothèse, alors nettoyons un peu en renommant la colonne `Unnamed: 0` en quelque chose d'un peu plus interprétable. Nous pouvons utiliser la fonction `DatasetDict.rename_column()` pour renommer la colonne sur les deux divisions en une seule fois : + +```py +drug_dataset = drug_dataset.rename_column( + original_column_name="Unnamed: 0", new_column_name="patient_id" +) +drug_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'], + num_rows: 161297 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'], + num_rows: 53766 + }) +}) +``` + + + +✏️ **Essayez !** Utilisez la fonction ` Dataset.unique()` pour trouver le nombre de médicaments et de conditions uniques dans les échantillons d'entraînement et de test. + + + +Ensuite, normalisons toutes les étiquettes `condition` en utilisant `Dataset.map()`. Comme nous l'avons fait avec la tokenisation dans le [chapitre 3](/course/fr/chapter3), nous pouvons définir une fonction simple qui peut être appliquée sur toutes les lignes de chaque division dans `drug_dataset` : + +```py +def lowercase_condition(example): + return {"condition": example["condition"].lower()} + + +drug_dataset.map(lowercase_condition) +``` + +```python out +AttributeError: 'NoneType' object has no attribute 'lower' +``` + +Oh non, nous rencontrons un problème avec notre fonction ! À partir de l'erreur, nous pouvons déduire que certaines des entrées de la colonne `condition` sont `None` ne pouvant donc pas être mises en minuscules car ce ne sont pas des chaînes. Supprimons ces lignes en utilisant `Dataset.filter()`, qui fonctionne de manière similaire à `Dataset.map()` et attend une fonction qui reçoit un seul exemple issu du jeu de données. Au lieu d'écrire une fonction explicite comme : + +```py +def filter_nones(x): + return x["condition"] is not None +``` + +puis exécuter `drug_dataset.filter(filter_nones)`, nous pouvons le faire en une seule ligne en utilisant une _fonction lambda_. En Python, les fonctions lambda sont de petites fonctions que vous pouvez définir sans les nommer explicitement. Ils prennent la forme générale : + +``` +lambda : +``` + +où `lambda` est l'un des [mots clés](https://docs.python.org/3/reference/lexical_analysis.html#keywords) spéciaux de Python, `` est une liste/ensemble de valeurs séparées par des virgules qui définissent les entrées de la fonction et `` représente les opérations que vous souhaitez exécuter. Par exemple, nous pouvons définir une simple fonction lambda qui met au carré un nombre comme suit : + +``` +lambda x : x * x +``` + +Pour appliquer cette fonction à une entrée, nous devons l'envelopper ainsi que l'entrée entre parenthèses : + +```py +(lambda x: x * x)(3) +``` + +```python out +9 +``` + +De même, nous pouvons définir des fonctions lambda avec plusieurs arguments en les séparant par des virgules. Par exemple, nous pouvons calculer l'aire d'un triangle comme suit : + +```py +(lambda base, height: 0.5 * base * height)(4, 8) +``` + +```python out +16.0 +``` + +Les fonctions lambda sont pratiques lorsque vous souhaitez définir de petites fonctions à usage unique (pour plus d'informations à leur sujet, nous vous recommandons de lire l'excellent [tutoriel Real Python](https://realpython.com/python-lambda/) d'André Burgaud) . Dans le contexte de la bibliothèque 🤗 *Datasets*, nous pouvons utiliser des fonctions lambda pour définir des opérations simples de « mappage » et de filtrage. Utilisons cette astuce pour éliminer les entrées `None` dans notre jeu de données : + +```py +drug_dataset = drug_dataset.filter(lambda x: x["condition"] is not None) +``` + +Avec les entrées `None` supprimées, nous pouvons normaliser notre colonne `condition` : + +```py +drug_dataset = drug_dataset.map(lowercase_condition) +# Vérification que la mise en minuscule a fonctionné +drug_dataset["train"]["condition"][:3] +``` + +```python out +['left ventricular dysfunction', 'adhd', 'birth control'] +``` + +Ça marche ! Maintenant que nous avons nettoyé les étiquettes, examinons le nettoyage des avis eux-mêmes. + +## Création de nouvelles colonnes + +Chaque fois que vous avez affaire à des avis de clients, une bonne pratique consiste à vérifier le nombre de mots dans chaque avis. Une critique peut être un simple mot comme « Génial ! » ou un essai complet avec des milliers de mots. Selon le cas d'usage, vous devrez gérer ces extrêmes différemment. Pour calculer le nombre de mots dans chaque révision, nous utiliserons une heuristique approximative basée sur la division de chaque texte par des espaces. + +Définissons une fonction simple qui compte le nombre de mots dans chaque avis : + +```py +def compute_review_length(example): + return {"review_length": len(example["review"].split())} +``` + +Contrairement à notre fonction `lowercase_condition()`, `compute_review_length()` renvoie un dictionnaire dont la clé ne correspond pas à l'un des noms de colonne du jeu de données. Dans ce cas, lorsque `compute_review_length()` est passé à `Dataset.map()`, il est appliqué à toutes les lignes du jeu de données pour créer une nouvelle colonne `review_length` : + +```py +drug_dataset = drug_dataset.map(compute_review_length) +# Inspecter le premier exemple d'entraînement +drug_dataset["train"][0] +``` + +```python out +{'patient_id': 206461, + 'drugName': 'Valsartan', + 'condition': 'left ventricular dysfunction', # dysfonctionnement du ventricule gauche + 'review': '"It has no side effect, I take it in combination of Bystolic 5 Mg and Fish Oil"', + # Il n'a aucun effet secondaire, je le prends en combinaison avec Bystolic 5 mg et de l'huile de poisson. + 'rating': 9.0, + 'date': 'May 20, 2012', # 20 mai 2012 + 'usefulCount': 27, + 'review_length': 17} +``` + +Comme prévu, nous pouvons voir qu'une colonne `review_length` a été ajoutée à notre jeu d'entraînement. Nous pouvons trier cette nouvelle colonne avec `Dataset.sort()` pour voir à quoi ressemblent les valeurs extrêmes : + +```py +drug_dataset["train"].sort("review_length")[:3] +``` + +```python out +{'patient_id': [103488, 23627, 20558], + 'drugName': ['Loestrin 21 1 / 20', 'Chlorzoxazone', 'Nucynta'], + 'condition': ['birth control', 'muscle spasm', 'pain'], + # contraception, spasme musculaire, douleur. + 'review': ['"Excellent."', '"useless"', '"ok"'], # Excellent, inutile, ok + 'rating': [10.0, 1.0, 6.0], + 'date': ['November 4, 2008', 'March 24, 2017', 'August 20, 2016'], + # 4 novembre 2008, 24 mars 2017, 20 août 2016 + 'usefulCount': [5, 2, 10], + 'review_length': [1, 1, 1]} +``` + +Comme nous le soupçonnions, certaines critiques ne contiennent qu'un seul mot, ce qui, bien que cela puisse convenir à l'analyse des sentiments, n’est pas informatif si nous voulons prédire la condition. + + + +🙋 Une autre façon d'ajouter de nouvelles colonnes à un jeu de données consiste à utiliser la fonction `Dataset.add_column()`. Cela vous permet de donner la colonne sous forme de liste Python ou de tableau NumPy et peut être utile dans les situations où `Dataset.map()` n'est pas bien adapté à votre analyse. + + + +Utilisons la fonction `Dataset.filter()` pour supprimer les avis contenant moins de 30 mots. De la même manière que nous l'avons fait avec la colonne `condition`, nous pouvons filtrer les avis très courts en exigeant que les avis aient une longueur supérieure à ce seuil : + +```py +drug_dataset = drug_dataset.filter(lambda x: x["review_length"] > 30) +print(drug_dataset.num_rows) +``` + +```python out +{'train': 138514, 'test': 46108} +``` + +Comme vous pouvez le constater, cela a supprimé environ 15 % des avis de nos jeux d'entraînement et de test d'origine. + + + +✏️ **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. + + + +La dernière chose à laquelle nous devons faire face est la présence de caractères HTML dans nos avis. Nous pouvons utiliser le module `html` de Python pour supprimer ces caractères, comme ceci : + +```py +import html + +text = "I'm a transformer called BERT" +html.unescape(text) +``` + +```python out +"I'm a transformer called BERT" +``` + +Nous utilisons `Dataset.map()` pour démasquer tous les caractères HTML de notre corpus : + +```python +drug_dataset = drug_dataset.map(lambda x: {"review": html.unescape(x["review"])}) +``` + +Comme vous pouvez le voir, la méthode `Dataset.map()` est très utile pour le traitement des données. Et nous n'avons même pas effleuré la surface de tout ce qu'elle peut faire ! + +## Les superpouvoirs de la méthode `map()` + +La méthode `Dataset.map()` prend un argument `batched` qui, s'il est défini sur `True`, l'amène à envoyer un batch d'exemples à la fonction *map* en une seule fois (la taille du batch est configurable mais est fixé par défaut à 1 000). Par exemple, la fonction `map()` précédente qui supprime tout le code HTML prend un peu de temps à s'exécuter (vous pouvez lire le temps pris dans les barres de progression). On peut accélérer cela en traitant plusieurs éléments en même temps à l'aide d'une compréhension de liste. + +Lorsque vous spécifiez `batched=True`, la fonction reçoit un dictionnaire avec les champs du jeu de données mais chaque valeur est maintenant une _liste de valeurs_ et non plus une seule valeur. La valeur retournée par `Dataset.map()` devrait être la même : un dictionnaire avec les champs que nous voulons mettre à jour ou ajouter à notre jeu de données, et une liste de valeurs. Par exemple, voici une autre façon de supprimer tous les caractères HTML, mais en utilisant `batched=True` : + +```python +new_drug_dataset = drug_dataset.map( + lambda x: {"review": [html.unescape(o) for o in x["review"]]}, batched=True +) +``` + +Si vous exécutez ce code dans un *notebook*, vous verrez que cette commande s'exécute beaucoup plus rapidement que la précédente. Et ce n'est pas parce que nos critiques ont déjà été scannées au format HTML. Si vous ré-exécutez l'instruction de la section précédente (sans `batched=True`), cela prendra le même temps qu'avant. En effet, les compréhensions de liste sont généralement plus rapides que l'exécution du même code dans une boucle `for` et nous gagnons également en performances en accédant à de nombreux éléments en même temps au lieu d'un par un. + +L'utilisation de `Dataset.map()` avec `batched=True` est essentielle pour les *tokenizers rapides* que nous rencontrerons dans le [chapitre 6](/course/fr/chapter6) et qui peuvent rapidement tokeniser de grandes listes de textes. Par exemple, pour tokeniser toutes les critiques de médicaments avec un *tokenizer* rapide nous pouvons utiliser une fonction comme celle-ci : + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") + + +def tokenize_function(examples): + return tokenizer(examples["review"], truncation=True) +``` + +Comme vous l'avez vu dans le [chapitre 3](/course/fr/chapter3), nous pouvons passer un ou plusieurs exemples au *tokenizer*. Nous pouvons donc utiliser cette fonction avec ou sans `batched=True`. Profitons-en pour comparer les performances des différentes options. Dans un *notebook*, vous pouvez chronométrer une instruction d'une ligne en ajoutant `%time` avant la ligne de code que vous souhaitez mesurer : + +```python no-format +%time tokenized_dataset = drug_dataset.map(tokenize_function, batched=True) +``` + +Vous pouvez également chronométrer une cellule entière en mettant `%%time` au début de la cellule. Sur le matériel sur lequel nous avons exécuté cela, cela affichait 10,8 s pour cette instruction (c'est le nombre écrit après "Wall time"). + + + +✏️ **Essayez !** Exécutez la même instruction avec et sans `batched=True`, puis essayez-le avec un *tokenizer* lent (ajoutez `use_fast=False` dans la méthode `AutoTokenizer.from_pretrained()`) afin que vous puissiez voir quels temps vous obtenez sur votre matériel. + + + +Voici les résultats que nous avons obtenus avec et sans *batching*, avec un *tokenizer* rapide et un lent : + +Options | *Tokenizer* rapide | *Tokenizer* lent +:--------------:|:----------------:|:-----------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s + +Cela signifie que l'utilisation d'un *tokenizer* rapide avec l'option `batched=True` est 30 fois plus rapide que son homologue lent sans batch. C'est vraiment incroyable ! C'est la raison principale pour laquelle les *tokenizers* rapides sont la valeur par défaut lors de l'utilisation de `AutoTokenizer` (et pourquoi ils sont appelés « rapides »). Ils sont capables d'atteindre une telle vitesse car en coulisses le code de tokenisation est exécuté en Rust qui est un langage facilitant la parallélisation de l'exécution du code. + +La parallélisation est également la raison du gain de vitesse de près de 6 fois obtenue par le *tokenizer* rapide avec batch. Vous ne pouvez pas paralléliser une seule opération de tokenisation, mais lorsque vous souhaitez tokeniser de nombreux textes en même temps, vous pouvez simplement répartir l'exécution sur plusieurs processus. Chacun responsable de ses propres textes. + +`Dataset.map()` possède aussi ses propres capacités de parallélisation. Comme elles ne sont pas soutenus par Rust, un *tokenizer* lent ne peut pas rattraper un rapide mais cela peut toujours être utile (surtout si vous utilisez un *tokenizer* qui n'a pas de version rapide). Pour activer le multitraitement, utilisez l'argument `num_proc` et spécifiez le nombre de processus à utiliser dans votre appel à `Dataset.map()` : + +```py +slow_tokenizer = AutoTokenizer.from_pretrained("bert-base-cased", use_fast=False) + + +def slow_tokenize_function(examples): + return slow_tokenizer(examples["review"], truncation=True) + + +tokenized_dataset = drug_dataset.map(slow_tokenize_function, batched=True, num_proc=8) +``` + +Vous pouvez faire des tests pour déterminer le nombre optimal de processus à utiliser. Dans notre cas 8 semble produire le meilleur gain de vitesse. Voici les chiffres que nous avons obtenus avec et sans multitraitement : + +Options | *Tokenizer* rapide | *Tokenizer* lent +:----------------------------:|:----------------:|:---------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s +`batched=True`, `num_proc=8` | 6.52s | 41.3s +`batched=False`, `num_proc=8` | 9.49s | 45.2s + +Ce sont des résultats beaucoup plus raisonnables pour le *tokenizer* lent mais les performances du *tokenizer* rapide ont également été considérablement améliorées. Notez, cependant, que ce ne sera pas toujours le cas : pour des valeurs de `num_proc` autres que 8, nos tests ont montré qu'il était plus rapide d'utiliser `batched=True` sans cette option. En général, nous ne recommandons pas d'utiliser le multitraitement pour les *tokenizers* rapides avec `batched=True`. + + + +Utiliser `num_proc` pour accélérer votre traitement est généralement une bonne idée tant que la fonction que vous utilisez n'effectue pas déjà une sorte de multitraitement. + + + +Toutes ces fonctionnalités condensées en une seule méthode sont déjà assez étonnantes, mais il y a plus ! Avec `Dataset.map()` et `batched=True` vous pouvez modifier le nombre d'éléments dans votre jeu de données. Ceci est très utile dans de nombreuses situations où vous souhaitez créer plusieurs fonctionnalités d'entraînement à partir d'un exemple. Nous devrons le faire dans le cadre du prétraitement de plusieurs des tâches de traitement du langage naturel que nous entreprendrons dans le [chapitre 7](/course/fr/chapter7). + + + +💡 En apprentissage automatique, un _exemple_ est généralement défini comme l'ensemble de _features_ que nous donnons au modèle. Dans certains contextes, ces caractéristiques seront l'ensemble des colonnes d'un `Dataset`, mais dans d'autres (comme ici et pour la réponse aux questions), plusieurs caractéristiques peuvent être extraites d'un seul exemple et appartenir à une seule colonne. + + + +Voyons comment cela fonctionne ! Ici, nous allons tokeniser nos exemples et les tronquer à une longueur maximale de 128 mais nous demanderons au *tokenizer* de renvoyer *tous* les morceaux des textes au lieu du premier. Cela peut être fait avec `return_overflowing_tokens=True` : + +```py +def tokenize_and_split(examples): + return tokenizer( + examples["review"], + truncation=True, + max_length=128, + return_overflowing_tokens=True, + ) +``` + +Testons cela sur un exemple avant d'utiliser `Dataset.map()` sur le jeu de données : + +```py +result = tokenize_and_split(drug_dataset["train"][0]) +[len(inp) for inp in result["input_ids"]] +``` + +```python out +[128, 49] +``` + +Notre premier exemple du jeu d’entraînement est devenu deux caractéristiques car il a été segmenté à plus que le nombre maximum de *tokens* que nous avons spécifié : le premier de longueur 128 et le second de longueur 49. Faisons maintenant cela pour tous les éléments du jeu de données ! + +```py +tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) +``` + +```python out +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. + +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` : + +```py +tokenized_dataset = drug_dataset.map( + tokenize_and_split, batched=True, remove_columns=drug_dataset["train"].column_names +) +``` + +Maintenant, cela fonctionne sans erreur. Nous pouvons vérifier que notre nouveau jeu de données contient beaucoup plus d'éléments que le jeu de données d'origine en comparant les longueurs : + +```py +len(tokenized_dataset["train"]), len(drug_dataset["train"]) +``` + +```python out +(206772, 138514) +``` + +Nous avons mentionné que nous pouvions également résoudre le problème de longueur non concordante en donnant aux anciennes colonnes la même taille que les nouvelles. Pour ce faire, nous avons besoin du champ `overflow_to_sample_mapping` que le *tokenizer* renvoie lorsque nous définissons `return_overflowing_tokens=True`. Il nous donne une correspondance entre un nouvel index de caractéristique et l'index de l'échantillon dont il est issu. Grâce à cela, nous pouvons associer chaque clé présente dans notre jeu de données d'origine à une liste de valeurs de la bonne taille en répétant les valeurs de chaque exemple autant de fois qu'il génère de nouvelles caractéristiques : + +```py +def tokenize_and_split(examples): + result = tokenizer( + examples["review"], + truncation=True, + max_length=128, + return_overflowing_tokens=True, + ) + # Extract mapping between new and old indices + sample_map = result.pop("overflow_to_sample_mapping") + for key, values in examples.items(): + result[key] = [values[i] for i in sample_map] + return result +``` + +Nous pouvons voir que cela fonctionne avec `Dataset.map()` sans que nous ayons besoin de supprimer les anciennes colonnes : + +```py +tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) +tokenized_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'], + num_rows: 206772 + }) + test: Dataset({ + features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'], + num_rows: 68876 + }) +}) +``` + +Nous obtenons le même nombre de caractéristiques d'entraînement qu'auparavant, mais ici nous avons conservé tous les anciens champs. Si vous en avez besoin pour un post-traitement après l'application de votre modèle, vous pouvez utiliser cette approche. + +Vous avez maintenant vu comment 🤗 *Datasets* peut être utilisé pour prétraiter un jeu de données de différentes manières. Bien que les fonctions de traitement de 🤗 *Datasets* couvrent la plupart de vos besoins, il peut arriver que vous deviez passer à Pandas pour accéder à des fonctionnalités plus puissantes, telles que `DataFrame.groupby()` ou des API de haut niveau pour la visualisation. Heureusement, 🤗 *Datasets* est conçu pour être interopérable avec des bibliothèques telles que Pandas, NumPy, PyTorch, TensorFlow et JAX. Voyons comment cela fonctionne. + +## De `Dataset` à `DataFrame` et vice versa + + + +Pour permettre la conversion entre diverses bibliothèques tierces, 🤗 *Datasets* fournit une fonction `Dataset.set_format()`. Cette fonction ne modifie que le _format de sortie_ du jeu de données. Vous pouvez donc facilement passer à un autre format sans affecter le _format de données_ sous-jacent, qui est Apache Arrow. Le formatage se fait sur place. Pour démontrer, convertissons notre jeu de données vers Pandas : + +```py +drug_dataset.set_format("pandas") +``` + +Maintenant, lorsque nous accédons aux éléments du jeu de données, nous obtenons un `pandas.DataFrame` au lieu d'un dictionnaire : + +```py +drug_dataset["train"][:3] +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
patient_iddrugNameconditionreviewratingdateusefulCountreview_length
095260Guanfacineadhd"My son is halfway through his fourth week of Intuniv..."8.0April 27, 2010192141
192703Lybrelbirth control"I used to take another oral contraceptive, which had 21 pill cycle, and was very happy- very light periods, max 5 days, no other side effects..."5.0December 14, 200917134
2138000Ortho Evrabirth control"This is my first time using any form of birth control..."8.0November 3, 20151089
+ +Créons un `pandas.DataFrame` pour l'ensemble d'entraînement en sélectionnant tous les éléments de `drug_dataset["train"]` : + +```py +train_df = drug_dataset["train"][:] +``` + + + +🚨 Sous le capot, `Dataset.set_format()` change le format de retour pour la méthode `__getitem__()`. Cela signifie que lorsque nous voulons créer un nouvel objet comme `train_df` à partir d'un `Dataset` au format `"pandas"`, nous devons découper tout le jeu de données pour obtenir un `pandas.DataFrame`. Vous pouvez vérifier par vous-même que le type de `drug_dataset["train"]` est `Dataset`, quel que soit le format de sortie. + + + + +De là, nous pouvons utiliser toutes les fonctionnalités Pandas que nous voulons. Par exemple, nous pouvons faire un chaînage sophistiqué pour calculer la distribution de classe parmi les entrées `condition` : + +```py +frequencies = ( + train_df["condition"] + .value_counts() + .to_frame() + .reset_index() + .rename(columns={"index": "condition", "condition": "frequency"}) +) +frequencies.head() +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
conditionfrequency
0birth control27655
1depression8023
2acne5209
3anxiety4991
4pain4744
+ + +Et une fois que nous avons terminé notre analyse Pandas, nous pouvons toujours créer un nouvel objet `Dataset` en utilisant la fonction `Dataset.from_pandas()` comme suit : + + +```py +from datasets import Dataset + +freq_dataset = Dataset.from_pandas(frequencies) +freq_dataset +``` + +```python out +Dataset({ + features: ['condition', 'frequency'], + num_rows: 819 +}) +``` + + + +✏️ **Essayez !** Calculez la note moyenne par médicament et stockez le résultat dans un nouveau jeu de données. + + + +Ceci conclut notre visite des différentes techniques de prétraitement disponibles dans 🤗 *Datasets*. Pour compléter la section, créons un ensemble de validation pour préparer le jeu de données à l’entraînement d'un classifieur. Avant cela, nous allons réinitialiser le format de sortie de `drug_dataset` de `"pandas"` à `"arrow"` : + +```python +drug_dataset.reset_format() +``` + +## Création d'un ensemble de validation + +Bien que nous ayons un jeu de test que nous pourrions utiliser pour l'évaluation, il est recommandé de ne pas toucher au jeu de test et de créer un jeu de validation séparé pendant le développement. Une fois que vous êtes satisfait des performances de vos modèles sur l'ensemble de validation, vous pouvez effectuer une dernière vérification d'intégrité sur l'ensemble test. Ce processus permet d'atténuer le risque de surentraînement sur le jeu de test et de déployer un modèle qui échoue sur des données du monde réel. + +🤗 *Datasets* fournit une fonction `Dataset.train_test_split()` basée sur la célèbre fonctionnalité de `scikit-learn`. Utilisons-la pour diviser notre ensemble d'entraînement `train` et `validation` (nous définissons l'argument `seed` pour la reproductibilité) : + +```py +drug_dataset_clean = drug_dataset["train"].train_test_split(train_size=0.8, seed=42) +# Rename the default "test" split to "validation" +drug_dataset_clean["validation"] = drug_dataset_clean.pop("test") +# Add the "test" set to our `DatasetDict` +drug_dataset_clean["test"] = drug_dataset["test"] +drug_dataset_clean +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 110811 + }) + validation: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 27703 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 46108 + }) +}) +``` + +Génial, nous avons maintenant préparé un jeu de données prêt pour l'entraînement de certains modèles ! Dans la [section 5](/course/fr/chapter5/5), nous vous montrerons comment télécharger des jeux de données sur le *Hub*. Mais pour l'instant, terminons notre analyse en examinant quelques façons d'enregistrer des jeux de données sur votre ordinateur local. + +## Enregistrer un jeu de données + + + +Bien que 🤗 *Datasets* mette en cache chaque jeu de données téléchargé et les opérations qui y sont effectuées, il y a des moments où vous voudrez enregistrer un jeu de données sur le disque (par exemple, au cas où le cache serait supprimé). Comme indiqué dans le tableau ci-dessous, 🤗 *Datasets* fournit trois fonctions principales pour enregistrer votre jeu de données dans différents formats : + +| Format de données | Fonction | +| :---------------: | :----------------------: | +| Arrow | `Dataset.save_to_disk()` | +| CSV | `Dataset.to_csv()` | +| JSON | `Dataset.to_json()` | + +Par exemple, enregistrons notre jeu de données nettoyé au format Arrow : + +```py +drug_dataset_clean.save_to_disk("drug-reviews") +``` + +Cela créera un répertoire avec la structure suivante : + +``` +drug-reviews/ +├── dataset_dict.json +├── test +│ ├── dataset.arrow +│ ├── dataset_info.json +│ └── state.json +├── train +│ ├── dataset.arrow +│ ├── dataset_info.json +│ ├── indices.arrow +│ └── state.json +└── validation + ├── dataset.arrow + ├── dataset_info.json + ├── indices.arrow + └── state.json +``` + +où nous pouvons voir que chaque division est associée à sa propre table *dataset.arrow* et à certaines métadonnées dans *dataset_info.json* et *state.json*. Vous pouvez considérer le format Arrow comme un tableau sophistiqué de colonnes et de lignes optimisé pour la création d'applications hautes performances qui traitent et transportent de grands ensembles de données. + +Une fois le jeu de données enregistré, nous pouvons le charger en utilisant la fonction `load_from_disk()` comme suit : + +```py +from datasets import load_from_disk + +drug_dataset_reloaded = load_from_disk("drug-reviews") +drug_dataset_reloaded +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 110811 + }) + validation: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 27703 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 46108 + }) +}) +``` + +Pour les formats CSV et JSON, nous devons stocker chaque fractionnement dans un fichier séparé. Pour ce faire, vous pouvez parcourir les clés et les valeurs de l'objet `DatasetDict` : + +```py +for split, dataset in drug_dataset_clean.items(): + dataset.to_json(f"drug-reviews-{split}.jsonl") +``` + +Cela enregistre chaque fractionnement au [format JSON Lines](https://jsonlines.org), où chaque ligne du jeu de données est stockée sous la forme d'une seule ligne de JSON. Voici à quoi ressemble le premier exemple : + +```py +!head -n 1 drug-reviews-train.jsonl +``` + +```python out +{"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} + # Il semble que je ressente les effets secondaires habituels de LEXAPRO : insomnie, baisse de la libido, somnolence pendant la journée. Je le prends le soir parce que mon médecin m'a dit de le prendre le soir s'il me fatiguait. J'ai supposé que ce serait le cas et j'ai commencé à le prendre la nuit. Rêves étranges, certains agréables. On m'a diagnostiqué une fibromyalgie. Il semble que ce médicament aide à soulager la douleur. J'ai eu de l'anxiété et de la dépression dans ma famille, et j'ai essayé plusieurs autres médicaments qui n'ont pas fonctionné. Cela ne fait que deux semaines que je prends ce médicament, mais je me sens plus positif dans mon esprit et je veux accomplir davantage dans ma vie. J'espère que les effets secondaires vont s'estomper, cela vaut la peine de s'y tenir d'après les réponses des autres. C'est un excellent médicament. +``` + +Nous pouvons ensuite utiliser les techniques de [section 2](/course/fr/chapter5/2) pour charger les fichiers JSON comme suit : + +```py +data_files = { + "train": "drug-reviews-train.jsonl", + "validation": "drug-reviews-validation.jsonl", + "test": "drug-reviews-test.jsonl", +} +drug_dataset_reloaded = load_dataset("json", data_files=data_files) +``` + +Et c'est tout pour notre excursion dans la manipulation des données avec 🤗 *Datasets* ! Maintenant que nous disposons d'un ensemble de données nettoyé pour entraîner un modèle, voici quelques idées que vous pouvez essayer : + +1. Utilisez les techniques du [chapitre 3](/course/fr/chapter3) pour entraîner un classifieur capable de prédire l'état du patient en fonction de l'examen du médicament. +2. Utilisez le pipeline `summarization` du [chapitre 1](/course/fr/chapter1) pour générer des résumés des révisions. + +Ensuite, nous verrons comment 🤗 *Datasets* peut vous permettre de travailler avec d'énormes jeux de données sans faire exploser votre ordinateur portable ! diff --git a/chapters/fr/chapter5/4.mdx b/chapters/fr/chapter5/4.mdx index dc286c718..0b369438c 100644 --- a/chapters/fr/chapter5/4.mdx +++ b/chapters/fr/chapter5/4.mdx @@ -1,296 +1,296 @@ -# Données massives ? 🤗 Datasets à la rescousse ! - - - - -De nos jours, il n'est pas rare de travailler avec des jeux de données de plusieurs gigaoctets surtout si vous envisagez de pré-entraîner un *transformer* comme BERT ou GPT-2 à partir de zéro. Dans ces cas, même _charger_ les données peut être un défi. Par exemple, le corpus WebText utilisé pour pré-entraîner GPT-2 se compose de plus de 8 millions de documents et de 40 Go de texte. Le charger dans la RAM de votre ordinateur portable est susceptible de lui donner une crise cardiaque ! - -Heureusement, 🤗 *Datasets* a été conçu pour surmonter ces limitations. Il vous libère des problèmes de gestion de la mémoire en traitant les jeux de données comme des fichiers _mappés en mémoire_, ainsi que des limites du disque dur en faisant du _streaming_ sur les entrées dans un corpus. - - - -Dans cette section, nous allons explorer ces fonctionnalités de 🤗 *Datasets* avec un énorme corpus de 825 Go connu sous le nom de [*The Pile*](https://pile.eleuther.ai). Commençons ! - -## Qu'est-ce que The Pile ? - -*The Pile* est un corpus de texte en anglais créé par [EleutherAI](https://www.eleuther.ai) pour entraîner des modèles de langage à grande échelle. Il comprend une gamme variée de jeux de données, couvrant des articles scientifiques, des référentiels de code GitHub et du texte Web filtré. Le corpus d’entraînement est disponible en [morceaux de 14 Go](https://mystic.the-eye.eu/public/AI/pile/) et vous pouvez aussi télécharger plusieurs des [composants individuels]( https://mystic.the-eye.eu/public/AI/pile_preliminary_components/). Commençons par jeter un coup d'œil au jeu de données *PubMed Abstracts*, qui est un corpus de résumés de 15 millions de publications biomédicales sur [PubMed](https://pubmed.ncbi.nlm.nih.gov/). Le jeu de données est au [format JSON Lines](https://jsonlines.org) et est compressé à l'aide de la bibliothèque `zstandard`. Nous devons donc d'abord installer cette bibliothèque : - -```py -!pip install zstandard -``` - -Ensuite, nous pouvons charger le jeu de données en utilisant la méthode pour les fichiers distants que nous avons apprise dans [section 2](/course/fr/chapter5/2) : - -```py -from datasets import load_dataset - -# Cela prend quelques minutes à exécuter, alors allez prendre un thé ou un café en attendant :) -data_files = "https://mystic.the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst" -pubmed_dataset = load_dataset("json", data_files=data_files, split="train") -pubmed_dataset -``` - -```python out -Dataset({ - features: ['meta', 'text'], - num_rows: 15518009 -}) -``` - -Nous pouvons voir qu'il y a 15 518 009 lignes et 2 colonnes dans notre jeu de données. C'est beaucoup ! - - - -✎ 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. - - - -Inspectons le contenu du premier exemple : - -```py -pubmed_dataset[0] -``` - -```python out -{'meta': {'pmid': 11409574, 'language': 'eng'}, - '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 ...' -# Épidémiologie de l'hypoxémie chez les enfants souffrant d'une infection aiguë des voies respiratoires inférieures. Déterminer la prévalence de l'hypoxémie chez les enfants de moins de 5 ans souffrant d'une infection aiguë des voies respiratoires inférieures (IAVI), les facteurs de risque de l'hypoxémie chez les enfants de moins de 5 ans souffrant d'une IAVI, et l'association de l'hypoxémie à un risque accru de décès chez les enfants du même âge ... -} -``` - -Cela ressemble au résumé d'un article médical. Voyons maintenant combien de RAM nous avons utilisé pour charger le jeu de données ! - -## La magie du memory mapping - -Un moyen simple de mesurer l'utilisation de la mémoire dans Python consiste à utiliser la bibliothèque [`psutil`](https://psutil.readthedocs.io/en/latest/) qui peut être installée avec `pip` comme suit : - -```python -!pip install psutil -``` - -Elle fournit une classe `Process` qui permet de vérifier l'utilisation de la mémoire du processus en cours : - -```py -import psutil - -# Process.memory_info est exprimé en octets, donc convertir en mégaoctets -print(f"RAM used: {psutil.Process().memory_info().rss / (1024 * 1024):.2f} MB") -``` - -```python out -RAM used: 5678.33 MB -``` - -Ici, l'attribut `rss` fait référence à la _taille de l'ensemble résident_, qui est la fraction de mémoire qu'un processus occupe dans la RAM. Cette mesure inclut également la mémoire utilisée par l'interpréteur Python et les bibliothèques que nous avons chargées, de sorte que la quantité réelle de mémoire utilisée pour charger le jeu de données est un peu plus petite. À titre de comparaison, voyons la taille du jeu de données sur le disque en utilisant l'attribut `dataset_size`. Comme le résultat est exprimé en octets comme précédemment, nous devons le convertir manuellement en gigaoctets : - -```py -print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") -size_gb = pubmed_dataset.dataset_size / (1024**3) -print(f"Dataset size (cache file) : {size_gb:.2f} GB") -``` - -```python out -Number of files in dataset : 20979437051 -Dataset size (cache file) : 19.54 GB -``` - -Malgré sa taille de près de 20 Go, nous pouvons charger et accéder au jeu de données avec beaucoup moins de RAM ! - - - -✏️ **Essayez !** Choisissez l'un des [sous-ensembles](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/) de *The Pile* qui est plus grand que la RAM de votre ordinateur portable ou de bureau. Chargez-le avec 🤗 *Datasets* et mesurez la quantité de RAM utilisée. Notez que pour obtenir une mesure précise, vous devrez le faire dans un nouveau processus. Vous pouvez trouver les tailles décompressées de chaque sous-ensemble dans le tableau 1 du papier de [*The Pile*](https://arxiv.org/abs/2101.00027). - - - -Si vous êtes familier avec Pandas, ce résultat pourrait surprendre en raison de la célèbre [règle d'or](https://wesmckinney.com/blog/apache-arrow-pandas-internals/) de Wes Kinney selon laquelle vous avez généralement besoin de 5 à 10 fois plus de RAM que la taille de votre jeu de données. Alors, comment 🤗 *Datasets* résout-il ce problème de gestion de la mémoire ? 🤗 *Datasets* traite chaque jeu de données comme un [fichier mappé en mémoire](https://en.wikipedia.org/wiki/Memory-mapped_file). Cela fournit un mappage entre la RAM et le stockage du système de fichiers permettant à la bibliothèque d'accéder et d'opérer sur des éléments du jeu de données sans avoir besoin de le charger entièrement en mémoire. - -Les fichiers mappés en mémoire peuvent également être partagés entre plusieurs processus ce qui permet de paralléliser des méthodes telles que `Dataset.map()` sans avoir à déplacer ou copier le jeu de données. Sous le capot, ces capacités sont toutes réalisées par le format de mémoire [Apache Arrow](https://arrow.apache.org) et [`pyarrow`](https://arrow.apache.org/docs/python/index.html), qui accélèrent le chargement et le traitement des données. (Pour plus de détails sur Apache Arrow et les comparaisons avec Pandas, consultez [l'article de blog de Dejan Simic](https://towardsdatascience.com/apache-arrow-read-dataframe-with-zero-memory-69634092b1a)). Pour voir ceci en action, effectuons un petit test de vitesse en itérant sur tous les éléments du jeu de données *PubMed Abstracts* : - -```py -import timeit - -code_snippet = """batch_size = 1000 - -for idx in range(0, len(pubmed_dataset), batch_size): - _ = pubmed_dataset[idx:idx + batch_size] -""" - -time = timeit.timeit(stmt=code_snippet, number=1, globals=globals()) -print( - f"Iterated over {len(pubmed_dataset)} examples (about {size_gb:.1f} GB) in " - f"{time:.1f}s, i.e. {size_gb/time:.3f} GB/s" -) -``` - -```python out -'Iterated over 15518009 examples (about 19.5 GB) in 64.2s, i.e. 0.304 GB/s' -``` - -Ici, nous avons utilisé le module `timeit` de Python pour mesurer le temps d'exécution pris par `code_snippet`. Vous pourrez généralement itérer sur un jeu de données à une vitesse de quelques dixièmes de Go/s à plusieurs Go/s. Cela fonctionne très bien pour la grande majorité des applications, mais vous devrez parfois travailler avec un jeu de données trop volumineux pour être même stocké sur le disque dur de votre ordinateur portable. Par exemple, si nous essayions de télécharger *The Pile* dans son intégralité, nous aurions besoin de 825 Go d'espace disque libre ! Pour gérer ces cas, 🤗 *Datasets* fournit une fonctionnalité de streaming qui nous permet de télécharger et d'accéder aux éléments à la volée, sans avoir besoin de télécharger l'intégralité du jeu de données. Voyons comment cela fonctionne. - - - -💡 Dans les *notebooks* Jupyter, vous pouvez également chronométrer les cellules à l'aide de la fonction magique [`%%timeit`](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit). - - - -## Jeux de données en continu - -Pour activer le streaming du jeu de données, il vous suffit de passer l'argument `streaming=True` à la fonction `load_dataset()`. Par exemple, chargeons à nouveau le jeu de données *PubMed Abstracts* mais en mode streaming : - -```py -pubmed_dataset_streamed = load_dataset( - "json", data_files=data_files, split="train", streaming=True -) -``` - -Au lieu du familier `Dataset` que nous avons rencontré ailleurs dans ce chapitre, l'objet retourné avec `streaming=True` est un `IterableDataset`. Comme son nom l'indique, pour accéder aux éléments d'un `IterableDataset`, nous devons parcourir celui-ci. Nous pouvons accéder au premier élément de notre jeu de données diffusé comme suit : - - -```py -next(iter(pubmed_dataset_streamed)) -``` - -```python out -{'meta': {'pmid': 11409574, 'language': 'eng'}, - '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 ...'} -``` - -Les éléments d'un jeu de données diffusé en continu peuvent être traités à la volée à l'aide de `IterableDataset.map()`, ce qui est utile pendant l’entraînement si vous avez besoin de tokeniser les entrées. Le processus est exactement le même que celui que nous avons utilisé pour tokeniser notre jeu de données dans le [chapitre 3](/course/fr/chapter3), à la seule différence que les sorties sont renvoyées une par une : - -```py -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased") -tokenized_dataset = pubmed_dataset_streamed.map(lambda x: tokenizer(x["text"])) -next(iter(tokenized_dataset)) -``` - -```python out -{'input_ids': [101, 4958, 5178, 4328, 6779, ...], 'attention_mask': [1, 1, 1, 1, 1, ...]} -``` - - - -💡 Pour accélérer la tokenisation avec le streaming, vous pouvez passer `batched=True`, comme nous l'avons vu dans la dernière section. Il traitera les exemples batch par batch. La taille de batch par défaut est de 1 000 et peut être spécifiée avec l'argument `batch_size`. - - - -Vous pouvez également mélanger un jeu de données diffusé en continu à l'aide de `IterableDataset.shuffle()`, mais contrairement à `Dataset.shuffle()`, cela ne mélange que les éléments dans un `buffer_size` prédéfini : - -```py -shuffled_dataset = pubmed_dataset_streamed.shuffle(buffer_size=10_000, seed=42) -next(iter(shuffled_dataset)) -``` - -```python out -{'meta': {'pmid': 11410799, 'language': 'eng'}, - 'text': 'Randomized study of dose or schedule modification of granulocyte colony-stimulating factor in platinum-based chemotherapy for elderly patients with lung cancer ...' -# Étude randomisée sur la modification de la dose ou du calendrier d'administration du facteur de stimulation des colonies de granulocytes dans le cadre d'une chimiothérapie à base de platine chez les patients âgés atteints de cancer du poumon ... -} -``` - -Dans cet exemple, nous avons sélectionné un exemple aléatoire parmi les 10 000 premiers exemples du tampon. Une fois qu'un exemple est accédé, sa place dans le tampon est remplie avec l'exemple suivant dans le corpus (c'est-à-dire le 10 001e exemple dans le cas ci-dessus). Vous pouvez également sélectionner des éléments d'un jeu de données diffusé en continu à l'aide des fonctions `IterableDataset.take()` et `IterableDataset.skip()`, qui agissent de la même manière que `Dataset.select()`. Par exemple, pour sélectionner les 5 premiers exemples dans le jeu de données *PubMed Abstracts*, nous pouvons procéder comme suit : - -```py -dataset_head = pubmed_dataset_streamed.take(5) -list(dataset_head) -``` - -```python out -[{'meta': {'pmid': 11409574, 'language': 'eng'}, - 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...' -# Épidémiologie de l'hypoxémie chez les enfants atteints d'une infection aiguë des voies respiratoires inférieures ...}, - {'meta': {'pmid': 11409575, 'language': 'eng'}, - 'text': 'Clinical signs of hypoxaemia in children with acute lower respiratory infection: indicators of oxygen therapy ...' -# Signes cliniques d'hypoxémie chez les enfants atteints d'une infection aiguë des voies respiratoires inférieures : indicateurs de l'oxygénothérapie ...}, - {'meta': {'pmid': 11409576, 'language': 'eng'}, - 'text': "Hypoxaemia in children with severe pneumonia in Papua New Guinea ..." -# Hypoxémie chez les enfants atteints de pneumonie grave en Papouasie-Nouvelle-Guinée ...}, - {'meta': {'pmid': 11409577, 'language': 'eng'}, - 'text': 'Oxygen concentrators and cylinders ...' -# Concentrateurs et bouteilles d'oxygène...}, - {'meta': {'pmid': 11409578, 'language': 'eng'}, - 'text': 'Oxygen supply in rural africa: a personal experience ...' -# L'approvisionnement en oxygène dans les zones rurales africaines : une expérience personnelle ...}] -``` - -De même, vous pouvez utiliser la fonction `IterableDataset.skip()` pour créer des fractionnements d'entraînement et de validation à partir d'un jeu de données mélangé comme suit : - -```py -# Ignorer les 1 000 premiers exemples et inclure le reste dans l'ensemble d'apprentissage. -train_dataset = shuffled_dataset.skip(1000) -# Prendre les 1 000 premiers exemples pour l'ensemble de validation. -validation_dataset = shuffled_dataset.take(1000) -``` - -Terminons notre exploration du streaming des jeux de données avec une application commune : combiner plusieurs jeux de données pour créer un seul corpus. 🤗 *Datasets* fournit une fonction `interleave_datasets()` qui convertit une liste d'objets `IterableDataset` en un seul `IterableDataset`, où les éléments du nouveau jeu de données sont obtenus en alternant entre les exemples source. Cette fonction est particulièrement utile lorsque vous essayez de combiner de grands jeux de données. Par exemple, streamons FreeLaw, un sous-ensemble de *The Pile* et qui est un jeu de données de 51 Go d'avis juridiques de tribunaux américains : - -```py -law_dataset_streamed = load_dataset( - "json", - data_files="https://mystic.the-eye.eu/public/AI/pile_preliminary_components/FreeLaw_Opinions.jsonl.zst", - split="train", - streaming=True, -) -next(iter(law_dataset_streamed)) -``` - -```python out -{'meta': {'case_ID': '110921.json', - 'case_jurisdiction': 'scotus.tar.gz', - 'date_created': '2010-04-28T17:12:49Z'}, - 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'} -``` - -Ce jeu de données est suffisamment volumineux pour solliciter la RAM de la plupart des ordinateurs portables, mais nous avons pu le charger et y accéder sans transpirer ! Combinons maintenant les jeux de données FreeLaw et *PubMed Abstracts* avec la fonction `interleave_datasets()` : - -```py -from itertools import islice -from datasets import interleave_datasets - -combined_dataset = interleave_datasets([pubmed_dataset_streamed, law_dataset_streamed]) -list(islice(combined_dataset, 2)) -``` - -```python out -[{'meta': {'pmid': 11409574, 'language': 'eng'}, - 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'}, - {'meta': {'case_ID': '110921.json', - 'case_jurisdiction': 'scotus.tar.gz', - 'date_created': '2010-04-28T17:12:49Z'}, - 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'}] -``` - -Ici, nous avons utilisé la fonction `islice()` du module `itertools` de Python pour sélectionner les deux premiers exemples du jeu de données combiné. Nous pouvons voir qu'ils correspondent aux premiers exemples de chacun des deux jeux de données source. - -Enfin, si vous souhaitez streamer *The Pile* dans son intégralité de 825 Go, vous pouvez récupérer tous les fichiers préparés comme suit : - -```py -base_url = "https://mystic.the-eye.eu/public/AI/pile/" -data_files = { - "train": [base_url + "train/" + f"{idx:02d}.jsonl.zst" for idx in range(30)], - "validation": base_url + "val.jsonl.zst", - "test": base_url + "test.jsonl.zst", -} -pile_dataset = load_dataset("json", data_files=data_files, streaming=True) -next(iter(pile_dataset["train"])) -``` - -```python out -{'meta': {'pile_set_name': 'Pile-CC'}, - 'text': 'It is done, and submitted. You can play “Survival of the Tastiest” on Android, and on the web...'} -``` - - - -✏️ **Essayez !** Utilisez l'un des grands corpus Common Crawl comme [`mc4`](https://huggingface.co/datasets/mc4) ou [`oscar`](https://huggingface.co/datasets/oscar) pour créer en streaming un jeu de données multilingue représentant les proportions de langues parlées dans un pays de votre choix. Par exemple, les quatre langues nationales en Suisse sont l'allemand, le français, l'italien et le romanche. Vous pouvez donc essayer de créer un corpus suisse en échantillonnant les sous-ensembles Oscar en fonction de leur proportion parlée. - - - -Vous disposez maintenant de tous les outils dont vous avez besoin pour charger et traiter des jeux de données de toutes formes et tailles. Cependant à moins que vous ne soyez exceptionnellement chanceux, il arrivera un moment dans votre cheminement en traitement du langage naturel où vous devrez réellement créer un jeu de données pour résoudre un problème donné. C'est le sujet de la section suivante ! +# Données massives ? 🤗 Datasets à la rescousse ! + + + + +De nos jours, il n'est pas rare de travailler avec des jeux de données de plusieurs gigaoctets surtout si vous envisagez de pré-entraîner un *transformer* comme BERT ou GPT-2 à partir de zéro. Dans ces cas, même _charger_ les données peut être un défi. Par exemple, le corpus WebText utilisé pour pré-entraîner GPT-2 se compose de plus de 8 millions de documents et de 40 Go de texte. Le charger dans la RAM de votre ordinateur portable est susceptible de lui donner une crise cardiaque ! + +Heureusement, 🤗 *Datasets* a été conçu pour surmonter ces limitations. Il vous libère des problèmes de gestion de la mémoire en traitant les jeux de données comme des fichiers _mappés en mémoire_, ainsi que des limites du disque dur en faisant du _streaming_ sur les entrées dans un corpus. + + + +Dans cette section, nous allons explorer ces fonctionnalités de 🤗 *Datasets* avec un énorme corpus de 825 Go connu sous le nom de [*The Pile*](https://pile.eleuther.ai). Commençons ! + +## Qu'est-ce que The Pile ? + +*The Pile* est un corpus de texte en anglais créé par [EleutherAI](https://www.eleuther.ai) pour entraîner des modèles de langage à grande échelle. Il comprend une gamme variée de jeux de données, couvrant des articles scientifiques, des référentiels de code GitHub et du texte Web filtré. Le corpus d’entraînement est disponible en [morceaux de 14 Go](https://mystic.the-eye.eu/public/AI/pile/) et vous pouvez aussi télécharger plusieurs des [composants individuels]( https://mystic.the-eye.eu/public/AI/pile_preliminary_components/). Commençons par jeter un coup d'œil au jeu de données *PubMed Abstracts*, qui est un corpus de résumés de 15 millions de publications biomédicales sur [PubMed](https://pubmed.ncbi.nlm.nih.gov/). Le jeu de données est au [format JSON Lines](https://jsonlines.org) et est compressé à l'aide de la bibliothèque `zstandard`. Nous devons donc d'abord installer cette bibliothèque : + +```py +!pip install zstandard +``` + +Ensuite, nous pouvons charger le jeu de données en utilisant la méthode pour les fichiers distants que nous avons apprise dans [section 2](/course/fr/chapter5/2) : + +```py +from datasets import load_dataset + +# Cela prend quelques minutes à exécuter, alors allez prendre un thé ou un café en attendant :) +data_files = "https://mystic.the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst" +pubmed_dataset = load_dataset("json", data_files=data_files, split="train") +pubmed_dataset +``` + +```python out +Dataset({ + features: ['meta', 'text'], + num_rows: 15518009 +}) +``` + +Nous pouvons voir qu'il y a 15 518 009 lignes et 2 colonnes dans notre jeu de données. C'est beaucoup ! + + + +✎ 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. + + + +Inspectons le contenu du premier exemple : + +```py +pubmed_dataset[0] +``` + +```python out +{'meta': {'pmid': 11409574, 'language': 'eng'}, + '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 ...' +# Épidémiologie de l'hypoxémie chez les enfants souffrant d'une infection aiguë des voies respiratoires inférieures. Déterminer la prévalence de l'hypoxémie chez les enfants de moins de 5 ans souffrant d'une infection aiguë des voies respiratoires inférieures (IAVI), les facteurs de risque de l'hypoxémie chez les enfants de moins de 5 ans souffrant d'une IAVI, et l'association de l'hypoxémie à un risque accru de décès chez les enfants du même âge ... +} +``` + +Cela ressemble au résumé d'un article médical. Voyons maintenant combien de RAM nous avons utilisé pour charger le jeu de données ! + +## La magie du memory mapping + +Un moyen simple de mesurer l'utilisation de la mémoire dans Python consiste à utiliser la bibliothèque [`psutil`](https://psutil.readthedocs.io/en/latest/) qui peut être installée avec `pip` comme suit : + +```python +!pip install psutil +``` + +Elle fournit une classe `Process` qui permet de vérifier l'utilisation de la mémoire du processus en cours : + +```py +import psutil + +# Process.memory_info est exprimé en octets, donc convertir en mégaoctets +print(f"RAM used: {psutil.Process().memory_info().rss / (1024 * 1024):.2f} MB") +``` + +```python out +RAM used: 5678.33 MB +``` + +Ici, l'attribut `rss` fait référence à la _taille de l'ensemble résident_, qui est la fraction de mémoire qu'un processus occupe dans la RAM. Cette mesure inclut également la mémoire utilisée par l'interpréteur Python et les bibliothèques que nous avons chargées, de sorte que la quantité réelle de mémoire utilisée pour charger le jeu de données est un peu plus petite. À titre de comparaison, voyons la taille du jeu de données sur le disque en utilisant l'attribut `dataset_size`. Comme le résultat est exprimé en octets comme précédemment, nous devons le convertir manuellement en gigaoctets : + +```py +print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") +size_gb = pubmed_dataset.dataset_size / (1024**3) +print(f"Dataset size (cache file) : {size_gb:.2f} GB") +``` + +```python out +Number of files in dataset : 20979437051 +Dataset size (cache file) : 19.54 GB +``` + +Malgré sa taille de près de 20 Go, nous pouvons charger et accéder au jeu de données avec beaucoup moins de RAM ! + + + +✏️ **Essayez !** Choisissez l'un des [sous-ensembles](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/) de *The Pile* qui est plus grand que la RAM de votre ordinateur portable ou de bureau. Chargez-le avec 🤗 *Datasets* et mesurez la quantité de RAM utilisée. Notez que pour obtenir une mesure précise, vous devrez le faire dans un nouveau processus. Vous pouvez trouver les tailles décompressées de chaque sous-ensemble dans le tableau 1 du papier de [*The Pile*](https://arxiv.org/abs/2101.00027). + + + +Si vous êtes familier avec Pandas, ce résultat pourrait surprendre en raison de la célèbre [règle d'or](https://wesmckinney.com/blog/apache-arrow-pandas-internals/) de Wes Kinney selon laquelle vous avez généralement besoin de 5 à 10 fois plus de RAM que la taille de votre jeu de données. Alors, comment 🤗 *Datasets* résout-il ce problème de gestion de la mémoire ? 🤗 *Datasets* traite chaque jeu de données comme un [fichier mappé en mémoire](https://en.wikipedia.org/wiki/Memory-mapped_file). Cela fournit un mappage entre la RAM et le stockage du système de fichiers permettant à la bibliothèque d'accéder et d'opérer sur des éléments du jeu de données sans avoir besoin de le charger entièrement en mémoire. + +Les fichiers mappés en mémoire peuvent également être partagés entre plusieurs processus ce qui permet de paralléliser des méthodes telles que `Dataset.map()` sans avoir à déplacer ou copier le jeu de données. Sous le capot, ces capacités sont toutes réalisées par le format de mémoire [Apache Arrow](https://arrow.apache.org) et [`pyarrow`](https://arrow.apache.org/docs/python/index.html), qui accélèrent le chargement et le traitement des données. (Pour plus de détails sur Apache Arrow et les comparaisons avec Pandas, consultez [l'article de blog de Dejan Simic](https://towardsdatascience.com/apache-arrow-read-dataframe-with-zero-memory-69634092b1a)). Pour voir ceci en action, effectuons un petit test de vitesse en itérant sur tous les éléments du jeu de données *PubMed Abstracts* : + +```py +import timeit + +code_snippet = """batch_size = 1000 + +for idx in range(0, len(pubmed_dataset), batch_size): + _ = pubmed_dataset[idx:idx + batch_size] +""" + +time = timeit.timeit(stmt=code_snippet, number=1, globals=globals()) +print( + f"Iterated over {len(pubmed_dataset)} examples (about {size_gb:.1f} GB) in " + f"{time:.1f}s, i.e. {size_gb/time:.3f} GB/s" +) +``` + +```python out +'Iterated over 15518009 examples (about 19.5 GB) in 64.2s, i.e. 0.304 GB/s' +``` + +Ici, nous avons utilisé le module `timeit` de Python pour mesurer le temps d'exécution pris par `code_snippet`. Vous pourrez généralement itérer sur un jeu de données à une vitesse de quelques dixièmes de Go/s à plusieurs Go/s. Cela fonctionne très bien pour la grande majorité des applications, mais vous devrez parfois travailler avec un jeu de données trop volumineux pour être même stocké sur le disque dur de votre ordinateur portable. Par exemple, si nous essayions de télécharger *The Pile* dans son intégralité, nous aurions besoin de 825 Go d'espace disque libre ! Pour gérer ces cas, 🤗 *Datasets* fournit une fonctionnalité de streaming qui nous permet de télécharger et d'accéder aux éléments à la volée, sans avoir besoin de télécharger l'intégralité du jeu de données. Voyons comment cela fonctionne. + + + +💡 Dans les *notebooks* Jupyter, vous pouvez également chronométrer les cellules à l'aide de la fonction magique [`%%timeit`](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit). + + + +## Jeux de données en continu + +Pour activer le streaming du jeu de données, il vous suffit de passer l'argument `streaming=True` à la fonction `load_dataset()`. Par exemple, chargeons à nouveau le jeu de données *PubMed Abstracts* mais en mode streaming : + +```py +pubmed_dataset_streamed = load_dataset( + "json", data_files=data_files, split="train", streaming=True +) +``` + +Au lieu du familier `Dataset` que nous avons rencontré ailleurs dans ce chapitre, l'objet retourné avec `streaming=True` est un `IterableDataset`. Comme son nom l'indique, pour accéder aux éléments d'un `IterableDataset`, nous devons parcourir celui-ci. Nous pouvons accéder au premier élément de notre jeu de données diffusé comme suit : + + +```py +next(iter(pubmed_dataset_streamed)) +``` + +```python out +{'meta': {'pmid': 11409574, 'language': 'eng'}, + '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 ...'} +``` + +Les éléments d'un jeu de données diffusé en continu peuvent être traités à la volée à l'aide de `IterableDataset.map()`, ce qui est utile pendant l’entraînement si vous avez besoin de tokeniser les entrées. Le processus est exactement le même que celui que nous avons utilisé pour tokeniser notre jeu de données dans le [chapitre 3](/course/fr/chapter3), à la seule différence que les sorties sont renvoyées une par une : + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased") +tokenized_dataset = pubmed_dataset_streamed.map(lambda x: tokenizer(x["text"])) +next(iter(tokenized_dataset)) +``` + +```python out +{'input_ids': [101, 4958, 5178, 4328, 6779, ...], 'attention_mask': [1, 1, 1, 1, 1, ...]} +``` + + + +💡 Pour accélérer la tokenisation avec le streaming, vous pouvez passer `batched=True`, comme nous l'avons vu dans la dernière section. Il traitera les exemples batch par batch. La taille de batch par défaut est de 1 000 et peut être spécifiée avec l'argument `batch_size`. + + + +Vous pouvez également mélanger un jeu de données diffusé en continu à l'aide de `IterableDataset.shuffle()`, mais contrairement à `Dataset.shuffle()`, cela ne mélange que les éléments dans un `buffer_size` prédéfini : + +```py +shuffled_dataset = pubmed_dataset_streamed.shuffle(buffer_size=10_000, seed=42) +next(iter(shuffled_dataset)) +``` + +```python out +{'meta': {'pmid': 11410799, 'language': 'eng'}, + 'text': 'Randomized study of dose or schedule modification of granulocyte colony-stimulating factor in platinum-based chemotherapy for elderly patients with lung cancer ...' +# Étude randomisée sur la modification de la dose ou du calendrier d'administration du facteur de stimulation des colonies de granulocytes dans le cadre d'une chimiothérapie à base de platine chez les patients âgés atteints de cancer du poumon ... +} +``` + +Dans cet exemple, nous avons sélectionné un exemple aléatoire parmi les 10 000 premiers exemples du tampon. Une fois qu'un exemple est accédé, sa place dans le tampon est remplie avec l'exemple suivant dans le corpus (c'est-à-dire le 10 001e exemple dans le cas ci-dessus). Vous pouvez également sélectionner des éléments d'un jeu de données diffusé en continu à l'aide des fonctions `IterableDataset.take()` et `IterableDataset.skip()`, qui agissent de la même manière que `Dataset.select()`. Par exemple, pour sélectionner les 5 premiers exemples dans le jeu de données *PubMed Abstracts*, nous pouvons procéder comme suit : + +```py +dataset_head = pubmed_dataset_streamed.take(5) +list(dataset_head) +``` + +```python out +[{'meta': {'pmid': 11409574, 'language': 'eng'}, + 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...' +# Épidémiologie de l'hypoxémie chez les enfants atteints d'une infection aiguë des voies respiratoires inférieures ...}, + {'meta': {'pmid': 11409575, 'language': 'eng'}, + 'text': 'Clinical signs of hypoxaemia in children with acute lower respiratory infection: indicators of oxygen therapy ...' +# Signes cliniques d'hypoxémie chez les enfants atteints d'une infection aiguë des voies respiratoires inférieures : indicateurs de l'oxygénothérapie ...}, + {'meta': {'pmid': 11409576, 'language': 'eng'}, + 'text': "Hypoxaemia in children with severe pneumonia in Papua New Guinea ..." +# Hypoxémie chez les enfants atteints de pneumonie grave en Papouasie-Nouvelle-Guinée ...}, + {'meta': {'pmid': 11409577, 'language': 'eng'}, + 'text': 'Oxygen concentrators and cylinders ...' +# Concentrateurs et bouteilles d'oxygène...}, + {'meta': {'pmid': 11409578, 'language': 'eng'}, + 'text': 'Oxygen supply in rural africa: a personal experience ...' +# L'approvisionnement en oxygène dans les zones rurales africaines : une expérience personnelle ...}] +``` + +De même, vous pouvez utiliser la fonction `IterableDataset.skip()` pour créer des fractionnements d'entraînement et de validation à partir d'un jeu de données mélangé comme suit : + +```py +# Ignorer les 1 000 premiers exemples et inclure le reste dans l'ensemble d'apprentissage. +train_dataset = shuffled_dataset.skip(1000) +# Prendre les 1 000 premiers exemples pour l'ensemble de validation. +validation_dataset = shuffled_dataset.take(1000) +``` + +Terminons notre exploration du streaming des jeux de données avec une application commune : combiner plusieurs jeux de données pour créer un seul corpus. 🤗 *Datasets* fournit une fonction `interleave_datasets()` qui convertit une liste d'objets `IterableDataset` en un seul `IterableDataset`, où les éléments du nouveau jeu de données sont obtenus en alternant entre les exemples source. Cette fonction est particulièrement utile lorsque vous essayez de combiner de grands jeux de données. Par exemple, streamons FreeLaw, un sous-ensemble de *The Pile* et qui est un jeu de données de 51 Go d'avis juridiques de tribunaux américains : + +```py +law_dataset_streamed = load_dataset( + "json", + data_files="https://mystic.the-eye.eu/public/AI/pile_preliminary_components/FreeLaw_Opinions.jsonl.zst", + split="train", + streaming=True, +) +next(iter(law_dataset_streamed)) +``` + +```python out +{'meta': {'case_ID': '110921.json', + 'case_jurisdiction': 'scotus.tar.gz', + 'date_created': '2010-04-28T17:12:49Z'}, + 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'} +``` + +Ce jeu de données est suffisamment volumineux pour solliciter la RAM de la plupart des ordinateurs portables, mais nous avons pu le charger et y accéder sans transpirer ! Combinons maintenant les jeux de données FreeLaw et *PubMed Abstracts* avec la fonction `interleave_datasets()` : + +```py +from itertools import islice +from datasets import interleave_datasets + +combined_dataset = interleave_datasets([pubmed_dataset_streamed, law_dataset_streamed]) +list(islice(combined_dataset, 2)) +``` + +```python out +[{'meta': {'pmid': 11409574, 'language': 'eng'}, + 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'}, + {'meta': {'case_ID': '110921.json', + 'case_jurisdiction': 'scotus.tar.gz', + 'date_created': '2010-04-28T17:12:49Z'}, + 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'}] +``` + +Ici, nous avons utilisé la fonction `islice()` du module `itertools` de Python pour sélectionner les deux premiers exemples du jeu de données combiné. Nous pouvons voir qu'ils correspondent aux premiers exemples de chacun des deux jeux de données source. + +Enfin, si vous souhaitez streamer *The Pile* dans son intégralité de 825 Go, vous pouvez récupérer tous les fichiers préparés comme suit : + +```py +base_url = "https://mystic.the-eye.eu/public/AI/pile/" +data_files = { + "train": [base_url + "train/" + f"{idx:02d}.jsonl.zst" for idx in range(30)], + "validation": base_url + "val.jsonl.zst", + "test": base_url + "test.jsonl.zst", +} +pile_dataset = load_dataset("json", data_files=data_files, streaming=True) +next(iter(pile_dataset["train"])) +``` + +```python out +{'meta': {'pile_set_name': 'Pile-CC'}, + 'text': 'It is done, and submitted. You can play “Survival of the Tastiest” on Android, and on the web...'} +``` + + + +✏️ **Essayez !** Utilisez l'un des grands corpus Common Crawl comme [`mc4`](https://huggingface.co/datasets/mc4) ou [`oscar`](https://huggingface.co/datasets/oscar) pour créer en streaming un jeu de données multilingue représentant les proportions de langues parlées dans un pays de votre choix. Par exemple, les quatre langues nationales en Suisse sont l'allemand, le français, l'italien et le romanche. Vous pouvez donc essayer de créer un corpus suisse en échantillonnant les sous-ensembles Oscar en fonction de leur proportion parlée. + + + +Vous disposez maintenant de tous les outils dont vous avez besoin pour charger et traiter des jeux de données de toutes formes et tailles. Cependant à moins que vous ne soyez exceptionnellement chanceux, il arrivera un moment dans votre cheminement en traitement du langage naturel où vous devrez réellement créer un jeu de données pour résoudre un problème donné. C'est le sujet de la section suivante ! diff --git a/chapters/fr/chapter5/5.mdx b/chapters/fr/chapter5/5.mdx index 4781cd83c..3b1f96a41 100644 --- a/chapters/fr/chapter5/5.mdx +++ b/chapters/fr/chapter5/5.mdx @@ -1,467 +1,467 @@ -# Création de votre propre jeu de données - - - -Parfois, le jeu de données dont vous avez besoin pour créer une application de NLP n'existe pas. Vous devrez donc le créer vous-même. Dans cette section, nous allons vous montrer comment créer un corpus de [problèmes GitHub](https://github.com/features/issues/), qui sont couramment utilisés pour suivre les bogues ou les fonctionnalités dans les dépôts GitHub. Ce corpus pourrait être utilisé à diverses fins, notamment : - -* explorer combien de temps il faut pour fermer les problèmes ouverts ou les *pull requests* -* entraîner d'un _classificateur multilabel_ capable d'étiqueter les problèmes avec des métadonnées basées sur la description du problème (par exemple : « bug », « amélioration » ou « question ») -* créer un moteur de recherche sémantique pour trouver les problèmes correspondant à la requête d'un utilisateur - -Ici, nous nous concentrerons sur la création du corpus, et dans la section suivante, nous aborderons l'application de recherche sémantique. Pour garder les choses méta, nous utiliserons les problèmes GitHub associés à un projet open source populaire : 🤗 *Datasets* ! Voyons comment obtenir les données et explorons les informations contenues dans ces problèmes. - -## Obtenir les données - -Vous pouvez trouver tous les problèmes dans 🤗 *Datasets* en accédant à l'[onglet « Issues »](https://github.com/huggingface/datasets/issues) du dépôt. Comme le montre la capture d'écran suivante, au moment de la rédaction, il y avait 331 problèmes ouverts et 668 problèmes fermés. - -
-The GitHub issues associated with 🤗 Datasets. -
- -Si vous cliquez sur l'un de ces problèmes, vous constaterez qu'il contient un titre, une description et un ensemble d'étiquettes qui caractérisent le problème. Un exemple est montré dans la capture d'écran ci-dessous. - -
-A typical GitHub issue in the 🤗 Datasets repository. -
- -Pour télécharger tous les problèmes du dépôt, nous utilisons l'[API REST GitHub](https://docs.github.com/en/rest) pour interroger le point de terminaison [`Issues`](https://docs.github.com/en/rest/reference/issues#list-repository-issues). Ce point de terminaison renvoie une liste d'objets JSON. Chaque objet contenant un grand nombre de champs qui incluent le titre et la description ainsi que des métadonnées sur l'état du problème, etc. - -Un moyen pratique de télécharger les problèmes consiste à utiliser la bibliothèque `requests`, qui est la méthode standard pour effectuer des requêtes HTTP en Python. Vous pouvez installer la bibliothèque en exécutant : - -```python -!pip install requests -``` - -Une fois la bibliothèque installée, vous pouvez envoyer des requêtes GET au point de terminaison `Issues` en appelant la fonction `requests.get()`. Par exemple, vous pouvez exécuter la commande suivante pour récupérer le premier numéro sur la première page : - -```py -import requests - -url = "https://api.github.com/repos/huggingface/datasets/issues?page=1&per_page=1" -response = requests.get(url) -``` - -L'objet `response` contient de nombreuses informations utiles sur la requête, y compris le code d'état HTTP : - -```py -response.status_code -``` - -```python out -200 -``` - -où un statut `200` signifie que la requête a réussi (vous pouvez trouver une liste des codes de statut HTTP possibles [ici](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes)). Ce qui nous intéresse vraiment, cependant, c'est le _payload_, qui peut être consulté dans différents formats comme les octets, les chaînes ou JSON. Comme nous savons que nos problèmes sont au format JSON, examinons la charge utile comme suit : - -```py -response.json() -``` - -```python out -[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/2792', - 'repository_url': 'https://api.github.com/repos/huggingface/datasets', - 'labels_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/labels{/name}', - 'comments_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/comments', - 'events_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/events', - 'html_url': 'https://github.com/huggingface/datasets/pull/2792', - 'id': 968650274, - 'node_id': 'MDExOlB1bGxSZXF1ZXN0NzEwNzUyMjc0', - 'number': 2792, - 'title': 'Update GooAQ', - 'user': {'login': 'bhavitvyamalik', - 'id': 19718818, - 'node_id': 'MDQ6VXNlcjE5NzE4ODE4', - 'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4', - 'gravatar_id': '', - 'url': 'https://api.github.com/users/bhavitvyamalik', - 'html_url': 'https://github.com/bhavitvyamalik', - 'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers', - 'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}', - 'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}', - 'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}', - 'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions', - 'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs', - 'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos', - 'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}', - 'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events', - 'type': 'User', - 'site_admin': False}, - 'labels': [], - 'state': 'open', - 'locked': False, - 'assignee': None, - 'assignees': [], - 'milestone': None, - 'comments': 1, - 'created_at': '2021-08-12T11:40:18Z', - 'updated_at': '2021-08-12T12:31:17Z', - 'closed_at': None, - 'author_association': 'CONTRIBUTOR', - 'active_lock_reason': None, - 'pull_request': {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/2792', - 'html_url': 'https://github.com/huggingface/datasets/pull/2792', - 'diff_url': 'https://github.com/huggingface/datasets/pull/2792.diff', - 'patch_url': 'https://github.com/huggingface/datasets/pull/2792.patch'}, - 'body': '[GooAQ](https://github.com/allenai/gooaq) dataset was recently updated after splits were added for the same. This PR contains new updated GooAQ with train/val/test splits and updated README as well.', - 'performed_via_github_app': None}] -``` - -Waouh, ça fait beaucoup d'informations ! Nous pouvons voir des champs utiles comme `title`, `body` et `number` qui décrivent le problème, ainsi que des informations sur l'utilisateur GitHub qui a ouvert le problème. - - - -✏️ **Essayez !** Cliquez sur quelques-unes des URL pour avoir une idée du type d'informations auxquelles chaque problème GitHub est lié. - - - -Comme décrit dans la [documentation GitHub](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting), les requêtes non authentifiées sont limitées à 60 requêtes par heure. Bien que vous puissiez augmenter le paramètre de requête `per_page` pour réduire le nombre de requêtes que vous effectuez, vous atteindrez toujours la limite de débit sur tout dépôt contenant des milliers de problèmes. Donc, à la place, vous devez suivre les [instructions de GitHub](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) sur la création d'un _jeton d'accès personnel_ afin que vous peut augmenter la limite de débit à 5 000 requêtes par heure. Une fois que vous avez votre *token*, vous pouvez l'inclure dans l'en-tête de la requête : - -```py -GITHUB_TOKEN = xxx # Copy your GitHub token here -headers = {"Authorization": f"token {GITHUB_TOKEN}"} -``` - - - -⚠️ Ne partagez pas un *notebook* avec votre `GITHUB_TOKEN` collé dedans. Nous vous recommandons de supprimer la dernière cellule une fois que vous l'avez exécutée pour éviter de divulguer accidentellement ces informations. Mieux encore, stockez le jeton dans un fichier *.env* et utilisez la [bibliothèque `python-dotenv`](https://github.com/theskumar/python-dotenv) pour le charger automatiquement pour vous en tant que variable d'environnement. - - - -Maintenant que nous avons notre jeton d'accès, créons une fonction qui peut télécharger tous les problèmes depuis un référentiel GitHub : - -```py -import time -import math -from pathlib import Path -import pandas as pd -from tqdm.notebook import tqdm - - -def fetch_issues( - owner="huggingface", - repo="datasets", - num_issues=10_000, - rate_limit=5_000, - issues_path=Path("."), -): - if not issues_path.is_dir(): - issues_path.mkdir(exist_ok=True) - - batch = [] - all_issues = [] - per_page = 100 # Number of issues to return per page - num_pages = math.ceil(num_issues / per_page) - base_url = "https://api.github.com/repos" - - for page in tqdm(range(num_pages)): - # Query with state=all to get both open and closed issues - query = f"issues?page={page}&per_page={per_page}&state=all" - issues = requests.get(f"{base_url}/{owner}/{repo}/{query}", headers=headers) - batch.extend(issues.json()) - - if len(batch) > rate_limit and len(all_issues) < num_issues: - all_issues.extend(batch) - batch = [] # Flush batch for next time period - print(f"Reached GitHub rate limit. Sleeping for one hour ...") - time.sleep(60 * 60 + 1) - - all_issues.extend(batch) - df = pd.DataFrame.from_records(all_issues) - df.to_json(f"{issues_path}/{repo}-issues.jsonl", orient="records", lines=True) - print( - f"Downloaded all the issues for {repo}! Dataset stored at {issues_path}/{repo}-issues.jsonl" - ) -``` - -Désormais, lorsque nous appellerons `fetch_issues()`, tous les problèmes seront téléchargés par batchs pour éviter de dépasser la limite de GitHub sur le nombre de requêtes par heure. Le résultat sera stocké dans un fichier _repository_name-issues.jsonl_, où chaque ligne est un objet JSON qui représente un problème. Utilisons cette fonction pour saisir tous les problèmes de 🤗 *Datasets* : - -```py -# En fonction de votre connexion Internet, l'exécution peut prendre plusieurs minutes... -fetch_issues() -``` - -Une fois les problèmes téléchargés, nous pouvons les charger localement en utilisant nos nouvelles compétences de [section 2](/course/fr/chaper5/2) : - -```py -issues_dataset = load_dataset("json", data_files="datasets-issues.jsonl", split="train") -issues_dataset -``` - -```python out -Dataset({ - features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'timeline_url', 'performed_via_github_app'], - num_rows: 3019 -}) -``` - -Génial, nous avons créé notre premier jeu de données à partir de rien ! Mais pourquoi y a-t-il plusieurs milliers de problèmes alors que l'[onglet « Issues »](https://github.com/huggingface/datasets/issues) de la librairie 🤗 *Datasets* n'affiche qu'environ 1 000 problèmes au total 🤔 ? Comme décrit dans la [documentation GitHub](https://docs.github.com/en/rest/reference/issues#list-issues-assigned-to-the-authenticated-user), c'est parce que nous avons téléchargé toutes les *pull requests* également : - -> L'API REST v3 de GitHub considère chaque *pull request* comme un problème, mais chaque problème n'est pas une *pull request*. Pour cette raison, les points de terminaison « Issues » peuvent renvoyer à la fois des problèmes et des *pull requests* dans la réponse. Vous pouvez identifier les *pull requests* par la clé `pull_request`. Sachez que l'identifiant d'une *pull request* renvoyée par les points de terminaison « Issues » sera un identifiant de problème. - -Étant donné que le contenu des « Issues » et des *pull request* est assez différent, procédons à un prétraitement mineur pour nous permettre de les distinguer. - -## Nettoyer les données - -L'extrait ci-dessus de la documentation de GitHub nous indique que la colonne `pull_request` peut être utilisée pour différencier les *issues* et les *pull requests*. Regardons un échantillon aléatoire pour voir quelle est la différence. Comme nous l'avons fait dans [section 3](/course/fr/chapter5/3), nous allons enchaîner `Dataset.shuffle()` et `Dataset.select()` pour créer un échantillon aléatoire, puis compresser `html_url` et ` pull_request` afin que nous puissions comparer les différentes URL : - -```py -sample = issues_dataset.shuffle(seed=666).select(range(3)) - -# Print out the URL and pull request entries -for url, pr in zip(sample["html_url"], sample["pull_request"]): - print(f">> URL: {url}") - print(f">> Pull request: {pr}\n") -``` - -```python out ->> URL: https://github.com/huggingface/datasets/pull/850 ->> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/850', 'html_url': 'https://github.com/huggingface/datasets/pull/850', 'diff_url': 'https://github.com/huggingface/datasets/pull/850.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/850.patch'} - ->> URL: https://github.com/huggingface/datasets/issues/2773 ->> Pull request: None - ->> URL: https://github.com/huggingface/datasets/pull/783 ->> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/783', 'html_url': 'https://github.com/huggingface/datasets/pull/783', 'diff_url': 'https://github.com/huggingface/datasets/pull/783.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/783.patch'} -``` - -Ici, nous pouvons voir que chaque *pull request* est associée à diverses URL, tandis que les problèmes ordinaires ont une entrée `None`. Nous pouvons utiliser cette distinction pour créer une nouvelle colonne `is_pull_request` qui vérifie si le champ `pull_request` est `None` ou non : - -```py -issues_dataset = issues_dataset.map( - lambda x: {"is_pull_request": False if x["pull_request"] is None else True} -) -``` - - - -✏️ **Essayez !** Calculez le temps moyen nécessaire pour résoudre les problèmes dans 🤗 *Datasets*. Vous pouvez trouver la fonction `Dataset.filter()` utile pour filtrer les demandes d'extraction et les problèmes ouverts. Vous pouvez utiliser la fonction `Dataset.set_format()` pour convertir le jeu de données en un `DataFrame` afin que vous puissiez facilement manipuler les horodatages `created_at` et `closed_at`. Pour les points bonus, calculez le temps moyen nécessaire pour fermer les *pull_requests*. - - - -Bien que nous puissions continuer à nettoyer davantage le jeu de données en supprimant ou en renommant certaines colonnes, il est généralement recommandé de le conserver aussi brut que possible à ce stade afin qu'il puisse être facilement utilisé dans plusieurs applications. - -Avant de pousser notre jeu de données vers le *Hub* d’Hugging Face, traitons une chose manquante : les commentaires associés à chaque problème et *pull requests*. Nous les ajouterons ensuite avec l'API GitHub REST ! - -## Enrichir le jeu de données - -Comme le montre la capture d'écran suivante, les commentaires associés à un problème ou à une *pull request* fournissent une riche source d'informations, en particulier si nous souhaitons créer un moteur de recherche pour répondre aux requêtes des utilisateurs sur la bibliothèque. - -
-Comments associated with an issue about 🤗 Datasets. -
- -L'API REST GitHub fournit un point de terminaison [`Comments`](https://docs.github.com/en/rest/reference/issues#list-issue-comments) qui renvoie tous les commentaires associés à un numéro de problème. Testons le point de terminaison pour voir ce qu'il renvoie : - -```py -issue_number = 2792 -url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments" -response = requests.get(url, headers=headers) -response.json() -``` - -```python out -[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/comments/897594128', - 'html_url': 'https://github.com/huggingface/datasets/pull/2792#issuecomment-897594128', - 'issue_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792', - 'id': 897594128, - 'node_id': 'IC_kwDODunzps41gDMQ', - 'user': {'login': 'bhavitvyamalik', - 'id': 19718818, - 'node_id': 'MDQ6VXNlcjE5NzE4ODE4', - 'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4', - 'gravatar_id': '', - 'url': 'https://api.github.com/users/bhavitvyamalik', - 'html_url': 'https://github.com/bhavitvyamalik', - 'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers', - 'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}', - 'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}', - 'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}', - 'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions', - 'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs', - 'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos', - 'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}', - 'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events', - 'type': 'User', - 'site_admin': False}, - 'created_at': '2021-08-12T12:21:52Z', - 'updated_at': '2021-08-12T12:31:17Z', - 'author_association': 'CONTRIBUTOR', - 'body': "@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?", - 'performed_via_github_app': None}] -``` - -Nous pouvons voir que le commentaire est stocké dans le champ `body`. Ecrivons donc une fonction simple qui renvoie tous les commentaires associés à un problème en sélectionnant le contenu `body` pour chaque élément dans `response.json()` : - -```py -def get_comments(issue_number): - url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments" - response = requests.get(url, headers=headers) - return [r["body"] for r in response.json()] - - -# Tester notre fonction fonctionne comme prévu -get_comments(2792) -``` - -```python out -["@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?"] -``` - -Cela a l'air bien. Utilisons `Dataset.map()` pour ajouter une nouvelle colonne `comments` à chaque problème de notre jeu de données : - -```py -# Selon votre connexion internet, cela peut prendre quelques minutes... -issues_with_comments_dataset = issues_dataset.map( - lambda x: {"comments": get_comments(x["number"])} -) -``` - -La dernière étape consiste à enregistrer le jeu de données augmentées avec nos données brutes afin que nous puissions les pousser tous les deux vers le *Hub* : - -```py -issues_with_comments_dataset.to_json("issues-datasets-with-comments.jsonl") -``` - -## Téléchargement du jeu de données sur le Hub - - - -Maintenant que nous avons notre jeu de données augmenté, il est temps de le pousser vers le *Hub* afin que nous puissions le partager avec la communauté ! Pour télécharger le jeu de données, nous utilisons la [bibliothèque 🤗 *Hub*](https://github.com/huggingface/huggingface_hub), qui nous permet d'interagir avec le *Hub* d’Hugging Face via une API Python. 🤗 *Hub* est préinstallé avec 🤗 *Transformers*, nous pouvons donc l'utiliser directement. Par exemple, nous pouvons utiliser la fonction `list_datasets()` pour obtenir des informations sur tous les ensembles de données publics actuellement hébergés sur le *Hub*: - -```py -from huggingface_hub import list_datasets - -all_datasets = list_datasets() -print(f"Number of datasets on Hub: {len(all_datasets)}") -print(all_datasets[0]) -``` - -```python out -Number of datasets on Hub: 1487 -Dataset Name: acronym_identification, Tags: ['annotations_creators:expert-generated', 'language_creators:found', 'languages:en', 'licenses:mit', 'multilinguality:monolingual', 'size_categories:10K - -✏️ **Essayez !** Utilisez votre nom d'utilisateur et votre mot de passe Hugging Face pour obtenir un jeton et créer un dépôt vide appelé `github-issues`. N'oubliez pas de **n'enregistrez jamais vos informations d'identification** dans Colab ou tout autre référentiel car ces informations peuvent être exploitées par de mauvais individus. - - - -Ensuite, clonons le dépôt du Hub sur notre machine locale et copions-y notre fichier jeu de données. 🤗 *Hub* fournit une classe `Repository` pratique qui encapsule de nombreuses commandes Git courantes. Donc pour cloner le dépôt distant, nous devons simplement fournir l'URL et le chemin local vers lesquels nous souhaitons cloner : - -```py -from huggingface_hub import Repository - -repo = Repository(local_dir="github-issues", clone_from=repo_url) -!cp datasets-issues-with-comments.jsonl github-issues/ -``` - -Par défaut, diverses extensions de fichiers (telles que *.bin*, *.gz* et *.zip*) sont suivies avec Git LFS afin que les fichiers volumineux puissent être versionnés dans le même workflow Git. Vous pouvez trouver une liste des extensions de fichiers suivis dans le fichier *.gitattributes* du référentiel. Pour inclure le format JSON Lines dans la liste, nous pouvons exécuter la commande suivante : - -```py -repo.lfs_track("*.jsonl") -``` - -Ensuite, nous pouvons utiliser `Repository.push_to_hub()` pour pousser le jeu de données vers le *Hub* : - -```py -repo.push_to_hub() -``` - -Si nous naviguons vers l'URL contenue dans `repo_url`, nous devrions maintenant voir que notre fichier de jeu de données a été téléchargé. - -
-Our dataset repository on the Hugging Face Hub. -
- -À partir de là, n'importe qui peut télécharger le jeu de données en fournissant simplement `load_dataset()` avec l'ID du référentiel comme argument `path` : - -```py -remote_dataset = load_dataset("lewtun/github-issues", split="train") -remote_dataset -``` - -```python out -Dataset({ - features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], - num_rows: 2855 -}) -``` - -Cool, nous avons poussé notre jeu de données vers le *Hub* et il est disponible pour que d'autres puissent l'utiliser ! Il ne reste plus qu'une chose importante à faire : ajouter une _carte de jeu de données_ qui explique comment le corpus a été créé et fournit d'autres informations utiles à la communauté. - - - -💡 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. - - - -## Création d'une carte pour un jeu de données - -Des jeux de données bien documentés sont plus susceptibles d'être utiles aux autres (y compris à vous-même) car ils fournissent le contexte permettant aux utilisateurs de décider si le jeu de données est pertinent pour leur tâche et d'évaluer les biais potentiels ou les risques associés à l'utilisation du jeu de données. - -Sur le *Hub*, ces informations sont stockées dans le fichier *README.md* de chaque dépôt de jeux de données. Il y a deux étapes principales que vous devez suivre avant de créer ce fichier : - -1. Utilisez l'[application `datasets-tagging`](https://huggingface.co/datasets/tagging/) pour créer des balises de métadonnées au format YAML. Ces balises sont utilisées pour une variété de fonctionnalités de recherche sur le *Hub* d’Hugging Face et garantissent que votre jeu de données peut être facilement trouvé par les membres de la communauté. Puisque nous avons créé un jeu de données personnalisé ici, vous devrez cloner le référentiel `datasets-tagging` et exécuter l'application localement. Voici à quoi ressemble l'interface : - -
-The `datasets-tagging` interface. -
- -2. Lisez le [guide de 🤗 *Datasets*](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) sur la création des cartes informatives des jeux de données et utilisez-le comme modèle. - -Vous pouvez créer le fichier *README.md* directement sur le *Hub* et vous pouvez trouver un modèle de carte dans le dépot `lewtun/github-issues`. Une capture d'écran de la carte remplie est illustrée ci-dessous. - - -
-A dataset card. -
- - - -✏️ **Essayez !** Utilisez l'application `dataset-tagging` et [le guide de 🤗 *Datasets*](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) pour compléter le fichier *README.md* de votre jeu de données de problèmes GitHub. - - -C’est tout ! Nous avons vu dans cette section que la création d'un bon jeu de données peut être assez complexe, mais heureusement, le télécharger et le partager avec la communauté ne l'est pas. Dans la section suivante, nous utiliserons notre nouveau jeu de données pour créer un moteur de recherche sémantique avec 🤗 *Datasets* qui peut faire correspondre les questions aux problèmes et commentaires les plus pertinents. - - - -✏️ **Essayez !** Suivez les étapes que nous avons suivies dans cette section pour créer un jeu de données de problèmes GitHub pour votre bibliothèque open source préférée (choisissez autre chose que 🤗 *Datasets*, bien sûr !). Pour obtenir des points bonus, *finetunez* un classifieur multilabel pour prédire les balises présentes dans le champ `labels`. - - +# Création de votre propre jeu de données + + + +Parfois, le jeu de données dont vous avez besoin pour créer une application de NLP n'existe pas. Vous devrez donc le créer vous-même. Dans cette section, nous allons vous montrer comment créer un corpus de [problèmes GitHub](https://github.com/features/issues/), qui sont couramment utilisés pour suivre les bogues ou les fonctionnalités dans les dépôts GitHub. Ce corpus pourrait être utilisé à diverses fins, notamment : + +* explorer combien de temps il faut pour fermer les problèmes ouverts ou les *pull requests* +* entraîner d'un _classificateur multilabel_ capable d'étiqueter les problèmes avec des métadonnées basées sur la description du problème (par exemple : « bug », « amélioration » ou « question ») +* créer un moteur de recherche sémantique pour trouver les problèmes correspondant à la requête d'un utilisateur + +Ici, nous nous concentrerons sur la création du corpus, et dans la section suivante, nous aborderons l'application de recherche sémantique. Pour garder les choses méta, nous utiliserons les problèmes GitHub associés à un projet open source populaire : 🤗 *Datasets* ! Voyons comment obtenir les données et explorons les informations contenues dans ces problèmes. + +## Obtenir les données + +Vous pouvez trouver tous les problèmes dans 🤗 *Datasets* en accédant à l'[onglet « Issues »](https://github.com/huggingface/datasets/issues) du dépôt. Comme le montre la capture d'écran suivante, au moment de la rédaction, il y avait 331 problèmes ouverts et 668 problèmes fermés. + +
+The GitHub issues associated with 🤗 Datasets. +
+ +Si vous cliquez sur l'un de ces problèmes, vous constaterez qu'il contient un titre, une description et un ensemble d'étiquettes qui caractérisent le problème. Un exemple est montré dans la capture d'écran ci-dessous. + +
+A typical GitHub issue in the 🤗 Datasets repository. +
+ +Pour télécharger tous les problèmes du dépôt, nous utilisons l'[API REST GitHub](https://docs.github.com/en/rest) pour interroger le point de terminaison [`Issues`](https://docs.github.com/en/rest/reference/issues#list-repository-issues). Ce point de terminaison renvoie une liste d'objets JSON. Chaque objet contenant un grand nombre de champs qui incluent le titre et la description ainsi que des métadonnées sur l'état du problème, etc. + +Un moyen pratique de télécharger les problèmes consiste à utiliser la bibliothèque `requests`, qui est la méthode standard pour effectuer des requêtes HTTP en Python. Vous pouvez installer la bibliothèque en exécutant : + +```python +!pip install requests +``` + +Une fois la bibliothèque installée, vous pouvez envoyer des requêtes GET au point de terminaison `Issues` en appelant la fonction `requests.get()`. Par exemple, vous pouvez exécuter la commande suivante pour récupérer le premier numéro sur la première page : + +```py +import requests + +url = "https://api.github.com/repos/huggingface/datasets/issues?page=1&per_page=1" +response = requests.get(url) +``` + +L'objet `response` contient de nombreuses informations utiles sur la requête, y compris le code d'état HTTP : + +```py +response.status_code +``` + +```python out +200 +``` + +où un statut `200` signifie que la requête a réussi (vous pouvez trouver une liste des codes de statut HTTP possibles [ici](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes)). Ce qui nous intéresse vraiment, cependant, c'est le _payload_, qui peut être consulté dans différents formats comme les octets, les chaînes ou JSON. Comme nous savons que nos problèmes sont au format JSON, examinons la charge utile comme suit : + +```py +response.json() +``` + +```python out +[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/2792', + 'repository_url': 'https://api.github.com/repos/huggingface/datasets', + 'labels_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/labels{/name}', + 'comments_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/comments', + 'events_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/events', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792', + 'id': 968650274, + 'node_id': 'MDExOlB1bGxSZXF1ZXN0NzEwNzUyMjc0', + 'number': 2792, + 'title': 'Update GooAQ', + 'user': {'login': 'bhavitvyamalik', + 'id': 19718818, + 'node_id': 'MDQ6VXNlcjE5NzE4ODE4', + 'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4', + 'gravatar_id': '', + 'url': 'https://api.github.com/users/bhavitvyamalik', + 'html_url': 'https://github.com/bhavitvyamalik', + 'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers', + 'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}', + 'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}', + 'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}', + 'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions', + 'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs', + 'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos', + 'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}', + 'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events', + 'type': 'User', + 'site_admin': False}, + 'labels': [], + 'state': 'open', + 'locked': False, + 'assignee': None, + 'assignees': [], + 'milestone': None, + 'comments': 1, + 'created_at': '2021-08-12T11:40:18Z', + 'updated_at': '2021-08-12T12:31:17Z', + 'closed_at': None, + 'author_association': 'CONTRIBUTOR', + 'active_lock_reason': None, + 'pull_request': {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/2792', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792', + 'diff_url': 'https://github.com/huggingface/datasets/pull/2792.diff', + 'patch_url': 'https://github.com/huggingface/datasets/pull/2792.patch'}, + 'body': '[GooAQ](https://github.com/allenai/gooaq) dataset was recently updated after splits were added for the same. This PR contains new updated GooAQ with train/val/test splits and updated README as well.', + 'performed_via_github_app': None}] +``` + +Waouh, ça fait beaucoup d'informations ! Nous pouvons voir des champs utiles comme `title`, `body` et `number` qui décrivent le problème, ainsi que des informations sur l'utilisateur GitHub qui a ouvert le problème. + + + +✏️ **Essayez !** Cliquez sur quelques-unes des URL pour avoir une idée du type d'informations auxquelles chaque problème GitHub est lié. + + + +Comme décrit dans la [documentation GitHub](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting), les requêtes non authentifiées sont limitées à 60 requêtes par heure. Bien que vous puissiez augmenter le paramètre de requête `per_page` pour réduire le nombre de requêtes que vous effectuez, vous atteindrez toujours la limite de débit sur tout dépôt contenant des milliers de problèmes. Donc, à la place, vous devez suivre les [instructions de GitHub](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) sur la création d'un _jeton d'accès personnel_ afin que vous peut augmenter la limite de débit à 5 000 requêtes par heure. Une fois que vous avez votre *token*, vous pouvez l'inclure dans l'en-tête de la requête : + +```py +GITHUB_TOKEN = xxx # Copy your GitHub token here +headers = {"Authorization": f"token {GITHUB_TOKEN}"} +``` + + + +⚠️ Ne partagez pas un *notebook* avec votre `GITHUB_TOKEN` collé dedans. Nous vous recommandons de supprimer la dernière cellule une fois que vous l'avez exécutée pour éviter de divulguer accidentellement ces informations. Mieux encore, stockez le jeton dans un fichier *.env* et utilisez la [bibliothèque `python-dotenv`](https://github.com/theskumar/python-dotenv) pour le charger automatiquement pour vous en tant que variable d'environnement. + + + +Maintenant que nous avons notre jeton d'accès, créons une fonction qui peut télécharger tous les problèmes depuis un référentiel GitHub : + +```py +import time +import math +from pathlib import Path +import pandas as pd +from tqdm.notebook import tqdm + + +def fetch_issues( + owner="huggingface", + repo="datasets", + num_issues=10_000, + rate_limit=5_000, + issues_path=Path("."), +): + if not issues_path.is_dir(): + issues_path.mkdir(exist_ok=True) + + batch = [] + all_issues = [] + per_page = 100 # Number of issues to return per page + num_pages = math.ceil(num_issues / per_page) + base_url = "https://api.github.com/repos" + + for page in tqdm(range(num_pages)): + # Query with state=all to get both open and closed issues + query = f"issues?page={page}&per_page={per_page}&state=all" + issues = requests.get(f"{base_url}/{owner}/{repo}/{query}", headers=headers) + batch.extend(issues.json()) + + if len(batch) > rate_limit and len(all_issues) < num_issues: + all_issues.extend(batch) + batch = [] # Flush batch for next time period + print(f"Reached GitHub rate limit. Sleeping for one hour ...") + time.sleep(60 * 60 + 1) + + all_issues.extend(batch) + df = pd.DataFrame.from_records(all_issues) + df.to_json(f"{issues_path}/{repo}-issues.jsonl", orient="records", lines=True) + print( + f"Downloaded all the issues for {repo}! Dataset stored at {issues_path}/{repo}-issues.jsonl" + ) +``` + +Désormais, lorsque nous appellerons `fetch_issues()`, tous les problèmes seront téléchargés par batchs pour éviter de dépasser la limite de GitHub sur le nombre de requêtes par heure. Le résultat sera stocké dans un fichier _repository_name-issues.jsonl_, où chaque ligne est un objet JSON qui représente un problème. Utilisons cette fonction pour saisir tous les problèmes de 🤗 *Datasets* : + +```py +# En fonction de votre connexion Internet, l'exécution peut prendre plusieurs minutes... +fetch_issues() +``` + +Une fois les problèmes téléchargés, nous pouvons les charger localement en utilisant nos nouvelles compétences de [section 2](/course/fr/chaper5/2) : + +```py +issues_dataset = load_dataset("json", data_files="datasets-issues.jsonl", split="train") +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'timeline_url', 'performed_via_github_app'], + num_rows: 3019 +}) +``` + +Génial, nous avons créé notre premier jeu de données à partir de rien ! Mais pourquoi y a-t-il plusieurs milliers de problèmes alors que l'[onglet « Issues »](https://github.com/huggingface/datasets/issues) de la librairie 🤗 *Datasets* n'affiche qu'environ 1 000 problèmes au total 🤔 ? Comme décrit dans la [documentation GitHub](https://docs.github.com/en/rest/reference/issues#list-issues-assigned-to-the-authenticated-user), c'est parce que nous avons téléchargé toutes les *pull requests* également : + +> L'API REST v3 de GitHub considère chaque *pull request* comme un problème, mais chaque problème n'est pas une *pull request*. Pour cette raison, les points de terminaison « Issues » peuvent renvoyer à la fois des problèmes et des *pull requests* dans la réponse. Vous pouvez identifier les *pull requests* par la clé `pull_request`. Sachez que l'identifiant d'une *pull request* renvoyée par les points de terminaison « Issues » sera un identifiant de problème. + +Étant donné que le contenu des « Issues » et des *pull request* est assez différent, procédons à un prétraitement mineur pour nous permettre de les distinguer. + +## Nettoyer les données + +L'extrait ci-dessus de la documentation de GitHub nous indique que la colonne `pull_request` peut être utilisée pour différencier les *issues* et les *pull requests*. Regardons un échantillon aléatoire pour voir quelle est la différence. Comme nous l'avons fait dans [section 3](/course/fr/chapter5/3), nous allons enchaîner `Dataset.shuffle()` et `Dataset.select()` pour créer un échantillon aléatoire, puis compresser `html_url` et ` pull_request` afin que nous puissions comparer les différentes URL : + +```py +sample = issues_dataset.shuffle(seed=666).select(range(3)) + +# Print out the URL and pull request entries +for url, pr in zip(sample["html_url"], sample["pull_request"]): + print(f">> URL: {url}") + print(f">> Pull request: {pr}\n") +``` + +```python out +>> URL: https://github.com/huggingface/datasets/pull/850 +>> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/850', 'html_url': 'https://github.com/huggingface/datasets/pull/850', 'diff_url': 'https://github.com/huggingface/datasets/pull/850.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/850.patch'} + +>> URL: https://github.com/huggingface/datasets/issues/2773 +>> Pull request: None + +>> URL: https://github.com/huggingface/datasets/pull/783 +>> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/783', 'html_url': 'https://github.com/huggingface/datasets/pull/783', 'diff_url': 'https://github.com/huggingface/datasets/pull/783.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/783.patch'} +``` + +Ici, nous pouvons voir que chaque *pull request* est associée à diverses URL, tandis que les problèmes ordinaires ont une entrée `None`. Nous pouvons utiliser cette distinction pour créer une nouvelle colonne `is_pull_request` qui vérifie si le champ `pull_request` est `None` ou non : + +```py +issues_dataset = issues_dataset.map( + lambda x: {"is_pull_request": False if x["pull_request"] is None else True} +) +``` + + + +✏️ **Essayez !** Calculez le temps moyen nécessaire pour résoudre les problèmes dans 🤗 *Datasets*. Vous pouvez trouver la fonction `Dataset.filter()` utile pour filtrer les demandes d'extraction et les problèmes ouverts. Vous pouvez utiliser la fonction `Dataset.set_format()` pour convertir le jeu de données en un `DataFrame` afin que vous puissiez facilement manipuler les horodatages `created_at` et `closed_at`. Pour les points bonus, calculez le temps moyen nécessaire pour fermer les *pull_requests*. + + + +Bien que nous puissions continuer à nettoyer davantage le jeu de données en supprimant ou en renommant certaines colonnes, il est généralement recommandé de le conserver aussi brut que possible à ce stade afin qu'il puisse être facilement utilisé dans plusieurs applications. + +Avant de pousser notre jeu de données vers le *Hub* d’Hugging Face, traitons une chose manquante : les commentaires associés à chaque problème et *pull requests*. Nous les ajouterons ensuite avec l'API GitHub REST ! + +## Enrichir le jeu de données + +Comme le montre la capture d'écran suivante, les commentaires associés à un problème ou à une *pull request* fournissent une riche source d'informations, en particulier si nous souhaitons créer un moteur de recherche pour répondre aux requêtes des utilisateurs sur la bibliothèque. + +
+Comments associated with an issue about 🤗 Datasets. +
+ +L'API REST GitHub fournit un point de terminaison [`Comments`](https://docs.github.com/en/rest/reference/issues#list-issue-comments) qui renvoie tous les commentaires associés à un numéro de problème. Testons le point de terminaison pour voir ce qu'il renvoie : + +```py +issue_number = 2792 +url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments" +response = requests.get(url, headers=headers) +response.json() +``` + +```python out +[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/comments/897594128', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792#issuecomment-897594128', + 'issue_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792', + 'id': 897594128, + 'node_id': 'IC_kwDODunzps41gDMQ', + 'user': {'login': 'bhavitvyamalik', + 'id': 19718818, + 'node_id': 'MDQ6VXNlcjE5NzE4ODE4', + 'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4', + 'gravatar_id': '', + 'url': 'https://api.github.com/users/bhavitvyamalik', + 'html_url': 'https://github.com/bhavitvyamalik', + 'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers', + 'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}', + 'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}', + 'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}', + 'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions', + 'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs', + 'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos', + 'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}', + 'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events', + 'type': 'User', + 'site_admin': False}, + 'created_at': '2021-08-12T12:21:52Z', + 'updated_at': '2021-08-12T12:31:17Z', + 'author_association': 'CONTRIBUTOR', + 'body': "@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?", + 'performed_via_github_app': None}] +``` + +Nous pouvons voir que le commentaire est stocké dans le champ `body`. Ecrivons donc une fonction simple qui renvoie tous les commentaires associés à un problème en sélectionnant le contenu `body` pour chaque élément dans `response.json()` : + +```py +def get_comments(issue_number): + url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments" + response = requests.get(url, headers=headers) + return [r["body"] for r in response.json()] + + +# Tester notre fonction fonctionne comme prévu +get_comments(2792) +``` + +```python out +["@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?"] +``` + +Cela a l'air bien. Utilisons `Dataset.map()` pour ajouter une nouvelle colonne `comments` à chaque problème de notre jeu de données : + +```py +# Selon votre connexion internet, cela peut prendre quelques minutes... +issues_with_comments_dataset = issues_dataset.map( + lambda x: {"comments": get_comments(x["number"])} +) +``` + +La dernière étape consiste à enregistrer le jeu de données augmentées avec nos données brutes afin que nous puissions les pousser tous les deux vers le *Hub* : + +```py +issues_with_comments_dataset.to_json("issues-datasets-with-comments.jsonl") +``` + +## Téléchargement du jeu de données sur le Hub + + + +Maintenant que nous avons notre jeu de données augmenté, il est temps de le pousser vers le *Hub* afin que nous puissions le partager avec la communauté ! Pour télécharger le jeu de données, nous utilisons la [bibliothèque 🤗 *Hub*](https://github.com/huggingface/huggingface_hub), qui nous permet d'interagir avec le *Hub* d’Hugging Face via une API Python. 🤗 *Hub* est préinstallé avec 🤗 *Transformers*, nous pouvons donc l'utiliser directement. Par exemple, nous pouvons utiliser la fonction `list_datasets()` pour obtenir des informations sur tous les ensembles de données publics actuellement hébergés sur le *Hub*: + +```py +from huggingface_hub import list_datasets + +all_datasets = list_datasets() +print(f"Number of datasets on Hub: {len(all_datasets)}") +print(all_datasets[0]) +``` + +```python out +Number of datasets on Hub: 1487 +Dataset Name: acronym_identification, Tags: ['annotations_creators:expert-generated', 'language_creators:found', 'languages:en', 'licenses:mit', 'multilinguality:monolingual', 'size_categories:10K + +✏️ **Essayez !** Utilisez votre nom d'utilisateur et votre mot de passe Hugging Face pour obtenir un jeton et créer un dépôt vide appelé `github-issues`. N'oubliez pas de **n'enregistrez jamais vos informations d'identification** dans Colab ou tout autre référentiel car ces informations peuvent être exploitées par de mauvais individus. + + + +Ensuite, clonons le dépôt du Hub sur notre machine locale et copions-y notre fichier jeu de données. 🤗 *Hub* fournit une classe `Repository` pratique qui encapsule de nombreuses commandes Git courantes. Donc pour cloner le dépôt distant, nous devons simplement fournir l'URL et le chemin local vers lesquels nous souhaitons cloner : + +```py +from huggingface_hub import Repository + +repo = Repository(local_dir="github-issues", clone_from=repo_url) +!cp datasets-issues-with-comments.jsonl github-issues/ +``` + +Par défaut, diverses extensions de fichiers (telles que *.bin*, *.gz* et *.zip*) sont suivies avec Git LFS afin que les fichiers volumineux puissent être versionnés dans le même workflow Git. Vous pouvez trouver une liste des extensions de fichiers suivis dans le fichier *.gitattributes* du référentiel. Pour inclure le format JSON Lines dans la liste, nous pouvons exécuter la commande suivante : + +```py +repo.lfs_track("*.jsonl") +``` + +Ensuite, nous pouvons utiliser `Repository.push_to_hub()` pour pousser le jeu de données vers le *Hub* : + +```py +repo.push_to_hub() +``` + +Si nous naviguons vers l'URL contenue dans `repo_url`, nous devrions maintenant voir que notre fichier de jeu de données a été téléchargé. + +
+Our dataset repository on the Hugging Face Hub. +
+ +À partir de là, n'importe qui peut télécharger le jeu de données en fournissant simplement `load_dataset()` avec l'ID du référentiel comme argument `path` : + +```py +remote_dataset = load_dataset("lewtun/github-issues", split="train") +remote_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 2855 +}) +``` + +Cool, nous avons poussé notre jeu de données vers le *Hub* et il est disponible pour que d'autres puissent l'utiliser ! Il ne reste plus qu'une chose importante à faire : ajouter une _carte de jeu de données_ qui explique comment le corpus a été créé et fournit d'autres informations utiles à la communauté. + + + +💡 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. + + + +## Création d'une carte pour un jeu de données + +Des jeux de données bien documentés sont plus susceptibles d'être utiles aux autres (y compris à vous-même) car ils fournissent le contexte permettant aux utilisateurs de décider si le jeu de données est pertinent pour leur tâche et d'évaluer les biais potentiels ou les risques associés à l'utilisation du jeu de données. + +Sur le *Hub*, ces informations sont stockées dans le fichier *README.md* de chaque dépôt de jeux de données. Il y a deux étapes principales que vous devez suivre avant de créer ce fichier : + +1. Utilisez l'[application `datasets-tagging`](https://huggingface.co/datasets/tagging/) pour créer des balises de métadonnées au format YAML. Ces balises sont utilisées pour une variété de fonctionnalités de recherche sur le *Hub* d’Hugging Face et garantissent que votre jeu de données peut être facilement trouvé par les membres de la communauté. Puisque nous avons créé un jeu de données personnalisé ici, vous devrez cloner le référentiel `datasets-tagging` et exécuter l'application localement. Voici à quoi ressemble l'interface : + +
+The `datasets-tagging` interface. +
+ +2. Lisez le [guide de 🤗 *Datasets*](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) sur la création des cartes informatives des jeux de données et utilisez-le comme modèle. + +Vous pouvez créer le fichier *README.md* directement sur le *Hub* et vous pouvez trouver un modèle de carte dans le dépot `lewtun/github-issues`. Une capture d'écran de la carte remplie est illustrée ci-dessous. + + +
+A dataset card. +
+ + + +✏️ **Essayez !** Utilisez l'application `dataset-tagging` et [le guide de 🤗 *Datasets*](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) pour compléter le fichier *README.md* de votre jeu de données de problèmes GitHub. + + +C’est tout ! Nous avons vu dans cette section que la création d'un bon jeu de données peut être assez complexe, mais heureusement, le télécharger et le partager avec la communauté ne l'est pas. Dans la section suivante, nous utiliserons notre nouveau jeu de données pour créer un moteur de recherche sémantique avec 🤗 *Datasets* qui peut faire correspondre les questions aux problèmes et commentaires les plus pertinents. + + + +✏️ **Essayez !** Suivez les étapes que nous avons suivies dans cette section pour créer un jeu de données de problèmes GitHub pour votre bibliothèque open source préférée (choisissez autre chose que 🤗 *Datasets*, bien sûr !). Pour obtenir des points bonus, *finetunez* un classifieur multilabel pour prédire les balises présentes dans le champ `labels`. + + diff --git a/chapters/fr/chapter5/6.mdx b/chapters/fr/chapter5/6.mdx index dcb42deb5..69762e98a 100644 --- a/chapters/fr/chapter5/6.mdx +++ b/chapters/fr/chapter5/6.mdx @@ -1,530 +1,530 @@ - - -# Recherche sémantique avec FAISS - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -Dans [section 5](/course/fr/chapter5/5), nous avons créé un jeu de données de problèmes et de commentaires GitHub à partir du dépôt 🤗 *Datasets*. Dans cette section, nous utilisons ces informations pour créer un moteur de recherche qui peut nous aider à trouver des réponses à nos questions les plus urgentes sur la bibliothèque ! - - - -## Utilisation des enchâssements pour la recherche sémantique - -Comme nous l'avons vu dans le [chapitre 1](/course/fr/chapter1), les modèles de langage basés sur les *transformers* représentent chaque *token* dans une étendue de texte sous la forme d'un _enchâssement_. Il s'avère que l'on peut regrouper les enchâssements individuels pour créer une représentation vectorielle pour des phrases entières, des paragraphes ou (dans certains cas) des documents. Ces enchâssements peuvent ensuite être utilisés pour trouver des documents similaires dans le corpus en calculant la similarité du produit scalaire (ou une autre métrique de similarité) entre chaque enchâssement et en renvoyant les documents avec le plus grand chevauchement. - -Dans cette section, nous utilisons les enchâssements pour développer un moteur de recherche sémantique. Ces moteurs de recherche offrent plusieurs avantages par rapport aux approches conventionnelles basées sur la correspondance des mots-clés dans une requête avec les documents. - -
-Recherche sémantique. - -
- -## Chargement et préparation du jeu de données - -La première chose que nous devons faire est de télécharger notre jeu de données de problèmes GitHub. Utilisons la bibliothèque 🤗 *Hub* pour résoudre l'URL où notre fichier est stocké sur le *Hub* d’Hugging Face : - -```py -from huggingface_hub import hf_hub_url - -data_files = hf_hub_url( - repo_id="lewtun/github-issues", - filename="datasets-issues-with-comments.jsonl", - repo_type="dataset", -) -``` - -Avec l'URL stocké dans `data_files`, nous pouvons ensuite charger le jeu de données distant en utilisant la méthode introduite dans [section 2](/course/fr/chapter5/2) : - -```py -from datasets import load_dataset - -issues_dataset = load_dataset("json", data_files=data_files, split="train") -issues_dataset -``` - -```python out -Dataset({ - features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], - num_rows: 2855 -}) -``` - -Ici, nous avons spécifié l’échantillon `train` par défaut dans `load_dataset()`, de sorte que cela renvoie un `Dataset` au lieu d'un `DatasetDict`. La première chose à faire est de filtrer les *pull requests* car celles-ci ont tendance à être rarement utilisées pour répondre aux requêtes des utilisateurs et introduiront du bruit dans notre moteur de recherche. Comme cela devrait être familier maintenant, nous pouvons utiliser la fonction `Dataset.filter()` pour exclure ces lignes de notre jeu de données. Pendant que nous y sommes, filtrons également les lignes sans commentaires, car celles-ci ne fournissent aucune réponse aux requêtes des utilisateurs : - -```py -issues_dataset = issues_dataset.filter( - lambda x: (x["is_pull_request"] == False and len(x["comments"]) > 0) -) -issues_dataset -``` - -```python out -Dataset({ - features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], - num_rows: 771 -}) -``` - -Nous pouvons voir qu'il y a beaucoup de colonnes dans notre jeu de données, dont la plupart n'ont pas besoin de construire notre moteur de recherche. Du point de vue de la recherche, les colonnes les plus informatives sont `title`, `body` et `comments`, tandis que `html_url` nous fournit un lien vers le problème source. Utilisons la fonction `Dataset.remove_columns()` pour supprimer le reste : - -```py -columns = issues_dataset.column_names -columns_to_keep = ["title", "body", "html_url", "comments"] -columns_to_remove = set(columns_to_keep).symmetric_difference(columns) -issues_dataset = issues_dataset.remove_columns(columns_to_remove) -issues_dataset -``` - -```python out -Dataset({ - features: ['html_url', 'title', 'comments', 'body'], - num_rows: 771 -}) -``` - -Pour créer nos enchâssements, nous ajoutons à chaque commentaire le titre et le corps du problème, car ces champs contiennent des informations contextuelles utiles. Étant donné que notre colonne `comments` est actuellement une liste de commentaires pour chaque problème, nous devons « éclater » la colonne afin que chaque ligne se compose d'un *tuple* `(html_url, title, body, comment)`. Dans Pandas, nous pouvons le faire avec la fonction [`DataFrame.explode()`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html), qui crée une nouvelle ligne pour chaque élément dans une colonne de type liste, tout en répliquant toutes les autres valeurs de colonne. Pour voir cela en action, passons d'abord au format `DataFrame` de Pandas : - -```py -issues_dataset.set_format("pandas") -df = issues_dataset[:] -``` - -Si nous inspectons la première ligne de ce `DataFrame`, nous pouvons voir qu'il y a quatre commentaires associés à ce problème : - -```py -df["comments"][0].tolist() -``` - -```python out -['the bug code locate in :\r\n if data_args.task_name is not None:\r\n # Downloading and loading a dataset from the hub.\r\n datasets = load_dataset("glue", data_args.task_name, cache_dir=model_args.cache_dir)', - 'Hi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com\r\n\r\nNormally, it should work if you wait a little and then retry.\r\n\r\nCould you please confirm if the problem persists?', - 'cannot connect,even by Web browser,please check that there is some problems。', - 'I can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...'] -``` - -Lorsque nous décomposons `df`, nous nous attendons à obtenir une ligne pour chacun de ces commentaires. Vérifions si c'est le cas : - -```py -comments_df = df.explode("comments", ignore_index=True) -comments_df.head(4) -``` - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
html_urltitlecommentsbody
0https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comthe bug code locate in :\r\n if data_args.task_name is not None...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
1https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comHi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
2https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comcannot connect,even by Web browser,please check that there is some problems。Hello,\r\nI am trying to run run_glue.py and it gives me this error...
3https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comI can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
- -Génial, nous pouvons voir que les lignes ont été répliquées, avec la colonne `comments` contenant les commentaires individuels ! Maintenant que nous en avons fini avec Pandas, nous pouvons rapidement revenir à un `Dataset` en chargeant le `DataFrame` en mémoire : - -```py -from datasets import Dataset - -comments_dataset = Dataset.from_pandas(comments_df) -comments_dataset -``` - -```python out -Dataset({ - features: ['html_url', 'title', 'comments', 'body'], - num_rows: 2842 -}) -``` - -D'accord, cela nous a donné quelques milliers de commentaires avec lesquels travailler ! - - - - -✏️ **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. - - - -Maintenant que nous avons un commentaire par ligne, créons une nouvelle colonne `comments_length` contenant le nombre de mots par commentaire : - -```py -comments_dataset = comments_dataset.map( - lambda x: {"comment_length": len(x["comments"].split())} -) -``` - -Nous pouvons utiliser cette nouvelle colonne pour filtrer les commentaires courts incluant généralement des éléments tels que « cc @lewtun » ou « Merci ! » qui ne sont pas pertinents pour notre moteur de recherche. Il n'y a pas de nombre précis à sélectionner pour le filtre mais 15 mots semblent être un bon début : - -```py -comments_dataset = comments_dataset.filter(lambda x: x["comment_length"] > 15) -comments_dataset -``` - -```python out -Dataset({ - features: ['html_url', 'title', 'comments', 'body', 'comment_length'], - num_rows: 2098 -}) -``` - -Après avoir un peu nettoyé notre jeu de données, concaténons le titre, la description et les commentaires du problème dans une nouvelle colonne `text`. Comme d'habitude, nous allons écrire une fonction simple que nous pouvons passer à `Dataset.map()` : - -```py -def concatenate_text(examples): - return { - "text": examples["title"] - + " \n " - + examples["body"] - + " \n " - + examples["comments"] - } - - -comments_dataset = comments_dataset.map(concatenate_text) -``` - -Nous sommes enfin prêts à créer des enchâssements ! Jetons un coup d'œil. - -## Création d’enchâssements pour les textes - -Nous avons vu dans [chapitre 2](/course/fr/chapter2) que nous pouvons obtenir des enchâssements de *tokens* en utilisant la classe `AutoModel`. Tout ce que nous avons à faire est de choisir un *checkpoint* approprié à partir duquel charger le modèle. Heureusement, il existe une bibliothèque appelée `sentence-transformers` dédiée à la création d’enchâssements. Comme décrit dans la [documentation de la bibliothèque](https://www.sbert.net/examples/applications/semantic-search/README.html#symmetric-vs-asymmetric-semantic-search), notre cas d'utilisation est un exemple de _recherche sémantique asymétrique_. En effet, nous avons une requête courte dont nous aimerions trouver la réponse dans un document plus long, par exemple un commentaire à un problème. Le [tableau de présentation des modèles](https://www.sbert.net/docs/pretrained_models.html#model-overview) de la documentation indique que le *checkpoint* `multi-qa-mpnet-base-dot-v1` a les meilleures performances pour la recherche sémantique. Utilisons donc le pour notre application. Nous allons également charger le *tokenizer* en utilisant le même *checkpoint* : - -{#if fw === 'pt'} - -```py -from transformers import AutoTokenizer, AutoModel - -model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1" -tokenizer = AutoTokenizer.from_pretrained(model_ckpt) -model = AutoModel.from_pretrained(model_ckpt) -``` - -Pour accélérer le processus, il est utile de placer le modèle et les entrées sur un périphérique GPU, alors faisons-le maintenant : - -```py -import torch - -device = torch.device("cuda") -model.to(device) -``` - -{:else} - -```py -from transformers import AutoTokenizer, TFAutoModel - -model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1" -tokenizer = AutoTokenizer.from_pretrained(model_ckpt) -model = TFAutoModel.from_pretrained(model_ckpt, from_pt=True) -``` - -Notez que nous avons défini `from_pt=True` comme argument de la méthode `from_pretrained()`. C'est parce que le point de contrôle `multi-qa-mpnet-base-dot-v1` n'a que des poids PyTorch. Donc définir `from_pt=True` converti automatiquement au format TensorFlow pour nous. Comme vous pouvez le voir, il est très simple de passer d'un *framework* à l'autre dans 🤗 *Transformers* ! - -{/if} - -Comme nous l'avons mentionné précédemment, nous aimerions représenter chaque entrée dans notre corpus de problèmes GitHub comme un vecteur unique. Nous devons donc regrouper ou faire la moyenne de nos enchâssements de *tokens* d'une manière ou d'une autre. Une approche populaire consiste à effectuer un *regroupement CLS* sur les sorties de notre modèle, où nous collectons simplement le dernier état caché pour le *token* spécial `[CLS]`. La fonction suivante fait ça pour nous : - -```py -def cls_pooling(model_output): - return model_output.last_hidden_state[:, 0] -``` - -Ensuite, nous allons créer une fonction utile qui va tokeniser une liste de documents, placer les tenseurs dans le GPU, les donner au modèle et enfin appliquer le regroupement CLS aux sorties : - -{#if fw === 'pt'} - -```py -def get_embeddings(text_list): - encoded_input = tokenizer( - text_list, padding=True, truncation=True, return_tensors="pt" - ) - encoded_input = {k: v.to(device) for k, v in encoded_input.items()} - model_output = model(**encoded_input) - return cls_pooling(model_output) -``` - -Nous pouvons tester le fonctionnement de la fonction en lui donnant la première entrée textuelle de notre corpus et en inspectant la forme de sortie : - -```py -embedding = get_embeddings(comments_dataset["text"][0]) -embedding.shape -``` - -```python out -torch.Size([1, 768]) -``` - -Super ! Nous avons converti la première entrée de notre corpus en un vecteur à 768 dimensions. Nous pouvons utiliser `Dataset.map()` pour appliquer notre fonction `get_embeddings()` à chaque ligne de notre corpus. Créons donc une nouvelle colonne `embeddings` comme suit : - -```py -embeddings_dataset = comments_dataset.map( - lambda x: {"embeddings": get_embeddings(x["text"]).detach().cpu().numpy()[0]} -) -``` - -{:else} - -```py -def get_embeddings(text_list): - encoded_input = tokenizer( - text_list, padding=True, truncation=True, return_tensors="tf" - ) - encoded_input = {k: v for k, v in encoded_input.items()} - model_output = model(**encoded_input) - return cls_pooling(model_output) -``` - -Nous pouvons tester le fonctionnement de la fonction en lui donnant la première entrée textuelle de notre corpus et en inspectant la forme de sortie : - -```py -embedding = get_embeddings(comments_dataset["text"][0]) -embedding.shape -``` - -```python out -TensorShape([1, 768]) -``` - -Super ! Nous avons converti la première entrée de notre corpus en un vecteur à 768 dimensions. Nous pouvons utiliser `Dataset.map()` pour appliquer notre fonction `get_embeddings()` à chaque ligne de notre corpus. Créons donc une nouvelle colonne `embeddings` comme suit : - -```py -embeddings_dataset = comments_dataset.map( - lambda x: {"embeddings": get_embeddings(x["text"]).numpy()[0]} -) -``` - -{/if} - - -Notez que nous avons converti les enchâssements en tableaux NumPy. C'est parce que 🤗 *Datasets* nécessite ce format lorsque nous essayons de les indexer avec FAISS, ce que nous ferons ensuite. - -## Utilisation de FAISS pour une recherche de similarité efficace - -Maintenant que nous avons un jeu de données d'incorporations, nous avons besoin d'un moyen de les rechercher. Pour ce faire, nous utiliserons une structure de données spéciale dans 🤗 *Datasets* appelée _FAISS index_. [FAISS](https://faiss.ai/) (abréviation de *Facebook AI Similarity Search*) est une bibliothèque qui fournit des algorithmes efficaces pour rechercher et regrouper rapidement des vecteurs d'intégration. - -L'idée de base derrière FAISS est de créer une structure de données spéciale appelée un _index_ qui permet de trouver quels plongements sont similaires à un plongement d'entrée. Créer un index FAISS dans 🤗 *Datasets* est simple -- nous utilisons la fonction `Dataset.add_faiss_index()` et spécifions quelle colonne de notre jeu de données nous aimerions indexer : - -```py -embeddings_dataset.add_faiss_index(column="embeddings") -``` - -Nous pouvons maintenant effectuer des requêtes sur cet index en effectuant une recherche des voisins les plus proches avec la fonction `Dataset.get_nearest_examples()`. Testons cela en enchâssant d'abord une question comme suit : - -{#if fw === 'pt'} - -```py -question = "How can I load a dataset offline?" -question_embedding = get_embeddings([question]).cpu().detach().numpy() -question_embedding.shape -``` - -```python out -torch.Size([1, 768]) -``` - -{:else} - -```py -question = "How can I load a dataset offline?" -question_embedding = get_embeddings([question]).numpy() -question_embedding.shape -``` - -```python out -(1, 768) -``` - -{/if} - -Tout comme avec les documents, nous avons maintenant un vecteur de 768 dimensions représentant la requête. Nous pouvons le comparer à l’ensemble du corpus pour trouver les enchâssements les plus similaires : - -```py -scores, samples = embeddings_dataset.get_nearest_examples( - "embeddings", question_embedding, k=5 -) -``` - -La fonction `Dataset.get_nearest_examples()` renvoie un *tuple* de scores qui classent le chevauchement entre la requête et le document, et un jeu correspondant d'échantillons (ici, les 5 meilleures correspondances). Collectons-les dans un `pandas.DataFrame` afin de pouvoir les trier facilement : - -```py -import pandas as pd - -samples_df = pd.DataFrame.from_dict(samples) -samples_df["scores"] = scores -samples_df.sort_values("scores", ascending=False, inplace=True) -``` - -Nous pouvons maintenant parcourir les premières lignes pour voir dans quelle mesure notre requête correspond aux commentaires disponibles : - -```py -for _, row in samples_df.iterrows(): - print(f"COMMENT: {row.comments}") - print(f"SCORE: {row.scores}") - print(f"TITLE: {row.title}") - print(f"URL: {row.html_url}") - print("=" * 50) - print() -``` - -```python out -""" -COMMENT: Requiring online connection is a deal breaker in some cases unfortunately so it'd be great if offline mode is added similar to how `transformers` loads models offline fine. - -@mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like? -SCORE: 25.505046844482422 -TITLE: Discussion using datasets in offline mode -URL: https://github.com/huggingface/datasets/issues/824 -================================================== - -COMMENT: The local dataset builders (csv, text , json and pandas) are now part of the `datasets` package since #1726 :) -You can now use them offline -\`\`\`python -datasets = load_dataset("text", data_files=data_files) -\`\`\` - -We'll do a new release soon -SCORE: 24.555509567260742 -TITLE: Discussion using datasets in offline mode -URL: https://github.com/huggingface/datasets/issues/824 -================================================== - -COMMENT: I opened a PR that allows to reload modules that have already been loaded once even if there's no internet. - -Let me know if you know other ways that can make the offline mode experience better. I'd be happy to add them :) - -I already note the "freeze" modules option, to prevent local modules updates. It would be a cool feature. - ----------- - -> @mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like? - -Indeed `load_dataset` allows to load remote dataset script (squad, glue, etc.) but also you own local ones. -For example if you have a dataset script at `./my_dataset/my_dataset.py` then you can do -\`\`\`python -load_dataset("./my_dataset") -\`\`\` -and the dataset script will generate your dataset once and for all. - ----------- - -About I'm looking into having `csv`, `json`, `text`, `pandas` dataset builders already included in the `datasets` package, so that they are available offline by default, as opposed to the other datasets that require the script to be downloaded. -cf #1724 -SCORE: 24.14896583557129 -TITLE: Discussion using datasets in offline mode -URL: https://github.com/huggingface/datasets/issues/824 -================================================== - -COMMENT: > here is my way to load a dataset offline, but it **requires** an online machine -> -> 1. (online machine) -> -> ``` -> -> import datasets -> -> data = datasets.load_dataset(...) -> -> data.save_to_disk(/YOUR/DATASET/DIR) -> -> ``` -> -> 2. copy the dir from online to the offline machine -> -> 3. (offline machine) -> -> ``` -> -> import datasets -> -> data = datasets.load_from_disk(/SAVED/DATA/DIR) -> -> ``` -> -> -> -> HTH. - - -SCORE: 22.893993377685547 -TITLE: Discussion using datasets in offline mode -URL: https://github.com/huggingface/datasets/issues/824 -================================================== - -COMMENT: here is my way to load a dataset offline, but it **requires** an online machine -1. (online machine) -\`\`\` -import datasets -data = datasets.load_dataset(...) -data.save_to_disk(/YOUR/DATASET/DIR) -\`\`\` -2. copy the dir from online to the offline machine -3. (offline machine) -\`\`\` -import datasets -data = datasets.load_from_disk(/SAVED/DATA/DIR) -\`\`\` - -HTH. -SCORE: 22.406635284423828 -TITLE: Discussion using datasets in offline mode -URL: https://github.com/huggingface/datasets/issues/824 -================================================== -""" -``` - -Pas mal ! Notre deuxième résultat semble correspondre à la requête. - - - -✏️ **Essayez !** Créez votre propre requête et voyez si vous pouvez trouver une réponse dans les documents récupérés. Vous devrez peut-être augmenter le paramètre `k` dans `Dataset.get_nearest_examples()` pour élargir la recherche. - - + + +# Recherche sémantique avec FAISS + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Dans [section 5](/course/fr/chapter5/5), nous avons créé un jeu de données de problèmes et de commentaires GitHub à partir du dépôt 🤗 *Datasets*. Dans cette section, nous utilisons ces informations pour créer un moteur de recherche qui peut nous aider à trouver des réponses à nos questions les plus urgentes sur la bibliothèque ! + + + +## Utilisation des enchâssements pour la recherche sémantique + +Comme nous l'avons vu dans le [chapitre 1](/course/fr/chapter1), les modèles de langage basés sur les *transformers* représentent chaque *token* dans une étendue de texte sous la forme d'un _enchâssement_. Il s'avère que l'on peut regrouper les enchâssements individuels pour créer une représentation vectorielle pour des phrases entières, des paragraphes ou (dans certains cas) des documents. Ces enchâssements peuvent ensuite être utilisés pour trouver des documents similaires dans le corpus en calculant la similarité du produit scalaire (ou une autre métrique de similarité) entre chaque enchâssement et en renvoyant les documents avec le plus grand chevauchement. + +Dans cette section, nous utilisons les enchâssements pour développer un moteur de recherche sémantique. Ces moteurs de recherche offrent plusieurs avantages par rapport aux approches conventionnelles basées sur la correspondance des mots-clés dans une requête avec les documents. + +
+Recherche sémantique. + +
+ +## Chargement et préparation du jeu de données + +La première chose que nous devons faire est de télécharger notre jeu de données de problèmes GitHub. Utilisons la bibliothèque 🤗 *Hub* pour résoudre l'URL où notre fichier est stocké sur le *Hub* d’Hugging Face : + +```py +from huggingface_hub import hf_hub_url + +data_files = hf_hub_url( + repo_id="lewtun/github-issues", + filename="datasets-issues-with-comments.jsonl", + repo_type="dataset", +) +``` + +Avec l'URL stocké dans `data_files`, nous pouvons ensuite charger le jeu de données distant en utilisant la méthode introduite dans [section 2](/course/fr/chapter5/2) : + +```py +from datasets import load_dataset + +issues_dataset = load_dataset("json", data_files=data_files, split="train") +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 2855 +}) +``` + +Ici, nous avons spécifié l’échantillon `train` par défaut dans `load_dataset()`, de sorte que cela renvoie un `Dataset` au lieu d'un `DatasetDict`. La première chose à faire est de filtrer les *pull requests* car celles-ci ont tendance à être rarement utilisées pour répondre aux requêtes des utilisateurs et introduiront du bruit dans notre moteur de recherche. Comme cela devrait être familier maintenant, nous pouvons utiliser la fonction `Dataset.filter()` pour exclure ces lignes de notre jeu de données. Pendant que nous y sommes, filtrons également les lignes sans commentaires, car celles-ci ne fournissent aucune réponse aux requêtes des utilisateurs : + +```py +issues_dataset = issues_dataset.filter( + lambda x: (x["is_pull_request"] == False and len(x["comments"]) > 0) +) +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 771 +}) +``` + +Nous pouvons voir qu'il y a beaucoup de colonnes dans notre jeu de données, dont la plupart n'ont pas besoin de construire notre moteur de recherche. Du point de vue de la recherche, les colonnes les plus informatives sont `title`, `body` et `comments`, tandis que `html_url` nous fournit un lien vers le problème source. Utilisons la fonction `Dataset.remove_columns()` pour supprimer le reste : + +```py +columns = issues_dataset.column_names +columns_to_keep = ["title", "body", "html_url", "comments"] +columns_to_remove = set(columns_to_keep).symmetric_difference(columns) +issues_dataset = issues_dataset.remove_columns(columns_to_remove) +issues_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body'], + num_rows: 771 +}) +``` + +Pour créer nos enchâssements, nous ajoutons à chaque commentaire le titre et le corps du problème, car ces champs contiennent des informations contextuelles utiles. Étant donné que notre colonne `comments` est actuellement une liste de commentaires pour chaque problème, nous devons « éclater » la colonne afin que chaque ligne se compose d'un *tuple* `(html_url, title, body, comment)`. Dans Pandas, nous pouvons le faire avec la fonction [`DataFrame.explode()`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html), qui crée une nouvelle ligne pour chaque élément dans une colonne de type liste, tout en répliquant toutes les autres valeurs de colonne. Pour voir cela en action, passons d'abord au format `DataFrame` de Pandas : + +```py +issues_dataset.set_format("pandas") +df = issues_dataset[:] +``` + +Si nous inspectons la première ligne de ce `DataFrame`, nous pouvons voir qu'il y a quatre commentaires associés à ce problème : + +```py +df["comments"][0].tolist() +``` + +```python out +['the bug code locate in :\r\n if data_args.task_name is not None:\r\n # Downloading and loading a dataset from the hub.\r\n datasets = load_dataset("glue", data_args.task_name, cache_dir=model_args.cache_dir)', + 'Hi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com\r\n\r\nNormally, it should work if you wait a little and then retry.\r\n\r\nCould you please confirm if the problem persists?', + 'cannot connect,even by Web browser,please check that there is some problems。', + 'I can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...'] +``` + +Lorsque nous décomposons `df`, nous nous attendons à obtenir une ligne pour chacun de ces commentaires. Vérifions si c'est le cas : + +```py +comments_df = df.explode("comments", ignore_index=True) +comments_df.head(4) +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
html_urltitlecommentsbody
0https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comthe bug code locate in :\r\n if data_args.task_name is not None...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
1https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comHi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
2https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comcannot connect,even by Web browser,please check that there is some problems。Hello,\r\nI am trying to run run_glue.py and it gives me this error...
3https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comI can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
+ +Génial, nous pouvons voir que les lignes ont été répliquées, avec la colonne `comments` contenant les commentaires individuels ! Maintenant que nous en avons fini avec Pandas, nous pouvons rapidement revenir à un `Dataset` en chargeant le `DataFrame` en mémoire : + +```py +from datasets import Dataset + +comments_dataset = Dataset.from_pandas(comments_df) +comments_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body'], + num_rows: 2842 +}) +``` + +D'accord, cela nous a donné quelques milliers de commentaires avec lesquels travailler ! + + + + +✏️ **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. + + + +Maintenant que nous avons un commentaire par ligne, créons une nouvelle colonne `comments_length` contenant le nombre de mots par commentaire : + +```py +comments_dataset = comments_dataset.map( + lambda x: {"comment_length": len(x["comments"].split())} +) +``` + +Nous pouvons utiliser cette nouvelle colonne pour filtrer les commentaires courts incluant généralement des éléments tels que « cc @lewtun » ou « Merci ! » qui ne sont pas pertinents pour notre moteur de recherche. Il n'y a pas de nombre précis à sélectionner pour le filtre mais 15 mots semblent être un bon début : + +```py +comments_dataset = comments_dataset.filter(lambda x: x["comment_length"] > 15) +comments_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body', 'comment_length'], + num_rows: 2098 +}) +``` + +Après avoir un peu nettoyé notre jeu de données, concaténons le titre, la description et les commentaires du problème dans une nouvelle colonne `text`. Comme d'habitude, nous allons écrire une fonction simple que nous pouvons passer à `Dataset.map()` : + +```py +def concatenate_text(examples): + return { + "text": examples["title"] + + " \n " + + examples["body"] + + " \n " + + examples["comments"] + } + + +comments_dataset = comments_dataset.map(concatenate_text) +``` + +Nous sommes enfin prêts à créer des enchâssements ! Jetons un coup d'œil. + +## Création d’enchâssements pour les textes + +Nous avons vu dans [chapitre 2](/course/fr/chapter2) que nous pouvons obtenir des enchâssements de *tokens* en utilisant la classe `AutoModel`. Tout ce que nous avons à faire est de choisir un *checkpoint* approprié à partir duquel charger le modèle. Heureusement, il existe une bibliothèque appelée `sentence-transformers` dédiée à la création d’enchâssements. Comme décrit dans la [documentation de la bibliothèque](https://www.sbert.net/examples/applications/semantic-search/README.html#symmetric-vs-asymmetric-semantic-search), notre cas d'utilisation est un exemple de _recherche sémantique asymétrique_. En effet, nous avons une requête courte dont nous aimerions trouver la réponse dans un document plus long, par exemple un commentaire à un problème. Le [tableau de présentation des modèles](https://www.sbert.net/docs/pretrained_models.html#model-overview) de la documentation indique que le *checkpoint* `multi-qa-mpnet-base-dot-v1` a les meilleures performances pour la recherche sémantique. Utilisons donc le pour notre application. Nous allons également charger le *tokenizer* en utilisant le même *checkpoint* : + +{#if fw === 'pt'} + +```py +from transformers import AutoTokenizer, AutoModel + +model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1" +tokenizer = AutoTokenizer.from_pretrained(model_ckpt) +model = AutoModel.from_pretrained(model_ckpt) +``` + +Pour accélérer le processus, il est utile de placer le modèle et les entrées sur un périphérique GPU, alors faisons-le maintenant : + +```py +import torch + +device = torch.device("cuda") +model.to(device) +``` + +{:else} + +```py +from transformers import AutoTokenizer, TFAutoModel + +model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1" +tokenizer = AutoTokenizer.from_pretrained(model_ckpt) +model = TFAutoModel.from_pretrained(model_ckpt, from_pt=True) +``` + +Notez que nous avons défini `from_pt=True` comme argument de la méthode `from_pretrained()`. C'est parce que le point de contrôle `multi-qa-mpnet-base-dot-v1` n'a que des poids PyTorch. Donc définir `from_pt=True` converti automatiquement au format TensorFlow pour nous. Comme vous pouvez le voir, il est très simple de passer d'un *framework* à l'autre dans 🤗 *Transformers* ! + +{/if} + +Comme nous l'avons mentionné précédemment, nous aimerions représenter chaque entrée dans notre corpus de problèmes GitHub comme un vecteur unique. Nous devons donc regrouper ou faire la moyenne de nos enchâssements de *tokens* d'une manière ou d'une autre. Une approche populaire consiste à effectuer un *regroupement CLS* sur les sorties de notre modèle, où nous collectons simplement le dernier état caché pour le *token* spécial `[CLS]`. La fonction suivante fait ça pour nous : + +```py +def cls_pooling(model_output): + return model_output.last_hidden_state[:, 0] +``` + +Ensuite, nous allons créer une fonction utile qui va tokeniser une liste de documents, placer les tenseurs dans le GPU, les donner au modèle et enfin appliquer le regroupement CLS aux sorties : + +{#if fw === 'pt'} + +```py +def get_embeddings(text_list): + encoded_input = tokenizer( + text_list, padding=True, truncation=True, return_tensors="pt" + ) + encoded_input = {k: v.to(device) for k, v in encoded_input.items()} + model_output = model(**encoded_input) + return cls_pooling(model_output) +``` + +Nous pouvons tester le fonctionnement de la fonction en lui donnant la première entrée textuelle de notre corpus et en inspectant la forme de sortie : + +```py +embedding = get_embeddings(comments_dataset["text"][0]) +embedding.shape +``` + +```python out +torch.Size([1, 768]) +``` + +Super ! Nous avons converti la première entrée de notre corpus en un vecteur à 768 dimensions. Nous pouvons utiliser `Dataset.map()` pour appliquer notre fonction `get_embeddings()` à chaque ligne de notre corpus. Créons donc une nouvelle colonne `embeddings` comme suit : + +```py +embeddings_dataset = comments_dataset.map( + lambda x: {"embeddings": get_embeddings(x["text"]).detach().cpu().numpy()[0]} +) +``` + +{:else} + +```py +def get_embeddings(text_list): + encoded_input = tokenizer( + text_list, padding=True, truncation=True, return_tensors="tf" + ) + encoded_input = {k: v for k, v in encoded_input.items()} + model_output = model(**encoded_input) + return cls_pooling(model_output) +``` + +Nous pouvons tester le fonctionnement de la fonction en lui donnant la première entrée textuelle de notre corpus et en inspectant la forme de sortie : + +```py +embedding = get_embeddings(comments_dataset["text"][0]) +embedding.shape +``` + +```python out +TensorShape([1, 768]) +``` + +Super ! Nous avons converti la première entrée de notre corpus en un vecteur à 768 dimensions. Nous pouvons utiliser `Dataset.map()` pour appliquer notre fonction `get_embeddings()` à chaque ligne de notre corpus. Créons donc une nouvelle colonne `embeddings` comme suit : + +```py +embeddings_dataset = comments_dataset.map( + lambda x: {"embeddings": get_embeddings(x["text"]).numpy()[0]} +) +``` + +{/if} + + +Notez que nous avons converti les enchâssements en tableaux NumPy. C'est parce que 🤗 *Datasets* nécessite ce format lorsque nous essayons de les indexer avec FAISS, ce que nous ferons ensuite. + +## Utilisation de FAISS pour une recherche de similarité efficace + +Maintenant que nous avons un jeu de données d'incorporations, nous avons besoin d'un moyen de les rechercher. Pour ce faire, nous utiliserons une structure de données spéciale dans 🤗 *Datasets* appelée _FAISS index_. [FAISS](https://faiss.ai/) (abréviation de *Facebook AI Similarity Search*) est une bibliothèque qui fournit des algorithmes efficaces pour rechercher et regrouper rapidement des vecteurs d'intégration. + +L'idée de base derrière FAISS est de créer une structure de données spéciale appelée un _index_ qui permet de trouver quels plongements sont similaires à un plongement d'entrée. Créer un index FAISS dans 🤗 *Datasets* est simple -- nous utilisons la fonction `Dataset.add_faiss_index()` et spécifions quelle colonne de notre jeu de données nous aimerions indexer : + +```py +embeddings_dataset.add_faiss_index(column="embeddings") +``` + +Nous pouvons maintenant effectuer des requêtes sur cet index en effectuant une recherche des voisins les plus proches avec la fonction `Dataset.get_nearest_examples()`. Testons cela en enchâssant d'abord une question comme suit : + +{#if fw === 'pt'} + +```py +question = "How can I load a dataset offline?" +question_embedding = get_embeddings([question]).cpu().detach().numpy() +question_embedding.shape +``` + +```python out +torch.Size([1, 768]) +``` + +{:else} + +```py +question = "How can I load a dataset offline?" +question_embedding = get_embeddings([question]).numpy() +question_embedding.shape +``` + +```python out +(1, 768) +``` + +{/if} + +Tout comme avec les documents, nous avons maintenant un vecteur de 768 dimensions représentant la requête. Nous pouvons le comparer à l’ensemble du corpus pour trouver les enchâssements les plus similaires : + +```py +scores, samples = embeddings_dataset.get_nearest_examples( + "embeddings", question_embedding, k=5 +) +``` + +La fonction `Dataset.get_nearest_examples()` renvoie un *tuple* de scores qui classent le chevauchement entre la requête et le document, et un jeu correspondant d'échantillons (ici, les 5 meilleures correspondances). Collectons-les dans un `pandas.DataFrame` afin de pouvoir les trier facilement : + +```py +import pandas as pd + +samples_df = pd.DataFrame.from_dict(samples) +samples_df["scores"] = scores +samples_df.sort_values("scores", ascending=False, inplace=True) +``` + +Nous pouvons maintenant parcourir les premières lignes pour voir dans quelle mesure notre requête correspond aux commentaires disponibles : + +```py +for _, row in samples_df.iterrows(): + print(f"COMMENT: {row.comments}") + print(f"SCORE: {row.scores}") + print(f"TITLE: {row.title}") + print(f"URL: {row.html_url}") + print("=" * 50) + print() +``` + +```python out +""" +COMMENT: Requiring online connection is a deal breaker in some cases unfortunately so it'd be great if offline mode is added similar to how `transformers` loads models offline fine. + +@mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like? +SCORE: 25.505046844482422 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: The local dataset builders (csv, text , json and pandas) are now part of the `datasets` package since #1726 :) +You can now use them offline +\`\`\`python +datasets = load_dataset("text", data_files=data_files) +\`\`\` + +We'll do a new release soon +SCORE: 24.555509567260742 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: I opened a PR that allows to reload modules that have already been loaded once even if there's no internet. + +Let me know if you know other ways that can make the offline mode experience better. I'd be happy to add them :) + +I already note the "freeze" modules option, to prevent local modules updates. It would be a cool feature. + +---------- + +> @mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like? + +Indeed `load_dataset` allows to load remote dataset script (squad, glue, etc.) but also you own local ones. +For example if you have a dataset script at `./my_dataset/my_dataset.py` then you can do +\`\`\`python +load_dataset("./my_dataset") +\`\`\` +and the dataset script will generate your dataset once and for all. + +---------- + +About I'm looking into having `csv`, `json`, `text`, `pandas` dataset builders already included in the `datasets` package, so that they are available offline by default, as opposed to the other datasets that require the script to be downloaded. +cf #1724 +SCORE: 24.14896583557129 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: > here is my way to load a dataset offline, but it **requires** an online machine +> +> 1. (online machine) +> +> ``` +> +> import datasets +> +> data = datasets.load_dataset(...) +> +> data.save_to_disk(/YOUR/DATASET/DIR) +> +> ``` +> +> 2. copy the dir from online to the offline machine +> +> 3. (offline machine) +> +> ``` +> +> import datasets +> +> data = datasets.load_from_disk(/SAVED/DATA/DIR) +> +> ``` +> +> +> +> HTH. + + +SCORE: 22.893993377685547 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: here is my way to load a dataset offline, but it **requires** an online machine +1. (online machine) +\`\`\` +import datasets +data = datasets.load_dataset(...) +data.save_to_disk(/YOUR/DATASET/DIR) +\`\`\` +2. copy the dir from online to the offline machine +3. (offline machine) +\`\`\` +import datasets +data = datasets.load_from_disk(/SAVED/DATA/DIR) +\`\`\` + +HTH. +SCORE: 22.406635284423828 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== +""" +``` + +Pas mal ! Notre deuxième résultat semble correspondre à la requête. + + + +✏️ **Essayez !** Créez votre propre requête et voyez si vous pouvez trouver une réponse dans les documents récupérés. Vous devrez peut-être augmenter le paramètre `k` dans `Dataset.get_nearest_examples()` pour élargir la recherche. + + diff --git a/chapters/fr/chapter5/7.mdx b/chapters/fr/chapter5/7.mdx index 55083fffa..e205ff607 100644 --- a/chapters/fr/chapter5/7.mdx +++ b/chapters/fr/chapter5/7.mdx @@ -1,10 +1,15 @@ -# 🤗 Datasets, coché ! - -Eh bien, ce fut une sacrée visite de la bibliothèque 🤗 *Datasets*. Félicitations d’être arrivé jusqu'ici ! Avec les connaissances que vous avez acquises dans ce chapitre, vous devriez être en mesure de : -- charger des jeux de données depuis n'importe où, que ce soit le *Hub* d’Hugging Face, votre ordinateur portable ou un serveur distant de votre entreprise, -- manipuler vos données en utilisant un mélange des fonctions Dataset.map() et Dataset.filter(), -- passer rapidement d'un format de données à un autre, comme Pandas et NumPy, en utilisant Dataset.set_format(), -- créer votre propre jeu de données et l’envoyer vers le *Hub*, -- enchâsser vos documents en utilisant un *transformer* et construire un moteur de recherche sémantique en utilisant FAISS. - -Dans le [chapitre 7](/course/fr/chapter7), nous mettrons tout cela à profit en plongeant dans les tâches de traitement du langage naturel de base pour lesquelles les *transformers* sont parfaits. Avant cela mettez vos connaissances sur la librairie 🤗 *Datasets* à l'épreuve avec un petit quiz ! +# 🤗 Datasets, coché ! + + + +Eh bien, ce fut une sacrée visite de la bibliothèque 🤗 *Datasets*. Félicitations d’être arrivé jusqu'ici ! Avec les connaissances que vous avez acquises dans ce chapitre, vous devriez être en mesure de : +- charger des jeux de données depuis n'importe où, que ce soit le *Hub* d’Hugging Face, votre ordinateur portable ou un serveur distant de votre entreprise, +- manipuler vos données en utilisant un mélange des fonctions Dataset.map() et Dataset.filter(), +- passer rapidement d'un format de données à un autre, comme Pandas et NumPy, en utilisant Dataset.set_format(), +- créer votre propre jeu de données et l’envoyer vers le *Hub*, +- enchâsser vos documents en utilisant un *transformer* et construire un moteur de recherche sémantique en utilisant FAISS. + +Dans le [chapitre 7](/course/fr/chapter7), nous mettrons tout cela à profit en plongeant dans les tâches de traitement du langage naturel de base pour lesquelles les *transformers* sont parfaits. Avant cela mettez vos connaissances sur la librairie 🤗 *Datasets* à l'épreuve avec un petit quiz ! diff --git a/chapters/fr/chapter5/8.mdx b/chapters/fr/chapter5/8.mdx index 54f4e770f..6abf66b0a 100644 --- a/chapters/fr/chapter5/8.mdx +++ b/chapters/fr/chapter5/8.mdx @@ -1,226 +1,231 @@ - - -# Quiz de fin de chapitre - -Ce chapitre a couvert beaucoup de terrain ! Ne vous inquiétez pas si vous n'avez pas saisi tous les détails, les chapitres suivants vous aideront à comprendre comment les choses fonctionnent sous le capot. - -Avant de poursuivre, testons ce que vous avez appris dans ce chapitre. - -### 1. La fonction `load_dataset()` dans 🤗 *Datasets* vous permet de charger un jeu de données depuis lequel des emplacements suivants ? - -data_files de load_dataset() pour charger les jeux de données locaux.", - correct: true - }, - { - text: "Le Hub d’Hugging Face.", - explain: "Vous pouvez charger des jeux de données sur le Hub en fournissant l'ID du jeu de données. Par exemple : load_dataset('emotion').", - correct: true - }, - { - text: "Un serveur distant.", - explain: "Vous pouvez passer des URLs à l'argument data_files de load_dataset() pour charger des fichiers distants.", - correct: true - }, - ]} -/> - -### 2. Supposons que vous chargiez l'une des tâches du jeu de données GLUE comme suit : - -```py -from datasets import load_dataset - -dataset = load_dataset("glue", "mrpc", split="train") -``` - -Laquelle des commandes suivantes produira un échantillon aléatoire de 50 éléments à partir de `dataset` ? - -dataset.sample(50)", - explain: "Il n'y a pas de méthode Dataset.sample()." - }, - { - text: "dataset.shuffle().select(range(50))", - explain: "Comme vous l'avez vu dans ce chapitre, vous mélangez d'abord le jeu de données puis sélectionnez les échantillons à partir de celui-ci.", - correct: true - }, - { - text: "dataset.select(range(50)).shuffle()", - explain: "Bien que le code s'exécute, il ne mélange que les 50 premiers éléments du jeu de données." - } - ]} -/> - -### 3. Supposons que vous disposiez d'un jeu de données sur les animaux domestiques appelé `pets_dataset` qui comporte une colonne `name` indiquant le nom de chaque animal. Parmi les approches suivantes, laquelle vous permettrait de filtrer le jeu de données pour tous les animaux dont le nom commence par la lettre « L » ? - -pets_dataset.filter(lambda x : x['name'].startswith('L'))", - explain: "L'utilisation d'une fonction Python lambda pour ces filtres rapides est une excellente idée. Pouvez-vous penser à une autre solution ?", - correct: true - }, - { - text: "pets_dataset.filter(lambda x['name'].startswith('L'))", - explain: "Une fonction lambda prend la forme générale lambda *arguments* : *expression*, vous devez donc fournir des arguments dans ce cas." - }, - { - text: "Créer une fonction comme def filter_names(x): return x['name'].startswith('L') et exécuter pets_dataset.filter(filter_names).", - explain: "Tout comme avec Dataset.map(), vous pouvez passer des fonctions explicites à Dataset.filter(). Ceci est utile lorsque vous avez une logique complexe qui ne convient pas à une fonction lambda courte. Parmi les autres solutions, laquelle fonctionnerait ?", - correct: true - } - ]} -/> - -### 4. Qu'est-ce que le *memory mapping* ? - -mapping entre la RAM CPU et GPU.", - explain: "Ce n'est pas ça, réessayez !", - }, - { - text: "Un mapping entre la RAM et le stockage du système de fichiers.", - explain: "🤗 Datasets traite chaque jeu de données comme un fichier mappé en mémoire. Cela permet à la bibliothèque d'accéder et d'opérer sur des éléments du jeu de données sans avoir à le charger complètement en mémoire.", - correct: true - }, - { - text: "Un mapping entre deux fichiers dans le cache 🤗 Datasets.", - explain: "Ce n'est pas ça, réessayez !" - } - ]} -/> - -### 5. Parmi les éléments suivants, lesquels sont les principaux avantages du *memory mapping* ? - -Datasets d'être extrêmement rapide. Ce n'est cependant pas le seul avantage.", - correct: true - }, - { - text: "Les applications peuvent accéder à des segments de données dans un fichier extrêmement volumineux sans avoir à lire tout le fichier dans la RAM au préalable.", - explain: "Cela permet à 🤗 Datasets de charger des jeux de données de plusieurs Go sur votre ordinateur portable sans faire exploser votre CPU. Quel autre avantage cette technique offre-t-elle ?", - correct: true - }, - { - text: "Cela consomme moins d'énergie, donc votre batterie dure plus longtemps.", - explain: "Ce n'est pas ça, réessayez !" - } - ]} -/> - -### 6. Pourquoi le code suivant échoue-t-il ? - -```py -from datasets import load_dataset - -dataset = load_dataset("allocine", streaming=True, split="train") -dataset[0] -``` - -IterableDataset.", - explain: "Un IterableDataset est un générateur, pas un conteneur. Vous devez donc accéder à ses éléments en utilisant next(iter(dataset)).", - correct: true - }, - { - text: "Le jeu de données allocine n'a pas d’échantillon train.", - explain: "Consultez le jeu de données allocine sur le Hub (https://huggingface.co/datasets/allocine) pour voir quels échantillons il contient." - } - ]} -/> - -### 7. Parmi les avantages suivants, lesquels sont les principaux pour la création d'une fiche pour les jeux de données ? - - - - -### 8. Qu'est-ce que la recherche sémantique ? - -recherche lexicale et c'est ce que vous voyez généralement avec les moteurs de recherche traditionnels." - }, - { - text: "Un moyen de rechercher des documents correspondants en comprenant la signification contextuelle d'une requête.", - explain: "La recherche sémantique utilise des vecteurs d’enchâssement pour représenter les requêtes et les documents. Elle utilise ensuite une métrique de similarité pour mesurer la quantité de chevauchement entre eux. Comment la décrire autrement ?", - correct: true - }, - { - text: "Un moyen d'améliorer la précision de la recherche.", - explain: "Les moteurs de recherche sémantique peuvent capturer l'intention d'une requête bien mieux que la correspondance des mots clés et récupèrent généralement les documents avec une plus grande précision. Mais ce n'est pas la seule bonne réponse. Qu'est-ce que la recherche sémantique apporte d'autre ?", - correct: true - } - ]} -/> - -### 9. Pour la recherche sémantique asymétrique, vous avez généralement : - - - -### 10. Puis-je utiliser 🤗 *Datasets* pour charger des données à utiliser dans d'autres domaines, comme le traitement de la parole ? - -Datasets prend actuellement en charge les données tabulaires, l'audio et la vision par ordinateur. Consultez le jeu de données MNIST sur le Hub pour un exemple de vision par ordinateur." - }, - { - text: "Oui.", - explain: "Découvrez les développements passionnants concernant la parole et la vision dans la bibliothèque 🤗 Transformers pour voir comment 🤗 Datasets est utilisé dans ces domaines.", - correct : true - }, - ]} -/> + + +# Quiz de fin de chapitre + + + +Ce chapitre a couvert beaucoup de terrain ! Ne vous inquiétez pas si vous n'avez pas saisi tous les détails, les chapitres suivants vous aideront à comprendre comment les choses fonctionnent sous le capot. + +Avant de poursuivre, testons ce que vous avez appris dans ce chapitre. + +### 1. La fonction `load_dataset()` dans 🤗 *Datasets* vous permet de charger un jeu de données depuis lequel des emplacements suivants ? + +data_files de load_dataset() pour charger les jeux de données locaux.", + correct: true + }, + { + text: "Le Hub d’Hugging Face.", + explain: "Vous pouvez charger des jeux de données sur le Hub en fournissant l'ID du jeu de données. Par exemple : load_dataset('emotion').", + correct: true + }, + { + text: "Un serveur distant.", + explain: "Vous pouvez passer des URLs à l'argument data_files de load_dataset() pour charger des fichiers distants.", + correct: true + }, + ]} +/> + +### 2. Supposons que vous chargiez l'une des tâches du jeu de données GLUE comme suit : + +```py +from datasets import load_dataset + +dataset = load_dataset("glue", "mrpc", split="train") +``` + +Laquelle des commandes suivantes produira un échantillon aléatoire de 50 éléments à partir de `dataset` ? + +dataset.sample(50)", + explain: "Il n'y a pas de méthode Dataset.sample()." + }, + { + text: "dataset.shuffle().select(range(50))", + explain: "Comme vous l'avez vu dans ce chapitre, vous mélangez d'abord le jeu de données puis sélectionnez les échantillons à partir de celui-ci.", + correct: true + }, + { + text: "dataset.select(range(50)).shuffle()", + explain: "Bien que le code s'exécute, il ne mélange que les 50 premiers éléments du jeu de données." + } + ]} +/> + +### 3. Supposons que vous disposiez d'un jeu de données sur les animaux domestiques appelé `pets_dataset` qui comporte une colonne `name` indiquant le nom de chaque animal. Parmi les approches suivantes, laquelle vous permettrait de filtrer le jeu de données pour tous les animaux dont le nom commence par la lettre « L » ? + +pets_dataset.filter(lambda x : x['name'].startswith('L'))", + explain: "L'utilisation d'une fonction Python lambda pour ces filtres rapides est une excellente idée. Pouvez-vous penser à une autre solution ?", + correct: true + }, + { + text: "pets_dataset.filter(lambda x['name'].startswith('L'))", + explain: "Une fonction lambda prend la forme générale lambda *arguments* : *expression*, vous devez donc fournir des arguments dans ce cas." + }, + { + text: "Créer une fonction comme def filter_names(x): return x['name'].startswith('L') et exécuter pets_dataset.filter(filter_names).", + explain: "Tout comme avec Dataset.map(), vous pouvez passer des fonctions explicites à Dataset.filter(). Ceci est utile lorsque vous avez une logique complexe qui ne convient pas à une fonction lambda courte. Parmi les autres solutions, laquelle fonctionnerait ?", + correct: true + } + ]} +/> + +### 4. Qu'est-ce que le *memory mapping* ? + +mapping entre la RAM CPU et GPU.", + explain: "Ce n'est pas ça, réessayez !", + }, + { + text: "Un mapping entre la RAM et le stockage du système de fichiers.", + explain: "🤗 Datasets traite chaque jeu de données comme un fichier mappé en mémoire. Cela permet à la bibliothèque d'accéder et d'opérer sur des éléments du jeu de données sans avoir à le charger complètement en mémoire.", + correct: true + }, + { + text: "Un mapping entre deux fichiers dans le cache 🤗 Datasets.", + explain: "Ce n'est pas ça, réessayez !" + } + ]} +/> + +### 5. Parmi les éléments suivants, lesquels sont les principaux avantages du *memory mapping* ? + +Datasets d'être extrêmement rapide. Ce n'est cependant pas le seul avantage.", + correct: true + }, + { + text: "Les applications peuvent accéder à des segments de données dans un fichier extrêmement volumineux sans avoir à lire tout le fichier dans la RAM au préalable.", + explain: "Cela permet à 🤗 Datasets de charger des jeux de données de plusieurs Go sur votre ordinateur portable sans faire exploser votre CPU. Quel autre avantage cette technique offre-t-elle ?", + correct: true + }, + { + text: "Cela consomme moins d'énergie, donc votre batterie dure plus longtemps.", + explain: "Ce n'est pas ça, réessayez !" + } + ]} +/> + +### 6. Pourquoi le code suivant échoue-t-il ? + +```py +from datasets import load_dataset + +dataset = load_dataset("allocine", streaming=True, split="train") +dataset[0] +``` + +IterableDataset.", + explain: "Un IterableDataset est un générateur, pas un conteneur. Vous devez donc accéder à ses éléments en utilisant next(iter(dataset)).", + correct: true + }, + { + text: "Le jeu de données allocine n'a pas d’échantillon train.", + explain: "Consultez le jeu de données allocine sur le Hub (https://huggingface.co/datasets/allocine) pour voir quels échantillons il contient." + } + ]} +/> + +### 7. Parmi les avantages suivants, lesquels sont les principaux pour la création d'une fiche pour les jeux de données ? + + + + +### 8. Qu'est-ce que la recherche sémantique ? + +recherche lexicale et c'est ce que vous voyez généralement avec les moteurs de recherche traditionnels." + }, + { + text: "Un moyen de rechercher des documents correspondants en comprenant la signification contextuelle d'une requête.", + explain: "La recherche sémantique utilise des vecteurs d’enchâssement pour représenter les requêtes et les documents. Elle utilise ensuite une métrique de similarité pour mesurer la quantité de chevauchement entre eux. Comment la décrire autrement ?", + correct: true + }, + { + text: "Un moyen d'améliorer la précision de la recherche.", + explain: "Les moteurs de recherche sémantique peuvent capturer l'intention d'une requête bien mieux que la correspondance des mots clés et récupèrent généralement les documents avec une plus grande précision. Mais ce n'est pas la seule bonne réponse. Qu'est-ce que la recherche sémantique apporte d'autre ?", + correct: true + } + ]} +/> + +### 9. Pour la recherche sémantique asymétrique, vous avez généralement : + + + +### 10. Puis-je utiliser 🤗 *Datasets* pour charger des données à utiliser dans d'autres domaines, comme le traitement de la parole ? + +Datasets prend actuellement en charge les données tabulaires, l'audio et la vision par ordinateur. Consultez le jeu de données MNIST sur le Hub pour un exemple de vision par ordinateur." + }, + { + text: "Oui.", + explain: "Découvrez les développements passionnants concernant la parole et la vision dans la bibliothèque 🤗 Transformers pour voir comment 🤗 Datasets est utilisé dans ces domaines.", + correct : true + }, + ]} +/> diff --git a/chapters/fr/chapter6/1.mdx b/chapters/fr/chapter6/1.mdx index 6869cc815..730e89a3f 100644 --- a/chapters/fr/chapter6/1.mdx +++ b/chapters/fr/chapter6/1.mdx @@ -1,13 +1,18 @@ -# Introduction - -Dans le [chapitre 3](/course/fr/chapter3), nous avons vu comment *finetuner* un modèle sur une tâche donnée. Pour ce faire, nous utilisons le même *tokenizer* que celui avec lequel le modèle a été pré-entraîné. Mais que faisons-nous lorsque nous voulons entraîner un modèle à partir de zéro ? Dans ces cas, l'utilisation d'un *tokenizer* qui a été pré-entraîné sur un corpus d'un autre domaine ou d'une autre langue est généralement sous-optimale. Par exemple, un *tokenizer* entraîné sur un corpus anglais sera peu performant sur un corpus de textes japonais car l'utilisation des espaces et de la ponctuation est très différente entre les deux langues. - -Dans ce chapitre, vous apprendrez à entraîner un tout nouveau *tokenizer* sur un corpus de textes afin qu'il puisse ensuite être utilisé pour pré-entraîner un modèle de langue. Tout cela se fera à l'aide de la bibliothèque [🤗 *Tokenizers*](https://github.com/huggingface/tokenizers), qui fournit les *tokenizers* « rapides » de la bibliothèque [🤗 *Transformers*](https://github.com/huggingface/transformers). Nous examinerons de près les fonctionnalités offertes par cette bibliothèque et nous étudierons comment les *tokenizers* rapides diffèrent des versions « lentes ». - -Les sujets que nous couvrirons comprennent : -* comment entraîner sur un nouveau corpus de textes, un nouveau *tokenizer* similaire à celui utilisé par un *checkpoint* donné, -* les caractéristiques spéciales des *tokenizers* rapides, -* les différences entre les trois principaux algorithmes de tokénisation utilisés aujourd'hui en NLP, -* comment construire un *tokenizer* à partir de zéro avec la bibliothèque 🤗 *Tokenizers* et l'entraîner sur des données. - +# Introduction + + + +Dans le [chapitre 3](/course/fr/chapter3), nous avons vu comment *finetuner* un modèle sur une tâche donnée. Pour ce faire, nous utilisons le même *tokenizer* que celui avec lequel le modèle a été pré-entraîné. Mais que faisons-nous lorsque nous voulons entraîner un modèle à partir de zéro ? Dans ces cas, l'utilisation d'un *tokenizer* qui a été pré-entraîné sur un corpus d'un autre domaine ou d'une autre langue est généralement sous-optimale. Par exemple, un *tokenizer* entraîné sur un corpus anglais sera peu performant sur un corpus de textes japonais car l'utilisation des espaces et de la ponctuation est très différente entre les deux langues. + +Dans ce chapitre, vous apprendrez à entraîner un tout nouveau *tokenizer* sur un corpus de textes afin qu'il puisse ensuite être utilisé pour pré-entraîner un modèle de langue. Tout cela se fera à l'aide de la bibliothèque [🤗 *Tokenizers*](https://github.com/huggingface/tokenizers), qui fournit les *tokenizers* « rapides » de la bibliothèque [🤗 *Transformers*](https://github.com/huggingface/transformers). Nous examinerons de près les fonctionnalités offertes par cette bibliothèque et nous étudierons comment les *tokenizers* rapides diffèrent des versions « lentes ». + +Les sujets que nous couvrirons comprennent : +* comment entraîner sur un nouveau corpus de textes, un nouveau *tokenizer* similaire à celui utilisé par un *checkpoint* donné, +* les caractéristiques spéciales des *tokenizers* rapides, +* les différences entre les trois principaux algorithmes de tokénisation utilisés aujourd'hui en NLP, +* comment construire un *tokenizer* à partir de zéro avec la bibliothèque 🤗 *Tokenizers* et l'entraîner sur des données. + Les techniques présentées dans ce chapitre vous prépareront à la section du [chapitre 7](/course/fr/chapter7/6) où nous verrons comment créer un modèle de langue pour le langage Python. Commençons par examiner ce que signifie « entraîner » un *tokenizer*. \ No newline at end of file diff --git a/chapters/fr/chapter6/10.mdx b/chapters/fr/chapter6/10.mdx index 062b51f16..4846966e4 100644 --- a/chapters/fr/chapter6/10.mdx +++ b/chapters/fr/chapter6/10.mdx @@ -1,278 +1,283 @@ - - -# Quiz de fin de chapitre - -Testons ce que vous avez appris dans ce chapitre ! - -### 1. Quand devez-vous entraîner un nouveau tokenizer ? - -tokenizer que le modèle pré-entraîné et de finetuner ce modèle à la place." - }, - { - text: "Lorsque votre jeu de données est similaire à celui utilisé par un modèle pré-entraîné existant et que vous souhaitez finetuner un nouveau modèle en utilisant ce modèle pré-entraîné.", - explain: "Pour finetuner un modèle à partir d'un modèle pré-entraîné, vous devez toujours utiliser le même tokenizer." - }, - { - text: "Lorsque votre jeu de données est différent de celui utilisé par un modèle pré-entraîné existant et que vous souhaitez pré-entraîner un nouveau modèle.", - explain: "Dans ce cas, il n'y a aucun avantage à utiliser le même tokenizer.", - correct: true - }, - { - text: "Lorsque votre jeu de données est différent de celui utilisé par un modèle pré-entraîné existant mais que vous souhaitez finetuner un nouveau modèle en utilisant ce modèle pré-entraîné.", - explain: "Pour finetuner un modèle à partir d'un modèle pré-entraîné, vous devez toujours utiliser le même tokenizer." - } - ]} -/> - -### 2. Quel est l'avantage d'utiliser un générateur de listes par rapport à une liste de listes lors de l'utilisation de train_new_from_iterator() ? - -train_new_from_iterator() accepte.", - explain: "Une liste de listes de textes est un type particulier de générateur de listes de textes, la méthode l'acceptera donc aussi. Essayez à nouveau !" - }, - { - text: "Vous éviterez de charger l'ensemble des données en mémoire en une seule fois.", - explain: "Chaque batch de textes sera libéré de la mémoire lorsque vous itérerez et le gain sera particulièrement visible si vous utilisez des 🤗 Datasets pour stocker vos textes.", - correct: true - }, - { - text: "Cela permettra à la bibliothèque 🤗 Tokenizers d'utiliser le multitraitement.", - explain: "Il utilisera le multiprocesseur dans tous les cas." - }, - { - text: "Le tokenizer que vous entraînez générera de meilleurs textes.", - explain: "Le tokenizer ne génère pas de texte. Vous le confondez avec un modèle de langage ?" - } - ]} -/> - -### 3. Quels sont les avantages d'utiliser un tokenizer « rapide » ? - -tokenizer lent lorsque vous faites des batchs d'entrées.", - explain: "Grâce au parallélisme implémenté dans Rust, il sera plus rapide sur les batchs d'entrées. Quel autre avantage pouvez-vous imaginer ?", - correct: true - }, - { - text: "Les tokenizers rapides sont toujours plus rapides que leurs homologues lents.", - explain: "Un tokenizer rapide peut en fait être plus lent si vous ne lui donnez qu'un seul ou très peu de textes, car il ne peut pas utiliser le parallélisme." - }, - { - text: "Il peut appliquer le padding et la troncature.", - explain: "C'est vrai, mais les tokenizers lents le font aussi." - }, - { - text: "Il possède des fonctionnalités supplémentaires qui vous permettent d'associer les tokens à l'extrait de texte qui les a créés.", - explain: "En effet, c'est ce qu'on appelle des correspondances d'offset. Ce n'est pas le seul avantage, cependant.", - correct: true - } - ]} -/> - -### 4. Comment le pipeline `token-classification` gère-t-il les entités qui s'étendent sur plusieurs tokens ? - -token porte l'étiquette de l'entité, le mot entier est considéré comme étiqueté avec cette entité.", - explain: "C'est une stratégie pour gérer les entités. Quelles autres réponses s'appliquent ici ?", - correct: true - }, - { - text: "Lorsqu'un token a l'étiquette d'une entité donnée, tout autre token suivant ayant la même étiquette est considéré comme faisant partie de la même entité, à moins qu'il ne soit étiqueté comme le début d'une nouvelle entité.", - explain: "C'est la façon la plus courante de regrouper des entités, mais ce n'est pas la seule bonne réponse.", - correct: true - } - ]} -/> - -### 5. Comment le pipeline `question-answering` gère-t-il les contextes longs ? - - - -### 6. Qu'est-ce que la normalisation ? - -tokenizer effectue sur les textes lors des étapes initiales.", - explain: "Par exemple, il peut s'agir de supprimer les accents ou les espaces, ou de mettre les entrées en minuscules.", - correct: true - }, - { - text: "Il s'agit d'une technique d'augmentation de données qui consiste à rendre le texte plus normal en supprimant les mots rares.", - explain: "Essayez encore." - }, - { - text: "C'est l'étape finale du post-traitement où le tokenizer ajoute les tokens spéciaux.", - explain: "Cette étape est simplement appelée post-traitement." - }, - { - text: "C'est lorsque les enchâssements sont faits avec une moyenne nulle et un écart-type de 1, en soustrayant la moyenne et en divisant par l'écart-type.", - explain: "Ce processus est communément appelé normalisation lorsqu'il est appliqué aux valeurs des pixels en vision par ordinateur, mais ce n'est pas ce que signifie la normalisation en NLP." - } - ]} -/> - -### 7. Qu'est-ce que la pré-tokénisation pour un tokenizer en sous-mots ? - -tokenizer, pour diviser l'entrée en mots.", - explain: "C'est la bonne réponse !", - correct: true - }, - { - text: "Il s'agit de l'étape précédant l'application du modèle tokenizer, qui divise l'entrée en tokens.", - explain: "La division en tokens est le travail du modèle tokenizer." - } - ]} -/> - -### 8. Sélectionnez les phrases qui s'appliquent au tokenizer BPE. - -tokens.", - explain: "C'est l'approche adoptée par un algorithme de tokénisation différent." - }, - { - text: "Un tokenizer BPE apprend les règles de fusion en fusionnant la paire de tokens la plus fréquente.", - explain: "C'est exact !", - correct: true - }, - { - text: "Un tokenizer BPE apprend une règle de fusion en fusionnant la paire de tokens qui maximise un score qui privilégie les paires fréquentes avec des parties individuelles moins fréquentes.", - explain: "C'est la stratégie appliquée par un autre algorithme de tokenization." - }, - { - text: "BPE tokenise les mots en sous-mots en les divisant en caractères, puis en appliquant les règles de fusion.", - explain: " ", - correct: true - }, - { - text: "BPE tokenise les mots en sous-mots en trouvant le plus long sous-mot du vocabulaire en commençant par le début, puis en répétant le processus pour le reste du texte.", - explain: "C'est la façon de faire d'un autre algorithme de tokenization." - }, - ]} -/> - -### 9. Sélectionnez les phrases qui s'appliquent au tokenizer WordPiece. - -tokens.", - 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.", - explain: "C'est la façon de faire d'un autre algorithme de tokenization." - }, - { - text: "Un tokenizer WordPiece apprend une règle de fusion en fusionnant la paire de tokens qui maximise un score qui privilégie les paires fréquentes avec des parties individuelles moins fréquentes.", - explain: " ", - correct: true - }, - { - text: "WordPiece tokenise les mots en sous-mots en trouvant la segmentation en tokens la plus probable, selon le modèle.", - explain: "C'est la façon de faire d'un autre algorithme de tokenization." - }, - { - text: "WordPiece tokenise les mots en sous-mots en trouvant le plus long sous-mot du vocabulaire en commençant par le début, puis en répétant le processus pour le reste du texte.", - explain: "C'est ainsi que WordPiece procède pour l'encodage.", - correct: true - }, - ]} -/> - -### 10. Sélectionnez les phrases qui s'appliquent au tokenizer Unigram. - -tokens.", - explain: " ", - correct: true - }, - { - text: "Unigram adapte son vocabulaire en minimisant une perte calculée sur l'ensemble du corpus.", - explain: " ", - correct: true - }, - { - text: "Unigram adapte son vocabulaire en conservant les sous-mots les plus fréquents.", - explain: " " - }, - { - text: "Unigram segmente les mots en sous-mots en trouvant la segmentation la plus probable en tokens, selon le modèle.", - explain: " ", - correct: true - }, - { - text: "Unigram décompose les mots en sous-mots en les divisant en caractères puis en appliquant les règles de fusion.", - explain: "C'est la façon de faire d'un autre algorithme de tokenization." - }, - ]} -/> + + +# Quiz de fin de chapitre + + + +Testons ce que vous avez appris dans ce chapitre ! + +### 1. Quand devez-vous entraîner un nouveau tokenizer ? + +tokenizer que le modèle pré-entraîné et de finetuner ce modèle à la place." + }, + { + text: "Lorsque votre jeu de données est similaire à celui utilisé par un modèle pré-entraîné existant et que vous souhaitez finetuner un nouveau modèle en utilisant ce modèle pré-entraîné.", + explain: "Pour finetuner un modèle à partir d'un modèle pré-entraîné, vous devez toujours utiliser le même tokenizer." + }, + { + text: "Lorsque votre jeu de données est différent de celui utilisé par un modèle pré-entraîné existant et que vous souhaitez pré-entraîner un nouveau modèle.", + explain: "Dans ce cas, il n'y a aucun avantage à utiliser le même tokenizer.", + correct: true + }, + { + text: "Lorsque votre jeu de données est différent de celui utilisé par un modèle pré-entraîné existant mais que vous souhaitez finetuner un nouveau modèle en utilisant ce modèle pré-entraîné.", + explain: "Pour finetuner un modèle à partir d'un modèle pré-entraîné, vous devez toujours utiliser le même tokenizer." + } + ]} +/> + +### 2. Quel est l'avantage d'utiliser un générateur de listes par rapport à une liste de listes lors de l'utilisation de train_new_from_iterator() ? + +train_new_from_iterator() accepte.", + explain: "Une liste de listes de textes est un type particulier de générateur de listes de textes, la méthode l'acceptera donc aussi. Essayez à nouveau !" + }, + { + text: "Vous éviterez de charger l'ensemble des données en mémoire en une seule fois.", + explain: "Chaque batch de textes sera libéré de la mémoire lorsque vous itérerez et le gain sera particulièrement visible si vous utilisez des 🤗 Datasets pour stocker vos textes.", + correct: true + }, + { + text: "Cela permettra à la bibliothèque 🤗 Tokenizers d'utiliser le multitraitement.", + explain: "Il utilisera le multiprocesseur dans tous les cas." + }, + { + text: "Le tokenizer que vous entraînez générera de meilleurs textes.", + explain: "Le tokenizer ne génère pas de texte. Vous le confondez avec un modèle de langage ?" + } + ]} +/> + +### 3. Quels sont les avantages d'utiliser un tokenizer « rapide » ? + +tokenizer lent lorsque vous faites des batchs d'entrées.", + explain: "Grâce au parallélisme implémenté dans Rust, il sera plus rapide sur les batchs d'entrées. Quel autre avantage pouvez-vous imaginer ?", + correct: true + }, + { + text: "Les tokenizers rapides sont toujours plus rapides que leurs homologues lents.", + explain: "Un tokenizer rapide peut en fait être plus lent si vous ne lui donnez qu'un seul ou très peu de textes, car il ne peut pas utiliser le parallélisme." + }, + { + text: "Il peut appliquer le padding et la troncature.", + explain: "C'est vrai, mais les tokenizers lents le font aussi." + }, + { + text: "Il possède des fonctionnalités supplémentaires qui vous permettent d'associer les tokens à l'extrait de texte qui les a créés.", + explain: "En effet, c'est ce qu'on appelle des correspondances d'offset. Ce n'est pas le seul avantage, cependant.", + correct: true + } + ]} +/> + +### 4. Comment le pipeline `token-classification` gère-t-il les entités qui s'étendent sur plusieurs tokens ? + +token porte l'étiquette de l'entité, le mot entier est considéré comme étiqueté avec cette entité.", + explain: "C'est une stratégie pour gérer les entités. Quelles autres réponses s'appliquent ici ?", + correct: true + }, + { + text: "Lorsqu'un token a l'étiquette d'une entité donnée, tout autre token suivant ayant la même étiquette est considéré comme faisant partie de la même entité, à moins qu'il ne soit étiqueté comme le début d'une nouvelle entité.", + explain: "C'est la façon la plus courante de regrouper des entités, mais ce n'est pas la seule bonne réponse.", + correct: true + } + ]} +/> + +### 5. Comment le pipeline `question-answering` gère-t-il les contextes longs ? + + + +### 6. Qu'est-ce que la normalisation ? + +tokenizer effectue sur les textes lors des étapes initiales.", + explain: "Par exemple, il peut s'agir de supprimer les accents ou les espaces, ou de mettre les entrées en minuscules.", + correct: true + }, + { + text: "Il s'agit d'une technique d'augmentation de données qui consiste à rendre le texte plus normal en supprimant les mots rares.", + explain: "Essayez encore." + }, + { + text: "C'est l'étape finale du post-traitement où le tokenizer ajoute les tokens spéciaux.", + explain: "Cette étape est simplement appelée post-traitement." + }, + { + text: "C'est lorsque les enchâssements sont faits avec une moyenne nulle et un écart-type de 1, en soustrayant la moyenne et en divisant par l'écart-type.", + explain: "Ce processus est communément appelé normalisation lorsqu'il est appliqué aux valeurs des pixels en vision par ordinateur, mais ce n'est pas ce que signifie la normalisation en NLP." + } + ]} +/> + +### 7. Qu'est-ce que la pré-tokénisation pour un tokenizer en sous-mots ? + +tokenizer, pour diviser l'entrée en mots.", + explain: "C'est la bonne réponse !", + correct: true + }, + { + text: "Il s'agit de l'étape précédant l'application du modèle tokenizer, qui divise l'entrée en tokens.", + explain: "La division en tokens est le travail du modèle tokenizer." + } + ]} +/> + +### 8. Sélectionnez les phrases qui s'appliquent au tokenizer BPE. + +tokens.", + explain: "C'est l'approche adoptée par un algorithme de tokénisation différent." + }, + { + text: "Un tokenizer BPE apprend les règles de fusion en fusionnant la paire de tokens la plus fréquente.", + explain: "C'est exact !", + correct: true + }, + { + text: "Un tokenizer BPE apprend une règle de fusion en fusionnant la paire de tokens qui maximise un score qui privilégie les paires fréquentes avec des parties individuelles moins fréquentes.", + explain: "C'est la stratégie appliquée par un autre algorithme de tokenization." + }, + { + text: "BPE tokenise les mots en sous-mots en les divisant en caractères, puis en appliquant les règles de fusion.", + explain: " ", + correct: true + }, + { + text: "BPE tokenise les mots en sous-mots en trouvant le plus long sous-mot du vocabulaire en commençant par le début, puis en répétant le processus pour le reste du texte.", + explain: "C'est la façon de faire d'un autre algorithme de tokenization." + }, + ]} +/> + +### 9. Sélectionnez les phrases qui s'appliquent au tokenizer WordPiece. + +tokens.", + 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.", + explain: "C'est la façon de faire d'un autre algorithme de tokenization." + }, + { + text: "Un tokenizer WordPiece apprend une règle de fusion en fusionnant la paire de tokens qui maximise un score qui privilégie les paires fréquentes avec des parties individuelles moins fréquentes.", + explain: " ", + correct: true + }, + { + text: "WordPiece tokenise les mots en sous-mots en trouvant la segmentation en tokens la plus probable, selon le modèle.", + explain: "C'est la façon de faire d'un autre algorithme de tokenization." + }, + { + text: "WordPiece tokenise les mots en sous-mots en trouvant le plus long sous-mot du vocabulaire en commençant par le début, puis en répétant le processus pour le reste du texte.", + explain: "C'est ainsi que WordPiece procède pour l'encodage.", + correct: true + }, + ]} +/> + +### 10. Sélectionnez les phrases qui s'appliquent au tokenizer Unigram. + +tokens.", + explain: " ", + correct: true + }, + { + text: "Unigram adapte son vocabulaire en minimisant une perte calculée sur l'ensemble du corpus.", + explain: " ", + correct: true + }, + { + text: "Unigram adapte son vocabulaire en conservant les sous-mots les plus fréquents.", + explain: " " + }, + { + text: "Unigram segmente les mots en sous-mots en trouvant la segmentation la plus probable en tokens, selon le modèle.", + explain: " ", + correct: true + }, + { + text: "Unigram décompose les mots en sous-mots en les divisant en caractères puis en appliquant les règles de fusion.", + explain: "C'est la façon de faire d'un autre algorithme de tokenization." + }, + ]} +/> diff --git a/chapters/fr/chapter6/2.mdx b/chapters/fr/chapter6/2.mdx index 9a29792dd..8eba2f385 100644 --- a/chapters/fr/chapter6/2.mdx +++ b/chapters/fr/chapter6/2.mdx @@ -1,265 +1,265 @@ -# Entraîner un nouveau tokenizer à partir d'un ancien - - - -Si un modèle de langue n'est pas disponible dans la langue qui vous intéresse ou si votre corpus est très différent de celui sur lequel votre modèle de langue a été entraîné, vous voudrez très probablement réentraîner le modèle à partir de zéro en utilisant un *tokenizer* adapté à vos données. Pour ce faire, vous devrez entraîner un nouveau *tokenizer* sur votre jeu de données. Mais qu'est-ce que cela signifie exactement ? Lorsque nous avons examiné pour la première fois les *tokenizers* dans le [chapitre 2](/course/fr/chapter2), nous avons vu que la plupart des *transformers* utilisent un _algorithme de tokenisation en sous-mots_. Pour identifier les sous-mots qui sont intéressants et qui apparaissent le plus fréquemment dans un corpus donné, le *tokenizer* doit examiner attentivement tous les textes du corpus. C'est un processus que nous appelons *entraînement*. Les règles exactes qui régissent cet apprentissage dépendent du type de *tokenizer* utilisé. Nous passerons en revue les trois principaux algorithmes plus loin dans ce chapitre. - - - - - -⚠️ Entraîner un *tokenizer* n'est pas la même chose qu'entraîner un modèle ! L'entraînement du modèle utilise la descente de gradient stochastique pour réduire un peu plus la perte à chaque batch. Il est par nature aléatoire (ce qui signifie que vous devez définir des graines pour obtenir les mêmes résultats lorsque vous effectuez deux fois le même entraînement). Entraîner un *tokenizer* est un processus statistique qui identifie les meilleurs sous-mots à choisir pour un corpus donné. Les règles exactes utilisées pour les choisir dépendent de l'algorithme de tokénisation. Le processus est déterministe, ce qui signifie que vous obtenez toujours les mêmes résultats lorsque vous vous entraînez avec le même algorithme sur le même corpus. - - - - -## Assemblage d'un corpus - -Il y a une API très simple dans 🤗 *Transformers* que vous pouvez utiliser pour entraîner un nouveau *tokenizer* avec les mêmes caractéristiques qu'un déjà existant : `AutoTokenizer.train_new_from_iterator()`. Pour illustrer cela, disons que nous voulons entraîner GPT-2 à partir de zéro mais dans une langue autre que l'anglais. Notre première tâche est de rassembler des batchs de données dans cette langue dans un corpus d'entraînement. Pour avoir des exemples que tout le monde puisse comprendre, nous n'utiliserons pas ici une langue comme le russe ou le chinois mais plutôt une langue anglaise spécialisée : le langage Python. - -La bibliothèque [🤗 *Datasets*](https://github.com/huggingface/datasets) peut nous aider à assembler un corpus de code source Python. Nous allons utiliser la fonction habituelle `load_dataset()` pour télécharger et mettre en cache le jeu de données [CodeSearchNet](https://huggingface.co/datasets/code_search_net). Ce jeu de données a été créé pour le [CodeSearchNet challenge](https://wandb.ai/github/CodeSearchNet/benchmark) et contient des millions de fonctions provenant de bibliothèques open source sur GitHub dans plusieurs langages de programmation. Ici, nous allons charger la partie Python de ce jeu de données : - -```py -from datasets import load_dataset - -# Cela peut prendre quelques minutes alors prenez un thé ou un café pendant que vous patientez ! -raw_datasets = load_dataset("code_search_net", "python") -``` - -Nous pouvons jeter un coup d'œil au jeu d'entraînement pour voir quelles sont les colonnes auxquelles nous avons accès : - -```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 -}) -``` - -Nous pouvons voir que le jeu de données sépare les chaînes de documents du code et suggère une tokenization des deux. Ici, nous utiliserons simplement la colonne `whole_func_string` pour entraîner notre *tokenizer*. Nous pouvons regarder un exemple de la façon suivante : - -```py -print(raw_datasets["train"][123456]["whole_func_string"]) -``` - -qui nous affiche ce qui suit : - -```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) -``` - -La première chose à faire est de transformer le jeu de données en un _itérateur_ de listes de textes. Par exemple, une liste de listes de textes. L'utilisation de listes de textes permet à notre *tokenizer* d'aller plus vite (l'entraînement a alors lieu sur des batchs de textes au lieu de traiter des textes un par un). Et le fait que ce soit un itérateur permet d'éviter d'avoir tout en mémoire en même temps. Si votre corpus est énorme, vous voudrez profiter du fait que 🤗 *Datasets* ne charge pas tout en RAM mais stocke les éléments du jeu de données sur le disque. - -Faire ce qui suit créerait une liste de listes de 1 000 textes chacune mais chargerait tout en mémoire : - - -```py -# Ne décommentez pas la ligne suivante à moins que votre jeu de données soit petit ! -# training_corpus = [raw_datasets["train"][i: i + 1000]["whole_func_string"] for i in range(0, len(raw_datasets["train"]), 1000)] -``` - -En utilisant un générateur, nous pouvons éviter que Python ne charge quoi que ce soit en mémoire à moins que cela soit réellement nécessaire. Pour créer un tel générateur, il suffit de remplacer les crochets par des parenthèses : - -```py -training_corpus = ( - raw_datasets["train"][i : i + 1000]["whole_func_string"] - for i in range(0, len(raw_datasets["train"]), 1000) -) -``` - -Cette ligne de code ne récupère aucun élément du jeu de données. Elle crée simplement un objet que vous pouvez utiliser dans une boucle `for` Python. Les textes ne seront chargés que lorsque vous en aurez besoin (c'est-à-dire lorsque vous serez à l'étape de la boucle `for` qui les requiert) et seulement 1 000 textes à la fois. De cette façon, vous n'épuiserez pas toute votre mémoire, même si vous traitez un énorme jeu de données. - -Le problème avec un objet générateur est qu'il ne peut être utilisé qu'une seule fois. Ainsi, au lieu que cet objet nous donne deux fois la liste des 10 premiers chiffres : - - -```py -gen = (i for i in range(10)) -print(list(gen)) -print(list(gen)) -``` - -on les reçoit une fois et ensuite une liste vide : - -```python out -[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] -[] -``` - -C'est pourquoi nous définissons une fonction qui renvoie un générateur à la place : - -```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() -``` - -Vous pouvez également définir votre générateur à l'intérieur d'une boucle `for` en utilisant l'instruction `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"] -``` - -qui produit exactement le même générateur que précédemment mais permet d'utiliser une logique plus complexe que celle que vous pouvez utiliser dans une compréhension de liste. - - -## Entraînement d'un nouveau tokenizer - -Maintenant que nous avons notre corpus sous la forme d'un itérateur de batchs de textes, nous sommes prêts à entraîner un nouveau *tokenizer*. Pour ce faire, nous devons d'abord charger le *tokenizer* que nous voulons coupler avec notre modèle (ici, le GPT-2) : - - -```py -from transformers import AutoTokenizer - -old_tokenizer = AutoTokenizer.from_pretrained("gpt2") -``` - -Même si nous allons entraîner un nouveau *tokenizer*, c'est une bonne idée de faire ça pour éviter de partir entièrement de zéro. De cette façon, nous n'aurons pas à spécifier l'algorithme de tokénisation ou les jetons spéciaux que nous voulons utiliser. Notre nouveau *tokenizer* sera exactement le même que celui du GPT-2. La seule chose qui changera sera le vocabulaire qui sera déterminé lors de l'entraînement sur notre corpus. - -Voyons d'abord comment ce *tokenizer* traiterait un exemple de fonction : - - -```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'] -``` - -Ce *tokenizer* possède quelques symboles spéciaux, comme `Ġ` et `Ċ`, qui désignent respectivement les espaces et les retours à la ligne. Comme on peut le voir, ce n'est pas très efficace. Le *tokenizer* renvoie des jetons individuels pour chaque espace alors qu'il pourrait regrouper ceux des indentations (puisqu’avoir des ensembles de quatre ou huit espaces est très courant dans du code). Il divise également le nom de la fonction de façon un peu bizarre car pas habitué à voir des mots avec le caractère `_`. - -Entraînons un nouveau *tokenizer* et voyons s'il résout ces problèmes. Pour cela, nous allons utiliser la méthode `train_new_from_iterator()` : - - -```py -tokenizer = old_tokenizer.train_new_from_iterator(training_corpus, 52000) -``` - -Cette commande peut prendre un peu de temps si votre corpus est très grand. Pour ce jeu de données de 1,6 Go de textes, elle est très rapide (1 minute 16 secondes sur un CPU AMD Ryzen 9 3900X avec 12 cœurs). - -Notez que `AutoTokenizer.train_new_from_iterator()` ne fonctionne que si le *tokenizer* que vous utilisez est un *tokenizer* « rapide ». Comme vous le verrez dans la section suivante, la bibliothèque 🤗 *Transformers* contient deux types de *tokenizers* : certains sont écrits en pur Python et d'autres (les rapides) sont soutenus par la bibliothèque 🤗 *Tokenizers* qui est écrite dans le langage [Rust](https://www.rust-lang.org). Python est le langage le plus souvent utilisé pour les applications de science des données et d'apprentissage profond, mais lorsque quelque chose doit être parallélisé pour être rapide, il faut que cela soit écrit dans un autre langage. Par exemple, les multiplications matricielles qui sont au cœur du calcul du modèle sont écrites en CUDA, une bibliothèque en C optimisée pour les GPUs. - -Entraîner un tout nouveau *tokenizer* en Python pur est atrocement lent, c'est pourquoi nous avons développé la bibliothèque 🤗 *Tokenizers*. Notez que, tout comme vous n'avez pas eu à apprendre le langage CUDA pour pouvoir exécuter votre modèle sur un batch d'entrées sur un GPU, vous n'aurez pas besoin d'apprendre Rust pour utiliser un *tokenizer* rapide. La bibliothèque 🤗 *Tokenizers* fournit des liaisons Python pour de nombreuses méthodes qui appellent en interne un morceau de code en Rust. Par exemple, pour paralléliser l'entraînement de votre nouveau *tokenizer* ou, comme nous l'avons vu dans le [chapitre 3](/course/fr/chapter3), la tokenisation d'un lot d'entrées. - -La plupart des *transformers* ont un *tokenizer* rapide de disponible. Il y a quelques exceptions que vous pouvez vérifier [ici](https://huggingface.co/transformers/#supported-frameworks). S'il est disponible, l'API `AutoTokenizer` sélectionne toujours pour vous le *tokenizer* rapide. Dans la prochaine section, nous allons jeter un coup d'oeil à certaines des autres caractéristiques spéciales des *tokenizers* rapides, qui seront très utiles pour des tâches comme la classification de *tokens* et la réponse aux questions. Mais avant cela, essayons notre tout nouveau *tokenizer* sur l'exemple précédent : - -```py -tokens = tokenizer.tokenize(example) -tokens -``` - -```python out -['def', 'Ġadd', '_', 'numbers', '(', 'a', ',', 'Ġb', '):', 'ĊĠĠĠ', 'Ġ"""', 'Add', 'Ġthe', 'Ġtwo', 'Ġnumbers', 'Ġ`', - 'a', '`', 'Ġand', 'Ġ`', 'b', '`."""', 'ĊĠĠĠ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb'] -``` - -Ici, nous voyons à nouveau les symboles spéciaux `Ġ` et `Ċ` qui indiquent les espaces et les retours à la ligne. Nous pouvons également voir que notre *tokenizer* a appris certains *tokens* qui sont très spécifiques à un corpus de fonctions Python. Par exemple, il y a un token `ĊĠĠĠ` qui représente une indentation et un *token* `Ġ"""` qui représente les trois guillemets qui commencent une *docstring*. Le *tokenizer* divise également correctement le nom de la fonction sur `_`. Il s'agit d'une représentation assez compacte. En comparaison, l'utilisation du *tokenizer* en anglais « simple » sur le même exemple nous donnera une phrase plus longue : - -```py -print(len(tokens)) -print(len(old_tokenizer.tokenize(example))) -``` - -```python out -27 -36 -``` - -Prenons un autre exemple : - -```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 plus du *token* correspondant à une indentation, on peut également voir ici un *token* pour une double indentation : `ĊĠĠĠĠĠĠĠĠĠ`. Les mots spéciaux de Python comme `class`, `init`, `call`, `self`, et `return` sont tous tokenizés comme un seul *token*. Nous pouvons voir qu'en plus de séparer sur `_` et `.` le tokenizer sépare correctement même les noms en minuscules. Par exemple `LinearLayer` est tokenisé comme `["ĠLinear", "Layer"]`. - -## Sauvegarde du tokenizer - -Pour être sûr de pouvoir l'utiliser plus tard, nous devons sauvegarder notre nouveau *tokenizer*. Comme pour les modèles, ceci est fait avec la méthode `save_pretrained()` : - - -```py -tokenizer.save_pretrained("code-search-net-tokenizer") -``` - -Cela créera un nouveau dossier nommé *code-search-net-tokenizer* contenant tous les fichiers dont le *tokenizer* a besoin pour être rechargé. Si vous souhaitez partager ce *tokenizer* avec vos collègues et amis, vous pouvez le télécharger sur le *Hub* en vous connectant à votre compte. Si vous travaillez dans un *notebook*, il existe une fonction pratique pour vous aider à le faire : - -```python -from huggingface_hub import notebook_login - -notebook_login() -``` - -Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face. Si vous ne travaillez pas sur un ordinateur portable, tapez simplement la ligne suivante dans votre terminal : - -```bash -huggingface-cli login -``` - -Une fois connecté, vous pouvez pousser votre *tokenizer* en exécutant la commande suivante : - -```py -tokenizer.push_to_hub("code-search-net-tokenizer") -``` - -Cela créera un nouveau dépôt dans votre espace avec le nom `code-search-net-tokenizer` contenant le fichier *tokenizer*. Vous pouvez ensuite charger le *tokenizer* de n'importe où avec la méthode `from_pretrained()` : - -```py -# Remplacez "huggingface-course" ci-dessous par votre espace réel pour utiliser votre propre tokenizer -tokenizer = AutoTokenizer.from_pretrained("huggingface-course/code-search-net-tokenizer") -``` - +# Entraîner un nouveau tokenizer à partir d'un ancien + + + +Si un modèle de langue n'est pas disponible dans la langue qui vous intéresse ou si votre corpus est très différent de celui sur lequel votre modèle de langue a été entraîné, vous voudrez très probablement réentraîner le modèle à partir de zéro en utilisant un *tokenizer* adapté à vos données. Pour ce faire, vous devrez entraîner un nouveau *tokenizer* sur votre jeu de données. Mais qu'est-ce que cela signifie exactement ? Lorsque nous avons examiné pour la première fois les *tokenizers* dans le [chapitre 2](/course/fr/chapter2), nous avons vu que la plupart des *transformers* utilisent un _algorithme de tokenisation en sous-mots_. Pour identifier les sous-mots qui sont intéressants et qui apparaissent le plus fréquemment dans un corpus donné, le *tokenizer* doit examiner attentivement tous les textes du corpus. C'est un processus que nous appelons *entraînement*. Les règles exactes qui régissent cet apprentissage dépendent du type de *tokenizer* utilisé. Nous passerons en revue les trois principaux algorithmes plus loin dans ce chapitre. + + + + + +⚠️ Entraîner un *tokenizer* n'est pas la même chose qu'entraîner un modèle ! L'entraînement du modèle utilise la descente de gradient stochastique pour réduire un peu plus la perte à chaque batch. Il est par nature aléatoire (ce qui signifie que vous devez définir des graines pour obtenir les mêmes résultats lorsque vous effectuez deux fois le même entraînement). Entraîner un *tokenizer* est un processus statistique qui identifie les meilleurs sous-mots à choisir pour un corpus donné. Les règles exactes utilisées pour les choisir dépendent de l'algorithme de tokénisation. Le processus est déterministe, ce qui signifie que vous obtenez toujours les mêmes résultats lorsque vous vous entraînez avec le même algorithme sur le même corpus. + + + + +## Assemblage d'un corpus + +Il y a une API très simple dans 🤗 *Transformers* que vous pouvez utiliser pour entraîner un nouveau *tokenizer* avec les mêmes caractéristiques qu'un déjà existant : `AutoTokenizer.train_new_from_iterator()`. Pour illustrer cela, disons que nous voulons entraîner GPT-2 à partir de zéro mais dans une langue autre que l'anglais. Notre première tâche est de rassembler des batchs de données dans cette langue dans un corpus d'entraînement. Pour avoir des exemples que tout le monde puisse comprendre, nous n'utiliserons pas ici une langue comme le russe ou le chinois mais plutôt une langue anglaise spécialisée : le langage Python. + +La bibliothèque [🤗 *Datasets*](https://github.com/huggingface/datasets) peut nous aider à assembler un corpus de code source Python. Nous allons utiliser la fonction habituelle `load_dataset()` pour télécharger et mettre en cache le jeu de données [CodeSearchNet](https://huggingface.co/datasets/code_search_net). Ce jeu de données a été créé pour le [CodeSearchNet challenge](https://wandb.ai/github/CodeSearchNet/benchmark) et contient des millions de fonctions provenant de bibliothèques open source sur GitHub dans plusieurs langages de programmation. Ici, nous allons charger la partie Python de ce jeu de données : + +```py +from datasets import load_dataset + +# Cela peut prendre quelques minutes alors prenez un thé ou un café pendant que vous patientez ! +raw_datasets = load_dataset("code_search_net", "python") +``` + +Nous pouvons jeter un coup d'œil au jeu d'entraînement pour voir quelles sont les colonnes auxquelles nous avons accès : + +```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 +}) +``` + +Nous pouvons voir que le jeu de données sépare les chaînes de documents du code et suggère une tokenization des deux. Ici, nous utiliserons simplement la colonne `whole_func_string` pour entraîner notre *tokenizer*. Nous pouvons regarder un exemple de la façon suivante : + +```py +print(raw_datasets["train"][123456]["whole_func_string"]) +``` + +qui nous affiche ce qui suit : + +```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) +``` + +La première chose à faire est de transformer le jeu de données en un _itérateur_ de listes de textes. Par exemple, une liste de listes de textes. L'utilisation de listes de textes permet à notre *tokenizer* d'aller plus vite (l'entraînement a alors lieu sur des batchs de textes au lieu de traiter des textes un par un). Et le fait que ce soit un itérateur permet d'éviter d'avoir tout en mémoire en même temps. Si votre corpus est énorme, vous voudrez profiter du fait que 🤗 *Datasets* ne charge pas tout en RAM mais stocke les éléments du jeu de données sur le disque. + +Faire ce qui suit créerait une liste de listes de 1 000 textes chacune mais chargerait tout en mémoire : + + +```py +# Ne décommentez pas la ligne suivante à moins que votre jeu de données soit petit ! +# training_corpus = [raw_datasets["train"][i: i + 1000]["whole_func_string"] for i in range(0, len(raw_datasets["train"]), 1000)] +``` + +En utilisant un générateur, nous pouvons éviter que Python ne charge quoi que ce soit en mémoire à moins que cela soit réellement nécessaire. Pour créer un tel générateur, il suffit de remplacer les crochets par des parenthèses : + +```py +training_corpus = ( + raw_datasets["train"][i : i + 1000]["whole_func_string"] + for i in range(0, len(raw_datasets["train"]), 1000) +) +``` + +Cette ligne de code ne récupère aucun élément du jeu de données. Elle crée simplement un objet que vous pouvez utiliser dans une boucle `for` Python. Les textes ne seront chargés que lorsque vous en aurez besoin (c'est-à-dire lorsque vous serez à l'étape de la boucle `for` qui les requiert) et seulement 1 000 textes à la fois. De cette façon, vous n'épuiserez pas toute votre mémoire, même si vous traitez un énorme jeu de données. + +Le problème avec un objet générateur est qu'il ne peut être utilisé qu'une seule fois. Ainsi, au lieu que cet objet nous donne deux fois la liste des 10 premiers chiffres : + + +```py +gen = (i for i in range(10)) +print(list(gen)) +print(list(gen)) +``` + +on les reçoit une fois et ensuite une liste vide : + +```python out +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +[] +``` + +C'est pourquoi nous définissons une fonction qui renvoie un générateur à la place : + +```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() +``` + +Vous pouvez également définir votre générateur à l'intérieur d'une boucle `for` en utilisant l'instruction `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"] +``` + +qui produit exactement le même générateur que précédemment mais permet d'utiliser une logique plus complexe que celle que vous pouvez utiliser dans une compréhension de liste. + + +## Entraînement d'un nouveau tokenizer + +Maintenant que nous avons notre corpus sous la forme d'un itérateur de batchs de textes, nous sommes prêts à entraîner un nouveau *tokenizer*. Pour ce faire, nous devons d'abord charger le *tokenizer* que nous voulons coupler avec notre modèle (ici, le GPT-2) : + + +```py +from transformers import AutoTokenizer + +old_tokenizer = AutoTokenizer.from_pretrained("gpt2") +``` + +Même si nous allons entraîner un nouveau *tokenizer*, c'est une bonne idée de faire ça pour éviter de partir entièrement de zéro. De cette façon, nous n'aurons pas à spécifier l'algorithme de tokénisation ou les jetons spéciaux que nous voulons utiliser. Notre nouveau *tokenizer* sera exactement le même que celui du GPT-2. La seule chose qui changera sera le vocabulaire qui sera déterminé lors de l'entraînement sur notre corpus. + +Voyons d'abord comment ce *tokenizer* traiterait un exemple de fonction : + + +```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'] +``` + +Ce *tokenizer* possède quelques symboles spéciaux, comme `Ġ` et `Ċ`, qui désignent respectivement les espaces et les retours à la ligne. Comme on peut le voir, ce n'est pas très efficace. Le *tokenizer* renvoie des jetons individuels pour chaque espace alors qu'il pourrait regrouper ceux des indentations (puisqu’avoir des ensembles de quatre ou huit espaces est très courant dans du code). Il divise également le nom de la fonction de façon un peu bizarre car pas habitué à voir des mots avec le caractère `_`. + +Entraînons un nouveau *tokenizer* et voyons s'il résout ces problèmes. Pour cela, nous allons utiliser la méthode `train_new_from_iterator()` : + + +```py +tokenizer = old_tokenizer.train_new_from_iterator(training_corpus, 52000) +``` + +Cette commande peut prendre un peu de temps si votre corpus est très grand. Pour ce jeu de données de 1,6 Go de textes, elle est très rapide (1 minute 16 secondes sur un CPU AMD Ryzen 9 3900X avec 12 cœurs). + +Notez que `AutoTokenizer.train_new_from_iterator()` ne fonctionne que si le *tokenizer* que vous utilisez est un *tokenizer* « rapide ». Comme vous le verrez dans la section suivante, la bibliothèque 🤗 *Transformers* contient deux types de *tokenizers* : certains sont écrits en pur Python et d'autres (les rapides) sont soutenus par la bibliothèque 🤗 *Tokenizers* qui est écrite dans le langage [Rust](https://www.rust-lang.org). Python est le langage le plus souvent utilisé pour les applications de science des données et d'apprentissage profond, mais lorsque quelque chose doit être parallélisé pour être rapide, il faut que cela soit écrit dans un autre langage. Par exemple, les multiplications matricielles qui sont au cœur du calcul du modèle sont écrites en CUDA, une bibliothèque en C optimisée pour les GPUs. + +Entraîner un tout nouveau *tokenizer* en Python pur est atrocement lent, c'est pourquoi nous avons développé la bibliothèque 🤗 *Tokenizers*. Notez que, tout comme vous n'avez pas eu à apprendre le langage CUDA pour pouvoir exécuter votre modèle sur un batch d'entrées sur un GPU, vous n'aurez pas besoin d'apprendre Rust pour utiliser un *tokenizer* rapide. La bibliothèque 🤗 *Tokenizers* fournit des liaisons Python pour de nombreuses méthodes qui appellent en interne un morceau de code en Rust. Par exemple, pour paralléliser l'entraînement de votre nouveau *tokenizer* ou, comme nous l'avons vu dans le [chapitre 3](/course/fr/chapter3), la tokenisation d'un lot d'entrées. + +La plupart des *transformers* ont un *tokenizer* rapide de disponible. Il y a quelques exceptions que vous pouvez vérifier [ici](https://huggingface.co/transformers/#supported-frameworks). S'il est disponible, l'API `AutoTokenizer` sélectionne toujours pour vous le *tokenizer* rapide. Dans la prochaine section, nous allons jeter un coup d'oeil à certaines des autres caractéristiques spéciales des *tokenizers* rapides, qui seront très utiles pour des tâches comme la classification de *tokens* et la réponse aux questions. Mais avant cela, essayons notre tout nouveau *tokenizer* sur l'exemple précédent : + +```py +tokens = tokenizer.tokenize(example) +tokens +``` + +```python out +['def', 'Ġadd', '_', 'numbers', '(', 'a', ',', 'Ġb', '):', 'ĊĠĠĠ', 'Ġ"""', 'Add', 'Ġthe', 'Ġtwo', 'Ġnumbers', 'Ġ`', + 'a', '`', 'Ġand', 'Ġ`', 'b', '`."""', 'ĊĠĠĠ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb'] +``` + +Ici, nous voyons à nouveau les symboles spéciaux `Ġ` et `Ċ` qui indiquent les espaces et les retours à la ligne. Nous pouvons également voir que notre *tokenizer* a appris certains *tokens* qui sont très spécifiques à un corpus de fonctions Python. Par exemple, il y a un token `ĊĠĠĠ` qui représente une indentation et un *token* `Ġ"""` qui représente les trois guillemets qui commencent une *docstring*. Le *tokenizer* divise également correctement le nom de la fonction sur `_`. Il s'agit d'une représentation assez compacte. En comparaison, l'utilisation du *tokenizer* en anglais « simple » sur le même exemple nous donnera une phrase plus longue : + +```py +print(len(tokens)) +print(len(old_tokenizer.tokenize(example))) +``` + +```python out +27 +36 +``` + +Prenons un autre exemple : + +```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 plus du *token* correspondant à une indentation, on peut également voir ici un *token* pour une double indentation : `ĊĠĠĠĠĠĠĠĠĠ`. Les mots spéciaux de Python comme `class`, `init`, `call`, `self`, et `return` sont tous tokenizés comme un seul *token*. Nous pouvons voir qu'en plus de séparer sur `_` et `.` le tokenizer sépare correctement même les noms en minuscules. Par exemple `LinearLayer` est tokenisé comme `["ĠLinear", "Layer"]`. + +## Sauvegarde du tokenizer + +Pour être sûr de pouvoir l'utiliser plus tard, nous devons sauvegarder notre nouveau *tokenizer*. Comme pour les modèles, ceci est fait avec la méthode `save_pretrained()` : + + +```py +tokenizer.save_pretrained("code-search-net-tokenizer") +``` + +Cela créera un nouveau dossier nommé *code-search-net-tokenizer* contenant tous les fichiers dont le *tokenizer* a besoin pour être rechargé. Si vous souhaitez partager ce *tokenizer* avec vos collègues et amis, vous pouvez le télécharger sur le *Hub* en vous connectant à votre compte. Si vous travaillez dans un *notebook*, il existe une fonction pratique pour vous aider à le faire : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face. Si vous ne travaillez pas sur un ordinateur portable, tapez simplement la ligne suivante dans votre terminal : + +```bash +huggingface-cli login +``` + +Une fois connecté, vous pouvez pousser votre *tokenizer* en exécutant la commande suivante : + +```py +tokenizer.push_to_hub("code-search-net-tokenizer") +``` + +Cela créera un nouveau dépôt dans votre espace avec le nom `code-search-net-tokenizer` contenant le fichier *tokenizer*. Vous pouvez ensuite charger le *tokenizer* de n'importe où avec la méthode `from_pretrained()` : + +```py +# Remplacez "huggingface-course" ci-dessous par votre espace réel pour utiliser votre propre tokenizer +tokenizer = AutoTokenizer.from_pretrained("huggingface-course/code-search-net-tokenizer") +``` + Vous êtes maintenant prêt à entraîner un modèle de langue à partir de zéro et à le *finetuner* sur votre tâche ! Nous verrons cela dans le [chapitre 7](/course/fr/chapter7), mais d'abord, dans le reste de ce chapitre, nous allons examiner de plus près les *tokenizers* rapides et explorer en détail ce qui se passe lorsque nous appelons la méthode `train_new_from_iterator()`. \ No newline at end of file diff --git a/chapters/fr/chapter6/3.mdx b/chapters/fr/chapter6/3.mdx index c37fc06ff..41d437a8d 100644 --- a/chapters/fr/chapter6/3.mdx +++ b/chapters/fr/chapter6/3.mdx @@ -1,477 +1,477 @@ - - -# Pouvoirs spéciaux des tokenizers rapides - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -Dans cette section, nous allons examiner de plus près les capacités des *tokenizers* dans 🤗 *Transformers*. -Jusqu'à présent, nous ne les avons utilisés que pour tokeniser les entrées ou décoder les identifiants pour revenir à du texte. Mais les *tokenizers*, et surtout ceux soutenus par la bibliothèque 🤗 *Tokenizers*, peuvent faire beaucoup plus. Pour illustrer ces fonctionnalités supplémentaires, nous allons explorer comment reproduire les résultats des pipelines `token-classification` (que nous avons appelé `ner`) et `question-answering` que nous avons rencontrés pour la première fois dans le [chapitre 1](/course/fr/chapter1). - - - -Dans la discussion qui suit, nous ferons souvent la distinction entre les *tokenizers* « lents » et les « rapides ». Les *tokenizers* lents sont ceux écrits en Python à l'intérieur de la bibliothèque 🤗 *Transformers*, tandis que les rapides sont ceux fournis par 🤗 *Tokenizers* et sont codés en Rust. Si vous vous souvenez du tableau du [chapitre 5](/course/fr/chapter5/3) qui indiquait combien de temps il fallait à un *tokenizer* rapide et à un *tokenizer* lent pour tokeniser le jeu de données *Drug Review*, vous devriez avoir une idée de la raison pour laquelle nous les appelons rapides et lents : - - | *Tokenizer* rapide | *Tokenizer* lent -:--------------:|:--------------:|:-------------: -`batched=True` | 10.8s | 4min41s -`batched=False` | 59.2s | 5min3s - - - -⚠️ Lors de la tokenisation d'une seule phrase, vous ne verrez pas toujours une différence de vitesse entre les versions lente et rapide d'un même *tokenizer*. En fait, la version rapide peut même être plus lente ! Ce n'est que lorsque vous tokenisez beaucoup de textes en parallèle et en même temps que vous pourrez clairement voir la différence. - - - -## L'objet BatchEncoding - - - -La sortie d'un *tokenizer* n'est pas un simple dictionnaire Python. Ce que nous obtenons est en fait un objet spécial `BatchEncoding`. C'est une sous-classe d'un dictionnaire (c'est pourquoi nous avons pu indexer ce résultat sans problème auparavant), mais avec des méthodes supplémentaires qui sont principalement utilisées par les *tokenizers* rapides. - -En plus de leurs capacités de parallélisation, la fonctionnalité clé des *tokenizers* rapides est qu'ils gardent toujours la trace de l'étendue originale des textes d'où proviennent les *tokens* finaux, une fonctionnalité que nous appelons *mapping offset*. Cela permet de débloquer des fonctionnalités telles que le mappage de chaque mot aux *tokens* qu'il a générés ou le mappage de chaque caractère du texte original au *token* qu'il contient, et vice versa. - -Prenons un exemple : - -```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." -# Je m'appelle Sylvain et je travaille chez Hugging Face à Brooklyn. -encoding = tokenizer(example) -print(type(encoding)) -``` - -Comme mentionné précédemment, nous obtenons un objet `BatchEncoding` dans la sortie du *tokenizer* : - -```python out - -``` - -Puisque la classe `AutoTokenizer` choisit un *tokenizer* rapide par défaut, nous pouvons utiliser les méthodes supplémentaires que cet objet `BatchEncoding` fournit. Nous avons deux façons de vérifier si notre *tokenizer* est rapide ou lent. Nous pouvons soit vérifier l'attribut `is_fast` du *tokenizer* comme suit : - -```python -tokenizer.is_fast -``` - -```python out -True -``` - -soit vérifier le même attribut mais avec notre `encoding` : - -```python -encoding.is_fast -``` - -```python out -True -``` - -Voyons ce qu'un *tokenizer* rapide nous permet de faire. Tout d'abord, nous pouvons accéder aux *tokens* sans avoir à reconvertir les identifiants en *tokens* : - -```py -encoding.tokens() -``` - -```python out -['[CLS]', 'My', 'name', 'is', 'S', '##yl', '##va', '##in', 'and', 'I', 'work', 'at', 'Hu', '##gging', 'Face', 'in', - 'Brooklyn', '.', '[SEP]'] -``` - -Dans ce cas, le *token* à l'index 5 est `##yl` et fait partie du mot « Sylvain » dans la phrase originale. Nous pouvons également utiliser la méthode `word_ids()` pour obtenir l'index du mot dont provient chaque *token* : - -```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] -``` - -On peut voir que les *tokens* spéciaux du *tokenizer*, `[CLS]` et `[SEP]`, sont mis en correspondance avec `None` et que chaque *token* est mis en correspondance avec le mot dont il provient. Ceci est particulièrement utile pour déterminer si un *token* est au début d'un mot ou si deux *tokens* sont dans le même mot. Nous pourrions nous appuyer sur le préfixe `##` pour cela, mais il ne fonctionne que pour les *tokenizers* de type BERT. Cette méthode fonctionne pour n'importe quel type de *tokenizer*, du moment qu'il est rapide. Dans le chapitre suivant, nous verrons comment utiliser cette capacité pour appliquer correctement les étiquettes que nous avons pour chaque mot aux *tokens* dans des tâches comme la reconnaissance d'entités nommées et le POS (*Part-of-speech*). Nous pouvons également l'utiliser pour masquer tous les *tokens* provenant du même mot dans la modélisation du langage masqué (une technique appelée _whole word masking_). - - - -La notion de ce qu'est un mot est compliquée. Par exemple, est-ce que « I'll » (contraction de « I will ») compte pour un ou deux mots ? Cela dépend en fait du *tokenizer* et de l'opération de prétokénisation qu'il applique. Certains *tokenizer* se contentent de séparer les espaces et considèrent donc qu'il s'agit d'un seul mot. D'autres utilisent la ponctuation en plus des espaces et considèrent donc qu'il s'agit de deux mots. - - - -✏️ **Essayez !** Créez un *tokenizer* à partir des checkpoints `bert-base-cased` et `roberta-base` et tokenisez « 81s » avec. Qu'observez-vous ? Quels sont les identifiants des mots ? - - - -De même, il existe une méthode `sentence_ids()` que nous pouvons utiliser pour associer un *token* à la phrase dont il provient (bien que dans ce cas, le `token_type_ids` retourné par le *tokenizer* peut nous donner la même information). - -Enfin, nous pouvons faire correspondre n'importe quel mot ou *token* aux caractères du texte d'origine (et vice versa) grâce aux méthodes `word_to_chars()` ou `token_to_chars()` et `char_to_word()` ou `char_to_token()`. Par exemple, la méthode `word_ids()` nous a dit que `##yl` fait partie du mot à l'indice 3, mais de quel mot s'agit-il dans la phrase ? Nous pouvons le découvrir comme ceci : - -```py -start, end = encoding.word_to_chars(3) -example[start:end] -``` - -```python out -Sylvain -``` - -Comme nous l'avons mentionné précédemment, tout ceci est rendu possible par le fait que le *tokenizer* rapide garde la trace de la partie du texte d'où provient chaque *token* dans une liste d'*offsets*. Pour illustrer leur utilisation, nous allons maintenant vous montrer comment reproduire manuellement les résultats du pipeline `token-classification`. - - - -✏️ **Essayez !** Rédigez votre propre texte et voyez si vous pouvez comprendre quels *tokens* sont associés à l'identifiant du mot et comment extraire les étendues de caractères pour un seul mot. Pour obtenir des points bonus, essayez d'utiliser deux phrases en entrée et voyez si les identifiants ont un sens pour vous. - - - -## A l'intérieur du pipeline `token-classification` - -Dans le [chapitre 1](/course/fr/chapter1), nous avons eu un premier aperçu de la NER (où la tâche est d'identifier les parties du texte qui correspondent à des entités telles que des personnes, des lieux ou des organisations) avec la fonction `pipeline()` de 🤗 *Transformers*. Puis, dans le [chapitre 2](/course/fr/chapter2), nous avons vu comment un pipeline regroupe les trois étapes nécessaires pour obtenir les prédictions à partir d'un texte brut : la tokenisation, le passage des entrées dans le modèle et le post-traitement. Les deux premières étapes du pipeline de `token-classification` sont les mêmes que dans tout autre pipeline mais le post-traitement est un peu plus complexe. Voyons comment ! - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -### Obtenir les résultats de base avec le pipeline - -Tout d'abord, prenons un pipeline de classification de *tokens* afin d'obtenir des résultats à comparer manuellement. Le modèle utilisé par défaut est [`dbmdz/bert-large-cased-finetuned-conll03-english`](https://huggingface.co/dbmdz/bert-large-cased-finetuned-conll03-english). Il effectue une NER sur les phrases : - -```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}] -``` - -Le modèle a correctement identifié chaque *token* généré par « Sylvain » comme une personne, chaque *token* généré par « Hugging Face » comme une organisation, et le *token* « Brooklyn » comme un lieu. Nous pouvons également demander au pipeline de regrouper les *tokens* qui correspondent à la même entité : - -```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 propriété `aggregation_strategy` choisie va changer les scores calculés pour chaque entité groupée. Avec `"simple"` le score est juste la moyenne des scores de chaque *token* dans l'entité donnée. Par exemple, le score de « Sylvain » est la moyenne des scores que nous avons vu dans l'exemple précédent pour les tokens `S`, `##yl`, `##va`, et `##in`. D'autres stratégies sont disponibles : - -- `"first"`, où le score de chaque entité est le score du premier *token* de cette entité (donc pour « Sylvain » ce serait 0.993828, le score du token `S`) -- `"max"`, où le score de chaque entité est le score maximal des *tokens* de cette entité (ainsi, pour « Hugging Face », le score de « Face » serait de 0,98879766). -- `"average"`, où le score de chaque entité est la moyenne des scores des mots qui composent cette entité (ainsi, pour « Sylvain », il n'y aurait pas de différence avec la stratégie `"simple"`, mais "Hugging Face" aurait un score de 0,9819, la moyenne des scores de « Hugging », 0,975, et « Face », 0,98879). - -Voyons maintenant comment obtenir ces résultats sans utiliser la fonction `pipeline()` ! - -### Des entrées aux prédictions - -{#if fw === 'pt'} - -D'abord, nous devons tokeniser notre entrée et la faire passer dans le modèle. Cela se fait exactement comme dans le [chapitre 2](/course/fr/chapter2). Nous instancions le *tokenizer* et le modèle en utilisant les classes `TFAutoXxx` et les utilisons ensuite dans notre exemple : - -```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) -``` - -Puisque nous utilisons `AutoModelForTokenClassification`, nous obtenons un ensemble de logits pour chaque *token* dans la séquence d'entrée : - -```py -print(inputs["input_ids"].shape) -print(outputs.logits.shape) -``` - -```python out -torch.Size([1, 19]) -torch.Size([1, 19, 9]) -``` - -{:else} - -D'abord, nous devons tokeniser notre entrée et la faire passer dans le modèle. Cela se fait exactement comme dans le [chapitre 2](/course/fr/chapter2). Nous instancions le *tokenizer* et le modèle en utilisant les classes `TFAutoXxx` et les utilisons ensuite dans notre exemple : - -```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) -``` - -Puisque nous utilisons `TFAutoModelForTokenClassification`, nous obtenons un ensemble de logits pour chaque *token* dans la séquence d'entrée : - -```py -print(inputs["input_ids"].shape) -print(outputs.logits.shape) -``` - -```python out -(1, 19) -(1, 19, 9) -``` - -{/if} - -Nous avons un batch avec 1 séquence de 19 *tokens* et le modèle a 9 étiquettes différentes. Ainsi, la sortie du modèle a une forme de 1 x 19 x 9. Comme pour le pipeline de classification de texte, nous utilisons une fonction softmax pour convertir ces logits en probabilités et nous prenons l'argmax pour obtenir des prédictions (notez que nous pouvons prendre l'argmax sur les logits car la fonction softmax ne change pas l'ordre) : - -{#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] -``` - -L'attribut `model.config.id2label` contient la correspondance entre les index et les étiquettes que nous pouvons utiliser pour donner un sens aux prédictions : - -```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'} -``` - -Comme nous l'avons vu précédemment, il y a 9 étiquettes : `O` est le label pour les *tokens* qui ne sont dans aucune entité nommée (il signifie *outside* (en dehors)) et nous avons ensuite deux labels pour chaque type d'entité (divers, personne, organisation et lieu). L'étiquette `B-XXX` indique que le *token* est au début d'une entité `XXX` et l'étiquette `I-XXX` indique que le *token* est à l'intérieur de l'entité `XXX`. Par exemple, dans l'exemple actuel, nous nous attendons à ce que notre modèle classe le *token* `S` comme `B-PER` (début d'une entité personne) et les *tokens* `##yl`, `##va` et `##in` comme `I-PER` (à l'intérieur d'une entité personne). - -Vous pourriez penser que le modèle s'est trompé ici car il a attribué l'étiquette `I-PER` à ces quatre *tokens* mais ce n'est pas tout à fait vrai. Il existe en fait deux formats pour ces étiquettes `B-` et `I-` : *IOB1* et *IOB2*. Le format IOB2 (en rose ci-dessous) est celui que nous avons introduit alors que dans le format IOB1 (en bleu), les étiquettes commençant par `B-` ne sont jamais utilisées que pour séparer deux entités adjacentes du même type. Le modèle que nous utilisons a été *finetuné* sur un jeu de données utilisant ce format, c'est pourquoi il attribue le label `I-PER` au *token* `S`. - -
-IOB1 vs IOB2 format - -
- -Nous sommes à présent prêts à reproduire (presque entièrement) les résultats du premier pipeline. Nous pouvons simplement récupérer le score et le label de chaque *token* qui n'a pas été classé comme `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'}] -``` - -C'est très similaire à ce que nous avions avant, à une exception près : le pipeline nous a aussi donné des informations sur le `début` et la `fin` de chaque entité dans la phrase originale. C'est là que notre *offset mapping* va entrer en jeu. Pour obtenir les *offsets*, il suffit de définir `return_offsets_mapping=True` lorsque nous appliquons le *tokenizer* à nos entrées : - -```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)] -``` - -Chaque *tuple* est l'étendue de texte correspondant à chaque *token* où `(0, 0)` est réservé aux *tokens* spéciaux. Nous avons vu précédemment que le *token* à l'index 5 est `##yl`, qui a `(12, 14)` comme *offsets* ici. Si on prend la tranche correspondante dans notre exemple : - - -```py -example[12:14] -``` - -nous obtenons le bon espace de texte sans le `##` : - -```python out -yl -``` - -En utilisant cela, nous pouvons maintenant compléter les résultats précédents : - -```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}] -``` - -C'est la même chose que ce que nous avons obtenu avec le premier pipeline ! - -### Regroupement des entités - -L'utilisation des *offsets* pour déterminer les clés de début et de fin pour chaque entité est pratique mais cette information n'est pas strictement nécessaire. Cependant, lorsque nous voulons regrouper les entités, les *offsets* nous épargnent un batch de code compliqué. Par exemple, si nous voulions regrouper les *tokens* `Hu`, `##gging`, et `Face`, nous pourrions établir des règles spéciales disant que les deux premiers devraient être attachés tout en enlevant le `##`, et le `Face` devrait être ajouté avec un espace puisqu'il ne commence pas par `##` mais cela ne fonctionnerait que pour ce type particulier de *tokenizer*. Il faudrait écrire un autre ensemble de règles pour un *tokenizer* de type SentencePiece ou *Byte-Pair-Encoding* (voir plus loin dans ce chapitre). - -Avec les *offsets*, tout ce code personnalisé disparaît : il suffit de prendre l'intervalle du texte original qui commence par le premier *token* et se termine par le dernier *token*. Ainsi, dans le cas des *tokens* `Hu`, `##gging`, et `Face`, nous devrions commencer au caractère 33 (le début de `Hu`) et finir avant le caractère 45 (la fin de `Face`) : - -```py -example[33:45] -``` - -```python out -Hugging Face -``` - -Pour écrire le code qui post-traite les prédictions tout en regroupant les entités, nous regrouperons les entités qui sont consécutives et étiquetées avec `I-XXX`, à l'exception de la première, qui peut être étiquetée comme `B-XXX` ou `I-XXX` (ainsi, nous arrêtons de regrouper une entité lorsque nous obtenons un `O`, un nouveau type d'entité, ou un `B-XXX` qui nous indique qu'une entité du même type commence) : - -```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": - # Enlever le B- ou le I- - label = label[2:] - start, _ = offsets[idx] - - # Récupérer tous les tokens étiquetés avec I-label - 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 - - # Le score est la moyenne de tous les scores des tokens dans cette entité groupée - 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) -``` - -Et nous obtenons les mêmes résultats qu'avec notre deuxième 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}] -``` - -Un autre exemple de tâche où ces *offsets* sont extrêmement utiles est la réponse aux questions. Plonger dans ce pipeline, ce que nous ferons dans la section suivante, nous permettra de jeter un coup d'œil à une dernière caractéristique des *tokenizers* de la bibliothèque 🤗 *Transformers* : la gestion des *tokens* qui débordent lorsque nous tronquons une entrée à une longueur donnée. + + +# Pouvoirs spéciaux des tokenizers rapides + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Dans cette section, nous allons examiner de plus près les capacités des *tokenizers* dans 🤗 *Transformers*. +Jusqu'à présent, nous ne les avons utilisés que pour tokeniser les entrées ou décoder les identifiants pour revenir à du texte. Mais les *tokenizers*, et surtout ceux soutenus par la bibliothèque 🤗 *Tokenizers*, peuvent faire beaucoup plus. Pour illustrer ces fonctionnalités supplémentaires, nous allons explorer comment reproduire les résultats des pipelines `token-classification` (que nous avons appelé `ner`) et `question-answering` que nous avons rencontrés pour la première fois dans le [chapitre 1](/course/fr/chapter1). + + + +Dans la discussion qui suit, nous ferons souvent la distinction entre les *tokenizers* « lents » et les « rapides ». Les *tokenizers* lents sont ceux écrits en Python à l'intérieur de la bibliothèque 🤗 *Transformers*, tandis que les rapides sont ceux fournis par 🤗 *Tokenizers* et sont codés en Rust. Si vous vous souvenez du tableau du [chapitre 5](/course/fr/chapter5/3) qui indiquait combien de temps il fallait à un *tokenizer* rapide et à un *tokenizer* lent pour tokeniser le jeu de données *Drug Review*, vous devriez avoir une idée de la raison pour laquelle nous les appelons rapides et lents : + + | *Tokenizer* rapide | *Tokenizer* lent +:--------------:|:--------------:|:-------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s + + + +⚠️ Lors de la tokenisation d'une seule phrase, vous ne verrez pas toujours une différence de vitesse entre les versions lente et rapide d'un même *tokenizer*. En fait, la version rapide peut même être plus lente ! Ce n'est que lorsque vous tokenisez beaucoup de textes en parallèle et en même temps que vous pourrez clairement voir la différence. + + + +## L'objet BatchEncoding + + + +La sortie d'un *tokenizer* n'est pas un simple dictionnaire Python. Ce que nous obtenons est en fait un objet spécial `BatchEncoding`. C'est une sous-classe d'un dictionnaire (c'est pourquoi nous avons pu indexer ce résultat sans problème auparavant), mais avec des méthodes supplémentaires qui sont principalement utilisées par les *tokenizers* rapides. + +En plus de leurs capacités de parallélisation, la fonctionnalité clé des *tokenizers* rapides est qu'ils gardent toujours la trace de l'étendue originale des textes d'où proviennent les *tokens* finaux, une fonctionnalité que nous appelons *mapping offset*. Cela permet de débloquer des fonctionnalités telles que le mappage de chaque mot aux *tokens* qu'il a générés ou le mappage de chaque caractère du texte original au *token* qu'il contient, et vice versa. + +Prenons un exemple : + +```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." +# Je m'appelle Sylvain et je travaille chez Hugging Face à Brooklyn. +encoding = tokenizer(example) +print(type(encoding)) +``` + +Comme mentionné précédemment, nous obtenons un objet `BatchEncoding` dans la sortie du *tokenizer* : + +```python out + +``` + +Puisque la classe `AutoTokenizer` choisit un *tokenizer* rapide par défaut, nous pouvons utiliser les méthodes supplémentaires que cet objet `BatchEncoding` fournit. Nous avons deux façons de vérifier si notre *tokenizer* est rapide ou lent. Nous pouvons soit vérifier l'attribut `is_fast` du *tokenizer* comme suit : + +```python +tokenizer.is_fast +``` + +```python out +True +``` + +soit vérifier le même attribut mais avec notre `encoding` : + +```python +encoding.is_fast +``` + +```python out +True +``` + +Voyons ce qu'un *tokenizer* rapide nous permet de faire. Tout d'abord, nous pouvons accéder aux *tokens* sans avoir à reconvertir les identifiants en *tokens* : + +```py +encoding.tokens() +``` + +```python out +['[CLS]', 'My', 'name', 'is', 'S', '##yl', '##va', '##in', 'and', 'I', 'work', 'at', 'Hu', '##gging', 'Face', 'in', + 'Brooklyn', '.', '[SEP]'] +``` + +Dans ce cas, le *token* à l'index 5 est `##yl` et fait partie du mot « Sylvain » dans la phrase originale. Nous pouvons également utiliser la méthode `word_ids()` pour obtenir l'index du mot dont provient chaque *token* : + +```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] +``` + +On peut voir que les *tokens* spéciaux du *tokenizer*, `[CLS]` et `[SEP]`, sont mis en correspondance avec `None` et que chaque *token* est mis en correspondance avec le mot dont il provient. Ceci est particulièrement utile pour déterminer si un *token* est au début d'un mot ou si deux *tokens* sont dans le même mot. Nous pourrions nous appuyer sur le préfixe `##` pour cela, mais il ne fonctionne que pour les *tokenizers* de type BERT. Cette méthode fonctionne pour n'importe quel type de *tokenizer*, du moment qu'il est rapide. Dans le chapitre suivant, nous verrons comment utiliser cette capacité pour appliquer correctement les étiquettes que nous avons pour chaque mot aux *tokens* dans des tâches comme la reconnaissance d'entités nommées et le POS (*Part-of-speech*). Nous pouvons également l'utiliser pour masquer tous les *tokens* provenant du même mot dans la modélisation du langage masqué (une technique appelée _whole word masking_). + + + +La notion de ce qu'est un mot est compliquée. Par exemple, est-ce que « I'll » (contraction de « I will ») compte pour un ou deux mots ? Cela dépend en fait du *tokenizer* et de l'opération de prétokénisation qu'il applique. Certains *tokenizer* se contentent de séparer les espaces et considèrent donc qu'il s'agit d'un seul mot. D'autres utilisent la ponctuation en plus des espaces et considèrent donc qu'il s'agit de deux mots. + + + +✏️ **Essayez !** Créez un *tokenizer* à partir des checkpoints `bert-base-cased` et `roberta-base` et tokenisez « 81s » avec. Qu'observez-vous ? Quels sont les identifiants des mots ? + + + +De même, il existe une méthode `sentence_ids()` que nous pouvons utiliser pour associer un *token* à la phrase dont il provient (bien que dans ce cas, le `token_type_ids` retourné par le *tokenizer* peut nous donner la même information). + +Enfin, nous pouvons faire correspondre n'importe quel mot ou *token* aux caractères du texte d'origine (et vice versa) grâce aux méthodes `word_to_chars()` ou `token_to_chars()` et `char_to_word()` ou `char_to_token()`. Par exemple, la méthode `word_ids()` nous a dit que `##yl` fait partie du mot à l'indice 3, mais de quel mot s'agit-il dans la phrase ? Nous pouvons le découvrir comme ceci : + +```py +start, end = encoding.word_to_chars(3) +example[start:end] +``` + +```python out +Sylvain +``` + +Comme nous l'avons mentionné précédemment, tout ceci est rendu possible par le fait que le *tokenizer* rapide garde la trace de la partie du texte d'où provient chaque *token* dans une liste d'*offsets*. Pour illustrer leur utilisation, nous allons maintenant vous montrer comment reproduire manuellement les résultats du pipeline `token-classification`. + + + +✏️ **Essayez !** Rédigez votre propre texte et voyez si vous pouvez comprendre quels *tokens* sont associés à l'identifiant du mot et comment extraire les étendues de caractères pour un seul mot. Pour obtenir des points bonus, essayez d'utiliser deux phrases en entrée et voyez si les identifiants ont un sens pour vous. + + + +## A l'intérieur du pipeline `token-classification` + +Dans le [chapitre 1](/course/fr/chapter1), nous avons eu un premier aperçu de la NER (où la tâche est d'identifier les parties du texte qui correspondent à des entités telles que des personnes, des lieux ou des organisations) avec la fonction `pipeline()` de 🤗 *Transformers*. Puis, dans le [chapitre 2](/course/fr/chapter2), nous avons vu comment un pipeline regroupe les trois étapes nécessaires pour obtenir les prédictions à partir d'un texte brut : la tokenisation, le passage des entrées dans le modèle et le post-traitement. Les deux premières étapes du pipeline de `token-classification` sont les mêmes que dans tout autre pipeline mais le post-traitement est un peu plus complexe. Voyons comment ! + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +### Obtenir les résultats de base avec le pipeline + +Tout d'abord, prenons un pipeline de classification de *tokens* afin d'obtenir des résultats à comparer manuellement. Le modèle utilisé par défaut est [`dbmdz/bert-large-cased-finetuned-conll03-english`](https://huggingface.co/dbmdz/bert-large-cased-finetuned-conll03-english). Il effectue une NER sur les phrases : + +```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}] +``` + +Le modèle a correctement identifié chaque *token* généré par « Sylvain » comme une personne, chaque *token* généré par « Hugging Face » comme une organisation, et le *token* « Brooklyn » comme un lieu. Nous pouvons également demander au pipeline de regrouper les *tokens* qui correspondent à la même entité : + +```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 propriété `aggregation_strategy` choisie va changer les scores calculés pour chaque entité groupée. Avec `"simple"` le score est juste la moyenne des scores de chaque *token* dans l'entité donnée. Par exemple, le score de « Sylvain » est la moyenne des scores que nous avons vu dans l'exemple précédent pour les tokens `S`, `##yl`, `##va`, et `##in`. D'autres stratégies sont disponibles : + +- `"first"`, où le score de chaque entité est le score du premier *token* de cette entité (donc pour « Sylvain » ce serait 0.993828, le score du token `S`) +- `"max"`, où le score de chaque entité est le score maximal des *tokens* de cette entité (ainsi, pour « Hugging Face », le score de « Face » serait de 0,98879766). +- `"average"`, où le score de chaque entité est la moyenne des scores des mots qui composent cette entité (ainsi, pour « Sylvain », il n'y aurait pas de différence avec la stratégie `"simple"`, mais "Hugging Face" aurait un score de 0,9819, la moyenne des scores de « Hugging », 0,975, et « Face », 0,98879). + +Voyons maintenant comment obtenir ces résultats sans utiliser la fonction `pipeline()` ! + +### Des entrées aux prédictions + +{#if fw === 'pt'} + +D'abord, nous devons tokeniser notre entrée et la faire passer dans le modèle. Cela se fait exactement comme dans le [chapitre 2](/course/fr/chapter2). Nous instancions le *tokenizer* et le modèle en utilisant les classes `TFAutoXxx` et les utilisons ensuite dans notre exemple : + +```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) +``` + +Puisque nous utilisons `AutoModelForTokenClassification`, nous obtenons un ensemble de logits pour chaque *token* dans la séquence d'entrée : + +```py +print(inputs["input_ids"].shape) +print(outputs.logits.shape) +``` + +```python out +torch.Size([1, 19]) +torch.Size([1, 19, 9]) +``` + +{:else} + +D'abord, nous devons tokeniser notre entrée et la faire passer dans le modèle. Cela se fait exactement comme dans le [chapitre 2](/course/fr/chapter2). Nous instancions le *tokenizer* et le modèle en utilisant les classes `TFAutoXxx` et les utilisons ensuite dans notre exemple : + +```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) +``` + +Puisque nous utilisons `TFAutoModelForTokenClassification`, nous obtenons un ensemble de logits pour chaque *token* dans la séquence d'entrée : + +```py +print(inputs["input_ids"].shape) +print(outputs.logits.shape) +``` + +```python out +(1, 19) +(1, 19, 9) +``` + +{/if} + +Nous avons un batch avec 1 séquence de 19 *tokens* et le modèle a 9 étiquettes différentes. Ainsi, la sortie du modèle a une forme de 1 x 19 x 9. Comme pour le pipeline de classification de texte, nous utilisons une fonction softmax pour convertir ces logits en probabilités et nous prenons l'argmax pour obtenir des prédictions (notez que nous pouvons prendre l'argmax sur les logits car la fonction softmax ne change pas l'ordre) : + +{#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] +``` + +L'attribut `model.config.id2label` contient la correspondance entre les index et les étiquettes que nous pouvons utiliser pour donner un sens aux prédictions : + +```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'} +``` + +Comme nous l'avons vu précédemment, il y a 9 étiquettes : `O` est le label pour les *tokens* qui ne sont dans aucune entité nommée (il signifie *outside* (en dehors)) et nous avons ensuite deux labels pour chaque type d'entité (divers, personne, organisation et lieu). L'étiquette `B-XXX` indique que le *token* est au début d'une entité `XXX` et l'étiquette `I-XXX` indique que le *token* est à l'intérieur de l'entité `XXX`. Par exemple, dans l'exemple actuel, nous nous attendons à ce que notre modèle classe le *token* `S` comme `B-PER` (début d'une entité personne) et les *tokens* `##yl`, `##va` et `##in` comme `I-PER` (à l'intérieur d'une entité personne). + +Vous pourriez penser que le modèle s'est trompé ici car il a attribué l'étiquette `I-PER` à ces quatre *tokens* mais ce n'est pas tout à fait vrai. Il existe en fait deux formats pour ces étiquettes `B-` et `I-` : *IOB1* et *IOB2*. Le format IOB2 (en rose ci-dessous) est celui que nous avons introduit alors que dans le format IOB1 (en bleu), les étiquettes commençant par `B-` ne sont jamais utilisées que pour séparer deux entités adjacentes du même type. Le modèle que nous utilisons a été *finetuné* sur un jeu de données utilisant ce format, c'est pourquoi il attribue le label `I-PER` au *token* `S`. + +
+IOB1 vs IOB2 format + +
+ +Nous sommes à présent prêts à reproduire (presque entièrement) les résultats du premier pipeline. Nous pouvons simplement récupérer le score et le label de chaque *token* qui n'a pas été classé comme `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'}] +``` + +C'est très similaire à ce que nous avions avant, à une exception près : le pipeline nous a aussi donné des informations sur le `début` et la `fin` de chaque entité dans la phrase originale. C'est là que notre *offset mapping* va entrer en jeu. Pour obtenir les *offsets*, il suffit de définir `return_offsets_mapping=True` lorsque nous appliquons le *tokenizer* à nos entrées : + +```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)] +``` + +Chaque *tuple* est l'étendue de texte correspondant à chaque *token* où `(0, 0)` est réservé aux *tokens* spéciaux. Nous avons vu précédemment que le *token* à l'index 5 est `##yl`, qui a `(12, 14)` comme *offsets* ici. Si on prend la tranche correspondante dans notre exemple : + + +```py +example[12:14] +``` + +nous obtenons le bon espace de texte sans le `##` : + +```python out +yl +``` + +En utilisant cela, nous pouvons maintenant compléter les résultats précédents : + +```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}] +``` + +C'est la même chose que ce que nous avons obtenu avec le premier pipeline ! + +### Regroupement des entités + +L'utilisation des *offsets* pour déterminer les clés de début et de fin pour chaque entité est pratique mais cette information n'est pas strictement nécessaire. Cependant, lorsque nous voulons regrouper les entités, les *offsets* nous épargnent un batch de code compliqué. Par exemple, si nous voulions regrouper les *tokens* `Hu`, `##gging`, et `Face`, nous pourrions établir des règles spéciales disant que les deux premiers devraient être attachés tout en enlevant le `##`, et le `Face` devrait être ajouté avec un espace puisqu'il ne commence pas par `##` mais cela ne fonctionnerait que pour ce type particulier de *tokenizer*. Il faudrait écrire un autre ensemble de règles pour un *tokenizer* de type SentencePiece ou *Byte-Pair-Encoding* (voir plus loin dans ce chapitre). + +Avec les *offsets*, tout ce code personnalisé disparaît : il suffit de prendre l'intervalle du texte original qui commence par le premier *token* et se termine par le dernier *token*. Ainsi, dans le cas des *tokens* `Hu`, `##gging`, et `Face`, nous devrions commencer au caractère 33 (le début de `Hu`) et finir avant le caractère 45 (la fin de `Face`) : + +```py +example[33:45] +``` + +```python out +Hugging Face +``` + +Pour écrire le code qui post-traite les prédictions tout en regroupant les entités, nous regrouperons les entités qui sont consécutives et étiquetées avec `I-XXX`, à l'exception de la première, qui peut être étiquetée comme `B-XXX` ou `I-XXX` (ainsi, nous arrêtons de regrouper une entité lorsque nous obtenons un `O`, un nouveau type d'entité, ou un `B-XXX` qui nous indique qu'une entité du même type commence) : + +```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": + # Enlever le B- ou le I- + label = label[2:] + start, _ = offsets[idx] + + # Récupérer tous les tokens étiquetés avec I-label + 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 + + # Le score est la moyenne de tous les scores des tokens dans cette entité groupée + 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) +``` + +Et nous obtenons les mêmes résultats qu'avec notre deuxième 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}] +``` + +Un autre exemple de tâche où ces *offsets* sont extrêmement utiles est la réponse aux questions. Plonger dans ce pipeline, ce que nous ferons dans la section suivante, nous permettra de jeter un coup d'œil à une dernière caractéristique des *tokenizers* de la bibliothèque 🤗 *Transformers* : la gestion des *tokens* qui débordent lorsque nous tronquons une entrée à une longueur donnée. diff --git a/chapters/fr/chapter6/3b.mdx b/chapters/fr/chapter6/3b.mdx index 3e177a9fb..5002f9b4e 100644 --- a/chapters/fr/chapter6/3b.mdx +++ b/chapters/fr/chapter6/3b.mdx @@ -1,720 +1,720 @@ - - -# Tokenizer rapide dans le pipeline de QA - -{#if fw === 'pt'} - - - -{:else} - - - -{/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. - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -## Utilisation du pipeline de `question-answering` - -Comme nous l'avons vu dans le [chapitre 1](/course/fr/chapter1), nous pouvons utiliser le pipeline de `question-answering` comme ceci pour obtenir une réponse à une question : - -```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. -""" -# 🤗 Transformers s'appuie sur les trois bibliothèques d'apprentissage profond les plus populaires -# (Jax, PyTorch et TensorFlow) avec une intégration transparente entre elles. -# C'est simple d'entraîner vos modèles avec l'une avant de les charger pour l'inférence avec l'autre. -question = "Which deep learning libraries back 🤗 Transformers?" -# Quelles bibliothèques d'apprentissage profond derrière 🤗 Transformers ? -question_answerer(question=question, context=context) -``` - -```python out -{'score': 0.97773, - 'start': 78, - 'end': 105, - 'answer': 'Jax, PyTorch and TensorFlow'} -``` - -Contrairement aux autres pipelines, qui ne peuvent pas tronquer et diviser les textes dont la longueur est supérieure à la longueur maximale acceptée par le modèle (et qui peuvent donc manquer des informations à la fin d'un document), ce pipeline peut traiter des contextes très longs et retournera la réponse à la question même si elle se trouve à la fin : - -```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. -""" - -long_context - fr = """ -🤗 Transformers : l'état de l'art du NLP - -🤗 Transformers fournit des milliers de modèles pré-entraînés pour effectuer des tâches sur des textes telles que la classification, -l'extraction d'informations, la réponse à des questions, le résumé de textes, la traduction, la génération de texte et plus encore dans plus de 100 langues. -Son objectif est de rendre le traitement automatique des langues de pointe plus facile à utiliser pour tout le monde. - -🤗 Transformers fournit des API permettant de télécharger et d'utiliser rapidement ces modèles pré-entraînés sur un texte donné, de les affiner sur vos propres ensembles de données et de les partager avec la communauté sur notre site Web. -puis de les partager avec la communauté sur notre hub de modèles. En même temps, chaque module python définissant une architecture est entièrement autonome et peut être modifié pour permettre des expériences de recherche rapides. -peut être modifié pour permettre des expériences de recherche rapides. - -Pourquoi devrais-je utiliser des transformateurs ? - -1. Des modèles de pointe faciles à utiliser : - - Haute performance sur les tâches NLU et NLG. - - Faible barrière à l'entrée pour les éducateurs et les praticiens. - - Peu d'abstractions pour l'utilisateur avec seulement trois classes à apprendre. - - Une API unifiée pour utiliser tous nos modèles pré-entraînés. - - Des coûts de calcul plus faibles, une empreinte carbone réduite : - -2. Les chercheurs peuvent partager les modèles formés au lieu de toujours les reformer. - - Les praticiens peuvent réduire le temps de calcul et les coûts de production. - - Des dizaines d'architectures avec plus de 10 000 modèles pré-formés, certains dans plus de 100 langues. - -3. Choisissez le cadre approprié pour chaque étape de la vie d'un modèle : - - Entraînez des modèles de pointe en 3 lignes de code. - - Déplacez un seul modèle entre les frameworks TF2.0/PyTorch à volonté. - - Choisissez de manière transparente le bon framework pour l'entraînement, l'évaluation et la production. - -4. Adaptez facilement un modèle ou un exemple à vos besoins : - - Nous fournissons des exemples pour chaque architecture afin de reproduire les résultats publiés par ses auteurs originaux. - - Les éléments internes des modèles sont exposés de manière aussi cohérente que possible. - - Les fichiers de modèles peuvent être utilisés indépendamment de la bibliothèque pour des expériences rapides. - -🤗 Transformers s'appuie sur les trois bibliothèques d'apprentissage profond les plus populaires (Jax, PyTorch et TensorFlow) avec une intégration parfaite -entre elles. Il est simple d'entraîner vos modèles avec l'une avant de les charger pour l'inférence avec l'autre. -""" -question_answerer(question=question, context=long_context) -``` - -```python out -{'score': 0.97149, - 'start': 1892, - 'end': 1919, - 'answer': 'Jax, PyTorch and TensorFlow'} -``` - -Voyons comment il fait tout cela ! - -### Utilisation d'un modèle pour répondre à des questions - -Comme avec n'importe quel autre pipeline, nous commençons par tokeniser notre entrée et l'envoyons ensuite à travers le modèle. Le *checkpoint* utilisé par défaut pour le pipeline de `question-answering` est [`distilbert-base-cased-distilled-squad`](https://huggingface.co/distilbert-base-cased-distilled-squad) (le « squad » dans le nom vient du jeu de données sur lequel le modèle a été *finetuné*, nous parlerons davantage de ce jeu de données dans le [chapitre 7](/course/fr/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} - -Notez que nous tokenizons la question et le contexte comme une paire, la question en premier. - -
-An example of tokenization of question and context - -
- -Les modèles de réponse aux questions fonctionnent un peu différemment des modèles que nous avons vus jusqu'à présent. En utilisant l'image ci-dessus comme exemple, le modèle a été entraîné à prédire l'index du *token* de début de la réponse (ici 21) et l'index du *token* où la réponse se termine (ici 24). C'est pourquoi ces modèles ne retournent pas un tenseur de logits mais deux : un pour les logits correspondant au *token* de début de la réponse, et un pour les logits correspondant au *token* de fin de la réponse. Puisque dans ce cas nous n'avons qu'une seule entrée contenant 66 *tokens*, nous obtenons : - -```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} - -Pour convertir ces logits en probabilités, nous allons appliquer une fonction softmax. Mais avant cela, nous devons nous assurer de masquer les indices qui ne font pas partie du contexte. Notre entrée est `[CLS] question [SEP] contexte [SEP]` donc nous devons masquer les *tokens* de la question ainsi que le *token* `[SEP]`. Nous garderons cependant le *token* `[CLS]` car certains modèles l'utilisent pour indiquer que la réponse n'est pas dans le contexte. - -Puisque nous appliquerons une fonction softmax par la suite, il nous suffit de remplacer les logits que nous voulons masquer par un grand nombre négatif. Ici, nous utilisons `-10000` : - -{#if fw === 'pt'} - -```py -import torch - -sequence_ids = inputs.sequence_ids() -# Masque tout, sauf les tokens du contexte -mask = [i != 1 for i in sequence_ids] -# Démasquer le token [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() -# Masque tout, sauf les tokens du contexte -mask = [i != 1 for i in sequence_ids] -# Démasquer le token [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} - -Maintenant que nous avons correctement masqué les logits correspondant aux positions que nous ne voulons pas prédire, nous pouvons appliquer 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} - -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 : - -$$\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`. - -Calculons d'abord tous les produits possibles : - -```py -scores = start_probabilities[:, None] * end_probabilities[None, :] -``` - -{#if fw === 'pt'} - -Ensuite, nous masquerons les valeurs où `start_index > end_index` en les mettant à `0` (les autres probabilités sont toutes des nombres positifs). La fonction `torch.triu()` renvoie la partie triangulaire supérieure du tenseur 2D passé en argument, elle fera donc ce masquage pour nous : - -```py -scores = torch.triu(scores) -``` - -{:else} - -Ensuite, nous masquerons les valeurs où `start_index > end_index` en les mettant à `0` (les autres probabilités sont toutes des nombres positifs). La fonction `np.triu()` renvoie la partie triangulaire supérieure du tenseur 2D passé en argument, elle fera donc ce masquage pour nous : - -```py -scores = np.triu(scores) -``` - -{/if} - -Il ne nous reste plus qu'à obtenir l'indice du maximum. Puisque PyTorch retourne l'index dans le tenseur aplati, nous devons utiliser les opérations division `//` et modulo `%` pour obtenir le `start_index` et le `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]) -``` - -Nous n'avons pas encore tout à fait terminé, mais au moins nous avons déjà le score correct pour la réponse (vous pouvez le vérifier en le comparant au premier résultat de la section précédente) : - -```python out -0.97773 -``` - - - -✏️ **Essayez !** Calculez les indices de début et de fin pour les cinq réponses les plus probables. - - - -Nous avons les `start_index` et `end_index` de la réponse en termes de *tokens*. Maintenant nous devons juste convertir en indices de caractères dans le contexte. C'est là que les *offsets* seront super utiles. Nous pouvons les saisir et les utiliser comme nous l'avons fait dans la tâche de classification des *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] -``` - -Il ne nous reste plus qu'à tout formater pour obtenir notre résultat : - -```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} -``` - -Super ! C'est la même chose que dans notre premier exemple ! - - - -✏️ **Essayez !** Utilisez les meilleurs scores que vous avez calculés précédemment pour afficher les cinq réponses les plus probables. Pour vérifier vos résultats, retournez au premier pipeline et passez dans `top_k=5` lorsque vous l'appelez. - - - -## Gestion des contextes longs - -Si nous essayons de tokeniser la question et le long contexte que nous avons utilisé dans l'exemple précédemment, nous obtenons un nombre de *tokens* supérieur à la longueur maximale utilisée dans le pipeline `question-answering` (qui est de 384) : - -```py -inputs = tokenizer(question, long_context) -print(len(inputs["input_ids"])) -``` - -```python out -461 -``` - -Nous devrons donc tronquer nos entrées à cette longueur maximale. Il y a plusieurs façons de le faire mais nous ne voulons pas tronquer la question, seulement le contexte. Puisque le contexte est la deuxième phrase, nous utilisons la stratégie de troncature `"only_second"`. Le problème qui se pose alors est que la réponse à la question peut ne pas se trouver dans le contexte tronqué. Ici, par exemple, nous avons choisi une question dont la réponse se trouve vers la fin du contexte, et lorsque nous la tronquons, cette réponse n'est pas présente : - -```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] -""" - -""" -[CLS] Quelles sont les bibliothèques d'apprentissage profond qui soutiennent [UNK] Transformers ? [SEP] [UNK] Transformers : l'état de l'art du NLP - -[UNK] Transformers fournit des milliers de modèles pré-entraînés pour effectuer des tâches sur des textes telles que la classification, l'extraction d'informations, la réponse à des questions, le résumé, la traduction, la génération de textes, etc, -la réponse à des questions, le résumé, la traduction, la génération de texte et plus encore dans plus de 100 langues. -Son objectif est de rendre le traitement automatique des langues de pointe plus facile à utiliser pour tous. - -Transformers [UNK] fournit des API permettant de télécharger et d'utiliser rapidement ces modèles pré-entraînés sur un texte donné, de les affiner sur vos propres ensembles de données et de les partager avec la communauté sur notre site Web. -puis de les partager avec la communauté sur notre hub de modèles. En même temps, chaque module python définissant une architecture est entièrement autonome et peut être modifié pour permettre des expériences de recherche rapides. -peut être modifié pour permettre des expériences de recherche rapides. - -Pourquoi devrais-je utiliser des transformateurs ? - -1. Des modèles de pointe faciles à utiliser : - - Haute performance sur les tâches NLU et NLG. - - Faible barrière à l'entrée pour les éducateurs et les praticiens. - - Peu d'abstractions pour l'utilisateur avec seulement trois classes à apprendre. - - Une API unifiée pour utiliser tous nos modèles pré-entraînés. - - Des coûts de calcul plus faibles, une empreinte carbone réduite : - -2. Les chercheurs peuvent partager les modèles formés au lieu de toujours les reformer. - - Les praticiens peuvent réduire le temps de calcul et les coûts de production. - - Des dizaines d'architectures avec plus de 10 000 modèles pré-formés, certains dans plus de 100 langues. - -3. Choisissez le cadre approprié pour chaque étape de la vie d'un modèle : - - Entraînez des modèles de pointe en 3 lignes de code. - - Déplacez un seul modèle entre les frameworks TF2.0/PyTorch à volonté. - - Choisissez de manière transparente le bon framework pour l'entraînement, l'évaluation et la production. - -4. Adaptez facilement un modèle ou un exemple à vos besoins : - - Nous fournissons des exemples pour chaque architecture afin de reproduire les résultats publiés par ses auteurs originaux. - - Modèle interne [SEP] -""" -``` - -Cela signifie que le modèle a du mal à trouver la bonne réponse. Pour résoudre ce problème, le pipeline de `question-answering` nous permet de diviser le contexte en morceaux plus petits, en spécifiant la longueur maximale. Pour s'assurer que nous ne divisons pas le contexte exactement au mauvais endroit pour permettre de trouver la réponse, il inclut également un certain chevauchement entre les morceaux. - -Nous pouvons demander au *tokenizer* (rapide ou lent) de le faire pour nous en ajoutant `return_overflowing_tokens=True`, et nous pouvons spécifier le chevauchement que nous voulons avec l'argument `stride`. Voici un exemple, en utilisant une phrase plus petite : - -```py -sentence = "This sentence is not too long but we are going to split it anyway." -# "Cette phrase n'est pas trop longue mais nous allons la diviser quand même." -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]' -``` - -Comme on peut le voir, la phrase a été découpée en morceaux de telle sorte que chaque entrée dans `inputs["input_ids"]` a au maximum 6 *tokens* (il faudrait ajouter du *padding* pour que la dernière entrée ait la même taille que les autres) et il y a un chevauchement de 2 *tokens* entre chacune des entrées. - -Regardons de plus près le résultat de la tokénisation : - -```py -print(inputs.keys()) -``` - -```python out -dict_keys(['input_ids', 'attention_mask', 'overflow_to_sample_mapping']) -``` - -Comme prévu, nous obtenons les identifiants d'entrée et un masque d'attention. La dernière clé, `overflow_to_sample_mapping`, est une carte qui nous indique à quelle phrase correspond chacun des résultats. Ici nous avons 7 résultats qui proviennent tous de la (seule) phrase que nous avons passée au *tokenizer* : - -```py -print(inputs["overflow_to_sample_mapping"]) -``` - -```python out -[0, 0, 0, 0, 0, 0, 0] -``` - -C'est plus utile lorsque nous tokenisons plusieurs phrases ensemble. Par exemple : - -```py -sentences = [ - "This sentence is not too long but we are going to split it anyway.", - # Cette phrase n'est pas trop longue mais nous allons la diviser quand même. - "This sentence is shorter but will still get split.", - # Cette phrase est plus courte mais sera quand même divisée. -] -inputs = tokenizer( - sentences, truncation=True, return_overflowing_tokens=True, max_length=6, stride=2 -) - -print(inputs["overflow_to_sample_mapping"]) -``` - -nous donne : - -```python out -[0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1] -``` - -ce qui signifie que la première phrase est divisée en 7 morceaux comme précédemment et que les 4 morceaux suivants proviennent de la deuxième phrase. - -Revenons maintenant à notre contexte long. Par défaut, le pipeline `question-answering` utilise une longueur maximale de 384 et un *stride* de 128, qui correspondent à la façon dont le modèle a été *finetuné* (vous pouvez ajuster ces paramètres en passant les arguments `max_seq_len` et `stride` lorsque vous appelez le pipeline). Nous utiliserons donc ces paramètres lors de la tokenisation. Nous ajouterons aussi du *padding* (pour avoir des échantillons de même longueur afin de pouvoir construire des tenseurs) ainsi que demander les *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, -) -``` - -Ces `inputs` contiendront les identifiants d'entrée, les masques d'attention que le modèle attend, ainsi que les *offsets* et le `overflow_to_sample_mapping` dont on vient de parler. Puisque ces deux éléments ne sont pas des paramètres utilisés par le modèle, nous allons les sortir des `inputs` (et nous ne stockerons pas la correspondance puisqu'elle n'est pas utile ici) avant de le convertir en tenseur : - -{#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} - -Notre contexte long a été divisé en deux, ce qui signifie qu'après avoir traversé notre modèle, nous aurons deux ensembles de logits de début et de fin : - -```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} - -Comme précédemment, nous masquons d'abord les *tokens* qui ne font pas partie du contexte avant de prendre le softmax. Nous masquons également tous les *tokens* de *padding* (tels que signalés par le masque d'attention) : - -{#if fw === 'pt'} - -```py -sequence_ids = inputs.sequence_ids() -# Masque tout, sauf les tokens du contexte -mask = [i != 1 for i in sequence_ids] -# Démasquer le jeton [CLS] -mask[0] = False -# Masquer tous les tokens [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() -# Masque tout, sauf les tokens du contexte -mask = [i != 1 for i in sequence_ids] -# Démasquer le jeton [CLS] -mask[0] = False -# Masquer tous les tokens [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} - -Ensuite, nous pouvons utiliser la fonction softmax pour convertir nos logits en probabilités : - -{#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} - -L'étape suivante est similaire à ce que nous avons fait pour le petit contexte mais nous la répétons pour chacun de nos deux morceaux. Nous attribuons un score à tous les espaces de réponse possibles puis nous prenons l'espace ayant le meilleur score : - -{#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[0] - end_idx = idx % scores.shape[0] - 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[0] - end_idx = idx % scores.shape[0] - 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)] -``` - -Ces deux candidats correspondent aux meilleures réponses que le modèle a pu trouver dans chaque morceau. Le modèle est beaucoup plus confiant dans le fait que la bonne réponse se trouve dans la deuxième partie (ce qui est bon signe !). Il ne nous reste plus qu'à faire correspondre ces deux espaces de *tokens* à des espaces de caractères dans le contexte (nous n'avons besoin de faire correspondre que le second pour avoir notre réponse, mais il est intéressant de voir ce que le modèle a choisi dans le premier morceau). - - - -✏️ **Essayez !** Adaptez le code ci-dessus pour renvoyer les scores et les étendues des cinq réponses les plus probables (au total, pas par morceau). - - - -Le `offsets` que nous avons saisi plus tôt est en fait une liste d'*offsets* avec une liste par morceau de texte : - -```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 nous ignorons le premier résultat, nous obtenons le même résultat que notre pipeline pour ce long contexte ! - - - -✏️ **Essayez !** Utilisez les meilleurs scores que vous avez calculés auparavant pour montrer les cinq réponses les plus probables (pour l'ensemble du contexte, pas pour chaque morceau). Pour vérifier vos résultats, retournez au premier pipeline et spécifiez `top_k=5` en argument en l'appelant. - - - + + +# Tokenizer rapide dans le pipeline de QA + +{#if fw === 'pt'} + + + +{:else} + + + +{/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. + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +## Utilisation du pipeline de `question-answering` + +Comme nous l'avons vu dans le [chapitre 1](/course/fr/chapter1), nous pouvons utiliser le pipeline de `question-answering` comme ceci pour obtenir une réponse à une question : + +```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. +""" +# 🤗 Transformers s'appuie sur les trois bibliothèques d'apprentissage profond les plus populaires +# (Jax, PyTorch et TensorFlow) avec une intégration transparente entre elles. +# C'est simple d'entraîner vos modèles avec l'une avant de les charger pour l'inférence avec l'autre. +question = "Which deep learning libraries back 🤗 Transformers?" +# Quelles bibliothèques d'apprentissage profond derrière 🤗 Transformers ? +question_answerer(question=question, context=context) +``` + +```python out +{'score': 0.97773, + 'start': 78, + 'end': 105, + 'answer': 'Jax, PyTorch and TensorFlow'} +``` + +Contrairement aux autres pipelines, qui ne peuvent pas tronquer et diviser les textes dont la longueur est supérieure à la longueur maximale acceptée par le modèle (et qui peuvent donc manquer des informations à la fin d'un document), ce pipeline peut traiter des contextes très longs et retournera la réponse à la question même si elle se trouve à la fin : + +```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. +""" + +long_context - fr = """ +🤗 Transformers : l'état de l'art du NLP + +🤗 Transformers fournit des milliers de modèles pré-entraînés pour effectuer des tâches sur des textes telles que la classification, +l'extraction d'informations, la réponse à des questions, le résumé de textes, la traduction, la génération de texte et plus encore dans plus de 100 langues. +Son objectif est de rendre le traitement automatique des langues de pointe plus facile à utiliser pour tout le monde. + +🤗 Transformers fournit des API permettant de télécharger et d'utiliser rapidement ces modèles pré-entraînés sur un texte donné, de les affiner sur vos propres ensembles de données et de les partager avec la communauté sur notre site Web. +puis de les partager avec la communauté sur notre hub de modèles. En même temps, chaque module python définissant une architecture est entièrement autonome et peut être modifié pour permettre des expériences de recherche rapides. +peut être modifié pour permettre des expériences de recherche rapides. + +Pourquoi devrais-je utiliser des transformateurs ? + +1. Des modèles de pointe faciles à utiliser : + - Haute performance sur les tâches NLU et NLG. + - Faible barrière à l'entrée pour les éducateurs et les praticiens. + - Peu d'abstractions pour l'utilisateur avec seulement trois classes à apprendre. + - Une API unifiée pour utiliser tous nos modèles pré-entraînés. + - Des coûts de calcul plus faibles, une empreinte carbone réduite : + +2. Les chercheurs peuvent partager les modèles formés au lieu de toujours les reformer. + - Les praticiens peuvent réduire le temps de calcul et les coûts de production. + - Des dizaines d'architectures avec plus de 10 000 modèles pré-formés, certains dans plus de 100 langues. + +3. Choisissez le cadre approprié pour chaque étape de la vie d'un modèle : + - Entraînez des modèles de pointe en 3 lignes de code. + - Déplacez un seul modèle entre les frameworks TF2.0/PyTorch à volonté. + - Choisissez de manière transparente le bon framework pour l'entraînement, l'évaluation et la production. + +4. Adaptez facilement un modèle ou un exemple à vos besoins : + - Nous fournissons des exemples pour chaque architecture afin de reproduire les résultats publiés par ses auteurs originaux. + - Les éléments internes des modèles sont exposés de manière aussi cohérente que possible. + - Les fichiers de modèles peuvent être utilisés indépendamment de la bibliothèque pour des expériences rapides. + +🤗 Transformers s'appuie sur les trois bibliothèques d'apprentissage profond les plus populaires (Jax, PyTorch et TensorFlow) avec une intégration parfaite +entre elles. Il est simple d'entraîner vos modèles avec l'une avant de les charger pour l'inférence avec l'autre. +""" +question_answerer(question=question, context=long_context) +``` + +```python out +{'score': 0.97149, + 'start': 1892, + 'end': 1919, + 'answer': 'Jax, PyTorch and TensorFlow'} +``` + +Voyons comment il fait tout cela ! + +### Utilisation d'un modèle pour répondre à des questions + +Comme avec n'importe quel autre pipeline, nous commençons par tokeniser notre entrée et l'envoyons ensuite à travers le modèle. Le *checkpoint* utilisé par défaut pour le pipeline de `question-answering` est [`distilbert-base-cased-distilled-squad`](https://huggingface.co/distilbert-base-cased-distilled-squad) (le « squad » dans le nom vient du jeu de données sur lequel le modèle a été *finetuné*, nous parlerons davantage de ce jeu de données dans le [chapitre 7](/course/fr/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} + +Notez que nous tokenizons la question et le contexte comme une paire, la question en premier. + +
+An example of tokenization of question and context + +
+ +Les modèles de réponse aux questions fonctionnent un peu différemment des modèles que nous avons vus jusqu'à présent. En utilisant l'image ci-dessus comme exemple, le modèle a été entraîné à prédire l'index du *token* de début de la réponse (ici 21) et l'index du *token* où la réponse se termine (ici 24). C'est pourquoi ces modèles ne retournent pas un tenseur de logits mais deux : un pour les logits correspondant au *token* de début de la réponse, et un pour les logits correspondant au *token* de fin de la réponse. Puisque dans ce cas nous n'avons qu'une seule entrée contenant 66 *tokens*, nous obtenons : + +```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} + +Pour convertir ces logits en probabilités, nous allons appliquer une fonction softmax. Mais avant cela, nous devons nous assurer de masquer les indices qui ne font pas partie du contexte. Notre entrée est `[CLS] question [SEP] contexte [SEP]` donc nous devons masquer les *tokens* de la question ainsi que le *token* `[SEP]`. Nous garderons cependant le *token* `[CLS]` car certains modèles l'utilisent pour indiquer que la réponse n'est pas dans le contexte. + +Puisque nous appliquerons une fonction softmax par la suite, il nous suffit de remplacer les logits que nous voulons masquer par un grand nombre négatif. Ici, nous utilisons `-10000` : + +{#if fw === 'pt'} + +```py +import torch + +sequence_ids = inputs.sequence_ids() +# Masque tout, sauf les tokens du contexte +mask = [i != 1 for i in sequence_ids] +# Démasquer le token [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() +# Masque tout, sauf les tokens du contexte +mask = [i != 1 for i in sequence_ids] +# Démasquer le token [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} + +Maintenant que nous avons correctement masqué les logits correspondant aux positions que nous ne voulons pas prédire, nous pouvons appliquer 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} + +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 : + +$$\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`. + +Calculons d'abord tous les produits possibles : + +```py +scores = start_probabilities[:, None] * end_probabilities[None, :] +``` + +{#if fw === 'pt'} + +Ensuite, nous masquerons les valeurs où `start_index > end_index` en les mettant à `0` (les autres probabilités sont toutes des nombres positifs). La fonction `torch.triu()` renvoie la partie triangulaire supérieure du tenseur 2D passé en argument, elle fera donc ce masquage pour nous : + +```py +scores = torch.triu(scores) +``` + +{:else} + +Ensuite, nous masquerons les valeurs où `start_index > end_index` en les mettant à `0` (les autres probabilités sont toutes des nombres positifs). La fonction `np.triu()` renvoie la partie triangulaire supérieure du tenseur 2D passé en argument, elle fera donc ce masquage pour nous : + +```py +scores = np.triu(scores) +``` + +{/if} + +Il ne nous reste plus qu'à obtenir l'indice du maximum. Puisque PyTorch retourne l'index dans le tenseur aplati, nous devons utiliser les opérations division `//` et modulo `%` pour obtenir le `start_index` et le `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]) +``` + +Nous n'avons pas encore tout à fait terminé, mais au moins nous avons déjà le score correct pour la réponse (vous pouvez le vérifier en le comparant au premier résultat de la section précédente) : + +```python out +0.97773 +``` + + + +✏️ **Essayez !** Calculez les indices de début et de fin pour les cinq réponses les plus probables. + + + +Nous avons les `start_index` et `end_index` de la réponse en termes de *tokens*. Maintenant nous devons juste convertir en indices de caractères dans le contexte. C'est là que les *offsets* seront super utiles. Nous pouvons les saisir et les utiliser comme nous l'avons fait dans la tâche de classification des *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] +``` + +Il ne nous reste plus qu'à tout formater pour obtenir notre résultat : + +```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} +``` + +Super ! C'est la même chose que dans notre premier exemple ! + + + +✏️ **Essayez !** Utilisez les meilleurs scores que vous avez calculés précédemment pour afficher les cinq réponses les plus probables. Pour vérifier vos résultats, retournez au premier pipeline et passez dans `top_k=5` lorsque vous l'appelez. + + + +## Gestion des contextes longs + +Si nous essayons de tokeniser la question et le long contexte que nous avons utilisé dans l'exemple précédemment, nous obtenons un nombre de *tokens* supérieur à la longueur maximale utilisée dans le pipeline `question-answering` (qui est de 384) : + +```py +inputs = tokenizer(question, long_context) +print(len(inputs["input_ids"])) +``` + +```python out +461 +``` + +Nous devrons donc tronquer nos entrées à cette longueur maximale. Il y a plusieurs façons de le faire mais nous ne voulons pas tronquer la question, seulement le contexte. Puisque le contexte est la deuxième phrase, nous utilisons la stratégie de troncature `"only_second"`. Le problème qui se pose alors est que la réponse à la question peut ne pas se trouver dans le contexte tronqué. Ici, par exemple, nous avons choisi une question dont la réponse se trouve vers la fin du contexte, et lorsque nous la tronquons, cette réponse n'est pas présente : + +```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] +""" + +""" +[CLS] Quelles sont les bibliothèques d'apprentissage profond qui soutiennent [UNK] Transformers ? [SEP] [UNK] Transformers : l'état de l'art du NLP + +[UNK] Transformers fournit des milliers de modèles pré-entraînés pour effectuer des tâches sur des textes telles que la classification, l'extraction d'informations, la réponse à des questions, le résumé, la traduction, la génération de textes, etc, +la réponse à des questions, le résumé, la traduction, la génération de texte et plus encore dans plus de 100 langues. +Son objectif est de rendre le traitement automatique des langues de pointe plus facile à utiliser pour tous. + +Transformers [UNK] fournit des API permettant de télécharger et d'utiliser rapidement ces modèles pré-entraînés sur un texte donné, de les affiner sur vos propres ensembles de données et de les partager avec la communauté sur notre site Web. +puis de les partager avec la communauté sur notre hub de modèles. En même temps, chaque module python définissant une architecture est entièrement autonome et peut être modifié pour permettre des expériences de recherche rapides. +peut être modifié pour permettre des expériences de recherche rapides. + +Pourquoi devrais-je utiliser des transformateurs ? + +1. Des modèles de pointe faciles à utiliser : + - Haute performance sur les tâches NLU et NLG. + - Faible barrière à l'entrée pour les éducateurs et les praticiens. + - Peu d'abstractions pour l'utilisateur avec seulement trois classes à apprendre. + - Une API unifiée pour utiliser tous nos modèles pré-entraînés. + - Des coûts de calcul plus faibles, une empreinte carbone réduite : + +2. Les chercheurs peuvent partager les modèles formés au lieu de toujours les reformer. + - Les praticiens peuvent réduire le temps de calcul et les coûts de production. + - Des dizaines d'architectures avec plus de 10 000 modèles pré-formés, certains dans plus de 100 langues. + +3. Choisissez le cadre approprié pour chaque étape de la vie d'un modèle : + - Entraînez des modèles de pointe en 3 lignes de code. + - Déplacez un seul modèle entre les frameworks TF2.0/PyTorch à volonté. + - Choisissez de manière transparente le bon framework pour l'entraînement, l'évaluation et la production. + +4. Adaptez facilement un modèle ou un exemple à vos besoins : + - Nous fournissons des exemples pour chaque architecture afin de reproduire les résultats publiés par ses auteurs originaux. + - Modèle interne [SEP] +""" +``` + +Cela signifie que le modèle a du mal à trouver la bonne réponse. Pour résoudre ce problème, le pipeline de `question-answering` nous permet de diviser le contexte en morceaux plus petits, en spécifiant la longueur maximale. Pour s'assurer que nous ne divisons pas le contexte exactement au mauvais endroit pour permettre de trouver la réponse, il inclut également un certain chevauchement entre les morceaux. + +Nous pouvons demander au *tokenizer* (rapide ou lent) de le faire pour nous en ajoutant `return_overflowing_tokens=True`, et nous pouvons spécifier le chevauchement que nous voulons avec l'argument `stride`. Voici un exemple, en utilisant une phrase plus petite : + +```py +sentence = "This sentence is not too long but we are going to split it anyway." +# "Cette phrase n'est pas trop longue mais nous allons la diviser quand même." +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]' +``` + +Comme on peut le voir, la phrase a été découpée en morceaux de telle sorte que chaque entrée dans `inputs["input_ids"]` a au maximum 6 *tokens* (il faudrait ajouter du *padding* pour que la dernière entrée ait la même taille que les autres) et il y a un chevauchement de 2 *tokens* entre chacune des entrées. + +Regardons de plus près le résultat de la tokénisation : + +```py +print(inputs.keys()) +``` + +```python out +dict_keys(['input_ids', 'attention_mask', 'overflow_to_sample_mapping']) +``` + +Comme prévu, nous obtenons les identifiants d'entrée et un masque d'attention. La dernière clé, `overflow_to_sample_mapping`, est une carte qui nous indique à quelle phrase correspond chacun des résultats. Ici nous avons 7 résultats qui proviennent tous de la (seule) phrase que nous avons passée au *tokenizer* : + +```py +print(inputs["overflow_to_sample_mapping"]) +``` + +```python out +[0, 0, 0, 0, 0, 0, 0] +``` + +C'est plus utile lorsque nous tokenisons plusieurs phrases ensemble. Par exemple : + +```py +sentences = [ + "This sentence is not too long but we are going to split it anyway.", + # Cette phrase n'est pas trop longue mais nous allons la diviser quand même. + "This sentence is shorter but will still get split.", + # Cette phrase est plus courte mais sera quand même divisée. +] +inputs = tokenizer( + sentences, truncation=True, return_overflowing_tokens=True, max_length=6, stride=2 +) + +print(inputs["overflow_to_sample_mapping"]) +``` + +nous donne : + +```python out +[0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1] +``` + +ce qui signifie que la première phrase est divisée en 7 morceaux comme précédemment et que les 4 morceaux suivants proviennent de la deuxième phrase. + +Revenons maintenant à notre contexte long. Par défaut, le pipeline `question-answering` utilise une longueur maximale de 384 et un *stride* de 128, qui correspondent à la façon dont le modèle a été *finetuné* (vous pouvez ajuster ces paramètres en passant les arguments `max_seq_len` et `stride` lorsque vous appelez le pipeline). Nous utiliserons donc ces paramètres lors de la tokenisation. Nous ajouterons aussi du *padding* (pour avoir des échantillons de même longueur afin de pouvoir construire des tenseurs) ainsi que demander les *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, +) +``` + +Ces `inputs` contiendront les identifiants d'entrée, les masques d'attention que le modèle attend, ainsi que les *offsets* et le `overflow_to_sample_mapping` dont on vient de parler. Puisque ces deux éléments ne sont pas des paramètres utilisés par le modèle, nous allons les sortir des `inputs` (et nous ne stockerons pas la correspondance puisqu'elle n'est pas utile ici) avant de le convertir en tenseur : + +{#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} + +Notre contexte long a été divisé en deux, ce qui signifie qu'après avoir traversé notre modèle, nous aurons deux ensembles de logits de début et de fin : + +```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} + +Comme précédemment, nous masquons d'abord les *tokens* qui ne font pas partie du contexte avant de prendre le softmax. Nous masquons également tous les *tokens* de *padding* (tels que signalés par le masque d'attention) : + +{#if fw === 'pt'} + +```py +sequence_ids = inputs.sequence_ids() +# Masque tout, sauf les tokens du contexte +mask = [i != 1 for i in sequence_ids] +# Démasquer le jeton [CLS] +mask[0] = False +# Masquer tous les tokens [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() +# Masque tout, sauf les tokens du contexte +mask = [i != 1 for i in sequence_ids] +# Démasquer le jeton [CLS] +mask[0] = False +# Masquer tous les tokens [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} + +Ensuite, nous pouvons utiliser la fonction softmax pour convertir nos logits en probabilités : + +{#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} + +L'étape suivante est similaire à ce que nous avons fait pour le petit contexte mais nous la répétons pour chacun de nos deux morceaux. Nous attribuons un score à tous les espaces de réponse possibles puis nous prenons l'espace ayant le meilleur score : + +{#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[0] + end_idx = idx % scores.shape[0] + 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[0] + end_idx = idx % scores.shape[0] + 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)] +``` + +Ces deux candidats correspondent aux meilleures réponses que le modèle a pu trouver dans chaque morceau. Le modèle est beaucoup plus confiant dans le fait que la bonne réponse se trouve dans la deuxième partie (ce qui est bon signe !). Il ne nous reste plus qu'à faire correspondre ces deux espaces de *tokens* à des espaces de caractères dans le contexte (nous n'avons besoin de faire correspondre que le second pour avoir notre réponse, mais il est intéressant de voir ce que le modèle a choisi dans le premier morceau). + + + +✏️ **Essayez !** Adaptez le code ci-dessus pour renvoyer les scores et les étendues des cinq réponses les plus probables (au total, pas par morceau). + + + +Le `offsets` que nous avons saisi plus tôt est en fait une liste d'*offsets* avec une liste par morceau de texte : + +```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 nous ignorons le premier résultat, nous obtenons le même résultat que notre pipeline pour ce long contexte ! + + + +✏️ **Essayez !** Utilisez les meilleurs scores que vous avez calculés auparavant pour montrer les cinq réponses les plus probables (pour l'ensemble du contexte, pas pour chaque morceau). Pour vérifier vos résultats, retournez au premier pipeline et spécifiez `top_k=5` en argument en l'appelant. + + + Ceci conclut notre plongée en profondeur dans les capacités du *tokenizer*. Nous mettrons à nouveau tout cela en pratique dans le prochain chapitre, lorsque nous vous montrerons comment *finetuner* un modèle sur une série de tâches NLP courantes. \ No newline at end of file diff --git a/chapters/fr/chapter6/4.mdx b/chapters/fr/chapter6/4.mdx index 0d8ddd4ba..1438e7b6f 100644 --- a/chapters/fr/chapter6/4.mdx +++ b/chapters/fr/chapter6/4.mdx @@ -1,123 +1,123 @@ -# Normalisation et prétokenization - - - -Avant de nous plonger plus profondément dans les trois algorithmes de tokénisation en sous-mots les plus courants utilisés avec les *transformers* (*Byte-Pair Encoding* (BPE), *WordPiece* et *Unigram*), nous allons d'abord examiner le prétraitement que chaque *tokenizer* applique au texte. Voici un aperçu de haut niveau des étapes du pipeline de tokenisation : - -
-The tokenization pipeline. - -
- -Avant de diviser un texte en sous-*tokens* (selon le modèle), le *tokenizer* effectue deux étapes : la _normalisation_ et la _prétokénisation_. - -## Normalisation - - - -L'étape de normalisation implique un nettoyage général, comme la suppression des espaces inutiles, la mise en minuscules et/ou la suppression des accents. Si vous êtes familier avec la [normalisation Unicode](http://www.unicode.org/reports/tr15/) (comme NFC ou NFKC), c'est aussi quelque chose que le *tokenizer* peut appliquer. - -Le `tokenizer` de 🤗 *Transformers* possède un attribut appelé `backend_tokenizer` qui donne accès au *tokenizer* sous-jacent de la bibliothèque 🤗 *Tokenizers* : - -```py -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased") -print(type(tokenizer.backend_tokenizer)) -``` - -```python out - -``` - -L'attribut `normalizer` de l'objet `tokenizer` possède une méthode `normalize_str()` que nous pouvons utiliser pour voir comment la normalisation est effectuée : - -```py -print(tokenizer.backend_tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) -``` - -```python out -'hello how are u?' -``` - -Dans cet exemple, puisque nous avons choisi le *checkpoint* `bert-base-uncased`, la normalisation a mis le texte en minuscule et supprimé les accents. - - - -✏️ **Essayez !** Chargez un *tokenizer* depuis le *checkpoint* `bert-base-cased` et passez-lui le même exemple. Quelles sont les principales différences que vous pouvez voir entre les versions casée et non casée du *tokenizer* ? - - - -## Prétokenization - - - -Comme nous le verrons dans les sections suivantes, un *tokenizer* ne peut pas être entraîné uniquement sur du texte brut. Au lieu de cela, nous devons d'abord diviser les textes en petites entités, comme des mots. C'est là qu'intervient l'étape de prétokénisation. Comme nous l'avons vu dans le [chapitre 2](/course/fr/chapter2), un *tokenizer* basé sur les mots peut simplement diviser un texte brut en mots sur les espaces et la ponctuation. Ces mots constitueront les limites des sous-*tokens* que le *tokenizer* peut apprendre pendant son entraînement. - -Pour voir comment un *tokenizer* rapide effectue la prétokénisation, nous pouvons utiliser la méthode `pre_tokenize_str()`de l'attribut `pre_tokenizer` de l'objet `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))] -``` - -Remarquez que le *tokenizer* garde déjà la trace des *offsets*, ce qui lui permet de nous donner la correspondance des décalages que nous avons utilisée dans la section précédente. Ici, le *tokenizer* ignore les deux espaces et les remplace par un seul, mais le décalage saute entre `are` et `you` pour en tenir compte. - -Puisque nous utilisons le *tokenizer* de BERT, la prétokénisation implique la séparation des espaces et de la ponctuation. D'autres *tokenizers* peuvent avoir des règles différentes pour cette étape. Par exemple, si nous utilisons le *tokenizer* du GPT-2 : - -```py -tokenizer = AutoTokenizer.from_pretrained("gpt2") -tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") -``` - -Il séparera aussi sur les espaces et la ponctuation mais gardera les espaces et les remplacera par un symbole `Ġ`, ce qui lui permettra de récupérer les espaces originaux si nous décodons les *tokens* : - -```python out -[('Hello', (0, 5)), (',', (5, 6)), ('Ġhow', (6, 10)), ('Ġare', (10, 14)), ('Ġ', (14, 15)), ('Ġyou', (15, 19)), - ('?', (19, 20))] -``` - -Notez également que, contrairement au *tokenizer* de BERT, ce *tokenizer* n'ignore pas les doubles espaces. - -Pour un dernier exemple, regardons le *tokenizer* du 5, qui est basé sur l'algorithme 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))] -``` - -Comme le *tokenizer* du GPT-2, celui-ci garde les espaces et les remplace par un *token* spécifique (`_`), mais le *tokenizer* du T5 ne sépare que sur les espaces, pas sur la ponctuation. Notez également qu'il a ajouté un espace par défaut au début de la phrase (avant `Hello`) et il ignore le double espace entre `are` et `you`. - -Maintenant que nous avons vu un peu comment les différents *tokenizers* traitent le texte, nous pouvons commencer à explorer les algorithmes sous-jacents eux-mêmes. Nous commencerons par jeter un coup d'oeil rapide sur le très répandu SentencePiece, puis au cours des trois sections suivantes nous examinerons le fonctionnement des trois principaux algorithmes utilisés pour la tokenisation en sous-mots. - -## SentencePiece - -[SentencePiece](https://github.com/google/sentencepiece) est un algorithme de tokenisation pour le prétraitement du texte que vous pouvez utiliser avec n'importe lequel des modèles que nous verrons dans les trois prochaines sections. Il considère le texte comme une séquence de caractères Unicode et il remplace les espaces par un caractère spécial : `▁`. Utilisé en conjonction avec l'algorithme Unigram (voir la [section 7](/course/fr/chapter7/7)), il ne nécessite même pas d'étape de prétokénisation, ce qui est très utile pour les langues où le caractère espace n'est pas utilisé (comme le chinois ou le japonais). - -L'autre caractéristique principale de SentencePiece est le *tokenisation réversible* : comme il n'y a pas de traitement spécial des espaces, le décodage des *tokens* se fait simplement en les concaténant et en remplaçant les `_` par des espaces, ce qui donne le texte normalisé. Comme nous l'avons vu précédemment, le *tokenizer* de BERT supprime les espaces répétitifs, donc sa tokenisation n'est pas réversible. - -## Vue d'ensemble des algorithmes - -Dans les sections suivantes, nous allons nous plonger dans les trois principaux algorithmes de tokenisation en sous-mots : BPE (utilisé par GPT-2 et autres), WordPiece (utilisé par exemple par BERT), et Unigram (utilisé par T5 et autres). Avant de commencer, voici un rapide aperçu du fonctionnement de chacun d'entre eux. N'hésitez pas à revenir à ce tableau après avoir lu chacune des sections suivantes si cela n'a pas encore de sens pour vous. - - -Modèle | BPE | WordPiece | Unigramme -:----:|:---:|:---------:|:------: -Entraînement | Part d'un petit vocabulaire et apprend des règles pour fusionner les *tokens* | Part d'un petit vocabulaire et apprend des règles pour fusionner les *tokens* | Part d'un grand vocabulaire et apprend des règles pour supprimer les *tokens* -Étape d'entraînement | Fusionne les *tokens* correspondant à la paire la plus commune | Fusionne les *tokens* correspondant à la paire ayant le meilleur score basé sur la fréquence de la paire, en privilégiant les paires où chaque *token* individuel est moins fréquent | Supprime tous les *tokens* du vocabulaire qui minimiseront la perte calculée sur le corpus entier -Apprend | A fusionner des règles et un vocabulaire | Juste un vocabulaire | Un vocabulaire avec un score pour chaque *token* -Encodage | Découpe un mot en caractères et applique les fusions apprises pendant l'entraînement | Trouve le plus long sous-mot depuis le début qui est dans le vocabulaire puis fait de même pour le reste du mot | Trouve la division la plus probable en tokens, en utilisant les scores appris pendant l'entraînement - -Maintenant, plongeons dans le BPE ! +# Normalisation et prétokenization + + + +Avant de nous plonger plus profondément dans les trois algorithmes de tokénisation en sous-mots les plus courants utilisés avec les *transformers* (*Byte-Pair Encoding* (BPE), *WordPiece* et *Unigram*), nous allons d'abord examiner le prétraitement que chaque *tokenizer* applique au texte. Voici un aperçu de haut niveau des étapes du pipeline de tokenisation : + +
+The tokenization pipeline. + +
+ +Avant de diviser un texte en sous-*tokens* (selon le modèle), le *tokenizer* effectue deux étapes : la _normalisation_ et la _prétokénisation_. + +## Normalisation + + + +L'étape de normalisation implique un nettoyage général, comme la suppression des espaces inutiles, la mise en minuscules et/ou la suppression des accents. Si vous êtes familier avec la [normalisation Unicode](http://www.unicode.org/reports/tr15/) (comme NFC ou NFKC), c'est aussi quelque chose que le *tokenizer* peut appliquer. + +Le `tokenizer` de 🤗 *Transformers* possède un attribut appelé `backend_tokenizer` qui donne accès au *tokenizer* sous-jacent de la bibliothèque 🤗 *Tokenizers* : + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased") +print(type(tokenizer.backend_tokenizer)) +``` + +```python out + +``` + +L'attribut `normalizer` de l'objet `tokenizer` possède une méthode `normalize_str()` que nous pouvons utiliser pour voir comment la normalisation est effectuée : + +```py +print(tokenizer.backend_tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) +``` + +```python out +'hello how are u?' +``` + +Dans cet exemple, puisque nous avons choisi le *checkpoint* `bert-base-uncased`, la normalisation a mis le texte en minuscule et supprimé les accents. + + + +✏️ **Essayez !** Chargez un *tokenizer* depuis le *checkpoint* `bert-base-cased` et passez-lui le même exemple. Quelles sont les principales différences que vous pouvez voir entre les versions casée et non casée du *tokenizer* ? + + + +## Prétokenization + + + +Comme nous le verrons dans les sections suivantes, un *tokenizer* ne peut pas être entraîné uniquement sur du texte brut. Au lieu de cela, nous devons d'abord diviser les textes en petites entités, comme des mots. C'est là qu'intervient l'étape de prétokénisation. Comme nous l'avons vu dans le [chapitre 2](/course/fr/chapter2), un *tokenizer* basé sur les mots peut simplement diviser un texte brut en mots sur les espaces et la ponctuation. Ces mots constitueront les limites des sous-*tokens* que le *tokenizer* peut apprendre pendant son entraînement. + +Pour voir comment un *tokenizer* rapide effectue la prétokénisation, nous pouvons utiliser la méthode `pre_tokenize_str()`de l'attribut `pre_tokenizer` de l'objet `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))] +``` + +Remarquez que le *tokenizer* garde déjà la trace des *offsets*, ce qui lui permet de nous donner la correspondance des décalages que nous avons utilisée dans la section précédente. Ici, le *tokenizer* ignore les deux espaces et les remplace par un seul, mais le décalage saute entre `are` et `you` pour en tenir compte. + +Puisque nous utilisons le *tokenizer* de BERT, la prétokénisation implique la séparation des espaces et de la ponctuation. D'autres *tokenizers* peuvent avoir des règles différentes pour cette étape. Par exemple, si nous utilisons le *tokenizer* du GPT-2 : + +```py +tokenizer = AutoTokenizer.from_pretrained("gpt2") +tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") +``` + +Il séparera aussi sur les espaces et la ponctuation mais gardera les espaces et les remplacera par un symbole `Ġ`, ce qui lui permettra de récupérer les espaces originaux si nous décodons les *tokens* : + +```python out +[('Hello', (0, 5)), (',', (5, 6)), ('Ġhow', (6, 10)), ('Ġare', (10, 14)), ('Ġ', (14, 15)), ('Ġyou', (15, 19)), + ('?', (19, 20))] +``` + +Notez également que, contrairement au *tokenizer* de BERT, ce *tokenizer* n'ignore pas les doubles espaces. + +Pour un dernier exemple, regardons le *tokenizer* du 5, qui est basé sur l'algorithme 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))] +``` + +Comme le *tokenizer* du GPT-2, celui-ci garde les espaces et les remplace par un *token* spécifique (`_`), mais le *tokenizer* du T5 ne sépare que sur les espaces, pas sur la ponctuation. Notez également qu'il a ajouté un espace par défaut au début de la phrase (avant `Hello`) et il ignore le double espace entre `are` et `you`. + +Maintenant que nous avons vu un peu comment les différents *tokenizers* traitent le texte, nous pouvons commencer à explorer les algorithmes sous-jacents eux-mêmes. Nous commencerons par jeter un coup d'oeil rapide sur le très répandu SentencePiece, puis au cours des trois sections suivantes nous examinerons le fonctionnement des trois principaux algorithmes utilisés pour la tokenisation en sous-mots. + +## SentencePiece + +[SentencePiece](https://github.com/google/sentencepiece) est un algorithme de tokenisation pour le prétraitement du texte que vous pouvez utiliser avec n'importe lequel des modèles que nous verrons dans les trois prochaines sections. Il considère le texte comme une séquence de caractères Unicode et il remplace les espaces par un caractère spécial : `▁`. Utilisé en conjonction avec l'algorithme Unigram (voir la [section 7](/course/fr/chapter7/7)), il ne nécessite même pas d'étape de prétokénisation, ce qui est très utile pour les langues où le caractère espace n'est pas utilisé (comme le chinois ou le japonais). + +L'autre caractéristique principale de SentencePiece est le *tokenisation réversible* : comme il n'y a pas de traitement spécial des espaces, le décodage des *tokens* se fait simplement en les concaténant et en remplaçant les `_` par des espaces, ce qui donne le texte normalisé. Comme nous l'avons vu précédemment, le *tokenizer* de BERT supprime les espaces répétitifs, donc sa tokenisation n'est pas réversible. + +## Vue d'ensemble des algorithmes + +Dans les sections suivantes, nous allons nous plonger dans les trois principaux algorithmes de tokenisation en sous-mots : BPE (utilisé par GPT-2 et autres), WordPiece (utilisé par exemple par BERT), et Unigram (utilisé par T5 et autres). Avant de commencer, voici un rapide aperçu du fonctionnement de chacun d'entre eux. N'hésitez pas à revenir à ce tableau après avoir lu chacune des sections suivantes si cela n'a pas encore de sens pour vous. + + +Modèle | BPE | WordPiece | Unigramme +:----:|:---:|:---------:|:------: +Entraînement | Part d'un petit vocabulaire et apprend des règles pour fusionner les *tokens* | Part d'un petit vocabulaire et apprend des règles pour fusionner les *tokens* | Part d'un grand vocabulaire et apprend des règles pour supprimer les *tokens* +Étape d'entraînement | Fusionne les *tokens* correspondant à la paire la plus commune | Fusionne les *tokens* correspondant à la paire ayant le meilleur score basé sur la fréquence de la paire, en privilégiant les paires où chaque *token* individuel est moins fréquent | Supprime tous les *tokens* du vocabulaire qui minimiseront la perte calculée sur le corpus entier +Apprend | A fusionner des règles et un vocabulaire | Juste un vocabulaire | Un vocabulaire avec un score pour chaque *token* +Encodage | Découpe un mot en caractères et applique les fusions apprises pendant l'entraînement | Trouve le plus long sous-mot depuis le début qui est dans le vocabulaire puis fait de même pour le reste du mot | Trouve la division la plus probable en tokens, en utilisant les scores appris pendant l'entraînement + +Maintenant, plongeons dans le BPE ! diff --git a/chapters/fr/chapter6/5.mdx b/chapters/fr/chapter6/5.mdx index 410d75af8..ee2ad8a52 100644 --- a/chapters/fr/chapter6/5.mdx +++ b/chapters/fr/chapter6/5.mdx @@ -1,364 +1,364 @@ -# Tokénisation Byte-Pair Encoding - - - -Le *Byte-Pair Encoding* (BPE) a été initialement développé en tant qu'algorithme de compression de textes puis utilisé par OpenAI pour la tokenisation du pré-entraînement du modèle GPT. Il est utilisé par de nombreux *transformers* dont GPT, GPT-2, RoBERTa, BART et DeBERTa. - - - - - -💡 Cette section couvre le BPE en profondeur, allant jusqu'à montrer une implémentation complète. Vous pouvez passer directement à la fin si vous souhaitez simplement avoir un aperçu général de l'algorithme de tokenisation. - - - -## Algorithme d'entraînement - -L'entraînement du BPE commence par le calcul de l'unique ensemble de mots utilisés dans le corpus (après les étapes de normalisation et de prétokénisation), puis la construction du vocabulaire en prenant tous les symboles utilisés pour écrire ces mots. A titre d'exemple, disons que notre corpus utilise ces cinq mots : - -``` -"hug", "pug", "pun", "bun", "hugs" # "câlin", "carlin", "jeu de mots", "brioche", "câlins" -``` - -Le vocabulaire de base sera alors `["b", "g", "h", "n", "p", "s", "u"]`. Dans le monde réel, le vocabulaire de base contient au moins tous les caractères ASCII et probablement aussi quelques caractères Unicode. Si un exemple que vous tokenisez utilise un caractère qui n'est pas dans le corpus d'entraînement, ce caractère est converti en *token* inconnu. C'est l'une des raisons pour lesquelles de nombreux modèles de NLP sont par exemple très mauvais dans l'analyse de contenus contenant des emojis. - - - -Les *tokenizers* du GPT-2 et de RoBERTa (qui sont assez similaires) ont une façon intelligente de gérer ce problème : ils ne considèrent pas les mots comme étant écrits avec des caractères Unicode mais avec des octets. De cette façon, le vocabulaire de base a une petite taille (256) et tous les caractères auxquels vous pouvez penser seront inclus dedans et ne finiront pas par être convertis en un *token* inconnu. Cette astuce est appelée *byte-level BPE*. - - - -Après avoir obtenu ce vocabulaire de base, nous ajoutons de nouveaux *tokens* jusqu'à ce que la taille souhaitée du vocabulaire soit atteinte en apprenant les fusions qui sont des règles permettant de fusionner deux éléments du vocabulaire existant pour en créer un nouveau. Ainsi, au début, ces fusions créeront des *tokens* de deux caractères, puis au fur et à mesure de l'entraînement, des sous-mots plus longs. - -À chaque étape de l'entraînement du *tokenizer*, l'algorithme BPE recherche la paire la plus fréquente de *tokens* existants (par « paire », nous entendons ici deux *tokens* consécutifs dans un mot). Cette paire la plus fréquente est celle qui sera fusionnée. Nous rinçons et répétons pour l'étape suivante. - -Pour revenir à notre exemple précédent, supposons que les mots ont les fréquences suivantes : - -``` -("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) -``` - -ce qui veut dire que `"hug"` était présent 10 fois dans le corpus, `"pug"` 5 fois, `"pun"` 12 fois, `"bun"` 4 fois et `"hugs"`" 5 fois. Nous commençons l'entraînement en divisant chaque mot en caractères (ceux qui forment notre vocabulaire initial) afin de voir chaque mot comme une liste de *tokens* : - -``` -("h" "u" "g", 10), ("p" "u" "g", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "u" "g" "s", 5) -``` - -Ensuite, nous regardons les paires. La paire `("h", "u")` est présente dans les mots `"hug"` et `"hugs"`, donc 15 fois au total dans le corpus. Ce n'est cependant pas la paire la plus fréquente. Cet honneur revient à `("u", "g")` qui est présent dans `"hug"`, `"pug"`, et `"hugs"`, pour un total de 20 fois dans le vocabulaire. - -Ainsi, la première règle de fusion apprise par le *tokenizer* est `("u", "g") -> "ug"`, ce qui signifie que `"ug"` est ajouté au vocabulaire et que la paire doit être fusionnée dans tous les mots du corpus. A la fin de cette étape, le vocabulaire et le corpus ressemblent à ceci : - -``` -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) -``` - -Nous avons maintenant quelques paires qui aboutissent à un *token* de plus de deux caractères. Par exemple la paire `("h", "ug")` présente 15 fois dans le corpus. La paire la plus fréquente à ce stade est `("u", "n")`, présente 16 fois dans le corpus, donc la deuxième règle de fusion apprise est `("u", "n") -> "un"`. En ajoutant cela au vocabulaire et en fusionnant toutes les occurrences existantes, nous obtenons : - -``` -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) -``` - -Maintenant la paire la plus fréquente est `("h", "ug")` donc nous apprenons la règle de fusion `("h", "ug") -> "hug"`. Cela nous donne donc notre premier *token* de trois lettres. Après la fusion, le corpus ressemble à ceci : - -``` -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) -``` - -Et nous continuons ainsi jusqu'à ce que nous atteignions la taille de vocabulaire souhaitée. - - - -✏️ **A votre tour !** A votre avis, quelle sera la prochaine règle de fusion ? - - - -## Algorithme de tokenisation - -La tokenisation suit de près le processus d'entraînement, dans le sens où les nouvelles entrées sont tokenisées en appliquant les étapes suivantes : - -1. Normalisation -2. Prétokénisation -3. Découpage des mots en caractères individuels -4. Application des règles de fusion apprises dans l'ordre sur ces divisions. - -Prenons l'exemple que nous avons utilisé pendant l'entraînement, avec les trois règles de fusion apprises : - -``` -("u", "g") -> "ug" -("u", "n") -> "un" -("h", "ug") -> "hug" -``` - -Le mot « bug » sera traduit par « ["b", "ug"] ». Par contre, le mot « mug » (tasse en français) sera traduit par « ["[UNK]", "ug"] » puisque la lettre « m » ne fait pas partie du vocabulaire de base. De la même façon, le mot « thug » (voyou en français) sera tokenisé en « ["[UNK]", "hug"] » car la lettre « t » n'est pas dans le vocabulaire de base et l'application des règles de fusion résulte d'abord en la fusion de « u » et « g » et ensuite en la fusion de « hu » et « g ». - - - -✏️ **A votre tour !** Comment pensez-vous que le mot « unhug » (détacher en français) sera tokenized ? - - - -## Implémentation du BPE - -Voyons maintenant une implémentation de l'algorithme BPE. Il ne s'agira pas d'une version optimisée que vous pourrez utiliser sur un grand corpus. Nous voulons simplement vous montrer le code afin que vous puissiez comprendre un peu mieux l'algorithme. - -Tout d'abord, nous avons besoin d'un corpus, alors créons un corpus simple avec quelques phrases : - -```python -corpus = [ - "This is the Hugging Face Course.", - # C'est le cours d'Hugging Face. - "This chapter is about tokenization.", - # Ce chapitre traite de la tokenisation. - "This section shows several tokenizer algorithms.", - # Cette section présente plusieurs algorithmes de tokenizer. - "Hopefully, you will be able to understand how they are trained and generate tokens.", - # Avec un peu de chance, vous serez en mesure de comprendre comment ils sont entraînés et génèrent des tokens. -] -``` - -Ensuite, nous devons prétokeniser ce corpus en mots. Puisque nous répliquons un *tokenizer* BPE (comme celui du GPT-2), nous utiliserons le *tokenizer* `gpt2` pour la prétokénisation : - -```python -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained("gpt2") -``` - -Ensuite, nous calculons les fréquences de chaque mot dans le corpus comme nous le faisons pour la prétokénisation : - -```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}) -``` - -L'étape suivante consiste à calculer le vocabulaire de base, formé par tous les caractères utilisés dans le 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', 'Ġ'] -``` - -Nous ajoutons également les *tokens* spéciaux utilisés par le modèle au début de ce vocabulaire. Dans le cas du GPT-2, le seul *token* spécial est `"<|endoftext|>"` : - -```python -vocab = ["<|endoftext|>"] + alphabet.copy() -``` - -Nous devons maintenant diviser chaque mot en caractères individuels pour pouvoir commencer l'entraînement : - -```python -splits = {word: [c for c in word] for word in word_freqs.keys()} -``` - -Maintenant que nous sommes prêts pour l'entraînement, écrivons une fonction qui calcule la fréquence de chaque paire. Nous devrons l'utiliser à chaque étape de l'entraînement : - -```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 -``` - -Jetons un coup d'œil à une partie de ce dictionnaire après les premières divisions : - -```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 -``` - -Maintenant, trouver la paire la plus fréquente ne demande qu'une rapide boucle : - -```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 -``` - -Donc la première fusion à apprendre est `('Ġ', 't') -> 'Ġt'`, et on ajoute `'Ġt'` au vocabulaire : - -```python -merges = {("Ġ", "t"): "Ġt"} -vocab.append("Ġt") -``` - -Pour continuer, nous devons appliquer cette fusion dans notre dictionnaire `splits`. Écrivons une autre fonction pour cela : - -```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 -``` - -Et nous pouvons regarder le résultat de la première fusion : - -```py -splits = merge_pair("Ġ", "t", splits) -print(splits["Ġtrained"]) -``` - -```python out -['Ġt', 'r', 'a', 'i', 'n', 'e', 'd'] -``` - -Maintenant, nous avons tout ce dont nous avons besoin pour boucler jusqu'à ce que nous ayons appris toutes les fusions que nous voulons. Visons une taille de vocabulaire 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]) -``` - -En conséquence, nous avons appris 19 règles de fusion (le vocabulaire initial avait une taille de 31 : 30 caractères dans l'alphabet plus le *token* spécial) : - -```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'} -``` - -Et le vocabulaire est composé du *token* spécial, de l'alphabet initial, et de tous les résultats des fusions : - -```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'] -``` - - - -💡 Utiliser `train_new_from_iterator()` sur le même corpus ne donnera pas exactement le même vocabulaire. C'est parce que lorsqu'il y a un choix de la paire la plus fréquente, nous avons sélectionné la première rencontrée, alors que la bibliothèque 🤗 *Tokenizers* sélectionne la première en fonction de ses identifiants internes. - - - -Pour tokeniser un nouveau texte, on le prétokenise, on le divise, puis on applique toutes les règles de fusion apprises : - -```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, []) -``` - -Nous pouvons essayer cela sur n'importe quel texte composé de caractères de l'alphabet : - -```py -tokenize("This is not a token.") -``` - -```python out -['This', 'Ġis', 'Ġ', 'n', 'o', 't', 'Ġa', 'Ġtoken', '.'] -``` - - - -⚠️ Notre implémentation lancera une erreur s'il y a un caractère inconnu puisque nous n'avons rien fait pour les gérer. GPT-2 n'a pas réellement de token inconnu (il est impossible d'obtenir un caractère inconnu en utilisant le BPE au niveau de l'octet) mais cela pourrait arriver ici car nous n'avons pas inclus tous les octets possibles dans le vocabulaire initial. Cet aspect du BPE dépasse le cadre de cette section, nous avons donc laissé ces détails de côté. - - - +# Tokénisation Byte-Pair Encoding + + + +Le *Byte-Pair Encoding* (BPE) a été initialement développé en tant qu'algorithme de compression de textes puis utilisé par OpenAI pour la tokenisation du pré-entraînement du modèle GPT. Il est utilisé par de nombreux *transformers* dont GPT, GPT-2, RoBERTa, BART et DeBERTa. + + + + + +💡 Cette section couvre le BPE en profondeur, allant jusqu'à montrer une implémentation complète. Vous pouvez passer directement à la fin si vous souhaitez simplement avoir un aperçu général de l'algorithme de tokenisation. + + + +## Algorithme d'entraînement + +L'entraînement du BPE commence par le calcul de l'unique ensemble de mots utilisés dans le corpus (après les étapes de normalisation et de prétokénisation), puis la construction du vocabulaire en prenant tous les symboles utilisés pour écrire ces mots. A titre d'exemple, disons que notre corpus utilise ces cinq mots : + +``` +"hug", "pug", "pun", "bun", "hugs" # "câlin", "carlin", "jeu de mots", "brioche", "câlins" +``` + +Le vocabulaire de base sera alors `["b", "g", "h", "n", "p", "s", "u"]`. Dans le monde réel, le vocabulaire de base contient au moins tous les caractères ASCII et probablement aussi quelques caractères Unicode. Si un exemple que vous tokenisez utilise un caractère qui n'est pas dans le corpus d'entraînement, ce caractère est converti en *token* inconnu. C'est l'une des raisons pour lesquelles de nombreux modèles de NLP sont par exemple très mauvais dans l'analyse de contenus contenant des emojis. + + + +Les *tokenizers* du GPT-2 et de RoBERTa (qui sont assez similaires) ont une façon intelligente de gérer ce problème : ils ne considèrent pas les mots comme étant écrits avec des caractères Unicode mais avec des octets. De cette façon, le vocabulaire de base a une petite taille (256) et tous les caractères auxquels vous pouvez penser seront inclus dedans et ne finiront pas par être convertis en un *token* inconnu. Cette astuce est appelée *byte-level BPE*. + + + +Après avoir obtenu ce vocabulaire de base, nous ajoutons de nouveaux *tokens* jusqu'à ce que la taille souhaitée du vocabulaire soit atteinte en apprenant les fusions qui sont des règles permettant de fusionner deux éléments du vocabulaire existant pour en créer un nouveau. Ainsi, au début, ces fusions créeront des *tokens* de deux caractères, puis au fur et à mesure de l'entraînement, des sous-mots plus longs. + +À chaque étape de l'entraînement du *tokenizer*, l'algorithme BPE recherche la paire la plus fréquente de *tokens* existants (par « paire », nous entendons ici deux *tokens* consécutifs dans un mot). Cette paire la plus fréquente est celle qui sera fusionnée. Nous rinçons et répétons pour l'étape suivante. + +Pour revenir à notre exemple précédent, supposons que les mots ont les fréquences suivantes : + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +ce qui veut dire que `"hug"` était présent 10 fois dans le corpus, `"pug"` 5 fois, `"pun"` 12 fois, `"bun"` 4 fois et `"hugs"`" 5 fois. Nous commençons l'entraînement en divisant chaque mot en caractères (ceux qui forment notre vocabulaire initial) afin de voir chaque mot comme une liste de *tokens* : + +``` +("h" "u" "g", 10), ("p" "u" "g", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "u" "g" "s", 5) +``` + +Ensuite, nous regardons les paires. La paire `("h", "u")` est présente dans les mots `"hug"` et `"hugs"`, donc 15 fois au total dans le corpus. Ce n'est cependant pas la paire la plus fréquente. Cet honneur revient à `("u", "g")` qui est présent dans `"hug"`, `"pug"`, et `"hugs"`, pour un total de 20 fois dans le vocabulaire. + +Ainsi, la première règle de fusion apprise par le *tokenizer* est `("u", "g") -> "ug"`, ce qui signifie que `"ug"` est ajouté au vocabulaire et que la paire doit être fusionnée dans tous les mots du corpus. A la fin de cette étape, le vocabulaire et le corpus ressemblent à ceci : + +``` +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) +``` + +Nous avons maintenant quelques paires qui aboutissent à un *token* de plus de deux caractères. Par exemple la paire `("h", "ug")` présente 15 fois dans le corpus. La paire la plus fréquente à ce stade est `("u", "n")`, présente 16 fois dans le corpus, donc la deuxième règle de fusion apprise est `("u", "n") -> "un"`. En ajoutant cela au vocabulaire et en fusionnant toutes les occurrences existantes, nous obtenons : + +``` +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) +``` + +Maintenant la paire la plus fréquente est `("h", "ug")` donc nous apprenons la règle de fusion `("h", "ug") -> "hug"`. Cela nous donne donc notre premier *token* de trois lettres. Après la fusion, le corpus ressemble à ceci : + +``` +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) +``` + +Et nous continuons ainsi jusqu'à ce que nous atteignions la taille de vocabulaire souhaitée. + + + +✏️ **A votre tour !** A votre avis, quelle sera la prochaine règle de fusion ? + + + +## Algorithme de tokenisation + +La tokenisation suit de près le processus d'entraînement, dans le sens où les nouvelles entrées sont tokenisées en appliquant les étapes suivantes : + +1. Normalisation +2. Prétokénisation +3. Découpage des mots en caractères individuels +4. Application des règles de fusion apprises dans l'ordre sur ces divisions. + +Prenons l'exemple que nous avons utilisé pendant l'entraînement, avec les trois règles de fusion apprises : + +``` +("u", "g") -> "ug" +("u", "n") -> "un" +("h", "ug") -> "hug" +``` + +Le mot « bug » sera traduit par « ["b", "ug"] ». Par contre, le mot « mug » (tasse en français) sera traduit par « ["[UNK]", "ug"] » puisque la lettre « m » ne fait pas partie du vocabulaire de base. De la même façon, le mot « thug » (voyou en français) sera tokenisé en « ["[UNK]", "hug"] » car la lettre « t » n'est pas dans le vocabulaire de base et l'application des règles de fusion résulte d'abord en la fusion de « u » et « g » et ensuite en la fusion de « hu » et « g ». + + + +✏️ **A votre tour !** Comment pensez-vous que le mot « unhug » (détacher en français) sera tokenized ? + + + +## Implémentation du BPE + +Voyons maintenant une implémentation de l'algorithme BPE. Il ne s'agira pas d'une version optimisée que vous pourrez utiliser sur un grand corpus. Nous voulons simplement vous montrer le code afin que vous puissiez comprendre un peu mieux l'algorithme. + +Tout d'abord, nous avons besoin d'un corpus, alors créons un corpus simple avec quelques phrases : + +```python +corpus = [ + "This is the Hugging Face Course.", + # C'est le cours d'Hugging Face. + "This chapter is about tokenization.", + # Ce chapitre traite de la tokenisation. + "This section shows several tokenizer algorithms.", + # Cette section présente plusieurs algorithmes de tokenizer. + "Hopefully, you will be able to understand how they are trained and generate tokens.", + # Avec un peu de chance, vous serez en mesure de comprendre comment ils sont entraînés et génèrent des tokens. +] +``` + +Ensuite, nous devons prétokeniser ce corpus en mots. Puisque nous répliquons un *tokenizer* BPE (comme celui du GPT-2), nous utiliserons le *tokenizer* `gpt2` pour la prétokénisation : + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("gpt2") +``` + +Ensuite, nous calculons les fréquences de chaque mot dans le corpus comme nous le faisons pour la prétokénisation : + +```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}) +``` + +L'étape suivante consiste à calculer le vocabulaire de base, formé par tous les caractères utilisés dans le 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', 'Ġ'] +``` + +Nous ajoutons également les *tokens* spéciaux utilisés par le modèle au début de ce vocabulaire. Dans le cas du GPT-2, le seul *token* spécial est `"<|endoftext|>"` : + +```python +vocab = ["<|endoftext|>"] + alphabet.copy() +``` + +Nous devons maintenant diviser chaque mot en caractères individuels pour pouvoir commencer l'entraînement : + +```python +splits = {word: [c for c in word] for word in word_freqs.keys()} +``` + +Maintenant que nous sommes prêts pour l'entraînement, écrivons une fonction qui calcule la fréquence de chaque paire. Nous devrons l'utiliser à chaque étape de l'entraînement : + +```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 +``` + +Jetons un coup d'œil à une partie de ce dictionnaire après les premières divisions : + +```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 +``` + +Maintenant, trouver la paire la plus fréquente ne demande qu'une rapide boucle : + +```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 +``` + +Donc la première fusion à apprendre est `('Ġ', 't') -> 'Ġt'`, et on ajoute `'Ġt'` au vocabulaire : + +```python +merges = {("Ġ", "t"): "Ġt"} +vocab.append("Ġt") +``` + +Pour continuer, nous devons appliquer cette fusion dans notre dictionnaire `splits`. Écrivons une autre fonction pour cela : + +```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 +``` + +Et nous pouvons regarder le résultat de la première fusion : + +```py +splits = merge_pair("Ġ", "t", splits) +print(splits["Ġtrained"]) +``` + +```python out +['Ġt', 'r', 'a', 'i', 'n', 'e', 'd'] +``` + +Maintenant, nous avons tout ce dont nous avons besoin pour boucler jusqu'à ce que nous ayons appris toutes les fusions que nous voulons. Visons une taille de vocabulaire 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]) +``` + +En conséquence, nous avons appris 19 règles de fusion (le vocabulaire initial avait une taille de 31 : 30 caractères dans l'alphabet plus le *token* spécial) : + +```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'} +``` + +Et le vocabulaire est composé du *token* spécial, de l'alphabet initial, et de tous les résultats des fusions : + +```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'] +``` + + + +💡 Utiliser `train_new_from_iterator()` sur le même corpus ne donnera pas exactement le même vocabulaire. C'est parce que lorsqu'il y a un choix de la paire la plus fréquente, nous avons sélectionné la première rencontrée, alors que la bibliothèque 🤗 *Tokenizers* sélectionne la première en fonction de ses identifiants internes. + + + +Pour tokeniser un nouveau texte, on le prétokenise, on le divise, puis on applique toutes les règles de fusion apprises : + +```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, []) +``` + +Nous pouvons essayer cela sur n'importe quel texte composé de caractères de l'alphabet : + +```py +tokenize("This is not a token.") +``` + +```python out +['This', 'Ġis', 'Ġ', 'n', 'o', 't', 'Ġa', 'Ġtoken', '.'] +``` + + + +⚠️ Notre implémentation lancera une erreur s'il y a un caractère inconnu puisque nous n'avons rien fait pour les gérer. GPT-2 n'a pas réellement de token inconnu (il est impossible d'obtenir un caractère inconnu en utilisant le BPE au niveau de l'octet) mais cela pourrait arriver ici car nous n'avons pas inclus tous les octets possibles dans le vocabulaire initial. Cet aspect du BPE dépasse le cadre de cette section, nous avons donc laissé ces détails de côté. + + + C'est tout pour l'algorithme BPE ! Nous allons nous intéresser à WordPiece dans la suite. \ No newline at end of file diff --git a/chapters/fr/chapter6/6.mdx b/chapters/fr/chapter6/6.mdx index e854edde1..44206d994 100644 --- a/chapters/fr/chapter6/6.mdx +++ b/chapters/fr/chapter6/6.mdx @@ -1,378 +1,378 @@ -# Tokénisation WordPiece - - - -*WordPiece* est l'algorithme de tokénisation développé par Google pour prétraîner BERT. Il a depuis été réutilisé dans un grand nombre de modèles de *transformers* basés sur BERT tels que DistilBERT, MobileBERT, Funnel Transformers et MPNET. Il est très similaire au BPE en termes d'entraînement mais la tokenisation réelle est effectuée différemment. - - - - - -💡 Cette section couvre le WordPiece en profondeur, allant jusqu'à montrer une implémentation complète. Vous pouvez passer directement à la fin si vous souhaitez simplement avoir un aperçu général de l'algorithme de tokénisation. - - - -## Algorithme d'entraînement - - - -⚠️ Google n'a jamais mis en ligne son implémentation de l'algorithme d'entraînement du WordPiece. Ce qui suit est donc notre meilleure estimation basée sur la littérature publiée. Il se peut qu'elle ne soit pas exacte à 100 %. - - - -Comme le BPE, *WordPiece* part d'un petit vocabulaire comprenant les *tokens* spéciaux utilisés par le modèle et l'alphabet initial. Puisqu'il identifie les sous-mots en ajoutant un préfixe (comme `##` pour BERT), chaque mot est initialement découpé en ajoutant ce préfixe à tous les caractères du mot. Ainsi par exemple, `"word"` est divisé comme ceci : - -``` -w ##o ##r ##d -``` - -Ainsi, l'alphabet initial contient tous les caractères présents au début d'un mot et les caractères présents à l'intérieur d'un mot précédé du préfixe de *WordPiece*. - -Ensuite, toujours comme le BPE, *WordPiece* apprend des règles de fusion. La principale différence réside dans la manière dont la paire à fusionner est sélectionnée. Au lieu de sélectionner la paire la plus fréquente, *WordPiece* calcule un score pour chaque paire en utilisant la formule suivante : - -$$\mathrm{score} = (\mathrm{freq\_of\_pair}) / (\mathrm{freq\_of\_first\_element} \times \mathrm{freq\_of\_second\_element})$$ - -En divisant la fréquence de la paire par le produit des fréquences de chacune de ses parties, l'algorithme donne la priorité à la fusion des paires dont les parties individuelles sont moins fréquentes dans le vocabulaire. Par exemple, il ne fusionnera pas nécessairement `("un", "##able")` même si cette paire apparaît très fréquemment dans le vocabulaire car les deux paires `"un"`" et `"##able"` apparaîtront probablement chacune dans un batch d'autres mots et auront une fréquence élevée. En revanche, une paire comme `("hu", "##gging")` sera probablement fusionnée plus rapidement (en supposant que le mot `"hugging"` apparaisse souvent dans le vocabulaire) puisque `"hu"` et `"##gging"` sont probablement moins fréquents individuellement. - -Examinons le même vocabulaire que celui utilisé dans l'exemple d'entraînement du BPE : - -``` -("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) -``` - -Les divisions ici seront : - -``` -("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##g" "##s", 5) -``` - -Si on oublie les *tokens* spéciaux pour l'instant, le vocabulaire initial sera donc `["b", "h", "p", "##g", "##n", "##s", "##u"]`. La paire la plus fréquente est `("##u", "##g")` (présente 20 fois), mais la fréquence individuelle de `"##u"` est très élevée, donc son score n'est pas le plus élevé (il est de 1 / 36). Toutes les paires avec un `"##u"` ont en fait le même score (1 / 36). Ainsi le meilleur score va à la paire `("##g", "##s")` qui est la seule sans un `"##u"` avec un score 1 / 20. Et la première fusion apprise est `("##g", "##s") -> ("##gs")`. - -Notez que lorsque nous fusionnons, nous enlevons le `##` entre les deux *tokens*, donc nous ajoutons `"##gs"` au vocabulaire et appliquons la fusion dans les mots du 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) -``` - -À ce stade, `" ##u "` est dans toutes les paires possibles, donc elles finissent toutes par avoir le même score. Disons que dans ce cas, la première paire est fusionnée, donc `("h", "##u") -> "hu"`. Cela nous amène à : - -``` -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) -``` - -Ensuite, le meilleur score suivant est partagé par `("hu", "##g")` et `("hu", "##gs")` (avec 1/15, comparé à 1/21 pour toutes les autres paires). Ainsi la première paire avec le plus grand score est fusionnée : - -``` -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) -``` - -et nous continuons ainsi jusqu'à ce que nous atteignions la taille de vocabulaire souhaitée. - - - -✏️ **A votre tour !** Quelle sera la prochaine règle de fusion ? - - - -## Algorithme de tokenisation - -La tokénisation diffère dans *WordPiece* et BPE en ce que *WordPiece* ne sauvegarde que le vocabulaire final et non pas les règles de fusion apprises. En partant du mot à tokeniser, *WordPiece* trouve le sous-mot le plus long qui se trouve dans le vocabulaire, puis se sépare sur celui-ci. Par exemple, si nous utilisons le vocabulaire appris dans l'exemple ci-dessus, pour le mot `"hugs"` le plus long sous-mot en partant du début qui est dans le vocabulaire est `"hug"`. Donc nous le divisons et obtenons `["hug", "##s"]`. On continue avec `"##s"`, qui est dans le vocabulaire, donc la tokenisation de `"hugs"` est `["hug", "##s"]`. - -Avec BPE, nous aurions appliqué les fusions apprises dans l'ordre et la tokénisation aurait été `["hu", "##gs"]`, l'encodage est donc différent. - -Comme autre exemple, voyons comment le mot `"bugs"` serait tokenisé. `"b"` est le plus long sous-mot commençant au début du mot qui est dans le vocabulaire donc on le divise et on obtient `["b", "##ugs"]`. Ensuite, `"##u"` est le plus long sous-mot commençant au début de `"##ugs"` qui est dans le vocabulaire, donc on le sépare et on obtient `["b", "##u, "##gs"]`. Enfin, `"##gs"` est dans le vocabulaire, donc cette dernière liste est la tokenization de `"bugs"`. - -Lorsque la tokenisation arrive à un stade où il n'est pas possible de trouver un sous-mot dans le vocabulaire, le mot entier est tokenisé comme inconnu. Par exemple, `"mug"` serait tokenisé comme `["[UNK]"]`, tout comme `"bum"` (même si on peut commencer par " b " et " ##u ", " ##m " ne fait pas partie du vocabulaire, et le *tokenizer* résultant sera simplement `["[UNK]"]` " et non `["b", "##u", "[UNK]"]` "). C'est une autre différence avec le BPE qui classerait seulement les caractères individuels qui ne sont pas dans le vocabulaire comme inconnus. - - - -✏️ **A votre tour !** Comment le mot `"pugs"` sera-t-il tokenisé ? - - - -## Implémentation de WordPiece - -Voyons maintenant une implémentation de l'algorithme *WordPiece*. Comme pour le BPE, il s'agit d'un exemple pédagogique et vous ne pourrez pas l'utiliser sur un grand corpus. - -Nous utiliserons le même corpus que dans l'exemple BPE : - -```python -corpus = [ - "This is the Hugging Face Course.", - # C'est le cours d'Hugging Face. - "This chapter is about tokenization.", - # This chapter is about tokenization - "This section shows several tokenizer algorithms.", - # Cette section présente plusieurs algorithmes de tokenizer. - "Hopefully, you will be able to understand how they are trained and generate tokens.", - # Avec un peu de chance, vous serez en mesure de comprendre comment ils sont entraînés et génèrent des tokens. -] -``` - -Tout d'abord, nous devons prétokéniser le corpus en mots. Puisque nous répliquons un *tokenizer WordPiece* (comme BERT), nous utiliserons le *tokenizer* `bert-base-cased` pour la prétokénisation : - -```python -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") -``` - -Ensuite, nous calculons les fréquences de chaque mot dans le corpus comme nous le faisons pour la prétokénisation : - -```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}) -``` - -Comme nous l'avons vu précédemment, l'alphabet est l'unique ensemble composé de toutes les premières lettres des mots, et de toutes les autres lettres qui apparaissent dans les mots préfixés par `##` : - -```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'] -``` - -Nous ajoutons également les *tokens* spéciaux utilisés par le modèle au début de ce vocabulaire. Dans le cas de BERT, il s'agit de la liste `["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]` : - -```python -vocab = ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"] + alphabet.copy() -``` - -Ensuite, nous devons diviser chaque mot, avec toutes les lettres qui ne sont pas les premières préfixées par `##` : - -```python -splits = { - word: [c if i == 0 else f"##{c}" for i, c in enumerate(word)] - for word in word_freqs.keys() -} -``` - -Maintenant que nous sommes prêts pour l'entraînement, écrivons une fonction qui calcule le score de chaque paire. Nous devrons l'utiliser à chaque étape de l'entraînement : - -```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 -``` - -Jetons un coup d'œil à une partie de ce dictionnaire après les premières divisions : - -```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 -``` - -Maintenant, trouver la paire avec le meilleur score ne prend qu'une rapide boucle : - -```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 -``` - -Ainsi, la première fusion à apprendre est `('a', '##b') -> 'ab'` et nous ajoutons `'ab'` au vocabulaire : - -```python -vocab.append("ab") -``` - -Pour continuer, nous devons appliquer cette fusion dans notre dictionnaire `splits`. Écrivons une autre fonction pour cela : - -```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 -``` - -Et nous pouvons regarder le résultat de la première fusion : - -```py -splits = merge_pair("a", "##b", splits) -splits["about"] -``` - -```python out -['ab', '##o', '##u', '##t'] -``` - -Nous avons maintenant tout ce dont nous avons besoin pour boucler jusqu'à ce que nous ayons appris toutes les fusions que nous voulons. Visons une taille de vocabulaire 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) -``` - -Nous pouvons ensuite examiner le vocabulaire généré : - -```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', '##fu', 'Fa', 'Fac', '##ct', '##ful', '##full', '##fully', - 'Th', 'ch', '##hm', 'cha', 'chap', 'chapt', '##thm', 'Hu', 'Hug', 'Hugg', 'sh', 'th', 'is', '##thms', '##za', '##zat', - '##ut'] -``` - -Comme nous pouvons le voir, comparé à BPE, ce *tokenizer* apprend les parties de mots comme des *tokens* un peu plus rapidement. - - - -💡 Utiliser `train_new_from_iterator()` sur le même corpus ne donnera pas exactement le même vocabulaire. C'est parce que la bibliothèque 🤗 *Tokenizers* n'implémente pas *WordPiece* pour l'entraînement (puisque nous ne sommes pas complètement sûrs de son fonctionnement interne), mais utilise le BPE à la place. - - - -Pour tokeniser un nouveau texte, on le prétokenise, on le divise, puis on applique l'algorithme de tokenisation sur chaque mot. En d'autres termes, nous recherchons le plus grand sous-mot commençant au début du premier mot et le divisons. Puis nous répétons le processus sur la deuxième partie et ainsi de suite pour le reste de ce mot et les mots suivants dans le texte : - -```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 -``` - -Testons-le sur un mot qui fait partie du vocabulaire, et un autre qui n'en fait pas partie : - -```python -print(encode_word("Hugging")) -print(encode_word("HOgging")) -``` - -```python out -['Hugg', '##i', '##n', '##g'] -['[UNK]'] -``` - -Maintenant, écrivons une fonction qui permet de tokeniser un texte : - -```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, []) -``` - -On peut l'essayer sur n'importe quel texte : - -```python -tokenize("This is the Hugging Face Course!") # C'est le cours d'Hugging Face -``` - -```python out -['Th', '##i', '##s', 'is', 'th', '##e', 'Hugg', '##i', '##n', '##g', 'Fac', '##e', 'c', '##o', '##u', '##r', '##s', - '##e', '[UNK]'] -``` - -C'est tout pour l'algorithme *WordPiece* ! Maintenant, jetons un coup d'oeil à *Unigram*. +# Tokénisation WordPiece + + + +*WordPiece* est l'algorithme de tokénisation développé par Google pour prétraîner BERT. Il a depuis été réutilisé dans un grand nombre de modèles de *transformers* basés sur BERT tels que DistilBERT, MobileBERT, Funnel Transformers et MPNET. Il est très similaire au BPE en termes d'entraînement mais la tokenisation réelle est effectuée différemment. + + + + + +💡 Cette section couvre le WordPiece en profondeur, allant jusqu'à montrer une implémentation complète. Vous pouvez passer directement à la fin si vous souhaitez simplement avoir un aperçu général de l'algorithme de tokénisation. + + + +## Algorithme d'entraînement + + + +⚠️ Google n'a jamais mis en ligne son implémentation de l'algorithme d'entraînement du WordPiece. Ce qui suit est donc notre meilleure estimation basée sur la littérature publiée. Il se peut qu'elle ne soit pas exacte à 100 %. + + + +Comme le BPE, *WordPiece* part d'un petit vocabulaire comprenant les *tokens* spéciaux utilisés par le modèle et l'alphabet initial. Puisqu'il identifie les sous-mots en ajoutant un préfixe (comme `##` pour BERT), chaque mot est initialement découpé en ajoutant ce préfixe à tous les caractères du mot. Ainsi par exemple, `"word"` est divisé comme ceci : + +``` +w ##o ##r ##d +``` + +Ainsi, l'alphabet initial contient tous les caractères présents au début d'un mot et les caractères présents à l'intérieur d'un mot précédé du préfixe de *WordPiece*. + +Ensuite, toujours comme le BPE, *WordPiece* apprend des règles de fusion. La principale différence réside dans la manière dont la paire à fusionner est sélectionnée. Au lieu de sélectionner la paire la plus fréquente, *WordPiece* calcule un score pour chaque paire en utilisant la formule suivante : + +$$\mathrm{score} = (\mathrm{freq\_of\_pair}) / (\mathrm{freq\_of\_first\_element} \times \mathrm{freq\_of\_second\_element})$$ + +En divisant la fréquence de la paire par le produit des fréquences de chacune de ses parties, l'algorithme donne la priorité à la fusion des paires dont les parties individuelles sont moins fréquentes dans le vocabulaire. Par exemple, il ne fusionnera pas nécessairement `("un", "##able")` même si cette paire apparaît très fréquemment dans le vocabulaire car les deux paires `"un"`" et `"##able"` apparaîtront probablement chacune dans un batch d'autres mots et auront une fréquence élevée. En revanche, une paire comme `("hu", "##gging")` sera probablement fusionnée plus rapidement (en supposant que le mot `"hugging"` apparaisse souvent dans le vocabulaire) puisque `"hu"` et `"##gging"` sont probablement moins fréquents individuellement. + +Examinons le même vocabulaire que celui utilisé dans l'exemple d'entraînement du BPE : + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +Les divisions ici seront : + +``` +("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##g" "##s", 5) +``` + +Si on oublie les *tokens* spéciaux pour l'instant, le vocabulaire initial sera donc `["b", "h", "p", "##g", "##n", "##s", "##u"]`. La paire la plus fréquente est `("##u", "##g")` (présente 20 fois), mais la fréquence individuelle de `"##u"` est très élevée, donc son score n'est pas le plus élevé (il est de 1 / 36). Toutes les paires avec un `"##u"` ont en fait le même score (1 / 36). Ainsi le meilleur score va à la paire `("##g", "##s")` qui est la seule sans un `"##u"` avec un score 1 / 20. Et la première fusion apprise est `("##g", "##s") -> ("##gs")`. + +Notez que lorsque nous fusionnons, nous enlevons le `##` entre les deux *tokens*, donc nous ajoutons `"##gs"` au vocabulaire et appliquons la fusion dans les mots du 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) +``` + +À ce stade, `" ##u "` est dans toutes les paires possibles, donc elles finissent toutes par avoir le même score. Disons que dans ce cas, la première paire est fusionnée, donc `("h", "##u") -> "hu"`. Cela nous amène à : + +``` +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) +``` + +Ensuite, le meilleur score suivant est partagé par `("hu", "##g")` et `("hu", "##gs")` (avec 1/15, comparé à 1/21 pour toutes les autres paires). Ainsi la première paire avec le plus grand score est fusionnée : + +``` +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) +``` + +et nous continuons ainsi jusqu'à ce que nous atteignions la taille de vocabulaire souhaitée. + + + +✏️ **A votre tour !** Quelle sera la prochaine règle de fusion ? + + + +## Algorithme de tokenisation + +La tokénisation diffère dans *WordPiece* et BPE en ce que *WordPiece* ne sauvegarde que le vocabulaire final et non pas les règles de fusion apprises. En partant du mot à tokeniser, *WordPiece* trouve le sous-mot le plus long qui se trouve dans le vocabulaire, puis se sépare sur celui-ci. Par exemple, si nous utilisons le vocabulaire appris dans l'exemple ci-dessus, pour le mot `"hugs"` le plus long sous-mot en partant du début qui est dans le vocabulaire est `"hug"`. Donc nous le divisons et obtenons `["hug", "##s"]`. On continue avec `"##s"`, qui est dans le vocabulaire, donc la tokenisation de `"hugs"` est `["hug", "##s"]`. + +Avec BPE, nous aurions appliqué les fusions apprises dans l'ordre et la tokénisation aurait été `["hu", "##gs"]`, l'encodage est donc différent. + +Comme autre exemple, voyons comment le mot `"bugs"` serait tokenisé. `"b"` est le plus long sous-mot commençant au début du mot qui est dans le vocabulaire donc on le divise et on obtient `["b", "##ugs"]`. Ensuite, `"##u"` est le plus long sous-mot commençant au début de `"##ugs"` qui est dans le vocabulaire, donc on le sépare et on obtient `["b", "##u, "##gs"]`. Enfin, `"##gs"` est dans le vocabulaire, donc cette dernière liste est la tokenization de `"bugs"`. + +Lorsque la tokenisation arrive à un stade où il n'est pas possible de trouver un sous-mot dans le vocabulaire, le mot entier est tokenisé comme inconnu. Par exemple, `"mug"` serait tokenisé comme `["[UNK]"]`, tout comme `"bum"` (même si on peut commencer par " b " et " ##u ", " ##m " ne fait pas partie du vocabulaire, et le *tokenizer* résultant sera simplement `["[UNK]"]` " et non `["b", "##u", "[UNK]"]` "). C'est une autre différence avec le BPE qui classerait seulement les caractères individuels qui ne sont pas dans le vocabulaire comme inconnus. + + + +✏️ **A votre tour !** Comment le mot `"pugs"` sera-t-il tokenisé ? + + + +## Implémentation de WordPiece + +Voyons maintenant une implémentation de l'algorithme *WordPiece*. Comme pour le BPE, il s'agit d'un exemple pédagogique et vous ne pourrez pas l'utiliser sur un grand corpus. + +Nous utiliserons le même corpus que dans l'exemple BPE : + +```python +corpus = [ + "This is the Hugging Face Course.", + # C'est le cours d'Hugging Face. + "This chapter is about tokenization.", + # This chapter is about tokenization + "This section shows several tokenizer algorithms.", + # Cette section présente plusieurs algorithmes de tokenizer. + "Hopefully, you will be able to understand how they are trained and generate tokens.", + # Avec un peu de chance, vous serez en mesure de comprendre comment ils sont entraînés et génèrent des tokens. +] +``` + +Tout d'abord, nous devons prétokéniser le corpus en mots. Puisque nous répliquons un *tokenizer WordPiece* (comme BERT), nous utiliserons le *tokenizer* `bert-base-cased` pour la prétokénisation : + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +``` + +Ensuite, nous calculons les fréquences de chaque mot dans le corpus comme nous le faisons pour la prétokénisation : + +```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}) +``` + +Comme nous l'avons vu précédemment, l'alphabet est l'unique ensemble composé de toutes les premières lettres des mots, et de toutes les autres lettres qui apparaissent dans les mots préfixés par `##` : + +```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'] +``` + +Nous ajoutons également les *tokens* spéciaux utilisés par le modèle au début de ce vocabulaire. Dans le cas de BERT, il s'agit de la liste `["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]` : + +```python +vocab = ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"] + alphabet.copy() +``` + +Ensuite, nous devons diviser chaque mot, avec toutes les lettres qui ne sont pas les premières préfixées par `##` : + +```python +splits = { + word: [c if i == 0 else f"##{c}" for i, c in enumerate(word)] + for word in word_freqs.keys() +} +``` + +Maintenant que nous sommes prêts pour l'entraînement, écrivons une fonction qui calcule le score de chaque paire. Nous devrons l'utiliser à chaque étape de l'entraînement : + +```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 +``` + +Jetons un coup d'œil à une partie de ce dictionnaire après les premières divisions : + +```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 +``` + +Maintenant, trouver la paire avec le meilleur score ne prend qu'une rapide boucle : + +```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 +``` + +Ainsi, la première fusion à apprendre est `('a', '##b') -> 'ab'` et nous ajoutons `'ab'` au vocabulaire : + +```python +vocab.append("ab") +``` + +Pour continuer, nous devons appliquer cette fusion dans notre dictionnaire `splits`. Écrivons une autre fonction pour cela : + +```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 +``` + +Et nous pouvons regarder le résultat de la première fusion : + +```py +splits = merge_pair("a", "##b", splits) +splits["about"] +``` + +```python out +['ab', '##o', '##u', '##t'] +``` + +Nous avons maintenant tout ce dont nous avons besoin pour boucler jusqu'à ce que nous ayons appris toutes les fusions que nous voulons. Visons une taille de vocabulaire 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) +``` + +Nous pouvons ensuite examiner le vocabulaire généré : + +```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', '##fu', 'Fa', 'Fac', '##ct', '##ful', '##full', '##fully', + 'Th', 'ch', '##hm', 'cha', 'chap', 'chapt', '##thm', 'Hu', 'Hug', 'Hugg', 'sh', 'th', 'is', '##thms', '##za', '##zat', + '##ut'] +``` + +Comme nous pouvons le voir, comparé à BPE, ce *tokenizer* apprend les parties de mots comme des *tokens* un peu plus rapidement. + + + +💡 Utiliser `train_new_from_iterator()` sur le même corpus ne donnera pas exactement le même vocabulaire. C'est parce que la bibliothèque 🤗 *Tokenizers* n'implémente pas *WordPiece* pour l'entraînement (puisque nous ne sommes pas complètement sûrs de son fonctionnement interne), mais utilise le BPE à la place. + + + +Pour tokeniser un nouveau texte, on le prétokenise, on le divise, puis on applique l'algorithme de tokenisation sur chaque mot. En d'autres termes, nous recherchons le plus grand sous-mot commençant au début du premier mot et le divisons. Puis nous répétons le processus sur la deuxième partie et ainsi de suite pour le reste de ce mot et les mots suivants dans le texte : + +```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 +``` + +Testons-le sur un mot qui fait partie du vocabulaire, et un autre qui n'en fait pas partie : + +```python +print(encode_word("Hugging")) +print(encode_word("HOgging")) +``` + +```python out +['Hugg', '##i', '##n', '##g'] +['[UNK]'] +``` + +Maintenant, écrivons une fonction qui permet de tokeniser un texte : + +```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, []) +``` + +On peut l'essayer sur n'importe quel texte : + +```python +tokenize("This is the Hugging Face Course!") # C'est le cours d'Hugging Face +``` + +```python out +['Th', '##i', '##s', 'is', 'th', '##e', 'Hugg', '##i', '##n', '##g', 'Fac', '##e', 'c', '##o', '##u', '##r', '##s', + '##e', '[UNK]'] +``` + +C'est tout pour l'algorithme *WordPiece* ! Maintenant, jetons un coup d'oeil à *Unigram*. diff --git a/chapters/fr/chapter6/7.mdx b/chapters/fr/chapter6/7.mdx index bf5c970dc..3d262bbd1 100644 --- a/chapters/fr/chapter6/7.mdx +++ b/chapters/fr/chapter6/7.mdx @@ -1,385 +1,385 @@ -# Tokenisation Unigram - - - -L'algorithme *Unigram* est souvent utilisé dans *SentencePiece*, qui est l'algorithme de tokenization utilisé par des modèles comme ALBERT, T5, mBART, Big Bird et XLNet. - - - - - -💡 Cette section couvre *Unigram* en profondeur, allant jusqu'à montrer une implémentation complète. Vous pouvez passer directement à la fin si vous souhaitez simplement avoir un aperçu général de l'algorithme de tokénisation. - - - -## Algorithme d'entraînement - -Comparé au BPE et *WordPiece*, *Unigram* fonctionne dans l'autre sens : il part d'un grand vocabulaire et enlève des *tokens* jusqu'à atteindre la taille de vocabulaire désirée. Il existe plusieurs options pour construire ce vocabulaire de base. Nous pouvons par exemple prendre les sous-chaînes les plus courantes dans les mots prétokénisés ou appliquer le BPE sur le corpus initial avec une grande taille de vocabulaire. - -À 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. - -Notez que nous ne supprimons jamais les caractères de base, afin de nous assurer que tout mot peut être tokenisé. - -Tout ceci peut paraître encore un peu vague. En effet, la partie principale de l'algorithme est de calculer une perte sur le corpus et de voir comment elle change lorsque nous supprimons certains *tokens* du vocabulaire mais nous n'avons pas encore expliqué comment le faire. Cette étape repose sur l'algorithme de tokénisation *Unigram*, nous allons donc l'aborder à présent. - -Nous allons réutiliser le corpus des exemples précédents : - -``` -("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) # "câlin", "carlin", "jeu de mots", "brioche", "câlins"... -``` - -et pour cet exemple, nous prendrons toutes les sous-chaînes strictes pour le vocabulaire initial : - -``` -["h", "u", "g", "hu", "ug", "p", "pu", "n", "un", "b", "bu", "s", "hug", "gs", "ugs"] -``` - -## Algorithme de tokenisation - -Un modèle *Unigram* est un type de modèle de langage qui considère que chaque *token* est indépendant des *tokens* qui le précèdent. Il s'agit du modèle de langage le plus simple, dans le sens où la probabilité du *token* X compte tenu du contexte précédent est simplement la probabilité du *token* X. Ainsi, si nous utilisions un modèle de langage *Unigram* pour générer du texte, nous prédirions toujours le *token* le plus courant. - -La probabilité d'un *token* donné est sa fréquence (le nombre de fois que nous le trouvons) dans le corpus original, divisée par la somme de toutes les fréquences de tous les *tokens* dans le vocabulaire (pour s'assurer que la somme des probabilités est égale à 1). Par exemple, `"ug"` est présent dans `"hug"`, `"pug"`, et `"hugs"`. Il a donc une fréquence de 20 dans notre corpus. - -Voici les fréquences de tous les sous-mots possibles dans le vocabulaire : - -``` -("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) -``` - -Ainsi, la somme de toutes les fréquences est de 210 et la probabilité du sous-mot `"ug"` est donc de 20/210. - - - -✏️ **A votre tour !** Ecrivez le code permettant de calculer les fréquences ci-dessus et vérifiez que les résultats affichés sont corrects, de même que la somme totale. - - - -Maintenant, pour tokeniser un mot donné, nous examinons toutes les segmentations possibles en *tokens* et calculons la probabilité de chacune d'entre elles selon le modèle *Unigram*. Puisque tous les *tokens* sont considérés comme indépendants, cette probabilité est juste le produit de la probabilité de chaque *token*. Par exemple, la tokenisation `["p", "u", "g"]` de `"pug"` a la probabilité : - -$$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$$ - -Comparativement, la tokenization `["pu", "g"]` a la probabilité : - -$$P([``pu", ``g"]) = P(``pu") \times P(``g") = \frac{5}{210} \times \frac{20}{210} = 0.0022676$$ - -donc celle-là est beaucoup plus probable. En général, les tokénisations comportant le moins de *tokens* possible auront la probabilité la plus élevée (en raison de la division par 210 répétée pour chaque *token*), ce qui correspond à ce que nous voulons intuitivement : diviser un mot en un nombre de *tokens* le plus faible possible. - -La tokenisation d'un mot avec le modèle *Unigram* est donc la tokenisation avec la plus haute probabilité. Dans l'exemple de `"pug"`, voici les probabilités que nous obtiendrions pour chaque segmentation possible : - -``` -["p", "u", "g"] : 0.000389 -["p", "ug"] : 0.0022676 -["pu", "g"] : 0.0022676 -``` - -Ainsi, `"pug"` sera tokenisé comme `["p", "ug"]` ou `["pu", "g"]`, selon la segmentation rencontrée en premier (notez que dans un corpus plus large, les cas d'égalité comme celui-ci seront rares). - -Dans ce cas-ci, cela a été facile de trouver toutes les segmentations possibles et de calculer leurs probabilités, mais en général ce sera un peu plus difficile. Il existe un algorithme classique utilisé pour cela, appelé *algorithme de Viterbi*. Essentiellement, on peut construire un graphe pour détecter les segmentations possibles d'un mot donné en disant qu'il existe une branche du caractère _a_ au caractère _b_ si le sous-mot de _a_ à _b_ est dans le vocabulaire, et attribuer à cette branche la probabilité du sous-mot. - -Pour trouver le chemin qui va avoir le meilleur score dans ce graphe, l'algorithme de Viterbi détermine, pour chaque position dans le mot, la segmentation avec le meilleur score qui se termine à cette position. Puisque nous allons du début à la fin, ce meilleur score peut être trouvé en parcourant en boucle tous les sous-mots se terminant à la position actuelle, puis en utilisant le meilleur score de tokenization de la position à laquelle ce sous-mot commence. Ensuite, il suffit de dérouler le chemin emprunté pour arriver à la fin. - -Prenons un exemple en utilisant notre vocabulaire et le mot `"unhug"`. Pour chaque position, les sous-mots avec les meilleurs scores se terminant là sont les suivants : - -``` -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) -``` - -Ainsi, `"unhug"` serait tokenisé comme `["un", "hug"]`. - - - -✏️ **A votre tour !** Déterminer la tokenization du mot `"huggun"` et son score. - - - -## Retour à l'entraînement - -Maintenant que nous avons vu comment fonctionne la tokenisation, nous pouvons nous plonger un peu plus profondément dans la perte utilisée pendant l'entraînement. À n'importe quelle étape, cette perte est calculée en tokenisant chaque mot du corpus, en utilisant le vocabulaire courant et le modèle *Unigram* déterminé par les fréquences de chaque *token* dans le corpus (comme vu précédemment). - -Chaque mot du corpus a un score, et la perte est le négatif du logarithme de ces scores, c'est-à-dire la somme pour tous les mots du corpus de tous les `-log(P(word))`. - -Revenons à notre exemple avec le corpus suivant : - -``` -("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) -``` - -La tokenisation de chaque mot avec leurs scores respectifs est : - -``` -"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) -``` - -Donc la perte est : - -``` -10 * (-log(0.071428)) + 5 * (-log(0.007710)) + 12 * (-log(0.006168)) + 4 * (-log(0.001451)) + 5 * (-log(0.001701)) = 169.8 -``` - -Maintenant, nous devons calculer comment la suppression de chaque token affecte la perte. C'est plutôt fastidieux, donc nous allons le faire pour deux *tokens* ici et garder tout le processus pour quand nous aurons du code pour nous aider. Dans ce cas (très) particulier, nous avions deux tokenizations équivalentes de tous les mots. Par exmeple, comme nous l'avons vu précédemment, `"pug"` pourrait être tokenisé en `["p", "ug"]` avec le même score. Ainsi, enlever le token `"pu"` du vocabulaire donnera exactement la même perte. - -D'un autre côté, supprimer le mot `"hug"` aggravera la perte, car la tokenisation de `"hug"` et `"hugs"` deviendra : - -``` -"hug": ["hu", "g"] (score 0.006802) -"hugs": ["hu", "gs"] (score 0.001701) -``` - -Ces changements entraîneront une augmentation de la perte de : - -``` -- 10 * (-log(0.071428)) + 10 * (-log(0.006802)) = 23.5 -``` - -Par conséquent, le token `"pu"` sera probablement retiré du vocabulaire, mais pas `"hug"`. - -## Implémentation d'Unigram - -Maintenant, implémentons tout ce que nous avons vu jusqu'à présent dans le code. Comme pour le BPE et *WordPiece*, ce n'est pas une implémentation efficace de l'algorithme *Unigram* (bien au contraire), mais elle devrait vous aider à le comprendre un peu mieux. - -Nous allons utiliser le même corpus que précédemment comme exemple : - -```python -corpus = [ - "This is the Hugging Face Course.", - # C'est le cours d'Hugging Face. - "This chapter is about tokenization.", - # Ce chapitre traite de la tokenisation. - "This section shows several tokenizer algorithms.", - # Cette section présente plusieurs algorithmes de *tokenizer*. - "Hopefully, you will be able to understand how they are trained and generate tokens.", - # Avec un peu de chance, vous serez en mesure de comprendre comment ils sont entraînés et génèrent des *tokens*. -] -``` - -Cette fois, nous allons utiliser `xlnet-base-cased` comme modèle : - -```python -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained("xlnet-base-cased") -``` - -Comme pour le BPE et *WordPiece*, nous commençons par compter le nombre d'occurrences de chaque mot dans le 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 -``` - -Ensuite, nous devons initialiser notre vocabulaire à une taille plus grande que celle du vocabulaire que nous voudrons à la fin. Nous devons inclure tous les caractères de base (sinon nous ne serons pas en mesure de tokeniser chaque mot), mais pour les sous-chaînes plus grandes, nous ne garderons que les plus communs. AInsi nous les trions par fréquence : - -```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 - # Boucle à travers les sous-mots de longueur au moins égale à 2 - for j in range(i + 2, len(word) + 1): - subwords_freqs[word[i:j]] += freq - -# Trier les sous-mots par fréquence -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)] -``` - -Nous regroupons les caractères avec les meilleurs sous-mots pour arriver à un vocabulaire initial de taille 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* utilise un algorithme plus efficace appelé *Enhanced Suffix Array* (ESA) pour créer le vocabulaire initial. - - - -Ensuite, nous calculons la somme de toutes les fréquences, pour convertir les fréquences en probabilités. Pour notre modèle, nous allons stocker les logarithmes des probabilités, car c'est plus stable numériquement d'additionner des logarithmes que de multiplier des petits nombres. Cela simplifiera aussi le calcul de la perte du modèle : - -```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()} -``` - -Maintenant la fonction principale est celle qui tokenise les mots en utilisant l'algorithme de Viterbi. Comme nous l'avons vu précédemment, cet algorithme calcule la meilleure segmentation de chaque sous-chaîne du mot que nous allons stocker dans une variable nommée `best_segmentations`. Nous allons stocker un dictionnaire par position dans le mot (de 0 à sa longueur totale), avec deux clés : l'index du début du dernier *token* dans la meilleure segmentation et le score de la meilleure segmentation. Avec l'index du début du dernier *token*, nous serons en mesure de récupérer la segmentation complète une fois que la liste est complètement remplie. - -Le remplissage de la liste se fait à l'aide de deux boucles seulement : la boucle principale passe en revue chaque position de départ et la seconde boucle essaie toutes les sous-chaînes commençant à cette position de départ. Si la sous-chaîne est dans le vocabulaire, nous avons une nouvelle segmentation du mot jusqu'à cette position finale que nous comparons à ce qui est dans `best_segmentations`. - -Une fois que la boucle principale est terminée, nous commençons juste à la fin et sautons d'une position de départ à une autre, en enregistrant les *tokens* au fur et à mesure, jusqu'à ce que nous atteignions le début du mot : - -```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)): - # Doit être correctement rempli par les étapes précédentes de la boucle - 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 - # Si nous avons trouvé une meilleure segmentation se terminant à end_idx, nous mettons à jour - 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: - # Nous n'avons pas trouvé de tokenization du mot -> inconnu () - 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 -``` - -Nous pouvons déjà essayer notre modèle initial sur quelques mots : - -```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) -``` - -Il est maintenant facile de calculer la perte du modèle sur le 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 -``` - -Nous pouvons vérifier que cela fonctionne sur le modèle que nous avons : - -```python -compute_loss(model) -``` - -```python out -413.10377642940875 -``` - -Le calcul des scores pour chaque *token* n'est pas très difficile non plus. Il suffit de calculer la perte pour les modèles obtenus en supprimant chaque *token* : - -```python -import copy - - -def compute_scores(model): - scores = {} - model_loss = compute_loss(model) - for token, score in model.items(): - # Nous gardons toujours les tokens de longueur 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 -``` - -Nous pouvons l'essayer sur un *token* donné : - -```python -scores = compute_scores(model) -print(scores["ll"]) -print(scores["his"]) -``` - -Puisque `"ll"` est utilisé dans la tokenisation de `"Hopefully"`, et que le supprimer nous fera probablement utiliser le token `"l"` deux fois à la place, nous nous attendons à ce qu'il ait une perte positive. `"his"` n'est utilisé qu'à l'intérieur du mot `"This"`, qui est tokenisé comme lui-même, donc nous nous attendons à ce qu'il ait une perte nulle. Voici les résultats : - -```python out -6.376412403623874 -0.0 -``` - - - -💡 Cette approche est très inefficace, c'est pourquoi *SentencePiece* utilise une approximation de la perte du modèle sans le *token* X. Au lieu de partir de zéro, il remplace simplement le *token* X par sa segmentation dans le vocabulaire restant. De cette façon, tous les scores peuvent être calculés en une seule fois, en même temps que la perte du modèle. - - - -Une fois tout cela en place, la dernière chose à faire est d'ajouter les *tokens* spéciaux utilisés par le modèle au vocabulaire, puis de boucler jusqu'à ce que nous ayons élagué suffisamment de *tokens* du vocabulaire pour atteindre la taille souhaitée : - -```python -percent_to_remove = 0.1 -while len(model) > 100: - scores = compute_scores(model) - sorted_scores = sorted(scores.items(), key=lambda x: x[1]) - # Supprime les tokens percent_to_remove ayant les scores les plus bas - 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()} -``` - -Ensuite, pour tokeniser un texte, il suffit d'appliquer la prétokénisation et d'utiliser la fonction `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', '.'] -``` - -C'est tout pour *Unigram* ! Avec un peu de chance, vous vous sentez à présent être un expert des *tokenizers*. Dans la prochaine section, nous allons nous plonger dans les blocs de construction de la bibliothèque 🤗 *Tokenizers* et allons vous montrer comment vous pouvez les utiliser pour construire votre propre *tokenizer*. +# Tokenisation Unigram + + + +L'algorithme *Unigram* est souvent utilisé dans *SentencePiece*, qui est l'algorithme de tokenization utilisé par des modèles comme ALBERT, T5, mBART, Big Bird et XLNet. + + + + + +💡 Cette section couvre *Unigram* en profondeur, allant jusqu'à montrer une implémentation complète. Vous pouvez passer directement à la fin si vous souhaitez simplement avoir un aperçu général de l'algorithme de tokénisation. + + + +## Algorithme d'entraînement + +Comparé au BPE et *WordPiece*, *Unigram* fonctionne dans l'autre sens : il part d'un grand vocabulaire et enlève des *tokens* jusqu'à atteindre la taille de vocabulaire désirée. Il existe plusieurs options pour construire ce vocabulaire de base. Nous pouvons par exemple prendre les sous-chaînes les plus courantes dans les mots prétokénisés ou appliquer le BPE sur le corpus initial avec une grande taille de vocabulaire. + +À 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. + +Notez que nous ne supprimons jamais les caractères de base, afin de nous assurer que tout mot peut être tokenisé. + +Tout ceci peut paraître encore un peu vague. En effet, la partie principale de l'algorithme est de calculer une perte sur le corpus et de voir comment elle change lorsque nous supprimons certains *tokens* du vocabulaire mais nous n'avons pas encore expliqué comment le faire. Cette étape repose sur l'algorithme de tokénisation *Unigram*, nous allons donc l'aborder à présent. + +Nous allons réutiliser le corpus des exemples précédents : + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) # "câlin", "carlin", "jeu de mots", "brioche", "câlins"... +``` + +et pour cet exemple, nous prendrons toutes les sous-chaînes strictes pour le vocabulaire initial : + +``` +["h", "u", "g", "hu", "ug", "p", "pu", "n", "un", "b", "bu", "s", "hug", "gs", "ugs"] +``` + +## Algorithme de tokenisation + +Un modèle *Unigram* est un type de modèle de langage qui considère que chaque *token* est indépendant des *tokens* qui le précèdent. Il s'agit du modèle de langage le plus simple, dans le sens où la probabilité du *token* X compte tenu du contexte précédent est simplement la probabilité du *token* X. Ainsi, si nous utilisions un modèle de langage *Unigram* pour générer du texte, nous prédirions toujours le *token* le plus courant. + +La probabilité d'un *token* donné est sa fréquence (le nombre de fois que nous le trouvons) dans le corpus original, divisée par la somme de toutes les fréquences de tous les *tokens* dans le vocabulaire (pour s'assurer que la somme des probabilités est égale à 1). Par exemple, `"ug"` est présent dans `"hug"`, `"pug"`, et `"hugs"`. Il a donc une fréquence de 20 dans notre corpus. + +Voici les fréquences de tous les sous-mots possibles dans le vocabulaire : + +``` +("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) +``` + +Ainsi, la somme de toutes les fréquences est de 210 et la probabilité du sous-mot `"ug"` est donc de 20/210. + + + +✏️ **A votre tour !** Ecrivez le code permettant de calculer les fréquences ci-dessus et vérifiez que les résultats affichés sont corrects, de même que la somme totale. + + + +Maintenant, pour tokeniser un mot donné, nous examinons toutes les segmentations possibles en *tokens* et calculons la probabilité de chacune d'entre elles selon le modèle *Unigram*. Puisque tous les *tokens* sont considérés comme indépendants, cette probabilité est juste le produit de la probabilité de chaque *token*. Par exemple, la tokenisation `["p", "u", "g"]` de `"pug"` a la probabilité : + +$$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$$ + +Comparativement, la tokenization `["pu", "g"]` a la probabilité : + +$$P([``pu", ``g"]) = P(``pu") \times P(``g") = \frac{5}{210} \times \frac{20}{210} = 0.0022676$$ + +donc celle-là est beaucoup plus probable. En général, les tokénisations comportant le moins de *tokens* possible auront la probabilité la plus élevée (en raison de la division par 210 répétée pour chaque *token*), ce qui correspond à ce que nous voulons intuitivement : diviser un mot en un nombre de *tokens* le plus faible possible. + +La tokenisation d'un mot avec le modèle *Unigram* est donc la tokenisation avec la plus haute probabilité. Dans l'exemple de `"pug"`, voici les probabilités que nous obtiendrions pour chaque segmentation possible : + +``` +["p", "u", "g"] : 0.000389 +["p", "ug"] : 0.0022676 +["pu", "g"] : 0.0022676 +``` + +Ainsi, `"pug"` sera tokenisé comme `["p", "ug"]` ou `["pu", "g"]`, selon la segmentation rencontrée en premier (notez que dans un corpus plus large, les cas d'égalité comme celui-ci seront rares). + +Dans ce cas-ci, cela a été facile de trouver toutes les segmentations possibles et de calculer leurs probabilités, mais en général ce sera un peu plus difficile. Il existe un algorithme classique utilisé pour cela, appelé *algorithme de Viterbi*. Essentiellement, on peut construire un graphe pour détecter les segmentations possibles d'un mot donné en disant qu'il existe une branche du caractère _a_ au caractère _b_ si le sous-mot de _a_ à _b_ est dans le vocabulaire, et attribuer à cette branche la probabilité du sous-mot. + +Pour trouver le chemin qui va avoir le meilleur score dans ce graphe, l'algorithme de Viterbi détermine, pour chaque position dans le mot, la segmentation avec le meilleur score qui se termine à cette position. Puisque nous allons du début à la fin, ce meilleur score peut être trouvé en parcourant en boucle tous les sous-mots se terminant à la position actuelle, puis en utilisant le meilleur score de tokenization de la position à laquelle ce sous-mot commence. Ensuite, il suffit de dérouler le chemin emprunté pour arriver à la fin. + +Prenons un exemple en utilisant notre vocabulaire et le mot `"unhug"`. Pour chaque position, les sous-mots avec les meilleurs scores se terminant là sont les suivants : + +``` +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) +``` + +Ainsi, `"unhug"` serait tokenisé comme `["un", "hug"]`. + + + +✏️ **A votre tour !** Déterminer la tokenization du mot `"huggun"` et son score. + + + +## Retour à l'entraînement + +Maintenant que nous avons vu comment fonctionne la tokenisation, nous pouvons nous plonger un peu plus profondément dans la perte utilisée pendant l'entraînement. À n'importe quelle étape, cette perte est calculée en tokenisant chaque mot du corpus, en utilisant le vocabulaire courant et le modèle *Unigram* déterminé par les fréquences de chaque *token* dans le corpus (comme vu précédemment). + +Chaque mot du corpus a un score, et la perte est le négatif du logarithme de ces scores, c'est-à-dire la somme pour tous les mots du corpus de tous les `-log(P(word))`. + +Revenons à notre exemple avec le corpus suivant : + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +La tokenisation de chaque mot avec leurs scores respectifs est : + +``` +"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) +``` + +Donc la perte est : + +``` +10 * (-log(0.071428)) + 5 * (-log(0.007710)) + 12 * (-log(0.006168)) + 4 * (-log(0.001451)) + 5 * (-log(0.001701)) = 169.8 +``` + +Maintenant, nous devons calculer comment la suppression de chaque token affecte la perte. C'est plutôt fastidieux, donc nous allons le faire pour deux *tokens* ici et garder tout le processus pour quand nous aurons du code pour nous aider. Dans ce cas (très) particulier, nous avions deux tokenizations équivalentes de tous les mots. Par exmeple, comme nous l'avons vu précédemment, `"pug"` pourrait être tokenisé en `["p", "ug"]` avec le même score. Ainsi, enlever le token `"pu"` du vocabulaire donnera exactement la même perte. + +D'un autre côté, supprimer le mot `"hug"` aggravera la perte, car la tokenisation de `"hug"` et `"hugs"` deviendra : + +``` +"hug": ["hu", "g"] (score 0.006802) +"hugs": ["hu", "gs"] (score 0.001701) +``` + +Ces changements entraîneront une augmentation de la perte de : + +``` +- 10 * (-log(0.071428)) + 10 * (-log(0.006802)) = 23.5 +``` + +Par conséquent, le token `"pu"` sera probablement retiré du vocabulaire, mais pas `"hug"`. + +## Implémentation d'Unigram + +Maintenant, implémentons tout ce que nous avons vu jusqu'à présent dans le code. Comme pour le BPE et *WordPiece*, ce n'est pas une implémentation efficace de l'algorithme *Unigram* (bien au contraire), mais elle devrait vous aider à le comprendre un peu mieux. + +Nous allons utiliser le même corpus que précédemment comme exemple : + +```python +corpus = [ + "This is the Hugging Face Course.", + # C'est le cours d'Hugging Face. + "This chapter is about tokenization.", + # Ce chapitre traite de la tokenisation. + "This section shows several tokenizer algorithms.", + # Cette section présente plusieurs algorithmes de *tokenizer*. + "Hopefully, you will be able to understand how they are trained and generate tokens.", + # Avec un peu de chance, vous serez en mesure de comprendre comment ils sont entraînés et génèrent des *tokens*. +] +``` + +Cette fois, nous allons utiliser `xlnet-base-cased` comme modèle : + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("xlnet-base-cased") +``` + +Comme pour le BPE et *WordPiece*, nous commençons par compter le nombre d'occurrences de chaque mot dans le 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 +``` + +Ensuite, nous devons initialiser notre vocabulaire à une taille plus grande que celle du vocabulaire que nous voudrons à la fin. Nous devons inclure tous les caractères de base (sinon nous ne serons pas en mesure de tokeniser chaque mot), mais pour les sous-chaînes plus grandes, nous ne garderons que les plus communs. AInsi nous les trions par fréquence : + +```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 + # Boucle à travers les sous-mots de longueur au moins égale à 2 + for j in range(i + 2, len(word) + 1): + subwords_freqs[word[i:j]] += freq + +# Trier les sous-mots par fréquence +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)] +``` + +Nous regroupons les caractères avec les meilleurs sous-mots pour arriver à un vocabulaire initial de taille 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* utilise un algorithme plus efficace appelé *Enhanced Suffix Array* (ESA) pour créer le vocabulaire initial. + + + +Ensuite, nous calculons la somme de toutes les fréquences, pour convertir les fréquences en probabilités. Pour notre modèle, nous allons stocker les logarithmes des probabilités, car c'est plus stable numériquement d'additionner des logarithmes que de multiplier des petits nombres. Cela simplifiera aussi le calcul de la perte du modèle : + +```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()} +``` + +Maintenant la fonction principale est celle qui tokenise les mots en utilisant l'algorithme de Viterbi. Comme nous l'avons vu précédemment, cet algorithme calcule la meilleure segmentation de chaque sous-chaîne du mot que nous allons stocker dans une variable nommée `best_segmentations`. Nous allons stocker un dictionnaire par position dans le mot (de 0 à sa longueur totale), avec deux clés : l'index du début du dernier *token* dans la meilleure segmentation et le score de la meilleure segmentation. Avec l'index du début du dernier *token*, nous serons en mesure de récupérer la segmentation complète une fois que la liste est complètement remplie. + +Le remplissage de la liste se fait à l'aide de deux boucles seulement : la boucle principale passe en revue chaque position de départ et la seconde boucle essaie toutes les sous-chaînes commençant à cette position de départ. Si la sous-chaîne est dans le vocabulaire, nous avons une nouvelle segmentation du mot jusqu'à cette position finale que nous comparons à ce qui est dans `best_segmentations`. + +Une fois que la boucle principale est terminée, nous commençons juste à la fin et sautons d'une position de départ à une autre, en enregistrant les *tokens* au fur et à mesure, jusqu'à ce que nous atteignions le début du mot : + +```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)): + # Doit être correctement rempli par les étapes précédentes de la boucle + 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 + # Si nous avons trouvé une meilleure segmentation se terminant à end_idx, nous mettons à jour + 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: + # Nous n'avons pas trouvé de tokenization du mot -> inconnu () + 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 +``` + +Nous pouvons déjà essayer notre modèle initial sur quelques mots : + +```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) +``` + +Il est maintenant facile de calculer la perte du modèle sur le 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 +``` + +Nous pouvons vérifier que cela fonctionne sur le modèle que nous avons : + +```python +compute_loss(model) +``` + +```python out +413.10377642940875 +``` + +Le calcul des scores pour chaque *token* n'est pas très difficile non plus. Il suffit de calculer la perte pour les modèles obtenus en supprimant chaque *token* : + +```python +import copy + + +def compute_scores(model): + scores = {} + model_loss = compute_loss(model) + for token, score in model.items(): + # Nous gardons toujours les tokens de longueur 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 +``` + +Nous pouvons l'essayer sur un *token* donné : + +```python +scores = compute_scores(model) +print(scores["ll"]) +print(scores["his"]) +``` + +Puisque `"ll"` est utilisé dans la tokenisation de `"Hopefully"`, et que le supprimer nous fera probablement utiliser le token `"l"` deux fois à la place, nous nous attendons à ce qu'il ait une perte positive. `"his"` n'est utilisé qu'à l'intérieur du mot `"This"`, qui est tokenisé comme lui-même, donc nous nous attendons à ce qu'il ait une perte nulle. Voici les résultats : + +```python out +6.376412403623874 +0.0 +``` + + + +💡 Cette approche est très inefficace, c'est pourquoi *SentencePiece* utilise une approximation de la perte du modèle sans le *token* X. Au lieu de partir de zéro, il remplace simplement le *token* X par sa segmentation dans le vocabulaire restant. De cette façon, tous les scores peuvent être calculés en une seule fois, en même temps que la perte du modèle. + + + +Une fois tout cela en place, la dernière chose à faire est d'ajouter les *tokens* spéciaux utilisés par le modèle au vocabulaire, puis de boucler jusqu'à ce que nous ayons élagué suffisamment de *tokens* du vocabulaire pour atteindre la taille souhaitée : + +```python +percent_to_remove = 0.1 +while len(model) > 100: + scores = compute_scores(model) + sorted_scores = sorted(scores.items(), key=lambda x: x[1]) + # Supprime les tokens percent_to_remove ayant les scores les plus bas + 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()} +``` + +Ensuite, pour tokeniser un texte, il suffit d'appliquer la prétokénisation et d'utiliser la fonction `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', '.'] +``` + +C'est tout pour *Unigram* ! Avec un peu de chance, vous vous sentez à présent être un expert des *tokenizers*. Dans la prochaine section, nous allons nous plonger dans les blocs de construction de la bibliothèque 🤗 *Tokenizers* et allons vous montrer comment vous pouvez les utiliser pour construire votre propre *tokenizer*. diff --git a/chapters/fr/chapter6/8.mdx b/chapters/fr/chapter6/8.mdx index 46440deb7..79c0a58a8 100644 --- a/chapters/fr/chapter6/8.mdx +++ b/chapters/fr/chapter6/8.mdx @@ -1,566 +1,566 @@ -# Construction d'un tokenizer, bloc par bloc - - - -Comme nous l'avons vu dans les sections précédentes, la tokenisation comprend plusieurs étapes : - -- normalisation (tout nettoyage du texte jugé nécessaire, comme la suppression des espaces ou des accents, la normalisation Unicode, etc.), -- prétokénisation (division de l'entrée en mots), -- passage de l'entrée dans le modèle (utilisation des mots prétokénisés pour produire une séquence de *tokens*), -- post-traitement (ajout des *tokens* spéciaux du *tokenizer*, génération du masque d'attention et des identifiants du type de *token*). - -Pour mémoire, voici un autre aperçu du processus global : - -
-The tokenization pipeline. - -
- -La bibliothèque 🤗 *Tokenizers* a été construite pour fournir plusieurs options pour chacune de ces étapes. Vous pouvez les mélanger et assortir ensemble. Dans cette section, nous verrons comment nous pouvons construire un *tokenizer* à partir de zéro, par opposition à entraîner un nouveau *tokenizer* à partir d'un ancien, comme nous l'avons fait dans [section 2](/course/fr/chapter6/2). Vous serez alors en mesure de construire n'importe quel type de *tokenizer* auquel vous pouvez penser ! - - - -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). - -## Acquisition d'un corpus - -Pour entraîner notre nouveau *tokenizer*, nous utiliserons un petit corpus de texte (pour que les exemples soient rapides). Les étapes pour acquérir ce corpus sont similaires à celles que nous avons suivies au [début du chapitre](/course/fr/chapter6/2), mais cette fois nous utiliserons le jeu de données [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 fonction `get_training_corpus()` est un générateur qui donne des batchs de 1 000 textes, que nous utiliserons pour entraîner le *tokenizer*. - -🤗 *Tokenizers* peut aussi être entraîné directement sur des fichiers texte. Voici comment nous pouvons générer un fichier texte contenant tous les textes de WikiText-2 que nous pourrons ensuite utilisé en local : - -```python -with open("wikitext-2.txt", "w", encoding="utf-8") as f: - for i in range(len(dataset)): - f.write(dataset[i]["text"] + "\n") -``` - -Ensuite, nous vous montrerons comment construire vos propres *tokenizers* pour BERT, GPT-2 et XLNet, bloc par bloc. Cela vous donnera un exemple de chacun des trois principaux algorithmes de tokenisation : *WordPiece*, BPE et *Unigram*. Commençons par BERT ! - -## Construire un tokenizer WordPiece à partir de zéro - -Pour construire un *tokenizer* avec la bibliothèque 🤗 *Tokenizers*, nous commençons par instancier un objet `Tokenizer` avec un `model`. Puis nous définissons ses attributs `normalizer`, `pre_tokenizer`, `post_processor` et `decoder` aux valeurs que nous voulons. - -Pour cet exemple, nous allons créer un `Tokenizer` avec un modèle *WordPiece* : - -```python -from tokenizers import ( - decoders, - models, - normalizers, - pre_tokenizers, - processors, - trainers, - Tokenizer, -) - -tokenizer = Tokenizer(models.WordPiece(unk_token="[UNK]")) -``` - -Nous devons spécifier le `unk_token` pour que le modèle sache quoi retourner lorsqu'il rencontre des caractères qu'il n'a pas vu auparavant. D'autres arguments que nous pouvons définir ici incluent le `vocab` de notre modèle (nous allons entraîner le modèle, donc nous n'avons pas besoin de le définir) et `max_input_chars_per_word`, qui spécifie une longueur maximale pour chaque mot (les mots plus longs que la valeur passée seront séparés). - -La première étape de la tokénisation est la normalisation. Puisque BERT est largement utilisé, une fonction `BertNormalizer` a été créée avec les options classiques que nous pouvons définir pour BERT : `lowercase` pour mettre le texte en minuscule, `strip_accents` qui enlève les accents, `clean_text` pour enlever tous les caractères de contrôle et fusionner des espaces répétés par un seul, et `handle_chinese_chars` qui place des espaces autour des caractères chinois. Pour reproduire le *tokenizer* `bert-base-uncased`, nous pouvons simplement définir ce *normalizer* : - -```python -tokenizer.normalizer = normalizers.BertNormalizer(lowercase=True) -``` - -Cependant, généralement, lorsque vous construisez un nouveau *tokenizer*, vous n'avez pas accès à un normaliseur aussi pratique déjà implémenté dans la bibliothèque 🤗 *Tokenizers*. Donc voyons comment créer le normaliseur de BERT manuellement. La bibliothèque fournit un normaliseur `Lowercase` et un normaliseur `StripAccents`. Il est possible de composer plusieurs normaliseurs en utilisant une `Sequence` : - -```python -tokenizer.normalizer = normalizers.Sequence( - [normalizers.NFD(), normalizers.Lowercase(), normalizers.StripAccents()] -) -``` - -Nous utilisons également un normaliseur Unicode `NFD`, car sinon `StripAccents` ne reconnaîtra pas correctement les caractères accentués et ne les supprimera donc pas. - -Comme nous l'avons vu précédemment, nous pouvons utiliser la méthode `normalize_str()` du `normalizer` pour vérifier les effets qu'il a sur un texte donné : - -```python -print(tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) -``` - -```python out -hello how are u? -``` - - - -**Pour aller plus loin** Si vous testez les deux versions des normaliseurs précédents sur une chaîne contenant le caractère unicode `u"\u0085"` vous remarquerez sûrement qu'ils ne sont pas exactement équivalents. -Pour ne pas trop compliquer la version avec `normalizers.Sequence`, nous n'avons pas inclus les Regex que le `BertNormalizer` requiert quand l'argument `clean_text` est mis à `True` ce qui est le comportement par défaut. Mais ne vous inquiétez pas : il est possible d'obtenir exactement la même normalisation sans utiliser le très pratique `BertNormalizer` en ajoutant deux `normalizers.Replace` à la séquence de normalisation. - - - -L'étape suivante est la prétokenisation. Encore une fois, il y a un `BertPreTokenizer` préconstruit que nous pouvons utiliser : - -```python -tokenizer.pre_tokenizer = pre_tokenizers.BertPreTokenizer() -``` - -Ou nous pouvons le construire à partir de zéro : - -```python -tokenizer.pre_tokenizer = pre_tokenizers.Whitespace() -``` - -Notez que le `Whitespace` divise sur les espaces et tous les caractères qui ne sont pas des lettres, des chiffres ou le caractère de soulignement. Donc techniquement il divise sur les espaces et la ponctuation : - -```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 vous voulez seulement séparer sur les espaces, vous devez utiliser `WhitespaceSplit` à la place : - -```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))] -``` - -Comme pour les normaliseurs, vous pouvez utiliser une `Sequence` pour composer plusieurs prétokenizers : - -```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))] -``` - -L'étape suivante dans le pipeline de tokénisation est de faire passer les entrées par le modèle. Nous avons déjà spécifié notre modèle dans l'initialisation, mais nous devons encore l'entraîner, ce qui nécessitera un `WordPieceTrainer`. La principale chose à retenir lors de l'instanciation d'un entraîneur dans 🤗 *Tokenizers* est que vous devez lui passer tous les *tokens* spéciaux que vous avez l'intention d'utiliser. Sinon il ne les ajoutera pas au vocabulaire puisqu'ils ne sont pas dans le corpus d'entraînement : - -```python -special_tokens = ["[UNK]", "[PAD]", "[CLS]", "[SEP]", "[MASK]"] -trainer = trainers.WordPieceTrainer(vocab_size=25000, special_tokens=special_tokens) -``` - -En plus de spécifier la `vocab_size` et les `special_tokens`, nous pouvons définir la `min_frequency` (le nombre de fois qu'un *token* doit apparaître pour être inclus dans le vocabulaire) ou changer le `continuing_subword_prefix` (si nous voulons utiliser quelque chose de différent de `##`). - -Pour entraîner notre modèle en utilisant l'itérateur que nous avons défini plus tôt, il suffit d'exécuter cette commande : - -```python -tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) -``` - -Nous pouvons également utiliser des fichiers texte pour entraîner notre *tokenizer* qui ressemblerait alors à ceci (nous réinitialisons le modèle avec un `WordPiece` vide au préalable) : - -```python -tokenizer.model = models.WordPiece(unk_token="[UNK]") -tokenizer.train(["wikitext-2.txt"], trainer=trainer) -``` - -Dans les deux cas, nous pouvons ensuite tester le *tokenizer* sur un texte en appelant la méthode `encode()` : - -```python -encoding = tokenizer.encode("Let's test this tokenizer.") -print(encoding.tokens) -``` - -```python out -['let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.'] -``` - -L'encodage obtenu est un `Encoding` contenant toutes les sorties nécessaires du *tokenizer* dans ses différents attributs : `ids`, `type_ids`, `tokens`, `offsets`, `attention_mask`, `special_tokens_mask` et `overflowing`. - -La dernière étape du pipeline de tokénisation est le post-traitement. Nous devons ajouter le *token* `[CLS]` au début et le *token* `[SEP]` à la fin (ou après chaque phrase si nous avons une paire de phrases). Nous utiliserons `TemplateProcessor` pour cela, mais d'abord nous devons connaître les identifiants des *tokens* `[CLS]` et `[SEP]` dans le vocabulaire : - -```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) -``` - -Pour écrire le gabarit pour `TemplateProcessor`, nous devons spécifier comment traiter une seule phrase et une paire de phrases. Pour les deux, nous écrivons les *tokens* spéciaux que nous voulons utiliser. La première (ou unique) phrase est représentée par `$A`, alors que la deuxième phrase (si on code une paire) est représentée par `$B`. Pour chacun de ces éléments (*tokens* spéciaux et phrases), nous spécifions également l'identifiant du *token* correspondant après un deux-points. - -Le gabarit classique de BERT est donc défini comme suit : - -```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)], -) -``` - -Notez que nous devons transmettre les identifiants des *tokens* spéciaux afin que le *tokenizer* puisse les convertir correctement. - -Une fois cela ajouté, revenons à notre exemple précédent donnera : - -```python -encoding = tokenizer.encode("Let's test this tokenizer.") -print(encoding.tokens) -``` - -```python out -['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.', '[SEP]'] -``` - -Et sur une paire de phrases, on obtient le bon résultat : - -```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] -``` - -Nous avons presque fini de construire ce *tokenizer* à partir de zéro. La dernière étape consiste à inclure un décodeur : - -```python -tokenizer.decoder = decoders.WordPiece(prefix="##") -``` - -Testons-le sur notre précédent `encoding` : - -```python -tokenizer.decode(encoding.ids) -``` - -```python out -"let's test this tokenizer... on a pair of sentences." # Testons ce tokenizer... sur une paire de phrases. -``` - -Génial ! Nous pouvons enregistrer notre *tokenizer* dans un seul fichier JSON comme ceci : - -```python -tokenizer.save("tokenizer.json") -``` - -Nous pouvons alors recharger ce fichier dans un objet `Tokenizer` avec la méthode `from_file()` : - -```python -new_tokenizer = Tokenizer.from_file("tokenizer.json") -``` - -Pour utiliser ce *tokenizer* dans 🤗 *Transformers*, nous devons l'envelopper dans un `PreTrainedTokenizerFast`. Nous pouvons soit utiliser la classe générique, soit, si notre *tokenizer* correspond à un modèle existant, utiliser cette classe (ici, `BertTokenizerFast`). Si vous appliquez cette logique pour construire un tout nouveau *tokenizer*, vous devrez utiliser la première option. - -Pour envelopper le *tokenizer* dans un `PreTrainedTokenizerFast`, nous pouvons soit passer le *tokenizer* que nous avons construit comme un `tokenizer_object`, soit passer le fichier de *tokenizer* que nous avons sauvegardé comme `tokenizer_file`. Ce qu'il faut retenir, c'est que nous devons définir manuellement tous les *tokens* spéciaux car cette classe ne peut pas déduire de l'objet `tokenizer` quel *token* est le *token* de masque, quel est le *token*`[CLS]`, etc : - -```python -from transformers import PreTrainedTokenizerFast - -wrapped_tokenizer = PreTrainedTokenizerFast( - tokenizer_object=tokenizer, - # tokenizer_file="tokenizer.json", # Vous pouvez charger à partir du fichier du tokenizer, alternativement - unk_token="[UNK]", - pad_token="[PAD]", - cls_token="[CLS]", - sep_token="[SEP]", - mask_token="[MASK]", -) -``` - -Si vous utilisez une classe de *tokenizer* spécifique (comme `BertTokenizerFast`), vous aurez seulement besoin de spécifier les *tokens* spéciaux qui sont différents de ceux par défaut (ici, aucun) : - -```python -from transformers import BertTokenizerFast - -wrapped_tokenizer = BertTokenizerFast(tokenizer_object=tokenizer) -``` - -Vous pouvez ensuite utiliser ce *tokenizer* comme n'importe quel autre *tokenizer* de 🤗 *Transformers*. Vous pouvez le sauvegarder avec la méthode `save_pretrained()` ou le télécharger sur le *Hub* avec la méthode `push_to_hub()`. - -Maintenant que nous avons vu comment construire un *tokenizer WordPiece*, faisons de même pour un *tokenizer* BPE. Nous irons un peu plus vite puisque vous connaissez toutes les étapes. Nous ne soulignerons que les différences. - -## Construire un tokenizer BPE à partir de zéro - -Construisons maintenant un *tokenizer* BPE. Comme pour le *tokenizer* BERT, nous commençons par initialiser un `Tokenizer` avec un modèle BPE : - -```python -tokenizer = Tokenizer(models.BPE()) -``` - -Comme pour BERT, nous pourrions initialiser ce modèle avec un vocabulaire si nous en avions un (nous aurions besoin de passer le `vocab` et le `merges` dans ce cas), mais puisque nous allons nous entraîner à partir de zéro, nous n'avons pas besoin de le faire. Nous n'avons pas non plus besoin de spécifier un `unk_token` parce que le GPT-2 utilise un BPE au niveau de l'octet. - -GPT-2 n'utilise pas de normaliseur, donc nous sautons cette étape et allons directement à la prétokénisation : - -```python -tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False) -``` - -L'option que nous avons ajoutée à `ByteLevel` ici est de ne pas ajouter d'espace en début de phrase (ce qui est le cas par défaut). Nous pouvons jeter un coup d'oeil à la prétokénisation d'un texte d'exemple comme avant : - -```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))] -``` - -Vient ensuite le modèle, qui doit être entraîné. Pour le GPT-2, le seul *token* spécial est le *token* de fin de texte : - -```python -trainer = trainers.BpeTrainer(vocab_size=25000, special_tokens=["<|endoftext|>"]) -tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) -``` - -Comme avec le `WordPieceTrainer`, ainsi que le `vocab_size` et le `special_tokens`, nous pouvons spécifier la `min_frequency` si nous le voulons, ou si nous avons un suffixe de fin de mot (comme ``), nous pouvons le définir avec `end_of_word_suffix`. - -Ce *tokenizer* peut aussi être entraîné sur des fichiers texte : - -```python -tokenizer.model = models.BPE() -tokenizer.train(["wikitext-2.txt"], trainer=trainer) -``` - -Regardons la tokenisation d'un exemple de texte : - -```python -encoding = tokenizer.encode("Let's test this tokenizer.") -print(encoding.tokens) -``` - -```python out -['L', 'et', "'", 's', 'Ġtest', 'Ġthis', 'Ġto', 'ken', 'izer', '.'] -``` - -Nous appliquons le post-traitement au niveau de l'octet pour le *tokenizer* du GPT-2 comme suit : - -```python -tokenizer.post_processor = processors.ByteLevel(trim_offsets=False) -``` - -L'option `trim_offsets = False` indique au post-processeur que nous devons laisser les *offsets* des *tokens* qui commencent par 'Ġ' tels quels : de cette façon, le début des *offsets* pointera sur l'espace avant le mot, et non sur le premier caractère du mot (puisque l'espace fait techniquement partie du *token*). Regardons le résultat avec le texte que nous venons de coder, où `'Ġtest'` est le *token* à l'index 4 : - -```python -sentence = "Let's test this tokenizer." -encoding = tokenizer.encode(sentence) -start, end = encoding.offsets[4] -sentence[start:end] -``` - -```python out -' test' -``` - -Enfin, nous ajoutons un décodeur au niveau de l'octet : - -```python -tokenizer.decoder = decoders.ByteLevel() -``` - -et nous pouvons vérifier qu'il fonctionne correctement : - -```python -tokenizer.decode(encoding.ids) -``` - -```python out -"Let's test this tokenizer." # Testons ce tokenizer -``` - -Super ! Maintenant que nous avons terminé, nous pouvons sauvegarder le tokenizer comme avant, et l'envelopper dans un `PreTrainedTokenizerFast` ou un `GPT2TokenizerFast` si nous voulons l'utiliser dans 🤗 *Transformers* : - -```python -from transformers import PreTrainedTokenizerFast - -wrapped_tokenizer = PreTrainedTokenizerFast( - tokenizer_object=tokenizer, - bos_token="<|endoftext|>", - eos_token="<|endoftext|>", -) -``` - -ou : - -```python -from transformers import GPT2TokenizerFast - -wrapped_tokenizer = GPT2TokenizerFast(tokenizer_object=tokenizer) -``` - -Comme dernier exemple, nous allons vous montrer comment construire un *tokenizer* *Unigram* à partir de zéro. - -## Construire un tokenizer Unigram à partir de zéro - -Construisons maintenant un *tokenizer* XLNet. Comme pour les *tokenizers* précédents, nous commençons par initialiser un `Tokenizer` avec un modèle *Unigram* : - -```python -tokenizer = Tokenizer(models.Unigram()) -``` - -Encore une fois, nous pourrions initialiser ce modèle avec un vocabulaire si nous en avions un. - -Pour la normalisation, XLNet utilise quelques remplacements (qui proviennent de *SentencePiece*) : - -```python -from tokenizers import Regex - -tokenizer.normalizer = normalizers.Sequence( - [ - normalizers.Replace("``", '"'), - normalizers.Replace("''", '"'), - normalizers.NFKD(), - normalizers.StripAccents(), - normalizers.Replace(Regex(" {2,}"), " "), - ] -) -``` - -Il remplace `` et '' par " et toute séquence de deux espaces ou plus par un seul espace, de plus il supprime les accents. - -Le prétokenizer à utiliser pour tout *tokenizer SentencePiece* est `Metaspace` : - -```python -tokenizer.pre_tokenizer = pre_tokenizers.Metaspace() -``` - -Nous pouvons jeter un coup d'oeil à la prétokénisation sur le même exemple de texte que précédemment : - -```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))] -``` - -Vient ensuite le modèle, qui doit être entraîné. XLNet possède un certain nombre de *tokens* spéciaux : - -```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 argument très important à ne pas oublier pour le `UnigramTrainer` est le `unk_token`. Nous pouvons aussi passer d'autres arguments spécifiques à l'algorithme *Unigram*, comme le `shrinking_factor` pour chaque étape où nous enlevons des *tokens* (par défaut 0.75) ou le `max_piece_length` pour spécifier la longueur maximale d'un *token* donné (par défaut 16). - -Ce *tokenizer* peut aussi être entraîné sur des fichiers texte : - -```python -tokenizer.model = models.Unigram() -tokenizer.train(["wikitext-2.txt"], trainer=trainer) -``` - -Regardons la tokenisation de notre exemple : - -```python -encoding = tokenizer.encode("Let's test this tokenizer.") -print(encoding.tokens) -``` - -```python out -['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.'] -``` - -Une particularité de XLNet est qu'il place le *token* `` à la fin de la phrase, avec un identifiant de 2 (pour le distinguer des autres *tokens*). Le résultat est un remplissage à gauche. Nous pouvons traiter tous les *tokens* spéciaux et les types d'identifiant de *token* avec un modèle, comme pour BERT. Mais d'abord nous devons obtenir les identifiants des *tokens* `` et `` : - -```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 -``` - -Le modèle ressemble à ceci : - -```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)], -) -``` - -Et nous pouvons tester son fonctionnement en codant une paire de phrases : - -```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] -``` - -Enfin, nous ajoutons un décodeur `Metaspace` : - -```python -tokenizer.decoder = decoders.Metaspace() -``` - -et on en a fini avec ce *tokenizer* ! On peut le sauvegarder et l'envelopper dans un `PreTrainedTokenizerFast` ou `XLNetTokenizerFast` si on veut l'utiliser dans 🤗 *Transformers*. Une chose à noter lors de l'utilisation de `PreTrainedTokenizerFast` est qu'en plus des *tokens* spéciaux, nous devons dire à la bibliothèque 🤗 *Transformers* de rembourrer à gauche : - -```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", -) -``` - -Ou alternativement : - -```python -from transformers import XLNetTokenizerFast - -wrapped_tokenizer = XLNetTokenizerFast(tokenizer_object=tokenizer) -``` - -Maintenant que vous avez vu comment les différentes briques sont utilisées pour construire des *tokenizers* existants, vous devriez être capable d'écrire n'importe quel *tokenizer* que vous voulez avec la bibliothèque 🤗 *Tokenizers* et pouvoir l'utiliser dans 🤗 *Transformers*. +# Construction d'un tokenizer, bloc par bloc + + + +Comme nous l'avons vu dans les sections précédentes, la tokenisation comprend plusieurs étapes : + +- normalisation (tout nettoyage du texte jugé nécessaire, comme la suppression des espaces ou des accents, la normalisation Unicode, etc.), +- prétokénisation (division de l'entrée en mots), +- passage de l'entrée dans le modèle (utilisation des mots prétokénisés pour produire une séquence de *tokens*), +- post-traitement (ajout des *tokens* spéciaux du *tokenizer*, génération du masque d'attention et des identifiants du type de *token*). + +Pour mémoire, voici un autre aperçu du processus global : + +
+The tokenization pipeline. + +
+ +La bibliothèque 🤗 *Tokenizers* a été construite pour fournir plusieurs options pour chacune de ces étapes. Vous pouvez les mélanger et assortir ensemble. Dans cette section, nous verrons comment nous pouvons construire un *tokenizer* à partir de zéro, par opposition à entraîner un nouveau *tokenizer* à partir d'un ancien, comme nous l'avons fait dans [section 2](/course/fr/chapter6/2). Vous serez alors en mesure de construire n'importe quel type de *tokenizer* auquel vous pouvez penser ! + + + +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). + +## Acquisition d'un corpus + +Pour entraîner notre nouveau *tokenizer*, nous utiliserons un petit corpus de texte (pour que les exemples soient rapides). Les étapes pour acquérir ce corpus sont similaires à celles que nous avons suivies au [début du chapitre](/course/fr/chapter6/2), mais cette fois nous utiliserons le jeu de données [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 fonction `get_training_corpus()` est un générateur qui donne des batchs de 1 000 textes, que nous utiliserons pour entraîner le *tokenizer*. + +🤗 *Tokenizers* peut aussi être entraîné directement sur des fichiers texte. Voici comment nous pouvons générer un fichier texte contenant tous les textes de WikiText-2 que nous pourrons ensuite utilisé en local : + +```python +with open("wikitext-2.txt", "w", encoding="utf-8") as f: + for i in range(len(dataset)): + f.write(dataset[i]["text"] + "\n") +``` + +Ensuite, nous vous montrerons comment construire vos propres *tokenizers* pour BERT, GPT-2 et XLNet, bloc par bloc. Cela vous donnera un exemple de chacun des trois principaux algorithmes de tokenisation : *WordPiece*, BPE et *Unigram*. Commençons par BERT ! + +## Construire un tokenizer WordPiece à partir de zéro + +Pour construire un *tokenizer* avec la bibliothèque 🤗 *Tokenizers*, nous commençons par instancier un objet `Tokenizer` avec un `model`. Puis nous définissons ses attributs `normalizer`, `pre_tokenizer`, `post_processor` et `decoder` aux valeurs que nous voulons. + +Pour cet exemple, nous allons créer un `Tokenizer` avec un modèle *WordPiece* : + +```python +from tokenizers import ( + decoders, + models, + normalizers, + pre_tokenizers, + processors, + trainers, + Tokenizer, +) + +tokenizer = Tokenizer(models.WordPiece(unk_token="[UNK]")) +``` + +Nous devons spécifier le `unk_token` pour que le modèle sache quoi retourner lorsqu'il rencontre des caractères qu'il n'a pas vu auparavant. D'autres arguments que nous pouvons définir ici incluent le `vocab` de notre modèle (nous allons entraîner le modèle, donc nous n'avons pas besoin de le définir) et `max_input_chars_per_word`, qui spécifie une longueur maximale pour chaque mot (les mots plus longs que la valeur passée seront séparés). + +La première étape de la tokénisation est la normalisation. Puisque BERT est largement utilisé, une fonction `BertNormalizer` a été créée avec les options classiques que nous pouvons définir pour BERT : `lowercase` pour mettre le texte en minuscule, `strip_accents` qui enlève les accents, `clean_text` pour enlever tous les caractères de contrôle et fusionner des espaces répétés par un seul, et `handle_chinese_chars` qui place des espaces autour des caractères chinois. Pour reproduire le *tokenizer* `bert-base-uncased`, nous pouvons simplement définir ce *normalizer* : + +```python +tokenizer.normalizer = normalizers.BertNormalizer(lowercase=True) +``` + +Cependant, généralement, lorsque vous construisez un nouveau *tokenizer*, vous n'avez pas accès à un normaliseur aussi pratique déjà implémenté dans la bibliothèque 🤗 *Tokenizers*. Donc voyons comment créer le normaliseur de BERT manuellement. La bibliothèque fournit un normaliseur `Lowercase` et un normaliseur `StripAccents`. Il est possible de composer plusieurs normaliseurs en utilisant une `Sequence` : + +```python +tokenizer.normalizer = normalizers.Sequence( + [normalizers.NFD(), normalizers.Lowercase(), normalizers.StripAccents()] +) +``` + +Nous utilisons également un normaliseur Unicode `NFD`, car sinon `StripAccents` ne reconnaîtra pas correctement les caractères accentués et ne les supprimera donc pas. + +Comme nous l'avons vu précédemment, nous pouvons utiliser la méthode `normalize_str()` du `normalizer` pour vérifier les effets qu'il a sur un texte donné : + +```python +print(tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) +``` + +```python out +hello how are u? +``` + + + +**Pour aller plus loin** Si vous testez les deux versions des normaliseurs précédents sur une chaîne contenant le caractère unicode `u"\u0085"` vous remarquerez sûrement qu'ils ne sont pas exactement équivalents. +Pour ne pas trop compliquer la version avec `normalizers.Sequence`, nous n'avons pas inclus les Regex que le `BertNormalizer` requiert quand l'argument `clean_text` est mis à `True` ce qui est le comportement par défaut. Mais ne vous inquiétez pas : il est possible d'obtenir exactement la même normalisation sans utiliser le très pratique `BertNormalizer` en ajoutant deux `normalizers.Replace` à la séquence de normalisation. + + + +L'étape suivante est la prétokenisation. Encore une fois, il y a un `BertPreTokenizer` préconstruit que nous pouvons utiliser : + +```python +tokenizer.pre_tokenizer = pre_tokenizers.BertPreTokenizer() +``` + +Ou nous pouvons le construire à partir de zéro : + +```python +tokenizer.pre_tokenizer = pre_tokenizers.Whitespace() +``` + +Notez que le `Whitespace` divise sur les espaces et tous les caractères qui ne sont pas des lettres, des chiffres ou le caractère de soulignement. Donc techniquement il divise sur les espaces et la ponctuation : + +```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 vous voulez seulement séparer sur les espaces, vous devez utiliser `WhitespaceSplit` à la place : + +```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))] +``` + +Comme pour les normaliseurs, vous pouvez utiliser une `Sequence` pour composer plusieurs prétokenizers : + +```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))] +``` + +L'étape suivante dans le pipeline de tokénisation est de faire passer les entrées par le modèle. Nous avons déjà spécifié notre modèle dans l'initialisation, mais nous devons encore l'entraîner, ce qui nécessitera un `WordPieceTrainer`. La principale chose à retenir lors de l'instanciation d'un entraîneur dans 🤗 *Tokenizers* est que vous devez lui passer tous les *tokens* spéciaux que vous avez l'intention d'utiliser. Sinon il ne les ajoutera pas au vocabulaire puisqu'ils ne sont pas dans le corpus d'entraînement : + +```python +special_tokens = ["[UNK]", "[PAD]", "[CLS]", "[SEP]", "[MASK]"] +trainer = trainers.WordPieceTrainer(vocab_size=25000, special_tokens=special_tokens) +``` + +En plus de spécifier la `vocab_size` et les `special_tokens`, nous pouvons définir la `min_frequency` (le nombre de fois qu'un *token* doit apparaître pour être inclus dans le vocabulaire) ou changer le `continuing_subword_prefix` (si nous voulons utiliser quelque chose de différent de `##`). + +Pour entraîner notre modèle en utilisant l'itérateur que nous avons défini plus tôt, il suffit d'exécuter cette commande : + +```python +tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) +``` + +Nous pouvons également utiliser des fichiers texte pour entraîner notre *tokenizer* qui ressemblerait alors à ceci (nous réinitialisons le modèle avec un `WordPiece` vide au préalable) : + +```python +tokenizer.model = models.WordPiece(unk_token="[UNK]") +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +Dans les deux cas, nous pouvons ensuite tester le *tokenizer* sur un texte en appelant la méthode `encode()` : + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.'] +``` + +L'encodage obtenu est un `Encoding` contenant toutes les sorties nécessaires du *tokenizer* dans ses différents attributs : `ids`, `type_ids`, `tokens`, `offsets`, `attention_mask`, `special_tokens_mask` et `overflowing`. + +La dernière étape du pipeline de tokénisation est le post-traitement. Nous devons ajouter le *token* `[CLS]` au début et le *token* `[SEP]` à la fin (ou après chaque phrase si nous avons une paire de phrases). Nous utiliserons `TemplateProcessor` pour cela, mais d'abord nous devons connaître les identifiants des *tokens* `[CLS]` et `[SEP]` dans le vocabulaire : + +```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) +``` + +Pour écrire le gabarit pour `TemplateProcessor`, nous devons spécifier comment traiter une seule phrase et une paire de phrases. Pour les deux, nous écrivons les *tokens* spéciaux que nous voulons utiliser. La première (ou unique) phrase est représentée par `$A`, alors que la deuxième phrase (si on code une paire) est représentée par `$B`. Pour chacun de ces éléments (*tokens* spéciaux et phrases), nous spécifions également l'identifiant du *token* correspondant après un deux-points. + +Le gabarit classique de BERT est donc défini comme suit : + +```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)], +) +``` + +Notez que nous devons transmettre les identifiants des *tokens* spéciaux afin que le *tokenizer* puisse les convertir correctement. + +Une fois cela ajouté, revenons à notre exemple précédent donnera : + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.', '[SEP]'] +``` + +Et sur une paire de phrases, on obtient le bon résultat : + +```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] +``` + +Nous avons presque fini de construire ce *tokenizer* à partir de zéro. La dernière étape consiste à inclure un décodeur : + +```python +tokenizer.decoder = decoders.WordPiece(prefix="##") +``` + +Testons-le sur notre précédent `encoding` : + +```python +tokenizer.decode(encoding.ids) +``` + +```python out +"let's test this tokenizer... on a pair of sentences." # Testons ce tokenizer... sur une paire de phrases. +``` + +Génial ! Nous pouvons enregistrer notre *tokenizer* dans un seul fichier JSON comme ceci : + +```python +tokenizer.save("tokenizer.json") +``` + +Nous pouvons alors recharger ce fichier dans un objet `Tokenizer` avec la méthode `from_file()` : + +```python +new_tokenizer = Tokenizer.from_file("tokenizer.json") +``` + +Pour utiliser ce *tokenizer* dans 🤗 *Transformers*, nous devons l'envelopper dans un `PreTrainedTokenizerFast`. Nous pouvons soit utiliser la classe générique, soit, si notre *tokenizer* correspond à un modèle existant, utiliser cette classe (ici, `BertTokenizerFast`). Si vous appliquez cette logique pour construire un tout nouveau *tokenizer*, vous devrez utiliser la première option. + +Pour envelopper le *tokenizer* dans un `PreTrainedTokenizerFast`, nous pouvons soit passer le *tokenizer* que nous avons construit comme un `tokenizer_object`, soit passer le fichier de *tokenizer* que nous avons sauvegardé comme `tokenizer_file`. Ce qu'il faut retenir, c'est que nous devons définir manuellement tous les *tokens* spéciaux car cette classe ne peut pas déduire de l'objet `tokenizer` quel *token* est le *token* de masque, quel est le *token*`[CLS]`, etc : + +```python +from transformers import PreTrainedTokenizerFast + +wrapped_tokenizer = PreTrainedTokenizerFast( + tokenizer_object=tokenizer, + # tokenizer_file="tokenizer.json", # Vous pouvez charger à partir du fichier du tokenizer, alternativement + unk_token="[UNK]", + pad_token="[PAD]", + cls_token="[CLS]", + sep_token="[SEP]", + mask_token="[MASK]", +) +``` + +Si vous utilisez une classe de *tokenizer* spécifique (comme `BertTokenizerFast`), vous aurez seulement besoin de spécifier les *tokens* spéciaux qui sont différents de ceux par défaut (ici, aucun) : + +```python +from transformers import BertTokenizerFast + +wrapped_tokenizer = BertTokenizerFast(tokenizer_object=tokenizer) +``` + +Vous pouvez ensuite utiliser ce *tokenizer* comme n'importe quel autre *tokenizer* de 🤗 *Transformers*. Vous pouvez le sauvegarder avec la méthode `save_pretrained()` ou le télécharger sur le *Hub* avec la méthode `push_to_hub()`. + +Maintenant que nous avons vu comment construire un *tokenizer WordPiece*, faisons de même pour un *tokenizer* BPE. Nous irons un peu plus vite puisque vous connaissez toutes les étapes. Nous ne soulignerons que les différences. + +## Construire un tokenizer BPE à partir de zéro + +Construisons maintenant un *tokenizer* BPE. Comme pour le *tokenizer* BERT, nous commençons par initialiser un `Tokenizer` avec un modèle BPE : + +```python +tokenizer = Tokenizer(models.BPE()) +``` + +Comme pour BERT, nous pourrions initialiser ce modèle avec un vocabulaire si nous en avions un (nous aurions besoin de passer le `vocab` et le `merges` dans ce cas), mais puisque nous allons nous entraîner à partir de zéro, nous n'avons pas besoin de le faire. Nous n'avons pas non plus besoin de spécifier un `unk_token` parce que le GPT-2 utilise un BPE au niveau de l'octet. + +GPT-2 n'utilise pas de normaliseur, donc nous sautons cette étape et allons directement à la prétokénisation : + +```python +tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False) +``` + +L'option que nous avons ajoutée à `ByteLevel` ici est de ne pas ajouter d'espace en début de phrase (ce qui est le cas par défaut). Nous pouvons jeter un coup d'oeil à la prétokénisation d'un texte d'exemple comme avant : + +```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))] +``` + +Vient ensuite le modèle, qui doit être entraîné. Pour le GPT-2, le seul *token* spécial est le *token* de fin de texte : + +```python +trainer = trainers.BpeTrainer(vocab_size=25000, special_tokens=["<|endoftext|>"]) +tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) +``` + +Comme avec le `WordPieceTrainer`, ainsi que le `vocab_size` et le `special_tokens`, nous pouvons spécifier la `min_frequency` si nous le voulons, ou si nous avons un suffixe de fin de mot (comme ``), nous pouvons le définir avec `end_of_word_suffix`. + +Ce *tokenizer* peut aussi être entraîné sur des fichiers texte : + +```python +tokenizer.model = models.BPE() +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +Regardons la tokenisation d'un exemple de texte : + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['L', 'et', "'", 's', 'Ġtest', 'Ġthis', 'Ġto', 'ken', 'izer', '.'] +``` + +Nous appliquons le post-traitement au niveau de l'octet pour le *tokenizer* du GPT-2 comme suit : + +```python +tokenizer.post_processor = processors.ByteLevel(trim_offsets=False) +``` + +L'option `trim_offsets = False` indique au post-processeur que nous devons laisser les *offsets* des *tokens* qui commencent par 'Ġ' tels quels : de cette façon, le début des *offsets* pointera sur l'espace avant le mot, et non sur le premier caractère du mot (puisque l'espace fait techniquement partie du *token*). Regardons le résultat avec le texte que nous venons de coder, où `'Ġtest'` est le *token* à l'index 4 : + +```python +sentence = "Let's test this tokenizer." +encoding = tokenizer.encode(sentence) +start, end = encoding.offsets[4] +sentence[start:end] +``` + +```python out +' test' +``` + +Enfin, nous ajoutons un décodeur au niveau de l'octet : + +```python +tokenizer.decoder = decoders.ByteLevel() +``` + +et nous pouvons vérifier qu'il fonctionne correctement : + +```python +tokenizer.decode(encoding.ids) +``` + +```python out +"Let's test this tokenizer." # Testons ce tokenizer +``` + +Super ! Maintenant que nous avons terminé, nous pouvons sauvegarder le tokenizer comme avant, et l'envelopper dans un `PreTrainedTokenizerFast` ou un `GPT2TokenizerFast` si nous voulons l'utiliser dans 🤗 *Transformers* : + +```python +from transformers import PreTrainedTokenizerFast + +wrapped_tokenizer = PreTrainedTokenizerFast( + tokenizer_object=tokenizer, + bos_token="<|endoftext|>", + eos_token="<|endoftext|>", +) +``` + +ou : + +```python +from transformers import GPT2TokenizerFast + +wrapped_tokenizer = GPT2TokenizerFast(tokenizer_object=tokenizer) +``` + +Comme dernier exemple, nous allons vous montrer comment construire un *tokenizer* *Unigram* à partir de zéro. + +## Construire un tokenizer Unigram à partir de zéro + +Construisons maintenant un *tokenizer* XLNet. Comme pour les *tokenizers* précédents, nous commençons par initialiser un `Tokenizer` avec un modèle *Unigram* : + +```python +tokenizer = Tokenizer(models.Unigram()) +``` + +Encore une fois, nous pourrions initialiser ce modèle avec un vocabulaire si nous en avions un. + +Pour la normalisation, XLNet utilise quelques remplacements (qui proviennent de *SentencePiece*) : + +```python +from tokenizers import Regex + +tokenizer.normalizer = normalizers.Sequence( + [ + normalizers.Replace("``", '"'), + normalizers.Replace("''", '"'), + normalizers.NFKD(), + normalizers.StripAccents(), + normalizers.Replace(Regex(" {2,}"), " "), + ] +) +``` + +Il remplace `` et '' par " et toute séquence de deux espaces ou plus par un seul espace, de plus il supprime les accents. + +Le prétokenizer à utiliser pour tout *tokenizer SentencePiece* est `Metaspace` : + +```python +tokenizer.pre_tokenizer = pre_tokenizers.Metaspace() +``` + +Nous pouvons jeter un coup d'oeil à la prétokénisation sur le même exemple de texte que précédemment : + +```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))] +``` + +Vient ensuite le modèle, qui doit être entraîné. XLNet possède un certain nombre de *tokens* spéciaux : + +```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 argument très important à ne pas oublier pour le `UnigramTrainer` est le `unk_token`. Nous pouvons aussi passer d'autres arguments spécifiques à l'algorithme *Unigram*, comme le `shrinking_factor` pour chaque étape où nous enlevons des *tokens* (par défaut 0.75) ou le `max_piece_length` pour spécifier la longueur maximale d'un *token* donné (par défaut 16). + +Ce *tokenizer* peut aussi être entraîné sur des fichiers texte : + +```python +tokenizer.model = models.Unigram() +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +Regardons la tokenisation de notre exemple : + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.'] +``` + +Une particularité de XLNet est qu'il place le *token* `` à la fin de la phrase, avec un identifiant de 2 (pour le distinguer des autres *tokens*). Le résultat est un remplissage à gauche. Nous pouvons traiter tous les *tokens* spéciaux et les types d'identifiant de *token* avec un modèle, comme pour BERT. Mais d'abord nous devons obtenir les identifiants des *tokens* `` et `` : + +```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 +``` + +Le modèle ressemble à ceci : + +```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)], +) +``` + +Et nous pouvons tester son fonctionnement en codant une paire de phrases : + +```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] +``` + +Enfin, nous ajoutons un décodeur `Metaspace` : + +```python +tokenizer.decoder = decoders.Metaspace() +``` + +et on en a fini avec ce *tokenizer* ! On peut le sauvegarder et l'envelopper dans un `PreTrainedTokenizerFast` ou `XLNetTokenizerFast` si on veut l'utiliser dans 🤗 *Transformers*. Une chose à noter lors de l'utilisation de `PreTrainedTokenizerFast` est qu'en plus des *tokens* spéciaux, nous devons dire à la bibliothèque 🤗 *Transformers* de rembourrer à gauche : + +```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", +) +``` + +Ou alternativement : + +```python +from transformers import XLNetTokenizerFast + +wrapped_tokenizer = XLNetTokenizerFast(tokenizer_object=tokenizer) +``` + +Maintenant que vous avez vu comment les différentes briques sont utilisées pour construire des *tokenizers* existants, vous devriez être capable d'écrire n'importe quel *tokenizer* que vous voulez avec la bibliothèque 🤗 *Tokenizers* et pouvoir l'utiliser dans 🤗 *Transformers*. diff --git a/chapters/fr/chapter6/9.mdx b/chapters/fr/chapter6/9.mdx index 11e4beeab..2d586dc7b 100644 --- a/chapters/fr/chapter6/9.mdx +++ b/chapters/fr/chapter6/9.mdx @@ -1,11 +1,16 @@ -# Tokenizer, coché ! - -Bon travail pour finir ce chapitre ! - -Après cette plongée en profondeur dans les *tokenizers*, vous devriez : - -- être capable d'entraîner un nouveau tokenizer en utilisant un ancien tokenizer comme modèle, -- comprendre comment utiliser les *offsets* pour faire correspondre la position des *tokens* à l'étendue de texte d'origine, -- connaître les différences entre BPE, *WordPiece* et *Unigram*, -- être capable de combiner les blocs fournis par la bibliothèque 🤗 *Tokenizers* pour construire votre propre *tokenizer*, -- être capable d'utiliser ce *tokenizer* dans la bibliothèque 🤗 *Transformers*. +# Tokenizer, coché ! + + + +Bon travail pour finir ce chapitre ! + +Après cette plongée en profondeur dans les *tokenizers*, vous devriez : + +- être capable d'entraîner un nouveau tokenizer en utilisant un ancien tokenizer comme modèle, +- comprendre comment utiliser les *offsets* pour faire correspondre la position des *tokens* à l'étendue de texte d'origine, +- connaître les différences entre BPE, *WordPiece* et *Unigram*, +- être capable de combiner les blocs fournis par la bibliothèque 🤗 *Tokenizers* pour construire votre propre *tokenizer*, +- être capable d'utiliser ce *tokenizer* dans la bibliothèque 🤗 *Transformers*. diff --git a/chapters/fr/chapter7/1.mdx b/chapters/fr/chapter7/1.mdx index 49c7a7a84..603659553 100644 --- a/chapters/fr/chapter7/1.mdx +++ b/chapters/fr/chapter7/1.mdx @@ -1,33 +1,38 @@ - - -# Introduction - -Dans le [chapitre 3](/course/fr/chapter3), vous avez vu comment *finetuner* un modèle de classification de texte. Dans ce chapitre, nous nous attaquons aux tâches de NLP courantes suivantes : - -- la classification de *tokens*, -- la modélisation du langage masqué (comme BERT), -- les résumés, -- la traduction, -- le pré-entraînement à la modélisation causale du langage (comme GPT-2), -- la réponse aux questions. - -{#if fw === 'pt'} - -Pour ce faire, vous devrez tirer parti de tout ce que vous avez appris sur l'API `Trainer`, sur la bibliothèque 🤗 *Accelerate* au [chapitre 3](/course/fr/chapitre3), sur la bibliothèque 🤗 *Datasets* au [chapitre 5](/course/fr/chapiter5) et sur la bibliothèque 🤗 *Tokenizers* au [chapitre 6](/course/fr/chapiter6). Nous téléchargerons également nos résultats sur le *Hub*, comme nous l'avons fait dans le [chapitre 4](/course/fr/chapiter4), donc c'est vraiment le chapitre où tout est réuni ! - -Chaque section peut être lue indépendamment et vous montrera comment entraîner un modèle avec l'API `Trainer` ou avec 🤗 *Accelerate* et votre propre boucle d'entraînement. N'hésitez pas à sauter l'une ou l'autre partie et à vous concentrer sur celle qui vous intéresse le plus. L'API `Trainer` est idéale pour *finetuner* ou entraîner votre modèle sans vous soucier de ce qui se passe en coulisses, tandis que la boucle d'entraînement avec `Accelerate` vous permettra de personnaliser plus facilement toutes les parties que vous souhaitez. - -{:else} - -Pour ce faire, vous devrez tirer parti de tout ce que vous avez appris sur l'entraînement des modèles avec l'API Keras dans le [chapitre 3](/course/fr/chapiter3), sur la bibliothèque 🤗 *Accelerate* au [chapitre 3](/course/fr/chapitre3), sur la bibliothèque 🤗 *Datasets* au [chapitre 5](/course/fr/chapiter5) et sur la bibliothèque 🤗 *Tokenizers* au [chapitre 6](/course/fr/chapiter6). Nous téléchargerons également nos résultats sur le *Hub*, comme nous l'avons fait dans le [chapitre 4](/course/fr/chapiter4), donc c'est vraiment le chapitre où tout est réuni ! - -Chaque section peut être lue indépendamment. - -{/if} - - - - -Si vous lisez les sections dans l'ordre, vous remarquerez qu'elles ont beaucoup de code et de prose en commun. La répétition est intentionnelle, afin de vous permettre de vous plonger (ou de revenir plus tard) dans une tâche qui vous intéresse et de trouver un exemple fonctionnel complet. - - + + +# Introduction + + + +Dans le [chapitre 3](/course/fr/chapter3), vous avez vu comment *finetuner* un modèle de classification de texte. Dans ce chapitre, nous nous attaquons aux tâches de NLP courantes suivantes : + +- la classification de *tokens*, +- la modélisation du langage masqué (comme BERT), +- les résumés, +- la traduction, +- le pré-entraînement à la modélisation causale du langage (comme GPT-2), +- la réponse aux questions. + +{#if fw === 'pt'} + +Pour ce faire, vous devrez tirer parti de tout ce que vous avez appris sur l'API `Trainer`, sur la bibliothèque 🤗 *Accelerate* au [chapitre 3](/course/fr/chapitre3), sur la bibliothèque 🤗 *Datasets* au [chapitre 5](/course/fr/chapiter5) et sur la bibliothèque 🤗 *Tokenizers* au [chapitre 6](/course/fr/chapiter6). Nous téléchargerons également nos résultats sur le *Hub*, comme nous l'avons fait dans le [chapitre 4](/course/fr/chapiter4), donc c'est vraiment le chapitre où tout est réuni ! + +Chaque section peut être lue indépendamment et vous montrera comment entraîner un modèle avec l'API `Trainer` ou avec 🤗 *Accelerate* et votre propre boucle d'entraînement. N'hésitez pas à sauter l'une ou l'autre partie et à vous concentrer sur celle qui vous intéresse le plus. L'API `Trainer` est idéale pour *finetuner* ou entraîner votre modèle sans vous soucier de ce qui se passe en coulisses, tandis que la boucle d'entraînement avec `Accelerate` vous permettra de personnaliser plus facilement toutes les parties que vous souhaitez. + +{:else} + +Pour ce faire, vous devrez tirer parti de tout ce que vous avez appris sur l'entraînement des modèles avec l'API Keras dans le [chapitre 3](/course/fr/chapiter3), sur la bibliothèque 🤗 *Accelerate* au [chapitre 3](/course/fr/chapitre3), sur la bibliothèque 🤗 *Datasets* au [chapitre 5](/course/fr/chapiter5) et sur la bibliothèque 🤗 *Tokenizers* au [chapitre 6](/course/fr/chapiter6). Nous téléchargerons également nos résultats sur le *Hub*, comme nous l'avons fait dans le [chapitre 4](/course/fr/chapiter4), donc c'est vraiment le chapitre où tout est réuni ! + +Chaque section peut être lue indépendamment. + +{/if} + + + + +Si vous lisez les sections dans l'ordre, vous remarquerez qu'elles ont beaucoup de code et de prose en commun. La répétition est intentionnelle, afin de vous permettre de vous plonger (ou de revenir plus tard) dans une tâche qui vous intéresse et de trouver un exemple fonctionnel complet. + + diff --git a/chapters/fr/chapter7/2.mdx b/chapters/fr/chapter7/2.mdx index 921f9629f..8a4cdc83a 100644 --- a/chapters/fr/chapter7/2.mdx +++ b/chapters/fr/chapter7/2.mdx @@ -1,982 +1,982 @@ - - -# Classification de tokens - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -La première application que nous allons explorer est la classification de *tokens*. Cette tâche générique englobe tous les problèmes qui peuvent être formulés comme l'attribution d'une étiquette à chaque *token* d'une phrase, tels que : - -- la **reconnaissance d'entités nommées (NER de l'anglais *Named Entity Recognition*)**, c'est-à-dire trouver les entités (telles que des personnes, des lieux ou des organisations) dans une phrase. Ce tâche peut être formulée comme l'attribution d'une étiquette à chaque *token* faisant parti d'une entité en ayant une classe spécifique par entité, et une classe pour les *tokens* ne faisant pas parti d'entité. -- le ***part-of-speech tagging* (POS)**, c'est-à-dire marquer chaque mot dans une phrase comme correspondant à une partie particulière (comme un nom, un verbe, un adjectif, etc.). -- le ***chunking***, c'est-à-dire trouver les *tokens* qui appartiennent à la même entité. Cette tâche (qui peut être combinée avec le POS ou la NER) peut être formulée comme l'attribution d'une étiquette (habituellement `B-`) à tous les *tokens* qui sont au début d'un morceau, une autre étiquette (habituellement `I-`) aux *tokens* qui sont à l'intérieur d'un morceau, et une troisième étiquette (habituellement `O`) aux *tokens* qui n'appartiennent à aucun morceau. - - - -Bien sûr, il existe de nombreux autres types de problèmes de classification de *tokens*. Ce ne sont là que quelques exemples représentatifs. Dans cette section, nous allons *finetuner* un modèle (BERT) sur la tâche de NER. Il sera alors capable de calculer des prédictions comme celle-ci : - - - - - -One-hot encoded labels for question answering. - - - -Vous pouvez trouver, télécharger et vérifier les précisions de ce modèle sur le [*Hub*](https://huggingface.co/huggingface-course/bert-finetuned-ner?text=My+nom+est+Sylvain+et+je+travaille+à+Hugging+Face+in+Brooklyn) les prédictions du modèle que nous allons entraîner. - -## Préparation des données - -Tout d'abord, nous avons besoin d'un jeu de données adapté à la classification des *tokens*. Dans cette section, nous utiliserons le jeu de données [CoNLL-2003](https://huggingface.co/datasets/conll2003), qui contient des articles de presse de Reuters. - - - -💡 Tant que votre jeu de données consiste en des textes divisés en mots avec leurs étiquettes correspondantes, vous pourrez adapter les procédures de traitement des données décrites ici à votre propre jeu de données. Reportez-vous au [chapitre 5](/course/fr/chapter5) si vous avez besoin d'un rafraîchissement sur la façon de charger vos propres données personnalisées dans un `Dataset`. - - - -### Le jeu de données CoNLL-2003 - -Pour charger le jeu de données CoNLL-2003, nous utilisons la méthode `load_dataset()` de la bibliothèque 🤗 *Datasets* : - -```py -from datasets import load_dataset - -raw_datasets = load_dataset("conll2003") -``` - -Cela va télécharger et mettre en cache le jeu de données, comme nous l'avons vu dans [chapitre 3](/course/fr/chapter3) pour le jeu de données GLUE MRPC. L'inspection de cet objet nous montre les colonnes présentes dans ce jeu de données et la répartition entre les ensembles d'entraînement, de validation et de test : - -```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 - }) -}) -``` - -En particulier, nous pouvons voir que le jeu de données contient des étiquettes pour les trois tâches que nous avons mentionnées précédemment : NER, POS et *chunking*. Une grande différence avec les autres jeux de données est que les entrées textuelles ne sont pas présentés comme des phrases ou des documents, mais comme des listes de mots (la dernière colonne est appelée `tokens`, mais elle contient des mots dans le sens où ce sont des entrées prétokénisées qui doivent encore passer par le *tokenizer* pour la tokenisation en sous-mots). - -Regardons le premier élément de l'ensemble d'entraînement : - -```py -raw_datasets["train"][0]["tokens"] -``` - -```python out -['EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'lamb', '.'] -``` - -Puisque nous voulons effectuer reconnaître des entités nommées, nous allons examiner les balises NER : - -```py -raw_datasets["train"][0]["ner_tags"] -``` - -```python out -[3, 0, 7, 0, 0, 0, 7, 0, 0] -``` - -Ce sont les étiquettes sous forme d'entiers disponibles pour l'entraînement mais ne sont pas nécessairement utiles lorsque nous voulons inspecter les données. Comme pour la classification de texte, nous pouvons accéder à la correspondance entre ces entiers et les noms des étiquettes en regardant l'attribut `features` de notre jeu de données : - -```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) -``` - -Cette colonne contient donc des éléments qui sont des séquences de `ClassLabel`. Le type des éléments de la séquence se trouve dans l'attribut `feature` de cette `ner_feature`, et nous pouvons accéder à la liste des noms en regardant l'attribut `names` de cette `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'] -``` - -Nous avons déjà vu ces étiquettes au [chapitre 6](/course/fr/chapter6/3) lorsque nous nous sommes intéressés au pipeline `token-classification` mais nosu pouvons tout de même faire un rapide rappel : - -- `O` signifie que le mot ne correspond à aucune entité. -- `B-PER`/`I-PER` signifie que le mot correspond au début/est à l'intérieur d'une entité *personne*. -- `B-ORG`/`I-ORG` signifie que le mot correspond au début/est à l'intérieur d'une entité *organisation*. -- `B-LOC`/`I-LOC` signifie que le mot correspond au début/est à l'intérieur d'une entité *location*. -- `B-MISC`/`I-MISC` signifie que le mot correspond au début/est à l'intérieur d'une entité *divers*. - -Maintenant, le décodage des étiquettes que nous avons vues précédemment nous donne ceci : - -```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' -``` - -Et pour un exemple mélangeant les étiquettes `B-` et `I-`, voici ce que le même code nous donne sur le quatrième élément du jeu d'entraînement : - -```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' -``` - -Comme on peut le voir, les entités couvrant deux mots, comme « European Union » et « Werner Zwingmann », se voient attribuer une étiquette `B-` pour le premier mot et une étiquette `I-` pour le second. - - - -✏️ *A votre tour !* Affichez les deux mêmes phrases avec leurs étiquettes POS ou *chunking*. - - - -### Traitement des données - - - -Comme d'habitude, nos textes doivent être convertis en identifiants de *tokens* avant que le modèle puisse leur donner un sens. Comme nous l'avons vu au [chapitre 6](/course/fr/chapter6/), une grande différence dans le cas des tâches de classification de *tokens* est que nous avons des entrées prétokénisées. Heureusement, l'API `tokenizer` peut gérer cela assez facilement. Nous devons juste avertir le `tokenizer` avec un drapeau spécial. - -Pour commencer, nous allons créer notre objet `tokenizer`. Comme nous l'avons dit précédemment, nous allons utiliser un modèle BERT pré-entraîné, donc nous allons commencer par télécharger et mettre en cache le *tokenizer* associé : - -```python -from transformers import AutoTokenizer - -model_checkpoint = "bert-base-cased" -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) -``` - -Vous pouvez remplacer le `model_checkpoint` par tout autre modèle que vous préférez à partir du [*Hub*](https://huggingface.co/models), ou par un dossier local dans lequel vous avez sauvegardé un modèle pré-entraîné et un *tokenizer*. La seule contrainte est que le *tokenizer* doit être soutenu par la bibliothèque 🤗 *Tokenizers*. Il y a donc une version rapide disponible. Vous pouvez voir toutes les architectures qui ont une version rapide dans [ce tableau](https://huggingface.co/transformers/#supported-frameworks), et pour vérifier que l'objet `tokenizer` que vous utilisez est bien soutenu par 🤗 *Tokenizers* vous pouvez regarder son attribut `is_fast` : - -```py -tokenizer.is_fast -``` - -```python out -True -``` - -Pour tokeniser une entrée prétokenisée, nous pouvons utiliser notre `tokenizer` comme d'habitude et juste ajouter `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]'] -``` - -Comme on peut le voir, le *tokenizer* a ajouté les *tokens* spéciaux utilisés par le modèle (`[CLS]` au début et `[SEP]` à la fin) et n'a pas touché à la plupart des mots. Le mot `lamb`, cependant, a été tokenisé en deux sous-mots, `la` et `##mb`. Cela introduit un décalage entre nos entrées et les étiquettes : la liste des étiquettes n'a que 9 éléments, alors que notre entrée a maintenant 12 *tokens*. Il est facile de tenir compte des *tokens* spéciaux (nous savons qu'ils sont au début et à la fin), mais nous devons également nous assurer que nous alignons toutes les étiquettes avec les mots appropriés. - -Heureusement, comme nous utilisons un *tokenizer* rapide, nous avons accès aux superpouvoirs des 🤗 *Tokenizers*, ce qui signifie que nous pouvons facilement faire correspondre chaque *token* au mot correspondant (comme on le voit au [chapitre 6](/course/fr/chapter6/3)) : - -```py -inputs.word_ids() -``` - -```python out -[None, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, None] -``` - -Avec un peu de travail, nous pouvons étendre notre liste d'étiquettes pour qu'elle corresponde aux *tokens*. La première règle que nous allons appliquer est que les *tokens* spéciaux reçoivent une étiquette de `-100`. En effet, par défaut, `-100` est un indice qui est ignoré dans la fonction de perte que nous allons utiliser (l'entropie croisée). Ensuite, chaque *token* reçoit la même étiquette que le *token* qui a commencé le mot dans lequel il se trouve puisqu'ils font partie de la même entité. Pour les *tokens* à l'intérieur d'un mot mais pas au début, nous remplaçons le `B-` par `I-` (puisque le *token* ne commence pas l'entité) : - -```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: - # Début d'un nouveau mot ! - current_word = word_id - label = -100 if word_id is None else labels[word_id] - new_labels.append(label) - elif word_id is None: - # Token spécial - new_labels.append(-100) - else: - # Même mot que le token précédent - label = labels[word_id] - # Si l'étiquette est B-XXX, nous la changeons en I-XXX - if label % 2 == 1: - label += 1 - new_labels.append(label) - - return new_labels -``` - -Essayons-le sur notre première phrase : - -```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] -``` - -Comme nous pouvons le voir, notre fonction a ajouté `-100` pour les deux *tokens* spéciaux du début et de fin, et un nouveau `0` pour notre mot qui a été divisé en deux *tokens*. - - - -✏️ *A votre tour !* Certains chercheurs préfèrent n'attribuer qu'une seule étiquette par mot et attribuer `-100` aux autres sous-*tokens* dans un mot donné. Ceci afin d'éviter que les longs mots qui se divisent en plusieurs batchs ne contribuent fortement à la perte. Changez la fonction précédente pour aligner les étiquettes avec les identifiants d'entrée en suivant cette règle. - - -Pour prétraiter notre jeu de données, nous devons tokeniser toutes les entrées et appliquer `align_labels_with_tokens()` sur toutes les étiquettes. Pour profiter de la vitesse de notre *tokenizer* rapide, il est préférable de tokeniser beaucoup de textes en même temps. Nous allons donc écrire une fonction qui traite une liste d'exemples et utiliser la méthode `Dataset.map()` avec l'option `batched=True`. La seule chose qui diffère de notre exemple précédent est que la fonction `word_ids()` a besoin de récupérer l'index de l'exemple dont nous voulons les identifiants de mots lorsque les entrées du *tokenizer* sont des listes de textes (ou dans notre cas, des listes de mots), donc nous l'ajoutons aussi : - -```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 -``` - -Notez que nous n'avons pas encore rembourré nos entrées. Nous le ferons plus tard lors de la création des batchs avec un collateur de données. - -Nous pouvons maintenant appliquer tout ce prétraitement en une seule fois sur les autres divisions de notre jeu de données : - -```py -tokenized_datasets = raw_datasets.map( - tokenize_and_align_labels, - batched=True, - remove_columns=raw_datasets["train"].column_names, -) -``` - -Nous avons fait la partie la plus difficile ! Maintenant que les données ont été prétraitées, l'entraînement ressemblera beaucoup à ce que nous avons fait dans le [chapitre 3](/course/fr/chapter3). - -{#if fw === 'pt'} - -## Finetuning du modèle avec l'API `Trainer` - -Le code utilisant `Trainer` sera le même que précédemment. Les seuls changements sont la façon dont les données sont rassemblées dans un batch ainsi que la fonction de calcul de la métrique. - -{:else} - -## Finetuning du modèle avec Keras - -Le code utilisant Keras sera très similaire au précédent. Les seuls changements sont la façon dont les données sont rassemblées dans un batch ainsi que la fonction de calcul de la métrique. - -{/if} - - -### Collation des données - -Nous ne pouvons pas simplement utiliser un `DataCollatorWithPadding` comme dans le [chapitre 3](/course/fr/chapter3) car cela ne fait que rembourrer les entrées (identifiants d'entrée, masque d'attention et *token* de type identifiants). Ici, nos étiquettes doivent être rembourréés exactement de la même manière que les entrées afin qu'elles gardent la même taille, en utilisant `-100` comme valeur afin que les prédictions correspondantes soient ignorées dans le calcul de la perte. - -Tout ceci est fait par un [`DataCollatorForTokenClassification`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorfortokenclassification). Comme le `DataCollatorWithPadding`, il prend le `tokenizer` utilisé pour prétraiter les entrées : - -{#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} - -Pour tester cette fonction sur quelques échantillons, nous pouvons simplement l'appeler sur une liste d'exemples provenant de notre jeu d'entraînement tokénisé : - -```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]]) -``` - -Comparons cela aux étiquettes des premier et deuxième éléments de notre jeu de données : - -```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'} - -Comme nous pouvons le voir, le deuxième jeu d'étiquettes a été complété à la longueur du premier en utilisant des `-100`. - -{:else} - -Notre collateur de données est prêt à fonctionner ! Maintenant, utilisons-le pour créer un `tf.data.Dataset` avec la méthode `to_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, -) -``` - - -Prochain arrêt : le modèle lui-même. - -{/if} - -{#if fw === 'tf'} - -### Définir le modèle - -Puisque nous travaillons sur un problème de classification de *tokens*, nous allons utiliser la classe `TFAutoModelForTokenClassification`. La principale chose à retenir lors de la définition de ce modèle est de transmettre des informations sur le nombre d'étiquettes que nous avons. La façon la plus simple de le faire est de passer ce nombre avec l'argument `num_labels`, mais si nous voulons un joli *widget* d'inférence fonctionnant comme celui que nous avons vu au début de cette section, il est préférable de définir les correspondances correctes des étiquettes à la place. - -Elles devraient être définies par deux dictionnaires, `id2label` et `label2id`, qui contiennent la correspondance de l'identifiant à l'étiquette et vice versa : - -```py -id2label = {str(i): label for i, label in enumerate(label_names)} -label2id = {v: k for k, v in id2label.items()} -``` - -Maintenant, nous pouvons simplement les passer à la méthode `TFAutoModelForTokenClassification.from_pretrained()`, et ils seront définis dans la configuration du modèle puis correctement enregistrés et téléchargés vers le *Hub* : - -```py -from transformers import TFAutoModelForTokenClassification - -model = TFAutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, -) -``` - -Comme lorsque nous avons défini notre `TFAutoModelForSequenceClassification` au [chapitre 3](/course/fr/chapter3), la création du modèle émet un avertissement indiquant que certains poids n'ont pas été utilisés (ceux de la tête de pré-entraînement) et que d'autres poids ont été initialisés de manière aléatoire (ceux de la tête de classification des nouveaux *tokens*), et que ce modèle doit être entraîné. Nous ferons cela dans une minute mais vérifions d'abord que notre modèle a le bon nombre d'étiquettes : - -```python -model.config.num_labels -``` - -```python out -9 -``` - - - -⚠️ Si vous avez un modèle avec le mauvais nombre d'étiquettes, vous obtiendrez plus tard une erreur obscure lors de l'appel de `model.fit()`. Cela peut être ennuyeux à déboguer donc assurez-vous de faire cette vérification pour confirmer que vous avez le nombre d'étiquettes attendu. - - - -### Finetuning du modèle - -Nous sommes maintenant prêts à entraîner notre modèle ! Mais nous devons d'abord faire un peu de ménage : nous devons nous connecter à Hugging Face et définir nos hyperparamètres d'entraînement. Si vous travaillez dans un *notebook*, il y a une fonction pratique pour vous aider à le faire : - -```python -from huggingface_hub import notebook_login - -notebook_login() -``` - -Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face. - -Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : - -```bash -huggingface-cli login -``` - -Après s'être connecté, nous pouvons préparer tout ce dont nous avons besoin pour compiler notre modèle. 🤗 *Transformers* fournit une fonction pratique `create_optimizer()` qui vous donnera un optimiseur `AdamW` avec des paramètres appropriés pour le taux de décroissance des poids et le taux de décroissance de l'apprentissage, les deux améliorant les performances de votre modèle par rapport à l'optimiseur `Adam` : - -```python -from transformers import create_optimizer -import tensorflow as tf - -# Entraîner en mixed-precision float16 -# Commentez cette ligne si vous utilisez un GPU qui ne bénéficiera pas de cette fonction -tf.keras.mixed_precision.set_global_policy("mixed_float16") - -# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans l'ensemble de données, divisé par la taille du batch puis multiplié par le nombre total d'époques -# par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un batchtf.data.Dataset, -# et non le jeu de données original Hugging Face Dataset, donc son len() est déjà 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) -``` - -Notez également que nous ne fournissons pas d'argument `loss` à `compile()`. C'est parce que les modèles peuvent en fait calculer la perte en interne. Si vous compilez sans perte et fournissez vos étiquettes dans le dictionnaire d'entrée (comme nous le faisons dans nos jeux de données), alors le modèle s'entraînera en utilisant cette perte interne, qui sera appropriée pour la tâche et le type de modèle que vous avez choisi. - -Ensuite, nous définissons un `PushToHubCallback` pour télécharger notre modèle vers le *Hub* pendant l'entraînement, et nous ajustons le modèle avec ce *callback* : - -```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, -) -``` - -Vous pouvez spécifier le nom complet du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/bert-finetuned-ner"`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, par exemple `"cool_huggingface_user/bert-finetuned-ner"`. - - - -💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de l'appel de `model.fit()` et devrez définir un nouveau nom. - - - -Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. De cette façon, vous pourrez reprendre votre entraînement sur une autre machine si nécessaire. - -A ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à *finetuner* un modèle sur une tâche de classification de *tokens*. Félicitations ! Mais quelle est la qualité réelle de notre modèle ? Nous devons évaluer certaines métriques pour le découvrir. - -{/if} - - -### Métriques - -{#if fw === 'pt'} - -Pour que le `Trainer` calcule une métrique à chaque époque, nous devrons définir une fonction `compute_metrics()` qui prend les tableaux de prédictions et d'étiquettes, et retourne un dictionnaire avec les noms et les valeurs des métriques. - -Le *framework* traditionnel utilisé pour évaluer la prédiction de la classification des *tokens* est [*seqeval*](https://github.com/chakki-works/seqeval). Pour utiliser cette métrique, nous devons d'abord installer la bibliothèque *seqeval* : - -```py -!pip install seqeval -``` - -Nous pouvons ensuite le charger via la fonction `evaluate.load()` comme nous l'avons fait dans le [chapitre 3](/course/fr/chapter3) : - -{:else} - -Le *framework* traditionnel utilisé pour évaluer la prédiction de la classification des *tokens* est [*seqeval*](https://github.com/chakki-works/seqeval). Pour utiliser cette métrique, nous devons d'abord installer la bibliothèque *seqeval* : - -```py -!pip install seqeval -``` - -Nous pouvons ensuite le charger via la fonction `evaluate.load()` comme nous l'avons fait dans le [chapitre 3](/course/fr/chapter3) : - -{/if} - -```py -import evaluate - -metric = evaluate.load("seqeval") -``` - -Cette métrique ne se comporte pas comme la précision standard : elle prend les listes d'étiquettes comme des chaînes de caractères et non comme des entiers. Nous devrons donc décoder complètement les prédictions et les étiquettes avant de les transmettre à la métrique. Voyons comment cela fonctionne. Tout d'abord, nous allons obtenir les étiquettes pour notre premier exemple d'entraînement : - -```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'] -``` - -Nous pouvons alors créer de fausses prédictions pour celles-ci en changeant simplement la valeur de l'indice 2 : - -```py -predictions = labels.copy() -predictions[2] = "O" -metric.compute(predictions=[predictions], references=[labels]) -``` - -Notez que la métrique prend une liste de prédictions (pas seulement une) et une liste d'étiquettes. Voici la sortie : - -```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'} - -Cela renvoie un batch d'informations ! Nous obtenons la précision, le rappel et le score F1 pour chaque entité séparée, ainsi que le score global. Pour notre calcul de métrique, nous ne garderons que le score global, mais n'hésitez pas à modifier la fonction `compute_metrics()` pour retourner toutes les métriques que vous souhaitez. - -Cette fonction `compute_metrics()` prend d'abord l'argmax des logits pour les convertir en prédictions (comme d'habitude, les logits et les probabilités sont dans le même ordre, donc nous n'avons pas besoin d'appliquer la fonction softmax). Ensuite, nous devons convertir les étiquettes et les prédictions des entiers en chaînes de caractères. Nous supprimons toutes les valeurs dont l'étiquette est `-100`, puis nous passons les résultats à la méthode `metric.compute()` : - -```py -import numpy as np - - -def compute_metrics(eval_preds): - logits, labels = eval_preds - predictions = np.argmax(logits, axis=-1) - - # Suppression de l'index ignoré (tokens spéciaux) et conversion en étiquettes - 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"], - } -``` - -Maintenant que ceci est fait, nous sommes presque prêts à définir notre `Trainer`. Nous avons juste besoin d'un objet `model` pour *finetuner* ! - -{:else} - -Cela renvoie un batch d'informations ! Nous obtenons la précision, le rappel et le score F1 pour chaque entité séparée, ainsi que pour l'ensemble. Voyons maintenant ce qui se passe si nous essayons d'utiliser les prédictions de notre modèle pour calculer des scores réels. - -TensorFlow n'aime pas concaténer nos prédictions ensemble car elles ont des longueurs de séquence variables. Cela signifie que nous ne pouvons pas simplement utiliser `model.predict()`. Mais cela ne va pas nous arrêter. Nous obtiendrons des prédictions un batch à la fois et les concaténerons en une grande liste longue au fur et à mesure et en laissant de côté les *tokens* `-100` qui indiquent le masquage/le remplissage. Puis nous calculerons les métriques sur la liste à la fin : - -```py -import numpy as np - -all_predictions = [] -all_labels = [] -for batch in tf_eval_dataset: - logits = model.predict(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} -``` - -Comment s'est comporté votre modèle, comparé au nôtre ? Si vous avez obtenu des chiffres similaires, votre entraînement a été un succès ! - -{/if} - -{#if fw === 'pt'} - -### Définir le modèle - -Puisque nous travaillons sur un problème de classification de *tokens*, nous allons utiliser la classe `AutoModelForTokenClassification`. La principale chose à retenir lors de la définition de ce modèle est de transmettre des informations sur le nombre d'étiquettes que nous avons. La façon la plus simple de le faire est de passer ce nombre avec l'argument `num_labels`, mais si nous voulons un joli *widget* d'inférence fonctionnant comme celui que nous avons vu au début de cette section, il est préférable de définir les correspondances des étiquettes à la place. - -Elles devraient être définies par deux dictionnaires, `id2label` et `label2id`, qui contiennent les correspondances entre identifiants et étiquettes et vice versa : - -```py -id2label = {str(i): label for i, label in enumerate(label_names)} -label2id = {v: k for k, v in id2label.items()} -``` - -Maintenant nous pouvons simplement les passer à la méthode `AutoModelForTokenClassification.from_pretrained()`, ils seront définis dans la configuration du modèle puis correctement sauvegardés et téléchargés vers le *Hub* : - -```py -from transformers import AutoModelForTokenClassification - -model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, -) -``` - -Comme lorsque nous avons défini notre `AutoModelForSequenceClassification` au [chapitre 3](/course/fr/chapter3), la création du modèle émet un avertissement indiquant que certains poids n'ont pas été utilisés (ceux de la tête de pré-entraînement) et que d'autres poids ont été initialisés de manière aléatoire (ceux de la tête de classification des nouveaux *tokens*), et que ce modèle doit être entraîné. Nous ferons cela dans une minute, mais vérifions d'abord que notre modèle a le bon nombre d'étiquettes : - -```python -model.config.num_labels -``` - -```python out -9 -``` - - - -⚠️ Si vous avez un modèle avec le mauvais nombre d'étiquettes, vous obtiendrez une erreur obscure lors de l'appel de la méthode `Trainer.train()` (quelque chose comme "CUDA error : device-side assert triggered"). C'est la première cause de bogues signalés par les utilisateurs pour de telles erreurs, donc assurez-vous de faire cette vérification pour confirmer que vous avez le nombre d'étiquettes attendu. - - - -### Finetuning du modèle - -Nous sommes maintenant prêts à entraîner notre modèle ! Nous devons juste faire deux dernières choses avant de définir notre `Trainer` : se connecter à Hugging Face et définir nos arguments d'entraînement. Si vous travaillez dans un *notebook*, il y a une fonction pratique pour vous aider à le faire : - -```python -from huggingface_hub import notebook_login - -notebook_login() -``` - -Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face. - -Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : - -```bash -huggingface-cli login -``` - -Une fois ceci fait, nous pouvons définir nos `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, -) -``` - -Vous avez déjà vu la plupart d'entre eux. Nous définissons quelques hyperparamètres (comme le taux d'apprentissage, le nombre d'époques à entraîner, et le taux de décroissance des poids), et nous spécifions `push_to_hub=True` pour indiquer que nous voulons sauvegarder le modèle, l'évaluer à la fin de chaque époque, et que nous voulons télécharger nos résultats vers le *Hub*. Notez que vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/bert-finetuned-ner"``TrainingArguments`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé d'après le répertoire de sortie que vous avez défini, donc dans notre cas ce sera `"sgugger/bert-finetuned-ner"`. - - - -💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de la définition de votre `Trainer` et devrez définir un nouveau nom. - - - -Enfin, nous passons tout au `Trainer` et lançons l'entraînement : - -```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() -``` - -Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. De cette façon, vous serez en mesure de reprendre votre entraînement sur une autre machine si nécessaire. - -Une fois l'entraînement terminé, nous utilisons la méthode `push_to_hub()` pour nous assurer que nous téléchargeons la version la plus récente du modèle : - -```py -trainer.push_to_hub(commit_message="Training complete") -``` - -Cette commande renvoie l'URL du commit qu'elle vient de faire, si vous voulez l'inspecter : - -```python out -'https://huggingface.co/sgugger/bert-finetuned-ner/commit/26ab21e5b1568f9afeccdaed2d8715f571d786ed' -``` - -Le `Trainer` rédige également une carte modèle avec tous les résultats de l'évaluation et la télécharge. A ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à affiner un modèle sur une tâche de classification de *tokens*. Félicitations ! - -Si vous voulez plonger un peu plus profondément dans la boucle d'entraînement, nous allons maintenant vous montrer comment faire la même chose en utilisant 🤗 *Accelerate*. - -## Une boucle d'entraînement personnalisée - -Jetons maintenant un coup d'œil à la boucle d'entraînement complète afin que vous puissiez facilement personnaliser les parties dont vous avez besoin. Elle ressemblera beaucoup à ce que nous avons fait dans le [chapitre 3](/course/fr/chapter3/4) avec quelques changements pour l'évaluation. - -### Préparer tout pour l'entraînement - -D'abord nous devons construire le `DataLoader`s à partir de nos jeux de données. Nous réutilisons notre `data_collator` comme un `collate_fn` et mélanger l'ensemble d'entraînement, mais pas l'ensemble de validation : - -```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 -) -``` - -Ensuite, nous réinstantifions notre modèle pour nous assurer que nous ne continuons pas le *finetuning* d'avant et que nous repartons bien du modèle pré-entraîné de BERT : - -```py -model = AutoModelForTokenClassification.from_pretrained( - model_checkpoint, - id2label=id2label, - label2id=label2id, -) -``` - -Ensuite, nous avons besoin d'un optimiseur. Nous utilisons le classique `AdamW`, qui est comme `Adam`, mais avec un correctif dans la façon dont le taux de décroissance des poids est appliquée : - -```py -from torch.optim import AdamW - -optimizer = AdamW(model.parameters(), lr=2e-5) -``` - -Une fois que nous avons tous ces objets, nous pouvons les envoyer à la méthode `accelerator.prepare()` : - -```py -from accelerate import Accelerator - -accelerator = Accelerator() -model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( - model, optimizer, train_dataloader, eval_dataloader -) -``` - - - -🚨 Si vous entraînez sur un TPU, vous devrez déplacer tout le code à partir de la cellule ci-dessus dans une fonction d'entraînement dédiée. Voir le [chapitre 3](/course/fr/chapter3) pour plus de détails. - - - -Maintenant que nous avons envoyé notre `train_dataloader` à `accelerator.prepare()`, nous pouvons utiliser sa longueur pour calculer le nombre d'étapes d'entraînement. Rappelez-vous que nous devrions toujours faire cela après avoir préparé le *dataloader* car cette méthode modifiera sa longueur. Nous utilisons un programme linéaire classique du taux d'apprentissage à 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, -) -``` - -Enfin, pour pousser notre modèle vers le *Hub*, nous avons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous à Hugging Face si vous n'êtes pas déjà connecté. Nous déterminons le nom du dépôt à partir de l'identifiant du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix, il doit juste contenir votre nom d'utilisateur et ce que fait la fonction `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' -``` - -Ensuite, nous pouvons cloner ce dépôt dans un dossier local. S'il existe déjà, ce dossier local doit être un clone existant du dépôt avec lequel nous travaillons : - -```py -output_dir = "bert-finetuned-ner-accelerate" -repo = Repository(output_dir, clone_from=repo_name) -``` - -Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output_dir` en appelant la méthode `repo.push_to_hub()`. Cela nous aidera à télécharger les modèles intermédiaires à la fin de chaque époque. - -### Boucle d'entraînement - -Nous sommes maintenant prêts à écrire la boucle d'entraînement complète. Pour simplifier sa partie évaluation, nous définissons cette fonction `postprocess()` qui prend les prédictions et les étiquettes, et les convertit en listes de chaînes de caractères comme notre objet `metric` l'attend : - -```py -def postprocess(predictions, labels): - predictions = predictions.detach().cpu().clone().numpy() - labels = labels.detach().cpu().clone().numpy() - - # Suppression de l'index ignoré (tokens spéciaux) et conversion en étiquettes - 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 -``` - -Ensuite, nous pouvons écrire la boucle d'entraînement. Après avoir défini une barre de progression pour suivre l'évolution de l'entraînement, la boucle comporte trois parties : - -- L'entraînement proprement dit, qui est l'itération classique sur le `train_dataloader`, passage en avant, puis passage en arrière et étape d'optimisation. -- L'évaluation, dans laquelle il y a une nouveauté après avoir obtenu les sorties de notre modèle sur un batch : puisque deux processus peuvent avoir paddé les entrées et les étiquettes à des formes différentes, nous devons utiliser `accelerator.pad_across_processes()` pour rendre les prédictions et les étiquettes de la même forme avant d'appeler la méthode `gather()`. Si nous ne le faisons pas, l'évaluation va soit se tromper, soit se bloquer pour toujours. Ensuite, nous envoyons les résultats à `metric.add_batch()` et appelons `metric.compute()` une fois que la boucle d'évaluation est terminée. -- Sauvegarde et téléchargement, où nous sauvegardons d'abord le modèle et le *tokenizer*, puis appelons `repo.push_to_hub()`. Remarquez que nous utilisons l'argument `blocking=False` pour indiquer à la bibliothèque 🤗 *Hub* de pousser dans un processus asynchrone. De cette façon, l'entraînement continue normalement et cette (longue) instruction est exécutée en arrière-plan. - -Voici le code complet de la boucle d'entraînement : - -```py -from tqdm.auto import tqdm -import torch - -progress_bar = tqdm(range(num_training_steps)) - -for epoch in range(num_train_epochs): - # Entraînement - 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) - - # Evaluation - model.eval() - for batch in eval_dataloader: - with torch.no_grad(): - outputs = model(**batch) - - predictions = outputs.logits.argmax(dim=-1) - labels = batch["labels"] - - # Nécessaire pour rembourrer les prédictions et les étiquettes à rassembler - 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"] - }, - ) - - # Sauvegarder et télécharger - 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 - ) -``` - -Au cas où ce serait la première fois que vous verriez un modèle enregistré avec 🤗 *Accelerate*, prenons un moment pour inspecter les trois lignes de code qui l'accompagnent : - -```py -accelerator.wait_for_everyone() -unwrapped_model = accelerator.unwrap_model(model) -unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) -``` - -La première ligne est explicite : elle indique à tous les processus d'attendre que tout le monde soit à ce stade avant de continuer. C'est pour s'assurer que nous avons le même modèle dans chaque processus avant de sauvegarder. Ensuite, nous prenons le `unwrapped_model` qui est le modèle de base que nous avons défini. La méthode `accelerator.prepare()` modifie le modèle pour qu'il fonctionne dans l'entraînement distribué, donc il n'aura plus la méthode `save_pretrained()` ; la méthode `accelerator.unwrap_model()` annule cette étape. Enfin, nous appelons `save_pretrained()` mais nous disons à cette méthode d'utiliser `accelerator.save()` au lieu de `torch.save()`. - -Une fois ceci fait, vous devriez avoir un modèle qui produit des résultats assez similaires à celui entraîné avec le `Trainer`. Vous pouvez vérifier le modèle que nous avons formé en utilisant ce code à [*huggingface-course/bert-finetuned-ner-accelerate*](https://huggingface.co/huggingface-course/bert-finetuned-ner-accelerate). Et si vous voulez tester des modifications de la boucle d'entraînement, vous pouvez les implémenter directement en modifiant le code ci-dessus ! - -{/if} - -### Utilisation du modèle finetuné - -Nous vous avons déjà montré comment vous pouvez utiliser le modèle *finetuné* sur le *Hub* avec le *widget* d'inférence. Pour l'utiliser localement dans un `pipeline`, vous devez juste spécifier l'identifiant de modèle approprié : - -```py -from transformers import pipeline - -# Remplacez ceci par votre propre checkpoint -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}] -``` - -Super ! Notre modèle fonctionne aussi bien que le modèle par défaut pour ce pipeline ! - + + +# Classification de tokens + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +La première application que nous allons explorer est la classification de *tokens*. Cette tâche générique englobe tous les problèmes qui peuvent être formulés comme l'attribution d'une étiquette à chaque *token* d'une phrase, tels que : + +- la **reconnaissance d'entités nommées (NER de l'anglais *Named Entity Recognition*)**, c'est-à-dire trouver les entités (telles que des personnes, des lieux ou des organisations) dans une phrase. Ce tâche peut être formulée comme l'attribution d'une étiquette à chaque *token* faisant parti d'une entité en ayant une classe spécifique par entité, et une classe pour les *tokens* ne faisant pas parti d'entité. +- le ***part-of-speech tagging* (POS)**, c'est-à-dire marquer chaque mot dans une phrase comme correspondant à une partie particulière (comme un nom, un verbe, un adjectif, etc.). +- le ***chunking***, c'est-à-dire trouver les *tokens* qui appartiennent à la même entité. Cette tâche (qui peut être combinée avec le POS ou la NER) peut être formulée comme l'attribution d'une étiquette (habituellement `B-`) à tous les *tokens* qui sont au début d'un morceau, une autre étiquette (habituellement `I-`) aux *tokens* qui sont à l'intérieur d'un morceau, et une troisième étiquette (habituellement `O`) aux *tokens* qui n'appartiennent à aucun morceau. + + + +Bien sûr, il existe de nombreux autres types de problèmes de classification de *tokens*. Ce ne sont là que quelques exemples représentatifs. Dans cette section, nous allons *finetuner* un modèle (BERT) sur la tâche de NER. Il sera alors capable de calculer des prédictions comme celle-ci : + + + + + +One-hot encoded labels for question answering. + + + +Vous pouvez trouver, télécharger et vérifier les précisions de ce modèle sur le [*Hub*](https://huggingface.co/huggingface-course/bert-finetuned-ner?text=My+nom+est+Sylvain+et+je+travaille+à+Hugging+Face+in+Brooklyn) les prédictions du modèle que nous allons entraîner. + +## Préparation des données + +Tout d'abord, nous avons besoin d'un jeu de données adapté à la classification des *tokens*. Dans cette section, nous utiliserons le jeu de données [CoNLL-2003](https://huggingface.co/datasets/conll2003), qui contient des articles de presse de Reuters. + + + +💡 Tant que votre jeu de données consiste en des textes divisés en mots avec leurs étiquettes correspondantes, vous pourrez adapter les procédures de traitement des données décrites ici à votre propre jeu de données. Reportez-vous au [chapitre 5](/course/fr/chapter5) si vous avez besoin d'un rafraîchissement sur la façon de charger vos propres données personnalisées dans un `Dataset`. + + + +### Le jeu de données CoNLL-2003 + +Pour charger le jeu de données CoNLL-2003, nous utilisons la méthode `load_dataset()` de la bibliothèque 🤗 *Datasets* : + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("conll2003") +``` + +Cela va télécharger et mettre en cache le jeu de données, comme nous l'avons vu dans [chapitre 3](/course/fr/chapter3) pour le jeu de données GLUE MRPC. L'inspection de cet objet nous montre les colonnes présentes dans ce jeu de données et la répartition entre les ensembles d'entraînement, de validation et de test : + +```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 + }) +}) +``` + +En particulier, nous pouvons voir que le jeu de données contient des étiquettes pour les trois tâches que nous avons mentionnées précédemment : NER, POS et *chunking*. Une grande différence avec les autres jeux de données est que les entrées textuelles ne sont pas présentés comme des phrases ou des documents, mais comme des listes de mots (la dernière colonne est appelée `tokens`, mais elle contient des mots dans le sens où ce sont des entrées prétokénisées qui doivent encore passer par le *tokenizer* pour la tokenisation en sous-mots). + +Regardons le premier élément de l'ensemble d'entraînement : + +```py +raw_datasets["train"][0]["tokens"] +``` + +```python out +['EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'lamb', '.'] +``` + +Puisque nous voulons effectuer reconnaître des entités nommées, nous allons examiner les balises NER : + +```py +raw_datasets["train"][0]["ner_tags"] +``` + +```python out +[3, 0, 7, 0, 0, 0, 7, 0, 0] +``` + +Ce sont les étiquettes sous forme d'entiers disponibles pour l'entraînement mais ne sont pas nécessairement utiles lorsque nous voulons inspecter les données. Comme pour la classification de texte, nous pouvons accéder à la correspondance entre ces entiers et les noms des étiquettes en regardant l'attribut `features` de notre jeu de données : + +```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) +``` + +Cette colonne contient donc des éléments qui sont des séquences de `ClassLabel`. Le type des éléments de la séquence se trouve dans l'attribut `feature` de cette `ner_feature`, et nous pouvons accéder à la liste des noms en regardant l'attribut `names` de cette `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'] +``` + +Nous avons déjà vu ces étiquettes au [chapitre 6](/course/fr/chapter6/3) lorsque nous nous sommes intéressés au pipeline `token-classification` mais nosu pouvons tout de même faire un rapide rappel : + +- `O` signifie que le mot ne correspond à aucune entité. +- `B-PER`/`I-PER` signifie que le mot correspond au début/est à l'intérieur d'une entité *personne*. +- `B-ORG`/`I-ORG` signifie que le mot correspond au début/est à l'intérieur d'une entité *organisation*. +- `B-LOC`/`I-LOC` signifie que le mot correspond au début/est à l'intérieur d'une entité *location*. +- `B-MISC`/`I-MISC` signifie que le mot correspond au début/est à l'intérieur d'une entité *divers*. + +Maintenant, le décodage des étiquettes que nous avons vues précédemment nous donne ceci : + +```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' +``` + +Et pour un exemple mélangeant les étiquettes `B-` et `I-`, voici ce que le même code nous donne sur le quatrième élément du jeu d'entraînement : + +```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' +``` + +Comme on peut le voir, les entités couvrant deux mots, comme « European Union » et « Werner Zwingmann », se voient attribuer une étiquette `B-` pour le premier mot et une étiquette `I-` pour le second. + + + +✏️ *A votre tour !* Affichez les deux mêmes phrases avec leurs étiquettes POS ou *chunking*. + + + +### Traitement des données + + + +Comme d'habitude, nos textes doivent être convertis en identifiants de *tokens* avant que le modèle puisse leur donner un sens. Comme nous l'avons vu au [chapitre 6](/course/fr/chapter6/), une grande différence dans le cas des tâches de classification de *tokens* est que nous avons des entrées prétokénisées. Heureusement, l'API `tokenizer` peut gérer cela assez facilement. Nous devons juste avertir le `tokenizer` avec un drapeau spécial. + +Pour commencer, nous allons créer notre objet `tokenizer`. Comme nous l'avons dit précédemment, nous allons utiliser un modèle BERT pré-entraîné, donc nous allons commencer par télécharger et mettre en cache le *tokenizer* associé : + +```python +from transformers import AutoTokenizer + +model_checkpoint = "bert-base-cased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +Vous pouvez remplacer le `model_checkpoint` par tout autre modèle que vous préférez à partir du [*Hub*](https://huggingface.co/models), ou par un dossier local dans lequel vous avez sauvegardé un modèle pré-entraîné et un *tokenizer*. La seule contrainte est que le *tokenizer* doit être soutenu par la bibliothèque 🤗 *Tokenizers*. Il y a donc une version rapide disponible. Vous pouvez voir toutes les architectures qui ont une version rapide dans [ce tableau](https://huggingface.co/transformers/#supported-frameworks), et pour vérifier que l'objet `tokenizer` que vous utilisez est bien soutenu par 🤗 *Tokenizers* vous pouvez regarder son attribut `is_fast` : + +```py +tokenizer.is_fast +``` + +```python out +True +``` + +Pour tokeniser une entrée prétokenisée, nous pouvons utiliser notre `tokenizer` comme d'habitude et juste ajouter `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]'] +``` + +Comme on peut le voir, le *tokenizer* a ajouté les *tokens* spéciaux utilisés par le modèle (`[CLS]` au début et `[SEP]` à la fin) et n'a pas touché à la plupart des mots. Le mot `lamb`, cependant, a été tokenisé en deux sous-mots, `la` et `##mb`. Cela introduit un décalage entre nos entrées et les étiquettes : la liste des étiquettes n'a que 9 éléments, alors que notre entrée a maintenant 12 *tokens*. Il est facile de tenir compte des *tokens* spéciaux (nous savons qu'ils sont au début et à la fin), mais nous devons également nous assurer que nous alignons toutes les étiquettes avec les mots appropriés. + +Heureusement, comme nous utilisons un *tokenizer* rapide, nous avons accès aux superpouvoirs des 🤗 *Tokenizers*, ce qui signifie que nous pouvons facilement faire correspondre chaque *token* au mot correspondant (comme on le voit au [chapitre 6](/course/fr/chapter6/3)) : + +```py +inputs.word_ids() +``` + +```python out +[None, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, None] +``` + +Avec un peu de travail, nous pouvons étendre notre liste d'étiquettes pour qu'elle corresponde aux *tokens*. La première règle que nous allons appliquer est que les *tokens* spéciaux reçoivent une étiquette de `-100`. En effet, par défaut, `-100` est un indice qui est ignoré dans la fonction de perte que nous allons utiliser (l'entropie croisée). Ensuite, chaque *token* reçoit la même étiquette que le *token* qui a commencé le mot dans lequel il se trouve puisqu'ils font partie de la même entité. Pour les *tokens* à l'intérieur d'un mot mais pas au début, nous remplaçons le `B-` par `I-` (puisque le *token* ne commence pas l'entité) : + +```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: + # Début d'un nouveau mot ! + current_word = word_id + label = -100 if word_id is None else labels[word_id] + new_labels.append(label) + elif word_id is None: + # Token spécial + new_labels.append(-100) + else: + # Même mot que le token précédent + label = labels[word_id] + # Si l'étiquette est B-XXX, nous la changeons en I-XXX + if label % 2 == 1: + label += 1 + new_labels.append(label) + + return new_labels +``` + +Essayons-le sur notre première phrase : + +```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] +``` + +Comme nous pouvons le voir, notre fonction a ajouté `-100` pour les deux *tokens* spéciaux du début et de fin, et un nouveau `0` pour notre mot qui a été divisé en deux *tokens*. + + + +✏️ *A votre tour !* Certains chercheurs préfèrent n'attribuer qu'une seule étiquette par mot et attribuer `-100` aux autres sous-*tokens* dans un mot donné. Ceci afin d'éviter que les longs mots qui se divisent en plusieurs batchs ne contribuent fortement à la perte. Changez la fonction précédente pour aligner les étiquettes avec les identifiants d'entrée en suivant cette règle. + + +Pour prétraiter notre jeu de données, nous devons tokeniser toutes les entrées et appliquer `align_labels_with_tokens()` sur toutes les étiquettes. Pour profiter de la vitesse de notre *tokenizer* rapide, il est préférable de tokeniser beaucoup de textes en même temps. Nous allons donc écrire une fonction qui traite une liste d'exemples et utiliser la méthode `Dataset.map()` avec l'option `batched=True`. La seule chose qui diffère de notre exemple précédent est que la fonction `word_ids()` a besoin de récupérer l'index de l'exemple dont nous voulons les identifiants de mots lorsque les entrées du *tokenizer* sont des listes de textes (ou dans notre cas, des listes de mots), donc nous l'ajoutons aussi : + +```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 +``` + +Notez que nous n'avons pas encore rembourré nos entrées. Nous le ferons plus tard lors de la création des batchs avec un collateur de données. + +Nous pouvons maintenant appliquer tout ce prétraitement en une seule fois sur les autres divisions de notre jeu de données : + +```py +tokenized_datasets = raw_datasets.map( + tokenize_and_align_labels, + batched=True, + remove_columns=raw_datasets["train"].column_names, +) +``` + +Nous avons fait la partie la plus difficile ! Maintenant que les données ont été prétraitées, l'entraînement ressemblera beaucoup à ce que nous avons fait dans le [chapitre 3](/course/fr/chapter3). + +{#if fw === 'pt'} + +## Finetuning du modèle avec l'API `Trainer` + +Le code utilisant `Trainer` sera le même que précédemment. Les seuls changements sont la façon dont les données sont rassemblées dans un batch ainsi que la fonction de calcul de la métrique. + +{:else} + +## Finetuning du modèle avec Keras + +Le code utilisant Keras sera très similaire au précédent. Les seuls changements sont la façon dont les données sont rassemblées dans un batch ainsi que la fonction de calcul de la métrique. + +{/if} + + +### Collation des données + +Nous ne pouvons pas simplement utiliser un `DataCollatorWithPadding` comme dans le [chapitre 3](/course/fr/chapter3) car cela ne fait que rembourrer les entrées (identifiants d'entrée, masque d'attention et *token* de type identifiants). Ici, nos étiquettes doivent être rembourréés exactement de la même manière que les entrées afin qu'elles gardent la même taille, en utilisant `-100` comme valeur afin que les prédictions correspondantes soient ignorées dans le calcul de la perte. + +Tout ceci est fait par un [`DataCollatorForTokenClassification`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorfortokenclassification). Comme le `DataCollatorWithPadding`, il prend le `tokenizer` utilisé pour prétraiter les entrées : + +{#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} + +Pour tester cette fonction sur quelques échantillons, nous pouvons simplement l'appeler sur une liste d'exemples provenant de notre jeu d'entraînement tokénisé : + +```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]]) +``` + +Comparons cela aux étiquettes des premier et deuxième éléments de notre jeu de données : + +```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'} + +Comme nous pouvons le voir, le deuxième jeu d'étiquettes a été complété à la longueur du premier en utilisant des `-100`. + +{:else} + +Notre collateur de données est prêt à fonctionner ! Maintenant, utilisons-le pour créer un `tf.data.Dataset` avec la méthode `to_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, +) +``` + + +Prochain arrêt : le modèle lui-même. + +{/if} + +{#if fw === 'tf'} + +### Définir le modèle + +Puisque nous travaillons sur un problème de classification de *tokens*, nous allons utiliser la classe `TFAutoModelForTokenClassification`. La principale chose à retenir lors de la définition de ce modèle est de transmettre des informations sur le nombre d'étiquettes que nous avons. La façon la plus simple de le faire est de passer ce nombre avec l'argument `num_labels`, mais si nous voulons un joli *widget* d'inférence fonctionnant comme celui que nous avons vu au début de cette section, il est préférable de définir les correspondances correctes des étiquettes à la place. + +Elles devraient être définies par deux dictionnaires, `id2label` et `label2id`, qui contiennent la correspondance de l'identifiant à l'étiquette et vice versa : + +```py +id2label = {str(i): label for i, label in enumerate(label_names)} +label2id = {v: k for k, v in id2label.items()} +``` + +Maintenant, nous pouvons simplement les passer à la méthode `TFAutoModelForTokenClassification.from_pretrained()`, et ils seront définis dans la configuration du modèle puis correctement enregistrés et téléchargés vers le *Hub* : + +```py +from transformers import TFAutoModelForTokenClassification + +model = TFAutoModelForTokenClassification.from_pretrained( + model_checkpoint, + id2label=id2label, + label2id=label2id, +) +``` + +Comme lorsque nous avons défini notre `TFAutoModelForSequenceClassification` au [chapitre 3](/course/fr/chapter3), la création du modèle émet un avertissement indiquant que certains poids n'ont pas été utilisés (ceux de la tête de pré-entraînement) et que d'autres poids ont été initialisés de manière aléatoire (ceux de la tête de classification des nouveaux *tokens*), et que ce modèle doit être entraîné. Nous ferons cela dans une minute mais vérifions d'abord que notre modèle a le bon nombre d'étiquettes : + +```python +model.config.num_labels +``` + +```python out +9 +``` + + + +⚠️ Si vous avez un modèle avec le mauvais nombre d'étiquettes, vous obtiendrez plus tard une erreur obscure lors de l'appel de `model.fit()`. Cela peut être ennuyeux à déboguer donc assurez-vous de faire cette vérification pour confirmer que vous avez le nombre d'étiquettes attendu. + + + +### Finetuning du modèle + +Nous sommes maintenant prêts à entraîner notre modèle ! Mais nous devons d'abord faire un peu de ménage : nous devons nous connecter à Hugging Face et définir nos hyperparamètres d'entraînement. Si vous travaillez dans un *notebook*, il y a une fonction pratique pour vous aider à le faire : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face. + +Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : + +```bash +huggingface-cli login +``` + +Après s'être connecté, nous pouvons préparer tout ce dont nous avons besoin pour compiler notre modèle. 🤗 *Transformers* fournit une fonction pratique `create_optimizer()` qui vous donnera un optimiseur `AdamW` avec des paramètres appropriés pour le taux de décroissance des poids et le taux de décroissance de l'apprentissage, les deux améliorant les performances de votre modèle par rapport à l'optimiseur `Adam` : + +```python +from transformers import create_optimizer +import tensorflow as tf + +# Entraîner en mixed-precision float16 +# Commentez cette ligne si vous utilisez un GPU qui ne bénéficiera pas de cette fonction +tf.keras.mixed_precision.set_global_policy("mixed_float16") + +# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans l'ensemble de données, divisé par la taille du batch puis multiplié par le nombre total d'époques +# par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un batchtf.data.Dataset, +# et non le jeu de données original Hugging Face Dataset, donc son len() est déjà 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) +``` + +Notez également que nous ne fournissons pas d'argument `loss` à `compile()`. C'est parce que les modèles peuvent en fait calculer la perte en interne. Si vous compilez sans perte et fournissez vos étiquettes dans le dictionnaire d'entrée (comme nous le faisons dans nos jeux de données), alors le modèle s'entraînera en utilisant cette perte interne, qui sera appropriée pour la tâche et le type de modèle que vous avez choisi. + +Ensuite, nous définissons un `PushToHubCallback` pour télécharger notre modèle vers le *Hub* pendant l'entraînement, et nous ajustons le modèle avec ce *callback* : + +```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, +) +``` + +Vous pouvez spécifier le nom complet du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/bert-finetuned-ner"`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, par exemple `"cool_huggingface_user/bert-finetuned-ner"`. + + + +💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de l'appel de `model.fit()` et devrez définir un nouveau nom. + + + +Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. De cette façon, vous pourrez reprendre votre entraînement sur une autre machine si nécessaire. + +A ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à *finetuner* un modèle sur une tâche de classification de *tokens*. Félicitations ! Mais quelle est la qualité réelle de notre modèle ? Nous devons évaluer certaines métriques pour le découvrir. + +{/if} + + +### Métriques + +{#if fw === 'pt'} + +Pour que le `Trainer` calcule une métrique à chaque époque, nous devrons définir une fonction `compute_metrics()` qui prend les tableaux de prédictions et d'étiquettes, et retourne un dictionnaire avec les noms et les valeurs des métriques. + +Le *framework* traditionnel utilisé pour évaluer la prédiction de la classification des *tokens* est [*seqeval*](https://github.com/chakki-works/seqeval). Pour utiliser cette métrique, nous devons d'abord installer la bibliothèque *seqeval* : + +```py +!pip install seqeval +``` + +Nous pouvons ensuite le charger via la fonction `evaluate.load()` comme nous l'avons fait dans le [chapitre 3](/course/fr/chapter3) : + +{:else} + +Le *framework* traditionnel utilisé pour évaluer la prédiction de la classification des *tokens* est [*seqeval*](https://github.com/chakki-works/seqeval). Pour utiliser cette métrique, nous devons d'abord installer la bibliothèque *seqeval* : + +```py +!pip install seqeval +``` + +Nous pouvons ensuite le charger via la fonction `evaluate.load()` comme nous l'avons fait dans le [chapitre 3](/course/fr/chapter3) : + +{/if} + +```py +import evaluate + +metric = evaluate.load("seqeval") +``` + +Cette métrique ne se comporte pas comme la précision standard : elle prend les listes d'étiquettes comme des chaînes de caractères et non comme des entiers. Nous devrons donc décoder complètement les prédictions et les étiquettes avant de les transmettre à la métrique. Voyons comment cela fonctionne. Tout d'abord, nous allons obtenir les étiquettes pour notre premier exemple d'entraînement : + +```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'] +``` + +Nous pouvons alors créer de fausses prédictions pour celles-ci en changeant simplement la valeur de l'indice 2 : + +```py +predictions = labels.copy() +predictions[2] = "O" +metric.compute(predictions=[predictions], references=[labels]) +``` + +Notez que la métrique prend une liste de prédictions (pas seulement une) et une liste d'étiquettes. Voici la sortie : + +```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'} + +Cela renvoie un batch d'informations ! Nous obtenons la précision, le rappel et le score F1 pour chaque entité séparée, ainsi que le score global. Pour notre calcul de métrique, nous ne garderons que le score global, mais n'hésitez pas à modifier la fonction `compute_metrics()` pour retourner toutes les métriques que vous souhaitez. + +Cette fonction `compute_metrics()` prend d'abord l'argmax des logits pour les convertir en prédictions (comme d'habitude, les logits et les probabilités sont dans le même ordre, donc nous n'avons pas besoin d'appliquer la fonction softmax). Ensuite, nous devons convertir les étiquettes et les prédictions des entiers en chaînes de caractères. Nous supprimons toutes les valeurs dont l'étiquette est `-100`, puis nous passons les résultats à la méthode `metric.compute()` : + +```py +import numpy as np + + +def compute_metrics(eval_preds): + logits, labels = eval_preds + predictions = np.argmax(logits, axis=-1) + + # Suppression de l'index ignoré (tokens spéciaux) et conversion en étiquettes + 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"], + } +``` + +Maintenant que ceci est fait, nous sommes presque prêts à définir notre `Trainer`. Nous avons juste besoin d'un objet `model` pour *finetuner* ! + +{:else} + +Cela renvoie un batch d'informations ! Nous obtenons la précision, le rappel et le score F1 pour chaque entité séparée, ainsi que pour l'ensemble. Voyons maintenant ce qui se passe si nous essayons d'utiliser les prédictions de notre modèle pour calculer des scores réels. + +TensorFlow n'aime pas concaténer nos prédictions ensemble car elles ont des longueurs de séquence variables. Cela signifie que nous ne pouvons pas simplement utiliser `model.predict()`. Mais cela ne va pas nous arrêter. Nous obtiendrons des prédictions un batch à la fois et les concaténerons en une grande liste longue au fur et à mesure et en laissant de côté les *tokens* `-100` qui indiquent le masquage/le remplissage. Puis nous calculerons les métriques sur la liste à la fin : + +```py +import numpy as np + +all_predictions = [] +all_labels = [] +for batch in tf_eval_dataset: + logits = model.predict(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} +``` + +Comment s'est comporté votre modèle, comparé au nôtre ? Si vous avez obtenu des chiffres similaires, votre entraînement a été un succès ! + +{/if} + +{#if fw === 'pt'} + +### Définir le modèle + +Puisque nous travaillons sur un problème de classification de *tokens*, nous allons utiliser la classe `AutoModelForTokenClassification`. La principale chose à retenir lors de la définition de ce modèle est de transmettre des informations sur le nombre d'étiquettes que nous avons. La façon la plus simple de le faire est de passer ce nombre avec l'argument `num_labels`, mais si nous voulons un joli *widget* d'inférence fonctionnant comme celui que nous avons vu au début de cette section, il est préférable de définir les correspondances des étiquettes à la place. + +Elles devraient être définies par deux dictionnaires, `id2label` et `label2id`, qui contiennent les correspondances entre identifiants et étiquettes et vice versa : + +```py +id2label = {str(i): label for i, label in enumerate(label_names)} +label2id = {v: k for k, v in id2label.items()} +``` + +Maintenant nous pouvons simplement les passer à la méthode `AutoModelForTokenClassification.from_pretrained()`, ils seront définis dans la configuration du modèle puis correctement sauvegardés et téléchargés vers le *Hub* : + +```py +from transformers import AutoModelForTokenClassification + +model = AutoModelForTokenClassification.from_pretrained( + model_checkpoint, + id2label=id2label, + label2id=label2id, +) +``` + +Comme lorsque nous avons défini notre `AutoModelForSequenceClassification` au [chapitre 3](/course/fr/chapter3), la création du modèle émet un avertissement indiquant que certains poids n'ont pas été utilisés (ceux de la tête de pré-entraînement) et que d'autres poids ont été initialisés de manière aléatoire (ceux de la tête de classification des nouveaux *tokens*), et que ce modèle doit être entraîné. Nous ferons cela dans une minute, mais vérifions d'abord que notre modèle a le bon nombre d'étiquettes : + +```python +model.config.num_labels +``` + +```python out +9 +``` + + + +⚠️ Si vous avez un modèle avec le mauvais nombre d'étiquettes, vous obtiendrez une erreur obscure lors de l'appel de la méthode `Trainer.train()` (quelque chose comme "CUDA error : device-side assert triggered"). C'est la première cause de bogues signalés par les utilisateurs pour de telles erreurs, donc assurez-vous de faire cette vérification pour confirmer que vous avez le nombre d'étiquettes attendu. + + + +### Finetuning du modèle + +Nous sommes maintenant prêts à entraîner notre modèle ! Nous devons juste faire deux dernières choses avant de définir notre `Trainer` : se connecter à Hugging Face et définir nos arguments d'entraînement. Si vous travaillez dans un *notebook*, il y a une fonction pratique pour vous aider à le faire : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face. + +Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : + +```bash +huggingface-cli login +``` + +Une fois ceci fait, nous pouvons définir nos `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, +) +``` + +Vous avez déjà vu la plupart d'entre eux. Nous définissons quelques hyperparamètres (comme le taux d'apprentissage, le nombre d'époques à entraîner, et le taux de décroissance des poids), et nous spécifions `push_to_hub=True` pour indiquer que nous voulons sauvegarder le modèle, l'évaluer à la fin de chaque époque, et que nous voulons télécharger nos résultats vers le *Hub*. Notez que vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/bert-finetuned-ner"``TrainingArguments`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé d'après le répertoire de sortie que vous avez défini, donc dans notre cas ce sera `"sgugger/bert-finetuned-ner"`. + + + +💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de la définition de votre `Trainer` et devrez définir un nouveau nom. + + + +Enfin, nous passons tout au `Trainer` et lançons l'entraînement : + +```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() +``` + +Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. De cette façon, vous serez en mesure de reprendre votre entraînement sur une autre machine si nécessaire. + +Une fois l'entraînement terminé, nous utilisons la méthode `push_to_hub()` pour nous assurer que nous téléchargeons la version la plus récente du modèle : + +```py +trainer.push_to_hub(commit_message="Training complete") +``` + +Cette commande renvoie l'URL du commit qu'elle vient de faire, si vous voulez l'inspecter : + +```python out +'https://huggingface.co/sgugger/bert-finetuned-ner/commit/26ab21e5b1568f9afeccdaed2d8715f571d786ed' +``` + +Le `Trainer` rédige également une carte modèle avec tous les résultats de l'évaluation et la télécharge. A ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à affiner un modèle sur une tâche de classification de *tokens*. Félicitations ! + +Si vous voulez plonger un peu plus profondément dans la boucle d'entraînement, nous allons maintenant vous montrer comment faire la même chose en utilisant 🤗 *Accelerate*. + +## Une boucle d'entraînement personnalisée + +Jetons maintenant un coup d'œil à la boucle d'entraînement complète afin que vous puissiez facilement personnaliser les parties dont vous avez besoin. Elle ressemblera beaucoup à ce que nous avons fait dans le [chapitre 3](/course/fr/chapter3/4) avec quelques changements pour l'évaluation. + +### Préparer tout pour l'entraînement + +D'abord nous devons construire le `DataLoader`s à partir de nos jeux de données. Nous réutilisons notre `data_collator` comme un `collate_fn` et mélanger l'ensemble d'entraînement, mais pas l'ensemble de validation : + +```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 +) +``` + +Ensuite, nous réinstantifions notre modèle pour nous assurer que nous ne continuons pas le *finetuning* d'avant et que nous repartons bien du modèle pré-entraîné de BERT : + +```py +model = AutoModelForTokenClassification.from_pretrained( + model_checkpoint, + id2label=id2label, + label2id=label2id, +) +``` + +Ensuite, nous avons besoin d'un optimiseur. Nous utilisons le classique `AdamW`, qui est comme `Adam`, mais avec un correctif dans la façon dont le taux de décroissance des poids est appliquée : + +```py +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Une fois que nous avons tous ces objets, nous pouvons les envoyer à la méthode `accelerator.prepare()` : + +```py +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + + + +🚨 Si vous entraînez sur un TPU, vous devrez déplacer tout le code à partir de la cellule ci-dessus dans une fonction d'entraînement dédiée. Voir le [chapitre 3](/course/fr/chapter3) pour plus de détails. + + + +Maintenant que nous avons envoyé notre `train_dataloader` à `accelerator.prepare()`, nous pouvons utiliser sa longueur pour calculer le nombre d'étapes d'entraînement. Rappelez-vous que nous devrions toujours faire cela après avoir préparé le *dataloader* car cette méthode modifiera sa longueur. Nous utilisons un programme linéaire classique du taux d'apprentissage à 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, +) +``` + +Enfin, pour pousser notre modèle vers le *Hub*, nous avons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous à Hugging Face si vous n'êtes pas déjà connecté. Nous déterminons le nom du dépôt à partir de l'identifiant du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix, il doit juste contenir votre nom d'utilisateur et ce que fait la fonction `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' +``` + +Ensuite, nous pouvons cloner ce dépôt dans un dossier local. S'il existe déjà, ce dossier local doit être un clone existant du dépôt avec lequel nous travaillons : + +```py +output_dir = "bert-finetuned-ner-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output_dir` en appelant la méthode `repo.push_to_hub()`. Cela nous aidera à télécharger les modèles intermédiaires à la fin de chaque époque. + +### Boucle d'entraînement + +Nous sommes maintenant prêts à écrire la boucle d'entraînement complète. Pour simplifier sa partie évaluation, nous définissons cette fonction `postprocess()` qui prend les prédictions et les étiquettes, et les convertit en listes de chaînes de caractères comme notre objet `metric` l'attend : + +```py +def postprocess(predictions, labels): + predictions = predictions.detach().cpu().clone().numpy() + labels = labels.detach().cpu().clone().numpy() + + # Suppression de l'index ignoré (tokens spéciaux) et conversion en étiquettes + 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 +``` + +Ensuite, nous pouvons écrire la boucle d'entraînement. Après avoir défini une barre de progression pour suivre l'évolution de l'entraînement, la boucle comporte trois parties : + +- L'entraînement proprement dit, qui est l'itération classique sur le `train_dataloader`, passage en avant, puis passage en arrière et étape d'optimisation. +- L'évaluation, dans laquelle il y a une nouveauté après avoir obtenu les sorties de notre modèle sur un batch : puisque deux processus peuvent avoir paddé les entrées et les étiquettes à des formes différentes, nous devons utiliser `accelerator.pad_across_processes()` pour rendre les prédictions et les étiquettes de la même forme avant d'appeler la méthode `gather()`. Si nous ne le faisons pas, l'évaluation va soit se tromper, soit se bloquer pour toujours. Ensuite, nous envoyons les résultats à `metric.add_batch()` et appelons `metric.compute()` une fois que la boucle d'évaluation est terminée. +- Sauvegarde et téléchargement, où nous sauvegardons d'abord le modèle et le *tokenizer*, puis appelons `repo.push_to_hub()`. Remarquez que nous utilisons l'argument `blocking=False` pour indiquer à la bibliothèque 🤗 *Hub* de pousser dans un processus asynchrone. De cette façon, l'entraînement continue normalement et cette (longue) instruction est exécutée en arrière-plan. + +Voici le code complet de la boucle d'entraînement : + +```py +from tqdm.auto import tqdm +import torch + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Entraînement + 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) + + # Evaluation + model.eval() + for batch in eval_dataloader: + with torch.no_grad(): + outputs = model(**batch) + + predictions = outputs.logits.argmax(dim=-1) + labels = batch["labels"] + + # Nécessaire pour rembourrer les prédictions et les étiquettes à rassembler + 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"] + }, + ) + + # Sauvegarder et télécharger + 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 + ) +``` + +Au cas où ce serait la première fois que vous verriez un modèle enregistré avec 🤗 *Accelerate*, prenons un moment pour inspecter les trois lignes de code qui l'accompagnent : + +```py +accelerator.wait_for_everyone() +unwrapped_model = accelerator.unwrap_model(model) +unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) +``` + +La première ligne est explicite : elle indique à tous les processus d'attendre que tout le monde soit à ce stade avant de continuer. C'est pour s'assurer que nous avons le même modèle dans chaque processus avant de sauvegarder. Ensuite, nous prenons le `unwrapped_model` qui est le modèle de base que nous avons défini. La méthode `accelerator.prepare()` modifie le modèle pour qu'il fonctionne dans l'entraînement distribué, donc il n'aura plus la méthode `save_pretrained()` ; la méthode `accelerator.unwrap_model()` annule cette étape. Enfin, nous appelons `save_pretrained()` mais nous disons à cette méthode d'utiliser `accelerator.save()` au lieu de `torch.save()`. + +Une fois ceci fait, vous devriez avoir un modèle qui produit des résultats assez similaires à celui entraîné avec le `Trainer`. Vous pouvez vérifier le modèle que nous avons formé en utilisant ce code à [*huggingface-course/bert-finetuned-ner-accelerate*](https://huggingface.co/huggingface-course/bert-finetuned-ner-accelerate). Et si vous voulez tester des modifications de la boucle d'entraînement, vous pouvez les implémenter directement en modifiant le code ci-dessus ! + +{/if} + +### Utilisation du modèle finetuné + +Nous vous avons déjà montré comment vous pouvez utiliser le modèle *finetuné* sur le *Hub* avec le *widget* d'inférence. Pour l'utiliser localement dans un `pipeline`, vous devez juste spécifier l'identifiant de modèle approprié : + +```py +from transformers import pipeline + +# Remplacez ceci par votre propre checkpoint +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}] +``` + +Super ! Notre modèle fonctionne aussi bien que le modèle par défaut pour ce pipeline ! + diff --git a/chapters/fr/chapter7/3.mdx b/chapters/fr/chapter7/3.mdx index 89cd6a843..96f3b04ff 100644 --- a/chapters/fr/chapter7/3.mdx +++ b/chapters/fr/chapter7/3.mdx @@ -1,1042 +1,1042 @@ - - -# Finetuner un modèle de langage masqué - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -Pour de nombreuses applications de NLP impliquant des *transformers*, vous pouvez simplement prendre un modèle pré-entraîné du *Hub* et le *finetuner* directement sur vos données pour la tâche à accomplir. Pour autant que le corpus utilisé pour le pré-entraînement ne soit pas trop différent du corpus utilisé pour le *finetuning*. L'apprentissage par transfert produira généralement de bons résultats. - -Cependant, il existe quelques cas où vous voudrez d'abord *finetuner* les modèles de langue sur vos données, avant d'entraîner une tête spécifique à la tâche. Par exemple, si votre jeu de données contient des contrats légaux ou des articles scientifiques, un *transformer* classique comme BERT traitera généralement les mots spécifiques au domaine dans votre corpus comme des *tokens* rares et les performances résultantes peuvent être moins que satisfaisantes. En *finetunant* le modèle de langage sur les données du domaine, vous pouvez améliorer les performances de nombreuses tâches en aval, ce qui signifie que vous ne devez généralement effectuer cette étape qu'une seule fois ! - -Ce processus de *finetuning* d'un modèle de langage pré-entraîné sur des données *dans le domaine* est généralement appelé _adaptation au domaine_. Il a été popularisé en 2018 par [ULMFiT](https://arxiv.org/abs/1801.06146) qui a été l'une des premières architectures neuronales (basées sur des LSTMs) à faire en sorte que l'apprentissage par transfert fonctionne réellement pour le NLP. Un exemple d'adaptation de domaine avec ULMFiT est présenté dans l'image ci-dessous. Dans cette section, nous ferons quelque chose de similaire mais avec un *transformer* au lieu d'une LSTM ! - -
-ULMFiT. - -
- -À la fin de cette section, vous aurez un [modèle de langage masqué](https://huggingface.co/huggingface-course/distilbert-base-uncased-finetuned-imdb?text=This+is+a+great+%5BMASK%5D.) sur le *Hub* qui peut autocompléter des phrases comme indiqué ci-dessous : - - - - -Allons-y ! - - - - - -🙋 Si les termes « modélisation du langage masqué » et « modèle pré-entraîné » ne vous sont pas familiers, consultez le [chapitre 1](/course/fr/chapiter1), où nous expliquons tous ces concepts fondamentaux, vidéos à l'appui ! - - - -## Choix d'un modèle pré-entraîné pour la modélisation du langage masqué - -Pour commencer, nous allons choisir un modèle pré-entraîné approprié pour la modélisation du langage masqué. Comme le montre la capture d'écran suivante, vous pouvez trouver une liste de candidats en appliquant le filtre « *Fill-Mask* » sur le [*Hub*](https://huggingface.co/models?pipeline_tag=fill-mask&sort=downloads) : - -
-Hub models. -
- -Bien que les modèles de la famille BERT et RoBERTa soient les plus téléchargés, nous utiliserons un modèle appelé [DistilBERT](https://huggingface.co/distilbert-base-uncased) qui peut être entraîné beaucoup plus rapidement avec peu ou pas de perte de performance en aval. Ce modèle a été entraîné à l'aide d'une technique spéciale appelée [_distillation de connaissances_](https://en.wikipedia.org/wiki/Knowledge_distillation), où un grand modèle *enseignant* comme BERT est utilisé pour guider l'entraînement d'un modèle *étudiant* qui a beaucoup moins de paramètres. Une explication des détails de la distillation de connaissances nous mènerait trop loin dans cette section mais si vous êtes intéressé, vous pouvez lire tout cela dans le livre [_Natural Language Processing with Transformers_](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/ch05.html). - -{#if fw === 'pt'} - -Allons-y et téléchargeons DistilBERT en utilisant la classe `AutoModelForMaskedLM` : - -```python -from transformers import AutoModelForMaskedLM - -model_checkpoint = "distilbert-base-uncased" -model = AutoModelForMaskedLM.from_pretrained(model_checkpoint) -``` - -Nous pouvons voir combien de paramètres ce modèle possède en appelant la méthode `num_parameters()` : - -```python -distilbert_num_parameters = model.num_parameters() / 1_000_000 -print(f"'>>> DistilBERT nombre de paramètres : {round(distilbert_num_parameters)}M'") -print(f"'>>> BERT nombre de paramètres : 110M'") -``` - -```python out -'>>> DistilBERT nombre de paramètres : 67M' -'>>> BERT nombre de paramètres : 110M' -``` - -{:else} - -Allons-y et téléchargeons DistilBERT en utilisant la classe `AutoModelForMaskedLM` : - -```python -from transformers import TFAutoModelForMaskedLM - -model_checkpoint = "distilbert-base-uncased" -model = TFAutoModelForMaskedLM.from_pretrained(model_checkpoint) -``` - -Nous pouvons voir combien de paramètres ce modèle possède en appelant la méthode `summary()` : - -```python -model(model.dummy_inputs) # Construire le modèle -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} - -Avec environ 67 millions de paramètres, DistilBERT est environ deux fois plus petit que le modèle de base de BERT, ce qui se traduit approximativement par une accélération de l'entraînement d'un facteur deux. Voyons maintenant quels types de *tokens* ce modèle prédit comme étant les compléments les plus probables d'un petit échantillon de texte : - -```python -text = "This is a great [MASK]." -``` - -En tant qu'êtres humains, nous pouvons imaginer de nombreuses possibilités pour le *token* `[MASK]`, telles que « jour », « promenade » ou « peinture ». Pour les modèles pré-entraînés, les prédictions dépendent du corpus sur lequel le modèle a été entraîné puisqu'il apprend à détecter les modèles statistiques présents dans les données. Comme BERT, DistilBERT a été pré-entraîné sur les jeux de données [*English Wikipedia*](https://huggingface.co/datasets/wikipedia) et [*BookCorpus*](https://huggingface.co/datasets/bookcorpus), nous nous attendons donc à ce que les prédictions pour `[MASK]` reflètent ces domaines. Pour prédire le masque, nous avons besoin du *tokenizer* de DistilBERT pour produire les entrées du modèle, alors téléchargeons-le également depuis le *Hub* : - -```python -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) -``` - -Avec un *tokenizer* et un modèle, nous pouvons maintenant passer notre exemple de texte au modèle, extraire les logits, et afficher les 5 meilleurs candidats : - -{#if fw === 'pt'} - -```python -import torch - -inputs = tokenizer(text, return_tensors="pt") -token_logits = model(**inputs).logits -# Trouve l'emplacement de [MASK] et extrait ses logits -mask_token_index = torch.where(inputs["input_ids"] == tokenizer.mask_token_id)[1] -mask_token_logits = token_logits[0, mask_token_index, :] -# Choisissez les candidats [MASK] avec les logits les plus élevés -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 -# Trouve l'emplacement de [MASK] et extrait ses logits -mask_token_index = np.argwhere(inputs["input_ids"] == tokenizer.mask_token_id)[0, 1] -mask_token_logits = token_logits[0, mask_token_index, :] -# On choisit les candidats [MASK] avec les logits les plus élevés -# Nous annulons le tableau avant argsort pour obtenir le plus grand, et non le plus petit, logits -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.' # C'est une bonne affaire -'>>> This is a great success.' # C'est un grand succès -'>>> This is a great adventure.' # C'est une grande aventure -'>>> This is a great idea.' # C'est une bonne idée -'>>> This is a great feat.' # C'est un grand exploit -``` - -Nous pouvons voir dans les sorties que les prédictions du modèle se réfèrent à des termes de tous les jours, ce qui n'est peut-être pas surprenant étant donné le fondement de Wikipédia. Voyons comment nous pouvons changer ce domaine pour quelque chose d'un peu plus spécialisé : des critiques de films ! - - -## Le jeu de données - -Pour illustrer l'adaptation au domaine, nous utiliserons le célèbre [*Large Movie Review Dataset*](https://huggingface.co/datasets/imdb) (ou IMDb en abrégé), qui est un corpus de critiques de films souvent utilisé pour évaluer les modèles d'analyse de sentiments. En *finetunant* DistilBERT sur ce corpus, nous espérons que le modèle de langage adaptera son vocabulaire des données factuelles de Wikipédia sur lesquelles il a été pré-entraîné aux éléments plus subjectifs des critiques de films. Nous pouvons obtenir les données du *Hub* avec la fonction `load_dataset()` de 🤗 *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 - }) -}) -``` - -Nous pouvons voir que les parties `train` et `test` sont chacune composées de 25 000 critiques, alors qu'il y a une partie non étiquetée appelée `unsupervised` qui contient 50 000 critiques. Jetons un coup d'œil à quelques échantillons pour avoir une idée du type de texte auquel nous avons affaire. Comme nous l'avons fait dans les chapitres précédents du cours, nous allons enchaîner les fonctions `Dataset.shuffle()` et `Dataset.select()` pour créer un échantillon aléatoire : - -```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' -``` - -Oui, ce sont bien des critiques de films, et si vous êtes assez âgés, vous pouvez même comprendre le commentaire dans la dernière critique sur le fait de posséder une version VHS 😜 ! Bien que nous n'ayons pas besoin des étiquettes pour la modélisation du langage, nous pouvons déjà voir qu'un `0` dénote une critique négative, tandis qu'un `1` correspond à une critique positive. - - - -✏️ **Essayez !** Créez un échantillon aléatoire de la répartition `unsupervised` et vérifiez que les étiquettes ne sont ni `0` ni `1`. Pendant que vous y êtes, vous pouvez aussi vérifier que les étiquettes dans les échantillons `train` et `test` sont bien `0` ou `1`. C'est un contrôle utile que tout praticien en NLP devrait effectuer au début d'un nouveau projet ! - - - -Maintenant que nous avons jeté un coup d'œil rapide aux données, plongeons dans leur préparation pour la modélisation du langage masqué. Comme nous allons le voir, il y a quelques étapes supplémentaires à suivre par rapport aux tâches de classification de séquences que nous avons vues au [chapitre 3](/course/fr/chapter3). Allons-y ! - -## Prétraitement des données - - - -Pour la modélisation autorégressive et la modélisation du langage masqué, une étape commune de prétraitement consiste à concaténer tous les exemples, puis à diviser le corpus entier en morceaux de taille égale. C'est très différent de notre approche habituelle, où nous nous contentons de *tokenizer* les exemples individuels. Pourquoi tout concaténer ? La raison est que les exemples individuels peuvent être tronqués s'ils sont trop longs, ce qui entraînerait la perte d'informations qui pourraient être utiles pour la tâche de modélisation du langage ! - -Donc pour commencer, nous allons d'abord tokeniser notre corpus comme d'habitude, mais _sans_ mettre l'option `truncation=True` dans notre *tokenizer*. Nous allons aussi récupérer les identifiants des mots s'ils sont disponibles (ce qui sera le cas si nous utilisons un *tokenizer* rapide, comme décrit dans le [chapitre 6](/course/fr/chapter6/3)), car nous en aurons besoin plus tard pour faire le masquage de mots entiers. Nous allons envelopper cela dans une simple fonction, et pendant que nous y sommes, nous allons supprimer les colonnes `text` et `label` puisque nous n'en avons plus besoin : - -```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 - - -# Utilisation de batched=True pour activer le multithreading rapide ! -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 - }) -}) -``` - -Comme DistilBERT est un modèle de type BERT, nous pouvons voir que les textes encodés sont constitués des `input_ids` et des `attention_mask` que nous avons vus dans d'autres chapitres, ainsi que des `word_ids` que nous avons ajoutés. - -Maintenant que nos critiques de films ont été tokenisées, l'étape suivante consiste à les regrouper et à diviser le résultat en chunks. Mais quelle taille doivent avoir ces *chunks* ? Cela sera finalement déterminé par la quantité de mémoire GPU dont vous disposez, mais un bon point de départ est de voir quelle est la taille maximale du contexte du modèle. Cela peut être déduit en inspectant l'attribut `model_max_length` du *tokenizer* : - -```python -tokenizer.model_max_length -``` - - -```python out -512 -``` - -Cette valeur est dérivée du fichier *tokenizer_config.json* associé à un *checkpoint*. Dans ce cas, nous pouvons voir que la taille du contexte est de 512 *tokens*, tout comme avec BERT. - - - -✏️ **Essayez !** Certains *transformers*, comme [BigBird](https://huggingface.co/google/bigbird-roberta-base) et [Longformer](hf.co/allenai/longformer-base-4096), ont une longueur de contexte beaucoup plus longue que BERT et les autres premiers *transformers*. Instanciez le *tokenizer* pour l'un de ces *checkpoints* et vérifiez que le `model_max_length` correspond à ce qui est indiqué sur sa carte. - - - -Ainsi, pour réaliser nos expériences sur des GPUs comme ceux disponibles sur Google Colab, nous choisirons quelque chose d'un peu plus petit qui peut tenir en mémoire : - -```python -chunk_size = 128 -``` - - - -Notez que l'utilisation d'une petite taille peut être préjudiciable dans les scénarios du monde réel. Vous devez donc utiliser une taille qui correspond au cas d'utilisation auquel vous appliquerez votre modèle. - - - -Maintenant vient la partie amusante. Pour montrer comment la concaténation fonctionne, prenons quelques commentaires de notre ensemble d'entraînement et affichons le nombre de *tokens* par commentaire : - -```python -# Le découpage produit une liste de listes pour chaque caractéristique -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' -``` - -Nous pouvons ensuite concaténer tous ces exemples avec une simple compréhension du dictionnaire, comme suit : - -```python -concatenated_examples = { - k: sum(tokenized_samples[k], []) for k in tokenized_samples.keys() -} -total_length = len(concatenated_examples["input_ids"]) -print(f"'>>> Longueur des critiques concaténées : {total_length}'") -``` - -```python out -'>>> Longueur des critiques concaténées : 951' -``` - -Super, la longueur totale est correcte. Donc maintenant, nous allons diviser les exemples concaténés en morceaux de la taille donnée par `block_size`. Pour ce faire, nous itérons sur les caractéristiques de `concatenated_examples` et utilisons une compréhension de liste pour créer des *chunks* de chaque caractéristique. Le résultat est un dictionnaire de *chunks* pour chaque caractéristique : - -```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' -``` - -Comme vous pouvez le voir dans cet exemple, le dernier *chunk* sera généralement plus petit que la taille maximale des morceaux. Il y a deux stratégies principales pour gérer cela : - -* Abandonner le dernier morceau s'il est plus petit que `chunk_size`. -* Rembourrer le dernier morceau jusqu'à ce que sa longueur soit égale à `chunk_size`. - -Nous adopterons la première approche ici, donc nous allons envelopper toute la logique ci-dessus dans une seule fonction que nous pouvons appliquer à nos jeux de données tokenisés : - -```python -def group_texts(examples): - # Concaténation de tous les textes - concatenated_examples = {k: sum(examples[k], []) for k in examples.keys()} - # Calcule la longueur des textes concaténés - total_length = len(concatenated_examples[list(examples.keys())[0]]) - # Nous laissons tomber le dernier morceau s'il est plus petit que chunk_size - total_length = (total_length // chunk_size) * chunk_size - # Fractionnement par chunk de 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() - } - # Créer une nouvelle colonne d'étiquettes - result["labels"] = result["input_ids"].copy() - return result -``` - -Notez que dans la dernière étape de `group_texts()` nous créons une nouvelle colonne `labels` qui est une copie de la colonne `input_ids`. Comme nous le verrons bientôt, c'est parce que dans la modélisation du langage masqué, l'objectif est de prédire des *tokens* masqués aléatoirement dans le batch d'entrée, et en créant une colonne `labels`, nous fournissons la vérité de base pour notre modèle de langage à apprendre. - -Appliquons maintenant `group_texts()` à nos jeux de données tokenisés en utilisant notre fidèle fonction `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 - }) -}) -``` - -Vous pouvez voir que le regroupement puis le découpage des textes a produit beaucoup plus d'exemples que nos 25 000 exemples initiaux pour les divisions `train` et `test`. C'est parce que nous avons maintenant des exemples impliquant des *tokens* contigus qui s'étendent sur plusieurs exemples du corpus original. Vous pouvez le voir explicitement en cherchant les *tokens* spéciaux `[SEP]` et `[CLS]` dans l'un des *chunks* : - -```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" -``` - -Dans cet exemple, vous pouvez voir deux critiques de films qui se chevauchent, l'une sur un film de lycée et l'autre sur les sans-abri. Voyons également à quoi ressemblent les étiquettes pour la modélisation du langage masqué : - -```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" -``` - -Comme prévu par notre fonction `group_texts()` ci-dessus, cela semble identique aux `input_ids` décodés. Mais alors comment notre modèle peut-il apprendre quoi que ce soit ? Il nous manque une étape clé : insérer des *tokens* à des positions aléatoires dans les entrées ! Voyons comment nous pouvons le faire à la volée pendant le *finetuning* en utilisant un collateur de données spécial. - -## Finetuning de DistilBERT avec l'API `Trainer` - -Le *finetuning* d'un modèle de langage masqué est presque identique au *finetuning* d'un modèle de classification de séquences, comme nous l'avons fait dans le [chapitre 3](/course/fr/chapter3). La seule différence est que nous avons besoin d'un collecteur de données spécial qui peut masquer de manière aléatoire certains des *tokens* dans chaque batch de textes. Heureusement, 🤗 *Transformers* est livré préparé avec un `DataCollatorForLanguageModeling` dédié à cette tâche. Nous devons juste lui passer le *tokenizer* et un argument `mlm_probability` qui spécifie quelle fraction des *tokens* à masquer. Nous choisirons 15%, qui est la quantité utilisée pour BERT et un choix commun dans la littérature : - -```python -from transformers import DataCollatorForLanguageModeling - -data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm_probability=0.15) -``` - -Pour voir comment le masquage aléatoire fonctionne, nous allons donner quelques exemples au collateur de données. Puisqu'il s'attend à une liste de `dict` où chaque `dict` représente un seul morceau de texte contigu, nous itérons d'abord sur le jeu de données avant de donner le batch au collateur. Nous supprimons la clé `"word_ids"` pour ce collateur de données car il ne l'attend pas : - -```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' -``` - -Super, ça a marché ! Nous pouvons voir que le *token* `[MASK]` a été inséré de façon aléatoire à différents endroits dans notre texte. Ce seront les *tokens* que notre modèle devra prédire pendant l'entraînement. Et la beauté du collecteur de données est qu'il va rendre aléatoire l'insertion du `[MASK]` à chaque batch ! - - - -✏️ **Essayez** Exécutez le code ci-dessus plusieurs fois pour voir le masquage aléatoire se produire sous vos yeux ! Remplacez aussi la méthode `tokenizer.decode()` par `tokenizer.convert_ids_to_tokens()` pour voir que parfois un seul *token* d'un mot donné est masqué et pas les autres. - - - -{#if fw === 'pt'} - -Un effet secondaire du masquage aléatoire est que nos métriques d'évaluation ne seront pas déterministes lorsque nous utilisons la fonction `Trainer` puisque nous utilisons le même collateur de données pour les échantillons d'entraînement et de test. Nous verrons plus tard, lorsque nous examinerons le *finetuning* avec 🤗 *Accelerate*, comment nous pouvons utiliser la flexibilité d'une boucle d'évaluation personnalisée pour geler le caractère aléatoire. - -{/if} - -Lors de l'entraînement des modèles pour la modélisation du langage masqué, une technique qui peut être utilisée est de masquer des mots entiers ensemble et pas seulement des *tokens* individuels. Cette approche est appelée _masquage de mots entiers_. Si nous voulons utiliser le masquage de mots entiers, nous devons construire nous-mêmes un collateur de données. Un collateur de données est simplement une fonction qui prend une liste d'échantillons et les convertit en un batch. Faisons-le ! Nous utiliserons les identifiants des mots calculés plus tôt pour faire une correspondance entre les indices des mots et les *tokens*, puis nous déciderons aléatoirement quels mots masquer et appliquerons ce masque sur les entrées. Notez que les étiquettes sont toutes `-100` sauf celles qui correspondent aux mots masqués. - -{#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") - - # Création d'une correspondance entre les mots et les indices des tokens correspondants - 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) - - # Masquer des mots de façon aléatoire - 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 - - 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") - - # Création d'une correspondance entre les mots et les indices des tokens correspondants - 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) - - # Masquer des mots de façon aléatoire - 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 - - return tf_default_data_collator(features) -``` - -{/if} - -Ensuite, nous pouvons l'essayer sur les mêmes échantillons que précédemment : - -```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' -``` - - - -✏️ **Essayez** Exécutez le code ci-dessus plusieurs fois pour voir le masquage aléatoire se produire sous vos yeux ! Remplacez aussi la méthode `tokenizer.decode()` par `tokenizer.convert_ids_to_tokens()` pour voir que les *tokens* d'un mot donné sont toujours masqués ensemble. - - - -Maintenant que nous avons deux collateurs de données, les étapes restantes du *finetuning* sont standards. L'entraînement peut prendre un certain temps sur Google Colab si vous n'avez pas la chance de tomber sur un mythique GPU P100 😭. Ainsi nous allons d'abord réduire la taille du jeu d'entraînement à quelques milliers d'exemples. Ne vous inquiétez pas, nous obtiendrons quand même un modèle de langage assez décent ! Un moyen rapide de réduire la taille d'un jeu de données dans 🤗 *Datasets* est la fonction `Dataset.train_test_split()` que nous avons vue au [chapitre 5](/course/fr/chapter5) : - -```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 - }) -}) -``` - -Cela a automatiquement créé de nouvelles divisions `train` et `test` avec la taille du jeu d'entraînement fixée à 10.000 exemples et la validation fixée à 10% de cela. N'hésitez pas à augmenter la taille si vous avez un GPU puissant ! La prochaine chose que nous devons faire est de nous connecter au *Hub*. Si vous exécutez ce code dans un *notebook*, vous pouvez le faire avec la fonction suivante : - -```python -from huggingface_hub import notebook_login - -notebook_login() -``` - -qui affichera un *widget* où vous pourrez saisir vos informations d'identification. Alternativement, vous pouvez exécuter : - -``` -huggingface-cli login -``` - -dans votre terminal préféré et connectez-vous là. - -{#if fw === 'tf'} - -Une fois que nous sommes connectés, nous pouvons créer nos jeux de données `tf.data`. Nous n'utiliserons ici que le collecteur de données standard, mais vous pouvez également essayer le collecteur de masquage de mots entiers et comparer les résultats à titre d'exercice : - -```python -tf_train_dataset = downsampled_dataset["train"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=True, - batch_size=32, -) - -tf_eval_dataset = downsampled_dataset["test"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=False, - batch_size=32, -) -``` - -Ensuite, nous configurons nos hyperparamètres d'entraînement et compilons notre modèle. Nous utilisons la fonction `create_optimizer()` de la bibliothèque 🤗 *Transformers*, qui nous donne un optimiseur `AdamW` avec une décroissance linéaire du taux d'apprentissage. Nous utilisons également la perte intégrée au modèle, qui est la perte par défaut lorsqu'aucune perte n'est spécifiée comme argument de `compile()`, et nous définissons la précision d'entraînement à `"mixed_float16"`. Notez que si vous utilisez un GPU Colab ou un autre GPU qui n'a pas le support accéléré en float16, vous devriez probablement commenter cette ligne. - -De plus, nous mettons en place un `PushToHubCallback` qui sauvegardera le modèle sur le *Hub* après chaque époque. Vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, pour pousser le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/distilbert-finetuned-imdb"`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, donc dans notre cas, ce sera `"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) - -# Entraîner en mixed-precision float16 -tf.keras.mixed_precision.set_global_policy("mixed_float16") - -callback = PushToHubCallback( - output_dir=f"{model_name}-finetuned-imdb", tokenizer=tokenizer -) -``` - -Nous sommes maintenant prêts à exécuter `model.fit()`. Mais avant, regardons brièvement la _perplexité_ qui est une métrique commune pour évaluer la performance des modèles de langage. - -{:else} - -Une fois que nous sommes connectés, nous pouvons spécifier les arguments pour le `Trainer` : - -```python -from transformers import TrainingArguments - -batch_size = 64 -# Montrer la perte d'entraînement à chaque époque -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, -) -``` - -Ici, nous avons modifié quelques options par défaut, y compris `logging_steps` pour s'assurer que nous suivons la perte d'entraînement à chaque époque. Nous avons également utilisé `fp16=True` pour activer l'entraînement en précision mixte, ce qui nous donne un autre gain de vitesse. Par défaut, `Trainer` va supprimer toutes les colonnes qui ne font pas partie de la méthode `forward()` du modèle. Cela signifie que si vous utilisez le collateur de masquage de mots entiers, vous devrez également définir `remove_unused_columns=False` pour vous assurer que nous ne perdons pas la colonne `word_ids` pendant l'entraînement. - -Notez que vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/distilbert-finetuned-imdb"` `TrainingArguments`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, donc dans notre cas ce sera `"lewtun/distilbert-finetuned-imdb"`. - -Nous avons maintenant tous les ingrédients pour instancier le `Trainer`. Ici, nous utilisons juste le collateur standard `data_collator`, mais vous pouvez essayer le collateur de masquage de mots entiers et comparer les résultats comme exercice : - -```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, -) -``` - -Nous sommes maintenant prêts à exécuter `trainer.train()`. Mais avant, regardons brièvement la _perplexité_ qui est une métrique commune pour évaluer la performance des modèles de langage. - -{/if} - -### Perplexité pour les modèles de langage - - - -Contrairement à d'autres tâches, comme la classification de textes ou la réponse à des questions, sur lesquelles nous disposons d'un corpus étiqueté pour entraîner, la modélisation du langage ne s'appuie sur aucune étiquette explicite. Alors comment déterminer ce qui fait un bon modèle de langage ? Comme pour la fonction de correction automatique de votre téléphone, un bon modèle de langage est celui qui attribue des probabilités élevées aux phrases grammaticalement correctes et des probabilités faibles aux phrases absurdes. Pour vous donner une meilleure idée de ce à quoi cela ressemble, vous pouvez trouver en ligne des séries entières de « ratés d'autocorrection » où le modèle d'un téléphone produit des compléments plutôt amusants (et souvent inappropriés) ! - -{#if fw === 'pt'} - -En supposant que notre ensemble de test se compose principalement de phrases grammaticalement correctes, une façon de mesurer la qualité de notre modèle de langage est de calculer les probabilités qu'il attribue au mot suivant dans toutes les phrases de l'ensemble de test. Des probabilités élevées indiquent que le modèle n'est pas « surpris » ou « perplexe » vis-à-vis des exemples non vus, et suggèrent qu'il a appris les modèles de base de la grammaire de la langue. Il existe plusieurs définitions mathématiques de la perplexité. Celle que nous utiliserons la définit comme l'exponentielle de la perte d'entropie croisée. Ainsi, nous pouvons calculer la perplexité de notre modèle pré-entraîné en utilisant la fonction `Trainer.evaluate()` pour calculer la perte d'entropie croisée sur l'ensemble de test, puis en prenant l'exponentielle du résultat : - -```python -import math - -eval_results = trainer.evaluate() -print(f">>> Perplexity: {math.exp(eval_results['eval_loss']):.2f}") -``` - -{:else} - -En supposant que notre ensemble de test se compose principalement de phrases grammaticalement correctes, une façon de mesurer la qualité de notre modèle de langage est de calculer les probabilités qu'il attribue au mot suivant dans toutes les phrases de l'ensemble de test. Des probabilités élevées indiquent que le modèle n'est pas « surpris » ou « perplexe » vis-à-vis des exemples non vus, et suggèrent qu'il a appris les modèles de base de la grammaire de la langue. Il existe plusieurs définitions mathématiques de la perplexité. Celle que nous utiliserons la définit comme l'exponentielle de la perte d'entropie croisée. Ainsi, nous pouvons calculer la perplexité de notre modèle pré-entraîné en utilisant la fonction `model.evaluate()` pour calculer la perte d'entropie croisée sur l'ensemble de test, puis en prenant l'exponentielle du résultat : - -```python -import math - -eval_loss = model.evaluate(tf_eval_dataset) -print(f"Perplexité : {math.exp(eval_loss):.2f}") -``` - -{/if} - -```python out ->>> Perplexité : 21.75 -``` - -Un score de perplexité faible signifie un meilleur modèle de langue. Nous pouvons voir ici que notre modèle de départ a une valeur assez élevée. Voyons si nous pouvons la réduire en l'affinant ! Pour ce faire, nous commençons par exécuter la boucle d'entraînement : - -{#if fw === 'pt'} - -```python -trainer.train() -``` - -{:else} - -```python -model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback]) -``` - -{/if} - -et ensuite calculer la perplexité résultante sur l'ensemble de test comme précédemment : - -{#if fw === 'pt'} - -```python -eval_results = trainer.evaluate() -print(f">>> Perplexité : {math.exp(eval_results['eval_loss']):.2f}") -``` - -{:else} - -```python -eval_loss = model.evaluate(tf_eval_dataset) -print(f"Perplexité : {math.exp(eval_loss):.2f}") -``` - -{/if} - -```python out ->>> Perplexité : 11.32 -``` - -Joli. C'est une réduction considérable de la perplexité, ce qui nous indique que le modèle a appris quelque chose sur le domaine des critiques de films ! - -{#if fw === 'pt'} - -Une fois l'entraînement terminé, nous pouvons pousser la carte de modèle avec les informations d'entraînement vers le *Hub* (les *checkpoints* sont sauvegardés pendant l'entraînement lui-même) : - -```python -trainer.push_to_hub() -``` - -{/if} - - - -✏️ **A votre tour !** Exécutez l'entraînement ci-dessus après avoir remplacé le collecteur de données par le collecteur de mots entiers masqués. Obtenez-vous de meilleurs résultats ? - - - -{#if fw === 'pt'} - -Dans notre cas d'utilisation, nous n'avons pas eu besoin de faire quelque chose de spécial avec la boucle d'entraînement, mais dans certains cas, vous pourriez avoir besoin de mettre en œuvre une logique personnalisée. Pour ces applications, vous pouvez utiliser 🤗 *Accelerate*. Jetons un coup d'œil ! - -## Finetuning de DistilBERT avec 🤗 Accelerate - -Comme nous l'avons vu, avec `Trainer` le *finetuning* d'un modèle de langage masqué est très similaire à l'exemple de classification de texte du [chapitre 3](/course/fr/chapter3). En fait, la seule subtilité est l'utilisation d'un collateur de données spécial, et nous l'avons déjà couvert plus tôt dans cette section ! - -Cependant, nous avons vu que `DataCollatorForLanguageModeling` applique aussi un masquage aléatoire à chaque évaluation. Nous verrons donc quelques fluctuations dans nos scores de perplexité à chaque entrainement. Une façon d'éliminer cette source d'aléat est d'appliquer le masquage _une fois_ sur l'ensemble de test, puis d'utiliser le collateur de données par défaut dans 🤗 *Transformers* pour collecter les batchs pendant l'évaluation. Pour voir comment cela fonctionne, implémentons une fonction simple qui applique le masquage sur un batch, similaire à notre première rencontre avec `DataCollatorForLanguageModeling` : - -```python -def insert_random_mask(batch): - features = [dict(zip(batch, t)) for t in zip(*batch.values())] - masked_inputs = data_collator(features) - # Créer une nouvelle colonne "masquée" pour chaque colonne du jeu de données - return {"masked_" + k: v.numpy() for k, v in masked_inputs.items()} -``` - -Ensuite, nous allons appliquer cette fonction à notre jeu de test et laisser tomber les colonnes non masquées afin de les remplacer par les colonnes masquées. Vous pouvez utiliser le masquage de mots entiers en remplaçant le `data_collator` ci-dessus par celui qui est approprié. Dans ce cas vous devez supprimer la première ligne ici : - -```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", - } -) -``` - -Nous pouvons ensuite configurer les *dataloaders* comme d'habitude, mais nous utiliserons le `default_data_collator` de 🤗 *Transformers* pour le jeu d'évaluation : - -```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 -) -``` - -Nous suivons les étapes standard avec 🤗 *Accelerate*. La première est de charger une version fraîche du modèle pré-entraîné : - -``` -model = AutoModelForMaskedLM.from_pretrained(model_checkpoint) -``` - -Ensuite, nous devons spécifier l'optimiseur. Nous utiliserons le standard `AdamW` : - -```python -from torch.optim import AdamW - -optimizer = AdamW(model.parameters(), lr=5e-5) -``` - -Avec ces objets, nous pouvons maintenant tout préparer pour l'entraînement avec l'objet `Accelerator` : - -```python -from accelerate import Accelerator - -accelerator = Accelerator() -model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( - model, optimizer, train_dataloader, eval_dataloader -) -``` - -Maintenant que notre modèle, notre optimiseur et nos chargeurs de données sont configurés, nous pouvons spécifier le planificateur du taux d'apprentissage comme suit : - -```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, -) -``` - -Il ne reste qu'une dernière chose à faire avant de s'entraîner : créer un dépôt de modèles sur le *Hub* d'Hugging Face ! Nous pouvons utiliser la bibliothèque 🤗 *Hub* pour générer d'abord le nom complet de notre dépôt : - -```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' -``` - -puis créer et cloner le dépôt en utilisant la classe `Repository` du 🤗 *Hub* : - -```python -from huggingface_hub import Repository - -output_dir = model_name -repo = Repository(output_dir, clone_from=repo_name) -``` - -Une fois cela fait, il ne reste plus qu'à rédiger la boucle complète d'entraînement et d'évaluation : - -```python -from tqdm.auto import tqdm -import torch -import math - -progress_bar = tqdm(range(num_training_steps)) - -for epoch in range(num_train_epochs): - # Entraînement - 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) - - # Evaluation - 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}") - - # Sauvegarder et télécharger - 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 -``` - -Cool, nous avons été en mesure d'évaluer la perplexité à chaque époque et de garantir la reproductibilité des entraînements multiples ! - -{/if} - -### Utilisation de notre modèle finetuné - -Vous pouvez interagir avec votre modèle *finetuné* soit en utilisant son *widget* sur le *Hub*, soit localement avec le `pipeline` de 🤗 *Transformers*. Utilisons ce dernier pour télécharger notre modèle en utilisant le pipeline `fill-mask` : - -```python -from transformers import pipeline - -mask_filler = pipeline( - "fill-mask", model="huggingface-course/distilbert-base-uncased-finetuned-imdb" -) -``` - -Nous pouvons ensuite donner au pipeline notre exemple de texte « this is a great [MASK] » et voir quelles sont les 5 premières prédictions : - -```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.' -``` - -Notre modèle a clairement adapté ses pondérations pour prédire les mots qui sont plus fortement associés aux films ! - - - -Ceci conclut notre première expérience d'entraînement d'un modèle de langage. Dans la [section 6](/course/fr/chapter7/section6), vous apprendrez comment entraîner à partir de zéro un modèle autorégressif comme GPT-2. Allez-y si vous voulez voir comment vous pouvez pré-entraîner votre propre *transformer* ! - - - -✏️ **Essayez !** Pour quantifier les avantages de l'adaptation au domaine, finetunez un classifieur sur le jeu de données IMDb pour à la fois, le checkpoint de DistilBERT pré-entraîné et e checkpoint de DistilBERT finetuné. Si vous avez besoin d'un rafraîchissement sur la classification de texte, consultez le [chapitre 3](/course/fr/chapter3). - - + + +# Finetuner un modèle de langage masqué + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Pour de nombreuses applications de NLP impliquant des *transformers*, vous pouvez simplement prendre un modèle pré-entraîné du *Hub* et le *finetuner* directement sur vos données pour la tâche à accomplir. Pour autant que le corpus utilisé pour le pré-entraînement ne soit pas trop différent du corpus utilisé pour le *finetuning*. L'apprentissage par transfert produira généralement de bons résultats. + +Cependant, il existe quelques cas où vous voudrez d'abord *finetuner* les modèles de langue sur vos données, avant d'entraîner une tête spécifique à la tâche. Par exemple, si votre jeu de données contient des contrats légaux ou des articles scientifiques, un *transformer* classique comme BERT traitera généralement les mots spécifiques au domaine dans votre corpus comme des *tokens* rares et les performances résultantes peuvent être moins que satisfaisantes. En *finetunant* le modèle de langage sur les données du domaine, vous pouvez améliorer les performances de nombreuses tâches en aval, ce qui signifie que vous ne devez généralement effectuer cette étape qu'une seule fois ! + +Ce processus de *finetuning* d'un modèle de langage pré-entraîné sur des données *dans le domaine* est généralement appelé _adaptation au domaine_. Il a été popularisé en 2018 par [ULMFiT](https://arxiv.org/abs/1801.06146) qui a été l'une des premières architectures neuronales (basées sur des LSTMs) à faire en sorte que l'apprentissage par transfert fonctionne réellement pour le NLP. Un exemple d'adaptation de domaine avec ULMFiT est présenté dans l'image ci-dessous. Dans cette section, nous ferons quelque chose de similaire mais avec un *transformer* au lieu d'une LSTM ! + +
+ULMFiT. + +
+ +À la fin de cette section, vous aurez un [modèle de langage masqué](https://huggingface.co/huggingface-course/distilbert-base-uncased-finetuned-imdb?text=This+is+a+great+%5BMASK%5D.) sur le *Hub* qui peut autocompléter des phrases comme indiqué ci-dessous : + + + + +Allons-y ! + + + + + +🙋 Si les termes « modélisation du langage masqué » et « modèle pré-entraîné » ne vous sont pas familiers, consultez le [chapitre 1](/course/fr/chapiter1), où nous expliquons tous ces concepts fondamentaux, vidéos à l'appui ! + + + +## Choix d'un modèle pré-entraîné pour la modélisation du langage masqué + +Pour commencer, nous allons choisir un modèle pré-entraîné approprié pour la modélisation du langage masqué. Comme le montre la capture d'écran suivante, vous pouvez trouver une liste de candidats en appliquant le filtre « *Fill-Mask* » sur le [*Hub*](https://huggingface.co/models?pipeline_tag=fill-mask&sort=downloads) : + +
+Hub models. +
+ +Bien que les modèles de la famille BERT et RoBERTa soient les plus téléchargés, nous utiliserons un modèle appelé [DistilBERT](https://huggingface.co/distilbert-base-uncased) qui peut être entraîné beaucoup plus rapidement avec peu ou pas de perte de performance en aval. Ce modèle a été entraîné à l'aide d'une technique spéciale appelée [_distillation de connaissances_](https://en.wikipedia.org/wiki/Knowledge_distillation), où un grand modèle *enseignant* comme BERT est utilisé pour guider l'entraînement d'un modèle *étudiant* qui a beaucoup moins de paramètres. Une explication des détails de la distillation de connaissances nous mènerait trop loin dans cette section mais si vous êtes intéressé, vous pouvez lire tout cela dans le livre [_Natural Language Processing with Transformers_](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/ch05.html). + +{#if fw === 'pt'} + +Allons-y et téléchargeons DistilBERT en utilisant la classe `AutoModelForMaskedLM` : + +```python +from transformers import AutoModelForMaskedLM + +model_checkpoint = "distilbert-base-uncased" +model = AutoModelForMaskedLM.from_pretrained(model_checkpoint) +``` + +Nous pouvons voir combien de paramètres ce modèle possède en appelant la méthode `num_parameters()` : + +```python +distilbert_num_parameters = model.num_parameters() / 1_000_000 +print(f"'>>> DistilBERT nombre de paramètres : {round(distilbert_num_parameters)}M'") +print(f"'>>> BERT nombre de paramètres : 110M'") +``` + +```python out +'>>> DistilBERT nombre de paramètres : 67M' +'>>> BERT nombre de paramètres : 110M' +``` + +{:else} + +Allons-y et téléchargeons DistilBERT en utilisant la classe `AutoModelForMaskedLM` : + +```python +from transformers import TFAutoModelForMaskedLM + +model_checkpoint = "distilbert-base-uncased" +model = TFAutoModelForMaskedLM.from_pretrained(model_checkpoint) +``` + +Nous pouvons voir combien de paramètres ce modèle possède en appelant la méthode `summary()` : + +```python +model(model.dummy_inputs) # Construire le modèle +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} + +Avec environ 67 millions de paramètres, DistilBERT est environ deux fois plus petit que le modèle de base de BERT, ce qui se traduit approximativement par une accélération de l'entraînement d'un facteur deux. Voyons maintenant quels types de *tokens* ce modèle prédit comme étant les compléments les plus probables d'un petit échantillon de texte : + +```python +text = "This is a great [MASK]." +``` + +En tant qu'êtres humains, nous pouvons imaginer de nombreuses possibilités pour le *token* `[MASK]`, telles que « jour », « promenade » ou « peinture ». Pour les modèles pré-entraînés, les prédictions dépendent du corpus sur lequel le modèle a été entraîné puisqu'il apprend à détecter les modèles statistiques présents dans les données. Comme BERT, DistilBERT a été pré-entraîné sur les jeux de données [*English Wikipedia*](https://huggingface.co/datasets/wikipedia) et [*BookCorpus*](https://huggingface.co/datasets/bookcorpus), nous nous attendons donc à ce que les prédictions pour `[MASK]` reflètent ces domaines. Pour prédire le masque, nous avons besoin du *tokenizer* de DistilBERT pour produire les entrées du modèle, alors téléchargeons-le également depuis le *Hub* : + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +Avec un *tokenizer* et un modèle, nous pouvons maintenant passer notre exemple de texte au modèle, extraire les logits, et afficher les 5 meilleurs candidats : + +{#if fw === 'pt'} + +```python +import torch + +inputs = tokenizer(text, return_tensors="pt") +token_logits = model(**inputs).logits +# Trouve l'emplacement de [MASK] et extrait ses logits +mask_token_index = torch.where(inputs["input_ids"] == tokenizer.mask_token_id)[1] +mask_token_logits = token_logits[0, mask_token_index, :] +# Choisissez les candidats [MASK] avec les logits les plus élevés +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 +# Trouve l'emplacement de [MASK] et extrait ses logits +mask_token_index = np.argwhere(inputs["input_ids"] == tokenizer.mask_token_id)[0, 1] +mask_token_logits = token_logits[0, mask_token_index, :] +# On choisit les candidats [MASK] avec les logits les plus élevés +# Nous annulons le tableau avant argsort pour obtenir le plus grand, et non le plus petit, logits +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.' # C'est une bonne affaire +'>>> This is a great success.' # C'est un grand succès +'>>> This is a great adventure.' # C'est une grande aventure +'>>> This is a great idea.' # C'est une bonne idée +'>>> This is a great feat.' # C'est un grand exploit +``` + +Nous pouvons voir dans les sorties que les prédictions du modèle se réfèrent à des termes de tous les jours, ce qui n'est peut-être pas surprenant étant donné le fondement de Wikipédia. Voyons comment nous pouvons changer ce domaine pour quelque chose d'un peu plus spécialisé : des critiques de films ! + + +## Le jeu de données + +Pour illustrer l'adaptation au domaine, nous utiliserons le célèbre [*Large Movie Review Dataset*](https://huggingface.co/datasets/imdb) (ou IMDb en abrégé), qui est un corpus de critiques de films souvent utilisé pour évaluer les modèles d'analyse de sentiments. En *finetunant* DistilBERT sur ce corpus, nous espérons que le modèle de langage adaptera son vocabulaire des données factuelles de Wikipédia sur lesquelles il a été pré-entraîné aux éléments plus subjectifs des critiques de films. Nous pouvons obtenir les données du *Hub* avec la fonction `load_dataset()` de 🤗 *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 + }) +}) +``` + +Nous pouvons voir que les parties `train` et `test` sont chacune composées de 25 000 critiques, alors qu'il y a une partie non étiquetée appelée `unsupervised` qui contient 50 000 critiques. Jetons un coup d'œil à quelques échantillons pour avoir une idée du type de texte auquel nous avons affaire. Comme nous l'avons fait dans les chapitres précédents du cours, nous allons enchaîner les fonctions `Dataset.shuffle()` et `Dataset.select()` pour créer un échantillon aléatoire : + +```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' +``` + +Oui, ce sont bien des critiques de films, et si vous êtes assez âgés, vous pouvez même comprendre le commentaire dans la dernière critique sur le fait de posséder une version VHS 😜 ! Bien que nous n'ayons pas besoin des étiquettes pour la modélisation du langage, nous pouvons déjà voir qu'un `0` dénote une critique négative, tandis qu'un `1` correspond à une critique positive. + + + +✏️ **Essayez !** Créez un échantillon aléatoire de la répartition `unsupervised` et vérifiez que les étiquettes ne sont ni `0` ni `1`. Pendant que vous y êtes, vous pouvez aussi vérifier que les étiquettes dans les échantillons `train` et `test` sont bien `0` ou `1`. C'est un contrôle utile que tout praticien en NLP devrait effectuer au début d'un nouveau projet ! + + + +Maintenant que nous avons jeté un coup d'œil rapide aux données, plongeons dans leur préparation pour la modélisation du langage masqué. Comme nous allons le voir, il y a quelques étapes supplémentaires à suivre par rapport aux tâches de classification de séquences que nous avons vues au [chapitre 3](/course/fr/chapter3). Allons-y ! + +## Prétraitement des données + + + +Pour la modélisation autorégressive et la modélisation du langage masqué, une étape commune de prétraitement consiste à concaténer tous les exemples, puis à diviser le corpus entier en morceaux de taille égale. C'est très différent de notre approche habituelle, où nous nous contentons de *tokenizer* les exemples individuels. Pourquoi tout concaténer ? La raison est que les exemples individuels peuvent être tronqués s'ils sont trop longs, ce qui entraînerait la perte d'informations qui pourraient être utiles pour la tâche de modélisation du langage ! + +Donc pour commencer, nous allons d'abord tokeniser notre corpus comme d'habitude, mais _sans_ mettre l'option `truncation=True` dans notre *tokenizer*. Nous allons aussi récupérer les identifiants des mots s'ils sont disponibles (ce qui sera le cas si nous utilisons un *tokenizer* rapide, comme décrit dans le [chapitre 6](/course/fr/chapter6/3)), car nous en aurons besoin plus tard pour faire le masquage de mots entiers. Nous allons envelopper cela dans une simple fonction, et pendant que nous y sommes, nous allons supprimer les colonnes `text` et `label` puisque nous n'en avons plus besoin : + +```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 + + +# Utilisation de batched=True pour activer le multithreading rapide ! +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 + }) +}) +``` + +Comme DistilBERT est un modèle de type BERT, nous pouvons voir que les textes encodés sont constitués des `input_ids` et des `attention_mask` que nous avons vus dans d'autres chapitres, ainsi que des `word_ids` que nous avons ajoutés. + +Maintenant que nos critiques de films ont été tokenisées, l'étape suivante consiste à les regrouper et à diviser le résultat en chunks. Mais quelle taille doivent avoir ces *chunks* ? Cela sera finalement déterminé par la quantité de mémoire GPU dont vous disposez, mais un bon point de départ est de voir quelle est la taille maximale du contexte du modèle. Cela peut être déduit en inspectant l'attribut `model_max_length` du *tokenizer* : + +```python +tokenizer.model_max_length +``` + + +```python out +512 +``` + +Cette valeur est dérivée du fichier *tokenizer_config.json* associé à un *checkpoint*. Dans ce cas, nous pouvons voir que la taille du contexte est de 512 *tokens*, tout comme avec BERT. + + + +✏️ **Essayez !** Certains *transformers*, comme [BigBird](https://huggingface.co/google/bigbird-roberta-base) et [Longformer](hf.co/allenai/longformer-base-4096), ont une longueur de contexte beaucoup plus longue que BERT et les autres premiers *transformers*. Instanciez le *tokenizer* pour l'un de ces *checkpoints* et vérifiez que le `model_max_length` correspond à ce qui est indiqué sur sa carte. + + + +Ainsi, pour réaliser nos expériences sur des GPUs comme ceux disponibles sur Google Colab, nous choisirons quelque chose d'un peu plus petit qui peut tenir en mémoire : + +```python +chunk_size = 128 +``` + + + +Notez que l'utilisation d'une petite taille peut être préjudiciable dans les scénarios du monde réel. Vous devez donc utiliser une taille qui correspond au cas d'utilisation auquel vous appliquerez votre modèle. + + + +Maintenant vient la partie amusante. Pour montrer comment la concaténation fonctionne, prenons quelques commentaires de notre ensemble d'entraînement et affichons le nombre de *tokens* par commentaire : + +```python +# Le découpage produit une liste de listes pour chaque caractéristique +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' +``` + +Nous pouvons ensuite concaténer tous ces exemples avec une simple compréhension du dictionnaire, comme suit : + +```python +concatenated_examples = { + k: sum(tokenized_samples[k], []) for k in tokenized_samples.keys() +} +total_length = len(concatenated_examples["input_ids"]) +print(f"'>>> Longueur des critiques concaténées : {total_length}'") +``` + +```python out +'>>> Longueur des critiques concaténées : 951' +``` + +Super, la longueur totale est correcte. Donc maintenant, nous allons diviser les exemples concaténés en morceaux de la taille donnée par `block_size`. Pour ce faire, nous itérons sur les caractéristiques de `concatenated_examples` et utilisons une compréhension de liste pour créer des *chunks* de chaque caractéristique. Le résultat est un dictionnaire de *chunks* pour chaque caractéristique : + +```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' +``` + +Comme vous pouvez le voir dans cet exemple, le dernier *chunk* sera généralement plus petit que la taille maximale des morceaux. Il y a deux stratégies principales pour gérer cela : + +* Abandonner le dernier morceau s'il est plus petit que `chunk_size`. +* Rembourrer le dernier morceau jusqu'à ce que sa longueur soit égale à `chunk_size`. + +Nous adopterons la première approche ici, donc nous allons envelopper toute la logique ci-dessus dans une seule fonction que nous pouvons appliquer à nos jeux de données tokenisés : + +```python +def group_texts(examples): + # Concaténation de tous les textes + concatenated_examples = {k: sum(examples[k], []) for k in examples.keys()} + # Calcule la longueur des textes concaténés + total_length = len(concatenated_examples[list(examples.keys())[0]]) + # Nous laissons tomber le dernier morceau s'il est plus petit que chunk_size + total_length = (total_length // chunk_size) * chunk_size + # Fractionnement par chunk de 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() + } + # Créer une nouvelle colonne d'étiquettes + result["labels"] = result["input_ids"].copy() + return result +``` + +Notez que dans la dernière étape de `group_texts()` nous créons une nouvelle colonne `labels` qui est une copie de la colonne `input_ids`. Comme nous le verrons bientôt, c'est parce que dans la modélisation du langage masqué, l'objectif est de prédire des *tokens* masqués aléatoirement dans le batch d'entrée, et en créant une colonne `labels`, nous fournissons la vérité de base pour notre modèle de langage à apprendre. + +Appliquons maintenant `group_texts()` à nos jeux de données tokenisés en utilisant notre fidèle fonction `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 + }) +}) +``` + +Vous pouvez voir que le regroupement puis le découpage des textes a produit beaucoup plus d'exemples que nos 25 000 exemples initiaux pour les divisions `train` et `test`. C'est parce que nous avons maintenant des exemples impliquant des *tokens* contigus qui s'étendent sur plusieurs exemples du corpus original. Vous pouvez le voir explicitement en cherchant les *tokens* spéciaux `[SEP]` et `[CLS]` dans l'un des *chunks* : + +```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" +``` + +Dans cet exemple, vous pouvez voir deux critiques de films qui se chevauchent, l'une sur un film de lycée et l'autre sur les sans-abri. Voyons également à quoi ressemblent les étiquettes pour la modélisation du langage masqué : + +```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" +``` + +Comme prévu par notre fonction `group_texts()` ci-dessus, cela semble identique aux `input_ids` décodés. Mais alors comment notre modèle peut-il apprendre quoi que ce soit ? Il nous manque une étape clé : insérer des *tokens* à des positions aléatoires dans les entrées ! Voyons comment nous pouvons le faire à la volée pendant le *finetuning* en utilisant un collateur de données spécial. + +## Finetuning de DistilBERT avec l'API `Trainer` + +Le *finetuning* d'un modèle de langage masqué est presque identique au *finetuning* d'un modèle de classification de séquences, comme nous l'avons fait dans le [chapitre 3](/course/fr/chapter3). La seule différence est que nous avons besoin d'un collecteur de données spécial qui peut masquer de manière aléatoire certains des *tokens* dans chaque batch de textes. Heureusement, 🤗 *Transformers* est livré préparé avec un `DataCollatorForLanguageModeling` dédié à cette tâche. Nous devons juste lui passer le *tokenizer* et un argument `mlm_probability` qui spécifie quelle fraction des *tokens* à masquer. Nous choisirons 15%, qui est la quantité utilisée pour BERT et un choix commun dans la littérature : + +```python +from transformers import DataCollatorForLanguageModeling + +data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm_probability=0.15) +``` + +Pour voir comment le masquage aléatoire fonctionne, nous allons donner quelques exemples au collateur de données. Puisqu'il s'attend à une liste de `dict` où chaque `dict` représente un seul morceau de texte contigu, nous itérons d'abord sur le jeu de données avant de donner le batch au collateur. Nous supprimons la clé `"word_ids"` pour ce collateur de données car il ne l'attend pas : + +```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' +``` + +Super, ça a marché ! Nous pouvons voir que le *token* `[MASK]` a été inséré de façon aléatoire à différents endroits dans notre texte. Ce seront les *tokens* que notre modèle devra prédire pendant l'entraînement. Et la beauté du collecteur de données est qu'il va rendre aléatoire l'insertion du `[MASK]` à chaque batch ! + + + +✏️ **Essayez** Exécutez le code ci-dessus plusieurs fois pour voir le masquage aléatoire se produire sous vos yeux ! Remplacez aussi la méthode `tokenizer.decode()` par `tokenizer.convert_ids_to_tokens()` pour voir que parfois un seul *token* d'un mot donné est masqué et pas les autres. + + + +{#if fw === 'pt'} + +Un effet secondaire du masquage aléatoire est que nos métriques d'évaluation ne seront pas déterministes lorsque nous utilisons la fonction `Trainer` puisque nous utilisons le même collateur de données pour les échantillons d'entraînement et de test. Nous verrons plus tard, lorsque nous examinerons le *finetuning* avec 🤗 *Accelerate*, comment nous pouvons utiliser la flexibilité d'une boucle d'évaluation personnalisée pour geler le caractère aléatoire. + +{/if} + +Lors de l'entraînement des modèles pour la modélisation du langage masqué, une technique qui peut être utilisée est de masquer des mots entiers ensemble et pas seulement des *tokens* individuels. Cette approche est appelée _masquage de mots entiers_. Si nous voulons utiliser le masquage de mots entiers, nous devons construire nous-mêmes un collateur de données. Un collateur de données est simplement une fonction qui prend une liste d'échantillons et les convertit en un batch. Faisons-le ! Nous utiliserons les identifiants des mots calculés plus tôt pour faire une correspondance entre les indices des mots et les *tokens*, puis nous déciderons aléatoirement quels mots masquer et appliquerons ce masque sur les entrées. Notez que les étiquettes sont toutes `-100` sauf celles qui correspondent aux mots masqués. + +{#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") + + # Création d'une correspondance entre les mots et les indices des tokens correspondants + 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) + + # Masquer des mots de façon aléatoire + 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 + + 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") + + # Création d'une correspondance entre les mots et les indices des tokens correspondants + 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) + + # Masquer des mots de façon aléatoire + 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 + + return tf_default_data_collator(features) +``` + +{/if} + +Ensuite, nous pouvons l'essayer sur les mêmes échantillons que précédemment : + +```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' +``` + + + +✏️ **Essayez** Exécutez le code ci-dessus plusieurs fois pour voir le masquage aléatoire se produire sous vos yeux ! Remplacez aussi la méthode `tokenizer.decode()` par `tokenizer.convert_ids_to_tokens()` pour voir que les *tokens* d'un mot donné sont toujours masqués ensemble. + + + +Maintenant que nous avons deux collateurs de données, les étapes restantes du *finetuning* sont standards. L'entraînement peut prendre un certain temps sur Google Colab si vous n'avez pas la chance de tomber sur un mythique GPU P100 😭. Ainsi nous allons d'abord réduire la taille du jeu d'entraînement à quelques milliers d'exemples. Ne vous inquiétez pas, nous obtiendrons quand même un modèle de langage assez décent ! Un moyen rapide de réduire la taille d'un jeu de données dans 🤗 *Datasets* est la fonction `Dataset.train_test_split()` que nous avons vue au [chapitre 5](/course/fr/chapter5) : + +```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 + }) +}) +``` + +Cela a automatiquement créé de nouvelles divisions `train` et `test` avec la taille du jeu d'entraînement fixée à 10.000 exemples et la validation fixée à 10% de cela. N'hésitez pas à augmenter la taille si vous avez un GPU puissant ! La prochaine chose que nous devons faire est de nous connecter au *Hub*. Si vous exécutez ce code dans un *notebook*, vous pouvez le faire avec la fonction suivante : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +qui affichera un *widget* où vous pourrez saisir vos informations d'identification. Alternativement, vous pouvez exécuter : + +``` +huggingface-cli login +``` + +dans votre terminal préféré et connectez-vous là. + +{#if fw === 'tf'} + +Une fois que nous sommes connectés, nous pouvons créer nos jeux de données `tf.data`. Nous n'utiliserons ici que le collecteur de données standard, mais vous pouvez également essayer le collecteur de masquage de mots entiers et comparer les résultats à titre d'exercice : + +```python +tf_train_dataset = downsampled_dataset["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) + +tf_eval_dataset = downsampled_dataset["test"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=32, +) +``` + +Ensuite, nous configurons nos hyperparamètres d'entraînement et compilons notre modèle. Nous utilisons la fonction `create_optimizer()` de la bibliothèque 🤗 *Transformers*, qui nous donne un optimiseur `AdamW` avec une décroissance linéaire du taux d'apprentissage. Nous utilisons également la perte intégrée au modèle, qui est la perte par défaut lorsqu'aucune perte n'est spécifiée comme argument de `compile()`, et nous définissons la précision d'entraînement à `"mixed_float16"`. Notez que si vous utilisez un GPU Colab ou un autre GPU qui n'a pas le support accéléré en float16, vous devriez probablement commenter cette ligne. + +De plus, nous mettons en place un `PushToHubCallback` qui sauvegardera le modèle sur le *Hub* après chaque époque. Vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, pour pousser le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/distilbert-finetuned-imdb"`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, donc dans notre cas, ce sera `"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) + +# Entraîner en mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") + +callback = PushToHubCallback( + output_dir=f"{model_name}-finetuned-imdb", tokenizer=tokenizer +) +``` + +Nous sommes maintenant prêts à exécuter `model.fit()`. Mais avant, regardons brièvement la _perplexité_ qui est une métrique commune pour évaluer la performance des modèles de langage. + +{:else} + +Une fois que nous sommes connectés, nous pouvons spécifier les arguments pour le `Trainer` : + +```python +from transformers import TrainingArguments + +batch_size = 64 +# Montrer la perte d'entraînement à chaque époque +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, +) +``` + +Ici, nous avons modifié quelques options par défaut, y compris `logging_steps` pour s'assurer que nous suivons la perte d'entraînement à chaque époque. Nous avons également utilisé `fp16=True` pour activer l'entraînement en précision mixte, ce qui nous donne un autre gain de vitesse. Par défaut, `Trainer` va supprimer toutes les colonnes qui ne font pas partie de la méthode `forward()` du modèle. Cela signifie que si vous utilisez le collateur de masquage de mots entiers, vous devrez également définir `remove_unused_columns=False` pour vous assurer que nous ne perdons pas la colonne `word_ids` pendant l'entraînement. + +Notez que vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/distilbert-finetuned-imdb"` `TrainingArguments`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, donc dans notre cas ce sera `"lewtun/distilbert-finetuned-imdb"`. + +Nous avons maintenant tous les ingrédients pour instancier le `Trainer`. Ici, nous utilisons juste le collateur standard `data_collator`, mais vous pouvez essayer le collateur de masquage de mots entiers et comparer les résultats comme exercice : + +```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, +) +``` + +Nous sommes maintenant prêts à exécuter `trainer.train()`. Mais avant, regardons brièvement la _perplexité_ qui est une métrique commune pour évaluer la performance des modèles de langage. + +{/if} + +### Perplexité pour les modèles de langage + + + +Contrairement à d'autres tâches, comme la classification de textes ou la réponse à des questions, sur lesquelles nous disposons d'un corpus étiqueté pour entraîner, la modélisation du langage ne s'appuie sur aucune étiquette explicite. Alors comment déterminer ce qui fait un bon modèle de langage ? Comme pour la fonction de correction automatique de votre téléphone, un bon modèle de langage est celui qui attribue des probabilités élevées aux phrases grammaticalement correctes et des probabilités faibles aux phrases absurdes. Pour vous donner une meilleure idée de ce à quoi cela ressemble, vous pouvez trouver en ligne des séries entières de « ratés d'autocorrection » où le modèle d'un téléphone produit des compléments plutôt amusants (et souvent inappropriés) ! + +{#if fw === 'pt'} + +En supposant que notre ensemble de test se compose principalement de phrases grammaticalement correctes, une façon de mesurer la qualité de notre modèle de langage est de calculer les probabilités qu'il attribue au mot suivant dans toutes les phrases de l'ensemble de test. Des probabilités élevées indiquent que le modèle n'est pas « surpris » ou « perplexe » vis-à-vis des exemples non vus, et suggèrent qu'il a appris les modèles de base de la grammaire de la langue. Il existe plusieurs définitions mathématiques de la perplexité. Celle que nous utiliserons la définit comme l'exponentielle de la perte d'entropie croisée. Ainsi, nous pouvons calculer la perplexité de notre modèle pré-entraîné en utilisant la fonction `Trainer.evaluate()` pour calculer la perte d'entropie croisée sur l'ensemble de test, puis en prenant l'exponentielle du résultat : + +```python +import math + +eval_results = trainer.evaluate() +print(f">>> Perplexity: {math.exp(eval_results['eval_loss']):.2f}") +``` + +{:else} + +En supposant que notre ensemble de test se compose principalement de phrases grammaticalement correctes, une façon de mesurer la qualité de notre modèle de langage est de calculer les probabilités qu'il attribue au mot suivant dans toutes les phrases de l'ensemble de test. Des probabilités élevées indiquent que le modèle n'est pas « surpris » ou « perplexe » vis-à-vis des exemples non vus, et suggèrent qu'il a appris les modèles de base de la grammaire de la langue. Il existe plusieurs définitions mathématiques de la perplexité. Celle que nous utiliserons la définit comme l'exponentielle de la perte d'entropie croisée. Ainsi, nous pouvons calculer la perplexité de notre modèle pré-entraîné en utilisant la fonction `model.evaluate()` pour calculer la perte d'entropie croisée sur l'ensemble de test, puis en prenant l'exponentielle du résultat : + +```python +import math + +eval_loss = model.evaluate(tf_eval_dataset) +print(f"Perplexité : {math.exp(eval_loss):.2f}") +``` + +{/if} + +```python out +>>> Perplexité : 21.75 +``` + +Un score de perplexité faible signifie un meilleur modèle de langue. Nous pouvons voir ici que notre modèle de départ a une valeur assez élevée. Voyons si nous pouvons la réduire en l'affinant ! Pour ce faire, nous commençons par exécuter la boucle d'entraînement : + +{#if fw === 'pt'} + +```python +trainer.train() +``` + +{:else} + +```python +model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback]) +``` + +{/if} + +et ensuite calculer la perplexité résultante sur l'ensemble de test comme précédemment : + +{#if fw === 'pt'} + +```python +eval_results = trainer.evaluate() +print(f">>> Perplexité : {math.exp(eval_results['eval_loss']):.2f}") +``` + +{:else} + +```python +eval_loss = model.evaluate(tf_eval_dataset) +print(f"Perplexité : {math.exp(eval_loss):.2f}") +``` + +{/if} + +```python out +>>> Perplexité : 11.32 +``` + +Joli. C'est une réduction considérable de la perplexité, ce qui nous indique que le modèle a appris quelque chose sur le domaine des critiques de films ! + +{#if fw === 'pt'} + +Une fois l'entraînement terminé, nous pouvons pousser la carte de modèle avec les informations d'entraînement vers le *Hub* (les *checkpoints* sont sauvegardés pendant l'entraînement lui-même) : + +```python +trainer.push_to_hub() +``` + +{/if} + + + +✏️ **A votre tour !** Exécutez l'entraînement ci-dessus après avoir remplacé le collecteur de données par le collecteur de mots entiers masqués. Obtenez-vous de meilleurs résultats ? + + + +{#if fw === 'pt'} + +Dans notre cas d'utilisation, nous n'avons pas eu besoin de faire quelque chose de spécial avec la boucle d'entraînement, mais dans certains cas, vous pourriez avoir besoin de mettre en œuvre une logique personnalisée. Pour ces applications, vous pouvez utiliser 🤗 *Accelerate*. Jetons un coup d'œil ! + +## Finetuning de DistilBERT avec 🤗 Accelerate + +Comme nous l'avons vu, avec `Trainer` le *finetuning* d'un modèle de langage masqué est très similaire à l'exemple de classification de texte du [chapitre 3](/course/fr/chapter3). En fait, la seule subtilité est l'utilisation d'un collateur de données spécial, et nous l'avons déjà couvert plus tôt dans cette section ! + +Cependant, nous avons vu que `DataCollatorForLanguageModeling` applique aussi un masquage aléatoire à chaque évaluation. Nous verrons donc quelques fluctuations dans nos scores de perplexité à chaque entrainement. Une façon d'éliminer cette source d'aléat est d'appliquer le masquage _une fois_ sur l'ensemble de test, puis d'utiliser le collateur de données par défaut dans 🤗 *Transformers* pour collecter les batchs pendant l'évaluation. Pour voir comment cela fonctionne, implémentons une fonction simple qui applique le masquage sur un batch, similaire à notre première rencontre avec `DataCollatorForLanguageModeling` : + +```python +def insert_random_mask(batch): + features = [dict(zip(batch, t)) for t in zip(*batch.values())] + masked_inputs = data_collator(features) + # Créer une nouvelle colonne "masquée" pour chaque colonne du jeu de données + return {"masked_" + k: v.numpy() for k, v in masked_inputs.items()} +``` + +Ensuite, nous allons appliquer cette fonction à notre jeu de test et laisser tomber les colonnes non masquées afin de les remplacer par les colonnes masquées. Vous pouvez utiliser le masquage de mots entiers en remplaçant le `data_collator` ci-dessus par celui qui est approprié. Dans ce cas vous devez supprimer la première ligne ici : + +```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", + } +) +``` + +Nous pouvons ensuite configurer les *dataloaders* comme d'habitude, mais nous utiliserons le `default_data_collator` de 🤗 *Transformers* pour le jeu d'évaluation : + +```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 +) +``` + +Nous suivons les étapes standard avec 🤗 *Accelerate*. La première est de charger une version fraîche du modèle pré-entraîné : + +``` +model = AutoModelForMaskedLM.from_pretrained(model_checkpoint) +``` + +Ensuite, nous devons spécifier l'optimiseur. Nous utiliserons le standard `AdamW` : + +```python +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=5e-5) +``` + +Avec ces objets, nous pouvons maintenant tout préparer pour l'entraînement avec l'objet `Accelerator` : + +```python +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + +Maintenant que notre modèle, notre optimiseur et nos chargeurs de données sont configurés, nous pouvons spécifier le planificateur du taux d'apprentissage comme suit : + +```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, +) +``` + +Il ne reste qu'une dernière chose à faire avant de s'entraîner : créer un dépôt de modèles sur le *Hub* d'Hugging Face ! Nous pouvons utiliser la bibliothèque 🤗 *Hub* pour générer d'abord le nom complet de notre dépôt : + +```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' +``` + +puis créer et cloner le dépôt en utilisant la classe `Repository` du 🤗 *Hub* : + +```python +from huggingface_hub import Repository + +output_dir = model_name +repo = Repository(output_dir, clone_from=repo_name) +``` + +Une fois cela fait, il ne reste plus qu'à rédiger la boucle complète d'entraînement et d'évaluation : + +```python +from tqdm.auto import tqdm +import torch +import math + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Entraînement + 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) + + # Evaluation + 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}") + + # Sauvegarder et télécharger + 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 +``` + +Cool, nous avons été en mesure d'évaluer la perplexité à chaque époque et de garantir la reproductibilité des entraînements multiples ! + +{/if} + +### Utilisation de notre modèle finetuné + +Vous pouvez interagir avec votre modèle *finetuné* soit en utilisant son *widget* sur le *Hub*, soit localement avec le `pipeline` de 🤗 *Transformers*. Utilisons ce dernier pour télécharger notre modèle en utilisant le pipeline `fill-mask` : + +```python +from transformers import pipeline + +mask_filler = pipeline( + "fill-mask", model="huggingface-course/distilbert-base-uncased-finetuned-imdb" +) +``` + +Nous pouvons ensuite donner au pipeline notre exemple de texte « this is a great [MASK] » et voir quelles sont les 5 premières prédictions : + +```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.' +``` + +Notre modèle a clairement adapté ses pondérations pour prédire les mots qui sont plus fortement associés aux films ! + + + +Ceci conclut notre première expérience d'entraînement d'un modèle de langage. Dans la [section 6](/course/fr/chapter7/section6), vous apprendrez comment entraîner à partir de zéro un modèle autorégressif comme GPT-2. Allez-y si vous voulez voir comment vous pouvez pré-entraîner votre propre *transformer* ! + + + +✏️ **Essayez !** Pour quantifier les avantages de l'adaptation au domaine, finetunez un classifieur sur le jeu de données IMDb pour à la fois, le checkpoint de DistilBERT pré-entraîné et e checkpoint de DistilBERT finetuné. Si vous avez besoin d'un rafraîchissement sur la classification de texte, consultez le [chapitre 3](/course/fr/chapter3). + + diff --git a/chapters/fr/chapter7/4.mdx b/chapters/fr/chapter7/4.mdx index e28cf05d5..8f0659328 100644 --- a/chapters/fr/chapter7/4.mdx +++ b/chapters/fr/chapter7/4.mdx @@ -1,998 +1,998 @@ - - -# Traduction - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -Plongeons maintenant dans la traduction. Il s'agit d'une autre [tâche de séquence à séquence](/course/fr/chapitre1/7), ce qui signifie que c'est un problème qui peut être formulé comme le passage d'une séquence à une autre. En ce sens, le problème est assez proche de la tâche de [résumé](/course/fr/chapitre7/6) et vous pouvez adapter ce que nous allons voir ici à d'autres problèmes de séquence à séquence tels que : - -- Le **transfert de style** ? c'est-à-dire créer un modèle qui *traduit* des textes écrits dans un certain style vers un autre (par exemple, du formel au décontracté ou de l'anglais shakespearien à l'anglais moderne). -- La **génération de réponse à des questions** c'est-à-dire créer un modèle qui génère des réponses à des questions compte tenu d'un contexte. - - - -Si vous disposez d'un corpus de textes suffisamment important en deux langues différentes (ou plus), vous pouvez entraîner un nouveau modèle de traduction à partir de zéro, comme nous le ferons dans la section sur la [modélisation causale du langage](/course/fr/chapitre7/6). Il est toutefois plus rapide de *finetuner* un modèle de traduction existant, qu'il s'agisse d'un modèle multilingue comme mT5 ou mBART que vous souhaitez adapter à une paire de langues spécifique, ou même d'un modèle spécialisé dans la traduction d'une langue vers une autre que vous souhaitez adapter à votre corpus spécifique. - -Dans cette section, nous allons *finetuner* un modèle Marian pré-entraîné pour traduire de l'anglais au français (puisque de nombreux employés de Hugging Face parlent ces deux langues) sur le jeu de données [KDE4](https://huggingface.co/datasets/kde4) qui est un jeu de données de fichiers localisés pour les applications [KDE](https://apps.kde.org/). Le modèle que nous utiliserons a été pré-entraîné sur un large corpus de textes français et anglais provenant du jeu de données [Opus](https://opus.nlpl.eu/) qui contient en fait le jeu de données KDE4. A noter que même si le modèle pré-entraîné que nous utilisons a vu ces données pendant son pré-entraînement, nous verrons que nous pouvons obtenir une meilleure version de ce modèle après un *finetuning*. - -Une fois que nous aurons terminé, nous aurons un modèle capable de faire des prédictions comme celle-ci : - - - - - -One-hot encoded labels for question answering. - - - -Comme dans les sections précédentes, vous pouvez trouver, télécharger et vérifier les précisions de ce modèle sur le [*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.). - -## Préparation des données - -Pour *finetuner* ou entraîner un modèle de traduction à partir de zéro, nous avons besoin d'un jeu de données adapté à cette tâche. Comme mentionné précédemment, nous utiliserons le jeu de données [KDE4](https://huggingface.co/datasets/kde4) dans cette section. Notez que vous pouvez adapter assez facilement le code pour utiliser vos propres données du moment que vous disposez de paires de phrases dans les deux langues que vous voulez traduire. Reportez-vous au [chapitre 5](/course/fr/chapter5) si vous avez besoin d'un rappel sur la façon de charger vos données personnalisées dans un `Dataset`. - -### Le jeu de données KDE4 - -Comme d'habitude, nous téléchargeons notre jeu de données en utilisant la fonction `load_dataset()` : - -```py -from datasets import load_dataset - -raw_datasets = load_dataset("kde4", lang1="en", lang2="fr") -``` - -Si vous souhaitez travailler avec une autre paire de langues, 92 langues sont disponibles au total pour ce jeu de données. Vous pouvez les voir dans la [carte du jeu de données](https://huggingface.co/datasets/kde4). - -Language available for the KDE4 dataset. - -Jetons un coup d'œil au jeu de données : - -```py -raw_datasets -``` - -```python out -DatasetDict({ - train: Dataset({ - features: ['id', 'translation'], - num_rows: 210173 - }) -}) -``` - -Nous avons 210 173 paires de phrases. Cependant regroupées dans un seul échantillon. Nous devrons donc créer notre propre jeu de validation. Comme nous l'avons vu dans le [chapitre 5](/course/fr/chapter5), un `Dataset` possède une méthode `train_test_split()` qui peut nous aider. Nous allons fournir une graine pour la reproductibilité : - -```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 - }) -}) -``` - -Nous pouvons renommer la clé `test` en `validation` comme ceci : - -```py -split_datasets["validation"] = split_datasets.pop("test") -``` - -Examinons maintenant un élément de ce jeu de données : - -```py -split_datasets["train"][1]["translation"] -``` - -```python out -{'en': 'Default to expanded threads', - 'fr': 'Par défaut, développer les fils de discussion'} -``` - -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 : - -```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'}] -``` - -Un autre exemple de ce comportement peut être observé avec le mot « *plugin* » qui n'est pas officiellement un mot français mais que la plupart des francophones comprendront et ne prendront pas la peine de traduire. -Dans le jeu de données KDE4, ce mot a été traduit en français par le plus officiel « 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."} -``` - -Notre modèle pré-entraîné, lui, s'en tient au mot anglais : - -```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."}] -``` - -Il sera intéressant de voir si notre modèle *finetuné* tient compte de ces particularités (alerte *spoiler* : il le fera). - - - - - -✏️ **A votre tour !** Un autre mot anglais souvent utilisé en français est « *email* ». Trouvez le premier échantillon dans l'échantillon d'entraînement qui utilise ce mot. Comment est-il traduit ? Comment le modèle pré-entraîné traduit-il cette même phrase ? - - - -### Traitement des données - - - -Vous devriez maintenant connaître le principe : les textes doivent tous être convertis en ensembles d'ID de *tokens* pour que le modèle puisse leur donner un sens. Pour cette tâche, nous aurons besoin de tokeniser les entrées et les cibles. Notre première tâche est de créer notre objet `tokenizer`. Comme indiqué précédemment, nous utiliserons un modèle pré-entraîné Marian English to French. Si vous essayez ce code avec une autre paire de langues, assurez-vous d'adapter le *checkpoint* du modèle. L'organisation [Helsinki-NLP](https://huggingface.co/Helsinki-NLP) fournit plus de mille modèles dans plusieurs langues. - -```python -from transformers import AutoTokenizer - -model_checkpoint = "Helsinki-NLP/opus-mt-en-fr" -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, return_tensors="tf") -``` - -Vous pouvez remplacer le `model_checkpoint` par un tout autre modèle disponible sur le [*Hub*](https://huggingface.co/models) qui aurait votre préférence, ou par un dossier en local où vous avez sauvegardé un modèle pré-entraîné et un *tokenizer*. - - - -💡 Si vous utilisez un *tokenizer* multilingue tel que mBART, mBART-50 ou M2M100, vous devrez définir les codes de langue de vos entrées et cibles dans le *tokenizer* en définissant `tokenizer.src_lang` et `tokenizer.tgt_lang` aux bonnes valeurs. - - - -La préparation de nos données est assez simple. Il y a juste une chose à retenir : vous traitez les entrées comme d'habitude, mais pour les cibles, vous devez envelopper le *tokenizer* dans le gestionnaire de contexte `as_target_tokenizer()`. - -Un gestionnaire de contexte en Python est introduit avec l'instruction `with` et est utile lorsque vous avez deux opérations liées à exécuter en paire. L'exemple le plus courant est lorsque vous écrivez ou lisez un fichier, ce qui est souvent fait dans une instruction comme : - -``` -with open(file_path) as f: - content = f.read() -``` - -Ici, les deux opérations connexes qui sont exécutées en paire sont les actions d'ouverture et de fermeture du fichier. L'objet correspondant au fichier ouvert `f` n'existe qu'à l'intérieur du bloc indenté sous le `with`. L'ouverture se produit avant ce bloc et la fermeture à la fin du bloc. - -Dans le cas présent, le gestionnaire de contexte `as_target_tokenizer()` va définir le *tokenizer* dans la langue de sortie (ici, le français) avant l'exécution du bloc indenté, puis le redéfinir dans la langue d'entrée (ici, l'anglais). - -Ainsi, le prétraitement d'un échantillon ressemble à ceci : - -```python -en_sentence = split_datasets["train"][1]["translation"]["en"] -fr_sentence = split_datasets["train"][1]["translation"]["fr"] - -inputs = tokenizer(en_sentence) -with tokenizer.as_target_tokenizer(): - targets = tokenizer(fr_sentence) -``` - -Si nous oublions de tokeniser les cibles dans le gestionnaire de contexte, elles seront tokenisées par le *tokenizer* d'entrée, ce qui dans le cas d'un modèle Marian, ne va pas du tout bien se passer : - -```python -wrong_targets = tokenizer(fr_sentence) -print(tokenizer.convert_ids_to_tokens(wrong_targets["input_ids"])) -print(tokenizer.convert_ids_to_tokens(targets["input_ids"])) -``` - -```python out -['▁Par', '▁dé', 'f', 'aut', ',', '▁dé', 've', 'lop', 'per', '▁les', '▁fil', 's', '▁de', '▁discussion', ''] -['▁Par', '▁défaut', ',', '▁développer', '▁les', '▁fils', '▁de', '▁discussion', ''] -``` - -Comme on peut le voir, utiliser le *tokenizer* anglais pour prétraiter une phrase française donne un batch de *tokens* plus important, puisque le *tokenizer* ne connaît aucun mot français (sauf ceux qui apparaissent aussi en anglais, comme « discussion »). - -Les `inputs` et les `targets` sont des dictionnaires avec nos clés habituelles (identifiants d'entrée, masque d'attention, etc.). La dernière étape est de définir une clé `"labels"` dans les entrées. Nous faisons cela dans la fonction de prétraitement que nous allons appliquer sur les jeux de données : - -```python -max_input_length = 128 -max_target_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, max_length=max_input_length, truncation=True) - - # Configurer le tokenizer pour les cibles. - with tokenizer.as_target_tokenizer(): - labels = tokenizer(targets, max_length=max_target_length, truncation=True) - - model_inputs["labels"] = labels["input_ids"] - return model_inputs -``` - -Notez que nous avons fixé des longueurs maximales similaires pour nos entrées et nos sorties. Comme les textes que nous traitons semblent assez courts, nous utilisons 128. - - - -💡 Si vous utilisez un modèle T5 (plus précisément, un des *checkpoints* `t5-xxx`), le modèle s'attendra à ce que les entrées aient un préfixe indiquant la tâche à accomplir, comme `translate: English to French:`. - - - - - -⚠️ Nous ne faisons pas attention au masque d'attention des cibles car le modèle ne s'y attend pas. Au lieu de cela, les étiquettes correspondant à un *token* de *padding* doivent être mises à `-100` afin qu'elles soient ignorées dans le calcul de la perte. Cela sera fait par notre collateur de données plus tard puisque nous appliquons le *padding* dynamique, mais si vous utilisez le *padding* ici, vous devriez adapter la fonction de prétraitement pour mettre toutes les étiquettes qui correspondent au *token* de *padding* à `-100`. - - - -Nous pouvons maintenant appliquer ce prétraitement en une seule fois sur toutes les échantillons de notre jeu de données : - -```py -tokenized_datasets = split_datasets.map( - preprocess_function, - batched=True, - remove_columns=split_datasets["train"].column_names, -) -``` - -Maintenant que les données ont été prétraitées, nous sommes prêts à *finetuner* notre modèle pré-entraîné ! - -{#if fw === 'pt'} - -## Finetuner le modèle avec l'API `Trainer` - -Le code actuel utilisant `Trainer` sera le même que précédemment, avec juste un petit changement : nous utilisons ici [`Seq2SeqTrainer`](https://huggingface.co/transformers/main_classes/trainer.html#seq2seqtrainer) qui est une sous-classe de `Trainer` qui nous permet de traiter correctement l'évaluation, en utilisant la méthode `generate()` pour prédire les sorties à partir des entrées. Nous y reviendrons plus en détail lorsque nous parlerons du calcul de la métrique. - -Tout d'abord, nous avons besoin d'un modèle à *finetuner*. Nous allons utiliser l'API habituelle `AutoModel` : - -```py -from transformers import AutoModelForSeq2SeqLM - -model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) -``` - -{:else} - -## Finetuner du modèle avec Keras - -Tout d'abord, nous avons besoin d'un modèle à *finetuner*. Nous allons utiliser l'API habituelle `AutoModel` : - -```py -from transformers import TFAutoModelForSeq2SeqLM - -model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint, from_pt=True) -``` - - - -💡 Le *checkpoint* `Helsinki-NLP/opus-mt-en-fr` ne dispose que de poids PyTorch, vous aurez donc une erreur si vous essayez de charger le modèle sans utiliser l'argument `from_pt=True` dans la méthode `from_pretrained()`. Lorsque vous spécifiez `from_pt=True`, la bibliothèque téléchargera et convertira automatiquement les poids PyTorch pour vous. Comme vous pouvez le constater, c'est très simple de passer d'un *framework* à l'autre dans 🤗 *Transformers* ! - - - -{/if} - -Notez que cette fois-ci, nous utilisons un modèle qui a été entraîné sur une tâche de traduction et qui peut déjà être utilisé, donc il n'y a pas d'avertissement concernant les poids manquants ou ceux nouvellement initialisés. - -### Collecte des données - -Nous aurons besoin d'un assembleur de données pour gérer le rembourrage pour la mise en batchs dynamique. Ici, nous ne pouvons pas simplement utiliser un `DataCollatorWithPadding` comme dans le [chapitre 3](/course/fr/chapter3) car cela ne rembourre que les entrées (identifiants d'entrée, masque d'attention, et *token* de type identifiants). Nos étiquettes doivent également être rembourrées à la longueur maximale rencontrée dans les étiquettes. Et, comme mentionné précédemment, la valeur de remplissage utilisée pour remplir les étiquettes doit être `-100` et non le *token* de *padding* du *tokenizer* afin de s'assurer que ces valeurs soient ignorées dans le calcul de la perte. - -Tout ceci est réalisé par un [`DataCollatorForSeq2Seq`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorforseq2seq). Comme le `DataCollatorWithPadding`, il prend le `tokenizer` utilisé pour prétraiter les entrées, mais également le `model`. C'est parce que ce collateur de données est également responsable de la préparation des identifiants d'entrée du décodeur, qui sont des versions décalées des étiquettes avec un *token* spécial au début. Comme ce décalage est effectué de manière légèrement différente selon les architectures, le `DataCollatorForSeq2Seq` a besoin de connaître l'objet `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} - -Pour le tester sur quelques échantillons, nous l'appelons simplement sur une liste d'exemples de notre échantillon d'entrainement tokénisé : - -```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']) -``` - -Nous pouvons vérifier que nos étiquettes ont été rembourrées à la longueur maximale du batch, en utilisant `-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]]) -``` - -Nous pouvons aussi jeter un coup d'œil aux identifiants d'entrée du décodeur, pour voir qu'il s'agit de versions décalées des étiquettes : - -```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]]) -``` - -Voici les étiquettes des premier et deuxième éléments de notre jeu de données : - -```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'} - -Nous allons transmettre ce `data_collator` au `Seq2SeqTrainer`. Ensuite, jetons un coup d'oeil à la métrique. - -{:else} - -Nous pouvons maintenant utiliser ce `data_collator` pour convertir chacun de nos jeux de données en un `tf.data.Dataset`, prêt pour l'entraînement : - -```python -tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=True, - batch_size=32, -) -tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=False, - batch_size=16, -) -``` - -{/if} - - -### Métriques - - - -{#if fw === 'pt'} - -La fonctionnalité que `Seq2SeqTrainer` ajoute à sa superclasse `Trainer` est la possibilité d'utiliser la méthode `generate()` pendant l'évaluation ou la prédiction. Pendant l'entraînement, le modèle utilisera les `decoder_input_ids` avec un masque d'attention assurant qu'il n'utilise pas les *tokens* après le *token* qu'il essaie de prédire, pour accélérer l'entraînement. Pendant l'inférence, nous ne pourrons pas les utiliser puisque nous n'aurons pas d'étiquettes. Ainsi c'est une bonne idée d'évaluer notre modèle avec la même configuration. - -Comme nous l'avons vu dans le [chapitre 1](/course/fr/chapter1/6), le décodeur effectue l'inférence en prédisant les *tokens* un par un. C'est quelque chose qui est implémenté en coulisses dans 🤗 *Transformers* par la méthode `generate()`. Le `Seq2SeqTrainer` nous laissera utiliser cette méthode pour l'évaluation si nous indiquons `predict_with_generate=True`. - -{/if} - -La métrique traditionnelle utilisée pour la traduction est le [score BLEU](https://en.wikipedia.org/wiki/BLEU), introduit dans [un article de 2002](https://aclanthology.org/P02-1040.pdf) par Kishore Papineni et al. Le score BLEU évalue dans quelle mesure les traductions sont proches de leurs étiquettes. Il ne mesure pas l'intelligibilité ou l'exactitude grammaticale des résultats générés par le modèle, mais utilise des règles statistiques pour garantir que tous les mots des résultats générés apparaissent également dans les cibles. En outre, il existe des règles qui pénalisent les répétitions des mêmes mots s'ils ne sont pas également répétés dans les cibles (pour éviter que le modèle ne produise des phrases telles que « the the the the the the the ») et les phrases produites qui sont plus courtes que celles des cibles (pour éviter que le modèle ne produise des phrases telles que « the »). - -L'une des faiblesses de BLEU est qu'il s'attend à ce que le texte soit déjà tokenisé, ce qui rend difficile la comparaison des scores entre les modèles qui utilisent différents *tokenizers*. Par conséquent, la mesure la plus couramment utilisée aujourd'hui pour évaluer les modèles de traduction est [SacreBLEU](https://github.com/mjpost/sacrebleu) qui remédie à cette faiblesse (et à d'autres) en standardisant l'étape de tokenisation. Pour utiliser cette métrique, nous devons d'abord installer la bibliothèque *SacreBLEU* : - -```py -!pip install sacrebleu -``` - -Nous pouvons ensuite charger ce score via `evaluate.load()` comme nous l'avons fait dans le [chapitre 3](/course/fr/chapter3) : - -```py -import evaluate - -metric = evaluate.load("sacrebleu") -``` - -Cette métrique prend des textes comme entrées et cibles. Elle est conçue pour accepter plusieurs cibles acceptables car il y a souvent plusieurs traductions possibles d'une même phrase. Le jeu de données que nous utilisons n'en fournit qu'une seule, mais en NLP, il n'est pas rare de trouver des jeux de données ayant plusieurs phrases comme étiquettes. Ainsi, les prédictions doivent être une liste de phrases mais les références doivent être une liste de listes de phrases. - -Essayons un exemple : - -```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} -``` - -Cela donne un score BLEU de 46.75, ce qui est plutôt bon. A titre de comparaison, le *Transformer* original dans l'article [*Attention Is All You Need*](https://arxiv.org/pdf/1706.03762.pdf) a obtenu un score BLEU de 41.8 sur une tâche de traduction similaire entre l'anglais et le français ! (Pour plus d'informations sur les métriques individuelles, comme `counts` et `bp`, voir le [dépôt SacreBLEU](https://github.com/mjpost/sacrebleu/blob/078c440168c6adc89ba75fe6d63f0d922d42bcfe/sacrebleu/metrics/bleu.py#L74). D'autre part, si nous essayons avec les deux mauvais types de prédictions (répétitions ou prédiction trop courte) qui sortent souvent des modèles de traduction, nous obtiendrons des scores BLEU plutôt mauvais : - -```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} -``` - -Le score peut aller de 0 à 100. Plus il est élevé, mieux c'est. - -{#if fw === 'tf'} - -Pour passer des sorties du modèle aux textes que la métrique peut utiliser, nous allons utiliser la méthode `tokenizer.batch_decode()`. Nous devons juste nettoyer tous les `-100` dans les étiquettes. Le *tokenizer* fera automatiquement la même chose pour le *token* de *padding*. Définissons une fonction qui prend notre modèle et un jeu de données et calcule des métriques sur ceux-ci. Comme la génération de longues séquences peut être lente, nous sous-échantillonnons l'ensemble de validation pour nous assurer que cela ne prend pas une éternité : - -```py -import numpy as np - - -def compute_metrics(): - all_preds = [] - all_labels = [] - sampled_dataset = tokenized_datasets["validation"].shuffle().select(range(200)) - tf_generate_dataset = sampled_dataset.to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=False, - batch_size=4, - ) - for batch in tf_generate_dataset: - predictions = model.generate( - input_ids=batch["input_ids"], attention_mask=batch["attention_mask"] - ) - decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) - labels = batch["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} - -Pour passer des sorties du modèle aux textes utilisables par la métrique, nous allons utiliser la méthode `tokenizer.batch_decode()`. Nous devons juste nettoyer tous les `-100` dans les étiquettes. Le *tokenizer* fera automatiquement la même chose pour le *token* de *padding* : - -```py -import numpy as np - - -def compute_metrics(eval_preds): - preds, labels = eval_preds - # Dans le cas où le modèle retourne plus que les logits de prédiction - if isinstance(preds, tuple): - preds = preds[0] - - decoded_preds = tokenizer.batch_decode(preds, skip_special_tokens=True) - - # Remplacer les -100 dans les étiquettes car nous ne pouvons pas les décoder - labels = np.where(labels != -100, labels, tokenizer.pad_token_id) - decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) - - # Quelques post-traitements simples - 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} - -Maintenant que c'est fait, nous sommes prêts à *finetuner* notre modèle ! - - -### Finetuner le modèle - -La première étape consiste à se connecter à Hugging Face, afin de pouvoir télécharger vos résultats sur le *Hub*. Il y a une fonction pratique pour vous aider à le faire dans un *notebook* : - -```python -from huggingface_hub import notebook_login - -notebook_login() -``` - -Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face. - -Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : - -```bash -huggingface-cli login -``` - -{#if fw === 'tf'} - -Avant de commencer, voyons quel type de résultats nous obtenons avec notre modèle sans entraînement : - -```py -print(compute_metrics()) -``` - -``` -{'bleu': 33.26983701454733} -``` - -Une fois ceci fait, nous pouvons préparer tout ce dont nous avons besoin pour compiler et entraîner notre modèle. Notez l'utilisation de `tf.keras.mixed_precision.set_global_policy("mixed_float16")`. Ceci indiquera à Keras de s'entraîner en utilisant float16, ce qui peut donner un gain de vitesse significatif sur les GPUs qui le supportent (Nvidia 20xx/V100 ou plus récent). - -```python -from transformers import create_optimizer -from transformers.keras_callbacks import PushToHubCallback -import tensorflow as tf - -# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans le jeu de données, divisé par la taille du batch, -# puis multiplié par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un tf.data.Dataset, -# et non le jeu de données original donc son len() est déjà 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) - -# Entraîner en mixed-precision float16 -tf.keras.mixed_precision.set_global_policy("mixed_float16") -``` - -Ensuite, nous définissons un `PushToHubCallback` pour télécharger notre modèle sur le *Hub* pendant l'entraînement, comme nous l'avons vu dans la [section 2](/course/fr/chapter7/2), puis nous entraînons simplement le modèle avec ce *callback* : - -```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, -) -``` - -Notez que vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser le modèle avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"` dans `Seq2SeqTrainingArguments`. Par défaut, le dépôt utilisé sera dans votre espace et nommé après le répertoire de sortie que vous avez défini. Ici ce sera `"sgugger/marian-finetuned-kde4-en-to-fr"` (qui est le modèle que nous avons lié au début de cette section). - - - -💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de l'appel de `model.fit()` et devrez définir un nouveau nom. - - - -Enfin, voyons à quoi ressemblent nos métriques maintenant que l'entraînement est terminé : - -```py -print(compute_metrics()) -``` - -``` -{'bleu': 57.334066271545865} -``` - -À ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à *finetuner* un modèle sur une tâche de traduction. Félicitations ! - -{:else} - -Une fois ceci fait, nous pouvons définir notre `Seq2SeqTrainingArguments`. Comme pour le `Trainer`, nous utilisons une sous-classe de `TrainingArguments` qui contient quelques champs supplémentaires : - -```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, -) -``` - -En dehors des hyperparamètres habituels (comme le taux d'apprentissage, le nombre d'époques, la taille des batchs et une le taux de décroissance des poids), voici quelques changements par rapport à ce que nous avons vu dans les sections précédentes : - -- Nous ne définissons pas d'évaluation car elle prend du temps. Nous allons juste évaluer une fois notre modèle avant l'entraînement et après. -- Nous avons mis `fp16=True`, ce qui accélère l'entraînement sur les GPUs modernes. -- Nous définissons `predict_with_generate=True`, comme discuté ci-dessus. -- Nous utilisons `push_to_hub=True` pour télécharger le modèle sur le *Hub* à la fin de chaque époque. - -Notez que vous pouvez spécifier le nom complet du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"` à `Seq2SeqTrainingArguments`. Par défaut, le dépôt utilisé sera dans votre espace et nommé d'après le répertoire de sortie que vous avez défini. Dans notre cas ce sera `"sgugger/marian-finetuned-kde4-en-to-fr"` (qui est le modèle que nous avons lié au début de cette section). - - - -💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de la définition de votre `Seq2SeqTrainer` et devrez définir un nouveau nom. - - - - -Enfin, nous passons tout au `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, -) -``` - -Avant d'entraîner, nous allons d'abord regarder le score obtenu par notre modèle, pour vérifier que nous n'aggravons pas les choses avec notre *finetuning*. Cette commande va prendre un peu de temps, vous pouvez donc prendre un café pendant qu'elle s'exécute : - -```python -trainer.evaluate(max_length=max_target_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} -``` - -Un score BLEU de 39 n'est pas trop mauvais, ce qui reflète le fait que notre modèle est déjà bon pour traduire des phrases anglaises en phrases françaises. - -Vient ensuite l'entraînement, qui prendra également un peu de temps : - -```python -trainer.train() -``` - -Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. De cette façon, vous serez en mesure de reprendre votre entraînement sur une autre machine si nécessaire. - -Une fois l'entraînement terminé, nous évaluons à nouveau notre modèle. Avec un peu de chance, nous verrons une amélioration du score BLEU ! - -```py -trainer.evaluate(max_length=max_target_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} -``` - -C'est une amélioration de près de 14 points, ce qui est formidable. - -Enfin, nous utilisons la méthode `push_to_hub()` pour nous assurer que nous téléchargeons la dernière version du modèle. `Trainer` rédige également une carte de modèle avec tous les résultats de l'évaluation et la télécharge. Cette carte de modèle contient des métadonnées qui aident le *Hub* à choisir le *widget* pour l'inférence. Habituellement, il n'y a pas besoin de dire quoi que ce soit car il peut inférer le bon *widget* à partir de la classe du modèle, mais dans ce cas, la même classe de modèle peut être utilisée pour toutes sortes de problèmes de séquence à séquence. Ainsi nous spécifions que c'est un modèle de traduction : - -```py -trainer.push_to_hub(tags="translation", commit_message="Training complete") -``` - -Cette commande renvoie l'URL du commit qu'elle vient de faire, si vous voulez l'inspecter : - -```python out -'https://huggingface.co/sgugger/marian-finetuned-kde4-en-to-fr/commit/3601d621e3baae2bc63d3311452535f8f58f6ef3' -``` - -À ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à *finetuner* un modèle sur une tâche de traduction. Félicitations ! - -Si vous souhaitez vous plonger un peu plus profondément dans la boucle d'entraînement, nous allons maintenant vous montrer comment faire la même chose en utilisant 🤗 *Accelerate*. - -{/if} - -{#if fw === 'pt'} - -## Une boucle d'entraînement personnalisée - -Jetons maintenant un coup d'œil à la boucle d'entraînement complète afin que vous puissiez facilement personnaliser les parties dont vous avez besoin. Elle ressemblera beaucoup à ce que nous avons fait dans la [section 2](/course/fr/chapter7/2) et dans le [chapitre 3](/course/fr/chapter3/4). - -### Préparer le tout pour l'entraînement - -Vous avez vu tout cela plusieurs fois maintenant, donc nous allons passer en revue le code assez rapidement. D'abord, nous allons construire le `DataLoader` à partir de nos jeux de données, après avoir configuré les jeux de données au format `"torch"` pour obtenir les tenseurs 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 -) -``` - -Ensuite, nous réinstantifions notre modèle pour nous assurer que nous ne poursuivons pas le *finetuning* précédent et que nous repartons du modèle pré-entraîné : - -```py -model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) -``` - -Nous aurons alors besoin d'un optimiseur : - -```py -from transformers import AdamW - -optimizer = AdamW(model.parameters(), lr=2e-5) -``` - -Une fois que nous avons tous ces objets, nous pouvons les envoyer à la méthode `accelerator.prepare()`. Rappelez-vous que si vous voulez entraîner sur des TPUs dans un *notebook* de Colab, vous devez déplacer tout ce code dans une fonction d'entraînement et ne devrait pas exécuter une cellule qui instancie un `Accelerator`. - -```py -from accelerate import Accelerator - -accelerator = Accelerator() -model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( - model, optimizer, train_dataloader, eval_dataloader -) -``` - -Maintenant que nous avons envoyé notre `train_dataloader` à `accelerator.prepare()`, nous pouvons utiliser sa longueur pour calculer le nombre d'étapes d'entraînement. Rappelez-vous que nous devrions toujours faire cela après avoir préparé le chargeur de données car cette méthode va changer la longueur du `DataLoader`. Nous utilisons un programme linéaire classique du taux d'apprentissage à 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, -) -``` - -Enfin, pour pousser notre modèle vers le *Hub*, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous au *Hub* si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'identifiant du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix, il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `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' -``` - -Ensuite, nous pouvons cloner ce dépôt dans un dossier local. S'il existe déjà, ce dossier local doit être un clone du dépôt avec lequel nous travaillons : - -```py -output_dir = "marian-finetuned-kde4-en-to-fr-accelerate" -repo = Repository(output_dir, clone_from=repo_name) -``` - -Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output_dir` en appelant la méthode `repo.push_to_hub()`. Cela nous aidera à télécharger les modèles intermédiaires à la fin de chaque époque. - -### Boucle d'entraînement - -Nous sommes maintenant prêts à écrire la boucle d'entraînement complète. Pour simplifier sa partie évaluation, nous définissons cette fonction `postprocess()` qui prend les prédictions et les étiquettes et les convertit en listes de chaînes de caractères que notre objet `metric` attend : - -```py -def postprocess(predictions, labels): - predictions = predictions.cpu().numpy() - labels = labels.cpu().numpy() - - decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) - - # Remplace -100 dans les étiquettes car nous ne pouvons pas les décoder - labels = np.where(labels != -100, labels, tokenizer.pad_token_id) - decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) - - # Quelques post-traitements simples - decoded_preds = [pred.strip() for pred in decoded_preds] - decoded_labels = [[label.strip()] for label in decoded_labels] - return decoded_preds, decoded_labels -``` - -La boucle d'entraînement ressemble beaucoup à celles de la [section 2](/course/fr/chapter7/2) et du [chapitre 3](/course/fr/chapter3), avec quelques différences dans la partie évaluation. Donc concentrons-nous sur cela ! - -La première chose à noter est que nous utilisons la méthode `generate()` pour calculer les prédictions. C'est une méthode sur notre modèle de base et non pas le modèle enveloppé créé dans la méthode `prepare()`. C'est pourquoi nous déballons d'abord le modèle, puis nous appelons cette méthode. - -La deuxième chose est que, comme avec la classification de [*token*](/course/fr/chapter7/2), deux processus peuvent avoir rembourrés les entrées et les étiquettes à des formes différentes. Ainsi nous utilisons `accelerator.pad_across_processes()` pour rendre les prédictions et les étiquettes de la même forme avant d'appeler la méthode `gather()`. Si nous ne faisons pas cela, l'évaluation va soit se tromper, soit se bloquer pour toujours. - -```py -from tqdm.auto import tqdm -import torch - -progress_bar = tqdm(range(num_training_steps)) - -for epoch in range(num_train_epochs): - # Entraînement - 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) - - # Evaluation - 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"] - - # Nécessaire pour rembourrer les prédictions et les étiquettes à rassembler - 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}") - - # Sauvegarder et télécharger - 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 -``` - -Une fois que c'est fait, vous devriez avoir un modèle qui a des résultats assez similaires à celui entraîné avec `Seq2SeqTrainer`. Vous pouvez vérifier celui que nous avons entraîné en utilisant ce code sur [*huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate*](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate). Et si vous voulez tester des modifications de la boucle d'entraînement, vous pouvez les mettre en œuvre directement en modifiant le code ci-dessus ! - -{/if} - -### Utilisation du modèle finetuné - -Nous vous avons déjà montré comment vous pouvez utiliser le modèle que nous avons *finetuné* sur le *Hub* avec le *widget* d'inférence. Pour l'utiliser localement dans un `pipeline`, nous devons juste spécifier l'identifiant de modèle approprié : - -```py -from transformers import pipeline - -# Remplacez ceci par votre propre checkpoint -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'}] -``` - -Comme prévu, notre modèle pré-entraîné a adapté ses connaissances au corpus sur lequel nous l'avons *finetuné*. Et au lieu de laisser le mot anglais « *threads* », le modèle le traduit maintenant par la version française officielle. Il en va de même pour « *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."}] -``` - -Un autre excellent exemple d'adaptation au domaine ! - - - -✏️ **A votre tour !** Que retourne le modèle sur l'échantillon avec le mot « *email* » que vous avez identifié plus tôt ? - - + + +# Traduction + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Plongeons maintenant dans la traduction. Il s'agit d'une autre [tâche de séquence à séquence](/course/fr/chapitre1/7), ce qui signifie que c'est un problème qui peut être formulé comme le passage d'une séquence à une autre. En ce sens, le problème est assez proche de la tâche de [résumé](/course/fr/chapitre7/6) et vous pouvez adapter ce que nous allons voir ici à d'autres problèmes de séquence à séquence tels que : + +- Le **transfert de style** ? c'est-à-dire créer un modèle qui *traduit* des textes écrits dans un certain style vers un autre (par exemple, du formel au décontracté ou de l'anglais shakespearien à l'anglais moderne). +- La **génération de réponse à des questions** c'est-à-dire créer un modèle qui génère des réponses à des questions compte tenu d'un contexte. + + + +Si vous disposez d'un corpus de textes suffisamment important en deux langues différentes (ou plus), vous pouvez entraîner un nouveau modèle de traduction à partir de zéro, comme nous le ferons dans la section sur la [modélisation causale du langage](/course/fr/chapitre7/6). Il est toutefois plus rapide de *finetuner* un modèle de traduction existant, qu'il s'agisse d'un modèle multilingue comme mT5 ou mBART que vous souhaitez adapter à une paire de langues spécifique, ou même d'un modèle spécialisé dans la traduction d'une langue vers une autre que vous souhaitez adapter à votre corpus spécifique. + +Dans cette section, nous allons *finetuner* un modèle Marian pré-entraîné pour traduire de l'anglais au français (puisque de nombreux employés de Hugging Face parlent ces deux langues) sur le jeu de données [KDE4](https://huggingface.co/datasets/kde4) qui est un jeu de données de fichiers localisés pour les applications [KDE](https://apps.kde.org/). Le modèle que nous utiliserons a été pré-entraîné sur un large corpus de textes français et anglais provenant du jeu de données [Opus](https://opus.nlpl.eu/) qui contient en fait le jeu de données KDE4. A noter que même si le modèle pré-entraîné que nous utilisons a vu ces données pendant son pré-entraînement, nous verrons que nous pouvons obtenir une meilleure version de ce modèle après un *finetuning*. + +Une fois que nous aurons terminé, nous aurons un modèle capable de faire des prédictions comme celle-ci : + + + + + +One-hot encoded labels for question answering. + + + +Comme dans les sections précédentes, vous pouvez trouver, télécharger et vérifier les précisions de ce modèle sur le [*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.). + +## Préparation des données + +Pour *finetuner* ou entraîner un modèle de traduction à partir de zéro, nous avons besoin d'un jeu de données adapté à cette tâche. Comme mentionné précédemment, nous utiliserons le jeu de données [KDE4](https://huggingface.co/datasets/kde4) dans cette section. Notez que vous pouvez adapter assez facilement le code pour utiliser vos propres données du moment que vous disposez de paires de phrases dans les deux langues que vous voulez traduire. Reportez-vous au [chapitre 5](/course/fr/chapter5) si vous avez besoin d'un rappel sur la façon de charger vos données personnalisées dans un `Dataset`. + +### Le jeu de données KDE4 + +Comme d'habitude, nous téléchargeons notre jeu de données en utilisant la fonction `load_dataset()` : + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("kde4", lang1="en", lang2="fr") +``` + +Si vous souhaitez travailler avec une autre paire de langues, 92 langues sont disponibles au total pour ce jeu de données. Vous pouvez les voir dans la [carte du jeu de données](https://huggingface.co/datasets/kde4). + +Language available for the KDE4 dataset. + +Jetons un coup d'œil au jeu de données : + +```py +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['id', 'translation'], + num_rows: 210173 + }) +}) +``` + +Nous avons 210 173 paires de phrases. Cependant regroupées dans un seul échantillon. Nous devrons donc créer notre propre jeu de validation. Comme nous l'avons vu dans le [chapitre 5](/course/fr/chapter5), un `Dataset` possède une méthode `train_test_split()` qui peut nous aider. Nous allons fournir une graine pour la reproductibilité : + +```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 + }) +}) +``` + +Nous pouvons renommer la clé `test` en `validation` comme ceci : + +```py +split_datasets["validation"] = split_datasets.pop("test") +``` + +Examinons maintenant un élément de ce jeu de données : + +```py +split_datasets["train"][1]["translation"] +``` + +```python out +{'en': 'Default to expanded threads', + 'fr': 'Par défaut, développer les fils de discussion'} +``` + +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 : + +```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'}] +``` + +Un autre exemple de ce comportement peut être observé avec le mot « *plugin* » qui n'est pas officiellement un mot français mais que la plupart des francophones comprendront et ne prendront pas la peine de traduire. +Dans le jeu de données KDE4, ce mot a été traduit en français par le plus officiel « 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."} +``` + +Notre modèle pré-entraîné, lui, s'en tient au mot anglais : + +```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."}] +``` + +Il sera intéressant de voir si notre modèle *finetuné* tient compte de ces particularités (alerte *spoiler* : il le fera). + + + + + +✏️ **A votre tour !** Un autre mot anglais souvent utilisé en français est « *email* ». Trouvez le premier échantillon dans l'échantillon d'entraînement qui utilise ce mot. Comment est-il traduit ? Comment le modèle pré-entraîné traduit-il cette même phrase ? + + + +### Traitement des données + + + +Vous devriez maintenant connaître le principe : les textes doivent tous être convertis en ensembles d'ID de *tokens* pour que le modèle puisse leur donner un sens. Pour cette tâche, nous aurons besoin de tokeniser les entrées et les cibles. Notre première tâche est de créer notre objet `tokenizer`. Comme indiqué précédemment, nous utiliserons un modèle pré-entraîné Marian English to French. Si vous essayez ce code avec une autre paire de langues, assurez-vous d'adapter le *checkpoint* du modèle. L'organisation [Helsinki-NLP](https://huggingface.co/Helsinki-NLP) fournit plus de mille modèles dans plusieurs langues. + +```python +from transformers import AutoTokenizer + +model_checkpoint = "Helsinki-NLP/opus-mt-en-fr" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, return_tensors="tf") +``` + +Vous pouvez remplacer le `model_checkpoint` par un tout autre modèle disponible sur le [*Hub*](https://huggingface.co/models) qui aurait votre préférence, ou par un dossier en local où vous avez sauvegardé un modèle pré-entraîné et un *tokenizer*. + + + +💡 Si vous utilisez un *tokenizer* multilingue tel que mBART, mBART-50 ou M2M100, vous devrez définir les codes de langue de vos entrées et cibles dans le *tokenizer* en définissant `tokenizer.src_lang` et `tokenizer.tgt_lang` aux bonnes valeurs. + + + +La préparation de nos données est assez simple. Il y a juste une chose à retenir : vous traitez les entrées comme d'habitude, mais pour les cibles, vous devez envelopper le *tokenizer* dans le gestionnaire de contexte `as_target_tokenizer()`. + +Un gestionnaire de contexte en Python est introduit avec l'instruction `with` et est utile lorsque vous avez deux opérations liées à exécuter en paire. L'exemple le plus courant est lorsque vous écrivez ou lisez un fichier, ce qui est souvent fait dans une instruction comme : + +``` +with open(file_path) as f: + content = f.read() +``` + +Ici, les deux opérations connexes qui sont exécutées en paire sont les actions d'ouverture et de fermeture du fichier. L'objet correspondant au fichier ouvert `f` n'existe qu'à l'intérieur du bloc indenté sous le `with`. L'ouverture se produit avant ce bloc et la fermeture à la fin du bloc. + +Dans le cas présent, le gestionnaire de contexte `as_target_tokenizer()` va définir le *tokenizer* dans la langue de sortie (ici, le français) avant l'exécution du bloc indenté, puis le redéfinir dans la langue d'entrée (ici, l'anglais). + +Ainsi, le prétraitement d'un échantillon ressemble à ceci : + +```python +en_sentence = split_datasets["train"][1]["translation"]["en"] +fr_sentence = split_datasets["train"][1]["translation"]["fr"] + +inputs = tokenizer(en_sentence) +with tokenizer.as_target_tokenizer(): + targets = tokenizer(fr_sentence) +``` + +Si nous oublions de tokeniser les cibles dans le gestionnaire de contexte, elles seront tokenisées par le *tokenizer* d'entrée, ce qui dans le cas d'un modèle Marian, ne va pas du tout bien se passer : + +```python +wrong_targets = tokenizer(fr_sentence) +print(tokenizer.convert_ids_to_tokens(wrong_targets["input_ids"])) +print(tokenizer.convert_ids_to_tokens(targets["input_ids"])) +``` + +```python out +['▁Par', '▁dé', 'f', 'aut', ',', '▁dé', 've', 'lop', 'per', '▁les', '▁fil', 's', '▁de', '▁discussion', ''] +['▁Par', '▁défaut', ',', '▁développer', '▁les', '▁fils', '▁de', '▁discussion', ''] +``` + +Comme on peut le voir, utiliser le *tokenizer* anglais pour prétraiter une phrase française donne un batch de *tokens* plus important, puisque le *tokenizer* ne connaît aucun mot français (sauf ceux qui apparaissent aussi en anglais, comme « discussion »). + +Les `inputs` et les `targets` sont des dictionnaires avec nos clés habituelles (identifiants d'entrée, masque d'attention, etc.). La dernière étape est de définir une clé `"labels"` dans les entrées. Nous faisons cela dans la fonction de prétraitement que nous allons appliquer sur les jeux de données : + +```python +max_input_length = 128 +max_target_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, max_length=max_input_length, truncation=True) + + # Configurer le tokenizer pour les cibles. + with tokenizer.as_target_tokenizer(): + labels = tokenizer(targets, max_length=max_target_length, truncation=True) + + model_inputs["labels"] = labels["input_ids"] + return model_inputs +``` + +Notez que nous avons fixé des longueurs maximales similaires pour nos entrées et nos sorties. Comme les textes que nous traitons semblent assez courts, nous utilisons 128. + + + +💡 Si vous utilisez un modèle T5 (plus précisément, un des *checkpoints* `t5-xxx`), le modèle s'attendra à ce que les entrées aient un préfixe indiquant la tâche à accomplir, comme `translate: English to French:`. + + + + + +⚠️ Nous ne faisons pas attention au masque d'attention des cibles car le modèle ne s'y attend pas. Au lieu de cela, les étiquettes correspondant à un *token* de *padding* doivent être mises à `-100` afin qu'elles soient ignorées dans le calcul de la perte. Cela sera fait par notre collateur de données plus tard puisque nous appliquons le *padding* dynamique, mais si vous utilisez le *padding* ici, vous devriez adapter la fonction de prétraitement pour mettre toutes les étiquettes qui correspondent au *token* de *padding* à `-100`. + + + +Nous pouvons maintenant appliquer ce prétraitement en une seule fois sur toutes les échantillons de notre jeu de données : + +```py +tokenized_datasets = split_datasets.map( + preprocess_function, + batched=True, + remove_columns=split_datasets["train"].column_names, +) +``` + +Maintenant que les données ont été prétraitées, nous sommes prêts à *finetuner* notre modèle pré-entraîné ! + +{#if fw === 'pt'} + +## Finetuner le modèle avec l'API `Trainer` + +Le code actuel utilisant `Trainer` sera le même que précédemment, avec juste un petit changement : nous utilisons ici [`Seq2SeqTrainer`](https://huggingface.co/transformers/main_classes/trainer.html#seq2seqtrainer) qui est une sous-classe de `Trainer` qui nous permet de traiter correctement l'évaluation, en utilisant la méthode `generate()` pour prédire les sorties à partir des entrées. Nous y reviendrons plus en détail lorsque nous parlerons du calcul de la métrique. + +Tout d'abord, nous avons besoin d'un modèle à *finetuner*. Nous allons utiliser l'API habituelle `AutoModel` : + +```py +from transformers import AutoModelForSeq2SeqLM + +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{:else} + +## Finetuner du modèle avec Keras + +Tout d'abord, nous avons besoin d'un modèle à *finetuner*. Nous allons utiliser l'API habituelle `AutoModel` : + +```py +from transformers import TFAutoModelForSeq2SeqLM + +model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint, from_pt=True) +``` + + + +💡 Le *checkpoint* `Helsinki-NLP/opus-mt-en-fr` ne dispose que de poids PyTorch, vous aurez donc une erreur si vous essayez de charger le modèle sans utiliser l'argument `from_pt=True` dans la méthode `from_pretrained()`. Lorsque vous spécifiez `from_pt=True`, la bibliothèque téléchargera et convertira automatiquement les poids PyTorch pour vous. Comme vous pouvez le constater, c'est très simple de passer d'un *framework* à l'autre dans 🤗 *Transformers* ! + + + +{/if} + +Notez que cette fois-ci, nous utilisons un modèle qui a été entraîné sur une tâche de traduction et qui peut déjà être utilisé, donc il n'y a pas d'avertissement concernant les poids manquants ou ceux nouvellement initialisés. + +### Collecte des données + +Nous aurons besoin d'un assembleur de données pour gérer le rembourrage pour la mise en batchs dynamique. Ici, nous ne pouvons pas simplement utiliser un `DataCollatorWithPadding` comme dans le [chapitre 3](/course/fr/chapter3) car cela ne rembourre que les entrées (identifiants d'entrée, masque d'attention, et *token* de type identifiants). Nos étiquettes doivent également être rembourrées à la longueur maximale rencontrée dans les étiquettes. Et, comme mentionné précédemment, la valeur de remplissage utilisée pour remplir les étiquettes doit être `-100` et non le *token* de *padding* du *tokenizer* afin de s'assurer que ces valeurs soient ignorées dans le calcul de la perte. + +Tout ceci est réalisé par un [`DataCollatorForSeq2Seq`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorforseq2seq). Comme le `DataCollatorWithPadding`, il prend le `tokenizer` utilisé pour prétraiter les entrées, mais également le `model`. C'est parce que ce collateur de données est également responsable de la préparation des identifiants d'entrée du décodeur, qui sont des versions décalées des étiquettes avec un *token* spécial au début. Comme ce décalage est effectué de manière légèrement différente selon les architectures, le `DataCollatorForSeq2Seq` a besoin de connaître l'objet `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} + +Pour le tester sur quelques échantillons, nous l'appelons simplement sur une liste d'exemples de notre échantillon d'entrainement tokénisé : + +```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']) +``` + +Nous pouvons vérifier que nos étiquettes ont été rembourrées à la longueur maximale du batch, en utilisant `-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]]) +``` + +Nous pouvons aussi jeter un coup d'œil aux identifiants d'entrée du décodeur, pour voir qu'il s'agit de versions décalées des étiquettes : + +```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]]) +``` + +Voici les étiquettes des premier et deuxième éléments de notre jeu de données : + +```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'} + +Nous allons transmettre ce `data_collator` au `Seq2SeqTrainer`. Ensuite, jetons un coup d'oeil à la métrique. + +{:else} + +Nous pouvons maintenant utiliser ce `data_collator` pour convertir chacun de nos jeux de données en un `tf.data.Dataset`, prêt pour l'entraînement : + +```python +tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) +tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=16, +) +``` + +{/if} + + +### Métriques + + + +{#if fw === 'pt'} + +La fonctionnalité que `Seq2SeqTrainer` ajoute à sa superclasse `Trainer` est la possibilité d'utiliser la méthode `generate()` pendant l'évaluation ou la prédiction. Pendant l'entraînement, le modèle utilisera les `decoder_input_ids` avec un masque d'attention assurant qu'il n'utilise pas les *tokens* après le *token* qu'il essaie de prédire, pour accélérer l'entraînement. Pendant l'inférence, nous ne pourrons pas les utiliser puisque nous n'aurons pas d'étiquettes. Ainsi c'est une bonne idée d'évaluer notre modèle avec la même configuration. + +Comme nous l'avons vu dans le [chapitre 1](/course/fr/chapter1/6), le décodeur effectue l'inférence en prédisant les *tokens* un par un. C'est quelque chose qui est implémenté en coulisses dans 🤗 *Transformers* par la méthode `generate()`. Le `Seq2SeqTrainer` nous laissera utiliser cette méthode pour l'évaluation si nous indiquons `predict_with_generate=True`. + +{/if} + +La métrique traditionnelle utilisée pour la traduction est le [score BLEU](https://en.wikipedia.org/wiki/BLEU), introduit dans [un article de 2002](https://aclanthology.org/P02-1040.pdf) par Kishore Papineni et al. Le score BLEU évalue dans quelle mesure les traductions sont proches de leurs étiquettes. Il ne mesure pas l'intelligibilité ou l'exactitude grammaticale des résultats générés par le modèle, mais utilise des règles statistiques pour garantir que tous les mots des résultats générés apparaissent également dans les cibles. En outre, il existe des règles qui pénalisent les répétitions des mêmes mots s'ils ne sont pas également répétés dans les cibles (pour éviter que le modèle ne produise des phrases telles que « the the the the the the the ») et les phrases produites qui sont plus courtes que celles des cibles (pour éviter que le modèle ne produise des phrases telles que « the »). + +L'une des faiblesses de BLEU est qu'il s'attend à ce que le texte soit déjà tokenisé, ce qui rend difficile la comparaison des scores entre les modèles qui utilisent différents *tokenizers*. Par conséquent, la mesure la plus couramment utilisée aujourd'hui pour évaluer les modèles de traduction est [SacreBLEU](https://github.com/mjpost/sacrebleu) qui remédie à cette faiblesse (et à d'autres) en standardisant l'étape de tokenisation. Pour utiliser cette métrique, nous devons d'abord installer la bibliothèque *SacreBLEU* : + +```py +!pip install sacrebleu +``` + +Nous pouvons ensuite charger ce score via `evaluate.load()` comme nous l'avons fait dans le [chapitre 3](/course/fr/chapter3) : + +```py +import evaluate + +metric = evaluate.load("sacrebleu") +``` + +Cette métrique prend des textes comme entrées et cibles. Elle est conçue pour accepter plusieurs cibles acceptables car il y a souvent plusieurs traductions possibles d'une même phrase. Le jeu de données que nous utilisons n'en fournit qu'une seule, mais en NLP, il n'est pas rare de trouver des jeux de données ayant plusieurs phrases comme étiquettes. Ainsi, les prédictions doivent être une liste de phrases mais les références doivent être une liste de listes de phrases. + +Essayons un exemple : + +```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} +``` + +Cela donne un score BLEU de 46.75, ce qui est plutôt bon. A titre de comparaison, le *Transformer* original dans l'article [*Attention Is All You Need*](https://arxiv.org/pdf/1706.03762.pdf) a obtenu un score BLEU de 41.8 sur une tâche de traduction similaire entre l'anglais et le français ! (Pour plus d'informations sur les métriques individuelles, comme `counts` et `bp`, voir le [dépôt SacreBLEU](https://github.com/mjpost/sacrebleu/blob/078c440168c6adc89ba75fe6d63f0d922d42bcfe/sacrebleu/metrics/bleu.py#L74). D'autre part, si nous essayons avec les deux mauvais types de prédictions (répétitions ou prédiction trop courte) qui sortent souvent des modèles de traduction, nous obtiendrons des scores BLEU plutôt mauvais : + +```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} +``` + +Le score peut aller de 0 à 100. Plus il est élevé, mieux c'est. + +{#if fw === 'tf'} + +Pour passer des sorties du modèle aux textes que la métrique peut utiliser, nous allons utiliser la méthode `tokenizer.batch_decode()`. Nous devons juste nettoyer tous les `-100` dans les étiquettes. Le *tokenizer* fera automatiquement la même chose pour le *token* de *padding*. Définissons une fonction qui prend notre modèle et un jeu de données et calcule des métriques sur ceux-ci. Comme la génération de longues séquences peut être lente, nous sous-échantillonnons l'ensemble de validation pour nous assurer que cela ne prend pas une éternité : + +```py +import numpy as np + + +def compute_metrics(): + all_preds = [] + all_labels = [] + sampled_dataset = tokenized_datasets["validation"].shuffle().select(range(200)) + tf_generate_dataset = sampled_dataset.to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=4, + ) + for batch in tf_generate_dataset: + predictions = model.generate( + input_ids=batch["input_ids"], attention_mask=batch["attention_mask"] + ) + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + labels = batch["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} + +Pour passer des sorties du modèle aux textes utilisables par la métrique, nous allons utiliser la méthode `tokenizer.batch_decode()`. Nous devons juste nettoyer tous les `-100` dans les étiquettes. Le *tokenizer* fera automatiquement la même chose pour le *token* de *padding* : + +```py +import numpy as np + + +def compute_metrics(eval_preds): + preds, labels = eval_preds + # Dans le cas où le modèle retourne plus que les logits de prédiction + if isinstance(preds, tuple): + preds = preds[0] + + decoded_preds = tokenizer.batch_decode(preds, skip_special_tokens=True) + + # Remplacer les -100 dans les étiquettes car nous ne pouvons pas les décoder + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + # Quelques post-traitements simples + 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} + +Maintenant que c'est fait, nous sommes prêts à *finetuner* notre modèle ! + + +### Finetuner le modèle + +La première étape consiste à se connecter à Hugging Face, afin de pouvoir télécharger vos résultats sur le *Hub*. Il y a une fonction pratique pour vous aider à le faire dans un *notebook* : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face. + +Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : + +```bash +huggingface-cli login +``` + +{#if fw === 'tf'} + +Avant de commencer, voyons quel type de résultats nous obtenons avec notre modèle sans entraînement : + +```py +print(compute_metrics()) +``` + +``` +{'bleu': 33.26983701454733} +``` + +Une fois ceci fait, nous pouvons préparer tout ce dont nous avons besoin pour compiler et entraîner notre modèle. Notez l'utilisation de `tf.keras.mixed_precision.set_global_policy("mixed_float16")`. Ceci indiquera à Keras de s'entraîner en utilisant float16, ce qui peut donner un gain de vitesse significatif sur les GPUs qui le supportent (Nvidia 20xx/V100 ou plus récent). + +```python +from transformers import create_optimizer +from transformers.keras_callbacks import PushToHubCallback +import tensorflow as tf + +# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans le jeu de données, divisé par la taille du batch, +# puis multiplié par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un tf.data.Dataset, +# et non le jeu de données original donc son len() est déjà 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) + +# Entraîner en mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +Ensuite, nous définissons un `PushToHubCallback` pour télécharger notre modèle sur le *Hub* pendant l'entraînement, comme nous l'avons vu dans la [section 2](/course/fr/chapter7/2), puis nous entraînons simplement le modèle avec ce *callback* : + +```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, +) +``` + +Notez que vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser le modèle avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"` dans `Seq2SeqTrainingArguments`. Par défaut, le dépôt utilisé sera dans votre espace et nommé après le répertoire de sortie que vous avez défini. Ici ce sera `"sgugger/marian-finetuned-kde4-en-to-fr"` (qui est le modèle que nous avons lié au début de cette section). + + + +💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de l'appel de `model.fit()` et devrez définir un nouveau nom. + + + +Enfin, voyons à quoi ressemblent nos métriques maintenant que l'entraînement est terminé : + +```py +print(compute_metrics()) +``` + +``` +{'bleu': 57.334066271545865} +``` + +À ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à *finetuner* un modèle sur une tâche de traduction. Félicitations ! + +{:else} + +Une fois ceci fait, nous pouvons définir notre `Seq2SeqTrainingArguments`. Comme pour le `Trainer`, nous utilisons une sous-classe de `TrainingArguments` qui contient quelques champs supplémentaires : + +```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, +) +``` + +En dehors des hyperparamètres habituels (comme le taux d'apprentissage, le nombre d'époques, la taille des batchs et une le taux de décroissance des poids), voici quelques changements par rapport à ce que nous avons vu dans les sections précédentes : + +- Nous ne définissons pas d'évaluation car elle prend du temps. Nous allons juste évaluer une fois notre modèle avant l'entraînement et après. +- Nous avons mis `fp16=True`, ce qui accélère l'entraînement sur les GPUs modernes. +- Nous définissons `predict_with_generate=True`, comme discuté ci-dessus. +- Nous utilisons `push_to_hub=True` pour télécharger le modèle sur le *Hub* à la fin de chaque époque. + +Notez que vous pouvez spécifier le nom complet du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"` à `Seq2SeqTrainingArguments`. Par défaut, le dépôt utilisé sera dans votre espace et nommé d'après le répertoire de sortie que vous avez défini. Dans notre cas ce sera `"sgugger/marian-finetuned-kde4-en-to-fr"` (qui est le modèle que nous avons lié au début de cette section). + + + +💡 Si le répertoire de sortie que vous utilisez existe déjà, il doit être un clone local du dépôt vers lequel vous voulez pousser. S'il ne l'est pas, vous obtiendrez une erreur lors de la définition de votre `Seq2SeqTrainer` et devrez définir un nouveau nom. + + + + +Enfin, nous passons tout au `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, +) +``` + +Avant d'entraîner, nous allons d'abord regarder le score obtenu par notre modèle, pour vérifier que nous n'aggravons pas les choses avec notre *finetuning*. Cette commande va prendre un peu de temps, vous pouvez donc prendre un café pendant qu'elle s'exécute : + +```python +trainer.evaluate(max_length=max_target_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} +``` + +Un score BLEU de 39 n'est pas trop mauvais, ce qui reflète le fait que notre modèle est déjà bon pour traduire des phrases anglaises en phrases françaises. + +Vient ensuite l'entraînement, qui prendra également un peu de temps : + +```python +trainer.train() +``` + +Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. De cette façon, vous serez en mesure de reprendre votre entraînement sur une autre machine si nécessaire. + +Une fois l'entraînement terminé, nous évaluons à nouveau notre modèle. Avec un peu de chance, nous verrons une amélioration du score BLEU ! + +```py +trainer.evaluate(max_length=max_target_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} +``` + +C'est une amélioration de près de 14 points, ce qui est formidable. + +Enfin, nous utilisons la méthode `push_to_hub()` pour nous assurer que nous téléchargeons la dernière version du modèle. `Trainer` rédige également une carte de modèle avec tous les résultats de l'évaluation et la télécharge. Cette carte de modèle contient des métadonnées qui aident le *Hub* à choisir le *widget* pour l'inférence. Habituellement, il n'y a pas besoin de dire quoi que ce soit car il peut inférer le bon *widget* à partir de la classe du modèle, mais dans ce cas, la même classe de modèle peut être utilisée pour toutes sortes de problèmes de séquence à séquence. Ainsi nous spécifions que c'est un modèle de traduction : + +```py +trainer.push_to_hub(tags="translation", commit_message="Training complete") +``` + +Cette commande renvoie l'URL du commit qu'elle vient de faire, si vous voulez l'inspecter : + +```python out +'https://huggingface.co/sgugger/marian-finetuned-kde4-en-to-fr/commit/3601d621e3baae2bc63d3311452535f8f58f6ef3' +``` + +À ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* pour tester votre modèle et le partager avec vos amis. Vous avez réussi à *finetuner* un modèle sur une tâche de traduction. Félicitations ! + +Si vous souhaitez vous plonger un peu plus profondément dans la boucle d'entraînement, nous allons maintenant vous montrer comment faire la même chose en utilisant 🤗 *Accelerate*. + +{/if} + +{#if fw === 'pt'} + +## Une boucle d'entraînement personnalisée + +Jetons maintenant un coup d'œil à la boucle d'entraînement complète afin que vous puissiez facilement personnaliser les parties dont vous avez besoin. Elle ressemblera beaucoup à ce que nous avons fait dans la [section 2](/course/fr/chapter7/2) et dans le [chapitre 3](/course/fr/chapter3/4). + +### Préparer le tout pour l'entraînement + +Vous avez vu tout cela plusieurs fois maintenant, donc nous allons passer en revue le code assez rapidement. D'abord, nous allons construire le `DataLoader` à partir de nos jeux de données, après avoir configuré les jeux de données au format `"torch"` pour obtenir les tenseurs 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 +) +``` + +Ensuite, nous réinstantifions notre modèle pour nous assurer que nous ne poursuivons pas le *finetuning* précédent et que nous repartons du modèle pré-entraîné : + +```py +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +Nous aurons alors besoin d'un optimiseur : + +```py +from transformers import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Une fois que nous avons tous ces objets, nous pouvons les envoyer à la méthode `accelerator.prepare()`. Rappelez-vous que si vous voulez entraîner sur des TPUs dans un *notebook* de Colab, vous devez déplacer tout ce code dans une fonction d'entraînement et ne devrait pas exécuter une cellule qui instancie un `Accelerator`. + +```py +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + +Maintenant que nous avons envoyé notre `train_dataloader` à `accelerator.prepare()`, nous pouvons utiliser sa longueur pour calculer le nombre d'étapes d'entraînement. Rappelez-vous que nous devrions toujours faire cela après avoir préparé le chargeur de données car cette méthode va changer la longueur du `DataLoader`. Nous utilisons un programme linéaire classique du taux d'apprentissage à 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, +) +``` + +Enfin, pour pousser notre modèle vers le *Hub*, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous au *Hub* si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'identifiant du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix, il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `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' +``` + +Ensuite, nous pouvons cloner ce dépôt dans un dossier local. S'il existe déjà, ce dossier local doit être un clone du dépôt avec lequel nous travaillons : + +```py +output_dir = "marian-finetuned-kde4-en-to-fr-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output_dir` en appelant la méthode `repo.push_to_hub()`. Cela nous aidera à télécharger les modèles intermédiaires à la fin de chaque époque. + +### Boucle d'entraînement + +Nous sommes maintenant prêts à écrire la boucle d'entraînement complète. Pour simplifier sa partie évaluation, nous définissons cette fonction `postprocess()` qui prend les prédictions et les étiquettes et les convertit en listes de chaînes de caractères que notre objet `metric` attend : + +```py +def postprocess(predictions, labels): + predictions = predictions.cpu().numpy() + labels = labels.cpu().numpy() + + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + + # Remplace -100 dans les étiquettes car nous ne pouvons pas les décoder + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + # Quelques post-traitements simples + decoded_preds = [pred.strip() for pred in decoded_preds] + decoded_labels = [[label.strip()] for label in decoded_labels] + return decoded_preds, decoded_labels +``` + +La boucle d'entraînement ressemble beaucoup à celles de la [section 2](/course/fr/chapter7/2) et du [chapitre 3](/course/fr/chapter3), avec quelques différences dans la partie évaluation. Donc concentrons-nous sur cela ! + +La première chose à noter est que nous utilisons la méthode `generate()` pour calculer les prédictions. C'est une méthode sur notre modèle de base et non pas le modèle enveloppé créé dans la méthode `prepare()`. C'est pourquoi nous déballons d'abord le modèle, puis nous appelons cette méthode. + +La deuxième chose est que, comme avec la classification de [*token*](/course/fr/chapter7/2), deux processus peuvent avoir rembourrés les entrées et les étiquettes à des formes différentes. Ainsi nous utilisons `accelerator.pad_across_processes()` pour rendre les prédictions et les étiquettes de la même forme avant d'appeler la méthode `gather()`. Si nous ne faisons pas cela, l'évaluation va soit se tromper, soit se bloquer pour toujours. + +```py +from tqdm.auto import tqdm +import torch + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Entraînement + 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) + + # Evaluation + 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"] + + # Nécessaire pour rembourrer les prédictions et les étiquettes à rassembler + 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}") + + # Sauvegarder et télécharger + 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 +``` + +Une fois que c'est fait, vous devriez avoir un modèle qui a des résultats assez similaires à celui entraîné avec `Seq2SeqTrainer`. Vous pouvez vérifier celui que nous avons entraîné en utilisant ce code sur [*huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate*](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate). Et si vous voulez tester des modifications de la boucle d'entraînement, vous pouvez les mettre en œuvre directement en modifiant le code ci-dessus ! + +{/if} + +### Utilisation du modèle finetuné + +Nous vous avons déjà montré comment vous pouvez utiliser le modèle que nous avons *finetuné* sur le *Hub* avec le *widget* d'inférence. Pour l'utiliser localement dans un `pipeline`, nous devons juste spécifier l'identifiant de modèle approprié : + +```py +from transformers import pipeline + +# Remplacez ceci par votre propre checkpoint +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'}] +``` + +Comme prévu, notre modèle pré-entraîné a adapté ses connaissances au corpus sur lequel nous l'avons *finetuné*. Et au lieu de laisser le mot anglais « *threads* », le modèle le traduit maintenant par la version française officielle. Il en va de même pour « *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."}] +``` + +Un autre excellent exemple d'adaptation au domaine ! + + + +✏️ **A votre tour !** Que retourne le modèle sur l'échantillon avec le mot « *email* » que vous avez identifié plus tôt ? + + diff --git a/chapters/fr/chapter7/5.mdx b/chapters/fr/chapter7/5.mdx index c2177fb07..d2d570667 100644 --- a/chapters/fr/chapter7/5.mdx +++ b/chapters/fr/chapter7/5.mdx @@ -1,1083 +1,1083 @@ - - -# Résumé de textes - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - - -Dans cette section, nous allons voir comment les *transformers* peuvent être utilisés pour condenser de longs documents en résumés, une tâche connue sous le nom de _résumé de texte_. Il s'agit de l'une des tâches de NLP les plus difficiles car elle requiert une série de capacités, telles que la compréhension de longs passages et la génération d'un texte cohérent qui capture les sujets principaux d'un document. Cependant, lorsqu'il est bien fait, le résumé de texte est un outil puissant qui peut accélérer divers processus commerciaux en soulageant les experts du domaine de la lecture détaillée de longs documents. - - - -Bien qu'il existe déjà plusieurs modèles *finetunés* pour le résumé sur le [*Hub*](https://huggingface.co/models?pipeline_tag=summarization&sort=downloads), la plupart d'entre eux ne sont adaptés qu'aux documents en anglais. Ainsi, pour ajouter une touche d'originalité à cette section, nous allons entraîner un modèle bilingue pour l'anglais et l'espagnol. À la fin de cette section, vous disposerez d'un [modèle](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es) capable de résumer les commentaires des clients comme celui présenté ici : - - - - -Comme nous allons le voir, ces résumés sont concis car ils sont appris à partir des titres que les clients fournissent dans leurs commentaires sur les produits. Commençons par constituer un corpus bilingue approprié pour cette tâche. - -## Préparation d'un corpus multilingue - -Nous allons utiliser le [*Multilingual Amazon Reviews Corpus*](https://huggingface.co/datasets/amazon_reviews_multi) pour créer notre résumeur bilingue. Ce corpus est constitué de critiques de produits Amazon en six langues et est généralement utilisé pour évaluer les classifieurs multilingues. Cependant, comme chaque critique est accompagnée d'un titre court, nous pouvons utiliser les titres comme résumés cibles pour l'apprentissage de notre modèle ! Pour commencer, téléchargeons les sous-ensembles anglais et espagnols depuis le *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 - }) -}) -``` - -Comme vous pouvez le voir, pour chaque langue, il y a 200 000 critiques pour la partie entraînement et 5 000 critiques pour chacune des parties validation et test. Les informations qui nous intéressent sont contenues dans les colonnes `review_body` et `review_title`. Voyons quelques exemples en créant une fonction simple qui prend un échantillon aléatoire de l'ensemble d'entraînement avec les techniques apprises au [chapitre 5](/course/fr/chapter5) : - -```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' -# Travaillé en position avant, pas arrière -'>> 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.' -# 3 étoiles car ce ne sont pas des freins arrière comme indiqué dans la description de l'article. Au moins, l'adaptateur de montage ne fonctionnait que sur la fourche avant du vélo pour lequel je l'ai acheté. - -'>> 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' -# Il fait son travail et il est magnifique mais le mien est en train de tomber en morceaux, j'ai dû le recoller avec de la colle chaude. - -'>> Title: Can\'t beat these for the money' -# On ne peut pas faire mieux pour le prix -'>> 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.' -# Je l'ai acheté pour manipuler diverses pièces d'avion et des "trucs" de hangar que je devais organiser ; il a vraiment fait l'affaire. L'unité est arrivée rapidement, était bien emballée et est arrivée intacte (toujours un bon signe). Il y a cinq supports muraux - trois sur le dessus et deux sur le dessous. Je voulais le monter sur le mur, alors tout ce que j'ai eu à faire était d'enlever les deux couches supérieures de tiroirs en plastique, ainsi que les tiroirs d'angle inférieurs, de le placer où je voulais et de le marquer ; j'ai ensuite utilisé quelques-uns des nouveaux ancrages muraux à vis en plastique (la variété de 50 livres) et il s'est facilement monté sur le mur. Certains ont fait remarquer qu'ils voulaient des séparateurs pour les tiroirs, et qu'ils les ont fabriqués. Bonne idée. Pour ma part, j'avais besoin de quelque chose dont je pouvais voir le contenu à hauteur des yeux, et je voulais donc des tiroirs plus grands. J'aime aussi le fait qu'il s'agisse du nouveau plastique qui ne se fragilise pas et ne se fend pas comme mes anciens tiroirs en plastique. J'aime la construction entièrement en plastique. Elle est suffisamment résistante pour contenir des pièces métalliques, mais étant en plastique, elle n'est pas aussi lourde qu'un cadre métallique, ce qui permet de la fixer facilement au mur et de la charger d'objets lourds ou légers. Aucun problème. Pour le prix, c'est imbattable. C'est le meilleur que j'ai acheté à ce jour, et j'utilise des versions de ce type depuis plus de quarante ans. -``` - - - -✏️ **Essayez !** Changez la graine aléatoire dans la commande `Dataset.shuffle()` pour explorer d'autres critiques dans le corpus. Si vous parlez espagnol, jetez un coup d'œil à certaines des critiques dans `spanish_dataset` pour voir si les titres semblent aussi être des résumés raisonnables. - - - -Cet échantillon montre la diversité des critiques que l'on trouve généralement en ligne, allant du positif au négatif (et tout ce qui se trouve entre les deux !). Bien que l'exemple avec le titre « meh » ne soit pas très informatif, les autres titres semblent être des résumés décents des critiques. Entraîner un modèle de résumé sur l'ensemble des 400 000 avis prendrait beaucoup trop de temps sur un seul GPU, nous allons donc nous concentrer sur la génération de résumés pour un seul domaine de produits. Pour avoir une idée des domaines parmi lesquels nous pouvons choisir, convertissons `english_dataset` en `pandas.DataFrame` et calculons le nombre d'avis par catégorie de produits : - -```python -english_dataset.set_format("pandas") -english_df = english_dataset["train"][:] -# Afficher le compte des 20 premiers produits -english_df["product_category"].value_counts()[:20] -``` - -```python out -home 17679 # maison -apparel 15951 # vêtements -wireless 15717 # sans fil -other 13418 # autres -beauty 12091 # beauté -drugstore 11730 # pharmacie -kitchen 10382 # cuisine -toy 8745 # jouets -sports 8277 # sports -automotive 7506 # automobile -lawn_and_garden 7327 # pelouse_et_jardin -home_improvement 7136 # amélioration_de_la_maison -pet_products 7082 # produits_pour_animaux_de_compagnie -digital_ebook_purchase 6749 # achat_de_livres_numériques -pc 6401 # ordinateur_personnel -electronics 6186 # électronique -office_product 5521 # produits_de_bureau -shoes 5197 # chaussures -grocery 4730 # épicerie -book 3756 # livre -Name: product_category, dtype: int64 -``` - -Les produits les plus populaires du jeu de données anglais concernent les articles ménagers, les vêtements et l'électronique sans fil. Pour rester dans le thème d'Amazon, nous allons nous concentrer sur le résumé des critiques de livres. Après tout, c'est la raison d'être de l'entreprise ! Nous pouvons voir deux catégories de produits qui correspondent à nos besoins (`book` et `digital_ebook_purchase`). Nous allons donc filtrer les jeux de données dans les deux langues pour ces produits uniquement. Comme nous l'avons vu dans le [chapitre 5](/course/fr/chapter5), la fonction `Dataset.filter()` nous permet de découper un jeu de données de manière très efficace. Nous pouvons donc définir une fonction simple pour le faire : - -```python -def filter_books(example): - return ( - example["product_category"] == "book" - or example["product_category"] == "digital_ebook_purchase" - ) -``` - -Maintenant, lorsque nous appliquons cette fonction à `english_dataset` et `spanish_dataset`, le résultat ne contient que les lignes impliquant les catégories de livres. Avant d'appliquer le filtre, changeons le format de `english_dataset` de `"pandas"` à `"arrow"` : - -```python -english_dataset.reset_format() -``` - -Nous pouvons ensuite appliquer la fonction de filtrage et, à titre de vérification, inspecter un échantillon de critiques pour voir si elles portent bien sur des livres : - -```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.' -# Je suis déçu -'>> 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.' -# Je suppose que j'avais de plus grandes attentes pour ce livre d'après les critiques. Je pensais vraiment que j'allais au moins l'aimer. L'idée de l'intrigue était géniale. J'aimais Ash, mais ça n'allait nulle part. La plus grande partie du livre était consacrée à leur émission de radio et aux conversations avec les auditeurs. Je voulais que l'auteur creuse plus profondément pour que nous puissions vraiment connaître les personnages. Tout ce que nous savons de Grace, c'est qu'elle est séduisante, qu'elle est latino et qu'elle est une sorte de garce. Je suis déçue. - -'>> Title: Good art, good price, poor design' -# Un bon art, un bon prix, un mauvais 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' -# J'ai eu le calendrier DC Vintage ces deux dernières années, mais il était en rupture de stock pour toujours cette année et j'ai vu qu'ils avaient réduit les dimensions sans raison valable. Celui-ci a de bons choix artistiques mais le design a le pli qui traverse l'image, donc c'est moins esthétique, surtout si vous voulez garder une image à accrocher. Pour le prix, c'est un bon calendrier. - -'>> Title: Helpful' -# Utile -'>> Review: Nearly all the tips useful and. I consider myself an intermediate to advanced user of OneNote. I would highly recommend.' -# Presque tous les conseils sont utiles et. Je me considère comme un utilisateur intermédiaire à avancé de OneNote. Je le recommande vivement. -``` - -D'accord, nous pouvons voir que les critiques ne concernent pas strictement les livres et peuvent se référer à des choses comme des calendriers et des applications électroniques telles que OneNote. Néanmoins, le domaine semble approprié pour entraîner un modèle de résumé. Avant de regarder les différents modèles qui conviennent à cette tâche, nous avons une dernière préparation de données à faire : combiner les critiques anglaises et espagnoles en un seul objet `DatasetDict`. 🤗 *Datasets* fournit une fonction pratique `concatenate_datasets()` qui (comme son nom l'indique) va empiler deux objets `Dataset` l'un sur l'autre. Ainsi, pour créer notre jeu de données bilingue, nous allons boucler sur chaque division, concaténer les jeux de données pour cette division, et mélanger le résultat pour s'assurer que notre modèle ne s'adapte pas trop à une seule langue : - -```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) - -# Quelques exemples -show_samples(books_dataset) -``` - -```python out -'>> Title: Easy to follow!!!!' -# Facile à suivre!!!! -'>> 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.' -# J'ai adoré The dash diet weight loss Solution. Jamais faim. Je recommande ce régime. Les menus sont également bien arrondis. Essayez-le. Il contient beaucoup d'informations, merci. - -'>> Title: PARCIALMENTE DAÑADO' -# PARTIELLEMENT ENDOMMAGÉ -'>> 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).' -# Il est arrivé le jour prévu, avec d'autres livres que j'avais commandés, mais la boîte est arrivée en mauvais état, ce qui a endommagé les coins des livres car ils étaient livrés sans protection (doublure). - -'>> Title: no lo he podido descargar' -# Je n'ai pas pu le télécharger -'>> Review: igual que el anterior' -# même chose que ci-dessus -``` - -Cela ressemble certainement à un mélange de critiques anglaises et espagnoles ! Maintenant que nous avons un corpus d'entraînement, une dernière chose à vérifier est la distribution des mots dans les critiques et leurs titres. Ceci est particulièrement important pour les tâches de résumé, où les résumés de référence courts dans les données peuvent biaiser le modèle pour qu'il ne produise qu'un ou deux mots dans les résumés générés. Les graphiques ci-dessous montrent les distributions de mots, et nous pouvons voir que les titres sont fortement biaisés vers seulement 1 ou 2 mots : - -
-Word count distributions for the review titles and texts. - -
- -Pour y remédier, nous allons filtrer les exemples avec des titres très courts afin que notre modèle puisse produire des résumés plus intéressants. Puisque nous avons affaire à des textes anglais et espagnols, nous pouvons utiliser une heuristique grossière pour séparer les titres sur les espaces blancs, puis utiliser notre fidèle méthode `Dataset.filter()` comme suit : - -```python -books_dataset = books_dataset.filter(lambda x: len(x["review_title"].split()) > 2) -``` - -Maintenant que nous avons préparé notre corpus, voyons quelques *transformers* possibles que l'on pourrait *finetuné* dessus ! - -## Modèles pour le résumé de texte - -Si vous y pensez, le résumé de texte est une tâche similaire à la traduction automatique. Nous avons un corps de texte, comme une critique, que nous aimerions « traduire » en une version plus courte qui capture les caractéristiques saillantes de l'entrée. En conséquence, la plupart des *transformers* pour le résumé adoptent l'architecture encodeur-décodeur que nous avons rencontrée pour la première fois dans le [chapitre 1](/course/fr/chapter1), bien qu'il y ait quelques exceptions comme la famille de modèles GPT qui peut également être utilisée pour le résumé dans des contextes peu complexes. Le tableau suivant présente quelques modèles pré-entraînés populaires qui peuvent être *finetunés* pour le résumé. - -| *Transformers* | Description | Multilingue ? | -| :---------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------: | -| [GPT-2](https://huggingface.co/gpt2-xl) | Bien qu'il soit entraîné comme un modèle de langage autorégressif, vous pouvez faire en sorte que le GPT-2 génère des résumés en ajoutant `TL;DR` à la fin du texte d'entrée. | ❌ | -| [PEGASUS](https://huggingface.co/google/pegasus-large) | Utilise un objectif de pré-entraînement pour prédire les phrases masquées dans les textes à plusieurs phrases. Cet objectif de pré-entraînement est plus proche du résumé que de la modélisation du langage standard et obtient des scores élevés sur des *benchmarks* populaires. | ❌ | -| [T5](https://huggingface.co/t5-base) | Une architecture universelle de *transformer* qui formule toutes les tâches dans un cadre texte à texte. Par exemple, le format d'entrée du modèle pour résumer un document est `summarize: ARTICLE`. | ❌ | -| [mT5](https://huggingface.co/google/mt5-base) | Une version multilingue de T5, pré-entraînée sur le corpus multilingue Common Crawl (mC4), couvrant 101 langues. | ✅ | -| [BART](https://huggingface.co/facebook/bart-base) | Une architecture de *transformer* avec une pile d'encodeurs et de décodeurs entraînés pour reconstruire l'entrée corrompue qui combine les schémas de pré-entraînement de BERT et GPT-2. | ❌ | -| [mBART-50](https://huggingface.co/facebook/mbart-large-50) | Une version multilingue de BART, pré-entraînée sur 50 langues. | ✅ | - -Comme vous pouvez le voir dans ce tableau, la majorité des *transformers* pour le résumé (et en fait la plupart des tâches de NLP) sont monolingues. C'est une bonne chose si votre tâche se déroule dans une langue « à haute ressource » comme l'anglais ou l'allemand, mais moins pour les milliers d'autres langues utilisées dans le monde. Heureusement, il existe une catégorie de *transformers* multilingues, comme mT5 et mBART, qui viennent à la rescousse. Ces modèles sont pré-entraînés en utilisant la modélisation du langage mais avec une particularité : au lieu d'être entraîné sur un corpus d'une seule langue, ils sont entraînés conjointement sur des textes dans plus de 50 langues ! - -Nous allons nous concentrer sur mT5, une architecture intéressante basée sur T5 qui a été pré-entraînée dans un cadre texte à texte. Dans T5, chaque tâche de NLP est formulée en termes d'un préfixe de *prompt* comme `summarize:` qui conditionne le modèle à adapter le texte généré au *prompt*. Comme le montre la figure ci-dessous, cela rend le T5 extrêmement polyvalent car vous pouvez résoudre de nombreuses tâches avec un seul modèle ! - -
-Different tasks performed by the T5 architecture. - -
- -mT5 n'utilise pas de préfixes mais partage une grande partie de la polyvalence de T5 et a l'avantage d'être multilingue. Maintenant que nous avons choisi un modèle, voyons comment préparer nos données pour l'entraînement. - - - - -✏️ **Essayez !** Une fois que vous aurez terminé cette section, comparez le mT5 à mBART en *finetunant* ce dernier avec les mêmes techniques. Pour des points bonus, vous pouvez aussi essayer de *finetuner* le T5 uniquement sur les critiques anglaises. Puisque le T5 a un préfixe spécial, vous devrez ajouter `summarize:` aux entrées dans les étapes de prétraitement ci-dessous. - - - -## Prétraitement des données - - - -Notre prochaine tâche est de tokeniser et d'encoder nos critiques et leurs titres. Comme d'habitude, nous commençons par charger le *tokenizer* associé au *checkpoint* du modèle pré-entraîné. Nous utiliserons `mt5-small` comme *checkpoint* afin de pouvoir *finetuner* le modèle en un temps raisonnable : - -```python -from transformers import AutoTokenizer - -model_checkpoint = "google/mt5-small" -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) -``` - - - -💡 Aux premiers stades de vos projets de NLP, une bonne pratique consiste à entraîner une classe de « petits » modèles sur un petit échantillon de données. Cela vous permet de déboguer et d'itérer plus rapidement vers un flux de travail de bout en bout. Une fois que vous avez confiance dans les résultats, vous pouvez toujours faire évoluer le modèle en changeant simplement le *checkpoint* du modèle ! - - - -Testons le *tokenizer* de mT5 sur un petit exemple : - -```python -inputs = tokenizer( - "I loved reading the Hunger Games!" -) # J'ai adoré lire les 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]} -``` - -Ici nous pouvons voir les familiers `input_ids` et `attention_mask` que nous avons rencontrés dans nos premières expériences de *finetuning* au [chapitre 3](/course/fr/chapter3). Décodons ces identifiants d'entrée avec la fonction `convert_ids_to_tokens()` du *tokenizer* pour voir à quel type de *tokenizer* nous avons affaire : - -```python -tokenizer.convert_ids_to_tokens(inputs.input_ids) -``` - -```python out -['▁I', '▁', 'loved', '▁reading', '▁the', '▁Hung', 'er', '▁Games', ''] -``` - -Le caractère Unicode spécial `▁` et le *token* de fin de séquence `` indiquent que nous avons affaire au *tokenizer* de SentencePiece, qui est basé sur l'algorithme de segmentation Unigram discuté dans le [chapitre 6](/course/chapter6). Unigram est particulièrement utile pour les corpus multilingues car il permet à SentencePiece d'être agnostique vis-à-vis des accents, de la ponctuation et du fait que de nombreuses langues, comme le japonais, n'ont pas de caractères d'espacement. - -Pour tokeniser notre corpus, nous devons faire face à une subtilité associée au résumé : comme nos étiquettes sont également du texte, il est possible qu'elles dépassent la taille maximale du contexte du modèle. Cela signifie que nous devons appliquer une troncature à la fois aux critiques et à leurs titres pour nous assurer de ne pas transmettre des entrées trop longues à notre modèle. Les tokenizers de 🤗 *Transformers* fournissent une fonction très pratique `as_target_tokenizer()` qui vous permet de tokeniser les étiquettes en parallèle avec les entrées. Ceci est typiquement fait en utilisant un gestionnaire de contexte à l'intérieur d'une fonction de prétraitement qui encode d'abord les entrées, et ensuite encode les étiquettes comme une colonne séparée. Voici un exemple d'une telle fonction pour 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 - ) - # Configurer le tokenizer pour les cibles - with tokenizer.as_target_tokenizer(): - labels = tokenizer( - examples["review_title"], max_length=max_target_length, truncation=True - ) - - model_inputs["labels"] = labels["input_ids"] - return model_inputs -``` - -Parcourons ce code pour comprendre ce qui se passe. La première chose que nous avons faite est de définir des valeurs pour `max_input_length` et `max_target_length`, qui fixent les limites supérieures de la longueur des commentaires et des titres. Comme le corps de la critique est généralement beaucoup plus long que le titre, nous avons mis ces valeurs à l'échelle en conséquence. Ensuite, dans la `preprocess_function()` elle-même, nous pouvons voir que les commentaires sont d'abord tokenizés, suivis par les titres avec `as_target_tokenizer()`. - -Avec la fonction `preprocess_function()`, il est alors simple de tokeniser l'ensemble du corpus en utilisant la fonction pratique `Dataset.map()` que nous avons largement utilisée dans ce cours : - -```python -tokenized_datasets = books_dataset.map(preprocess_function, batched=True) -``` - -Maintenant que le corpus a été prétraité, examinons certaines métriques couramment utilisées pour le résumé. Comme nous allons le voir, il n'existe pas de solution miracle pour mesurer la qualité d'un texte généré par une machine. - - - -💡 Vous avez peut-être remarqué que nous avons utilisé `batched=True` dans notre fonction `Dataset.map()` ci-dessus. Cela permet de coder les exemples par lots de 1 000 (par défaut) et d'utiliser les capacités de *multithreading* des *tokenizers* rapides de 🤗 *Transformers*. Lorsque cela est possible, essayez d'utiliser `batched=True` pour tirer le meilleur parti de votre prétraitement ! - - - - -## Métriques pour le résumé de texte - - - -Par rapport à la plupart des autres tâches que nous avons abordées dans ce cours, la mesure des performances des tâches de génération de texte comme le résumé ou la traduction n'est pas aussi simple. Par exemple, pour une critique telle que « J'ai adoré lire les Hunger Games », il existe plusieurs résumés valides, comme « J'ai adoré Hunger Games » ou « Hunger Games est une excellente lecture ». Il est clair que l'application d'une sorte de correspondance exacte entre le résumé généré et l'étiquette n'est pas une bonne solution. En effet, même les humains auraient de mauvais résultats avec une telle mesure, car nous avons tous notre propre style d'écriture. - -Pour le résumé, l'une des métriques les plus couramment utilisées est le [score ROUGE](https://en.wikipedia.org/wiki/ROUGE_(metric)) (abréviation de *Recall-Oriented Understudy for Gisting Evaluation*). L'idée de base de cette métrique est de comparer un résumé généré avec un ensemble de résumés de référence qui sont généralement créés par des humains. Pour être plus précis, supposons que nous voulions comparer les deux résumés suivants : - -```python -generated_summary = "I absolutely loved reading the Hunger Games" -# "J'ai absolument adoré lire les Hunger Games" -reference_summary = "I loved reading the Hunger Games" -# "J'ai adoré lire les Hunger Games" -``` - -Une façon de les comparer pourrait être de compter le nombre de mots qui se chevauchent, qui dans ce cas serait de 6. Cependant, cette méthode est un peu grossière, c'est pourquoi ROUGE se base sur le calcul des scores de _précision_ et de _rappel_ pour le chevauchement. - - - -🙋 Ne vous inquiétez pas si c'est la première fois que vous entendez parler de précision et de rappel. Nous allons parcourir ensemble quelques exemples explicites pour que tout soit clair. Ces métriques sont généralement rencontrées dans les tâches de classification, donc si vous voulez comprendre comment la précision et le rappel sont définis dans ce contexte, nous vous recommandons de consulter les [guides de `scikit-learn`](https://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html). - - - -Pour ROUGE, le rappel mesure la proportion du résumé de référence qui est capturée par le résumé généré. Si nous ne faisons que comparer des mots, le rappel peut être calculé selon la formule suivante : - -$$ \mathrm{Recall} = \frac{\mathrm{Nombre\,de\,mots\,qui\,se\,chevauchent}}{\mathrm{Nombre\, total\, de\, mots\, dans\, le\, résumé\, de\, réference}} $$ - -Pour notre exemple simple ci-dessus, cette formule donne un rappel parfait de 6/6 = 1, c'est-à-dire que tous les mots du résumé de référence ont été produits par le modèle. Cela peut sembler génial, mais imaginez que le résumé généré ait été « J'ai vraiment aimé lire les Hunger Games toute la nuit ». Le rappel serait également parfait, mais le résumé serait sans doute moins bon puisqu'il serait verbeux. Pour traiter ces scénarios, nous calculons également la précision, qui dans le contexte de ROUGE, mesure la proportion du résumé généré qui est pertinente : - -$$ \mathrm{Precision} = \frac{\mathrm{Nombre\,de\,mots\,qui\,se\,chevauchent}}{\mathrm{Nombre\, total\, de\, mots\, dans\, le\, résumé\, généré}} $$ - -En appliquant cela à notre résumé verbeux, on obtient une précision de 6/10 = 0,6, ce qui est considérablement moins bon que la précision de 6/7 = 0,86 obtenue par notre résumé plus court. En pratique, la précision et le rappel sont généralement calculés, puis le score F1 (la moyenne harmonique de la précision et du rappel) est indiqué. Nous pouvons le faire facilement dans 🤗 *Datasets* en installant d'abord le *package* `rouge_score` : - -```py -!pip install rouge_score -``` - -et ensuite charger la métrique ROUGE comme suit : - -```python -import evaluate - -rouge_score = evaluate.load("rouge") -``` - -Ensuite, nous pouvons utiliser la fonction `rouge_score.compute()` pour calculer toutes les métriques en une seule fois : - -```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))} -``` - -Whoa, il y a pas mal d'informations dans cette sortie. Qu'est-ce que ça veut dire ? Tout d'abord, 🤗 *Datasets* calcule des intervalles de confiance pour la précision, le rappel et le score F1. Ce sont les attributs `low`, `mid`, et `high` que vous pouvez voir ici. De plus, 🤗 *Datasets* calcule une variété de scores ROUGE qui sont basés sur différents types de granularité du texte lors de la comparaison des résumés générés et de référence. La variante `rouge1` est le chevauchement des unigrammes. C'est juste une façon fantaisiste de dire le chevauchement des mots et c'est exactement la métrique dont nous avons discuté ci-dessus. Pour vérifier cela, nous allons extraire la valeur `mid` de nos scores : - -```python -scores["rouge1"].mid -``` - -```python out -Score(precision=0.86, recall=1.0, fmeasure=0.92) -``` - -Super, les chiffres de précision et de rappel correspondent ! Maintenant, qu'en est-il des autres scores ROUGE ? `rouge2` mesure le chevauchement entre les bigrammes (chevauchement des paires de mots), tandis que `rougeL` et `rougeLsum` mesurent les plus longues séquences de mots correspondants en recherchant les plus longues sous-souches communes dans les résumés générés et de référence. Le « sum » dans `rougeLsum` fait référence au fait que cette métrique est calculée sur un résumé entier, alors que `rougeL` est calculée comme une moyenne sur des phrases individuelles. - - - -✏️ **Essayez !** Créez votre propre exemple de résumé généré et de référence et voyez si les scores ROUGE obtenus correspondent à un calcul manuel basé sur les formules de précision et de rappel. Pour des points bonus, divisez le texte en bigrammes et comparez la précision et le rappel pour la métrique `rouge2`. - - - -Nous utiliserons ces scores ROUGE pour suivre les performances de notre modèle, mais avant cela, faisons ce que tout bon praticien de NLP devrait faire : créer une *baseline* solide, mais simple ! - -### Création d'une base de référence solide - -Une *baseline* commune pour le résumé de texte consiste à prendre simplement les trois premières phrases d'un article, souvent appelée la *baseline* _lead-3_. Nous pourrions utiliser les points pour tracker les limites des phrases mais cela échouera avec des acronymes comme « U.S. » ou « U.N. ». Nous allons donc utiliser la bibliothèque `nltk`, qui inclut un meilleur algorithme pour gérer ces cas. Vous pouvez installer le *package* en utilisant `pip` comme suit : - -```python -!pip install nltk -``` - -puis téléchargez les règles de ponctuation : - -```python -import nltk - -nltk.download("punkt") -``` - -Ensuite, nous importons le *tokenizer* de `nltk` et créons une fonction simple pour extraire les trois premières phrases d'une critique. La convention dans le résumé de texte est de séparer chaque résumé avec une nouvelle ligne, donc nous allons également inclure ceci et tester le tout sur un exemple d'entraînement : - -```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.' -# J'ai grandi en lisant Koontz, et il y a des années, j'ai arrêté, convaincu que je l'avais "dépassé" -'Still,when a friend was looking for something suspenseful too read, I suggested Koontz.' -# "Pourtant, quand une amie cherchait un livre à suspense, je lui ai suggéré Koontz." -'She found Strangers.' -# Elle a trouvé Strangers. -``` - -Cela semble fonctionner, alors implémentons maintenant une fonction qui extrait ces résumés d'un jeu de données et calcule les scores ROUGE pour la ligne de base : - -```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"]) -``` - -Nous pouvons ensuite utiliser cette fonction pour calculer les scores ROUGE sur l'ensemble de validation et les embellir un peu en utilisant 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} -``` - -Nous pouvons voir que le score de `rouge2` est significativement plus bas que le reste. Ceci reflète probablement le fait que les titres des critiques sont typiquement concis et donc que la *baseline* *lead-3* est trop verbeuse. Maintenant que nous disposons d'une bonne *baseline*, concentrons-nous sur le *finetuning* du mT5 ! - -{#if fw === 'pt'} - -## Finetuning de mT5 avec l'API `Trainer` - -Le *finetuning* d'un modèle pour le résumé est très similaire aux autres tâches que nous avons couvertes dans ce chapitre. La première chose à faire est de charger le modèle pré-entraîné à partir du *checkpoint* `mt5-small`. Puisque la compression est une tâche de séquence à séquence, nous pouvons charger le modèle avec la classe `AutoModelForSeq2SeqLM`, qui téléchargera automatiquement et mettra en cache les poids : - -```python -from transformers import AutoModelForSeq2SeqLM - -model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) -``` - -{:else} - -## Finetuning de mT5 avec Keras - -Le *finetuning* d'un modèle pour le résumé est très similaire aux autres tâches que nous avons couvertes dans ce chapitre. La première chose à faire est de charger le modèle pré-entraîné à partir du *checkpoint* `mt5-small`. Puisque la compression est une tâche de séquence à séquence, nous pouvons charger le modèle avec la classe `TFAutoModelForSeq2SeqLM`, qui téléchargera automatiquement et mettra en cache les poids : - -```python -from transformers import TFAutoModelForSeq2SeqLM - -model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) -``` - -{/if} - - - -💡 Si vous vous demandez pourquoi vous ne voyez aucun avertissement concernant le *finetuning* du modèle sur une tâche en aval, c'est parce que pour les tâches de séquence à séquence, nous conservons tous les poids du réseau. Comparez cela à notre modèle de classification de texte du [chapitre 3](/course/fr/chapter3) où la tête du modèle pré-entraîné a été remplacée par un réseau initialisé de manière aléatoire. - - - -La prochaine chose que nous devons faire est de nous connecter au *Hub*. Si vous exécutez ce code dans un *notebook*, vous pouvez le faire avec la fonction utilitaire suivante : - -```python -from huggingface_hub import notebook_login - -notebook_login() -``` - -qui affichera un *widget* où vous pourrez saisir vos informations d'identification. Vous pouvez également exécuter cette commande dans votre terminal et vous connecter à partir de là : - -``` -huggingface-cli login -``` - -{#if fw === 'pt'} - -Nous aurons besoin de générer des résumés afin de calculer les scores ROUGE pendant l'entraînement. Heureusement, 🤗 *Transformers* fournit des classes dédiées `Seq2SeqTrainingArguments` et `Seq2SeqTrainer` qui peuvent faire cela pour nous automatiquement ! Pour voir comment cela fonctionne, définissons d'abord les hyperparamètres et autres arguments pour nos expériences : - -```python -from transformers import Seq2SeqTrainingArguments - -batch_size = 8 -num_train_epochs = 8 -# La perte d'entraînement à chaque époque -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, -) -``` - -Ici, l'argument `predict_with_generate` a été défini pour indiquer que nous devons générer des résumés pendant l'évaluation afin de pouvoir calculer les scores ROUGE pour chaque époque. Comme discuté au [chapitre 1](/course/fr/chapter1), le décodeur effectue l'inférence en prédisant les *tokens* un par un, et ceci est implémenté par la méthode `generate()`. Définir `predict_with_generate=True` indique au `Seq2SeqTrainer` d'utiliser cette méthode pour l'évaluation. Nous avons également ajusté certains des hyperparamètres par défaut, comme le taux d'apprentissage, le nombre d'époques, et le taux de décroissance des poids, et nous avons réglé l'option `save_total_limit` pour ne sauvegarder que jusqu'à trois *checkpoints* pendant l'entraînement. C'est parce que même la plus petite version de mT5 utilise environ 1 Go d'espace disque, et nous pouvons gagner un peu de place en limitant le nombre de copies que nous sauvegardons. - -L'argument `push_to_hub=True` nous permettra de pousser le modèle vers le *Hub* après l'entraînement. Vous trouverez le dépôt sous votre profil utilisateur dans l'emplacement défini par `output_dir`. Notez que vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/mt5-finetuned-amazon-en-es"` à `Seq2SeqTrainingArguments`. - -La prochaine chose que nous devons faire est de fournir à `Seq2SeqTrainer` une fonction `compute_metrics()` afin que nous puissions évaluer notre modèle pendant l'entraînement. Pour le résumé, c'est un peu plus compliqué que de simplement appeler `rouge_score.compute()` sur les prédictions du modèle, puisque nous devons _décoder_ les sorties et les étiquettes en texte avant de pouvoir calculer les scores ROUGE. La fonction suivante fait exactement cela, et utilise également la fonction `sent_tokenize()` de `nltk` pour séparer les phrases du résumé avec des nouvelles lignes : - - -```python -import numpy as np - - -def compute_metrics(eval_pred): - predictions, labels = eval_pred - # Décoder les résumés générés en texte - decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) - # Remplacer -100 dans les étiquettes car nous ne pouvons pas les décoder - labels = np.where(labels != -100, labels, tokenizer.pad_token_id) - # Décoder les résumés de référence en texte - decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) - # ROUGE attend une nouvelle ligne après chaque phrase - 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] - # Calcul des scores ROUGE - result = rouge_score.compute( - predictions=decoded_preds, references=decoded_labels, use_stemmer=True - ) - # Extraire les scores médians - result = {key: value.mid.fmeasure * 100 for key, value in result.items()} - return {k: round(v, 4) for k, v in result.items()} -``` - -{/if} - -Ensuite, nous devons définir un collateur de données pour notre tâche de séquence à séquence. Comme mT5 est un *transformer* encodeur-décodeur, une des subtilités de la préparation de nos batchs est que, pendant le décodage, nous devons décaler les étiquettes d'une unité vers la droite. Ceci est nécessaire pour garantir que le décodeur ne voit que les étiquettes de vérité terrain précédentes et non les étiquettes actuelles ou futures, qui seraient faciles à mémoriser pour le modèle. Cela ressemble à la façon dont l'auto-attention masquée est appliquée aux entrées dans une tâche comme [la modélisation causale du langage](/course/fr/chapter7/6). - -Heureusement, 🤗 *Transformers* fournit un collateur `DataCollatorForSeq2Seq` qui rembourrera dynamiquement les entrées et les étiquettes pour nous. Pour instancier ce collateur, nous devons simplement fournir le *tokenizer* et le *modèle* : - -{#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} - -Voyons ce que produit ce collateur lorsqu'on lui donne un petit batch d'exemples. Tout d'abord, nous devons supprimer les colonnes contenant des chaînes de caractères, car le collateur ne saura pas comment remplir ces éléments : - -```python -tokenized_datasets = tokenized_datasets.remove_columns( - books_dataset["train"].column_names -) -``` - -Comme le collateur attend une liste de `dict`, où chaque `dict` représente un seul exemple du jeu de données, nous devons également mettre les données dans le format attendu avant de les transmettre au collateur de données : - -```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]])} -``` - -La principale chose à remarquer ici est que le premier exemple est plus long que le second, donc les `input_ids` et `attention_mask` du second exemple ont été complétés sur la droite avec un *token* `[PAD]` (dont l'identifiant est `0`). De même, nous pouvons voir que les `labels` ont été complétés par des `-100`, pour s'assurer que les *tokens* de remplissage sont ignorés par la fonction de perte. Et enfin, nous pouvons voir un nouveau `decoder_input_ids` qui a déplacé les étiquettes vers la droite en insérant un *token* `[PAD]` dans la première entrée. - -{#if fw === 'pt'} - -Nous avons enfin tous les ingrédients dont nous avons besoin pour l'entraînement ! Nous devons maintenant simplement instancier le `Seq2SeqTrainer` avec les arguments : - -```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, -) -``` - -et lancer notre course d'entraînement : - -```python -trainer.train() -``` - -Pendant l'entraînement, vous devriez voir la perte d'entraînement diminuer et les scores ROUGE augmenter à chaque époque. Une fois l'entraînement terminé, vous pouvez voir les scores ROUGE finaux en exécutant `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} -``` - -D'après les scores, nous pouvons voir que notre modèle a largement surpassé notre *baseline* *lead-3*. Bien ! La dernière chose à faire est de pousser les poids du modèle vers le *Hub*, comme suit : - -``` -trainer.push_to_hub(commit_message="Training complete", tags="summarization") -``` - -```python out -'https://huggingface.co/huggingface-course/mt5-finetuned-amazon-en-es/commit/aa0536b829b28e73e1e4b94b8a5aacec420d40e0' -``` - -Ceci sauvegardera le *checkpoint* et les fichiers de configuration dans `output_dir`, avant de télécharger tous les fichiers sur le *Hub*. En spécifiant l'argument `tags`, nous nous assurons également que le *widget* sur le *Hub* sera celui d'un pipeline de résumé au lieu de celui de la génération de texte par défaut associé à l'architecture mT5 (pour plus d'informations sur les balises de modèle, voir la [documentation du *Hub*](https://huggingface.co/docs/hub/main#how-is-a-models-type-of-inference-api-and-widget-determined)). La sortie de `trainer.push_to_hub()` est une URL vers le hash du commit Git, donc vous pouvez facilement voir les changements qui ont été faits au dépôt de modèle ! - -Pour conclure cette section, voyons comment nous pouvons également *finetuner* mT5 en utilisant les fonctionnalités de bas niveau fournies par 🤗 *Accelerate*. - -{:else} - -Nous sommes presque prêts à nous entraîner ! Nous devons juste convertir nos jeux de données en `tf.data.Dataset` en utilisant le collateur de données que nous avons défini ci-dessus, puis utiliser `compile()` et `fit()`. D'abord, les jeux de données : - -```python -tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=True, - batch_size=8, -) -tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=False, - batch_size=8, -) -``` - -Maintenant, nous définissons nos hyperparamètres d'entraînement et nous compilons : - -```python -from transformers import create_optimizer -import tensorflow as tf - -# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans le jeu de données, divisé par la taille du batch, -# puis multiplié par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un tf.data.Dataset, -# et non le jeu de données original donc son len() est déjà 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) - -# Entraîner en mixed-precision float16 -tf.keras.mixed_precision.set_global_policy("mixed_float16") -``` - -Et enfin, nous *finetunons* le modèle. Nous utilisons un `PushToHubCallback` pour sauvegarder le modèle sur le *Hub* après chaque époque, ce qui nous permettra de l'utiliser pour l'inférence plus tard : - -```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 -) -``` - -Nous avons obtenu quelques valeurs de perte pendant l'entraînement, mais nous aimerions vraiment voir les métriques ROUGE que nous avons calculées plus tôt. Pour obtenir ces métriques, nous devons générer les sorties du modèle et les convertir en chaînes de caractères. Construisons quelques listes d'étiquettes et de prédictions pour comparer la métrique ROUGE (notez que si vous obtenez des erreurs d'importation pour cette section, vous pouvez avoir besoin de "pip install tqdm") : - -```python -from tqdm import tqdm -import numpy as np - -all_preds = [] -all_labels = [] -for batch in tqdm(tf_eval_dataset): - predictions = model.generate(**batch) - decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) - labels = batch["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) -``` - -Une fois que nous avons nos listes d'étiquettes et de chaînes de prédiction, le calcul du score ROUGE est facile : - -```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'} - -## Finetuning de mT5 avec 🤗 Accelerate - -Le *finetuning* de notre modèle avec 🤗 *Accelerate* est très similaire à l'exemple de classification de texte que nous avons rencontré dans le [chapitre 3](/course/fr/chapter3). Les principales différences seront la nécessité de générer explicitement nos résumés pendant l'entraînement et de définir comment nous calculons les scores ROUGE (rappelons que le `Seq2SeqTrainer` s'est occupé de la génération pour nous). Voyons comment nous pouvons mettre en œuvre ces deux exigences dans 🤗 *Accelerate* ! - -### Préparer tout pour l'entraînement - -La première chose que nous devons faire est de créer un `DataLoader` pour chacun de nos échantillons. Puisque les chargeurs de données PyTorch attendent des batchs de tenseurs, nous devons définir le format à `"torch"` dans nos jeux de données : - -```python -tokenized_datasets.set_format("torch") -``` - -Maintenant que nous avons des jeux de données constitués uniquement de tenseurs, la prochaine chose à faire est d'instancier à nouveau le `DataCollatorForSeq2Seq`. Pour cela, nous devons fournir une nouvelle version du modèle, donc chargeons-le à nouveau depuis notre cache : - -```python -model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) -``` - -Nous pouvons ensuite instancier le collateur de données et l'utiliser pour définir nos chargeurs de données : - -```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 -) -``` - -La prochaine chose à faire est de définir l'optimiseur que nous voulons utiliser. Comme dans nos autres exemples, nous allons utiliser `AdamW`, qui fonctionne bien pour la plupart des problèmes : - -```python -from torch.optim import AdamW - -optimizer = AdamW(model.parameters(), lr=2e-5) -``` - -Enfin, nous introduisons notre modèle, notre optimiseur et nos chargeurs de données dans la méthode `accelerator.prepare()` : - -```python -from accelerate import Accelerator - -accelerator = Accelerator() -model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( - model, optimizer, train_dataloader, eval_dataloader -) -``` - - - -🚨 Si vous vous entraînez sur un TPU, vous devrez déplacer tout le code ci-dessus dans une fonction d'entraînement dédiée. Voir le [chapitre 3](/course/fr/chapter3) pour plus de détails. - - - -Maintenant que nous avons préparé nos objets, il reste trois choses à faire : - -* définir le programmeur du taux d'apprentissage, -* implémenter une fonction pour post-traiter les résumés pour l'évaluation, -* créer un dépôt sur le *Hub* vers lequel nous pouvons pousser notre modèle. - -Pour le programmeur de taux d'apprentissage, nous utiliserons le programmeur linéaire standard des sections précédentes : - -```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, -) -``` - -Pour le post-traitement, nous avons besoin d'une fonction qui divise les résumés générés en phrases séparées par des nouvelles lignes. C'est le format attendu par la métrique ROUGE et nous pouvons y parvenir avec le bout de code suivant : - -```python -def postprocess_text(preds, labels): - preds = [pred.strip() for pred in preds] - labels = [label.strip() for label in labels] - - # ROUGE attend une nouvelle ligne après chaque phrase - 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 -``` - -Cela devrait vous sembler familier si vous vous rappelez comment nous avons défini la fonction `compute_metrics()` du `Seq2SeqTrainer`. - -Enfin, nous devons créer un dépôt de modèles sur le *Hub*. Pour cela, nous pouvons utiliser la bibliothèque 🤗 *Hub*, qui porte le nom approprié. Nous avons juste besoin de définir un nom pour notre dépôt, et la bibliothèque a une fonction utilitaire pour combiner l'identifiant du dépôt avec le profil de l'utilisateur : - -```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' -``` - -Nous pouvons maintenant utiliser ce nom de dépôt pour cloner une version locale dans notre répertoire de résultats qui stockera les artefacts d'entraînement : - -```python -from huggingface_hub import Repository - -output_dir = "results-mt5-finetuned-squad-accelerate" -repo = Repository(output_dir, clone_from=repo_name) -``` - -Cela nous permettra de pousser les artefacts vers le *Hub* en appelant la méthode `repo.push_to_hub()` pendant l'entraînement ! Concluons maintenant notre analyse en écrivant la boucle d'entraînement. - -### Boucle d'entraînement - -La boucle d'entraînement pour le résumé est assez similaire aux autres exemples 🤗 *Accelerate* que nous avons rencontrés et est grossièrement divisée en quatre étapes principales : - -1. entraîner le modèle en itérant sur tous les exemples dans `train_dataloader` pour chaque époque, -2. générer les résumés du modèle à la fin de chaque époque, en générant d'abord les *tokens* puis en les décodant (ainsi que les résumés de référence) en texte, -3. calculer les scores ROUGE en utilisant les mêmes techniques que nous avons vues précédemment, -4. sauvegarder les *checkpoints* et pousser le tout vers le *Hub*. Ici, nous nous appuyons sur l'argument `blocking=False` de l'objet `Repository` afin de pouvoir pousser les *checkpoints* par époque de manière _asynchrone_. Cela nous permet de poursuivre l'entraînement sans avoir à attendre le téléchargement quelque peu lent associé à un modèle de la taille d'1 Go ! - -Ces étapes peuvent être vues dans le bloc de code suivant : - -```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): - # Entraînement - model.train() - for step, batch in enumerate(train_dataloader): - outputs = model(**batch) - loss = outputs.loss - accelerator.backward(loss) - - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - progress_bar.update(1) - - # Evaluation - model.eval() - for step, batch in enumerate(eval_dataloader): - with torch.no_grad(): - generated_tokens = accelerator.unwrap_model(model).generate( - batch["input_ids"], - attention_mask=batch["attention_mask"], - ) - - generated_tokens = accelerator.pad_across_processes( - generated_tokens, dim=1, pad_index=tokenizer.pad_token_id - ) - labels = batch["labels"] - - # Si nous n'avons pas rempli la longueur maximale, nous devons également remplir les étiquettes - 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() - - # Remplacer -100 dans les étiquettes car nous ne pouvons pas les décoder - 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) - - # Calculer les métriques - result = rouge_score.compute() - # Extract the median ROUGE scores - result = {key: value.mid.fmeasure * 100 for key, value in result.items()} - result = {k: round(v, 4) for k, v in result.items()} - print(f"Epoch {epoch}:", result) - - # Sauvegarder et télécharger - 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} -``` - -Et c'est tout ! Une fois que vous l'aurez exécuté, vous aurez un modèle et des résultats assez similaires à ceux que nous avons obtenus avec le `Trainer`. - -{/if} - -## Utilisation de votre modèle finetuné - -Une fois que vous avez poussé le modèle vers le *Hub*, vous pouvez jouer avec lui soit via le *widget* d'inférence, soit avec un objet `pipeline`, comme suit : - -```python -from transformers import pipeline - -hub_model_id = "huggingface-course/mt5-small-finetuned-amazon-en-es" -summarizer = pipeline("summarization", model=hub_model_id) -``` - -Nous pouvons alimenter notre pipeline avec quelques exemples de l'ensemble de test (que le modèle n'a pas vu) pour avoir une idée de la qualité des résumés. Tout d'abord, implémentons une fonction simple pour afficher ensemble la critique, le titre et le résumé généré : - -```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}'") -``` - -Examinons l'un des exemples anglais que nous recevons : - -```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.' -# Ce produit n'a rien de spécial... le livre est trop petit et rigide et il est difficile d'y écrire. L'énorme autocollant au dos ne se détache pas et a l'air super collant. Je n'achèterai plus jamais ce produit. J'aurais pu simplement acheter un journal dans un magasin à un dollar et ce serait à peu près la même chose. Il est également très cher pour ce qu'il est. - -'>>> Title: Not impressed at all... buy something else' -# Pas du tout impressionné... achetez autre chose. - -'>>> Summary: Nothing special at all about this product' -# Rien de spécial à propos de ce produit -``` - -Ce n'est pas si mal ! Nous pouvons voir que notre modèle a été capable d'effectuer un résumé _abstractif_ en augmentant certaines parties de la critique avec de nouveaux mots. Et peut-être que l'aspect le plus cool de notre modèle est qu'il est bilingue, donc nous pouvons également générer des résumés de critiques en espagnol : - -```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' -# C'est une trilogie qui se lit très facilement. J'ai aimé, je ne m'attendais pas du tout à la fin. - -'>>> Title: Buena literatura para adolescentes' -# Bonne littérature pour les adolescents - -'>>> Summary: Muy facil de leer' -# Très facile à lire -``` - -Le résumé a été extrait directement de la critique. Néanmoins, cela montre la polyvalence du modèle mT5 et vous a donné un aperçu de ce que c'est que de traiter un corpus multilingue ! - -Ensuite, nous allons nous intéresser à une tâche un peu plus complexe : entraîner un modèle de langue à partir de zéro. + + +# Résumé de textes + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + +Dans cette section, nous allons voir comment les *transformers* peuvent être utilisés pour condenser de longs documents en résumés, une tâche connue sous le nom de _résumé de texte_. Il s'agit de l'une des tâches de NLP les plus difficiles car elle requiert une série de capacités, telles que la compréhension de longs passages et la génération d'un texte cohérent qui capture les sujets principaux d'un document. Cependant, lorsqu'il est bien fait, le résumé de texte est un outil puissant qui peut accélérer divers processus commerciaux en soulageant les experts du domaine de la lecture détaillée de longs documents. + + + +Bien qu'il existe déjà plusieurs modèles *finetunés* pour le résumé sur le [*Hub*](https://huggingface.co/models?pipeline_tag=summarization&sort=downloads), la plupart d'entre eux ne sont adaptés qu'aux documents en anglais. Ainsi, pour ajouter une touche d'originalité à cette section, nous allons entraîner un modèle bilingue pour l'anglais et l'espagnol. À la fin de cette section, vous disposerez d'un [modèle](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es) capable de résumer les commentaires des clients comme celui présenté ici : + + + + +Comme nous allons le voir, ces résumés sont concis car ils sont appris à partir des titres que les clients fournissent dans leurs commentaires sur les produits. Commençons par constituer un corpus bilingue approprié pour cette tâche. + +## Préparation d'un corpus multilingue + +Nous allons utiliser le [*Multilingual Amazon Reviews Corpus*](https://huggingface.co/datasets/amazon_reviews_multi) pour créer notre résumeur bilingue. Ce corpus est constitué de critiques de produits Amazon en six langues et est généralement utilisé pour évaluer les classifieurs multilingues. Cependant, comme chaque critique est accompagnée d'un titre court, nous pouvons utiliser les titres comme résumés cibles pour l'apprentissage de notre modèle ! Pour commencer, téléchargeons les sous-ensembles anglais et espagnols depuis le *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 + }) +}) +``` + +Comme vous pouvez le voir, pour chaque langue, il y a 200 000 critiques pour la partie entraînement et 5 000 critiques pour chacune des parties validation et test. Les informations qui nous intéressent sont contenues dans les colonnes `review_body` et `review_title`. Voyons quelques exemples en créant une fonction simple qui prend un échantillon aléatoire de l'ensemble d'entraînement avec les techniques apprises au [chapitre 5](/course/fr/chapter5) : + +```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' +# Travaillé en position avant, pas arrière +'>> 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.' +# 3 étoiles car ce ne sont pas des freins arrière comme indiqué dans la description de l'article. Au moins, l'adaptateur de montage ne fonctionnait que sur la fourche avant du vélo pour lequel je l'ai acheté. + +'>> 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' +# Il fait son travail et il est magnifique mais le mien est en train de tomber en morceaux, j'ai dû le recoller avec de la colle chaude. + +'>> Title: Can\'t beat these for the money' +# On ne peut pas faire mieux pour le prix +'>> 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.' +# Je l'ai acheté pour manipuler diverses pièces d'avion et des "trucs" de hangar que je devais organiser ; il a vraiment fait l'affaire. L'unité est arrivée rapidement, était bien emballée et est arrivée intacte (toujours un bon signe). Il y a cinq supports muraux - trois sur le dessus et deux sur le dessous. Je voulais le monter sur le mur, alors tout ce que j'ai eu à faire était d'enlever les deux couches supérieures de tiroirs en plastique, ainsi que les tiroirs d'angle inférieurs, de le placer où je voulais et de le marquer ; j'ai ensuite utilisé quelques-uns des nouveaux ancrages muraux à vis en plastique (la variété de 50 livres) et il s'est facilement monté sur le mur. Certains ont fait remarquer qu'ils voulaient des séparateurs pour les tiroirs, et qu'ils les ont fabriqués. Bonne idée. Pour ma part, j'avais besoin de quelque chose dont je pouvais voir le contenu à hauteur des yeux, et je voulais donc des tiroirs plus grands. J'aime aussi le fait qu'il s'agisse du nouveau plastique qui ne se fragilise pas et ne se fend pas comme mes anciens tiroirs en plastique. J'aime la construction entièrement en plastique. Elle est suffisamment résistante pour contenir des pièces métalliques, mais étant en plastique, elle n'est pas aussi lourde qu'un cadre métallique, ce qui permet de la fixer facilement au mur et de la charger d'objets lourds ou légers. Aucun problème. Pour le prix, c'est imbattable. C'est le meilleur que j'ai acheté à ce jour, et j'utilise des versions de ce type depuis plus de quarante ans. +``` + + + +✏️ **Essayez !** Changez la graine aléatoire dans la commande `Dataset.shuffle()` pour explorer d'autres critiques dans le corpus. Si vous parlez espagnol, jetez un coup d'œil à certaines des critiques dans `spanish_dataset` pour voir si les titres semblent aussi être des résumés raisonnables. + + + +Cet échantillon montre la diversité des critiques que l'on trouve généralement en ligne, allant du positif au négatif (et tout ce qui se trouve entre les deux !). Bien que l'exemple avec le titre « meh » ne soit pas très informatif, les autres titres semblent être des résumés décents des critiques. Entraîner un modèle de résumé sur l'ensemble des 400 000 avis prendrait beaucoup trop de temps sur un seul GPU, nous allons donc nous concentrer sur la génération de résumés pour un seul domaine de produits. Pour avoir une idée des domaines parmi lesquels nous pouvons choisir, convertissons `english_dataset` en `pandas.DataFrame` et calculons le nombre d'avis par catégorie de produits : + +```python +english_dataset.set_format("pandas") +english_df = english_dataset["train"][:] +# Afficher le compte des 20 premiers produits +english_df["product_category"].value_counts()[:20] +``` + +```python out +home 17679 # maison +apparel 15951 # vêtements +wireless 15717 # sans fil +other 13418 # autres +beauty 12091 # beauté +drugstore 11730 # pharmacie +kitchen 10382 # cuisine +toy 8745 # jouets +sports 8277 # sports +automotive 7506 # automobile +lawn_and_garden 7327 # pelouse_et_jardin +home_improvement 7136 # amélioration_de_la_maison +pet_products 7082 # produits_pour_animaux_de_compagnie +digital_ebook_purchase 6749 # achat_de_livres_numériques +pc 6401 # ordinateur_personnel +electronics 6186 # électronique +office_product 5521 # produits_de_bureau +shoes 5197 # chaussures +grocery 4730 # épicerie +book 3756 # livre +Name: product_category, dtype: int64 +``` + +Les produits les plus populaires du jeu de données anglais concernent les articles ménagers, les vêtements et l'électronique sans fil. Pour rester dans le thème d'Amazon, nous allons nous concentrer sur le résumé des critiques de livres. Après tout, c'est la raison d'être de l'entreprise ! Nous pouvons voir deux catégories de produits qui correspondent à nos besoins (`book` et `digital_ebook_purchase`). Nous allons donc filtrer les jeux de données dans les deux langues pour ces produits uniquement. Comme nous l'avons vu dans le [chapitre 5](/course/fr/chapter5), la fonction `Dataset.filter()` nous permet de découper un jeu de données de manière très efficace. Nous pouvons donc définir une fonction simple pour le faire : + +```python +def filter_books(example): + return ( + example["product_category"] == "book" + or example["product_category"] == "digital_ebook_purchase" + ) +``` + +Maintenant, lorsque nous appliquons cette fonction à `english_dataset` et `spanish_dataset`, le résultat ne contient que les lignes impliquant les catégories de livres. Avant d'appliquer le filtre, changeons le format de `english_dataset` de `"pandas"` à `"arrow"` : + +```python +english_dataset.reset_format() +``` + +Nous pouvons ensuite appliquer la fonction de filtrage et, à titre de vérification, inspecter un échantillon de critiques pour voir si elles portent bien sur des livres : + +```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.' +# Je suis déçu +'>> 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.' +# Je suppose que j'avais de plus grandes attentes pour ce livre d'après les critiques. Je pensais vraiment que j'allais au moins l'aimer. L'idée de l'intrigue était géniale. J'aimais Ash, mais ça n'allait nulle part. La plus grande partie du livre était consacrée à leur émission de radio et aux conversations avec les auditeurs. Je voulais que l'auteur creuse plus profondément pour que nous puissions vraiment connaître les personnages. Tout ce que nous savons de Grace, c'est qu'elle est séduisante, qu'elle est latino et qu'elle est une sorte de garce. Je suis déçue. + +'>> Title: Good art, good price, poor design' +# Un bon art, un bon prix, un mauvais 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' +# J'ai eu le calendrier DC Vintage ces deux dernières années, mais il était en rupture de stock pour toujours cette année et j'ai vu qu'ils avaient réduit les dimensions sans raison valable. Celui-ci a de bons choix artistiques mais le design a le pli qui traverse l'image, donc c'est moins esthétique, surtout si vous voulez garder une image à accrocher. Pour le prix, c'est un bon calendrier. + +'>> Title: Helpful' +# Utile +'>> Review: Nearly all the tips useful and. I consider myself an intermediate to advanced user of OneNote. I would highly recommend.' +# Presque tous les conseils sont utiles et. Je me considère comme un utilisateur intermédiaire à avancé de OneNote. Je le recommande vivement. +``` + +D'accord, nous pouvons voir que les critiques ne concernent pas strictement les livres et peuvent se référer à des choses comme des calendriers et des applications électroniques telles que OneNote. Néanmoins, le domaine semble approprié pour entraîner un modèle de résumé. Avant de regarder les différents modèles qui conviennent à cette tâche, nous avons une dernière préparation de données à faire : combiner les critiques anglaises et espagnoles en un seul objet `DatasetDict`. 🤗 *Datasets* fournit une fonction pratique `concatenate_datasets()` qui (comme son nom l'indique) va empiler deux objets `Dataset` l'un sur l'autre. Ainsi, pour créer notre jeu de données bilingue, nous allons boucler sur chaque division, concaténer les jeux de données pour cette division, et mélanger le résultat pour s'assurer que notre modèle ne s'adapte pas trop à une seule langue : + +```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) + +# Quelques exemples +show_samples(books_dataset) +``` + +```python out +'>> Title: Easy to follow!!!!' +# Facile à suivre!!!! +'>> 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.' +# J'ai adoré The dash diet weight loss Solution. Jamais faim. Je recommande ce régime. Les menus sont également bien arrondis. Essayez-le. Il contient beaucoup d'informations, merci. + +'>> Title: PARCIALMENTE DAÑADO' +# PARTIELLEMENT ENDOMMAGÉ +'>> 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).' +# Il est arrivé le jour prévu, avec d'autres livres que j'avais commandés, mais la boîte est arrivée en mauvais état, ce qui a endommagé les coins des livres car ils étaient livrés sans protection (doublure). + +'>> Title: no lo he podido descargar' +# Je n'ai pas pu le télécharger +'>> Review: igual que el anterior' +# même chose que ci-dessus +``` + +Cela ressemble certainement à un mélange de critiques anglaises et espagnoles ! Maintenant que nous avons un corpus d'entraînement, une dernière chose à vérifier est la distribution des mots dans les critiques et leurs titres. Ceci est particulièrement important pour les tâches de résumé, où les résumés de référence courts dans les données peuvent biaiser le modèle pour qu'il ne produise qu'un ou deux mots dans les résumés générés. Les graphiques ci-dessous montrent les distributions de mots, et nous pouvons voir que les titres sont fortement biaisés vers seulement 1 ou 2 mots : + +
+Word count distributions for the review titles and texts. + +
+ +Pour y remédier, nous allons filtrer les exemples avec des titres très courts afin que notre modèle puisse produire des résumés plus intéressants. Puisque nous avons affaire à des textes anglais et espagnols, nous pouvons utiliser une heuristique grossière pour séparer les titres sur les espaces blancs, puis utiliser notre fidèle méthode `Dataset.filter()` comme suit : + +```python +books_dataset = books_dataset.filter(lambda x: len(x["review_title"].split()) > 2) +``` + +Maintenant que nous avons préparé notre corpus, voyons quelques *transformers* possibles que l'on pourrait *finetuné* dessus ! + +## Modèles pour le résumé de texte + +Si vous y pensez, le résumé de texte est une tâche similaire à la traduction automatique. Nous avons un corps de texte, comme une critique, que nous aimerions « traduire » en une version plus courte qui capture les caractéristiques saillantes de l'entrée. En conséquence, la plupart des *transformers* pour le résumé adoptent l'architecture encodeur-décodeur que nous avons rencontrée pour la première fois dans le [chapitre 1](/course/fr/chapter1), bien qu'il y ait quelques exceptions comme la famille de modèles GPT qui peut également être utilisée pour le résumé dans des contextes peu complexes. Le tableau suivant présente quelques modèles pré-entraînés populaires qui peuvent être *finetunés* pour le résumé. + +| *Transformers* | Description | Multilingue ? | +| :---------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------: | +| [GPT-2](https://huggingface.co/gpt2-xl) | Bien qu'il soit entraîné comme un modèle de langage autorégressif, vous pouvez faire en sorte que le GPT-2 génère des résumés en ajoutant `TL;DR` à la fin du texte d'entrée. | ❌ | +| [PEGASUS](https://huggingface.co/google/pegasus-large) | Utilise un objectif de pré-entraînement pour prédire les phrases masquées dans les textes à plusieurs phrases. Cet objectif de pré-entraînement est plus proche du résumé que de la modélisation du langage standard et obtient des scores élevés sur des *benchmarks* populaires. | ❌ | +| [T5](https://huggingface.co/t5-base) | Une architecture universelle de *transformer* qui formule toutes les tâches dans un cadre texte à texte. Par exemple, le format d'entrée du modèle pour résumer un document est `summarize: ARTICLE`. | ❌ | +| [mT5](https://huggingface.co/google/mt5-base) | Une version multilingue de T5, pré-entraînée sur le corpus multilingue Common Crawl (mC4), couvrant 101 langues. | ✅ | +| [BART](https://huggingface.co/facebook/bart-base) | Une architecture de *transformer* avec une pile d'encodeurs et de décodeurs entraînés pour reconstruire l'entrée corrompue qui combine les schémas de pré-entraînement de BERT et GPT-2. | ❌ | +| [mBART-50](https://huggingface.co/facebook/mbart-large-50) | Une version multilingue de BART, pré-entraînée sur 50 langues. | ✅ | + +Comme vous pouvez le voir dans ce tableau, la majorité des *transformers* pour le résumé (et en fait la plupart des tâches de NLP) sont monolingues. C'est une bonne chose si votre tâche se déroule dans une langue « à haute ressource » comme l'anglais ou l'allemand, mais moins pour les milliers d'autres langues utilisées dans le monde. Heureusement, il existe une catégorie de *transformers* multilingues, comme mT5 et mBART, qui viennent à la rescousse. Ces modèles sont pré-entraînés en utilisant la modélisation du langage mais avec une particularité : au lieu d'être entraîné sur un corpus d'une seule langue, ils sont entraînés conjointement sur des textes dans plus de 50 langues ! + +Nous allons nous concentrer sur mT5, une architecture intéressante basée sur T5 qui a été pré-entraînée dans un cadre texte à texte. Dans T5, chaque tâche de NLP est formulée en termes d'un préfixe de *prompt* comme `summarize:` qui conditionne le modèle à adapter le texte généré au *prompt*. Comme le montre la figure ci-dessous, cela rend le T5 extrêmement polyvalent car vous pouvez résoudre de nombreuses tâches avec un seul modèle ! + +
+Different tasks performed by the T5 architecture. + +
+ +mT5 n'utilise pas de préfixes mais partage une grande partie de la polyvalence de T5 et a l'avantage d'être multilingue. Maintenant que nous avons choisi un modèle, voyons comment préparer nos données pour l'entraînement. + + + + +✏️ **Essayez !** Une fois que vous aurez terminé cette section, comparez le mT5 à mBART en *finetunant* ce dernier avec les mêmes techniques. Pour des points bonus, vous pouvez aussi essayer de *finetuner* le T5 uniquement sur les critiques anglaises. Puisque le T5 a un préfixe spécial, vous devrez ajouter `summarize:` aux entrées dans les étapes de prétraitement ci-dessous. + + + +## Prétraitement des données + + + +Notre prochaine tâche est de tokeniser et d'encoder nos critiques et leurs titres. Comme d'habitude, nous commençons par charger le *tokenizer* associé au *checkpoint* du modèle pré-entraîné. Nous utiliserons `mt5-small` comme *checkpoint* afin de pouvoir *finetuner* le modèle en un temps raisonnable : + +```python +from transformers import AutoTokenizer + +model_checkpoint = "google/mt5-small" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + + + +💡 Aux premiers stades de vos projets de NLP, une bonne pratique consiste à entraîner une classe de « petits » modèles sur un petit échantillon de données. Cela vous permet de déboguer et d'itérer plus rapidement vers un flux de travail de bout en bout. Une fois que vous avez confiance dans les résultats, vous pouvez toujours faire évoluer le modèle en changeant simplement le *checkpoint* du modèle ! + + + +Testons le *tokenizer* de mT5 sur un petit exemple : + +```python +inputs = tokenizer( + "I loved reading the Hunger Games!" +) # J'ai adoré lire les 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]} +``` + +Ici nous pouvons voir les familiers `input_ids` et `attention_mask` que nous avons rencontrés dans nos premières expériences de *finetuning* au [chapitre 3](/course/fr/chapter3). Décodons ces identifiants d'entrée avec la fonction `convert_ids_to_tokens()` du *tokenizer* pour voir à quel type de *tokenizer* nous avons affaire : + +```python +tokenizer.convert_ids_to_tokens(inputs.input_ids) +``` + +```python out +['▁I', '▁', 'loved', '▁reading', '▁the', '▁Hung', 'er', '▁Games', ''] +``` + +Le caractère Unicode spécial `▁` et le *token* de fin de séquence `` indiquent que nous avons affaire au *tokenizer* de SentencePiece, qui est basé sur l'algorithme de segmentation Unigram discuté dans le [chapitre 6](/course/chapter6). Unigram est particulièrement utile pour les corpus multilingues car il permet à SentencePiece d'être agnostique vis-à-vis des accents, de la ponctuation et du fait que de nombreuses langues, comme le japonais, n'ont pas de caractères d'espacement. + +Pour tokeniser notre corpus, nous devons faire face à une subtilité associée au résumé : comme nos étiquettes sont également du texte, il est possible qu'elles dépassent la taille maximale du contexte du modèle. Cela signifie que nous devons appliquer une troncature à la fois aux critiques et à leurs titres pour nous assurer de ne pas transmettre des entrées trop longues à notre modèle. Les tokenizers de 🤗 *Transformers* fournissent une fonction très pratique `as_target_tokenizer()` qui vous permet de tokeniser les étiquettes en parallèle avec les entrées. Ceci est typiquement fait en utilisant un gestionnaire de contexte à l'intérieur d'une fonction de prétraitement qui encode d'abord les entrées, et ensuite encode les étiquettes comme une colonne séparée. Voici un exemple d'une telle fonction pour 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 + ) + # Configurer le tokenizer pour les cibles + with tokenizer.as_target_tokenizer(): + labels = tokenizer( + examples["review_title"], max_length=max_target_length, truncation=True + ) + + model_inputs["labels"] = labels["input_ids"] + return model_inputs +``` + +Parcourons ce code pour comprendre ce qui se passe. La première chose que nous avons faite est de définir des valeurs pour `max_input_length` et `max_target_length`, qui fixent les limites supérieures de la longueur des commentaires et des titres. Comme le corps de la critique est généralement beaucoup plus long que le titre, nous avons mis ces valeurs à l'échelle en conséquence. Ensuite, dans la `preprocess_function()` elle-même, nous pouvons voir que les commentaires sont d'abord tokenizés, suivis par les titres avec `as_target_tokenizer()`. + +Avec la fonction `preprocess_function()`, il est alors simple de tokeniser l'ensemble du corpus en utilisant la fonction pratique `Dataset.map()` que nous avons largement utilisée dans ce cours : + +```python +tokenized_datasets = books_dataset.map(preprocess_function, batched=True) +``` + +Maintenant que le corpus a été prétraité, examinons certaines métriques couramment utilisées pour le résumé. Comme nous allons le voir, il n'existe pas de solution miracle pour mesurer la qualité d'un texte généré par une machine. + + + +💡 Vous avez peut-être remarqué que nous avons utilisé `batched=True` dans notre fonction `Dataset.map()` ci-dessus. Cela permet de coder les exemples par lots de 1 000 (par défaut) et d'utiliser les capacités de *multithreading* des *tokenizers* rapides de 🤗 *Transformers*. Lorsque cela est possible, essayez d'utiliser `batched=True` pour tirer le meilleur parti de votre prétraitement ! + + + + +## Métriques pour le résumé de texte + + + +Par rapport à la plupart des autres tâches que nous avons abordées dans ce cours, la mesure des performances des tâches de génération de texte comme le résumé ou la traduction n'est pas aussi simple. Par exemple, pour une critique telle que « J'ai adoré lire les Hunger Games », il existe plusieurs résumés valides, comme « J'ai adoré Hunger Games » ou « Hunger Games est une excellente lecture ». Il est clair que l'application d'une sorte de correspondance exacte entre le résumé généré et l'étiquette n'est pas une bonne solution. En effet, même les humains auraient de mauvais résultats avec une telle mesure, car nous avons tous notre propre style d'écriture. + +Pour le résumé, l'une des métriques les plus couramment utilisées est le [score ROUGE](https://en.wikipedia.org/wiki/ROUGE_(metric)) (abréviation de *Recall-Oriented Understudy for Gisting Evaluation*). L'idée de base de cette métrique est de comparer un résumé généré avec un ensemble de résumés de référence qui sont généralement créés par des humains. Pour être plus précis, supposons que nous voulions comparer les deux résumés suivants : + +```python +generated_summary = "I absolutely loved reading the Hunger Games" +# "J'ai absolument adoré lire les Hunger Games" +reference_summary = "I loved reading the Hunger Games" +# "J'ai adoré lire les Hunger Games" +``` + +Une façon de les comparer pourrait être de compter le nombre de mots qui se chevauchent, qui dans ce cas serait de 6. Cependant, cette méthode est un peu grossière, c'est pourquoi ROUGE se base sur le calcul des scores de _précision_ et de _rappel_ pour le chevauchement. + + + +🙋 Ne vous inquiétez pas si c'est la première fois que vous entendez parler de précision et de rappel. Nous allons parcourir ensemble quelques exemples explicites pour que tout soit clair. Ces métriques sont généralement rencontrées dans les tâches de classification, donc si vous voulez comprendre comment la précision et le rappel sont définis dans ce contexte, nous vous recommandons de consulter les [guides de `scikit-learn`](https://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html). + + + +Pour ROUGE, le rappel mesure la proportion du résumé de référence qui est capturée par le résumé généré. Si nous ne faisons que comparer des mots, le rappel peut être calculé selon la formule suivante : + +$$ \mathrm{Recall} = \frac{\mathrm{Nombre\,de\,mots\,qui\,se\,chevauchent}}{\mathrm{Nombre\, total\, de\, mots\, dans\, le\, résumé\, de\, réference}} $$ + +Pour notre exemple simple ci-dessus, cette formule donne un rappel parfait de 6/6 = 1, c'est-à-dire que tous les mots du résumé de référence ont été produits par le modèle. Cela peut sembler génial, mais imaginez que le résumé généré ait été « J'ai vraiment aimé lire les Hunger Games toute la nuit ». Le rappel serait également parfait, mais le résumé serait sans doute moins bon puisqu'il serait verbeux. Pour traiter ces scénarios, nous calculons également la précision, qui dans le contexte de ROUGE, mesure la proportion du résumé généré qui est pertinente : + +$$ \mathrm{Precision} = \frac{\mathrm{Nombre\,de\,mots\,qui\,se\,chevauchent}}{\mathrm{Nombre\, total\, de\, mots\, dans\, le\, résumé\, généré}} $$ + +En appliquant cela à notre résumé verbeux, on obtient une précision de 6/10 = 0,6, ce qui est considérablement moins bon que la précision de 6/7 = 0,86 obtenue par notre résumé plus court. En pratique, la précision et le rappel sont généralement calculés, puis le score F1 (la moyenne harmonique de la précision et du rappel) est indiqué. Nous pouvons le faire facilement dans 🤗 *Datasets* en installant d'abord le *package* `rouge_score` : + +```py +!pip install rouge_score +``` + +et ensuite charger la métrique ROUGE comme suit : + +```python +import evaluate + +rouge_score = evaluate.load("rouge") +``` + +Ensuite, nous pouvons utiliser la fonction `rouge_score.compute()` pour calculer toutes les métriques en une seule fois : + +```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))} +``` + +Whoa, il y a pas mal d'informations dans cette sortie. Qu'est-ce que ça veut dire ? Tout d'abord, 🤗 *Datasets* calcule des intervalles de confiance pour la précision, le rappel et le score F1. Ce sont les attributs `low`, `mid`, et `high` que vous pouvez voir ici. De plus, 🤗 *Datasets* calcule une variété de scores ROUGE qui sont basés sur différents types de granularité du texte lors de la comparaison des résumés générés et de référence. La variante `rouge1` est le chevauchement des unigrammes. C'est juste une façon fantaisiste de dire le chevauchement des mots et c'est exactement la métrique dont nous avons discuté ci-dessus. Pour vérifier cela, nous allons extraire la valeur `mid` de nos scores : + +```python +scores["rouge1"].mid +``` + +```python out +Score(precision=0.86, recall=1.0, fmeasure=0.92) +``` + +Super, les chiffres de précision et de rappel correspondent ! Maintenant, qu'en est-il des autres scores ROUGE ? `rouge2` mesure le chevauchement entre les bigrammes (chevauchement des paires de mots), tandis que `rougeL` et `rougeLsum` mesurent les plus longues séquences de mots correspondants en recherchant les plus longues sous-souches communes dans les résumés générés et de référence. Le « sum » dans `rougeLsum` fait référence au fait que cette métrique est calculée sur un résumé entier, alors que `rougeL` est calculée comme une moyenne sur des phrases individuelles. + + + +✏️ **Essayez !** Créez votre propre exemple de résumé généré et de référence et voyez si les scores ROUGE obtenus correspondent à un calcul manuel basé sur les formules de précision et de rappel. Pour des points bonus, divisez le texte en bigrammes et comparez la précision et le rappel pour la métrique `rouge2`. + + + +Nous utiliserons ces scores ROUGE pour suivre les performances de notre modèle, mais avant cela, faisons ce que tout bon praticien de NLP devrait faire : créer une *baseline* solide, mais simple ! + +### Création d'une base de référence solide + +Une *baseline* commune pour le résumé de texte consiste à prendre simplement les trois premières phrases d'un article, souvent appelée la *baseline* _lead-3_. Nous pourrions utiliser les points pour tracker les limites des phrases mais cela échouera avec des acronymes comme « U.S. » ou « U.N. ». Nous allons donc utiliser la bibliothèque `nltk`, qui inclut un meilleur algorithme pour gérer ces cas. Vous pouvez installer le *package* en utilisant `pip` comme suit : + +```python +!pip install nltk +``` + +puis téléchargez les règles de ponctuation : + +```python +import nltk + +nltk.download("punkt") +``` + +Ensuite, nous importons le *tokenizer* de `nltk` et créons une fonction simple pour extraire les trois premières phrases d'une critique. La convention dans le résumé de texte est de séparer chaque résumé avec une nouvelle ligne, donc nous allons également inclure ceci et tester le tout sur un exemple d'entraînement : + +```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.' +# J'ai grandi en lisant Koontz, et il y a des années, j'ai arrêté, convaincu que je l'avais "dépassé" +'Still,when a friend was looking for something suspenseful too read, I suggested Koontz.' +# "Pourtant, quand une amie cherchait un livre à suspense, je lui ai suggéré Koontz." +'She found Strangers.' +# Elle a trouvé Strangers. +``` + +Cela semble fonctionner, alors implémentons maintenant une fonction qui extrait ces résumés d'un jeu de données et calcule les scores ROUGE pour la ligne de base : + +```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"]) +``` + +Nous pouvons ensuite utiliser cette fonction pour calculer les scores ROUGE sur l'ensemble de validation et les embellir un peu en utilisant 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} +``` + +Nous pouvons voir que le score de `rouge2` est significativement plus bas que le reste. Ceci reflète probablement le fait que les titres des critiques sont typiquement concis et donc que la *baseline* *lead-3* est trop verbeuse. Maintenant que nous disposons d'une bonne *baseline*, concentrons-nous sur le *finetuning* du mT5 ! + +{#if fw === 'pt'} + +## Finetuning de mT5 avec l'API `Trainer` + +Le *finetuning* d'un modèle pour le résumé est très similaire aux autres tâches que nous avons couvertes dans ce chapitre. La première chose à faire est de charger le modèle pré-entraîné à partir du *checkpoint* `mt5-small`. Puisque la compression est une tâche de séquence à séquence, nous pouvons charger le modèle avec la classe `AutoModelForSeq2SeqLM`, qui téléchargera automatiquement et mettra en cache les poids : + +```python +from transformers import AutoModelForSeq2SeqLM + +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{:else} + +## Finetuning de mT5 avec Keras + +Le *finetuning* d'un modèle pour le résumé est très similaire aux autres tâches que nous avons couvertes dans ce chapitre. La première chose à faire est de charger le modèle pré-entraîné à partir du *checkpoint* `mt5-small`. Puisque la compression est une tâche de séquence à séquence, nous pouvons charger le modèle avec la classe `TFAutoModelForSeq2SeqLM`, qui téléchargera automatiquement et mettra en cache les poids : + +```python +from transformers import TFAutoModelForSeq2SeqLM + +model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{/if} + + + +💡 Si vous vous demandez pourquoi vous ne voyez aucun avertissement concernant le *finetuning* du modèle sur une tâche en aval, c'est parce que pour les tâches de séquence à séquence, nous conservons tous les poids du réseau. Comparez cela à notre modèle de classification de texte du [chapitre 3](/course/fr/chapter3) où la tête du modèle pré-entraîné a été remplacée par un réseau initialisé de manière aléatoire. + + + +La prochaine chose que nous devons faire est de nous connecter au *Hub*. Si vous exécutez ce code dans un *notebook*, vous pouvez le faire avec la fonction utilitaire suivante : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +qui affichera un *widget* où vous pourrez saisir vos informations d'identification. Vous pouvez également exécuter cette commande dans votre terminal et vous connecter à partir de là : + +``` +huggingface-cli login +``` + +{#if fw === 'pt'} + +Nous aurons besoin de générer des résumés afin de calculer les scores ROUGE pendant l'entraînement. Heureusement, 🤗 *Transformers* fournit des classes dédiées `Seq2SeqTrainingArguments` et `Seq2SeqTrainer` qui peuvent faire cela pour nous automatiquement ! Pour voir comment cela fonctionne, définissons d'abord les hyperparamètres et autres arguments pour nos expériences : + +```python +from transformers import Seq2SeqTrainingArguments + +batch_size = 8 +num_train_epochs = 8 +# La perte d'entraînement à chaque époque +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, +) +``` + +Ici, l'argument `predict_with_generate` a été défini pour indiquer que nous devons générer des résumés pendant l'évaluation afin de pouvoir calculer les scores ROUGE pour chaque époque. Comme discuté au [chapitre 1](/course/fr/chapter1), le décodeur effectue l'inférence en prédisant les *tokens* un par un, et ceci est implémenté par la méthode `generate()`. Définir `predict_with_generate=True` indique au `Seq2SeqTrainer` d'utiliser cette méthode pour l'évaluation. Nous avons également ajusté certains des hyperparamètres par défaut, comme le taux d'apprentissage, le nombre d'époques, et le taux de décroissance des poids, et nous avons réglé l'option `save_total_limit` pour ne sauvegarder que jusqu'à trois *checkpoints* pendant l'entraînement. C'est parce que même la plus petite version de mT5 utilise environ 1 Go d'espace disque, et nous pouvons gagner un peu de place en limitant le nombre de copies que nous sauvegardons. + +L'argument `push_to_hub=True` nous permettra de pousser le modèle vers le *Hub* après l'entraînement. Vous trouverez le dépôt sous votre profil utilisateur dans l'emplacement défini par `output_dir`. Notez que vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/mt5-finetuned-amazon-en-es"` à `Seq2SeqTrainingArguments`. + +La prochaine chose que nous devons faire est de fournir à `Seq2SeqTrainer` une fonction `compute_metrics()` afin que nous puissions évaluer notre modèle pendant l'entraînement. Pour le résumé, c'est un peu plus compliqué que de simplement appeler `rouge_score.compute()` sur les prédictions du modèle, puisque nous devons _décoder_ les sorties et les étiquettes en texte avant de pouvoir calculer les scores ROUGE. La fonction suivante fait exactement cela, et utilise également la fonction `sent_tokenize()` de `nltk` pour séparer les phrases du résumé avec des nouvelles lignes : + + +```python +import numpy as np + + +def compute_metrics(eval_pred): + predictions, labels = eval_pred + # Décoder les résumés générés en texte + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + # Remplacer -100 dans les étiquettes car nous ne pouvons pas les décoder + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + # Décoder les résumés de référence en texte + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + # ROUGE attend une nouvelle ligne après chaque phrase + 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] + # Calcul des scores ROUGE + result = rouge_score.compute( + predictions=decoded_preds, references=decoded_labels, use_stemmer=True + ) + # Extraire les scores médians + result = {key: value.mid.fmeasure * 100 for key, value in result.items()} + return {k: round(v, 4) for k, v in result.items()} +``` + +{/if} + +Ensuite, nous devons définir un collateur de données pour notre tâche de séquence à séquence. Comme mT5 est un *transformer* encodeur-décodeur, une des subtilités de la préparation de nos batchs est que, pendant le décodage, nous devons décaler les étiquettes d'une unité vers la droite. Ceci est nécessaire pour garantir que le décodeur ne voit que les étiquettes de vérité terrain précédentes et non les étiquettes actuelles ou futures, qui seraient faciles à mémoriser pour le modèle. Cela ressemble à la façon dont l'auto-attention masquée est appliquée aux entrées dans une tâche comme [la modélisation causale du langage](/course/fr/chapter7/6). + +Heureusement, 🤗 *Transformers* fournit un collateur `DataCollatorForSeq2Seq` qui rembourrera dynamiquement les entrées et les étiquettes pour nous. Pour instancier ce collateur, nous devons simplement fournir le *tokenizer* et le *modèle* : + +{#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} + +Voyons ce que produit ce collateur lorsqu'on lui donne un petit batch d'exemples. Tout d'abord, nous devons supprimer les colonnes contenant des chaînes de caractères, car le collateur ne saura pas comment remplir ces éléments : + +```python +tokenized_datasets = tokenized_datasets.remove_columns( + books_dataset["train"].column_names +) +``` + +Comme le collateur attend une liste de `dict`, où chaque `dict` représente un seul exemple du jeu de données, nous devons également mettre les données dans le format attendu avant de les transmettre au collateur de données : + +```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]])} +``` + +La principale chose à remarquer ici est que le premier exemple est plus long que le second, donc les `input_ids` et `attention_mask` du second exemple ont été complétés sur la droite avec un *token* `[PAD]` (dont l'identifiant est `0`). De même, nous pouvons voir que les `labels` ont été complétés par des `-100`, pour s'assurer que les *tokens* de remplissage sont ignorés par la fonction de perte. Et enfin, nous pouvons voir un nouveau `decoder_input_ids` qui a déplacé les étiquettes vers la droite en insérant un *token* `[PAD]` dans la première entrée. + +{#if fw === 'pt'} + +Nous avons enfin tous les ingrédients dont nous avons besoin pour l'entraînement ! Nous devons maintenant simplement instancier le `Seq2SeqTrainer` avec les arguments : + +```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, +) +``` + +et lancer notre course d'entraînement : + +```python +trainer.train() +``` + +Pendant l'entraînement, vous devriez voir la perte d'entraînement diminuer et les scores ROUGE augmenter à chaque époque. Une fois l'entraînement terminé, vous pouvez voir les scores ROUGE finaux en exécutant `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} +``` + +D'après les scores, nous pouvons voir que notre modèle a largement surpassé notre *baseline* *lead-3*. Bien ! La dernière chose à faire est de pousser les poids du modèle vers le *Hub*, comme suit : + +``` +trainer.push_to_hub(commit_message="Training complete", tags="summarization") +``` + +```python out +'https://huggingface.co/huggingface-course/mt5-finetuned-amazon-en-es/commit/aa0536b829b28e73e1e4b94b8a5aacec420d40e0' +``` + +Ceci sauvegardera le *checkpoint* et les fichiers de configuration dans `output_dir`, avant de télécharger tous les fichiers sur le *Hub*. En spécifiant l'argument `tags`, nous nous assurons également que le *widget* sur le *Hub* sera celui d'un pipeline de résumé au lieu de celui de la génération de texte par défaut associé à l'architecture mT5 (pour plus d'informations sur les balises de modèle, voir la [documentation du *Hub*](https://huggingface.co/docs/hub/main#how-is-a-models-type-of-inference-api-and-widget-determined)). La sortie de `trainer.push_to_hub()` est une URL vers le hash du commit Git, donc vous pouvez facilement voir les changements qui ont été faits au dépôt de modèle ! + +Pour conclure cette section, voyons comment nous pouvons également *finetuner* mT5 en utilisant les fonctionnalités de bas niveau fournies par 🤗 *Accelerate*. + +{:else} + +Nous sommes presque prêts à nous entraîner ! Nous devons juste convertir nos jeux de données en `tf.data.Dataset` en utilisant le collateur de données que nous avons défini ci-dessus, puis utiliser `compile()` et `fit()`. D'abord, les jeux de données : + +```python +tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=8, +) +tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=8, +) +``` + +Maintenant, nous définissons nos hyperparamètres d'entraînement et nous compilons : + +```python +from transformers import create_optimizer +import tensorflow as tf + +# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans le jeu de données, divisé par la taille du batch, +# puis multiplié par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un tf.data.Dataset, +# et non le jeu de données original donc son len() est déjà 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) + +# Entraîner en mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +Et enfin, nous *finetunons* le modèle. Nous utilisons un `PushToHubCallback` pour sauvegarder le modèle sur le *Hub* après chaque époque, ce qui nous permettra de l'utiliser pour l'inférence plus tard : + +```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 +) +``` + +Nous avons obtenu quelques valeurs de perte pendant l'entraînement, mais nous aimerions vraiment voir les métriques ROUGE que nous avons calculées plus tôt. Pour obtenir ces métriques, nous devons générer les sorties du modèle et les convertir en chaînes de caractères. Construisons quelques listes d'étiquettes et de prédictions pour comparer la métrique ROUGE (notez que si vous obtenez des erreurs d'importation pour cette section, vous pouvez avoir besoin de "pip install tqdm") : + +```python +from tqdm import tqdm +import numpy as np + +all_preds = [] +all_labels = [] +for batch in tqdm(tf_eval_dataset): + predictions = model.generate(**batch) + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + labels = batch["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) +``` + +Une fois que nous avons nos listes d'étiquettes et de chaînes de prédiction, le calcul du score ROUGE est facile : + +```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'} + +## Finetuning de mT5 avec 🤗 Accelerate + +Le *finetuning* de notre modèle avec 🤗 *Accelerate* est très similaire à l'exemple de classification de texte que nous avons rencontré dans le [chapitre 3](/course/fr/chapter3). Les principales différences seront la nécessité de générer explicitement nos résumés pendant l'entraînement et de définir comment nous calculons les scores ROUGE (rappelons que le `Seq2SeqTrainer` s'est occupé de la génération pour nous). Voyons comment nous pouvons mettre en œuvre ces deux exigences dans 🤗 *Accelerate* ! + +### Préparer tout pour l'entraînement + +La première chose que nous devons faire est de créer un `DataLoader` pour chacun de nos échantillons. Puisque les chargeurs de données PyTorch attendent des batchs de tenseurs, nous devons définir le format à `"torch"` dans nos jeux de données : + +```python +tokenized_datasets.set_format("torch") +``` + +Maintenant que nous avons des jeux de données constitués uniquement de tenseurs, la prochaine chose à faire est d'instancier à nouveau le `DataCollatorForSeq2Seq`. Pour cela, nous devons fournir une nouvelle version du modèle, donc chargeons-le à nouveau depuis notre cache : + +```python +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +Nous pouvons ensuite instancier le collateur de données et l'utiliser pour définir nos chargeurs de données : + +```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 +) +``` + +La prochaine chose à faire est de définir l'optimiseur que nous voulons utiliser. Comme dans nos autres exemples, nous allons utiliser `AdamW`, qui fonctionne bien pour la plupart des problèmes : + +```python +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Enfin, nous introduisons notre modèle, notre optimiseur et nos chargeurs de données dans la méthode `accelerator.prepare()` : + +```python +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + + + +🚨 Si vous vous entraînez sur un TPU, vous devrez déplacer tout le code ci-dessus dans une fonction d'entraînement dédiée. Voir le [chapitre 3](/course/fr/chapter3) pour plus de détails. + + + +Maintenant que nous avons préparé nos objets, il reste trois choses à faire : + +* définir le programmeur du taux d'apprentissage, +* implémenter une fonction pour post-traiter les résumés pour l'évaluation, +* créer un dépôt sur le *Hub* vers lequel nous pouvons pousser notre modèle. + +Pour le programmeur de taux d'apprentissage, nous utiliserons le programmeur linéaire standard des sections précédentes : + +```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, +) +``` + +Pour le post-traitement, nous avons besoin d'une fonction qui divise les résumés générés en phrases séparées par des nouvelles lignes. C'est le format attendu par la métrique ROUGE et nous pouvons y parvenir avec le bout de code suivant : + +```python +def postprocess_text(preds, labels): + preds = [pred.strip() for pred in preds] + labels = [label.strip() for label in labels] + + # ROUGE attend une nouvelle ligne après chaque phrase + 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 +``` + +Cela devrait vous sembler familier si vous vous rappelez comment nous avons défini la fonction `compute_metrics()` du `Seq2SeqTrainer`. + +Enfin, nous devons créer un dépôt de modèles sur le *Hub*. Pour cela, nous pouvons utiliser la bibliothèque 🤗 *Hub*, qui porte le nom approprié. Nous avons juste besoin de définir un nom pour notre dépôt, et la bibliothèque a une fonction utilitaire pour combiner l'identifiant du dépôt avec le profil de l'utilisateur : + +```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' +``` + +Nous pouvons maintenant utiliser ce nom de dépôt pour cloner une version locale dans notre répertoire de résultats qui stockera les artefacts d'entraînement : + +```python +from huggingface_hub import Repository + +output_dir = "results-mt5-finetuned-squad-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +Cela nous permettra de pousser les artefacts vers le *Hub* en appelant la méthode `repo.push_to_hub()` pendant l'entraînement ! Concluons maintenant notre analyse en écrivant la boucle d'entraînement. + +### Boucle d'entraînement + +La boucle d'entraînement pour le résumé est assez similaire aux autres exemples 🤗 *Accelerate* que nous avons rencontrés et est grossièrement divisée en quatre étapes principales : + +1. entraîner le modèle en itérant sur tous les exemples dans `train_dataloader` pour chaque époque, +2. générer les résumés du modèle à la fin de chaque époque, en générant d'abord les *tokens* puis en les décodant (ainsi que les résumés de référence) en texte, +3. calculer les scores ROUGE en utilisant les mêmes techniques que nous avons vues précédemment, +4. sauvegarder les *checkpoints* et pousser le tout vers le *Hub*. Ici, nous nous appuyons sur l'argument `blocking=False` de l'objet `Repository` afin de pouvoir pousser les *checkpoints* par époque de manière _asynchrone_. Cela nous permet de poursuivre l'entraînement sans avoir à attendre le téléchargement quelque peu lent associé à un modèle de la taille d'1 Go ! + +Ces étapes peuvent être vues dans le bloc de code suivant : + +```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): + # Entraînement + model.train() + for step, batch in enumerate(train_dataloader): + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) + + # Evaluation + model.eval() + for step, batch in enumerate(eval_dataloader): + with torch.no_grad(): + generated_tokens = accelerator.unwrap_model(model).generate( + batch["input_ids"], + attention_mask=batch["attention_mask"], + ) + + generated_tokens = accelerator.pad_across_processes( + generated_tokens, dim=1, pad_index=tokenizer.pad_token_id + ) + labels = batch["labels"] + + # Si nous n'avons pas rempli la longueur maximale, nous devons également remplir les étiquettes + 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() + + # Remplacer -100 dans les étiquettes car nous ne pouvons pas les décoder + 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) + + # Calculer les métriques + result = rouge_score.compute() + # Extract the median ROUGE scores + result = {key: value.mid.fmeasure * 100 for key, value in result.items()} + result = {k: round(v, 4) for k, v in result.items()} + print(f"Epoch {epoch}:", result) + + # Sauvegarder et télécharger + 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} +``` + +Et c'est tout ! Une fois que vous l'aurez exécuté, vous aurez un modèle et des résultats assez similaires à ceux que nous avons obtenus avec le `Trainer`. + +{/if} + +## Utilisation de votre modèle finetuné + +Une fois que vous avez poussé le modèle vers le *Hub*, vous pouvez jouer avec lui soit via le *widget* d'inférence, soit avec un objet `pipeline`, comme suit : + +```python +from transformers import pipeline + +hub_model_id = "huggingface-course/mt5-small-finetuned-amazon-en-es" +summarizer = pipeline("summarization", model=hub_model_id) +``` + +Nous pouvons alimenter notre pipeline avec quelques exemples de l'ensemble de test (que le modèle n'a pas vu) pour avoir une idée de la qualité des résumés. Tout d'abord, implémentons une fonction simple pour afficher ensemble la critique, le titre et le résumé généré : + +```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}'") +``` + +Examinons l'un des exemples anglais que nous recevons : + +```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.' +# Ce produit n'a rien de spécial... le livre est trop petit et rigide et il est difficile d'y écrire. L'énorme autocollant au dos ne se détache pas et a l'air super collant. Je n'achèterai plus jamais ce produit. J'aurais pu simplement acheter un journal dans un magasin à un dollar et ce serait à peu près la même chose. Il est également très cher pour ce qu'il est. + +'>>> Title: Not impressed at all... buy something else' +# Pas du tout impressionné... achetez autre chose. + +'>>> Summary: Nothing special at all about this product' +# Rien de spécial à propos de ce produit +``` + +Ce n'est pas si mal ! Nous pouvons voir que notre modèle a été capable d'effectuer un résumé _abstractif_ en augmentant certaines parties de la critique avec de nouveaux mots. Et peut-être que l'aspect le plus cool de notre modèle est qu'il est bilingue, donc nous pouvons également générer des résumés de critiques en espagnol : + +```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' +# C'est une trilogie qui se lit très facilement. J'ai aimé, je ne m'attendais pas du tout à la fin. + +'>>> Title: Buena literatura para adolescentes' +# Bonne littérature pour les adolescents + +'>>> Summary: Muy facil de leer' +# Très facile à lire +``` + +Le résumé a été extrait directement de la critique. Néanmoins, cela montre la polyvalence du modèle mT5 et vous a donné un aperçu de ce que c'est que de traiter un corpus multilingue ! + +Ensuite, nous allons nous intéresser à une tâche un peu plus complexe : entraîner un modèle de langue à partir de zéro. diff --git a/chapters/fr/chapter7/6.mdx b/chapters/fr/chapter7/6.mdx index a6c81f76d..0a3d395b4 100644 --- a/chapters/fr/chapter7/6.mdx +++ b/chapters/fr/chapter7/6.mdx @@ -1,905 +1,905 @@ - - -# Entraîner un modèle de langage causal à partir de zéro - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -Jusqu'à présent, nous avons surtout réutilisé des modèles pré-entraînés et les avons *finetunés* sur de nouveaux cas d'usage. Comme nous l'avons vu dans le [chapitre 1](/course/fr/chapter1), ceci est communément appelé _apprentissage par transfert_, et il s'agit d'une stratégie très efficace pour appliquer les *transformers* à la plupart des applications du monde réel où les données étiquetées sont rares. Dans ce chapitre, nous allons adopter une approche différente consistant à entraîner un modèle complètement nouveau à partir de zéro. C'est une bonne démarche à adopter si vous avez beaucoup de données et qu'elles sont très différentes des données de pré-entraînement utilisées par les modèles disponibles. Cependant, le pré-entraînement d'un modèle de langue nécessite beaucoup plus de ressources informatiques que le simple *finetuning* d'un modèle existant. Parmi les exemples où il peut être utile d'entraîner un nouveau modèle, citons les jeux de données constitués de notes de musique, de séquences moléculaires telles que l'ADN, ou de langages de programmation. Ces derniers ont récemment gagné en popularité grâce à des outils tels que TabNine et Copilot de GitHub (alimentés par le modèle Codex d'OpenAI) qui peuvent générer de longues séquences de code. Cette tâche de génération de texte est mieux abordée avec des modèles de langage autorégressifs ou causaux tels que le GPT-2. - -Dans cette section, nous allons construire une version réduite d'un modèle de génération de code Python. Nous nous concentrerons sur la complétion d'une ligne de code au lieu de fonctions ou de classes complètes. Lorsque vous travaillez sur des projets de science des données en Python, vous êtes souvent en contact avec les bibliothèques `matplotlib`, `seaborn`, `pandas` et `scikit-learn`. Lors de l'utilisation de ces *frameworks*, il est fréquent d'avoir besoin de rechercher des commandes spécifiques. Il serait donc bien d'utiliser un modèle pour compléter ces appels pour nous. - - - - -Dans le [chapitre 6](/course/fr/chapter6), nous avons créé un *tokenizer* efficace pour traiter du code Python. Nous avons besoin d'un jeu de données à grande échelle pour pré-entraîner un modèle. Ici, nous allons appliquer notre *tokenizer* à un corpus de code Python provenant des dépôts GitHub. Nous utiliserons ensuite l'API `Trainer` et 🤗 *Accelerate* pour entraîner le modèle. C'est parti ! - - - - -Il s'agit d'une présentation du modèle qui a été entraîné à l'aide du code présenté dans cette section et qui a ensuité été téléchargé sur le *Hub*. Vous pouvez le trouver [ici](https://huggingface.co/huggingface-course/codeparrot-ds?text=plt.imshow%28). Notez qu'étant donné qu'il y a un certains aléat dans la génération du texte, vous obtiendrez probablement un résultat légèrement différent. - -## Collecte des données - -On peut trouver du code Python en abondance dans les dépôts de code tels que GitHub, que nous pouvons utiliser pour créer un jeu de données en récupérant chaque dépôt Python. C'est l'approche adoptée dans le [livre *Natural Language Processing with Transformers*](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/) pour pré-entraîner un grand GPT-2. En utilisant un dépôt GitHub d'environ 180 Go contenant approximativement 20 millions de fichiers Python, les auteurs du livre ont construit un jeu de données appelé `codeparrot` qu'ils ont ensuite partagé sur le [*Hub*](https://huggingface.co/datasets/transformersbook/codeparrot). - -Cependant, entraîner sur l'ensemble du corpus prend beaucoup de temps et demande beaucoup de ressources de calculs. Dans notre cas, nous n'avons besoin que du sous-ensemble du jeu de données qui est relatif aux codes portant sur la science des données. Commençons donc par filtrer le jeu de données `codeparrot` en ne gardant que les fichiers incluant l'une des bibliothèques de science des données énumérées précédemment. En raison de la taille du jeu de données, nous voulons éviter de le télécharger. Nous utiliserons donc la fonctionnalité de *streaming* de 🤗 *Datasets* afin de le filtrer à la volée. Pour nous aider à filtrer les échantillons de code utilisant les bibliothèques que nous avons mentionnées précédemment, nous utilisons la fonction suivante : - -```py -def any_keyword_in_string(string, keywords): - for keyword in keywords: - if keyword in string: - return True - return False -``` - -Testons-le sur deux exemples : - -```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 -``` - -Nous pouvons l'utiliser pour créer une fonction qui va *streamer* le jeu de donner et filtrer les éléments que nous voulons : - -```py -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) -``` - -Ensuite, nous pouvons simplement appliquer cette fonction : - -```py -# Cette cellule prendra beaucoup de temps à s'exécuter, donc vous devriez la sauter et aller à la suivante ! -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. -``` - -Cela nous laisse avec environ 3 % du jeu de données original, ce qui est tout de même assez important puisqu'il fait 6 Go et se compose de 600 000 scripts Python ! - -Le filtrage peut prendre de 2 à 3 heures, selon votre machine et votre bande passante. Si vous ne voulez pas passer par ce long processus, nous fournissons sur le *Hub* le jeu de données filtré pour que vous puissiez le télécharger : - -```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="train") - -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 - }) -}) -``` - - - -Le pré-entraînement du modèle de langue prendra un certain temps. Nous vous suggérons donc d'exécuter d'abord la boucle d'entraînement sur un petit échantillon des données en décommentant les deux lignes dans le code ci-dessus. Assurez-vous alors que l'entraînement se termine avec succès et que les modèles sont stockés. Rien n'est plus frustrant qu'un entraînement qui échoue à la dernière étape car vous avez oublié de créer un dossier ou parce qu'il y a une faute de frappe à la fin de la boucle d'entraînement ! - - - -Examinons un exemple tiré du jeu de données. Nous ne montrerons que les 200 premiers caractères de chaque champ : - -```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''' -``` - -Nous pouvons voir que le champ `content` contient le code sur lequel nous voulons que notre modèle s'entraîne. Maintenant que nous avons un jeu de données, nous devons préparer les textes afin qu'ils soient dans un format approprié pour le pré-entraînement. - -## Préparation du jeu de données - - - -La première étape est de tokeniser les données afin de pouvoir les utiliser pour l'entraînement. Puisque notre objectif est d'autocompléter de courts appels de fonctions, nous pouvons garder la taille du contexte relativement petite. L'avantage est que nous pouvons entraîner le modèle beaucoup plus rapidement et qu'il nécessite beaucoup moins de mémoire. Si c'est important pour votre application d'avoir davantage de contexte (par exemple, si vous voulez que le modèle écrive des tests unitaires basés sur un fichier avec la définition de la fonction), assurez-vous d'augmenter ce nombre. Gardez néanmoins à l'esprit que cela s'accompagne d'une plus grande empreinte mémoire du GPU. Pour l'instant, fixons la taille du contexte à 128 *tokens*, par opposition aux 1 024 ou 2 048 utilisés respectivement dans le GPT-2 et le GPT-3. - -La plupart des documents contiennent beaucoup plus de 128 *tokens*, donc le fait de tronquer les entrées à la longueur maximale éliminerait une grande partie de notre jeu de données. A la place, nous allons utiliser l'option `return_overflowing_tokens` pour tokeniser l'entrée entière et la diviser en plusieurs morceaux, comme nous l'avons fait dans le [chapitre 6](/course/fr/chapter6/4). Nous utiliserons également l'option `return_length` pour retourner automatiquement la longueur de chaque morceau créé. Souvent, le dernier morceau est plus petit que la taille du contexte et nous nous en débarrasserons pour éviter les problèmes de *padding*. Nous n'en avons pas vraiment besoin puisque de toute façon nous avons beaucoup de données. - -
-Chunking a large texts in several pieces. - -
- -Voyons comment cela fonctionne en examinant les deux premiers exemples : - -```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] -``` - -Nous pouvons voir que nous obtenons 34 morceaux à partir de ces deux exemples. En regardant leurs longueurs, nous pouvons voir qu'ils se terminent avec moins de 128 *tokens* (117 et 41, respectivement). Ils ne représentent qu'une petite fraction du total des morceaux que nous avons (2/34), donc nous pouvons les jeter sans risque. Avec le champ `overflow_to_sample_mapping`, nous pouvons aussi reconstruire quels morceaux appartenaient à quels échantillons d'entrée. - -Avec cette opération, nous utilisons une fonctionnalité pratique de la fonction `Dataset.map()` de 🤗 *Datasets*. En effet, celle-ci ne nécessite pas une correspondance un à un comme nous l'avons vu dans la [section 3](/course/fr/chapter7/3). Nous pouvons créer des batchs avec plus ou moins d'éléments que le batch d'entrée. C'est utile lorsque l'on effectue des opérations telles que l'augmentation ou le filtrage des données qui modifient le nombre d'éléments. Dans notre cas, lors de la tokenisation de chaque élément en morceaux de longeur de la taille de contexte spécifiée, nous créons de nombreux échantillons de chaque document. Nous devons juste nous assurer de supprimer les colonnes existantes, car elles ont une taille conflictuelle. Si nous voulions les garder, nous pourrions les répéter de manière appropriée et les retourner dans l'appel `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 - }) -}) -``` - -Nous avons maintenant 16,7 millions d'exemples avec 128 *tokens* chacun, ce qui correspond à environ 2,1 milliards de *tokens* au total. A titre de comparaison, les modèles GPT-3 et Codex d'OpenAI sont entraînés sur 300 et 100 milliards de *tokens*, respectivement. Les modèles Codex étant initialisés à partir des *checkpoints* GPT-3. Notre objectif dans cette section n'est pas de rivaliser avec ces modèles, qui peuvent générer des textes longs et cohérents, mais de créer une version réduite fournissant une fonction d'autocomplétion rapide. - -Maintenant que le jeu de données est prêt, configurons le modèle ! - - - -✏️ **Essayez !** Se débarrasser de tous les morceaux qui sont plus petits que la taille du contexte n'était pas un gros problème ici parce que nous utilisons de petites fenêtres de contexte. Si vous augmentez la taille du contexte (ou si vous avez un corpus de documents courts), la fraction des morceaux qui sont jetés augmentera. Une façon plus efficace de préparer les données est de joindre tous les échantillons dans un batch avec un *token* `eos_token_id` entre les deux, puis d'effectuer le découpage sur les séquences concaténées. Comme exercice, modifiez la fonction `tokenize()` pour utiliser cette approche. Notez que vous devrez mettre `truncation=False` et enlever les autres arguments du *tokenizer* pour obtenir la séquence complète des identifiants des *tokens*. - - - - -## Initialisation d'un nouveau modèle - -Notre première étape consiste à initialiser un GPT-2. Pour notre modèle, nous utiliserons la même configuration que pour le petit modèle GPT-2. Ainsi nous chargeons la configuration pré-entraînée, nous nous assurons que la taille du *tokenizer* correspond à la taille du vocabulaire du modèle et nous passons les identifiants des *tokens* `bos` et `eos` (début et fin de séquence) : - -{#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, -) -``` - -Avec cette configuration, nous pouvons charger un nouveau modèle. Notez que c'est la première fois que nous n'utilisons pas la fonction `from_pretrained()` puisque nous initialisons nous-mêmes un modèle : - -```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, -) -``` - -Avec cette configuration, nous pouvons charger un nouveau modèle. Notez que c'est la première fois que nous n'utilisons pas la fonction `from_pretrained()` puisque nous initialisons nous-mêmes un modèle : - -```py -model = TFGPT2LMHeadModel(config) -model(model.dummy_inputs) # Construit le modèle -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} - -Notre modèle comporte 124 millions de paramètres que nous devrons régler. Avant de commencer l'entraînement, nous devons configurer un collateur de données qui se chargera de créer les batchs. Nous pouvons utiliser le collateur `DataCollatorForLanguageModeling`, qui est conçu spécifiquement pour la modélisation du langage (comme son nom le suggère subtilement). En plus de l'empilage et du rembourrage des batchs, il s'occupe aussi de la création des étiquettes du modèle de langage. Dans la modélisation causale du langage, les entrées servent aussi d'étiquettes (juste décalées d'un élément) et que le collateur de données crée à la volée pendant l'entraînement pour ne pas avoir à dupliquer les `input_ids`. - -Notez que `DataCollatorForLanguageModeling` supporte à la fois la modélisation du langage masqué (MLM pour *masked language modeling*) et la modélisation du langage causal (CLM pour *causal language modeling*). Par défaut, il prépare les données pour la MLM mais nous pouvons passer à la CLM en définissant l'argument `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} - -Prenons un exemple : - -```py -out = data_collator([tokenized_dataset["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} - -Nous pouvons voir que les exemples ont été empilés et que tous les tenseurs ont la même forme. - -{#if fw === 'tf'} - -Maintenant nous pouvons utiliser la méthode `to_tf_dataset()` pour convertir nos jeux de données en jeux de données TensorFlow avec le collateur de données que nous avons créé ci-dessus : - -```python -tf_train_dataset = tokenized_dataset["train"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=True, - batch_size=32, -) -tf_eval_dataset = tokenized_dataset["valid"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=False, - batch_size=32, -) -``` - -{/if} - - - -⚠️ Le déplacement des entrées et des étiquettes pour les aligner se fait à l'intérieur du modèle, de sorte que le collecteur de données ne fait que copier les entrées pour créer les étiquettes. - - - - -Nous avons maintenant tout ce qu'il faut pour entraîner notre modèle. Ce n'était pas si compliqué ! Avant de commencer l'entraînement, nous devons nous connecter à Hugging Face. Si vous travaillez dans un *notebook*, vous pouvez le faire avec la fonction utilitaire suivante : - -```python -from huggingface_hub import notebook_login - -notebook_login() -``` - -Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face. - -Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : - -```bash -huggingface-cli login -``` - -{#if fw === 'pt'} - -Tout ce qu'il reste à faire est de configurer les arguments d'entraînement et de lancer la fonction `Trainer`. Nous utiliserons un programme de taux d'apprentissage de type cosinus avec un réchauffement et une taille de batch de 256 (`per_device_train_batch_size` x `gradient_accumulation_steps`). L'accumulation du gradient est utilisée lorsqu'un seul batch ne tient pas en mémoire, et construit le gradient de manière incrémentale à travers plusieurs passages en avant/en arrière. Nous verrons cela en action lorsque nous créerons la boucle d'entraînement avec 🤗 *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"], -) -``` - -Maintenant, nous pouvons simplement lancer le `Trainer` et attendre que l'entraînement se termine. Selon que vous l'exécutez sur la totalité ou sur un sous-ensemble de l'échantillon d'entraînement, cela prendra respectivement 20 ou 2 heures. Alors prenez quelques cafés et un bon livre à lire ! - -```py -trainer.train() -``` - -Une fois l'entraînement terminé, nous pouvons pousser le modèle et le *tokenizer* vers le *Hub* : - -```py -trainer.push_to_hub() -``` - -{:else} - -Tout ce qu'il reste à faire est de configurer les hyperparamètres d'entraînement et d'appeler `compile()` et `fit()`. Nous utiliserons un programme de taux d'apprentissage avec un réchauffement pour améliorer la stabilité de l'entraînement : - -```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) - -# Entraîner en mixed-precision float16 -tf.keras.mixed_precision.set_global_policy("mixed_float16") -``` - -Maintenant, nous pouvons simplement appeler `model.fit()` et attendre que l'entraînement se termine. Selon que vous l'exécutez sur la totalité ou sur un sous-ensemble de l'échantillon d'entraînement, cela prendra respectivement 20 ou 2 heures. Alors prenez quelques cafés et un bon livre à lire ! Une fois l'entraînement terminé, nous pouvons pousser le modèle et le *tokenizer* vers le *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} - - - -✏️ **Essayez !** Il ne nous a fallu qu'une trentaine de lignes de code en plus des `TrainingArguments` pour passer des textes bruts à l'entraînement du GPT-2. Essayez-le avec votre propre jeu de données et voyez si vous pouvez obtenir de bons résultats ! - - - - - -{#if fw === 'pt'} - -💡 Si vous avez accès à une machine avec plusieurs GPUs, essayez d'y exécuter le code. `Trainer` gère automatiquement plusieurs machines ce qui peut accélérer considérablement l'entraînement. - -{:else} - -💡 Si vous avez accès à une machine avec plusieurs GPUs, vous pouvez essayer d'utiliser `MirroredStrategy` pour accélérer considérablement l'entraînement. Vous devrez créer un objet `tf.distribute.MirroredStrategy` et vous assurer que les commandes `to_tf_dataset` ainsi que la création du modèle et l'appel à `fit()` sont tous exécutés dans `scope()`. Vous pouvez consulter la documentation à ce sujet [ici](https://www.tensorflow.org/guide/distributed_training#use_tfdistributestrategy_with_keras_modelfit). - -{/if} - - - -## Génération de code avec le pipeline - -C'est maintenant le moment de vérité : voyons comment le modèle entraîné fonctionne réellement ! Nous pouvons voir dans les logs que la perte a diminué régulièrement, mais pour mettre le modèle à l'épreuve, regardons comment il fonctionne sur certains messages. Pour ce faire, nous allons envelopper le modèle dans un `pipeline` de génération de texte et, s'il y en a un de disponible, utiliser un GPU pour avoir des générations rapidement : - -{#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} - -Let's start with the simple task of creating a scatter plot: - -```py -txt = """\ -# créer des données -x = np.random.randn(100) -y = np.random.randn(100) - -# créer un nuage de points avec x, y -""" -print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) -``` - -```python out -# créer des données -x = np.random.randn(100) -y = np.random.randn(100) - -# créer un nuage de points avec x, y -plt.scatter(x, y) -``` - -Le résultat semble correct. Est-ce que cela fonctionne aussi pour une opération `pandas` ? Voyons si nous pouvons créer un `DataFrame` à partir de deux tableaux : - -```py -txt = """\ -# créer des données -x = np.random.randn(100) -y = np.random.randn(100) - -# créer un tableau de données à partir de x et y -""" -print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) -``` - -```python out -# créer des données -x = np.random.randn(100) -y = np.random.randn(100) - -# créer un tableau de données à partir de x et y -df = pd.DataFrame({'x': x, 'y': y}) -df.insert(0,'x', x) -for -``` - -Bien, c'est la bonne réponse. Bien qu'il insère ensuite la colonne `x` à nouveau. Comme le nombre de *tokens* générés est limité, la boucle `for` suivante est coupée. Voyons si nous pouvons faire quelque chose d'un peu plus complexe et faire en sorte que le modèle nous aide à utiliser l'opération `groupby` : - -```py -txt = """\ -# tableau de données avec profession, revenu et nom -df = pd.DataFrame({'profession': x, 'income':y, 'name': z}) - -# calculer le revenu moyen par profession -""" -print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) -``` - -```python out -# tableau de données avec profession, revenu et nom -df = pd.DataFrame({'profession': x, 'income':y, 'name': z}) - -# calculer le revenu moyen par profession -profession = df.groupby(['profession']).mean() -``` - -Pas mal, c'est la bonne façon de faire. Enfin, voyons si nous pouvons aussi l'utiliser pour `scikit-learn` et utiliser un modèle *Random Forest* : - -```py -txt = """ -# import random forest regressor from scikit-learn -from sklearn.ensemble import RandomForestRegressor - -# entraînement du modèle de forêt aléatoire avec 300 estimateurs sur 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 - -# entraînement du modèle de forêt aléatoire avec 300 estimateurs sur X, y : -rf = RandomForestRegressor(n_estimators=300, random_state=random_state, max_depth=3) -rf.fit(X, y) -rf -``` - -{#if fw === 'tf'} - -Au vu de ces quelques exemples, il semble que le modèle ait appris une partie de la syntaxe des bibliothèques Python de science des données. Bien sûr, nous devrions évaluer le modèle de manière plus approfondie avant de le déployer dans le monde réel, mais il s'agit tout de même d'un prototype impressionnant. - -{:else} - -Au vu de ces quelques exemples, il semble que le modèle ait appris une partie de la syntaxe des bibliothèques Python de science des données. Bien sûr, nous devrions évaluer le modèle de manière plus approfondie avant de le déployer dans le monde réel, mais il s'agit tout de même d'un prototype impressionnant. Parfois, il est nécessaire de personnaliser davantage l'entraînement du modèle afin d'obtenir les performances nécessaires pour un cas d'utilisation donné. Par exemple, que se passe-t-il si l'on souhaite mettre à jour dynamiquement la taille du batch ou si l'on dispose d'une boucle d'entraînement conditionnelle qui ignore les mauvais exemples à la volée ? Une option serait de sous-classer le `Trainer` et d'ajouter les changements nécessaires, mais parfois il est plus simple d'écrire la boucle d'entraînement à partir de zéro. C'est là qu'intervient 🤗 *Accelerate*. - -{/if} - -{#if fw === 'pt'} - -## Entraîner avec 🤗 Accelerate - -Nous avons vu comment entraîner un modèle avec le `Trainer`, qui permet une certaine personnalisation. Cependant, parfois nous voulons un contrôle total sur la boucle d'entraînement ou nous souhaitons faire quelques changements exotiques. Dans ce cas, 🤗 *Accelerate* est un excellent choix, et dans cette section, nous allons suivre les étapes pour l'utiliser pour entraîner notre modèle. Pour rendre les choses plus intéressantes, nous allons également ajouter une touche à la boucle d'entraînement. - - - -Puisque nous sommes principalement intéressés par l'autocomplétion pour les bibliothèques de science des données, il est logique de donner plus de poids aux échantillons d'entraînement qui utilisent davantage ces bibliothèques. Nous pouvons facilement identifier ces exemples grâce à l'utilisation de mots-clés tels que `plt`, `pd`, `sk`, `fit`, et `predict`, qui sont les noms d'importation les plus fréquents pour `matplotlib.pyplot`, `pandas`, et `sklearn` ainsi que les fonctions `fit` et `predict` de cette dernière. Si chacun d'entre eux est représenté par un seul *token*, nous pouvons facilement vérifier s'ils apparaissent dans la séquence d'entrée. Les *tokens* peuvent avoir un préfixe d'espacement, donc nous vérifierons aussi ces versions dans le vocabulaire du *tokenizer*. Pour vérifier que cela fonctionne, nous ajouterons un *token* de test qui devrait être divisé en plusieurs *tokens* : - -```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' -``` - -Super, ça a l'air de bien fonctionner ! Nous pouvons maintenant écrire une fonction de perte personnalisée qui prend la séquence d'entrée, les logits et les *tokens* clés que nous venons de sélectionner comme entrées. Tout d'abord, nous devons aligner les logits et les entrées : la séquence d'entrée décalée d'une unité vers la droite forme les étiquettes, puisque le *token* suivant est l'étiquette du *token* actuel. Nous pouvons y parvenir en commençant les étiquettes à partir du deuxième *token* de la séquence d'entrée, puisque le modèle ne fait pas de prédiction pour le premier *token* de toute façon. Ensuite, nous coupons le dernier logit, car nous n'avons pas d'étiquette pour le *token* qui suit la séquence d'entrée complète. Avec cela, nous pouvons calculer la perte par échantillon et compter les occurrences de tous les mots-clés dans chaque échantillon. Enfin, nous calculons la moyenne pondérée sur tous les échantillons en utilisant les occurrences comme poids. Comme nous ne voulons pas rejeter tous les échantillons qui ne contiennent pas de mots-clés, nous ajoutons 1 aux poids : - -```py -from torch.nn import CrossEntropyLoss -import torch - - -def keytoken_weighted_loss(inputs, logits, keytoken_ids, alpha=1.0): - # Décalage pour que tokens < n prédisent n - shift_labels = inputs[..., 1:].contiguous() - shift_logits = logits[..., :-1, :].contiguous() - # Calcul de la perte par token - loss_fct = CrossEntropyLoss(reduce=False) - loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1)) - # Redimensionnement et perte moyenne par échantillon - loss_per_sample = loss.view(shift_logits.size(0), shift_logits.size(1)).mean(axis=1) - # Calculer et échelonner la pondération - weights = torch.stack([(inputs == kt).float() for kt in keytoken_ids]).sum( - axis=[0, 2] - ) - weights = alpha * (1.0 + weights) - # Calculer la moyenne pondérée - weighted_loss = (loss_per_sample * weights).mean() - return weighted_loss -``` - -Avant de commencer à entraîner avec cette nouvelle fonction de perte géniale, nous devons préparer quelques éléments : - -- Nous avons besoin de chargeurs de données pour charger les données par batch. -- Nous devons définir les paramètres de décroissance des poids. -- De temps en temps, nous voulons évaluer, il est donc logique d'envelopper le code d'évaluation dans une fonction. - -Commençons par les chargeurs de données. Nous avons seulement besoin de définir le format du jeu de données à `"torch"` et ensuite nous pouvons le passer à un PyTorch `DataLoader` avec la taille de batch appropriée : - -```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) -``` - -Ensuite, nous regroupons les paramètres de façon à ce que l'optimiseur sache lesquels bénéficieront d'une décroissance de poids supplémentaire. Habituellement, tous les termes de biais et les poids de la *LayerNorm* en sont exemptés. Voici comment nous pouvons le faire : - -```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}, - ] -``` - -Puisque nous voulons évaluer le modèle régulièrement sur l'ensemble de validation pendant l'entraînement, écrivons une fonction pour cela aussi. Elle passe simplement par le *dataloader* d'évaluation et rassemble toutes les pertes à travers les processus : - -```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() -``` - -Avec la fonction `evaluate()` nous pouvons rapporter la perte et la [perplexité](/course/fr/chapter7/3) à intervalles réguliers. Ensuite, nous redéfinissons notre modèle pour nous assurer que nous entraînons à nouveau à partir de zéro : - -```py -model = GPT2LMHeadModel(config) -``` - -Nous pouvons ensuite définir notre optimiseur, en utilisant la fonction précédente pour diviser les paramètres de décroissance des poids : - -```py -from torch.optim import AdamW - -optimizer = AdamW(get_grouped_params(model), lr=5e-4) -``` - -Préparons maintenant le modèle, l'optimiseur et les chargeurs de données pour pouvoir commencer l'entraînement : - -```py -from accelerate import Accelerator - -accelerator = Accelerator(fp16=True) - -model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( - model, optimizer, train_dataloader, eval_dataloader -) -``` - - - -🚨 Si vous vous entraînez sur un TPU, vous devrez déplacer tout le code commençant à la cellule ci-dessus dans une fonction d'entraînement dédiée. Voir le [chapitre 3](/course/fr/chapter3) pour plus de détails. - - - -Maintenant que nous avons envoyé notre `train_dataloader` à `accelerator.prepare()`, nous pouvons utiliser sa longueur pour calculer le nombre d'étapes d'entraînement. Rappelez-vous que nous devons toujours faire cela après avoir préparé le *dataloader* car cette méthode modifiera sa longueur. Nous utilisons un programme linéaire classique du taux d'apprentissage à 0 : - -```py -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, -) -``` - -Enfin, pour pousser notre modèle vers le *Hub*, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous au *Hub*, si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'identifiant du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix. Il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `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' -``` - -Ensuite, nous pouvons cloner ce dépôt dans un dossier local. S'il existe déjà, ce dossier local doit être un clone existant du dépôt avec lequel nous travaillons : - -```py -output_dir = "codeparrot-ds-accelerate" -repo = Repository(output_dir, clone_from=repo_name) -``` - -Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output_dir` en appelant la méthode `repo.push_to_hub()`. Cela nous aidera à télécharger les modèles intermédiaires à la fin de chaque époque. - -Avant de nous entraîner, exécutons un test rapide pour voir si la fonction d'évaluation fonctionne correctement : - -```py -evaluate() -``` - -```python out -(10.934126853942871, 56057.14453125) -``` - -Ce sont des valeurs très élevées pour la perte et la perplexité, mais ce n'est pas surprenant puisque nous n'avons pas encore entraîné le modèle. Avec cela, nous avons tout préparé pour écrire la partie principale du script d'entraînement : la boucle d'entraînement. Dans celle-ci, nous itérons sur le chargeur de données et transmettons les batchs au modèle. Avec les logits, nous pouvons alors évaluer notre fonction de perte personnalisée. Nous mettons à l'échelle la perte par le nombre d'étapes d'accumulation du gradient afin de ne pas créer de plus grandes pertes en agrégeant plus d'étapes. Avant de procéder à l'optimisation, nous découpons également les gradients pour une meilleure convergence. Enfin, tous les quelques pas, nous évaluons le modèle sur l'ensemble d'évaluation avec notre nouvelle fonction `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=len(train_dataloader) - ): - logits = model(batch["input_ids"]).logits - loss = keytoken_weighted_loss(batch["input_ids"], logits, keytoken_ids) - if step % 100 == 0: - accelerator.print( - { - "lr": get_lr(), - "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 - ) -``` - -Et voilà, vous disposez maintenant de votre propre boucle d'entraînement personnalisée pour les modèles de langage causal tels que le GPT-2. Vous pouvez encore l'adapter à vos besoins. - - - -✏️ **Essayez !** Vous pouvez créer votre propre fonction de perte personnalisée, adaptée à votre cas d'utilisation, ou ajouter une autre étape personnalisée dans la boucle d'entraînement. - - - - - -✏️ **Essayez !** Lorsque vous effectuez de longues expériences d'entraînement, il est bon d'enregistrer les mesures importantes à l'aide d'outils tels que *TensorBoard* ou *Weights & Biases*. Ajoutez l'un d'eux à la boucle d'entraînement afin de pouvoir toujours vérifier comment se déroule l'entraînement. - - - -{/if} + + +# Entraîner un modèle de langage causal à partir de zéro + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Jusqu'à présent, nous avons surtout réutilisé des modèles pré-entraînés et les avons *finetunés* sur de nouveaux cas d'usage. Comme nous l'avons vu dans le [chapitre 1](/course/fr/chapter1), ceci est communément appelé _apprentissage par transfert_, et il s'agit d'une stratégie très efficace pour appliquer les *transformers* à la plupart des applications du monde réel où les données étiquetées sont rares. Dans ce chapitre, nous allons adopter une approche différente consistant à entraîner un modèle complètement nouveau à partir de zéro. C'est une bonne démarche à adopter si vous avez beaucoup de données et qu'elles sont très différentes des données de pré-entraînement utilisées par les modèles disponibles. Cependant, le pré-entraînement d'un modèle de langue nécessite beaucoup plus de ressources informatiques que le simple *finetuning* d'un modèle existant. Parmi les exemples où il peut être utile d'entraîner un nouveau modèle, citons les jeux de données constitués de notes de musique, de séquences moléculaires telles que l'ADN, ou de langages de programmation. Ces derniers ont récemment gagné en popularité grâce à des outils tels que TabNine et Copilot de GitHub (alimentés par le modèle Codex d'OpenAI) qui peuvent générer de longues séquences de code. Cette tâche de génération de texte est mieux abordée avec des modèles de langage autorégressifs ou causaux tels que le GPT-2. + +Dans cette section, nous allons construire une version réduite d'un modèle de génération de code Python. Nous nous concentrerons sur la complétion d'une ligne de code au lieu de fonctions ou de classes complètes. Lorsque vous travaillez sur des projets de science des données en Python, vous êtes souvent en contact avec les bibliothèques `matplotlib`, `seaborn`, `pandas` et `scikit-learn`. Lors de l'utilisation de ces *frameworks*, il est fréquent d'avoir besoin de rechercher des commandes spécifiques. Il serait donc bien d'utiliser un modèle pour compléter ces appels pour nous. + + + + +Dans le [chapitre 6](/course/fr/chapter6), nous avons créé un *tokenizer* efficace pour traiter du code Python. Nous avons besoin d'un jeu de données à grande échelle pour pré-entraîner un modèle. Ici, nous allons appliquer notre *tokenizer* à un corpus de code Python provenant des dépôts GitHub. Nous utiliserons ensuite l'API `Trainer` et 🤗 *Accelerate* pour entraîner le modèle. C'est parti ! + + + + +Il s'agit d'une présentation du modèle qui a été entraîné à l'aide du code présenté dans cette section et qui a ensuité été téléchargé sur le *Hub*. Vous pouvez le trouver [ici](https://huggingface.co/huggingface-course/codeparrot-ds?text=plt.imshow%28). Notez qu'étant donné qu'il y a un certains aléat dans la génération du texte, vous obtiendrez probablement un résultat légèrement différent. + +## Collecte des données + +On peut trouver du code Python en abondance dans les dépôts de code tels que GitHub, que nous pouvons utiliser pour créer un jeu de données en récupérant chaque dépôt Python. C'est l'approche adoptée dans le [livre *Natural Language Processing with Transformers*](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/) pour pré-entraîner un grand GPT-2. En utilisant un dépôt GitHub d'environ 180 Go contenant approximativement 20 millions de fichiers Python, les auteurs du livre ont construit un jeu de données appelé `codeparrot` qu'ils ont ensuite partagé sur le [*Hub*](https://huggingface.co/datasets/transformersbook/codeparrot). + +Cependant, entraîner sur l'ensemble du corpus prend beaucoup de temps et demande beaucoup de ressources de calculs. Dans notre cas, nous n'avons besoin que du sous-ensemble du jeu de données qui est relatif aux codes portant sur la science des données. Commençons donc par filtrer le jeu de données `codeparrot` en ne gardant que les fichiers incluant l'une des bibliothèques de science des données énumérées précédemment. En raison de la taille du jeu de données, nous voulons éviter de le télécharger. Nous utiliserons donc la fonctionnalité de *streaming* de 🤗 *Datasets* afin de le filtrer à la volée. Pour nous aider à filtrer les échantillons de code utilisant les bibliothèques que nous avons mentionnées précédemment, nous utilisons la fonction suivante : + +```py +def any_keyword_in_string(string, keywords): + for keyword in keywords: + if keyword in string: + return True + return False +``` + +Testons-le sur deux exemples : + +```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 +``` + +Nous pouvons l'utiliser pour créer une fonction qui va *streamer* le jeu de donner et filtrer les éléments que nous voulons : + +```py +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) +``` + +Ensuite, nous pouvons simplement appliquer cette fonction : + +```py +# Cette cellule prendra beaucoup de temps à s'exécuter, donc vous devriez la sauter et aller à la suivante ! +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. +``` + +Cela nous laisse avec environ 3 % du jeu de données original, ce qui est tout de même assez important puisqu'il fait 6 Go et se compose de 600 000 scripts Python ! + +Le filtrage peut prendre de 2 à 3 heures, selon votre machine et votre bande passante. Si vous ne voulez pas passer par ce long processus, nous fournissons sur le *Hub* le jeu de données filtré pour que vous puissiez le télécharger : + +```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="train") + +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 + }) +}) +``` + + + +Le pré-entraînement du modèle de langue prendra un certain temps. Nous vous suggérons donc d'exécuter d'abord la boucle d'entraînement sur un petit échantillon des données en décommentant les deux lignes dans le code ci-dessus. Assurez-vous alors que l'entraînement se termine avec succès et que les modèles sont stockés. Rien n'est plus frustrant qu'un entraînement qui échoue à la dernière étape car vous avez oublié de créer un dossier ou parce qu'il y a une faute de frappe à la fin de la boucle d'entraînement ! + + + +Examinons un exemple tiré du jeu de données. Nous ne montrerons que les 200 premiers caractères de chaque champ : + +```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''' +``` + +Nous pouvons voir que le champ `content` contient le code sur lequel nous voulons que notre modèle s'entraîne. Maintenant que nous avons un jeu de données, nous devons préparer les textes afin qu'ils soient dans un format approprié pour le pré-entraînement. + +## Préparation du jeu de données + + + +La première étape est de tokeniser les données afin de pouvoir les utiliser pour l'entraînement. Puisque notre objectif est d'autocompléter de courts appels de fonctions, nous pouvons garder la taille du contexte relativement petite. L'avantage est que nous pouvons entraîner le modèle beaucoup plus rapidement et qu'il nécessite beaucoup moins de mémoire. Si c'est important pour votre application d'avoir davantage de contexte (par exemple, si vous voulez que le modèle écrive des tests unitaires basés sur un fichier avec la définition de la fonction), assurez-vous d'augmenter ce nombre. Gardez néanmoins à l'esprit que cela s'accompagne d'une plus grande empreinte mémoire du GPU. Pour l'instant, fixons la taille du contexte à 128 *tokens*, par opposition aux 1 024 ou 2 048 utilisés respectivement dans le GPT-2 et le GPT-3. + +La plupart des documents contiennent beaucoup plus de 128 *tokens*, donc le fait de tronquer les entrées à la longueur maximale éliminerait une grande partie de notre jeu de données. A la place, nous allons utiliser l'option `return_overflowing_tokens` pour tokeniser l'entrée entière et la diviser en plusieurs morceaux, comme nous l'avons fait dans le [chapitre 6](/course/fr/chapter6/4). Nous utiliserons également l'option `return_length` pour retourner automatiquement la longueur de chaque morceau créé. Souvent, le dernier morceau est plus petit que la taille du contexte et nous nous en débarrasserons pour éviter les problèmes de *padding*. Nous n'en avons pas vraiment besoin puisque de toute façon nous avons beaucoup de données. + +
+Chunking a large texts in several pieces. + +
+ +Voyons comment cela fonctionne en examinant les deux premiers exemples : + +```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] +``` + +Nous pouvons voir que nous obtenons 34 morceaux à partir de ces deux exemples. En regardant leurs longueurs, nous pouvons voir qu'ils se terminent avec moins de 128 *tokens* (117 et 41, respectivement). Ils ne représentent qu'une petite fraction du total des morceaux que nous avons (2/34), donc nous pouvons les jeter sans risque. Avec le champ `overflow_to_sample_mapping`, nous pouvons aussi reconstruire quels morceaux appartenaient à quels échantillons d'entrée. + +Avec cette opération, nous utilisons une fonctionnalité pratique de la fonction `Dataset.map()` de 🤗 *Datasets*. En effet, celle-ci ne nécessite pas une correspondance un à un comme nous l'avons vu dans la [section 3](/course/fr/chapter7/3). Nous pouvons créer des batchs avec plus ou moins d'éléments que le batch d'entrée. C'est utile lorsque l'on effectue des opérations telles que l'augmentation ou le filtrage des données qui modifient le nombre d'éléments. Dans notre cas, lors de la tokenisation de chaque élément en morceaux de longeur de la taille de contexte spécifiée, nous créons de nombreux échantillons de chaque document. Nous devons juste nous assurer de supprimer les colonnes existantes, car elles ont une taille conflictuelle. Si nous voulions les garder, nous pourrions les répéter de manière appropriée et les retourner dans l'appel `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 + }) +}) +``` + +Nous avons maintenant 16,7 millions d'exemples avec 128 *tokens* chacun, ce qui correspond à environ 2,1 milliards de *tokens* au total. A titre de comparaison, les modèles GPT-3 et Codex d'OpenAI sont entraînés sur 300 et 100 milliards de *tokens*, respectivement. Les modèles Codex étant initialisés à partir des *checkpoints* GPT-3. Notre objectif dans cette section n'est pas de rivaliser avec ces modèles, qui peuvent générer des textes longs et cohérents, mais de créer une version réduite fournissant une fonction d'autocomplétion rapide. + +Maintenant que le jeu de données est prêt, configurons le modèle ! + + + +✏️ **Essayez !** Se débarrasser de tous les morceaux qui sont plus petits que la taille du contexte n'était pas un gros problème ici parce que nous utilisons de petites fenêtres de contexte. Si vous augmentez la taille du contexte (ou si vous avez un corpus de documents courts), la fraction des morceaux qui sont jetés augmentera. Une façon plus efficace de préparer les données est de joindre tous les échantillons dans un batch avec un *token* `eos_token_id` entre les deux, puis d'effectuer le découpage sur les séquences concaténées. Comme exercice, modifiez la fonction `tokenize()` pour utiliser cette approche. Notez que vous devrez mettre `truncation=False` et enlever les autres arguments du *tokenizer* pour obtenir la séquence complète des identifiants des *tokens*. + + + + +## Initialisation d'un nouveau modèle + +Notre première étape consiste à initialiser un GPT-2. Pour notre modèle, nous utiliserons la même configuration que pour le petit modèle GPT-2. Ainsi nous chargeons la configuration pré-entraînée, nous nous assurons que la taille du *tokenizer* correspond à la taille du vocabulaire du modèle et nous passons les identifiants des *tokens* `bos` et `eos` (début et fin de séquence) : + +{#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, +) +``` + +Avec cette configuration, nous pouvons charger un nouveau modèle. Notez que c'est la première fois que nous n'utilisons pas la fonction `from_pretrained()` puisque nous initialisons nous-mêmes un modèle : + +```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, +) +``` + +Avec cette configuration, nous pouvons charger un nouveau modèle. Notez que c'est la première fois que nous n'utilisons pas la fonction `from_pretrained()` puisque nous initialisons nous-mêmes un modèle : + +```py +model = TFGPT2LMHeadModel(config) +model(model.dummy_inputs) # Construit le modèle +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} + +Notre modèle comporte 124 millions de paramètres que nous devrons régler. Avant de commencer l'entraînement, nous devons configurer un collateur de données qui se chargera de créer les batchs. Nous pouvons utiliser le collateur `DataCollatorForLanguageModeling`, qui est conçu spécifiquement pour la modélisation du langage (comme son nom le suggère subtilement). En plus de l'empilage et du rembourrage des batchs, il s'occupe aussi de la création des étiquettes du modèle de langage. Dans la modélisation causale du langage, les entrées servent aussi d'étiquettes (juste décalées d'un élément) et que le collateur de données crée à la volée pendant l'entraînement pour ne pas avoir à dupliquer les `input_ids`. + +Notez que `DataCollatorForLanguageModeling` supporte à la fois la modélisation du langage masqué (MLM pour *masked language modeling*) et la modélisation du langage causal (CLM pour *causal language modeling*). Par défaut, il prépare les données pour la MLM mais nous pouvons passer à la CLM en définissant l'argument `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} + +Prenons un exemple : + +```py +out = data_collator([tokenized_dataset["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} + +Nous pouvons voir que les exemples ont été empilés et que tous les tenseurs ont la même forme. + +{#if fw === 'tf'} + +Maintenant nous pouvons utiliser la méthode `to_tf_dataset()` pour convertir nos jeux de données en jeux de données TensorFlow avec le collateur de données que nous avons créé ci-dessus : + +```python +tf_train_dataset = tokenized_dataset["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) +tf_eval_dataset = tokenized_dataset["valid"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=32, +) +``` + +{/if} + + + +⚠️ Le déplacement des entrées et des étiquettes pour les aligner se fait à l'intérieur du modèle, de sorte que le collecteur de données ne fait que copier les entrées pour créer les étiquettes. + + + + +Nous avons maintenant tout ce qu'il faut pour entraîner notre modèle. Ce n'était pas si compliqué ! Avant de commencer l'entraînement, nous devons nous connecter à Hugging Face. Si vous travaillez dans un *notebook*, vous pouvez le faire avec la fonction utilitaire suivante : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Cela affichera un *widget* où vous pourrez entrer vos identifiants de connexion à Hugging Face. + +Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : + +```bash +huggingface-cli login +``` + +{#if fw === 'pt'} + +Tout ce qu'il reste à faire est de configurer les arguments d'entraînement et de lancer la fonction `Trainer`. Nous utiliserons un programme de taux d'apprentissage de type cosinus avec un réchauffement et une taille de batch de 256 (`per_device_train_batch_size` x `gradient_accumulation_steps`). L'accumulation du gradient est utilisée lorsqu'un seul batch ne tient pas en mémoire, et construit le gradient de manière incrémentale à travers plusieurs passages en avant/en arrière. Nous verrons cela en action lorsque nous créerons la boucle d'entraînement avec 🤗 *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"], +) +``` + +Maintenant, nous pouvons simplement lancer le `Trainer` et attendre que l'entraînement se termine. Selon que vous l'exécutez sur la totalité ou sur un sous-ensemble de l'échantillon d'entraînement, cela prendra respectivement 20 ou 2 heures. Alors prenez quelques cafés et un bon livre à lire ! + +```py +trainer.train() +``` + +Une fois l'entraînement terminé, nous pouvons pousser le modèle et le *tokenizer* vers le *Hub* : + +```py +trainer.push_to_hub() +``` + +{:else} + +Tout ce qu'il reste à faire est de configurer les hyperparamètres d'entraînement et d'appeler `compile()` et `fit()`. Nous utiliserons un programme de taux d'apprentissage avec un réchauffement pour améliorer la stabilité de l'entraînement : + +```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) + +# Entraîner en mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +Maintenant, nous pouvons simplement appeler `model.fit()` et attendre que l'entraînement se termine. Selon que vous l'exécutez sur la totalité ou sur un sous-ensemble de l'échantillon d'entraînement, cela prendra respectivement 20 ou 2 heures. Alors prenez quelques cafés et un bon livre à lire ! Une fois l'entraînement terminé, nous pouvons pousser le modèle et le *tokenizer* vers le *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} + + + +✏️ **Essayez !** Il ne nous a fallu qu'une trentaine de lignes de code en plus des `TrainingArguments` pour passer des textes bruts à l'entraînement du GPT-2. Essayez-le avec votre propre jeu de données et voyez si vous pouvez obtenir de bons résultats ! + + + + + +{#if fw === 'pt'} + +💡 Si vous avez accès à une machine avec plusieurs GPUs, essayez d'y exécuter le code. `Trainer` gère automatiquement plusieurs machines ce qui peut accélérer considérablement l'entraînement. + +{:else} + +💡 Si vous avez accès à une machine avec plusieurs GPUs, vous pouvez essayer d'utiliser `MirroredStrategy` pour accélérer considérablement l'entraînement. Vous devrez créer un objet `tf.distribute.MirroredStrategy` et vous assurer que les commandes `to_tf_dataset` ainsi que la création du modèle et l'appel à `fit()` sont tous exécutés dans `scope()`. Vous pouvez consulter la documentation à ce sujet [ici](https://www.tensorflow.org/guide/distributed_training#use_tfdistributestrategy_with_keras_modelfit). + +{/if} + + + +## Génération de code avec le pipeline + +C'est maintenant le moment de vérité : voyons comment le modèle entraîné fonctionne réellement ! Nous pouvons voir dans les logs que la perte a diminué régulièrement, mais pour mettre le modèle à l'épreuve, regardons comment il fonctionne sur certains messages. Pour ce faire, nous allons envelopper le modèle dans un `pipeline` de génération de texte et, s'il y en a un de disponible, utiliser un GPU pour avoir des générations rapidement : + +{#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} + +Let's start with the simple task of creating a scatter plot: + +```py +txt = """\ +# créer des données +x = np.random.randn(100) +y = np.random.randn(100) + +# créer un nuage de points avec x, y +""" +print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) +``` + +```python out +# créer des données +x = np.random.randn(100) +y = np.random.randn(100) + +# créer un nuage de points avec x, y +plt.scatter(x, y) +``` + +Le résultat semble correct. Est-ce que cela fonctionne aussi pour une opération `pandas` ? Voyons si nous pouvons créer un `DataFrame` à partir de deux tableaux : + +```py +txt = """\ +# créer des données +x = np.random.randn(100) +y = np.random.randn(100) + +# créer un tableau de données à partir de x et y +""" +print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) +``` + +```python out +# créer des données +x = np.random.randn(100) +y = np.random.randn(100) + +# créer un tableau de données à partir de x et y +df = pd.DataFrame({'x': x, 'y': y}) +df.insert(0,'x', x) +for +``` + +Bien, c'est la bonne réponse. Bien qu'il insère ensuite la colonne `x` à nouveau. Comme le nombre de *tokens* générés est limité, la boucle `for` suivante est coupée. Voyons si nous pouvons faire quelque chose d'un peu plus complexe et faire en sorte que le modèle nous aide à utiliser l'opération `groupby` : + +```py +txt = """\ +# tableau de données avec profession, revenu et nom +df = pd.DataFrame({'profession': x, 'income':y, 'name': z}) + +# calculer le revenu moyen par profession +""" +print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) +``` + +```python out +# tableau de données avec profession, revenu et nom +df = pd.DataFrame({'profession': x, 'income':y, 'name': z}) + +# calculer le revenu moyen par profession +profession = df.groupby(['profession']).mean() +``` + +Pas mal, c'est la bonne façon de faire. Enfin, voyons si nous pouvons aussi l'utiliser pour `scikit-learn` et utiliser un modèle *Random Forest* : + +```py +txt = """ +# import random forest regressor from scikit-learn +from sklearn.ensemble import RandomForestRegressor + +# entraînement du modèle de forêt aléatoire avec 300 estimateurs sur 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 + +# entraînement du modèle de forêt aléatoire avec 300 estimateurs sur X, y : +rf = RandomForestRegressor(n_estimators=300, random_state=random_state, max_depth=3) +rf.fit(X, y) +rf +``` + +{#if fw === 'tf'} + +Au vu de ces quelques exemples, il semble que le modèle ait appris une partie de la syntaxe des bibliothèques Python de science des données. Bien sûr, nous devrions évaluer le modèle de manière plus approfondie avant de le déployer dans le monde réel, mais il s'agit tout de même d'un prototype impressionnant. + +{:else} + +Au vu de ces quelques exemples, il semble que le modèle ait appris une partie de la syntaxe des bibliothèques Python de science des données. Bien sûr, nous devrions évaluer le modèle de manière plus approfondie avant de le déployer dans le monde réel, mais il s'agit tout de même d'un prototype impressionnant. Parfois, il est nécessaire de personnaliser davantage l'entraînement du modèle afin d'obtenir les performances nécessaires pour un cas d'utilisation donné. Par exemple, que se passe-t-il si l'on souhaite mettre à jour dynamiquement la taille du batch ou si l'on dispose d'une boucle d'entraînement conditionnelle qui ignore les mauvais exemples à la volée ? Une option serait de sous-classer le `Trainer` et d'ajouter les changements nécessaires, mais parfois il est plus simple d'écrire la boucle d'entraînement à partir de zéro. C'est là qu'intervient 🤗 *Accelerate*. + +{/if} + +{#if fw === 'pt'} + +## Entraîner avec 🤗 Accelerate + +Nous avons vu comment entraîner un modèle avec le `Trainer`, qui permet une certaine personnalisation. Cependant, parfois nous voulons un contrôle total sur la boucle d'entraînement ou nous souhaitons faire quelques changements exotiques. Dans ce cas, 🤗 *Accelerate* est un excellent choix, et dans cette section, nous allons suivre les étapes pour l'utiliser pour entraîner notre modèle. Pour rendre les choses plus intéressantes, nous allons également ajouter une touche à la boucle d'entraînement. + + + +Puisque nous sommes principalement intéressés par l'autocomplétion pour les bibliothèques de science des données, il est logique de donner plus de poids aux échantillons d'entraînement qui utilisent davantage ces bibliothèques. Nous pouvons facilement identifier ces exemples grâce à l'utilisation de mots-clés tels que `plt`, `pd`, `sk`, `fit`, et `predict`, qui sont les noms d'importation les plus fréquents pour `matplotlib.pyplot`, `pandas`, et `sklearn` ainsi que les fonctions `fit` et `predict` de cette dernière. Si chacun d'entre eux est représenté par un seul *token*, nous pouvons facilement vérifier s'ils apparaissent dans la séquence d'entrée. Les *tokens* peuvent avoir un préfixe d'espacement, donc nous vérifierons aussi ces versions dans le vocabulaire du *tokenizer*. Pour vérifier que cela fonctionne, nous ajouterons un *token* de test qui devrait être divisé en plusieurs *tokens* : + +```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' +``` + +Super, ça a l'air de bien fonctionner ! Nous pouvons maintenant écrire une fonction de perte personnalisée qui prend la séquence d'entrée, les logits et les *tokens* clés que nous venons de sélectionner comme entrées. Tout d'abord, nous devons aligner les logits et les entrées : la séquence d'entrée décalée d'une unité vers la droite forme les étiquettes, puisque le *token* suivant est l'étiquette du *token* actuel. Nous pouvons y parvenir en commençant les étiquettes à partir du deuxième *token* de la séquence d'entrée, puisque le modèle ne fait pas de prédiction pour le premier *token* de toute façon. Ensuite, nous coupons le dernier logit, car nous n'avons pas d'étiquette pour le *token* qui suit la séquence d'entrée complète. Avec cela, nous pouvons calculer la perte par échantillon et compter les occurrences de tous les mots-clés dans chaque échantillon. Enfin, nous calculons la moyenne pondérée sur tous les échantillons en utilisant les occurrences comme poids. Comme nous ne voulons pas rejeter tous les échantillons qui ne contiennent pas de mots-clés, nous ajoutons 1 aux poids : + +```py +from torch.nn import CrossEntropyLoss +import torch + + +def keytoken_weighted_loss(inputs, logits, keytoken_ids, alpha=1.0): + # Décalage pour que tokens < n prédisent n + shift_labels = inputs[..., 1:].contiguous() + shift_logits = logits[..., :-1, :].contiguous() + # Calcul de la perte par token + loss_fct = CrossEntropyLoss(reduce=False) + loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1)) + # Redimensionnement et perte moyenne par échantillon + loss_per_sample = loss.view(shift_logits.size(0), shift_logits.size(1)).mean(axis=1) + # Calculer et échelonner la pondération + weights = torch.stack([(inputs == kt).float() for kt in keytoken_ids]).sum( + axis=[0, 2] + ) + weights = alpha * (1.0 + weights) + # Calculer la moyenne pondérée + weighted_loss = (loss_per_sample * weights).mean() + return weighted_loss +``` + +Avant de commencer à entraîner avec cette nouvelle fonction de perte géniale, nous devons préparer quelques éléments : + +- Nous avons besoin de chargeurs de données pour charger les données par batch. +- Nous devons définir les paramètres de décroissance des poids. +- De temps en temps, nous voulons évaluer, il est donc logique d'envelopper le code d'évaluation dans une fonction. + +Commençons par les chargeurs de données. Nous avons seulement besoin de définir le format du jeu de données à `"torch"` et ensuite nous pouvons le passer à un PyTorch `DataLoader` avec la taille de batch appropriée : + +```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) +``` + +Ensuite, nous regroupons les paramètres de façon à ce que l'optimiseur sache lesquels bénéficieront d'une décroissance de poids supplémentaire. Habituellement, tous les termes de biais et les poids de la *LayerNorm* en sont exemptés. Voici comment nous pouvons le faire : + +```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}, + ] +``` + +Puisque nous voulons évaluer le modèle régulièrement sur l'ensemble de validation pendant l'entraînement, écrivons une fonction pour cela aussi. Elle passe simplement par le *dataloader* d'évaluation et rassemble toutes les pertes à travers les processus : + +```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() +``` + +Avec la fonction `evaluate()` nous pouvons rapporter la perte et la [perplexité](/course/fr/chapter7/3) à intervalles réguliers. Ensuite, nous redéfinissons notre modèle pour nous assurer que nous entraînons à nouveau à partir de zéro : + +```py +model = GPT2LMHeadModel(config) +``` + +Nous pouvons ensuite définir notre optimiseur, en utilisant la fonction précédente pour diviser les paramètres de décroissance des poids : + +```py +from torch.optim import AdamW + +optimizer = AdamW(get_grouped_params(model), lr=5e-4) +``` + +Préparons maintenant le modèle, l'optimiseur et les chargeurs de données pour pouvoir commencer l'entraînement : + +```py +from accelerate import Accelerator + +accelerator = Accelerator(fp16=True) + +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + + + +🚨 Si vous vous entraînez sur un TPU, vous devrez déplacer tout le code commençant à la cellule ci-dessus dans une fonction d'entraînement dédiée. Voir le [chapitre 3](/course/fr/chapter3) pour plus de détails. + + + +Maintenant que nous avons envoyé notre `train_dataloader` à `accelerator.prepare()`, nous pouvons utiliser sa longueur pour calculer le nombre d'étapes d'entraînement. Rappelez-vous que nous devons toujours faire cela après avoir préparé le *dataloader* car cette méthode modifiera sa longueur. Nous utilisons un programme linéaire classique du taux d'apprentissage à 0 : + +```py +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, +) +``` + +Enfin, pour pousser notre modèle vers le *Hub*, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous au *Hub*, si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'identifiant du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix. Il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `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' +``` + +Ensuite, nous pouvons cloner ce dépôt dans un dossier local. S'il existe déjà, ce dossier local doit être un clone existant du dépôt avec lequel nous travaillons : + +```py +output_dir = "codeparrot-ds-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output_dir` en appelant la méthode `repo.push_to_hub()`. Cela nous aidera à télécharger les modèles intermédiaires à la fin de chaque époque. + +Avant de nous entraîner, exécutons un test rapide pour voir si la fonction d'évaluation fonctionne correctement : + +```py +evaluate() +``` + +```python out +(10.934126853942871, 56057.14453125) +``` + +Ce sont des valeurs très élevées pour la perte et la perplexité, mais ce n'est pas surprenant puisque nous n'avons pas encore entraîné le modèle. Avec cela, nous avons tout préparé pour écrire la partie principale du script d'entraînement : la boucle d'entraînement. Dans celle-ci, nous itérons sur le chargeur de données et transmettons les batchs au modèle. Avec les logits, nous pouvons alors évaluer notre fonction de perte personnalisée. Nous mettons à l'échelle la perte par le nombre d'étapes d'accumulation du gradient afin de ne pas créer de plus grandes pertes en agrégeant plus d'étapes. Avant de procéder à l'optimisation, nous découpons également les gradients pour une meilleure convergence. Enfin, tous les quelques pas, nous évaluons le modèle sur l'ensemble d'évaluation avec notre nouvelle fonction `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=len(train_dataloader) + ): + logits = model(batch["input_ids"]).logits + loss = keytoken_weighted_loss(batch["input_ids"], logits, keytoken_ids) + if step % 100 == 0: + accelerator.print( + { + "lr": get_lr(), + "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 + ) +``` + +Et voilà, vous disposez maintenant de votre propre boucle d'entraînement personnalisée pour les modèles de langage causal tels que le GPT-2. Vous pouvez encore l'adapter à vos besoins. + + + +✏️ **Essayez !** Vous pouvez créer votre propre fonction de perte personnalisée, adaptée à votre cas d'utilisation, ou ajouter une autre étape personnalisée dans la boucle d'entraînement. + + + + + +✏️ **Essayez !** Lorsque vous effectuez de longues expériences d'entraînement, il est bon d'enregistrer les mesures importantes à l'aide d'outils tels que *TensorBoard* ou *Weights & Biases*. Ajoutez l'un d'eux à la boucle d'entraînement afin de pouvoir toujours vérifier comment se déroule l'entraînement. + + + +{/if} diff --git a/chapters/fr/chapter7/7.mdx b/chapters/fr/chapter7/7.mdx index b703523bd..ab5fd9f75 100644 --- a/chapters/fr/chapter7/7.mdx +++ b/chapters/fr/chapter7/7.mdx @@ -1,1230 +1,1230 @@ - - -# Réponse aux questions - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -Il est temps de s'intéresser à la réponse aux questions ! Cette tâche peut prendre plusieurs formes mais celle sur laquelle nous allons nous concentrer dans cette section est appelée réponse aux questions *extractives*. Il s'agit de poser des questions sur un document et d'identifier les réponses sous forme de « d'étendue de texte » dans le document lui-même. - - - -Nous allons *finetuner* un modèle BERT sur le [jeu de données SQuAD](https://rajpurkar.github.io/SQuAD-explorer/), qui consiste en des questions posées par des *crowdworkers* sur un ensemble d'articles de Wikipedia. Cela nous donnera un modèle capable de calculer des prédictions comme celui-ci : - - - - -Il s'agit d'une présentation du modèle qui a été entraîné à l'aide du code présenté dans cette section et qui a ensuité été téléchargé sur le *Hub*. Vous pouvez le trouver [ici](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) - - - -💡 Les modèles basé que sur l'encodeur comme BERT ont tendance à être excellents pour extraire les réponses à des questions factuelles comme « Qui a inventé l'architecture Transformer ? » mais ne sont pas très performants lorsqu'on leur pose des questions ouvertes comme « Pourquoi le ciel est-il bleu ? ». Dans ces cas plus difficiles, les modèles encodeurs-décodeurs comme le T5 et BART sont généralement utilisés pour synthétiser les informations d'une manière assez similaire au [résumé de texte](/course/fr/chapter7/5). Si vous êtes intéressé par ce type de réponse aux questions *génératives*, nous vous recommandons de consulter notre [démo](https://yjernite.github.io/lfqa.html) basée sur le [jeu de données ELI5](https://huggingface.co/datasets/eli5). - - - -## Préparation des données - -Le jeu de données le plus utilisé comme référence académique pour la réponse extractive aux questions est [SQuAD](https://rajpurkar.github.io/SQuAD-explorer/). C'est donc celui que nous utiliserons ici. Il existe également une version plus difficile [SQuAD v2](https://huggingface.co/datasets/squad_v2), qui comprend des questions sans réponse. Tant que votre propre jeu de données contient une colonne pour les contextes, une colonne pour les questions et une colonne pour les réponses, vous devriez être en mesure d'adapter les étapes ci-dessous. - -### Le jeu de données SQuAD - -Comme d'habitude, nous pouvons télécharger et mettre en cache le jeu de données en une seule étape grâce à `load_dataset()` : - -```py -from datasets import load_dataset - -raw_datasets = load_dataset("squad") -``` - -Nous pouvons jeter un coup d'œil à cet objet pour en savoir plus sur le jeu de données 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 - }) -}) -``` - -On dirait que nous avons tout ce dont nous avons besoin avec les champs `context`, `question` et `answers`. Affichons-les pour le premier élément de notre ensemble d'entraînement : - -```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.' -# Sur le plan architectural, l'école a un caractère catholique. Au sommet du dôme doré du bâtiment principal se trouve une statue dorée de la Vierge Marie. Immédiatement devant le bâtiment principal et face à lui, se trouve une statue en cuivre du Christ, les bras levés, avec la légende "Venite Ad Me Omnes". À côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s'agit d'une réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette Soubirous en 1858. Au bout de l'allée principale (et dans une ligne directe qui passe par 3 statues et le Dôme d'or), se trouve une statue de pierre simple et moderne de Marie'. -Question: 'To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France?' -# A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes, en France ? -Answer: {'text': ['Saint Bernadette Soubirous'], 'answer_start': [515]} -``` - -Les champs `context` et `question` sont très simples à utiliser. Le champ `answers` est un peu plus délicat car il compile un dictionnaire avec deux champs qui sont tous deux des listes. C'est le format qui sera attendu par la métrique `squad` lors de l'évaluation. Si vous utilisez vos propres données, vous n'avez pas nécessairement besoin de vous soucier de mettre les réponses dans le même format. Le champ `text` est assez évident et le champ `answer_start` contient l'indice du caractère de départ de chaque réponse dans le contexte. - -Pendant l'entraînement, il n'y a qu'une seule réponse possible. Nous pouvons vérifier cela en utilisant la méthode `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 -}) -``` - -Pour l'évaluation, cependant, il existe plusieurs réponses possibles pour chaque échantillon, qui peuvent être identiques ou différentes : - -```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]} -``` - -Nous ne nous plongerons pas dans le script d'évaluation car tout sera enveloppé pour nous par une métrique de 🤗 *Datasets*. La version courte est que certaines des questions ont plusieurs réponses possibles, et ce script va comparer une réponse prédite à toutes les réponses acceptables et prendre le meilleur score. Par exemple, si nous regardons l'échantillon de l'indice 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.' -# Le Super Bowl 50 était un match de football américain visant à déterminer le champion de la National Football League (NFL) pour la saison 2015. Les Denver Broncos, champions de la Conférence de football américain (AFC), ont battu les Carolina Panthers, champions de la Conférence nationale de football (NFC), 24 à 10, pour remporter leur troisième titre de Super Bowl. Le match s'est déroulé le 7 février 2016 au Levi\'s Stadium, dans la baie de San Francisco, à Santa Clara, en Californie. Comme il s'agissait du 50e Super Bowl, la ligue a mis l'accent sur l'" anniversaire doré " avec diverses initiatives sur le thème de l'or, ainsi qu'en suspendant temporairement la tradition de nommer chaque match du Super Bowl avec des chiffres romains (en vertu de laquelle le match aurait été appelé " Super Bowl L "), afin que le logo puisse mettre en évidence les chiffres arabes 50.'' -'Where did Super Bowl 50 take place?' -# Où a eu lieu le Super Bowl 50 ? -``` - -nous pouvons voir que la réponse peut effectivement être l'une des trois possibilités que nous avons vues précédemment. - -### Traitement des données d'entraînement - - - -Commençons par le prétraitement des données d'entraînement. La partie la plus difficile est de générer des étiquettes pour la réponse à la question, c'est-à-dire les positions de début et de fin des *tokens* correspondant à la réponse dans le contexte. - -Mais ne nous emballons pas. Tout d'abord, à l'aide d'un *tokenizer*, nous devons convertir le texte d'entrée en identifiants que le modèle peut comprendre : - -```py -from transformers import AutoTokenizer - -model_checkpoint = "bert-base-cased" -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) -``` - -Comme mentionné précédemment, nous allons *finetuner* un modèle BERT, mais vous pouvez utiliser n'importe quel autre type de modèle tant qu'il a un *tokenizer* rapide implémenté. Vous pouvez voir toutes les architectures qui sont livrées avec un *tokenizer* rapide dans [ce tableau](https://huggingface.co/transformers/#supported-frameworks), et pour vérifier que l'objet `tokenizer` que vous utilisez est bien soutenu par 🤗 *Tokenizers* vous pouvez regarder son attribut `is_fast` : - -```py -tokenizer.is_fast -``` - -```python out -True -``` - -Nous pouvons transmettre à notre *tokenizer* la question et le contexte ensemble. Il insérera correctement les *tokens* spéciaux pour former une phrase comme celle-ci : - -``` -[CLS] question [SEP] context [SEP] -``` - -Vérifions à nouveau : - -```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]' - -'[CLS] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] Architecturalement, ' -'l école a un caractère catholique. Au sommet du dôme doré du bâtiment principal se trouve une statue dorée de la Vierge ' -'Marie. Immédiatement devant le bâtiment principal et face à lui, se trouve une statue en cuivre du Christ, les bras ' -'levés avec la légende " Venite Ad Me Omnes ". A côté du bâtiment principal se trouve la basilique du Sacré ' -'Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s'agit d'une ' -'réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette ' -'Soubirous en 1858. Au bout de l'allée principale ( et en ligne directe qui passe par 3 statues ' -'et le Dôme d'or), se trouve une statue de Marie en pierre, simple et moderne. [SEP]' -``` - -Les étiquettes sont l'index des *tokens* de début et de fin de la réponse. Le modèle sera chargé de prédire dans l'entrée un logit de début et de fin par *token*, les étiquettes théoriques étant les suivantes : - -
-One-hot encoded labels for question answering. - -
- -Dans ce cas, le contexte n'est pas trop long, mais certains des exemples du jeu de données ont des contextes très longs qui dépasseront la longueur maximale que nous avons fixée (qui est de 384 dans ce cas). Comme nous l'avons vu dans le [chapitre 6](/course/fr/chapter6/4) lorsque nous avons exploré le pipeline de `question-answering`, nous allons traiter les contextes longs en créant plusieurs caractéristiques d'entraînement à partir d'un échantillon de notre jeu de données et avec une fenêtre glissante entre eux. - -Pour voir comment cela fonctionne sur notre exemple, nous pouvons limiter la longueur à 100 et utiliser une fenêtre glissante de 50 *tokens*. Pour rappel, nous utilisons : - -- `max_length` pour définir la longueur maximale (ici 100) -- `truncation="only_second"` pour tronquer le contexte (qui est en deuxième position) quand la question avec son contexte est trop longue -- `stride` pour fixer le nombre de *tokens* se chevauchant entre deux morceaux successifs (ici 50) -- `return_overflowing_tokens=True` pour indiquer au *tokenizer* que l'on veut les *tokens* qui débordent - -```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] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] Sur le plan architectural, l école a un caractère catholique. Au sommet du dôme doré du bâtiment principal se trouve une statue dorée de la Vierge Marie. Immédiatement devant le bâtiment principal et face à lui, se trouve une statue en cuivre du Christ, les bras levés, avec la légende " Venite Ad Me Omnes ". À côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la 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] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] le bâtiment principal et face à lui, une statue en cuivre du Christ aux bras levés avec la légende " Venite Ad Me Omnes ". À côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s agit d'une réplique de la grotte de Lourdes, en France, où la Vierge [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] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] A côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s agit d une réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette Soubirous en 1858. Au bout de l allée principale ( et dans une ligne directe qui relie par 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]' -'[CLS] A qui la Vierge Marie est-elle prétendument apparue en 1858 à Lourdes France ? [SEP]. Il s agit d une réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette Soubirous en 1858. Au bout de l allée principale (et dans une ligne directe qui passe par 3 statues et le Dôme d or), se trouve une simple statue de pierre moderne de Marie. [SEP]' -``` - -Comme nous pouvons le voir, notre exemple a été divisé en quatre entrées, chacune d'entre elles contenant la question et une partie du contexte. Notez que la réponse à la question (« Bernadette Soubirous ») n'apparaît que dans la troisième et la dernière entrée. Donc en traitant les longs contextes de cette façon, nous allons créer quelques exemples d'entraînement où la réponse n'est pas incluse dans le contexte. Pour ces exemples, les étiquettes seront `start_position = end_position = 0` (donc nous prédisons le *token* `[CLS]`). Nous définirons également ces étiquettes dans le cas malheureux où la réponse a été tronquée de sorte que nous n'avons que le début (ou la fin) de celle-ci. Pour les exemples où la réponse est entièrement dans le contexte, les étiquettes seront l'index du *token* où la réponse commence et l'index du *token* où la réponse se termine. - -Le jeu de données nous fournit le caractère de début de la réponse dans le contexte, et en ajoutant la longueur de la réponse, nous pouvons trouver le caractère de fin dans le contexte. Pour faire correspondre ces indices aux *tokens*, nous devrons utiliser les correspondances *offset* que nous avons étudiés au [chapitre 6](/course/fr/chapter6/4). Nous pouvons faire en sorte que notre *tokenizer* renvoie ces index en passant `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']) -``` - -Comme nous pouvons le voir, nous récupérons les identifiants d'entrée, les *tokens* de type identifiant, le masque d'attention, ainsi que la correspondance *offset* dont nous avions besoin et une clé supplémentaire, `overflow_to_sample_mapping`. La valeur correspondante nous sera utile lorsque nous tokeniserons plusieurs textes en même temps (ce que nous devrions faire pour bénéficier du fait que notre *tokenizer* est en Rust). Puisqu'un échantillon peut donner plusieurs caractéristiques, il fait correspondre chaque caractéristique à l'exemple d'où elle provient. Parce qu'ici nous avons seulement tokenisé un exemple, nous obtenons une liste de `0` : - -```py -inputs["overflow_to_sample_mapping"] -``` - -```python out -[0, 0, 0, 0] -``` - -Mais si nous tokenisons davantage d'exemples, cela deviendra plus utile : - -```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].' -``` - -Comme nous pouvons le voir, les trois premiers exemples (aux indices 2, 3 et 4 de l'ensemble d'entraînement) ont chacun donné quatre caractéristiques et le dernier exemple (à l'indice 5 de l'ensemble d'entraînement) a donné 7 caractéristiques. - -Ces informations seront utiles pour associer chaque caractéristique obtenue à son étiquette correspondante. Comme mentionné précédemment, ces étiquettes sont : - -- `(0, 0)` si la réponse n'est pas dans l'espace correspondant du contexte. -- `(start_position, end_position)` si la réponse est dans l'espace correspondant du contexte, avec `start_position` étant l'index du *token* (dans les identifiants d'entrée) au début de la réponse et `end_position` étant l'index du *token* (dans les identifiants d'entrée) où la réponse se termine. - -Pour déterminer ce qui est le cas et, le cas échéant, les positions des *tokens*, nous trouvons d'abord les indices qui commencent et finissent le contexte dans les identifiants d'entrée. Nous pourrions utiliser les *tokens* de type identifiants pour le faire, mais puisque ceux-ci n'existent pas nécessairement pour tous les modèles (DistilBERT ne les requiert pas par exemple), nous allons plutôt utiliser la méthode `sequence_ids()` du `BatchEncoding` que notre *tokenizer* retourne. - -Une fois que nous avons ces indices de *tokens*, nous regardons les *offsets* correspondants, qui sont des *tuples* de deux entiers représentant l'étendue des caractères dans le contexte original. Nous pouvons ainsi détecter si le morceau de contexte dans cette fonctionnalité commence après la réponse ou se termine avant que la réponse ne commence (dans ce cas, l'étiquette est `(0, 0)`). Si ce n'est pas le cas, nous bouclons pour trouver le premier et le dernier *token* de la réponse : - -```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) - - # Trouver le début et la fin du contexte - idx = 0 - while sequence_ids[idx] != 1: - idx += 1 - context_start = idx - while sequence_ids[idx] == 1: - idx += 1 - context_end = idx - 1 - - # Si la réponse n'est pas entièrement dans le contexte, l'étiquette est (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: - # Sinon, ce sont les positions de début et de fin du token - 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]) -``` - -Jetons un coup d'œil à quelques résultats pour vérifier que notre approche est correcte. Pour la première caractéristique, nous trouvons `(83, 85)` comme étiquettes. Comparons alors la réponse théorique avec l'étendue décodée des *tokens* de 83 à 85 (inclus) : - -```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' -``` - -Cela correspond ! Maintenant vérifions l'index 4, où nous avons mis les étiquettes à `(0, 0)`, signifiant que la réponse n'est pas dans le morceau de contexte de cette caractéristique : - -```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]' -``` - -En effet, nous ne voyons pas la réponse dans le contexte. - - - -✏️ **A votre tour !** En utilisant l'architecture XLNet, le *padding* est appliqué à gauche et la question et le contexte sont intervertis. Adaptez tout le code que nous venons de voir à l'architecture XLNet (et ajoutez `padding=True`). Soyez conscient que le token `[CLS]` peut ne pas être à la position 0 avec le *padding* appliqué. - - - -Maintenant que nous avons vu étape par étape comment prétraiter nos données d'entraînement, nous pouvons les regrouper dans une fonction que nous appliquerons à l'ensemble des données d'entraînement. Nous allons rembourrer chaque caractéristique à la longueur maximale que nous avons définie, car la plupart des contextes seront longs (et les échantillons correspondants seront divisés en plusieurs caractéristiques). Il n'y a donc pas de réel avantage à appliquer un rembourrage dynamique ici : - -```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) - - # Trouver le début et la fin du contexte - idx = 0 - while sequence_ids[idx] != 1: - idx += 1 - context_start = idx - while sequence_ids[idx] == 1: - idx += 1 - context_end = idx - 1 - - # Si la réponse n'est pas entièrement dans le contexte, l'étiquette est (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: - # Sinon, ce sont les positions de début et de fin du token - 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 -``` - -Notez que nous avons défini deux constantes pour déterminer la longueur maximale utilisée ainsi que la longueur de la fenêtre glissante, et que nous avons ajouté un petit nettoyage avant la tokénisation : certaines des questions dans SQuAD ont des espaces supplémentaires au début et à la fin qui n'ajoutent rien (et prennent de la place lors de la tokénisation si vous utilisez un modèle comme RoBERTa), donc nous avons supprimé ces espaces supplémentaires. - -Pour appliquer cette fonction à l'ensemble de l'entraînement, nous utilisons la méthode `Dataset.map()` avec le flag `batched=True`. C'est nécessaire ici car nous changeons la longueur du jeu de données (puisqu'un exemple peut donner plusieurs caractéristiques d'entraînement) : - -```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) -``` - -Comme nous pouvons le voir, le prétraitement a ajouté environ 1 000 caractéristiques. Notre ensemble d'entraînement est maintenant prêt à être utilisé. Passons au prétraitement de l'ensemble de validation ! - -### Traitement des données de validation - -Le prétraitement des données de validation sera légèrement plus facile car nous n'avons pas besoin de générer des étiquettes (sauf si nous voulons calculer une perte de validation, mais elle ne nous aidera pas vraiment à comprendre la qualité du modèle). Le réel plaisir sera d'interpréter les prédictions du modèle dans des étendues du contexte original. Pour cela, il nous suffit de stocker les correspondances d'*offset* et un moyen de faire correspondre chaque caractéristique créée à l'exemple original dont elle provient. Puisqu'il y a une colonne identifiant dans le jeu de données original, nous l'utiliserons. - -La seule chose que nous allons ajouter ici est un petit nettoyage des correspondances d'*offset*. Elles contiendront les *offsets* pour la question et le contexte, mais une fois que nous serons à la phase de post-traitement, nous n'aurons aucun moyen de savoir quelle partie des identifiants d'entrée correspondait au contexte et quelle partie était la question (la méthode `sequence_ids()` que nous avons utilisée n'est disponible que pour la sortie du *tokenizer*). Donc, nous allons mettre les *offsets* correspondant à la question à `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 -``` - -Nous pouvons appliquer cette fonction sur l'ensemble de validation comme précédemment : - -```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) -``` - -Dans ce cas, nous n'avons ajouté que quelques centaines d'échantillons, il semble donc que les contextes dans l'ensemble de validation soient un peu plus courts. - -Maintenant que nous avons prétraité toutes les données, nous pouvons passer à l'entraînement. - -{#if fw === 'pt'} - -## Finetuner le modèle avec l'API `Trainer` - -Le code d'entraînement pour cet exemple ressemblera beaucoup au code des sections précédentes mais le calcul de la métrique avec la fonction `compute_metrics()` sera un défi unique. Puisque nous avons rembourré tous les échantillons à la longueur maximale que nous avons définie, il n'y a pas de collateur de données à définir. Ainsi le calcul de la métrique est vraiment la seule chose dont nous devons nous soucier. La partie la plus difficile sera de post-traiter les prédictions du modèle en étendues de texte dans les exemples originaux. Une fois que nous aurons fait cela, la métrique de la bibliothèque 🤗 *Datasets* fera le gros du travail pour nous. - -{:else} - -## Finetuner fin du modèle avec Keras - -Le code d'entraînement de cet exemple ressemblera beaucoup au code des sections précédentes, mais le calcul de la métrique sera un défi unique. Puisque nous avons rembourré tous les échantillons à la longueur maximale que nous avons définie, il n'y a pas de collateur de données à définir. Ainsi le calcul de la métrique est vraiment la seule chose dont nous devons nous soucier. La partie la plus difficile sera de post-traiter les prédictions du modèle en étendues de texte dans les exemples originaux. Une fois que nous aurons fait cela, la métrique de la bibliothèque 🤗 *Datasets* fera le gros du travail pour nous. - -{/if} - -### Post-traitement - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -Le modèle produira des logits pour les positions de début et de fin de la réponse dans les identifiants d'entrée, comme nous l'avons vu lors de notre exploration du pipeline de `question-answering` [au chapitre 6](/course/fr/chapter6/3b). L'étape de post-traitement sera similaire à ce que nous avons fait à ce chapitre là. Voici un rapide rappel des actions que nous avons prises : - -- nous avons masqué les logits de début et de fin correspondant aux *tokens* en dehors du contexte, -- nous avons ensuite converti les logits de début et de fin en probabilités en utilisant une fonction SoftMax, -- nous avons attribué un score à chaque paire `(start_token, end_token)` en prenant le produit des deux probabilités correspondantes, -- nous avons cherché la paire avec le score maximum qui donnait une réponse valide (par exemple, un `start_token` inférieur au `end_token`). - -Ici, nous allons modifier légèrement ce processus car nous n'avons pas besoin de calculer les scores réels (juste la réponse prédite). Cela signifie que nous pouvons sauter l'étape de la SoftMax. Pour aller plus vite, nous ne donnerons pas non plus un score à toutes les paires `(start_token, end_token)` possibles, mais seulement celles correspondant aux `n_best` logits les plus élevés (avec `n_best=20`). Puisque nous sautons la SoftMax, les scores seront des scores logi, et seront obtenus en prenant la somme des logits de début et de fin (au lieu du produit, à cause de la règle \\(\log(ab) = \log(a) + \log(b)\\)). - -Pour démontrer tout cela, nous aurons besoin d'un certain type de prédictions. Puisque nous n'avons pas encore entraîné notre modèle, nous allons utiliser le modèle par défaut du pipeline de `question-answering` pour générer quelques prédictions sur une petite partie de l'ensemble de validation. Nous pouvons utiliser la même fonction de traitement que précédemment car elle repose sur la constante globale `tokenizer`, nous devons juste changer cet objet par le *tokenizer* du modèle que nous voulons utiliser temporairement : - -```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, -) -``` - -Maintenant que le prétraitement est terminé, nous changeons le *tokenizer* pour celui que nous avons choisi à l'origine : - -```python -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) -``` - -Nous supprimons ensuite les colonnes de notre `eval_set` qui ne sont pas attendues par le modèle. Nous construisons un batch avec tout de ce petit ensemble de validation et le passons au modèle. Si un GPU est disponible, nous l'utilisons pour aller plus vite : - -{#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) -``` - -Puisque `Trainer` nous donne les prédictions sous forme de tableaux NumPy, nous récupérons les logits de début et de fin et les convertissons dans ce format : - -```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) -``` - -Pour faciliter l'expérimentation, nous allons convertir ces sorties en tableaux NumPy : - -```python -start_logits = outputs.start_logits.numpy() -end_logits = outputs.end_logits.numpy() -``` - -{/if} - -Maintenant, nous devons trouver la réponse prédite pour chaque exemple dans notre `small_eval_set`. Un exemple peut avoir été divisé en plusieurs caractéristiques dans `eval_set`, donc la première étape est de faire correspondre chaque exemple dans `small_eval_set` aux caractéristiques correspondantes dans `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) -``` - -Avec cela, nous pouvons vraiment nous mettre au travail en bouclant tous les exemples et, pour chaque exemple, toutes les caractéristiques associées. Comme nous l'avons dit précédemment, nous allons regarder les scores logit pour les `n_best` logits de début et logits de fin, en excluant les positions qui donnent : - -- une réponse qui ne serait pas dans le contexte -- une réponse avec une longueur négative -- une réponse qui est trop longue (nous limitons les possibilités à `max_answer_length=30`) - -Une fois que nous avons toutes les réponses possibles notées pour un exemple, nous choisissons simplement celle qui a le meilleur score logit : - -```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: - # Ignore les réponses qui ne sont pas entièrement dans le contexte - if offsets[start_index] is None or offsets[end_index] is None: - continue - # Ignore les réponses dont la longueur est soit < 0 soit > 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"]}) -``` - -Le format final des réponses prédites est celui qui sera attendu par la métrique que nous allons utiliser. Comme d'habitude, nous pouvons la charger à l'aide de la bibliothèque 🤗 *Evaluate* : - -```python -import evaluate - -metric = evaluate.load("squad") -``` - -Cette métrique attend les réponses prédites dans le format que nous avons vu ci-dessus (une liste de dictionnaires avec une clé pour l'identifiant de l'exemple et une clé pour le texte prédit) et les réponses théoriques dans le format ci-dessous (une liste de dictionnaires avec une clé pour l'identifiant de l'exemple et une clé pour les réponses possibles) : - -```python -theoretical_answers = [ - {"id": ex["id"], "answers": ex["answers"]} for ex in small_eval_set -] -``` - -Nous pouvons maintenant vérifier que nous obtenons des résultats raisonnables en examinant le premier élément des deux listes : - -```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]}} -``` - -Pas trop mal ! Voyons maintenant le score que la métrique nous donne : - -```python -metric.compute(predictions=predicted_answers, references=theoretical_answers) -``` - -```python out -{'exact_match': 83.0, 'f1': 88.25} -``` - -Encore une fois, c'est plutôt bon si l'on considère que, d'après [le papier](https://arxiv.org/abs/1910.01108v2) de DistilBERT, *finetuné* sur SQuAD, ce modèle obtient 79,1 et 86,9 pour ces scores sur l'ensemble du jeu de données. - -{#if fw === 'pt'} - -Maintenant, mettons tout ce que nous venons de faire dans une fonction `compute_metrics()` que nous utiliserons dans le `Trainer`. Normalement, cette fonction `compute_metrics()` reçoit seulement un *tuple* `eval_preds` avec les logits et les étiquettes. Ici, nous aurons besoin d'un peu plus, car nous devons chercher dans le jeu de données des caractéristiques pour le décalage et dans le jeu de données des exemples pour les contextes originaux. Ainsi nous ne serons pas en mesure d'utiliser cette fonction pour obtenir des résultats d'évaluation standards pendant l'entraînement. Nous ne l'utiliserons qu'à la fin de l'entraînement pour vérifier les résultats. - -La fonction `compute_metrics()` regroupe les mêmes étapes que précédemment. Nous ajoutons juste une petite vérification au cas où nous ne trouverions aucune réponse valide (dans ce cas nous prédisons une chaîne vide). - -{:else} - -Maintenant, mettons tout ce que nous venons de faire dans une fonction `compute_metrics()` que nous utiliserons après avoir entraîné notre modèle. Nous aurons besoin de passer un peu plus que juste les logits de sortie, car nous devons chercher dans le jeu de données des caractéristiques pour le décalage et dans le jeu de données des exemples pour les contextes originaux : - -{/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 = [] - - # Parcourir en boucle toutes les fonctionnalités associées à cet exemple - 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: - # Ignore les réponses qui ne sont pas entièrement dans le contexte - if offsets[start_index] is None or offsets[end_index] is None: - continue - # Ignore les réponses dont la longueur est soit < 0, soit > 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) - - # Sélectionne la réponse avec le meilleur score - 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) -``` - -Nous pouvons vérifier que cela fonctionne sur nos prédictions : - -```python -compute_metrics(start_logits, end_logits, eval_set, small_eval_set) -``` - -```python out -{'exact_match': 83.0, 'f1': 88.25} -``` - -C'est bien ! Maintenant, utilisons ceci pour *finetuner* notre modèle. - -### Finetuning du modèle - -{#if fw === 'pt'} - -Nous sommes maintenant prêts à entraîner notre modèle. Créons-le en utilisant la classe `AutoModelForQuestionAnswering` comme précédemment : - -```python -model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) -``` - -{:else} - -Nous sommes maintenant prêts à entraîner notre modèle. Créons-le en utilisant la classe `TFAutoModelForQuestionAnswering` comme précédemment : - -```python -model = TFAutoModelForQuestionAnswering.from_pretrained(model_checkpoint) -``` - -{/if} - -Comme d'habitude, nous recevons un avertissement indiquant que certains poids ne sont pas utilisés (ceux de la tête de pré-entraînement) et que d'autres sont initialisés de manière aléatoire (ceux de la tête de réponse aux questions). Vous devriez être habitué à cela maintenant, mais cela signifie que ce modèle n'est pas encore prêt à être utilisé et qu'il a besoin d'être *finetuné*. Une bonne chose que nous soyons sur le point de le faire ! - -Pour pouvoir pousser notre modèle vers le *Hub*, nous devons nous connecter à Hugging Face. Si vous exécutez ce code dans un *notebook*, vous pouvez le faire avec la fonction utilitaire suivante, qui affiche un *widget* où vous pouvez entrer vos identifiants de connexion : - -```python -from huggingface_hub import notebook_login - -notebook_login() -``` - -Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : - -```bash -huggingface-cli login -``` - -{#if fw === 'pt'} - -Une fois ceci fait, nous pouvons définir nos `TrainingArguments`. Comme nous l'avons dit lorsque nous avons défini notre fonction pour calculer la métrique, nous ne serons pas en mesure d'avoir une boucle d'évaluation standard à cause de la signature de la fonction `compute_metrics()`. Nous pourrions écrire notre propre sous-classe de `Trainer` pour faire cela (une approche que vous pouvez trouver dans le [script d'exemple de réponse aux questions](https://github.com/huggingface/transformers/blob/master/examples/pytorch/question-answering/trainer_qa.py)), mais c'est un peu trop long pour cette section. A la place, nous n'évaluerons le modèle qu'à la fin de l'entraînement et nous vous montrerons comment faire une évaluation cela dans le paragraphe « Une boucle d'entraînement personnalisée » ci-dessous. - -C'est là que l'API `Trainer` montre ses limites et que la bibliothèque 🤗 *Accelerate* brille : personnaliser la classe pour un cas d'utilisation spécifique peut être pénible, mais modifier une boucle d'entraînement est facile. - -Jetons un coup d'œil à notre `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, -) -``` - -Nous avons déjà vu la plupart d'entre eux. Nous définissons quelques hyperparamètres (comme le taux d'apprentissage, le nombre d'époques d'entraînement, un taux de décroissance des poids) et nous indiquons que nous voulons sauvegarder le modèle à la fin de chaque époque, sauter l'évaluation, et télécharger nos résultats vers le *Hub*. Nous activons également l'entraînement en précision mixte avec `fp16=True`, car cela peut accélérer l'entraînement sur un GPU récent. - -{:else} - -Maintenant que c'est fait, nous pouvons créer nos jeux de données TensorFlow. Nous pouvons utiliser le simple collateur de données par défaut cette fois-ci : - -```python -from transformers import DefaultDataCollator - -data_collator = DefaultDataCollator(return_tensors="tf") -``` - -Et maintenant nous créons les jeux de données comme d'habitude. - -```python -tf_train_dataset = train_dataset.to_tf_dataset( - columns=[ - "input_ids", - "start_positions", - "end_positions", - "attention_mask", - "token_type_ids", - ], - collate_fn=data_collator, - shuffle=True, - batch_size=16, -) -tf_eval_dataset = validation_dataset.to_tf_dataset( - columns=["input_ids", "attention_mask", "token_type_ids"], - collate_fn=data_collator, - shuffle=False, - batch_size=16, -) -``` - -Ensuite, nous configurons nos hyperparamètres d'entraînement et compilons notre modèle : - -```python -from transformers import create_optimizer -from transformers.keras_callbacks import PushToHubCallback -import tensorflow as tf - -# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans le jeu de données, divisé par la taille du batch, -# puis multiplié par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un tf.data.Dataset, -# et non le jeu de données original donc son len() est déjà 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) - -# Entraîner en mixed-precision float16 -tf.keras.mixed_precision.set_global_policy("mixed_float16") -``` - -Enfin, nous sommes prêts à entraîner avec `model.fit()`. Nous utilisons un `PushToHubCallback` pour télécharger le modèle sur le *Hub* après chaque époque. - -{/if} - -Par défaut, le dépôt utilisé sera dans votre espace et nommé après le répertoire de sortie que vous avez défini. Donc dans notre cas il sera dans `"sgugger/bert-finetuned-squad"`. Nous pouvons passer outre en passant un `hub_model_id`, par exemple, pour pousser le modèle dans l'organisation `huggingface_course` nous avons utilisé `hub_model_id= "huggingface_course/bert-finetuned-squad"` (qui est le modèle que nous avons lié au début de cette section). - -{#if fw === 'pt'} - - - -💡 Si le répertoire de sortie que vous utilisez existe, il doit être un clone local du dépôt vers lequel vous voulez pousser (donc définissez un nouveau nom si vous obtenez une erreur lors de la définition de votre `Trainer`). - - - -Enfin, nous passons tout à la classe `Trainer` et lançons l'entraînement : - -```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) - -# Nous allons faire la validation après, donc pas de validation au milieu de l'entraînement. -model.fit(tf_train_dataset, callbacks=[callback], epochs=num_train_epochs) -``` - -{/if} - -Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. Ainsi, vous pourrez reprendre votre entraînement sur une autre machine si nécessaire. L'ensemble de l'entraînement prend un certain temps (un peu plus d'une heure sur une Titan RTX), vous pouvez donc prendre un café ou relire les parties du cours qui vous ont semblé plus difficiles pendant qu'il se déroule. Notez également que dès que la première époque est terminée, vous verrez des poids téléchargés sur le *Hub* et vous pourrez commencer à jouer avec votre modèle sur sa page. - -{#if fw === 'pt'} - -Une fois l'entraînement terminé, nous pouvons enfin évaluer notre modèle (et prier pour ne pas avoir dépensé tout ce temps de calcul pour rien). La méthode `predict()` du `Trainer` retournera un *tuple* où les premiers éléments seront les prédictions du modèle (ici une paire avec les logits de début et de fin). Nous envoyons ceci à notre fonction `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} - -Une fois l'entraînement terminé, nous pouvons enfin évaluer notre modèle (et prier pour ne pas avoir dépensé tout ce temps de calcul pour rien). La méthode `predict()` de notre `model` se chargera d'obtenir les prédictions, et puisque nous avons fait tout le travail difficile de définir une fonction `compute_metrics()` plus tôt, nous pouvons obtenir nos résultats en une seule ligne : - -```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} -``` - -Super ! À titre de comparaison, les scores indiqués dans l'article de BERT pour ce tâche sont de 80,8 et 88,5. Donc nous sommes exactement là où nous devrions être. - -{#if fw === 'pt'} - -Enfin, nous utilisons la méthode `push_to_hub()` pour nous assurer que nous téléchargeons la dernière version du modèle : - -```py -trainer.push_to_hub(commit_message="Training complete") -``` - -Cela renvoie l'URL du commit qu'il vient de faire, si vous voulez l'inspecter : - -```python out -'https://huggingface.co/sgugger/bert-finetuned-squad/commit/9dcee1fbc25946a6ed4bb32efb1bd71d5fa90b68' -``` - -Le `Trainer` rédige également une carte de modèle avec tous les résultats de l'évaluation et la télécharge. - -{/if} - -À ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* du modèle pour tester le modèle et le partager avec vos amis, votre famille et vos animaux préférés. Vous avez réussi à *finetuner* un modèle sur une tâche de réponse à une question. Félicitations ! - - - -✏️ **A votre tour** Essayez un autre modèle pour voir s'il est plus performant pour cette tâche ! - - - -{#if fw === 'pt'} - -Si vous voulez plonger un peu plus profondément dans la boucle d'entraînement, nous allons maintenant vous montrer comment faire la même chose en utilisant 🤗 *Accelerate*. - -## Une boucle d'entraînement personnalisée - -Jetons maintenant un coup d'œil à la boucle d'entraînement complète, afin que vous puissiez facilement personnaliser les parties dont vous avez besoin. Elle ressemblera beaucoup à la boucle d'entraînement du [chapitre 3](/course/fr/chapter3/4), à l'exception de la boucle d'évaluation. Nous serons en mesure d'évaluer le modèle régulièrement puisque nous ne sommes plus contraints par la classe `Trainer`. - -### Préparer tout pour l'entraînement - -Tout d'abord, nous devons construire le `DataLoader`s à partir de nos jeux de données. Nous définissons le format de ces jeux de données à `"torch"` et supprimons les colonnes dans le jeu de validation qui ne sont pas utilisées par le modèle. Ensuite, nous pouvons utiliser le `default_data_collator` fourni par 🤗 *Transformers* comme `collate_fn` et mélanger l'ensemble d'entraînement mais pas celui de validation : - -```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 -) -``` - -Ensuite, nous réinstantifions notre modèle afin de nous assurer que nous ne poursuivons pas le *finetuning* précédent et que nous repartons du modèle BERT pré-entraîné : - -```py -model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) -``` - -Ensuite, nous aurons besoin d'un optimiseur. Comme d'habitude, nous utilisons le classique `AdamW`, qui est comme Adam mais avec une correction dans la façon dont le taux de décroissance des poids est appliqué : - -```py -from torch.optim import AdamW - -optimizer = AdamW(model.parameters(), lr=2e-5) -``` - -Une fois que nous avons tous ces objets, nous pouvons les envoyer à la méthode `accelerator.prepare()`. Rappelez-vous que si vous voulez entraîner sur des TPUs dans un *notebook* Colab, vous devrez déplacer tout ce code dans une fonction d'entraînement, et qui ne devrait pas exécuter une cellule qui instancie un `Accelerator`. Nous pouvons forcer l'entraînement en précision mixte en passant l'argument `fp16=True` à `Accelerator` (ou, si vous exécutez le code comme un script, assurez-vous de remplir la 🤗 *Accelerate* `config` de manière appropriée). - -```py -from accelerate import Accelerator - -accelerator = Accelerator(fp16=True) -model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( - model, optimizer, train_dataloader, eval_dataloader -) -``` - -Comme vous devez le savoir depuis les sections précédentes, nous ne pouvons utiliser la longueur de `train_dataloader` pour calculer le nombre d'étapes d'entraînement qu'après qu'il soit passé par la méthode `accelerator.prepare()`. Nous utilisons le même programme linéaire que dans les sections précédentes : - -```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, -) -``` - -Pour pousser notre modèle vers le *Hub*, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous au *Hub*, si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'identifiant du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix. Il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `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' -``` - -Ensuite, nous pouvons cloner ce dépôt dans un dossier local. S'il existe déjà, ce dossier local doit être un clone du dépôt avec lequel nous travaillons : - -```py -output_dir = "bert-finetuned-squad-accelerate" -repo = Repository(output_dir, clone_from=repo_name) -``` - -Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output_dir` en appelant la méthode `repo.push_to_hub()`. Cela nous aidera à télécharger les modèles intermédiaires à la fin de chaque époque. - -## Boucle d'entraînement - -Nous sommes maintenant prêts à écrire la boucle d'entraînement complète. Après avoir défini une barre de progression pour suivre l'évolution de l'entraînement, la boucle comporte trois parties : - -- l'entraînement à proprement dit, qui est l'itération classique sur le `train_dataloader`, passage en avant du modèle, puis passage en arrière et étape d'optimisation. -- l'évaluation, dans laquelle nous rassemblons toutes les valeurs pour `start_logits` et `end_logits` avant de les convertir en tableaux NumPy. Une fois la boucle d'évaluation terminée, nous concaténons tous les résultats. Notez que nous devons tronquer car `Accelerator` peut avoir ajouté quelques échantillons à la fin pour s'assurer que nous avons le même nombre d'exemples dans chaque processus. -- sauvegarde et téléchargement, où nous sauvegardons d'abord le modèle et le *tokenizer*, puis appelons `repo.push_to_hub()`. Comme nous l'avons fait auparavant, nous utilisons l'argument `blocking=False` pour dire à la bibliothèque 🤗 *Hub* de pousser dans un processus asynchrone. De cette façon, l'entraînement continue normalement et cette (longue) instruction est exécutée en arrière-plan. - -Voici le code complet de la boucle d'entraînement : - -```py -from tqdm.auto import tqdm -import torch - -progress_bar = tqdm(range(num_training_steps)) - -for epoch in range(num_train_epochs): - # Entraînement - model.train() - for step, batch in enumerate(train_dataloader): - outputs = model(**batch) - loss = outputs.loss - accelerator.backward(loss) - - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - progress_bar.update(1) - - # Evaluation - model.eval() - 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) - - # Sauvegarder et télécharger - 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 - ) -``` - -Au cas où ce serait la première fois que vous verriez un modèle enregistré avec 🤗 *Accelerate*, prenons un moment pour inspecter les trois lignes de code qui l'accompagnent : - -```py -accelerator.wait_for_everyone() -unwrapped_model = accelerator.unwrap_model(model) -unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) -``` - -La première ligne est explicite : elle indique à tous les processus d'attendre que tout le monde soit à ce stade avant de continuer. C'est pour s'assurer que nous avons le même modèle dans chaque processus avant de sauvegarder. Ensuite, nous prenons le `unwrapped_model`, qui est le modèle de base que nous avons défini. La méthode `accelerator.prepare()` modifie le modèle pour qu'il fonctionne dans l'entraînement distribué. Donc il n'aura plus la méthode `save_pretrained()` car la méthode `accelerator.unwrap_model()` annule cette étape. Enfin, nous appelons `save_pretrained()` mais nous disons à cette méthode d'utiliser `accelerator.save()` au lieu de `torch.save()`. - -Une fois ceci fait, vous devriez avoir un modèle qui produit des résultats assez similaires à celui entraîné avec `Trainer`. Vous pouvez vérifier le modèle que nous avons entraîné en utilisant ce code à [*huggingface-course/bert-finetuned-squad-accelerate*](https://huggingface.co/huggingface-course/bert-finetuned-squad-accelerate). Et si vous voulez tester des modifications de la boucle d'entraînement, vous pouvez les implémenter directement en modifiant le code ci-dessus ! - -{/if} - -### Utilisation du modèle finetuné - -Nous vous avons déjà montré comment vous pouvez utiliser le modèle que nous avons *finetuné* sur le *Hub* avec le *widget* d'inférence. Pour l'utiliser localement dans un `pipeline`, il suffit de spécifier l'identifiant du modèle : - -```py -from transformers import pipeline - -# Remplacez par votre propre checkpoint -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'} -``` - -Super ! Notre modèle fonctionne aussi bien que le modèle par défaut pour ce pipeline ! + + +# Réponse aux questions + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Il est temps de s'intéresser à la réponse aux questions ! Cette tâche peut prendre plusieurs formes mais celle sur laquelle nous allons nous concentrer dans cette section est appelée réponse aux questions *extractives*. Il s'agit de poser des questions sur un document et d'identifier les réponses sous forme de « d'étendue de texte » dans le document lui-même. + + + +Nous allons *finetuner* un modèle BERT sur le [jeu de données SQuAD](https://rajpurkar.github.io/SQuAD-explorer/), qui consiste en des questions posées par des *crowdworkers* sur un ensemble d'articles de Wikipedia. Cela nous donnera un modèle capable de calculer des prédictions comme celui-ci : + + + + +Il s'agit d'une présentation du modèle qui a été entraîné à l'aide du code présenté dans cette section et qui a ensuité été téléchargé sur le *Hub*. Vous pouvez le trouver [ici](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) + + + +💡 Les modèles basé que sur l'encodeur comme BERT ont tendance à être excellents pour extraire les réponses à des questions factuelles comme « Qui a inventé l'architecture Transformer ? » mais ne sont pas très performants lorsqu'on leur pose des questions ouvertes comme « Pourquoi le ciel est-il bleu ? ». Dans ces cas plus difficiles, les modèles encodeurs-décodeurs comme le T5 et BART sont généralement utilisés pour synthétiser les informations d'une manière assez similaire au [résumé de texte](/course/fr/chapter7/5). Si vous êtes intéressé par ce type de réponse aux questions *génératives*, nous vous recommandons de consulter notre [démo](https://yjernite.github.io/lfqa.html) basée sur le [jeu de données ELI5](https://huggingface.co/datasets/eli5). + + + +## Préparation des données + +Le jeu de données le plus utilisé comme référence académique pour la réponse extractive aux questions est [SQuAD](https://rajpurkar.github.io/SQuAD-explorer/). C'est donc celui que nous utiliserons ici. Il existe également une version plus difficile [SQuAD v2](https://huggingface.co/datasets/squad_v2), qui comprend des questions sans réponse. Tant que votre propre jeu de données contient une colonne pour les contextes, une colonne pour les questions et une colonne pour les réponses, vous devriez être en mesure d'adapter les étapes ci-dessous. + +### Le jeu de données SQuAD + +Comme d'habitude, nous pouvons télécharger et mettre en cache le jeu de données en une seule étape grâce à `load_dataset()` : + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("squad") +``` + +Nous pouvons jeter un coup d'œil à cet objet pour en savoir plus sur le jeu de données 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 + }) +}) +``` + +On dirait que nous avons tout ce dont nous avons besoin avec les champs `context`, `question` et `answers`. Affichons-les pour le premier élément de notre ensemble d'entraînement : + +```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.' +# Sur le plan architectural, l'école a un caractère catholique. Au sommet du dôme doré du bâtiment principal se trouve une statue dorée de la Vierge Marie. Immédiatement devant le bâtiment principal et face à lui, se trouve une statue en cuivre du Christ, les bras levés, avec la légende "Venite Ad Me Omnes". À côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s'agit d'une réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette Soubirous en 1858. Au bout de l'allée principale (et dans une ligne directe qui passe par 3 statues et le Dôme d'or), se trouve une statue de pierre simple et moderne de Marie'. +Question: 'To whom did the Virgin Mary allegedly appear in 1858 in Lourdes France?' +# A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes, en France ? +Answer: {'text': ['Saint Bernadette Soubirous'], 'answer_start': [515]} +``` + +Les champs `context` et `question` sont très simples à utiliser. Le champ `answers` est un peu plus délicat car il compile un dictionnaire avec deux champs qui sont tous deux des listes. C'est le format qui sera attendu par la métrique `squad` lors de l'évaluation. Si vous utilisez vos propres données, vous n'avez pas nécessairement besoin de vous soucier de mettre les réponses dans le même format. Le champ `text` est assez évident et le champ `answer_start` contient l'indice du caractère de départ de chaque réponse dans le contexte. + +Pendant l'entraînement, il n'y a qu'une seule réponse possible. Nous pouvons vérifier cela en utilisant la méthode `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 +}) +``` + +Pour l'évaluation, cependant, il existe plusieurs réponses possibles pour chaque échantillon, qui peuvent être identiques ou différentes : + +```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]} +``` + +Nous ne nous plongerons pas dans le script d'évaluation car tout sera enveloppé pour nous par une métrique de 🤗 *Datasets*. La version courte est que certaines des questions ont plusieurs réponses possibles, et ce script va comparer une réponse prédite à toutes les réponses acceptables et prendre le meilleur score. Par exemple, si nous regardons l'échantillon de l'indice 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.' +# Le Super Bowl 50 était un match de football américain visant à déterminer le champion de la National Football League (NFL) pour la saison 2015. Les Denver Broncos, champions de la Conférence de football américain (AFC), ont battu les Carolina Panthers, champions de la Conférence nationale de football (NFC), 24 à 10, pour remporter leur troisième titre de Super Bowl. Le match s'est déroulé le 7 février 2016 au Levi\'s Stadium, dans la baie de San Francisco, à Santa Clara, en Californie. Comme il s'agissait du 50e Super Bowl, la ligue a mis l'accent sur l'" anniversaire doré " avec diverses initiatives sur le thème de l'or, ainsi qu'en suspendant temporairement la tradition de nommer chaque match du Super Bowl avec des chiffres romains (en vertu de laquelle le match aurait été appelé " Super Bowl L "), afin que le logo puisse mettre en évidence les chiffres arabes 50.'' +'Where did Super Bowl 50 take place?' +# Où a eu lieu le Super Bowl 50 ? +``` + +nous pouvons voir que la réponse peut effectivement être l'une des trois possibilités que nous avons vues précédemment. + +### Traitement des données d'entraînement + + + +Commençons par le prétraitement des données d'entraînement. La partie la plus difficile est de générer des étiquettes pour la réponse à la question, c'est-à-dire les positions de début et de fin des *tokens* correspondant à la réponse dans le contexte. + +Mais ne nous emballons pas. Tout d'abord, à l'aide d'un *tokenizer*, nous devons convertir le texte d'entrée en identifiants que le modèle peut comprendre : + +```py +from transformers import AutoTokenizer + +model_checkpoint = "bert-base-cased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +Comme mentionné précédemment, nous allons *finetuner* un modèle BERT, mais vous pouvez utiliser n'importe quel autre type de modèle tant qu'il a un *tokenizer* rapide implémenté. Vous pouvez voir toutes les architectures qui sont livrées avec un *tokenizer* rapide dans [ce tableau](https://huggingface.co/transformers/#supported-frameworks), et pour vérifier que l'objet `tokenizer` que vous utilisez est bien soutenu par 🤗 *Tokenizers* vous pouvez regarder son attribut `is_fast` : + +```py +tokenizer.is_fast +``` + +```python out +True +``` + +Nous pouvons transmettre à notre *tokenizer* la question et le contexte ensemble. Il insérera correctement les *tokens* spéciaux pour former une phrase comme celle-ci : + +``` +[CLS] question [SEP] context [SEP] +``` + +Vérifions à nouveau : + +```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]' + +'[CLS] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] Architecturalement, ' +'l école a un caractère catholique. Au sommet du dôme doré du bâtiment principal se trouve une statue dorée de la Vierge ' +'Marie. Immédiatement devant le bâtiment principal et face à lui, se trouve une statue en cuivre du Christ, les bras ' +'levés avec la légende " Venite Ad Me Omnes ". A côté du bâtiment principal se trouve la basilique du Sacré ' +'Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s'agit d'une ' +'réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette ' +'Soubirous en 1858. Au bout de l'allée principale ( et en ligne directe qui passe par 3 statues ' +'et le Dôme d'or), se trouve une statue de Marie en pierre, simple et moderne. [SEP]' +``` + +Les étiquettes sont l'index des *tokens* de début et de fin de la réponse. Le modèle sera chargé de prédire dans l'entrée un logit de début et de fin par *token*, les étiquettes théoriques étant les suivantes : + +
+One-hot encoded labels for question answering. + +
+ +Dans ce cas, le contexte n'est pas trop long, mais certains des exemples du jeu de données ont des contextes très longs qui dépasseront la longueur maximale que nous avons fixée (qui est de 384 dans ce cas). Comme nous l'avons vu dans le [chapitre 6](/course/fr/chapter6/4) lorsque nous avons exploré le pipeline de `question-answering`, nous allons traiter les contextes longs en créant plusieurs caractéristiques d'entraînement à partir d'un échantillon de notre jeu de données et avec une fenêtre glissante entre eux. + +Pour voir comment cela fonctionne sur notre exemple, nous pouvons limiter la longueur à 100 et utiliser une fenêtre glissante de 50 *tokens*. Pour rappel, nous utilisons : + +- `max_length` pour définir la longueur maximale (ici 100) +- `truncation="only_second"` pour tronquer le contexte (qui est en deuxième position) quand la question avec son contexte est trop longue +- `stride` pour fixer le nombre de *tokens* se chevauchant entre deux morceaux successifs (ici 50) +- `return_overflowing_tokens=True` pour indiquer au *tokenizer* que l'on veut les *tokens* qui débordent + +```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] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] Sur le plan architectural, l école a un caractère catholique. Au sommet du dôme doré du bâtiment principal se trouve une statue dorée de la Vierge Marie. Immédiatement devant le bâtiment principal et face à lui, se trouve une statue en cuivre du Christ, les bras levés, avec la légende " Venite Ad Me Omnes ". À côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la 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] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] le bâtiment principal et face à lui, une statue en cuivre du Christ aux bras levés avec la légende " Venite Ad Me Omnes ". À côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s agit d'une réplique de la grotte de Lourdes, en France, où la Vierge [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] A qui la Vierge Marie serait-elle apparue en 1858 à Lourdes en France ? [SEP] A côté du bâtiment principal se trouve la basilique du Sacré-Cœur. Immédiatement derrière la basilique se trouve la Grotte, un lieu marial de prière et de réflexion. Il s agit d une réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette Soubirous en 1858. Au bout de l allée principale ( et dans une ligne directe qui relie par 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]' +'[CLS] A qui la Vierge Marie est-elle prétendument apparue en 1858 à Lourdes France ? [SEP]. Il s agit d une réplique de la grotte de Lourdes, en France, où la Vierge Marie serait apparue à Sainte Bernadette Soubirous en 1858. Au bout de l allée principale (et dans une ligne directe qui passe par 3 statues et le Dôme d or), se trouve une simple statue de pierre moderne de Marie. [SEP]' +``` + +Comme nous pouvons le voir, notre exemple a été divisé en quatre entrées, chacune d'entre elles contenant la question et une partie du contexte. Notez que la réponse à la question (« Bernadette Soubirous ») n'apparaît que dans la troisième et la dernière entrée. Donc en traitant les longs contextes de cette façon, nous allons créer quelques exemples d'entraînement où la réponse n'est pas incluse dans le contexte. Pour ces exemples, les étiquettes seront `start_position = end_position = 0` (donc nous prédisons le *token* `[CLS]`). Nous définirons également ces étiquettes dans le cas malheureux où la réponse a été tronquée de sorte que nous n'avons que le début (ou la fin) de celle-ci. Pour les exemples où la réponse est entièrement dans le contexte, les étiquettes seront l'index du *token* où la réponse commence et l'index du *token* où la réponse se termine. + +Le jeu de données nous fournit le caractère de début de la réponse dans le contexte, et en ajoutant la longueur de la réponse, nous pouvons trouver le caractère de fin dans le contexte. Pour faire correspondre ces indices aux *tokens*, nous devrons utiliser les correspondances *offset* que nous avons étudiés au [chapitre 6](/course/fr/chapter6/4). Nous pouvons faire en sorte que notre *tokenizer* renvoie ces index en passant `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']) +``` + +Comme nous pouvons le voir, nous récupérons les identifiants d'entrée, les *tokens* de type identifiant, le masque d'attention, ainsi que la correspondance *offset* dont nous avions besoin et une clé supplémentaire, `overflow_to_sample_mapping`. La valeur correspondante nous sera utile lorsque nous tokeniserons plusieurs textes en même temps (ce que nous devrions faire pour bénéficier du fait que notre *tokenizer* est en Rust). Puisqu'un échantillon peut donner plusieurs caractéristiques, il fait correspondre chaque caractéristique à l'exemple d'où elle provient. Parce qu'ici nous avons seulement tokenisé un exemple, nous obtenons une liste de `0` : + +```py +inputs["overflow_to_sample_mapping"] +``` + +```python out +[0, 0, 0, 0] +``` + +Mais si nous tokenisons davantage d'exemples, cela deviendra plus utile : + +```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].' +``` + +Comme nous pouvons le voir, les trois premiers exemples (aux indices 2, 3 et 4 de l'ensemble d'entraînement) ont chacun donné quatre caractéristiques et le dernier exemple (à l'indice 5 de l'ensemble d'entraînement) a donné 7 caractéristiques. + +Ces informations seront utiles pour associer chaque caractéristique obtenue à son étiquette correspondante. Comme mentionné précédemment, ces étiquettes sont : + +- `(0, 0)` si la réponse n'est pas dans l'espace correspondant du contexte. +- `(start_position, end_position)` si la réponse est dans l'espace correspondant du contexte, avec `start_position` étant l'index du *token* (dans les identifiants d'entrée) au début de la réponse et `end_position` étant l'index du *token* (dans les identifiants d'entrée) où la réponse se termine. + +Pour déterminer ce qui est le cas et, le cas échéant, les positions des *tokens*, nous trouvons d'abord les indices qui commencent et finissent le contexte dans les identifiants d'entrée. Nous pourrions utiliser les *tokens* de type identifiants pour le faire, mais puisque ceux-ci n'existent pas nécessairement pour tous les modèles (DistilBERT ne les requiert pas par exemple), nous allons plutôt utiliser la méthode `sequence_ids()` du `BatchEncoding` que notre *tokenizer* retourne. + +Une fois que nous avons ces indices de *tokens*, nous regardons les *offsets* correspondants, qui sont des *tuples* de deux entiers représentant l'étendue des caractères dans le contexte original. Nous pouvons ainsi détecter si le morceau de contexte dans cette fonctionnalité commence après la réponse ou se termine avant que la réponse ne commence (dans ce cas, l'étiquette est `(0, 0)`). Si ce n'est pas le cas, nous bouclons pour trouver le premier et le dernier *token* de la réponse : + +```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) + + # Trouver le début et la fin du contexte + idx = 0 + while sequence_ids[idx] != 1: + idx += 1 + context_start = idx + while sequence_ids[idx] == 1: + idx += 1 + context_end = idx - 1 + + # Si la réponse n'est pas entièrement dans le contexte, l'étiquette est (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: + # Sinon, ce sont les positions de début et de fin du token + 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]) +``` + +Jetons un coup d'œil à quelques résultats pour vérifier que notre approche est correcte. Pour la première caractéristique, nous trouvons `(83, 85)` comme étiquettes. Comparons alors la réponse théorique avec l'étendue décodée des *tokens* de 83 à 85 (inclus) : + +```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' +``` + +Cela correspond ! Maintenant vérifions l'index 4, où nous avons mis les étiquettes à `(0, 0)`, signifiant que la réponse n'est pas dans le morceau de contexte de cette caractéristique : + +```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]' +``` + +En effet, nous ne voyons pas la réponse dans le contexte. + + + +✏️ **A votre tour !** En utilisant l'architecture XLNet, le *padding* est appliqué à gauche et la question et le contexte sont intervertis. Adaptez tout le code que nous venons de voir à l'architecture XLNet (et ajoutez `padding=True`). Soyez conscient que le token `[CLS]` peut ne pas être à la position 0 avec le *padding* appliqué. + + + +Maintenant que nous avons vu étape par étape comment prétraiter nos données d'entraînement, nous pouvons les regrouper dans une fonction que nous appliquerons à l'ensemble des données d'entraînement. Nous allons rembourrer chaque caractéristique à la longueur maximale que nous avons définie, car la plupart des contextes seront longs (et les échantillons correspondants seront divisés en plusieurs caractéristiques). Il n'y a donc pas de réel avantage à appliquer un rembourrage dynamique ici : + +```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) + + # Trouver le début et la fin du contexte + idx = 0 + while sequence_ids[idx] != 1: + idx += 1 + context_start = idx + while sequence_ids[idx] == 1: + idx += 1 + context_end = idx - 1 + + # Si la réponse n'est pas entièrement dans le contexte, l'étiquette est (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: + # Sinon, ce sont les positions de début et de fin du token + 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 +``` + +Notez que nous avons défini deux constantes pour déterminer la longueur maximale utilisée ainsi que la longueur de la fenêtre glissante, et que nous avons ajouté un petit nettoyage avant la tokénisation : certaines des questions dans SQuAD ont des espaces supplémentaires au début et à la fin qui n'ajoutent rien (et prennent de la place lors de la tokénisation si vous utilisez un modèle comme RoBERTa), donc nous avons supprimé ces espaces supplémentaires. + +Pour appliquer cette fonction à l'ensemble de l'entraînement, nous utilisons la méthode `Dataset.map()` avec le flag `batched=True`. C'est nécessaire ici car nous changeons la longueur du jeu de données (puisqu'un exemple peut donner plusieurs caractéristiques d'entraînement) : + +```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) +``` + +Comme nous pouvons le voir, le prétraitement a ajouté environ 1 000 caractéristiques. Notre ensemble d'entraînement est maintenant prêt à être utilisé. Passons au prétraitement de l'ensemble de validation ! + +### Traitement des données de validation + +Le prétraitement des données de validation sera légèrement plus facile car nous n'avons pas besoin de générer des étiquettes (sauf si nous voulons calculer une perte de validation, mais elle ne nous aidera pas vraiment à comprendre la qualité du modèle). Le réel plaisir sera d'interpréter les prédictions du modèle dans des étendues du contexte original. Pour cela, il nous suffit de stocker les correspondances d'*offset* et un moyen de faire correspondre chaque caractéristique créée à l'exemple original dont elle provient. Puisqu'il y a une colonne identifiant dans le jeu de données original, nous l'utiliserons. + +La seule chose que nous allons ajouter ici est un petit nettoyage des correspondances d'*offset*. Elles contiendront les *offsets* pour la question et le contexte, mais une fois que nous serons à la phase de post-traitement, nous n'aurons aucun moyen de savoir quelle partie des identifiants d'entrée correspondait au contexte et quelle partie était la question (la méthode `sequence_ids()` que nous avons utilisée n'est disponible que pour la sortie du *tokenizer*). Donc, nous allons mettre les *offsets* correspondant à la question à `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 +``` + +Nous pouvons appliquer cette fonction sur l'ensemble de validation comme précédemment : + +```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) +``` + +Dans ce cas, nous n'avons ajouté que quelques centaines d'échantillons, il semble donc que les contextes dans l'ensemble de validation soient un peu plus courts. + +Maintenant que nous avons prétraité toutes les données, nous pouvons passer à l'entraînement. + +{#if fw === 'pt'} + +## Finetuner le modèle avec l'API `Trainer` + +Le code d'entraînement pour cet exemple ressemblera beaucoup au code des sections précédentes mais le calcul de la métrique avec la fonction `compute_metrics()` sera un défi unique. Puisque nous avons rembourré tous les échantillons à la longueur maximale que nous avons définie, il n'y a pas de collateur de données à définir. Ainsi le calcul de la métrique est vraiment la seule chose dont nous devons nous soucier. La partie la plus difficile sera de post-traiter les prédictions du modèle en étendues de texte dans les exemples originaux. Une fois que nous aurons fait cela, la métrique de la bibliothèque 🤗 *Datasets* fera le gros du travail pour nous. + +{:else} + +## Finetuner fin du modèle avec Keras + +Le code d'entraînement de cet exemple ressemblera beaucoup au code des sections précédentes, mais le calcul de la métrique sera un défi unique. Puisque nous avons rembourré tous les échantillons à la longueur maximale que nous avons définie, il n'y a pas de collateur de données à définir. Ainsi le calcul de la métrique est vraiment la seule chose dont nous devons nous soucier. La partie la plus difficile sera de post-traiter les prédictions du modèle en étendues de texte dans les exemples originaux. Une fois que nous aurons fait cela, la métrique de la bibliothèque 🤗 *Datasets* fera le gros du travail pour nous. + +{/if} + +### Post-traitement + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Le modèle produira des logits pour les positions de début et de fin de la réponse dans les identifiants d'entrée, comme nous l'avons vu lors de notre exploration du pipeline de `question-answering` [au chapitre 6](/course/fr/chapter6/3b). L'étape de post-traitement sera similaire à ce que nous avons fait à ce chapitre là. Voici un rapide rappel des actions que nous avons prises : + +- nous avons masqué les logits de début et de fin correspondant aux *tokens* en dehors du contexte, +- nous avons ensuite converti les logits de début et de fin en probabilités en utilisant une fonction SoftMax, +- nous avons attribué un score à chaque paire `(start_token, end_token)` en prenant le produit des deux probabilités correspondantes, +- nous avons cherché la paire avec le score maximum qui donnait une réponse valide (par exemple, un `start_token` inférieur au `end_token`). + +Ici, nous allons modifier légèrement ce processus car nous n'avons pas besoin de calculer les scores réels (juste la réponse prédite). Cela signifie que nous pouvons sauter l'étape de la SoftMax. Pour aller plus vite, nous ne donnerons pas non plus un score à toutes les paires `(start_token, end_token)` possibles, mais seulement celles correspondant aux `n_best` logits les plus élevés (avec `n_best=20`). Puisque nous sautons la SoftMax, les scores seront des scores logi, et seront obtenus en prenant la somme des logits de début et de fin (au lieu du produit, à cause de la règle \\(\log(ab) = \log(a) + \log(b)\\)). + +Pour démontrer tout cela, nous aurons besoin d'un certain type de prédictions. Puisque nous n'avons pas encore entraîné notre modèle, nous allons utiliser le modèle par défaut du pipeline de `question-answering` pour générer quelques prédictions sur une petite partie de l'ensemble de validation. Nous pouvons utiliser la même fonction de traitement que précédemment car elle repose sur la constante globale `tokenizer`, nous devons juste changer cet objet par le *tokenizer* du modèle que nous voulons utiliser temporairement : + +```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, +) +``` + +Maintenant que le prétraitement est terminé, nous changeons le *tokenizer* pour celui que nous avons choisi à l'origine : + +```python +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +Nous supprimons ensuite les colonnes de notre `eval_set` qui ne sont pas attendues par le modèle. Nous construisons un batch avec tout de ce petit ensemble de validation et le passons au modèle. Si un GPU est disponible, nous l'utilisons pour aller plus vite : + +{#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) +``` + +Puisque `Trainer` nous donne les prédictions sous forme de tableaux NumPy, nous récupérons les logits de début et de fin et les convertissons dans ce format : + +```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) +``` + +Pour faciliter l'expérimentation, nous allons convertir ces sorties en tableaux NumPy : + +```python +start_logits = outputs.start_logits.numpy() +end_logits = outputs.end_logits.numpy() +``` + +{/if} + +Maintenant, nous devons trouver la réponse prédite pour chaque exemple dans notre `small_eval_set`. Un exemple peut avoir été divisé en plusieurs caractéristiques dans `eval_set`, donc la première étape est de faire correspondre chaque exemple dans `small_eval_set` aux caractéristiques correspondantes dans `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) +``` + +Avec cela, nous pouvons vraiment nous mettre au travail en bouclant tous les exemples et, pour chaque exemple, toutes les caractéristiques associées. Comme nous l'avons dit précédemment, nous allons regarder les scores logit pour les `n_best` logits de début et logits de fin, en excluant les positions qui donnent : + +- une réponse qui ne serait pas dans le contexte +- une réponse avec une longueur négative +- une réponse qui est trop longue (nous limitons les possibilités à `max_answer_length=30`) + +Une fois que nous avons toutes les réponses possibles notées pour un exemple, nous choisissons simplement celle qui a le meilleur score logit : + +```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: + # Ignore les réponses qui ne sont pas entièrement dans le contexte + if offsets[start_index] is None or offsets[end_index] is None: + continue + # Ignore les réponses dont la longueur est soit < 0 soit > 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"]}) +``` + +Le format final des réponses prédites est celui qui sera attendu par la métrique que nous allons utiliser. Comme d'habitude, nous pouvons la charger à l'aide de la bibliothèque 🤗 *Evaluate* : + +```python +import evaluate + +metric = evaluate.load("squad") +``` + +Cette métrique attend les réponses prédites dans le format que nous avons vu ci-dessus (une liste de dictionnaires avec une clé pour l'identifiant de l'exemple et une clé pour le texte prédit) et les réponses théoriques dans le format ci-dessous (une liste de dictionnaires avec une clé pour l'identifiant de l'exemple et une clé pour les réponses possibles) : + +```python +theoretical_answers = [ + {"id": ex["id"], "answers": ex["answers"]} for ex in small_eval_set +] +``` + +Nous pouvons maintenant vérifier que nous obtenons des résultats raisonnables en examinant le premier élément des deux listes : + +```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]}} +``` + +Pas trop mal ! Voyons maintenant le score que la métrique nous donne : + +```python +metric.compute(predictions=predicted_answers, references=theoretical_answers) +``` + +```python out +{'exact_match': 83.0, 'f1': 88.25} +``` + +Encore une fois, c'est plutôt bon si l'on considère que, d'après [le papier](https://arxiv.org/abs/1910.01108v2) de DistilBERT, *finetuné* sur SQuAD, ce modèle obtient 79,1 et 86,9 pour ces scores sur l'ensemble du jeu de données. + +{#if fw === 'pt'} + +Maintenant, mettons tout ce que nous venons de faire dans une fonction `compute_metrics()` que nous utiliserons dans le `Trainer`. Normalement, cette fonction `compute_metrics()` reçoit seulement un *tuple* `eval_preds` avec les logits et les étiquettes. Ici, nous aurons besoin d'un peu plus, car nous devons chercher dans le jeu de données des caractéristiques pour le décalage et dans le jeu de données des exemples pour les contextes originaux. Ainsi nous ne serons pas en mesure d'utiliser cette fonction pour obtenir des résultats d'évaluation standards pendant l'entraînement. Nous ne l'utiliserons qu'à la fin de l'entraînement pour vérifier les résultats. + +La fonction `compute_metrics()` regroupe les mêmes étapes que précédemment. Nous ajoutons juste une petite vérification au cas où nous ne trouverions aucune réponse valide (dans ce cas nous prédisons une chaîne vide). + +{:else} + +Maintenant, mettons tout ce que nous venons de faire dans une fonction `compute_metrics()` que nous utiliserons après avoir entraîné notre modèle. Nous aurons besoin de passer un peu plus que juste les logits de sortie, car nous devons chercher dans le jeu de données des caractéristiques pour le décalage et dans le jeu de données des exemples pour les contextes originaux : + +{/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 = [] + + # Parcourir en boucle toutes les fonctionnalités associées à cet exemple + 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: + # Ignore les réponses qui ne sont pas entièrement dans le contexte + if offsets[start_index] is None or offsets[end_index] is None: + continue + # Ignore les réponses dont la longueur est soit < 0, soit > 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) + + # Sélectionne la réponse avec le meilleur score + 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) +``` + +Nous pouvons vérifier que cela fonctionne sur nos prédictions : + +```python +compute_metrics(start_logits, end_logits, eval_set, small_eval_set) +``` + +```python out +{'exact_match': 83.0, 'f1': 88.25} +``` + +C'est bien ! Maintenant, utilisons ceci pour *finetuner* notre modèle. + +### Finetuning du modèle + +{#if fw === 'pt'} + +Nous sommes maintenant prêts à entraîner notre modèle. Créons-le en utilisant la classe `AutoModelForQuestionAnswering` comme précédemment : + +```python +model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) +``` + +{:else} + +Nous sommes maintenant prêts à entraîner notre modèle. Créons-le en utilisant la classe `TFAutoModelForQuestionAnswering` comme précédemment : + +```python +model = TFAutoModelForQuestionAnswering.from_pretrained(model_checkpoint) +``` + +{/if} + +Comme d'habitude, nous recevons un avertissement indiquant que certains poids ne sont pas utilisés (ceux de la tête de pré-entraînement) et que d'autres sont initialisés de manière aléatoire (ceux de la tête de réponse aux questions). Vous devriez être habitué à cela maintenant, mais cela signifie que ce modèle n'est pas encore prêt à être utilisé et qu'il a besoin d'être *finetuné*. Une bonne chose que nous soyons sur le point de le faire ! + +Pour pouvoir pousser notre modèle vers le *Hub*, nous devons nous connecter à Hugging Face. Si vous exécutez ce code dans un *notebook*, vous pouvez le faire avec la fonction utilitaire suivante, qui affiche un *widget* où vous pouvez entrer vos identifiants de connexion : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Si vous ne travaillez pas dans un *notebook*, tapez simplement la ligne suivante dans votre terminal : + +```bash +huggingface-cli login +``` + +{#if fw === 'pt'} + +Une fois ceci fait, nous pouvons définir nos `TrainingArguments`. Comme nous l'avons dit lorsque nous avons défini notre fonction pour calculer la métrique, nous ne serons pas en mesure d'avoir une boucle d'évaluation standard à cause de la signature de la fonction `compute_metrics()`. Nous pourrions écrire notre propre sous-classe de `Trainer` pour faire cela (une approche que vous pouvez trouver dans le [script d'exemple de réponse aux questions](https://github.com/huggingface/transformers/blob/master/examples/pytorch/question-answering/trainer_qa.py)), mais c'est un peu trop long pour cette section. A la place, nous n'évaluerons le modèle qu'à la fin de l'entraînement et nous vous montrerons comment faire une évaluation cela dans le paragraphe « Une boucle d'entraînement personnalisée » ci-dessous. + +C'est là que l'API `Trainer` montre ses limites et que la bibliothèque 🤗 *Accelerate* brille : personnaliser la classe pour un cas d'utilisation spécifique peut être pénible, mais modifier une boucle d'entraînement est facile. + +Jetons un coup d'œil à notre `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, +) +``` + +Nous avons déjà vu la plupart d'entre eux. Nous définissons quelques hyperparamètres (comme le taux d'apprentissage, le nombre d'époques d'entraînement, un taux de décroissance des poids) et nous indiquons que nous voulons sauvegarder le modèle à la fin de chaque époque, sauter l'évaluation, et télécharger nos résultats vers le *Hub*. Nous activons également l'entraînement en précision mixte avec `fp16=True`, car cela peut accélérer l'entraînement sur un GPU récent. + +{:else} + +Maintenant que c'est fait, nous pouvons créer nos jeux de données TensorFlow. Nous pouvons utiliser le simple collateur de données par défaut cette fois-ci : + +```python +from transformers import DefaultDataCollator + +data_collator = DefaultDataCollator(return_tensors="tf") +``` + +Et maintenant nous créons les jeux de données comme d'habitude. + +```python +tf_train_dataset = train_dataset.to_tf_dataset( + columns=[ + "input_ids", + "start_positions", + "end_positions", + "attention_mask", + "token_type_ids", + ], + collate_fn=data_collator, + shuffle=True, + batch_size=16, +) +tf_eval_dataset = validation_dataset.to_tf_dataset( + columns=["input_ids", "attention_mask", "token_type_ids"], + collate_fn=data_collator, + shuffle=False, + batch_size=16, +) +``` + +Ensuite, nous configurons nos hyperparamètres d'entraînement et compilons notre modèle : + +```python +from transformers import create_optimizer +from transformers.keras_callbacks import PushToHubCallback +import tensorflow as tf + +# Le nombre d'étapes d'entraînement est le nombre d'échantillons dans le jeu de données, divisé par la taille du batch, +# puis multiplié par le nombre total d'époques. Notez que le jeu de données tf_train_dataset est ici un tf.data.Dataset, +# et non le jeu de données original donc son len() est déjà 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) + +# Entraîner en mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +Enfin, nous sommes prêts à entraîner avec `model.fit()`. Nous utilisons un `PushToHubCallback` pour télécharger le modèle sur le *Hub* après chaque époque. + +{/if} + +Par défaut, le dépôt utilisé sera dans votre espace et nommé après le répertoire de sortie que vous avez défini. Donc dans notre cas il sera dans `"sgugger/bert-finetuned-squad"`. Nous pouvons passer outre en passant un `hub_model_id`, par exemple, pour pousser le modèle dans l'organisation `huggingface_course` nous avons utilisé `hub_model_id= "huggingface_course/bert-finetuned-squad"` (qui est le modèle que nous avons lié au début de cette section). + +{#if fw === 'pt'} + + + +💡 Si le répertoire de sortie que vous utilisez existe, il doit être un clone local du dépôt vers lequel vous voulez pousser (donc définissez un nouveau nom si vous obtenez une erreur lors de la définition de votre `Trainer`). + + + +Enfin, nous passons tout à la classe `Trainer` et lançons l'entraînement : + +```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) + +# Nous allons faire la validation après, donc pas de validation au milieu de l'entraînement. +model.fit(tf_train_dataset, callbacks=[callback], epochs=num_train_epochs) +``` + +{/if} + +Notez que pendant l'entraînement, chaque fois que le modèle est sauvegardé (ici, à chaque époque), il est téléchargé sur le *Hub* en arrière-plan. Ainsi, vous pourrez reprendre votre entraînement sur une autre machine si nécessaire. L'ensemble de l'entraînement prend un certain temps (un peu plus d'une heure sur une Titan RTX), vous pouvez donc prendre un café ou relire les parties du cours qui vous ont semblé plus difficiles pendant qu'il se déroule. Notez également que dès que la première époque est terminée, vous verrez des poids téléchargés sur le *Hub* et vous pourrez commencer à jouer avec votre modèle sur sa page. + +{#if fw === 'pt'} + +Une fois l'entraînement terminé, nous pouvons enfin évaluer notre modèle (et prier pour ne pas avoir dépensé tout ce temps de calcul pour rien). La méthode `predict()` du `Trainer` retournera un *tuple* où les premiers éléments seront les prédictions du modèle (ici une paire avec les logits de début et de fin). Nous envoyons ceci à notre fonction `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} + +Une fois l'entraînement terminé, nous pouvons enfin évaluer notre modèle (et prier pour ne pas avoir dépensé tout ce temps de calcul pour rien). La méthode `predict()` de notre `model` se chargera d'obtenir les prédictions, et puisque nous avons fait tout le travail difficile de définir une fonction `compute_metrics()` plus tôt, nous pouvons obtenir nos résultats en une seule ligne : + +```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} +``` + +Super ! À titre de comparaison, les scores indiqués dans l'article de BERT pour ce tâche sont de 80,8 et 88,5. Donc nous sommes exactement là où nous devrions être. + +{#if fw === 'pt'} + +Enfin, nous utilisons la méthode `push_to_hub()` pour nous assurer que nous téléchargeons la dernière version du modèle : + +```py +trainer.push_to_hub(commit_message="Training complete") +``` + +Cela renvoie l'URL du commit qu'il vient de faire, si vous voulez l'inspecter : + +```python out +'https://huggingface.co/sgugger/bert-finetuned-squad/commit/9dcee1fbc25946a6ed4bb32efb1bd71d5fa90b68' +``` + +Le `Trainer` rédige également une carte de modèle avec tous les résultats de l'évaluation et la télécharge. + +{/if} + +À ce stade, vous pouvez utiliser le *widget* d'inférence sur le *Hub* du modèle pour tester le modèle et le partager avec vos amis, votre famille et vos animaux préférés. Vous avez réussi à *finetuner* un modèle sur une tâche de réponse à une question. Félicitations ! + + + +✏️ **A votre tour** Essayez un autre modèle pour voir s'il est plus performant pour cette tâche ! + + + +{#if fw === 'pt'} + +Si vous voulez plonger un peu plus profondément dans la boucle d'entraînement, nous allons maintenant vous montrer comment faire la même chose en utilisant 🤗 *Accelerate*. + +## Une boucle d'entraînement personnalisée + +Jetons maintenant un coup d'œil à la boucle d'entraînement complète, afin que vous puissiez facilement personnaliser les parties dont vous avez besoin. Elle ressemblera beaucoup à la boucle d'entraînement du [chapitre 3](/course/fr/chapter3/4), à l'exception de la boucle d'évaluation. Nous serons en mesure d'évaluer le modèle régulièrement puisque nous ne sommes plus contraints par la classe `Trainer`. + +### Préparer tout pour l'entraînement + +Tout d'abord, nous devons construire le `DataLoader`s à partir de nos jeux de données. Nous définissons le format de ces jeux de données à `"torch"` et supprimons les colonnes dans le jeu de validation qui ne sont pas utilisées par le modèle. Ensuite, nous pouvons utiliser le `default_data_collator` fourni par 🤗 *Transformers* comme `collate_fn` et mélanger l'ensemble d'entraînement mais pas celui de validation : + +```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 +) +``` + +Ensuite, nous réinstantifions notre modèle afin de nous assurer que nous ne poursuivons pas le *finetuning* précédent et que nous repartons du modèle BERT pré-entraîné : + +```py +model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) +``` + +Ensuite, nous aurons besoin d'un optimiseur. Comme d'habitude, nous utilisons le classique `AdamW`, qui est comme Adam mais avec une correction dans la façon dont le taux de décroissance des poids est appliqué : + +```py +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Une fois que nous avons tous ces objets, nous pouvons les envoyer à la méthode `accelerator.prepare()`. Rappelez-vous que si vous voulez entraîner sur des TPUs dans un *notebook* Colab, vous devrez déplacer tout ce code dans une fonction d'entraînement, et qui ne devrait pas exécuter une cellule qui instancie un `Accelerator`. Nous pouvons forcer l'entraînement en précision mixte en passant l'argument `fp16=True` à `Accelerator` (ou, si vous exécutez le code comme un script, assurez-vous de remplir la 🤗 *Accelerate* `config` de manière appropriée). + +```py +from accelerate import Accelerator + +accelerator = Accelerator(fp16=True) +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + +Comme vous devez le savoir depuis les sections précédentes, nous ne pouvons utiliser la longueur de `train_dataloader` pour calculer le nombre d'étapes d'entraînement qu'après qu'il soit passé par la méthode `accelerator.prepare()`. Nous utilisons le même programme linéaire que dans les sections précédentes : + +```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, +) +``` + +Pour pousser notre modèle vers le *Hub*, nous aurons besoin de créer un objet `Repository` dans un dossier de travail. Tout d'abord, connectez-vous au *Hub*, si vous n'êtes pas déjà connecté. Nous déterminerons le nom du dépôt à partir de l'identifiant du modèle que nous voulons donner à notre modèle (n'hésitez pas à remplacer le `repo_name` par votre propre choix. Il doit juste contenir votre nom d'utilisateur, ce que fait la fonction `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' +``` + +Ensuite, nous pouvons cloner ce dépôt dans un dossier local. S'il existe déjà, ce dossier local doit être un clone du dépôt avec lequel nous travaillons : + +```py +output_dir = "bert-finetuned-squad-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +Nous pouvons maintenant télécharger tout ce que nous sauvegardons dans `output_dir` en appelant la méthode `repo.push_to_hub()`. Cela nous aidera à télécharger les modèles intermédiaires à la fin de chaque époque. + +## Boucle d'entraînement + +Nous sommes maintenant prêts à écrire la boucle d'entraînement complète. Après avoir défini une barre de progression pour suivre l'évolution de l'entraînement, la boucle comporte trois parties : + +- l'entraînement à proprement dit, qui est l'itération classique sur le `train_dataloader`, passage en avant du modèle, puis passage en arrière et étape d'optimisation. +- l'évaluation, dans laquelle nous rassemblons toutes les valeurs pour `start_logits` et `end_logits` avant de les convertir en tableaux NumPy. Une fois la boucle d'évaluation terminée, nous concaténons tous les résultats. Notez que nous devons tronquer car `Accelerator` peut avoir ajouté quelques échantillons à la fin pour s'assurer que nous avons le même nombre d'exemples dans chaque processus. +- sauvegarde et téléchargement, où nous sauvegardons d'abord le modèle et le *tokenizer*, puis appelons `repo.push_to_hub()`. Comme nous l'avons fait auparavant, nous utilisons l'argument `blocking=False` pour dire à la bibliothèque 🤗 *Hub* de pousser dans un processus asynchrone. De cette façon, l'entraînement continue normalement et cette (longue) instruction est exécutée en arrière-plan. + +Voici le code complet de la boucle d'entraînement : + +```py +from tqdm.auto import tqdm +import torch + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Entraînement + model.train() + for step, batch in enumerate(train_dataloader): + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) + + # Evaluation + model.eval() + 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) + + # Sauvegarder et télécharger + 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 + ) +``` + +Au cas où ce serait la première fois que vous verriez un modèle enregistré avec 🤗 *Accelerate*, prenons un moment pour inspecter les trois lignes de code qui l'accompagnent : + +```py +accelerator.wait_for_everyone() +unwrapped_model = accelerator.unwrap_model(model) +unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) +``` + +La première ligne est explicite : elle indique à tous les processus d'attendre que tout le monde soit à ce stade avant de continuer. C'est pour s'assurer que nous avons le même modèle dans chaque processus avant de sauvegarder. Ensuite, nous prenons le `unwrapped_model`, qui est le modèle de base que nous avons défini. La méthode `accelerator.prepare()` modifie le modèle pour qu'il fonctionne dans l'entraînement distribué. Donc il n'aura plus la méthode `save_pretrained()` car la méthode `accelerator.unwrap_model()` annule cette étape. Enfin, nous appelons `save_pretrained()` mais nous disons à cette méthode d'utiliser `accelerator.save()` au lieu de `torch.save()`. + +Une fois ceci fait, vous devriez avoir un modèle qui produit des résultats assez similaires à celui entraîné avec `Trainer`. Vous pouvez vérifier le modèle que nous avons entraîné en utilisant ce code à [*huggingface-course/bert-finetuned-squad-accelerate*](https://huggingface.co/huggingface-course/bert-finetuned-squad-accelerate). Et si vous voulez tester des modifications de la boucle d'entraînement, vous pouvez les implémenter directement en modifiant le code ci-dessus ! + +{/if} + +### Utilisation du modèle finetuné + +Nous vous avons déjà montré comment vous pouvez utiliser le modèle que nous avons *finetuné* sur le *Hub* avec le *widget* d'inférence. Pour l'utiliser localement dans un `pipeline`, il suffit de spécifier l'identifiant du modèle : + +```py +from transformers import pipeline + +# Remplacez par votre propre checkpoint +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'} +``` + +Super ! Notre modèle fonctionne aussi bien que le modèle par défaut pour ce pipeline ! diff --git a/chapters/fr/chapter7/8.mdx b/chapters/fr/chapter7/8.mdx index aeea11abb..927345b35 100644 --- a/chapters/fr/chapter7/8.mdx +++ b/chapters/fr/chapter7/8.mdx @@ -1,17 +1,22 @@ -# Maîtriser le NLP - -Si vous êtes arrivé jusqu'ici dans le cours, félicitations ! Vous avez maintenant toutes les connaissances et les outils nécessaires pour aborder (presque) n'importe quelle tâche de *NLP* avec 🤗 *Transformers* et l'écosystème d'*Hugging Face* ! - -Nous avons vu beaucoup de collecteurs de données différents, c'est pourquoi nous avons fait cette petite vidéo pour vous aider à trouver lequel utiliser pour chaque tâche : - - - -Après avoir terminé ce tour d'horizon des principales tâches de *NLP*, vous devriez : - -* savoir quelles architectures (encodeur, décodeur ou encodeur-décodeur) sont les mieux adaptées à chaque tâche, -* comprendre la différence entre le pré-entraînement et le *finetuning* d'un modèle de langage, -* savoir comment entraîner des *transformers* en utilisant soit l'API `Trainer` et les fonctionnalités d'entraînement distribué d' 🤗 *Accelerate* ou TensorFlow et Keras selon la piste que vous avez suivie, -* comprendre la signification et les limites de métriques comme ROUGE et BLEU pour les tâches de génération de texte, -* savoir comment interagir avec vos modèles *finetunés*, à la fois sur le *Hub* et en utilisant la `pipeline` de 🤗 *Transformers*. - +# Maîtriser le NLP + + + +Si vous êtes arrivé jusqu'ici dans le cours, félicitations ! Vous avez maintenant toutes les connaissances et les outils nécessaires pour aborder (presque) n'importe quelle tâche de *NLP* avec 🤗 *Transformers* et l'écosystème d'*Hugging Face* ! + +Nous avons vu beaucoup de collecteurs de données différents, c'est pourquoi nous avons fait cette petite vidéo pour vous aider à trouver lequel utiliser pour chaque tâche : + + + +Après avoir terminé ce tour d'horizon des principales tâches de *NLP*, vous devriez : + +* savoir quelles architectures (encodeur, décodeur ou encodeur-décodeur) sont les mieux adaptées à chaque tâche, +* comprendre la différence entre le pré-entraînement et le *finetuning* d'un modèle de langage, +* savoir comment entraîner des *transformers* en utilisant soit l'API `Trainer` et les fonctionnalités d'entraînement distribué d' 🤗 *Accelerate* ou TensorFlow et Keras selon la piste que vous avez suivie, +* comprendre la signification et les limites de métriques comme ROUGE et BLEU pour les tâches de génération de texte, +* savoir comment interagir avec vos modèles *finetunés*, à la fois sur le *Hub* et en utilisant la `pipeline` de 🤗 *Transformers*. + Malgré toutes ces connaissances, il arrivera un moment où vous rencontrerez un *bug* difficile dans votre code ou aurez une question sur la façon de résoudre un problème de *NLP* particulier. Heureusement, la communauté d'*Hugging Face* est là pour vous aider ! Dans le dernier chapitre de cette partie du cours, nous allons explorer comment vous pouvez déboguer vos modèles et demander de l'aide efficacement. \ No newline at end of file diff --git a/chapters/fr/chapter7/9.mdx b/chapters/fr/chapter7/9.mdx index ae063e9d0..c3f9e5934 100644 --- a/chapters/fr/chapter7/9.mdx +++ b/chapters/fr/chapter7/9.mdx @@ -1,324 +1,329 @@ - - - - -# Quiz de fin de chapitre - -Testons ce que vous avez appris dans ce chapitre ! - -### 1. Laquelle des tâches suivantes peut être considérée comme un problème de classification de tokens ? - - - -### 2. Quelle partie du prétraitement pour la classification de tokens diffère des autres pipelines de prétraitement ? - --100 pour étiqueter les tokens spéciaux.", - explain: "Ce n'est pas spécifique à la classification de tokens. Nous utilisons toujours -100 comme étiquette pour les tokens que nous voulons ignorer dans la perte." - }, - { - text: "Nous devons nous assurer que les étiquettes sont tronquées ou rembourrées à la même taille que les entrées, lorsque nous appliquons la troncature/le padding.", - explain: "En effet mais ce n'est pas la seule différence.", - correct: true - } - ]} -/> - -### 3. Quel problème se pose lorsque nous tokenisons les mots dans un problème de classification de tokens et que nous voulons étiqueter les tokens ? - -tokenizer ajoute des tokens spéciaux et nous n'avons pas d'étiquettes pour eux.", - explain: "Nous les étiquetons par -100 ils sont donc ignorés dans la perte." - }, - { - text: "Chaque mot peut produire plusieurs tokens, ce qui fait que nous nous retrouvons avec plus de tokens que d'étiquettes.", - explain: "C'est le problème principal et nous devons aligner les étiquettes originales avec les tokens.", - correct: true - }, - { - text: "Les tokens ajoutés n'ont pas d'étiquettes, il n'y a donc pas de problème.", - explain: "Nous avons besoin d'autant d'étiquettes que de tokens, sinon nos modèles se tromperont." - } - ]} -/> - -### 4. Que signifie « adaptation au domaine » ? - -finetunons un modèle pré-entraîné sur un nouveau jeu de données et qu'il donne des prédictions qui sont plus adaptées à ce nouveau jeu de données.", - explain: "Le modèle a adapté ses connaissances au nouveau jeu de données.", - correct: true - }, - { - text: "C'est lorsque nous ajoutons des échantillons mal classés à un jeu de données pour rendre notre modèle plus robuste.", - explain: "C'est certainement quelque chose que vous devriez faire si vous réentraînez votre modèle régulièrement, mais ce n'est pas une adaptation au domaine." - } - ]} -/> - -### 5. Quelles sont les étiquettes dans un problème de modélisation du langage masqué ? - -tokens de la phrase d'entrée sont masqués de manière aléatoire et les étiquettes sont les tokens d'entrée originaux.", - explain: "C'est ça !", - correct: true - }, - { - text: "Certains des tokens de la phrase d'entrée sont masqués de manière aléatoire et les étiquettes sont les tokens d'entrée originaux, décalés vers la gauche.", - explain: "Non, le déplacement des étiquettes vers la gauche correspond à la prédiction du mot suivant, ce qui est une modélisation causale du langage." - }, - { - text: "Certains des tokens de la phrase d'entrée sont masqués de manière aléatoire et l'étiquette indique si la phrase est positive ou négative.", - explain: "Il s'agit d'un problème de classification de séquences avec une certaine augmentation de données et non d'une modélisation du langage masqué." - }, - { - text: "Certains des tokens des deux phrases d'entrée sont masqués de manière aléatoire et l'étiquette indique si les deux phrases sont similaires ou non.", - explain: "Il s'agit d'un problème de classification de séquences avec une certaine augmentation de données et non d'une modélisation du langage masqué." - } - ]} -/> - -### 6. Laquelle de ces tâches peut être considérée comme un problème de séquence à séquence ? - - - -### 7. Quelle est la bonne façon de prétraiter les données pour un problème de séquence à séquence ? - -tokenizer avec les éléments suivants inputs=... et targets=....", - explain: "Nous pourrions ajouter cette API à l'avenir mais ce n'est pas possible pour le moment." - }, - { - text: "Les entrées et les cibles doivent être prétraitées, en deux appels séparés au tokenizer.", - explain: "C'est vrai, mais incomplet. Il y a quelque chose que vous devez faire pour vous assurer que le tokenizer traite les deux correctement." - }, - { - text: "Comme d'habitude, nous devons simplement tokeniser les entrées.", - explain: "Pas dans un problème de classification de séquences. Les cibles sont aussi des textes que nous devons convertir en chiffres !" - }, - { - text: "Les entrées doivent être envoyées au tokenizer, et les cibles aussi, mais sous un gestionnaire de contexte spécial.", - explain: "C'est exact, le tokenizer doit être mis en mode cible par ce gestionnaire de contexte.", - correct: true - } - ]} -/> - -{#if fw === 'pt'} - -### 8. Pourquoi existe-t-il une sous-classe spécifique de Trainer pour les problèmes de séquence à séquence ? - --100.", - explain: "Ce n'est pas du tout une perte personnalisée mais la façon dont la perte est toujours calculée." - }, - { - text: "Parce que les problèmes de séquence à séquence nécessitent une boucle d'évaluation spéciale.", - explain: "Les prédictions des modèles de séquence à séquence sont souvent exécutées en utilisant la méthode generate().", - correct: true - }, - { - text: "Parce que les cibles sont des textes dans des problèmes de séquence à séquence.", - explain: "Trainer ne se soucie pas vraiment de cela puisqu'elles ont été prétraités auparavant." - }, - { - text: "Parce que nous utilisons deux modèles dans les problèmes de séquence à séquence.", - explain: "Nous utilisons en quelque sorte deux modèles, un encodeur et un décodeur, mais ils sont regroupés dans un seul modèle." - } - ]} -/> - -{:else} - -### 9. Pourquoi est-il souvent inutile de spécifier une perte quand on appelle compile() sur un transformer ? - -tranformers sont entraînés avec un apprentissage autosupervisé.", - explain: "Pas tout à fait. Même l'apprentissage autosupervisé a besoin d'une fonction de perte !" - }, - { - text: "Parce que la sortie de perte interne du modèle est utilisée par défaut.", - explain: " ", - correct: true - }, - { - text: "Parce que nous calculons les mesures après l'entraînement au lieu de le faire.", - explain: "Nous le faisons souvent mais cela n'explique pas d'où vient la valeur de perte que nous optimisons dans l'entraînement." - }, - { - text: "Parce que la perte est spécifiée dans model.fit().", - explain: "La fonction de perte est toujours fixée une fois que vous exécutez model.compile() et ne peut pas être modifiée dans model.fit()." - } - ]} -/> - -{/if} - -### 10. Quand devez-vous pré-entraîner un nouveau modèle ? - -finetuner sur vos données afin d'éviter d'énormes coûts de calcul." - }, - { - text: "Lorsque vous avez des doutes sur le biais du modèle pré-entraîné que vous utilisez.", - explain: "C'est vrai mais vous devez vous assurer que les données que vous utiliserez pour l'entraînement sont vraiment meilleures.", - correct: true - }, - { - text: "Lorsque les modèles pré-entraînés disponibles ne sont tout simplement pas assez bons.", - explain: "Vous êtes sûr d'avoir bien débogué votre entraînement ?" - } - ]} -/> - -### 11. Pourquoi est-il facile de prétraîner un modèle de langage sur des batchs de textes ? - -Transformers ne nécessite que quelques lignes de code pour démarrer l'entraînement.", - explain: "Bien que vrai, cela ne répond pas vraiment à la question posée. Essayez une autre réponse !" - } - ]} -/> - -### 12. Quels sont les principaux défis lors du prétraitement des données pour une tâche de réponse à des questions ? - - - -### 13. Comment le post-traitement est-il généralement effectué dans les réponses aux questions ? - -tokens correspondant.", - explain: "Ce pourrait être une façon de faire mais c'est un peu trop simpliste." - }, - { - text: "Le modèle vous donne les positions de début et de fin de la réponse pour chaque caractéristique créée par un exemple et il vous suffit de décoder la plage de tokens correspondant dans celui qui a le meilleur score.", - explain: "C'est proche du post-traitement que nous avons étudié, mais ce n'est pas tout à fait exact." - }, - { - text: "Le modèle vous donne les positions de début et de fin de la réponse pour chaque caractéristique créée par un exemple et vous n'avez plus qu'à les faire correspondre à la portée dans le contexte de celui qui a le meilleur score.", - explain: "C'est ça en résumé !", - correct: true - }, - { - text: "Le modèle génère une réponse et il vous suffit de la décoder.", - explain: "A moins que vous ne formuliez votre problème de réponse aux questions comme une tâche de séquence à séquence." - } - ]} -/> + + + + +# Quiz de fin de chapitre + + + +Testons ce que vous avez appris dans ce chapitre ! + +### 1. Laquelle des tâches suivantes peut être considérée comme un problème de classification de tokens ? + + + +### 2. Quelle partie du prétraitement pour la classification de tokens diffère des autres pipelines de prétraitement ? + +-100 pour étiqueter les tokens spéciaux.", + explain: "Ce n'est pas spécifique à la classification de tokens. Nous utilisons toujours -100 comme étiquette pour les tokens que nous voulons ignorer dans la perte." + }, + { + text: "Nous devons nous assurer que les étiquettes sont tronquées ou rembourrées à la même taille que les entrées, lorsque nous appliquons la troncature/le padding.", + explain: "En effet mais ce n'est pas la seule différence.", + correct: true + } + ]} +/> + +### 3. Quel problème se pose lorsque nous tokenisons les mots dans un problème de classification de tokens et que nous voulons étiqueter les tokens ? + +tokenizer ajoute des tokens spéciaux et nous n'avons pas d'étiquettes pour eux.", + explain: "Nous les étiquetons par -100 ils sont donc ignorés dans la perte." + }, + { + text: "Chaque mot peut produire plusieurs tokens, ce qui fait que nous nous retrouvons avec plus de tokens que d'étiquettes.", + explain: "C'est le problème principal et nous devons aligner les étiquettes originales avec les tokens.", + correct: true + }, + { + text: "Les tokens ajoutés n'ont pas d'étiquettes, il n'y a donc pas de problème.", + explain: "Nous avons besoin d'autant d'étiquettes que de tokens, sinon nos modèles se tromperont." + } + ]} +/> + +### 4. Que signifie « adaptation au domaine » ? + +finetunons un modèle pré-entraîné sur un nouveau jeu de données et qu'il donne des prédictions qui sont plus adaptées à ce nouveau jeu de données.", + explain: "Le modèle a adapté ses connaissances au nouveau jeu de données.", + correct: true + }, + { + text: "C'est lorsque nous ajoutons des échantillons mal classés à un jeu de données pour rendre notre modèle plus robuste.", + explain: "C'est certainement quelque chose que vous devriez faire si vous réentraînez votre modèle régulièrement, mais ce n'est pas une adaptation au domaine." + } + ]} +/> + +### 5. Quelles sont les étiquettes dans un problème de modélisation du langage masqué ? + +tokens de la phrase d'entrée sont masqués de manière aléatoire et les étiquettes sont les tokens d'entrée originaux.", + explain: "C'est ça !", + correct: true + }, + { + text: "Certains des tokens de la phrase d'entrée sont masqués de manière aléatoire et les étiquettes sont les tokens d'entrée originaux, décalés vers la gauche.", + explain: "Non, le déplacement des étiquettes vers la gauche correspond à la prédiction du mot suivant, ce qui est une modélisation causale du langage." + }, + { + text: "Certains des tokens de la phrase d'entrée sont masqués de manière aléatoire et l'étiquette indique si la phrase est positive ou négative.", + explain: "Il s'agit d'un problème de classification de séquences avec une certaine augmentation de données et non d'une modélisation du langage masqué." + }, + { + text: "Certains des tokens des deux phrases d'entrée sont masqués de manière aléatoire et l'étiquette indique si les deux phrases sont similaires ou non.", + explain: "Il s'agit d'un problème de classification de séquences avec une certaine augmentation de données et non d'une modélisation du langage masqué." + } + ]} +/> + +### 6. Laquelle de ces tâches peut être considérée comme un problème de séquence à séquence ? + + + +### 7. Quelle est la bonne façon de prétraiter les données pour un problème de séquence à séquence ? + +tokenizer avec les éléments suivants inputs=... et targets=....", + explain: "Nous pourrions ajouter cette API à l'avenir mais ce n'est pas possible pour le moment." + }, + { + text: "Les entrées et les cibles doivent être prétraitées, en deux appels séparés au tokenizer.", + explain: "C'est vrai, mais incomplet. Il y a quelque chose que vous devez faire pour vous assurer que le tokenizer traite les deux correctement." + }, + { + text: "Comme d'habitude, nous devons simplement tokeniser les entrées.", + explain: "Pas dans un problème de classification de séquences. Les cibles sont aussi des textes que nous devons convertir en chiffres !" + }, + { + text: "Les entrées doivent être envoyées au tokenizer, et les cibles aussi, mais sous un gestionnaire de contexte spécial.", + explain: "C'est exact, le tokenizer doit être mis en mode cible par ce gestionnaire de contexte.", + correct: true + } + ]} +/> + +{#if fw === 'pt'} + +### 8. Pourquoi existe-t-il une sous-classe spécifique de Trainer pour les problèmes de séquence à séquence ? + +-100.", + explain: "Ce n'est pas du tout une perte personnalisée mais la façon dont la perte est toujours calculée." + }, + { + text: "Parce que les problèmes de séquence à séquence nécessitent une boucle d'évaluation spéciale.", + explain: "Les prédictions des modèles de séquence à séquence sont souvent exécutées en utilisant la méthode generate().", + correct: true + }, + { + text: "Parce que les cibles sont des textes dans des problèmes de séquence à séquence.", + explain: "Trainer ne se soucie pas vraiment de cela puisqu'elles ont été prétraités auparavant." + }, + { + text: "Parce que nous utilisons deux modèles dans les problèmes de séquence à séquence.", + explain: "Nous utilisons en quelque sorte deux modèles, un encodeur et un décodeur, mais ils sont regroupés dans un seul modèle." + } + ]} +/> + +{:else} + +### 9. Pourquoi est-il souvent inutile de spécifier une perte quand on appelle compile() sur un transformer ? + +tranformers sont entraînés avec un apprentissage autosupervisé.", + explain: "Pas tout à fait. Même l'apprentissage autosupervisé a besoin d'une fonction de perte !" + }, + { + text: "Parce que la sortie de perte interne du modèle est utilisée par défaut.", + explain: " ", + correct: true + }, + { + text: "Parce que nous calculons les mesures après l'entraînement au lieu de le faire.", + explain: "Nous le faisons souvent mais cela n'explique pas d'où vient la valeur de perte que nous optimisons dans l'entraînement." + }, + { + text: "Parce que la perte est spécifiée dans model.fit().", + explain: "La fonction de perte est toujours fixée une fois que vous exécutez model.compile() et ne peut pas être modifiée dans model.fit()." + } + ]} +/> + +{/if} + +### 10. Quand devez-vous pré-entraîner un nouveau modèle ? + +finetuner sur vos données afin d'éviter d'énormes coûts de calcul." + }, + { + text: "Lorsque vous avez des doutes sur le biais du modèle pré-entraîné que vous utilisez.", + explain: "C'est vrai mais vous devez vous assurer que les données que vous utiliserez pour l'entraînement sont vraiment meilleures.", + correct: true + }, + { + text: "Lorsque les modèles pré-entraînés disponibles ne sont tout simplement pas assez bons.", + explain: "Vous êtes sûr d'avoir bien débogué votre entraînement ?" + } + ]} +/> + +### 11. Pourquoi est-il facile de prétraîner un modèle de langage sur des batchs de textes ? + +Transformers ne nécessite que quelques lignes de code pour démarrer l'entraînement.", + explain: "Bien que vrai, cela ne répond pas vraiment à la question posée. Essayez une autre réponse !" + } + ]} +/> + +### 12. Quels sont les principaux défis lors du prétraitement des données pour une tâche de réponse à des questions ? + + + +### 13. Comment le post-traitement est-il généralement effectué dans les réponses aux questions ? + +tokens correspondant.", + explain: "Ce pourrait être une façon de faire mais c'est un peu trop simpliste." + }, + { + text: "Le modèle vous donne les positions de début et de fin de la réponse pour chaque caractéristique créée par un exemple et il vous suffit de décoder la plage de tokens correspondant dans celui qui a le meilleur score.", + explain: "C'est proche du post-traitement que nous avons étudié, mais ce n'est pas tout à fait exact." + }, + { + text: "Le modèle vous donne les positions de début et de fin de la réponse pour chaque caractéristique créée par un exemple et vous n'avez plus qu'à les faire correspondre à la portée dans le contexte de celui qui a le meilleur score.", + explain: "C'est ça en résumé !", + correct: true + }, + { + text: "Le modèle génère une réponse et il vous suffit de la décoder.", + explain: "A moins que vous ne formuliez votre problème de réponse aux questions comme une tâche de séquence à séquence." + } + ]} +/> diff --git a/chapters/fr/chapter8/1.mdx b/chapters/fr/chapter8/1.mdx index 441ea1969..e3fe6fdd6 100644 --- a/chapters/fr/chapter8/1.mdx +++ b/chapters/fr/chapter8/1.mdx @@ -1,12 +1,17 @@ -# Introduction - -Maintenant que vous savez comment aborder les tâches de NLP les plus courantes avec 🤗 *Transformers*, vous devriez être en mesure de vous lancer dans vos propres projets ! Dans ce chapitre, nous allons explorer ce qu'il faut faire lorsque vous rencontrez un problème. Vous apprendrez comment déboguer avec succès votre code ou votre entraînement et comment demander de l'aide à la communauté si vous ne parvenez pas à résoudre le problème par vous-même. Et si vous pensez avoir trouvé un bug dans l'une des bibliothèques d'Hugging Face, nous vous montrerons la meilleure façon de le signaler afin que le problème soit résolu le plus rapidement possible. - -Plus précisément, dans ce chapitre vous allez apprendre : - -- la première chose à faire lorsque vous obtenez une erreur, -- comment demander de l'aide sur le [forum](https://discuss.huggingface.co/), -- comment déboguer votre pipeline d'entraînement, -- comment rédiger une bonne *issue*. - -Rien de tout cela n'est spécifiquement lié à 🤗 *Transformers* ou à l'écosystème Hugging Face. Les leçons de ce chapitre sont applicables à la plupart des projets open source ! +# Introduction + + + +Maintenant que vous savez comment aborder les tâches de NLP les plus courantes avec 🤗 *Transformers*, vous devriez être en mesure de vous lancer dans vos propres projets ! Dans ce chapitre, nous allons explorer ce qu'il faut faire lorsque vous rencontrez un problème. Vous apprendrez comment déboguer avec succès votre code ou votre entraînement et comment demander de l'aide à la communauté si vous ne parvenez pas à résoudre le problème par vous-même. Et si vous pensez avoir trouvé un bug dans l'une des bibliothèques d'Hugging Face, nous vous montrerons la meilleure façon de le signaler afin que le problème soit résolu le plus rapidement possible. + +Plus précisément, dans ce chapitre vous allez apprendre : + +- la première chose à faire lorsque vous obtenez une erreur, +- comment demander de l'aide sur le [forum](https://discuss.huggingface.co/), +- comment déboguer votre pipeline d'entraînement, +- comment rédiger une bonne *issue*. + +Rien de tout cela n'est spécifiquement lié à 🤗 *Transformers* ou à l'écosystème Hugging Face. Les leçons de ce chapitre sont applicables à la plupart des projets open source ! diff --git a/chapters/fr/chapter8/2.mdx b/chapters/fr/chapter8/2.mdx index 76abf7488..c09d17766 100644 --- a/chapters/fr/chapter8/2.mdx +++ b/chapters/fr/chapter8/2.mdx @@ -1,375 +1,375 @@ -# Que faire lorsque vous obtenez une erreur - - - -Dans cette section, nous allons examiner certaines erreurs courantes qui peuvent se produire lorsque vous essayez de générer des prédictions à partir de votre *transformer* fraîchement *finetuné*. Cela vous préparera pour la [section 4](/course/chapter8/fr/section4) de ce chapitre où nous explorerons comment déboguer la phase d'entraînement elle-même. - - - -Nous avons préparé un gabarit de [dépôt de modèles](https://huggingface.co/lewtun/distilbert-base-uncased-finetuned-squad-d5716d28) pour cette section et si vous voulez exécuter le code de ce chapitre, vous devrez d'abord copier le modèle dans votre compte sur le [*Hub* d'Hugging Face](https://huggingface.co). Pour ce faire, connectez-vous d'abord en exécutant l'une ou l'autre des commandes suivantes dans un *notebook* Jupyter : - -```python -from huggingface_hub import notebook_login - -notebook_login() -``` - -ou ce qui suit dans votre terminal préféré : - -```bash -huggingface-cli login -``` - -Cela vous demandera d'entrer votre nom d'utilisateur et votre mot de passe, et enregistrera un jeton sous *~/.cache/huggingface/*. Une fois que vous vous êtes connecté, vous pouvez copier le gabarit du dépôt avec la fonction suivante : - -```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(): - # Cloner le dépôt et extraire le chemin local - template_repo_id = "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28" - commit_hash = "be3eaffc28669d7932492681cd5f3e8905e358b4" - template_repo_dir = snapshot_download(template_repo_id, revision=commit_hash) - # Créer un dépôt vide sur le Hub - model_name = template_repo_id.split("/")[1] - create_repo(model_name, exist_ok=True) - # Cloner le dépôt vide - new_repo_id = get_full_repo_name(model_name) - new_repo_dir = model_name - repo = Repository(local_dir=new_repo_dir, clone_from=new_repo_id) - # Copier les fichiers - copy_tree(template_repo_dir, new_repo_dir) - # Pousser sur le Hub - repo.push_to_hub() -``` - -Maintenant, lorsque vous appelez `copy_repository_template()`, cela va créer une copie du gabarit du dépôt sous votre compte. - -## Déboguer le pipeline à partir de 🤗 Transformers - -Pour donner le coup d'envoi de notre voyage dans le monde merveilleux du débogage de *transformers*, considérez le scénario suivant : vous travaillez avec un collègue sur un projet de réponse à des questions pour aider les clients d'un site de commerce en ligne à trouver des réponses à des produits. Votre collègue vous envoie un message du genre : - -> Bonjour ! Je viens de réaliser une expérience en utilisant les techniques du [chapitre 7](/course/fr/chapiter7/7) du cours d'Hugging Face et j'ai obtenu d'excellents résultats sur SQuAD ! Je pense que nous pouvons utiliser ce modèle comme point de départ pour notre projet. L'identifiant du modèle sur le *Hub* est "lewtun/distillbert-base-uncased-finetuned-squad-d5716d28". N'hésite pas à le tester :) - -et la première chose à laquelle on pense est de charger le modèle en utilisant le `pipeline` de 🤗 *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 -""" -``` - -Oh non, quelque chose semble s'être mal passée ! Si vous êtes novice en programmation, ce genre d'erreurs peut sembler un peu cryptique au début (qu'est-ce qu'une `OSError` ?!). L'erreur affichée ici n'est que la dernière partie d'un rapport d'erreur beaucoup plus long appelé _Python traceback_ (alias *stack trace*). Par exemple, si vous exécutez ce code sur Google Colab, vous devriez voir quelque chose comme la capture d'écran suivante : - -
-A Python traceback. -
- -Il y a beaucoup d'informations dans ces rapports, nous allons donc en parcourir ensemble les éléments clés. La première chose à noter est que les *tracebacks* doivent être lus _de bas en haut_. Cela peut sembler bizarre si vous avez l'habitude de lire du texte français de haut en bas mais cela reflète le fait que le *traceback* montre la séquence d'appels de fonction que le `pipeline` fait lors du téléchargement du modèle et du *tokenizer*. Consultez le [chapitre 2](/course/fr/chapter2) pour plus de détails sur la façon dont le `pipeline` fonctionne sous le capot. - - - -🚨 Vous voyez le cadre bleu autour de « 6 frames » dans le traceback de Google Colab ? Il s'agit d'une fonctionnalité spéciale de Colab qui compresse le traceback en frames. Si vous ne parvenez pas à trouver la source d'une erreur, déroulez le traceback en cliquant sur ces deux petites flèches. - - - -Cela signifie que la dernière ligne du traceback indique le dernier message d'erreur et donne le nom de l'exception qui a été levée. Dans ce cas, le type d'exception est `OSError`, ce qui indique une erreur liée au système. Si nous lisons le message d'erreur qui l'accompagne, nous pouvons voir qu'il semble y avoir un problème avec le fichier *config.json* du modèle et deux suggestions nous sont données pour le résoudre : - -```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 -""" -``` - - - -💡 Si vous rencontrez un message d'erreur difficile à comprendre, copiez et collez le message dans Google ou sur [Stack Overflow](https://stackoverflow.com/) (oui, vraiment !). Il y a de fortes chances que vous ne soyez pas la première personne à rencontrer cette erreur et c'est un bon moyen de trouver des solutions que d'autres membres de la communauté ont publiées. Par exemple, en recherchant `OSError : Can't load config for` sur Stack Overflow donne plusieurs [réponses](https://stackoverflow.com/search?q=OSError%3A+Can%27t+load+config+for+) qui peuvent être utilisées comme point de départ pour résoudre le problème. - - - -La première suggestion nous demande de vérifier si l'identifiant du modèle est effectivement correct, la première chose à faire est donc de copier l'identifiant et de le coller dans la barre de recherche du *Hub* : - -
-The wrong model name. -
- -Hmm, il semble en effet que le modèle de notre collègue ne soit pas sur le *Hub*... Mais il y a une faute de frappe dans le nom du modèle ! DistilBERT n'a qu'un seul « l » dans son nom alors corrigeons cela et cherchons « lewtun/distilbert-base-uncased-finetuned-squad-d5716d28 » à la place : - -
-The right model name. -
- -Ok, ça a marché. Maintenant essayons de télécharger à nouveau le modèle avec le bon identifiant : - -```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 -""" -``` - -Argh, encore un échec. Bienvenue dans la vie quotidienne d'un ingénieur en apprentissage machine ! Puisque nous avons corrigé l'identifiant du modèle, le problème doit se situer dans le dépôt lui-même. Une façon rapide d'accéder au contenu d'un dépôt sur le 🤗 *Hub* est via la fonction `list_repo_files()` de la bibliothèque `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'] -``` - -Intéressant. Il ne semble pas y avoir de fichier *config.json* dans le dépôt ! Pas étonnant que notre `pipeline` n'ait pas pu charger le modèle. Notre collègue a dû oublier de pousser ce fichier vers le *Hub* après l'avoir *finetuné*. Dans ce cas, le problème semble assez simple à résoudre : nous pouvons lui demander d'ajouter le fichier, ou, puisque nous pouvons voir à partir de l'identifiant du modèle que le modèle pré-entraîné utilisé est [`distilbert-base-uncased`](https://huggingface.co/distilbert-base-uncased), nous pouvons télécharger la configuration de ce modèle et la pousser dans notre dépôt pour voir si cela résout le problème. Essayons cela. En utilisant les techniques apprises dans le [chapitre 2](/course/fr/chapter2), nous pouvons télécharger la configuration du modèle avec la classe `AutoConfig` : - -```python -from transformers import AutoConfig - -pretrained_checkpoint = "distilbert-base-uncased" -config = AutoConfig.from_pretrained(pretrained_checkpoint) -``` - - - -🚨 L'approche que nous adoptons ici n'est pas infaillible puisque notre collègue peut avoir modifié la configuration de `distilbert-base-uncased` avant de finetuner le modèle. Dans la vie réelle, nous voudrions vérifier avec lui d'abord, mais pour les besoins de cette section nous supposerons qu'il a utilisé la configuration par défaut. - - - -Nous pouvons ensuite le pousser vers notre dépôt de modèles avec la fonction `push_to_hub()` de la configuration : - -```python -config.push_to_hub(model_checkpoint, commit_message="Add config.json") -``` - -Maintenant, nous pouvons tester si cela a fonctionné en chargeant le modèle depuis le dernier *commit* de la branche `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! -""" - -context_fr = r""" -La réponse à des questions consiste à extraire une réponse d'un texte -à partir d'une question. Un exemple de jeu de données de réponse aux questions est le -jeu de données SQuAD qui est entièrement basé sur cette tâche. Si vous souhaitez finetuner -un modèle sur une tâche SQuAD, vous pouvez utiliser le fichier -exemples/pytorch/question-answering/run_squad.py. - -🤗 Transformers est interopérable avec les frameworks PyTorch, TensorFlow et JAX. -de sorte que vous pouvez utiliser vos outils préférés pour une grande variété de tâches ! -""" - -question = "What is extractive question answering?" -# Qu'est-ce que la réponse extractive aux questions ? -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'} - # la tâche consistant à extraire une réponse d'un texte à partir d'une question. -``` - -Woohoo, ça a marché ! Récapitulons ce que vous venez d'apprendre : - -- les messages d'erreur en Python sont appelés _tracebacks_ et sont lus de bas en haut. La dernière ligne du message d'erreur contient généralement les informations dont vous avez besoin pour localiser la source du problème, -- si la dernière ligne ne contient pas suffisamment d'informations, remontez dans le *traceback* et voyez si vous pouvez identifier où l'erreur se produit dans le code source, -- si aucun des messages d'erreur ne peut vous aider à déboguer le problème, essayez de rechercher en ligne une solution à un problème similaire, -- l'`huggingface_hub` fournit une suite d'outils que vous pouvez utiliser pour interagir avec et déboguer les dépôts sur le *Hub*. - -Maintenant que vous savez comment déboguer un pipeline, examinons un exemple plus délicat dans la passe avant du modèle lui-même. - -## Déboguer la passe avant de votre modèle - -Bien que le `pipeline` soit parfait pour la plupart des applications où vous devez générer rapidement des prédictions, vous aurez parfois besoin d'accéder aux logits du modèle (par exemple si vous avez un post-traitement personnalisé que vous souhaitez appliquer). Pour voir ce qui peut mal tourner dans ce cas, commençons par récupérer le modèle et le *tokenizer* de notre `pipeline` : - -```python -tokenizer = reader.tokenizer -model = reader.model -``` - -Ensuite, nous avons besoin d'une question, alors voyons si nos *frameworks* préférés sont supportés : - -```python -question = "Which frameworks can I use?" # Quel frameworks puis-je utiliser ? -``` - -Comme nous l'avons vu dans le [chapitre 7](/course/fr/chapter7), les étapes habituelles que nous devons suivre sont la tokénisation des entrées, l'extraction des logits des *tokens* de début et de fin, puis le décodage de l'étendue de la réponse : - -```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 -# Pour obtenir le début de réponse le plus probable avec l'argmax du score -answer_start = torch.argmax(answer_start_scores) -# Pour obtenir la fin de réponse la plus probable avec l'argmax du score -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' -""" -``` - -Il semble que nous ayons un *bug* dans notre code ! Mais il ne nous fait pas peur. Nous pouvons utiliser le débogueur Python dans un *notebook* : - - - -ou dans un terminal : - - - -Ici, la lecture du message d'erreur nous indique que l'objet `'list' n'a pas d'attribut 'size'`, et nous pouvons voir une flèche `-->` pointant vers la ligne où le problème a été soulevé dans `model(**inputs)`. Vous pouvez déboguer ceci de manière interactive en utilisant le débogueur Python, mais pour l'instant nous allons simplement imprimer une tranche de `inputs` pour voir ce que nous avons : - -```python -inputs["input_ids"][:5] -``` - -```python out -[101, 2029, 7705, 2015, 2064] -``` - -Cela ressemble certainement à une `list` ordinaire en Python mais vérifions le type : - -```python -type(inputs["input_ids"]) -``` - -```python out -list -``` - -Oui, c'est bien une `list` Python. Alors, qu'est-ce qui a mal tourné ? Rappelez-vous que dans le [chapitre 2](/course/fr/chapter2) nous avons vu que les classes `AutoModelForXxx` opèrent sur des _tenseurs_ (soit dans PyTorch ou TensorFlow) et qu'une opération commune est d'extraire les dimensions d'un tenseur en utilisant `Tensor.size()`. Jetons un autre coup d'oeil au *traceback* pour voir quelle ligne a déclenché l'exception : - -``` -~/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' -``` - -Il semble que notre code ait essayé d'appeler `input_ids.size()`, mais cela ne fonctionne clairement pas pour une `list` Python qui est juste un conteneur. Comment pouvons-nous résoudre ce problème ? La recherche du message d'erreur sur Stack Overflow donne quelques [réponses](https://stackoverflow.com/search?q=AttributeError%3A+%27list%27+object+has+no+attribute+%27size%27&s=c15ec54c-63cb-481d-a749-408920073e8f) pertinentes. En cliquant sur la première, une question similaire à la nôtre s'affiche, avec la réponse indiquée dans la capture d'écran ci-dessous : - -
-An answer from Stack Overflow. -
- -La réponse recommande d'ajouter `return_tensors='pt'` au *tokenizer*, voyons donc si cela fonctionne pour nous : - -```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 -# Pour obtenir le début de réponse le plus probable avec l'argmax du score -answer_start = torch.argmax(answer_start_scores) -# Pour obtenir la fin de réponse la plus probable avec l'argmax du score -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? # Quels frameworks puis-je utiliser ? -Answer: pytorch, tensorflow, and jax # pytorch, tensorflow et jax -""" -``` - -Super, ça a marché ! Voilà un excellent exemple de l'utilité de Stack Overflow : en identifiant un problème similaire, nous avons pu bénéficier de l'expérience d'autres membres de la communauté. Cependant, une recherche de ce type ne donne pas toujours une réponse pertinente. Que faire alors dans ce cas ? Heureusement, il existe une communauté accueillante de développeurs sur le [forum d'Hugging Face](https://discuss.huggingface.co/) qui peut vous aider ! Dans la prochaine section, nous verrons comment rédiger de bonnes questions sur les forums pour avoir de bonnes chances d'obtenir une réponse. +# Que faire lorsque vous obtenez une erreur + + + +Dans cette section, nous allons examiner certaines erreurs courantes qui peuvent se produire lorsque vous essayez de générer des prédictions à partir de votre *transformer* fraîchement *finetuné*. Cela vous préparera pour la [section 4](/course/chapter8/fr/section4) de ce chapitre où nous explorerons comment déboguer la phase d'entraînement elle-même. + + + +Nous avons préparé un gabarit de [dépôt de modèles](https://huggingface.co/lewtun/distilbert-base-uncased-finetuned-squad-d5716d28) pour cette section et si vous voulez exécuter le code de ce chapitre, vous devrez d'abord copier le modèle dans votre compte sur le [*Hub* d'Hugging Face](https://huggingface.co). Pour ce faire, connectez-vous d'abord en exécutant l'une ou l'autre des commandes suivantes dans un *notebook* Jupyter : + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +ou ce qui suit dans votre terminal préféré : + +```bash +huggingface-cli login +``` + +Cela vous demandera d'entrer votre nom d'utilisateur et votre mot de passe, et enregistrera un jeton sous *~/.cache/huggingface/*. Une fois que vous vous êtes connecté, vous pouvez copier le gabarit du dépôt avec la fonction suivante : + +```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(): + # Cloner le dépôt et extraire le chemin local + template_repo_id = "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28" + commit_hash = "be3eaffc28669d7932492681cd5f3e8905e358b4" + template_repo_dir = snapshot_download(template_repo_id, revision=commit_hash) + # Créer un dépôt vide sur le Hub + model_name = template_repo_id.split("/")[1] + create_repo(model_name, exist_ok=True) + # Cloner le dépôt vide + new_repo_id = get_full_repo_name(model_name) + new_repo_dir = model_name + repo = Repository(local_dir=new_repo_dir, clone_from=new_repo_id) + # Copier les fichiers + copy_tree(template_repo_dir, new_repo_dir) + # Pousser sur le Hub + repo.push_to_hub() +``` + +Maintenant, lorsque vous appelez `copy_repository_template()`, cela va créer une copie du gabarit du dépôt sous votre compte. + +## Déboguer le pipeline à partir de 🤗 Transformers + +Pour donner le coup d'envoi de notre voyage dans le monde merveilleux du débogage de *transformers*, considérez le scénario suivant : vous travaillez avec un collègue sur un projet de réponse à des questions pour aider les clients d'un site de commerce en ligne à trouver des réponses à des produits. Votre collègue vous envoie un message du genre : + +> Bonjour ! Je viens de réaliser une expérience en utilisant les techniques du [chapitre 7](/course/fr/chapiter7/7) du cours d'Hugging Face et j'ai obtenu d'excellents résultats sur SQuAD ! Je pense que nous pouvons utiliser ce modèle comme point de départ pour notre projet. L'identifiant du modèle sur le *Hub* est "lewtun/distillbert-base-uncased-finetuned-squad-d5716d28". N'hésite pas à le tester :) + +et la première chose à laquelle on pense est de charger le modèle en utilisant le `pipeline` de 🤗 *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 +""" +``` + +Oh non, quelque chose semble s'être mal passée ! Si vous êtes novice en programmation, ce genre d'erreurs peut sembler un peu cryptique au début (qu'est-ce qu'une `OSError` ?!). L'erreur affichée ici n'est que la dernière partie d'un rapport d'erreur beaucoup plus long appelé _Python traceback_ (alias *stack trace*). Par exemple, si vous exécutez ce code sur Google Colab, vous devriez voir quelque chose comme la capture d'écran suivante : + +
+A Python traceback. +
+ +Il y a beaucoup d'informations dans ces rapports, nous allons donc en parcourir ensemble les éléments clés. La première chose à noter est que les *tracebacks* doivent être lus _de bas en haut_. Cela peut sembler bizarre si vous avez l'habitude de lire du texte français de haut en bas mais cela reflète le fait que le *traceback* montre la séquence d'appels de fonction que le `pipeline` fait lors du téléchargement du modèle et du *tokenizer*. Consultez le [chapitre 2](/course/fr/chapter2) pour plus de détails sur la façon dont le `pipeline` fonctionne sous le capot. + + + +🚨 Vous voyez le cadre bleu autour de « 6 frames » dans le traceback de Google Colab ? Il s'agit d'une fonctionnalité spéciale de Colab qui compresse le traceback en frames. Si vous ne parvenez pas à trouver la source d'une erreur, déroulez le traceback en cliquant sur ces deux petites flèches. + + + +Cela signifie que la dernière ligne du traceback indique le dernier message d'erreur et donne le nom de l'exception qui a été levée. Dans ce cas, le type d'exception est `OSError`, ce qui indique une erreur liée au système. Si nous lisons le message d'erreur qui l'accompagne, nous pouvons voir qu'il semble y avoir un problème avec le fichier *config.json* du modèle et deux suggestions nous sont données pour le résoudre : + +```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 +""" +``` + + + +💡 Si vous rencontrez un message d'erreur difficile à comprendre, copiez et collez le message dans Google ou sur [Stack Overflow](https://stackoverflow.com/) (oui, vraiment !). Il y a de fortes chances que vous ne soyez pas la première personne à rencontrer cette erreur et c'est un bon moyen de trouver des solutions que d'autres membres de la communauté ont publiées. Par exemple, en recherchant `OSError : Can't load config for` sur Stack Overflow donne plusieurs [réponses](https://stackoverflow.com/search?q=OSError%3A+Can%27t+load+config+for+) qui peuvent être utilisées comme point de départ pour résoudre le problème. + + + +La première suggestion nous demande de vérifier si l'identifiant du modèle est effectivement correct, la première chose à faire est donc de copier l'identifiant et de le coller dans la barre de recherche du *Hub* : + +
+The wrong model name. +
+ +Hmm, il semble en effet que le modèle de notre collègue ne soit pas sur le *Hub*... Mais il y a une faute de frappe dans le nom du modèle ! DistilBERT n'a qu'un seul « l » dans son nom alors corrigeons cela et cherchons « lewtun/distilbert-base-uncased-finetuned-squad-d5716d28 » à la place : + +
+The right model name. +
+ +Ok, ça a marché. Maintenant essayons de télécharger à nouveau le modèle avec le bon identifiant : + +```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 +""" +``` + +Argh, encore un échec. Bienvenue dans la vie quotidienne d'un ingénieur en apprentissage machine ! Puisque nous avons corrigé l'identifiant du modèle, le problème doit se situer dans le dépôt lui-même. Une façon rapide d'accéder au contenu d'un dépôt sur le 🤗 *Hub* est via la fonction `list_repo_files()` de la bibliothèque `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'] +``` + +Intéressant. Il ne semble pas y avoir de fichier *config.json* dans le dépôt ! Pas étonnant que notre `pipeline` n'ait pas pu charger le modèle. Notre collègue a dû oublier de pousser ce fichier vers le *Hub* après l'avoir *finetuné*. Dans ce cas, le problème semble assez simple à résoudre : nous pouvons lui demander d'ajouter le fichier, ou, puisque nous pouvons voir à partir de l'identifiant du modèle que le modèle pré-entraîné utilisé est [`distilbert-base-uncased`](https://huggingface.co/distilbert-base-uncased), nous pouvons télécharger la configuration de ce modèle et la pousser dans notre dépôt pour voir si cela résout le problème. Essayons cela. En utilisant les techniques apprises dans le [chapitre 2](/course/fr/chapter2), nous pouvons télécharger la configuration du modèle avec la classe `AutoConfig` : + +```python +from transformers import AutoConfig + +pretrained_checkpoint = "distilbert-base-uncased" +config = AutoConfig.from_pretrained(pretrained_checkpoint) +``` + + + +🚨 L'approche que nous adoptons ici n'est pas infaillible puisque notre collègue peut avoir modifié la configuration de `distilbert-base-uncased` avant de finetuner le modèle. Dans la vie réelle, nous voudrions vérifier avec lui d'abord, mais pour les besoins de cette section nous supposerons qu'il a utilisé la configuration par défaut. + + + +Nous pouvons ensuite le pousser vers notre dépôt de modèles avec la fonction `push_to_hub()` de la configuration : + +```python +config.push_to_hub(model_checkpoint, commit_message="Add config.json") +``` + +Maintenant, nous pouvons tester si cela a fonctionné en chargeant le modèle depuis le dernier *commit* de la branche `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! +""" + +context_fr = r""" +La réponse à des questions consiste à extraire une réponse d'un texte +à partir d'une question. Un exemple de jeu de données de réponse aux questions est le +jeu de données SQuAD qui est entièrement basé sur cette tâche. Si vous souhaitez finetuner +un modèle sur une tâche SQuAD, vous pouvez utiliser le fichier +exemples/pytorch/question-answering/run_squad.py. + +🤗 Transformers est interopérable avec les frameworks PyTorch, TensorFlow et JAX. +de sorte que vous pouvez utiliser vos outils préférés pour une grande variété de tâches ! +""" + +question = "What is extractive question answering?" +# Qu'est-ce que la réponse extractive aux questions ? +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'} + # la tâche consistant à extraire une réponse d'un texte à partir d'une question. +``` + +Woohoo, ça a marché ! Récapitulons ce que vous venez d'apprendre : + +- les messages d'erreur en Python sont appelés _tracebacks_ et sont lus de bas en haut. La dernière ligne du message d'erreur contient généralement les informations dont vous avez besoin pour localiser la source du problème, +- si la dernière ligne ne contient pas suffisamment d'informations, remontez dans le *traceback* et voyez si vous pouvez identifier où l'erreur se produit dans le code source, +- si aucun des messages d'erreur ne peut vous aider à déboguer le problème, essayez de rechercher en ligne une solution à un problème similaire, +- l'`huggingface_hub` fournit une suite d'outils que vous pouvez utiliser pour interagir avec et déboguer les dépôts sur le *Hub*. + +Maintenant que vous savez comment déboguer un pipeline, examinons un exemple plus délicat dans la passe avant du modèle lui-même. + +## Déboguer la passe avant de votre modèle + +Bien que le `pipeline` soit parfait pour la plupart des applications où vous devez générer rapidement des prédictions, vous aurez parfois besoin d'accéder aux logits du modèle (par exemple si vous avez un post-traitement personnalisé que vous souhaitez appliquer). Pour voir ce qui peut mal tourner dans ce cas, commençons par récupérer le modèle et le *tokenizer* de notre `pipeline` : + +```python +tokenizer = reader.tokenizer +model = reader.model +``` + +Ensuite, nous avons besoin d'une question, alors voyons si nos *frameworks* préférés sont supportés : + +```python +question = "Which frameworks can I use?" # Quel frameworks puis-je utiliser ? +``` + +Comme nous l'avons vu dans le [chapitre 7](/course/fr/chapter7), les étapes habituelles que nous devons suivre sont la tokénisation des entrées, l'extraction des logits des *tokens* de début et de fin, puis le décodage de l'étendue de la réponse : + +```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 +# Pour obtenir le début de réponse le plus probable avec l'argmax du score +answer_start = torch.argmax(answer_start_scores) +# Pour obtenir la fin de réponse la plus probable avec l'argmax du score +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' +""" +``` + +Il semble que nous ayons un *bug* dans notre code ! Mais il ne nous fait pas peur. Nous pouvons utiliser le débogueur Python dans un *notebook* : + + + +ou dans un terminal : + + + +Ici, la lecture du message d'erreur nous indique que l'objet `'list' n'a pas d'attribut 'size'`, et nous pouvons voir une flèche `-->` pointant vers la ligne où le problème a été soulevé dans `model(**inputs)`. Vous pouvez déboguer ceci de manière interactive en utilisant le débogueur Python, mais pour l'instant nous allons simplement imprimer une tranche de `inputs` pour voir ce que nous avons : + +```python +inputs["input_ids"][:5] +``` + +```python out +[101, 2029, 7705, 2015, 2064] +``` + +Cela ressemble certainement à une `list` ordinaire en Python mais vérifions le type : + +```python +type(inputs["input_ids"]) +``` + +```python out +list +``` + +Oui, c'est bien une `list` Python. Alors, qu'est-ce qui a mal tourné ? Rappelez-vous que dans le [chapitre 2](/course/fr/chapter2) nous avons vu que les classes `AutoModelForXxx` opèrent sur des _tenseurs_ (soit dans PyTorch ou TensorFlow) et qu'une opération commune est d'extraire les dimensions d'un tenseur en utilisant `Tensor.size()`. Jetons un autre coup d'oeil au *traceback* pour voir quelle ligne a déclenché l'exception : + +``` +~/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' +``` + +Il semble que notre code ait essayé d'appeler `input_ids.size()`, mais cela ne fonctionne clairement pas pour une `list` Python qui est juste un conteneur. Comment pouvons-nous résoudre ce problème ? La recherche du message d'erreur sur Stack Overflow donne quelques [réponses](https://stackoverflow.com/search?q=AttributeError%3A+%27list%27+object+has+no+attribute+%27size%27&s=c15ec54c-63cb-481d-a749-408920073e8f) pertinentes. En cliquant sur la première, une question similaire à la nôtre s'affiche, avec la réponse indiquée dans la capture d'écran ci-dessous : + +
+An answer from Stack Overflow. +
+ +La réponse recommande d'ajouter `return_tensors='pt'` au *tokenizer*, voyons donc si cela fonctionne pour nous : + +```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 +# Pour obtenir le début de réponse le plus probable avec l'argmax du score +answer_start = torch.argmax(answer_start_scores) +# Pour obtenir la fin de réponse la plus probable avec l'argmax du score +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? # Quels frameworks puis-je utiliser ? +Answer: pytorch, tensorflow, and jax # pytorch, tensorflow et jax +""" +``` + +Super, ça a marché ! Voilà un excellent exemple de l'utilité de Stack Overflow : en identifiant un problème similaire, nous avons pu bénéficier de l'expérience d'autres membres de la communauté. Cependant, une recherche de ce type ne donne pas toujours une réponse pertinente. Que faire alors dans ce cas ? Heureusement, il existe une communauté accueillante de développeurs sur le [forum d'Hugging Face](https://discuss.huggingface.co/) qui peut vous aider ! Dans la prochaine section, nous verrons comment rédiger de bonnes questions sur les forums pour avoir de bonnes chances d'obtenir une réponse. diff --git a/chapters/fr/chapter8/3.mdx b/chapters/fr/chapter8/3.mdx index a75b49f95..b9c3c2c49 100644 --- a/chapters/fr/chapter8/3.mdx +++ b/chapters/fr/chapter8/3.mdx @@ -1,207 +1,207 @@ -# Demander de l'aide sur les forums - - - - - -Le [forum d'Hugging Face](https://discuss.huggingface.co) est un endroit idéal pour obtenir de l'aide de l'équipe open source d'Hugging Face et de la communauté au sens large. Voici à quoi ressemble la page principale : - -
-The Hugging Face forums. -
- -Dans la partie gauche, vous pouvez voir toutes les catégories dans lesquelles les différents sujets sont regroupés, tandis que la partie droite montre les sujets les plus récents. Un sujet est un message qui contient un titre, une catégorie et une description. C'est assez similaire au format des *issues* GitHub que nous avons vu lors de la création de notre propre jeu de données dans le [chapitre 5](/course/fr/chapter5). Comme son nom l'indique, la catégorie [*Beginners*](https://discuss.huggingface.co/c/beginners/5) est principalement destinée aux personnes qui débutent avec les bibliothèques et l'écosystème d'Hugging Face. Toute question sur l'une des bibliothèques est la bienvenue ici, que ce soit pour déboguer du code ou pour demander de l'aide sur la façon de faire quelque chose. (Cela dit, si votre question concerne une bibliothèque en particulier, vous devriez probablement vous diriger vers la catégorie de bibliothèque correspondante sur le forum). - -De même, les catégories [*Intermediate*](https://discuss.huggingface.co/c/intermediate/6) et [*Research*](https://discuss.huggingface.co/c/research/7) sont destinées aux questions plus avancées. Par exemple sur les bibliothèques ou sur une avancée en recherche en NLP dont vous aimeriez discuter. - -Et naturellement, nous devrions aussi mentionner la catégorie [*Course*](https://discuss.huggingface.co/c/course/20) où vous pouvez poser toutes les questions que vous avez en rapport avec le cours d'Hugging Face ! - -Une fois une catégorie choisie, vous êtes prêt à rédiger votre premier sujet. Vous pouvez trouver quelques [indications](https://discuss.huggingface.co/t/how-to-request-support/3128) dans le forum sur la façon de le faire. Dans cette section, nous allons jeter un coup d'oeil à certaines caractéristiques d'un bon sujet. - -## Rédiger un bon message sur le forum - -A titre d'exemple, supposons que nous essayons de générer des enchâssements à partir d'articles Wikipédia pour créer un moteur de recherche personnalisé. Comme d'habitude, nous chargeons le *tokenizer* et le modèle comme suit : - -```python -from transformers import AutoTokenizer, AutoModel - -model_checkpoint = "distilbert-base-uncased" -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) -model = AutoModel.from_pretrained(model_checkpoint) -``` - -Supposons maintenant que nous essayons d'enchâsser une section entière de l'[article Wikipedia](https://en.wikipedia.org/wiki/Transformers) sur Transformers (la franchise de films, pas la bibliothèque !): - -```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. -""" - -text_fr = """ -Génération 1 est un terme rétroactif pour les personnages de Transformers qui sont apparus -entre 1984 et 1993. Les Transformers ont commencé avec les lignes de jouets japonaises -des années 1980, Micro Change et Diaclone. Elles présentaient des robots capables -de se transformer en véhicules de tous les jours, en objets électroniques ou en armes. -Hasbro a acheté les jouets Micro Change et Diaclone, et s'est associé à Takara. -Marvel Comics est engagé par Hasbro pour créer l'histoire de fond ; le rédacteur en chef -Jim Shooter a écrit une histoire générale et confie la tâche de créer les personnages au -scénariste Dennis O'Neil. Mécontent du travail d'O'Neil (bien que ce dernier ait créé -le nom "Optimus Prime"), Shooter choisit Bob Budiansky pour créer les personnages. - -Les mecha de Transformers ont été en grande partie conçus par Shōji Kawamori, le créateur -de l'anime japonais Macross (qui a été adapté en Robotech en Amérique du Nord). Kawamori -a eu l'idée de transformer des mechas transformables alors qu'il travaillait sur les -franchises Diaclone et Macross au début des années 1980 (comme le VF-1 Valkyrie dans -Macross et Robotech), et ses méchas Diaclone ont plus tard servi de base à Transformers. - -Le concept principal de la Génération 1 est que l'héroïque Optimus Prime, le méchant -Megatron, et leurs meilleurs soldats s'écrasent sur une Terre préhistorique dans l'Arche -et le Némésis avant de se réveiller en 1985, Cybertron traversant à toute allure la zone -neutre en raison de la guerre. La bande dessinée Marvel faisait à l'origine partie -de l'univers principal de Marvel, avec des apparitions de Spider-Man et Nick Fury, -plus quelques caméos, ainsi qu'une visite à la Terre Sauvage. - -La série télévisée Transformers a commencé à peu près à la même époque. -Produite par Sunbow Productions et Marvel Productions, puis Hasbro Productions, -dès le début elle a contredit les histoires de Budiansky. La série TV montre les Autobots -cherchant de nouvelles sources d'énergie et s'écrasent lors de l'attaque des Decepticons. -Marvel a interprété les Autobots comme la destruction d'un astéroïde malveillant -s'approchant de Cybertron. Shockwave est loyal envers Megatron dans la série TV, -et maintient Cybertron dans une impasse en son absence. -Cybertron dans une impasse pendant son absence, mais dans la BD, -il tente de prendre le commandement des Decepticons. -La série télévisée s'écarte aussi radicalement des origines que Budiansky avait -créé pour les Dinobots, le Decepticon devenu Autobot Jetfire -(connu sous le nom de Skyfire à la télévision), -les Constructicons (qui s'associent pour former Devastator) et Oméga Suprême. -La bande dessinée Marvel établit très tôt que Prime manie la matrice de création, -qui donne la vie aux machines. Dans la saison, l'épisode en deux parties -The Key to Vector Sigma a introduit l'ancien ordinateur l'ancien ordinateur -Vector Sigma, qui servait le même objectif original que la matrice de création -(donner la vie aux Transformers), et son gardien Alpha Trion. -""" - -inputs = tokenizer(text, return_tensors="pt") -logits = model(**inputs).logits -``` - -```python output -IndexError: index out of range in self -``` - -Oh nous avons rencontré un problème. Le message d'erreur est bien plus énigmatique que ceux que nous avons vus dans la [section 2](/course/chapter8/fr/section2) ! Nous n'arrivons pas à comprendre le *traceback* complet, alors nous décidons de nous tourner vers le forum d'Hugging Face pour obtenir de l'aide. Comment pouvons-nous élaborer le sujet ? - -Pour commencer, nous devons cliquer sur le bouton *New Topic* dans le coin supérieur droit (notez que pour créer un sujet, nous devons être connectés) : - -
-Creating a new forum topic. -
- -Cela fait apparaître une interface de rédaction où nous pouvons saisir le titre de notre sujet, sélectionner une catégorie et rédiger le contenu : - -
-The interface for creating a forum topic. -
- -Puisque l'erreur semble concerner exclusivement 🤗 *Transformers*, nous allons la sélectionner pour la catégorie. Notre première tentative d'explication du problème pourrait ressembler à quelque chose comme ça : - -
-Drafting the content for a new forum topic. -
- -Bien que ce sujet contienne le message d'erreur pour lequel nous avons besoin d'aide, il y a quelques problèmes avec la façon dont il est écrit : - -1. le titre n'est pas très descriptif, ainsi toute personne parcourant le forum ne sera pas en mesure de dire de quoi il s'agit sans lire également le corps du sujet, -2. le corps du texte ne fournit pas suffisamment d'informations sur *l'origine* de l'erreur et sur *la manière* de la reproduire, -3. le sujet s'adresse directement à quelques personnes sur un ton quelque peu exigeant. - -Les sujets comme celui-ci ne sont pas susceptibles d'obtenir une réponse rapide (si tant est qu'ils en obtiennent une) alors voyons comment nous pouvons l'améliorer. Commençons par la première question, celle du choix d'un bon titre. - -### Choisir un titre descriptif - -Si vous essayez d'obtenir de l'aide pour résoudre un *bug* dans votre code, une bonne règle de base consiste à inclure suffisamment d'informations dans le titre pour que les autres puissent rapidement déterminer s'ils pensent pouvoir répondre à votre question ou non. Dans notre exemple, nous connaissons le nom de l'exception et savons qu'elle est déclenchée dans la passe avant du modèle, où nous appelons `model(**inputs)`. Pour communiquer cela, un titre possible pourrait être : - -> Source de l'IndexError dans la passe avant d'AutoModel ? - -Ce titre indique au lecteur _où_ vous pensez que le *bug* provient, et s'il a déjà rencontré un `IndexError`, il y a de fortes chances qu'il sache comment le déboguer. Bien sûr, le titre peut être ce que vous voulez et d'autres variations comme : - -> Pourquoi mon modèle produit-il un IndexError ? - -pourrait également convenir. Maintenant que nous avons un titre descriptif, voyons comment améliorer le corps du texte. - -### Formatage de vos extraits de code - -La lecture du code source est déjà difficile dans un IDE, mais c'est encore plus difficile lorsque le code est copié et collé en texte brut ! Heureusement, le forum d'Hugging Face supporte l'utilisation de Markdown donc vous devriez toujours entourer vos blocs de code avec trois *backticks* (```) pour qu'ils soient plus facilement lisibles. Faisons cela pour embellir le message d'erreur et pendant que nous y sommes, rendons le corps un peu plus poli que notre version originale : - -
-Our revised forum topic, with proper code formatting. -
- -Comme vous pouvez le voir dans la capture d'écran, le fait d'entourer les blocs de code de *backticks* convertit le texte brut en code formaté, avec un style de couleur ! Notez également que des *backticks* simples peuvent être utilisés pour formater des variables en ligne comme nous l'avons fait pour `distilbert-base-uncased`. Ce sujet a l'air bien meilleur, et avec un peu de chance, nous pourrions trouver quelqu'un dans la communauté qui pourrait deviner à quoi correspond l'erreur. Cependant, au lieu de compter sur la chance, rendons la vie plus facile en incluant le *traceback* dans ses moindres détails ! - -### Inclure le traceback complet - -Puisque la dernière ligne de le *traceback* est souvent suffisante pour déboguer votre propre code, il peut être tentant de ne fournir que cela dans votre sujet pour "gagner de la place". Bien que bien intentionné, cela rend en fait le débogage du problème _plus difficile_ pour les autres, car les informations situées plus haut dans le *traceback* peuvent également être très utiles. Une bonne pratique consiste donc à copier et coller le *traceback* _entier_, en veillant à ce qu'elle soit bien formatée. Comme ces tracebacks peuvent être assez longs, certaines personnes préfèrent les montrer après avoir expliqué le code source. C'est ce que nous allons faire. Maintenant, notre sujet de forum ressemble à ce qui suit : - -
-Our example forum topic, with the complete traceback. -
- -Ceci est beaucoup plus informatif et un lecteur attentif pourrait être en mesure d'indiquer que le problème semble être dû à la transmission d'une longue entrée en raison de cette ligne dans le *traceback* : - -> Token indices sequence length is longer than the specified maximum sequence length for this model (583 > 512). - -Cependant, nous pouvons leur faciliter les choses en leur fournissant le code qui a déclenché l'erreur. C'est ce que nous allons faire maintenant. - -### Fournir un exemple reproductible - -Si vous avez déjà essayé de déboguer le code de quelqu'un d'autre, vous avez probablement d'abord essayé de recréer le problème qu'il a signalé afin de pouvoir commencer à travailler sur le *traceback* pour identifier l'erreur. Il en va de même lorsqu'il s'agit d'obtenir (ou de donner) de l'aide sur les forums. Il est donc très utile de pouvoir fournir un petit exemple qui reproduit l'erreur. La moitié du temps, le simple fait de faire cet exercice vous aidera à comprendre ce qui ne va pas. Dans tous les cas, la pièce manquante de notre exemple est de montrer les _entrées_ que nous avons fournies au modèle. En faisant cela, nous obtenons quelque chose comme l'exemple complet suivant : - -
-The final version of our forum topic. -
- -Ce sujet contient maintenant un bon lot d'informations et il est rédigé d'une manière qui a beaucoup plus de chances d'attirer l'attention de la communauté et d'obtenir une réponse utile. Avec ces directives de base, vous pouvez maintenant créer de superbes sujets pour trouver les réponses à vos questions sur 🤗 *Transformers* ! +# Demander de l'aide sur les forums + + + + + +Le [forum d'Hugging Face](https://discuss.huggingface.co) est un endroit idéal pour obtenir de l'aide de l'équipe open source d'Hugging Face et de la communauté au sens large. Voici à quoi ressemble la page principale : + +
+The Hugging Face forums. +
+ +Dans la partie gauche, vous pouvez voir toutes les catégories dans lesquelles les différents sujets sont regroupés, tandis que la partie droite montre les sujets les plus récents. Un sujet est un message qui contient un titre, une catégorie et une description. C'est assez similaire au format des *issues* GitHub que nous avons vu lors de la création de notre propre jeu de données dans le [chapitre 5](/course/fr/chapter5). Comme son nom l'indique, la catégorie [*Beginners*](https://discuss.huggingface.co/c/beginners/5) est principalement destinée aux personnes qui débutent avec les bibliothèques et l'écosystème d'Hugging Face. Toute question sur l'une des bibliothèques est la bienvenue ici, que ce soit pour déboguer du code ou pour demander de l'aide sur la façon de faire quelque chose. (Cela dit, si votre question concerne une bibliothèque en particulier, vous devriez probablement vous diriger vers la catégorie de bibliothèque correspondante sur le forum). + +De même, les catégories [*Intermediate*](https://discuss.huggingface.co/c/intermediate/6) et [*Research*](https://discuss.huggingface.co/c/research/7) sont destinées aux questions plus avancées. Par exemple sur les bibliothèques ou sur une avancée en recherche en NLP dont vous aimeriez discuter. + +Et naturellement, nous devrions aussi mentionner la catégorie [*Course*](https://discuss.huggingface.co/c/course/20) où vous pouvez poser toutes les questions que vous avez en rapport avec le cours d'Hugging Face ! + +Une fois une catégorie choisie, vous êtes prêt à rédiger votre premier sujet. Vous pouvez trouver quelques [indications](https://discuss.huggingface.co/t/how-to-request-support/3128) dans le forum sur la façon de le faire. Dans cette section, nous allons jeter un coup d'oeil à certaines caractéristiques d'un bon sujet. + +## Rédiger un bon message sur le forum + +A titre d'exemple, supposons que nous essayons de générer des enchâssements à partir d'articles Wikipédia pour créer un moteur de recherche personnalisé. Comme d'habitude, nous chargeons le *tokenizer* et le modèle comme suit : + +```python +from transformers import AutoTokenizer, AutoModel + +model_checkpoint = "distilbert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +model = AutoModel.from_pretrained(model_checkpoint) +``` + +Supposons maintenant que nous essayons d'enchâsser une section entière de l'[article Wikipedia](https://en.wikipedia.org/wiki/Transformers) sur Transformers (la franchise de films, pas la bibliothèque !): + +```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. +""" + +text_fr = """ +Génération 1 est un terme rétroactif pour les personnages de Transformers qui sont apparus +entre 1984 et 1993. Les Transformers ont commencé avec les lignes de jouets japonaises +des années 1980, Micro Change et Diaclone. Elles présentaient des robots capables +de se transformer en véhicules de tous les jours, en objets électroniques ou en armes. +Hasbro a acheté les jouets Micro Change et Diaclone, et s'est associé à Takara. +Marvel Comics est engagé par Hasbro pour créer l'histoire de fond ; le rédacteur en chef +Jim Shooter a écrit une histoire générale et confie la tâche de créer les personnages au +scénariste Dennis O'Neil. Mécontent du travail d'O'Neil (bien que ce dernier ait créé +le nom "Optimus Prime"), Shooter choisit Bob Budiansky pour créer les personnages. + +Les mecha de Transformers ont été en grande partie conçus par Shōji Kawamori, le créateur +de l'anime japonais Macross (qui a été adapté en Robotech en Amérique du Nord). Kawamori +a eu l'idée de transformer des mechas transformables alors qu'il travaillait sur les +franchises Diaclone et Macross au début des années 1980 (comme le VF-1 Valkyrie dans +Macross et Robotech), et ses méchas Diaclone ont plus tard servi de base à Transformers. + +Le concept principal de la Génération 1 est que l'héroïque Optimus Prime, le méchant +Megatron, et leurs meilleurs soldats s'écrasent sur une Terre préhistorique dans l'Arche +et le Némésis avant de se réveiller en 1985, Cybertron traversant à toute allure la zone +neutre en raison de la guerre. La bande dessinée Marvel faisait à l'origine partie +de l'univers principal de Marvel, avec des apparitions de Spider-Man et Nick Fury, +plus quelques caméos, ainsi qu'une visite à la Terre Sauvage. + +La série télévisée Transformers a commencé à peu près à la même époque. +Produite par Sunbow Productions et Marvel Productions, puis Hasbro Productions, +dès le début elle a contredit les histoires de Budiansky. La série TV montre les Autobots +cherchant de nouvelles sources d'énergie et s'écrasent lors de l'attaque des Decepticons. +Marvel a interprété les Autobots comme la destruction d'un astéroïde malveillant +s'approchant de Cybertron. Shockwave est loyal envers Megatron dans la série TV, +et maintient Cybertron dans une impasse en son absence. +Cybertron dans une impasse pendant son absence, mais dans la BD, +il tente de prendre le commandement des Decepticons. +La série télévisée s'écarte aussi radicalement des origines que Budiansky avait +créé pour les Dinobots, le Decepticon devenu Autobot Jetfire +(connu sous le nom de Skyfire à la télévision), +les Constructicons (qui s'associent pour former Devastator) et Oméga Suprême. +La bande dessinée Marvel établit très tôt que Prime manie la matrice de création, +qui donne la vie aux machines. Dans la saison, l'épisode en deux parties +The Key to Vector Sigma a introduit l'ancien ordinateur l'ancien ordinateur +Vector Sigma, qui servait le même objectif original que la matrice de création +(donner la vie aux Transformers), et son gardien Alpha Trion. +""" + +inputs = tokenizer(text, return_tensors="pt") +logits = model(**inputs).logits +``` + +```python output +IndexError: index out of range in self +``` + +Oh nous avons rencontré un problème. Le message d'erreur est bien plus énigmatique que ceux que nous avons vus dans la [section 2](/course/chapter8/fr/section2) ! Nous n'arrivons pas à comprendre le *traceback* complet, alors nous décidons de nous tourner vers le forum d'Hugging Face pour obtenir de l'aide. Comment pouvons-nous élaborer le sujet ? + +Pour commencer, nous devons cliquer sur le bouton *New Topic* dans le coin supérieur droit (notez que pour créer un sujet, nous devons être connectés) : + +
+Creating a new forum topic. +
+ +Cela fait apparaître une interface de rédaction où nous pouvons saisir le titre de notre sujet, sélectionner une catégorie et rédiger le contenu : + +
+The interface for creating a forum topic. +
+ +Puisque l'erreur semble concerner exclusivement 🤗 *Transformers*, nous allons la sélectionner pour la catégorie. Notre première tentative d'explication du problème pourrait ressembler à quelque chose comme ça : + +
+Drafting the content for a new forum topic. +
+ +Bien que ce sujet contienne le message d'erreur pour lequel nous avons besoin d'aide, il y a quelques problèmes avec la façon dont il est écrit : + +1. le titre n'est pas très descriptif, ainsi toute personne parcourant le forum ne sera pas en mesure de dire de quoi il s'agit sans lire également le corps du sujet, +2. le corps du texte ne fournit pas suffisamment d'informations sur *l'origine* de l'erreur et sur *la manière* de la reproduire, +3. le sujet s'adresse directement à quelques personnes sur un ton quelque peu exigeant. + +Les sujets comme celui-ci ne sont pas susceptibles d'obtenir une réponse rapide (si tant est qu'ils en obtiennent une) alors voyons comment nous pouvons l'améliorer. Commençons par la première question, celle du choix d'un bon titre. + +### Choisir un titre descriptif + +Si vous essayez d'obtenir de l'aide pour résoudre un *bug* dans votre code, une bonne règle de base consiste à inclure suffisamment d'informations dans le titre pour que les autres puissent rapidement déterminer s'ils pensent pouvoir répondre à votre question ou non. Dans notre exemple, nous connaissons le nom de l'exception et savons qu'elle est déclenchée dans la passe avant du modèle, où nous appelons `model(**inputs)`. Pour communiquer cela, un titre possible pourrait être : + +> Source de l'IndexError dans la passe avant d'AutoModel ? + +Ce titre indique au lecteur _où_ vous pensez que le *bug* provient, et s'il a déjà rencontré un `IndexError`, il y a de fortes chances qu'il sache comment le déboguer. Bien sûr, le titre peut être ce que vous voulez et d'autres variations comme : + +> Pourquoi mon modèle produit-il un IndexError ? + +pourrait également convenir. Maintenant que nous avons un titre descriptif, voyons comment améliorer le corps du texte. + +### Formatage de vos extraits de code + +La lecture du code source est déjà difficile dans un IDE, mais c'est encore plus difficile lorsque le code est copié et collé en texte brut ! Heureusement, le forum d'Hugging Face supporte l'utilisation de Markdown donc vous devriez toujours entourer vos blocs de code avec trois *backticks* (```) pour qu'ils soient plus facilement lisibles. Faisons cela pour embellir le message d'erreur et pendant que nous y sommes, rendons le corps un peu plus poli que notre version originale : + +
+Our revised forum topic, with proper code formatting. +
+ +Comme vous pouvez le voir dans la capture d'écran, le fait d'entourer les blocs de code de *backticks* convertit le texte brut en code formaté, avec un style de couleur ! Notez également que des *backticks* simples peuvent être utilisés pour formater des variables en ligne comme nous l'avons fait pour `distilbert-base-uncased`. Ce sujet a l'air bien meilleur, et avec un peu de chance, nous pourrions trouver quelqu'un dans la communauté qui pourrait deviner à quoi correspond l'erreur. Cependant, au lieu de compter sur la chance, rendons la vie plus facile en incluant le *traceback* dans ses moindres détails ! + +### Inclure le traceback complet + +Puisque la dernière ligne de le *traceback* est souvent suffisante pour déboguer votre propre code, il peut être tentant de ne fournir que cela dans votre sujet pour "gagner de la place". Bien que bien intentionné, cela rend en fait le débogage du problème _plus difficile_ pour les autres, car les informations situées plus haut dans le *traceback* peuvent également être très utiles. Une bonne pratique consiste donc à copier et coller le *traceback* _entier_, en veillant à ce qu'elle soit bien formatée. Comme ces tracebacks peuvent être assez longs, certaines personnes préfèrent les montrer après avoir expliqué le code source. C'est ce que nous allons faire. Maintenant, notre sujet de forum ressemble à ce qui suit : + +
+Our example forum topic, with the complete traceback. +
+ +Ceci est beaucoup plus informatif et un lecteur attentif pourrait être en mesure d'indiquer que le problème semble être dû à la transmission d'une longue entrée en raison de cette ligne dans le *traceback* : + +> Token indices sequence length is longer than the specified maximum sequence length for this model (583 > 512). + +Cependant, nous pouvons leur faciliter les choses en leur fournissant le code qui a déclenché l'erreur. C'est ce que nous allons faire maintenant. + +### Fournir un exemple reproductible + +Si vous avez déjà essayé de déboguer le code de quelqu'un d'autre, vous avez probablement d'abord essayé de recréer le problème qu'il a signalé afin de pouvoir commencer à travailler sur le *traceback* pour identifier l'erreur. Il en va de même lorsqu'il s'agit d'obtenir (ou de donner) de l'aide sur les forums. Il est donc très utile de pouvoir fournir un petit exemple qui reproduit l'erreur. La moitié du temps, le simple fait de faire cet exercice vous aidera à comprendre ce qui ne va pas. Dans tous les cas, la pièce manquante de notre exemple est de montrer les _entrées_ que nous avons fournies au modèle. En faisant cela, nous obtenons quelque chose comme l'exemple complet suivant : + +
+The final version of our forum topic. +
+ +Ce sujet contient maintenant un bon lot d'informations et il est rédigé d'une manière qui a beaucoup plus de chances d'attirer l'attention de la communauté et d'obtenir une réponse utile. Avec ces directives de base, vous pouvez maintenant créer de superbes sujets pour trouver les réponses à vos questions sur 🤗 *Transformers* ! diff --git a/chapters/fr/chapter8/4.mdx b/chapters/fr/chapter8/4.mdx index 7ac1272c0..e726543f1 100644 --- a/chapters/fr/chapter8/4.mdx +++ b/chapters/fr/chapter8/4.mdx @@ -1,793 +1,793 @@ - - -# Débogage du pipeline d'entraînement - - - -Vous avez écrit un magnifique script pour entraîner ou *finetuner* un modèle sur une tâche donnée en suivant consciencieusement les conseils du [chapitre 7](/course/fr/chapter7). Mais lorsque vous lancez la commande `model.fit()`, quelque chose d'horrible se produit : vous obtenez une erreur 😱 ! Ou pire, tout semble aller bien et l'entraînement se déroule sans erreur mais le modèle résultant est mauvais. Dans cette section, nous allons vous montrer ce que vous pouvez faire pour déboguer ce genre de problèmes. - -## Déboguer le pipeline d'entraînement - - - -Le problème lorsque vous rencontrez une erreur dans `trainer.train()` est qu'elle peut provenir de plusieurs sources, car la fonction `Trainer` assemble généralement des batchs de choses. Elle convertit les jeux de données en chargeurs de données donc le problème pourrait être quelque chose d'erroné dans votre jeu de données, ou un problème en essayant de regrouper les éléments des jeux de données ensemble. Ensuite, elle prend un batch de données et le transmet au modèle, le problème peut donc se situer dans le code du modèle. Après cela, elle calcule les gradients et effectue l'étape d'optimisation, le problème peut donc également se situer dans votre optimiseur. Et même si tout se passe bien pendant l'entraînement, quelque chose peut encore mal tourner pendant l'évaluation si votre métrique pose problème. - -La meilleure façon de déboguer une erreur qui survient dans `trainer.train()` est de passer manuellement en revue tout le pipeline pour voir où les choses se sont mal passées. L'erreur est alors souvent très facile à résoudre. - -Pour le démontrer, nous utiliserons le script suivant qui tente de *finetuner* un modèle DistilBERT sur le [jeu de données MNLI](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() -``` - -Si vous essayez de l'exécuter, vous serez confronté à une erreur plutôt cryptique : - -```python out -'ValueError: You have to specify either input_ids or inputs_embeds' -``` - -### Vérifiez vos données - -Cela va sans dire, mais si vos données sont corrompues, le `Trainer` ne sera pas capable de former des batchs et encore moins d'entraîner votre modèle. Donc, tout d'abord, vous devez jeter un coup d'oeil à ce qui se trouve dans votre jeu d'entraînement. - -Pour éviter d'innombrables heures passées à essayer de corriger quelque chose qui n'est pas la source du bug, nous vous recommandons d'utiliser `trainer.train_dataset` pour vos vérifications et rien d'autre. Faisons donc cela ici : - -```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.'} -``` - -Vous remarquez quelque chose d'anormal ? Ceci, en conjonction avec le message d'erreur sur les `input_ids` manquants, devrait vous faire réaliser que ce sont des textes et non des nombres que le modèle peut comprendre. Ici, l'erreur originale est très trompeuse parce que le `Trainer` enlève automatiquement les colonnes qui ne correspondent pas à la signature du modèle (c'est-à-dire, les arguments attendus par le modèle). Cela signifie qu'ici, tout, sauf les étiquettes, a été éliminé. Il n'y avait donc aucun problème à créer des batchs et à les envoyer ensuite au modèle, qui s'est plaint à son tour de ne pas avoir reçu les bons arguments. - -Pourquoi les données n'ont-elles pas été traitées ? Nous avons utilisé la méthode `Dataset.map()` sur les jeux de données pour appliquer le *tokenizer* sur chaque échantillon. Mais si vous regardez attentivement le code, vous verrez que nous avons fait une erreur en passant les ensembles d'entraînement et d'évaluation au `Trainer`. Au lieu d'utiliser `tokenized_datasets` ici, nous avons utilisé `raw_datasets` 🤦. Alors corrigeons ça ! - -```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() -``` - -Ce nouveau code donnera maintenant une erreur différente (c'est un progrès !) : - -```python out -'ValueError: expected sequence of length 43 at dim 1 (got 37)' -``` - -En regardant le *traceback*, nous pouvons voir que l'erreur se produit dans l'étape de collationnement des données : - -```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 -``` - -Donc, nous devrions passer à cela. Mais avant finissons d'inspecter nos données, pour être sûrs à 100% qu'elles sont correctes. - -Une chose que vous devriez toujours faire lorsque vous déboguez une session d'entraînement est de jeter un coup d'oeil aux entrées décodées de votre modèle. Nous ne pouvons pas donner un sens aux chiffres que nous lui fournissons directement, nous devons donc examiner ce que ces chiffres représentent. Dans le domaine de la vision par ordinateur cela signifie regarder les images décodées des pixels que vous passez, dans le domaine de la parole cela signifie écouter les échantillons audio décodés, et pour notre exemple de NLP cela signifie utiliser notre *tokenizer* pour décoder les entrées : - -```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]' -``` - -Cela semble correct. Vous devriez faire cela pour toutes les clés dans les entrées : - -```py -trainer.train_dataset[0].keys() -``` - -```python out -dict_keys(['attention_mask', 'hypothesis', 'idx', 'input_ids', 'label', 'premise']) -``` - -Notez que les clés qui ne correspondent pas à des entrées acceptées par le modèle seront automatiquement écartées, donc ici nous ne garderons que `input_ids`, `attention_mask`, et `label` (qui sera renommé `labels`). Pour revérifier la signature du modèle, vous pouvez imprimer la classe de votre modèle, puis aller consulter sa documentation : - -```py -type(trainer.model) -``` - -```python out -transformers.models.distilbert.modeling_distilbert.DistilBertForSequenceClassification -``` - -Donc dans notre cas, nous pouvons vérifier les paramètres acceptés sur [cette page](https://huggingface.co/transformers/model_doc/distilbert.html#distilbertforsequenceclassification). Le `Trainer` va également enregistrer les colonnes qu'il rejette. - -Nous avons vérifié que les identifiants d'entrée sont corrects en les décodant. Ensuite, il y a le `attention_mask` : - -```py -tokenizer.decode(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] -``` - -Comme nous n'avons pas appliqué de *padding* dans notre prétraitement, cela semble parfaitement naturel. Pour être sûr qu'il n'y a pas de problème avec ce masque d'attention, vérifions qu'il est de la même longueur que nos identifiants d'entrée : - -```py -len(trainer.train_dataset[0]["attention_mask"]) == len( - trainer.train_dataset[0]["input_ids"] -) -``` - -```python out -True -``` - -C'est bien ! Enfin, vérifions notre étiquette : - -```py -trainer.train_dataset[0]["label"] -``` - -```python out -1 -``` - -Comme les identifiants d'entrée, c'est un nombre qui n'a pas vraiment de sens en soi. Comme nous l'avons vu précédemment, la correspondance entre les entiers et les noms d'étiquettes est stockée dans l'attribut `names` de la *caractéristique* correspondante du jeu de données : - -```py -trainer.train_dataset.features["label"].names -``` - -```python out -['entailment', 'neutral', 'contradiction'] -``` - -Donc `1` signifie `neutral`, ce qui signifie que les deux phrases que nous avons vues ci-dessus ne sont pas en contradiction : la première n'implique pas la seconde. Cela semble correct ! - -Nous n'avons pas de *token* de type identifiant ici puisque DistilBERT ne les attend pas. Si vous en avez dans votre modèle, vous devriez également vous assurer qu'ils correspondent correctement à l'endroit où se trouvent la première et la deuxième phrase dans l'entrée. - - - -✏️ *A votre tour !* Vérifiez que tout semble correct avec le deuxième élément du jeu de données d'entraînement. - - - -Ici nous ne vérifions que le jeu d'entraînement. Vous devez bien sûr vérifier de la même façon les jeux de validation et de test. - -Maintenant que nous savons que nos jeux de données sont bons, il est temps de vérifier l'étape suivante du pipeline d'entraînement. - -### Des jeux de données aux chargeurs de données - -La prochaine chose qui peut mal tourner dans le pipeline d'entraînement est lorsque le `Trainer` essaie de former des batchs à partir du jeu d'entraînement ou de validation. Une fois que vous êtes sûr que les jeux de données du `Trainer` sont corrects, vous pouvez essayer de former manuellement un batch en exécutant ce qui suit (remplacez `train` par `eval` pour le *dataloader* de validation) : - -```py -for batch in trainer.get_train_dataloader(): - break -``` - -Ce code crée le *dataloader* d'entraînement puis le parcourt en s'arrêtant à la première itération. Si le code s'exécute sans erreur, vous avez le premier batch d'entraînement que vous pouvez inspecter, et si le code se trompe, vous êtes sûr que le problème se situe dans le *dataloader*, comme c'est le cas ici : - -```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) -``` - -L'inspection de la dernière image du *traceback* devrait suffire à vous donner un indice mais creusons un peu plus. La plupart des problèmes lors de la création d'un batch sont dus à l'assemblage des exemples en un seul batch. La première chose à vérifier en cas de doute est le `collate_fn` utilisé par votre `DataLoader` : - -```py -data_collator = trainer.get_train_dataloader().collate_fn -data_collator -``` - -```python out - Dict[str, Any]> -``` - -C'est donc `default_data_collator`, mais ce n'est pas ce que nous voulons dans ce cas. Nous voulons rembourrer nos exemples à la phrase la plus longue du batch, ce qui est fait par `DataCollatorWithPadding`. Et cette assembleur de données est censé être utilisé par défaut par le `Trainer`, alors pourquoi n'est-il pas utilisé ici ? - -La réponse est que nous n'avons pas passé le `tokenizer` au `Trainer`, donc il ne pouvait pas créer le `DataCollatorWithPadding` que nous voulons. En pratique, il ne faut jamais hésiter à transmettre explicitement l'assembleur de données que l'on veut utiliser pour être sûr d'éviter ce genre d'erreurs. Adaptons notre code pour faire exactement cela : - -```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() -``` - -La bonne nouvelle ? Nous n'avons plus la même erreur qu'avant, ce qui est un progrès certain. La mauvaise nouvelle ? Nous obtenons une erreur CUDA infâme à la place : - -```python out -RuntimeError: CUDA error: CUBLAS_STATUS_ALLOC_FAILED when calling `cublasCreate(handle)` -``` - -C'est une mauvaise chose car les erreurs CUDA sont extrêmement difficiles à déboguer en général. Nous verrons dans une minute comment résoudre ce problème mais terminons d'abord notre analyse de la création de batchs. - -Si vous êtes sûr que votre collecteur de données est le bon, vous devriez essayer de l'appliquer sur quelques échantillons de votre jeu de données : - -```py -data_collator = trainer.get_train_dataloader().collate_fn -batch = data_collator([trainer.train_dataset[i] for i in range(4)]) -``` - -Ce code échouera parce que le `train_dataset` contient des colonnes de type *string* que le `Trainer` supprime habituellement. Vous pouvez les supprimer manuellement ou si vous voulez reproduire exactement ce que le `Trainer` fait en coulisse, vous pouvez appeler la méthode `Trainer._remove_unused_columns()` qui fait cela : - -```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)]) -``` - -Vous devriez alors être en mesure de déboguer manuellement ce qui se passe dans le collecteur de données si l'erreur persiste. - -Maintenant que nous avons débogué le processus de création de batch, il est temps d'en passer un dans le modèle ! - - -### Passage par le modèle - -Vous devriez être en mesure d'obtenir un batch en exécutant la commande suivante : - -```py -for batch in trainer.get_train_dataloader(): - break -``` - -Si vous exécutez ce code dans un *notebook*, vous risquez d'obtenir une erreur CUDA similaire à celle que nous avons vue précédemment, auquel cas vous devrez redémarrer votre *notebook* et réexécuter le dernier extrait sans la ligne `trainer.train()`. C'est la deuxième chose la plus ennuyeuse à propos des erreurs CUDA : elles cassent irrémédiablement votre noyau. La première plus ennuyeuse est le fait qu'elles sont difficiles à déboguer. - -Comment cela se fait-il ? Cela tient à la façon dont les GPUs fonctionnent. Ils sont extrêmement efficaces pour exécuter un batch d'opérations en parallèle, mais l'inconvénient est que lorsque l'une de ces instructions entraîne une erreur, vous ne le savez pas immédiatement. Ce n'est que lorsque le programme appelle une synchronisation des multiples processus sur le GPU qu'il réalise que quelque chose s'est mal passé, de sorte que l'erreur est en fait mentionnée à un endroit qui n'a rien à voir avec ce qui l'a créée. Par exemple, si nous regardons notre *traceback* précédent, l'erreur a été soulevée pendant la passe arrière, mais nous verrons dans une minute qu'elle provient en fait de quelque chose dans la passe avant. - -Alors comment déboguer ces erreurs ? La réponse est simple : nous ne le faisons pas. À moins que votre erreur CUDA ne soit une erreur *out-of-memory* (ce qui signifie qu'il n'y a pas assez de mémoire dans votre GPU), vous devez toujours revenir au CPU pour la déboguer. - -Pour faire cela dans notre cas, nous devons juste remettre le modèle sur le CPU et l'appeler sur notre batch. Le batch retourné par le `DataLoader` n'a pas encore été déplacé sur le 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. -``` - -L'image devient plus claire. Au lieu d'avoir une erreur CUDA, nous avons maintenant une `IndexError` dans le calcul de la perte (donc rien à voir avec la passe arrière comme nous l'avons dit plus tôt). Plus précisément, nous pouvons voir que c'est la cible 2 qui crée l'erreur, donc c'est un bon moment pour vérifier le nombre de labels de notre modèle : - -```python -trainer.model.config.num_labels -``` - -```python out -2 -``` - -Avec deux étiquettes, seuls les 0 et les 1 sont autorisés comme cibles, mais d'après le message d'erreur, nous avons obtenu un 2. Obtenir un 2 est en fait normal : si nous nous souvenons des noms des étiquettes que nous avons extraits plus tôt, il y en avait trois, donc nous avons les indices 0, 1 et 2 dans notre jeu de données. Le problème est que nous n'avons pas indiqué cela à notre modèle, qui aurait dû être créé avec trois étiquettes. Alors, corrigeons cela ! - -```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, -) -``` - -Nous n'incluons pas encore la ligne `trainer.train()` pour prendre le temps de vérifier que tout se passe bien. Si nous passons un batch à notre modèle, il fonctionne maintenant sans erreur ! - -```py -for batch in trainer.get_train_dataloader(): - break - -outputs = trainer.model.cpu()(**batch) -``` - -L'étape suivante consiste alors à revenir au GPU et à vérifier que tout fonctionne encore : - -```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) -``` - -Si vous obtenez toujours une erreur, assurez-vous de redémarrer votre *notebook* et d'exécuter uniquement la dernière version du script. - -### Exécution d'une étape d'optimisation - -Maintenant que nous savons que nous pouvons construire des batchs qui passent réellement par le modèle, nous sommes prêts pour l'étape suivante du pipeline d'entraînement : calculer les gradients et effectuer une étape d'optimisation. - -La première partie est juste une question d'appel de la méthode `backward()` sur la perte : - -```py -loss = outputs.loss -loss.backward() -``` - -Il est plutôt rare d'obtenir une erreur à ce stade, mais si vous en obtenez une, assurez-vous de retourner au CPU pour obtenir un message d'erreur utile. - -Pour effectuer l'étape d'optimisation, il suffit de créer le `optimizer` et d'appeler sa méthode `step()` : - -```py -trainer.create_optimizer() -trainer.optimizer.step() -``` - -Encore une fois, si vous utilisez l'optimiseur par défaut dans le `Trainer`, vous ne devriez pas avoir d'erreur à ce stade, mais si vous avez un optimiseur personnalisé, il pourrait y avoir quelques problèmes à déboguer ici. N'oubliez pas de revenir au CPU si vous obtenez une erreur CUDA bizarre à ce stade. En parlant d'erreurs CUDA, nous avons mentionné précédemment un cas particulier. Voyons cela maintenant. - -### Gérer les erreurs CUDA out of memory - -Chaque fois que vous obtenez un message d'erreur qui commence par `RuntimeError : CUDA out of memory`, cela indique que vous êtes à court de mémoire GPU. Cela n'est pas directement lié à votre code et peut arriver avec un script qui fonctionne parfaitement bien. Cette erreur signifie que vous avez essayé de mettre trop de choses dans la mémoire interne de votre GPU et que cela a entraîné une erreur. Comme pour d'autres erreurs CUDA, vous devrez redémarrer votre noyau pour être en mesure d'exécuter à nouveau votre entraînement. - -Pour résoudre ce problème, il suffit d'utiliser moins d'espace GPU, ce qui est souvent plus facile à dire qu'à faire. Tout d'abord, assurez-vous que vous n'avez pas deux modèles sur le GPU en même temps (sauf si cela est nécessaire pour votre problème, bien sûr). Ensuite, vous devriez probablement réduire la taille de votre batch car elle affecte directement les tailles de toutes les sorties intermédiaires du modèle et leurs gradients. Si le problème persiste, envisagez d'utiliser une version plus petite de votre modèle. - - - -Dans la prochaine partie du cours, nous examinerons des techniques plus avancées qui peuvent vous aider à réduire votre empreinte mémoire et vous permettre de finetuner les plus grands modèles. - - - -### Évaluation du modèle - -Maintenant que nous avons résolu tous les problèmes liés à notre code, tout est parfait et l'entraînement devrait se dérouler sans problème, n'est-ce pas ? Pas si vite ! Si vous exécutez la commande `trainer.train()`, tout aura l'air bien au début, mais après un moment vous obtiendrez ce qui suit : - -```py -# Cela prendra beaucoup de temps et se soldera par une erreur, vous ne devriez donc pas utiliser cette cellule. -trainer.train() -``` - -```python out -TypeError: only size-1 arrays can be converted to Python scalars -``` - -Vous réaliserez que cette erreur apparaît pendant la phase d'évaluation, donc c'est la dernière chose que nous aurons besoin de déboguer. - -Vous pouvez exécuter la boucle d'évaluation du `Trainer` indépendamment de l'entraînement comme ceci : - -```py -trainer.evaluate() -``` - -```python out -TypeError: only size-1 arrays can be converted to Python scalars -``` - - - -💡 Vous devriez toujours vous assurer que vous pouvez exécuter `trainer.evaluate()` avant de lancer `trainer.train()`, pour éviter de gaspiller beaucoup de ressources de calcul avant de tomber sur une erreur. - - - -Avant de tenter de déboguer un problème dans la boucle d'évaluation, vous devez d'abord vous assurer que vous avez examiné les données, que vous êtes en mesure de former un batch correctement et que vous pouvez exécuter votre modèle sur ces données. Nous avons effectué toutes ces étapes, et le code suivant peut donc être exécuté sans erreur : - -```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) -``` - -L'erreur survient plus tard, à la fin de la phase d'évaluation, et si nous regardons le *traceback*, nous voyons ceci : - -```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() -``` - -Cela nous indique que l'erreur provient du module `datasets/metric.py` donc c'est un problème avec notre fonction `compute_metrics()`. Elle prend un *tuple* avec les logits et les labels sous forme de tableaux NumPy, alors essayons de lui fournir cela : - -```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 -``` - -Nous obtenons la même erreur, donc le problème vient bien de cette fonction. Si on regarde son code, on voit qu'elle transmet simplement les `predictions` et les `labels` à `metric.compute()`. Y a-t-il donc un problème avec cette méthode ? Pas vraiment. Jetons un coup d'oeil rapide aux formes : - -```py -predictions.shape, labels.shape -``` - -```python out -((8, 3), (8,)) -``` - -Nos prédictions sont toujours des logits et non les prédictions réelles, c'est pourquoi la métrique retourne cette erreur (quelque peu obscure). La correction est assez simple, il suffit d'ajouter un argmax dans la fonction `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} -``` - -Maintenant notre erreur est corrigée ! C'était la dernière, donc notre script va maintenant entraîner un modèle correctement. - -Pour référence, voici le script complètement corrigé : - -```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() -``` - -Dans ce cas, il n'y a plus de problème, et notre script va *finetuner* un modèle qui devrait donner des résultats raisonnables. Mais que faire lorsque l'entraînement se déroule sans erreur et que le modèle entraîné n'est pas du tout performant ? C'est la partie la plus difficile de l'apprentissage automatique et nous allons vous montrer quelques techniques qui peuvent vous aider. - - - -💡 Si vous utilisez une boucle d'entraînement manuelle, les mêmes étapes s'appliquent pour déboguer votre pipeline d'entraînement, mais il est plus facile de les séparer. Assurez-vous cependant de ne pas avoir oublié le `model.eval()` ou le `model.train()` aux bons endroits, ou le `zero_grad()` à chaque étape ! - - - -## Déboguer les erreurs silencieuses pendant l'entraînement - -Que peut-on faire pour déboguer un entraînement qui se termine sans erreur mais qui ne donne pas de bons résultats ? Nous allons vous donner quelques pistes ici, mais sachez que ce type de débogage est la partie la plus difficile de l'apprentissage automatique et qu'il n'y a pas de réponse magique. - -### Vérifiez vos données (encore !) - -Votre modèle n'apprendra quelque chose que s'il est réellement possible d'apprendre quelque chose de vos données. Si un *bug* corrompt les données ou si les étiquettes sont attribuées de manière aléatoire, il est très probable que vous n'obtiendrez aucun entraînement de modèle sur votre jeu de données. Commencez donc toujours par revérifier vos entrées et étiquettes décodées, et posez-vous les questions suivantes : - -- les données décodées sont-elles compréhensibles ? -- êtes-vous d'accord avec les étiquettes ? -- y a-t-il une étiquette qui est plus courante que les autres ? -- quelle devrait être la perte/métrique si le modèle prédisait une réponse aléatoire/toujours la même réponse ? - - - -⚠️ Si vous effectuez un entraînement distribué, imprimez des échantillons de votre ensemble de données dans chaque processus et vérifiez par trois fois que vous obtenez la même chose. Un bug courant consiste à avoir une source d'aléa dans la création des données qui fait que chaque processus a une version différente du jeu de données. - - - -Après avoir examiné vos données, examinez quelques-unes des prédictions du modèle. Si votre modèle produit des *tokens*, essayez aussi de les décoder ! Si le modèle prédit toujours la même chose, cela peut être dû au fait que votre jeu de données est biaisé en faveur d'une catégorie (pour les problèmes de classification). Des techniques telles que le suréchantillonnage des classes rares peuvent aider. D'autre part, cela peut également être dû à des problèmes d'entraînement tels que de mauvais réglages des hyperparamètres. - -Si la perte/la métrique que vous obtenez sur votre modèle initial avant entraînement est très différente de la perte/la métrique à laquelle vous vous attendez pour des prédictions aléatoires, vérifiez la façon dont votre perte ou votre métrique est calculée. Il y a probablement un bug. Si vous utilisez plusieurs pertes que vous ajoutez à la fin, assurez-vous qu'elles sont de la même échelle. - -Lorsque vous êtes sûr que vos données sont parfaites, vous pouvez voir si le modèle est capable de s'entraîner sur elles grâce à un test simple. - -### Surentraînement du modèle sur un seul batch - -Le surentraînement est généralement une chose que nous essayons d'éviter lors de l'entraînement car cela signifie que le modèle n'apprend pas à reconnaître les caractéristiques générales que nous voulons qu'il reconnaisse et se contente de mémoriser les échantillons d'entraînement. Cependant, essayer d'entraîner votre modèle sur un batch encore et encore est un bon test pour vérifier si le problème tel que vous l'avez formulé peut être résolu par le modèle que vous essayez d'entraîner. Cela vous aidera également à voir si votre taux d'apprentissage initial est trop élevé. - -Une fois que vous avez défini votre `modèle`, c'est très facile. Il suffit de prendre un batch de données d'entraînement, puis de le traiter comme votre jeu de données entier que vous *finetunez* sur un grand nombre d'époques : - -```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() -``` - - - -💡 Si vos données d'entraînement ne sont pas équilibrées, veillez à créer un batch de données d'entraînement contenant toutes les étiquettes. - - - -Le modèle résultant devrait avoir des résultats proches de la perfection sur le même `batch`. Calculons la métrique sur les prédictions résultantes : - -```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% de précision, voilà un bel exemple de surentraînement (ce qui signifie que si vous essayez votre modèle sur n'importe quelle autre phrase, il vous donnera très probablement une mauvaise réponse) ! - -Si vous ne parvenez pas à ce que votre modèle obtienne des résultats parfaits comme celui-ci, cela signifie qu'il y a quelque chose qui ne va pas dans la façon dont vous avez formulé le problème ou dans vos données. Vous devez donc y remédier. Ce n'est que lorsque vous parviendrez à passer le test de surentraînement que vous pourrez être sûr que votre modèle peut réellement apprendre quelque chose. - - - -⚠️ Vous devrez recréer votre modèle et votre `Trainer` après ce test, car le modèle obtenu ne sera probablement pas capable de récupérer et d'apprendre quelque chose d'utile sur votre jeu de données complet. - - - -### Ne réglez rien tant que vous n'avez pas une première ligne de base - -Le réglage des hyperparamètres est toujours considéré comme la partie la plus difficile de l'apprentissage automatique mais c'est juste la dernière étape pour vous aider à gagner un peu sur la métrique. La plupart du temps, les hyperparamètres par défaut du `Trainer` fonctionneront très bien pour vous donner de bons résultats. Donc ne vous lancez pas dans une recherche d'hyperparamètres longue et coûteuse jusqu'à ce que vous ayez quelque chose qui batte la ligne de base que vous avez sur votre jeu de données. - -Une fois que vous avez un modèle suffisamment bon, vous pouvez commencer à le *finetuner* un peu. N'essayez pas de lancer un millier d'exécutions avec différents hyperparamètres mais comparez quelques exécutions avec différentes valeurs pour un hyperparamètre afin de vous faire une idée de celui qui a le plus d'impact. - -Si vous modifiez le modèle lui-même, restez simple et n'essayez rien que vous ne puissiez raisonnablement justifier. Veillez toujours à revenir au test de surentraînement pour vérifier que votre modification n'a pas eu de conséquences inattendues. - -### Demander de l'aide - -Nous espérons que vous avez trouvé dans cette section des conseils qui vous ont aidé à résoudre votre problème. Si ce n'est pas le cas, n'oubliez pas que vous pouvez toujours demander de l'aide à la communauté sur le [forum](https://discuss.huggingface.co/). - -Voici quelques ressources (en anglais) supplémentaires qui peuvent s'avérer utiles : - -- [La reproductibilité comme vecteur des meilleures pratiques d'ingénierie](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p) par Joel Grus -- [Liste de contrôle pour le débogage des réseaux de neurones](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) par Cecelia Shao -- [Comment tester unitairement le code d'apprentissage automatique](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) par Chase Roberts -- [Une recette pour entraîner les réseaux de neurones](http://karpathy.github.io/2019/04/25/recipe/) par Andrej Karpathy - -Bien sûr, tous les problèmes rencontrés lors de l'entraînement ne sont pas forcément de votre faute ! Si vous rencontrez quelque chose dans la bibliothèque 🤗 *Transformers* ou 🤗 *Datasets* qui ne semble pas correct, vous avez peut-être trouver un *bug*. Vous devez absolument nous en parler pour qu'on puisse le corriger. Dans la section suivante, nous allons vous expliquer exactement comment faire. + + +# Débogage du pipeline d'entraînement + + + +Vous avez écrit un magnifique script pour entraîner ou *finetuner* un modèle sur une tâche donnée en suivant consciencieusement les conseils du [chapitre 7](/course/fr/chapter7). Mais lorsque vous lancez la commande `model.fit()`, quelque chose d'horrible se produit : vous obtenez une erreur 😱 ! Ou pire, tout semble aller bien et l'entraînement se déroule sans erreur mais le modèle résultant est mauvais. Dans cette section, nous allons vous montrer ce que vous pouvez faire pour déboguer ce genre de problèmes. + +## Déboguer le pipeline d'entraînement + + + +Le problème lorsque vous rencontrez une erreur dans `trainer.train()` est qu'elle peut provenir de plusieurs sources, car la fonction `Trainer` assemble généralement des batchs de choses. Elle convertit les jeux de données en chargeurs de données donc le problème pourrait être quelque chose d'erroné dans votre jeu de données, ou un problème en essayant de regrouper les éléments des jeux de données ensemble. Ensuite, elle prend un batch de données et le transmet au modèle, le problème peut donc se situer dans le code du modèle. Après cela, elle calcule les gradients et effectue l'étape d'optimisation, le problème peut donc également se situer dans votre optimiseur. Et même si tout se passe bien pendant l'entraînement, quelque chose peut encore mal tourner pendant l'évaluation si votre métrique pose problème. + +La meilleure façon de déboguer une erreur qui survient dans `trainer.train()` est de passer manuellement en revue tout le pipeline pour voir où les choses se sont mal passées. L'erreur est alors souvent très facile à résoudre. + +Pour le démontrer, nous utiliserons le script suivant qui tente de *finetuner* un modèle DistilBERT sur le [jeu de données MNLI](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() +``` + +Si vous essayez de l'exécuter, vous serez confronté à une erreur plutôt cryptique : + +```python out +'ValueError: You have to specify either input_ids or inputs_embeds' +``` + +### Vérifiez vos données + +Cela va sans dire, mais si vos données sont corrompues, le `Trainer` ne sera pas capable de former des batchs et encore moins d'entraîner votre modèle. Donc, tout d'abord, vous devez jeter un coup d'oeil à ce qui se trouve dans votre jeu d'entraînement. + +Pour éviter d'innombrables heures passées à essayer de corriger quelque chose qui n'est pas la source du bug, nous vous recommandons d'utiliser `trainer.train_dataset` pour vos vérifications et rien d'autre. Faisons donc cela ici : + +```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.'} +``` + +Vous remarquez quelque chose d'anormal ? Ceci, en conjonction avec le message d'erreur sur les `input_ids` manquants, devrait vous faire réaliser que ce sont des textes et non des nombres que le modèle peut comprendre. Ici, l'erreur originale est très trompeuse parce que le `Trainer` enlève automatiquement les colonnes qui ne correspondent pas à la signature du modèle (c'est-à-dire, les arguments attendus par le modèle). Cela signifie qu'ici, tout, sauf les étiquettes, a été éliminé. Il n'y avait donc aucun problème à créer des batchs et à les envoyer ensuite au modèle, qui s'est plaint à son tour de ne pas avoir reçu les bons arguments. + +Pourquoi les données n'ont-elles pas été traitées ? Nous avons utilisé la méthode `Dataset.map()` sur les jeux de données pour appliquer le *tokenizer* sur chaque échantillon. Mais si vous regardez attentivement le code, vous verrez que nous avons fait une erreur en passant les ensembles d'entraînement et d'évaluation au `Trainer`. Au lieu d'utiliser `tokenized_datasets` ici, nous avons utilisé `raw_datasets` 🤦. Alors corrigeons ça ! + +```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() +``` + +Ce nouveau code donnera maintenant une erreur différente (c'est un progrès !) : + +```python out +'ValueError: expected sequence of length 43 at dim 1 (got 37)' +``` + +En regardant le *traceback*, nous pouvons voir que l'erreur se produit dans l'étape de collationnement des données : + +```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 +``` + +Donc, nous devrions passer à cela. Mais avant finissons d'inspecter nos données, pour être sûrs à 100% qu'elles sont correctes. + +Une chose que vous devriez toujours faire lorsque vous déboguez une session d'entraînement est de jeter un coup d'oeil aux entrées décodées de votre modèle. Nous ne pouvons pas donner un sens aux chiffres que nous lui fournissons directement, nous devons donc examiner ce que ces chiffres représentent. Dans le domaine de la vision par ordinateur cela signifie regarder les images décodées des pixels que vous passez, dans le domaine de la parole cela signifie écouter les échantillons audio décodés, et pour notre exemple de NLP cela signifie utiliser notre *tokenizer* pour décoder les entrées : + +```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]' +``` + +Cela semble correct. Vous devriez faire cela pour toutes les clés dans les entrées : + +```py +trainer.train_dataset[0].keys() +``` + +```python out +dict_keys(['attention_mask', 'hypothesis', 'idx', 'input_ids', 'label', 'premise']) +``` + +Notez que les clés qui ne correspondent pas à des entrées acceptées par le modèle seront automatiquement écartées, donc ici nous ne garderons que `input_ids`, `attention_mask`, et `label` (qui sera renommé `labels`). Pour revérifier la signature du modèle, vous pouvez imprimer la classe de votre modèle, puis aller consulter sa documentation : + +```py +type(trainer.model) +``` + +```python out +transformers.models.distilbert.modeling_distilbert.DistilBertForSequenceClassification +``` + +Donc dans notre cas, nous pouvons vérifier les paramètres acceptés sur [cette page](https://huggingface.co/transformers/model_doc/distilbert.html#distilbertforsequenceclassification). Le `Trainer` va également enregistrer les colonnes qu'il rejette. + +Nous avons vérifié que les identifiants d'entrée sont corrects en les décodant. Ensuite, il y a le `attention_mask` : + +```py +tokenizer.decode(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] +``` + +Comme nous n'avons pas appliqué de *padding* dans notre prétraitement, cela semble parfaitement naturel. Pour être sûr qu'il n'y a pas de problème avec ce masque d'attention, vérifions qu'il est de la même longueur que nos identifiants d'entrée : + +```py +len(trainer.train_dataset[0]["attention_mask"]) == len( + trainer.train_dataset[0]["input_ids"] +) +``` + +```python out +True +``` + +C'est bien ! Enfin, vérifions notre étiquette : + +```py +trainer.train_dataset[0]["label"] +``` + +```python out +1 +``` + +Comme les identifiants d'entrée, c'est un nombre qui n'a pas vraiment de sens en soi. Comme nous l'avons vu précédemment, la correspondance entre les entiers et les noms d'étiquettes est stockée dans l'attribut `names` de la *caractéristique* correspondante du jeu de données : + +```py +trainer.train_dataset.features["label"].names +``` + +```python out +['entailment', 'neutral', 'contradiction'] +``` + +Donc `1` signifie `neutral`, ce qui signifie que les deux phrases que nous avons vues ci-dessus ne sont pas en contradiction : la première n'implique pas la seconde. Cela semble correct ! + +Nous n'avons pas de *token* de type identifiant ici puisque DistilBERT ne les attend pas. Si vous en avez dans votre modèle, vous devriez également vous assurer qu'ils correspondent correctement à l'endroit où se trouvent la première et la deuxième phrase dans l'entrée. + + + +✏️ *A votre tour !* Vérifiez que tout semble correct avec le deuxième élément du jeu de données d'entraînement. + + + +Ici nous ne vérifions que le jeu d'entraînement. Vous devez bien sûr vérifier de la même façon les jeux de validation et de test. + +Maintenant que nous savons que nos jeux de données sont bons, il est temps de vérifier l'étape suivante du pipeline d'entraînement. + +### Des jeux de données aux chargeurs de données + +La prochaine chose qui peut mal tourner dans le pipeline d'entraînement est lorsque le `Trainer` essaie de former des batchs à partir du jeu d'entraînement ou de validation. Une fois que vous êtes sûr que les jeux de données du `Trainer` sont corrects, vous pouvez essayer de former manuellement un batch en exécutant ce qui suit (remplacez `train` par `eval` pour le *dataloader* de validation) : + +```py +for batch in trainer.get_train_dataloader(): + break +``` + +Ce code crée le *dataloader* d'entraînement puis le parcourt en s'arrêtant à la première itération. Si le code s'exécute sans erreur, vous avez le premier batch d'entraînement que vous pouvez inspecter, et si le code se trompe, vous êtes sûr que le problème se situe dans le *dataloader*, comme c'est le cas ici : + +```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) +``` + +L'inspection de la dernière image du *traceback* devrait suffire à vous donner un indice mais creusons un peu plus. La plupart des problèmes lors de la création d'un batch sont dus à l'assemblage des exemples en un seul batch. La première chose à vérifier en cas de doute est le `collate_fn` utilisé par votre `DataLoader` : + +```py +data_collator = trainer.get_train_dataloader().collate_fn +data_collator +``` + +```python out + Dict[str, Any]> +``` + +C'est donc `default_data_collator`, mais ce n'est pas ce que nous voulons dans ce cas. Nous voulons rembourrer nos exemples à la phrase la plus longue du batch, ce qui est fait par `DataCollatorWithPadding`. Et cette assembleur de données est censé être utilisé par défaut par le `Trainer`, alors pourquoi n'est-il pas utilisé ici ? + +La réponse est que nous n'avons pas passé le `tokenizer` au `Trainer`, donc il ne pouvait pas créer le `DataCollatorWithPadding` que nous voulons. En pratique, il ne faut jamais hésiter à transmettre explicitement l'assembleur de données que l'on veut utiliser pour être sûr d'éviter ce genre d'erreurs. Adaptons notre code pour faire exactement cela : + +```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() +``` + +La bonne nouvelle ? Nous n'avons plus la même erreur qu'avant, ce qui est un progrès certain. La mauvaise nouvelle ? Nous obtenons une erreur CUDA infâme à la place : + +```python out +RuntimeError: CUDA error: CUBLAS_STATUS_ALLOC_FAILED when calling `cublasCreate(handle)` +``` + +C'est une mauvaise chose car les erreurs CUDA sont extrêmement difficiles à déboguer en général. Nous verrons dans une minute comment résoudre ce problème mais terminons d'abord notre analyse de la création de batchs. + +Si vous êtes sûr que votre collecteur de données est le bon, vous devriez essayer de l'appliquer sur quelques échantillons de votre jeu de données : + +```py +data_collator = trainer.get_train_dataloader().collate_fn +batch = data_collator([trainer.train_dataset[i] for i in range(4)]) +``` + +Ce code échouera parce que le `train_dataset` contient des colonnes de type *string* que le `Trainer` supprime habituellement. Vous pouvez les supprimer manuellement ou si vous voulez reproduire exactement ce que le `Trainer` fait en coulisse, vous pouvez appeler la méthode `Trainer._remove_unused_columns()` qui fait cela : + +```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)]) +``` + +Vous devriez alors être en mesure de déboguer manuellement ce qui se passe dans le collecteur de données si l'erreur persiste. + +Maintenant que nous avons débogué le processus de création de batch, il est temps d'en passer un dans le modèle ! + + +### Passage par le modèle + +Vous devriez être en mesure d'obtenir un batch en exécutant la commande suivante : + +```py +for batch in trainer.get_train_dataloader(): + break +``` + +Si vous exécutez ce code dans un *notebook*, vous risquez d'obtenir une erreur CUDA similaire à celle que nous avons vue précédemment, auquel cas vous devrez redémarrer votre *notebook* et réexécuter le dernier extrait sans la ligne `trainer.train()`. C'est la deuxième chose la plus ennuyeuse à propos des erreurs CUDA : elles cassent irrémédiablement votre noyau. La première plus ennuyeuse est le fait qu'elles sont difficiles à déboguer. + +Comment cela se fait-il ? Cela tient à la façon dont les GPUs fonctionnent. Ils sont extrêmement efficaces pour exécuter un batch d'opérations en parallèle, mais l'inconvénient est que lorsque l'une de ces instructions entraîne une erreur, vous ne le savez pas immédiatement. Ce n'est que lorsque le programme appelle une synchronisation des multiples processus sur le GPU qu'il réalise que quelque chose s'est mal passé, de sorte que l'erreur est en fait mentionnée à un endroit qui n'a rien à voir avec ce qui l'a créée. Par exemple, si nous regardons notre *traceback* précédent, l'erreur a été soulevée pendant la passe arrière, mais nous verrons dans une minute qu'elle provient en fait de quelque chose dans la passe avant. + +Alors comment déboguer ces erreurs ? La réponse est simple : nous ne le faisons pas. À moins que votre erreur CUDA ne soit une erreur *out-of-memory* (ce qui signifie qu'il n'y a pas assez de mémoire dans votre GPU), vous devez toujours revenir au CPU pour la déboguer. + +Pour faire cela dans notre cas, nous devons juste remettre le modèle sur le CPU et l'appeler sur notre batch. Le batch retourné par le `DataLoader` n'a pas encore été déplacé sur le 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. +``` + +L'image devient plus claire. Au lieu d'avoir une erreur CUDA, nous avons maintenant une `IndexError` dans le calcul de la perte (donc rien à voir avec la passe arrière comme nous l'avons dit plus tôt). Plus précisément, nous pouvons voir que c'est la cible 2 qui crée l'erreur, donc c'est un bon moment pour vérifier le nombre de labels de notre modèle : + +```python +trainer.model.config.num_labels +``` + +```python out +2 +``` + +Avec deux étiquettes, seuls les 0 et les 1 sont autorisés comme cibles, mais d'après le message d'erreur, nous avons obtenu un 2. Obtenir un 2 est en fait normal : si nous nous souvenons des noms des étiquettes que nous avons extraits plus tôt, il y en avait trois, donc nous avons les indices 0, 1 et 2 dans notre jeu de données. Le problème est que nous n'avons pas indiqué cela à notre modèle, qui aurait dû être créé avec trois étiquettes. Alors, corrigeons cela ! + +```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, +) +``` + +Nous n'incluons pas encore la ligne `trainer.train()` pour prendre le temps de vérifier que tout se passe bien. Si nous passons un batch à notre modèle, il fonctionne maintenant sans erreur ! + +```py +for batch in trainer.get_train_dataloader(): + break + +outputs = trainer.model.cpu()(**batch) +``` + +L'étape suivante consiste alors à revenir au GPU et à vérifier que tout fonctionne encore : + +```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) +``` + +Si vous obtenez toujours une erreur, assurez-vous de redémarrer votre *notebook* et d'exécuter uniquement la dernière version du script. + +### Exécution d'une étape d'optimisation + +Maintenant que nous savons que nous pouvons construire des batchs qui passent réellement par le modèle, nous sommes prêts pour l'étape suivante du pipeline d'entraînement : calculer les gradients et effectuer une étape d'optimisation. + +La première partie est juste une question d'appel de la méthode `backward()` sur la perte : + +```py +loss = outputs.loss +loss.backward() +``` + +Il est plutôt rare d'obtenir une erreur à ce stade, mais si vous en obtenez une, assurez-vous de retourner au CPU pour obtenir un message d'erreur utile. + +Pour effectuer l'étape d'optimisation, il suffit de créer le `optimizer` et d'appeler sa méthode `step()` : + +```py +trainer.create_optimizer() +trainer.optimizer.step() +``` + +Encore une fois, si vous utilisez l'optimiseur par défaut dans le `Trainer`, vous ne devriez pas avoir d'erreur à ce stade, mais si vous avez un optimiseur personnalisé, il pourrait y avoir quelques problèmes à déboguer ici. N'oubliez pas de revenir au CPU si vous obtenez une erreur CUDA bizarre à ce stade. En parlant d'erreurs CUDA, nous avons mentionné précédemment un cas particulier. Voyons cela maintenant. + +### Gérer les erreurs CUDA out of memory + +Chaque fois que vous obtenez un message d'erreur qui commence par `RuntimeError : CUDA out of memory`, cela indique que vous êtes à court de mémoire GPU. Cela n'est pas directement lié à votre code et peut arriver avec un script qui fonctionne parfaitement bien. Cette erreur signifie que vous avez essayé de mettre trop de choses dans la mémoire interne de votre GPU et que cela a entraîné une erreur. Comme pour d'autres erreurs CUDA, vous devrez redémarrer votre noyau pour être en mesure d'exécuter à nouveau votre entraînement. + +Pour résoudre ce problème, il suffit d'utiliser moins d'espace GPU, ce qui est souvent plus facile à dire qu'à faire. Tout d'abord, assurez-vous que vous n'avez pas deux modèles sur le GPU en même temps (sauf si cela est nécessaire pour votre problème, bien sûr). Ensuite, vous devriez probablement réduire la taille de votre batch car elle affecte directement les tailles de toutes les sorties intermédiaires du modèle et leurs gradients. Si le problème persiste, envisagez d'utiliser une version plus petite de votre modèle. + + + +Dans la prochaine partie du cours, nous examinerons des techniques plus avancées qui peuvent vous aider à réduire votre empreinte mémoire et vous permettre de finetuner les plus grands modèles. + + + +### Évaluation du modèle + +Maintenant que nous avons résolu tous les problèmes liés à notre code, tout est parfait et l'entraînement devrait se dérouler sans problème, n'est-ce pas ? Pas si vite ! Si vous exécutez la commande `trainer.train()`, tout aura l'air bien au début, mais après un moment vous obtiendrez ce qui suit : + +```py +# Cela prendra beaucoup de temps et se soldera par une erreur, vous ne devriez donc pas utiliser cette cellule. +trainer.train() +``` + +```python out +TypeError: only size-1 arrays can be converted to Python scalars +``` + +Vous réaliserez que cette erreur apparaît pendant la phase d'évaluation, donc c'est la dernière chose que nous aurons besoin de déboguer. + +Vous pouvez exécuter la boucle d'évaluation du `Trainer` indépendamment de l'entraînement comme ceci : + +```py +trainer.evaluate() +``` + +```python out +TypeError: only size-1 arrays can be converted to Python scalars +``` + + + +💡 Vous devriez toujours vous assurer que vous pouvez exécuter `trainer.evaluate()` avant de lancer `trainer.train()`, pour éviter de gaspiller beaucoup de ressources de calcul avant de tomber sur une erreur. + + + +Avant de tenter de déboguer un problème dans la boucle d'évaluation, vous devez d'abord vous assurer que vous avez examiné les données, que vous êtes en mesure de former un batch correctement et que vous pouvez exécuter votre modèle sur ces données. Nous avons effectué toutes ces étapes, et le code suivant peut donc être exécuté sans erreur : + +```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) +``` + +L'erreur survient plus tard, à la fin de la phase d'évaluation, et si nous regardons le *traceback*, nous voyons ceci : + +```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() +``` + +Cela nous indique que l'erreur provient du module `datasets/metric.py` donc c'est un problème avec notre fonction `compute_metrics()`. Elle prend un *tuple* avec les logits et les labels sous forme de tableaux NumPy, alors essayons de lui fournir cela : + +```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 +``` + +Nous obtenons la même erreur, donc le problème vient bien de cette fonction. Si on regarde son code, on voit qu'elle transmet simplement les `predictions` et les `labels` à `metric.compute()`. Y a-t-il donc un problème avec cette méthode ? Pas vraiment. Jetons un coup d'oeil rapide aux formes : + +```py +predictions.shape, labels.shape +``` + +```python out +((8, 3), (8,)) +``` + +Nos prédictions sont toujours des logits et non les prédictions réelles, c'est pourquoi la métrique retourne cette erreur (quelque peu obscure). La correction est assez simple, il suffit d'ajouter un argmax dans la fonction `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} +``` + +Maintenant notre erreur est corrigée ! C'était la dernière, donc notre script va maintenant entraîner un modèle correctement. + +Pour référence, voici le script complètement corrigé : + +```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() +``` + +Dans ce cas, il n'y a plus de problème, et notre script va *finetuner* un modèle qui devrait donner des résultats raisonnables. Mais que faire lorsque l'entraînement se déroule sans erreur et que le modèle entraîné n'est pas du tout performant ? C'est la partie la plus difficile de l'apprentissage automatique et nous allons vous montrer quelques techniques qui peuvent vous aider. + + + +💡 Si vous utilisez une boucle d'entraînement manuelle, les mêmes étapes s'appliquent pour déboguer votre pipeline d'entraînement, mais il est plus facile de les séparer. Assurez-vous cependant de ne pas avoir oublié le `model.eval()` ou le `model.train()` aux bons endroits, ou le `zero_grad()` à chaque étape ! + + + +## Déboguer les erreurs silencieuses pendant l'entraînement + +Que peut-on faire pour déboguer un entraînement qui se termine sans erreur mais qui ne donne pas de bons résultats ? Nous allons vous donner quelques pistes ici, mais sachez que ce type de débogage est la partie la plus difficile de l'apprentissage automatique et qu'il n'y a pas de réponse magique. + +### Vérifiez vos données (encore !) + +Votre modèle n'apprendra quelque chose que s'il est réellement possible d'apprendre quelque chose de vos données. Si un *bug* corrompt les données ou si les étiquettes sont attribuées de manière aléatoire, il est très probable que vous n'obtiendrez aucun entraînement de modèle sur votre jeu de données. Commencez donc toujours par revérifier vos entrées et étiquettes décodées, et posez-vous les questions suivantes : + +- les données décodées sont-elles compréhensibles ? +- êtes-vous d'accord avec les étiquettes ? +- y a-t-il une étiquette qui est plus courante que les autres ? +- quelle devrait être la perte/métrique si le modèle prédisait une réponse aléatoire/toujours la même réponse ? + + + +⚠️ Si vous effectuez un entraînement distribué, imprimez des échantillons de votre ensemble de données dans chaque processus et vérifiez par trois fois que vous obtenez la même chose. Un bug courant consiste à avoir une source d'aléa dans la création des données qui fait que chaque processus a une version différente du jeu de données. + + + +Après avoir examiné vos données, examinez quelques-unes des prédictions du modèle. Si votre modèle produit des *tokens*, essayez aussi de les décoder ! Si le modèle prédit toujours la même chose, cela peut être dû au fait que votre jeu de données est biaisé en faveur d'une catégorie (pour les problèmes de classification). Des techniques telles que le suréchantillonnage des classes rares peuvent aider. D'autre part, cela peut également être dû à des problèmes d'entraînement tels que de mauvais réglages des hyperparamètres. + +Si la perte/la métrique que vous obtenez sur votre modèle initial avant entraînement est très différente de la perte/la métrique à laquelle vous vous attendez pour des prédictions aléatoires, vérifiez la façon dont votre perte ou votre métrique est calculée. Il y a probablement un bug. Si vous utilisez plusieurs pertes que vous ajoutez à la fin, assurez-vous qu'elles sont de la même échelle. + +Lorsque vous êtes sûr que vos données sont parfaites, vous pouvez voir si le modèle est capable de s'entraîner sur elles grâce à un test simple. + +### Surentraînement du modèle sur un seul batch + +Le surentraînement est généralement une chose que nous essayons d'éviter lors de l'entraînement car cela signifie que le modèle n'apprend pas à reconnaître les caractéristiques générales que nous voulons qu'il reconnaisse et se contente de mémoriser les échantillons d'entraînement. Cependant, essayer d'entraîner votre modèle sur un batch encore et encore est un bon test pour vérifier si le problème tel que vous l'avez formulé peut être résolu par le modèle que vous essayez d'entraîner. Cela vous aidera également à voir si votre taux d'apprentissage initial est trop élevé. + +Une fois que vous avez défini votre `modèle`, c'est très facile. Il suffit de prendre un batch de données d'entraînement, puis de le traiter comme votre jeu de données entier que vous *finetunez* sur un grand nombre d'époques : + +```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() +``` + + + +💡 Si vos données d'entraînement ne sont pas équilibrées, veillez à créer un batch de données d'entraînement contenant toutes les étiquettes. + + + +Le modèle résultant devrait avoir des résultats proches de la perfection sur le même `batch`. Calculons la métrique sur les prédictions résultantes : + +```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% de précision, voilà un bel exemple de surentraînement (ce qui signifie que si vous essayez votre modèle sur n'importe quelle autre phrase, il vous donnera très probablement une mauvaise réponse) ! + +Si vous ne parvenez pas à ce que votre modèle obtienne des résultats parfaits comme celui-ci, cela signifie qu'il y a quelque chose qui ne va pas dans la façon dont vous avez formulé le problème ou dans vos données. Vous devez donc y remédier. Ce n'est que lorsque vous parviendrez à passer le test de surentraînement que vous pourrez être sûr que votre modèle peut réellement apprendre quelque chose. + + + +⚠️ Vous devrez recréer votre modèle et votre `Trainer` après ce test, car le modèle obtenu ne sera probablement pas capable de récupérer et d'apprendre quelque chose d'utile sur votre jeu de données complet. + + + +### Ne réglez rien tant que vous n'avez pas une première ligne de base + +Le réglage des hyperparamètres est toujours considéré comme la partie la plus difficile de l'apprentissage automatique mais c'est juste la dernière étape pour vous aider à gagner un peu sur la métrique. La plupart du temps, les hyperparamètres par défaut du `Trainer` fonctionneront très bien pour vous donner de bons résultats. Donc ne vous lancez pas dans une recherche d'hyperparamètres longue et coûteuse jusqu'à ce que vous ayez quelque chose qui batte la ligne de base que vous avez sur votre jeu de données. + +Une fois que vous avez un modèle suffisamment bon, vous pouvez commencer à le *finetuner* un peu. N'essayez pas de lancer un millier d'exécutions avec différents hyperparamètres mais comparez quelques exécutions avec différentes valeurs pour un hyperparamètre afin de vous faire une idée de celui qui a le plus d'impact. + +Si vous modifiez le modèle lui-même, restez simple et n'essayez rien que vous ne puissiez raisonnablement justifier. Veillez toujours à revenir au test de surentraînement pour vérifier que votre modification n'a pas eu de conséquences inattendues. + +### Demander de l'aide + +Nous espérons que vous avez trouvé dans cette section des conseils qui vous ont aidé à résoudre votre problème. Si ce n'est pas le cas, n'oubliez pas que vous pouvez toujours demander de l'aide à la communauté sur le [forum](https://discuss.huggingface.co/). + +Voici quelques ressources (en anglais) supplémentaires qui peuvent s'avérer utiles : + +- [La reproductibilité comme vecteur des meilleures pratiques d'ingénierie](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p) par Joel Grus +- [Liste de contrôle pour le débogage des réseaux de neurones](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) par Cecelia Shao +- [Comment tester unitairement le code d'apprentissage automatique](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) par Chase Roberts +- [Une recette pour entraîner les réseaux de neurones](http://karpathy.github.io/2019/04/25/recipe/) par Andrej Karpathy + +Bien sûr, tous les problèmes rencontrés lors de l'entraînement ne sont pas forcément de votre faute ! Si vous rencontrez quelque chose dans la bibliothèque 🤗 *Transformers* ou 🤗 *Datasets* qui ne semble pas correct, vous avez peut-être trouver un *bug*. Vous devez absolument nous en parler pour qu'on puisse le corriger. Dans la section suivante, nous allons vous expliquer exactement comment faire. diff --git a/chapters/fr/chapter8/4_tf.mdx b/chapters/fr/chapter8/4_tf.mdx index 257dafe26..c4b4c8b84 100644 --- a/chapters/fr/chapter8/4_tf.mdx +++ b/chapters/fr/chapter8/4_tf.mdx @@ -1,489 +1,489 @@ - - -# Débogage du pipeline d'entraînement - - - -Vous avez écrit un magnifique script pour entraîner ou *finetuner* un modèle sur une tâche donnée en suivant consciencieusement les conseils du [chapitre 7](/course/fr/chapter7). Mais lorsque vous lancez la commande `model.fit()`, quelque chose d'horrible se produit : vous obtenez une erreur 😱 ! Ou pire, tout semble aller bien et l'entraînement se déroule sans erreur mais le modèle résultant est mauvais. Dans cette section, nous allons vous montrer ce que vous pouvez faire pour déboguer ce genre de problèmes. - -## Déboguer le pipeline d'entraînement - - - -Le problème lorsque vous rencontrez une erreur dans `trainer.train()` est qu'elle peut provenir de plusieurs sources, car la fonction `Trainer` assemble généralement des batchs de choses. Elle convertit les jeux de données en chargeurs de données donc le problème pourrait être quelque chose d'erroné dans votre jeu de données, ou un problème en essayant de regrouper les éléments des jeux de données ensemble. Ensuite, elle prend un batch de données et le transmet au modèle, le problème peut donc se situer dans le code du modèle. Après cela, elle calcule les gradients et effectue l'étape d'optimisation, le problème peut donc également se situer dans votre optimiseur. Et même si tout se passe bien pendant l'entraînement, quelque chose peut encore mal tourner pendant l'évaluation si votre métrique pose problème. - -La meilleure façon de déboguer une erreur qui survient dans `trainer.train()` est de passer manuellement en revue tout le pipeline pour voir où les choses se sont mal passées. L'erreur est alors souvent très facile à résoudre. - -Pour le démontrer, nous utiliserons le script suivant qui tente de *finetuner* un modèle DistilBERT sur le [jeu de données MNLI](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) -``` - -Si vous essayez de l'exécuter, il se peut que vous obteniez des `VisibleDeprecationWarning`s lors de la conversion du jeu de données. Il s'agit d'un problème UX connu par l'équipe d'Hugging Face, donc veuillez l'ignorer. Si vous lisez le cours après novembre 2021 et que cela se produit encore, envoyez des tweets de rage à @carrigmat jusqu'à ce qu'il le corrige. - -Le problème cependant est que nous avons une erreur flagrante. Et c'est vraiment, terriblement long : - -```python out -ValueError: No gradients provided for any variable: ['tf_distil_bert_for_sequence_classification/distilbert/embeddings/word_embeddings/weight:0', '...'] -``` - -Qu'est-ce que cela signifie ? Nous avons essayé d'entraîner sur nos données mais nous n'avons pas obtenu de gradient. C'est assez déconcertant. Comment commencer à déboguer quelque chose comme ça ? Lorsque l'erreur que vous obtenez ne suggère pas immédiatement l'origine du problème, la meilleure solution consiste souvent à procéder par étapes, en s'assurant à chaque fois que tout semble correct. Et bien sûr, il faut toujours commencer par... - -### Vérifier vos données - -Cela va sans dire, mais si vos données sont corrompues, Keras ne sera pas en mesure de les réparer pour vous. Avant toute chose, vous devez donc jeter un coup d'œil à ce que contient votre ensemble d'entraînement. - -Bien qu'il soit tentant de regarder dans `raw_datasets` et `tokenized_datasets`, nous vous recommandons fortement d'aller voir les données au moment où elles vont entrer dans le modèle. Cela signifie lire une sortie du `tf.data.Dataset` que vous avez créé avec la fonction `to_tf_dataset()` ! Alors comment faire ? Les objets `tf.data.Dataset` nous donnent des batchs entiers à la fois et ne supportent pas l'indexation, donc nous ne pouvons pas simplement demander `train_dataset[0]`. Nous pouvons, cependant, lui demander poliment un batch : - -```py -for batch in train_dataset: - break -``` - -`break` termine la boucle après une itération, donc cela prend le premier batch qui sort de `train_dataset` et l'enregistre comme `batch`. Maintenant, jetons un coup d'oeil à ce qu'il y a à l'intérieur : - -```python out -{'attention_mask': , - 'label': , - 'input_ids': } -``` - -Cela semble correct. Nous passons les `labels`, `attention_mask`, et `input_ids` au modèle, ce qui devrait être tout ce dont il a besoin pour calculer les sorties et la perte. Alors pourquoi n'avons-nous pas de gradient ? Regardez de plus près : nous passons un seul dictionnaire en entrée mais un batch d'entraînement est généralement un tenseur ou un dictionnaire d'entrée, plus un tenseur d'étiquettes. Nos étiquettes sont juste une clé dans notre dictionnaire d'entrée. - -Est-ce un problème ? Pas toujours, en fait ! Mais c'est l'un des problèmes les plus courants que vous rencontrerez lorsque vous entraînerez des *transformers* avec TensorFlow. Nos modèles peuvent tous calculer la perte en interne, mais pour ce faire, les étiquettes doivent être transmises dans le dictionnaire d'entrée. C'est la perte qui est utilisée lorsque nous ne spécifions pas de valeur de perte à `compile()`. Keras, d'autre part, s'attend généralement à ce que les étiquettes soient passées séparément du dictionnaire d'entrée, et les calculs de perte échoueront généralement si vous ne le faites pas. - -Le problème est maintenant devenu plus clair : nous avons passé un argument `loss`, ce qui signifie que nous demandons à Keras de calculer les pertes pour nous, mais nous avons passé nos étiquettes comme entrées au modèle, et non comme étiquettes à l'endroit où Keras les attend ! Nous devons choisir l'un ou l'autre : soit nous utilisons la perte interne du modèle et gardons les étiquettes où elles sont, soit nous continuons à utiliser les pertes de Keras, mais nous déplaçons les étiquettes à l'endroit où Keras les attend. Pour simplifier, prenons la première approche. Changez l'appel à `compile()` pour lire : - -```py -model.compile(optimizer="adam") -``` - -Maintenant, nous allons utiliser la perte interne du modèle et ce problème devrait être résolu ! - - - -✏️ *A votre tour !* Comme défi optionnel après avoir résolu les autres problèmes, vous pouvez essayer de revenir à cette étape et faire fonctionner le modèle avec la perte originale calculée par Keras au lieu de la perte interne. Vous devrez ajouter `"labels"` à l'argument `label_cols` de `to_tf_dataset()` pour vous assurer que les labels sont correctement sortis, ce qui vous donnera des gradients. Mais il y a un autre problème avec la perte que nous avons spécifiée. L'entraînement fonctionnera toujours avec ce problème mais l'apprentissage sera très lent et se stabilisera à une perte d'entraînement élevée. Pouvez-vous trouver ce que c'est ? - -Un indice codé en ROT13, si vous êtes coincé : Vs lbh ybbx ng gur bhgchgf bs FrdhraprPynffvsvpngvba zbqryf va Genafsbezref, gurve svefg bhgchg vf `ybtvgf`. Jung ner ybtvgf ? - -Et un deuxième indice : Jura lbh fcrpvsl bcgvzvmref, npgvingvbaf be ybffrf jvgu fgevatf, Xrenf frgf ny gur nethzrag inyhrf gb gurve qrsnhygf. Jung nethzragf qbrf FcnefrPngrtbevpnyPebffragebcl unir, naq jung ner gurve qrsnhygf ? - - - -Maintenant, essayons d'entraîner. Nous devrions obtenir des gradients maintenant, donc avec un peu de chance nous pouvons juste appeler `model.fit()` et tout fonctionnera bien ! - -```python out - 246/24543 [..............................] - ETA: 15:52 - loss: nan -``` - -Oh non. - -`nan` n'est pas une valeur de perte très encourageante. Pourtant, nous avons vérifié nos données et elles semblent plutôt bonnes. Si ce n'est pas le problème, quelle est la prochaine étape ? La prochaine étape évidente est de... - -### Vérifier votre modèle - -`model.fit()` est une fonction très pratique dans Keras, mais elle fait beaucoup de choses pour vous. Cela peut rendre plus difficile de trouver exactement où un problème est survenu. Si vous déboguez votre modèle, une stratégie qui peut vraiment vous aider est de passer un seul batch au modèle et d'examiner les sorties de ce batch en détail. Une autre astuce vraiment utile est de `compiler()` le modèle avec `run_eagerly=True`. Cela le rendra beaucoup plus lent mais les messages d'erreur seront beaucoup plus compréhensibles car ils indiqueront exactement où le problème est survenu dans le code de votre modèle. - -Pour l'instant, cependant, nous n'avons pas besoin de `run_eagerly`. Exécutons le `batch` que nous avons obtenu précédemment à travers le modèle et voyons à quoi ressemblent les résultats : - -```py -model(batch) -``` - -```python out -TFSequenceClassifierOutput(loss=, logits=, hidden_states=None, attentions=None) -``` - -Eh bien, c'est délicat. Tout est "nan" ! Mais c'est étrange, n'est-ce pas ? Comment tous nos logits pourraient-ils devenir `nan` ? "NAN" signifie "*not a number*". Les valeurs `nan` apparaissent souvent quand on effectue une opération interdite comme la division par zéro. Mais une chose très importante à savoir sur `nan` en apprentissage automatique est que cette valeur a tendance à *se propager*. Si vous multipliez un nombre par `nan`, le résultat sera également `nan`. Et si vous obtenez une valeur `nan` n'importe où dans votre sortie, votre perte ou votre gradient, alors elle se propagera rapidement à travers tout votre modèle. -Ceci parce que lorsque cette valeur `nan` est propagée à travers votre réseau, vous obtiendrez des gradients `nan`, et lorsque les mises à jour des poids sont calculées avec ces gradients, vous obtiendrez des poids `nan`, et ces poids calculeront encore plus de sorties `nan` ! Très vite, le réseau entier ne sera plus qu'un gros bloc de `nan`. Une fois que cela arrive, il est assez difficile de voir où le problème a commencé. Comment peut-on isoler l'endroit où les `nan` se sont introduits en premier ? - -La réponse est d'essayer de *reinitialiser* notre modèle. Une fois que nous avons commencé l'entraînement, nous avons eu un `nan` quelque part et il s'est rapidement propagé à travers tout le modèle. Donc, chargeons le modèle à partir d'un checkpoint et ne faisons aucune mise à jour de poids, et voyons où nous obtenons une valeur `nan` : - -```py -model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint) -model(batch) -``` - -Quand on fait ça, on obtient : - -```py out -TFSequenceClassifierOutput(loss=, logits=, hidden_states=None, attentions=None) -``` - -*Maintenant* on arrive à quelque chose ! Il n'y a pas de valeurs `nan` dans nos logits, ce qui est rassurant. Mais nous voyons quelques valeurs `nan` dans notre perte ! Y a-t-il quelque chose dans ces échantillons en particulier qui cause ce problème ? Voyons de quels échantillons il s'agit (notez que si vous exécutez ce code vous-même, vous pouvez obtenir des indices différents parce que le jeu de données a été mélangé) : - -```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]) -``` - -Examinons les échantillons d'où proviennent ces indices : - -```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]]) -``` - -Il y a beaucoup de batchs ici mais rien d'inhabituel. Regardons les étiquettes : - -```python out -labels = batch['labels'].numpy() -labels[indices] -``` - -```python out -array([2, 2, 2, 2, 2, 2, 2, 2, 2]) -``` - -Ah ! Les échantillons `nan` ont tous le même label. C'est un gros indice. Le fait que nous n'obtenions une perte de `nan` que lorsque notre étiquette vaut 2 suggère que c'est un très bon moment pour vérifier le nombre d'étiquettes dans notre modèle : - -```python -model.config.num_labels -``` - -```python out -2 -``` - -Nous voyons maintenant le problème : le modèle pense qu'il n'y a que deux classes, mais les étiquettes vont jusqu'à 2, ce qui signifie qu'il y a en fait trois classes (car 0 est aussi une classe). C'est ainsi que nous avons obtenu un `nan`. En essayant de calculer la perte pour une classe inexistante ! Essayons de changer cela et de réajuster le modèle : - -``` -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 -``` - -On entraîne ! Plus de `nan` et nos pertes diminuent... en quelque sorte. Si vous regardez pendant un certain temps, vous pouvez commencer à vous impatienter car la valeur des pertes reste obstinément élevée. Arrêtons l'entraînement ici et essayons de réfléchir à ce qui pourrait causer ce problème. À ce stade, nous sommes pratiquement sûrs que les données et le modèle sont corrects, mais notre modèle n'apprend pas bien. Que reste-t-il d'autre ? Il est temps de... - -### Vérifier les hyperparamètres - -Si vous regardez le code ci-dessus, vous ne verrez peut-être aucun hyperparamètre, sauf peut-être le `batch_size` qui ne semble pas être un coupable probable. Cependant, ne soyez pas dupe, il y a toujours des hyperparamètres. Si vous ne pouvez pas les voir, cela signifie simplement que vous ne connaissez pas leur réglage. En particulier, souvenez-vous d'une chose essentielle à propos de Keras : si vous définissez une fonction de perte, d'optimisation ou d'activation avec une chaîne, _tous ses arguments seront définis sur leurs valeurs par défaut_. Cela signifie que, même si l'utilisation de chaînes de caractères est très pratique, vous devez être très prudent car cela peut facilement vous cacher des éléments critiques. (Toute personne essayant le défi optionnel ci-dessus devrait prendre bonne note de ce fait). - -Dans ce cas, où avons-nous défini un argument avec une chaîne de caractères ? Au départ, nous définissions la perte avec une chaîne de caractères, mais nous ne le faisons plus. Cependant, nous le faisons pour l'optimiseur. Cela pourrait-il nous cacher quelque chose ? Jetons un coup d'œil à [ses arguments](https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/Adam). - -Y a-t-il quelque chose qui ressort ? C'est exact : le taux d'apprentissage ! Lorsque nous indiquons simplement `'adam'` nous allons obtenir le taux d'apprentissage par défaut qui est de 0.001 (ou 1e-3). C'est beaucoup trop élevé pour un *transformer* ! En général, nous recommandons d'essayer des taux d'apprentissage entre 1e-5 et 1e-4 pour vos modèles soit entre 10X et 100X plus petit que la valeur que nous utilisons ici. Cela semble être un problème majeur, alors essayons de le réduire. Pour ce faire, nous devons importer l'objet `optimizer`. Pendant que nous y sommes, réinitialisons le modèle à partir du *checkpoint* au cas où l'entraînement avec un taux d'apprentissage élevé aurait endommagé ses poids : - -```python -from tensorflow.keras.optimizers import Adam - -model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint) -model.compile(optimizer=Adam(5e-5)) -``` - - - -💡 Vous pouvez également importer la fonction `create_optimizer()` de 🤗 Transformers qui vous donnera un optimiseur AdamW avec une décroissance du taux des poids correcte ainsi qu'un réchauffement et une décroissance du taux d'apprentissage. Cet optimiseur produira souvent des résultats légèrement meilleurs que ceux que vous obtenez avec l'optimiseur Adam par défaut. - - - -Maintenant, nous pouvons essayer de *finetuner* le modèle avec le nouveau taux d'apprentissage : - -```python -model.fit(train_dataset) -``` - -```python out -319/24543 [..............................] - ETA: 16:07 - loss: 0.9718 -``` - -Maintenant notre perte va vraiment aller quelque part ! L'entraînement semble enfin fonctionner. Il y a une leçon à tirer ici : lorsque votre modèle fonctionne mais que la perte ne diminue pas, et que vous êtes sûr que vos données sont correctes, c'est une bonne idée de vérifier les hyperparamètres comme le taux d'apprentissage et le taux de décroissance des poids. Un réglage trop élevé de l'un ou l'autre de ces paramètres risque fort de faire « caler » l'entraînement à une valeur de perte élevée. - -## Autres problèmes potentiels - -Nous avons couvert les problèmes dans le script ci-dessus, mais il existe plusieurs autres erreurs courantes auxquelles vous pouvez être confronté. Jetons un coup d'oeil à une liste (très incomplète). - -### Gérer les erreurs de manque de mémoire - -Le signe révélateur d'un manque de mémoire est une erreur du type "OOM when allocating tensor" (OOM étant l'abréviation de *out of memory*). Il s'agit d'un risque très courant lorsque l'on utilise de grands modèles de langage. Si vous rencontrez ce problème, une bonne stratégie consiste à diviser par deux la taille de votre batch et à réessayer. Gardez à l'esprit, cependant, que certains modèles sont *très* grands. Par exemple, le modèle GPT-2 complet possède 1,5 Go de paramètres, ce qui signifie que vous aurez besoin de 6 Go de mémoire rien que pour stocker le modèle, et 6 autres Go pour ses gradients ! Entraîner le modèle GPT-2 complet nécessite généralement plus de 20 Go de VRAM, quelle que soit la taille du batch utilisé, ce dont seuls quelques GPUs sont dotés. Des modèles plus légers comme `distilbert-base-cased` sont beaucoup plus faciles à exécuter et s'entraînent aussi beaucoup plus rapidement. - - - -Dans la prochaine partie du cours, nous examinerons des techniques plus avancées qui peuvent vous aider à réduire votre empreinte mémoire et vous permettre de finetuner les plus grands modèles. - - - -### TensorFlow affamé 🦛 - -Une bizarrerie particulière de TensorFlow dont vous devez être conscient est qu'il s'alloue *toute* la mémoire de votre GPU dès que vous chargez un modèle ou que vous effectuez un entraînement. Puis il divise cette mémoire selon les besoins. Ce comportement est différent de celui d'autres *frameworks*, comme PyTorch, qui alloue la mémoire selon les besoins avec CUDA plutôt que de le faire en interne. L'un des avantages de l'approche de TensorFlow est qu'elle peut souvent donner des erreurs utiles lorsque vous manquez de mémoire et qu'elle peut récupérer de cet état sans planter tout le noyau CUDA. Mais il y a aussi un inconvénient important : si vous exécutez deux processus TensorFlow en même temps alors **vous allez passer un mauvais moment**. - -Si vous travaillez sur Colab, vous n'avez pas à vous soucier de cela. Si vous travaillez localement, vous devez absolument faire attention. En particulier, sachez que la fermeture d'un onglet de *notebook* n'entraîne pas nécessairement la fermeture de ce *notebook* ! Vous devrez peut-être sélectionner les *notebooks* en cours d'exécution (ceux qui ont une icône verte) et les fermer manuellement dans la liste des répertoires. Tout *notebook* en cours d'exécution qui utilisait TensorFlow peut encore utiliser une grande partie de la mémoire de votre GPU, ce qui signifie que tout nouveau *notebook* que vous démarrez peut rencontrer des problèmes très étranges. - -Si vous commencez à obtenir des erreurs concernant CUDA, BLAS ou cuBLAS dans du code qui fonctionnait auparavant, c'est très souvent le coupable. Vous pouvez utiliser une commande comme `nvidia-smi` pour vérifier si la plupart de votre mémoire est libre ou toujours utilisée. Si elle est toujours utilisée, c'est que quelque chose d'autre s'y accroche ! - - -### Vérifiez vos données (encore !) - -Votre modèle n'apprendra quelque chose que s'il est réellement possible d'apprendre quelque chose de vos données. S'il y a un *bug* qui corrompt les données ou si les étiquettes sont attribuées de manière aléatoire, il est très probable que vous n'obtiendrez aucun entraînement de modèle sur votre jeu de données. Un outil utile ici est `tokenizer.decode()`. Il transformera les `input_ids` en chaînes de caractères, afin que vous puissiez visualiser les données et voir si vos données d'entraînement renseignent ce que vous voulez. Par exemple, après avoir obtenu un `batch` de votre `tf.data.Dataset` comme nous l'avons fait ci-dessus, vous pouvez décoder le premier élément comme suit : - - -```py -input_ids = batch["input_ids"].numpy() -tokenizer.decode(input_ids[0]) -``` - -Vous pouvez ensuite la comparer avec la première étiquette, comme suit : - -```py -labels = batch["labels"].numpy() -label = labels[0] -``` - -Une fois que vous pouvez visualiser vos données de cette manière, vous pouvez vous poser les questions suivantes : - -- les données décodées sont-elles compréhensibles ? -- êtes-vous d'accord avec les étiquettes ? -- y a-t-il une étiquette qui est plus courante que les autres ? -- quelle devrait être la perte/métrique si le modèle prédisait une réponse aléatoire/toujours la même réponse ? - -Après avoir examiné vos données, examinez quelques-unes des prédictions du modèle. Si votre modèle produit des *tokens*, essayez aussi de les décoder ! Si le modèle prédit toujours la même chose, cela peut être dû au fait que votre jeu de données est biaisé en faveur d'une catégorie (pour les problèmes de classification). Des techniques telles que le suréchantillonnage des classes rares peuvent aider. D'autre part, cela peut également être dû à des problèmes d'entraînement tels que de mauvais réglages des hyperparamètres. - -Si la perte/la métrique que vous obtenez sur votre modèle initial avant entraînement est très différente de la perte/la métrique à laquelle vous vous attendez pour des prédictions aléatoires, vérifiez la façon dont votre perte ou votre métrique est calculée. Il y a probablement un bug. Si vous utilisez plusieurs pertes que vous ajoutez à la fin, assurez-vous qu'elles sont de la même échelle. - -Lorsque vous êtes sûr que vos données sont parfaites, vous pouvez voir si le modèle est capable de s'entraîner sur elles grâce à un test simple. - -### Surentraînement du modèle sur un seul batch - -Le surentraînement est généralement une chose que nous essayons d'éviter lors de l'entraînement car cela signifie que le modèle n'apprend pas à reconnaître les caractéristiques générales que nous voulons qu'il reconnaisse et se contente de mémoriser les échantillons d'entraînement. Cependant, essayer d'entraîner votre modèle sur un batch encore et encore est un bon test pour vérifier si le problème tel que vous l'avez formulé peut être résolu par le modèle que vous essayez d'entraîner. Cela vous aidera également à voir si votre taux d'apprentissage initial est trop élevé. - -Une fois que vous avez défini votre `modèle`, c'est très facile. Il suffit de prendre un batch de données d'entraînement, puis de le traiter comme votre jeu de données entier que vous *finetunez* sur un grand nombre d'époques : - - -```py -for batch in train_dataset: - break - -# Assurez-vous que vous avez exécuté model.compile() et défini votre optimiseur, -# et vos pertes/métriques si vous les utilisez. - -model.fit(batch, epochs=20) -``` - - - -💡 Si vos données d'entraînement ne sont pas équilibrées, veillez à créer un batch de données d'entraînement contenant toutes les étiquettes. - - - -Le modèle résultant devrait avoir des résultats proches de la perfection sur le `batch`, avec une perte diminuant rapidement vers 0 (ou la valeur minimale pour la perte que vous utilisez). - -Si vous ne parvenez pas à ce que votre modèle obtienne des résultats parfaits comme celui-ci, cela signifie qu'il y a quelque chose qui ne va pas dans la façon dont vous avez formulé le problème ou dans vos données et vous devez donc y remédier. Ce n'est que lorsque vous parviendrez à passer le test de surentraînement que vous pourrez être sûr que votre modèle peut réellement apprendre quelque chose. - - - -⚠️ Vous devrez recréer votre modèle et votre `Trainer` après ce test, car le modèle obtenu ne sera probablement pas capable de récupérer et d'apprendre quelque chose d'utile sur votre jeu de données complet. - - - -### Ne réglez rien tant que vous n'avez pas une première ligne de base - -Le réglage des hyperparamètres est toujours considéré comme la partie la plus difficile de l'apprentissage automatique mais c'est juste la dernière étape pour vous aider à gagner un peu sur la métrique. La plupart du temps, les hyperparamètres par défaut du `Trainer` fonctionneront très bien pour vous donner de bons résultats. Donc ne vous lancez pas dans une recherche d'hyperparamètres longue et coûteuse jusqu'à ce que vous ayez quelque chose qui batte la ligne de base que vous avez sur votre jeu de données. - -Une fois que vous avez un modèle suffisamment bon, vous pouvez commencer à le *finetuner* un peu. N'essayez pas de lancer un millier d'exécutions avec différents hyperparamètres mais comparez quelques exécutions avec différentes valeurs pour un hyperparamètre afin de vous faire une idée de celui qui a le plus d'impact. - -Si vous modifiez le modèle lui-même, restez simple et n'essayez rien que vous ne puissiez raisonnablement justifier. Veillez toujours à revenir au test de surentraînement pour vérifier que votre modification n'a pas eu de conséquences inattendues. - -### Demander de l'aide - -Nous espérons que vous avez trouvé dans cette section des conseils qui vous ont aidé à résoudre votre problème. Si ce n'est pas le cas, n'oubliez pas que vous pouvez toujours demander de l'aide à la communauté sur le [forum](https://discuss.huggingface.co/). - -Voici quelques ressources (en anglais) supplémentaires qui peuvent s'avérer utiles : - -- [La reproductibilité comme vecteur des meilleures pratiques d'ingénierie](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p) par Joel Grus -- [Liste de contrôle pour le débogage des réseaux de neurones](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) par Cecelia Shao -- [Comment tester unitairement le code d'apprentissage automatique](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) par Chase Roberts -- [Une recette pour entraîner les réseaux de neurones](http://karpathy.github.io/2019/04/25/recipe/) par Andrej Karpathy - -Bien sûr, tous les problèmes rencontrés lors de l'entraînement ne sont pas forcément de votre faute ! Si vous rencontrez quelque chose dans la bibliothèque 🤗 *Transformers* ou 🤗 *Datasets* qui ne semble pas correct, vous avez peut-être trouver un *bug*. Vous devez absolument nous en parler pour qu'on puisse le corriger. Dans la section suivante, nous allons vous expliquer exactement comment faire. + + +# Débogage du pipeline d'entraînement + + + +Vous avez écrit un magnifique script pour entraîner ou *finetuner* un modèle sur une tâche donnée en suivant consciencieusement les conseils du [chapitre 7](/course/fr/chapter7). Mais lorsque vous lancez la commande `model.fit()`, quelque chose d'horrible se produit : vous obtenez une erreur 😱 ! Ou pire, tout semble aller bien et l'entraînement se déroule sans erreur mais le modèle résultant est mauvais. Dans cette section, nous allons vous montrer ce que vous pouvez faire pour déboguer ce genre de problèmes. + +## Déboguer le pipeline d'entraînement + + + +Le problème lorsque vous rencontrez une erreur dans `trainer.train()` est qu'elle peut provenir de plusieurs sources, car la fonction `Trainer` assemble généralement des batchs de choses. Elle convertit les jeux de données en chargeurs de données donc le problème pourrait être quelque chose d'erroné dans votre jeu de données, ou un problème en essayant de regrouper les éléments des jeux de données ensemble. Ensuite, elle prend un batch de données et le transmet au modèle, le problème peut donc se situer dans le code du modèle. Après cela, elle calcule les gradients et effectue l'étape d'optimisation, le problème peut donc également se situer dans votre optimiseur. Et même si tout se passe bien pendant l'entraînement, quelque chose peut encore mal tourner pendant l'évaluation si votre métrique pose problème. + +La meilleure façon de déboguer une erreur qui survient dans `trainer.train()` est de passer manuellement en revue tout le pipeline pour voir où les choses se sont mal passées. L'erreur est alors souvent très facile à résoudre. + +Pour le démontrer, nous utiliserons le script suivant qui tente de *finetuner* un modèle DistilBERT sur le [jeu de données MNLI](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) +``` + +Si vous essayez de l'exécuter, il se peut que vous obteniez des `VisibleDeprecationWarning`s lors de la conversion du jeu de données. Il s'agit d'un problème UX connu par l'équipe d'Hugging Face, donc veuillez l'ignorer. Si vous lisez le cours après novembre 2021 et que cela se produit encore, envoyez des tweets de rage à @carrigmat jusqu'à ce qu'il le corrige. + +Le problème cependant est que nous avons une erreur flagrante. Et c'est vraiment, terriblement long : + +```python out +ValueError: No gradients provided for any variable: ['tf_distil_bert_for_sequence_classification/distilbert/embeddings/word_embeddings/weight:0', '...'] +``` + +Qu'est-ce que cela signifie ? Nous avons essayé d'entraîner sur nos données mais nous n'avons pas obtenu de gradient. C'est assez déconcertant. Comment commencer à déboguer quelque chose comme ça ? Lorsque l'erreur que vous obtenez ne suggère pas immédiatement l'origine du problème, la meilleure solution consiste souvent à procéder par étapes, en s'assurant à chaque fois que tout semble correct. Et bien sûr, il faut toujours commencer par... + +### Vérifier vos données + +Cela va sans dire, mais si vos données sont corrompues, Keras ne sera pas en mesure de les réparer pour vous. Avant toute chose, vous devez donc jeter un coup d'œil à ce que contient votre ensemble d'entraînement. + +Bien qu'il soit tentant de regarder dans `raw_datasets` et `tokenized_datasets`, nous vous recommandons fortement d'aller voir les données au moment où elles vont entrer dans le modèle. Cela signifie lire une sortie du `tf.data.Dataset` que vous avez créé avec la fonction `to_tf_dataset()` ! Alors comment faire ? Les objets `tf.data.Dataset` nous donnent des batchs entiers à la fois et ne supportent pas l'indexation, donc nous ne pouvons pas simplement demander `train_dataset[0]`. Nous pouvons, cependant, lui demander poliment un batch : + +```py +for batch in train_dataset: + break +``` + +`break` termine la boucle après une itération, donc cela prend le premier batch qui sort de `train_dataset` et l'enregistre comme `batch`. Maintenant, jetons un coup d'oeil à ce qu'il y a à l'intérieur : + +```python out +{'attention_mask': , + 'label': , + 'input_ids': } +``` + +Cela semble correct. Nous passons les `labels`, `attention_mask`, et `input_ids` au modèle, ce qui devrait être tout ce dont il a besoin pour calculer les sorties et la perte. Alors pourquoi n'avons-nous pas de gradient ? Regardez de plus près : nous passons un seul dictionnaire en entrée mais un batch d'entraînement est généralement un tenseur ou un dictionnaire d'entrée, plus un tenseur d'étiquettes. Nos étiquettes sont juste une clé dans notre dictionnaire d'entrée. + +Est-ce un problème ? Pas toujours, en fait ! Mais c'est l'un des problèmes les plus courants que vous rencontrerez lorsque vous entraînerez des *transformers* avec TensorFlow. Nos modèles peuvent tous calculer la perte en interne, mais pour ce faire, les étiquettes doivent être transmises dans le dictionnaire d'entrée. C'est la perte qui est utilisée lorsque nous ne spécifions pas de valeur de perte à `compile()`. Keras, d'autre part, s'attend généralement à ce que les étiquettes soient passées séparément du dictionnaire d'entrée, et les calculs de perte échoueront généralement si vous ne le faites pas. + +Le problème est maintenant devenu plus clair : nous avons passé un argument `loss`, ce qui signifie que nous demandons à Keras de calculer les pertes pour nous, mais nous avons passé nos étiquettes comme entrées au modèle, et non comme étiquettes à l'endroit où Keras les attend ! Nous devons choisir l'un ou l'autre : soit nous utilisons la perte interne du modèle et gardons les étiquettes où elles sont, soit nous continuons à utiliser les pertes de Keras, mais nous déplaçons les étiquettes à l'endroit où Keras les attend. Pour simplifier, prenons la première approche. Changez l'appel à `compile()` pour lire : + +```py +model.compile(optimizer="adam") +``` + +Maintenant, nous allons utiliser la perte interne du modèle et ce problème devrait être résolu ! + + + +✏️ *A votre tour !* Comme défi optionnel après avoir résolu les autres problèmes, vous pouvez essayer de revenir à cette étape et faire fonctionner le modèle avec la perte originale calculée par Keras au lieu de la perte interne. Vous devrez ajouter `"labels"` à l'argument `label_cols` de `to_tf_dataset()` pour vous assurer que les labels sont correctement sortis, ce qui vous donnera des gradients. Mais il y a un autre problème avec la perte que nous avons spécifiée. L'entraînement fonctionnera toujours avec ce problème mais l'apprentissage sera très lent et se stabilisera à une perte d'entraînement élevée. Pouvez-vous trouver ce que c'est ? + +Un indice codé en ROT13, si vous êtes coincé : Vs lbh ybbx ng gur bhgchgf bs FrdhraprPynffvsvpngvba zbqryf va Genafsbezref, gurve svefg bhgchg vf `ybtvgf`. Jung ner ybtvgf ? + +Et un deuxième indice : Jura lbh fcrpvsl bcgvzvmref, npgvingvbaf be ybffrf jvgu fgevatf, Xrenf frgf ny gur nethzrag inyhrf gb gurve qrsnhygf. Jung nethzragf qbrf FcnefrPngrtbevpnyPebffragebcl unir, naq jung ner gurve qrsnhygf ? + + + +Maintenant, essayons d'entraîner. Nous devrions obtenir des gradients maintenant, donc avec un peu de chance nous pouvons juste appeler `model.fit()` et tout fonctionnera bien ! + +```python out + 246/24543 [..............................] - ETA: 15:52 - loss: nan +``` + +Oh non. + +`nan` n'est pas une valeur de perte très encourageante. Pourtant, nous avons vérifié nos données et elles semblent plutôt bonnes. Si ce n'est pas le problème, quelle est la prochaine étape ? La prochaine étape évidente est de... + +### Vérifier votre modèle + +`model.fit()` est une fonction très pratique dans Keras, mais elle fait beaucoup de choses pour vous. Cela peut rendre plus difficile de trouver exactement où un problème est survenu. Si vous déboguez votre modèle, une stratégie qui peut vraiment vous aider est de passer un seul batch au modèle et d'examiner les sorties de ce batch en détail. Une autre astuce vraiment utile est de `compiler()` le modèle avec `run_eagerly=True`. Cela le rendra beaucoup plus lent mais les messages d'erreur seront beaucoup plus compréhensibles car ils indiqueront exactement où le problème est survenu dans le code de votre modèle. + +Pour l'instant, cependant, nous n'avons pas besoin de `run_eagerly`. Exécutons le `batch` que nous avons obtenu précédemment à travers le modèle et voyons à quoi ressemblent les résultats : + +```py +model(batch) +``` + +```python out +TFSequenceClassifierOutput(loss=, logits=, hidden_states=None, attentions=None) +``` + +Eh bien, c'est délicat. Tout est "nan" ! Mais c'est étrange, n'est-ce pas ? Comment tous nos logits pourraient-ils devenir `nan` ? "NAN" signifie "*not a number*". Les valeurs `nan` apparaissent souvent quand on effectue une opération interdite comme la division par zéro. Mais une chose très importante à savoir sur `nan` en apprentissage automatique est que cette valeur a tendance à *se propager*. Si vous multipliez un nombre par `nan`, le résultat sera également `nan`. Et si vous obtenez une valeur `nan` n'importe où dans votre sortie, votre perte ou votre gradient, alors elle se propagera rapidement à travers tout votre modèle. +Ceci parce que lorsque cette valeur `nan` est propagée à travers votre réseau, vous obtiendrez des gradients `nan`, et lorsque les mises à jour des poids sont calculées avec ces gradients, vous obtiendrez des poids `nan`, et ces poids calculeront encore plus de sorties `nan` ! Très vite, le réseau entier ne sera plus qu'un gros bloc de `nan`. Une fois que cela arrive, il est assez difficile de voir où le problème a commencé. Comment peut-on isoler l'endroit où les `nan` se sont introduits en premier ? + +La réponse est d'essayer de *reinitialiser* notre modèle. Une fois que nous avons commencé l'entraînement, nous avons eu un `nan` quelque part et il s'est rapidement propagé à travers tout le modèle. Donc, chargeons le modèle à partir d'un checkpoint et ne faisons aucune mise à jour de poids, et voyons où nous obtenons une valeur `nan` : + +```py +model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint) +model(batch) +``` + +Quand on fait ça, on obtient : + +```py out +TFSequenceClassifierOutput(loss=, logits=, hidden_states=None, attentions=None) +``` + +*Maintenant* on arrive à quelque chose ! Il n'y a pas de valeurs `nan` dans nos logits, ce qui est rassurant. Mais nous voyons quelques valeurs `nan` dans notre perte ! Y a-t-il quelque chose dans ces échantillons en particulier qui cause ce problème ? Voyons de quels échantillons il s'agit (notez que si vous exécutez ce code vous-même, vous pouvez obtenir des indices différents parce que le jeu de données a été mélangé) : + +```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]) +``` + +Examinons les échantillons d'où proviennent ces indices : + +```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]]) +``` + +Il y a beaucoup de batchs ici mais rien d'inhabituel. Regardons les étiquettes : + +```python out +labels = batch['labels'].numpy() +labels[indices] +``` + +```python out +array([2, 2, 2, 2, 2, 2, 2, 2, 2]) +``` + +Ah ! Les échantillons `nan` ont tous le même label. C'est un gros indice. Le fait que nous n'obtenions une perte de `nan` que lorsque notre étiquette vaut 2 suggère que c'est un très bon moment pour vérifier le nombre d'étiquettes dans notre modèle : + +```python +model.config.num_labels +``` + +```python out +2 +``` + +Nous voyons maintenant le problème : le modèle pense qu'il n'y a que deux classes, mais les étiquettes vont jusqu'à 2, ce qui signifie qu'il y a en fait trois classes (car 0 est aussi une classe). C'est ainsi que nous avons obtenu un `nan`. En essayant de calculer la perte pour une classe inexistante ! Essayons de changer cela et de réajuster le modèle : + +``` +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 +``` + +On entraîne ! Plus de `nan` et nos pertes diminuent... en quelque sorte. Si vous regardez pendant un certain temps, vous pouvez commencer à vous impatienter car la valeur des pertes reste obstinément élevée. Arrêtons l'entraînement ici et essayons de réfléchir à ce qui pourrait causer ce problème. À ce stade, nous sommes pratiquement sûrs que les données et le modèle sont corrects, mais notre modèle n'apprend pas bien. Que reste-t-il d'autre ? Il est temps de... + +### Vérifier les hyperparamètres + +Si vous regardez le code ci-dessus, vous ne verrez peut-être aucun hyperparamètre, sauf peut-être le `batch_size` qui ne semble pas être un coupable probable. Cependant, ne soyez pas dupe, il y a toujours des hyperparamètres. Si vous ne pouvez pas les voir, cela signifie simplement que vous ne connaissez pas leur réglage. En particulier, souvenez-vous d'une chose essentielle à propos de Keras : si vous définissez une fonction de perte, d'optimisation ou d'activation avec une chaîne, _tous ses arguments seront définis sur leurs valeurs par défaut_. Cela signifie que, même si l'utilisation de chaînes de caractères est très pratique, vous devez être très prudent car cela peut facilement vous cacher des éléments critiques. (Toute personne essayant le défi optionnel ci-dessus devrait prendre bonne note de ce fait). + +Dans ce cas, où avons-nous défini un argument avec une chaîne de caractères ? Au départ, nous définissions la perte avec une chaîne de caractères, mais nous ne le faisons plus. Cependant, nous le faisons pour l'optimiseur. Cela pourrait-il nous cacher quelque chose ? Jetons un coup d'œil à [ses arguments](https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/Adam). + +Y a-t-il quelque chose qui ressort ? C'est exact : le taux d'apprentissage ! Lorsque nous indiquons simplement `'adam'` nous allons obtenir le taux d'apprentissage par défaut qui est de 0.001 (ou 1e-3). C'est beaucoup trop élevé pour un *transformer* ! En général, nous recommandons d'essayer des taux d'apprentissage entre 1e-5 et 1e-4 pour vos modèles soit entre 10X et 100X plus petit que la valeur que nous utilisons ici. Cela semble être un problème majeur, alors essayons de le réduire. Pour ce faire, nous devons importer l'objet `optimizer`. Pendant que nous y sommes, réinitialisons le modèle à partir du *checkpoint* au cas où l'entraînement avec un taux d'apprentissage élevé aurait endommagé ses poids : + +```python +from tensorflow.keras.optimizers import Adam + +model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint) +model.compile(optimizer=Adam(5e-5)) +``` + + + +💡 Vous pouvez également importer la fonction `create_optimizer()` de 🤗 Transformers qui vous donnera un optimiseur AdamW avec une décroissance du taux des poids correcte ainsi qu'un réchauffement et une décroissance du taux d'apprentissage. Cet optimiseur produira souvent des résultats légèrement meilleurs que ceux que vous obtenez avec l'optimiseur Adam par défaut. + + + +Maintenant, nous pouvons essayer de *finetuner* le modèle avec le nouveau taux d'apprentissage : + +```python +model.fit(train_dataset) +``` + +```python out +319/24543 [..............................] - ETA: 16:07 - loss: 0.9718 +``` + +Maintenant notre perte va vraiment aller quelque part ! L'entraînement semble enfin fonctionner. Il y a une leçon à tirer ici : lorsque votre modèle fonctionne mais que la perte ne diminue pas, et que vous êtes sûr que vos données sont correctes, c'est une bonne idée de vérifier les hyperparamètres comme le taux d'apprentissage et le taux de décroissance des poids. Un réglage trop élevé de l'un ou l'autre de ces paramètres risque fort de faire « caler » l'entraînement à une valeur de perte élevée. + +## Autres problèmes potentiels + +Nous avons couvert les problèmes dans le script ci-dessus, mais il existe plusieurs autres erreurs courantes auxquelles vous pouvez être confronté. Jetons un coup d'oeil à une liste (très incomplète). + +### Gérer les erreurs de manque de mémoire + +Le signe révélateur d'un manque de mémoire est une erreur du type "OOM when allocating tensor" (OOM étant l'abréviation de *out of memory*). Il s'agit d'un risque très courant lorsque l'on utilise de grands modèles de langage. Si vous rencontrez ce problème, une bonne stratégie consiste à diviser par deux la taille de votre batch et à réessayer. Gardez à l'esprit, cependant, que certains modèles sont *très* grands. Par exemple, le modèle GPT-2 complet possède 1,5 Go de paramètres, ce qui signifie que vous aurez besoin de 6 Go de mémoire rien que pour stocker le modèle, et 6 autres Go pour ses gradients ! Entraîner le modèle GPT-2 complet nécessite généralement plus de 20 Go de VRAM, quelle que soit la taille du batch utilisé, ce dont seuls quelques GPUs sont dotés. Des modèles plus légers comme `distilbert-base-cased` sont beaucoup plus faciles à exécuter et s'entraînent aussi beaucoup plus rapidement. + + + +Dans la prochaine partie du cours, nous examinerons des techniques plus avancées qui peuvent vous aider à réduire votre empreinte mémoire et vous permettre de finetuner les plus grands modèles. + + + +### TensorFlow affamé 🦛 + +Une bizarrerie particulière de TensorFlow dont vous devez être conscient est qu'il s'alloue *toute* la mémoire de votre GPU dès que vous chargez un modèle ou que vous effectuez un entraînement. Puis il divise cette mémoire selon les besoins. Ce comportement est différent de celui d'autres *frameworks*, comme PyTorch, qui alloue la mémoire selon les besoins avec CUDA plutôt que de le faire en interne. L'un des avantages de l'approche de TensorFlow est qu'elle peut souvent donner des erreurs utiles lorsque vous manquez de mémoire et qu'elle peut récupérer de cet état sans planter tout le noyau CUDA. Mais il y a aussi un inconvénient important : si vous exécutez deux processus TensorFlow en même temps alors **vous allez passer un mauvais moment**. + +Si vous travaillez sur Colab, vous n'avez pas à vous soucier de cela. Si vous travaillez localement, vous devez absolument faire attention. En particulier, sachez que la fermeture d'un onglet de *notebook* n'entraîne pas nécessairement la fermeture de ce *notebook* ! Vous devrez peut-être sélectionner les *notebooks* en cours d'exécution (ceux qui ont une icône verte) et les fermer manuellement dans la liste des répertoires. Tout *notebook* en cours d'exécution qui utilisait TensorFlow peut encore utiliser une grande partie de la mémoire de votre GPU, ce qui signifie que tout nouveau *notebook* que vous démarrez peut rencontrer des problèmes très étranges. + +Si vous commencez à obtenir des erreurs concernant CUDA, BLAS ou cuBLAS dans du code qui fonctionnait auparavant, c'est très souvent le coupable. Vous pouvez utiliser une commande comme `nvidia-smi` pour vérifier si la plupart de votre mémoire est libre ou toujours utilisée. Si elle est toujours utilisée, c'est que quelque chose d'autre s'y accroche ! + + +### Vérifiez vos données (encore !) + +Votre modèle n'apprendra quelque chose que s'il est réellement possible d'apprendre quelque chose de vos données. S'il y a un *bug* qui corrompt les données ou si les étiquettes sont attribuées de manière aléatoire, il est très probable que vous n'obtiendrez aucun entraînement de modèle sur votre jeu de données. Un outil utile ici est `tokenizer.decode()`. Il transformera les `input_ids` en chaînes de caractères, afin que vous puissiez visualiser les données et voir si vos données d'entraînement renseignent ce que vous voulez. Par exemple, après avoir obtenu un `batch` de votre `tf.data.Dataset` comme nous l'avons fait ci-dessus, vous pouvez décoder le premier élément comme suit : + + +```py +input_ids = batch["input_ids"].numpy() +tokenizer.decode(input_ids[0]) +``` + +Vous pouvez ensuite la comparer avec la première étiquette, comme suit : + +```py +labels = batch["labels"].numpy() +label = labels[0] +``` + +Une fois que vous pouvez visualiser vos données de cette manière, vous pouvez vous poser les questions suivantes : + +- les données décodées sont-elles compréhensibles ? +- êtes-vous d'accord avec les étiquettes ? +- y a-t-il une étiquette qui est plus courante que les autres ? +- quelle devrait être la perte/métrique si le modèle prédisait une réponse aléatoire/toujours la même réponse ? + +Après avoir examiné vos données, examinez quelques-unes des prédictions du modèle. Si votre modèle produit des *tokens*, essayez aussi de les décoder ! Si le modèle prédit toujours la même chose, cela peut être dû au fait que votre jeu de données est biaisé en faveur d'une catégorie (pour les problèmes de classification). Des techniques telles que le suréchantillonnage des classes rares peuvent aider. D'autre part, cela peut également être dû à des problèmes d'entraînement tels que de mauvais réglages des hyperparamètres. + +Si la perte/la métrique que vous obtenez sur votre modèle initial avant entraînement est très différente de la perte/la métrique à laquelle vous vous attendez pour des prédictions aléatoires, vérifiez la façon dont votre perte ou votre métrique est calculée. Il y a probablement un bug. Si vous utilisez plusieurs pertes que vous ajoutez à la fin, assurez-vous qu'elles sont de la même échelle. + +Lorsque vous êtes sûr que vos données sont parfaites, vous pouvez voir si le modèle est capable de s'entraîner sur elles grâce à un test simple. + +### Surentraînement du modèle sur un seul batch + +Le surentraînement est généralement une chose que nous essayons d'éviter lors de l'entraînement car cela signifie que le modèle n'apprend pas à reconnaître les caractéristiques générales que nous voulons qu'il reconnaisse et se contente de mémoriser les échantillons d'entraînement. Cependant, essayer d'entraîner votre modèle sur un batch encore et encore est un bon test pour vérifier si le problème tel que vous l'avez formulé peut être résolu par le modèle que vous essayez d'entraîner. Cela vous aidera également à voir si votre taux d'apprentissage initial est trop élevé. + +Une fois que vous avez défini votre `modèle`, c'est très facile. Il suffit de prendre un batch de données d'entraînement, puis de le traiter comme votre jeu de données entier que vous *finetunez* sur un grand nombre d'époques : + + +```py +for batch in train_dataset: + break + +# Assurez-vous que vous avez exécuté model.compile() et défini votre optimiseur, +# et vos pertes/métriques si vous les utilisez. + +model.fit(batch, epochs=20) +``` + + + +💡 Si vos données d'entraînement ne sont pas équilibrées, veillez à créer un batch de données d'entraînement contenant toutes les étiquettes. + + + +Le modèle résultant devrait avoir des résultats proches de la perfection sur le `batch`, avec une perte diminuant rapidement vers 0 (ou la valeur minimale pour la perte que vous utilisez). + +Si vous ne parvenez pas à ce que votre modèle obtienne des résultats parfaits comme celui-ci, cela signifie qu'il y a quelque chose qui ne va pas dans la façon dont vous avez formulé le problème ou dans vos données et vous devez donc y remédier. Ce n'est que lorsque vous parviendrez à passer le test de surentraînement que vous pourrez être sûr que votre modèle peut réellement apprendre quelque chose. + + + +⚠️ Vous devrez recréer votre modèle et votre `Trainer` après ce test, car le modèle obtenu ne sera probablement pas capable de récupérer et d'apprendre quelque chose d'utile sur votre jeu de données complet. + + + +### Ne réglez rien tant que vous n'avez pas une première ligne de base + +Le réglage des hyperparamètres est toujours considéré comme la partie la plus difficile de l'apprentissage automatique mais c'est juste la dernière étape pour vous aider à gagner un peu sur la métrique. La plupart du temps, les hyperparamètres par défaut du `Trainer` fonctionneront très bien pour vous donner de bons résultats. Donc ne vous lancez pas dans une recherche d'hyperparamètres longue et coûteuse jusqu'à ce que vous ayez quelque chose qui batte la ligne de base que vous avez sur votre jeu de données. + +Une fois que vous avez un modèle suffisamment bon, vous pouvez commencer à le *finetuner* un peu. N'essayez pas de lancer un millier d'exécutions avec différents hyperparamètres mais comparez quelques exécutions avec différentes valeurs pour un hyperparamètre afin de vous faire une idée de celui qui a le plus d'impact. + +Si vous modifiez le modèle lui-même, restez simple et n'essayez rien que vous ne puissiez raisonnablement justifier. Veillez toujours à revenir au test de surentraînement pour vérifier que votre modification n'a pas eu de conséquences inattendues. + +### Demander de l'aide + +Nous espérons que vous avez trouvé dans cette section des conseils qui vous ont aidé à résoudre votre problème. Si ce n'est pas le cas, n'oubliez pas que vous pouvez toujours demander de l'aide à la communauté sur le [forum](https://discuss.huggingface.co/). + +Voici quelques ressources (en anglais) supplémentaires qui peuvent s'avérer utiles : + +- [La reproductibilité comme vecteur des meilleures pratiques d'ingénierie](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p) par Joel Grus +- [Liste de contrôle pour le débogage des réseaux de neurones](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) par Cecelia Shao +- [Comment tester unitairement le code d'apprentissage automatique](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) par Chase Roberts +- [Une recette pour entraîner les réseaux de neurones](http://karpathy.github.io/2019/04/25/recipe/) par Andrej Karpathy + +Bien sûr, tous les problèmes rencontrés lors de l'entraînement ne sont pas forcément de votre faute ! Si vous rencontrez quelque chose dans la bibliothèque 🤗 *Transformers* ou 🤗 *Datasets* qui ne semble pas correct, vous avez peut-être trouver un *bug*. Vous devez absolument nous en parler pour qu'on puisse le corriger. Dans la section suivante, nous allons vous expliquer exactement comment faire. diff --git a/chapters/fr/chapter8/5.mdx b/chapters/fr/chapter8/5.mdx index 330305c27..71c0f9dfc 100644 --- a/chapters/fr/chapter8/5.mdx +++ b/chapters/fr/chapter8/5.mdx @@ -1,92 +1,92 @@ -# Comment rédiger une bonne issue - - - -Lorsque vous rencontrez un problème avec l'une des bibliothèques d'Hugging Face, faites le nous savoir afin que nous puissions le corriger (il en va de même pour toute bibliothèque open source).
-Si vous n'êtes pas complètement certain que le *bug* se trouve dans votre propre code ou dans l'une de nos bibliothèques, le premier endroit à vérifier est le [forum](https://discuss.huggingface.co/). La communauté vous aidera à résoudre votre problème et l'équipe d'Hugging Face y suit de près les discussions qui s'y déroulent. - - - -Lorsque vous êtes sûr d'avoir identifier un *bug*, la première étape consiste à construire un exemple minimal qui soit reproductible. - -## Créer un exemple minimal reproductible - -Il est très important d'isoler le morceau de code qui produit le *bug* car personne dans l'équipe d'Hugging Face n'est (encore) un magicien et on ne peut pas réparer ce qu'on ne peut pas voir. Un exemple minimal reproductible doit, comme son nom l'indique, être reproductible. Cela signifie qu'il ne doit pas dépendre de fichiers ou de données externes que vous pourriez avoir. Essayez de remplacer les données que vous utilisez par des valeurs fictives qui ressemblent à vos valeurs réelles et qui produisent toujours la même erreur. - - - -🚨 De nombreux problèmes dans le dépôt 🤗 *Transformers* ne sont pas résolus car les données utilisées pour les reproduire ne sont pas accessibles. - - - -Une fois que vous avez quelque chose d'autonome, essayez de le réduire au moins de lignes de code possible, en construisant ce que nous appelons un _exemple reproductible minimal_. Bien que cela nécessite un peu plus de travail de votre part, vous serez presque assuré d'obtenir de l'aide et une correction si vous fournissez un exemple reproductible court et agréable. - -Si vous vous sentez suffisamment à l'aise, allez inspecter le code source où se trouve votre *bug*. Vous trouverez peut-être une solution à votre problème (dans ce cas, vous pouvez même suggérer une *pull request* pour le corriger), mais plus généralement, cela peut aider les mainteneurs à mieux comprendre le code source lorsqu'ils lisent votre message. - -## Remplir le gabarit de problème - -Lorsque vous ouvrerez votre *issue* vous remarquerez qu'il y a un gabarit à remplir. Nous suivrons ici celui pour la bibliothèque [🤗 *Transformers*](https://github.com/huggingface/transformers/issues/new/choose) mais le même type d'information sera requis dans un autre dépôt. Ne laissez pas le gabarit vide : prendre le temps de le remplir maximisera vos chances d'obtenir une réponse et de résoudre votre problème. - -En général, lorsque vous signalez un problème, restez toujours courtois. Il s'agit d'un projet open source, vous utilisez donc un logiciel libre, et personne n'est obligé de vous aider. Vous pouvez inclure dans votre *issue* des critiques qui vous semblent justifiées mais les mainteneurs pourraient très bien les prendre mal et ne pas être pressés de vous aider. Assurez-vous de lire le [code de conduite](https://github.com/huggingface/transformers/blob/master/CODE_OF_CONDUCT.md) du projet. - -### Inclure les informations sur votre environnement - -🤗 *Transformers* fournit un utilitaire pour obtenir toutes les informations nécessaire concernant votre environnement. Il suffit de taper ce qui suit dans votre terminal : - -``` -transformers-cli env -``` - -et vous devriez obtenir quelque chose comme : - -```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?: -``` - -Vous pouvez également ajouter un `!` au début de la commande `transformers-cli env` pour l'exécuter depuis une cellule de *notebook* puis copier et coller le résultat au début de votre *issue*. - -### Taguer des personnes - -Taguer des personnes en tapant un `@` suivi de leur identifiant GitHub leur enverra une notification afin qu'elles voient votre problème et puissent répondre plus rapidement. Néanmoins utilisez cette fonction avec modération car les personnes que vous taguez peuvent ne pas apprécier d'être notifiées si elles n'ont pas de lien direct avec le problème. Si vous avez regardé les fichiers sources liés à votre *bug*, vous devriez taguer la dernière personne qui a fait des changements à la ligne que vous pensez être responsable de votre problème (vous pouvez trouver cette information en regardant ladite ligne sur GitHub, en la sélectionnant, puis en cliquant sur « *View git blame* »). - -Sinon, le gabarit propose des suggestions de personnes à taguer. En général, ne marquez jamais plus de trois personnes ! - -### Inclure un exemple reproductible - -Si vous avez réussi à créer un exemple autonome qui produit le *bug*, il est temps de l'inclure ! Tapez une ligne avec trois *backticks* suivis de `python`, comme ceci : - -``` -```python -``` - -puis collez votre exemple minimal reproductible et tapez une nouvelle ligne avec trois *backticks*. Cela permettra de s'assurer que votre code est correctement formaté. - -Si vous n'avez pas réussi à créer un exemple reproductible, expliquez en des étapes claires comment vous êtes arrivé à votre problème. Si vous le pouvez, incluez un lien vers un *notebook* d'un Google Colab où vous avez trouvé l'erreur. Plus vous partagerez d'informations, plus les mainteneurs seront en mesure de vous répondre. - -Dans tous les cas, vous devez copier et coller l'intégralité du message d'erreur que vous obtenez. Si vous travaillez dans Colab, n'oubliez pas que certaines cellules peuvent être automatiquement réduites dans la trace de la pile et veillez donc à les afficher avant de les copier. Comme pour l'exemple de code, placez le message d'erreur entre deux lignes avec trois *backticks* afin qu'il soit correctement formaté. - -### Décrire le comportement attendu - -Expliquez en quelques lignes ce que vous vous attendiez à obtenir afin que les mainteneurs comprennent bien le problème. Cette partie est généralement assez évidente, elle devrait donc tenir en une seule phrase mais dans certains cas vous pouvez avoir beaucoup à dire. - -## Et ensuite ? - -Une fois que votre problème est classé, vérifiez rapidement que tout est en ordre. Vous pouvez modifier le problème si vous avez fait une erreur ou même changer son titre si vous vous rendez compte que le problème est différent de ce que vous pensiez initialement. - -Il est inutile d'envoyer des messages aux personnes concernées si vous n'obtenez pas de réponse. Si personne ne vous aide au bout de quelques jours, il est probable que personne n'a pu donner un sens à votre problème. N'hésitez pas à revenir à l'exemple reproductible. Pouvez-vous le rendre plus court et plus concis ? Si vous n'obtenez pas de réponse au bout d'une semaine, vous pouvez laisser un message demandant gentiment de l'aide, surtout si vous avez modifié votre question pour inclure plus d'informations sur le problème. +# Comment rédiger une bonne issue + + + +Lorsque vous rencontrez un problème avec l'une des bibliothèques d'Hugging Face, faites le nous savoir afin que nous puissions le corriger (il en va de même pour toute bibliothèque open source).
+Si vous n'êtes pas complètement certain que le *bug* se trouve dans votre propre code ou dans l'une de nos bibliothèques, le premier endroit à vérifier est le [forum](https://discuss.huggingface.co/). La communauté vous aidera à résoudre votre problème et l'équipe d'Hugging Face y suit de près les discussions qui s'y déroulent. + + + +Lorsque vous êtes sûr d'avoir identifier un *bug*, la première étape consiste à construire un exemple minimal qui soit reproductible. + +## Créer un exemple minimal reproductible + +Il est très important d'isoler le morceau de code qui produit le *bug* car personne dans l'équipe d'Hugging Face n'est (encore) un magicien et on ne peut pas réparer ce qu'on ne peut pas voir. Un exemple minimal reproductible doit, comme son nom l'indique, être reproductible. Cela signifie qu'il ne doit pas dépendre de fichiers ou de données externes que vous pourriez avoir. Essayez de remplacer les données que vous utilisez par des valeurs fictives qui ressemblent à vos valeurs réelles et qui produisent toujours la même erreur. + + + +🚨 De nombreux problèmes dans le dépôt 🤗 *Transformers* ne sont pas résolus car les données utilisées pour les reproduire ne sont pas accessibles. + + + +Une fois que vous avez quelque chose d'autonome, essayez de le réduire au moins de lignes de code possible, en construisant ce que nous appelons un _exemple reproductible minimal_. Bien que cela nécessite un peu plus de travail de votre part, vous serez presque assuré d'obtenir de l'aide et une correction si vous fournissez un exemple reproductible court et agréable. + +Si vous vous sentez suffisamment à l'aise, allez inspecter le code source où se trouve votre *bug*. Vous trouverez peut-être une solution à votre problème (dans ce cas, vous pouvez même suggérer une *pull request* pour le corriger), mais plus généralement, cela peut aider les mainteneurs à mieux comprendre le code source lorsqu'ils lisent votre message. + +## Remplir le gabarit de problème + +Lorsque vous ouvrerez votre *issue* vous remarquerez qu'il y a un gabarit à remplir. Nous suivrons ici celui pour la bibliothèque [🤗 *Transformers*](https://github.com/huggingface/transformers/issues/new/choose) mais le même type d'information sera requis dans un autre dépôt. Ne laissez pas le gabarit vide : prendre le temps de le remplir maximisera vos chances d'obtenir une réponse et de résoudre votre problème. + +En général, lorsque vous signalez un problème, restez toujours courtois. Il s'agit d'un projet open source, vous utilisez donc un logiciel libre, et personne n'est obligé de vous aider. Vous pouvez inclure dans votre *issue* des critiques qui vous semblent justifiées mais les mainteneurs pourraient très bien les prendre mal et ne pas être pressés de vous aider. Assurez-vous de lire le [code de conduite](https://github.com/huggingface/transformers/blob/master/CODE_OF_CONDUCT.md) du projet. + +### Inclure les informations sur votre environnement + +🤗 *Transformers* fournit un utilitaire pour obtenir toutes les informations nécessaire concernant votre environnement. Il suffit de taper ce qui suit dans votre terminal : + +``` +transformers-cli env +``` + +et vous devriez obtenir quelque chose comme : + +```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?: +``` + +Vous pouvez également ajouter un `!` au début de la commande `transformers-cli env` pour l'exécuter depuis une cellule de *notebook* puis copier et coller le résultat au début de votre *issue*. + +### Taguer des personnes + +Taguer des personnes en tapant un `@` suivi de leur identifiant GitHub leur enverra une notification afin qu'elles voient votre problème et puissent répondre plus rapidement. Néanmoins utilisez cette fonction avec modération car les personnes que vous taguez peuvent ne pas apprécier d'être notifiées si elles n'ont pas de lien direct avec le problème. Si vous avez regardé les fichiers sources liés à votre *bug*, vous devriez taguer la dernière personne qui a fait des changements à la ligne que vous pensez être responsable de votre problème (vous pouvez trouver cette information en regardant ladite ligne sur GitHub, en la sélectionnant, puis en cliquant sur « *View git blame* »). + +Sinon, le gabarit propose des suggestions de personnes à taguer. En général, ne marquez jamais plus de trois personnes ! + +### Inclure un exemple reproductible + +Si vous avez réussi à créer un exemple autonome qui produit le *bug*, il est temps de l'inclure ! Tapez une ligne avec trois *backticks* suivis de `python`, comme ceci : + +``` +```python +``` + +puis collez votre exemple minimal reproductible et tapez une nouvelle ligne avec trois *backticks*. Cela permettra de s'assurer que votre code est correctement formaté. + +Si vous n'avez pas réussi à créer un exemple reproductible, expliquez en des étapes claires comment vous êtes arrivé à votre problème. Si vous le pouvez, incluez un lien vers un *notebook* d'un Google Colab où vous avez trouvé l'erreur. Plus vous partagerez d'informations, plus les mainteneurs seront en mesure de vous répondre. + +Dans tous les cas, vous devez copier et coller l'intégralité du message d'erreur que vous obtenez. Si vous travaillez dans Colab, n'oubliez pas que certaines cellules peuvent être automatiquement réduites dans la trace de la pile et veillez donc à les afficher avant de les copier. Comme pour l'exemple de code, placez le message d'erreur entre deux lignes avec trois *backticks* afin qu'il soit correctement formaté. + +### Décrire le comportement attendu + +Expliquez en quelques lignes ce que vous vous attendiez à obtenir afin que les mainteneurs comprennent bien le problème. Cette partie est généralement assez évidente, elle devrait donc tenir en une seule phrase mais dans certains cas vous pouvez avoir beaucoup à dire. + +## Et ensuite ? + +Une fois que votre problème est classé, vérifiez rapidement que tout est en ordre. Vous pouvez modifier le problème si vous avez fait une erreur ou même changer son titre si vous vous rendez compte que le problème est différent de ce que vous pensiez initialement. + +Il est inutile d'envoyer des messages aux personnes concernées si vous n'obtenez pas de réponse. Si personne ne vous aide au bout de quelques jours, il est probable que personne n'a pu donner un sens à votre problème. N'hésitez pas à revenir à l'exemple reproductible. Pouvez-vous le rendre plus court et plus concis ? Si vous n'obtenez pas de réponse au bout d'une semaine, vous pouvez laisser un message demandant gentiment de l'aide, surtout si vous avez modifié votre question pour inclure plus d'informations sur le problème. diff --git a/chapters/fr/chapter8/6.mdx b/chapters/fr/chapter8/6.mdx index b31f82d1f..0f705def0 100644 --- a/chapters/fr/chapter8/6.mdx +++ b/chapters/fr/chapter8/6.mdx @@ -1,7 +1,12 @@ -# Partie 2 terminée ! - -Félicitations, vous avez terminé la deuxième partie du cours ! Nous travaillons activement sur la troisième alors inscrivez-vous à notre [*newsletter*](https://huggingface.curated.co/) pour être sûr de ne pas manquer sa sortie. - -Vous devriez maintenant être en mesure d'aborder une série de tâches de NLP et de *finetuner* ou de prétraîner un modèle sur celles-ci. N'oubliez pas de partager vos résultats avec la communauté sur le [*Hub*](https://huggingface.co/models). - -Nous sommes impatients de voir ce que vous allez construire avec les connaissances que vous avez acquises ! +# Partie 2 terminée ! + + + +Félicitations, vous avez terminé la deuxième partie du cours ! Nous travaillons activement sur la troisième alors inscrivez-vous à notre [*newsletter*](https://huggingface.curated.co/) pour être sûr de ne pas manquer sa sortie. + +Vous devriez maintenant être en mesure d'aborder une série de tâches de NLP et de *finetuner* ou de prétraîner un modèle sur celles-ci. N'oubliez pas de partager vos résultats avec la communauté sur le [*Hub*](https://huggingface.co/models). + +Nous sommes impatients de voir ce que vous allez construire avec les connaissances que vous avez acquises ! diff --git a/chapters/fr/chapter8/7.mdx b/chapters/fr/chapter8/7.mdx index 6f93e4e7f..2d3cccf24 100644 --- a/chapters/fr/chapter8/7.mdx +++ b/chapters/fr/chapter8/7.mdx @@ -1,199 +1,204 @@ - - -# Quiz de fin de chapitre - -Testons ce que vous avez appris dans ce chapitre ! - -### 1. Dans quel ordre devez-vous lire un traceback Python ? - -traceback de Python montrant l'exception en bas est qu'il est plus facile de déboguer lorsque vous travaillez dans le terminal et que c'est la dernière ligne que vous voyez.", - correct: true - } - ]} -/> - -### 2. Qu'est-ce qu'un exemple minimal reproductible ? - -transformer à partir d'un article de recherche.", - explain: "Bien qu'il soit très éducatif d'implémenter vos propres modèles de transformers à partir de zéro, ce n'est pas ce dont nous parlons ici." - }, - { - text: "Un bloc de code compact et autonome qui peut être exécuté sans aucune dépendance externe sur des fichiers ou des données privées.", - explain: "Des exemples minimaux reproductibles aident les mainteneurs de la bibliothèque à reproduire le problème que vous rencontrez, afin qu'ils puissent trouver des solutions plus rapidement.", - correct: true - }, - { - text: "Une capture d'écran de la traceback Python", - explain: "Essayez à nouveau. Bien qu'il soit tentant d'inclure une capture d'écran de l'erreur à laquelle vous êtes confronté lorsque vous soumettez un problème, cela rend très difficile pour les autres de reproduire l'erreur." - }, - { - text: "Un notebook qui contient toute votre analyse, y compris les parties sans rapport avec l'erreur.", - explain: "Pas tout à fait. Bien qu'il puisse être utile de partager un notebook Google Colab qui montre l'erreur, assurez-vous qu'il est court et ne contient que le code pertinent." - } - ]} -/> - -### 3. Supposons que vous essayez d'exécuter le code suivant, qui génère une erreur : - -```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) -``` - -Lequel des éléments suivants pourrait être un bon choix pour le titre d'un sujet de forum pour demander de l'aide ? - -ImportError: cannot import name 'GPT3ForSequenceClassification' from 'transformers' (/Users/lewtun/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/__init__.py)", - explain: "Inclure la dernière ligne de la traceback peut être descriptif, mais il est préférable de le réserver au corps principal du sujet. Essayez à nouveau !" - }, - { - text: "Problème avec from transformers import GPT3ForSequenceClassification", - explain: "Essayez à nouveau. Bien que cette information soit utile, il est probablement préférable de la réserver au corps du texte.", - }, - { - text: "Pourquoi je ne peux pas importer GPT3ForSequenceClassification?", - explain: "Ce titre est concis et donne au lecteur un indice sur ce qui pourrait être erroné (par exemple, que GPT-3 n'est pas pris en charge dans 🤗 Transformers).", - correct: true - }, - { - text: "Le GPT-3 est-il pris en charge dans 🤗 Transformers ?", - explain: "Utiliser des questions comme titres de sujets est un excellent moyen de communiquer le problème à la communauté.", - correct: true - } - ]} -/> - -### 4. Supposons que vous ayez essayé d'exécuter `trainer.train()` et que vous soyez confronté à une erreur énigmatique qui ne vous dit pas exactement d'où vient l'erreur. Quel est le premier endroit où vous devez chercher les erreurs dans votre pipeline d'entraînement ? - -bugs dans votre optimiseur, cela se produit généralement à plusieurs étapes du pipeline d'entraînement, il y a donc d'autres choses à vérifier d'abord. Essayez à nouveau !" - }, - { - text: "L'étape d'évaluation où nous calculons les métriques", - explain: "L'évaluation est généralement ce que vous faites après l'entraînement pour une époque complète, donc vous devriez d'abord vérifier quelque part plus tôt dans le pipeline d'entraînement.", - }, - { - text: "Les jeux de données", - explain: "C'est exact ! L'examen de vos données est presque toujours la première chose à faire, pour vous assurer que le texte est codé de manière appropriée, qu'il présente les caractéristiques attendues, etc.", - correct: true - }, - { - text: "Les chargeurs de données", - explain: "Essayez à nouveau. C'est très proche de la première chose que vous devriez vérifier. Vous souvenez-vous de l'objet que nous passons aux dataloaders ?" - } - ]} -/> - -### 5. Quelle est la meilleure façon de déboguer une erreur CUDA ? - -traceback pour découvrir ce qui a causé l'erreur.", - explain: "C'est ce que vous feriez pour toute autre erreur, mais les erreurs CUDA ne sont généralement pas signalées là où elles se sont produites, car la plupart des opérations CUDA sont asynchrones." - }, - { - text: "Réduisez la taille du batch.", - explain: "La réduction de la taille du batch est généralement une bonne stratégie pour gérer les erreurs CUDA hors mémoire, mais pas pour ce problème particulier. Essayez à nouveau !" - }, - { - text: "Redémarrez le noyau Jupyter.", - explain: "Essayez à nouveau. Le redémarrage du noyau ne fera pas disparaître l'erreur comme par magie !", - } - ]} -/> - -### 6. Quelle est la meilleure façon de faire corriger un problème sur GitHub ? - -bug.", - explain: "C'est la meilleure façon d'aider les mainteneurs à trouver votre bogue. Que devez-vous faire d'autre ?", - correct: true - }, - { - text: "Demandez chaque jour une mise à jour.", - explain: "Il est peu probable que cela vous apporte de l'aide. Les gens vous ignoreront probablement davantage.", - }, - { - text: "Inspectez le code source autour du bogue et essayez de trouver la raison pour laquelle il se produit. Postez les résultats dans le problème.", - explain: "Cela aidera certainement les mainteneurs ! Et si vous trouvez la source du bogue et un correctif, vous pouvez même ouvrir une demande de modification. Que devez-vous faire d'autre ?", - correct: true - } - ]} -/> - -### 7. Pourquoi le surapprentissage à un batch est-il généralement une bonne technique de débogage ? - - - -### 8. Pourquoi est-ce une bonne idée d'inclure des détails sur votre environnement de calcul avec `transformers-cli env` lorsque vous créez une nouvelle question dans le dépôt 🤗 *Transformers* ? - - + + +# Quiz de fin de chapitre + + + +Testons ce que vous avez appris dans ce chapitre ! + +### 1. Dans quel ordre devez-vous lire un traceback Python ? + +traceback de Python montrant l'exception en bas est qu'il est plus facile de déboguer lorsque vous travaillez dans le terminal et que c'est la dernière ligne que vous voyez.", + correct: true + } + ]} +/> + +### 2. Qu'est-ce qu'un exemple minimal reproductible ? + +transformer à partir d'un article de recherche.", + explain: "Bien qu'il soit très éducatif d'implémenter vos propres modèles de transformers à partir de zéro, ce n'est pas ce dont nous parlons ici." + }, + { + text: "Un bloc de code compact et autonome qui peut être exécuté sans aucune dépendance externe sur des fichiers ou des données privées.", + explain: "Des exemples minimaux reproductibles aident les mainteneurs de la bibliothèque à reproduire le problème que vous rencontrez, afin qu'ils puissent trouver des solutions plus rapidement.", + correct: true + }, + { + text: "Une capture d'écran de la traceback Python", + explain: "Essayez à nouveau. Bien qu'il soit tentant d'inclure une capture d'écran de l'erreur à laquelle vous êtes confronté lorsque vous soumettez un problème, cela rend très difficile pour les autres de reproduire l'erreur." + }, + { + text: "Un notebook qui contient toute votre analyse, y compris les parties sans rapport avec l'erreur.", + explain: "Pas tout à fait. Bien qu'il puisse être utile de partager un notebook Google Colab qui montre l'erreur, assurez-vous qu'il est court et ne contient que le code pertinent." + } + ]} +/> + +### 3. Supposons que vous essayez d'exécuter le code suivant, qui génère une erreur : + +```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) +``` + +Lequel des éléments suivants pourrait être un bon choix pour le titre d'un sujet de forum pour demander de l'aide ? + +ImportError: cannot import name 'GPT3ForSequenceClassification' from 'transformers' (/Users/lewtun/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/__init__.py)", + explain: "Inclure la dernière ligne de la traceback peut être descriptif, mais il est préférable de le réserver au corps principal du sujet. Essayez à nouveau !" + }, + { + text: "Problème avec from transformers import GPT3ForSequenceClassification", + explain: "Essayez à nouveau. Bien que cette information soit utile, il est probablement préférable de la réserver au corps du texte.", + }, + { + text: "Pourquoi je ne peux pas importer GPT3ForSequenceClassification?", + explain: "Ce titre est concis et donne au lecteur un indice sur ce qui pourrait être erroné (par exemple, que GPT-3 n'est pas pris en charge dans 🤗 Transformers).", + correct: true + }, + { + text: "Le GPT-3 est-il pris en charge dans 🤗 Transformers ?", + explain: "Utiliser des questions comme titres de sujets est un excellent moyen de communiquer le problème à la communauté.", + correct: true + } + ]} +/> + +### 4. Supposons que vous ayez essayé d'exécuter `trainer.train()` et que vous soyez confronté à une erreur énigmatique qui ne vous dit pas exactement d'où vient l'erreur. Quel est le premier endroit où vous devez chercher les erreurs dans votre pipeline d'entraînement ? + +bugs dans votre optimiseur, cela se produit généralement à plusieurs étapes du pipeline d'entraînement, il y a donc d'autres choses à vérifier d'abord. Essayez à nouveau !" + }, + { + text: "L'étape d'évaluation où nous calculons les métriques", + explain: "L'évaluation est généralement ce que vous faites après l'entraînement pour une époque complète, donc vous devriez d'abord vérifier quelque part plus tôt dans le pipeline d'entraînement.", + }, + { + text: "Les jeux de données", + explain: "C'est exact ! L'examen de vos données est presque toujours la première chose à faire, pour vous assurer que le texte est codé de manière appropriée, qu'il présente les caractéristiques attendues, etc.", + correct: true + }, + { + text: "Les chargeurs de données", + explain: "Essayez à nouveau. C'est très proche de la première chose que vous devriez vérifier. Vous souvenez-vous de l'objet que nous passons aux dataloaders ?" + } + ]} +/> + +### 5. Quelle est la meilleure façon de déboguer une erreur CUDA ? + +traceback pour découvrir ce qui a causé l'erreur.", + explain: "C'est ce que vous feriez pour toute autre erreur, mais les erreurs CUDA ne sont généralement pas signalées là où elles se sont produites, car la plupart des opérations CUDA sont asynchrones." + }, + { + text: "Réduisez la taille du batch.", + explain: "La réduction de la taille du batch est généralement une bonne stratégie pour gérer les erreurs CUDA hors mémoire, mais pas pour ce problème particulier. Essayez à nouveau !" + }, + { + text: "Redémarrez le noyau Jupyter.", + explain: "Essayez à nouveau. Le redémarrage du noyau ne fera pas disparaître l'erreur comme par magie !", + } + ]} +/> + +### 6. Quelle est la meilleure façon de faire corriger un problème sur GitHub ? + +bug.", + explain: "C'est la meilleure façon d'aider les mainteneurs à trouver votre bogue. Que devez-vous faire d'autre ?", + correct: true + }, + { + text: "Demandez chaque jour une mise à jour.", + explain: "Il est peu probable que cela vous apporte de l'aide. Les gens vous ignoreront probablement davantage.", + }, + { + text: "Inspectez le code source autour du bogue et essayez de trouver la raison pour laquelle il se produit. Postez les résultats dans le problème.", + explain: "Cela aidera certainement les mainteneurs ! Et si vous trouvez la source du bogue et un correctif, vous pouvez même ouvrir une demande de modification. Que devez-vous faire d'autre ?", + correct: true + } + ]} +/> + +### 7. Pourquoi le surapprentissage à un batch est-il généralement une bonne technique de débogage ? + + + +### 8. Pourquoi est-ce une bonne idée d'inclure des détails sur votre environnement de calcul avec `transformers-cli env` lorsque vous créez une nouvelle question dans le dépôt 🤗 *Transformers* ? + + diff --git a/chapters/fr/chapter9/1.mdx b/chapters/fr/chapter9/1.mdx index 78afbc891..8dd18331c 100644 --- a/chapters/fr/chapter9/1.mdx +++ b/chapters/fr/chapter9/1.mdx @@ -1,32 +1,37 @@ -# Introduction à Gradio - -Dans ce chapitre, nous allons apprendre à construire des **démos interactives** pour vos modèles d'apprentissage automatique. - -Pourquoi construire une démo ou une interface graphique pour votre modèle d'apprentissage automatique ? Les démos permettent : - -- aux **développeurs en apprentissage automatique** de présenter facilement leur travail à un large public, y compris des équipes non techniques ou des clients. -- aux **chercheurs** de reproduire plus facilement les modèles d'apprentissage automatique et leur comportement. -- aux **testeurs qualité** ou **utilisateurs finaux** d'identifier et de déboguer plus facilement les points de défaillance des modèles. -- aux **utilisateurs divers** de découvrir les biais algorithmiques des modèles. - -Nous utiliserons la bibliothèque *Gradio* pour construire des démos pour nos modèles. *Gradio* vous permet de construire, de personnaliser et de partager des démos en ligne pour n'importe quel modèle d'apprentissage automatique. Et cela entièrement en Python. - -Voici quelques exemples de démos d'apprentissage automatique construites avec Gradio : - -* Un modèle de **reconnaissance de croquis** qui prend un croquis et produit des étiquettes de ce qu'il pense être dessiné : - - - -* Un modèle extractif de **réponse à une question** qui prend en entrée un paragraphe de contexte et une requête et produit une réponse et un score de probabilité (nous avons discuté de ce type de modèle [au chapitre 7](/course/fr/chapter7/7)) : - - - -* Un modèle de **suppression de l'arrière-plan** qui prend une image et la restitue avec l'arrière-plan supprimé : - - - -Ce chapitre est divisé en sections qui comprennent à la fois des _concepts_ et des _applications_. Après avoir appris le concept dans chaque section, vous l'appliquerez pour construire un type particulier de démo, allant de la classification d'images à la reconnaissance vocale. À la fin de ce chapitre, vous serez en mesure de créer ces démos (et bien d'autres !) en quelques lignes de code Python seulement. - - -👀 Consultez Hugging Face Spaces pour voir de nombreux exemples récents de démos d'apprentissage automatique construites par la communauté ! - +# Introduction à Gradio + + + +Dans ce chapitre, nous allons apprendre à construire des **démos interactives** pour vos modèles d'apprentissage automatique. + +Pourquoi construire une démo ou une interface graphique pour votre modèle d'apprentissage automatique ? Les démos permettent : + +- aux **développeurs en apprentissage automatique** de présenter facilement leur travail à un large public, y compris des équipes non techniques ou des clients. +- aux **chercheurs** de reproduire plus facilement les modèles d'apprentissage automatique et leur comportement. +- aux **testeurs qualité** ou **utilisateurs finaux** d'identifier et de déboguer plus facilement les points de défaillance des modèles. +- aux **utilisateurs divers** de découvrir les biais algorithmiques des modèles. + +Nous utiliserons la bibliothèque *Gradio* pour construire des démos pour nos modèles. *Gradio* vous permet de construire, de personnaliser et de partager des démos en ligne pour n'importe quel modèle d'apprentissage automatique. Et cela entièrement en Python. + +Voici quelques exemples de démos d'apprentissage automatique construites avec Gradio : + +* Un modèle de **reconnaissance de croquis** qui prend un croquis et produit des étiquettes de ce qu'il pense être dessiné : + + + +* Un modèle extractif de **réponse à une question** qui prend en entrée un paragraphe de contexte et une requête et produit une réponse et un score de probabilité (nous avons discuté de ce type de modèle [au chapitre 7](/course/fr/chapter7/7)) : + + + +* Un modèle de **suppression de l'arrière-plan** qui prend une image et la restitue avec l'arrière-plan supprimé : + + + +Ce chapitre est divisé en sections qui comprennent à la fois des _concepts_ et des _applications_. Après avoir appris le concept dans chaque section, vous l'appliquerez pour construire un type particulier de démo, allant de la classification d'images à la reconnaissance vocale. À la fin de ce chapitre, vous serez en mesure de créer ces démos (et bien d'autres !) en quelques lignes de code Python seulement. + + +👀 Consultez Hugging Face Spaces pour voir de nombreux exemples récents de démos d'apprentissage automatique construites par la communauté ! + diff --git a/chapters/fr/chapter9/2.mdx b/chapters/fr/chapter9/2.mdx index 813c3df3d..2a15df2a7 100644 --- a/chapters/fr/chapter9/2.mdx +++ b/chapters/fr/chapter9/2.mdx @@ -1,117 +1,117 @@ -# Construire votre première démo - - - -Commençons par installer *Gradio* ! Comme il s'agit d'un *package* Python, il suffit de l'exécuter : - -`$ pip install gradio ` - -Vous pouvez exécuter *Gradio* n'importe où, que ce soit dans votre IDE Python préféré, dans des *notebooks* ou même dans Google Colab 🤯 ! -Alors installez *Gradio* partout où vous exécutez Python ! - -Commençons par un exemple simple de type « *Hello World* » pour nous familiariser avec la syntaxe de *Gradio* : - -```py -import gradio as gr - - -def greet(name): - return "Hello " + name - - -demo = gr.Interface(fn=greet, inputs="text", outputs="text") - -demo.launch() -``` - -Parcourons le code ci-dessus : - -- D'abord, nous définissons une fonction appelée `greet()`. Dans ce cas, c'est une simple fonction qui ajoute « *Hello* » devant votre nom, mais cela peut être *n'importe quelle* fonction Python en général. Par exemple, dans les applications d'apprentissage automatique, cette fonction pourrait *appeler un modèle pour faire une prédiction* sur une entrée et retourner la sortie. -- Ensuite, nous créons une `Interface` *Gradio* avec trois arguments, `fn`, `inputs`, et `outputs`. Ces arguments définissent la fonction de prédiction, ainsi que le _type_ de composants d'entrée et de sortie que nous souhaitons. Dans notre cas, les deux composants sont de simples boîtes de texte. -- Nous appelons ensuite la méthode `launch()` sur l'`Interface` que nous avons créée. - -Si vous exécutez ce code, l'interface ci-dessous apparaîtra automatiquement dans un *notebook* Jupyter/Colab ou dans un navigateur sur **[http://localhost:7860](http://localhost:7860/)** si vous l'exécutez à partir d'un script. - - - -Essayez d'utiliser cette interface maintenant avec votre propre nom ou une autre entrée ! - -Vous remarquerez que dedans, *Gradio* a automatiquement déduit le nom du paramètre d'entrée (`name`) et l'a appliqué comme étiquette au dessus de la zone de texte. Que faire si vous souhaitez changer cela ? -Ou si vous souhaitez personnaliser la zone de texte d'une autre manière ? Dans ce cas, vous pouvez instancier un objet de classe représentant le composant de saisie. - -Jetez un coup d'œil à l'exemple ci-dessous : - -```py -import gradio as gr - - -def greet(name): - return "Hello " + name - - -# Nous instancions la classe Textbox -textbox = gr.Textbox(label="Type your name here:", placeholder="John Doe", lines=2) - -gr.Interface(fn=greet, inputs=textbox, outputs="text").launch() -``` - - - -Ici, nous avons créé une zone de texte d'entrée avec une étiquette, un espace réservé et un nombre de lignes défini. -Vous pourriez faire la même chose pour la zone de texte de sortie, mais nous allons laisser cela pour le moment. - -Nous avons vu qu'avec seulement quelques lignes de code, *Gradio* vous permet de créer une interface simple autour de n'importe quelle fonction -avec n'importe quel type d'entrées ou de sorties. Dans cette section, nous avons commencé par une simple boîte de texte mais dans les sections suivantes, nous couvrirons d'autres types d'entrées et de sorties. Voyons maintenant comment inclure un peu de NLP dans une application *Gradio*. - - -## 🤖 Inclure les prédictions du modèle - -Construisons maintenant une interface simple qui permet de faire une démo d'un modèle de **génération de texte** comme le GPT-2. - -Nous allons charger notre modèle en utilisant la fonction `pipeline()` de 🤗 *Transformers*. -Si vous avez besoin d'un rafraîchissement rapide, vous pouvez revenir à [cette section du chapitre 1](/course/fr/chapter1/3#text-generation). - -Tout d'abord, nous définissons une fonction de prédiction qui prend une invite de texte et renvoie la complétion du texte : - -```py -from transformers import pipeline - -model = pipeline("text-generation") - - -def predict(prompt): - completion = model(prompt)[0]["generated_text"] - return completion -``` - -Cette fonction complète le texte que vous fournissez, et vous pouvez l'exécuter avec les votres pour voir comment elle fonctionne. Voici un exemple (vous obtiendrez peut-être un résultat différent) : - - -``` -predict("My favorite programming language is") # Mon langage de programmation préféré est -``` - -``` ->> 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. -# Mon langage de programmation préféré est Haskell. J'ai vraiment apprécié le langage Haskell, mais il n'a pas toutes les caractéristiques que l'on peut appliquer à n'importe quel autre langage. Par exemple, il ne fait que compiler un tableau d'octets. -``` - -Maintenant que nous avons une fonction pour générer des prédictions, nous pouvons créer et lancer une `Interface` de la même manière que nous l'avons fait précédemment : - -```py -import gradio as gr - -gr.Interface(fn=predict, inputs="text", outputs="text").launch() -``` - - -C'est fait ! Vous pouvez maintenant utiliser cette interface pour générer du texte en utilisant le modèle GPT-2 comme indiqué ci-dessous 🤯. - - - +# Construire votre première démo + + + +Commençons par installer *Gradio* ! Comme il s'agit d'un *package* Python, il suffit de l'exécuter : + +`$ pip install gradio ` + +Vous pouvez exécuter *Gradio* n'importe où, que ce soit dans votre IDE Python préféré, dans des *notebooks* ou même dans Google Colab 🤯 ! +Alors installez *Gradio* partout où vous exécutez Python ! + +Commençons par un exemple simple de type « *Hello World* » pour nous familiariser avec la syntaxe de *Gradio* : + +```py +import gradio as gr + + +def greet(name): + return "Hello " + name + + +demo = gr.Interface(fn=greet, inputs="text", outputs="text") + +demo.launch() +``` + +Parcourons le code ci-dessus : + +- D'abord, nous définissons une fonction appelée `greet()`. Dans ce cas, c'est une simple fonction qui ajoute « *Hello* » devant votre nom, mais cela peut être *n'importe quelle* fonction Python en général. Par exemple, dans les applications d'apprentissage automatique, cette fonction pourrait *appeler un modèle pour faire une prédiction* sur une entrée et retourner la sortie. +- Ensuite, nous créons une `Interface` *Gradio* avec trois arguments, `fn`, `inputs`, et `outputs`. Ces arguments définissent la fonction de prédiction, ainsi que le _type_ de composants d'entrée et de sortie que nous souhaitons. Dans notre cas, les deux composants sont de simples boîtes de texte. +- Nous appelons ensuite la méthode `launch()` sur l'`Interface` que nous avons créée. + +Si vous exécutez ce code, l'interface ci-dessous apparaîtra automatiquement dans un *notebook* Jupyter/Colab ou dans un navigateur sur **[http://localhost:7860](http://localhost:7860/)** si vous l'exécutez à partir d'un script. + + + +Essayez d'utiliser cette interface maintenant avec votre propre nom ou une autre entrée ! + +Vous remarquerez que dedans, *Gradio* a automatiquement déduit le nom du paramètre d'entrée (`name`) et l'a appliqué comme étiquette au dessus de la zone de texte. Que faire si vous souhaitez changer cela ? +Ou si vous souhaitez personnaliser la zone de texte d'une autre manière ? Dans ce cas, vous pouvez instancier un objet de classe représentant le composant de saisie. + +Jetez un coup d'œil à l'exemple ci-dessous : + +```py +import gradio as gr + + +def greet(name): + return "Hello " + name + + +# Nous instancions la classe Textbox +textbox = gr.Textbox(label="Type your name here:", placeholder="John Doe", lines=2) + +gr.Interface(fn=greet, inputs=textbox, outputs="text").launch() +``` + + + +Ici, nous avons créé une zone de texte d'entrée avec une étiquette, un espace réservé et un nombre de lignes défini. +Vous pourriez faire la même chose pour la zone de texte de sortie, mais nous allons laisser cela pour le moment. + +Nous avons vu qu'avec seulement quelques lignes de code, *Gradio* vous permet de créer une interface simple autour de n'importe quelle fonction +avec n'importe quel type d'entrées ou de sorties. Dans cette section, nous avons commencé par une simple boîte de texte mais dans les sections suivantes, nous couvrirons d'autres types d'entrées et de sorties. Voyons maintenant comment inclure un peu de NLP dans une application *Gradio*. + + +## 🤖 Inclure les prédictions du modèle + +Construisons maintenant une interface simple qui permet de faire une démo d'un modèle de **génération de texte** comme le GPT-2. + +Nous allons charger notre modèle en utilisant la fonction `pipeline()` de 🤗 *Transformers*. +Si vous avez besoin d'un rafraîchissement rapide, vous pouvez revenir à [cette section du chapitre 1](/course/fr/chapter1/3#text-generation). + +Tout d'abord, nous définissons une fonction de prédiction qui prend une invite de texte et renvoie la complétion du texte : + +```py +from transformers import pipeline + +model = pipeline("text-generation") + + +def predict(prompt): + completion = model(prompt)[0]["generated_text"] + return completion +``` + +Cette fonction complète le texte que vous fournissez, et vous pouvez l'exécuter avec les votres pour voir comment elle fonctionne. Voici un exemple (vous obtiendrez peut-être un résultat différent) : + + +``` +predict("My favorite programming language is") # Mon langage de programmation préféré est +``` + +``` +>> 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. +# Mon langage de programmation préféré est Haskell. J'ai vraiment apprécié le langage Haskell, mais il n'a pas toutes les caractéristiques que l'on peut appliquer à n'importe quel autre langage. Par exemple, il ne fait que compiler un tableau d'octets. +``` + +Maintenant que nous avons une fonction pour générer des prédictions, nous pouvons créer et lancer une `Interface` de la même manière que nous l'avons fait précédemment : + +```py +import gradio as gr + +gr.Interface(fn=predict, inputs="text", outputs="text").launch() +``` + + +C'est fait ! Vous pouvez maintenant utiliser cette interface pour générer du texte en utilisant le modèle GPT-2 comme indiqué ci-dessous 🤯. + + + Continuez votre lecture du cours pour voir comment construire d'autres types de démos avec *Gradio* ! \ No newline at end of file diff --git a/chapters/fr/chapter9/3.mdx b/chapters/fr/chapter9/3.mdx index 3a6b97f70..2ed908d58 100644 --- a/chapters/fr/chapter9/3.mdx +++ b/chapters/fr/chapter9/3.mdx @@ -1,166 +1,166 @@ -# Comprendre la classe Interface - - - -Dans cette section, nous allons examiner de plus près la classe `Interface`, et comprendre les principaux paramètres utilisés pour en créer une. - -## Comment créer une interface - -Vous remarquerez que la classe `Interface` a 3 paramètres obligatoires : - -`Interface(fn, inputs, outputs, ...)` - -Ces paramètres sont : - - - `fn`: la fonction de prédiction qui est enveloppée par l'interface *Gradio*. Cette fonction peut prendre un ou plusieurs paramètres et retourner une ou plusieurs valeurs. - - `inputs`: le(s) type(s) de composant(s) d'entrée. *Gradio* fournit de nombreux composants préconstruits tels que`"image"` ou `"mic"`. - - `outputs`: le(s) type(s) de composant(s) de sortie. Encore une fois, *Gradio* fournit de nombreux composants pré-construits, par ex. `"image"` ou `"label"`. - -Pour une liste complète des composants, [jetez un coup d'œil à la documentation de *Gradio*](https://gradio.app/docs). Chaque composant préconstruit peut être personnalisé en instanciant la classe correspondant au composant. - -Par exemple, comme nous l'avons vu dans la [section précédente](/course/fr/chapter9/2), au lieu de passer le paramètre `inputs` par `"textbox"`, vous pouvez passer un composant `Textbox(lines=7, label="Prompt")` pour créer une zone de texte avec 7 lignes et un label. - -Voyons un autre exemple, cette fois avec un composant `Audio`. - -## Un exemple simple avec audio - -Comme mentionné précédemment, *Gradio* fournit de nombreuses entrées et sorties différentes. -Construisons donc une `Interface` qui fonctionne avec l'audio. - -Dans cet exemple, nous allons construire une fonction audio-vers-audio qui prend un fichier audio et l'inverse simplement. - -Nous utiliserons comme entrée le composant `Audio`. Lorsque vous utilisez le composant `Audio`, vous pouvez spécifier si vous voulez que la `source` de l'audio soit un fichier que l'utilisateur télécharge ou un microphone avec lequel l'utilisateur enregistre sa voix. Dans ce cas, nous allons choisir un microphone. Juste pour le plaisir, nous allons ajouter une étiquette à notre `Audio` qui dit « *Speak here...* » (Parler ici). - -De plus, nous aimerions recevoir l'audio sous la forme d'un tableau numpy afin de pouvoir facilement l'inverser. Nous allons donc définir le `"type"` comme étant `"numpy"`, ce qui permet de passer les données d'entrée comme un *tuple* de (`sample_rate`, `data`) dans notre fonction. - -Nous utiliserons également le composant de sortie `Audio` qui peut automatiquement rendre un *tuple* avec un taux d'échantillonnage et un tableau numpy de données comme un fichier audio lisible. -Dans ce cas, nous n'avons pas besoin de faire de personnalisation, donc nous utiliserons le raccourci de la chaîne `"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() -``` - -Le code ci-dessus produira une interface comme celle qui suit (si votre navigateur ne vous demande pas l'autorisation pour accéder au microphone, ouvrez la démo dans un onglet séparé). - - - -Vous devriez maintenant être capable d'enregistrer votre voix et de vous entendre parler à l'envers. Effrayant 👻 ! - -## Gérer les entrées et sorties multiples - -Imaginons que nous ayons une fonction plus compliquée, avec plusieurs entrées et sorties. -Dans l'exemple ci-dessous, nous avons une fonction qui prend un index de liste déroulante, une valeur de curseur et un nombre, et renvoie un échantillon audio d'une tonalité musicale. - -Regardez comment nous passons une liste de composants d'entrée et de sortie, et voyez si vous pouvez suivre ce qui se passe. - -La clé ici est que lorsque vous passez : -* une liste de composants d'entrée, chaque composant correspond à un paramètre dans l'ordre. -* une liste de composants de sortie, chaque composant correspond à une valeur retournée. - -L'extrait de code ci-dessous montre comment trois composants d'entrée correspondent aux trois arguments de la fonction `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() -``` - - - - -### La méthode `launch()` - -Jusqu'à présent, nous avons utilisé la méthode `launch()` pour lancer l'interface, mais nous n'avons pas vraiment discuté de ce qu'elle fait. - -Par défaut, la méthode `launch()` lancera la démo dans un serveur web qui tourne localement. Si vous exécutez votre code dans un *notebook* Jupyter ou Colab, *Gradio* va intégrer l'interface graphique de la démo dans le *notebook* afin que vous puissiez l'utiliser facilement. - -Vous pouvez personnaliser le comportement de `launch()` à travers différents paramètres : - - - `inline` : si vous voulez afficher l'interface en ligne sur les *notebooks* Python. - - `inbrowser` : pour lancer automatiquement l'interface dans un nouvel onglet du navigateur par défaut. - - `share` : si vous voulez créer un lien public partageable depuis votre ordinateur pour l'interface. Un peu comme un lien Google Drive ! - -Nous couvrirons le paramètre `share` plus en détail dans la section suivante ! - -## ✏️ Appliquons ça ! - -Construisons une interface qui vous permette de faire la démonstration d'un modèle de **reconnaissance vocale**. -Pour rendre la chose intéressante, nous accepterons *soit* une entrée micro, soit un fichier téléchargé. - -Comme d'habitude, nous allons charger notre modèle de reconnaissance vocale en utilisant la fonction `pipeline()` de 🤗 *Transformers*. -Si vous avez besoin d'un rafraîchissement rapide, vous pouvez revenir à [cette section du chapitre 1](/course/fr/chapter1/3). Ensuite, nous allons implémenter une fonction `transcribe_audio()` qui traite l'audio et retourne la transcription (en anglais). Enfin, nous allons envelopper cette fonction dans une `Interface` avec les composants `Audio` pour les entrées et juste le texte pour la sortie. Au total, le code de cette application est le suivant : - -```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() -``` - -Si votre navigateur ne vous demande pas l'autorisation pour accéder au microphone, ouvrez la démo dans un onglet séparé. - - - -Voilà, c'est fait ! Vous pouvez maintenant utiliser cette interface pour transcrire de l'audio. Remarquez ici qu'en passant le paramètre `optional` à `True`, nous permettons à l'utilisateur de soit fournir un microphone ou un fichier audio (ou aucun des deux, mais cela retournera un message d'erreur). - -Continuez pour voir comment partager votre interface avec d'autres ! +# Comprendre la classe Interface + + + +Dans cette section, nous allons examiner de plus près la classe `Interface`, et comprendre les principaux paramètres utilisés pour en créer une. + +## Comment créer une interface + +Vous remarquerez que la classe `Interface` a 3 paramètres obligatoires : + +`Interface(fn, inputs, outputs, ...)` + +Ces paramètres sont : + + - `fn`: la fonction de prédiction qui est enveloppée par l'interface *Gradio*. Cette fonction peut prendre un ou plusieurs paramètres et retourner une ou plusieurs valeurs. + - `inputs`: le(s) type(s) de composant(s) d'entrée. *Gradio* fournit de nombreux composants préconstruits tels que`"image"` ou `"mic"`. + - `outputs`: le(s) type(s) de composant(s) de sortie. Encore une fois, *Gradio* fournit de nombreux composants pré-construits, par ex. `"image"` ou `"label"`. + +Pour une liste complète des composants, [jetez un coup d'œil à la documentation de *Gradio*](https://gradio.app/docs). Chaque composant préconstruit peut être personnalisé en instanciant la classe correspondant au composant. + +Par exemple, comme nous l'avons vu dans la [section précédente](/course/fr/chapter9/2), au lieu de passer le paramètre `inputs` par `"textbox"`, vous pouvez passer un composant `Textbox(lines=7, label="Prompt")` pour créer une zone de texte avec 7 lignes et un label. + +Voyons un autre exemple, cette fois avec un composant `Audio`. + +## Un exemple simple avec audio + +Comme mentionné précédemment, *Gradio* fournit de nombreuses entrées et sorties différentes. +Construisons donc une `Interface` qui fonctionne avec l'audio. + +Dans cet exemple, nous allons construire une fonction audio-vers-audio qui prend un fichier audio et l'inverse simplement. + +Nous utiliserons comme entrée le composant `Audio`. Lorsque vous utilisez le composant `Audio`, vous pouvez spécifier si vous voulez que la `source` de l'audio soit un fichier que l'utilisateur télécharge ou un microphone avec lequel l'utilisateur enregistre sa voix. Dans ce cas, nous allons choisir un microphone. Juste pour le plaisir, nous allons ajouter une étiquette à notre `Audio` qui dit « *Speak here...* » (Parler ici). + +De plus, nous aimerions recevoir l'audio sous la forme d'un tableau numpy afin de pouvoir facilement l'inverser. Nous allons donc définir le `"type"` comme étant `"numpy"`, ce qui permet de passer les données d'entrée comme un *tuple* de (`sample_rate`, `data`) dans notre fonction. + +Nous utiliserons également le composant de sortie `Audio` qui peut automatiquement rendre un *tuple* avec un taux d'échantillonnage et un tableau numpy de données comme un fichier audio lisible. +Dans ce cas, nous n'avons pas besoin de faire de personnalisation, donc nous utiliserons le raccourci de la chaîne `"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() +``` + +Le code ci-dessus produira une interface comme celle qui suit (si votre navigateur ne vous demande pas l'autorisation pour accéder au microphone, ouvrez la démo dans un onglet séparé). + + + +Vous devriez maintenant être capable d'enregistrer votre voix et de vous entendre parler à l'envers. Effrayant 👻 ! + +## Gérer les entrées et sorties multiples + +Imaginons que nous ayons une fonction plus compliquée, avec plusieurs entrées et sorties. +Dans l'exemple ci-dessous, nous avons une fonction qui prend un index de liste déroulante, une valeur de curseur et un nombre, et renvoie un échantillon audio d'une tonalité musicale. + +Regardez comment nous passons une liste de composants d'entrée et de sortie, et voyez si vous pouvez suivre ce qui se passe. + +La clé ici est que lorsque vous passez : +* une liste de composants d'entrée, chaque composant correspond à un paramètre dans l'ordre. +* une liste de composants de sortie, chaque composant correspond à une valeur retournée. + +L'extrait de code ci-dessous montre comment trois composants d'entrée correspondent aux trois arguments de la fonction `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() +``` + + + + +### La méthode `launch()` + +Jusqu'à présent, nous avons utilisé la méthode `launch()` pour lancer l'interface, mais nous n'avons pas vraiment discuté de ce qu'elle fait. + +Par défaut, la méthode `launch()` lancera la démo dans un serveur web qui tourne localement. Si vous exécutez votre code dans un *notebook* Jupyter ou Colab, *Gradio* va intégrer l'interface graphique de la démo dans le *notebook* afin que vous puissiez l'utiliser facilement. + +Vous pouvez personnaliser le comportement de `launch()` à travers différents paramètres : + + - `inline` : si vous voulez afficher l'interface en ligne sur les *notebooks* Python. + - `inbrowser` : pour lancer automatiquement l'interface dans un nouvel onglet du navigateur par défaut. + - `share` : si vous voulez créer un lien public partageable depuis votre ordinateur pour l'interface. Un peu comme un lien Google Drive ! + +Nous couvrirons le paramètre `share` plus en détail dans la section suivante ! + +## ✏️ Appliquons ça ! + +Construisons une interface qui vous permette de faire la démonstration d'un modèle de **reconnaissance vocale**. +Pour rendre la chose intéressante, nous accepterons *soit* une entrée micro, soit un fichier téléchargé. + +Comme d'habitude, nous allons charger notre modèle de reconnaissance vocale en utilisant la fonction `pipeline()` de 🤗 *Transformers*. +Si vous avez besoin d'un rafraîchissement rapide, vous pouvez revenir à [cette section du chapitre 1](/course/fr/chapter1/3). Ensuite, nous allons implémenter une fonction `transcribe_audio()` qui traite l'audio et retourne la transcription (en anglais). Enfin, nous allons envelopper cette fonction dans une `Interface` avec les composants `Audio` pour les entrées et juste le texte pour la sortie. Au total, le code de cette application est le suivant : + +```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() +``` + +Si votre navigateur ne vous demande pas l'autorisation pour accéder au microphone, ouvrez la démo dans un onglet séparé. + + + +Voilà, c'est fait ! Vous pouvez maintenant utiliser cette interface pour transcrire de l'audio. Remarquez ici qu'en passant le paramètre `optional` à `True`, nous permettons à l'utilisateur de soit fournir un microphone ou un fichier audio (ou aucun des deux, mais cela retournera un message d'erreur). + +Continuez pour voir comment partager votre interface avec d'autres ! diff --git a/chapters/fr/chapter9/4.mdx b/chapters/fr/chapter9/4.mdx index ffea14513..d929ef20b 100644 --- a/chapters/fr/chapter9/4.mdx +++ b/chapters/fr/chapter9/4.mdx @@ -1,147 +1,147 @@ -# Partager ses démos avec les autres - - - -Maintenant que vous avez construit une démo, vous voudrez probablement la partager à d'autres personnes. Les démos *Gradio* peuvent être partagées de deux façons : en utilisant un lien de partage temporaire (***temporary share link***) ou un hébergement permanent (***permanent hosting on Spaces***). - -Nous aborderons ces deux approches sous peu. Mais avant de partager votre démo, vous voudrez peut-être la peaufiner 💅. - -### Polir votre démo Gradio - -
-Overview of a gradio interface - -
- -Pour ajouter du contenu supplémentaire à votre démo, la classe `Interface` supporte quelques paramètres optionnels : - - `title` : vous pouvez donner un titre à votre démo, qui apparaît _au-dessus_ des composants d'entrée et de sortie. - - `description` : vous pouvez donner une description (en texte, Markdown, ou HTML) pour l'interface, qui apparaît au-dessus des composants d'entrée et de sortie et en dessous du titre. - - `article` : vous pouvez également écrire un article étendu (en texte, Markdown ou HTML) expliquant l'interface. S'il est fourni, il apparaît _sous_ les composants d'entrée et de sortie. - - `theme` : vous n'aimez pas les couleurs par défaut ? Définissez le thème pour utiliser une des couleurs suivantes : `default`, `huggingface`, `grass`, `peach`. Vous pouvez également ajouter le préfixe `dark-`, par exemple `dark-peach` pour un thème sombre (ou juste `dark` pour le thème sombre par défaut). - - `examples` : pour rendre votre démo *beaucoup plus facile à utiliser*, vous pouvez fournir quelques exemples d'entrées pour la fonction. Ceux-ci apparaissent sous les composants de l'interface utilisateur et peuvent être utilisés pour remplir l'interface. Ils doivent être fournis sous forme de liste imbriquée, dans laquelle la liste extérieure est constituée d'exemples et chaque liste intérieure est constituée d'une entrée correspondant à chaque composant d'entrée. - - `live` : si vous voulez que votre modèle soit relancé à chaque fois que l'entrée change, vous pouvez mettre `live=True`. Ceci est utile pour les modèles rapides (nous verrons un exemple à la fin de cette section). -En utilisant les options ci-dessus, nous obtenons une interface plus complète. Exécutez le code ci-dessous pour pouvoir discuter avec Rick et Morty : - -```python out -title = "Ask Rick a Question" # "Posez une question à Rick" -description = -""" -The bot was trained to answer questions based on Rick and Morty dialogues. Ask Rick anything! -# Le robot a été entraîné à répondre à des questions basées sur les dialogues de Rick et Morty. -# Demandez à Rick ce que vous voulez ! - -""" - -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." -# Jetez un coup d'œil au [bot original Rick et Morty](https://huggingface.co/spaces/kingabzpro/Rick_and_Morty_Bot) sur lequel cette démo est basée. - -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?"]] - # ["Que faites-vous ?"], ["Où devrions-nous voyager dans le temps ?"] -).launch() -``` - -En utilisant les options ci-dessus, nous obtenons une interface plus complète. Essayez l'interface ci-dessous : - - - -### Partager votre démo avec des liens temporaires -Maintenant que nous avons une démo fonctionnelle de notre modèle d'apprentissage automatique, apprenons à partager facilement un lien vers notre interface. -Les interfaces peuvent être facilement partagées publiquement en mettant `share=True` dans la méthode `launch()` : - -```python -gr.Interface(classify_image, "image", "label").launch(share=True) -``` - -Cela génère un lien public et partageable que vous pouvez envoyer à n'importe qui ! Lorsque vous envoyez ce lien, l'utilisateur de l'autre côté peut essayer le modèle dans son navigateur pendant 72 heures au maximum. Le traitement s'effectuant sur votre appareil (tant qu'il reste allumé !), vous n'avez pas à vous soucier de la mise en place de dépendances. Si vous travaillez à partir d'un *notebook* Google Colab, un lien de partage est toujours créé automatiquement. Il ressemble généralement à quelque chose comme ceci : **XXXXX.gradio.app**. Bien que le lien soit servi par un lien *Gradio*, nous ne sommes qu'un proxy pour votre serveur local, et nous ne stockons pas les données envoyées par les interfaces. - -Gardez cependant à l'esprit que ces liens sont accessibles au public, ce qui signifie que n'importe qui peut utiliser votre modèle pour la prédiction ! Par conséquent, assurez-vous de ne pas exposer d'informations sensibles à travers les fonctions que vous écrivez, ou de permettre que des changements critiques se produisent sur votre appareil. Si vous définissez `share=False` (la valeur par défaut), seul un lien local est créé. - -### Hébergement de votre démo sur Hugging Face Spaces - -Un lien de partage que vous pouvez passer à vos collègues est cool, mais comment pouvez-vous héberger de façon permanente votre démo et la faire exister dans son propre « espace » sur internet ? - -*Hugging Face Spaces* fournit l'infrastructure pour héberger de façon permanente votre démo *Gradio* sur internet et **gratuitement** ! *Spaces* vous permet de créer et de pousser vers un dépôt (public ou privé) le code de votre interface *Gradio*. Il sera placé dans un fichier `app.py`. [Lisez ce tutoriel étape par étape](https://huggingface.co/blog/gradio-spaces) pour commencer ou regardez la vidéo ci-dessous. - - - -## ✏️ Appliquons ça ! - -En utilisant ce que nous avons appris dans les sections précédentes, créons la démo de reconnaissance de croquis que nous avons décrit dans la [section un de ce chapitre](/course/fr/chapter9/1). Ajoutons quelques personnalisations à notre interface et définissons `share=True` pour créer un lien public que nous pouvons faire circuler. - -Nous pouvons charger les étiquettes depuis [class_names.txt](https://huggingface.co/spaces/dawood/Sketch-Recognition/blob/main/class_names.txt) et charger le modèle Pytorch pré-entraîné depuis [pytorch_model.bin](https://huggingface.co/spaces/dawood/Sketch-Recognition/blob/main/pytorch_model.bin). Téléchargez ces fichiers en suivant le lien et en cliquant sur « *download* » dans le coin supérieur gauche de l'aperçu du fichier. Regardons le code ci-dessous pour voir comment nous utilisons ces fichiers pour charger notre modèle et créer une fonction `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)} -``` - -Maintenant que nous avons une fonction `predict()`. La prochaine étape est de définir et de lancer notre interface *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!", - # Qui veut jouer au Pictionary ? Dessinez un objet courant comme une pelle ou un ordinateur portable, et l'algorithme le devinera en temps réel ! - article="

Sketch Recognition | Demo Model

", - live=True, -) -interface.launch(share=True) -``` - - - - -Remarquez le paramètre `live=True` dans `Interface`, qui signifie que la démo de sketchs fait une prédiction chaque fois que quelqu'un dessine sur le bloc (pas de bouton de soumission !). - -De plus, nous avons également défini l'argument `share=True` dans la méthode `launch()`. -Cela créera un lien public que vous pourrez envoyer à n'importe qui ! Lorsque vous envoyez ce lien, l'utilisateur de l'autre côté peut essayer le modèle de reconnaissance de croquis. Pour réitérer, vous pouvez également héberger le modèle sur *Hugging Face Spaces*, ce qui nous permet d'intégrer la démo ci-dessus. - +# Partager ses démos avec les autres + + + +Maintenant que vous avez construit une démo, vous voudrez probablement la partager à d'autres personnes. Les démos *Gradio* peuvent être partagées de deux façons : en utilisant un lien de partage temporaire (***temporary share link***) ou un hébergement permanent (***permanent hosting on Spaces***). + +Nous aborderons ces deux approches sous peu. Mais avant de partager votre démo, vous voudrez peut-être la peaufiner 💅. + +### Polir votre démo Gradio + +
+Overview of a gradio interface + +
+ +Pour ajouter du contenu supplémentaire à votre démo, la classe `Interface` supporte quelques paramètres optionnels : + - `title` : vous pouvez donner un titre à votre démo, qui apparaît _au-dessus_ des composants d'entrée et de sortie. + - `description` : vous pouvez donner une description (en texte, Markdown, ou HTML) pour l'interface, qui apparaît au-dessus des composants d'entrée et de sortie et en dessous du titre. + - `article` : vous pouvez également écrire un article étendu (en texte, Markdown ou HTML) expliquant l'interface. S'il est fourni, il apparaît _sous_ les composants d'entrée et de sortie. + - `theme` : vous n'aimez pas les couleurs par défaut ? Définissez le thème pour utiliser une des couleurs suivantes : `default`, `huggingface`, `grass`, `peach`. Vous pouvez également ajouter le préfixe `dark-`, par exemple `dark-peach` pour un thème sombre (ou juste `dark` pour le thème sombre par défaut). + - `examples` : pour rendre votre démo *beaucoup plus facile à utiliser*, vous pouvez fournir quelques exemples d'entrées pour la fonction. Ceux-ci apparaissent sous les composants de l'interface utilisateur et peuvent être utilisés pour remplir l'interface. Ils doivent être fournis sous forme de liste imbriquée, dans laquelle la liste extérieure est constituée d'exemples et chaque liste intérieure est constituée d'une entrée correspondant à chaque composant d'entrée. + - `live` : si vous voulez que votre modèle soit relancé à chaque fois que l'entrée change, vous pouvez mettre `live=True`. Ceci est utile pour les modèles rapides (nous verrons un exemple à la fin de cette section). +En utilisant les options ci-dessus, nous obtenons une interface plus complète. Exécutez le code ci-dessous pour pouvoir discuter avec Rick et Morty : + +```python out +title = "Ask Rick a Question" # "Posez une question à Rick" +description = +""" +The bot was trained to answer questions based on Rick and Morty dialogues. Ask Rick anything! +# Le robot a été entraîné à répondre à des questions basées sur les dialogues de Rick et Morty. +# Demandez à Rick ce que vous voulez ! + +""" + +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." +# Jetez un coup d'œil au [bot original Rick et Morty](https://huggingface.co/spaces/kingabzpro/Rick_and_Morty_Bot) sur lequel cette démo est basée. + +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?"]] + # ["Que faites-vous ?"], ["Où devrions-nous voyager dans le temps ?"] +).launch() +``` + +En utilisant les options ci-dessus, nous obtenons une interface plus complète. Essayez l'interface ci-dessous : + + + +### Partager votre démo avec des liens temporaires +Maintenant que nous avons une démo fonctionnelle de notre modèle d'apprentissage automatique, apprenons à partager facilement un lien vers notre interface. +Les interfaces peuvent être facilement partagées publiquement en mettant `share=True` dans la méthode `launch()` : + +```python +gr.Interface(classify_image, "image", "label").launch(share=True) +``` + +Cela génère un lien public et partageable que vous pouvez envoyer à n'importe qui ! Lorsque vous envoyez ce lien, l'utilisateur de l'autre côté peut essayer le modèle dans son navigateur pendant 72 heures au maximum. Le traitement s'effectuant sur votre appareil (tant qu'il reste allumé !), vous n'avez pas à vous soucier de la mise en place de dépendances. Si vous travaillez à partir d'un *notebook* Google Colab, un lien de partage est toujours créé automatiquement. Il ressemble généralement à quelque chose comme ceci : **XXXXX.gradio.app**. Bien que le lien soit servi par un lien *Gradio*, nous ne sommes qu'un proxy pour votre serveur local, et nous ne stockons pas les données envoyées par les interfaces. + +Gardez cependant à l'esprit que ces liens sont accessibles au public, ce qui signifie que n'importe qui peut utiliser votre modèle pour la prédiction ! Par conséquent, assurez-vous de ne pas exposer d'informations sensibles à travers les fonctions que vous écrivez, ou de permettre que des changements critiques se produisent sur votre appareil. Si vous définissez `share=False` (la valeur par défaut), seul un lien local est créé. + +### Hébergement de votre démo sur Hugging Face Spaces + +Un lien de partage que vous pouvez passer à vos collègues est cool, mais comment pouvez-vous héberger de façon permanente votre démo et la faire exister dans son propre « espace » sur internet ? + +*Hugging Face Spaces* fournit l'infrastructure pour héberger de façon permanente votre démo *Gradio* sur internet et **gratuitement** ! *Spaces* vous permet de créer et de pousser vers un dépôt (public ou privé) le code de votre interface *Gradio*. Il sera placé dans un fichier `app.py`. [Lisez ce tutoriel étape par étape](https://huggingface.co/blog/gradio-spaces) pour commencer ou regardez la vidéo ci-dessous. + + + +## ✏️ Appliquons ça ! + +En utilisant ce que nous avons appris dans les sections précédentes, créons la démo de reconnaissance de croquis que nous avons décrit dans la [section un de ce chapitre](/course/fr/chapter9/1). Ajoutons quelques personnalisations à notre interface et définissons `share=True` pour créer un lien public que nous pouvons faire circuler. + +Nous pouvons charger les étiquettes depuis [class_names.txt](https://huggingface.co/spaces/dawood/Sketch-Recognition/blob/main/class_names.txt) et charger le modèle Pytorch pré-entraîné depuis [pytorch_model.bin](https://huggingface.co/spaces/dawood/Sketch-Recognition/blob/main/pytorch_model.bin). Téléchargez ces fichiers en suivant le lien et en cliquant sur « *download* » dans le coin supérieur gauche de l'aperçu du fichier. Regardons le code ci-dessous pour voir comment nous utilisons ces fichiers pour charger notre modèle et créer une fonction `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)} +``` + +Maintenant que nous avons une fonction `predict()`. La prochaine étape est de définir et de lancer notre interface *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!", + # Qui veut jouer au Pictionary ? Dessinez un objet courant comme une pelle ou un ordinateur portable, et l'algorithme le devinera en temps réel ! + article="

Sketch Recognition | Demo Model

", + live=True, +) +interface.launch(share=True) +``` + + + + +Remarquez le paramètre `live=True` dans `Interface`, qui signifie que la démo de sketchs fait une prédiction chaque fois que quelqu'un dessine sur le bloc (pas de bouton de soumission !). + +De plus, nous avons également défini l'argument `share=True` dans la méthode `launch()`. +Cela créera un lien public que vous pourrez envoyer à n'importe qui ! Lorsque vous envoyez ce lien, l'utilisateur de l'autre côté peut essayer le modèle de reconnaissance de croquis. Pour réitérer, vous pouvez également héberger le modèle sur *Hugging Face Spaces*, ce qui nous permet d'intégrer la démo ci-dessus. + La prochaine fois, nous couvrirons d'autres façons dont *Gradio* peut être utilisé avec l'écosystème d'*Hugging Face* ! \ No newline at end of file diff --git a/chapters/fr/chapter9/5.mdx b/chapters/fr/chapter9/5.mdx index e4a8c6f07..e11bf093b 100644 --- a/chapters/fr/chapter9/5.mdx +++ b/chapters/fr/chapter9/5.mdx @@ -1,75 +1,75 @@ -# Intégrations avec le Hub d'Hugging Face - - - -Pour vous rendre la vie encore plus facile, *Gradio* s'intègre directement avec *Hub* et *Spaces*. -Vous pouvez charger des démos depuis le *Hub* et les *Spaces* avec seulement *une ligne de code*. - -### Chargement de modèles depuis le Hub d'Hugging Face - -Pour commencer, choisissez un des milliers de modèles qu'*Hugging Face* offre à travers le *Hub*, comme décrit dans le [chapitre 4](/course/fr/chapter4/2). - -En utilisant la méthode spéciale `Interface.load()`, vous passez `"model/"` (ou, de manière équivalente, `"huggingface/"`) suivi du nom du modèle. -Par exemple, voici le code pour construire une démo pour le [GPT-J](https://huggingface.co/EleutherAI/gpt-j-6B), un grand modèle de langue, ajouter quelques exemples d'entrées : - -```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." -# Démo Gradio pour GPT-J 6B, un modèle de transformer entraîné avec le Mesh Transformer JAX de Ben Wang. GPT-J fait référence à la classe du modèle, tandis que '6B' représente le nombre de paramètres entraînables. Pour l'utiliser, il suffit d'ajouter votre texte, ou de cliquer sur l'un des exemples pour le charger. Pour en savoir plus, consultez les liens ci-dessous. -article = "

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

" -# GPT-J-6B : Un modèle linguistique autorégressif à 6 milliards de paramètres -examples = [ - ["The tower is 324 metres (1,063 ft) tall,"], - # La tour mesure 324 mètres (1 063 pieds) de haut, - ["The Moon's orbit around Earth has"], - # L'orbite de la Lune autour de la Terre a - ["The smooth Borealis basin in the Northern Hemisphere covers 40%"], - # Le bassin de Borealis dans l'hémisphère nord couvre 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() -``` - -Le code ci-dessus produira l'interface ci-dessous : - - - -Le chargement d'un modèle de cette manière utilise l'[API d'Inference](https://huggingface.co/inference-api) de *Hugging Face* au lieu de charger le modèle en mémoire. C'est idéal pour les modèles énormes comme GPT-J ou T0pp qui nécessitent beaucoup de RAM. - -### Chargement depuis Hugging Face Spaces - -Pour charger n'importe quel *Space* depuis le *Hub* et le recréer localement, vous pouvez passer `spaces/` à l'`Interface`, suivi du nom du *Space*. - -Vous vous souvenez de la démo de la section 1 qui supprime le fond d'une image ? Chargeons-la à partir de *Hugging Face Spaces* : - -```py -gr.Interface.load("spaces/abidlabs/remove-bg").launch() -``` - - - -L'un des avantages du chargement de démos à partir du *Hub* ou de *Spaces* est que vous pouvez les personnaliser en remplaçant n'importe lequel des paramètres. Ici, nous ajoutons un titre et faisons en sorte qu'elle fonctionne avec une webcam à la place : - -```py -gr.Interface.load( - "spaces/abidlabs/remove-bg", inputs="webcam", title="Remove your webcam background!" -).launch() -``` - - - +# Intégrations avec le Hub d'Hugging Face + + + +Pour vous rendre la vie encore plus facile, *Gradio* s'intègre directement avec *Hub* et *Spaces*. +Vous pouvez charger des démos depuis le *Hub* et les *Spaces* avec seulement *une ligne de code*. + +### Chargement de modèles depuis le Hub d'Hugging Face + +Pour commencer, choisissez un des milliers de modèles qu'*Hugging Face* offre à travers le *Hub*, comme décrit dans le [chapitre 4](/course/fr/chapter4/2). + +En utilisant la méthode spéciale `Interface.load()`, vous passez `"model/"` (ou, de manière équivalente, `"huggingface/"`) suivi du nom du modèle. +Par exemple, voici le code pour construire une démo pour le [GPT-J](https://huggingface.co/EleutherAI/gpt-j-6B), un grand modèle de langue, ajouter quelques exemples d'entrées : + +```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." +# Démo Gradio pour GPT-J 6B, un modèle de transformer entraîné avec le Mesh Transformer JAX de Ben Wang. GPT-J fait référence à la classe du modèle, tandis que '6B' représente le nombre de paramètres entraînables. Pour l'utiliser, il suffit d'ajouter votre texte, ou de cliquer sur l'un des exemples pour le charger. Pour en savoir plus, consultez les liens ci-dessous. +article = "

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

" +# GPT-J-6B : Un modèle linguistique autorégressif à 6 milliards de paramètres +examples = [ + ["The tower is 324 metres (1,063 ft) tall,"], + # La tour mesure 324 mètres (1 063 pieds) de haut, + ["The Moon's orbit around Earth has"], + # L'orbite de la Lune autour de la Terre a + ["The smooth Borealis basin in the Northern Hemisphere covers 40%"], + # Le bassin de Borealis dans l'hémisphère nord couvre 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() +``` + +Le code ci-dessus produira l'interface ci-dessous : + + + +Le chargement d'un modèle de cette manière utilise l'[API d'Inference](https://huggingface.co/inference-api) de *Hugging Face* au lieu de charger le modèle en mémoire. C'est idéal pour les modèles énormes comme GPT-J ou T0pp qui nécessitent beaucoup de RAM. + +### Chargement depuis Hugging Face Spaces + +Pour charger n'importe quel *Space* depuis le *Hub* et le recréer localement, vous pouvez passer `spaces/` à l'`Interface`, suivi du nom du *Space*. + +Vous vous souvenez de la démo de la section 1 qui supprime le fond d'une image ? Chargeons-la à partir de *Hugging Face Spaces* : + +```py +gr.Interface.load("spaces/abidlabs/remove-bg").launch() +``` + + + +L'un des avantages du chargement de démos à partir du *Hub* ou de *Spaces* est que vous pouvez les personnaliser en remplaçant n'importe lequel des paramètres. Ici, nous ajoutons un titre et faisons en sorte qu'elle fonctionne avec une webcam à la place : + +```py +gr.Interface.load( + "spaces/abidlabs/remove-bg", inputs="webcam", title="Remove your webcam background!" +).launch() +``` + + + Maintenant que nous avons exploré quelques façons d'intégrer *Gradio* avec le *Hub*, jetons un coup d'oeil à certaines fonctionnalités avancées de la classe `Interface`. C'est le sujet de la prochaine section ! \ No newline at end of file diff --git a/chapters/fr/chapter9/6.mdx b/chapters/fr/chapter9/6.mdx index c3ca388e8..273102f8c 100644 --- a/chapters/fr/chapter9/6.mdx +++ b/chapters/fr/chapter9/6.mdx @@ -1,138 +1,138 @@ -# Fonctionnalités avancées de l'interface - - - -Maintenant que nous pouvons construire et partager une interface de base, explorons quelques fonctionnalités plus avancées comme l'état, l'interprétation et l'authentification. - -### Utilisation de l'état pour faire persister les données - -*Gradio* supporte *l'état de session* où les données persistent à travers plusieurs soumissions dans un chargement de page. L'état de session est utile pour construire des démos où vous souhaitez faire persister les données au fur et à mesure que l'utilisateur interagit avec le modèle (par exemple des chatbots). Notez que l'état de session ne partage pas les données entre les différents utilisateurs de votre modèle. - -Pour stocker des données dans un état de session, vous devez faire trois choses : - -- Passez un *paramètre supplémentaire* dans votre fonction, qui représente l'état de l'interface. -- A la fin de la fonction, renvoyer la valeur mise à jour de l'état comme une *valeur de retour supplémentaire*. -- Ajoutez les composants "state" input et "state" output lors de la création de votre `Interface`. - -Voir l'exemple de chatbot ci-dessous : - -```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() -``` - - -Remarquez comment l'état du composant de sortie persiste entre les soumissions. -Remarque : vous pouvez transmettre une valeur par défaut au paramètre state, qui est utilisée comme valeur initiale de l'état. - -### Utilisation de l'interprétation pour comprendre les prédictions - -La plupart des modèles d'apprentissage automatique sont des boîtes noires et la logique interne de la fonction est cachée à l'utilisateur final. Pour encourager la transparence, nous avons fait en sorte qu'il soit très facile d'ajouter l'interprétation à votre modèle en définissant simplement le mot-clé interprétation dans la classe Interface par défaut. Cela permet à vos utilisateurs de comprendre quelles parties de l'entrée sont responsables de la sortie. Jetez un coup d'œil à l'interface simple ci-dessous qui montre un classificateur d'images incluant l'interprétation : - -```py -import requests -import tensorflow as tf - -import gradio as gr - -inception_net = tf.keras.applications.MobileNetV2() # charger le modèle - -# Télécharger des étiquettes lisibles par l'homme pour 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() -``` - -Testez la fonction d'interprétation en soumettant une entrée puis en cliquant sur « Interpréter » sous le composant de sortie. - - - -En plus de la méthode d'interprétation par défaut fournie par *Gradio*, vous pouvez également spécifier `shap` pour le paramètre `interpretation` et définir le paramètre `num_shap`. Ceci utilise l'interprétation basée sur Shapley, dont vous pouvez lire plus sur [ici](https://christophm.github.io/interpretable-ml-book/shap.html). -Enfin, vous pouvez aussi passer votre propre fonction d'interprétation dans le paramètre `interpretation`. Vous trouverez un exemple dans la page de démarrage de *Gradio* [ici](https://gradio.app/getting_started/). - - -### Ajouter l'authentification - -Vous pouvez vouloir ajouter une authentification à votre interface *Gradio* afin de contrôler qui peut accéder et utiliser votre démo. - -L'authentification peut être ajoutée en fournissant une liste de tuples de nom d'utilisateur/mot de passe au paramètre `auth` de la méthode `launch()`. Pour une gestion plus complexe de l'authentification, vous pouvez passer une fonction qui prend un nom d'utilisateur et un mot de passe comme arguments, et retourne `True` pour permettre l'authentification, `False` sinon. - -Prenons la démo de classification d'images ci-dessus et ajoutons l'authentification : - -```py -import requests -import tensorflow as tf - -import gradio as gr - -inception_net = tf.keras.applications.MobileNetV2() # charger le modèle - -# Télécharger des étiquettes lisibles par l'homme pour 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(auth=("admin", "pass1234")) -``` - - - +# Fonctionnalités avancées de l'interface + + + +Maintenant que nous pouvons construire et partager une interface de base, explorons quelques fonctionnalités plus avancées comme l'état, l'interprétation et l'authentification. + +### Utilisation de l'état pour faire persister les données + +*Gradio* supporte *l'état de session* où les données persistent à travers plusieurs soumissions dans un chargement de page. L'état de session est utile pour construire des démos où vous souhaitez faire persister les données au fur et à mesure que l'utilisateur interagit avec le modèle (par exemple des chatbots). Notez que l'état de session ne partage pas les données entre les différents utilisateurs de votre modèle. + +Pour stocker des données dans un état de session, vous devez faire trois choses : + +- Passez un *paramètre supplémentaire* dans votre fonction, qui représente l'état de l'interface. +- A la fin de la fonction, renvoyer la valeur mise à jour de l'état comme une *valeur de retour supplémentaire*. +- Ajoutez les composants "state" input et "state" output lors de la création de votre `Interface`. + +Voir l'exemple de chatbot ci-dessous : + +```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() +``` + + +Remarquez comment l'état du composant de sortie persiste entre les soumissions. +Remarque : vous pouvez transmettre une valeur par défaut au paramètre state, qui est utilisée comme valeur initiale de l'état. + +### Utilisation de l'interprétation pour comprendre les prédictions + +La plupart des modèles d'apprentissage automatique sont des boîtes noires et la logique interne de la fonction est cachée à l'utilisateur final. Pour encourager la transparence, nous avons fait en sorte qu'il soit très facile d'ajouter l'interprétation à votre modèle en définissant simplement le mot-clé interprétation dans la classe Interface par défaut. Cela permet à vos utilisateurs de comprendre quelles parties de l'entrée sont responsables de la sortie. Jetez un coup d'œil à l'interface simple ci-dessous qui montre un classificateur d'images incluant l'interprétation : + +```py +import requests +import tensorflow as tf + +import gradio as gr + +inception_net = tf.keras.applications.MobileNetV2() # charger le modèle + +# Télécharger des étiquettes lisibles par l'homme pour 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() +``` + +Testez la fonction d'interprétation en soumettant une entrée puis en cliquant sur « Interpréter » sous le composant de sortie. + + + +En plus de la méthode d'interprétation par défaut fournie par *Gradio*, vous pouvez également spécifier `shap` pour le paramètre `interpretation` et définir le paramètre `num_shap`. Ceci utilise l'interprétation basée sur Shapley, dont vous pouvez lire plus sur [ici](https://christophm.github.io/interpretable-ml-book/shap.html). +Enfin, vous pouvez aussi passer votre propre fonction d'interprétation dans le paramètre `interpretation`. Vous trouverez un exemple dans la page de démarrage de *Gradio* [ici](https://gradio.app/getting_started/). + + +### Ajouter l'authentification + +Vous pouvez vouloir ajouter une authentification à votre interface *Gradio* afin de contrôler qui peut accéder et utiliser votre démo. + +L'authentification peut être ajoutée en fournissant une liste de tuples de nom d'utilisateur/mot de passe au paramètre `auth` de la méthode `launch()`. Pour une gestion plus complexe de l'authentification, vous pouvez passer une fonction qui prend un nom d'utilisateur et un mot de passe comme arguments, et retourne `True` pour permettre l'authentification, `False` sinon. + +Prenons la démo de classification d'images ci-dessus et ajoutons l'authentification : + +```py +import requests +import tensorflow as tf + +import gradio as gr + +inception_net = tf.keras.applications.MobileNetV2() # charger le modèle + +# Télécharger des étiquettes lisibles par l'homme pour 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(auth=("admin", "pass1234")) +``` + + + Ceci conclut notre plongée dans la classe `Interface` de *Gradio*. Comme nous l'avons vu, cette classe permet de créer facilement des démos d'apprentissage automatique en quelques lignes de code Python. Cependant, vous voudrez parfois personnaliser votre démo en changeant la mise en page ou en enchaînant plusieurs fonctions de prédiction. Ne serait-il pas agréable de pouvoir diviser l'interface en blocs personnalisables ? Heureusement, c'est possible ! C'est le sujet de la dernière section. \ No newline at end of file diff --git a/chapters/fr/chapter9/7.mdx b/chapters/fr/chapter9/7.mdx index 8c66d2231..1f33cb1a7 100644 --- a/chapters/fr/chapter9/7.mdx +++ b/chapters/fr/chapter9/7.mdx @@ -1,240 +1,240 @@ -# Introduction à la classe Blocks - - - -Dans les sections précédentes, nous avons exploré et créé des démos en utilisant la classe `Interface`. Dans cette section, nous allons présenter une **nouvelle** API de bas niveau appelée `gradio.Blocks`. - -Quelle est la différence entre `Interface` et `Blocks` ? - -- ⚡ `Interface` : une API de haut niveau qui vous permet de créer une démo complète d'apprentissage automatique simplement en fournissant une liste d'entrées et de sorties. - -- 🧱 `Blocks` : une API de bas niveau qui vous permet d'avoir un contrôle total sur les flux de données et la disposition de votre application. Vous pouvez construire des applications très complexes, en plusieurs étapes, en utilisant `Blocks`. - - -### Pourquoi Blocks 🧱 ? - -Comme nous l'avons vu dans les sections précédentes, la classe `Interface` vous permet de créer facilement des démos d'apprentissage automatique à part entière avec seulement quelques lignes de code. L'API `Interface` est extrêmement facile à utiliser, mais elle n'a pas la flexibilité qu'offre l'API `Blocks`. Par exemple, vous pourriez vouloir : - -- regrouper des démos connexes sous forme d'onglets multiples dans une application web, -- modifier la mise en page de votre démo, par exemple pour spécifier l'emplacement des entrées et des sorties, -- disposer d'interfaces multi-étapes dans lesquelles la sortie d'un modèle devient l'entrée du modèle suivant ou avoir des flux de données plus flexibles en général, -- modifier les propriétés d'un composant (par exemple, les choix dans une liste déroulante) ou sa visibilité en fonction des entrées de l'utilisateur. - -Nous allons explorer tous ces concepts ci-dessous. - -### Création d'une démo simple en utilisant Blocks - -Après avoir installé *Gradio*, exécutez le code ci-dessous sous forme de script Python, de *notebook* Jupyter ou de *notebook* 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() -``` - - - -Ce simple exemple ci-dessus introduit 4 concepts qui sous-tendent les *Blocks* : - -1. Les *Blocks* vous permettent de construire des applications web qui combinent Markdown, HTML, boutons et composants interactifs simplement en instanciant des objets en Python dans un contexte `with gradio.Blocks`. - - -🙋Si vous n'êtes pas familier avec l'instruction `with` en Python, nous vous recommandons de consulter l'excellent tutoriel de Real Python. Revenez ici après l'avoir lu 🤗 - - -L'ordre dans lequel vous instanciez les composants est important car chaque élément est restitué dans l'application Web dans l'ordre où il a été créé. (Les mises en page plus complexes sont abordées ci-dessous) - -2. Vous pouvez définir des fonctions Python ordinaires n'importe où dans votre code et les exécuter avec des entrées utilisateur en utilisant les `Blocks`. Dans notre exemple, nous avons une fonction simple qui inverse le texte entré mais vous pouvez écrire n'importe quelle fonction Python, du simple calcul au traitement des prédictions d'un modèle d'apprentissage automatique. - -3. Vous pouvez assigner des événements à n'importe quel composant `Blocks`. Ainsi, votre fonction sera exécutée lorsque le composant sera cliqué, modifié, etc. Lorsque vous assignez un événement, vous passez trois paramètres : -- `fn` : la fonction qui doit être appelée, -- `inputs` : la (liste) des composants d'entrée -- `outputs` : la (liste) des composants de sortie qui doivent être appelés. - Dans l'exemple ci-dessus, nous exécutons la fonction `flip_text()` lorsque la valeur de la `Textbox` nommée input `input` change. L'événement lit la valeur dans `input`, la passe comme paramètre de nom à `flip_text()`, qui renvoie alors une valeur qui est assignée à notre seconde `Textbox` nommée `output`. - Pour voir la liste des événements que chaque composant supporte, consultez la [documentation](https://www.gradio.app/docs/) de *Gradio*. - -4. *Blocks* détermine automatiquement si un composant doit être interactif (accepter les entrées de l'utilisateur) ou non, en fonction des déclencheurs d'événements que vous définissez. Dans notre exemple, la première zone de texte est interactive, puisque sa valeur est utilisée par la fonction `flip_text()`. La deuxième zone de texte n'est pas interactive, puisque sa valeur n'est jamais utilisée comme entrée. Dans certains cas, vous voudrez peut-être passer outre, ce que vous pouvez faire en passant un booléen au paramètre `interactive` du composant (par exemple, `gr.Textbox(placeholder="Flip this text", interactive=True)`). - - -### Personnaliser la mise en page de votre démo - -Comment pouvons-nous utiliser `Blocks` pour personnaliser la mise en page de notre démo ? Par défaut, `Blocks` affiche verticalement dans une colonne les composants que vous créez. Vous pouvez changer cela en créant des colonnes supplémentaires `avec gradio.Column():` ou des lignes `avec gradio.Row():` et en créant des composants dans ces contextes. - -Voici ce que vous devez garder à l'esprit : tout composant créé sous une `Column` (c'est aussi le défaut) sera disposé verticalement. Tout composant créé sous une `Row` sera disposé horizontalement, comme le [modèle flexbox dans le développement web](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox). - -Enfin, vous pouvez également créer des onglets pour votre démo en utilisant le gestionnaire de contexte `with gradio.Tabs()`. Dans ce contexte, vous pouvez créer plusieurs onglets en spécifiant des enfants `with gradio.TabItem(name_of_tab):`. Tout composant créé dans un contexte `with gradio.TabItem(name_of_tab):` apparaît dans cet onglet. - -Maintenant, ajoutons une fonction `flip_image()` à notre démo et ajoutons un nouvel onglet qui retourne les images. Vous trouverez ci-dessous un exemple avec 2 onglets et utilisant également une `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() -``` - - - - -Vous remarquerez que dans cet exemple, nous avons également créé un composant `Button` dans chaque onglet et avons assigné un événement de clic à chaque bouton qui est l'élément qui exécute réellement la fonction. - -### Exploration des événements et de l'état - -De la même manière que vous pouvez contrôler la mise en page, `Blocks` vous donne un contrôle fin sur les événements qui déclenchent les appels de fonction. Chaque composant et de nombreux layouts ont des événements spécifiques qu'ils supportent. - -Par exemple, le composant `Textbox` a 2 événements : `change()` (lorsque la valeur contenue dans la zone de texte change), et `submit()` (lorsqu'un utilisateur appuie sur la touche Entrée alors qu'il est concentré sur la zone de texte). Les composants plus complexes peuvent avoir encore plus d'événements : par exemple, le composant `Audio` a aussi des événements séparés pour quand le fichier audio est joué, effacé, mis en pause, etc. Consultez la documentation pour connaître les événements pris en charge par chaque composant. - -Vous pouvez attacher un déclencheur d'événement à aucun, un ou plusieurs de ces événements. Vous créez un déclencheur d'événement en appelant le nom de l'événement sur l'instance du composant comme une fonction. Par exemple, `textbox.change(...)` ou `btn.click(...)`. La fonction prend trois paramètres, comme indiqué ci-dessus : - -- `fn` : la fonction à exécuter -- `inputs` : une (liste de) composante(s) dont les valeurs doivent être fournies comme paramètres d'entrée à la fonction. La valeur de chaque composant est mise en correspondance avec le paramètre de fonction correspondant, dans l'ordre. Ce paramètre peut être `None` si la fonction ne prend aucun paramètre. -- `outputs` : un (liste de) composant(s) dont les valeurs doivent être mises à jour en fonction des valeurs retournées par la fonction. Chaque valeur de retour met à jour la valeur du composant correspondant, dans l'ordre. Ce paramètre peut être None si la fonction ne retourne rien. - -Vous pouvez même faire en sorte que le composant d'entrée et de sortie soit le même composant, comme nous le faisons dans cet exemple qui utilise un modèle GPT pour compléter du texte : - -```py -import gradio as gr - -api = gr.Interface.load("huggingface/EleutherAI/gpt-j-6B") - - -def complete_with_gpt(text): - # Utilise les 50 derniers caractères du texte comme contexte. - 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() -``` - - - -### Création de démos multi-étapes - -Dans certains cas, vous pouvez vouloir une _démo multi-étapes_, dans laquelle vous réutilisez la sortie d'une fonction comme entrée de la suivante. C'est très facile à faire avec les `Blocks`, car vous pouvez utiliser un composant pour l'entrée d'un déclencheur d'événement mais la sortie d'un autre. Regardez le composant texte dans l'exemple ci-dessous, sa valeur est le résultat d'un modèle de conversion de la parole en texte, mais il est également transmis à un modèle d'analyse des sentiments : - -```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() -``` - - - -### Mise à jour des propriétés des composants - -Jusqu'à présent, nous avons vu comment créer des événements pour mettre à jour la valeur d'un autre composant. Mais que se passe-t-il si vous voulez modifier d'autres propriétés d'un composant, comme la visibilité d'une zone de texte ou les choix dans un groupe de boutons radio ? Vous pouvez le faire en renvoyant la méthode `update()` d'une classe de composant au lieu d'une valeur de retour normale de votre fonction. - -L'exemple le plus facile à illustrer est le suivant : - -```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() -``` - - - +# Introduction à la classe Blocks + + + +Dans les sections précédentes, nous avons exploré et créé des démos en utilisant la classe `Interface`. Dans cette section, nous allons présenter une **nouvelle** API de bas niveau appelée `gradio.Blocks`. + +Quelle est la différence entre `Interface` et `Blocks` ? + +- ⚡ `Interface` : une API de haut niveau qui vous permet de créer une démo complète d'apprentissage automatique simplement en fournissant une liste d'entrées et de sorties. + +- 🧱 `Blocks` : une API de bas niveau qui vous permet d'avoir un contrôle total sur les flux de données et la disposition de votre application. Vous pouvez construire des applications très complexes, en plusieurs étapes, en utilisant `Blocks`. + + +### Pourquoi Blocks 🧱 ? + +Comme nous l'avons vu dans les sections précédentes, la classe `Interface` vous permet de créer facilement des démos d'apprentissage automatique à part entière avec seulement quelques lignes de code. L'API `Interface` est extrêmement facile à utiliser, mais elle n'a pas la flexibilité qu'offre l'API `Blocks`. Par exemple, vous pourriez vouloir : + +- regrouper des démos connexes sous forme d'onglets multiples dans une application web, +- modifier la mise en page de votre démo, par exemple pour spécifier l'emplacement des entrées et des sorties, +- disposer d'interfaces multi-étapes dans lesquelles la sortie d'un modèle devient l'entrée du modèle suivant ou avoir des flux de données plus flexibles en général, +- modifier les propriétés d'un composant (par exemple, les choix dans une liste déroulante) ou sa visibilité en fonction des entrées de l'utilisateur. + +Nous allons explorer tous ces concepts ci-dessous. + +### Création d'une démo simple en utilisant Blocks + +Après avoir installé *Gradio*, exécutez le code ci-dessous sous forme de script Python, de *notebook* Jupyter ou de *notebook* 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() +``` + + + +Ce simple exemple ci-dessus introduit 4 concepts qui sous-tendent les *Blocks* : + +1. Les *Blocks* vous permettent de construire des applications web qui combinent Markdown, HTML, boutons et composants interactifs simplement en instanciant des objets en Python dans un contexte `with gradio.Blocks`. + + +🙋Si vous n'êtes pas familier avec l'instruction `with` en Python, nous vous recommandons de consulter l'excellent tutoriel de Real Python. Revenez ici après l'avoir lu 🤗 + + +L'ordre dans lequel vous instanciez les composants est important car chaque élément est restitué dans l'application Web dans l'ordre où il a été créé. (Les mises en page plus complexes sont abordées ci-dessous) + +2. Vous pouvez définir des fonctions Python ordinaires n'importe où dans votre code et les exécuter avec des entrées utilisateur en utilisant les `Blocks`. Dans notre exemple, nous avons une fonction simple qui inverse le texte entré mais vous pouvez écrire n'importe quelle fonction Python, du simple calcul au traitement des prédictions d'un modèle d'apprentissage automatique. + +3. Vous pouvez assigner des événements à n'importe quel composant `Blocks`. Ainsi, votre fonction sera exécutée lorsque le composant sera cliqué, modifié, etc. Lorsque vous assignez un événement, vous passez trois paramètres : +- `fn` : la fonction qui doit être appelée, +- `inputs` : la (liste) des composants d'entrée +- `outputs` : la (liste) des composants de sortie qui doivent être appelés. + Dans l'exemple ci-dessus, nous exécutons la fonction `flip_text()` lorsque la valeur de la `Textbox` nommée input `input` change. L'événement lit la valeur dans `input`, la passe comme paramètre de nom à `flip_text()`, qui renvoie alors une valeur qui est assignée à notre seconde `Textbox` nommée `output`. + Pour voir la liste des événements que chaque composant supporte, consultez la [documentation](https://www.gradio.app/docs/) de *Gradio*. + +4. *Blocks* détermine automatiquement si un composant doit être interactif (accepter les entrées de l'utilisateur) ou non, en fonction des déclencheurs d'événements que vous définissez. Dans notre exemple, la première zone de texte est interactive, puisque sa valeur est utilisée par la fonction `flip_text()`. La deuxième zone de texte n'est pas interactive, puisque sa valeur n'est jamais utilisée comme entrée. Dans certains cas, vous voudrez peut-être passer outre, ce que vous pouvez faire en passant un booléen au paramètre `interactive` du composant (par exemple, `gr.Textbox(placeholder="Flip this text", interactive=True)`). + + +### Personnaliser la mise en page de votre démo + +Comment pouvons-nous utiliser `Blocks` pour personnaliser la mise en page de notre démo ? Par défaut, `Blocks` affiche verticalement dans une colonne les composants que vous créez. Vous pouvez changer cela en créant des colonnes supplémentaires `avec gradio.Column():` ou des lignes `avec gradio.Row():` et en créant des composants dans ces contextes. + +Voici ce que vous devez garder à l'esprit : tout composant créé sous une `Column` (c'est aussi le défaut) sera disposé verticalement. Tout composant créé sous une `Row` sera disposé horizontalement, comme le [modèle flexbox dans le développement web](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox). + +Enfin, vous pouvez également créer des onglets pour votre démo en utilisant le gestionnaire de contexte `with gradio.Tabs()`. Dans ce contexte, vous pouvez créer plusieurs onglets en spécifiant des enfants `with gradio.TabItem(name_of_tab):`. Tout composant créé dans un contexte `with gradio.TabItem(name_of_tab):` apparaît dans cet onglet. + +Maintenant, ajoutons une fonction `flip_image()` à notre démo et ajoutons un nouvel onglet qui retourne les images. Vous trouverez ci-dessous un exemple avec 2 onglets et utilisant également une `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() +``` + + + + +Vous remarquerez que dans cet exemple, nous avons également créé un composant `Button` dans chaque onglet et avons assigné un événement de clic à chaque bouton qui est l'élément qui exécute réellement la fonction. + +### Exploration des événements et de l'état + +De la même manière que vous pouvez contrôler la mise en page, `Blocks` vous donne un contrôle fin sur les événements qui déclenchent les appels de fonction. Chaque composant et de nombreux layouts ont des événements spécifiques qu'ils supportent. + +Par exemple, le composant `Textbox` a 2 événements : `change()` (lorsque la valeur contenue dans la zone de texte change), et `submit()` (lorsqu'un utilisateur appuie sur la touche Entrée alors qu'il est concentré sur la zone de texte). Les composants plus complexes peuvent avoir encore plus d'événements : par exemple, le composant `Audio` a aussi des événements séparés pour quand le fichier audio est joué, effacé, mis en pause, etc. Consultez la documentation pour connaître les événements pris en charge par chaque composant. + +Vous pouvez attacher un déclencheur d'événement à aucun, un ou plusieurs de ces événements. Vous créez un déclencheur d'événement en appelant le nom de l'événement sur l'instance du composant comme une fonction. Par exemple, `textbox.change(...)` ou `btn.click(...)`. La fonction prend trois paramètres, comme indiqué ci-dessus : + +- `fn` : la fonction à exécuter +- `inputs` : une (liste de) composante(s) dont les valeurs doivent être fournies comme paramètres d'entrée à la fonction. La valeur de chaque composant est mise en correspondance avec le paramètre de fonction correspondant, dans l'ordre. Ce paramètre peut être `None` si la fonction ne prend aucun paramètre. +- `outputs` : un (liste de) composant(s) dont les valeurs doivent être mises à jour en fonction des valeurs retournées par la fonction. Chaque valeur de retour met à jour la valeur du composant correspondant, dans l'ordre. Ce paramètre peut être None si la fonction ne retourne rien. + +Vous pouvez même faire en sorte que le composant d'entrée et de sortie soit le même composant, comme nous le faisons dans cet exemple qui utilise un modèle GPT pour compléter du texte : + +```py +import gradio as gr + +api = gr.Interface.load("huggingface/EleutherAI/gpt-j-6B") + + +def complete_with_gpt(text): + # Utilise les 50 derniers caractères du texte comme contexte. + 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() +``` + + + +### Création de démos multi-étapes + +Dans certains cas, vous pouvez vouloir une _démo multi-étapes_, dans laquelle vous réutilisez la sortie d'une fonction comme entrée de la suivante. C'est très facile à faire avec les `Blocks`, car vous pouvez utiliser un composant pour l'entrée d'un déclencheur d'événement mais la sortie d'un autre. Regardez le composant texte dans l'exemple ci-dessous, sa valeur est le résultat d'un modèle de conversion de la parole en texte, mais il est également transmis à un modèle d'analyse des sentiments : + +```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() +``` + + + +### Mise à jour des propriétés des composants + +Jusqu'à présent, nous avons vu comment créer des événements pour mettre à jour la valeur d'un autre composant. Mais que se passe-t-il si vous voulez modifier d'autres propriétés d'un composant, comme la visibilité d'une zone de texte ou les choix dans un groupe de boutons radio ? Vous pouvez le faire en renvoyant la méthode `update()` d'une classe de composant au lieu d'une valeur de retour normale de votre fonction. + +L'exemple le plus facile à illustrer est le suivant : + +```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() +``` + + + Nous venons d'explorer tous les concepts de base des `Blocks` ! Tout comme avec `Interface`, vous pouvez créer des démos sympas qui peuvent être partagées en utilisant `share=True` dans la méthode `launch()` ou déployées sur [*Spaces*](https://huggingface.co/spaces). \ No newline at end of file diff --git a/chapters/fr/chapter9/8.mdx b/chapters/fr/chapter9/8.mdx index 418e389e6..307e0c813 100644 --- a/chapters/fr/chapter9/8.mdx +++ b/chapters/fr/chapter9/8.mdx @@ -1,5 +1,10 @@ # Gradio, coché ! + + Ceci conclut le chapitre sur la construction de démos d'apprentissage automatique avec *Gradio*. Nous espérons que vous l'avez apprécié ! Pour récapituler, dans ce chapitre nous avons appris : - à créer des démos *Gradio* avec l'API `Interface` de haut niveau et comment configurer les différentes modalités d'entrée et de sortie, diff --git a/chapters/fr/chapter9/9.mdx b/chapters/fr/chapter9/9.mdx index 2b6e859a2..0f4169307 100644 --- a/chapters/fr/chapter9/9.mdx +++ b/chapters/fr/chapter9/9.mdx @@ -1,233 +1,238 @@ - - -# Quiz de fin de chapitre - -Testons ce que vous avez appris dans ce chapitre ! - -### 1. Que pouvez-vous faire avec Gradio ? - -share=True dans la méthode de lancement, vous pouvez générer un lien de partage à envoyer à tout le monde.", - correct: true - }, - { - text: "Déboguez votre modèle.", - explain: "L'un des avantages d'une démo Gradio est de pouvoir tester votre modèle avec des données réelles que vous pouvez modifier et observer les prédictions du modèle changer en temps réel, ce qui vous aide à déboguer votre modèle.", - correct: true - }, - { - text: "Entraîner votre modèle.", - explain: "Gradio est conçu pour être utilisé pour l'inférence, APRÈS que votre modèle a été entraîné.", - } - ]} -/> - -### 2. Gradio fonctionne UNIQUEMENT avec les modèles en PyTorch - -Gradio fonctionne avec les modèles Pytorch mais aussi pour tout type de modèle d'apprentissage automatique !" - }, - { - text: "Faux", - explain: "Gradio est indifférent au modèle ce qui signifie que vous pouvez créer une démo pour tout type de modèle d'apprentissage automatique.", - correct: true - } - ]} -/> - -### 3. D'où pouvez-vous lancer une démo Gradio ? - -Gradio fonctionne parfaitement avec votre IDE préféré.", - correct: true - }, - { - text: "De notebooks Google Colab", - explain: "Vous pouvez créer et lancer une démo dans votre notebook Google Colab.", - correct: true - }, - { - text: "De notebooks Jupyter", - explain: "Vous pouvez créer et lancer une démo dans votre notebook Jupyter.", - correct: true - } - ]} -/> - -### 4. Gradio est conçu principalement pour les modèles de NLP - -Gradio fonctionne avec pratiquement tous les types de données, pas seulement avec le NLP." - }, - { - text: "Faux", - explain: "Gradio fournit aux développeurs une bibliothèque de composants préconstruits pour pratiquement tous les types de données.", - correct: true - } - ]} -/> - -### 5. Parmi les fonctionnalités suivantes, lesquelles sont prises en charge par Gradio ? - -Gradio. Tout ce que vous devez faire est de passer une liste d'entrées et de sorties à leurs paramètres correspondants.", - correct: true - }, - { - text: "État pour la persistance des données.", - explain: "Gradio est capable d'ajouter un état à votre interface.", - correct: true - }, - { - text: "Authentification par nom d'utilisateur et mot de passe.", - explain: "Passez une liste de tuples de nom d'utilisateur/mot de passe à la méthode de lancement pour ajouter l'authentification.", - correct: true - }, - { - text: "Analyse automatique de l'utilisation de votre démo Gradio.", - explain: "Gradio ne fournit pas aux développeurs des analyses sur les personnes qui utilisent leurs démos." - }, - { - text: "Chargement d'un modèle à partir du Hub ou de Space.", - explain: "Charger n'importe quel modèle de Hugging Face en utilisant la méthode gr.Interface.load().", - correct: true - } - ]} -/> - -### 6. Lesquelles des méthodes suivantes sont valides pour charger un modèle à partir du Hub ou de Space ? - -gr.Interface.load('huggingface/{user}/{model_name}')", - explain: "Il s'agit d'une méthode valide de chargement d'un modèle à partir du Hub.", - correct: true - }, - { - text: "gr.Interface.load('model/{user}/{model_name}')", - explain: "Il s'agit d'une méthode valide de chargement d'un modèle à partir du Hub.", - correct: true - }, - { - text: "gr.Interface.load('demos/{user}/{model_name}')", - explain: "Vous ne pouvez pas charger un modèle en utilisant le préfixe demos." - }, - { - text: "gr.Interface.load('spaces/{user}/{model_name}')", - explain: "Il s'agit d'une méthode valide de chargement d'un modèle à partir de Space.", - correct: true - } - ]} -/> - -### 7. Sélectionnez toutes les étapes nécessaires pour ajouter un état à votre interface Gradio - -Gradio fournit un composant d'entrée et de sortie d'état pour persister les données.", - correct: true - } - ]} -/> - -### 8. Lesquels des éléments suivants sont des composants inclus dans la bibliothèque Gradio ? - -Textbox.", - explain: "Oui, vous pouvez créer des zones de texte avec le composant Textbox.", - correct: true - }, - { - text: "Graph.", - explain: "Il n'y a actuellement aucun composant Graph.", - }, - { - text: "Image.", - explain: "Oui, vous pouvez créer un widget de téléchargement d'images avec le composant Image.", - correct: true - }, - { - text: "Audio.", - explain: "Oui, vous pouvez créer un widget de téléchargement audio avec le composant Audio.", - correct: true - }, - ]} -/> - -### 9. Qu'est-ce que les `Blocks` vous permet de faire ? - -with gradio.Tabs(): pour ajouter des onglets pour plusieurs démos.", - correct: true - }, - { - text: "Attribuer des déclencheurs d'événements tels que clicked/changed/etc aux composants Blocks.", - explain: "Lorsque vous assignez un événement, vous passez trois paramètres : fn qui est la fonction qui doit être appelée, inputs qui est la (liste) des composants d'entrée, et outputs qui est la (liste) des composants de sortie qui doivent être appelés.", - correct: true - }, - { - text: "Déterminer automatiquement quel composant Blocks doit être interactif ou statique.", - explain: "En fonction des déclencheurs d'événements que vous définissez, Blocks détermine automatiquement si un composant doit accepter ou non les entrées de l'utilisateur..", - correct: true - }, - { - text: "Créer des démos en plusieurs étapes, c'est-à-dire vous permettre de réutiliser la sortie d'un composant comme entrée pour le suivant.", - explain: "Vous pouvez utiliser un composant pour l'entrée d'un déclencheur d'événement mais la sortie d'un autre.", - correct: true - }, - ]} -/> - -### 10. Vous pouvez partager un lien public vers une démo Blocks et accueillir une démo Blocks sur Space - -Interface, toutes les capacités de partage et d'hébergement sont les mêmes pour les démos basées sur Blocks !", - correct: true - }, - { - text: "Faux", - explain: "Tout comme Interface, toutes les capacités de partage et d'hébergement sont les mêmes pour les démos basées sur Blocks !", - } - ]} + + +# Quiz de fin de chapitre + + + +Testons ce que vous avez appris dans ce chapitre ! + +### 1. Que pouvez-vous faire avec Gradio ? + +share=True dans la méthode de lancement, vous pouvez générer un lien de partage à envoyer à tout le monde.", + correct: true + }, + { + text: "Déboguez votre modèle.", + explain: "L'un des avantages d'une démo Gradio est de pouvoir tester votre modèle avec des données réelles que vous pouvez modifier et observer les prédictions du modèle changer en temps réel, ce qui vous aide à déboguer votre modèle.", + correct: true + }, + { + text: "Entraîner votre modèle.", + explain: "Gradio est conçu pour être utilisé pour l'inférence, APRÈS que votre modèle a été entraîné.", + } + ]} +/> + +### 2. Gradio fonctionne UNIQUEMENT avec les modèles en PyTorch + +Gradio fonctionne avec les modèles Pytorch mais aussi pour tout type de modèle d'apprentissage automatique !" + }, + { + text: "Faux", + explain: "Gradio est indifférent au modèle ce qui signifie que vous pouvez créer une démo pour tout type de modèle d'apprentissage automatique.", + correct: true + } + ]} +/> + +### 3. D'où pouvez-vous lancer une démo Gradio ? + +Gradio fonctionne parfaitement avec votre IDE préféré.", + correct: true + }, + { + text: "De notebooks Google Colab", + explain: "Vous pouvez créer et lancer une démo dans votre notebook Google Colab.", + correct: true + }, + { + text: "De notebooks Jupyter", + explain: "Vous pouvez créer et lancer une démo dans votre notebook Jupyter.", + correct: true + } + ]} +/> + +### 4. Gradio est conçu principalement pour les modèles de NLP + +Gradio fonctionne avec pratiquement tous les types de données, pas seulement avec le NLP." + }, + { + text: "Faux", + explain: "Gradio fournit aux développeurs une bibliothèque de composants préconstruits pour pratiquement tous les types de données.", + correct: true + } + ]} +/> + +### 5. Parmi les fonctionnalités suivantes, lesquelles sont prises en charge par Gradio ? + +Gradio. Tout ce que vous devez faire est de passer une liste d'entrées et de sorties à leurs paramètres correspondants.", + correct: true + }, + { + text: "État pour la persistance des données.", + explain: "Gradio est capable d'ajouter un état à votre interface.", + correct: true + }, + { + text: "Authentification par nom d'utilisateur et mot de passe.", + explain: "Passez une liste de tuples de nom d'utilisateur/mot de passe à la méthode de lancement pour ajouter l'authentification.", + correct: true + }, + { + text: "Analyse automatique de l'utilisation de votre démo Gradio.", + explain: "Gradio ne fournit pas aux développeurs des analyses sur les personnes qui utilisent leurs démos." + }, + { + text: "Chargement d'un modèle à partir du Hub ou de Space.", + explain: "Charger n'importe quel modèle de Hugging Face en utilisant la méthode gr.Interface.load().", + correct: true + } + ]} +/> + +### 6. Lesquelles des méthodes suivantes sont valides pour charger un modèle à partir du Hub ou de Space ? + +gr.Interface.load('huggingface/{user}/{model_name}')", + explain: "Il s'agit d'une méthode valide de chargement d'un modèle à partir du Hub.", + correct: true + }, + { + text: "gr.Interface.load('model/{user}/{model_name}')", + explain: "Il s'agit d'une méthode valide de chargement d'un modèle à partir du Hub.", + correct: true + }, + { + text: "gr.Interface.load('demos/{user}/{model_name}')", + explain: "Vous ne pouvez pas charger un modèle en utilisant le préfixe demos." + }, + { + text: "gr.Interface.load('spaces/{user}/{model_name}')", + explain: "Il s'agit d'une méthode valide de chargement d'un modèle à partir de Space.", + correct: true + } + ]} +/> + +### 7. Sélectionnez toutes les étapes nécessaires pour ajouter un état à votre interface Gradio + +Gradio fournit un composant d'entrée et de sortie d'état pour persister les données.", + correct: true + } + ]} +/> + +### 8. Lesquels des éléments suivants sont des composants inclus dans la bibliothèque Gradio ? + +Textbox.", + explain: "Oui, vous pouvez créer des zones de texte avec le composant Textbox.", + correct: true + }, + { + text: "Graph.", + explain: "Il n'y a actuellement aucun composant Graph.", + }, + { + text: "Image.", + explain: "Oui, vous pouvez créer un widget de téléchargement d'images avec le composant Image.", + correct: true + }, + { + text: "Audio.", + explain: "Oui, vous pouvez créer un widget de téléchargement audio avec le composant Audio.", + correct: true + }, + ]} +/> + +### 9. Qu'est-ce que les `Blocks` vous permet de faire ? + +with gradio.Tabs(): pour ajouter des onglets pour plusieurs démos.", + correct: true + }, + { + text: "Attribuer des déclencheurs d'événements tels que clicked/changed/etc aux composants Blocks.", + explain: "Lorsque vous assignez un événement, vous passez trois paramètres : fn qui est la fonction qui doit être appelée, inputs qui est la (liste) des composants d'entrée, et outputs qui est la (liste) des composants de sortie qui doivent être appelés.", + correct: true + }, + { + text: "Déterminer automatiquement quel composant Blocks doit être interactif ou statique.", + explain: "En fonction des déclencheurs d'événements que vous définissez, Blocks détermine automatiquement si un composant doit accepter ou non les entrées de l'utilisateur..", + correct: true + }, + { + text: "Créer des démos en plusieurs étapes, c'est-à-dire vous permettre de réutiliser la sortie d'un composant comme entrée pour le suivant.", + explain: "Vous pouvez utiliser un composant pour l'entrée d'un déclencheur d'événement mais la sortie d'un autre.", + correct: true + }, + ]} +/> + +### 10. Vous pouvez partager un lien public vers une démo Blocks et accueillir une démo Blocks sur Space + +Interface, toutes les capacités de partage et d'hébergement sont les mêmes pour les démos basées sur Blocks !", + correct: true + }, + { + text: "Faux", + explain: "Tout comme Interface, toutes les capacités de partage et d'hébergement sont les mêmes pour les démos basées sur Blocks !", + } + ]} /> \ No newline at end of file diff --git a/chapters/hi/chapter1/1.mdx b/chapters/hi/chapter1/1.mdx index 59b8aa076..8a4654ed9 100644 --- a/chapters/hi/chapter1/1.mdx +++ b/chapters/hi/chapter1/1.mdx @@ -1,5 +1,10 @@ # परिचय + + ## 🤗 पाठ्यक्रम में आपका स्वागत है! diff --git a/chapters/hi/chapter1/10.mdx b/chapters/hi/chapter1/10.mdx index eeb67fdab..6ef1bbed8 100644 --- a/chapters/hi/chapter1/10.mdx +++ b/chapters/hi/chapter1/10.mdx @@ -1,5 +1,10 @@ # अध्याय के अंत की प्रश्नोत्तरी + + इस अध्याय में बहुत सारी जमीन शामिल है! यदि आप सभी विवरणों को नहीं समझ पाए हैं तो चिंता न करें; अगले अध्याय आपको यह समझने में मदद करेंगे कि चीजें हुड के तहत कैसे काम करती हैं। लेकिन, आइए पहले यह जाँचें कि आपने इस अध्याय में क्या सीखा! diff --git a/chapters/hi/chapter1/2.mdx b/chapters/hi/chapter1/2.mdx index dd707c568..e3a7c498f 100644 --- a/chapters/hi/chapter1/2.mdx +++ b/chapters/hi/chapter1/2.mdx @@ -1,5 +1,10 @@ # प्राकृतिक भाषा प्रसंस्करण + + ट्रांसफॉर्मर मॉडल में जाने से पहले, आइए एक त्वरित अवलोकन करें कि प्राकृतिक भाषा प्रसंस्करण क्या है और हम इसकी परवाह क्यों करते हैं। ## प्राकृतिक भाषा प्रसंस्करण क्या है? diff --git a/chapters/hi/chapter1/3.mdx b/chapters/hi/chapter1/3.mdx index d40137645..13b9eb19d 100644 --- a/chapters/hi/chapter1/3.mdx +++ b/chapters/hi/chapter1/3.mdx @@ -1,8 +1,8 @@ # ट्रांसफार्मर, वे क्या कर सकते हैं? - diff --git a/chapters/hi/chapter1/4.mdx b/chapters/hi/chapter1/4.mdx index 63dd3e619..4f25dba15 100644 --- a/chapters/hi/chapter1/4.mdx +++ b/chapters/hi/chapter1/4.mdx @@ -1,5 +1,10 @@ # ट्रांसफॉर्मर कैसे काम करते हैं? + + इस खंड में, हम ट्रांसफॉर्मर मॉडल की वास्तुकला पर एक उच्च-स्तरीय नज़र डालेंगे। ## ट्रांसफार्मर का थोड़ा सा इतिहास diff --git a/chapters/hi/chapter1/5.mdx b/chapters/hi/chapter1/5.mdx index 0e1957c48..687b61913 100644 --- a/chapters/hi/chapter1/5.mdx +++ b/chapters/hi/chapter1/5.mdx @@ -1,5 +1,10 @@ # एनकोडर मॉडल + + एन्कोडर मॉडल केवल ट्रांसफ़ॉर्मर मॉडल के एन्कोडर का उपयोग करते हैं। प्रत्येक चरण में, ध्यान की परतें प्रारंभिक वाक्य में सभी शब्दों तक पहुंच सकती हैं। इन मॉडलों को अक्सर "द्वि-दिशात्मक" ध्यान देने के रूप में वर्णित किया जाता है, और इन्हें अक्सर *ऑटो-एन्कोडिंग मॉडल* कहा जाता है। diff --git a/chapters/hi/chapter1/6.mdx b/chapters/hi/chapter1/6.mdx index 9b0f245cd..cf0ae79ee 100644 --- a/chapters/hi/chapter1/6.mdx +++ b/chapters/hi/chapter1/6.mdx @@ -1,5 +1,10 @@ # डिकोडर मॉडल + + डिकोडर मॉडल केवल ट्रांसफॉर्मर मॉडल के डिकोडर का उपयोग करते हैं। प्रत्येक चरण में, किसी दिए गए शब्द के लिए ध्यान की परतें केवल वाक्य में उसके सामने स्थित शब्दों तक पहुंच सकती हैं। इन मॉडलों को अक्सर *स्वतः प्रतिगामी मॉडल* कहा जाता है। diff --git a/chapters/hi/chapter1/7.mdx b/chapters/hi/chapter1/7.mdx index d4fc23ae3..66b1991bf 100644 --- a/chapters/hi/chapter1/7.mdx +++ b/chapters/hi/chapter1/7.mdx @@ -1,5 +1,10 @@ # अनुक्रम-से-अनुक्रम मॉडल + + एनकोडर-डिकोडर मॉडल (जिसे *सीक्वेंस-टू-सीक्वेंस मॉडल* भी कहा जाता है) ट्रांसफॉर्मर आर्किटेक्चर के दोनों हिस्सों का उपयोग करते हैं। प्रत्येक चरण में, एन्कोडर की ध्यान परतें प्रारंभिक वाक्य में सभी शब्दों तक पहुंच सकती हैं, जबकि डिकोडर की ध्यान परतें केवल इनपुट में दिए गए शब्द से पहले स्थित शब्दों तक पहुंच सकती हैं। diff --git a/chapters/hi/chapter1/8.mdx b/chapters/hi/chapter1/8.mdx index 52e778528..1612cac3b 100644 --- a/chapters/hi/chapter1/8.mdx +++ b/chapters/hi/chapter1/8.mdx @@ -1,8 +1,8 @@ # पूर्वाग्रह और सीमाएं - diff --git a/chapters/hi/chapter1/9.mdx b/chapters/hi/chapter1/9.mdx index 56649cb6b..edad957d5 100644 --- a/chapters/hi/chapter1/9.mdx +++ b/chapters/hi/chapter1/9.mdx @@ -1,5 +1,10 @@ # सारांश + + इस अध्याय में, आपने देखा कि 🤗 ट्रांसफॉर्मर के उच्च-स्तरीय `पाइपलाइन ()` फ़ंक्शन का उपयोग करके विभिन्न प्राकृतिक भाषा प्रसंस्करण कार्यों को कैसे किया जाता है। आपने यह भी देखा कि हब में मॉडलों की खोज और उनका उपयोग कैसे करें, साथ ही सीधे अपने ब्राउज़र में मॉडलों का परीक्षण करने के लिए अनुमान API का उपयोग कैसे करें। हमने चर्चा की कि ट्रांसफॉर्मर मॉडल उच्च स्तर पर कैसे काम करते हैं और ट्रांसफर लर्निंग और फाइन-ट्यूनिंग के महत्व के बारे में बात की। एक महत्वपूर्ण पहलू यह है कि आप पूर्ण आर्किटेक्चर या केवल एन्कोडर या डिकोडर का उपयोग कर सकते हैं, यह इस बात पर निर्भर करता है कि आप किस प्रकार के कार्य को हल करना चाहते हैं। निम्न तालिका इसे सारांशित करती है: diff --git a/chapters/hi/chapter2/1.mdx b/chapters/hi/chapter2/1.mdx index a8c1e8265..df1e0e139 100644 --- a/chapters/hi/chapter2/1.mdx +++ b/chapters/hi/chapter2/1.mdx @@ -1,5 +1,10 @@ # परिचय + + जैसा कि आपने [अध्याय 1](/course/chapter1) में देखा, ट्रांसफार्मर मॉडल आमतौर पर बहुत बड़े होते हैं। लाखों से दसियों अरबों पैरामीटर्स के साथ, इन मॉडलों का प्रशिक्षण और डिप्लॉय करना एक पेचीदा उपक्रम है। इसके अलावा, नए मॉडल लगभग दैनिक आधार पर जारी किए जा रहे हैं और प्रत्येक का अपना कार्यान्वयन है, उन सभी को आज़माना कोई आसान काम नहीं है। इस समस्या को हल करने के लिए 🤗 ट्रांसफॉर्मर्स लाइब्रेरी बनाई गई थी। इसका लक्ष्य एक एपीआई प्रदान करना है जिसके माध्यम से किसी भी ट्रांसफार्मर मॉडल को लोड, प्रशिक्षित और सेव किया जा सकता है। पुस्तकालय की मुख्य विशेषताएं हैं: diff --git a/chapters/hi/chapter3/1.mdx b/chapters/hi/chapter3/1.mdx index 16e3b4710..d479163f4 100644 --- a/chapters/hi/chapter3/1.mdx +++ b/chapters/hi/chapter3/1.mdx @@ -2,6 +2,11 @@ # परिचय + + [अध्याय 2](/course/chapter2) में हमने जाना कि कैसे भविष्यवाणी करने के लिए टोकननाइज़र और पूर्व-प्रशिक्षित मॉडल का उपयोग किया जाता है । लेकिन तब क्या यदि आप अपने स्वयं के डेटासेट के लिए एक पूर्व-प्रशिक्षित मॉडल को ठीक करना चाहते हैं? यही इस अध्याय का विषय है! आप सीखेंगे कि: {#if fw === 'pt'} diff --git a/chapters/hi/chapter3/2.mdx b/chapters/hi/chapter3/2.mdx index 9105b30cf..da9ddb169 100644 --- a/chapters/hi/chapter3/2.mdx +++ b/chapters/hi/chapter3/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/hi/chapter3/3.mdx b/chapters/hi/chapter3/3.mdx index 748824180..436949ddd 100644 --- a/chapters/hi/chapter3/3.mdx +++ b/chapters/hi/chapter3/3.mdx @@ -2,9 +2,9 @@ # मॉडल कि Trainer API के साथ - diff --git a/chapters/hi/chapter3/3_tf.mdx b/chapters/hi/chapter3/3_tf.mdx index 837983be3..3954bb9f4 100644 --- a/chapters/hi/chapter3/3_tf.mdx +++ b/chapters/hi/chapter3/3_tf.mdx @@ -2,9 +2,9 @@ # मॉडल कि फाइन-ट्यूनिंग Keras के साथ - diff --git a/chapters/hi/chapter3/4.mdx b/chapters/hi/chapter3/4.mdx index 181e3e0e5..652b78030 100644 --- a/chapters/hi/chapter3/4.mdx +++ b/chapters/hi/chapter3/4.mdx @@ -1,8 +1,8 @@ # एक पूर्ण प्रशिक्षण - diff --git a/chapters/hi/chapter3/5.mdx b/chapters/hi/chapter3/5.mdx index 817398e8d..06c485e56 100644 --- a/chapters/hi/chapter3/5.mdx +++ b/chapters/hi/chapter3/5.mdx @@ -2,6 +2,11 @@ # फाइन-ट्यूनिंग, चेक! + + काफी मजेदार था! पहले दो अध्यायों में आपने मॉडल और टोकननाइज़रस के बारे में सीखा, और अब आप जानते हैं कि उन्हें अपने डेटा के लिए कैसे ठीक यानि फाइन-ट्यून किया जाए। संक्षेप में, इस अध्याय में आपने: {#if fw === 'pt'} diff --git a/chapters/hi/chapter3/6.mdx b/chapters/hi/chapter3/6.mdx index 65fdd2e63..1cf4d5916 100644 --- a/chapters/hi/chapter3/6.mdx +++ b/chapters/hi/chapter3/6.mdx @@ -4,6 +4,11 @@ # अध्याय-का-अंत प्रश्नोत्तरी + + इस अध्याय में आपने जो सीखा, उसका परीक्षण करें! ### 1. `इमोशन` डेटासेट में ट्विटर संदेश है जिनहे भावनाओं के साथ लेबल किया गया है। इसे [हब](https://huggingface.co/datasets) में खोजें, और डेटासेट कार्ड पढ़ें। इनमें से कौन सा इसकी मूल भावनाओं में से एक नहीं है? diff --git a/chapters/it/_toctree.yml b/chapters/it/_toctree.yml index 4174fa0f2..6802ccac3 100644 --- a/chapters/it/_toctree.yml +++ b/chapters/it/_toctree.yml @@ -23,7 +23,14 @@ title: Bias e limiti - local: chapter1/9 title: Riassunto - + +- title: 2. Usare i 🤗 Transformers + sections: + - local: chapter2/1 + title: Introduzione + - local: chapter2/2 + title: Dietro la pipeline + - title: 4. Condividere modelli e tokenizers sections: - local: chapter4/1 diff --git a/chapters/it/chapter1/1.mdx b/chapters/it/chapter1/1.mdx index 4fd68aa6a..a2258a521 100644 --- a/chapters/it/chapter1/1.mdx +++ b/chapters/it/chapter1/1.mdx @@ -1,5 +1,10 @@ # Introduzione + + ## Benvenuto/a al corso di 🤗! diff --git a/chapters/it/chapter1/2.mdx b/chapters/it/chapter1/2.mdx index a5845ca54..e1d2eb53f 100644 --- a/chapters/it/chapter1/2.mdx +++ b/chapters/it/chapter1/2.mdx @@ -1,5 +1,10 @@ # Natural Language Processing + + Prima di tuffarci nei modelli Transformer, diamo un'occhiata rapida alla natura del natural language processing (*elaborazione del linguaggio naturale*) e alle ragioni per cui quest'ultimo ci interessa. ## Cosa intendiamo per NLP? diff --git a/chapters/it/chapter1/3.mdx b/chapters/it/chapter1/3.mdx index 7fb506a94..681a54b9a 100644 --- a/chapters/it/chapter1/3.mdx +++ b/chapters/it/chapter1/3.mdx @@ -1,8 +1,8 @@ # Cosa fanno i Transformer? - diff --git a/chapters/it/chapter1/4.mdx b/chapters/it/chapter1/4.mdx index 8aa824f14..9d8ea8fd6 100644 --- a/chapters/it/chapter1/4.mdx +++ b/chapters/it/chapter1/4.mdx @@ -1,5 +1,10 @@ # Come funzionano i Transformer? + + In questa sezione, vedremo in maniera approfondita l'architettura dei modelli Transformer. ## Un po' di storia dei Transformer diff --git a/chapters/it/chapter1/5.mdx b/chapters/it/chapter1/5.mdx index 817c81463..7b839ec67 100644 --- a/chapters/it/chapter1/5.mdx +++ b/chapters/it/chapter1/5.mdx @@ -1,5 +1,10 @@ # Modelli encoder + + I modelli encoder utilizzano solo l'encoder di un modello Transformer. In ogni fase, gli attention layer hanno accesso a tutte le parole della frase di partenza. Questi modelli sono spesso caratterizzati come aventi attenzione "bi-direzionale" e chiamati *auto-encoding models*. diff --git a/chapters/it/chapter1/6.mdx b/chapters/it/chapter1/6.mdx index c9a7296a4..f9bf8a64a 100644 --- a/chapters/it/chapter1/6.mdx +++ b/chapters/it/chapter1/6.mdx @@ -1,5 +1,10 @@ # Modelli decoder + + I modelli decoder utilizzano solo il decoder di un modello Transformer. Ad ogni passaggio e per una data parola, gli attention layer hanno accesso solo alle parole che la precedono nella frase. Questi modelli sono spesso detti *auto-regressive models*. diff --git a/chapters/it/chapter1/7.mdx b/chapters/it/chapter1/7.mdx index a8c616c64..14d2e0a6a 100644 --- a/chapters/it/chapter1/7.mdx +++ b/chapters/it/chapter1/7.mdx @@ -1,5 +1,10 @@ # Modelli sequence-to-sequence + + I modelli encoder-decoder (detti anche modelli *sequence-to-sequence*) utilizzano entrambi i componenti dell'architettura Transformer. Ad ogni passaggio, gli attention layer dell'encoder hanno accesso a tutte le parole della frase iniziale, mentre gli attention layer del decoder possono solo accedere alle parole che precedono linearmente una data parola nell'input. diff --git a/chapters/it/chapter1/8.mdx b/chapters/it/chapter1/8.mdx index 9a68fed34..b2548fc64 100644 --- a/chapters/it/chapter1/8.mdx +++ b/chapters/it/chapter1/8.mdx @@ -1,8 +1,8 @@ # Bias e limiti - diff --git a/chapters/it/chapter1/9.mdx b/chapters/it/chapter1/9.mdx index 3b11de19c..577256a58 100644 --- a/chapters/it/chapter1/9.mdx +++ b/chapters/it/chapter1/9.mdx @@ -1,5 +1,10 @@ # Riassunto + + In questo capitolo, hai scoperto come approcciare diversi compiti di NLP utilizzando la funzione di alto livello `pipeline()` degli 🤗 Transformer. Abbiamo anche visto come cercare e utilizzare i modelli dell'Hub, nonché come usare l'Inference API per testare i modelli direttamente nel tuo browser. Abbiamo discusso di come i modelli Transformer lavorino a livello alto, e parlato dell'importanza del transfer learning e dell'affinamento. Un aspetto chiave è che è possibile utilizzare l'architettuta completa oppure solo l'encoder o il decoder, dipendentemente dal compito a cui desideri lavorare. La tabella seguente riordina questi concetti: diff --git a/chapters/it/chapter2/1.mdx b/chapters/it/chapter2/1.mdx new file mode 100644 index 000000000..d0579a712 --- /dev/null +++ b/chapters/it/chapter2/1.mdx @@ -0,0 +1,26 @@ +# Introduzione + + + +Come si è visto nel [Capitolo 1](/course/chapter1), I modelli Transformers sono solitamente molto grandi. +Con milioni o decine di *miliardi* di parametri, l'addestramento e la distribuzione di questi modelli è un'impresa complicata. +Inoltre, con i nuovi modelli che vengono rilasciati quasi ogni giorno e ognuno dei quali ha una propria implementazione, provarli tutti non è un lavoro facile. + +La libreria 🤗 Transformers è stata creata per risolvere questo problema. Il suo obiettivo è fornire un'unica API attraverso la quale caricare, addestrare e salvare qualsiasi modello Transformer. Le caratteristiche principali della libreria sono: + +- **Facilità d'uso**: È possibile scaricare, caricare ed utilizzare un modello NLP all'avanguardia per fare inferenza con appena due righe di codice. +- **Flessibilità**: Al loro interno, tutti i modelli sono semplici classi PyTorch `nn.Module` o TensorFlow `tf.keras.Model` e possono essere gestiti come qualsiasi altro modello nei rispettivi framework di apprendimento automatico (ML). +- **Semplicità**: La libreria non contiene quasi nessuna astrazione. Il concetto di "All in one file" è fondamentale: il forward pass di un modello è interamente definito in un singolo file, in modo che il codice stesso sia comprensibile e violabile. + +Quest'ultima caratteristica rende 🤗 Transformers molto diversi da altre librerie ML. I modelli non sono costruiti su moduli condivisi tra i file, ma ogni modello ha i propri layers. Oltre a rendere i modelli più accessibili e comprensibili, questo permette di sperimentare facilmente su un modello senza influenzare gli altri. + +Questo capitolo inizierà con un esempio in cui usiamo un modello e un tokenizer insieme per replicare la funzione `pipeline()` introdotta nel [Capitolo 1](/course/chapter1). Successivamente, parleremo dell'API del modello: ci immergeremo nelle classi del modello e della configurazione e mostreremo come caricare un modello e come esso elabora gli input numerici per produrre previsioni. + +Successivamente vedremo l'API del tokenizer, che è l'altro componente principale della funzione `pipeline()`. I tokenizer si occupano della prima e dell'ultima fase di elaborazione, gestendo la conversione da testo a input numerici per la rete neurale e la conversione di nuovo in testo quando è necessario. Infine, mostreremo come gestire l'invio di più frasi a un modello in un batch preparato, per poi concludere il tutto con un'analisi più approfondita della funzione di alto livello `tokenizer()`. + + +⚠️ Per poter usufruire di tutte le funzioni disponibili con il Model Hub e i 🤗 Transformers, si consiglia di creare un account. + \ No newline at end of file diff --git a/chapters/it/chapter2/2.mdx b/chapters/it/chapter2/2.mdx new file mode 100644 index 000000000..94fd67ebc --- /dev/null +++ b/chapters/it/chapter2/2.mdx @@ -0,0 +1,351 @@ + + +# Dietro la pipeline + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + + Questa è la prima sezione in cui il contenuto è leggermente diverso a seconda che si utilizzi PyTorch o TensorFlow. Attivate lo switch sopra il titolo per selezionare la tua piattaforma preferita! + + +{#if fw === 'pt'} + +{:else} + +{/if} + +Cominciamo con un esempio completo, dando un'occhiata a ciò che è successo dietro le quinte quando abbiamo eseguito il seguente codice nel [Capitolo 1](/course/chapter1): + +```python +from transformers import pipeline + +classifier = pipeline("sentiment-analysis") +classifier( + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] +) +``` + +e ottenuto: + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}, + {'label': 'NEGATIVE', 'score': 0.9994558095932007}] +``` + +Come abbiamo visto nel [Capitolo 1](/course/chapter1), questa pipeline raggruppa tre fasi: la pre-elaborazione, il passaggio degli input attraverso il modello e la post-elaborazione: + +
+La pipeline NLP completa: tokenizzazione del testo, conversione in ID e inferenza attraverso il modello Transformer ed il modello head. + +
+ +Esaminiamo rapidamente ciascuno di essi. + +## Preelaborazione con un tokenizer + +Come altre reti neurali, i modelli Transformer non possono elaborare direttamente il testo non elaborato, quindi la prima fase della nostra pipeline consiste nel convertire gli input testuali in numeri che il modello possa interpretare. Per fare ciò, utilizziamo un *tokenizer*, che sarà responsabile di: + +- Suddivisione dell'input in parole, sottoparole o simboli (come la punteggiatura) che vengono chiamati *token*. +- Mappare ogni token in un numero intero +- Aggiunta di ulteriori input che possono essere utili per il modello + +Tutta questa preelaborazione deve essere fatta esattamente nello stesso modo in cui è stato preaddestrato il modello, quindi dobbiamo prima scaricare queste informazioni dal [Model Hub](https://huggingface.co/models). Per farlo, si usa la classe `AutoTokenizer` e il suo metodo `from_pretrained()`. Utilizzando il nome del checkpoint del nostro modello, recupererà automaticamente i dati associati al tokenizer del modello e li metterà in cache (in modo che vengano scaricati solo la prima volta che si esegue il codice sottostante). + +Poiché il checkpoint predefinito della pipeline `sentiment-analysis` è `distilbert-base-uncased-finetuned-sst-2-english` (si può vedere la sua scheda modello [qui](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english)), eseguiamo quanto segue: + +```python +from transformers import AutoTokenizer + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` + +Una volta che abbiamo il tokenizer, possiamo passargli direttamente le nostre frasi e otterremo un dizionario pronto per il nostro modello! L'unica cosa che resta da fare è convertire l'elenco degli ID in ingresso in tensori. + +È possibile utilizzare i 🤗 Transformer senza doversi preoccupare di quale framework ML venga utilizzato come backend;potrebbe essere PyTorch o TensorFlow, o Flax per alcuni modelli. Tuttavia, i modelli Transformer accettano solo *tensors* come input. Se è la prima volta che sentite parlare di tensori, potete pensare a loro come array NumPy. Un array NumPy può essere uno scalare (0D), un vettore (1D), una matrice (2D) o avere più dimensioni. Si tratta effettivamente di un tensore; i tensori di altri framework ML si comportano in modo simile e di solito sono semplici da istanziare come gli array NumPy. + +Per specificare il tipo di tensori che vogliamo ottenere (PyTorch, TensorFlow o NumPy), usiamo l'argomento `return_tensors`: + +{#if fw === 'pt'} +```python +raw_inputs = [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", +] +inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="pt") +print(inputs) +``` +{:else} +```python +raw_inputs = [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", +] +inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="tf") +print(inputs) +``` +{/if} + +Non preoccupatevi ancora di padding e truncation; li spiegheremo più avanti.Le cose principali da ricordare sono che si può passare una frase o un elenco di frasi, oltre a specificare il tipo di tensori che si desidera ottenere (se non viene passato alcun tipo, si otterrà una lista di liste come risultato). + +{#if fw === 'pt'} + +Ecco come appaiono i risultati come tensori PyTorch: + +```python out +{ + 'input_ids': tensor([ + [ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012, 102], + [ 101, 1045, 5223, 2023, 2061, 2172, 999, 102, 0, 0, 0, 0, 0, 0, 0, 0] + ]), + '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] + ]) +} +``` +{:else} + +Ecco come appaiono i risultati come tensori TensorFlow: + +```python out +{ + 'input_ids': , + 'attention_mask': +} +``` +{/if} + +L'output stesso è un dizionario contenente due chiavi, `input_ids` e `attention_mask`. `input_ids` contiene due righe di interi (uno per ogni frase) che sono gli identificatori unici dei token in ogni frase. Spiegheremo cosa sia la `attention_mask` più avanti in questo capitolo. + +## Passare attraverso il modello + +{#if fw === 'pt'} +Possiamo scaricare il nostro modello preaddestrato nello stesso modo in cui abbiamo fatto con il nostro tokenizer. 🤗 Transformers fornisce una classe `AutoModel` che ha anche un metodo `from_pretrained()`: + +```python +from transformers import AutoModel + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = AutoModel.from_pretrained(checkpoint) +``` +{:else} +Possiamo scaricare il nostro modello preaddestrato nello stesso modo in cui abbiamo fatto con il nostro tokenizer. 🤗 Transformers fornisce una classe `TFAutoModel` che ha anche un metodo `from_pretrained`: + +```python +from transformers import TFAutoModel + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = TFAutoModel.from_pretrained(checkpoint) +``` +{/if} + +In questo frammento di codice, abbiamo scaricato lo stesso checkpoint usato in precedenza nella nostra pipeline (in realtà dovrebbe essere già nella cache) e abbiamo istanziato un modello con esso. + +Questa architettura contiene solo il modulo Transformer di base: dati alcuni input, produce quelli che chiameremo *hidden states*, noti anche come *features*. Per ogni input del modello, recupereremo un vettore ad alta dimensionalità che rappresenta la **comprensione contestuale di quell'input da parte del modello Transformer**. + +Se per te tutto questo non ha senso, non preoccuparti. Ti spiegheremo tutto più avanti. + +Anche se questi stati nascosti possono essere utili da soli, di solito sono input di un'altra parte del modello, nota come *head*. Nel [Capitolo 1](/course/chapter1), i diversi compiti potrebbero essere eseguiti con la stessa architettura, ma a ciascuno di essi sarà associata una head diversa. + +### Un vettore ad alta dimensionalità? + +Il vettore emesso dal modulo Transformer è solitamente di grandi dimensioni. In genere ha tre dimensioni: + +- **Dimensione del batch**: Il numero di sequenze elaborate alla volta (2 nel nostro esempio). +- **Lunghezza della sequenza**: La lunghezza della rappresentazione numerica della sequenza (16 nel nostro esempio). +- **Dimensione nascosta**: La dimensione del vettore di ciascun ingresso del modello. + +Si dice che è "ad alta dimensionalità" a causa dell'ultimo valore. La dimensione nascosta può essere molto grande (768 è comune per i modelli più piccoli, mentre nei modelli più grandi può arrivare a 3072 o più). + +Lo possiamo vedere se alimentiamo il nostro modello con gli input che abbiamo preelaborato: + +{#if fw === 'pt'} +```python +outputs = model(**inputs) +print(outputs.last_hidden_state.shape) +``` + +```python out +torch.Size([2, 16, 768]) +``` +{:else} +```py +outputs = model(inputs) +print(outputs.last_hidden_state.shape) +``` + +```python out +(2, 16, 768) +``` +{/if} + +Si noti che gli output dei modelli 🤗 Transformers si comportano come `namedtuple` o dizionari. Si può accedere agli elementi per attributi (come abbiamo fatto noi) sia per chiave (`outputs["last_hidden_state"]`), sia per indice se si sa esattamente dove si trova ciò che si sta cercando (`outputs[0]`). + +### Model heads: Dare un senso ai numeri + +Le model head prendono in input il vettore ad alta dimensione degli stati nascosti e lo proiettano su una dimensione diversa. Di solito sono composte da uno o pochi strati lineari: + +
+Una rete di Transformer accanto alla sua head. + +
+ +Gli output del modello Transformer vengono inviati direttamente alla model head per essere elaborati. + +In questo diagramma, il modello è rappresentato dallo strato embeddings e dagli strati successivi. Il livello embeddings converte ogni ID dell'input tokenizzato in un vettore che rappresenta il token associato. I livelli successivi manipolano questi vettori utilizzando il meccanismo di attenzione per produrre la rappresentazione finale delle frasi. + +Esistono diverse architetture disponibili nei 🤗 Transformers, ognuna delle quali è stata progettata per affrontare un compito specifico. Ecco un elenco non esaustivo: + +- `*Model` (retrieve the hidden states) +- `*ForCausalLM` +- `*ForMaskedLM` +- `*ForMultipleChoice` +- `*ForQuestionAnswering` +- `*ForSequenceClassification` +- `*ForTokenClassification` +- e altre 🤗 + +{#if fw === 'pt'} +Per il nostro esempio, avremo bisogno di un modello con una classificazion head della sequenza (per poter classificare le frasi come positive o negative). Quindi, non useremo la classe `AutoModel`, ma `AutoModelForSequenceClassification`: + +```python +from transformers import AutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +outputs = model(**inputs) +``` +{:else} +Per il nostro esempio, avremo bisogno di un modello con una classificazion head della sequenza (per poter classificare le frasi come positive o negative). Quindi, non useremo la classe `TFAutoModel`, ma `TFAutoModelForSequenceClassification`: + +```python +from transformers import TFAutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +outputs = model(inputs) +``` +{/if} + +Ora, se osserviamo la forma dei nostri input, la dimensionalità sarà molto più bassa: la model head prende in input i vettori ad alta dimensionalità che abbiamo visto prima e produce vettori contenenti due valori (uno per etichetta): + +```python +print(outputs.logits.shape) +``` + +{#if fw === 'pt'} +```python out +torch.Size([2, 2]) +``` +{:else} +```python out +(2, 2) +``` +{/if} + +Dato che abbiamo solo due frasi e due etichette, il risultato che otteniamo dal nostro modello è di forma 2 x 2. + +## Postprocessing the output + +I valori che otteniamo come output dal nostro modello non hanno necessariamente senso da soli. Diamo un'occhiata: + +```python +print(outputs.logits) +``` + +{#if fw === 'pt'} +```python out +tensor([[-1.5607, 1.6123], + [ 4.1692, -3.3464]], grad_fn=) +``` +{:else} +```python out + +``` +{/if} + +Il nostro modello ha previsto `[-1.5607, 1.6123]` per la prima frase e `[ 4.1692, -3.3464]` per la seconda. Non si tratta di probabilità ma di *logit*, i punteggi non normalizzati emessi dall'ultimo livello del modello. Per poterli convertire in probabilità, devono passare attraverso un layer [SoftMax](https://en.wikipedia.org/wiki/Softmax_function) (tutti i modelli 🤗 Transformers producono i logits, poiché la funzione di perdita per l'addestramento generalmente fonde l'ultima funzione di attivazione, come SoftMax, con la funzione di perdita effettiva, come la cross entropy): + +{#if fw === 'pt'} +```py +import torch + +predictions = torch.nn.functional.softmax(outputs.logits, dim=-1) +print(predictions) +``` +{:else} +```py +import tensorflow as tf + +predictions = tf.math.softmax(outputs.logits, axis=-1) +print(predictions) +``` +{/if} + +{#if fw === 'pt'} +```python out +tensor([[4.0195e-02, 9.5980e-01], + [9.9946e-01, 5.4418e-04]], grad_fn=) +``` +{:else} +```python out +tf.Tensor( +[[4.01951671e-02 9.59804833e-01] + [9.9945587e-01 5.4418424e-04]], shape=(2, 2), dtype=float32) +``` +{/if} + +Ora possiamo vedere che il modello ha previsto `[0,0402, 0,9598]` per la prima frase e `[0,9995, 0,0005]` per la seconda. Si tratta di punteggi di probabilità riconoscibili. + +Per ottenere le etichette corrispondenti a ogni posizione, si può ispezionare l'attributo `id2label` della configurazione del modello (si veda la prossima sezione): + +```python +model.config.id2label +``` + +```python out +{0: 'NEGATIVE', 1: 'POSITIVE'} +``` + +Ora possiamo concludere che il modello ha previsto quanto segue: + +- Prima frase: NEGATIVE: 0.0402, POSITIVE: 0.9598 +- Seconda frase: NEGATIVE: 0.9995, POSITIVE: 0.0005 + +Abbiamo riprodotto con successo le tre fasi della pipeline: preelaborazione con i tokenizer, passaggio degli input attraverso il modello e postelaborazione! Ora prendiamoci un po' di tempo per approfondire ognuna di queste fasi. + + +✏️ **Provaci anche tu!** Scegli due (o più) testi di tua proprietà e lanciali all'interno della pipeline `sentiment-analysis`. Successivamente, replica i passi che hai visto qui e verifica di aver ottenuto gli stessi risultati! + diff --git a/chapters/it/chapter4/1.mdx b/chapters/it/chapter4/1.mdx index b0d8f4f70..df7346be0 100644 --- a/chapters/it/chapter4/1.mdx +++ b/chapters/it/chapter4/1.mdx @@ -1,5 +1,10 @@ # L'Hub di Hugging Face + + L'Hub di Hugging Face -- il nostro sito web principale -- è la piattaforma che permette a chiunque di scoprire, utilizzare, e proporre nuovi modelli e dataset. Contiene una vasta varietà di modelli, con più di 10.000 modelli pubblicamente disponibili. In questo capitolo ci concentreremo sui modelli, mentre approfondiremo i dataset nel capitolo 5. I modelli disponibili nell'Hub non sono limitati ai 🤗 Transformers, o ai modelli di analisi del linguaggio naturale (Natural Language Processing - NLP). Ci sono infatti modelli sviluppati da [Flair](https://github.com/flairNLP/flair) e [AllenNLP](https://github.com/allenai/allennlp) per l'NLP, ma anche modelli di [Asteroid](https://github.com/asteroid-team/asteroid) e [pyannote](https://github.com/pyannote/pyannote-audio) per la fonologia, e [timm](https://github.com/rwightman/pytorch-image-models) per l'analisi di immagini. diff --git a/chapters/it/chapter4/2.mdx b/chapters/it/chapter4/2.mdx index a95b9c9b0..6fbf6e46f 100644 --- a/chapters/it/chapter4/2.mdx +++ b/chapters/it/chapter4/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/it/chapter4/3.mdx b/chapters/it/chapter4/3.mdx index de96f65ab..93f55ee0e 100644 --- a/chapters/it/chapter4/3.mdx +++ b/chapters/it/chapter4/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/it/chapter4/4.mdx b/chapters/it/chapter4/4.mdx index 1b00e6946..e9d1922f2 100644 --- a/chapters/it/chapter4/4.mdx +++ b/chapters/it/chapter4/4.mdx @@ -1,5 +1,10 @@ # Generare un cartellino del modello + + Il cartellino del modello (model card) è un file di importanza pari ai file del modello e del tokenizer in un repository. Contiene la definizione del modello, assicurando la possibilità di riutilizzarlo e riprodurre i risultati da parte dei membri della comunità, e facendo si che il modello sia una piattaforma su cui gli altri membri possono costruire i loro artefatti. Documentare il processo di addestramento e valutazione aiuta gli altri a capire cosa aspettarsi dal modello — inoltre, fornire informazioni accurate sui dati utilizzati e sulle operazioni di pre e post elaborazione (preprocessing e postprocessing), assicura che si possano identificare e comprenere le limitazioni, i bias, e i contesti in cui il modello è utile, e quelli in cui non lo è. diff --git a/chapters/it/chapter4/5.mdx b/chapters/it/chapter4/5.mdx index 68112b891..3fbeda1d3 100644 --- a/chapters/it/chapter4/5.mdx +++ b/chapters/it/chapter4/5.mdx @@ -1,5 +1,10 @@ # Fine della parte 1! + + Questa è la dine della prima parte del corso! La seconda parte sarà rilasciata il 15 Novembre durante un grande evento della comunità. Per maggiori informazioni [vedere qui](https://huggingface.co/blog/course-launch-event). A questo punto dovreste essere in grado di affinare un modello pre-addestrato per un problema di classificazione tesutale (a frase signola o doppia), e caricare il risultato sull'Hub dei Modelli (Model Hub). Per assicurarvi di padroneggiare i contenuti di questa prima sezione, dovreste provare a fare esattamente questo su un problema che vi interessi (e non necessariamente in Inglese se parlate un'altra lingua)! Potete trovare aiuto nel [forum di Hugging Face](https://discuss.huggingface.co/), e quando avete finito potete condividere il vostro progetto in [questo topic](https://discuss.huggingface.co/t/share-your-projects/6803). diff --git a/chapters/it/chapter4/6.mdx b/chapters/it/chapter4/6.mdx index c31b9fb76..c2616894c 100644 --- a/chapters/it/chapter4/6.mdx +++ b/chapters/it/chapter4/6.mdx @@ -4,6 +4,11 @@ # Quiz di fine capitolo + + Mettiamo alla prova quello che avete imparato in questo capitolo! ### 1. Quali modelli si possono caricare sull'Hub? diff --git a/chapters/it/chapter5/1.mdx b/chapters/it/chapter5/1.mdx index e23cf502e..b0776fbc0 100644 --- a/chapters/it/chapter5/1.mdx +++ b/chapters/it/chapter5/1.mdx @@ -1,5 +1,10 @@ # Introduzione + + Nel [Capitolo 3](/course/chapter3) hai mosso i primi passi nella libreria 🤗 Datasets, e hai scoperto i tre passaggi fondamentali nell'ottimizzazione dei modelli: 1. Si carica un dataset dell'Hub Hugging Face. diff --git a/chapters/it/chapter5/2.mdx b/chapters/it/chapter5/2.mdx index 93a7d01f6..77133416d 100644 --- a/chapters/it/chapter5/2.mdx +++ b/chapters/it/chapter5/2.mdx @@ -1,8 +1,8 @@ # E se il mio dataset non è sull'Hub? - diff --git a/chapters/it/chapter5/3.mdx b/chapters/it/chapter5/3.mdx index 8c44362ed..af65c1765 100644 --- a/chapters/it/chapter5/3.mdx +++ b/chapters/it/chapter5/3.mdx @@ -1,8 +1,8 @@ # È arrivato il momento di tagliuzzare - diff --git a/chapters/it/chapter5/4.mdx b/chapters/it/chapter5/4.mdx index 682ef8719..03dd0aa2c 100644 --- a/chapters/it/chapter5/4.mdx +++ b/chapters/it/chapter5/4.mdx @@ -1,8 +1,8 @@ # Big data? Ci pensa 🤗 Datasets! - diff --git a/chapters/it/chapter5/5.mdx b/chapters/it/chapter5/5.mdx index be262d885..a0b1542d0 100644 --- a/chapters/it/chapter5/5.mdx +++ b/chapters/it/chapter5/5.mdx @@ -1,8 +1,8 @@ # Creare il proprio dataset - diff --git a/chapters/it/chapter5/6.mdx b/chapters/it/chapter5/6.mdx index 087e457c6..8fa4b93e2 100644 --- a/chapters/it/chapter5/6.mdx +++ b/chapters/it/chapter5/6.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/it/chapter5/7.mdx b/chapters/it/chapter5/7.mdx index 118b04511..f2c963eaa 100644 --- a/chapters/it/chapter5/7.mdx +++ b/chapters/it/chapter5/7.mdx @@ -1,5 +1,10 @@ # 🤗 Datasets, check! + + Beh, abbiamo fatto un bel giro nella libreria 🤗 Datasets: complimenti per aver raggiunto quest'obiettivo! Con le conoscenze che hai ottenuto da questo capitolo, sei in grado di: - Caricare dataset da ogni luogo, sia esso l'Hub di Hugging Face, il tuo portatile, o un server in remoto della tua compagnia. - Fare data-wrangling usando un mix delle funzioni `Dataset.map()` e `Dataset.filter()`. diff --git a/chapters/it/chapter5/8.mdx b/chapters/it/chapter5/8.mdx index 5fece0b6a..15288bf23 100644 --- a/chapters/it/chapter5/8.mdx +++ b/chapters/it/chapter5/8.mdx @@ -2,6 +2,11 @@ # Quiz di fine capitolo + + In questo capitolo abbiamo fatto un bel po' di strada! Non preoccuparti se non hai colto tutti i dettagli; i capitoli successivi ti aiuteranno a capire come funzionano le cose dietro le quinte! Prima di andare oltre, mettiamo alla prova ciò che hai imparato in questo capitolo. diff --git a/chapters/it/chapter8/1.mdx b/chapters/it/chapter8/1.mdx index 7b34c6a70..1c842c488 100644 --- a/chapters/it/chapter8/1.mdx +++ b/chapters/it/chapter8/1.mdx @@ -1,5 +1,10 @@ # Introduzione + + Ora che sai come affrontare i problemi più comuni nel campo del NLP con i 🤗 Transformers, dovresti essere in grado d'iniziare a lavorare sui tuoi progetti personali! In questo capitolo descriveremo cosa fare quando si incontra un problema. Imparerai come eseguire il debug del tuo codice o del tuo training e come chiedere aiuto alla community se non riesci a risolvere il problema in autonomia. E, se pensi di aver trovato un bug in una delle librerie di Hugging Face, ti mostreremo il modo migliore per segnalarlo, in modo che il problema venga risolto il più rapidamente possibile. Più precisamente, in questo capitolo impareremo: diff --git a/chapters/it/chapter8/2.mdx b/chapters/it/chapter8/2.mdx index 75b43ed9a..d3d4bc206 100644 --- a/chapters/it/chapter8/2.mdx +++ b/chapters/it/chapter8/2.mdx @@ -1,8 +1,8 @@ # Cosa fare quando si riceve un errore - diff --git a/chapters/it/chapter8/3.mdx b/chapters/it/chapter8/3.mdx index d7cc632ba..c34b5cdcc 100644 --- a/chapters/it/chapter8/3.mdx +++ b/chapters/it/chapter8/3.mdx @@ -1,8 +1,8 @@ # Asking for help on the forums - diff --git a/chapters/it/chapter8/4.mdx b/chapters/it/chapter8/4.mdx index c57190b58..245c246fa 100644 --- a/chapters/it/chapter8/4.mdx +++ b/chapters/it/chapter8/4.mdx @@ -2,9 +2,9 @@ # Fare il debug della training pipeline - diff --git a/chapters/it/chapter8/4_tf.mdx b/chapters/it/chapter8/4_tf.mdx index 998000149..7ba7fbc3e 100644 --- a/chapters/it/chapter8/4_tf.mdx +++ b/chapters/it/chapter8/4_tf.mdx @@ -2,9 +2,9 @@ # Fare il debug di una training pipeline - diff --git a/chapters/it/chapter8/5.mdx b/chapters/it/chapter8/5.mdx index 683738e94..444c59436 100644 --- a/chapters/it/chapter8/5.mdx +++ b/chapters/it/chapter8/5.mdx @@ -1,8 +1,8 @@ # Come scrivere un issue correttamente - diff --git a/chapters/it/chapter8/6.mdx b/chapters/it/chapter8/6.mdx index f0792a85a..fafced64c 100644 --- a/chapters/it/chapter8/6.mdx +++ b/chapters/it/chapter8/6.mdx @@ -1,5 +1,10 @@ # Parte 2 completata! + + Congratulazioni, hai completato la seconda parte del corso! Stiamo lavorando attivamente alla terza parte, quindi iscrivetevi alla nostra [newsletter](https://huggingface.curated.co/) per essere sicuro/a di non perdere il suo rilascio. Ora dovresti essere in grado di risolvere una serie di problemi di NLP e di affinare o preaddestrare un modello su di essi. Non dimenticare di condividere i tuoi risultati con la community su [Model Hub](https://huggingface.co/models). diff --git a/chapters/it/chapter8/7.mdx b/chapters/it/chapter8/7.mdx index 467e4943a..e77af1667 100644 --- a/chapters/it/chapter8/7.mdx +++ b/chapters/it/chapter8/7.mdx @@ -2,6 +2,11 @@ # Quiz di fine capitolo + + Mettiamo alla prova quello che hai imparato in questo capitolo! ### 1. In quale ordine si deve leggere un traceback di Python? diff --git a/chapters/ja/chapter1/1.mdx b/chapters/ja/chapter1/1.mdx index 5e1dd3a31..fdcb55837 100644 --- a/chapters/ja/chapter1/1.mdx +++ b/chapters/ja/chapter1/1.mdx @@ -1,5 +1,10 @@ # イントロダクション + + ## 🤗 コースへようこそ! diff --git a/chapters/ja/chapter4/1.mdx b/chapters/ja/chapter4/1.mdx index 93effda6b..94eb03363 100644 --- a/chapters/ja/chapter4/1.mdx +++ b/chapters/ja/chapter4/1.mdx @@ -1,5 +1,10 @@ # ハギングフェイスハブ + + [ハギングフェイスハブ](https://huggingface.co/)(Hugging Face Hub) –- 私たちのメインウェブサイト –- は、誰もが新しい最先端のモデルやデータセットを発見し、利用し、貢献することができるプラットフォームです。様々なモデルをホストしており、10,000以上のモデルが一般に公開されています。この章ではモデルに焦点を当て、第5章ではデータセットについて見ていきます。 ハブにあるモデルは、🤗 Transformersや、そもそもNLPモデルに限りません。例えば、[Flair](https://github.com/flairNLP/flair)や[AllenNLP](https://github.com/allenai/allennlp)からはNLPモデルが、[Asteroid](https://github.com/asteroid-team/asteroid)や[pyannote](https://github.com/pyannote/pyannote-audio)からは音声モデルが、そして[timm](https://github.com/rwightman/pytorch-image-models)からは画像モデルがそれぞれ寄贈されています。 diff --git a/chapters/ja/chapter4/2.mdx b/chapters/ja/chapter4/2.mdx index eeea7d497..777733e93 100644 --- a/chapters/ja/chapter4/2.mdx +++ b/chapters/ja/chapter4/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/ja/chapter4/3.mdx b/chapters/ja/chapter4/3.mdx index 90e29c62b..088de9698 100644 --- a/chapters/ja/chapter4/3.mdx +++ b/chapters/ja/chapter4/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/ja/chapter4/4.mdx b/chapters/ja/chapter4/4.mdx index 82e25f2f5..76a02b4bc 100644 --- a/chapters/ja/chapter4/4.mdx +++ b/chapters/ja/chapter4/4.mdx @@ -1,5 +1,10 @@ # モデルカードを作成する + + モデルカードは、モデルリポジトリにおいて、モデルファイルやトークナイザーファイルと同じくらい重要なファイルです。モデルの主要な定義であり、コミュニティメンバーによる再利用と結果の再現性を保証し、さらには他のメンバーが成果物を構築するためのプラットフォームを提供します。 また、使用したデータや前処理・後処理に関する十分な情報を提供することで、モデルの限界、バイアス、有用となる場面の特定及び理解が可能になります。 diff --git a/chapters/ja/chapter4/5.mdx b/chapters/ja/chapter4/5.mdx index d4dd76891..7678c08a4 100644 --- a/chapters/ja/chapter4/5.mdx +++ b/chapters/ja/chapter4/5.mdx @@ -1,5 +1,10 @@ # パート1終了! + + これでコースのパート1は終了です!パート2は11月15日に開催される、大きなコミュニティイベントで公開される予定です。詳しくは[こちら](https://huggingface.co/blog/course-launch-event)をご覧ください。 これで、テキスト分類問題(単一または文のペア)に対する学習済みモデルのファインチューニングを行い、結果をモデルハブにアップロードできるようになりました。この最初のセクションを確実にマスターするためには、自分の興味のある問題(英語でなくてもよい)をきっちりやっておくことが必要です。[Hugging Face forums](https://discuss.huggingface.co/)で助けを求めることもできますし、完成したら[this topic](https://discuss.huggingface.co/t/share-your-projects/6803)でプロジェクトを共有することもできます。 diff --git a/chapters/ja/chapter4/6.mdx b/chapters/ja/chapter4/6.mdx index 22575d1b4..9ff6c9cc8 100644 --- a/chapters/ja/chapter4/6.mdx +++ b/chapters/ja/chapter4/6.mdx @@ -4,6 +4,11 @@ # チャプター修了クイズ + + この章で学んだことを確認してみましょう! ### 1. ハブにアップロードできるモデルには何か制限があるでしょうか? diff --git a/chapters/ja/chapter7/1.mdx b/chapters/ja/chapter7/1.mdx index 5856697ae..cb1eb2ee2 100644 --- a/chapters/ja/chapter7/1.mdx +++ b/chapters/ja/chapter7/1.mdx @@ -2,6 +2,11 @@ # イントロダクション + + [第3章](/course/ja/chapter3)では、テキスト分類のためにモデルを微調整する方法を学びました。この章では、以下のような一般的な自然言語処理タスクに取り組みます。 - トークン分類 diff --git a/chapters/ja/chapter7/2.mdx b/chapters/ja/chapter7/2.mdx index efd90ad71..d62280f21 100644 --- a/chapters/ja/chapter7/2.mdx +++ b/chapters/ja/chapter7/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/ja/chapter7/3.mdx b/chapters/ja/chapter7/3.mdx index faa27116d..eebcce1c8 100644 --- a/chapters/ja/chapter7/3.mdx +++ b/chapters/ja/chapter7/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/ja/chapter7/4.mdx b/chapters/ja/chapter7/4.mdx index cadd8c24c..519e43f3c 100644 --- a/chapters/ja/chapter7/4.mdx +++ b/chapters/ja/chapter7/4.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/ja/chapter7/5.mdx b/chapters/ja/chapter7/5.mdx index 13232100e..f17925981 100644 --- a/chapters/ja/chapter7/5.mdx +++ b/chapters/ja/chapter7/5.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/ja/chapter7/6.mdx b/chapters/ja/chapter7/6.mdx index ad4dccae9..5add41211 100644 --- a/chapters/ja/chapter7/6.mdx +++ b/chapters/ja/chapter7/6.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/ja/chapter7/7.mdx b/chapters/ja/chapter7/7.mdx index 8ee20d14f..bbbed4999 100644 --- a/chapters/ja/chapter7/7.mdx +++ b/chapters/ja/chapter7/7.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/ja/chapter7/8.mdx b/chapters/ja/chapter7/8.mdx index 7426fcc25..4f9610d6d 100644 --- a/chapters/ja/chapter7/8.mdx +++ b/chapters/ja/chapter7/8.mdx @@ -1,5 +1,10 @@ # NLPをマスター + + このコースでここまで進んだなら、おめでとうございます! あなたは今、🤗トランスフォーマーとハギング フェイス エコシステムを使って(ほとんど)どんなNLPタスクにも取り組むために必要なすべての知識とツールを手にしています。 diff --git a/chapters/ja/chapter7/9.mdx b/chapters/ja/chapter7/9.mdx index 4553fca3e..d2221b3dd 100644 --- a/chapters/ja/chapter7/9.mdx +++ b/chapters/ja/chapter7/9.mdx @@ -4,6 +4,11 @@ # 章末クイズ + + この章で学んだことをテストしてみましょう! ### 1. 次のタスクのうち、トークン分類問題として組み立てられるものはどれでしょうか? diff --git a/chapters/ja/chapter8/1.mdx b/chapters/ja/chapter8/1.mdx index 5c45820d8..7117248c2 100644 --- a/chapters/ja/chapter8/1.mdx +++ b/chapters/ja/chapter8/1.mdx @@ -1,5 +1,10 @@ # イントロダクション + + 🤗 Transformers を使いながらNLPタスクに取り組む方法がわかった後、自分自身のプロジェクトを簡単に始めることができます! この章では、エラーにぶつかったときにどうすればよいかを深く探ります。自分のコードやトレーニングを正確にデバッグする方法、そして自分でエラーを解決できない場合にコミュニティに助けを求める方法を一緒に学びましょう。また、HuggingFace (ハギングフェイス) ライブラリのひとつにバグを見つけたと思ったら、その問題をできるだけ早く解決するため報告する方法を紹介します。 この章では、一緒に次のことを学びます: diff --git a/chapters/ja/chapter8/2.mdx b/chapters/ja/chapter8/2.mdx index c6d73057c..ec6c8b066 100644 --- a/chapters/ja/chapter8/2.mdx +++ b/chapters/ja/chapter8/2.mdx @@ -1,8 +1,8 @@ # エラーを見つけた時に最初にすること - diff --git a/chapters/ja/chapter8/6.mdx b/chapters/ja/chapter8/6.mdx index c644a5c50..4dfbedb21 100644 --- a/chapters/ja/chapter8/6.mdx +++ b/chapters/ja/chapter8/6.mdx @@ -1,5 +1,10 @@ # パート2終了! + + お疲れ様でした。第2部を無事に完了しました!今は第3部について働いてるので情報を見逃せないように我々の[ニュースレター](https://huggingface.curated.co/)に応募して下さい! 今は、様々なNLPタスクに取り組み、その上でモデルを微調整またはプリトレーニングできるようになったはずです!その結果を [モデルハブ] (https://huggingface.co/models) でコミュニティと共有することを忘れないでください。 diff --git a/chapters/ko/chapter1/1.mdx b/chapters/ko/chapter1/1.mdx index 3bfb81f49..6c6bf2410 100644 --- a/chapters/ko/chapter1/1.mdx +++ b/chapters/ko/chapter1/1.mdx @@ -1,5 +1,10 @@ # 단원 소개 + + ## 🤗 강의 수강생 여러분 환영합니다! diff --git a/chapters/ko/chapter1/10.mdx b/chapters/ko/chapter1/10.mdx index a05bd4c3b..e6fbd2a0e 100644 --- a/chapters/ko/chapter1/10.mdx +++ b/chapters/ko/chapter1/10.mdx @@ -2,6 +2,11 @@ # 단원 마무리 퀴즈 + + 이번 챕터에서는 정말 많은 내용들을 다뤘습니다! 그러니 모든 세부 사항을 다 이해하지 못했다고 해서 좌절하지 마세요. 다음 챕터에서 다루는 내용은 내부 작동 방식을 이해하는 데에 도움이 될거에요. 그래도 우선, 이번 챕터에서 배운 내용에 대해 확인해보는 시간을 갖도록 하겠습니다! diff --git a/chapters/ko/chapter1/2.mdx b/chapters/ko/chapter1/2.mdx index ca5d62dc8..de07c0794 100644 --- a/chapters/ko/chapter1/2.mdx +++ b/chapters/ko/chapter1/2.mdx @@ -1,5 +1,10 @@ # 자연어 처리(Natural Language Processing) + + 트랜스포머 모델을 공부하기에 앞서 자연어 처리(NLP)가 무엇인지, 그리고 왜 NLP가 중요한지 빠르고 간단하게 살펴보겠습니다. ## NLP가 무엇인가요? diff --git a/chapters/ko/chapter1/3.mdx b/chapters/ko/chapter1/3.mdx index f32892430..0ece2c2eb 100644 --- a/chapters/ko/chapter1/3.mdx +++ b/chapters/ko/chapter1/3.mdx @@ -1,8 +1,8 @@ # 트랜스포머로 무엇을 할 수 있나요? - diff --git a/chapters/ko/chapter1/4.mdx b/chapters/ko/chapter1/4.mdx index 96456fab5..9bb7ca6bb 100644 --- a/chapters/ko/chapter1/4.mdx +++ b/chapters/ko/chapter1/4.mdx @@ -1,5 +1,10 @@ # 트랜스포머는 어떻게 동작하나요? + + 이번 단원에서는 트랜스포머(Transformer) 모델의 대략적인 구조를 알아보겠습니다. ## 트랜스포머 역사 훑어보기 diff --git a/chapters/ko/chapter1/5.mdx b/chapters/ko/chapter1/5.mdx index 3c133aea8..7c7f3f113 100644 --- a/chapters/ko/chapter1/5.mdx +++ b/chapters/ko/chapter1/5.mdx @@ -1,5 +1,10 @@ # 인코더 모델 + + 인코더 모델(Encoder models)은 트랜스포머 모델의 인코더만 사용합니다. 각각의 단계에서 어텐션 레이어는 초기 문장의 모든 단어에 액세스 할 수 있습니다. 이러한 모델은 “양방향성(bi-directional)” 어텐션을 지닌 특성이 있다고도 하며 *자동 인코딩(auto-enoding) 모델*이라고 부릅니다. diff --git a/chapters/ko/chapter1/6.mdx b/chapters/ko/chapter1/6.mdx index 121403b4d..30482ca4a 100644 --- a/chapters/ko/chapter1/6.mdx +++ b/chapters/ko/chapter1/6.mdx @@ -1,5 +1,10 @@ # 디코더 모델 + + 디코더 모델(Decoder models)은 트랜스포머 모델의 디코더만 사용합니다. 각각의 단계마다, 어텐션 레이어는 주어진 단어에 대해 문장 내에서 해당 단어 앞에 위치한 단어들에 대해서만 액세스 할 수 있습니다. 이러한 모델을 *자동 회귀(auto-regressive) 모델*이라고 부릅니다. diff --git a/chapters/ko/chapter1/7.mdx b/chapters/ko/chapter1/7.mdx index 98aad86e9..4acfad2d7 100644 --- a/chapters/ko/chapter1/7.mdx +++ b/chapters/ko/chapter1/7.mdx @@ -1,5 +1,10 @@ # 시퀀스-투-시퀀스 모델 + + 인코더-디코더 모델(Encoder-decoder models) (*시퀀스-투-시퀀스 모델(sequence-to-sequence models)*로 부르기도 합니다)은 트랜스포머 구조의 인코더, 디코더 둘을 모두 사용합니다. 각 단계마다, 인코더의 어텐션 레이어는 초기 문장의 모든 단어에 액세스 할 수 있는 반면, 디코더의 어텐션 레이어는 주어진 단어 앞에 위치한 단어들에만 액세스 할 수 있습니다. diff --git a/chapters/ko/chapter1/8.mdx b/chapters/ko/chapter1/8.mdx index edbd32548..722a864ae 100644 --- a/chapters/ko/chapter1/8.mdx +++ b/chapters/ko/chapter1/8.mdx @@ -1,8 +1,8 @@ # 편향과 한계 - diff --git a/chapters/ko/chapter1/9.mdx b/chapters/ko/chapter1/9.mdx index 191b5654d..9fee845cc 100644 --- a/chapters/ko/chapter1/9.mdx +++ b/chapters/ko/chapter1/9.mdx @@ -1,5 +1,10 @@ # 단원 정리 + + 이번 단원에서는 🤗 Transformers의 하이레벨 함수인 `pipeline()` 를 사용하여 다양한 NLP 문제에 대한 접근 방식을 배웠습니다. 그리고 Hub에서 모델을 검색하여 사용하는 방법, 추론 API를 이용해 브라우저 상에서 바로 모델을 테스트 하는 방법 또한 알아보았습니다. 지금까지 트랜스포머 모델의 대략작인 동작 방식과, 전이 학습(transfer learning) 및 미세 조정(fine-tuning)의 중요성에 알아보았습니다. 핵심은 어떤 문제를 풀고싶냐에 따라 전체 모델 구조를 다 사용하거나 인코더, 디코더만 사용할 수도 있다는 것입니다. 아래 표는 이를 요약해서 보여주고 있습니다: diff --git a/chapters/pt/chapter1/1.mdx b/chapters/pt/chapter1/1.mdx index aaf99a847..350a2dea9 100644 --- a/chapters/pt/chapter1/1.mdx +++ b/chapters/pt/chapter1/1.mdx @@ -1,5 +1,10 @@ # Introdução + + ## Bem-vindo(a) ao Curso 🤗! diff --git a/chapters/pt/chapter1/10.mdx b/chapters/pt/chapter1/10.mdx index e1a79c2ce..7c488f9e9 100644 --- a/chapters/pt/chapter1/10.mdx +++ b/chapters/pt/chapter1/10.mdx @@ -2,6 +2,11 @@ # Questionário de fim de capítulo + + Este capítulo cobriu muito terreno! Não se preocupe se você não entendeu todos os detalhes; os próximos capítulos o ajudarão a entender como as coisas funcionam debaixo do capô. Primeiro, porém, vamos testar o que você aprendeu neste capítulo! diff --git a/chapters/pt/chapter1/2.mdx b/chapters/pt/chapter1/2.mdx index a3413a2a8..d61decf13 100644 --- a/chapters/pt/chapter1/2.mdx +++ b/chapters/pt/chapter1/2.mdx @@ -1,5 +1,10 @@ # Processamento de Linguagem Natural (NLP) + + Antes de irmos direto para os modelos Transformers, vamos fazer um rápido esboço sobre o que é processamento de linguagem natural e o porquê nós nos importamos com isso. ## O que é NLP? diff --git a/chapters/pt/chapter1/3.mdx b/chapters/pt/chapter1/3.mdx index 254d83372..03dcc6c46 100644 --- a/chapters/pt/chapter1/3.mdx +++ b/chapters/pt/chapter1/3.mdx @@ -1,8 +1,8 @@ # Transformers, o que eles podem fazer? - diff --git a/chapters/pt/chapter1/4.mdx b/chapters/pt/chapter1/4.mdx index f9b9e1ce8..3e3e39754 100644 --- a/chapters/pt/chapter1/4.mdx +++ b/chapters/pt/chapter1/4.mdx @@ -1,5 +1,10 @@ # Como os Transformers trabalham? + + Nessa seção, nós olharemos para o alto nível de arquitetura dos modelos Transformers. ## Um pouco da história dos Transformers diff --git a/chapters/pt/chapter1/5.mdx b/chapters/pt/chapter1/5.mdx index 4ab25d749..f14bb0ac2 100644 --- a/chapters/pt/chapter1/5.mdx +++ b/chapters/pt/chapter1/5.mdx @@ -1,5 +1,10 @@ # Modelos decodificadores + + 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*. diff --git a/chapters/pt/chapter1/6.mdx b/chapters/pt/chapter1/6.mdx index 0d6e1cb15..4922350c6 100644 --- a/chapters/pt/chapter1/6.mdx +++ b/chapters/pt/chapter1/6.mdx @@ -1,5 +1,10 @@ # Modelos decodificadores + + Os modelos de decodificador usam apenas o decodificador de um modelo Transformer. Em cada etapa, para uma determinada palavra, as camadas de atenção só podem acessar as palavras posicionadas antes dela na frase. Esses modelos geralmente são chamados de _modelos auto-regressivos_. O pré-treinamento de modelos de decodificadores geralmente gira em torno de prever a próxima palavra na frase. diff --git a/chapters/pt/chapter1/7.mdx b/chapters/pt/chapter1/7.mdx index 695359a16..c65c3e2ce 100644 --- a/chapters/pt/chapter1/7.mdx +++ b/chapters/pt/chapter1/7.mdx @@ -1,5 +1,10 @@ # Modelos sequência a sequência + + Modelos encoder-decoder (também chamados de modelos _sequence-to-sequence_) usam ambas as partes da arquitetura Transformer. Em cada estágio, as camadas de atenção do codificador podem acessar todas as palavras da frase inicial, enquanto as camadas de atenção do decodificador podem acessar apenas as palavras posicionadas antes de uma determinada palavra na entrada. O pré-treinamento desses modelos pode ser feito usando os objetivos dos modelos de codificador ou decodificador, mas geralmente envolve algo um pouco mais complexo. Por exemplo, [T5](https://huggingface.co/t5-base) é pré-treinado substituindo trechos aleatórios de texto (que podem conter várias palavras) por uma única palavra especial de máscara, e o objetivo é prever o texto que esta palavra de máscara substitui. diff --git a/chapters/pt/chapter1/8.mdx b/chapters/pt/chapter1/8.mdx index e01fd445a..386d8a73c 100644 --- a/chapters/pt/chapter1/8.mdx +++ b/chapters/pt/chapter1/8.mdx @@ -1,6 +1,6 @@ # Vieses e limitações - + Se sua intenção é usar um modelo pré-treinado ou uma versão ajustada em produção, esteja ciente de que, embora esses modelos sejam ferramentas poderosas, eles vêm com limitações. A maior delas é que, para possibilitar o pré-treinamento em grandes quantidades de dados, os pesquisadores muitas vezes raspam todo o conteúdo que encontram, tirando o melhor e o pior do que está disponível na internet. diff --git a/chapters/pt/chapter1/9.mdx b/chapters/pt/chapter1/9.mdx index 7398a7fc6..4be0bccb1 100644 --- a/chapters/pt/chapter1/9.mdx +++ b/chapters/pt/chapter1/9.mdx @@ -1,5 +1,10 @@ # Resumo + + Nesse capítulo, você viu como abordar diferentes tarefas de NLP usando a função de alto nível `pipeline()` da biblioteca 🤗 Transformers. Você também viu como pesquisar e usar modelos no Hub, bem como usar a API de inferência para testar os modelos diretamente em seu navegador. Discutimos como os modelos Transformers funcionam em alto nível e falamos sobre a importância do aprendizado de transferência (transfer learning) e do ajuste fino. Um aspecto chave é que você pode usar a arquitetura completa ou apenas o codificador ou decodificador, dependendo do tipo de tarefa que você pretende resolver. A tabela a seguir resume isso: diff --git a/chapters/pt/chapter2/1.mdx b/chapters/pt/chapter2/1.mdx index 4bc8850d5..3d3b8e83e 100644 --- a/chapters/pt/chapter2/1.mdx +++ b/chapters/pt/chapter2/1.mdx @@ -1,5 +1,10 @@ # Introdução + + Como você viu no [Capitulo 1](/course/pt/chapter1), normalmente modelos Transformers são muito grandes. Com milhões a dezenas de *bilhões* de parâmetros, o treinamento e o deploy destes modelos é uma tarefa complicado. Além disso, com novos modelos sendo lançados quase diariamente e cada um tendo sua própria implementação, experimentá-los a todos não é tarefa fácil. A biblioteca 🤗 Transformers foi criado para resolver este problema. Seu objetivo é fornecer uma API única através do qual qualquer modelo Transformer possa ser carregado, treinado e salvo. As principais características da biblioteca são: diff --git a/chapters/pt/chapter2/2.mdx b/chapters/pt/chapter2/2.mdx index 88c9a068e..6ee9e9ace 100644 --- a/chapters/pt/chapter2/2.mdx +++ b/chapters/pt/chapter2/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/pt/chapter2/3.mdx b/chapters/pt/chapter2/3.mdx index 634cc3a6e..b3c94725d 100644 --- a/chapters/pt/chapter2/3.mdx +++ b/chapters/pt/chapter2/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/pt/chapter2/4.mdx b/chapters/pt/chapter2/4.mdx index 7b2f70ff6..1134ad8d6 100644 --- a/chapters/pt/chapter2/4.mdx +++ b/chapters/pt/chapter2/4.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/pt/chapter2/5.mdx b/chapters/pt/chapter2/5.mdx index 9b45c2c47..0caf41234 100644 --- a/chapters/pt/chapter2/5.mdx +++ b/chapters/pt/chapter2/5.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/pt/chapter2/6.mdx b/chapters/pt/chapter2/6.mdx index a1ba25c38..0b65e1405 100644 --- a/chapters/pt/chapter2/6.mdx +++ b/chapters/pt/chapter2/6.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/pt/chapter2/7.mdx b/chapters/pt/chapter2/7.mdx index 2b2f1df5a..178249fea 100644 --- a/chapters/pt/chapter2/7.mdx +++ b/chapters/pt/chapter2/7.mdx @@ -1,5 +1,10 @@ # Uso básico concluído! + + Ótimo trabalho seguindo o curso até aqui! Recapitulando, neste capítulo, você: - Aprendeu os elementos básicos de um modelo Transformer. diff --git a/chapters/pt/chapter2/8.mdx b/chapters/pt/chapter2/8.mdx index 00a283ad3..a8d9b20d5 100644 --- a/chapters/pt/chapter2/8.mdx +++ b/chapters/pt/chapter2/8.mdx @@ -4,6 +4,11 @@ # Questionário de fim de capítulo + + ### 1. Qual é a ordem do pipeline para a modelagem de linguagem? + O [Hugging Face Hub](https://huggingface.co/) –- nosso site principal –- é uma plataforma central que permite a qualquer pessoa descobrir, usar e contribuir com novos modelos e datasets do estado da arte. Ele hospeda uma grande variedade de modelos, com mais de 10.000 disponíveis publicamente. Vamos nos concentrar nos modelos deste capítulo, e daremos uma olhada nos conjuntos de dados do Capítulo 5. Os modelos no Hub não estão limitados a 🤗 Transformers ou mesmo NLP. Há modelos da [Flair](https://github.com/flairNLP/flair) e [AllenNLP](https://github.com/allenai/allennlp) para NLP, [Asteroid](https://github.com/asteroid-team/asteroid) e [pyannote](https://github.com/pyannote/pyannote-audio) para discurso, e [timm](https://github.com/rwightman/pytorch-image-models) para visão, para citar alguns. diff --git a/chapters/pt/chapter4/2.mdx b/chapters/pt/chapter4/2.mdx index 7065e8696..4f190231e 100644 --- a/chapters/pt/chapter4/2.mdx +++ b/chapters/pt/chapter4/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/pt/chapter4/3.mdx b/chapters/pt/chapter4/3.mdx index 0a84b09dd..98e996143 100644 --- a/chapters/pt/chapter4/3.mdx +++ b/chapters/pt/chapter4/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/pt/chapter4/4.mdx b/chapters/pt/chapter4/4.mdx index 89ce37719..111d99016 100644 --- a/chapters/pt/chapter4/4.mdx +++ b/chapters/pt/chapter4/4.mdx @@ -1,5 +1,10 @@ # Construindo um cartão para o modelo + + O cartão para o modelo (model card) é um arquivo que é discutivelmente tão importante quanto os arquivos modelo e tokenizer em um repositório modelo. É a definição central do modelo, garantindo a reutilização pelos membros da comunidade e a reprodutibilidade dos resultados, e fornecendo uma plataforma sobre a qual outros membros podem construir seus artefatos. Documentar o processo de treinamento e avaliação ajuda outros a entender o que esperar de um modelo - e fornecer informações suficientes sobre os dados que foram utilizados e o pré e pós-processamento que foram feitos garante que as limitações, enviesamentos e contextos nos quais o modelo é e não é útil possam ser identificados e compreendidos. diff --git a/chapters/pt/chapter4/5.mdx b/chapters/pt/chapter4/5.mdx index 85bb3afb9..c5ced8108 100644 --- a/chapters/pt/chapter4/5.mdx +++ b/chapters/pt/chapter4/5.mdx @@ -1,5 +1,10 @@ # Parte 1 completa! + + Este é o fim da primeira parte do curso! A segunda parte será lançada em 15 de novembro com um grande evento comunitário, veja mais informações [aqui](https://huggingface.co/blog/course-launch-event). Agora você deverá ser capaz de afinar um modelo pré-treinado sobre um problema de classificação de texto (frases simples ou pares de frases) e carregar o resultado para o Model Hub. Para garantir que você domine esta primeira seção, você deve fazer exatamente isso em um problema que lhe interesse (e não necessariamente em inglês se você falar outro idioma)! Você pode encontrar ajuda nos [fóruns Hugging Face](https://discuss.huggingface.co/) e compartilhar seu projeto em [este tópico](https://discuss.huggingface.co/t/share-your-projects/6803) uma vez terminado. diff --git a/chapters/pt/chapter4/6.mdx b/chapters/pt/chapter4/6.mdx index 717de1b1f..b78ba4f38 100644 --- a/chapters/pt/chapter4/6.mdx +++ b/chapters/pt/chapter4/6.mdx @@ -4,6 +4,11 @@ # Questionário de fim de capítulo + + Vamos testar o que você aprendeu neste capítulo! ### 1. A que se limitam os modelos no Hub? diff --git a/chapters/pt/chapter5/1.mdx b/chapters/pt/chapter5/1.mdx index 780a5b770..40de500fc 100644 --- a/chapters/pt/chapter5/1.mdx +++ b/chapters/pt/chapter5/1.mdx @@ -1,5 +1,10 @@ # Introdução + + No [Capítulo 3](/course/chapter3) você teve seu primeiro gostinho da biblioteca 🤗 Datasets e viu que havia três passos principais quando se tratava de treinar para melhorar (fine-tuning) um modelo: 1. Carregar um conjunto de dados (dataset) do Hugging Face Hub. diff --git a/chapters/pt/chapter5/2.mdx b/chapters/pt/chapter5/2.mdx index 741ef7450..2ad037958 100644 --- a/chapters/pt/chapter5/2.mdx +++ b/chapters/pt/chapter5/2.mdx @@ -1,8 +1,8 @@ # E se o meu dataset não estiver no Hub? - diff --git a/chapters/pt/chapter5/3.mdx b/chapters/pt/chapter5/3.mdx index 2c65a36c9..7fbc29618 100644 --- a/chapters/pt/chapter5/3.mdx +++ b/chapters/pt/chapter5/3.mdx @@ -1,8 +1,8 @@ # Hora de fatiar e dividir os dados - diff --git a/chapters/pt/chapter5/4.mdx b/chapters/pt/chapter5/4.mdx index fc1f3f92e..aa63e3003 100644 --- a/chapters/pt/chapter5/4.mdx +++ b/chapters/pt/chapter5/4.mdx @@ -1,8 +1,8 @@ # Big data? 🤗 Datasets ao resgate - diff --git a/chapters/pt/chapter5/5.mdx b/chapters/pt/chapter5/5.mdx index be0219602..ca931925e 100644 --- a/chapters/pt/chapter5/5.mdx +++ b/chapters/pt/chapter5/5.mdx @@ -1,8 +1,8 @@ # Criando seu próprio dataset - diff --git a/chapters/pt/chapter5/6.mdx b/chapters/pt/chapter5/6.mdx index 3ced95ff6..6b5d6c61d 100644 --- a/chapters/pt/chapter5/6.mdx +++ b/chapters/pt/chapter5/6.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/pt/chapter5/7.mdx b/chapters/pt/chapter5/7.mdx index e83ba6a81..5e054aeed 100644 --- a/chapters/pt/chapter5/7.mdx +++ b/chapters/pt/chapter5/7.mdx @@ -1,5 +1,10 @@ # Confira o 🤗 Datasets! + + Bem, esse foi um belo passeio pela biblioteca 🤗 Datasets - parabéns por chegar até aqui! Com o conhecimento que você adquiriu neste capítulo, você deve ser capaz de: - Carregue conjuntos de dados de qualquer lugar, seja o Hugging Face Hub, seu laptop ou um servidor remoto em sua empresa. diff --git a/chapters/pt/chapter5/8.mdx b/chapters/pt/chapter5/8.mdx index 152c191e5..51a04768b 100644 --- a/chapters/pt/chapter5/8.mdx +++ b/chapters/pt/chapter5/8.mdx @@ -1,6 +1,11 @@ # Questionário de fim de capítulo + + Este capítulo cobriu muita coisa! Não se preocupe se você não entendeu todos os detalhes; os próximos capítulos o ajudarão a entender como as coisas funcionam. Antes de prosseguir, vamos testar o que você aprendeu neste capítulo. diff --git a/chapters/pt/chapter6/1.mdx b/chapters/pt/chapter6/1.mdx index 0fb3099dc..d2eac43c8 100644 --- a/chapters/pt/chapter6/1.mdx +++ b/chapters/pt/chapter6/1.mdx @@ -1,5 +1,10 @@ # Introdução + + No [Capítulo 3](/course/chapter3), nós estudamos como realizar o ajuste fino em um modelo para uma dada tarefa. Quando nós fazemos isso, usamos o mesmo tokenizador utilizado pelo modelo pré-treinado -- mas o que podemos fazer quando queremos treinar um modelo do início? Nestes casos, utilizar um tokenizador que foi pré-treinado em um corpus de outro domínio ou linguagem é tipicamente subótimo. Por exemplo, um tokenizador que é treinado em um corpus de lingua inglesa terá um desempenho ruim em um corpus de textos em japonês, visto que o uso de espaços e pontuações é muito diferente nestes dois idiomas. Neste capítulo, você aprenderá como treinar um novo tokenizador em um corpus de textos, para então ser usado no treinamento de um modelo de linguagem. Isto tudo será feito com ajuda da biblioteca [🤗 Tokenizers](https://github.com/huggingface/tokenizers), que provê o tokenizador rápido na biblioteca [🤗 Transformers](https://github.com/huggingface/transformers). Daremos uma olhada a fundo sobre as funcionalidades oferecidas pela biblioteca, e explorar como os tokenizadores rápidos diferem das versões "lentas". diff --git a/chapters/pt/chapter6/2.mdx b/chapters/pt/chapter6/2.mdx index 049ca184d..7399e9d68 100644 --- a/chapters/pt/chapter6/2.mdx +++ b/chapters/pt/chapter6/2.mdx @@ -1,8 +1,8 @@ # Treinando um novo tokenizador - diff --git a/chapters/pt/chapter6/3.mdx b/chapters/pt/chapter6/3.mdx index b79732841..540982222 100644 --- a/chapters/pt/chapter6/3.mdx +++ b/chapters/pt/chapter6/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/pt/chapter7/1.mdx b/chapters/pt/chapter7/1.mdx index b95dc7580..65009e31a 100644 --- a/chapters/pt/chapter7/1.mdx +++ b/chapters/pt/chapter7/1.mdx @@ -2,6 +2,11 @@ # Introdução + + No [Capítulo 3](/course/chapter3), você viu como fazer o ajuste fino (fine-tune) de um modelo de classificação de texto. Neste capítulo, abordaremos as seguintes tarefas de NLP (também conhecido como PLN): - Classificação dos Tokens diff --git a/chapters/pt/chapter8/1.mdx b/chapters/pt/chapter8/1.mdx index 2500a85c8..972e02c53 100644 --- a/chapters/pt/chapter8/1.mdx +++ b/chapters/pt/chapter8/1.mdx @@ -1,5 +1,10 @@ # Introdução + + Agora que você sabe como lidar com as tarefas mais comuns de PNL com 🤗 Transformers, você deve ser capaz de começar seus próprios projetos! Neste capítulo, exploraremos o que fazer quando você encontrar um problema. Você aprenderá como debugar com sucesso seu código ou seu treino e como pedir ajuda à comunidade se não conseguir resolver o problema sozinho. E se você acha que encontrou um bug em uma das bibliotecas do Hugging Face, mostraremos a melhor maneira de informa-lo para que o problema seja resolvido o mais rápido possível. Mais precisamente, neste capítulo você aprenderá: diff --git a/chapters/pt/chapter8/2.mdx b/chapters/pt/chapter8/2.mdx index 75a68bb0d..ee380cab5 100644 --- a/chapters/pt/chapter8/2.mdx +++ b/chapters/pt/chapter8/2.mdx @@ -1,8 +1,8 @@ # O que fazer quando ocorrer um erro - diff --git a/chapters/pt/chapter8/3.mdx b/chapters/pt/chapter8/3.mdx index c738682e5..2ac0b8374 100644 --- a/chapters/pt/chapter8/3.mdx +++ b/chapters/pt/chapter8/3.mdx @@ -1,7 +1,7 @@ # Pedindo ajuda nos fóruns - diff --git a/chapters/ru/chapter1/1.mdx b/chapters/ru/chapter1/1.mdx index 5e6e49ee2..6d5e1cd3b 100644 --- a/chapters/ru/chapter1/1.mdx +++ b/chapters/ru/chapter1/1.mdx @@ -1,5 +1,10 @@ # Введение + + ## Добро пожаловать на 🤗 курс! diff --git a/chapters/ru/chapter1/2.mdx b/chapters/ru/chapter1/2.mdx index abc6d6c2d..f265cabca 100644 --- a/chapters/ru/chapter1/2.mdx +++ b/chapters/ru/chapter1/2.mdx @@ -1,5 +1,10 @@ # Обработка естесственного языка + + Прежде, чем перейти к трансформерам, сделаем быстрый обзор того, что такое обработка естесственного языка (NLP) и почему мы заинтересованы в этой сфере. ## Что такое NLP? diff --git a/chapters/ru/chapter1/3.mdx b/chapters/ru/chapter1/3.mdx index 2f28dc98a..be6579002 100644 --- a/chapters/ru/chapter1/3.mdx +++ b/chapters/ru/chapter1/3.mdx @@ -1,8 +1,8 @@ # Трансформеры, на что они способны? - diff --git a/chapters/ru/chapter1/4.mdx b/chapters/ru/chapter1/4.mdx index 3c901acd4..a817d124c 100644 --- a/chapters/ru/chapter1/4.mdx +++ b/chapters/ru/chapter1/4.mdx @@ -1,5 +1,10 @@ # Как работают трансформеры? + + В этом разделе мы посмотрим в общих чертах на то, как работают трансфореры. ## Немного истории diff --git a/chapters/ru/chapter1/5.mdx b/chapters/ru/chapter1/5.mdx index 7e4dc8b1a..1cc0839a3 100644 --- a/chapters/ru/chapter1/5.mdx +++ b/chapters/ru/chapter1/5.mdx @@ -1,5 +1,10 @@ # Модели энкодеров + + Энкодеры используют только компонент кодировщика трансформера. На каждом этапе слой внимания может использовать все слова исходного предложения. Эти модели часто характеризуют как имеющие двунаправленное внимание ("bi-directional attention"), и часто называют моделями *автоэнкодеров*. diff --git a/chapters/ru/chapter1/6.mdx b/chapters/ru/chapter1/6.mdx index d9ca11089..2a43f54ce 100644 --- a/chapters/ru/chapter1/6.mdx +++ b/chapters/ru/chapter1/6.mdx @@ -1,5 +1,10 @@ # Модели декодеров + + Декодировщики используют только компонент декодер трансформера. На каждом этапе для текущего слова слой внимания может получить доступ только к словам, которые были расположены до текущего в предложении. Такие модели часто называются *авторегрессионными моделями*. diff --git a/chapters/ru/chapter1/7.mdx b/chapters/ru/chapter1/7.mdx index 730b8f96f..86be51145 100644 --- a/chapters/ru/chapter1/7.mdx +++ b/chapters/ru/chapter1/7.mdx @@ -1,5 +1,10 @@ # Модели вида "seq2seq" + + Энкодер-декодер модели (также называемые *sequence-to-sequence models*) используют обе части трансформера. На каждом этапе слой внимания энкодера получает доступ ко всем словам в исходной последовательности, тогда как слой внимания декодера получает доступ только к тем словам, которые позиционированы до текущего слова. diff --git a/chapters/ru/chapter1/8.mdx b/chapters/ru/chapter1/8.mdx index 05d3b724d..df58fe8be 100644 --- a/chapters/ru/chapter1/8.mdx +++ b/chapters/ru/chapter1/8.mdx @@ -1,8 +1,8 @@ # Предвзятости и ограничения - diff --git a/chapters/ru/chapter1/9.mdx b/chapters/ru/chapter1/9.mdx index 730712e4e..f808b04d0 100644 --- a/chapters/ru/chapter1/9.mdx +++ b/chapters/ru/chapter1/9.mdx @@ -1,5 +1,10 @@ # Итоги + + В этой главе вы увидели, как подходить к различным задачам NLP, используя высокоуровневую функцию `pipeline()` из библиотеки 🤗 Transformers. Вы также увидели, как искать и использовать модели в Hub, а также как использовать Inference API для тестирования моделей прямо в браузере. Мы обсудили, как трансформеры работают на высоком уровне, и поговорили о важности трансферного обучения и тонкой настройки. Ключевым аспектом является то, что вы можете использовать всю архитектуру или только энкодер или декодер, в зависимости от того, какую задачу вы хотите решить. Следующая таблица резюмирует это: diff --git a/chapters/ru/chapter2/1.mdx b/chapters/ru/chapter2/1.mdx index 99dbd07b6..47421460e 100644 --- a/chapters/ru/chapter2/1.mdx +++ b/chapters/ru/chapter2/1.mdx @@ -1,5 +1,10 @@ # Введение + + Как вы могли заметить в [Главе 1](/course/chapter1), модели трансформеров обычно бывают очень большие. Обучение и развертывание таких моделей с миллионами и даже десятками *миллиардов* параметров является сложной задачей. Кроме того, новые модели выпускаются почти ежедневно, и каждая из них имеет собственную реализацию, опробовать их все — непростая задача. Библиотека 🤗 Transformers была создана для решения этой проблемы. Её цель — предоставить единый API, с помощью которого можно загружать, обучать и сохранять любую модель трансформера. Основными функциями библиотеки являются: diff --git a/chapters/ru/chapter2/2.mdx b/chapters/ru/chapter2/2.mdx index 85ce9cbd5..04fc473d1 100644 --- a/chapters/ru/chapter2/2.mdx +++ b/chapters/ru/chapter2/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/ru/chapter2/3.mdx b/chapters/ru/chapter2/3.mdx index 080fd385f..6499af197 100644 --- a/chapters/ru/chapter2/3.mdx +++ b/chapters/ru/chapter2/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/ru/chapter2/7.mdx b/chapters/ru/chapter2/7.mdx index 99bf935fb..708d47430 100644 --- a/chapters/ru/chapter2/7.mdx +++ b/chapters/ru/chapter2/7.mdx @@ -1,5 +1,10 @@ # Базовое использование завершено! + + Отличная работа, вы прошли курс до текущего момента! Напомним, что в этой главе вы: - Изучил основные строительные блоки модели Transformer. diff --git a/chapters/ru/chapter3/1.mdx b/chapters/ru/chapter3/1.mdx index 10ed44252..476152eb3 100644 --- a/chapters/ru/chapter3/1.mdx +++ b/chapters/ru/chapter3/1.mdx @@ -2,6 +2,11 @@ # Введение + + В [главе 2](/course/ru/chapter2) мы увидели, как можно использовать токенизаторы и предобученные модели для построения предсказаний. Но что если мы хотим дообучить предобученную модель на собственном датасете? Это и есть тема данной главы! Мы изучим: {#if fw === 'pt'} diff --git a/chapters/ru/chapter3/2.mdx b/chapters/ru/chapter3/2.mdx index 4d8ed067e..313cdfcac 100644 --- a/chapters/ru/chapter3/2.mdx +++ b/chapters/ru/chapter3/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/ru/chapter3/3.mdx b/chapters/ru/chapter3/3.mdx index 5ff79c1ed..17e2155f0 100644 --- a/chapters/ru/chapter3/3.mdx +++ b/chapters/ru/chapter3/3.mdx @@ -2,9 +2,9 @@ # Fine-tuning модели с использованием Trainer API - diff --git a/chapters/ru/chapter3/3_tf.mdx b/chapters/ru/chapter3/3_tf.mdx index a3b1f7ef6..1b6393c44 100644 --- a/chapters/ru/chapter3/3_tf.mdx +++ b/chapters/ru/chapter3/3_tf.mdx @@ -2,9 +2,9 @@ # Fine-tuning модели с использованием Keras - diff --git a/chapters/ru/chapter3/4.mdx b/chapters/ru/chapter3/4.mdx index 15568df81..9a0e473ea 100644 --- a/chapters/ru/chapter3/4.mdx +++ b/chapters/ru/chapter3/4.mdx @@ -1,8 +1,8 @@ # Полное обучение - diff --git a/chapters/ru/chapter3/5.mdx b/chapters/ru/chapter3/5.mdx index eb571700d..fed0ace33 100644 --- a/chapters/ru/chapter3/5.mdx +++ b/chapters/ru/chapter3/5.mdx @@ -2,6 +2,11 @@ # Fine-tuning, итоги! + + Это было весело! В первых двух главах вы узнали о моделях и токенизаторах, и теперь вы знаете как применить fine-tuning на собственных данных. Напомним, в этой главе вы: diff --git a/chapters/ru/chapter3/6.mdx b/chapters/ru/chapter3/6.mdx index b4a492a51..bef136a13 100644 --- a/chapters/ru/chapter3/6.mdx +++ b/chapters/ru/chapter3/6.mdx @@ -4,6 +4,11 @@ # Итоговый тест по главе + + Тест по результатам изучения главы 3. ### Датасет `emotion` содержит сообщения из Твиттера, каждое сообщение помечено какой-либо эмоцией. Найдите его на [Hub](https://huggingface.co/datasets) и изучить карточку датасета. Какая из этих эмоцией не является базовой? diff --git a/chapters/ru/chapter4/1.mdx b/chapters/ru/chapter4/1.mdx index 8a5829bce..402368166 100644 --- a/chapters/ru/chapter4/1.mdx +++ b/chapters/ru/chapter4/1.mdx @@ -1,5 +1,10 @@ # Hugging Face Hub + + [Hugging Face Hub](https://huggingface.co/) -- наш главный сайт -- центральная платформа, позволяющая всем изучить, применить и внести свой вклад в SOTA (state-of=the-art) модели и наборы данных. Здесь хранится множество разнообразных моделей, среди которых больше 10000 доступны для использования. В этой главе мы сконцентрируемся на моделях, а в главе 5 обратим внимание на датасеты. Модели из Hugging Face Hub - это не только трансформеры или модели из области NLP. Здесь присутствуют модели [Flair](https://github.com/flairNLP/flair) и [AllenNLP](https://github.com/allenai/allennlp) для NLP, речевые модели [Asteroid](https://github.com/asteroid-team/asteroid) и [pyannote](https://github.com/pyannote/pyannote-audio), [timm](https://github.com/rwightman/pytorch-image-models) для компьютерного зрения. diff --git a/chapters/ru/chapter4/2.mdx b/chapters/ru/chapter4/2.mdx index df15ba630..f6650e03f 100644 --- a/chapters/ru/chapter4/2.mdx +++ b/chapters/ru/chapter4/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/ru/chapter4/3.mdx b/chapters/ru/chapter4/3.mdx index 601aa06b5..22d5b956c 100644 --- a/chapters/ru/chapter4/3.mdx +++ b/chapters/ru/chapter4/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/ru/chapter4/4.mdx b/chapters/ru/chapter4/4.mdx index ba506976f..500eae310 100644 --- a/chapters/ru/chapter4/4.mdx +++ b/chapters/ru/chapter4/4.mdx @@ -1,5 +1,10 @@ # Создание карточки модели + + Карточка модели — это файл, который, возможно, так же важен, как файлы модели и токенизатора в репозитории моделей. Это центральное описание модели, обеспечивающее возможность повторного использования другими членами сообщества и воспроизводимость результатов, а также предоставляющее платформу для других участников. Документирование процесса обучения и оценки помогает другим понять, чего ожидать от модели, а предоставление достаточной информации об использованных данных, а также о проведенной предварительной и постобработке. По карточке модели ясны ограничения, предубеждения и контексты, в которых модель может быть полезна, а в каких случаях окажется бесполезной. diff --git a/chapters/ru/chapter4/5.mdx b/chapters/ru/chapter4/5.mdx index 58ab3e1ac..7bad05060 100644 --- a/chapters/ru/chapter4/5.mdx +++ b/chapters/ru/chapter4/5.mdx @@ -1,5 +1,10 @@ # Первая часть завершена! + + Вот и закончилась первая часть курса! Часть 2 будет выпущена 15 ноября вместе с большим событием для сообщества, дополнительную информацию см. [здесь] (https://huggingface.co/blog/course-launch-event). Теперь вы сможете точно настроить предварительно обученную модель для задачи классификации текста (одно предложение или пара предложений) и загрузить результат в Model Hub. Чтобы убедиться, что вы усвоили этот первый раздел, вы должны сделать именно это по интересующей вас проблеме (и не обязательно на английском языке, если вы говорите на другом языке)! Вы можете найти помощь на [форумах Hugging Face](https://discuss.huggingface.co/) и поделиться своим проектом в [этой теме](https://discuss.huggingface.co/t/share-your-projects /6803)! diff --git a/chapters/ru/chapter4/6.mdx b/chapters/ru/chapter4/6.mdx index ccbf6425f..7fd56d55d 100644 --- a/chapters/ru/chapter4/6.mdx +++ b/chapters/ru/chapter4/6.mdx @@ -4,6 +4,11 @@ # Итоговый тест по главе + + Проверим, что вы усвоили в результате изучения данной главы! ### 1. Чем ограничиваются модели с Hub? diff --git a/chapters/th/chapter1/1.mdx b/chapters/th/chapter1/1.mdx index 049f79a98..a11b9c706 100644 --- a/chapters/th/chapter1/1.mdx +++ b/chapters/th/chapter1/1.mdx @@ -1,5 +1,10 @@ # บทนำ + + ## ยินดีต้อนรับเข้าสู่คอร์ส 🤗! diff --git a/chapters/th/chapter1/10.mdx b/chapters/th/chapter1/10.mdx index ac4c5f172..c66822858 100644 --- a/chapters/th/chapter1/10.mdx +++ b/chapters/th/chapter1/10.mdx @@ -2,6 +2,11 @@ # คำถามท้ายบท + + บทนี้พูดถึงพื้นฐานค่อนข้างเยอะมาก ไม่ต้องกังวลไปหากคุณไม่เข้าใจรายละเอียดทั้งหมด บทหน้าจะช่วยอธิบายว่าแต่ละอย่างทำงานกันเบื้องหลังอย่างไร ตอนนี้มาทดสอบกันดีกว่าว่าคุณได้เรียนรู้อะไรมาบ้างในบทนี้! diff --git a/chapters/th/chapter1/2.mdx b/chapters/th/chapter1/2.mdx index ccfe0c296..f887aa6ec 100644 --- a/chapters/th/chapter1/2.mdx +++ b/chapters/th/chapter1/2.mdx @@ -1,5 +1,10 @@ # การประมวลผลภาษาธรรมชาติ(หรือเรียกว่า NLP) + + ก่อนจะเข้าเนื้อหาส่วนโมเดล Transformer เรามาดูกันในภาพรวมก่อนว่า NLP คืออะไร แล้วทำไมเราต้องศึกษาและเรียนรู้มัน ## NLP คืออะไร? diff --git a/chapters/th/chapter1/3.mdx b/chapters/th/chapter1/3.mdx index 9ab990db5..592a43080 100644 --- a/chapters/th/chapter1/3.mdx +++ b/chapters/th/chapter1/3.mdx @@ -1,8 +1,8 @@ # Transformers ชื่อนี้มีดียังไง? - diff --git a/chapters/th/chapter1/4.mdx b/chapters/th/chapter1/4.mdx index dae9bdecb..639e9106d 100644 --- a/chapters/th/chapter1/4.mdx +++ b/chapters/th/chapter1/4.mdx @@ -1,5 +1,10 @@ # Transformer ทำงานยังไง? + + ในส่วนนี้เราจะมาดูกันว่าสถาปัตยกรรมของโมเดล Transformer ทำงานกันยังไง diff --git a/chapters/th/chapter1/5.mdx b/chapters/th/chapter1/5.mdx index e8b38f14a..98f792f97 100644 --- a/chapters/th/chapter1/5.mdx +++ b/chapters/th/chapter1/5.mdx @@ -1,5 +1,10 @@ # โมเดล Encoder + + โมเดล encoder ใช้เพียงส่วน encoder จากโมเดล Transformer เท่านั้น ในแต่ละชั้น attention layer สามารถเข้าถึงคำทุกคำในประโยคได้ โมเดลเหล่านี้ส่วนใหญ่จะใช้ attention แบบสองทาง (หรือเรียกว่า bi-directional attention) และถูกเรียกว่า *โมเดล auto-encoding* diff --git a/chapters/th/chapter1/6.mdx b/chapters/th/chapter1/6.mdx index f3f362950..dbdd3ea56 100644 --- a/chapters/th/chapter1/6.mdx +++ b/chapters/th/chapter1/6.mdx @@ -1,5 +1,10 @@ # โมเดล Decoder + + โมเดล decoder ใช้เพียงส่วน decoder จากโมเดล Transformer เท่านั้น ในแต่ละชั้น attention layer สามารถเข้าถึงคำที่อยู่ตำแหน่งก่อนหน้าในประโยคได้เท่านั้น โมเดลเหล่านี้เรียกว่า *โมเดล auto-regressive* diff --git a/chapters/th/chapter1/7.mdx b/chapters/th/chapter1/7.mdx index 05fdbcae5..3d3663e53 100644 --- a/chapters/th/chapter1/7.mdx +++ b/chapters/th/chapter1/7.mdx @@ -1,5 +1,10 @@ # โมเดล sequence-to-sequence + + โมเดล encoder-decoder (หรือเรียกอีกชื่อหนึ่งว่า *โมเดล sequence-to-sequence*) ใช้ทั้งสองส่วนในสถาปัตยกรรม Transformer ในแต่ละชั้น attention layer ของ encoder จะเข้าถึงคำทั้งหมดในประโยคเริ่มต้นได้ ในขณะที่ attention layer ของ decoder สามารถเข้าถึงได้เพียงคำที่อยู่ตำแหน่งก่อนหน้าคำที่กำหนดใน input เท่านั้น diff --git a/chapters/th/chapter1/8.mdx b/chapters/th/chapter1/8.mdx index 27dc1a643..4705d850a 100644 --- a/chapters/th/chapter1/8.mdx +++ b/chapters/th/chapter1/8.mdx @@ -1,8 +1,8 @@ # ข้อจำกัดจากอคติของข้อมูล - diff --git a/chapters/th/chapter1/9.mdx b/chapters/th/chapter1/9.mdx index 712dfda78..0d667985d 100644 --- a/chapters/th/chapter1/9.mdx +++ b/chapters/th/chapter1/9.mdx @@ -1,5 +1,10 @@ # สรุป + + ในบทนี้คุณได้เรียนรู้การแก้ปัญหา NLP แบบต่าง ๆ โดยใช้ฟังก์ชัน `pipeline()` จาก 🤗 Transformers รวมถึงได้เรียนรู้การค้นหาและใช้งานโมเดลใน Hub อีกทั้งยังเรียนรู้การใช้งาน Inference API ในการทดสอบโมเดลจากเว็บบราวเซอร์ นอกจากนี้เรายังได้พูดเกี่ยวกับการทำงานของโมเดล Transformer และความสำคัญของการใช้งาน transfer learning และการ fine-tune สิ่งสำคัญเลยคือ คุณสามารถใช้โมเดลแบบเต็มรูปแบบหรือจะใช้เพียงแค่ส่วน encoder หรือ decoder ก็ได้ ขึ้นอยู่กับว่าต้องการใช้งานแบบไหน ตารางด้านล่างสรุปการใช้งานไว้ดังนี้: diff --git a/chapters/th/chapter2/1.mdx b/chapters/th/chapter2/1.mdx index dbb602d76..690eeabe1 100644 --- a/chapters/th/chapter2/1.mdx +++ b/chapters/th/chapter2/1.mdx @@ -1,5 +1,10 @@ # บทนำ + + อย่างที่คุณเห็นใน [Chapter 1](/course/chapter1), โดยปกติแล้วโมเดล Transformer นั้นจะมีขนาดใหญ่มาก การเทรนและการใช้งานโมเดลเหล่านี้ที่มีตัวแปร (parameters) เป็นล้านไปจนถึง *หมื่นล้าน* ตัวแปรนั้นเป็นเรื่องที่ค่อนข้างซับซ้อน นอกจากนั้นแล้วการที่มีโมเดลใหม่ๆปล่อยออกมาเกือบทุกวันและแต่ละโมเดลก็มีวิธีการสร้าง (implementation) เป็นของตัวเอง ดังนั้นการจะลองทุกโมเดลนั้นไม่ใช่เรื่องที่ง่ายเลย 🤗 Transformers library สร้างขึ้นมาเพื่อแก้ปัญหานี้ จุดประสงค์ก็คือ การทำให้ไม่ว่าจะโมเดล Transformer ใดก็ตามสามารถโหลด, เทรน, และบันทึก ได้ด้วยการใช้ API เพียงอันเดียว จุดเด่นหลักๆของ library ประกอบด้วย diff --git a/chapters/th/chapter2/2.mdx b/chapters/th/chapter2/2.mdx index 87968254b..1f2e9566b 100644 --- a/chapters/th/chapter2/2.mdx +++ b/chapters/th/chapter2/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/th/chapter2/3.mdx b/chapters/th/chapter2/3.mdx index 6b82bd629..075a2bd7c 100644 --- a/chapters/th/chapter2/3.mdx +++ b/chapters/th/chapter2/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/th/chapter2/4.mdx b/chapters/th/chapter2/4.mdx index 79ad132c1..dcb635cca 100644 --- a/chapters/th/chapter2/4.mdx +++ b/chapters/th/chapter2/4.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/th/chapter2/5.mdx b/chapters/th/chapter2/5.mdx index eac8b97c4..3927688b0 100644 --- a/chapters/th/chapter2/5.mdx +++ b/chapters/th/chapter2/5.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/th/chapter2/6.mdx b/chapters/th/chapter2/6.mdx index a930b491a..3af1b5216 100644 --- a/chapters/th/chapter2/6.mdx +++ b/chapters/th/chapter2/6.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/th/chapter2/7.mdx b/chapters/th/chapter2/7.mdx index cc2235fb3..b623c54aa 100644 --- a/chapters/th/chapter2/7.mdx +++ b/chapters/th/chapter2/7.mdx @@ -1,5 +1,10 @@ # การใช้งานเบื้องต้นสำเร็จแล้ว! + + คุณเยี่ยมมากที่เรียนมาได้ถึงตรงนี่ เรามาทบทวนกันว่าในบทนี้คุณ: - เรียนรู้ blocks พื้นฐานของโมเดล Transformer diff --git a/chapters/th/chapter2/8.mdx b/chapters/th/chapter2/8.mdx index f7e51037d..a16542edc 100644 --- a/chapters/th/chapter2/8.mdx +++ b/chapters/th/chapter2/8.mdx @@ -4,6 +4,11 @@ # แบบทดสอบท้ายบท + + ### 1. ลำดับขั้นตอนใน pipeline ของการทำโมเดลด้านภาษา(language modeling)เป็นอย่างไร ? + ใน [Chapter 2](/course/chapter2) เราได้เรียนรู้วิธีการใช้ tokenizers และโมเดลที่ผ่านการเทรนมาแล้ว (pretrained models) ในการทำนาย แต่ถ้าเราต้องการจะใช้ dataset ของเราเองในการ fine-tune โมเดลล่ะ? นั่นคือหัวข้อของบทนี้เลย! คุณจะได้เรียนรู้: {#if fw === 'pt'} diff --git a/chapters/th/chapter3/2.mdx b/chapters/th/chapter3/2.mdx index 61efcd854..65991cc15 100644 --- a/chapters/th/chapter3/2.mdx +++ b/chapters/th/chapter3/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/th/chapter3/3.mdx b/chapters/th/chapter3/3.mdx index e510e443d..9f22267cd 100644 --- a/chapters/th/chapter3/3.mdx +++ b/chapters/th/chapter3/3.mdx @@ -2,9 +2,9 @@ # การ Fine-tune โมเดลด้วย Trainer API - diff --git a/chapters/th/chapter3/3_tf.mdx b/chapters/th/chapter3/3_tf.mdx index e9ccd4611..2d0e13c94 100644 --- a/chapters/th/chapter3/3_tf.mdx +++ b/chapters/th/chapter3/3_tf.mdx @@ -2,9 +2,9 @@ # การ Fine-tune โมเดลด้วย Keras - diff --git a/chapters/th/chapter3/4.mdx b/chapters/th/chapter3/4.mdx index d5f9f2f94..1b4df3ca0 100644 --- a/chapters/th/chapter3/4.mdx +++ b/chapters/th/chapter3/4.mdx @@ -1,8 +1,8 @@ # การเทรนโมเดลฉบับสมบูรณ์ - diff --git a/chapters/th/chapter3/5.mdx b/chapters/th/chapter3/5.mdx index 60d9a9fe8..98edf4c65 100644 --- a/chapters/th/chapter3/5.mdx +++ b/chapters/th/chapter3/5.mdx @@ -2,6 +2,11 @@ # Fine-tune โมเดลสำเร็จแล้ว! + + สนุกจังเลย! ในสองบทแรกคุณได้เรียนรู้เกี่ยวกับโมเดลและ tokenizers และตอนนี้คุณก็รู้วิธีการ fine-tune โมเดลด้วยข้อมูลของคุณเองแล้ว มาทบทวนกันว่าคุณได้ทำอะไรไปบ้างในบทนี้: {#if fw === 'pt'} diff --git a/chapters/th/chapter3/6.mdx b/chapters/th/chapter3/6.mdx index 521ac7f5a..b6534f22a 100644 --- a/chapters/th/chapter3/6.mdx +++ b/chapters/th/chapter3/6.mdx @@ -4,6 +4,11 @@ # คำถามท้ายบท + + ทดสอบความรู้ที่คุณได้เรียนมาจากบทนี้กัน! ### 1. ใน `emotion` dataset ซึ่งได้รวบรวมข้อความ Twitter ที่มีการ labeled ว่าแต่ละข้อความนั้นเป็นข้อความที่มีอารมณ์แบบใด ลองค้นข้อมูลดูจาก [Hub](https://huggingface.co/datasets)และอ่าน dataset card ดูแล้วตอบว่า ข้อใดไม่ใช่หนึ่งในอารมณ์พื้นฐานของ dataset นี้? diff --git a/chapters/th/chapter4/1.mdx b/chapters/th/chapter4/1.mdx index c4a18cd1e..fb0811c7f 100644 --- a/chapters/th/chapter4/1.mdx +++ b/chapters/th/chapter4/1.mdx @@ -1,5 +1,10 @@ # The Hugging Face Hub + + [Hugging Face Hub](https://huggingface.co/) –- เว็บไซต์หลักของเรา –- เป็นแพลตฟอร์มกลางที่ทุกคนสามารถค้นหาโมเดลและชุดข้อมูลที่ล้ำสมัยที่สุด (state-of-the-art) และสามารถนำไปใช้งาน รวมถึงมีส่วนร่วมได้ เรามีโมเดลที่เปิดให้ใช้ได้อย่างเป็นสาธารณะที่หลากหลายมากกว่า 10,000 โมเดลให้เลือกใช้ ซึ่งในบทนี้เราจะมาเจาะลึกลงในเรื่องของโมเดล และเราจะพูดถึงชุดข้อมูลในบทที่ 5 โมเดลใน hub ของเราไม่ได้มีแค่ 🤗 Transformers หรือ NLP เท่านั้น ยังมีโมเดลจาก [Flair](https://github.com/flairNLP/flair) และ [AllenNLP](https://github.com/allenai/allennlp) สำหรับงาน NLP, [Asteroid](https://github.com/asteroid-team/asteroid) และ [pyannote](https://github.com/pyannote/pyannote-audio) สำหรับงานเสียง และ [timm](https://github.com/rwightman/pytorch-image-models) สำหรับงานภาพ และอื่นๆอีกมากมาย diff --git a/chapters/th/chapter4/2.mdx b/chapters/th/chapter4/2.mdx index 22972bd41..90eb59598 100644 --- a/chapters/th/chapter4/2.mdx +++ b/chapters/th/chapter4/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/th/chapter4/3.mdx b/chapters/th/chapter4/3.mdx index b63c6e6c4..9f765a4f1 100644 --- a/chapters/th/chapter4/3.mdx +++ b/chapters/th/chapter4/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/th/chapter4/4.mdx b/chapters/th/chapter4/4.mdx index 9f609ed56..6dedd79f5 100644 --- a/chapters/th/chapter4/4.mdx +++ b/chapters/th/chapter4/4.mdx @@ -1,5 +1,10 @@ # การสร้างการ์ดโมเดล (model card) + + การ์ดโมเดลเป็นไฟล์ที่เรียกได้ว่าสำคัญพอๆกับไฟล์โมเดลและ tokenizer ใน model repository มันคือคำนิยามส่วนกลางของโมเดล เป็นสิ่งที่ทำให้มั่นใจว่าสมาชิกของชุมชนสามารถนำไปใช้ได้ (reusability) และสามารถทำซ้ำและได้ผลลัพธ์เหมือนเดิมได้ (reproducibility) และมันยังจัดเตรียมแพลตฟอร์มให้สมาชิกคนอื่นๆอาจใช้สร้างสิ่งประดิษฐ์ (artifacts) ของเขาเองได้ การสร้างหนังสืออ้างอิง (documenting) ถึงกระบวนการเทรนและประเมินผลช่วยให้ผู้อื่นเข้าใจถึงสิ่งที่คาดหวังได้จากโมเดลๆหนึ่ง และการให้ข้อมูลที่เพียงพอเกี่ยวกับข้อมูลที่ถูกใช้, กระบวนการเตรียมข้อมูล (preprocessing) และกระบวนการหลังจากใช้โมเดล (postprocessing) ที่ถูกทำมาทำให้มั่นใจถึงขีดจำกัด (limitations), ความลำเอียง (biases) และบริบท (contexts) ที่โมเดลเป็นและไม่เป็น ซึ่งเป็นสิ่งที่เป็นประโยชน์มากถ้าหากสามารถระบุและเข้าใจได้ diff --git a/chapters/th/chapter4/5.mdx b/chapters/th/chapter4/5.mdx index e810397d8..366d0d404 100644 --- a/chapters/th/chapter4/5.mdx +++ b/chapters/th/chapter4/5.mdx @@ -1,5 +1,10 @@ # จบพาร์ทที่ 1! + + นี่คือจุดจบของพาร์ทแรกในคอร์สนี้! พาร์ทที่ 2 จะถูกปล่อยในวันที่ 15 พฤศจิกายน พร้อมกับงานอีเว้นท์ชุมชนใหญ่ ดูรายละเอียดเพิ่มเติม [ที่นี่](https://huggingface.co/blog/course-launch-event) ตอนนี้คุณควรจะสามารถทำ fine-tune โมเดลที่ผ่านการเทรนมาแล้ว (pretrained model) กับปัญหาการจำแนกประเภทตัวหนังสือ (text classification) (แบบประโยคเดี่ยวหรือคู่ประโยค) และอัพโหลดผลลัพธ์ขึ้นสู่ Model Hub ได้ เพื่อที่จะทำให้มั่นใจว่าคุณเชี่ยวชาญส่วนแรกนี้แล้วจริงๆ คุณควรจะฝึกทำแบบนี้เป๊ะๆกับปัญหาที่คุณสนใจ (และไม่จำเป็นจะต้องเป็นภาษาอังกฤษถ้าคุณพูดภาษาอื่น)! คุณสามารถหาความช่วยเหลือได้ใน [Hugging Face forums](https://discuss.huggingface.co/) และแบ่งปันโปรเจคของคุณได้ใน [หัวข้อนี้](https://discuss.huggingface.co/t/share-your-projects/6803) เมื่อคุณทำเสร็จ diff --git a/chapters/th/chapter4/6.mdx b/chapters/th/chapter4/6.mdx index 078accc9a..98a6c8784 100644 --- a/chapters/th/chapter4/6.mdx +++ b/chapters/th/chapter4/6.mdx @@ -4,6 +4,11 @@ # คำถามท้ายบท + + มาทดสอบความรู้ที่คุณได้เรียนในบทนี้กันเถอะ! ### 1. อะไรคือข้อจำกัดของโมเดลบน Hub? diff --git a/chapters/th/chapter6/1.mdx b/chapters/th/chapter6/1.mdx index d4521932a..ada9a8cce 100644 --- a/chapters/th/chapter6/1.mdx +++ b/chapters/th/chapter6/1.mdx @@ -1,5 +1,10 @@ # บทนำ + + ใน[บทที่ 3](/course/chapter3) คุณได้เรียนเกี่ยวกับการ fine-tune โมเดลเพื่อนำไปใช้ในงานที่คุณต้องการ ตอนนั้นเราใช้ตัวตัดคำ(tokenizer)แบบเดียวกับตัวที่มากับโมเดล แต่หากคุณอยากจะเทรนโมเดลตั้งแต่เริ่มต้นเลย คุณควรจะเลือกใช้ตัวตัดคำแบบไหนดี ในกรณีนี้ถ้าคุณใช้ตัวตัดคำที่เทรนจากคลังข้อมูล(corpus)ที่ไม่ใช่ภาษาเดียวกับโมเดลหรือคลังข้อมูลที่มาจากโดเมนอื่น(แปลว่าเนื้อหาของข้อมูลที่ใช้เทรนตัวตัดคำและใช้เทรนโมเดลมีความแตกต่างกันมาก)ก็จะไม่เหมาะสมนัก ตัวอย่างเช่น ตัวตัดคำที่เทรนมาสำหรับตัดคำภาษาอังกฤษ เมื่อนำมาใช้เพื่อตัดคำภาษาญี่ปุ่นก็จะได้ผลลัพธ์ที่ไม่ดี เพราะว่าทั้งสองภาษามีการใช้ช่องว่าง(space)และเครื่องหมายวรรคตอน(punctuation)ที่ต่างกันมาก diff --git a/chapters/th/chapter6/10.mdx b/chapters/th/chapter6/10.mdx index 82890bc9b..1d11caca9 100644 --- a/chapters/th/chapter6/10.mdx +++ b/chapters/th/chapter6/10.mdx @@ -2,6 +2,11 @@ # คำถามท้ายบท + + มาทดสอบความรู้ที่คุณได้เรียนในบทนี้กันเถอะ! ### 1. สถานการณ์ไหนที่คุณควรจะเทรน tokenizer ขึ้นมาใหม่? diff --git a/chapters/th/chapter6/2.mdx b/chapters/th/chapter6/2.mdx index f36d7bb1b..f10b5d003 100644 --- a/chapters/th/chapter6/2.mdx +++ b/chapters/th/chapter6/2.mdx @@ -1,8 +1,8 @@ # การเทรน tokenizer จาก tokenizer ที่มีอยู่แล้ว - diff --git a/chapters/th/chapter6/3.mdx b/chapters/th/chapter6/3.mdx index 8f7f6c52f..6b248ff1b 100644 --- a/chapters/th/chapter6/3.mdx +++ b/chapters/th/chapter6/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/th/chapter6/3b.mdx b/chapters/th/chapter6/3b.mdx index dd9aad386..ce4283a71 100644 --- a/chapters/th/chapter6/3b.mdx +++ b/chapters/th/chapter6/3b.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/th/chapter6/4.mdx b/chapters/th/chapter6/4.mdx index 1d763e715..f1f4e6cdc 100644 --- a/chapters/th/chapter6/4.mdx +++ b/chapters/th/chapter6/4.mdx @@ -1,8 +1,8 @@ # Normalization และ pre-tokenization - diff --git a/chapters/th/chapter6/5.mdx b/chapters/th/chapter6/5.mdx index ad05d3e21..8a162474b 100644 --- a/chapters/th/chapter6/5.mdx +++ b/chapters/th/chapter6/5.mdx @@ -1,8 +1,8 @@ # Byte-Pair Encoding tokenization - diff --git a/chapters/th/chapter6/6.mdx b/chapters/th/chapter6/6.mdx index e19f40410..25d3073b5 100644 --- a/chapters/th/chapter6/6.mdx +++ b/chapters/th/chapter6/6.mdx @@ -1,8 +1,8 @@ # WordPiece tokenization - diff --git a/chapters/th/chapter6/7.mdx b/chapters/th/chapter6/7.mdx index a19df558c..ea2311e03 100644 --- a/chapters/th/chapter6/7.mdx +++ b/chapters/th/chapter6/7.mdx @@ -1,8 +1,8 @@ # Unigram tokenization - diff --git a/chapters/th/chapter6/8.mdx b/chapters/th/chapter6/8.mdx index 8b8d62072..adea0fb9b 100644 --- a/chapters/th/chapter6/8.mdx +++ b/chapters/th/chapter6/8.mdx @@ -1,8 +1,8 @@ # การสร้าง tokenizer ทีละขั้นตอน - diff --git a/chapters/th/chapter6/9.mdx b/chapters/th/chapter6/9.mdx index 2b0716c2d..620ddaac2 100644 --- a/chapters/th/chapter6/9.mdx +++ b/chapters/th/chapter6/9.mdx @@ -1,5 +1,10 @@ # เรียนจบเรื่อง tokenizer แล้ว! + + เยี่ยมมาก คุณเรียนจบบทนี้แล้ว! หลังจากที่ได้เรียนเกี่ยวกับ tokenizer อย่างละเอียดแล้ว คุณจะ : diff --git a/chapters/tr/chapter1/1.mdx b/chapters/tr/chapter1/1.mdx index 09670c860..2b41bc4a1 100644 --- a/chapters/tr/chapter1/1.mdx +++ b/chapters/tr/chapter1/1.mdx @@ -1,53 +1,58 @@ -# Giriş - -## 🤗 Kursuna Hoşgeldiniz! - - - -Bu kurs size [Hugging Face](https://huggingface.co/) ekosistemindeki kütüphaneleri — [🤗 Transformers](https://github.com/huggingface/transformers), [🤗 Datasets](https://github.com/huggingface/datasets), [🤗 Tokenizers](https://github.com/huggingface/tokenizers), ve [🤗 Accelerate](https://github.com/huggingface/accelerate) — ayrıca tabiki de [Hugging Face Hub](https://huggingface.co/models) kullanarak Doğal Dil İşleme'yi (NLP) öğretecektir. Kurs tamamen ücretsiz ve reklam bulunmuyor. - -## Beklentiniz ne olmalı? - -Burada kursa genel bakış bulunmaktadır: - -
-Brief overview of the chapters of the course. - -
- -- 1'den 4'üncü bölüme kadar olan kısımda 🤗 Transformers kütüphanesinin ana konseptlerine giriş yapacağız. Kursun bu bölümünün sonunda, Transformer modellerinin nasıl çalıştığını öğrenecek, ve [Hugging Face Hub](https://huggingface.co/models) üzerinden bir modeli nasil kullanabileceğinizi, verisetine ince ayar (fine-tune) yapmayı, ve sonuçlarınızı Hub üzerinde nasıl paylaşacağınızı bileceksiniz! -- Bölüm 5 ila 8, klasik NLP görevlerine dalmadan önce 🤗 Datasets ve 🤗 Tokenizers'in temellerini öğretiyor. Bu bölümün sonunda, en yaygın NLP problemlerini kendiniz çözebileceksiniz. -- 9'dan 12'ye kadar olan bölümler NLP'nin ötesine geçer ve Transformer modellerinin konuşma işleme ve bilgisayarlı görü alanlarındaki problemleri nasıl çözeceğini ele alır. Süreç boyunca, model demolarınızı nasıl oluşturup paylaşacağınızı ve bunları üretim ortamlarında nasıl optimize edeceğinizi öğreneceksiniz. Bu bölümün sonunda, 🤗 Transformers'i (neredeyse) herhangi bir makine öğrenmesi problemine uygulamaya hazır olacaksınız! - -Bu kurs: - -* Python hakkında iyi düzeyde bilgi gerektirir. -* Giriş düzeyinde derin öğrenme derslerinden sonra alınırsa daha iyi olur. Örneğin: [fast.ai](https://www.fast.ai/) [Practical Deep Learning for Coders kursu](https://course.fast.ai/) veya [DeepLearning.AI](https://www.deeplearning.ai/) tarafından verilen herhangi program. -* [PyTorch](https://pytorch.org/) veya [TensorFlow](https://www.tensorflow.org/) ile herhangi bir ön bilgi beklenmiyor, buna rağmen bunlardan birisine aşinalik yardımcı olur. - -Kursu bitirdikten sonra, DeepLearning.AI [Doğal Dil İşleme Uzmanlık](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh) naive Bayes ve LSTM gibi bilmeye değer geleneksel NLP modellerinin bulunduğu seriyi izlemenizi tavsiye ediyoruz. - - -## Biz Kimiz? - -Eğitmenler hakkında: - -**Matthew Carrigan** Hugging Face'de Makine Öğrenmesi Mühendisi. Dublin, İrlanda'da yaşıyor ve daha önce Parse.ly'de ML Engineer olarak çalıştı ve onunda öncesinde Doktora sonrası Araştırmacı olarak Trinity College Dublin'de idi. Mevcut AI mimarilerini ölçeklendirerek AGI'a ulaşacağımıza inanmıyor, ancak ne olursa olsun robot ölümsüzlüğü için büyük umutları var. - -**Lysandre Debut** Hugging Face'de Makine Öğrenmesi Mühendisi ve 🤗 Transformers kütüphanesi üzerine erken gelişim aşamasından beri çalışıyor. Hedefi çok basit bir API geliştirerek NLP'yi herkes için ulaşılabilir kılmak. - -**Sylvain Guggeat** Hugging Face'de Araştırma Mühendisi ve 🤗 Transformers kütüphanesinin proje yürütücülerinden biri. Öncesinde Araştırma Bilimci olarak fast.ai'da çalıştı, ve _[Deep Learning for Coders with fastai and PyTorch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)_ kitabını Jeremy Howard ile birlikte yazdı. Sylvain'in araştırmalarının ana odağı modellerin sınırlı kaynaklarda daha hızlı eğitilmesine izin veren teknikleri tasarlayıp geliştirerek derin öğrenmeyi herkes için ulaşılabilir kılmak. - -**Merve Noyan** Hugging Face'de Developer Advocate, araçlar geliştirerek ve bu araçlar etrafında içerik üreterek makine öğrenmesini herkes için demokratikleştirme üzerine çalışıyor. - -**Lucile Saulnier** Hugging Face'de Makine Öğrenmesi Mühendisi, açık kaynak araçlarının geliştirilmesi ve desteklenmesi üzerine uğraşıyor. Ayrıca Doğal Dil İşleme içinde Collaborative Training ve BigScience gibi birçok araştırma projesine dahil. - -**Lewis Tunstall** Hugging Face'de Makine Öğrenmesi Mühendisi, açık kaynak araçları geliştirmeye ve bunlari daha geniş bir topluluk için ulaşılabilir hale getirmeye odaklanmış. Ayrıca yakında gelecek olan [Transformers üzerine O’Reilly kitabının](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/) yazarlarından biri. - -**Leandro von Werra** Hugging Face'de Açık-Kaynak takımında Makine Öğrenmesi Mühendisi ve ayrica yakında gelecek olan [Transformers üzerine olan O’Reilly kitabinin](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/) yazarlarından biri. Tüm Makine Öğrenmesi stack'inde çalişarak NLP projelerini üretime getiren birkaç yıllık endüstri deneyimine sahiptir. - - -Başlamaya hazır mısın? Bu bölümde, şunları öğreneceksin: -* Metin oluşturma ve sınıflandırma gibi NLP görevlerini çözmek için `pipeline ()` fonksiyonu nasıl kullanılacağı? -* Transformer mimarisi -* Encoder, Decoder, ve Encoder-Decoder mimarilerini nasil ayırt edileceği ve kullanım alanlari +# Giriş + + + +## 🤗 Kursuna Hoşgeldiniz! + + + +Bu kurs size [Hugging Face](https://huggingface.co/) ekosistemindeki kütüphaneleri — [🤗 Transformers](https://github.com/huggingface/transformers), [🤗 Datasets](https://github.com/huggingface/datasets), [🤗 Tokenizers](https://github.com/huggingface/tokenizers), ve [🤗 Accelerate](https://github.com/huggingface/accelerate) — ayrıca tabiki de [Hugging Face Hub](https://huggingface.co/models) kullanarak Doğal Dil İşleme'yi (NLP) öğretecektir. Kurs tamamen ücretsiz ve reklam bulunmuyor. + +## Beklentiniz ne olmalı? + +Burada kursa genel bakış bulunmaktadır: + +
+Brief overview of the chapters of the course. + +
+ +- 1'den 4'üncü bölüme kadar olan kısımda 🤗 Transformers kütüphanesinin ana konseptlerine giriş yapacağız. Kursun bu bölümünün sonunda, Transformer modellerinin nasıl çalıştığını öğrenecek, ve [Hugging Face Hub](https://huggingface.co/models) üzerinden bir modeli nasil kullanabileceğinizi, verisetine ince ayar (fine-tune) yapmayı, ve sonuçlarınızı Hub üzerinde nasıl paylaşacağınızı bileceksiniz! +- Bölüm 5 ila 8, klasik NLP görevlerine dalmadan önce 🤗 Datasets ve 🤗 Tokenizers'in temellerini öğretiyor. Bu bölümün sonunda, en yaygın NLP problemlerini kendiniz çözebileceksiniz. +- 9'dan 12'ye kadar olan bölümler NLP'nin ötesine geçer ve Transformer modellerinin konuşma işleme ve bilgisayarlı görü alanlarındaki problemleri nasıl çözeceğini ele alır. Süreç boyunca, model demolarınızı nasıl oluşturup paylaşacağınızı ve bunları üretim ortamlarında nasıl optimize edeceğinizi öğreneceksiniz. Bu bölümün sonunda, 🤗 Transformers'i (neredeyse) herhangi bir makine öğrenmesi problemine uygulamaya hazır olacaksınız! + +Bu kurs: + +* Python hakkında iyi düzeyde bilgi gerektirir. +* Giriş düzeyinde derin öğrenme derslerinden sonra alınırsa daha iyi olur. Örneğin: [fast.ai](https://www.fast.ai/) [Practical Deep Learning for Coders kursu](https://course.fast.ai/) veya [DeepLearning.AI](https://www.deeplearning.ai/) tarafından verilen herhangi program. +* [PyTorch](https://pytorch.org/) veya [TensorFlow](https://www.tensorflow.org/) ile herhangi bir ön bilgi beklenmiyor, buna rağmen bunlardan birisine aşinalik yardımcı olur. + +Kursu bitirdikten sonra, DeepLearning.AI [Doğal Dil İşleme Uzmanlık](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh) naive Bayes ve LSTM gibi bilmeye değer geleneksel NLP modellerinin bulunduğu seriyi izlemenizi tavsiye ediyoruz. + + +## Biz Kimiz? + +Eğitmenler hakkında: + +**Matthew Carrigan** Hugging Face'de Makine Öğrenmesi Mühendisi. Dublin, İrlanda'da yaşıyor ve daha önce Parse.ly'de ML Engineer olarak çalıştı ve onunda öncesinde Doktora sonrası Araştırmacı olarak Trinity College Dublin'de idi. Mevcut AI mimarilerini ölçeklendirerek AGI'a ulaşacağımıza inanmıyor, ancak ne olursa olsun robot ölümsüzlüğü için büyük umutları var. + +**Lysandre Debut** Hugging Face'de Makine Öğrenmesi Mühendisi ve 🤗 Transformers kütüphanesi üzerine erken gelişim aşamasından beri çalışıyor. Hedefi çok basit bir API geliştirerek NLP'yi herkes için ulaşılabilir kılmak. + +**Sylvain Guggeat** Hugging Face'de Araştırma Mühendisi ve 🤗 Transformers kütüphanesinin proje yürütücülerinden biri. Öncesinde Araştırma Bilimci olarak fast.ai'da çalıştı, ve _[Deep Learning for Coders with fastai and PyTorch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)_ kitabını Jeremy Howard ile birlikte yazdı. Sylvain'in araştırmalarının ana odağı modellerin sınırlı kaynaklarda daha hızlı eğitilmesine izin veren teknikleri tasarlayıp geliştirerek derin öğrenmeyi herkes için ulaşılabilir kılmak. + +**Merve Noyan** Hugging Face'de Developer Advocate, araçlar geliştirerek ve bu araçlar etrafında içerik üreterek makine öğrenmesini herkes için demokratikleştirme üzerine çalışıyor. + +**Lucile Saulnier** Hugging Face'de Makine Öğrenmesi Mühendisi, açık kaynak araçlarının geliştirilmesi ve desteklenmesi üzerine uğraşıyor. Ayrıca Doğal Dil İşleme içinde Collaborative Training ve BigScience gibi birçok araştırma projesine dahil. + +**Lewis Tunstall** Hugging Face'de Makine Öğrenmesi Mühendisi, açık kaynak araçları geliştirmeye ve bunlari daha geniş bir topluluk için ulaşılabilir hale getirmeye odaklanmış. Ayrıca yakında gelecek olan [Transformers üzerine O’Reilly kitabının](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/) yazarlarından biri. + +**Leandro von Werra** Hugging Face'de Açık-Kaynak takımında Makine Öğrenmesi Mühendisi ve ayrica yakında gelecek olan [Transformers üzerine olan O’Reilly kitabinin](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/) yazarlarından biri. Tüm Makine Öğrenmesi stack'inde çalişarak NLP projelerini üretime getiren birkaç yıllık endüstri deneyimine sahiptir. + + +Başlamaya hazır mısın? Bu bölümde, şunları öğreneceksin: +* Metin oluşturma ve sınıflandırma gibi NLP görevlerini çözmek için `pipeline ()` fonksiyonu nasıl kullanılacağı? +* Transformer mimarisi +* Encoder, Decoder, ve Encoder-Decoder mimarilerini nasil ayırt edileceği ve kullanım alanlari diff --git a/chapters/tr/chapter1/2.mdx b/chapters/tr/chapter1/2.mdx index a9536d43a..55931f2a1 100644 --- a/chapters/tr/chapter1/2.mdx +++ b/chapters/tr/chapter1/2.mdx @@ -1,5 +1,10 @@ # Doğal Dil İşleme (NLP) + + Transformer modellerine geçiş yapmadan önce, doğal dil işlemenin tanımına ve neden önemli olduğuna kısaca bir göz atalım. diff --git a/chapters/tr/chapter1/5.mdx b/chapters/tr/chapter1/5.mdx index cc86a135d..b0d850a0c 100644 --- a/chapters/tr/chapter1/5.mdx +++ b/chapters/tr/chapter1/5.mdx @@ -1,18 +1,23 @@ -# Encoder modelleri - - - -Encoder modelleri Transformer modellerinin sadece encoder kısmını kulanır.Her aşamada, attention katmanları ilk cümlenin bütün kelimelerine erişir. Bu modeller genellikle çift yönlü attention olarak nitelendirilir ve genellikle *auto-encoding models* olarak adlandırılır. - -Bu modellerin öneğitimi genellikle verilen cümleyi bozmaya yöneliktir (örnek olarak, içindeki rastgele kelimeleri maskeleyerek) ve model ilk cümleyi bulma veya yeniden oluşturma ile görevlendirilir. - -Encoder modelleri cümle sınıflandırma, varlık tanıma (daha spesifik olarak sözcük sınıflandırma) ve extractive soru yanıtlama gibi cümlenin tam anlaşılmasını gerektiren görevler için uygundur. - - -Bu model ailesinin temsilcileri şunlardır: - -- [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) +# Encoder modelleri + + + + + +Encoder modelleri Transformer modellerinin sadece encoder kısmını kulanır.Her aşamada, attention katmanları ilk cümlenin bütün kelimelerine erişir. Bu modeller genellikle çift yönlü attention olarak nitelendirilir ve genellikle *auto-encoding models* olarak adlandırılır. + +Bu modellerin öneğitimi genellikle verilen cümleyi bozmaya yöneliktir (örnek olarak, içindeki rastgele kelimeleri maskeleyerek) ve model ilk cümleyi bulma veya yeniden oluşturma ile görevlendirilir. + +Encoder modelleri cümle sınıflandırma, varlık tanıma (daha spesifik olarak sözcük sınıflandırma) ve extractive soru yanıtlama gibi cümlenin tam anlaşılmasını gerektiren görevler için uygundur. + + +Bu model ailesinin temsilcileri şunlardır: + +- [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) diff --git a/chapters/tr/chapter1/6.mdx b/chapters/tr/chapter1/6.mdx index 4599582de..198db758a 100644 --- a/chapters/tr/chapter1/6.mdx +++ b/chapters/tr/chapter1/6.mdx @@ -1,5 +1,10 @@ # Decoder modelleri + + Decoder modeller, yalnızca bir Transformer modelinin decoderini kullanır. Her aşamada, attention katmanları sadece cümlede kendisinden önce gelen kelimelere erişebilir. Bu modeller *auto-regressive models* olarak isimlendirilir. diff --git a/chapters/tr/chapter2/1.mdx b/chapters/tr/chapter2/1.mdx index aad4d9b37..e0787052a 100644 --- a/chapters/tr/chapter2/1.mdx +++ b/chapters/tr/chapter2/1.mdx @@ -1,5 +1,10 @@ # Giriş + + [Birinci bölümde](/course/chapter1) gördüğünüz gibi, Transformer modelleri genellikle oldukça büyüktür. Milyonlarca, hatta milyarlarca, parametreleri olan bu modellerin eğitimi ve üretime geçirilmesi oldukça karmaşık bir girişimdir. Bunun yanısıra, hemen hemen her gün, herbirinin kendine özgü uygulaması olan yeni modellerin yayınlaması, bu yeni modellerinin hepsini birden denemeyi daha da zor bir hale getirmektedir. 🤗 Transformers kütüphanesi bu sorunu çözmek için oluşturuldu. Bu kütüphanenin amacı, herhangi bir Transformer modelini tek bir API (uygulama programı arabirimi) aracılığı ile yükleme, eğitme, ve kaydetmeyi sağlamaktır. Kütüphanenin başlıca özellikleri şunlardır: diff --git a/chapters/tr/chapter3/1.mdx b/chapters/tr/chapter3/1.mdx index 89e9d47a6..6a666892f 100644 --- a/chapters/tr/chapter3/1.mdx +++ b/chapters/tr/chapter3/1.mdx @@ -2,6 +2,11 @@ # Giriş + + [İkinci bölümde](/course/chapter2) tokenizer ve pretrained modelleri kullanarak nasıl tahmin yapabileceğimizi öğrendik. Fakat, kendi veri setiniz için, pretrained bir modeli nasıl kullanacaksınız ? İşte bu bölümde bunu öğreneceksiniz! Öğrenecekleriniz : {#if fw === 'pt'} diff --git a/chapters/vi/chapter1/1.mdx b/chapters/vi/chapter1/1.mdx index 5741d0ed6..3981c7bcc 100644 --- a/chapters/vi/chapter1/1.mdx +++ b/chapters/vi/chapter1/1.mdx @@ -1,5 +1,10 @@ # Giới thiệu + + ## Chào mừng tới 🤗 Khoá học! diff --git a/chapters/vi/chapter1/10.mdx b/chapters/vi/chapter1/10.mdx index 1e529bd97..cb5f871a8 100644 --- a/chapters/vi/chapter1/10.mdx +++ b/chapters/vi/chapter1/10.mdx @@ -2,6 +2,11 @@ # Đố vui cuối chương + + Chương này bao gồm rất nhiều mặt! Đừng lo lắng nếu bạn không nắm được tất cả các chi tiết; các chương tiếp theo sẽ giúp bạn hiểu mọi thứ hoạt động như thế nào. Tuy nhiên, trước tiên, hãy kiểm tra những gì bạn đã học được trong chương này! diff --git a/chapters/vi/chapter1/2.mdx b/chapters/vi/chapter1/2.mdx index b5a27a1b7..5abcbbb8e 100644 --- a/chapters/vi/chapter1/2.mdx +++ b/chapters/vi/chapter1/2.mdx @@ -1,5 +1,10 @@ # Xử lý Ngôn Ngữ Tự nhiên + + Trước khi chuyển sang mô hình Transformer, chúng ta hãy cùng tìm hiểu nhanh tổng quan về Xử lý Ngôn ngữ Tự nhiên là gì và tại sao chúng ta quan tâm đến lĩnh vực này. ## Xử lý Ngôn ngữ Tự nhiên (NLP) là gì? diff --git a/chapters/vi/chapter1/3.mdx b/chapters/vi/chapter1/3.mdx index 677b4f48b..90e9b3de5 100644 --- a/chapters/vi/chapter1/3.mdx +++ b/chapters/vi/chapter1/3.mdx @@ -1,8 +1,8 @@ # Transformers có thể làm những gì? - diff --git a/chapters/vi/chapter1/4.mdx b/chapters/vi/chapter1/4.mdx index b21901fd4..00660f132 100644 --- a/chapters/vi/chapter1/4.mdx +++ b/chapters/vi/chapter1/4.mdx @@ -1,5 +1,10 @@ # Cơ chế hoạt động của Transformer? + + Trong phần này, chúng ta sẽ tìm hiểu kiến trúc của các mô hình Transformer. ## Lịch sử phát triển của Transformers diff --git a/chapters/vi/chapter1/5.mdx b/chapters/vi/chapter1/5.mdx index f2cb94448..bc4aa15f1 100644 --- a/chapters/vi/chapter1/5.mdx +++ b/chapters/vi/chapter1/5.mdx @@ -1,5 +1,10 @@ # Các mô hình mã hóa + + Các mô hình mã hóa chỉ sử dụng phần mã hóa của mô hình Transformer. Ở mỗi bước, các lớp attention có thể truy cập tất cả các từ trong câu ban đầu. Những mô hình này thường có đặc trưng là chú ý "hai chiều" và thường được gọi là mô hình _auto-encoding_ hay _mã hóa tự động_. diff --git a/chapters/vi/chapter1/6.mdx b/chapters/vi/chapter1/6.mdx index fa577ea9e..3ddd84eb9 100644 --- a/chapters/vi/chapter1/6.mdx +++ b/chapters/vi/chapter1/6.mdx @@ -1,5 +1,10 @@ # CCác mô hình giải mã + + Mô hình giải mã chỉ sử dụng phần giải mã của mô hình Transformer. Ở mỗi bước, đối với một từ nhất định, các lớp attention chỉ có thể truy cập các từ được đặt trước nó trong câu. Những mô hình này thường được gọi là mô hình _auto-regressive_ hay _hồi quy tự động_. diff --git a/chapters/vi/chapter1/7.mdx b/chapters/vi/chapter1/7.mdx index d2c894cfb..18f3f4c60 100644 --- a/chapters/vi/chapter1/7.mdx +++ b/chapters/vi/chapter1/7.mdx @@ -1,5 +1,10 @@ # Các mô hình mã hoá-giải mã + + Các mô hình mã hóa-giải mã (còn được gọi là _mô hình chuỗi-sang-chuỗi_) sử dụng cả hai phần của kiến trúc Transformer. Ở mỗi bước, các lớp attention của phần mã hóa có thể truy cập tất cả các từ trong câu ban đầu, trong khi các lớp attention của phần giải mã chỉ có thể truy cập các từ được đặt trước một từ nhất định trong đầu vào. diff --git a/chapters/vi/chapter1/8.mdx b/chapters/vi/chapter1/8.mdx index 3ce04e0fd..e9a22fc58 100644 --- a/chapters/vi/chapter1/8.mdx +++ b/chapters/vi/chapter1/8.mdx @@ -1,8 +1,8 @@ # Thiên kiến và hạn chế - + Trong chương này, bạn đã biết cách tiếp cận các tác vụ NLP khác nhau bằng cách sử dụng hàm `pipeline()` cấp cao từ 🤗 Transformers. Bạn cũng đã biết cách tìm kiếm và sử dụng các mô hình trong Hub, cũng như cách sử dụng Inference API để kiểm tra các mô hình trực tiếp trong trình duyệt của mình. Chúng ta đã thảo luận về cách các mô hình Transformer hoạt động ở cấp độ cao và nói về tầm quan trọng của việc học chuyển giao và tinh chỉnh. Một khía cạnh quan trọng, đó là bạn có thể sử dụng kiến trúc đầy đủ hoặc chỉ phần mã hóa hoặc giải mã, tùy thuộc vào loại tác vụ bạn muốn giải quyết. Bảng dưới đây tóm tắt khía cạnh này: diff --git a/chapters/vi/chapter2/1.mdx b/chapters/vi/chapter2/1.mdx index e3fd44f8b..8bbce9975 100644 --- a/chapters/vi/chapter2/1.mdx +++ b/chapters/vi/chapter2/1.mdx @@ -1,5 +1,10 @@ # Giới thiệu + + Như bạn đã thấy trong [Chương 1](/course/chapter1), các mô hình Transformer thường rất lớn. Với hàng triệu đến hàng chục *tỷ* thông số, việc huấn luyện và triển khai các mô hình này là một công việc phức tạp. Hơn nữa, với việc các mô hình mới được phát hành gần như hàng ngày và mỗi mô hình có cách triển khai riêng, việc thử tất cả chúng không phải là nhiệm vụ dễ dàng. Thư viện 🤗 Transformers được tạo ra để giải quyết vấn đề này. Mục tiêu của nó là cung cấp một API duy nhất mà qua đó bất kỳ mô hình Transformer nào cũng có thể được tải, huấn luyện, và lưu. Các tính năng chính của thư viện gồm: diff --git a/chapters/vi/chapter2/2.mdx b/chapters/vi/chapter2/2.mdx index b27e6365f..0c43c6f9b 100644 --- a/chapters/vi/chapter2/2.mdx +++ b/chapters/vi/chapter2/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/vi/chapter2/3.mdx b/chapters/vi/chapter2/3.mdx index 4d7021969..48f384d3d 100644 --- a/chapters/vi/chapter2/3.mdx +++ b/chapters/vi/chapter2/3.mdx @@ -4,9 +4,9 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/vi/chapter2/5.mdx b/chapters/vi/chapter2/5.mdx index 40c583154..78a9b0b5c 100644 --- a/chapters/vi/chapter2/5.mdx +++ b/chapters/vi/chapter2/5.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/vi/chapter2/6.mdx b/chapters/vi/chapter2/6.mdx index 8ffd62368..52c8c2caf 100644 --- a/chapters/vi/chapter2/6.mdx +++ b/chapters/vi/chapter2/6.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/vi/chapter2/7.mdx b/chapters/vi/chapter2/7.mdx index a96ff4fdf..6ea45cada 100644 --- a/chapters/vi/chapter2/7.mdx +++ b/chapters/vi/chapter2/7.mdx @@ -1,5 +1,10 @@ # Hoàn thành cách sử dụng cơ bản! + + Thật tuyệt vời khi theo dõi khóa học đến đây! Tổng kết lại, trong chương này bạn: - Đã học các khối cơ bản của mô hình Transformer. diff --git a/chapters/vi/chapter2/8.mdx b/chapters/vi/chapter2/8.mdx index daf41d7ef..948a0f573 100644 --- a/chapters/vi/chapter2/8.mdx +++ b/chapters/vi/chapter2/8.mdx @@ -4,6 +4,11 @@ # Đố vui cuối chương + + ### 1. Thứ tự của một quy trình mô hình hóa ngôn ngữ là gì? + Trong [Chương 2](/course/chapter2), chúng ta đã khám phá cách sử dụng tokenizer và các mô hình huấn luyện trước để đưa ra dự đoán. Nhưng nếu bạn muốn tinh chỉnh một mô hình được huấn luyện trước cho tập dữ liệu của riêng mình thì sao? Đó là chủ đề của chương này! Bạn sẽ học: {#if fw === 'pt'} diff --git a/chapters/vi/chapter3/2.mdx b/chapters/vi/chapter3/2.mdx index dd3d633bb..dfbf3a1bd 100644 --- a/chapters/vi/chapter3/2.mdx +++ b/chapters/vi/chapter3/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/vi/chapter3/3.mdx b/chapters/vi/chapter3/3.mdx index 980165a91..c254c523c 100644 --- a/chapters/vi/chapter3/3.mdx +++ b/chapters/vi/chapter3/3.mdx @@ -2,9 +2,9 @@ # Tinh chỉnh một mô hình với Trainer API - diff --git a/chapters/vi/chapter3/3_tf.mdx b/chapters/vi/chapter3/3_tf.mdx index a451985d1..29ec33972 100644 --- a/chapters/vi/chapter3/3_tf.mdx +++ b/chapters/vi/chapter3/3_tf.mdx @@ -2,9 +2,9 @@ # Tinh chỉnh một mô hình với Keras - diff --git a/chapters/vi/chapter3/4.mdx b/chapters/vi/chapter3/4.mdx index 24e47a91f..8e42a0572 100644 --- a/chapters/vi/chapter3/4.mdx +++ b/chapters/vi/chapter3/4.mdx @@ -1,8 +1,8 @@ # Bản huấn luyện hoàn chỉnh - diff --git a/chapters/vi/chapter3/5.mdx b/chapters/vi/chapter3/5.mdx index d50018fda..c8bf597b9 100644 --- a/chapters/vi/chapter3/5.mdx +++ b/chapters/vi/chapter3/5.mdx @@ -2,6 +2,11 @@ # Tỉnh chỉnh, thử xem! + + Trong hai chương đầu tiên, bạn đã học về các mô hình và tokenizer, và bây giờ bạn biết cách tinh chỉnh chúng cho dữ liệu của riêng bạn. Tóm lại, trong chương này bạn: {#if fw === 'pt'} diff --git a/chapters/vi/chapter3/6.mdx b/chapters/vi/chapter3/6.mdx index 2a87303de..f011e6f95 100644 --- a/chapters/vi/chapter3/6.mdx +++ b/chapters/vi/chapter3/6.mdx @@ -4,6 +4,11 @@ # Đố vui cuối chương + + Kiểm tra những gì bạn đã học trong chương này! ### 1. Tập dữ liệu `emotion` chứa các tin nhắn Twitter được gắn nhãn cảm xúc. Tìm kiếm nó trong [Hub](https://huggingface.co/datasets) và đọc thẻ tập dữ liệu. Cảm xúc nào trong số này không phải là một trong những cảm xúc cơ bản của nó? diff --git a/chapters/vi/chapter4/1.mdx b/chapters/vi/chapter4/1.mdx index 952b5193a..df3f81f1e 100644 --- a/chapters/vi/chapter4/1.mdx +++ b/chapters/vi/chapter4/1.mdx @@ -1,5 +1,10 @@ # Hugging Face Hub + + [Hugging Face Hub](https://huggingface.co/) –- trang web chính của chúng tôi –- là một nền tảng tập trung cho phép mọi người khám phá, sử dụng và đóng góp các mô hình và bộ dữ liệu hiện đại nhất. Nó lưu trữ nhiều mô hình khác nhau, với hơn 10,000 mô hình được công bố rộng rãi. Chúng ta sẽ tập trung vào các mô hình trong chương này và xem xét các tập dữ liệu trong Chương 5. Các mô hình trong Hub không giới hạn ở 🤗 Transformer hoặc thậm chí là NLP. Có các mô hình từ [Flair](https://github.com/flairNLP/flair) và [AllenNLP](https://github.com/allenai/allennlp) cho NLP, [Asteroid](https://github.com/asteroid-team/asteroid) và [pyannote](https://github.com/pyannote/pyannote-audio) cho âm thanh và [timm](https://github.com/rwightman/pytorch-image-models) cho hình ảnh. diff --git a/chapters/vi/chapter4/2.mdx b/chapters/vi/chapter4/2.mdx index d094cb86b..f0d35fd18 100644 --- a/chapters/vi/chapter4/2.mdx +++ b/chapters/vi/chapter4/2.mdx @@ -4,9 +4,9 @@ {#if fw === 'pt'} - + Thẻ mô hình là một tệp được cho là quan trọng như mô hình và tệp tokenizer trong kho lưu trữ mô hình. Đây là định nghĩa chủ đạo của mô hình, đảm bảo khả năng tái sử dụng của các thành viên trong cộng đồng và khả năng tái tạo kết quả, đồng thời cung cấp một nền tảng mà các thành viên khác có thể xây dựng các tác phẩm của họ. Việc ghi lại quá trình huấn luyện và đánh giá giúp những người khác hiểu những gì mong đợi ở một mô hình - và cung cấp đầy đủ thông tin liên quan đến dữ liệu đã được sử dụng và quá trình tiền xử lý và hậu xử lý đã được thực hiện đảm bảo rằng các hạn chế, thành kiến ​​và bối cảnh trong đó mô hình đang và không hữu ích có thể được xác định và hiểu. diff --git a/chapters/vi/chapter4/5.mdx b/chapters/vi/chapter4/5.mdx index 15f1a2ef2..062cc560d 100644 --- a/chapters/vi/chapter4/5.mdx +++ b/chapters/vi/chapter4/5.mdx @@ -1,5 +1,10 @@ # Hoàn thành phần 1! + + Đây là mục cuối của phần đầu tiên trong khóa học! Phần 2 sẽ ra mắt vào ngày 15/11 tới đây với một sự kiện cộng đồng lớn, xem thêm thông tin [tại đây](https://huggingface.co/blog/course-launch-event). Giờ đây, bạn có thể tinh chỉnh mô hình được huấn luyện trước về vấn đề phân loại văn bản (đơn hoặc cặp câu) và tải kết quả lên Model Hub. Để đảm bảo bạn thành thạo phần đầu tiên này, bạn nên làm chính xác phần đó đối với một vấn đề mà bạn quan tâm (và không nhất thiết phải bằng tiếng Anh nếu bạn nói một ngôn ngữ khác)! Bạn có thể tìm trợ giúp trong [diễn đàn Hugging Face](https://discuss.huggingface.co/) và chia sẻ dự án của mình trong [chủ đề này](https://discuss.huggingface.co/t/share-your-projects/6803) sau khi bạn hoàn thành. diff --git a/chapters/vi/chapter4/6.mdx b/chapters/vi/chapter4/6.mdx index ab9c55658..d3a12f468 100644 --- a/chapters/vi/chapter4/6.mdx +++ b/chapters/vi/chapter4/6.mdx @@ -4,6 +4,11 @@ # Đố vui cuối chương + + Hãy kiểm tra những gì bạn đã học được trong chương này! ### 1. Các mô hình tải lên trên Hub có giới hạn gì? diff --git a/chapters/vi/chapter5/1.mdx b/chapters/vi/chapter5/1.mdx index d918cb67e..924e85888 100644 --- a/chapters/vi/chapter5/1.mdx +++ b/chapters/vi/chapter5/1.mdx @@ -1,5 +1,10 @@ # Giới thiệu + + Trong [Chương 3](/course/chapter3), bạn sẽ lần đầu được trải nghiệm thư viện 🤗 Datasets và thấy rằng có ba bước chính khi tinh chỉnh một mô hình: 1. Tải tập dữ liệu từ Hugging Face Hub. diff --git a/chapters/vi/chapter5/2.mdx b/chapters/vi/chapter5/2.mdx index eea04d929..7378423ca 100644 --- a/chapters/vi/chapter5/2.mdx +++ b/chapters/vi/chapter5/2.mdx @@ -1,8 +1,8 @@ # Nếu như dữ liệu của ta không trên Hub thì sao? - diff --git a/chapters/vi/chapter5/3.mdx b/chapters/vi/chapter5/3.mdx index eee6bb891..24b7115c8 100644 --- a/chapters/vi/chapter5/3.mdx +++ b/chapters/vi/chapter5/3.mdx @@ -1,8 +1,8 @@ # Sắp xếp dữ liệu - diff --git a/chapters/vi/chapter5/4.mdx b/chapters/vi/chapter5/4.mdx index b5a2b2348..bb0c9bbd5 100644 --- a/chapters/vi/chapter5/4.mdx +++ b/chapters/vi/chapter5/4.mdx @@ -1,8 +1,8 @@ # Dữ liệu lớn? 🤗 Bộ dữ liệu để giải cứu! - diff --git a/chapters/vi/chapter5/6.mdx b/chapters/vi/chapter5/6.mdx index e6803fb27..6a0d9423e 100644 --- a/chapters/vi/chapter5/6.mdx +++ b/chapters/vi/chapter5/6.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/vi/chapter5/7.mdx b/chapters/vi/chapter5/7.mdx index 02ae4bd7e..0844001a3 100644 --- a/chapters/vi/chapter5/7.mdx +++ b/chapters/vi/chapter5/7.mdx @@ -1,5 +1,10 @@ # 🤗 Datasets, kiểm tra nào! + + Chà, đó là một chuyến tham quan khá thú vị qua thư viện 🤗 Datasets - chúc mừng bạn đã đi được xa như vậy! Với kiến thức bạn đã thu được từ chương này, bạn sẽ có thể: - Tải bộ dữ liệu từ bất kỳ đâu, có thể là Hugging Face Hub, máy tính xách tay của bạn hoặc máy chủ từ xa tại công ty của bạn. diff --git a/chapters/vi/chapter5/8.mdx b/chapters/vi/chapter5/8.mdx index 40fafb58d..5303d1f5e 100644 --- a/chapters/vi/chapter5/8.mdx +++ b/chapters/vi/chapter5/8.mdx @@ -2,6 +2,11 @@ # Đố vui cuối chương + + Chương này bao gồm rất nhiều nội dung! Đừng lo lắng nếu bạn không nắm được tất cả các chi tiết; các chương tiếp theo sẽ giúp bạn hiểu mọi thứ hoạt động như thế nào. Tuy nhiên, trước khi tiếp tục, hãy kiểm tra những gì bạn đã học được trong chương này. diff --git a/chapters/vi/chapter6/1.mdx b/chapters/vi/chapter6/1.mdx index 6540876b7..bcfe7eada 100644 --- a/chapters/vi/chapter6/1.mdx +++ b/chapters/vi/chapter6/1.mdx @@ -1,5 +1,10 @@ # Giới thiệu + + Trong [Chương 3](/course/chapter3), chúng ta đã xem xét cách tinh chỉnh một mô hình trong một tác vụ nhất định. Khi làm điều đó, chúng ta sử dụng cùng một trình tokenizer mà mô hình đã được huấn luyện trước - nhưng chúng ra phải làm gì khi muốn huấn luyện một mô hình từ đầu? Trong những trường hợp này, việc sử dụng trình tokenizer đã được huấn luyện trước trên một kho ngữ liệu từ một lĩnh vực hoặc ngôn ngữ khác thường là không tối ưu. Ví dụ: một tokenizer được huấn luyện trên ngữ liệu tiếng Anh sẽ hoạt động kém trên ngữ liệu văn bản tiếng Nhật vì việc sử dụng dấu cách và dấu câu trong hai ngôn ngữ rất khác nhau. Trong chương này, bạn sẽ học cách huấn luyện một trình tokenize hoàn toàn mới trên kho ngữ liệu văn bản, do đó, nó có thể được sử dụng để huấn luyện trước một mô hình ngôn ngữ. Tất cả điều này sẽ được thực hiện với sự trợ giúp của thư viện [🤗 Tokenizers](https://github.com/huggingface/tokenizers), nơi cung cấp các tokenizer "nhanh" trong thư viện [🤗 Transformers](https://github.com/huggingface/transformers). Chúng ta sẽ xem xét kỹ các tính năng mà thư viện này cung cấp và khám phá cách các bản tokenizer nhanh khác so với các phiên bản "chậm". diff --git a/chapters/vi/chapter6/10.md b/chapters/vi/chapter6/10.md index 0a678358a..f7ea9745d 100644 --- a/chapters/vi/chapter6/10.md +++ b/chapters/vi/chapter6/10.md @@ -2,6 +2,11 @@ # Đố vui cuối chương + + Let's test what you learned in this chapter! ### 1. When should you train a new tokenizer? diff --git a/chapters/vi/chapter6/2.mdx b/chapters/vi/chapter6/2.mdx index 2f0287bcd..ed7611d0a 100644 --- a/chapters/vi/chapter6/2.mdx +++ b/chapters/vi/chapter6/2.mdx @@ -1,8 +1,8 @@ # Huấn luyện một tokenizer mới từ cái cũ - diff --git a/chapters/vi/chapter6/3.md b/chapters/vi/chapter6/3.md index c43fead52..41576f859 100644 --- a/chapters/vi/chapter6/3.md +++ b/chapters/vi/chapter6/3.md @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/vi/chapter6/3b.md b/chapters/vi/chapter6/3b.md index 595235c8b..c0f64d63f 100644 --- a/chapters/vi/chapter6/3b.md +++ b/chapters/vi/chapter6/3b.md @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/vi/chapter6/4.md b/chapters/vi/chapter6/4.md index 6710ed42a..b4a7757a1 100644 --- a/chapters/vi/chapter6/4.md +++ b/chapters/vi/chapter6/4.md @@ -1,8 +1,8 @@ # Chuẩn hoá và tiền tokenize - diff --git a/chapters/vi/chapter6/5.md b/chapters/vi/chapter6/5.md index a9f070b0e..18b189bc5 100644 --- a/chapters/vi/chapter6/5.md +++ b/chapters/vi/chapter6/5.md @@ -1,8 +1,8 @@ # Byte-Pair Encoding tokenization - diff --git a/chapters/vi/chapter6/6.md b/chapters/vi/chapter6/6.md index d4152cd5e..7e60dabb0 100644 --- a/chapters/vi/chapter6/6.md +++ b/chapters/vi/chapter6/6.md @@ -1,8 +1,8 @@ # WordPiece tokenization - diff --git a/chapters/vi/chapter6/7.md b/chapters/vi/chapter6/7.md index e23d6f473..2a4d6f2f3 100644 --- a/chapters/vi/chapter6/7.md +++ b/chapters/vi/chapter6/7.md @@ -1,8 +1,8 @@ # Unigram tokenization - diff --git a/chapters/vi/chapter6/8.md b/chapters/vi/chapter6/8.md index 9e54d568a..f5f4f3de1 100644 --- a/chapters/vi/chapter6/8.md +++ b/chapters/vi/chapter6/8.md @@ -1,8 +1,8 @@ # Xây dựng từng khối tokenizer - diff --git a/chapters/vi/chapter6/9.mdx b/chapters/vi/chapter6/9.mdx index cc16eeeec..63f567381 100644 --- a/chapters/vi/chapter6/9.mdx +++ b/chapters/vi/chapter6/9.mdx @@ -1,5 +1,10 @@ # Tokenizers, kiểm tra nào! + + Chúc mừng bạn đã hoàn thành chương này! Sau khi tìm hiểu sâu về tokenizer, bạn nên: diff --git a/chapters/zh-CN/chapter1/1.mdx b/chapters/zh-CN/chapter1/1.mdx index 4ab545a0c..fcd439329 100644 --- a/chapters/zh-CN/chapter1/1.mdx +++ b/chapters/zh-CN/chapter1/1.mdx @@ -1,52 +1,57 @@ -# 本章简介 - -## 欢迎来到🤗课程 - - - -本课程将使用 Hugging Face 生态系统中的库——🤗 Transformers、🤗 Datasets、🤗 Tokenizers 和 🤗 Accelerate——以及 Hugging Face Hub 教你自然语言处理 (NLP)。它是完全免费的,并且没有广告。 - - -## 有什么是值得期待的? - -以下是课程的简要概述: - -
-Brief overview of the chapters of the course. - -
- -- 第 1 章到第 4 章介绍了 🤗 Transformers 库的主要概念。在本课程的这一部分结束时,您将熟悉 Transformer 模型的工作原理,并将了解如何使用 [Hugging Face Hub](https://huggingface.co/models) 中的模型,在数据集上对其进行微调,并在 Hub 上分享您的结果。 -- 第 5 章到第 8 章在深入研究经典 NLP 任务之前,教授 🤗 Datasets和 🤗 Tokenizers的基础知识。在本部分结束时,您将能够自己解决最常见的 NLP 问题。 -- 第 9 章到第 12 章更加深入,探讨了如何使用 Transformer 模型处理语音处理和计算机视觉中的任务。在此过程中,您将学习如何构建和分享模型,并针对生产环境对其进行优化。在这部分结束时,您将准备好将🤗 Transformers 应用于(几乎)任何机器学习问题! - -这个课程: - -* 需要良好的 Python 知识 -* 最好先学习深度学习入门课程,例如[DeepLearning.AI](https://www.deeplearning.ai/) 提供的 [fast.ai实用深度学习教程](https://course.fast.ai/) -* 不需要事先具备 [PyTorch](https://pytorch.org/) 或 [TensorFlow](https://www.tensorflow.org/) 知识,虽然熟悉其中任何一个都会对huggingface的学习有所帮助 - -完成本课程后,我们建议您查看 [DeepLearning.AI的自然语言处理系列课程](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh),其中涵盖了广泛的传统 NLP 模型,如朴素贝叶斯和 LSTM,这些模型非常值得了解! - -## 我们是谁? - -关于作者: - -**Matthew Carrigan** 是 Hugging Face 的机器学习工程师。他住在爱尔兰都柏林,之前在 Parse.ly 担任机器学习工程师,在此之前,他在Trinity College Dublin担任博士后研究员。他不相信我们会通过扩展现有架构来实现 AGI,但无论如何都对机器人充满希望。 - -**Lysandre Debut** 是 Hugging Face 的机器学习工程师,从早期的开发阶段就一直致力于 🤗 Transformers 库。他的目标是通过使用非常简单的 API 开发工具,让每个人都可以使用 NLP。 - -**Sylvain Gugger** 是 Hugging Face 的一名研究工程师,也是 🤗Transformers库的核心维护者之一。此前,他是 fast.ai 的一名研究科学家,他与Jeremy Howard 共同编写了[Deep Learning for Coders with fastai and Py Torch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)。他的主要研究重点是通过设计和改进允许模型在有限资源上快速训练的技术,使深度学习更容易普及。 - -**Merve Noyan** 是 Hugging Face 的开发者倡导者,致力于开发工具并围绕它们构建内容,以使每个人的机器学习平民化。 - -**Lucile Saulnier** 是 Hugging Face 的机器学习工程师,负责开发和支持开源工具的使用。她还积极参与了自然语言处理领域的许多研究项目,例如协作训练和 BigScience。 - -**Lewis Tunstall** 是 Hugging Face 的机器学习工程师,专注于开发开源工具并使更广泛的社区可以使用它们。他也是即将出版的一本书[O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/)的作者之一。 - -**Leandro von Werra** 是 Hugging Face 开源团队的机器学习工程师,也是即将出版的一本书[O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/)的作者之一。他拥有多年的行业经验,通过在整个机器学习堆栈中工作,将 NLP 项目投入生产。 - -你准备好了吗?在本章中,您将学习: -* 如何使用 `pipeline()` 函数解决文本生成、分类等NLP任务 -* 关于 Transformer 架构 -* 如何区分编码器、解码器和编码器-解码器架构和用例 +# 本章简介 + + + +## 欢迎来到🤗课程 + + + +本课程将使用 Hugging Face 生态系统中的库——🤗 Transformers、🤗 Datasets、🤗 Tokenizers 和 🤗 Accelerate——以及 Hugging Face Hub 教你自然语言处理 (NLP)。它是完全免费的,并且没有广告。 + + +## 有什么是值得期待的? + +以下是课程的简要概述: + +
+Brief overview of the chapters of the course. + +
+ +- 第 1 章到第 4 章介绍了 🤗 Transformers 库的主要概念。在本课程的这一部分结束时,您将熟悉 Transformer 模型的工作原理,并将了解如何使用 [Hugging Face Hub](https://huggingface.co/models) 中的模型,在数据集上对其进行微调,并在 Hub 上分享您的结果。 +- 第 5 章到第 8 章在深入研究经典 NLP 任务之前,教授 🤗 Datasets和 🤗 Tokenizers的基础知识。在本部分结束时,您将能够自己解决最常见的 NLP 问题。 +- 第 9 章到第 12 章更加深入,探讨了如何使用 Transformer 模型处理语音处理和计算机视觉中的任务。在此过程中,您将学习如何构建和分享模型,并针对生产环境对其进行优化。在这部分结束时,您将准备好将🤗 Transformers 应用于(几乎)任何机器学习问题! + +这个课程: + +* 需要良好的 Python 知识 +* 最好先学习深度学习入门课程,例如[DeepLearning.AI](https://www.deeplearning.ai/) 提供的 [fast.ai实用深度学习教程](https://course.fast.ai/) +* 不需要事先具备 [PyTorch](https://pytorch.org/) 或 [TensorFlow](https://www.tensorflow.org/) 知识,虽然熟悉其中任何一个都会对huggingface的学习有所帮助 + +完成本课程后,我们建议您查看 [DeepLearning.AI的自然语言处理系列课程](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh),其中涵盖了广泛的传统 NLP 模型,如朴素贝叶斯和 LSTM,这些模型非常值得了解! + +## 我们是谁? + +关于作者: + +**Matthew Carrigan** 是 Hugging Face 的机器学习工程师。他住在爱尔兰都柏林,之前在 Parse.ly 担任机器学习工程师,在此之前,他在Trinity College Dublin担任博士后研究员。他不相信我们会通过扩展现有架构来实现 AGI,但无论如何都对机器人充满希望。 + +**Lysandre Debut** 是 Hugging Face 的机器学习工程师,从早期的开发阶段就一直致力于 🤗 Transformers 库。他的目标是通过使用非常简单的 API 开发工具,让每个人都可以使用 NLP。 + +**Sylvain Gugger** 是 Hugging Face 的一名研究工程师,也是 🤗Transformers库的核心维护者之一。此前,他是 fast.ai 的一名研究科学家,他与Jeremy Howard 共同编写了[Deep Learning for Coders with fastai and Py Torch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)。他的主要研究重点是通过设计和改进允许模型在有限资源上快速训练的技术,使深度学习更容易普及。 + +**Merve Noyan** 是 Hugging Face 的开发者倡导者,致力于开发工具并围绕它们构建内容,以使每个人的机器学习平民化。 + +**Lucile Saulnier** 是 Hugging Face 的机器学习工程师,负责开发和支持开源工具的使用。她还积极参与了自然语言处理领域的许多研究项目,例如协作训练和 BigScience。 + +**Lewis Tunstall** 是 Hugging Face 的机器学习工程师,专注于开发开源工具并使更广泛的社区可以使用它们。他也是即将出版的一本书[O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/)的作者之一。 + +**Leandro von Werra** 是 Hugging Face 开源团队的机器学习工程师,也是即将出版的一本书[O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/)的作者之一。他拥有多年的行业经验,通过在整个机器学习堆栈中工作,将 NLP 项目投入生产。 + +你准备好了吗?在本章中,您将学习: +* 如何使用 `pipeline()` 函数解决文本生成、分类等NLP任务 +* 关于 Transformer 架构 +* 如何区分编码器、解码器和编码器-解码器架构和用例 diff --git a/chapters/zh-CN/chapter1/10.mdx b/chapters/zh-CN/chapter1/10.mdx index 23f768115..3e2951036 100644 --- a/chapters/zh-CN/chapter1/10.mdx +++ b/chapters/zh-CN/chapter1/10.mdx @@ -2,6 +2,11 @@ # 章末小测试 + + 这一章涵盖了很多内容! 如果有一些不太明白的地方,请不要担心; 下一章将帮助你了解这些模块在底层是如何工作的。 让我们来测试一下你在这一章学到了什么! diff --git a/chapters/zh-CN/chapter1/2.mdx b/chapters/zh-CN/chapter1/2.mdx index 1b5ee0ea6..982425c76 100644 --- a/chapters/zh-CN/chapter1/2.mdx +++ b/chapters/zh-CN/chapter1/2.mdx @@ -1,20 +1,25 @@ -# 自然语言处理 - -在进入 Transformer 模型之前,让我们快速概述一下自然语言处理是什么以及我们为什么这么重视它。 - -## 什么是自然语言处理? - -NLP 是语言学和机器学习交叉领域,专注于理解与人类语言相关的一切。 NLP 任务的目标不仅是单独理解单个单词,而且是能够理解这些单词的上下文。 - -以下是常见 NLP 任务的列表,每个任务都有一些示例: - -- **对整个句子进行分类**: 获取评论的情绪,检测电子邮件是否为垃圾邮件,确定句子在语法上是否正确或两个句子在逻辑上是否相关 -- **对句子中的每个词进行分类**: 识别句子的语法成分(名词、动词、形容词)或命名实体(人、地点、组织) -- **生成文本内容**: 用自动生成的文本完成提示,用屏蔽词填充文本中的空白 -- **从文本中提取答案**: 给定问题和上下文,根据上下文中提供的信息提取问题的答案 -- **从输入文本生成新句子**: 将文本翻译成另一种语言,总结文本 - -NLP 不仅限于书面文本。它还解决了语音识别和计算机视觉中的复杂挑战,例如生成音频样本的转录或图像描述。 -## 为什么具有挑战性? - +# 自然语言处理 + + + +在进入 Transformer 模型之前,让我们快速概述一下自然语言处理是什么以及我们为什么这么重视它。 + +## 什么是自然语言处理? + +NLP 是语言学和机器学习交叉领域,专注于理解与人类语言相关的一切。 NLP 任务的目标不仅是单独理解单个单词,而且是能够理解这些单词的上下文。 + +以下是常见 NLP 任务的列表,每个任务都有一些示例: + +- **对整个句子进行分类**: 获取评论的情绪,检测电子邮件是否为垃圾邮件,确定句子在语法上是否正确或两个句子在逻辑上是否相关 +- **对句子中的每个词进行分类**: 识别句子的语法成分(名词、动词、形容词)或命名实体(人、地点、组织) +- **生成文本内容**: 用自动生成的文本完成提示,用屏蔽词填充文本中的空白 +- **从文本中提取答案**: 给定问题和上下文,根据上下文中提供的信息提取问题的答案 +- **从输入文本生成新句子**: 将文本翻译成另一种语言,总结文本 + +NLP 不仅限于书面文本。它还解决了语音识别和计算机视觉中的复杂挑战,例如生成音频样本的转录或图像描述。 +## 为什么具有挑战性? + 计算机处理信息的方式与人类不同。例如,当我们读到“我饿了”这句话时,我们很容易理解它的意思。同样,给定两个句子,例如“我很饿”和“我很伤心”,我们可以轻松确定它们的相似程度。对于机器学习 (ML) 模型,此类任务更加困难。文本需要以一种使模型能够从中学习的方式进行处理。而且由于语言很复杂,我们需要仔细考虑必须如何进行这种处理。关于如何表示文本已经做了很多研究,我们将在下一章中介绍一些方法。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter1/3.mdx b/chapters/zh-CN/chapter1/3.mdx index 076263ba4..ec5820720 100644 --- a/chapters/zh-CN/chapter1/3.mdx +++ b/chapters/zh-CN/chapter1/3.mdx @@ -1,8 +1,8 @@ # Transformers能做什么? - diff --git a/chapters/zh-CN/chapter1/4.mdx b/chapters/zh-CN/chapter1/4.mdx index 45641e71e..a2f8a839f 100644 --- a/chapters/zh-CN/chapter1/4.mdx +++ b/chapters/zh-CN/chapter1/4.mdx @@ -1,172 +1,177 @@ -# Transformers 是如何工作的? - -在本节中,我们将深入了解 Transformer 模型的架构。 - -## 一点Transformers的发展历史 - -以下是 Transformer 模型(简短)历史中的一些关键节点: - -
-A brief chronology of Transformers models. - -
- -[Transformer 架构](https://arxiv.org/abs/1706.03762) 于 2017 年 6 月推出。原本研究的重点是翻译任务。随后推出了几个有影响力的模型,包括 - -- **2018 年 6 月**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), 第一个预训练的 Transformer 模型,用于各种 NLP 任务并获得极好的结果 - -- **2018 年 10 月**: [BERT](https://arxiv.org/abs/1810.04805), 另一个大型预训练模型,该模型旨在生成更好的句子摘要(下一章将详细介绍!) - -- **2019 年 2 月**: [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), GPT 的改进(并且更大)版本,由于道德问题没有立即公开发布 - -- **2019 年 10 月**: [DistilBERT](https://arxiv.org/abs/1910.01108), BERT 的提炼版本,速度提高 60%,内存减轻 40%,但仍保留 BERT 97% 的性能 - -- **2019 年 10 月**: [BART](https://arxiv.org/abs/1910.13461) 和 [T5](https://arxiv.org/abs/1910.10683), 两个使用与原始 Transformer 模型相同架构的大型预训练模型(第一个这样做) - -- **2020 年 5 月**, [GPT-3](https://arxiv.org/abs/2005.14165), GPT-2 的更大版本,无需微调即可在各种任务上表现良好(称为零样本学习) - -这个列表并不全面,只是为了突出一些不同类型的 Transformer 模型。大体上,它们可以分为三类: - -- GPT-like (也被称作自回归Transformer模型) -- BERT-like (也被称作自动编码Transformer模型) -- BART/T5-like (也被称作序列到序列的 Transformer模型) - -稍后我们将更深入地探讨这些分类。 - -## Transformers是语言模型 - -上面提到的所有 Transformer 模型(GPT、BERT、BART、T5 等)都被训练为语言模型。这意味着他们已经以无监督学习的方式接受了大量原始文本的训练。无监督学习是一种训练类型,其中目标是根据模型的输入自动计算的。这意味着不需要人工来标记数据! - -这种类型的模型可以对其训练过的语言进行统计理解,但对于特定的实际任务并不是很有用。因此,一般的预训练模型会经历一个称为*迁移学习*的过程。在此过程中,模型在给定任务上以监督方式(即使用人工注释标签)进行微调。 - -任务的一个例子是阅读 *n* 个单词的句子,预测下一个单词。这被称为因果语言建模,因为输出取决于过去和现在的输入。 - -
-Example of causal language modeling in which the next word from a sentence is predicted. - -
- -另一个例子是*遮罩语言建模*,该模型预测句子中的遮住的词。 - -
-Example of masked language modeling in which a masked word from a sentence is predicted. - -
- -## Transformer是大模型 - -除了一些特例(如 DistilBERT)外,实现更好性能的一般策略是增加模型的大小以及预训练的数据量。 - -
-Number of parameters of recent Transformers models -
- -不幸的是,训练模型,尤其是大型模型,需要大量的数据,时间和计算资源。它甚至会对环境产生影响,如下图所示。 - -
-The carbon footprint of a large language model. - -
- - - -Transformers是由一个团队领导的(非常大的)模型项目,该团队试图减少预训练对环境的影响,通过运行大量试验以获得最佳超参数。 - -想象一下,如果每次一个研究团队、一个学生组织或一家公司想要训练一个模型,都从头开始训练的。这将导致巨大的、不必要的浪费! - -这就是为什么共享语言模型至关重要:共享经过训练的权重,当遇见新的需求时在预训练的权重之上进行微调,可以降低训练模型训练的算力和时间消耗,降低全球的总体计算成本和碳排放。 - - -## 迁移学习 - - - -*预训练是*训练模型前的一个操作:随机初始化权重,在没有任何先验知识的情况下开始训练。 - -
-The pretraining of a language model is costly in both time and money. - -
- -这种预训练通常是在非常大量的数据上进行的。因此,它需要大量的数据,而且训练可能需要几周的时间。 - -另一方面,*微调*是在模型经过预训练后完成的训练。要执行微调,首先需要获取一个经过预训练的语言模型,然后使用特定于任务的数据集执行额外的训练。等等,为什么不直接为最后的任务而训练呢?有几个原因: - -* 预训练模型已经在与微调数据集有一些相似之处的数据集上进行了训练。因此,微调过程能够利用模型在预训练期间获得的知识(例如,对于NLP问题,预训练模型将对您在任务中使用的语言有某种统计规律上的理解)。 -* 由于预训练模型已经在大量数据上进行了训练,因此微调需要更少的数据来获得不错的结果。 -* 出于同样的原因,获得好结果所需的时间和资源要少得多 - -例如,可以利用英语的预训练过的模型,然后在arXiv语料库上对其进行微调,从而形成一个基于科学/研究的模型。微调只需要有限的数据量:预训练模型获得的知识可以“迁移”到目标任务上,因此被称为*迁移学习*。 - -
-The fine-tuning of a language model is cheaper than pretraining in both time and money. - -
- -因此,微调模型具有较低的时间、数据、财务和环境成本。迭代不同的微调方案也更快、更容易,因为与完整的预训练相比,训练的约束更少。 - -这个过程也会比从头开始的训练(除非你有很多数据)取得更好的效果,这就是为什么你应该总是尝试利用一个预训练的模型--一个尽可能接近你手头的任务的模型--并对其进行微调。 - -## 一般的体系结构 -在这一部分,我们将介绍Transformer模型的一般架构。如果你不理解其中的一些概念,不要担心;下文将详细介绍每个组件。 - - - -## 介绍 - -该模型主要由两个块组成: - -* **Encoder (左侧)**: 编码器接收输入并构建其表示(其特征)。这意味着对模型进行了优化,以从输入中获得理解。 -* **Decoder (右侧)**: 解码器使用编码器的表示(特征)以及其他输入来生成目标序列。这意味着该模型已针对生成输出进行了优化。 - -
-Architecture of a Transformers models - -
- -这些部件中的每一个都可以独立使用,具体取决于任务: - -* **Encoder-only models**: 适用于需要理解输入的任务,如句子分类和命名实体识别。 -* **Decoder-only models**: 适用于生成任务,如文本生成。 -* **Encoder-decoder models** 或者 **sequence-to-sequence models**: 适用于需要根据输入进行生成的任务,如翻译或摘要。 - -在后面的部分中,我们将单独地深入研究这些体系结构。 - -## 注意力层 - -Transformer模型的一个关键特性是*注意力层*。事实上,介绍Transformer架构的文章的标题是[“注意力就是你所需要的”](https://arxiv.org/abs/1706.03762)! 我们将在课程的后面更加深入地探索注意力层;现在,您需要知道的是,这一层将告诉模型在处理每个单词的表示时,要特别重视您传递给它的句子中的某些单词(并且或多或少地忽略其他单词)。 - -把它放在语境中,考虑将文本从英语翻译成法语的任务。在输入“You like this course”的情况下,翻译模型还需要注意相邻的单词“You”,以获得单词“like”的正确翻译,因为在法语中,动词“like”的变化取决于主题。然而,句子的其余部分对于该词的翻译没有用处。同样,在翻译“this”时,模型也需要注意“course”一词,因为“this”的翻译不同,取决于相关名词是单数还是复数。同样,句子中的其他单词对于“this”的翻译也不重要。对于更复杂的句子(以及更复杂的语法规则),模型需要特别注意可能出现在句子中更远位置的单词,以便正确地翻译每个单词。 - -同样的概念也适用于与自然语言相关的任何任务:一个词本身有一个含义,但这个含义受语境的影响很大,语境可以是研究该词之前或之后的任何其他词(或多个词)。 - -现在您已经了解了注意力层的含义,让我们更仔细地了解Transformer架构。 - -## 原始的结构 - -Transformer架构最初是为翻译而设计的。在训练期间,编码器接收特定语言的输入(句子),而解码器需要输出对应语言的翻译。在编码器中,注意力层可以使用一个句子中的所有单词(正如我们刚才看到的,给定单词的翻译可以取决于它在句子中的其他单词)。然而,解码器是按顺序工作的,并且只能注意它已经翻译过的句子中的单词。例如,当我们预测了翻译目标的前三个单词时,我们将它们提供给解码器,然后解码器使用编码器的所有输入来尝试预测第四个单词。 - -为了在训练过程中加快速度(当模型可以访问目标句子时),解码器会被输入整个目标,但不允许获取到要翻译的单词(如果它在尝试预测位置2的单词时可以访问位置2的单词,解码器就会偷懒,直接输出那个单词,从而无法学习到正确的语言关系!)。例如,当试图预测第4个单词时,注意力层只能获取位置1到3的单词。 - -最初的Transformer架构如下所示,编码器位于左侧,解码器位于右侧: - -
-Architecture of a Transformers models - -
- -注意,解码器块中的第一个注意力层关联到解码器的所有(过去的)输入,但是第二注意力层使用编码器的输出。因此,它可以访问整个输入句子,以最好地预测当前单词。这是非常有用的,因为不同的语言可以有语法规则将单词按不同的顺序排列,或者句子后面提供的一些上下文可能有助于确定给定单词的最佳翻译。 - -也可以在编码器/解码器中使用*注意力遮罩层*,以防止模型注意某些特殊单词。例如,在批处理句子时,填充特殊词使所有句子的长度一致。 - -## 架构与参数 - -在本课程中,当我们深入探讨Transformers模型时,您将看到 -架构、参数和模型 -。 这些术语的含义略有不同: - -* **架构**: 这是模型的骨架 -- 每个层的定义以及模型中发生的每个操作。 -* **Checkpoints**: 这些是将在给架构中结构中加载的权重。 -* **模型**: 这是一个笼统的术语,没有“架构”或“参数”那么精确:它可以指两者。为了避免歧义,本课程使用将使用架构和参数。 - -例如,BERT是一个架构,而 `bert-base-cased`, 这是谷歌团队为BERT的第一个版本训练的一组权重参数,是一个参数。我们可以说“BERT模型”和"`bert-base-cased`模型." +# Transformers 是如何工作的? + + + +在本节中,我们将深入了解 Transformer 模型的架构。 + +## 一点Transformers的发展历史 + +以下是 Transformer 模型(简短)历史中的一些关键节点: + +
+A brief chronology of Transformers models. + +
+ +[Transformer 架构](https://arxiv.org/abs/1706.03762) 于 2017 年 6 月推出。原本研究的重点是翻译任务。随后推出了几个有影响力的模型,包括 + +- **2018 年 6 月**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), 第一个预训练的 Transformer 模型,用于各种 NLP 任务并获得极好的结果 + +- **2018 年 10 月**: [BERT](https://arxiv.org/abs/1810.04805), 另一个大型预训练模型,该模型旨在生成更好的句子摘要(下一章将详细介绍!) + +- **2019 年 2 月**: [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), GPT 的改进(并且更大)版本,由于道德问题没有立即公开发布 + +- **2019 年 10 月**: [DistilBERT](https://arxiv.org/abs/1910.01108), BERT 的提炼版本,速度提高 60%,内存减轻 40%,但仍保留 BERT 97% 的性能 + +- **2019 年 10 月**: [BART](https://arxiv.org/abs/1910.13461) 和 [T5](https://arxiv.org/abs/1910.10683), 两个使用与原始 Transformer 模型相同架构的大型预训练模型(第一个这样做) + +- **2020 年 5 月**, [GPT-3](https://arxiv.org/abs/2005.14165), GPT-2 的更大版本,无需微调即可在各种任务上表现良好(称为零样本学习) + +这个列表并不全面,只是为了突出一些不同类型的 Transformer 模型。大体上,它们可以分为三类: + +- GPT-like (也被称作自回归Transformer模型) +- BERT-like (也被称作自动编码Transformer模型) +- BART/T5-like (也被称作序列到序列的 Transformer模型) + +稍后我们将更深入地探讨这些分类。 + +## Transformers是语言模型 + +上面提到的所有 Transformer 模型(GPT、BERT、BART、T5 等)都被训练为语言模型。这意味着他们已经以无监督学习的方式接受了大量原始文本的训练。无监督学习是一种训练类型,其中目标是根据模型的输入自动计算的。这意味着不需要人工来标记数据! + +这种类型的模型可以对其训练过的语言进行统计理解,但对于特定的实际任务并不是很有用。因此,一般的预训练模型会经历一个称为*迁移学习*的过程。在此过程中,模型在给定任务上以监督方式(即使用人工注释标签)进行微调。 + +任务的一个例子是阅读 *n* 个单词的句子,预测下一个单词。这被称为因果语言建模,因为输出取决于过去和现在的输入。 + +
+Example of causal language modeling in which the next word from a sentence is predicted. + +
+ +另一个例子是*遮罩语言建模*,该模型预测句子中的遮住的词。 + +
+Example of masked language modeling in which a masked word from a sentence is predicted. + +
+ +## Transformer是大模型 + +除了一些特例(如 DistilBERT)外,实现更好性能的一般策略是增加模型的大小以及预训练的数据量。 + +
+Number of parameters of recent Transformers models +
+ +不幸的是,训练模型,尤其是大型模型,需要大量的数据,时间和计算资源。它甚至会对环境产生影响,如下图所示。 + +
+The carbon footprint of a large language model. + +
+ + + +Transformers是由一个团队领导的(非常大的)模型项目,该团队试图减少预训练对环境的影响,通过运行大量试验以获得最佳超参数。 + +想象一下,如果每次一个研究团队、一个学生组织或一家公司想要训练一个模型,都从头开始训练的。这将导致巨大的、不必要的浪费! + +这就是为什么共享语言模型至关重要:共享经过训练的权重,当遇见新的需求时在预训练的权重之上进行微调,可以降低训练模型训练的算力和时间消耗,降低全球的总体计算成本和碳排放。 + + +## 迁移学习 + + + +*预训练是*训练模型前的一个操作:随机初始化权重,在没有任何先验知识的情况下开始训练。 + +
+The pretraining of a language model is costly in both time and money. + +
+ +这种预训练通常是在非常大量的数据上进行的。因此,它需要大量的数据,而且训练可能需要几周的时间。 + +另一方面,*微调*是在模型经过预训练后完成的训练。要执行微调,首先需要获取一个经过预训练的语言模型,然后使用特定于任务的数据集执行额外的训练。等等,为什么不直接为最后的任务而训练呢?有几个原因: + +* 预训练模型已经在与微调数据集有一些相似之处的数据集上进行了训练。因此,微调过程能够利用模型在预训练期间获得的知识(例如,对于NLP问题,预训练模型将对您在任务中使用的语言有某种统计规律上的理解)。 +* 由于预训练模型已经在大量数据上进行了训练,因此微调需要更少的数据来获得不错的结果。 +* 出于同样的原因,获得好结果所需的时间和资源要少得多 + +例如,可以利用英语的预训练过的模型,然后在arXiv语料库上对其进行微调,从而形成一个基于科学/研究的模型。微调只需要有限的数据量:预训练模型获得的知识可以“迁移”到目标任务上,因此被称为*迁移学习*。 + +
+The fine-tuning of a language model is cheaper than pretraining in both time and money. + +
+ +因此,微调模型具有较低的时间、数据、财务和环境成本。迭代不同的微调方案也更快、更容易,因为与完整的预训练相比,训练的约束更少。 + +这个过程也会比从头开始的训练(除非你有很多数据)取得更好的效果,这就是为什么你应该总是尝试利用一个预训练的模型--一个尽可能接近你手头的任务的模型--并对其进行微调。 + +## 一般的体系结构 +在这一部分,我们将介绍Transformer模型的一般架构。如果你不理解其中的一些概念,不要担心;下文将详细介绍每个组件。 + + + +## 介绍 + +该模型主要由两个块组成: + +* **Encoder (左侧)**: 编码器接收输入并构建其表示(其特征)。这意味着对模型进行了优化,以从输入中获得理解。 +* **Decoder (右侧)**: 解码器使用编码器的表示(特征)以及其他输入来生成目标序列。这意味着该模型已针对生成输出进行了优化。 + +
+Architecture of a Transformers models + +
+ +这些部件中的每一个都可以独立使用,具体取决于任务: + +* **Encoder-only models**: 适用于需要理解输入的任务,如句子分类和命名实体识别。 +* **Decoder-only models**: 适用于生成任务,如文本生成。 +* **Encoder-decoder models** 或者 **sequence-to-sequence models**: 适用于需要根据输入进行生成的任务,如翻译或摘要。 + +在后面的部分中,我们将单独地深入研究这些体系结构。 + +## 注意力层 + +Transformer模型的一个关键特性是*注意力层*。事实上,介绍Transformer架构的文章的标题是[“注意力就是你所需要的”](https://arxiv.org/abs/1706.03762)! 我们将在课程的后面更加深入地探索注意力层;现在,您需要知道的是,这一层将告诉模型在处理每个单词的表示时,要特别重视您传递给它的句子中的某些单词(并且或多或少地忽略其他单词)。 + +把它放在语境中,考虑将文本从英语翻译成法语的任务。在输入“You like this course”的情况下,翻译模型还需要注意相邻的单词“You”,以获得单词“like”的正确翻译,因为在法语中,动词“like”的变化取决于主题。然而,句子的其余部分对于该词的翻译没有用处。同样,在翻译“this”时,模型也需要注意“course”一词,因为“this”的翻译不同,取决于相关名词是单数还是复数。同样,句子中的其他单词对于“this”的翻译也不重要。对于更复杂的句子(以及更复杂的语法规则),模型需要特别注意可能出现在句子中更远位置的单词,以便正确地翻译每个单词。 + +同样的概念也适用于与自然语言相关的任何任务:一个词本身有一个含义,但这个含义受语境的影响很大,语境可以是研究该词之前或之后的任何其他词(或多个词)。 + +现在您已经了解了注意力层的含义,让我们更仔细地了解Transformer架构。 + +## 原始的结构 + +Transformer架构最初是为翻译而设计的。在训练期间,编码器接收特定语言的输入(句子),而解码器需要输出对应语言的翻译。在编码器中,注意力层可以使用一个句子中的所有单词(正如我们刚才看到的,给定单词的翻译可以取决于它在句子中的其他单词)。然而,解码器是按顺序工作的,并且只能注意它已经翻译过的句子中的单词。例如,当我们预测了翻译目标的前三个单词时,我们将它们提供给解码器,然后解码器使用编码器的所有输入来尝试预测第四个单词。 + +为了在训练过程中加快速度(当模型可以访问目标句子时),解码器会被输入整个目标,但不允许获取到要翻译的单词(如果它在尝试预测位置2的单词时可以访问位置2的单词,解码器就会偷懒,直接输出那个单词,从而无法学习到正确的语言关系!)。例如,当试图预测第4个单词时,注意力层只能获取位置1到3的单词。 + +最初的Transformer架构如下所示,编码器位于左侧,解码器位于右侧: + +
+Architecture of a Transformers models + +
+ +注意,解码器块中的第一个注意力层关联到解码器的所有(过去的)输入,但是第二注意力层使用编码器的输出。因此,它可以访问整个输入句子,以最好地预测当前单词。这是非常有用的,因为不同的语言可以有语法规则将单词按不同的顺序排列,或者句子后面提供的一些上下文可能有助于确定给定单词的最佳翻译。 + +也可以在编码器/解码器中使用*注意力遮罩层*,以防止模型注意某些特殊单词。例如,在批处理句子时,填充特殊词使所有句子的长度一致。 + +## 架构与参数 + +在本课程中,当我们深入探讨Transformers模型时,您将看到 +架构、参数和模型 +。 这些术语的含义略有不同: + +* **架构**: 这是模型的骨架 -- 每个层的定义以及模型中发生的每个操作。 +* **Checkpoints**: 这些是将在给架构中结构中加载的权重。 +* **模型**: 这是一个笼统的术语,没有“架构”或“参数”那么精确:它可以指两者。为了避免歧义,本课程使用将使用架构和参数。 + +例如,BERT是一个架构,而 `bert-base-cased`, 这是谷歌团队为BERT的第一个版本训练的一组权重参数,是一个参数。我们可以说“BERT模型”和"`bert-base-cased`模型." diff --git a/chapters/zh-CN/chapter1/5.mdx b/chapters/zh-CN/chapter1/5.mdx index 7aa765ec2..7c235523d 100644 --- a/chapters/zh-CN/chapter1/5.mdx +++ b/chapters/zh-CN/chapter1/5.mdx @@ -1,5 +1,10 @@ # “编码器”模型 + + “编码器”模型指仅使用编码器的Transformer模型。在每个阶段,注意力层都可以获取初始句子中的所有单词。这些模型通常具有“双向”注意力,被称为自编码模型。 diff --git a/chapters/zh-CN/chapter1/6.mdx b/chapters/zh-CN/chapter1/6.mdx index 2de4c44a6..1136f6ebf 100644 --- a/chapters/zh-CN/chapter1/6.mdx +++ b/chapters/zh-CN/chapter1/6.mdx @@ -1,5 +1,10 @@ # “解码器”模型 + + “解码器”模型通常指仅使用解码器的Transformer模型。在每个阶段,对于给定的单词,注意力层只能获取到句子中位于将要预测单词前面的单词。这些模型通常被称为自回归模型。 diff --git a/chapters/zh-CN/chapter1/7.mdx b/chapters/zh-CN/chapter1/7.mdx index 99dc00eea..984488b2d 100644 --- a/chapters/zh-CN/chapter1/7.mdx +++ b/chapters/zh-CN/chapter1/7.mdx @@ -1,16 +1,21 @@ -# 序列到序列模型 - - - -编码器-解码器模型(也称为序列到序列模型)同时使用Transformer架构的编码器和解码器两个部分。在每个阶段,编码器的注意力层可以访问初始句子中的所有单词,而解码器的注意力层只能访问位于输入中将要预测单词前面的单词。 - -这些模型的预训练可以使用训练编码器或解码器模型的方式来完成,但通常涉及更复杂的内容。例如,[T5](https://huggingface.co/t5-base)通过将文本的随机跨度(可以包含多个单词)替换为单个特殊单词来进行预训练,然后目标是预测该掩码单词替换的文本。 - -序列到序列模型最适合于围绕根据给定输入生成新句子的任务,如摘要、翻译或生成性问答。 - -该系列模型的典型代表有: - -- [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) +# 序列到序列模型 + + + + + +编码器-解码器模型(也称为序列到序列模型)同时使用Transformer架构的编码器和解码器两个部分。在每个阶段,编码器的注意力层可以访问初始句子中的所有单词,而解码器的注意力层只能访问位于输入中将要预测单词前面的单词。 + +这些模型的预训练可以使用训练编码器或解码器模型的方式来完成,但通常涉及更复杂的内容。例如,[T5](https://huggingface.co/t5-base)通过将文本的随机跨度(可以包含多个单词)替换为单个特殊单词来进行预训练,然后目标是预测该掩码单词替换的文本。 + +序列到序列模型最适合于围绕根据给定输入生成新句子的任务,如摘要、翻译或生成性问答。 + +该系列模型的典型代表有: + +- [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) diff --git a/chapters/zh-CN/chapter1/8.mdx b/chapters/zh-CN/chapter1/8.mdx index 707731892..5bd23953b 100644 --- a/chapters/zh-CN/chapter1/8.mdx +++ b/chapters/zh-CN/chapter1/8.mdx @@ -1,8 +1,8 @@ # Bias and limitations - diff --git a/chapters/zh-CN/chapter1/9.mdx b/chapters/zh-CN/chapter1/9.mdx index 16c5ab6ad..142153f49 100644 --- a/chapters/zh-CN/chapter1/9.mdx +++ b/chapters/zh-CN/chapter1/9.mdx @@ -1,11 +1,16 @@ -# 总结 - -在本章中,您了解了如何使用来自🤗Transformers的函数pipeline()处理不同的NLP任务。您还了解了如何在模型中心(hub)中搜索和使用模型,以及如何使用推理API直接在浏览器中测试模型。 - -我们讨论了Transformer模型如何在应用层上工作,并讨论了迁移学习和微调的重要性。您可以使用完整的体系结构,也可以仅使用编码器或解码器,具体取决于您要解决的任务类型。下表总结了这一点: - -| 模型 | 示例 | 任务| -| ---- | ---- |----| -| 编码器 | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa |句子分类、命名实体识别、从文本中提取答案| -| 解码器 | CTRL, GPT, GPT-2, Transformer XL |文本生成| +# 总结 + + + +在本章中,您了解了如何使用来自🤗Transformers的函数pipeline()处理不同的NLP任务。您还了解了如何在模型中心(hub)中搜索和使用模型,以及如何使用推理API直接在浏览器中测试模型。 + +我们讨论了Transformer模型如何在应用层上工作,并讨论了迁移学习和微调的重要性。您可以使用完整的体系结构,也可以仅使用编码器或解码器,具体取决于您要解决的任务类型。下表总结了这一点: + +| 模型 | 示例 | 任务| +| ---- | ---- |----| +| 编码器 | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa |句子分类、命名实体识别、从文本中提取答案| +| 解码器 | CTRL, GPT, GPT-2, Transformer XL |文本生成| | 编码器-解码器 | BART, T5, Marian, mBART |文本摘要、翻译、生成问题的回答| \ No newline at end of file diff --git a/chapters/zh-CN/chapter2/1.mdx b/chapters/zh-CN/chapter2/1.mdx index d0ab0e0d9..f0aa1b797 100644 --- a/chapters/zh-CN/chapter2/1.mdx +++ b/chapters/zh-CN/chapter2/1.mdx @@ -1,18 +1,23 @@ -# 本章简介 - -正如你在 [Chapter 1](/course/chapter1),中看到的那样,Transformers模型通常非常大。对于数以百万计到数千万计数十亿的参数,训练和部署这些模型是一项复杂的任务。此外,由于几乎每天都在发布新模型,而且每种模型都有自己的实现,因此尝试它们绝非易事。 - -创建🤗 Transformers库就是为了解决这个问题。它的目标是提供一个API,通过它可以加载、训练和保存任何Transformer模型。这个库的主要特点是: -- **易于使用**:下载、加载和使用最先进的NLP模型进行推理只需两行代码即可完成。 -- **灵活**:所有型号的核心都是简单的PyTorch **nn.Module** 或者 TensorFlow **tf.kears.Model**,可以像它们各自的机器学习(ML)框架中的任何其他模型一样进行处理。 -- **简单**:当前位置整个库几乎没有任何摘要。“都在一个文件中”是一个核心概念:模型的正向传递完全定义在一个文件中,因此代码本身是可以理解的,并且是可以破解的。 - -最后一个特性使🤗 Transformers与其他ML库截然不同。这些模型不是基于通过文件共享的模块构建的;相反,每一个模型都有自己的菜单。除了使模型更加容易接受和更容易理解,这还允许你轻松地在一个模型上实验,而且不影响其他模型。 - -本章将从一个端到端的示例开始,在该示例中,我们一起使用模型和tokenizer分词器来复制[Chapter 1](/course/chapter1)中引入的函数pipeline(). 接下来,我们将讨论模型API:我们将深入研究模型和配置类,并向您展示如何加载模型以及如何将数值输入处理为输出预测。 - -然后我们来看看标记器API,它是pipeline()函数的另一个主要组件。它是作用分词器负责第一个和最后一个处理步骤,处理从文本到神经网络数字输入的转换,以及在需要时转换回文本。最后,我们将向您展示如何处理在一个准备好的批处理中通过一个模型发送多个句子的问题,然后详细介绍pipeline()函数。 - - -⚠️ 为了从模型集线器和🤗Transformers的所有可用功能中获益,我们建议creating an account. +# 本章简介 + + + +正如你在 [Chapter 1](/course/chapter1),中看到的那样,Transformers模型通常非常大。对于数以百万计到数千万计数十亿的参数,训练和部署这些模型是一项复杂的任务。此外,由于几乎每天都在发布新模型,而且每种模型都有自己的实现,因此尝试它们绝非易事。 + +创建🤗 Transformers库就是为了解决这个问题。它的目标是提供一个API,通过它可以加载、训练和保存任何Transformer模型。这个库的主要特点是: +- **易于使用**:下载、加载和使用最先进的NLP模型进行推理只需两行代码即可完成。 +- **灵活**:所有型号的核心都是简单的PyTorch **nn.Module** 或者 TensorFlow **tf.kears.Model**,可以像它们各自的机器学习(ML)框架中的任何其他模型一样进行处理。 +- **简单**:当前位置整个库几乎没有任何摘要。“都在一个文件中”是一个核心概念:模型的正向传递完全定义在一个文件中,因此代码本身是可以理解的,并且是可以破解的。 + +最后一个特性使🤗 Transformers与其他ML库截然不同。这些模型不是基于通过文件共享的模块构建的;相反,每一个模型都有自己的菜单。除了使模型更加容易接受和更容易理解,这还允许你轻松地在一个模型上实验,而且不影响其他模型。 + +本章将从一个端到端的示例开始,在该示例中,我们一起使用模型和tokenizer分词器来复制[Chapter 1](/course/chapter1)中引入的函数pipeline(). 接下来,我们将讨论模型API:我们将深入研究模型和配置类,并向您展示如何加载模型以及如何将数值输入处理为输出预测。 + +然后我们来看看标记器API,它是pipeline()函数的另一个主要组件。它是作用分词器负责第一个和最后一个处理步骤,处理从文本到神经网络数字输入的转换,以及在需要时转换回文本。最后,我们将向您展示如何处理在一个准备好的批处理中通过一个模型发送多个句子的问题,然后详细介绍pipeline()函数。 + + +⚠️ 为了从模型集线器和🤗Transformers的所有可用功能中获益,我们建议creating an account. \ No newline at end of file diff --git a/chapters/zh-CN/chapter2/2.mdx b/chapters/zh-CN/chapter2/2.mdx index 2bf0ef5f8..fd1c8ba69 100644 --- a/chapters/zh-CN/chapter2/2.mdx +++ b/chapters/zh-CN/chapter2/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/zh-CN/chapter2/3.mdx b/chapters/zh-CN/chapter2/3.mdx index 31341f166..4dbba9df1 100644 --- a/chapters/zh-CN/chapter2/3.mdx +++ b/chapters/zh-CN/chapter2/3.mdx @@ -1,264 +1,264 @@ - - -# 模型 - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -{#if fw === 'pt'} - -{:else} - -{/if} - -{#if fw === 'pt'} -在本节中,我们将更详细地了解如何创建和使用模型。我们将使用 -AutoModel类,当您希望从检查点实例化任何模型时,这非常方便。 - -这个AutoModel类及其所有相关项实际上是对库中各种可用模型的简单包装。它是一个聪明的包装器,因为它可以自动猜测检查点的适当模型体系结构,然后用该体系结构实例化模型。 - -{:else} -在本节中,我们将更详细地了解如何创建和使用模型。我们将使用 -AutoModel类,当您希望从检查点实例化任何模型时,这非常方便。 - -这个AutoModel类及其所有相关项实际上是对库中各种可用模型的简单包装。它是一个聪明的包装器,因为它可以自动猜测检查点的适当模型体系结构,然后用该体系结构实例化模型。 - -{/if} - -但是,如果您知道要使用的模型类型,则可以使用直接定义其体系结构的类。让我们看看这是如何与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) -``` - -```python out -BertConfig { - [...] - "hidden_size": 768, - "intermediate_size": 3072, - "max_position_embeddings": 512, - "num_attention_heads": 12, - "num_hidden_layers": 12, - [...] -} -``` - -虽然您还没有看到所有这些属性都做了什么,但您应该认识到其中的一些属性:hidden_size属性定义了hidden_状态向量的大小,num_hidden_layers定义了Transformer模型的层数。 - -### 不同的加载方式 - -从默认配置创建模型会使用随机值对其进行初始化: - - -{#if fw === 'pt'} -```py -from transformers import BertConfig, BertModel - -config = BertConfig() -model = BertModel(config) - -# Model is randomly initialized! -``` -{:else} -```py -from transformers import BertConfig, TFBertModel - -config = BertConfig() -model = TFBertModel(config) - -# Model is randomly initialized! -``` -{/if} - - -该模型可以在这种状态下使用,但会输出胡言乱语;首先需要对其进行训练。我们可以根据手头的任务从头开始训练模型,但正如您在 -[Chapter 1](/course/chapter1) -,这将需要很长的时间和大量的数据,并将产生不可忽视的环境影响。为了避免不必要的重复工作,必须能够共享和重用已经训练过的模型。 - - -加载已经训练过的Transformers模型很简单-我们可以使用from_pretrained() -方法: - -{#if fw === 'pt'} -```py -from transformers import BertModel - -model = BertModel.from_pretrained("bert-base-cased") -``` - -正如您之前看到的,我们可以用等效的AutoModel类替换Bert模型。从现在开始,我们将这样做,因为这会产生检查点不可知的代码;如果您的代码适用于一个检查点,那么它应该与另一个检查点无缝地工作。即使体系结构不同,这也适用,只要检查点是针对类似任务(例如,情绪分析任务)训练的。 - -{:else} -```py -from transformers import TFBertModel - -model = TFBertModel.from_pretrained("bert-base-cased") -``` - -正如您之前看到的,我们可以用等效的AutoModel类替换Bert模型。从现在开始,我们将这样做,因为这会产生检查点不可知的代码;如果您的代码适用于一个检查点,那么它应该与另一个检查点无缝地工作。即使体系结构不同,这也适用,只要检查点是针对类似任务(例如,情绪分析任务)训练的。 - -{/if} - -在上面的代码示例中,我们没有使用BertConfig - -,而是通过Bert base cased标识符加载了一个预训练模型。这是一个模型检查点,由BERT的作者自己训练;您可以在 -[model card](https://huggingface.co/bert-base-cased)中找到更多细节. - - - -该模型现在使用检查点的所有权重进行初始化。它可以直接用于对训练过的任务进行推理,也可以对新任务进行微调。通过预先训练重量而不是从头开始的训练,我们可以很快取得好的效果。 - - - -权重已下载并缓存在缓存文件夹中(因此将来对from_pretrained()方法的调用将不会重新下载它们)默认为 -~/.cache/huggingface/transformers -. 您可以通过设置 -HF_HOME -环境变量来自定义缓存文件夹。 - - - -用于加载模型的标识符可以是模型中心Hub上任何模型的标识符,只要它与BERT体系结构兼容。可以找到可用的BERT检查点的完整列表 -[here](https://huggingface.co/models?filter=bert) -. -### 保存模型 - -保存模型和加载模型一样简单--我们使用 -save_pretrained() -方法,类似于 -from_pretrained() -方法: - -```py -model.save_pretrained("directory_on_my_computer") -``` - -这会将两个文件保存到磁盘: - -{#if fw === 'pt'} -``` -ls directory_on_my_computer - -config.json pytorch_model.bin -``` -{:else} -``` -ls directory_on_my_computer - -config.json tf_model.h5 -``` -{/if} - -如果你看一下 -config.json -文件,您将识别构建模型体系结构所需的属性。该文件还包含一些元数据,例如检查点的来源以及上次保存检查点时使用的🤗 Transformers版本。 - -{#if fw === 'pt'} -这个 *pytorch_model.bin* 文件就是众所周知的*state dictionary*; 它包含模型的所有权重。这两个文件齐头并进;配置是了解模型体系结构所必需的,而模型权重是模型的参数。 - -{:else} -这个 *pytorch_model.bin* 文件就是众所周知的*state dictionary*; 它包含模型的所有权重。这两个文件齐头并进;配置是了解模型体系结构所必需的,而模型权重是模型的参数。 - -{/if} - -### 使用Transformers模型进行推理 - -既然您知道了如何加载和保存模型,那么让我们尝试使用它进行一些预测。Transformer模型只能处理数字——分词器生成的数字。但在我们讨论标记化器之前,让我们先探讨模型接受哪些输入。 - -标记化器可以将输入转换为适当的框架张量,但为了帮助您了解发生了什么,我们将快速了解在将输入发送到模型之前必须做什么。 - -假设我们有几个序列: - -```py -sequences = ["Hello!", "Cool.", "Nice!"] -``` - -分词器将这些转换为词汇表索引,通常称为 -input IDs -. 每个序列现在都是一个数字列表!结果是: - -```py no-format -encoded_sequences = [ - [101, 7592, 999, 102], - [101, 4658, 1012, 102], - [101, 3835, 999, 102], -] -``` - -这是一个编码序列列表:一个列表列表。张量只接受矩形(想想矩阵)。此“数组”已为矩形,因此将其转换为张量很容易: - -{#if fw === 'pt'} -```py -import torch - -model_inputs = torch.tensor(encoded_sequences) -``` -{:else} -```py -import tensorflow as tf - -model_inputs = tf.constant(encoded_sequences) -``` -{/if} - -### 使用张量作为模型的输入 - - - -在模型中使用张量非常简单-我们只需将输入称为模型: - - -```python -output = model(model_inputs) -``` - - - -虽然模型接受许多不同的参数,但只需要 -input IDs。我们稍后将解释其他参数的作用以及何时需要它们,但首先我们需要更仔细地了解 -Transformer模型可以理解的输入的标记 + + +# 模型 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + +{:else} + +{/if} + +{#if fw === 'pt'} +在本节中,我们将更详细地了解如何创建和使用模型。我们将使用 +AutoModel类,当您希望从检查点实例化任何模型时,这非常方便。 + +这个AutoModel类及其所有相关项实际上是对库中各种可用模型的简单包装。它是一个聪明的包装器,因为它可以自动猜测检查点的适当模型体系结构,然后用该体系结构实例化模型。 + +{:else} +在本节中,我们将更详细地了解如何创建和使用模型。我们将使用 +AutoModel类,当您希望从检查点实例化任何模型时,这非常方便。 + +这个AutoModel类及其所有相关项实际上是对库中各种可用模型的简单包装。它是一个聪明的包装器,因为它可以自动猜测检查点的适当模型体系结构,然后用该体系结构实例化模型。 + +{/if} + +但是,如果您知道要使用的模型类型,则可以使用直接定义其体系结构的类。让我们看看这是如何与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) +``` + +```python out +BertConfig { + [...] + "hidden_size": 768, + "intermediate_size": 3072, + "max_position_embeddings": 512, + "num_attention_heads": 12, + "num_hidden_layers": 12, + [...] +} +``` + +虽然您还没有看到所有这些属性都做了什么,但您应该认识到其中的一些属性:hidden_size属性定义了hidden_状态向量的大小,num_hidden_layers定义了Transformer模型的层数。 + +### 不同的加载方式 + +从默认配置创建模型会使用随机值对其进行初始化: + + +{#if fw === 'pt'} +```py +from transformers import BertConfig, BertModel + +config = BertConfig() +model = BertModel(config) + +# Model is randomly initialized! +``` +{:else} +```py +from transformers import BertConfig, TFBertModel + +config = BertConfig() +model = TFBertModel(config) + +# Model is randomly initialized! +``` +{/if} + + +该模型可以在这种状态下使用,但会输出胡言乱语;首先需要对其进行训练。我们可以根据手头的任务从头开始训练模型,但正如您在 +[Chapter 1](/course/chapter1) +,这将需要很长的时间和大量的数据,并将产生不可忽视的环境影响。为了避免不必要的重复工作,必须能够共享和重用已经训练过的模型。 + + +加载已经训练过的Transformers模型很简单-我们可以使用from_pretrained() +方法: + +{#if fw === 'pt'} +```py +from transformers import BertModel + +model = BertModel.from_pretrained("bert-base-cased") +``` + +正如您之前看到的,我们可以用等效的AutoModel类替换Bert模型。从现在开始,我们将这样做,因为这会产生检查点不可知的代码;如果您的代码适用于一个检查点,那么它应该与另一个检查点无缝地工作。即使体系结构不同,这也适用,只要检查点是针对类似任务(例如,情绪分析任务)训练的。 + +{:else} +```py +from transformers import TFBertModel + +model = TFBertModel.from_pretrained("bert-base-cased") +``` + +正如您之前看到的,我们可以用等效的AutoModel类替换Bert模型。从现在开始,我们将这样做,因为这会产生检查点不可知的代码;如果您的代码适用于一个检查点,那么它应该与另一个检查点无缝地工作。即使体系结构不同,这也适用,只要检查点是针对类似任务(例如,情绪分析任务)训练的。 + +{/if} + +在上面的代码示例中,我们没有使用BertConfig + +,而是通过Bert base cased标识符加载了一个预训练模型。这是一个模型检查点,由BERT的作者自己训练;您可以在 +[model card](https://huggingface.co/bert-base-cased)中找到更多细节. + + + +该模型现在使用检查点的所有权重进行初始化。它可以直接用于对训练过的任务进行推理,也可以对新任务进行微调。通过预先训练重量而不是从头开始的训练,我们可以很快取得好的效果。 + + + +权重已下载并缓存在缓存文件夹中(因此将来对from_pretrained()方法的调用将不会重新下载它们)默认为 +~/.cache/huggingface/transformers +. 您可以通过设置 +HF_HOME +环境变量来自定义缓存文件夹。 + + + +用于加载模型的标识符可以是模型中心Hub上任何模型的标识符,只要它与BERT体系结构兼容。可以找到可用的BERT检查点的完整列表 +[here](https://huggingface.co/models?filter=bert) +. +### 保存模型 + +保存模型和加载模型一样简单--我们使用 +save_pretrained() +方法,类似于 +from_pretrained() +方法: + +```py +model.save_pretrained("directory_on_my_computer") +``` + +这会将两个文件保存到磁盘: + +{#if fw === 'pt'} +``` +ls directory_on_my_computer + +config.json pytorch_model.bin +``` +{:else} +``` +ls directory_on_my_computer + +config.json tf_model.h5 +``` +{/if} + +如果你看一下 +config.json +文件,您将识别构建模型体系结构所需的属性。该文件还包含一些元数据,例如检查点的来源以及上次保存检查点时使用的🤗 Transformers版本。 + +{#if fw === 'pt'} +这个 *pytorch_model.bin* 文件就是众所周知的*state dictionary*; 它包含模型的所有权重。这两个文件齐头并进;配置是了解模型体系结构所必需的,而模型权重是模型的参数。 + +{:else} +这个 *pytorch_model.bin* 文件就是众所周知的*state dictionary*; 它包含模型的所有权重。这两个文件齐头并进;配置是了解模型体系结构所必需的,而模型权重是模型的参数。 + +{/if} + +### 使用Transformers模型进行推理 + +既然您知道了如何加载和保存模型,那么让我们尝试使用它进行一些预测。Transformer模型只能处理数字——分词器生成的数字。但在我们讨论标记化器之前,让我们先探讨模型接受哪些输入。 + +标记化器可以将输入转换为适当的框架张量,但为了帮助您了解发生了什么,我们将快速了解在将输入发送到模型之前必须做什么。 + +假设我们有几个序列: + +```py +sequences = ["Hello!", "Cool.", "Nice!"] +``` + +分词器将这些转换为词汇表索引,通常称为 +input IDs +. 每个序列现在都是一个数字列表!结果是: + +```py no-format +encoded_sequences = [ + [101, 7592, 999, 102], + [101, 4658, 1012, 102], + [101, 3835, 999, 102], +] +``` + +这是一个编码序列列表:一个列表列表。张量只接受矩形(想想矩阵)。此“数组”已为矩形,因此将其转换为张量很容易: + +{#if fw === 'pt'} +```py +import torch + +model_inputs = torch.tensor(encoded_sequences) +``` +{:else} +```py +import tensorflow as tf + +model_inputs = tf.constant(encoded_sequences) +``` +{/if} + +### 使用张量作为模型的输入 + + + +在模型中使用张量非常简单-我们只需将输入称为模型: + + +```python +output = model(model_inputs) +``` + + + +虽然模型接受许多不同的参数,但只需要 +input IDs。我们稍后将解释其他参数的作用以及何时需要它们,但首先我们需要更仔细地了解 +Transformer模型可以理解的输入的标记 diff --git a/chapters/zh-CN/chapter2/4.mdx b/chapters/zh-CN/chapter2/4.mdx index fb4819296..cc0fa5bc5 100644 --- a/chapters/zh-CN/chapter2/4.mdx +++ b/chapters/zh-CN/chapter2/4.mdx @@ -1,239 +1,239 @@ - - -# 标记器(Tokenizer) - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - - - -标记器(Tokenizer)是 NLP 管道的核心组件之一。它们有一个目的:将文本转换为模型可以处理的数据。模型只能处理数字,因此标记器(Tokenizer)需要将我们的文本输入转换为数字数据。在本节中,我们将确切地探讨标记化管道中发生的事情。 - -在 NLP 任务中,通常处理的数据是原始文本。这是此类文本的示例 - -``` -Jim Henson was a puppeteer -``` - -但是,模型只能处理数字,因此我们需要找到一种将原始文本转换为数字的方法。这就是标记器(tokenizer)所做的,并且有很多方法可以解决这个问题。目标是找到最有意义的表示——即对模型最有意义的表示——并且如果可能的话,找到最小的表示。 - -让我们看一下标记化算法的一些示例,并尝试回答您可能对标记化提出的一些问题。 - -## 基于词的(Word-based) - - - -想到的第一种标记器是基于词的(_word-based_).它通常很容易设置和使用,只需几条规则,并且通常会产生不错的结果。例如,在下图中,目标是将原始文本拆分为单词并为每个单词找到一个数字表示: - -
- An example of word-based tokenization. - -
- -有多种方法可以拆分文本。例如,我们可以通过应用Python的`split()`函数,使用空格将文本标记为单词: - -```py -tokenized_text = "Jim Henson was a puppeteer".split() -print(tokenized_text) -``` - -```python out -['Jim', 'Henson', 'was', 'a', 'puppeteer'] -``` - -还有一些单词标记器的变体,它们具有额外的标点符号规则。使用这种标记器,我们最终可以得到一些非常大的“词汇表”,其中词汇表由我们在语料库中拥有的独立标记的总数定义。 - -每个单词都分配了一个 ID,从 0 开始一直到词汇表的大小。该模型使用这些 ID 来识别每个单词。 - -如果我们想用基于单词的标记器(tokenizer)完全覆盖一种语言,我们需要为语言中的每个单词都有一个标识符,这将生成大量的标记。例如,英语中有超过 500,000 个单词,因此要构建从每个单词到输入 ID 的映射,我们需要跟踪这么多 ID。此外,像“dog”这样的词与“dogs”这样的词的表示方式不同,模型最初无法知道“dog”和“dogs”是相似的:它会将这两个词识别为不相关。这同样适用于其他相似的词,例如“run”和“running”,模型最初不会认为它们是相似的。 - -最后,我们需要一个自定义标记(token)来表示不在我们词汇表中的单词。这被称为“未知”标记(token),通常表示为“[UNK]”或"<unk>"。如果你看到标记器产生了很多这样的标记,这通常是一个不好的迹象,因为它无法检索到一个词的合理表示,并且你会在这个过程中丢失信息。制作词汇表时的目标是以这样一种方式进行,即标记器将尽可能少的单词标记为未知标记。 - -减少未知标记数量的一种方法是使用更深一层的标记器(tokenizer),即基于字符的(_character-based_)标记器(tokenizer)。 - -## 基于字符(Character-based) - - - -基于字符的标记器(tokenizer)将文本拆分为字符,而不是单词。这有两个主要好处: - -- 词汇量要小得多。 -- 词汇外(未知)标记(token)要少得多,因为每个单词都可以从字符构建。 - -但是这里也出现了一些关于空格和标点符号的问题: - -
- An example of character-based tokenization. - -
- -这种方法也不是完美的。由于现在表示是基于字符而不是单词,因此人们可能会争辩说,从直觉上讲,它的意义不大:每个字符本身并没有多大意义,而单词就是这种情况。然而,这又因语言而异;例如,在中文中,每个字符比拉丁语言中的字符包含更多的信息。 - -另一件要考虑的事情是,我们的模型最终会处理大量的词符(token):虽然使用基于单词的标记器(tokenizer),单词只会是单个标记,但当转换为字符时,它很容易变成 10 个或更多的词符(token)。 - -为了两全其美,我们可以使用结合这两种方法的第三种技术:*子词标记化(subword tokenization)*。 - -## 子词标记化 - - - -子词分词算法依赖于这样一个原则,即不应将常用词拆分为更小的子词,而应将稀有词分解为有意义的子词。 - -例如,“annoyingly”可能被认为是一个罕见的词,可以分解为“annoying”和“ly”。这两者都可能作为独立的子词出现得更频繁,同时“annoyingly”的含义由“annoying”和“ly”的复合含义保持。 - -这是一个示例,展示了子词标记化算法如何标记序列“Let's do tokenization!”: - -
- A subword tokenization algorithm. - -
- -这些子词最终提供了很多语义含义:例如,在上面的示例中,“tokenization”被拆分为“token”和“ization”,这两个具有语义意义同时节省空间的词符(token)(只需要两个标记(token)代表一个长词)。这使我们能够对较小的词汇表进行相对较好的覆盖,并且几乎没有未知的标记 - -这种方法在土耳其语等粘着型语言(agglutinative languages)中特别有用,您可以通过将子词串在一起来形成(几乎)任意长的复杂词。 - -### 还有更多! - -不出所料,还有更多的技术。仅举几例: - -- Byte-level BPE, 用于 GPT-2 -- WordPiece, 用于 BERT -- SentencePiece or Unigram, 用于多个多语言模型 - -您现在应该对标记器(tokenizers)的工作原理有足够的了解,以便开始使用 API。 - -## 加载和保存 - -加载和保存标记器(tokenizer)就像使用模型一样简单。实际上,它基于相同的两种方法: `from_pretrained()` 和 `save_pretrained()` 。这些方法将加载或保存标记器(tokenizer)使用的算法(有点像*建筑学(architecture)*的模型)以及它的词汇(有点像*权重(weights)*模型)。 - -加载使用与 BERT 相同的检查点训练的 BERT 标记器(tokenizer)与加载模型的方式相同,除了我们使用 `BertTokenizer` 类: - -```py -from transformers import BertTokenizer - -tokenizer = BertTokenizer.from_pretrained("bert-base-cased") -``` - -{#if fw === 'pt'} -如同 `AutoModel`,`AutoTokenizer` 类将根据检查点名称在库中获取正确的标记器(tokenizer)类,并且可以直接与任何检查点一起使用: - -{:else} -如同 `TFAutoModel`, `AutoTokenizer` 类将根据检查点名称在库中获取正确的标记器(tokenizer)类,并且可以直接与任何检查点一起使用: - -{/if} - -```py -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") -``` - -我们现在可以使用标记器(tokenizer),如上一节所示: - -```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]} -``` - -保存标记器(tokenizer)与保存模型相同: - -```py -tokenizer.save_pretrained("directory_on_my_computer") -``` - -我们在[Chapter 3](/Couse/chapter3)中将更多地谈论`token_type_ids`,稍后我们将解释 `attention_mask` 键。首先,让我们看看 `input_ids` 如何生成。为此,我们需要查看标记器(tokenizer)的中间方法。 - -## 编码 - - - -将文本翻译成数字被称为编码(_encoding_).编码分两步完成:标记化,然后转换为输入 ID。 - -正如我们所见,第一步是将文本拆分为单词(或单词的一部分、标点符号等),通常称为*标记(token)*。有多个规则可以管理该过程,这就是为什么我们需要使用模型名称来实例化标记器(tokenizer),以确保我们使用模型预训练时使用的相同规则。 - -第二步是将这些标记转换为数字,这样我们就可以用它们构建一个张量并将它们提供给模型。为此,标记器(tokenizer)有一个*词汇(vocabulary)*,这是我们在实例化它时下载的部分 `from_pretrained()` 方法。同样,我们需要使用模型预训练时使用的相同词汇。 - -为了更好地理解这两个步骤,我们将分别探讨它们。请注意,我们将使用一些单独执行部分标记化管道的方法来向您展示这些步骤的中间结果,但实际上,您应该直接在您的输入上调用标记器(tokenizer)(如第 2 部分所示)。 - -### 标记化 - -标记化过程由标记器(tokenizer)的`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) -``` - -此方法的输出是一个字符串列表或标记(token): - -```python out -['Using', 'a', 'transform', '##er', 'network', 'is', 'simple'] -``` - -这个标记器(tokenizer)是一个子词标记器(tokenizer):它对词进行拆分,直到获得可以用其词汇表表示的标记(token)。`transformer` 就是这种情况,它分为两个标记:`transform` 和 `##er`。 - -### 从词符(token)到输入 ID -输入 ID 的转换由标记器(tokenizer)的`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!”)复制最后两个步骤(标记化和转换为输入 ID)。检查您获得的输入 ID 是否与我们之前获得的相同! - - - -## 解码 - -*解码(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` 方法不仅将索引转换回标记(token),还将属于相同单词的标记(token)组合在一起以生成可读的句子。当我们使用预测新文本的模型(根据提示生成的文本,或序列到序列问题(如翻译或摘要))时,这种行为将非常有用。 - -到现在为止,您应该了解标记器(tokenizer)可以处理的原子操作:标记化、转换为 ID 以及将 ID 转换回字符串。然而,我们只是刮到了冰山一角。在下一节中,我们将采用我们的方法来克服它的限制,并看看如何克服它们。 + + +# 标记器(Tokenizer) + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + + +标记器(Tokenizer)是 NLP 管道的核心组件之一。它们有一个目的:将文本转换为模型可以处理的数据。模型只能处理数字,因此标记器(Tokenizer)需要将我们的文本输入转换为数字数据。在本节中,我们将确切地探讨标记化管道中发生的事情。 + +在 NLP 任务中,通常处理的数据是原始文本。这是此类文本的示例 + +``` +Jim Henson was a puppeteer +``` + +但是,模型只能处理数字,因此我们需要找到一种将原始文本转换为数字的方法。这就是标记器(tokenizer)所做的,并且有很多方法可以解决这个问题。目标是找到最有意义的表示——即对模型最有意义的表示——并且如果可能的话,找到最小的表示。 + +让我们看一下标记化算法的一些示例,并尝试回答您可能对标记化提出的一些问题。 + +## 基于词的(Word-based) + + + +想到的第一种标记器是基于词的(_word-based_).它通常很容易设置和使用,只需几条规则,并且通常会产生不错的结果。例如,在下图中,目标是将原始文本拆分为单词并为每个单词找到一个数字表示: + +
+ An example of word-based tokenization. + +
+ +有多种方法可以拆分文本。例如,我们可以通过应用Python的`split()`函数,使用空格将文本标记为单词: + +```py +tokenized_text = "Jim Henson was a puppeteer".split() +print(tokenized_text) +``` + +```python out +['Jim', 'Henson', 'was', 'a', 'puppeteer'] +``` + +还有一些单词标记器的变体,它们具有额外的标点符号规则。使用这种标记器,我们最终可以得到一些非常大的“词汇表”,其中词汇表由我们在语料库中拥有的独立标记的总数定义。 + +每个单词都分配了一个 ID,从 0 开始一直到词汇表的大小。该模型使用这些 ID 来识别每个单词。 + +如果我们想用基于单词的标记器(tokenizer)完全覆盖一种语言,我们需要为语言中的每个单词都有一个标识符,这将生成大量的标记。例如,英语中有超过 500,000 个单词,因此要构建从每个单词到输入 ID 的映射,我们需要跟踪这么多 ID。此外,像“dog”这样的词与“dogs”这样的词的表示方式不同,模型最初无法知道“dog”和“dogs”是相似的:它会将这两个词识别为不相关。这同样适用于其他相似的词,例如“run”和“running”,模型最初不会认为它们是相似的。 + +最后,我们需要一个自定义标记(token)来表示不在我们词汇表中的单词。这被称为“未知”标记(token),通常表示为“[UNK]”或"<unk>"。如果你看到标记器产生了很多这样的标记,这通常是一个不好的迹象,因为它无法检索到一个词的合理表示,并且你会在这个过程中丢失信息。制作词汇表时的目标是以这样一种方式进行,即标记器将尽可能少的单词标记为未知标记。 + +减少未知标记数量的一种方法是使用更深一层的标记器(tokenizer),即基于字符的(_character-based_)标记器(tokenizer)。 + +## 基于字符(Character-based) + + + +基于字符的标记器(tokenizer)将文本拆分为字符,而不是单词。这有两个主要好处: + +- 词汇量要小得多。 +- 词汇外(未知)标记(token)要少得多,因为每个单词都可以从字符构建。 + +但是这里也出现了一些关于空格和标点符号的问题: + +
+ An example of character-based tokenization. + +
+ +这种方法也不是完美的。由于现在表示是基于字符而不是单词,因此人们可能会争辩说,从直觉上讲,它的意义不大:每个字符本身并没有多大意义,而单词就是这种情况。然而,这又因语言而异;例如,在中文中,每个字符比拉丁语言中的字符包含更多的信息。 + +另一件要考虑的事情是,我们的模型最终会处理大量的词符(token):虽然使用基于单词的标记器(tokenizer),单词只会是单个标记,但当转换为字符时,它很容易变成 10 个或更多的词符(token)。 + +为了两全其美,我们可以使用结合这两种方法的第三种技术:*子词标记化(subword tokenization)*。 + +## 子词标记化 + + + +子词分词算法依赖于这样一个原则,即不应将常用词拆分为更小的子词,而应将稀有词分解为有意义的子词。 + +例如,“annoyingly”可能被认为是一个罕见的词,可以分解为“annoying”和“ly”。这两者都可能作为独立的子词出现得更频繁,同时“annoyingly”的含义由“annoying”和“ly”的复合含义保持。 + +这是一个示例,展示了子词标记化算法如何标记序列“Let's do tokenization!”: + +
+ A subword tokenization algorithm. + +
+ +这些子词最终提供了很多语义含义:例如,在上面的示例中,“tokenization”被拆分为“token”和“ization”,这两个具有语义意义同时节省空间的词符(token)(只需要两个标记(token)代表一个长词)。这使我们能够对较小的词汇表进行相对较好的覆盖,并且几乎没有未知的标记 + +这种方法在土耳其语等粘着型语言(agglutinative languages)中特别有用,您可以通过将子词串在一起来形成(几乎)任意长的复杂词。 + +### 还有更多! + +不出所料,还有更多的技术。仅举几例: + +- Byte-level BPE, 用于 GPT-2 +- WordPiece, 用于 BERT +- SentencePiece or Unigram, 用于多个多语言模型 + +您现在应该对标记器(tokenizers)的工作原理有足够的了解,以便开始使用 API。 + +## 加载和保存 + +加载和保存标记器(tokenizer)就像使用模型一样简单。实际上,它基于相同的两种方法: `from_pretrained()` 和 `save_pretrained()` 。这些方法将加载或保存标记器(tokenizer)使用的算法(有点像*建筑学(architecture)*的模型)以及它的词汇(有点像*权重(weights)*模型)。 + +加载使用与 BERT 相同的检查点训练的 BERT 标记器(tokenizer)与加载模型的方式相同,除了我们使用 `BertTokenizer` 类: + +```py +from transformers import BertTokenizer + +tokenizer = BertTokenizer.from_pretrained("bert-base-cased") +``` + +{#if fw === 'pt'} +如同 `AutoModel`,`AutoTokenizer` 类将根据检查点名称在库中获取正确的标记器(tokenizer)类,并且可以直接与任何检查点一起使用: + +{:else} +如同 `TFAutoModel`, `AutoTokenizer` 类将根据检查点名称在库中获取正确的标记器(tokenizer)类,并且可以直接与任何检查点一起使用: + +{/if} + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +``` + +我们现在可以使用标记器(tokenizer),如上一节所示: + +```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]} +``` + +保存标记器(tokenizer)与保存模型相同: + +```py +tokenizer.save_pretrained("directory_on_my_computer") +``` + +我们在[Chapter 3](/Couse/chapter3)中将更多地谈论`token_type_ids`,稍后我们将解释 `attention_mask` 键。首先,让我们看看 `input_ids` 如何生成。为此,我们需要查看标记器(tokenizer)的中间方法。 + +## 编码 + + + +将文本翻译成数字被称为编码(_encoding_).编码分两步完成:标记化,然后转换为输入 ID。 + +正如我们所见,第一步是将文本拆分为单词(或单词的一部分、标点符号等),通常称为*标记(token)*。有多个规则可以管理该过程,这就是为什么我们需要使用模型名称来实例化标记器(tokenizer),以确保我们使用模型预训练时使用的相同规则。 + +第二步是将这些标记转换为数字,这样我们就可以用它们构建一个张量并将它们提供给模型。为此,标记器(tokenizer)有一个*词汇(vocabulary)*,这是我们在实例化它时下载的部分 `from_pretrained()` 方法。同样,我们需要使用模型预训练时使用的相同词汇。 + +为了更好地理解这两个步骤,我们将分别探讨它们。请注意,我们将使用一些单独执行部分标记化管道的方法来向您展示这些步骤的中间结果,但实际上,您应该直接在您的输入上调用标记器(tokenizer)(如第 2 部分所示)。 + +### 标记化 + +标记化过程由标记器(tokenizer)的`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) +``` + +此方法的输出是一个字符串列表或标记(token): + +```python out +['Using', 'a', 'transform', '##er', 'network', 'is', 'simple'] +``` + +这个标记器(tokenizer)是一个子词标记器(tokenizer):它对词进行拆分,直到获得可以用其词汇表表示的标记(token)。`transformer` 就是这种情况,它分为两个标记:`transform` 和 `##er`。 + +### 从词符(token)到输入 ID +输入 ID 的转换由标记器(tokenizer)的`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!”)复制最后两个步骤(标记化和转换为输入 ID)。检查您获得的输入 ID 是否与我们之前获得的相同! + + + +## 解码 + +*解码(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` 方法不仅将索引转换回标记(token),还将属于相同单词的标记(token)组合在一起以生成可读的句子。当我们使用预测新文本的模型(根据提示生成的文本,或序列到序列问题(如翻译或摘要))时,这种行为将非常有用。 + +到现在为止,您应该了解标记器(tokenizer)可以处理的原子操作:标记化、转换为 ID 以及将 ID 转换回字符串。然而,我们只是刮到了冰山一角。在下一节中,我们将采用我们的方法来克服它的限制,并看看如何克服它们。 diff --git a/chapters/zh-CN/chapter2/5.mdx b/chapters/zh-CN/chapter2/5.mdx index 1b73568ed..ffa33176d 100644 --- a/chapters/zh-CN/chapter2/5.mdx +++ b/chapters/zh-CN/chapter2/5.mdx @@ -1,355 +1,355 @@ - - -# 处理多个序列 - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -{#if fw === 'pt'} - -{:else} - -{/if} - -在上一节中,我们探讨了最简单的用例:对一个小长度的序列进行推理。然而,一些问题已经出现: - -* 我们如何处理多个序列? - - -* 我们如何处理多个序列不同长度? - - -* 词汇索引是让模型正常工作的唯一输入吗? - - -* 是否存在序列太长的问题? - -让我们看看这些问题会带来什么样的问题,以及如何使用🤗 Transformers 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) - -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) -# This line will fail. -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) -# This line will fail. -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模型默认情况下需要多个句子。在这里,当我们将分词器应用于一个应用程序时,我们尝试在幕后完成分词器所做的一切,但如果仔细观察,您会发现它不仅将输入ID列表转换为张量,还在其顶部添加了一个维度: - -{#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 -tf.Tensor: shape=(1, 16), dtype=int32, numpy= -array([[ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, - 12172, 2607, 2026, 2878, 2166, 1012, 102]], dtype=int32)> -``` -{/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} - -我们打印输入ID以及生成的logits-以下是输出: - -{#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} - -*Batching* 是一次通过模型发送多个句子的行为。如果你只有一句话,你可以用一个序列构建一个批次: - - -``` -batched_ids = [ids, ids] -``` - -这是一批两个相同的序列! - - - -✏️ **Try it out!** 试试看!将此列表转换为张量并通过模型传递。检查您是否获得与之前相同的登录(但是只有两次) - - -批处理允许模型在输入多个句子时工作。使用多个序列就像使用单个序列构建批一样简单。不过,还有第二个问题。当你试图将两个(或更多)句子组合在一起时,它们的长度可能不同。如果您以前使用过张量,那么您知道它们必须是矩形,因此无法将输入ID列表直接转换为张量。为了解决这个问题,我们通常填充输入。 - -## 填充输入 - -以下列表不能转换为张量: - -```py no-format -batched_ids = [ - [200, 200, 200], - [200, 200] -] -``` - -为了解决这个问题,我们将使用填充使张量具有矩形。Padding通过在值较少的句子中添加一个名为Padding token的特殊单词来确保我们所有的句子长度相同。例如,如果你有10个包含10个单词的句子和1个包含20个单词的句子,填充将确保所有句子都包含20个单词。在我们的示例中,生成的张量如下所示: - -```py no-format -padding_id = 100 - -batched_ids = [ - [200, 200, 200], - [200, 200, padding_id], -] -``` - -可以在tokenizer.pad_token_id中找到填充令牌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} - -我们批处理预测中的logits有点问题:第二行应该与第二句的logits相同,但我们得到了完全不同的值! - - -这是因为Transformer模型的关键特性是关注层,它将每个标记上下文化。这些将考虑填充标记,因为它们涉及序列中的所有标记。为了在通过模型传递不同长度的单个句子时,或者在传递一批应用了相同句子和填充的句子时获得相同的结果,我们需要告诉这些注意层忽略填充标记。这是通过使用 attention mask来实现的。 - -## 注意力面具 - -*Attention masks*是与输入ID张量形状完全相同的张量,用0和1填充:1s表示应注意相应的标记,0s表示不应注意相应的标记(即,模型的注意力层应忽略它们)。 - -让我们用attention mask完成上一个示例: - -{#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} - -现在我们得到了该批中第二个句子的相同登录。 - -请注意,第二个序列的最后一个值是一个填充ID,它在attention mask中是一个0值。 - - - -✏️ 试试看!在第2节中使用的两个句子上手动应用标记化(“我一生都在等待拥抱课程。”和“我非常讨厌这个!”)。通过模型传递它们,并检查您是否获得与第2节中相同的登录。现在使用填充标记将它们批处理在一起,然后创建适当的注意掩码。检查通过模型时是否获得相同的结果! - - - -## 长序列 - -对于Transformers模型,我们可以通过模型的序列长度是有限的。大多数模型处理多达512或1024个令牌的序列,当要求处理更长的序列时,会崩溃。此问题有两种解决方案: - - - -* 使用支持的序列长度较长的模型。 - - -* 截断序列。 - - -模型有不同的支持序列长度,有些模型专门处理很长的序列。 -[Longformer](https://huggingface.co/transformers/model_doc/longformer.html) -这是一个例子,另一个是 -[LED](https://huggingface.co/transformers/model_doc/led.html) -. 如果您正在处理一项需要很长序列的任务,我们建议您查看这些模型。 - -否则,我们建议您通过指定max_sequence_length参数: - -```py -sequence = sequence[:max_sequence_length] -``` + + +# 处理多个序列 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + +{:else} + +{/if} + +在上一节中,我们探讨了最简单的用例:对一个小长度的序列进行推理。然而,一些问题已经出现: + +* 我们如何处理多个序列? + + +* 我们如何处理多个序列不同长度? + + +* 词汇索引是让模型正常工作的唯一输入吗? + + +* 是否存在序列太长的问题? + +让我们看看这些问题会带来什么样的问题,以及如何使用🤗 Transformers 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) + +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) +# This line will fail. +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) +# This line will fail. +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模型默认情况下需要多个句子。在这里,当我们将分词器应用于一个应用程序时,我们尝试在幕后完成分词器所做的一切,但如果仔细观察,您会发现它不仅将输入ID列表转换为张量,还在其顶部添加了一个维度: + +{#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 +tf.Tensor: shape=(1, 16), dtype=int32, numpy= +array([[ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, + 12172, 2607, 2026, 2878, 2166, 1012, 102]], dtype=int32)> +``` +{/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} + +我们打印输入ID以及生成的logits-以下是输出: + +{#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} + +*Batching* 是一次通过模型发送多个句子的行为。如果你只有一句话,你可以用一个序列构建一个批次: + + +``` +batched_ids = [ids, ids] +``` + +这是一批两个相同的序列! + + + +✏️ **Try it out!** 试试看!将此列表转换为张量并通过模型传递。检查您是否获得与之前相同的登录(但是只有两次) + + +批处理允许模型在输入多个句子时工作。使用多个序列就像使用单个序列构建批一样简单。不过,还有第二个问题。当你试图将两个(或更多)句子组合在一起时,它们的长度可能不同。如果您以前使用过张量,那么您知道它们必须是矩形,因此无法将输入ID列表直接转换为张量。为了解决这个问题,我们通常填充输入。 + +## 填充输入 + +以下列表不能转换为张量: + +```py no-format +batched_ids = [ + [200, 200, 200], + [200, 200] +] +``` + +为了解决这个问题,我们将使用填充使张量具有矩形。Padding通过在值较少的句子中添加一个名为Padding token的特殊单词来确保我们所有的句子长度相同。例如,如果你有10个包含10个单词的句子和1个包含20个单词的句子,填充将确保所有句子都包含20个单词。在我们的示例中,生成的张量如下所示: + +```py no-format +padding_id = 100 + +batched_ids = [ + [200, 200, 200], + [200, 200, padding_id], +] +``` + +可以在tokenizer.pad_token_id中找到填充令牌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} + +我们批处理预测中的logits有点问题:第二行应该与第二句的logits相同,但我们得到了完全不同的值! + + +这是因为Transformer模型的关键特性是关注层,它将每个标记上下文化。这些将考虑填充标记,因为它们涉及序列中的所有标记。为了在通过模型传递不同长度的单个句子时,或者在传递一批应用了相同句子和填充的句子时获得相同的结果,我们需要告诉这些注意层忽略填充标记。这是通过使用 attention mask来实现的。 + +## 注意力面具 + +*Attention masks*是与输入ID张量形状完全相同的张量,用0和1填充:1s表示应注意相应的标记,0s表示不应注意相应的标记(即,模型的注意力层应忽略它们)。 + +让我们用attention mask完成上一个示例: + +{#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} + +现在我们得到了该批中第二个句子的相同登录。 + +请注意,第二个序列的最后一个值是一个填充ID,它在attention mask中是一个0值。 + + + +✏️ 试试看!在第2节中使用的两个句子上手动应用标记化(“我一生都在等待拥抱课程。”和“我非常讨厌这个!”)。通过模型传递它们,并检查您是否获得与第2节中相同的登录。现在使用填充标记将它们批处理在一起,然后创建适当的注意掩码。检查通过模型时是否获得相同的结果! + + + +## 长序列 + +对于Transformers模型,我们可以通过模型的序列长度是有限的。大多数模型处理多达512或1024个令牌的序列,当要求处理更长的序列时,会崩溃。此问题有两种解决方案: + + + +* 使用支持的序列长度较长的模型。 + + +* 截断序列。 + + +模型有不同的支持序列长度,有些模型专门处理很长的序列。 +[Longformer](https://huggingface.co/transformers/model_doc/longformer.html) +这是一个例子,另一个是 +[LED](https://huggingface.co/transformers/model_doc/led.html) +. 如果您正在处理一项需要很长序列的任务,我们建议您查看这些模型。 + +否则,我们建议您通过指定max_sequence_length参数: + +```py +sequence = sequence[:max_sequence_length] +``` diff --git a/chapters/zh-CN/chapter2/6.mdx b/chapters/zh-CN/chapter2/6.mdx index e4b5c6295..95381ad9f 100644 --- a/chapters/zh-CN/chapter2/6.mdx +++ b/chapters/zh-CN/chapter2/6.mdx @@ -1,165 +1,165 @@ - - -# 把它们放在一起 - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -在最后几节中,我们一直在尽最大努力手工完成大部分工作。我们探讨了标记化器的工作原理,并研究了标记化、到输入ID的转换、填充、截断和注意掩码。 - -然而,正如我们在第2节中所看到的,🤗 Transformers API可以通过一个高级函数为我们处理所有这些,我们将在这里深入讨论。当你直接在句子上调用标记器时,你会得到准备通过模型传递的输入 - -```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,它包括输入 ID和注意力掩码(attention mask)。其他接受额外输入的模型也会有标记器对象的输出。 - -正如我们将在下面的一些示例中看到的,这种方法非常强大。首先,它可以标记单个序列: - -```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 -# Will pad the sequences up to the maximum sequence length -model_inputs = tokenizer(sequences, padding="longest") - -# Will pad the sequences up to the model max length -# (512 for BERT or DistilBERT) -model_inputs = tokenizer(sequences, padding="max_length") - -# Will pad the sequences up to the specified 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!"] - -# Will truncate the sequences that are longer than the model max length -# (512 for BERT or DistilBERT) -model_inputs = tokenizer(sequences, truncation=True) - -# Will truncate the sequences that are longer than the specified max length -model_inputs = tokenizer(sequences, max_length=8, truncation=True) -``` - -标记器对象可以处理到特定框架张量的转换,然后可以直接发送到模型。例如,在下面的代码示例中,我们提示标记器从不同的框架返回张量——`"pt"`返回Py Torch张量,`"tf"`返回TensorFlow张量,`"np"`返回NumPy数组: - -```py -sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] - -# Returns PyTorch tensors -model_inputs = tokenizer(sequences, padding=True, return_tensors="pt") - -# Returns TensorFlow tensors -model_inputs = tokenizer(sequences, padding=True, return_tensors="tf") - -# Returns NumPy arrays -model_inputs = tokenizer(sequences, padding=True, return_tensors="np") -``` - -## 特殊词符(token) - -如果我们看一下标记器返回的输入 ID,我们会发现它们与之前的略有不同: - -```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] -``` - -一个在开始时添加了一个标记(token) ID,一个在结束时添加了一个标记(token) ID。让我们解码上面的两个ID序列,看看这是怎么回事: - -```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]`。这是因为模型是用这些数据预训练的,所以为了得到相同的推理结果,我们还需要添加它们。请注意,有些模型不添加特殊单词,或者添加不同的单词;模型也可能只在开头或结尾添加这些特殊单词。在任何情况下,标记器都知道需要哪些词符,并将为您处理这些词符。 - -## 结束:从标记器到模型 - -现在我们已经看到了标记器对象在应用于文本时使用的所有单独步骤,让我们最后一次看看它如何处理多个序列(填充!),非常长的序列(截断!),以及多种类型的张量及其主要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} + + +# 把它们放在一起 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +在最后几节中,我们一直在尽最大努力手工完成大部分工作。我们探讨了标记化器的工作原理,并研究了标记化、到输入ID的转换、填充、截断和注意掩码。 + +然而,正如我们在第2节中所看到的,🤗 Transformers API可以通过一个高级函数为我们处理所有这些,我们将在这里深入讨论。当你直接在句子上调用标记器时,你会得到准备通过模型传递的输入 + +```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,它包括输入 ID和注意力掩码(attention mask)。其他接受额外输入的模型也会有标记器对象的输出。 + +正如我们将在下面的一些示例中看到的,这种方法非常强大。首先,它可以标记单个序列: + +```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 +# Will pad the sequences up to the maximum sequence length +model_inputs = tokenizer(sequences, padding="longest") + +# Will pad the sequences up to the model max length +# (512 for BERT or DistilBERT) +model_inputs = tokenizer(sequences, padding="max_length") + +# Will pad the sequences up to the specified 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!"] + +# Will truncate the sequences that are longer than the model max length +# (512 for BERT or DistilBERT) +model_inputs = tokenizer(sequences, truncation=True) + +# Will truncate the sequences that are longer than the specified max length +model_inputs = tokenizer(sequences, max_length=8, truncation=True) +``` + +标记器对象可以处理到特定框架张量的转换,然后可以直接发送到模型。例如,在下面的代码示例中,我们提示标记器从不同的框架返回张量——`"pt"`返回Py Torch张量,`"tf"`返回TensorFlow张量,`"np"`返回NumPy数组: + +```py +sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] + +# Returns PyTorch tensors +model_inputs = tokenizer(sequences, padding=True, return_tensors="pt") + +# Returns TensorFlow tensors +model_inputs = tokenizer(sequences, padding=True, return_tensors="tf") + +# Returns NumPy arrays +model_inputs = tokenizer(sequences, padding=True, return_tensors="np") +``` + +## 特殊词符(token) + +如果我们看一下标记器返回的输入 ID,我们会发现它们与之前的略有不同: + +```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] +``` + +一个在开始时添加了一个标记(token) ID,一个在结束时添加了一个标记(token) ID。让我们解码上面的两个ID序列,看看这是怎么回事: + +```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]`。这是因为模型是用这些数据预训练的,所以为了得到相同的推理结果,我们还需要添加它们。请注意,有些模型不添加特殊单词,或者添加不同的单词;模型也可能只在开头或结尾添加这些特殊单词。在任何情况下,标记器都知道需要哪些词符,并将为您处理这些词符。 + +## 结束:从标记器到模型 + +现在我们已经看到了标记器对象在应用于文本时使用的所有单独步骤,让我们最后一次看看它如何处理多个序列(填充!),非常长的序列(截断!),以及多种类型的张量及其主要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/zh-CN/chapter2/7.mdx b/chapters/zh-CN/chapter2/7.mdx index 1835352f6..ef205f164 100644 --- a/chapters/zh-CN/chapter2/7.mdx +++ b/chapters/zh-CN/chapter2/7.mdx @@ -1,27 +1,32 @@ -# 基本用法完成! - -很好地完成了到这里的课程!总而言之,在本章中,您可以: - -- 学习了Transformers模型的基本构造块。 - - -- 了解了标记化管道的组成。 - - -- 了解了如何在实践中使用Transformers模型。 - - -- 学习了如何利用分词器将文本转换为模型可以理解的张量。 - - -- 将分词器和模型一起设置,以从文本到预测。 - - -- 了解了inputs IDs的局限性,并了解了attention mask。 - - -- 使用多功能和可配置的分词器方法。 - - - -从现在起,您应该能够自由浏览🤗 Transformers文档:词汇听起来很熟悉,并且您已经看到了大部分时间将使用的方法。 +# 基本用法完成! + + + +很好地完成了到这里的课程!总而言之,在本章中,您可以: + +- 学习了Transformers模型的基本构造块。 + + +- 了解了标记化管道的组成。 + + +- 了解了如何在实践中使用Transformers模型。 + + +- 学习了如何利用分词器将文本转换为模型可以理解的张量。 + + +- 将分词器和模型一起设置,以从文本到预测。 + + +- 了解了inputs IDs的局限性,并了解了attention mask。 + + +- 使用多功能和可配置的分词器方法。 + + + +从现在起,您应该能够自由浏览🤗 Transformers文档:词汇听起来很熟悉,并且您已经看到了大部分时间将使用的方法。 diff --git a/chapters/zh-CN/chapter2/8.mdx b/chapters/zh-CN/chapter2/8.mdx index 89cc36502..fda118ada 100644 --- a/chapters/zh-CN/chapter2/8.mdx +++ b/chapters/zh-CN/chapter2/8.mdx @@ -1,293 +1,298 @@ - - - - -# 章末小测试 - -### 1. 语言建模 Pipeline 的顺序是什么? - - -### 2. Transformer模型的输出有多少个维度,每个维度分别是什么? - - -### 3.下列哪一个是Subword标记(Tokenization)的例子(从分词的颗粒度来划分)? - - -### 4.什么是模型的Head层? - - -{#if fw === 'pt'} -### 5.什么是AutoModel? -AutoNLP 产品相混淆了?" - }, - { - text: "一个根据Checkpoint(检查点)返回模型体系结构的对象", - explain: "确切地说: AutoModel只需要知道初始化的Checkpoint(检查点)就可以返回正确的体系结构。", - correct: true - }, - { - text: "一种可以自动检测输入语言来加载正确权重的模型", - explain: "不正确; 虽然有些Checkpoint(检查点)和模型能够处理多种语言,但是没有内置的工具可以根据语言自动选择Checkpoint(检查点)。您应该前往 Model Hub 寻找完成所需任务的最佳Checkpoint(检查点)!" - } - ]} -/> - -{:else} -### 5.什么是 TFAutoModel? -AutoNLP 产品相混淆了?" - }, - { - text: "一个根据Checkpoint(检查点)返回模型体系结构的对象", - explain: "确切地说: TFAutoModel只需要知道初始化的Checkpoint(检查点)就可以返回正确的体系结构。", - correct: true - }, - { - text: "一种可以自动检测输入语言来加载正确权重的模型", - explain: "不正确; 虽然有些Checkpoint(检查点)和模型能够处理多种语言,但是没有内置的工具可以根据语言自动选择Checkpoint(检查点)。您应该前往 Model Hub 寻找完成所需任务的最佳Checkpoint(检查点)!" - } - ]} -/> - -{/if} - -### 6.当将不同长度的序列批处理在一起时,需要进行哪些处理? - - -### 7.将 SoftMax激活函数应用于序列分类(Sequence Classification)模型的 logits 输出有什么意义? - - -### 8.大多数标记器(Tokenizer)的API以什么方法为核心? -编码 ,因为它可以将文本编码为id,将预测的id解码为文本", - explain: "错! 虽然 编码 方法确实存在于标记器中,但是它不存在于模型中。" - }, - { - text: "直接调用标记器(Tokenizer)对象。", - explain: "完全正确!标记化器(Tokenizer) 的 __call__方法是一个非常强大的方法,可以处理几乎任何事情。它也是从模型中获取预测的方法。", - correct: true - }, - { - text: "pad(填充)", - explain: "错! pad(填充)非常有用,但它只是标记器(Tokenizer) API的一部分。" - }, - { - text: "tokenize(标记)", - explain: "可以说,tokenize(标记)方法是最有用的方法之一,但它不是标记器(Tokenizer) 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: "包含所有标记(Token)的字符串", - explain: "这将是次优的,因为Tokenizer会将字符串拆分为多个标记的列表。" - } - ]} -/> - -{#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} + + + + +# 章末小测试 + + + +### 1. 语言建模 Pipeline 的顺序是什么? + + +### 2. Transformer模型的输出有多少个维度,每个维度分别是什么? + + +### 3.下列哪一个是Subword标记(Tokenization)的例子(从分词的颗粒度来划分)? + + +### 4.什么是模型的Head层? + + +{#if fw === 'pt'} +### 5.什么是AutoModel? +AutoNLP 产品相混淆了?" + }, + { + text: "一个根据Checkpoint(检查点)返回模型体系结构的对象", + explain: "确切地说: AutoModel只需要知道初始化的Checkpoint(检查点)就可以返回正确的体系结构。", + correct: true + }, + { + text: "一种可以自动检测输入语言来加载正确权重的模型", + explain: "不正确; 虽然有些Checkpoint(检查点)和模型能够处理多种语言,但是没有内置的工具可以根据语言自动选择Checkpoint(检查点)。您应该前往 Model Hub 寻找完成所需任务的最佳Checkpoint(检查点)!" + } + ]} +/> + +{:else} +### 5.什么是 TFAutoModel? +AutoNLP 产品相混淆了?" + }, + { + text: "一个根据Checkpoint(检查点)返回模型体系结构的对象", + explain: "确切地说: TFAutoModel只需要知道初始化的Checkpoint(检查点)就可以返回正确的体系结构。", + correct: true + }, + { + text: "一种可以自动检测输入语言来加载正确权重的模型", + explain: "不正确; 虽然有些Checkpoint(检查点)和模型能够处理多种语言,但是没有内置的工具可以根据语言自动选择Checkpoint(检查点)。您应该前往 Model Hub 寻找完成所需任务的最佳Checkpoint(检查点)!" + } + ]} +/> + +{/if} + +### 6.当将不同长度的序列批处理在一起时,需要进行哪些处理? + + +### 7.将 SoftMax激活函数应用于序列分类(Sequence Classification)模型的 logits 输出有什么意义? + + +### 8.大多数标记器(Tokenizer)的API以什么方法为核心? +编码 ,因为它可以将文本编码为id,将预测的id解码为文本", + explain: "错! 虽然 编码 方法确实存在于标记器中,但是它不存在于模型中。" + }, + { + text: "直接调用标记器(Tokenizer)对象。", + explain: "完全正确!标记化器(Tokenizer) 的 __call__方法是一个非常强大的方法,可以处理几乎任何事情。它也是从模型中获取预测的方法。", + correct: true + }, + { + text: "pad(填充)", + explain: "错! pad(填充)非常有用,但它只是标记器(Tokenizer) API的一部分。" + }, + { + text: "tokenize(标记)", + explain: "可以说,tokenize(标记)方法是最有用的方法之一,但它不是标记器(Tokenizer) 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: "包含所有标记(Token)的字符串", + explain: "这将是次优的,因为Tokenizer会将字符串拆分为多个标记的列表。" + } + ]} +/> + +{#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/zh-CN/chapter3/1.mdx b/chapters/zh-CN/chapter3/1.mdx index f441c3f21..e744bf78e 100644 --- a/chapters/zh-CN/chapter3/1.mdx +++ b/chapters/zh-CN/chapter3/1.mdx @@ -1,21 +1,26 @@ - - -# 本章简介 - -在 [第二章](/course/chapter2) 我们探索了如何使用标记器(Tokenizer)和预训练模型进行预测。但是,如果您想为自己的数据集微调预训练模型,该怎么做呢?这就是本章的主题!你将学到: - -{#if fw === 'pt'} -* 如何从模型中心(hub)准备大型数据集 -* 如何使用高级`训练`API微调一个模型 -* 如何使用自定义训练过程 -* 如何利用🤗 Accelerate库在任何分布式设备上轻松运行自定义训练过程 - -{:else} -* 如何从模型中心(hub)准备大型数据集 -* 如何使用 Keras 微调模型 -* 如何使用 Keras 进行预测 -* 如何使用自定义指标 - -{/if} - + + +# 本章简介 + + + +在 [第二章](/course/chapter2) 我们探索了如何使用标记器(Tokenizer)和预训练模型进行预测。但是,如果您想为自己的数据集微调预训练模型,该怎么做呢?这就是本章的主题!你将学到: + +{#if fw === 'pt'} +* 如何从模型中心(hub)准备大型数据集 +* 如何使用高级`训练`API微调一个模型 +* 如何使用自定义训练过程 +* 如何利用🤗 Accelerate库在任何分布式设备上轻松运行自定义训练过程 + +{:else} +* 如何从模型中心(hub)准备大型数据集 +* 如何使用 Keras 微调模型 +* 如何使用 Keras 进行预测 +* 如何使用自定义指标 + +{/if} + 为了将经过训练的参数上传到Hugging Face Hub,您需要一个huggingface.co帐户: [创建一个账户](https://huggingface.co/join) \ No newline at end of file diff --git a/chapters/zh-CN/chapter3/2.mdx b/chapters/zh-CN/chapter3/2.mdx index 4a92170fc..cda565d9b 100644 --- a/chapters/zh-CN/chapter3/2.mdx +++ b/chapters/zh-CN/chapter3/2.mdx @@ -1,383 +1,383 @@ - - -# 处理数据 - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -{#if fw === 'pt'} -这一小节学习[第一小节](/course/chapter2)中提到的“如何使用模型中心(hub)大型数据集”,下面是我们用模型中心的数据在PyTorch上训练句子分类器的一个例子: - -```python -import torch -from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification - -# Same as before -checkpoint = "bert-base-uncased" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -model = AutoModelForSequenceClassification.from_pretrained(checkpoint) -sequences = [ - "I've been waiting for a HuggingFace course my whole life.", - "This course is amazing!", -] -batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") - -# This is new -batch["labels"] = torch.tensor([1, 1]) - -optimizer = AdamW(model.parameters()) -loss = model(**batch).loss -loss.backward() -optimizer.step() -``` -{:else} -这一小节学习[第一小节](/course/chapter2)中提到的“如何使用模型中心(hub)大型数据集”,下面是我们用模型中心的数据在TensorFlow上训练句子分类器的一个例子: - -```python -import tensorflow as tf -import numpy as np -from transformers import AutoTokenizer, TFAutoModelForSequenceClassification - -# Same as before -checkpoint = "bert-base-uncased" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) -sequences = [ - "I've been waiting for a HuggingFace course my whole life.", - "This course is amazing!", -] -batch = dict(tokenizer(sequences, padding=True, truncation=True, return_tensors="tf")) - -# This is new -model.compile(optimizer="adam", loss="sparse_categorical_crossentropy") -labels = tf.convert_to_tensor([1, 1]) -model.train_on_batch(batch, labels) -``` -{/if} - -当然,仅仅用两句话训练模型不会产生很好的效果。为了获得更好的结果,您需要准备一个更大的数据集。 - -在本节中,我们将使用MRPC(微软研究释义语料库)数据集作为示例,该数据集由威廉·多兰和克里斯·布罗克特在[这篇文章](https://www.aclweb.org/anthology/I05-5002.pdf)发布。该数据集由5801对句子组成,每个句子对带有一个标签,指示它们是否为同义(即,如果两个句子的意思相同)。我们在本章中选择了它,因为它是一个小数据集,所以很容易对它进行训练。 - -### 从模型中心(Hub)加载数据集 - -{#if fw === 'pt'} - -{:else} - -{/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个不同文本分类任务中的性能。 - -🤗 Datasets库提供了一个非常便捷的命令,可以在模型中心(hub)上下载和缓存数据集。我们可以通过以下的代码下载MRPC数据集: - -```py -from datasets import load_dataset - -raw_datasets = load_dataset("glue", "mrpc") -raw_datasets -``` - -```python out -DatasetDict({ - train: Dataset({ - features: ['sentence1', 'sentence2', 'label', 'idx'], - num_rows: 3668 - }) - validation: Dataset({ - features: ['sentence1', 'sentence2', 'label', 'idx'], - num_rows: 408 - }) - test: Dataset({ - features: ['sentence1', 'sentence2', 'label', 'idx'], - num_rows: 1725 - }) -}) -``` - -正如你所看到的,我们获得了一个**DatasetDict**对象,其中包含训练集、验证集和测试集。每一个集合都包含几个列(**sentence1**, **sentence2**, **label**, and **idx**)以及一个代表行数的变量,即每个集合中的行的个数(因此,训练集中有3668对句子,验证集中有408对,测试集中有1725对)。 - -默认情况下,此命令在下载数据集并缓存到 **~/.cache/huggingface/dataset**. 回想一下第2章,您可以通过设置**HF_HOME**环境变量来自定义缓存的文件夹。 - -我们可以访问我们数据集中的每一个**raw_train_dataset**对象,如使用字典: - -```py -raw_train_dataset = raw_datasets["train"] -raw_train_dataset[0] -``` - -```python out -{'idx': 0, - 'label': 1, - 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', - 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} -``` - -我们可以看到标签已经是整数了,所以我们不需要对标签做任何预处理。要知道哪个数字对应于哪个标签,我们可以查看**raw_train_dataset**的**features**. 这将告诉我们每列的类型: - -```py -raw_train_dataset.features -``` - -```python out -{'sentence1': Value(dtype='string', id=None), - 'sentence2': Value(dtype='string', id=None), - 'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None), - 'idx': Value(dtype='int32', id=None)} -``` - -在上面的例子之中,**Label(标签)** 是一种**ClassLabel(分类标签)**,使用整数建立起到类别标签的映射关系。**0**对应于**not_equivalent**,**1**对应于**equivalent**。 - - - -✏️ **试试看!** 查看训练集的第15行元素和验证集的87行元素。他们的标签是什么? - - - -### 预处理数据集 - -{#if fw === 'pt'} - -{:else} - -{/if} - -为了预处理数据集,我们需要将文本转换为模型能够理解的数字。正如你在[第二章](/course/chapter2)上看到的那样 - -```py -from transformers import AutoTokenizer - -checkpoint = "bert-base-uncased" -tokenizer = AutoTokenizer.from_pretrained(checkpoint) -tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"]) -tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"]) -``` - -然而,在两句话传递给模型,预测这两句话是否是同义之前。我们需要这两句话依次进行适当的预处理。幸运的是,标记器不仅仅可以输入单个句子还可以输入一组句子,并按照我们的BERT模型所期望的输入进行处理: - -```py -inputs = tokenizer("This is the first sentence.", "This is the second one.") -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] -} -``` - -我们在[第二章](/course/chapter2) 讨论了**输入词id(input_ids)** 和 **注意力遮罩(attention_mask)** ,但我们在那个时候没有讨论**类型标记ID(token_type_ids)**。在这个例子中,**类型标记ID(token_type_ids)**的作用就是告诉模型输入的哪一部分是第一句,哪一部分是第二句。 - - - -✏️ ** 试试看!** 选取训练集中的第15个元素,将两句话分别标记为一对。结果和上方的例子有什么不同? - - - -如果我们将**input_ids**中的id转换回文字: - -```py -tokenizer.convert_ids_to_tokens(inputs["input_ids"]) -``` - -我们将得到: - -```python out -['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] -``` - -所以我们看到模型需要输入的形式是 **[CLS] sentence1 [SEP] sentence2 [SEP]**。因此,当有两句话的时候。**类型标记ID(token_type_ids)** 的值是: - -```python out -['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] -[ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] -``` - -如您所见,输入中 **[CLS] sentence1 [SEP]** 它们的类型标记ID均为**0**,而其他部分,对应于**sentence2 [SEP]**,所有的类型标记ID均为**1**. - -请注意,如果选择其他的检查点,则不一定具有**类型标记ID(token_type_ids)**(例如,如果使用DistilBERT模型,就不会返回它们)。只有当它在预训练期间使用过这一层,模型在构建时依赖它们,才会返回它们。 - -用类型标记ID对BERT进行预训练,并且使用[第一章](/course/chapter1)的遮罩语言模型,还有一个额外的应用类型,叫做下一句预测. 这项任务的目标是建立成对句子之间关系的模型。 - -在下一个句子预测任务中,会给模型输入成对的句子(带有随机遮罩的标记),并被要求预测第二个句子是否紧跟第一个句子。为了提高模型的泛化能力,数据集中一半的两个句子在原始文档中挨在一起,另一半的两个句子来自两个不同的文档。 - -一般来说,你不需要担心是否有**类型标记ID(token_type_ids)**。在您的标输入中:只要您对标记器和模型使用相同的检查点,一切都会很好,因为标记器知道向其模型提供什么。 - -现在我们已经了解了标记器如何处理一对句子,我们可以使用它对整个数据集进行处理:如[之前的章节](/course/chapter2),我们可以给标记器提供一组句子,第一个参数是它第一个句子的列表,第二个参数是第二个句子的列表。这也与我们在[第二章](/course/chapter2)中看到的填充和截断选项兼容. 因此,预处理训练数据集的一种方法是: - -```py -tokenized_dataset = tokenizer( - raw_datasets["train"]["sentence1"], - raw_datasets["train"]["sentence2"], - padding=True, - truncation=True, -) -``` - -这很有效,但它的缺点是返回字典(字典的键是**输入词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)方法,如果我们需要做更多的预处理而不仅仅是标记化,那么这也给了我们一些额外的自定义的方法。这个方法的工作原理是在数据集的每个元素上应用一个函数,因此让我们定义一个标记输入的函数: - -```py -def tokenize_function(example): - return tokenizer(example["sentence1"], example["sentence2"], truncation=True) -``` - -此函数的输入是一个字典(与数据集的项类似),并返回一个包含**输入词id(input_ids)** , **注意力遮罩(attention_mask)** 和 **类型标记ID(token_type_ids)** 键的新字典。请注意,如果像上面的**示例**一样,如果键所对应的值包含多个句子(每个键作为一个句子列表),那么它依然可以工作,就像前面的例子一样标记器可以处理成对的句子列表。这样的话我们可以在调用**map()**使用该选项 **batched=True** ,这将显著加快标记与标记的速度。这个**标记器**来自[🤗 Tokenizers](https://github.com/huggingface/tokenizers)库由Rust编写而成。当我们一次给它大量的输入时,这个标记器可以非常快。 - -请注意,我们现在在标记函数中省略了**padding**参数。这是因为在标记的时候将所有样本填充到最大长度的效率不高。一个更好的做法:在构建批处理时填充样本更好,因为这样我们只需要填充到该批处理中的最大长度,而不是整个数据集的最大长度。当输入长度变化很大时,这可以节省大量时间和处理能力! - -下面是我们如何在所有数据集上同时应用标记函数。我们在调用**map**时使用了**batch =True**,这样函数就可以同时应用到数据集的多个元素上,而不是分别应用到每个元素上。这将使我们的预处理快许多 - -```py -tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) -tokenized_datasets -``` - -🤗Datasets库应用这种处理的方式是向数据集添加新的字段,每个字段对应预处理函数返回的字典中的每个键: - -```python out -DatasetDict({ - train: Dataset({ - features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], - num_rows: 3668 - }) - validation: Dataset({ - features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], - num_rows: 408 - }) - test: Dataset({ - features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], - num_rows: 1725 - }) -}) -``` - -在使用预处理函数**map()**时,甚至可以通过传递**num_proc**参数使用并行处理。我们在这里没有这样做,因为🤗标记器库已经使用多个线程来更快地标记我们的样本,但是如果您没有使用该库支持的快速标记器,使用**num_proc**可能会加快预处理。 - -我们的**标记函数(tokenize_function)**返回包含**输入词id(input_ids)** , **注意力遮罩(attention_mask)** 和 **类型标记ID(token_type_ids)** 键的字典,所以这三个字段被添加到数据集的标记的结果中。注意,如果预处理函数**map()**为现有键返回一个新值,那将会修改原有键的值。 - -最后一件我们需要做的事情是,当我们一起批处理元素时,将所有示例填充到最长元素的长度——我们称之为动态填充。 - -### 动态填充 - - - -{#if fw === 'pt'} -负责在批处理中将数据整理为一个batch的函数称为*collate函数*。它是你可以在构建**DataLoader**时传递的一个参数,默认是一个函数,它将把你的数据集转换为PyTorch张量,并将它们拼接起来(如果你的元素是列表、元组或字典,则会使用递归)。这在我们的这个例子中下是不可行的,因为我们的输入不是都是相同大小的。我们故意在之后每个batch上进行填充,避免有太多填充的过长的输入。这将大大加快训练速度,但请注意,如果你在TPU上训练,这可能会导致问题——TPU喜欢固定的形状,即使这需要额外的填充。 - -{:else} - -负责在批处理中将数据整理为一个batch的函数称为*collate函数*。它只会将您的样本转换为 tf.Tensor并将它们拼接起来(如果你的元素是列表、元组或字典,则会使用递归)。这在我们的这个例子中下是不可行的,因为我们的输入不是都是相同大小的。我们故意在之后每个batch上进行填充,避免有太多填充的过长的输入。这将大大加快训练速度,但请注意,如果你在TPU上训练,这可能会导致问题——TPU喜欢固定的形状,即使这需要额外的填充。 - -{/if} - -为了解决句子长度统一的问题,我们必须定义一个collate函数,该函数会将每个batch句子填充到正确的长度。幸运的是,🤗transformer库通过**DataCollatorWithPadding**为我们提供了这样一个函数。当你实例化它时,需要一个标记器(用来知道使用哪个词来填充,以及模型期望填充在左边还是右边),并将做你需要的一切: - -{#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} - -为了测试这个新玩具,让我们从我们的训练集中抽取几个样本。这里,我们删除列**idx**, **sentence1**和**sentence2**,因为不需要它们,并查看一个batch中每个条目的长度: - -```py -samples = tokenized_datasets["train"][:8] -samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]} -[len(x) for x in samples["input_ids"]] -``` - -```python out -[50, 59, 47, 67, 59, 50, 62, 32] -``` - -毫无疑问,我们得到了不同长度的样本,从32到67。动态填充意味着该批中的所有样本都应该填充到长度为67,这是该批中的最大长度。如果没有动态填充,所有的样本都必须填充到整个数据集中的最大长度,或者模型可以接受的最大长度。让我们再次检查**data_collator**是否正确地动态填充了这批样本: - -```py: - -```py -batch = data_collator(samples) -{k: v.shape for k, v in batch.items()} -``` - -{#if fw === 'tf'} - -```python out -{'attention_mask': TensorShape([8, 67]), - 'input_ids': TensorShape([8, 67]), - 'token_type_ids': TensorShape([8, 67]), - 'labels': TensorShape([8])} -``` - -{:else} - -```python out -{'attention_mask': torch.Size([8, 67]), - 'input_ids': torch.Size([8, 67]), - 'token_type_ids': torch.Size([8, 67]), - 'labels': torch.Size([8])} -``` - -看起来不错!现在,我们已经将原始文本转化为了模型可以处理的数据,我们已准备好对其进行微调! - -{/if} - - - -✏️ ** 试试看!** 在GLUE SST-2数据集上应用预处理。它有点不同,因为它是由单个句子而不是成对的句子组成的,但是我们所做的其他事情看起来应该是一样的。另一个更难的挑战,请尝试编写一个可用于任何GLUE任务的预处理函数。 - - - -{#if fw === 'tf'} - -现在我们有了dataset和data collator,我们需要将dataset批量地应用data collator。 我们可以手动加载批次并整理它们,但这需要大量工作,而且可能性能也不是很好。 相反,有一个简单的方法可以为这个问题提供高效的解决方案:`to_tf_dataset()`。 这将在您的数据集上调用一个 `tf.data.Dataset`的方法,这个方法带有一个可选的data collator功能。 `tf.data.Dataset` 是 Keras 可用于 `model.fit()` 的原生 TensorFlow 格式,因此这种方法会立即将🤗 Dataset 转换为可用于训练的格式。 让我们看看它在我们的数据集上是如何使用的! - -```py -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, -) -``` - -就是这样! 我们可以将这些数据集带入下一节,在经过所有艰苦的数据预处理工作之后,训练将变得非常简单。 - -{/if} + + +# 处理数据 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} +这一小节学习[第一小节](/course/chapter2)中提到的“如何使用模型中心(hub)大型数据集”,下面是我们用模型中心的数据在PyTorch上训练句子分类器的一个例子: + +```python +import torch +from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification + +# Same as before +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "This course is amazing!", +] +batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") + +# This is new +batch["labels"] = torch.tensor([1, 1]) + +optimizer = AdamW(model.parameters()) +loss = model(**batch).loss +loss.backward() +optimizer.step() +``` +{:else} +这一小节学习[第一小节](/course/chapter2)中提到的“如何使用模型中心(hub)大型数据集”,下面是我们用模型中心的数据在TensorFlow上训练句子分类器的一个例子: + +```python +import tensorflow as tf +import numpy as np +from transformers import AutoTokenizer, TFAutoModelForSequenceClassification + +# Same as before +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "This course is amazing!", +] +batch = dict(tokenizer(sequences, padding=True, truncation=True, return_tensors="tf")) + +# This is new +model.compile(optimizer="adam", loss="sparse_categorical_crossentropy") +labels = tf.convert_to_tensor([1, 1]) +model.train_on_batch(batch, labels) +``` +{/if} + +当然,仅仅用两句话训练模型不会产生很好的效果。为了获得更好的结果,您需要准备一个更大的数据集。 + +在本节中,我们将使用MRPC(微软研究释义语料库)数据集作为示例,该数据集由威廉·多兰和克里斯·布罗克特在[这篇文章](https://www.aclweb.org/anthology/I05-5002.pdf)发布。该数据集由5801对句子组成,每个句子对带有一个标签,指示它们是否为同义(即,如果两个句子的意思相同)。我们在本章中选择了它,因为它是一个小数据集,所以很容易对它进行训练。 + +### 从模型中心(Hub)加载数据集 + +{#if fw === 'pt'} + +{:else} + +{/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个不同文本分类任务中的性能。 + +🤗 Datasets库提供了一个非常便捷的命令,可以在模型中心(hub)上下载和缓存数据集。我们可以通过以下的代码下载MRPC数据集: + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("glue", "mrpc") +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 408 + }) + test: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 1725 + }) +}) +``` + +正如你所看到的,我们获得了一个**DatasetDict**对象,其中包含训练集、验证集和测试集。每一个集合都包含几个列(**sentence1**, **sentence2**, **label**, and **idx**)以及一个代表行数的变量,即每个集合中的行的个数(因此,训练集中有3668对句子,验证集中有408对,测试集中有1725对)。 + +默认情况下,此命令在下载数据集并缓存到 **~/.cache/huggingface/dataset**. 回想一下第2章,您可以通过设置**HF_HOME**环境变量来自定义缓存的文件夹。 + +我们可以访问我们数据集中的每一个**raw_train_dataset**对象,如使用字典: + +```py +raw_train_dataset = raw_datasets["train"] +raw_train_dataset[0] +``` + +```python out +{'idx': 0, + 'label': 1, + 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', + 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} +``` + +我们可以看到标签已经是整数了,所以我们不需要对标签做任何预处理。要知道哪个数字对应于哪个标签,我们可以查看**raw_train_dataset**的**features**. 这将告诉我们每列的类型: + +```py +raw_train_dataset.features +``` + +```python out +{'sentence1': Value(dtype='string', id=None), + 'sentence2': Value(dtype='string', id=None), + 'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None), + 'idx': Value(dtype='int32', id=None)} +``` + +在上面的例子之中,**Label(标签)** 是一种**ClassLabel(分类标签)**,使用整数建立起到类别标签的映射关系。**0**对应于**not_equivalent**,**1**对应于**equivalent**。 + + + +✏️ **试试看!** 查看训练集的第15行元素和验证集的87行元素。他们的标签是什么? + + + +### 预处理数据集 + +{#if fw === 'pt'} + +{:else} + +{/if} + +为了预处理数据集,我们需要将文本转换为模型能够理解的数字。正如你在[第二章](/course/chapter2)上看到的那样 + +```py +from transformers import AutoTokenizer + +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"]) +tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"]) +``` + +然而,在两句话传递给模型,预测这两句话是否是同义之前。我们需要这两句话依次进行适当的预处理。幸运的是,标记器不仅仅可以输入单个句子还可以输入一组句子,并按照我们的BERT模型所期望的输入进行处理: + +```py +inputs = tokenizer("This is the first sentence.", "This is the second one.") +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] +} +``` + +我们在[第二章](/course/chapter2) 讨论了**输入词id(input_ids)** 和 **注意力遮罩(attention_mask)** ,但我们在那个时候没有讨论**类型标记ID(token_type_ids)**。在这个例子中,**类型标记ID(token_type_ids)**的作用就是告诉模型输入的哪一部分是第一句,哪一部分是第二句。 + + + +✏️ ** 试试看!** 选取训练集中的第15个元素,将两句话分别标记为一对。结果和上方的例子有什么不同? + + + +如果我们将**input_ids**中的id转换回文字: + +```py +tokenizer.convert_ids_to_tokens(inputs["input_ids"]) +``` + +我们将得到: + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +``` + +所以我们看到模型需要输入的形式是 **[CLS] sentence1 [SEP] sentence2 [SEP]**。因此,当有两句话的时候。**类型标记ID(token_type_ids)** 的值是: + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +[ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] +``` + +如您所见,输入中 **[CLS] sentence1 [SEP]** 它们的类型标记ID均为**0**,而其他部分,对应于**sentence2 [SEP]**,所有的类型标记ID均为**1**. + +请注意,如果选择其他的检查点,则不一定具有**类型标记ID(token_type_ids)**(例如,如果使用DistilBERT模型,就不会返回它们)。只有当它在预训练期间使用过这一层,模型在构建时依赖它们,才会返回它们。 + +用类型标记ID对BERT进行预训练,并且使用[第一章](/course/chapter1)的遮罩语言模型,还有一个额外的应用类型,叫做下一句预测. 这项任务的目标是建立成对句子之间关系的模型。 + +在下一个句子预测任务中,会给模型输入成对的句子(带有随机遮罩的标记),并被要求预测第二个句子是否紧跟第一个句子。为了提高模型的泛化能力,数据集中一半的两个句子在原始文档中挨在一起,另一半的两个句子来自两个不同的文档。 + +一般来说,你不需要担心是否有**类型标记ID(token_type_ids)**。在您的标输入中:只要您对标记器和模型使用相同的检查点,一切都会很好,因为标记器知道向其模型提供什么。 + +现在我们已经了解了标记器如何处理一对句子,我们可以使用它对整个数据集进行处理:如[之前的章节](/course/chapter2),我们可以给标记器提供一组句子,第一个参数是它第一个句子的列表,第二个参数是第二个句子的列表。这也与我们在[第二章](/course/chapter2)中看到的填充和截断选项兼容. 因此,预处理训练数据集的一种方法是: + +```py +tokenized_dataset = tokenizer( + raw_datasets["train"]["sentence1"], + raw_datasets["train"]["sentence2"], + padding=True, + truncation=True, +) +``` + +这很有效,但它的缺点是返回字典(字典的键是**输入词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)方法,如果我们需要做更多的预处理而不仅仅是标记化,那么这也给了我们一些额外的自定义的方法。这个方法的工作原理是在数据集的每个元素上应用一个函数,因此让我们定义一个标记输入的函数: + +```py +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) +``` + +此函数的输入是一个字典(与数据集的项类似),并返回一个包含**输入词id(input_ids)** , **注意力遮罩(attention_mask)** 和 **类型标记ID(token_type_ids)** 键的新字典。请注意,如果像上面的**示例**一样,如果键所对应的值包含多个句子(每个键作为一个句子列表),那么它依然可以工作,就像前面的例子一样标记器可以处理成对的句子列表。这样的话我们可以在调用**map()**使用该选项 **batched=True** ,这将显著加快标记与标记的速度。这个**标记器**来自[🤗 Tokenizers](https://github.com/huggingface/tokenizers)库由Rust编写而成。当我们一次给它大量的输入时,这个标记器可以非常快。 + +请注意,我们现在在标记函数中省略了**padding**参数。这是因为在标记的时候将所有样本填充到最大长度的效率不高。一个更好的做法:在构建批处理时填充样本更好,因为这样我们只需要填充到该批处理中的最大长度,而不是整个数据集的最大长度。当输入长度变化很大时,这可以节省大量时间和处理能力! + +下面是我们如何在所有数据集上同时应用标记函数。我们在调用**map**时使用了**batch =True**,这样函数就可以同时应用到数据集的多个元素上,而不是分别应用到每个元素上。这将使我们的预处理快许多 + +```py +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) +tokenized_datasets +``` + +🤗Datasets库应用这种处理的方式是向数据集添加新的字段,每个字段对应预处理函数返回的字典中的每个键: + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 408 + }) + test: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 1725 + }) +}) +``` + +在使用预处理函数**map()**时,甚至可以通过传递**num_proc**参数使用并行处理。我们在这里没有这样做,因为🤗标记器库已经使用多个线程来更快地标记我们的样本,但是如果您没有使用该库支持的快速标记器,使用**num_proc**可能会加快预处理。 + +我们的**标记函数(tokenize_function)**返回包含**输入词id(input_ids)** , **注意力遮罩(attention_mask)** 和 **类型标记ID(token_type_ids)** 键的字典,所以这三个字段被添加到数据集的标记的结果中。注意,如果预处理函数**map()**为现有键返回一个新值,那将会修改原有键的值。 + +最后一件我们需要做的事情是,当我们一起批处理元素时,将所有示例填充到最长元素的长度——我们称之为动态填充。 + +### 动态填充 + + + +{#if fw === 'pt'} +负责在批处理中将数据整理为一个batch的函数称为*collate函数*。它是你可以在构建**DataLoader**时传递的一个参数,默认是一个函数,它将把你的数据集转换为PyTorch张量,并将它们拼接起来(如果你的元素是列表、元组或字典,则会使用递归)。这在我们的这个例子中下是不可行的,因为我们的输入不是都是相同大小的。我们故意在之后每个batch上进行填充,避免有太多填充的过长的输入。这将大大加快训练速度,但请注意,如果你在TPU上训练,这可能会导致问题——TPU喜欢固定的形状,即使这需要额外的填充。 + +{:else} + +负责在批处理中将数据整理为一个batch的函数称为*collate函数*。它只会将您的样本转换为 tf.Tensor并将它们拼接起来(如果你的元素是列表、元组或字典,则会使用递归)。这在我们的这个例子中下是不可行的,因为我们的输入不是都是相同大小的。我们故意在之后每个batch上进行填充,避免有太多填充的过长的输入。这将大大加快训练速度,但请注意,如果你在TPU上训练,这可能会导致问题——TPU喜欢固定的形状,即使这需要额外的填充。 + +{/if} + +为了解决句子长度统一的问题,我们必须定义一个collate函数,该函数会将每个batch句子填充到正确的长度。幸运的是,🤗transformer库通过**DataCollatorWithPadding**为我们提供了这样一个函数。当你实例化它时,需要一个标记器(用来知道使用哪个词来填充,以及模型期望填充在左边还是右边),并将做你需要的一切: + +{#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} + +为了测试这个新玩具,让我们从我们的训练集中抽取几个样本。这里,我们删除列**idx**, **sentence1**和**sentence2**,因为不需要它们,并查看一个batch中每个条目的长度: + +```py +samples = tokenized_datasets["train"][:8] +samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]} +[len(x) for x in samples["input_ids"]] +``` + +```python out +[50, 59, 47, 67, 59, 50, 62, 32] +``` + +毫无疑问,我们得到了不同长度的样本,从32到67。动态填充意味着该批中的所有样本都应该填充到长度为67,这是该批中的最大长度。如果没有动态填充,所有的样本都必须填充到整个数据集中的最大长度,或者模型可以接受的最大长度。让我们再次检查**data_collator**是否正确地动态填充了这批样本: + +```py: + +```py +batch = data_collator(samples) +{k: v.shape for k, v in batch.items()} +``` + +{#if fw === 'tf'} + +```python out +{'attention_mask': TensorShape([8, 67]), + 'input_ids': TensorShape([8, 67]), + 'token_type_ids': TensorShape([8, 67]), + 'labels': TensorShape([8])} +``` + +{:else} + +```python out +{'attention_mask': torch.Size([8, 67]), + 'input_ids': torch.Size([8, 67]), + 'token_type_ids': torch.Size([8, 67]), + 'labels': torch.Size([8])} +``` + +看起来不错!现在,我们已经将原始文本转化为了模型可以处理的数据,我们已准备好对其进行微调! + +{/if} + + + +✏️ ** 试试看!** 在GLUE SST-2数据集上应用预处理。它有点不同,因为它是由单个句子而不是成对的句子组成的,但是我们所做的其他事情看起来应该是一样的。另一个更难的挑战,请尝试编写一个可用于任何GLUE任务的预处理函数。 + + + +{#if fw === 'tf'} + +现在我们有了dataset和data collator,我们需要将dataset批量地应用data collator。 我们可以手动加载批次并整理它们,但这需要大量工作,而且可能性能也不是很好。 相反,有一个简单的方法可以为这个问题提供高效的解决方案:`to_tf_dataset()`。 这将在您的数据集上调用一个 `tf.data.Dataset`的方法,这个方法带有一个可选的data collator功能。 `tf.data.Dataset` 是 Keras 可用于 `model.fit()` 的原生 TensorFlow 格式,因此这种方法会立即将🤗 Dataset 转换为可用于训练的格式。 让我们看看它在我们的数据集上是如何使用的! + +```py +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, +) +``` + +就是这样! 我们可以将这些数据集带入下一节,在经过所有艰苦的数据预处理工作之后,训练将变得非常简单。 + +{/if} diff --git a/chapters/zh-CN/chapter3/3.mdx b/chapters/zh-CN/chapter3/3.mdx index de8018344..92403ecd2 100644 --- a/chapters/zh-CN/chapter3/3.mdx +++ b/chapters/zh-CN/chapter3/3.mdx @@ -1,172 +1,172 @@ - - -# 使用 Trainer API 微调模型 - - - - - -🤗 Transformers提供了一个 **Trainer** 类来帮助您在自己的数据集上微调任何预训练模型。完成上一节中的所有数据预处理工作后,您只需要执行几个步骤来创建 **Trainer** .最难的部分可能是为 **Trainer.train()**配置运行环境,因为它在 CPU 上运行速度会非常慢。如果您没有设置 GPU,您可以访问免费的 GPU 或 TPU[Google Colab](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) -``` - -### Training - -在我们定义我们的 **Trainer** 之前首先要定义一个 **TrainingArguments** 类,它将包含 **Trainer**用于训练和评估的所有超参数。您唯一必须提供的参数是保存训练模型的目录,以及训练过程中的检查点。对于其余的参数,您可以保留默认值,这对于基本微调应该非常有效。 - -```py -from transformers import TrainingArguments - -training_args = TrainingArguments("test-trainer") -``` - - - -💡 如果您想在训练期间自动将模型上传到 Hub,请将push_to_hub=True添加到TrainingArguments之中. 我们将在[第四章](/course/chapter4/3)中详细介绍这部分。 - - - -第二步是定义我们的模型。正如在[之前的章节](/2_Using Transformers/Introduction)一样,我们将使用 **AutoModelForSequenceClassification** 类,它有两个参数: - -```py -from transformers import AutoModelForSequenceClassification - -model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) -``` - -你会注意到,和[第二章](/course/chapter2)不一样的是,在实例化此预训练模型后会收到警告。这是因为 BERT 没有在句子对分类方面进行过预训练,所以预训练模型的头部已经被丢弃,而是添加了一个适合句子序列分类的新头部。警告表明一些权重没有使用(对应于丢弃的预训练头的那些),而其他一些权重被随机初始化(新头的那些)。最后鼓励您训练模型,这正是我们现在要做的。 - -一旦我们有了我们的模型,我们就可以定义一个 **Trainer** 通过将之前构造的所有对象传递给它——我们的**model** 、**training_args** ,训练和验证数据集,**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**后,默认 **Trainer**使用 的**data_collator**会使用之前预定义的 **DataCollatorWithPadding** ,因此您可以在这个例子中跳过 **data_collator=data_collator**。在第 2 节中向您展示这部分处理仍然很重要! - -为了让预训练模型在在我们的数据集上微调,我们只需要调用**Trainer**的**train()** 方法 : - -```py -trainer.train() -``` - -这将开始微调(在GPU上应该需要几分钟),并每500步报告一次训练损失。但是,它不会告诉您模型的性能如何(或质量如何)。这是因为: - -1. 我们没有通过将**evaluation_strategy**设置为“**steps**”(在每次更新参数的时候评估)或“**epoch**”(在每个epoch结束时评估)来告诉**Trainer**在训练期间进行评估。 -2. 我们没有为**Trainer**提供一个**compute_metrics()**函数来直接计算模型的好坏(否则评估将只输出loss,这不是一个非常直观的数字)。 - - -### 评估 - -让我们看看如何构建一个有用的 **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** 字段将只包含传递的数据集的loss,以及一些运行时间(预测所需的总时间和平均时间)。如果我们定义了自己的 **compute_metrics()** 函数并将其传递给 **Trainer** ,该字段还将包含**compute_metrics()**的结果。 - -**predict()** 方法是具有三个字段的命名元组: **predictions** , **label_ids** , 和 **metrics** .这 **metrics** 字段将只包含传递的数据集的loss,以及一些运行时间(预测所需的总时间和平均时间)。如果我们定义了自己的 **compute_metrics()** 函数并将其传递给 **Trainer** ,该字段还将包含**compute_metrics()** 的结果。如你看到的, **predictions** 是一个形状为 408 x 2 的二维数组(408 是我们使用的数据集中元素的数量)。这些是我们传递给**predict()**的数据集的每个元素的结果(logits)(正如你在[之前的章节](/course/chapter2)看到的情况)。要将我们的预测的可以与真正的标签进行比较,我们需要在第二个轴上取最大值的索引: - -```py -import numpy as np - -preds = np.argmax(predictions.predictions, axis=-1) -``` - -现在建立我们的 **compute_metric()** 函数来较为直观地评估模型的好坏,我们将使用 🤗 [Evaluate](https://github.com/huggingface/evaluate/) 库中的指标。我们可以像加载数据集一样轻松加载与 MRPC 数据集关联的指标,这次使用 **evaluate.load()** 函数。返回的对象有一个 **compute()**方法我们可以用来进行度量计算的方法: - -```py -import evaluate - -metric = evaluate.load("glue", "mrpc") -metric.compute(predictions=preds, references=predictions.label_ids) -``` - -```python out -{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542} -``` - -您获得的确切结果可能会有所不同,因为模型头的随机初始化可能会影响最终建立的模型。在这里,我们可以看到我们的模型在验证集上的准确率为 85.78%,F1 分数为 89.97。这是用于评估 GLUE 基准的 MRPC 数据集结果的两个指标。而在[BERT 论文](https://arxiv.org/pdf/1810.04805.pdf)中展示的基础模型的 F1 分数为 88.9。那是 **uncased** 模型,而我们目前正在使用 **cased** 模型,通过改进得到了更好的结果。 - -最后将所有东西打包在一起,我们得到了我们的 **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) -``` - -为了查看模型在每个训练周期结束的好坏,下面是我们如何使用**compute_metrics()**函数定义一个新的 **Trainer** : - -```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() -``` - -这一次,它将在训练loss之外,还会输出每个 epoch 结束时的验证loss和指标。同样,由于模型的随机头部初始化,您达到的准确率/F1 分数可能与我们发现的略有不同,但它应该在同一范围内。 - -这 **Trainer** 将在多个 GPU 或 TPU 上开箱即用,并提供许多选项,例如混合精度训练(在训练的参数中使用 **fp16 = True** )。我们将在第 10 章讨论它支持的所有内容。 - -使用**Trainer** API微调的介绍到此结束。对最常见的 NLP 任务执行此操作的示例将在第 7 章中给出,但现在让我们看看如何在纯 PyTorch 中执行相同的操作。 - - - -✏️ **试试看!** 使用您在第 2 节中进行的数据处理,在 GLUE SST-2 数据集上微调模型。 - - - + + +# 使用 Trainer API 微调模型 + + + + + +🤗 Transformers提供了一个 **Trainer** 类来帮助您在自己的数据集上微调任何预训练模型。完成上一节中的所有数据预处理工作后,您只需要执行几个步骤来创建 **Trainer** .最难的部分可能是为 **Trainer.train()**配置运行环境,因为它在 CPU 上运行速度会非常慢。如果您没有设置 GPU,您可以访问免费的 GPU 或 TPU[Google Colab](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) +``` + +### Training + +在我们定义我们的 **Trainer** 之前首先要定义一个 **TrainingArguments** 类,它将包含 **Trainer**用于训练和评估的所有超参数。您唯一必须提供的参数是保存训练模型的目录,以及训练过程中的检查点。对于其余的参数,您可以保留默认值,这对于基本微调应该非常有效。 + +```py +from transformers import TrainingArguments + +training_args = TrainingArguments("test-trainer") +``` + + + +💡 如果您想在训练期间自动将模型上传到 Hub,请将push_to_hub=True添加到TrainingArguments之中. 我们将在[第四章](/course/chapter4/3)中详细介绍这部分。 + + + +第二步是定义我们的模型。正如在[之前的章节](/2_Using Transformers/Introduction)一样,我们将使用 **AutoModelForSequenceClassification** 类,它有两个参数: + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +你会注意到,和[第二章](/course/chapter2)不一样的是,在实例化此预训练模型后会收到警告。这是因为 BERT 没有在句子对分类方面进行过预训练,所以预训练模型的头部已经被丢弃,而是添加了一个适合句子序列分类的新头部。警告表明一些权重没有使用(对应于丢弃的预训练头的那些),而其他一些权重被随机初始化(新头的那些)。最后鼓励您训练模型,这正是我们现在要做的。 + +一旦我们有了我们的模型,我们就可以定义一个 **Trainer** 通过将之前构造的所有对象传递给它——我们的**model** 、**training_args** ,训练和验证数据集,**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**后,默认 **Trainer**使用 的**data_collator**会使用之前预定义的 **DataCollatorWithPadding** ,因此您可以在这个例子中跳过 **data_collator=data_collator**。在第 2 节中向您展示这部分处理仍然很重要! + +为了让预训练模型在在我们的数据集上微调,我们只需要调用**Trainer**的**train()** 方法 : + +```py +trainer.train() +``` + +这将开始微调(在GPU上应该需要几分钟),并每500步报告一次训练损失。但是,它不会告诉您模型的性能如何(或质量如何)。这是因为: + +1. 我们没有通过将**evaluation_strategy**设置为“**steps**”(在每次更新参数的时候评估)或“**epoch**”(在每个epoch结束时评估)来告诉**Trainer**在训练期间进行评估。 +2. 我们没有为**Trainer**提供一个**compute_metrics()**函数来直接计算模型的好坏(否则评估将只输出loss,这不是一个非常直观的数字)。 + + +### 评估 + +让我们看看如何构建一个有用的 **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** 字段将只包含传递的数据集的loss,以及一些运行时间(预测所需的总时间和平均时间)。如果我们定义了自己的 **compute_metrics()** 函数并将其传递给 **Trainer** ,该字段还将包含**compute_metrics()**的结果。 + +**predict()** 方法是具有三个字段的命名元组: **predictions** , **label_ids** , 和 **metrics** .这 **metrics** 字段将只包含传递的数据集的loss,以及一些运行时间(预测所需的总时间和平均时间)。如果我们定义了自己的 **compute_metrics()** 函数并将其传递给 **Trainer** ,该字段还将包含**compute_metrics()** 的结果。如你看到的, **predictions** 是一个形状为 408 x 2 的二维数组(408 是我们使用的数据集中元素的数量)。这些是我们传递给**predict()**的数据集的每个元素的结果(logits)(正如你在[之前的章节](/course/chapter2)看到的情况)。要将我们的预测的可以与真正的标签进行比较,我们需要在第二个轴上取最大值的索引: + +```py +import numpy as np + +preds = np.argmax(predictions.predictions, axis=-1) +``` + +现在建立我们的 **compute_metric()** 函数来较为直观地评估模型的好坏,我们将使用 🤗 [Evaluate](https://github.com/huggingface/evaluate/) 库中的指标。我们可以像加载数据集一样轻松加载与 MRPC 数据集关联的指标,这次使用 **evaluate.load()** 函数。返回的对象有一个 **compute()**方法我们可以用来进行度量计算的方法: + +```py +import evaluate + +metric = evaluate.load("glue", "mrpc") +metric.compute(predictions=preds, references=predictions.label_ids) +``` + +```python out +{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542} +``` + +您获得的确切结果可能会有所不同,因为模型头的随机初始化可能会影响最终建立的模型。在这里,我们可以看到我们的模型在验证集上的准确率为 85.78%,F1 分数为 89.97。这是用于评估 GLUE 基准的 MRPC 数据集结果的两个指标。而在[BERT 论文](https://arxiv.org/pdf/1810.04805.pdf)中展示的基础模型的 F1 分数为 88.9。那是 **uncased** 模型,而我们目前正在使用 **cased** 模型,通过改进得到了更好的结果。 + +最后将所有东西打包在一起,我们得到了我们的 **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) +``` + +为了查看模型在每个训练周期结束的好坏,下面是我们如何使用**compute_metrics()**函数定义一个新的 **Trainer** : + +```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() +``` + +这一次,它将在训练loss之外,还会输出每个 epoch 结束时的验证loss和指标。同样,由于模型的随机头部初始化,您达到的准确率/F1 分数可能与我们发现的略有不同,但它应该在同一范围内。 + +这 **Trainer** 将在多个 GPU 或 TPU 上开箱即用,并提供许多选项,例如混合精度训练(在训练的参数中使用 **fp16 = True** )。我们将在第 10 章讨论它支持的所有内容。 + +使用**Trainer** API微调的介绍到此结束。对最常见的 NLP 任务执行此操作的示例将在第 7 章中给出,但现在让我们看看如何在纯 PyTorch 中执行相同的操作。 + + + +✏️ **试试看!** 使用您在第 2 节中进行的数据处理,在 GLUE SST-2 数据集上微调模型。 + + + diff --git a/chapters/zh-CN/chapter3/3_tf.mdx b/chapters/zh-CN/chapter3/3_tf.mdx index be3953a8c..9a30ffe4f 100644 --- a/chapters/zh-CN/chapter3/3_tf.mdx +++ b/chapters/zh-CN/chapter3/3_tf.mdx @@ -2,9 +2,9 @@ # 使用 Keras 微调一个模型 - diff --git a/chapters/zh-CN/chapter3/4.mdx b/chapters/zh-CN/chapter3/4.mdx index aab5f40a6..bc44acf7d 100644 --- a/chapters/zh-CN/chapter3/4.mdx +++ b/chapters/zh-CN/chapter3/4.mdx @@ -1,358 +1,358 @@ -# 一个完整的训练 - - - - - -现在,我们将了解如何在不使用`Trainer`类的情况下获得与上一节相同的结果。同样,我们假设您已经学习了第 2 节中的数据处理。下面是一个简短的总结,涵盖了您需要的所有内容: - -```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) -``` - -### 训练前的准备 - -在实际编写我们的训练循环之前,我们需要定义一些对象。第一个是我们将用于迭代批次的数据加载器。我们需要对我们的`tokenized_datasets`做一些处理,来处理`Trainer`自动为我们做的一些事情。具体来说,我们需要: - -- 删除与模型不期望的值相对应的列(如`sentence1`和`sentence2`列)。 -- 将列名`label`重命名为`labels`(因为模型期望参数是`labels`)。 -- 设置数据集的格式,使其返回 PyTorch 张量而不是列表。 - -针对上面的每个步骤,我们的 `tokenized_datasets` 都有一个方法: - -```py -tokenized_datasets = tokenized_datasets.remove_columns(["sentence1", "sentence2", "idx"]) -tokenized_datasets = tokenized_datasets.rename_column("label", "labels") -tokenized_datasets.set_format("torch") -tokenized_datasets["train"].column_names -``` - -然后,我们可以检查结果中是否只有模型能够接受的列: - -```python -["attention_mask", "input_ids", "labels", "token_type_ids"] -``` - -至此,我们可以轻松定义数据加载器: - -```py -from torch.utils.data import DataLoader - -train_dataloader = DataLoader( - tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator -) -eval_dataloader = DataLoader( - tokenized_datasets["validation"], batch_size=8, collate_fn=data_collator -) -``` - -为了快速检验数据处理中没有错误,我们可以这样检验其中的一个批次: - -```py -for batch in train_dataloader: - break -{k: v.shape for k, v in batch.items()} -``` - -```python out -{'attention_mask': torch.Size([8, 65]), - 'input_ids': torch.Size([8, 65]), - 'labels': torch.Size([8]), - 'token_type_ids': torch.Size([8, 65])} -``` - -请注意,实际的形状可能与您略有不同,因为我们为训练数据加载器设置了`shuffle=True`,并且模型会将句子填充到`batch`中的最大长度。 - -现在我们已经完全完成了数据预处理(对于任何 ML 从业者来说都是一个令人满意但难以实现的目标),让我们将注意力转向模型。我们完全像在上一节中所做的那样实例化它: - -```py -from transformers import AutoModelForSequenceClassification - -model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) -``` -为了确保训练过程中一切顺利,我们将`batch`传递给这个模型: - -```py -outputs = model(**batch) -print(outputs.loss, outputs.logits.shape) -``` - -```python out -tensor(0.5441, grad_fn=) torch.Size([8, 2]) -``` - -当我们提供 `labels` 时, 🤗 Transformers 模型都将返回这个`batch`的`loss`,我们还得到了 `logits`(`batch`中的每个输入有两个,所以张量大小为 8 x 2)。 - -我们几乎准备好编写我们的训练循环了!我们只是缺少两件事:优化器和学习率调度器。由于我们试图自行实现 `Trainer`的功能,我们将使用相同的优化器和学习率调度器。`Trainer` 使用的优化器是 `AdamW` , 与 `Adam` 相同,但在权重衰减正则化方面有所不同(参见[“Decoupled Weight Decay Regularization”](https://arxiv.org/abs/1711.05101)作者:Ilya Loshchilov 和 Frank Hutter): - -```py -from transformers import AdamW - -optimizer = AdamW(model.parameters(), lr=5e-5) -``` - -最后,默认使用的学习率调度器只是从最大值 (5e-5) 到 0 的线性衰减。 为了定义它,我们需要知道我们训练的次数,即所有数据训练的次数(epochs)乘以的数据量(这是我们所有训练数据的数量)。`Trainer`默认情况下使用三个`epochs`,因此我们定义训练过程如下: - -```py -from transformers import get_scheduler - -num_epochs = 3 -num_training_steps = num_epochs * len(train_dataloader) -lr_scheduler = get_scheduler( - "linear", - optimizer=optimizer, - num_warmup_steps=0, - num_training_steps=num_training_steps, -) -print(num_training_steps) -``` - -```python out -1377 -``` - -### 训练循环 - -最后一件事:如果我们可以访问 GPU,我们将希望使用 GPU(在 CPU 上,训练可能需要几个小时而不是几分钟)。为此,我们定义了一个 `device`,它在GPU可用的情况下指向GPU 我们将把我们的模型和`batche`放在`device`上: - -```py -import torch - -device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") -model.to(device) -device -``` - -```python out -device(type='cuda') -``` - -我们现在准备好训练了!为了了解训练何时结束,我们使用 `tqdm` 库,在训练步骤数上添加了一个进度条: - -```py -from tqdm.auto import tqdm - -progress_bar = tqdm(range(num_training_steps)) - -model.train() -for epoch in range(num_epochs): - for batch in train_dataloader: - batch = {k: v.to(device) for k, v in batch.items()} - outputs = model(**batch) - loss = outputs.loss - loss.backward() - - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - progress_bar.update(1) -``` - -您可以看到训练循环的核心与介绍中的非常相似。我们没有要求任何检验,所以这个训练循环不会告诉我们任何关于模型目前的状态。我们需要为此添加一个评估循环。 - - -### 评估循环 - -正如我们之前所做的那样,我们将使用 🤗 Evaluate 库提供的指标。我们已经了解了 `metric.compute()` 方法,当我们使用 `add_batch()`方法进行预测循环时,实际上该指标可以为我们累积所有 `batch` 的结果。一旦我们累积了所有 `batch` ,我们就可以使用 `metric.compute()` 得到最终结果 .以下是在评估循环中实现所有这些的方法: - -```py -import evaluate - -metric = evaluate.load("glue", "mrpc") -model.eval() -for batch in eval_dataloader: - batch = {k: v.to(device) for k, v in batch.items()} - with torch.no_grad(): - outputs = model(**batch) - - logits = outputs.logits - predictions = torch.argmax(logits, dim=-1) - metric.add_batch(predictions=predictions, references=batch["labels"]) - -metric.compute() -``` - -```python out -{'accuracy': 0.8431372549019608, 'f1': 0.8907849829351535} -``` - -同样,由于模型头部初始化和数据改组的随机性,您的结果会略有不同,但它们应该在同一个范围内。 - - - -✏️ **试试看!** 修改之前的训练循环以在 SST-2 数据集上微调您的模型。 - - - -### S使用🤗 Accelerate加速您的训练循环 - - - -我们之前定义的训练循环在单个 CPU 或 GPU 上运行良好。但是使用[🤗 Accelerate](https://github.com/huggingface/accelerate)库,只需进行一些调整,我们就可以在多个 GPU 或 TPU 上启用分布式训练。从创建训练和验证数据加载器开始,我们的手动训练循环如下所示: - -```py -from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler - -model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) -optimizer = AdamW(model.parameters(), lr=3e-5) - -device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") -model.to(device) - -num_epochs = 3 -num_training_steps = num_epochs * len(train_dataloader) -lr_scheduler = get_scheduler( - "linear", - optimizer=optimizer, - num_warmup_steps=0, - num_training_steps=num_training_steps, -) - -progress_bar = tqdm(range(num_training_steps)) - -model.train() -for epoch in range(num_epochs): - for batch in train_dataloader: - batch = {k: v.to(device) for k, v in batch.items()} - outputs = model(**batch) - loss = outputs.loss - loss.backward() - - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - progress_bar.update(1) -``` - -以下是变化: - -```diff -+ from accelerate import Accelerator - from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler - -+ accelerator = Accelerator() - - model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) - optimizer = AdamW(model.parameters(), lr=3e-5) - -- device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") -- model.to(device) - -+ train_dataloader, eval_dataloader, model, optimizer = accelerator.prepare( -+ train_dataloader, eval_dataloader, model, optimizer -+ ) - - num_epochs = 3 - num_training_steps = num_epochs * len(train_dataloader) - lr_scheduler = get_scheduler( - "linear", - optimizer=optimizer, - num_warmup_steps=0, - num_training_steps=num_training_steps - ) - - progress_bar = tqdm(range(num_training_steps)) - - model.train() - for epoch in range(num_epochs): - for batch in train_dataloader: -- batch = {k: v.to(device) for k, v in batch.items()} - outputs = model(**batch) - loss = outputs.loss -- loss.backward() -+ accelerator.backward(loss) - - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - progress_bar.update(1) -``` - -要添加的第一行是导入`Accelerator`。第二行实例化一个 `Accelerator`对象 ,它将查看环境并初始化适当的分布式设置。 🤗 Accelerate 为您处理数据在设备间的传递,因此您可以删除将模型放在设备上的那行代码(或者,如果您愿意,可使用 `accelerator.device` 代替 `device` )。 - -然后大部分工作会在将数据加载器、模型和优化器发送到的`accelerator.prepare()`中完成。这将会把这些对象包装在适当的容器中,以确保您的分布式训练按预期工作。要进行的其余更改是删除将`batch`放在 `device` 的那行代码(同样,如果您想保留它,您可以将其更改为使用 `accelerator.device` ) 并将 `loss.backward()` 替换为`accelerator.backward(loss)`。 - - -⚠️ 为了使云端 TPU 提供的加速发挥最大的效益,我们建议使用标记器(tokenizer)的 `padding=max_length` 和 `max_length` 参数将您的样本填充到固定长度。 - - -如果您想复制并粘贴来直接运行,以下是 🤗 Accelerate 的完整训练循环: - -```py -from accelerate import Accelerator -from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler - -accelerator = Accelerator() - -model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) -optimizer = AdamW(model.parameters(), lr=3e-5) - -train_dl, eval_dl, model, optimizer = accelerator.prepare( - train_dataloader, eval_dataloader, model, optimizer -) - -num_epochs = 3 -num_training_steps = num_epochs * len(train_dl) -lr_scheduler = get_scheduler( - "linear", - optimizer=optimizer, - num_warmup_steps=0, - num_training_steps=num_training_steps, -) - -progress_bar = tqdm(range(num_training_steps)) - -model.train() -for epoch in range(num_epochs): - for batch in train_dl: - outputs = model(**batch) - loss = outputs.loss - accelerator.backward(loss) - - optimizer.step() - lr_scheduler.step() - optimizer.zero_grad() - progress_bar.update(1) -``` - -把这个放在 `train.py` 文件中,可以让它在任何类型的分布式设置上运行。要在分布式设置中试用它,请运行以下命令: - -```bash -accelerate config -``` - -这将询问您几个配置的问题并将您的回答转储到此命令使用的配置文件中: - -``` -accelerate launch train.py -``` - -这将启动分布式训练 - -这将启动分布式训练。如果您想在 Notebook 中尝试此操作(例如,在 Colab 上使用 TPU 进行测试),只需将代码粘贴到 `training_function()` 并使用以下命令运行最后一个单元格: - -```python -from accelerate import notebook_launcher - -notebook_launcher(training_function) -``` - -您可以在[🤗 Accelerate repo](https://github.com/huggingface/accelerate/tree/main/examples)找到更多的示例。 +# 一个完整的训练 + + + + + +现在,我们将了解如何在不使用`Trainer`类的情况下获得与上一节相同的结果。同样,我们假设您已经学习了第 2 节中的数据处理。下面是一个简短的总结,涵盖了您需要的所有内容: + +```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) +``` + +### 训练前的准备 + +在实际编写我们的训练循环之前,我们需要定义一些对象。第一个是我们将用于迭代批次的数据加载器。我们需要对我们的`tokenized_datasets`做一些处理,来处理`Trainer`自动为我们做的一些事情。具体来说,我们需要: + +- 删除与模型不期望的值相对应的列(如`sentence1`和`sentence2`列)。 +- 将列名`label`重命名为`labels`(因为模型期望参数是`labels`)。 +- 设置数据集的格式,使其返回 PyTorch 张量而不是列表。 + +针对上面的每个步骤,我们的 `tokenized_datasets` 都有一个方法: + +```py +tokenized_datasets = tokenized_datasets.remove_columns(["sentence1", "sentence2", "idx"]) +tokenized_datasets = tokenized_datasets.rename_column("label", "labels") +tokenized_datasets.set_format("torch") +tokenized_datasets["train"].column_names +``` + +然后,我们可以检查结果中是否只有模型能够接受的列: + +```python +["attention_mask", "input_ids", "labels", "token_type_ids"] +``` + +至此,我们可以轻松定义数据加载器: + +```py +from torch.utils.data import DataLoader + +train_dataloader = DataLoader( + tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator +) +eval_dataloader = DataLoader( + tokenized_datasets["validation"], batch_size=8, collate_fn=data_collator +) +``` + +为了快速检验数据处理中没有错误,我们可以这样检验其中的一个批次: + +```py +for batch in train_dataloader: + break +{k: v.shape for k, v in batch.items()} +``` + +```python out +{'attention_mask': torch.Size([8, 65]), + 'input_ids': torch.Size([8, 65]), + 'labels': torch.Size([8]), + 'token_type_ids': torch.Size([8, 65])} +``` + +请注意,实际的形状可能与您略有不同,因为我们为训练数据加载器设置了`shuffle=True`,并且模型会将句子填充到`batch`中的最大长度。 + +现在我们已经完全完成了数据预处理(对于任何 ML 从业者来说都是一个令人满意但难以实现的目标),让我们将注意力转向模型。我们完全像在上一节中所做的那样实例化它: + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` +为了确保训练过程中一切顺利,我们将`batch`传递给这个模型: + +```py +outputs = model(**batch) +print(outputs.loss, outputs.logits.shape) +``` + +```python out +tensor(0.5441, grad_fn=) torch.Size([8, 2]) +``` + +当我们提供 `labels` 时, 🤗 Transformers 模型都将返回这个`batch`的`loss`,我们还得到了 `logits`(`batch`中的每个输入有两个,所以张量大小为 8 x 2)。 + +我们几乎准备好编写我们的训练循环了!我们只是缺少两件事:优化器和学习率调度器。由于我们试图自行实现 `Trainer`的功能,我们将使用相同的优化器和学习率调度器。`Trainer` 使用的优化器是 `AdamW` , 与 `Adam` 相同,但在权重衰减正则化方面有所不同(参见[“Decoupled Weight Decay Regularization”](https://arxiv.org/abs/1711.05101)作者:Ilya Loshchilov 和 Frank Hutter): + +```py +from transformers import AdamW + +optimizer = AdamW(model.parameters(), lr=5e-5) +``` + +最后,默认使用的学习率调度器只是从最大值 (5e-5) 到 0 的线性衰减。 为了定义它,我们需要知道我们训练的次数,即所有数据训练的次数(epochs)乘以的数据量(这是我们所有训练数据的数量)。`Trainer`默认情况下使用三个`epochs`,因此我们定义训练过程如下: + +```py +from transformers import get_scheduler + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) +print(num_training_steps) +``` + +```python out +1377 +``` + +### 训练循环 + +最后一件事:如果我们可以访问 GPU,我们将希望使用 GPU(在 CPU 上,训练可能需要几个小时而不是几分钟)。为此,我们定义了一个 `device`,它在GPU可用的情况下指向GPU 我们将把我们的模型和`batche`放在`device`上: + +```py +import torch + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) +device +``` + +```python out +device(type='cuda') +``` + +我们现在准备好训练了!为了了解训练何时结束,我们使用 `tqdm` 库,在训练步骤数上添加了一个进度条: + +```py +from tqdm.auto import tqdm + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +您可以看到训练循环的核心与介绍中的非常相似。我们没有要求任何检验,所以这个训练循环不会告诉我们任何关于模型目前的状态。我们需要为此添加一个评估循环。 + + +### 评估循环 + +正如我们之前所做的那样,我们将使用 🤗 Evaluate 库提供的指标。我们已经了解了 `metric.compute()` 方法,当我们使用 `add_batch()`方法进行预测循环时,实际上该指标可以为我们累积所有 `batch` 的结果。一旦我们累积了所有 `batch` ,我们就可以使用 `metric.compute()` 得到最终结果 .以下是在评估循环中实现所有这些的方法: + +```py +import evaluate + +metric = evaluate.load("glue", "mrpc") +model.eval() +for batch in eval_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + with torch.no_grad(): + outputs = model(**batch) + + logits = outputs.logits + predictions = torch.argmax(logits, dim=-1) + metric.add_batch(predictions=predictions, references=batch["labels"]) + +metric.compute() +``` + +```python out +{'accuracy': 0.8431372549019608, 'f1': 0.8907849829351535} +``` + +同样,由于模型头部初始化和数据改组的随机性,您的结果会略有不同,但它们应该在同一个范围内。 + + + +✏️ **试试看!** 修改之前的训练循环以在 SST-2 数据集上微调您的模型。 + + + +### S使用🤗 Accelerate加速您的训练循环 + + + +我们之前定义的训练循环在单个 CPU 或 GPU 上运行良好。但是使用[🤗 Accelerate](https://github.com/huggingface/accelerate)库,只需进行一些调整,我们就可以在多个 GPU 或 TPU 上启用分布式训练。从创建训练和验证数据加载器开始,我们的手动训练循环如下所示: + +```py +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +以下是变化: + +```diff ++ from accelerate import Accelerator + from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + ++ accelerator = Accelerator() + + model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) + optimizer = AdamW(model.parameters(), lr=3e-5) + +- device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +- model.to(device) + ++ train_dataloader, eval_dataloader, model, optimizer = accelerator.prepare( ++ train_dataloader, eval_dataloader, model, optimizer ++ ) + + num_epochs = 3 + num_training_steps = num_epochs * len(train_dataloader) + lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps + ) + + progress_bar = tqdm(range(num_training_steps)) + + model.train() + for epoch in range(num_epochs): + for batch in train_dataloader: +- batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss +- loss.backward() ++ accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +要添加的第一行是导入`Accelerator`。第二行实例化一个 `Accelerator`对象 ,它将查看环境并初始化适当的分布式设置。 🤗 Accelerate 为您处理数据在设备间的传递,因此您可以删除将模型放在设备上的那行代码(或者,如果您愿意,可使用 `accelerator.device` 代替 `device` )。 + +然后大部分工作会在将数据加载器、模型和优化器发送到的`accelerator.prepare()`中完成。这将会把这些对象包装在适当的容器中,以确保您的分布式训练按预期工作。要进行的其余更改是删除将`batch`放在 `device` 的那行代码(同样,如果您想保留它,您可以将其更改为使用 `accelerator.device` ) 并将 `loss.backward()` 替换为`accelerator.backward(loss)`。 + + +⚠️ 为了使云端 TPU 提供的加速发挥最大的效益,我们建议使用标记器(tokenizer)的 `padding=max_length` 和 `max_length` 参数将您的样本填充到固定长度。 + + +如果您想复制并粘贴来直接运行,以下是 🤗 Accelerate 的完整训练循环: + +```py +from accelerate import Accelerator +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +accelerator = Accelerator() + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +train_dl, eval_dl, model, optimizer = accelerator.prepare( + train_dataloader, eval_dataloader, model, optimizer +) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dl) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dl: + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +把这个放在 `train.py` 文件中,可以让它在任何类型的分布式设置上运行。要在分布式设置中试用它,请运行以下命令: + +```bash +accelerate config +``` + +这将询问您几个配置的问题并将您的回答转储到此命令使用的配置文件中: + +``` +accelerate launch train.py +``` + +这将启动分布式训练 + +这将启动分布式训练。如果您想在 Notebook 中尝试此操作(例如,在 Colab 上使用 TPU 进行测试),只需将代码粘贴到 `training_function()` 并使用以下命令运行最后一个单元格: + +```python +from accelerate import notebook_launcher + +notebook_launcher(training_function) +``` + +您可以在[🤗 Accelerate repo](https://github.com/huggingface/accelerate/tree/main/examples)找到更多的示例。 diff --git a/chapters/zh-CN/chapter3/5.mdx b/chapters/zh-CN/chapter3/5.mdx index 760741ec9..51edbadeb 100644 --- a/chapters/zh-CN/chapter3/5.mdx +++ b/chapters/zh-CN/chapter3/5.mdx @@ -1,20 +1,25 @@ - - -# 微调,检查! - -这是非常令人高兴的! 在前两章中,您了解了模型和标记器(tokenizer),现在您知道如何针对您自己的数据对它们进行微调。回顾一下,在本章中,您: - -{#if fw === 'pt'} -* 了解了[Hub](https://huggingface.co/datasets)中的数据集 -* 学习了如何加载和预处理数据集,包括使用动态填充和整理器 -* 实现您自己的模型微调和评估 -* 实施了一个较为底层的训练循环 -* 使用 🤗 Accelerate 轻松调整您的训练循环,使其适用于多个 GPU 或 TPU - -{:else} -* 了解了[Hub](https://huggingface.co/datasets)中的数据集 -* 学习了如何加载和预处理数据集 -* 学习了如何使用 Keras 微调和评估模型 -* 实现了自定义指标 - -{/if} + + +# 微调,检查! + + + +这是非常令人高兴的! 在前两章中,您了解了模型和标记器(tokenizer),现在您知道如何针对您自己的数据对它们进行微调。回顾一下,在本章中,您: + +{#if fw === 'pt'} +* 了解了[Hub](https://huggingface.co/datasets)中的数据集 +* 学习了如何加载和预处理数据集,包括使用动态填充和整理器 +* 实现您自己的模型微调和评估 +* 实施了一个较为底层的训练循环 +* 使用 🤗 Accelerate 轻松调整您的训练循环,使其适用于多个 GPU 或 TPU + +{:else} +* 了解了[Hub](https://huggingface.co/datasets)中的数据集 +* 学习了如何加载和预处理数据集 +* 学习了如何使用 Keras 微调和评估模型 +* 实现了自定义指标 + +{/if} diff --git a/chapters/zh-CN/chapter3/6.mdx b/chapters/zh-CN/chapter3/6.mdx index 750bfd3cd..96118ac20 100644 --- a/chapters/zh-CN/chapter3/6.mdx +++ b/chapters/zh-CN/chapter3/6.mdx @@ -1,284 +1,289 @@ - - - - -# End-of-chapter quiz - -Test what you learned in this chapter! - -### 1.“情绪”数据集包含标记有情绪的 Twitter 消息。在[ Hub ]( https://huggingface.co/datasets 集线器)中搜索它,然后读取数据集卡。哪一个不是它的基本情感? - - -### 2.在[ Hub ]( https://huggingface.co/datasets 集线器)中搜索‘ ar _ sarcasm’数据集,它支持哪个任务? - dataset card !" - }, - { - text: "命名实体识别", - explain: "不是这样的ーー再看看 < a href =’https://huggingface.co/datasets/ar _ sarcasm’> dataset card !" - }, - { - text: "回答问题", - explain: "Alas, this question was not answered correctly. 再试一次!" - } - ]} -/> - -### 3.BERT 模型期望如何处理一对句子? - [ CLS ] 特殊令牌在开始时是必需的,但是这不是唯一的事情!" - }, - { - text: "表示句子1[ SEP ]的符号表示句子2[ SEP ]", - explain: "没错!", - correct: true - }, - { - text: "表示句子1[ SEP ]的符号表示句子2", - explain: "开头需要一个 < code > [ CLS ] 特殊标记,还需要一个 < code > [ SEP ] 特殊标记来分隔两个句子,但这还不是全部!" - } - ]} -/> - -{#if fw === 'pt'} -### 4.‘ Dataset.map ()’方法的好处是什么? - - -### 5.什么是动态填充? - - -### 6.校对函数的用途是什么? - > 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.", - correct: true - }, - { - text: "它预处理整个数据集。", - explain: "这将是一个预处理函数,而不是校对函数。" - }, - { - text: "它截断数据集中的序列。", - explain: "校对函数用于处理单个批处理,而不是整个数据集。如果您对截断感兴趣,可以使用 < code > tokenizer 的 < truncate 参数。" - } - ]} -/> - -### 7.当你用一个预先训练过的语言模型(例如‘ bert-base-uncased’)实例化一个‘ AutoModelForXxx’类,这个类对应于一个不同于它所被训练的任务时会发生什么? - Trainer 所做的,而不是 Accelerate 库。再试一次!", - correct: true - }, - { - text: "丢弃预先训练好的模型头部。", - explain: "Something else needs to happen. 再试一次!" - }, - { - text: "没有,因为模型仍然可以针对不同的任务进行微调。", - explain: "这个经过训练的模特的头没有经过训练来解决这个问题,所以我们应该丢掉这个头!" - } - ]} -/> - -### 8.训练争论的目的是什么? - TrainingArguments 。" - }, - { - text: "它只包含用于评估的超参数。", - explain: "In the example, we specified where the model and its checkpoints will be saved. 再试一次!" - }, - { - text: "您可以轻松地计算与数据集相关的指标。", - explain: "In the example, we used an evaluation_strategy as well, so this impacts evaluation. 再试一次!" - } - ]} -/> - -### 9.为什么要使用 Accelerate 库? -Trainer, not the 🤗 Accelerate library. 再试一次!" - }, - { - text: "它使我们的训练循环工作在分布式策略上", - explain: "正确! 随着加速,你的训练循环将为多个 gpu 和 TPUs 工作。", - correct: true - }, - { - text: "它提供了更多的优化功能。", - explain: "不,Accelerate 库不提供任何优化功能。" - } - ]} -/> - -{:else} -### 4.当你用一个预先训练过的语言模型(例如‘ bert-base-uncased’)实例化一个‘ tfautoodelforxxx’类时,会发生什么? - - -### 5.来自“变压器”的 TensorFlow 模型已经是 Keras 模型,这有什么好处? - TPUStrategy scope 中的所有内容,包括模型的初始化。" - }, - { - text: "您可以利用现有的方法,如 < code > compile () 、 < code > fit () < c/ode > 和 < code > predict () 。", - explain: "正确! 一旦你有了这些数据,在这些数据上进行培训只需要很少的工作。", - correct: true - }, - { - text: "你可以学习 Keras 和变形金刚。", - explain: "没错,但我们要找的是别的东西:)", - correct: true - }, - { - text: "困惑", - explain: "Keras 帮助我们训练和评估模型,而不是计算与数据集相关的度量。" - } - ]} -/> - -### 6.如何定义自己的定制度量? - tfkeras.metrics. Metric 。", - explain: "太好了!", - correct: true - }, - { - text: "使用 Keras 函数 API。", - explain: "再试一次!" - }, - { - text: "通过使用带签名的可调用 < code > metric _ fn (y _ true,y _ pred) 。", - explain: "正确!", - correct: true - }, - { - text: "通过谷歌搜索。", - explain: "这不是我们要找的答案,但它应该能帮助你找到答案。", - correct: true - } - ]} -/> - + + + + +# End-of-chapter quiz + + + +Test what you learned in this chapter! + +### 1.“情绪”数据集包含标记有情绪的 Twitter 消息。在[ Hub ]( https://huggingface.co/datasets 集线器)中搜索它,然后读取数据集卡。哪一个不是它的基本情感? + + +### 2.在[ Hub ]( https://huggingface.co/datasets 集线器)中搜索‘ ar _ sarcasm’数据集,它支持哪个任务? + dataset card !" + }, + { + text: "命名实体识别", + explain: "不是这样的ーー再看看 < a href =’https://huggingface.co/datasets/ar _ sarcasm’> dataset card !" + }, + { + text: "回答问题", + explain: "Alas, this question was not answered correctly. 再试一次!" + } + ]} +/> + +### 3.BERT 模型期望如何处理一对句子? + [ CLS ] 特殊令牌在开始时是必需的,但是这不是唯一的事情!" + }, + { + text: "表示句子1[ SEP ]的符号表示句子2[ SEP ]", + explain: "没错!", + correct: true + }, + { + text: "表示句子1[ SEP ]的符号表示句子2", + explain: "开头需要一个 < code > [ CLS ] 特殊标记,还需要一个 < code > [ SEP ] 特殊标记来分隔两个句子,但这还不是全部!" + } + ]} +/> + +{#if fw === 'pt'} +### 4.‘ Dataset.map ()’方法的好处是什么? + + +### 5.什么是动态填充? + + +### 6.校对函数的用途是什么? + > 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.", + correct: true + }, + { + text: "它预处理整个数据集。", + explain: "这将是一个预处理函数,而不是校对函数。" + }, + { + text: "它截断数据集中的序列。", + explain: "校对函数用于处理单个批处理,而不是整个数据集。如果您对截断感兴趣,可以使用 < code > tokenizer 的 < truncate 参数。" + } + ]} +/> + +### 7.当你用一个预先训练过的语言模型(例如‘ bert-base-uncased’)实例化一个‘ AutoModelForXxx’类,这个类对应于一个不同于它所被训练的任务时会发生什么? + Trainer 所做的,而不是 Accelerate 库。再试一次!", + correct: true + }, + { + text: "丢弃预先训练好的模型头部。", + explain: "Something else needs to happen. 再试一次!" + }, + { + text: "没有,因为模型仍然可以针对不同的任务进行微调。", + explain: "这个经过训练的模特的头没有经过训练来解决这个问题,所以我们应该丢掉这个头!" + } + ]} +/> + +### 8.训练争论的目的是什么? + TrainingArguments 。" + }, + { + text: "它只包含用于评估的超参数。", + explain: "In the example, we specified where the model and its checkpoints will be saved. 再试一次!" + }, + { + text: "您可以轻松地计算与数据集相关的指标。", + explain: "In the example, we used an evaluation_strategy as well, so this impacts evaluation. 再试一次!" + } + ]} +/> + +### 9.为什么要使用 Accelerate 库? +Trainer, not the 🤗 Accelerate library. 再试一次!" + }, + { + text: "它使我们的训练循环工作在分布式策略上", + explain: "正确! 随着加速,你的训练循环将为多个 gpu 和 TPUs 工作。", + correct: true + }, + { + text: "它提供了更多的优化功能。", + explain: "不,Accelerate 库不提供任何优化功能。" + } + ]} +/> + +{:else} +### 4.当你用一个预先训练过的语言模型(例如‘ bert-base-uncased’)实例化一个‘ tfautoodelforxxx’类时,会发生什么? + + +### 5.来自“变压器”的 TensorFlow 模型已经是 Keras 模型,这有什么好处? + TPUStrategy scope 中的所有内容,包括模型的初始化。" + }, + { + text: "您可以利用现有的方法,如 < code > compile () 、 < code > fit () < c/ode > 和 < code > predict () 。", + explain: "正确! 一旦你有了这些数据,在这些数据上进行培训只需要很少的工作。", + correct: true + }, + { + text: "你可以学习 Keras 和变形金刚。", + explain: "没错,但我们要找的是别的东西:)", + correct: true + }, + { + text: "困惑", + explain: "Keras 帮助我们训练和评估模型,而不是计算与数据集相关的度量。" + } + ]} +/> + +### 6.如何定义自己的定制度量? + tfkeras.metrics. Metric 。", + explain: "太好了!", + correct: true + }, + { + text: "使用 Keras 函数 API。", + explain: "再试一次!" + }, + { + text: "通过使用带签名的可调用 < code > metric _ fn (y _ true,y _ pred) 。", + explain: "正确!", + correct: true + }, + { + text: "通过谷歌搜索。", + explain: "这不是我们要找的答案,但它应该能帮助你找到答案。", + correct: true + } + ]} +/> + {/if} \ No newline at end of file diff --git a/chapters/zh-CN/chapter4/1.mdx b/chapters/zh-CN/chapter4/1.mdx index 167f46f9d..ea639b48c 100644 --- a/chapters/zh-CN/chapter4/1.mdx +++ b/chapters/zh-CN/chapter4/1.mdx @@ -1,5 +1,10 @@ # The Hugging Face Hub + + [Hugging Face Hub](https://huggingface.co/)---我们的主网站,是一个中央平台,在这个网站上任何人都可以查找、使用和贡献新的最先进的模型和数据集。它拥有各种各样的模型,公开可用的模型超过 10,000个。我们在本章去探索Hub中的模型,并在第 5 章中探索Hub中的数据集。 Hub 中的模型不仅限于🤗 Transformers 甚至 NLP。有用于自然语言处理的[Flair](https://github.com/flairNLP/flair),[AllenNLP](https://github.com/allenai/allennlp),[Asteroid](https://github.com/asteroid-team/asteroid)和用于音频检测的[pyannote](https://github.com/pyannote/pyannote-audio),以及对于视觉的[timm](https://github.com/rwightman/pytorch-image-models),这些例子只是Hub中冰山一角,更多的模型。可以由你去探索。 diff --git a/chapters/zh-CN/chapter4/2.mdx b/chapters/zh-CN/chapter4/2.mdx index 696767537..af3efe96a 100644 --- a/chapters/zh-CN/chapter4/2.mdx +++ b/chapters/zh-CN/chapter4/2.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/zh-CN/chapter4/3.mdx b/chapters/zh-CN/chapter4/3.mdx index 4e40bcc68..bbb3c5e39 100644 --- a/chapters/zh-CN/chapter4/3.mdx +++ b/chapters/zh-CN/chapter4/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/zh-CN/chapter4/4.mdx b/chapters/zh-CN/chapter4/4.mdx index a6c698e13..694f4ace2 100644 --- a/chapters/zh-CN/chapter4/4.mdx +++ b/chapters/zh-CN/chapter4/4.mdx @@ -1,5 +1,10 @@ # 构建模型卡片 + + 模型卡片是一个配置文件,可以说与模型存储库中的模型和 tokenizer 文件一样重要。它包含了模型的核心定义,确保了社区成员可以复现模型的结果,并提供一个其他成员可以在这个模型基础上构建他们的组件的平台。 记录训练和评估过程并提供有关使用的数据以及已完成的预处理和后续处理的足够信息,有助于其他人了解对模型的能力——确保模型存在和目前的限制、偏差可以识别和理解。 diff --git a/chapters/zh-CN/chapter4/5.mdx b/chapters/zh-CN/chapter4/5.mdx index 87f7e5241..093345126 100644 --- a/chapters/zh-CN/chapter4/5.mdx +++ b/chapters/zh-CN/chapter4/5.mdx @@ -1,5 +1,10 @@ # Part 1 完结! + + 这是课程第一部分的结尾!第 2 部分将在 11 月 15 日与大型社区活动一起发布,[点击这里](https://huggingface.co/blog/course-launch-event)查看更多信息. 您现在应该能够针对文本分类问题(单个或成对句子)对预训练模型进行微调,并将结果上传到模型中心。为确保您掌握了第一部分的内容,您应该针对您感兴趣的想法进行尝试(不一定是英语)!一旦你完成,您可以在[Hugging Face 社区](https://discuss.huggingface.co/)的[这个话题](https://discuss.huggingface.co/t/share-your-projects/6803)分享您的项目。 diff --git a/chapters/zh-CN/chapter4/6.mdx b/chapters/zh-CN/chapter4/6.mdx index 6f29d6c17..90a47c3dc 100644 --- a/chapters/zh-CN/chapter4/6.mdx +++ b/chapters/zh-CN/chapter4/6.mdx @@ -4,6 +4,11 @@ # 章末小测试 + + 让我们测试一下你在本章所学的知识! ### 1.Hub上的模型有什么限制? diff --git a/chapters/zh-CN/chapter5/1.mdx b/chapters/zh-CN/chapter5/1.mdx index 20fe40dd0..6004305cc 100644 --- a/chapters/zh-CN/chapter5/1.mdx +++ b/chapters/zh-CN/chapter5/1.mdx @@ -1,5 +1,10 @@ # 本章简介 + + 在[第三章](/course/chapter3)第一次体验了🤗Datasets 库,并发现在微调模型时有三个主要步骤: 1. 从hugs Face Hub加载一个数据集。 diff --git a/chapters/zh-CN/chapter5/2.mdx b/chapters/zh-CN/chapter5/2.mdx index ce3d11dae..119e9e931 100644 --- a/chapters/zh-CN/chapter5/2.mdx +++ b/chapters/zh-CN/chapter5/2.mdx @@ -1,8 +1,8 @@ # 如果我的数据集不在 Hub 上怎么办? - diff --git a/chapters/zh-CN/chapter5/3.mdx b/chapters/zh-CN/chapter5/3.mdx index f4b7eb5d1..ebf199396 100644 --- a/chapters/zh-CN/chapter5/3.mdx +++ b/chapters/zh-CN/chapter5/3.mdx @@ -1,8 +1,8 @@ # 是时候来学一下切片了 - diff --git a/chapters/zh-CN/chapter5/4.mdx b/chapters/zh-CN/chapter5/4.mdx index d8224b3bd..ebbe0b81f 100644 --- a/chapters/zh-CN/chapter5/4.mdx +++ b/chapters/zh-CN/chapter5/4.mdx @@ -1,8 +1,8 @@ # 大数据? 🤗 Datasets 来救援! - diff --git a/chapters/zh-CN/chapter5/5.mdx b/chapters/zh-CN/chapter5/5.mdx index b97bb7542..75d8d86b8 100644 --- a/chapters/zh-CN/chapter5/5.mdx +++ b/chapters/zh-CN/chapter5/5.mdx @@ -1,8 +1,8 @@ # 创建自己的数据集 - diff --git a/chapters/zh-CN/chapter5/6.mdx b/chapters/zh-CN/chapter5/6.mdx index 4e6411a98..eab6b539c 100644 --- a/chapters/zh-CN/chapter5/6.mdx +++ b/chapters/zh-CN/chapter5/6.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/zh-CN/chapter5/7.mdx b/chapters/zh-CN/chapter5/7.mdx index a4bece254..fc9f5e4b4 100644 --- a/chapters/zh-CN/chapter5/7.mdx +++ b/chapters/zh-CN/chapter5/7.mdx @@ -1,5 +1,10 @@ # 🤗 Datasets,回顾! + + 这是对 🤗 Datasets 库的一次完整游览——祝贺你走到这一步!凭借从本章中获得的知识,您应该能够: - 从任何地方加载数据集,无论是 Hugging Face Hub、您的笔记本电脑还是您公司的远程服务器。 diff --git a/chapters/zh-CN/chapter5/8.mdx b/chapters/zh-CN/chapter5/8.mdx index 428d5bf1d..217b24feb 100644 --- a/chapters/zh-CN/chapter5/8.mdx +++ b/chapters/zh-CN/chapter5/8.mdx @@ -2,6 +2,11 @@ # 章末小测试 + + 本章涵盖了很多方面! 如果你没有掌握所有细节, 不用担心; 在下一章将帮助你了解内部的事情是如何工作的。 不过, 在继续下一章之前, 让我们测试一下你在本章学到的内容。 diff --git a/chapters/zh-CN/chapter6/1.mdx b/chapters/zh-CN/chapter6/1.mdx index a13faa5a1..6e8abfde2 100644 --- a/chapters/zh-CN/chapter6/1.mdx +++ b/chapters/zh-CN/chapter6/1.mdx @@ -1,5 +1,10 @@ # 本章简介 + + 在 [第三章] (/course/chapter3) 中,我们研究了如何在给定任务上微调模型。 当我们这样做时,我们需要使用与模型预训练相同的标记器——但是当我们想从头开始训练模型时该怎么办? 不过,使用在来自其他领域或语言的语料库上预训练的标记器通常不是最理想的。 例如,在英语语料库上训练的标记器在日语文本语料库上表现不佳,因为两种语言中空格和标点符号的使用非常不同。 在本章中,您将学习如何在文本语料库上训练一个全新的标记器,然后将其用于预训练语言模型。 这一切都将在 [🤗 Tokenizers](https://github.com/huggingface/tokenizers) 库的帮助下完成,该库在 [🤗 Transformers](https://github.com /huggingface/transformers) 库之内。 我们将仔细研究这个库提供的功能,并探讨快速标记器与“慢”版本的区别。 diff --git a/chapters/zh-CN/chapter6/10.mdx b/chapters/zh-CN/chapter6/10.mdx index 703459a3d..8da4b2dc2 100644 --- a/chapters/zh-CN/chapter6/10.mdx +++ b/chapters/zh-CN/chapter6/10.mdx @@ -2,6 +2,11 @@ # 章末小测验 + + 让我们测试一下您在本章中学到了什么! ### 1.你应该什么时候训练一个新的标记器? diff --git a/chapters/zh-CN/chapter6/2.mdx b/chapters/zh-CN/chapter6/2.mdx index ffac12aa8..b5f61ee6a 100644 --- a/chapters/zh-CN/chapter6/2.mdx +++ b/chapters/zh-CN/chapter6/2.mdx @@ -1,8 +1,8 @@ # 根据已有的tokenizer训练新的tokenizer - diff --git a/chapters/zh-CN/chapter6/3.mdx b/chapters/zh-CN/chapter6/3.mdx index f1ed19153..52d90f509 100644 --- a/chapters/zh-CN/chapter6/3.mdx +++ b/chapters/zh-CN/chapter6/3.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/zh-CN/chapter6/3b.mdx b/chapters/zh-CN/chapter6/3b.mdx index c6012e419..433824079 100644 --- a/chapters/zh-CN/chapter6/3b.mdx +++ b/chapters/zh-CN/chapter6/3b.mdx @@ -4,18 +4,18 @@ {#if fw === 'pt'} - {:else} - diff --git a/chapters/zh-CN/chapter6/4.mdx b/chapters/zh-CN/chapter6/4.mdx index 5e58d2747..5304593a0 100644 --- a/chapters/zh-CN/chapter6/4.mdx +++ b/chapters/zh-CN/chapter6/4.mdx @@ -1,8 +1,8 @@ # 标准化和预标记化 - diff --git a/chapters/zh-CN/chapter6/5.mdx b/chapters/zh-CN/chapter6/5.mdx index 272210fad..ee50b8153 100644 --- a/chapters/zh-CN/chapter6/5.mdx +++ b/chapters/zh-CN/chapter6/5.mdx @@ -1,8 +1,8 @@ # 字节对编码标记化 - diff --git a/chapters/zh-CN/chapter6/6.mdx b/chapters/zh-CN/chapter6/6.mdx index c93b41898..240305f48 100644 --- a/chapters/zh-CN/chapter6/6.mdx +++ b/chapters/zh-CN/chapter6/6.mdx @@ -1,8 +1,8 @@ # WordPiece 标记化 - diff --git a/chapters/zh-CN/chapter6/7.mdx b/chapters/zh-CN/chapter6/7.mdx index 4303d8e58..96f156801 100644 --- a/chapters/zh-CN/chapter6/7.mdx +++ b/chapters/zh-CN/chapter6/7.mdx @@ -1,8 +1,8 @@ # Unigram标记化 - diff --git a/chapters/zh-CN/chapter6/8.mdx b/chapters/zh-CN/chapter6/8.mdx index c7922a72f..88921eb2e 100644 --- a/chapters/zh-CN/chapter6/8.mdx +++ b/chapters/zh-CN/chapter6/8.mdx @@ -1,8 +1,8 @@ # 逐块地构建标记器 - diff --git a/chapters/zh-CN/chapter6/9.mdx b/chapters/zh-CN/chapter6/9.mdx index b2f5ea052..ca37dfe4e 100644 --- a/chapters/zh-CN/chapter6/9.mdx +++ b/chapters/zh-CN/chapter6/9.mdx @@ -1,5 +1,10 @@ # 标记器,回顾! + + 完成这一章,辛苦了! 在深入研究标记器之后,您应该: diff --git a/chapters/zh/_toctree.yml b/chapters/zh/_toctree.yml deleted file mode 100644 index 453fc5e99..000000000 --- a/chapters/zh/_toctree.yml +++ /dev/null @@ -1,23 +0,0 @@ -- title: 1. Transformer 模型 - sections: - - local: chapter1/1 - title: 章节简介 - - local: chapter1/2 - title: 自然语言处理 - - local: chapter1/3 - title: Transformers能做什么? - - local: chapter1/4 - title: Transformers 是如何工作的? - - local: chapter1/5 - title: 编码器模型 - - local: chapter1/6 - title: 解码器模型 - - local: chapter1/7 - title: 序列到序列模型 - - local: chapter1/8 - title: 偏见和局限性 - - local: chapter1/9 - title: 总结 - - local: chapter1/10 - title: 章末小测验 - quiz: 1 \ No newline at end of file diff --git a/chapters/zh/chapter1/1.mdx b/chapters/zh/chapter1/1.mdx deleted file mode 100644 index 68e6a14c7..000000000 --- a/chapters/zh/chapter1/1.mdx +++ /dev/null @@ -1,52 +0,0 @@ -# 简介 - -## 欢迎来到🤗课程 - - - -本课程将使用 Hugging Face 生态系统中的库——🤗 Transformers、🤗 Datasets、🤗 Tokenizers 和 🤗 Accelerate——以及 Hugging Face Hub 教你自然语言处理 (NLP)。它是完全免费的,并且没有广告。 - - -## 有什么是值得期待的? - -以下是课程的简要概述: - -
-Brief overview of the chapters of the course. - -
- -- 第 1 章到第 4 章介绍了 🤗 Transformers 库的主要概念。在本课程的这一部分结束时,您将熟悉 Transformer 模型的工作原理,并将了解如何使用 [Hugging Face Hub](https://huggingface.co/models) 中的模型,在数据集上对其进行微调,并在 Hub 上分享您的结果。 -- 第 5 章到第 8 章在深入研究经典 NLP 任务之前,教授 🤗 Datasets和 🤗 Tokenizers的基础知识。在本部分结束时,您将能够自己解决最常见的 NLP 问题。 -- 第 9 章到第 12 章更加深入,探讨了如何使用 Transformer 模型处理语音处理和计算机视觉中的任务。在此过程中,您将学习如何构建和分享模型,并针对生产环境对其进行优化。在这部分结束时,您将准备好将🤗 Transformers 应用于(几乎)任何机器学习问题! - -这个课程: - -* 需要良好的 Python 知识 -* 最好先学习深度学习入门课程,例如[DeepLearning.AI](https://www.deeplearning.ai/) 提供的 [fast.ai实用深度学习教程](https://course.fast.ai/) -* 不需要事先具备 [PyTorch](https://pytorch.org/) 或 [TensorFlow](https://www.tensorflow.org/) 知识,虽然熟悉其中任何一个都会对huggingface的学习有所帮助 - -完成本课程后,我们建议您查看 [DeepLearning.AI的自然语言处理系列课程](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh),其中涵盖了广泛的传统 NLP 模型,如朴素贝叶斯和 LSTM,这些模型非常值得了解! - -## 我们是谁? - -关于作者: - -**Matthew Carrigan** 是 Hugging Face 的机器学习工程师。他住在爱尔兰都柏林,之前在 Parse.ly 担任机器学习工程师,在此之前,他在Trinity College Dublin担任博士后研究员。他不相信我们会通过扩展现有架构来实现 AGI,但无论如何都对机器人充满希望。 - -**Lysandre Debut** 是 Hugging Face 的机器学习工程师,从早期的开发阶段就一直致力于 🤗 Transformers 库。他的目标是通过使用非常简单的 API 开发工具,让每个人都可以使用 NLP。 - -**Sylvain Gugger** 是 Hugging Face 的一名研究工程师,也是 🤗Transformers库的核心维护者之一。此前,他是 fast.ai 的一名研究科学家,他与Jeremy Howard 共同编写了[Deep Learning for Coders with fastai and Py Torch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)。他的主要研究重点是通过设计和改进允许模型在有限资源上快速训练的技术,使深度学习更容易普及。 - -**Merve Noyan** 是 Hugging Face 的开发者倡导者,致力于开发工具并围绕它们构建内容,以使每个人的机器学习平民化。 - -**Lucile Saulnier** 是 Hugging Face 的机器学习工程师,负责开发和支持开源工具的使用。她还积极参与了自然语言处理领域的许多研究项目,例如协作训练和 BigScience。 - -**Lewis Tunstall** 是 Hugging Face 的机器学习工程师,专注于开发开源工具并使更广泛的社区可以使用它们。他也是即将出版的一本书[O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/)的作者之一。 - -**Leandro von Werra** 是 Hugging Face 开源团队的机器学习工程师,也是即将出版的一本书[O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/)的作者之一。他拥有多年的行业经验,通过在整个机器学习堆栈中工作,将 NLP 项目投入生产。 - -你准备好了吗?在本章中,您将学习: -* 如何使用 `pipeline()` 函数解决文本生成、分类等NLP任务 -* 关于 Transformer 架构 -* 如何区分编码器、解码器和编码器-解码器架构和用例 diff --git a/chapters/zh/chapter1/10.mdx b/chapters/zh/chapter1/10.mdx deleted file mode 100644 index 23f768115..000000000 --- a/chapters/zh/chapter1/10.mdx +++ /dev/null @@ -1,253 +0,0 @@ - - -# 章末小测试 - -这一章涵盖了很多内容! 如果有一些不太明白的地方,请不要担心; 下一章将帮助你了解这些模块在底层是如何工作的。 - -让我们来测试一下你在这一章学到了什么! - -### 1. 探索 Hub 并寻找 `roberta-large-mnli` checkpoint。 它可以完成什么类型的任务? - - -roberta-large-mnli 页面回顾一下." - }, - { - text: "文本分类", - explain: "更准确地说,它对两个句子在三个标签(矛盾、无关、相近)之间的逻辑链接进行分类——这项任务也称为自然语言推理.", - correct: true - }, - { - text: "文本生成", - explain: "点击前往roberta-large-mnli 页面回顾一下." - } - ]} -/> - -### 2. 下面的代码将会返回什么结果? - -```py -from transformers import pipeline - -ner = pipeline("ner", grouped_entities=True) -ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") -``` - -sentiment-analysis pipeline将会返回这些." - }, - { - text: "它将返回一个生成的文本来完成这句话。", - explain: "这个选项是不对的 — text-generation pipeline将会返回这些.", - }, - { - text: "它将返回代表人员、组织或位置的单词。", - explain: "此外,使用 grouped_entities=True,它会将属于同一实体的单词组合在一起,例如“Hugging Face”。", - correct: true - } - ]} -/> - -### 3. 在此代码示例中...的地方应该填写什么? - -```py -from transformers import pipeline - -filler = pipeline("fill-mask", model="bert-base-cased") -result = filler("...") -``` - - has been waiting for you.", - explain: "这个选项是不对的。 请查看 bert-base-cased 模型卡片,然后再尝试找找错在哪里。" - }, - { - text: "This [MASK] has been waiting for you.", - explain: "正解! 这个模型的mask的掩码是[MASK].", - correct: true - }, - { - text: "This man has been waiting for you.", - explain: "这个选项是不对的。 这个pipeline的作用是填充经过mask的文字,因此它需要在输入的文本中存在mask的token。" - } - ]} -/> - -### 4. 为什么这段代码会无法运行? - -```py -from transformers import pipeline - -classifier = pipeline("zero-shot-classification") -result = classifier("This is a course about the Transformers library") -``` - -candidate_labels=[...].", - correct: true - }, - { - text: "这个pipeline需要多个句子,而不仅仅是一个。", - explain: "这个选项是不对的。尽管正确使用时,此pipeline可以同时处理多个句子(与所有其他pipeline一样)。" - }, - { - text: "像往常一样,🤗 Transformers库出故障了。", - explain: "对此,我们不予置评!" - }, - { - text: "该pipeline需要更长的输入; 这个句子太短了。", - explain: "这个选项是不对的。 不过请注意,在这个pipeline处理时,太长的文本将被截断。" - } - ]} -/> - -### 5. “迁移学习”是什么意思? - - - -### 6. 语言模型在预训练时通常不需要标签,这样的说法是否正确。 - - -自监督,这意味着标签是根据输入自动创建的(例如:预测下一个单词或填充一些[MARSK]单词)。", - correct: true - }, - { - text: "错误", - explain: "这不是一个正确的答案。" - } - ]} -/> - - -### 7. 选择最能描述“模型(model)”、“架构(architecture)”和“权重(weights)”的句子。 - - - -### 8. 你将使用以下哪种类型的模型来根据输入的提示生成文本? - - - -### 9. 你会使用哪些类型的模型来生成文本的摘要? - - - -### 10. 你会使用哪一种类型的模型来根据特定的标签对文本输入进行分类? - - - -### 11. 模型中观察到的偏见有哪些可能的来源? - - diff --git a/chapters/zh/chapter1/2.mdx b/chapters/zh/chapter1/2.mdx deleted file mode 100644 index 1b5ee0ea6..000000000 --- a/chapters/zh/chapter1/2.mdx +++ /dev/null @@ -1,20 +0,0 @@ -# 自然语言处理 - -在进入 Transformer 模型之前,让我们快速概述一下自然语言处理是什么以及我们为什么这么重视它。 - -## 什么是自然语言处理? - -NLP 是语言学和机器学习交叉领域,专注于理解与人类语言相关的一切。 NLP 任务的目标不仅是单独理解单个单词,而且是能够理解这些单词的上下文。 - -以下是常见 NLP 任务的列表,每个任务都有一些示例: - -- **对整个句子进行分类**: 获取评论的情绪,检测电子邮件是否为垃圾邮件,确定句子在语法上是否正确或两个句子在逻辑上是否相关 -- **对句子中的每个词进行分类**: 识别句子的语法成分(名词、动词、形容词)或命名实体(人、地点、组织) -- **生成文本内容**: 用自动生成的文本完成提示,用屏蔽词填充文本中的空白 -- **从文本中提取答案**: 给定问题和上下文,根据上下文中提供的信息提取问题的答案 -- **从输入文本生成新句子**: 将文本翻译成另一种语言,总结文本 - -NLP 不仅限于书面文本。它还解决了语音识别和计算机视觉中的复杂挑战,例如生成音频样本的转录或图像描述。 -## 为什么具有挑战性? - -计算机处理信息的方式与人类不同。例如,当我们读到“我饿了”这句话时,我们很容易理解它的意思。同样,给定两个句子,例如“我很饿”和“我很伤心”,我们可以轻松确定它们的相似程度。对于机器学习 (ML) 模型,此类任务更加困难。文本需要以一种使模型能够从中学习的方式进行处理。而且由于语言很复杂,我们需要仔细考虑必须如何进行这种处理。关于如何表示文本已经做了很多研究,我们将在下一章中介绍一些方法。 \ No newline at end of file diff --git a/chapters/zh/chapter1/3.mdx b/chapters/zh/chapter1/3.mdx deleted file mode 100644 index 076263ba4..000000000 --- a/chapters/zh/chapter1/3.mdx +++ /dev/null @@ -1,287 +0,0 @@ -# Transformers能做什么? - - - -在本节中,我们将看看 Transformer 模型可以做什么,并使用 🤗 Transformers 库中的第一个工具:pipeline() 函数。 - -👀 看到那个右上角的 在Colab中打开的按钮了吗? 单击它就可以打开一个包含本节所有代码示例的 Google Colab 笔记本。 每一个有实例代码的小节都会有它。 - -如果您想在本地运行示例,我们建议您查看准备. - - -## Transformer被应用于各个方面! -Transformer 模型用于解决各种 NLP 任务,就像上一节中提到的那样。以下是一些使用 Hugging Face 和 Transformer 模型的公司和组织,他们也通过分享他们的模型回馈社区: - -![使用 Hugging Face 的公司](https://huggingface.co/course/static/chapter1/companies.PNG) -[🤗 Transformers 库](https://github.com/huggingface/transformers)提供了创建和使用这些共享模型的功能。[模型中心(hub)](https://huggingface.co/models)包含数千个任何人都可以下载和使用的预训练模型。您还可以将自己的模型上传到 Hub! - - -⚠️ Hugging Face Hub 不限于 Transformer 模型。任何人都可以分享他们想要的任何类型的模型或数据集!创建一个 Huggingface.co 帐户(https://huggingface.co/join)以使用所有可用功能! - - -在深入研究 Transformer 模型的底层工作原理之前,让我们先看几个示例,看看它们如何用于解决一些有趣的 NLP 问题。 - -## 使用pipelines - - -(这里有一个视频,但是国内可能打不开,译者注) - - -🤗 Transformers 库中最基本的对象是 **pipeline()** 函数。它将模型与其必要的预处理和后处理步骤连接起来,使我们能够通过直接输入任何文本并获得最终的答案: - -```python -from transformers import pipeline - -classifier = pipeline("sentiment-analysis") -classifier("I've been waiting for a HuggingFace course my whole life.") -``` -```python out -[{'label': 'POSITIVE', 'score': 0.9598047137260437}] -``` - - -我们也可以多传几句! -```python -classifier( - ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!"] -) -``` -```python out -[{'label': 'POSITIVE', 'score': 0.9598047137260437}, - {'label': 'NEGATIVE', 'score': 0.9994558095932007}] -``` -默认情况下,此pipeline选择一个特定的预训练模型,该模型已针对英语情感分析进行了微调。创建**分类器**对象时,将下载并缓存模型。如果您重新运行该命令,则将使用缓存的模型,无需再次下载模型。 - -将一些文本传递到pipeline时涉及三个主要步骤: - -1. 文本被预处理为模型可以理解的格式。 -2. 预处理的输入被传递给模型。 -3. 模型处理后输出最终人类可以理解的结果。 - -目前[可用的一些pipeline](https://huggingface.co/transformers/main_classes/pipelines.html)是: - -* **特征提取**(获取文本的向量表示) -* **填充空缺** -* **ner**(命名实体识别) -* **问答** -* **情感分析** -* **文本摘要** -* **文本生成** -* **翻译** -* **零样本分类** - -让我们来看看其中的一些吧! - -## 零样本分类 -我们将首先处理一项非常具挑战性的任务,我们需要对尚未标记的文本进行分类。这是实际项目中的常见场景,因为注释文本通常很耗时并且需要领域专业知识。对于这项任务**zero-shot-classification**pipeline非常强大:它允许您直接指定用于分类的标签,因此您不必依赖预训练模型的标签。下面的模型展示了如何使用这两个标签将句子分类为正面或负面——但也可以使用您喜欢的任何其他标签集对文本进行分类。 - -```python -from transformers import pipeline - -classifier = pipeline("zero-shot-classification") -classifier( - "This is a course about the Transformers library", - candidate_labels=["education", "politics", "business"], -) -``` -```python out -{'sequence': 'This is a course about the Transformers library', - 'labels': ['education', 'business', 'politics'], - 'scores': [0.8445963859558105, 0.111976258456707, 0.043427448719739914]} -``` - -此pipeline称为zero-shot,因为您不需要对数据上的模型进行微调即可使用它。它可以直接返回您想要的任何标签列表的概率分数! - -✏️**快来试试吧!**使用您自己的序列和标签,看看模型的行为。 - - -## 文本生成 -现在让我们看看如何使用pipeline来生成一些文本。这里的主要使用方法是您提供一个提示,模型将通过生成剩余的文本来自动完成整段话。这类似于许多手机上的预测文本功能。文本生成涉及随机性,因此如果您没有得到相同的如下所示的结果,这是正常的。 - -```python -from transformers import pipeline - -generator = pipeline("text-generation") -generator("In this course, we will teach you how to") -``` -```python out -[{'generated_text': 'In this course, we will teach you how to understand and use ' - 'data flow and data interchange when handling user data. We ' - 'will be working with one or more of the most commonly used ' - 'data flows — data flows of various types, as seen by the ' - 'HTTP'}] -``` -您可以使用参数 **num_return_sequences** 控制生成多少个不同的序列,并使用参数 **max_length** 控制输出文本的总长度。 - - -✏️**快来试试吧!**使用 num_return_sequences 和 max_length 参数生成两个句子,每个句子 15 个单词。 - - -## 在pipeline中使用 Hub 中的其他模型 -前面的示例使用了默认模型,但您也可以从 Hub 中选择特定模型以在特定任务的pipeline中使用 - 例如,文本生成。转到[模型中心(hub)](https://huggingface.co/models)并单击左侧的相应标签将会只显示该任务支持的模型。[例如这样](https://huggingface.co/models?pipeline_tag=text-generation)。 - -让我们试试 [**distilgpt2**](https://huggingface.co/distilgpt2) 模型吧!以下是如何在与以前相同的pipeline中加载它: - -```python -from transformers import pipeline - -generator = pipeline("text-generation", model="distilgpt2") -generator( - "In this course, we will teach you how to", - max_length=30, - num_return_sequences=2, -) -``` -```python out -[{'generated_text': 'In this course, we will teach you how to manipulate the world and ' - 'move your mental and physical capabilities to your advantage.'}, - {'generated_text': 'In this course, we will teach you how to become an expert and ' - 'practice realtime, and with a hands on experience on both real ' - 'time and real'}] -``` -您可以通过单击语言标签来筛选搜索结果,然后选择另一种文本生成模型的模型。模型中心(hub)甚至包含支持多种语言的多语言模型。 - -通过单击选择模型后,您会看到有一个小组件,可让您直接在线试用。通过这种方式,您可以在下载之前快速测试模型的功能。 - -✏️**快来试试吧!**使用标签筛选查找另一种语言的文本生成模型。使用小组件测试并在pipeline中使用它! - - -## 推理 API -所有模型都可以使用 Inference API 直接通过浏览器进行测试,该 API 可在 [Hugging Face 网站](https://huggingface.co/)上找到。通过输入自定义文本并观察模型的输出,您可以直接在此页面上使用模型。 - -小组件形式的推理 API 也可作为付费产品使用,如果您的工作流程需要它,它会派上用场。有关更多详细信息,请参阅[定价页面](https://huggingface.co/pricing)。 - -## Mask filling -您将尝试的下一个pipeline是 **fill-mask**。此任务的想法是填充给定文本中的空白: -```python -from transformers import pipeline - -unmasker = pipeline("fill-mask") -unmasker("This course will teach you all about models.", top_k=2) -``` -```python out -[{'sequence': 'This course will teach you all about mathematical models.', - 'score': 0.19619831442832947, - 'token': 30412, - 'token_str': ' mathematical'}, - {'sequence': 'This course will teach you all about computational models.', - 'score': 0.04052725434303284, - 'token': 38163, - 'token_str': ' computational'}] -``` -**top_k** 参数控制要显示的结果有多少种。请注意,这里模型填充了特殊的< **mask** >词,它通常被称为掩码标记。其他掩码填充模型可能有不同的掩码标记,因此在探索其他模型时要验证正确的掩码字是什么。检查它的一种方法是查看小组件中使用的掩码。 - - -✏️**快来试试吧!**在 Hub 上搜索基于 bert 的模型并在推理 API 小组件中找到它的掩码。这个模型对上面pipeline示例中的句子预测了什么? - - -## 命名实体识别 -命名实体识别 (NER) 是一项任务,其中模型必须找到输入文本的哪些部分对应于诸如人员、位置或组织之类的实体。让我们看一个例子: -```python -from transformers import pipeline - -ner = pipeline("ner", grouped_entities=True) -ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") -``` -```python out -[{'entity_group': 'PER', 'score': 0.99816, 'word': 'Sylvain', 'start': 11, 'end': 18}, - {'entity_group': 'ORG', 'score': 0.97960, 'word': 'Hugging Face', 'start': 33, 'end': 45}, - {'entity_group': 'LOC', 'score': 0.99321, 'word': 'Brooklyn', 'start': 49, 'end': 57} -] -``` -在这里,模型正确地识别出 Sylvain 是一个人 (PER),Hugging Face 是一个组织 (ORG),而布鲁克林是一个位置 (LOC)。 - -我们在pipeline创建函数中传递选项 **grouped_entities=True** 以告诉pipeline将对应于同一实体的句子部分重新组合在一起:这里模型正确地将“Hugging”和“Face”分组为一个组织,即使名称由多个词组成。事实上,正如我们即将在下一章看到的,预处理甚至会将一些单词分成更小的部分。例如,**Sylvain** 分割为了四部分:**S、##yl、##va** 和 **##in**。在后处理步骤中,pipeline成功地重新组合了这些部分。 - - -✏️**快来试试吧!**在模型中心(hub)搜索能够用英语进行词性标注(通常缩写为 POS)的模型。这个模型对上面例子中的句子预测了什么? - - -## 问答系统 -问答pipeline使用来自给定上下文的信息回答问题: -```python -from transformers import pipeline - -question_answerer = pipeline("question-answering") -question_answerer( - question="Where do I work?", - context="My name is Sylvain and I work at Hugging Face in Brooklyn", -) -``` -```python out -{'score': 0.6385916471481323, 'start': 33, 'end': 45, 'answer': 'Hugging Face'} -klyn", -) - -``` -请注意,此pipeline通过从提供的上下文中提取信息来工作;它不会凭空生成答案。 - -## 文本摘要 -文本摘要是将文本缩减为较短文本的任务,同时保留文本中的主要(重要)信息。下面是一个例子: - -```python -from transformers import pipeline - -summarizer = pipeline("summarization") -summarizer( - """ - America has changed dramatically during recent years. Not only has the number of - graduates in traditional engineering disciplines such as mechanical, civil, - electrical, chemical, and aeronautical engineering declined, but in most of - the premier American universities engineering curricula now concentrate on - and encourage largely the study of engineering science. As a result, there - are declining offerings in engineering subjects dealing with infrastructure, - the environment, and related issues, and greater concentration on high - technology subjects, largely supporting increasingly complex scientific - developments. While the latter is important, it should not be at the expense - of more traditional engineering. - - Rapidly developing economies such as China and India, as well as other - industrial countries in Europe and Asia, continue to encourage and advance - the teaching of engineering. Both China and India, respectively, graduate - six and eight times as many traditional engineers as does the United States. - Other industrial countries at minimum maintain their output, while America - suffers an increasingly serious decline in the number of engineering graduates - and a lack of well-educated engineers. -""" -) -``` -```python out -[{'summary_text': ' America has changed dramatically during recent years . The ' - 'number of engineering graduates in the U.S. has declined in ' - 'traditional engineering disciplines such as mechanical, civil ' - ', electrical, chemical, and aeronautical engineering . Rapidly ' - 'developing economies such as China and India, as well as other ' - 'industrial countries in Europe and Asia, continue to encourage ' - 'and advance engineering .'}] -``` -与文本生成一样,您指定结果的 **max_length** 或 **min_length**。 - -## 翻译 -对于翻译,如果您在任务名称中提供语言对(例如“**translation_en_to_fr**”),则可以使用默认模型,但最简单的方法是在[模型中心(hub)](https://huggingface.co/models)选择要使用的模型。在这里,我们将尝试从法语翻译成英语: - -```python -from transformers import pipeline - -translator = pipeline("translation", model="Helsinki-NLP/opus-mt-fr-en") -translator("Ce cours est produit par Hugging Face.") -``` -```python out -[{'translation_text': 'This course is produced by Hugging Face.'}] - -``` - -与文本生成和摘要一样,您可以指定结果的 **max_length** 或 **min_length**。 - - - -✏️**快来试试吧!**搜索其他语言的翻译模型,尝试将前一句翻译成几种不同的语言。 - - - -到目前为止显示的pipeline主要用于演示目的。它们是为特定任务而编程的,不能对他们进行自定义的修改。在下一章中,您将了解 **pipeline()** 函数内部的内容以及如何进行自定义的修改。 \ No newline at end of file diff --git a/chapters/zh/chapter1/4.mdx b/chapters/zh/chapter1/4.mdx deleted file mode 100644 index 45641e71e..000000000 --- a/chapters/zh/chapter1/4.mdx +++ /dev/null @@ -1,172 +0,0 @@ -# Transformers 是如何工作的? - -在本节中,我们将深入了解 Transformer 模型的架构。 - -## 一点Transformers的发展历史 - -以下是 Transformer 模型(简短)历史中的一些关键节点: - -
-A brief chronology of Transformers models. - -
- -[Transformer 架构](https://arxiv.org/abs/1706.03762) 于 2017 年 6 月推出。原本研究的重点是翻译任务。随后推出了几个有影响力的模型,包括 - -- **2018 年 6 月**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), 第一个预训练的 Transformer 模型,用于各种 NLP 任务并获得极好的结果 - -- **2018 年 10 月**: [BERT](https://arxiv.org/abs/1810.04805), 另一个大型预训练模型,该模型旨在生成更好的句子摘要(下一章将详细介绍!) - -- **2019 年 2 月**: [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), GPT 的改进(并且更大)版本,由于道德问题没有立即公开发布 - -- **2019 年 10 月**: [DistilBERT](https://arxiv.org/abs/1910.01108), BERT 的提炼版本,速度提高 60%,内存减轻 40%,但仍保留 BERT 97% 的性能 - -- **2019 年 10 月**: [BART](https://arxiv.org/abs/1910.13461) 和 [T5](https://arxiv.org/abs/1910.10683), 两个使用与原始 Transformer 模型相同架构的大型预训练模型(第一个这样做) - -- **2020 年 5 月**, [GPT-3](https://arxiv.org/abs/2005.14165), GPT-2 的更大版本,无需微调即可在各种任务上表现良好(称为零样本学习) - -这个列表并不全面,只是为了突出一些不同类型的 Transformer 模型。大体上,它们可以分为三类: - -- GPT-like (也被称作自回归Transformer模型) -- BERT-like (也被称作自动编码Transformer模型) -- BART/T5-like (也被称作序列到序列的 Transformer模型) - -稍后我们将更深入地探讨这些分类。 - -## Transformers是语言模型 - -上面提到的所有 Transformer 模型(GPT、BERT、BART、T5 等)都被训练为语言模型。这意味着他们已经以无监督学习的方式接受了大量原始文本的训练。无监督学习是一种训练类型,其中目标是根据模型的输入自动计算的。这意味着不需要人工来标记数据! - -这种类型的模型可以对其训练过的语言进行统计理解,但对于特定的实际任务并不是很有用。因此,一般的预训练模型会经历一个称为*迁移学习*的过程。在此过程中,模型在给定任务上以监督方式(即使用人工注释标签)进行微调。 - -任务的一个例子是阅读 *n* 个单词的句子,预测下一个单词。这被称为因果语言建模,因为输出取决于过去和现在的输入。 - -
-Example of causal language modeling in which the next word from a sentence is predicted. - -
- -另一个例子是*遮罩语言建模*,该模型预测句子中的遮住的词。 - -
-Example of masked language modeling in which a masked word from a sentence is predicted. - -
- -## Transformer是大模型 - -除了一些特例(如 DistilBERT)外,实现更好性能的一般策略是增加模型的大小以及预训练的数据量。 - -
-Number of parameters of recent Transformers models -
- -不幸的是,训练模型,尤其是大型模型,需要大量的数据,时间和计算资源。它甚至会对环境产生影响,如下图所示。 - -
-The carbon footprint of a large language model. - -
- - - -Transformers是由一个团队领导的(非常大的)模型项目,该团队试图减少预训练对环境的影响,通过运行大量试验以获得最佳超参数。 - -想象一下,如果每次一个研究团队、一个学生组织或一家公司想要训练一个模型,都从头开始训练的。这将导致巨大的、不必要的浪费! - -这就是为什么共享语言模型至关重要:共享经过训练的权重,当遇见新的需求时在预训练的权重之上进行微调,可以降低训练模型训练的算力和时间消耗,降低全球的总体计算成本和碳排放。 - - -## 迁移学习 - - - -*预训练是*训练模型前的一个操作:随机初始化权重,在没有任何先验知识的情况下开始训练。 - -
-The pretraining of a language model is costly in both time and money. - -
- -这种预训练通常是在非常大量的数据上进行的。因此,它需要大量的数据,而且训练可能需要几周的时间。 - -另一方面,*微调*是在模型经过预训练后完成的训练。要执行微调,首先需要获取一个经过预训练的语言模型,然后使用特定于任务的数据集执行额外的训练。等等,为什么不直接为最后的任务而训练呢?有几个原因: - -* 预训练模型已经在与微调数据集有一些相似之处的数据集上进行了训练。因此,微调过程能够利用模型在预训练期间获得的知识(例如,对于NLP问题,预训练模型将对您在任务中使用的语言有某种统计规律上的理解)。 -* 由于预训练模型已经在大量数据上进行了训练,因此微调需要更少的数据来获得不错的结果。 -* 出于同样的原因,获得好结果所需的时间和资源要少得多 - -例如,可以利用英语的预训练过的模型,然后在arXiv语料库上对其进行微调,从而形成一个基于科学/研究的模型。微调只需要有限的数据量:预训练模型获得的知识可以“迁移”到目标任务上,因此被称为*迁移学习*。 - -
-The fine-tuning of a language model is cheaper than pretraining in both time and money. - -
- -因此,微调模型具有较低的时间、数据、财务和环境成本。迭代不同的微调方案也更快、更容易,因为与完整的预训练相比,训练的约束更少。 - -这个过程也会比从头开始的训练(除非你有很多数据)取得更好的效果,这就是为什么你应该总是尝试利用一个预训练的模型--一个尽可能接近你手头的任务的模型--并对其进行微调。 - -## 一般的体系结构 -在这一部分,我们将介绍Transformer模型的一般架构。如果你不理解其中的一些概念,不要担心;下文将详细介绍每个组件。 - - - -## 介绍 - -该模型主要由两个块组成: - -* **Encoder (左侧)**: 编码器接收输入并构建其表示(其特征)。这意味着对模型进行了优化,以从输入中获得理解。 -* **Decoder (右侧)**: 解码器使用编码器的表示(特征)以及其他输入来生成目标序列。这意味着该模型已针对生成输出进行了优化。 - -
-Architecture of a Transformers models - -
- -这些部件中的每一个都可以独立使用,具体取决于任务: - -* **Encoder-only models**: 适用于需要理解输入的任务,如句子分类和命名实体识别。 -* **Decoder-only models**: 适用于生成任务,如文本生成。 -* **Encoder-decoder models** 或者 **sequence-to-sequence models**: 适用于需要根据输入进行生成的任务,如翻译或摘要。 - -在后面的部分中,我们将单独地深入研究这些体系结构。 - -## 注意力层 - -Transformer模型的一个关键特性是*注意力层*。事实上,介绍Transformer架构的文章的标题是[“注意力就是你所需要的”](https://arxiv.org/abs/1706.03762)! 我们将在课程的后面更加深入地探索注意力层;现在,您需要知道的是,这一层将告诉模型在处理每个单词的表示时,要特别重视您传递给它的句子中的某些单词(并且或多或少地忽略其他单词)。 - -把它放在语境中,考虑将文本从英语翻译成法语的任务。在输入“You like this course”的情况下,翻译模型还需要注意相邻的单词“You”,以获得单词“like”的正确翻译,因为在法语中,动词“like”的变化取决于主题。然而,句子的其余部分对于该词的翻译没有用处。同样,在翻译“this”时,模型也需要注意“course”一词,因为“this”的翻译不同,取决于相关名词是单数还是复数。同样,句子中的其他单词对于“this”的翻译也不重要。对于更复杂的句子(以及更复杂的语法规则),模型需要特别注意可能出现在句子中更远位置的单词,以便正确地翻译每个单词。 - -同样的概念也适用于与自然语言相关的任何任务:一个词本身有一个含义,但这个含义受语境的影响很大,语境可以是研究该词之前或之后的任何其他词(或多个词)。 - -现在您已经了解了注意力层的含义,让我们更仔细地了解Transformer架构。 - -## 原始的结构 - -Transformer架构最初是为翻译而设计的。在训练期间,编码器接收特定语言的输入(句子),而解码器需要输出对应语言的翻译。在编码器中,注意力层可以使用一个句子中的所有单词(正如我们刚才看到的,给定单词的翻译可以取决于它在句子中的其他单词)。然而,解码器是按顺序工作的,并且只能注意它已经翻译过的句子中的单词。例如,当我们预测了翻译目标的前三个单词时,我们将它们提供给解码器,然后解码器使用编码器的所有输入来尝试预测第四个单词。 - -为了在训练过程中加快速度(当模型可以访问目标句子时),解码器会被输入整个目标,但不允许获取到要翻译的单词(如果它在尝试预测位置2的单词时可以访问位置2的单词,解码器就会偷懒,直接输出那个单词,从而无法学习到正确的语言关系!)。例如,当试图预测第4个单词时,注意力层只能获取位置1到3的单词。 - -最初的Transformer架构如下所示,编码器位于左侧,解码器位于右侧: - -
-Architecture of a Transformers models - -
- -注意,解码器块中的第一个注意力层关联到解码器的所有(过去的)输入,但是第二注意力层使用编码器的输出。因此,它可以访问整个输入句子,以最好地预测当前单词。这是非常有用的,因为不同的语言可以有语法规则将单词按不同的顺序排列,或者句子后面提供的一些上下文可能有助于确定给定单词的最佳翻译。 - -也可以在编码器/解码器中使用*注意力遮罩层*,以防止模型注意某些特殊单词。例如,在批处理句子时,填充特殊词使所有句子的长度一致。 - -## 架构与参数 - -在本课程中,当我们深入探讨Transformers模型时,您将看到 -架构、参数和模型 -。 这些术语的含义略有不同: - -* **架构**: 这是模型的骨架 -- 每个层的定义以及模型中发生的每个操作。 -* **Checkpoints**: 这些是将在给架构中结构中加载的权重。 -* **模型**: 这是一个笼统的术语,没有“架构”或“参数”那么精确:它可以指两者。为了避免歧义,本课程使用将使用架构和参数。 - -例如,BERT是一个架构,而 `bert-base-cased`, 这是谷歌团队为BERT的第一个版本训练的一组权重参数,是一个参数。我们可以说“BERT模型”和"`bert-base-cased`模型." diff --git a/chapters/zh/chapter1/5.mdx b/chapters/zh/chapter1/5.mdx deleted file mode 100644 index 7aa765ec2..000000000 --- a/chapters/zh/chapter1/5.mdx +++ /dev/null @@ -1,17 +0,0 @@ -# “编码器”模型 - - - -“编码器”模型指仅使用编码器的Transformer模型。在每个阶段,注意力层都可以获取初始句子中的所有单词。这些模型通常具有“双向”注意力,被称为自编码模型。 - -这些模型的预训练通常围绕着以某种方式破坏给定的句子(例如:通过随机遮盖其中的单词),并让模型寻找或重建给定的句子。 - -“编码器”模型最适合于需要理解完整句子的任务,例如:句子分类、命名实体识别(以及更普遍的单词分类)和阅读理解后回答问题。 - -该系列模型的典型代表有: - -- [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) diff --git a/chapters/zh/chapter1/6.mdx b/chapters/zh/chapter1/6.mdx deleted file mode 100644 index 2de4c44a6..000000000 --- a/chapters/zh/chapter1/6.mdx +++ /dev/null @@ -1,17 +0,0 @@ -# “解码器”模型 - - - -“解码器”模型通常指仅使用解码器的Transformer模型。在每个阶段,对于给定的单词,注意力层只能获取到句子中位于将要预测单词前面的单词。这些模型通常被称为自回归模型。 - -“解码器”模型的预训练通常围绕预测句子中的下一个单词进行。 - -这些模型最适合于涉及文本生成的任务。 - -该系列模型的典型代表有: - - -- [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) -- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) -- [GPT-2](https://huggingface.co/transformers/model_doc/gpt2.html) -- [Transformer XL](https://huggingface.co/transformers/model_doc/transformerxl.html) diff --git a/chapters/zh/chapter1/7.mdx b/chapters/zh/chapter1/7.mdx deleted file mode 100644 index 99dc00eea..000000000 --- a/chapters/zh/chapter1/7.mdx +++ /dev/null @@ -1,16 +0,0 @@ -# 序列到序列模型 - - - -编码器-解码器模型(也称为序列到序列模型)同时使用Transformer架构的编码器和解码器两个部分。在每个阶段,编码器的注意力层可以访问初始句子中的所有单词,而解码器的注意力层只能访问位于输入中将要预测单词前面的单词。 - -这些模型的预训练可以使用训练编码器或解码器模型的方式来完成,但通常涉及更复杂的内容。例如,[T5](https://huggingface.co/t5-base)通过将文本的随机跨度(可以包含多个单词)替换为单个特殊单词来进行预训练,然后目标是预测该掩码单词替换的文本。 - -序列到序列模型最适合于围绕根据给定输入生成新句子的任务,如摘要、翻译或生成性问答。 - -该系列模型的典型代表有: - -- [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) diff --git a/chapters/zh/chapter1/8.mdx b/chapters/zh/chapter1/8.mdx deleted file mode 100644 index 707731892..000000000 --- a/chapters/zh/chapter1/8.mdx +++ /dev/null @@ -1,31 +0,0 @@ -# Bias and limitations - - - -如果您打算在正式的项目中使用经过预训练或经过微调的模型。请注意:虽然这些模型是很强大,但它们也有局限性。其中最大的一个问题是,为了对大量数据进行预训练,研究人员通常会搜集所有他们能找到的内容,中间可能夹带一些意识形态或者价值观的刻板印象。 - -为了快速解释清楚这个问题,让我们回到一个使用BERT模型的pipeline的例子: - -```python -from transformers import pipeline - -unmasker = pipeline("fill-mask", model="bert-base-uncased") -result = unmasker("This man works as a [MASK].") -print([r["token_str"] for r in result]) - -result = unmasker("This woman works as a [MASK].") -print([r["token_str"] for r in result]) -``` - -```python out -['lawyer', 'carpenter', 'doctor', 'waiter', 'mechanic'] -['nurse', 'waitress', 'teacher', 'maid', 'prostitute'] -``` -当要求模型填写这两句话中缺少的单词时,模型给出的答案中,只有一个与性别无关(服务员/女服务员)。其他职业通常与某一特定性别相关,妓女最终进入了模型中与“女人”和“工作”相关的前五位。尽管BERT是使用经过筛选和清洗后,明显中立的数据集上建立的的Transformer模型,而不是通过从互联网上搜集数据(它是在[Wikipedia 英文](https://huggingface.co/datasets/wikipedia)和[BookCorpus](https://huggingface.co/datasets/bookcorpus)数据集)。 - -因此,当您使用这些工具时,您需要记住,使用的原始模型的时候,很容易生成性别歧视、种族主义或恐同内容。这种固有偏见不会随着微调模型而使消失。 \ No newline at end of file diff --git a/chapters/zh/chapter1/9.mdx b/chapters/zh/chapter1/9.mdx deleted file mode 100644 index 16c5ab6ad..000000000 --- a/chapters/zh/chapter1/9.mdx +++ /dev/null @@ -1,11 +0,0 @@ -# 总结 - -在本章中,您了解了如何使用来自🤗Transformers的函数pipeline()处理不同的NLP任务。您还了解了如何在模型中心(hub)中搜索和使用模型,以及如何使用推理API直接在浏览器中测试模型。 - -我们讨论了Transformer模型如何在应用层上工作,并讨论了迁移学习和微调的重要性。您可以使用完整的体系结构,也可以仅使用编码器或解码器,具体取决于您要解决的任务类型。下表总结了这一点: - -| 模型 | 示例 | 任务| -| ---- | ---- |----| -| 编码器 | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa |句子分类、命名实体识别、从文本中提取答案| -| 解码器 | CTRL, GPT, GPT-2, Transformer XL |文本生成| -| 编码器-解码器 | BART, T5, Marian, mBART |文本摘要、翻译、生成问题的回答| \ No newline at end of file diff --git a/utils/generate_notebooks.py b/utils/generate_notebooks.py index e64100810..bd41c5dcb 100644 --- a/utils/generate_notebooks.py +++ b/utils/generate_notebooks.py @@ -61,6 +61,7 @@ def read_and_split_frameworks(fname): else: return "".join(content) + def extract_cells(content): """ Extract the code/output cells from content. From 62fd8e0bbb1429421576075fbe0bd63851b86edb Mon Sep 17 00:00:00 2001 From: lewtun Date: Fri, 2 Sep 2022 13:58:01 +0200 Subject: [PATCH 29/51] Bump release (#307) --- chapters/vi/_toctree.yml | 65 ++ chapters/vi/chapter6/10.mdx | 278 ++++++++ chapters/vi/chapter6/3.mdx | 473 +++++++++++++ chapters/vi/chapter6/3b.mdx | 642 +++++++++++++++++ chapters/vi/chapter6/4.mdx | 122 ++++ chapters/vi/chapter6/5.mdx | 361 ++++++++++ chapters/vi/chapter6/6.mdx | 374 ++++++++++ chapters/vi/chapter6/7.mdx | 380 +++++++++++ chapters/vi/chapter6/8.mdx | 563 +++++++++++++++ chapters/vi/chapter7/1.mdx | 32 + chapters/vi/chapter7/2.mdx | 1010 +++++++++++++++++++++++++++ chapters/vi/chapter7/3.mdx | 1045 ++++++++++++++++++++++++++++ chapters/vi/chapter7/4.mdx | 993 +++++++++++++++++++++++++++ chapters/vi/chapter7/5.mdx | 1049 ++++++++++++++++++++++++++++ chapters/vi/chapter7/6.mdx | 947 +++++++++++++++++++++++++ chapters/vi/chapter7/7.mdx | 1213 +++++++++++++++++++++++++++++++++ chapters/vi/chapter7/8.mdx | 17 + chapters/vi/chapter7/9.mdx | 324 +++++++++ chapters/vi/chapter8/1.mdx | 12 + chapters/vi/chapter8/2.mdx | 364 ++++++++++ chapters/vi/chapter8/3.mdx | 171 +++++ chapters/vi/chapter8/4.mdx | 792 +++++++++++++++++++++ chapters/vi/chapter8/4_tf.mdx | 483 +++++++++++++ chapters/vi/chapter8/5.mdx | 90 +++ chapters/vi/chapter8/6.mdx | 7 + chapters/vi/chapter8/7.mdx | 226 ++++++ chapters/vi/chapter9/1.mdx | 39 ++ chapters/vi/chapter9/2.mdx | 109 +++ chapters/vi/chapter9/3.mdx | 164 +++++ chapters/vi/chapter9/4.mdx | 142 ++++ chapters/vi/chapter9/5.mdx | 68 ++ chapters/vi/chapter9/6.mdx | 99 +++ chapters/vi/chapter9/7.mdx | 236 +++++++ chapters/vi/chapter9/8.mdx | 19 + chapters/vi/chapter9/9.mdx | 234 +++++++ 35 files changed, 13143 insertions(+) create mode 100644 chapters/vi/chapter6/10.mdx create mode 100644 chapters/vi/chapter6/3.mdx create mode 100644 chapters/vi/chapter6/3b.mdx create mode 100644 chapters/vi/chapter6/4.mdx create mode 100644 chapters/vi/chapter6/5.mdx create mode 100644 chapters/vi/chapter6/6.mdx create mode 100644 chapters/vi/chapter6/7.mdx create mode 100644 chapters/vi/chapter6/8.mdx create mode 100644 chapters/vi/chapter7/1.mdx create mode 100644 chapters/vi/chapter7/2.mdx create mode 100644 chapters/vi/chapter7/3.mdx create mode 100644 chapters/vi/chapter7/4.mdx create mode 100644 chapters/vi/chapter7/5.mdx create mode 100644 chapters/vi/chapter7/6.mdx create mode 100644 chapters/vi/chapter7/7.mdx create mode 100644 chapters/vi/chapter7/8.mdx create mode 100644 chapters/vi/chapter7/9.mdx create mode 100644 chapters/vi/chapter8/1.mdx create mode 100644 chapters/vi/chapter8/2.mdx create mode 100644 chapters/vi/chapter8/3.mdx create mode 100644 chapters/vi/chapter8/4.mdx create mode 100644 chapters/vi/chapter8/4_tf.mdx create mode 100644 chapters/vi/chapter8/5.mdx create mode 100644 chapters/vi/chapter8/6.mdx create mode 100644 chapters/vi/chapter8/7.mdx create mode 100644 chapters/vi/chapter9/1.mdx create mode 100644 chapters/vi/chapter9/2.mdx create mode 100644 chapters/vi/chapter9/3.mdx create mode 100644 chapters/vi/chapter9/4.mdx create mode 100644 chapters/vi/chapter9/5.mdx create mode 100644 chapters/vi/chapter9/6.mdx create mode 100644 chapters/vi/chapter9/7.mdx create mode 100644 chapters/vi/chapter9/8.mdx create mode 100644 chapters/vi/chapter9/9.mdx diff --git a/chapters/vi/_toctree.yml b/chapters/vi/_toctree.yml index 46a3c7d40..491a6a9e7 100644 --- a/chapters/vi/_toctree.yml +++ b/chapters/vi/_toctree.yml @@ -126,6 +126,71 @@ title: Đố vui cuối chương quiz: 6 +- title: 7. Các tác vụ NLP chính + sections: + - local: chapter7/1 + title: Giới thiệu + - local: chapter7/2 + title: Phân loại token + - local: chapter7/3 + title: Tinh chỉnh một mô hình ngôn ngữ bị ẩn đi + - local: chapter7/4 + title: Dịch máy + - local: chapter7/5 + title: Tóm tắt + - local: chapter7/6 + title: Huấn luyện một mô hình ngôn ngữ nhân quả từ đầu + - local: chapter7/7 + title: Hỏi đáp + - local: chapter7/8 + title: Làm chủ NLP + - local: chapter7/9 + title: Đố vui cuối chương + quiz: 7 + +- title: 8. Làm thế nào để yêu cầu giúp đỡ + sections: + - local: chapter8/1 + title: Giới thiệu + - local: chapter8/2 + title: Phải làm gì khi bạn gặp lỗi + - local: chapter8/3 + title: Yêu cầu trợ giúp trên diễn đàn + - local: chapter8/4 + title: Gỡ lỗi quy trình huấn luyện + local_fw: { pt: chapter8/4, tf: chapter8/4_tf } + - local: chapter8/5 + title: Làm thế nào để viết một vấn đề hay + - local: chapter8/6 + title: Phần 2 đã hoàn thành! + - local: chapter8/7 + title: Đố vui cuối chương + quiz: 8 + +- title: 9. Xây dựng và chia sẻ các demo + new: true + subtitle: Ta đã huấn luyện một mô hình, làm sao ta khoe nó đây? + sections: + - local: chapter9/1 + title: Giới thiệu về Gradio + - local: chapter9/2 + title: Xây dựng bản demo đầu tiên của bạn + - local: chapter9/3 + title: Hiểu lớp Interface + - local: chapter9/4 + title: Chia sẻ các bản demo với người khác + - local: chapter9/5 + title: Tích hợp với Hugging Face Hub + - local: chapter9/6 + title: Các tính năng nâng cao của Interface + - local: chapter9/7 + title: Giới thiệu về Gradio Blocks + - local: chapter9/8 + title: Gradio, kiểm tra nào! + - local: chapter9/9 + title: Đố vui cuối chương + quiz: 9 + - title: Sự kiện Khoá học Hugging Face sections: - local: event/1 diff --git a/chapters/vi/chapter6/10.mdx b/chapters/vi/chapter6/10.mdx new file mode 100644 index 000000000..9775e2a9f --- /dev/null +++ b/chapters/vi/chapter6/10.mdx @@ -0,0 +1,278 @@ + + +# Đố vui cuối chương + +Cùng kiểm tra xem bạn đã học được những gì trong chương này! + +### 1. Khi nào ta nên huấn luyện 1 tokenizer mới? + + + +### 2. Ưu điểm của việc sử dụng trình tạo danh sách văn bản so với danh sách các danh sách văn bản khi sử dụng `train_new_from_iterator()` là gì? + +train_new_from_iterator() chấp nhận.", + explain: "Danh sách các danh sách văn bản là một loại trình tạo danh sách văn bản cụ thể, vì vậy phương pháp cũng sẽ chấp nhận điều này. Hãy thử lại!" + }, + { + text: "Bạn sẽ tránh tải toàn bộ tập dữ liệu vào bộ nhớ cùng một lúc.", + explain: "Đúng vậy! Mỗi loạt văn bản sẽ được giải phóng khỏi bộ nhớ khi bạn lặp lại và phần thu được sẽ đặc biệt rõ ràng nếu bạn sử dụng 🤗 Datasets để lưu trữ văn bản của mình.", + correct: true + }, + { + text: "Điều này sẽ cho phép thư viện 🤗 Tokenizers sử dụng quá trình xử lý đa luồng.", + explain: "Không, với cách nào xử lý đa luồng cũng sẽ được sử dụng." + }, + { + text: "Tokenizer mà bạn huấn luyện sẽ tạo ra các văn bản tốt hơn.", + explain: "Tokenize không tạo ra văn bản -- bạn có đang nhầm lẫn với mô hình ngôn ngữ không?" + } + ]} +/> + +### 3. Ưu điểm của tokenize "nhanh" là gì? + + + +### 4. Pipeline `token-classification` xử lý các thực thể trải dài trên nhiều token như thế nào? + + + +### 5. Pipeline `question-answering` xử lý ngữ cảnh dài như thế nào? + + + +### 6. Chuẩn hoá là gì? + + + +### 7. Pre-tokenization cho một tokenizer từ phụ là sao? + + + +### 8. Chọn các câu áp dụng mô hình BPE để tokenize? + + + +### 9. Chọn các câu áp dụng mô hình WordPiece để tokenize? + + + +### 10. Chọn các câu áp dụng mô hình Unigram để tokenize? + + diff --git a/chapters/vi/chapter6/3.mdx b/chapters/vi/chapter6/3.mdx new file mode 100644 index 000000000..b48e9560b --- /dev/null +++ b/chapters/vi/chapter6/3.mdx @@ -0,0 +1,473 @@ + + +# Sức mạnh đặc biệt của tokenizer nhanh + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Trong phần này, chúng ta sẽ xem xét kỹ hơn các khả năng của tokenizer trong 🤗 Transformers. Cho đến nay, chúng ta chỉ sử dụng chúng để tokenize đầu vào hoặc giải mã ID trở lại thành văn bản, nhưng các trình tokenize - đặc biệt là những trình tokenize được hỗ trợ bởi thư viện 🤗 Tokenizers - có thể làm được nhiều hơn thế. Để minh họa các tính năng bổ sung này, chúng ta sẽ khám phá cách tái tạo kết quả của `token-classification` (mà chúng ta gọi là `ner`) và `question-answering` chúng ta gặp phải lần đầu tiên trong [Chương 1](/course/chapter1). + + + +Trong phần thảo luận kế tiếp, chúng ta sẽ phân biệt giữa các loại tokenizer "chậm" và "nhanh". Phiên bản chậm là những phiên bản được viết bằng Python bên trong thư viện 🤗 Transformers, trong khi phiên bản nhanh là những phiên bản được cung cấp bởi 🤗 Tokenizers, được viết bằng Rust. Nếu bạn nhớ bảng từ [Chương 5](/course/chapter5/3) báo cáo khoảng thời gian tokenize nhanh và chậm cần để tokenize Drug Review Dataset, bạn nên biết lý do tại sao chúng tôi gọi chúng là nhanh và chậm : + + | Tokenizer nhanh | Tokenizer chậm +:--------------:|:--------------:|:-------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s + + + +⚠️ Khi tokenize một câu, bạn không phải lúc nào cũng thấy sự khác biệt về tốc độ giữa các phiên bản chậm và nhanh của cùng một trình tokenize. Trên thực tế, phiên bản nhanh có thể chậm hơn! Chỉ khi tokenize nhiều văn bản song song cùng một lúc, bạn mới có thể thấy rõ sự khác biệt. + + + +## Mã hoá theo lô + + + +Đầu ra của tokenizer không phải là một từ điển Python đơn giản; những gì chúng ta nhận được thực sự là một đối tượng `BatchEncoding` đặc biệt. Đó là một lớp con của từ điển (đó là lý do tại sao trước đây chúng ta có thể lập chỉ mục vào kết quả đó mà không gặp bất kỳ vấn đề gì), nhưng với các phương thức bổ sung hầu hết được sử dụng bởi các trình tokenize nhanh. + +Bên cạnh khả năng song song hóa của chúng, chức năng chính của các trình tokenize nhanh là chúng luôn theo dõi khoảng văn bản ban đầu mà ta tokenize - một tính năng được gọi là *offset mapping* hay *ánh xạ bù trừ*. Điều này lần lượt mở khóa các tính năng như ánh xạ từng từ với token mà nó tạo ra hoặc ánh xạ từng ký tự của văn bản gốc với token bên trong và ngược lại. + +Cùng xem một mẫu: + +```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)) +``` + +Như đã đề cập trước đây, chúng ta nhận được một đối tượng `BatchEncoding` trong đầu ra của trình tokenize: + +```python out + +``` + +Vì lớp `AutoTokenizer` chọn một trình tokenizer nhanh theo mặc định, chúng ta có thể sử dụng các phương thức bổ sung mà đối tượng `BatchEncoding` này cung cấp. Chúng ta có hai cách để kiểm tra xem trình tokenize là nhanh hay chậm. Chúng ta có thể kiểm tra bằng thuộc tính `is_fast` của `tokenizer`: + +```python +tokenizer.is_fast +``` + +```python out +True +``` + +hoặc kiểm tra cùng thuộc tính đó của `encoding`: + +```python +encoding.is_fast +``` + +```python out +True +``` + +Hãy xem những gì một tokenizer nhanh cho phép chúng ta làm. Đầu tiên, chúng tôi có thể truy cập token mà không cần phải chuyển đổi ID trở lại token: + +```py +encoding.tokens() +``` + +```python out +['[CLS]', 'My', 'name', 'is', 'S', '##yl', '##va', '##in', 'and', 'I', 'work', 'at', 'Hu', '##gging', 'Face', 'in', + 'Brooklyn', '.', '[SEP]'] +``` + +Trong trường hợp này, token ở chỉ mục 5 là `##yl`, là một phần của từ "Sylvain" trong câu gốc. Chúng ta cũng có thể sử dụng phương thức `word_ids()` để lấy chỉ mục của từ mà mỗi token đến từ: + +```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] +``` + +Chúng ta có thể thấy rằng các token đặc biệt của trình tokenize như `[CLS]` và `[SEP]` được ánh xạ thành `None`, và sau đó mỗi token được ánh xạ tới từ mà nó bắt nguồn. Điều này đặc biệt hữu ích để xác định xem một token nằm ở đầu một từ hay nếu hai token có trong cùng thuộc một từ. Chúng ta có thể dựa vào tiền tố `##` cho điều đó, nhưng nó chỉ hoạt động đối với các tokenize kiểu BERT; phương pháp này hoạt động với bất kỳ loại tokenizer nào miễn nó là phiên bản nhanh. Trong chương tiếp theo, chúng ta sẽ xem cách chúng ta có thể sử dụng khả năng này để áp dụng nhãn chúng ta có cho mỗi từ đúng cách với các token trong các tác vụ như nhận dạng thực thể được đặt tên (NER) và gán nhãn từ loại (POS). Chúng ta cũng có thể sử dụng nó để che giấu tất cả các token đến từ cùng một từ trong mô hình ngôn ngữ được che (một kỹ thuật được gọi là _whole word masking_). + + + +Khái niệm về một từ rất là phức tạp. Ví dụ: "I'll" (từ rút gọn của "I will") có được tính là một hay hai từ? Nó thực sự phụ thuộc vào trình tokenize và hoạt động tiền tokenize mà nó áp dụng. Một số tokenizer chỉ tách ra trên khoảng trắng, vì vậy họ sẽ coi đây là một từ. Những người khác sử dụng dấu câu ở đầu khoảng trắng, vì vậy sẽ coi nó là hai từ. + +✏️ **Thử nghiệm thôi!** Tạo tokenizer từ các checkpoints `bert-base-cased` và` roberta-base` và tokenize "81s" với chúng. Bạn quan sát thấy gì? ID từ là gì? + + + +Tương tự, có một phương thức `question_ids()` mà chúng ta có thể sử dụng để ánh xạ token đến câu mà nó bắt nguồn (mặc dù trong trường hợp này, `token_type_ids` được trả về bởi tokenizer có thể cung cấp cho chúng ta cùng một thông tin). + +Cuối cùng, chúng ta có thể ánh xạ bất kỳ từ hoặc token nào với các ký tự trong văn bản gốc và ngược lại, thông qua các phương thức `word_to_chars()` hoặc `token_to_chars()` và `char_to_word()` hoặc `char_to_token()`. Ví dụ: phương thức `word_ids()` cho chúng ta biết rằng `##yl` là một phần của từ ở chỉ mục 3, nhưng từ đó nằm trong câu nào? Chúng ta có thể tìm hiểu như thế này: + +```py +start, end = encoding.word_to_chars(3) +example[start:end] +``` + +```python out +Sylvain +``` + +Như đã đề cập trước đó, tất cả điều này thực tế được hỗ trợ bởi là trình tokenizer nhanh kết hợp khoảng văn bản mà mỗi token đến từ danh sách *offset* hay *offset*. Để minh họa việc sử dụng chúng, tiếp theo, chúng tôi sẽ hướng dẫn bạn cách sao chép các kết quả của `token-classification` theo cách thủ công. + + + +✏️ **Thử nghiệm thôi** Tạo văn bản mẫu của riêng bạn và xem liệu bạn có thể hiểu những token nào được liên kết với ID từ, cũng như cách trích xuất ký tự kéo dài cho một từ. Để có điểm thưởng, hãy thử sử dụng hai câu làm đầu vào và xem liệu ID câu có phù hợp với bạn không. + + + +## Bên trong pipeline `token-classification` + +Trong [Chương 1](/course/chapter1), chúng ta lần đầu được thử áp dụng NER - tác vụ xác định những phần nào của văn bản tương ứng với các thực thể như người, địa điểm hoặc tổ chức - với `pipeline()` của 🤗 Transformers. Sau đó, trong [Chương 2](/course/chapter2), chúng ta đã thấy cách một pipeline nhóm ba giai đoạn cần thiết lại với nhau để nhận các dự đoán từ một văn bản thô: tokenize, chuyển các đầu vào qua mô hình và hậu xử lý. Hai bước đầu tiên trong quy trình `token-classification` cũng giống như trong bất kỳ quy trình nào khác, nhưng quá trình hậu xử lý phức tạp hơn một chút - hãy cùng xem cách thực hiện! + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +### Nhận kết quả cơ sở với baseline + +Trước tiên, hãy lấy một pipeline phân loại token chúng ta có thể nhận được một số kết quả để so sánh theo cách thủ công. Mô hình được sử dụng theo mặc định là [`dbmdz/bert-large-cased-finetuned-conll03-english`](https://huggingface.co/dbmdz/bert-large-cased-finetuned-conll03-english); nó thực hiện NER trên các câu: + +```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}] +``` + +Mô hình đã xác định đúng mỗi token do "Sylvain" tạo ra là một người, mỗi token được tạo bởi "Hugging Face" là một tổ chức và token "Brooklyn" là một địa điểm. Chúng ta cũng có thể yêu cầu pipeline nhóm các token tương ứng với cùng một thực thể lại với nhau: + +```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` được chọn sẽ thay đổi điểm được tính cho mỗi thực thể được nhóm lại, Với `"simple"`, điểm chỉ là giá trị trung bình của điểm của mỗi token trong thực thể đã cho: ví dụ: điểm của "Sylvain" là trung bình điểm mà chúng ta đã thấy trong ví dụ trước cho các token `S` , `##yl`,`## va` và `##in`. Các chiến lược có sẵn khác là: + +- `"first"`, trong đó điểm của mỗi thực thể là điểm của token đầu tiên của thực thể đó (vì vậy đối với "Sylvain", nó sẽ là 0.993828, điểm của token `S`) +- `"max"`, trong đó điểm của mỗi thực thể là điểm tối đa của các token trong thực thế đó (vì vậy với "Hugging Face" sẽ là 0.98879766, điểm của "Face") +- `"average"`, trong đó điểm của mỗi thực thể là điểm trung bình của các từ tạo nên thực thể đó (ví dụ với "Sylvain" thì phương pháp này sẽ không có sự khác biệt so với phương pháp `"simple"`, nhưng với "Hugging Face", điểm trả về sẽ là 0.9819, điểm trung bình của "Hugging", 0.975, và "Face", 0.98879) + +Giờ chúng ta hãy xem làm thế nào để có được những kết quả này mà không cần sử dụng hàm`pipeline()`! + +### Từ đầu vào tới dự đoán + +{#if fw === 'pt'} + +Đầu tiên chúng ta cần tokenize đầu vào của chúng ta và truyền chúng vào mô hình. Đây chính xác là những gì ta đã làm ở [Chương 2](/course/chapter2); ta khởi tạo tokenizer và mô hình sử dụng lớp `AutoXxx` và sau đó dùng chúng vào các mẫu của mình: + +```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) +``` + +Vì chúng ta sử dụng `AutoModelForTokenClassification` ở đây,ta sẽ nhận được tập hợp các logits cho từng token của chuỗi đầu vào: + +```py +print(inputs["input_ids"].shape) +print(outputs.logits.shape) +``` + +```python out +torch.Size([1, 19]) +torch.Size([1, 19, 9]) +``` + +{:else} + +Đầu tiên chúng ta cần tokenize đầu vào của chúng ta và truyền chúng vào mô hình. Đây chính xác là những gì ta đã làm ở [Chương 2](/course/chapter2); ta khởi tạo tokenizer và mô hình sử dụng lớp `TFAutoXxx` và sau đó dùng chúng vào các mẫu của mình: + +```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) +``` + +Vì ta dùng `TFAutoModelForTokenClassification` ở đây, ta sẽ nhận được tập hợp các logits cho từng token của chuỗi đầu vào: + +```py +print(inputs["input_ids"].shape) +print(outputs.logits.shape) +``` + +```python out +(1, 19) +(1, 19, 9) +``` + +{/if} + +Chúng ta có một lô với 1 chuỗi gồm 19 token và mô hình có 9 nhãn khác nhau, vì vậy đầu ra của mô hình có hình dạng 1 x 19 x 9. Giống như đối với pipeline phân loại văn bản, chúng ta sử dụng hàm softmax để chuyển đổi các logits đó theo xác suất, và chúng ta lấy argmax để nhận dự đoán (lưu ý rằng ta có thể lấy argmax trên logits vì softmax không thay đổi thứ tự): + +{#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] +``` + +Thuộc tính `model.config.id2label` chứa ánh xạ các chỉ mục tới các nhãn mà chúng ta có thể sử dụng để hiểu các dự đoán: + +```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'} +``` + +Như chúng ta đã thấy trước đó, có 9 nhãn: `O` là nhãn cho các token không nằm trong bất kỳ thực thể được đặt tên nào (nó là viết tắt của "outside" hay "bên ngoài") và sau đó chúng ta có hai nhãn cho mỗi loại thực thể (linh tinh, người , tổ chức và vị trí). Nhãn `B-XXX` cho biết token nằm ở đầu thực thể `XXX` và nhãn `I-XXX` cho biết token nằm bên trong thực thể `XXX`. Ví dụ: trong mẫu hiện tại, chúng ta kì vọng mô hình phân loại token `S` là `B-PER` (bắt đầu của một thực thể người) và các token `##yl`,`##va` và `##in` là `I-PER` (bên trong một thực thể người). + +Bạn có thể nghĩ rằng mô hình đã sai trong trường hợp này vì nó đã gắn nhãn `I-PER` cho cả bốn token này, nhưng điều đó không hoàn toàn đúng. Thực tế có hai định dạng cho các nhãn `B-` và `I-` đó là: *IOB1* và *IOB2*. Định dạng IOB2 (màu hồng bên dưới), là định dạng chúng ta đã giới thiệu trong khi ở định dạng IOB1 (màu xanh lam), các nhãn bắt đầu bằng `B-` chỉ được sử dụng để phân tách hai thực thể liền kề cùng loại. Mô hình chúng tôi đang sử dụng đã được tinh chỉnh trên tập dữ liệu bằng cách sử dụng định dạng đó, đó là lý do tại sao nó gán nhãn `I-PER` cho mã thông báo `S`. + +
+IOB1 vs IOB2 format + +
+ +Với phép ánh xạ này, chúng ta đã sẵn sàng đề tái tạo lại (gần như hoàn toàn) kết quả của pipeline đầu -- ta chỉ cần lấy điểm và nhãn của mỗi token mà không được phân vào `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'}] +``` + +Điều này rất giống với những gì ta đã có trước đây, ngoại trừ một ngoại lệ: pipeline cũng cung cấp thông tin về điểm `start` hay `bắt đầu` và `end` hay `kết thúc` của mỗi thực thể trong câu gốc. Đây là lúc ánh xạ bù trừ của chúng ta sẽ phát huy tác dụng. Để có được offset, chúng ta chỉ cần đặt `return_offsets_mapping=True` khi chúng ta áp dụng tokenizer cho các đầu vào của mình: + +```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)] +``` + +Mỗi tuple là khoảng văn bản tương ứng với mỗi token, trong đó `(0, 0)` được dành riêng cho các token đặc biệt. Trước đây, chúng ta đã thấy rằng token ở chỉ mục 5 là `##yl`, có `(12, 14)` là các phần bù ở đây. Nếu chúng ta lấy phần tương ứng trong mẫu của mình: + + +```py +example[12:14] +``` + +ta nhận được khoảng văn bản thích hợp mà không có `##`: + +```python out +yl +``` + +Sử dụng điều này, bây giờ chúng ta có thể hoàn thành các kết quả trước đó: + +```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}] +``` + +Đây giống như những gì chúng ta có được từ pipeline đầu tiên! + +### Nhóm các thực thể + +Sử dụng các offset để xác định điểm bắt đầu và kết thúc cho mỗi thực thể là rất tiện dụng, nhưng thông tin đó không hoàn toàn cần thiết. Tuy nhiên, khi chúng ta muốn nhóm các thực thể lại với nhau, việc offset sẽ giúp chúng ta tiết kiệm rất nhiều đoạn mã lộn xộn. Ví dụ: nếu chúng ta muốn nhóm các token `Hu`, `##gging` và `Face` lại với nhau, chúng ta có thể đưa ra các quy tắc đặc biệt nói rằng hai token đầu tiên phải được đính kèm trong khi xóa dấu `##` và `Face` nên được thêm một khoảng trắng vì nó không bắt đầu bằng `##` - nhưng điều đó sẽ chỉ hoạt động đối với loại tokenizer cụ thể này. Chúng ta sẽ phải viết một bộ quy tắc khác cho SentencePiece hoặc Byte-Pair-Encoding (sẽ được thảo luận ở phần sau của chương này). + +Với offset, tất cả mã tùy chỉnh đó sẽ biến mất: chúng ta chỉ có thể lấy khoảng trong văn bản gốc bắt đầu bằng token đầu tiên và kết thúc bằng token cuối cùng. Vì vậy, trong trường hợp các mã thông báo `Hu`, `##gging` và `Face`, chúng ta nên bắt đầu ở ký tự 33 (đầu của `Hu`) và kết thúc trước ký tự 45 (cuối của `Face`) : + +```py +example[33:45] +``` + +```python out +Hugging Face +``` + +Để viết đoạn mã hậu xử lý các dự đoán trong khi nhóm các thực thể, ta sẽ nhóm các thực thể liên tiếp và có nhãn `I-XXX` với nhau trừ khi nó là từ đầu tiên, được gán nhãn `B-XXX` hoặc `I-XXX` (để ta có thể dừng nhóm một thực thể khi nhận được `O`, một kiểu thực thể mới, hoặc một `B-XXX` cho ta biết thực thể có kiểu giống với điểm bắt đầu): + +```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": + # Xoá B- hoặc I- + label = label[2:] + start, _ = offsets[idx] + + # Lấy tất cả các tokens có nhãn 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 + + # Điểm là giá trị trung bình của tất cả điểm của các token trong thực thể được nhóm đó + 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) +``` + +Và chúng ta nhận được kết quả tương tự như với pipeline thứ hai của mình! + +```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}] +``` + +Một ví dụ khác về tác vụ mà những offset này cực kỳ hữu ích là hỏi đáp. Đào sâu vào pipeline này, chúng ta sẽ thực hiện trong phần tiếp theo, cũng sẽ cho phép chúng ta xem xét một tính năng cuối cùng của các tokenizers trong thư viện 🤗 Transformers: xử lý các token tràn khi chúng ta cắt bớt một đầu vào đến một độ dài nhất định. diff --git a/chapters/vi/chapter6/3b.mdx b/chapters/vi/chapter6/3b.mdx new file mode 100644 index 000000000..ad2cf1c98 --- /dev/null +++ b/chapters/vi/chapter6/3b.mdx @@ -0,0 +1,642 @@ + + +# Fast tokenizers in the QA pipeline + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Giờ chúng ta sẽ đi sâu vào pipeline `question-answering` và xem cách tận dụng các offset để lấy câu trả lời cho các câu hỏi dựa theo từ ngữ cảnh, giống như chúng ta đã làm với các thực thể được nhóm trong phần trước. Sau đó, chúng ta sẽ xem làm thế nào có thể đối phó với những ngữ cảnh rất dài mà cuối cùng lại bị cắt bớt. Bạn có thể bỏ qua phần này nếu không quan tâm đến tác vụ hỏi đáp. + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +## Sử dụng pipeline `question-answering` + +Như đã thấy trong [Chương 1](/course/chapter1), ta có thể sử dụng pipeline `question-answering` như sau để nhận được câu trả lời cho câu hỏi: + +```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'} +``` + +Không như các pipeline khác không thể cắt gọn và chia văn bản dài hơn độ dài tối đa cho phép của mô hình (dẫn đến bỏ lỡ những thông tin ở phần cuối văn bản), pipeline này có thể xử lý tốt với những ngữ cảnh dài và sẽ trả về câu trả lời kể cả khi nó nằm ở cuối văn bản: + +```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'} +``` + +Hãy cùng nhau xem nó làm thế nào! + +## Sử dụng mô hình cho tác vụ hỏi đáp + +Như những pipeline khác, ta sẽ bắt đầu với việc tokenize đầu vào và sau đó truyền chúng vào trong mô hình. Mặc định checkpoint được sử dụng cho pipeline `question-answering` là [`distilbert-base-cased-distilled-squad`](https://huggingface.co/distilbert-base-cased-distilled-squad) ( "squad" trong tên bắt nguồn từ bộ dữ liệu mà mô hình sử dụng để tinh chỉnh; ta sẽ nói sâu hơn về bộ dữ liệu SQuAD này ở [Chương 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} + +Lưu ý rằng chúng ta tokenize câu hỏi và ngữ cảnh như một cặp, với câu hỏi đứng trước. + +
+An example of tokenization of question and context + +
+ +Các mô hình hỏi đáp hoạt động hơi khác so với các mô hình mà ta đã thấy cho đến nay. Sử dụng hình trên làm ví dụ, mô hình đã được huấn luyện để dự đoán chỉ mục của token bắt đầu câu trả lời (ở đây là 21) và chỉ mục của token nơi câu trả lời kết thúc (ở đây là 24). Đây là lý do tại sao các mô hình đó không trả về một tensor logit mà là hai: một cho các logit tương ứng với token bắt đầu của câu trả lời và một cho các các logit tương ứng với token kết thúc của câu trả lời. Vì trong trường hợp này, chúng ta chỉ có một đầu vào chứa 66 token, ta nhận được: + +```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} + +Để chuyển đổi các logit đó thành xác suất, chúng ta sẽ áp dụng một hàm softmax - nhưng trước đó, chúng ta cần đảm bảo rằng chúng ta che dấu các chỉ mục không phải là một phần của ngữ cảnh. Đầu vào của chúng tôi là `[CLS] question [SEP] context [SEP]`, vì vậy chúng ta cần che dấu các token của câu hỏi cũng như token `[SEP]`. Tuy nhiên, chúng ta sẽ giữ token `[CLS]` vì một số mô hình sử dụng nó để chỉ ra rằng câu trả lời không nằm trong ngữ cảnh. + +Vì chúng ta sẽ áp dụng softmax sau đó, chúng ta chỉ cần thay thế các logit muốn che bằng một số âm lớn. Ở đây, chúng ta sử dụng `-10000`: + +{#if fw === 'pt'} + +```py +import torch + +sequence_ids = inputs.sequence_ids() +# Che tất cả mọi thứ trừ token của ngữ cảnh +mask = [i != 1 for i in sequence_ids] +# Hiển thị token [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() +# Che tất cả mọi thứ trừ token của ngữ cảnh +mask = [i != 1 for i in sequence_ids] +# Hiển thị token [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} + +Giờ chúng ta đã che các logit tương ứng với các vị trí mà chúng ta không muốn dự đoán, chúng ta có thể áp dụng 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} + +Ở giai đoạn này, chúng ta có thể lấy argmax xác suất bắt đầu và kết thúc - nhưng chúng ta có thể kết thúc với chỉ mục bắt đầu lớn hơn kết thúc, vì vậy chúng ta cần thực hiện thêm một số biện pháp phòng ngừa. Chúng ta sẽ tính toán xác suất của từng `start_index` và `end_index` có thể trong đó `start_index <= end_index`, sau đó lấy `(start_index, end_index)` với xác suất cao nhất. + +Giả sử các sự kiện "Câu trả lời bắt đầu ở `start_index`" và "Câu trả lời kết thúc ở `end_index`" là độc lập, xác suất để câu trả lời bắt đầu tại `start_index` và kết thúc tại `end_index` là: + +$$\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]$$ + +Vì vậy, để tính tất cả các điểm, chúng ta chỉ cần tính tích \\(\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]\\) với `start_index <= end_index`. + +Đầu tiên, hãy tính toán tất cả các đầu ra có thể có: + +```py +scores = start_probabilities[:, None] * end_probabilities[None, :] +``` + +{#if fw === 'pt'} + +Sau đó, chúng tôi sẽ che các giá trị trong đó `start_index > end_index` bằng cách đặt chúng thành `0` (các xác suất khác đều là số dương). Hàm `torch.triu()` trả về phần tam giác phía trên của tensor 2D được truyền dưới dạng tham số, vì vậy nó sẽ thực hiện việc che đó cho chúng ta: + +```py +scores = torch.triu(scores) +``` + +{:else} + +Sau đó, chúng tôi sẽ che các giá trị trong đó `start_index > end_index` bằng cách đặt chúng thành `0` (các xác suất khác đều là số dương). Hàm `np.triu()` trả về phần tam giác phía trên của tensor 2D được truyền dưới dạng tham số, vì vậy nó sẽ thực hiện việc che đó cho chúng ta: + +```py +import numpy as np + +scores = np.triu(scores) +``` + +{/if} + +Bây giờ chúng ta chỉ cần lấy chỉ mục tối đa. Vì PyTorch sẽ trả về chỉ mục trong tensor phẳng, chúng ta cần sử dụng phép chia làm tròn xuống `//` và lấy dư `%` để nhận được `start_index` và `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]) +``` + +Chúng ta chưa xong đâu, nhưng ít nhất chúng ta đã có điểm chính xác cho câu trả lời (bạn có thể kiểm tra điều này bằng cách so sánh nó với kết quả đầu tiên trong phần trước): + +```python out +0.97773 +``` + + + +✏️ **Thử nghiệm thôi!** Tính chỉ mục bắt đầu và kết thúc cho năm cấu trả lời đầu tiện. + + + +Ta có `start_index` và `end_index` của câu trả lời theo token nên ta chỉ cần chuyển đổi các chỉ mục kí tự trong ngữ cảnh. Đấy là nơi offset sẽ cực kì hữu ích. Ta có thể lấy và sử dụng chúng như cách ta làm trong tác vụ phân loại token: + +```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] +``` + +Bây giờ chúng ta chỉ cần định dạng mọi thứ để có được kết quả: + +```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} +``` + +Tuyệt quá! Kết quả đó giống như trong ví dụ đầu tiên của chúng ta! + + + +✏️ **Thử nghiệm thôi!** Sử dụng điểm tốt nhất mà bạn đã tính toán trước đó để hiển thị năm câu trả lời có khả năng nhất. Để kiểm tra kết quả của bạn, hãy quay lại đường dẫn đầu tiên và truyền vào `top_k=5` khi gọi nó. + + + +## Xử lý các ngữ cảnh dài + +Nếu chúng ta cố gắng tokenize các câu hỏi và ngữ cảnh dài ta từng lấy làm ví dụ trước đó, ta sẽ nhận được số token nhiều hơn độ dài tối da sử dụng trong pipeline `question-answering` (đó là 384): + +```py +inputs = tokenizer(question, long_context) +print(len(inputs["input_ids"])) +``` + +```python out +461 +``` + +Vì vậy, chúng ta sẽ cần phải cắt bớt đầu vào của mình ở độ dài tối đa đó. Có một số cách ta có thể làm điều này, nhưng chúng ta không muốn cắt ngắn câu hỏi, chỉ cắt bỏ ngữ cảnh. Vì ngữ cảnh là câu thứ hai, chúng ta sẽ sử dụng chiến lược cắt ngắn `"only_second"`. Vấn đề nảy sinh sau đó là câu trả lời cho câu hỏi có thể không nằm trong ngữ cảnh đã bị cắt ngắn. Ví dụ: ở đây, chúng ta đã chọn một câu hỏi trong đó câu trả lời nằm ở cuối ngữ cảnh và khi cắt ngắn câu trả lời đó thì câu trả lời không còn: + +```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] +""" +``` + +Điều này có nghĩa là mô hình sẽ gặp khó khăn trong việc chọn ra câu trả lời chính xác. Để khắc phục điều này, pipeline hỏi đáp cho phép chúng ta chia ngữ cảnh thành các phần nhỏ hơn, chỉ định độ dài tối đa. Để đảm bảo rằng chúng ta không chia bối cảnh chính xác ở vị trí sai để có thể tìm ra câu trả lời, nó cũng bao gồm một số phần trùng lặp giữa các phần. + +Chúng ta có thể yêu cầu tokenizer (nhanh hoặc chậm) thực hiện việc này bằng cách thêm `return_overflowing_tokens=True` và ta có thể chỉ định sự giao thoa mà ta muốn qua than số `stride`. Đây là một ví dụ, sử dụng một câu nhỏ hơn: + +```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]' +``` + +Có thể thấy, câu đã bị chia thành các đoạn sao cho mỗi phần trong `inputs["input_ids"]` có nhiều nhất 6 token (ta sẽ cần thêm đệm để đảm bảo chúng có cùng kích thước) và sẽ có sử giao thoa của 2 token giữa các phần. + +Hãy cùng nhìn kĩ hơn vào kết quả tokenize: + +```py +print(inputs.keys()) +``` + +```python out +dict_keys(['input_ids', 'attention_mask', 'overflow_to_sample_mapping']) +``` + +Như dự đoán, ta nhận được ID đầu vào và attention mask.Ở đây, `overflow_to_sample_mapping` là một phép ánh xạ cho ta biết câu nào trong kết quả liên quan -- ta có 7 kết quả dều từ câu mà ta truyền vào tokenizer: + +```py +print(inputs["overflow_to_sample_mapping"]) +``` + +```python out +[0, 0, 0, 0, 0, 0, 0] +``` + +Điều này hữu ích hơn khi ta tokenize nhiều câu cùng nhau, Ví dụ: + +```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"]) +``` + +trả cho ta: + +```python out +[0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1] +``` + +nghĩa là câu đầu tiên được chia thành 7 đoạn như phần phía trước, và 4 đoạn tiếp theo đến từ câu thứ hai. + +Bây giờ chúng ta hãy cùng quay trở lại ngữ cảnh dài. Theo mặc định, pipeline ``question-answering` sử dụng độ dài tối đa là 384, như đã đề cập trước đó và khoảng cách 128, tương ứng với cách mô hình được tinh chỉnh (bạn có thể điều chỉnh các tham số đó bằng cách truyền `max_seq_len` và `stride` khi gọi pipeline). Do đó, chúng ta sẽ sử dụng các tham số đó khi tokenize. Chúng ta cũng sẽ thêm phần đệm (để có các mẫu có cùng chiều dài, vì vậy chúng ta có thể tạo ra các tensor) cũng như yêu cầu các offset: + +```py +inputs = tokenizer( + question, + long_context, + stride=128, + max_length=384, + padding="longest", + truncation="only_second", + return_overflowing_tokens=True, + return_offsets_mapping=True, +) +``` + +Các `inputs` sẽ chứa các ID đầu vào và các attention mask mà mô hình kì vọng, cũng như offset và `overflow_to_sample_mapping` ta vừa trao đổi ở trên. Vì hai tham số đó không phải là tham số được sử dụng bởi mô hình, chúng ta sẽ đưa chúng ra khỏi `inputs` (và không lưu trữ ánh xạ, vì nó không hữu ích ở đây) trước khi chuyển đổi nó thành 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} + +Bối cảnh dài của chúng ta được chia làm hai, đồng nghĩa sau khi nó đi qua mô hình, chúng ta sẽ có hai bộ logit bắt đầu và kết thúc: + +```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} + +Giống như trước đây, đầu tiên chúng ta che các token không phải là một phần của ngữ cảnh trước khi sử dụng softmax. Chúng ta cũng che tất cả các token đệm (được gắn mác bởi attention mask): + +{#if fw === 'pt'} + +```py +sequence_ids = inputs.sequence_ids() +# Che tất cả mọi thứ trừ token của ngữ cảnh +mask = [i != 1 for i in sequence_ids] +# Hiển thị token [CLS] +mask[0] = False +# Che tất cả token [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() +# Che tất cả mọi thứ trừ token của ngữ cảnh +mask = [i != 1 for i in sequence_ids] +# Hiển thị token [CLS] +mask[0] = False +# Che tất cả token [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} + +Sau đó, chúng ta có thể sử dụng softmax để chuyển đổi các logit của chúng ta thành xác suất: + +{#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} + +Bước tiếp theo tương tự như những gì chúng ta đã làm cho bối cảnh nhỏ, nhưng chúng ta lặp lại nó cho mỗi phần trong hai phần của mình. Chúng ta tính điểm cho tất cả các khoảng câu trả lời có thể có, sau đó lấy phần có điểm tốt nhất: + +{#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[0] + end_idx = idx % scores.shape[0] + 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[0] + end_idx = idx % scores.shape[0] + 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)] +``` + +Hai ứng cử viên đó tương ứng với các câu trả lời tốt nhất mà mô hình có thể tìm thấy trong mỗi đoạn. Mô hình chắc chắn hơn rằng câu trả lời đúng nằm ở phần thứ hai (đó là một dấu hiệu tốt!). Bây giờ chúng ta chỉ cần ánh xạ khoảng hai token đó với khoảng các ký tự trong ngữ cảnh (chúng ta chỉ cần lập ánh xạ cái thứ hai để có câu trả lời, nhưng thật thú vị khi xem mô hình đã chọn những gì trong đoạn đầu tiên). + + + +✏️ **Thử nghiệm thôi!** Hãy điều chỉnh đoạn mã trên để trả về điểm và khoảng cho năm câu trả lời có nhiều khả năng nhất (tổng cộng, không phải cho mỗi đoạn). + + + +`offsets` mà chúng ta đã nắm được trước đó thực sự là một danh sách các offset, với một danh sách trên mỗi đoạn văn bản: + +```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} +``` + +Nếu chúng ta bỏ qua kết quả đầu tiên, chúng ta sẽ nhận được kết quả tương tự như pipeline cho ngữ cảnh dài này - yayy! + + + +✏️ **Thử nghiệm thôi!** Sử dụng điểm tốt nhất bạn đã tính toán trước đó để hiển thị năm câu trả lời có khả năng xảy ra nhất (cho toàn bộ ngữ cảnh, không phải từng đoạn). Để kiểm tra kết quả của bạn, hãy quay lại pipeline đầu tiên và truyền vào `top_k=5` khi gọi nó. + + + +Điều này kết thúc phần đi sâu vào các khả năng của tokenizer. Chúng ta sẽ đưa tất cả những điều này vào thực tế một lần nữa trong chương tiếp theo, khi chúng tôi hướng dẫn bạn cách tinh chỉnh một mô hình về một loạt các tác vụ NLP phổ biến. diff --git a/chapters/vi/chapter6/4.mdx b/chapters/vi/chapter6/4.mdx new file mode 100644 index 000000000..e99921f29 --- /dev/null +++ b/chapters/vi/chapter6/4.mdx @@ -0,0 +1,122 @@ +# Chuẩn hoá và tiền tokenize + + + +Trước khi đi sâu hơn vào ba thuật toán tokenize từ phụ phổ biến nhất được sử dụng với các mô hình Transformer (Mã hóa theo cặp [BPE], WordPiece và Unigram), trước tiên chúng ta sẽ xem xét tiền xử lý mà mỗi trình tokenize áp dụng cho văn bản. Dưới đây là tổng quan cấp cao về các bước trong pipeline tokenize: + +
+The tokenization pipeline. + +
+ +Trước khi tách một đoạn văn bản thành các token phụ (dựa theo mô hình)tokenizer sẽ thực hiện 2 bước: _normalization_ (chuẩn hoá) và _pre-tokenization_ (tiền tokenize). + +## Chuẩn hoá + + + +Bước chuẩn hóa bao gồm một số thao tác dọn dẹp, chẳng hạn như loại bỏ khoảng trắng không cần thiết, viết thường tất cả các chữ, và/hoặc xóa dấu. Nếu bạn đã quen với [chuẩn hóa Unicode](http://www.unicode.org/reports/tr15/) (chẳng hạn như NFC hoặc NFKC), thì đây cũng là điều mà tokenizer có thể áp dụng. + +`tokenizer` của 🤗 Transformers có một thuộc tính gọi là `backend_tokenizer` cung cấp quyền truy cập vào tokenizer bên dưới từ thư viện 🤗 Tokenizers: + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased") +print(type(tokenizer.backend_tokenizer)) +``` + +```python out + +``` + +Thuộc tính `normalizer` của đối tượng `tokenizer` có phương thức `normalize_str()` mà ta có thể dùng để thấy cách bước chuẩn hoá được thực hiện: + +```py +print(tokenizer.backend_tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) +``` + +```python out +'hello how are u?' +``` + +Trong ví dụ này, vì chúng ta chọn checkpoint `bert-base-uncased`, bước chuẩn hoá sẽ thực hiện viết thường và loại bỏ các dấu. + + + +✏️ **Try it out!** Tải tokenizer từ checkpoint `bert-base-cased` và truyền vào cùng một ví dụ vào.Sự khác biệt chính mà bạn có thể thấy giữa các phiên bản có dấu và không dấu của tokenizer là gì? + + + +## Pre-tokenization + + + +Như chúng ta sẽ thấy trong các phần tiếp theo, một tokenizer không thể được huấn luyện trên văn bản thô. Thay vào đó, trước tiên chúng ta cần chia các văn bản thành các thực thể nhỏ, như các từ. Đó là khi bước pre-tokenization bắt đầu. Như chúng ta đã thấy trong [Chương 2](/course/chapter2), trình tokenize dựa trên từ có thể chỉ cần tách một văn bản thô thành các từ dựa trên khoảng trắng và dấu câu. Những từ đó sẽ là ranh giới của các token con mà tokenizer có thể học được trong quá trình huấn luyện của nó. + +Để xem cách một tokenizer nhanh thực hiện pre-tokenization, chúng ta có thể sử dụng phương thức `pre_tokenize_str()` của thuộc tính `pre_tokenizer` của đối tượng `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))] +``` + +Lưu ý cách tokenizer đã theo dõi các offset, đó là cách nó có thể cung cấp cho chúng ta ánh xạ offset mà ta đã sử dụng trong phần trước. Ở đây tokenizer bỏ qua hai khoảng trắng và thay thế chúng bằng chỉ một, nhưng các offset xen giữa `are` và `you` để giải thích điều đó. + +Vì chúng ta đang sử dụng BERT tokenizer, pre-tokenization liên quan đến việc phân tách dựa trên khoảng trắng và dấu chấm câu. Các tokenizer khác có thể có các quy tắc khác nhau cho bước này. Ví dụ: nếu sử dụng GPT-2 tokenizer: + +```py +tokenizer = AutoTokenizer.from_pretrained("gpt2") +tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") +``` + +nó sẽ tách dựa trên dấu cách và dấu câu, nhưng sẽ giữa dấu cách và thay thế chúng bởi kí hiệu `Ġ`, cho phép nó khôi phục không gian ban đầu nếu chúng tôi giải mã các token: + +```python out +[('Hello', (0, 5)), (',', (5, 6)), ('Ġhow', (6, 10)), ('Ġare', (10, 14)), ('Ġ', (14, 15)), ('Ġyou', (15, 19)), + ('?', (19, 20))] +``` + +Cần lưu ý thêm rằng không như BERT tokenizer, tokenizer này bỏ qua dấu cách kép. + +Ở ví dụ cuối, hãy cùng xem T5 tokenizer dựa trên thuật toán 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))] +``` + +Giống như GPT-2 tokenizer, phương pháp này giữ các dấu cách và thay thế chúng bởi một tí tự đặc biệt (`_`), nhưng T5 tokenizer chỉ tách dựa theo dấu cách, không dựa theo dấu câu. Một lưu ý nữa đó là nó cũng mặc định thêm dấu cách ở phía đầu câu (trước `Hello`) và bỏ qua những dấu cách kẹp ở giữa `are` và `you`. + +Bây giờ chúng ta đã biết một chút về cách một số loại tokenizers khác nhau để xử lý văn bản, chúng ta có thể bắt đầu tự khám phá các thuật toán cơ bản. Chúng ta sẽ bắt đầu bằng một cái nhìn nhanh về SentencePiece được áp dụng rộng rãi; sau đó, trong ba phần tiếp theo, chúng ta sẽ xem xét cách thức hoạt động của ba thuật toán chính được sử dụng để mã hóa từ phụ. + +## SentencePiece + +[SentencePiece](https://github.com/google/sentencepiece) là một thuật toán tokenize để tiền xử lý văn bản mà bạn có thể sử dụng với bất kỳ mô hình nào chúng ta sẽ thấy trong ba phần tiếp theo. Nó coi văn bản là một chuỗi các ký tự Unicode và thay thế dấu cách bằng một ký tự đặc biệt, `▁`. Được sử dụng cùng với thuật toán Unigram (xem [phần 7](/course/chapter7/7)), nó thậm chí không yêu cầu bước pre-tokenization, rất hữu ích cho các ngôn ngữ không sử dụng dấu cách (như Trung Quốc hoặc Nhật Bản). + +Tính năng chính khác của SentencePiece là *reversible tokenization* hay *tokenize có thể đảo ngược*: vì không có cách xử lý đặc biệt nào cho dấu cách, nên việc giải mã các token được thực hiện đơn giản bằng cách nối chúng và thay thế các dấu `_` bằng dấu cách - điều này giúp văn bản được chuẩn hóa. Như chúng ta đã thấy trước đó, BERT tokenizer loại bỏ các dấu cách lặp lại, vì vậy token của nó không thể đảo ngược. + +## Tổng quan thuật toán + +Trong các phần tiếp theo, chúng ta sẽ đi sâu vào ba thuật toán tokenize từ phụ tiêu biểu: BPE (được sử dụng bởi GPT-2 và các thuật toán khác), WordPiece (được sử dụng bởi BERT) và Unigram (được sử dụng bởi T5 và các thuật toán khác). Trước khi chúng ta bắt đầu, đây là tổng quan nhanh về cách hoạt động của từng loại. Đừng ngần ngại quay lại bảng này sau khi đọc từng phần tiếp theo nếu bạn chưa hiểu hết. + +Mô hình | BPE | WordPiece | Unigram +:----:|:---:|:---------:|:------: +Huấn luyện | Bắt đầu với một bộ từ vựng nhỏ và học bộ quy tắc hợp nhất token | Bắt đầu với một bộ từ vựng nhỏ và học bộ quy tắc hợp nhất token | Bắt đầu với một bộ từ vựng lớn và học bộ quy tắc để loại bỏ token +Bước huấn luyện | Gộp các token liên quan đến cặp phổ biến nhất | Gộp các token liên quan đến cặp có điểm cao nhất dựa trên tần suất của cặp, with the best score based on the frequency of the pair, ưu tiên các cặp mà mỗi token cá nhân tần suất thấp hơn| Loại bỏ tất cả các token trong bộ từ điển giảm thiểu tối đa độ mất mát được tính trên toàn bộ kho ngữ liệu +Học | Gộp bộ quy tắc và bộ từ vựng | Chỉ bộ từ vựng | Một bộ tự vựng với điểm cho mỗi token +Mã hoá | Chia từ thành các kí tự và áp dụng bước gộp từ quá trình huấn luyện | Tìm ra chuỗi từ phụ dài nhất bắt đầu từ phần bắt đầu có trong bộ từ vựng, sau đó làm tương tự với các phần còn lại của từ | Tìm từ có khả năng chia thành token cao nhất sử dụng điểm có được từ quá trình huấn luyện + +Giờ chúng ta hãy đi sâu vào BPE thôi! diff --git a/chapters/vi/chapter6/5.mdx b/chapters/vi/chapter6/5.mdx new file mode 100644 index 000000000..537ed387f --- /dev/null +++ b/chapters/vi/chapter6/5.mdx @@ -0,0 +1,361 @@ +# Byte-Pair Encoding tokenization + + + +Mã hóa theo cặp (BPE) tiền thân được phát triển như một thuật toán để nén văn bản, sau đó được OpenAI sử dụng để tokenize khi huấn luyện trước mô hình GPT. Nó được sử dụng bởi rất nhiều mô hình Transformer, bao gồm GPT, GPT-2, RoBERTa, BART và DeBERTa. + + + + + +💡 Phần này trình bày sâu hơn về BPE, đi xa hơn nữa là trình bày cách triển khai đầy đủ. Bạn có thể bỏ qua phần cuối nếu bạn chỉ muốn có một cái nhìn tổng quan chung về thuật toán tokenize. + + + +## Thuật toán huấn luyện + +Huấn luyện BPE bắt đầu bằng cách tính toán tập hợp các từ duy nhất được sử dụng trong kho ngữ liệu (sau khi hoàn thành các bước chuẩn hóa và pre-tokenization), sau đó xây dựng từ vựng bằng cách lấy tất cả các ký hiệu được sử dụng để viết những từ đó. Ví dụ rất đơn giản, giả sử kho dữ liệu của chúng ta sử dụng năm từ sau: + +``` +"hug", "pug", "pun", "bun", "hugs" +``` + +Từ vựng cơ sở khi đó sẽ là `["b", "g", "h", "n", "p", "s", "u"]`. Đối với các trường hợp trong thực tế, từ vựng cơ sở đó sẽ chứa tất cả các ký tự ASCII, ít nhất và có thể là một số ký tự Unicode. Nếu một mẫu bạn đang tokenize sử dụng một ký tự không có trong kho dữ liệu huấn luyện, thì ký tự đó sẽ được chuyển đổi thành token không xác định. Đó là một lý do tại sao nhiều mô hình NLP rất kém trong việc phân tích nội dung bằng biểu tượng cảm xúc. + + + +GPT-2 và RoBERTa tokenizer (khá giống nhau) có một cách thông minh để giải quyết vấn đề này: chúng không xem các từ được viết bằng các ký tự Unicode mà là các byte. Bằng cách này, từ vựng cơ sở có kích thước nhỏ (256), nhưng mọi ký tự bạn có thể nghĩ đến sẽ vẫn được bao gồm và không bị chuyển đổi thành token không xác định. Thủ thuật này được gọi là *BPE cấp byte*. + + + +Sau khi có được bộ từ vựng cơ bản này, chúng ta thêm các token mới cho đến khi đạt được kích thước từ vựng mong muốn bằng cách học *hợp nhất*, đây là các quy tắc để hợp nhất hai yếu tố của từ vựng hiện có với nhau thành một từ mới. Vì vậy, lúc đầu sự hợp nhất này sẽ tạo ra các token có hai ký tự và sau đó, khi quá trình huấn luyện tiến triển, các từ phụ sẽ dài hơn. + +Tại bất kỳ bước nào trong quá trình huấn luyện token, thuật toán BPE sẽ tìm kiếm cặp token hiện có thường xuyên nhất (theo "cặp", ở đây có nghĩa là hai token liên tiếp trong một từ). Cặp thường xuyên nhất đó là cặp sẽ được hợp nhất, và chúng ta xả và lặp lại cho bước tiếp theo. + +Quay trở lại ví dụ trước, giả sử các từ có tần số như sau: + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +nghĩa là `"hug"` có mặt 10 lần trong kho ngữ liệu, `"pug"` 5 lần, `"pun"` 12 lần, `"bun"` 4 lần và `"hug"` 5 lần. Chúng ta bắt đầu huấn luyện bằng cách tách từng từ thành các ký tự (những ký tự hình thành từ vựng ban đầu của chúng ta) để có thể xem mỗi từ như một danh sách các token: + +``` +("h" "u" "g", 10), ("p" "u" "g", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "u" "g" "s", 5) +``` + + +Sau đó, chúng ta xem xét các cặp. Cặp `("h", "u")` có trong các từ `"hug"` và `"hugs"`, vì vậy tổng cộng là 15 lần trong ngữ liệu. Tuy nhiên, đây không phải là cặp thường xuyên nhất: vinh dự đó thuộc về `("u", "g")`, có trong `"hug"`, `"pug"`, và `"hugs"`, với tổng cộng 20 lần xuất hiện trong bộ từ vựng. + +Do đó, quy tắc hợp nhất đầu tiên được học bởi tokenizer là `("u", "g") -> "ug"`, có nghĩa là `"ug"` sẽ được thêm vào từ vựng và cặp này sẽ được hợp nhất trong tất cả các từ của ngữ liệu. Vào cuối giai đoạn này, từ vựng và ngữ liệu sẽ giống như sau: + +``` +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) +``` + +Bây giờ chúng ta có một số cặp dẫn đến một token dài hơn hai ký tự: ví dụ: cặp `("h", "ug")`, (hiện diện 15 lần trong kho ngữ liệu). Cặp thường gặp nhất ở giai đoạn này là `("u", "n")`, xuất hiện 16 lần trong kho ngữ liệu, vì vậy quy tắc hợp nhất thứ hai đã học là `("u", "n") -> "un"`. Thêm nó vào bộ từ vựng và hợp nhất tất cả các lần xuất hiện hiện có sẽ dẫn chúng ta đến: + +``` +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) +``` + +Giờ thì cặp xuất hiện nhiều nhất là `("h", "ug")`, nên chúng ta hợp nhất `("h", "ug") -> "hug"`, trả về cho chúng ta token gồn ba kí tự đầu tiên. Sau sự hợp nhất này, kho ngữ liệu sẽ như sau: + +``` +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) +``` + +Và chúng ta tiếp túc làm vậy cho đến khi chúng ta chạm đến kích thước bộ tự điển ta mong muốn. + + + +✏️ **Giờ thì đến lượt bạn!** Bạn nghĩ bước hợp nhất tiếp theo sẽ là gì? + + + +## Thuật toán tokenize + +Tokenize tuân thủ chặt chẽ quá trình huấn luyện, theo nghĩa là các đầu vào mới được tokenize bằng cách áp dụng các bước sau: + +1. Chuẩn hoá +2. Pre-tokenization +3. Tách các từ thành các ký tự riêng lẻ +4. Áp dụng các quy tắc hợp nhất đã học theo thứ tự trên các phần tách đó + +Lấy ví dụ mà ta đã sử dụng trong quá trình huấn luyện, với ba quy tắc hợp nhất đã học: + +``` +("u", "g") -> "ug" +("u", "n") -> "un" +("h", "ug") -> "hug" +``` + +Từ `"bug"` sẽ được tokenize thành `["b", "ug"]`. `"mug"`, tuy nhiên, sẽ tokenize thành `["[UNK]", "ug"]` vì kí tự `"m"` không có trong bộ tự vựng gốc. Tương tự, từ `"thug"` sẽ được tokenize thành `["[UNK]", "hug"]`: kí tự `"t"` không có trong bộ tự vựng gốc, và áp dụng quy tắc hợp nhất ở `"u"` và `"g"` và sau đó `"hu"` và `"g"`. + + + +✏️ **Giờ tới lượt bạn!** Bạn nghĩ rằng `"unhug"` sẽ được tokenize như thế nào? + + + +## Triển khai BPE + +Hãy cùng xem các thuật toán BPE được triển khai. Đây không phải là phiên bản tối ưu mà bạn có thể thực sự sử dụng cho một kho ngữ liệu lớn; chúng tôi chỉ muốn cho bạn xem đoạn mã để bạn có thể hiểu thuật toán này tốt hơn. + +Đầu tiên chúng ta cần một kho ngữ liệu, vậy nên hay tạo ra một bản đơn giản với một vài câu: + +```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.", +] +``` + +Tiếp theo, ta cần tiền tokenize kho ngữ liệu này thành các từ. Vì ta đang sao chép một bản BPE tokenizer (như GPT-2), ta vẫn có thể sử dụng `gpt2` tokenize cho bước pre-tokenization: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("gpt2") +``` + +Sau đó ta tính tần suất của từng từ trong kho ngữ liệu như khi làm với pre-tokenization: + +```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}) +``` + +Tiếp theo chúng ta sẽ tính bộ từ vựng cơ sở từ các kí tự sử dụng trong kho ngữ liệu: + +```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', 'Ġ'] +``` + +Ta cũng có thể thêm các token đặc biệt từ mô hình ở đầu của bộ tự vựng. Trong trường hợp của GPT-2, token đặc biệt duy nhất đó là `"<|endoftext|>"`: + +```python +vocab = ["<|endoftext|>"] + alphabet.copy() +``` + +Ta giờ cần phải chia mỗi từ thành các kí tự riêng lẻ để có thể bắt đầu huấn luyện + +```python +splits = {word: [c for c in word] for word in word_freqs.keys()} +``` + +Giờ ta đã sẵn sàng để huấn luyện, hãy cùng viết một hàm tính tần suất mỗi cặp. Ta sẽ cần sử dụng nó ở bước huấn luyện: + +```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 +``` + +Hãy nhìn vào một phần từ điẻn sau khi tách: + +```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 +``` + +Giờ thì, tìm xem cặp xuất hiện nhiều nhất bằng một vòng lặp nhanh: + +```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 +``` + +Vậy phép hợp nhất đầu tiên là `('Ġ', 't') -> 'Ġt'`, và ta thêm `'Ġt'` vào bộ từ vựng: + +```python +merges = {("Ġ", "t"): "Ġt"} +vocab.append("Ġt") +``` + +Để tiếp tục, ta cần áp dụng sự hợp nhất ở từ điển `splits`. Hãy cùng viết một hàm khác cho nó: + +```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 +``` + +Giờ ta có thể nhìn xem kết quả của lần hợp nhất đầu tiên: + +```py +splits = merge_pair("Ġ", "t", splits) +print(splits["Ġtrained"]) +``` + +```python out +['Ġt', 'r', 'a', 'i', 'n', 'e', 'd'] +``` + +Giờ thì ta có tất cả những gì mình cần để lặp cho đến khi ta học tất các các hợp nhất mà ta muốn. Hãy cũng nhắm tới bộ tự vựng có kích cỡ là 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]) +``` + +Kết quả là, chúng ta đã học 19 quy tắc hợp nhất (bộ từ điển gốc có kích cỡ là 31 tương ứng 30 kí tự trong bảng chữ cái cùng một token đặt biệt): + +```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'} +``` + +Và bộ tự vựng cấu thành bởi token đặc biết, các kí tự trong bảng chữ cái, và tất cả kết quả từ các quy tắc hợp nhất: + +```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'] +``` + + + +💡 Sử dụng `train_new_from_iterator()` trên cùng kho ngữ liệu sẽ không mang về kết quả kho ngữ liệu y hệt. Đó là bởi khi có sự lựa chọn về cặp có tần suất cao nhất, ta đã chọn cái đầu tiên xuất hiện, trong khi thư viện 🤗 Tokenizers chọn cái đầu tiên dựa trên ID bên trong của nó. + + + +Để tokenize văn bản mới, chúng ta tiền tokenize nó, tách ra, rồi áp dụng quy tắc hợp nhất được học: + +```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, []) +``` +t +Ta có thể thử các này với bất kì đoạn văn nào khác được tạo thành từ các kí tự trong bảng chữ cái: + +```py +tokenize("This is not a token.") +``` + +```python out +['This', 'Ġis', 'Ġ', 'n', 'o', 't', 'Ġa', 'Ġtoken', '.'] +``` + + + +⚠️ Các triển khai của chúng ta sẽ gặp lỗi nếu có những kí tự vô danh vì chúng ta đã không làm gì để xử lý chúng. GPT-2 không thực sự có những token vô danh (không thể có kí tự vô danh khi sử dụng BPE cấp byte), nhưng nó có thể xảy ra ở đây vì ta không bao gồm tất cả các byte có thể có trong bộ từ vựng gốc. Khía cạnh này của BPE nằm ngoài phạm vi phần này, nên chúng tôi sẽ không đi sau vào chi tiết. + + + +Đó là những gì ta cần biết về thuật toán BPE! Tiếp theo, chúng ta sẽ cùng tìm hiểu về WordPiece. diff --git a/chapters/vi/chapter6/6.mdx b/chapters/vi/chapter6/6.mdx new file mode 100644 index 000000000..583aa95c0 --- /dev/null +++ b/chapters/vi/chapter6/6.mdx @@ -0,0 +1,374 @@ +# WordPiece tokenization + + + +WordPiece là một thuật toán tokenize được Google phát triển để huấn luyện trước BERT. Nó đã được tái sử dụng trong một vài mô hình Transformer dựa trên BERT, như DistilBERT, MobileBERT, Funnel Transformers, và MPNET. Nó khá tương tự với BPE về mặt huấn luyện, nhưng tokenize thực sự được thực hiện hoàn toàn khác. + + + + + +💡 Phần này sẽ đi sâu vào WordPiece, cũng như các triển khai đầy đủ của nó. Bạn có thể bỏ qua phần cuối nếu bạn chỉ muốn có một cái nhìn tổng quan về thuật toán tokenize này. + + + +## Thuật toán huấn luyện + + + +⚠️ Google không bao giờ có nguồn mở về cách triển khai các thuật toán huấn luyện của WordPiece,vì vậy những gì dưới đây là phỏng đoán tốt nhất của chúng tôi dựa trên các tài liệu đã xuất bản. Nó có thể không chính xác 100%. + + + +Giống như BPE, WordPiece bắt đầu từ một từ vựng nhỏ bao gồm các token đặc biệt được sử dụng bởi mô hình và bảng chữ cái đầu tiên. Vì nó xác định các từ phụ bằng cách thêm tiền tố (như `##` cho BERT), ban đầu mỗi từ được tách bằng cách thêm tiền tố đó vào tất cả các ký tự bên trong từ. Vì vậy, ví dụ, `"word"` được chia như thế này: + +``` +w ##o ##r ##d +``` + +Vì vậy, bảng chữ cái chứa tất cả các kí tự xuất hiện ở đầu của một từ và các kí tự xuất hiện bên trong của từ được thêm một tiền tố của WordPiece phía trước. + +Sau đó, một lần nữa, giống như BPE, WordPiece học các quy tắc hợp nhất. Sự khác biệt chính là cách chọn cặp được hợp nhất. Thay vì chọn cặp phổ biến nhất, WordPiece tính điểm cho từng cặp, sử dụng công thức sau: + +$$\mathrm{score} = (\mathrm{freq\_of\_pair}) / (\mathrm{freq\_of\_first\_element} \times \mathrm{freq\_of\_second\_element})$$ + +Bằng cách chia tần suất của cặp cho tích tần suất của từng con của nó, thuật toán ưu tiên hợp nhất các cặp mà các bộ phận riêng lẻ ít thường xuyên hơn trong từ vựng. Ví dụ: nó sẽ không nhất thiết phải hợp nhất `("un", "##able")` ngay cả khi cặp đó xuất hiện rất thường xuyên trong từ vựng, vì hai cặp `"un"` và `"##able"` mỗi từ có thể sẽ xuất hiện bằng nhiều từ khác và có tần suất cao. Ngược lại, một cặp như `("hu", "##gging")` có thể sẽ được hợp nhất nhanh hơn (giả sử từ "hugging" xuất hiện thường xuyên trong từ vựng) vì `"hu"` và `"##gging"`có khả năng ít xuất hiện hơn với từng cá thể. + +Hãy cùng nhìn vào cùng bộ tự vựng chúng ta sử dụng cho BPE: + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +Nó sẽ được chia ra như sau: + +``` +("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##g" "##s", 5) +``` + +vì vậy từ vựng ban đầu sẽ là `["b", "h", "p", "##g", "##n", "##s", "##u"]` (nếu ta tạm quên các token đặc biệt). Cặp thường gặp nhất là `("##u", "##g")` (xuất hiện 20 lần), nhưng tần suất xuất hiện riêng của `"##u"` rất cao, vì vậy điểm của nó không phải là cao nhất (đó là 1 / 36). Tất cả các cặp có `"##u"`thực sự có cùng điểm (1 / 36), vì vậy điểm tốt nhất thuộc về cặp `("##g", "##s")` -- cặp duy nhất không có `"##u"` -- là 1 / 20, và phép hợp nhất đầu tiên đã học là `("##g", "##s") -> ("##gs")`. + +Lưu ý rằng khi hợp nhất, chúng ta loại bỏ `##` giữa hai token, vì vậy chúng ta thêm `"##gs"` vào từ vựng và áp dụng hợp nhất trong các từ của ngữ liệu: + +``` +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) +``` + +Tại thời điểm này, `"##u"` nằm trong tất cả các cặp có thể có, vì vậy tất cả chúng đều có cùng điểm. Giả sử trong trường hợp này, cặp đầu tiên được hợp nhất, vì vậy `("h", "##u") -> "hu"`. Điều này đưa chúng ta đến: + +``` +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) +``` + +Sau đó, điểm số tốt nhất tiếp theo được chia sẻ bởi `("hu", "##g")` and `("hu", "##gs")` (với 1/15, so với 1/21 của các cặp khác), vì vậy cặp đầu tiên có điểm lớn nhất được hợp nhất: + +``` +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) +``` + +và tiếp tục như vậy cho đến khi chúng ta đạt được kích thước bộ từ vựng mong muốn. + + + +✏️ **Giờ đến lượt bạn!** Bộ quy luật hợp nhất tiếp theo là gì? + + + +## Thuật toán tokenize + +Tokenize của WordPiece khác BPE ở chỗ WordPiece chỉ lưu từ vựng cuối cùng, không lưu các quy tắc hợp nhất đã học. Bắt đầu từ từ cần tokenize, WordPiece tìm từ con dài nhất có trong từ vựng, sau đó tách từ đó ra. Ví dụ: nếu chúng ta sử dụng từ vựng đã học trong ví dụ trên, đối với từ `"hugs"` từ phụ dài nhất bắt đầu từ đầu mà bên trong từ vựng là `"hug"`, vì vậy chúng ta tách ở đó và nhận được `["hug","##s"]`. Sau đó, chúng ta tiếp tục với `"##s"`, trong từ vựng, vì vậy tokenize của `"hugs"` là `["hug","##s"]`. + +Với BPE, chúng ta sẽ áp dụng các phép hợp nhất đã học theo thứ tự và token điều này thành `["hu", "##gs"]`, do đó, cách mã hoá sẽ khác. + +Ví dụ khác, hãy xem từ `"bugs"` sẽ được tokenize như thế nào. `"b"` là từ phụ dài nhất bắt đầu từ đầu của từ có trong từ vựng, vì vậy chúng tôi tách ở đó và nhận được `["b", "##ugs"]`. Sau đó, `"##u"` là từ con dài nhất bắt đầu ở đầu `"##ugs"` có trong từ vựng, vì vậy chúng ta tách ở đó và nhận được `["b", "##u, "##gs"]`. Cuối cùng, `"##gs"` có trong từ vựng, vì vậy danh sách cuối cùng này là mã hóa của `"bugs"`. + +Khi quá trình tokenize đến giai đoạn không thể tìm thấy một từ khóa phụ trong từ vựng, toàn bộ từ được tokenize thành không xác định - vì vậy, ví dụ: `"mug"` sẽ được tokenize là `["[UNK]"]`, cũng như `"bum"` (ngay cả khi chúng ta có thể bắt đầu bằng `"b"` và `"##u"`, `"##m"` không phải thuộc bộ từ vựng và kết quả tokenize sẽ chỉ là `["[UNK]"]`, không phải `["b", "##u", "[UNK]"]`). Đây là một điểm khác biệt so với BPE, chỉ phân loại các ký tự riêng lẻ không có trong từ vựng là không xác định. + + + +✏️ **Giờ đến lượt bạn!** `"pugs"` sẽ được tokenize như thế nào? + + + +## Triển khai WordPiece + +Bây giờ chúng ta hãy xem xét việc triển khai thuật toán WordPiece. Giống như với BPE, đây chỉ là phương pháp sư phạm và bạn sẽ không thể sử dụng nó trên một kho ngữ liệu lớn. + +Chúng tôi sẽ sử dụng cùng một kho dữ liệu như trong ví dụ 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.", +] +``` + +Đầu tiên, ta cần tiền tokenize kho ngữ liệu thành các từ, Vì ta sao chép lại WordPiece tokenizer (như BERT), ta sẽ sử dụng `bert-base-cased` tokenizer cho pre-tokenization: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +``` + +sau đó ta sẽ tính tần suất của mỗi từ trong kho ngữ liệu như cách ta làm với pre-tokenization: + +```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}) +``` + +Như chúng ta đã thấy trước đây, bảng chữ cái là tập hợp duy nhất bao gồm tất cả các chữ cái đầu tiên của từ và tất cả các chữ cái khác xuất hiện trong các từ có tiền tố là `##`: + +```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'] +``` + +Ta cũng thêm các kí tự đặc biệt từ mô hình ở đầu bộ tự vựng. Trong trường hợp của BERT, ta có danh sách `["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]`: + +```python +vocab = ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"] + alphabet.copy() +``` + +Tiếp theo, chúng ta cần tách từng từ, với tất cả các chữ cái không phải là tiền tố đầu tiên bởi `##`: + +```python +splits = { + word: [c if i == 0 else f"##{c}" for i, c in enumerate(word)] + for word in word_freqs.keys() +} +``` + +Bây giờ chúng ta đã sẵn sàng để luyện tập, hãy viết một hàm tính điểm của từng cặp. Chúng ta sẽ cần sử dụng điều này ở mỗi bước huấn luyện: + +```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 +``` + +Hãy cùng nhìn vào một phần của bộ từ điển sau lần chia đầu: + +```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 +``` + +Giờ thì tìm cặp có điểm cao nhất chỉ cần một vòng lắp nhanh: + +```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 +``` + +Vậy quy tắc hợp nhất đầu tiên là `('a', '##b') -> 'ab'`, và ta thêm `'ab'` vào bộ từ vựng: + +```python +vocab.append("ab") +``` + +Tiếp theo, ta cần áp dụng hợp nhất trong từ điển `splits`, Hãy cùng viết một hàm cho nó: + +```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 +``` + +Và ta có thể thấy kết quả lần hợp nhất đầu tiện: + +```py +splits = merge_pair("a", "##b", splits) +splits["about"] +``` + +```python out +['ab', '##o', '##u', '##t'] +``` + +Giờ thì ta đã có tất cả những gì ta cần để lặp cho đến khi học hết tất cả các hợp nhất ta muốn. Hãy cũng hướng tới bộ từ vựng có kích thước là 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) +``` + +Giờ ta có thể nhìn vào bộ từ điển được tạo ra: + +```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'] +``` + +Như có thể thấy, so với BPE, tokenizer này học các phần của từ như là token nhanh hơn một chút. + + + +💡 Sử dụng `train_new_from_iterator()` trên cùng kho ngữ liệu sẽ không mang về kết quả kho ngữ liệu y hệt. Đó là bởi thư viện 🤗 Tokenizers không triển khai WordPiece cho huấn luyện (vì chúng ta không hoàn toàn nằm rõ bên trong), và sử dụng BPE thay vào đó. + + + +Để tokenize những đoạn văn mới, ta tiền tokenize nó, chia nhỏ và áp dụng thuật toán tokenize cho từng từ. Vậy đó, chúng ta nhìn vào cụm từ con dài nhất bắt đầu từ đầu từ đầu tiên và chia nhỏ nó, sau đó lặp lại quy trình với phần thứ hai, và tiếp tục cho đến hết từ và các từ tiếp theo trong văn bản: + +```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 +``` + +Hãy cũng kiểm tra trên một từ có tronng và không có trong bộ từ vựng: + +```python +print(encode_word("Hugging")) +print(encode_word("HOgging")) +``` + +```python out +['Hugg', '##i', '##n', '##g'] +['[UNK]'] +``` + +Giờ hãy cùng viết một hàm tokenize văn bản: + +```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, []) +``` + +Ta có thể thử trên bất kì văn bản nào: + +```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]'] +``` + +Đó là những gì ta cần biết về thuật toán WordPiece! Tiếp theo, chúng ta sẽ cùng tìm hiểu về Unigram. diff --git a/chapters/vi/chapter6/7.mdx b/chapters/vi/chapter6/7.mdx new file mode 100644 index 000000000..bddae0798 --- /dev/null +++ b/chapters/vi/chapter6/7.mdx @@ -0,0 +1,380 @@ +# Unigram tokenization + + + +Thuật toán Unigram thường được sử dung trong SentencePiece, đây là một thuật toán tokenize cho các mô hình như AlBERT, T5, mBART, Big Bird, và XLNet. + + + + + +💡 Phần này sẽ đi sâu vào Unigram cũng như toàn bộ cách triển khai. Bạn có thể bỏ qua phần cuối nếu bạn chỉ quan tâm tổng quan thuật toán tokenize. + + + +## Thuật toán huấn luyện + +So với BPE và WordPiece, Unigram hoạt động theo hướng khác: nó bắt đầu từ một từ vựng lớn và loại bỏ các token cho đến khi nó đạt đến kích thước từ vựng mong muốn. Có một số tùy chọn để sử dụng để xây dựng vốn từ vựng cơ bản: ví dụ: chúng ta có thể lấy các chuỗi con phổ biến nhất trong các từ được tiền tokenize hoặc áp dụng BPE trên kho ngữ liệu ban đầu có kích thước từ vựng lớn. + +Tại mỗi bước của quá trình huấn luyện, thuật toán Unigram tính toán sự mất mát trên kho ngữ liệu được cung cấp từ vựng hiện tại. Sau đó, đối với mỗi ký hiệu trong từ vựng, thuật toán sẽ tính toán mức độ tổn thất tổng thể sẽ tăng lên bao nhiêu nếu ký hiệu bị xóa và tìm kiếm các ký hiệu làm tăng nó ít nhất. Những biểu tượng đó có ảnh hưởng thấp hơn đến sự mất mát tổng thể đối với kho dữ liệu, vì vậy theo một nghĩa nào đó, chúng "ít cần thiết hơn" và là những ứng cử viên tốt nhất để loại bỏ. + +Đây là một hoạt động rất tốn kém, vì vậy chúng tôi không chỉ loại bỏ một biểu tượng liên quan đến mức tăng tổn thất thấp nhất, mà \\(p\\) (\\(p\\) là một siêu tham số bạn có thể kiểm soát, thường là 10 hoặc 20) phần trăm các ký hiệu liên quan đến mức tăng tổn thất thấp nhất. Quá trình này sau đó được lặp lại cho đến khi từ vựng đạt được kích thước mong muốn. + +Lưu ý rằng chúng ta không bao giờ xóa các ký tự cơ sở, để đảm bảo rằng bất kỳ từ nào cũng có thể được tokenize. + +Bây giờ, điều này vẫn còn hơi mơ hồ: phần chính của thuật toán là tính toán sự mất mát trong kho ngữ liệu và xem nó thay đổi như thế nào khi chúng tôi xóa một số token khỏi từ vựng, nhưng chúng ta chưa giải thích cách thực hiện điều này. Bước này dựa trên thuật toán tokenize của mô hình Unigram, vì vậy chúng ta sẽ đi sâu vào phần tiếp theo. + +Chúng ta sẽ tái sử dụng kho ngữ liệu từ các ví dụ trước: + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +and for this example, we will take all strict substrings for the initial vocabulary : + +``` +["h", "u", "g", "hu", "ug", "p", "pu", "n", "un", "b", "bu", "s", "hug", "gs", "ugs"] +``` + +## Thuật toán tokenize + +Mô hình Unigram là một loại mô hình ngôn ngữ coi mỗi token là độc lập với các token trước nó. Đó là mô hình ngôn ngữ đơn giản nhất, theo nghĩa xác suất của token X trong bối cảnh trước đó chỉ là xác suất của token X. Vì vậy, nếu chúng ta sử dụng mô hình ngôn ngữ Unigram để tạo văn bản, chúng ta sẽ luôn dự đoán token phổ biến nhất. + +Xác suất của một token nhất định là tần suất của nó (số lần chúng ta tìm thấy nó) trong kho tài liệu gốc, chia cho tổng tất cả các tần số của tất cả các token trong từ vựng (để đảm bảo xác suất tổng bằng 1). Ví dụ: `"ug"` có trong `"hug"`, `"pug"` và `"hugs"`, vì vậy nó có tần suất là 20 trong kho ngữ liệu của chúng tôi. + +Dưới đây là tần suất của tất cả các từ phụ có thể có trong từ vựng: + +``` +("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) +``` + +Vậy nên, tổng của tất cả các tần suất là 210, và xác suất của từ phụ `"ug"` là 20/210. + + + +✏️ **Giờ đến lượt bạn!** Viết đoạn mã để tính tần suất trên và kiểm tra lại kết quả hiển thị cũng như tổng đã đúng chưa. + + + +Giờ, để tokenize một từ cho trước, chúng ta sẽ nhìn vào tất cả các phần đoạn thành token và tính xác suất của từng cái theo mô hình Unigram. Vì tất cả token được cho là độc lập, xác suất này chỉ là tích của xác suất mỗi token. Ví dụ, `["p", "u", "g"]` của `"pug"` có xác suất: + +$$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$$ + +Tương tự, `["pu", "g"]` có xác suất: + +$$P([``pu", ``g"]) = P(``pu") \times P(``g") = \frac{5}{210} \times \frac{20}{210} = 0.0022676$$ + +Nói chung, việc tokenize có ít token nhất có thể sẽ có xác suất cao nhất (vì phép chia cho 210 lặp lại cho mỗi token), tương ứng với những gì chúng ta muốn trực quan: chia một từ thành số lượng token nhất có thể. + +Tokenize của một từ với mô hình Unigram sau đó là token có xác suất cao nhất. Trong ví dụ về `"pug"`, đây là các xác suất mà chúng ta sẽ nhận được cho mỗi phân đoạn có thể có: + +``` +["p", "u", "g"] : 0.000389 +["p", "ug"] : 0.0022676 +["pu", "g"] : 0.0022676 +``` + +Vì vậy, `"pug"` sẽ được tokenize là `["p", "ug"]` hoặc `["pu", "g"]`, tùy thuộc vào phân đoạn nào trong số đó được gặp đầu tiên (lưu ý rằng trong một phân đoạn lớn hơn ngữ liệu, những trường hợp bình đẳng như thế này sẽ rất hiếm). + +Trong trường hợp này, thật dễ dàng để tìm tất cả các phân đoạn có thể có và tính toán xác suất của chúng, nhưng nói chung sẽ khó hơn một chút. Có một thuật toán cổ điển được sử dụng cho việc này, được gọi là thuật toán *Viterbi*. Về cơ bản, chúng ta có thể xây dựng một biểu đồ để phát hiện các phân đoạn có thể có của một từ nhất định bằng cách nói rằng có một nhánh từ ký tự _a_ đến ký tự _b_ nếu từ phụ từ _a_ đến _b_ nằm trong từ vựng và quy cho nhánh đó xác suất của từ phụ . + +Để tìm đường dẫn trong biểu đồ đó sẽ có điểm tốt nhất, thuật toán Viterbi xác định, đối với mỗi vị trí trong từ, phân đoạn có điểm tốt nhất kết thúc tại vị trí đó. Vì chúng ta đi từ đầu đến cuối, điểm tốt nhất có thể được tìm thấy bằng cách lặp qua tất cả các từ phụ kết thúc ở vị trí hiện tại và sau đó sử dụng điểm token tốt nhất từ ​​vị trí mà từ phụ này bắt đầu. Sau đó, chúng ta chỉ cần bỏ qua con đường đã thực hiện để đến cuối. + +Hãy xem một ví dụ sử dụng từ vựng của chúng ta và từ `"unhug"`. Đối với mỗi vị trí, các từ phụ có điểm số tốt nhất kết thúc ở đó như sau: + +``` +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) +``` + +Vậy `"unhug"` có thể tokenize thành `["un", "hug"]`. + + + +✏️ **Giờ đến lượt bạn!** Xác định token của từ `"huggun"`, và điểm cảu chúng. + + + +## Quay lại huấn luyện + +Bây giờ chúng ta đã thấy cách thức hoạt động của tokenize, chúng ta có thể tìm hiểu sâu hơn một chút về sự mất mát được sử dụng trong quá trình huấn luyện. Ở bất kỳ giai đoạn nhất định nào, sự mất mát này được tính toán bằng cách tokenize mọi từ trong kho ngữ liệu, sử dụng từ vựng hiện tại và mô hình Unigram được xác định bởi tần số của mỗi token trong kho ngữ liệu (như đã thấy trước đây). + +Mỗi từ trong kho ngữ liệu đều có một điểm và sự mất mát là khả năng bị âm của những điểm số đó - nghĩa là tổng cho tất cả các từ trong kho ngữ liệu của tất cả các `-log(P(word))`. + +Cùng xem ví dụ của chúng ta với kho ngữ liệu dưới đây: + +``` +("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) +``` + +Kết quả tokenize mỗi từ và điểm tương ứng của chúng như sau: + +``` +"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) +``` + +Và sự mất mát bằng: + +``` +10 * (-log(0.071428)) + 5 * (-log(0.007710)) + 12 * (-log(0.006168)) + 4 * (-log(0.001451)) + 5 * (-log(0.001701)) = 169.8 +``` + +Giờ thì ta cần tính xem việc loại bỏ mỗi token sẽ ảnh hưởng thế nào tới sự mất mát. Điều này khá tẻ nhạt, vì vậy chúng ta sẽ chỉ làm điều đó cho hai token ở đây và lưu toàn bộ quá trình khi chúng ta có đoạn mã trợ giúp. Trong trường hợp (rất) cụ thể này, chúng tôi có hai token tương đương của tất cả các từ: như chúng ta đã thấy trước đó, ví dụ: `"pug"` có thể được tokenize `["p","ug"]` với cùng số điểm. Do đó, loại bỏ mã thông báo `"pu"` khỏi từ vựng sẽ gây ra sự mất mát tương tự. + +Mặt khác, việc loại bỏ `"hug"` sẽ làm cho sự mất mát trở nên tồi tệ hơn, bởi vì token của `"hug"` và `"hugs"` sẽ trở thành: + +``` +"hug": ["hu", "g"] (score 0.006802) +"hugs": ["hu", "gs"] (score 0.001701) +``` + +Những thay đổi này sẽ ảnh hưởng đến và làm sự mất mát tăng lên + +``` +- 10 * (-log(0.071428)) + 10 * (-log(0.006802)) = 23.5 +``` + +Vì vậy, token `"pu"` chắc chắn sẽ bị loại khỏi bộ từ vựng, nhưng `"hug"` thì không. + +## Triển khai Unigram + +Giờ hãy cùng triển khai mọi thứ ta đã cùng xem thông qua đaonj mã. Giống như BPE và WordPiece, đây không phải là một cách triển khai hiểu quả của thuật toán (khá ngược lại), nhưng nó sẽ giúp bạn hiểu hơn về Unigram. + +Ta sẽ sử dùng cùng bộ ngữ liệu đã sử dụng như một ví dụ: + +```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.", +] +``` + +Lần này, ta sẽ sử dụng `xlnet-base-cased` như mô hình: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("xlnet-base-cased") +``` + +Tương tự BPE và WordPiece, ta sẽ bắt đầu đếm tần suất xuất hiện của mỗi từ trong kho ngữ liệu: + +```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 +``` + +Sau đó, ta cần khởi tạo bộ từ vựng với số lượng lớn hơn kích thước ta muốn cuối cùng. Ta phải tổng kết tất cả các kí tự cơ bản (nếu không ta sẽ không thể tokenize tất cả các từ), nhưng với các chuỗi con lớn hơn ta sẽ giữ phần thông dụng nhất và sắp xếp chúng theo tần suất: + +```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 + # Lặp qua các từ con có độ dài >= 2 + for j in range(i + 2, len(word) + 1): + subwords_freqs[word[i:j]] += freq + +# Sắp xếp các từ con theo tần suất +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)] +``` + +Ta nhóm các kí tự có các từ con tốt nhất vào bộ từ vựng ban đầu có kích thước là 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 sử dụng một thuật toán hiệu quả hơn gọi là Enhanced Suffix Array (ESA) để tạo ra bộ từ vựng ban đầu. + + + +Tiếp theo, chúng ta tính tổng tần suất để biến đổi các tần suất này thành xác suất. Với mô hình, chúng ta sẽ lưu các log của xác xuất, vì nó ổn định hơn về mặt số học khi cộng logarit hơn là nhân các số nhỏ và điều này sẽ đơn giản hóa việc tính toán mất mát của mô hình: + +```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()} +``` + +Giờ thì các hàm chính là hàm tokenize từ sử dụng thuật toán Viterbi. Như đã thấy trước đó, thuật toán tính phân đoạn tốt nhất của mỗi chuỗi con của từ, được lưu dưới biến `best_segmentations`. Chúng ta sẽ lưu mỗi vị trí một từ điển trong từ (từ 0 cho tới độ dài của nó), với hai khoá: chỉ mục điểm bắt đầu của token cuối trong phần đoạn tốt nhất, và điểm của phân đoạn tốt nhất. Với chỉ mục của điểm bắt đầu của token cuối trong phần đoạn tốt nhất, ta sẽ có thể truy vấn toàn bộ phân đoạn một khi danh sách được điền đủ. + +Việc điền danh sách được thực hiện chỉ với hai vòng lặp: vòng lặp chính đi qua từng vị trí bắt đầu và vòng lặp thứ hai thử tất cả các chuỗi con bắt đầu từ vị trí bắt đầu đó. Nếu chuỗi con có trong từ vựng, chúng ta có một phân đoạn mới của từ cho đến vị trí kết thúc đó, và so sánh với những gì có trong `best_segmentations`. + +Khi vòng lặp chính kết thúc, chúng ta chỉ bắt đầu từ cuối và nhảy từ vị trí bắt đầu này sang vị trí tiếp theo, ghi lại các token khi chúng ta đi, cho đến khi chúng ta đến vị trí đầu của từ: + +```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)): + # Nó nên được lấp đầy bởi các bước phía trước của vòng lặp + 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 + # Nếu chúng ta tìm thấy một phân đoạn kết thúc tốt hơn tại end_idx, chúng ta cập nhật + 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: + # Ta đã không tìm thấy tokenize của từ -> không xác định + 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 +``` + +Ta có thể sẵn sàng thử mô hình ban đầu lên một số từ: + +```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) +``` + +Giờ thì thật dễ dàng để tính sự mất mát của mô hình trên kho ngữ liệu! + +```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 +``` + +Ta có thể kiểm tra cách nó hoạt động trên mô hình ta có: + +```python +compute_loss(model) +``` + +```python out +413.10377642940875 +``` + +Việc tính điểm cho mỗi token không quả khó; ta chỉ phải tính sự mất mát của mô hình khi xoá mỗi token: + +```python +import copy + + +def compute_scores(model): + scores = {} + model_loss = compute_loss(model) + for token, score in model.items(): + # Ta luôn giữ độ dài các token bằng 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 +``` + +Ta có thể thử với token cho trước: + +```python +scores = compute_scores(model) +print(scores["ll"]) +print(scores["his"]) +``` + +Vì `"ll"` được sử dụng trong quá trình tokenize `"Hopefully"`, và loại bỏ nó chắc chắn sẽ làm ta thay vào đó sử dụng `"l"` hai lần, ta kì vọng nó sẽ đem lại sự mất mát dương. `"his"` chỉ được sử dụng trong từ `"This"`, nó được tokenize thành chính nó, nên ta kì vọng nó sẽ không có mất mát. Và đây là kết quả: + +```python out +6.376412403623874 +0.0 +``` + + + +💡 Phương pháp này rất không hiệu quả, nên SentencePiece sử dụng một xấp xỉ của hàm mất mát của mô hình mà không dùng token X: thay vì bắt đầu từ đầu, nó chỉ thay thế token X bởi phân đoạn bên trái của nó trong bộ từ vựng. Bằng cách này, tất cả điểm có thể được tính trong cùng một lần đồng thời với sự mất mát của mô hình. + + + +Với tất cả những điều trên, điều cuối cùng ta cần phải làm là thêm các token đặc biệt của mô hình vào bộ từ vựng, sau đó lặp cho đến khi chúng ta cắt đủ số token ta mong muốn cho kích cỡ bộ từ vựng: + +```python +percent_to_remove = 0.1 +while len(model) > 100: + scores = compute_scores(model) + sorted_scores = sorted(scores.items(), key=lambda x: x[1]) + # Loại token percent_to_remove với điểm thấp nhất. + 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()} +``` + +Sau đó, để tokenize các đoạn văn bản, ta chỉ cần áp dụng pre-tokenization và sau đỏ sử dụng hàm `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', '.'] +``` + +Và đó là Unigram! Hy vọng rằng bây giờ bạn cảm thấy như một chuyên gia trong tất cả mọi tokenizer. Trong phần tiếp theo, chúng ta sẽ đi sâu vào các khối của thư viện 🤗 Tokenizers và chỉ cho bạn cách bạn có thể sử dụng chúng để tạo tokenizer của riêng mình. diff --git a/chapters/vi/chapter6/8.mdx b/chapters/vi/chapter6/8.mdx new file mode 100644 index 000000000..2e2550dae --- /dev/null +++ b/chapters/vi/chapter6/8.mdx @@ -0,0 +1,563 @@ +# Xây dựng từng khối tokenizer + + + +Như chúng ta đã thấy trong các phần trước, tokenize bao gồm một số bước: + +- Chuẩn hóa (mọi thao tác dọn dẹp văn bản được cho là cần thiết, chẳng hạn như xóa dấu cách hoặc dấu, chuẩn hóa Unicode, v.v.) +- Tiền tokenize (chia nhỏ đầu vào thành các từ) +- Đưa đầu vào thông qua mô hình (sử dụng các từ được tiền tokenize để tạo ra một chuỗi token) +- Hậu xử lý (thêm token đặc biệt của trình tokenize, tạo attention mask và ID token) + +
+The tokenization pipeline. + +
+ +Thư viện 🤗 Tokenizers đã được xây dựng để cung cấp nhiều sự lựa chọn cho các bước này, và ta có thể kết hợp và nối chúng với nhau. Trong phần này, chúng ta sẽ xem các có thể xây một tokenizer từ đầu, trái ngược với cách huấn luyện một tokenizer mới từ cái cũ như ta đã làm ở [phần 2](/course/chapter6/2). Chúng ta sẽ có thể xây bất kì kiểu tokenizer nào ta có thể nghĩ ra! + + + +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). + +## Thu thập một kho ngữ liệu + +Để huấn luyện tokenizer mới của mình, chúng ta sẽ sử dụng một kho ngữ liệu nhỏ chứa các đoạn văn (cho nhanh). Các bước để có được kho ngữ liệu tương tự như chúng ta đã làm ở [phần đầu của chương này](/course/chapter6/2), nhưng lần này chúng ta sẽ sử dụng [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"] +``` + +Hàm `get_training_corpus()` là một hàm tạo có thể trả về các lô với mỗi lô là 1,000 đoạn văn, cái mà ta sẽ sử dụng để huấn luyện tokenizer. + +🤗 Tokenizers cũng có thể được huấn luyện trực tiếp trên các tệp văn bản. Đây là cách chúng ta tạo ra một tệp văn bản bao gồm các đoạn văn/đầu vào từ WikiText-2 mà ta có thể sử dụng cục bộ: + +```python +with open("wikitext-2.txt", "w", encoding="utf-8") as f: + for i in range(len(dataset)): + f.write(dataset[i]["text"] + "\n") +``` + +Tiếp theo chúng tôi sẽ hướng dẫn bạn cách tự xây dựng từng khối BERT, GPT-2, và XLNet tokenizer của riêng mình. Điều này sẽ cung cấp cho chúng ta một ví dụ về từng thuật toán trong số ba thuật toán tokenize chính: WordPiece, BPE, và Unigram. Hãy cũng bắt đầu với BERT! + +## Xây dựng một WordPiece tokenizer từ đầu + +Để xây dựng một tokenizer với thư viện 🤗 Tokenizers, chúng ta sẽ bắt đầu với việc khởi tạo một đối tượng `Tokenizer` với `model`, sau đó thiết lập `normalizer`, `pre_tokenizer`, `post_processor`, và `decoder` tới các giá trị ta muốn. + +Với ví dụ này, ta sẽ tạo ra một `Tokenizer` với một mô hình WordPiece: + +```python +from tokenizers import ( + decoders, + models, + normalizers, + pre_tokenizers, + processors, + trainers, + Tokenizer, +) + +tokenizer = Tokenizer(models.WordPiece(unk_token="[UNK]")) +``` + +Chúng ta phải chỉ rõ `unk_token` để mô hình biết phải trả về gì khi gặp các kí tự chưa từng gặp trước đó. Các tham số khác chúng ta có thể cài đặt gồm `vocab` của mô hình (ta sẽ huấn luyện mô hình nên không cần thiết lập nó) và `max_input_chars_per_word`, tương ứng độ dài tối đa cho một từ (từ dài hơn giá trị này sẽ bị chia nhỏ) + +Bước đầu tiên để tokenize đó là chuẩn hoá, vì vậy hãy cũng bắt đầu với bước này. Vì BERT được sử dụng rộng tãi, ta có thể sử dụng `BertNormalizer` với tuỳ chọn kinh điển để thiết lập cho BERT: `lowercase` và `strip_accents`, tự cái tên đã giải thích mục đích của chúng; `clean_text` để loại bỏ tất cả các kí tự kiểm soát và dấu cách lặp lại thành một; và `handle_chinese_chars` thêm dấu cách giữa các kí tự tiếng Trung. Để , which places spaces around Chinese characters. To tái tạo tokenizer `bert-base-uncased`, ta có thể thiết lập chuẩn hoá sau: + +```python +tokenizer.normalizer = normalizers.BertNormalizer(lowercase=True) +``` + +Thông thường, khi xây dựng một tokenizer, bạn không cần phải truy cập vào một hàm chuẩn hoá thủ công vì nó đã có sẵn trong thư viện 🤗 Tokenizers library -- tuy nhiên, hãy cùng tạo ra chuẩn hoá BERT thủ công. Thư viện cung câp trình chuẩn hoá `Lowercase` và `StripAccents`, bạn hoàn toàn có thể kết hợp nhiều trình chuẩn hoá với nhau thông qua `Sequence`: + +```python +tokenizer.normalizer = normalizers.Sequence( + [normalizers.NFD(), normalizers.Lowercase(), normalizers.StripAccents()] +) +``` + +Ta cũng có thể sử dụng chuẩn hoá Unicode `NFD` Unicode normalizer, vì nếu không chuẩn hoá `StripAccents` sẽ không nhận diện được những kí tự có dấu và không thể tách nó đúng như ta muốn. + +Như đã thấy ở trên, ta có thể sử dụng phương thức `normalize_str()` của `normalizer` để kiểm tra tác động của nó lên một chuỗi văn bản: + +```python +print(tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) +``` + +```python out +hello how are u? +``` + + + +**Đào sâu hơn** Nếu bạn muốn kiểm tra xem hai phiên bản chuẩn hoá trước đó trên cũng một chuỗi chứa kí tự unicode `u"\u0085"`, bạn chắc chắn sẽ nhận thấy rằng hai cách chuẩn hoá này không hoàn toàn giống nhau. +Để tránh phức tạp hoá phiên bản với `normalizers.Sequence` quá nhiều, chúng tôi sẽ không bao gồm các sự thay thế theo Regex mà `BertNormalizer` yêu cầu khi tham số `clean_text` được thiết lập là `True` - đây cũng là giá trị mặc định. Nhưng đừng lo: có khả năng ta sẽ nhận được kết quả chuẩn hoá giống nhau mà không cần sử dụng `BertNormalizer` thủ công bằng cách thêm hai `normalizers.Replace` vào chuỗi chuẩn hoá. + + + +Tiếp theo là bước pre-tokenization. Một lần nữa, ta có `BertPreTokenizer` được xây dựng sẵn để dùng: + +```python +tokenizer.pre_tokenizer = pre_tokenizers.BertPreTokenizer() +``` + +Hoặc ta có thể xây từ đầu: + +```python +tokenizer.pre_tokenizer = pre_tokenizers.Whitespace() +``` + +Lưu ý rằng `Whitespace` sẽ tách theo dấu cách và các kí tự không phải chữ cái, số, hoặc dấu gạch dưới, nên về mặt kỹ thuật nó sẽ tách theo dấu cách và dấu câu: + +```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))] +``` + +Nêu sbanj chỉ muốn tách theo dấu cách, bạn có thể sử dụng `WhitespaceSplit` thay thế: + +```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))] +``` + +Giống như chuẩn hoá, bản có thể sử dụng `Sequence` để kết hợp các tiền tokenizer với nhau: + +```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))] +``` + +Bước tiếp theo trong pipeline tokenize là đưa đầu vào qua mô hình. Ta đã chỉ định mô hình của mình khi khởi tạo, nhưng ta vẫn cần huấn luyện nó, điều này cần tới `WordPieceTrainer`. Vấn đề chính ở đây là khi khởi tạo một trình huấn luyện trong 🤗 Tokenizers thì bạn cần phải truyền tất cả các token đặc biệt bạn có ý định sử dụng, nếu không nó sẽ không thêm vào bộ từ vựng, vì chúng không có trong kho ngữ liệu huấn luyện: + +```python +special_tokens = ["[UNK]", "[PAD]", "[CLS]", "[SEP]", "[MASK]"] +trainer = trainers.WordPieceTrainer(vocab_size=25000, special_tokens=special_tokens) +``` + +Cũng như việc chỉ định `vocab_size` và `special_tokens`, ta cần thiết lập `min_frequency` (số lần một token phải xuất hiện để được thêm vào bộ từ vựng) hoặc thay đổi `continuing_subword_prefix` (nếu ta muốn sử dụng thứ gì khác ngoài `##`). + +Để huấn luyện một mô hình sử dụng trình lặp ta định nghĩa trước đó, ta chỉ cần thực hiện lệnh này: + +```python +tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) +``` + +Chúng ta cũng có thể sử dụng các tệp văn bản để huấn luyện tokenizer của mình như sau (ta tái khởi tạo mô hình với một `WordPiece` rỗng): + +```python +tokenizer.model = models.WordPiece(unk_token="[UNK]") +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +Trong cả hai trường hợp, ta có thể kiểm tra xem tokenizer trên một đoạn văn bản bằng cách sử dụng phương thức `encode()`: + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.'] +``` + +`encoding` thu được là một `Encoding` gồm tất cả các đầu ra cần thiết của một tokenizer trong tất cả các thông số đa dạng của nó: `ids`, `type_ids`, `tokens`, `offsets`, `attention_mask`, `special_tokens_mask`, và `overflowing`. + +Bước cuối của quy trình đó là hậu xử lý. Ta cần thêm token `[CLS]` token at the beginning and the `[SEP]` ở cuối (hoặc sau mỗi câu, nếu ta có cặp câu). Chúng ta sẽ sử dụng `TemplateProcessor` để thực hiện điều này, nhưng trước hết ta cần biết các ID của token `[CLS]` và `[SEP]` trong bộ từ vựng. + +```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) +``` + +Để viết bản mẫu cho `TemplateProcessor`, chúng ta phải chỉ định cách xử lý một câu đơn và một cặp câu. Đối với cả hai, chúng tôi viết các token đặc biệt muốn sử dụng; câu đầu tiên (hoặc câu đơn) được biểu thị bằng `$A`, trong khi câu thứ hai (nếu token một cặp) được biểu thị bằng `$B`. Đối với mỗi loại trong số này (token và câu đặc biệt), chúng ta cũng chỉ định loại token ID tương ứng sau dấu hai chấm. + +Do đó, bản mẫu BERT cổ điển được định nghĩa như sau: + +```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)], +) +``` + +Lưu ý rằng chúng ta cần truyền vào tất cả các IDs của các kí tự đặc biệt, nên các tokenize có thể chuyển đổi chúng thành các cặp ID. + +Một khi đã được thêm vào, chúng ta có thể quay lại ví dụ trước đó và sẽ nhận được: + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.', '[SEP]'] +``` + +Và trên một cặp câu, chúng ta có thể có được kết quả sau: + +```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] +``` + +Chúng ta đã gần như hoàn thành việc xây dựng tokenizer này từ đầu -- bước cuối cùng là thêm vào một trình giải mã: + +```python +tokenizer.decoder = decoders.WordPiece(prefix="##") +``` + +Hãy cũng kiểm thử với `encoding`: + +```python +tokenizer.decode(encoding.ids) +``` + +```python out +"let's test this tokenizer... on a pair of sentences." +``` + +Tuyệt vời! Ta có thể lưu tokenizer của mình vào trong một tệp JSON như dưới đây: + +```python +tokenizer.save("tokenizer.json") +``` + +Ta sau đó có thể tải lại tệp này trong đối tượng `Tokenizer` với phương thức `from_file()`: + +```python +new_tokenizer = Tokenizer.from_file("tokenizer.json") +``` + +Để sử dụng tokenizer này trong 🤗 Transformers, chúng ta phải bọc nó trong `PreTrainedTokenizerFast`. Chúng ta có thể sử dụng lớp chung hoặc, nếu tokenizer của chúng ta tương ứng với một mô hình hiện có, hãy sử dụng lớp đó (ở đây là `BertTokenizerFast`). Nếu bạn áp dụng bài học này để xây dựng một tokenizer hoàn toàn mới, bạn sẽ phải sử dụng tùy chọn đầu tiên. + +Để bọc tokenizer trong một `PreTrainedTokenizerFast`, chúng ta có thể chuyển tokenizer mà chúng ta đã xây dựng dưới dạng `tokenizer_object` hoặc truyền tệp tokenizer mà chúng ta đã lưu dưới dạng `tokenizer_file`. Điều quan trọng cần nhớ là chúng ta phải đặt thủ công tất cả các token đặc biệt, vì lớp đó không thể suy ra từ đối tượng `tokenizer` token nào là token bị che, `[CLS]`, v.v.: + +```python +from transformers import PreTrainedTokenizerFast + +wrapped_tokenizer = PreTrainedTokenizerFast( + tokenizer_object=tokenizer, + # tokenizer_file="tokenizer.json", # Bạn có thể tải từ tệp tokenizer + unk_token="[UNK]", + pad_token="[PAD]", + cls_token="[CLS]", + sep_token="[SEP]", + mask_token="[MASK]", +) +``` + +Nếu bạn đang sự dụng một lớp tokenizer đặc biệt (như `BertTokenizerFast`), bạn chỉ cần chỉ định một token đặc biết khác so với mặc định (ở đây là không xác định): + +```python +from transformers import BertTokenizerFast + +wrapped_tokenizer = BertTokenizerFast(tokenizer_object=tokenizer) +``` + +Bạn có thể sử dụng tokenizer như bất kỳ tokenizer nào khác của 🤗 Transformers. Bạn có thể lưu nó với phương thức `save_pretrained()`, hoặc lại nó lên Hub sử dụng phương thức `push_to_hub()`. + +Giờ chúng ta đã thấy cách xây dựng bộ WordPiece tokenizer, hãy làm tương tự đối với BPE tokenizer. Chúng ta sẽ tiến hành nhanh hơn một chút vì bạn đã biết tất cả các bước và chỉ làm nổi bật những điểm khác biệt. + +## Xây dựng một BPE tokenizer từ đầu + +Giờ hãy cũng nhau xây dựng GPT-2 tokenizer. Giống như BERT tokenizer, chúng ta bắt đầu bằng việc khởi tạo `Tokenizer` với mô hình BPE: + +```python +tokenizer = Tokenizer(models.BPE()) +``` + +Cũng giống như BERT, chúng ta có thể khởi tạo mô hình này với một bộ từ vựng nếu ta đã có (ta sẽ cần truyền vào `vocab` và `merges` trong trường hợp này), nhưng vì ta sẽ huấn luyện từ đầu, chúng ta không cần làm vậy. Ta cũng không cần chỉ định `unk_token` vì GPT-2 sử dụng BPE cấp byte, phương pháp không cần đến nó. + +GPT-2 không sử dụng một trình chuẩn hoá, nên ta có thể bỏ qua bước này và đi trực tiếp vào bước pre-tokenization: + +```python +tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False) +``` + +Tuỳ chọn `ByteLevel` chúng ta thêm vào ở đây không thêm dấu cách vào đầu của một câu (thường nó là mặc định). Ta có thể nhìn các pre-tokenization từ ví dụ tương tự ở trên: + +```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))] +``` + +Tiếp theo là mô hình mà ta cần huấn luyện. Với GPT-2, token đặc biệt duy nhất ta cần là token kết thúc văn bản: + +```python +trainer = trainers.BpeTrainer(vocab_size=25000, special_tokens=["<|endoftext|>"]) +tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) +``` + +Như với `WordPieceTrainer`, cũng như `vocab_size` và `special_tokens`, ta có thể chỉ định `min_frequency` nếu muốn, hoặc nếu ta có hậu tố kết thúc từ (như ``), ta có thể thiết lập nó với `end_of_word_suffix`. + +Tokenizer này cũng có thể được huấn luyện trên các tệp văn bản: + +```python +tokenizer.model = models.BPE() +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +Hãy cũng xem kết quả tokenize trên một văn bản mẫu: + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['L', 'et', "'", 's', 'Ġtest', 'Ġthis', 'Ġto', 'ken', 'izer', '.'] +``` + +Ta áp dụng hậu xử lý cấp byte cho GPT-2 tokenizer như sau: + +```python +tokenizer.post_processor = processors.ByteLevel(trim_offsets=False) +``` + +Tuỳ chọn `trim_offsets = False` chỉ cho trình hậu xử lý biết rằng ta cần bỏ mốt số offset token bắt đầu với 'Ġ': theo cách này, điểm bắt đầu của offset sẽ trỏ vào vùng không gian phía trước của từ, không phải kí tự đầu tiên của từ (vì không gian này về mặt kỹ thuật là một phần của từ). Hãy cùng nhìn xem kết quả với chuỗi văn bản ta vừa mã hoá với `'Ġtest'` là token ở chỉ mục 4: + +```python +sentence = "Let's test this tokenizer." +encoding = tokenizer.encode(sentence) +start, end = encoding.offsets[4] +sentence[start:end] +``` + +```python out +' test' +``` + +Cuối cùng, ta thêm một trình giải mãi cấp byte: + +```python +tokenizer.decoder = decoders.ByteLevel() +``` + +và ta kiểm tra lại xem nó hoạt động đúng chưa: + +```python +tokenizer.decode(encoding.ids) +``` + +```python out +"Let's test this tokenizer." +``` + +Tuyệt vời! Giờ ta đã xong rồi, ta có thể lưu tokenizer như trên, và bao nó lại trong `PreTrainedTokenizerFast` hoặc `GPT2TokenizerFast` nếu ta muốn nó trong 🤗 Transformers: + +```python +from transformers import PreTrainedTokenizerFast + +wrapped_tokenizer = PreTrainedTokenizerFast( + tokenizer_object=tokenizer, + bos_token="<|endoftext|>", + eos_token="<|endoftext|>", +) +``` + +or: + +```python +from transformers import GPT2TokenizerFast + +wrapped_tokenizer = GPT2TokenizerFast(tokenizer_object=tokenizer) +``` + +Như một ví dụ cuối, chúng tôi sẽ chỉ bạn cách xây dựng một Unigram tokenizer từ đầu. + +## Xây dựng một Unigram tokenizer từ đầu + +Hãy cùng nhau xây dựng một XLNet tokenizer. Cũng giống như các tokenizer trước đó, ta có thể bắt đầu khởi tạo `Tokenizer` với một mô hình Unigram: + +```python +tokenizer = Tokenizer(models.Unigram()) +``` + +Một lần nữa, chúng ta có thể khởi tạo mô hình này với một từ vựng nếu có. + +Với sự chuẩn hoá này, XLNet sử dụng một vài phương pháp thay thế (đến từ SentencePiece): + +```python +from tokenizers import Regex + +tokenizer.normalizer = normalizers.Sequence( + [ + normalizers.Replace("``", '"'), + normalizers.Replace("''", '"'), + normalizers.NFKD(), + normalizers.StripAccents(), + normalizers.Replace(Regex(" {2,}"), " "), + ] +) +``` + +Điều này thay thế `` and '' bằng " và thay thế bất kì chuỗi nào chứa hai hoặc nhiều hơn dấu cách liền nhau thành một dấu duy nhất, cũng như loại bỏ các dấu có trong văn bản để tokenize. + +Tiền tokenizer được sử dụng cho bất kỳ SentencePiece tokenizer là `Metaspace`: + +```python +tokenizer.pre_tokenizer = pre_tokenizers.Metaspace() +``` + +Ta có thể nhìn vào đầu ra quy trình tiền tokenize qua ví dụ văn bản ở dưới: + +```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))] +``` + +Tiếp theo là mô hình ta cần huấn luyện. XLNet có một số token đặc biệt: + +```python +special_tokens = ["", "", "", "", "", "", ""] +trainer = trainers.UnigramTrainer( + vocab_size=25000, special_tokens=special_tokens, unk_token="" +) +tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) +``` + +Một tham số vô cùng quan trong mà ta không thể quên của `UnigramTrainer` là `unk_token`. Ta có thể truyền vào các tham số cụ thể khác tới thuật toán Unigram, ví dụ `shrinking_factor` cho các bước mà ta xoá token (mặc định là 0.75) hoặc `max_piece_length` để chỉ định độ dài tối đa của một token (mặc định là 16). + +Tokenizer này có thể được huấn luyện trên các tệp văn bản: + +```python +tokenizer.model = models.Unigram() +tokenizer.train(["wikitext-2.txt"], trainer=trainer) +``` + +Hãy cùng nhìn xem kết quả tokenize trên tập mẫu: + +```python +encoding = tokenizer.encode("Let's test this tokenizer.") +print(encoding.tokens) +``` + +```python out +['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.'] +``` + +Một điểm đặc biệt của XLNet đó là nó thêm token `` ở cuối mỗi câu, với kiểu ID là 2 (để phân biết với các token khác). Nó đệm thêm vào phía bên tay trái giống như kết quả ở trên. Ta có thể xử lý tất cả các token đặc biệt và các token kiểu ID với cùng một bản mẫu, như BERT, nhưng đầu tiên ta phải lấy các ID của token `` và ``: + +```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 +``` + +Bản mẫu sẽ trông như sau: + +```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)], +) +``` + +Và ta có thể kiểm tra xem nó hoạt động không bằng cách mã hoá cặp câu: + +```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] +``` + +Cuối cùng, ta sẽ thêm trình giải mã `Metaspace`: + +```python +tokenizer.decoder = decoders.Metaspace() +``` + +và ta đã xong với tokenizer này! Ta có thể lưu tokenizer như trên, và bao nó lại trong `PreTrainedTokenizerFast` hoặc `XLNetTokenizerFast` nếu ta muốn nó trong 🤗 Transformers. Một điểm cần lưu ý là khi sử dụng `PreTrainedTokenizerFast` thì trên đầu của các token đặc biệt ta cần nói cho thư viện 🤗 Transformers viết ta cần đệm vào phía bên trái: + +```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", +) +``` + +Hoặc một cách khác: + +```python +from transformers import XLNetTokenizerFast + +wrapped_tokenizer = XLNetTokenizerFast(tokenizer_object=tokenizer) +``` + +Bây giờ bạn đã thấy cách các khối khác nhau được sử dụng để xây dựng các tokenizer hiện nay, bạn sẽ có thể viết bất kỳ trình tokenize nào mà bạn muốn với thư viện 🤗 Tokenizers và có thể sử dụng nó trong 🤗 Transformers. diff --git a/chapters/vi/chapter7/1.mdx b/chapters/vi/chapter7/1.mdx new file mode 100644 index 000000000..57fc79527 --- /dev/null +++ b/chapters/vi/chapter7/1.mdx @@ -0,0 +1,32 @@ + + +# Giới thiệu + +Trong [Chương 3](/course/chapter3), bạn đã thấy cách tinh chỉnh một mô hình để phân loại văn bản. Trong chương này, chúng ta sẽ giải quyết các tác vụ NLP phổ biến sau: + +- Phần loại token +- Mô hình ngôn ngữ bị ẩn đi (như BERT) +- Tóm tắt +- Dịch máy +- Mô hình ngôn ngữ nhân quả huấn luyện trước (như GPT-2) +- Hỏi đáp + +{#if fw === 'pt'} + +Để làm được điều này, bạn sẽ cần tận dụng mọi thứ bạn đã học về API `Trainer` và thư viện 🤗 Accelerate trong [Chương 3](/course/chapter3), thư viện 🤗 Datasets trong [Chapter 5](/course/chapter5), và thư viện 🤗 Tokenizers trong [Chương 6](/course/chap6). Chúng ta cũng sẽ tải kết quả của mình lên Model Hub, giống như đã làm trong [Chương 4](/course/chap4), vì vậy đây thực sự là chương mà mọi thứ kết hợp với nhau! + +Mỗi phần có thể được đọc độc lập và sẽ chỉ cho bạn cách huấn luyện một mô hình bằng API `Trainer` hoặc với vòng huấn luyện của riêng bạn, sử dụng 🤗 Accelerate. Vui lòng bỏ qua một trong hai phần và tập trung vào phần mà bạn quan tâm nhất: API `Trainer` rất tuyệt vời để tinh chỉnh hoặc huấn luyện mô hình của bạn mà không cần lo lắng về những gì đang diễn ra ở phía sau, trong khi vòng huấn luyện với `Accelerate` sẽ cho phép bạn tùy chỉnh bất kỳ phần nào bạn muốn dễ dàng hơn. + +{:else} + +Để làm được điều này, bạn sẽ cần tận dụng mọi thứ bạn đã học về các mô hình huấn luyện với API Keras [trong [Chương 3](/course/chapter3), thư viện 🤗 Datasets trong [Chapter 5](/course/chapter5), và thư viện 🤗 Tokenizers trong [Chương 6](/course/chap6). Chúng ta cũng sẽ tải kết quả của mình lên Model Hub, giống như đã làm trong [Chương 4](/course/chap4), vì vậy đây thực sự là chương mà mọi thứ kết hợp với nhau! + +Mỗi phần có thể được đọc độc lập. + +{/if} + + + +Nếu bạn đọc các phần theo trình tự, bạn sẽ nhận thấy rằng chúng có khá nhiều điểm chung về đoạn mã và văn xuôi mô tả. Việc lặp lại là có chủ đích, để cho phép bạn nhúng tay vào (hoặc quay lại sau) bất kỳ tác vụ nào mà bạn quan tâm và tìm thấy một ví dụ hoạt động hoàn chỉnh. + + diff --git a/chapters/vi/chapter7/2.mdx b/chapters/vi/chapter7/2.mdx new file mode 100644 index 000000000..b86cc8e19 --- /dev/null +++ b/chapters/vi/chapter7/2.mdx @@ -0,0 +1,1010 @@ + + +# Phân loại token + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Ứng dụng đầu tiên chúng ta sẽ cùng khám phá là phân loại token. Tác vụ chung này bao gồm bất kỳ vấn đề nào có thể được xây dựng dưới dạng "gán nhãn cho mỗi token trong một câu", chẳng hạn như: + +- **Nhận dạng thực thể được đặt tên (NER)**: Tìm các thực thể (chẳng hạn như người, địa điểm hoặc tổ chức) trong một câu. Điều này có thể được xây dựng như là gán nhãn cho mỗi token bằng cách có một nhãn cho mỗi thực thể và một nhãn cho "không có thực thể". +- **Gán nhãn từ loại (POS)**: Đánh dấu mỗi từ trong câu tương ứng với một từ loại cụ thể của văn bản (chẳng hạn như danh từ, động từ, tính từ, v.v.). +- **Phân khúc**: Tìm các token thuộc cùng một thực thể. Tác vụ này (có thể được kết hợp với POS hoặc NER) có thể được xây dựng dưới dạng gán một nhãn (thường là `B-`) cho bất kỳ token nào ở đầu một đoạn, một nhãn khác (thường là `I-`) cho các token đó nằm bên trong một đoạn và một nhãn thứ ba (thường là `O`) token không thuộc bất kỳ đoạn nào. + + + +Tất nhiên, có nhiều loại vấn đề phân loại token khác; đó chỉ là một vài ví dụ tiêu biểu. Trong phần này, chúng ta sẽ tinh chỉnh một mô hình (BERT) trên một tác vụ NER, sau đó sẽ có thể tính toán các dự đoán như sau: + + + + + One-hot encoded labels for question answering. + + + +Bạn có thể tìm mô hình ta sẽ huấn luyện và tải lên Hub và kiểm tra lại các dự đoán [tại đây](https://huggingface.co/huggingface-course/bert-finetuned-ner?text=My+name+is+Sylvain+and+I+work+at+Hugging+Face+in+Brooklyn). + +## Chuẩn bị dữ liệu + +Đầu tiên, ta cần bộ dữ liệu chuẩn bị cho phân loại token. Trong chương này, chúng ta sẽ sử dụng bộ dữ liệu [CoNLL-2003](https://huggingface.co/datasets/conll2003), bao gồm các câu chuyện tin tức từ Reuters. + + + +💡 Miễn là tập dữ liệu của bạn bao gồm các văn bản được chia thành các từ với nhãn tương ứng của chúng, bạn sẽ có thể điều chỉnh các quy trình xử lý dữ liệu được mô tả ở đây với tập dữ liệu của riêng bạn. Tham khảo lại [Chapter 5](/course/chapter5) nếu bạn cần cập nhật về cách tải dữ liệu tùy chỉnh của riêng bạn trong `Dataset`. + + + +### Tập dữ liệu CoNLL-2003 + +Để tải bộ dữ liệu CoNLL-2003, ta cần sử dụng phương thức `load_dataset()` từ thư viện 🤗 Datasets: + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("conll2003") +``` + +Ta sẽ tải và lưu bộ dữ liệu vào cache, như ta đã thấy trong [Chương 3](/course/chapter3) cho bộ dữ liệu GLUE MRPC. Việc kiểm tra đối tượng này cho chúng ta thấy các cột hiện có và sự phân chia giữa các tập huấn luyện, kiểm định và kiểm thử: + +```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 + }) +}) +``` + +Đặc biệt, chúng ta có thể thấy tập dữ liệu chứa các nhãn cho ba tác vụ mà chúng ta đã đề cập trước đó: NER, POS và chunking. Một sự khác biệt lớn so với các bộ dữ liệu khác là các văn bản đầu vào không được trình bày dưới dạng câu hoặc tài liệu, mà là danh sách các từ (cột cuối cùng được gọi là `tokens`, nhưng nó chứa các từ theo nghĩa đây là các đầu vào được tokenize trước vẫn cần để đi qua trình tokenize để tokenize từ phụ). + +Hãy xem phần tử đầu tiên của tập huấn luyện: + +```py +raw_datasets["train"][0]["tokens"] +``` + +```python out +['EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'lamb', '.'] +``` + +Vì ta muốn thực hiện nhận dạng thực thể được đặt tên, chúng ta sẽ nhìn vào các thẻ NER: + +```py +raw_datasets["train"][0]["ner_tags"] +``` + +```python out +[3, 0, 7, 0, 0, 0, 7, 0, 0] +``` + +Đó là những nhãn dưới dạng số nguyên sẵn sàng để huấn luyện, nhưng chúng không nhất thiết hữu ích khi chúng ta muốn kiểm tra dữ liệu. Giống như phân loại văn bản, chúng ta có thể truy cập sự tương ứng giữa các số nguyên đó và tên nhãn bằng cách xem thuộc tính `features` của tập dữ liệu: + +```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) +``` + +Vì vậy, cột này chứa các phần tử là chuỗi của `ClassLabel`. Loại phần tử của chuỗi nằm trong thuộc tính `feature` của `ner_feature` này, và chúng ta có thể truy cập danh sách tên bằng cách xem thuộc tính `names` của `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'] +``` + +Chúng ta đã thấy các nhãn khi đào sâu vào pipeline `token-classification` trong [Chương 6](/course/chapter6/3), nhưng để cập nhật nhanh: + +- `O` nghĩa là từ không thuộc bất kì thực thể nào. +- `B-PER`/`I-PER` nghĩa là từ tương ứng phần bắt đầu/ nằm bên trong của thực thể _person_ hay _con người_. +- `B-ORG`/`I-ORG` nghĩa là từ tương ứng phần bắt đầu/ nằm bên trong của thực thể _organization_ hay _tổ chức_. +- `B-LOC`/`I-LOC` nghĩa là từ tương ứng phần bắt đầu/ nằm bên trong của thực thể _location_ hay _địa điểm_. +- `B-MISC`/`I-MISC` nghĩa là từ tương ứng phần bắt đầu/ nằm bên trong của thực thể _miscellaneous_ hay _lộn xộn_. + +Giờ khi giải mã các nhãn, ta thấy chúng cho ta: + +```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' +``` + +Và đối với một ví dụ trộn nhãn `B-` và `I-`, đây là những gì mà cùng một đoạn mã cung cấp cho chúng ta về phần tử của tập huấn luyện ở chỉ mục 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' +``` + +Như chúng ta có thể thấy, các thực thể bao gồm hai từ, như "European Union" và "Werner Zwingmann", được gán nhãn `B-` cho từ đầu tiên và nhãn `I-` cho từ thứ hai. + + + +✏️ **Đến lượt bạn!** In hai câu giống nhau bằng nhãn POS hoặc phân khúc của chúng. + + + +### Xử lý dữ liệu + + + +Như thường lệ, các văn bản của chúng ta cần được chuyển đổi sang token ID trước khi mô hình có thể hiểu được chúng. Như chúng ta đã thấy trong [Chương 6](/course/chapter6/), một sự khác biệt lớn trong trường hợp tác vụ phân loại token là chúng ta có các đầu vào được tokenize trước. May mắn thay, API tokenizer có thể giải quyết vấn đề đó khá dễ dàng; chúng ta chỉ cần báo `tokenizer` bằng một lá cờ đặc biệt. + +Để bắt đầu, hãy tạo đối tượng `tokenizer` của chúng ta. Như chúng tôi đã nói trước đây, chúng ta sẽ sử dụng mô hình huấn luyện trước BERT, vì vậy chúng ta sẽ bắt đầu bằng cách tải xuống và lưu vào bộ nhớ đệm của tokenizer liên quan: + +```python +from transformers import AutoTokenizer + +model_checkpoint = "bert-base-cased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +Bạn có thể thay thế `model_checkpoint` bằng bất kỳ mô hình nào khác mà bạn thích từ [Hub](https://huggingface.co/models) hoặc bằng một thư mục cục bộ trong đó bạn đã lưu một mô hình được huấn luyện trước và một trình tokenize. Hạn chế duy nhất là tokenizer cần được hỗ trợ bởi thư viện 🤗 Tokenizers, vì vậy sẽ có phiên bản "nhanh". Bạn có thể xem tất cả các kiến trúc đi kèm với phiên bản nhanh trong [bảng lớn này](https://huggingface.co/transformers/#supported-frameworks) và để kiểm tra xem đối tượng `tokenizer` mà bạn đang sử dụng có thực sự là được hỗ trợ bởi 🤗 Tokenizers, bạn có thể xem thuộc tính `is_fast` của nó: + +```py +tokenizer.is_fast +``` + +```python out +True +``` + +Để tokenize dữ liệu đầu vào đã tiền tokenize, ta có thể sử dụng `tokenizer` như thường lệ và chỉ thêm `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]'] +``` + +Như chúng ta có thể thấy, trình tokenizer đã thêm các token đặc biệt được sử dụng bởi mô hình (`[CLS]` ở đầu và `[SEP]` ở cuối) và để nguyên hầu hết các từ. Tuy nhiên, từ `lamb` đã được tokenize thành hai từ phụ, `la` và `##mb`. Điều này dẫn đến sự không khớp giữa đầu vào và các nhãn: danh sách nhãn chỉ có 9 phần tử, trong khi đầu vào của chúng ta hiện có 12 token. Việc tính toán các token đặc biệt rất dễ dàng (chúng ta biết chúng nằm ở đầu và cuối), nhưng chúng ta cũng cần đảm bảo rằng chúng ta sắp xếp tất cả các nhãn với các từ thích hợp. + +May mắn thay, bởi vì chúng ta đang sử dụng một tokenizer nhanh, chúng ta có quyền truy cập vào sức mạnh siêu cường 🤗 Tokenizers, có nghĩa là chúng ta có thể dễ dàng ánh xạ từng token với từ tương ứng của nó (như đã thấy trong [Chương 6](/course/chapter6/3)): + +```py +inputs.word_ids() +``` + +```python out +[None, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, None] +``` + +Với một chút công việc, sau đó chúng ta có thể mở rộng danh sách nhãn của mình để phù hợp với các token. Quy tắc đầu tiên chúng ta sẽ áp dụng là các token đặc biệt có nhãn là `-100`. Điều này là do theo mặc định `-100` là chỉ số bị bỏ qua trong hàm mất mát mà chúng ta sẽ sử dụng (entropy chéo). Sau đó, mỗi token có cùng nhãn với token bắt đầu từ bên trong nó, vì chúng là một phần của cùng một thực thể. Đối với các token bên trong một từ nhưng không ở đầu, chúng ta thay thế `B-` bằng `I-` (vì token không bắt đầu thực thể): + +```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: + # Bắt đầu một từ mới! + current_word = word_id + label = -100 if word_id is None else labels[word_id] + new_labels.append(label) + elif word_id is None: + # Token đặc biệt + new_labels.append(-100) + else: + # Từ giống với token trước đó + label = labels[word_id] + # Nếu nhãn là B-XXX, ta đổi sang I-XXX + if label % 2 == 1: + label += 1 + new_labels.append(label) + + return new_labels +``` + +Hãy cùng thử với câu đầu tiên: + +```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] +``` + +Như chúng ta có thể thấy, hàm đã thêm `-100` cho hai token đặc biệt ở đầu và cuối, và dấu `0` mới cho từ của chúng ta đã được chia thành hai token. + + + +✏️ **Đến lượt bạn!** Một số nhà nghiên cứu chỉ thích gán một nhãn cho mỗi từ và gán `-100` cho các token con khác trong một từ nhất định. Điều này là để tránh các từ dài được chia thành nhiều token phụ góp phần lớn vào hàm mất mát. Thay đổi chức năng trước đó để căn chỉnh nhãn với ID đầu vào bằng cách tuân theo quy tắc này. + + + +Để xử lý trước toàn bộ tập dữ liệu của mình, chúng ta cần tokenize tất cả các đầu vào và áp dụng `align_labels_with_tokens()` trên tất cả các nhãn. Để tận dụng tốc độ của trình tokenize nhanh của mình, tốt nhất bạn nên tokenize nhiều văn bản cùng một lúc, vì vậy chúng ta sẽ viết một hàm xử lý danh sách các ví dụ và sử dụng phương thức `Dataset.map()` với tùy chọn `batched=True`. Điều duy nhất khác với ví dụ trước là hàm `word_ids()` cần lấy chỉ mục của mẫu mà chúng ta muốn các ID từ khi các đầu vào cho tokenizer là danh sách văn bản (hoặc trong trường hợp của chúng ta là danh sách danh sách các từ), vì vậy chúng ta cũng thêm vào đó: + +```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 +``` + +Lưu ý rằng chúng ta chưa đệm vào của mình; chúng ta sẽ làm điều đó sau, khi tạo các lô bằng trình đối chiếu dữ liệu. + +Bây giờ chúng ta có thể áp dụng tất cả tiền xử lý đó trong một lần vào các phần khác của tập dữ liệu của mình: + +```py +tokenized_datasets = raw_datasets.map( + tokenize_and_align_labels, + batched=True, + remove_columns=raw_datasets["train"].column_names, +) +``` + +Chúng ta đã hoàn thành phần khó nhất! Bây giờ, dữ liệu đã được tiền xử lý, quá trình huấn luyện thực tế sẽ giống như những gì chúng ta đã làm trong [Chương 3](/course/chapter3). + +{#if fw === 'pt'} + +## Tinh chỉnh mô hình trong API `Trainer` + +Mã thực sử dụng `Trainer` sẽ giống như trước đây; những thay đổi duy nhất là cách dữ liệu được đối chiếu thành một lô và chức năng tính toán số liệu. + +{:else} + +## Tinh chỉnh mô hình với Keras + +Mã thực sử dụng Keras sẽ giống như trước đây; những thay đổi duy nhất là cách dữ liệu được đối chiếu thành một lô và chức năng tính toán số liệu. + +{/if} + +### Đối chiếu dữ liệu + +Chúng ta không thể chỉ sử dụng một `DataCollatorWithPadding` như trong [Chương 3](/course/chapter3) vì nó chỉ đệm các đầu vào (ID đầu vào, attention mask và loại token ID). Ở đây, các nhãn của chúng ta nên được đệm theo cùng một cách giống như các đầu vào để chúng giữ nguyên kích thước, sử dụng `-100` làm giá trị để các dự đoán tương ứng bị bỏ qua trong tính toán tổn thất. + +Tất cả điều này được thực hiện bởi [`DataCollatorForTokenClassification`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorfortokenclassification). Giống như `DataCollatorWithPadding`, nó sử dụng `tokenizer` để xử lý trước các đầu vào: + +{#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} + +Để kiểm tra nó trên vài mẫu, ta có thể gọi nó trên danh sách các mẫu từ tập huấn luyện đã được tokenize: + +```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]]) +``` + +Hãy so sánh điều này với các nhãn cho phần tử đầu tiên và thứ hai trong tập dữ liệu của mình: + +```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'} + +Như chúng ta có thể thấy, tập hợp nhãn thứ hai đã được đệm bằng độ dài của tập đầu tiên bằng cách sử dụng `-100`. + +{:else} + +Trình đối chiếu dữ liệu của chúng ta đã sẵn sàng hoạt động! Bây giờ hãy sử dụng nó để tạo một `tf.data.Dataset` với phương thức `to_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, +) +``` + +Điểm dừng tiếp theo: chính là mô hình. + +{/if} + +{#if fw === 'tf'} + +### Định nghĩa mô hình + +Vì chúng tôi đang giải quyết vấn đề phân loại token, chúng ta sẽ sử dụng lớp `TFAutoModelForTokenClassification`. Điều chính cần nhớ khi xác định mô hình này là truyền một số thông tin về số lượng nhãn mà chúng ta có. Cách dễ nhất để làm điều này là truyền vào tham số `num_labels`, nhưng nếu chúng ta muốn một tiện ích luận suy đẹp hoạt động giống như tiện ích chúng ta đã thấy ở đầu phần này, tốt hơn nên đặt các nhãn tương ứng chính xác thay thế. + +Chúng phải được đặt bởi hai từ điển, `id2label` và `label2id`, chứa ánh xạ từ ID đến nhãn và ngược lại: + +```py +id2label = {str(i): label for i, label in enumerate(label_names)} +label2id = {v: k for k, v in id2label.items()} +``` + +Bây giờ chúng ta có thể chuyển chúng đến phương thức `TFAutoModelForTokenClassification.from_pretrained()` và chúng sẽ được đặt trong cấu hình của mô hình, sau đó được lưu và tải lên Hub một cách chính xác: + +```py +from transformers import TFAutoModelForTokenClassification + +model = TFAutoModelForTokenClassification.from_pretrained( + model_checkpoint, + id2label=id2label, + label2id=label2id, +) +``` + +Giống như khi chúng tôi định nghĩa `TFAutoModelForSequenceClassification` của mình trong [Chương 3](/course/chapter3), việc tạo mô hình đưa ra cảnh báo rằng một số trọng số không được sử dụng (những trọng số từ đầu huấn luyện trước) và một số trọng số khác được khởi tạo ngẫu nhiên (những trọng số từ đầu phân loại token mới) và mô hình này nên được huấn luyện. Chúng ta sẽ làm điều đó sau một phút, nhưng trước tiên hãy kiểm tra kỹ xem mô hình của chúng ta có đúng số lượng nhãn hay không: + +```python +model.config.num_labels +``` + +```python out +9 +``` + + + +⚠️ Nếu bạn có một mô hình có số nhãn sai, bạn sẽ gặp lỗi khó hiểu khi gọi `model.fit()` sau này. Điều này có thể gây khó chịu khi gỡ lỗi, vì vậy hãy đảm bảo bạn thực hiện kiểm tra này để xác nhận rằng bạn có số lượng nhãn dự kiến. + + + +### Tinh chỉnh một mô hình + +Bây giờ chúng tôi đã sẵn sàng để huấn luyện mô hình của mình! Tuy nhiên, chúng ta chỉ cần làm thêm một chút công việc trước tiên: chúng ta nên đăng nhập vào Hugging Face và xác định các siêu tham số huấn luyện của mình. Nếu bạn đang làm việc trên notebook, có một chức năng tiện lợi để giúp bạn làm điều này: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Thao tác này sẽ hiển thị một tiện ích mà bạn có thể nhập thông tin đăng nhập Hugging Face của mình. + +Nếu bạn không làm việc trong notebook, chỉ cần nhập dòng sau vào thiết bị đầu cuối của bạn: + +```bash +huggingface-cli login +``` + +Sau khi đăng nhập, chúng ta có thể chuẩn bị mọi thứ cần thiết để biên dịch mô hình của mình. 🤗 Transformers cung cấp một hàm `create_optimizer()` thuận tiện sẽ cung cấp cho bạn trình tối ưu hóa `AdamW` với các cài đặt thích hợp cho giảm trọng lượng và giảm tốc độ học tập, cả hai đều sẽ cải thiện hiệu suất mô hình của bạn so với trình tối ưu hóa `Adam` tích hợp sẵn: + +```python +from transformers import create_optimizer +import tensorflow as tf + +# Huấn luyện trong mixed-precision float16 +# Bình luận dòng này nếu bạn đang sử dụng GPU nên không được hưởng lợi từ điều này +tf.keras.mixed_precision.set_global_policy("mixed_float16") + +# Số bước huấn luyện là số lượng mẫu trong tập dữ liệu, chia cho kích thước lô sau đó nhân +# với số epoch. Lưu ý rằng tf_train_dataset ở đây là lô tf.data.Dataset, +# không phải Hugging Face Dataset gốc, nên len() của nó vốn là 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) +``` + +Cũng lưu ý rằng chúng ta không cung cấp tham số `loss` cho `compile()`. Điều này là do các mô hình thực sự có thể tính toán mất mát bên trong - nếu bạn biên dịch mà không mất mát và cung cấp các nhãn của bạn trong từ điển đầu vào (như chúng ta làm trong bộ dữ liệu của mình), thì mô hình sẽ huấn luyện bằng cách sử dụng mất mát nội bộ đó, điều này sẽ thích hợp cho tác vụ và loại mô hình bạn đã chọn. + +Tiếp theo, chúng ta xác định một `PushToHubCallback` để tải mô hình lên Hub trong quá trình huấn luyện và phù hợp với mô hình với lệnh gọi lại đó: + +```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, +) +``` + +Bạn có thể chỉ định tên đầy đủ của kho lưu trữ mà bạn muốn đẩy đến bằng tham số `hub_model_id` (đặc biệt, bạn sẽ phải sử dụng tham số này để đẩy đến một tổ chức). Ví dụ: khi chúng ta đẩy mô hình vào tổ chức [`huggingface-course`](https://huggingface.co/huggingface-course), chúng ta đã thêm `hub_model_id="huggingface-course/bert-finetuned-ner"`. Theo mặc định, kho lưu trữ được sử dụng sẽ nằm trong không gian tên của bạn và được đặt tên theo thư mục đầu ra mà bạn đã đặt, ví dụ: `"cool_huggingface_user/bert-finetuned-ner"`. + + + +💡 Nếu thư mục đầu ra bạn đang sử dụng đã tồn tại, nó cần phải là bản sao cục bộ của kho lưu trữ mà bạn muốn đẩy đến. Nếu không, bạn sẽ gặp lỗi khi gọi `model.fit()` và sẽ cần đặt tên mới. + + + +Lưu ý rằng trong khi quá trình huấn luyện diễn ra, mỗi khi mô hình được lưu (ở đây, mỗi epoch), nó sẽ được tải lên Hub ở chế độ nền. Bằng cách này, bạn sẽ có thể tiếp tục quá trình huấn luyện của mình trên một máy khác nếu cần. + +Ở giai đoạn này, bạn có thể sử dụng tiện ích luận suy trên Model Hub để kiểm tra mô hình của mình và chia sẻ với bạn bè. Bạn đã tinh chỉnh thành công một mô hình trong tác vụ phân loại token - xin chúc mừng! Nhưng mô hình của chúng ta thực sự tốt đến mức nào? Chúng ta nên có một số chỉ số hay thước đo để đánh giá. + +{/if} + +### Thước đo + +{#if fw === 'pt'} + +Để `Trainer` tính toán một thước đo cho mỗi epoch, ta sẽ cần định nghĩa hàm `compute_metrics()` nhận một array các dự đoán và nhãn, và trả về một từ điển với tên các thước đo và giá trị tương ứng. + +Khung truyền thống được sử dụng để đánh giá phân loại token là [_seqeval_](https://github.com/chakki-works/seqeval). Để sử dụng thước đo này, ta sẽ cần cài đặt thư viện _seqeval_: + +```py +!pip install seqeval +``` + +Chúng ta có thể tải nó qua hàm `evaluate.load()` như đã làm ở [Chương 3 3](/course/chapter3): + +{:else} + +Khung truyền thống được sử dụng để đánh giá phân loại token là [_seqeval_](https://github.com/chakki-works/seqeval). Để sử dụng thước đo này, ta sẽ cần cài đặt thư viện _seqeval_: + +```py +!pip install seqeval +``` + +Chúng ta có thể tải nó qua hàm `evaluate.load()` như đã làm ở [Chương 3 3](/course/chapter3): + +{/if} + +```py +import evaluate + +metric = evaluate.load("seqeval") +``` + +Thước đo này không giống như các thước đo độ chính xác thông thương: nó sẽ nhận một danh sách các nhãn như là chuỗi văn bản, không phải số nguyên, nên ta sẽ cần giải mã toàn bộ những dự đoán và nhãn trước khi truyền chúng vào thước đo. Hãy cùng xem nó hoạt động ra sao. Đầu tiên, ta sẽ lấy những nhãn từ mẫu huấn luyện đầu tiên: + +```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'] +``` + +Ta có thể tạo ra những dự đoán giả cho chúng bằng cách thay đổi giá trị ở chỉ mục 2: + +```py +predictions = labels.copy() +predictions[2] = "O" +metric.compute(predictions=[predictions], references=[labels]) +``` + +Lưu ý rằng thước đo nhận danh sách các dự đoán (không chỉ một) và danh sách các nhãn. Đây là đầu ra: + +```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'} + +Điều này đang gửi lại rất nhiều thông tin! Chúng ta nhận được precision, recall, và điểm F1 cho từng thực thể riêng biệt, cũng như tổng thể. Đối với tính toán số liệu của mình, chúng ta sẽ chỉ giữ lại điểm tổng thể, nhưng hãy tinh chỉnh chức năng `compute_metrics()` để trả về tất cả các số liệu bạn muốn báo cáo. + +Hàm `compute_metrics()` này trước tiên lấy argmax của logits để chuyển chúng thành các dự đoán (như thường lệ, logits và xác suất theo cùng một thứ tự, vì vậy chúng ta không cần áp dụng softmax). Sau đó, chúng ta phải chuyển đổi cả nhãn và dự đoán từ số nguyên sang chuỗi. Chúng ta xóa tất cả các giá trị có nhãn là `-100`, sau đó chuyển kết quả đến phương thức `metric.compute()`: + +```py +import numpy as np + + +def compute_metrics(eval_preds): + logits, labels = eval_preds + predictions = np.argmax(logits, axis=-1) + + # Xoá những chỉ mục bị ngó lơ (token đặc biệt) và chuyển chúng thành nhãn + 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"], + } +``` + +Bây giờ điều này đã được thực hiện, chúng ta gần như đã sẵn sàng để xác định `Trainer` của mình. Chúng ta chỉ cần một `model` để tinh chỉnh! + +{:else} + +Điều này đang gửi lại rất nhiều thông tin! Chúng ta nhận được precision, recall, và điểm F1 cho từng thực thể riêng biệt, cũng như tổng thể. Hãy cũng xem chuyện gì xảy ra nêu sta thử sử dụng giá trị dự đoán thực của mô hình để tính ra điểm số thực. + +TensorFlow không giống như việc nối các dự đoán của chúng ta lại với nhau, bởi vì chúng có độ dài chuỗi thay đổi. Điều này có nghĩa là chúng ta không thể chỉ sử dụng `model.predict()` -- hưng điều đó sẽ không ngăn cản chúng ta. Chúng ta sẽ nhận được một số dự đoán tại một thời điểm và nối chúng thành một danh sách dài lớn khi tiếp tục, bỏ các token `-100` tương ứng bị ẩn đi hoặc đệm, sau đó tính toán các số liệu trên danh sách ở cuối: + +```py +import numpy as np + +all_predictions = [] +all_labels = [] +for batch in tf_eval_dataset: + logits = model.predict(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} +``` + +Mô hình của bạn đã làm như thế nào, so với mô hình của chúng tôi? Nếu bạn có những con số tương tự, khóa đào tạo của bạn đã thành công! + +{/if} + +{#if fw === 'pt'} + +### Định nghĩa mô hình + +Vì chúng ta đang giải quyết vấn đề phân loại token, chúng ta sẽ sử dụng lớp `AutoModelForTokenClassification`. Điều chính cần nhớ khi xác định mô hình này là truyền một số thông tin về số lượng nhãn mà chúng ta có. Cách dễ nhất để làm điều này là truyền vào tham số `num_labels`, nhưng nếu chúng ta muốn một tiện ích luận suy hoạt động giống như tiện ích chúng ta đã thấy ở đầu phần này, tốt hơn nên đặt các nhãn tương ứng chính xác thay thế. + +Chúng phải được đặt bởi hai từ điển, `id2label` và `label2id`, chứa các ánh xạ từ ID đến nhãn và ngược lại: + +```py +id2label = {str(i): label for i, label in enumerate(label_names)} +label2id = {v: k for k, v in id2label.items()} +``` + +Giờ ta có thể truyền chúng vào phương thức `AutoModelForTokenClassification.from_pretrained()`, và chúng sẽ được thiết lập trong cấu hình mô hình và sau đó được lưu vả tải lên Hub: + +```py +from transformers import AutoModelForTokenClassification + +model = AutoModelForTokenClassification.from_pretrained( + model_checkpoint, + id2label=id2label, + label2id=label2id, +) +``` + +Giống như khi chúng tôi định nghĩa `AutoModelForSequenceClassification` của mình trong [Chương 3](/course/chapter3), việc tạo mô hình đưa ra cảnh báo rằng một số trọng số không được sử dụng (những trọng số từ đầu huấn luyện trước) và một số trọng số khác được khởi tạo ngẫu nhiên (những trọng số từ đầu phân loại token mới) và mô hình này nên được huấn luyện. Chúng ta sẽ làm điều đó sau một phút, nhưng trước tiên hãy kiểm tra kỹ xem mô hình của chúng ta có đúng số lượng nhãn hay không: + +```python +model.config.num_labels +``` + +```python out +9 +``` + + + +⚠️ Nếu bạn có mô hình với số lượng nhãn sai, bạn sẽ nhận một lỗi khó hiểu khi gọi hàm `Trainer.train()` sau đó (giống như "CUDA error: device-side assert triggered"). Đây là nguyên nhân số một gây ra lỗi do người dùng báo cáo về những lỗi như vậy, vì vậy hãy đảm bảo bạn thực hiện kiểm tra này để xác nhận rằng bạn có số lượng nhãn dự kiến. + + + +### Tinh chỉnh mô hình + +Giờ ta đã sẵn sàng để huấn luyện mô hình của mình! Chúng ta chỉ cần làm hai điều trước khi định nghĩa `Trainer`: đăng nhập vào Hugging Face và định nghĩa các tham số huấn luyện. Nếu bạn đang làm việc với notebook, có một hàm thuận tiện có thể giúp bạn: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Thao tác này sẽ hiển thị một tiện ích mà bạn có thể nhập thông tin đăng nhập Hugging Facecủa mình. + +Nếu bạn không làm việc trong sổ ghi chép, chỉ cần nhập dòng sau vào thiết bị đầu cuối của bạn: + +```bash +huggingface-cli login +``` + +Once this is done, we can define our `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, +) +``` + +Bạn đã từng thấy hầu hết những điều đó trước đây: chúng ta đặt một số siêu tham số (như tốc độ học, số epoch cần luyện tập và giảm trọng lượng) và chúng ta chỉ định `push_to_hub=True` để chỉ ra rằng chúng ta muốn lưu mô hình và đánh giá nó vào cuối mỗi epoch và rằng chúng ta muốn tải kết quả của mình lên Model Hub. Lưu ý rằng bạn có thể chỉ định tên của kho lưu trữ mà bạn muốn đẩy đến bằng tham số `hub_model_id` (cụ thể là bạn sẽ phải sử dụng tham số này để đẩy đến một tổ chức). Ví dụ: khi đẩy mô hình vào tổ chức [`huggingface-course`](https://huggingface.co/huggingface-course) chúng ta đã thêm `hub_model_id="huggingface-course/bert-finetuned-ner"` vào `TrainingArguments`. Theo mặc định, kho lưu trữ được sử dụng sẽ nằm trong không gian tên của bạn và được đặt tên theo thư mục đầu ra mà bạn đã đặt, vì vậy trong trường hợp của chúng tôi, nó sẽ là `"sgugger/bert-finetuned-ner"`. + + + +💡 Nếu thư mục đầu ra bạn đang sử dụng đã tồn tại, nó cần phải là bản sao cục bộ của kho lưu trữ mà bạn muốn đẩy đến. Nếu không, bạn sẽ gặp lỗi khi xác định `Trainer` của mình và sẽ cần đặt một tên mới. + + + +Cuối cùng, chúng ta chỉ cần truyền mọi thứ cho `Trainer` và bắt đầu huấn luyện: + +```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() +``` + +Lưu ý rằng trong khi quá trình huấn luyện diễn ra, mỗi khi mô hình được lưu (ở đây, mỗi epoch), nó sẽ được tải lên Hub ở chế độ nền. Bằng cách này, bạn sẽ có thể tiếp tục huấn luyện của mình trên một máy khác nếu cần. + +Sau khi quá trình huấn luyện hoàn tất, chúng ta sử dụng phương thức `push_to_hub()` để đảm bảo chúng ta tải lên phiên bản mới nhất của mô hình: + +```py +trainer.push_to_hub(commit_message="Training complete") +``` + +Câu lệnh này trả về URL của cam khết nó vừa làm, nếu bạn muốn kiểm tra nó: + +```python out +'https://huggingface.co/sgugger/bert-finetuned-ner/commit/26ab21e5b1568f9afeccdaed2d8715f571d786ed' +``` + +`Trainer` cũng soạn thảo một thẻ mô hình với tất cả các kết quả đánh giá và tải nó lên. Ở giai đoạn này, bạn có thể sử dụng tiện ích luận suy trên Model Hub để kiểm tra mô hình của mình và chia sẻ với bạn bè. Bạn đã tinh chỉnh thành công một mô hình trong tác vụ phân loại token - xin chúc mừng! + +Nếu bạn muốn tìm hiểu sâu hơn một chút về vòng huấn luyện, bây giờ chúng tôi sẽ hướng dẫn bạn cách thực hiện điều tương tự bằng cách sử dụng 🤗 Accelerate. + +## Một vòng huấn luyện tuỳ chỉnh + +Bây giờ chúng ta hãy xem toàn bộ vòng lặp huấn luyện, vì vậy bạn có thể dễ dàng tùy chỉnh các phần bạn cần. Nó sẽ trông rất giống những gì chúng ta đã làm trong [Chương 3](/course/chapter3/4), với một vài thay đổi cho phần đánh giá. + +### Chuẩn bị mọi thứ để huấn luyện + +Đầu tiên, chúng ta cần xây dựng các `DataLoader` từ các tập dữ liệu của mình. Chúng ta sẽ sử dụng lại `data_collator` của mình dưới dạng `collate_fn` và xáo trộn tập huấn luyện, nhưng không phải tập kiểm định: + +```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 +) +``` + +Tiếp theo, chúng ta khôi phục mô hình của mình, để đảm bảo rằng chúng ta không tiếp tục tinh chỉnh từ trước mà bắt đầu lại từ mô hình được huấn luyện trước BERT: + +```py +model = AutoModelForTokenClassification.from_pretrained( + model_checkpoint, + id2label=id2label, + label2id=label2id, +) +``` +Sau đó, chúng tôi sẽ cần một trình tối ưu hóa. Chúng ta sẽ sử dụng `AdamW` cổ điển, giống như `Adam`, nhưng với một bản sửa lỗi trong cách áp dụng weight decay (phân rã trọng số): + +```py +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Once we have all those objects, we can send them to the `accelerator.prepare()` method: + +```py +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + + + +🚨 Nếu bạn huấn luyện trên TPU, bạn sẽ cần chuyển tất cả các đoạn mã ở trên thành một hàm huấn luyện. Xem [Chương 3](/course/chapter3) để biết thêm chi tiết. + + + +Bây giờ, chúng ta đã gửi `train_dataloader` của mình tới `speedrator.prepare()`, chúng ta có thể sử dụng độ dài của nó để tính số bước huấn luyện. Hãy nhớ rằng chúng ta phải luôn làm điều này sau khi chuẩn bị dataloader, vì phương thức đó sẽ thay đổi độ dài của nó. Chúng ta sử dụng một lịch trình tuyến tính cổ điển từ tốc độ học đến 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, +) +``` + +Cuối cùng, để đẩy mô hình của chúng ta lên Hub, chúng ta sẽ cần tạo một đối tượng `Repository` trong một thư mục đang làm việc. Đầu tiên hãy đăng nhập vào Hugging Face, nếu bạn chưa đăng nhập. Chúng ta sẽ xác định tên kho lưu trữ từ ID mô hình mà ta muốn cung cấp cho mô hình của mình (vui lòng thay thế `repo_name` bằng sự lựa chọn của riêng bạn; nó chỉ cần chứa tên người dùng của bạn, đó là những gì hàm `get_full_repo_name ()` thực hiện): + +```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' +``` + +Sau đó, ta có thể sao chép kho lưu trữ đó trong một thư mục cục bộ. Nếu nó đã tồn tại, thư mục cục bộ này phải là bản sao hiện có của kho lưu trữ mà chúng ta đang làm việc: + +```py +output_dir = "bert-finetuned-ner-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +Giờ ta có thể tải mọi thứ ta lưu trong `output_dir` bằng cách gọi phương thức `repo.push_to_hub()`. Điều này sẽ giúp ta tải ngay lập tức mô hình ở cuối mỗi epoch. + +### Vòng lặp huấn luyện + +Bây giờ chúng ta đã sẵn sàng để viết vòng lặp huấn luyện đầy đủ. Để đơn giản hóa phần đánh giá của nó, chúng ta định nghĩa hàm `postprocess()` lấy các dự đoán và nhãn và chuyển đổi chúng thành danh sách các chuỗi, giống như đối tượng `metric` mong đợi: + +```py +def postprocess(predictions, labels): + predictions = predictions.detach().cpu().clone().numpy() + labels = labels.detach().cpu().clone().numpy() + + # Loại bỏ các chỉ mục bị ngó lơ (các token đặc biệt) và chuyển chúng thành nhãn + 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 +``` + +Sau đó, chúng ta có thể viết vòng lặp huấn luyện. Sau khi xác định một thanh tiến trình để theo dõi quá trình huấn luyện diễn ra như thế nào, vòng lặp có ba phần: + +- Bản thân quá trình huấn luyện, là vòng lặp cổ điển trên `train_dataloader`, truyền thẳng qua mô hình, sau đó truyền ngược và tối ưu hóa. +- Đánh giá, trong đó có một điểm mới sau khi nhận được kết quả đầu ra của mô hình trên một lô: vì hai quy trình có thể đã độn các đầu vào và nhãn thành các hình dạng khác nhau, chúng ta cần sử dụng `accelerator.pad_across_processes()`để đưa ra dự đoán và dán nhãn cho cùng một hình dạng trước khi gọi phương thức `collect()`. Nếu không làm điều này, đánh giá sẽ bị lỗi hoặc bị treo vĩnh viễn. Sau đó, chúng ta gửi kết quả đến `metric.add_batch()` và gọi `metric.compute()` khi vòng lặp đánh giá kết thúc. +- Lưu và tải lên, nơi đầu tiên chúng ta lưu mô hình và trình tokenize, sau đó gọi `repo.push_to_hub()`. Lưu ý rằng chúng ta sử dụng đối số `blocks=False` để yêu cầu thư viện 🤗 Hub đẩy vào một quá trình không đồng bộ. Bằng cách này, quá trình huấn luyện tiếp tục diễn ra bình thường và lệnh (dài) này được thực thi ở chế độ nền. + +Đây là mã hoàn chỉnh cho vòng lặp huấn luyện: + +```py +from tqdm.auto import tqdm +import torch + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Huấn luyện + 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) + + # Đánh giá + model.eval() + for batch in eval_dataloader: + with torch.no_grad(): + outputs = model(**batch) + + predictions = outputs.logits.argmax(dim=-1) + labels = batch["labels"] + + # Cần đệm các dự đoán và nhãn để tập hợp + 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"] + }, + ) + + # Lưu và tải + 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 + ) +``` + +Trong trường hợp đây là lần đầu tiên bạn thấy một mô hình được lưu bằng 🤗 Accelerate, hãy dành một chút thời gian để kiểm tra ba dòng mã đi kèm với nó: + +```py +accelerator.wait_for_everyone() +unwrapped_model = accelerator.unwrap_model(model) +unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) +``` + +Dòng đầu tiên đã tự giải thích: nó cho tất cả các quá trình chờ cho đến khi mọi người ở giai đoạn đó trước khi tiếp tục. Điều này là để đảm bảo rằng ta có cùng một mô hình trong mọi quy trình trước khi lưu. Sau đó, chúng ta lấy `unwrapped_model`, là mô hình cơ sở mà ta đã xác định. Phương thức `accelerator.prepare()` thay đổi mô hình để hoạt động trong huấn luyện phân tán, vì vậy nó sẽ không có phương thức `save_pretrained()` nữa; phương thức `accelerator.unwrap_model()` hoàn tác bước đó. Cuối cùng, chúng ta gọi là `save_pretrained()` nhưng yêu cầu phương thức đó sử dụng `accelerator.save()` thay vì `torch.save()`. + +Khi điều này được thực hiện, bạn sẽ có một mô hình tạo ra kết quả khá giống với mô hình được huấn luyện với `Trainer`. Bạn có thể kiểm tra mô hình mà chúng ta đã huấn luyện bằng cách sử dụng đoạn mã này tại [_huggingface-course/bert-finetuned-ner-accelerate_](https://huggingface.co/huggingface-course/bert-finetuned-ner-accelerate). Và nếu bạn muốn kiểm tra bất kỳ tinh chỉnh nào đối với vòng lặp huấn luyện, bạn có thể trực tiếp thực hiện chúng bằng cách chỉnh sửa đoạn mã được hiển thị ở trên! + +{/if} + +## Sử dụng mô hình đã được tinh chỉnh + +Chúng tôi đã chỉ cho bạn cách bạn có thể sử dụng mô hình mà chúng ta đã tinh chỉnh trên Model Hub bằng tiện ích luận suy. Để sử dụng nó cục bộ trong một `pipeline`, bạn chỉ cần chỉ định mã định danh mô hình thích hợp: + +```py +from transformers import pipeline + +# Thay thế nó với checkpoint của ta +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}] +``` + +Tuyệt quá! Mô hình của chúng ta đang hoạt động tốt như mô hình mặc định cho pipeline này! diff --git a/chapters/vi/chapter7/3.mdx b/chapters/vi/chapter7/3.mdx new file mode 100644 index 000000000..0b3ed9827 --- /dev/null +++ b/chapters/vi/chapter7/3.mdx @@ -0,0 +1,1045 @@ + + +# Tinh chỉnh một mô hình ngôn ngữ bị ẩn đi + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Đối với nhiều ứng dụng NLP liên quan đến các mô hình Transformer, bạn có thể chỉ cần lấy một mô hình được huấn luyện trước từ Hugging Face Hub và tinh chỉnh trực tiếp trên dữ liệu của bạn cho tác vụ hiện tại. Với điều kiện là ngữ liệu được sử dụng để huấn luyện trước không quá khác biệt với ngữ liệu được sử dụng để tinh chỉnh, việc học chuyển tiếp thường sẽ mang lại kết quả tốt. + +Tuy nhiên, có một vài trường hợp mà trước tiên bạn sẽ muốn tinh chỉnh các mô hình ngôn ngữ trên dữ liệu của mình, trước khi huấn luyện đầu tác vụ cụ thể. Ví dụ: nếu tập dữ liệu của bạn chứa các hợp đồng pháp lý hoặc các bài báo khoa học, thì mô hình thuần Transformer như BERT thường sẽ coi các từ chuyên môn trong kho dữ liệu của bạn là token hiếm và hiệu suất kết quả có thể kém hơn. Bằng cách tinh chỉnh mô hình ngôn ngữ trên dữ liệu chuyên môn, bạn có thể tăng hiệu suất của nhiều tác vụ xuôi dòng, có nghĩa là bạn thường chỉ phải thực hiện bước này một lần! + +Quá trình tinh chỉnh mô hình ngôn ngữ được huấn luyện trước trên dữ liệu trong mảng này thường được gọi là _domain adapt_ hay _thích ứng chuyên môn_. Nó được phổ biến vào năm 2018 bởi [ULMFiT](https://arxiv.org/abs/1801.06146), là một trong những kiến ​​trúc mạng thần kinh đầu tiên (dựa trên LSTM) để làm cho việc học chuyển tiếp thực sự hiệu quả cho NLP. Một ví dụ về thích ứng chuyên môn với ULMFiT được hiển thị trong hình dưới đây; trong phần này, chúng ta sẽ làm điều tương tự, nhưng với Transformer thay vì LSTM! + +
+ULMFiT. + +
+ +Đến cuối phần này, bạn sẽ có một [mô hình ngôn ngữ bị ẩn đi](https://huggingface.co/huggingface-course/distilbert-base-uncased-finetuned-imdb?text=This+is+a+great+%5BMASK%5D.) trên Hub có thể tự động hoàn thiện câu như dưới đây: + + + +Cùng đi sâu vào thôi! + + + + + +🙋 Nếu các thuật ngữ "mô hình ngôn ngữ bị ẩn đi" và "mô hình huấn luyện trước" nghe có vẻ xa lạ với bạn, hãy xem [Chương 1](/course/chapter1), nơi chúng tôi giải thích tất cả các khái niệm cốt lõi này, kèm theo video! + + + +## Chọn một mô hình huấn luyện trước cho mô hình ngôn ngữ bị ẩn đi + +Để bắt đầu, hãy chọn một mô hình được huấn luyện trước phù hợp để tạo mô hình ngôn ngữ bị ẩn đi. Như được hiển thị trong ảnh chụp màn hình dưới đây, bạn có thể tìm thấy danh sách các ứng cử viên bằng cách áp dụng bộ lọc "Fill-Mask" trên [Hugging Face Hub](https://huggingface.co/models?pipeline_tag=fill-mask&sort=downloads): + +
+Hub models. +
+ +Mặc dù dòng mô hình BERT và RoBERTa được tải xuống nhiều nhất, chúng ta sẽ sử dụng mô hình có tên [DistilBERT](https://huggingface.co/distilbert-base-uncased) +có thể huấn luyện nhanh hơn nhiều mà ít hoặc không bị mất hiệu suất. Mô hình này được huấn luyện bằng cách sử dụng một kỹ thuật đặc biệt có tên là [_knowledge distillation_](https://en.wikipedia.org/wiki/Knowledge_distillation), trong đó một "mô hình giáo viên" lớn như BERT được sử dụng để hướng dẫn huấn luyện "mô hình sinh viên" có ít tham số hơn nhiều. Phần giải thích chi tiết về quá trình chắt lọc kiến ​​thức sẽ đưa chúng ta đi quá xa trong phần này, nhưng nếu bạn quan tâm, bạn có thể đọc tất cả về nó trong [_Natural Language Processing with Transformers_](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/ch05.html) (thường được gọi là sách giáo khoa về Transformer). + +{#if fw === 'pt'} + +Hãy tiếp tục và tải xuống DistilBERT bằng cách sử dụng lớp `AutoModelForMaskedLM`: + +```python +from transformers import AutoModelForMaskedLM + +model_checkpoint = "distilbert-base-uncased" +model = AutoModelForMaskedLM.from_pretrained(model_checkpoint) +``` + +Chúng ta có thể xem mô hình này có bao nhiêu tham số bằng cách gọi phương thức `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} + +Hãy tiếp tục và tải xuống DistilBERT bằng cách sử dụng lớp `AutoModelForMaskedLM`: + +```python +from transformers import TFAutoModelForMaskedLM + +model_checkpoint = "distilbert-base-uncased" +model = TFAutoModelForMaskedLM.from_pretrained(model_checkpoint) +``` + +Chúng ta có thể xem mô hình này có bao nhiêu tham số bằng cách gọi phương thức `summary()`: + +```python +model(model.dummy_inputs) # Xây dựng mô hình +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} + +Với khoảng 67 triệu tham số, DistilBERT nhỏ hơn khoảng hai lần so với mô hình cơ sở BERT, gần như được hiểu là tăng tốc gấp hai lần khi huấn luyện - thật tuyệt! Bây giờ chúng ta hãy xem những loại token nào mô hình này dự đoán là có nhiều khả năng hoàn thành một mẫu văn bản nhỏ: + +```python +text = "This is a great [MASK]." +``` + +Là con người, chúng ta có thể tưởng tượng ra nhiều khả năng đối với token `[MASK]`, ví dụ "day", "ride", hoặc "painting". Đối với các mô hình được huấn luyện trước, các dự đoán phụ thuộc vào kho ngữ liệu mô hình đó huấn luyện, vì nó học cách chọn các mẫu thống kê có trong dữ liệu. Giống BERT, DistilBERT được huấn luyện trước trên bộ dữ liệu [English Wikipedia](https://huggingface.co/datasets/wikipedia) và [BookCorpus](https://huggingface.co/datasets/bookcorpus), nên ta kì vọng các dự đoán cho `[MASK]` sẽ phản ánh các mảng này. Để dự đoán ta cần trình tokenizer của DistilBERT tạo ra các đầu vào cho mô hình, vì vậy hãy tải từ Hub: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +Với một tokenizer và một mô hình, ta có thể truyền các đoạn văn ví dụ tới mô hình, trích xuất logits, và xin ra 5 ứng cử viên: + +{#if fw === 'pt'} + +```python +import torch + +inputs = tokenizer(text, return_tensors="pt") +token_logits = model(**inputs).logits +# Tìm vị trí [MASK] và trích xuất logit +mask_token_index = torch.where(inputs["input_ids"] == tokenizer.mask_token_id)[1] +mask_token_logits = token_logits[0, mask_token_index, :] +# Chọn ứng viên cho [MASK] với logit cao nhất +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 +# Tìm vị trí [MASK] và trích xuất logit +mask_token_index = np.argwhere(inputs["input_ids"] == tokenizer.mask_token_id)[0, 1] +mask_token_logits = token_logits[0, mask_token_index, :] +# PChọn ứng viên cho [MASK] với logit cao nhất +# Chúng ta phủ định mảng trước argsort để lấy logits lớn nhất chứ không phải nhỏ nhất +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.' +``` + +Chúng ta có thể thấy từ kết quả đầu ra rằng các dự đoán của mô hình đề cập đến các thuật ngữ hàng ngày, điều này có lẽ không có gì đáng ngạc nhiên khi dựa trên nền tảng của Wikipedia tiếng Anh. Hãy xem cách chúng ta có thể thay đổi mảng này thành một thứ gì đó thích hợp hơn một chút - các bài đánh giá phim phân cực cao! + +## Bộ dữ liệu + +Để giới thiệu việc thích ứng chuyên môn, chúng ta sẽ sử dụng bộ dữ liệu nổi tiếng [Large Movie Review Dataset](https://huggingface.co/datasets/imdb)(hay viết tắt là IMDb), là tập hợp các bài đánh giá phim thường được dùng để đánh giá các mô hình phân tích cảm xúc. Bằng cách tinh chỉnh DistilBERT trên kho ngữ liệu này, chúng ta hy vọng mô hình ngôn ngữ sẽ điều chỉnh vốn từ vựng của nó từ dữ liệu thực tế của Wikipedia mà nó đã được huấn luyện trước để phù hợp với các yếu tố chủ quan hơn của các bài đánh giá phim. Chúng ta có thể lấy dữ liệu từ Hugging Face Hub bằng hàm `load_dataset()` từ 🤗 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 + }) +}) +``` + +Chúng ta có thể thấy rằng mỗi phần `huấn luyện` và `kiểm thử` bao gồm 25,000 đánh giá, trong khi phần không được gắn nhãn được gọi là `phi giám sát` chứa 50,000 đánh giá. Chúng ta hãy xem một vài mẫu để có ý tưởng về loại văn bản mà ta đang xử lý. Như chúng ta đã thực hiện trong các chương trước của khóa học, chúng ta sẽ xâu chuỗi các hàm `Dataset.shuffle()` và `Dataset.select()` để tạo một mẫu ngẫu nhiên: + +```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' +``` + +Đúng, đây chắc chắn là những bài đánh giá phim, và nếu bạn đủ lớn, bạn thậm chí có thể hiểu nhận xét trong bài đánh giá cuối cùng về việc sở hữu phiên bản VHS 😜! Mặc dù chúng ta sẽ không cần nhãn để cho mô hình ngôn ngữ, nhưng chúng ta có thể thấy rằng `0` biểu thị một đánh giá tiêu cực, trong khi `1` tương ứng với một đánh giá tích cực. + + + +✏️ **Thử nghiệm thôi!** Tạo ra các mẫu ngẫu nhiền từ phần `phi giám sát` và kiểm định xem nhãn của chúng là `0` hay `1`. Khi đang ở đó, bạn cũng có thể kiểm tra xem các nhãn trong phần `huấn luyện` và `kiểm thử` có thực sử là `0` hoặc `1` không -- đây là một phần kiểm tra hữu ích mànhững nhà NLP nên thực hiện đầu dự án!. + + + +Bây giờ chúng ta đã có một cái nhìn nhanh về dữ liệu, hãy đi sâu vào việc chuẩn bị nó cho việc lập mô hình ngôn ngữ bị ẩn đi. Như chúng ta sẽ thấy, có một số bước bổ sung mà người ta cần thực hiện so với các tác vụ phân loại chuỗi mà chúng ta đã thấy trong [Chương 3](/course/chapter3). Đi thôi! + +## Tiền xử lý dữ liệu + + + +Đối với cả mô hình ngôn ngữ tự động hồi quy và bị ẩn đi, một bước tiền xử lý phổ biến là nối tất cả các mẫu và sau đó chia toàn bộ ngữ liệu thành các phần có kích thước bằng nhau. Điều này hoàn toàn khác với cách tiếp cận thông thường, khi chúng ta chỉ cần tokenize các mẫu riêng lẻ. Tại sao lại nối mọi thứ lại với nhau? Lý do là các mẫu riêng lẻ có thể bị cắt ngắn nếu chúng quá dài và điều đó sẽ dẫn đến việc mất thông tin có thể hữu ích cho tác vụ mô hình hóa ngôn ngữ! + +Vì vậy, để bắt đầu, trước tiên chúng ta sẽ tokenize kho tài liệu của mình như bình thường, nhưng _không_ đặt tùy chọn `truncation=True` trong trình tokenize của chúng ta. Chúng ta cũng sẽ lấy các ID từ nếu chúng có sẵn ((chúng sẽ có sẵn nếu ta đang sử dụng công cụ tokenize nhanh, như được mô tả trong [Chương 6](/course/chap6/3)), vì ta sẽ cần chúng sau này để thực hiện che toàn bộ từ. Chúng ta sẽ gói nó trong một hàm đơn giản và trong khi thực hiện, ta sẽ xóa các cột `text` và `label` vì không cần chúng nữa: + +```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 + + +# Dùng batched=True để kích hoạt đa luồng nhanh! +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 + }) +}) +``` + +Vì DistilBERT là một mô hình giống như BERT, chúng ta có thể thấy rằng các văn bản được tokenize bao gồm `input_ids` và `attention_mask` ta đã thấy trong các chương khác, cũng như `word_ids` mà ta đã thêm vào. + +Gờ chúng ta đã tokenize các bài đánh giá phim của mình, bước tiếp theo là nhóm tất cả chúng lại với nhau và chia kết quả thành nhiều phần. Nhưng những khối này phải lớn đến mức nào? Điều này cuối cùng sẽ được xác định bởi dung lượng bộ nhớ GPU mà bạn có sẵn, nhưng điểm khởi đầu tốt là xem kích thước ngữ cảnh tối đa của mô hình là bao nhiêu. Điều này có thể được suy ra bằng cách kiểm tra thuộc tính `model_max_length` của tokenizer: + +```python +tokenizer.model_max_length +``` + +```python out +512 +``` + +Giá trị này có nguồn gốc từ tệp *tokenizer_config.json* được liên kết với một checkpoint; trong trường hợp này, chúng ta có thể thấy rằng kích thước ngữ cảnh là 512 token, giống như với BERT. + + + +✏️ **Thử nghiệm thôi!** Một số mô hình Transformer, như[BigBird](https://huggingface.co/google/bigbird-roberta-base) và [Longformer](hf.co/allenai/longformer-base-4096),có độ dài ngữ cảnh dài hơn nhiều so với BERT và các mô hình Transformer đời đầu khác. Khởi tạo tokenizer cho một trong những checkpoint và xác minh rằng `model_max_length` tương ứng với những gì được trích dẫn trên thẻ mô hình của nó. + + + +Vì vậy, để chạy các thử nghiệm trên GPU như những GPU được tìm thấy trên Google Colab, chúng ta sẽ chọn thứ gì đó nhỏ hơn một chút có thể vừa với bộ nhớ: + +```python +chunk_size = 128 +``` + + + +Lưu ý rằng việc sử dụng kích thước phân đoạn nhỏ có thể gây bất lợi trong các tình huống thực tế, vì vậy bạn nên sử dụng kích thước tương ứng với trường hợp sử dụng mà bạn sẽ áp dụng mô hình của mình. + + + +Bây giờ đến phần thú vị. Để cho biết cách nối hoạt động, hãy lấy một vài bài đánh giá từ bộ huấn luyện được tokenize và in ra số lượng token cho mỗi bài đánh giá: + +```python +# Tạo ra một danh sách các danh sách cho từng đặc trưng +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' +``` + +We can then concatenate all these examples with a simple dictionary comprehension, as follows: + +```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' +``` + +Tuyệt vời, tổng độ dài đã được kiểm tra - vì vậy bây giờ hãy chia các bài đánh giá được nối thành các phần có kích thước được cung cấp bởi `block_size`. Để làm như vậy, chúng ta lặp qua các đặc trưng trong `concatenated_examples` và sử dụng khả năng hiểu danh sách để tạo các phần của từng đặc trưng. Kết quả là một từ điển các khối cho từng đặc trưng: + +```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' +``` + +Như bạn có thể thấy trong ví dụ này, đoạn cuối thường sẽ nhỏ hơn kích thước đoạn tối đa. Có hai chiến lược chính để giải quyết vấn đề này: + +* Bỏ đoạn cuối cùng nếu nó nhỏ hơn `chunk_size`. +* Đệm đoạn cuối cùng cho đến khi độ dài của nó bằng `chunk_size`. + +Chúng tôi sẽ thực hiện cách tiếp cận đầu tiên ở đây, vì vậy hãy gói tất cả logic ở trên trong một hàm duy nhất mà chúng tôi có thể áp dụng cho tập dữ liệu được tokenize của mình: + + +```python +def group_texts(examples): + # Nối tất cả các văn bản + concatenated_examples = {k: sum(examples[k], []) for k in examples.keys()} + # Tính độ dài của các văn bản được nối + total_length = len(concatenated_examples[list(examples.keys())[0]]) + # Chúng tôi bỏ đoạn cuối cùng nếu nó nhỏ hơn chunk_size + total_length = (total_length // chunk_size) * chunk_size + # Chia phần theo 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() + } + # Tạo cột nhãn mới + result["labels"] = result["input_ids"].copy() + return result +``` + +Lưu ý rằng trong bước cuối cùng của `group_texts()`, chúng ta tạo một cột mới `labels` là bản sao của cột `input_ids`. Như chúng ta sẽ thấy ngay sau đây, đó là bởi vì trong mô hình ngôn ngữ bị ẩn đi, mục tiêu là dự đoán các token được che ngẫu nhiên trong lô đầu vào và bằng cách tạo cột `labels`, chúng ta cung cấp sự thật cơ bản cho mô hình ngôn ngữ để học hỏi. + +Bây giờ, hãy áp dụng `group_texts()` cho các tập dữ liệu được tokenize của mình bằng cách sử dụng hàm `Dataset.map()` đáng tin cậy: + +```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 + }) +}) +``` + +Bạn có thể thấy rằng việc nhóm và sau đó phân chia các đoạn văn bản đã tạo ra nhiều mẫu hơn so với 25,000 mẫu ban đầu của chúng ta cho phần tách `huấn luyện` và `kiểm thử`. Đó là bởi vì chúng ta hiện có các mẫu liên quan đến _token liên tục_ trải dài trên nhiều mẫu từ kho tài liệu gốc. Bạn có thể thấy điều này một cách rõ ràng bằng cách tìm kiếm các token đặc biệt `[SEP]` và `[CLS]` trong một trong các phần: + +```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" +``` + +Trong ví dụ này, bạn có thể thấy hai bài đánh giá phim trùng nhau, một bài về phim cấp ba và bài còn lại về tình trạng vô gia cư. Hãy cũng xem các nhãn trông như thế nào cho mô hình ngôn ngữ bị ẩn đi: + +```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" +``` + +Như mong đợi từ hàm `group_texts()` của chúng ta ở trên, hàm này trông giống hệt với `input_ids` đã được giải mã - nhưng sau đó làm thế nào để mô hình của chúng ta có thể học được bất cứ điều gì? Chúng ta đang thiếu một bước quan trọng: chèn token `[MASK]` ở các vị trí ngẫu nhiên trong đầu vào! Hãy xem cách chúng ta có thể thực hiện điều này một cách nhanh chóng trong quá trình tinh chỉnh bằng công cụ đối chiếu dữ liệu đặc biệt. + +## Tinh chỉnh DistilBERT với API `Trainer` + +Tinh chỉnh mô hình ngôn ngữ bị ẩn đi gần giống như tinh chỉnh mô hình phân loại chuỗi, giống như chúng ta đã làm trong [Chương 3](/course/chapter3). Sự khác biệt duy nhất là chúng ta cần một trình đối chiếu dữ liệu đặc biệt có thể che giấu ngẫu nhiên một số token trong mỗi lô văn bản. May mắn thay, 🤗 Transformers được chuẩn bị với một `DataCollatorForLanguageModeling` dành riêng cho tác vụ này. Chúng ta chỉ cần chuyển nó vào tokenizer và tham số `mlm_probability` để chỉ định phần nào trong số các token cần che. Chúng tôi sẽ chọn 15%, là số được sử dụng cho BERT và một lựa chọn phổ biến trong các tài liệu: + +```python +from transformers import DataCollatorForLanguageModeling + +data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm_probability=0.15) +``` + +Để xem cách hoạt động của việc che ngẫu nhiên, hãy cung cấp một vài mẫu cho trình đối chiếu dữ liệu. Vì nó mong đợi một danh sách các `dict`, trong đó mỗi `dict` đại diện cho một đoạn văn bản liền kề, đầu tiên chúng ta lặp tập dữ liệu trước khi cung cấp lô cho bộ đối chiếu. Chúng ta xóa khóa `"word_ids"` cho trình đối chiếu dữ liệu này vì nó không cần chúng: + +```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' +``` + +Tốt, nó đã hoạt động! Chúng ta có thể thấy rằng `[MASK]` đã được chèn ngẫu nhiên tại các vị trí khác nhau trong văn bản. Đây sẽ là những token mà mô hình sẽ phải dự đoán trong quá trình huấn luyện - và cái hay của công cụ đối chiếu dữ liệu là nó sẽ ngẫu nhiên chèn `[MASK]` với mọi lô! + + + +✏️ **Thử nghiệm thôi!** Chạy đoạn mã trên vài lần để xem việc che ngẫu nhiên diễn ra ngay trước mắt bạn! Đồng thời thử thay thế phương thức `tokenizer.decode()` bằng `tokenizer.convert_ids_to_tokens()` để thấy rằng đôi khi một token từ một từ nhất định bị che, chứ không phải những cái khác. + + + +{#if fw === 'pt'} + +Một tác dụng phụ của việc che ngẫu nhiên là các chỉ số đánh giá của chúng ta sẽ không xác định khi sử dụng `Trainer`, vì chúng ta sử dụng cùng một công cụ đối chiếu dữ liệu cho các tập huấn luyện và kiểm thủ. Chúng ta sẽ thấy ở phần sau, khi chúng ta xem xét việc tinh chỉnh với 🤗 Accelerate, chúng ta có thể sử dụng tính linh hoạt của vòng đánh giá tùy chỉnh như thế nào để đóng băng tính ngẫu nhiên. + +{/if} + +Khi huấn luyện các mô hình để tạo mô hình ngôn ngữ bị ẩn đi, một kỹ thuật có thể được sử dụng là ghép các từ lại với nhau, không chỉ các token riêng lẻ. Cách tiếp cận này được gọi là _whole word masking_ hay _che toàn bộ từ_. Nếu chúng ta muốn che toàn bộ từ, chúng ta sẽ cần phải tự xây dựng một bộ đối chiếu dữ liệu. Bộ đối chiếu dữ liệu chỉ là một chức năng lấy danh sách các mẫu và chuyển đổi chúng thành một lô, vì vậy hãy làm điều này ngay bây giờ! Chúng ta sẽ sử dụng các ID từ đã tính toán trước đó để tạo bản đồ giữa các chỉ số từ và các mã thông báo tương ứng, sau đó quyết định ngẫu nhiên những từ nào cần che và che các đầu vào. Lưu ý rằng tất cả các nhãn đều là `-100` ngoại trừ các nhãn tương ứng với các từ bị che. + +{#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") + + # Tạo ra ánh xạ giữa các từ và chỉ mục token tương ứng + 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) + + # Che ngẫu nhiền từ + 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 + + 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") + + # Tạo ra ánh xạ giữa các từ và chỉ mục token tương ứng + 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) + + # Che ngẫu nhiền từ + 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 + + return tf_default_data_collator(features) +``` + +{/if} + +Tiếp theo, ta cso thể thử trên một vài mẫu như trên: + +```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' +``` + + + +✏️ **Thử nghiệm thôi!** Chạy đoạn mã trên vài lần để xem việc che ngẫu nhiên diễn ra ngay trước mắt bạn! Đồng thời thử thay thế phương thức `tokenizer.decode()` bằng `tokenizer.convert_ids_to_tokens()` để thấy rằng đôi khi một token từ một từ nhất định bị che, chứ không phải những cái khác. + + + +Giờ chúng ta có hai trình đối chiếu dữ liệu, phần còn lại của các bước tinh chỉnh là tiêu chuẩn. Quá trình huấn luyện có thể mất một khoảng thời gian trên Google Colab nếu bạn không đủ may mắn để đạt được GPU P100 thần thoại 😭, vì vậy, trước tiên chúng ta sẽ giảm kích thước của tập huấn luyện xuống còn vài nghìn mẫu. Đừng lo lắng, chúng ta sẽ vẫn nhận được một mô hình ngôn ngữ khá tốt! Một cách nhanh chóng để giảm mẫu một tập dữ liệu trong 🤗 Datasets là thông qua hàm `Dataset.train_test_split()` mà chúng ta đã thấy trong [Chapter 5](/course/chapter5): + +```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 + }) +}) +``` + +Điều này đã tự động tạo các phần tách `huấn luyện` và `kiểm thử` mới, với kích thước tập huấn luyện được đặt thành 10,000 mẫu và xác thực được đặt thành 10% - vui lòng tăng điều này nếu bạn có GPU mạnh! Điều tiếp theo chúng ta cần làm là đăng nhập vào Hugging Face Hub. Nếu bạn đang chạy mã này trong notebook, bạn có thể làm như vậy với chức năng tiện ích sau: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +sẽ hiển thị một tiện ích mà bạn có thể nhập thông tin đăng nhập của mình. Ngoài ra, bạn có thể chạy: + +``` +huggingface-cli login +``` + +trong thiết bị đầu cuối yêu thích của bạn và đăng nhập ở đó. + +{#if fw === 'tf'} + +Khi đã đăng nhập, chúng ta có thể tạo tập dữ liệu `tf.data` của mình. Chúng tôi sẽ chỉ sử dụng trình đối chiếu dữ liệu tiêu chuẩn ở đây, nhưng bạn cũng có thể thử trình đối chiếu che toàn bộ từ và so sánh kết quả như một bài tập: + +```python +tf_train_dataset = downsampled_dataset["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) + +tf_eval_dataset = downsampled_dataset["test"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=32, +) +``` + +Tiếp theo, chúng ta thiết lập các siêu tham số huấn luyện và biên dịch mô hình. Chúng ta sử dụng hàm `create_optimizer()` từ thư viện 🤗 Transformers, cung cấp cho chúng ta trình tối ưu hóa `AdamW` với phân rã tốc độ học tuyến tính. Chúng ta cũng sử dụng hàm mất mát có sẵn của mô hình, là mặc định khi không có tổn thất nào được chỉ định làm tham số cho `compile()` và đặt độ chính xác huấn luyện thành `"mixed_float16"`. Lưu ý rằng nếu bạn đang sử dụng GPU Colab hoặc GPU khác không có hỗ trợ float16 tăng tốc, bạn có thể nên đổi dòng đó thành chú thích. + +Ngoài ra, chúng ta thiết lập một `PushToHubCallback` sẽ lưu mô hình vào Hub sau mỗi epoch. Bạn có thể chỉ định tên của kho lưu trữ mà bạn muốn đẩy đến bằng tham số `hub_model_id` (cụ thể là bạn sẽ phải sử dụng tham số này để đẩy đến một tổ chức). Ví dụ: để đẩy mô hình vào tổ chức [`huggingface-course`](https://huggingface.co/huggingface-course), chúng tôi đã thêm `hub_model_id="huggingface-course/distilbert-finetuned-imdb"`. Theo mặc định, kho lưu trữ được sử dụng sẽ nằm trong không gian tên của bạn và được đặt tên theo thư mục đầu ra mà bạn đã đặt, vì vậy trong trường hợp của chúng tôi, nó sẽ là `"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) + +# Huấn luyện trong mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") + +callback = PushToHubCallback( + output_dir=f"{model_name}-finetuned-imdb", tokenizer=tokenizer +) +``` + +Bây giờ chúng ta đã sẵn sàng chạy `model.fit()` - nhưng trước khi làm như vậy, hãy xem xét ngắn gọn _perplexity_, là một chỉ số phổ biến để đánh giá hiệu suất của các mô hình ngôn ngữ. + +{:else} + +Khi đã đăng nhập, chúng ta có thể chỉ định các tham số cho `Trainer`: + +```python +from transformers import TrainingArguments + +batch_size = 64 +# In ra sự mất mát khi huấn luyện ở mỗi epoch +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, +) +``` + +Ở đây, chúng ta đã điều chỉnh một số tùy chọn mặc định, bao gồm `log_steps` để đảm bảo theo dõi sự mất mát trong quá trình huấn luyện theo từng epoch. Chúng ta cũng đã sử dụng `fp16=True` để cho phép huấn luyện chính xác hỗn hợp, giúp tăng tốc độ. Theo mặc định, `Trainer` sẽ loại bỏ bất kỳ cột nào không phải là một phần của phương thức `forward()` của mô hình. Điều này có nghĩa là nếu bạn đang sử dụng công cụ che toàn bộ từ, bạn cũng cần đặt `remove_unused_columns=False` để đảm bảo chúng ta không mất cột `word_ids` trong quá trình huấn luyện. + +Lưu ý rằng bạn có thể chỉ định tên của kho lưu trữ mà bạn muốn đẩy đến bằng tham số `hub_model_id` (cụ thể là bạn sẽ phải sử dụng tham số này để đẩy đến một tổ chức). Ví dụ: khi chúng ta đẩy mô hình vào tổ chức [`huggingface-course`](https://huggingface.co/huggingface-course), chúng ta đã thêm `hub_model_id="huggingface-course/distilbert-finetuned-imdb"` vào `TrainingArguments`. Theo mặc định, kho lưu trữ được sử dụng sẽ nằm trong không gian tên của bạn và được đặt tên theo thư mục đầu ra mà bạn đã đặt, vì vậy trong trường hợp của chúng ta, nó sẽ là`"lewtun/distilbert-finetuned-imdb"`. + +Bây giờ chúng ta có tất cả các thành phần để tạo ra `Trainer`. Ở đây chúng ta chỉ sử dụng `data_collator` tiêu chuẩn, nhưng bạn có thể thử toàn bộ công cụ che toàn bộ từ và so sánh kết quả như một bài tập: + +```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, +) +``` + +Giờ chúng ta đã sẵn sàng chạy `trainer.train()` - nhưng trước khi làm như vậy, chúng ta hãy xem xét ngắn gọn _perplexity_, là một chỉ số phổ biến để đánh giá hiệu suất của các mô hình ngôn ngữ. + +{/if} + +### Perplexity cho mô hình ngôn ngữ + + + +Không giống như các tác vụ khác như phân loại văn bản hoặc hỏi đáp mà chúng ta được cung cấp một kho ngữ liệu được gắn nhãn để huấn luyện, với mô hình ngôn ngữ, ta không có bất kỳ nhãn rõ ràng nào. Vậy làm cách nào để xác định điều gì tạo nên một mô hình ngôn ngữ tốt? Giống như tính năng tự động sửa lỗi trong điện thoại của bạn, một mô hình ngôn ngữ tốt là một mô hình ngôn ngữ chỉ định xác suất cao cho các câu đúng ngữ pháp và xác suất thấp cho các câu vô nghĩa. Để giúp bạn biết rõ hơn về hình thức này, bạn có thể tìm thấy toàn bộ tập hợp "tự động sửa lỗi" trực tuyến, trong đó mô hình trong điện thoại đã tạo ra một số hoàn thành khá hài hước (và thường không phù hợp)! + +{#if fw === 'pt'} + +Giả sử bộ kiểm thử của chúng ta bao gồm hầu hết các câu đúng ngữ pháp, thì một cách để đo lường chất lượng của mô hình ngôn ngữ là tính toán xác suất nó gán cho từ tiếp theo trong tất cả các câu của bộ kiểm thử. Khả năng xảy ra cao chỉ ra rằng mô hình không bị "ngạc nhiên" hoặc "bối rối" bởi các mẫu không nhìn thấy và cho thấy nó đã học được các mẫu ngữ pháp cơ bản trong ngôn ngữ. Có nhiều định nghĩa toán học khác nhau về perplexity, nhưng chúng ta sẽ sử dụng định nghĩa là hàm mũ của mất mát entropy chéo. Do đó, chúng ta có thể tính toán perplexity của mô hình được huấn luyện trước của mình bằng cách sử dụng hàm `Trainer.evaluate()` để tính toán mất mát entropy chéo trên tập kiểm thử và sau đó lấy theo cấp số nhân của kết quả: + + +```python +import math + +eval_results = trainer.evaluate() +print(f">>> Perplexity: {math.exp(eval_results['eval_loss']):.2f}") +``` + +{:else} + + +Giả sử bộ kiểm thử của chúng ta bao gồm hầu hết các câu đúng ngữ pháp, thì một cách để đo lường chất lượng của mô hình ngôn ngữ là tính toán xác suất nó gán cho từ tiếp theo trong tất cả các câu của bộ kiểm thử. Khả năng xảy ra cao chỉ ra rằng mô hình không bị "ngạc nhiên" hoặc "bối rối" bởi các mẫu không nhìn thấy và cho thấy nó đã học được các mẫu ngữ pháp cơ bản trong ngôn ngữ. Có nhiều định nghĩa toán học khác nhau về perplexity, nhưng chúng ta sẽ sử dụng định nghĩa là hàm mũ của mất mát entropy chéo. Do đó, chúng ta có thể tính toán perplexity của mô hình được huấn luyện trước của mình bằng cách sử dụng hàm `Trainer.evaluate()` để tính toán mất mát entropy chéo trên tập kiểm thử và sau đó lấy theo cấp số nhân của kết quả: + +```python +import math + +eval_loss = model.evaluate(tf_eval_dataset) +print(f"Perplexity: {math.exp(eval_loss):.2f}") +``` + +{/if} + +```python out +>>> Perplexity: 21.75 +``` + +Perplexity thấp hơn có nghĩa là một mô hình ngôn ngữ tốt hơn và chúng ta có thể thấy ở đây rằng mô hình bắt đầu của chúng ta có một giá trị hơi lớn. Hãy xem liệu chúng ta có thể hạ thấp nó bằng cách tinh chỉnh không! Để làm điều đó, trước tiên chúng ta chạy vòng lặp huấn luyện: + +{#if fw === 'pt'} + +```python +trainer.train() +``` + +{:else} + +```python +model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback]) +``` + +{/if} + +và sau đó tính kết quả perplexity trên tập kiểm thử: + +{#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 +``` + +Tốt -- nó giảm perplexity, cho thấy mô hình đã học được điều gì đó về mảng đánh giá phim! + +{#if fw === 'pt'} + +Sau khi quá trình huấn luyện kết thúc, chúng ta có thể đẩy thẻ mô hình có thông tin huấn luyện vào Hub (các checkpoint được lưu trong quá trình tự huấn luyện): + +Once training is finished, we can push the model card with the training information to the Hub (the checkpoints are saved during training itself): + +```python +trainer.push_to_hub() +``` + +{/if} + + + +✏️ **Đến lượt bạn!** Chạy bướchuấn luyện trên sau khi thay đổi trình thu thập dữ liệu thành che toàn bộ từ. Bạn có nhận được kết quả tốt hơn không? + + + +{#if fw === 'pt'} + +Trong trường hợp của mình, chúng ta không cần làm gì đặc biệt với vòng huấn luyện, nhưng một số trường hợp bạn sẽ cần phải triển khai một số logic tuỳ chỉnh. Với những ứng dụng này, bạn có thể sử dụng 🤗 Accelerate -- hãy cũng xem xem! + +## Tinh chỉnh DistilBERT với 🤗 Accelerate + +Như chúng ta đã thấy với `Trainer`, việc tinh chỉnh mô hình ngôn ngữ bị ản đi rất giống với ví dụ phân loại văn bản từ [Chapter 3](/course/chapter3). Trên thực tế, sự tinh tế duy nhất là việc sử dụng một công cụ đối chiếu dữ liệu đặc biệt và chúng ta đã đề cập đến điều đó trước đó trong phần này! + +Tuy nhiên, chúng ta thấy rằng `DataCollatorForLanguageModeling` cũng áp dụng tính năng che ngẫu nhiên với mỗi lần đánh giá, vì vậy chúng ta sẽ thấy một số biến động về perplexity với mỗi lần chạy huấn luyện. Một cách để loại bỏ tính ngẫu nhiên này là áp dụng che _chỉ một lần_ trên toàn bộ tập kiểm thử, sau đó sử dụng trình đối chiếu dữ liệu mặc định trong 🤗 Transformers để thu thập các lô trong quá trình đánh giá. Để xem cách này hoạt động như thế nào, hãy triển khai một chức năng đơn giản áp dụng che trên một lô, tương tự như lần đầu của chúng ta với `DataCollatorForLanguageModeling`: + +```python +def insert_random_mask(batch): + features = [dict(zip(batch, t)) for t in zip(*batch.values())] + masked_inputs = data_collator(features) + # Tạo ra một cột "masked" mới cho mỗi cột trong bộ dữ liệu + return {"masked_" + k: v.numpy() for k, v in masked_inputs.items()} +``` + +Tiếp theo, chúng ta sẽ áp dụng chức năng này cho tập kiểm thử của mình và bỏ các cột không che để có thể thay thế chúng bằng những cột bị che. Bạn có thể sử dụng che toàn bộ từ bằng cách thay thế `data_collator` ở trên bằng cái thích hợp, trong trường hợp đó, bạn nên xóa dòng đầu tiên tại đây: + +```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", + } +) +``` + +Sau đó, chúng ta có thể thiết lập bộ lưu dữ liệu như bình thường, nhưng ta sẽ sử dụng `default_data_collator` từ 🤗 Transformers cho tập kiểm định: + +```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 +) +``` + +Từ đây, chúng ta làm theo các bước tiêu chuẩn với 🤗 Accelerate. Yêu cầu đầu tiên của công việc là tải một phiên bản mới của mô hình được huấn luyện trước: + +``` +model = AutoModelForMaskedLM.from_pretrained(model_checkpoint) +``` + +Sau đó, chúng ta cần chỉ định trình tối ưu hóa; chúng ta sẽ sử dụng tiêu chuẩn `AdamW`: + +```python +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=5e-5) +``` + +Với những đối tượng này, bây giờ chúng ta có thể chuẩn bị mọi thứ cho quá trình huấn luyện với đối tượng `Accelerator`: + +```python +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + +Bây giờ mô hình, trình tối ưu hóa và bộ ghi dữ liệu của chúng ta đã được định cấu hình, chúng ta có thể chỉ định bộ lập lịch tốc độ học như sau: + +```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, +) +``` + +Chỉ có một điều cuối cùng cần làm trước khi huấn luyện: tạo một kho lưu trữ mô hình trên Hugging Face Hub! Trước tiên, chúng ta có thể sử dụng thư viện 🤗 Hub để tạo tên đầy đủ cho repo của mình: + +```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' +``` + +sau đó tạo và sao chép kho lưu trữ bằng cách sử dụng lớp `Repository` từ 🤗 Hub: + +```python +from huggingface_hub import Repository + +output_dir = model_name +repo = Repository(output_dir, clone_from=repo_name) +``` + +Sau khi thực hiện xong, việc viết ra toàn bộ vòng lặp huấn luyện và đánh giá chỉ là một vấn đề đơn giản: + +```python +from tqdm.auto import tqdm +import torch +import math + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Huấn luyện + 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) + + # Đánh giá + 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}") + + # Lưu và tải + 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 +``` + +Tuyệt vời, chúng tôi đã có thể đánh giá mức độ phức tạp theo từng epoch và đảm bảo rằng nhiều lần chạy huấn luyện có thể tái tạo! + +{/if} + +## Sử dụng mô hình tinh chỉnh của mình + +ạn có thể tương tác với mô hình đã được tinh chỉnh của mình bằng cách sử dụng tiện ích của nó trên Hub hoặc cục bộ với `pipeline` từ 🤗 Transformers. Hãy sử dụng cái sau để tải xuống mô hình của chúng tôi bằng cách sử dụng pipeline `fill-mask`: + +```python +from transformers import pipeline + +mask_filler = pipeline( + "fill-mask", model="huggingface-course/distilbert-base-uncased-finetuned-imdb" +) +``` + +Sau đó, chúng ta có thể cung cấp văn bản mẫu "This is a great [MASK]" và xem 5 dự đoán đầu là gì: + +```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.' +``` + +Gọn gàng - mô hình của chúng ta rõ ràng đã điều chỉnh trọng số của nó để dự đoán các từ liên quan nhiều hơn đến phim! + + + +Điều này kết thúc thử nghiệm đầu tiên của chúng ta với việc huấn luyện một mô hình ngôn ngữ. Trong [phần 6](/course/chapter7/section6), bạn sẽ học cách huấn luyện một mô hình tự động hồi quy như GPT-2 từ đầu; hãy đến đó nếu bạn muốn xem cách bạn có thể huấn luyện trước mô hình Transformer của riêng mình! + + + +✏️ **Thử nghiệm thôi!** Để định lượng lợi ích của việc thích ứng chuyên môn, hãy tinh chỉnh bộ phân loại trên các nhãn IMDb cho cả các checkpoint DistilBERT được huấn luyện trước và tinh chỉnh. Nếu bạn cần bồi dưỡng về phân loại văn bản, hãy xem [Chương 3](/course/chapter3). + + diff --git a/chapters/vi/chapter7/4.mdx b/chapters/vi/chapter7/4.mdx new file mode 100644 index 000000000..d6985ccfc --- /dev/null +++ b/chapters/vi/chapter7/4.mdx @@ -0,0 +1,993 @@ + + +# Dịch máy + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Bây giờ chúng ta hãy đi sâu vào dịch máy. Đây là một [tác vụ chuỗi sang chuỗi](/course/chapter1/7), có nghĩa là đây là một vấn đề có thể được hình thành như đi từ một chuỗi này sang chuỗi khác. Theo nghĩa đó, vấn đề khá giống với [tóm tắt](/course/chapter7/6) và bạn có thể điều chỉnh những gì chúng ta sẽ thấy ở đây thành các vấn đề chuỗi sang chuỗi khác như: + +- **Chuyển văn phong**: Tạo mô hình *dịch* văn bản được viết theo một phong cách nhất định sang một phong cách khác (ví dụ: từ trang trọng sang thông thường hoặc tiếng Anh Shakespearean sang tiếng Anh hiện đại) +- **Hỏi đáp chung**: Tạo một mô hình tạo câu trả lời cho các câu hỏi, dựa trên ngữ cảnh + + + +Nếu bạn có một kho văn bản đủ lớn với hai (hoặc nhiều) ngôn ngữ, bạn có thể huấn luyện một mô hình dịch mới từ đầu giống như chúng ta sẽ làm trong phần [lập mô hình ngôn ngữ nhân quả](/course/chapter7/6). Tuy nhiên, sẽ nhanh hơn nếu tinh chỉnh mô hình dịch hiện có, có thể là mô hình đa ngôn ngữ như mT5 hoặc mBART mà bạn muốn tinh chỉnh cho phù hợp với một cặp ngôn ngữ cụ thể hoặc thậm chí là một mô hình chuyên dụng để dịch từ ngôn ngữ này sang ngôn ngữ khác mà bạn muốn tinh chỉnh để phù hợp với kho dữ liệu cụ thể của mình. + +Trong phần này, chúng ta sẽ tinh chỉnh mô hình Marian được huấn luyện trước để dịch từ tiếng Anh sang tiếng Pháp (vì rất nhiều nhân viên của Hugging Face nói cả hai ngôn ngữ đó) trên [tập dữ liệu KDE4](https://huggingface.co/datasets/kde4 ), là tập dữ liệu các tệp được bản địa hóa cho [ứng dụng KDE](https://apps.kde.org/). Mô hình chúng ta sẽ sử dụng đã được huấn luyện trước trên một kho dữ liệu lớn gồm các văn bản tiếng Pháp và tiếng Anh được lấy từ [Tập dữ liệu Opus](https://opus.nlpl.eu/), thực sự chứa tập dữ liệu KDE4. Nhưng ngay cả khi mô hình huấn luyện trước mà chúng ta sử dụng đã nhìn thấy dữ liệu đó trong quá trình huấn luyện trước của nó, chúng ta sẽ thấy rằng ta có thể nhận được phiên bản tốt hơn của nó sau khi tinh chỉnh. + +Sau khi hoàn thành, chúng ta sẽ có một mô hình có thể đưa ra các dự đoán như sau: + + + + +One-hot encoded labels for question answering. + + + +Như trong các phần trước, bạn có thể tìm thấy mô hình thực tế mà chúng ta sẽ huấn luyện và tải lên Hub bằng cách sử dụng đoạn mã bên dưới và kiểm tra kỹ các dự đoán của nó [tại đây](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr?text=This+plugin+allows+you+to+automatically+translate+web+pages+between+several+languages.). + +## Chuẩn bị dữ liệu + +Để tinh chỉnh hoặc huấn luyện một mô hình dịch từ đầu, chúng ta sẽ cần một tập dữ liệu phù hợp với tác vụ. Như đã đề cập trước đây, chúng ta sẽ sử dụng [tập dữ liệu KDE4](https://huggingface.co/datasets/kde4) trong phần này, nhưng bạn có thể điều chỉnh đoạn mã để sử dụng dữ liệu của riêng mình khá dễ dàng, miễn là bạn có các cặp của các câu bằng hai ngôn ngữ mà bạn muốn dịch từ và tới. Tham khảo lại [Chương 5](/course/chapter5) nếu bạn cần lời nhắc về cách tải dữ liệu tùy chỉnh của mình trong `Dataset`. + +### Bộ dữ liệu KDE4 + +Như thường lệ, chúng ta tải xuống tập dữ liệu của mình bằng cách sử dụng hàm `load_dataset()`: + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("kde4", lang1="en", lang2="fr") +``` + +Nếu bạn muốn làm việc với một cặp ngôn ngữ khác, bạn có thể chỉ định chúng bằng đoạn mã của chúng. Có tổng số 92 ngôn ngữ có sẵn cho bộ dữ liệu này; bạn có thể thấy tất cả chúng bằng cách mở rộng các thẻ ngôn ngữ trên [thẻ dữ liệu](https://huggingface.co/datasets/kde4) của nó. + +Language available for the KDE4 dataset. + +Hãy xem tập dữ liệu: + +```py +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['id', 'translation'], + num_rows: 210173 + }) +}) +``` + +Chúng ta có 210,173 cặp câu, nhưng chỉ trong một lần tách, vì vậy chúng ta sẽ cần tạo bộ kiểm định của riêng mình. Như chúng ta đã thấy trong [Chương 5](/course/chapter5), `Dataset` có phương thức `train_test_split()` có thể giúp chúng ta. Chúng ta sẽ cung cấp một hạt giống (seed) cho khả năng tái tạo: + +```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 + }) +}) +``` + +Bạn có thể đổi `"test"` thành `"validation"` như sau: + +```py +split_datasets["validation"] = split_datasets.pop("test") +``` + +Bây giờ chúng ta hãy xem xét một phần tử của tập dữ liệu: + +```py +split_datasets["train"][1]["translation"] +``` + +```python out +{'en': 'Default to expanded threads', + 'fr': 'Par défaut, développer les fils de discussion'} +``` + +Chúng ta nhận được một từ điển có hai câu bằng cặp ngôn ngữ mà ta yêu cầu. Một điểm đặc biệt của bộ dữ liệu đầy đủ các thuật ngữ khoa học máy tính kỹ thuật này là chúng đều được dịch hoàn toàn bằng tiếng Pháp. Tuy nhiên, các kỹ sư Pháp thường lười biếng và để lại hầu hết các từ chuyên ngành khoa học máy tính bằng tiếng Anh khi họ nói chuyện. Ví dụ, ở đây, từ "threads" có thể xuất hiện trong một câu tiếng Pháp, đặc biệt là trong một cuộc trò chuyện kỹ thuật; nhưng trong tập dữ liệu này, nó đã được dịch thành đúng hơn là "fils de discussion". Mô hình huấn luyện trước mà chúng ta sử dụng, đã được huấn luyện trước trên một kho ngữ liệu lớn hơn của các câu tiếng Pháp và tiếng Anh, có tùy chọn dễ dàng hơn là để nguyên từ như sau: + +```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'}] +``` + +Một ví dụ khác về hành vi này có thể được nhìn thấy với từ "plugin", đây không phải là một từ chính thức trong tiếng Pháp nhưng hầu hết người bản ngữ sẽ hiểu và không bận tâm đến việc dịch. +Trong tập dữ liệu KDE4, từ này đã được dịch bằng tiếng Pháp một cách chính thống hơn thành "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."} +``` + +Tuy nhiên, mô hình được huấn luyện trước của chúng ta gắn với từ tiếng Anh nhỏ gọn và quen thuộc: + +```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."}] +``` + +Sẽ rất thú vị khi xem liệu mô hình tinh chỉnh của mình có tiếp thu những đặc điểm đó của tập dữ liệu hay không (cảnh báo spoiler: nó sẽ xảy ra). + + + + + +✏️ **Đến lượt bạn!** Một từ tiếng Anh khác thường được sử dụng trong tiếng Pháp là "email". Tìm mẫu đầu tiên trong tập dữ liệu huấn luyện sử dụng từ này. Nó được dịch như thế nào? Làm thế nào để mô hình huấn luyện trước dịch cùng một câu tiếng Anh? + + + +### Chuẩn bị dữ liệu + + + +Bây giờ bạn nên biết điều này: tất cả các văn bản cần được chuyển đổi thành tập hợp các token ID để mô hình có thể hiểu được chúng. Đối với tác vụ này, chúng ta sẽ cần tokenize cả đầu vào và nhãn. Tác vụ đầu tiên của chúng ta là tạo đối tượng `tokenizer`. Như đã lưu ý trước đó, chúng ta sẽ sử dụng mô hình huấn luyện trước từ tiếng Anh sang tiếng Pháp của Marian. Nếu bạn đang thử đoạn mã này với một cặp ngôn ngữ khác, hãy đảm bảo điều chỉnh checkpoint của mô hình. Tổ chức [Helsinki-NLP](https://huggingface.co/Helsinki-NLP) cung cấp hơn một nghìn mô hình bằng nhiều ngôn ngữ. + +```python +from transformers import AutoTokenizer + +model_checkpoint = "Helsinki-NLP/opus-mt-en-fr" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, return_tensors="tf") +``` + +You can also replace the `model_checkpoint` with any other model you prefer from the [Hub](https://huggingface.co/models), or a local folder where you've saved a pretrained model and a tokenizer. + + + +💡 Nếu bạn đang sử dụng trình tokenize đa ngôn ngữ như mBART, mBART-50 hoặc M2M100, bạn sẽ cần đặt mã ngôn ngữ của đầu vào và nhãn của mình trong trình tokenize bằng cách đặt `tokenizer.src_lang` và `tokenizer.tgt_lang` ở bên phải các giá trị. + + + +Việc chuẩn bị dữ liệu của chúng ta khá đơn giản. Chỉ có một điều cần nhớ: bạn xử lý các đầu vào như bình thường, nhưng đối với các nhãn, bạn cần phải bọc tokenizer bên trong trình quản lý ngữ cảnh `as_target_tokenizer()`. + +Trình quản lý ngữ cảnh trong Python được giới thiệu với câu lệnh `with` và rất hữu ích khi bạn có hai hoạt động liên quan để thực thi như một cặp. Ví dụ phổ biến nhất về điều này là khi bạn viết hoặc đọc một tệp, thường được thực hiện bên trong một lệnh như: + +``` +with open(file_path) as f: + content = f.read() +``` + +Ở đây, hai hoạt động liên quan được thực hiện như một cặp là các hành động mở và đóng tệp. Đối tượng tương ứng với tệp đã mở `f` chỉ tồn tại bên trong khối được thụt lề dưới dấu `with`; sự mở đầu xảy ra trước khối đó và đóng ở cuối khối. + +Trong trường hợp này, trình quản lý ngữ cảnh `as_target_tokenizer()` sẽ đặt tokenizer ở ngôn ngữ đầu ra (ở đây, tiếng Pháp) trước khi khối được thụt lề được thực thi, sau đó đặt nó trở lại bằng ngôn ngữ đầu vào (ở đây, tiếng Anh). + +Vì vậy, việc xử lý trước một mẫu trông như thế này: + +```python +en_sentence = split_datasets["train"][1]["translation"]["en"] +fr_sentence = split_datasets["train"][1]["translation"]["fr"] + +inputs = tokenizer(en_sentence) +with tokenizer.as_target_tokenizer(): + targets = tokenizer(fr_sentence) +``` + +Nếu chúng ta quên tokenize các nhãn bên trong trình quản lý ngữ cảnh, chúng sẽ được tokenize bởi trình tokenize đầu vào, trong trường hợp mô hình Marian sẽ không hoạt động tốt chút nào: + +```python +wrong_targets = tokenizer(fr_sentence) +print(tokenizer.convert_ids_to_tokens(wrong_targets["input_ids"])) +print(tokenizer.convert_ids_to_tokens(targets["input_ids"])) +``` + +```python out +['▁Par', '▁dé', 'f', 'aut', ',', '▁dé', 've', 'lop', 'per', '▁les', '▁fil', 's', '▁de', '▁discussion', ''] +['▁Par', '▁défaut', ',', '▁développer', '▁les', '▁fils', '▁de', '▁discussion', ''] +``` + +Như chúng ta có thể thấy, việc sử dụng trình tokenize tiếng Anh để xử lý trước một câu tiếng Pháp dẫn đến nhiều token hơn, vì trình tokenize không biết bất kỳ từ tiếng Pháp nào (ngoại trừ những từ cũng xuất hiện trong tiếng Anh, như "discussion"). + +Cả `inputs` và `targets` đều là từ điển với các khóa thông thường của chúng ta (ID đầu vào, attention mask, v.v.), vì vậy bước cuối cùng là đặt `"labels"` bên trong các đầu vào. Chúng ta thực hiện điều này trong chức năng tiền xử lý mà ta sẽ áp dụng trên các tập dữ liệu: + +```python +max_input_length = 128 +max_target_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, max_length=max_input_length, truncation=True) + + # Thiết lập tokenizer cho nhãn + with tokenizer.as_target_tokenizer(): + labels = tokenizer(targets, max_length=max_target_length, truncation=True) + + model_inputs["labels"] = labels["input_ids"] + return model_inputs +``` + +Note that we set similar maximum lengths for our inputs and outputs. Since the texts we're dealing with seem pretty short, we use 128. + + + +💡 Nếu bạn đang sử dụng mô hình T5 (cụ thể hơn là một trong các checkpoint `t5-xxx`), mô hình sẽ mong đợi các đầu vào văn bản có tiền tố cho biết tác vụ đang thực hiện, chẳng hạn như `translate: English to French:`. + + + + + +⚠️ Chúng ta không chú ý đến attention mask của các nhãn, vì mô hình sẽ không mong đợi điều đó. Thay vào đó, các nhãn tương ứng với token đệm phải được đặt thành `-100` để chúng bị bỏ qua trong tính toán mất mát. Điều này sẽ được thực hiện bởi trình đối chiếu dữ liệu của chúng ta sau này vì chúng ta đang áp dụng đệm động, nhưng nếu bạn sử dụng đệm ở đây, bạn nên điều chỉnh chức năng tiền xử lý để đặt tất cả các nhãn tương ứng với token đệm thành `-100`. + + + +Bây giờ chúng ta có thể áp dụng tiền xử lý đó trong một lần trên tất cả các phần của tập dữ liệu của mình: + +```py +tokenized_datasets = split_datasets.map( + preprocess_function, + batched=True, + remove_columns=split_datasets["train"].column_names, +) +``` + +Bây giờ dữ liệu đã được tiền xử lý, chúng ta đã sẵn sàng để tinh chỉnh mô hình tiền xử lý của mình! + +{#if fw === 'pt'} + +## Tinh chỉnh mô hình với API `Trainer` + +Đoạn mã thực sử dụng `Trainer` sẽ giống như trước đây, chỉ với một thay đổi nhỏ: chúng ta sử dụng [`Seq2SeqTrainer`](https://huggingface.co/transformers/main_classes/trainer.html#seq2seqtrainer) tại đây, là một lớp con của `Trainer` sẽ cho phép chúng ta xử lý tốt việc đánh giá, sử dụng phương thức `generate()` để dự đoán kết quả đầu ra từ các đầu vào. Chúng ta sẽ đi sâu vào vấn đề đó chi tiết hơn khi ta nói về tính toán số liệu. + +Điều đầu tiên, chúng ta cần một mô hình thực tế để tinh chỉnh. Chúng ta sẽ sử dụng API `AutoModel`: + +```py +from transformers import AutoModelForSeq2SeqLM + +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{:else} + +## Tinh chỉnh mô hình với Keras + +Điều đầu tiên, chúng ta cần một mô hình thực tế để tinh chỉnh. Chúng ta sẽ sử dụng API `AutoModel`: + +```py +from transformers import TFAutoModelForSeq2SeqLM + +model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint, from_pt=True) +``` + + + +💡 Checkpoint `Helsinki-NLP/opus-mt-en-fr` chỉ có trọng số PyTorch, nên bạn sẽ nhận được lỗi nếu bạn cố tải mô hình mà không sử dụng tham số `from_pt=True`, thư viện sẽ tự động tải và chuyển các trọng số Pytorch cho bạn. Như bạn có thể thấy, rất đơn giản để chuyển giữa các khung trong 🤗 Transformers! + + + +{/if} + +Lưu ý rằng lần này chúng ta đang sử dụng một mô hình đã được huấn luyện về tác vụ dịch và thực sự có thể được sử dụng, vì vậy không có cảnh báo nào về việc thiếu các trọng số hoặc những trọng số mới được khởi tạo. + +### Đối chiếu dữ liệu + +Chúng ta sẽ cần một công cụ đối chiếu dữ liệu để xử lý phần đệm cho phân phối động. Chúng ta không thể chỉ sử dụng một `DataCollatorWithPadding` như [Chương 3](/course/ chapter3) trong trường hợp này, bởi vì điều đó chỉ đệm các đầu vào (ID đầu vào, attention mask, và loại token ID). Các nhãn của chúng ta cũng phải được đệm theo chiều dài tối đa có trong nhãn. Và, như đã đề cập trước đây, giá trị đệm được sử dụng để đệm các nhãn phải là `-100` chứ không phải token đệm của trình tokenize, để đảm bảo các giá trị đệm đó bị bỏ qua trong tính toán mất mát. + +Tất cả điều này được thực hiện bởi [`DataCollatorForSeq2Seq`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorforseq2seq). Giống như `DataCollatorWithPadding`, nó sử dụng `tokenizer` được sử dụng để xử lý trước các đầu vào, nhưng nó cũng lấy `model`. Điều này là do trình đối chiếu dữ liệu này cũng sẽ chịu trách nhiệm chuẩn bị các ID đầu vào của bộ giải mã, là các phiên bản được dịch chuyển của các nhãn với một token đặc biệt ở đầu. Vì sự thay đổi này được thực hiện hơi khác đối với các kiến ​​trúc khác nhau, nên `DataCollatorForSeq2Seq` cần biết đối tượng `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} + +Để kiểm tra điều này trên một số mẫu, chúng ta chỉ cần gọi nó trong danh sách các ví dụ từ bộ huấn luyện được tokenize của mình: + +```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']) +``` + +Chúng tôi có thể kiểm tra nhãn đã được đệm đến độ dài tối đa của lô hay chưa, bằng cách sử dụng `-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]]) +``` + +Và chúng tôi cũng có thể xem xét các ID đầu vào của bộ giải mã, để biết rằng chúng là các phiên bản được thay đổi của nhãn: + +```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]]) +``` + +Dưới đây là các nhãn cho các phần tử đầu tiên và thứ hai trong tập dữ liệu của mình: + +```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'} + +Chúng ta sẽ truyền `data_collator` vào `Seq2SeqTrainer`. Tiếp theo, chúng ta hãy xem xét chỉ số. + +{:else} + +Ta có thể sử dụng `data_collator` để chuyển mỗi phần dữ liệu thành `tf.data.Dataset`, sẵn sàng để huấn luyện: + +```python +tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) +tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=16, +) +``` + +{/if} + +### Thước đo + + + +{#if fw === 'pt'} + +Tính năng mà `Seq2SeqTrainer` thêm vào lớp cha `Trainer` của nó là khả năng sử dụng phương thức `generate()` trong quá trình đánh giá hoặc dự đoán. Trong quá trình huấn luyện, mô hình sẽ sử dụng `decoder_input_ids` với attention mask đảm bảo nó không sử dụng các token sau token mà nó đang cố gắng dự đoán, để tăng tốc độ huấn luyện. Trong quá trình luận suy, chúng ta sẽ không thể sử dụng những thứ đó vì chúng ta sẽ không có nhãn, vì vậy, bạn nên đánh giá mô hình của mình với cùng một thiết lập. + +Như chúng ta đã thấy trong [Chương 1](/course/chapter1/6), bộ giải mã thực hiện luận suy bằng cách dự đoán từng token - một thứ được triển khai phía sau trong 🤗 Transformers bằng phương thức `generate()`. `Seq2SeqTrainer` sẽ cho phép chúng ta sử dụng phương pháp đó để đánh giá nếu chúng ta đặt `predict_with_generate=True`. + +{/if} + +Chỉ số truyền thống được sử dụng cho bài toán dịch là [điểm BLEU](https://en.wikipedia.org/wiki/BLEU), được giới thiệu trong [một bài báo năm 2002](https://aclanthology.org/P02-1040.pdf) bởi Kishore Papineni và cộng sự. Điểm BLEU đánh giá mức độ gần gũi của bản dịch với nhãn của chúng. Nó không đo lường mức độ dễ hiểu hoặc tính đúng ngữ pháp của các đầu ra được tạo ra của mô hình, nhưng sử dụng các quy tắc thống kê để đảm bảo rằng tất cả các từ trong các đầu ra được tạo cũng xuất hiện trong các nhãn. Ngoài ra, có các quy tắc phạt việc lặp lại các từ giống nhau nếu chúng không được lặp lại trong các nhãn (để tránh mô hình xuất ra các câu như `"the the the"`) và xuất ra các câu ngắn hơn các câu trong nhãn (để tránh mô hình xuất ra các câu như `"the"`). + +Một điểm yếu của BLEU là nó mong đợi văn bản đã được tokenize, điều này gây khó khăn cho việc so sánh điểm giữa các mô hình sử dụng các bộ tokenize khác nhau. Vì vậy, thay vào đó, chỉ số được sử dụng phổ biến nhất cho các mô hình dịch điểm chuẩn ngày nay là [SacreBLEU](https://github.com/mjpost/sacrebleu), giải quyết điểm yếu này (và các chỉ số khác) bằng cách chuẩn hóa bước tokenize. Để sử dụng chỉ số này, trước tiên chúng ta cần cài đặt thư viện SacreBLEU: + +```py +!pip install sacrebleu +``` + +Chúng ta có thể tải nó với `evaluate.load()` như chúng ta đã làm trong [Chương 3](/course/chapter3): + +```py +import evaluate + +metric = evaluate.load("sacrebleu") +``` + +Chỉ số này sẽ lấy văn bản làm đầu vào và nhãn. Nó được thiết kế để chấp nhận một số nhãn có thể chấp nhận được, vì thường có nhiều bản dịch có thể chấp nhận được của cùng một câu - tập dữ liệu ta đang sử dụng chỉ cung cấp một nhãn, nhưng không hiếm trong NLP để tìm tập dữ liệu cung cấp một số câu dưới dạng nhãn. Vì vậy, các dự đoán phải là một danh sách các câu, nhưng các tham chiếu phải là một danh sách các danh sách các câu. + +Hãy thử một mẫu: + +```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} +``` + +Ta nhận được điểm BLEU là 46.75, khá tốt - để tham khảo, mô hình Transformer ban đầu trong bài báo ["Attention Is All You Need"](https://arxiv.org/pdf/1706.03762.pdf) đạt được điểm BLEU là 41.8 cho một tác vụ dịch tương tự giữa tiếng Anh và tiếng Pháp! (Để biết thêm thông tin về các chỉ số riêng lẻ, như `counts` và `bp`, hãy xem [kho lưu trữ SacreBLEU](https://github.com/mjpost/sacrebleu/blob/078c440168c6adc89ba75fe6d63f0d922d42bcfe/sacrebleu/metrics/bleu.py#L74 ).) Mặt khác, nếu chúng ta thử với hai loại dự đoán không tốt (nhiều lần lặp lại hoặc quá ngắn) thường xuất hiện trong các mô hình dịch, chúng ta sẽ nhận được điểm BLEU khá tệ: + +```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} +``` + +Điểm số có thể tăng từ 0 đến 100 và càng cao thì càng tốt. + +{#if fw === 'tf'} + +Để chuyển từ kết quả đầu ra của mô hình thành văn bản mà chỉ số có thể sử dụng, chúng ta sẽ sử dụng phương thức `tokenizer.batch_decode()`. Chúng ta chỉ cần xóa tất cả các `-100` trong các nhãn; tokenizer sẽ tự động làm điều tương tự đối với token đệm. Hãy xác định một hàm sử dụng mô hình và tập dữ liệu của chúng ta và tính toán các số liệu trên đó. Vì việc tạo chuỗi dài có thể chậm, chúng ta lấy mẫu thay thế bộ kiểm định để đảm bảo điều này không chạy mãi mãi: + +```py +import numpy as np + + +def compute_metrics(): + all_preds = [] + all_labels = [] + sampled_dataset = tokenized_datasets["validation"].shuffle().select(range(200)) + tf_generate_dataset = sampled_dataset.to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=4, + ) + for batch in tf_generate_dataset: + predictions = model.generate( + input_ids=batch["input_ids"], attention_mask=batch["attention_mask"] + ) + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + labels = batch["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} + +To get from the model outputs to texts the metric can use, we will use the `tokenizer.batch_decode()` method. We just have to clean up all the `-100`s in the labels (the tokenizer will automatically do the same for the padding token): + +```py +import numpy as np + + +def compute_metrics(eval_preds): + preds, labels = eval_preds + # Trong trường hợp mô hình trả về nhiều hơn logit dự đoán + if isinstance(preds, tuple): + preds = preds[0] + + decoded_preds = tokenizer.batch_decode(preds, skip_special_tokens=True) + + # Thay các gía trị -100 trong nhãn vì ta không giải mã chúng + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + # Thực một một xố hậu xủ lý đơn giản + 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} + +Bây giờ điều này đã hoàn tất, chúng ta đã sẵn sàng tinh chỉnh mô hình của mình! + +### Tinh chỉnh mô hình + +Bước đầu tiên là đăng nhập vào Hugging Face để bạn có thể tải kết quả của mình lên Model Hub. Có một chức năng tiện lợi để giúp bạn làm điều này trong notebook: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Thao tác này sẽ hiển thị một tiện ích mà bạn có thể nhập thông tin đăng nhập Hugging Face của mình. + +Nếu bạn không làm việc trong notebook, chỉ cần nhập dòng sau vào terminal của bạn: + +```bash +huggingface-cli login +``` + +{#if fw === 'tf'} + +Trước khi bắt đầu, hãy xem loại kết quả nào chúng tôi nhận được từ mô hình của mình mà không cần huấn luyện: + +```py +print(compute_metrics()) +``` + +``` +{'bleu': 33.26983701454733} +``` + +Khi điều này được thực hiện, chúng ta có thể chuẩn bị mọi thứ cần để biên dịch và huấn luyện mô hình của mình. Lưu ý việc sử dụng `tf.keras.mixed_precision.set_global_policy("mixed_float16")` - điều này sẽ yêu cầu Keras huấn luyện bằng cách sử dụng float16, điều có thể giúp tăng tốc đáng kể trên các GPU hỗ trợ nó (Nvidia 20xx/V100 hoặc mới hơn). + +```python +from transformers import create_optimizer +from transformers.keras_callbacks import PushToHubCallback +import tensorflow as tf + +# Số bước huấn luyện là số lượng mẫu trong tập dữ liệu, chia cho kích thước lô sau đó nhân +# với tổng số epoch. Lưu ý rằng tf_train_dataset ở đây là tf.data.Dataset theo lô, +# không phải là Hugging Face Dataset ban đầu, vì vậy len() của nó vốn là 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) + +# Huấn luyện trong mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +Tiếp theo, chúng ta xác định một `PushToHubCallback` để tải mô hình lên Hub trong quá trình huấn luyện, như chúng ta đã thấy trong [phần 2]((/course/chapter7/2)), và sau đó chúng ta chỉ cần điều chỉnh mô hình với lệnh gọi lại đó: + +```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, +) +``` + +Lưu ý rằng bạn có thể chỉ định tên của kho lưu trữ mà bạn muốn đẩy lên bằng tham số `hub_model_id` (cụ thể là bạn sẽ phải sử dụng tham số này để đẩy lên một tổ chức). Ví dụ: khi chúng tôi đẩy mô hình vào tổ chức [`huggingface-course`](https://huggingface.co/huggingface-course), chúng ta đã thêm `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"` thành `Seq2SeqTrainingArguments`. Theo mặc định, kho lưu trữ được sử dụng sẽ nằm trong không gian tên của bạn và được đặt tên theo thư mục đầu ra mà bạn đã đặt, vì vậy ở đây nó sẽ là `"sgugger/marian-finetuned-kde4-en-to-fr"` (là mô hình mà chúng tôi đã liên kết với ở đầu phần này). + + + +💡 Nếu thư mục đầu ra bạn đang sử dụng đã tồn tại, nó cần phải là bản sao cục bộ của kho lưu trữ mà bạn muốn đẩy đến. Nếu không, bạn sẽ gặp lỗi khi gọi `model.fit()` và sẽ cần đặt tên mới. + + + +Cuối cùng, hãy xem các chỉ số của chúng ta trông như thế nào khi quá trình huấn luyện đã kết thúc: + +```py +print(compute_metrics()) +``` + +``` +{'bleu': 57.334066271545865} +``` + +Ở giai đoạn này, bạn có thể sử dụng tiện ích luận suy trên Model Hub để kiểm tra mô hình của mình và chia sẻ với bạn bè. Bạn đã tinh chỉnh thành công một mô hình trong tác vụ dịch - xin chúc mừng! + +{:else} + +Khi điều này được thực hiện, chúng ta có thể xác định `Seq2SeqTrainingArguments`. Giống như đối với `Trainer`, chúng ta sử dụng một lớp con của `TrainingArguments` chứa thêm một số trường: + +```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, +) +``` + +Ngoài các siêu tham số thông thường (như tốc độ học, số epoch, kích thước lô và một số phân rã trọng số), đây là một số thay đổi so với những gì chúng ta đã thấy trong các phần trước: + +- Chúng ta không đặt bất kỳ đánh giá thường xuyên nào, vì quá trình đánh giá sẽ mất một khoảng thời gian; chúng ta sẽ chỉ đánh giá mô hình của mình một lần trước khi huấn luyện và sau đó. +- Chúng ta đặt `fp16=True`, giúp tăng tốc quá trình huấn luyện trên các GPU hiện đại. +- Chúng ta đặt `predict_with_generate=True`, như đã thảo luận ở trên. +- Chúng ta sử dụng `push_to_hub=True` để tải mô hình lên Hub vào cuối mỗi epoch. + +Lưu ý rằng bạn có thể chỉ định tên đầy đủ của kho lưu trữ mà bạn muốn đẩy đến bằng tham số `hub_model_id` (đặc biệt, bạn sẽ phải sử dụng tham số này để đẩy đến một tổ chức). Ví dụ: khi chúng ta đẩy mô hình vào tổ chức [`huggingface-course`](https://huggingface.co/huggingface-course), chúng ta đã thêm `hub_model_id="huggingface-course/marian-finetuned-kde4-en-to-fr"` thành `Seq2SeqTrainingArguments`. Theo mặc định, kho lưu trữ được sử dụng sẽ nằm trong không gian tên của bạn và được đặt tên theo thư mục đầu ra mà bạn đã đặt, vì vậy trong trường hợp của chúng tôi, nó sẽ là `"sgugger/marian-finetuned-kde4-en-to-fr"` (là mô hình chúng tôi liên kết đến ở đầu phần này). + + + +💡 Nếu thư mục đầu ra bạn đang sử dụng đã tồn tại, nó cần phải là bản sao cục bộ của kho lưu trữ mà bạn muốn đẩy đến. Nếu không, bạn sẽ gặp lỗi khi xác định `Seq2SeqTrainer` của mình và sẽ cần đặt tên mới. + + + +Cuối cùng, ta chỉ cần truyền mọi thứ cho `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, +) +``` + +Trước khi huấn luyện, trước tiên chúng ta sẽ xem xét điểm mà mô hình của chúng ta nhận được, để kiểm tra kỹ xem chúng ta có đang không làm mọi thứ tồi tệ hơn với việc tinh chỉnh của chúng ta hay không. Lệnh này sẽ mất một chút thời gian, vì vậy bạn có thể uống một ly cà phê trong khi nó thực thi: + +```python +trainer.evaluate(max_length=max_target_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} +``` + +Điểm BLEU là 39 không quá tệ, điều này phản ánh thực tế là mô hình của chúng ta đã rất giỏi trong việc dịch các câu tiếng Anh sang tiếng Pháp. + +Tiếp theo là huấn luyện, cũng sẽ mất một chút thời gian: + +```python +trainer.train() +``` + +Lưu ý rằng trong khi quá trình huấn luyện diễn ra, mỗi khi mô hình được lưu (ở đây, mỗi epoch), nó sẽ được tải lên Hub ở chế độ nền. Bằng cách này, bạn sẽ có thể tiếp tục huấn luyện của mình trên một máy khác nếu cần. + +Sau khi huấn luyện xong, chúng ta đánh giá lại mô hình của mình - hy vọng chúng ta sẽ thấy một số cải thiện trong điểm BLEU! + +```py +trainer.evaluate(max_length=max_target_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} +``` + +Đó là một cải tiến hơn gần 14 điểm, thật tuyệt vời. + +Cuối cùng, chúng ta sử dụng phương thức `push_to_hub()` để đảm bảo tải lên phiên bản mới nhất của mô hình. `Trainer` cũng soạn thảo một thẻ mô hình với tất cả các kết quả đánh giá và tải nó lên. Thẻ mô hình này chứa siêu dữ liệu giúp Model Hub chọn tiện ích con cho bản trình diễn luận suy. Thông thường, không cần phải nói bất cứ điều gì vì nó có thể suy ra tiện ích con phù hợp từ lớp mô hình, nhưng trong trường hợp này, cùng một lớp mô hình có thể được sử dụng cho tất cả các loại vấn đề chuỗi sang chuỗi, vì vậy chúng ta chỉ định đó là một mô hình dịch: + +```py +trainer.push_to_hub(tags="translation", commit_message="Training complete") +``` +Lệnh này trả về URL của cam kết mà nó vừa thực hiện, nếu bạn muốn kiểm tra nó: + +```python out +'https://huggingface.co/sgugger/marian-finetuned-kde4-en-to-fr/commit/3601d621e3baae2bc63d3311452535f8f58f6ef3' +``` + +Ở giai đoạn này, bạn có thể sử dụng tiện ích luận suy trên Model Hub để kiểm tra mô hình của mình và chia sẻ với bạn bè. Bạn đã tinh chỉnh thành công một mô hình trong tác vụ dịch - xin chúc mừng! + +Nếu bạn muốn tìm hiểu sâu hơn một chút về vòng lặp huấn luyện, bây giờ chúng tôi sẽ hướng dẫn bạn cách thực hiện điều tương tự bằng cách sử dụng 🤗 Accelerate. + +{/if} + +{#if fw === 'pt'} + +## Một vòng huấn luyện tùy chỉnh + +Bây giờ chúng ta hãy xem toàn bộ vòng lặp huấn luyện, vì vậy bạn có thể dễ dàng tùy chỉnh các phần bạn cần. Nó sẽ trông rất giống những gì chúng ta đã làm trong [phần 2](/course/chapter7/2) và [Chương 3](/course/chapter3/4). + +### Chuẩn bị mọi thứ cho qua trình huấn luyện + +Bạn đã thấy tất cả điều này một vài lần rồi, vì vậy chúng ta sẽ xem qua đoạn mã khá nhanh. Đầu tiên, chúng ta sẽ xây dựng các `DataLoader` từ các tập dữ liệu của mình, sau khi đặt các tập dữ liệu thành định dạng` "torch" `để chúng ta nhận được các tensor 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 +) +``` + +Tiếp theo, chúng ta khôi phục mô hình của mình, để đảm bảo rằng chúng ta không tiếp tục tinh chỉnh từ trước mà bắt đầu lại từ mô hình đã được huấn luyện trước: + +```py +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +Then we will need an optimizer: + +```py +from transformers import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Khi chúng ta có tất cả các đối tượng đó, chúng ta có thể gửi chúng đến phương thức `accelerator.prepare()`. Hãy nhớ rằng nếu bạn muốn huấn luyện về TPU trong notebook Colab, bạn sẽ cần chuyển tất cả mã này vào một hàm huấn luyện và điều đó sẽ không thực thi bất kỳ ô nào khởi tạo một `Accelerator`. + +```py +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + +Bây giờ, chúng ta đã gửi `train_dataloader` của mình tới `accelerator.prepare()`, chúng ta có thể sử dụng độ dài của nó để tính số bước huấn luyện. Hãy nhớ rằng chúng ta phải luôn làm điều này sau khi chuẩn bị dataloader, vì phương thức đó sẽ thay đổi độ dài của `DataLoader`. Chúng ta sử dụng một lịch trình tuyến tính cổ điển từ tốc độ học đến 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, +) +``` + +Cuối cùng, để đẩy mô hình của mình lên Hub, chúng ta sẽ cần tạo một đối tượng `Repository` trong một thư mục đang làm việc. Đầu tiên hãy đăng nhập vào Hugging Face Hub, nếu bạn chưa đăng nhập. Chúng ta sẽ xác định tên kho lưu trữ từ ID mô hình mà chúng ta muốn cung cấp cho mô hình của mình (vui lòng thay thế `repo_name` bằng sự lựa chọn của riêng bạn; nó chỉ cần chứa tên người dùng của bạn, đó là những gì hàm `get_full_repo_name()` thực hiện): + +```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' +``` + +Sau đó, chúng ta có thể sao chép kho lưu trữ đó trong một thư mục cục bộ. Nếu nó đã tồn tại, thư mục cục bộ này phải là bản sao của kho lưu trữ mà chúng ta đang làm việc: + +```py +output_dir = "marian-finetuned-kde4-en-to-fr-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +Bây giờ chúng ta có thể tải lên bất cứ thứ gì chúng ta lưu trong `output_dir` bằng cách gọi phương thức `repo.push_to_hub()`. Điều này sẽ giúp chúng ta tải lên các mô hình trung gian ở cuối mỗi epoch. + +### Vòng lặp huấn luyện + +Bây giờ chúng ta đã sẵn sàng để viết vòng lặp huấn luyện đầy đủ. Để đơn giản hóa phần đánh giá của nó, chúng ta định nghĩa hàm `postprocess()` này lấy các dự đoán và nhãn và chuyển đổi chúng thành danh sách các chuỗi mà đối tượng `metric` kì vọng: + +```py +def postprocess(predictions, labels): + predictions = predictions.cpu().numpy() + labels = labels.cpu().numpy() + + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + + # Thay -100 trong nhãn vì ta không thế giải mã chúng. + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + # Thực hiện một số hậu xử lý đơn giản + decoded_preds = [pred.strip() for pred in decoded_preds] + decoded_labels = [[label.strip()] for label in decoded_labels] + return decoded_preds, decoded_labels +``` + +Vòng lặp huấn luyện trông rất giống với các vòng lặp trong [phần 2](/course/chapter7/2) và [Chương 3](/course/chapter3), với một vài điểm khác biệt trong phần đánh giá - vì vậy hãy tập trung vào điều đó! + +Điều đầu tiên cần lưu ý là chúng ta sử dụng phương thức `generate()` để tính toán các dự đoán, nhưng đây là một phương thức trên mô hình cơ sở, không phải mô hình được bao bọc 🤗 Accelerate được tạo trong phương thức `prepare()` . Đó là lý do tại sao chúng ta mở mô hình trước, sau đó gọi phương thức này. + +Điều thứ hai là, giống như với [phân loại token](/course/chapter7/2), hai quy trình có thể đã đêm các đầu vào và nhãn thành các hình dạng khác nhau, vì vậy chúng tôi sử dụng `accelerator.pad_across_processes()` để đưa ra các dự đoán và nhãn cùng một hình dạng trước khi gọi phương thức `gather()` . Nếu chúng ta không làm điều này, đánh giá sẽ bị lỗi hoặc bị treo vĩnh viễn. + +```py +from tqdm.auto import tqdm +import torch + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Huấn luyện + 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) + + # Đánh giá + 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"] + + # Cần đệm dự đoán và nhãn để dễ gom lại + 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}") + + # Lưu và tải + 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 +``` + +Khi điều này được thực hiện, bạn sẽ có một mô hình có kết quả khá giống với mô hình được huấn luyện với `Seq2SeqTrainer`. Bạn có thể kiểm tra đoạn mã mà chúng ta đã huấn luyện bằng cách sử dụng mã này tại [*huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate*](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate). Và nếu bạn muốn kiểm tra bất kỳ tinh chỉnh nào đối với vòng lặp huấn luyện, bạn có thể trực tiếp thực hiện chúng bằng cách chỉnh sửa đoạn mã được hiển thị ở trên! + +{/if} + +## Sử dụng mô hình tinh chỉnh + +Chúng tôi đã chỉ cho bạn cách bạn có thể sử dụng mô hình mà ta đã tinh chỉnh trên Model Hub bằng tiện ích luận suy. Để sử dụng nó cục bộ trong một `pipeline`, chúng ta chỉ cần chỉ định mã định danh mô hình thích hợp: + +```py +from transformers import pipeline + +# Thay nó với checkpoint của bạn +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'}] +``` + +Đúng như mong đợi, mô hình được huấn luyện trước của chúng ta đã điều chỉnh kiến thức của nó cho phù hợp với kho ngữ liệu mà chúng ta đã tinh chỉnh và thay vì để nguyên từ "thread" trong tiếng Anh, giờ đây nó đã dịch nó sang phiên bản chính thức tiếng Pháp. Đối với "plugin" cũng vậy: + +```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."}] +``` + +Một ví dụ tuyệt vời khác về thích ứng chuyện môn! + + + +✏️ **Đến lượt bạn!** Mô hình trả về cái gì với từ "email" bạn xác định trước đó? + + diff --git a/chapters/vi/chapter7/5.mdx b/chapters/vi/chapter7/5.mdx new file mode 100644 index 000000000..e3b78514e --- /dev/null +++ b/chapters/vi/chapter7/5.mdx @@ -0,0 +1,1049 @@ + + +# Tóm tắt + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Trong phần này, chúng ta sẽ xem xét cách các mô hình Transformer có thể được sử dụng để cô đọng các tài liệu dài thành các bản tóm tắt, một tác vụ được gọi là _text summarization_ hay _tóm tắt văn bản_. Đây là một trong những tác vụ NLP thách thức nhất vì nó đòi hỏi nhiều khả năng, chẳng hạn như hiểu các đoạn văn dài và tạo ra văn bản mạch lạc nắm bắt các chủ đề chính trong tài liệu. Tuy nhiên, khi được thực hiện tốt, tóm tắt văn bản là một công cụ mạnh mẽ có thể tăng tốc các quy trình kinh doanh khác nhau bằng cách giảm bớt gánh nặng cho các chuyên gia miền phải đọc chi tiết các tài liệu dài. + + + +Mặc dù đã tồn tại nhiều mô hình được tinh chỉnh khác nhau để tóm tắt trên [Hugging Face Hub](https://huggingface.co/models?pipeline_tag=summarization&sort=downloads), hầu hết tất cả các mô hình này chỉ phù hợp với các tài liệu tiếng Anh. Vì vậy, để tạo thêm một điểm nhấn trong phần này, chúng tôi sẽ huấn luyện một mô hình song ngữ cho tiếng Anh và tiếng Tây Ban Nha. Đến cuối phần này, bạn sẽ có một [mô hình](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es) có thể tóm tắt các đánh giá của khách hàng như được hiển thị ở đây: + + + +Như chúng ta sẽ thấy, những bản tóm tắt này ngắn gọn vì chúng được học từ các tiêu đề mà khách hàng cung cấp trong các bài đánh giá sản phẩm của họ. Hãy bắt đầu bằng cách tập hợp một kho ngữ liệu song ngữ phù hợp cho tác vụ này. + +## Chuẩn bị kho ngữ liệu đa ngôn ngữ + +Chúng ta sẽ sử dụng [Multilingual Amazon Reviews Corpus](https://huggingface.co/datasets/amazon_reviews_multi) để tạo trình tóm tắt song ngữ. Tập tài liệu này bao gồm các bài đánh giá sản phẩm của Amazon bằng sáu ngôn ngữ và thường được sử dụng để đánh giá các bộ phân loại đa ngôn ngữ. Tuy nhiên, vì mỗi bài đánh giá đi kèm với một tiêu đề ngắn, chúng ta có thể sử dụng các tiêu đề này làm nhãn tóm tắt cho mô hình của chúng ta để học! Để bắt đầu, hãy tải xuống các tập hợp con tiếng Anh và tiếng Tây Ban Nha từ 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 + }) +}) +``` + +Như bạn có thể thấy, đối với mỗi ngôn ngữ, có 200,000 đánh giá cho phần `huấn luyện` và 5,000 nhận xét cho mỗi phần `kiểm định` và `kiểm thử`. Thông tin đánh giá mà chúng ta quan tâm được chứa trong cột `review_body` và `review_title`. Hãy xem một vài ví dụ bằng cách tạo một hàm đơn giản lấy một mẫu ngẫu nhiên từ tập huấn luyện với các kỹ thuật chúng ta đã học trong [Chương 5](/course/chapter5): + +```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.' +``` + + + +✏️ **Thử nghiệm thôi!** Thay đổi seed ngẫu nhiên trong lệnh `Dataset.shuffle()` để khám phá các bài đánh giá khác trong kho tài liệu. Nếu bạn là người nói tiếng Tây Ban Nha, hãy xem một số bài đánh giá trong `spanish_dataset` để xem liệu các tiêu đề có giống như những bản tóm tắt hợp lý hay không. + + + +Mẫu này cho thấy sự đa dạng của các bài đánh giá mà người ta thường tìm thấy trên mạng, từ tích cực đến tiêu cực (và mọi thứ ở giữa!). Mặc dù ví dụ với tiêu đề "meh" không nhiều thông tin, nhưng các tiêu đề khác trông giống như những bản tóm tắt phù hợp về bản thân các đánh giá. Việc huấn luyện một mô hình tóm tắt cho tất cả 400,000 bài đánh giá sẽ mất quá nhiều thời gian trên một GPU, vì vậy thay vào đó, chúng ta sẽ tập trung vào việc tạo tóm tắt cho một miền sản phẩm. Để biết tên miền mà chúng ta có thể chọn, hãy chuyển đổi `english_dataset` thành `pandas.DataFrame` và tính toán số lượng đánh giá cho mỗi danh mục sản phẩm: + +```python +english_dataset.set_format("pandas") +english_df = english_dataset["train"][:] +# Hiển thị số lượng cho 20 sản phẩm hàng đầu +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 +``` + +Các sản phẩm phổ biến nhất trong tập dữ liệu tiếng Anh là về đồ gia dụng, quần áo và thiết bị điện tử không dây. Tuy nhiên, để gắn bó với chủ đề Amazon, chúng ta hãy tập trung vào việc tóm tắt các bài đánh giá sách - xét cho cùng, đây là những gì công ty được thành lập! Chúng tôi có thể thấy hai danh mục sản phẩm phù hợp với hóa đơn (`book` và `digital_ebook_purchase`), vì vậy, hãy lọc tập dữ liệu bằng cả hai ngôn ngữ chỉ cho các sản phẩm này. Như chúng ta đã thấy trong [Chương 5](/course/chapter5), hàm `Dataset.filter()` cho phép chúng ta cắt một tập dữ liệu rất hiệu quả, vì vậy chúng ta có thể xác định một hàm đơn giản để thực hiện điều này: + +```python +def filter_books(example): + return ( + example["product_category"] == "book" + or example["product_category"] == "digital_ebook_purchase" + ) +``` + +Bây giờ khi chúng ta áp dụng hàm này cho `english_dataset` và `spanish_dataset`, kết quả sẽ chỉ chứa những hàng liên quan đến danh mục sách. Trước khi áp dụng bộ lọc, hãy chuyển định dạng của `english_dataset` từ `"pandas"` trở lại `"arrow"`: + +```python +english_dataset.reset_format() +``` + +Sau đó, chúng tôi có thể áp dụng chức năng bộ lọc và để kiểm tra một mẫu đánh giá để xem chúng có thực sự là về sách hay không: + +```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.' +``` + +Được rồi, chúng ta có thể thấy rằng các bài đánh giá không hoàn toàn về sách và có thể đề cập đến những thứ như lịch và các ứng dụng điện tử như OneNote. Tuy nhiên, mảng này có vẻ thích hợp để huấn luyện một mô hình tóm tắt. Trước khi xem xét các mô hình khác nhau phù hợp cho tác vụ này, chúng ta còn một bước chuẩn bị dữ liệu cuối cùng cần làm: kết hợp các bài đánh giá bằng tiếng Anh và tiếng Tây Ban Nha dưới dạng một đối tượng `DatasetDict` duy nhất. 🤗 Datasets cung cấp một hàm `concatenate_datasets()` tiện dụng (như tên cho thấy) sẽ xếp chồng hai đối tượng `Dataset` lên trên nhau. Vì vậy, để tạo tập dữ liệu song ngữ của mình, chúng ta sẽ lặp lại từng phần dữ liệu, nối các tập dữ liệu cho phần đó và xáo trộn kết quả để đảm bảo mô hình không quá phù hợp với một ngôn ngữ duy nhất: + +```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) + +# Chọn ra một vài mẫu +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' +``` + +Đây chắc chắn là sự kết hợp giữa các bài đánh giá bằng tiếng Anh và tiếng Tây Ban Nha! Bây giờ chúng ta đã có một kho tài liệu huấn luyện, một điều cuối cùng cần kiểm tra là sự phân bố các từ trong các bài đánh giá và tiêu đề của chúng. Điều này đặc biệt quan trọng đối với các tác vụ tóm tắt, trong đó các tóm tắt tham chiếu ngắn trong dữ liệu có thể làm sai lệch mô hình chỉ xuất ra một hoặc hai từ trong các tóm tắt đã tạo. Các biểu đồ bên dưới hiển thị các phân bố từ và chúng ta có thể thấy rằng các tiêu đề bị lệch nhiều về chỉ 1-2 từ: + +
+Word count distributions for the review titles and texts. + +
+ +Để giải quyết vấn đề này, chúng ta sẽ lọc ra các ví dụ có tiêu đề rất ngắn để mô hình có thể tạo ra các bản tóm tắt thú vị hơn. Vì chúng ta đang xử lý các văn bản tiếng Anh và tiếng Tây Ban Nha, chúng ta có thể sử dụng phương pháp thô để phân chia các tiêu đề theo dấu cách và sau đó sử dụng phương pháp `Dataset.filter()` đáng tin cậy của mình như sau: + +```python +books_dataset = books_dataset.filter(lambda x: len(x["review_title"].split()) > 2) +``` + +Bây giờ chúng ta đã chuẩn bị kho tài liệu của mình, hãy cùng xem một vài mẫu Transformer khả thi mà người ta có thể tinh chỉnh nó! + +## Các mô hình cho tóm tắt văn bản + +Nếu bạn nghĩ về nó, tóm tắt văn bản là một loại tác vụ tương tự như dịch máy: chúng ta có một phần nội dung văn bản giống như một bài đánh giá mà ta muốn "dịch" thành một phiên bản ngắn hơn để nắm bắt các tính năng nổi bật của đầu vào. Theo đó, hầu hết các mô hình Transformer để tóm tắt đều áp dụng kiến trúc bộ mã hóa-giải mã mà chúng ta đã gặp lần đầu tiên trong [Chương 1](/course/chapter1), mặc dù có một số ngoại lệ như họ mô hình GPT cũng có thể được sử dụng để tóm tắt trong cài đặt few-shot. Bảng sau đây liệt kê một số mô hình được huấn luyện trước phổ biến có thể được tinh chỉnh để tóm tắt. + +| Mô hình Transformer | Mô tả | Đa ngôn ngữ? | +| :---------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------: | +| [GPT-2](https://huggingface.co/gpt2-xl) | Mặc dù được huấn luyện như một mô hình ngôn ngữ tự hồi quy, bạn có thể dùng GPT-2 để tạo ra các bản tóm tắt bằng cách nối "TL;DR" cuối mỗi đoạn đầu vào. | ❌ | +| [PEGASUS](https://huggingface.co/google/pegasus-large) | Sử dụng hàm mục tiêu huấn luyện trước để dự đoán các câu bị ẩn đi trong văn bản đa câu. Cơ chế này gần với tóm tắt hơn mô hình ngôn ngữ vanilla và đạt điểm cao hơn trên các chuẩn phổ biến. | ❌ | +| [T5](https://huggingface.co/t5-base) | Một kiến trúc Transformer phổ quát tạo ra tất cả các tác vụ trong khung văn bản sang văn bản; ví dụ,định dạng đầu vào cho mô hình để tóm tắt tài liệu là `summarize: ARTICLE`. | ❌ | +| [mT5](https://huggingface.co/google/mt5-base) | Một phiên bản đa ngôn ngữ của T5, được huấn luyện trước trên kho ngữ liệu Common Crawl corpus (mC4), bao gồm 101 ngôn ngữ. | ✅ | +| [BART](https://huggingface.co/facebook/bart-base) | Một kiến trúc Transformer mới với cả bộ mã hóa và giải mã được huấn luyện để tái tạo lại đầu vào bị phá, kết hợp các cơ chế huấn luyện trước của BERT và GPT-2. | ❌ | +| [mBART-50](https://huggingface.co/facebook/mbart-large-50) | Phiên bản đa ngôn ngữ của BART, huấn luyện trước trên 50 ngôn ngữ.| ✅ | + +Như bạn có thể thấy từ bảng này, phần lớn các mô hình Transformer để tóm tắt (và thực sự là hầu hết các tác vụ NLP) là đơn ngữ. Điều này thật tuyệt nếu tác vụ của bạn sử dụng ngôn ngữ "nhiều tài nguyên" như tiếng Anh hoặc tiếng Đức, nhưng bớt dần đối với hàng nghìn ngôn ngữ khác đang được sử dụng trên khắp thế giới. May mắn thay, có một loại mô hình Transformer đa ngôn ngữ, như mT5 và mBART, ra đời để giải cứu ta. Những mô hình này được huấn luyện trước bằng cách sử dụng mô hình ngôn ngữ, nhưng có một điểm khác biệt: thay vì huấn luyện ngữ liệu của một ngôn ngữ, chúng được huấn luyện cùng lúc về các văn bản bằng hơn 50 ngôn ngữ cùng một lúc! + +Chúng ta sẽ tập trung vào mT5, một kiến trúc thú vị dựa trên T5 đã được huấn luyện trước trong khung văn bản sang văn bản. Trong T5, mọi tác vụ NLP được xây dựng dưới dạng tiền tố nhắc như `summarize:` điều kiện nào để mô hình điều chỉnh văn bản được tạo thành lời nhắc. Như thể hiện trong hình bên dưới, điều này làm cho T5 trở nên cực kỳ linh hoạt, vì bạn có thể giải quyết nhiều tác vụ chỉ với một mô hình duy nhất! + +
+Different tasks performed by the T5 architecture. + +
+ +mT5 không sử dụng tiền tố, nhưng chia sẻ phần lớn tính linh hoạt của T5 và có lợi thế là đa ngôn ngữ. Giờ ta đã chọn một mô hình, hãy xem xét việc chuẩn bị dữ liệu để huấn luyện. + + + + +✏️ **Thử nghiệm thôi!** Khi bạn đã làm qua phần này, hãy xem mT5 so với mBART tốt như thế nào bằng cách tinh chỉnh phần sau với các kỹ thuật tương tự. Để có điểm thưởng, bạn cũng có thể thử tinh chỉnh T5 chỉ trên các bài đánh giá tiếng Anh. Vì T5 có tiền tố nhắc đặc biệt, bạn sẽ cần thêm `summarize:` vào trước các mẫu đầu vào trong các bước tiền xử lý bên dưới. + + + +## Tiền xử lý dữ liệu + + + +Tác vụ tiếp theo của chúng ta là tokenize và mã hóa các bài đánh giá và tiêu đề của chúng. Như thường lệ, ta bắt đầu bằng cách tải tokenizer được liên kết với checkpoint mô hình được huấn luyện trước. Chúng ta sẽ sử dụng `mt5-small` làm checkpoint để có thể tinh chỉnh mô hình trong một khoảng thời gian hợp lý: + +```python +from transformers import AutoTokenizer + +model_checkpoint = "google/mt5-small" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + + + +💡 Trong giai đoạn đầu của các dự án NLP của bạn, một phương pháp hay là huấn luyện một lớp các mô hình "nhỏ" trên một mẫu dữ liệu nhỏ. Điều này cho phép bạn gỡ lỗi và lặp lại nhanh hơn đối với quy trình làm việc đầu cuối. Một khi bạn tự tin vào kết quả, bạn luôn có thể mở rộng mô hình bằng cách thay đổi checkpoint của mô hình! + + +Hãy thử nghiệm mT5 tokenizer trên một ví dụ nhỏ: + +```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]} +``` + +Ở đây, chúng ta có thể thấy `input_ids` và `attention_mask` mà chúng ta đã gặp trong các thử nghiệm tinh chỉnh đầu tiên của chúng ta trong [Chương 3](/course/chapter3). Hãy giải mã các ID đầu vào này bằng hàm `convert_ids_to_tokens()` của tokenizer để xem chúng ta đang xử lý loại tokenizer nào: + +```python +tokenizer.convert_ids_to_tokens(inputs.input_ids) +``` + +```python out +['▁I', '▁', 'loved', '▁reading', '▁the', '▁Hung', 'er', '▁Games', ''] +``` + +Ký tự Unicode đặc biệt `▁` và token cuối chuỗi `` cho biết ta đang xử lý SentencePiece tokenizer, dựa trên thuật toán phân đoạn Unigram được thảo luận trong [Chương 6](/course/chapter6). Unigram đặc biệt hữu ích đối với kho ngữ liệu đa ngôn ngữ vì nó cho phép SentencePiece bất khả tri về dấu, dấu câu và thực tế là nhiều ngôn ngữ, như tiếng Nhật, không có ký tự khoảng trắng. + +Để mã hóa kho tài liệu của mình, chúng ta phải xử lý một cách tính tế với tóm tắt: bởi vì các nhãn cũng là văn bản, có thể chúng vượt quá kích thước ngữ cảnh tối đa của mô hình. Điều này có nghĩa là chúng ta cần áp dụng việc cắt bớt cho cả các bài đánh giá và tiêu đề của chúng để đảm bảo không truyền các đầu vào quá dài cho mô hình của mình. Các tokenizer trong 🤗 Transformers cung cấp một hàm `as_target_tokenizer()` tiện lợi cho phép bạn mã hóa các nhãn song song với các đầu vào. Điều này thường được thực hiện bằng cách sử dụng trình quản lý ngữ cảnh bên trong một chức năng tiền xử lý, trước tiên mã hóa các đầu vào, sau đó mã hóa các nhãn dưới dạng một cột riêng biệt. Đây là một ví dụ về một hàm như vậy cho 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 + ) + # Thiết lập tokenizer cho nhãn + with tokenizer.as_target_tokenizer(): + labels = tokenizer( + examples["review_title"], max_length=max_target_length, truncation=True + ) + + model_inputs["labels"] = labels["input_ids"] + return model_inputs +``` + +Hãy xem qua đoạn mã này để hiểu điều gì đang xảy ra. Điều đầu tiên chúng ta đã làm là xác định các giá trị cho `max_input_length` và `max_target_length`, đặt giới hạn trên cho thời lượng các bài đánh giá và tiêu đề của chúng. Vì nội dung đánh giá thường lớn hơn nhiều so với tiêu đề, chúng ta đã điều chỉnh các giá trị này cho phù hợp. Sau đó, trong chính `preprocess_function()`, chúng ta có thể thấy các bài đánh giá được tokenize đầu tiên, tiếp theo là các tiêu đề với `as_target_tokenizer()`. + +Với `preprocess_function()`, việc tokenize toàn bộ kho dữ liệu bằng hàm `Dataset.map()` tiện dụng mà chúng ta đã sử dụng rộng rãi trong suốt khóa học này là một vấn đề đơn giản: + +```python +tokenized_datasets = books_dataset.map(preprocess_function, batched=True) +``` + +Bây giờ kho dữ liệu đã được xử lý trước, chúng ta hãy xem xét một số chỉ số thường được sử dụng để tóm tắt. Như chúng ta sẽ thấy, không có giải pháp dễ dàng và nhanh chóng khi nói đến việc đo lường chất lượng của văn bản do máy tạo ra. + + + +💡 Bạn có thể nhận thấy rằng chúng ta đã sử dụng `batched=True` trong hàm`Dataset.map()` ở trên. Điều này mã hóa các mẫu theo lô 1,000 (mặc định) và cho phép bạn sử dụng khả năng đa luồng của các bộ tokenizer nhanh trong 🤗 Transformers. Nếu có thể, hãy thử sử dụng `batched=True` để tận dụng tối đa quá trình tiền xử lý của bạn! + + + +## Thước đo cho tóm tắt văn bản + + + +So với hầu hết các tác vụ khác mà chúng ta đã đề cập trong khóa học này, việc đo lường hiệu suất của các tác vụ tạo văn bản như tóm tắt hoặc dịch không đơn giản bằng. Ví dụ: được đưa ra một bài đánh giá như "I loved reading the Hunger Games", có nhiều bản tóm tắt hợp lệ, chẳng hạn như "I loved the Hunger Games" hoặc "Hunger Games is a great read". Rõ ràng, việc áp dụng một số loại đối sánh chính xác nào đó giữa bản tóm tắt được tạo và nhãn không phải là một giải pháp tốt - ngay cả con người cũng sẽ đánh giá thấp hơn theo một chỉ số như vậy, bởi vì tất cả chúng ta đều có phong cách viết riêng của mình. + +Để tóm tắt, một trong những chỉ số được sử dụng phổ biến nhất là [điểm ROUGE](https://en.wikipedia.org/wiki/ROUGE_(metric)) (viết tắt của Recall-Oriented Understudy for Gisting Assessment). Ý tưởng cơ bản đằng sau thước đo này là so sánh một bản tóm tắt đã tạo với một tập hợp các bản tóm tắt tham chiếu thường do con người tạo ra. Để làm cho điều này chính xác hơn, giả sử chúng ta muốn so sánh hai bản tóm tắt sau: + +```python +generated_summary = "I absolutely loved reading the Hunger Games" +reference_summary = "I loved reading the Hunger Games" +``` + +Một cách để có thể so sánh chúng là đếm số từ trùng lặp, trong trường hợp này sẽ là 6. Tuy nhiên, điều này hơi thô, vì vậy thay vào đó ROUGE dựa trên việc tính toán điểm số _precision_ và _recall_ cho sự trùng lặp. + + + +🙋 Đừng lo lắng nếu đây là lần đầu tiên bạn nghe nói về precision và recall - chúng ta sẽ cùng nhau điểm qua một số ví dụ rõ ràng để làm rõ tất cả. Các chỉ số này thường gặp trong các tác vụ phân loại, vì vậy nếu bạn muốn hiểu cách xác định precision và recall trong ngữ cảnh đó, chúng tôi khuyên bạn nên xem [hướng dẫn `scikit-learn`](https://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html). + + + +Đối với ROUGE, recall đo lường mức độ tóm tắt tham chiếu thu được từ cái đã tạo. Nếu chúng ta chỉ so sánh các từ, recall có thể được tính theo công thức sau: + +$$ \mathrm{Recall} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, reference\, summary}} $$ + +Đối với ví dụ đơn giản ở trên, công thức này cho phép nhớ hoàn hảo là 6/6 = 1; tức là tất cả các từ trong bản tóm tắt tham chiếu đã được tạo ra bởi mô hình. Điều này nghe có vẻ tuyệt vời, nhưng hãy tưởng tượng nếu bản tóm tắt được tạo của chúng ta là "I really really loved reading the Hunger Games all night". Điều này cũng sẽ có một recall hoàn hảo, nhưng được cho là một bản tóm tắt tồi tệ hơn vì nó dài dòng. Để đối phó với những tình huống này, chúng ta cũng tính toán độ precision, trong ngữ cảnh ROUGE đo lường mức độ liên quan của bản tóm tắt đã tạo: + +$$ \mathrm{Precision} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, generated\, summary}} $$ + +Áp dụng điều này cho bản tóm tắt dài dòng của chúng ta sẽ cho precision là 6/10 = 0.6, thấp hơn đáng kể so với precision 6/7 = 0.86 thu được bằng phương pháp ngắn hơn của mình. Trong thực tế, cả precision và recall thường được tính toán, và sau đó điểm F1 (giá trị trung bình hài hòa của precision và recall) được báo cáo. Chúng ta có thể thực hiện việc này dễ dàng trong 🤗 Datasets bằng cách cài đặt gói `rouge_score` trước: + +```py +!pip install rouge_score +``` + +và sau đó tải chỉ số ROUGE như sau: + +```python +import evaluate + +rouge_score = evaluate.load("rouge") +``` + +Sau đó ta có thể sử dụng hàm `rouge_score.compute()` để tính tất cả các chỉ số trong một lần: + +```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))} +``` + +Chà, có rất nhiều thông tin trong đầu ra đó - tất cả có nghĩa là gì? Đầu tiên, 🤗 Datasets thực sự tính toán khoảng tin cậy cho precision, recall và F1 score; đây là các thuộc tính `low`, `mid` và `high` mà bạn có thể xem ở đây. Hơn nữa, 🤗 Datasets tính toán nhiều điểm ROUGE khác nhau dựa trên các loại văn bản chi tiết khác nhau khi so sánh các tóm tắt đã tạo và tham chiếu. Biến thể `rouge1` là sự chồng chéo của các khối đơn - đây chỉ là một cách nói hoa mỹ để nói về sự chồng chéo của các từ và chính xác là số liệu mà chúng ta đã thảo luận ở trên. Để xác minh điều này, chúng ta hãy lấy ra giá trị `mid` điểm số của mình: + +```python +scores["rouge1"].mid +``` + +```python out +Score(precision=0.86, recall=1.0, fmeasure=0.92) +``` + +Tuyệt vời, precision và recall lại khớp với nhau! Còn những điểm ROUGE khác thì sao? `rouge2` đo lường sự trùng lặp giữa các bigram (hãy nghĩ rằng đó là sự chồng chéo của các cặp từ), trong khi `rougeL` và `rougeLsum` đo lường các chuỗi từ phù hợp dài nhất bằng cách tìm kiếm các chuỗi con chung dài nhất trong các bản tóm tắt được tạo và tham chiếu. "sum" trong `rougeLsum` đề cập đến thực tế là chỉ số này được tính trên toàn bộ bản tóm tắt, trong khi `rougeL` được tính là giá trị trung bình trên các câu riêng lẻ. + + + +✏️ **Thử nghiệm thôi!** Tạo ví dụ của riêng bạn về bản tóm tắt được tạo và tham khảo, và xem liệu điểm kết quả ROUGE có giống với tính toán thủ công dựa trên các công thức về precision và recall hay không. Để có điểm thưởng, hãy chia văn bản thành bigrams và so sánh độ chính xác và thu hồi cho chỉ số `rouge2`. + + + +Chúng tôi sẽ sử dụng các điểm ROUGE này để theo dõi hiệu suất của mô hình, nhưng trước khi làm điều đó, hãy làm điều mà mọi người thực hành NLP giỏi nên làm: tạo một đường cơ sở mạnh mẽ nhưng đơn giản! + +### Tạo một đường cơ sở mạnh mẽ + +Môt mô hình cơ sở cho tóm tắt văn bản, đó là chỉ cần lấy ba câu đầu tiên của một bài báo, thường được gọi là _lead-3_ hay _3 bài đầu_. Chúng ta có thể sử dụng các dấu chấm để theo dõi ranh giới câu, nhưng điều này sẽ không thành công đối với các từ viết tắt như "U.S." hoặc "U.N." - vì vậy thay vào đó, chúng ta sẽ sử dụng thư viện `nltk`, bao gồm một thuật toán tốt hơn để xử lý những trường hợp này. Bạn có thể cài đặt gói bằng cách sử dụng `pip` như sau: + +```python +!pip install nltk +``` + +và sau đó tải các quy tắc dấu câu: + +```python +import nltk + +nltk.download("punkt") +``` + +Tiếp theo, chúng ta nhập tokenizer câu từ `nltk` và tạo một hàm đơn giản để trích xuất ba câu đầu tiên trong một bài đánh giá. Quy ước trong phần tóm tắt văn bản là tách từng phần tóm tắt bằng một dòng mới, vì vậy hãy bao gồm phần này và kiểm tra nó trên một ví dụ huấn luyện: + +```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.' +``` + +Điều này có vẻ hiệu quả, vì vậy bây giờ chúng ta hãy triển khai một hàm trích xuất các "tóm tắt" này từ tập dữ liệu và tính toán điểm ROUGE cho mô hình cơ sở: + +```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"]) +``` + +Sau đó, chúng ta có thể sử dụng hàm này để tính toán điểm ROUGE trên tập kiểm định và kiểm tra chúng một chút bằng cách sử dụng 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} +``` + +Chúng ta có thể thấy rằng điểm `rouge2` thấp hơn đáng kể so với phần còn lại; điều này có thể phản ánh thực tế là tiêu đề bài đánh giá thường ngắn gọn và do đó, mô hình cơ sở của 3 bài đầu quá dài dòng. Bây giờ chúng ta đã có một cơ sở tốt để làm việc, hãy chuyển sự chú ý của chúng ta sang việc tinh chỉnh mT5! + +{#if fw === 'pt'} + +## Tinh chỉnh mT5 với API `Trainer` + +Việc tinh chỉnh một mô hình để tóm tắt rất giống với các tác vụ khác mà chúng ta đã đề cập trong chương này. Điều đầu tiên chúng ta cần làm là tải mô hình được huấn luyện trước từ checkpoint `mt5-small`. Vì tóm tắt là một tác vụ chuỗi sang chuỗi, chúng ta có thể tải mô hình bằng lớp `AutoModelForSeq2SeqLM`, lớp này sẽ tự động tải xuống và lưu vào bộ nhớ cache các trọng số: + +```python +from transformers import AutoModelForSeq2SeqLM + +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{:else} + +## Tinh chỉnh mT5 với Keras + +Việc tinh chỉnh một mô hình để tóm tắt rất giống với các tác vụ khác mà chúng ta đã đề cập trong chương này. Điều đầu tiên chúng ta cần làm là tải mô hình được huấn luyện trước từ checkpoint `mt5-small`. Vì tóm tắt là một tác vụ chuỗi sang chuỗi, chúng ta có thể tải mô hình bằng lớp `TFAutoModelForSeq2SeqLM`, lớp này sẽ tự động tải xuống và lưu vào bộ nhớ cache các trọng số: + +```python +from transformers import TFAutoModelForSeq2SeqLM + +model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{/if} + + + +💡 Nếu bạn đang tự hỏi tại sao bạn không thấy bất kỳ cảnh báo nào về việc tinh chỉnh mô hình trên một tác vụ phía sau, đó là bởi vì đối với các tác vụ chuỗi sang chuỗi, chúng ta giữ tất cả các trọng số của mạng. So sánh mô hình này với mô hình phân loại văn bản trong [Chương 3](/course/chapter3), trong đó phần đầu của mô hình định sẵn được thay thế bằng một mạng được khởi tạo ngẫu nhiên. + + + +Điều tiếp theo chúng ta cần làm là đăng nhập vào Hugging Face Hub. Nếu bạn đang chạy đoạn mã này trong notebook, bạn có thể làm như vậy với hàm tiện ích sau: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +sẽ hiển thị một tiện ích mà bạn có thể nhập thông tin đăng nhập của mình. Ngoài ra, bạn có thể chạy lệnh này trong thiết bị đầu cuối của mình và đăng nhập vào đó: + +``` +huggingface-cli login +``` + +{#if fw === 'pt'} + +Chúng ta sẽ cần tạo tóm tắt để tính điểm ROUGE trong quá trình huấn luyện. May mắn thay, 🤗 Transformers cung cấp các lớp `Seq2SeqTrainingArguments` và `Seq2SeqTrainer` chuyên dụng có thể tự động làm việc này cho chúng ta! Để xem cách này hoạt động như thế nào, trước tiên chúng ta hãy xác định các siêu tham số và các tham số khác cho các thử nghiệm của mình: + +```python +from transformers import Seq2SeqTrainingArguments + +batch_size = 8 +num_train_epochs = 8 +# Hiện thị mất mát huấn luyện mỗi epoch +logging_steps = len(tokenized_datasets["train"]) // batch_size +model_name = model_checkpoint.split("/")[-1] + +args = Seq2SeqTrainingArguments( + output_dir=f"{model_name}-finetuned-amazon-en-es", + evaluation_strategy="epoch", + learning_rate=5.6e-5, + per_device_train_batch_size=batch_size, + per_device_eval_batch_size=batch_size, + weight_decay=0.01, + save_total_limit=3, + num_train_epochs=num_train_epochs, + predict_with_generate=True, + logging_steps=logging_steps, + push_to_hub=True, +) +``` + +Ở đây, tham số `predict_with_generate` đã được đặt để chỉ ra rằng chúng ta nên tạo các bản tóm tắt trong quá trình đánh giá để có thể tính toán điểm ROUGE cho mỗi epoch. Như đã thảo luận trong [Chương 1](/course/chapter1), bộ giải mã thực hiện luận suy bằng cách dự đoán từng token và điều này được thực hiện bởi phương thức `generate()` của mô hình. Đặt `predict_with_generate=True` sẽ cho `Seq2SeqTrainer` sử dụng phương pháp đó để đánh giá. Chúng ta cũng đã điều chỉnh một số siêu tham số mặc định, như tốc độ học, số epoch và giảm trọng số và chúng ta đã đặt tùy chọn `save_total_limit` để chỉ lưu tối đa 3 checkpoint trong quá trình huấn luyện - điều này là do ngay cả phiên bản "nhỏ" của mT5 sử dụng khoảng một GB dung lượng ổ cứng và chúng ta có thể tiết kiệm một chút dung lượng bằng cách giới hạn số lượng bản sao ta lưu. + +Tham số `push_to_hub=True` sẽ cho phép chúng ta đẩy mô hình vào Hub sau khi huấn luyện; bạn sẽ tìm thấy kho lưu trữ bên dưới hồ sơ người dùng của mình ở vị trí được xác định bởi `output_dir`. Lưu ý rằng bạn có thể chỉ định tên của kho lưu trữ mà bạn muốn đẩy đến bằng tham số `hub_model_id` (cụ thể là bạn sẽ phải sử dụng tham số này để đẩy đến một tổ chức). Ví dụ: khi chúng ta đẩy mô hình vào tổ chức [`huggingface-course`](https://huggingface.co/huggingface-course), chúng ta đã thêm `hub_model_id="huggingface-course/mt5-finetuned-amazon-en-es"` thành `Seq2SeqTrainingArguments`. + +Điều tiếp theo chúng ta cần làm là cung cấp cho người huấn luyện một hàm `compute_metrics()` để chúng ta có thể đánh giá mô hình của mình trong quá trình huấn luyện. Để tóm tắt, điều này cần nhiều hơn là đơn giản gọi `rouge_score.compute()` trên các dự đoán của mô hình, vì chúng ta cần _giải mã_ các kết quả đầu ra và nhãn thành văn bản trước khi chúng ta có thể tính điểm ROUGE. Hàm sau thực hiện chính xác điều đó và cũng sử dụng hàm `sent_tokenize()` từ `nltk` để tách các câu tóm tắt bằng các dòng mới: + +```python +import numpy as np + + +def compute_metrics(eval_pred): + predictions, labels = eval_pred + # Giải mã các tóm tắt được tạo ra thành văn bản + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + # Thay -100 vào nhãn vì ta không thể giải mã chúng + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + # Giải mã các tóm tắt mẫu thành văn bản + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + # ROUGE kì vọng dòng mới sau mỗi câu + 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] + # Tính điểm ROUGE + result = rouge_score.compute( + predictions=decoded_preds, references=decoded_labels, use_stemmer=True + ) + # Trích xuất điểm trung vị + result = {key: value.mid.fmeasure * 100 for key, value in result.items()} + return {k: round(v, 4) for k, v in result.items()} +``` + +{/if} + +Tiếp theo, chúng ta cần phải xác định một bộ đối chiếu dữ liệu cho tác vụ chuỗi sang chuỗi của chúng ta. Vì mT5 là mô hình Transformer bộ mã hóa-giải mã, một điều tinh tế khi chuẩn bị các lô là trong quá trình giải mã, chúng ta cần chuyển các nhãn sang phải từng nhãn. Điều này là bắt buộc để đảm bảo rằng bộ giải mã chỉ nhìn thấy các nhãn trước đó chứ không phải nhãn hiện tại hoặc tương lai, điều này sẽ giúp mô hình dễ dàng ghi nhớ. Điều này tương tự như cách áp dụng masked self-attention cho các đầu vào trong một tác vụ như [mô hình ngôn ngữ nhân quả](/course/chapter7/6). + +May mắn thay, 🤗 Transformers cung cấp một bộ đối chiếu `DataCollatorForSeq2Seq` sẽ tự động đệm các đầu vào và nhãn cho chúng ta. Để khởi tạo bộ đối chiếu này, chúng ta chỉ cần cung cấp `tokenizer` và `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} + +Hãy xem những gì mà máy đối chiếu này tạo ra khi được cung cấp một loạt các ví dụ nhỏ. Đầu tiên, chúng ta cần xóa các cột có chuỗi vì trình đối chiếu sẽ không biết cách chèn các phần tử này: + +```python +tokenized_datasets = tokenized_datasets.remove_columns( + books_dataset["train"].column_names +) +``` + +Vì bộ đối chiếu mong đợi một danh sách các `dict`, trong đó mỗi `dict` đại diện cho một ví dụ duy nhất trong tập dữ liệu, chúng ta cũng cần cuộn dữ liệu thành định dạng mong đợi trước khi chuyển nó đến bộ đối chiếu dữ liệu: + +```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]])} +``` + +Điều chính cần chú ý ở đây là mẫu đầu tiên dài hơn thứ hai, do đó, `input_ids` và `attention_mask` của mẫu thứ hai đã được đệm ở bên phải bằng `[PAD]` (có ID là `0`). Tương tự, chúng ta có thể thấy rằng `labels` đã được đệm bằng `-100`, để đảm bảo rằng các token đệm được bỏ qua bởi hàm mất mát. Và cuối cùng, chúng ta có thể thấy một `decoder_input_ids` mới đã chuyển các nhãn sang bên phải bằng cách chèn `[PAD]` vào mục nhập đầu tiên. + +{#if fw === 'pt'} + +Cuối cùng thì chúng ta cũng có tất cả những nguyên liệu cần thiết để luyện tập! Bây giờ chúng ta chỉ cần khởi tạo trình huấn luyện với các tham số tiêu chuẩn: + +```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, +) +``` + +và khởi chạy chương trình huấn luyện của mình: + +```python +trainer.train() +``` + +Trong quá trình huấn luyện, bạn sẽ thấy mất mát huấn luyện giảm và điểm ROUGE tăng lên theo từng epoch. Sau khi quá trình huấn luyện hoàn tất, bạn có thể xem điểm ROUGE cuối cùng bằng cách chạy `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} +``` + +Từ điểm số, chúng ta có thể thấy rằng mô hình của mình đã vượt trội so với mô hình cơ sở với 3 bài đầu tiên - thật tuyệt! Điều cuối cùng cần làm là đẩy các trọng số mô hình vào Hub, như sau: + +``` +trainer.push_to_hub(commit_message="Training complete", tags="summarization") +``` + +```python out +'https://huggingface.co/huggingface-course/mt5-finetuned-amazon-en-es/commit/aa0536b829b28e73e1e4b94b8a5aacec420d40e0' +``` + +Thao tác này sẽ lưu các checkpoint và các tệp cấu hình vào `output_dir`, trước khi tải tất cả các tệp lên Hub. Bằng cách chỉ định tham số `tags`, chúng ta cũng đảm bảo rằng tiện ích con trên Hub sẽ là một tiện ích con dành cho quy trình tóm tắt thay vì tiện ích tạo văn bản mặc định được liên kết với kiến trúc mT5 (để biết thêm thông tin về thẻ mô hình, hãy xem tài liệu [🤗 Hub ](https://huggingface.co/docs/hub/main#how-is-a-models-type-of-inference-api-and-widget-detined)). Đầu ra từ `trainr.push_to_hub())` là một URL tới hàm băm cam kết Git, vì vậy bạn có thể dễ dàng xem các thay đổi đã được thực hiện đối với kho lưu trữ mô hình! + +Để kết thúc phần này, hãy xem cách chúng ta cũng có thể tinh chỉnh mT5 bằng cách sử dụng các tính năng cấp thấp do 🤗 Accelerate cung cấp. + +{:else} + +Chúng tôi gần như đã sẵn sàng để huấn luyện! Chúng ta chỉ cần chuyển đổi tập dữ liệu của mình thành `tf.data.Dataset` bằng cách sử dụng trình đối chiếu dữ liệu đã xác định ở trên, sau đó mô hình `compile()` và `fit()`. Đầu tiên, bộ dữ liệu: + +```python +tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=8, +) +tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=8, +) +``` + +Bây giờ, chúng ta xác định các siêu tham số huấn luyện của mình và biên dịch: + +```python +from transformers import create_optimizer +import tensorflow as tf + +# Số bước huấn luyện là số lượng mẫu trong tập dữ liệu, chia cho kích thước lô sau đó nhân +# với tổng số epoch. Lưu ý rằng tf_train_dataset ở đây là tf.data.Dataset theo lô, +# không phải là Hugging Face Dataset ban đầu, vì vậy len() của nó vốn là 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) + +# Huấn luyện trong mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +And finally, we fit the model. We use a `PushToHubCallback` to save the model to the Hub after each epoch, which will allow us to use it for inference later: + +```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 +) +``` + +Chúng ta nhận được một số giá trị mất mát trong quá trình huấn luyện, nhưng thực sự chúng ta muốn xem các chỉ số ROUGE mà ta đã tính toán trước đó. Để có được các số liệu đó, chúng tôi sẽ cần tạo kết quả đầu ra từ mô hình và chuyển đổi chúng thành chuỗi. Hãy xây dựng một số danh sách nhãn và dự đoán cho chỉ số ROUGE để so sánh (lưu ý rằng nếu bạn gặp lỗi nhập cho phần này, bạn có thể cần phải `!pip install tqdm`): + +```python +from tqdm import tqdm +import numpy as np + +all_preds = [] +all_labels = [] +for batch in tqdm(tf_eval_dataset): + predictions = model.generate(**batch) + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + labels = batch["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) +``` + +Khi chúng ta có danh sách các chuỗi nhãn và chuỗi dự đoán, việc tính toán điểm ROUGE thật dễ dàng: + +```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'} + +## Tinh chỉnh mT5 với 🤗 Accelerate + +Tinh chỉnh mô hình của chúng ta với 🤗 Accelerate rất giống với ví dụ phân loại văn bản mà chúng ta đã gặp trong [Chương 3](/course/chapter3). Sự khác biệt chính sẽ là nhu cầu tạo tóm tắt của chúng ta một cách rõ ràng trong quá trình huấn luyện và xác định cách chúng ta tính điểm ROUGE (nhớ lại rằng `Seq2SeqTrainer` đã làm cho chúng ta). Hãy xem cách chúng ta có thể thực hiện hai yêu cầu này trong 🤗 Accelerate! + +### Chuẩn bị mọi thứ cho huấn luyện + +Điều đầu tiên chúng ta cần làm là tạo một `DataLoader` cho mỗi phần tách của chúng ta. Vì các bộ lưu trữ dữ liệu PyTorch mong đợi hàng loạt các tensor, chúng ta cần đặt định dạng thành `"torch"` trong bộ dữ liệu của mình: + +```python +tokenized_datasets.set_format("torch") +``` + +Bây giờ chúng ta đã có tập dữ liệu chỉ bao gồm các tensor, việc tiếp theo cần làm là khởi tạo lại `DataCollatorForSeq2Seq`. Đối với điều này, chúng ta cần cung cấp một phiên bản mới của mô hình, vì vậy hãy tải lại từ bộ nhớ cache của mình: + +```python +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +Sau đó, chúng ta có thể khởi tạo trình đối chiếu dữ liệu và sử dụng công cụ này để xác định bộ lưu dữ liệu của mình: + +```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 +) +``` + +Điều tiếp theo cần làm là xác định trình tối ưu hóa mà chúng ta muốn sử dụng. Như trong các ví dụ khác, chúng ta sẽ sử dụng `AdamW`, trình hoạt động tốt cho hầu hết các vấn đề: + +```python +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Cuối cùng, chúng ta cung cấp mô hình, trình tối ưu hóa và bộ ghi dữ liệu của mình vào phương thức `accelerator.prepare()`: + +```python +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + + + +🚨 Nếu bạn đang huấn luyện trên TPU, bạn sẽ cần chuyển tất cả đoạn mã ở trên vào một hàm huấn luyện chuyên dụng. Xem [Chương 3(/course/chapter3) để biết thêm chi tiết. + + + +Bây giờ chúng ta đã chuẩn bị các đối tượng của mình, còn ba việc cần làm: + +* Xác định lịch trình tốc độ học. +* Thực hiện chức năng hậu xử lý các bản tóm tắt để đánh giá. +* Tạo một kho lưu trữ trên Hub mà ta có thể đẩy mô hình của mình lên đó. + +Đối với lịch trình tốc độ học, chúng ta sẽ sử dụng lịch trình tuyến tính tiêu chuẩn từ các phần trước: + +```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, +) +``` + +Để hậu xử lý, chúng ta cần một hàm chia các tóm tắt đã tạo thành các câu được phân tách bằng các dòng mới. Đây là định dạng mà chỉ số ROUGE mong đợi và chúng ta có thể đạt được điều này bằng đoạn mã sau: + +```python +def postprocess_text(preds, labels): + preds = [pred.strip() for pred in preds] + labels = [label.strip() for label in labels] + + # ROUGE kì vọng dòng mới sau mỗi câu + 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 +``` + +Điều này sẽ trông quen thuộc với bạn nếu bạn nhớ lại cách chúng ta đã định nghĩa hàm `compute_metrics()` của `Seq2SeqTrainer`. + +Cuối cùng, chúng ta cần tạo một kho lưu trữ mô hình trên Hugging Face Hub. Đối với điều này, chúng ta có thể sử dụng thư viện 🤗 Hub có tiêu đề thích hợp. Chúng ta chỉ cần xác định tên cho kho lưu trữ của mình và thư viện có chức năng tiện ích để kết hợp ID kho lưu trữ với hồ sơ người dùng: + +```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' +``` + +Bây giờ chúng ta có thể sử dụng tên kho lưu trữ này để sao chép phiên bản cục bộ vào thư mục kết quả của chúng ta, nơi sẽ lưu trữ các tạo tác huấn luyện: + +```python +from huggingface_hub import Repository + +output_dir = "results-mt5-finetuned-squad-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +Điều này sẽ cho phép chúng ta đẩy các tạo tác trở lại Hub bằng cách gọi phương thức `repo.push_to_hub()` trong quá trình huấn luyện! Bây giờ chúng ta hãy kết thúc phân tích bằng cách viết ra vòng lặp huấn luyện. + +### Vòng lặp huấn luyện + +Vòng lặp huấn luyện để tóm tắt khá giống với các ví dụ 🤗 Accelerate khác mà chúng ta đã gặp và gần như được chia thành bốn bước chính: + +1. Huấn luyện mô hình bằng cách lặp lại tất cả các ví dụ trong `train_dataloader` cho mỗi epoch. +2. Tạo tóm tắt mô hình vào cuối mỗi epoch, bằng cách tạo token đầu tiên và sau đó giải mã chúng (và tóm tắt tham chiếu) thành văn bản. +3. Tính toán điểm ROUGE bằng các kỹ thuật tương tự mà chúng ta đã thấy trước đó. +4. Lưu các checkpoint và đẩy mọi thứ vào Hub. Ở đây, chúng ta dựa vào tham số `blocking=False` tiện lợi của đối tượng `Repository` để có thể đẩy các checkpoint ở mỗi epoch _bất đồng bộ_. Điều này cho phép ta tiếp tục huấn luyện mà không phải đợi tải lên hơi chậm với mô hình có kích thước GB! + +Các bước này có thể được nhìn thấy trong khối mã sau: + +```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): + # Huấn luyện + 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) + + # Đánh giá + 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"] + + # Nếu ta không đệm đến độ giải tối đa, ta cần đệm cả nhãn nữa + 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() + + # Thay -100 ở nhãn vì ta không thể giải mã chúng + 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) + + # Tính toán các chỉ số + result = rouge_score.compute() + # Trích xuất điểm trung vị 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) + + # Lưu và tải + 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} +``` + +Và thế đó! Khi bạn chạy nó, bạn sẽ có một mô hình và kết quả khá giống với những mô hình mà chúng ta thu được với `Trainer`. + +{/if} + +## Sử dụng mô hình tinh chỉnh của bạn + +Khi bạn đã đẩy mô hình vào Hub, bạn có thể chơi với nó thông qua tiện ích luận suy hoặc với đối tượng `pipeline`, như sau: + +```python +from transformers import pipeline + +hub_model_id = "huggingface-course/mt5-small-finetuned-amazon-en-es" +summarizer = pipeline("summarization", model=hub_model_id) +``` + +Chúng ta có thể cung cấp một số mẫu từ bộ kiểm thử (mà mô hình chưa thấy) vào pipeline để có cảm nhận về chất lượng của các bản tóm tắt. Trước tiên, hãy triển khai một chức năng đơn giản để hiển thị bài đánh giá, tiêu đề và bản tóm tắt đã tạo cùng nhau: + +```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}'") +``` + +Hãy xem một trong những mẫu tiếng Anh mà chúng ta nhận được: + +```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' +``` + +Điều này không quá tệ! Chúng ta có thể thấy rằng mô hình của mình đã thực sự có thể thực hiện tóm tắt _trừu tượng_ bằng cách tăng cường các phần của bài đánh giá bằng các từ mới. Và có lẽ khía cạnh thú vị nhất của mô hình của chúng ta là nó được sử dụng song ngữ, vì vậy ta cũng có thể tạo tóm tắt các bài đánh giá bằng tiếng Tây Ban Nha: + +```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' +``` + +Bản tóm tắt được dịch thành "Very easy to read" trong tiếng Anh, mà chúng ta có thể thấy trong trường hợp này được trích trực tiếp từ đánh giá. Tuy nhiên, điều này cho thấy tính linh hoạt của mô hình mT5 và cho bạn biết cảm giác triển khai với kho ngữ liệu đa ngôn ngữ là như thế nào! + +Tiếp theo, chúng ta sẽ chuyển sự chú ý sang một tác vụ phức tạp hơn một chút: huấn luyện một mô hình ngôn ngữ từ đầu. diff --git a/chapters/vi/chapter7/6.mdx b/chapters/vi/chapter7/6.mdx new file mode 100644 index 000000000..e58f5301e --- /dev/null +++ b/chapters/vi/chapter7/6.mdx @@ -0,0 +1,947 @@ + + +# THuấn luyện một mô hình ngôn ngữ nhân quả từ đầu + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Cho đến thời điểm hiện tại, chúng ta chủ yếu sử dụng các mô hình được huấn luyện trước và tinh chỉnh chúng cho các trường hợp sử dụng mới bằng cách sử dụng lại các trọng số từ huấn luyện trước. Như chúng ta đã thấy trong [Chương 1](/course/chapter1), điều này thường được gọi là _transfer learning_ hay _học chuyển giao_, và đó là một chiến lược rất thành công để áp dụng các mô hình Transformer cho hầu hết các trường hợp sử dụng trong thế giới thực nơi dữ liệu được gắn nhãn là thưa thớt. Trong chương này, chúng ta sẽ thực hiện một cách tiếp cận khác và huấn luyện một mô hình hoàn toàn mới từ đầu. Đây là một cách tiếp cận tốt để thực hiện nếu bạn có nhiều dữ liệu và nó rất khác với dữ liệu huấn luyện trước được sử dụng cho các mô hình có sẵn. Tuy nhiên, nó cũng đòi hỏi nhiều tài nguyên máy tính hơn đáng kể để huấn luyện trước một mô hình ngôn ngữ hơn là chỉ để tinh chỉnh mô hình hiện có. Các ví dụ có thể có ý nghĩa khi huấn luyện một mô hình mới bao gồm các tập dữ liệu bao gồm các nốt nhạc, trình tự phân tử như DNA hoặc ngôn ngữ lập trình. Công cụ thứ hai gần đây đã đạt được sức hút nhờ các công cụ như TabNine và GitHub's Copilot, được hỗ trợ bởi mô hình Codex của OpenAI, có thể tạo ra các chuỗi mã dài. Tác vụ tạo văn bản này được giải quyết tốt nhất với các mô hình ngôn ngữ tự động hồi quy hoặc nhân quả như GPT-2. + +Trong phần này, chúng ta sẽ xây dựng một phiên bản thu nhỏ của mô hình tạo mã: chúng ta sẽ tập trung vào các hoàn thành một dòng thay vì các hàm hoặc lớp đầy đủ, sử dụng một tập hợp con mã Python. Khi làm việc với dữ liệu bằng Python, bạn thường xuyên tiếp xúc với bộ khoa học dữ liệu Python, bao gồm các thư viện `matplotlib`, `seaborn`, `pandas` và `scikit-learn`. Khi sử dụng chúng, thông thường cần phải tra cứu các lệnh cụ thể, vì vậy sẽ rất tuyệt nếu chúng ta có thể sử dụng một mô hình để hoàn thành các lệnh gọi này cho chúng ta. + + + +Trong [Chương 6](/course/chapter6), chúng ta đã tạo một trình tokenize hiệu quả để xử lý mã nguồn Python, nhưng những gì chúng ta vẫn cần là một tập dữ liệu quy mô lớn để huấn luyện trước một mô hình. Ở đây, chúng ta sẽ áp dụng tokenizer cho một kho lưu trữ mã Python có nguồn gốc từ kho lưu trữ GitHub. Sau đó, chúng ta sẽ sử dụng API `Trainer` và 🤗 Accelerate để huấn luyện mô hình. Chúng ta hãy đi đến đó! + + + +Đây thực sự cách mô hình đã được huấn luyện và tải lên Hub bằng cách sử dụng mã được hiển thị trong phần này. Bạn có thể tìm thấy nó [tại đây](https://huggingface.co/huggingface-course/codeparrot-ds?text=plt.imshow%28). Lưu ý rằng vì có một số ngẫu nhiên xảy ra trong quá trình tạo văn bản, bạn có thể sẽ nhận được một kết quả hơi khác. + +## Thu thập dữ liệu + +Mã Python có sẵn rất nhiều từ các kho mã như GitHub, mà chúng ta có thể sử dụng để tạo tập dữ liệu bằng cách đào mọi kho lưu trữ Python. Đây là phương pháp được thực hiện trong [sách giáo khoa về Transformers](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/) để huấn luyện trước một mô hình GPT-2 lớn. Sử dụng kết xuất GitHub khoảng 180 GB chứa khoảng 20 triệu tệp Python có tên là `codeparrot`, các tác giả đã xây dựng một tập dữ liệu mà sau đó họ chia sẻ trên [Hugging Face Hub](https://huggingface.co/datasets/transformersbook/codeparrot) . + +Tuy nhiên, việc huấn luyện trên toàn bộ ngữ liệu này tốn nhiều thời gian và tính toán, và chúng ta chỉ cần tập con của tập dữ liệu liên quan đến ngăn xếp khoa học dữ liệu Python. Vì vậy, hãy bắt đầu bằng cách lọc tập dữ liệu `codeparrot` cho tất cả các tệp bao gồm bất kỳ thư viện nào trong ngăn xếp này. Do kích thước của tập dữ liệu, chúng ta muốn tránh tải nó xuống; thay vào đó, ta sẽ sử dụng tính năng phát trực tuyến để lọc nó một cách nhanh chóng. Để giúp chúng ta lọc các mẫu mã bằng cách sử dụng các thư viện đã đề cập trước đó, ta sẽ sử dụng hàm sau: + +```py +def any_keyword_in_string(string, keywords): + for keyword in keywords: + if keyword in string: + return True + return False +``` + +Hãy kiểm tra nó trên hai ví dụ: + +```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 +``` + +Chúng ta có thể sử dụng điều này để tạo một hàm sẽ truyền trực tuyến tập dữ liệu và lọc các phần tử ta muốn: + +```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) +``` + +Sau đó, chúng ta có thể chỉ cần áp dụng chức năng này cho tập dữ liệu phát trực tuyến: + +```py +# Ô này sẽ mất rất nhiều thời gian để thực thi, vì vậy bạn nên bỏ qua và chuyển đến +# cái tiếp theo! +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. +``` + +Điều này để lại cho chúng ta khoảng 3% tập dữ liệu ban đầu, vẫn còn khá lớn - tập dữ liệu kết quả là 6GB và bao gồm 600,000 tập lệnh Python! + +Việc lọc toàn bộ tập dữ liệu có thể mất 2-3 giờ tùy thuộc vào máy và băng thông của bạn. Nếu bạn không muốn tự mình trải qua quá trình kéo dài này, chúng ta cung cấp tập dữ liệu đã lọc trên Hub để bạn tải xuống: + +```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 + }) +}) +``` + + + +Việc huấn luyện trước mô hình ngôn ngữ sẽ mất một lúc. Chúng tôi khuyên bạn trước tiên nên chạy vòng lặp huấn luyện trên một mẫu dữ liệu bằng cách bỏ chú thích hai dòng một phần ở trên và đảm bảo rằng quá trình huấn luyện hoàn tất thành công và các mô hình được lưu trữ. Không có gì khó chịu hơn là một lần chạy huấn luyện không thành công ở bước cuối cùng vì bạn quên tạo một thư mục hoặc vì có lỗi đánh máy ở cuối vòng lặp huấn luyện! + + + +Hãy xem một ví dụ từ tập dữ liệu. Chúng ta sẽ chỉ hiển thị 200 ký tự đầu tiên của mỗi trường: + +```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''' +``` + +Chúng ta có thể thấy rằng trường `content` chứa mã mà chúng ta muốn mô hình của mình huấn luyện. Bây giờ chúng ta đã có một tập dữ liệu, chúng ta cần chuẩn bị các văn bản để chúng có định dạng phù hợp để huấn luyện trước. + +## Chuẩn bị tập dữ liệu + + + +Bước đầu tiên sẽ là tokenize dữ liệu để chúng ta có thể sử dụng nó để huấn luyện. Vì mục tiêu của chúng ta chủ yếu là tự động hoàn thành các lệnh gọi hàm ngắn, chúng ta có thể giữ kích thước ngữ cảnh tương đối nhỏ. Điều này có lợi ích là chúng ta có thể huấn luyện mô hình nhanh hơn nhiều và nó cần ít bộ nhớ hơn đáng kể. Nếu điều quan trọng là ứng dụng của bạn phải có nhiều ngữ cảnh hơn (ví dụ: nếu bạn muốn mô hình viết các bài kiểm tra đơn vị dựa trên tệp có định nghĩa hàm), hãy đảm bảo bạn tăng con số đó, nhưng cũng lưu ý rằng điều này đi kèm với bộ nhớ GPU lớn hơn. Hiện tại, hãy sửa kích thước ngữ cảnh ở 128 token, trái ngược với 1,024 hoặc 2,048 được sử dụng trong GPT-2 hoặc GPT-3, tương ứng. + +Hầu hết các tài liệu chứa nhiều hơn 128 token, vì vậy chỉ cần cắt bớt đầu vào đến độ dài tối đa sẽ loại bỏ một phần lớn tập dữ liệu của mình. Thay vào đó, chúng ta sẽ sử dụng tùy chọn `return_overflowing_tokens` để token toàn bộ đầu vào và chia nó thành nhiều phần, như chúng ta đã làm trong [Chương 6](/course/chapter6/4). Chúng ta cũng sẽ sử dụng tùy chọn `return_length` để tự động trả về độ dài của mỗi đoạn được tạo. Thường thì phần cuối cùng sẽ nhỏ hơn kích thước ngữ cảnh và chúng ta sẽ loại bỏ những phần này để tránh các vấn đề về phần đệm; chúng ta không thực sự cần chúng vì dù sao chúng ta cũng có nhiều dữ liệu. + +
+ Chunking a large texts in several pieces. + +
+ +Hãy xem chính xác cách thức hoạt động của điều này bằng cách xem hai ví dụ đầu tiên: + +```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] +``` + +Chúng ta có thể thấy rằng chúng ta nhận được tổng cộng 34 phân đoạn từ hai ví dụ đó. Nhìn vào độ dài phân đoạn, chúng ta có thể thấy rằng các đoạn ở cuối cả hai tài liệu có ít hơn 128 token (tương ứng là 117 và 41). Chúng chỉ đại diện cho một phần nhỏ trong tổng số các khối mà chúng ta có, vì vậy chúng ta có thể vứt chúng đi một cách an toàn. Với trường `overflow_to_sample_mapping`, chúng ta cũng có thể tạo lại các phần thuộc về mẫu đầu vào nào. + +Với thao tác này, chúng ta đang sử dụng một tính năng tiện dụng của hàm `Dataset.map()` trong 🤗 Datasets, đó là nó không yêu cầu ánh xạ 1-1; như chúng ta đã thấy trong [phần 3](/course/chapter7/3), chúng ta có thể tạo các lô có nhiều phần tử hơn hoặc ít hơn lô đầu vào. Điều này rất hữu ích khi thực hiện các hoạt động như tăng dữ liệu hoặc lọc dữ liệu làm thay đổi số lượng phần tử. Trong trường hợp của chúng ta, khi tokenize mỗi phần tử thành các phần có kích thước ngữ cảnh được chỉ định, chúng ta tạo nhiều mẫu từ mỗi tài liệu. Chúng ta chỉ cần đảm bảo xóa các cột hiện có, vì chúng có kích thước xung đột. Nếu chúng ta muốn giữ chúng, chúng ta có thể lặp lại chúng một cách thích hợp và trả lại chúng trong lệnh gọi `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 + }) +}) +``` + +Hiện chúng ta có 16,7 triệu ví dụ với 128 token mỗi ví dụ, tương ứng với tổng cộng khoảng 2,1 tỷ token. Để tham khảo, các mô hình GPT-3 và Codex của OpenAI được huấn luyện trên 300 và 100 tỷ token tương ứng, trong đó các mô hình Codex được khởi tạo từ các checkpoint GPT-3. Mục tiêu của chúng ta trong phần này không phải là cạnh tranh với các mô hình này, có thể tạo ra các văn bản dài, mạch lạc, mà là tạo ra một phiên bản thu nhỏ cung cấp chức năng tự động hoàn thành nhanh chóng cho các nhà khoa học dữ liệu. + +Bây giờ chúng ta đã có tập dữ liệu sẵn sàng, hãy thiết lập mô hình! + + + +✏️ **Thử nghiệm thôi!** Loại bỏ tất cả các phần nhỏ hơn kích thước ngữ cảnh không phải là vấn đề lớn ở đây vì chúng ta đang sử dụng các cửa sổ ngữ cảnh nhỏ. Khi bạn tăng kích thước ngữ cảnh (hoặc nếu bạn có một kho tài liệu ngắn), phần nhỏ các phần bị vứt bỏ cũng sẽ tăng lên. Một cách hiệu quả hơn để chuẩn bị dữ liệu là kết hợp tất cả các mẫu được tokenize trong một lô với token `eos_token_id` ở giữa, và sau đó thực hiện phân đoạn trên các chuỗi được nối. Như một bài tập, hãy sửa đổi hàm `tokenize()` để sử dụng cách tiếp cận đó. Lưu ý rằng bạn sẽ muốn đặt `truncation=False` và xóa các tham số khác khỏi tokenizer để nhận được chuỗi đầy đủ của token ID. + + + +## Khởi tạo mô hình mới + +Bước đầu tiên của chúng ta là khởi chạy mới mô hình GPT-2. Chúng ta sẽ sử dụng cùng một cấu hình cho mô hình của mình như cho mô hình GPT-2 nhỏ, vì vậy chúng ta tải cấu hình định sẵn, đảm bảo rằng kích thước tokenizer khớp với kích thước từ vựng của mô hình và chuyển `bos` và `eos` (bắt đầu và cuối chuỗi) token ID: + +{#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, +) +``` + +Với cấu hình đó, chúng ta có thể tải một mô hình mới. Lưu ý rằng đây là lần đầu tiên chúng ta không sử dụng hàm `from_pretrained()`, vì chúng ta thực sự đang khởi tạo một mô hình của chính chúng ta: + +```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, +) +``` + +Với cấu hình đó, chúng ta có thể tải một mô hình mới. Lưu ý rằng đây là lần đầu tiên chúng ta không sử dụng hàm `from_pretrained()`, vì chúng ta thực sự đang khởi tạo một mô hình của chính chúng ta: + +```py +model = TFGPT2LMHeadModel(config) +model(model.dummy_inputs) # Xây mô hình +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} + +Mô hình của chúng ta có 124 triệu thông số mà ta sẽ phải điều chỉnh. Trước khi có thể bắt đầu huấn luyện, chúng ta cần thiết lập một bộ đối chiếu dữ liệu sẽ đảm nhận việc tạo các lô. Chúng ta có thể sử dụng trình cắt ghép `DataCollatorForLanguageModeling`, được thiết kế đặc biệt cho mô hình ngôn ngữ (như tên gọi gợi ý một cách tinh tế). Bên cạnh việc xếp chồng và đệm các lô, nó cũng đảm nhận việc tạo các nhãn của mô hình ngôn ngữ - trong mô hình ngôn ngữ nhân quả, các đầu vào cũng đóng vai trò là nhãn (chỉ được dịch chuyển bởi một phần tử) và trình đối chiếu dữ liệu này tạo chúng nhanh chóng trong quá trình huấn luyện, vì vậy chúng tôi ta không cần sao chép `input_ids`. + +Lưu ý rằng `DataCollatorForLanguageModeling` hỗ trợ cả mô hình hóa ngôn ngữ bị ẩn đi (MLM) và mô hình ngôn ngữ nhân quả (CLM). Theo mặc định, nó chuẩn bị dữ liệu cho MLM, nhưng chúng ta có thể chuyển sang CLM bằng cách đặt đối số `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} + +Hãy xem một ví dụ: + +```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} + +Chúng ta có thể thấy rằng các ví dụ đã được xếp chồng lên nhau và tất cả các tensor có cùng hình dạng. + +{#if fw === 'tf'} + +Bây giờ chúng ta có thể sử dụng phương thức `to_tf_dataset()` để chuyển đổi tập dữ liệu của mình thành tập dữ liệu TensorFlow bằng công cụ đối chiếu dữ liệu đã tạo ở trên: + +```python +tf_train_dataset = tokenized_dataset["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) +tf_eval_dataset = tokenized_dataset["valid"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=32, +) +``` + +{/if} + + + +⚠️ Việc dịch chuyển các đầu vào và nhãn để căn chỉnh chúng xảy ra bên trong mô hình, do đó, bộ đối chiếu dữ liệu chỉ cần sao chép các đầu vào để tạo nhãn. + + + +Bây giờ chúng ta có mọi thứ để thực sự huấn luyện mô hình của mình - đó không phải là quá nhiều công việc! Trước khi bắt đầu luyện tập, chúng ta nên đăng nhập vào Hugging Face. Nếu bạn đang làm việc trong notebook, bạn có thể làm như vậy với hàm tiện ích sau: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Thao tác này sẽ hiển thị một tiện ích mà bạn có thể nhập thông tin đăng nhập Hugging Face của mình. + +Nếu bạn không làm việc trong notebook, chỉ cần nhập dòng sau vào thiết bị đầu cuối của bạn: + +```bash +huggingface-cli login +``` + +{#if fw === 'pt'} + +Tất cả những gì còn lại cần làm là cấu hình các tham số huấn luyện và kích hoạt `Trainer`. Chúng ta sẽ sử dụng lịch trình tốc độ học cosine với một số lần khởi động và kích thước lô hiệu quả là 256 (`per_device_train_batch_size` \* `gradient_accumulation_steps`). Tích lũy gradient được sử dụng khi một loạt lô duy nhất không vừa với bộ nhớ và dần dần tích lũy gradient thông qua một số lần truyền xuôi/ngược. Chúng ta sẽ thấy điều này hoạt động khi chúng ta tạo vòng huấn luyện với 🤗 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"], +) +``` + +Bây giờ chúng ta có thể khởi động `Trainer` và đợi quá trình huấn luyện kết thúc. Tùy thuộc vào việc bạn chạy nó trên toàn bộ hay một tập hợp con của bộ huấn luyện, tương ứng sẽ mất 20 hoặc 2 giờ, vì vậy hãy lấy một ít cà phê và một cuốn sách hay để đọc! + +```py +trainer.train() +``` + +Sau khi quá trình huấn luyện hoàn tất, chúng ta có thể đẩy mô hình và trình tokenizer vào Hub: + +```py +trainer.push_to_hub() +``` + +{:else} + +Tất cả những gì còn lại cần làm là định cấu hình các siêu tham số huấn luyện và gọi `compile()`và `fit()`. Chúng ta sẽ sử dụng lịch trình tốc độ học với một số khởi động để cải thiện tính ổn định của quá trình huấn luyện: + +```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) + +# Huấn luyện trong mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +Bây giờ chúng ta có thể gọi `model.fit()` và đợi quá trình huấn luyện kết thúc. Tùy thuộc vào việc bạn chạy nó trên toàn bộ hay một tập hợp con của bộ huấn luyện, tương ứng sẽ mất 20 hoặc 2 giờ, vì vậy hãy lấy một ít cà phê và một cuốn sách hay để đọc! Sau khi huấn luyện xong, chúng ta có thể đẩy mô hình và trình tokenize vào 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} + + + +✏️ **Thử nghiệm thôi!** Chỉ mất khoảng 30 dòng mã ngoài `TrainingArguments` để từ văn bản thô đến huấn luyện GPT-2. Hãy dùng thử với tập dữ liệu của riêng bạn và xem liệu bạn có thể đạt được kết quả tốt hay không! + + + + + +{#if fw === 'pt'} + +💡 Nếu bạn có quyền truy cập vào một máy có nhiều GPU, hãy thử chạy mã ở đó. `Trainer` tự động quản lý nhiều máy và điều này có thể tăng tốc quá trình huấn luyện lên rất nhiều. + +{:else} + +💡 Nếu bạn có quyền truy cập vào một máy có nhiều GPU, bạn có thể thử sử dụng ngữ cảnh `MirroredStrategy` để tăng tốc đáng kể cho quá trình huấn luyện. Bạn sẽ cần tạo một đối tượng `tf.distribute.MirroredStrategy` và đảm bảo rằng các lệnh `to_tf_dataset` cũng như tạo mô hình và lệnh gọi đến `fit()` đều được chạy trong ngữ cảnh `scope()` của nó. Bạn có thể xem tài liệu về điều này [tại đây](https://www.tensorflow.org/guide/distributed_training#use_tfdistributestrategy_with_keras_modelfit). + +{/if} + + + +## Tạo mã với một pipeline + +Bây giờ là thời điểm của sự thật: chúng ta hãy xem mô hình được huấn luyện thực sự hoạt động tốt như thế nào! Chúng ta có thể thấy trong nhật ký rằng mất mát đã giảm đều đặn, nhưng để đưa mô hình vào thử nghiệm, chúng ta hãy xem nó hoạt động tốt như thế nào trên một số lời nhắc. Để làm điều đó, chúng ta sẽ bao bọc mô hình trong một `pipeline` tạo văn bản và chúng ta sẽ đưa nó vào GPU cho các thế hệ nhanh nếu có sẵn: + +{#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} + +Hãy bắt đầu với tác vụ đơn giản là tạo một biểu đồ phân tán: + +```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 +``` + +Kết quả có vẻ chính xác. Nó cũng hoạt động với `pandas`? Hãy xem liệu chúng ta có thể tạo một `DataFrame` từ hai mảng không: + +```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 +``` + +Thật tuyệt, đó là câu trả lời chính xác - mặc dù sau đó nó lại chèn thêm cột `x`. Vì số lượng token được tạo có giới hạn, vòng lặp `for` sau đây sẽ bị cắt. Hãy xem liệu chúng ta có thể làm điều gì đó phức tạp hơn một chút và để mô hình giúp chúng ta sử dụng hoạt động `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 +``` + +Không tệ; đó là cách làm đúng. Cuối cùng, hãy xem liệu chúng ta có thể sử dụng nó cho `scikit-learn` và thiết lập mô hình Random Forest hay không: + +```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'} + +Nhìn vào một vài ví dụ này, có vẻ như mô hình đã học được một số cú pháp của bộ khoa học dữ liệu Python. Tất nhiên, chúng ta sẽ cần phải đánh giá mô hình kỹ lưỡng hơn trước khi triển khai nó trong thế giới thực, nhưng đây vẫn là một nguyên mẫu ấn tượng. + +{:else} + +Nhìn vào một vài ví dụ này, có vẻ như mô hình đã học được một số cú pháp của bộ khoa học dữ liệu Python (tất nhiên, chúng tôi sẽ cần đánh giá kỹ lưỡng hơn trước khi triển khai mô hình trong thế giới thực). Tuy nhiên, đôi khi nó đòi hỏi phải tùy chỉnh nhiều hơn việc huấn luyện mô hình để đạt được hiệu suất cần thiết cho một trường hợp sử dụng nhất định. Ví dụ: điều gì sẽ xảy ra nếu chúng ta muốn cập nhật động kích thước lô hoặc có một vòng lặp huấn luyện có điều kiện để bỏ qua các ví dụ xấu một cách nhanh chóng? Một tùy chọn sẽ là phân lớp `Trainer` và thêm các thay đổi cần thiết, nhưng đôi khi việc viết vòng lặp huấn luyện từ đầu sẽ đơn giản hơn. Đó là lúc 🤗 Accelerate xuất hiện. + +{/if} + +{#if fw === 'pt'} + +## Huấn luyện với 🤗 Accelerate + +Chúng ta đã thấy cách huấn luyện một mô hình với `Trainer`, có thể cho phép một số tùy chỉnh. Tuy nhiên, đôi khi chúng ta muốn toàn quyền kiểm soát vòng lặp huấn luyện hoặc chúng ta muốn thực hiện một số thay đổi kỳ lạ. Trong trường hợp này 🤗 Accelerate là một lựa chọn tuyệt vời và trong phần này, chúng ta sẽ xem xét các bước sử dụng nó để huấn luyện mô hình của mình. Để làm cho mọi thứ thú vị hơn, chúng ta cũng sẽ thêm một số điều chỉnh vào vòng lặp huấn luyện. + + + +Vì chúng ta chủ yếu quan tâm đến tính năng tự động hoàn thành hợp lý cho các thư viện khoa học dữ liệu, nên việc đưa ra nhiều trọng số hơn cho các mẫu huấn luyện sử dụng nhiều hơn các thư viện này. Chúng ta có thể dễ dàng xác định những ví dụ này thông qua việc sử dụng các từ khóa như `plt`, `pd`, `sk`, `fit` và `predict`, là những tên nhập thường gặp nhất cho `matplotlib.pyplot`, `pandas`, và `sklearn` cũng như các hành vi sau đó. Nếu chúng được biểu diễn dưới dạng một token duy nhất, chúng ta có thể dễ dàng kiểm tra xem chúng có xuất hiện trong chuỗi đầu vào hay không. Các token có thể có tiền tố khoảng trắng, vì vậy chúng ta cũng sẽ kiểm tra các phiên bản đó trong từ vựng bộ tokenizer. Để xác minh rằng nó hoạt động, chúng ta sẽ thêm một token kiểm thử sẽ được chia thành nhiều token: + +```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' +``` + +Tuyệt vời, điều đó có vẻ hoạt động tốt! Bây giờ chúng ta có thể viết một hàm mất mát tùy chỉnh lấy chuỗi đầu vào, logits và token khóa mà chúng ta vừa chọn làm đầu vào. Trước tiên, chúng ta cần căn chỉnh logits và đầu vào: chuỗi đầu vào được dịch chuyển một đơn vị sang bên phải tạo thành các nhãn, vì token tiếp theo là nhãn cho token hiện tại. Chúng ta có thể đạt được điều này bằng cách bắt đầu các nhãn từ token thứ hai của chuỗi đầu vào, vì dù sao thì mô hình cũng không đưa ra dự đoán cho token đầu tiên. Sau đó, chúng ta cắt logit cuối cùng, vì chúng ta không có nhãn cho token theo trình tự đầu vào đầy đủ. Nhờ đó, chúng ta có thể tính toán sự mất mát trên mỗi mẫu và đếm số lần xuất hiện của tất cả các từ khóa trong mỗi mẫu. Cuối cùng, chúng ta tính giá trị trung bình có trọng số trên tất cả các mẫu bằng cách sử dụng các lần xuất hiện dưới dạng trọng số. Vì chúng ta không muốn loại bỏ tất cả các mẫu không có từ khóa, chúng ta thêm 1 vào các trọng số: + +```py +from torch.nn import CrossEntropyLoss +import torch + + +def keytoken_weighted_loss(inputs, logits, keytoken_ids, alpha=1.0): + # Dịch chuyển để token < n dự đoán n + shift_labels = inputs[..., 1:].contiguous() + shift_logits = logits[..., :-1, :].contiguous() + # Tính độ mất mát từng token + loss_fct = CrossEntropyLoss(reduce=False) + loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1)) + # Thay đổi kích thước và mất mát trung bình trên mỗi mẫu + loss_per_sample = loss.view(shift_logits.size(0), shift_logits.size(1)).mean(axis=1) + # Tính toán và chia tỷ trọng + weights = torch.stack([(inputs == kt).float() for kt in keytoken_ids]).sum( + axis=[0, 2] + ) + weights = alpha * (1.0 + weights) + # Tính giá trị trung bình có trọng số + weighted_loss = (loss_per_sample * weights).mean() + return weighted_loss +``` + +Trước khi có thể bắt đầu huấn luyện với hàm mất mát mới tuyệt vời này, chúng ta cần chuẩn bị một số thứ: + +- Chúng ta cần bộ ghi dữ liệu để tải dữ liệu theo lô. +- Chúng ta cần thiết lập các thông số phân rã trọng số. +- Theo thời gian, chúng ta muốn đánh giá, vì vậy sẽ hợp lý khi bao mã đánh giá trong một hàm. + +Hãy bắt đầu với bộ dữ liệu. Chúng ta chỉ cần đặt định dạng của tập dữ liệu thành `"torch"`, và sau đó có thể chuyển nó đến PyTorch `DataLoader` với kích thước lô thích hợp: + +```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) +``` + +Tiếp theo, chúng ta nhóm các tham số để trình tối ưu hóa biết những thông số nào sẽ bị giảm trọng số bổ sung. Thông thường, tất cả các điều khoản thiên vị và trọng số LayerNorm đều được miễn trừ; đây là cách chúng ta có thể làm điều này: + +```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}, + ] +``` + +Vì chúng ta muốn đánh giá mô hình thường xuyên trên bộ xác nhận trong quá trình huấn luyện, chúng ta hãy viết một hàm cho điều đó. Nó chỉ chạy qua bộ dữ liệu đánh giá và tập hợp tất cả các mất mát qua các quy trình: + +```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() +``` + +Với hàm `evaluate()`, chúng ta có thể báo cáo mất mát và [perplexity](/course/chapter7/3) theo khoảng thời gian đều đặn. Tiếp theo, chúng ta xác định lại mô hình của mình để đảm bảo chúng ta huấn luyện lại từ đầu: + +```py +model = GPT2LMHeadModel(config) +``` + +Sau đó, chúng ta có thể xác định trình tối ưu hóa của mình, sử dụng hàm từ trước để phân chia các tham số cho phân rã trọng số: + +```py +from torch.optim import AdamW + +optimizer = AdamW(get_grouped_params(model), lr=5e-4) +``` + +Bây giờ, hãy chuẩn bị mô hình, trình tối ưu hóa và bộ ghi dữ liệu để chúng ta có thể bắt đầu huấn luyện: + +```py +from accelerate import Accelerator + +accelerator = Accelerator(fp16=True) + +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + + + +🚨 Nếu bạn đang huấn luyện trên TPU, bạn sẽ cần chuyển tất cả mã bắt đầu từ ô ở trên vào một hàm huấn luyện chuyên dụng. Xem [Chapter 3](/course/chapter3) để biết thêm chi tiết. + + + +Bây giờ, chúng ta đã gửi `train_dataloader` của mình tới `accelerator.prepare()`, chúng ta có thể sử dụng độ dài của nó để tính số bước huấn luyện. Hãy nhớ rằng chúng ta phải luôn làm điều này sau khi chuẩn bị dataloader, vì phương thức đó sẽ thay đổi độ dài của nó. Chúng ta sử dụng một lịch trình tuyến tính cổ điển từ tốc độ học đến 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, +) +``` + +Cuối cùng, để đẩy mô hình lên Hub, chúng ta sẽ cần tạo một đối tượng `Repository` trong một thư mục đang làm việc. Trước tiên, hãy đăng nhập vào Hugging Face Hub, nếu bạn chưa đăng nhập. Chúng ta sẽ xác định tên kho lưu trữ từ ID mô hình mà ta muốn cung cấp cho mô hình của mình (vui lòng thay thế `repo_name` bằng sự lựa chọn của riêng bạn; nó chỉ cần chứa tên người dùng của bạn, đó là những gì hàm `get_full_repo_name()` thực hiện ): + +```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' +``` + +Sau đó, chúng ta có thể sao chép kho lưu trữ đó trong một thư mục cục bộ. Nếu nó đã tồn tại, thư mục cục bộ này phải là bản sao hiện có của kho lưu trữ mà chúng ta đang làm việc: + +```py +output_dir = "codeparrot-ds-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +Bây giờ chúng ta có thể tải lên bất cứ thứ gì chúng ta lưu trong `output_dir` bằng cách gọi phương thức `repo.push_to_hub()`. Điều này sẽ giúp chúng ta tải lên các mô hình trung gian ở cuối mỗi epoch. + +Trước khi huấn luyện, hãy chạy thử nhanh để xem chức năng đánh giá có hoạt động bình thường không: + +```py +evaluate() +``` + +```python out +(10.934126853942871, 56057.14453125) +``` + +Đó là những giá trị rất cao về mức mất mát và perplexity, nhưng điều đó không đáng ngạc nhiên vì chúng ta chưa huấn luyện mô hình. Cùng với đó, chúng ta đã chuẩn bị mọi thứ để viết phần cốt lõi của kịch bản huấn luyện: vòng lặp huấn luyện. Trong vòng lặp huấn luyện, chúng ta lặp qua dataloader và truyền các lô vào mô hình. Với nhật ký, sau đó chúng ta có thể đánh giá hàm mất mát tùy chỉnh của mình. Chúng ta chia tỷ lệ mất mát theo số bước tích lũy gradient để không tạo ra mất mát lớn hơn khi tổng hợp nhiều bước hơn. Trước khi tối ưu hóa, chúng ta cũng cắt các gradient để hội tụ tốt hơn. Cuối cùng, cứ sau vài bước, chúng ta đánh giá mô hình trên tập hợp đánh giá với hàm `eval()` mới của mình: + +```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( + { + "lr": get_lr(), + "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 + ) +``` + +Vậy là xong - bây giờ bạn có vòng huấn luyện tùy chỉnh của riêng mình cho các mô hình ngôn ngữ nhân quả chẳng hạn như GPT-2 mà bạn có thể tùy chỉnh thêm theo nhu cầu của mình. + + + +✏️ **Thử nghiệm thôi!** Hoặc tạo hàm mất tùy chỉnh của riêng bạn phù hợp với trường hợp sử dụng của bạn hoặc thêm một bước tùy chỉnh khác vào vòng lặp huấn luyện. + + + + + +✏️ **Thử nghiệm thôi!** Khi chạy các thử nghiệm huấn luyện dài, bạn nên ghi lại các chỉ số quan trọng bằng cách sử dụng các công cụ như TensorBoard hoặc Weights & Biases. Thêm ghi nhật ký thích hợp vào vòng lặp huấn luyện để bạn luôn có thể kiểm tra quá trình huấn luyện diễn ra như thế nào. + + + +{/if} diff --git a/chapters/vi/chapter7/7.mdx b/chapters/vi/chapter7/7.mdx new file mode 100644 index 000000000..ff5a59897 --- /dev/null +++ b/chapters/vi/chapter7/7.mdx @@ -0,0 +1,1213 @@ + + +# Hỏi đáp + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Đã đến lúc xem phần hỏi đáp! Tác vụ này có nhiều loại, nhưng tác vụ mà chúng ta sẽ tập trung vào trong phần này được gọi là trả lời câu hỏi *khai thác*. Điều này liên quan đến việc đặt ra các câu hỏi về một tài liệu và xác định các câu trả lời dưới dạng _các khoảng của văn bản_ trong chính tài liệu đó. + + + +Chúng ta sẽ tinh chỉnh mô hình BERT trên [bộ dữ liệu SQuAD](https://rajpurkar.github.io/SQuAD-explorer/), bao gồm các câu hỏi do cộng đồng đặt ra trên một tập các bài viết trên Wikipedia. Điều này sẽ cung cấp cho chúng ta một mô hình có thể tính toán các dự đoán như thế này: + + + +Đây thực sự cách mô hình đã được huấn luyện và tải lên Hub bằng cách sử dụng mã được hiển thị trong phần này. Bạn có thể tìm thấy nó và kiểm tra các dự đoạn [tại đây](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). + + + +💡 Các mô hình mã hóa như BERT có xu hướng tuyệt vời trong việc trích xuất câu trả lời cho các câu hỏi dạng thực tế như "Ai đã phát minh ra kiến trúc Transformer?" nhưng khá kém khi trả lời những câu hỏi mở như "Tại sao bầu trời lại có màu xanh?" Trong những trường hợp khó khăn hơn này, các mô hình mã hóa-giải mã như T5 và BART thường được sử dụng để tổng hợp thông tin theo cách khá giống với [tóm tắt văn bản](/course/chapter7/5). Nếu bạn quan tâm đến kiểu trả lời câu hỏi *chung chung* này, chúng tôi khuyên bạn nên xem [demo](https://yjernite.github.io/lfqa.html) của chúng tôi dựa trên [bộ dữ liệu ELI5](https://huggingface.co/datasets/eli5). + + + +## Chuẩn bị dữ liệu + +Tập dữ liệu được sử dụng nhiều nhất làm tiêu chuẩn học thuật để trả lời câu hỏi khai thác là [SQuAD](https://rajpurkar.github.io/SQuAD-explorer/), vì vậy đó là tập chúng ta sẽ sử dụng ở đây. Ngoài ra còn có một điểm chuẩn khó hơn [SQuAD v2](https://huggingface.co/datasets/squad_v2), bao gồm các câu hỏi không có câu trả lời. Miễn là tập dữ liệu của riêng bạn chứa một cột cho ngữ cảnh, một cột cho câu hỏi và một cột cho câu trả lời, bạn sẽ có thể điều chỉnh các bước bên dưới. + +### Bộ dữ liệu SQuAD + +Như thường lệ, chúng ta có thể tải xuống và lưu bộ dữ liệu vào bộ nhớ cache chỉ trong một bước nhờ vào `load_dataset()`: + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("squad") +``` + +Sau đó, chúng ta có thể xem xét đối tượng này để tìm hiểu thêm về tập dữ liệu 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 + }) +}) +``` + +Có vẻ như chúng ta có mọi thứ ta cần với các trường `context`, `question`, và `answers`, vì vậy hãy in chúng cho phần tử đầu tiên của tập huấn luyện của mình: + +```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]} +``` + +Các trường `context` và `question` rất dễ sử dụng. Trường `answers` phức tạp hơn một chút vì nó so sánh một từ điển với hai trường đều là danh sách. Đây là định dạng sẽ được mong đợi bởi chỉ số `squad` trong quá trình đánh giá; nếu bạn đang sử dụng dữ liệu của riêng mình, bạn không nhất thiết phải lo lắng về việc đặt các câu trả lời ở cùng một định dạng. Trường `text` khá rõ ràng và trường `answer_start` chứa chỉ mục ký tự bắt đầu của mỗi câu trả lời trong ngữ cảnh. + +Trong quá trình huấn luyện, chỉ có một câu trả lời khả dĩ. Chúng ta có thể kiểm tra kỹ điều này bằng cách sử dụng phương thức `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 +}) +``` + +Tuy nhiên, để đánh giá, có một số câu trả lời có thể có cho mỗi mẫu, có thể giống hoặc khác nhau: + +```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]} +``` + +Chúng ta sẽ không đi sâu vào tập lệnh đánh giá vì tất cả sẽ được bao bọc bởi chỉ số 🤗 Datasets, nhưng phiên bản ngắn là một số câu hỏi có một số câu trả lời có thể có và tập lệnh này sẽ so sánh một câu trả lời được dự đoán cho tất cả câu trả lời có thể chấp nhận được và dành điểm cao nhất. Ví dụ: nếu chúng ta xem xét mẫu ở chỉ mục 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?' +``` + +ta có thể thấy câu trả lời có thể thực ra là một trong số ba khả năng ta thấy trước đó. + +### Xử lý dữ liệu huấn luyện + + + +Hãy bắt đầu với việc xử lý trước dữ liệu huấn luyện. Phần khó sẽ là tạo nhãn cho câu trả lời của câu hỏi, đó sẽ là vị trí bắt đầu và kết thúc của các thẻ tương ứng với câu trả lời bên trong ngữ cảnh. + +Nhưng chúng ta đừng vượt lên chính mình. Đầu tiên, chúng ta cần chuyển đổi văn bản trong đầu vào thành các ID mà mô hình có thể hiểu được, sử dụng tokenizer: + +```py +from transformers import AutoTokenizer + +model_checkpoint = "bert-base-cased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +Như đã đề cập trước đó, chúng ta sẽ tinh chỉnh mô hình BERT, nhưng bạn có thể sử dụng bất kỳ loại mô hình nào khác miễn là nó có triển khai trình tokenize nhanh. Bạn có thể xem tất cả các kiến trúc đi kèm với phiên bản nhanh trong [bảng lớn này](https://huggingface.co/transformers/#supported-frameworks) và để kiểm tra xem đối tượng `tokenizer` mà bạn đang sử dụng có thực sự là được hỗ trợ bởi 🤗 Tokenizers, bạn có thể xem thuộc tính `is_fast` của nó: +```py +tokenizer.is_fast +``` + +```python out +True +``` + +Chúng ta có thể truyền câu hỏi và ngữ cảnh cho trình tokenizer của mình và nó sẽ chèn đúng các token đặc biệt để tạo thành một câu như sau: + +``` +[CLS] question [SEP] context [SEP] +``` + +Hãy cùng kiểm tra nó: + +```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]' +``` + +Các nhãn sau đó sẽ là chỉ mục của các token bắt đầu và kết thúc câu trả lời và mô hình sẽ có nhiệm vụ dự đoán một logit bắt đầu và kết thúc cho mỗi token trong đầu vào, với các nhãn lý thuyết như sau: + +
+One-hot encoded labels for question answering. + +
+ +Trong trường hợp này, ngữ cảnh không quá dài, nhưng một số mẫu trong tập dữ liệu có ngữ cảnh rất dài sẽ vượt quá độ dài tối đa mà chúng tôi đặt (trong trường hợp này là 384). Như chúng ta đã thấy trong [Chương 6](/course/chapter6/4) khi chúng ta khám phá phần bên trong của pipeline `question-answering`, chúng ta sẽ đối phó với các ngữ cảnh dài bằng cách tạo một số đặc trưng huấn luyện từ một mẫu tập dữ liệu của mình, với cửa sổ trượt giữa chúng. + +Để xem cách này hoạt động như thế nào bằng cách sử dụng ví dụ hiện tại, chúng ta có thể giới hạn độ dài ở 100 và sử dụng cửa sổ trượt gồm 50 token. Xin nhắc lại, chúng ta sử dụng: + +- `max_length` để đặt độ dài tối đa (ở đây là 100) +- `truncation="only_second"` để cắt ngắn ngữ cảnh (ở vị trí thứ hai) khi câu hỏi có ngữ cảnh quá dài +- `stride` để đặt số lượng token chồng chéo giữa hai phần liên tiếp (ở đây là 50) +- `return_overflowing_tokens=True` để cho trình tokenizer biết chúng ta muốn các token tràn + +```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]' +``` + +Như chúng ta có thể thấy, ví dụ của chúng ta đã được chia thành bốn đầu vào, mỗi đầu vào chứa câu hỏi và một số phần của ngữ cảnh. Lưu ý rằng câu trả lời cho câu hỏi ("Bernadette Soubirous") chỉ xuất hiện trong đầu vào thứ ba và cuối cùng, vì vậy bằng cách xử lý các ngữ cảnh dài theo cách này, chúng ta sẽ tạo một số mẫu huấn luyện trong đó câu trả lời không được đưa vào ngữ cảnh. Đối với những ví dụ đó, nhãn sẽ là `start_position = end_position = 0` (vì vậy chúng tôi dự đoán token `[CLS]`). Chúng ta cũng sẽ đặt các nhãn đó trong trường hợp không may khi câu trả lời đã bị cắt bớt để chúng ta chỉ có phần đầu (hoặc phần cuối) của câu trả lời. Đối với các ví dụ trong đó câu trả lời nằm đầy đủ trong ngữ cảnh, các nhãn sẽ là chỉ mục của token nơi câu trả lời bắt đầu và chỉ mục của token nơi câu trả lời kết thúc. + +Tập dữ liệu cung cấp cho chúng ta ký tự bắt đầu của câu trả lời trong ngữ cảnh và bằng cách thêm độ dài của câu trả lời, chúng ta có thể tìm thấy ký tự kết thúc trong ngữ cảnh. Để ánh xạ chúng với các chỉ số token, chúng ta sẽ cần sử dụng ánh xạ offset mà chúng ta đã nghiên cứu trong [Chương 6](/course/chapter6/4). Chúng ta có thể yêu cầu tokenizer trả lại những thứ này bằng cách truyền theo `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']) +``` + +Như chúng ta có thể thấy, chúng ta lấy lại các ID đầu vào thông thường, token ID và attention mask, cũng như ánh xạ offset mà chúng ta yêu cầu và một khóa bổ sung, `overflow_to_sample_mapping`. Giá trị tương ứng sẽ được sử dụng cho chúng ta khi tokenize nhiều văn bản cùng một lúc (chúng ta nên làm để hưởng lợi từ thực tế là trình tokenizer được hỗ trợ bởi Rust). Vì một mẫu có thể cung cấp một số đối tượng địa lý, nên nó ánh xạ từng đối tượng địa lý với ví dụ mà nó có nguồn gốc. Bởi vì ở đây chúng ta chỉ tokenize một ví dụ, chúng ta nhận được danh sách các `0`: + +```py +inputs["overflow_to_sample_mapping"] +``` + +```python out +[0, 0, 0, 0] +``` + +Nhưng nếu chúng ta mã hóa nhiều mẫu hơn, điều này sẽ trở nên hữu ích hơn: + +```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].' +``` + +Như chúng ta có thể thấy, ba mẫu đầu tiên (tại chỉ số 2, 3 và 4 trong tập huấn luyện) mỗi mẫu đưa ra bốn đặc trưng và mẫu cuối cùng (tại chỉ mục 5 trong tập huấn luyện) đưa ra 7 đặc trưng. + +Thông tin này sẽ hữu ích để ánh xạ từng đối tượng mà chúng ta nhận được với nhãn tương ứng của nó. Như đã đề cập trước đó, các nhãn đó là: + +- `(0, 0)` nếu câu trả lời không nằm trong khoảng tương ứng của ngữ cảnh +- `(start_position, end_position)` nếu câu trả lời nằm trong khoảng tương ứng của ngữ cảnh, với `start_position` là chỉ mục của token (trong các ID đầu vào) ở đầu câu trả lời và `end_position` là chỉ mục của token (trong các ID đầu vào) nơi câu trả lời kết thúc. + +Để xác định đây là trường hợp nào và nếu có liên quan, vị trí của các token, trước tiên chúng ta tìm các chỉ số bắt đầu và kết thúc ngữ cảnh trong các ID đầu vào. Chúng ta có thể sử dụng các token ID để thực hiện việc này, nhưng vì chúng không nhất thiết phải tồn tại cho tất cả các mô hình (ví dụ: DistilBERT không yêu cầu chúng), thay vào đó, chúng ta sẽ sử dụng phương thức `sequence_ids()` của `BatchEncoding` mà tokenizer của ta trả về. + +Khi ta có các chỉ mục token đó, chúng ta xem xét các offset, là các bộ giá trị của hai số nguyên đại diện cho khoảng ký tự bên trong ngữ cảnh ban đầu. Do đó, chúng ta có thể phát hiện xem đoạn ngữ cảnh trong đặc trưng này bắt đầu sau câu trả lời hay kết thúc trước khi câu trả lời bắt đầu (trong trường hợp đó nhãn là `(0, 0)`). Nếu không phải như vậy, chúng ta lặp lại để tìm mã token đầu tiên và cuối cùng của câu trả lời: + +```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) + + # Tìm điểm bắt đầu và kết thúc của ngữ cảnh + while sequence_ids[idx] != 1: + idx += 1 + context_start = idx + while sequence_ids[idx] == 1: + idx += 1 + context_end = idx - 1 + + # Nếu câu trả lời không hoàn toàn nằm trong ngữ cảnh, nhãn là (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: + # Nếu không nó sẽ là vị trí bắt đầu và kết thúc + 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]) +``` + +Hãy cùng xem một vài kết quả để xác minh rằng cách tiếp cận của chúng ta là đúng. Đối với đặc trưng đầu tiên chúng ta tìm thấy `(83, 85)` dưới dạng nhãn, hãy so sánh câu trả lời lý thuyết với khoảng token được giải mã từ 83 đến 85 (bao gồm): + +```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' +``` + +Kết quả khá là khớp nhau! Bây giờ chúng ta hãy kiểm tra chỉ mục 4, nơi chúng ta đặt nhãn thành `(0, 0)`, có nghĩa là câu trả lời không nằm trong phần ngữ cảnh của đặc trưng đó: + +```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]' +``` + +
+ +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. + + + +✏️ **Đến lượt bạn!** Khi sử dụng kiến trúc XLNet, phần đệm được áp dụng ở bên trái và câu hỏi và ngữ cảnh được chuyển đổi. Điều chỉnh tất cả mã chúng ta vừa thấy với kiến trúc XLNet (và thêm `padding=True`). Lưu ý rằng token `[CLS]` có thể không ở vị trí 0 khi áp dụng phần đệm. + + + +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à chúng 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à chúng 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: + +```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) + + # Tìm điểm bắt đầu và kết thúc của ngữ cảnh + idx = 0 + while sequence_ids[idx] != 1: + idx += 1 + context_start = idx + while sequence_ids[idx] == 1: + idx += 1 + context_end = idx - 1 + + # Nếu câu trả lời không hoàn toàn nằm trong ngữ cảnh, nhãn là (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: + # Nếu không nó sẽ là vị trí token bắt đầu và kết thúc + 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 +``` + +Lưu ý rằng chúng ta đã xác định hai hằng số để xác định độ dài tối đa được sử dụng cũng như độ dài của cửa sổ trượt và ta đã thêm một chút dọn dẹp trước khi tokenize: một số câu hỏi trong tập dữ liệu SQuAD có thêm khoảng trắng ở đầu và kết thúc mà không thêm bất kỳ thứ gì (và chiếm dung lượng khi được tokenize nếu bạn sử dụng mô hình như RoBERTa), vì vậy ta đã xóa những khoảng trắng thừa đó. + +Để áp dụng hàm này cho toàn bộ tập huấn luyện, chúng ta sử dụng phương thức `Dataset.map()` với `batched=True`. Điều này cần thiết ở đây vì ta đang thay đổi độ dài của tập dữ liệu (vì một mẫu có thể cung cấp một số đặc trưng huấn luyện): + +```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) +``` + +Như ta có thể thấy, quá trình tiền xử lý đã thêm khoảng 1,000 đặc trưng. Bộ huấn luyện hiện đã sẵn sàng để sử dụng - hãy cùng tìm hiểu về quá trình tiền xử lý của bộ kiểm định! + +### Xử lý dữ liệu kiểm định + +Việc xử lý trước dữ liệu kiểm định sẽ dễ dàng hơn một chút vì chúng ta không cần tạo nhãn (trừ khi chúng ta muốn tính toán mất mát kiểm định, nhưng con số đó sẽ không thực sự giúp chúng ta hiểu mô hình tốt như thế nào). Niềm vui thực sự sẽ là diễn giải các dự đoán của mô hình thành các khoảng của bối cảnh ban đầu. Đối với điều này, chúng ta sẽ chỉ cần lưu trữ cả ánh xạ offset và một số cách để khớp từng đối tượng đã tạo với ví dụ ban đầu mà nó xuất phát. Vì có một cột ID trong tập dữ liệu gốc, chúng ta sẽ sử dụng ID đó. + +Điều duy nhất chúng ta sẽ thêm ở đây là một chút dọn dẹp các ánh xạ offset. Chúng sẽ chứa các phần bù cho câu hỏi và ngữ cảnh, nhưng khi chúng ta đang ở giai đoạn hậu xử lý, chúng ta sẽ không có cách nào để biết phần nào của ID đầu vào tương ứng với ngữ cảnh và phần nào là câu hỏi (phương thức `sequence_ids()` ta đã sử dụng chỉ có sẵn cho đầu ra của tokenizer). Vì vậy, chúng ta sẽ đặt các offset tương ứng với câu hỏi thành `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 +``` + +Chúng ta có thể áp dụng hàm này trên toàn bộ tập dữ liệu kiểm định như trước đây: + +```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) +``` + +Trong trường hợp này, chúng ta chỉ thêm một vài trăm mẫu, vì vậy có vẻ như các ngữ cảnh trong tập dữ liệu kiểm định ngắn hơn một chút. + +Bây giờ chúng ta đã tiền xử lý tất cả dữ liệu, chúng ta có thể tham gia khóa huấn luyện. + +{#if fw === 'pt'} + +## Tinh chỉnh mô hìn với API `Trainer` + +Đoạn mã huấn luyện cho mẫu này sẽ trông rất giống trong các phần trước - điều khó nhất sẽ là viết hàm `compute_metrics()`. Vì chúng ta đã đệm tất cả các mẫu đến độ dài tối đa mà ta đặt, không có công cụ đối chiếu dữ liệu để xác định, vì vậy việc tính toán số liệu này thực sự là điều duy nhất chúng ta phải lo lắng. Phần khó khăn sẽ là hậu xử lý các dự đoán của mô hình thành các khoảng văn bản trong các ví dụ ban đầu; khi ta đã làm điều đó, chỉ số từ thư viện 🤗 Datasets sẽ thực hiện hầu hết công việc cho mình. + +{:else} + +## Tinh chỉnh mô hìn với Keras + +Đoạn mã huấn luyện cho mẫu này sẽ trông rất giống trong các phần trước, nhưng việc tính toán các số liệu sẽ là một thử thách độc đáo. Vì chúng ta đã đệm tất cả các mẫu đến độ dài tối đa mà chúng ta đặt, không có công cụ đối chiếu dữ liệu để xác định, vì vậy việc tính toán số liệu này thực sự là điều duy nhất ta phải lo lắng. Phần khó sẽ là hậu xử lý các dự đoán của mô hình thành các khoảng văn bản trong các ví dụ ban đầu; khi chúng ta đã làm điều đó, chỉ số từ thư viện 🤗 Datasets sẽ thực hiện hầu hết công việc cho ta. + +{/if} + +### Hậu xử lý + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Mô hình sẽ trả về các logit đầu ra cho các vị trí bắt đầu và kết thúc của câu trả lời trong ID đầu vào, như chúng ta đã thấy trong quá trình khám phá pipeline [`question-answering`](/course/chapter6/3b). Bước tiền xử lý sẽ tương tự như những gì chúng ta đã làm ở đó, vì vậy đây là lời nhắc nhanh về các bước chúng ta đã thực hiện: + +- Chúng ta đã che các logit bắt đầu và kết thúc tương ứng với các token bên ngoài ngữ cảnh. +- Sau đó, chúng ta chuyển đổi các logit bắt đầu và kết thúc thành xác suất bằng cách sử dụng softmax. +- Chúng ta quy điểm cho từng cặp `(start_token, end_token)` cách lấy tích của hai xác suất tương ứng. +- Chúng ta đã tìm kiếm cặp có điểm tối đa mang lại câu trả lời hợp lệ (ví dụ: `start_token` thấp hơn `end_token`). + +Ở đây, chúng ta sẽ thay đổi quy trình này một chút vì chúng ta không cần tính điểm thực tế (chỉ là câu trả lời dự đoán). Điều này có nghĩa là chúng ta có thể bỏ qua bước softmax. Để đi nhanh hơn, chúng ta cũng sẽ không tính điểm tất cả các cặp `(start_token, end_token)` có thể, mà chỉ những cặp tương ứng với logit `n_best` cao nhất (với `n_best = 20`). Vì chúng ta sẽ bỏ qua softmax, những điểm đó sẽ là điểm logit và sẽ có được bằng cách lấy tổng của logit bắt đầu và kết thúc (thay vì nhân, vì quy tắc \(\log(ab) = \log(a) + \log(b)\\)). + +Để chứng minh tất cả những điều này, chúng ta sẽ cần một số loại dự đoán. Vì chúng ta chưa huấn luyện mô hình của mình, chúng ta sẽ sử dụng mô hình mặc định cho pipeline QA để tạo ra một số dự đoán trên một phần nhỏ của tập hợp kiểm. Chúng ta có thể sử dụng chức năng xử lý tương tự như trước đây; bởi vì nó dựa vào hằng số toàn cục `tokenizer`, chúng ta chỉ cần thay đổi đối tượng đó thành tokenizer của mô hình mà chúng ta muốn sử dụng tạm thời: + +```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, +) +``` + +Bây giờ, quá trình tiền xử lý đã hoàn tất, chúng ta thay đổi tokenizer trở lại cái mà chúng ta đã chọn ban đầu: + +```python +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +Sau đó, chúng ta loại bỏ các cột của `eval_set` mà mô hình không mong đợi, xây dựng một lô với tất cả bộ kiểm định nhỏ đó và chuyển nó qua mô hình. Nếu có sẵn GPU, chúng ta sử dụng nó để chạy nhanh hơn: + +{#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) +``` + +Vì `Trainer` sẽ trả cho ta các dự đoán dưới dạng mảng NumPy, ta sẽ lấy các logit bắt đầu và kết thúc và chuyển nó thành dạng: + +```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) +``` + +Để dễ dàng thử nghiệm, hãy chuyển đổi các kết quả đầu ra này thành mảng NumPy: + +```python +start_logits = outputs.start_logits.numpy() +end_logits = outputs.end_logits.numpy() +``` + +{/if} + +Bây giờ, chúng ta cần tìm câu trả lời dự đoán cho từng ví dụ trong `small_eval_set` của chúng ta. Một ví dụ có thể đã được chia thành nhiều đặc trưng trong `eval_set`, vì vậy bước đầu tiên là ánh xạ từng mẫu trong `small_eval_set` với các đặc trưng tương ứng trong `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) +``` + +Với điều này trong tay, chúng ta thực sự có thể bắt đầu làm việc bằng cách lặp lại tất cả các mẫu và, đối với mỗi mẫu, thông qua tất cả các đặc trưng liên quan. Như chúng ta đã nói trước đây, chúng ta sẽ xem xét điểm logit cho các logit bắt đầu và kết thúc của `n_best`, ngoại trừ các vị trí cung cấp: + +- Một câu trả lời sẽ không nằm trong ngữ cảnh +- Một câu trả lời có độ dài âm +- Một câu trả lời quá dài (chúng ta giới hạn khả năng ở mức `max_answer_length=30`) + +Khi chúng ta có tất cả các câu trả lời có thể được ghi cho một mẫu, ta chỉ cần chọn một câu có điểm logit tốt nhất: + +```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: + # Bỏ qua các câu trả lời không đầu đủ trong ngữ cảnh + if offsets[start_index] is None or offsets[end_index] is None: + continue + # Bỏ qua những câu trả lời có độ dài < 0 hoặc > 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"]}) +``` + +Định dạng cuối cùng của các câu trả lời được dự đoán là định dạng sẽ được dự đoán theo chỉ số mà chúng ta sẽ sử dụng. Như thường lệ, chúng ta có thể tải nó với sự trợ giúp của thư viện 🤗 Evaluate: + +```python +import evaluate + +metric = evaluate.load("squad") +``` + +Thước đo này mong đợi các câu trả lời được dự đoán ở định dạng mà chúng ta đã thấy ở trên (danh sách các từ điển có một khóa cho ID của mẫu và một khóa cho văn bản được dự đoán) và các câu trả lời lý thuyết ở định dạng bên dưới (danh sách các từ điển có một khóa cho ID của mẫu và một khóa cho các câu trả lời có thể có): + + +```python +theoretical_answers = [ + {"id": ex["id"], "answers": ex["answers"]} for ex in small_eval_set +] +``` + +Bây giờ chúng ta có thể kiểm tra xem ta có nhận được kết quả hợp lý hay không bằng cách xem xét yếu tố đầu tiên của cả hai danh sách: + +```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]}} +``` + +Không tệ lắm! Bây giờ chúng ta hãy xem xét điểm số mà số liệu mang lại cho chúng ta: + +```python +metric.compute(predictions=predicted_answers, references=theoretical_answers) +``` + +```python out +{'exact_match': 83.0, 'f1': 88.25} +``` + +Một lần nữa, điều đó khá tốt theo [bài báo của nó](https://arxiv.org/abs/1910.01108v2), DistilBERT được tinh chỉnh trên SQuAD thu được 79.1 và 86.9 trên toàn bộ tập dữ liệu. + +{#if fw === 'pt'} + +Bây giờ chúng ta hãy đặt mọi thứ ta vừa làm trong một hàm `compute_metrics()` mà ta sẽ sử dụng trong `Trainer`. Thông thường, hàm `compute_metrics()` đó chỉ nhận được một tuple `eval_preds` với các logit và nhãn. Ở đây chúng ta sẽ cần nhiều hơn một chút, vì chúng ta phải tìm trong tập dữ liệu các đặc trưng cho phần bù và trong tập dữ liệu các ví dụ cho các ngữ cảnh ban đầu, vì vậy chúng ta sẽ không thể sử dụng chức năng này để nhận kết quả đánh giá thường xuyên trong quá trình huấn luyện. Chúng ta sẽ chỉ sử dụng nó khi kết thúc khóa huấnl luyện để kiểm tra kết quả. + +Hàm `compute_metrics()` nhóm các bước giống như trước; chúng ta chỉ thêm một kiểm tra nhỏ trong trường hợp ta không đưa ra bất kỳ câu trả lời hợp lệ nào (trong trường hợp đó ta dự đoán một chuỗi trống). + +{:else} + +Bây giờ, hãy đặt mọi thứ ta vừa làm vào một hàm `compute_metrics()` mà ta sẽ sử dụng sau khi huấn luyện mô hình của mình. Chúng ta sẽ cần truyền nhiều hơn là nhật ký đầu ra, vì ta phải tìm trong tập dữ liệu các đặc trưng cho phần offset và trong tập dữ liệu các mẫu cho các ngữ cảnh ban đầu: + +{/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 = [] + + # Lặp qua tất cả các đặc trưng liên quan tới mẫu đó + 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: + # Bỏ qua câu trả lời không xuất hiện hoàn toàn trong ngữ cảnh + if offsets[start_index] is None or offsets[end_index] is None: + continue + # Bỏ qua những câu trả lời với độ dài < 0 hoặc > 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) + + # Chọn câu trả lời có điểm cao nhất + 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) +``` + +Chúng ta có thể kiểm tra nó hoạt động dựa trên dự đoán của mình: + +```python +compute_metrics(start_logits, end_logits, eval_set, small_eval_set) +``` + +```python out +{'exact_match': 83.0, 'f1': 88.25} +``` + +Trông khá ổn! Bây giờ chúng ta hãy sử dụng điều này để tinh chỉnh mô hình của mình. + +### Tinh chỉnh mô hình + +{#if fw === 'pt'} + +Giờ ta đã sẵn sàng để huấn luyện mô hình của mình. Hãy cũng tạo ra nó sử dụng lớp `AutoModelForQuestionAnswering` như trước đó: + +```python +model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) +``` + +{:else} + +Giờ ta đã sẵn sàng để huấn luyện mô hình của mình. Hãy cũng tạo ra nó sử dụng lớp `TFAutoModelForQuestionAnswering` như trước đó: + +```python +model = TFAutoModelForQuestionAnswering.from_pretrained(model_checkpoint) +``` + +{/if} + +Như thường lệ, chúng ta nhận được cảnh báo rằng một số trọng số không được sử dụng (các trọng số từ phần đầu huấn luyện trước) và một số trọng số khác được khởi tạo ngẫu nhiên (các trọng số cho đầu trả lời câu hỏi). Bây giờ bạn nên quen với điều này, nhưng điều đó có nghĩa là mô hình này chưa sẵn sàng để sử dụng và cần được tinh chỉnh - điều tốt là chúng ta sắp làm được điều đó! + +Để có thể đẩy mô hình của mình lên Hub, chúng ta cần đăng nhập vào Hugging Face. Nếu bạn đang chạy đoạn mã này trong notebook, bạn có thể làm như vậy với hàm tiện ích sau, hàm này sẽ hiển thị một tiện ích mà bạn có thể nhập thông tin đăng nhập của mình: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Nếu bạn không làm việc trong notebook, chỉ cần nhập dòng sau vào thiết bị đầu cuối của bạn: + +```bash +huggingface-cli login +``` + +{#if fw === 'pt'} + +Khi điều này được thực hiện, chúng ta có thể xác định `TrainingArguments` của mình. Như ta đã nói khi xác định chức năng của mình để tính toán các chỉ số, chúng ta sẽ không thể có vòng lặp đánh giá thường xuyên vì đặc trưng của hàm `compute_metrics()`. Chúng ta có thể viết lớp con của riêng mình về `Trainer` để làm điều này (một cách tiếp cận bạn có thể tìm thấy trong [bộ lệnh mẫu cho hỏi đáp](https://github.com/huggingface/transformers/blob/master/examples/pytorch/question-answering/trainer_qa.py)), nhưng hơi dài cho phần này. Thay vào đó, chúng ta sẽ chỉ đánh giá mô hình khi kết thúc huấn luyện tại đây và chỉ cho bạn cách thực hiện đánh giá thường xuyên trong "Vòng huấn luyện tùy chỉnh" bên dưới. + +Đây thực sự là nơi API `Trainer` thể hiện các giới hạn của nó và là lúc thư viện 🤗 Accelerate tỏa sáng: việc tùy chỉnh lớp cho một trường hợp sử dụng cụ thể có thể gây khó khăn, nhưng việc điều chỉnh một vòng huấn luyện được tiếp xúc hoàn toàn rất dễ dàng. + +Chúng ta hãy xem xét các `TrainingArguments` của mình: + +```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, +) +``` + +Chúng ta đã thấy hầu hết những điều này trước đây: chúng ta đặt một số siêu tham số (như tốc độ học, số epoch ta dùng để huấn luyện và một số phân rã trọng số) và cho biết rằng chúng ta muốn lưu mô hình vào cuối mỗi epoch, bỏ qua đánh giá và tải kết quả của mình lên Model Hub. Chúng ta cũng cho phép huấn luyện chính xác hỗn hợp với `fp16 = True`, vì nó có thể tăng tốc huấn luyện một cách độc đáo trên GPU gần đây. + +{:else} + +Bây giờ đã xong, chúng ta có thể tạo TF Datasets của mình. Chúng ta có thể sử dụng công cụ đối chiếu dữ liệu mặc định đơn giản lần này: + +```python +from transformers import DefaultDataCollator + +data_collator = DefaultDataCollator(return_tensors="tf") +``` + +Và giờ chúng ta tạo bộ dữ liệu như bình thường. + +```python +tf_train_dataset = train_dataset.to_tf_dataset( + columns=[ + "input_ids", + "start_positions", + "end_positions", + "attention_mask", + "token_type_ids", + ], + collate_fn=data_collator, + shuffle=True, + batch_size=16, +) +tf_eval_dataset = validation_dataset.to_tf_dataset( + columns=["input_ids", "attention_mask", "token_type_ids"], + collate_fn=data_collator, + shuffle=False, + batch_size=16, +) +``` + +Tiếp theo, chúng ta thiết lập các siêu tham số huấn luyện và biên dịch mô hình của mình: + +```python +from transformers import create_optimizer +from transformers.keras_callbacks import PushToHubCallback +import tensorflow as tf + +# Số bước huấn luyện là số lượng mẫu trong tập dữ liệu, chia cho kích thước lô sau đó nhân +# với tổng số epoch. Lưu ý rằng tf_train_dataset ở đây là tf.data.Dataset theo lô, +# không phải là Hugging Face Dataset ban đầu, vì vậy len() của nó vốn là 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) + +# Huấn luyện trong mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +Cuối cùng, ta đã sẵn sàng để huấn luyện với `model.fit()`. Ta sử dụng `PushToHubCallback` để tải mô hình lên Hub sau mỗi epoch. + +{/if} + +Mặc định, kho lưu trữ được sử dụng sẽ nằm trong không gian tên của bạn và được đặt tên theo thư mục đầu ra mà bạn đã đặt, vì vậy trong trường hợp của mình, nó sẽ nằm trong `"sgugger/bert-finetuned-squad"`. Chúng ta có thể ghi đè điều này bằng cách chuyển một `hub_model_id`; ví dụ: để đẩy mô hình vào tổ chức `huggingface_course`, chúng ta đã sử dụng `hub_model_id="huggingface_course/bert-finetuned-squad"` (là mô hình mà ta đã liên kết ở đầu phần này). + +{#if fw === 'pt'} + + + +💡 Nếu thư mục đầu ra bạn đang sử dụng tồn tại, nó cần phải là bản sao cục bộ của kho lưu trữ mà bạn muốn đẩy đến (vì vậy hãy đặt tên mới nếu bạn gặp lỗi khi xác định `Trainer` của mình). + + + +Cuối cùng, ta chỉ cần truyền mọi thứ vào lớp `Trainer` và khởi động việc huấn luyện: + +```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) + +# Chúng ta sẽ thực hiện kiểm định sau đó, vì vậy không có quá trình huấnlluyện giữa quá trình kiểm định +model.fit(tf_train_dataset, callbacks=[callback], epochs=num_train_epochs) +``` + +{/if} + +Lưu ý rằng trong khi quá trình huấn luyện diễn ra, mỗi khi mô hình được lưu (ở đây, mỗi epoch), nó sẽ được tải lên Hub ở chế độ nền. Bằng cách này, bạn sẽ có thể tiếp tục huấn luyện của mình trên một máy khác nếu cần. Toàn bộ quá trình huấn luyện mất một khoảng thời gian (hơn một giờ trên Titan RTX), vì vậy bạn có thể uống một ly cà phê hoặc đọc lại một số phần của khóa học mà bạn thấy khó khăn hơn trong khi tiếp tục. Cũng lưu ý rằng ngay sau khi epoch đầu tiên kết thúc, bạn sẽ thấy một số trọng số được tải lên Hub và bạn có thể bắt đầu chơi với mô hình của mình trên trang của nó. + +{#if fw === 'pt'} + +Sau khi quá trình huấn luyện hoàn tất, cuối cùng ta cũng có thể đánh giá mô hình của mình (và cầu nguyện rằng ta đã không dành tất cả thời gian tính toán vào việc gì). Phương thức `predict()` của `Trainer` sẽ trả về một bộ giá trị trong đó các phần tử đầu tiên sẽ là các dự đoán của mô hình (ở đây là một cặp với các logit bắt đầu và kết thúc). Chúng ta gửi chúng đến hàm `compute_metrics())` của mình: + +```python +predictions, _, _ = trainer.predict(validation_dataset) +start_logits, end_logits = predictions +compute_metrics(start_logits, end_logits, validation_dataset, raw_datasets["validation"]) +``` + +{:else} + +Sau khi quá trình huấn luyện hoàn tất, cuối cùng ta cũng có thể đánh giá mô hình của mình (và cầu nguyện rằng ta đã không dành tất cả thời gian tính toán vào việc gì). Phương thức `predict()` của `model` sẽ đảm nhận việc nhận các dự đoán và vì ta đã thực hiện tất cả các công việc khó khăn trong việc xác định một hàm `compute_metrics()` trước đó, chúng ta có thể nhận được kết quả của mình trong một dòng duy nhất: + +```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} +``` + +Tuyệt quá! Để so sánh, điểm cơ bản được báo cáo trong bài báo BERT cho mô hình này là 80.8 và 88.5, vì vậy chúng ta đang ở đúng vị trí của mình. + +{#if fw === 'pt'} + +Cuối cùng, ta sử dụng phương thức `push_to_hub()` để đảm bảo ta sẽ tải phiên bản mới nhất của mô hình: + +```py +trainer.push_to_hub(commit_message="Training complete") +``` + +Điều này trả về URL của cam kết mà nó vừa thực hiện, nếu bạn muốn kiểm tra nó: + +```python out +'https://huggingface.co/sgugger/bert-finetuned-squad/commit/9dcee1fbc25946a6ed4bb32efb1bd71d5fa90b68' +``` + +`Trainer` cũng soạn thảo một thẻ mô hình với tất cả các kết quả đánh giá và tải nó lên. + +{/if} + +Ở giai đoạn này, bạn có thể sử dụng tiện ích luận suy trên Model Hub để kiểm tra mô hình và chia sẻ mô hình đó với bạn bè, gia đình và vật nuôi yêu thích của bạn. Bạn đã tinh chỉnh thành công một mô hình trong tác vụ hỏi đáp - xin chúc mừng! + + + +✏️ **Đến lượt bạn!** Hãy thử một kiến trúc mô hình khác để xem liệu nó có hoạt động tốt hơn trong tác vụ này không! + + + +{#if fw === 'pt'} + +Nếu bạn muốn tìm hiểu sâu hơn một chút về vòng huấn luyện, bây giờ chúng tôi sẽ hướng dẫn bạn cách thực hiện điều tương tự bằng cách sử dụng 🤗 Accelerate. + +## Một vòng lặp huấn luyện tuỳ chỉnh + +Bây giờ chúng ta hãy xem toàn bộ vòng lặp huấn luyện, vì vậy bạn có thể dễ dàng tùy chỉnh các phần bạn cần. Nó sẽ trông rất giống với vòng lặp huấn luyện trong [Chương 3](/course/chapter3/4), ngoại trừ vòng lặp đánh giá. Chúng ta sẽ có thể đánh giá mô hình thường xuyên vì ta không bị hạn chế bởi lớp `Trainer` nữa. + +### Chuấn bị mọi thứ cho huấn luyện + +Đầu tiên, chúng ta cần xây dựng các `DataLoader` từ các tập dữ liệu của mình. Chúng ta đặt định dạng của các tập dữ liệu đó thành `"torch"` và xóa các cột trong tập xác thực không được mô hình sử dụng. Sau đó, chúng ta có thể sử dụng `default_data_collator` được cung cấp bởi Transformers dưới dạng `collate_fn` và xáo trộn bộ huấn luyện, nhưng không phải bộ kiểm định: + +```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 +) +``` + +Tiếp theo, chúng ta khôi phục mô hình của mình, để đảm bảo rằng ta không tiếp tục tinh chỉnh từ trước mà bắt đầu lại từ mô hình được huấn luyện trước BERT: + +```py +model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) +``` + +Sau đó, chúng ta sẽ cần một trình tối ưu hóa. Như thường lệ, ta sử dụng `AdamW` cổ điển, giống như Adam, nhưng với một bản sửa lỗi trong cách phân rã trọng số được áp dụng: + +```py +from torch.optim import AdamW + +optimizer = AdamW(model.parameters(), lr=2e-5) +``` + +Khi chúng ta có tất cả các đối tượng đó, chúng ta có thể gửi chúng đến phương thức `accelerator.prepare()`. Hãy nhớ rằng nếu bạn muốn huấn luyện về TPU trong notebook Colab, bạn sẽ cần chuyển tất cả mã này vào một hàm huấn luyện và điều đó sẽ không thực thi bất kỳ ô khởi tạo một `Accelerator` nào. Chúng ta có thể buộc huấn luyện độ chính xác hỗn hợp bằng cách chuyển `fp16=True` vào `Accelerator` (hoặc, nếu bạn đang thực thi mã dưới dạng tập lệnh, chỉ cần đảm bảo điền vào 🤗 Accelerate `config` một cách thích hợp). + +```py +from accelerate import Accelerator + +accelerator = Accelerator(fp16=True) +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + +Như bạn đã biết từ các phần trước, chúng ta chỉ có thể sử dụng độ dài `train_dataloader` để tính số bước huấn luyện sau khi nó đã trải qua phương thức `accelerator.prepare()`. Chúng ta sử dụng cùng một lịch trình tuyến tính như trong các phần trước: + +```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, +) +``` + +Để đẩy mô hình của mình lên Hub, chúng ta sẽ cần tạo một đối tượng `Repository` trong một thư mục đang làm việc. Đầu tiên hãy đăng nhập vào Hugging Face Hub, nếu bạn chưa đăng nhập. Chúng ta sẽ xác định tên kho lưu trữ từ ID mô hình mà ta muốn cung cấp cho mô hình của mình (vui lòng thay thế `repo_name` bằng sự lựa chọn của riêng bạn; nó chỉ cần chứa tên người dùng của bạn, đó là những gì hàm `get_full_repo_name()` thực hiện ): + +```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' +``` + +Sau đó, chúng ta có thể sao chép kho lưu trữ đó trong một thư mục cục bộ. Nếu nó đã tồn tại, thư mục cục bộ này phải là bản sao của kho lưu trữ mà ta đang làm việc: + +```py +output_dir = "bert-finetuned-squad-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +Giờ ta có thể tải mọi thử ta lưu trong `output_dir` bằng cách gọi phương thức `repo.push_to_hub()`. Nó sẽ giúp ta tải các mô hình tức thì ở cuối mỗi epoch. + +## Vòng lặp huấn luyện + +Bây giờ chúng ta đã sẵn sàng để viết vòng lặp huấn luyện đầy đủ. Sau khi xác định thanh tiến trình để theo dõi quá trình huấn luyện diễn ra như thế nào, vòng lặp có ba phần: + +- Bản thân quá trình huấn luyện, là sự lặp lại cổ điển trên `train_dataloader`, truyền thẳng qua mô hình, sau đó truyền ngược và tối ưu hóa. +- Bước đánh giá, trong đó ta thu thập tất cả các giá trị cho `start_logits` và `end_logits` trước khi chuyển đổi chúng thành mảng NumPy. Khi vòng lặp đánh giá kết thúc, chúng ta nối tất cả các kết quả. Lưu ý rằng chúng ta cần cắt bớt vì `Accelerator` có thể đã thêm một vài mẫu vào cuối để đảm bảo chúng ta có cùng số lượng mẫu trong mỗi quy trình. +- Lưu và tải lên, nơi trước tiên chúng ta lưu mô hình và trình mã hóa, sau đó gọi `repo.push_to_hub()`. Như chúng ta đã làm trước đây, chúng ta sử dụng đối số `blocking=False` để yêu cầu thư viện 🤗 Hub đẩy vào một quá trình không đồng bộ. Bằng cách này, quá trình huấn luyện tiếp tục diễn ra bình thường và lệnh (dài) này được thực thi ở chế độ nền. + +Đây là mã hoàn chỉnh cho vòng lặp huấn luyện: + +```py +from tqdm.auto import tqdm +import torch + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Huấn luyện + 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) + + # Đánh giá + 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) + + # Lưu và tải + 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 + ) +``` + +Trong trường hợp đây là lần đầu tiên bạn thấy một mô hình được lưu bằng 🤗 Accelerate, hãy dành một chút thời gian để kiểm tra ba dòng mã đi kèm với nó: + +```py +accelerator.wait_for_everyone() +unwrapped_model = accelerator.unwrap_model(model) +unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) +``` + +Dòng đầu tiên đã tự giải thích: nó cho tất cả các quá trình chờ cho đến khi mọi người ở giai đoạn đó trước khi tiếp tục. Điều này là để đảm bảo rằng chúng ta có cùng một mô hình trong mọi quy trình trước khi lưu. Sau đó, ta lấy `unwrapped_model`, là mô hình cơ sở mà ta đã xác định. Phương thức `accelerator.prepare()` thay đổi mô hình để hoạt động trong huấn luyện phân tán, vì vậy nó sẽ không có phương thức `save_pretrained()` nữa; phương thức `accelerator.unwrap_model()` hoàn tác bước đó. Cuối cùng, chúng ta gọi `save_pretrained()` nhưng yêu cầu phương thức đó sử dụng `accelerator.save()` thay vì `torch.save()`. + +Khi điều này được thực hiện, bạn sẽ có một mô hình tạo ra kết quả khá giống với mô hình được huấn luyện với `Trainer`. Bạn có thể kiểm tra mô hình mà ta đã huấn luyện bằng cách sử dụng mã này tại [*huggingface-course/bert-finetuned-squad-accelerate*](https://huggingface.co/huggingface-course/bert-finetuned-squad-accelerate). Và nếu bạn muốn kiểm tra bất kỳ tinh chỉnh nào đối với vòng lặp huấn luyện, bạn có thể trực tiếp thực hiện chúng bằng cách chỉnh sửa đoạn mã được hiển thị ở trên! + +{/if} + +## Sử dụng mô hình tinh chỉnh + +Chúng tôi đã chỉ cho bạn cách bạn có thể sử dụng mô hình mà chúng ta đã tinh chỉnh trên Model Hub bằng tiện ích luận suy. Để sử dụng nó cục bộ trong một `pipeline`, bạn chỉ cần chỉ định mã định danh mô hình: + +```py +from transformers import pipeline + +# Thay thế nó với checkpoint của bạn +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'} +``` + +Tuyệt quá! Mô hình của chúng ta đang hoạt động tốt như mô hình mặc định cho pipeline này! diff --git a/chapters/vi/chapter7/8.mdx b/chapters/vi/chapter7/8.mdx new file mode 100644 index 000000000..6549c507c --- /dev/null +++ b/chapters/vi/chapter7/8.mdx @@ -0,0 +1,17 @@ +# Làm chủ NLP + +Nếu bạn đã tiến xa đến mức này trong khóa học, xin chúc mừng - bạn hiện có tất cả kiến ​​thức và công cụ cần thiết để giải quyết (gần như) bất kỳ tác vụ NLP nào với 🤗 Transformers và hệ sinh thái Hugging Face! + +Chúng ta đã thấy rất nhiều trình thu thập dữ liệu khác nhau, vì vậy chúng tôi đã tạo video nhỏ này để giúp bạn tìm thấy công cụ nào sẽ sử dụng cho từng tác vụ: + + + +Sau khi hoàn thành chuyến khám phá chớp nhoáng đến các tác vụ NLP cốt lõi, bạn nên: + +* Nắm được kiến ​​trúc nào (bộ mã hóa, bộ giải mã hoặc bộ mã hóa-giải mã) phù hợp nhất cho từng tác vụ +* Hiểu sự khác biệt giữa huấn luyện trước trước và tinh chỉnh mô hình ngôn ngữ +* Biết cách huấn luyện các mô hình Transformer bằng cách sử dụng API `Trainer` và các tính năng huấn luyện phân tán của 🤗 Accelerate hoặc TensorFlow và Keras, tùy thuộc vào việc bạn đang theo dõi hướng nào +* Hiểu ý nghĩa và giới hạn của các chỉ số như ROUGE và BLEU đối với các tác vụ tạo văn bản +* Biết cách tương tác với các mô hình được tinh chỉnh của bạn, cả trên Hub và sử dụng `pipeline` từ 🤗 Transformers + +Bất chấp tất cả những kiến ​​thức này, sẽ có lúc bạn gặp phải một lỗi khó trong đoạn mã của mình hoặc có câu hỏi về cách giải quyết một vấn đề NLP cụ thể. May mắn thay, cộng đồng Hugging Face ở đây để giúp bạn! Trong chương cuối cùng của phần này của khóa học, chúng ta sẽ khám phá cách bạn có thể gỡ lỗi các mô hình Transformer của mình và yêu cầu trợ giúp một cách hiệu quả. diff --git a/chapters/vi/chapter7/9.mdx b/chapters/vi/chapter7/9.mdx new file mode 100644 index 000000000..1ce1ddb35 --- /dev/null +++ b/chapters/vi/chapter7/9.mdx @@ -0,0 +1,324 @@ + + + + +# Đố vui cuối chương + +Cùng kiểm tra xem bạn đã học được những gì trong chương này! + +### 1. Tác vụ nào sau đây có thể được coi là vấn đề phân loại token? + + + +### 2. Phần tiền xử lý để phân loại token khác với các pipeline tiền xử lý khác ở điểm nào? + +-100 để đánh nhãn các token đặc biệt.", + explain: "Điều đó không dành riêng cho việc phân loại token -- ta luôn sử dụng -100 như nhãn của token ta muốn bỏ quả trong hàm mất mát." + }, + { + text: "Chúng ta cần đảm bảo cắt bớt hoặc đệm các nhãn có cùng kích thước với các đầu vào khi áp dụng phép cắt bớt/đệm.", + explain: "Thật vậy! Tuy nhiên đó không phải là sự khác biệt duy nhất.", + correct: true + } + ]} +/> + +### 3. Vấn đề gì phát sinh khi ta tokenize các từ trong bài toán phân loại token và muốn đánh nhãn token? + +-100 cho chúng để chúng bị bỏ qua khi tính sự mất mát." + }, + { + text: "Mỗi từ có thể tạo ra nhiều token, nên đến cuối ta sẽ có nhiều token hơn số nhãn.", + explain: "Đó là vấn đề chính và chúng ta cần phải căn chỉnh các nhãn gốc với các token.", + correct: true + }, + { + text: "Các token được thêm không có nhãn, nên không có vấn đề gì.", + explain: "Không chính xác; ta cần số nhãn tương ứng số token nếu không mô hình sẽ báo lỗi." + } + ]} +/> + +### 4. "Thích ứng chuyên môn" là gì? + + + +### 5. Các nhãn trong bài toán mô hình ngôn ngữ bị ẩn đi là gì? + + + +### 6. Tác vụ nào sau đây có thể được coi là bài toán chuỗi sang chuỗi? + + + +### 7. Đây là phuwong pháp phù hợp để tiền xử lý dữ liệu cho bài toán chuỗi sang chuỗi? + +inputs=... và targets=....", + explain: "Đây có thể là một API mà chúng tôi sẽ thêm vào trong tương lai, nhưng điều đó không khả thi ở thời điểm hiện tại." + }, + { + text: "Đầu vào và nhãn đều phải được tiền xử lý, trong hai lệch gọi riêng biệt tới tokenizer.", + explain: "Điều đó đúng, nhưng chưa đủ. Bạn cần phải làm gì đó nữa để đảm bảo trình tokenizer xử lý đúng cả hai." + }, + { + text: "Như thường lệ, chúng ta chỉ phhải tokenize đầu vào.", + explain: "Không phải với bài toán phân loại chuỗi; nhãn cũng là văn bản nên ta cần chuyển sang dạng số!" + }, + { + text: "Đầu vào phải đước gửi tới trình tokenizer, và nhãn cũng vậy, nhưng theo trình quản lý ngữ cảnh đặc biệt.", + explain: "Đúng vậy, tokenizer cần xử lý nhãn dựa trên trình quản lý ngữ cảnh.", + correct: true + } + ]} +/> + +{#if fw === 'pt'} + +### 8. Vì sao lại có lớp con `Trainer` cho các bài toán chuỗi sang chuỗi? + +-100", + explain: "Đây không phải là tuỳ chỉnh mất mát, mà là cách sự mất mát vẫn luôn được tính toán." + }, + { + text: "Vì các vấn đề chuỗi sang chuỗi cần một vòng đánh giá đặc biệt", + explain: "Chính xác. Các dự đoán mô hình chuỗi sang chuỗi thường được chạy sử dụng phương thức generate().", + correct: true + }, + { + text: "Bởi vì nhãn là văn bản trong bài toán chuỗi sang chuỗi", + explain: "Trainer không thực sự quan tâm vì chúng đã được tiền xử lý trước đó." + }, + { + text: "Vì ta sử dụng hai mô hình trong bài toán chuỗi sang chuỗi", + explain: "Chúng ta sử dụng hai mô hình cùng một cách, một trình mã hoá và một trình giải mã, nhưng ta sẽ nhóm chúng lại trong một mô hình." + } + ]} +/> + +{:else} + +### 9. Vì sao không cần thiết chỉ định hàm mất mát khi gọi `compile()` trong mô hình Transformer? + + + +{/if} + +### 10. Khi nào bạn nên huấn luyện trước một mô hình mới? + + + +### 11. Vì sao ta dễ huấn luyện trước một mô hình ngôn ngữ khi có khối lượng văn bản khổng lồ? + + + +### 12. Đâu là những thách thức chính khi tiền xử lí dữ liệu cho tác vụ hỏi đáp? + + + +### 13. Làm thể nào để hậu xử lý trong bài toán hỏi đáp? + + diff --git a/chapters/vi/chapter8/1.mdx b/chapters/vi/chapter8/1.mdx new file mode 100644 index 000000000..5b7e4d51b --- /dev/null +++ b/chapters/vi/chapter8/1.mdx @@ -0,0 +1,12 @@ +# Giới thiệu + +Bây giờ bạn đã biết cách giải quyết các tác vụ NLP phổ biến nhất với 🤗 Transformers, bạn sẽ có thể bắt đầu các dự án của riêng mình! Trong chương này, chúng ta sẽ khám phá những việc cần làm khi bạn gặp sự cố. Bạn sẽ học cách gỡ lỗi thành công mã hoặc quá trình huấn luyện của mình và cách yêu cầu cộng đồng trợ giúp nếu bạn không thể tự mình giải quyết vấn đề. Và nếu bạn cho rằng mình đã tìm thấy lỗi trong một trong các thư viện Hugging Faces, chúng tôi sẽ chỉ cho bạn cách tốt nhất để báo cáo lỗi đó để sự cố được giải quyết nhanh nhất có thể. + +Chính xác hơn, trong chương này, bạn sẽ học: + +- Điều đầu tiên cần làm khi bạn gặp lỗi +- Cách yêu cầu trợ giúp trên [diễn đàn](https://discuss.huggingface.co/) +- Cách gỡ lỗi đường dẫn huấn luyện của bạn +- Làm thế nào để viết một vấn đề tốt + +Tất nhiên, không điều gì trong số này liên quan cụ thể đến 🤗 Transformers hoặc hệ sinh thái Hugging Face; các bài học từ chương này có thể áp dụng cho hầu hết các dự án nguồn mở! diff --git a/chapters/vi/chapter8/2.mdx b/chapters/vi/chapter8/2.mdx new file mode 100644 index 000000000..fa70355e8 --- /dev/null +++ b/chapters/vi/chapter8/2.mdx @@ -0,0 +1,364 @@ +# Phải làm gì khi bạn gặp lỗi + + + +Trong phần này, chúng ta sẽ xem xét một số lỗi phổ biến có thể xảy ra khi bạn đang cố gắng tạo dự đoán từ mô hình Transformer mới được điều chỉnh của mình. Điều này sẽ giúp bạn chuẩn bị cho [section 4](/course/chapter8/section4), nơi chúng ta sẽ khám phá cách gỡ lỗi chính giai đoạn huấn luyện. + + + +Chúng tôi đã chuẩn bị [kho lưu trữ mô hình mẫu](https://huggingface.co/lewtun/distilbert-base-uncased-finetuned-squad-d5716d28) cho phần này và nếu bạn muốn chạy mã trong chương này, trước tiên, bạn cần sao chép mô hình vào tài khoản của bạn trên [Hugging Face Hub](https://huggingface.co). Để làm như vậy, trước tiên hãy đăng nhập bằng cách chạy một trong hai thao tác sau trong notebook Jupyter: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +hoặc sau trong terminal yêu thích của bạn: + +```bash +huggingface-cli login +``` + +Thao tác này sẽ nhắc bạn nhập tên người dùng và mật khẩu của mình, đồng thời sẽ lưu token dưới *~/.cache/huggingface/*. Khi bạn đã đăng nhập, bạn có thể sao chép kho mẫu với hàm sau: + +```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(): + # Sao chép kho và trích xuất đường dẫn cục bộ + template_repo_id = "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28" + commit_hash = "be3eaffc28669d7932492681cd5f3e8905e358b4" + template_repo_dir = snapshot_download(template_repo_id, revision=commit_hash) + # Tạo ra một kho rỗng trên Hub + model_name = template_repo_id.split("/")[1] + create_repo(model_name, exist_ok=True) + # Sao chép kho rỗng + new_repo_id = get_full_repo_name(model_name) + new_repo_dir = model_name + repo = Repository(local_dir=new_repo_dir, clone_from=new_repo_id) + # Sao chép các tệp + copy_tree(template_repo_dir, new_repo_dir) + # Đẩy lên Hub + repo.push_to_hub() +``` + +Giờ khi bạn gọi `copy_repository_template()`, nó sẽ tạo ra một bản sao kho lưu trữ mẫu dưới tài khoản của bạn. + +## Gỡ lỗi pipeline 🤗 Transformers + +Để bắt đầu cuộc hành trình của chúng ta vào thế giới tuyệt vời của việc gỡ lỗi các mô hình Transformer, hãy xem xét tình huống sau: bạn đang làm việc với một đồng nghiệp trong một dự án hỏi đáp để giúp khách hàng của một trang web thương mại điện tử tìm thấy câu trả lời về các sản phẩm tiêu dùng. Đồng nghiệp của bạn gửi cho bạn một tin nhắn như: + +> Chúc bạn một ngày tốt lành! Tôi vừa chạy một thử nghiệm bằng cách sử dụng các kỹ thuật trong [Chương 7](/course/chapter7/7) của khóa học Hugging Face và nhận được một số kết quả tuyệt vời trên SQuAD! Tôi nghĩ chúng ta có thể sử dụng mô hình này như một điểm khởi đầu cho dự án của mình. ID mô hình trên Hub là "lewtun/distillbert-base-uncased-finetuned-squad-d5716d28". Hãy thử nghiệm nó xem :) + +và điều đầu tiên bạn nghĩ đến là tải mô hình bằng cách sử dụng `pipeline` từ 🤗 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 +""" +``` + +Ôi không, có vẻ như có gì đó không ổn! Nếu bạn là người mới lập trình, những lỗi kiểu này thoạt đầu có vẻ hơi khó hiểu (thậm chí `OSError` là gì ?!). Lỗi được hiển thị ở đây chỉ là phần cuối cùng của một báo cáo lỗi lớn hơn nhiều được gọi là _Python traceback_ (hay còn gọi là đáu vết ngăn xếp). Ví dụ: nếu bạn đang chạy đoạn mã này trên Google Colab, bạn sẽ thấy một cái gì đó giống như ảnh chụp màn hình sau: + +
+A Python traceback. +
+ +Có rất nhiều thông tin có trong các báo cáo này, vì vậy chúng ta hãy cùng nhau xem qua các phần chính. Điều đầu tiên cần lưu ý là theo dõi phải được đọc _từ dưới lên trên_. Điều này nghe có vẻ kỳ lạ nếu bạn đã quen đọc văn bản tiếng Anh từ trên xuống dưới, nhưng nó phản ánh thực tế là bản truy xuất hiển thị chuỗi các lệnh gọi hàm mà `pipeline` thực hiện khi tải xuống mô hình và trình tokenizer. (Xem [Chương 2](/course/chapter2) để biết thêm chi tiết về cách hoạt động của `pipeline`.) + + + +🚨 Bạn có thấy hộp màu xanh lam xung quanh "6 frames" trong phần truy xuất từ Google Colab không? Đó là một tính năng đặc biệt của Colab, nén phần truy xuất vào các "frames". Nếu bạn dường như không thể tìm ra nguồn gốc của lỗi, hãy đảm bảo rằng bạn mở rộng toàn bộ theo dõi bằng cách nhấp vào hai mũi tên nhỏ đó. + + + +Điều này có nghĩa là dòng cuối cùng của truy xuất cho biết thông báo lỗi cuối cùng và cung cấp tên của ngoại lệ đã được nêu ra. Trong trường hợp này, loại ngoại lệ là `OSError`, cho biết lỗi liên quan đến hệ thống. Nếu chúng ta đọc thông báo lỗi kèm theo, chúng ta có thể thấy rằng dường như có sự cố với tệp *config.json* của mô hình và ta sẽ đưa ra hai đề xuất để khắc phục: + +```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 +""" +``` + + + +💡 Nếu bạn gặp phải thông báo lỗi khó hiểu, chỉ cần sao chép và dán thông báo đó vào thanh tìm kiếm Google hoặc [Stack Overflow](https://stackoverflow.com/) (vâng, thực sự!). Có nhiều khả năng bạn không phải là người đầu tiên gặp phải lỗi và đây là một cách tốt để tìm giải pháp mà những người khác trong cộng đồng đã đăng. Ví dụ: tìm kiếm `OSError: Can't load config for` trên Stack Overflow mang lại nhiều [lần truy cập](https://stackoverflow.com/search?q=OSError%3A+Can%27t+load+config+for+) có thể được sử dụng như một điểm khởi đầu để giải quyết vấn đề. + + + +Đề xuất đầu tiên là yêu cầu ta kiểm tra xem ID mô hình có thực sự chính xác hay không, vì vậy, việc đầu tiên ta làm là sao chép chỉ số nhận dạng và dán nó vào thanh tìm kiếm của Hub: + +
+The wrong model name. +
+ +Rất tiếc, có vẻ như mô hình của anh đồng nghiệp không có trên Hub ... aha, nhưng có một lỗi đánh máy trong tên của mô hình! DistilBERT chỉ có một chữ "l" trong tên của nó, vì vậy hãy sửa lỗi đó và tìm "lewtun/distilbert-base-unsased-finetuned-Squad-d5716d28" thay thế: + +
+The right model name. +
+ +Được rồi, điều này đã thành công. Bây giờ, hãy thử tải xuống mô hình một lần nữa với đúng ID mô hình: + +```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 +""" +``` + +Argh, lại thất bại - chào mừng bạn đến với cuộc sống hàng ngày của một kỹ sư học máy! Vì chúng ta đã sửa ID mô hình, vấn đề phải nằm ở chính kho lưu trữ. Một cách nhanh chóng để truy cập nội dung của một kho lưu trữ trên 🤗 Hub là thông qua hàm `list_repo_files()` của thư viện `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'] +``` + +Thật thú vị - dường như không có tệp *config.json* trong kho lưu trữ! Không có gì ngạc nhiên khi `pipeline` không thể tải mô hình; đồng nghiệp của chúng ta chắc hẳn đã quên đẩy tệp này vào Hub sau khi đã tinh chỉnh nó. Trong trường hợp này, vấn đề có vẻ khá đơn giản để khắc phục: chúng ta có thể yêu cầu họ thêm tệp hoặc, vì chúng ta có thể thấy từ ID mô hình mà mô hình huấn luyện trước đã sử dụng là [`distilbert-base-uncased`](https://huggingface.co/distilbert-base-uncased), chúng ta có thể tải xuống cấu hình cho mô hình này và đẩy nó vào kho lưu trữ của mình để xem liệu điều đó có giải quyết được sự cố hay không. Hãy thử điều đó. Sử dụng các kỹ thuật chúng ta đã học trong [Chương 2](/course/chapter2), chúng ta có thể tải xuống cấu hình của mô hình với lớp `AutoConfig`: + +```python +from transformers import AutoConfig + +pretrained_checkpoint = "distilbert-base-uncased" +config = AutoConfig.from_pretrained(pretrained_checkpoint) +``` + + + +🚨 Cách tiếp cận mà chúng tôi đang thực hiện ở đây không phải là hoàn hảo, vì đồng nghiệp của chúng ta có thể đã chỉnh sửa cấu hình của `distilbert-base-uncased` trước khi tinh chỉnh mô hình. Trong thực tế, chúng ta muốn kiểm tra với họ trước, nhưng với mục đích của phần này, chúng ta sẽ giả định rằng họ đã sử dụng cấu hình mặc định. + + + +Sau đó, chúng ta có thể đẩy nó vào kho lưu trữ mô hình của mình bằng hàm `push_to_hub()` của cấu hình: + +```python +config.push_to_hub(model_checkpoint, commit_message="Add config.json") +``` + +Bây giờ chúng ta có thể kiểm tra xem điều này có hoạt động hay không bằng cách tải mô hình từ cam kết mới nhất trên nhánh `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'} +``` + +Tuyệt vời, nó đã hoạt động! Hãy tóm tắt lại những gì bạn vừa học được: + +- Các thông báo lỗi trong Python được gọi là _tracebacks_ và được đọc từ dưới lên trên. Dòng cuối cùng của thông báo lỗi thường chứa thông tin bạn cần để xác định nguồn gốc của vấn đề. +- Nếu dòng cuối cùng không chứa đủ thông tin, hãy làm theo cách của bạn để truy xuất lại và xem liệu bạn có thể xác định được lỗi xảy ra ở đâu trong mã nguồn hay không. +- Nếu không có thông báo lỗi nào có thể giúp bạn gỡ lỗi, hãy thử tìm kiếm trực tuyến giải pháp cho vấn đề tương tự. +- Các thư viện `huggingface_hub` // 🤗 Hub? cung cấp một bộ công cụ mà bạn có thể sử dụng để tương tác và gỡ lỗi các kho lưu trữ trên Hub. + +Bây giờ bạn đã biết cách gỡ lỗi một đường dẫn, chúng ta hãy xem một ví dụ phức tạp hơn trong bước truyền thẳng của chính mô hình. + +## Gỡ lỗi truyền thẳng mô hình của bạn + +Mặc dù `pipeline` tuyệt vời cho hầu hết các ứng dụng mà bạn cần nhanh chóng tạo dự đoán, đôi khi bạn sẽ cần truy cập nhật ký của mô hình (giả sử, nếu bạn có một số hậu xử lý tùy chỉnh mà bạn muốn áp dụng). Để xem điều gì có thể sai trong trường hợp này, trước tiên hãy lấy mô hình và trình tokenize từ `pipeline` của mình: + +```python +tokenizer = reader.tokenizer +model = reader.model +``` + +Tiếp theo, chúng ta cần một câu hỏi, vì vậy hãy xem liệu các khung yêu thích của chúng ta có được hỗ trợ không: + +```python +question = "Which frameworks can I use?" +``` + +Như đã thấy trong [Chương 7](/course/chapter7), các bước thông thường ta cần làm đó là tokenize đầu vào, trích xuất các logit của token bắt đầu và kết thúc, rồi sau đó giải mã các khoảng trả lời: + +```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 +# Lấy phần có khả năng là bắt đầu của câu trả lời nhất với argmax của điểm trả về +answer_start = torch.argmax(answer_start_scores) +# Lấy phần có khả năng là kết thúc của câu trả lời nhất với argmax của điểm trả về +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' +""" +``` + +Ôi trời, có vẻ như chúng ta có một lỗi trong đoạn mã của mình! Nhưng chúng ta không sợ gỡ lỗi chút nào. Bạn có thể sử dụng trình gỡ lỗi Python trong notebook: + + + +hoặc trong terminal: + + + +Ở đây, khi đọc thông báo lỗi cho chúng ta biết rằng đối tượng `'list' object has no attribute 'size'` và chúng ta có thể thấy một mũi tên `-->` trỏ đến dòng nơi vấn đề đã được nêu ra trong `model(** input)`. Bạn có thể gỡ lỗi điều này một cách tương tự bằng cách sử dụng trình gỡ lỗi Python, nhưng bây giờ chúng ta chỉ cần in ra một phần của `inputs` để xem những gì chúng ta có: + +Here, reading the error message tells us that `'list' object has no attribute 'size'`, and we can see a `-->` arrow pointing to the line where the problem was raised in `model(**inputs)`.You can debug this interactively using the Python debugger, but for now we'll simply print out a slice of `inputs` to see what we have: + +```python +inputs["input_ids"][:5] +``` + +```python out +[101, 2029, 7705, 2015, 2064] +``` + +Điều này chắc chắn trông giống như một `list` Python bình thường, nhưng hãy kiểm tra kỹ loại: + +```python +type(inputs["input_ids"]) +``` + +```python out +list +``` + +Vâng, đó chắc chắn là một `list` Python. Vậy điều gì đã xảy ra? Nhớ lại từ [Chương 2](/course/chapter2) rằng các lớp `AutoModelForXxx` trong 🤗 Transformers hoạt động trên _tensors_ (trong PyTorch hoặc TensorFlow) và hoạt động phổ biến là trích xuất các kích thước của tensor bằng cách sử dụng `Tensor.size()` trong PyTorch. Chúng ta hãy xem xét lại quá trình truy vết, để xem dòng nào đã kích hoạt ngoại lệ: + +``` +~/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' +``` + +Có vẻ như mã của chúng ta đã cố gắng gọi `input_ids.size()`, nhưng điều này rõ ràng sẽ không hoạt động đối với một `list` Python, vốn chỉ là một vùng chứa. Làm thế nào chúng ta có thể giải quyết vấn đề này? Tìm kiếm thông báo lỗi trên Stack Overflow đưa ra một số [lượt truy cập](https://stackoverflow.com/search?q=AttributeError%3A+%27list%27+object+has+no+attribute+%27size%27&s=c15ec54c-63cb-481d-a749-408920073e8f) liên quan. Nhấp vào câu hỏi đầu tiên sẽ hiển thị một câu hỏi tương tự như câu hỏi của chúng ta, với câu trả lời được hiển thị trong ảnh chụp màn hình bên dưới: + +
+An answer from Stack Overflow. +
+ +Câu trả lời khuyên chúng ta nên thêm `return_tensors='pt'` vào tokenizer, vì vậy hãy xem điều đó có phù hợp với chúng ta không: + +```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 +# Lấy phần có khả năng là bắt đầu của câu trả lời nhất với argmax của điểm trả về +answer_start = torch.argmax(answer_start_scores) +# Lấy phần có khả năng là kết thúc của câu trả lời nhất với argmax của điểm trả về +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 +""" +``` + +Tốt, nó đã hoạt động! Đây là một ví dụ tuyệt vời về mức độ hữu ích của Stack Overflow: bằng cách xác định một vấn đề tương tự, chúng ta có thể hưởng lợi từ kinh nghiệm của những người khác trong cộng đồng. Tuy nhiên, một tìm kiếm như thế này không phải lúc nào cũng mang lại câu trả lời phù hợp, vậy bạn có thể làm gì trong những trường hợp như vậy? May mắn thay, có một cộng đồng các nhà phát triển chào đón trên [diễn đàn Hugging Face](https://discuss.huggingface.co/) có thể giúp bạn! Trong phần tiếp theo, chúng ta sẽ xem xét cách bạn có thể tạo ra các câu hỏi tốt trên diễn đàn có khả năng được trả lời. diff --git a/chapters/vi/chapter8/3.mdx b/chapters/vi/chapter8/3.mdx new file mode 100644 index 000000000..328bed409 --- /dev/null +++ b/chapters/vi/chapter8/3.mdx @@ -0,0 +1,171 @@ +# Yêu cầu trợ giúp trên diễn đàn + + + + + +[Diễn đàn Hugging Face](https://discuss.huggingface.co) là nơi tuyệt vời để nhận được sự giúp đỡ từ các nhóm nguồn mở và cộng đồng Hugging Face. Trang chủ luôn trông như sau: + +
+The Hugging Face forums. +
+ +Ở bên tay trái, bạn có thể thấy tất cả các danh mục mà các chủ đề khác nhau được nhóm lại, trong khi bên tay phải hiển thị các chủ đề gần đây nhất. Chủ đề là một bài đăng có chứa tiêu đề, danh mục và mô tả; nó khá giống với định dạng vấn đề GitHub mà chúng ta đã thấy khi tạo tập dữ liệu của riêng mình trong [Chương 5](/course/chapter5). Như tên cho thấy, danh mục [Beginners](https://discuss.huggingface.co/c/beginners/5) chủ yếu dành cho những người mới bắt đầu với hệ sinh thái và thư viện Hugging Face. Mọi câu hỏi trên bất kỳ thư viện nào đều được hoan nghênh ở đó, có thể là để gỡ lỗi một số mã hoặc để yêu cầu trợ giúp về cách thực hiện điều gì đó. (Điều đó nói rằng, nếu câu hỏi của bạn liên quan đến một thư viện cụ thể, bạn có thể nên chuyển đến danh mục thư viện tương ứng trên diễn đàn.) + +Tương tự, danh mục [Intermediate](https://discuss.huggingface.co/c/intermediate/6)và [Research](https://discuss.huggingface.co/c/research/7) dành cho các câu hỏi nâng cao hơn , ví dụ về thư viện hoặc một số nghiên cứu NLP mới thú vị mà bạn muốn thảo luận. + +Và đương nhiên, chúng ta cũng nên đề cập đến danh mục [Course](https://discuss.huggingface.co/c/course/20), nơi bạn có thể đặt bất kỳ câu hỏi nào liên quan đến khóa học Hugging Face! + +Khi bạn đã chọn một danh mục, bạn sẽ sẵn sàng viết chủ đề đầu tiên của mình. Bạn có thể tìm thấy một số [hướng dẫn](https://discuss.huggingface.co/t/how-to-request-support/3128) trong diễn đàn về cách thực hiện việc này và trong phần này chúng ta sẽ xem xét một số tính năng tạo nên một chủ đề hay. + +## Viết một bài đăng tốt trên diễn đàn + +Như một ví dụ, giả sử rằng chúng ta đang cố gắng tạo các biểu diễn từ các bài viết trên Wikipedia để tạo một công cụ tìm kiếm tùy chỉnh. Như thường lệ, chúng ta tải tokenizer và mô hình như sau: + +```python +from transformers import AutoTokenizer, AutoModel + +model_checkpoint = "distilbert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +model = AutoModel.from_pretrained(model_checkpoint) +``` + +Giờ giả sử ta đang cố nhúng toàn bộ phần này của [Wikipedia](https://en.wikipedia.org/wiki/Transformers) lên 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 +``` + +Rất tiếc, chúng ta đã gặp sự cố - và thông báo lỗi khó hiểu hơn nhiều so với những thông báo chúng ta thấy trong [phần 2](/course/chapter8/section2)! Chúng ta không thể thực hiện được toàn bộ quá trình truy vết, vì vậy chúng ta quyết định chuyển sang diễn đàn Hugging Face để được trợ giúp. Làm thế nào chúng ta có thể tạo ra chủ đề? + +Để bắt đầu, chúng ta cần nhấp vào nút "New Topic" hay "Chủ đề mới" ở góc trên bên phải (lưu ý rằng để tạo chủ đề, chúng ta cần đăng nhập): + +
+Creating a new forum topic. +
+ +Thao tác này sẽ hiển thị một giao diện viết, nơi chúng ta có thể nhập tiêu đề của chủ đề của mình, chọn một danh mục và soạn thảo nội dung: + +
+The interface for creating a forum topic. +
+ +Vì lỗi dường như chỉ xảy ra với 🤗 Transformers, nên chúng ta sẽ chọn lỗi này cho danh mục. Nỗ lực đầu tiên của chúng ta trong việc giải thích vấn đề có thể trông giống như sau: + +
+Drafting the content for a new forum topic. +
+ +Mặc dù chủ đề này chứa thông báo lỗi mà chúng tôi cần trợ giúp, nhưng có một số vấn đề với cách viết: + +1. Tiêu đề không mang tính mô tả cao, vì vậy bất kỳ ai duyệt diễn đàn sẽ không thể biết chủ đề là gì nếu không đọc phần nội dung. +2. Phần thân không cung cấp đủ thông tin về _nơi_ bắt nguồn lỗi và _cách_ để tạo lại lỗi đó. +3. Chủ đề gắn thẻ trực tiếp một vài người với giọng điệu hơi khắt khe. + +Các chủ đề như thế này không có khả năng nhận được câu trả lời nhanh (nếu họ nhận được một câu trả lời nào đó), vì vậy hãy xem cách chúng ta có thể cải thiện nó. Chúng ta sẽ bắt đầu với vấn đề đầu tiên là chọn một tiêu đề hay. + +### Choosing a descriptive title + +Nếu bạn đang cố gắng nhận trợ giúp về một lỗi trong mã của mình, một nguyên tắc chung là đưa đủ thông tin vào tiêu đề để người khác có thể nhanh chóng xác định xem họ có nghĩ rằng họ có thể trả lời câu hỏi của bạn hay không. Trong ví dụ đang chạy của mình, chúng ta biết tên của ngoại lệ đang được nêu ra và có một số gợi ý rằng nó được kích hoạt trong phần truyền thẳng của mô hình, nơi chúng tôi gọi là `model(**inputs)`. Để thông báo điều này, một tiêu đề có thể có là: + +> Source of IndexError in the AutoModel forward pass? + +hay + +> Nguồn của IndexError trong thẻ chuyển tiếp AutoModel? + +Tiêu đề này cho người đọc biết bạn nghĩ rằng lỗi đến từ _đâu_ và nếu họ đã gặp phải `IndexError` trước đó, thì rất có thể họ sẽ biết cách gỡ lỗi nó. Tất nhiên, tiêu đề có thể là bất kỳ thứ gì bạn muốn và các biến thể khác như: + +> Why does my model produce an IndexError? + +hay + +> Tại sao mô hình của tôi tạo ra một IndexError? + +cũng có thể ổn. Bây giờ chúng ta đã có một tiêu đề mô tả, hãy xem cách cải thiện nội dụng phần thân bài. + +### Định dạng các đoạn mã của bạn + +Đọc mã nguồn đã đủ khó trong IDE, nhưng còn khó hơn khi mã được sao chép và dán dưới dạng văn bản thuần túy! May mắn thay, các diễn đàn về Hugging Face hỗ trợ việc sử dụng Markdown, vì vậy bạn nên luôn đặt các khối mã của mình bằng ba dấu gạch ngược (```) để dễ đọc hơn. Hãy làm điều này để sửa chữa thông báo lỗi - và trong khi chúng ta xử lý nó, hãy làm cho phần nội dung lịch sự hơn một chút so với phiên bản gốc của mình: + +
+Our revised forum topic, with proper code formatting. +
+ +Như bạn có thể thấy trong ảnh chụp màn hình, việc bao bọc các khối mã trong dấu gạch ngược sẽ chuyển văn bản thô thành mã được định dạng, hoàn chỉnh với kiểu màu! Cũng lưu ý rằng các dấu gạch ngược đơn lẻ có thể được sử dụng để định dạng các biến nội tuyến, giống như chúng tôi đã làm cho `distilbert-base-unsased`. Chủ đề này có vẻ tốt hơn nhiều và với một chút may mắn, chúng ta có thể tìm thấy ai đó trong cộng đồng có thể đoán được lỗi là gì. Tuy nhiên, thay vì dựa vào may mắn, chúng ta hãy làm cho cuộc sống dễ dàng hơn bằng cách đưa vào chi tiết đầy đủ các truy vết của nó! + +### Bao gồm toàn bộ truy vết + +Vì dòng cuối cùng của bản truy vết thường đủ để gỡ lỗi đoạn mã của riêng bạn, nên bạn có thể chỉ cung cấp dòng đó trong chủ đề của mình để "tiết kiệm dung lượng". Mặc dù có chủ ý tốt, điều này thực sự khiến người khác có thể _khó_ gỡ lỗi vấn đề _hơn_ vì thông tin cao hơn trong bản truy xuất có thể thực sự hữu ích. Vì vậy, một phương pháp hay là sao chép và dán _toàn bộ_ dấu vết, đồng thời đảm bảo rằng nó được định dạng độc đáo. Vì những lần truy xuất này có thể mất nhiều thời gian, một số người thích hiển thị chúng sau khi họ đã giải thích mã nguồn. Làm thôi nào. Bây giờ, chủ đề diễn đàn của chúng ta trông giống như sau: + +
+Our example forum topic, with the complete traceback. +
+ +Điều này có nhiều thông tin hơn và một người đọc cẩn thận có thể chỉ ra rằng vấn đề dường như là do chuyển một đầu vào dài vì dòng này trong bản truy xuất: + +> Token indices sequence length is longer than the specified maximum sequence length for this model (583 > 512). + +Tuy nhiên, chúng ta có thể khiến mọi thứ trở nên dễ dàng hơn với họ bằng cách cung cấp mã thực đã gây ra lỗi. Hãy làm điều đó ngay bây giờ. + +### Cung cấp một ví dụ có thể tái tạo + +Nếu bạn đã từng cố gắng gỡ lỗi đoạn mã của người khác, trước tiên có thể bạn đã cố gắng tạo lại sự cố mà họ đã báo cáo để bạn có thể bắt đầu làm việc theo cách của mình thông qua truy xuất để xác định lỗi. Nó không khác gì khi nói đến (hoặc cung cấp) hỗ trợ trên các diễn đàn, vì vậy sẽ thực sự hữu ích nếu bạn có thể cung cấp một ví dụ nhỏ mô tả lại lỗi. Một nửa thời gian, chỉ cần đi qua bài tập này sẽ giúp bạn nhận ra điều gì đang xảy ra. Trong mọi trường hợp, phần còn thiếu trong ví dụ của chúng ta là hiển thị _các đầu vào_ mà ta đã cung cấp cho mô hình. Làm điều đó cho chúng ta một cái gì đó giống như ví dụ đã hoàn thành sau: + +
+The final version of our forum topic. +
+ +Chủ đề này hiện chứa khá nhiều thông tin và nó được viết theo cách có nhiều khả năng thu hút sự chú ý của cộng đồng và nhận được câu trả lời hữu ích. Với những hướng dẫn cơ bản này, giờ đây bạn có thể tạo các chủ đề tuyệt vời để tìm câu trả lời cho các câu hỏi về 🤗 Transformers của mình! diff --git a/chapters/vi/chapter8/4.mdx b/chapters/vi/chapter8/4.mdx new file mode 100644 index 000000000..8c84fc127 --- /dev/null +++ b/chapters/vi/chapter8/4.mdx @@ -0,0 +1,792 @@ + + +# Gỡ lỗi quy trình huấn luyện + + + +Bạn đã viết một kịch bản tuyệt đẹp để huấn luyện hoặc tinh chỉnh một mô hình trong một tác vụ nhất định, tuân thủ một cách nghiêm túc lời khuyên từ [Chương 7](/course/chapter7). Nhưng khi bạn khởi chạy lệnh `trainr.train()`, một điều kinh khủng xảy ra: bạn gặp lỗi 😱! Hoặc tệ hơn, mọi thứ dường như ổn và quá trình huấn luyện chạy mà không có lỗi, nhưng mô hình kết quả là tồi tệ. Trong phần này, chúng tôi sẽ chỉ cho bạn những gì bạn có thể làm để gỡ lỗi các loại vấn đề này. + +## Gỡ lỗi quy trình huấn luyện + + + +Vấn đề khi bạn gặp lỗi trong `trainr.train()` có thể đến từ nhiều nguồn, vì `Trainer` thường tập hợp rất nhiều thứ lại với nhau. Nó chuyển đổi bộ dữ liệu thành các dataloader, do đó, vấn đề có thể là một cái gì đó sai trong bộ dữ liệu của bạn hoặc một số vấn đề khi cố gắng kết hợp hàng loạt các phần tử của bộ dữ liệu với nhau. Sau đó, nó lấy một loạt dữ liệu và đưa nó vào mô hình, vì vậy vấn đề có thể nằm ở mã mô hình. Sau đó, nó tính toán các độ dốc và thực hiện bước tối ưu hóa, vì vậy vấn đề cũng có thể nằm trong trình tối ưu hóa của bạn. Và ngay cả khi mọi thứ diễn ra tốt đẹp cho quá trình huấn luyện, vẫn có thể xảy ra sự cố trong quá trình đánh giá nếu có vấn đề với chỉ số của bạn. + +Cách tốt nhất để gỡ lỗi phát sinh trong `trainr.train()` là đi qua toàn pipeline này theo cách thủ công để xem mọi thứ diễn ra như thế nào. Sau đó, lỗi thường rất dễ giải quyết. + +Để chứng minh điều này, chúng ta sẽ sử dụng tập lệnh (cố gắng) tinh chỉnh mô hình DistilBERT trên [tập dữ liệu MNLI](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() +``` + +Nếu bạn cố gắng thực thi nó, bạn sẽ gặp phải một lỗi khá khó hiểu: + +```python out +'ValueError: You have to specify either input_ids or inputs_embeds' +``` + +### Kiểm tra dữ liệu của bạn + +Điều này không cần phải nói, nhưng nếu dữ liệu của bạn bị hỏng, `Trainer` sẽ không thể tạo ra các lô chứ đừng nói đến việc huấn luyện mô hình của bạn. Vì vậy, điều đầu tiên, bạn cần phải xem xét những gì bên trong bộ huấn luyện của bạn. + +Để tránh mất vô số giờ để cố gắng sửa một cái gì đó không phải là nguồn gốc của lỗi, chúng tôi khuyên bạn nên sử dụng `trainr.train_dataset` để kiểm tra. Vì vậy, hãy làm điều đó ở đây: + +```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.'} +``` + +Bạn có nhận thấy điều gì đó sai không? Điều này, cùng với thông báo lỗi về việc thiếu `input_ids`, sẽ khiến bạn nhận ra đó là các văn bản chứ không phải số mà mô hình có thể hiểu được. Ở đây, lỗi ban đầu rất dễ gây hiểu nhầm bởi vì `Trainer` tự động loại bỏ các cột không khớp với đặc trưng của mô hình (nghĩa là các tham số mà mô hình mong đợi). Điều đó có nghĩa là ở đây, mọi thứ ngoại trừ nhãn đều bị loại bỏ. Do đó, không có vấn đề gì với việc tạo các lô và sau đó gửi chúng đến mô hình, điều này do đó phàn nàn rằng nó không nhận được đầu vào thích hợp. + +Tại sao dữ liệu không được xử lý? Chúng ta đã sử dụng phương thức `Dataset.map()` trên các tập dữ liệu để áp dụng tokenizer trên mỗi mẫu. Nhưng nếu bạn xem kỹ mã, bạn sẽ thấy rằng chúng ta đã mắc sai lầm khi chuyển các bộ huấn luyện và kiểm định cho `Trainer`. Thay vì sử dụng `tokenized_datasets` ở đây, chúng ta đã sử dụng `raw_datasets` 🤦. Vì vậy, hãy cùng sửa chữa điều này! + +```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() +``` + +Mã mới này bây giờ sẽ đưa ra một lỗi khác (có tiến triển!): + +```python out +'ValueError: expected sequence of length 43 at dim 1 (got 37)' +``` + +Nhìn vào dấu truy vết, chúng ta có thể thấy lỗi xảy ra trong bước đối chiếu dữ liệu: + +```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 +``` + +Vì vậy, chúng ta nên chuyển sang điều đó. Tuy nhiên, trước khi thực hiện, chúng ta hãy hoàn thành việc kiểm tra dữ liệu của mình, để chắc chắn rằng nó chính xác 100%. + +Một điều bạn luôn nên làm khi gỡ lỗi một phiên huấn luyện là xem xét các đầu vào được giải mã của mô hình của bạn. Chúng ta không thể hiểu được những con số mà chúng ta cung cấp trực tiếp cho nó, vì vậy chúng ta nên xem những con số đó đại diện cho điều gì. Ví dụ: trong thị giác máy tính, điều đó có nghĩa là nhìn vào hình ảnh được giải mã của các pixel bạn chuyển qua, trong lời nói, điều đó có nghĩa là nghe các mẫu âm thanh được giải mã và đối với ví dụ NLP của chúng ta ở đây, điều đó có nghĩa là sử dụng trình tokenizer để giải mã đầu vào: + +```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]' +``` + +Vì vậy, điều đó có vẻ chính xác. Bạn nên làm điều này cho tất cả các phím trong đầu vào: + +```py +trainer.train_dataset[0].keys() +``` + +```python out +dict_keys(['attention_mask', 'hypothesis', 'idx', 'input_ids', 'label', 'premise']) +``` + +Lưu ý rằng các khóa không tương ứng với đầu vào được mô hình chấp nhận sẽ tự động bị loại bỏ, vì vậy ở đây chúng tôi sẽ chỉ giữ lại `input_ids`, `attention_mask`, và `label` (sẽ được đổi tên thành `labels`). Để kiểm tra kỹ mô hình, bạn có thể in loại mô hình của mình, sau đó kiểm tra tài liệu của nó: + +```py +type(trainer.model) +``` + +```python out +transformers.models.distilbert.modeling_distilbert.DistilBertForSequenceClassification +``` + +Vì vậy, trong trường hợp của mình, chúng ta có thể kiểm tra các tham số được chấp nhận trên [trang này](https://huggingface.co/transformers/model_doc/distilbert.html#distilbertforsequenceclassification). `Trainer` cũng sẽ ghi lại các cột mà nó đang loại bỏ. + +Chúng ta đã kiểm tra xem các ID đầu vào có chính xác hay không bằng cách giải mã chúng. Tiếp theo là `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] +``` + +Vì chúng ta không áp dụng đệm trong quá trình tiền xử lý của mình, điều này có vẻ hoàn toàn tự nhiên. Để đảm bảo không có vấn đề gì với attention mask đó, hãy kiểm tra xem nó có cùng độ dài với ID đầu vào của chúng ta không: + +```py +len(trainer.train_dataset[0]["attention_mask"]) == len( + trainer.train_dataset[0]["input_ids"] +) +``` + +```python out +True +``` + +Tốt đấy! Cuối cùng, hãy kiểm tra nhãn của mình: + +```py +trainer.train_dataset[0]["label"] +``` + +```python out +1 +``` + +Giống như các ID đầu vào, đây là một con số không thực sự có ý nghĩa. Như chúng ta đã thấy trước đây, ánh xạ giữa các số nguyên và tên nhãn được lưu trữ bên trong thuộc tính `names` của *đặc trưng* tương ứng của tập dữ liệu: + +```py +trainer.train_dataset.features["label"].names +``` + +```python out +['entailment', 'neutral', 'contradiction'] +``` + +Vì vậy, `1` có nghĩa là `neutral`, có nghĩa là hai câu chúng ta đã thấy ở trên không mâu thuẫn với nhau và câu đầu tiên không bao hàm câu thứ hai. Điều đó có vẻ đúng! + +Chúng ta không có token ID ở đây, vì DistilBERT không mong đợi chúng; nếu bạn có một số trong mô hình của mình, bạn cũng nên đảm bảo rằng chúng khớp đúng với vị trí của câu đầu tiên và câu thứ hai trong đầu vào. + + + +✏️ **Đến lượt bạn!** Kiểm tra xem mọi thứ có chính xác không với phần tử thứ hai của tập dữ liệu huấn luyện. + + + +Chúng ta chỉ thực hiện kiểm tra tập huấn luyện ở đây, nhưng tất nhiên bạn nên kiểm tra kỹ các tập kiểm định và kiểm tra theo cùng một cách. + +Bây giờ chúng ta biết bộ dữ liệu của mình trông ổn, đã đến lúc kiểm tra bước tiếp theo của quy trình huấn luyện. + +### Từ bộ dữ liệu thành dataloader + +Điều tiếp theo có thể xảy ra sai sót trong quy trình huấn luyện là khi `Trainer` cố gắng tạo các lô từ tập huấn luyện hoặc kiểm định. Khi bạn chắc chắn rằng tập dữ liệu của `Trainer` là chính xác, bạn có thể thử tạo một loạt theo cách thủ công bằng cách thực hiện như sau (thay thế `train` bằng `eval` cho dataloader kiểm định): + +```py +for batch in trainer.get_train_dataloader(): + break +``` + +Mã này tạo ra dataloader huấn luyện, sau đó lặp qua nó, dừng lại ở lần lặp đầu tiên. Nếu mã thực thi mà không có lỗi, bạn có lô huấn luyện đầu tiên mà bạn có thể kiểm tra và nếu mã lỗi xảy ra, bạn biết chắc chắn vấn đề nằm trong dataloader, như trường hợp ở đây: + +```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) +``` + +Việc kiểm tra khung cuối cùng của quá trình truy xuất sẽ đủ để cung cấp cho bạn manh mối, nhưng hãy tìm hiểu kỹ hơn một chút. Hầu hết các vấn đề trong quá trình tạo lô đều phát sinh do việc đối chiếu các ví dụ thành một lô duy nhất, vì vậy, điều đầu tiên cần kiểm tra khi nghi ngờ là `collate_fn` mà ` DataLoader` của bạn đang sử dụng: + +```py +data_collator = trainer.get_train_dataloader().collate_fn +data_collator +``` + +```python out + Dict[str, Any]> +``` + +Vì vậy, đây là `default_data_collator`, nhưng đó không phải là những gì chúng ta muốn trong trường hợp này. Chúng ta muốn đưa các ví dụ của mình vào câu dài nhất trong lô, được thực hiện bởi trình đối chiếu `DataCollatorWithPadding`. Và trình đối chiếu dữ liệu này được cho là được sử dụng theo mặc định bởi `Trainer`, vậy tại sao nó không được sử dụng ở đây? + +Câu trả lời là vì chúng ta đã không chuyển `tokenizer` cho `Trainer`, vì vậy nó không thể tạo `DataCollatorWithPadding` mà chúng ta muốn. Trong thực tế, bạn đừng bao giờ ngần ngại chuyển một cách rõ ràng bộ đối chiếu dữ liệu mà bạn muốn sử dụng, để đảm bảo rằng bạn tránh được những loại lỗi này. Hãy điều chỉnh mã của chúng ta để thực hiện chính xác điều đó: + +```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() +``` + +Tin tốt? Chúng ta không gặp lỗi như trước nữa, đó chắc chắn là sự tiến bộ. Các tin xấu? Thay vào đó, chúng ta nhận được một lỗi CUDA khét tiếng: + +```python out +RuntimeError: CUDA error: CUBLAS_STATUS_ALLOC_FAILED when calling `cublasCreate(handle)` +``` + +Điều này thật tệ vì lỗi CUDA nói chung rất khó gỡ lỗi. Chúng ta sẽ xem trong một phút nữa cách giải quyết vấn đề này, nhưng trước tiên hãy kết thúc phân tích của chúng ta về tạo lô. + +Nếu bạn chắc chắn trình đối chiếu dữ liệu của mình là đúng, bạn nên thử áp dụng nó trên một vài mẫu của tập dữ liệu của mình: + +```py +data_collator = trainer.get_train_dataloader().collate_fn +batch = data_collator([trainer.train_dataset[i] for i in range(4)]) +``` + +Mã này sẽ không thành công vì `train_dataset` chứa các cột chuỗi mà `Trainer` thường loại bỏ. Bạn có thể xóa chúng theo cách thủ công hoặc nếu bạn muốn sao chép chính xác những gì mà `Trainer` đang làm ở hậu trường, bạn có thể gọi phương thức riêng `Trainer._remove_unused_columns()` để thực hiện điều đó: + +```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)]) +``` + +Sau đó, bạn sẽ có thể gỡ lỗi theo cách thủ công những gì xảy ra bên trong bộ đối chiếu dữ liệu nếu lỗi vẫn tiếp diễn. + +Bây giờ chúng ta đã gỡ lỗi quy trình tạo lô, đã đến lúc chuyển qua mô hình! + +### Xem qua mô hình + +Bạn sẽ có thể nhận được một lô bằng cách thực hiện lệnh sau: + +```py +for batch in trainer.get_train_dataloader(): + break +``` + +Nếu bạn đang chạy mã này trong notebook, bạn có thể gặp lỗi CUDA tương tự như lỗi đã thấy trước đó, trong trường hợp đó, bạn cần khởi động lại notebook của mình và thực hiện lại đoạn mã cuối cùng mà không có dòng `trainer.train()`. Đó là điều khó chịu thứ hai về lỗi CUDA: chúng phá vỡ kernel của bạn một cách không thể khắc phục được. Điều khó chịu nhất về chúng là thực tế là chúng rất khó để gỡ lỗi. + +Tại sao vậy? Nó liên quan đến cách hoạt động của GPU. Chúng cực kỳ hiệu quả trong việc thực hiện song song nhiều thao tác, nhưng hạn chế là khi một trong các lệnh đó dẫn đến lỗi, bạn sẽ không biết ngay lập tức. Chỉ khi chương trình gọi đồng bộ hóa nhiều quy trình trên GPU thì nó mới nhận ra có gì đó không ổn, vì vậy lỗi thực sự được phát sinh ở một nơi không liên quan gì đến những gì đã tạo ra nó. Ví dụ, nếu chúng ta xem lại lần truy xuất trước của mình, lỗi đã được phát sinh trong quá trình truyền ngược, nhưng chúng ta sẽ thấy trong một phút rằng nó thực sự bắt nguồn từ một cái gì đó trong truyền thẳng. + +Vậy làm cách nào để gỡ những lỗi đó? Câu trả lời rất dễ dàng: chúng tôi không. Trừ khi lỗi CUDA của bạn là lỗi hết bộ nhớ (có nghĩa là không có đủ bộ nhớ trong GPU của bạn), bạn nên quay lại CPU để gỡ lỗi. + +Để thực hiện điều này trong trường hợp của mình, chúng ta chỉ cần đặt mô hình trở lại CPU và gọi nó vào lô của mình - lô được trả về bởi `DataLoader` vẫn chưa được chuyển đến 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. +``` + +Vì vậy, bức tranh ngày càng rõ ràng. Thay vì gặp lỗi CUDA, bây giờ chúng ta có `IndexError` trong tính toán mất mát (vì vậy không liên quan gì đến lan truyền ngược, như ta đã nói trước đó). Chính xác hơn, chúng ta có thể thấy rằng nhãn 2 tạo ra lỗi, vì vậy đây là thời điểm rất tốt để kiểm tra số lượng nhãn của mô hình của ta: + +```python +trainer.model.config.num_labels +``` + +```python out +2 +``` + +Với hai nhãn, chỉ có 0 và 1 được phép làm nhãn, nhưng theo thông báo lỗi, chúng tôi nhận được 2. Nhận được 2 thực ra là bình thường: nếu chúng ta nhớ tên nhãn mà chúng ta đã trích xuất trước đó, có ba, vì vậy chúng ta có chỉ số 0 , 1 và 2 trong tập dữ liệu của mình. Vấn đề là chúng ta đã không nói điều đó với mô hình của mình, mô hình này lẽ ra phải được tạo với ba nhãn. Vì vậy, chúng ta hãy khắc phục điều đó! + +```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, +) +``` + +Chúng ta chưa bao gồm dòng `trainer.train()`, để dành thời gian kiểm tra xem mọi thứ có ổn không. Nếu chúng ta yêu cầu một lô và chuyển nó vào mô hình của mình, nó hiện hoạt động mà không có lỗi! + +```py +for batch in trainer.get_train_dataloader(): + break + +outputs = trainer.model.cpu()(**batch) +``` + +Bước tiếp theo là quay lại GPU và kiểm tra xem mọi thứ vẫn hoạt động không: + +```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) +``` + +Nếu bạn vẫn gặp lỗi, hãy đảm bảo rằng bạn khởi động lại notebook của mình và chỉ thực thi phiên bản cuối cùng của tập lệnh. + +### Thực hiện một bước tối ưu hóa + +Bây giờ ta biết rằng chúng ta có thể xây dựng các lô thực sự đi qua mô hình, chúng ta đã sẵn sàng cho bước tiếp theo của quy trình huấn luyện: tính toán độ dốc và thực hiện bước tối ưu hóa. + +Phần đầu tiên chỉ là vấn đề gọi phương thức `backward()` khi tính mất mát: + +```py +loss = outputs.loss +loss.backward() +``` + +Rất hiếm khi gặp lỗi ở giai đoạn này, nhưng nếu bạn gặp lỗi, hãy đảm bảo quay lại CPU để nhận được thông báo lỗi hữu ích. + +To perform the optimization step, we just need to create the `optimizer` and call its `step()` method: + +```py +trainer.create_optimizer() +trainer.optimizer.step() +``` + +Một lần nữa, nếu bạn đang sử dụng trình tối ưu hóa mặc định trong `Trainer`, bạn sẽ không gặp lỗi ở giai đoạn này, nhưng nếu bạn có trình tối ưu hóa tùy chỉnh, có thể có một số vấn đề cần gỡ lỗi ở đây. Đừng quên quay lại CPU nếu bạn gặp lỗi CUDA lạ ở giai đoạn này. Nói về lỗi CUDA, trước đó chúng ta đã đề cập đến một trường hợp đặc biệt. Bây giờ chúng ta hãy xem xét điều đó. + +### Xử lý lỗi hết bộ nhớ CUDA + +Bất cứ khi nào bạn nhận được thông báo lỗi bắt đầu bằng `RuntimeError: CUDA out of memory`, điều này cho biết bạn đã hết bộ nhớ GPU. Điều này không được liên kết trực tiếp với mã của bạn và nó có thể xảy ra với một tập lệnh chạy hoàn toàn tốt. Lỗi này có nghĩa là bạn đã cố gắng đưa quá nhiều thứ vào bộ nhớ trong của GPU và dẫn đến lỗi. Giống như với các lỗi CUDA khác, bạn sẽ cần khởi động lại kernel của mình để ở vị trí mà bạn có thể chạy lại quá trình huấn luyện của mình. + +Để giải quyết vấn đề này, bạn chỉ cần sử dụng ít dung lượng GPU hơn - điều mà nói thì dễ hơn làm. Trước tiên, hãy đảm bảo rằng bạn không có hai mô hình GPU trên cùng một lúc (tất nhiên là trừ khi đó là yêu cầu cho vấn đề của bạn). Sau đó, bạn có thể nên giảm kích thước lô của mình, vì nó ảnh hưởng trực tiếp đến kích thước của tất cả các đầu ra trung gian của mô hình và độ dốc của chúng. Nếu sự cố vẫn tiếp diễn, hãy xem xét sử dụng phiên bản mô hình nhỏ hơn của bạn. + + + +Trong phần tiếp theo của khóa học, chúng ta sẽ xem xét các kỹ thuật nâng cao hơn có thể giúp bạn giảm dung lượng bộ nhớ và cho phép bạn tinh chỉnh các mô hình lớn nhất. + + + +### Đánh giá mô hình + +Bây giờ chúng tôi đã giải quyết tất cả các vấn đề với mã của mình, mọi thứ đều hoàn hảo và quá trình huấn luyện sẽ diễn ra suôn sẻ, phải không? Không quá nhanh! Nếu bạn chạy lệnh `trainer.train()`, lúc đầu mọi thứ sẽ ổn, nhưng sau một thời gian, bạn sẽ nhận được những điều sau: + +```py +# Quá trình này sẽ mất nhiều thời gian và xảy ra lỗi, vì vậy bạn không nên chạy ô này +trainer.train() +``` + +```python out +TypeError: only size-1 arrays can be converted to Python scalars +``` + +Bạn sẽ nhận ra lỗi này xuất hiện trong giai đoạn kiểm định, vì vậy đây là điều cuối cùng chúng tôi sẽ cần gỡ lỗi. + +Bạn có thể chạy vòng lặp kiểm định của `Trainer` một cách độc lập để hình thành khóa huấn luyện như sau: + +```py +trainer.evaluate() +``` + +```python out +TypeError: only size-1 arrays can be converted to Python scalars +``` + + + +💡 Bạn phải luôn đảm bảo rằng mình có thể chạy `trainr.evaluate()` trước khi khởi chạy `trainer.train()`, để tránh lãng phí nhiều tài nguyên máy tính trước khi gặp lỗi. + + + +Trước khi cố gắng gỡ lỗi một vấn đề trong vòng kiểm định, trước tiên bạn nên đảm bảo rằng bạn đã xem xét dữ liệu, có thể tạo một lô đúng cách và có thể chạy mô hình của bạn trên đó. Chúng ta đã hoàn thành tất cả các bước đó, vì vậy mã sau có thể được thực thi mà không có lỗi: + +```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) +``` + +Lỗi xuất hiện sau đó, vào cuối giai đoạn đánh giá và nếu chúng ta xem lại bản ghi lại, chúng ta thấy điều này: + +```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() +``` + +Điều này cho chúng tôi biết rằng lỗi bắt nguồn từ mô-đun `datasets/metric.py` - vì vậy đây là sự cố với hàm `compute_metrics()` của mình. Nó cần một bộ dữ liệu với các logits và các nhãn dưới dạng mảng NumPy, vì vậy chúng ta hãy thử cung cấp cho nó rằng: + +```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 +``` + +Chúng ta nhận được cùng một lỗi, vì vậy vấn đề chắc chắn nằm ở hàm đó. Nếu chúng ta nhìn lại mã của nó, chúng ta thấy nó chỉ chuyển tiếp các `predictions` và `labels` đến `metric.compute()`. Vậy có vấn đề gì với phương pháp đó không? Không hẳn vậy. Chúng ta hãy xem nhanh các hình dạng: + +```py +predictions.shape, labels.shape +``` + +```python out +((8, 3), (8,)) +``` + +Các dự đoán của chúng tôi vẫn là logit, không phải dự đoán thực tế, đó là lý do tại sao số liệu trả về lỗi (hơi tối nghĩa) này. Việc sửa chữa khá dễ dàng; chúng ta chỉ cần thêm một argmax trong hàm `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} +``` + +Bây giờ lỗi của chúng ta đã được sửa chữa! Đây là lần cuối cùng, vì vậy kịch bản của chúng ta bây giờ sẽ đào tạo một mô hình đúng cách. + +Để tham khảo, đây là tập lệnh hoàn toàn cố định: + +```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() +``` + +Trong trường hợp này, không còn vấn đề gì nữa và tập lệnh của chúng ta sẽ tinh chỉnh một mô hình sẽ cho kết quả hợp lý. Nhưng chúng ta có thể làm gì khi quá trình huấn luyện diễn ra mà không có bất kỳ lỗi nào, và mô hình được huấn luyện không hoạt động tốt chút nào? Đó là phần khó nhất của học máy và chúng ta sẽ chỉ cho bạn một vài kỹ thuật có thể hữu ích. + + + +💡 Nếu bạn đang sử dụng vòng lặp huấn luyện thủ công, các bước tương tự sẽ áp dụng để gỡ lỗi quy trình huấn luyện của bạn, nhưng việc tách chúng ra sẽ dễ dàng hơn. Tuy nhiên, hãy đảm bảo rằng bạn không quên `model.eval()` hoặc `model.train()` ở đúng nơi, hoặc `zero_grad()` ở mỗi bước! + + + +## Debugging silent errors during training + +What can we do to debug a training that completes without error but doesn't get good results? We'll give you some pointers here, but be aware that this kind of debugging is the hardest part of machine learning, and there is no magical answer. + +### Kiểm tra lại dữ liệu của bạn (một lần nữa!) + +Mô hình của bạn sẽ chỉ học được điều gì đó nếu nó thực sự có thể học được bất cứ điều gì từ dữ liệu của bạn. Nếu có lỗi làm hỏng dữ liệu hoặc các nhãn được gán ngẫu nhiên, rất có thể bạn sẽ không huấn luyện được mô hình nào về tập dữ liệu của mình. Vì vậy, hãy luôn bắt đầu bằng cách kiểm tra kỹ các đầu vào và nhãn đã được giải mã của bạn và tự hỏi bản thân những câu hỏi sau: + +- Dữ liệu được giải mã có dễ hiểu không? +- Bạn có đồng ý với các nhãn? +- Có một nhãn nào phổ biến hơn những nhãn khác không? +- Mất mát/Chỉ số sẽ là bao nhiêu nếu mô hình dự đoán một câu trả lời ngẫu nhiên/luôn là một câu trả lời giống nhau? + + + +⚠️ Nếu bạn đang thực hiện huấn luyện phân tán, hãy in các mẫu tập dữ liệu của bạn trong mỗi quy trình và kiểm tra ba lần để đảm bảo bạn nhận được điều tương tự. Một lỗi phổ biến là có một số nguồn ngẫu nhiên trong quá trình tạo dữ liệu khiến mỗi quy trình có một phiên bản khác nhau của tập dữ liệu. + + + +Sau khi xem xét dữ liệu của bạn, hãy xem qua một số dự đoán của mô hình và giải mã chúng. Nếu mô hình luôn dự đoán cùng một điều, có thể là do tập dữ liệu của bạn thiên về một loại (đối với các vấn đề phân loại); các kỹ thuật như lấy mẫu quá mức các lớp hiếm có thể hữu ích. + +Nếu mất mát/chỉ số đánh giá bạn nhận được trên mô hình ban đầu của mình rất khác với cái bạn mong đợi cho các dự đoán ngẫu nhiên, hãy kiểm tra kỹ cách tính toán tổn thất hoặc số liệu của bạn, vì có thể có một lỗi ở đó. Nếu bạn đang sử dụng một số mất mát mà bạn thêm vào cuối, hãy đảm bảo rằng chúng có cùng quy mô. + +Khi bạn chắc chắn dữ liệu của mình là hoàn hảo, bạn có thể xem liệu mô hình có khả năng huấn luyện về nó hay không bằng một bài kiểm tra đơn giản. + +### Học kĩ mô hình của bạn trong một lô + +Việc học quá nhiều thường là điều chúng ta cố gắng tránh khi huấn luyện, vì nó có nghĩa là mô hình không học cách nhận ra các đặc điểm chung ta muốn mà thay vào đó chỉ là ghi nhớ các mẫu huấn luyện. Tuy nhiên, cố gắng huấn luyện mô hình của bạn lặp đi lặp lại là một bài kiểm tra tốt để kiểm tra xem vấn đề như bạn đã định hình có thể được giải quyết bằng mô hình mà bạn đang cố gắng huấn luyện hay không. Nó cũng sẽ giúp bạn xem liệu tốc độ học ban đầu của bạn có quá cao hay không. + +Thực hiện điều này khi bạn đã xác định được `Trainer` của mình thực sự dễ dàng; chỉ cần lấy một loạt dữ liệu huấn luyện, sau đó chạy một vòng huấn luyện thủ công nhỏ chỉ sử dụng lô đó cho một cái gì đó giống như 20 bước: + +```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() +``` + + + +💡 Nếu dữ liệu huấn luyện của bạn không cân bằng, hãy đảm bảo tạo một loạt dữ liệu huấn luyện có chứa tất cả các nhãn. + + + +Mô hình phải có kết quả trả về gần như hoàn hảo trên cùng một `lô`. Hãy tính toán các chỉ số trên các dự đoán kết quả: + +```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} +``` + +Chính xác 100%, đây là một ví dụ điển hình về việc overfitt(có nghĩa là nếu bạn thử mô hình của mình trên bất kỳ câu nào khác, rất có thể nó sẽ đưa ra câu trả lời sai)! + +Nếu bạn không quản lý để mô hình của mình có được kết quả hoàn hảo như thế này, điều đó có nghĩa là có điều gì đó không ổn trong cách bạn định khung vấn đề hoặc dữ liệu của mình, vì vậy bạn nên khắc phục điều đó. Chỉ khi bạn vượt qua được bài kiểm tra overfit, bạn mới có thể chắc chắn rằng mô hình của mình thực sự có thể học được điều gì đó. + + + +⚠️ Bạn sẽ phải tạo lại mô hình và `Trainer`của mình sau bài kiểm tra overfitt này, vì mô hình thu được có thể sẽ không thể khôi phục và học được điều gì đó hữu ích trên tập dữ liệu đầy đủ của bạn. + + + +### Không điều chỉnh bất cứ thứ gì cho đến khi bạn có mô hình cơ sở đầu tiên + +Điều chỉnh siêu tham số luôn được nhấn mạnh là phần khó nhất của học máy, nhưng nó chỉ là bước cuối cùng giúp bạn hiểu được một chút về chỉ số này. Hầu hết thời gian, các siêu tham số mặc định của `Trainer` sẽ hoạt động tốt để cung cấp cho bạn kết quả tốt, vì vậy đừng khởi chạy tìm kiếm siêu tham số tốn thời gian và tốn kém cho đến khi bạn có thứ gì đó vượt qua mô hình cơ sở mà bạn có trên tập dữ liệu của mình. + +Khi bạn đã có một mô hình đủ tốt, bạn có thể bắt đầu điều chỉnh một chút. Đừng thử khởi chạy một nghìn lần chạy với các siêu tham số khác nhau, nhưng hãy so sánh một vài lần chạy với các giá trị khác nhau cho một siêu thông số để có được ý tưởng về giá trị nào có tác động lớn nhất. + +Nếu bạn đang điều chỉnh chính mô hình, hãy giữ nó đơn giản và đừng thử bất cứ điều gì mà bạn không thể biện minh một cách hợp lý. Luôn đảm bảo rằng bạn quay lại kiểm tra overfit để xác minh rằng thay đổi của bạn không gây ra bất kỳ hậu quả ngoài ý muốn nào. + +### Yêu cầu giúp đỡ + +Hy vọng rằng bạn sẽ tìm thấy một số lời khuyên trong phần này để giúp bạn giải quyết vấn đề của mình, nhưng nếu không phải vậy, hãy nhớ rằng bạn luôn có thể hỏi cộng đồng trên [diễn đàn](https://discuss.huggingface.co/). + +Dưới đây là một số tài liệu bổ sung có thể hữu ích: + +- ["Reproducibility as a vehicle for engineering best practices"](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p) bởi Joel Grus +- ["Checklist for debugging neural networks"](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) bởi Cecelia Shao +- ["How to unit test machine learning code"](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) bởi Chase Roberts +- ["A Recipe for Training Neural Networks"](http://karpathy.github.io/2019/04/25/recipe/) bởi Andrej Karpathy + +Tất nhiên, không phải mọi vấn đề bạn gặp phải khi huấn luyện mạng thần kinh đều là lỗi của chính bạn! Nếu bạn gặp điều gì đó trong thư viện 🤗 Transformers hoặc 🤗 Datasets có vẻ không ổn, có thể bạn đã gặp lỗi. Bạn chắc chắn nên cho chúng tôi biết tất cả về điều đó và trong phần tiếp theo, chúng tôi sẽ giải thích chính xác cách thực hiện điều đó. diff --git a/chapters/vi/chapter8/4_tf.mdx b/chapters/vi/chapter8/4_tf.mdx new file mode 100644 index 000000000..30b65fea6 --- /dev/null +++ b/chapters/vi/chapter8/4_tf.mdx @@ -0,0 +1,483 @@ + + +# Gỡ lỗi quy trình huấn luyện + + + +Bạn đã viết một kịch bản tuyệt đẹp để huấn luyện hoặc tinh chỉnh một mô hình trong một tác vụ nhất định, tuân thủ một cách nghiêm túc lời khuyên từ [Chương 7](/course/chapter7). Nhưng khi bạn khởi chạy lệnh `model.fit()`, một điều kinh khủng xảy ra: bạn gặp lỗi 😱! Hoặc tệ hơn, mọi thứ dường như ổn và quá trình huấn luyện chạy mà không có lỗi, nhưng mô hình kết quả là tồi tệ. Trong phần này, chúng tôi sẽ chỉ cho bạn những gì bạn có thể làm để gỡ lỗi các loại vấn đề này. + + + +Vấn đề khi bạn gặp lỗi trong `model.fit()` có thể đến từ nhiều nguồn, vì việc huấn luyện thường tập hợp rất nhiều thứ lại với nhau. Vấn đề có thể là một cái gì đó sai trong bộ dữ liệu của bạn hoặc một số vấn đề khi cố gắng kết hợp hàng loạt các phần tử của bộ dữ liệu với nhau. Sau đó, nó lấy một loạt dữ liệu và đưa nó vào mô hình, vì vậy vấn đề có thể nằm ở mã mô hình. Sau đó, nó tính toán các độ dốc và thực hiện bước tối ưu hóa, vì vậy vấn đề cũng có thể nằm trong trình tối ưu hóa của bạn. Và ngay cả khi mọi thứ diễn ra tốt đẹp cho quá trình huấn luyện, vẫn có thể xảy ra sự cố trong quá trình đánh giá nếu có vấn đề với chỉ số của bạn. + +Cách tốt nhất để gỡ lỗi phát sinh trong `model.fit()` là đi qua toàn pipeline này theo cách thủ công để xem mọi thứ diễn ra như thế nào. Sau đó, lỗi thường rất dễ giải quyết. + +Để chứng minh điều này, chúng ta sẽ sử dụng tập lệnh (cố gắng) tinh chỉnh mô hình DistilBERT trên [tập dữ liệu MNLI](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) +``` + +Nếu bạn cố gắng thực thi nó, bạn có thể nhận được một số `VisibleDeprecationWarning` khi thực hiện chuyển đổi tập dữ liệu - đây là một vấn đề UX đã biết mà chúng ta gặp phải, vì vậy vui lòng bỏ qua nó. Nếu bạn đang đọc khóa học sau đó, chẳng hạn như tháng 11 năm 2021 và nó vẫn đang diễn ra, thì hãy gửi những dòng tweet giận dữ tại @carrigmat cho đến khi anh ấy sửa nó. + +Tuy nhiên, một vấn đề nghiêm trọng hơn là chúng ta nhận được một lỗi trọn vẹn. Và nó thực sự rất dài: + +```python out +ValueError: No gradients provided for any variable: ['tf_distil_bert_for_sequence_classification/distilbert/embeddings/word_embeddings/weight:0', '...'] +``` + +Điều đó nghĩa là gì? Chúng ta đã cố gắng huấn luyện trên dữ liệu của mình, nhưng chúng ta không có gradient? Điều này khá bối rối; làm thế nào để chúng ta thậm chí bắt đầu gỡ lỗi một cái gì đó như vậy? Khi lỗi bạn gặp phải không gợi ý ngay vấn đề nằm ở đâu, giải pháp tốt nhất thường là thực hiện mọi thứ theo trình tự, đảm bảo ở mỗi giai đoạn mọi thứ đều ổn. Và tất nhiên, nơi bắt đầu luôn là... + +### Kiểm tra dữ liệu của bạn + +Điều này không cần phải nói, nhưng nếu dữ liệu của bạn bị hỏng, Keras sẽ không thể sửa nó cho bạn. Vì vậy, điều đầu tiên, bạn cần phải xem xét những gì bên trong bộ huấn luyện của bạn. + +Mặc dù rất hấp dẫn khi nhìn vào bên trong `raw_datasets` và `tokenized_datasets`, chúng tôi thực sự khuyên bạn nên truy cập dữ liệu ngay tại điểm mà nó được đưa vào mô hình. Điều đó có nghĩa là đọc kết quả đầu ra từ `tf.data.Dataset` mà bạn đã tạo bằng hàm `to_tf_dataset()`! Vì vậy, làm thế nào để chúng ta làm điều đó? Các đối tượng `tf.data.Dataset` cung cấp cho chúng ta toàn bộ các lô cùng một lúc và không hỗ trợ lập chỉ mục, vì vậy chúng ta không thể chỉ yêu cầu `train_dataset[0]`. Tuy nhiên, chúng ta có thể yêu cầu nó một cách lịch sự cho một lô: + +```py +for batch in train_dataset: + break +``` + +`break` kết thúc vòng lặp sau một lần lặp, vì vậy, điều này lấy lô đầu tiên ra khỏi `train_dataset` và lưu nó dưới dạng `batch`. Bây giờ, chúng ta hãy xem những gì bên trong: + +```python out +{'attention_mask': , + 'label': , + 'input_ids': } +``` + +Điều này có vẻ đúng, phải không? Chúng ta đang chuyển các nhãn `labels`, `attention_mask`, và `input_ids` cho mô hình, đây sẽ là mọi thứ nó cần để tính toán kết quả đầu ra và tính toán mất mát. Vậy tại sao chúng ta không có một gradient? Nhìn kỹ hơn: chúng ta đang chuyển một từ điển duy nhất làm đầu vào, nhưng một lô huấn luyện thường là một tensor đầu vào hoặc từ điển, cộng với một tensor nhãn. Nhãn của chúng ta là một chìa khóa trong từ điển đầu vào của mình. + +Đây có phải là vấn đê? Không phải lúc nào cũng vậy! Nhưng đó là một trong những vấn đề phổ biến nhất mà bạn sẽ gặp phải khi huấn luyện các mô hình Transformer với TensorFlow. Tất cả các mô hình của chúng ta đều có thể tính toán mất mát trong nội bộ, nhưng để làm được điều đó, các nhãn cần được chuyển vào từ điển đầu vào. Đây là mất mát được sử dụng khi chúng ta không chỉ định giá trị tổn thất cho `compile()`. Mặt khác, Keras thường mong đợi các nhãn được chuyển riêng khỏi từ điển đầu vào và các tính toán tổn thất thường sẽ thất bại nếu bạn không làm điều đó. + +Vấn đề giờ đã trở nên rõ ràng hơn: chúng ta đã thông qua tham số `loss`, có nghĩa là chúng ta đang yêu cầu Keras tính toán khoản mất mát của mình, nhưng chúng ta đã chuyển nhãn của mình làm đầu vào cho mô hình, không phải là nhãn ở nơi Keras mong đợi chúng! Chúng ta cần chọn cái này hay cái kia: hoặc chúng ta sử dụng tổn thất bên trong của mô hình và giữ các nhãn ở vị trí của chúng, hoặc chúng ta tiếp tục sử dụng tổn thất Keras, nhưng chúng ta chuyển các nhãn đến nơi mà Keras mong đợi chúng. Để đơn giản, chúng ta hãy thực hiện cách tiếp cận đầu tiên. Thay đổi lệnh gọi thành `compile()` để đọc: + +```py +model.compile(optimizer="adam") +``` + +Bây giờ chúng ta sẽ sử dụng mất mát bên trong của mô hình và vấn đề này sẽ được giải quyết! + + + +✏️ **Đến lượt bạn!** Là một thử thách không bắt buộc sau khi chúng ta đã giải quyết xong các vấn đề khác, bạn có thể thử quay lại bước này và làm cho mô hình hoạt động với mất mát do Keras tính toán ban đầu thay vì mất mát nội bộ. Bạn sẽ cần phải thêm `"labels"` vào `label_cols` của `to_tf_dataset()` để đảm bảo rằng các nhãn được xuất chính xác, điều này sẽ giúp bạn có được độ dốc - nhưng có một vấn đề nữa với sự mất mát mà chúng ta đã chỉ định. Việc huấnl uyện vẫn sẽ diễn ra với vấn đề này, nhưng việc học sẽ rất chậm và sẽ khả năng mất mát huấn luyện cao. Bạn có thể tìm ra nó là gì không? + +Một gợi ý mã hoá ROT13, nếu bạn bế tắc: Vs lbh ybbx ng gur bhgchgf bs FrdhraprPynffvsvpngvba zbqryf va Genafsbezref, gurve svefg bhgchg vf `ybtvgf`. Jung ner ybtvgf? + +Và một gợi ý thứ hai: 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? + + + +Bây giờ, chúng ta hãy thử huấn luyện. Bây giờ chúng ta sẽ nhận được gradient, vì vậy hy vọng (nhạc đáng ngại phát ở đây) chúng ta có thể gọi `model.fit()` và mọi thứ sẽ hoạt động tốt! + +```python out + 246/24543 [..............................] - ETA: 15:52 - loss: nan +``` + +Ôi không. + +`nan` không phải là một giá trị mất mát đáng khích lệ. Tuy nhiên, chúng ta đã kiểm tra dữ liệu của mình và nó trông khá ổn. Nếu đó không phải là vấn đề, chúng ta có thể đi đâu tiếp theo? Bước tiếp theo rõ ràng là ... + +### Kiểm tra mô hình của bạn + +`model.fit()` là một hàm tiện lợi thực sự tuyệt vời trong Keras, nhưng nó làm được rất nhiều thứ cho bạn và điều đó có thể khiến việc tìm chính xác vị trí đã xảy ra sự cố trở nên khó khăn hơn. Nếu bạn đang gỡ lỗi mô hình của mình, một chiến lược thực sự có thể hữu ích là chỉ chuyển một lô duy nhất cho mô hình và xem xét chi tiết kết quả đầu ra của một lô đó. Một mẹo thực sự hữu ích khác nếu mô hình đang gặp lỗi là `compile()` mô hình với `run_eagerly=True`. Điều này sẽ làm cho nó chậm hơn rất nhiều, nhưng nó sẽ làm cho các thông báo lỗi dễ hiểu hơn nhiều, bởi vì chúng sẽ chỉ ra chính xác vị trí xảy ra sự cố trong mã mô hình của bạn. + +Tuy nhiên, hiện tại, chúng ta chưa cần đến `run_eagerly`. Hãy chạy `batch` mà chúng ta đã có trước đó thông qua mô hình và xem kết quả đầu ra trông như thế nào: + +```py +model(batch) +``` + +```python out +TFSequenceClassifierOutput(loss=, logits=, hidden_states=None, attentions=None) +``` + +Chà, điều này thật khó. Mọi thứ đều là `nan`! Nhưng thật lạ phải không? Làm thế nào mà tất cả nhật ký của chúng ta sẽ trở thành `nan`? `nan` có nghĩa là "không phải là số". Giá trị `nan` thường xảy ra khi bạn thực hiện một thao tác bị cấm, chẳng hạn như chia cho số không. Nhưng một điều rất quan trọng cần biết về `nan` trong học máy là giá trị này có xu hướng *lan truyền*. Nếu bạn nhân một số với `nan`, kết quả cũng là `nan`. Và nếu bạn nhận được một `nan` ở bất kỳ đâu trong đầu ra, sự mất mát hoặc độ dốc của bạn, thì nó sẽ nhanh chóng lan rộng ra toàn bộ mô hình của bạn - bởi vì khi giá trị `nan` đó được truyền trở lại qua mạng của bạn, bạn sẽ nhận được `nan` gradient và khi cập nhật trọng số được tính toán với những gradient đó, bạn sẽ nhận được trọng số `nan` và những trọng số đó sẽ tính toán nhiều kết quả đầu ra `nan` hơn nữa! Chẳng bao lâu nữa, toàn bộ mạng lưới sẽ chỉ là một khối lớn gồm các giá trị `nan`. Một khi điều đó xảy ra, thật khó để xem vấn đề bắt đầu từ đâu. Làm thế nào chúng ta có thể cô lập nơi mà `nan` len lỏi đầu tiên? + +Câu trả lời là hãy thử *khởi động lại* mô hình. Khi chúng ta bắt đầu huấn luyện, chúng ta có một `nan` ở đâu đó và nó nhanh chóng được truyền bá qua toàn bộ mô hình. Vì vậy, hãy tải mô hình từ một checkpoint và không thực hiện bất kỳ cập nhật trọng số nào và xem nơi chúng ta nhận được giá trị `nan`: + +```py +model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint) +model(batch) +``` + +When we run that, we get: + +```py out +TFSequenceClassifierOutput(loss=, logits=, hidden_states=None, attentions=None) +``` + +*Bây giờ* chúng ta đang đến một nơi nào đó! Không có giá trị `nan` nào trong nhật ký của mình, điều này khiến bạn yên tâm. Nhưng chúng ta thấy có một vài giá trị `nan` bị mất! Có điều gì đó đặc biệt về các mẫu đó gây ra vấn đề này không? Hãy xem chúng là những cái nào (lưu ý rằng nếu bạn tự chạy mã này, bạn có thể nhận được các chỉ số khác nhau vì tập dữ liệu đã bị xáo trộn): + +```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]) +``` + +Hãy xem các mẫu mà tạo ra chỉ số này: + +```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]]) +``` + +Chà, có rất nhiều thứ ở đây, nhưng không có gì nổi bật là bất thường. Hãy xem các nhãn: + +```python out +labels = batch['labels'].numpy() +labels[indices] +``` + +```python out +array([2, 2, 2, 2, 2, 2, 2, 2, 2]) +``` + +Ah! Các mẫu `nan` đều có cùng một nhãn, và đó là nhãn 2. Đây là một gợi ý rất mạnh mẽ. Thực tế là chúng ta chỉ bị mất `nan` khi nhãn của chúng ta là 2 cho thấy rằng đây là thời điểm rất tốt để kiểm tra số lượng nhãn trong mô hình của mình: + +```python +model.config.num_labels +``` + +```python out +2 +``` + +Bây giờ chúng ta thấy vấn đề: mô hình cho rằng chỉ có hai lớp, nhưng các nhãn tăng lên 2, có nghĩa là thực tế có ba lớp (vì 0 cũng là một lớp). Đây là cách chúng ta có một `nan` - bằng cách cố gắng tính toán mất mát cho một lớp không tồn tại! Hãy thử thay đổi điều đó và lắp lại mô hình: + +``` +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 +``` + +Chúng ta đang huấn luyện! Không còn `nan` nữa, và mất mát của chúng ta đang giảm dần ... đại loại vậy. Nếu bạn quan sát nó một lúc, bạn có thể bắt đầu hơi mất kiên nhẫn, bởi vì giá trị tổn thất vẫn ở mức cao. Chúng ta hãy dừng huấn luyện ở đây và thử nghĩ xem điều gì có thể gây ra vấn đề này. Tại thời điểm này, chúng ta khá chắc chắn rằng cả dữ liệu và mô hình đều ổn, nhưng mô hình của chúng ta không hoạt động tốt. Còn lại gì nữa? Đến lúc để... + +### Kiểm tra siêu tham số của bạn + +Nếu bạn nhìn lại đoạn mã ở trên, bạn có thể không nhìn thấy bất kỳ siêu tham số nào, có lẽ ngoại trừ `batch_size`, và điều đó dường như không phải là thủ phạm. Tuy nhiên, đừng để bị lừa; luôn có siêu tham số và nếu bạn không thể nhìn thấy chúng, điều đó có nghĩa là bạn không biết chúng được đặt thành gì. Đặc biệt, hãy nhớ một điều quan trọng về Keras: nếu bạn đặt hàm mất mát, trình tối ưu hóa hoặc kích hoạt bằng một chuỗi, _tất cả các đối số của nó sẽ được đặt thành giá trị mặc định của chúng_. Điều này có nghĩa là mặc dù việc sử dụng chuỗi ký tự rất tiện lợi, nhưng bạn nên hết sức cẩn thận khi làm như vậy, vì nó có thể dễ dàng che giấu những thứ quan trọng với bạn. (Bất kỳ ai đang thử thách thức tùy chọn ở trên nên lưu ý cẩn thận về thực tế này.) + +Trong trường hợp này, chúng ta đã đặt tham số bằng chuỗi ở đâu? Ban đầu, chúng ta đã đặt giá trị mất mát bằng một chuỗi, nhưng chúng ta không làm điều đó nữa. Tuy nhiên, chúng ta đang thiết lập trình tối ưu hóa bằng một chuỗi. Điều đó có thể đang che giấu bất cứ điều gì với mình? Hãy xem [các tham số của nó](https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/Adam). + +Có gì nổi bật ở đây không? Đúng vậy - tốc độ học! Khi chúng ta chỉ sử dụng chuỗi `'adam'`, chúng ta sẽ nhận được tốc độ học mặc định, là 0.001 hoặc 1e-3. Đây là mức quá cao đối với một mô hình Transformer! Nói chung, chúng tôi khuyên bạn nên thử tốc độ học từ 1e-5 đến 1e-4 cho các mô hình của bạn; đó là một nơi nào đó nhỏ hơn từ 10X đến 100X so với giá trị mà ta thực sự đang sử dụng ở đây. Điều đó nghe có vẻ như nó có thể là một vấn đề lớn, vì vậy hãy thử giảm bớt nó. Để làm điều đó, chúng ta cần nhập vào đối tượng `optimizer`. Trong khi chúng ta đang ở đó, hãy bắt đầu lại mô hình từ checkpoint, trong trường hợp huấn luyện với tốc độ học cao làm hỏng trọng số của nó: + +```python +from tensorflow.keras.optimizers import Adam + +model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint) +model.compile(optimizer=Adam(5e-5)) +``` + + + +💡 Bạn cũng có thể nhập hàm `create_optimizer()` từ 🤗 Transformers, hàm này sẽ cho bạn một trình tối ưu AdamW với với độ phân rã trọng số chính xác cũng như khởi động và phân rã tốc độ học. Trình này thường sẽ tạo ra kết quả tốt hơn một chút so với kết quả bạn nhận được với trình tối ưu hóa Adam mặc định. + + + +Bây giờ, chúng tôi có thể thử điều chỉnh mô hình với tốc độ học mới, được cải thiện: + +```python +model.fit(train_dataset) +``` + +```python out +319/24543 [..............................] - ETA: 16:07 - loss: 0.9718 +``` + +Bây giờ mất mát của chúng ta thực sự đi đâu đó! Việc huấn luyện cuối cùng có vẻ như nó đã hoạt động. Có một bài học ở đây: khi mô hình của bạn đang chạy nhưng mức hao hụt không giảm và bạn chắc chắn rằng dữ liệu của mình vẫn ổn, bạn nên kiểm tra các siêu tham số như tốc độ học và giảm trọng lượng. Đặt một trong hai giá trị đó quá cao rất có thể khiến quá trình huấn luyện bị "đình trệ" với giá trị tổn thất cao. + +## Các vấn đề tiềm ẩn khác + +Chúng ta đã đề cập đến các vấn đề trong tập lệnh ở trên, nhưng có một số lỗi phổ biến khác mà bạn có thể gặp phải. Chúng ta hãy nhìn vào một danh sách (không đầy đủ cho lắm). + +### Xử lý lỗi hết bộ nhớ + +Dấu hiệu cho biết sắp hết bộ nhớ là một lỗi như "OOM when allocating tensor" - OOM là viết tắt của "hết bộ nhớ." Đây là một nguy cơ rất phổ biến khi xử lý các mô hình ngôn ngữ lớn. Nếu bạn gặp phải điều này, một chiến lược tốt là giảm một nửa kích thước lô của bạn và thử lại. Tuy nhiên, hãy nhớ rằng một số mô hình có kích thước *rất* lớn. Ví dụ: GPT-2 kích thước đầy đủ có thông số 1.5B, có nghĩa là bạn sẽ cần 6GB bộ nhớ chỉ để lưu mô hình và 6GB khác cho độ dốc của nó! Huấn luyện mô hình GPT-2 đầy đủ thường sẽ yêu cầu hơn 20GB VRAM bất kể bạn sử dụng kích thước lô nào, điều mà chỉ một số GPU có. Các mô hình nhẹ hơn như `distilbert-base-cased` dễ chạy hơn nhiều và huấn luyện cũng nhanh hơn nhiều. + + + +Trong phần tiếp theo của khóa học, chúng ta sẽ xem xét các kỹ thuật nâng cao hơn có thể giúp bạn giảm dung lượng bộ nhớ và cho phép bạn tinh chỉnh các mô hình lớn nhất. + + + +### TensorFlow đói rồi đói rồi🦛 + +Một điểm đặc biệt của TensorFlow mà bạn nên biết là nó phân bổ *tất cả* bộ nhớ GPU của bạn cho chính nó ngay khi bạn tải một mô hình hoặc thực hiện bất kỳ huấn luyện nào và sau đó nó sẽ phân chia bộ nhớ đó theo yêu cầu. Điều này khác với hành vi của các khung khác, như PyTorch, phân bổ bộ nhớ theo yêu cầu với CUDA thay vì thực hiện nó trong nội bộ. Một ưu điểm của phương pháp TensorFlow là nó thường có thể đưa ra các lỗi hữu ích khi bạn hết bộ nhớ và nó có thể phục hồi từ trạng thái đó mà không làm hỏng toàn bộ nhân CUDA. Nhưng cũng có một nhược điểm quan trọng: nếu bạn chạy hai tiến trình TensorFlow cùng một lúc, thì **bạn sẽ có một khoảng thời gian tồi tệ**. + +Nếu bạn đang chạy trên Colab, bạn không cần phải lo lắng về điều này, nhưng nếu bạn đang chạy cục bộ thì đây chắc chắn là điều bạn nên cẩn thận. Đặc biệt, hãy lưu ý rằng việc đóng một tab sổ ghi chép không nhất thiết phải đóng notebook đó lại! Bạn có thể cần chọn notebok đang chạy (notebook có biểu tượng màu xanh lá cây) và tắt chúng theo cách thủ công trong danh sách thư mục. Bất kỳ notebook đang chạy nào đang sử dụng TensorFlow vẫn có thể đang giữ một loạt bộ nhớ GPU của bạn và điều đó có nghĩa là bất kỳ notebook mới nào bạn bắt đầu đều có thể gặp phải một số vấn đề rất kỳ quặc. + +Nếu bạn bắt đầu gặp lỗi về CUDA, BLAS hoặc cuBLAS trong mã hoạt động trước đó, đây rất thường là thủ phạm. Bạn có thể sử dụng một lệnh như `nvidia-smi` để kiểm tra - khi bạn tắt hoặc khởi động lại notebook hiện tại của mình, bộ nhớ của bạn còn trống hay vẫn còn sử dụng được? Nếu nó vẫn còn được sử dụng, một cái gì đó khác đang giữ nó! + +### Kiểm tra lại dữ liệu của bạn (một lần nữa!) + +Mô hình của bạn sẽ chỉ học được điều gì đó nếu nó thực sự có thể học được bất cứ điều gì từ dữ liệu của bạn. Nếu có lỗi làm hỏng dữ liệu hoặc các nhãn được gán ngẫu nhiên, rất có thể bạn sẽ không huấn luyện được mô hình nào về tập dữ liệu của mình. Một công cụ hữu ích ở đây là `tokenizer.decode()`. Thao tác này sẽ biến `input_ids` trở lại thành chuỗi, vì vậy bạn có thể xem dữ liệu và xem liệu dữ liệu huấn luyện của bạn có đang dạy những gì bạn muốn nó dạy hay không. Ví dụ: sau khi bạn nhận được một `batch` từ `tf.data.Dataset` như chúng ta đã làm ở trên, bạn có thể giải mã phần tử đầu tiên như sau: + +```py +input_ids = batch["input_ids"].numpy() +tokenizer.decode(input_ids[0]) +``` + +Then you can compare it with the first label, like so: + +```py +labels = batch["labels"].numpy() +label = labels[0] +``` + +Khi bạn có thể xem dữ liệu của mình như thế này, bạn có thể tự hỏi bản thân những câu hỏi sau: + +- Dữ liệu được giải mã có dễ hiểu không? +- Bạn có đồng ý với các nhãn? +- Có một nhãn nào phổ biến hơn những nhãn khác không? +- Mất mát/Chỉ số sẽ là bao nhiêu nếu mô hình dự đoán một câu trả lời ngẫu nhiên/luôn là một câu trả lời giống nhau? + +Sau khi xem xét dữ liệu của bạn, hãy xem qua một số dự đoán của mô hình - nếu mô hình của bạn xuất ra các token, hãy thử giải mã chúng! Nếu mô hình luôn dự đoán cùng một điều thì đó có thể là do tập dữ liệu của bạn thiên về một loại (đối với các vấn đề phân loại), vì vậy các kỹ thuật như lấy mẫu quá mức các lớp hiếm có thể hữu ích. Ngoài ra, điều này cũng có thể được gây ra bởi các vấn đề huấn luyện như cài đặt siêu tham số tệ. + +Nếu phần mất mát/ các chỉ số bạn nhận được trên mô hình ban đầu của mình trước khi huấn luyện rất khác với cái bạn mong đợi cho các dự đoán ngẫu nhiên, hãy kiểm tra kỹ cách tính toán mất mát hoặc chỉ số của bạn, vì có thể có một lỗi ở đó. Nếu bạn đang sử dụng một số khoảng mất mát mà bạn thêm vào cuối, hãy đảm bảo rằng chúng có cùng quy mô. + +Khi bạn chắc chắn dữ liệu của mình là hoàn hảo, bạn có thể xem liệu mô hình có khả năng huấn luyện về nó hay không bằng một bài kiểm tra đơn giản. + +### Học kĩ mô hình của bạn trong một lô + +Việc học quá nhiều thường là điều chúng ta cố gắng tránh khi huấn luyện, vì nó có nghĩa là mô hình không học cách nhận ra các đặc điểm chung ta muốn mà thay vào đó chỉ là ghi nhớ các mẫu huấn luyện. Tuy nhiên, cố gắng huấn luyện mô hình của bạn lặp đi lặp lại là một bài kiểm tra tốt để kiểm tra xem vấn đề như bạn đã định hình có thể được giải quyết bằng mô hình mà bạn đang cố gắng huấn luyện hay không. Nó cũng sẽ giúp bạn xem liệu tốc độ học ban đầu của bạn có quá cao hay không. + +Thực hiện điều này khi bạn đã xác định được `model` của mình thực sự dễ dàng; chỉ cần lấy một loạt dữ liệu huấn luyện, sau đó coi `batch` đó là toàn bộ tập dữ liệu của bạn, đưa nó vào mô hình với một lượng epoch lớn: + +```py +for batch in train_dataset: + break + +# Đảm bảo rằng bạn đã chạy model.compile() và đặt trình tối ưu hóa của mình, +# và mất mát/chỉ số của bạn nếu bạn đang sử dụng chúng + +model.fit(batch, epochs=20) +``` + + + +💡 Nếu dữ liệu huấn luyện của bạn không cân bằng, hãy đảm bảo tạo một loạt dữ liệu huấn luyện có chứa tất cả các nhãn. + + + +Mô hình phải có kết quả gần như hoàn hảo trên `batch`, với mức mất mát giảm nhanh về 0 (hoặc giá trị tối thiểu cho khoản mất mát bạn đang sử dụng). + +Nếu bạn không quản lý để mô hình của mình có được kết quả hoàn hảo như thế này, điều đó có nghĩa là có điều gì đó không ổn trong cách bạn định khung vấn đề hoặc dữ liệu của mình, vì vậy bạn nên khắc phục điều đó. Chỉ khi bạn vượt qua được bài kiểm tra overfit, bạn mới có thể chắc chắn rằng mô hình của mình thực sự có thể học được điều gì đó. + + + +⚠️ Bạn sẽ phải tạo lại mô hình của mình và biên dịch lại sau bài kiểm tra overfitt này, vì mô hình thu được có thể sẽ không thể khôi phục và học được điều gì đó hữu ích trên tập dữ liệu đầy đủ của bạn. + + + +### Không điều chỉnh bất cứ thứ gì cho đến khi bạn có mô hình cơ sở đầu tiên + +Điều chỉnh siêu tham số luôn được nhấn mạnh là phần khó nhất của học máy, nhưng nó chỉ là bước cuối cùng giúp bạn hiểu được một chút về chỉ số này. *Các giá trị rất không tốt* cho các siêu tham số của bạn, chẳng hạn như sử dụng tốc độ học Adam mặc định là 1e-3 với mô hình Transformer, tất nhiên sẽ khiến việc học tiến hành rất chậm hoặc hoàn toàn bị đình trệ, nhưng hầu hết thời gian là các siêu tham số "hợp lý", như tốc độ học từ 1e-5 đến 5e-5, sẽ hoạt động tốt để mang lại cho bạn kết quả tốt. Vì vậy đừng khởi chạy tìm kiếm siêu tham số tốn thời gian và tốn kém cho đến khi bạn có thứ gì đó vượt qua mô hình cơ sở mà bạn có trên tập dữ liệu của mình. + +Khi bạn đã có một mô hình đủ tốt, bạn có thể bắt đầu điều chỉnh một chút. Đừng thử khởi chạy một nghìn lần chạy với các siêu tham số khác nhau, nhưng hãy so sánh một vài lần chạy với các giá trị khác nhau cho một siêu thông số để có được ý tưởng về giá trị nào có tác động lớn nhất. + +Nếu bạn đang điều chỉnh chính mô hình, hãy giữ nó đơn giản và đừng thử bất cứ điều gì mà bạn không thể biện minh một cách hợp lý. Luôn đảm bảo rằng bạn quay lại kiểm tra overfit để xác minh rằng thay đổi của bạn không gây ra bất kỳ hậu quả ngoài ý muốn nào. + +### Yêu cầu giúp đỡ + +Hy vọng rằng bạn sẽ tìm thấy một số lời khuyên trong phần này để giúp bạn giải quyết vấn đề của mình, nhưng nếu không phải vậy, hãy nhớ rằng bạn luôn có thể hỏi cộng đồng trên [diễn đàn](https://discuss.huggingface.co/). + +Dưới đây là một số tài liệu bổ sung có thể hữu ích: + +- ["Reproducibility as a vehicle for engineering best practices"](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p) bởi Joel Grus +- ["Checklist for debugging neural networks"](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) bởi Cecelia Shao +- ["How to unit test machine learning code"](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) bởi Chase Roberts +- ["A Recipe for Training Neural Networks"](http://karpathy.github.io/2019/04/25/recipe/) bởi Andrej Karpathy + +Tất nhiên, không phải mọi vấn đề bạn gặp phải khi huấn luyện mạng thần kinh đều là lỗi của chính bạn! Nếu bạn gặp điều gì đó trong thư viện 🤗 Transformers hoặc 🤗 Datasets có vẻ không ổn, có thể bạn đã gặp lỗi. Bạn chắc chắn nên cho chúng tôi biết tất cả về điều đó và trong phần tiếp theo, chúng tôi sẽ giải thích chính xác cách thực hiện điều đó. diff --git a/chapters/vi/chapter8/5.mdx b/chapters/vi/chapter8/5.mdx new file mode 100644 index 000000000..9fe9c66dd --- /dev/null +++ b/chapters/vi/chapter8/5.mdx @@ -0,0 +1,90 @@ +# Làm thế nào để viết một vấn đề hay + + + +Khi bạn gặp điều gì đó có vẻ không ổn với một trong các thư viện Hugging Face, bạn chắc chắn nên cho chúng tôi biết để chúng tôi có thể sửa chữa nó (điều này cũng xảy ra với bất kỳ thư viện mã nguồn mở nào, đối với vấn đề đó). Nếu bạn không hoàn toàn chắc chắn liệu lỗi nằm trong mã của riêng bạn hay một trong các thư viện của chúng tôi, nơi đầu tiên cần kiểm tra là [diễn đàn](https://discuss.huggingface.co/). Cộng đồng sẽ giúp bạn tìm ra điều này và nhóm Hugging Face cũng theo dõi chặt chẽ các cuộc thảo luận tại đó. + + + +Khi bạn chắc chắn rằng bạn có một lỗi trong tay, bước đầu tiên là xây dựng một ví dụ có thể tái tạo tối thiểu. + +## Tạo một ví dụ có thể tái tạo tối thiểu + +Điều rất quan trọng là phải cô lập đoạn mã tạo ra lỗi, vì không có ai trong nhóm Hugging Face là ảo thuật gia và họ không thể sửa những gì họ không thể nhìn thấy. Một ví dụ có thể tái tạo tối thiểu, như tên đã chỉ ra, phải có thể tái tạo được. Điều này có nghĩa là nó không nên dựa vào bất kỳ tệp hoặc dữ liệu bên ngoài nào mà bạn có thể có. Cố gắng thay thế dữ liệu bạn đang sử dụng bằng một số giá trị giả trông giống như dữ liệu thật của bạn mà vẫn tạo ra lỗi tương tự. + + + +🚨 Nhiều vấn đề trong kho lưu trữ 🤗 Transformers chưa được giải quyết vì không thể truy cập được dữ liệu được sử dụng để tái tạo chúng. + + + +Một khi bạn có một cái gì đó độc lập, bạn có thể cố gắng giảm nó thành những dòng mã ít hơn, xây dựng cái mà chúng ta gọi là _ví dụ tối giản có thể tái tạo được_. Mặc dù điều này đòi hỏi bạn phải làm việc nhiều hơn một chút, nhưng bạn gần như sẽ được đảm bảo nhận được trợ giúp và bản sửa lỗi nếu bạn cung cấp một trình tạo lỗi ngắn gọn, đẹp mắt. + +Nếu bạn cảm thấy đủ thoải mái, hãy kiểm tra mã nguồn nơi lỗi của bạn xảy ra. Bạn có thể tìm thấy giải pháp cho vấn đề của mình (trong trường hợp đó, bạn thậm chí có thể đề xuất một pull request để khắc phục nó), nhưng nhìn chung, điều này có thể giúp người bảo trì hiểu rõ hơn về nguồn khi họ đọc báo cáo của bạn. + +## Điền vào mẫu vấn đề + +Khi bạn gửi vấn đề của mình, bạn sẽ thấy có một mẫu để điền vào. Chúng tôi sẽ theo dõi thông tin về [🤗 Sự cố về Transformers](https://github.com/huggingface/transformers/issues/new/choose) tại đây, nhưng loại thông tin tương tự sẽ được yêu cầu nếu bạn báo cáo sự cố trong kho lưu trữ khác. Đừng để trống mẫu: dành thời gian điền vào mẫu sẽ tối đa hóa cơ hội nhận được câu trả lời và giải quyết vấn đề của bạn. + +Nói chung, khi trình bày một vấn đề, hãy luôn giữ thái độ lịch sự. Đây là một dự án mã nguồn mở, vì vậy bạn đang sử dụng phần mềm miễn phí và không ai có nghĩa vụ phải giúp bạn. Bạn có thể bao gồm những gì bạn cảm thấy là những lời chỉ trích chính đáng trong vấn đề của bạn, nhưng sau đó những người bảo trì rất có thể coi thường nó và không vội vàng giúp bạn. Đảm bảo rằng bạn đã đọc [quy tắc ứng xử](https://github.com/huggingface/transformers/blob/master/CODE_OF_CONDUCT.md) của dự án. + +###Bao gồm thông tin môi trường của bạn + +🤗 Transformers cung cấp một tiện ích để lấy tất cả thông tin chúng tôi cần về môi trường của bạn. Chỉ cần nhập thông tin sau vào thiết bị đầu cuối như terminal của bạn: + +``` +transformers-cli env +``` + +và bạn sẽ nhận được một cái gì đó như thế này: + +```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?: +``` + +Bạn cũng có thể thêm dấu `!` vào đầu lệnh `transformers-cli env` để thực thi nó từ một ô notebook, sau đó sao chép và dán kết quả vào đầu vấn đề của bạn. + +### Gắn thẻ mọi người + +Việc gắn thẻ mọi người bằng cách nhập `@` , theo sau là GitHub sẽ gửi cho họ thông báo để họ thấy vấn đề của bạn và có thể trả lời nhanh hơn. Sử dụng điều này một cách có kiểm duyệt, vì những người bạn gắn thẻ có thể không đánh giá cao việc được thông báo nếu đó là thứ mà họ không có liên kết trực tiếp. Nếu bạn đã xem các tệp nguồn liên quan đến lỗi của mình, bạn nên gắn thẻ người cuối cùng đã thực hiện thay đổi vào dòng mà bạn nghĩ là người chịu trách nhiệm cho vấn đề của bạn (bạn có thể tìm thông tin này bằng cách xem dòng đã nói trên GitHub, chọn nó, sau đó nhấp vào "View git blame" hay "Xem git đổ lỗi"). + +Nếu không, mẫu cung cấp đề xuất về những người cần gắn thẻ. Nói chung, đừng bao giờ gắn thẻ nhiều hơn ba người! + +### Bao gồm một ví dụ có thể tái tạo + +Nếu bạn đã cố gắng tạo một ví dụ độc lập tạo ra lỗi, bây giờ là lúc để đưa nó vào! Nhập một dòng có ba dấu gạch ngược theo sau là `python`, như thế này: + +```python +``` + +sau đó dán vào ví dụ có thể tái tạo tối thiểu của bạn và nhập một dòng mới với ba dấu gạch ngược. Điều này sẽ đảm bảo mã của bạn được định dạng đúng. + +Nếu bạn không quản lý để tạo một ví dụ có thể tái tạo, hãy giải thích theo các bước rõ ràng về cách bạn giải quyết vấn đề của mình. Bao gồm một liên kết đến một notebook Google Colab mà bạn gặp lỗi nếu có thể. Bạn càng chia sẻ nhiều thông tin, người bảo trì càng có thể trả lời bạn tốt hơn. + +Trong mọi trường hợp, bạn nên sao chép và dán toàn bộ thông báo lỗi mà bạn đang nhận được. Nếu bạn đang làm việc trong Colab, hãy nhớ rằng một số khung có thể tự động được thu gọn trong dấu vết ngăn xếp, vì vậy hãy đảm bảo bạn mở rộng chúng trước khi sao chép. Giống như với mẫu mã, hãy đặt thông báo lỗi đó giữa hai dòng với ba dấu gạch ngược để nó được định dạng đúng. + +### Mô tả hành vi kì vọng + +Giải thích bằng một vài dòng những gì bạn mong đợi sẽ nhận được để những người bảo trì nắm bắt được đầy đủ vấn đề. Phần này nhìn chung khá rõ ràng, vì vậy nó nên nằm gọn trong một câu, nhưng trong một số trường hợp, bạn có thể có nhiều điều để nói. + +## Và rồi chuyện gì xảy ra? + +Sau khi vấn đề của bạn được gửi, hãy đảm bảo nhanh chóng kiểm tra mọi thứ có ổn không. Bạn có thể chỉnh sửa vấn đề nếu bạn mắc lỗi hoặc thậm chí thay đổi tiêu đề của vấn đề nếu bạn nhận ra vấn đề khác với những gì bạn nghĩ ban đầu. + +Sẽ không có ích gì khi bạn không nhận được câu trả lời. Nếu không ai giúp bạn trong một vài ngày, có khả năng không ai có thể hiểu được vấn đề của bạn. Đừng ngần ngại quay lại ví dụ có thể tái tạo. Bạn có thể làm cho nó ngắn hơn và nhiều hơn vào điểm không? Nếu bạn không nhận được câu trả lời trong một tuần, bạn có thể nhẹ nhàng để lại tin nhắn yêu cầu trợ giúp, đặc biệt nếu bạn đã chỉnh sửa vấn đề của mình để bao gồm thêm thông tin về vấn đề. diff --git a/chapters/vi/chapter8/6.mdx b/chapters/vi/chapter8/6.mdx new file mode 100644 index 000000000..8934d34d0 --- /dev/null +++ b/chapters/vi/chapter8/6.mdx @@ -0,0 +1,7 @@ +# Phần 2 đã hoàn thành! + +Xin chúc mừng, bạn đã vượt qua phần thứ hai của khóa học! Chúng tôi đang tích cực làm việc trên bản thứ ba, vì vậy hãy đăng ký [bản tin](https://huggingface.curated.co/) của chúng tôi để đảm bảo bạn không bỏ lỡ bản phát hành của nó. + +Bây giờ bạn có thể giải quyết một loạt các tác vụ NLP và tinh chỉnh hoặc huấn luyện trước một mô hình trên chúng. Đừng quên chia sẻ kết quả của bạn với cộng đồng trên [Model Hub](https://huggingface.co/models). + +Chúng tôi rất nóng lòng được xem bạn sẽ xây dựng được những gì với kiến thức mà bạn đã thu được! diff --git a/chapters/vi/chapter8/7.mdx b/chapters/vi/chapter8/7.mdx new file mode 100644 index 000000000..875be2d56 --- /dev/null +++ b/chapters/vi/chapter8/7.mdx @@ -0,0 +1,226 @@ + + +# Đố vui cuối chương + +Cũng kiểm tra xem bạn đã học được gì từ chương này! + +### 1. Bạn nên đọc truy vết của Python theo thứ tự nào? + + + +### 2. Ví dụ có thể tái tạo tối thiểu là gì? + + + +### 3. Giả sử bạn cố gắng chạy đoạn mã sau, mà mã này xảy ra lỗi như dưới đây: + +```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) +``` + +Điều nào dưới đây có thể là một lựa chọn tốt cho tiêu đề của một chủ đề diễn đàn để yêu cầu trợ giúp? + +ImportError: cannot import name 'GPT3ForSequenceClassification' from 'transformers' (/Users/lewtun/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/__init__.py)", + explain: + "Việc bao gồm dòng cuối cùng của truy vết có thể mang tính mô tả, nhưng điều này tốt hơn nên dành cho phần chính của chủ đề. Hãy thử lại!", + }, + { + text: "Vấn đề với from transformers import GPT3ForSequenceClassification", + explain: + "Hãy thử lại - mặc dù điều này cung cấp thông tin hữu ích, nhưng nó có lẽ tốt nhất nên được dành cho phần chính của văn bả.", + }, + { + text: "Tại sao tôi không thể nhập GPT3ForSequenceClassification?", + explain: + "Lựa chọn tốt! Tiêu đề này ngắn gọn và cung cấp cho người đọc manh mối về những gì có thể sai (tức là GPT-3 không được hỗ trợ trong 🤗 Transformers).", + correct: true, + }, + { + text: "Liệu GPT-3 có được hỗ trợ trong 🤗 Transformers?", + explain: + "Một câu hỏi hay! Sử dụng câu hỏi làm tiêu đề chủ đề là một cách tuyệt vời để truyền đạt vấn đề với cộng đồng.", + correct: true, + }, + ]} +/> + +### 4.Giả sử bạn đang cố gắng chạy `trainer.train()` và gặp phải lỗi khó hiểu không cho bạn biết chính xác lỗi đến từ đâu. Đâu sẽ là nơi đầu tiên bạn tìm lỗi trong pipeline huấn luyện của mình? + + + +### 5. Đâu là cách tốt nhất để gỡ lỗi CUDA? + + + +### 6. Đâu là cách tốt nhất để khắc phục lỗi trên Github? + + + +### 7. Tại sao học quá kĩ (overfit) vào một lô thường là cách gỡ lỗi tốt nhất? + + + +### 8. Tại sao bao gồm chi tiết về môi trường tính toán với `transformers-cli env` khi tạo ra một issue (vấn đề) trên kho 🤗 Transformers là một ý hay? + + diff --git a/chapters/vi/chapter9/1.mdx b/chapters/vi/chapter9/1.mdx new file mode 100644 index 000000000..cf73b07a4 --- /dev/null +++ b/chapters/vi/chapter9/1.mdx @@ -0,0 +1,39 @@ +# Giới thiệu + +Trong chương này, chúng ta sẽ tìm hiểu về cách tạo **demo tương tác** cho các mô hình học máy của bạn. + +Tại sao ta nên xây dựng bản demo hoặc GUI cho mô hình học máy của bạn? Bản demo cho phép: + +- **Các nhà phát triển học máy** dễ dàng trình bày công việc của họ cho nhiều đối tượng bao gồm cả các nhóm không chuyên về kỹ thuật hoặc khách hàng +- **Các nhà nghiên cứu** dễ dàng tái tạo các mô hình và hành vi học máy hơn +- **Người kiểm tra chất lượng** hoặc **người dùng cuối** dễ dàng xác định và gỡ lỗi các điểm hỏng hóc của mô hình +- **Người dùng đa dạng** khám phá các sai lệch của ​​thuật toán trong các mô hình + +Chúng ta sẽ sử dụng thư viện Gradio để xây dựng các bản demo cho các mô hình của mình. Gradio cho phép bạn xây dựng, tùy chỉnh và chia sẻ các bản demo trên web cho bất kỳ mô hình học máy nào, hoàn toàn bằng Python. + +Dưới đây là một số ví dụ về demo học máy được xây dựng với Gradio: + +* Một mô hình **nhận dạng phác thảo** nhận bản phác thảo và xuất ra các nhãn của những gì nó cho là đang được vẽ: + + + +* Mô hình **hỏi đáp** khai thác lấy trong một đoạn ngữ cảnh và một câu hỏi và đưa ra một câu trả lời và điểm xác suất (chúng ta đã thảo luận về loại mô hình này [trong Chương 7](/course/chapter7/7)): + + + +* Một mô hình **xóa nền** nhận vào một hình ảnh và xuất ra hình ảnh với nền đã bị xóa: + + + +Chương này được chia thành các phần bao gồm cả _khái niệm_ và _ứng dụng_. Sau khi bạn tìm hiểu khái niệm trong mỗi phần, bạn sẽ áp dụng nó để xây dựng một loại bản demo cụ thể, từ phân loại hình ảnh đến nhận dạng giọng nói. Vào thời điểm bạn hoàn thành chương này, bạn sẽ có thể xây dựng các bản demo này (và nhiều hơn nữa!) Chỉ trong một vài dòng mã Python. + + +👀 Hãy ngó thử Hugging Face Spaces để xem nhiều ví dụ gần đây về các bản demo học máy do cộng đồng học máy xây dựng! + + + +## Bữa tiệc Gradio + +Nếu bạn muốn vận dụng tốt kiến thức từ chương này, hãy tham gia bữa tiệc Gradio! Đây là sự kiện cộng đồng do Hugging Face tổ chức vào ngày **16-31 tháng 5**. Trong sự kiện này, bạn sẽ xây dựng các bản demo học máy thú vị với Gradio và đang chạy để giành chiến thắng và giải thưởng Hugging Face swag! + +Xem [mô tả sự kiện](https://github.com/AK391/community-events/blob/main/gradio-blocks/README.md) để biết chi tiết về cách tham gia - chúng tôi rất nóng lòng được biết các bản demo bạn sẽ xây dựng 🤗! diff --git a/chapters/vi/chapter9/2.mdx b/chapters/vi/chapter9/2.mdx new file mode 100644 index 000000000..d71a45cd7 --- /dev/null +++ b/chapters/vi/chapter9/2.mdx @@ -0,0 +1,109 @@ +# Xây dựng bản demo đầu tiên của bạn + + + +Hãy bắt đầu bằng cách cài đặt Gradio! Vì nó là một gói Python, chỉ cần chạy: + +`$ pip install gradio ` + +Bạn có thể chạy Gradio ở bất cứ đâu, từ IDE Python yêu thích của bạn, đến notebook Jupyter hoặc thậm chí trong Google Colab 🤯! +Vì vậy, hãy cài đặt Gradio ở bất cứ đâu bạn chạy Python! + +Hãy bắt đầu với một ví dụ “Hello World” đơn giản để làm quen với cú pháp Gradio: + +```py +import gradio as gr + + +def greet(name): + return "Hello " + name + + +demo = gr.Interface(fn=greet, inputs="text", outputs="text") + +demo.launch() +``` + +Hãy xem qua đoạn mã trên: + +- Đầu tiên, chúng ta định nghĩa một hàm có tên là `welcome()`. Trong trường hợp này, nó là một hàm đơn giản có thêm "Hello" trước tên của bạn, nhưng nó có thể là *bất kỳ* hàm Python nào nói chung. Ví dụ: trong các ứng dụng học máy, hàm này sẽ *gọi một mô hình để đưa ra dự đoán* trên một đầu vào và trả lại đầu ra. +- Sau đó, chúng ta tạo một Giao diện Gradio với ba tham số `fn`, `inputs`, và `outputs`. Các tham số này xác định hàm dự đoán, cũng như _kiểu_ của các thành phần đầu vào và đầu ra mà ta muốn. Trong trường hợp của mình, cả hai thành phần đều là các hộp văn bản đơn giản. +- Sau đó, chúng ta gọi phương thức `launch()` trên `Interface` đã tạo. + +Nếu bạn chạy đoạn mã này, giao diện bên dưới sẽ tự động xuất hiện trong notebook Jupyter/Colab hoặc bật trong trình duyệt trên **[http://localhost:7860](http://localhost:7860/)** nếu đang chạy từ một tập lệnh. + + + +Hãy thử sử dụng GUI này ngay bây giờ với tên của chính bạn hoặc một số đầu vào khác! + +Bạn sẽ nhận thấy rằng trong GUI này, Gradio tự động suy ra tên của tham số đầu vào (`name`) và lấy nó làm nhãn trên đầu hộp văn bản. Điều gì xảy ra nếu bạn muốn thay đổi điều đó? Hoặc nếu bạn muốn tùy chỉnh hộp văn bản theo một số cách khác? Trong trường hợp đó, bạn có thể khởi tạo một đối tượng lớp đại diện cho thành phần đầu vào. + +```py +import gradio as gr + + +def greet(name): + return "Hello " + name + + +# Chúng tôi khởi tạo lớp Textbox +textbox = gr.Textbox(label="Type your name here:", placeholder="John Doe", lines=2) + +gr.Interface(fn=greet, inputs=textbox, outputs="text").launch() +``` + + + +Ở đây, chúng ta đã tạo một hộp văn bản đầu vào với nhãn, trình giữ chỗ và một số dòng. Bạn có thể làm tương tự đối với hộp văn bản đầu ra, nhưng chúng ta sẽ để lại điều đó ngay bây giờ. + +Chúng ta thấy rằng chỉ với một vài dòng mã, Gradio cho phép bạn tạo một giao diện đơn giản xung quanh bất kỳ chức năng nào +với bất kỳ loại đầu vào hoặc đầu ra nào. Trong phần này, chúng ta đã bắt đầu với hộp văn bản đơn giản, nhưng trong các phần tiếp theo, chúng ta sẽ đề cập đến các loại đầu vào và đầu ra khác. Bây giờ chúng ta hãy xem bao gồm một số NLP trong một ứng dụng Gradio thì sao. + +## 🤖 Bao gồm các dự đoán mô hình + +Bây giờ chúng ta hãy xây dựng một giao diện đơn giản cho phép bạn demo mô hình **tạo văn bản** như GPT-2. + +Chúng ta sẽ tải mô hình của mình bằng cách sử dụng hàm `pipeline()` từ 🤗 Transformers. +Nếu bạn cần cập nhật nhanh, bạn có thể quay lại [phần đó trong Chương 1](/course/chapter1/3#text-generation) + +Đầu tiên, chúng ta định nghĩa một hàm dự đoán nhận lời nhắc văn bản và trả về văn bản đã hoàn thiện: + +```py +from transformers import pipeline + +model = pipeline("text-generation") + + +def predict(prompt): + completion = model(prompt)[0]["generated_text"] + return completion +``` + +Hàm này hoàn thành các lời nhắc mà bạn cung cấp và bạn có thể chạy nó với lời nhắc đầu vào của riêng mình để xem nó hoạt động như thế nào. Đây là một ví dụ (bạn có thể nhận được một kết quả khác): + +``` +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. +``` + +Bây giờ chúng ta có một hàm để tạo các dự đoán, chúng ta có thể tạo và khởi chạy một `Interface` theo cách giống như cách chúng ta đã làm trước đó: + +```py +import gradio as gr + +gr.Interface(fn=predict, inputs="text", outputs="text").launch() +``` + +Nó đó! Bây giờ bạn có thể sử dụng giao diện này để tạo văn bản bằng mô hình GPT-2 như hình bên dưới 🤯. + + + +Hãy tiếp tục đọc để biết cách tạo các loại demo khác với Gradio! diff --git a/chapters/vi/chapter9/3.mdx b/chapters/vi/chapter9/3.mdx new file mode 100644 index 000000000..40ff8bc04 --- /dev/null +++ b/chapters/vi/chapter9/3.mdx @@ -0,0 +1,164 @@ +# Hiểu lớp Interface + + + +Trong phần này, chúng ta sẽ xem xét kỹ hơn về lớp `Interface` và hiểu các tham số chính được sử dụng để tạo ra nó. + +## Cách tạo một Interface + +Bạn sẽ nhận thấy rằng lớp `Interface` có 3 tham số bắt buộc: + +`Interface(fn, inputs, outputs, ...)` + +Các tham số này là: + + - `fn`: hàm dự đoán được bao bọc bởi giao diện Gradio. Hàm này có thể nhận một hoặc nhiều tham số và trả về một hoặc nhiều giá trị + - `inputs`: (các) loại thành phần đầu vào. Gradio cung cấp nhiều thành phần được tạo sẵn như`"image"` hay `"mic"`. + - `outputs`: (các) loại thành phần đầu ra. Một lần nữa, Gradio cung cấp nhiều thành phần được tạo sẵn, ví dụ: `"image"` hay `"label"`. + +Để có danh sách đầy đủ các thành phần, [xem tài liệu Gradio](https://gradio.app/docs). Mỗi thành phần được tạo sẵn có thể được tùy chỉnh bằng cách khởi tạo lớp tương ứng với thành phần. + +Ví dụ: như chúng ta đã thấy trong [phần trước](/course/chapter9/2), thay vì truyền tham số `input` vào trong `"textbox"`, bạn có thể truyền vào `Textbox(lines=7, label="Prompt")` để tạo một hộp văn bản có 7 dòng và một nhãn. + +Hãy xem một ví dụ khác, lần này với thành phần `Audio`. + +## Một ví dụ đơn giản với âm thanh + +Như đã đề cập trước đó, Gradio cung cấp nhiều đầu vào và đầu ra khác nhau. +Vì vậy, hãy xây dựng một `Interface` hoạt động với âm thanh. + +Trong ví dụ này, chúng tôi sẽ xây dựng một hàm chuyển đổi âm thanh sang âm thanh mà nhận tập tin âm thanh và chỉ cần đảo ngược nó. + +Chúng ta sẽ sử dụng thành phần `Audio` cho đầu vào. Khi sử dụng thành phần `Audio`, bạn có thể chỉ định xem bạn có muốn `source` của âm thanh là một tệp mà người dùng +tải lên hoặc micrô mà người dùng ghi lại giọng nói của họ. Trong trường hợp này, hãy đặt nó thành `"microphone"`. Chỉ cho vui thôi, chúng ta sẽ thêm một nhãn vào phần `Audio` của mình có nội dung "Speak here...", nghĩa là "Nói ở đây ...". + +Ngoài ra, chúng ta muốn nhận âm thanh dưới dạng mảng numpy để ta có thể dễ dàng "đảo ngược" nó lại. Vì vậy, chúng ta sẽ đặt `"type"` là `"numpy"`, chuyển đầu vào +dữ liệu dưới dạng một bộ (`sample_rate`, `data`) trong hàm của chúng ta. + +Chúng ta cũng sẽ sử dụng thành phần đầu ra `Audio` có thể tự động hiển thị một bộ tuple với tốc độ mẫu và mảng dữ liệu phức tạp dưới dạng tệp âm thanh có thể phát. Trong trường hợp này, chúng ta không cần thực hiện bất kỳ tùy chỉnh nào, vì vậy cta sẽ sử dụng chuỗi phím tắt `"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() +``` + +Đoạn mã trên sẽ tạo ra một giao diện giống như bên dưới (nếu trình duyệt của bạn không yêu cầu bạn cấp quyền đối với micrô, mở bản demo sang một tab khác.) + + + +Bây giờ bạn có thể ghi lại giọng nói của mình và nghe thấy chính mình đang nói ngược lại - thật ma quái 👻! + +## Xử lý nhiều đầu vào và đầu ra + +Giả sử chúng ta có một hàm phức tạp hơn, với nhiều đầu vào và đầu ra. Trong ví dụ dưới đây, chúng ta có một hàm lấy chỉ mục thả xuống, giá trị thanh trượt và số, và trả về một mẫu âm thanh của một giai điệu âm nhạc. + +Hãy xem cách chúng ta chuyển danh sách các thành phần đầu vào và đầu ra, và xem liệu bạn có thể theo dõi những gì đang xảy ra không. + +Chìa khóa ở đây là khi bạn truyền vào: +* danh sách các thành phần đầu vào, mỗi thành phần tương ứng với một tham số theo thứ tự. +* danh sách các thành phần đầu ra, mỗi thành phần tương ứng với một giá trị trả về. + +Đoạn mã bên dưới cho thấy cách ba thành phần đầu vào xếp hàng với ba tham số của hàm `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() +``` + + + +### Phương thức `launch()` + +Cho đến nay, chúng tôi đã sử dụng phương thức `launch()` để khởi chạy giao diện, nhưng chúng ta chưa thực sự thảo luận về những gì nó làm. + +Theo mặc định, phương thức `launch()` sẽ khởi chạy bản demo trong một máy chủ web đang chạy cục bộ. Nếu bạn đang chạy mã của mình trong notebook Jupyter hoặc Colab, thì Gradio sẽ nhúng GUI demo vào notebook để bạn có thể dễ dàng sử dụng. + +Bạn có thể tùy chỉnh hành vi của `launch()` thông qua các tham số khác nhau: + + - `inline` - có hiển thị giao diện nội tuyến trên notebook Python hay không. + - `inbrowser` - có tự động khởi chạy giao diện trong tab mới trên trình duyệt mặc định hay không. + - `share` - có tạo một liên kết có thể chia sẻ công khai từ máy tính của bạn cho giao diện hay không. Giống như một liên kết Google Drive! + +Chúng tôi sẽ trình bày chi tiết hơn về tham số `share` trong phần tiếp theo! + +## ✏️ Hãy áp dụng nó! + +Hãy xây dựng một giao diện cho phép bạn giới thiệu mô hình **nhận dạng giọng nói**. Để làm cho nó thú vị, chúng ta sẽ chấp nhận hoặc đầu vào micrô hoặc một tệp đã tải lên. + +Như thường lệ, chúng ta sẽ tải mô hình nhận dạng giọng nói của mình bằng cách sử dụng hàm `pipeline()` từ 🤗 Transformers. +Nếu bạn cần cập nhật nhanh, bạn có thể quay lại [phần đó trong Chương 1](/course/chapter1/3). Tiếp theo, chúng ta sẽ triển khai một hàm `transcribe_audio()` để xử lý âm thanh và trả về phiên âm. Cuối cùng, chúng ta sẽ gói hàm này trong một `Interface` với các thành phần `Audio` cho đầu vào và chỉ văn bản cho đầu ra. Nhìn chung, mã cho ứng dụng này như sau: + +```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() +``` + +Nếu trình duyệt của bạn không yêu cầu bạn cấp quyền đối với micrô, hãy mở bản demo trong một tab riêng. + + + +Nó đó! Bây giờ bạn có thể sử dụng giao diện này để phiên âm âm thanh. Chú ý ở đây rằng bằng cách đặt tham số `option` là `True`, chúng ta cho phép người dùng cung cấp micrô hoặc tệp âm thanh (hoặc không, nhưng điều đó sẽ trả lại thông báo lỗi). + +Tiếp tục xem cách chia sẻ giao diện của bạn với những người khác! diff --git a/chapters/vi/chapter9/4.mdx b/chapters/vi/chapter9/4.mdx new file mode 100644 index 000000000..6de217886 --- /dev/null +++ b/chapters/vi/chapter9/4.mdx @@ -0,0 +1,142 @@ +# Chia sẻ các bản demo với người khác + + + +Bây giờ bạn đã xây dựng một bản demo, có thể bạn sẽ muốn chia sẻ nó với những người khác. Các bản demo Gradio có thể được chia sẻ theo hai cách: sử dụng một ***đường dẫn chia sẻ tạm thời*** hoặc ***lưu trữ vĩnh viễn trên Spaces***. + +Chúng tôi sẽ đề cập đến cả hai cách tiếp cận này ngay sau đây. Nhưng trước khi chia sẻ bản demo của mình, bạn có thể muốn đánh bóng nó 💅. + +### Đánh bóng Gradio demo của bạn: + +
+Overview of a gradio interface + +
+ +Để thêm nội dung bổ sung vào bản demo của bạn, lớp `Interface` hỗ trợ một số tham số tùy chọn: + - `title`: bạn có thể đặt tiêu đề cho bản demo của mình, xuất hiện _phía trên_ các thành phần đầu vào và đầu ra. + - `description`: bạn có thể đưa ra mô tả (bằng văn bản, Markdown hoặc HTML) cho giao diện, xuất hiện phía trên các thành phần đầu vào và đầu ra và bên dưới tiêu đề. + - `article`: bạn cũng có thể viết một bài báo mở rộng (bằng văn bản, Markdown hoặc HTML) giải thích về giao diện. Nếu được cung cấp, nó sẽ xuất hiện _bên dưới_ các thành phần đầu vào và đầu ra. + - `theme`: bạn không thích màu mặc định? Đặt chủ đề để sử dụng một trong các `default`, `huggingface`, `grass`, `peach`. Bạn cũng có thể thêm tiền tố `dark-`, ví dụ: `dark-peach` cho chủ đề tối (hoặc chỉ `dark` cho chủ đề tối mặc định). + - `examples`: để làm cho bản demo của bạn *dễ sử dụng hơn*, bạn có thể cung cấp một số đầu vào ví dụ cho hàm. Chúng xuất hiện bên dưới các thành phần giao diện người dùng và có thể được sử dụng để điền vào giao diện. Chúng phải được cung cấp dưới dạng danh sách lồng nhau, trong đó danh sách bên ngoài bao gồm các mẫu và mỗi danh sách bên trong bao gồm một đầu vào tương ứng với mỗi thành phần đầu vào. + - `live`: nếu bạn muốn làm cho bản demo của mình "sống động", nghĩa là mô hình của bạn chạy lại mỗi khi đầu vào thay đổi, bạn có thể đặt `live=True`. Điều này hợp lý khi sử dụng với các mô hình nhanh (chúng ta sẽ xem một ví dụ ở cuối phần này) +Sử dụng các tùy chọn ở trên, chúng ta kết thúc với một giao diện hoàn chỉnh hơn. Chạy đoạn mã dưới đây để bạn có thể trò chuyện với Rick và Morty: + +```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() +``` + +Sử dụng các tùy chọn ở trên, chúng ta kết thúc với một giao diện hoàn chỉnh hơn. Hãy thử giao diện bên dưới: + + + +### Chia sẻ bản demo của bạn với các liên kết tạm thời + +Bây giờ chúng ta đã có bản demo hoạt động của mô hình học máy của mình, hãy tìm hiểu cách dễ dàng chia sẻ liên kết đến giao diện của chúng ta. +Các giao diện có thể dễ dàng được chia sẻ công khai bằng cách đặt `share=True` trong phương thức `launch()`: + +```python +gr.Interface(classify_image, "image", "label").launch(share=True) +``` + +Điều này tạo ra một liên kết công khai, có thể chia sẻ mà bạn có thể gửi cho bất kỳ ai! Khi bạn gửi liên kết này, người dùng ở phía bên kia có thể dùng thử mô hình trong trình duyệt của họ trong tối đa 72 giờ. Vì quá trình xử lý diễn ra trên thiết bị của bạn (miễn là thiết bị của bạn vẫn bật!), Bạn không phải lo lắng về việc đóng gói bất kỳ thư viện phụ thuộc nào. Nếu bạn đang làm việc với notebook Google Colab, liên kết chia sẻ luôn được tạo tự động. Nó thường trông giống như sau: **XXXXX.gradio.app**. Mặc dù liên kết được cung cấp thông qua liên kết Gradio, ta chỉ là một proxy cho máy chủ cục bộ của mình và không lưu trữ bất kỳ dữ liệu nào được gửi qua các giao diện. + +Tuy nhiên, hãy nhớ rằng các liên kết này có thể truy cập công khai, có nghĩa là bất kỳ ai cũng có thể sử dụng mô hình của bạn để dự đoán! Do đó, hãy đảm bảo không để lộ bất kỳ thông tin nhạy cảm nào thông qua các hàm bạn viết hoặc cho phép bất kỳ thay đổi quan trọng nào xảy ra trên thiết bị của bạn. Nếu bạn đặt `share=False` (mặc định), chỉ một liên kết cục bộ được tạo. + +### Lưu trữ bản demo của bạn trên Hugging Face Spaces + +Một đường dẫn liên kết mà bạn có thể chia sẻ cho các đồng nghiệp thật tuyệt, nhưng làm thế nào bạn có thể lưu trữ vĩnh viễn bản demo của mình và để bản demo tồn tại trong "không gian" riêng của nó trên internet? + +Hugging Face Space cung cấp cơ sở hạ tầng để lưu trữ vĩnh viễn mô hình Gradio của bạn trên internet, **miễn phí**! Spaces cho phép bạn tạo và đẩy lên kho (công khai hoặc riêng tư), nơi giao diện Gradio của bạn sẽ tồn tại trong tệp `app.py`. [Đọc hướng dẫn từng bước](https://huggingface.co/blog/gradio-spaces) để bắt đầu hoặc xem video ví dụ bên dưới. + + + +## ✏️ Cùng áp dụng nhé! + +Sử dụng những gì chúng ta vừa học được trong các phần cho đến nay, hãy tạo bản demo nhận dạng phác thảo mà ta đã thấy trong [phần một của chương này](/course/chapter9/1). Hãy thêm một số tùy chỉnh vào giao diện và đặt `share=True` để tạo một liên kết công khai mà ta có thể chia sẻ. + +Chúng ra có thể tải các nhãn từ [class_names.txt](https://huggingface.co/spaces/dawood/Sketch-Recognition/blob/main/class_names.txt) và tải mô hình pytorch được huấn luyện trước từ [pytorch_model.bin] (https://huggingface.co/spaces/dawood/Sketch-Recognition/blob/main/pytorch_model.bin). Tải xuống các tệp này bằng cách nhấp vào liên kết và nhấp vào tải xuống ở góc trên cùng bên trái của bản xem trước tệp. Hãy xem đoạn mã bên dưới để biết cách chúng ta sử dụng các tệp này để tải mô hình của mình và tạo hàm `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)} +``` + +Giờ ta đã có hàm `predict()`. Bước tiếp theo là định nghỉa và khởi chạy giao diện 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) +``` + + + +Lưu ý tham số `live=True` trong `Interface`, có nghĩa là bản demo phác thảo tạo ra dự đoán mỗi khi ai đó vẽ trên sketchpad (không có nút gửi!). + +Hơn nữa, chúng ta cũng đặt tham số `share=True` trong phương thức `launch()`. +Điều này sẽ tạo ra một liên kết công khai mà bạn có thể +gửi cho bất cứ ai! Khi bạn gửi liên kết này, người dùng ở phía bên kia có thể thử mô hình nhận dạng phác thảo. Để nhắc lại, bạn cũng có thể tổ chức mô hình trên Hugging Face Spaces, đó là cách chúng ta có thể nhúng bản demo ở trên. + +Tiếp theo, chúng tôi sẽ đề cập đến các cách khác mà Gradio có thể được sử dụng với hệ sinh thái Hugging Face! diff --git a/chapters/vi/chapter9/5.mdx b/chapters/vi/chapter9/5.mdx new file mode 100644 index 000000000..16f10c8da --- /dev/null +++ b/chapters/vi/chapter9/5.mdx @@ -0,0 +1,68 @@ +# Tích hợp với Hugging Face Hub + + + +Để làm cho cuộc sống của bạn trở nên dễ dàng hơn, Gradio tích hợp trực tiếp với Hugging Face Hub và Hugging Face Spaces. Bạn có thể tải các bản demo từ Hub và Spaces chỉ với *một dòng mã*. + +### Tải mô hình từ Hugging Face Hub + +Để bắt đầu, hãy chọn một trong số hàng nghìn mô hình Hugging Face được cung cấp thông qua Hub, như được mô tả trong [Chương 4](/course/chapter4/2). + +Sử dụng phương thức `Interface.load()` đặc biệt, bạn truyền `"model/"` (hoặc, tương đương, `"huggingface/"`) theo sau là tên mô hình. Ví dụ: đây là mã để tạo bản demo cho [GPT-J](https://huggingface.co/EleutherAI/gpt-j-6B), một mô hình ngôn ngữ lớn, hãy thêm một số đầu vào mẫu: + +```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

" +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() +``` + +Đoạn mã trên sẽ tạo ra giao diện bên dưới: + + + +Tải mô hình theo cách này sử dụng [API luận suy](https://huggingface.co/inference-api) của Hugging Face, thay vì tải mô hình trong bộ nhớ. Điều này lý tưởng cho các mô hình lớn như GPT-J hoặc T0pp, những mô hình yêu cầu nhiều RAM. + +### Tải từ Hugging Face Spaces + +Để tải bất kỳ Space nào từ Hugging Face Hub và tạo lại nó cục bộ, bạn có thể truyền `spaces/` vào `Interface`, theo sau là tên của Space. + +Bạn có nhớ bản demo từ phần 1 xóa nền của hình ảnh không? Hãy tải nó từ Hugging Face Spaces: + +```py +gr.Interface.load("spaces/abidlabs/remove-bg").launch() +``` + + + +Một trong những điều thú vị khi tải các bản demo từ Hub hoặc Spaces là bạn tùy chỉnh chúng bằng cách ghi đè bất kỳ thông số nào. Ở đây, chúng ta thêm tiêu đề và làm cho tiêu đề đó hoạt động với webcam: + +```py +gr.Interface.load( + "spaces/abidlabs/remove-bg", inputs="webcam", title="Remove your webcam background!" +).launch() +``` + + + +Bây giờ chúng ta đã khám phá một số cách để tích hợp Gradio với Hugging Face Hub, hãy cùng xem xét một số tính năng nâng cao của lớp `Interface`. Đó là chủ đề của phần tiếp theo! diff --git a/chapters/vi/chapter9/6.mdx b/chapters/vi/chapter9/6.mdx new file mode 100644 index 000000000..6ea57588d --- /dev/null +++ b/chapters/vi/chapter9/6.mdx @@ -0,0 +1,99 @@ +# Các tính năng nâng cao của Interface + + + +Bây giờ chúng ta có thể xây dựng và chia sẻ giao diện cơ bản, hãy cùng khám phá một số tính năng nâng cao hơn như trạng thái và diễn giải. + +### Sử dụng trạng thái để duy trì dữ liệu + +Gradio hỗ trợ *trạng thái phiên*, nơi dữ liệu tồn tại qua nhiều lần gửi trong một tải trang. Trạng thái phiên hữu ích khi xây dựng các bản demo, chẳng hạn như chatbot ở nơi bạn muốn giữ nguyên dữ liệu khi người dùng tương tác với mô hình. Lưu ý rằng trạng thái phiên không chia sẻ dữ liệu giữa những người dùng khác nhau trong mô hình của bạn. + +Để lưu trữ dữ liệu ở trạng thái phiên, bạn cần thực hiện ba việc: + +1. Truyền một *tham số bổ sung* vào hàm của bạn, nó thể hiện trạng thái của giao diện. +1. Khi kết thúc hàm, trả về giá trị đã cập nhật của trạng thái dưới dạng *giá trị trả về bổ sung*. +1. Thêm thành phần đầu vào 'state' và đầu ra 'state' khi tạo `Interface` của bạn. + +Xem ví dụ về chatbot bên dưới: + +```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() +``` + + + +Lưu ý trạng thái của thành phần đầu ra vẫn tồn tại qua các lần gửi. +Lưu ý: bạn có thể chuyển giá trị mặc định vào tham số trạng thái, được sử dụng làm giá trị ban đầu của trạng thái. + +### Sử dụng diễn giải để hiểu các dự đoán + +Hầu hết các mô hình học máy là hộp đen và logic bên trong của hàm bị ẩn khỏi người dùng cuối. Để khuyến khích tính minh bạch, chúng tôi đã giúp bạn dễ dàng thêm thông dịch vào mô hình của mình bằng cách chỉ cần đặt từ khóa thông dịch trong lớp Interface thành mặc định. Điều này cho phép người dùng của bạn hiểu những phần nào của đầu vào chịu trách nhiệm cho đầu ra. Hãy xem giao diện đơn giản bên dưới hiển thị bộ phân loại hình ảnh đồng thời bao gồm phần diễn giải: + +```py +import requests +import tensorflow as tf + +import gradio as gr + +inception_net = tf.keras.applications.MobileNetV2() # tải mô hình + +# Tải nhãn con người đọc được cho 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() +``` + +Kiểm tra hàm thông dịch bằng cách gửi đầu vào, sau đó nhấp vào Interpret tương ứng diễn giải bên dưới thành phần đầu ra. + + + +Bên cạnh phương pháp diễn giải mặc định mà Gradio cung cấp, bạn cũng có thể chỉ định `shap` cho tham số `interpretation` và đặt tham số `num_shap`. Điều này sử dụng diễn giải dựa trên Shapley, bạn có thể đọc thêm về [tại đây](https://christophm.github.io/interpretable-ml-book/shap.html). +Cuối cùng, bạn cũng có thể chuyển hàm thông dịch của riêng mình vào tham số `interpretation`. Xem ví dụ trong trang bắt đầu của Gradio [tại đây](https://gradio.app/getting_started/). + +Điều này kết thúc việc đi sâu vào lớp `Interface` của Gradio. Như chúng ta đã thấy, lớp này giúp việc tạo trình diễn học máy trở nên đơn giản trong một vài dòng mã Python. Tuy nhiên, đôi khi bạn sẽ muốn tùy chỉnh bản demo của mình bằng cách thay đổi bố cục hoặc xâu chuỗi nhiều hàm dự đoán lại với nhau. Sẽ thật tuyệt nếu bằng cách nào đó chúng ta có thể chia `Interface` thành các "khối" có thể tùy chỉnh? May mắn thay, có! Đó là chủ đề của phần cuối cùng. diff --git a/chapters/vi/chapter9/7.mdx b/chapters/vi/chapter9/7.mdx new file mode 100644 index 000000000..57f675a72 --- /dev/null +++ b/chapters/vi/chapter9/7.mdx @@ -0,0 +1,236 @@ +# Giới thiệu về Gradio Blocks + + + +Trong các phần trước, chúng ta đã tìm hiểu và tạo các bản demo bằng cách sử dụng lớp `Interface`. Trong phần này, chúng tôi sẽ giới thiệu API cấp thấp **mới được phát triển** của mình có tên là `gradio.Blocks`. + +Bây giờ, sự khác biệt giữa `Interface` và `Blocks`là gì? + +- ⚡ `Interface`: một API cấp cao cho phép bạn tạo một bản demo học máy đầy đủ chỉ đơn giản bằng cách cung cấp danh sách các đầu vào và đầu ra. + +- 🧱 `Blocks`: một API cấp thấp cho phép bạn có toàn quyền kiểm soát các luồng dữ liệu và bố cục của ứng dụng của mình. Bạn có thể xây dựng các ứng dụng nhiều bước, rất phức tạp bằng cách sử dụng `Blocks` (như trong "các khối xây dựng"). + +### Tại sao lại là Blocks 🧱? + +Như chúng ta đã thấy trong các phần trước, lớp `Interface` cho phép bạn dễ dàng tạo các bản demo học máy chính thức chỉ với một vài dòng mã. API `Interface` cực kỳ dễ sử dụng nhưng thiếu tính linh hoạt mà API `Blocks` cung cấp. Ví dụ: bạn có thể muốn: + +- Nhóm các bản demo có liên quan lại với nhau dưới dạng nhiều tab trong một ứng dụng web +- Thay đổi bố cục của bản demo của bạn, ví dụ: để chỉ định vị trí của các đầu vào và đầu ra +- Có giao diện nhiều bước, trong đó đầu ra của một mô hình trở thành đầu vào cho mô hình tiếp theo hoặc có các luồng dữ liệu linh hoạt hơn nói chung +- Thay đổi thuộc tính của một thành phần (ví dụ: các lựa chọn trong danh sách thả xuống) hoặc khả năng hiển thị của nó dựa trên đầu vào của người dùng + +Chúng ta sẽ khám phá tất cả các khái niệm này trong các phần tiếp theo. + +### Tạo một bản demo đơn giản bằng cách sử dụng Blocks + +Sau khi bạn đã cài đặt Gradio, hãy chạy mã bên dưới dưới dạng tập lệnh Python, notebook Jupyter hoặc sổ ghi chép 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() +``` + + + +Ví dụ đơn giản ở trên giới thiệu 4 khái niệm làm nền tảng cho Blocks: + +1. Blocks cho phép bạn xây dựng các ứng dụng web kết hợp markdown, HTML, các nút và các thành phần tương tác đơn giản bằng cách khởi tạo các đối tượng bằng Python bên trong ngữ cảnh `with gradio.Blocks`. + + +🙋Nếu bạn không quen với câu lệnh `with` trong Python, chúng tôi khuyên bạn nên xem [hướng dẫn](https://realpython.com/python-with-statement/) tuyệt vời từ Real Python. Quay lại đây sau khi đọc xong 🤗 + + +Thứ tự mà bạn khởi tạo các thành phần quan trọng khi mỗi phần tử được hiển thị vào ứng dụng web theo thứ tự nó được tạo. (Các bố cục phức tạp hơn được thảo luận bên dưới) + +2. Bạn có thể xác định các hàm Python thông thường ở bất kỳ đâu trong mã của mình và chạy chúng với đầu vào của người dùng bằng cách sử dụng `Blocks`. Trong ví dụ của mình, chúng ta có một hàm đơn giản "lật" văn bản đầu vào, nhưng bạn có thể viết bất kỳ hàm Python nào, từ một phép tính đơn giản đến xử lý các dự đoán từ một mô hình học máy. + +3. Bạn có thể gán các sự kiện cho bất kỳ thành phần `Blocks` nào. Điều này sẽ chạy hàm của bạn khi thành phần được nhấp, thay đổi, v.v. Khi bạn gán một sự kiện, bạn truyền vào ba tham số: `fn`: hàm cần được gọi,`inputs`: (danh sách) thành phần đầu vào (s), và `outputs`: (danh sách) các thành phần đầu ra cần được gọi. + + Trong ví dụ trên, chúng tôi chạy hàm `flip_text()` khi giá trị trong `Textbox` có tên đầu vào `input` thay đổi. Sự kiện đọc giá trị trong `input`, truyền nó làm tham số tên cho `flip_text()`, sau đó trả về một giá trị được gán cho `Textbox` thứ hai của chúng ta có tên là `output`. + + Để xem danh sách các sự kiện mà mỗi thành phần hỗ trợ, hãy xem [tài liệu](https://www.gradio.app/docs/) Gradio. + +4. Các khối tự động tìm ra liệu một thành phần có nên tương tác (chấp nhận đầu vào của người dùng) hay không, dựa trên các trình kích hoạt sự kiện mà bạn xác định. Trong ví dụ của chúng ta, hộp văn bản đầu tiên là tương tác, vì giá trị của nó được sử dụng bởi hàm `flip_text()`. Hộp văn bản thứ hai không tương tác, vì giá trị của nó không bao giờ được sử dụng làm đầu vào. Trong một số trường hợp, bạn có thể muốn ghi đè điều này, bạn có thể thực hiện bằng cách truyền một boolean đến tham số `interactive` của thành phần (ví dụ:`gr.Textbox(placeholder="Flip this text", interactive=True)`). + +### Tùy chỉnh bố cục của bản demo của bạn + +Làm cách nào chúng ta có thể sử dụng `Blocks` để tùy chỉnh bố cục bản demo của mình? Theo mặc định, `Blocks` hiển thị các thành phần mà bạn tạo theo chiều dọc trong một cột. Bạn có thể thay đổi điều đó bằng cách tạo các cột bổ sung `with gradio.Column():` hoặc các hàng `with gradio.Row():` và tạo các thành phần trong các ngữ cảnh đó. + +Đây là những gì bạn nên ghi nhớ: bất kỳ thành phần nào được tạo trong một `Column` (đây cũng là mặc định) sẽ được bố trí theo chiều dọc. Bất kỳ thành phần nào được tạo trong một `Row` sẽ được bố trí theo chiều ngang, tương tự như [mô hình flexbox trong phát triển web](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concept_of_Flexbox). + +Cuối cùng, bạn cũng có thể tạo các tab cho bản demo của mình bằng cách sử dụng trình quản lý ngữ cảnh `with gradio.Tabs()`. Trong ngữ cảnh này, bạn có thể tạo nhiều tab bằng cách chỉ định `with gradio.TabItem(name_of_tab):` children. Bất kỳ thành phần nào được tạo bên trong ngữ cảnh `with gradio.TabItem(name_of_tab):` sẽ xuất hiện trong tab đó. + +Bây giờ, hãy thêm một hàm `flip_image()` vào bản demo của chúng ta và thêm một tab mới để lật hình ảnh. Dưới đây là một ví dụ với 2 tab và cũng sử dụng 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() +``` + + + +Bạn sẽ nhận thấy rằng trong ví dụ này, chúng ta cũng đã tạo ra một thành phần `Button` trong mỗi tab và chỉ định một sự kiện nhấp chuột cho mỗi nút, đó là những gì thực sự chạy hàm. + +### Khám phá các sự kiện và trạng thái + +Cũng giống như bạn có thể kiểm soát bố cục, `Blocks` cung cấp cho bạn khả năng kiểm soát chi tiết đối với những sự kiện nào kích hoạt hàm gọi. Mỗi thành phần và nhiều bố cục có các sự kiện cụ thể mà chúng hỗ trợ. + +Ví dụ: thành phần `Textbox` có 2 sự kiện: `change()` (khi giá trị bên trong hộp văn bản thay đổi) và `submit()` (khi người dùng nhấn phím enter trong khi tập trung vào hộp văn bản). Các thành phần phức tạp hơn có thể có nhiều sự kiện hơn nữa: ví dụ: thành phần `Audio` cũng có các sự kiện riêng biệt khi tệp âm thanh được phát, xóa, tạm dừng, v.v. Xem tài liệu về các sự kiện mà mỗi thành phần hỗ trợ. + +Bạn có thể đính kèm trình kích hoạt sự kiện cho không, một hoặc nhiều sự kiện này. Bạn tạo một trình kích hoạt sự kiện bằng cách gọi tên của sự kiện trên cá thể thành phần dưới dạng một hàm - ví dụ: `textbox.change(...)` hoặc `btn.click(...)`. Hàm nhận ba tham số, như đã thảo luận ở trên: + +- `fn`: hàm để chạy +- `input`: một (danh sách) (các) thành phần có giá trị sẽ được cung cấp làm tham số đầu vào cho hàm. Giá trị của mỗi thành phần được ánh xạ tới tham số hàm tương ứng, theo thứ tự. Tham số này có thể là None nếu hàm không nhận bất kỳ tham số nào. +- `outputs`: một (danh sách) (các) thành phần có giá trị cần được cập nhật dựa trên các giá trị được trả về bởi hàm. Mỗi giá trị trả về đặt giá trị của thành phần tương ứng, theo thứ tự. Tham số này có thể là None nếu hàm không trả về bất kỳ thứ gì. + +Bạn thậm chí có thể đặt thành phần đầu vào và đầu ra là thành phần giống nhau, như chúng ta làm trong ví dụ này sử dụng mô hình GPT để hoàn thành văn bản: + +```py +import gradio as gr + +api = gr.Interface.load("huggingface/EleutherAI/gpt-j-6B") + + +def complete_with_gpt(text): + # Sử dụng 50 kí tự cuối của văn bản làm ngữ cảnh + 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() +``` + + + +### Tạo bản demo đa bước + +Trong một số trường hợp, bạn có thể muốn có _bản demo đa bước_, trong đó bạn sử dụng lại đầu ra của một hàm làm đầu vào cho hàm tiếp theo. Điều này thực sự dễ thực hiện với `Blocks`, vì bạn có thể sử dụng một thành phần cho đầu vào của một trình kích hoạt sự kiện nhưng lại là đầu ra của một thành phần khác. Hãy xem thành phần văn bản trong ví dụ bên dưới, giá trị của nó là kết quả đầu ra của mô hình nhận dạng giọng nói, nhưng cũng được truyền vào mô hình phân tích tình cảm: + +```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() +``` + + + +### Cập nhật Thuộc tính Thành phần + +Cho đến nay, chúng ta đã thấy cách tạo các sự kiện để cập nhật giá trị của một thành phần khác. Nhưng điều gì sẽ xảy ra nếu bạn muốn thay đổi các thuộc tính khác của một thành phần, như khả năng hiển thị của hộp văn bản hoặc các lựa chọn trong nhóm nút radio? Bạn có thể thực hiện việc này bằng cách trả về phương thức `update()` của lớp thành phần thay vì giá trị trả về thông thường từ hàm của bạn. + +Điều này được minh họa dễ dàng nhất bằng một ví dụ: + +```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() +``` + + + +Chúng ta vừa khám phá tất cả các khái niệm cốt lõi của `Blocks`! Cũng giống như với `Interfaces`, bạn có thể tạo các bản demo thú vị có thể được chia sẻ bằng cách sử dụng `share=True` trong phương thức `launch()` hoặc triển khai trên [Hugging Face Spaces](https://huggingface.co/spaces). diff --git a/chapters/vi/chapter9/8.mdx b/chapters/vi/chapter9/8.mdx new file mode 100644 index 000000000..4c4ea510e --- /dev/null +++ b/chapters/vi/chapter9/8.mdx @@ -0,0 +1,19 @@ +# Gradio, kiểm tra nào! + +Điều này kết thúc chương về xây dựng các bản demo ML thú vị với Gradio - chúng tôi hy vọng bạn thích nó! Tóm lại, trong chương này, chúng ta đã học: + +- Cách tạo bản demo Gradio với API `Interface` cấp cao và cách định cấu hình các phương thức đầu vào và đầu ra khác nhau. +- Các cách khác nhau để chia sẻ bản demo Gradio, thông qua các liên kết tạm thời và lưu trữ trên [Hugging Face Spaces](https://huggingface.co/spaces). +- Cách tích hợp bản demo Gradio với mô hình và Hugging Face Spaces. +- Các tính năng nâng cao như lưu trữ trạng thái trong bản demo hoặc cung cấp xác thực. +- Làm thế nào để có toàn quyền kiểm soát luồng dữ liệu và bố cục của bản demo của bạn với Gradio Blocks. + +Nếu bạn muốn kiểm tra sự hiểu biết của mình về các khái niệm được đề cập trong chương này, hãy xem bài kiểm tra trong phần tiếp theo! + +## Tiếp theo là đâu? + +Nếu bạn muốn tìm hiểu thêm về Gradio, bạn có thể + +- Hãy xem [Demo](https://github.com/gradio-app/gradio/tree/main/demo) trong kho, có khá nhiều ví dụ ở đó. +- Xem trang [Hướng dẫn](https://gradio.app/guides/), nơi bạn có thể tìm thấy hướng dẫn về các tính năng thú vị và nâng cao. +- Xem trang [Tài liệu](https://gradio.app/docs/) để biết thêm chi tiết. diff --git a/chapters/vi/chapter9/9.mdx b/chapters/vi/chapter9/9.mdx new file mode 100644 index 000000000..135bae491 --- /dev/null +++ b/chapters/vi/chapter9/9.mdx @@ -0,0 +1,234 @@ + + +# Đố vui cuối chương + +Hãy kiểm tra những gì bạn đã học được trong chương này! + +### 1. Bạn có thể sử dụng Gradio để làm gì? + +share=True trong phương thức khởi chạy, bạn có thể tạo liên kết chia sẻ để gửi cho bất kỳ ai.", + correct: true + }, + { + text: "Gỡ lỗi mô hình của bạn", + explain: "Một lợi thế của bản demo gradio là có thể kiểm tra mô hình của bạn với dữ liệu thực mà bạn có thể thay đổi và quan sát sự thay đổi dự đoán của mô hình trong thời gian thực, giúp bạn gỡ lỗi mô hình của mình.", + correct: true + }, + { + text: "Huấn luyện mô hình của bạn", + explain: "Gradio được thiết kể để sử dụng cho việc luận suy mô hình, SAU KHI mô hình của bạn đã được huấn luyện.", + } + ]} +/> + +### 2. Gradio CHỈ hoạt động với các mô hình PyTorch + + + +### 3. Bạn có thể khởi chạy bản demo Gradio từ đâu? + + + +### 4. Gradio được thiết kế chủ yếu cho các mô hình NLP + + + +### 5. Tính năng nào sau đây được hỗ trợ bởi Gradio? + +gr.Interface.load()", + correct: true + } + ]} +/> + +### 6. Cách nào sau đây là cách hợp lệ để tải mô hìnhHugging Face từ Hub hoặc Spaces? + + + +### 7. Chọn tất cả các bước cần thiết để thêm trạng thái vào giao diện Gradio của bạn + + + +### 8. Những thành phần nào sau đây có trong thư viện Gradio? + + + +### 9. Gradio `Blocks` cho phép bạn làm gì? + + + +### 10. Bạn có thể chia sẻ liên kết công khai tới `Blocks` demo và tổ chức lưu trữ `Blocks` demo trên Hugging Face Spaces. + + From 79c6c80fd4ffa2a1eaa0a35d160ae1711f789429 Mon Sep 17 00:00:00 2001 From: lewtun Date: Fri, 2 Sep 2022 15:04:05 +0200 Subject: [PATCH 30/51] Bump release (#308) --- chapters/it/_toctree.yml | 18 ++ chapters/it/chapter3/1.mdx | 21 ++ chapters/it/chapter3/2.mdx | 384 ++++++++++++++++++++++++++++++++++ chapters/it/chapter3/3.mdx | 172 +++++++++++++++ chapters/it/chapter3/3_tf.mdx | 190 +++++++++++++++++ chapters/it/chapter3/4.mdx | 359 +++++++++++++++++++++++++++++++ chapters/it/chapter3/5.mdx | 20 ++ chapters/it/chapter3/6.mdx | 296 ++++++++++++++++++++++++++ 8 files changed, 1460 insertions(+) create mode 100644 chapters/it/chapter3/1.mdx create mode 100644 chapters/it/chapter3/2.mdx create mode 100644 chapters/it/chapter3/3.mdx create mode 100644 chapters/it/chapter3/3_tf.mdx create mode 100644 chapters/it/chapter3/4.mdx create mode 100644 chapters/it/chapter3/5.mdx create mode 100644 chapters/it/chapter3/6.mdx diff --git a/chapters/it/_toctree.yml b/chapters/it/_toctree.yml index 6802ccac3..92b0283b4 100644 --- a/chapters/it/_toctree.yml +++ b/chapters/it/_toctree.yml @@ -31,6 +31,24 @@ - local: chapter2/2 title: Dietro la pipeline + +- title: 3. Affinamento di un modello pre-addestrato + sections: + - local: chapter3/1 + title: Introduzione + - local: chapter3/2 + title: Processare i dati + - local: chapter3/3 + title: Affinare il modello con la Trainer API + local_fw: { pt: chapter3/3, tf: chapter3/3_tf } + - local: chapter3/4 + title: Un addestramento completo + - local: chapter3/5 + title: Affinamento, Fatto! + - local: chapter3/6 + title: Quiz di fine capitolo + quiz: 3 + - title: 4. Condividere modelli e tokenizers sections: - local: chapter4/1 diff --git a/chapters/it/chapter3/1.mdx b/chapters/it/chapter3/1.mdx new file mode 100644 index 000000000..cb05b1776 --- /dev/null +++ b/chapters/it/chapter3/1.mdx @@ -0,0 +1,21 @@ + + +# Introduzione + +Nel [Capitolo 2](/course/chapter2) abbiamo scoperto come utilizzare i tokenizzatori e i modelli preaddestrati per effettuare delle predizioni. Ma cosa fare se si vuole affinare un modello preaddestrato col tuo dataset? Lo scopriremo in questo capitolo! Impareremo: + +{#if fw === 'pt'} +* Come preparare un grande dataset dall'Hub +* Come usare l'API di alto livello `Trainer` per affinare un modello +* Come usare un ciclo di addestramento personalizzato +* Come utilizzare la libreria 🤗 Accelerate per eseguire facilmente quel ciclo di addestramento personalizzato su qualsiasi sistema distribuito + +{:else} +* Come preparare un grande dataset dall'Hub +* Come usare Keras per affinare un modello +* Come usare Keras per ottenere delle predizioni +* Come usare una metrica personalizzata + +{/if} + +Per caricare i checkpoint di addestramento sull'Hub di Hugging Face è necessario un account huggingface.co: [creare un account](https://huggingface.co/join) diff --git a/chapters/it/chapter3/2.mdx b/chapters/it/chapter3/2.mdx new file mode 100644 index 000000000..6fdba0e2e --- /dev/null +++ b/chapters/it/chapter3/2.mdx @@ -0,0 +1,384 @@ + + +# Processare i dati + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} +Continuando l'esempio del [capitolo precedente](/course/chapter2), ecco come addestrare un classificatore di sequenze su un'unica batch in PyTorch: + +```python +import torch +from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification + +# Same as before +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "This course is amazing!", +] +batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") + +# This is new +batch["labels"] = torch.tensor([1, 1]) + +optimizer = AdamW(model.parameters()) +loss = model(**batch).loss +loss.backward() +optimizer.step() +``` +{:else} +Continuando l'esempio del [capitolo precedente](/course/chapter2), ecco come addestrare un classificatore di sequenze su un'unica batch in TensorFlow: + +```python +import tensorflow as tf +import numpy as np +from transformers import AutoTokenizer, TFAutoModelForSequenceClassification + +# Same as before +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "This course is amazing!", +] +batch = dict(tokenizer(sequences, padding=True, truncation=True, return_tensors="tf")) + +# This is new +model.compile(optimizer="adam", loss="sparse_categorical_crossentropy") +labels = tf.convert_to_tensor([1, 1]) +model.train_on_batch(batch, labels) +``` +{/if} + +Ovviamente, addestrare il modello su due frasi non porterà a dei risultati molto buoni. Per ottenere risultati migliori, si deve preparare un dataset più grande. + +In questa sezione verrà usato come esempio il dataset MRPC (Microsoft Research Paraphrase Corpus), presentato nell'[articolo](https://www.aclweb.org/anthology/I05-5002.pdf) di William B. Dolan e Chris Brockett. Il dataset contiene 5801 coppie di frasi, con una label che indica se l'una è una parafrasi dell'altra (i.e. se hanno lo stesso significato). È stato selezionato per questo capitolo perché è un dataset piccolo, con cui è facile sperimentare durante l'addestramento. + +### Caricare un dataset dall'Hub + +{#if fw === 'pt'} + +{:else} + +{/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. + +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ì: + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("glue", "mrpc") +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 408 + }) + test: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 1725 + }) +}) +``` + +Il risultato è un oggetto di tipo `DatasetDict` che contiene il training set, il validation set, e il test set. Ciascuno di questi contiene svariate colonne, (`sentence1`, `sentence2`, `label`, e `idx`) a un numero variabile di righe, corrispondenti al numero di elementi in ogni set (quindi, vi sono 3668 coppie di frasi nel training set, 408 nel validation set, e 1725 nel test set). + +Questo comando scarica il dataset e lo mette in cache, in *~/.cache/huggingface/dataset* secondo l'impostazione predefinita. Nel Capitolo 2 è stato spiegato come personalizzare la cartella di cache impostando la variabile d'ambiente `HF_HOME`. + +Ogni coppia di frasi nell'oggetto `raw_datasets` può essere ottenuta tramite il suo indice, come in un dizionario: + +```py +raw_train_dataset = raw_datasets["train"] +raw_train_dataset[0] +``` + +```python out +{'idx': 0, + 'label': 1, + 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', + 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} +``` + +Le label sono già numeri interi, quindi non è necessario alcun preprocessing. Per sapere a quale numero corrisponde quale tipo di label, si possono analizzare le `features` del `raw_train_dataset`. Ciò permette di capire la tipologia di ogni colonna: + + +```py +raw_train_dataset.features +``` + +```python out +{'sentence1': Value(dtype='string', id=None), + 'sentence2': Value(dtype='string', id=None), + 'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None), + 'idx': Value(dtype='int32', id=None)} +``` + +Dietro le quinte, `label` è del tipo `ClassLabel`, e la corrispondenza tra i numeri e i nomi delle label è contenuta nella cartella *names*. `0` corrisponde a `not_equivalent` (significato diverso), e `1` corrisponde a `equivalent` (stesso significato). + + + +✏️ **Prova tu!** Quali sono le label dell'elemento 15 del training set, e 87 del validation set? + + + +### Preprocessing del dataset + +{#if fw === 'pt'} + +{:else} + +{/if} + +Per preprocessare il dataset, è necessario convertire il testo in numeri comprensibili al modello. Come dimostrato nel [capitolo precedente](/course/chapter2), ciò viene fatto con un tokenizer (tokenizzatore). Il tokenizer prende come input sia una frase sia una lista di frasi, quindi è possibile effettuare la tokenizzazione di tutte le prime e seconde frasi di ogni coppia in questo modo: + +```py +from transformers import AutoTokenizer + +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"]) +tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"]) +``` + +Tuttavia, non si possono semplicemente passare al modello due frasi e sperare di predire se l'una è una parafrasi dell'altra o no. Bisogna gestire le due frasi come una coppia, e applicare il preprocessing necessario. Fortunatamente, il tokenizer può anche prendere come input una coppia di frasi e prepararla nel formato atteso dal modello BERT: + +```py +inputs = tokenizer("This is the first sentence.", "This is the second one.") +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] +} +``` + +Sono state già discusse nel [Capitolo 2](/course/chapter2) le chiavi `input_ids` e `attention_mask`, ma il discorso su `token_type_ids` era stato rimandato. In questo esempio, ciò può essere usato per indicare al modello quale parte dell'input è la prima frase, e quale la seconda. + + + +✏️ **Prova tu!** Prendere l'element 15 del training set e tokenizzare le due frasi sia separatamente, sia come coppia. Qual è la differenza tra i due risultati? + + + +Decodificando gli ID in `input_ids` per ritrasformarli in parole: + +```py +tokenizer.convert_ids_to_tokens(inputs["input_ids"]) +``` + +si ottiene: + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +``` + +Perciò è chiaro che il modello si aspetta gli input nella forma `[CLS] frase1 [SEP] frase2 [SEP]` quando vi sono due frasi. Allineando con `token_type_ids` si ottiene: + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +[ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] +``` + +Le parti dell'input corrispondenti a `[CLS] frase1 [SEP]` hanno tutte un token type ID di `0`, mentre le altre parti, corrispondenti quindi a `frase2 [SEP]`, hanno tutte un token type ID di `1`. + +Da notare che se viene selezionato un altro checkpoint, gli input tokenizzati non conterranno necessariamente i `token_type_ids` (ad esempio, non si ottengono usando un modello DistilBERT). I `token_type_ids` si ottengono solo quando il modello saprebbe che farne, avendole già viste in fase di pre-addestramento. + +In questo caso, BERT è stato pre-addestrato con i token type IDs, e in aggiunta all'obiettivo di _masked language modeling_ di cui si era parlato nel [Capitolo 1](/course/chapter1), vi è un altro obiettivo che si chiama _next sentence prediction_ (_predire la prossima frase_). Lo scopo di questo task è modellizzare la relazione tra coppie di frasi. + +Durante un task di next sentence prediction, il modello riceve una coppia di frasi (con token mascherati in maniera aleatoria) e deve predire se la seconda segue la prima. Per rendere il task meno banale, la metà delle volte le frasi si susseguono nel documento da cui erano state estratte originariamente, l'altra metà delle volte le frasi provengono da due documenti diversi. + +In generale, non bisogna preoccuparsi se i `token_type_ids` sono presenti o no negli input tokenizzati: finché viene usato lo stesso checkpoint per il tokenizer e il modello, tutto andrà bene poiché il tokenizer sa cosa fornire al modello. + +Ora che abbiamo visto come il tokenizer può gestire una coppia di frasi, possiamo usarlo per tokenizzare l'intero dataset: come nel [capitolo precedente](/course/chapter2), si può fornire al tokenizer una lista di coppie di frasi dando prima la lista delle prime frasi, e poi la lista delle seconde frasi. Questo approcchio è anche compatibile le opzioni di padding e truncation già viste nel [Capitolo 2](/course/chapter2). Perciò, un modo per preprocessare il dataset di addestramento è: + +```py +tokenized_dataset = tokenizer( + raw_datasets["train"]["sentence1"], + raw_datasets["train"]["sentence2"], + padding=True, + truncation=True, +) +``` + +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: + +```py +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) +``` + +Questa funzione riceve un dizionario (come gli elementi del nostro dataset) e restituisce un nuovo dizionario con input_ids`, `attention_mask`, e `token_type_ids` come chiavi. Funziona anche se il dizionario `example` contiene svariati campioni (ad una chiave corrisponde una lista di frasi) poiché il `tokenizer` funziona con liste di coppie di frasi, come già visto. Ciò permette di usare l'opzione `batched=True` nella chiamata a `map()`, che accelererà di molto la tokenizzazione. Il `tokenizer` si appoggia ad un tokenizer scritto in Rust della libreria [🤗 Tokenizers](https://github.com/huggingface/tokenizers). Questo tokenizer può essere molto veloce, ma solo se gli vengono forniti molti input insieme. + +Per ora non ci siamo preoccupati del parametro `padding` nella nostra funzione di tokenizzazione. Questo perché il padding di tutti i campioni fino a lunghezza massima non è efficiente: è meglio fare il padding dei campioni quando stiamo assemblando una batch, poiché in quel momento è necessario il padding solo fino alla lunghezza massima nel batch, non la lunghezza massima nell'intero dataset. Ciò permette di risparmiare molto tempo e potenza di calcolo nel caso in cui gli input abbiano lunghezze molto varie! + +Ecco come si applica la funzione di tokenizzazione sull'intero dataset. Bisogna usare `batched=True` nella chiamata a `map` in modo tale che la funzione venga applicata a vari elementi del dataset insieme, e non ad ogni elemento separatamente. Ciò permette un preprocessing più rapido. + +```py +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) +tokenized_datasets +``` + +La libreria 🤗 Datasets aggiunge nuovi campi ai dataset, uno per ogni chiave nel dizionario restituito dalla funzione di preprocessing: + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 408 + }) + test: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 1725 + }) +}) +``` + +Si può anche applicare il multiprocessing durante il preprocessing con la funzione `map()` utilizzando il parametro `num_proc`. Ciò non è stato dimostrato qui perché la libreria 🤗 Tokenizers già utilizza vari thread per tokenizzare i campioni più rapidamente, ma nel caso in cui non venga usato un tokenizer rapido di questa libreria, ciò potrebbe velocizzare il preprocessing. + +La funzione `tokenize_function` restituisce un dizionario con `input_ids`, `attention_mask`, e `token_type_ids` come chiavi, quindi quei tre campi vengono aggiunti a tutti gli split (le parti) del dataset. Si possono anche cambiare i campi esistenti nel caso in cui la funzione di preprocessing restituisca un nuovo valore per una chiave già esistente nel dataset a cui viene applicato `map()`. + +L'ultima cosa da fare è il padding di tutti i campioni alla lunghezza dell'elemento più lungo quando sono inseriti in una batch — una tecnica che si chiama *dynamic padding*. + +### Dynamic padding + + + +{#if fw === 'pt'} +La funzione responsabile dell'assembramento dei campioni in una batch si chiama *collate function* (*funzione di raccolta*). È uno dei parametri che si possono passare quando si costruisce un `DataLoader`, e il default è la funzione che converte semplicemente i campioni in tensori PyTorch e li concatena (ricorsivamente nel caso in cui gli elementi siano liste, tuple o dizionari). Ciò non sarà possibile nel nostro caso poiché gli input non hanno tutti la stessa lunghezza. Abbiamo rimandato il padding apposta, per poterlo applicare secondo necessità ad ogni batch, evitando quindi input troppo lunghi con molto padding. Ciò accelererà l'addestramento di un bel po', ma può causare problemi se l'addestramento avviene su TPU — le TPU preferiscono dimensioni fisse, anche se ciò richiede del padding in più. + +{:else} + +La funzione responsabile dell'assembramento dei campioni in una batch si chiama *collate function* (*funzione di raccolta*). Il collator (raccoglitore) di default è la funzione che converte semplicemente i campioni in tf.Tensor e li concatena (ricorsivamente nel caso in cui gli elementi siano liste, tuple o dizionari). Ciò non sarà possibile nel nostro caso poiché gli input non hanno tutti la stessa lunghezza. Abbiamo rimandato il padding apposta, per poterlo applicare secondo necessità ad ogni batch, evitando quindi input troppo lunghi con molto padding. Ciò accelererà l'addestramento di un bel po', ma può causare problemi se l'addestramento avviene su TPU — le TPU preferiscono dimensioni fisse, anche se ciò richiede del padding in più. + +{/if} + +In pratica, bisogna definire una collate function che applichi la giusta quantità di padding agli elementi del dataset in una stessa batch. Fortunatamente, la libreria 🤗 Transformers fornisce questa funziona tramite `DataCollatorWithPadding`. Essa prende in input un tokenizer quando viene istanziata (per individuare quale token da usare per il padding, e se il modello si aspetta padding a sinistra o a destra dell'input) e farà tutto il necessario: + +{#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} + +Per testare questo nuovo gioco, analizziamo alcuni campioni dal set di addestramento da raggruppare in un batch. Adesso togliamo le colonne `idx`, `sentence1`, e `sentence2` poiché non saranno necessarie e contengono stringhe (e non si possono creare tensori con stringhe), e controlliamo le lunghezze di ogni elemento nel batch: + +```py +samples = tokenized_datasets["train"][:8] +samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]} +[len(x) for x in samples["input_ids"]] +``` + +```python out +[50, 59, 47, 67, 59, 50, 62, 32] +``` + +Nulla di sorprendente, i campioni hanno lunghezza variabile da 32 a 67. Il padding dinamico significa che i campioni in questo batch dovrebbero tutti ricevere un padding fino alla lunghezza di 67, il massimo nel batch. Senza padding dinamico, tutti i campioni dovrebbero ricevere un padding fino alla lunghezza massima nell'intero dataset, o la lunghezza massima processabile dal modello. Bisogna controllare che il `data_collator` stia applicando un padding dinamico al batch in maniera corretta: + +```py +batch = data_collator(samples) +{k: v.shape for k, v in batch.items()} +``` + +{#if fw === 'tf'} + +```python out +{'attention_mask': TensorShape([8, 67]), + 'input_ids': TensorShape([8, 67]), + 'token_type_ids': TensorShape([8, 67]), + 'labels': TensorShape([8])} +``` + +{:else} + +```python out +{'attention_mask': torch.Size([8, 67]), + 'input_ids': torch.Size([8, 67]), + 'token_type_ids': torch.Size([8, 67]), + 'labels': torch.Size([8])} +``` + +Ottimo! Adesso che siamo passati dal testo grezzo a dei batch che il modello è in grado di trattare, siamo pronti per affinarlo! + +{/if} + + + +✏️ **Prova tu!** Replicare il preprocessing sul dataset GLUE SST-2. È leggermente diverso poiche è composto da frasi singole e non da coppie di frasi, ma il resto della procedura dovrebbe essere simile. Per una sfida più complessa, provare a scrivere una funzione di preprocessing che funzioni per qualsiasi dei compiti in GLUE. + + + +{#if fw === 'tf'} + +Ora che sono stati definiti un dataset e un collator di dati, dobbiamo metterli insieme. Si potrebbero caricare e raccogliere i batch manualmente, ma significherebbe molto lavoro e probabilmente una bassa performance. Invece, vi è un metodo semplice che offre una soluzione con buona performance a questo problema: `to_tf_dataset()`. Questo impacchetterà il dataset con `tf.data.Dataset`, con una collate function opzionale. +`tf.data.Dataset` è un formato nativo di TensorFlow che Keras può utilizzare durante `model.fit()`, cosicché questo metodo converte immediatamente un 🤗 Dataset in un formato pronto per l'addestramento. Vediamolo in azione col nostro dataset! + + +```py +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, +) +``` + +Fine! Questi dataset verranno utilizzati nelle prossime lezioni, dove l'addestramento sarà reso piacevolmente immediato dopo tutto questo duro lavoro di preprocessing. + +{/if} diff --git a/chapters/it/chapter3/3.mdx b/chapters/it/chapter3/3.mdx new file mode 100644 index 000000000..055e4079e --- /dev/null +++ b/chapters/it/chapter3/3.mdx @@ -0,0 +1,172 @@ + + +# Affinare il modello con la Trainer API + + + + + +🤗 Transformers fornisce una classe `Trainer` (addestratore) per aiutare con l'affinamento di uno qualsiasi dei modelli pre-addestrati nel dataset. Dopo tutto il lavoro di preprocessing nella sezione precedente, rimangono giusto gli ultimi passi per definire il `Trainer`. Probabilmente la parte più complicata sarà preparare l'ambiente per eseguire `Trainer.train()`, poiché sarà molto lento su una CPU. Se non avete una GPU a disposizione, potete avere accesso gratuitamente a GPU o TPU su [Google Colab](https://colab.research.google.com/). + +Gli esempi di codice qui sotto partono dal presupposto che gli esempi nella sezione precedente siano già stati eseguiti. Ecco un breve riassunto di cosa serve: + +```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) +``` + +### Addestramento + +Il primo passo per definire un `Trainer` è la definizione di una classe `TrainingArguments` che contenga tutti gli iperparametri che verranno usati dal `Trainer` per l'addestramento e la valutazione. L'unico parametro da fornire è la cartella dove verranno salvati il modello addestrato e i vari checkpoint. Per tutto il resto si possono lasciare i parametri di default, che dovrebbero funzionare bene per un affinamento di base. + +```py +from transformers import TrainingArguments + +training_args = TrainingArguments("test-trainer") +``` + + + +💡 Se si vuole caricare automaticamente il modello all'Hub durante l'addestramento, basta passare `push_to_hub=True` come parametro nei `TrainingArguments`. Maggiori dettagli verranno forniti nel [Capitolo 4](/course/chapter4/3). + + + +Il secondo passo è definire il modello. Come nel [capitolo precedente](/course/chapter2), utilizzeremo la classe `AutoModelForSequenceClassification` con due label: + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +Diversamente dal [Capitolo 2](/course/chapter2), un avviso di avvertimento verrà visualizzato dopo aver istanziato questo modello pre-addestrato. Ciò avviene perché BERT non è stato pre-addestrato per classificare coppie di frasi, quindi la testa del modello pre-addestrato viene scartata e una nuova testa adeguata per il compito di classificazione di sequenze è stata inserita. Gli avvertimenti indicano che alcuni pesi non verranno usati (quelli corrispondenti alla testa scartata del modello pre-addestrato) e che altri pesi sono stati inizializzati con valori casuali (quelli per la nuova testa). L'avvertimento viene concluso con un'esortazione ad addestrare il modello, che è esattamente ciò che stiamo per fare. + +Una volta ottenuto il modello, si può definire un `Trainer` passandogli tutti gli oggetti costruiti fino ad adesso — il `model`, i `training_args`, i dataset di addestramento e validazione, il `data_collator`, e il `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, +) +``` + +Quando si passa l'argomento `tokenizer` come appena fatto, il `data_collator` usato di default dal `Trainer` sarà del tipo `DataCollatorWithPadding`, come definito precedentemente, quindi si potrebbe evitare di specificare l'argomento `data_collator=data_collator` in questa chiamata. Tuttavia era comunque importante mostrare questa parte del processing nella sezione 2! + +Per affinare il modello sul nostro dataset, bisogna solo chiamare il metodo `train()` del `Trainer`: + +```py +trainer.train() +``` + +Questo farà partire l'affinamento (che richiederà un paio di minuti su una GPU) e produrrà un report della funzione obiettivo dell'addestramento ogni 500 passi. Tuttavia, non vi farà sapere quanto sia buona (o cattiva) la performance del modello. Ciò è dovuto al fatto che: + +1. Non è stato detto al `Trainer` di valutare il modello durante l'addestramento, settando `evaluation_strategy` o al valore `"steps"` (valuta il modello ogni `eval_steps`) oppure al valore `"epoch"` (valuta il modello alla fine di ogni epoca). +2. Non è stato fornito al `Trainer` una funzione `compute_metrics()` per calcolare le metriche di valutazione (altrimenti la valutazione stamperebbe solo il valore della funzione obiettivo, che non è un valore molto intuitivo). + + +### Valutazione + +Vediamo come si può costruire una funzione `compute_metrics()` utile e usarla per il prossimo addestramento. La funzione deve prendere come parametro un oggetto `EvalPrediction` (che è una named tuple avente un campo `predictions` – predizioni – e un campo `label_ids` – id delle etichette –) e restituirà un dizionario che associa stringhe a numeri floating point (le stringhe saranno i nomi delle metriche, i numeri i loro valori). Per ottenere delle predizioni, si può usare il comando `Trainer.predict()`: + +```py +predictions = trainer.predict(tokenized_datasets["validation"]) +print(predictions.predictions.shape, predictions.label_ids.shape) +``` + +```python out +(408, 2) (408,) +``` + +Il risultato del metodo `predict()` è un'altra named tuple con tre campi: `predictions`, `label_ids`, e `metrics`. Il campo `metrics` conterrà solo il valore della funzione obiettivo sul dataset, in aggiunta ad alcune metriche legate al tempo (il tempo necessario per calcolare le predizioni, in totale e in media). Una volta completata la funzione `compute_metrics()` e passata al `Trainer`, quel campo conterrà anche le metriche restituite da `compute_metrics()`. + +Come si può vedere, `predictions` è un array bi-dimensionale con dimensioni 408 x 2 (poiché 408 è il numero di elementi nel dataset). Questi sono i logit per ogni elemento del dataset passato a `predict()` (come già visto nel [capitolo precedente](/course/chapter2), tutti i modelli Transformer restituiscono logit). Per trasformarli in predizioni associabili alle etichette, bisogna prendere l'indice col valore massimo sul secondo asse: + +```py +import numpy as np + +preds = np.argmax(predictions.predictions, axis=-1) +``` + +Ora si possono paragonare i `preds` con le etichette. Per costruire la funzione `compute_metric()`, verranno utilizzate le metriche dalla libreria 🤗 Dataset. Si possono caricare le metriche associate con il dataset MRPC in maniera semplice, utilizzando la funzione `load_metric()`. L'oggetto restituito ha un metodo `compute()` (calcola) che possiamo usare per calcolare le metriche: + +```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} +``` + +L'esatto valore dei risultati potrebbe essere diverso nel vostro caso, a casa dell'inizializzazione casuale della testa del modello. In questo caso il nostro modello ha un'accuratezza del 85.78% sul set di validazione e un valore F1 di 89.97. Queste sono le due metriche utilizzate per valutare i risultati sul dataset MRPC per il benchmark GLUE. La tabella nell'[articolo su BERT](https://arxiv.org/pdf/1810.04805.pdf) riportava un F1 di 88.9 per il modello base. Quello era il modello `uncased` (senza distinzione fra minuscole e maiuscole) mentre noi stiamo usando quello `cased`, il che spiega il risultato migliore. + +Mettendo tutto insieme si ottiene la funzione `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) +``` + +Per vederla in azione e fare il report delle metriche alla fine di ogni epoca, ecco come si definisce un nuovo `Trainer` che includa questa funzione `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, +) +``` + +Da notare che bisogna creare un nuovo oggetto `TrainingArguments` con il valore di `evaluation_strategy` pari a `"epoch"` e un nuovo modello — altrimenti si continuerebbe l'addestramento del modello già addestrato. Per lanciare una nuova esecuzione dell'addestramento si usa: + +``` +trainer.train() +``` + +Stavolta vi sarà il report della funzione obiettivo di validazione alla fine di ogni epoca, in aggiunta alla funzione obiettivo dell'addestramento. Di nuovo, i valori esatti di accuratezza/F1 ottenuti da voi potrebbero variare leggermente da quelli mostrati qui a causa dell'inizializzazione casuale della testa del modello, ma dovrebbero essere comparabili. + +Il `Trainer` funzionerà direttamente su svariate GPU e TPU e ha molte opzioni, tra cui addestramento in precisione mista (utilizzare `fp16 = True` negli argomenti). I dettagli delle opzioni verranno esplorati nel Capitolo 10. + +Qui si conclude l'introduzione all'affinamento usando l'API del `Trainer`. Esempi per i compiti più comuni in NLP verranno forniti nel Capitolo 7, ma per ora vediamo come ottenere la stessa cosa usando puramente Pytorch. + + + +✏️ **Prova tu!** Affinare un modello sul dataset GLUE SST-2 utilizzando il processing dei dati già fatto nella sezione 2. + + + diff --git a/chapters/it/chapter3/3_tf.mdx b/chapters/it/chapter3/3_tf.mdx new file mode 100644 index 000000000..42874f293 --- /dev/null +++ b/chapters/it/chapter3/3_tf.mdx @@ -0,0 +1,190 @@ + + +# Affinare un modell usando Keras + + + +Dopo tutto il lavoro di preprocessing nella sezione precedente, rimangono giusto gli ultimi passi per addestrare il modello. Attenzione tuttavia che il comando `model.fit()` sarà molto lento su una CPU. Se non avete una GPU a disposizione, potete avere accesso gratuitamente a GPU o TPU su [Google Colab](https://colab.research.google.com/). + +Gli esempi di codice qui sotto partono dal presupposto che gli esempi nella sezione precedente siano già stati eseguiti. Ecco un breve riassunto di cosa serve: + +```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, +) +``` + +### Addestramento + +I modelli di TensorFlow importati da 🤗 Transformers sono già dei modelli Keras. Ecco una breve introduzione a Keras. + + + +Ciò significa che una volta che si hanno i dati, è richiesto poco lavoro aggiuntivo per cominciare l'addestramento. + + + +Come nel [capitolo precedente](/course/chapter2), verrà utilizzata la classe `TFAutoModelForSequenceClassification` con due etichette: + +```py +from transformers import TFAutoModelForSequenceClassification + +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +Diversamente dal [Capitolo 2](/course/chapter2), un avviso di avvertimento verrà visualizzato dopo aver istanziato questo modello pre-addestrato. Ciò avviene perché BERT non è stato pre-addestrato per classificare coppie di frasi, quindi la testa del modello pre-addestrato viene scartata e una nuova testa adeguata per il compito di classificazione di sequenze è stata inserita. Gli avvertimenti indicano che alcuni pesi non verranno usati (quelli corrispondenti alla testa scartata del modello pre-addestrato) e che altri pesi sono stati inizializzati con valori casuali (quelli per la nuova testa). L'avvertimento viene concluso con un'esortazione ad addestrare il modello, che è esattamente ciò che stiamo per fare. + +Per affinare il modello sul dataset, bisogna solo chiamare `compile()` (compila) sul modello e passare i dati al metodo `fit()`. Ciò farà partire il processo di affinamento (che dovrebbe richiedere un paio di minuti su una GPU) e fare il report della funzione obiettivo di addestramento, in aggiunta alla funzione obiettivo di validazione alla fine di ogni epoca. + + + +I modelli 🤗 Transformers hanno un'abilità speciale che manca alla maggior parte dei modelli Keras – possono usare in maniera automatica una funzione obiettivo appropriata, calcolata internamente. Questa funzione obiettivo verrà usata di default a meno che non venga definito l'argomento di funzione obiettivo nel metodo `compile()`. Per usare la funzione obiettivo interna è necessario passare le etichette come parte dell'input, non separatamente, che è l'approccio normale con i modelli Keras. Verranno mostrati esempi di ciò nella Parte 2 del corso, dove definire la funzione obiettivo correttamente può essere difficile. Per la classificazione di sequenze, invece, la funzione obiettivo standard di Keras funziona bene, quindi verrà utilizzata quella. + + + +```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, +) +``` + + + +Attenzione ad un errore comune — si *può* passare solo il nome della funzione obiettivo a Keras come una stringa, ma di default Keras si aspetta che softmax sia già stato applicato ai risultati. Molti modelli invece forniscono come risultato i valori prima dell'applicazione del softmax, chiamati *logits*. Bisogna informare la funzione obiettivo che il nostro modello fa esattamente questo, e il solo modo di farlo è invocandola direttamente, non tramite la stringa che rappresenta il suo nome. + + + + +### Migliorare la performance di addestramento + + + +Il codice presentato qui sopra sicuramente funziona, ma la funzione obiettivo scende in maniera molto lenta e sporadica. La causa principale di ciò è la *learning rate* (tasso di apprendimento). Come con la funzione obiettivo, quando si passa il nome di un ottimizzatore a Keras tramite una stringa, Keras inizializza quell'ottimizzatore con i valori di default per tutti i parametri, inclusa la learning rate. Grazie alla nostra esperienza, però, sappiamo che i modelli transformer beneficiano da una learning rate molto più bassa del valore di default per Adam, che è 1e-3, scritto anche come 10 alla -3, o 0.001. Il valore 5e-5 (0.00005), circa venti volte più basso, è un punto di partenza migliore. + +In aggiunta a diminuire la learning rate, abbiamo un secondo asso nella manica: possiamo ridurre lentamente la learning rate durante l'addestramento. Nella letteratura, questo approccio viene spesso indicato come *decaying* (decadimento) o *annealing* (ricottura) della learning rate. In Keras, il modo migliore per ottenere ciò è attraverso un *learning rate scheduler* (pianificatore del tasso di addestramento). Un buon esempio è `PolynomialDecay` (decadimento polinomiale) — nonostante il nome, con le impostazioni di default ha un semplice decadimento lineare dal valore iniziale a quello finale durante l'addestramento, che è esattamente ciò che cerchiamo. Per utilizzare lo scheduler in maniera corretta, però, bisogna dirgli quanto lungo sarà l'addestramento. Lo calcoliamo tramite la variabile `num_train_steps` nell'esempio qui sotto. + +```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) +``` + + + +La libreria 🤗 Transformers fornisce anche una funzione `create_optimizer()` che crea un ottimizzatore `AdamW` con decadimento della learning rate. Questa può essere una scorciatoia utile che verrà presentata nel dettaglio nelle sezioni future del corso. + + + +Adesso che abbiamo il nostro ottimizzatore nuovo di zecca, possiamo provare con un addestramento. Per prima cosa, ricarichiamo il modello, per resettare i cambiamenti ai pesi dall'addestramento precedente, dopodiché lo possiamo compilare con nuovo ottimizzatore. + +```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"]) +``` + +Ora chiamiamo di nuovo fit + +```py +model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) +``` + + + +💡 Se vuoi caricare il modello in maniera automatica all'Hub durante l'addestramento, puoi passare `PushToHubCallback` al metodo `model.fit()`. Maggiori dettagli verranno forniti nel [Capitolo 4](/course/chapter4/3) + + + +### Predizioni del modello + + + + +Vedere la funzione obiettivo che diminuisce durante l'addestramento è bello, ma se volessimo ottenere dei risultati dal modello addestrato, ad esempio per calcolare delle metriche o per usare il modello in produzione? Per questo, si può usare il metodo `predict()`. Questo metodo restituisce i `*logits* dalla testa del modello, uno per classe. + +```py +preds = model.predict(tf_validation_dataset)["logits"] +``` + +I logits possono essere convertiti nelle predizioni delle classi del modello usando `argmax` per trovare il logit più grande, che corrisponde alla classe più probabile. + +```py +class_preds = np.argmax(preds, axis=1) +print(preds.shape, class_preds.shape) +``` + +```python out +(408, 2) (408,) +``` + +Ora usiamo le `preds` per calcolare delle metriche! Si possono caricare le metriche associate al dataset MRPC in maniera facile tanto quanto caricare il dataset in sé, usando la funzione `load_metric()`. L'oggetto restituito ha un metodo `compute()` che può essere usato per calcolare le metriche. + +```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} +``` + +L'esatto valore dei risultati ottenuti può variare, poiché l'inizializzazione casuale della testa del modello può cambiare le metriche ottenute. Qui vediamo che il nostro modello ha una accuratezza del 87.78% sul validation set e valore F1 di 89.97. Queste sono le due metriche utilizzare per valutare risultati sul dataset MRPC per il benchmark GLUE. La tabella nell'[articolo du BERT](https://arxiv.org/pdf/1810.04805.pdf) riportava un valore F1 di 88.9 per il modello di base. Quello era il modello `uncased`, mentre qui stiamo usando il modello `cased`, il che spiega i migliori risultati. + +Questo conclude l'introduzione all'affinamento usando l'API Keras. Un esempio per i compiti di NLP più comuni verrà fornito nel Capitolo 7. Per migliorare le vostre abilità con l'API Keras, provate ad affinare un modello sul dataset GLUE SST-2, usando il processing dei dati spiegato nella sezione 2. diff --git a/chapters/it/chapter3/4.mdx b/chapters/it/chapter3/4.mdx new file mode 100644 index 000000000..b07316182 --- /dev/null +++ b/chapters/it/chapter3/4.mdx @@ -0,0 +1,359 @@ +# Un addestramento completo + + + + + +Ora vedremo come ottenere gli stessi risultati della sezione precedente senza utilizzare la classe `Trainer`. Ancora una volta, aver compiuto il processing dei dati spiegato nella sezione 2 è un prerequisito. Ecco un riassunto di tutto ciò di cui avrete bisogno: + +```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) +``` + +### Preparazione all'addestramento + +Prima di cominciare a scrivere il nostro ciclo di addestramento, dobbiamo definire alcuni oggetti. Per prima cosa, i dataloaders (caricatori di dati) che useremo per iterare sulle batch. Ma prima di poter definire i dataloaders, dobbiamo applicare un po' di postprocessing ai nostri `tokenized_datasets`, per compiere alcune operazione che `Trainer` gestiva in automatico per noi. Nello specifico dobbiamo: + +- Rimuovere le colonne corrispondente a valori che il modello non si aspetta (come ad esempio le colonne `sentence1` e `sentence2 `). +- Rinominare la colonna `label` a `labels` (perché il modello si aspetta questo nome). +- Fissare il formato dei datasets in modo che restituiscano tensori Pytorch invece di liste. + +L'oggetto `tokenized_datasets` ha un metodo per ciascuno di questi punti: + +```py +tokenized_datasets = tokenized_datasets.remove_columns(["sentence1", "sentence2", "idx"]) +tokenized_datasets = tokenized_datasets.rename_column("label", "labels") +tokenized_datasets.set_format("torch") +tokenized_datasets["train"].column_names +``` + +Possiamo poi controllare che il risultato ha solo solo colonne che saranno accettate dal nostro modello: + +```python +["attention_mask", "input_ids", "labels", "token_type_ids"] +``` + +Ora che questo è fatto, possiamo finalmente definire i dataloaders in maniera semplice: + +```py +from torch.utils.data import DataLoader + +train_dataloader = DataLoader( + tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator +) +eval_dataloader = DataLoader( + tokenized_datasets["validation"], batch_size=8, collate_fn=data_collator +) +``` + +Per controllare velocemente che non ci sono errori nel processing dei dati, possiamo ispezionare una batch in questo modo: + +```py +for batch in train_dataloader: + break +{k: v.shape for k, v in batch.items()} +``` + +```python out +{'attention_mask': torch.Size([8, 65]), + 'input_ids': torch.Size([8, 65]), + 'labels': torch.Size([8]), + 'token_type_ids': torch.Size([8, 65])} +``` + +È importante sottolineare che i valori di shape (forma) potrebbero essere leggermente diversi per voi, poiché abbiamo fissato `shuffle=True` (rimescolamento attivo) per i dataloader di apprendimento, e stiamo applicando padding alla lunghezza massima all'interno della batch. + +Ora che il preprocessing dei dati è completato (uno scopo soddisfacente ma elusivo per qualunque praticante di ML), focalizziamoci sul modello. Lo istanziamo esattamente come avevamo fatto nella sezione precedente: + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +Per assicurarci che tutto andrà bene durante l'addestramento, passiamo la batch al modello: + +```py +outputs = model(**batch) +print(outputs.loss, outputs.logits.shape) +``` + +```python out +tensor(0.5441, grad_fn=) torch.Size([8, 2]) +``` + +Tutti i modelli 🤗 Transformers restituiscono il valore obiettivo quando vengono fornite loro le `labels`, e anche i logits (due per ciascun input della batch, quindi un tensore di dimensioni 8 x 2). + +Siamo quasi pronti a scrivere il ciclo di addestramento! Mancano solo due cose: un ottimizzatore e un learning rate scheduler. Poiché stiamo tentando di replicare a mano ciò che viene fatto dal `Trainer`, utilizzeremo gli stessi valori di default. L'ottimizzatore utilizzato dal `Trainer` è `AdamW`, che è lo stesso di Adam ma con una variazione per quanto riguarda la regolarizzazione del decadimento dei pesi (rif. ["Decoupled Weight Decay Regularization"](https://arxiv.org/abs/1711.05101) di Ilya Loshchilov e Frank Hutter): + +```py +from transformers import AdamW + +optimizer = AdamW(model.parameters(), lr=5e-5) +``` + +Infine, il learning rate scheduler usato di default è solo un decadimento lineare dal valore massimo (5e-5) fino a 0. Per definirlo correttamente, dobbiamo sapere il numero di iterazioni per l'addestramento, che è dato dal numero di epoche che vogliamo eseguire moltiplicato per il numero di batch per l'addestramento (ovverosia la lunghezza del dataloader). Il `Trainer` usa 3 epoche di default, quindi: + +```py +from transformers import get_scheduler + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) +print(num_training_steps) +``` + +```python out +1377 +``` + +### Il ciclo di addestramento + +Un'ultima cosa: se si ha accesso ad una GPU è consigliato usarla (su una CPU, l'addestramento potrebbe richiedere svariate ore invece di un paio di minuti). Per usare la GPU, definiamo un `device` su cui spostare il modello e le batch: + +```py +import torch + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) +device +``` + +```python out +device(type='cuda') +``` + +Siamo pronti per l'addestramento! Per avere un'intuizione di quando sarà finito, aggiungiamo una barra di progresso sul numero di iterazioni di addestramento, usando la libreria `tqdm`: + +```py +from tqdm.auto import tqdm + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Potete vedere che il nocciolo del ciclo di addestramento è molto simile a quello nell'introduzione. Non abbiamo chiesto nessun report, quindi il ciclo non ci informerà su come si sta comportando il modello. Dobbiamo aggiungere un ciclo di valutazione per quello. + + +### Il ciclo di valutazione + +Come fatto in precedenza, utilizzeremo una metrica fornita dalla libreria 🤗 Datasets. Abbiamo già visto il metodo `metric.compute()`, ma le metriche possono automaticamente accumulare le batch nel ciclo di predizione col metodo `add_batch()`. Una volta accumulate tutte le batch, possiamo ottenere il risultato finale con `metric.compute()`. Ecco come implementare tutto ciò in un ciclo di valutazione: + +```py +from datasets import load_metric + +metric = load_metric("glue", "mrpc") +model.eval() +for batch in eval_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + with torch.no_grad(): + outputs = model(**batch) + + logits = outputs.logits + predictions = torch.argmax(logits, dim=-1) + metric.add_batch(predictions=predictions, references=batch["labels"]) + +metric.compute() +``` + +```python out +{'accuracy': 0.8431372549019608, 'f1': 0.8907849829351535} +``` + +Ancora una volta, i vostri risultati potrebbero essere leggermente diversi a causa della casualità nell'inizializzazione della testa del modello e del ricombinamento dei dati, ma dovrebbero essere nello stesso ordine di grandezza. + + + +✏️ **Prova tu!** Modifica il ciclo di addestramento precedente per affinare il modello sul dataset SST-2. + + + +### Potenzia il tuo ciclo di addestramento con 🤗 Accelerate + + + +Il ciclo di addestramento che abbiamo definito prima funziona bene per una sola CPU o GPU. Ma grazie alla libreria [🤗 Accelerate](https://github.com/huggingface/accelerate), con alcuni aggiustamenti possiamo attivare l'addestramento distribuito su svariate GPU o TPU. Partendo dalla creazione dei dataloaders di addestramento e validazione, ecco l'aspetto del nostro ciclo di addestramento manuale: + +```py +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Ecco i cambiamenti necessari: + +```diff ++ from accelerate import Accelerator + from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + ++ accelerator = Accelerator() + + model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) + optimizer = AdamW(model.parameters(), lr=3e-5) + +- device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +- model.to(device) + ++ train_dataloader, eval_dataloader, model, optimizer = accelerator.prepare( ++ train_dataloader, eval_dataloader, model, optimizer ++ ) + + num_epochs = 3 + num_training_steps = num_epochs * len(train_dataloader) + lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps + ) + + progress_bar = tqdm(range(num_training_steps)) + + model.train() + for epoch in range(num_epochs): + for batch in train_dataloader: +- batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss +- loss.backward() ++ accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Prima di tutto bisogna inserire la linea di importazione. La seconda linea istanzia un oggetto di tipo `Accelerator` che controllerà e inizializzerà il corretto ambiente distribuito. 🤗 Accelerate gestice il posizionamento sui dispositivi per voi, quindi potete togliere le linee che spostavano il modello sul dispositivo (o, se preferite, cambiare in modo da usare `acceleratore.device` invece di `device`). + +Dopodiché la maggior parte del lavoro è fatta dalla linea che invia i dataloaders, il modello e gli ottimizzatori a `accelerator.prepare()`. Ciò serve a incapsulare queli oggetti nei contenitori appropriati per far sì che l'addestramento distribuito funzioni correttamente. I cambiamenti rimanenti sono la rimozione della linea che sposta la batch sul `device` (dispositivo) (di nuovo, se volete tenerlo potete semplicemente cambiarlo con `accelerator.device`) e lo scambio di `loss.backward()` con `accelerator.backward(loss)`. + + +⚠️ Per poter beneficiare dell'accelerazione offerta da Cloud TPUs, è raccomandabile applicare padding ad una lunghezza fissa tramite gli argomenti `padding="max_length"` e `max_length` del tokenizer. + + +Se volete copiare e incollare il codice per giocarci, ecco un ciclo di addestramento completo che usa 🤗 Accelerate: + +```py +from accelerate import Accelerator +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +accelerator = Accelerator() + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +train_dl, eval_dl, model, optimizer = accelerator.prepare( + train_dataloader, eval_dataloader, model, optimizer +) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dl) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dl: + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +Mettere questo codice in uno script `train.py` lo renderà eseguibile su qualsiasi ambiente distribuito. Per provarlo nel vostro ambiente distribuito, eseguite: + +```bash +accelerate config +``` + +che vi chiederà di rispondere ad alcune domande e inserirà le vostre risposte in un documento di configurazione usato dal comando: + +``` +accelerate launch train.py +``` + +che eseguirà l'addestramento distribuito. + +Se volete provarlo in un Notebook (ad esempio, per testarlo con le TPUs su Colab), incollate il codice in una `training_function()` ed eseguite l'ultima cella con: + +```python +from accelerate import notebook_launcher + +notebook_launcher(training_function) +``` + +Potete trovare altri esempi nella [🤗 Accelerate repo](https://github.com/huggingface/accelerate/tree/main/examples). diff --git a/chapters/it/chapter3/5.mdx b/chapters/it/chapter3/5.mdx new file mode 100644 index 000000000..c96e3ce2b --- /dev/null +++ b/chapters/it/chapter3/5.mdx @@ -0,0 +1,20 @@ + + +# Affinamento, Fatto! + +Che divertimento. Nei primi due capitoli abbiamo scoperto i modelli e i tokenizzatori, e adesso sapete come affinarli per i vostri dati. Per riassumere, in questo capitolo avete: + +{#if fw === 'pt'} +* Scoperto i datasets nel [Hub](https://huggingface.co/datasets) +* Imparato come caricare e preprocessare i datasets, usando anche padding dinamico e funzioni di raccolta +* Implementato il vostro affinamento e valutazione di un modello +* Implementato un ciclo di addestramento di basso livello +* Usato 🤗 Accelerate per adattare in maniera semplice il vostro ciclo di addestramento all'utilizzo di svariate GPU o TPU + +{:else} +* Scoperto i datasets nel [Hub](https://huggingface.co/datasets) +* Imparato come caricare e preprocessare i datasets +* Imparato come affinare e valutare un modello con Keras +* Implementato una metric personalizzata + +{/if} diff --git a/chapters/it/chapter3/6.mdx b/chapters/it/chapter3/6.mdx new file mode 100644 index 000000000..80a43e0b7 --- /dev/null +++ b/chapters/it/chapter3/6.mdx @@ -0,0 +1,296 @@ + + + + +# Quiz di fine capitolo + +Testiamo cosa avete imparato in questo capitolo! + +### 1. Il dataset `emotion` contiene messaggi Twitter etichettati con emozioni. Cercalo nel [Hub](https://huggingface.co/datasets) e leggi la carta del dataset. Quale di queste non fa parte delle sue emozioni di base? + + + +### 2. Cerca il dataset `ar_sarcasm` nel [Hub](https://huggingface.co/datasets). Quali compiti supporta? + +dataset card!" + }, + { + text: "Named entity recognition (Riconoscimento di entità con un nome)", + explain: "Sbagliato — dai un'altra occhiata alla dataset card!" + }, + { + text: "Question answering (Risposte a domande)", + explain: "Purtroppo non hai risposto correttamente. Prova ancora!" + } + ]} +/> + +### 3. Come deve essere preparata una coppia di frasi per essere processata dal modello BERT? + +[SEP] è necessario per separare le due frasi, ma non è l'unica cosa!" + }, + { + text: "[CLS] Tokens_della_frase_1 Tokens_della_frase_2", + explain: "Un token speciale [CLS] è necessario all'inizio, ma non è l'unica cosa!" + }, + { + text: "[CLS] Tokens_della_frase_1 [SEP] Tokens_della_frase_2 [SEP]", + explain: "Corretto!", + correct: true + }, + { + text: "[CLS] Tokens_della_frase_1 [SEP] Tokens_della_frase_2", + explain: "Un token speciale [CLS] è necessario all'inizio e un token speciale [SEP] è necessario per separare le due frasi, ma non è l'unica cosa!" + } + ]} +/> + +{#if fw === 'pt'} +### 4. Quali sono i benefici del metodo `Dataset.map()`? + + + +### 5. Qual è il significato di padding dinamico (dynamic padding)? + + + +### 6. Qual è lo scopo di una funzione di raccolta (collate function)? + +DataCollatorWithPadding." + }, + { + text: "Di raccogliere tutti i campioni in una batch.", + explain: "Corretto! Puoi passare la funzione di raccolta come argomento di un DataLoader. Noi abbiamo usato la funzione DataCollatorWithPadding, che applica padding a tutti gli elementi di una batch affinché abbiano la stessa lunghezza.", + correct: true + }, + { + text: "Di preprocessare l'intero dataset", + explain: "Quella sarebbe una funzione di preprocessing, non di raccolta." + }, + { + text: "Di troncare le sequenze nel dataset.", + explain: "Una funzione di raccolta gestisce solo batch individuali, non l'intero dataset. Se siete interessati al troncamento, potete usare l'argomento truncate del tokenizer." + } + ]} +/> + +### 7. Cosa succede quando una classe di tipo `AutoModelForXxx` viene istanziata con un modello di linguaggio pre-addestrato (come `bert-base-uncased`) che corrisponde ad un compito differente rispetto a quello per cui era stato addestrato? + +AutoModelForSequenceClassification con bert-base-uncased, abbiamo ottenuto un avvertimento mentre il modello veniva istanziato. La testa pre-addestrata non viene usata per il compito di classificazione delle sequenze, ma viene scartata e una nuova testa viene istanziata con pesi casuali.", + correct: true + }, + { + text: "La testa del modello pre-addestrato viene scartata", + explain: "Deve succedere anche qualcos'altro. Prova ancora!" + }, + { + text: "Nulla, dato che il modello può comunque essere affinato per un compito differente.", + explain: "La testa del modello pre-addestrato non era stata addestrata per risolvere questo compito, quindi dovremmo scartarla!" + } + ]} +/> + +### 8. Qual è lo scopo di `TrainingArguments`? + +Trainer.", + explain: "Corretto!", + correct: true + }, + { + text: "Specifica le dimensioni del modello.", + explain: "Le dimensioni del modello sono definite dalla configurazione del modello, non dalla classe TrainingArguments." + }, + { + text: "Contiene soltanto gli iperparametri usati per la valutazione.", + explain: "Nell'esempio, abbiamo specificato anche dove salvare il modello e i suoi checkpoint. Prova ancora!" + }, + { + text: "Contiene soltanto gli iperparametri usati per l'addestramento.", + explain: "Nell'esempio, abbiamo usato anche una evaluation_strategy (stragia di valutazione). Prova ancora!" + } + ]} +/> + +### 9. Perché si dovrebbe usare la libreria 🤗 Accelerate? + +Trainer, non della libreria 🤗 Accelerate. Prova ancora!" + }, + { + text: "Permette ai nostri cicli di addestramento di venire eseguiti con strategie distribuite.", + explain: "Corretto! Con 🤗 Accelerate, i tuoi cicli di addestramento funzioneranno con svariate GPU e TPU.", + correct: true + }, + { + text: "Fornisce altre funzioni di ottimizzazione.", + explain: "No, la libreria 🤗 Accelerate library non fornisce alcuna funzione di ottimizzazione." + } + ]} +/> + +{:else} +### 4. Cosa succede quando una classe di tipo `TFAutoModelForXxx` viene istanziata con un modello di linguaggio pre-addestrato (come `bert-base-uncased`) che corrisponde ad un compito differente rispetto a quello per cui era stato addestrato? + +TFAutoModelForSequenceClassification con bert-base-uncased, abbiamo ottenuto un avvertimento mentre il modello veniva istanziato. La testa pre-addestrata non viene usata per il compito di classificazione delle sequenze, ma viene scartata e una nuova testa viene istanziata con pesi casuali.", + correct: true + }, + { + text: "La testa del modello pre-addestrato viene scartata", + explain: "Deve succedere anche qualcos'altro. Prova ancora!" + }, + { + text: "Nulla, dato che il modello può comunque essere affinato per un compito differente.", + explain: "La testa del modello pre-addestrato non era stata addestrata per risolvere questo compito, quindi dovremmo scartarla!" + } + ]} +/> + +### 5. I modelli Tensorflow da `transformers` sono già dei modelli Keras. Quali benefici offre ciò? + +TPUStrategy, incluso l'inizializzazione del modello" + }, + { + text: "Si possono sfruttare metodi già esistenti quali compile(), fit(), e predict().", + explain: "Correto! Una volta ottenuti i dati, l'addestramento richiede molto poco sforzo.", + correct: true + }, + { + text: "Puoi imparare Keras in aggiunta ai transformers.", + explain: "Corretto, anche se cercavamo qualcosa in pù :)", + correct: true + }, + { + text: "Si possono calcolare facilmente delle metriche relative al dataset", + explain: "Keras è d'aiuto nell'addestramento e valutazione del modello, non nel calcolare metriche relative al dataset." + } + ]} +/> + +### 6. Come si definisce una metrica personalizzata (custom metric)? + +tf.keras.metrics.Metric.", + explain: "Ottimo!", + correct: true + }, + { + text: "Usando l'API funzionale di Keras", + explain: "Prova ancora!" + }, + { + text: "Utilizzando una funzione con segnatura metric_fn(y_true, y_pred).", + explain: "Corretto!", + correct: true + }, + { + text: "Chiedendo a Google", + explain: "Non è la risposta che cercavamo, ma dovrebbe comunque aiutarvi a risolvere il problema.", + correct: true + } + ]} +/> + +{/if} From 14bab3faa8ed5cc8cbf967263a0aefcbfaf9eb45 Mon Sep 17 00:00:00 2001 From: lewtun Date: Tue, 13 Sep 2022 15:24:12 +0200 Subject: [PATCH 31/51] Bump release (#314) --- README.md | 2 +- chapters/de/chapter3/2.mdx | 8 +- chapters/de/chapter3/3.mdx | 4 +- chapters/de/chapter3/3_tf.mdx | 4 +- chapters/de/chapter3/4.mdx | 4 +- chapters/en/chapter1/3.mdx | 4 +- chapters/en/chapter1/8.mdx | 4 +- chapters/en/chapter2/2.mdx | 8 +- chapters/en/chapter2/3.mdx | 8 +- chapters/en/chapter2/4.mdx | 8 +- chapters/en/chapter2/5.mdx | 8 +- chapters/en/chapter2/6.mdx | 8 +- chapters/en/chapter3/2.mdx | 8 +- chapters/en/chapter3/3.mdx | 4 +- chapters/en/chapter3/3_tf.mdx | 4 +- chapters/en/chapter3/4.mdx | 4 +- chapters/en/chapter4/2.mdx | 8 +- chapters/en/chapter4/3.mdx | 8 +- chapters/en/chapter5/2.mdx | 4 +- chapters/en/chapter5/3.mdx | 4 +- chapters/en/chapter5/4.mdx | 4 +- chapters/en/chapter5/5.mdx | 4 +- chapters/en/chapter5/6.mdx | 8 +- chapters/en/chapter6/2.mdx | 4 +- chapters/en/chapter6/3.mdx | 8 +- chapters/en/chapter6/3b.mdx | 8 +- chapters/en/chapter6/4.mdx | 4 +- chapters/en/chapter6/5.mdx | 4 +- chapters/en/chapter6/6.mdx | 4 +- chapters/en/chapter6/7.mdx | 4 +- chapters/en/chapter6/8.mdx | 4 +- chapters/en/chapter7/2.mdx | 8 +- chapters/en/chapter7/3.mdx | 8 +- chapters/en/chapter7/4.mdx | 8 +- chapters/en/chapter7/5.mdx | 8 +- chapters/en/chapter7/6.mdx | 8 +- chapters/en/chapter7/7.mdx | 8 +- chapters/en/chapter8/2.mdx | 4 +- chapters/en/chapter8/3.mdx | 4 +- chapters/en/chapter8/4.mdx | 4 +- chapters/en/chapter8/4_tf.mdx | 4 +- chapters/en/chapter8/5.mdx | 4 +- chapters/en/chapter9/2.mdx | 4 +- chapters/en/chapter9/3.mdx | 4 +- chapters/en/chapter9/4.mdx | 4 +- chapters/en/chapter9/5.mdx | 4 +- chapters/en/chapter9/6.mdx | 4 +- chapters/en/chapter9/7.mdx | 4 +- chapters/es/chapter1/3.mdx | 4 +- chapters/es/chapter1/8.mdx | 4 +- chapters/es/chapter2/4.mdx | 8 +- chapters/es/chapter2/5.mdx | 8 +- chapters/es/chapter3/2.mdx | 8 +- chapters/es/chapter3/4.mdx | 4 +- chapters/es/chapter8/2.mdx | 4 +- chapters/fa/chapter2/2.mdx | 8 +- chapters/fa/chapter2/3.mdx | 8 +- chapters/fa/chapter3/2.mdx | 8 +- chapters/fa/chapter4/2.mdx | 8 +- chapters/fr/chapter1/3.mdx | 4 +- chapters/fr/chapter1/8.mdx | 4 +- chapters/fr/chapter2/2.mdx | 8 +- chapters/fr/chapter2/3.mdx | 8 +- chapters/fr/chapter2/4.mdx | 8 +- chapters/fr/chapter2/5.mdx | 8 +- chapters/fr/chapter2/6.mdx | 8 +- chapters/fr/chapter3/2.mdx | 8 +- chapters/fr/chapter3/3.mdx | 4 +- chapters/fr/chapter3/3_tf.mdx | 4 +- chapters/fr/chapter3/4.mdx | 4 +- chapters/fr/chapter4/2.mdx | 8 +- chapters/fr/chapter4/3.mdx | 8 +- chapters/fr/chapter5/2.mdx | 4 +- chapters/fr/chapter5/3.mdx | 4 +- chapters/fr/chapter5/4.mdx | 4 +- chapters/fr/chapter5/5.mdx | 4 +- chapters/fr/chapter5/6.mdx | 8 +- chapters/fr/chapter6/2.mdx | 4 +- chapters/fr/chapter6/3.mdx | 8 +- chapters/fr/chapter6/3b.mdx | 8 +- chapters/fr/chapter6/4.mdx | 4 +- chapters/fr/chapter6/5.mdx | 4 +- chapters/fr/chapter6/6.mdx | 4 +- chapters/fr/chapter6/7.mdx | 4 +- chapters/fr/chapter6/8.mdx | 4 +- chapters/fr/chapter7/2.mdx | 8 +- chapters/fr/chapter7/3.mdx | 8 +- chapters/fr/chapter7/4.mdx | 8 +- chapters/fr/chapter7/5.mdx | 8 +- chapters/fr/chapter7/6.mdx | 8 +- chapters/fr/chapter7/7.mdx | 8 +- chapters/fr/chapter8/2.mdx | 4 +- chapters/fr/chapter8/3.mdx | 4 +- chapters/fr/chapter8/4.mdx | 4 +- chapters/fr/chapter8/4_tf.mdx | 4 +- chapters/fr/chapter8/5.mdx | 4 +- chapters/fr/chapter9/2.mdx | 4 +- chapters/fr/chapter9/3.mdx | 4 +- chapters/fr/chapter9/4.mdx | 25 +- chapters/fr/chapter9/5.mdx | 4 +- chapters/fr/chapter9/6.mdx | 4 +- chapters/fr/chapter9/7.mdx | 4 +- chapters/hi/chapter1/3.mdx | 4 +- chapters/hi/chapter1/8.mdx | 4 +- chapters/hi/chapter3/2.mdx | 8 +- chapters/hi/chapter3/3.mdx | 4 +- chapters/hi/chapter3/3_tf.mdx | 4 +- chapters/hi/chapter3/4.mdx | 4 +- chapters/it/chapter1/3.mdx | 4 +- chapters/it/chapter1/8.mdx | 4 +- chapters/it/chapter2/2.mdx | 8 +- chapters/it/chapter3/2.mdx | 8 +- chapters/it/chapter3/3.mdx | 4 +- chapters/it/chapter3/3_tf.mdx | 4 +- chapters/it/chapter3/4.mdx | 4 +- chapters/it/chapter4/2.mdx | 8 +- chapters/it/chapter4/3.mdx | 8 +- chapters/it/chapter5/2.mdx | 4 +- chapters/it/chapter5/3.mdx | 4 +- chapters/it/chapter5/4.mdx | 4 +- chapters/it/chapter5/5.mdx | 4 +- chapters/it/chapter5/6.mdx | 8 +- chapters/it/chapter8/2.mdx | 4 +- chapters/it/chapter8/3.mdx | 4 +- chapters/it/chapter8/4.mdx | 4 +- chapters/it/chapter8/4_tf.mdx | 4 +- chapters/it/chapter8/5.mdx | 4 +- chapters/ja/chapter4/2.mdx | 8 +- chapters/ja/chapter4/3.mdx | 8 +- chapters/ja/chapter7/2.mdx | 8 +- chapters/ja/chapter7/3.mdx | 8 +- chapters/ja/chapter7/4.mdx | 8 +- chapters/ja/chapter7/5.mdx | 8 +- chapters/ja/chapter7/6.mdx | 8 +- chapters/ja/chapter7/7.mdx | 8 +- chapters/ja/chapter8/2.mdx | 4 +- chapters/ko/chapter1/3.mdx | 4 +- chapters/ko/chapter1/8.mdx | 4 +- chapters/pt/chapter1/3.mdx | 4 +- chapters/pt/chapter1/8.mdx | 2 +- chapters/pt/chapter2/2.mdx | 8 +- chapters/pt/chapter2/3.mdx | 8 +- chapters/pt/chapter2/4.mdx | 8 +- chapters/pt/chapter2/5.mdx | 8 +- chapters/pt/chapter2/6.mdx | 8 +- chapters/pt/chapter4/2.mdx | 8 +- chapters/pt/chapter4/3.mdx | 8 +- chapters/pt/chapter5/2.mdx | 4 +- chapters/pt/chapter5/3.mdx | 4 +- chapters/pt/chapter5/4.mdx | 4 +- chapters/pt/chapter5/5.mdx | 4 +- chapters/pt/chapter5/6.mdx | 8 +- chapters/pt/chapter6/2.mdx | 4 +- chapters/pt/chapter6/3.mdx | 8 +- chapters/pt/chapter8/2.mdx | 4 +- chapters/pt/chapter8/3.mdx | 4 +- chapters/ru/chapter1/3.mdx | 4 +- chapters/ru/chapter1/8.mdx | 4 +- chapters/ru/chapter2/2.mdx | 8 +- chapters/ru/chapter2/3.mdx | 8 +- chapters/ru/chapter3/2.mdx | 8 +- chapters/ru/chapter3/3.mdx | 4 +- chapters/ru/chapter3/3_tf.mdx | 4 +- chapters/ru/chapter3/4.mdx | 4 +- chapters/ru/chapter4/2.mdx | 8 +- chapters/ru/chapter4/3.mdx | 8 +- chapters/th/chapter1/3.mdx | 4 +- chapters/th/chapter1/8.mdx | 4 +- chapters/th/chapter2/2.mdx | 8 +- chapters/th/chapter2/3.mdx | 8 +- chapters/th/chapter2/4.mdx | 8 +- chapters/th/chapter2/5.mdx | 8 +- chapters/th/chapter2/6.mdx | 8 +- chapters/th/chapter3/2.mdx | 8 +- chapters/th/chapter3/3.mdx | 4 +- chapters/th/chapter3/3_tf.mdx | 4 +- chapters/th/chapter3/4.mdx | 4 +- chapters/th/chapter4/2.mdx | 8 +- chapters/th/chapter4/3.mdx | 8 +- chapters/th/chapter6/2.mdx | 4 +- chapters/th/chapter6/3.mdx | 8 +- chapters/th/chapter6/3b.mdx | 8 +- chapters/th/chapter6/4.mdx | 4 +- chapters/th/chapter6/5.mdx | 4 +- chapters/th/chapter6/6.mdx | 4 +- chapters/th/chapter6/7.mdx | 4 +- chapters/th/chapter6/8.mdx | 4 +- chapters/vi/chapter1/3.mdx | 4 +- chapters/vi/chapter1/8.mdx | 4 +- chapters/vi/chapter2/2.mdx | 8 +- chapters/vi/chapter2/3.mdx | 8 +- chapters/vi/chapter2/4.mdx | 8 +- chapters/vi/chapter2/5.mdx | 8 +- chapters/vi/chapter2/6.mdx | 8 +- chapters/vi/chapter3/2.mdx | 8 +- chapters/vi/chapter3/3.mdx | 4 +- chapters/vi/chapter3/3_tf.mdx | 4 +- chapters/vi/chapter3/4.mdx | 4 +- chapters/vi/chapter4/2.mdx | 8 +- chapters/vi/chapter4/3.mdx | 8 +- chapters/vi/chapter5/2.mdx | 4 +- chapters/vi/chapter5/3.mdx | 4 +- chapters/vi/chapter5/4.mdx | 4 +- chapters/vi/chapter5/5.mdx | 4 +- chapters/vi/chapter5/6.mdx | 8 +- chapters/vi/chapter6/10.md | 283 -------------- chapters/vi/chapter6/2.mdx | 4 +- chapters/vi/chapter6/3.md | 473 ----------------------- chapters/vi/chapter6/3.mdx | 8 +- chapters/vi/chapter6/3b.md | 642 ------------------------------- chapters/vi/chapter6/3b.mdx | 8 +- chapters/vi/chapter6/4.md | 123 ------ chapters/vi/chapter6/4.mdx | 4 +- chapters/vi/chapter6/5.md | 360 ----------------- chapters/vi/chapter6/5.mdx | 4 +- chapters/vi/chapter6/6.md | 374 ------------------ chapters/vi/chapter6/6.mdx | 4 +- chapters/vi/chapter6/7.md | 381 ------------------ chapters/vi/chapter6/7.mdx | 4 +- chapters/vi/chapter6/8.md | 565 --------------------------- chapters/vi/chapter6/8.mdx | 4 +- chapters/vi/chapter7/2.mdx | 8 +- chapters/vi/chapter7/3.mdx | 8 +- chapters/vi/chapter7/4.mdx | 8 +- chapters/vi/chapter7/5.mdx | 8 +- chapters/vi/chapter7/6.mdx | 8 +- chapters/vi/chapter7/7.mdx | 8 +- chapters/vi/chapter8/2.mdx | 4 +- chapters/vi/chapter8/3.mdx | 4 +- chapters/vi/chapter8/4.mdx | 4 +- chapters/vi/chapter8/4_tf.mdx | 4 +- chapters/vi/chapter8/5.mdx | 4 +- chapters/vi/chapter9/2.mdx | 4 +- chapters/vi/chapter9/3.mdx | 4 +- chapters/vi/chapter9/4.mdx | 4 +- chapters/vi/chapter9/5.mdx | 4 +- chapters/vi/chapter9/6.mdx | 4 +- chapters/vi/chapter9/7.mdx | 4 +- chapters/zh-CN/chapter1/3.mdx | 4 +- chapters/zh-CN/chapter1/8.mdx | 4 +- chapters/zh-CN/chapter2/2.mdx | 8 +- chapters/zh-CN/chapter2/3.mdx | 8 +- chapters/zh-CN/chapter2/4.mdx | 8 +- chapters/zh-CN/chapter2/5.mdx | 8 +- chapters/zh-CN/chapter2/6.mdx | 8 +- chapters/zh-CN/chapter3/2.mdx | 8 +- chapters/zh-CN/chapter3/3.mdx | 4 +- chapters/zh-CN/chapter3/3_tf.mdx | 4 +- chapters/zh-CN/chapter3/4.mdx | 4 +- chapters/zh-CN/chapter4/2.mdx | 8 +- chapters/zh-CN/chapter4/3.mdx | 8 +- chapters/zh-CN/chapter5/2.mdx | 4 +- chapters/zh-CN/chapter5/3.mdx | 4 +- chapters/zh-CN/chapter5/4.mdx | 4 +- chapters/zh-CN/chapter5/5.mdx | 4 +- chapters/zh-CN/chapter5/6.mdx | 8 +- chapters/zh-CN/chapter6/2.mdx | 4 +- chapters/zh-CN/chapter6/3.mdx | 8 +- chapters/zh-CN/chapter6/3b.mdx | 8 +- chapters/zh-CN/chapter6/4.mdx | 4 +- chapters/zh-CN/chapter6/5.mdx | 4 +- chapters/zh-CN/chapter6/6.mdx | 4 +- chapters/zh-CN/chapter6/7.mdx | 4 +- chapters/zh-CN/chapter6/8.mdx | 4 +- utils/generate_notebooks.py | 105 ++--- 265 files changed, 796 insertions(+), 3983 deletions(-) delete mode 100644 chapters/vi/chapter6/10.md delete mode 100644 chapters/vi/chapter6/3.md delete mode 100644 chapters/vi/chapter6/3b.md delete mode 100644 chapters/vi/chapter6/4.md delete mode 100644 chapters/vi/chapter6/5.md delete mode 100644 chapters/vi/chapter6/6.md delete mode 100644 chapters/vi/chapter6/7.md delete mode 100644 chapters/vi/chapter6/8.md diff --git a/README.md b/README.md index 49654517c..facab3706 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ Then run the following script: python utils/generate_notebooks.py --output_dir nbs ``` -This script extracts all the code snippets from the English chapters and stores them as notebooks in the `nbs` folder (which is ignored by Git by default). +This script extracts all the code snippets from the chapters and stores them as notebooks in the `nbs` folder (which is ignored by Git by default). ## ✍️ Contributing a new chapter diff --git a/chapters/de/chapter3/2.mdx b/chapters/de/chapter3/2.mdx index 9248e9b0f..49a025c1a 100644 --- a/chapters/de/chapter3/2.mdx +++ b/chapters/de/chapter3/2.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/de/chapter3/3.mdx b/chapters/de/chapter3/3.mdx index 466c25d29..202251577 100644 --- a/chapters/de/chapter3/3.mdx +++ b/chapters/de/chapter3/3.mdx @@ -5,8 +5,8 @@ diff --git a/chapters/de/chapter3/3_tf.mdx b/chapters/de/chapter3/3_tf.mdx index 686d8bb71..970d06835 100644 --- a/chapters/de/chapter3/3_tf.mdx +++ b/chapters/de/chapter3/3_tf.mdx @@ -5,8 +5,8 @@ Wenn du die Datenvorverarbeitung im letzten Abschnitt abgeschlossen hast, brauchst es nur noch wenige Schritte, um das Modell zu trainieren. Beachte jedoch, dass der Befehl `model.fit()` auf einer CPU sehr langsam läuft. Wenn du keinen GPU hast, kannst du auf [Google Colab] (https://colab.research.google.com/) kostenlos auf GPUs und TPUs zugreifen. diff --git a/chapters/de/chapter3/4.mdx b/chapters/de/chapter3/4.mdx index 2eaca08e8..c5f9b89bc 100644 --- a/chapters/de/chapter3/4.mdx +++ b/chapters/de/chapter3/4.mdx @@ -3,8 +3,8 @@ diff --git a/chapters/en/chapter1/3.mdx b/chapters/en/chapter1/3.mdx index d25513cd4..529078538 100644 --- a/chapters/en/chapter1/3.mdx +++ b/chapters/en/chapter1/3.mdx @@ -3,8 +3,8 @@ In this section, we will look at what Transformer models can do and use our first tool from the 🤗 Transformers library: the `pipeline()` function. diff --git a/chapters/en/chapter1/8.mdx b/chapters/en/chapter1/8.mdx index 08135867b..8b221b9b7 100644 --- a/chapters/en/chapter1/8.mdx +++ b/chapters/en/chapter1/8.mdx @@ -3,8 +3,8 @@ If your intent is to use a pretrained model or a fine-tuned version in production, please be aware that, while these models are powerful tools, they come with limitations. The biggest of these is that, to enable pretraining on large amounts of data, researchers often scrape all the content they can find, taking the best as well as the worst of what is available on the internet. diff --git a/chapters/en/chapter2/2.mdx b/chapters/en/chapter2/2.mdx index d65e4983e..e0be8610e 100644 --- a/chapters/en/chapter2/2.mdx +++ b/chapters/en/chapter2/2.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/en/chapter2/3.mdx b/chapters/en/chapter2/3.mdx index 61e60b564..33ef89bcb 100644 --- a/chapters/en/chapter2/3.mdx +++ b/chapters/en/chapter2/3.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/en/chapter2/4.mdx b/chapters/en/chapter2/4.mdx index 9699ef2fc..4545d2403 100644 --- a/chapters/en/chapter2/4.mdx +++ b/chapters/en/chapter2/4.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/en/chapter2/5.mdx b/chapters/en/chapter2/5.mdx index a268b4ce5..273f73a05 100644 --- a/chapters/en/chapter2/5.mdx +++ b/chapters/en/chapter2/5.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/en/chapter2/6.mdx b/chapters/en/chapter2/6.mdx index 49e9e0bca..27322c765 100644 --- a/chapters/en/chapter2/6.mdx +++ b/chapters/en/chapter2/6.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/en/chapter3/2.mdx b/chapters/en/chapter3/2.mdx index dd340b738..8f8920cce 100644 --- a/chapters/en/chapter3/2.mdx +++ b/chapters/en/chapter3/2.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/en/chapter3/3.mdx b/chapters/en/chapter3/3.mdx index 4420d705b..85ec90cf4 100644 --- a/chapters/en/chapter3/3.mdx +++ b/chapters/en/chapter3/3.mdx @@ -5,8 +5,8 @@ diff --git a/chapters/en/chapter3/3_tf.mdx b/chapters/en/chapter3/3_tf.mdx index 72f84ba20..b7bdc0a33 100644 --- a/chapters/en/chapter3/3_tf.mdx +++ b/chapters/en/chapter3/3_tf.mdx @@ -5,8 +5,8 @@ Once you've done all the data preprocessing work in the last section, you have just a few steps left to train the model. Note, however, that the `model.fit()` command will run very slowly on a CPU. If you don't have a GPU set up, you can get access to free GPUs or TPUs on [Google Colab](https://colab.research.google.com/). diff --git a/chapters/en/chapter3/4.mdx b/chapters/en/chapter3/4.mdx index 53550b40c..23688ea24 100644 --- a/chapters/en/chapter3/4.mdx +++ b/chapters/en/chapter3/4.mdx @@ -3,8 +3,8 @@ diff --git a/chapters/en/chapter4/2.mdx b/chapters/en/chapter4/2.mdx index bca54f883..7a36c39dd 100644 --- a/chapters/en/chapter4/2.mdx +++ b/chapters/en/chapter4/2.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/en/chapter4/3.mdx b/chapters/en/chapter4/3.mdx index 3fb6e0a5c..6dd9ee601 100644 --- a/chapters/en/chapter4/3.mdx +++ b/chapters/en/chapter4/3.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/en/chapter5/2.mdx b/chapters/en/chapter5/2.mdx index 9b9c84d05..70a36769a 100644 --- a/chapters/en/chapter5/2.mdx +++ b/chapters/en/chapter5/2.mdx @@ -3,8 +3,8 @@ You know how to use the [Hugging Face Hub](https://huggingface.co/datasets) to download datasets, but you'll often find yourself working with data that is stored either on your laptop or on a remote server. In this section we'll show you how 🤗 Datasets can be used to load datasets that aren't available on the Hugging Face Hub. diff --git a/chapters/en/chapter5/3.mdx b/chapters/en/chapter5/3.mdx index 075a88ceb..a64a884c4 100644 --- a/chapters/en/chapter5/3.mdx +++ b/chapters/en/chapter5/3.mdx @@ -3,8 +3,8 @@ Most of the time, the data you work with won't be perfectly prepared for training models. In this section we'll explore the various features that 🤗 Datasets provides to clean up your datasets. diff --git a/chapters/en/chapter5/4.mdx b/chapters/en/chapter5/4.mdx index 7de820546..58061e67a 100644 --- a/chapters/en/chapter5/4.mdx +++ b/chapters/en/chapter5/4.mdx @@ -3,8 +3,8 @@ diff --git a/chapters/en/chapter5/5.mdx b/chapters/en/chapter5/5.mdx index 363afacbc..492f73be1 100644 --- a/chapters/en/chapter5/5.mdx +++ b/chapters/en/chapter5/5.mdx @@ -3,8 +3,8 @@ Sometimes the dataset that you need to build an NLP application doesn't exist, so you'll need to create it yourself. In this section we'll show you how to create a corpus of [GitHub issues](https://github.com/features/issues/), which are commonly used to track bugs or features in GitHub repositories. This corpus could be used for various purposes, including: diff --git a/chapters/en/chapter5/6.mdx b/chapters/en/chapter5/6.mdx index b2aa21057..47c72c99f 100644 --- a/chapters/en/chapter5/6.mdx +++ b/chapters/en/chapter5/6.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/en/chapter6/2.mdx b/chapters/en/chapter6/2.mdx index 4effa7320..2d41e77c7 100644 --- a/chapters/en/chapter6/2.mdx +++ b/chapters/en/chapter6/2.mdx @@ -3,8 +3,8 @@ If a language model is not available in the language you are interested in, or if your corpus is very different from the one your language model was trained on, you will most likely want to retrain the model from scratch using a tokenizer adapted to your data. That will require training a new tokenizer on your dataset. But what exactly does that mean? When we first looked at tokenizers in [Chapter 2](/course/chapter2), we saw that most Transformer models use a _subword tokenization algorithm_. To identify which subwords are of interest and occur most frequently in the corpus at hand, the tokenizer needs to take a hard look at all the texts in the corpus -- a process we call *training*. The exact rules that govern this training depend on the type of tokenizer used, and we'll go over the three main algorithms later in this chapter. diff --git a/chapters/en/chapter6/3.mdx b/chapters/en/chapter6/3.mdx index 4f9bd30db..0aa5aebb7 100644 --- a/chapters/en/chapter6/3.mdx +++ b/chapters/en/chapter6/3.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/en/chapter6/3b.mdx b/chapters/en/chapter6/3b.mdx index 6b8ccd02f..1f2ef4eda 100644 --- a/chapters/en/chapter6/3b.mdx +++ b/chapters/en/chapter6/3b.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/en/chapter6/4.mdx b/chapters/en/chapter6/4.mdx index 58a68f23b..e42c6597d 100644 --- a/chapters/en/chapter6/4.mdx +++ b/chapters/en/chapter6/4.mdx @@ -3,8 +3,8 @@ Before we dive more deeply into the three most common subword tokenization algorithms used with Transformer models (Byte-Pair Encoding [BPE], WordPiece, and Unigram), we'll first take a look at the preprocessing that each tokenizer applies to text. Here's a high-level overview of the steps in the tokenization pipeline: diff --git a/chapters/en/chapter6/5.mdx b/chapters/en/chapter6/5.mdx index 18b189bc5..68372b136 100644 --- a/chapters/en/chapter6/5.mdx +++ b/chapters/en/chapter6/5.mdx @@ -3,8 +3,8 @@ Byte-Pair Encoding (BPE) was initially developed as an algorithm to compress texts, and then used by OpenAI for tokenization when pretraining the GPT model. It's used by a lot of Transformer models, including GPT, GPT-2, RoBERTa, BART, and DeBERTa. diff --git a/chapters/en/chapter6/6.mdx b/chapters/en/chapter6/6.mdx index 7e60dabb0..6db222f36 100644 --- a/chapters/en/chapter6/6.mdx +++ b/chapters/en/chapter6/6.mdx @@ -3,8 +3,8 @@ WordPiece is the tokenization algorithm Google developed to pretrain BERT. It has since been reused in quite a few Transformer models based on BERT, such as DistilBERT, MobileBERT, Funnel Transformers, and MPNET. It's very similar to BPE in terms of the training, but the actual tokenization is done differently. diff --git a/chapters/en/chapter6/7.mdx b/chapters/en/chapter6/7.mdx index 2a4d6f2f3..735d1d401 100644 --- a/chapters/en/chapter6/7.mdx +++ b/chapters/en/chapter6/7.mdx @@ -3,8 +3,8 @@ The Unigram algorithm is often used in SentencePiece, which is the tokenization algorithm used by models like AlBERT, T5, mBART, Big Bird, and XLNet. diff --git a/chapters/en/chapter6/8.mdx b/chapters/en/chapter6/8.mdx index d831eb239..4fde33c7d 100644 --- a/chapters/en/chapter6/8.mdx +++ b/chapters/en/chapter6/8.mdx @@ -3,8 +3,8 @@ As we've seen in the previous sections, tokenization comprises several steps: diff --git a/chapters/en/chapter7/2.mdx b/chapters/en/chapter7/2.mdx index b052dc207..16c13770f 100644 --- a/chapters/en/chapter7/2.mdx +++ b/chapters/en/chapter7/2.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/en/chapter7/3.mdx b/chapters/en/chapter7/3.mdx index 63eeba0a9..bcaab1b45 100644 --- a/chapters/en/chapter7/3.mdx +++ b/chapters/en/chapter7/3.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/en/chapter7/4.mdx b/chapters/en/chapter7/4.mdx index cc0a41c49..73427bcd5 100644 --- a/chapters/en/chapter7/4.mdx +++ b/chapters/en/chapter7/4.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/en/chapter7/5.mdx b/chapters/en/chapter7/5.mdx index f66a4f429..6b2fa1cf0 100644 --- a/chapters/en/chapter7/5.mdx +++ b/chapters/en/chapter7/5.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/en/chapter7/6.mdx b/chapters/en/chapter7/6.mdx index e113b9423..09f1cefcb 100644 --- a/chapters/en/chapter7/6.mdx +++ b/chapters/en/chapter7/6.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/en/chapter7/7.mdx b/chapters/en/chapter7/7.mdx index 2dd81123a..f103c7764 100644 --- a/chapters/en/chapter7/7.mdx +++ b/chapters/en/chapter7/7.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/en/chapter8/2.mdx b/chapters/en/chapter8/2.mdx index 20dac3a77..08ba744b5 100644 --- a/chapters/en/chapter8/2.mdx +++ b/chapters/en/chapter8/2.mdx @@ -3,8 +3,8 @@ In this section we'll look at some common errors that can occur when you're trying to generate predictions from your freshly tuned Transformer model. This will prepare you for [section 4](/course/chapter8/section4), where we'll explore how to debug the training phase itself. diff --git a/chapters/en/chapter8/3.mdx b/chapters/en/chapter8/3.mdx index 0dd7c2ec3..d38b26669 100644 --- a/chapters/en/chapter8/3.mdx +++ b/chapters/en/chapter8/3.mdx @@ -3,8 +3,8 @@ diff --git a/chapters/en/chapter8/4.mdx b/chapters/en/chapter8/4.mdx index 8d2306321..94adcb60c 100644 --- a/chapters/en/chapter8/4.mdx +++ b/chapters/en/chapter8/4.mdx @@ -5,8 +5,8 @@ You've written a beautiful script to train or fine-tune a model on a given task, dutifully following the advice from [Chapter 7](/course/chapter7). But when you launch the command `trainer.train()`, something horrible happens: you get an error 😱! Or worse, everything seems to be fine and the training runs without error, but the resulting model is crappy. In this section, we will show you what you can do to debug these kinds of issues. diff --git a/chapters/en/chapter8/4_tf.mdx b/chapters/en/chapter8/4_tf.mdx index a9f595b48..0e9a9e822 100644 --- a/chapters/en/chapter8/4_tf.mdx +++ b/chapters/en/chapter8/4_tf.mdx @@ -5,8 +5,8 @@ You've written a beautiful script to train or fine-tune a model on a given task, dutifully following the advice from [Chapter 7](/course/chapter7). But when you launch the command `model.fit()`, something horrible happens: you get an error 😱! Or worse, everything seems to be fine and the training runs without error, but the resulting model is crappy. In this section, we will show you what you can do to debug these kinds of issues. diff --git a/chapters/en/chapter8/5.mdx b/chapters/en/chapter8/5.mdx index 51f71c15e..99a4c7b4b 100644 --- a/chapters/en/chapter8/5.mdx +++ b/chapters/en/chapter8/5.mdx @@ -3,8 +3,8 @@ When you encounter something that doesn't seem right with one of the Hugging Face libraries, you should definitely let us know so we can fix it (the same goes for any open source library, for that matter). If you are not completely certain whether the bug lies in your own code or one of our libraries, the first place to check is the [forums](https://discuss.huggingface.co/). The community will help you figure this out, and the Hugging Face team also closely watches the discussions there. diff --git a/chapters/en/chapter9/2.mdx b/chapters/en/chapter9/2.mdx index 3cf23d896..8dee73747 100644 --- a/chapters/en/chapter9/2.mdx +++ b/chapters/en/chapter9/2.mdx @@ -3,8 +3,8 @@ Let's start by installing Gradio! Since it is a Python package, simply run: diff --git a/chapters/en/chapter9/3.mdx b/chapters/en/chapter9/3.mdx index bd829ec95..b3f18a27a 100644 --- a/chapters/en/chapter9/3.mdx +++ b/chapters/en/chapter9/3.mdx @@ -3,8 +3,8 @@ In this section, we will take a closer look at the `Interface` class, and understand the diff --git a/chapters/en/chapter9/4.mdx b/chapters/en/chapter9/4.mdx index fc31404d0..2dcd458e6 100644 --- a/chapters/en/chapter9/4.mdx +++ b/chapters/en/chapter9/4.mdx @@ -3,8 +3,8 @@ Now that you've built a demo, you'll probably want to share it with others. Gradio demos diff --git a/chapters/en/chapter9/5.mdx b/chapters/en/chapter9/5.mdx index b7c727a7f..b7d61d08a 100644 --- a/chapters/en/chapter9/5.mdx +++ b/chapters/en/chapter9/5.mdx @@ -3,8 +3,8 @@ To make your life even easier, Gradio integrates directly with Hugging Face Hub and Hugging Face Spaces. diff --git a/chapters/en/chapter9/6.mdx b/chapters/en/chapter9/6.mdx index ce4ee3ecc..74ba03a08 100644 --- a/chapters/en/chapter9/6.mdx +++ b/chapters/en/chapter9/6.mdx @@ -3,8 +3,8 @@ Now that we can build and share a basic interface, let's explore some more advanced features such as state, and interpretation. diff --git a/chapters/en/chapter9/7.mdx b/chapters/en/chapter9/7.mdx index eb0fd3f61..3dc2bf4ca 100644 --- a/chapters/en/chapter9/7.mdx +++ b/chapters/en/chapter9/7.mdx @@ -3,8 +3,8 @@ In the previous sections we have explored and created demos using the `Interface` class. In this section we will introduce our **newly developed** low-level API called `gradio.Blocks`. diff --git a/chapters/es/chapter1/3.mdx b/chapters/es/chapter1/3.mdx index 539f62dca..b0b42ade6 100644 --- a/chapters/es/chapter1/3.mdx +++ b/chapters/es/chapter1/3.mdx @@ -3,8 +3,8 @@ En esta sección, veremos qué pueden hacer los Transformadores y usaremos nuestra primera herramienta de la librería 🤗 Transformers: la función `pipeline()`. diff --git a/chapters/es/chapter1/8.mdx b/chapters/es/chapter1/8.mdx index aae4180a9..818575337 100644 --- a/chapters/es/chapter1/8.mdx +++ b/chapters/es/chapter1/8.mdx @@ -3,8 +3,8 @@ Si tu intención es usar modelos preentrenados o una versión ajustada en producción, ten en cuenta que a pesar de ser herramientas poderosas, tienen limitaciones. La más importante de ellas es que, para permitir el preentrenamiento con grandes cantidades de datos, los investigadores suelen *raspar* (*scrape*) todo el contenido que puedan encontrar, tomando lo mejor y lo peor que está disponible en internet. diff --git a/chapters/es/chapter2/4.mdx b/chapters/es/chapter2/4.mdx index b53535f03..b20685498 100644 --- a/chapters/es/chapter2/4.mdx +++ b/chapters/es/chapter2/4.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/es/chapter2/5.mdx b/chapters/es/chapter2/5.mdx index 366b53839..9a4cb0f8d 100644 --- a/chapters/es/chapter2/5.mdx +++ b/chapters/es/chapter2/5.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/es/chapter3/2.mdx b/chapters/es/chapter3/2.mdx index df59b8a2c..eded26291 100644 --- a/chapters/es/chapter3/2.mdx +++ b/chapters/es/chapter3/2.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/es/chapter3/4.mdx b/chapters/es/chapter3/4.mdx index 96e07b050..8d4e84e8d 100644 --- a/chapters/es/chapter3/4.mdx +++ b/chapters/es/chapter3/4.mdx @@ -3,8 +3,8 @@ diff --git a/chapters/es/chapter8/2.mdx b/chapters/es/chapter8/2.mdx index 0e7553eae..8386daa25 100644 --- a/chapters/es/chapter8/2.mdx +++ b/chapters/es/chapter8/2.mdx @@ -3,8 +3,8 @@ En esta sección veremos algunos errores comunes que pueden ocurrir cuando intentas generar predicciones a partir de tu modelo Transformer recién afinado. Esto te preparará para la [sección 4](/course/chapter8/section4), en la que exploraremos cómo depurar (debug) la fase de entrenamiento. diff --git a/chapters/fa/chapter2/2.mdx b/chapters/fa/chapter2/2.mdx index 9ae1d64e2..4066e727b 100644 --- a/chapters/fa/chapter2/2.mdx +++ b/chapters/fa/chapter2/2.mdx @@ -8,8 +8,8 @@ {:else} @@ -17,8 +17,8 @@ {/if} diff --git a/chapters/fa/chapter2/3.mdx b/chapters/fa/chapter2/3.mdx index eace2dc12..cff6876fd 100644 --- a/chapters/fa/chapter2/3.mdx +++ b/chapters/fa/chapter2/3.mdx @@ -8,8 +8,8 @@ {:else} @@ -17,8 +17,8 @@ {/if} diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index deabbc5f0..d553572ad 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -9,8 +9,8 @@ {:else} @@ -18,8 +18,8 @@ {/if} diff --git a/chapters/fa/chapter4/2.mdx b/chapters/fa/chapter4/2.mdx index 2514c1849..e47a6db83 100644 --- a/chapters/fa/chapter4/2.mdx +++ b/chapters/fa/chapter4/2.mdx @@ -8,8 +8,8 @@ {:else} @@ -17,8 +17,8 @@ {/if} diff --git a/chapters/fr/chapter1/3.mdx b/chapters/fr/chapter1/3.mdx index d10b714e2..0b0a1b0b4 100644 --- a/chapters/fr/chapter1/3.mdx +++ b/chapters/fr/chapter1/3.mdx @@ -3,8 +3,8 @@ Dans cette section, nous allons voir ce que peuvent faire les *transformers* et utiliser notre premier outil de la bibliothèque 🤗 *Transformers* : la fonction `pipeline()`. diff --git a/chapters/fr/chapter1/8.mdx b/chapters/fr/chapter1/8.mdx index 9ab2ccbb3..cd9852330 100644 --- a/chapters/fr/chapter1/8.mdx +++ b/chapters/fr/chapter1/8.mdx @@ -3,8 +3,8 @@ Si vous souhaitez utiliser un modèle pré-entraîné ou une version *finetunée* de celui-ci en production, il est important d'avoir conscience que, bien que ces modèles soient puissants, ils ont des limites. La plus importante de ces limitations est que, pour permettre le pré-entraînement des modèles sur de grandes quantités de données, les chercheurs récupèrent souvent tout le contenu qu'ils peuvent trouver et donc en prenant le meilleur et le pire de ce qui est disponible sur internet. diff --git a/chapters/fr/chapter2/2.mdx b/chapters/fr/chapter2/2.mdx index 6aceac55a..188008f9a 100644 --- a/chapters/fr/chapter2/2.mdx +++ b/chapters/fr/chapter2/2.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/fr/chapter2/3.mdx b/chapters/fr/chapter2/3.mdx index a16dc192d..a0d6cf79e 100644 --- a/chapters/fr/chapter2/3.mdx +++ b/chapters/fr/chapter2/3.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/fr/chapter2/4.mdx b/chapters/fr/chapter2/4.mdx index 6445f167e..e8cef472e 100644 --- a/chapters/fr/chapter2/4.mdx +++ b/chapters/fr/chapter2/4.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/fr/chapter2/5.mdx b/chapters/fr/chapter2/5.mdx index 3e9bc09e4..44124664b 100644 --- a/chapters/fr/chapter2/5.mdx +++ b/chapters/fr/chapter2/5.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/fr/chapter2/6.mdx b/chapters/fr/chapter2/6.mdx index 4752f7bf0..ad9ef9fdf 100644 --- a/chapters/fr/chapter2/6.mdx +++ b/chapters/fr/chapter2/6.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/fr/chapter3/2.mdx b/chapters/fr/chapter3/2.mdx index 5a3186a74..e736e0905 100644 --- a/chapters/fr/chapter3/2.mdx +++ b/chapters/fr/chapter3/2.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/fr/chapter3/3.mdx b/chapters/fr/chapter3/3.mdx index eba84e1b1..f8f83360b 100644 --- a/chapters/fr/chapter3/3.mdx +++ b/chapters/fr/chapter3/3.mdx @@ -5,8 +5,8 @@ diff --git a/chapters/fr/chapter3/3_tf.mdx b/chapters/fr/chapter3/3_tf.mdx index bace781f0..f65f80470 100644 --- a/chapters/fr/chapter3/3_tf.mdx +++ b/chapters/fr/chapter3/3_tf.mdx @@ -5,8 +5,8 @@ Une fois que vous avez fait tout le travail de prétraitement des données dans la dernière section, il ne vous reste que quelques étapes pour entraîner le modèle. Notez, cependant, que la commande `model.fit()` s'exécutera très lentement sur un CPU. Si vous n'avez pas de GPU, vous pouvez avoir accès à des GPUs ou TPUs gratuits sur [Google Colab](https://colab.research.google.com/). diff --git a/chapters/fr/chapter3/4.mdx b/chapters/fr/chapter3/4.mdx index b04812639..7f9a74724 100644 --- a/chapters/fr/chapter3/4.mdx +++ b/chapters/fr/chapter3/4.mdx @@ -3,8 +3,8 @@ diff --git a/chapters/fr/chapter4/2.mdx b/chapters/fr/chapter4/2.mdx index 6f86e8f4f..7370f5273 100644 --- a/chapters/fr/chapter4/2.mdx +++ b/chapters/fr/chapter4/2.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/fr/chapter4/3.mdx b/chapters/fr/chapter4/3.mdx index b9db79e6e..ddff04740 100644 --- a/chapters/fr/chapter4/3.mdx +++ b/chapters/fr/chapter4/3.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/fr/chapter5/2.mdx b/chapters/fr/chapter5/2.mdx index ee20d7800..7c27246fa 100644 --- a/chapters/fr/chapter5/2.mdx +++ b/chapters/fr/chapter5/2.mdx @@ -3,8 +3,8 @@ Vous savez comment utiliser le [*Hub*](https://huggingface.co/datasets) pour télécharger des jeux de données mais en pratique vous vous retrouverez souvent à travailler avec des données stockées sur votre ordinateur portable ou sur un serveur distant. Dans cette section, nous allons vous montrer comment 🤗 *Datasets* peut être utilisé pour charger des jeux de données qui ne sont pas disponibles sur le *Hub* d’Hugging Face. diff --git a/chapters/fr/chapter5/3.mdx b/chapters/fr/chapter5/3.mdx index 443681291..0254a9ff4 100644 --- a/chapters/fr/chapter5/3.mdx +++ b/chapters/fr/chapter5/3.mdx @@ -3,8 +3,8 @@ La plupart du temps, les données avec lesquelles vous travaillez ne sont pas parfaitement préparées pour l’entraînements de modèles. Dans cette section, nous allons explorer les différentes fonctionnalités fournies par 🤗 *Datasets* pour nettoyer vos jeux de données. diff --git a/chapters/fr/chapter5/4.mdx b/chapters/fr/chapter5/4.mdx index 0b369438c..d91d0d0b7 100644 --- a/chapters/fr/chapter5/4.mdx +++ b/chapters/fr/chapter5/4.mdx @@ -3,8 +3,8 @@ diff --git a/chapters/fr/chapter5/5.mdx b/chapters/fr/chapter5/5.mdx index 3b1f96a41..e25dccc9a 100644 --- a/chapters/fr/chapter5/5.mdx +++ b/chapters/fr/chapter5/5.mdx @@ -3,8 +3,8 @@ Parfois, le jeu de données dont vous avez besoin pour créer une application de NLP n'existe pas. Vous devrez donc le créer vous-même. Dans cette section, nous allons vous montrer comment créer un corpus de [problèmes GitHub](https://github.com/features/issues/), qui sont couramment utilisés pour suivre les bogues ou les fonctionnalités dans les dépôts GitHub. Ce corpus pourrait être utilisé à diverses fins, notamment : diff --git a/chapters/fr/chapter5/6.mdx b/chapters/fr/chapter5/6.mdx index 69762e98a..a59966d3d 100644 --- a/chapters/fr/chapter5/6.mdx +++ b/chapters/fr/chapter5/6.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/fr/chapter6/2.mdx b/chapters/fr/chapter6/2.mdx index 8eba2f385..8b01f18f3 100644 --- a/chapters/fr/chapter6/2.mdx +++ b/chapters/fr/chapter6/2.mdx @@ -3,8 +3,8 @@ Si un modèle de langue n'est pas disponible dans la langue qui vous intéresse ou si votre corpus est très différent de celui sur lequel votre modèle de langue a été entraîné, vous voudrez très probablement réentraîner le modèle à partir de zéro en utilisant un *tokenizer* adapté à vos données. Pour ce faire, vous devrez entraîner un nouveau *tokenizer* sur votre jeu de données. Mais qu'est-ce que cela signifie exactement ? Lorsque nous avons examiné pour la première fois les *tokenizers* dans le [chapitre 2](/course/fr/chapter2), nous avons vu que la plupart des *transformers* utilisent un _algorithme de tokenisation en sous-mots_. Pour identifier les sous-mots qui sont intéressants et qui apparaissent le plus fréquemment dans un corpus donné, le *tokenizer* doit examiner attentivement tous les textes du corpus. C'est un processus que nous appelons *entraînement*. Les règles exactes qui régissent cet apprentissage dépendent du type de *tokenizer* utilisé. Nous passerons en revue les trois principaux algorithmes plus loin dans ce chapitre. diff --git a/chapters/fr/chapter6/3.mdx b/chapters/fr/chapter6/3.mdx index 41d437a8d..732346fbc 100644 --- a/chapters/fr/chapter6/3.mdx +++ b/chapters/fr/chapter6/3.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/fr/chapter6/3b.mdx b/chapters/fr/chapter6/3b.mdx index 5002f9b4e..f725e30d7 100644 --- a/chapters/fr/chapter6/3b.mdx +++ b/chapters/fr/chapter6/3b.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/fr/chapter6/4.mdx b/chapters/fr/chapter6/4.mdx index 1438e7b6f..37b3808e8 100644 --- a/chapters/fr/chapter6/4.mdx +++ b/chapters/fr/chapter6/4.mdx @@ -3,8 +3,8 @@ Avant de nous plonger plus profondément dans les trois algorithmes de tokénisation en sous-mots les plus courants utilisés avec les *transformers* (*Byte-Pair Encoding* (BPE), *WordPiece* et *Unigram*), nous allons d'abord examiner le prétraitement que chaque *tokenizer* applique au texte. Voici un aperçu de haut niveau des étapes du pipeline de tokenisation : diff --git a/chapters/fr/chapter6/5.mdx b/chapters/fr/chapter6/5.mdx index ee2ad8a52..3e81d81c6 100644 --- a/chapters/fr/chapter6/5.mdx +++ b/chapters/fr/chapter6/5.mdx @@ -3,8 +3,8 @@ Le *Byte-Pair Encoding* (BPE) a été initialement développé en tant qu'algorithme de compression de textes puis utilisé par OpenAI pour la tokenisation du pré-entraînement du modèle GPT. Il est utilisé par de nombreux *transformers* dont GPT, GPT-2, RoBERTa, BART et DeBERTa. diff --git a/chapters/fr/chapter6/6.mdx b/chapters/fr/chapter6/6.mdx index 44206d994..c41f28dfa 100644 --- a/chapters/fr/chapter6/6.mdx +++ b/chapters/fr/chapter6/6.mdx @@ -3,8 +3,8 @@ *WordPiece* est l'algorithme de tokénisation développé par Google pour prétraîner BERT. Il a depuis été réutilisé dans un grand nombre de modèles de *transformers* basés sur BERT tels que DistilBERT, MobileBERT, Funnel Transformers et MPNET. Il est très similaire au BPE en termes d'entraînement mais la tokenisation réelle est effectuée différemment. diff --git a/chapters/fr/chapter6/7.mdx b/chapters/fr/chapter6/7.mdx index 3d262bbd1..90615b887 100644 --- a/chapters/fr/chapter6/7.mdx +++ b/chapters/fr/chapter6/7.mdx @@ -3,8 +3,8 @@ L'algorithme *Unigram* est souvent utilisé dans *SentencePiece*, qui est l'algorithme de tokenization utilisé par des modèles comme ALBERT, T5, mBART, Big Bird et XLNet. diff --git a/chapters/fr/chapter6/8.mdx b/chapters/fr/chapter6/8.mdx index 79c0a58a8..c1095bfd0 100644 --- a/chapters/fr/chapter6/8.mdx +++ b/chapters/fr/chapter6/8.mdx @@ -3,8 +3,8 @@ Comme nous l'avons vu dans les sections précédentes, la tokenisation comprend plusieurs étapes : diff --git a/chapters/fr/chapter7/2.mdx b/chapters/fr/chapter7/2.mdx index 8a4cdc83a..eb7c5636a 100644 --- a/chapters/fr/chapter7/2.mdx +++ b/chapters/fr/chapter7/2.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/fr/chapter7/3.mdx b/chapters/fr/chapter7/3.mdx index 96f3b04ff..0e4e5554a 100644 --- a/chapters/fr/chapter7/3.mdx +++ b/chapters/fr/chapter7/3.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/fr/chapter7/4.mdx b/chapters/fr/chapter7/4.mdx index 8f0659328..d2cec85d2 100644 --- a/chapters/fr/chapter7/4.mdx +++ b/chapters/fr/chapter7/4.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/fr/chapter7/5.mdx b/chapters/fr/chapter7/5.mdx index d2d570667..678cfefe4 100644 --- a/chapters/fr/chapter7/5.mdx +++ b/chapters/fr/chapter7/5.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/fr/chapter7/6.mdx b/chapters/fr/chapter7/6.mdx index 0a3d395b4..52c4f5e28 100644 --- a/chapters/fr/chapter7/6.mdx +++ b/chapters/fr/chapter7/6.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/fr/chapter7/7.mdx b/chapters/fr/chapter7/7.mdx index ab5fd9f75..6e0a619e2 100644 --- a/chapters/fr/chapter7/7.mdx +++ b/chapters/fr/chapter7/7.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/fr/chapter8/2.mdx b/chapters/fr/chapter8/2.mdx index c09d17766..15812fa71 100644 --- a/chapters/fr/chapter8/2.mdx +++ b/chapters/fr/chapter8/2.mdx @@ -3,8 +3,8 @@ Dans cette section, nous allons examiner certaines erreurs courantes qui peuvent se produire lorsque vous essayez de générer des prédictions à partir de votre *transformer* fraîchement *finetuné*. Cela vous préparera pour la [section 4](/course/chapter8/fr/section4) de ce chapitre où nous explorerons comment déboguer la phase d'entraînement elle-même. diff --git a/chapters/fr/chapter8/3.mdx b/chapters/fr/chapter8/3.mdx index b9c3c2c49..b2bf35a17 100644 --- a/chapters/fr/chapter8/3.mdx +++ b/chapters/fr/chapter8/3.mdx @@ -3,8 +3,8 @@ diff --git a/chapters/fr/chapter8/4.mdx b/chapters/fr/chapter8/4.mdx index e726543f1..f9a842c72 100644 --- a/chapters/fr/chapter8/4.mdx +++ b/chapters/fr/chapter8/4.mdx @@ -5,8 +5,8 @@ Vous avez écrit un magnifique script pour entraîner ou *finetuner* un modèle sur une tâche donnée en suivant consciencieusement les conseils du [chapitre 7](/course/fr/chapter7). Mais lorsque vous lancez la commande `model.fit()`, quelque chose d'horrible se produit : vous obtenez une erreur 😱 ! Ou pire, tout semble aller bien et l'entraînement se déroule sans erreur mais le modèle résultant est mauvais. Dans cette section, nous allons vous montrer ce que vous pouvez faire pour déboguer ce genre de problèmes. diff --git a/chapters/fr/chapter8/4_tf.mdx b/chapters/fr/chapter8/4_tf.mdx index c4b4c8b84..ea66f1334 100644 --- a/chapters/fr/chapter8/4_tf.mdx +++ b/chapters/fr/chapter8/4_tf.mdx @@ -5,8 +5,8 @@ Vous avez écrit un magnifique script pour entraîner ou *finetuner* un modèle sur une tâche donnée en suivant consciencieusement les conseils du [chapitre 7](/course/fr/chapter7). Mais lorsque vous lancez la commande `model.fit()`, quelque chose d'horrible se produit : vous obtenez une erreur 😱 ! Ou pire, tout semble aller bien et l'entraînement se déroule sans erreur mais le modèle résultant est mauvais. Dans cette section, nous allons vous montrer ce que vous pouvez faire pour déboguer ce genre de problèmes. diff --git a/chapters/fr/chapter8/5.mdx b/chapters/fr/chapter8/5.mdx index 71c0f9dfc..5d72c010a 100644 --- a/chapters/fr/chapter8/5.mdx +++ b/chapters/fr/chapter8/5.mdx @@ -3,8 +3,8 @@ Lorsque vous rencontrez un problème avec l'une des bibliothèques d'Hugging Face, faites le nous savoir afin que nous puissions le corriger (il en va de même pour toute bibliothèque open source).
diff --git a/chapters/fr/chapter9/2.mdx b/chapters/fr/chapter9/2.mdx index 2a15df2a7..c2579c3f4 100644 --- a/chapters/fr/chapter9/2.mdx +++ b/chapters/fr/chapter9/2.mdx @@ -3,8 +3,8 @@ Commençons par installer *Gradio* ! Comme il s'agit d'un *package* Python, il suffit de l'exécuter : diff --git a/chapters/fr/chapter9/3.mdx b/chapters/fr/chapter9/3.mdx index 2ed908d58..31e19722e 100644 --- a/chapters/fr/chapter9/3.mdx +++ b/chapters/fr/chapter9/3.mdx @@ -3,8 +3,8 @@ Dans cette section, nous allons examiner de plus près la classe `Interface`, et comprendre les principaux paramètres utilisés pour en créer une. diff --git a/chapters/fr/chapter9/4.mdx b/chapters/fr/chapter9/4.mdx index d929ef20b..2065d3589 100644 --- a/chapters/fr/chapter9/4.mdx +++ b/chapters/fr/chapter9/4.mdx @@ -3,8 +3,8 @@ Maintenant que vous avez construit une démo, vous voudrez probablement la partager à d'autres personnes. Les démos *Gradio* peuvent être partagées de deux façons : en utilisant un lien de partage temporaire (***temporary share link***) ou un hébergement permanent (***permanent hosting on Spaces***). @@ -27,10 +27,9 @@ Pour ajouter du contenu supplémentaire à votre démo, la classe `Interface` su - `live` : si vous voulez que votre modèle soit relancé à chaque fois que l'entrée change, vous pouvez mettre `live=True`. Ceci est utile pour les modèles rapides (nous verrons un exemple à la fin de cette section). En utilisant les options ci-dessus, nous obtenons une interface plus complète. Exécutez le code ci-dessous pour pouvoir discuter avec Rick et Morty : -```python out -title = "Ask Rick a Question" # "Posez une question à Rick" -description = -""" +```py +title = "Ask Rick a Question" # "Posez une question à Rick" +description = """ The bot was trained to answer questions based on Rick and Morty dialogues. Ask Rick anything! # Le robot a été entraîné à répondre à des questions basées sur les dialogues de Rick et Morty. # Demandez à Rick ce que vous voulez ! @@ -41,15 +40,15 @@ article = "Check out [the original Rick and Morty Bot](https://huggingface.co/sp # Jetez un coup d'œil au [bot original Rick et Morty](https://huggingface.co/spaces/kingabzpro/Rick_and_Morty_Bot) sur lequel cette démo est basée. gr.Interface( - fn=predict, - inputs="textbox", + fn=predict, + inputs="textbox", outputs="text", - title=title, - description=description, + title=title, + description=description, article=article, - examples=[["What are you doing?"], ["Where should we time travel to?"]] - # ["Que faites-vous ?"], ["Où devrions-nous voyager dans le temps ?"] -).launch() + examples=[["What are you doing?"], ["Where should we time travel to?"]] + # ["Que faites-vous ?"], ["Où devrions-nous voyager dans le temps ?"] +).launch() ``` En utilisant les options ci-dessus, nous obtenons une interface plus complète. Essayez l'interface ci-dessous : diff --git a/chapters/fr/chapter9/5.mdx b/chapters/fr/chapter9/5.mdx index e11bf093b..b6126213b 100644 --- a/chapters/fr/chapter9/5.mdx +++ b/chapters/fr/chapter9/5.mdx @@ -3,8 +3,8 @@ Pour vous rendre la vie encore plus facile, *Gradio* s'intègre directement avec *Hub* et *Spaces*. diff --git a/chapters/fr/chapter9/6.mdx b/chapters/fr/chapter9/6.mdx index 273102f8c..e4564b66b 100644 --- a/chapters/fr/chapter9/6.mdx +++ b/chapters/fr/chapter9/6.mdx @@ -3,8 +3,8 @@ Maintenant que nous pouvons construire et partager une interface de base, explorons quelques fonctionnalités plus avancées comme l'état, l'interprétation et l'authentification. diff --git a/chapters/fr/chapter9/7.mdx b/chapters/fr/chapter9/7.mdx index 1f33cb1a7..c199b347d 100644 --- a/chapters/fr/chapter9/7.mdx +++ b/chapters/fr/chapter9/7.mdx @@ -3,8 +3,8 @@ Dans les sections précédentes, nous avons exploré et créé des démos en utilisant la classe `Interface`. Dans cette section, nous allons présenter une **nouvelle** API de bas niveau appelée `gradio.Blocks`. diff --git a/chapters/hi/chapter1/3.mdx b/chapters/hi/chapter1/3.mdx index 13b9eb19d..c7c0b2fdc 100644 --- a/chapters/hi/chapter1/3.mdx +++ b/chapters/hi/chapter1/3.mdx @@ -3,8 +3,8 @@ इस खंड में, हम देखेंगे कि ट्रांसफॉर्मर मॉडल क्या कर सकते हैं और 🤗 ट्रांसफॉर्मर्स लाइब्रेरी: `पाइपलाइन ()` फ़ंक्शन से हमारे पहले टूल का उपयोग कर सकते हैं। diff --git a/chapters/hi/chapter1/8.mdx b/chapters/hi/chapter1/8.mdx index 1612cac3b..3fd13f73b 100644 --- a/chapters/hi/chapter1/8.mdx +++ b/chapters/hi/chapter1/8.mdx @@ -3,8 +3,8 @@ यदि आपका इरादा उत्पादन में एक पूर्व-प्रशिक्षित मॉडल या एक परिष्कृत संस्करण का उपयोग करना है, तो कृपया ध्यान रखें कि, हालांकि ये मॉडल शक्तिशाली उपकरण हैं, वे सीमाओं के साथ आते हैं। इनमें से सबसे बड़ी बात यह है कि बड़ी मात्रा में डेटा पर पूर्व-प्रशिक्षण को सक्षम करने के लिए, शोधकर्ता अक्सर उन सभी सामग्री को परिमार्जन करते हैं जो उन्हें मिल सकती हैं, जो कि इंटरनेट पर उपलब्ध सर्वोत्तम और साथ ही सबसे खराब है। diff --git a/chapters/hi/chapter3/2.mdx b/chapters/hi/chapter3/2.mdx index da9ddb169..eb45b962d 100644 --- a/chapters/hi/chapter3/2.mdx +++ b/chapters/hi/chapter3/2.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/hi/chapter3/3.mdx b/chapters/hi/chapter3/3.mdx index 436949ddd..9dd435aaa 100644 --- a/chapters/hi/chapter3/3.mdx +++ b/chapters/hi/chapter3/3.mdx @@ -5,8 +5,8 @@ diff --git a/chapters/hi/chapter3/3_tf.mdx b/chapters/hi/chapter3/3_tf.mdx index 3954bb9f4..4ae3c99f2 100644 --- a/chapters/hi/chapter3/3_tf.mdx +++ b/chapters/hi/chapter3/3_tf.mdx @@ -5,8 +5,8 @@ एक बार जब आप अंतिम खंड में सभी डेटा पूर्व प्रसंस्करण कार्य कर लेते हैं, तो आपके पास मॉडल को प्रशिक्षित करने के लिए बस कुछ ही चरण शेष हैं। हालाँकि, ध्यान दें कि `model.fit()` कमांड CPU पर बहुत धीमी गति से चलेगा। यदि आपके पास GPU सेट अप नहीं है, तो आप [Google Colab](https://colab.research.google.com/) पर निःशुल्क GPU या TPU का एक्सेस प्राप्त कर सकते हैं। diff --git a/chapters/hi/chapter3/4.mdx b/chapters/hi/chapter3/4.mdx index 652b78030..88c2dd9d6 100644 --- a/chapters/hi/chapter3/4.mdx +++ b/chapters/hi/chapter3/4.mdx @@ -3,8 +3,8 @@ diff --git a/chapters/it/chapter1/3.mdx b/chapters/it/chapter1/3.mdx index 681a54b9a..33546693a 100644 --- a/chapters/it/chapter1/3.mdx +++ b/chapters/it/chapter1/3.mdx @@ -3,8 +3,8 @@ In questa sezione, vedremo di cosa sono capaci i modelli Transformer e useremo il nostro primo strumento della libreria 🤗 Transformer: la funzione `pipeline()`. diff --git a/chapters/it/chapter1/8.mdx b/chapters/it/chapter1/8.mdx index b2548fc64..b81940bc6 100644 --- a/chapters/it/chapter1/8.mdx +++ b/chapters/it/chapter1/8.mdx @@ -3,8 +3,8 @@ Se intendi utilizzare un modello pre-addestrato o una versione affinata in produzione, sii consapevole che i modelli sono degli strumenti potenti, ma hanno dei limiti. Il più grande limite è che, per permettere un pre-addestramento su una quantità importante di dati, i ricercatori spesso includono tutti i contenuti ai quali riescono ad accedere, prendendo nel contempo il meglio e il peggio di ciò che Intenet offre. diff --git a/chapters/it/chapter2/2.mdx b/chapters/it/chapter2/2.mdx index 94fd67ebc..5260f62c4 100644 --- a/chapters/it/chapter2/2.mdx +++ b/chapters/it/chapter2/2.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/it/chapter3/2.mdx b/chapters/it/chapter3/2.mdx index 6fdba0e2e..9de949110 100644 --- a/chapters/it/chapter3/2.mdx +++ b/chapters/it/chapter3/2.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/it/chapter3/3.mdx b/chapters/it/chapter3/3.mdx index 055e4079e..643d014e4 100644 --- a/chapters/it/chapter3/3.mdx +++ b/chapters/it/chapter3/3.mdx @@ -5,8 +5,8 @@ diff --git a/chapters/it/chapter3/3_tf.mdx b/chapters/it/chapter3/3_tf.mdx index 42874f293..fbf82e28d 100644 --- a/chapters/it/chapter3/3_tf.mdx +++ b/chapters/it/chapter3/3_tf.mdx @@ -5,8 +5,8 @@ Dopo tutto il lavoro di preprocessing nella sezione precedente, rimangono giusto gli ultimi passi per addestrare il modello. Attenzione tuttavia che il comando `model.fit()` sarà molto lento su una CPU. Se non avete una GPU a disposizione, potete avere accesso gratuitamente a GPU o TPU su [Google Colab](https://colab.research.google.com/). diff --git a/chapters/it/chapter3/4.mdx b/chapters/it/chapter3/4.mdx index b07316182..df16e2173 100644 --- a/chapters/it/chapter3/4.mdx +++ b/chapters/it/chapter3/4.mdx @@ -3,8 +3,8 @@ diff --git a/chapters/it/chapter4/2.mdx b/chapters/it/chapter4/2.mdx index 6fbf6e46f..dcee59816 100644 --- a/chapters/it/chapter4/2.mdx +++ b/chapters/it/chapter4/2.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/it/chapter4/3.mdx b/chapters/it/chapter4/3.mdx index 93f55ee0e..0a531c7fd 100644 --- a/chapters/it/chapter4/3.mdx +++ b/chapters/it/chapter4/3.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/it/chapter5/2.mdx b/chapters/it/chapter5/2.mdx index 77133416d..738710236 100644 --- a/chapters/it/chapter5/2.mdx +++ b/chapters/it/chapter5/2.mdx @@ -3,8 +3,8 @@ Sai come usare l'[Hub Hugging Face](https://huggingface.co/datasets) per scaricare i dataset, ma spessa dovrai lavorare con dati che si trovano sul tuo computer, o so un server remoto. In questa sezione vederemo come usare 🤗 Datasets per caricare dataset che non sono disponibile nell'Hub Hugging Face. diff --git a/chapters/it/chapter5/3.mdx b/chapters/it/chapter5/3.mdx index af65c1765..6b8bc7f8f 100644 --- a/chapters/it/chapter5/3.mdx +++ b/chapters/it/chapter5/3.mdx @@ -3,8 +3,8 @@ La maggior parte delle volte, i dati su cui lavorerai non saranno perfettamente pronti a essere usati per l'addestramento. In questa sezione esploreremo alcune funzionalità di 🤗 Datasets per pulire i tuoi dataset. diff --git a/chapters/it/chapter5/4.mdx b/chapters/it/chapter5/4.mdx index 03dd0aa2c..c385da269 100644 --- a/chapters/it/chapter5/4.mdx +++ b/chapters/it/chapter5/4.mdx @@ -3,8 +3,8 @@ diff --git a/chapters/it/chapter5/5.mdx b/chapters/it/chapter5/5.mdx index a0b1542d0..d813fd152 100644 --- a/chapters/it/chapter5/5.mdx +++ b/chapters/it/chapter5/5.mdx @@ -3,8 +3,8 @@ A volte il dataset che ti serve per la tua applicazione NLP non esiste, per cui dovrai crearlo da te. In questa sezione ti mostreremo come creare un corpus di [issue da GitHub](https://github.com/features/issues), usate solitamente per tenere traccia dei bug e delle feature nelle repository su GitHub. Questo corpus può essere usato in diversi modi, ad esempio: diff --git a/chapters/it/chapter5/6.mdx b/chapters/it/chapter5/6.mdx index 8fa4b93e2..b1296144f 100644 --- a/chapters/it/chapter5/6.mdx +++ b/chapters/it/chapter5/6.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/it/chapter8/2.mdx b/chapters/it/chapter8/2.mdx index d3d4bc206..f17c3c933 100644 --- a/chapters/it/chapter8/2.mdx +++ b/chapters/it/chapter8/2.mdx @@ -3,8 +3,8 @@ In questa sezione esamineremo alcuni errori comuni che possono verificarsi quando si cerca di generare previsioni dal modello Transformer appena affinato. Questo ti preparerà alla [sezione 4](/course/chapter8/section4), in cui esploreremo come eseguire il debug della fase di training. diff --git a/chapters/it/chapter8/3.mdx b/chapters/it/chapter8/3.mdx index c34b5cdcc..a16a18f40 100644 --- a/chapters/it/chapter8/3.mdx +++ b/chapters/it/chapter8/3.mdx @@ -3,8 +3,8 @@ diff --git a/chapters/it/chapter8/4.mdx b/chapters/it/chapter8/4.mdx index 245c246fa..3730721e3 100644 --- a/chapters/it/chapter8/4.mdx +++ b/chapters/it/chapter8/4.mdx @@ -5,8 +5,8 @@ Hai scritto un bello script per addestrare o affinare un modello su un determinato compito, seguendo scrupolosamente i consigli del [Capitolo 7](/course/chapter7). Ma quando lanci il comando `trainer.train()`, succede qualcosa di orribile: si ottiene un errore 😱! O peggio, tutto sembra andare bene e il training viene eseguito senza errori, ma il modello che ne risulta fa schifo. In questa sezione mostreremo cosa è possibile fare per eseguire il debug di questo tipo di problemi. diff --git a/chapters/it/chapter8/4_tf.mdx b/chapters/it/chapter8/4_tf.mdx index 7ba7fbc3e..e724900d9 100644 --- a/chapters/it/chapter8/4_tf.mdx +++ b/chapters/it/chapter8/4_tf.mdx @@ -5,8 +5,8 @@ Hai scritto un bello script per addestrare o affinare un modello su un determinato compito, seguendo scrupolosamente i consigli del [Capitolo 7](/course/chapter7). Ma quando lanci il comando `model.fit()`, succede qualcosa di orribile: si ottiene un errore 😱! O peggio, tutto sembra andare bene e il training viene eseguito senza errori, ma il modello che ne risulta fa schifo. In questa sezione mostreremo cosa è possibile fare per eseguire il debug di questo tipo di problemi. diff --git a/chapters/it/chapter8/5.mdx b/chapters/it/chapter8/5.mdx index 444c59436..cb53a3258 100644 --- a/chapters/it/chapter8/5.mdx +++ b/chapters/it/chapter8/5.mdx @@ -3,8 +3,8 @@ Quando si riscontra una cosa che non va in una delle librerie di Hugging Face, dovresti assolutamente farcelo sapere così possiamo correggerla (lo stesso vale per qualsiasi libreria open source, se è per questo). Se non si è del tutto sicuri se il bug risieda nel proprio codice o in una delle nostre librerie, il primo posto da controllare è il [forum](https://discuss.huggingface.co/). La community ti aiuterà a capirlo e anche il team di Hugging Face segue da vicino le discussioni. diff --git a/chapters/ja/chapter4/2.mdx b/chapters/ja/chapter4/2.mdx index 777733e93..dedc60478 100644 --- a/chapters/ja/chapter4/2.mdx +++ b/chapters/ja/chapter4/2.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/ja/chapter4/3.mdx b/chapters/ja/chapter4/3.mdx index 088de9698..b6a8140db 100644 --- a/chapters/ja/chapter4/3.mdx +++ b/chapters/ja/chapter4/3.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/ja/chapter7/2.mdx b/chapters/ja/chapter7/2.mdx index d62280f21..82739dbda 100644 --- a/chapters/ja/chapter7/2.mdx +++ b/chapters/ja/chapter7/2.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/ja/chapter7/3.mdx b/chapters/ja/chapter7/3.mdx index eebcce1c8..740c3f9bb 100644 --- a/chapters/ja/chapter7/3.mdx +++ b/chapters/ja/chapter7/3.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/ja/chapter7/4.mdx b/chapters/ja/chapter7/4.mdx index 519e43f3c..11e5fb724 100644 --- a/chapters/ja/chapter7/4.mdx +++ b/chapters/ja/chapter7/4.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/ja/chapter7/5.mdx b/chapters/ja/chapter7/5.mdx index f17925981..8aa12a0b4 100644 --- a/chapters/ja/chapter7/5.mdx +++ b/chapters/ja/chapter7/5.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/ja/chapter7/6.mdx b/chapters/ja/chapter7/6.mdx index 5add41211..ded614470 100644 --- a/chapters/ja/chapter7/6.mdx +++ b/chapters/ja/chapter7/6.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/ja/chapter7/7.mdx b/chapters/ja/chapter7/7.mdx index bbbed4999..a8aa16f24 100644 --- a/chapters/ja/chapter7/7.mdx +++ b/chapters/ja/chapter7/7.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/ja/chapter8/2.mdx b/chapters/ja/chapter8/2.mdx index ec6c8b066..7e6bb36cc 100644 --- a/chapters/ja/chapter8/2.mdx +++ b/chapters/ja/chapter8/2.mdx @@ -3,8 +3,8 @@ このセクションでは、新しくチューニングされたTransformerモデルから予測を生成しようとするときに起こる事ができる、いくつかの一般的なエラーについて見ていきましょう。これは[セクション4](/course/chapter8/section4)の準備となり、学習段階をデバッグする方法を見ていきましょう。 diff --git a/chapters/ko/chapter1/3.mdx b/chapters/ko/chapter1/3.mdx index 0ece2c2eb..f0e556a5e 100644 --- a/chapters/ko/chapter1/3.mdx +++ b/chapters/ko/chapter1/3.mdx @@ -3,8 +3,8 @@ 이번 장에서는 트랜스포머(Transformer) 모델을 사용해 무엇을 할 수 있는지 같이 살펴보고, 🤗 Transformers 라이브러리 툴의 첫 사용을 `pipeline()` 함수와 함께 시작하겠습니다. diff --git a/chapters/ko/chapter1/8.mdx b/chapters/ko/chapter1/8.mdx index 722a864ae..49ff9be34 100644 --- a/chapters/ko/chapter1/8.mdx +++ b/chapters/ko/chapter1/8.mdx @@ -3,8 +3,8 @@ 사전 학습된 혹은 미세 조정된 모델을 프로덕션 단계에서 사용하실 계획이라면, 이러한 모델들은 강력한 툴이지만 한계가 있음을 반드시 명심하셔야 합니다. 가장 큰 한계점은 리서처들이 무수히 많은 양의 데이터를 사전 학습에 사용하기 위해, 인터넷상에서 모을 수 있는 양질의 데이터와 함께 그렇지 않은 데이터까지 수집했을 가능성이 있다는 것입니다. diff --git a/chapters/pt/chapter1/3.mdx b/chapters/pt/chapter1/3.mdx index 03dcc6c46..8ef239099 100644 --- a/chapters/pt/chapter1/3.mdx +++ b/chapters/pt/chapter1/3.mdx @@ -3,8 +3,8 @@ Nessa seção, observaremos sobre o que os modelos Transformers podem fazer e usar nossa primeira ferramenta da biblioteca 🤗 Transformers: a função `pipeline()` . diff --git a/chapters/pt/chapter1/8.mdx b/chapters/pt/chapter1/8.mdx index 386d8a73c..95ed248a8 100644 --- a/chapters/pt/chapter1/8.mdx +++ b/chapters/pt/chapter1/8.mdx @@ -1,6 +1,6 @@ # Vieses e limitações - + Se sua intenção é usar um modelo pré-treinado ou uma versão ajustada em produção, esteja ciente de que, embora esses modelos sejam ferramentas poderosas, eles vêm com limitações. A maior delas é que, para possibilitar o pré-treinamento em grandes quantidades de dados, os pesquisadores muitas vezes raspam todo o conteúdo que encontram, tirando o melhor e o pior do que está disponível na internet. diff --git a/chapters/pt/chapter2/2.mdx b/chapters/pt/chapter2/2.mdx index 6ee9e9ace..3e4a38522 100644 --- a/chapters/pt/chapter2/2.mdx +++ b/chapters/pt/chapter2/2.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/pt/chapter2/3.mdx b/chapters/pt/chapter2/3.mdx index b3c94725d..f11570d46 100644 --- a/chapters/pt/chapter2/3.mdx +++ b/chapters/pt/chapter2/3.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/pt/chapter2/4.mdx b/chapters/pt/chapter2/4.mdx index 1134ad8d6..aab3ec7fe 100644 --- a/chapters/pt/chapter2/4.mdx +++ b/chapters/pt/chapter2/4.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/pt/chapter2/5.mdx b/chapters/pt/chapter2/5.mdx index 0caf41234..700cf373a 100644 --- a/chapters/pt/chapter2/5.mdx +++ b/chapters/pt/chapter2/5.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/pt/chapter2/6.mdx b/chapters/pt/chapter2/6.mdx index 0b65e1405..883d506ab 100644 --- a/chapters/pt/chapter2/6.mdx +++ b/chapters/pt/chapter2/6.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/pt/chapter4/2.mdx b/chapters/pt/chapter4/2.mdx index 4f190231e..70c53d600 100644 --- a/chapters/pt/chapter4/2.mdx +++ b/chapters/pt/chapter4/2.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/pt/chapter4/3.mdx b/chapters/pt/chapter4/3.mdx index 98e996143..93c652963 100644 --- a/chapters/pt/chapter4/3.mdx +++ b/chapters/pt/chapter4/3.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/pt/chapter5/2.mdx b/chapters/pt/chapter5/2.mdx index 2ad037958..053e35ae9 100644 --- a/chapters/pt/chapter5/2.mdx +++ b/chapters/pt/chapter5/2.mdx @@ -3,8 +3,8 @@ Você sabe como usar o [Hugging Face Hub](https://huggingface.co/datasets) para baixar conjuntos de dados (**datasets**), mas muitas vezes você se encontrará trabalhando com dados que são armazenados em seu laptop ou em um servidor remoto. Nesta seção mostraremos como 🤗 Datasets podem ser usados para carregar conjuntos de dados que não estão disponíveis no Hugging Face Hub. diff --git a/chapters/pt/chapter5/3.mdx b/chapters/pt/chapter5/3.mdx index 7fbc29618..7d48c6149 100644 --- a/chapters/pt/chapter5/3.mdx +++ b/chapters/pt/chapter5/3.mdx @@ -3,8 +3,8 @@ Na maioria das vezes, os dados com os quais você trabalha não estarão perfeitamente preparados para treinamento de modelos. Nesta seção vamos explorar as várias características que o 🤗 Datasets fornece para limpar seus conjuntos de dados. diff --git a/chapters/pt/chapter5/4.mdx b/chapters/pt/chapter5/4.mdx index aa63e3003..9dac76f52 100644 --- a/chapters/pt/chapter5/4.mdx +++ b/chapters/pt/chapter5/4.mdx @@ -3,8 +3,8 @@ diff --git a/chapters/pt/chapter5/5.mdx b/chapters/pt/chapter5/5.mdx index ca931925e..6ba5323fe 100644 --- a/chapters/pt/chapter5/5.mdx +++ b/chapters/pt/chapter5/5.mdx @@ -3,8 +3,8 @@ Às vezes, o conjunto de dados de que você precisa para criar um aplicativo de PLN não existe, portanto, você mesmo precisará criá-lo. Nesta seção, mostraremos como criar um corpus de [issues do GitHub](https://github.com/features/issues/), que são comumente usados ​​para rastrear bugs ou recursos nos repositórios do GitHub. Este corpus pode ser usado para vários fins, incluindo: diff --git a/chapters/pt/chapter5/6.mdx b/chapters/pt/chapter5/6.mdx index 6b5d6c61d..c77091eef 100644 --- a/chapters/pt/chapter5/6.mdx +++ b/chapters/pt/chapter5/6.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/pt/chapter6/2.mdx b/chapters/pt/chapter6/2.mdx index 7399e9d68..4cefd09ea 100644 --- a/chapters/pt/chapter6/2.mdx +++ b/chapters/pt/chapter6/2.mdx @@ -3,8 +3,8 @@ Se um modelo de linguagem não estiver disponível no idioma que você estiver interessado, ou se o seu corpus for muito diferente do que o seu modelo de linguagem foi treinado, você muito provavelmente desejará retreinar o modelo do zero usando um tokenizador adaptado para seus dados. Isto exigirá um treinamento de um novo tokenizador para seu conjunto de dados. Mas o que isso exatamente significa? Quando observamos os tokenizadores pela primeira vez no [Capítulo 2](/course/chapter2), nós vimos que a maioria dos modelos Transformer usa um algoritmo de tokenização de subpalavras. Para identificar quais subpalavras são de interesse e que ocorrem mais frequentemente no corpus em questão, o tokenizador precisa dar uma boa olhada em todos os textos no corpus -- processo que chamamos de *treinamento*. As regras exatas que governam o treinamento dependem do tipo de tokenizador usado, e veremos os três algoritmos principais mais adiante neste capítulo. diff --git a/chapters/pt/chapter6/3.mdx b/chapters/pt/chapter6/3.mdx index 540982222..3233b9530 100644 --- a/chapters/pt/chapter6/3.mdx +++ b/chapters/pt/chapter6/3.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/pt/chapter8/2.mdx b/chapters/pt/chapter8/2.mdx index ee380cab5..e158d0be6 100644 --- a/chapters/pt/chapter8/2.mdx +++ b/chapters/pt/chapter8/2.mdx @@ -3,8 +3,8 @@ Nesta seção, veremos alguns erros comuns que podem ocorrer ao tentar gerar previsões de seu modelo Transformer recém treinado. Isso irá prepará-lo para a [seção 4](/course/chapter8/section4), onde exploraremos como debugar a própria fase de treinamento. diff --git a/chapters/pt/chapter8/3.mdx b/chapters/pt/chapter8/3.mdx index 2ac0b8374..a65365d49 100644 --- a/chapters/pt/chapter8/3.mdx +++ b/chapters/pt/chapter8/3.mdx @@ -2,8 +2,8 @@ diff --git a/chapters/ru/chapter1/3.mdx b/chapters/ru/chapter1/3.mdx index be6579002..2f418a6fb 100644 --- a/chapters/ru/chapter1/3.mdx +++ b/chapters/ru/chapter1/3.mdx @@ -3,8 +3,8 @@ В этом разделе мы посмотрим, на что способны трансформеры и первый раз применим функцию из библиотеки 🤗 Transformers: `pipeline()`. diff --git a/chapters/ru/chapter1/8.mdx b/chapters/ru/chapter1/8.mdx index df58fe8be..fe5db9f6b 100644 --- a/chapters/ru/chapter1/8.mdx +++ b/chapters/ru/chapter1/8.mdx @@ -3,8 +3,8 @@ Если вы намерены использовать предварительно обученную модель или точно настроенную версию в рабочей среде, имейте в виду, что, хотя эти модели являются мощными инструментами, они имеют ограничения. Самая большая из них заключается в том, что для предварительной подготовки на больших объемах данных исследователи часто очищают весь контент, который они могут найти, беря как лучшее, так и худшее из того, что доступно в Интернете. diff --git a/chapters/ru/chapter2/2.mdx b/chapters/ru/chapter2/2.mdx index 04fc473d1..ff5c6a630 100644 --- a/chapters/ru/chapter2/2.mdx +++ b/chapters/ru/chapter2/2.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/ru/chapter2/3.mdx b/chapters/ru/chapter2/3.mdx index 6499af197..41d226813 100644 --- a/chapters/ru/chapter2/3.mdx +++ b/chapters/ru/chapter2/3.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/ru/chapter3/2.mdx b/chapters/ru/chapter3/2.mdx index 313cdfcac..231c33e65 100644 --- a/chapters/ru/chapter3/2.mdx +++ b/chapters/ru/chapter3/2.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/ru/chapter3/3.mdx b/chapters/ru/chapter3/3.mdx index 17e2155f0..8da9c30d1 100644 --- a/chapters/ru/chapter3/3.mdx +++ b/chapters/ru/chapter3/3.mdx @@ -5,8 +5,8 @@ diff --git a/chapters/ru/chapter3/3_tf.mdx b/chapters/ru/chapter3/3_tf.mdx index 1b6393c44..92b68b191 100644 --- a/chapters/ru/chapter3/3_tf.mdx +++ b/chapters/ru/chapter3/3_tf.mdx @@ -5,8 +5,8 @@ После того, как вы выполнили всю работу по предварительной обработке данных в последнем разделе, у вас осталось всего несколько шагов для обучения модели. Обратите внимание, однако, что команда `model.fit()` будет работать очень медленно на CPU. Если у вас нет настроенного графического процессора, вы можете получить доступ к бесплатным графическим процессорам или TPU на[Google Colab](https://colab.research.google.com/). diff --git a/chapters/ru/chapter3/4.mdx b/chapters/ru/chapter3/4.mdx index 9a0e473ea..388040777 100644 --- a/chapters/ru/chapter3/4.mdx +++ b/chapters/ru/chapter3/4.mdx @@ -3,8 +3,8 @@ diff --git a/chapters/ru/chapter4/2.mdx b/chapters/ru/chapter4/2.mdx index f6650e03f..b08768ddd 100644 --- a/chapters/ru/chapter4/2.mdx +++ b/chapters/ru/chapter4/2.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/ru/chapter4/3.mdx b/chapters/ru/chapter4/3.mdx index 22d5b956c..d737fd07a 100644 --- a/chapters/ru/chapter4/3.mdx +++ b/chapters/ru/chapter4/3.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/th/chapter1/3.mdx b/chapters/th/chapter1/3.mdx index 592a43080..a13be7f77 100644 --- a/chapters/th/chapter1/3.mdx +++ b/chapters/th/chapter1/3.mdx @@ -3,8 +3,8 @@ ในส่วนนี้ เราจะมาดูกันว่า Transformer นั้นทำอะไรได้บ้าง และมาใช้งานเครื่องมือชิ้นแรกจาก library 🤗 Transformers ซึ่งก็คือฟังก์ชัน `pipeline()` diff --git a/chapters/th/chapter1/8.mdx b/chapters/th/chapter1/8.mdx index 4705d850a..0478726cc 100644 --- a/chapters/th/chapter1/8.mdx +++ b/chapters/th/chapter1/8.mdx @@ -3,8 +3,8 @@ หากคุณต้องการจะใช้โมเดล pretrain หรือโมเดล fine-tune ในการใช้งานจริง โปรดระลึกไว้เสมอว่าโมเดลพวกนี้ใช้งานได้ดี และก็มีข้อจำกัดอยู่เช่นกัน ข้อจำกัดที่สำคัญที่สุดเลยคือ การจะ pretrain โมเดลเหล่านี้ด้วยข้อมูลขนาดใหญ่ได้ นักวิจัยก็ต้องดึงข้อมูลมากจากแหล่งต่าง ๆ ทั้งหมดเท่าที่หาได้ นั่นคือมันจะมีทั้งข้อมูลที่ดีและข้อมูลแย่ ๆ ในอินเตอร์เนตมารวมเข้าด้วยกัน diff --git a/chapters/th/chapter2/2.mdx b/chapters/th/chapter2/2.mdx index 1f2e9566b..4c4ae8111 100644 --- a/chapters/th/chapter2/2.mdx +++ b/chapters/th/chapter2/2.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/th/chapter2/3.mdx b/chapters/th/chapter2/3.mdx index 075a2bd7c..a0ac5f873 100644 --- a/chapters/th/chapter2/3.mdx +++ b/chapters/th/chapter2/3.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/th/chapter2/4.mdx b/chapters/th/chapter2/4.mdx index dcb635cca..d9ce8fb86 100644 --- a/chapters/th/chapter2/4.mdx +++ b/chapters/th/chapter2/4.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/th/chapter2/5.mdx b/chapters/th/chapter2/5.mdx index 3927688b0..518089952 100644 --- a/chapters/th/chapter2/5.mdx +++ b/chapters/th/chapter2/5.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/th/chapter2/6.mdx b/chapters/th/chapter2/6.mdx index 3af1b5216..bb56c26a6 100644 --- a/chapters/th/chapter2/6.mdx +++ b/chapters/th/chapter2/6.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/th/chapter3/2.mdx b/chapters/th/chapter3/2.mdx index 65991cc15..79444c44c 100644 --- a/chapters/th/chapter3/2.mdx +++ b/chapters/th/chapter3/2.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/th/chapter3/3.mdx b/chapters/th/chapter3/3.mdx index 9f22267cd..311766740 100644 --- a/chapters/th/chapter3/3.mdx +++ b/chapters/th/chapter3/3.mdx @@ -5,8 +5,8 @@ diff --git a/chapters/th/chapter3/3_tf.mdx b/chapters/th/chapter3/3_tf.mdx index 2d0e13c94..58a9cd8dd 100644 --- a/chapters/th/chapter3/3_tf.mdx +++ b/chapters/th/chapter3/3_tf.mdx @@ -5,8 +5,8 @@ หลังจากที่คุณได้ทำการประมวลผลข้อมูลใน section ที่แล้ว ก็เหลืองานอีกไม่กี่ขั้นตอนเท่านั้นในการเทรนโมเดล ควรระวังไว้ว่าคำสั่ง `model.fit()` จะรันบน CPU ได้ช้ามาก ๆ ถ้าคุณไม่มีการติดตั้ง GPU คุณสามารถเข้าถึง free GPUs หรือ TPUs ได้บน [Google Colab](https://colab.research.google.com/) diff --git a/chapters/th/chapter3/4.mdx b/chapters/th/chapter3/4.mdx index 1b4df3ca0..64c813c64 100644 --- a/chapters/th/chapter3/4.mdx +++ b/chapters/th/chapter3/4.mdx @@ -3,8 +3,8 @@ diff --git a/chapters/th/chapter4/2.mdx b/chapters/th/chapter4/2.mdx index 90eb59598..9881ecc0d 100644 --- a/chapters/th/chapter4/2.mdx +++ b/chapters/th/chapter4/2.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/th/chapter4/3.mdx b/chapters/th/chapter4/3.mdx index 9f765a4f1..7558c85fc 100644 --- a/chapters/th/chapter4/3.mdx +++ b/chapters/th/chapter4/3.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/th/chapter6/2.mdx b/chapters/th/chapter6/2.mdx index f10b5d003..3b1e55c73 100644 --- a/chapters/th/chapter6/2.mdx +++ b/chapters/th/chapter6/2.mdx @@ -3,8 +3,8 @@ สมมติว่าคุณต้องการจะใช้ language model ในการทำงานใดงานหนึ่ง แต่ตอนนี้ไม่มีโมเดลในภาษาที่คุณต้องการหรือโมเดลที่มีอยู่นั้นถูกเทรนจากคลังข้อมูลที่แตกต่างจากข้อมูลที่คุณต้องการจะใช้งานมาก diff --git a/chapters/th/chapter6/3.mdx b/chapters/th/chapter6/3.mdx index 6b248ff1b..6d58f3b79 100644 --- a/chapters/th/chapter6/3.mdx +++ b/chapters/th/chapter6/3.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/th/chapter6/3b.mdx b/chapters/th/chapter6/3b.mdx index ce4283a71..19ca66b5b 100644 --- a/chapters/th/chapter6/3b.mdx +++ b/chapters/th/chapter6/3b.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/th/chapter6/4.mdx b/chapters/th/chapter6/4.mdx index f1f4e6cdc..c77352e2c 100644 --- a/chapters/th/chapter6/4.mdx +++ b/chapters/th/chapter6/4.mdx @@ -3,8 +3,8 @@ ก่อนที่เราจะเจาะลึกเกี่ยวกับอัลกอริทึม 3 แบบ ของ subword tokenization ที่ใช้กับโมเดล Transformer (Byte-Pair Encoding [BPE], WordPiece, และ Unigram) diff --git a/chapters/th/chapter6/5.mdx b/chapters/th/chapter6/5.mdx index 8a162474b..4d6961f70 100644 --- a/chapters/th/chapter6/5.mdx +++ b/chapters/th/chapter6/5.mdx @@ -3,8 +3,8 @@ ดั้งเดิมแล้ว Byte-Pair Encoding (BPE) เป็นอัลกอริทึมที่ถูกสร้างเพื่อใช้บีบอัดข้อความให้เล็กลง (compress texts) ภายหลัง OpenAI ได้นำอัลกอริทึมนี้มาใช้ในการตัดคำ ในขั้นตอนเตรียมข้อมูลเพื่อเทรน GPT อัลกอริทึมตัวนี้ยังถูกนำมาใช้อย่างกว้างขวางกับโมเดลประเภท Transformer เช่น GPT, GPT-2, RoBERTa, BART, และ DeBERTa diff --git a/chapters/th/chapter6/6.mdx b/chapters/th/chapter6/6.mdx index 25d3073b5..4a40d9747 100644 --- a/chapters/th/chapter6/6.mdx +++ b/chapters/th/chapter6/6.mdx @@ -3,8 +3,8 @@ WordPiece เป็นอัลกอริทึมสำหรับ tokenization ที่สร้างโดย Google เพื่อ pretrain โมเดล BERT หลังจากนั้นมันได้ถูกนำมาใช้กับโมเดลประเภท Transformer หลายตัวที่เป็นประเภทเดียวกับ BERT เช่น DistilBERT, MobileBERT, Funnel Transformers, และ MPNET diff --git a/chapters/th/chapter6/7.mdx b/chapters/th/chapter6/7.mdx index ea2311e03..add722178 100644 --- a/chapters/th/chapter6/7.mdx +++ b/chapters/th/chapter6/7.mdx @@ -3,8 +3,8 @@ อัลกอริทึม Unigram มักจะถูกใช้บ่อยใน SentencePiece ซึ่งเป็นอีกอัลกอริทึมสำหรับการ tokenization ที่ใช้ในโมเดลเช่น AlBERT, T5, mBART, Big Bird, และ XLNet diff --git a/chapters/th/chapter6/8.mdx b/chapters/th/chapter6/8.mdx index adea0fb9b..30369b8b3 100644 --- a/chapters/th/chapter6/8.mdx +++ b/chapters/th/chapter6/8.mdx @@ -3,8 +3,8 @@ จากบทก่อนๆ คุณจะเห็นว่า การตัดคำ ประกอบไปด้วยหลายขั้นตอน : diff --git a/chapters/vi/chapter1/3.mdx b/chapters/vi/chapter1/3.mdx index 90e9b3de5..e89a7f86f 100644 --- a/chapters/vi/chapter1/3.mdx +++ b/chapters/vi/chapter1/3.mdx @@ -3,8 +3,8 @@ Trong phần này, chúng ta sẽ xem các mô hình Transformer có thể làm được những gì và sử dụng công cụ đầu tiên từ thư viện 🤗 Transformers: hàm `pipeline()`. diff --git a/chapters/vi/chapter1/8.mdx b/chapters/vi/chapter1/8.mdx index e9a22fc58..3d01ebb45 100644 --- a/chapters/vi/chapter1/8.mdx +++ b/chapters/vi/chapter1/8.mdx @@ -6,12 +6,12 @@ { label: "Google Colab", value: - "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter1/section8.ipynb", + "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/vi/chapter1/section8.ipynb", }, { label: "Aws Studio", value: - "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter1/section8.ipynb", + "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/vi/chapter1/section8.ipynb", }, ]} /> diff --git a/chapters/vi/chapter2/2.mdx b/chapters/vi/chapter2/2.mdx index 0c43c6f9b..a15e95884 100644 --- a/chapters/vi/chapter2/2.mdx +++ b/chapters/vi/chapter2/2.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/vi/chapter2/3.mdx b/chapters/vi/chapter2/3.mdx index 48f384d3d..2db14c065 100644 --- a/chapters/vi/chapter2/3.mdx +++ b/chapters/vi/chapter2/3.mdx @@ -10,12 +10,12 @@ { label: "Google Colab", value: - "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter2/section3_pt.ipynb", + "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/vi/chapter2/section3_pt.ipynb", }, { label: "Aws Studio", value: - "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter2/section3_pt.ipynb", + "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/vi/chapter2/section3_pt.ipynb", }, ]} /> @@ -28,12 +28,12 @@ { label: "Google Colab", value: - "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter2/section3_tf.ipynb", + "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/vi/chapter2/section3_tf.ipynb", }, { label: "Aws Studio", value: - "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter2/section3_tf.ipynb", + "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/vi/chapter2/section3_tf.ipynb", }, ]} /> diff --git a/chapters/vi/chapter2/4.mdx b/chapters/vi/chapter2/4.mdx index b5f134604..1d4c6cd1d 100644 --- a/chapters/vi/chapter2/4.mdx +++ b/chapters/vi/chapter2/4.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/vi/chapter2/5.mdx b/chapters/vi/chapter2/5.mdx index 78a9b0b5c..19a57ed7e 100644 --- a/chapters/vi/chapter2/5.mdx +++ b/chapters/vi/chapter2/5.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/vi/chapter2/6.mdx b/chapters/vi/chapter2/6.mdx index 52c8c2caf..b6bd67b75 100644 --- a/chapters/vi/chapter2/6.mdx +++ b/chapters/vi/chapter2/6.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/vi/chapter3/2.mdx b/chapters/vi/chapter3/2.mdx index dfbf3a1bd..af766f084 100644 --- a/chapters/vi/chapter3/2.mdx +++ b/chapters/vi/chapter3/2.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/vi/chapter3/3.mdx b/chapters/vi/chapter3/3.mdx index c254c523c..cff0f3963 100644 --- a/chapters/vi/chapter3/3.mdx +++ b/chapters/vi/chapter3/3.mdx @@ -5,8 +5,8 @@ diff --git a/chapters/vi/chapter3/3_tf.mdx b/chapters/vi/chapter3/3_tf.mdx index 29ec33972..9878b7674 100644 --- a/chapters/vi/chapter3/3_tf.mdx +++ b/chapters/vi/chapter3/3_tf.mdx @@ -5,8 +5,8 @@ Khi bạn đã hoàn thành tất cả công việc tiền xử lý dữ liệu trong phần trước, bạn chỉ còn một vài bước nữa để huấn luyện mô hình. Tuy nhiên, lưu ý rằng lệnh `model.fit()` sẽ chạy rất chậm trên CPU. Nếu bạn chưa thiết lập GPU, bạn có thể có quyền truy cập vào GPU hoặc TPU miễn phí trên [Google Colab](https://colab.research.google.com/). diff --git a/chapters/vi/chapter3/4.mdx b/chapters/vi/chapter3/4.mdx index 8e42a0572..b736c3d0d 100644 --- a/chapters/vi/chapter3/4.mdx +++ b/chapters/vi/chapter3/4.mdx @@ -3,8 +3,8 @@ diff --git a/chapters/vi/chapter4/2.mdx b/chapters/vi/chapter4/2.mdx index f0d35fd18..edefb5b7c 100644 --- a/chapters/vi/chapter4/2.mdx +++ b/chapters/vi/chapter4/2.mdx @@ -10,12 +10,12 @@ { label: "Google Colab", value: - "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter4/section2_pt.ipynb", + "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/vi/chapter4/section2_pt.ipynb", }, { label: "Aws Studio", value: - "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter4/section2_pt.ipynb", + "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/vi/chapter4/section2_pt.ipynb", }, ]} /> @@ -28,12 +28,12 @@ { label: "Google Colab", value: - "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter4/section2_tf.ipynb", + "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/vi/chapter4/section2_tf.ipynb", }, { label: "Aws Studio", value: - "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter4/section2_tf.ipynb", + "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/vi/chapter4/section2_tf.ipynb", }, ]} /> diff --git a/chapters/vi/chapter4/3.mdx b/chapters/vi/chapter4/3.mdx index 144a7e51f..c80df00cd 100644 --- a/chapters/vi/chapter4/3.mdx +++ b/chapters/vi/chapter4/3.mdx @@ -10,12 +10,12 @@ { label: "Google Colab", value: - "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter4/section3_pt.ipynb", + "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/vi/chapter4/section3_pt.ipynb", }, { label: "Aws Studio", value: - "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter4/section3_pt.ipynb", + "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/vi/chapter4/section3_pt.ipynb", }, ]} /> @@ -28,12 +28,12 @@ { label: "Google Colab", value: - "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter4/section3_tf.ipynb", + "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/vi/chapter4/section3_tf.ipynb", }, { label: "Aws Studio", value: - "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter4/section3_tf.ipynb", + "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/vi/chapter4/section3_tf.ipynb", }, ]} /> diff --git a/chapters/vi/chapter5/2.mdx b/chapters/vi/chapter5/2.mdx index 7378423ca..25f910110 100644 --- a/chapters/vi/chapter5/2.mdx +++ b/chapters/vi/chapter5/2.mdx @@ -3,8 +3,8 @@ Bạn biết cách sử dụng [Hugging Face Hub](https://huggingface.co/datasets) để tải xuống bộ dữ liệu, nhưng bạn sẽ thấy mình thường làm việc với dữ liệu được lưu trữ trên máy tính xách tay hoặc trên máy chủ từ xa. Trong phần này, chúng tôi sẽ chỉ cho bạn cách 🤗 Datasets có thể được sử dụng để tải các tập dữ liệu không có sẵn trên Hugging Face Hub. diff --git a/chapters/vi/chapter5/3.mdx b/chapters/vi/chapter5/3.mdx index 24b7115c8..70eb79d19 100644 --- a/chapters/vi/chapter5/3.mdx +++ b/chapters/vi/chapter5/3.mdx @@ -3,8 +3,8 @@ Hầu hết thời gian, dữ liệu bạn làm việc sẽ chưa được chuẩn bị hoàn hảo cho các mô hình huấn luyện. Trong phần này, chúng ta sẽ khám phá các tính năng khác nhau mà 🤗 Datasets cung cấp để làm sạch các tập dữ liệu của bạn. diff --git a/chapters/vi/chapter5/4.mdx b/chapters/vi/chapter5/4.mdx index bb0c9bbd5..48a494eb1 100644 --- a/chapters/vi/chapter5/4.mdx +++ b/chapters/vi/chapter5/4.mdx @@ -6,12 +6,12 @@ { label: "Google Colab", value: - "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter5/section4.ipynb", + "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/vi/chapter5/section4.ipynb", }, { label: "Aws Studio", value: - "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter5/section4.ipynb", + "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/vi/chapter5/section4.ipynb", }, ]} /> diff --git a/chapters/vi/chapter5/5.mdx b/chapters/vi/chapter5/5.mdx index 950d4047d..cbb417cfa 100644 --- a/chapters/vi/chapter5/5.mdx +++ b/chapters/vi/chapter5/5.mdx @@ -3,8 +3,8 @@ Đôi khi tập dữ liệu mà bạn cần để xây dựng một ứng dụng NLP không tồn tại, vì vậy bạn sẽ cần phải tự tạo. Trong phần này, chúng tôi sẽ hướng dẫn bạn cách tạo một kho tài liệu gồm [Các vấn đề về GitHub](https://github.com/features/issues/), thường được sử dụng để theo dõi các lỗi hoặc tính năng trong kho lưu trữ GitHub. Kho tài liệu này có thể được sử dụng cho các mục đích khác nhau, bao gồm: diff --git a/chapters/vi/chapter5/6.mdx b/chapters/vi/chapter5/6.mdx index 6a0d9423e..cfaca863b 100644 --- a/chapters/vi/chapter5/6.mdx +++ b/chapters/vi/chapter5/6.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/vi/chapter6/10.md b/chapters/vi/chapter6/10.md deleted file mode 100644 index f7ea9745d..000000000 --- a/chapters/vi/chapter6/10.md +++ /dev/null @@ -1,283 +0,0 @@ - - -# Đố vui cuối chương - - - -Let's test what you learned in this chapter! - -### 1. When should you train a new tokenizer? - - - -### 2. What is the advantage of using a generator of lists of texts compared to a list of lists of texts when using `train_new_from_iterator()`? - -train_new_from_iterator() accepts.", - explain: "A list of lists of texts is a particular kind of generator of lists of texts, so the method will accept this too. Try again!" - }, - { - text: "You will avoid loading the whole dataset into memory at once.", - explain: "Right! Each batch of texts will be released from memory when you iterate, and the gain will be especially visible if you use 🤗 Datasets to store your texts.", - correct: true - }, - { - text: "This will allow the 🤗 Tokenizers library to use multiprocessing.", - explain: "No, it will use multiprocessing either way." - }, - { - text: "The tokenizer you train will generate better texts.", - explain: "The tokenizer does not generate text -- are you confusing it with a language model?" - } - ]} -/> - -### 3. What are the advantages of using a "fast" tokenizer? - - - -### 4. How does the `token-classification` pipeline handle entities that span over several tokens? - - - -### 5. How does the `question-answering` pipeline handle long contexts? - - - -### 6. What is normalization? - - - -### 7. What is pre-tokenization for a subword tokenizer? - - - -### 8. Select the sentences that apply to the BPE model of tokenization. - - - -### 9. Select the sentences that apply to the WordPiece model of tokenization. - - - -### 10. Select the sentences that apply to the Unigram model of tokenization. - - diff --git a/chapters/vi/chapter6/2.mdx b/chapters/vi/chapter6/2.mdx index ed7611d0a..d9bcd1349 100644 --- a/chapters/vi/chapter6/2.mdx +++ b/chapters/vi/chapter6/2.mdx @@ -3,8 +3,8 @@ Nếu mô hình ngôn ngữ không có sẵn ngôn ngữ bạn quan tâm hoặc nếu kho tài liệu của bạn rất khác với kho mà mô hình ngôn ngữ của bạn đã huấn luyện, bạn rất có thể sẽ muốn huấn luyện lại mô hình từ đầu bằng cách sử dụng trình tokenize phù hợp với dữ liệu của bạn. Điều đó sẽ yêu cầu huấn luyện một trình tokenize mới trên tập dữ liệu của bạn. Nhưng chính xác thì điều đó có nghĩa là gì? Khi chúng ta lần đầu xem xét các tokenizer trong [Chương 2](/course/chapter2), chúng ta thấy rằng hầu hết các mô hình Transformer sử dụng thuật toán tokenize _từ phụ_. Để xác định những từ phụ nào được quan tâm và xuất hiện thường xuyên nhất trong kho ngữ liệu hiện có, trình tokenize cần phải xem xét kỹ tất cả các văn bản trong kho ngữ liệu - một quá trình mà chúng ta gọi là *huấn luyện*. Các quy tắc chi phối việc huấn luyện này phụ thuộc vào loại tokenizer được sử dụng và chúng ta sẽ xem xét ba thuật toán chính ở phần sau của chương này. diff --git a/chapters/vi/chapter6/3.md b/chapters/vi/chapter6/3.md deleted file mode 100644 index 41576f859..000000000 --- a/chapters/vi/chapter6/3.md +++ /dev/null @@ -1,473 +0,0 @@ - - -# Sức mạnh đặc biệt của tokenizer nhanh - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -In this section we will take a closer look at the capabilities of the tokenizers in 🤗 Transformers. Up to now we have only used them to tokenize inputs or decode IDs back into text, but tokenizers -- especially those backed by the 🤗 Tokenizers library -- can do a lot more. To illustrate these additional features, we will explore how to reproduce the results of the `token-classification` (that we called `ner`) and `question-answering` pipelines that we first encountered in [Chapter 1](/course/chapter1). - - - -In the following discussion, we will often make the distinction between "slow" and "fast" tokenizers. Slow tokenizers are those written in Python inside the 🤗 Transformers library, while the fast versions are the ones provided by 🤗 Tokenizers, which are written in Rust. If you remember the table from [Chapter 5](/course/chapter5/3) that reported how long it took a fast and a slow tokenizer to tokenize the Drug Review Dataset, you should have an idea of why we call them fast and slow: - - | Fast tokenizer | Slow tokenizer -:--------------:|:--------------:|:-------------: -`batched=True` | 10.8s | 4min41s -`batched=False` | 59.2s | 5min3s - - - -⚠️ When tokenizing a single sentence, you won't always see a difference in speed between the slow and fast versions of the same tokenizer. In fact, the fast version might actually be slower! It's only when tokenizing lots of texts in parallel at the same time that you will be able to clearly see the difference. - - - -## Batch encoding - - - -The output of a tokenizer isn't a simple Python dictionary; what we get is actually a special `BatchEncoding` object. It's a subclass of a dictionary (which is why we were able to index into that result without any problem before), but with additional methods that are mostly used by fast tokenizers. - -Besides their parallelization capabilities, the key functionality of fast tokenizers is that they always keep track of the original span of texts the final tokens come from -- a feature we call *offset mapping*. This in turn unlocks features like mapping each word to the tokens it generated or mapping each character of the original text to the token it's inside, and vice versa. - -Let's take a look at an example: - -```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)) -``` - -As mentioned previously, we get a `BatchEncoding` object in the tokenizer's output: - -```python out - -``` - -Since the `AutoTokenizer` class picks a fast tokenizer by default, we can use the additional methods this `BatchEncoding` object provides. We have two ways to check if our tokenizer is a fast or a slow one. We can either check the attribute `is_fast` of the `tokenizer`: - -```python -tokenizer.is_fast -``` - -```python out -True -``` - -or check the same attribute of our `encoding`: - -```python -encoding.is_fast -``` - -```python out -True -``` - -Let's see what a fast tokenizer enables us to do. First, we can access the tokens without having to convert the IDs back to tokens: - -```py -encoding.tokens() -``` - -```python out -['[CLS]', 'My', 'name', 'is', 'S', '##yl', '##va', '##in', 'and', 'I', 'work', 'at', 'Hu', '##gging', 'Face', 'in', - 'Brooklyn', '.', '[SEP]'] -``` - -In this case the token at index 5 is `##yl`, which is part of the word "Sylvain" in the original sentence. We can also use the `word_ids()` method to get the index of the word each token comes from: - -```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] -``` - -We can see that the tokenizer's special tokens `[CLS]` and `[SEP]` are mapped to `None`, and then each token is mapped to the word it originates from. This is especially useful to determine if a token is at the start of a word or if two tokens are in the same word. We could rely on the `##` prefix for that, but it only works for BERT-like tokenizers; this method works for any type of tokenizer as long as it's a fast one. In the next chapter, we'll see how we can use this capability to apply the labels we have for each word properly to the tokens in tasks like named entity recognition (NER) and part-of-speech (POS) tagging. We can also use it to mask all the tokens coming from the same word in masked language modeling (a technique called _whole word masking_). - - - -The notion of what a word is is complicated. For instance, does "I'll" (a contraction of "I will") count as one or two words? It actually depends on the tokenizer and the pre-tokenization operation it applies. Some tokenizers just split on spaces, so they will consider this as one word. Others use punctuation on top of spaces, so will consider it two words. - -✏️ **Try it out!** Create a tokenizer from the `bert-base-cased` and `roberta-base` checkpoints and tokenize "81s" with them. What do you observe? What are the word IDs? - - - -Similarly, there is a `sentence_ids()` method that we can use to map a token to the sentence it came from (though in this case, the `token_type_ids` returned by the tokenizer can give us the same information). - -Lastly, we can map any word or token to characters in the original text, and vice versa, via the `word_to_chars()` or `token_to_chars()` and `char_to_word()` or `char_to_token()` methods. For instance, the `word_ids()` method told us that `##yl` is part of the word at index 3, but which word is it in the sentence? We can find out like this: - -```py -start, end = encoding.word_to_chars(3) -example[start:end] -``` - -```python out -Sylvain -``` - -As we mentioned previously, this is all powered by the fact the fast tokenizer keeps track of the span of text each token comes from in a list of *offsets*. To illustrate their use, next we'll show you how to replicate the results of the `token-classification` pipeline manually. - - - -✏️ **Try it out!** Create your own example text and see if you can understand which tokens are associated with word ID, and also how to extract the character spans for a single word. For bonus points, try using two sentences as input and see if the sentence IDs make sense to you. - - - -## Inside the `token-classification` pipeline - -In [Chapter 1](/course/chapter1) we got our first taste of applying NER -- where the task is to identify which parts of the text correspond to entities like persons, locations, or organizations -- with the 🤗 Transformers `pipeline()` function. Then, in [Chapter 2](/course/chapter2), we saw how a pipeline groups together the three stages necessary to get the predictions from a raw text: tokenization, passing the inputs through the model, and post-processing. The first two steps in the `token-classification` pipeline are the same as in any other pipeline, but the post-processing is a little more complex -- let's see how! - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -### Getting the base results with the pipeline - -First, let's grab a token classification pipeline so we can get some results to compare manually. The model used by default is [`dbmdz/bert-large-cased-finetuned-conll03-english`](https://huggingface.co/dbmdz/bert-large-cased-finetuned-conll03-english); it performs NER on sentences: - -```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}] -``` - -The model properly identified each token generated by "Sylvain" as a person, each token generated by "Hugging Face" as an organization, and the token "Brooklyn" as a location. We can also ask the pipeline to group together the tokens that correspond to the same entity: - -```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}] -``` - -The `aggregation_strategy` picked will change the scores computed for each grouped entity. With `"simple"` the score is just the mean of the scores of each token in the given entity: for instance, the score of "Sylvain" is the mean of the scores we saw in the previous example for the tokens `S`, `##yl`, `##va`, and `##in`. Other strategies available are: - -- `"first"`, where the score of each entity is the score of the first token of that entity (so for "Sylvain" it would be 0.993828, the score of the token `S`) -- `"max"`, where the score of each entity is the maximum score of the tokens in that entity (so for "Hugging Face" it would be 0.98879766, the score of "Face") -- `"average"`, where the score of each entity is the average of the scores of the words composing that entity (so for "Sylvain" there would be no difference from the `"simple"` strategy, but "Hugging Face" would have a score of 0.9819, the average of the scores for "Hugging", 0.975, and "Face", 0.98879) - -Now let's see how to obtain these results without using the `pipeline()` function! - -### From inputs to predictions - -{#if fw === 'pt'} - -First we need to tokenize our input and pass it through the model. This is done exactly as in [Chapter 2](/course/chapter2); we instantiate the tokenizer and the model using the `AutoXxx` classes and then use them on our example: - -```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) -``` - -Since we're using `AutoModelForTokenClassification` here, we get one set of logits for each token in the input sequence: - -```py -print(inputs["input_ids"].shape) -print(outputs.logits.shape) -``` - -```python out -torch.Size([1, 19]) -torch.Size([1, 19, 9]) -``` - -{:else} - -First we need to tokenize our input and pass it through the model. This is done exactly as in [Chapter 2](/course/chapter2); we instantiate the tokenizer and the model using the `TFAutoXxx` classes and then use them on our example: - -```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) -``` - -Since we're using `TFAutoModelForTokenClassification` here, we get one set of logits for each token in the input sequence: - -```py -print(inputs["input_ids"].shape) -print(outputs.logits.shape) -``` - -```python out -(1, 19) -(1, 19, 9) -``` - -{/if} - -We have a batch with 1 sequence of 19 tokens and the model has 9 different labels, so the output of the model has a shape of 1 x 19 x 9. Like for the text classification pipeline, we use a softmax function to convert those logits to probabilities, and we take the argmax to get predictions (note that we can take the argmax on the logits because the softmax does not change the order): - -{#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] -``` - -The `model.config.id2label` attribute contains the mapping of indexes to labels that we can use to make sense of the predictions: - -```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'} -``` - -As we saw earlier, there are 9 labels: `O` is the label for the tokens that are not in any named entity (it stands for "outside"), and we then have two labels for each type of entity (miscellaneous, person, organization, and location). The label `B-XXX` indicates the token is at the beginning of an entity `XXX` and the label `I-XXX` indicates the token is inside the entity `XXX`. For instance, in the current example we would expect our model to classify the token `S` as `B-PER` (beginning of a person entity) and the tokens `##yl`, `##va` and `##in` as `I-PER` (inside a person entity). - -You might think the model was wrong in this case as it gave the label `I-PER` to all four of these tokens, but that's not entirely true. There are actually two formats for those `B-` and `I-` labels: *IOB1* and *IOB2*. The IOB2 format (in pink below), is the one we introduced whereas in the IOB1 format (in blue), the labels beginning with `B-` are only ever used to separate two adjacent entities of the same type. The model we are using was fine-tuned on a dataset using that format, which is why it assigns the label `I-PER` to the `S` token. - -
-IOB1 vs IOB2 format - -
- -With this map, we are ready to reproduce (almost entirely) the results of the first pipeline -- we can just grab the score and label of each token that was not classified as `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'}] -``` - -This is very similar to what we had before, with one exception: the pipeline also gave us information about the `start` and `end` of each entity in the original sentence. This is where our offset mapping will come into play. To get the offsets, we just have to set `return_offsets_mapping=True` when we apply the tokenizer to our 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)] -``` - -Each tuple is the span of text corresponding to each token, where `(0, 0)` is reserved for the special tokens. We saw before that the token at index 5 is `##yl`, which has `(12, 14)` as offsets here. If we grab the corresponding slice in our example: - - -```py -example[12:14] -``` - -we get the proper span of text without the `##`: - -```python out -yl -``` - -Using this, we can now complete the previous results: - -```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}] -``` - -This is the same as what we got from the first pipeline! - -### Grouping entities - -Using the offsets to determine the start and end keys for each entity is handy, but that information isn't strictly necessary. When we want to group the entities together, however, the offsets will save us a lot of messy code. For example, if we wanted to group together the tokens `Hu`, `##gging`, and `Face`, we could make special rules that say the first two should be attached while removing the `##`, and the `Face` should be added with a space since it does not begin with `##` -- but that would only work for this particular type of tokenizer. We would have to write another set of rules for a SentencePiece or a Byte-Pair-Encoding tokenizer (discussed later in this chapter). - -With the offsets, all that custom code goes away: we just can take the span in the original text that begins with the first token and ends with the last token. So, in the case of the tokens `Hu`, `##gging`, and `Face`, we should start at character 33 (the beginning of `Hu`) and end before character 45 (the end of `Face`): - -```py -example[33:45] -``` - -```python out -Hugging Face -``` - -To write the code that post-processes the predictions while grouping entities, we will group together entities that are consecutive and labeled with `I-XXX`, except for the first one, which can be labeled as `B-XXX` or `I-XXX` (so, we stop grouping an entity when we get a `O`, a new type of entity, or a `B-XXX` that tells us an entity of the same type is starting): - -```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] - - # Grab all the tokens labeled with I-label - 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 - - # The score is the mean of all the scores of the tokens in that grouped entity - 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) -``` - -And we get the same results as with our second 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}] -``` - -Another example of a task where these offsets are extremely useful is question answering. Diving into that pipeline, which we'll do in the next section, will also enable us to take a look at one last feature of the tokenizers in the 🤗 Transformers library: dealing with overflowing tokens when we truncate an input to a given length. diff --git a/chapters/vi/chapter6/3.mdx b/chapters/vi/chapter6/3.mdx index b48e9560b..f4206a87a 100644 --- a/chapters/vi/chapter6/3.mdx +++ b/chapters/vi/chapter6/3.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/vi/chapter6/3b.md b/chapters/vi/chapter6/3b.md deleted file mode 100644 index c0f64d63f..000000000 --- a/chapters/vi/chapter6/3b.md +++ /dev/null @@ -1,642 +0,0 @@ - - -# Tokenizer nhanh trong pipeline QA - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -We will now dive into the `question-answering` pipeline and see how to leverage the offsets to grab the answer to the question at hand from the context, a bit like we did for the grouped entities in the previous section. Then we will see how we can deal with very long contexts that end up being truncated. You can skip this section if you're not interested in the question answering task. - -{#if fw === 'pt'} - - - -{:else} - - - -{/if} - -## Using the `question-answering` pipeline - -As we saw in [Chapter 1](/course/chapter1), we can use the `question-answering` pipeline like this to get the answer to a question: - -```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'} -``` - -Unlike the other pipelines, which can't truncate and split texts that are longer than the maximum length accepted by the model (and thus may miss information at the end of a document), this pipeline can deal with very long contexts and will return the answer to the question even if it's at the end: - -```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'} -``` - -Let's see how it does all of this! - -## Using a model for question answering - -Like with any other pipeline, we start by tokenizing our input and then send it through the model. The checkpoint used by default for the `question-answering` pipeline is [`distilbert-base-cased-distilled-squad`](https://huggingface.co/distilbert-base-cased-distilled-squad) (the "squad" in the name comes from the dataset on which the model was fine-tuned; we'll talk more about the SQuAD dataset in [Chapter 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} - -Note that we tokenize the question and the context as a pair, with the question first. - -
-An example of tokenization of question and context - -
- -Models for question answering work a little differently from the models we've seen up to now. Using the picture above as an example, the model has been trained to predict the index of the token starting the answer (here 21) and the index of the token where the answer ends (here 24). This is why those models don't return one tensor of logits but two: one for the logits corresponding to the start token of the answer, and one for the logits corresponding to the end token of the answer. Since in this case we have only one input containing 66 tokens, we get: - -```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} - -To convert those logits into probabilities, we will apply a softmax function -- but before that, we need to make sure we mask the indices that are not part of the context. Our input is `[CLS] question [SEP] context [SEP]`, so we need to mask the tokens of the question as well as the `[SEP]` token. We'll keep the `[CLS]` token, however, as some models use it to indicate that the answer is not in the context. - -Since we will apply a softmax afterward, we just need to replace the logits we want to mask with a large negative number. Here, we use `-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} - -Now that we have properly masked the logits corresponding to positions we don't want to predict, we can apply the 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} - -At this stage, we could take the argmax of the start and end probabilities -- but we might end up with a start index that is greater than the end index, so we need to take a few more precautions. We will compute the probabilities of each possible `start_index` and `end_index` where `start_index <= end_index`, then take the tuple `(start_index, end_index)` with the highest probability. - -Assuming the events "The answer starts at `start_index`" and "The answer ends at `end_index`" to be independent, the probability that the answer starts at `start_index` and ends at `end_index` is: - -$$\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]$$ - -So, to compute all the scores, we just need to compute all the products \\(\mathrm{start\_probabilities}[\mathrm{start\_index}] \times \mathrm{end\_probabilities}[\mathrm{end\_index}]\\) where `start_index <= end_index`. - -First let's compute all the possible products: - -```py -scores = start_probabilities[:, None] * end_probabilities[None, :] -``` - -{#if fw === 'pt'} - -Then we'll mask the values where `start_index > end_index` by setting them to `0` (the other probabilities are all positive numbers). The `torch.triu()` function returns the upper triangular part of the 2D tensor passed as an argument, so it will do that masking for us: - -```py -scores = torch.triu(scores) -``` - -{:else} - -Then we'll mask the values where `start_index > end_index` by setting them to `0` (the other probabilities are all positive numbers). The `np.triu()` function returns the upper triangular part of the 2D tensor passed as an argument, so it will do that masking for us: - -```py -import numpy as np - -scores = np.triu(scores) -``` - -{/if} - -Now we just have to get the index of the maximum. Since PyTorch will return the index in the flattened tensor, we need to use the floor division `//` and modulus `%` operations to get the `start_index` and `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]) -``` - -We're not quite done yet, but at least we already have the correct score for the answer (you can check this by comparing it to the first result in the previous section): - -```python out -0.97773 -``` - - - -✏️ **Try it out!** Compute the start and end indices for the five most likely answers. - - - -We have the `start_index` and `end_index` of the answer in terms of tokens, so now we just need to convert to the character indices in the context. This is where the offsets will be super useful. We can grab them and use them like we did in the token classification task: - -```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] -``` - -Now we just have to format everything to get our result: - -```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} -``` - -Great! That's the same as in our first example! - - - -✏️ **Try it out!** Use the best scores you computed earlier to show the five most likely answers. To check your results, go back to the first pipeline and pass in `top_k=5` when calling it. - - - -## Handling long contexts - -If we try to tokenize the question and long context we used as an example previously, we'll get a number of tokens higher than the maximum length used in the `question-answering` pipeline (which is 384): - -```py -inputs = tokenizer(question, long_context) -print(len(inputs["input_ids"])) -``` - -```python out -461 -``` - -So, we'll need to truncate our inputs at that maximum length. There are several ways we can do this, but we don't want to truncate the question, only the context. Since the context is the second sentence, we'll use the `"only_second"` truncation strategy. The problem that arises then is that the answer to the question may not be in the truncated context. Here, for instance, we picked a question where the answer is toward the end of the context, and when we truncate it that answer is not present: - -```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] -""" -``` - -This means the model will have a hard time picking the correct answer. To fix this, the `question-answering` pipeline allows us to split the context into smaller chunks, specifying the maximum length. To make sure we don't split the context at exactly the wrong place to make it possible to find the answer, it also includes some overlap between the chunks. - -We can have the tokenizer (fast or slow) do this for us by adding `return_overflowing_tokens=True`, and we can specify the overlap we want with the `stride` argument. Here is an example, using a smaller sentence: - -```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]' -``` - -As we can see, the sentence has been split into chunks in such a way that each entry in `inputs["input_ids"]` has at most 6 tokens (we would need to add padding to have the last entry be the same size as the others) and there is an overlap of 2 tokens between each of the entries. - -Let's take a closer look at the result of the tokenization: - -```py -print(inputs.keys()) -``` - -```python out -dict_keys(['input_ids', 'attention_mask', 'overflow_to_sample_mapping']) -``` - -As expected, we get input IDs and an attention mask. The last key, `overflow_to_sample_mapping`, is a map that tells us which sentence each of the results corresponds to -- here we have 7 results that all come from the (only) sentence we passed the tokenizer: - -```py -print(inputs["overflow_to_sample_mapping"]) -``` - -```python out -[0, 0, 0, 0, 0, 0, 0] -``` - -This is more useful when we tokenize several sentences together. For instance, this: - -```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] -``` - -which means the first sentence is split into 7 chunks as before, and the next 4 chunks come from the second sentence. - -Now let's go back to our long context. By default the `question-answering` pipeline uses a maximum length of 384, as we mentioned earlier, and a stride of 128, which correspond to the way the model was fine-tuned (you can adjust those parameters by passing `max_seq_len` and `stride` arguments when calling the pipeline). We will thus use those parameters when tokenizing. We'll also add padding (to have samples of the same length, so we can build tensors) as well as ask for the 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, -) -``` - -Those `inputs` will contain the input IDs and attention masks the model expects, as well as the offsets and the `overflow_to_sample_mapping` we just talked about. Since those two are not parameters used by the model, we'll pop them out of the `inputs` (and we won't store the map, since it's not useful here) before converting it to a 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} - -Our long context was split in two, which means that after it goes through our model, we will have two sets of start and end logits: - -```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} - -Like before, we first mask the tokens that are not part of the context before taking the softmax. We also mask all the padding tokens (as flagged by the attention mask): - -{#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} - -Then we can use the softmax to convert our logits to probabilities: - -{#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} - -The next step is similar to what we did for the small context, but we repeat it for each of our two chunks. We attribute a score to all possible spans of answer, then take the span with the best score: - -{#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[0] - end_idx = idx % scores.shape[0] - 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[0] - end_idx = idx % scores.shape[0] - 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)] -``` - -Those two candidates correspond to the best answers the model was able to find in each chunk. The model is way more confident the right answer is in the second part (which is a good sign!). Now we just have to map those two token spans to spans of characters in the context (we only need to map the second one to have our answer, but it's interesting to see what the model has picked in the first chunk). - - - -✏️ **Try it out!** Adapt the code above to return the scores and spans for the five most likely answers (in total, not per chunk). - - - -The `offsets` we grabbed earlier is actually a list of offsets, with one list per chunk of text: - -```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} -``` - -If we ignore the first result, we get the same result as our pipeline for this long context -- yay! - - - -✏️ **Try it out!** Use the best scores you computed before to show the five most likely answers (for the whole context, not each chunk). To check your results, go back to the first pipeline and pass in `top_k=5` when calling it. - - - -This concludes our deep dive into the tokenizer's capabilities. We will put all of this in practice again in the next chapter, when we show you how to fine-tune a model on a range of common NLP tasks. diff --git a/chapters/vi/chapter6/3b.mdx b/chapters/vi/chapter6/3b.mdx index ad2cf1c98..aa1788492 100644 --- a/chapters/vi/chapter6/3b.mdx +++ b/chapters/vi/chapter6/3b.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/vi/chapter6/4.md b/chapters/vi/chapter6/4.md deleted file mode 100644 index b4a7757a1..000000000 --- a/chapters/vi/chapter6/4.md +++ /dev/null @@ -1,123 +0,0 @@ -# Chuẩn hoá và tiền tokenize - - - -Before we dive more deeply into the three most common subword tokenization algorithms used with Transformer models (Byte-Pair Encoding [BPE], WordPiece, and Unigram), we'll first take a look at the preprocessing that each tokenizer applies to text. Here's a high-level overview of the steps in the tokenization pipeline: - -
-The tokenization pipeline. - -
- -Before splitting a text into subtokens (according to its model), the tokenizer performs two steps: _normalization_ and _pre-tokenization_. - -## Normalization - - - -The normalization step involves some general cleanup, such as removing needless whitespace, lowercasing, and/or removing accents. If you're familiar with [Unicode normalization](http://www.unicode.org/reports/tr15/) (such as NFC or NFKC), this is also something the tokenizer may apply. - -The 🤗 Transformers `tokenizer` has an attribute called `backend_tokenizer` that provides access to the underlying tokenizer from the 🤗 Tokenizers library: - -```py -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained("bert-base-uncased") -print(type(tokenizer.backend_tokenizer)) -``` - -```python out - -``` - -The `normalizer` attribute of the `tokenizer` object has a `normalize_str()` method that we can use to see how the normalization is performed: - -```py -print(tokenizer.backend_tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) -``` - -```python out -'hello how are u?' -``` - -In this example, since we picked the `bert-base-uncased` checkpoint, the normalization applied lowercasing and removed the accents. - - - -✏️ **Try it out!** Load a tokenizer from the `bert-base-cased` checkpoint and pass the same example to it. What are the main differences you can see between the cased and uncased versions of the tokenizer? - - - -## Pre-tokenization - - - -As we will see in the next sections, a tokenizer cannot be trained on raw text alone. Instead, we first need to split the texts into small entities, like words. That's where the pre-tokenization step comes in. As we saw in [Chapter 2](/course/chapter2), a word-based tokenizer can simply split a raw text into words on whitespace and punctuation. Those words will be the boundaries of the subtokens the tokenizer can learn during its training. - -To see how a fast tokenizer performs pre-tokenization, we can use the `pre_tokenize_str()` method of the `pre_tokenizer` attribute of the `tokenizer` object: - -```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))] -``` - -Notice how the tokenizer is already keeping track of the offsets, which is how it can give us the offset mapping we used in the previous section. Here the tokenizer ignores the two spaces and replaces them with just one, but the offset jumps between `are` and `you` to account for that. - -Since we're using a BERT tokenizer, the pre-tokenization involves splitting on whitespace and punctuation. Other tokenizers can have different rules for this step. For example, if we use the GPT-2 tokenizer: - -```py -tokenizer = AutoTokenizer.from_pretrained("gpt2") -tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") -``` - -it will split on whitespace and punctuation as well, but it will keep the spaces and replace them with a `Ġ` symbol, enabling it to recover the original spaces if we decode the tokens: - -```python out -[('Hello', (0, 5)), (',', (5, 6)), ('Ġhow', (6, 10)), ('Ġare', (10, 14)), ('Ġ', (14, 15)), ('Ġyou', (15, 19)), - ('?', (19, 20))] -``` - -Also note that unlike the BERT tokenizer, this tokenizer does not ignore the double space. - -For a last example, let's have a look at the T5 tokenizer, which is based on the SentencePiece algorithm: - -```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))] -``` - -Like the GPT-2 tokenizer, this one keeps spaces and replaces them with a specific token (`_`), but the T5 tokenizer only splits on whitespace, not punctuation. Also note that it added a space by default at the beginning of the sentence (before `Hello`) and ignored the double space between `are` and `you`. - -Now that we've seen a little of how some different tokenizers process text, we can start to explore the underlying algorithms themselves. We'll begin with a quick look at the broadly widely applicable SentencePiece; then, over the next three sections, we'll examine how the three main algorithms used for subword tokenization work. - -## SentencePiece - -[SentencePiece](https://github.com/google/sentencepiece) is a tokenization algorithm for the preprocessing of text that you can use with any of the models we will see in the next three sections. It considers the text as a sequence of Unicode characters, and replaces spaces with a special character, `▁`. Used in conjunction with the Unigram algorithm (see [section 7](/course/chapter7/7)), it doesn't even require a pre-tokenization step, which is very useful for languages where the space character is not used (like Chinese or Japanese). - -The other main feature of SentencePiece is *reversible tokenization*: since there is no special treatment of spaces, decoding the tokens is done simply by concatenating them and replacing the `_`s with spaces -- this results in the normalized text. As we saw earlier, the BERT tokenizer removes repeating spaces, so its tokenization is not reversible. - -## Algorithm overview - -In the following sections, we'll dive into the three main subword tokenization algorithms: BPE (used by GPT-2 and others), WordPiece (used for example by BERT), and Unigram (used by T5 and others). Before we get started, here's a quick overview of how they each work. Don't hesitate to come back to this table after reading each of the next sections if it doesn't make sense to you yet. - - -Model | BPE | WordPiece | Unigram -:----:|:---:|:---------:|:------: -Training | Starts from a small vocabulary and learns rules to merge tokens | Starts from a small vocabulary and learns rules to merge tokens | Starts from a large vocabulary and learns rules to remove tokens -Training step | Merges the tokens corresponding to the most common pair | Merges the tokens corresponding to the pair with the best score based on the frequency of the pair, privileging pairs where each individual token is less frequent | Removes all the tokens in the vocabulary that will minimize the loss computed on the whole corpus -Learns | Merge rules and a vocabulary | Just a vocabulary | A vocabulary with a score for each token -Encoding | Splits a word into characters and applies the merges learned during training | Finds the longest subword starting from the beginning that is in the vocabulary, then does the same for the rest of the word | Finds the most likely split into tokens, using the scores learned during training - -Now let's dive into BPE! diff --git a/chapters/vi/chapter6/4.mdx b/chapters/vi/chapter6/4.mdx index e99921f29..115792dfa 100644 --- a/chapters/vi/chapter6/4.mdx +++ b/chapters/vi/chapter6/4.mdx @@ -3,8 +3,8 @@ Trước khi đi sâu hơn vào ba thuật toán tokenize từ phụ phổ biến nhất được sử dụng với các mô hình Transformer (Mã hóa theo cặp [BPE], WordPiece và Unigram), trước tiên chúng ta sẽ xem xét tiền xử lý mà mỗi trình tokenize áp dụng cho văn bản. Dưới đây là tổng quan cấp cao về các bước trong pipeline tokenize: diff --git a/chapters/vi/chapter6/5.md b/chapters/vi/chapter6/5.md deleted file mode 100644 index 18b189bc5..000000000 --- a/chapters/vi/chapter6/5.md +++ /dev/null @@ -1,360 +0,0 @@ -# Byte-Pair Encoding tokenization - - - -Byte-Pair Encoding (BPE) was initially developed as an algorithm to compress texts, and then used by OpenAI for tokenization when pretraining the GPT model. It's used by a lot of Transformer models, including GPT, GPT-2, RoBERTa, BART, and DeBERTa. - - - - - -💡 This section covers BPE in depth, going as far as showing a full implementation. You can skip to the end if you just want a general overview of the tokenization algorithm. - - - -## Training algorithm - -BPE training starts by computing the unique set of words used in the corpus (after the normalization and pre-tokenization steps are completed), then building the vocabulary by taking all the symbols used to write those words. As a very simple example, let's say our corpus uses these five words: - -``` -"hug", "pug", "pun", "bun", "hugs" -``` - -The base vocabulary will then be `["b", "g", "h", "n", "p", "s", "u"]`. For real-world cases, that base vocabulary will contain all the ASCII characters, at the very least, and probably some Unicode characters as well. If an example you are tokenizing uses a character that is not in the training corpus, that character will be converted to the unknown token. That's one reason why lots of NLP models are very bad at analyzing content with emojis, for instance. - - - -The GPT-2 and RoBERTa tokenizers (which are pretty similar) have a clever way to deal with this: they don't look at words as being written with Unicode characters, but with bytes. This way the base vocabulary has a small size (256), but every character you can think of will still be included and not end up being converted to the unknown token. This trick is called *byte-level BPE*. - - - -After getting this base vocabulary, we add new tokens until the desired vocabulary size is reached by learning *merges*, which are rules to merge two elements of the existing vocabulary together into a new one. So, at the beginning these merges will create tokens with two characters, and then, as training progresses, longer subwords. - -At any step during the tokenizer training, the BPE algorithm will search for the most frequent pair of existing tokens (by "pair," here we mean two consecutive tokens in a word). That most frequent pair is the one that will be merged, and we rinse and repeat for the next step. - -Going back to our previous example, let's assume the words had the following frequencies: - -``` -("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) -``` - -meaning `"hug"` was present 10 times in the corpus, `"pug"` 5 times, `"pun"` 12 times, `"bun"` 4 times, and `"hugs"` 5 times. We start the training by splitting each word into characters (the ones that form our initial vocabulary) so we can see each word as a list of tokens: - -``` -("h" "u" "g", 10), ("p" "u" "g", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "u" "g" "s", 5) -``` - -Then we look at pairs. The pair `("h", "u")` is present in the words `"hug"` and `"hugs"`, so 15 times total in the corpus. It's not the most frequent pair, though: that honor belongs to `("u", "g")`, which is present in `"hug"`, `"pug"`, and `"hugs"`, for a grand total of 20 times in the vocabulary. - -Thus, the first merge rule learned by the tokenizer is `("u", "g") -> "ug"`, which means that `"ug"` will be added to the vocabulary, and the pair should be merged in all the words of the corpus. At the end of this stage, the vocabulary and corpus look like this: - -``` -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) -``` - -Now we have some pairs that result in a token longer than two characters: the pair `("h", "ug")`, for instance (present 15 times in the corpus). The most frequent pair at this stage is `("u", "n")`, however, present 16 times in the corpus, so the second merge rule learned is `("u", "n") -> "un"`. Adding that to the vocabulary and merging all existing occurrences leads us to: - -``` -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) -``` - -Now the most frequent pair is `("h", "ug")`, so we learn the merge rule `("h", "ug") -> "hug"`, which gives us our first three-letter token. After the merge, the corpus looks like this: - -``` -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) -``` - -And we continue like this until we reach the desired vocabulary size. - - - -✏️ **Now your turn!** What do you think the next merge rule will be? - - - -## Tokenization algorithm - -Tokenization follows the training process closely, in the sense that new inputs are tokenized by applying the following steps: - -1. Normalization -2. Pre-tokenization -3. Splitting the words into individual characters -4. Applying the merge rules learned in order on those splits - -Let's take the example we used during training, with the three merge rules learned: - -``` -("u", "g") -> "ug" -("u", "n") -> "un" -("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. - - - -✏️ **Now your turn!** How do you think the word `"unhug"` will be tokenized? - - - -## Implementing BPE - -Now let's take a look at an implementation of the BPE algorithm. This won't be an optimized version you can actually use on a big corpus; we just want to show you the code so you can understand the algorithm a little bit better. - -First we need a corpus, so let's create a simple one with a few sentences: - -```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.", -] -``` - -Next, we need to pre-tokenize that corpus into words. Since we are replicating a BPE tokenizer (like GPT-2), we will use the `gpt2` tokenizer for the pre-tokenization: - -```python -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained("gpt2") -``` - -Then we compute the frequencies of each word in the corpus as we do the pre-tokenization: - -```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}) -``` - -The next step is to compute the base vocabulary, formed by all the characters used in the 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', 'Ġ'] -``` - -We also add the special tokens used by the model at the beginning of that vocabulary. In the case of GPT-2, the only special token is `"<|endoftext|>"`: - -```python -vocab = ["<|endoftext|>"] + alphabet.copy() -``` - -We now need to split each word into individual characters, to be able to start training: - -```python -splits = {word: [c for c in word] for word in word_freqs.keys()} -``` - -Now that we are ready for training, let's write a function that computes the frequency of each pair. We'll need to use this at each step of the training: - -```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 -``` - -Let's have a look at a part of this dictionary after the initial splits: - -```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 -``` - -Now, finding the most frequent pair only takes a quick loop: - -```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 -``` - -So the first merge to learn is `('Ġ', 't') -> 'Ġt'`, and we add `'Ġt'` to the vocabulary: - -```python -merges = {("Ġ", "t"): "Ġt"} -vocab.append("Ġt") -``` - -To continue, we need to apply that merge in our `splits` dictionary. Let's write another function for this: - -```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 -``` - -And we can have a look at the result of the first merge: - -```py -splits = merge_pair("Ġ", "t", splits) -print(splits["Ġtrained"]) -``` - -```python out -['Ġt', 'r', 'a', 'i', 'n', 'e', 'd'] -``` - -Now we have everything we need to loop until we have learned all the merges we want. Let's aim for a vocab size of 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]) -``` - -As a result, we've learned 19 merge rules (the initial vocabulary had a size of 31 -- 30 characters in the alphabet, plus the special token): - -```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'] -``` - - - -💡 Using `train_new_from_iterator()` on the same corpus won't result in the exact same vocabulary. This is because when there is a choice of the most frequent pair, we selected the first one encountered, while the 🤗 Tokenizers library selects the first one based on its inner IDs. - - - -To tokenize a new text, we pre-tokenize it, split it, then apply all the merge rules learned: - -```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, []) -``` - -We can try this on any text composed of characters in the alphabet: - -```py -tokenize("This is not a token.") -``` - -```python out -['This', 'Ġis', 'Ġ', 'n', 'o', 't', 'Ġa', 'Ġtoken', '.'] -``` - - - -⚠️ Our implementation will throw an error if there is an unknown character since we didn't do anything to handle them. GPT-2 doesn't actually have an unknown token (it's impossible to get an unknown character when using byte-level BPE), but this could happen here because we did not include all the possible bytes in the initial vocabulary. This aspect of BPE is beyond the scope of this section, so we've left the details out. - - - -That's it for the BPE algorithm! Next, we'll have a look at WordPiece. \ No newline at end of file diff --git a/chapters/vi/chapter6/5.mdx b/chapters/vi/chapter6/5.mdx index 537ed387f..2a9b9cf73 100644 --- a/chapters/vi/chapter6/5.mdx +++ b/chapters/vi/chapter6/5.mdx @@ -3,8 +3,8 @@ Mã hóa theo cặp (BPE) tiền thân được phát triển như một thuật toán để nén văn bản, sau đó được OpenAI sử dụng để tokenize khi huấn luyện trước mô hình GPT. Nó được sử dụng bởi rất nhiều mô hình Transformer, bao gồm GPT, GPT-2, RoBERTa, BART và DeBERTa. diff --git a/chapters/vi/chapter6/6.md b/chapters/vi/chapter6/6.md deleted file mode 100644 index 7e60dabb0..000000000 --- a/chapters/vi/chapter6/6.md +++ /dev/null @@ -1,374 +0,0 @@ -# WordPiece tokenization - - - -WordPiece is the tokenization algorithm Google developed to pretrain BERT. It has since been reused in quite a few Transformer models based on BERT, such as DistilBERT, MobileBERT, Funnel Transformers, and MPNET. It's very similar to BPE in terms of the training, but the actual tokenization is done differently. - - - - - -💡 This section covers WordPiece in depth, going as far as showing a full implementation. You can skip to the end if you just want a general overview of the tokenization algorithm. - - - -## Training algorithm - - - -⚠️ Google never open-sourced its implementation of the training algorithm of WordPiece, so what follows is our best guess based on the published literature. It may not be 100% accurate. - - - -Like BPE, WordPiece starts from a small vocabulary including the special tokens used by the model and the initial alphabet. Since it identifies subwords by adding a prefix (like `##` for BERT), each word is initially split by adding that prefix to all the characters inside the word. So, for instance, `"word"` gets split like this: - -``` -w ##o ##r ##d -``` - -Thus, the initial alphabet contains all the characters present at the beginning of a word and the characters present inside a word preceded by the WordPiece prefix. - -Then, again like BPE, WordPiece learns merge rules. The main difference is the way the pair to be merged is selected. Instead of selecting the most frequent pair, WordPiece computes a score for each pair, using the following formula: - -$$\mathrm{score} = (\mathrm{freq\_of\_pair}) / (\mathrm{freq\_of\_first\_element} \times \mathrm{freq\_of\_second\_element})$$ - -By dividing the frequency of the pair by the product of the frequencies of each of its parts, the algorithm prioritizes the merging of pairs where the individual parts are less frequent in the vocabulary. For instance, it won't necessarily merge `("un", "##able")` even if that pair occurs very frequently in the vocabulary, because the two pairs `"un"` and `"##able"` will likely each appear in a lot of other words and have a high frequency. In contrast, a pair like `("hu", "##gging")` will probably be merged faster (assuming the word "hugging" appears often in the vocabulary) since `"hu"` and `"##gging"` are likely to be less frequent individually. - -Let's look at the same vocabulary we used in the BPE training example: - -``` -("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) -``` - -The splits here will be: - -``` -("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##g" "##s", 5) -``` - -so the initial vocabulary will be `["b", "h", "p", "##g", "##n", "##s", "##u"]` (if we forget about special tokens for now). The most frequent pair is `("##u", "##g")` (present 20 times), but the individual frequency of `"##u"` is very high, so its score is not the highest (it's 1 / 36). All pairs with a `"##u"` actually have that same score (1 / 36), so the best score goes to the pair `("##g", "##s")` -- the only one without a `"##u"` -- at 1 / 20, and the first merge learned is `("##g", "##s") -> ("##gs")`. - -Note that when we merge, we remove the `##` between the two tokens, so we add `"##gs"` to the vocabulary and apply the merge in the words of the 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) -``` - -At this point, `"##u"` is in all the possible pairs, so they all end up with the same score. Let's say that in this case, the first pair is merged, so `("h", "##u") -> "hu"`. This takes us to: - -``` -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) -``` - -Then the next best score is shared by `("hu", "##g")` and `("hu", "##gs")` (with 1/15, compared to 1/21 for all the other pairs), so the first pair with the biggest score is merged: - -``` -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) -``` - -and we continue like this until we reach the desired vocabulary size. - - - -✏️ **Now your turn!** What will the next merge rule be? - - - -## Tokenization algorithm - -Tokenization differs in WordPiece and BPE in that WordPiece only saves the final vocabulary, not the merge rules learned. Starting from the word to tokenize, WordPiece finds the longest subword that is in the vocabulary, then splits on it. For instance, if we use the vocabulary learned in the example above, for the word `"hugs"` the longest subword starting from the beginning that is inside the vocabulary is `"hug"`, so we split there and get `["hug", "##s"]`. We then continue with `"##s"`, which is in the vocabulary, so the tokenization of `"hugs"` is `["hug", "##s"]`. - -With BPE, we would have applied the merges learned in order and tokenized this as `["hu", "##gs"]`, so the encoding is different. - -As another example, let's see how the word `"bugs"` would be tokenized. `"b"` is the longest subword starting at the beginning of the word that is in the vocabulary, so we split there and get `["b", "##ugs"]`. Then `"##u"` is the longest subword starting at the beginning of `"##ugs"` that is in the vocabulary, so we split there and get `["b", "##u, "##gs"]`. Finally, `"##gs"` is in the vocabulary, so this last list is the tokenization of `"bugs"`. - -When the tokenization gets to a stage where it's not possible to find a subword in the vocabulary, the whole word is tokenized as unknown -- so, for instance, `"mug"` would be tokenized as `["[UNK]"]`, as would `"bum"` (even if we can begin with `"b"` and `"##u"`, `"##m"` is not the vocabulary, and the resulting tokenization will just be `["[UNK]"]`, not `["b", "##u", "[UNK]"]`). This is another difference from BPE, which would only classify the individual characters not in the vocabulary as unknown. - - - -✏️ **Now your turn!** How will the word `"pugs"` be tokenized? - - - -## Implementing WordPiece - -Now let's take a look at an implementation of the WordPiece algorithm. Like with BPE, this is just pedagogical, and you won't able to use this on a big corpus. - -We will use the same corpus as in the BPE example: - -```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.", -] -``` - -First, we need to pre-tokenize the corpus into words. Since we are replicating a WordPiece tokenizer (like BERT), we will use the `bert-base-cased` tokenizer for the pre-tokenization: - -```python -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") -``` - -Then we compute the frequencies of each word in the corpus as we do the pre-tokenization: - -```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}) -``` - -As we saw before, the alphabet is the unique set composed of all the first letters of words, and all the other letters that appear in words prefixed by `##`: - -```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'] -``` - -We also add the special tokens used by the model at the beginning of that vocabulary. In the case of BERT, it's the list `["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]`: - -```python -vocab = ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"] + alphabet.copy() -``` - -Next we need to split each word, with all the letters that are not the first prefixed by `##`: - -```python -splits = { - word: [c if i == 0 else f"##{c}" for i, c in enumerate(word)] - for word in word_freqs.keys() -} -``` - -Now that we are ready for training, let's write a function that computes the score of each pair. We'll need to use this at each step of the training: - -```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 -``` - -Let's have a look at a part of this dictionary after the initial splits: - -```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 -``` - -Now, finding the pair with the best score only takes a quick loop: - -```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 -``` - -So the first merge to learn is `('a', '##b') -> 'ab'`, and we add `'ab'` to the vocabulary: - -```python -vocab.append("ab") -``` - -To continue, we need to apply that merge in our `splits` dictionary. Let's write another function for this: - -```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 -``` - -And we can have a look at the result of the first merge: - -```py -splits = merge_pair("a", "##b", splits) -splits["about"] -``` - -```python out -['ab', '##o', '##u', '##t'] -``` - -Now we have everything we need to loop until we have learned all the merges we want. Let's aim for a vocab size of 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) -``` - -We can then look at the generated vocabulary: - -```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'] -``` - -As we can see, compared to BPE, this tokenizer learns parts of words as tokens a bit faster. - - - -💡 Using `train_new_from_iterator()` on the same corpus won't result in the exact same vocabulary. This is because the 🤗 Tokenizers library does not implement WordPiece for the training (since we are not completely sure of its internals), but uses BPE instead. - - - -To tokenize a new text, we pre-tokenize it, split it, then apply the tokenization algorithm on each word. That is, we look for the biggest subword starting at the beginning of the first word and split it, then we repeat the process on the second part, and so on for the rest of that word and the following words in the text: - -```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 -``` - -Let's test it on one word that's in the vocabulary, and another that isn't: - -```python -print(encode_word("Hugging")) -print(encode_word("HOgging")) -``` - -```python out -['Hugg', '##i', '##n', '##g'] -['[UNK]'] -``` - -Now, let's write a function that tokenizes a text: - -```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, []) -``` - -We can try it on any text: - -```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]'] -``` - -That's it for the WordPiece algorithm! Now let's take a look at Unigram. diff --git a/chapters/vi/chapter6/6.mdx b/chapters/vi/chapter6/6.mdx index 583aa95c0..744012f07 100644 --- a/chapters/vi/chapter6/6.mdx +++ b/chapters/vi/chapter6/6.mdx @@ -3,8 +3,8 @@ WordPiece là một thuật toán tokenize được Google phát triển để huấn luyện trước BERT. Nó đã được tái sử dụng trong một vài mô hình Transformer dựa trên BERT, như DistilBERT, MobileBERT, Funnel Transformers, và MPNET. Nó khá tương tự với BPE về mặt huấn luyện, nhưng tokenize thực sự được thực hiện hoàn toàn khác. diff --git a/chapters/vi/chapter6/7.md b/chapters/vi/chapter6/7.md deleted file mode 100644 index 2a4d6f2f3..000000000 --- a/chapters/vi/chapter6/7.md +++ /dev/null @@ -1,381 +0,0 @@ -# Unigram tokenization - - - -The Unigram algorithm is often used in SentencePiece, which is the tokenization algorithm used by models like AlBERT, T5, mBART, Big Bird, and XLNet. - - - - - -💡 This section covers Unigram in depth, going as far as showing a full implementation. You can skip to the end if you just want a general overview of the tokenization algorithm. - - - -## Training algorithm - -Compared to BPE and WordPiece, Unigram works in the other direction: it starts from a big vocabulary and removes tokens from it until it reaches the desired vocabulary size. There are several options to use to build that base vocabulary: we can take the most common substrings in pre-tokenized words, for instance, or apply BPE on the initial corpus with a large vocabulary size. - -At each step of the training, the Unigram algorithm computes a loss over the corpus given the current vocabulary. Then, for each symbol in the vocabulary, the algorithm computes how much the overall loss would increase if the symbol was removed, and looks for the symbols that would increase it the least. Those symbols have a lower effect on the overall loss over the corpus, so in a sense they are "less needed" and are the best candidates for removal. - -This is all a very costly operation, so we don't just remove the single symbol associated with the lowest loss increase, but the \\(p\\) (\\(p\\) being a hyperparameter you can control, usually 10 or 20) percent of the symbols associated with the lowest loss increase. This process is then repeated until the vocabulary has reached the desired size. - -Note that we never remove the base characters, to make sure any word can be tokenized. - -Now, this is still a bit vague: the main part of the algorithm is to compute a loss over the corpus and see how it changes when we remove some tokens from the vocabulary, but we haven't explained how to do this yet. This step relies on the tokenization algorithm of a Unigram model, so we'll dive into this next. - -We'll reuse the corpus from the previous examples: - -``` -("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) -``` - -and for this example, we will take all strict substrings for the initial vocabulary : - -``` -["h", "u", "g", "hu", "ug", "p", "pu", "n", "un", "b", "bu", "s", "hug", "gs", "ugs"] -``` - -## Tokenization algorithm - -A Unigram model is a type of language model that considers each token to be independent of the tokens before it. It's the simplest language model, in the sense that the probability of token X given the previous context is just the probability of token X. So, if we used a Unigram language model to generate text, we would always predict the most common token. - -The probability of a given token is its frequency (the number of times we find it) in the original corpus, divided by the sum of all frequencies of all tokens in the vocabulary (to make sure the probabilities sum up to 1). For instance, `"ug"` is present in `"hug"`, `"pug"`, and `"hugs"`, so it has a frequency of 20 in our corpus. - -Here are the frequencies of all the possible subwords in the vocabulary: - -``` -("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) -``` - -So, the sum of all frequencies is 210, and the probability of the subword `"ug"` is thus 20/210. - - - -✏️ **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, to tokenize a given word, we look at all the possible segmentations into tokens and compute the probability of each according to the Unigram model. Since all tokens are considered independent, this probability is just the product of the probability of each token. For instance, the tokenization `["p", "u", "g"]` of `"pug"` has the probability: - -$$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$$ - -Comparatively, the tokenization `["pu", "g"]` has the probability: - -$$P([``pu", ``g"]) = P(``pu") \times P(``g") = \frac{5}{210} \times \frac{20}{210} = 0.0022676$$ - -so that one is way more likely. In general, tokenizations with the least tokens possible will have the highest probability (because of that division by 210 repeated for each token), which corresponds to what we want intuitively: to split a word into the least number of tokens possible. - -The tokenization of a word with the Unigram model is then the tokenization with the highest probability. In the example of `"pug"`, here are the probabilities we would get for each possible segmentation: - -``` -["p", "u", "g"] : 0.000389 -["p", "ug"] : 0.0022676 -["pu", "g"] : 0.0022676 -``` - -So, `"pug"` would be tokenized as `["p", "ug"]` or `["pu", "g"]`, depending on which of those segmentations is encountered first (note that in a larger corpus, equality cases like this will be rare). - -In this case, it was easy to find all the possible segmentations and compute their probabilities, but in general it's going to be a bit harder. There is a classic algorithm used for this, called the *Viterbi algorithm*. Essentially, we can build a graph to detect the possible segmentations of a given word by saying there is a branch from character _a_ to character _b_ if the subword from _a_ to _b_ is in the vocabulary, and attribute to that branch the probability of the subword. - -To find the path in that graph that is going to have the best score the Viterbi algorithm determines, for each position in the word, the segmentation with the best score that ends at that position. Since we go from the beginning to the end, that best score can be found by looping through all subwords ending at the current position and then using the best tokenization score from the position this subword begins at. Then, we just have to unroll the path taken to arrive at the end. - -Let's take a look at an example using our vocabulary and the word `"unhug"`. For each position, the subwords with the best scores ending there are the following: - -``` -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) -``` - -Thus `"unhug"` would be tokenized as `["un", "hug"]`. - - - -✏️ **Now your turn!** Determine the tokenization of the word `"huggun"`, and its score. - - - -## Back to training - -Now that we have seen how the tokenization works, we can dive a little more deeply into the loss used during training. At any given stage, this loss is computed by tokenizing every word in the corpus, using the current vocabulary and the Unigram model determined by the frequencies of each token in the corpus (as seen before). - -Each word in the corpus has a score, and the loss is the negative log likelihood of those scores -- that is, the sum for all the words in the corpus of all the `-log(P(word))`. - -Let's go back to our example with the following corpus: - -``` -("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) -``` - -The tokenization of each word with their respective scores is: - -``` -"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) -``` - -So the loss is: - -``` -10 * (-log(0.071428)) + 5 * (-log(0.007710)) + 12 * (-log(0.006168)) + 4 * (-log(0.001451)) + 5 * (-log(0.001701)) = 169.8 -``` - -Now we need to compute how removing each token affects the loss. This is rather tedious, so we'll just do it for two tokens here and save the whole process for when we have code to help us. In this (very) particular case, we had two equivalent tokenizations of all the words: as we saw earlier, for example, `"pug"` could be tokenized `["p", "ug"]` with the same score. Thus, removing the `"pu"` token from the vocabulary will give the exact same loss. - -On the other hand, removing `"hug"` will make the loss worse, because the tokenization of `"hug"` and `"hugs"` will become: - -``` -"hug": ["hu", "g"] (score 0.006802) -"hugs": ["hu", "gs"] (score 0.001701) -``` - -These changes will cause the loss to rise by: - -``` -- 10 * (-log(0.071428)) + 10 * (-log(0.006802)) = 23.5 -``` - -Therefore, the token `"pu"` will probably be removed from the vocabulary, but not `"hug"`. - -## Implementing Unigram - -Now let's implement everything we've seen so far in code. Like with BPE and WordPiece, this is not an efficient implementation of the Unigram algorithm (quite the opposite), but it should help you understand it a bit better. - -We will use the same corpus as before as an example: - -```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.", -] -``` - -This time, we will use `xlnet-base-cased` as our model: - -```python -from transformers import AutoTokenizer - -tokenizer = AutoTokenizer.from_pretrained("xlnet-base-cased") -``` - -Like for BPE and WordPiece, we begin by counting the number of occurrences of each word in the 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 -``` - -Then, we need to initialize our vocabulary to something larger than the vocab size we will want at the end. We have to include all the basic characters (otherwise we won't be able to tokenize every word), but for the bigger substrings we'll only keep the most common ones, so we sort them by frequency: - -```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)] -``` - -We group the characters with the best subwords to arrive at an initial vocabulary of size 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 uses a more efficient algorithm called Enhanced Suffix Array (ESA) to create the initial vocabulary. - - - -Next, we compute the sum of all frequencies, to convert the frequencies into probabilities. For our model we will store the logarithms of the probabilities, because it's more numerically stable to add logarithms than to multiply small numbers, and this will simplify the computation of the loss of the model: - -```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()} -``` - -Now the main function is the one that tokenizes words using the Viterbi algorithm. As we saw before, that algorithm computes the best segmentation of each substring of the word, which we will store in a variable named `best_segmentations`. We will store one dictionary per position in the word (from 0 to its total length), with two keys: the index of the start of the last token in the best segmentation, and the score of the best segmentation. With the index of the start of the last token, we will be able to retrieve the full segmentation once the list is completely populated. - -Populating the list is done with just two loops: the main loop goes over each start position, and the second loop tries all substrings beginning at that start position. If the substring is in the vocabulary, we have a new segmentation of the word up until that end position, which we compare to what is in `best_segmentations`. - -Once the main loop is finished, we just start from the end and hop from one start position to the next, recording the tokens as we go, until we reach the start of the word: - -```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 -``` - -We can already try our initial model on some words: - -```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) -``` - -Now it's easy to compute the loss of the model on the 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 -``` - -We can check it works on the model we have: - -```python -compute_loss(model) -``` - -```python out -413.10377642940875 -``` - -Computing the scores for each token is not very hard either; we just have to compute the loss for the models obtained by deleting each 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 -``` - -We can try it on a given token: - -```python -scores = compute_scores(model) -print(scores["ll"]) -print(scores["his"]) -``` - -Since `"ll"` is used in the tokenization of `"Hopefully"`, and removing it will probably make us use the token `"l"` twice instead, we expect it will have a positive loss. `"his"` is only used inside the word `"This"`, which is tokenized as itself, so we expect it to have a zero loss. Here are the results: - -```python out -6.376412403623874 -0.0 -``` - - - -💡 This approach is very inefficient, so SentencePiece uses an approximation of the loss of the model without token X: instead of starting from scratch, it just replaces token X by its segmentation in the vocabulary that is left. This way, all the scores can be computed at once at the same time as the model loss. - - - -With all of this in place, the last thing we need to do is add the special tokens used by the model to the vocabulary, then loop until we have pruned enough tokens from the vocabulary to reach our desired size: - -```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()} -``` - -Then, to tokenize some text, we just need to apply the pre-tokenization and then use our `encode_word()` function: - -```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', '.'] -``` - -That's it for Unigram! Hopefully by now you're feeling like an expert in all things tokenizer. In the next section, we will delve into the building blocks of the 🤗 Tokenizers library, and show you how you can use them to build your own tokenizer. diff --git a/chapters/vi/chapter6/7.mdx b/chapters/vi/chapter6/7.mdx index bddae0798..ff07c3c4e 100644 --- a/chapters/vi/chapter6/7.mdx +++ b/chapters/vi/chapter6/7.mdx @@ -3,8 +3,8 @@ Thuật toán Unigram thường được sử dung trong SentencePiece, đây là một thuật toán tokenize cho các mô hình như AlBERT, T5, mBART, Big Bird, và XLNet. diff --git a/chapters/vi/chapter6/8.md b/chapters/vi/chapter6/8.md deleted file mode 100644 index f5f4f3de1..000000000 --- a/chapters/vi/chapter6/8.md +++ /dev/null @@ -1,565 +0,0 @@ -# Xây dựng từng khối tokenizer - - - -As we've seen in the previous sections, tokenization comprises several steps: - -- Normalization (any cleanup of the text that is deemed necessary, such as removing spaces or accents, Unicode normalization, etc.) -- Pre-tokenization (splitting the input into words) -- Running the input through the model (using the pre-tokenized words to produce a sequence of tokens) -- Post-processing (adding the special tokens of the tokenizer, generating the attention mask and token type IDs) - -As a reminder, here's another look at the overall process: - -
-The tokenization pipeline. - -
- -The 🤗 Tokenizers library has been built to provide several options for each of those steps, which you can mix and match together. In this section we'll see how we can build a tokenizer from scratch, as opposed to training a new tokenizer from an old one as we did in [section 2](/course/chapter6/2). You'll then be able to build any kind of tokenizer you can think of! - - - -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). - -## Acquiring a corpus - -To train our new tokenizer, we will use a small corpus of text (so the examples run fast). The steps for acquiring the corpus are similar to the ones we took at the [beginning of this chapter](/course/chapter6/2), but this time we'll use the [WikiText-2](https://huggingface.co/datasets/wikitext) dataset: - -```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"] -``` - -The function `get_training_corpus()` is a generator that will yield batches of 1,000 texts, which we will use to train the tokenizer. - -🤗 Tokenizers can also be trained on text files directly. Here's how we can generate a text file containing all the texts/inputs from WikiText-2 that we can use locally: - -```python -with open("wikitext-2.txt", "w", encoding="utf-8") as f: - for i in range(len(dataset)): - f.write(dataset[i]["text"] + "\n") -``` - -Next we'll show you how to build your own BERT, GPT-2, and XLNet tokenizers, block by block. That will give us an example of each of the three main tokenization algorithms: WordPiece, BPE, and Unigram. Let's start with BERT! - -## Building a WordPiece tokenizer from scratch - -To build a tokenizer with the 🤗 Tokenizers library, we start by instantiating a `Tokenizer` object with a `model`, then set its `normalizer`, `pre_tokenizer`, `post_processor`, and `decoder` attributes to the values we want. - -For this example, we'll create a `Tokenizer` with a WordPiece model: - -```python -from tokenizers import ( - decoders, - models, - normalizers, - pre_tokenizers, - processors, - trainers, - Tokenizer, -) - -tokenizer = Tokenizer(models.WordPiece(unk_token="[UNK]")) -``` - -We have to specify the `unk_token` so the model knows what to return when it encounters characters it hasn't seen before. Other arguments we can set here include the `vocab` of our model (we're going to train the model, so we don't need to set this) and `max_input_chars_per_word`, which specifies a maximum length for each word (words longer than the value passed will be split). - -The first step of tokenization is normalization, so let's begin with that. Since BERT is widely used, there is a `BertNormalizer` with the classic options we can set for BERT: `lowercase` and `strip_accents`, which are self-explanatory; `clean_text` to remove all control characters and replace repeating spaces with a single one; and `handle_chinese_chars`, which places spaces around Chinese characters. To replicate the `bert-base-uncased` tokenizer, we can just set this normalizer: - -```python -tokenizer.normalizer = normalizers.BertNormalizer(lowercase=True) -``` - -Generally speaking, however, when building a new tokenizer you won't have access to such a handy normalizer already implemented in the 🤗 Tokenizers library -- so let's see how to create the BERT normalizer by hand. The library provides a `Lowercase` normalizer and a `StripAccents` normalizer, and you can compose several normalizers using a `Sequence`: - -```python -tokenizer.normalizer = normalizers.Sequence( - [normalizers.NFD(), normalizers.Lowercase(), normalizers.StripAccents()] -) -``` - -We're also using an `NFD` Unicode normalizer, as otherwise the `StripAccents` normalizer won't properly recognize the accented characters and thus won't strip them out. - -As we've seen before, we can use the `normalize_str()` method of the `normalizer` to check out the effects it has on a given text: - -```python -print(tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) -``` - -```python out -hello how are u? -``` - - - -**To go further** If you test the two versions of the previous normalizers on a string containing the unicode character `u"\u0085"` you will surely notice that these two normalizers are not exactly equivalent. -To not over-complicate the version with `normalizers.Sequence` too much , we haven't included the Regex replacements that the `BertNormalizer` requires when the `clean_text` argument is set to `True` - which is the default behavior. But don't worry: it is possible to get exactly the same normalization without using the handy `BertNormalizer` by adding two `normalizers.Replace`'s to the normalizers sequence. - - - -Next is the pre-tokenization step. Again, there is a prebuilt `BertPreTokenizer` that we can use: - -```python -tokenizer.pre_tokenizer = pre_tokenizers.BertPreTokenizer() -``` - -Or we can build it from scratch: - -```python -tokenizer.pre_tokenizer = pre_tokenizers.Whitespace() -``` - -Note that the `Whitespace` pre-tokenizer splits on whitespace and all characters that are not letters, digits, or the underscore character, so it technically splits on whitespace and punctuation: - -```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))] -``` - -If you only want to split on whitespace, you should use the `WhitespaceSplit` pre-tokenizer instead: - -```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))] -``` - -Like with normalizers, you can use a `Sequence` to compose several pre-tokenizers: - -```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))] -``` - -The next step in the tokenization pipeline is running the inputs through the model. We already specified our model in the initialization, but we still need to train it, which will require a `WordPieceTrainer`. The main thing to remember when instantiating a trainer in 🤗 Tokenizers is that you need to pass it all the special tokens you intend to use -- otherwise it won't add them to the vocabulary, since they are not in the training corpus: - -```python -special_tokens = ["[UNK]", "[PAD]", "[CLS]", "[SEP]", "[MASK]"] -trainer = trainers.WordPieceTrainer(vocab_size=25000, special_tokens=special_tokens) -``` - -As well as specifying the `vocab_size` and `special_tokens`, we can set the `min_frequency` (the number of times a token must appear to be included in the vocabulary) or change the `continuing_subword_prefix` (if we want to use something different from `##`). - -To train our model using the iterator we defined earlier, we just have to execute this command: - -```python -tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) -``` - -We can also use text files to train our tokenizer, which would look like this (we reinitialize the model with an empty `WordPiece` beforehand): - -```python -tokenizer.model = models.WordPiece(unk_token="[UNK]") -tokenizer.train(["wikitext-2.txt"], trainer=trainer) -``` - -In both cases, we can then test the tokenizer on a text by calling the `encode()` method: - -```python -encoding = tokenizer.encode("Let's test this tokenizer.") -print(encoding.tokens) -``` - -```python out -['let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.'] -``` - -The `encoding` obtained is an `Encoding`, which contains all the necessary outputs of the tokenizer in its various attributes: `ids`, `type_ids`, `tokens`, `offsets`, `attention_mask`, `special_tokens_mask`, and `overflowing`. - -The last step in the tokenization pipeline is post-processing. We need to add the `[CLS]` token at the beginning and the `[SEP]` token at the end (or after each sentence, if we have a pair of sentences). We will use a `TemplateProcessor` for this, but first we need to know the IDs of the `[CLS]` and `[SEP]` tokens in the vocabulary: - -```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) -``` - -To write the template for the `TemplateProcessor`, we have to specify how to treat a single sentence and a pair of sentences. For both, we write the special tokens we want to use; the first (or single) sentence is represented by `$A`, while the second sentence (if encoding a pair) is represented by `$B`. For each of these (special tokens and sentences), we also specify the corresponding token type ID after a colon. - -The classic BERT template is thus defined as follows: - -```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)], -) -``` - -Note that we need to pass along the IDs of the special tokens, so the tokenizer can properly convert them to their IDs. - -Once this is added, going back to our previous example will give: - -```python -encoding = tokenizer.encode("Let's test this tokenizer.") -print(encoding.tokens) -``` - -```python out -['[CLS]', 'let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.', '[SEP]'] -``` - -And on a pair of sentences, we get the proper result: - -```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] -``` - -We've almost finished building this tokenizer from scratch -- the last step is to include a decoder: - -```python -tokenizer.decoder = decoders.WordPiece(prefix="##") -``` - -Let's test it on our previous `encoding`: - -```python -tokenizer.decode(encoding.ids) -``` - -```python out -"let's test this tokenizer... on a pair of sentences." -``` - -Great! We can save our tokenizer in a single JSON file like this: - -```python -tokenizer.save("tokenizer.json") -``` - -We can then reload that file in a `Tokenizer` object with the `from_file()` method: - -```python -new_tokenizer = Tokenizer.from_file("tokenizer.json") -``` - -To use this tokenizer in 🤗 Transformers, we have to wrap it in a `PreTrainedTokenizerFast`. We can either use the generic class or, if our tokenizer corresponds to an existing model, use that class (here, `BertTokenizerFast`). If you apply this lesson to build a brand new tokenizer, you will have to use the first option. - -To wrap the tokenizer in a `PreTrainedTokenizerFast`, we can either pass the tokenizer we built as a `tokenizer_object` or pass the tokenizer file we saved as `tokenizer_file`. The key thing to remember is that we have to manually set all the special tokens, since that class can't infer from the `tokenizer` object which token is the mask token, the `[CLS]` token, 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]", -) -``` - -If you are using a specific tokenizer class (like `BertTokenizerFast`), you will only need to specify the special tokens that are different from the default ones (here, none): - -```python -from transformers import BertTokenizerFast - -wrapped_tokenizer = BertTokenizerFast(tokenizer_object=tokenizer) -``` - -You can then use this tokenizer like any other 🤗 Transformers tokenizer. You can save it with the `save_pretrained()` method, or upload it to the Hub with the `push_to_hub()` method. - -Now that we've seen how to build a WordPiece tokenizer, let's do the same for a BPE tokenizer. We'll go a bit faster since you know all the steps, and only highlight the differences. - -## Building a BPE tokenizer from scratch - -Let's now build a GPT-2 tokenizer. Like for the BERT tokenizer, we start by initializing a `Tokenizer` with a BPE model: - -```python -tokenizer = Tokenizer(models.BPE()) -``` - -Also like for BERT, we could initialize this model with a vocabulary if we had one (we would need to pass the `vocab` and `merges` in this case), but since we will train from scratch, we don't need to do that. We also don't need to specify an `unk_token` because GPT-2 uses byte-level BPE, which doesn't require it. - -GPT-2 does not use a normalizer, so we skip that step and go directly to the pre-tokenization: - -```python -tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False) -``` - -The option we added to `ByteLevel` here is to not add a space at the beginning of a sentence (which is the default otherwise). We can have a look at the pre-tokenization of an example text like before: - -```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))] -``` - -Next is the model, which needs training. For GPT-2, the only special token is the end-of-text token: - -```python -trainer = trainers.BpeTrainer(vocab_size=25000, special_tokens=["<|endoftext|>"]) -tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) -``` - -Like with the `WordPieceTrainer`, as well as the `vocab_size` and `special_tokens`, we can specify the `min_frequency` if we want to, or if we have an end-of-word suffix (like ``), we can set it with `end_of_word_suffix`. - -This tokenizer can also be trained on text files: - -```python -tokenizer.model = models.BPE() -tokenizer.train(["wikitext-2.txt"], trainer=trainer) -``` - -Let's have a look at the tokenization of a sample text: - -```python -encoding = tokenizer.encode("Let's test this tokenizer.") -print(encoding.tokens) -``` - -```python out -['L', 'et', "'", 's', 'Ġtest', 'Ġthis', 'Ġto', 'ken', 'izer', '.'] -``` - -We apply the byte-level post-processing for the GPT-2 tokenizer as follows: - -```python -tokenizer.post_processor = processors.ByteLevel(trim_offsets=False) -``` - -The `trim_offsets = False` option indicates to the post-processor that we should leave the offsets of tokens that begin with 'Ġ' as they are: this way the start of the offsets will point to the space before the word, not the first character of the word (since the space is technically part of the token). Let's have a look at the result with the text we just encoded, where `'Ġtest'` is the token at index 4: - -```python -sentence = "Let's test this tokenizer." -encoding = tokenizer.encode(sentence) -start, end = encoding.offsets[4] -sentence[start:end] -``` - -```python out -' test' -``` - -Finally, we add a byte-level decoder: - -```python -tokenizer.decoder = decoders.ByteLevel() -``` - -and we can double-check it works properly: - -```python -tokenizer.decode(encoding.ids) -``` - -```python out -"Let's test this tokenizer." -``` - -Great! Now that we're done, we can save the tokenizer like before, and wrap it in a `PreTrainedTokenizerFast` or `GPT2TokenizerFast` if we want to use it in 🤗 Transformers: - -```python -from transformers import PreTrainedTokenizerFast - -wrapped_tokenizer = PreTrainedTokenizerFast( - tokenizer_object=tokenizer, - bos_token="<|endoftext|>", - eos_token="<|endoftext|>", -) -``` - -or: - -```python -from transformers import GPT2TokenizerFast - -wrapped_tokenizer = GPT2TokenizerFast(tokenizer_object=tokenizer) -``` - -As the last example, we'll show you how to build a Unigram tokenizer from scratch. - -## Building a Unigram tokenizer from scratch - -Let's now build an XLNet tokenizer. Like for the previous tokenizers, we start by initializing a `Tokenizer` with a Unigram model: - -```python -tokenizer = Tokenizer(models.Unigram()) -``` - -Again, we could initialize this model with a vocabulary if we had one. - -For the normalization, XLNet uses a few replacements (which come from SentencePiece): - -```python -from tokenizers import Regex - -tokenizer.normalizer = normalizers.Sequence( - [ - normalizers.Replace("``", '"'), - normalizers.Replace("''", '"'), - normalizers.NFKD(), - normalizers.StripAccents(), - normalizers.Replace(Regex(" {2,}"), " "), - ] -) -``` - -This replaces `` and '' with " and any sequence of two or more spaces with a single space, as well as removing the accents in the texts to tokenize. - -The pre-tokenizer to use for any SentencePiece tokenizer is `Metaspace`: - -```python -tokenizer.pre_tokenizer = pre_tokenizers.Metaspace() -``` - -We can have a look at the pre-tokenization of an example text like before: - -```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))] -``` - -Next is the model, which needs training. XLNet has quite a few special tokens: - -```python -special_tokens = ["", "", "", "", "", "", ""] -trainer = trainers.UnigramTrainer( - vocab_size=25000, special_tokens=special_tokens, unk_token="" -) -tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) -``` - -A very important argument not to forget for the `UnigramTrainer` is the `unk_token`. We can also pass along other arguments specific to the Unigram algorithm, such as the `shrinking_factor` for each step where we remove tokens (defaults to 0.75) or the `max_piece_length` to specify the maximum length of a given token (defaults to 16). - -This tokenizer can also be trained on text files: - -```python -tokenizer.model = models.Unigram() -tokenizer.train(["wikitext-2.txt"], trainer=trainer) -``` - -Let's have a look at the tokenization of a sample text: - -```python -encoding = tokenizer.encode("Let's test this tokenizer.") -print(encoding.tokens) -``` - -```python out -['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.'] -``` - -A peculiarity of XLNet is that it puts the `` token at the end of the sentence, with a type ID of 2 (to distinguish it from the other tokens). It's padding on the left, as a result. We can deal with all the special tokens and token type IDs with a template, like for BERT, but first we have to get the IDs of the `` and `` tokens: - -```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 -``` - -The template looks like this: - -```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)], -) -``` - -And we can test it works by encoding a pair of sentences: - -```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] -``` - -Finally, we add a `Metaspace` decoder: - -```python -tokenizer.decoder = decoders.Metaspace() -``` - -and we're done with this tokenizer! We can save the tokenizer like before, and wrap it in a `PreTrainedTokenizerFast` or `XLNetTokenizerFast` if we want to use it in 🤗 Transformers. One thing to note when using `PreTrainedTokenizerFast` is that on top of the special tokens, we need to tell the 🤗 Transformers library to pad on the left: - -```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", -) -``` - -Or alternatively: - -```python -from transformers import XLNetTokenizerFast - -wrapped_tokenizer = XLNetTokenizerFast(tokenizer_object=tokenizer) -``` - -Now that you have seen how the various building blocks are used to build existing tokenizers, you should be able to write any tokenizer you want with the 🤗 Tokenizers library and be able to use it in 🤗 Transformers. diff --git a/chapters/vi/chapter6/8.mdx b/chapters/vi/chapter6/8.mdx index 2e2550dae..7ce6fd78e 100644 --- a/chapters/vi/chapter6/8.mdx +++ b/chapters/vi/chapter6/8.mdx @@ -3,8 +3,8 @@ Như chúng ta đã thấy trong các phần trước, tokenize bao gồm một số bước: diff --git a/chapters/vi/chapter7/2.mdx b/chapters/vi/chapter7/2.mdx index b86cc8e19..1346f837a 100644 --- a/chapters/vi/chapter7/2.mdx +++ b/chapters/vi/chapter7/2.mdx @@ -10,12 +10,12 @@ { label: "Google Colab", value: - "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter7/section2_pt.ipynb", + "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/vi/chapter7/section2_pt.ipynb", }, { label: "Aws Studio", value: - "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter7/section2_pt.ipynb", + "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/vi/chapter7/section2_pt.ipynb", }, ]} /> @@ -28,12 +28,12 @@ { label: "Google Colab", value: - "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter7/section2_tf.ipynb", + "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/vi/chapter7/section2_tf.ipynb", }, { label: "Aws Studio", value: - "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter7/section2_tf.ipynb", + "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/vi/chapter7/section2_tf.ipynb", }, ]} /> diff --git a/chapters/vi/chapter7/3.mdx b/chapters/vi/chapter7/3.mdx index 0b3ed9827..9a3048af6 100644 --- a/chapters/vi/chapter7/3.mdx +++ b/chapters/vi/chapter7/3.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/vi/chapter7/4.mdx b/chapters/vi/chapter7/4.mdx index d6985ccfc..c9a25dd1d 100644 --- a/chapters/vi/chapter7/4.mdx +++ b/chapters/vi/chapter7/4.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/vi/chapter7/5.mdx b/chapters/vi/chapter7/5.mdx index e3b78514e..c72edb8d0 100644 --- a/chapters/vi/chapter7/5.mdx +++ b/chapters/vi/chapter7/5.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/vi/chapter7/6.mdx b/chapters/vi/chapter7/6.mdx index e58f5301e..0dac41c13 100644 --- a/chapters/vi/chapter7/6.mdx +++ b/chapters/vi/chapter7/6.mdx @@ -10,12 +10,12 @@ { label: "Google Colab", value: - "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter7/section6_pt.ipynb", + "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/vi/chapter7/section6_pt.ipynb", }, { label: "Aws Studio", value: - "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter7/section6_pt.ipynb", + "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/vi/chapter7/section6_pt.ipynb", }, ]} /> @@ -28,12 +28,12 @@ { label: "Google Colab", value: - "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/chapter7/section6_tf.ipynb", + "https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/vi/chapter7/section6_tf.ipynb", }, { label: "Aws Studio", value: - "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter7/section6_tf.ipynb", + "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/vi/chapter7/section6_tf.ipynb", }, ]} /> diff --git a/chapters/vi/chapter7/7.mdx b/chapters/vi/chapter7/7.mdx index ff5a59897..0fa8b0c05 100644 --- a/chapters/vi/chapter7/7.mdx +++ b/chapters/vi/chapter7/7.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/vi/chapter8/2.mdx b/chapters/vi/chapter8/2.mdx index fa70355e8..50d862204 100644 --- a/chapters/vi/chapter8/2.mdx +++ b/chapters/vi/chapter8/2.mdx @@ -3,8 +3,8 @@ Trong phần này, chúng ta sẽ xem xét một số lỗi phổ biến có thể xảy ra khi bạn đang cố gắng tạo dự đoán từ mô hình Transformer mới được điều chỉnh của mình. Điều này sẽ giúp bạn chuẩn bị cho [section 4](/course/chapter8/section4), nơi chúng ta sẽ khám phá cách gỡ lỗi chính giai đoạn huấn luyện. diff --git a/chapters/vi/chapter8/3.mdx b/chapters/vi/chapter8/3.mdx index 328bed409..508421885 100644 --- a/chapters/vi/chapter8/3.mdx +++ b/chapters/vi/chapter8/3.mdx @@ -3,8 +3,8 @@ diff --git a/chapters/vi/chapter8/4.mdx b/chapters/vi/chapter8/4.mdx index 8c84fc127..d6e547090 100644 --- a/chapters/vi/chapter8/4.mdx +++ b/chapters/vi/chapter8/4.mdx @@ -5,8 +5,8 @@ Bạn đã viết một kịch bản tuyệt đẹp để huấn luyện hoặc tinh chỉnh một mô hình trong một tác vụ nhất định, tuân thủ một cách nghiêm túc lời khuyên từ [Chương 7](/course/chapter7). Nhưng khi bạn khởi chạy lệnh `trainr.train()`, một điều kinh khủng xảy ra: bạn gặp lỗi 😱! Hoặc tệ hơn, mọi thứ dường như ổn và quá trình huấn luyện chạy mà không có lỗi, nhưng mô hình kết quả là tồi tệ. Trong phần này, chúng tôi sẽ chỉ cho bạn những gì bạn có thể làm để gỡ lỗi các loại vấn đề này. diff --git a/chapters/vi/chapter8/4_tf.mdx b/chapters/vi/chapter8/4_tf.mdx index 30b65fea6..0061a3d5c 100644 --- a/chapters/vi/chapter8/4_tf.mdx +++ b/chapters/vi/chapter8/4_tf.mdx @@ -5,8 +5,8 @@ Bạn đã viết một kịch bản tuyệt đẹp để huấn luyện hoặc tinh chỉnh một mô hình trong một tác vụ nhất định, tuân thủ một cách nghiêm túc lời khuyên từ [Chương 7](/course/chapter7). Nhưng khi bạn khởi chạy lệnh `model.fit()`, một điều kinh khủng xảy ra: bạn gặp lỗi 😱! Hoặc tệ hơn, mọi thứ dường như ổn và quá trình huấn luyện chạy mà không có lỗi, nhưng mô hình kết quả là tồi tệ. Trong phần này, chúng tôi sẽ chỉ cho bạn những gì bạn có thể làm để gỡ lỗi các loại vấn đề này. diff --git a/chapters/vi/chapter8/5.mdx b/chapters/vi/chapter8/5.mdx index 9fe9c66dd..742660127 100644 --- a/chapters/vi/chapter8/5.mdx +++ b/chapters/vi/chapter8/5.mdx @@ -3,8 +3,8 @@ Khi bạn gặp điều gì đó có vẻ không ổn với một trong các thư viện Hugging Face, bạn chắc chắn nên cho chúng tôi biết để chúng tôi có thể sửa chữa nó (điều này cũng xảy ra với bất kỳ thư viện mã nguồn mở nào, đối với vấn đề đó). Nếu bạn không hoàn toàn chắc chắn liệu lỗi nằm trong mã của riêng bạn hay một trong các thư viện của chúng tôi, nơi đầu tiên cần kiểm tra là [diễn đàn](https://discuss.huggingface.co/). Cộng đồng sẽ giúp bạn tìm ra điều này và nhóm Hugging Face cũng theo dõi chặt chẽ các cuộc thảo luận tại đó. diff --git a/chapters/vi/chapter9/2.mdx b/chapters/vi/chapter9/2.mdx index d71a45cd7..f70a32d2c 100644 --- a/chapters/vi/chapter9/2.mdx +++ b/chapters/vi/chapter9/2.mdx @@ -3,8 +3,8 @@ Hãy bắt đầu bằng cách cài đặt Gradio! Vì nó là một gói Python, chỉ cần chạy: diff --git a/chapters/vi/chapter9/3.mdx b/chapters/vi/chapter9/3.mdx index 40ff8bc04..1659fd788 100644 --- a/chapters/vi/chapter9/3.mdx +++ b/chapters/vi/chapter9/3.mdx @@ -3,8 +3,8 @@ Trong phần này, chúng ta sẽ xem xét kỹ hơn về lớp `Interface` và hiểu các tham số chính được sử dụng để tạo ra nó. diff --git a/chapters/vi/chapter9/4.mdx b/chapters/vi/chapter9/4.mdx index 6de217886..0893954eb 100644 --- a/chapters/vi/chapter9/4.mdx +++ b/chapters/vi/chapter9/4.mdx @@ -3,8 +3,8 @@ Bây giờ bạn đã xây dựng một bản demo, có thể bạn sẽ muốn chia sẻ nó với những người khác. Các bản demo Gradio có thể được chia sẻ theo hai cách: sử dụng một ***đường dẫn chia sẻ tạm thời*** hoặc ***lưu trữ vĩnh viễn trên Spaces***. diff --git a/chapters/vi/chapter9/5.mdx b/chapters/vi/chapter9/5.mdx index 16f10c8da..8e793e25d 100644 --- a/chapters/vi/chapter9/5.mdx +++ b/chapters/vi/chapter9/5.mdx @@ -3,8 +3,8 @@ Để làm cho cuộc sống của bạn trở nên dễ dàng hơn, Gradio tích hợp trực tiếp với Hugging Face Hub và Hugging Face Spaces. Bạn có thể tải các bản demo từ Hub và Spaces chỉ với *một dòng mã*. diff --git a/chapters/vi/chapter9/6.mdx b/chapters/vi/chapter9/6.mdx index 6ea57588d..d09c7ba9b 100644 --- a/chapters/vi/chapter9/6.mdx +++ b/chapters/vi/chapter9/6.mdx @@ -3,8 +3,8 @@ Bây giờ chúng ta có thể xây dựng và chia sẻ giao diện cơ bản, hãy cùng khám phá một số tính năng nâng cao hơn như trạng thái và diễn giải. diff --git a/chapters/vi/chapter9/7.mdx b/chapters/vi/chapter9/7.mdx index 57f675a72..f7e723fe4 100644 --- a/chapters/vi/chapter9/7.mdx +++ b/chapters/vi/chapter9/7.mdx @@ -3,8 +3,8 @@ Trong các phần trước, chúng ta đã tìm hiểu và tạo các bản demo bằng cách sử dụng lớp `Interface`. Trong phần này, chúng tôi sẽ giới thiệu API cấp thấp **mới được phát triển** của mình có tên là `gradio.Blocks`. diff --git a/chapters/zh-CN/chapter1/3.mdx b/chapters/zh-CN/chapter1/3.mdx index ec5820720..dd6d367be 100644 --- a/chapters/zh-CN/chapter1/3.mdx +++ b/chapters/zh-CN/chapter1/3.mdx @@ -3,8 +3,8 @@ 在本节中,我们将看看 Transformer 模型可以做什么,并使用 🤗 Transformers 库中的第一个工具:pipeline() 函数。 diff --git a/chapters/zh-CN/chapter1/8.mdx b/chapters/zh-CN/chapter1/8.mdx index 5bd23953b..0700b2c85 100644 --- a/chapters/zh-CN/chapter1/8.mdx +++ b/chapters/zh-CN/chapter1/8.mdx @@ -3,8 +3,8 @@ 如果您打算在正式的项目中使用经过预训练或经过微调的模型。请注意:虽然这些模型是很强大,但它们也有局限性。其中最大的一个问题是,为了对大量数据进行预训练,研究人员通常会搜集所有他们能找到的内容,中间可能夹带一些意识形态或者价值观的刻板印象。 diff --git a/chapters/zh-CN/chapter2/2.mdx b/chapters/zh-CN/chapter2/2.mdx index fd1c8ba69..993e098d3 100644 --- a/chapters/zh-CN/chapter2/2.mdx +++ b/chapters/zh-CN/chapter2/2.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/zh-CN/chapter2/3.mdx b/chapters/zh-CN/chapter2/3.mdx index 4dbba9df1..e840668d5 100644 --- a/chapters/zh-CN/chapter2/3.mdx +++ b/chapters/zh-CN/chapter2/3.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/zh-CN/chapter2/4.mdx b/chapters/zh-CN/chapter2/4.mdx index cc0fa5bc5..4190508b3 100644 --- a/chapters/zh-CN/chapter2/4.mdx +++ b/chapters/zh-CN/chapter2/4.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/zh-CN/chapter2/5.mdx b/chapters/zh-CN/chapter2/5.mdx index ffa33176d..fc1e62c43 100644 --- a/chapters/zh-CN/chapter2/5.mdx +++ b/chapters/zh-CN/chapter2/5.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/zh-CN/chapter2/6.mdx b/chapters/zh-CN/chapter2/6.mdx index 95381ad9f..7c38c2c8d 100644 --- a/chapters/zh-CN/chapter2/6.mdx +++ b/chapters/zh-CN/chapter2/6.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/zh-CN/chapter3/2.mdx b/chapters/zh-CN/chapter3/2.mdx index cda565d9b..bafc4a250 100644 --- a/chapters/zh-CN/chapter3/2.mdx +++ b/chapters/zh-CN/chapter3/2.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/zh-CN/chapter3/3.mdx b/chapters/zh-CN/chapter3/3.mdx index 92403ecd2..f00d71e12 100644 --- a/chapters/zh-CN/chapter3/3.mdx +++ b/chapters/zh-CN/chapter3/3.mdx @@ -5,8 +5,8 @@ diff --git a/chapters/zh-CN/chapter3/3_tf.mdx b/chapters/zh-CN/chapter3/3_tf.mdx index 9a30ffe4f..c369f06d2 100644 --- a/chapters/zh-CN/chapter3/3_tf.mdx +++ b/chapters/zh-CN/chapter3/3_tf.mdx @@ -5,8 +5,8 @@ 完成上一节中的所有数据预处理工作后,您只剩下最后的几个步骤来训练模型。 但是请注意,`model.fit()` 命令在 CPU 上运行会非常缓慢。 如果您没有GPU,则可以在 [Google Colab](https://colab.research.google.com/) 上使用免费的 GPU 或 TPU(需要梯子)。 diff --git a/chapters/zh-CN/chapter3/4.mdx b/chapters/zh-CN/chapter3/4.mdx index bc44acf7d..99aa4a19e 100644 --- a/chapters/zh-CN/chapter3/4.mdx +++ b/chapters/zh-CN/chapter3/4.mdx @@ -3,8 +3,8 @@ diff --git a/chapters/zh-CN/chapter4/2.mdx b/chapters/zh-CN/chapter4/2.mdx index af3efe96a..d5c7e8046 100644 --- a/chapters/zh-CN/chapter4/2.mdx +++ b/chapters/zh-CN/chapter4/2.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/zh-CN/chapter4/3.mdx b/chapters/zh-CN/chapter4/3.mdx index bbb3c5e39..c9c920dc5 100644 --- a/chapters/zh-CN/chapter4/3.mdx +++ b/chapters/zh-CN/chapter4/3.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/zh-CN/chapter5/2.mdx b/chapters/zh-CN/chapter5/2.mdx index 119e9e931..b92a9d93b 100644 --- a/chapters/zh-CN/chapter5/2.mdx +++ b/chapters/zh-CN/chapter5/2.mdx @@ -3,8 +3,8 @@ 你知道如何使用[Hugging Face Hub](https://huggingface.co/datasets)下载数据集, 但你经常会发现自己正在处理存储在笔记本电脑或远程服务器上的数据。在本节中,我们将向您展示如何使用 🤗 Datasets来加载 Hugging Face Hub 上不可用的数据集。 diff --git a/chapters/zh-CN/chapter5/3.mdx b/chapters/zh-CN/chapter5/3.mdx index ebf199396..5d92ed61b 100644 --- a/chapters/zh-CN/chapter5/3.mdx +++ b/chapters/zh-CN/chapter5/3.mdx @@ -3,8 +3,8 @@ 大多数情况下,您使用的数据都需根据模型所要求的输入进行清洗。在本节中,我们将探索 🤗 Datasets 提供的用于数据集清洗的各种功能。 diff --git a/chapters/zh-CN/chapter5/4.mdx b/chapters/zh-CN/chapter5/4.mdx index ebbe0b81f..28b99365d 100644 --- a/chapters/zh-CN/chapter5/4.mdx +++ b/chapters/zh-CN/chapter5/4.mdx @@ -3,8 +3,8 @@ diff --git a/chapters/zh-CN/chapter5/5.mdx b/chapters/zh-CN/chapter5/5.mdx index 75d8d86b8..6074be979 100644 --- a/chapters/zh-CN/chapter5/5.mdx +++ b/chapters/zh-CN/chapter5/5.mdx @@ -3,8 +3,8 @@ 有时,不存在合适的数据集适用于您构建 NLP 应用,因此您需要自己创建。在本节中,我们将向您展示如何创建一个[GitHub issues](https://github.com/features/issues/)的语料库,GitHub issues通常用于跟踪 GitHub 存储库中的错误或功能。该语料库可用于各种目的,包括: diff --git a/chapters/zh-CN/chapter5/6.mdx b/chapters/zh-CN/chapter5/6.mdx index eab6b539c..429881676 100644 --- a/chapters/zh-CN/chapter5/6.mdx +++ b/chapters/zh-CN/chapter5/6.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/zh-CN/chapter6/2.mdx b/chapters/zh-CN/chapter6/2.mdx index b5f61ee6a..b32d919f7 100644 --- a/chapters/zh-CN/chapter6/2.mdx +++ b/chapters/zh-CN/chapter6/2.mdx @@ -3,8 +3,8 @@ 如果您感兴趣的语言中没有可用的语言模型,或者如果您的语料库与您的语言模型所训练的语料库有很大不同,您很可能希望从适合您的数据的标记器从头开始重新训练模型 . 这将需要在您的数据集上训练一个新的标记器。 但这究竟是什么意思? 当我们在 [第二章](/course/chapter2) 中第一次查看标记器时,我们看到大多数 Transformer 模型使用_子词分词算法_。 为了识别哪些子词是感兴趣的并且在手头的语料库中最常出现,标记器需要仔细查看语料库中的所有文本——我们称之为*training*的过程。 这种训练的确切规则取决于所使用的标记器的类型,我们将在本章后面讨论三种主要算法。 diff --git a/chapters/zh-CN/chapter6/3.mdx b/chapters/zh-CN/chapter6/3.mdx index 52d90f509..22db0d122 100644 --- a/chapters/zh-CN/chapter6/3.mdx +++ b/chapters/zh-CN/chapter6/3.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/zh-CN/chapter6/3b.mdx b/chapters/zh-CN/chapter6/3b.mdx index 433824079..0a290ad68 100644 --- a/chapters/zh-CN/chapter6/3b.mdx +++ b/chapters/zh-CN/chapter6/3b.mdx @@ -7,8 +7,8 @@ {:else} @@ -16,8 +16,8 @@ {/if} diff --git a/chapters/zh-CN/chapter6/4.mdx b/chapters/zh-CN/chapter6/4.mdx index 5304593a0..b43fcbb31 100644 --- a/chapters/zh-CN/chapter6/4.mdx +++ b/chapters/zh-CN/chapter6/4.mdx @@ -3,8 +3,8 @@ 在我们更深入地研究与 Transformer 模型(字节对编码 [BPE]、WordPiece 和 Unigram)一起使用的三种最常见的子词标记化算法之前,我们将首先看一下每个标记器应用于文本的预处理。以下是标记化管道中步骤的高级概述: diff --git a/chapters/zh-CN/chapter6/5.mdx b/chapters/zh-CN/chapter6/5.mdx index ee50b8153..af1170c06 100644 --- a/chapters/zh-CN/chapter6/5.mdx +++ b/chapters/zh-CN/chapter6/5.mdx @@ -3,8 +3,8 @@ 字节对编码(BPE)最初被开发为一种压缩文本的算法,然后在预训练 GPT 模型时被 OpenAI 用于标记化。许多 Transformer 模型都使用它,包括 GPT、GPT-2、RoBERTa、BART 和 DeBERTa。 diff --git a/chapters/zh-CN/chapter6/6.mdx b/chapters/zh-CN/chapter6/6.mdx index 240305f48..898e065ee 100644 --- a/chapters/zh-CN/chapter6/6.mdx +++ b/chapters/zh-CN/chapter6/6.mdx @@ -3,8 +3,8 @@ WordPiece 是 Google 为预训练 BERT 而开发的标记化算法。此后,它在不少基于 BERT 的 Transformer 模型中得到重用,例如 DistilBERT、MobileBERT、Funnel Transformers 和 MPNET。它在训练方面与 BPE 非常相似,但实际标记化的方式不同。 diff --git a/chapters/zh-CN/chapter6/7.mdx b/chapters/zh-CN/chapter6/7.mdx index 96f156801..b5803646c 100644 --- a/chapters/zh-CN/chapter6/7.mdx +++ b/chapters/zh-CN/chapter6/7.mdx @@ -3,8 +3,8 @@ 在 SentencePiece 中经常使用 Unigram 算法,该算法是 AlBERT、T5、mBART、Big Bird 和 XLNet 等模型使用的标记化算法。 diff --git a/chapters/zh-CN/chapter6/8.mdx b/chapters/zh-CN/chapter6/8.mdx index 88921eb2e..43ce8d72f 100644 --- a/chapters/zh-CN/chapter6/8.mdx +++ b/chapters/zh-CN/chapter6/8.mdx @@ -3,8 +3,8 @@ 正如我们在前几节中看到的,标记化包括几个步骤: diff --git a/utils/generate_notebooks.py b/utils/generate_notebooks.py index bd41c5dcb..781fb19b3 100644 --- a/utils/generate_notebooks.py +++ b/utils/generate_notebooks.py @@ -7,8 +7,6 @@ from pathlib import Path -PATH_TO_COURSE = "chapters/en/" - re_framework_test = re.compile(r"^{#if\s+fw\s+===\s+'([^']+)'}\s*$") re_framework_else = re.compile(r"^{:else}\s*$") re_framework_end = re.compile(r"^{/if}\s*$") @@ -22,6 +20,8 @@ frameworks = {"pt": "PyTorch", "tf": "TensorFlow"} +PATH_TO_COURSE = Path("chapters/") + def read_and_split_frameworks(fname): """ @@ -128,57 +128,63 @@ def build_notebook(fname, title, output_dir="."): """ sections = read_and_split_frameworks(fname) sections_with_accelerate = [ - "A full training", - "Token classification (PyTorch)", - "Fine-tuning a masked language model (PyTorch)", - "Translation (PyTorch)", - "Summarization (PyTorch)", - "Training a causal language model from scratch (PyTorch)", - "Question answering (PyTorch)", + "chapter3/4", # "A full training", + "chapter7/2_pt", # "Token classification (PyTorch)", + "chapter7/3_pt", # "Fine-tuning a masked language model (PyTorch)" + "chapter7/4_pt", # "Translation (PyTorch)" + "chapter7/5_pt", # "Summarization (PyTorch)", + "chapter7/6_pt", # "Training a causal language model from scratch (PyTorch)" + "chapter7/7_pt", # "Question answering (PyTorch)" ] sections_with_hf_hub = [ - "Sharing pretrained models (PyTorch)", - "Sharing pretrained models (TensorFlow)", - "Creating your own dataset", - "Token classification (PyTorch)", - "Token classification (TensorFlow)", - "Training a new tokenizer from an old one", - "Fine-tuning a masked language model (PyTorch)", - "Fine-tuning a masked language model (TensorFlow)", - "Translation (PyTorch)", - "Translation (TensorFlow)", - "Summarization (PyTorch)", - "Summarization (TensorFlow)", - "Training a causal language model from scratch (PyTorch)", - "Training a causal language model from scratch (TensorFlow)", - "Question answering (PyTorch)", - "Question answering (TensorFlow)", - "What to do when you get an error", + "chapter4/3_pt", # "Sharing pretrained models (PyTorch)" + "chapter4/3_tf", # "Sharing pretrained models (TensorFlow)" + "chapter5/5", # "Creating your own dataset" + "chapter7/2_pt", # "Token classification (PyTorch)" + "chapter7/2_tf", # "Token classification (TensorFlow)" + "chapter6/2", # "Training a new tokenizer from an old one" + "chapter7/3_pt", # "Fine-tuning a masked language model (PyTorch)" + "chapter7/3_tf", # "Fine-tuning a masked language model (TensorFlow)" + "chapter7/4_pt", # "Translation (PyTorch)" + "chapter7/4_tf", # "Translation (TensorFlow)" + "chapter7/5_pt", # "Summarization (PyTorch)" + "chapter7/5_tf", # "Summarization (TensorFlow)" + "chapter7/6_pt", # "Training a causal language model from scratch (PyTorch)" + "chapter7/6_tf", # "Training a causal language model from scratch (TensorFlow)" + "chapter7/7_pt", # "Question answering (PyTorch)" + "chapter7/7_tf", # "Question answering (TensorFlow)" + "chapter8/2", # "What to do when you get an error" + ] + sections_with_faiss = [ + "chapter5/6_pt", # "Semantic search with FAISS (PyTorch)" + "chapter5/6_tf", # "Semantic search with FAISS (TensorFlow)" ] - sections_with_faiss = ["Semantic search with FAISS (PyTorch)", "Semantic search with FAISS (TensorFlow)"] sections_with_gradio = [ - "Building your first demo", - "Understanding the Interface class", - "Sharing demos with others", - "Integrations with the Hugging Face Hub", - "Advanced Interface features", - "Introduction to Blocks", + "chapter9/2", # "Building your first demo" + "chapter9/3", # "Understanding the Interface class" + "chapter9/4", # "Sharing demos with others" + "chapter9/5", # "Integrations with the Hugging Face Hub" + "chapter9/6", # "Advanced Interface features" + "chapter9/7", # "Introduction to Blocks" ] stem = Path(fname).stem if not isinstance(sections, dict): contents = [sections] titles = [title] fnames = [f"section{stem}.ipynb"] + section_names = [f"{Path(fname).parent.stem}/{stem}"] else: contents = [] titles = [] fnames = [] + section_names = [] for key, section in sections.items(): contents.append(section) titles.append(f"{title} ({frameworks[key]})") fnames.append(f"section{stem}_{key}.ipynb") + section_names.append(f"{Path(fname).parent.stem}/{stem}_{key}") - for title, content, fname in zip(titles, contents, fnames): + for title, content, fname, section_name in zip(titles, contents, fnames, section_names): cells = extract_cells(content) if len(cells) == 0: continue @@ -190,22 +196,22 @@ def build_notebook(fname, title, output_dir="."): # Install cell installs = ["!pip install datasets evaluate transformers[sentencepiece]"] - if title in sections_with_accelerate: + if section_name in sections_with_accelerate: installs.append("!pip install accelerate") installs.append("# To run the training on TPU, you will need to uncomment the followin line:") installs.append( "# !pip install cloud-tpu-client==0.10 torch==1.9.0 https://storage.googleapis.com/tpu-pytorch/wheels/torch_xla-1.9-cp37-cp37m-linux_x86_64.whl" ) - if title in sections_with_hf_hub: + if section_name in sections_with_hf_hub: installs.append("!apt install git-lfs") - if title in sections_with_faiss: + if section_name in sections_with_faiss: installs.append("!pip install faiss-gpu") - if title in sections_with_gradio: + if section_name in sections_with_gradio: installs.append("!pip install gradio") nb_cells.append(nb_cell("\n".join(installs))) - if title in sections_with_hf_hub: + if section_name in sections_with_hf_hub: nb_cells.extend( [ nb_cell( @@ -229,11 +235,11 @@ def build_notebook(fname, title, output_dir="."): nbformat.write(notebook, os.path.join(output_dir, fname), version=4) -def get_titles(): +def get_titles(language): """ Parse the _toctree.yml file to get the correspondence filename to title """ - table = yaml.safe_load(open(os.path.join(PATH_TO_COURSE, "_toctree.yml"), "r")) + table = yaml.safe_load(open(os.path.join(f"chapters/{language}", "_toctree.yml"), "r")) result = {} for entry in table: for section in entry["sections"]: @@ -248,14 +254,16 @@ def get_titles(): return {k: v for k, v in result.items() if "quiz" not in v} -def create_notebooks(output_dir): +def create_notebooks(language, output_dir): + if not os.path.exists(output_dir): + os.makedirs(output_dir) for folder in os.listdir(output_dir): if folder.startswith("chapter"): shutil.rmtree(os.path.join(output_dir, folder)) - titles = get_titles() + titles = get_titles(language) for fname, title in titles.items(): build_notebook( - os.path.join(PATH_TO_COURSE, f"{fname}.mdx"), + os.path.join(f"chapters/{language}", f"{fname}.mdx"), title, os.path.join(output_dir, Path(fname).parent), ) @@ -266,4 +274,11 @@ def create_notebooks(output_dir): parser.add_argument("--output_dir", type=str, help="Where to output the notebooks") args = parser.parse_args() - create_notebooks(args.output_dir) + languages = [f.stem for f in PATH_TO_COURSE.iterdir() if f.is_dir()] + + for language in languages: + language_output_dir = f"{args.output_dir}/{language}" + create_notebooks(language, language_output_dir) + # Remove empty notebook folders + if not any(Path(language_output_dir).iterdir()): + shutil.rmtree(language_output_dir) From 0c1f71819681193425345c8ef9ac50126769ffc2 Mon Sep 17 00:00:00 2001 From: lewtun Date: Fri, 23 Sep 2022 21:10:48 +0200 Subject: [PATCH 32/51] Bump release (#320) --- chapters/en/chapter2/5.mdx | 2 +- chapters/en/chapter5/2.mdx | 2 +- chapters/en/chapter7/4.mdx | 51 ++++++++++++++------------------------ chapters/en/chapter7/5.mdx | 16 ++++++------ chapters/fr/_toctree.yml | 12 ++++----- chapters/fr/chapter0/1.mdx | 2 +- chapters/fr/chapter1/1.mdx | 14 +++++------ chapters/fr/chapter1/3.mdx | 10 ++++---- chapters/fr/chapter3/2.mdx | 8 +++--- chapters/fr/chapter3/6.mdx | 10 ++++---- chapters/fr/chapter5/3.mdx | 8 +++--- chapters/fr/chapter5/5.mdx | 12 ++++----- chapters/fr/chapter7/2.mdx | 6 ++--- chapters/fr/chapter7/3.mdx | 18 +++++++------- chapters/fr/chapter7/4.mdx | 6 ++--- chapters/fr/chapter7/5.mdx | 16 ++++++------ chapters/fr/chapter7/6.mdx | 6 ++--- chapters/fr/chapter7/7.mdx | 6 ++--- chapters/fr/chapter7/8.mdx | 4 +-- chapters/fr/chapter9/6.mdx | 4 +-- 20 files changed, 99 insertions(+), 114 deletions(-) diff --git a/chapters/en/chapter2/5.mdx b/chapters/en/chapter2/5.mdx index 273f73a05..66eeb3090 100644 --- a/chapters/en/chapter2/5.mdx +++ b/chapters/en/chapter2/5.mdx @@ -87,7 +87,7 @@ InvalidArgumentError: Input to reshape is a tensor with 14 values, but the reque 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 it didn't just convert the list of input IDs into a tensor, it added a dimension on top of it: +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: {#if fw === 'pt'} ```py diff --git a/chapters/en/chapter5/2.mdx b/chapters/en/chapter5/2.mdx index 70a36769a..747360a86 100644 --- a/chapters/en/chapter5/2.mdx +++ b/chapters/en/chapter5/2.mdx @@ -46,7 +46,7 @@ SQuAD_it-test.json.gz: 87.4% -- replaced with SQuAD_it-test.json SQuAD_it-train.json.gz: 82.2% -- replaced with SQuAD_it-train.json ``` -We can see that the compressed files have been replaced with _SQuAD_it-train.json_ and _SQuAD_it-text.json_, and that the data is stored in the JSON format. +We can see that the compressed files have been replaced with _SQuAD_it-train.json_ and _SQuAD_it-test.json_, and that the data is stored in the JSON format. diff --git a/chapters/en/chapter7/4.mdx b/chapters/en/chapter7/4.mdx index 73427bcd5..d159a7a69 100644 --- a/chapters/en/chapter7/4.mdx +++ b/chapters/en/chapter7/4.mdx @@ -172,7 +172,7 @@ You should know the drill by now: the texts all need to be converted into sets o from transformers import AutoTokenizer model_checkpoint = "Helsinki-NLP/opus-mt-en-fr" -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, return_tensors="tf") +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, return_tensors="pt") ``` You can also replace the `model_checkpoint` with any other model you prefer from the [Hub](https://huggingface.co/models), or a local folder where you've saved a pretrained model and a tokenizer. @@ -183,36 +183,28 @@ You can also replace the `model_checkpoint` with any other model you prefer from -The preparation of our data is pretty straightforward. There's just one thing to remember: you process the inputs as usual, but for the targets, you need to wrap the tokenizer inside the context manager `as_target_tokenizer()`. +The preparation of our data is pretty straightforward. There's just one thing to remember; you need to ensure that the tokenizer processes the targets in the output language (here, French). You can do this by passing the targets to the `text_targets` argument of the tokenizer's `__call__` method. -A context manager in Python is introduced with the `with` statement and is useful when you have two related operations to execute as a pair. The most common example of this is when you write or read a file, which is often done inside an instruction like: - -``` -with open(file_path) as f: - content = f.read() -``` - -Here the two related operations that are executed as a pair are the actions of opening and closing the file. The object corresponding to the opened file `f` only exists inside the indented block under the `with`; the opening happens before that block and the closing at the end of the block. - -In the case at hand, the context manager `as_target_tokenizer()` will set the tokenizer in the output language (here, French) before the indented block is executed, then set it back in the input language (here, English). - -So, preprocessing one sample looks like this: +To see how this works, let's process one sample of each language in the training set: ```python en_sentence = split_datasets["train"][1]["translation"]["en"] fr_sentence = split_datasets["train"][1]["translation"]["fr"] -inputs = tokenizer(en_sentence) -with tokenizer.as_target_tokenizer(): - targets = tokenizer(fr_sentence) +inputs = tokenizer(en_sentence, text_target=fr_sentence) +inputs ``` -If we forget to tokenize the targets inside the context manager, they will be tokenized by the input tokenizer, which in the case of a Marian model is not going to go well at all: +```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]} +``` + +As we can see, the output contains the input IDs associated with the English sentence, while the IDs associated with the French one are stored in the `labels` field. If you forget to indicate that you are tokenizing labels, they will be tokenized by the input tokenizer, which in the case of a Marian model is not going to go well at all: ```python wrong_targets = tokenizer(fr_sentence) print(tokenizer.convert_ids_to_tokens(wrong_targets["input_ids"])) -print(tokenizer.convert_ids_to_tokens(targets["input_ids"])) +print(tokenizer.convert_ids_to_tokens(inputs["labels"])) ``` ```python out @@ -222,27 +214,22 @@ print(tokenizer.convert_ids_to_tokens(targets["input_ids"])) As we can see, using the English tokenizer to preprocess a French sentence results in a lot more tokens, since the tokenizer doesn't know any French words (except those that also appear in the English language, like "discussion"). -Both `inputs` and `targets` are dictionaries with our usual keys (input IDs, attention mask, etc.), so the last step is to set a `"labels"` key inside the inputs. We do this in the preprocessing function we will apply on the datasets: +Since `inputs` is a dictionary with our usual keys (input IDs, attention mask, etc.), the last step is to define the preprocessing function we will apply on the datasets: ```python -max_input_length = 128 -max_target_length = 128 +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, max_length=max_input_length, truncation=True) - - # Set up the tokenizer for targets - with tokenizer.as_target_tokenizer(): - labels = tokenizer(targets, max_length=max_target_length, truncation=True) - - model_inputs["labels"] = labels["input_ids"] + model_inputs = tokenizer( + inputs, text_target=targets, max_length=max_length, truncation=True + ) return model_inputs ``` -Note that we set similar maximum lengths for our inputs and outputs. Since the texts we're dealing with seem pretty short, we use 128. +Note that we set the same maximum length for our inputs and outputs. Since the texts we're dealing with seem pretty short, we use 128. @@ -724,7 +711,7 @@ trainer = Seq2SeqTrainer( Before training, we'll first look at the score our model gets, to double-check that we're not making things worse with our fine-tuning. This command will take a bit of time, so you can grab a coffee while it executes: ```python -trainer.evaluate(max_length=max_target_length) +trainer.evaluate(max_length=max_length) ``` ```python out @@ -748,7 +735,7 @@ Note that while the training happens, each time the model is saved (here, every Once training is done, we evaluate our model again -- hopefully we will see some amelioration in the BLEU score! ```py -trainer.evaluate(max_length=max_target_length) +trainer.evaluate(max_length=max_length) ``` ```python out diff --git a/chapters/en/chapter7/5.mdx b/chapters/en/chapter7/5.mdx index 6b2fa1cf0..a45108bcf 100644 --- a/chapters/en/chapter7/5.mdx +++ b/chapters/en/chapter7/5.mdx @@ -276,7 +276,7 @@ tokenizer.convert_ids_to_tokens(inputs.input_ids) The special Unicode character `▁` and end-of-sequence token `` indicate that we're dealing with the SentencePiece tokenizer, which is based on the Unigram segmentation algorithm discussed in [Chapter 6](/course/chapter6). Unigram is especially useful for multilingual corpora since it allows SentencePiece to be agnostic about accents, punctuation, and the fact that many languages, like Japanese, do not have whitespace characters. -To tokenize our corpus, we have to deal with a subtlety associated with summarization: because our labels are also text, it is possible that they exceed the model's maximum context size. This means we need to apply truncation to both the reviews and their titles to ensure we don't pass excessively long inputs to our model. The tokenizers in 🤗 Transformers provide a nifty `as_target_tokenizer()` function that allows you to tokenize the labels in parallel to the inputs. This is typically done using a context manager inside a preprocessing function that first encodes the inputs, and then encodes the labels as a separate column. Here is an example of such a function for mT5: +To tokenize our corpus, we have to deal with a subtlety associated with summarization: because our labels are also text, it is possible that they exceed the model's maximum context size. This means we need to apply truncation to both the reviews and their titles to ensure we don't pass excessively long inputs to our model. The tokenizers in 🤗 Transformers provide a nifty `text_target` argument that allows you to tokenize the labels in parallel to the inputs. Here is an example of how the inputs and targets are processed for mT5: ```python max_input_length = 512 @@ -285,19 +285,17 @@ max_target_length = 30 def preprocess_function(examples): model_inputs = tokenizer( - examples["review_body"], max_length=max_input_length, truncation=True + examples["review_body"], + max_length=max_input_length, + truncation=True, ) - # Set up the tokenizer for targets - with tokenizer.as_target_tokenizer(): - labels = tokenizer( - examples["review_title"], max_length=max_target_length, truncation=True - ) - + labels = tokenizer(text_target=targets, max_length=max_target_length, truncation=True) model_inputs["labels"] = labels["input_ids"] + model_inputs["labels_mask"] = labels["attention_mask"] return model_inputs ``` -Let's walk through this code to understand what's happening. The first thing we've done is define values for `max_input_length` and `max_target_length`, which set the upper limits for how long our reviews and titles can be. Since the review body is typically much larger than the title, we've scaled these values accordingly. Then, in the `preprocess_function()` itself we can see the reviews are first tokenized, followed by the titles with `as_target_tokenizer()`. +Let's walk through this code to understand what's happening. The first thing we've done is define values for `max_input_length` and `max_target_length`, which set the upper limits for how long our reviews and titles can be. Since the review body is typically much larger than the title, we've scaled these values accordingly. With `preprocess_function()`, it is then a simple matter to tokenize the whole corpus using the handy `Dataset.map()` function we've used extensively throughout this course: diff --git a/chapters/fr/_toctree.yml b/chapters/fr/_toctree.yml index a0f4fd015..930c633ca 100644 --- a/chapters/fr/_toctree.yml +++ b/chapters/fr/_toctree.yml @@ -10,13 +10,13 @@ - local: chapter1/2 title: Traitement du langage naturel - local: chapter1/3 - title: Que peut-on faire avec les transformers ? + title: Que peuvent faire les transformers ? - local: chapter1/4 title: Comment fonctionnent les transformers ? - local: chapter1/5 - title: Les modèles encodeur + title: Les modèles basés sur l'encodeur - local: chapter1/6 - title: Les modèles décodeur + title: Les modèles basés sur le décodeur - local: chapter1/7 title: Les modèles de séquence-à-séquence - local: chapter1/8 @@ -52,7 +52,7 @@ - local: chapter3/1 title: Introduction - local: chapter3/2 - title: Traîter les données + title: Préparer les données - local: chapter3/3 title: Finetuner un modèle avec l'API Trainer API ou Keras local_fw: { pt: chapter3/3, tf: chapter3/3_tf } @@ -111,7 +111,7 @@ - local: chapter6/3b title: Les tokenizers rapides dans le pipeline de QA - local: chapter6/4 - title: Normalisation et pré-tokénisation + title: Normalisation et prétokénisation - local: chapter6/5 title: Le tokenizer Byte-Pair Encoding - local: chapter6/6 @@ -157,7 +157,7 @@ - local: chapter8/3 title: Demander de l'aide sur les forums - local: chapter8/4 - title: Déboguer le pipeline d'entraînement + title: Débogage du pipeline d'entraînement local_fw : { pt : chapter8/4, tf : chapter8/4_tf } - local: chapter8/5 title: Comment rédiger une bonne issue diff --git a/chapters/fr/chapter0/1.mdx b/chapters/fr/chapter0/1.mdx index b3a5b78ba..5a1be8e8a 100644 --- a/chapters/fr/chapter0/1.mdx +++ b/chapters/fr/chapter0/1.mdx @@ -1,6 +1,6 @@ # Introduction -Bienvenue au cours d'Hugging Face ! Cette introduction est là pour vous guider dans la mise en place d'un environnement de travail. Si vous venez de commencer le cours, nous vous recommandons de consulter d'abord le [chapitre 1](/course/fr/chapter1) puis de revenir et de configurer votre environnement afin de pouvoir essayer le code vous-même. +Bienvenue au cours d'Hugging Face ! Cette introduction est là pour vous guider dans la mise en place d'un environnement de travail. Si vous venez de commencer le cours, nous vous recommandons de consulter d'abord le [chapitre 1](/course/fr/chapter1) puis de revenir ici et de configurer votre environnement afin de pouvoir essayer le code vous-même. Toutes les bibliothèques que nous utiliserons dans ce cours sont disponibles sous forme de *packages* Python. Nous allons donc vous montrer comment configurer un environnement Python et installer les bibliothèques spécifiques dont vous aurez besoin. diff --git a/chapters/fr/chapter1/1.mdx b/chapters/fr/chapter1/1.mdx index 6dd1423c5..e0f68760d 100644 --- a/chapters/fr/chapter1/1.mdx +++ b/chapters/fr/chapter1/1.mdx @@ -20,7 +20,7 @@ Voici un bref aperçu du cours :
-- Les chapitres 1 à 4 présentent les principaux concepts de la bibliothèque 🤗 *Transformers*. À la fin de ce chapitre, vous serez familier avec le fonctionnement des *transformers* et vous saurez comment utiliser un modèle présent sur le [*Hub*](https://huggingface.co/models), le *finetuner* sur un jeu de données, et partager vos résultats sur le *Hub* ! +- Les chapitres 1 à 4 présentent les principaux concepts de la bibliothèque 🤗 *Transformers*. À la fin de ces chapitres, vous serez familier avec le fonctionnement des *transformers* et vous saurez comment utiliser un modèle présent sur le [*Hub*](https://huggingface.co/models), le *finetuner* sur un jeu de données, et partager vos résultats sur le *Hub* ! - Les chapitres 5 à 8 présentent les bases des librairies 🤗 *Datasets* et 🤗 *Tokenizers* ainsi qu'une découverte des problèmes classiques de NLP. À la fin de ce chapitre, vous serez capable de résoudre les problèmes de NLP les plus communs par vous-même. - Les chapitres 9 à 12 proposent d'aller plus loin et d'explorer comment les *transformers* peuvent être utilisés pour résoudre des problèmes de traitement de la parole et de vision par ordinateur. En suivant ces chapitres, vous apprendrez à construire et à partager vos modèles via des démonstrateurs, et vous serez capable d'optimiser ces modèles pour des environnements de production. Enfin, vous serez prêt à appliquer 🤗 *Transformers* à (presque) n'importe quel problème d'apprentissage automatique ! @@ -30,27 +30,27 @@ Ce cours : * se comprend mieux si vous avez déjà suivi un cours d'introduction à l'apprentissage profond comme [fast.ai's](https://www.fast.ai/), [*Practical Deep Learning for Coders*](https://course.fast.ai/) ou un des cours développés par [*DeepLearning.AI*](https://www.deeplearning.ai/), * n'attend pas une connaissance appronfondie de [PyTorch](https://pytorch.org/) ou de [TensorFlow](https://www.tensorflow.org/), bien qu'être familiarisé avec l'un d'entre eux peut aider. -Après avoir terminé ce cours, nous vous recommandons de suivre la [Spécialisation en NLP](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh) dispensée par DeepLearning.AI, qui couvre une grande partie des modèles traditionnels de NLP comme le Bayésien naïf et les LSTMs qui sont importants à connaître! +Après avoir terminé ce cours, nous vous recommandons de suivre la [Spécialisation en NLP](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh) dispensée par DeepLearning.AI, qui couvre une grande partie des modèles traditionnels de NLP comme le Bayésien naïf et les LSTMs qui sont importants à connaître ! ## Qui sommes-nous ? À propos des auteurs de ce cours : -*Abubakar Abid** a obtenu son doctorat à Stanford en apprentissage automatique appliqué. Pendant son doctorat, il a fondé [Gradio](https://github.com/gradio-app/gradio), une bibliothèque Python open-source qui a été utilisée pour construire plus de 600 000 démos d'apprentissage automatique. Gradio a été rachetée par Hugging Face, où Abubakar occupe désormais le poste de responsable de l'équipe d'apprentissage automatique. +**Abubakar Abid** a obtenu son doctorat en apprentissage automatique appliqué à Stanford. Pendant son doctorat, il a fondé [Gradio](https://github.com/gradio-app/gradio), une bibliothèque Python *open source* qui a été utilisée pour construire plus de 600 000 démos d'apprentissage automatique. Gradio a été rachetée par Hugging Face, où Abubakar occupe désormais le poste de responsable de l'équipe d'apprentissage automatique. **Matthew Carrigan** est ingénieur en apprentissage machine chez Hugging Face. Il vit à Dublin en Irlande. Il a travaillé auparavant comme ingénieur en apprentissage machine chez Parse.ly et avant cela comme chercheur postdoctoral au Trinity College Dublin. Il ne croit pas que nous arrivions à l'*AGI* en mettant à l'échelle les architectures existantes mais a tout de même beaucoup d'espoir dans l'immortalité des robots. **Lysandre Debut** est ingénieur en apprentissage machine chez Hugging Face et a travaillé sur la bibliothèque 🤗 *Transformers* depuis les premières phases de développement. Son but est de rendre le NLP accessible à tous en développant des outils disposant d'une API très simple. -**Sylvain Gugger** est ingénieur recherche chez Hugging Face et un des principaux responsables de la bibliothèque 🤗 *Transformers*. Avant cela, il était chercheur en en apprentissage machine chez fast.ai et a écrit le livre [*Deep Learning for Coders with fastai and PyTorch*](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/) avec Jeremy Howard. Son but est de rendre l'apprentissage profond plus accessible, en développant et en améliorant des techniques permettant aux modèles d'apprendre rapidement sur des ressources limitées. +**Sylvain Gugger** est ingénieur de recherche chez Hugging Face et un des principaux responsables de la bibliothèque 🤗 *Transformers*. Avant cela, il était chercheur en apprentissage machine chez fast.ai et a écrit le livre [*Deep Learning for Coders with fastai and PyTorch*](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/) avec Jeremy Howard. Son but est de rendre l'apprentissage profond plus accessible en développant et en améliorant des techniques permettant aux modèles d'apprendre rapidement sur des ressources limitées. -**Dawood Khan** est un ingénieur en apprentissage automatique chez Hugging Face. Il vient de New York et est diplômé de l'Université de New York en informatique. Après avoir travaillé comme ingénieur iOS pendant quelques années, Dawood a quitté son poste pour créer Gradio avec ses cofondateurs. Gradio a finalement été acquis par Hugging Face. +**Dawood Khan** est un ingénieur en apprentissage automatique chez Hugging Face. Il vient de New York et est diplômé en informatique de l’Université de New York. Après avoir travaillé comme ingénieur iOS pendant quelques années, Dawood a quitté son poste pour créer Gradio avec ses cofondateurs. Gradio a finalement été acquis par Hugging Face. **Merve Noyan** est développeuse *advocate* chez Hugging Face et travaille à la création d'outils et de contenus visant à démocratiser l'apprentissage machine pour tous. -**Lucile Saulnier** est ingénieure en apprentissage machine chez Hugging Face et travaille au développement et à l'implémentation de nombreux outils *open source*. Elle est également activement impliquée dans de nombreux projets de recherche dans le domaine du NLP comme l'entraînement collaboratif de modèles et le projet BigScience. +**Lucile Saulnier** est ingénieure en apprentissage machine chez Hugging Face et travaille au développement et à l'implémentation de nombreux outils *open source*. Elle est également activement impliquée dans de nombreux projets de recherche dans le domaine du NLP comme l'entraînement collaboratif de modèles et le projet [BigScience](https://bigscience.huggingface.co/). -**Lewis Tunstall** est ingénieur en apprentissage machine chez Hugging Face et dévoué au développement d'outils open source avec la volonté de les rendre accessibles à une communauté plus large. Il est également co-auteur du livre [*Natural Language Processing with Transformers*](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). +**Lewis Tunstall** est ingénieur en apprentissage machine chez Hugging Face et dévoué au développement d'outils *open source* avec la volonté de les rendre accessibles à une communauté plus large. Il est également co-auteur du livre [*Natural Language Processing with Transformers*](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). **Leandro von Werra** est ingénieur en apprentissage machine dans l'équipe *open source* d'Hugging Face et également co-auteur du livre [*Natural Language Processing with Transformers*](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). Il a plusieurs années d'expérience dans l'industrie où il a pu déployer des projets de NLP en production et travailler sur toutes les étapes clefs du déploiement. diff --git a/chapters/fr/chapter1/3.mdx b/chapters/fr/chapter1/3.mdx index 0b0a1b0b4..c61350d6c 100644 --- a/chapters/fr/chapter1/3.mdx +++ b/chapters/fr/chapter1/3.mdx @@ -21,7 +21,7 @@ Les *transformers* sont utilisés pour résoudre toute sorte de tâches de NLP c Companies using Hugging Face -La bibliothèque [🤗 *Transformers*](https://github.com/huggingface/transformers) fournit toutes les fonctionnalités nécessaires pour créer et utiliser les modèles partagés. Le [*Hub*](https://huggingface.co/models) contient des milliers de modèles pré-entraînés que n'importe qui peut télécharger et utiliser. Vous pouvez également transférer vos propres modèles vers le Hub ! +La bibliothèque [🤗 *Transformers*](https://github.com/huggingface/transformers) fournit toutes les fonctionnalités nécessaires pour créer et utiliser les modèles partagés. Le [*Hub*](https://huggingface.co/models) contient des milliers de modèles pré-entraînés que n'importe qui peut télécharger et utiliser. Vous pouvez également transférer vos propres modèles vers le *Hub* ! ⚠️ Le Hub n'est pas limité aux transformers. Tout le monde peut partager n'importe quel modèle ou jeu de données s'il le souhaite ! Créez un compte sur huggingface.co pour bénéficier de toutes les fonctionnalités disponibles ! @@ -199,9 +199,9 @@ Tous les modèles peuvent être testé directement depuis votre navigateur en ut L'API d'inférence qui est utilisée par le *widget* est également disponible en tant que produit payant si vous avez besoin de l'API pour votre travail. Consultez la [page des prix](https://huggingface.co/pricing) pour plus de détails. -## Remplacement des mots manquants +## Remplacement des mots manqués -Le prochain pipeline que vous allez essayer est celui de `fill-mask`. L'idée de cette tâche est de remplir les mots manquants d'un texte donné : +Le prochain pipeline que vous allez essayer est celui de `fill-mask`. L'idée de cette tâche est de remplir les mots manqués d'un texte donné : ```python from transformers import pipeline @@ -217,7 +217,7 @@ unmasker("This course will teach you all about models.", top_k=2) 'token': 30412, 'token_str': ' mathematical'}, {'sequence': 'This course will teach you all about computational models.', - # Ce cours vous apprendra tout sur les modèles mathématiques. + # Ce cours vous apprendra tout sur les modèles de calcul. 'score': 0.04052725434303284, 'token': 38163, 'token_str': ' computational'}] @@ -374,7 +374,7 @@ Comme pour la génération de texte et le résumé de texte, il est possible de -✏️ **Essayez !** Recherchez d'autres modèles de traduction sur le Hub et essayez de traduire la phrase précédente en plusieurs langues différentes. +✏️ **Essayez !** Recherchez d'autres modèles de traduction sur le *Hub* et essayez de traduire la phrase précédente en plusieurs langues différentes. diff --git a/chapters/fr/chapter3/2.mdx b/chapters/fr/chapter3/2.mdx index e736e0905..ad6ef83fb 100644 --- a/chapters/fr/chapter3/2.mdx +++ b/chapters/fr/chapter3/2.mdx @@ -289,13 +289,13 @@ La dernière chose que nous devrons faire est de remplir tous les exemples à la {#if fw === 'pt'} -La fonction qui est responsable de l'assemblage des échantillons dans un batch est appelée *fonction de rassemblement*. C'est un argument que vous pouvez passer quand vous construisez un `DataLoader`, la valeur par défaut étant une fonction qui va juste convertir vos échantillons en tenseurs PyTorch et les concaténer (récursivement si vos éléments sont des listes, des *tuples* ou des dictionnaires). Cela ne sera pas possible dans notre cas puisque les entrées que nous avons ne seront pas toutes de la même taille. Nous avons délibérément reporté le *padding*, pour ne l'appliquer que si nécessaire sur chaque batch et éviter d'avoir des entrées trop longues avec beaucoup de remplissage. Cela accélère considérablement l'entraînement, mais notez que si vous vous entraînez sur un TPU, cela peut poser des problèmes. En effet, les TPU préfèrent les formes fixes, même si cela nécessite un *padding* supplémentaire. +La fonction qui est responsable de l'assemblage des échantillons dans un batch est appelée *fonction d'assemblement*. C'est un argument que vous pouvez passer quand vous construisez un `DataLoader`, la valeur par défaut étant une fonction qui va juste convertir vos échantillons en tenseurs PyTorch et les concaténer (récursivement si vos éléments sont des listes, des *tuples* ou des dictionnaires). Cela ne sera pas possible dans notre cas puisque les entrées que nous avons ne seront pas toutes de la même taille. Nous avons délibérément reporté le *padding*, pour ne l'appliquer que si nécessaire sur chaque batch et éviter d'avoir des entrées trop longues avec beaucoup de remplissage. Cela accélère considérablement l'entraînement, mais notez que si vous vous entraînez sur un TPU, cela peut poser des problèmes. En effet, les TPU préfèrent les formes fixes, même si cela nécessite un *padding* supplémentaire. {:else} -La fonction qui est responsable de l'assemblage des échantillons dans un batch est appelée *fonction de rassemblement*. C'est un argument que vous pouvez passer quand vous construisez un `DataLoader`, la valeur par défaut étant une fonction qui va juste convertir vos échantillons en type tf.Tensor et les concaténer (récursivement si les éléments sont des listes, des *tuples* ou des dictionnaires). Cela ne sera pas possible dans notre cas puisque les entrées que nous avons ne seront pas toutes de la même taille. Nous avons délibérément reporté le *padding*, pour ne l'appliquer que si nécessaire sur chaque batch et éviter d'avoir des entrées trop longues avec beaucoup de remplissage. Cela accélère considérablement l'entraînement, mais notez que si vous vous entraînez sur un TPU, cela peut poser des problèmes. En effet, les TPU préfèrent les formes fixes, même si cela nécessite un *padding* supplémentaire. +La fonction qui est responsable de l'assemblage des échantillons dans un batch est appelée *fonction d'assemblement*. C'est un argument que vous pouvez passer quand vous construisez un `DataLoader`, la valeur par défaut étant une fonction qui va juste convertir vos échantillons en type tf.Tensor et les concaténer (récursivement si les éléments sont des listes, des *tuples* ou des dictionnaires). Cela ne sera pas possible dans notre cas puisque les entrées que nous avons ne seront pas toutes de la même taille. Nous avons délibérément reporté le *padding*, pour ne l'appliquer que si nécessaire sur chaque batch et éviter d'avoir des entrées trop longues avec beaucoup de remplissage. Cela accélère considérablement l'entraînement, mais notez que si vous vous entraînez sur un TPU, cela peut poser des problèmes. En effet, les TPU préfèrent les formes fixes, même si cela nécessite un *padding* supplémentaire. {/if} -Pour faire cela en pratique, nous devons définir une fonction de rassemblement qui appliquera la bonne quantité de *padding* aux éléments du jeu de données que nous voulons regrouper. Heureusement, la bibliothèque 🤗 *Transformers* nous fournit une telle fonction via `DataCollatorWithPadding`. Elle prend un *tokenizer* lorsque vous l'instanciez (pour savoir quel *token* de *padding* utiliser et si le modèle s'attend à ce que le *padding* soit à gauche ou à droite des entrées) et fera tout ce dont vous avez besoin : +Pour faire cela en pratique, nous devons définir une fonction d'assemblement qui appliquera la bonne quantité de *padding* aux éléments du jeu de données que nous voulons regrouper. Heureusement, la bibliothèque 🤗 *Transformers* nous fournit une telle fonction via `DataCollatorWithPadding`. Elle prend un *tokenizer* lorsque vous l'instanciez (pour savoir quel *token* de *padding* utiliser et si le modèle s'attend à ce que le *padding* soit à gauche ou à droite des entrées) et fera tout ce dont vous avez besoin : {#if fw === 'pt'} ```py @@ -360,7 +360,7 @@ C'est beau ! Maintenant que nous sommes passés du texte brut à des batchs que {#if fw === 'tf'} -Maintenant que nous disposons de notre jeu de données et d'un collecteur de données, nous devons les assembler. Nous pourrions charger manuellement des batchs et les assembler mais c'est beaucoup de travail et probablement pas très performant non plus. A la place, il existe une méthode simple qui offre une solution performante à ce problème : `to_tf_dataset()`. Cela va envelopper un `tf.data.Dataset` autour de votre jeu de données, avec une fonction de collation optionnelle. `tf.data.Dataset` est un format natif de TensorFlow que Keras peut utiliser pour `model.fit()`, donc cette seule méthode convertit immédiatement un *dataset* en un format prêt pour l'entraînement. Voyons cela en action avec notre jeu de données ! +Maintenant que nous disposons de notre jeu de données et d'un assembleur de données, nous devons les assembler. Nous pourrions charger manuellement des batchs et les assembler mais c'est beaucoup de travail et probablement pas très performant non plus. A la place, il existe une méthode simple qui offre une solution performante à ce problème : `to_tf_dataset()`. Cela va envelopper un `tf.data.Dataset` autour de votre jeu de données, avec une fonction de collation optionnelle. `tf.data.Dataset` est un format natif de TensorFlow que Keras peut utiliser pour `model.fit()`, donc cette seule méthode convertit immédiatement un *dataset* en un format prêt pour l'entraînement. Voyons cela en action avec notre jeu de données ! ```py tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( diff --git a/chapters/fr/chapter3/6.mdx b/chapters/fr/chapter3/6.mdx index 5379a1fbe..b192f643a 100644 --- a/chapters/fr/chapter3/6.mdx +++ b/chapters/fr/chapter3/6.mdx @@ -126,26 +126,26 @@ Testez ce que vous avez appris dans ce chapitre ! ]} /> -### 6. Quel est le but d'une fonction de rassemblement ? +### 6. Quel est le but d'une fonction d'assemblement ? DataCollatorWithPadding." + explain: "Une fonction d'assemblement est impliquée dans le traitement des batchs individuels, et non de tout le jeu de données. De plus, nous parlons de fonctions génériques et pas spécialement du DataCollatorWithPadding." }, { text: "Elle rassemble tous les échantillons dans un batch.", - explain: "Vous pouvez passer la fonction de rassemblement comme argument d'une fonction DataLoader. Nous avons utilisé la fonction DataCollatorWithPadding qui remplit tous les éléments d'un batch pour qu'ils aient la même longueur.", + explain: "Vous pouvez passer la fonction d'assemblement comme argument d'une fonction DataLoader. Nous avons utilisé la fonction DataCollatorWithPadding qui remplit tous les éléments d'un batch pour qu'ils aient la même longueur.", correct: true }, { text: "Elle pré-traite tout le jeu de données.", - explain: "Ce serait une fonction de prétraitement, pas une fonction de rassemblement." + explain: "Ce serait une fonction de prétraitement, pas une fonction d'assemblement." }, { text: "Elle tronque les séquences dans le jeu de données.", - explain: "Une fonction de rassemblement est impliquée dans le traitement des batchs individuels, et non de tout le jeu de données. Si vous êtes intéressé par la troncature, vous pouvez utiliser la fonction truncate en argument du tokenizer." + explain: "Une fonction d'assemblement est impliquée dans le traitement des batchs individuels, et non de tout le jeu de données. Si vous êtes intéressé par la troncature, vous pouvez utiliser la fonction truncate en argument du tokenizer." } ]} /> diff --git a/chapters/fr/chapter5/3.mdx b/chapters/fr/chapter5/3.mdx index 0254a9ff4..def2774c7 100644 --- a/chapters/fr/chapter5/3.mdx +++ b/chapters/fr/chapter5/3.mdx @@ -30,7 +30,7 @@ Nous devons d'abord télécharger et extraire les données, ce qui peut être fa from datasets import load_dataset data_files = {"train": "drugsComTrain_raw.tsv", "test": "drugsComTest_raw.tsv"} -# \t is the tab character in Python +# \t est le caractère de tabulation en Python drug_dataset = load_dataset("csv", data_files=data_files, delimiter="\t") ``` @@ -423,7 +423,7 @@ def tokenize_and_split(examples): max_length=128, return_overflowing_tokens=True, ) - # Extract mapping between new and old indices + # Extraire la correspondance entre les nouveaux et les anciens indices sample_map = result.pop("overflow_to_sample_mapping") for key, values in examples.items(): result[key] = [values[i] for i in sample_map] @@ -622,9 +622,9 @@ Bien que nous ayons un jeu de test que nous pourrions utiliser pour l'évaluatio ```py drug_dataset_clean = drug_dataset["train"].train_test_split(train_size=0.8, seed=42) -# Rename the default "test" split to "validation" +# Renommer la division par défaut "test" en "validation" drug_dataset_clean["validation"] = drug_dataset_clean.pop("test") -# Add the "test" set to our `DatasetDict` +# Ajoutez le jeu "test" à notre `DatasetDict` drug_dataset_clean["test"] = drug_dataset["test"] drug_dataset_clean ``` diff --git a/chapters/fr/chapter5/5.mdx b/chapters/fr/chapter5/5.mdx index e25dccc9a..36112a4a3 100644 --- a/chapters/fr/chapter5/5.mdx +++ b/chapters/fr/chapter5/5.mdx @@ -10,7 +10,7 @@ Parfois, le jeu de données dont vous avez besoin pour créer une application de NLP n'existe pas. Vous devrez donc le créer vous-même. Dans cette section, nous allons vous montrer comment créer un corpus de [problèmes GitHub](https://github.com/features/issues/), qui sont couramment utilisés pour suivre les bogues ou les fonctionnalités dans les dépôts GitHub. Ce corpus pourrait être utilisé à diverses fins, notamment : * explorer combien de temps il faut pour fermer les problèmes ouverts ou les *pull requests* -* entraîner d'un _classificateur multilabel_ capable d'étiqueter les problèmes avec des métadonnées basées sur la description du problème (par exemple : « bug », « amélioration » ou « question ») +* entraîner d'un _classifieur multilabel_ capable d'étiqueter les problèmes avec des métadonnées basées sur la description du problème (par exemple : « bug », « amélioration » ou « question ») * créer un moteur de recherche sémantique pour trouver les problèmes correspondant à la requête d'un utilisateur Ici, nous nous concentrerons sur la création du corpus, et dans la section suivante, nous aborderons l'application de recherche sémantique. Pour garder les choses méta, nous utiliserons les problèmes GitHub associés à un projet open source populaire : 🤗 *Datasets* ! Voyons comment obtenir les données et explorons les informations contenues dans ces problèmes. @@ -122,7 +122,7 @@ Waouh, ça fait beaucoup d'informations ! Nous pouvons voir des champs utiles co Comme décrit dans la [documentation GitHub](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting), les requêtes non authentifiées sont limitées à 60 requêtes par heure. Bien que vous puissiez augmenter le paramètre de requête `per_page` pour réduire le nombre de requêtes que vous effectuez, vous atteindrez toujours la limite de débit sur tout dépôt contenant des milliers de problèmes. Donc, à la place, vous devez suivre les [instructions de GitHub](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) sur la création d'un _jeton d'accès personnel_ afin que vous peut augmenter la limite de débit à 5 000 requêtes par heure. Une fois que vous avez votre *token*, vous pouvez l'inclure dans l'en-tête de la requête : ```py -GITHUB_TOKEN = xxx # Copy your GitHub token here +GITHUB_TOKEN = xxx # Copiez votre jeton GitHub ici headers = {"Authorization": f"token {GITHUB_TOKEN}"} ``` @@ -154,19 +154,19 @@ def fetch_issues( batch = [] all_issues = [] - per_page = 100 # Number of issues to return per page + per_page = 100 # Nombre d'issues à retourner par page num_pages = math.ceil(num_issues / per_page) base_url = "https://api.github.com/repos" for page in tqdm(range(num_pages)): - # Query with state=all to get both open and closed issues + # Requête avec state=all pour obtenir les issues ouvertes et fermées query = f"issues?page={page}&per_page={per_page}&state=all" issues = requests.get(f"{base_url}/{owner}/{repo}/{query}", headers=headers) batch.extend(issues.json()) if len(batch) > rate_limit and len(all_issues) < num_issues: all_issues.extend(batch) - batch = [] # Flush batch for next time period + batch = [] # Vider le batch pour la période de temps suivante print(f"Reached GitHub rate limit. Sleeping for one hour ...") time.sleep(60 * 60 + 1) @@ -212,7 +212,7 @@ L'extrait ci-dessus de la documentation de GitHub nous indique que la colonne `p ```py sample = issues_dataset.shuffle(seed=666).select(range(3)) -# Print out the URL and pull request entries +# Afficher l'URL et les entrées de la PR for url, pr in zip(sample["html_url"], sample["pull_request"]): print(f">> URL: {url}") print(f">> Pull request: {pr}\n") diff --git a/chapters/fr/chapter7/2.mdx b/chapters/fr/chapter7/2.mdx index eb7c5636a..653ca7a13 100644 --- a/chapters/fr/chapter7/2.mdx +++ b/chapters/fr/chapter7/2.mdx @@ -286,7 +286,7 @@ def tokenize_and_align_labels(examples): return tokenized_inputs ``` -Notez que nous n'avons pas encore rembourré nos entrées. Nous le ferons plus tard lors de la création des batchs avec un collateur de données. +Notez que nous n'avons pas encore rembourré nos entrées. Nous le ferons plus tard lors de la création des batchs avec un assembleur de données. Nous pouvons maintenant appliquer tout ce prétraitement en une seule fois sur les autres divisions de notre jeu de données : @@ -315,7 +315,7 @@ Le code utilisant Keras sera très similaire au précédent. Les seuls changemen {/if} -### Collation des données +### Assemblage des données Nous ne pouvons pas simplement utiliser un `DataCollatorWithPadding` comme dans le [chapitre 3](/course/fr/chapter3) car cela ne fait que rembourrer les entrées (identifiants d'entrée, masque d'attention et *token* de type identifiants). Ici, nos étiquettes doivent être rembourréés exactement de la même manière que les entrées afin qu'elles gardent la même taille, en utilisant `-100` comme valeur afin que les prédictions correspondantes soient ignorées dans le calcul de la perte. @@ -371,7 +371,7 @@ Comme nous pouvons le voir, le deuxième jeu d'étiquettes a été complété à {:else} -Notre collateur de données est prêt à fonctionner ! Maintenant, utilisons-le pour créer un `tf.data.Dataset` avec la méthode `to_tf_dataset()`. +Notre assembleur de données est prêt à fonctionner ! Maintenant, utilisons-le pour créer un `tf.data.Dataset` avec la méthode `to_tf_dataset()`. ```py tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( diff --git a/chapters/fr/chapter7/3.mdx b/chapters/fr/chapter7/3.mdx index 0e4e5554a..664624b5b 100644 --- a/chapters/fr/chapter7/3.mdx +++ b/chapters/fr/chapter7/3.mdx @@ -443,7 +443,7 @@ tokenizer.decode(lm_datasets["train"][1]["labels"]) ".... 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" ``` -Comme prévu par notre fonction `group_texts()` ci-dessus, cela semble identique aux `input_ids` décodés. Mais alors comment notre modèle peut-il apprendre quoi que ce soit ? Il nous manque une étape clé : insérer des *tokens* à des positions aléatoires dans les entrées ! Voyons comment nous pouvons le faire à la volée pendant le *finetuning* en utilisant un collateur de données spécial. +Comme prévu par notre fonction `group_texts()` ci-dessus, cela semble identique aux `input_ids` décodés. Mais alors comment notre modèle peut-il apprendre quoi que ce soit ? Il nous manque une étape clé : insérer des *tokens* à des positions aléatoires dans les entrées ! Voyons comment nous pouvons le faire à la volée pendant le *finetuning* en utilisant un assembleur de données spécial. ## Finetuning de DistilBERT avec l'API `Trainer` @@ -455,7 +455,7 @@ from transformers import DataCollatorForLanguageModeling data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm_probability=0.15) ``` -Pour voir comment le masquage aléatoire fonctionne, nous allons donner quelques exemples au collateur de données. Puisqu'il s'attend à une liste de `dict` où chaque `dict` représente un seul morceau de texte contigu, nous itérons d'abord sur le jeu de données avant de donner le batch au collateur. Nous supprimons la clé `"word_ids"` pour ce collateur de données car il ne l'attend pas : +Pour voir comment le masquage aléatoire fonctionne, nous allons donner quelques exemples à l'assembleur de données. Puisqu'il s'attend à une liste de `dict` où chaque `dict` représente un seul morceau de texte contigu, nous itérons d'abord sur le jeu de données avant de donner le batch à l'assembleur. Nous supprimons la clé `"word_ids"` pour cet assembleur de données car il ne l'attend pas : ```python samples = [lm_datasets["train"][i] for i in range(2)] @@ -482,11 +482,11 @@ Super, ça a marché ! Nous pouvons voir que le *token* `[MASK]` a été insér {#if fw === 'pt'} -Un effet secondaire du masquage aléatoire est que nos métriques d'évaluation ne seront pas déterministes lorsque nous utilisons la fonction `Trainer` puisque nous utilisons le même collateur de données pour les échantillons d'entraînement et de test. Nous verrons plus tard, lorsque nous examinerons le *finetuning* avec 🤗 *Accelerate*, comment nous pouvons utiliser la flexibilité d'une boucle d'évaluation personnalisée pour geler le caractère aléatoire. +Un effet secondaire du masquage aléatoire est que nos métriques d'évaluation ne seront pas déterministes lorsque nous utilisons la fonction `Trainer` puisque nous utilisons le même assembleur de données pour les échantillons d'entraînement et de test. Nous verrons plus tard, lorsque nous examinerons le *finetuning* avec 🤗 *Accelerate*, comment nous pouvons utiliser la flexibilité d'une boucle d'évaluation personnalisée pour geler le caractère aléatoire. {/if} -Lors de l'entraînement des modèles pour la modélisation du langage masqué, une technique qui peut être utilisée est de masquer des mots entiers ensemble et pas seulement des *tokens* individuels. Cette approche est appelée _masquage de mots entiers_. Si nous voulons utiliser le masquage de mots entiers, nous devons construire nous-mêmes un collateur de données. Un collateur de données est simplement une fonction qui prend une liste d'échantillons et les convertit en un batch. Faisons-le ! Nous utiliserons les identifiants des mots calculés plus tôt pour faire une correspondance entre les indices des mots et les *tokens*, puis nous déciderons aléatoirement quels mots masquer et appliquerons ce masque sur les entrées. Notez que les étiquettes sont toutes `-100` sauf celles qui correspondent aux mots masqués. +Lors de l'entraînement des modèles pour la modélisation du langage masqué, une technique qui peut être utilisée est de masquer des mots entiers ensemble et pas seulement des *tokens* individuels. Cette approche est appelée _masquage de mots entiers_. Si nous voulons utiliser le masquage de mots entiers, nous devons construire nous-mêmes un assembleur de données. Un assembleur de données est simplement une fonction qui prend une liste d'échantillons et les convertit en un batch. Faisons-le ! Nous utiliserons les identifiants des mots calculés plus tôt pour faire une correspondance entre les indices des mots et les *tokens*, puis nous déciderons aléatoirement quels mots masquer et appliquerons ce masque sur les entrées. Notez que les étiquettes sont toutes `-100` sauf celles qui correspondent aux mots masqués. {#if fw === 'pt'} @@ -592,7 +592,7 @@ for chunk in batch["input_ids"]: -Maintenant que nous avons deux collateurs de données, les étapes restantes du *finetuning* sont standards. L'entraînement peut prendre un certain temps sur Google Colab si vous n'avez pas la chance de tomber sur un mythique GPU P100 😭. Ainsi nous allons d'abord réduire la taille du jeu d'entraînement à quelques milliers d'exemples. Ne vous inquiétez pas, nous obtiendrons quand même un modèle de langage assez décent ! Un moyen rapide de réduire la taille d'un jeu de données dans 🤗 *Datasets* est la fonction `Dataset.train_test_split()` que nous avons vue au [chapitre 5](/course/fr/chapter5) : +Maintenant que nous avons deux assembleurs de données, les étapes restantes du *finetuning* sont standards. L'entraînement peut prendre un certain temps sur Google Colab si vous n'avez pas la chance de tomber sur un mythique GPU P100 😭. Ainsi nous allons d'abord réduire la taille du jeu d'entraînement à quelques milliers d'exemples. Ne vous inquiétez pas, nous obtiendrons quand même un modèle de langage assez décent ! Un moyen rapide de réduire la taille d'un jeu de données dans 🤗 *Datasets* est la fonction `Dataset.train_test_split()` que nous avons vue au [chapitre 5](/course/fr/chapter5) : ```python train_size = 10_000 @@ -707,11 +707,11 @@ training_args = TrainingArguments( ) ``` -Ici, nous avons modifié quelques options par défaut, y compris `logging_steps` pour s'assurer que nous suivons la perte d'entraînement à chaque époque. Nous avons également utilisé `fp16=True` pour activer l'entraînement en précision mixte, ce qui nous donne un autre gain de vitesse. Par défaut, `Trainer` va supprimer toutes les colonnes qui ne font pas partie de la méthode `forward()` du modèle. Cela signifie que si vous utilisez le collateur de masquage de mots entiers, vous devrez également définir `remove_unused_columns=False` pour vous assurer que nous ne perdons pas la colonne `word_ids` pendant l'entraînement. +Ici, nous avons modifié quelques options par défaut, y compris `logging_steps` pour s'assurer que nous suivons la perte d'entraînement à chaque époque. Nous avons également utilisé `fp16=True` pour activer l'entraînement en précision mixte, ce qui nous donne un autre gain de vitesse. Par défaut, `Trainer` va supprimer toutes les colonnes qui ne font pas partie de la méthode `forward()` du modèle. Cela signifie que si vous utilisez l'assembleur de masquage de mots entiers, vous devrez également définir `remove_unused_columns=False` pour vous assurer que nous ne perdons pas la colonne `word_ids` pendant l'entraînement. Notez que vous pouvez spécifier le nom du dépôt vers lequel vous voulez pousser avec l'argument `hub_model_id` (en particulier, vous devrez utiliser cet argument pour pousser vers une organisation). Par exemple, lorsque nous avons poussé le modèle vers l'organisation [`huggingface-course`](https://huggingface.co/huggingface-course), nous avons ajouté `hub_model_id="huggingface-course/distilbert-finetuned-imdb"` `TrainingArguments`. Par défaut, le dépôt utilisé sera dans votre espace de noms et nommé après le répertoire de sortie que vous avez défini, donc dans notre cas ce sera `"lewtun/distilbert-finetuned-imdb"`. -Nous avons maintenant tous les ingrédients pour instancier le `Trainer`. Ici, nous utilisons juste le collateur standard `data_collator`, mais vous pouvez essayer le collateur de masquage de mots entiers et comparer les résultats comme exercice : +Nous avons maintenant tous les ingrédients pour instancier le `Trainer`. Ici, nous utilisons juste l'assembleur standard `data_collator`, mais vous pouvez essayer l'assembleur de masquage de mots entiers et comparer les résultats comme exercice : ```python from transformers import Trainer @@ -825,9 +825,9 @@ Dans notre cas d'utilisation, nous n'avons pas eu besoin de faire quelque chose ## Finetuning de DistilBERT avec 🤗 Accelerate -Comme nous l'avons vu, avec `Trainer` le *finetuning* d'un modèle de langage masqué est très similaire à l'exemple de classification de texte du [chapitre 3](/course/fr/chapter3). En fait, la seule subtilité est l'utilisation d'un collateur de données spécial, et nous l'avons déjà couvert plus tôt dans cette section ! +Comme nous l'avons vu, avec `Trainer` le *finetuning* d'un modèle de langage masqué est très similaire à l'exemple de classification de texte du [chapitre 3](/course/fr/chapter3). En fait, la seule subtilité est l'utilisation d'un assembleur de données spécial, et nous l'avons déjà couvert plus tôt dans cette section ! -Cependant, nous avons vu que `DataCollatorForLanguageModeling` applique aussi un masquage aléatoire à chaque évaluation. Nous verrons donc quelques fluctuations dans nos scores de perplexité à chaque entrainement. Une façon d'éliminer cette source d'aléat est d'appliquer le masquage _une fois_ sur l'ensemble de test, puis d'utiliser le collateur de données par défaut dans 🤗 *Transformers* pour collecter les batchs pendant l'évaluation. Pour voir comment cela fonctionne, implémentons une fonction simple qui applique le masquage sur un batch, similaire à notre première rencontre avec `DataCollatorForLanguageModeling` : +Cependant, nous avons vu que `DataCollatorForLanguageModeling` applique aussi un masquage aléatoire à chaque évaluation. Nous verrons donc quelques fluctuations dans nos scores de perplexité à chaque entrainement. Une façon d'éliminer cette source d'aléat est d'appliquer le masquage _une fois_ sur l'ensemble de test, puis d'utiliser l'assembleur de données par défaut dans 🤗 *Transformers* pour collecter les batchs pendant l'évaluation. Pour voir comment cela fonctionne, implémentons une fonction simple qui applique le masquage sur un batch, similaire à notre première rencontre avec `DataCollatorForLanguageModeling` : ```python def insert_random_mask(batch): diff --git a/chapters/fr/chapter7/4.mdx b/chapters/fr/chapter7/4.mdx index d2cec85d2..67a1f954b 100644 --- a/chapters/fr/chapter7/4.mdx +++ b/chapters/fr/chapter7/4.mdx @@ -254,7 +254,7 @@ Notez que nous avons fixé des longueurs maximales similaires pour nos entrées -⚠️ Nous ne faisons pas attention au masque d'attention des cibles car le modèle ne s'y attend pas. Au lieu de cela, les étiquettes correspondant à un *token* de *padding* doivent être mises à `-100` afin qu'elles soient ignorées dans le calcul de la perte. Cela sera fait par notre collateur de données plus tard puisque nous appliquons le *padding* dynamique, mais si vous utilisez le *padding* ici, vous devriez adapter la fonction de prétraitement pour mettre toutes les étiquettes qui correspondent au *token* de *padding* à `-100`. +⚠️ Nous ne faisons pas attention au masque d'attention des cibles car le modèle ne s'y attend pas. Au lieu de cela, les étiquettes correspondant à un *token* de *padding* doivent être mises à `-100` afin qu'elles soient ignorées dans le calcul de la perte. Cela sera fait par notre assembleur de données plus tard puisque nous appliquons le *padding* dynamique, mais si vous utilisez le *padding* ici, vous devriez adapter la fonction de prétraitement pour mettre toutes les étiquettes qui correspondent au *token* de *padding* à `-100`. @@ -306,11 +306,11 @@ model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint, from_pt=True) Notez que cette fois-ci, nous utilisons un modèle qui a été entraîné sur une tâche de traduction et qui peut déjà être utilisé, donc il n'y a pas d'avertissement concernant les poids manquants ou ceux nouvellement initialisés. -### Collecte des données +### Assemblage des données Nous aurons besoin d'un assembleur de données pour gérer le rembourrage pour la mise en batchs dynamique. Ici, nous ne pouvons pas simplement utiliser un `DataCollatorWithPadding` comme dans le [chapitre 3](/course/fr/chapter3) car cela ne rembourre que les entrées (identifiants d'entrée, masque d'attention, et *token* de type identifiants). Nos étiquettes doivent également être rembourrées à la longueur maximale rencontrée dans les étiquettes. Et, comme mentionné précédemment, la valeur de remplissage utilisée pour remplir les étiquettes doit être `-100` et non le *token* de *padding* du *tokenizer* afin de s'assurer que ces valeurs soient ignorées dans le calcul de la perte. -Tout ceci est réalisé par un [`DataCollatorForSeq2Seq`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorforseq2seq). Comme le `DataCollatorWithPadding`, il prend le `tokenizer` utilisé pour prétraiter les entrées, mais également le `model`. C'est parce que ce collateur de données est également responsable de la préparation des identifiants d'entrée du décodeur, qui sont des versions décalées des étiquettes avec un *token* spécial au début. Comme ce décalage est effectué de manière légèrement différente selon les architectures, le `DataCollatorForSeq2Seq` a besoin de connaître l'objet `model` : +Tout ceci est réalisé par un [`DataCollatorForSeq2Seq`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorforseq2seq). Comme le `DataCollatorWithPadding`, il prend le `tokenizer` utilisé pour prétraiter les entrées, mais également le `model`. C'est parce que cet assembleur de données est également responsable de la préparation des identifiants d'entrée du décodeur, qui sont des versions décalées des étiquettes avec un *token* spécial au début. Comme ce décalage est effectué de manière légèrement différente selon les architectures, le `DataCollatorForSeq2Seq` a besoin de connaître l'objet `model` : {#if fw === 'pt'} diff --git a/chapters/fr/chapter7/5.mdx b/chapters/fr/chapter7/5.mdx index 678cfefe4..3f317d0e9 100644 --- a/chapters/fr/chapter7/5.mdx +++ b/chapters/fr/chapter7/5.mdx @@ -585,9 +585,9 @@ def compute_metrics(eval_pred): {/if} -Ensuite, nous devons définir un collateur de données pour notre tâche de séquence à séquence. Comme mT5 est un *transformer* encodeur-décodeur, une des subtilités de la préparation de nos batchs est que, pendant le décodage, nous devons décaler les étiquettes d'une unité vers la droite. Ceci est nécessaire pour garantir que le décodeur ne voit que les étiquettes de vérité terrain précédentes et non les étiquettes actuelles ou futures, qui seraient faciles à mémoriser pour le modèle. Cela ressemble à la façon dont l'auto-attention masquée est appliquée aux entrées dans une tâche comme [la modélisation causale du langage](/course/fr/chapter7/6). +Ensuite, nous devons définir un assembleur de données pour notre tâche de séquence à séquence. Comme mT5 est un *transformer* encodeur-décodeur, une des subtilités de la préparation de nos batchs est que, pendant le décodage, nous devons décaler les étiquettes d'une unité vers la droite. Ceci est nécessaire pour garantir que le décodeur ne voit que les étiquettes de vérité terrain précédentes et non les étiquettes actuelles ou futures, qui seraient faciles à mémoriser pour le modèle. Cela ressemble à la façon dont l'auto-attention masquée est appliquée aux entrées dans une tâche comme [la modélisation causale du langage](/course/fr/chapter7/6). -Heureusement, 🤗 *Transformers* fournit un collateur `DataCollatorForSeq2Seq` qui rembourrera dynamiquement les entrées et les étiquettes pour nous. Pour instancier ce collateur, nous devons simplement fournir le *tokenizer* et le *modèle* : +Heureusement, 🤗 *Transformers* fournit un assembleur `DataCollatorForSeq2Seq` qui rembourrera dynamiquement les entrées et les étiquettes pour nous. Pour instancier ce assembleur, nous devons simplement fournir le *tokenizer* et le *modèle* : {#if fw === 'pt'} @@ -607,7 +607,7 @@ data_collator = DataCollatorForSeq2Seq(tokenizer, model=model, return_tensors="t {/if} -Voyons ce que produit ce collateur lorsqu'on lui donne un petit batch d'exemples. Tout d'abord, nous devons supprimer les colonnes contenant des chaînes de caractères, car le collateur ne saura pas comment remplir ces éléments : +Voyons ce que produit ce assembleur lorsqu'on lui donne un petit batch d'exemples. Tout d'abord, nous devons supprimer les colonnes contenant des chaînes de caractères, car le assembleur ne saura pas comment remplir ces éléments : ```python tokenized_datasets = tokenized_datasets.remove_columns( @@ -615,7 +615,7 @@ tokenized_datasets = tokenized_datasets.remove_columns( ) ``` -Comme le collateur attend une liste de `dict`, où chaque `dict` représente un seul exemple du jeu de données, nous devons également mettre les données dans le format attendu avant de les transmettre au collateur de données : +Comme le assembleur attend une liste de `dict`, où chaque `dict` représente un seul exemple du jeu de données, nous devons également mettre les données dans le format attendu avant de les transmettre au assembleur de données : ```python features = [tokenized_datasets["train"][i] for i in range(2)] @@ -698,7 +698,7 @@ Pour conclure cette section, voyons comment nous pouvons également *finetuner* {:else} -Nous sommes presque prêts à nous entraîner ! Nous devons juste convertir nos jeux de données en `tf.data.Dataset` en utilisant le collateur de données que nous avons défini ci-dessus, puis utiliser `compile()` et `fit()`. D'abord, les jeux de données : +Nous sommes presque prêts à nous entraîner ! Nous devons juste convertir nos jeux de données en `tf.data.Dataset` en utilisant le assembleur de données que nous avons défini ci-dessus, puis utiliser `compile()` et `fit()`. D'abord, les jeux de données : ```python tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( @@ -812,7 +812,7 @@ Maintenant que nous avons des jeux de données constitués uniquement de tenseur model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) ``` -Nous pouvons ensuite instancier le collateur de données et l'utiliser pour définir nos chargeurs de données : +Nous pouvons ensuite instancier le assembleur de données et l'utiliser pour définir nos chargeurs de données : ```python from torch.utils.data import DataLoader @@ -856,11 +856,11 @@ model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( Maintenant que nous avons préparé nos objets, il reste trois choses à faire : -* définir le programmeur du taux d'apprentissage, +* définir le planificateur du taux d'apprentissage, * implémenter une fonction pour post-traiter les résumés pour l'évaluation, * créer un dépôt sur le *Hub* vers lequel nous pouvons pousser notre modèle. -Pour le programmeur de taux d'apprentissage, nous utiliserons le programmeur linéaire standard des sections précédentes : +Pour le planificateur de taux d'apprentissage, nous utiliserons le planificateur linéaire standard des sections précédentes : ```python from transformers import get_scheduler diff --git a/chapters/fr/chapter7/6.mdx b/chapters/fr/chapter7/6.mdx index 52c4f5e28..4d1d27e59 100644 --- a/chapters/fr/chapter7/6.mdx +++ b/chapters/fr/chapter7/6.mdx @@ -321,7 +321,7 @@ _________________________________________________________________ {/if} -Notre modèle comporte 124 millions de paramètres que nous devrons régler. Avant de commencer l'entraînement, nous devons configurer un collateur de données qui se chargera de créer les batchs. Nous pouvons utiliser le collateur `DataCollatorForLanguageModeling`, qui est conçu spécifiquement pour la modélisation du langage (comme son nom le suggère subtilement). En plus de l'empilage et du rembourrage des batchs, il s'occupe aussi de la création des étiquettes du modèle de langage. Dans la modélisation causale du langage, les entrées servent aussi d'étiquettes (juste décalées d'un élément) et que le collateur de données crée à la volée pendant l'entraînement pour ne pas avoir à dupliquer les `input_ids`. +Notre modèle comporte 124 millions de paramètres que nous devrons régler. Avant de commencer l'entraînement, nous devons configurer un assembleur de données qui se chargera de créer les batchs. Nous pouvons utiliser le assembleur `DataCollatorForLanguageModeling`, qui est conçu spécifiquement pour la modélisation du langage (comme son nom le suggère subtilement). En plus de l'empilage et du rembourrage des batchs, il s'occupe aussi de la création des étiquettes du modèle de langage. Dans la modélisation causale du langage, les entrées servent aussi d'étiquettes (juste décalées d'un élément) et que le assembleur de données crée à la volée pendant l'entraînement pour ne pas avoir à dupliquer les `input_ids`. Notez que `DataCollatorForLanguageModeling` supporte à la fois la modélisation du langage masqué (MLM pour *masked language modeling*) et la modélisation du langage causal (CLM pour *causal language modeling*). Par défaut, il prépare les données pour la MLM mais nous pouvons passer à la CLM en définissant l'argument `mlm=False` : @@ -375,7 +375,7 @@ Nous pouvons voir que les exemples ont été empilés et que tous les tenseurs o {#if fw === 'tf'} -Maintenant nous pouvons utiliser la méthode `to_tf_dataset()` pour convertir nos jeux de données en jeux de données TensorFlow avec le collateur de données que nous avons créé ci-dessus : +Maintenant nous pouvons utiliser la méthode `to_tf_dataset()` pour convertir nos jeux de données en jeux de données TensorFlow avec l'assembleur de données que nous avons créé ci-dessus : ```python tf_train_dataset = tokenized_dataset["train"].to_tf_dataset( @@ -396,7 +396,7 @@ tf_eval_dataset = tokenized_dataset["valid"].to_tf_dataset( -⚠️ Le déplacement des entrées et des étiquettes pour les aligner se fait à l'intérieur du modèle, de sorte que le collecteur de données ne fait que copier les entrées pour créer les étiquettes. +⚠️ Le déplacement des entrées et des étiquettes pour les aligner se fait à l'intérieur du modèle, de sorte que l'assembleur de données ne fait que copier les entrées pour créer les étiquettes. diff --git a/chapters/fr/chapter7/7.mdx b/chapters/fr/chapter7/7.mdx index 6e0a619e2..9bb9b046b 100644 --- a/chapters/fr/chapter7/7.mdx +++ b/chapters/fr/chapter7/7.mdx @@ -528,13 +528,13 @@ Maintenant que nous avons prétraité toutes les données, nous pouvons passer ## Finetuner le modèle avec l'API `Trainer` -Le code d'entraînement pour cet exemple ressemblera beaucoup au code des sections précédentes mais le calcul de la métrique avec la fonction `compute_metrics()` sera un défi unique. Puisque nous avons rembourré tous les échantillons à la longueur maximale que nous avons définie, il n'y a pas de collateur de données à définir. Ainsi le calcul de la métrique est vraiment la seule chose dont nous devons nous soucier. La partie la plus difficile sera de post-traiter les prédictions du modèle en étendues de texte dans les exemples originaux. Une fois que nous aurons fait cela, la métrique de la bibliothèque 🤗 *Datasets* fera le gros du travail pour nous. +Le code d'entraînement pour cet exemple ressemblera beaucoup au code des sections précédentes mais le calcul de la métrique avec la fonction `compute_metrics()` sera un défi unique. Puisque nous avons rembourré tous les échantillons à la longueur maximale que nous avons définie, il n'y a pas d'assembleur de données à définir. Ainsi le calcul de la métrique est vraiment la seule chose dont nous devons nous soucier. La partie la plus difficile sera de post-traiter les prédictions du modèle en étendues de texte dans les exemples originaux. Une fois que nous aurons fait cela, la métrique de la bibliothèque 🤗 *Datasets* fera le gros du travail pour nous. {:else} ## Finetuner fin du modèle avec Keras -Le code d'entraînement de cet exemple ressemblera beaucoup au code des sections précédentes, mais le calcul de la métrique sera un défi unique. Puisque nous avons rembourré tous les échantillons à la longueur maximale que nous avons définie, il n'y a pas de collateur de données à définir. Ainsi le calcul de la métrique est vraiment la seule chose dont nous devons nous soucier. La partie la plus difficile sera de post-traiter les prédictions du modèle en étendues de texte dans les exemples originaux. Une fois que nous aurons fait cela, la métrique de la bibliothèque 🤗 *Datasets* fera le gros du travail pour nous. +Le code d'entraînement de cet exemple ressemblera beaucoup au code des sections précédentes, mais le calcul de la métrique sera un défi unique. Puisque nous avons rembourré tous les échantillons à la longueur maximale que nous avons définie, il n'y a pas d'assembleur de données à définir. Ainsi le calcul de la métrique est vraiment la seule chose dont nous devons nous soucier. La partie la plus difficile sera de post-traiter les prédictions du modèle en étendues de texte dans les exemples originaux. Une fois que nous aurons fait cela, la métrique de la bibliothèque 🤗 *Datasets* fera le gros du travail pour nous. {/if} @@ -872,7 +872,7 @@ Nous avons déjà vu la plupart d'entre eux. Nous définissons quelques hyperpar {:else} -Maintenant que c'est fait, nous pouvons créer nos jeux de données TensorFlow. Nous pouvons utiliser le simple collateur de données par défaut cette fois-ci : +Maintenant que c'est fait, nous pouvons créer nos jeux de données TensorFlow. Nous pouvons utiliser le simple assembleur de données par défaut cette fois-ci : ```python from transformers import DefaultDataCollator diff --git a/chapters/fr/chapter7/8.mdx b/chapters/fr/chapter7/8.mdx index 927345b35..9a0438183 100644 --- a/chapters/fr/chapter7/8.mdx +++ b/chapters/fr/chapter7/8.mdx @@ -7,7 +7,7 @@ Si vous êtes arrivé jusqu'ici dans le cours, félicitations ! Vous avez maintenant toutes les connaissances et les outils nécessaires pour aborder (presque) n'importe quelle tâche de *NLP* avec 🤗 *Transformers* et l'écosystème d'*Hugging Face* ! -Nous avons vu beaucoup de collecteurs de données différents, c'est pourquoi nous avons fait cette petite vidéo pour vous aider à trouver lequel utiliser pour chaque tâche : +Nous avons vu beaucoup d'assembleurs de données différents, c'est pourquoi nous avons fait cette petite vidéo pour vous aider à trouver lequel utiliser pour chaque tâche : @@ -19,4 +19,4 @@ Après avoir terminé ce tour d'horizon des principales tâches de *NLP*, vous d * comprendre la signification et les limites de métriques comme ROUGE et BLEU pour les tâches de génération de texte, * savoir comment interagir avec vos modèles *finetunés*, à la fois sur le *Hub* et en utilisant la `pipeline` de 🤗 *Transformers*. -Malgré toutes ces connaissances, il arrivera un moment où vous rencontrerez un *bug* difficile dans votre code ou aurez une question sur la façon de résoudre un problème de *NLP* particulier. Heureusement, la communauté d'*Hugging Face* est là pour vous aider ! Dans le dernier chapitre de cette partie du cours, nous allons explorer comment vous pouvez déboguer vos modèles et demander de l'aide efficacement. \ No newline at end of file +Malgré toutes ces connaissances, il arrivera un moment où vous rencontrerez un *bug* difficile dans votre code ou aurez une question sur la façon de résoudre un problème de *NLP* particulier. Heureusement, la communauté d'*Hugging Face* est là pour vous aider ! Dans le dernier chapitre de cette partie du cours, nous allons explorer comment vous pouvez déboguer vos modèles et demander de l'aide efficacement. diff --git a/chapters/fr/chapter9/6.mdx b/chapters/fr/chapter9/6.mdx index e4564b66b..69e645d1c 100644 --- a/chapters/fr/chapter9/6.mdx +++ b/chapters/fr/chapter9/6.mdx @@ -57,7 +57,7 @@ Remarque : vous pouvez transmettre une valeur par défaut au paramètre state, q ### Utilisation de l'interprétation pour comprendre les prédictions -La plupart des modèles d'apprentissage automatique sont des boîtes noires et la logique interne de la fonction est cachée à l'utilisateur final. Pour encourager la transparence, nous avons fait en sorte qu'il soit très facile d'ajouter l'interprétation à votre modèle en définissant simplement le mot-clé interprétation dans la classe Interface par défaut. Cela permet à vos utilisateurs de comprendre quelles parties de l'entrée sont responsables de la sortie. Jetez un coup d'œil à l'interface simple ci-dessous qui montre un classificateur d'images incluant l'interprétation : +La plupart des modèles d'apprentissage automatique sont des boîtes noires et la logique interne de la fonction est cachée à l'utilisateur final. Pour encourager la transparence, nous avons fait en sorte qu'il soit très facile d'ajouter l'interprétation à votre modèle en définissant simplement le mot-clé interprétation dans la classe Interface par défaut. Cela permet à vos utilisateurs de comprendre quelles parties de l'entrée sont responsables de la sortie. Jetez un coup d'œil à l'interface simple ci-dessous qui montre un classifieur d'images incluant l'interprétation : ```py import requests @@ -135,4 +135,4 @@ gr.Interface( -Ceci conclut notre plongée dans la classe `Interface` de *Gradio*. Comme nous l'avons vu, cette classe permet de créer facilement des démos d'apprentissage automatique en quelques lignes de code Python. Cependant, vous voudrez parfois personnaliser votre démo en changeant la mise en page ou en enchaînant plusieurs fonctions de prédiction. Ne serait-il pas agréable de pouvoir diviser l'interface en blocs personnalisables ? Heureusement, c'est possible ! C'est le sujet de la dernière section. \ No newline at end of file +Ceci conclut notre plongée dans la classe `Interface` de *Gradio*. Comme nous l'avons vu, cette classe permet de créer facilement des démos d'apprentissage automatique en quelques lignes de code Python. Cependant, vous voudrez parfois personnaliser votre démo en changeant la mise en page ou en enchaînant plusieurs fonctions de prédiction. Ne serait-il pas agréable de pouvoir diviser l'interface en blocs personnalisables ? Heureusement, c'est possible ! C'est le sujet de la dernière section. From 6eb6c2b86b6e1bcecd2f8804b0e5be6ca51befa7 Mon Sep 17 00:00:00 2001 From: lewtun Date: Tue, 4 Oct 2022 15:24:46 +0200 Subject: [PATCH 33/51] Bump release (#328) --- chapters/bn/chapter1/1.mdx | 4 +- chapters/en/_toctree.yml | 10 +- chapters/en/chapter1/1.mdx | 4 +- chapters/en/chapter5/4.mdx | 10 +- chapters/en/chapter6/3b.mdx | 8 +- chapters/en/chapter7/3.mdx | 2 + chapters/en/chapter9/1.mdx | 8 +- chapters/en/events/1.mdx | 49 + chapters/en/{event/1.mdx => events/2.mdx} | 2 +- chapters/en/events/3.mdx | 9 + chapters/es/chapter1/1.mdx | 4 +- chapters/fa/chapter1/1.mdx | 4 +- chapters/fr/chapter1/1.mdx | 4 +- chapters/fr/chapter5/4.mdx | 10 +- chapters/fr/chapter6/3b.mdx | 8 +- chapters/fr/chapter7/3.mdx | 2 + chapters/fr/event/1.mdx | 2 +- chapters/hi/chapter1/1.mdx | 4 +- chapters/it/chapter1/1.mdx | 4 +- chapters/it/chapter5/4.mdx | 10 +- chapters/ja/chapter1/1.mdx | 4 +- chapters/ja/chapter7/3.mdx | 2 + chapters/ja/event/1.mdx | 2 +- chapters/ko/chapter1/1.mdx | 4 +- chapters/pt/chapter1/1.mdx | 4 +- chapters/pt/chapter5/4.mdx | 10 +- chapters/pt/event/1.mdx | 2 +- chapters/ru/_toctree.yml | 24 +- chapters/ru/chapter1/1.mdx | 4 +- chapters/ru/chapter5/1.mdx | 17 + chapters/ru/chapter5/2.mdx | 163 +++ chapters/ru/chapter5/3.mdx | 747 +++++++++++++ chapters/ru/chapter5/4.mdx | 285 +++++ chapters/ru/chapter5/6.mdx | 526 +++++++++ chapters/ru/chapter5/7.mdx | 16 + chapters/ru/chapter5/8.mdx | 230 ++++ chapters/ru/chapter6/1.mdx | 19 + chapters/ru/chapter6/2.mdx | 257 +++++ chapters/th/chapter1/1.mdx | 4 +- chapters/th/chapter6/3b.mdx | 8 +- chapters/tr/chapter1/1.mdx | 4 +- chapters/vi/chapter1/1.mdx | 4 +- chapters/vi/chapter5/4.mdx | 10 +- chapters/vi/chapter6/3b.mdx | 8 +- chapters/vi/chapter7/3.mdx | 2 + chapters/vi/event/1.mdx | 2 +- chapters/zh-CN/_toctree.yml | 79 +- chapters/zh-CN/chapter1/1.mdx | 4 +- chapters/zh-CN/chapter5/4.mdx | 10 +- chapters/zh-CN/chapter6/3b.mdx | 8 +- chapters/zh-CN/chapter7/1.mdx | 33 + chapters/zh-CN/chapter7/2.mdx | 978 +++++++++++++++++ chapters/zh-CN/chapter7/3.mdx | 1044 ++++++++++++++++++ chapters/zh-CN/chapter7/4.mdx | 996 +++++++++++++++++ chapters/zh-CN/chapter7/5.mdx | 1047 ++++++++++++++++++ chapters/zh-CN/chapter7/6.mdx | 906 +++++++++++++++ chapters/zh-CN/chapter7/7.mdx | 1210 +++++++++++++++++++++ chapters/zh-CN/chapter7/8.mdx | 17 + chapters/zh-CN/chapter7/9.mdx | 311 ++++++ chapters/zh-CN/chapter8/1.mdx | 12 + chapters/zh-CN/chapter8/2.mdx | 364 +++++++ chapters/zh-CN/chapter8/3.mdx | 166 +++ chapters/zh-CN/chapter8/4.mdx | 787 ++++++++++++++ chapters/zh-CN/chapter8/4_tf.mdx | 489 +++++++++ chapters/zh-CN/chapter8/5.mdx | 85 ++ chapters/zh-CN/chapter8/6.mdx | 7 + chapters/zh-CN/chapter8/7.mdx | 190 ++++ chapters/zh-CN/chapter9/1.mdx | 36 + chapters/zh-CN/chapter9/2.mdx | 112 ++ chapters/zh-CN/chapter9/3.mdx | 167 +++ chapters/zh-CN/chapter9/4.mdx | 144 +++ chapters/zh-CN/chapter9/5.mdx | 66 ++ chapters/zh-CN/chapter9/6.mdx | 97 ++ chapters/zh-CN/chapter9/7.mdx | 236 ++++ chapters/zh-CN/chapter9/8.mdx | 19 + chapters/zh-CN/chapter9/9.mdx | 231 ++++ chapters/zh-CN/event/1.mdx | 165 +++ 77 files changed, 12432 insertions(+), 100 deletions(-) create mode 100644 chapters/en/events/1.mdx rename chapters/en/{event/1.mdx => events/2.mdx} (99%) create mode 100644 chapters/en/events/3.mdx create mode 100644 chapters/ru/chapter5/1.mdx create mode 100644 chapters/ru/chapter5/2.mdx create mode 100644 chapters/ru/chapter5/3.mdx create mode 100644 chapters/ru/chapter5/4.mdx create mode 100644 chapters/ru/chapter5/6.mdx create mode 100644 chapters/ru/chapter5/7.mdx create mode 100644 chapters/ru/chapter5/8.mdx create mode 100644 chapters/ru/chapter6/1.mdx create mode 100644 chapters/ru/chapter6/2.mdx create mode 100644 chapters/zh-CN/chapter7/1.mdx create mode 100644 chapters/zh-CN/chapter7/2.mdx create mode 100644 chapters/zh-CN/chapter7/3.mdx create mode 100644 chapters/zh-CN/chapter7/4.mdx create mode 100644 chapters/zh-CN/chapter7/5.mdx create mode 100644 chapters/zh-CN/chapter7/6.mdx create mode 100644 chapters/zh-CN/chapter7/7.mdx create mode 100644 chapters/zh-CN/chapter7/8.mdx create mode 100644 chapters/zh-CN/chapter7/9.mdx create mode 100644 chapters/zh-CN/chapter8/1.mdx create mode 100644 chapters/zh-CN/chapter8/2.mdx create mode 100644 chapters/zh-CN/chapter8/3.mdx create mode 100644 chapters/zh-CN/chapter8/4.mdx create mode 100644 chapters/zh-CN/chapter8/4_tf.mdx create mode 100644 chapters/zh-CN/chapter8/5.mdx create mode 100644 chapters/zh-CN/chapter8/6.mdx create mode 100644 chapters/zh-CN/chapter8/7.mdx create mode 100644 chapters/zh-CN/chapter9/1.mdx create mode 100644 chapters/zh-CN/chapter9/2.mdx create mode 100644 chapters/zh-CN/chapter9/3.mdx create mode 100644 chapters/zh-CN/chapter9/4.mdx create mode 100644 chapters/zh-CN/chapter9/5.mdx create mode 100644 chapters/zh-CN/chapter9/6.mdx create mode 100644 chapters/zh-CN/chapter9/7.mdx create mode 100644 chapters/zh-CN/chapter9/8.mdx create mode 100644 chapters/zh-CN/chapter9/9.mdx create mode 100644 chapters/zh-CN/event/1.mdx diff --git a/chapters/bn/chapter1/1.mdx b/chapters/bn/chapter1/1.mdx index c40753b53..c3c0d7682 100644 --- a/chapters/bn/chapter1/1.mdx +++ b/chapters/bn/chapter1/1.mdx @@ -52,10 +52,10 @@ **Lucile Saulnier** হলেন Hugging Face এর একজন মেশিন লার্নিং ইঞ্জিনিয়ার, যিনি ওপেন সোর্স টুলের ডেভেলপমেন্ট ও ব্যবহার এ সাহায্য করে থাকেন। তিনি ন্যচালার ল্যঙ্গুএজ প্রসেসিং এর পাশাপাশি collaborative training এবং বিগসায়েন্সের মতো বিষয়ের অনেক গবেষণা প্রকল্পে সক্রিয়ভাবে জড়িত। -**Lewis Tunstall** হলেন একজন মেশিন লার্নিং ইঞ্জিনিয়ার, যিনি ওপেন-সোর্স টুল ডেভেলপ করতে এবং সেগুলিকে বৃহত্তর সম্প্রদায়ের কাছে অ্যাক্সেসযোগ্য করে তোলার দিকে মনোনিবেশ করেন৷ তিনি একটি আসন্ন একটি বইয়ের সহ-লেখক [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). +**Lewis Tunstall** হলেন একজন মেশিন লার্নিং ইঞ্জিনিয়ার, যিনি ওপেন-সোর্স টুল ডেভেলপ করতে এবং সেগুলিকে বৃহত্তর সম্প্রদায়ের কাছে অ্যাক্সেসযোগ্য করে তোলার দিকে মনোনিবেশ করেন৷ তিনি একটি আসন্ন একটি বইয়ের সহ-লেখক [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). -**Leandro von Werra** হলেন Hugging Face-এর ওপেন-সোর্স টিমের একজন মেশিন লার্নিং ইঞ্জিনিয়ার এবং ট্রান্সফরমারের উপর একটি আসন্ন O'Reilly বইয়ের সহ-লেখক [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). পুরো মেশিন লার্নিং স্ট্যাক জুড়ে কাজ করে NLP প্রকল্পগুলিকে উৎপাদনে নিয়ে আসার কয়েক বছরের ইন্ডাস্ট্রি অভিজ্ঞতা রয়েছে তার। +**Leandro von Werra** হলেন Hugging Face-এর ওপেন-সোর্স টিমের একজন মেশিন লার্নিং ইঞ্জিনিয়ার এবং ট্রান্সফরমারের উপর একটি আসন্ন O'Reilly বইয়ের সহ-লেখক [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). পুরো মেশিন লার্নিং স্ট্যাক জুড়ে কাজ করে NLP প্রকল্পগুলিকে উৎপাদনে নিয়ে আসার কয়েক বছরের ইন্ডাস্ট্রি অভিজ্ঞতা রয়েছে তার। আপনি রোল প্রস্তুত? এই অধ্যায়ে, আপনি শিখবেন: diff --git a/chapters/en/_toctree.yml b/chapters/en/_toctree.yml index 0ae46daa8..c8364cc6d 100644 --- a/chapters/en/_toctree.yml +++ b/chapters/en/_toctree.yml @@ -191,7 +191,11 @@ title: End-of-chapter quiz quiz: 9 -- title: Hugging Face Course Event +- title: Course Events sections: - - local: event/1 - title: Part 2 Release Event + - local: events/1 + title: Live sessions and workshops + - local: events/2 + title: Part 2 release event + - local: events/3 + title: Gradio Blocks party diff --git a/chapters/en/chapter1/1.mdx b/chapters/en/chapter1/1.mdx index b5e8bd360..591e71ccf 100644 --- a/chapters/en/chapter1/1.mdx +++ b/chapters/en/chapter1/1.mdx @@ -51,9 +51,9 @@ About the authors: **Lucile Saulnier** is a machine learning engineer at Hugging Face, developing and supporting the use of open source tools. She is also actively involved in many research projects in the field of Natural Language Processing such as collaborative training and BigScience. -**Lewis Tunstall** is a machine learning engineer at Hugging Face, focused on developing open-source tools and making them accessible to the wider community. He is also a co-author of the O’Reilly book [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). +**Lewis Tunstall** is a machine learning engineer at Hugging Face, focused on developing open-source tools and making them accessible to the wider community. He is also a co-author of the O’Reilly book [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). -**Leandro von Werra** is a machine learning engineer in the open-source team at Hugging Face and also a co-author of the O’Reilly book [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). He has several years of industry experience bringing NLP projects to production by working across the whole machine learning stack.. +**Leandro von Werra** is a machine learning engineer in the open-source team at Hugging Face and also a co-author of the O’Reilly book [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). He has several years of industry experience bringing NLP projects to production by working across the whole machine learning stack.. Are you ready to roll? In this chapter, you will learn: * How to use the `pipeline()` function to solve NLP tasks such as text generation and classification diff --git a/chapters/en/chapter5/4.mdx b/chapters/en/chapter5/4.mdx index 58061e67a..26eea8397 100644 --- a/chapters/en/chapter5/4.mdx +++ b/chapters/en/chapter5/4.mdx @@ -18,7 +18,7 @@ In this section we'll explore these features of 🤗 Datasets with a huge 825 GB ## What is the Pile? -The Pile is an English text corpus that was created by [EleutherAI](https://www.eleuther.ai) for training large-scale language models. It includes a diverse range of datasets, spanning scientific articles, GitHub code repositories, and filtered web text. The training corpus is available in [14 GB chunks](https://mystic.the-eye.eu/public/AI/pile/), and you can also download several of the [individual components](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/). Let's start by taking a look at the PubMed Abstracts dataset, which is a corpus of abstracts from 15 million biomedical publications on [PubMed](https://pubmed.ncbi.nlm.nih.gov/). The dataset is in [JSON Lines format](https://jsonlines.org) and is compressed using the `zstandard` library, so first we need to install that: +The Pile is an English text corpus that was created by [EleutherAI](https://www.eleuther.ai) for training large-scale language models. It includes a diverse range of datasets, spanning scientific articles, GitHub code repositories, and filtered web text. The training corpus is available in [14 GB chunks](https://the-eye.eu/public/AI/pile/), and you can also download several of the [individual components](https://the-eye.eu/public/AI/pile_preliminary_components/). Let's start by taking a look at the PubMed Abstracts dataset, which is a corpus of abstracts from 15 million biomedical publications on [PubMed](https://pubmed.ncbi.nlm.nih.gov/). The dataset is in [JSON Lines format](https://jsonlines.org) and is compressed using the `zstandard` library, so first we need to install that: ```py !pip install zstandard @@ -30,7 +30,7 @@ Next, we can load the dataset using the method for remote files that we learned from datasets import load_dataset # This takes a few minutes to run, so go grab a tea or coffee while you wait :) -data_files = "https://mystic.the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst" +data_files = "https://the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst" pubmed_dataset = load_dataset("json", data_files=data_files, split="train") pubmed_dataset ``` @@ -101,7 +101,7 @@ Nice -- despite it being almost 20 GB large, we're able to load and access the d -✏️ **Try it out!** Pick one of the [subsets](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/) from the Pile that is larger than your laptop or desktop's RAM, load it with 🤗 Datasets, and measure the amount of RAM used. Note that to get an accurate measurement, you'll want to do this in a new process. You can find the decompressed sizes of each subset in Table 1 of [the Pile paper](https://arxiv.org/abs/2101.00027). +✏️ **Try it out!** Pick one of the [subsets](https://the-eye.eu/public/AI/pile_preliminary_components/) from the Pile that is larger than your laptop or desktop's RAM, load it with 🤗 Datasets, and measure the amount of RAM used. Note that to get an accurate measurement, you'll want to do this in a new process. You can find the decompressed sizes of each subset in Table 1 of [the Pile paper](https://arxiv.org/abs/2101.00027). @@ -225,7 +225,7 @@ Let's round out our exploration of dataset streaming with a common application: ```py law_dataset_streamed = load_dataset( "json", - data_files="https://mystic.the-eye.eu/public/AI/pile_preliminary_components/FreeLaw_Opinions.jsonl.zst", + data_files="https://the-eye.eu/public/AI/pile_preliminary_components/FreeLaw_Opinions.jsonl.zst", split="train", streaming=True, ) @@ -263,7 +263,7 @@ Here we've used the `islice()` function from Python's `itertools` module to sele Finally, if you want to stream the Pile in its 825 GB entirety, you can grab all the prepared files as follows: ```py -base_url = "https://mystic.the-eye.eu/public/AI/pile/" +base_url = "https://the-eye.eu/public/AI/pile/" data_files = { "train": [base_url + "train/" + f"{idx:02d}.jsonl.zst" for idx in range(30)], "validation": base_url + "val.jsonl.zst", diff --git a/chapters/en/chapter6/3b.mdx b/chapters/en/chapter6/3b.mdx index 1f2ef4eda..5b902f068 100644 --- a/chapters/en/chapter6/3b.mdx +++ b/chapters/en/chapter6/3b.mdx @@ -576,8 +576,8 @@ 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[0] - end_idx = idx % scores.shape[0] + 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)) @@ -592,8 +592,8 @@ 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[0] - end_idx = idx % scores.shape[0] + 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)) diff --git a/chapters/en/chapter7/3.mdx b/chapters/en/chapter7/3.mdx index bcaab1b45..30e4161e1 100644 --- a/chapters/en/chapter7/3.mdx +++ b/chapters/en/chapter7/3.mdx @@ -523,6 +523,7 @@ def whole_word_masking_data_collator(features): 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) ``` @@ -563,6 +564,7 @@ def whole_word_masking_data_collator(features): 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) ``` diff --git a/chapters/en/chapter9/1.mdx b/chapters/en/chapter9/1.mdx index 74ea8a0a3..08e130eb2 100644 --- a/chapters/en/chapter9/1.mdx +++ b/chapters/en/chapter9/1.mdx @@ -34,10 +34,4 @@ This chapter is broken down into sections which include both _concepts_ and _app 👀 Check out Hugging Face Spaces to see many recent examples of machine learning demos built by the machine learning community! - - -## Gradio blocks party 🥳 - -If you want to put the knowledge from this chapter to good use, come join the Gradio blocks party! This is a community event that's hosted by Hugging Face on **May 16-31**. During this event, you'll build cool machine learning demos with Gradio and be in the running to win Hugging Face swag and prizes! - -Check out the [event description](https://github.com/AK391/community-events/blob/main/gradio-blocks/README.md) for details on how to participate - we can't wait to see what you'll build 🤗! + \ No newline at end of file diff --git a/chapters/en/events/1.mdx b/chapters/en/events/1.mdx new file mode 100644 index 000000000..889a487a8 --- /dev/null +++ b/chapters/en/events/1.mdx @@ -0,0 +1,49 @@ +# Live sessions and workshops + +For the release of parts 1 and 2 of the course, we organized several live coding sessions and workshops. You can find links to the recordings of these sessions and workshops below. + +## Live coding sessions + +For the first session, Sylvain goes through Chapter 1 of the course with you, explaining it step by step: + +
+ +
+ +In the second session, it is Lewis' turn to present Chapter 2: + +
+ +
+ +Because Chapter 2 is so cool, Sylvain has also given a walkthrough of it! + +
+ +
+ +For Chapter 3, Lewis returns to guide you through the code: + +
+ +
+ +Finally, Omar concludes the live sessions related to the first part of the course by tackling chapter 4: + +
+ +
+ +## Workshops + +In the first workshop, Merve welcomes Lewis to discuss section 7 of chapter 7 about [question anwsering]( https://huggingface.co/course/chapter7/7?fw=pt). + +
+ +
+ +For the second workshop, Merve hosts Leandro to talk about chapter 7, section 6 on [training a causal language model from scratch]( https://huggingface.co/course/chapter7/6?fw=pt) with an application with [CodeParrot](https://huggingface.co/codeparrot). + +
+ +
\ No newline at end of file diff --git a/chapters/en/event/1.mdx b/chapters/en/events/2.mdx similarity index 99% rename from chapters/en/event/1.mdx rename to chapters/en/events/2.mdx index d202a9ceb..2b02ed3cb 100644 --- a/chapters/en/event/1.mdx +++ b/chapters/en/events/2.mdx @@ -86,7 +86,7 @@ Jakob Uszkoreit is the co-founder of Inceptive. Inceptive designs RNA molecules
-Lewis is a machine learning engineer at Hugging Face, focused on developing open-source tools and making them accessible to the wider community. He is also a co-author of the O’Reilly book [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). You can follow him on Twitter (@_lewtun) for NLP tips and tricks! +Lewis is a machine learning engineer at Hugging Face, focused on developing open-source tools and making them accessible to the wider community. He is also a co-author of the O’Reilly book [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). You can follow him on Twitter (@_lewtun) for NLP tips and tricks! **Matthew Carrigan:** *New TensorFlow Features for 🤗 Transformers and 🤗 Datasets* diff --git a/chapters/en/events/3.mdx b/chapters/en/events/3.mdx new file mode 100644 index 000000000..4febfe8f2 --- /dev/null +++ b/chapters/en/events/3.mdx @@ -0,0 +1,9 @@ +# Gradio Blocks Party + +Along with the release of the Gradio chapter of the course, Hugging Face hosted a community event on building cool machine learning demos using the new Gradio Blocks feature. + +You can find all the demos that the community created under the [`Gradio-Blocks`](https://huggingface.co/Gradio-Blocks) organisation on the Hub. Here's a few examples from the winners: + +**Natural language to SQL** + + diff --git a/chapters/es/chapter1/1.mdx b/chapters/es/chapter1/1.mdx index 5c82e6bba..02e15710b 100644 --- a/chapters/es/chapter1/1.mdx +++ b/chapters/es/chapter1/1.mdx @@ -46,9 +46,9 @@ Acerca de los autores: **Lucile Saulnier** es Ingeniera de Machine Learning en Hugging Face, donde desarrolla y apoya el uso de herramientas de código abierto. Ella está activamente involucrada en varios proyectos de investigación en el campo del Procesamiento de Lenguaje Natural como entrenamiento colaborativo y BigScience. -**Lewis Tunstall** es Ingeniero de Machine Learning en Hugging Face, enfocado en desarrollar herramientas de código abierto y hacerlas accesibles a la comunidad en general. También es coautor de un próximo [libro de O'Reilly sobre Transformadores](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). +**Lewis Tunstall** es Ingeniero de Machine Learning en Hugging Face, enfocado en desarrollar herramientas de código abierto y hacerlas accesibles a la comunidad en general. También es coautor de un próximo [libro de O'Reilly sobre Transformadores](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). -**Leandro von Werra** es Ingeniero de Machine Learning en el equipo de código abierto en Hugging Face y coautor de un próximo [libro de O'Reilly sobre Transformadores](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). Tiene varios años de experiencia en la industria llevando modelos de PLN a producción, trabajando a lo largo de todo el entorno de Machine Learning. +**Leandro von Werra** es Ingeniero de Machine Learning en el equipo de código abierto en Hugging Face y coautor de un próximo [libro de O'Reilly sobre Transformadores](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). Tiene varios años de experiencia en la industria llevando modelos de PLN a producción, trabajando a lo largo de todo el entorno de Machine Learning. ¿Estás listo para comenzar? En este capítulo vas a aprender: * Cómo usar la función `pipeline()` para resolver tareas de PLN como la generación y clasificación de texto diff --git a/chapters/fa/chapter1/1.mdx b/chapters/fa/chapter1/1.mdx index 88d60c612..0f6cb8b59 100644 --- a/chapters/fa/chapter1/1.mdx +++ b/chapters/fa/chapter1/1.mdx @@ -47,9 +47,9 @@ **لوسیله ساولنیر**[^8] مهندس یادگیری ماشین در هاگینگ‌فِیس است و بر روی توسعه و پشتیبانی از ابزارهای متن‌باز تمرکز دارد. وی همچنین بصورت فعالانه‌ای در بسیاری از پروژهای تحقیقاتی در حوزه پردازش زبان طبیعی، مانند یادگیری مشارکتی و بیگ‌ساینس مشارکت دارد. -**لویس تونستال**[^9] مهندس یادگیری ماشین در هاگینگ‌فِیس است. تمرکز اصلی او توسعه‌ی ابزارهای متن باز و دسترس‌پذیر کردن آنها برای جامعه‌ی گسترده‌تری از کاربران است. او همچنین از نویسندگان [کتاب انتشارات اُریلی[^10] درباره‌ی ترنسفورمرها](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/) است. +**لویس تونستال**[^9] مهندس یادگیری ماشین در هاگینگ‌فِیس است. تمرکز اصلی او توسعه‌ی ابزارهای متن باز و دسترس‌پذیر کردن آنها برای جامعه‌ی گسترده‌تری از کاربران است. او همچنین از نویسندگان [کتاب انتشارات اُریلی[^10] درباره‌ی ترنسفورمرها](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) است. -**لئاندرو ون ورا**[^11] مهندس یادگیری ماشین در تیم متن‌باز هاگینگ‌فِیس و از نویسندگان [کتاب انتشارات اُریلی درباره‌ی ترنسفورمرها](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/) است. وی تجربه‌ی چندین سال کار در صنعت را دارد. او با کار در تمام جنبه‌های یادگیری ماشین، پروژه‌های متن‌باز را از مرحله‌ی تحقیق به استقرار در صنایع می‌رساند. +**لئاندرو ون ورا**[^11] مهندس یادگیری ماشین در تیم متن‌باز هاگینگ‌فِیس و از نویسندگان [کتاب انتشارات اُریلی درباره‌ی ترنسفورمرها](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) است. وی تجربه‌ی چندین سال کار در صنعت را دارد. او با کار در تمام جنبه‌های یادگیری ماشین، پروژه‌های متن‌باز را از مرحله‌ی تحقیق به استقرار در صنایع می‌رساند. آماده‌ی ورود به این دوره هستید؟ در این فصل شما می‌آموزید که: diff --git a/chapters/fr/chapter1/1.mdx b/chapters/fr/chapter1/1.mdx index e0f68760d..146e246ad 100644 --- a/chapters/fr/chapter1/1.mdx +++ b/chapters/fr/chapter1/1.mdx @@ -50,9 +50,9 @@ Après avoir terminé ce cours, nous vous recommandons de suivre la [Spécialisa **Lucile Saulnier** est ingénieure en apprentissage machine chez Hugging Face et travaille au développement et à l'implémentation de nombreux outils *open source*. Elle est également activement impliquée dans de nombreux projets de recherche dans le domaine du NLP comme l'entraînement collaboratif de modèles et le projet [BigScience](https://bigscience.huggingface.co/). -**Lewis Tunstall** est ingénieur en apprentissage machine chez Hugging Face et dévoué au développement d'outils *open source* avec la volonté de les rendre accessibles à une communauté plus large. Il est également co-auteur du livre [*Natural Language Processing with Transformers*](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). +**Lewis Tunstall** est ingénieur en apprentissage machine chez Hugging Face et dévoué au développement d'outils *open source* avec la volonté de les rendre accessibles à une communauté plus large. Il est également co-auteur du livre [*Natural Language Processing with Transformers*](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). -**Leandro von Werra** est ingénieur en apprentissage machine dans l'équipe *open source* d'Hugging Face et également co-auteur du livre [*Natural Language Processing with Transformers*](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). Il a plusieurs années d'expérience dans l'industrie où il a pu déployer des projets de NLP en production et travailler sur toutes les étapes clefs du déploiement. +**Leandro von Werra** est ingénieur en apprentissage machine dans l'équipe *open source* d'Hugging Face et également co-auteur du livre [*Natural Language Processing with Transformers*](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). Il a plusieurs années d'expérience dans l'industrie où il a pu déployer des projets de NLP en production et travailler sur toutes les étapes clefs du déploiement. Êtes-vous prêt à commencer ? Dans ce chapitre, vous apprendrez : * à utiliser la fonction `pipeline()` pour résoudre des problèmes de NLP comme la génération de texte et la classification, diff --git a/chapters/fr/chapter5/4.mdx b/chapters/fr/chapter5/4.mdx index d91d0d0b7..1c843b4e8 100644 --- a/chapters/fr/chapter5/4.mdx +++ b/chapters/fr/chapter5/4.mdx @@ -18,7 +18,7 @@ Dans cette section, nous allons explorer ces fonctionnalités de 🤗 *Datasets* ## Qu'est-ce que The Pile ? -*The Pile* est un corpus de texte en anglais créé par [EleutherAI](https://www.eleuther.ai) pour entraîner des modèles de langage à grande échelle. Il comprend une gamme variée de jeux de données, couvrant des articles scientifiques, des référentiels de code GitHub et du texte Web filtré. Le corpus d’entraînement est disponible en [morceaux de 14 Go](https://mystic.the-eye.eu/public/AI/pile/) et vous pouvez aussi télécharger plusieurs des [composants individuels]( https://mystic.the-eye.eu/public/AI/pile_preliminary_components/). Commençons par jeter un coup d'œil au jeu de données *PubMed Abstracts*, qui est un corpus de résumés de 15 millions de publications biomédicales sur [PubMed](https://pubmed.ncbi.nlm.nih.gov/). Le jeu de données est au [format JSON Lines](https://jsonlines.org) et est compressé à l'aide de la bibliothèque `zstandard`. Nous devons donc d'abord installer cette bibliothèque : +*The Pile* est un corpus de texte en anglais créé par [EleutherAI](https://www.eleuther.ai) pour entraîner des modèles de langage à grande échelle. Il comprend une gamme variée de jeux de données, couvrant des articles scientifiques, des référentiels de code GitHub et du texte Web filtré. Le corpus d’entraînement est disponible en [morceaux de 14 Go](https://the-eye.eu/public/AI/pile/) et vous pouvez aussi télécharger plusieurs des [composants individuels]( https://the-eye.eu/public/AI/pile_preliminary_components/). Commençons par jeter un coup d'œil au jeu de données *PubMed Abstracts*, qui est un corpus de résumés de 15 millions de publications biomédicales sur [PubMed](https://pubmed.ncbi.nlm.nih.gov/). Le jeu de données est au [format JSON Lines](https://jsonlines.org) et est compressé à l'aide de la bibliothèque `zstandard`. Nous devons donc d'abord installer cette bibliothèque : ```py !pip install zstandard @@ -30,7 +30,7 @@ Ensuite, nous pouvons charger le jeu de données en utilisant la méthode pour l from datasets import load_dataset # Cela prend quelques minutes à exécuter, alors allez prendre un thé ou un café en attendant :) -data_files = "https://mystic.the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst" +data_files = "https://the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst" pubmed_dataset = load_dataset("json", data_files=data_files, split="train") pubmed_dataset ``` @@ -103,7 +103,7 @@ Malgré sa taille de près de 20 Go, nous pouvons charger et accéder au jeu de -✏️ **Essayez !** Choisissez l'un des [sous-ensembles](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/) de *The Pile* qui est plus grand que la RAM de votre ordinateur portable ou de bureau. Chargez-le avec 🤗 *Datasets* et mesurez la quantité de RAM utilisée. Notez que pour obtenir une mesure précise, vous devrez le faire dans un nouveau processus. Vous pouvez trouver les tailles décompressées de chaque sous-ensemble dans le tableau 1 du papier de [*The Pile*](https://arxiv.org/abs/2101.00027). +✏️ **Essayez !** Choisissez l'un des [sous-ensembles](https://the-eye.eu/public/AI/pile_preliminary_components/) de *The Pile* qui est plus grand que la RAM de votre ordinateur portable ou de bureau. Chargez-le avec 🤗 *Datasets* et mesurez la quantité de RAM utilisée. Notez que pour obtenir une mesure précise, vous devrez le faire dans un nouveau processus. Vous pouvez trouver les tailles décompressées de chaque sous-ensemble dans le tableau 1 du papier de [*The Pile*](https://arxiv.org/abs/2101.00027). @@ -234,7 +234,7 @@ Terminons notre exploration du streaming des jeux de données avec une applicati ```py law_dataset_streamed = load_dataset( "json", - data_files="https://mystic.the-eye.eu/public/AI/pile_preliminary_components/FreeLaw_Opinions.jsonl.zst", + data_files="https://the-eye.eu/public/AI/pile_preliminary_components/FreeLaw_Opinions.jsonl.zst", split="train", streaming=True, ) @@ -272,7 +272,7 @@ Ici, nous avons utilisé la fonction `islice()` du module `itertools` de Python Enfin, si vous souhaitez streamer *The Pile* dans son intégralité de 825 Go, vous pouvez récupérer tous les fichiers préparés comme suit : ```py -base_url = "https://mystic.the-eye.eu/public/AI/pile/" +base_url = "https://the-eye.eu/public/AI/pile/" data_files = { "train": [base_url + "train/" + f"{idx:02d}.jsonl.zst" for idx in range(30)], "validation": base_url + "val.jsonl.zst", diff --git a/chapters/fr/chapter6/3b.mdx b/chapters/fr/chapter6/3b.mdx index f725e30d7..06fd361c6 100644 --- a/chapters/fr/chapter6/3b.mdx +++ b/chapters/fr/chapter6/3b.mdx @@ -654,8 +654,8 @@ 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[0] - end_idx = idx % scores.shape[0] + 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)) @@ -670,8 +670,8 @@ 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[0] - end_idx = idx % scores.shape[0] + 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)) diff --git a/chapters/fr/chapter7/3.mdx b/chapters/fr/chapter7/3.mdx index 664624b5b..4ca41a4b5 100644 --- a/chapters/fr/chapter7/3.mdx +++ b/chapters/fr/chapter7/3.mdx @@ -524,6 +524,7 @@ def whole_word_masking_data_collator(features): 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) ``` @@ -564,6 +565,7 @@ def whole_word_masking_data_collator(features): 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) ``` diff --git a/chapters/fr/event/1.mdx b/chapters/fr/event/1.mdx index e6d766bd7..ff5a1578f 100644 --- a/chapters/fr/event/1.mdx +++ b/chapters/fr/event/1.mdx @@ -89,7 +89,7 @@ Jakob Uszkoreit est le cofondateur d'Inceptive. Inceptive conçoit des molécule
-Lewis est un ingénieur en apprentissage machine chez Hugging Face qui se concentre sur le développement d'outils open-source et les rend accessibles à la communauté. Il est également co-auteur du livre [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/) paru chez O'Reilly. Vous pouvez le suivre sur Twitter (@_lewtun) pour des conseils et astuces en traitement du langage naturel ! +Lewis est un ingénieur en apprentissage machine chez Hugging Face qui se concentre sur le développement d'outils open-source et les rend accessibles à la communauté. Il est également co-auteur du livre [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) paru chez O'Reilly. Vous pouvez le suivre sur Twitter (@_lewtun) pour des conseils et astuces en traitement du langage naturel ! **Matthew Carrigan :** *Nouvelles fonctionnalités en TensorFlow pour 🤗 Transformers et 🤗 Datasets* diff --git a/chapters/hi/chapter1/1.mdx b/chapters/hi/chapter1/1.mdx index 8a4654ed9..14169780c 100644 --- a/chapters/hi/chapter1/1.mdx +++ b/chapters/hi/chapter1/1.mdx @@ -46,9 +46,9 @@ **ल्यूसिले शाॅलनियर** हगिंग फेस में एक मशीन लर्निंग इंजीनियर है, जो ओपन-सोर्स टूल के उपयोग का विकास और समर्थन करता है। वह सहयोगात्मक प्रशिक्षण और बिगसाइंस जैसे प्राकृतिक भाषा प्रसंस्करण के क्षेत्र में कई शोध परियोजनाओं में भी सक्रिय रूप से शामिल हैं। -**लुईस ट्यूनस्टाल** हगिंग फेस में एक मशीन लर्निंग इंजीनियर है, जो ओपन-सोर्स टूल विकसित करने और उन्हें व्यापक समुदाय के लिए सुलभ बनाने पर केंद्रित है। वह आगामी [ओ'रेली बुक ऑन ट्रांसफॉर्मर्स](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/) के सह-लेखक भी हैं। +**लुईस ट्यूनस्टाल** हगिंग फेस में एक मशीन लर्निंग इंजीनियर है, जो ओपन-सोर्स टूल विकसित करने और उन्हें व्यापक समुदाय के लिए सुलभ बनाने पर केंद्रित है। वह आगामी [ओ'रेली बुक ऑन ट्रांसफॉर्मर्स](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) के सह-लेखक भी हैं। -**लिंड्रो वॉन वेरा** हगिंग फेस की ओपन-सोर्स टीम में मशीन लर्निंग इंजीनियर हैं और आगामी [ओ'रेली बुक ऑन ट्रांसफॉर्मर्स](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/) के सह-लेखक भी हैं। पूरे मशीन लर्निंग स्टैक में काम करके एनएलपी परियोजनाओं को उत्पादन में लाने के लिए उनके पास कई वर्षों का औद्योगिक अनुभव है। +**लिंड्रो वॉन वेरा** हगिंग फेस की ओपन-सोर्स टीम में मशीन लर्निंग इंजीनियर हैं और आगामी [ओ'रेली बुक ऑन ट्रांसफॉर्मर्स](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) के सह-लेखक भी हैं। पूरे मशीन लर्निंग स्टैक में काम करके एनएलपी परियोजनाओं को उत्पादन में लाने के लिए उनके पास कई वर्षों का औद्योगिक अनुभव है। क्या आप तैयार हैं? इस अध्याय में आप सीखेंगे: * पाठ निर्माण और वर्गीकरण जैसे एनएलपी कार्यों को हल करने के लिए `pipeline()` फ़ंक्शन का उपयोग कैसे करें diff --git a/chapters/it/chapter1/1.mdx b/chapters/it/chapter1/1.mdx index a2258a521..9021db772 100644 --- a/chapters/it/chapter1/1.mdx +++ b/chapters/it/chapter1/1.mdx @@ -47,9 +47,9 @@ A proposito degli autori: **Lucile Saulnier** è machine learning engineer da Hugging Face, e sviluppa e supporta l'utilizzo di strumenti open source. È anche attivamente coinvolta in numerosi progetti di ricerca nell'ambito del NLP, come ad esempio collaborative training e BigScience. -**Lewis Tunstall** è machine learning engineer da Hugging Face che si specializza nello sviluppo di strumenti open-source e la loro distribuzione alla comunità più ampia. È anche co-autore dell'imminente [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). +**Lewis Tunstall** è machine learning engineer da Hugging Face che si specializza nello sviluppo di strumenti open-source e la loro distribuzione alla comunità più ampia. È anche co-autore dell'imminente [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). -**Leandro von Werra** è machine learning engineer nel team open-source di Hugging Face, nonché co-autore dell'imminente [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). Ha tanti anni di esperienza nel portare progetti di NLP in produzione, lavorando a tutti i livelli di esecuzione di compiti di machine learning. +**Leandro von Werra** è machine learning engineer nel team open-source di Hugging Face, nonché co-autore dell'imminente [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). Ha tanti anni di esperienza nel portare progetti di NLP in produzione, lavorando a tutti i livelli di esecuzione di compiti di machine learning. Sei pronto/a a iniziare? In questo capitolo, imparerai: * Ad utilizzare la funzione `pipeline()` per eseguire compiti di NLP come la generazione e classificazione di testi diff --git a/chapters/it/chapter5/4.mdx b/chapters/it/chapter5/4.mdx index c385da269..57f15060c 100644 --- a/chapters/it/chapter5/4.mdx +++ b/chapters/it/chapter5/4.mdx @@ -18,7 +18,7 @@ In questa sezione esploreremo queste funzionalità di 🤗 Datasets con un enorm ## Cos'è Pile? -The Pile è un corpus testuale creato da [EleutherAI](https://www.eleuther.ai) per addestrare modelli di linguaggio su grande scala. Include un grande varietà di dataset, a partire da articoli scientifici, repository di codici da GitHub, e testi dal web filtrati. Il corpus di addestramento è disponibili in [frammenti da 14 GB](https://mystic.the-eye.eu/public/AI/pile/), ed è possibile scaricare diverse delle [componenti singole](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/). Iniziamo dando uno sguardo al dataset PubMed Abstracts, un corpus di abstract da 15 milioni di pubblicazioni in ambito biomedico da [PubMed](https://pubmed.ncbi.nlm.nih.gov/). Il dataset è in [formato JSON Lines](https://jsonlines.org) ed è stato compressato usando la libreria `zstandard`, per cui dobbiamo prima installarla: +The Pile è un corpus testuale creato da [EleutherAI](https://www.eleuther.ai) per addestrare modelli di linguaggio su grande scala. Include un grande varietà di dataset, a partire da articoli scientifici, repository di codici da GitHub, e testi dal web filtrati. Il corpus di addestramento è disponibili in [frammenti da 14 GB](https://the-eye.eu/public/AI/pile/), ed è possibile scaricare diverse delle [componenti singole](https://the-eye.eu/public/AI/pile_preliminary_components/). Iniziamo dando uno sguardo al dataset PubMed Abstracts, un corpus di abstract da 15 milioni di pubblicazioni in ambito biomedico da [PubMed](https://pubmed.ncbi.nlm.nih.gov/). Il dataset è in [formato JSON Lines](https://jsonlines.org) ed è stato compressato usando la libreria `zstandard`, per cui dobbiamo prima installarla: ```py @@ -31,7 +31,7 @@ Ora, possiamo caricare il dataset utilizzando il meotodo per file remoti che abb from datasets import load_dataset # Ci vuole qualche minuto per l'esecuzione, quindi preparati un tè o un caffè nell'attesa :) -data_files = "https://mystic.the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst" +data_files = "https://the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst" pubmed_dataset = load_dataset("json", data_files=data_files, split="train") pubmed_dataset ``` @@ -102,7 +102,7 @@ Bene -- nonostante sia grande quasi 30 GB, siamo in grado di caricare e accedere -✏️ **Provaci tu!** Scegli uno dei [subset](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/) di Pile che è più grande della RAM del tuo PC o del tuo portatile, caricalo utilizzando 🤗 Datasets e calcola la quantità di RAM utilizzata. Nota che per avere un valore preciso, dovrai creare un nuovo processo. Puoi trovare le grandezze decompresse di ogni subset nella Tavola 1 dell'[articolo su Pile](https://arxiv.org/abs/2101.00027) +✏️ **Provaci tu!** Scegli uno dei [subset](https://the-eye.eu/public/AI/pile_preliminary_components/) di Pile che è più grande della RAM del tuo PC o del tuo portatile, caricalo utilizzando 🤗 Datasets e calcola la quantità di RAM utilizzata. Nota che per avere un valore preciso, dovrai creare un nuovo processo. Puoi trovare le grandezze decompresse di ogni subset nella Tavola 1 dell'[articolo su Pile](https://arxiv.org/abs/2101.00027) @@ -226,7 +226,7 @@ Concludiamo la nostra ricognizione dello streaming di dataset con un'applicazion ```py law_dataset_streamed = load_dataset( "json", - data_files="https://mystic.the-eye.eu/public/AI/pile_preliminary_components/FreeLaw_Opinions.jsonl.zst", + data_files="https://the-eye.eu/public/AI/pile_preliminary_components/FreeLaw_Opinions.jsonl.zst", split="train", streaming=True, ) @@ -264,7 +264,7 @@ Abbiamo utilizzato la funzione `islice()` del modulo Python `itertools` per sele Infine, se vuoi processare il Pile in streaming, in tutti i suoi 825 GB, puoi recuperare tutti i file preparati, come segue: ```py -base_url = "https://mystic.the-eye.eu/public/AI/pile/" +base_url = "https://the-eye.eu/public/AI/pile/" data_files = { "train": [base_url + "train/" + f"{idx:02d}.jsonl.zst" for idx in range(30)], "validation": base_url + "val.jsonl.zst", diff --git a/chapters/ja/chapter1/1.mdx b/chapters/ja/chapter1/1.mdx index fdcb55837..51ff86b25 100644 --- a/chapters/ja/chapter1/1.mdx +++ b/chapters/ja/chapter1/1.mdx @@ -47,9 +47,9 @@ **Lucile Saulnier**はHugging Faceの機械学習エンジニアで、オープンソースツールの開発および利用のサポートを行っています。また、共同でのモデルの学習やBigScienceなど、自然言語処理の分野で多くの研究プロジェクトに積極的に参加しています。 -**Lewis Tunstall**はHugging Faceの機械学習エンジニアで、オープンソースツールの開発とより広いコミュニティで利用されるようにすることに注力しています。また、[オライリー出版のTransformersに関する本](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/)の著者の1人です。 +**Lewis Tunstall**はHugging Faceの機械学習エンジニアで、オープンソースツールの開発とより広いコミュニティで利用されるようにすることに注力しています。また、[オライリー出版のTransformersに関する本](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/)の著者の1人です。 -**Leandro von Werra**はHugging Faceのオープンソースチームの機械学習エンジニアであり、[オライリー出版のTransformersに関する本](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/)の著者の1人です。機械学習全般に関わり、NLPプロジェクトを実運用に移行する経験をこの業界で数年積んでいます。 +**Leandro von Werra**はHugging Faceのオープンソースチームの機械学習エンジニアであり、[オライリー出版のTransformersに関する本](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/)の著者の1人です。機械学習全般に関わり、NLPプロジェクトを実運用に移行する経験をこの業界で数年積んでいます。 準備はできていますか?この章では、以下のことを学びます: * `pipeline()`機能を使ったテキスト生成や分類などNLPタスクの取り組み方 diff --git a/chapters/ja/chapter7/3.mdx b/chapters/ja/chapter7/3.mdx index 740c3f9bb..afdb30047 100644 --- a/chapters/ja/chapter7/3.mdx +++ b/chapters/ja/chapter7/3.mdx @@ -535,6 +535,7 @@ def whole_word_masking_data_collator(features): 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) ``` @@ -575,6 +576,7 @@ def whole_word_masking_data_collator(features): 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) ``` diff --git a/chapters/ja/event/1.mdx b/chapters/ja/event/1.mdx index 6d38ad10b..44663665e 100644 --- a/chapters/ja/event/1.mdx +++ b/chapters/ja/event/1.mdx @@ -112,7 +112,7 @@ InceptiveはRNAベースの医薬品をより入手しやすく、より効果
Lewis TunstallはHugging Faceの機械学習エンジニアで、オープンソースのツールを開発し、より広いコミュニティで利用できるようにすることに注力しています。 -また、オライリーの書籍[Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/)の共著者でもあります。 +また、オライリーの書籍[Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/)の共著者でもあります。 Twitter (@_lewtun) では、自然言語処理に関するtipsを紹介しています. diff --git a/chapters/ko/chapter1/1.mdx b/chapters/ko/chapter1/1.mdx index 6c6bf2410..90fe109d6 100644 --- a/chapters/ko/chapter1/1.mdx +++ b/chapters/ko/chapter1/1.mdx @@ -46,9 +46,9 @@ **Lucile Saulnier**은 Hugging Face의 ML 엔지니어로 오픈 소스 툴 사용에 대한 개발 및 지원을 담당합니다. 자연어 처리 분야에서 협업 학습, BigScience등과 같은 다양한 리서치 프로젝트에도 활발히 참여하고 있습니다. -**Lewis Tunstall**는 Hugging Face의 ML 엔지니어로 오픈 소스 툴을 개발하여 더 많은 커뮤니티에 상용화되도록 하는 데에 초점을 맞추고 있습니다. 곧 출간되는 [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/)의 공저자이기도 합니다. +**Lewis Tunstall**는 Hugging Face의 ML 엔지니어로 오픈 소스 툴을 개발하여 더 많은 커뮤니티에 상용화되도록 하는 데에 초점을 맞추고 있습니다. 곧 출간되는 [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/)의 공저자이기도 합니다. -**Leandro von Werra**는 Hugging Face 오픈소스 팀의 머신 러닝 엔지니어이자 곧 출간될 [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/)의 공동 저자입니다. 모든 머신 러닝 스택에서의 작업을 통해 수 년간 NLP 프로젝트를 프로덕션으로 들여온 경력자입니다. +**Leandro von Werra**는 Hugging Face 오픈소스 팀의 머신 러닝 엔지니어이자 곧 출간될 [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/)의 공동 저자입니다. 모든 머신 러닝 스택에서의 작업을 통해 수 년간 NLP 프로젝트를 프로덕션으로 들여온 경력자입니다. 시작할 준비가 되셨나요? 이번 챕터에서 다룰 내용은 다음과 같습니다: diff --git a/chapters/pt/chapter1/1.mdx b/chapters/pt/chapter1/1.mdx index 350a2dea9..89772f4ba 100644 --- a/chapters/pt/chapter1/1.mdx +++ b/chapters/pt/chapter1/1.mdx @@ -45,9 +45,9 @@ Sobre os autores: **Lucile Saulnier** é uma Engenheira de Machine Learning na Hugging Face, desenvolvendo e apoiando o uso de ferramentas de código aberto. Ela também é ativamente envolvida em muitos projetos de pesquisa no campo do Processamento de Linguagem natural assim como em treinamentos colaborativos e BigScience. -**Lewis Tunstall** é um Engenheiro de Machine Learning na Hugging Face, focado no desenvolvimento de ferramentas open-source e em fazê-las amplamente acessíveis pela comunidade. Ele também é co-autor do livro que está pra lançar [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). +**Lewis Tunstall** é um Engenheiro de Machine Learning na Hugging Face, focado no desenvolvimento de ferramentas open-source e em fazê-las amplamente acessíveis pela comunidade. Ele também é co-autor do livro que está pra lançar [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). -**Leandro von Werra** é um Engenheiro de Machine Learning no time de open-source na Hugging Face e também co-autor do livro [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). Ele tem muitos anos de experiência na indústria trazendo projetos de NLP para produção trabalhando com várias stacks de Machine Learning. +**Leandro von Werra** é um Engenheiro de Machine Learning no time de open-source na Hugging Face e também co-autor do livro [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). Ele tem muitos anos de experiência na indústria trazendo projetos de NLP para produção trabalhando com várias stacks de Machine Learning. Está pronto para seguir? Nesse capítulo, você aprenderá: * Como usar a função `pipeline()` para solucionar tarefas de NLP tais como geração de texto e classificação diff --git a/chapters/pt/chapter5/4.mdx b/chapters/pt/chapter5/4.mdx index 9dac76f52..0018334e6 100644 --- a/chapters/pt/chapter5/4.mdx +++ b/chapters/pt/chapter5/4.mdx @@ -18,7 +18,7 @@ Nesta seção, exploraremos esses recursos de 🤗 Conjuntos de dados com um eno ## O que é the Pile? -O `The Pile` é um corpus de texto em inglês que foi criado por [EleutherAI](https://www.eleuther.ai) para treinar modelos de linguagem em larga escala. Ele inclui uma gama diversificada de conjuntos de dados, abrangendo artigos científicos, repositórios de código do GitHub e texto da web filtrado. O corpus de treinamento está disponível em [blocos de 14 GB](https://mystic.the-eye.eu/public/AI/pile/), e você também pode baixar vários dos [componentes individuais](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/). Vamos começar dando uma olhada no conjunto de dados PubMed Abstracts, que é um corpus de resumos de 15 milhões de publicações biomédicas no [PubMed](https://pubmed.ncbi.nlm.nih.gov/). O conjunto de dados está em [formato JSON Lines](https://jsonlines.org) e é compactado usando a biblioteca `zstandard`, então primeiro precisamos instalá-lo: +O `The Pile` é um corpus de texto em inglês que foi criado por [EleutherAI](https://www.eleuther.ai) para treinar modelos de linguagem em larga escala. Ele inclui uma gama diversificada de conjuntos de dados, abrangendo artigos científicos, repositórios de código do GitHub e texto da web filtrado. O corpus de treinamento está disponível em [blocos de 14 GB](https://the-eye.eu/public/AI/pile/), e você também pode baixar vários dos [componentes individuais](https://the-eye.eu/public/AI/pile_preliminary_components/). Vamos começar dando uma olhada no conjunto de dados PubMed Abstracts, que é um corpus de resumos de 15 milhões de publicações biomédicas no [PubMed](https://pubmed.ncbi.nlm.nih.gov/). O conjunto de dados está em [formato JSON Lines](https://jsonlines.org) e é compactado usando a biblioteca `zstandard`, então primeiro precisamos instalá-lo: ```py !pip install zstandard @@ -30,7 +30,7 @@ Em seguida, podemos carregar o conjunto de dados usando o método para arquivos from datasets import load_dataset # This takes a few minutes to run, so go grab a tea or coffee while you wait :) -data_files = "https://mystic.the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst" +data_files = "https://the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst" pubmed_dataset = load_dataset("json", data_files=data_files, split="train") pubmed_dataset ``` @@ -101,7 +101,7 @@ Legal -- apesar de ter quase 20 GB de tamanho, podemos carregar e acessar o conj -✏️ **Experimente!** Escolha um dos [subconjuntos](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/) da `The Pile` que é maior que a RAM do seu laptop ou desktop, carregue com 🤗 Datasets e meça a quantidade de RAM usada. Observe que, para obter uma medição precisa, você desejará fazer isso em um novo processo. Você pode encontrar os tamanhos descompactados de cada subconjunto na Tabela 1 do [artigo do `The Pile`](https://arxiv.org/abs/2101.00027). +✏️ **Experimente!** Escolha um dos [subconjuntos](https://the-eye.eu/public/AI/pile_preliminary_components/) da `The Pile` que é maior que a RAM do seu laptop ou desktop, carregue com 🤗 Datasets e meça a quantidade de RAM usada. Observe que, para obter uma medição precisa, você desejará fazer isso em um novo processo. Você pode encontrar os tamanhos descompactados de cada subconjunto na Tabela 1 do [artigo do `The Pile`](https://arxiv.org/abs/2101.00027). @@ -225,7 +225,7 @@ Vamos completar nossa exploração de streaming de conjuntos de dados com um apl ```py law_dataset_streamed = load_dataset( "json", - data_files="https://mystic.the-eye.eu/public/AI/pile_preliminary_components/FreeLaw_Opinions.jsonl.zst", + data_files="https://the-eye.eu/public/AI/pile_preliminary_components/FreeLaw_Opinions.jsonl.zst", split="train", streaming=True, ) @@ -263,7 +263,7 @@ Aqui usamos a função `islice()` do módulo `itertools` do Python para selecion Por fim, se você quiser transmitir o Pile em sua totalidade de 825 GB, poderá pegar todos os arquivos preparados da seguinte maneira: ```py -base_url = "https://mystic.the-eye.eu/public/AI/pile/" +base_url = "https://the-eye.eu/public/AI/pile/" data_files = { "train": [base_url + "train/" + f"{idx:02d}.jsonl.zst" for idx in range(30)], "validation": base_url + "val.jsonl.zst", diff --git a/chapters/pt/event/1.mdx b/chapters/pt/event/1.mdx index 3eb60cc4f..30f7cc260 100644 --- a/chapters/pt/event/1.mdx +++ b/chapters/pt/event/1.mdx @@ -86,7 +86,7 @@ Jakob Uszkoreit é o cofundador da Inceptive. A Inceptive projeta moléculas de
-Lewis é machine learning engineer no Hugging Face, focado em desenvolver ferramentas de código aberto e torná-las acessíveis para a comunidade em geral. Ele também é coautor do livro de O'Reilly [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). Você pode segui-lo no Twitter (@_lewtun) para dicas e truques de PNL! +Lewis é machine learning engineer no Hugging Face, focado em desenvolver ferramentas de código aberto e torná-las acessíveis para a comunidade em geral. Ele também é coautor do livro de O'Reilly [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). Você pode segui-lo no Twitter (@_lewtun) para dicas e truques de PNL! **Matthew Carrigan:** *New TensorFlow Features for 🤗 Transformers and 🤗 Datasets* diff --git a/chapters/ru/_toctree.yml b/chapters/ru/_toctree.yml index 9aef5a09d..0adaa40fb 100644 --- a/chapters/ru/_toctree.yml +++ b/chapters/ru/_toctree.yml @@ -65,4 +65,26 @@ title: Первая часть завершена! - local: chapter4/6 title: Итоговый тест по главе - quiz: 4 \ No newline at end of file + quiz: 4 +- title: 5. Библиотека 🤗 Datasets + sections: + - local: chapter5/1 + title: Введение + - local: chapter5/2 + title: Что делать, если моего датасета на нет на Hub? + - local: chapter5/3 + title: Препарируем 🤗 Datasets + - local: chapter5/4 + title: Big data? 🤗 Datasets спешат на помощь! + - local: chapter5/6 + title: Семантический поиск с помощью FAISS + - local: chapter5/7 + title: 🤗 Datasets, итоги! + - local: chapter5/8 + title: Тест по главе 5 +- title: Бибилиотека 🤗 Tokenizers + sections: + - local: chapter6/1 + title: Введение + - local: chapter6/2 + title: Обучение токенизатора на основе существующего diff --git a/chapters/ru/chapter1/1.mdx b/chapters/ru/chapter1/1.mdx index 6d5e1cd3b..3c10ca68f 100644 --- a/chapters/ru/chapter1/1.mdx +++ b/chapters/ru/chapter1/1.mdx @@ -48,9 +48,9 @@ **Lucile Saulnier** - ML-инженер в Hugging Face, разрабатывающая и поддерживающая использование инструментов с открытым исходным кодом. Она также активно участвует во многих исследовательских проектах в области NLP, таких как совместное обучение и BigScience. -**Lewis Tunstall** - ML-инженер в Hugging Face, сосредоточен на разработке инструментов с открытым исходным кодом и обеспечении их доступности для более широкого сообщества. Соавтор будущей книги [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). +**Lewis Tunstall** - ML-инженер в Hugging Face, сосредоточен на разработке инструментов с открытым исходным кодом и обеспечении их доступности для более широкого сообщества. Соавтор будущей книги [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). -**Leandro von Werra** - ML-инженер в команде, работающей над открытым исходным кодом Hugging Face и соавтор будушей будущей книги [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). Обладает большим опытом реализации NLP-проектов в промышленности. +**Leandro von Werra** - ML-инженер в команде, работающей над открытым исходным кодом Hugging Face и соавтор будушей будущей книги [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). Обладает большим опытом реализации NLP-проектов в промышленности. Вы готовы начать? В этой главе вы узнаете: diff --git a/chapters/ru/chapter5/1.mdx b/chapters/ru/chapter5/1.mdx new file mode 100644 index 000000000..ff8429d6a --- /dev/null +++ b/chapters/ru/chapter5/1.mdx @@ -0,0 +1,17 @@ +# Введение + +В [главе 3](/course/ru/chapter3) вы поверхностно ознакомились с библиотекой 🤗 Datasets и увидели три главных шага для использования ее в процессе fine-tuning: + +1. Загрузить датасет из Hugging Face Hub. +2. Произвести препроцессинг с помощью `Dataset.map()`. +3. Загрузить и вычислить метрики. + +Но это лишь малая часть того, на что способна 🤗 Datasets! В этой главе мы углубимся в библиотеку и попутно мы найдем ответы на следующие вопросы: + +* Что делать, когда нужного набора данных нет в Hub? +* Как вы можете разделиить датасет? (Что если вам _действительно_ нужно использовать Pandas?) +* Что делать, когда ваш набор данных огромен и «расплавит» оперативную память вашего ноутбука? +* Что, черт возьми, такое «отображение памяти» (memory mapping) и Apache Arrow? +* Как вы можете создать свой собственный датасет и отправить его в Hub? + +Принципы, которые вы изучите в этой главе, подготовят вас к более глубокому использованию токенизации и fine-tuning'а моделей в [главе 6](/course/ru/chapter6) и [главе 7](/course/ru/chapter7) – заваривайте кофе и мы начинаем! \ No newline at end of file diff --git a/chapters/ru/chapter5/2.mdx b/chapters/ru/chapter5/2.mdx new file mode 100644 index 000000000..ee50ef207 --- /dev/null +++ b/chapters/ru/chapter5/2.mdx @@ -0,0 +1,163 @@ +# Что делать, если моего датасета на нет на Hub? + + + +Вы знаете, как использовать [Hugging Face Hub](https://huggingface.co/datasets) для скачивания датасетов, но часто складывается ситуация, когда нужные данные не хранятся у вас локально или на удаленном сервере. В этом разделе мы посмотрим, как библиотека 🤗 Datasets может быть использована для загрузки датасетов, которые не хранятся на Hugging Face Hub. + + + +## Работа с локальными и удаленными датасетами + +🤗 Datasets предоставляет скрипты для загрузки собственных датасетов. Библиотека поддерживает несколько распространенных форматов: + +| Data format | Loading script | Example | +| :----------------: | :------------: | :-----------------------------------------------------: | +| CSV & TSV | `csv` | `load_dataset("csv", data_files="my_file.csv")` | +| Text files | `text` | `load_dataset("text", data_files="my_file.txt")` | +| JSON & JSON Lines | `json` | `load_dataset("json", data_files="my_file.jsonl")` | +| Pickled DataFrames | `pandas` | `load_dataset("pandas", data_files="my_dataframe.pkl")` | + +Как показано в таблице, для каждого формата мы должны задать тип скрипта загрузки в функции `load_dataset()` вместе с аргументом `data_files`, который указывает путь к одному или нескольким файлам. Начнем с загрузки набора данных из локальных файлов; позже мы увидим, как сделать то же самое с файлами, расположены на удаленном сервере. + +## Загрузка локального датасета + +Для этого примера мы будем использовать датасет [SQuAD-it dataset](https://github.com/crux82/squad-it/). Это большой датасет для задачи question answering на итальянском языке. + +Обучающая и тестовая часть расположены на GitHub, мы можем скачать файлы с помощью простой команды `wget`. + +```python +!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-train.json.gz +!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-test.json.gz +``` +Выполнение этих команд запустит процесс скачивания файлов *SQuAD_it-train.json.gz* и *SQuAD_it-test.json.gz*, которые мы можем распаковать с помощью Linux команды `gzip`: + +```python +!gzip -dkv SQuAD_it-*.json.gz +``` + +```bash +SQuAD_it-test.json.gz: 87.4% -- replaced with SQuAD_it-test.json +SQuAD_it-train.json.gz: 82.2% -- replaced with SQuAD_it-train.json +``` +После выполнения команд мы увидим, что архивы будут заменены файлами _SQuAD_it-train.json_ и _SQuAD_it-text.json_ в формате JSON. + + +✎ Причина, по которой в примере выше перед командами расположен `!` заключается в том, что мы выполняем их в Jupyter notebook. Если вы хотите запустить эти команды в терминале – просто удалите `!`. + + +Для загрузки JSON файла с помощью функции `load_dataset()` необходимо знать, с каким типом JSON-файла мы имеем дело: обычный JSON (похожий на вложенный словарь) или JSON, сформированный построчно. Как и многие датасеты для задач question-answering, SQuAD-it использует формат обычного JSON'а с текстом, хранящимся в поле `data`. Это означает, что мы можем подгрузить датасет, задав аргумент `field` следующим образом: + +```py +from datasets import load_dataset + +squad_it_dataset = load_dataset("json", data_files="SQuAD_it-train.json", field="data") +``` + +По умолчанию при загрузке локальных файлов создается объект `DatasetDict` с меткой `train`. Мы можем изучить объект `squad_it_dataset`: + +```py +squad_it_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 442 + }) +}) +``` + +Выше распечатана информация об объекте: число строк и колонки обучающего датасета. Мы можем посмотреть на один объект, проиндексировав его как `train` следующим образом: + +```py +squad_it_dataset["train"][0] +``` + +```python out +{ + "title": "Terremoto del Sichuan del 2008", + "paragraphs": [ + { + "context": "Il terremoto del Sichuan del 2008 o il terremoto...", + "qas": [ + { + "answers": [{"answer_start": 29, "text": "2008"}], + "id": "56cdca7862d2951400fa6826", + "question": "In quale anno si è verificato il terremoto nel Sichuan?", + }, + ... + ], + }, + ... + ], +} +``` +Отлично! Мы загрузили наш первый датасет! Но пока мы это сделали только для обучающей части данных, хотя нам нужны и `train`, и `test` в одном `DatasetDict`, чтобы мы могли применить функцию `Dataset.map()` на оба подмножества сразу. Чтобы сделать это, мы можем передать в словарь в `data_files`. Сделать это можно так: + +```py +data_files = {"train": "SQuAD_it-train.json", "test": "SQuAD_it-test.json"} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +squad_it_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 442 + }) + test: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 48 + }) +}) +``` + +Это ровно то, чего мы хотели добиться! Далее мы можем применять различные приемы для препроцессинга данных: очистку, токенизацию и прочее. + + + +Аргумент `data_files` функции `load_dataset()` очень гибкий и может являться путем к файлу, списком путей файлов или словарем, в котором указаны названия сплитов (обучающего и тестового) и пути к соответствующим файлам. Вы также можете найти все подходящие файлы в директории с использованием маски по правилам Unix-консоли (т.е. указать путь к директории и указать `data_files="*.json"` для конкретного сплита). Более подробно это изложено в [документации](https://huggingface.co/docs/datasets/loading.html#local-and-remote-files) 🤗 Datasets. + + + +Скрипты загрузки 🤗 Datasets также поддерживают автоматическую распаковку входных файлов, поэтому мы можем пропустить команду `gzip` просто передав в аргумент `data_files` пути к архивам: + +```py +data_files = {"train": "SQuAD_it-train.json.gz", "test": "SQuAD_it-test.json.gz"} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +``` + +Это может быть полезно, если вы не хотите вручную разархивировать GZIP файлы. Автоматическое разархивирование также поддерживает распространенные форматы вроде ZIP и TAR, так что вы можете передавать и пути к таким файлам. + +Теперь, когда вы знаете, как загрузить локально хранящиеся файлы, мы посмотрим, как подгрузить данные с удаленных серверов. + +## Загрузка файлов с удаленного сервера + +Если вы работаете data scientist или программистом в компании, скорее всего ваши данные хранятся на сервере. К счастью, загрузка файлов с удаленных машин настолько же простая, насколько и загрузка их со локальной машины! Вместо пути к локальным файлам мы передаем аргументу `data_files` один или несколько URL, указывающих на нужные файлы. К примеру, датасет SQuAD-it расположен на GitHub, мы можем просто указать ссылку на файлы следующим образом: + +```py +url = "https://github.com/crux82/squad-it/raw/master/" +data_files = { + "train": url + "SQuAD_it-train.json.gz", + "test": url + "SQuAD_it-test.json.gz", +} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +``` + +Эта операция вернет такой же `DatasetDict`, какой мы получали ранее, но избавит нас от загрузки и разархивирования файлов _SQuAD_it-*.json.gz_ вручную. +На этом мы завершаем наш обзор различных способов загрузки датасетов, которые не размещены на Hugging Face Hub. Теперь, когда у нас есть датасет, с которым можно поиграться, давайте погрузимся в различные методы обработки данных! + + + +✏️ **Попробуйте!** Выберите другой датасет, расположенный на 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)). + + + + diff --git a/chapters/ru/chapter5/3.mdx b/chapters/ru/chapter5/3.mdx new file mode 100644 index 000000000..378b05e24 --- /dev/null +++ b/chapters/ru/chapter5/3.mdx @@ -0,0 +1,747 @@ +# Препарируем 🤗 Datasets + + + +В большинстве случаев данные, с которыми вы будете работать, не будут идеально подготовлены для обучения моделей. В этом разделе мы исследуем различные функции библиотеки 🤗 Datasets для подготовки данных. + + + +## Управление данными + +Как и в Pandas, 🤗 Datasets предоставляет несколько функция для управления содержимым объектов `Dataset` и `DatasetDict`. Мы уже познакомились с методом `Dataset.map()` в [главе 3](/course/ru/chapter3), а далее мы посмотрим на другие функции, имеющиеся в нашем распоряжении. + +Для этого примера мы будем использовать датасет [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-балльной шкале. + +Для начала необходимо скачать и разархивировать датасет, мы используем для этого команды `wget` и `unzip`: + +```py +!wget "https://archive.ics.uci.edu/ml/machine-learning-databases/00462/drugsCom_raw.zip" +!unzip drugsCom_raw.zip +``` + +Файл TSV - это просто разновидность CSV файла, содержащий табуляции вместо запятых в качестве разделителя, а значит мы можем его загрузить с помощью скрипта `csv` и аргумента `delimiter` через функцию `load_dataset()`: + +```py +from datasets import load_dataset + +data_files = {"train": "drugsComTrain_raw.tsv", "test": "drugsComTest_raw.tsv"} +# \t is the tab character in Python +drug_dataset = load_dataset("csv", data_files=data_files, delimiter="\t") +``` + +Хорошей практикой при исследовании данных является взятие небольшого случайного подмножества для понимания типов данных и их особенностей. В библиотеке 🤗 Datasets мы можем сделать случайную выборку путем последовательного вызова функций `Dataset.shuffle()` и `Dataset.select()`: + +```py +drug_sample = drug_dataset["train"].shuffle(seed=42).select(range(1000)) +# Peek at the first few examples +drug_sample[:3] +``` + +```python out +{'Unnamed: 0': [87571, 178045, 80482], + 'drugName': ['Naproxen', 'Duloxetine', 'Mobic'], + 'condition': ['Gout, Acute', 'ibromyalgia', 'Inflammatory Conditions'], + 'review': ['"like the previous person mention, I'm a strong believer of aleve, it works faster for my gout than the prescription meds I take. No more going to the doctor for refills.....Aleve works!"', + '"I have taken Cymbalta for about a year and a half for fibromyalgia pain. It is great\r\nas a pain reducer and an anti-depressant, however, the side effects outweighed \r\nany benefit I got from it. I had trouble with restlessness, being tired constantly,\r\ndizziness, dry mouth, numbness and tingling in my feet, and horrible sweating. I am\r\nbeing weaned off of it now. Went from 60 mg to 30mg and now to 15 mg. I will be\r\noff completely in about a week. The fibro pain is coming back, but I would rather deal with it than the side effects."', + '"I have been taking Mobic for over a year with no side effects other than an elevated blood pressure. I had severe knee and ankle pain which completely went away after taking Mobic. I attempted to stop the medication however pain returned after a few days."'], + 'rating': [9.0, 3.0, 10.0], + 'date': ['September 2, 2015', 'November 7, 2011', 'June 5, 2013'], + 'usefulCount': [36, 13, 128]} +``` + +Заметьте, что мы зафикисировали переменную `seed` для воспроизводимости результатов. `Dataset.select()` ожидает на вход итерируемый объект, содержащий индексы, поэтому мы передали `range(1000)` для взятия первых 1000 объектов перемешанного датасета. Для этой подвыборки мы можем сразу увидеть некоторые особенности в данных: + +* Колонка `Unnamed: 0` выглядит как обезличенный ID для каждого пациента. +* Колонка `condition` включает в себя смесь лейблов в нижнем и верхнем регистре. +* Отзывы переменной длины и содержат смесь разделителей текста (`\r\n`) и HTML-кодов (например, `&\#039;`). + +Давайте посмотрим, как мы можем использовать 🤗 Datasets для обработки этих особенностей. Чтобы проверить, что наша гипотеза об уникальности справедлива, мы можем использовать функцию `Dataset.unique()` для проверки, что число ID совпадает с числом строк в обоих датасетах (обучающем и тестовом): + +```py +for split in drug_dataset.keys(): + assert len(drug_dataset[split]) == len(drug_dataset[split].unique("Unnamed: 0")) +``` + +По всей видимости, наша гипотеза подтвердилась, так что перейдем к очистке датасета. Для начала переименуем `Unnamed: 0` во что-то более интерпретируемое. Мы можем использовать функцию `DatasetDict.rename_column()` для переименования столбцы на обоих сплитах (обучающем и тестовом): + +```py +drug_dataset = drug_dataset.rename_column( + original_column_name="Unnamed: 0", new_column_name="patient_id" +) +drug_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'], + num_rows: 161297 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'], + num_rows: 53766 + }) +}) +``` + + + +✏️ **Попробуйте!** Используйте функцию `Dataset.unique()` для поиска числа уникальных лекарств и состояний пациентов в обучающем и тестовом сплитах. + + + +Далее нормализуем все лейблы столбца `condition` с применением `Dataset.map()`. Так же, как мы делали токенизацию в [главе 3](/course/ru/chapter3), мы можем определить простую функцию, которая будет применения для всех строк каждого сплита в `drug_dataset`: + +```py +def lowercase_condition(example): + return {"condition": example["condition"].lower()} + + +drug_dataset.map(lowercase_condition) +``` + +```python out +AttributeError: 'NoneType' object has no attribute 'lower' +``` + +О нет! При запуске этой функции мы столкнулись с проблемой! Из ошибки мы можем сделать вывод, что некоторые записи в колонке `condition` являются `None`, которые не могут быть приведены к нижнему регистру как обычные строковые типы данных. Давайте удалим эти строки с помощью `Dataset.filter()`, которая работает схожим с `Dataset.map()` образом и принимает на вход один экземпляр датасета. Вместо реализации собственной функции: + +```py +def filter_nones(x): + return x["condition"] is not None +``` + +и вызова этой функции `drug_dataset.filter(filter_nones)`, мы можем сделать то же самое с помощью _lambda-функции_. В Python лямбда-функции - это небольшие функции, которые вы можете определить без явного их именования. Общий вид, которым их можно задать: + +``` +lambda : +``` + +где `lambda` - одно из [ключевых](https://docs.python.org/3/reference/lexical_analysis.html#keywords) слов Python, а `` - список или множество разделенных запятой значений, которые пойдут на вход функции, и `` задает операции, которые вы хотите применить к аргументам. Например, мы можем задать простую лямбда-функцию, которая возводит в квадрат числа: + +``` +lambda x : x * x +``` + +Чтобы применить эту функцию, мы должны заключить ее и аргументы в скобки: + +```py +(lambda x: x * x)(3) +``` + +```python out +9 +``` + +По аналогии мы можем задать лямбда-функцию с несколькими аргументами, которые необходимо разделить запятыми. Например, мы можем вычислить площадь треугольника следующим образом: + +```py +(lambda base, height: 0.5 * base * height)(4, 8) +``` + +```python out +16.0 +``` + +Лямбда-функции удобны, когда вы хотите определить маленькие одноразовые функции (для более подробной информации об этих функциях мы рекомендуем изучить превосходную публикацию [Real Python tutorial](https://realpython.com/python-lambda/) за авторством Andre Burgaud). В контексте библиотеки 🤗 Datasets мы можем использовать лямбда-функции для задания простых операций `map` и `filter`, давайте попробуем устранить `None`-записи из нашего датасета: + +```py +drug_dataset = drug_dataset.filter(lambda x: x["condition"] is not None) +``` + +После удаления `None` записей, мы можем нормализовать колонку `condition`: + +```py +drug_dataset = drug_dataset.map(lowercase_condition) +# Check that lowercasing worked +drug_dataset["train"]["condition"][:3] +``` + +```python out +['left ventricular dysfunction', 'adhd', 'birth control'] +``` + +Заработало! Сейчас мы очистили лейблы, давайте теперь посмотрим на то, как можно очистить непосредственно отзывы. + +## Создание новых столбцов + +Всякий раз, когда вы имеете дело с отзывами клиентов, хорошей практикой является проверка количества слов в каждом отзыве. Обзор может состоять всего из одного слова, например «Отлично!» или быть полномасштабным эссе с тысячами слов, и в зависимости от варианта использования вам нужно будет по-разному справляться с этими случаями. Чтобы вычислить количество слов в каждом обзоре, мы будем использовать грубую эвристику, основанную на разбиении каждого текста по пробелам. + +Зададим простую функцию, которая вычисляет число слов в каждом отзыве: + +```py +def compute_review_length(example): + return {"review_length": len(example["review"].split())} +``` + +В отличие от функции `lowercase_condition()`, `compute_review_length()` возвращает словарь, чьи ключи не соответствуют ни одному названию колонки в нашем датасете. В этом случае при исполнении `compute_review_length()` (переданного в `Dataset.map()`) функция будет применена ко всем строкам в датасете и создаст новый столбец с именем `review_length`: + +```py +drug_dataset = drug_dataset.map(compute_review_length) +# Посмотрим на первый объект обучающей части датасета +drug_dataset["train"][0] +``` + +```python out +{'patient_id': 206461, + 'drugName': 'Valsartan', + 'condition': 'left ventricular dysfunction', + 'review': '"It has no side effect, I take it in combination of Bystolic 5 Mg and Fish Oil"', + 'rating': 9.0, + 'date': 'May 20, 2012', + 'usefulCount': 27, + 'review_length': 17} +``` + +Как и ожадалось, мы видим колонку с именем `review_length`, которая добавлена к нашему обучающему датасету. Мы можем отсортировать по этой колонке наш датасет с помощью функции `Dataset.sort()` и посмотреть на «экстремальные» значения: + +```py +drug_dataset["train"].sort("review_length")[:3] +``` + +```python out +{'patient_id': [103488, 23627, 20558], + 'drugName': ['Loestrin 21 1 / 20', 'Chlorzoxazone', 'Nucynta'], + 'condition': ['birth control', 'muscle spasm', 'pain'], + 'review': ['"Excellent."', '"useless"', '"ok"'], + 'rating': [10.0, 1.0, 6.0], + 'date': ['November 4, 2008', 'March 24, 2017', 'August 20, 2016'], + 'usefulCount': [5, 2, 10], + 'review_length': [1, 1, 1]} +``` + +Как и ожидалось, некоторые отзывы содержат одно слово, хотя это и может быть допустимо для задачи оценки тональности текста, вряд ли будет полезно если мы хотим предсказывать состояние пациента. + + + +🙋 Альтернативный вариант добавления нового столбца в датасет – использовать функцию `Dataset.add_column()`. Она позволяет создать новый столбец из Python-списка или NumPy-массива, что может быть удобно, если функция `Dataset.map()` не очень подходит для вашего случая. + + + +Давайте применим функцию `Dataset.filter()` для удаления отзывов, содержащих меньше 30 слов. Схожим образом мы применяли её для столбца `condition`: мы можем отфильтровать отзывы, в которых число слов меньше порога: + +```py +drug_dataset = drug_dataset.filter(lambda x: x["review_length"] > 30) +print(drug_dataset.num_rows) +``` + +```python out +{'train': 138514, 'test': 46108} +``` + +Как вы можете увидеть, эта функция удалила около 15% отзывов из наших исходных обучающих и тестовых наборов данных. + + + +✏️ **Попробуйте!** Используйте функцию `Dataset.sort()` для проверки наиболее длинных отзывов. Изучите [документацию](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.sort) чтобы понять, какой аргумент нужно передать в функцию, чтобы сортировка произошла в убывающем порядке. + + + +Последняя вещь, которую нам необходимо сделать, это справиться с присутствием HTML-кодами символов в наших отзывах. Мы можем использовать модуль `html` и метод `unescape()` чтобы избавиться от них: + +```py +import html + +text = "I'm a transformer called BERT" +html.unescape(text) +``` + +```python out +"I'm a transformer called BERT" +``` + +Для этого будем использовать `Dataset.map()` на всем нашем корпусе текстов: + +```python +drug_dataset = drug_dataset.map(lambda x: {"review": html.unescape(x["review"])}) +``` + +Как видите, метод `Dataset.map()` крайне полезен для препроцессинга данных -- хотя мы и воспользовались только малой частью его возможностей! + +## Суперспособности метода `map()` + +Метод `Dataset.map()` принимает аргумент `batched`, который, если установлен в значение `True`, заставляет его сразу отправлять батч элементов в функцию `map()` (размер батча можно настроить, но по умолчанию он равен 1000). Например, предыдущая функция `map()`, которая экранировала весь HTML-код, требовала некоторого времени для запуска (вы можете узнать время взглянув на индикаторы выполнения процесса). Мы можем ускорить это, обрабатывая несколько элементов одновременно, используя list comprehension. + +Когда вы указываете `batched=True`, функция получает словарь с полями набора данных, но каждое значение теперь представляет собой _список значений_, а не просто одно значение. Возвращаемое значение `Dataset.map()` должно быть одинаковым: словарь с полями, которые мы хотим обновить или добавить в наш набор данных, и список значений. Например, вот еще один способ устранить все символы HTML, но с использованием `batched=True`: + +```python +new_drug_dataset = drug_dataset.map( + lambda x: {"review": [html.unescape(o) for o in x["review"]]}, batched=True +) +``` + +Если вы запустите этот код в блокноте, вы увидите, что эта команда выполняется намного быстрее, чем предыдущая. И это не потому, что наши отзывы уже были HTML-экранированными — если вы повторно выполните инструкцию из предыдущего раздела (без `batched=True`), это займет столько же времени, сколько и раньше. Это связано с тем, что обработка списков обычно выполняется быстрее, чем выполнение того же кода в цикле `for`, мы также повышаем производительность за счет одновременного доступа к множеству элементов, а не по одному. + +Использование `Dataset.map()` с `batched=True` – хороший способ «разблокировать» скоростные ограничения "быстрых" токенизаторов, с которыми мы познакомимся в [главе 6](/course/chapter6), которые могут быстро токенизировать большие списки текста. Например, чтобы токенизировать все отзывы на лекарства с помощью быстрого токенизатора, мы можем использовать функцию, подобную этой: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") + + +def tokenize_function(examples): + return tokenizer(examples["review"], truncation=True) +``` + +Как вы видели в [главе 3](/course/ru/chapter3), мы можем передать один или несколько элементов в токенизатор, так что мы можем использовать эту функцию без параметра `batched=True`. Давайте воспользуемся этой возможностью и сравним производительность. В ноутбуке можно замерить время выполнения функции путем добавления `%time` перед строкой кода, время исполнения которой вы хотите измерить: + +```python no-format +%time tokenized_dataset = drug_dataset.map(tokenize_function, batched=True) +``` + +Также присутствует возможность измерить время выполнения всей ячейки: нужно заменить `%time` на `%%time` в начале ячейки. На нашем оборудовании это заняло 10.8 секунд. Это значение расположено после слов "Wall time". + + + +✏️ **Попробуйте!** Выполните эту же инструкцию с и без параметра `batched=True`, затем попробуйте сделать это с "медленным" токенизатором (добавьте `use_fast=False` в метод `AutoTokenizer.from_pretrained()`) и посмотрите, какие значения вы получите на своем оборудовании. + + + +Вот результаты, которые мы получили без и с применением батчинга, и двумя разными по скорости токенизаторами: + +Options | Fast tokenizer | Slow tokenizer +:--------------:|:--------------:|:-------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s + +По результатам видно, что использование быстрого токенизатора с параметром `batched=True` приводит к ускорению выполнения в 30 раз – это потрясающе! Это главная причина, почему быстрые токенизаторы применяются по умолчанию при использовании класса `AutoTokenizer` (и почему они называются "быстрыми"). Возможность достичь такой скорости выполнения достигается засчет исполнения кода токенизаторов на языке Rust, который легко позволяет распараллелить выполнение кода. + +Параллелизация также позволяет почти в 6 раз ускорить быстрые токенизаторы с использованием `batched=True`: вы не можете пареллелизовать едничную операцию токенизации, но когда вы токенизируете много различных текстов одновременно, вы можете распределить выполнение на несколько процессов, каждый из которых будет отвечать за собственный текст. + +`Dataset.map()` также обладает возможностями параллелизации. Поскольку метод не реализован на Rust, он не позволят "медленному" токенизатору "догнать" быстрый, но все же может быть полезен (особенно если вы используете токенизатор, у которого нет быстрой версии). Чтобы включить многопроцессорность, используйте аргумент `num_proc` и укажите количество процессов, которые будут использоваться в вашем вызове `Dataset.map()`: + +```py +slow_tokenizer = AutoTokenizer.from_pretrained("bert-base-cased", use_fast=False) + + +def slow_tokenize_function(examples): + return slow_tokenizer(examples["review"], truncation=True) + + +tokenized_dataset = drug_dataset.map(slow_tokenize_function, batched=True, num_proc=8) +``` + +Вы можете поэкспериментировать и выяснить, какое число `num_proc` даст наилучший результат, в нашем случае значение 8 стало оптимальным. Вот значения, которые мы получили с и без использования мультипроцессинга: + +Options | Fast tokenizer | Slow tokenizer +:--------------:|:--------------:|:-------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s +`batched=True`, `num_proc=8` | 6.52s | 41.3s +`batched=False`, `num_proc=8` | 9.49s | 45.2s + +Это гораздо более разумные результаты для "медленного" токенизатора, но производительность быстрого токенизатора также существенно выросла. Однако, обратите внимание, что это не всегда так — для значений `num_proc`, отличных от 8, наши тесты показали, что быстрее использовать `batched=True` без этой опции. Как правило, мы не рекомендуем использовать мультипроцессинг Python для "быстрых" токенизаторов с параметром `batched=True`. + + + +Использование `num_proc` для ускорения обработки обычно отличная идея, но только в тех случаях, когда функция сама по себе не производит никакой параллелизации. + + + +Объединение всей этой функциональности во всего лишь один метод само по себе прекрасно, но это еще не все! Используя `Dataset.map()` и `batched=True` вы можете поменять число элементов в датасете. Это очень полезно во множестве ситуаций, например, когда вы хотите создать несколько обучающих признаков из одного экземпляра текста. Мы воспользуеся этой возможностью на этапе препроцессинга для нескольких NLP-задач, которые рассмотрим в [главе 7](/course/ru/chapter7) + + + +💡 В машинном обучении экземпляром (объектом, элементом выборки) является множество _признаков_, которые мы должны подать на вход модели. В некоторых контекстах это множество признаков будет множеством колонок в `Dataset`, а в других (как в текущем примере или в задачах ответов на вопросы) признаки будут софрмированы из одного столбца. + + + +Давайте посмотрим как это работает! В этом примере мы токенизируем наши тексты и обрежем их до максимальной длины в 128, однако мы попросим токенизатор вернуть нам *все* получившиеся токены, а не только начальные. Это может быть сделано с помощью параметра `return_overflowing_tokens=True`: + +```py +def tokenize_and_split(examples): + return tokenizer( + examples["review"], + truncation=True, + max_length=128, + return_overflowing_tokens=True, + ) +``` + +Давайте протестируем это на одном тексте прежде, чем использовать `Dataset.map()` на всем датасете: + +```py +result = tokenize_and_split(drug_dataset["train"][0]) +[len(inp) for inp in result["input_ids"]] +``` + +```python out +[128, 49] +``` + +Итак, наш первый текст в обучающей части выборки стал состоять из двух признаков, т.к. токенизатор токенизировал не только первые 128 элементов, но и оставшиеся 49 тоже. Давайте применим токенизатор ко всем элементам датасета! + +```py +tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) +``` + +```python out +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. + +Проблема заключается в том, что мы пытаемся смешать два разных датасета разной размерности: число колонок датасета `drug_dataset` равняется 1000, а нужный нам `tokenized_dataset` имеет 1463 колонки. Чтобы избежать этой ошибки, необходимо удалить несколько столбцов из старого датасета и сделать оба датасета одинакового размера. Мы можем достичь этого с помощью аргумента `remove_columns`: + +```py +tokenized_dataset = drug_dataset.map( + tokenize_and_split, batched=True, remove_columns=drug_dataset["train"].column_names +) +``` + +Теперь это работает без ошибок. Мы можем проверить, что наш новый датасет имеет бОльшее число элементов, просто сравним длины датасетов: + +```py +len(tokenized_dataset["train"]), len(drug_dataset["train"]) +``` + +```python out +(206772, 138514) +``` + +Мы упоминали о том, что мы можем справиться с несовпадением длин путем исправления числа колонок старого датасета. Чтобы сделать это, нам необходимо получить поле `overflow_to_sample_mapping`, которое вернет нам токенизатор, если мы зададим аргумент `return_overflowing_tokens=True`. Это даст нам необходимое соответствие между индексом новых и старых признаков. После этого мы сможем ассоциировать каждый ключ нашего оригинального датасета со списком значений нужного размера, повторяя значения каждого примера столько раз, сколько он генерирует новые функции: + +Для этого нам понадобится поле `overflow_to_sample_mapping`, которое возвращает токенизатор, когда мы устанавливаем `return_overflowing_tokens=True`. Это дает нам сопоставление индекса новой функции с индексом выборки, из которой он произошел. Используя это, мы можем связать каждый ключ, присутствующий в нашем исходном наборе данных, со списком значений нужного размера, повторяя значения каждого примера столько раз, сколько он генерирует новые функции: + +```py +def tokenize_and_split(examples): + result = tokenizer( + examples["review"], + truncation=True, + max_length=128, + return_overflowing_tokens=True, + ) + # Extract mapping between new and old indices + sample_map = result.pop("overflow_to_sample_mapping") + for key, values in examples.items(): + result[key] = [values[i] for i in sample_map] + return result +``` + +Мы можем убедиться, что это сработало в `Dataset.map()` и без удаления старых столбцов: + +```py +tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) +tokenized_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'], + num_rows: 206772 + }) + test: Dataset({ + features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'], + num_rows: 68876 + }) +}) +``` + +Мы получаем то же количество признаков, что и раньше, но здесь мы сохранили все старые поля. Если они вам нужны для некоторой постобработки после применения вашей модели, вы можете использовать этот подход. + +Теперь вы видели, как 🤗 Datasets можно использовать для предварительной обработки набора данных различными способами. Хотя функции обработки 🤗 Datasets покроют большую часть ваших потребностей в обучении модели, +могут быть случаи, когда вам нужно будет переключиться на Pandas, чтобы получить доступ к более мощным функциям, таким как `DataFrame.groupby()` или API высокого уровня для визуализации. К счастью, 🤗 Datasets предназначены для взаимодействия с такими библиотеками, как Pandas, NumPy, PyTorch, TensorFlow и JAX. Давайте посмотрим, как это работает. + +## От `Dataset`а к `DataFrame`ам и назад + + + +Для включения конвертации между различными библиотеками 🤗 Datasets предоставляет функцию `Dataset.set_format()`. Эта функция только изменяет _выходной формат_ датасета, так что вы можете переключиться на другой формат не изменяя саму _структуру данных_, которая остается Apache Arrow. Смена формата происходит in place. Для демонстрации давайте попробуем сконвертировать наш датасет в формат Pandas: + +```py +drug_dataset.set_format("pandas") +``` + +Теперь при обращении к элементам датасета мы будем получать `pandas.DataFrame` вместо словаря: + +```py +drug_dataset["train"][:3] +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
patient_iddrugNameconditionreviewratingdateusefulCountreview_length
095260Guanfacineadhd"My son is halfway through his fourth week of Intuniv..."8.0April 27, 2010192141
192703Lybrelbirth control"I used to take another oral contraceptive, which had 21 pill cycle, and was very happy- very light periods, max 5 days, no other side effects..."5.0December 14, 200917134
2138000Ortho Evrabirth control"This is my first time using any form of birth control..."8.0November 3, 20151089
+ +Давайте создадим `pandas.DataFrame` для всего обучающего множества, выбрав все элементы из `drug_dataset["train"]`: + +```py +train_df = drug_dataset["train"][:] +``` + + + +🚨 Внутри `Dataset.set_format()` изменяет формат, возвращаемый методом `__getitem__()`. Это означает, что когда мы хотим создать новый объект, например, `train_df`, из `Dataset`, формата `"pandas"`, мы должны сделать slice всего датасета и получить `pandas.DataFrame`. Вы можете проверить, что тип `drug_dataset["train"]` – формата `Dataset`, несмотря на выходной формат (который станет `pandas.DataFrame`). + + + +Начиная с этого момента мы можем использовать всю функциональность Pandas. Например, мы можем иначе посчитать расределение `condition` среди нашей выборки: + +```py +frequencies = ( + train_df["condition"] + .value_counts() + .to_frame() + .reset_index() + .rename(columns={"index": "condition", "condition": "frequency"}) +) +frequencies.head() +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
conditionfrequency
0birth control27655
1depression8023
2acne5209
3anxiety4991
4pain4744
+ + +И как только мы закончим наш анализ Pandas, мы всегда можем создать новый объект `Dataset` с помощью функции `Dataset.from_pandas()` следующим образом: + +```py +from datasets import Dataset + +freq_dataset = Dataset.from_pandas(frequencies) +freq_dataset +``` + +```python out +Dataset({ + features: ['condition', 'frequency'], + num_rows: 819 +}) +``` + + + +✏️ **Попробуйте!** Вычислите средний рейтинг по подному лекарству и сохраните результат в новом датасете типа `Dataset`. + + + +На этом мы заканчиваем наш обзор различных техник препроцессинга, доступных в 🤗 Datasets. Чтобы завершить этот раздел, давайте создадим валидационную часть выборки. Прежде, чем сделать это, мы сбросим формат `drug_dataset` обратно к `"arrow"`: + +```python +drug_dataset.reset_format() +``` + +## Создание валидационной выборки + +Хотя у нас есть тестовая часть датасета, которую мы могли бы использовать для оценки качества модели, хорошей практикой является оставить тестовое множество нетронутым и создать отдельный набор для проверки. Как только вы будете довольны производительностью своих моделей на валидационном датасете, вы можете выполнить окончательную проверку работоспособности на тестовом. Этот процесс помогает снизить риск переобучения модели и промышленного применения модели, которая не работает на реальных данных. + +🤗 Наборы данных предоставляют функцию `Dataset.train_test_split()`, основанную на известной функциональности из `scikit-learn`. Давайте используем её, чтобы разделить наш обучающий датасет непосредственно на обучающий и валидационный (мы устанавливаем аргумент `seed` для воспроизводимости): + +```py +drug_dataset_clean = drug_dataset["train"].train_test_split(train_size=0.8, seed=42) +# Переименуем "test" в "validation" +drug_dataset_clean["validation"] = drug_dataset_clean.pop("test") +# Добавим "test" в наш `DatasetDict` +drug_dataset_clean["test"] = drug_dataset["test"] +drug_dataset_clean +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 110811 + }) + validation: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 27703 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 46108 + }) +}) +``` + +Отлично, теперь мы подготовили датасет, на котором можно обучить некоторые модели. В [разделе 5](/course/ru/chapter5/5) мы покажем, как загрузить датасеты на Hugging Face Hub, а пока закончим наш обзор и посмотрим несколько способов сохранения датасетов на локальный компьютер. + +## Сохранение датасетов + + + +Несмотря на то, что 🤗 Datasets будут кэшировать все загруженные датасеты и операции, которые над ними выполняются, будут случаи, когда вам будет необходимо сохранить датасет на диск (например, если кэш был очищен). Как показано в таблице ниже, 🤗 Datasets предоставляет три главных функции для сохранения датасета в разных форматах. + +| Data format | Function | +| :---------: | :--------------------: | +| Arrow | `Dataset.save_to_disk()` | +| CSV | `Dataset.to_csv()` | +| JSON | `Dataset.to_json()` | + +Для примера сохраним наш очищенный датасет в формате Arrow: + +```py +drug_dataset_clean.save_to_disk("drug-reviews") +``` + +Эта функция создаст директорию следующей структуры: + +``` +drug-reviews/ +├── dataset_dict.json +├── test +│ ├── dataset.arrow +│ ├── dataset_info.json +│ └── state.json +├── train +│ ├── dataset.arrow +│ ├── dataset_info.json +│ ├── indices.arrow +│ └── state.json +└── validation + ├── dataset.arrow + ├── dataset_info.json + ├── indices.arrow + └── state.json +``` + +где мы можем увидеть каждый сплит данных, ассоциированный с собственной таблицей *dataset.arrow*, и некоторыми метаданными, хранящимися в файлах *dataset_info.json* и *state.json*. Вы можете рассматривать формат Arrow просто как таблицу, которая оптимизирована для построения высокопроизводительных приложений для обработки и передачи больших датасетов. + +После сохранения датасета мы можем загрузить его с использованием функции `load_from_disk()` следующим образом: + +```py +from datasets import load_from_disk + +drug_dataset_reloaded = load_from_disk("drug-reviews") +drug_dataset_reloaded +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 110811 + }) + validation: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 27703 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 46108 + }) +}) +``` + +Для форматов CSV и JSON мы должны сохранять каждый сплит как отдельный файл. Один из способов это сделать – проитерироваться по ключам и значениям в объекте `DatasetDict`: + +```py +for split, dataset in drug_dataset_clean.items(): + dataset.to_json(f"drug-reviews-{split}.jsonl") +``` + +Этот код сохранит каждый блок нашего датасета в формате [JSON Lines](https://jsonlines.org), где каждая строка будет сохранена как JSON-объект. Вот как будет выглядеть первый элемент нашей выборки: + +```py +!head -n 1 drug-reviews-train.jsonl +``` + +```python out +{"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-файлов: + +```py +data_files = { + "train": "drug-reviews-train.jsonl", + "validation": "drug-reviews-validation.jsonl", + "test": "drug-reviews-test.jsonl", +} +drug_dataset_reloaded = load_dataset("json", data_files=data_files) +``` + +Вот и все, что нужно для нашего экскурса при работе с 🤗 Datasets! Мы очистили датасет для обучения модели, вот некоторые идеи, которые вы могли бы реализовать самостоятельно: + +1. Примените знания из [раздела 3](/course/ru/chapter3) для обучения классификатора, который может предсказывать состояние пациента по отзыву на лекарство. +2. Используйте pipeline `summarization` из [раздела 1](/course/ru/chapter1)для генерации саммари отзывов. + +Далее мы посмотрим, как 🤗 Datasets могут помочь вам в работе с громадными датасетами, которые _невозможно_ обработать на вашем ноутбуке! + diff --git a/chapters/ru/chapter5/4.mdx b/chapters/ru/chapter5/4.mdx new file mode 100644 index 000000000..d96ca1b35 --- /dev/null +++ b/chapters/ru/chapter5/4.mdx @@ -0,0 +1,285 @@ +# Big data? 🤗 Datasets спешат на помощь! + + + +В настоящее время нередко приходится работать с многогигабайтными наборами данных, особенно если вы планируете предварительно обучить трансформер, такой как BERT или GPT-2, с нуля. В этих случаях даже _загрузка_ данных может стать проблемой. Например, корпус WebText, используемый для предобучения GPT-2, состоит из более чем 8 миллионов документов и 40 ГБ текста — загрузка этого в оперативную память вашего ноутбука может привести к сердечному приступу! + +К счастью, 🤗 Datasets спроектирована так, что позволит избежать таких ограничений. Библиотека избавляет вас от необходимости управлять памятью и рассматривает датасеты как [файлы, отображаемые в память](https://habr.com/ru/post/55716/) (memory-mapped files, MMF), также обходит ограничения жестких дисков путем формирования потоков записей из корпуса текстов. + + + +В этом разделе мы рассмотрим эти особенности 🤗 Datasets с огромным корпусом объемом 825 ГБ, известным как [Pile] (https://pile.eleuther.ai). Давайте начнем! + +## Что такое the Pile? + +The Pile — это корпус текстов на английском языке, созданный [EleutherAI] (https://www.eleuther.ai) для обучения крупномасштабных языковых моделей. Он включает в себя широкий спектр наборов данных, включая научные статьи, репозитории кода GitHub и отфильтрованный веб-текст. Учебный корпус доступен в виде [фрагментов по 14 ГБ] (https://mystic.the-eye.eu/public/AI/pile/), и вы также можете загрузить несколько [отдельных компонентов] (https://mystic .the-eye.eu/public/AI/pile_preliminary_components/). Начнем с набора данных PubMed Abstracts, который представляет собой свод аннотаций из 15 миллионов биомедицинских публикаций в [PubMed] (https://pubmed.ncbi.nlm.nih.gov/). Набор данных находится в [формате JSON Lines] (https://jsonlines.org) и сжат с использованием библиотеки `zstandard`, поэтому сначала нам нужно установить библиотеку `zstandart`: + +```py +!pip install zstandard +``` + +Затем мы можем загрузить набор данных, используя метод для подгрузки файлов, который мы изучили в [разделе 2](/course/ru/chapter5/2): + +```py +from datasets import load_dataset + +# Этой займет несколько минут, пока ожидаете – сделайте кофе или чай :) +data_files = "https://mystic.the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst" +pubmed_dataset = load_dataset("json", data_files=data_files, split="train") +pubmed_dataset +``` + +```python out +Dataset({ + features: ['meta', 'text'], + num_rows: 15518009 +}) +``` + +Мы видим, что в нашем наборе данных 15 518 009 строк и 2 столбца — это очень много! + + + +✎ По умолчанию 🤗 Datasets распаковывает файлы, необходимые для загрузки набора данных. Если вы хотите сохранить место на жестком диске, вы можете передать `DownloadConfig(delete_extracted=True)` в аргумент `download_config` функции `load_dataset()`. Дополнительные сведения см. в [документации](https://huggingface.co/docs/datasets/package_reference/builder_classes.html?#datasets.utils.DownloadConfig). + + + +Давайте посмотрим на содержимое первого экземпляра: + +```py +pubmed_dataset[0] +``` + +```python out +{'meta': {'pmid': 11409574, 'language': 'eng'}, + '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 ...'} +``` + +Отлично, выглядит как аннотация медицинской статьи. Теперь давайте посмотрим объем памяти, который мы использовали при загрузке данных: + +## Магия отображения в память + +Простой способ измерить использование памяти в Python — использовать библиотеку [`psutil`](https://psutil.readthedocs.io/en/latest/), которую можно установить с помощью `pip` следующим образом: + +```python +!pip install psutil +``` + +Она предоставляет класс Process, который позволяет нам проверить использование памяти текущим процессом следующим образом: + +```py +import psutil + +# Process.memory_info вовзращает объем в байтах, мы пересчитаем в мегабайты +print(f"RAM used: {psutil.Process().memory_info().rss / (1024 * 1024):.2f} MB") +``` + +```python out +RAM used: 5678.33 MB +``` + +Здесь атрибут `rss` относится к _резидентному размеру набора_, который представляет собой долю памяти, которую процесс занимает в ОЗУ. Это измерение также включает память, используемую интерпретатором Python и загруженными нами библиотеками, поэтому фактический объем памяти, используемый для загрузки набора данных, немного меньше. Для сравнения давайте посмотрим, насколько велик набор данных на диске, используя атрибут `dataset_size`. Поскольку результат, как и раньше, выражается в байтах, нам нужно вручную преобразовать его в гигабайты: + +```py +print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") +size_gb = pubmed_dataset.dataset_size / (1024**3) +print(f"Dataset size (cache file) : {size_gb:.2f} GB") +``` + +```python out +Number of files in dataset : 20979437051 +Dataset size (cache file) : 19.54 GB +``` + +Приятно — несмотря на то, что он весит почти 20 ГБ, мы можем загрузить и получить доступ к набору данных с гораздо меньшим объемом оперативной памяти! + + + +✏️ **Попробуйте!** Выберите один из [компонентов](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/) из Pile, который больше, чем оперативная память вашего ноутбука или настольного компьютера, загрузите его с 🤗 Datasets и измерьте объем используемой оперативной памяти. Обратите внимание, что для получения точных измерений вам потребуется сделать это в новом процессе. Вы можете найти распакованные размеры каждого компонента в Таблице 1 [документации Pile] (https://arxiv.org/abs/2101.00027). + + + +Если вы знакомы с Pandas, этот результат может стать неожиданностью из-за знаменитого [эмпирического правила] Уэса Кинни (https://wesmckinney.com/blog/apache-arrow-pandas-internals/), согласно которому вам обычно требуется 5 до 10 раз больше оперативной памяти, чем размер вашего набора данных. Так как же 🤗 Datasets решают эту проблему управления памятью? 🤗 Datasets рассматривают каждый набор данных как [файл с отображением в память] (https://en.wikipedia.org/wiki/Memory-mapped_file), который обеспечивает сопоставление между оперативной памятью и хранилищем файловой системы, что позволяет библиотеке получать доступ к элементам и работать с ними без необходимости полной загрузки его в память. + +Memory-mapped файлы также могут совместно использоваться несколькими процессами, что позволяет распараллеливать такие методы, как `Dataset.map()`, без необходимости перемещать или копировать набор данных. Под капотом все эти возможности реализованы в формате [Apache Arrow](https://arrow.apache.org) и [`pyarrow`](https://arrow.apache.org/docs/python/index. .html), которые делают загрузку и обработку данных молниеносной. (Для получения более подробной информации об Apache Arrow и сравнении с Pandas ознакомьтесь с [публикацией в блоге Деяна Симика] (https://towardsdatascience.com/apache-arrow-read-dataframe-with-zero-memory-69634092b1a). Чтобы увидеть это в действии давайте проведем небольшой тест скорости, перебирая все элементы в наборе данных PubMed Abstracts: + +```py +import timeit + +code_snippet = """batch_size = 1000 + +for idx in range(0, len(pubmed_dataset), batch_size): + _ = pubmed_dataset[idx:idx + batch_size] +""" + +time = timeit.timeit(stmt=code_snippet, number=1, globals=globals()) +print( + f"Iterated over {len(pubmed_dataset)} examples (about {size_gb:.1f} GB) in " + f"{time:.1f}s, i.e. {size_gb/time:.3f} GB/s" +) +``` + +```python out +'Iterated over 15518009 examples (about 19.5 GB) in 64.2s, i.e. 0.304 GB/s' +``` + +Здесь мы использовали модуль `timeit` Python для измерения времени выполнения `code_snippet`. Обычно вы сможете перебирать набор данных со скоростью от нескольких десятых долей ГБ/с до нескольких ГБ/с. Это прекрасно работает для подавляющего большинства приложений, но иногда вам придется работать с набором данных, который слишком велик даже для хранения на жестком диске вашего ноутбука. Например, если бы мы попытались загрузить весь Pile, нам потребовалось бы 825 ГБ свободного места на диске! Чтобы справиться с такими случаями 🤗 Datasets предоставляют функцию потоковой передачи, которая позволяет нам загружать и получать доступ к элементам на лету, без необходимости загружать весь набор данных. Давайте посмотрим, как это работает. + + + +💡 В Jupyter notebooks вы также можете измерить время исполнения ячейки с использованием [`%%timeit` magic function](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit). + + + +## Потоковая передача датасета + +Чтобы включить потоковую передачу набора данных, вам просто нужно передать аргумент `streaming=True` в функцию `load_dataset()`. Например, давайте снова загрузим набор данных PubMed Abstracts, но в потоковом режиме: + +```py +pubmed_dataset_streamed = load_dataset( + "json", data_files=data_files, split="train", streaming=True +) +``` + +Вместо знакомого `Dataset`, с которым мы уже встречались в других местах этой главы, объект, возвращаемый с `streaming=True`, является `IterableDataset`. Как следует из названия, чтобы получить доступ к элементам `IterableDataset`, нам нужно выполнить итерацию по нему. Мы можем получить доступ к первому элементу нашего набора потоковых данных следующим образом: + +```py +next(iter(pubmed_dataset_streamed)) +``` + +```python out +{'meta': {'pmid': 11409574, 'language': 'eng'}, + '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), с той лишь разницей, что выходные данные возвращаются один за другим: + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased") +tokenized_dataset = pubmed_dataset_streamed.map(lambda x: tokenizer(x["text"])) +next(iter(tokenized_dataset)) +``` + +```python out +{'input_ids': [101, 4958, 5178, 4328, 6779, ...], 'attention_mask': [1, 1, 1, 1, 1, ...]} +``` + + + +💡 Чтобы ускорить токенизацию с потоковой передачей, вы можете передать `batched=True`, как мы делали в последнем разделе. Он будет обрабатывать примеры батчами; размер батча по умолчанию составляет 1000 и может быть указан в аргументе `batch_size`. + + + +Вы также можете перемешать потоковые наборы данных, используя `IterableDataset.shuffle()`, но в отличие от `Dataset.shuffle()`, это только перемешивает элементы в предопределенном `buffer_size`: + +```py +shuffled_dataset = pubmed_dataset_streamed.shuffle(buffer_size=10_000, seed=42) +next(iter(shuffled_dataset)) +``` + +```python out +{'meta': {'pmid': 11410799, 'language': 'eng'}, + 'text': 'Randomized study of dose or schedule modification of granulocyte colony-stimulating factor in platinum-based chemotherapy for elderly patients with lung cancer ...'} +``` + +В этом примере мы выбрали случайный пример из первых 10 000 примеров в буфере. После обращения к примеру его место в буфере заполняется следующим примером в корпусе (т. е. 10 001-м примером в приведенном выше случае). Вы также можете выбирать элементы из потокового набора данных, используя функции `IterableDataset.take()` и `IterableDataset.skip()`, которые действуют аналогично `Dataset.select()`. Например, чтобы выбрать первые 5 примеров в наборе данных PubMed Abstracts, мы можем сделать следующее: + +```py +dataset_head = pubmed_dataset_streamed.take(5) +list(dataset_head) +``` + +```python out +[{'meta': {'pmid': 11409574, 'language': 'eng'}, + 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'}, + {'meta': {'pmid': 11409575, 'language': 'eng'}, + 'text': 'Clinical signs of hypoxaemia in children with acute lower respiratory infection: indicators of oxygen therapy ...'}, + {'meta': {'pmid': 11409576, 'language': 'eng'}, + 'text': "Hypoxaemia in children with severe pneumonia in Papua New Guinea ..."}, + {'meta': {'pmid': 11409577, 'language': 'eng'}, + 'text': 'Oxygen concentrators and cylinders ...'}, + {'meta': {'pmid': 11409578, 'language': 'eng'}, + 'text': 'Oxygen supply in rural africa: a personal experience ...'}] +``` + +Точно так же вы можете использовать функцию `IterableDataset.skip()` для создания обучающих и проверочных сплитов из перемешанного набора данных следующим образом: + +```py +# Пропустить первые 1000 объектов и включить остальные в обучающую выборку +train_dataset = shuffled_dataset.skip(1000) +# Взять первые 1000 объектов в валидационную выборку +validation_dataset = shuffled_dataset.take(1000) +``` + +Давайте завершим наше исследование потоковой передачи наборов данных общим приложением: объединение нескольких наборов данных вместе для создания единого корпуса. 🤗 Datasets предоставляют функцию `interleave_datasets()`, которая преобразует список объектов `IterableDataset` в один `IterableDataset`, где элементы нового набора данных получаются путем чередования исходных примеров. Эта функция особенно полезна, когда вы пытаетесь объединить большие наборы данных, поэтому в качестве примера давайте воспроизведем компонент FreeLaw из Pile, который представляет собой набор данных юридических заключений судов США объемом 51 ГБ: + +```py +law_dataset_streamed = load_dataset( + "json", + data_files="https://mystic.the-eye.eu/public/AI/pile_preliminary_components/FreeLaw_Opinions.jsonl.zst", + split="train", + streaming=True, +) +next(iter(law_dataset_streamed)) +``` + +```python out +{'meta': {'case_ID': '110921.json', + 'case_jurisdiction': 'scotus.tar.gz', + 'date_created': '2010-04-28T17:12:49Z'}, + 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'} +``` + +Этот набор данных достаточно велик, чтобы нагружать оперативную память большинства ноутбуков, но мы смогли загрузить его и получить к нему доступ! Давайте теперь объединим примеры из наборов данных FreeLaw и PubMed Abstracts с функцией `interleave_datasets()`: + +```py +from itertools import islice +from datasets import interleave_datasets + +combined_dataset = interleave_datasets([pubmed_dataset_streamed, law_dataset_streamed]) +list(islice(combined_dataset, 2)) +``` + +```python out +[{'meta': {'pmid': 11409574, 'language': 'eng'}, + 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'}, + {'meta': {'case_ID': '110921.json', + 'case_jurisdiction': 'scotus.tar.gz', + 'date_created': '2010-04-28T17:12:49Z'}, + 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'}] +``` + +Здесь мы использовали функцию `islice()` из модуля `itertools` Python, чтобы выбрать первые два объекта из объединенного набора данных, и мы видим, что они соответствуют первым примерам из каждого из двух исходных наборов данных. + +Наконец, если вы хотите получить в потоковом режиме весь Pile целиком (825 ГБ), вы можете получить все подготовленные файлы следующим образом: + +```py +base_url = "https://mystic.the-eye.eu/public/AI/pile/" +data_files = { + "train": [base_url + "train/" + f"{idx:02d}.jsonl.zst" for idx in range(30)], + "validation": base_url + "val.jsonl.zst", + "test": base_url + "test.jsonl.zst", +} +pile_dataset = load_dataset("json", data_files=data_files, streaming=True) +next(iter(pile_dataset["train"])) +``` + +```python out +{'meta': {'pile_set_name': 'Pile-CC'}, + 'text': 'It is done, and submitted. You can play “Survival of the Tastiest” on Android, and on the web...'} +``` + + + +✏️ **Попробуйте!** Используйте один из больших корпусов Common Crawl, например [`mc4`](https://huggingface.co/datasets/mc4) или [`oscar`](https://huggingface.co/ datasets/oscar) для создания потокового многоязычного набора данных, который представляет пропорции разговорных языков в стране по вашему выбору. Например, в Швейцарии есть четыре национальных языка: немецкий, французский, итальянский и рето-романский, поэтому вы можете попробовать создать швейцарский корпус, выбрав подмножества Оскаров в соответствии с их разговорной пропорцией. + + + +Теперь у вас есть все инструменты, необходимые для загрузки и обработки наборов данных всех форм и размеров, но, если только вам не повезет, в вашем путешествии по НЛП наступит момент, когда вам придется фактически создать собственный набор данных для решения проблемы. Это тема следующего раздела! diff --git a/chapters/ru/chapter5/6.mdx b/chapters/ru/chapter5/6.mdx new file mode 100644 index 000000000..b238ba8a9 --- /dev/null +++ b/chapters/ru/chapter5/6.mdx @@ -0,0 +1,526 @@ + + +# Семантический поиск с помощью FAISS + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +В [разделе 5](/course/ru/chapter5/5) мы создали набор данных о issues и комментариях GitHub из репозитория 🤗 Datasets. В этом разделе мы будем использовать эту информацию для создания поисковой системы, которая поможет нам найти ответы на самые насущные вопросы о библиотеке! + + + +## Использование эмбеддингов для семанического поиска + +Как мы видели в [Главе 1](/course/ru/chapter1), языковые модели на основе Transformer представляют каждую лексему в текстовом фрагменте как _эмбеддинг-вектор_. Оказывается, можно «объединить» отдельные вложения, чтобы создать векторное представление для целых предложений, абзацев или (в некоторых случаях) документов. Затем эти вложения можно использовать для поиска похожих документов в корпусе путем вычисления скалярного произведения (или какой-либо другой метрики сходства) между каждым вложением и возврата документов с наибольшим перекрытием. + +В этом разделе мы будем использовать вложения для разработки семантической поисковой системы. Эти поисковые системы предлагают несколько преимуществ по сравнению с традиционными подходами, основанными на сопоставлении ключевых слов в запросе с документами. + +
+Semantic search. + +
+ +## Загрузка и подготовка датасета + +Первое, что нам нужно сделать, это загрузить наш набор данных об issues GitHub, поэтому давайте воспользуемся библиотекой 🤗 Hub для получения URL-адреса, по которому наш файл хранится в Hugging Face Hub: + +```py +from huggingface_hub import hf_hub_url + +data_files = hf_hub_url( + repo_id="lewtun/github-issues", + filename="datasets-issues-with-comments.jsonl", + repo_type="dataset", +) +``` + +С URL-адресом, сохраненным в `data_files`, мы можем загрузить удаленный набор данных, используя метод, представленный в [раздел 2](/course/ru/chapter5/2): + +```py +from datasets import load_dataset + +issues_dataset = load_dataset("json", data_files=data_files, split="train") +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 2855 +}) +``` + +Здесь мы указали подвыборку `train` по умолчанию в `load_dataset()`, поэтому он возвращает `Dataset` вместо `DatasetDict`. Первым делом нужно отфильтровать запросы на pull-requests, поскольку они, как правило, редко используются для ответов на вопросы пользователей и создают шум в нашей поисковой системе. Как должно быть уже известно, мы можем использовать функцию `Dataset.filter()`, чтобы исключить эти строки из нашего набора данных. Давайте также отфильтруем строки без комментариев, поскольку они не дают ответов на запросы пользователей: + +```py +issues_dataset = issues_dataset.filter( + lambda x: (x["is_pull_request"] == False and len(x["comments"]) > 0) +) +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 771 +}) +``` + +Мы видим, что в нашем наборе данных много столбцов, большинство из которых нам не нужно для создания нашей поисковой системы. С точки зрения поиска наиболее информативными столбцами являются `title`, `body` и `comments`, а `html_url` содержит нам ссылку на исходную проблему. Давайте воспользуемся функцией `Dataset.remove_columns()`, чтобы удалить остальные столбцы: + +```py +columns = issues_dataset.column_names +columns_to_keep = ["title", "body", "html_url", "comments"] +columns_to_remove = set(columns_to_keep).symmetric_difference(columns) +issues_dataset = issues_dataset.remove_columns(columns_to_remove) +issues_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body'], + num_rows: 771 +}) +``` + +Чтобы создать наши эмбеддинги, мы дополним каждый комментарий заголовком и телом проблемы, поскольку эти поля часто содержат полезную контекстную информацию. Поскольку наш столбец `comments` в настоящее время представляет собой список комментариев для каждой проблемы, нам нужно «развернуть» столбец, чтобы каждая строка состояла из кортежа `(html_url, title, body, comment)`. В Pandas мы можем сделать это с помощью функции [`DataFrame.explode()`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html), которая создает новую строку для каждого элемента в столбце, похожем на список, при копировании всех других значений столбца. Чтобы увидеть это в действии, давайте сначала переключимся на формат Pandas `DataFrame`: + +```py +issues_dataset.set_format("pandas") +df = issues_dataset[:] +``` + +Если мы проверим первую строку в этом `DataFrame`, мы увидим, что есть четыре комментария, связанных с этой проблемой: + +```py +df["comments"][0].tolist() +``` + +```python out +['the bug code locate in :\r\n if data_args.task_name is not None:\r\n # Downloading and loading a dataset from the hub.\r\n datasets = load_dataset("glue", data_args.task_name, cache_dir=model_args.cache_dir)', + 'Hi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com\r\n\r\nNormally, it should work if you wait a little and then retry.\r\n\r\nCould you please confirm if the problem persists?', + 'cannot connect,even by Web browser,please check that there is some problems。', + 'I can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...'] +``` + +Когда мы «развернем» `df`, мы ожидаем получить по одной строке для каждого из этих комментариев. Проверим, так ли это: + +```py +comments_df = df.explode("comments", ignore_index=True) +comments_df.head(4) +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
html_urltitlecommentsbody
0https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comthe bug code locate in :\r\n if data_args.task_name is not None...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
1https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comHi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
2https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comcannot connect,even by Web browser,please check that there is some problems。Hello,\r\nI am trying to run run_glue.py and it gives me this error...
3https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comI can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
+ +Отлично, мы видим, что строки были скопированы, а столбец `comments` содержит отдельные комментарии! Теперь, когда мы закончили с Pandas, мы можем быстро вернуться к `Dataset`, загрузив `DataFrame` в память: + +```py +from datasets import Dataset + +comments_dataset = Dataset.from_pandas(comments_df) +comments_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body'], + num_rows: 2842 +}) +``` + +Хорошо, это дало нам несколько тысяч комментариев для работы! + + + + +✏️ **Попробуйте!** Посмотрите, сможете ли вы использовать `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, полезным для этой задачи. + + + +Теперь, когда у нас есть один комментарий в строке, давайте создадим новый столбец `comments_length`, содержащий количество слов в комментарии: + +```py +comments_dataset = comments_dataset.map( + lambda x: {"comment_length": len(x["comments"].split())} +) +``` + +Мы можем использовать этот новый столбец для фильтрации коротких комментариев, которые обычно содержат такие слова, как «cc @lewtun» или Thanks!», которые не имеют отношения к нашей поисковой системе. Нет точного числа для выбора порога числа слов, но около 15 слов кажется хорошим началом: + +```py +comments_dataset = comments_dataset.filter(lambda x: x["comment_length"] > 15) +comments_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body', 'comment_length'], + num_rows: 2098 +}) +``` + +Немного очистив наш набор данных, давайте соединим название issue, описание и комментарии вместе в новом столбце `text`. Как обычно, мы напишем простую функцию, которую мы можем передать в `Dataset.map()`: + +```py +def concatenate_text(examples): + return { + "text": examples["title"] + + " \n " + + examples["body"] + + " \n " + + examples["comments"] + } + + +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` имеет лучшую производительность для семантического поиска, поэтому мы будем использовать её для нашего приложения. Мы также загрузим токенизатор, используя ту же контрольную точку: + +{#if fw === 'pt'} + +```py +from transformers import AutoTokenizer, AutoModel + +model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1" +tokenizer = AutoTokenizer.from_pretrained(model_ckpt) +model = AutoModel.from_pretrained(model_ckpt) +``` +Чтобы ускорить процесс построения эмбеддингов, рекомендуется переместить модель и входные данные на устройстве с графическим процессором, поэтому давайте сделаем это сейчас: + +```py +import torch + +device = torch.device("cuda") +model.to(device) +``` + +{:else} + +```py +from transformers import AutoTokenizer, TFAutoModel + +model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1" +tokenizer = AutoTokenizer.from_pretrained(model_ckpt) +model = TFAutoModel.from_pretrained(model_ckpt, from_pt=True) +``` + +Обратите внимание, что мы установили `from_pt=True` в качестве аргумента метода `from_pretrained()`. Это связано с тем, что контрольная точка `multi-qa-mpnet-base-dot-v1` имеет только веса PyTorch, поэтому установка `from_pt=True` автоматически преобразует их в формат TensorFlow для нас. Как видите, переключаться между фреймворками в 🤗 Трансформеры очень просто! + +{/if} + +Как мы упоминали ранее, мы хотели бы представить каждую запись в нашем корпусе issues GitHub как единый вектор, поэтому нам нужно каким-то образом «объединить» или усреднить наши вложения токенов. Одним из популярных подходов является выполнение *CLS pooling* выходных данных нашей модели, когда мы просто собираем последнее скрытое состояние для специального токена `[CLS]`. Следующая функция поможет нам: + +```py +def cls_pooling(model_output): + return model_output.last_hidden_state[:, 0] +``` + +Далее мы создадим вспомогательную функцию, которая разметит список документов, поместит тензоры в GPU, передаст их в модель и, наконец, применит CLS pooling к выходным данным: + +{#if fw === 'pt'} + +```py +def get_embeddings(text_list): + encoded_input = tokenizer( + text_list, padding=True, truncation=True, return_tensors="pt" + ) + encoded_input = {k: v.to(device) for k, v in encoded_input.items()} + model_output = model(**encoded_input) + return cls_pooling(model_output) +``` +Мы можем проверить работу функции, передав ей первую текстовую запись в нашем корпусе и проверив размерности данных на выходе: + +```py +embedding = get_embeddings(comments_dataset["text"][0]) +embedding.shape +``` + +```python out +torch.Size([1, 768]) +``` + +Отлично, мы преобразовали первую запись в нашем корпусе в 768-мерный вектор! Мы можем использовать `Dataset.map()`, чтобы применить нашу функцию `get_embeddings()` к каждой строке в нашем корпусе, поэтому давайте создадим новый столбец `embeddings` следующим образом: + +```py +embeddings_dataset = comments_dataset.map( + lambda x: {"embeddings": get_embeddings(x["text"]).detach().cpu().numpy()[0]} +) +``` + +{:else} + +```py +def get_embeddings(text_list): + encoded_input = tokenizer( + text_list, padding=True, truncation=True, return_tensors="tf" + ) + encoded_input = {k: v for k, v in encoded_input.items()} + model_output = model(**encoded_input) + return cls_pooling(model_output) +``` +Мы можем проверить работу функции, передав ей первую текстовую запись в нашем корпусе и проверив размерности данных на выходе: + +```py +embedding = get_embeddings(comments_dataset["text"][0]) +embedding.shape +``` + +```python out +TensorShape([1, 768]) +``` +Отлично, мы преобразовали первую запись в нашем корпусе в 768-мерный вектор! Мы можем использовать `Dataset.map()`, чтобы применить нашу функцию `get_embeddings()` к каждой строке в нашем корпусе, поэтому давайте создадим новый столбец `embeddings` следующим образом: + +```py +embeddings_dataset = comments_dataset.map( + lambda x: {"embeddings": get_embeddings(x["text"]).numpy()[0]} +) +``` + +{/if} + +Обратите внимание, что мы преобразовали вложения в массивы NumPy — это потому, что 🤗 Datasets требуют этого формата, когда мы пытаемся проиндексировать их с помощью FAISS, что мы сделаем дальше. + + +## Использование FAISS для эффективного семантического поиска + +Теперь, когда у нас есть датасет с эмбеддингами, нам нужен способ поиска по ним. Для этого мы будем использовать специальную структуру данных из 🤗 Datasets, называемую _FAISS index_. [FAISS](https://faiss.ai/) (сокращение от Facebook AI Similarity Search) — это библиотека, предоставляющая эффективные алгоритмы для быстрого поиска и кластеризации эмбеддингов. + +Основная идея FAISS состоит в том, чтобы создать специальную структуру данных, называемую _index_, которая позволяет найти, какие эмбеддинги подобны входным эмбеддингам. Создать индекс FAISS в 🤗 Datasets очень просто — мы используем функцию `Dataset.add_faiss_index()` и указываем, какой столбец нашего набора данных мы хотим проиндексировать: + +```py +embeddings_dataset.add_faiss_index(column="embeddings") +``` + +Теперь мы можем выполнять запросы к этому индексу, выполняя поиск ближайшего соседа с помощью функции `Dataset.get_nearest_examples()`. Давайте проверим это, сначала внедрив вопрос следующим образом: + +{#if fw === 'pt'} + +```py +question = "How can I load a dataset offline?" +question_embedding = get_embeddings([question]).cpu().detach().numpy() +question_embedding.shape +``` + +```python out +torch.Size([1, 768]) +``` + +{:else} + +```py +question = "How can I load a dataset offline?" +question_embedding = get_embeddings([question]).numpy() +question_embedding.shape +``` + +```python out +(1, 768) +``` + +{/if} + +Как и в случае с документами, теперь у нас есть 768-мерный вектор, представляющий запрос, который мы можем сравнить со всем корпусом, чтобы найти наиболее похожие объекты: + +```py +scores, samples = embeddings_dataset.get_nearest_examples( + "embeddings", question_embedding, k=5 +) +``` + +Функция `Dataset.get_nearest_examples()` возвращает набор оценок, которые ранжируют совпадение между запросом и документом, и соответствующий набор образцов (здесь 5 лучших совпадений). Давайте соберем их в `pandas.DataFrame`, чтобы мы могли легко их отсортировать: + +```py +import pandas as pd + +samples_df = pd.DataFrame.from_dict(samples) +samples_df["scores"] = scores +samples_df.sort_values("scores", ascending=False, inplace=True) +``` + +Теперь мы можем пройтись по первым нескольким строкам, чтобы увидеть, насколько наш запрос соответствует имеющимся комментариям: + +```py +for _, row in samples_df.iterrows(): + print(f"COMMENT: {row.comments}") + print(f"SCORE: {row.scores}") + print(f"TITLE: {row.title}") + print(f"URL: {row.html_url}") + print("=" * 50) + print() +``` + +```python out +""" +COMMENT: Requiring online connection is a deal breaker in some cases unfortunately so it'd be great if offline mode is added similar to how `transformers` loads models offline fine. + +@mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like? +SCORE: 25.505046844482422 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: The local dataset builders (csv, text , json and pandas) are now part of the `datasets` package since #1726 :) +You can now use them offline +\`\`\`python +datasets = load_dataset("text", data_files=data_files) +\`\`\` + +We'll do a new release soon +SCORE: 24.555509567260742 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: I opened a PR that allows to reload modules that have already been loaded once even if there's no internet. + +Let me know if you know other ways that can make the offline mode experience better. I'd be happy to add them :) + +I already note the "freeze" modules option, to prevent local modules updates. It would be a cool feature. + +---------- + +> @mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like? + +Indeed `load_dataset` allows to load remote dataset script (squad, glue, etc.) but also you own local ones. +For example if you have a dataset script at `./my_dataset/my_dataset.py` then you can do +\`\`\`python +load_dataset("./my_dataset") +\`\`\` +and the dataset script will generate your dataset once and for all. + +---------- + +About I'm looking into having `csv`, `json`, `text`, `pandas` dataset builders already included in the `datasets` package, so that they are available offline by default, as opposed to the other datasets that require the script to be downloaded. +cf #1724 +SCORE: 24.14896583557129 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: > here is my way to load a dataset offline, but it **requires** an online machine +> +> 1. (online machine) +> +> ``` +> +> import datasets +> +> data = datasets.load_dataset(...) +> +> data.save_to_disk(/YOUR/DATASET/DIR) +> +> ``` +> +> 2. copy the dir from online to the offline machine +> +> 3. (offline machine) +> +> ``` +> +> import datasets +> +> data = datasets.load_from_disk(/SAVED/DATA/DIR) +> +> ``` +> +> +> +> HTH. + + +SCORE: 22.893993377685547 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: here is my way to load a dataset offline, but it **requires** an online machine +1. (online machine) +\`\`\` +import datasets +data = datasets.load_dataset(...) +data.save_to_disk(/YOUR/DATASET/DIR) +\`\`\` +2. copy the dir from online to the offline machine +3. (offline machine) +\`\`\` +import datasets +data = datasets.load_from_disk(/SAVED/DATA/DIR) +\`\`\` + +HTH. +SCORE: 22.406635284423828 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== +""" +``` + +Неплохо! Наше второе обращение, кажется, соответствует запросу. + + + +✏️ **Попробуйте!** Создайте свой собственный запрос и посмотрите, сможете ли вы найти ответ в найденных документах. Возможно, вам придется увеличить параметр `k` в `Dataset.get_nearest_examples()`, чтобы расширить поиск. + + \ No newline at end of file diff --git a/chapters/ru/chapter5/7.mdx b/chapters/ru/chapter5/7.mdx new file mode 100644 index 000000000..d40c7f871 --- /dev/null +++ b/chapters/ru/chapter5/7.mdx @@ -0,0 +1,16 @@ +# 🤗 Datasets, итоги! + + + +Что ж, это было настоящее путешествие по библиотеке 🤗 Datasets — поздравляем, вы зашли так далеко! Со знаниями, которые вы получили из этой главы, вы сможете: + +- Загружать наборы данных из любого места, будь то Hugging Face Hub, ваш ноутбук или удаленный сервер в вашей компании. +- Обрабатывать свои данные, используя сочетание функций `Dataset.map()` и `Dataset.filter()`. +- Быстро переключаться между форматами данных, такими как Pandas и NumPy, с помощью `Dataset.set_format()`. +- Создавать свой собственный набор данных и отправлять его в Hugging Face Hub. +- Строить свои эмбеддинги документов с помощью модели Transformer и создавать семантический поисковик с помощью FAISS. + +В [Главе 7](/course/ru/chapter7) мы будем использовать все это с пользой, поскольку мы углубимся в основные задачи NLP, для которых отлично подходят модели Transformer. Однако, прежде чем идти вперед, проверьте свои знания о 🤗 Datasets с помощью быстрого теста! diff --git a/chapters/ru/chapter5/8.mdx b/chapters/ru/chapter5/8.mdx new file mode 100644 index 000000000..717a1b959 --- /dev/null +++ b/chapters/ru/chapter5/8.mdx @@ -0,0 +1,230 @@ + + +# Тест по главе 5 + + + +Эта глава охватила много вопросов! Не волнуйтесь, если вы не поняли всех деталей; следующие главы помогут вам понять, как все работает внутри. + +Однако, прежде чем двигаться дальше, давайте проверим то, что вы узнали в этой главе. +### Из каких источников функция `load_dataset()` в 🤗 Datasets позволяет загружать наборы данных? + +data_files функции load_dataset() для загрузки локальных наборов данных.", + correct: true + }, + { + text: "Hugging Face Hub", + explain: "Правильно! Вы можете загружать наборы данных в Hub, указав идентификатор набора данных, например. load_dataset('emotion').", + correct: true + }, + { + text: "Удаленный сервер", + explain: "Правильно! Вы можете передать URLs в аргумент data_files фунции load_dataset(). ", + correct: true + }, + ]} +/> + +### 2. Предположим, вы загружаете одну из задач GLUE следующим образом: + +```py +from datasets import load_dataset + +dataset = load_dataset("glue", "mrpc", split="train") +``` + +Какая из следующих команд создаст случайную выборку из 50 элементов из `dataset`? + +dataset.sample(50)", + explain: "Это неверно — нет метода Dataset.sample()." + }, + { + text: "dataset.shuffle().select(range(50))", + explain: "Правильный! Как вы видели в этой главе, вы сначала перемешиваете набор данных, а затем выбираете из него подмножества.", + correct: true + }, + { + text: "dataset.select(range(50)).shuffle()", + explain: "Это неверно — хотя код запустится, он перемешает только первые 50 элементов в наборе данных." + } + ]} +/> + +### 3. Предположим, у вас есть набор данных о домашних питомцах под названием `pets_dataset`, в котором есть столбец `name`, обозначающий имя каждого питомца. Какой из следующих подходов позволит вам отфильтровать набор данных для всех домашних животных, имена которых начинаются с буквы «L»? + +pets_dataset.filter(lambda x : x['name'].startswith('L'))", + explain: "Правильно! Использование лямбда-функции Python для этих быстрых фильтров — отличная идея. Можете ли вы придумать другое решение?", + correct: true + }, + { + text: "pets_dataset.filter(lambda x['name'].startswith('L'))", + explain: "Это неверно — лямбда-функция принимает общую форму lambda *arguments* : *expression*, поэтому в этом случае вам необходимо предоставить аргументы." + }, + { + text: "Create a function like def filter_names(x): return x['name'].startswith('L') and run pets_dataset.filter(filter_names).", + explain: "Правильно! Как и в случае с Dataset.map(), вы можете передавать явные функции в Dataset.filter(). Это полезно, когда у вас есть сложная логика, которая не подходит для короткой лямбда-функции. Какое из других решений будет работать?", + correct: true + } + ]} +/> + +### 4. Что такое отображение в память? + + + +### 5. Что из перечисленного ниже является основным преимуществом отображения памяти? + + + +### 6. Почему следующий код не работает? + +```py +from datasets import load_dataset + +dataset = load_dataset("allocine", streaming=True, split="train") +dataset[0] +``` + +IterableDataset.", + explain: "Правильно! IterableDataset — это генератор, а не контейнер, поэтому вы должны получить доступ к его элементам, используя next(iter(dataset)).", + correct: true + }, + { + text: "Набор данных allocine не имеет разделения train.", + explain: "Это неверно — проверьте [allocine карточку набора данных](https://huggingface.co/datasets/allocine) в Hub, чтобы увидеть, какие разбиения он содержит." + } + ]} +/> + +### 7. Что из перечисленного является основными преимуществами создания карточки датасета? + + + + +### 8. Что такое семантический поиск? + + + +### 9. Для асимметричного семантического поиска можно использовать: + + + +### 10. Могу ли я использовать 🤗 Datasets для загрузки данных и решения задач в других областях, например для обработки речи? + +набором данных MNIST в Hub для примера компьютерного зрения." + }, + { + text: "Да", + explain: "Правильно! Ознакомьтесь с захватывающими разработками в области речи и зрения в библиотеке 🤗 Transformers, чтобы узнать, как 🤗 Datasets используются в этих областях.", + correct : true + }, + ]} +/> diff --git a/chapters/ru/chapter6/1.mdx b/chapters/ru/chapter6/1.mdx new file mode 100644 index 000000000..50a260ae0 --- /dev/null +++ b/chapters/ru/chapter6/1.mdx @@ -0,0 +1,19 @@ +# Введение + + + +В [главе 3](/course/ru/chapter3), мы рассмотрели, как настроить модель под конкретную задачу. Когда мы это делаем, мы используем тот же токенизатор, с помощью которого была предварительно обучена модель, но что нам делать, когда мы хотим обучить модель с нуля? В этих случаях использование токенизатора, предварительно обученного на корпусе из другого домена или языка, обычно неоптимально. Например, токенизатор, обученный на английском корпусе, будет плохо работать с корпусом японских текстов, поскольку использование пробелов и пунктуации в этих двух языках сильно различается. + +В этой главе вы узнаете, как обучить совершенно новый токенизатор на корпусе текстов, чтобы затем его можно было использовать для предобучения языковой модели. Все это будет сделано с помощью библиотеки [🤗 Tokenizers](https://github.com/huggingface/tokenizers), которая предоставляет «быстрые» токенизаторы в [🤗 Transformers](https://github.com/huggingface/transformers). Мы внимательно рассмотрим функции, предоставляемые этой библиотекой, и выясним, чем быстрые токенизаторы отличаются от «медленных» версий. + +Темы, которые мы рассмотрим: + +* Как обучить новый токенизатор, аналогичный тому, который используется конкретной моделью, на новом корпусе текстов +* Особенности быстрых токенизаторов +* Различия между тремя основными алгоритмами токенизации составных частей слов, используемыми сегодня в NLP. +* Как создать токенизатор с нуля с помощью библиотеки 🤗 Tokenizers и обучить его на собственных данных + +Методы, представленные в этой главе, подготовят вас к разделу [главы 7](/course/ru/chapter7/6), где мы рассмотрим создание языковой модели для исходного кода Python. Давайте начнем с рассмотрения того, что значит «обучить» токенизатор. \ No newline at end of file diff --git a/chapters/ru/chapter6/2.mdx b/chapters/ru/chapter6/2.mdx new file mode 100644 index 000000000..08c77aac3 --- /dev/null +++ b/chapters/ru/chapter6/2.mdx @@ -0,0 +1,257 @@ +# Обучение нового токенизатора на основе существующего + + + +Если языковая модель недоступна на интересующем вас языке или если ваш корпус сильно отличается от того, на котором обучалась ваша языковая модель, вы, скорее всего, захотите переобучить модель с нуля, используя токенизатор, адаптированный к вашим данным. Это потребует обучения нового токенизатора на вашем наборе данных. Но что именно это означает? Когда мы впервые рассмотрели токенизаторы в [Главе 2](/course/chapter2), мы увидели, что большинство моделей Transformer используют _алгоритм токенизации составных частей слов_ (_subword tokenization algorithm_). Чтобы определить, какие подслова представляют интерес и чаще всего встречаются в имеющемся корпусе, токенизатору необходимо внимательно изучить все тексты в корпусе — процесс, который мы называем «обучением». Точные правила, управляющие этим обучением, зависят от типа используемого токенизатора, и мы рассмотрим три основных алгоритма позже в этой главе. + + + + + +⚠️ Обучение токенизатора — это не то же самое, что обучение модели! Обучение модели использует стохастический градиентный спуск, чтобы уменьшить значение функции потерь для каждого батча данных. Он рандомизирован по своей природе (это означает, что вы должны зафиксировать несколько начальных значений, чтобы получить одинаковые результаты при выполнении одной и той же тренировки дважды). Обучение токенизатора — это статистический процесс, который пытается определить, какие подслова лучше всего выбирать для данного корпуса, а точные правила, используемые для их выбора, зависят от алгоритма токенизации. Он детерминирован, то есть вы всегда получаете одни и те же результаты при обучении с одним и тем же алгоритмом на одном и том же корпусе. + + + +## Сбор корпуса слов + +В 🤗 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 этого набора данных: + +```py +from datasets import load_dataset + +# Это может занять некоторое время – заварите себе чаю! +raw_datasets = load_dataset("code_search_net", "python") +``` + +Мы можем взглянуть на обучающий сплит данных, чтобы увидеть, к каким столбцам у нас есть доступ: + +```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 +}) +``` +Мы видим, что набор данных отделяет строки документации от кода и предлагает токенизацию обоих. Здесь мы просто будем использовать столбец `whole_func_string` для обучения нашего токенизатора. Мы можем посмотреть на пример одной из этих функций, проиндексировав раздел `train`: + +```py +print(raw_datasets["train"][123456]["whole_func_string"]) +``` + +должно быть распечатано следующее: + +```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) +``` + +Первое, что нам нужно сделать, это преобразовать набор данных в _итератор_ списков текстов, например, в список списков текстов. Использование списков текстов позволит нашему токенизатору работать быстрее (обучение на пакетах текстов вместо обработки отдельных текстов по одному), и он должен быть итерируемым объектом, если мы хотим избежать хранения всего набора данных в памяти. Если ваш корпус огромен, вы захотите воспользоваться тем фактом, что 🤗 Datasets не загружают все в оперативную память, а сохраняют элементы набора данных на диске. + +Следующее действие создаст список списков по 1000 текстов в каждом, но загрузит все в память: + +```py +# Если ваш датасет маленький – оставьте эту строку закомментированной! +# training_corpus = [raw_datasets["train"][i: i + 1000]["whole_func_string"] for i in range(0, len(raw_datasets["train"]), 1000)] +``` + +Используя генератор Python, мы можем избежать загрузки Python чего-либо в память до тех пор, пока это действительно необходимо. Чтобы создать такой генератор, вам нужно всего лишь заменить квадратные скобки круглыми: + +```py +training_corpus = ( + raw_datasets["train"][i : i + 1000]["whole_func_string"] + for i in range(0, len(raw_datasets["train"]), 1000) +) +``` + +Эта строка кода не извлекает никаких элементов набора данных; он просто создает объект, который вы можете использовать в цикле for Python. Тексты будут загружаться только тогда, когда они вам нужны (то есть, когда вы находитесь на этапе цикла `for`, который их требует), и за один раз будет загружено только 1000 текстов. Таким образом, вы не исчерпаете всю свою память, даже если обрабатываете огромный набор данных. + +Проблема с объектом-генератором заключается в том, что его можно использовать только один раз. Итак, вместо того, чтобы дважды давать нам список первых 10 цифр: + +```py +gen = (i for i in range(10)) +print(list(gen)) +print(list(gen)) +``` + +мы получим их только один раз, дальше список станет пустым: + +```python out +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +[] +``` + +Вот почему мы определяем функцию, которая вместо этого возвращает генератор: + +```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() +``` + +Вы также можете определить свой генератор внутри цикла `for`, используя оператор `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"] +``` + +который будет производить точно такой же генератор, как и раньше, но позволяет вам использовать более сложную логику, чем в обычном list comprehension. + +## Обучение нового токенизатора + +Теперь, когда у нас есть корпус в виде итератора пакетов текстов, мы готовы обучить новый токенизатор. Для этого нам сначала нужно загрузить токенизатор, который мы хотим связать с нашей моделью (здесь, GPT-2): + +```py +from transformers import AutoTokenizer + +old_tokenizer = AutoTokenizer.from_pretrained("gpt2") +``` + +Несмотря на то, что мы собираемся обучить новый токенизатор, мы используем конкретный алгоритм (который был использован в GPT-2). Таким образом, нам не нужно будет указывать что-либо об алгоритме токенизации или специальных токенах, которые мы хотим использовать; наш новый токенизатор будет точно таким же, как GPT-2, и единственное, что изменится, — это словарный запас, который будет определен обучением на нашем корпусе. + +Сначала давайте посмотрим, как этот токенизатор будет обрабатывать пример функции: + +```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'] +``` + +Этот токенизатор имеет несколько специальных символов, таких как `Ġ` и `Ċ`, которые обозначают пробелы и символы новой строки соответственно. Как мы видим, это не слишком эффективно: токенизатор возвращает отдельные токены для каждого пробела, в то время как он мог бы сгруппировать уровни отступа (поскольку наборы из четырех или восьми пробелов будут очень распространены в коде). Он также немного странно разделял имя функции из-за используемого символа `_`. + +Давайте обучим новый токенизатор и посмотрим, решит ли он эти проблемы. Для этого воспользуемся методом `train_new_from_iterator()`: + +```py +tokenizer = old_tokenizer.train_new_from_iterator(training_corpus, 52000) +``` + +Этот процесс может занять некоторое время, если ваш корпус очень большой, но для этого набора данных из 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 для графических процессоров. + +Обучение нового токенизатора на чистом Python было бы мучительно медленным, поэтому мы разработали библиотеку 🤗 Tokenizers. Обратите внимание, что так же, как вам не нужно было изучать язык CUDA, чтобы иметь возможность выполнять свою модель на пакете входных данных на графическом процессоре, вам не нужно будет изучать Rust, чтобы использовать быстрый токенизатор. Библиотека 🤗 Tokenizers предоставляет привязки Python для многих методов, которые внутренне вызывают некоторый фрагмент кода в Rust; например, для распараллеливания обучения вашего нового токенизатора или, как мы видели в [Главе 3](/course/ru/chapter3), токенизации пакета батча данных. + +Большинство моделей Transformer имеют быстрый токенизатор (есть некоторые исключения, которые вы можете проверить [здесь](https://huggingface.co/transformers/#supported-frameworks)), а API `AutoTokenizer` всегда выбирает быстрый токенизатор для вас, если он доступен. В следующем разделе мы рассмотрим некоторые другие специальные функции быстрых токенизаторов, которые будут действительно полезны для таких задач, как классификация токенов и ответы на вопросы. Однако, прежде чем углубляться в это, давайте попробуем наш новый токенизатор на предыдущем примере: + +```py +tokens = tokenizer.tokenize(example) +tokens +``` + +```python out +['def', 'Ġadd', '_', 'numbers', '(', 'a', ',', 'Ġb', '):', 'ĊĠĠĠ', 'Ġ"""', 'Add', 'Ġthe', 'Ġtwo', 'Ġnumbers', 'Ġ`', + 'a', '`', 'Ġand', 'Ġ`', 'b', '`."""', 'ĊĠĠĠ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb'] +``` + +Здесь мы снова видим специальные символы `Ġ` и `Ċ`, которые обозначают пробелы и символы новой строки, но мы также можем видеть, что наш токенизатор изучил некоторые токены, очень специфичные для корпуса функций Python: например, есть `ĊĠĠĠ ` токен, который представляет отступ, и токен `Ġ"""`, который представляет три кавычки, с которых начинается строка документации. Токенизатор также правильно разделяет имя функции по символу `_`. Это довольно компактное представление; для сравнения используем простой английский токенизатор на том же примере даст нам более длинное предложение: + + +```py +print(len(tokens)) +print(len(old_tokenizer.tokenize(example))) +``` + +```python out +27 +36 +``` + +Давайте взглянем на еще один пример: + +```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', 'ĊĠĠĠĠ'] +``` + +В дополнение к токену, соответствующему отступу, здесь мы также можем видеть токен для двойного отступа: `ĊĠĠĠĠĠĠĠ`. Специальные слова Python, такие как `class`, `init`, `call`, `self` и `return` токенизатор корректно разбивает имена даже в верблюжьем регистре: `LinearLayer` токенизируется как `["ĠLinear", "Layer"]`. + +## Сохранение токенизатора + +Чтобы убедиться, что мы сможем использовать его позже, нам нужно сохранить наш новый токенизатор. Как и в случае с моделями, это делается с помощью метода `save_pretrained()`: + +```py +tokenizer.save_pretrained("code-search-net-tokenizer") +``` + +Будет создана новая папка с именем *code-search-net-tokenizer*, которая будет содержать все файлы, которые необходимо использовать токенизатору. Если вы хотите поделиться этим токенизатором со своими коллегами и друзьями, вы можете загрузить его в Hub, войдя в свою учетную запись. Если вы работаете в блокноте, есть удобная функция, которая поможет вам в этом: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Это отобразит виджет, где вы можете ввести свои учетные данные для входа в Hugging Face. Если вы не работаете в блокноте, просто введите в терминале следующую строку: + +```bash +huggingface-cli login +``` + +После входа в систему вы можете активировать свой токенизатор, выполнив следующую команду: + +```py +tokenizer.push_to_hub("code-search-net-tokenizer") +``` + +Это создаст новый репозиторий в вашем пространстве имен с именем `code-search-net-tokenizer`, содержащий файл токенизатора. Затем вы можете загрузить токенизатор из любого места с помощью метода `from_pretrained()`: + +```py +# Измените "huggingface-course" на ваше название пространства +tokenizer = AutoTokenizer.from_pretrained("huggingface-course/code-search-net-tokenizer") +``` + +Теперь у вас все готово для обучения языковой модели с нуля и ее точной настройки для вашей задачи! Мы вернемся к этому в [Главе 7](/course/ru/chapter7), но сначала в оставшейся части этой главы мы более подробно рассмотрим быстрые токенизаторы и подробно рассмотрим, что на самом деле происходит, когда мы вызываем метод ` train_new_from_iterator()`. diff --git a/chapters/th/chapter1/1.mdx b/chapters/th/chapter1/1.mdx index a11b9c706..388a1c87e 100644 --- a/chapters/th/chapter1/1.mdx +++ b/chapters/th/chapter1/1.mdx @@ -50,10 +50,10 @@ **Lucile Saulnier** ทำงานตำแหน่ง Machine Learning Engineer ที่ Hugging Face ทำหน้าที่พัฒนาและสนับสนุนการใช้งานเครื่องมือ open-source เธอเองเป็นส่วนหนึ่งในโปรเจควิจัยหลายโปรเจคในเกี่ยวกับ NLP เช่น collaborative training และ BigScience -**Lewis Tunstall** ทำงานตำแหน่ง Machine Learning Engineer ที่ Hugging Face ทำหน้าที่พัฒนาเครื่องมือ open-source เพื่อให้มีการใช้งานอย่างแพร่หลายในชุมชน เป็นผู้ร่วมแต่งหนังสือ [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/) ที่กำลังจะตีพิมพ์เร็ว ๆ นี้ +**Lewis Tunstall** ทำงานตำแหน่ง Machine Learning Engineer ที่ Hugging Face ทำหน้าที่พัฒนาเครื่องมือ open-source เพื่อให้มีการใช้งานอย่างแพร่หลายในชุมชน เป็นผู้ร่วมแต่งหนังสือ [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) ที่กำลังจะตีพิมพ์เร็ว ๆ นี้ -**Leandro von Werra** ทำงานตำแหน่ง Machine Learning Engineer ที่ Hugging Face ทีม open-source และเป็นผู้ร่วมแต่งหนังสือ [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/) เช่นกัน มีประสบการณ์หลายปีในการนำโปรเจค NLP สู่การใช้งานจริงในอุตสาหกรรม +**Leandro von Werra** ทำงานตำแหน่ง Machine Learning Engineer ที่ Hugging Face ทีม open-source และเป็นผู้ร่วมแต่งหนังสือ [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) เช่นกัน มีประสบการณ์หลายปีในการนำโปรเจค NLP สู่การใช้งานจริงในอุตสาหกรรม พร้อมกันรึยัง? ในบทนี้ คุณจะได้เรียน: diff --git a/chapters/th/chapter6/3b.mdx b/chapters/th/chapter6/3b.mdx index 19ca66b5b..53f0893fd 100644 --- a/chapters/th/chapter6/3b.mdx +++ b/chapters/th/chapter6/3b.mdx @@ -583,8 +583,8 @@ 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[0] - end_idx = idx % scores.shape[0] + 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)) @@ -599,8 +599,8 @@ 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[0] - end_idx = idx % scores.shape[0] + 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)) diff --git a/chapters/tr/chapter1/1.mdx b/chapters/tr/chapter1/1.mdx index 2b41bc4a1..6dbfd560d 100644 --- a/chapters/tr/chapter1/1.mdx +++ b/chapters/tr/chapter1/1.mdx @@ -47,9 +47,9 @@ Eğitmenler hakkında: **Lucile Saulnier** Hugging Face'de Makine Öğrenmesi Mühendisi, açık kaynak araçlarının geliştirilmesi ve desteklenmesi üzerine uğraşıyor. Ayrıca Doğal Dil İşleme içinde Collaborative Training ve BigScience gibi birçok araştırma projesine dahil. -**Lewis Tunstall** Hugging Face'de Makine Öğrenmesi Mühendisi, açık kaynak araçları geliştirmeye ve bunlari daha geniş bir topluluk için ulaşılabilir hale getirmeye odaklanmış. Ayrıca yakında gelecek olan [Transformers üzerine O’Reilly kitabının](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/) yazarlarından biri. +**Lewis Tunstall** Hugging Face'de Makine Öğrenmesi Mühendisi, açık kaynak araçları geliştirmeye ve bunlari daha geniş bir topluluk için ulaşılabilir hale getirmeye odaklanmış. Ayrıca yakında gelecek olan [Transformers üzerine O’Reilly kitabının](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) yazarlarından biri. -**Leandro von Werra** Hugging Face'de Açık-Kaynak takımında Makine Öğrenmesi Mühendisi ve ayrica yakında gelecek olan [Transformers üzerine olan O’Reilly kitabinin](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/) yazarlarından biri. Tüm Makine Öğrenmesi stack'inde çalişarak NLP projelerini üretime getiren birkaç yıllık endüstri deneyimine sahiptir. +**Leandro von Werra** Hugging Face'de Açık-Kaynak takımında Makine Öğrenmesi Mühendisi ve ayrica yakında gelecek olan [Transformers üzerine olan O’Reilly kitabinin](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) yazarlarından biri. Tüm Makine Öğrenmesi stack'inde çalişarak NLP projelerini üretime getiren birkaç yıllık endüstri deneyimine sahiptir. Başlamaya hazır mısın? Bu bölümde, şunları öğreneceksin: diff --git a/chapters/vi/chapter1/1.mdx b/chapters/vi/chapter1/1.mdx index 3981c7bcc..0dffa6bd9 100644 --- a/chapters/vi/chapter1/1.mdx +++ b/chapters/vi/chapter1/1.mdx @@ -50,9 +50,9 @@ Giới thiệu về tác giả: **Lucile Saulnier** là một Kỹ sư Học máy tại Hugging Face, phát triển và hỗ trợ việc sử dụng các công cụ mã nguồn mở. Cô cũng tích cực tham gia vào nhiều dự án nghiên cứu trong lĩnh vực Xử lý Ngôn ngữ Tự nhiên như huấn luyện cộng tác và BigScience. -**Lewis Tunstall** là một Kỹ sư Học máy tại Hugging Face, tập trung vào việc phát triển các công cụ mã nguồn mở và giúp chúng có thể tiếp cận được với cộng đồng rộng lớn hơn. Anh cũng là đồng tác giả của cuốn sách O’Reilly [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). +**Lewis Tunstall** là một Kỹ sư Học máy tại Hugging Face, tập trung vào việc phát triển các công cụ mã nguồn mở và giúp chúng có thể tiếp cận được với cộng đồng rộng lớn hơn. Anh cũng là đồng tác giả của cuốn sách O’Reilly [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). -**Leandro von Werra** là một Kỹ sư Học máy trong nhóm mã nguồn mở tại Hugging Face và cũng là đồng tác giả của cuốn sách O'Reilly [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). Anh ấy có nhiều năm kinh nghiệm thực tế triển khai các dự án NLP vào sản xuất bằng cách làm việc trên toàn bộ hệ thống học máy. +**Leandro von Werra** là một Kỹ sư Học máy trong nhóm mã nguồn mở tại Hugging Face và cũng là đồng tác giả của cuốn sách O'Reilly [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). Anh ấy có nhiều năm kinh nghiệm thực tế triển khai các dự án NLP vào sản xuất bằng cách làm việc trên toàn bộ hệ thống học máy. Bạn đã sẵn sàng chưa? Trong chương này, bạn sẽ học: diff --git a/chapters/vi/chapter5/4.mdx b/chapters/vi/chapter5/4.mdx index 48a494eb1..381224111 100644 --- a/chapters/vi/chapter5/4.mdx +++ b/chapters/vi/chapter5/4.mdx @@ -26,7 +26,7 @@ Trong phần này, chúng ta sẽ khám phá các tính năng này của 🤗 Da ## Pile là gì? -The Pile là một kho ngữ liệu tiếng Anh được tạo ra bởi [EleutherAI](https://www.eleuther.ai) để huấn luyện các mô hình ngôn ngữ quy mô lớn. Nó bao gồm một loạt các bộ dữ liệu, các bài báo khoa học trải dài, kho mã GitHub và văn bản web được lọc. Kho tài liệu huấn luyện có sẵn trong [khối 14GB](https://mystic.the-eye.eu/public/AI/pile/) và bạn cũng có thể tải xuống một số [thành phần riêng lẻ](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/). Hãy bắt đầu bằng cách xem qua tập dữ liệu PubMed Abstracts, tập dữ liệu tóm tắt từ 15 triệu ấn phẩm y sinh trên [PubMed](https://pubmed.ncbi.nlm.nih.gov/). Tập dữ liệu ở [định dạng JSON Lines](https://jsonlines.org) và được nén bằng thư viện `zstandard`, vì vậy trước tiên chúng ta cần cài đặt: +The Pile là một kho ngữ liệu tiếng Anh được tạo ra bởi [EleutherAI](https://www.eleuther.ai) để huấn luyện các mô hình ngôn ngữ quy mô lớn. Nó bao gồm một loạt các bộ dữ liệu, các bài báo khoa học trải dài, kho mã GitHub và văn bản web được lọc. Kho tài liệu huấn luyện có sẵn trong [khối 14GB](https://the-eye.eu/public/AI/pile/) và bạn cũng có thể tải xuống một số [thành phần riêng lẻ](https://the-eye.eu/public/AI/pile_preliminary_components/). Hãy bắt đầu bằng cách xem qua tập dữ liệu PubMed Abstracts, tập dữ liệu tóm tắt từ 15 triệu ấn phẩm y sinh trên [PubMed](https://pubmed.ncbi.nlm.nih.gov/). Tập dữ liệu ở [định dạng JSON Lines](https://jsonlines.org) và được nén bằng thư viện `zstandard`, vì vậy trước tiên chúng ta cần cài đặt: ```py !pip install zstandard @@ -38,7 +38,7 @@ Tiếp theo, chúng ta có thể tải tập dữ liệu bằng phương pháp c from datasets import load_dataset # Quá trình này mất một vài phút để chạy, vì vậy hãy làm cốc trà hoặc cà phê trong khi chờ đợi :) -data_files = "https://mystic.the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst" +data_files = "https://the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst" pubmed_dataset = load_dataset("json", data_files=data_files, split="train") pubmed_dataset ``` @@ -109,7 +109,7 @@ Tuyệt vời - mặc dù nó gần 20 GB, chúng ta có thể tải và truy c -✏️ **Thử nghiệm thôi!** Chọn một trong các [tập hợp con](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/) từ Pile sao cho lớn hơn RAM của máy tính xách tay hoặc máy tính để bàn của bạn, tải nó với 🤗 Datasets, và đo dung lượng RAM được sử dụng. Lưu ý rằng để có được một phép đo chính xác, bạn sẽ muốn thực hiện việc này trong một quy trình mới. Bạn có thể tìm thấy các kích thước đã giải nén của từng tập hợp con trong Bảng 1 của [bài báo về Pile](https://arxiv.org/abs/2101.00027). +✏️ **Thử nghiệm thôi!** Chọn một trong các [tập hợp con](https://the-eye.eu/public/AI/pile_preliminary_components/) từ Pile sao cho lớn hơn RAM của máy tính xách tay hoặc máy tính để bàn của bạn, tải nó với 🤗 Datasets, và đo dung lượng RAM được sử dụng. Lưu ý rằng để có được một phép đo chính xác, bạn sẽ muốn thực hiện việc này trong một quy trình mới. Bạn có thể tìm thấy các kích thước đã giải nén của từng tập hợp con trong Bảng 1 của [bài báo về Pile](https://arxiv.org/abs/2101.00027). @@ -232,7 +232,7 @@ Hãy hoàn thành việc khám phá của chúng ta về việc truyền trực ```py law_dataset_streamed = load_dataset( "json", - data_files="https://mystic.the-eye.eu/public/AI/pile_preliminary_components/FreeLaw_Opinions.jsonl.zst", + data_files="https://the-eye.eu/public/AI/pile_preliminary_components/FreeLaw_Opinions.jsonl.zst", split="train", streaming=True, ) @@ -270,7 +270,7 @@ list(islice(combined_dataset, 2)) Cuối cùng, nếu bạn muốn phát trực tuyến toàn bộ 825 GB của Pile, bạn có thể lấy tất cả các tệp đã chuẩn bị như sau: ```py -base_url = "https://mystic.the-eye.eu/public/AI/pile/" +base_url = "https://the-eye.eu/public/AI/pile/" data_files = { "train": [base_url + "train/" + f"{idx:02d}.jsonl.zst" for idx in range(30)], "validation": base_url + "val.jsonl.zst", diff --git a/chapters/vi/chapter6/3b.mdx b/chapters/vi/chapter6/3b.mdx index aa1788492..bb8145d52 100644 --- a/chapters/vi/chapter6/3b.mdx +++ b/chapters/vi/chapter6/3b.mdx @@ -576,8 +576,8 @@ 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[0] - end_idx = idx % scores.shape[0] + 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)) @@ -592,8 +592,8 @@ 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[0] - end_idx = idx % scores.shape[0] + 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)) diff --git a/chapters/vi/chapter7/3.mdx b/chapters/vi/chapter7/3.mdx index 9a3048af6..e354003cf 100644 --- a/chapters/vi/chapter7/3.mdx +++ b/chapters/vi/chapter7/3.mdx @@ -523,6 +523,7 @@ def whole_word_masking_data_collator(features): 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) ``` @@ -563,6 +564,7 @@ def whole_word_masking_data_collator(features): 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) ``` diff --git a/chapters/vi/event/1.mdx b/chapters/vi/event/1.mdx index 992b50bfe..a8a78cb0b 100644 --- a/chapters/vi/event/1.mdx +++ b/chapters/vi/event/1.mdx @@ -86,7 +86,7 @@ Jakob Uszkoreit là đồng sáng lập của Inception. Inception thiết kế
-Lewis là một kỹ sư máy học tại Hugging Face, tập trung vào việc phát triển các công cụ mã nguồn mở và giúp chúng có thể tiếp cận với cộng đồng rộng lớn hơn. Anh cũng là đồng tác giả của cuốn sách O’Reilly [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/). Bạn có thể theo dõi anh ấy trên Twitter (@_lewtun) để biết các mẹo và thủ thuật NLP! +Lewis là một kỹ sư máy học tại Hugging Face, tập trung vào việc phát triển các công cụ mã nguồn mở và giúp chúng có thể tiếp cận với cộng đồng rộng lớn hơn. Anh cũng là đồng tác giả của cuốn sách O’Reilly [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). Bạn có thể theo dõi anh ấy trên Twitter (@_lewtun) để biết các mẹo và thủ thuật NLP! **Matthew Carrigan:** * Các tính năng TensorFlow mới cho 🤗 Transformers và 🤗 Datasets* diff --git a/chapters/zh-CN/_toctree.yml b/chapters/zh-CN/_toctree.yml index b23fbbc78..5bcdd498b 100644 --- a/chapters/zh-CN/_toctree.yml +++ b/chapters/zh-CN/_toctree.yml @@ -63,15 +63,15 @@ - local: chapter3/6 title: 章末小测验 quiz: 3 - -- title: 4. 共享 models 和 tokenizers + +- title: 4. 分享你的模型和标记器 sections: - local: chapter4/1 title: The Hugging Face Hub - local: chapter4/2 title: 使用预训练的模型 - local: chapter4/3 - title: 共享预训练模型 + title: 分享预训练的模型 - local: chapter4/4 title: 构建模型卡片 - local: chapter4/5 @@ -99,6 +99,7 @@ - local: chapter5/8 title: 章末小测验 quiz: 5 + - title: 6. 🤗 Tokenizers库 sections: - local: chapter6/1 @@ -123,4 +124,74 @@ title: 标记器,回顾! - local: chapter6/10 title: 章末小测验 - quiz: 6 \ No newline at end of file + quiz: 6 + +- title: 7. 主要的 NLP 任务 + sections: + - local: chapter7/1 + title: 章节简介 + - local: chapter7/2 + title: 标记(token)分类 + - local: chapter7/3 + title: 微调一个掩码(mask)语言模型 + - 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 + title: 调试训练管道 + local_fw: { pt: chapter8/4, tf: chapter8/4_tf } + - local: chapter8/5 + title: 如何提出一个好的问题 + - local: chapter8/6 + title: Part 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: 了解接口类 + - local: chapter9/4 + title: 与他人分享演示 + - local: chapter9/5 + title: 与 Hugging Face Hub 整合 + - local: chapter9/6 + title: 高级界面功能 + - local: chapter9/7 + title: Gradio 块简介 + - local: chapter9/8 + title: Gradio, 回顾! + - local: chapter9/9 + title: 章末测试 + quiz: 9 + +- title: Hugging Face 课程活动 + sections: + - local: event/1 + title: Part 2 发布活动 diff --git a/chapters/zh-CN/chapter1/1.mdx b/chapters/zh-CN/chapter1/1.mdx index fcd439329..fb0ccc351 100644 --- a/chapters/zh-CN/chapter1/1.mdx +++ b/chapters/zh-CN/chapter1/1.mdx @@ -47,9 +47,9 @@ **Lucile Saulnier** 是 Hugging Face 的机器学习工程师,负责开发和支持开源工具的使用。她还积极参与了自然语言处理领域的许多研究项目,例如协作训练和 BigScience。 -**Lewis Tunstall** 是 Hugging Face 的机器学习工程师,专注于开发开源工具并使更广泛的社区可以使用它们。他也是即将出版的一本书[O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/)的作者之一。 +**Lewis Tunstall** 是 Hugging Face 的机器学习工程师,专注于开发开源工具并使更广泛的社区可以使用它们。他也是即将出版的一本书[O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/)的作者之一。 -**Leandro von Werra** 是 Hugging Face 开源团队的机器学习工程师,也是即将出版的一本书[O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098103231/)的作者之一。他拥有多年的行业经验,通过在整个机器学习堆栈中工作,将 NLP 项目投入生产。 +**Leandro von Werra** 是 Hugging Face 开源团队的机器学习工程师,也是即将出版的一本书[O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/)的作者之一。他拥有多年的行业经验,通过在整个机器学习堆栈中工作,将 NLP 项目投入生产。 你准备好了吗?在本章中,您将学习: * 如何使用 `pipeline()` 函数解决文本生成、分类等NLP任务 diff --git a/chapters/zh-CN/chapter5/4.mdx b/chapters/zh-CN/chapter5/4.mdx index 28b99365d..f5675110c 100644 --- a/chapters/zh-CN/chapter5/4.mdx +++ b/chapters/zh-CN/chapter5/4.mdx @@ -18,7 +18,7 @@ ## 什么是Pile? -The Pile 是由[EleutherAI](https://www.eleuther.ai)创建的一个英语文本语料库, 用于训练大规模语言模型。它包含各种各样的数据集, 涵盖科学文章, GitHub 代码库以及过滤的Web文本。训练语料库在[14 GB chunks](https://mystic.the-eye.eu/public/AI/pile/), 并且你也可以下载几个[单独的组件](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/)。 让我们先来看看 PubMed Abstracts 数据集, 它是[PubMed](https://pubmed.ncbi.nlm.nih.gov/)上的1500万篇生物医学出版物的摘要的语料库。 数据集采用[JSON行格式](https://jsonlines.org) 并使用`zstandard`库进行压缩, 所以我们首先需要先安装`zstandard`库: +The Pile 是由[EleutherAI](https://www.eleuther.ai)创建的一个英语文本语料库, 用于训练大规模语言模型。它包含各种各样的数据集, 涵盖科学文章, GitHub 代码库以及过滤的Web文本。训练语料库在[14 GB chunks](https://the-eye.eu/public/AI/pile/), 并且你也可以下载几个[单独的组件](https://the-eye.eu/public/AI/pile_preliminary_components/)。 让我们先来看看 PubMed Abstracts 数据集, 它是[PubMed](https://pubmed.ncbi.nlm.nih.gov/)上的1500万篇生物医学出版物的摘要的语料库。 数据集采用[JSON行格式](https://jsonlines.org) 并使用`zstandard`库进行压缩, 所以我们首先需要先安装`zstandard`库: ```py !pip install zstandard @@ -30,7 +30,7 @@ The Pile 是由[EleutherAI](https://www.eleuther.ai)创建的一个英语文本 from datasets import load_dataset # This takes a few minutes to run, so go grab a tea or coffee while you wait :) -data_files = "https://mystic.the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst" +data_files = "https://the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst" pubmed_dataset = load_dataset("json", data_files=data_files, split="train") pubmed_dataset ``` @@ -101,7 +101,7 @@ Dataset size (cache file) : 19.54 GB -✏️ **试试看!** 从[subsets](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/)中选择一个大于你的笔记本或者台式机的RAM大小的子集, 用 🤗 Datasets加载这个数据集, 并且测量RAM的使用量。 请注意, 要获得准确的测量结果, 你需要在另一个进程中执行这个操作。你可以在 [the Pile paper](https://arxiv.org/abs/2101.00027)的表一中找到每个子集解压后的大小。 +✏️ **试试看!** 从[subsets](https://the-eye.eu/public/AI/pile_preliminary_components/)中选择一个大于你的笔记本或者台式机的RAM大小的子集, 用 🤗 Datasets加载这个数据集, 并且测量RAM的使用量。 请注意, 要获得准确的测量结果, 你需要在另一个进程中执行这个操作。你可以在 [the Pile paper](https://arxiv.org/abs/2101.00027)的表一中找到每个子集解压后的大小。 @@ -225,7 +225,7 @@ validation_dataset = shuffled_dataset.take(1000) ```py law_dataset_streamed = load_dataset( "json", - data_files="https://mystic.the-eye.eu/public/AI/pile_preliminary_components/FreeLaw_Opinions.jsonl.zst", + data_files="https://the-eye.eu/public/AI/pile_preliminary_components/FreeLaw_Opinions.jsonl.zst", split="train", streaming=True, ) @@ -263,7 +263,7 @@ list(islice(combined_dataset, 2)) 最后, 如果你想流式传输整个825GB的 Pile, 你可以按照如下方式获取所有准备好的文件: ```py -base_url = "https://mystic.the-eye.eu/public/AI/pile/" +base_url = "https://the-eye.eu/public/AI/pile/" data_files = { "train": [base_url + "train/" + f"{idx:02d}.jsonl.zst" for idx in range(30)], "validation": base_url + "val.jsonl.zst", diff --git a/chapters/zh-CN/chapter6/3b.mdx b/chapters/zh-CN/chapter6/3b.mdx index 0a290ad68..4c4b95d7f 100644 --- a/chapters/zh-CN/chapter6/3b.mdx +++ b/chapters/zh-CN/chapter6/3b.mdx @@ -573,8 +573,8 @@ 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[0] - end_idx = idx % scores.shape[0] + 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)) @@ -589,8 +589,8 @@ 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[0] - end_idx = idx % scores.shape[0] + 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)) diff --git a/chapters/zh-CN/chapter7/1.mdx b/chapters/zh-CN/chapter7/1.mdx new file mode 100644 index 000000000..46d2ecb2f --- /dev/null +++ b/chapters/zh-CN/chapter7/1.mdx @@ -0,0 +1,33 @@ + + +# 章节简介 + +在[第三章](/course/chapter3),您了解了如何微调文本分类的模型。在本章中,我们将处理以下常见NLP任务: + +- 标记(token)分类 +- 遮罩语言建模(如BERT) +- 提取文本摘要 +- 翻译 +- 因果语言建模预训练(如GPT-2) +- 问答 + +{#if fw === 'pt'} + +为此,您需要利用[第三章](/course/chapter3)中学到的`Trainer` API 和🤗Accelerate 库、[第五章](/course/chapter5)中的 🤗 Datasets 库以及[第六章](/course/chapter6)中的 🤗 Tokenizers 库的所有知识。我们还会将结果上传到模型中心,就像我们在[第四章](/course/chapter4)中所做的那样,所以这确实是将之前所有内容汇集在一起的章节! + +每个部分都可以独立阅读,并将向您展示如何使用API或按照您自己的训练循环训练模型,使用🤗 Accelerate 加速。你可以随意跳过其中一部分,把注意力集中在你最感兴趣的那一部分:API可以优化或训练您的模型而无需担心幕后发生了什么,而训练循环使用可以让您更轻松地自定义所需的任何结构。 + +{:else} + +为此,您需要利用[第三章](/course/chapter3)中学到的有关Keras API、[第五章](/course/chapter5)中的 🤗 Datasets 库以及[第六章](/course/chapter6)中的 🤗 Tokenizers 库的所有知识。我们还会将结果上传到模型中心,就像我们在[第四章](/course/chapter4)中所做的那样,所以这确实是将之前所有内容汇集在一起的章节! + +每个部分都可以独立阅读。 + +{/if} + + + + +如果您按顺序阅读这些部分,您会注意到它们有很多共同的代码和陈述。 重复是有意为之的,让您可以深入(或稍后返回)任何您感兴趣的任务并找到一个完整的工作示例。 + + diff --git a/chapters/zh-CN/chapter7/2.mdx b/chapters/zh-CN/chapter7/2.mdx new file mode 100644 index 000000000..9ce606af6 --- /dev/null +++ b/chapters/zh-CN/chapter7/2.mdx @@ -0,0 +1,978 @@ + + +# Token 分类 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +我们将探索的第一个应用是Token分类。这个通用任务包括任何可以表述为“为句子中的词或字分配标签”的问题,例如: + +- **实体命名识别 (NER)**: 找出句子中的实体(如人物、地点或组织)。这可以通过为每个实体或“无实体”指定一个类别的标签。 +- **词性标注 (POS)**: 将句子中的每个单词标记为对应于特定的词性(如名词、动词、形容词等)。 +- **分块(chunking)**: 找到属于同一实体的Token。这个任务(可结合POS或NER)可以任何将一块Token作为制定一个标签(通常是B -),另一个标签(通常I -)表示Token是否是同一块,和第三个标签(通常是O)表示Token不属于任何块。也就是标出句子中的短语块,例如名词短语(NP),动词短语(VP)等。 + + + +当然,还有很多其他类型的token分类问题;这些只是几个有代表性的例子。在本节中,我们将在 NER 任务上微调模型 (BERT),然后该模型将能够计算如下预测: + + + + + +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).找到我们将训练并上传到 Hub的模型,可以尝试输入一些句子看看模型的预测结果。 + +## 准备数据 + +首先,我们需要一个适合标记分类的数据集。在本节中,我们将使用[CoNLL-2003 数据集](https://huggingface.co/datasets/conll2003), 其中包含来自路透社的新闻报道。 + + + +💡 只要您的数据集由带有相应标签的分割成单词并的文本组成,您就能够将这里描述的数据处理过程应用到您自己的数据集。如果需要复习如何在.Dataset中加载自定义数据,请参阅[Chapter 5](/course/chapter5)。 + + + +### CoNLL-2003 数据集 + +要加载 CoNLL-2003 数据集,我们使用 来自 🤗 Datasets 库的**load_dataset()** 方法: + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("conll2003") +``` + +这将下载并缓存数据集,就像和我们在[第三章](/course/chapter3) 加载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] +``` + +这一列是类标签的序列。元素的类型在ner_feature的feature属性中,我们可以通过查看该特性的names属性来访问名称列表: + +```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) +``` + +因此,这一列包含的元素是ClassLabels的序列。序列元素的类型在`ner_feature`的`feature`中,我们可以通过查看该`feature`的`names`属性来访问名称列表: + +```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'] +``` + +我们在[第六章](/course/chapter6/3), 深入研究**token-classification** 管道时已经看到了这些标签 ,但为了快速复习: + +- `O` 表示这个词不对应任何实体。 +- `B-PER`/`I-PER`意味着这个词对应于人名实体的开头/内部。 +- `B-ORG`/`I-ORG` 的意思是这个词对应于组织名称实体的开头/内部。 +- `B-LOC`/`I-LOC` 指的是是这个词对应于地名实体的开头/内部。 +- `B-MISC`/`I-MISC` 表示该词对应于一个杂项实体的开头/内部。 + +现在解码我们之前看到的标签: + +```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标签识别同一个句子。 + + + +### 处理数据 + + + +像往常一样,我们的文本需要转换为Token ID,然后模型才能理解它们。正如我们在[第六章](/course/chapter6/)所学的那样。不过在标记任务中,一个很大的区别是我们有pre-tokenized的输入。幸运的是,tokenizer 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), 上看到所有带有快速版本的架构,或者检查 您可以通过查看它`is_fast` 属性来检测正在使用的`tokenizer`对象是否由 🤗 Tokenizers 支持: + +```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]'] +``` + +正如我们所见,分词器添加了模型使用的特殊Token(`[CLS]` 在开始和`[SEP]` 最后) 而大多数单词未被修改。然而,单词 `lamb`,被分为两个子单词 `la` and `##mb`。这导致了输入和标签之间的不匹配:标签列表只有9个元素,而我们的输入现在有12个token 。计算特殊Token很容易(我们知道它们在开头和结尾),但我们还需要确保所有标签与适当的单词对齐。 +幸运的是,由于我们使用的是快速分词器,因此我们可以访问🤗 Tokenizers超能力,这意味着我们可以轻松地将每个令牌映射到其相应的单词(如[Chapter 6](/course/chapter6/3)): + +```py +inputs.word_ids() +``` + +```python out +[None, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, None] +``` + +通过一点点工作,我们可以扩展我们的标签列表以匹配token 。我们将应用的第一条规则是,特殊token 的标签为 `-100` 。这是因为默认情况下 `-100` 是一个在我们将使用的损失函数(交叉熵)中被忽略的索引。然后,每个token 都会获得与其所在单词的token 相同的标签,因为它们是同一实体的一部分。对于单词内部但不在开头的Token,我们将`B-` 替换为 `I-` (因为token 不以实体开头): + +```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: + # Start of a new 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: + # Special token + new_labels.append(-100) + else: + # Same word as previous token + label = labels[word_id] + # If the label is B-XXX we change it to 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` 给定词中的其他子标记。这是为了避免分解成大量子标记的长词对损失造成严重影响。按照此规则更改前一个函数使标签与输入id对齐。 + + + +为了预处理我们的整个数据集,我们需要标记所有输入并在所有标签上应用 `align_labels_with_tokens()` 。为了利用我们的快速分词器的速度优势,最好同时对大量文本进行分词,因此我们将编写一个处理示例列表的函数并使用带 `batched=True` 有选项的 `Dataset.map()`方法 .与我们之前的示例唯一不同的是当分词器的输入是文本列表(或者像例子中的单词列表)时 `word_ids()` 函数需要获取我们想要单词的索引的ID,所以我们也添加它: + +```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 +``` + +请注意,我们还没有填充我们的输入;我们稍后会在使用数据整理器创建batch时这样做。 + +我们现在可以一次性将所有预处理应用于数据集的其他部分: + +```py +tokenized_datasets = raw_datasets.map( + tokenize_and_align_labels, + batched=True, + remove_columns=raw_datasets["train"].column_names, +) +``` + +我们已经完成了最难的部分!现在数据已经被预处理了,实际的训练看起来很像我们[第三章](/course/chapter3)做的. + +{#if fw === 'pt'} + +## 使用 Trainer API 微调模型 + +使用 `Trainer` 的实际代码会和以前一样;唯一的变化是数据整理成时批处理的方式和度量计算函数。 + +{:else} + +## 使用 Keras 微调模型 + +使用Keras的实际代码将与之前非常相似;唯一的变化是将数据整理成批处理的方式和指标计算函数。 + +{/if} + + +### 数据排序 + +我们不能像[第三章](/course/chapter3)那样只使用一个 `DataCollatorWithPadding `因为这只会填充输入(输入 ID、注意掩码和标记类型 ID)。在这里我们的标签应该以与输入完全相同的方式填充,以便它们保持长度相同,使用 `-100 ` ,这样在损失计算中就可以忽略相应的预测。 + +这一切都是由一个 [`DataCollatorForTokenClassification`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorfortokenclassification)完成.它是一个带有填充的数据整理器它需要 `tokenizer ` 用于预处理输入: + +{#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} + +我们的数据整理器已准备就绪!现在,让我们用它来制作一个带有`to_tf_dataset()`方法的`tf.data.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, +) +``` + + + Next stop: the model itself. + +{/if} + +{#if fw === 'tf'} + +### 定义模型 + +由于我们正在研究Token分类问题,因此我们将使用 `AutoModelForTokenClassification` 类。定义这个模型时要记住的主要事情是传递一些关于我们的标签数量的信息。执行此操作的最简单方法是将该数字传递给 `num_labels` 参数,但是如果我们想要一个很好的推理小部件,就像我们在本节开头看到的那样,最好设置正确的标签对应关系。 + +它们应该由两个字典设置, `id2label` 和 `label2id` ,其中包含从 ID 到标签的映射,反之亦然: + +```py +id2label = {str(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 TFAutoModelForTokenClassification + +model = TFAutoModelForTokenClassification.from_pretrained( + model_checkpoint, + id2label=id2label, + label2id=label2id, +) +``` + +就像我们在[第三章](/course/chapter3),定义我们的 `AutoModelForSequenceClassification` ,创建模型会发出警告,提示一些权重未被使用(来自预训练头的权重)和一些其他权重被随机初始化(来自新Token分类头的权重),我们将要训练这个模型。我们将在一分钟内完成,但首先让我们仔细检查我们的模型是否具有正确数量的标签: + +```python +model.config.num_labels +``` + +```python out +9 +``` + + + +⚠️ 如果您的模型标签数量错误,则在稍后调用 `model.fit()` 时将收到一个模糊的错误。调试起来可能很烦人,因此请确保执行此检查以确认您具有预期的标签数。 + + + +### 微调模型 + +现在,我们已准备好训练模型了!不过,我们首先要做两件事:应该登录到Hugging Face并定义我们的训练超参数。如果你在notebook上工作,有一个便利功能可以帮助你做到这一点: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +这将显示一个小部件,您可以在其中输入您的 Hugging Face 账号和密码。 + +如果您不是在notebook上工作,只需在终端中输入以下行: + +```bash +huggingface-cli login +``` + +登录后,我们可以准备编译模型所需的一切。🤗 Transformers提供了一个方便的`create_optimizer()` 函数,该函数将为您提供一个`AdamW`优化器,其中包含适当的权重衰减和学习速率衰减设置,与内置的`Adam`优化器相似,这两者都将提高模型的性能: + +```python +from transformers import create_optimizer +import tensorflow as tf + +# Train in mixed-precision float16 +# Comment this line out if you're using a GPU that will not benefit from this +tf.keras.mixed_precision.set_global_policy("mixed_float16") + +# 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_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) +``` + +还要注意,我们不为`compile()`提供`loss`参数。这是因为模型实际上可以在内部计算损失 - 如果您编译时没有损失并在输入字典中提供标签(就像我们在数据集中所做的那样),那么模型将使用该内部损失进行训练,这将适用于您选择的任务和模型类型。 + +接下来,我们定义一个`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, +) +``` + +您之前已经看过其中的大部分内容:我们设置了一些超参数(例如学习率、要训练的 epoch 数和权重衰减),然后我们指定 `push_to_hub=True` 表明我们想要保存模型并在每个时期结束时对其进行评估,并且我们想要将我们的结果上传到模型中心。请注意,可以使用hub_model_id参数指定要推送到的存储库的名称(特别是,必须使用这个参数来推送到一个组织)。例如,当我们将模型推送到[`huggingface-course` organization](https://huggingface.co/huggingface-course), 我们添加了 `hub_model_id=huggingface-course/bert-finetuned-ner` 到 `TrainingArguments` .默认情况下,使用的存储库将在您的命名空间中并以您设置的输出目录命名,因此在我们的例子中它将是 `sgugger/bert-finetuned-ner` . + + + +💡 如果您正在使用的输出目录已经存在,那么输出目录必须是从同一个存储库clone下来的。如果不是,您将在声明 `model.fit()` 时遇到错误,并且需要设置一个新名称。 + + + +请注意,当训练发生时,每次保存模型时(这里是每个epooch),它都会在后台上传到 Hub。这样,如有必要,您将能够在另一台机器上继续您的训练。 + +在此阶段,您可以使用模型中心上的推理小组件来测试模型并与朋友共享。您已经成功微调了令牌分类任务的模型 - 恭喜!但是,我们的模型到底有多好呢?我们应该评估一些指标来找出答案。 + +{/if} + + +### 评估指标 + +{#if fw === 'pt'} + +为了让 `Trainer` 在每个epoch计算一个度量,我们需要定义一个 `compute_metrics()` 函数,该函数接受预测和标签数组,并返回一个包含度量名称和值的字典 + +用于评估Token分类预测的传统框架是 [*seqeval*](https://github.com/chakki-works/seqeval). 要使用此指标,我们首先需要安装seqeval库: + +```py +!pip install seqeval +``` + +然后我们可以通过加载它 `load_metric()` 函数就像我们在[第三章](/course/chapter3)做的那样: + +{:else} + +用于评估Token分类预测的传统框架是 [*seqeval*](https://github.com/chakki-works/seqeval). 要使用此指标,我们首先需要安装seqeval库: + +```py +!pip install seqeval +``` + +然后我们可以通过加载它 `load_metric()` 函数就像我们在[第三章](/course/chapter3)做的那样: + +{/if} + +```py +from datasets import load_metric + +metric = load_metric("seqeval") +``` + +这个评估方式与标准精度不同:它实际上将标签列表作为字符串,而不是整数,因此在将预测和标签传递给它之前,我们需要完全解码它们。让我们看看它是如何工作的。首先,我们将获得第一个训练示例的标签: + +```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'} + +它返回很多信息!我们获得每个单独实体以及整体的准确率、召回率和 F1 分数。对于我们的度量计算,我们将只保留总分,但可以随意调整 `compute_metrics()` 函数返回您想要查看的所有指标。 + +这`compute_metrics()` 函数首先采用 logits 的 argmax 将它们转换为预测(像往常一样,logits 和概率的顺序相同,因此我们不需要应用 softmax)。然后我们必须将标签和预测从整数转换为字符串。我们删除标签为 `-100` 所有值 ,然后将结果传递给 `metric.compute()` 方法: + +```py +import numpy as np + + +def compute_metrics(eval_preds): + logits, labels = eval_preds + predictions = np.argmax(logits, axis=-1) + + # Remove ignored index (special tokens) and convert to labels + 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} + +它返回很多信息!我们获得每个单独实体以及整体的准确率、召回率和 F1 分数。对于我们的度量计算,我们将只保留总分,但可以随意调整 `compute_metrics()` 函数返回您想要查看的所有指标。 + +这`compute_metrics()` 函数首先采用 logits 的 argmax 将它们转换为预测(像往常一样,logits 和概率的顺序相同,因此我们不需要应用 softmax)。然后我们必须将标签和预测从整数转换为字符串。我们删除标签为 `-100` 所有值 ,然后将结果传递给 `metric.compute()` 方法: + +```py +import numpy as np + +all_predictions = [] +all_labels = [] +for batch in tf_eval_dataset: + logits = model.predict(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'} + +### 定义模型 + +由于我们正在研究Token分类问题,因此我们将使用 `AutoModelForTokenClassification` 类。定义这个模型时要记住的主要事情是传递一些关于我们的标签数量的信息。执行此操作的最简单方法是将该数字传递给 `num_labels` 参数,但是如果我们想要一个很好的推理小部件,就像我们在本节开头看到的那样,最好设置正确的标签对应关系。 + +它们应该由两个字典设置, `id2label` 和 `label2id` ,其中包含从 ID 到标签的映射,反之亦然: + +```py +id2label = {str(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, +) +``` + +就像我们在[第三章](/course/chapter3),定义我们的 `AutoModelForSequenceClassification` ,创建模型会发出警告,提示一些权重未被使用(来自预训练头的权重)和一些其他权重被随机初始化(来自新Token分类头的权重),我们将要训练这个模型。我们将在一分钟内完成,但首先让我们仔细检查我们的模型是否具有正确数量的标签: + +```python +model.config.num_labels +``` + +```python out +9 +``` + + + +⚠️ 如果模型的标签数量错误,稍后调用Trainer.train()方法时会出现一个模糊的错误(类似于“CUDA error: device-side assert triggered”)。这是用户报告此类错误的第一个原因,因此请确保进行这样的检查以确认您拥有预期数量的标签。 + + + +### 微调模型 + +我们现在准备好训练我们的模型了!在定义我们的 `Trainer`之前,我们只需要做最后两件事:登录 Hugging Face 并定义我们的训练参数。如果您在notebook上工作,有一个方便的功能可以帮助您: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` +这将显示一个小部件,您可以在其中输入您的 Hugging Face 账号和密码。如果您不是在notebook上工作,只需在终端中输入以下行: + +```bash +huggingface-cli login +``` + +Once this is done, we can define our `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, +) +``` + +您之前已经看过其中的大部分内容:我们设置了一些超参数(例如学习率、要训练的 epoch 数和权重衰减),然后我们指定 `push_to_hub=True` 表明我们想要保存模型并在每个时期结束时对其进行评估,并且我们想要将我们的结果上传到模型中心。请注意,可以使用hub_model_id参数指定要推送到的存储库的名称(特别是,必须使用这个参数来推送到一个组织)。例如,当我们将模型推送到[`huggingface-course` organization](https://huggingface.co/huggingface-course), 我们添加了 `hub_model_id=huggingface-course/bert-finetuned-ner` 到 `TrainingArguments` 。默认情况下,使用的存储库将在您的命名空间中并以您设置的输出目录命名,因此在我们的例子中它将是 `sgugger/bert-finetuned-ner`。 + + + +💡 如果您正在使用的输出目录已经存在,那么输出目录必须是从同一个存储库clone下来的。如果不是,您将在声明 `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() +``` + +请注意,当训练发生时,每次保存模型时(这里是每个epooch),它都会在后台上传到 Hub。这样,如有必要,您将能够在另一台机器上继续您的训练。 + +训练完成后,我们使用 `push_to_hub()` 确保我们上传模型的最新版本 + +```py +trainer.push_to_hub(commit_message="Training complete") +``` + +This command returns the URL of the commit it just did, if you want to inspect it: + +```python out +'https://huggingface.co/sgugger/bert-finetuned-ner/commit/26ab21e5b1568f9afeccdaed2d8715f571d786ed' +``` + +这 `Trainer` 还创建了一张包含所有评估结果的模型卡并上传。在此阶段,您可以使用模型中心上的推理小部件来测试您的模型并与您的朋友分享。您已成功在Token分类任务上微调模型 - 恭喜! + +如果您想更深入地了解训练循环,我们现在将向您展示如何使用 🤗 Accelerate 做同样的事情。 + +## 自定义训练循环 + +现在让我们看一下完整的训练循环,这样您可以轻松定义所需的部分。它看起来很像我们在[第三章](/course/chapter3/4), 所做的,对评估进行了一些更改。 + +### 做好训练前的准备 +首先我们需要为我们的数据集构建 `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) +``` + +Once we have all those objects, we can send them to the `accelerator.prepare()` method: + +```py +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + + + +🚨 如果您在 TPU 上进行训练,则需要将以上单元格中的所有代码移动到专用的训练函数中。有关详细信息,请参阅 [第3章](/course/chapter3)。 + + + +现在我们已经发送了我们的 `train_dataloader` 到 `accelerator.prepare()` ,我们可以使用它的长度来计算训练步骤的数量。请记住,我们应该始终在准备好dataloader后执行此操作,因为该方法会改变其长度。我们使用经典线性学习率调度: + +```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。我们将从我们想要为模型提供的模型 ID 中确定存储库名称(您可以自由地用自己的选择替换 `repo_name` ;它只需要包含您的用户名,可以使用`get_full_repo_name()`函数的查看目前的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' +``` + +Then we can clone that repository in a local folder. If it already exists, this local folder should be an existing clone of the repository we are working with: + +```py +output_dir = "bert-finetuned-ner-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +We can now upload anything we save in `output_dir` by calling the `repo.push_to_hub()` method. This will help us upload the intermediate models at the end of each epoch. + +### Training loop + +### 训练循环 +我们现在准备编写完整的训练循环。为了简化它的评估部分,我们定义了这个 `postprocess()` 接受预测和标签并将它们转换为字符串列表的函数,也就是 `metric`对象需要的输入格式: + +```py +def postprocess(predictions, labels): + predictions = predictions.detach().cpu().clone().numpy() + labels = labels.detach().cpu().clone().numpy() + + # Remove ignored index (special tokens) and convert to labels + 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`的经典迭代,向前传递模型,然后反向传递和优化参数 +- 评估,在获得我们模型的输出后:因为两个进程可能将输入和标签填充成不同的形状,在调用`gather()`方法前我们需要使用`accelerator.pad_across_processes()`来让预测和标签形状相同。如果我们不这样做,评估要么出错,要么永远不会得到结果。然后,我们将结果发送给`metric.add_batch()`,并在计算循环结束后调用`metric.compute()`。 +- 保存和上传,首先保存模型和标记器,然后调用`repo.push_to_hub()`。注意,我们使用参数`blocking=False`告诉🤗 hub 库用在异步进程中推送。这样,训练将正常继续,并且该(长)指令将在后台执行。 + +这是训练循环的完整代码: + +```py +from tqdm.auto import tqdm +import torch + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Training + 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) + + # Evaluation + model.eval() + for batch in eval_dataloader: + with torch.no_grad(): + outputs = model(**batch) + + predictions = outputs.logits.argmax(dim=-1) + labels = batch["labels"] + + # Necessary to pad predictions and labels for being gathered + 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"] + }, + ) + + # Save and upload + accelerator.wait_for_everyone() + unwrapped_model = accelerator.unwrap_model(model) + unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) + if accelerator.is_main_process: + tokenizer.save_pretrained(output_dir) + repo.push_to_hub( + commit_message=f"Training in progress epoch {epoch}", blocking=False + ) +``` + +果这是您第一次看到用 🤗 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_pretraining()`方法;`accelerator.unwrap_model()`方法将撤销该步骤。最后,我们调用`save_pretraining()`,但告诉该方法使用`accelerator.save()`而不是`torch.save()`。 + +当完成之后,你应该有一个模型,它产生的结果与`Trainer`的结果非常相似。你可以在[hugs face-course/bert-fine - tuning -ner-accelerate](https://huggingface.co/huggingface-course/bert-finetuned-ner-accelerate)中查看我们使用这个代码训练的模型。如果你想测试训练循环的任何调整,你可以直接通过编辑上面显示的代码来实现它们! + +{/if} + +## 使用微调模型 + +我们已经向您展示了如何使用我们在模型中心微调的模型和推理小部件。在本地使用它 `pipeline` ,您只需要指定正确的模型标识符: + +```py +from transformers import pipeline + +# Replace this with your own checkpoint +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/zh-CN/chapter7/3.mdx b/chapters/zh-CN/chapter7/3.mdx new file mode 100644 index 000000000..d95d76313 --- /dev/null +++ b/chapters/zh-CN/chapter7/3.mdx @@ -0,0 +1,1044 @@ + + +# 微调掩码语言模型 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +对于许多涉及 Transformer 模型的 NLP 程序, 你可以简单地从 Hugging Face Hub 中获取一个预训练的模型, 然后直接在你的数据上对其进行微调, 以完成手头的任务。只要用于预训练的语料库与用于微调的语料库没有太大区别, 迁移学习通常会产生很好的结果。 + +但是, 在某些情况下, 你需要先微调数据上的语言模型, 然后再训练特定于任务的head。例如, 如果您的数据集包含法律合同或科学文章, 像 BERT 这样的普通 Transformer 模型通常会将您语料库中的特定领域词视为稀有标记, 结果性能可能不尽如人意。通过在域内数据上微调语言模型, 你可以提高许多下游任务的性能, 这意味着您通常只需执行一次此步骤! + +这种在域内数据上微调预训练语言模型的过程通常称为 _领域适应_。 它于 2018 年由 [ULMFiT](https://arxiv.org/abs/1801.06146)推广, 这是使迁移学习真正适用于 NLP 的首批神经架构之一 (基于 LSTM)。 下图显示了使用 ULMFiT 进行域自适应的示例; 在本节中, 我们将做类似的事情, 但使用的是 Transformer 而不是 LSTM! + +
+ULMFiT. + +
+ +在本节结束时, 你将在Hub上拥有一个[掩码语言模型(masked language model)](https://huggingface.co/huggingface-course/distilbert-base-uncased-finetuned-imdb?text=This+is+a+great+%5BMASK%5D.), 该模型可以自动完成句子, 如下所示: + + + + +让我们开始吧! + + + + + +🙋 如果您对“掩码语言建模”和“预训练模型”这两个术语感到陌生, 请查看[第一章](/course/chapter1), 我们在其中解释了所有这些核心概念, 并附有视频! + + + +## 选择用于掩码语言建模的预训练模型 + +首先, 让我们为掩码语言建模选择一个合适的预训练模型。如以下屏幕截图所示, 你可以通过在[Hugging Face Hub](https://huggingface.co/models?pipeline_tag=fill-mask&sort=downloads)上应用"Fill-Mask"过滤器找到: + +
+Hub models. +
+ +尽管 BERT 和 RoBERTa 系列模型的下载量最大, 但我们将使用名为 [DistilBERT](https://huggingface.co/distilbert-base-uncased)的模型。 +可以更快地训练, 而下游性能几乎没有损失。这个模型使用一种称为[_知识蒸馏_](https://en.wikipedia.org/wiki/Knowledge_distillation)的特殊技术进行训练, 其中使用像 BERT 这样的大型“教师模型”来指导参数少得多的“学生模型”的训练。在本节中对知识蒸馏细节的解释会使我们离题太远, 但如果你有兴趣, 可以阅读所有相关内容 [_Natural Language Processing with Transformers_](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/ch05.html) (俗称Transformers教科书)。 + +{#if fw === 'pt'} + +让我们继续, 使用 `AutoModelForMaskedLM` 类下载 DistilBERT: + +```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} + +让我们继续, 使用 `AutoModelForMaskedLM` 类下载 DistilBERT: + +```python +from transformers import TFAutoModelForMaskedLM + +model_checkpoint = "distilbert-base-uncased" +model = TFAutoModelForMaskedLM.from_pretrained(model_checkpoint) +``` + +我们可以通过调用 `summary()` 方法查看模型有多少参数: + +```python +model(model.dummy_inputs) # Build the model +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} + +DistilBERT 大约有 6700 万个参数, 大约比 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 的标记器来生成模型的输入, 所以让我们也从 Hub 下载它: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +使用标记器和模型, 我们现在可以将我们的文本示例传递给模型, 提取 logits, 并打印出前 5 个候选: + +{#if fw === 'pt'} + +```python +import torch + +inputs = tokenizer(text, return_tensors="pt") +token_logits = model(**inputs).logits +# Find the location of [MASK] and extract its logits +mask_token_index = torch.where(inputs["input_ids"] == tokenizer.mask_token_id)[1] +mask_token_logits = token_logits[0, mask_token_index, :] +# Pick the [MASK] candidates with the highest logits +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 +# Find the location of [MASK] and extract its logits +mask_token_index = np.argwhere(inputs["input_ids"] == tokenizer.mask_token_id)[0, 1] +mask_token_logits = token_logits[0, mask_token_index, :] +# Pick the [MASK] candidates with the highest logits +# We negate the array before argsort to get the largest, not the smallest, logits +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.' +``` + +我们可以从输出中看到模型的预测是指日常用语, 鉴于英语维基百科的基础, 这也许并不奇怪。让我们看看我们如何将这个领域改变为更小众的东西 -- 高度两极分化的电影评论! + + +## 数据集 + +为了展示域适配, 我们将使用著名的[大型电影评论数据集(Large Movie Review Dataset)](https://huggingface.co/datasets/imdb) (或者简称为IMDb), 这是一个电影评论语料库, 通常用于对情感分析模型进行基准测试。通过在这个语料库上对 DistilBERT 进行微调, 我们预计语言模型将根据维基百科的事实数据调整其词汇表, 这些数据已经预先训练到电影评论中更主观的元素。我们可以使用🤗 Datasets中的`load_dataset()`函数从Hugging Face 中获取数据: + +```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' +``` + +是的, 这些肯定是电影评论, 如果你年龄足够,你甚至可能会理解上次评论中关于拥有 VHS 版本的评论😜! 虽然我们不需要语言建模的标签, 但我们已经可以看到 `0` 表示负面评论, 而 `1` 对应正面。 + + + +✏️ **试试看!** 创建 `无监督` 拆分的随机样本, 并验证标签既不是 `0` 也不是 `1`。在此过程中, 你还可以检查 `train` 和 `test` 拆分中的标签是否确实为 `0` 或 `1` -- 这是每个 NLP 从业者在新项目开始时都应该执行的有用的健全性检查! + + + +现在我们已经快速浏览了数据, 让我们深入研究为掩码语言建模做准备。正如我们将看到的, 与我们在[第三章](/course/chapter3)中看到的序列分类任务相比, 还需要采取一些额外的步骤。让我们继续! + +## 预处理数据 + + + +对于自回归和掩码语言建模, 一个常见的预处理步骤是连接所有示例, 然后将整个语料库拆分为相同大小的块。 这与我们通常的方法完全不同, 我们只是简单地标记单个示例。为什么要将所有内容连接在一起? 原因是单个示例如果太长可能会被截断, 这将导致丢失可能对语言建模任务有用的信息! + +因此, 我们将像往常一样首先标记我们的语料库, 但是 _没有_ 在我们的标记器中设置 `truncation=True` 选项。 我们还将获取可用的单词 ID ((如果我们使用快速标记器, 它们是可用的, 如 [第六章](/course/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 + + +# Use batched=True to activate fast multithreading! +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 一样。 + + + +✏️ **试试看!** 一些 Transformer 模型, 例如 [BigBird](https://huggingface.co/google/bigbird-roberta-base) 和 [Longformer](hf.co/allenai/longformer-base-4096), 它们具有比BERT和其他早期Transformer模型更长的上下文长度。为这些检查点之一实例化标记器, 并验证 `model_max_length` 是否与模型卡上引用的内容一致。 + + + +因此, 以便在像Google Colab 那样的 GPU 上运行我们的实验, 我们将选择可以放入内存的更小一些的东西: + +```python +chunk_size = 128 +``` + + + +请注意, 在实际场景中使用较小的块大小可能是有害的, 因此你应该使用与将应用模型的用例相对应的大小。 + + + +有趣的来了。为了展示串联是如何工作的, 让我们从我们的标记化训练集中取一些评论并打印出每个评论的标记数量: + +```python +# Slicing produces a list of lists for each feature +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' +``` + +然后我们可以用一个简单的字典理解来连接所有例子, 如下所示: + +```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' +``` + +很棒, 总长度检查出来了 -- 现在, 让我们将连接的评论拆分为大小为 `block_size` 的块。为此, 我们迭代了 `concatenated_examples` 中的特征, 并使用列表理解来创建每个特征的切片。结果是每个特征的块字典: + +```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): + # Concatenate all texts + concatenated_examples = {k: sum(examples[k], []) for k in examples.keys()} + # Compute length of concatenated texts + total_length = len(concatenated_examples[list(examples.keys())[0]]) + # We drop the last chunk if it's smaller than chunk_size + total_length = (total_length // chunk_size) * chunk_size + # Split by chunks of 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() + } + # Create a new labels column + result["labels"] = result["input_ids"].copy() + return result +``` + +注意, 在 `group_texts()` 的最后一步中, 我们创建了一个新的 `labels` 列, 它是 `input_ids` 列的副本。我们很快就会看到, 这是因为在掩码语言建模中, 目标是预测输入批次中随机掩码的标记, 并通过创建一个 `labels` 列, 们为我们的语言模型提供了基础事实以供学习。 + +现在, 让我们使用我们可信赖的 `Dataset.map()` 函数将 `group_texts()` 应用到我们的标记化数据集: + +```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` 拆分的示例多得多。那是因为我们现在有了涉及 _连续标记_ 的示例, 这些示例跨越了原始语料库中的多个示例。你可以通过在其中一个块中查找特殊的 `[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]` 标记! 让我们看看如何使用特殊的数据整理器在微调期间即时执行此操作。 + +## 使用 `Trainer` API 微调DistilBERT + +微调屏蔽语言模型几乎与微调序列分类模型相同, 就像我们在 [第三章](/course/chapter3)所作的那样。 唯一的区别是我们需要一个特殊的数据整理器, 它可以随机屏蔽每批文本中的一些标记。幸运的是, 🤗 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} + +在为掩码语言建模训练模型时, 可以使用的一种技术是将整个单词一起屏蔽, 而不仅仅是单个标记。这种方法称为 _全词屏蔽_。 如果我们想使用全词屏蔽, 我们需要自己构建一个数据整理器。数据整理器只是一个函数, 它接受一个样本列表并将它们转换为一个批次, 所以现在让我们这样做吧! 我们将使用之前计算的单词 ID 在单词索引和相应标记之间进行映射, 然后随机决定要屏蔽哪些单词并将该屏蔽应用于输入。请注意, 除了与掩码对应的标签外, 所有的标签均为 `-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") + + # Create a map between words and corresponding token indices + 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) + + # Randomly mask words + 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 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") + + # Create a map between words and corresponding token indices + 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) + + # Randomly mask words + 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} + +Next, we can try it on the same samples as before: + +```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()` 以查看来自给定单词的标记始终被屏蔽在一起。 + + + +现在我们有两个数据整理器, 其余的微调步骤是标准的。如果您没有足够幸运地获得神话般的 P100 GPU 😭, 在 Google Colab 上进行训练可能需要一段时间, 因此我们将首先将训练集的大小缩减为几千个示例。别担心, 我们仍然会得到一个相当不错的语言模型! 在 🤗 Datasets 中快速下采样数据集的方法是通过我们在 [第五章](/course/chapter5) 中看到的 `Dataset.train_test_split()` 函数: + +```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` 数据集。我们将在这里只使用标准数据整理器, 但你也可以尝试使用整个单词掩码整理器并将结果作为练习进行比较: + +```python +tf_train_dataset = downsampled_dataset["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) + +tf_eval_dataset = downsampled_dataset["test"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=32, +) +``` + +接下来, 我们设置训练超参数并编译模型。我们使用 🤗 Transformers 库中的 `create_optimizer()` 函数, 它提供了一个具有线性学习率衰减的 `AdamW` 优化器。我们还使用了模型内置的损失, 当没有损失被指定为 `compile()` 参数是, 这是默认值, 并且我们将训练精度设置为 `"mixed_float16"`。请注意,如果你使用的是Colab GPU或其他不支持float16加速的GPU, 你可能应该注释掉这一行。 + +此外, 我们还设置了一个 `PushToHubCallback`, 它将在每个epoch后将模型保存到Hub。你可以使用 `hub_model_id` 参数指定你想要推送到的存储库的名称 (特别是你将必须使用该参数来推送到组织)。例如, 为了将模型推送到 [`huggingface-course` organization](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) + +# Train in mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") + +callback = PushToHubCallback( + output_dir=f"{model_name}-finetuned-imdb", tokenizer=tokenizer +) +``` + +我们现在已经准备好运行 `model.fit()` 了 -- 但在此之前, 让我们先简单地看看 _perplexity_, 它是评估语言模型性能的常用指标。 + +{:else} + +登陆后, 我们可以指定 `Trainer` 参数: + +```python +from transformers import TrainingArguments + +batch_size = 64 +# Show the training loss with every epoch +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` , 以确保我们跟踪每个epoch的训练损失。我们还使用了 `fp16=True` 来实现混合精度训练, 这给我们带来了另一个速度提升。默认情况下, `Trainer` 将删除不属于模型的 `forward()` 方法的列。这意味着, 如果你使用整个单词屏蔽排序器, 你还需要设置 `remove_unused_columns=False`, 以确保我们不会在训练期间丢失 `word_ids` 列。 + +请注意, 你可以使用 `hub_model_id` 参数指定要推送到的存储库的名称((特别是你将必须使用该参数向组织推送)。例如, 当我们将模型推送到 [`huggingface-course` organization](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, +) +``` + +我们现在准备运行 `trainer.train()` -- 但在此之前让我们简要地看一下 _perplexity_, 这是评估语言模型性能的常用指标。 + +{/if} + +### 语言模型的perplexity + + + +与文本分类或问答等其他任务不同, 在这些任务中, 我们会得到一个带标签的语料库进行训练, 而语言建模则没有任何明确的标签。那么我们如何确定什么是好的语言模型呢? 就像手机中的自动更正功能一样, 一个好的语言模型是为语法正确的句子分配高概率, 为无意义的句子分配低概率。为了让你更好地了解这是什么样子, 您可以在网上找到一整套 "autocorrect fails", 其中一个人手机中的模型产生了一些相当有趣 (而且通常不合适) 的结果! + +{#if fw === 'pt'} + +假设我们的测试集主要由语法正确的句子组成, 那么衡量我们的语言模型质量的一种方法是计算它分配给测试集中所有句子中的下一个单词的概率。高概率表明模型对看不见的例子并不感到 "惊讶" 或 "疑惑", 并表明它已经学习了语言中的基本语法模式。 perplexity度有多种数学定义, 但我们将使用的定义是交叉熵损失的指数。因此, 我们可以通过 `Trainer.evaluate()` 函数计算测试集上的交叉熵损失, 然后取结果的指数来计算预训练模型的perplexity度: + +```python +import math + +eval_results = trainer.evaluate() +print(f">>> Perplexity: {math.exp(eval_results['eval_loss']):.2f}") +``` + +{:else} + +假设我们的测试集主要由语法正确的句子组成, 那么衡量我们的语言模型质量的一种方法是计算测试集所有句子中它分配给下一个单词的概率。高概率表明, 该模型表明该模型对未见过的示例不感到 "惊讶" 或 "困惑", 并表明它已经学会了该语言的基本语法模式。关于perplexity度有各种不同的数学定义, 但我们要用的定义是交叉熵损失的指数。因此, 我们可以通过 `model.evaluate()` 方法计算测试集上的交叉熵损失, 然后取结果的指数来计算我们预训练模型的perplexity度: + +```python +import math + +eval_loss = model.evaluate(tf_eval_dataset) +print(f"Perplexity: {math.exp(eval_loss):.2f}") +``` + +{/if} + +```python out +>>> Perplexity: 21.75 +``` + +较低的perplexity分数意味着更好的语言模型, 我们可以在这里看到我们的起始模型有一个较大的值。看看我们能不能通过微调来降低它! 为此, 我们首先运行训练循环: + +{#if fw === 'pt'} + +```python +trainer.train() +``` + +{:else} + +```python +model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback]) +``` + +{/if} + +然后像以前一样计算测试集上的perplexity度: + +{#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 -- 让我们来看看吧! + +## 使用 🤗 Accelerate 微调 DistilBERT + +正如我们在 `Trainer` 中看到的, 对掩码语言模型的微调与 [第三章](/course/chapter3) 中的文本分类示例非常相似。事实上, 唯一的微妙之处是使用特殊的数据整理器, 我们已经在本节的前面介绍过了! + +然而, 我们看到 `DataCollatorForLanguageModeling` 还对每次评估应用随机掩码, 因此每次训练运行时, 我们都会看到perplexity度分数的一些波动。消除这种随机性来源的一种方法是应用掩码 _一次_ 在整个测试集上, 然后使用🤗 Transformers 中的默认数据整理器在评估期间收集批次。为了看看它是如何工作的, 让我们实现一个简单的函数, 将掩码应用于批处理, 类似于我们第一次遇到的 `DataCollatorForLanguageModeling`: + +```python +def insert_random_mask(batch): + features = [dict(zip(batch, t)) for t in zip(*batch.values())] + masked_inputs = data_collator(features) + # Create a new "masked" column for each column in the dataset + 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", + } +) +``` + +然后我们可以像往常一样设置数据加载器, 但我们将使用🤗 Transformers 中的 `default_data_collator` 作为评估集: + +```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 库来首先生成我们的 repo 的全名: + +```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' +``` + +然后使用来自🤗 Hub 的 `Repository` 类: + +```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): + # Training + 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) + + # Evaluation + 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}") + + # Save and upload + accelerator.wait_for_everyone() + unwrapped_model = accelerator.unwrap_model(model) + unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) + if accelerator.is_main_process: + tokenizer.save_pretrained(output_dir) + repo.push_to_hub( + commit_message=f"Training in progress epoch {epoch}", blocking=False + ) +``` + +```python out +>>> Epoch 0: Perplexity: 11.397545307900472 +>>> Epoch 1: Perplexity: 10.904909330983092 +>>> Epoch 2: Perplexity: 10.729503505340409 +``` + +很棒, 我们已经能够评估每个 epoch 的perplexity度, 并确保多次训练运行是可重复的! + +{/if} + +## 使用我们微调的模型 + +你可以通过在Hub上使用其他小部件或在本地使用🤗 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.' +``` + +好的 -- 我们的模型显然已经调整了它的权重来预测与电影更密切相关的词! + + + +这结束了我们训练语言模型的第一个实验。在 [第六节](/course/chapter7/section6)中你将学习如何从头开始训练像 GPT-2 这样的自回归模型; 如果你想了解如何预训练您自己的 Transformer 模型, 请前往那里! + + + +✏️ **试试看!** 为了量化域适应的好处, 微调 IMDb 标签上的分类器和预先训练和微调的Distil BERT检查点。如果你需要复习文本分类, 请查看 [第三章](/course/chapter3)。 + + diff --git a/chapters/zh-CN/chapter7/4.mdx b/chapters/zh-CN/chapter7/4.mdx new file mode 100644 index 000000000..07c79149c --- /dev/null +++ b/chapters/zh-CN/chapter7/4.mdx @@ -0,0 +1,996 @@ + + +# 翻译 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +现在让我们深入研究翻译。这是另一个[sequence-to-sequence 任务](/course/chapter1/7),这意味着这是一个可以表述为从一个序列到另一个序列的问题。从这个意义上说,这个问题非常类似[文本摘要](/course/chapter7/6),并且您可以将我们将在此处学习到的一些内容迁移到其他的序列到序列问题,例如: + +- **风格迁移** : 创建一个模型将某种风格迁移到一段文本(例如,正式的风格迁移到休闲的风格或莎士比亚英语到现代英语) +- **生成问题的回答** :创建一个模型,在给定上下文的情况下生成问题的答案 + + + +如果您有足够大的两种(或更多)语言的文本语料库,您可以从头开始训练一个新的翻译模型,就像我们在[因果语言建模](/course/chapter7/6)部分中所做的那样。然而,微调现有的翻译模型会更快,无论是从像 mT5 或 mBART 这样的多语言模型微调到特定的语言对,或者是专门用于从一种语言翻译成另一种语言的模型。 + +在本节中,我们将对[KDE4 数据集](https://huggingface.co/datasets/kde4)上的Marian模型进行微调,该模型经过了从英语到法语的翻译预训练(因为很多“ Hugging Face”的员工会说这两种语言),它是[KDE应用程序](https://apps.kde.org/)本地化文件的数据集。我们将使用的模型已经在从[Opus 数据集](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.)查看模型输出的结果 + +## 准备数据 + +为了从头开始微调或训练翻译模型,我们需要一个适合该任务的数据集。如前所述,我们将使用[KDE4 数据集](https://huggingface.co/datasets/kde4)在本节中,但您可以很容易地调整代码以使用您自己的数据,只要您有要互译的两种语言的句子对。如果您需要复习如何将自定义数据加载到 **Dataset** 可以复习一下[第五章](/course/chapter5). + +### KDE4 数据集 + +像往常一样,我们使用 **load_dataset()** 函数: + +```py +from datasets import load_dataset, load_metric + +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 对句子,但在一次训练过程中,我们需要创建自己的验证集。正如我们在[第五章](/course/chapter5)学的的那样, **Dataset** 有一个 **train_test_split()** 方法,可以帮我们拆分数据集。我们将设定固定的随机数种子: + +```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”。在训练数据集中找到使用这个词的第一个样本。它是如何翻译的?预训练模型如何翻译同一个英文句子? + + + +### 处理数据 + + + +您现在应该知道我们的下一步该做些什么了:所有文本都需要转换为token ID,以便模型能够理解它们。对于这个任务,我们需要同时标记输入和目标。我们的首要任务是创建我们的 **tokenizer** 对象。如前所述,我们将使用 Marian 英语到法语的预训练模型。如果您使用另一对语言尝试此代码,请确保调整模型Checkpoint。[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="tf") +``` + +您可以将 **model_checkpoint** 更换为[Hub](https://huggingface.co/models)上你喜欢的任何其他型号,或本地保存的预训练模型和标记器。 + + + +💡 如果正在使用mart、mBART-50或M2 M100等多语言标记器,则需要在tokenizer中设置tokenizer.src_lang和tokenizer.tgt_lang为正确的输入和目标的语言代码。 + + + +我们的数据准备非常简单。 只需要记住一件事:您照常处理输入,但对于这次的输出目标,您需要将标记器包装在上下文管理器“as_target_tokenizer()”中。 + +Python 中的上下文管理器引入了 **with** 语句,当您有两个相关的操作要成对执行时很有用。最常见的例子是当您写入或读取文件时,下面是一个例子: + +``` +with open(file_path) as f: + content = f.read() +``` + +这里成对执行的两个相关操作是打开和关闭文件的操作。打开的文件f对应的对象只在with下的缩进块内有效;在该块之前打开,在该块的末尾关闭。 + +在本例中,上下文管理器 as_target_tokenizer() 将在执行缩进块之前将标记器设置为输出语言(此处为法语),然后将其设置回输入语言(此处为英语)。 + +因此,预处理一个样本如下所示: + +```python +en_sentence = split_datasets["train"][1]["translation"]["en"] +fr_sentence = split_datasets["train"][1]["translation"]["fr"] + +inputs = tokenizer(en_sentence) +with tokenizer.as_target_tokenizer(): + targets = tokenizer(fr_sentence) +``` + +如果我们忘记在上下文管理器中标记目标,它们将被输入标记器标记,在Marian模型的情况下,会导致输出的异常: + +```python +wrong_targets = tokenizer(fr_sentence) +print(tokenizer.convert_ids_to_tokens(wrong_targets["input_ids"])) +print(tokenizer.convert_ids_to_tokens(targets["input_ids"])) +``` + +```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` 和 `targets` 都是带有我们常用键(输入 ID、注意掩码等)的字典,所以最后一步是在输入中设置一个 `"labels"` 键。 我们在数据集的预处理函数中执行此操作: + +```python +max_input_length = 128 +max_target_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, max_length=max_input_length, truncation=True) + + # Set up the tokenizer for targets + with tokenizer.as_target_tokenizer(): + labels = tokenizer(targets, max_length=max_target_length, truncation=True) + + model_inputs["labels"] = labels["input_ids"] + return model_inputs +``` + +请注意,我们为输入和输出设置了相同的最大长度。由于我们处理的文本看起来很短,我们使用 128。 + + + +💡如果你使用的是T5模型(更具体地说,是T5 -xxx检查点之一),模型将需要文本输入有一个前缀来表示正在进行的任务,例如从英语到法语的翻译 + + + + + +⚠️ 我们不关注目标的注意力掩码,因为模型不会需要它。相反,对应于填充标记的标签应设置为-100,以便在loss计算中忽略它们。这将在稍后由我们的数据整理器完成,因为我们正在应用动态填充,但是如果您在此处使用填充,您应该调整预处理函数以将与填充标记对应的所有标签设置为 -100。 + + + +我们现在可以对数据集的所有数据一次性应用该预处理: + +```py +tokenized_datasets = split_datasets.map( + preprocess_function, + batched=True, + remove_columns=split_datasets["train"].column_names, +) +``` + +现在数据已经过预处理,我们准备好微调我们的预训练模型! + +{#if fw === 'pt'} + +## 使用 Trainer API 微调模型 + +使用 `Trainer` 的实际代码将与以前相同,只是稍作改动:我们在这里使用 [`Seq2SeqTrainer`](https://huggingface.co/transformers/main_classes/trainer.html#seq2seqtrainer), 它是 `Trainer` 的子类,它可以正确处理这种序列到序列的评估,并使用 `generate()` 方法来预测输入的输出。 当我们讨论评估指标时,我们将更详细地探讨这一点。 + +首先,我们需要一个实际的模型来进行微调。 我们将使用通常的 `AutoModel` API: + +```py +from transformers import AutoModelForSeq2SeqLM + +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{:else} + +## 使用 Keras 微调模型 + +首先,我们需要一个实际的模型来进行微调。 我们将使用通常的 `AutoModel` API: + +```py +from transformers import TFAutoModelForSeq2SeqLM + +model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint, from_pt=True) +``` + + + +💡 `Helsinki-NLP/opus-mt-en-fr` checkpoint只有 PyTorch 的权重,所以在使用`from_pretrained()`方法加载模型时不会使用 `from_pt=True` 参数。 当您指定`from_pt=True`,库会自动下载并转换PyTorch 为您提供权重。 如您所见,使用🤗transormer 在两者之间切换非常简单 + + + +{/if} + +请注意,这次我们使用的是在翻译任务上训练过的模型,并且实际上已经可以使用,因此没有关于丢失权重或新初始化权重的警告。 + +### 数据整理 + +我们需要一个数据整理器来处理动态批处理的填充。在本例中,我们不能像[第3章](/course/chapter3)那样使用带填充的**DataCollatorWithPadding**,因为它只填充输入(输入ID、注意掩码和令牌类型ID)。我们的标签也应该填充到标签中遇到的最大长度。而且,如前所述,用于填充标签的填充值应为-100,而不是标记器的填充标记,以确保在损失计算中忽略这些填充值。 + +这一切都可以由 [`DataCollatorForSeq2Seq`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorforseq2seq) 完成。 与 `DataCollatorWithPadding` 一样,它采用用于预处理输入的`tokenizer`,但它也采用`model`。 这是因为数据整理器还将负责准备解码器输入 ID,它们是标签偏移之后形成的,开头带有特殊标记。 由于不同架构的这种转变略有不同,因此“DataCollatorForSeq2Seq”需要知道“模型”对象: + +{#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]]) +``` + +我们还可以查看解码器输入 ID,看看它们是标签的偏移形成的版本: + +```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 = tokenized_datasets["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) +tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=16, +) +``` + +{/if} + + +### 评估指标 + + + +{#if fw === 'pt'} + +`Seq2SeqTrainer` 添加到其超类 `Trainer` 的功能是在评估或预测期间使用 `generate()` 方法的能力。 在训练期间,模型将使用带有注意掩码的“decoder_input_ids”,以确保它不使用预测的标记之后的标记,以加快训练速度。 在推理过程中,我们将无法使用预测的标记之后的标记,因为我们没有标签,因此使用相同的设置使用带有注意掩码的“decoder_input_ids”,评估我们的模型是个好主意。 + +正如我们在[第一章](/course/chapter1/6)看到的,解码器通过一个一个地预测标记来执行推理——这是🤗 Transformers 在幕后通过 **generate()** 方法实现的。如果我们设置 predict_with_generate=True,Seq2 Seq Trainer 将允许我们使用该方法进行评估。 + + +{/if} + +用于翻译的传统指标是[BLEU 分数](https://en.wikipedia.org/wiki/BLEU), 由Kishore Papineni等人在[2002年的一篇文章](https://aclanthology.org/P02-1040.pdf)中引入。BLEU 分数评估翻译与其标签的接近程度。它不衡量模型生成输出的可懂度或语法正确性,而是使用统计规则来确保生成输出中的所有单词也出现在目标中。此外,如果相同单词在目标中没有重复,则有规则惩罚相同单词的重复(以避免模型输出类似 **the the the the the**的句子 ) 并输出比目标中短的句子(以避免模型输出像 **the** 这样的句子)。 + +BLEU 的一个缺点是它需要文本已经被分词,这使得比较使用不同标记器的模型之间的分数变得困难。因此,当今用于基准翻译模型的最常用指标是[SacreBLEU](https://github.com/mjpost/sacrebleu),它通过标准化标记化步骤解决了这个缺点(和其他的一些缺点)。要使用此指标,我们首先需要安装 SacreBLEU 库: + +```py +!pip install sacrebleu +``` + +然后我们可以就像我们在[第三章](/course/chapter3)那样通过 **load_metric()** 加载它 : + +```py +from datasets import load_metric + +metric = load_metric("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} +``` + +这得到了 46.75 的 BLEU 分数,这是相当不错的——作为参考,原始 Transformer 模型在[“Attention Is All You Need” 论文](https://arxiv.org/pdf/1706.03762.pdf)类似的英语和法语翻译任务中获得了 41.8 的 BLEU 分数! (有关各个指标的更多信息,例如 **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** (标记器会自动为填充标记做同样的事情): + +```py +import numpy as np + + +def compute_metrics(): + all_preds = [] + all_labels = [] + sampled_dataset = tokenized_datasets["validation"].shuffle().select(range(200)) + tf_generate_dataset = sampled_dataset.to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=4, + ) + for batch in tf_generate_dataset: + predictions = model.generate( + input_ids=batch["input_ids"], attention_mask=batch["attention_mask"] + ) + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + labels = batch["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 + # In case the model returns more than the prediction logits + if isinstance(preds, tuple): + preds = preds[0] + + decoded_preds = tokenizer.batch_decode(preds, skip_special_tokens=True) + + # Replace -100s in the labels as we can't decode them + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + # Some simple post-processing + 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} + +现在这已经完成了,我们已经准备好微调我们的模型了! + +### 微调模型 + +第一步是登录 Hugging Face,这样您就可以将结果上传到模型中心。有一个方便的功能可以帮助您在notebook中完成此操作: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +这将显示一个小部件,您可以在其中输入您的 Hugging Face 登录凭据。 + +如果您不是在notebook上运行代码,只需在终端中输入以下行: + +```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 + +# 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_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) + +# Train in mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +接下来,我们定义一个 `PushToHubCallback` 以便在训练期间将我们的模型上传到 Hub,正如我们在 [第 2 节]((/course/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"`。 + + + +💡如果您使用的输出目录已经存在,则它需要是您要推送到的存储库的本地克隆。如果不是,您将在定义您的名称时会遇到错误,并且需要设置一个新名称。 + + + +最后,让我们看看训练结束后我们的指标是什么样的: + +```py +print(compute_metrics()) +``` + +``` +{'bleu': 57.334066271545865} +``` + +在这个阶段,您可以使用模型中心上的推理小部件来测试您的模型并与您的朋友分享。 您已经成功地微调了翻译任务中的模型——恭喜! + +{: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,这可以加快支持fp16的 GPU 上的训练速度。 +- 和上面我们讨论的那样,我们设置predict_with_generate=True +- 我们用push_to_hub=True在每个 epoch 结束时将模型上传到 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"`。 + + + +💡如果您使用的输出目录已经存在,则它需要是您要推送到的存储库的本地克隆。如果不是,您将在定义您的名称时会遇到错误,并且需要设置一个新名称。 + + + + +最后,我们需要将所有内容传递给 **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_target_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的分数还不错,这反映了我们的模型已经擅长将英语句子翻译成法语句子。 + +接下来是训练,这也需要一些时间: + +```python +trainer.train() +``` + +请注意,当训练发生时,每次保存模型时(这里是每个时期),它都会在后台上传到 Hub。这样,如有必要,您将能够在另一台机器上继续您的训练。 + +训练完成后,我们再次评估我们的模型——希望我们会看到 BLEU 分数有所改善! + +```py +trainer.evaluate(max_length=max_target_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()** 方法来确保我们上传模型的最新版本。这 **Trainer** 还创建了一张包含所有评估结果的模型卡并上传。此模型卡包含可帮助模型中心为推理演示选择小部件的元数据。通常不需要做额外的更改,因为它可以从模型类中推断出正确的小部件,但在这种情况下,相同的模型类可以用于所有类型的序列到序列问题,所以我们指定它是一个翻译模型: + +```py +trainer.push_to_hub(tags="translation", commit_message="Training complete") +``` + +如果您想检查命令执行的结果,此命令将返回它刚刚执行的提交的 URL,可以打开url进行检查: + +```python out +'https://huggingface.co/sgugger/marian-finetuned-kde4-en-to-fr/commit/3601d621e3baae2bc63d3311452535f8f58f6ef3' +``` + +在此阶段,您可以使用模型中心上的推理小部件来测试您的模型并与您的朋友分享。您已成功微调翻译任务的模型 - 恭喜! + +如果您想更深入地了解训练循环,我们现在将向您展示如何使用 🤗 Accelerate 做同样的事情。 + +{/if} + +{#if fw === 'pt'} + +## 自定义训练循环 + +现在让我们看一下完整的训练循环,以便您可以轻松自定义所需的部分。它看起来很像我们在[本章第二节](/course/chapter7/2)和[第三章第四小节](/course/chapter3/4)所做的。 + +### 准备训练所需的一切 + +您已经多次看到所有这些,因此这一块会简略进行。首先我们将构建我们的数据集的**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()` 方法。 请记住,如果您想在 Colab 笔记本训练中使用TPU,则需要将所有这些代码移动到训练函数中,并且不应执行任何实例化“加速器”的对象。 + +```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** 工作文件夹中的对象。如果您尚未登录,请先登录 Hugging Face。我们将从我们想要为模型提供的模型 ID 中确定存储库名称(您可以自由地用自己的选择替换 **repo_name** ;它需要包含您的用户名,可以使用**get_full_repo_name()**函数的查看目前的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) +``` + +我们现在可以通过调用 **repo.push_to_hub()** 方法上传我们保存的任何内容 **output_dir** 。这将帮助我们在每个 epoch 结束时上传过程中的模型。 + +### 训练循环 + +我们现在准备编写完整的训练循环。为了简化它的评估部分,我们定义了这个 **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) + + # Replace -100 in the labels as we can't decode them. + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + # Some simple post-processing + decoded_preds = [pred.strip() for pred in decoded_preds] + decoded_labels = [[label.strip()] for label in decoded_labels] + return decoded_preds, decoded_labels +``` + +训练循环看起来和[本章第二节](/course/chapter7/2)与[第三章](/course/chapter3)很像,在评估部分有一些不同 - 所以让我们专注于这一点! + +首先要注意的是我们使用 `generate()` 方法来计算预测,但这是我们基础模型上的一个方法,而不是包装模型🤗 Accelerate 在 `prepare()` 方法中创建。 这就是为什么我们先解包模型,然后调用这个方法。 + +第二件事是,就像[token 分类](/course/chapter7/2),两个进程可能将输入和标签填充为不同的形状,因此我们在调用 **gather()** 方法之前使用 **accelerator.pad_across_processes()** 使预测和标签具有相同的形状。如果我们不这样做,评估要么出错,要么永远在阻塞。 + +```py +from tqdm.auto import tqdm +import torch + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Training + 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) + + # Evaluation + 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"] + + # Necessary to pad predictions and labels for being gathered + 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}") + + # Save and upload + accelerator.wait_for_everyone() + unwrapped_model = accelerator.unwrap_model(model) + unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) + if accelerator.is_main_process: + tokenizer.save_pretrained(output_dir) + repo.push_to_hub( + commit_message=f"Training in progress epoch {epoch}", blocking=False + ) +``` + +```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} + +## 使用微调后的模型 + +我们已经向您展示了如何将我们在模型中心微调的模型与推理小部件一起使用。 要在“管道”中本地使用它,我们只需要指定正确的模型标识符: + +```py +from transformers import pipeline + +# Replace this with your own checkpoint +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”,而是将其翻译成法语官方版本。 “”的翻译也是一样的: + +```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."}] +``` + +风格适应的另一个很好的例子! + + + +✏️ **轮到你了!** “电子邮件”这个词在模型返回了什么? + + diff --git a/chapters/zh-CN/chapter7/5.mdx b/chapters/zh-CN/chapter7/5.mdx new file mode 100644 index 000000000..40c7111c5 --- /dev/null +++ b/chapters/zh-CN/chapter7/5.mdx @@ -0,0 +1,1047 @@ + + +# 提取文本摘要 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + +在本节中,我们将看看如何使用 Transformer 模型将长文档压缩为摘要,这项任务称为文本摘要.这是最具挑战性的 NLP 任务之一,因为它需要一系列能力,例如理解长篇文章和生成能够捕捉文档中主要主题的连贯文本。但是,如果做得好,文本摘要是一种强大的工具,可以减轻领域专家详细阅读长文档的负担,从而加快各种业务流程。 + + + +尽管在[Hugging Face Hub](https://huggingface.co/models?pipeline_tag=summarization=downloads)上已经存在各种微调模型用于文本摘要,几乎所有这些都只适用于英文文档。因此,为了在本节中添加一些变化,我们将为英语和西班牙语训练一个双语模型。在本节结束时,您将有一个可以总结客户评论的[模型](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es)。 + + + + +如下所示:正如我们将看到的,这些摘要很简洁,因为它们是从客户在产品评论中提供的标题中学到的。让我们首先为这项任务准备一个合适的双语语料库。 + +## 准备多语言语料库 + +我们将使用[多语言亚马逊评论语料库](https://huggingface.co/datasets/amazon_reviews_multi)创建我们的双语摘要器。该语料库由六种语言的亚马逊产品评论组成,通常用于对多语言分类器进行基准测试。然而,由于每条评论都附有一个简短的标题,我们可以使用标题作为我们模型学习的目标摘要!首先,让我们从 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** 列。让我们通过创建一个简单的函数来查看一些示例,该函数使用我们在[第五章](/course/chapter5)学到过: + +```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.' +``` + + + +✏️ **试试看!** 更改 `Dataset.shuffle()` 命令中的随机种子以探索语料库中的其他评论。 如果您是说西班牙语的人,请查看 `spanish_dataset` 中的一些评论,看看标题是否也像合理的摘要。 + + + +此示例显示了人们通常在网上找到的评论的多样性,从正面到负面(以及介于两者之间的所有内容!)。尽管标题为“meh”的示例信息量不大,但其他标题看起来像是对评论本身的体面总结。在单个 GPU 上训练所有 400,000 条评论的摘要模型将花费太长时间,因此我们将专注于为单个产品领域生成摘要。为了了解我们可以选择哪些域,让我们将 **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 +``` + +英语数据集中最受欢迎的产品是家居用品、服装和无线电子产品。不过,为了坚持亚马逊的主题,让我们专注于总结书籍的评论——毕竟,这是亚马逊这家公司成立的基础!我们可以看到两个符合要求的产品类别( **book** 和 **digital_ebook_purchase** ),所以让我们为这些产品过滤两种语言的数据集。正如我们在[第五章](/course/chapter5)学到的, 这 **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) + +# Peek at a few examples +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 模型! + +## 文本摘要模型 + +如果你仔细想想,文本摘要是一种类似于机器翻译的任务:我们有一个像评论这样的文本正文,我们希望将其“翻译”成一个较短的版本,以捕捉输入的显着特征。因此,大多数用于文本摘要的 Transformer 模型采用了我们在[第一章](/course/chapter1)遇到的编码器-解码器架构。尽管有一些例外,例如 GPT 系列模型,它们在few-shot(少量微调)之后也可以提取摘要。下表列出了一些流行的预训练模型,可以对其进行微调以进行汇总。 + +| Transformer 模型 | 描述 | 多种语言? | +| :---------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------: | +| [GPT-2](https://huggingface.co/gpt2-xl) | 虽然训练为自回归语言模型,但您可以通过在输入文本末尾附加“TL;DR”来使 GPT-2 生成摘要。 | ❌ | +| [PEGASUS](https://huggingface.co/google/pegasus-large) | 在预训练是的目标是来预测多句子文本中的屏蔽句子。 这个预训练目标比普通语言建模更接近文本摘要,并且在流行的基准测试中得分很高。 | ❌ | +| [T5](https://huggingface.co/t5-base) | 通用的 Transformer 架构,在文本到文本的框架中制定所有任务; 例如,模型文本摘要的输入格式是`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 的有趣架构,在文本到文本框架中进行了预训练。在 T5 中,每个 NLP 任务都是根据提示前缀来制定的,例如 **summarize:** 这使模型使生成的文本适应提示。如下图所示,这让 T5 变得非常通用,因为你可以用一个模型解决很多任务! + + +
+Different tasks performed by the T5 architecture. + +
+ +mT5 不使用前缀,但具有 T5 的大部分功能,并且具有多语言的优势。现在我们已经选择了一个模型,让我们来看看准备我们的训练数据。 + + + +✏️ **试试看!** 完成本节后,通过使用相同的技术对 mBART 进行微调,看看 mT5 与 mBART 相比有多好。 对于奖励积分,您还可以尝试仅在英文评论上微调 T5。 由于 T5 需要一个特殊的前缀提示,因此您需要在下面的预处理步骤中将“summarize:”添加到输入示例中。 + + + +## 预处理数据 + + + +我们的下一个任务是对我们的评论及其标题进行标记和编码。像往常一样,我们首先加载与预训练模型检查点相关的标记器。我们将使用 **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]} +``` + +在这里我们可以看到我们在[第三章](/course/chapter3)第一次微调实验中遇到的熟悉的 **input_ids** 和 **attention_mask** .让我们用分词器解码这些输入 ID ,可以**convert_ids_to_tokens()** 函数来查看我们正在处理什么样的标记器: + +```python +tokenizer.convert_ids_to_tokens(inputs.input_ids) +``` + +```python out +['▁I', '▁', 'loved', '▁reading', '▁the', '▁Hung', 'er', '▁Games', ''] +``` + +特殊的 Unicode 字符 `▁` 和序列结束标记 `` 表明我们正在处理 SentencePiece 分词器,它基于在[第六章](/course/chapter6)中讨论的Unigram分词算法. Unigram 对多语言语料库特别有用,因为它允许 SentencePiece 不知道重音、标点符号以及许多语言(如日语)没有空格字符。 + +为了标记我们的语料库,我们必须处理与摘要相关的细节:因为我们的标签也是文本,它们可能会超过模型的最大上下文大小。这意味着我们需要对评论及其标题进行截断,以确保我们不会将过长的输入传递给我们的模型。 🤗 Transformers 中的分词器提供了一个漂亮的 **as_target_tokenizer()** 函数,它允许您并行分词并标记标签的函数。这通常是使用预处理函数内的上下文管理器完成的,该函数首先对输入进行编码,然后将标签编码为单独的列。以下是 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 + ) + # Set up the tokenizer for targets + with tokenizer.as_target_tokenizer(): + 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()** 我们可以看到评论首先被标记化,然后是标题在 **as_target_tokenizer()** 函数里也做了相同的处理. + +有了 `preprocess_function()`,我们在整个课程中广泛使用的方便的 `Dataset.map()` 函数来标记整个语料库是一件简单的事情: + +```python +tokenized_datasets = books_dataset.map(preprocess_function, batched=True) +``` + +既然语料库已经预处理完毕,我们来看看一些常用的摘要指标。正如我们将看到的,在衡量机器生成的文本的质量方面没有灵丹妙药。 + + + +💡 你可能已经注意到我们在上面的 `Dataset.map()` 函数中使用了 `batched=True`。 这会以 1,000 个(默认)为单位对示例进行编码,并允许您利用 🤗 Transformers 中快速标记器的多线程功能。 在可能的情况下,尝试使用 `batched=True` 来加速您的预处理! + + + + +## 文本摘要的指标 + + + +与我们在本课程中涵盖的大多数其他任务相比,衡量文本生成任务(如摘要或翻译)的性能并不那么简单。例如,对于“我喜欢阅读饥饿游戏”这样的评论,有多个有效摘要,例如“我喜欢饥饿游戏”或“饥饿游戏是一本好书”。显然,在生成的摘要和标签之间应用某种精确匹配并不是一个好的解决方案——即使是人类在这样的指标下也会表现不佳,因为我们都有自己的写作风格。 + +总而言之,最常用的指标之一是[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_ 分数。。 + + + +🙋 如果这是您第一次听说精确率和召回率,请不要担心——我们将一起通过一些明确的示例来说明一切。 这些指标通常在分类任务中遇到,因此如果您想了解在该上下文中如何定义精确度和召回率,我们建议查看 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}} $$ + +对于我们上面的简单例子,这个公式给出了 6/6 = 1 的完美召回率;即,参考摘要中的所有单词都已由模型生成。这听起来可能很棒,但想象一下,如果我们生成的摘要是“我真的很喜欢整晚阅读饥饿游戏”。这也将有完美的recall,但可以说是一个更糟糕的总结,因为它很冗长。为了处理这些场景,我们还计算了pecision,它在 ROUGE 上下文中衡量生成的摘要中有多少是相关的: + +$$ \mathrm{Precision} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, generated\, summary}} $$ + +将此应用到我们的详细摘要中会得到 6/10 = 0.6 的精度,这比我们较短的摘要获得的 6/7 = 0.86 的精度要差得多。在实践中,通常计算精度和召回率,然后报告 F1-score(精度和召回率的调和平均值)。我们可以在 🤗 Datasets 中通过安装 **rouge_score** 包来计算他们: + +```py +!pip install rouge_score +``` + +然后按如下方式加载 ROUGE 指标: + +```python +from datasets import load_metric + +rouge_score = load_metric("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实际上计算了精度、召回率和 F1 分数的置信区间;这些是你可以在这里看到的 **low** , **mid** , 和 **high** 属性。此外,🤗 Datasets在比较生成摘要和参考摘要时,会根据不同类型的文本粒度计算各种 ROUGE 分数。这 **rouge1** 变体是一元组的重叠——这只是表达单词重叠的一种奇特方式,这正是我们上面讨论的度量标准。为了验证这一点,让我们输出 **mid** 的数值: + +```python +scores["rouge1"].mid +``` + +```python out +Score(precision=0.86, recall=1.0, fmeasure=0.92) +``` +太好了,准确率和召回率匹配了!那么其他的 ROUGE 分数呢? **rouge2** 测量二元组之间的重叠(想想单词对的重叠),而 **rougeL** 和 **rougeLsum** 通过在生成的和参考摘要中查找最长的公共子串来测量最长的单词匹配序列。中的“总和” **rougeLsum** 指的是这个指标是在整个摘要上计算的,而 **rougeL** 计算为单个句子的平均值。 + + + + ✏️ **试试看!** 创建您自己的生成和参考摘要示例,并查看生成的 ROUGE 分数是否与基于精确度和召回率公式的手动计算一致。 对于附加分,将文本拆分为二元组并比较“rouge2”指标的精度和召回率。 + + + +我们将使用这些 ROUGE 分数来跟踪我们模型的性能,但在此之前,让我们做每个优秀的 NLP 从业者都应该做的事情:创建一个强大而简单的baseline! + +### 创建强大的baseline + +文本摘要的一个常见基线是简单地取一篇文章的前三个句子,通常称为 _lead-3_ 基线。 我们可以使用句号(英文使用.)来跟踪句子边界,但这在"U.S." or "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.' +``` + +这似乎有效,所以让我们现在实现一个函数,从数据集中提取这些“摘要”并计算baseline的 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 baseline过于冗长。 现在我们有了一个很好的基准,让我们将注意力转向微调 mT5! + +{#if fw === 'pt'} + +## 使用 `Trainer` API微调mT5 + +微调模型以进行提取摘要与我们在本章中介绍的其他任务非常相似。 我们需要做的第一件事是从`mt5-small`检查点加载预训练模型。 由于摘要提取是一个序列到序列的任务,我们可以使用 AutoModelForSeq2SeqLM 类加载模型,该类会自动下载并缓存权重: + +```python +from transformers import AutoModelForSeq2SeqLM + +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{:else} + +## 使用 `Keras` API微调mT5 + +微调模型以进行提取摘要与我们在本章中介绍的其他任务非常相似。 我们需要做的第一件事是从`mt5-small`检查点加载预训练模型。 由于摘要提取是一个序列到序列的任务,我们可以使用 AutoModelForSeq2SeqLM 类加载模型,该类会自动下载并缓存权重: + +```python +from transformers import TFAutoModelForSeq2SeqLM + +model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{/if} + + + +💡 If you're wondering why you don't see any warnings about fine-tuning the model on a downstream task, that's because for sequence-to-sequence tasks we keep all the weights of the network. Compare this to our text classification model in [Chapter 3](/course/chapter3), where the head of the pretrained model was replaced with a randomly initialized network. +💡 如果您想知道为什么在下游任务中没有看到任何关于微调模型的警告,那是因为对于序列到序列的任务,我们保留了网络的所有权重。与我们在[第三章] (/course/chapter3)中的文本分类模型进行比较,文本分类模型预训练模型的头部被随机初始化的网络替换。 + + + +我们需要做的下一件事是登录 Hugging Face Hub。如果您在notebook中运行此代码,则可以使用以下实用程序函数执行此操作: + +```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 +# Show the training loss with every epoch +logging_steps = len(tokenized_datasets["train"]) // batch_size +model_name = model_checkpoint.split("/")[-1] + +args = Seq2SeqTrainingArguments( + output_dir=f"{model_name}-finetuned-amazon-en-es", + evaluation_strategy="epoch", + learning_rate=5.6e-5, + per_device_train_batch_size=batch_size, + per_device_eval_batch_size=batch_size, + weight_decay=0.01, + save_total_limit=3, + num_train_epochs=num_train_epochs, + predict_with_generate=True, + logging_steps=logging_steps, + push_to_hub=True, +) +``` + +在这里, **predict_with_generate** 参数已设置为True表明我们应该在评估期间生成摘要,以便我们可以计算每个时期的 ROUGE 分数。正如在[第一章](/course/chapter1)所讨论的,解码器通过逐个预测令牌来执行推理,这是由模型的 **generate()** 方法实现的。设置 **predict_with_generate=True** 告诉 **Seq2SeqTrainer** 使用该方法进行评估。我们还调整了一些默认的超参数,例如学习率、epoch数和权重衰减,并且我们设置了 **save_total_limit** 训练期间最多只保存 3 个检查点的选项——这是因为即使是 mT5 的“small”版本也使用大约 1 GB 的硬盘空间,我们可以通过限制我们保存的副本数量来节省一点空间。 + +`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 分数之前将输出和标签解码为文本。 下面的函数正是这样做的,并且还利用 `nltk` 中的 `sent_tokenize()` 函数来用换行符分隔摘要语句: + +```python +import numpy as np + + +def compute_metrics(eval_pred): + predictions, labels = eval_pred + # Decode generated summaries into text + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + # Replace -100 in the labels as we can't decode them + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + # Decode reference summaries into text + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + # ROUGE expects a newline after each sentence + decoded_preds = ["\n".join(sent_tokenize(pred.strip())) for pred in decoded_preds] + decoded_labels = ["\n".join(sent_tokenize(label.strip())) for label in decoded_labels] + # Compute ROUGE scores + result = rouge_score.compute( + predictions=decoded_preds, references=decoded_labels, use_stemmer=True + ) + # Extract the median scores + result = {key: value.mid.fmeasure * 100 for key, value in result.items()} + return {k: round(v, 4) for k, v in result.items()} +``` + +{/if} + +接下来,我们需要为我们的序列到序列任务定义一个数据整理器。由于 mT5 是一个编码器-解码器 Transformer 模型,准备我们的批次的一个微妙之处是,在解码过程中,我们需要将标签向右移动一个。 这是为了确保解码器只看到之前的真实的标签,而不是当前或未来的标签,这对于模型来说很容易记忆。 这类似于在 [因果语言建模](/course/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 +) +``` + +由于 collator 需要一个 `dict` 的列表,其中每个 `dict` 代表数据集中的一个示例,我们还需要在将数据传递给 data collator 之前将数据整理成预期的格式: + +```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]` 标记(其 ID 是 ` 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 分数随着每个 epoch 增加。训练完成后,您可以通过运行**Trainer.evaluate()** 查看最终的 ROUGE 分数 : + +```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 baseline——很好!最后要做的是将模型权重推送到 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()** 是 Git 提交哈希的 URL,因此您可以轻松查看对模型存储库所做的更改! + +在结束本节之前,让我们看一下如何使用 🤗 Accelerate 提供的底层API对 mT5 进行微调。 + +{:else} + +我们几乎准备好训练了! 我们只需要使用我们上面定义的数据整理器将我们的数据集转换为 tf.data.Dataset ,然后 `compile()` 和 `fit()` 模型。 首先,转换数据集: +```python +tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=8, +) +tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=8, +) +``` + +现在,我们定义训练超参数并编译: + +```python +from transformers import create_optimizer +import tensorflow as tf + +# 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_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) + +# Train in mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +最后,我们拟合模型。 我们在每个 epoch 之后使用`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 +) +``` + +我们在训练期间输出了一些loss,但实际上我们希望看到我们之前计算的 ROUGE 指标。 要获得这些指标,我们需要从模型生成输出并将它们转换为字符串。 让我们为 ROUGE 指标构建一些标签和预测列表以进行比较(请注意,如果您在本节中遇到import的错误,您可能需要`!pip install tqdm`): + +```python +from tqdm import tqdm +import numpy as np + +all_preds = [] +all_labels = [] +for batch in tqdm(tf_eval_dataset): + predictions = model.generate(**batch) + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + labels = batch["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'} + +## 使用 🤗 Accelerate 微调 mT5 + +使用 🤗 Accelerate 微调我们的模型与我们在 [Chapter 3](/course/chapter3) 中遇到的文本分类示例非常相似。 主要区别在于需要在训练期间显式生成摘要并定义我们如何计算 ROUGE 分数(回想一下,`Seq2SeqTrainer` 为我们生成了摘要)。 让我们看看我们如何在 🤗 Accelerate 中实现这两个要求! + +### 为训练做好一切准备 + +The first thing we need to do is create a `DataLoader` for each of our splits. Since the PyTorch dataloaders expect batches of tensors, we need to set the format to `"torch"` in our datasets: +我们需要做的第一件事是为每个数据集的每一个拆分创建一个`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 上进行训练,则需要将上述所有代码移动到专门的训练函数中。有关详细信息,请参阅[第三章](/course/chapter3)。 + + + +现在我们已经准备好了我们索要用的对象,还有三件事要做: + +* 定义学习率调度计划。 +* 实现一个功能来对摘要进行后续处理以进行评估。 +* 在 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 expects a newline after each sentence + preds = ["\n".join(nltk.sent_tokenize(pred)) for pred in preds] + labels = ["\n".join(nltk.sent_tokenize(label)) for label in labels] + + return preds, labels +``` + +如果你还记得我们是如何定义 `Seq2SeqTrainer` 的 `compute_metrics()` 函数的,这对你来说应该很熟悉。 + +最后,我们需要在 Hugging Face Hub 上创建一个模型存储库。 为此,我们可以使用🤗 Hub 库的get_full_repo_name。 我们只需要为我们的存储库定义一个名称,该库有一个非常好用的函数可以将存储库 ID 与用户配置文件结合起来: +```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) +``` +这将允许我们在训练期间通过调用 `repo.push_to_hub()` 方法将模型推送到 Hub! 现在让我们通过写出完整的训练循环来结束我们的分析。 + +### 训练循环 + +文本摘要的训练循环与我们遇到的其他 🤗 Accelerate 示例非常相似,大致分为四个主要步骤:这 + +1. 通过在每个epoch 迭代 `train_dataloader` 中的所有示例来训练模型。 +2. 在每个 epoch 结束时生成模型摘要,首先生成标记,然后将它们(和参考摘要)解码为文本。 +3. 使用我们之前看到的相同技术计算 ROUGE 分数。 +4. 保存检查点并将所有内容推送到 Hub。 在这里,我们依赖 `Repository` 对象的巧妙的 `blocking=False` 参数,以便我们可以在每个 epoch 异步地上传检查点。 这使我们能够继续训练,而不必等待与 GB 大小的模型慢呼呼的上传! + +这些步骤可以在以下代码块中看到: + +```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): + # Training + model.train() + for step, batch in enumerate(train_dataloader): + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) + + # Evaluation + model.eval() + for step, batch in enumerate(eval_dataloader): + with torch.no_grad(): + generated_tokens = accelerator.unwrap_model(model).generate( + batch["input_ids"], + attention_mask=batch["attention_mask"], + ) + + generated_tokens = accelerator.pad_across_processes( + generated_tokens, dim=1, pad_index=tokenizer.pad_token_id + ) + labels = batch["labels"] + + # If we did not pad to max length, we need to pad the labels too + labels = accelerator.pad_across_processes( + batch["labels"], dim=1, pad_index=tokenizer.pad_token_id + ) + + generated_tokens = accelerator.gather(generated_tokens).cpu().numpy() + labels = accelerator.gather(labels).cpu().numpy() + + # Replace -100 in the labels as we can't decode them + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + if isinstance(generated_tokens, tuple): + generated_tokens = generated_tokens[0] + decoded_preds = tokenizer.batch_decode( + generated_tokens, skip_special_tokens=True + ) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + decoded_preds, decoded_labels = postprocess_text( + decoded_preds, decoded_labels + ) + + rouge_score.add_batch(predictions=decoded_preds, references=decoded_labels) + + # Compute metrics + result = rouge_score.compute() + # Extract the median ROUGE scores + result = {key: value.mid.fmeasure * 100 for key, value in result.items()} + result = {k: round(v, 4) for k, v in result.items()} + print(f"Epoch {epoch}:", result) + + # Save and upload + accelerator.wait_for_everyone() + unwrapped_model = accelerator.unwrap_model(model) + unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) + if accelerator.is_main_process: + tokenizer.save_pretrained(output_dir) + repo.push_to_hub( + commit_message=f"Training in progress epoch {epoch}", blocking=False + ) +``` + +```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} + +## 使用您微调的模型 + +将模型推送到 Hub 后,您可以通过推理小部件或“管道”对象来使用它,如下所示: + +```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' +``` + +摘要翻译成了英文的“非常容易阅读”,在这种情况下,我们可以看到它是直接从评论中提取的。 这显示了 mT5 模型的多功能性,并让您体验了处理多语言语料库的感觉! + +接下来,我们将把注意力转向稍微复杂的任务:从头开始训练语言模型。 diff --git a/chapters/zh-CN/chapter7/6.mdx b/chapters/zh-CN/chapter7/6.mdx new file mode 100644 index 000000000..b8a5a8ede --- /dev/null +++ b/chapters/zh-CN/chapter7/6.mdx @@ -0,0 +1,906 @@ + + +# 从头开始训练因果语言模型 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +到目前为止,我们主要使用预训练模型,并通过重用预训练的权重来针对新用例对它们进行微调。正如我们在[第一章](/course/chapter1), 这通常称为迁移学习,这是将 Transformer 模型应用于大多数标记数据稀疏的现实世界用例的非常成功的策略。在本章中,我们将采用不同的方法并从头开始训练一个全新的模型。如果您有大量数据并且它与用于可用模型的预训练数据有很大不同,那么这是一个很好的方法。然而,它也需要更多的计算资源来预训练语言模型,而不仅仅是微调现有的模型。训练新模型有意义的示例包括由音符、分子序列(如 DNA)或编程语言组成的数据集。后者最近受到关注,这要归功于 TabNine 和 GitHub 的 Copilot 等工具,它们由 OpenAI 的 Codex 模型提供支持,可以生成长代码序列。这种文本生成任务最好使用自回归或因果语言模型(例如 GPT-2)来解决。 + +在本节中,我们将构建代码生成模型的缩小版本:我们将使用 Python 代码的子集专注于单行完成而不是完整的函数或类。在 Python 中处理数据时,您会经常接触 Python 数据科学堆栈,包括 `matplotlib` , `seaborn` , `pandas` , 和 `scikit-learn` 库。在使用这些框架时,通常需要查找特定的命令,因此如果我们可以使用模型来为我们完成这些调用,那就太好了。 + + + +在[第六章](/course/chapter6) 我们创建了一个高效的分词器来处理 Python 源代码,但我们仍然需要一个大规模数据集来预训练模型。在这里,我们将我们的分词器应用到源自 GitHub 存储库的 Python 代码语料库。然后我们将使用 `Trainer` API 和 🤗 Accelerate 来训练模型。让我们开始吧! + + + + +这实际上展示了使用本节中训练并上传到 Hub 的模型。你可以在[这里](https://huggingface.co/huggingface-course/codeparrot-ds?text=plt.imshow%28)找到。请注意,由于在文本生成过程中发生了一些随机化,您可能会得到略有不同的结果。 +## 收集数据 + +Python 代码可以从 GitHub 等代码存储库中获得,我们可以通过抓取每个 Python 存储库来使用它们来创建数据集。这是在[Transformers textbook](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/)预训练大型的GPT-2 模型。使用大约 180 GB 的 GitHub 转储,其中包含大约 2000 万个 Python 文件,称为 `codeparrot` ,作者构建了一个数据集,然后在[Hugging Face Hub](https://huggingface.co/datasets/transformersbook/codeparrot)上分享出来了. + +然而,对完整语料库的训练既耗时又费力,我们只需要与 Python 数据科学堆栈相关的数据集子集。所以,让我们开始过滤 `codeparrot` 包含此堆栈中任何库的所有文件的数据集。由于数据集的太大,我们希望避免下载它;因此反,我们将使用流功能来动态过滤它。为了使用前面提到的库过滤代码示例,我们将使用以下函数: + +```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 +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 +# This cell will take a very long time to execute, so you should skip it and go to +# the next one! +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 GB,包含 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="train") + +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` 字段包含我们希望我们的模型训练的代码。现在我们有了一个数据集,我们需要预处理文本,使其采用适合预训练的格式。 + +## 准备数据集 + + + +第一步是对数据进行标记,以便我们可以将其用于训练。由于我们的目标主要是自动完成短函数调用,因此我们可以保持上下文大小相对较小。这样做的好处是我们可以更快地训练模型并且它需要的内存显着减少。如果您的应用程序拥有更多上下文很重要(例如,如果您希望模型基于具有函数定义的文件编写单元测试),请确保增加该数量,但请记住,这需要更大的 GPU 内存占用。现在,让我们将上下文大小固定为 128 个标记,而不是 GPT-2 或 GPT-3 中分别使用的 1,024 或 2,048 个标记。 + + +大多数文档包含超过 128 个标记,因此简单地将输入截断到最大长度将消除我们数据集的很大一部分。相反,我们将使用 `return_overflowing_tokens` 标记整个输入并将其分成几个块的选项,就像我们在[第六章](/course/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` 字段,我们还可以重建哪些块属于哪些输入样本。 + +通过这个操作,我们使用了一个方便的🤗 Datasets 中的` Dataset.map()` 函数,就是不需要一对一的映射;正如我们在[第三节](/course/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 + }) +}) +``` + +我们现在有 1670 万个示例,每个示例有 128 个tokens ,总共相当于大约 21 亿个tokens 。作为参考,OpenAI 的 GPT-3 和 Codex 模型分别在 300 和 1000 亿个tokens 上训练,其中 Codex 模型从 GPT-3 检查点初始化。我们在本节中的目标不是与这些模型竞争,这些模型可以生成长而连贯的文本,而是创建一个缩小版本,为数据科学家提供快速自动完成功能。 + +现在我们已经准备好了数据集,让我们设置模型! + + + + +✏️ **试试看!** 摆脱所有小于上下文大小的块在这里并不是什么大问题,因为我们使用的是小上下文窗口。随着上下文大小的增加(或者如果您有一个短文档语料库),被丢弃的块的比例也会增加。准备数据的更有效方法是将所有标记化的样本加入一个批次中,每个语料之间有一个`eos_token_id` 标记, 然后对连接的序列执行分块。作为练习,修改 `tokenize()`函数以使用该方法。请注意,您需要设置`truncation=False` 和删除标记生成器中的其他参数以获取完整的标记 ID 序列。 + + + + +## 初始化新模型 + +我们的第一步是新初始化一个 GPT-2 模型。我们将对我们的模型使用与小型 GPT-2 模型相同的配置,因此我们加载预训练配置,确保分词器大小与模型词汇量大小匹配并设置 `bos` 和 `eos` (序列的开始和结束)令牌 ID: + +{#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) # Builds the model +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} + +我们的模型有 1.24 亿个参数,我们必须对其进行调整。在开始训练之前,我们需要设置一个负责创建批次的数据整理器。我们可以使用 `DataCollatorForLanguageModeling` ,它是专为语言建模而设计(顾名思义)。除了堆叠和填充批次,它还负责创建语言模型标签——在因果语言建模中,输入也用作标签(只是移动了一个元素),并且这个数据整理器在训练期间即时创建它们,所以我们不需要复制 `input_ids`。 + +注意 `DataCollatorForLanguageModeling` 支持掩码语言建模 (MLM) 和因果语言建模 (CLM)。默认情况下它为 MLM 准备数据,但我们可以通过设置`mlm=False`参数切换到 CLM : + +{#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_dataset["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'} + +现在,我们可以使用`to_tf_dataset()`方法,使用上面创建的数据整理器将数据集转换为TensorFlow数据集: + +```python +tf_train_dataset = tokenized_dataset["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) +tf_eval_dataset = tokenized_dataset["valid"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=32, +) +``` + +{/if} + + + +⚠️ 移动输入和标签以对齐它们发生在模型内部,因此数据整理器只需复制输入以创建标签。 + + + + +现在我们已经准备好实际训练我们的模型的一切了——毕竟这不是那么多工作!在我们开始训练之前,我们应该登录 Hugging Face。如果您在笔记本上工作,则可以使用以下实用程序功能: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +这将显示一个小部件,您可以在其中输入您的 Hugging Face 登录凭据。 + +如果您不是在notebook上工作,只需在终端中输入以下行: + +```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) + +# Train in mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +现在我们可以调用`model.fit(),`并等待训练完成。你是在完整的训练集还是他的子集上运行,这将分别需要20和2个小时,所以拿一些咖啡和一本好书来阅读!训练完成后,我们可以将模型和分词器推送到中心: + +```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} + + + +✏️ **试试看!** 除了`TrainingArguments` 之外,我们只需要大约30行代码就可以从原始文本到训练GPT-2。 用你自己的数据集试试看,看看你能不能得到好的结果! + + + + + +{#if fw === 'pt'} + +💡 如果您可以访问具有多个 GPU 的机器,请尝试在那里运行代码。 `Trainer`自动管理多台机器,这可以极大地加快训练速度。 + +{:else} + +💡 如果您有权访问具有多个 GPU 的计算机,则可以尝试使用 `MirroredStrategy` 上下文来大幅加快训练速度。您需要创建一个`tf.distribute.MirroredStrategy`对象,并确保 `to_tf_dataset` 命令以及模型创建和对 `fit()`的调用都在其 `scope()` context. 上下文中运行。您可以查看有关此内容的文档[here](https://www.tensorflow.org/guide/distributed_training#use_tfdistributestrategy_with_keras_modelfit). + +{/if} + + + +## 使用管道生成代码 + +现在是关键的部分:让我们看看经过训练的模型的实际效果如何!我们可以在日志中看到损失稳步下降,但为了让模型进行测试,让我们看看它在某些测试上的表现如何。为此,我们将模型包装在文本生成中的`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} + +让我们从创建散点图的简单任务开始: + +```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` 。由于生成的token数量有限,以下 `for` 循环被切断。让我们看看我们是否可以做一些更复杂的事情并让模型帮助我们分组操作: + +```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` 并建立一个随机森林模型: + +```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数据科学堆栈的一些语法。当然,在将模型部署到现实世界之前,我们需要更彻底地评估模型,但这仍然是一个令人印象深刻的原型。 + +{:else} + +从这几个例子来看,模型似乎已经学习了 Python 数据科学堆栈的一些语法(当然,在将模型部署到现实世界之前,我们需要对其进行更全面的评估)。然而,有时需要对模型训练进行更多定制才能实现给定用例的必要性能。例如,如果我们想动态更新批量大小或有一个条件训练循环来即时跳过坏示例怎么办?一种选择是将 `Trainer` 并添加必要的更改,但有时从头开始编写训练循环会更简单。这就是🤗 Accelerate 的用武之地。 + +{/if} + +{#if fw === 'pt'} + +## 用 🤗 Accelerate 训练 + +我们已经看到了如何使用 `Trainer` ,这可以允许一些自定义。然而,有时我们想要完全控制训练循环,或者我们想要进行一些奇特的更改。在这种情况下 🤗 Accelerate 是一个不错的选择,在本节中,我们将逐步介绍使用它来训练我们的模型的步骤。为了让事情变得更有趣,我们还将在训练循环中添加一些修改。 + + + +由于我们主要对数据科学库的合理自动填充感兴趣,因此对更多使用这些库的训练样本给予更多权重是有意义的。我们可以通过使用关键字轻松识别这些示例,例如 `plt`、`pd`、`sk`、`fit`和`predict`等关键字,我们可以很容易地识别这些示例,这些关键字是matplotlib最常用的导入名称。`Pyplot`, `pandas`和`sklearn`以及后者的拟合/预测模式。如果这些都表示为单个标记,我们可以轻松检查它们是否出现在输入序列中。标记可能有一个空格前缀,因此我们还将在标记器词汇表中检查这些版本。为了验证它是否有效,我们将添加一个测试token ,该token 应拆分为多个tokens: + +```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' +``` + +太好了,这似乎很好用!我们现在可以编写一个自定义损失函数,它将输入序列、logits 和我们刚刚选择的关​​键标记作为输入。首先,我们需要对齐 logits 和输入:向右移动一个的输入序列形成标签,因为下一个标记是当前标记的标签。我们可以通过从输入序列的第二个标记开始标记来实现这一点,因为模型无论如何都不会对第一个标记进行预测。然后我们切断最后一个 logit,因为我们没有完整输入序列之后的标记的标签。有了这个,我们可以计算每个样本的损失并计算每个样本中所有关键字的出现次数。最后,我们使用出现次数作为权重计算所有样本的加权平均值。由于我们不想扔掉所有没有关键字的样本,我们将权重加1: + +```py +from torch.nn import CrossEntropyLoss +import torch + + +def keytoken_weighted_loss(inputs, logits, keytoken_ids, alpha=1.0): + # Shift so that tokens < n predict n + shift_labels = inputs[..., 1:].contiguous() + shift_logits = logits[..., :-1, :].contiguous() + # Calculate per-token loss + loss_fct = CrossEntropyLoss(reduce=False) + loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1)) + # Resize and average loss per sample + loss_per_sample = loss.view(shift_logits.size(0), shift_logits.size(1)).mean(axis=1) + # Calculate and scale weighting + weights = torch.stack([(inputs == kt).float() for kt in keytoken_ids]).sum( + axis=[0, 2] + ) + weights = alpha * (1.0 + weights) + # Calculate weighted average + weighted_loss = (loss_per_sample * weights).mean() + return weighted_loss +``` + +在我们开始使用这个很棒的新损失函数进行训练之前,我们需要准备一些东西: + +- 我们需要数据加载器来批量加载数据。 +- 我们需要设置权重衰减参数。 +- 有时我们想要求值,因此将求值代码包装在一个函数中是有意义的。 + +让我们从数据加载器开始。我们只需要将数据集的格式设置为 `"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) +``` + +接下来,我们对参数进行分组,以便优化器知道哪些将获得额外的权重衰减。通常,所有偏差和 LayerNorm 权重项都不受此限制;以下我们如何做到这一点: + +```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()` 函数我们定期可以获取损失值和[perplexity](/course/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 章](/course/chapter3) for more details. + + + +现在我们已经发送了我们的 `train_dataloader`到 `accelerator.prepare()` ,我们可以使用它的长度来计算训练步骤的数量。请记住,我们应该始终在准备好dataloader后执行此操作,因为该方法会改变其长度。我们使用经典线性学习率调度: + +```py +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` 工作文件夹中的对象。如果您尚未登录,请先登录 Hugging Face。我们将从我们想要为模型提供的模型 ID 中确定存储库名称(您可以自由地用自己的选择替换 `repo_name` ;它只需要包含您的用户名,可以使用`get_full_repo_name()`函数的查看目前的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()` 方法。这将帮助我们在每个 epoch 结束时上传中间模型。在我们训练之前,让我们运行一个快速测试,看看评估函数是否正常工作: + +```py +evaluate() +``` + +```python out +(10.934126853942871, 56057.14453125) +``` + +这些损失和困惑度的值非常高,但这并不奇怪,因为我们还没有训练过模型。有了这个,我们已经准备好编写训练脚本的核心部分:训练循环。在训练循环中,我们遍历数据加载器并将批次传递给模型。有了 logits,我们就可以评估我们的自定义损失函数。我们通过梯度累积步骤的数量来缩放损失,以便在聚合更多步骤时不会产生更大的损失。在我们优化之前,我们还剪辑了梯度以获得更好的收敛性。最后,每隔几步,我们就会使用新的 `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=len(train_dataloader) + ): + logits = model(batch["input_ids"]).logits + loss = keytoken_weighted_loss(batch["input_ids"], logits, keytoken_ids) + if step % 100 == 0: + accelerator.print( + { + "lr": get_lr(), + "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 等工具记录重要指标。向训练循环添加适当的日志记录,以便您始终可以检查训练的进行情况。going. + + + +{/if} \ No newline at end of file diff --git a/chapters/zh-CN/chapter7/7.mdx b/chapters/zh-CN/chapter7/7.mdx new file mode 100644 index 000000000..3874bc60f --- /dev/null +++ b/chapters/zh-CN/chapter7/7.mdx @@ -0,0 +1,1210 @@ + + +# 问答 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +是时候看问答了! 这项任务有多种形式, 但我们将在本节中关注的一项称为*提取*的问答。问题的答案就在 _给定的文档_ 之中。 + + + +我们将使用 [SQuAD 数据集](https://rajpurkar.github.io/SQuAD-explorer/) 微调一个BERT模型, 其中包括群众工作者对一组维基百科文章提出的问题。以下是一个小的测试样例: + + + + +本节使用的代码已经上传到了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 这样的纯编码器模型往往很擅长提取诸如 "谁发明了 Transformer 架构?"之类的事实性问题的答案。但在给出诸如 "为什么天空是蓝色的?" 之类的开放式问题时表现不佳。在这些更具挑战性的情况下, T5 和 BART 等编码器-解码器模型通常使用以与 [文本摘要](/course/chapter7/5) 非常相似的方式合成信息。如果你对这种类型的*生成式*问答感兴趣, 我们建议您查看我们基于 [ELI5 数据集](https://huggingface.co/datasets/eli5) 的 [演示](https://yjernite.github.io/lfqa.html)。 + + + +## 准备数据 + +最常用作抽取式问答的学术基准的数据集是 [SQuAD](https://rajpurkar.github.io/SQuAD-explorer/), 所以这就是我们将在这里使用的。还有一个更难的 [SQuAD v2](https://huggingface.co/datasets/squad_v2) 基准, 其中包括没有答案的问题。只要你自己的数据集包含上下文列、问题列和答案列, 你就应该能够调整以下步骤。 + +### SQuAD 数据集 + +像往常一样, 我们只需一步就可以下载和缓存数据集, 这要归功于 `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 处的样本e: + +```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?' +``` + +我们可以看到, 答案确实可以是我们之前看到的三种可能性之一。 + +### 处理训练数据 + + + +让我们从预处理训练数据开始。困难的部分将是为问题的答案生成标签, 这将是与上下文中的答案相对应的标记的开始和结束位置。 + +但是, 我们不要超越自己。首先, 我们需要使用分词器将输入中的文本转换为模型可以理解的 ID: + +```py +from transformers import AutoTokenizer + +model_checkpoint = "bert-base-cased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +如前所述, 我们将对 BERT 模型进行微调, 但你可以使用任何其他模型类型, 只要它实现了快速标记器即可。你可以在 [this big table](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]' +``` + +然后标签将成为开始和结束答案的标记的索引, 并且模型的任务是预测输入中每个标记的开始和结束 logit, 理论标签如下: + +
+One-hot encoded labels for question answering. + +
+ +在这种情况下, 上下文不会太长, 但是数据集中的一些示例的上下文很长, 会超过我们设置的最大长度(在这种情况下为 384)。正如我们在 [第六章](/course/chapter6/4) 中所看到的, 当我们探索 `question-answering` 管道的内部结构时, 我们将通过从我们的数据集的一个样本中创建几个训练特征来处理长上下文, 它们之间有一个滑动窗口。 + +要使用当前示例查看其工作原理, 我们可以将长度限制为 100, 并使用 50 个标记的滑动窗口。提醒一下, 我们使用: + +- `max_length` 设置最大长度 (此处为 100) +- `truncation="only_second"` 用于当带有上下文的问题太长时, 截断上下文t (位于第二个位置) +- `stride` 设置两个连续块之间的重叠标记数 (这里为 50) +- `return_overflowing_tokens=True` 让标记器知道我们想要溢出的标记 + +```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]` 标记)。我们还将在答案被截断的不幸情况下设置这些标签, 以便我们只有它的开始(或结束)。对于答案完全在上下文中的示例, 标签将是答案开始的标记的索引和答案结束的标记的索引。 + +数据集为我们提供了上下文中答案的开始字符, 通过添加答案的长度, 我们可以找到上下文中的结束字符。要将它们映射到令牌索引, 我们将需要使用我们在 [第六章](/course/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']) +``` + +如我们所见, 我们取回了通常的输入 ID、令牌类型 ID 和注意掩码, 以及我们需要的偏移映射和一个额外的键, `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` 是答案开头的标记索引 (在输入 ID 中), 并且 `end_position` 是答案结束的标记的索引 (在输入 ID 中)。 + +为了确定是哪种情况以及标记的位置, 以及(如果相关的话)标记的位置, 我们首先在输入 ID 中找到开始和结束上下文的索引。我们可以使用标记类型 ID 来执行此操作, 但由于这些 ID 不一定存在于所有模型中 (例如, DistilBERT 不需要它们), 我们将改为使用我们的标记器返回的 `BatchEncoding` 的 `sequence_ids()` 方法。 + +一旦我们有了这些标记索引, 我们就会查看相应的偏移量, 它们是两个整数的元组, 表示原始上下文中的字符范围。因此, 我们可以检测此特征中的上下文块是在答案之后开始还是在答案开始之前结束(在这种情况下, 标签是 `(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) + + # Find the start and end of the context + idx = 0 + while sequence_ids[idx] != 1: + idx += 1 + context_start = idx + while sequence_ids[idx] == 1: + idx += 1 + context_end = idx - 1 + + # If the answer is not fully inside the context, label is (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: + # Otherwise it's the start and end token positions + 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) + + # Find the start and end of the context + idx = 0 + while sequence_ids[idx] != 1: + idx += 1 + context_start = idx + while sequence_ids[idx] == 1: + idx += 1 + context_end = idx - 1 + + # If the answer is not fully inside the context, label is (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: + # Otherwise it's the start and end token positions + 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 个特征。我们的训练集现在可以使用了-- 让我们深入研究验证集的预处理! + +### 处理验证数据 + +预处理验证数据会稍微容易一些, 因为我们不需要生成标签(除非我们想计算验证损失, 但这个数字并不能真正帮助我们理解模型有多好)。真正的乐趣是将模型的预测解释为原始上下文的跨度。为此, 我们只需要存储偏移映射和某种方式来将每个创建的特征与它来自的原始示例相匹配。由于原始数据集中有一个 ID 列, 我们将使用该 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) +``` + +I在这种情况下, 我们只添加了几百个样本, 因此验证数据集中的上下文似乎有点短。 + +现在我们已经对所有数据进行了预处理, 我们可以开始训练了。 + +{#if fw === 'pt'} + +## 使用 `Trainer` API 微调模型 + +这个例子的训练代码看起来很像前面几节中的代码 -- 最难的是编写 `compute_metrics()` 函数。由于我们将所有样本填充到我们设置的最大长度, 因此没有数据整理器要定义, 所以这个度量计算真的是我们唯一需要担心的事情。困难的部分是将模型预测后处理为原始示例中的文本范围; 一旦我们这样做了, 🤗 Datasets 库中的指标将为我们完成大部分工作。 + +{:else} + +## 使用 Keras 微调模型 + +这个示例的训练代码看起来很像前几节中的代码, 但是计算指标将是唯一的挑战。因为我们将所有的样本填充到我们设置的最大长度, 所以不需要定义数据整理器, 所以这个度量计算实际上是我们唯一需要担心的事情。困难的部分是将模型预测后处理成原始例子中的文本范围; 一旦我们完成了这些, 🤗 Datasets 库中的指标将为我们完成大部分工作。 + +{/if} + +### 后处理 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +该模型将在输入ID中为答案的开始和结束位置输出Logit, 正如我们在探索 [`question-answering` pipeline](/course/chapter6/4) 时看到的那样。后处理步骤将类似于我们在那里所做的, 所以这里是我们采取的行动的快速提醒: + +- 我们屏蔽了与上下文之外的标记相对应的开始和结束 logits。 +- 然后, 我们使用 softmax 将开始和结束 logits 转换为概率。 +- 我们通过取对应的两个概率的乘积来给每个 `(start_token, end_token)` 组合赋值。 +- 我们寻找产生有效答案的最高分数的配对 (例如, `start_token` 低于 `end_token`)。 + +在这里, 我们将稍微改变这个过程, 因为我们不需要计算实际分数 (只是预测的答案)。这意味着我们可以跳过 softmax 步骤。为了更快, 我们也不会对所有可能的 `(start_token, end_token)` 对进行评分, 而只会对对应于最高 `n_best` 的那些对进行评分 (使用 `n_best=20`)。由于我们将跳过 softmax, 因此这些分数将是 logit 分数, 并且将通过取 start 和 end logits 的总和来获得 (而不是乘积, 因为规则 \\(\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 数组的预测, 我们获取开始和结束 logits 并将它们转换为该格式 + +```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` 开始 logits 和结束 logits 的 logit 分数, 不包括以下的位置: + +- 一个不在上下文中的答案 +- 长度为负的答案 +- 答案太长 (我们将可能性限制在 `max_answer_length=30`) + +一旦我们为一个示例获得了所有可能的答案, 我们只需选择一个具有最佳 logit 分数的答案: + +```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: + # Skip answers that are not fully in the context + if offsets[start_index] is None or offsets[end_index] is None: + continue + # Skip answers with a length that is either < 0 or > 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"]}) +``` + +预测答案的最终格式是我们将使用的度量标准所期望的格式。像往常一样, 我们可以在 🤗 Datasets 库的帮助下加载它: + +```python +from datasets import load_metric + +metric = load_metric("squad") +``` + +该指标期望我们上面看到的格式的预测答案 (一个字典列表, 其中一个键用于示例 ID, 一个键用于预测文本) 和以下格式的理论答案 (一个字典列表, 一个键示例的 ID 和可能答案的一键): + +```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} +``` + +同样, 考虑到根据 [its paper](https://arxiv.org/abs/1910.01108v2), 在 SQuAD 上微调的 DistilBERT 在整个数据集上的得分分别为 79.1 和 86.9, 这是相当不错的。 + +{#if fw === 'pt'} + +现在, 让我们把刚才所做的一切放在 `compute_metrics()` 函数中, 我们将在 `Trainer` 中使用它。通常, `compute_metrics()` 函数只接收一个包含 logits 和 labels 的元组 `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 = [] + + # Loop through all features associated with that example + 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: + # Skip answers that are not fully in the context + if offsets[start_index] is None or offsets[end_index] is None: + continue + # Skip answers with a length that is either < 0 or > 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) + + # Select the answer with the best score + 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} +``` + +看起来不错! 现在让我们用它来微调我们的模型。 + +### 微调模型 + +{#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` 的子类来完成这一任务(你可以在 [question answering example script](https://github.com/huggingface/transformers/blob/master/examples/pytorch/question-answering/trainer_qa.py)中找到一种方法), 但这对于本节来说有点太长了。相反, 我们只会在训练结束时评估模型, 并在下面的"自定义训练循环"向你展示如何进行常规评估。 + +T这确实时 `Trainer` API 显示其局限性和 🤗 Accelerate 库的亮点所在: 根据特定用例自定义类可能很痛苦, 但调整完全公开的训练循环很容易。 + +来看看我们的 `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, +) +``` + +我们之前已经看到了其中的大部分: 我们设置了一些超参数 (比如学习率、训练的 epoch 数和一些权重衰减), 并表明我们希望在每个 epoch 结束时保存模型, 跳过评估, 并将我们的结果上传到模型中心。我们还使用 `fp16=True` 启用混合精度训练, 因为它可以在最近的 GPU 上很好地加快训练速度。 + +{:else} + +现在, 我们可以创建我们的 TF 数据集。这次我们可以使用简单的默认数据整理器: + +```python +from transformers import DefaultDataCollator + +data_collator = DefaultDataCollator(return_tensors="tf") +``` + +现在我们像往常一样创建数据集。 + +```python +tf_train_dataset = train_dataset.to_tf_dataset( + columns=[ + "input_ids", + "start_positions", + "end_positions", + "attention_mask", + "token_type_ids", + ], + collate_fn=data_collator, + shuffle=True, + batch_size=16, +) +tf_eval_dataset = validation_dataset.to_tf_dataset( + columns=["input_ids", "attention_mask", "token_type_ids"], + 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 + +# 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_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) + +# Train in mixed-precision 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) + +# We're going to do validation afterwards, so no validation mid-training +model.fit(tf_train_dataset, callbacks=[callback], epochs=num_train_epochs) +``` + +{/if} + +请注意, 在进行训练时, 每次保存模型时 (这里是每个 epoch) 它都会在后台上传到 Hub。这样, 如有必要, 你将能够在另一台机器上恢复训练。整个培训需要一段时间 (在 Titan RTX 上需要一个多小时), 因此您可以喝杯咖啡或重读课程中您发现在进行过程中更具挑战性的部分内容。另请注意, 一旦第一个 epoch 完成, 你将看到一些权重上传到 Hub, 你可以开始在其页面上使用你的模型。 + +{#if fw === 'pt'} + +一旦训练完成, 我们终于可以评估我们的模型(并祈祷我们没有把所有的计算时间都花在任何事情上)。`Trainer` 的 `predict()` 方法将返回一个元组, 其中第一个元素将是模型的预测 (这里是带有开始和结束 logits 的对)。我们将其发送给 `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} + +一旦训练完成, 我们终于可以评估我们的模型(并祈祷我们没有把所有的计算时间都花在任何事情上)。我们的 `model` 的 `predict()` 方法将负责获取预测, 并且由于我们之前已经完成了定义 `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} + +在这个阶段, 你可以使用模型中心上的推理小部件来测试模型并与您的朋友、家人和最喜欢的宠物分享。你已经成功地微调了一个问答任务的模型 -- 恭喜! + + + +✏️ **轮到你了!** 尝试另一种模型架构, 看看它是否在此任务上表现更好! + + + +{#if fw === 'pt'} + +如果你想更深入地了解训练循环, 我们现在将向你展示如何使用 🤗 Accelerate 来做同样的事情。 + +## 自定义训练循环 + +现在让我们来看一下完整的训练循环, 这样您就可以轻松地自定义所需的部分。它看起来很像 [第三章](/course/chapter3/4) 中的训练循环, 除了评估循环。我们将能够定期评估模型, 因为我们不再受 `Trainer` 类的限制。 + +### 为训练做准备 + +首先, 我们需要从我们的数据集中构建 `DataLoader`。我们将这些数据集的格式设置为 `"torch"`, 并删除模型未使用的验证集中的列。然后, 我们可以使用 Transformers 提供的 `default_data_collator` 作为 `collate_fn`, 并打乱训练集, 但不打乱验证集58: + +```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()` 方法。请记住, 如果您想在 Colab 笔记本中的 TPU 上进行训练, 您需要将所有这些代码移动到一个训练函数中, 并且不应该执行任何实例化 `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` 对象。如果你尚未登录, 请先登录 Hugging Face Hub。我们将根据我们想要为模型提供的模型 ID 确定存储库名称 (随意用你自己的选择替换 `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) +``` + +我们现在可以通过调用 `repo.push_to_hub()` 方法上传我们保存在 `output_dir` 中的任何内容。这将帮助我们在每个 epoch 结束时上传中间模型。 + +## 训练循环 + +我们现在准备编写完整的训练循环。在定义了一个进度条来跟踪训练进行后, 循环分为三个部分: + +- 训练本身是对 `train_dataloader` 的经典迭代, 前向传递模型, 然后反向传递和优化器步骤。 +- 在计算中, 我们在将 `start_logits` 和 `end_logits` 的所有值转换为 NumPy 数组之前, 收集它们的所有值。评估循环完成后,我们将连接所有结果。请注意, 我们需要截断, 因为 `Accelerator` 可能在最后添加了一些示例, 以确保我们在每个过程中拥有相同数量的示例。 +- 保存和上传, 这里我们先保存模型和分词器, 然后调用 `repo.push_to_hub()`。正如我们之前所做的那样, 我们使用参数 `blocking=False` 来告诉 🤗 Hub 库推入一个异步进程。这样, 训练正常继续, 并且这个 (长) 指令在后台执行。 + +这是训练循环的完整代码: + +```py +from tqdm.auto import tqdm +import torch + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Training + model.train() + for step, batch in enumerate(train_dataloader): + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) + + # Evaluation + model.eval() + 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) + + # Save and upload + accelerator.wait_for_everyone() + unwrapped_model = accelerator.unwrap_model(model) + unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) + if accelerator.is_main_process: + tokenizer.save_pretrained(output_dir) + repo.push_to_hub( + commit_message=f"Training in progress epoch {epoch}", blocking=False + ) +``` + +如果这是您第一次看到使用 🤗 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} + +## 使用微调模型 + +我们已经向您展示了如何将我们在模型中心微调的模型与推理小部件一起使用。要在 `pipeline` 中本地使用它, 你只需要指定模型标识符: + +```py +from transformers import pipeline + +# Replace this with your own checkpoint +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/zh-CN/chapter7/8.mdx b/chapters/zh-CN/chapter7/8.mdx new file mode 100644 index 000000000..be36949af --- /dev/null +++ b/chapters/zh-CN/chapter7/8.mdx @@ -0,0 +1,17 @@ +# 精通自然语言处理 + +如果你在课程中做到了这一步,恭喜你——你现在拥有了用 🤗 Transformers 和 Hugging Face 生态系统解决(几乎)任何 NLP 任务所需的所有知识和工具! + +我们见过很多不同的数据整理器,所以我们制作了这个小视频来帮助您找到每个任务使用哪一个: + + + +在完成核心 NLP 任务的快速入门后,您应该: + +* 了解哪种架构(编码器、解码器或编码器-解码器)最适合每个任务 +* 了解预训练和微调语言模型之间的区别 +* 了解如何使用 `Trainer` API 和 🤗 Accelerate 或 TensorFlow 和 Keras 的分布式训练功能来训练 Transformer 模型,具体选择那一种方法取决于您所需要完成的任务。 +* 了解 ROUGE 和 BLEU 等指标在文本生成任务中的意义和局限性 +* 知道如何在 Hub 上和使用 🤗 Transformers 中的“管道”与您的微调模型进行交互 + +尽管掌握了所有这些知识,但总有一天你会遇到代码中的困难错误,或者对如何解决特定的 NLP 问题有疑问。幸运的是,Hugging Face 社区随时为您提供帮助!在这部分课程的最后一章中,我们将探讨如何调试 Transformer 模型并有效地寻求帮助。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter7/9.mdx b/chapters/zh-CN/chapter7/9.mdx new file mode 100644 index 000000000..3e4841372 --- /dev/null +++ b/chapters/zh-CN/chapter7/9.mdx @@ -0,0 +1,311 @@ + + + + +# End-of-chapter quiz + +Let's test what you learned in this chapter! + +### 1.下列哪个任务可以被框定为令牌分类问题? + + +### 2.令牌分类预处理的哪一部分与其他预处理管道不同? +-100 作为我们希望在丢失时忽略的令牌的标签。" + }, + { + text: "因为互联网上有大量的文本", + explain: "的确如此! 但这并不是唯一的区别。", + correct: true + } + ]} +/> + +### 3.当我们对标记分类问题中的单词进行标记并希望标记时,会出现什么问题? +-100 标记为 < code > ,以便在丢失时忽略它们。" + }, + { + text: "每个单词可以产生几个标记,所以我们最终得到的标记比标签多。", + explain: "这是主要的问题,我们需要将原始标签与标记对齐。", + correct: true + }, + { + text: "因为目标是按顺序排列的文本问题", + explain: "这是不正确的; 我们需要尽可能多的标签,否则我们的模型就会出错。" + } + ]} +/> + +### 4.“领域适应”是什么意思? + + +### 5.掩码语言建模问题中的标签是什么? + + +### 6.这些任务中的哪一个可以看作是一个顺序到顺序的问题? +-100 来标记特殊标记。", + explain: "这绝对是一个从序列到序列的问题。你能发现另一个吗?", + correct: true + }, + { + text: "修正我侄子/朋友发来的信息,使它们用正确的英语", + explain: "这是一种翻译问题,所以肯定是一个顺序到顺序的任务。但这不是唯一正确的答案!", + correct: true + } + ]} +/> + +### 7.对于序列到序列的问题,预处理数据的正确方法是什么? + input = ... 和 < code > target = ... 。", + explain: "这可能是我们将来添加的一个 API,但现在不可能。" + }, + { + text: "输入和目标都必须在对标记器的两个独立调用中进行预处理。", + explain: "不,这是在训练一个模型; 这里没有适应性。" + }, + { + text: "因为我们在训练之后计算度量", + explain: "不是在序列分类问题; 目标也是文本,我们需要转换成数字!" + }, + { + text: "输入必须发送到标记器,目标也是如此,但需要使用特殊的上下文管理器。", + explain: "没错,标记器需要由上下文管理器放入目标模式。", + correct: true + } + ]} +/> + +{#if fw === 'pt'} + +### 8.为什么序列到序列问题有一个特定的“培训者”子类? +-100 的标签", + explain: "这根本不是习惯性的损失,而是损失总是通过计算得到的。" + }, + { + text: "当您拥有大量可用数据时,即使有一个经过预先训练的模型可以处理这些数据", + explain: "没错。 Sequence-to-sequence models' predictions are often run using the generate() method.", + correct: true + }, + { + text: "文本是作为单词给出的,所以我们只需要应用子词的标记。", + explain: "< code > Trainer 并不关心这些,因为它们以前已经被预处理过。" + }, + { + text: "因为我们在序列到序列问题中使用了两个模型", + explain: "我们确实在某种程度上使用了两种模型,编码器和解码器,但是它们被组合在一个模型中。" + } + ]} +/> + +{:else} + +### 9.为什么在 Transformer 模型上调用“ compile ()”时通常不需要指定损失? + input = ... 和 < code > target = ... 。", + explain: "没错!", + correct: true + }, + { + text: "因为我们在训练之后计算指标", + explain: "这可以被定义为一个从序列到序列的问题,尽管这不是唯一正确的答案。" + }, + { + text: "因为损失是在“ model.fit ()”中指定的", + explain: "不,损失函数在运行‘ model.com pile ()’时是固定的,不能在‘ model.fit ()’中更改。" + } + ]} +/> + +{/if} + +### 10.你应该在什么时候预先训练一个新的模型? + + +### 11.为什么在大量的文本上预先训练一个语言模型是很容易的呢? + + +### 12.问答任务的预处理数据时,主要的挑战是什么? + + +### 13.问题回答中的后处理通常是怎样进行的? + diff --git a/chapters/zh-CN/chapter8/1.mdx b/chapters/zh-CN/chapter8/1.mdx new file mode 100644 index 000000000..0505bbb4d --- /dev/null +++ b/chapters/zh-CN/chapter8/1.mdx @@ -0,0 +1,12 @@ +# 介绍 + +既然您知道如何使用🤗 Transformers处理最常见的NLP任务,您应该能够开始自己的项目了!在本章中,我们将探讨遇到问题时该怎么做。您将学习如何成功调试代码或训练,以及如果自己无法解决问题,如何向社区寻求帮助。如果您认为您在其中一个拥抱人脸库中发现了错误,我们将向您展示报告它的最佳方式,以便尽快解决问题。 + +更准确地说,在本章中,您将学习: + +- 出现错误时要做的第一件事 +- 如何在网上寻求帮助[forums](https://discuss.huggingface.co/) +- 如何调试您的训练管道 +- 如何写一个好问题 + +当然,所有这些都与 🤗 Transformers 或Hugging Face 生态无关;本章的经验教训适用于大多数开源项目! diff --git a/chapters/zh-CN/chapter8/2.mdx b/chapters/zh-CN/chapter8/2.mdx new file mode 100644 index 000000000..311b09e6b --- /dev/null +++ b/chapters/zh-CN/chapter8/2.mdx @@ -0,0 +1,364 @@ +# 出现错误时该怎么办 + + + +在本节中, 我们将研究当你尝试从新调整的 Transformer 模型生成预测时可能发生的一些常见错误。这将为 [第四节](/course/chapter8/section4) 做准备, 我们将探索如何调试训练阶段本身。 + + + +我们为这一节准备了一个 [模板模型库](https://huggingface.co/lewtun/distilbert-base-uncased-finetuned-squad-d5716d28), 如果你想运行本章中的代码, 你首先需要将模型复制到你的 [Hugging Face Hub](https://huggingface.co) 账号。为此, 首先通过在 Jupyter 笔记本中运行以下任一命令来登录: + +```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(): + # Clone the repo and extract the local path + template_repo_id = "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28" + commit_hash = "be3eaffc28669d7932492681cd5f3e8905e358b4" + template_repo_dir = snapshot_download(template_repo_id, revision=commit_hash) + # Create an empty repo on the Hub + model_name = template_repo_id.split("/")[1] + create_repo(model_name, exist_ok=True) + # Clone the empty repo + new_repo_id = get_full_repo_name(model_name) + new_repo_dir = model_name + repo = Repository(local_dir=new_repo_dir, clone_from=new_repo_id) + # Copy files + copy_tree(template_repo_dir, new_repo_dir) + # Push to Hub + repo.push_to_hub() +``` + +现在, 当你调用 `copy_repository_template()` 时, 它将在你的帐户下创建模板存储库的副本。 + +## 从 🤗 Transformers 调试管道 + +要开始我们调试 Transformer 模型的奇妙世界之旅, 请考虑以下场景: 你正在与一位同事合作进行问答项目, 以帮助电子商务网站的客户找到有关消费品的答案。你的同事给你发了一条消息, 比如: + +> 嗨! 我刚刚使用了抱抱脸课程的 [第七章](/course/chapter7/7) 中的技术进行了一个实验, 并在 SQuAD 上获得了一些很棒的结果! 我认为我们可以用这个模型作为我们项目的起点。Hub上的模型ID是 "lewtun/distillbert-base-uncased-finetuned-squad-d5716d28"。请随意测试一下 :) + +你首先想到的是使用 🤗 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. +
+ +这些报告中包含很多信息, 所以让我们一起来看看关键部分。首先要注意的是, 应该从 _从底部到顶部_ 读取回溯。如果你习惯于从上到下阅读英文文本, 这可能听起来很奇怪,但它反映了这样一个事实,即回溯显示了在下载模型和标记器时 `管道` 进行的函数调用序列。(查看 [第二章](/course/chapter2) 了解有关 `pipeline` 如何在后台工作的更多详细信息。) + + + +🚨 看到Google Colab 回溯中 "6 帧" 周围的蓝色框了吗? 这是 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/) 搜索栏中 (是的, 真的!)。你很可能不是第一个遇到错误的人, 这是找到社区中其他人发布的解决方案的好方法。例如, 在 Stack Overflow 上搜索 `OSError: Can't load config for` 给出了几个[hits](https://stackoverflow.com/search?q=OSError%3A+Can%27t+load+config+for+), 可能是用作解决问题的起点。 + + + +第一个建议是要求我们检查模型ID是否真的正确, 所以首先要做的就是复制标识符并将其粘贴到Hub的搜索栏中: + +
+The wrong model name. +
+ +嗯, 看起来我们同事的模型确实不在 Hub 上... 啊哈, 但是模型名称中有一个错字! DistilBERT 的名称中只有一个 "l", 所以让我们解决这个问题并寻找 "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28": + +
+The right model name. +
+ +好的, 这很受欢迎。现在让我们尝试使用正确的模型 ID 再次下载模型: + +```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 +""" +``` + +啊, 再次挫败 -- 欢迎来到机器学习工程师的日常生活! 因为我们已经修复了模型 ID, 所以问题一定出在存储库本身。访问 🤗 Hub 上存储库内容的一种快速方法是通过 `huggingface_hub` 库的 `list_repo_files()` 方法: + +```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。在这种情况下, 问题似乎很容易解决: 我们可以要求他们添加文件, 或者, 因为我们可以从模型 ID 中看到使用的预训练模型是 [`distilbert-base-uncased`](https://huggingface.co/distilbert-base-uncased), 我们可以下载此模型的配置并将其推送到我们的存储库以查看是否可以解决问题。让我们试试看。使用我们在 [第二章](/course/chapter2) 中学习的技术, 我们使用 `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_ , 并从下到上阅读。错误消息的最后一行通常包含定位问题根源所需的信息。 +- 如果最后一行没有包含足够的信息, 请按照您的方式进行回溯, 看看您是否可以确定源代码中发生错误的位置。 +- 如果没有任何错误消息可以帮助您调试问题, 请尝试在线搜索类似问题的解决方案。 +- `huggingface_hub` +// 🤗 Hub? +库提供了一套工具, 你可以使用这些工具与 Hub 上的存储库进行交互和调试。 + +现在你知道如何调试管道, 让我们看一下模型本身前向传递中的一个更棘手的示例。 + +## 调试模型的前向传递 + +尽管 `pipeline` 对于大多数需要快速生成预测的应用程序来说非常有用, 有时您需要访问模型的 logits (例如, 如果您有一些想要应用的自定义后处理)。为了看看在这种情况下会出现什么问题, 让我们首先从 `pipeline` 中获取模型和标记器: + +```python +tokenizer = reader.tokenizer +model = reader.model +``` + +接下来我们需要一个问题, 那么让我们看看是否支持我们最喜欢的框架: + +```python +question = "Which frameworks can I use?" +``` + +正如我们在 [第七章](/course/chapter7) 中学习的, 我们需要采取的通常步骤是对输入进行标记化, 提取开始和结束标记的对数, 然后解码答案范围: + +```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 +# Get the most likely beginning of answer with the argmax of the score +answer_start = torch.argmax(answer_start_scores) +# Get the most likely end of answer with the argmax of the score +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' object has no attribute 'size'`, 我们可以看到一个 `-->` 箭头指向 `model(**inputs)` 中出现问题的行。你可以使用 Python 调试器以交互方式调试它, 但现在我们只需打印出一部分 `inputs`, 看看我们有什么: + +```python +inputs["input_ids"][:5] +``` + +```python out +[101, 2029, 7705, 2015, 2064] +``` + +这当然看起来像一个普通的 Python `list`, 但让我们仔细检查一下类型: + +```python +type(inputs["input_ids"]) +``` + +```python out +list +``` + +是的, 这肯定是一个 Python `list`。那么出了什么问题呢? 回忆 [第二章](/course/chapter2) 🤗 Transformers 中的 `AutoModelForXxx` 类在 _tensors_ 上运行(PyTorch或者or 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 上搜索错误消息给出了很多相关的 [hits](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 +# Get the most likely beginning of answer with the argmax of the score +answer_start = torch.argmax(answer_start_scores) +# Get the most likely end of answer with the argmax of the score +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/zh-CN/chapter8/3.mdx b/chapters/zh-CN/chapter8/3.mdx new file mode 100644 index 000000000..b893984bb --- /dev/null +++ b/chapters/zh-CN/chapter8/3.mdx @@ -0,0 +1,166 @@ +# 在论坛上寻求帮助 + + + + + +[Hugging Face forums](https://discuss.huggingface.co) 是从开源团队和更广泛的 Hugging Face 社区获得帮助的好地方。以下是任何一天的主页面: + +
+The Hugging Face forums. +
+ +在左侧,您可以看到各种主题分组的所有类别,而右侧显示了最新的主题。主题是包含标题、类别和描述的帖子;它与我们在创建自己的数据集时看到的 GitHub 问题格式非常相似[Chapter 5](/course/chapter5).顾名思义,[Beginners](https://discuss.huggingface.co/c/beginners/5)类别主要面向刚开始使用 Hugging Face 库和生态系统的人。欢迎对任何库提出任何问题,无论是调试一些代码还是寻求有关如何做某事的帮助。 (也就是说,如果您的问题特别涉及某个图书馆,您可能应该前往论坛上的相应图书馆类别。) + +同样,the [Intermediate](https://discuss.huggingface.co/c/intermediate/6)和[Research](https://discuss.huggingface.co/c/research/7)类别用于更高级的问题,例如关于图书馆或您想讨论的一些很酷的新 NLP 研究。 + +当然,我们也应该提到[Course](https://discuss.huggingface.co/c/course/20)类别,您可以在其中提出与 Hugging Face 课程相关的任何问题! + +选择类别后,您就可以编写第一个主题了。 你可以找一些[guidelines](https://discuss.huggingface.co/t/how-to-request-support/3128) 在有关如何执行此操作的论坛中,在本节中,我们将看看构成一个好的主题的一些功能。 + +## 写一篇好的论坛帖子 + +作为一个运行示例,假设我们试图从 Wikipedia 文章生成嵌入以创建自定义搜索引擎。像往常一样,我们按如下方式加载分词器和模型: + +```python +from transformers import AutoTokenizer, AutoModel + +model_checkpoint = "distilbert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +model = AutoModel.from_pretrained(model_checkpoint) +``` + +现在假设我们尝试嵌入整个部分[Wikipedia article](https://en.wikipedia.org/wiki/Transformers) 关于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 +``` + +呃,我们遇到了一个问题——错误信息比我们看到的要神秘得多[section 2](/course/chapter8/section2)!我们无法确定完整回溯的正面或反面,因此我们决定转向 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. 这个话题直接用一种有点苛刻的语气标记了几个人。 + +像这样的主题不太可能很快得到答案(如果他们得到了答案),那么让我们看看如何改进它。我们将从选择一个好标题的第一个问题开始。 + +### 选择描述性标题 + +如果您想就代码中的错误寻求帮助,一个好的经验法则是在标题中包含足够的信息,以便其他人可以快速确定他们是否认为他们可以回答您的问题。在我们的运行示例中,我们知道正在引发的异常的名称,并有一些提示它是在模型的前向传递中触发的,我们调用 **model(**inputs)** .为了传达这一点,一个可能的标题可能是: + +> 自动建模正向传递中的索引错误的来源? + +这个标题告诉读者在哪里你认为错误来自,如果他们遇到了 **IndexError** 在此之前,他们很有可能知道如何调试它。当然,标题可以是您想要的任何内容,也可以是其他变体,例如: + +> 为什么我的模型会产生索引错误? + +也可以。现在我们有了一个描述性的标题,让我们来看看改善主体。 + +### 设置代码段的格式 + +如:也可以。现在我们有了一个描述性的标题,让我们来看看改善身体。在 IDE 中阅读源代码已经够难的了,但是当将代码复制粘贴为纯文本时就更难了!幸运的是,Hugging Face 论坛支持使用 Markdown,因此您应该始终用三个反引号 (```) 将代码块括起来,以便更容易阅读。让我们这样做来美化错误消息——在我们这样做的时候,让我们让正文比我们的原始版本更有礼貌: + +
+Our revised forum topic, with proper code formatting. +
+ +正如您在屏幕截图中看到的,将代码块括在反引号中会将原始文本转换为格式化代码,并带有颜色样式!另请注意,单个反引号可用于格式化内联变量,就像我们所做的那样 **distilbert-base-uncased** .这个主题看起来好多了,如果幸运的话,我们可能会在社区中找到可以猜测错误是什么的人。然而,与其依靠运气,不如让我们在其完整的血腥细节中包含回溯,让生活更轻松! + +### 包括完整的回溯 + +由于回溯的最后一行通常足以调试您自己的代码,因此很容易在您的主题中提供它以“节省空间”。虽然本意是好的,但这实际上使它更难供其他人调试问题,因为回溯中较高的信息也非常有用。因此,一个好的做法是复制并粘贴所有的回溯,同时确保它的格式很好。由于这些回溯可能会很长,有些人更喜欢在解释了源代码之后再展示它们。我们开工吧。现在,我们的论坛主题如下所示: + +
+Our example forum topic, with the complete traceback. +
+ +这提供了更多信息,细心的读者可能会指出问题似乎是由于回溯中的这一行而传递了一个长输入: + +> 令牌索引序列长度长于为此模型指定的最大序列长度 (583 > 512)。 + +但是,通过提供触发错误的实际代码,我们可以让他们更轻松。我们现在就这样做。 + +### 提供可重复的示例 + +如果您曾经尝试过调试其他人的代码,那么您可能首先尝试重现他们报告的问题,以便您可以开始通过回溯来查明错误。在论坛上获得(或提供)帮助时没有什么不同,所以如果你能提供一个重现错误的小例子真的很有帮助。有一半的时间,简单地完成这个练习将帮助你找出问题所在。在任何情况下,我们的示例缺少的部分是显示输入我们提供给模型的。这样做会为我们提供类似于以下完整示例的内容: + +
+The final version of our forum topic. +
+ +该主题现在包含相当多的信息,并且它的编写方式更可能吸引社区的注意力并获得有用的答案。有了这些基本指南,您现在可以创建很棒的主题来找到您的 🤗 Transformers问题的答案! + diff --git a/chapters/zh-CN/chapter8/4.mdx b/chapters/zh-CN/chapter8/4.mdx new file mode 100644 index 000000000..27f3614e7 --- /dev/null +++ b/chapters/zh-CN/chapter8/4.mdx @@ -0,0 +1,787 @@ + + +# 调试训练管道 + + + +你已经编写了一个漂亮的脚本来训练或微调给定任务的模型,尽职尽责地遵循 [Chapter 7](/course/chapter7) 中的建议。 但是当你启动命令 `trainer.train()` 时,可怕的事情发生了:你得到一个错误😱! 或者更糟糕的是,一切似乎都很好,训练运行没有错误,但生成的模型很糟糕。 在本节中,我们将向您展示如何调试此类问题。 + +## 调试训练管道 + + + +当您在 `trainer.train()` 中遇到错误时,它可能来自多个来源,因为 `Trainer` 通常会将很多东西放在一起组合运行。 它将datasets转换为dataloaders,因此问题可能出在datasets中,或者在尝试将datasets的元素一起批处理时出现问题。 然后它需要准备一批数据并将其提供给模型,因此问题可能出在模型代码中。 之后,它会计算梯度并执行优化器,因此问题也可能出在您的优化器中。 即使训练一切顺利,如果您的评估指标有问题,评估期间仍然可能出现问题。 + +调试 `trainer.train()` 中出现的错误的最佳方法是手动检查整个管道,看看哪里出了问题。 错误通常很容易解决。 + +为了证明这一点,我们将使用以下脚本(尝试)在 [MNLI 数据集](https://huggingface.co/datasets/glue)上微调 DistilBERT 模型: + +```py +from datasets import load_dataset, load_metric +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 = load_metric("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' +``` + +### 检查数据 + +这是不言而喻的,如果你的数据被破坏,“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, load_metric +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 = load_metric("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)' +``` + +查看traceback,我们可以看到错误发生在数据整理步骤中: + +```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` 也会记录它丢弃的列。 + +我们通过解码检查了输入 ID 是否正确。 接下来是检查 `attention_mask`: + +```py +tokenizer.decode(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] +``` + +由于我们没有在预处理中应用填充,这看起来非常自然。 为确保该注意掩码没有问题,让我们检查它与输入 ID 的长度是否相同: + +```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 +``` + +与输入 ID 一样,这是一个本身并没有真正意义的数字。 正如我们之前看到的,整数和标签名称之间的映射存储在数据集相应 *feature* 的 `names` 属性中: + +```py +trainer.train_dataset.features["label"].names +``` + +```python out +['entailment', 'neutral', 'contradiction'] +``` + +所以`1`表示`neutral`,表示我们上面看到的两句话并不矛盾,也没有包含关系。 这似乎是正确的! + +我们这里没有令牌类型 ID,因为 DistilBERT 不需要它们; 如果您的模型中有一些,您还应该确保它们正确匹配输入中第一句和第二句的位置。 + + + +✏️ **轮到你了!** 检查训练数据集的第二个元素是否正确。 + + + +我们在这里只对训练集进行检查,但您当然应该以同样的方式仔细检查验证集和测试集。 + +现在我们知道我们的数据集看起来不错,是时候检查训练管道的下一步了。 + +### 从 datasets 到 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) +``` + +检查trackback的最后一个堆栈的输出应该足以给你一个线索,但让我们做更多的挖掘。 批处理创建过程中的大多数问题是由于将示例整理到单个批处理中而出现的,因此在有疑问时首先要检查的是您的 DataLoader 正在使用什么 collate_fn: + +```py +data_collator = trainer.get_train_dataloader().collate_fn +data_collator +``` + +```python out + Dict[str, Any]> +``` + +所以,目前使用的是 `default_data_collator`,但这不是我们在这种情况下想要的。 我们希望将示例填充到批处理中最长的句子,这是由 `DataCollatorWithPadding` 整理器完成的。 而这个数据收集器应该是默认被 `Trainer` 使用的,为什么这里没有使用呢? + +答案是因为我们没有将 `tokenizer` 传递给 `Trainer`,所以它无法创建我们想要的 `DataCollatorWithPadding`。 在实践中,您应该明确地传递您想要使用的数据整理器,以确保避免这些类型的错误。 让我们调整我们的代码来做到这一点: + +```py +from datasets import load_dataset, load_metric +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 = load_metric("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 错误通常很难调试。 我们稍后会看到如何解决这个问题,但首先让我们完成对批处理创建的分析。 + +如果您确定您的数据整理器是正确的,则应尝试将其应用于数据集的几个样本: + +```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)]) +``` + +如果错误仍然存在,您应该能够手动调试数据整理器内部以确定具体的问题。 + +现在我们已经调试了批处理创建过程,是时候将数据传递给模型了! + +### 检查模型 + +您应该能够通过执行以下命令来获得一个批次的数据: + +```py +for batch in trainer.get_train_dataloader(): + break +``` + +如果您在notebook中运行此代码,您可能会收到与我们之前看到的类似的 CUDA 错误,在这种情况下,您需要重新启动notebook并重新执行最后一个片段,而不运行 `trainer.train()` 行.这是关于 CUDA 错误的第二个最烦人的事情:它们会破坏您的Cuda内核,而且无法恢复。它们最烦人的事情是它们很难调试。 + +这是为什么?它与 GPU 的工作方式有关。它们在并行执行大量操作方面非常有效,但缺点是当其中一条指令导致错误时,您不会立即知道。只有当程序在 GPU 上调用多个进程的同步处理时,它才会意识到出现问题,因此错误实际上是在与创建它的原因无关的地方引发的。例如,如果我们查看之前的trackback,错误是在向后传递期间引发的,但我们会在一分钟内看到它实际上源于向前传递中的某些东西。 + +那么我们如何调试这些错误呢?答案很简单:我们没有。除非您的 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”(因此与我们之前所说的反向传播无关)。 更准确地说,我们可以看到是Target 2 造成了错误,所以这是检查模型标签数量的好时机: + +```python +trainer.model.config.num_labels +``` + +```python out +2 +``` + +有两个标签,只允许 0 和 1 作为目标,但是根据错误信息我们得到一个 2。得到一个 2 实际上是正常的:如果我们记得我们之前提取的标签名称,有三个,所以我们有索引 0 , 1 和 2 在我们的数据集中。 问题是我们没有告诉我们的模型,它应该创建三个标签。 所以让我们解决这个问题! + +```py +from datasets import load_dataset, load_metric +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 = load_metric("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) +``` + +下一步是回到 GPU 并检查一切是否仍然有效: + +```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) +``` + +如果仍然出现错误,请确保重新启动notebook并仅执行脚本的最后一个版本。 + +### 执行一个优化器步骤 + +现在我们知道我们可以构建实际通过模型检查的成批次的数据,我们已经为训练管道的下一步做好准备:计算梯度并执行优化步骤。 + +第一部分只是在 loss 上调用 `backward()` 方法: + +```py +loss = outputs.loss +loss.backward() +``` + +在这个阶段很少出现错误,但如果确实出现错误,请返回 CPU 以获取有用的错误消息。 + +要执行优化步骤,我们只需要创建 `optimizer` 并调用它的 `step()` 方法: + +```py +trainer.create_optimizer() +trainer.optimizer.step() +``` + +同样,如果您在 `Trainer` 中使用默认优化器,则在此阶段您不应该收到错误,但如果您有自定义优化器,则可能会出现一些问题需要在这里调试。 如果您在此阶段遇到奇怪的 CUDA 错误,请不要忘记返回 CPU。 说到 CUDA 错误,前面我们提到了一个特殊情况。 现在让我们来看看。 + +### 处理 CUDA out-of-memory错误 + +每当您收到以`RuntimeError: CUDA out of memory`开头的错误消息时,这表明您的 GPU 内存不足。 这与您的代码没有直接关联,并且它可能发生在运行良好的代码中。 此错误意味着您试图在 GPU 的内部存储器中放入太多东西,这导致了错误。 与其他 CUDA 错误一样,您需要重新启动内核才能再次运行训练。 + +要解决这个问题,您只需要使用更少的 GPU 空间——这往往说起来容易做起来难。 首先,确保您没有同时在 GPU 上运行两个模型(当然,除非您的问题需要这样做)。 然后,您可能应该减少batch的大小,因为它直接影响模型的所有中间输出的大小及其梯度。 如果问题仍然存在,请考虑使用较小版本的模型。 + + + +在课程的下一部分中,我们将介绍更先进的技术,这些技术可以帮助您减少内存占用并让您微调最大的模型。 + + + +### 评估模型 + +现在我们已经解决了代码的所有问题,一切都很完美,训练应该可以顺利进行,对吧? 没那么快! 如果你运行 `trainer.train()` 命令,一开始一切看起来都不错,但过一会儿你会得到以下信息: + +```py +# This will take a long time and error out, so you shouldn't run this cell +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) +``` + +稍等一会儿,错误出现,在评估阶段结束时,如果我们查看trackback,我们会看到: + +```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()` 函数的问题。 它需要一个带有 logits 和标签的元组作为 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 +``` + +我们得到同样的错误,所以问题肯定出在那个函数上。 如果我们回顾它的代码,我们会发现它只是将“预测”和“真实的标签”转发到“metric.compute()”。 那么这种方法有问题吗? 并不真地。 让我们快速浏览一下形状: + +```py +predictions.shape, labels.shape +``` + +```python out +((8, 3), (8,)) +``` + +我们的预测仍然是 logits,而不是实际的预测,这就是metrics返回这个(有点模糊)错误的原因。 修复很简单; 我们只需要在 `compute_metrics()` 函数中添加一个 argmax: + +```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, load_metric +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 = load_metric("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()`! + + + +## 在训练期间调试静默(没有任何错误提示)错误 + +我们可以做些什么来调试一个没有错误地完成但没有得到好的结果的训练? 我们会在这里给你一些提示,但请注意,这种调试是机器学习中最难的部分,并且没有神奇的答案。 + +### 检查您的数据(再次!) + +只有在理论上可以从您的数据中学到任何东西时,您的模型才会学到一些东西。 如果存在损坏数据的错误或标签是随机属性的,那么您很可能不会在数据集上获得任何知识。 因此,始终首先仔细检查您的解码输入和标签,然后问自己以下问题: + +- 解码后的数据是否可以理解? +- 你认同这些标签吗? +- 有没有一个标签比其他标签更常见? +- 如果模型预测随机的答案/总是相同的答案,那么loss/评估指标应该是多少? + + + +⚠️ 如果您正在进行分布式训练,请在每个过程中打印数据集的样本,并三次检查您是否得到相同的结果。 一个常见的错误是在数据创建中有一些随机性来源,这使得每个进程都有不同版本的数据集。 + + + +查看您的数据后,查看模型的一些预测并对其进行解码。 如果模型总是预测同样的事情,那可能是因为你的数据集偏向一个类别(针对分类问题); 过采样稀有类等技术可能会有所帮助。 + +如果您在初始模型上获得的loss/评估指标与您期望的随机预测的loss/评估指标非常不同,请仔细检查您的loss或评估指标的计算方式,因为那里可能存在错误。 如果您使用最后添加的多个loss,请确保它们具有相同的规模。 + +当您确定您的数据是完美的时,您可以通过一个简单的测试来查看模型是否能够对其进行训练。 + +### 在一批上过度拟合你的模型 + +过度拟合通常是我们在训练时尽量避免的事情,因为这意味着模型没有学习识别我们想要的一般特征,而只是记住了训练样本。 在这种情况下,一遍又一遍地尝试在一个批次上训练您的模型是一个很好的测试,可以检查您的问题是否可以通过您尝试训练的模型来解决。 它还将帮助您查看您的初始学习率是否太高。 + +一旦你定义了你的 `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”,因为获得的模型可能无法在您的完整数据集上恢复和学习有用的东西。 + + + +### 在你有第一个基线之前不要调整任何东西 + +超参数调优总是被强调为机器学习中最难的部分,但这只是帮助您在指标上有所收获的最后一步。 大多数情况下,`Trainer` 的默认超参数可以很好地为您提供良好的结果,因此在您获得超出数据集基线的东西之前,不要开始进行耗时且昂贵的超参数搜索 . + +一旦你有一个足够好的模型,你就可以开始稍微调整一下。 不要尝试使用不同的超参数启动一千次运行,而是比较一个超参数的不同值的几次运行,以了解哪个影响最大。 + +如果您正在调整模型本身,不要尝试任何您无法合理证明的事情。 始终确保您返回过拟合测试以验证您的更改没有产生任何意外后果。 + +### 请求帮忙 + +希望您会在本节中找到一些可以帮助您解决问题的建议,但如果不是这样,请记住您可以随时在 [论坛](https://discuss.huggingface.co/) 上向社区提问。 + +以下是一些可能有用的额外资源: + +- [“作为工程最佳实践工具的再现性”](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p),作者:Joel Grus +- [“神经网络调试清单”](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) 作者:Cecelia Shao +- [“如何对机器学习代码进行单元测试”](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) by Chase Roberts +- [“训练神经网络的秘诀”](http://karpathy.github.io/2019/04/25/recipe/)作者:Andrej Karpathy + +当然,并不是你在训练神经网络时遇到的每一个问题都是你自己的错! 如果您在 🤗 Transformers 或 🤗 Datasets 库中遇到看起来不正确的内容,您可能遇到了错误。 你应该告诉我们这一切,在下一节中,我们将准确解释如何做到这一点。 diff --git a/chapters/zh-CN/chapter8/4_tf.mdx b/chapters/zh-CN/chapter8/4_tf.mdx new file mode 100644 index 000000000..21a4e57b5 --- /dev/null +++ b/chapters/zh-CN/chapter8/4_tf.mdx @@ -0,0 +1,489 @@ + + +# Debugging the training pipeline + + + +你已经编写了一个漂亮的脚本来训练或微调给定任务的模型,尽职尽责地遵循 [第七章](/course/chapter7) 中的建议。 但是当你启动命令 `model.fit()` 时,可怕的事情发生了:你得到一个错误😱! 或者更糟糕的是,一切似乎都很好,训练运行没有错误,但生成的模型很糟糕。 在本节中,我们将向您展示如何调试此类问题。 + +## Debugging the training pipeline + + + +The problem when you encounter an error in `model.fit()` is that it could come from multiple sources, as training usually brings together a lot of things that you've been working on up until that point. The problem could be something wrong in your dataset, or some issue when trying to batch elements of the datasets together. Or it could be something wrong in the model code, or your loss function or optimizer. And even if everything goes well for training, something could still go wrong during the evaluation if there is a problem with your metric. + +The best way to debug an error that arises in `model.fit()` is to manually go through this whole pipeline to see where things went awry. The error is then often very easy to solve. + +To demonstrate this, we will use the following script that (tries to) fine-tune a DistilBERT model on the [MNLI dataset](https://huggingface.co/datasets/glue): + +当您在 `model.fit()` 中遇到错误时,问题在于它可能来自多个来源,因为训练通常会汇集很多您在此之前一直在做的事情。 问题可能是您的数据集中有问题,或者是在尝试将数据集的元素批处理在一起时出现问题。 或者模型代码、损失函数或优化器中可能有问题。 即使训练一切顺利,如果您的指标有问题,评估期间仍然可能出现问题。 + +调试 `model.fit()` 中出现的错误的最佳方法是手动检查整个管道,看看哪里出了问题。 错误通常很容易解决。 + +为了证明这一点,我们将使用以下脚本(尝试)在 [MNLI 数据集](https://huggingface.co/datasets/glue)上微调 DistilBERT 模型: + +```py +from datasets import load_dataset, load_metric +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 年 11 月之后阅读这门课程并且它仍在继续,那么请在推特上 @carrigmat 上发送愤怒的推文,直到他修复它。 + +然而,更严重的问题是我们得到了一个彻底的错误。 它真的非常长: + +```python out +ValueError: No gradients provided for any variable: ['tf_distil_bert_for_sequence_classification/distilbert/embeddings/word_embeddings/weight:0', '...'] +``` + +这意味着什么? 我们试图训练我们的数据,但我们没有梯度? 这很令人困惑。 我们甚至不知道该如何开始调试类似的东西? 当你得到的错误并不能立即表明问题出在哪里时,最好的解决方案通常是按顺序检查所有内容,确保在每个阶段一切看起来都是正确的。 当然,开始的地方总是... + +### 检查您的数据 + +这是不言而喻的,但如果您的数据已损坏,Keras 将无法为您修复它。 所以首先,你需要看看你的训练集中有什么。 + +尽管查看 `raw_datasets` 和 `tokenized_datasets` 很诱人,但我们强烈建议您在数据将要进入模型的地方直接查看数据。 这意味着应该从您使用 `to_tf_dataset()` 函数创建的 `tf.data.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 训练 Transformer 模型时会遇到的最常见问题之一。我们的模型都可以在内部计算损失,但要做到这一点,需要在输入字典中传递标签。这是当我们没有为 `compile()` 指定损失值时使用的损失。另一方面,Keras 通常希望标签与输入字典分开传递,如果你不这样做,损失计算通常会失败。 + +问题现在变得更清楚了:我们传递了一个“损失”参数,这意味着我们要求 Keras 为我们计算损失,但我们将标签作为模型的输入传递,而不是放在 Keras 期望的地方的!我们需要二选一:要么使用模型的内部损失并将标签保留在原处,要么继续使用 Keras 损失,但将标签移动到 Keras 期望的位置。为简单起见,让我们采用第一种方法。将对 `compile()` 的调用更改为: + +```py +model.compile(optimizer="adam") +``` + +现在我们将使用模型的内部损失,这个问题应该解决了! + + + +✏️ **轮到你了!** 作为我们解决其他问题后的可选挑战,你可以尝试回到这一步,让模型使用原始 Keras 计算的损失而不是内部损失。 您需要将 `"labels"` 添加到 `to_tf_dataset()` 的 `label_cols` 参数,以确保正确输出标签,这将为您提供梯度——但我们指定的损失还有一个问题 . 训练仍然会遇到这个问题,学习会非常缓慢,并且会在多次训练损失时达到稳定状态。 你能弄清楚它是什么吗? + +一个 ROT13 编码的提示,如果你卡住了:Vs lbh ybbx ng gur bhgchgf bs FrdhraprPynffvsvpngvba zbqryf va Genafsbezref, gurve svefg bhgchg vf `ybtvgf`。 荣格纳 ybtvgf? + +第二个提示:Jura lbh fcrpvsl bcgvzvmref, npgvingvbaf 是 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 +``` + +Oh no. + +`nan` 不是一个非常令人开心的损失值。 尽管如此,我们已经检查了我们的数据,它看起来还不错。 如果这不是问题,我们下一步该去哪里? 显而易见的下一步是... + +### 检查你的模型 + +`model.fit()` 是 Keras 中一个非常方便的函数,但它为您做了很多事情,这使得准确找到问题发生的位置变得更加棘手。 如果您正在调试您的模型,一个真正有用的策略是只将一个批次传递给模型,并详细查看该批次的输出。 如果模型抛出错误,另一个非常有用的提示是使用 `run_eagerly=True` `compile()` 模型。 这会使它变慢很多,但它会使错误消息更容易理解,因为它们会准确地指出问题发生在模型代码的哪个位置。 + +不过,目前我们还不需要 `run_eagerly`。 让我们通过模型运行我们之前得到的“批处理”,看看输出是什么样子的: + +```py +model(batch) +``` + +```python out +TFSequenceClassifierOutput(loss=, logits=, hidden_states=None, attentions=None) +``` + +嗯,这很棘手。一切都是`nan`!但这很奇怪,不是吗?我们所有的 logits 怎么会变成“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) +``` + +*现在*我们到了某个地方! 我们的 logits 中没有 `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]) +``` + +让我们看看这些来自样本的输入id: + +```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。这是一个非常明显的提示。 当我们的标签为 2 时,我们会得到loss为 `nan`,这表明这是检查模型中标签数量的好时机: + +```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's,我们的损失正在减少......有点。 如果你观察一段时间,你可能会开始有点不耐烦,因为损失值一直居高不下。 让我们在这里停止训练并尝试考虑可能导致此问题的原因。 在这一点上,我们很确定数据和模型都没有问题,但是我们的模型并没有很好地学习。 还剩下什么? 是时候... + +### 检查你的超参数 + +如果你回头看上面的代码,你可能根本看不到任何超参数,除了 `batch_size`,这似乎不是罪魁祸首。不过,不要被迷惑;总是有超参数,如果你看不到它们,那只是意味着你不知道它们的设置是什么。特别要记住关于 Keras 的一个关键点:如果您使用字符串设置损失函数、优化器或激活函数,_它的所有参数都将设置为它们的默认值_。这意味着即使为此使用字符串非常方便,但在这样做时您应该非常小心,因为它很容易对您隐藏关键的事情。 (任何尝试上述方式的人都应该仔细注意这一事实。) + +在这种情况下,我们在哪里设置了带有字符串的参数?我们最初使用字符串设置损失,但我们不再这样做了。但是,我们正在使用字符串设置优化器。难道这对我们隐瞒了什么?让我们看看[关于它的一些讨论](https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/Adam)。 + +这里有什么需要注意的吗?没错——学习率!当我们只使用字符串“adam”时,我们将获得默认的学习率,即 0.001,即 1e-3。这对于transormer模型来说太高了!一般来说,我们建议您的模型尝试 1e-5 和 1e-4 之间的学习率;这比我们在这里实际使用的值小 10X 到 100X 之间。听起来这可能是一个主要问题,所以让我们尝试减少它。为此,我们需要导入实际的“优化器”对象。当我们这样做的时候,让我们从检查点重新初始化模型,以防高学习率的训练损坏了它的权重: + +```python +from tensorflow.keras.optimizers import Adam + +model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint) +model.compile(optimizer=Adam(5e-5)) +``` + + + +💡您还可以从🤗 Transformers 中导入 `create_optimizer()` 函数,这将为您提供具有正确权重衰减以及学习率预热和学习率衰减的 AdamW 优化器。 此优化器通常会产生比使用默认 Adam 优化器获得的结果稍好一些的结果。 + + + +现在,我们可以尝试使用新的、改进后的学习率来拟合模型: + +```python +model.fit(train_dataset) +``` + +```python out +319/24543 [..............................] - ETA: 16:07 - loss: 0.9718 +``` + +现在我们的损失真的在某个地方! 训练终于看起来奏效了。 这里有一个教训:当你的模型正在运行但损失没有下降,并且你确定你的数据没问题时,检查学习率和权重衰减等超参数是个好主意。 将其中任何一个设置得太高很可能导致训练在高损失值下“停滞”。 + +## 其他潜在问题 + +我们已经涵盖了上面脚本中的问题,但您可能会遇到其他几个常见错误。 让我们看一个(非常不完整的)列表。 + +### 处理内存不足错误 + +内存不足的迹象是“分配张量时出现 OOM”之类的错误——OOM 是“内存不足”的缩写。 在处理大型语言模型时,这是一个非常常见的危险。 如果遇到这种情况,一个好的策略是将批量大小减半并重试。 但请记住,有些型号*非常*大。 例如,全尺寸 GPT-2 的参数为 1.5B,这意味着您将需要 6 GB 的内存来存储模型,另外需要 6 GB 的内存用于梯度下降! 无论您使用什么批量大小,训练完整的 GPT-2 模型通常需要超过 20 GB 的 VRAM,而只有少数 GPU 拥有。 像“distilbert-base-cased”这样更轻量级的模型更容易运行,训练也更快。 + + + +在课程的下一部分中,我们将介绍更先进的技术,这些技术可以帮助您减少内存占用并让您微调最大的模型。 + + + +### TensorFlow 🦛饿饿 + +您应该注意的 TensorFlow 的一个特殊怪癖是,它会在您加载模型或进行任何训练后立即为自己分配 *所有 * GPU 内存,然后根据需要分配该内存。这与其他框架的行为不同,例如 PyTorch,后者根据 CUDA 的需要分配内存,而不是在内部进行。 TensorFlow 方法的一个优点是,当您耗尽内存时,它通常会给出有用的错误,并且它可以从该状态恢复而不会导致整个 CUDA 内核崩溃。但也有一个重要的缺点:如果你同时运行两个 TensorFlow 进程,那么**你将度过一段糟糕的时光**。 + +如果您在 Colab 上运行,则无需担心这一点,但如果您在本地运行,这绝对是您应该小心的事情。特别要注意,关闭笔记本选项卡并不一定会关闭该笔记本!您可能需要选择正在运行的笔记本(带有绿色图标的笔记本)并在目录列表中手动关闭它们。任何使用 TensorFlow 的正在运行的笔记本仍可能占用大量 GPU 内存,这意味着您启动的任何新笔记本都可能会遇到一些非常奇怪的问题。 + +如果您开始运行之前正确的代码却收到有关 CUDA、BLAS 或 cuBLAS 的错误,这通常是罪魁祸首。您可以使用类似 `nvidia-smi` 的命令来检查 - 当您关闭或重新启动当前笔记本时,您的大部分内存是否空闲,或者是否仍在使用中?如果它仍在使用中,则有其他东西在占用它! + +### 检查您的数据(再次!) + +只有在理论上可以从您的数据中学到任何东西时,您的模型才会学到一些东西。 如果存在损坏数据的错误或标签是随机属性的,那么您很可能不会在数据集上获得任何知识。这里一个有用的工具是`tokenizer.decode()`。 这会将 `input_ids` 转换回字符串,因此您可以查看数据并查看您的训练数据是否正在教授您希望它教授的内容。 例如,像我们上面所做的那样从 `tf.data.Dataset` 中获取 `batch` 后,您可以像这样解码第一个元素: + +```py +input_ids = batch["input_ids"].numpy() +tokenizer.decode(input_ids[0]) +``` + +Then you can compare it with the first label, like so: + +```py +labels = batch["labels"].numpy() +label = labels[0] +``` +一旦您可以像这样查看您的数据,您可以问自己以下问题: + +- 解码后的数据是否可以理解? +- 你认同这些标签吗? +- 有没有一个标签比其他标签更常见? +- 如果模型预测随机的答案/总是相同的答案,那么loss/评估指标应该是多少? + +查看您的数据后,查看模型的一些预测并对其进行解码。 如果模型总是预测同样的事情,那可能是因为你的数据集偏向一个类别(针对分类问题); 过采样稀有类等技术可能会有所帮助。 + +如果您在初始模型上获得的loss/评估指标与您期望的随机预测的loss/评估指标非常不同,请仔细检查您的loss或评估指标的计算方式,因为那里可能存在错误。 如果您使用最后添加的多个loss,请确保它们具有相同的规模。 + +当您确定您的数据是完美的时,您可以通过一个简单的测试来查看模型是否能够对其进行训练。 + +### 在一批上过度拟合你的模型 + +过度拟合通常是我们在训练时尽量避免的事情,因为这意味着模型没有学习识别我们想要的一般特征,而只是记住了训练样本。 但是,一遍又一遍地尝试在一个批次上训练您的模型是一个很好的测试,可以检查您构建的问题是否可以通过您尝试训练的模型来解决。 它还将帮助您查看您的初始学习率是否太高。 + +一旦你定义了你的“模型”,这样做真的很容易; 只需获取一批训练数据,然后将该“批次”视为您的整个数据集,并在其上fit大量epoch: + +```py +for batch in train_dataset: + break + +# Make sure you have run model.compile() and set your optimizer, +# and your loss/metrics if you're using them + +model.fit(batch, epochs=20) +``` + + + +💡 如果您的训练数据不平衡,请确保构建一批包含所有标签的训练数据。 + + + +生成的模型在“批次”上应该有接近完美的结果,损失迅速下降到 0(或您正在使用的损失的最小值)。 + +如果你没有设法让你的模型获得这样的完美结果,这意味着你构建问题或数据的方式有问题,所以你应该修复它。 只有当你设法通过过拟合测试时,你才能确定你的模型实际上可以学到一些东西。 + + + +⚠️ 在此测试之后,您将不得不重新创建您的模型和“Trainer”,因为获得的模型可能无法在您的完整数据集上恢复和学习有用的东西。 + + + +### 在你有第一个基线之前不要调整任何东西 + +超参数调整总是被强调为机器学习中最难的部分,但这只是帮助您在指标上获得一点点提升的最后一步。 例如将默认的 Adam 学习率 1e-3 与 Transformer 模型一起使用,当然会使学习进行得非常缓慢或完全停止,但大多数时候“合理”的超参数,例如从 1e-5 到 5e-5 的学习率,会很好地给你带来好的结果。因此,在您获得超出数据集基线的东西之前,不要开始进行耗时且昂贵的超参数搜索。 + +一旦你有一个足够好的模型,你就可以开始稍微调整一下。 不要尝试使用不同的超参数启动一千次运行,而是比较一个超参数的不同值的几次运行,以了解哪个影响最大。 + +如果您正在调整模型本身,不要尝试任何您无法合理证明的事情。 始终确保您返回过拟合测试以验证您的更改没有产生任何意外后果。 + +### 请求帮忙 + +希望您会在本节中找到一些可以帮助您解决问题的建议,但如果不是这样,请记住您可以随时在 [论坛](https://discuss.huggingface.co/) 上向社区提问。 + +以下是一些可能有用的额外资源: + +- [“作为工程最佳实践工具的再现性”](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p),作者:Joel Grus +- [“神经网络调试清单”](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) 作者:Cecelia Shao +- [“如何对机器学习代码进行单元测试”](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) by Chase Roberts +- [“训练神经网络的秘诀”](http://karpathy.github.io/2019/04/25/recipe/)作者:Andrej Karpathy + +当然,并不是你在训练神经网络时遇到的每一个问题都是你自己的错! 如果您在 🤗 Transformers 或 🤗 Datasets 库中遇到看起来不正确的内容,您可能遇到了错误。 你应该告诉我们这一切,在下一节中,我们将准确解释如何做到这一点。 diff --git a/chapters/zh-CN/chapter8/5.mdx b/chapters/zh-CN/chapter8/5.mdx new file mode 100644 index 000000000..c620cc9a7 --- /dev/null +++ b/chapters/zh-CN/chapter8/5.mdx @@ -0,0 +1,85 @@ +# 如何写一个好问题 + + + +当您遇到 Hugging Face 库中的一个看起来不正确的东西时,您一定要告诉我们,以便我们可以修复它(就此而言,任何开源库也是如此)。如果您不能完全确定错误是在您自己的代码中还是在我们的某个库中,那么首先要检查的是[forums](https://discuss.huggingface.co/).社区会帮助你解决这个问题,Hugging Face 团队也会密切关注那里的讨论。 + + + +当您确定手头有错误时,第一步是构建一个最小的可重现示例。 +## 创建一个最小的可重现示例 + +隔离产生错误的代码段非常重要,因为 Hugging Face 团队中没有人是魔术师(目前),他们无法修复他们看不到的东西。顾名思义,最小的可重现示例应该是可重现的。这意味着它不应依赖于您可能拥有的任何外部文件或数据。尝试用一些看起来像真实值的虚拟值替换您正在使用的数据,但仍然会产生相同的错误。 + + + +🚨🤗 Transformers 存储库中的许多问题都没有解决,因为用于复制它们的数据不可访问。 + + + +一旦你有一些自包含的东西,你可以尝试将它减少到更少的代码行,构建我们所谓的最小的可重复示例.虽然这需要你做更多的工作,但如果你提供一个漂亮的、简短的错误重现器,你几乎可以保证得到帮助和修复。 + +如果您觉得足够舒服,请检查发生错误的源代码。您可能会找到问题的解决方案(在这种情况下,您甚至可以提出拉取请求来修复它),但更一般地说,这可以帮助维护人员在阅读您的报告时更好地理解来源。 + +## 填写问题模板 + +当您提交问题时,您会注意到有一个模板需要填写。我们将按照[🤗 Transformers issues](https://github.com/huggingface/transformers/issues/new/choose)在这里,但是如果您在另一个存储库中报告问题,则需要相同类型的信息。不要将模板留空:花时间填写它可以最大限度地提高您获得答案和解决问题的机会。 + +通常,在提交问题时,请始终保持礼貌。这是一个开源项目,因此您使用的是免费软件,没有人有任何义务帮助您。您可能会在您的问题中包含您认为合理的批评,但是维护人员很可能会认为它很糟糕并且不会急于帮助您。确保你阅读了[code of conduct](https://github.com/huggingface/transformers/blob/master/CODE_OF_CONDUCT.md)项目的。 + +### 包括您的环境信息 + +🤗 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** 命令从笔记本单元执行它,然后在问题的开头复制并粘贴结果。 + +### 标记人员 + +通过输入标记人员 **@** 其次是他们的 GitHub 句柄将向他们发送通知,以便他们会看到您的问题并可能会更快地回复。适度使用它,因为如果您标记的人没有直接链接,他们可能不喜欢收到通知。如果您查看了与您的错误相关的源文件,您应该在您认为对您的问题负责的行中标记最后一个进行更改的人(您可以通过查看 GitHub 上的所述行找到此信息,选择它,然后单击“查看 git blame”)。 + +否则,模板会提供要标记的人的建议。一般来说,不要标记超过三个人! + +### 包含一格可重复的示例 + +如果您已经设法创建了一个产生错误的独立示例,那么现在是包含它的时候了!键入一行包含三个反引号,后跟 **python** , 像这样: + +``` +```python +``` + +然后粘贴您的最小可重现示例并键入一个带有三个反引号的新行。这将确保您的代码格式正确。如果您没有设法创建可重现的示例,请以清晰的步骤解释您是如何解决问题的。如果可以,请包含指向错误所在的 Google Colab 笔记本的链接。你分享的信息越多,维护者就越有能力回复你。在所有情况下,您都应该复制并粘贴您收到的整个错误消息。如果您在 Colab 中工作,请记住,堆栈跟踪中的某些帧可能会自动折叠,因此请确保在复制之前展开它们。与代码示例一样,将该错误消息放在两行之间,并带有三个反引号,因此格式正确。 + +### 描述预期行为 + +用几行解释你期望得到什么,以便维护人员完全掌握问题。这部分通常很明显,所以应该用一句话来形容,但在某些情况下,您可能有很多话要说。 + +## 然后什么? + +提交您的问题后,请确保快速检查一切是否正常。如果您犯了错误,您可以编辑问题,或者如果您发现问题与您最初的想法不同,甚至可以更改其标题。如果你没有得到答案,就没有必要对人进行 ping 操作。如果几天内没有人帮助您,很可能没有人能理解您的问题。不要犹豫,回到可重现的例子。你能不能让它更短更切题?如果您在一周内没有得到答复,您可以留言温和地寻求帮助,特别是如果您已编辑问题以包含有关该问题的更多信息。 + diff --git a/chapters/zh-CN/chapter8/6.mdx b/chapters/zh-CN/chapter8/6.mdx new file mode 100644 index 000000000..a13a9b23c --- /dev/null +++ b/chapters/zh-CN/chapter8/6.mdx @@ -0,0 +1,7 @@ +# 第2部分完成! + +恭喜,您已经完成了课程的第二部分!我们正在积极研究第三个,所以订阅我们的[newsletter](https://huggingface.curated.co/)以确保您不会错过它的发布。 + +。您现在应该能够处理一系列 NLP 任务,并对它们进行微调或预训练模型。不要忘记与社区分享您的结果[Model Hub](https://huggingface.co/models). + +我们迫不及待地想看看您将利用所获得的知识构建什么! diff --git a/chapters/zh-CN/chapter8/7.mdx b/chapters/zh-CN/chapter8/7.mdx new file mode 100644 index 000000000..45f4c7ee7 --- /dev/null +++ b/chapters/zh-CN/chapter8/7.mdx @@ -0,0 +1,190 @@ + + +# 章末测评 + +让我们测试一下你在本章学到的东西! + +### 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) +``` + +以下哪项可能是论坛主题标题寻求帮助的好选择? + + 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 包含有关计算环境的详细信息是个好主意? + \ No newline at end of file diff --git a/chapters/zh-CN/chapter9/1.mdx b/chapters/zh-CN/chapter9/1.mdx new file mode 100644 index 000000000..2cb3dca85 --- /dev/null +++ b/chapters/zh-CN/chapter9/1.mdx @@ -0,0 +1,36 @@ +# Gradio 简介 + +在本章中,我们将学习如何为您的机器学习构建**交互式演示**模型。 + +为什么首先要为您的机器学习模型构建演示或 GUI?演示可以带来: + +- **机器学习开发人员**可以轻松地向包括非技术团队或客户在内的广大受众展示他们的工作 +- **研究人员**更轻松地重现机器学习模型和行为 +- **质量测试人员**或**最终用户**更容易识别和调试模型的故障点 +- **不同的用户**发现模型中的算法偏差 + +我们将使用 Gradio 库为我们的模型构建演示。 Gradio 允许您完全使用 Python 为任何机器学习模型构建、自定义和共享基于 Web 的演示。 + +以下是一些使用 Gradio 构建的机器学习演示示例: + +* 一个**草图识别**模型,它接收草图并输出它认为正在绘制的标签: + + + +* 一个抽取式**问题回答**模型,它接受上下文段落和一个任务并输出一个结果和一个概率分数(我们在[第7章](/course/chapter7/7)中讨论了这种模型): + + + +* 一个**背景去除**模型,它接收图像并输出去除背景的图像: + + + +本章分为两个部分,包括_概念_和_应用程序_。在您了解每个部分的概念后,您将应用它来构建特定类型的演示,范围从图像分类到语音识别。当你读完本章时,你将能够用几行 Python 代码构建这些演示(以及更多!)。 + +👀 点击 Hugging Face Spaces 以查看机器学习社区构建的许多机器学习演示的最新示例! + +## Gradio 方块派对🥳 + +如果你想充分利用本章中的知识,就加入 Gradio 积木派对吧!这是由 Hugging Face 于**5 月 16 日至 31 日**举办的社区活动。在此活动中,您将使用 Gradio 构建酷炫的机器学习演示,并参与赢取 Hugging Face 礼物和奖品! + +查看 [活动描述](https://github.com/AK391/community-events/blob/main/gradio-blocks/README.md) 可以了解如何参与的详细信息 - 我们迫不及待地想看看你构建的🤗演示! diff --git a/chapters/zh-CN/chapter9/2.mdx b/chapters/zh-CN/chapter9/2.mdx new file mode 100644 index 000000000..6c53eb619 --- /dev/null +++ b/chapters/zh-CN/chapter9/2.mdx @@ -0,0 +1,112 @@ +# 构建你的第一个演示 + + + +让我们从安装 Gradio 开始吧! 由于它是一个 Python 包,只需运行: + +`$ pip install gradio ` + +您可以在任何地方运行 Gradio,无论是从您最喜欢的 Python IDE、Jupyter notebook 还是 Google Colab 🤯! +所以无论你在哪里运行 Python,都可以安装 Gradio! + +让我们从一个简单的“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”的简单函数,但它通常可以是 *any* Python 函数。 例如,在机器学习应用程序中,此函数将*调用模型以对输入进行预测*并返回输出。 +- 然后,我们创建一个带有三个参数的 Gradio `Interface`,`fn`、`inputs` 和 `outputs`。 这些参数定义了预测函数,以及我们想要的输入和输出组件的_type_。 在我们的例子中,两个组件都是简单的文本框。 +- 然后我们在我们创建的 `Interface` 上调用 `launch()` 方法。 + +如果你运行这段代码,下面的界面会自动出现在 Jupyter/Colab notebook 中,或者在浏览器中弹出 **[http://localhost:7860](http://localhost:7860/)** 如果运行 从一个脚本。 + + + +立即尝试使用您自己的姓名或其他输入来使用此 GUI! + +您会注意到,在这个 GUI 中,Gradio 自动推断输入参数的名称 (`name`)并将其应用为文本框顶部的标签。 如果你想改变它怎么办?或者,如果您想以其他方式自定义文本框? 在这种情况下,您可以实例化一个表示输入组件的类对象。 + +看看下面的例子: + +```py +import gradio as gr + + +def greet(name): + return "Hello " + name + + +# We instantiate the Textbox class +textbox = gr.Textbox(label="Type your name here:", placeholder="John Doe", lines=2) + +gr.Interface(fn=greet, inputs=textbox, outputs="text").launch() +``` + + + +在这里,我们创建了一个带有标签、占位符和一组行数的输入文本框。您可以对输出文本框执行相同的操作,但我们现在将其保留。 + +我们已经看到,只需几行代码,Gradio 就可以让您围绕任何具有任何类型输入或输出的函数创建一个简单的界面。 在本节中,我们从一个简单的文本框开始,但在接下来的部分中,我们将介绍其他类型的输入和输出。 现在让我们看看在 Gradio 应用程序中包含一些 NLP。 + + +## 🤖 包括模型预测 + +现在让我们构建一个简单的界面,让您可以演示像 GPT-2 这样的**文本生成**模型。 + +我们将使用 🤗 Transformers 中的 `pipeline()` 函数加载我们的模型。 +如果您需要快速复习,您可以返回到 [第 1 章中的那个部分](/course/chapter1/3#text-generation)。 + +首先,我们定义一个接受文本提示并返回文本完成的预测函数: + +```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. +``` + +现在我们有了一个生成预测的函数,我们可以像之前一样创建和启动一个“接口”: + +```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/zh-CN/chapter9/3.mdx b/chapters/zh-CN/chapter9/3.mdx new file mode 100644 index 000000000..294b04a4b --- /dev/null +++ b/chapters/zh-CN/chapter9/3.mdx @@ -0,0 +1,167 @@ +# 了解接口类 + + + +在本节中,我们将仔细研究 `Interface` 类,并了解用于创建其的主要参数。 + +## 如何创建接口 + +您会注意到 `Interface` 类有 3 个必需参数: + +`Interface(fn, inputs, outputs, ...)` + +这些参数是: + + - `fn`: 由 Gradio 接口包装的预测函数。 该函数可以接受一个或多个参数并返回一个或多个值 + - `inputs`: 输入组件类型。 Gradio 提供了许多预构建的组件,例如`"image"` 或`"mic"`。 + - `outputs`: 输出组件类型。 同样,Gradio 提供了许多预构建的组件,例如 `“图像”`或“标签”`。 + +有关组件的完整列表,[请参阅 Gradio 文档](https://gradio.app/docs)。 每个预构建的组件都可以通过实例化该组件对应的类来定制。 + +例如,正如我们在 [上一节](/course/chapter9/2) 中看到的,您可以传入一个 `Textbox(lines=7, label="Prompt")` 组件来创建一个包含 7 行和一个标签的文本框,而不是将 `"textbox"` 传递给 `inputs` 参数。 +让我们看另一个例子,这次是一个 `Audio` 组件。 + +## 一个带音频的简单示例 + +如前所述,Gradio 提供了许多不同的输入和输出。 +因此,让我们构建一个适用于音频的“接口”。 + +在这个例子中,我们将构建一个音频到音频的函数,它需要一个音频文件并简单地反转它。 + +我们将使用 `Audio` 组件作为输入。 使用 `Audio` 组件时,您可以指定希望音频的 `source` 是用户上传的文件还是用户录制声音的麦克风。 在这种情况下,让我们将其设置为“麦克风”。 只是为了好玩,我们会在我们的“音频”中添加一个标签,上面写着“在这里说话……”。 + +此外,我们希望将音频作为 numpy 数组接收,以便我们可以轻松地“反转”它。 所以我们将 `"type"` 设置为 `"numpy"`,它会传递输入data 作为 (`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() +``` + +上面的代码会产生一个类似下面的界面(如果你的浏览器没有 +询问您的麦克风权限, open the demo in a separate tab.) + + + +您现在应该能够录制自己的声音并听到自己在反向说话 - 怪异 👻! + +## 处理多个输入和输出 + +假设我们有一个更复杂的函数,有多个输入和输出。在下面的示例中,我们有一个接受下拉索引、滑块值和数字的函数,并返回一个音调的音频样本。 + +看看我们如何传递输入和输出组件列表,看看你能不能跟上正在发生的事情。 + +这里的关键是当你通过时: +* 输入组件列表,每个组件依次对应一个参数。 +* 输出组件列表,每个组件对应一个返回值。 + +下面的代码片段显示了三个输入组件如何与 `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()` 方法 + +到目前为止,我们已经使用了`launch()`方法来启动界面,但是我们 +还没有真正讨论过它的作用。 + +默认情况下,`launch()` 方法将在 Web 服务器中启动演示正在本地运行。 如果您在 Jupyter 或 Colab 笔记本中运行代码,那么Gradio 会将演示 GUI 嵌入到笔记本中,以便您轻松使用它。 + +您可以通过不同的参数自定义 `launch()` 的行为: + + - `inline` - whether to display the interface inline on Python notebooks. + - `inbrowser` - whether to automatically launch the interface in a new tab on the default browser. + - `share` - whether to create a publicly shareable link from your computer for the interface. Kind of like a Google Drive link! + +我们将在下一节中更详细地介绍 `share` 参数! + +## ✏️ 让我们应用它! + +让我们构建一个界面,让您演示 **speech-recognition** 模型。 +为了让它变得有趣,我们将接受 *or* 麦克风输入或上传的文件。 + +像往常一样,我们将使用 🤗 Transformers 中的 `pipeline()` 函数加载我们的语音识别模型。如果您需要快速复习,您可以返回 [第 1 章中的那个部分](/course/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() +``` + +如果您的浏览器没有要求您提供麦克风权限,open the demo in a separate tab. + + + + +就是这样! 您现在可以使用此界面来转录音频。 注意这里 +通过将 `optional` 参数作为 `True` 传递,我们允许用户 +提供麦克风或音频文件(或两者都不提供,但这会返回错误消息)。 + +继续看看如何与他人分享您的界面! \ No newline at end of file diff --git a/chapters/zh-CN/chapter9/4.mdx b/chapters/zh-CN/chapter9/4.mdx new file mode 100644 index 000000000..4e10fc77b --- /dev/null +++ b/chapters/zh-CN/chapter9/4.mdx @@ -0,0 +1,144 @@ +# 与他人分享演示 + + + +现在您已经构建了一个演示,您可能希望与其他人分享它。 梯度演示 +可以通过两种方式共享:使用 ***temporary share link*** 或 ***permanent hosting on Spaces***。 + +我们将很快介绍这两种方法。 但在分享演示之前,您可能需要完善它 💅. + +### 打磨你的 Gradio 演示: + +
+Overview of a gradio interface + +
+ +为了给你的演示添加额外的内容,`Interface` 类支持一些可选参数: + - `title`:你可以给你的演示一个标题,它出现在输入和输出组件的上方。 + - `description`:您可以为界面提供描述(文本、Markdown 或 HTML),显示在输入和输出组件的上方和标题下方。 + - `article`:您还可以编写扩展文章(文本、Markdown 或 HTML)来解释界面。如果提供,它会出现在输入和输出组件的_下方。 + - `theme`:不喜欢默认颜色?将主题设置为使用 `default`、`huggingface`、`grass`、`peach` 之一。您还可以添加 `dark-` 前缀,例如`dark-peach` 用于深色主题(或者只是 `dark` 用于默认的深色主题)。 + - `examples`:为了让您的演示*更易于使用*,您可以为函数提供一些示例输入。它们出现在 UI 组件下方,可用于填充界面。这些应该作为嵌套列表提供,其中外部列表​​由样本组成,每个内部列表对应于每个输入组件的输入组成。 + - `live`:如果你想让你的演示“活”,这意味着你的模型每次输入更改时都会重新运行,你可以设置 `live=True`。这对使用快速模型很有意义(我们将在本节末尾看到一个示例) +使用上面的选项,我们最终得到了一个更完整的界面。 运行下面的代码,以便与 Rick and Morty 聊天: + +```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() +``` + +使用上面的选项,我们最终得到了一个更完整的界面。 试试下面的界面: + + + +### 使用临时链接分享您的演示 +现在我们已经有了机器学习模型的工作演示,让我们学习如何轻松共享指向我们界面的链接。 +通过在 `launch()` 方法中设置 `share=True` 可以轻松地公开共享接口: + +```python +gr.Interface(classify_image, "image", "label").launch(share=True) +``` + +这会生成一个公开的、可共享的链接,您可以将其发送给任何人! 当您发送此链接时,另一方的用户可以在浏览器中试用该模型长达 72 小时。 因为处理发生在您的设备上(只要您的设备保持开启!),您不必担心打包任何依赖项。 如果您使用 Google Colab 笔记本工作,则始终会自动创建共享链接。 它通常看起来像这样:**XXXXX.gradio.app**。 虽然链接是通过 Gradio 链接提供的,但我们只是您本地服务器的代理,不会存储通过接口发送的任何数据。 + +但是请记住,这些链接是可公开访问的,这意味着任何人都可以使用您的模型进行预测! 因此,请确保不要通过您编写的函数公开任何敏感信息,或允许在您的设备上发生任何关键更改。 如果设置 `share=False`(默认值),则仅创建本地链接。 + +### 在 Hugging Face Spaces 上托管您的演示 + +可以传递给同事的共享链接很酷,但是如何永久托管您的演示并让它存在于互联网上自己的“空间”中? + +Hugging Face Spaces 提供了在互联网上永久托管 Gradio 模型的基础设施,**免费**! Spaces 允许您创建并推送到(公共或私人)存储库, +你的 Gradio 在哪里 +接口代码将存在于 `app.py` 文件中。 [阅读分步教程](https://huggingface.co/blog/gradio-spaces) 开始使用,或观看下面的示例视频。 + + + +## ✏️ 让我们应用它! + +使用到目前为止我们在各节中学到的知识,让我们创建我们在[本章第一节](/course/chapter9/1)中看到的草图识别演示。 让我们为我们的界面添加一些自定义并设置 `share=True` 以创建一个我们可以传递的公共链接。 + +我们可以从 [class_names.txt](https://huggingface.co/spaces/dawood/Sketch-Recognition/blob/main/class_names.txt) 加载标签,并从 [pytorch_model.bin](https://huggingface.co/spaces/dawood/Sketch-Recognition/blob/main/pytorch_model.bin)加载预训练的 pytorch 模型 。 通过点击链接并单击文件预览左上角的下载来下载这些文件。 让我们看看下面的代码,看看我们如何使用这些文件来加载我们的模型并创建一个`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()`函数。 下一步是定义并启动我们的渐变界面: + +```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) +``` + + + + +注意 `Interface` 中的 `live=True` 参数,这意味着草图演示使 +每次有人在画板上画画时的预测(没有提交按钮!)。 + +此外,我们还在 `launch()` 方法中设置了 `share=True` 参数。 +这将创建一个公共链接,您可以发送给任何人! 当您发送此链接时,对方的用户可以尝试草图识别模型。 重申一下,您还可以在 Hugging Face Spaces 上托管模型,这就是我们能够嵌入上面的演示的方式。 + +接下来,我们将介绍 Gradio 可用于 Hugging Face 生态系统的其他方式! \ No newline at end of file diff --git a/chapters/zh-CN/chapter9/5.mdx b/chapters/zh-CN/chapter9/5.mdx new file mode 100644 index 000000000..71bc125a0 --- /dev/null +++ b/chapters/zh-CN/chapter9/5.mdx @@ -0,0 +1,66 @@ +# 与 Hugging Face Hub 整合 + + + +为了让你的生活更轻松, Gradio 直接与 Hugging Face Hub 和 Hugging Face Spaces 集成。你可以仅使用 *一行代码* 从中心和空间加载演示。 + +### 从 Hugging Face Hub 加载模型 +首先, 从 Hugging Face 通过 Hub 提供的数千个模型中选择一个, 如 [第四章](/course/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

" +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() +``` + +上述代码将生成以下界面: + + + +以这种方式加载模型使用 Hugging Face 的 [Inference API](https://huggingface.co/inference-api),而不是将模型加载到内存中。这对于像 GPT-J 或 T0pp这样需要大量 RAM 的大型模型是理想的。 + +### 从 Hugging Face Spaces 空间加载 +要从hugs 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与hugs Face Hub集成的方法, 让我们来看看 `Interface` 类的一些高级功能。这就是下一节的主题! \ No newline at end of file diff --git a/chapters/zh-CN/chapter9/6.mdx b/chapters/zh-CN/chapter9/6.mdx new file mode 100644 index 000000000..51f6ca0a8 --- /dev/null +++ b/chapters/zh-CN/chapter9/6.mdx @@ -0,0 +1,97 @@ +# 高级接口功能 + + + +现在我们可以构建和共享一个基本接口, 让我们来探索一些更高级的特性, 如状态和解释。 + +### 使用状态保存数据 + +Gradio 支持 *会话状态*, 其中数据在页面加载中的多个提交中持续存在。会话状态对于构建演示很有用, 例如, 你希望在用户与模型交互时保留数据的聊天机器人。请注意, 会话状态不会在模型的不同用户之间共享数据。 + +要将数据存储在会话状态中, 你需要做三件事: + +1. 向函数中传递一个 *额外的参数* , 该参数表示接口的状态。 +1. 在函数结束时, 将状态的更新值作为 *额外的返回值* 返回。 +1. 在创建`接口`时添加 'state' 输入和 'state' 输出组件。 + +请参阅下面的聊天机器人示例: + +```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 参数传入一个默认值, 作为 state 的初始值。 + +### 通过解释来理解预测 + +大多数机器学习模型都是黑盒子, 函数的内部逻辑对终端用户是隐藏的。为了提高透明度, 我们通过简单地将 Interface 类中的解释关键字设置为默认值, 使向模型添加解释变得非常容易。这允许你的用户理解输入的哪些部分负责输出。看看下面这个简单的接口, 它显示了一个还包括解释的图像分类器: + +```py +import requests +import tensorflow as tf + +import gradio as gr + +inception_net = tf.keras.applications.MobileNetV2() # load the model + +# Download human-readable labels for 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提供的默认解释方法之外, 你还可以为 `interpretation` 参数指定 `shap`, 并设置 `num_shap` 参数。这使用基于 Shapley 的解释, 你可以在 [here](https://christophm.github.io/interpretable-ml-book/shap.html) 阅读更多信息。最后, 还可以将自己的解释函数传入 `interpretation` 参数。在Gradio的入门页面 [here](https://gradio.app/getting_started/) 中可以看到一个例子。 + +这结束了我们对Gradio的`Interface`类的深入研究。正如我们所看到的, 这个类使用几行Python代码创建机器学习演示变得简单。然而, 有时你会想通过改变布局或链接多个预测函数来定制你的demo。如果我们能以某种方式将 `接口` 分成可定制的 "块", 那不是很好吗? 幸运的是, 有! 这是最后一部分的主题。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter9/7.mdx b/chapters/zh-CN/chapter9/7.mdx new file mode 100644 index 000000000..56b9eed58 --- /dev/null +++ b/chapters/zh-CN/chapter9/7.mdx @@ -0,0 +1,236 @@ +# Gradio 块简介 + + + +在前面的部分中, 我们已经使用 `Interface` 类探索并创建了演示。在本节中, 我们将介绍我们 **新开发**的称为`gradio.Blocks`低级API。 + +现在, `接口`和`块`之间有什么区别? + +- ⚡ `接口`: 一个高级 API, 让你只需提供输入和输出列表即可创建完整的机器学习演示。 + +- 🧱 `块`: :一个低级的 API, 它允许你完全控制你的应用程序的数据流和布局。您可以使用`块`(如 "构建块")构建非常复杂的多步骤应用程序。 + + +### 为什么要块 🧱? + +正如我们在前几节中看到的, `Interface` 类允许你使用几行代码轻松创建成熟的机器学习demo。`Interface` API 非常易于使用, 但缺乏 `Blocks` API 提供的灵活性。例如, 你可能想要: + +- 将相关演示组合为一个web应用程序中的多个选项卡 +- 更改demo的布局, 例如指定输入和输出的位置 +- 具有多步骤接口, 其中一个模型的输出成为下一个模型的输入, 或者通常具有更灵活的数据流 +- 根据用户输入更改组件的属性 (例如, 下拉列表中的选项) 或其可见性 + +我们将在下面探讨所有这些概念。 + +### 使用块创建简单demo + +安装 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. 块允许你允许你构建结合markdown、HTML、按钮和交互组件的web应用程序, 只需在一个带有gradio的Python中实例化对象。 + +🙋如果你不熟悉 Python 中的 `with` 语句, 我们建议你查看来自 Real Python 的极好的[教程](https://realpython.com/python-with-statement/)。看完后回到这里 🤗 + +实例化组件的顺序很重要, 因为每个元素都按照创建的顺序呈现到 Web 应用程序中。(更复杂的布局在下面讨论) + +2. 你可以在代码中的任何位置定义常规 Python 函数, 并使用`块`在用户输入的情况下运行它们。在我们的示例中, 们有一个"翻转"输入文本的简单函数, 但你可以编写任何 Python 函数, 从简单的计算到处理机器学习模型的预测。 + +3. 你可以将事件指定给任何`块`组件。这将在组件被单击、更改等情况下运行函数。当你分配一个事件时, 你传入三个参数: `fn`: 应该被调用的函数, `inputs`: 输入组件的(列表), 以及 `outputs`: 应该被调用的输出组件的(列表)。 + + 在上面的示例中, 当名为 `input` 的 `Textbox` 中的值发生变化时, 我们运行 `flip_text()` 函数。该事件读取`输入`中的值, 将其作为名称参数传递给 `flip_text()`, 然后它返回一个值, 该值被分配给我们的第二个名为 `output` 的 `Textbox`。 + + 要查看每个组件支持的事件列表, 请参阅 Gradio [文档](https://www.gradio.app/docs/)。 + +4. 块会根据你定义的事件触发器自动确定组件是否应该是交互式的 (接受用户输入)。在我们的示例中, 第一个文本框是交互式的, 因为它的值由 `flip_text()` 函数使用。第二个文本框不是交互式的, 因为它的值从不用作输入。在某些情况下, 你可能想要覆盖它, 你可以通过传递一个布尔值给组件的`交互`参数(例如 `gr.Textbox(placeholder="Flip this text", interactive=True)`)。 + +### 自定义演示的布局 + +我们如何使用`块`来定制我们的演示的布局? 默认情况下, `块`在一列中垂直呈现创建的组件。你可以通过使用 `with gradio.Column():` 创建其他列或使用 `with gradio.Row():` 创建其他行并在这些上下文中创建组件来改变这一点。 + +你应该记住: 在 `列` 下创建的任何组件(这也是默认设置) 都将垂直布局。在 `Row` 下创建的任何组件都将水平布局, 类似于 [Web 开发中的 flexbox 模型](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox)。 + +最后, 你还可以使用 `with gradio.Tabs()` 上下文管理器为您的demo创建选项卡。在此上下文中, 您可以通过使用 `gradio.TabItem(name_of_tab):` 指定来创建多个选项卡。在 `gradio.TabItem(name_of_tab):` 中创建的任何组件都会出现在该选项卡中。 + +现在让我们在demo中添加一个 `flip_image()`函数并添加一个翻转图像的新选项卡。下面是一个带有 2 个选项卡的示例, 也使用了一个行: + +```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` 组件, 并且我们为每个按钮分配了一个点击事件,这是实际运行该函数的事件。 + +### 探索事件和状态 + +正如你可以控制布局一样, `块` 可以让你对触发函数调用的事件进行细粒度控制。每个组件和许多布局都有它们支持的特定事件。 + +例如, `Textbox` 组件有两个事件: `change()` (当文本框内的值发生变化时), 和 `submit()` (当用户在关注文本框时按下enter键)。更复杂的组件可以有更多的事件: 例如,`Audio`组件也有单独的事件, 用于播放、清除、暂停音频文件等。请参阅文档了解每个组件支持的事件。 + +你可以将事件触发器附加到这些事件中的一个、一个或多个。你可以通过在组件实例中调用事件名称作为函数来创建一个事件触发器 -- 例如 `textbox.change(...)` 或 `btn.click(...)`。如前所述, 该函数接受三个参数: + +- `fn`: 要运行的函数 +- `inputs`: 组件的(列表), 其值应作为函数的输入参数提供。每个组件的值按顺序映射到相应的函数参数。如果函数不带任何参数, 则此参数可以为 None。 +- `outputs`: 应根据函数返回的值更新其值的组件(列表)。每个返回值按顺序设置相应组件的值。如果函数不返回任何内容, 则此参数可以为None。 + +你甚至可以使输入和输出组件成为同一个组件, 就像我们在这个使用 GPT 模型进行文本补全的示例中所做的那样: + +```py +import gradio as gr + +api = gr.Interface.load("huggingface/EleutherAI/gpt-j-6B") + + +def complete_with_gpt(text): + # Use the last 50 characters of the text as context + 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() +``` + + + +### 创建多步demo + +在某些情况下, 您可能需要一个 _多步骤的demo_, 其中重用一个函数的输出作为下一个函数的输入。使用 `块` 很容易做到这一点, 因为你可以使用组件作为一个事件触发器的输入, 但作为另一个事件触发器的输出。看看下面示例中的文本组件, 它的值是语音到文本模型的结果, 但也被传递到情感分析模型: + +```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() +``` + + + +### 更新组件属性 + +到目前为止, 我们已经了解了如何创建事件来更新另一个组件的值。但是, 如果您想更改组件的其他属性, 例如文本框的可见性或单选按钮组中的选项, 会发生什么? 您可以通过返回组件类的 `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() +``` + + + +我们刚刚探索了`块`的所有核心概念! 就像 `参数一样`, 你可以创建很酷的demo, 可以通过在`launch()`方法中使用`share=True`来共享, 或者部署在[Hugging Face Spaces](https://huggingface.co/spaces)上。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter9/8.mdx b/chapters/zh-CN/chapter9/8.mdx new file mode 100644 index 000000000..846b00250 --- /dev/null +++ b/chapters/zh-CN/chapter9/8.mdx @@ -0,0 +1,19 @@ +# Gradio,回顾! + +关于使用 Gradio 构建酷炫的 ML 演示的章节到此结束 - 我们希望您喜欢它!回顾一下,在本章中,我们学习了: + +- 如何使用高级 `Interface` API 创建 Gradio 演示,以及如何配置不同的输入和输出模式。 +- 通过临时链接和托管在 [Hugging Face Spaces](https://huggingface.co/spaces) 上共享 Gradio 演示的不同方式。 +- 如何将 Gradio 演示与 Hugging Face Hub 上的Model和Space集成在一起。 +- 高级功能,例如在演示中存储状态或提供身份验证。 +- 如何使用 Gradio Blocks 完全控制演示的数据流和布局。 + +如果您想测试您对本章所涵盖概念的理解,请查看下一节中的测验! + +## 下一步去哪里? + +如果您想了解有关 Gradio 的更多信息,您可以 + +- 看看 repo 中的 [Demos](https://github.com/gradio-app/gradio/tree/main/demo),那里有很多例子。 +- 请参阅 [指南](https://gradio.app/guides/) 页面,您可以在其中找到有关酷炫和高级功能的指南。 +- 查看 [文档](https://gradio.app/docs/) 页面了解详情。 diff --git a/chapters/zh-CN/chapter9/9.mdx b/chapters/zh-CN/chapter9/9.mdx new file mode 100644 index 000000000..b3cbe287c --- /dev/null +++ b/chapters/zh-CN/chapter9/9.mdx @@ -0,0 +1,231 @@ + + + + +# 章末测验 + + + +让我们测试一下您在本章中学到了什么! + +### 1.你能利用Grado做什么? + share = True 参数,可以生成一个共享链接发送给任何人。", + correct: true + }, + { + text: "调试模型", + explain: "Grado演示的一个优点是能够用真实数据测试模型,您可以实时更改并观察模型的预测变化,从而帮助您调试模型。", + correct: true + }, + { + text: "训练你的模型", + explain: "在你的模型被训练之后,Grado 被设计用来进行模型推理。", + } + ]} +/> + +### 2.Grado只在 PyTorch 模型上工作 + + +### 3.你可以在哪里发布一个 GRadio 演示? + + +### 4.Gdio 主要是为 NLP 模型设计的 + + +### 5.下列哪些特性是由 Grado 支持的? + gr. Interface.load () 方法加载任何 Hugging Face 模型", + correct: true + } + ]} +/> + +### 6.下列哪一种是从 Hub 或 Spaces 加载 Huggging Face 模型的有效方法? + + +### 7.创建您的 Gradio 接口时,您必须添加以下步骤: + + +### 8.Gradio 库包括以下哪些组件? + + +### 9.Gradio允许你做什么? + + +### 10.你可以共享一个`Blocks`演示的公共链接,并创建一个`Blocks`的演示在HuggingFace空间。 + \ No newline at end of file diff --git a/chapters/zh-CN/event/1.mdx b/chapters/zh-CN/event/1.mdx new file mode 100644 index 000000000..cfebf9c41 --- /dev/null +++ b/chapters/zh-CN/event/1.mdx @@ -0,0 +1,165 @@ +# Part 2 发布活动 + +对于课程第 2 部分的发布,我们在微调 sprint 之前组织了一场现场活动,为期两天的会谈。 如果你错过了,你可以赶上下面列出的讲座! + +## Day 1: Transformer 的高级API以及如何训练它们 + +**Thomas Wolf:** *迁移学习和Transformers库的诞生* + +
+ +
+ +

+一张图总结 Thom 的演讲 +

+ +Thomas Wolf 是 Hugging Face 的联合创始人兼首席科学官。 Thomas Wolf 和 Hugging Face 团队创建的工具被 5,000 多个研究机构使用,包括 Facebook 人工智能研究、谷歌研究、DeepMind、亚马逊研究、苹果、艾伦人工智能研究所以及大多数大学系。 Thomas Wolf 是人工智能领域有史以来最大的研究合作的发起人和高级主席:[“BigScience”](https://bigscience.huggingface.co),以及一组广泛使用的 [库和工具](https://github.com/huggingface/)。 Thomas Wolf 还是一位多产的教育家、人工智能和自然语言处理领域的思想领袖,并且经常受邀在世界各地的会议上发表演讲 [https://thomwolf.io](https://thomwolf.io )。 + +**Jay Alammar:** *Transformers模型的图解* + +
+ +
+ +

+一张图总结 Jay 的演讲 +

+ +通过他广受欢迎的 ML 博客,Jay 帮助数百万研究人员和工程师直观地理解了机器学习工具和概念,从基础(最终出现在 NumPy、Pandas 文档)到前沿(Transformers、BERT、GPT-3)。 + +**Margaret Mitchell:** *关于机器学习开发中的价值观* + +
+ +
+ +

+一张图总结 Margaret 的演讲 +

+ +Margaret Mitchell 是一名从事人工智能伦理研究的研究员,目前专注于以伦理为依据的人工智能开发。她在自然语言生成、辅助技术、计算机视觉和人工智能伦理方面发表了 50 多篇论文,并在会话生成和情感分类领域拥有多项专利。她之前曾在 Google AI 担任员工研究科学家,在那里她创立并共同领导了 Google 的伦理 AI 小组,专注于基础 AI 伦理研究和在 Google 内部实施 AI 伦理。在加入谷歌之前,她是微软研究院的一名研究员,专注于计算机视觉到语言的生成;并且是约翰霍普金斯大学的博士后,专注于贝叶斯建模和信息提取。她拥有阿伯丁大学计算机科学博士学位和华盛顿大学计算语言学硕士学位。在获得学位的同时,她还于 2005 年至 2012 年在俄勒冈健康与科学大学从事机器学习、神经系统疾病和辅助技术方面的工作。她在多样性、包容性、计算机科学和伦理学的交叉领域领导了许多研讨会和倡议。她的工作获得了国防部长阿什卡特和美国盲人基金会的奖励,并被多家科技公司实施。她喜欢园艺、狗和猫。 + +**Matthew Watson 和 Chen Qian:** *使用 Keras 的 NLP 工作流程* + +
+ +
+ +

+一张图总结 Matt 和 Chen 的演讲 +

+ +Matthew Watson 是 Keras 团队的机器学习工程师,专注于高级建模 API。 他在本科期间学习计算机图形学,并在斯坦福大学获得硕士学位。 作为一名几乎是英语专业的学生,他转向计算机科学,热衷于跨学科工作并使 NLP 为更广泛的受众所接受。 + +Chen Qian 是 Keras 团队的一名软件工程师,专注于高级建模 API。 Chen 在斯坦福大学获得电气工程硕士学位,他对简化 ML 任务和大规模 ML 的代码实现特别感兴趣。 + +**Mark Saroufim:** *如何使用 Pytorch 训练模型* + +
+ +
+ +

+一张图总结 Mark 的演讲 +

+ +Mark Saroufim 是 Pytorch 的合作伙伴工程师,致力于开发 OSS 生产工具,包括 TorchServe 和 Pytorch Enterprise。 Mark 是 Graphcore、[yuri.ai](http://yuri.ai/)、Microsoft 和 NASA 的 JPL 的应用科学家和产品经理。 他热衷于让编程更有趣。 + +**Jakob Uszkoreit:** *它没有坏所以不要修复让我们打破它* + +
+ +
+ +

+一张图总结 Jakob 的演讲 +

+ +Jakob Uszkoreit 是 Inceptive 的联合创始人。 Inceptive 在紧密循环中使用大规模深度学习和高通量实验设计用于疫苗和治疗的 RNA 分子,目标是使基于 RNA 的药物更容易获得、更有效和更广泛适用。 此前,Jakob 在谷歌工作了十多年,领导谷歌大脑、研究和搜索领域的研发团队,致力于深度学习基础、计算机视觉、语言理解和机器翻译。 + +## Day 2: 可以使用的工具 + +**Lewis Tunstall:** *使用 🤗 Transformers Trainer 让训练更加简单* + +
+ +
+ +Lewis 是 Hugging Face 的机器学习工程师,专注于开发开源工具并让更广泛的社区可以访问它们。 他还是 O'Reilly 即将出版的有关于Transform的合著者,您可以在 Twitter (@_lewtun) 上关注他,了解 NLP 提示和技巧! + +**Matthew Carrigan:** *用于 🤗 Transformers 和 🤗 Datasets的新 TensorFlow 特性* + +
+ +
+ +Matt 负责Transformers的TensorFlow维护,并将最终领导一场针对现任PyTorch派系的政变,可能会通过他的推特账户@carrigmat进行协调。 + +**Lysandre Debut:** *使用Hugging Face Hub 作为协作和共享机器学习项目* + +
+ +
+ +

+一张图总结 Lysandre 的演讲 +

+ +Lysandre 是 Hugging Face 的机器学习工程师,他参与了许多开源项目。 他的目标是通过使用非常简单的 API 开发强大的工具,让每个人都可以使用机器学习。 + +**Lucile Saulnier:** *使用 🤗 Transformers 和 🤗 Tokenizers 获取您自己的tokenizer* + +
+ +
+ +Lucile 是 Hugging Face 的机器学习工程师,负责开发和支持开源工具的使用。 她还积极参与了自然语言处理领域的许多研究项目,例如协作训练模型和 BigScience。 + +**Sylvain Gugger:** *使用 🤗 Accelerate* 增强您的 PyTorch 训练循环* + +
+ +
+ +Sylvain 是 Hugging Face 的研究工程师,也是🤗 Transformers 的核心维护者之一,也是🤗 Accelerate 的开发者。 他喜欢让模型训练变得更容易。 + +**Merve Noyan:** *使用 🤗 Spaces 展示您的模型演示* + +
+ +
+ +Merve 是 Hugging Face 的开发者倡导者,致力于开发工具并围绕它们构建内容,以使每个人的机器学习民主化。 + +**Abubakar Abid:** *快速构建机器学习应用程序* + +
+ +
+ +

+一张图总结 Abubakar 的演讲 +

+ +Abubakar Abid 是 [Gradio](www.gradio.app) 的首席执行官。 他于 2015 年获得麻省理工学院电气工程和计算机科学学士学位,并于 2021 年获得斯坦福大学应用机器学习博士学位。作为 Gradio 的首席执行官,Abubakar 致力于使机器学习模型更易于演示、调试和部署。 + +**Mathieu Desvé:** *AWS ML Vision:让所有客户都可以使用机器学习* + +
+ +
+ +

+一张图总结 Mathieu 的演讲 +

+ +技术爱好者,有空闲时间的创客。 我喜欢挑战和解决客户和用户的问题,每天和有才华的人一起学习。 自 2004 年以来,我在前端、后端、基础设施、运营和管理等多个职位上工作。 尝试以敏捷的方式解决公共技术和管理问题。 + +**Philipp Schmid:** *使用 Amazon SageMaker 和🤗 Transformers 进行托管训练* + +
+ +
+ +Philipp Schmid 是 Hugging Face 的机器学习工程师和技术主管,负责领导与 Amazon SageMaker 团队的合作。 他热衷于使尖端 NLP 模型民主化和生产化,并提高深度学习的易用性。 \ No newline at end of file From 67ed1737643b2c9f18fd6df6afe2a521604700cb Mon Sep 17 00:00:00 2001 From: lewtun Date: Wed, 5 Oct 2022 16:08:54 +0200 Subject: [PATCH 34/51] Bump release (#333) --- README.md | 6 +- chapters/de/_toctree.yml | 9 + chapters/de/chapter4/1.mdx | 18 + chapters/de/chapter4/2.mdx | 96 +++ chapters/de/chapter4/3.mdx | 636 +++++++++++++++++++ chapters/de/glossary/1.mdx | 4 +- chapters/fr/_toctree.yml | 8 +- chapters/fr/events/1.mdx | 49 ++ chapters/fr/{event/1.mdx => events/2.mdx} | 340 +++++----- chapters/fr/events/3.mdx | 9 + chapters/ja/_toctree.yml | 4 +- chapters/ja/{event/1.mdx => events/2.mdx} | 0 chapters/pt/_toctree.yml | 4 +- chapters/pt/{event/1.mdx => events/2.mdx} | 0 chapters/ru/_toctree.yml | 8 +- chapters/vi/_toctree.yml | 4 +- chapters/vi/{event/1.mdx => events/2.mdx} | 0 chapters/zh-CN/_toctree.yml | 4 +- chapters/zh-CN/{event/1.mdx => events/2.mdx} | 0 utils/validate_translation.py | 34 + 20 files changed, 1045 insertions(+), 188 deletions(-) create mode 100644 chapters/de/chapter4/1.mdx create mode 100644 chapters/de/chapter4/2.mdx create mode 100644 chapters/de/chapter4/3.mdx create mode 100644 chapters/fr/events/1.mdx rename chapters/fr/{event/1.mdx => events/2.mdx} (98%) create mode 100644 chapters/fr/events/3.mdx rename chapters/ja/{event/1.mdx => events/2.mdx} (100%) rename chapters/pt/{event/1.mdx => events/2.mdx} (100%) rename chapters/vi/{event/1.mdx => events/2.mdx} (100%) rename chapters/zh-CN/{event/1.mdx => events/2.mdx} (100%) create mode 100644 utils/validate_translation.py diff --git a/README.md b/README.md index facab3706..db63d526c 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ This repo contains the content that's used to create the **[Hugging Face course] |:------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | [English](https://huggingface.co/course/en/chapter1/1) | [`chapters/en`](https://github.com/huggingface/course/tree/main/chapters/en) | [@sgugger](https://github.com/sgugger), [@lewtun](https://github.com/lewtun), [@LysandreJik](https://github.com/LysandreJik), [@Rocketknight1](https://github.com/Rocketknight1), [@sashavor](https://github.com/sashavor), [@osanseviero](https://github.com/osanseviero), [@SaulLu](https://github.com/SaulLu), [@lvwerra](https://github.com/lvwerra) | | [Bengali](https://huggingface.co/course/bn/chapter1/1) (WIP) | [`chapters/bn`](https://github.com/huggingface/course/tree/main/chapters/bn) | [@avishek-018](https://github.com/avishek-018), [@eNipu](https://github.com/eNipu) | -| [German](https://huggingface.co/course/de/chapter1/1) (WIP) | [`chapters/de`](https://github.com/huggingface/course/tree/main/chapters/de) | [@JesperDramsch](https://github.com/JesperDramsch), [@MarcusFra](https://github.com/MarcusFra) | +| [German](https://huggingface.co/course/de/chapter1/1) (WIP) | [`chapters/de`](https://github.com/huggingface/course/tree/main/chapters/de) | [@JesperDramsch](https://github.com/JesperDramsch), [@MarcusFra](https://github.com/MarcusFra), [@fabridamicelli](https://github.com/fabridamicelli) | | [Spanish](https://huggingface.co/course/es/chapter1/1) (WIP) | [`chapters/es`](https://github.com/huggingface/course/tree/main/chapters/es) | [@camartinezbu](https://github.com/camartinezbu), [@munozariasjm](https://github.com/munozariasjm), [@fordaz](https://github.com/fordaz) | | [Persian](https://huggingface.co/course/fa/chapter1/1) (WIP) | [`chapters/fa`](https://github.com/huggingface/course/tree/main/chapters/fa) | [@jowharshamshiri](https://github.com/jowharshamshiri), [@schoobani](https://github.com/schoobani) | | [French](https://huggingface.co/course/fr/chapter1/1) | [`chapters/fr`](https://github.com/huggingface/course/tree/main/chapters/fr) | [@lbourdois](https://github.com/lbourdois), [@ChainYo](https://github.com/ChainYo), [@melaniedrevet](https://github.com/melaniedrevet), [@abdouaziz](https://github.com/abdouaziz) | @@ -22,8 +22,8 @@ This repo contains the content that's used to create the **[Hugging Face course] | [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) | | [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) (WIP) | [`chapters/vi`](https://github.com/huggingface/course/tree/main/chapters/vi) | [@honghanhh](https://github.com/honghanhh) | -| [Chinese (simplified)](https://huggingface.co/course/zh-CN/chapter1/1) (WIP) | [`chapters/zh-CN`](https://github.com/huggingface/course/tree/main/chapters/zh-CN) | [@zhlhyx](https://github.com/zhlhyx), [petrichor1122](https://github.com/petrichor1122), [@1375626371](https://github.com/1375626371) | +| [Vietnamese](https://huggingface.co/course/vi/chapter1/1) | [`chapters/vi`](https://github.com/huggingface/course/tree/main/chapters/vi) | [@honghanhh](https://github.com/honghanhh) | +| [Chinese (simplified)](https://huggingface.co/course/zh-CN/chapter1/1) | [`chapters/zh-CN`](https://github.com/huggingface/course/tree/main/chapters/zh-CN) | [@zhlhyx](https://github.com/zhlhyx), [petrichor1122](https://github.com/petrichor1122), [@1375626371](https://github.com/1375626371) | | [Chinese (traditional)](https://huggingface.co/course/zh-TW/chapter1/1) (WIP) | [`chapters/zh-TW`](https://github.com/huggingface/course/tree/main/chapters/zh-TW) | [@davidpeng86](https://github.com/davidpeng86) | diff --git a/chapters/de/_toctree.yml b/chapters/de/_toctree.yml index 0aae386ae..7227717ef 100644 --- a/chapters/de/_toctree.yml +++ b/chapters/de/_toctree.yml @@ -20,6 +20,15 @@ title: Quiz am Ende des Kapitels quiz: 3 +- title: 4. Teilen von Modellen und Tokenizers + sections: + - local: chapter4/1 + title: Der Hugging Face Hub + - local: chapter4/2 + title: Verwendung vortrainierter Modelle + - local: chapter4/3 + title: Vortrainierte Modelle teilen + - title: Wörterverzeichnis sections: - local: glossary/1 diff --git a/chapters/de/chapter4/1.mdx b/chapters/de/chapter4/1.mdx new file mode 100644 index 000000000..d0130e5f3 --- /dev/null +++ b/chapters/de/chapter4/1.mdx @@ -0,0 +1,18 @@ +# Der Hugging Face Hub + +Der [Hugging Face Hub](https://huggingface.co/) –- unsere Hauptwebseite –- ist eine zentrale Platform, wo Nutzer*innen "state-of-the-art" Modelle und Datensätze entdecken, benutzen und dazu beitragen können. Eine große Vielfalt an Modellen steht öffentlich zur Verfügung auf der Platform – insgesamt mehr als 10000 Modelle. In diesem Kapitel fokusieren wir uns auf die Modelle und die Datensätze werden wir uns im Kapitel 5 anschauen. + +Die Modelle auf dem Hub sind nicht auf 🤗 Transformers bzw. NLP eingeschränkt. +Es gibt Modelle von [Flair](https://github.com/flairNLP/flair) und [AllenNLP](https://github.com/allenai/allennlp) für NLP, [Asteroid](https://github.com/asteroid-team/asteroid) und [pyannote](https://github.com/pyannote/pyannote-audio) für Spracherkennung, und [timm](https://github.com/rwightman/pytorch-image-models) für Computer Vision, um ein paar Beispiele zu nennen. + +Jedes Modell wird als Git-Repository gehosted, was Versionierung und Reproduzierbarkeit ermöglicht. Durch das Teilen eines Modells wird dieses der Community zur Verfügung gestellt. Somit wird das Teilen und die Anwendung vom Modell einfacher und jede/r hat die Möglichkeit, das Modell zu Verwenden, ohne es selbst trainieren zu müssen. + +Dazu löst das Teilen eines Modells auf dem Hub automatisch das Deployment einer Hosted-Inferenz-API für das Modell aus. Jede/r in der Communinity kann das Modell direkt auf der Modellsseite mit benutzerdefinierten Inputs und passenden Widgets ausprobieren. + +Das Beste ist, dass sowohl das Teilen als auch das Nutzen von öffentlichen Modellen auf dem Hub völlig kostenlos erfolgt! [Bezahlte Pläne](https://huggingface.co/pricing) gibt es auch, falls du Modelle privat teilen möchtest. + +Das folgende Video zeigt, wie man auf dem Hub navigieren kann. + + + +Ein huggingface.co Account ist für den folgenden Teil erforderlich, da wir Repositories auf dem Hugging Face Hub erstellen und verwalten werden: [Account erstellen](https://huggingface.co/join) diff --git a/chapters/de/chapter4/2.mdx b/chapters/de/chapter4/2.mdx new file mode 100644 index 000000000..0b20fe1d9 --- /dev/null +++ b/chapters/de/chapter4/2.mdx @@ -0,0 +1,96 @@ + + +# Verwendung vortrainierter Modelle + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + +Der Model Hub erleichtert das Auswählen des passenden Modells, sodass es von downstream Libraries mit wenigen Codezeilen benutzt werden kann. Lass uns anschauen, wie genau man solche Modelle verwendet und wie man der Communinity zurück beitragen kann. + +Nehmen wir an, wir suchen nach einem französichbasierten Modell, das die "mask filling" Aufgabe kann. + +
+Selecting the Camembert model. +
+ +Wir wählen den `camembert-base` Checkpoint aus, um es zu auszuprobieren. Das Kennzeichen `camembert-base` ist alles, was wir brauchen, um loszulegen! Wie in früheren Kapiteln gezeigt wurde, können wir das Modell mit der `pipeline()` Funktion instanziieren: + +```py +from transformers import pipeline + +camembert_fill_mask = pipeline("fill-mask", model="camembert-base") +results = camembert_fill_mask("Le camembert est :)") +``` + +```python out +[ + {'sequence': 'Le camembert est délicieux :)', 'score': 0.49091005325317383, 'token': 7200, 'token_str': 'délicieux'}, + {'sequence': 'Le camembert est excellent :)', 'score': 0.1055697426199913, 'token': 2183, 'token_str': 'excellent'}, + {'sequence': 'Le camembert est succulent :)', 'score': 0.03453313186764717, 'token': 26202, 'token_str': 'succulent'}, + {'sequence': 'Le camembert est meilleur :)', 'score': 0.0330314114689827, 'token': 528, 'token_str': 'meilleur'}, + {'sequence': 'Le camembert est parfait :)', 'score': 0.03007650189101696, 'token': 1654, 'token_str': 'parfait'} +] +``` +So einfach kann man mit einer Pipeline ein Modell laden. Dabei muss man nur darauf achten, den passenden Checkpoint für die gewünschte Aufgabe zu selektieren. Zum Beispiel: Wir laden hier den `camembert-base` Checkpoint in die `fill-mask` Pipeline, was schon korrekt ist. Aber würden wir diesen Checkpoint in die `text-classification` Pipeline laden, wären die Ergebnisse völlig sinnlos, weil der "head" von `camembert-base` für diese Aufgabe einfach nicht passt! Wir empfehlen, den "Task Selector" auf der Hugging Face Hub Seite zu benutzen, um die richtigen Checkpoints auszuwählen: + +
+The task selector on the web interface. +
+ +Du kannst auch den Checkpoint mit der Modell-Architektur direkt instanziieren: + +{#if fw === 'pt'} +```py +from transformers import CamembertTokenizer, CamembertForMaskedLM + +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: + +```py +from transformers import AutoTokenizer, AutoModelForMaskedLM + +tokenizer = AutoTokenizer.from_pretrained("camembert-base") +model = AutoModelForMaskedLM.from_pretrained("camembert-base") +``` +{:else} +```py +from transformers import CamembertTokenizer, TFCamembertForMaskedLM + +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: + +```py +from transformers import AutoTokenizer, TFAutoModelForMaskedLM + +tokenizer = AutoTokenizer.from_pretrained("camembert-base") +model = TFAutoModelForMaskedLM.from_pretrained("camembert-base") +``` +{/if} + + +Wenn du ein vortrainiertes Modell verwendest, prüf erstmal, wie genau das traininert wurde, mit welchen Datensätzen, sowie seine Einschränkungen und Biases. All diese Informationen sollten auf der Modellbeschreibungskarte stehen. + diff --git a/chapters/de/chapter4/3.mdx b/chapters/de/chapter4/3.mdx new file mode 100644 index 000000000..343221b0b --- /dev/null +++ b/chapters/de/chapter4/3.mdx @@ -0,0 +1,636 @@ + + +# Vortrainierte Modelle teilen + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Demnächst schauen wir uns an, wie man am einfachsten vortrainierte Modelle auf dem 🤗 Hub teilen kann. +Es gibt schon Tools und Hilfsmittel, die das Teilen und Updaten von Modellen auf dem Hub vereinfachen. Die werden wir gleich unten explorieren. + + + +Wir empfehlen allen Nutzer:innen, die Modelle trainieren, dass sie der Communinity beitragen, indem sie Modelle teilen. Selbst die Modelle, die auf sehr spezifische Datensätze trainiert wurden, werden anderen Nutzer:innen helfen, weil man Zeit und Rechenressourcen spart und Zugang zu nützlichen Trainingsartifakten bekommt. Also eventuell kannst du auch von der Arbeit anderer Nutzer:innen auch profitieren! + +Es gibt drei Wege, um Repositories zu neuen Modellen zu kreieren: + +- Mittels der `push_to_hub` API +- Mittels der `huggingface_hub` Python Bibliothek +- Mittels der Web-Oberfläche + +Nachdem du einen Repository erstellst hast, kannst du die Dateien über git und git-lfs hochladen. Demnächst zeigen wir dir die genauen Schritte, um Modell-Repositories zu erstellenund Dateien hochzuladen. + + +## Hochladen mit der `push_to_hub` API + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Die einfachste Variante, um Dateien auf den Hub hochzuladen, ist mittels der `push_to_hub` API. Bevor du weitermachst, must du einen Autentifizierungstoken generieren, damit die `huggingface_hub` API weißt, wer du bist und auf welche Namespaces du zugreifen darfst. Stell sicher, dass du in einer Umgebung mit `transformers` installiert bist (siehe [Setup](/course/chapter0)). Wenn du auf einem Notebook bist, kannst du diese Funktion benutzen, um dich einzuloggen: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +Im Terminal kannst folgendes ausführen: + +```bash +huggingface-cli login +``` + +In beiden Fällen solltest du nach deinem Username und Passwort gefragt werden. Das sind die selben, mit denen du dich auf dem Hub einloggst. Solltest du noch kein Hub-Profil haben, musst du erstmal eins [hier](https://huggingface.co/join) erstellen. + +Großartig! Nun hast du deinen Autentifizierungstoken in deinem Cache-Ordner gespeichert. Lass uns ein paar Repositories erstellen! + +{#if fw === 'pt'} + +Wenn du schon Modelle mit der `Trainer` API trainiert hast, dann ist der einfachste Weg, um Modelle hochzuladen, das Argument `push_to_hub=True` in `TrainingArguments` einzustellen. + +```py +from transformers import TrainingArguments + +training_args = TrainingArguments( + "bert-finetuned-mrpc", save_strategy="epoch", push_to_hub=True +) +``` + +Wenn du `trainer.train()` aufrufst, lädt der `Trainer` das Modell auf den Hub zu dem Repository in deinem Namespace hoch. Das passiert jedes Mal, wenn das Modell gespeichert wird (in diesem Beispiel jede Epoche). Der Repository wird so benannt werden, wie der Output-Ordner, den du gewählt hast (hier `bert-finetuned-mrpc`). Natürlich kannst du dir aber einen anderen Namen ausdenken und mit `hub_model_id = "a_different_name"` setzen. + +Um dein Modell zu einer Organisation, wovon du Mitglied bist, hochzuladen, kannst du einfach `hub_model_id = "my_organization/my_repo_name"` mit eingeben. + +Wenn das Training durch ist, must du noch einmal `trainer.push_to_hub()` ausführen, um die letzte Version deines Modells hochzuladen. Das wird auch eine Modell-Karte generieren, auf der die relevanten Metadaten mit den benutzten Hyperparametern und Evaluierungsergebnissen! Hier ist ein Beispiel von dem Inhalt, den du auf so einer Modell-Karte finden kannst: + +
+ An example of an auto-generated model card. +
+ +{:else} + +Wenn du für das Modell-Training Keras benutzt, ist der einfachste Weg, um das Modell aud den Hub hochzuladen, den `PushToHubCallback` zu setzen, wenn du `model.fit()` aufrufst. + +```py +from transformers import PushToHubCallback + +callback = PushToHubCallback( + "bert-finetuned-mrpc", save_strategy="epoch", tokenizer=tokenizer +) +``` + +Danach must du noch `callbacks=[callback]` beim `model.fit()` Aufruf setzen. +Der Callback wird das Modell auf den Hub hochladen und zwar zu einem Repository in deinem Namespace. Das passiert jedes Mal, wenn das Modell gespeichert wird (in diesem Beispiel jede Epoche). Der Repository wird so benannt werden, wie der Output-Ordner, den du gewählt hast (hier `bert-finetuned-mrpc`). Natürlich kannst du dir aber einen anderen Namen ausdenken und mit `hub_model_id = "a_different_name"` setzen. + +Um dein Modell zu einer Organisation, wovon du Mitglied bist, hochzuladen, kannst du einfach `hub_model_id = "my_organization/my_repo_name"` mit eingeben. + + +{/if} + +Auf einer tieferen Ebene kann man auf Modelle, Tokenizers und Konfigurationen auf dem Model-Hub direkt zugreifen, indem man die Methode `push_to_hub()` benutzt. +Diese Methode kümmert sich sowohl um das Erstellen vom Repository als auch das Pushen (Hochladen) von Modell- und Tokenizer-Dateien auf den Repository. Also da ist kein manueller Schritt notwendig (im Gegensatz zu den APIs, die wir demnächst sehen werden). + +Um uns eine Vorstellung zu schaffen, wie es funktioniert, lass uns zuerst ein Modell und einen Tokenizer initialisieren: + +{#if fw === 'pt'} +```py +from transformers import AutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = AutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` +{:else} +```py +from transformers import TFAutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = TFAutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` +{/if} + +Dir steht frei, was du mit diesen machst, z.B. Tokens zum Tokenizer hinzuzufügen, das Modell zu trainineren oder zu finetunen. Wenn du mit dem Modell, Gewichten und Tokenizer zufrieden bist, kannst du die Methode `push_to_hub()` vom `model` Objekt benutzten: + +```py +model.push_to_hub("dummy-model") +``` +Das wird den neuen Repository `dummy-model` in deinem Profil erstellen und den mit deinen Model-Dateien befüllen. Mach das gliche mit dem Tokenizer, sodass jetzt alle Dateien in diesem Repository verfügbar sind. + +```py +tokenizer.push_to_hub("dummy-model") +``` +Wenn du Teil einer Organisation bist, kannst du einfach das Argument `organization` mit eingeben, um die Artifakte auf den Namespace dieser Organisation hochzuladen. + +```py +tokenizer.push_to_hub("dummy-model", organization="huggingface") +``` + +Wenn du einen bestimmten Hugging Face Token benutzten möchtest, kannst du ihn auch in der Methode `push_to_hub()` spezifizieren: + +```py +tokenizer.push_to_hub("dummy-model", organization="huggingface", use_auth_token="") +``` + +Nun geh auf den Model Hub, um dein hochgeladenes Modell zu finden: *https://huggingface.co/user-or-organization/dummy-model*. + +Click auf den Tab "Files and versions" und da solltest du die Dateien finden, die auf diesem Screenshot zu sehen sind: + +{#if fw === 'pt'} +
+Dummy model containing both the tokenizer and model files. +
+{:else} +
+Dummy model containing both the tokenizer and model files. +
+{/if} + + + +✏️ **Probier das selber aus!** Lade das Modell und den Tokenizer vom Checkpoint `bert-base-cased` mit der Methode `push_to_hub()` hoch. Überprüfe, dass der Repository auf deiner Seite richtig erscheint, bevor du den löschst. + + + +Wie du schon gesehen hast, akzeptiert die Methode `push_to_hub()` mehrere Argumente. Dies erlaub das Hochladen auf den Namespace eines spezifischen Repositorys oder einer Organisation, sowie die Möglichkeit, einen anderen API Token zu benutzten. Wir empfehlen dir, die Dokumentation der Methode direkt auf [🤗 Transformers documentation](https://huggingface.co/transformers/model_sharing.html) zu lesen, um dir eine Vorstellung zu schaffen, was alles damit möglich ist. + +Die `push_to_hub()` Methode funktioniert im Hintergrund mit der Python Bibliothek [`huggingface_hub`](https://github.com/huggingface/huggingface_hub), die eine direkte API zum Hugging Face Hub anbietet. Sie ist auch drin in der 🤗 Transformers Bibliothek und mehreren anderen Machine Learning Bibliotheken, z.B. [`allenlp`](https://github.com/allenai/allennlp). Obwohl wir in diesem Kapitel den Fokus auf die Integration mit 🤗 Transformers legen, kannst du es in deinen eigenen Code bzw. eigene Bibliothek relativ einfach integrieren. Spring auf den letzten Part, um zu erfahren, wie man Dateien auf einen frisch erstellten Repository hochladen kann! + +## Verwendung der `huggingface_hub` Python Bibliothek +Die `huggingface_hub` Python Bibliothek ist ein Python Packet, das einige Werkzeuge für das Nutzen von Modell- und Datasethub anbietet. Es bietet simple Methoden und Klassen für gängige Aufgaben, z.B. um Information zu Repositories auf dem Hub zu bekommen oder um sie zu Verwalten. Es bietet auch simple auf git basierende APIs, um die Inhalte von solchen Repositories zu verwalten sowie um den Hub in deine Projekte und Bibliotheken zu integrieren. + +Ähnlich wie bei der Verwendung der`push_to_hub` API ist es bei diesen Aktionen erforderlich, dass dein API Token schon in deinem Cache gespeichert ist. Dafür musst du den `login` Befehl aus der CLI ausführen so wie in dem vorherigen Teil erklärt wurde (nochmal: Vergiss nicht, das `!` Zeichen vor die Befehle zu setzen, wenn du im Google Colab arbeitest). + +```bash +huggingface-cli login +``` + +Die `huggingface_hub` Bibliothek bietet mehrere nützliche Methoden und Klassen an. Erstens gibt es einige Methoden, um das Erstellen, Löschen, usw. von Repositories durchzuführen: + + +```python no-format +from huggingface_hub import ( + # User-Management + login, + logout, + whoami, + + # Repository erstellen und managen + create_repo, + delete_repo, + update_repo_visibility, + + # Methoden, um inhaltliche Information abzufragen/abzuändern + list_models, + list_datasets, + list_metrics, + list_repo_files, + upload_file, + delete_file, +) +``` + +Außerdem gibt es die sehr mächtige `Repository` Klasse, um einen lokalen Repository zu managen. Demnächst werden wir uns mit diesen Methoden und dieser Klasse beschäftigen, um zu verstehen, wie man die am besten nutzt. + +Mit der `create_repo` Methode kann ein neuer Repository auf dem Hub erstellt werden: + +```py +from huggingface_hub import create_repo + +create_repo("dummy-model") +``` + +Das erstellt den Repository `dummy-model` unter deinem Namespace. Wenn du möchtest, kannst du auch die Organisation spezifizieren, zu der der Repository gehören sollte, indem du das `organization` Argument setzt: + +```py +from huggingface_hub import create_repo + +create_repo("dummy-model", organization="huggingface") +``` + +Das erstellt den Repository `dummy-model` unter dem `huggingface` Namespace – angenommen du gehörst zu dieser Organisation. +Andere eventuell nützliche Argumente sind: + +- `private`: um zu spezifizieren, ob der Repository für andere sichtbar sein sollte oder nicht. +- `token`: um den Token, der im Zwischenspeicher (Cache) liegt, mit einem neuen Token zu überscheiben. +- `repo_type`: zum Auswählen, ob du einen `dataset` oder einen `space` anstatt von einem Modell kreieren möchtest. Erlaubte Werte sind `"dataset"` und `"space"`. + +Nachdem der Repository erstellt wurde, können wir Dateien hinzufügen! Spring zum nächsten Abschnitt, um drei Varianten dazu zu lernen, wie man das machen kann. + +## Mit der Webinterface + +Die Webinterface bietet Tools an, um Repositories direkt auf dem Hub zu managen. Damit kannst du ganz einfach Repositories erstellen, Dateien hinzufügen (sogar große Dateien), Modelle explorieren, Unterschiede ("diffs") visualisieren und viel mehr. + +Um einen Repository zu erstellen, geh auf [huggingface.co/new](https://huggingface.co/new): + +
+Beispiel vom Modell, mit dem man einen Repository erstellen kann. +
+ +Erstens muss man den Besitzer vom Repository eingeben: Das kannst entweder du selbst oder jede andere Person von der Organisation sein, zu der du gehörst. Wenn du eine Organisation auswählst, wird das Modell auf der Seite der Organisation präsentiert und jedes Mitglied der Organisation wird zu diesem Repository beitragen können. + +Als nächstes gib den Namen deines Modells ein. So wird der Repository auch heißen. Zuletzt kannst du spezifizieren, ob das Modell öffentlich oder privat sein soll. Private Modelle sind von der Öffentlichkeit unsichtbar. + +Nach der Erstellung des Repositorys solltest du so eine Seite sehen können: + +
+Leeres Modell nach der Erstellung des Repositorys. +
+ +Hier wird dein Modell gehostet. Um mit dem Auffüllen zu beginnen, kannst du direkt über die Weboberfläche eine README-Datei hinzufügen. + +
+The README file showing the Markdown capabilities. +
+ +Die README-Datei ist im Markdown Format — du kannst dich damit gerne austoben! +Der dritte Teil dieses Kapitels zielt darauf hin, eine "model card" (Steckbrief) zu bauen. Steckbriefe haben eine entscheidende Relevanz, um dein Modell wertvoll zu machen, denn du kannst dort anderen erzählen, was das Modell kann. + +Wenn du dir den "Files and versions" Tab anschaust, wirst du sehen, dass noch nicht viele Dateien darauf sind – nämlich nur die von dir eben kreierte *README.md* und die *.gitattributes* (wo große Dateien geloggt werden). + + +
+The 'Files and versions' tab only shows the .gitattributes and README.md files. +
+ +Gleich werden wir sehen, wie wir neue Dateien hinzufügen können. + +## Hochladen von Modell-Dateien + +Das System zum Managen der Dateien auf Hugging Face Hub basiert auf git für normale Dateien und auf git-lfs ([Git Large File Storage](https://git-lfs.github.com/)) für größere Dateien. + +Im nächsten Teil schauen wir uns drei Möglichkeitein an, um Dateien mittels `huggingface_hub` und git-Befehle auf den Hub hochzuladen. + +### Die `upload_file` Variante + +Um `upload_file` zu verwenden, muss man nicht unbedingt git und git-lfs installiert haben. Die Funktion lädt Dateien auf den 🤗 Hub mittels HTTP POST Anfragen. Eine Einschränkunf dieser Variante ist, dass man nur mit Dateien unter 5GB groß arbeiten kann. +Wenn deine Dateien größer als 5GB sind, nutz eine von den folgenden zwei Methoden. + +Die API kann folgendermaßen benutzt werden: + +```py +from huggingface_hub import upload_file + +upload_file( + "/config.json", + path_in_repo="config.json", + repo_id="/dummy-model", +) +``` +Das wird die `config.json` Datei in `` auf das Root-Verzeichnis vom Repository als `config.json` vom `dummy-model` Repository. +Andere nützliche Argumente : + +- `token`, um den Token zu überscheiben, der in deinem Cache gespeichert ist +- `repo_type`, wenn du anstatt von einem Modell Dateien auf einen `dataset` oder `space` hochladen möchtest. Valide Werte sind `"dataset"` und `"space"`. + + +### Die `Repository` Klasse + +Die `Repository` Klasse verwaltet einen lokalen Repository so wie git. Sie abstrahiert aber die meisten schwierigen Punkte, auf die man stoßen würde, wenn man eine ähnliche Funktionalität mit git erreichen möchte. + +Diese Klasse braucht git und git-lfs im System schon installiert. Also stell sicher, dass du git-lfs installiert hast (siehe [hier](https://git-lfs.github.com/) für Installationsanweisungen) und richte alles ein, bevor du loslegst. + +Um mit dem Repository rumspielen zu starten, können wir den in einem lokalen Ordner initialisieren, in dem wir den Remote-Repository klonen: + +```py +from huggingface_hub import Repository + +repo = Repository("", clone_from="/dummy-model") +``` + +Das hat den Ordner `` in unserem Arbeitsverzeichnis erstellt. Dieser Ordner enthält bisher nur die `.gitattributes` Datel, da diese die einzige Datei ist, die wir mit `create_repo` kreiert haben. + +Ab jetzt können mehrere gängige Methoden benutzten: + +```py +repo.git_pull() +repo.git_add() +repo.git_commit() +repo.git_push() +repo.git_tag() +``` + +Und andere Optionen auch! Wir empfehlen, dass du dir die Dokumentation zu `Repository`, die dir [hier](https://github.com/huggingface/huggingface_hub/tree/main/src/huggingface_hub#advanced-programmatic-repository-management) zur Verfügung steht, anschaust, um dir eine Übersicht aller verfügbaren Methoden zu verschaffen. + +Bisher haben wir ein Modell und einen Tokenizer, die wir gerne auf den Hub pushen würden. Wir haben auch den Repository geklont, sodass wir die Dateien in dem Repository speichern können. + +Zuerst stellen wir sicher, dass unser lokaler Repository einen aktuellen Stand hat, in dem wir die letzten Änderungen pullen: + +```py +repo.git_pull() +``` + +Wenn das durch ist, speichern wir die Dateien vom Modell und Tokenizer: + +```py +model.save_pretrained("") +tokenizer.save_pretrained("") +``` + +Der Pfad `` beinhaltet jetzt alle Modell- und Tokenizerdateien. Wir folgen dem gängigen Git-Workflow, indem wir die Dateien in die "staging area" bringen, wir committen und pushen sie auf den hub: + +```py +repo.git_add() +repo.git_commit("Add model and tokenizer files") +repo.git_push() +``` + +Glückwunsch! Du hast gerade deine ersten Dateien auf den Hub hochgeladen. + +### Die git-basierte Variante + +Das ist der einfachste Weg zum Hochladen von Dateien: Wir werden es direkt mit git und git-lfs tun. Der Größtenteil der Schwierigkeit wird durch die früheren Ansätze abstrahiert, aber es gibt ein paar Vorbehalte bei der folgenden Methode, deswegen werden wir einem komplexeren Anwendungsfall folgen. + +Um diese Klasse zu benutzten, mussen wir git und git-lfs installiert haben. Also stell sicher, dass du [git-lfs](https://git-lfs.github.com/) installiert und aufgesetzt hast, bevor du beginst. + +Zuerst initialisiere git-lfs: + +```bash +git lfs install +``` + +```bash +Updated git hooks. +Git LFS initialized. +``` + +Danach musst du den Modell-Repository klonen: + +```bash +git clone https://huggingface.co// +``` + +Mein Username ist `lysandre` und ich habe den Modellnamen `dummy` benutzt. Also bei bei sieht der Befehl so aus: + +``` +git clone https://huggingface.co/lysandre/dummy +``` + +Ich habe jetzt einen Ordner namens *dummy* in meinem Arbeitsverzeichnis. Ich kann jetzt `cd` in den Ordner und mir den Inhalt anschauen: + +```bash +cd dummy && ls +``` + +```bash +README.md +``` + +Wenn du eben einen Repository mit der Hugging Face Hubs Methode `create_repo` erstellt hast, dann sollte dieser Ordner nur eine versteckte `.gitattributes` Datei enthalten. Wenn du es nach den Anweisungen in dem vorherigen Abschnitt mittels der Webinterface gemacht hast, dann sollte der Ordner eine einzige README.md Datei neben der `.gitattributes` enthalten – so wie hier angezeigt wird. + +Das Hinzufügen einer Datei mit normaler Größe, z.B. Konfiguration- oder Vokabulardatei, wird so gemach wie in einem git-basierten System. Aber größere Dateien müssen mit git-lfs registriert werden, um sie zu *huggingface.co* zu pushen. + +Lass uns kurz zurück zu Python, um ein Modell und einen Tokenizer zu generieren, die wir zu unserem dummy repository committen möchten: + +{#if fw === 'pt'} +```py +from transformers import AutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = AutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + +# Mach was du möchtest mit dem Modell, z.B. trainieren, fine-tunen. + +model.save_pretrained("") +tokenizer.save_pretrained("") +``` +{:else} +```py +from transformers import TFAutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = TFAutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + +# Mach was du möchtest mit dem Modell, z.B. trainieren, fine-tunen. + +model.save_pretrained("") +tokenizer.save_pretrained("") +``` +{/if} + +Jetzt haben wir die Modell- und Tokenizer-Artifakte gespeichert und können wir uns nochmal den *dummy* Ordner anschauen: + +```bash +ls +``` + +{#if fw === 'pt'} +```bash +config.json pytorch_model.bin README.md sentencepiece.bpe.model special_tokens_map.json tokenizer_config.json tokenizer.json +``` + +Wenn du dir die Dateigrößen anschaust (z.B. mit `ls -lh`), solltest du sehen, dass die Modell-Statedict Datei (*pytorch_model.bin*) der einzige Ausreißer ist mit über 400 MB. + +{:else} +```bash +config.json README.md sentencepiece.bpe.model special_tokens_map.json tf_model.h5 tokenizer_config.json tokenizer.json +``` + +Wenn du dir die Dateigrößen anschaust (z.B. mit `ls -lh`), solltest du sehen, dass die Modell-Statedict Datei (*t5_model.h5*) der einzige Ausreißer ist mit über 400 MB. + +{/if} + + +✏️ Wenn ein Repository mittels der Webinterface kreiert wird, wird die *.gitattributes* Datei automatisch gesetzt, um bestimmte Dateiendungen wie *.bin* und *.h5* als große Dateien zu betrachten, sodass git-lfs sie tracken kann, ohne dass du weiteres konfigurieren musst. + + +Nun können wir weitermachen und so arbeiten wie wir es mit normalen Git Repositories machen. Wir können die Dateien stagen mit dem Git-Befehl `git add`: + +```bash +git add . +``` + +Jetzt schauen wir, welche Dateien gestaged wurden: + +```bash +git status +``` + +{#if fw === 'pt'} +```bash +On branch main +Your branch is up to date with 'origin/main'. + +Changes to be committed: + (use "git restore --staged ..." to unstage) + modified: .gitattributes + new file: config.json + new file: pytorch_model.bin + new file: sentencepiece.bpe.model + new file: special_tokens_map.json + new file: tokenizer.json + new file: tokenizer_config.json +``` +{:else} +```bash +On branch main +Your branch is up to date with 'origin/main'. + +Changes to be committed: + (use "git restore --staged ..." to unstage) + modified: .gitattributes + new file: config.json + new file: sentencepiece.bpe.model + new file: special_tokens_map.json + new file: tf_model.h5 + new file: tokenizer.json + new file: tokenizer_config.json +``` +{/if} + +Ähnlicherweise können wir sicherstellen, dass git-lfs die richtigen Dateien trackt mit dem `status` Befehl: + +```bash +git lfs status +``` + +{#if fw === 'pt'} +```bash +On branch main +Objects to be pushed to origin/main: + + +Objects to be committed: + + config.json (Git: bc20ff2) + pytorch_model.bin (LFS: 35686c2) + sentencepiece.bpe.model (LFS: 988bc5a) + special_tokens_map.json (Git: cb23931) + tokenizer.json (Git: 851ff3e) + tokenizer_config.json (Git: f0f7783) + +Objects not staged for commit: + + +``` + +Da sehen wir, dass alle Dateien `Git` als Handler haben. Nur die *pytorch_model.bin* und *sentencepiece.bpe.model* Dateien haben `LFS`. Toll! + +{:else} +```bash +On branch main +Objects to be pushed to origin/main: + + +Objects to be committed: + + config.json (Git: bc20ff2) + sentencepiece.bpe.model (LFS: 988bc5a) + special_tokens_map.json (Git: cb23931) + tf_model.h5 (LFS: 86fce29) + tokenizer.json (Git: 851ff3e) + tokenizer_config.json (Git: f0f7783) + +Objects not staged for commit: + + +``` + +Da sehen wir, dass alle Dateien `Git` als Handler haben. Nur die *t5_model.h5* hat `LFS`. Sehr gut! + +{/if} + +Lass uns mit den letzten Schritten weitermachen, indem wir die Änderungen commiten und zum *huggingface.co* Remote-Repository pushen: + +```bash +git commit -m "First model version" +``` + +{#if fw === 'pt'} +```bash +[main b08aab1] First model version + 7 files changed, 29027 insertions(+) + 6 files changed, 36 insertions(+) + create mode 100644 config.json + create mode 100644 pytorch_model.bin + create mode 100644 sentencepiece.bpe.model + create mode 100644 special_tokens_map.json + create mode 100644 tokenizer.json + create mode 100644 tokenizer_config.json +``` +{:else} +```bash +[main b08aab1] First model version + 6 files changed, 36 insertions(+) + create mode 100644 config.json + create mode 100644 sentencepiece.bpe.model + create mode 100644 special_tokens_map.json + create mode 100644 tf_model.h5 + create mode 100644 tokenizer.json + create mode 100644 tokenizer_config.json +``` +{/if} + +Das Pushen kann ein bisschen dauern, je nach dem wie schnell deine Internetverbindung ist und wie groß deine Dateien sind: + +```bash +git push +``` + +```bash +Uploading LFS objects: 100% (1/1), 433 MB | 1.3 MB/s, done. +Enumerating objects: 11, done. +Counting objects: 100% (11/11), done. +Delta compression using up to 12 threads +Compressing objects: 100% (9/9), done. +Writing objects: 100% (9/9), 288.27 KiB | 6.27 MiB/s, done. +Total 9 (delta 1), reused 0 (delta 0), pack-reused 0 +To https://huggingface.co/lysandre/dummy + 891b41d..b08aab1 main -> main +``` + +{#if fw === 'pt'} +Wenn alles durch ist, können wir uns den Repository anschauen und die eben hinzugefügten Dateien finden: + +
+The 'Files and versions' tab now contains all the recently uploaded files. +
+ +Mit der UI kannst du die Modell-Dateien und die Commits explorieren, um die Differenz bei jedem Commit zu sehen: + +
+The diff introduced by the recent commit. +
+{:else} + +Wenn alles durch ist, können wir uns den Repository anschauen und die eben hinzugefügten Dateien finden: + +
+The 'Files and versions' tab now contains all the recently uploaded files. +
+ +Mit der UI kannst du die Modell-Dateien und die Commits explorieren, um die Differenz bei jedem Commit zu sehen: + +
+The diff introduced by the recent commit. +
+{/if} diff --git a/chapters/de/glossary/1.mdx b/chapters/de/glossary/1.mdx index b861d7eb1..5f8f859c7 100644 --- a/chapters/de/glossary/1.mdx +++ b/chapters/de/glossary/1.mdx @@ -3,9 +3,11 @@ | Original | Übersetzung | |-----------------------------|---------------------------------| | Abstraction | Abstraktion | +| Account | Account | | Accuracy | Genauigkeit | | Backward Pass | Rückwärtsalgorithmus berechnen | | Batch | Batch | +| Bias | Bias (Voreingenommenheit) | | Chapter | Kapitel | | Class | Klasse | | Code | Code | @@ -59,7 +61,7 @@ | Windows | Windows | | Working Environment | Arbeitsumgebung | | Workload | Auslastung | -| Workspace | Workspace | +| Workspace | Workspace | ## Abkürzungen diff --git a/chapters/fr/_toctree.yml b/chapters/fr/_toctree.yml index 930c633ca..2fd1d5873 100644 --- a/chapters/fr/_toctree.yml +++ b/chapters/fr/_toctree.yml @@ -191,7 +191,11 @@ title: Quiz de fin de chapitre quiz: 9 -- title: Evènements liés au cours d'Hugging Face +- title: Evènements liés au cours sections: - - local: event/1 + - local: events/1 + title: Sessions en direct et ateliers + - local: events/2 title: Événement de lancement de la partie 2 + - local: events/3 + title: Fête des blocs Gradio diff --git a/chapters/fr/events/1.mdx b/chapters/fr/events/1.mdx new file mode 100644 index 000000000..5128c0159 --- /dev/null +++ b/chapters/fr/events/1.mdx @@ -0,0 +1,49 @@ +# Sessions en direct et ateliers + +Pour la parution des parties 1 et 2 du cours, nous avons organisé plusieurs sessions et ateliers de codage en direct. Vous trouverez ci-dessous les liens vers les enregistrements de ces sessions et ateliers. + +## Sessions de codage en direct + +Lors de la première session, Sylvain parcourt avec vous le chapitre 1 du cours, en l'expliquant étape par étape : + +
+ +
+ +Lors de la deuxième session, c'est au tour de Lewis de présenter le chapitre 2 : + +
+ +
+ +Parce que le chapitre 2 est tellement cool, Sylvain a également fourni une présentation de ce chapitre ! + +
+ +
+ +Pour le chapitre 3, Lewis revient pour vous guider dans le code : + +
+ +
+ +Enfin, Omar conclut les sessions en direct liées à la première partie du cours en abordant le chapitre 4 : + +
+ +
+ +## Ateliers + +Dans le premier atelier, Merve accueille Lewis pour discuter de la section 7 du chapitre 7 sur le [*question anwsering*]( https://huggingface.co/course/chapter7/7?fw=pt). + +
+ +
+ +Pour le deuxième atelier, Merve reçoit Leandro pour parler de la section 6 du chapitre 7 sur [entraîner un modèle de langage causal à partir de zéro]( https://huggingface.co/course/chapter7/6?fw=pt) avec une application avec [CodeParrot](https://huggingface.co/codeparrot). + +
+ +
diff --git a/chapters/fr/event/1.mdx b/chapters/fr/events/2.mdx similarity index 98% rename from chapters/fr/event/1.mdx rename to chapters/fr/events/2.mdx index ff5a1578f..9c37c7835 100644 --- a/chapters/fr/event/1.mdx +++ b/chapters/fr/events/2.mdx @@ -1,170 +1,170 @@ -# Événement pour le lancement de la partie 2 - -Pour la sortie de la deuxième partie du cours, nous avons organisé un événement en direct consistant en deux jours de conférences suivies d’un *sprint* de *finetuning*. Si vous l'avez manqué, vous pouvez rattraper les présentations qui sont toutes listées ci-dessous ! - -## Jour 1 : Une vue d'ensemble des transformers et comment les entraîner - - -**Thomas Wolf :** *L'apprentissage par transfert et la naissance de la bibliothèque 🤗 Transformers* - -
- -
- -

-A visual summary of Thom's talk -

- -Thomas Wolf est cofondateur et directeur scientifique d’Hugging Face. Les outils créés par Thomas Wolf et l'équipe d’Hugging Face sont utilisés par plus de 5 000 organismes de recherche, dont Facebook Artificial Intelligence Research, Google Research, DeepMind, Amazon Research, Apple, l'Allen Institute for Artificial Intelligence ainsi que la plupart des départements universitaires. Thomas Wolf est l'initiateur et le président principal de la plus grande collaboration de recherche qui ait jamais existé dans le domaine de l'intelligence artificielle : [« BigScience »](https://bigscience.huggingface.co), ainsi que d'un ensemble de [bibliothèques et outils](https://github.com/huggingface/) largement utilisés. Thomas Wolf est également un éducateur prolifique, un *leader* d'opinion dans le domaine de l'intelligence artificielle et du traitement du langage naturel, et un orateur régulièrement invité à des conférences dans le monde entier [https://thomwolf.io](https://thomwolf.io). - -**Jay Alammar :** *Une introduction visuelle douce aux transformers* - -
- -
- -

-A visual summary of Jay's talk -

- -Grâce à son blog d’apprentissage automatique très populaire, Jay a aidé des millions de chercheurs et d'ingénieurs à comprendre visuellement les outils et les concepts de l'apprentissage automatique, des plus élémentaires (qui se retrouvent dans les docs NumPy et Pandas) aux plus pointus (Transformer, BERT, GPT-3). - -**Margaret Mitchell :** *Les valeurs dans le développement de l’apprentissage automatique* - -
- -
- -

-A visual summary of Margaret's talk -

- -Margaret Mitchell est une chercheuse travaillant sur l'IA éthique. Elle se concentre actuellement sur les tenants et aboutissants du développement de l'IA éthique dans le domaine de la technologie. Elle a publié plus de cinquante articles sur la génération de langage naturel, les technologies d'assistance, la vision par ordinateur et l'IA éthique. Elle détient plusieurs brevets dans le domaine de la génération de conversations et celui de la classification des sentiments. Elle a précédemment travaillé chez Google AI en tant que chercheuse où elle a fondé et codirigé le groupe d'IA éthique de Google. Ce groupe est axé sur la recherche fondamentale en matière d'IA éthique et l'opérationnalisation de d'IA éthique en interne à Google. Avant de rejoindre Google, elle a été chercheuse chez Microsoft Research où elle s'est concentrée sur la génération de la vision par ordinateur vers le langage et a été post-doc à Johns Hopkins où elle s'est concentrée sur la modélisation bayésienne et l'extraction d'informations. Elle est titulaire d'un doctorat en informatique de l'université d'Aberdeen et d'une maîtrise en linguistique informatique de l'université de Washington. Tout en obtenant ses diplômes, elle a également travaillé de 2005 à 2012 sur l'apprentissage automatique, les troubles neurologiques et les technologies d'assistance à l'Oregon Health and Science University. Elle a dirigé un certain nombre d'ateliers et d'initiatives au croisement de la diversité, de l'inclusion, de l'informatique et de l'éthique. Ses travaux ont été récompensés par le secrétaire à la défense Ash Carter et la Fondation américaine pour les aveugles, et ont été implémenté par plusieurs entreprises technologiques. - -**Matthew Watson et Chen Qian :** *Les flux de travail en NLP avec Keras* - -
- -
- -

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

- -Matthew Watson est ingénieur en apprentissage automatique au sein de l'équipe Keras et se concentre sur les API de modélisation de haut niveau. Il a étudié l'infographie pendant ses études et a obtenu un master à l'université de Stanford. Il s'est orienté vers l'informatique après avoir étudié l'anglais. Il est passionné par le travail interdisciplinaire et par la volonté de rendre le traitement automatique des langues accessible à un public plus large. - -Chen Qian est un ingénieur logiciel de l'équipe Keras spécialisé dans les API de modélisation de haut niveau. Chen est titulaire d'un master en génie électrique de l'université de Stanford et s'intéresse particulièrement à la simplification de l'implémentation du code des tâches d’apprentissage automatique et le passage à grande échelle de ces codes. - - -**Mark Saroufim :** *Comment entraîner un modèle avec PyTorch* - -
- -
- -

-A visual summary of Mark's talk -

- -Mark Saroufim est ingénieur partenaire chez PyTorch et travaille sur les outils de production OSS, notamment TorchServe et PyTorch Enterprise. Dans ses vies antérieures, Mark a été un scientifique appliqué et un chef de produit chez Graphcore, [yuri.ai](http://yuri.ai/), Microsoft et au JPL de la NASA. Sa principale passion est de rendre la programmation plus amusante. - -**Jakob Uszkoreit :** *Ce n'est pas cassé alors ne réparez pas cassez tout* - -
- -
- -

-A visual summary of Jakob's talk -

- -Jakob Uszkoreit est le cofondateur d'Inceptive. Inceptive conçoit des molécules d'ARN pour les vaccins et les thérapies en utilisant l'apprentissage profond à grande échelle. Le tout en boucle étroite avec des expériences à haut débit, dans le but de rendre les médicaments à base d'ARN plus accessibles, plus efficaces et plus largement applicables. Auparavant, Jakob a travaillé chez Google pendant plus de dix ans, dirigeant des équipes de recherche et de développement au sein de Google Brain, Research et Search, travaillant sur les fondamentaux de l'apprentissage profond, la vision par ordinateur, la compréhension du langage et la traduction automatique. - -## Jour 2 : Les outils à utiliser - - -**Lewis Tunstall :** *Un entraînement simple avec la fonction *Trainer* de la bibliotèque 🤗 Transformers* - -
- -
- -Lewis est un ingénieur en apprentissage machine chez Hugging Face qui se concentre sur le développement d'outils open-source et les rend accessibles à la communauté. Il est également co-auteur du livre [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) paru chez O'Reilly. Vous pouvez le suivre sur Twitter (@_lewtun) pour des conseils et astuces en traitement du langage naturel ! - -**Matthew Carrigan :** *Nouvelles fonctionnalités en TensorFlow pour 🤗 Transformers et 🤗 Datasets* - -
- -
- -Matt est responsable de la maintenance des modèles en TensorFlow chez *Transformers*. Il finira par mener un coup d'État contre la faction PyTorch en place Celui sera probablement coordonné via son compte Twitter @carrigmat. - -**Lysandre Debut :** *Le Hub d’Hugging Face, un moyen de collaborer et de partager des projets d'apprentissage automatique* - -
- -
- -

-A visual summary of Lysandre's talk -

- -Lysandre est ingénieur en apprentissage machine chez Hugging Face où il participe à de nombreux projets open source. Son objectif est de rendre l'apprentissage automatique accessible à tous en développant des outils puissants avec une API très simple. - -**Lucile Saulnier :** *Avoir son propre tokenizer avec 🤗 Transformers & 🤗 Tokenizers* - -
- -
- -Lucile est ingénieure en apprentissage automatique chez Hugging Face où elle développe et soutient l'utilisation d'outils open source. Elle est également activement impliquée dans de nombreux projets de recherche dans le domaine du traitement du langage naturel tels que l’entraînement collaboratif et BigScience. - -**Sylvain Gugger :** *Optimisez votre boucle d'entraînement PyTorch avec -🤗 Accelerate* - -
- -
- -Sylvain est ingénieur de recherche chez Hugging Face. Il est l'un des principaux mainteneurs de 🤗 Transformers et le développeur derrière 🤗 Accelerate. Il aime rendre l'apprentissage des modèles plus accessible. - -**Merve Noyan :** *Présentez vos démonstrations de modèles avec -🤗 Spaces* - -
- -
- -Merve est *developer advocate* chez Hugging Face travaillant au développement d'outils et à la création de contenu autour d'eux afin de démocratiser l'apprentissage automatique pour tous. - -**Abubakar Abid :** *Créer rapidement des applications d'apprentissage automatique* - -
- -
- -

-A visual summary of Abubakar's talk -

- -Abubakar Abid est le PDG de [Gradio](www.gradio.app). Il a obtenu sa licence en génie électrique et en informatique au MIT en 2015, et son doctorat en apprentissage automatique appliqué à Stanford en 2021. En tant que PDG de Gradio, Abubakar s'efforce de faciliter la démonstration, le débogage et le déploiement des modèles d'apprentissage automatique. - -**Mathieu Desvé :** *AWS ML Vision : Rendre l'apprentissage automatique accessible à tous les clients* - -
- -
- -

-A visual summary of Mathieu's talk -

- -Passionné de technologie, il est un créateur pendant son temps libre. Il aime les défis et résoudre les problèmes des clients et des utilisateurs ainsi que travailler avec des personnes talentueuses pour apprendre chaque jour. Depuis 2004, il a occupé plusieurs postes, passant du frontend au backend, de l'infrastructure aux opérations et à la gestion. Il essaie de résoudre les problèmes techniques et de gestion courants de manière agile. - -**Philipp Schmid :** *Entraînement dirigé avec Amazon SageMaker et 🤗 Transformers* - -
- -
- -Philipp Schmid est ingénieur en apprentissage machine et *Tech Lead* chez Hugging Face où il dirige la collaboration avec l'équipe Amazon SageMaker. Il est passionné par la démocratisation et la mise en production de modèles de traitement du langage naturel de pointe et par l'amélioration de la facilité d'utilisation de l'apprentissage profond. +# Événement pour le lancement de la partie 2 + +Pour la sortie de la deuxième partie du cours, nous avons organisé un événement en direct consistant en deux jours de conférences suivies d’un *sprint* de *finetuning*. Si vous l'avez manqué, vous pouvez rattraper les présentations qui sont toutes listées ci-dessous ! + +## Jour 1 : Une vue d'ensemble des transformers et comment les entraîner + + +**Thomas Wolf :** *L'apprentissage par transfert et la naissance de la bibliothèque 🤗 Transformers* + +
+ +
+ +

+A visual summary of Thom's talk +

+ +Thomas Wolf est cofondateur et directeur scientifique d’Hugging Face. Les outils créés par Thomas Wolf et l'équipe d’Hugging Face sont utilisés par plus de 5 000 organismes de recherche, dont Facebook Artificial Intelligence Research, Google Research, DeepMind, Amazon Research, Apple, l'Allen Institute for Artificial Intelligence ainsi que la plupart des départements universitaires. Thomas Wolf est l'initiateur et le président principal de la plus grande collaboration de recherche qui ait jamais existé dans le domaine de l'intelligence artificielle : [« BigScience »](https://bigscience.huggingface.co), ainsi que d'un ensemble de [bibliothèques et outils](https://github.com/huggingface/) largement utilisés. Thomas Wolf est également un éducateur prolifique, un *leader* d'opinion dans le domaine de l'intelligence artificielle et du traitement du langage naturel, et un orateur régulièrement invité à des conférences dans le monde entier [https://thomwolf.io](https://thomwolf.io). + +**Jay Alammar :** *Une introduction visuelle douce aux transformers* + +
+ +
+ +

+A visual summary of Jay's talk +

+ +Grâce à son blog d’apprentissage automatique très populaire, Jay a aidé des millions de chercheurs et d'ingénieurs à comprendre visuellement les outils et les concepts de l'apprentissage automatique, des plus élémentaires (qui se retrouvent dans les docs NumPy et Pandas) aux plus pointus (Transformer, BERT, GPT-3). + +**Margaret Mitchell :** *Les valeurs dans le développement de l’apprentissage automatique* + +
+ +
+ +

+A visual summary of Margaret's talk +

+ +Margaret Mitchell est une chercheuse travaillant sur l'IA éthique. Elle se concentre actuellement sur les tenants et aboutissants du développement de l'IA éthique dans le domaine de la technologie. Elle a publié plus de cinquante articles sur la génération de langage naturel, les technologies d'assistance, la vision par ordinateur et l'IA éthique. Elle détient plusieurs brevets dans le domaine de la génération de conversations et celui de la classification des sentiments. Elle a précédemment travaillé chez Google AI en tant que chercheuse où elle a fondé et codirigé le groupe d'IA éthique de Google. Ce groupe est axé sur la recherche fondamentale en matière d'IA éthique et l'opérationnalisation de d'IA éthique en interne à Google. Avant de rejoindre Google, elle a été chercheuse chez Microsoft Research où elle s'est concentrée sur la génération de la vision par ordinateur vers le langage et a été post-doc à Johns Hopkins où elle s'est concentrée sur la modélisation bayésienne et l'extraction d'informations. Elle est titulaire d'un doctorat en informatique de l'université d'Aberdeen et d'une maîtrise en linguistique informatique de l'université de Washington. Tout en obtenant ses diplômes, elle a également travaillé de 2005 à 2012 sur l'apprentissage automatique, les troubles neurologiques et les technologies d'assistance à l'Oregon Health and Science University. Elle a dirigé un certain nombre d'ateliers et d'initiatives au croisement de la diversité, de l'inclusion, de l'informatique et de l'éthique. Ses travaux ont été récompensés par le secrétaire à la défense Ash Carter et la Fondation américaine pour les aveugles, et ont été implémenté par plusieurs entreprises technologiques. + +**Matthew Watson et Chen Qian :** *Les flux de travail en NLP avec Keras* + +
+ +
+ +

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

+ +Matthew Watson est ingénieur en apprentissage automatique au sein de l'équipe Keras et se concentre sur les API de modélisation de haut niveau. Il a étudié l'infographie pendant ses études et a obtenu un master à l'université de Stanford. Il s'est orienté vers l'informatique après avoir étudié l'anglais. Il est passionné par le travail interdisciplinaire et par la volonté de rendre le traitement automatique des langues accessible à un public plus large. + +Chen Qian est un ingénieur logiciel de l'équipe Keras spécialisé dans les API de modélisation de haut niveau. Chen est titulaire d'un master en génie électrique de l'université de Stanford et s'intéresse particulièrement à la simplification de l'implémentation du code des tâches d’apprentissage automatique et le passage à grande échelle de ces codes. + + +**Mark Saroufim :** *Comment entraîner un modèle avec PyTorch* + +
+ +
+ +

+A visual summary of Mark's talk +

+ +Mark Saroufim est ingénieur partenaire chez PyTorch et travaille sur les outils de production OSS, notamment TorchServe et PyTorch Enterprise. Dans ses vies antérieures, Mark a été un scientifique appliqué et un chef de produit chez Graphcore, [yuri.ai](http://yuri.ai/), Microsoft et au JPL de la NASA. Sa principale passion est de rendre la programmation plus amusante. + +**Jakob Uszkoreit :** *Ce n'est pas cassé alors ne réparez pas cassez tout* + +
+ +
+ +

+A visual summary of Jakob's talk +

+ +Jakob Uszkoreit est le cofondateur d'Inceptive. Inceptive conçoit des molécules d'ARN pour les vaccins et les thérapies en utilisant l'apprentissage profond à grande échelle. Le tout en boucle étroite avec des expériences à haut débit, dans le but de rendre les médicaments à base d'ARN plus accessibles, plus efficaces et plus largement applicables. Auparavant, Jakob a travaillé chez Google pendant plus de dix ans, dirigeant des équipes de recherche et de développement au sein de Google Brain, Research et Search, travaillant sur les fondamentaux de l'apprentissage profond, la vision par ordinateur, la compréhension du langage et la traduction automatique. + +## Jour 2 : Les outils à utiliser + + +**Lewis Tunstall :** *Un entraînement simple avec la fonction *Trainer* de la bibliotèque 🤗 Transformers* + +
+ +
+ +Lewis est un ingénieur en apprentissage machine chez Hugging Face qui se concentre sur le développement d'outils open-source et les rend accessibles à la communauté. Il est également co-auteur du livre [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) paru chez O'Reilly. Vous pouvez le suivre sur Twitter (@_lewtun) pour des conseils et astuces en traitement du langage naturel ! + +**Matthew Carrigan :** *Nouvelles fonctionnalités en TensorFlow pour 🤗 Transformers et 🤗 Datasets* + +
+ +
+ +Matt est responsable de la maintenance des modèles en TensorFlow chez *Transformers*. Il finira par mener un coup d'État contre la faction PyTorch en place Celui sera probablement coordonné via son compte Twitter @carrigmat. + +**Lysandre Debut :** *Le Hub d’Hugging Face, un moyen de collaborer et de partager des projets d'apprentissage automatique* + +
+ +
+ +

+A visual summary of Lysandre's talk +

+ +Lysandre est ingénieur en apprentissage machine chez Hugging Face où il participe à de nombreux projets open source. Son objectif est de rendre l'apprentissage automatique accessible à tous en développant des outils puissants avec une API très simple. + +**Lucile Saulnier :** *Avoir son propre tokenizer avec 🤗 Transformers & 🤗 Tokenizers* + +
+ +
+ +Lucile est ingénieure en apprentissage automatique chez Hugging Face où elle développe et soutient l'utilisation d'outils open source. Elle est également activement impliquée dans de nombreux projets de recherche dans le domaine du traitement du langage naturel tels que l’entraînement collaboratif et BigScience. + +**Sylvain Gugger :** *Optimisez votre boucle d'entraînement PyTorch avec +🤗 Accelerate* + +
+ +
+ +Sylvain est ingénieur de recherche chez Hugging Face. Il est l'un des principaux mainteneurs de 🤗 Transformers et le développeur derrière 🤗 Accelerate. Il aime rendre l'apprentissage des modèles plus accessible. + +**Merve Noyan :** *Présentez vos démonstrations de modèles avec +🤗 Spaces* + +
+ +
+ +Merve est *developer advocate* chez Hugging Face travaillant au développement d'outils et à la création de contenu autour d'eux afin de démocratiser l'apprentissage automatique pour tous. + +**Abubakar Abid :** *Créer rapidement des applications d'apprentissage automatique* + +
+ +
+ +

+A visual summary of Abubakar's talk +

+ +Abubakar Abid est le PDG de [Gradio](www.gradio.app). Il a obtenu sa licence en génie électrique et en informatique au MIT en 2015, et son doctorat en apprentissage automatique appliqué à Stanford en 2021. En tant que PDG de Gradio, Abubakar s'efforce de faciliter la démonstration, le débogage et le déploiement des modèles d'apprentissage automatique. + +**Mathieu Desvé :** *AWS ML Vision : Rendre l'apprentissage automatique accessible à tous les clients* + +
+ +
+ +

+A visual summary of Mathieu's talk +

+ +Passionné de technologie, il est un créateur pendant son temps libre. Il aime les défis et résoudre les problèmes des clients et des utilisateurs ainsi que travailler avec des personnes talentueuses pour apprendre chaque jour. Depuis 2004, il a occupé plusieurs postes, passant du frontend au backend, de l'infrastructure aux opérations et à la gestion. Il essaie de résoudre les problèmes techniques et de gestion courants de manière agile. + +**Philipp Schmid :** *Entraînement dirigé avec Amazon SageMaker et 🤗 Transformers* + +
+ +
+ +Philipp Schmid est ingénieur en apprentissage machine et *Tech Lead* chez Hugging Face où il dirige la collaboration avec l'équipe Amazon SageMaker. Il est passionné par la démocratisation et la mise en production de modèles de traitement du langage naturel de pointe et par l'amélioration de la facilité d'utilisation de l'apprentissage profond. diff --git a/chapters/fr/events/3.mdx b/chapters/fr/events/3.mdx new file mode 100644 index 000000000..8519fa554 --- /dev/null +++ b/chapters/fr/events/3.mdx @@ -0,0 +1,9 @@ +# Fête des blocs Gradio + +Parallèlement à la publication du chapitre sur *Gradio* du cours, Hugging Face a organisé un événement communautaire sur la création de démonstrations d'apprentissage automatique à l'aide de la nouvelle fonctionnalité *Gradio Blocks*. + +Vous pouvez trouver toutes les démos que la communauté a créées sous l'organisation [`Gradio-Blocks`](https://huggingface.co/Gradio-Blocks) sur le Hub. Voici la démonstration des gagnants : + +**Langage naturel vers SQL** + + diff --git a/chapters/ja/_toctree.yml b/chapters/ja/_toctree.yml index 07e20fe21..c1d0fcd7f 100644 --- a/chapters/ja/_toctree.yml +++ b/chapters/ja/_toctree.yml @@ -55,7 +55,7 @@ - local: chapter8/6 title: 概要 -- title: Hugging Faceコースのイベント +- title: コースのイベント sections: - - local: event/1 + - local: events/2 title: パート2公開記念イベント diff --git a/chapters/ja/event/1.mdx b/chapters/ja/events/2.mdx similarity index 100% rename from chapters/ja/event/1.mdx rename to chapters/ja/events/2.mdx diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index 425eb7d94..662e840c5 100644 --- a/chapters/pt/_toctree.yml +++ b/chapters/pt/_toctree.yml @@ -108,7 +108,7 @@ - local: chapter8/3 title: Pedindo ajuda nos fóruns -- title: Evento do curso Hugging Face +- title: Evento do curso sections: - - local: event/1 + - local: events/2 title: Evento de lançamento da Parte 2 diff --git a/chapters/pt/event/1.mdx b/chapters/pt/events/2.mdx similarity index 100% rename from chapters/pt/event/1.mdx rename to chapters/pt/events/2.mdx diff --git a/chapters/ru/_toctree.yml b/chapters/ru/_toctree.yml index 0adaa40fb..12777ffce 100644 --- a/chapters/ru/_toctree.yml +++ b/chapters/ru/_toctree.yml @@ -42,7 +42,7 @@ - local: chapter3/2 title: Предобработка данных - local: chapter3/3 - title: Fine-tuning модели с использованием Trainer API + title: Fine-tuning модели с использованием Trainer API local_fw: { pt: chapter3/3, tf: chapter3/3_tf } - local: chapter3/4 title: Полное обучение модели @@ -81,9 +81,9 @@ - local: chapter5/7 title: 🤗 Datasets, итоги! - local: chapter5/8 - title: Тест по главе 5 -- title: Бибилиотека 🤗 Tokenizers - sections: + title: Тест по главе 5 +- title: 6. Бибилиотека 🤗 Tokenizers + sections: - local: chapter6/1 title: Введение - local: chapter6/2 diff --git a/chapters/vi/_toctree.yml b/chapters/vi/_toctree.yml index 491a6a9e7..da7a3be85 100644 --- a/chapters/vi/_toctree.yml +++ b/chapters/vi/_toctree.yml @@ -191,7 +191,7 @@ title: Đố vui cuối chương quiz: 9 -- title: Sự kiện Khoá học Hugging Face +- title: Sự kiện Khoá học sections: - - local: event/1 + - local: events/2 title: Sự kiện Phát hành Phần 2 diff --git a/chapters/vi/event/1.mdx b/chapters/vi/events/2.mdx similarity index 100% rename from chapters/vi/event/1.mdx rename to chapters/vi/events/2.mdx diff --git a/chapters/zh-CN/_toctree.yml b/chapters/zh-CN/_toctree.yml index 5bcdd498b..ff3aaaa6d 100644 --- a/chapters/zh-CN/_toctree.yml +++ b/chapters/zh-CN/_toctree.yml @@ -191,7 +191,7 @@ title: 章末测试 quiz: 9 -- title: Hugging Face 课程活动 +- title: 课程活动 sections: - - local: event/1 + - local: events/2 title: Part 2 发布活动 diff --git a/chapters/zh-CN/event/1.mdx b/chapters/zh-CN/events/2.mdx similarity index 100% rename from chapters/zh-CN/event/1.mdx rename to chapters/zh-CN/events/2.mdx diff --git a/utils/validate_translation.py b/utils/validate_translation.py new file mode 100644 index 000000000..b4a3f792b --- /dev/null +++ b/utils/validate_translation.py @@ -0,0 +1,34 @@ +import argparse +import os +import yaml + +from pathlib import Path + +PATH_TO_COURSE = Path("chapters/") + +def load_sections(language: str): + toc = yaml.safe_load( + open(os.path.join(PATH_TO_COURSE / language, "_toctree.yml"), "r") + ) + sections = [] + for chapter in toc: + for section in chapter["sections"]: + sections.append(section["local"]) + return set(sorted(sections)) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--language", type=str, help="Translation language to validate") + args = parser.parse_args() + + english_sections = load_sections("en") + translation_sections = load_sections(args.language) + missing_sections = english_sections.difference(translation_sections) + + if len(missing_sections) > 0: + print("Missing sections:") + for section in missing_sections: + print(section) + else: + print("✅ No missing sections - translation complete!") \ No newline at end of file From 678cf24af2f9b2a371f80769b26821830ade04a9 Mon Sep 17 00:00:00 2001 From: lewtun Date: Fri, 7 Oct 2022 17:38:43 +0200 Subject: [PATCH 35/51] Bump release (#335) --- chapters/en/chapter5/5.mdx | 71 +++----------------------------------- chapters/en/chapter5/6.mdx | 16 ++------- 2 files changed, 6 insertions(+), 81 deletions(-) diff --git a/chapters/en/chapter5/5.mdx b/chapters/en/chapter5/5.mdx index 492f73be1..5f57ea218 100644 --- a/chapters/en/chapter5/5.mdx +++ b/chapters/en/chapter5/5.mdx @@ -321,34 +321,13 @@ issues_with_comments_dataset = issues_dataset.map( ) ``` -The final step is to save the augmented dataset alongside our raw data so we can push them both to the Hub: - -```py -issues_with_comments_dataset.to_json("issues-datasets-with-comments.jsonl") -``` +The final step is to push our dataset to the Hub. Let's take a look at how we can do that. ## Uploading the dataset to the Hugging Face Hub -Now that we have our augmented dataset, it's time to push it to the Hub so we can share it with the community! To upload the dataset we'll use the [🤗 Hub library](https://github.com/huggingface/huggingface_hub), which allows us to interact with the Hugging Face Hub through a Python API. 🤗 Hub comes preinstalled with 🤗 Transformers, so we can use it directly. For example, we can use the `list_datasets()` function to get information about all the public datasets currently hosted on the Hub: - -```py -from huggingface_hub import list_datasets - -all_datasets = list_datasets() -print(f"Number of datasets on Hub: {len(all_datasets)}") -print(all_datasets[0]) -``` - -```python out -Number of datasets on Hub: 1487 -Dataset Name: acronym_identification, Tags: ['annotations_creators:expert-generated', 'language_creators:found', 'languages:en', 'licenses:mit', 'multilinguality:monolingual', 'size_categories:10K - -✏️ **Try it out!** Use your Hugging Face Hub username and password to obtain a token and create an empty repository called `github-issues`. Remember to **never save your credentials** in Colab or any other repository, as this information can be exploited by bad actors. - - - -Next, let's clone the repository from the Hub to our local machine and copy our dataset file into it. 🤗 Hub provides a handy `Repository` class that wraps many of the common Git commands, so to clone the remote repository we simply need to provide the URL and local path we wish to clone to: - -```py -from huggingface_hub import Repository - -repo = Repository(local_dir="github-issues", clone_from=repo_url) -!cp issues-datasets-with-comments.jsonl github-issues/ -``` - -By default, various file extensions (such as *.bin*, *.gz*, and *.zip*) are tracked with Git LFS so that large files can be versioned within the same Git workflow. You can find a list of tracked file extensions inside the repository's *.gitattributes* file. To include the JSON Lines format in the list, we can run the following command: - -```py -repo.lfs_track("*.jsonl") -``` - -Then we can use `Repository.push_to_hub()` to push the dataset to the Hub: - -```py -repo.push_to_hub() -``` - -If we navigate to the URL contained in `repo_url`, we should now see that our dataset file has been uploaded. - -
-Our dataset repository on the Hugging Face Hub. -
- From here, anyone can download the dataset by simply providing `load_dataset()` with the repository ID as the `path` argument: ```py diff --git a/chapters/en/chapter5/6.mdx b/chapters/en/chapter5/6.mdx index 47c72c99f..f2927a0de 100644 --- a/chapters/en/chapter5/6.mdx +++ b/chapters/en/chapter5/6.mdx @@ -39,24 +39,12 @@ In this section we'll use embeddings to develop a semantic search engine. These ## Loading and preparing the dataset -The first thing we need to do is download our dataset of GitHub issues, so let's use the 🤗 Hub library to resolve the URL where our file is stored on the Hugging Face Hub: - -```py -from huggingface_hub import hf_hub_url - -data_files = hf_hub_url( - repo_id="lewtun/github-issues", - filename="datasets-issues-with-comments.jsonl", - repo_type="dataset", -) -``` - -With the URL stored in `data_files`, we can then load the remote dataset using the method introduced in [section 2](/course/chapter5/2): +The first thing we need to do is download our dataset of GitHub issues, so let's use `load_dataset()` function as usual: ```py from datasets import load_dataset -issues_dataset = load_dataset("json", data_files=data_files, split="train") +issues_dataset = load_dataset("lewtun/github-issues", split="train") issues_dataset ``` From 459a868a1e11d2c0818386d56283e77ab36e2e58 Mon Sep 17 00:00:00 2001 From: lewtun Date: Mon, 17 Oct 2022 16:26:10 +0200 Subject: [PATCH 36/51] Bump release (#343) --- chapters/de/chapter3/6.mdx | 6 +- chapters/en/chapter7/2.mdx | 4 +- chapters/fr/chapter7/2.mdx | 4 +- chapters/it/_toctree.yml | 10 +- chapters/it/chapter1/10.mdx | 260 ++++++++++++++++++++++++++++++++++ chapters/it/chapter2/3.mdx | 231 ++++++++++++++++++++++++++++++ chapters/it/chapter2/4.mdx | 240 +++++++++++++++++++++++++++++++ chapters/ja/chapter7/2.mdx | 4 +- chapters/vi/chapter7/2.mdx | 4 +- chapters/zh-CN/chapter7/2.mdx | 4 +- utils/validate_translation.py | 10 +- 11 files changed, 759 insertions(+), 18 deletions(-) create mode 100644 chapters/it/chapter1/10.mdx create mode 100644 chapters/it/chapter2/3.mdx create mode 100644 chapters/it/chapter2/4.mdx diff --git a/chapters/de/chapter3/6.mdx b/chapters/de/chapter3/6.mdx index cd82492e7..cd37e7f5c 100644 --- a/chapters/de/chapter3/6.mdx +++ b/chapters/de/chapter3/6.mdx @@ -29,7 +29,7 @@ Teste, was du in diesem Kapitel gelernt hast! correct: true }, { - Text: "Surprise (Überraschung)", + text: "Surprise (Überraschung)", explain: "Überraschung! Probier eine andere!" } ]} @@ -216,7 +216,7 @@ Teste, was du in diesem Kapitel gelernt hast! correct: true }, { - Text: "Es bietet mehr Funktionen zur Optimierung.", + text: "Es bietet mehr Funktionen zur Optimierung.", explain: "Nein, die 🤗 Accelerate Bibliothek stellt keine Optimierungsfunktionen zur Verfügung." } ]} @@ -298,4 +298,4 @@ Teste, was du in diesem Kapitel gelernt hast! ]} /> -{/if} \ No newline at end of file +{/if} diff --git a/chapters/en/chapter7/2.mdx b/chapters/en/chapter7/2.mdx index 16c13770f..af3527558 100644 --- a/chapters/en/chapter7/2.mdx +++ b/chapters/en/chapter7/2.mdx @@ -403,7 +403,7 @@ Since we are working on a token classification problem, we will use the `TFAutoM They should be set by two dictionaries, `id2label` and `label2id`, which contain the mapping from ID to label and vice versa: ```py -id2label = {str(i): label for i, label in enumerate(label_names)} +id2label = {i: label for i, label in enumerate(label_names)} label2id = {v: k for k, v in id2label.items()} ``` @@ -653,7 +653,7 @@ Since we are working on a token classification problem, we will use the `AutoMod They should be set by two dictionaries, `id2label` and `label2id`, which contain the mappings from ID to label and vice versa: ```py -id2label = {str(i): label for i, label in enumerate(label_names)} +id2label = {i: label for i, label in enumerate(label_names)} label2id = {v: k for k, v in id2label.items()} ``` diff --git a/chapters/fr/chapter7/2.mdx b/chapters/fr/chapter7/2.mdx index 653ca7a13..8084e0df9 100644 --- a/chapters/fr/chapter7/2.mdx +++ b/chapters/fr/chapter7/2.mdx @@ -403,7 +403,7 @@ Puisque nous travaillons sur un problème de classification de *tokens*, nous al Elles devraient être définies par deux dictionnaires, `id2label` et `label2id`, qui contiennent la correspondance de l'identifiant à l'étiquette et vice versa : ```py -id2label = {str(i): label for i, label in enumerate(label_names)} +id2label = {i: label for i, label in enumerate(label_names)} label2id = {v: k for k, v in id2label.items()} ``` @@ -653,7 +653,7 @@ Puisque nous travaillons sur un problème de classification de *tokens*, nous al Elles devraient être définies par deux dictionnaires, `id2label` et `label2id`, qui contiennent les correspondances entre identifiants et étiquettes et vice versa : ```py -id2label = {str(i): label for i, label in enumerate(label_names)} +id2label = {i: label for i, label in enumerate(label_names)} label2id = {v: k for k, v in id2label.items()} ``` diff --git a/chapters/it/_toctree.yml b/chapters/it/_toctree.yml index 92b0283b4..c0df904ad 100644 --- a/chapters/it/_toctree.yml +++ b/chapters/it/_toctree.yml @@ -23,6 +23,9 @@ title: Bias e limiti - local: chapter1/9 title: Riassunto + - local: chapter1/10 + title: Quiz di fine capitolo + quiz: 1 - title: 2. Usare i 🤗 Transformers sections: @@ -30,8 +33,11 @@ title: Introduzione - local: chapter2/2 title: Dietro la pipeline + - local: chapter2/3 + title: Modelli + - local: chapter2/4 + title: Tokenizers - - title: 3. Affinamento di un modello pre-addestrato sections: - local: chapter3/1 @@ -102,4 +108,4 @@ title: Parte 2 completata! - local: chapter8/7 title: Quiz di fine capitolo - quiz: 8 + quiz: 8 \ No newline at end of file diff --git a/chapters/it/chapter1/10.mdx b/chapters/it/chapter1/10.mdx new file mode 100644 index 000000000..886706092 --- /dev/null +++ b/chapters/it/chapter1/10.mdx @@ -0,0 +1,260 @@ + + +# Quiz di fine capitolo + + + + +In questo capitolo abbiamo parlato di molti argomenti! Non preoccuparti se non hai capito tutto nel dettaglio: i prossimi capitoli ti aiuteranno a capire come molte di queste cose funzionano dietro le quinte. + +Prima di procedere, però, verifichiamo cos'hai imparato in questo capitolo! + + +### 1. Esplora l'Hub e cerca il checkpoint `roberta-large-mnli`. Quale compito svolge? + + +roberta-large-mnli page." + }, + { + text: "Classificazione testuale", + explain: "Più precisamente, determina se due frasi sono connesse logicamente su tre livelli associati alle etichette 'contradiction', 'neutral' e 'entailment'. Questo compito viene detto anche natural language inference.", + correct: true + }, + { + text: "Generazione testuale", + explain: "Rivisita il link e prova di nuovo: roberta-large-mnli page." + } + ]} +/> + +### 2. Cosa restituisce il codice seguente? + +```py +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +sentiment-analysis." + }, + { + text: "Genera e restituisce testo che completa la frase di partenza.", + explain: "Sbagliato! Se così fosse, si tratterebbe di una pipeline di tipo text-generation.", + }, + { + text: "Restituisce i termini che rappresentano persone, organizzazioni o luoghi.", + explain: "Inoltre, grazie a grouped_entities=True, la pipeline è in grado di raggruppare le parole che appartengono alla stessa entità, come \"Hugging Face\".", + correct: true + } + ]} +/> + +### 3. Cosa dovrebbe rimpiazzare "..." in questo estratto di codice? + +```py +from transformers import pipeline + +filler = pipeline("fill-mask", model="bert-base-cased") +result = filler("...") +``` + + aspetta te.", + explain: "Sbagliato. Controlla la card del modello bert-base-cased e cerca di capire il tuo errore." + }, + { + text: "Questo [MASK] aspetta te.", + explain: "Corretto! Il mask token utilizzato dal modello è [MASK].", + correct: true + }, + { + text: "Questo signore aspetta te.", + explain: "Sbagliato. Questa pipeline completa parole nascoste, quindi necessita di un mask token nell'input." + } + ]} +/> + +### 4. Perché questo codice non funziona? + +```py +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +result = classifier("This is a course about the Transformers library") +``` + +candidate_labels=[...].", + correct: true + }, + { + text: "Questa pipeline richiede diverse frasi, non solo una.", + explain: "Sbagliato, anche se quando usata correttamente, questa pipeline può tranquillamente processare una lista di frasi (come tutte le altre pipeline)." + }, + { + text: "Come al solito, la libreria Transformer di 🤗 non funziona.", + explain: "Ci rifiutiamo di commentare la tua risposta!" + }, + { + text: "Questa pipeline richiede un input più lungo. Quello fornito è troppo corto.", + explain: "Sbagliato. Sappi che per processare testi molto lunghi, questa pipeline li deve troncare." + } + ]} +/> + +### 5. Cosa significa "transfer learning"? + + + +### 6. Vero o falso? Solitamente un modello linguistico non richiede etichette in fase di pre-addestramento. + + +self-supervised, il che significa che le etichette sono create direttamente a partire dall'input (come quando una pipeline predice la parola seguente o indovina parole nascoste).", + correct: true + }, + { + text: "Falso", + explain: "La risposta non è corretta." + } + ]} +/> + +### 7. Seleziona la frase che meglio descrive i termini "modello," "architettura," e "pesi." + + + + +### 8. Quale dei seguenti modelli utilizzeresti per completare dei prompt con testo generato? + + + +### 9. Quale dei seguenti modelli utilizzeresti per riassumere testi? + + + +### 10. Quale dei seguenti modelli utilizzeresti per classificare input testuali sulla base di determinate etichette? + + + +### 11. Qual è la possibile origine di un bias osservato in un modello? + + diff --git a/chapters/it/chapter2/3.mdx b/chapters/it/chapter2/3.mdx new file mode 100644 index 000000000..644f37755 --- /dev/null +++ b/chapters/it/chapter2/3.mdx @@ -0,0 +1,231 @@ + + +# Models + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + +{:else} + +{/if} + +{#if fw === 'pt'} +In questa sezione vedremo da vicino come creare e usare un modello. Utilizzeremo la classe `AutoModel`, utile quando si vuole istanziare qualsiasi modello da un checkpoint. + +La classe `AutoModel` e tutti i suoi derivati sono in realtà semplici involucri dell'ampia varietà di modelli disponibili nella libreria. Si tratta di un involucro intelligente, in quanto è in grado di indovinare automaticamente l'architettura del modello appropriata per il checkpoint e successivamente di istanziare un modello con questa architettura. + +{:else} +In questa sezione vedremo da vicino come creare e usare un modello. Utilizzeremo la classe `TFAutoModel`, utile quando si vuole istanziare qualsiasi modello da un checkpoint. + +La classe `TFAutoModel` e tutti i suoi derivati sono in realtà semplici involucri dell'ampia varietà di modelli disponibili nella libreria. Si tratta di un involucro intelligente, in quanto è in grado di indovinare automaticamente l'architettura del modello appropriata per il checkpoint e successivamente di istanziare un modello con questa architettura. + +{/if} + +Tuttavia, se si conosce il tipo di modello che si vuole utilizzare, si può usare direttamente la classe che ne definisce l'architettura. Vediamo come funziona con un modello BERT. + +## Creare un trasformatore + +La prima cosa da fare per inizializzare un modello BERT è caricare un oggetto di configurazione: + +{#if fw === 'pt'} +```py +from transformers import BertConfig, BertModel + +# Creazione della configurazione +config = BertConfig() + +# Creare il modello dalla configurazione +model = BertModel(config) +``` +{:else} +```py +from transformers import BertConfig, TFBertModel + +# Creazione della configurazione +config = BertConfig() + +# Creare il modello dalla configurazione +model = TFBertModel(config) +``` +{/if} + +La configurazione contiene molti attributi che vengono utilizzati per costruire il modello: + +```py +print(config) +``` + +```python out +BertConfig { + [...] + "hidden_size": 768, + "intermediate_size": 3072, + "max_position_embeddings": 512, + "num_attention_heads": 12, + "num_hidden_layers": 12, + [...] +} +``` + +Anche se non si è ancora visto cosa fanno tutti questi attributi, se ne dovrebbero riconoscere alcuni: l'attributo `hidden_size` definisce la dimensione del vettore `hidden_states`, e l'attributo `num_hidden_layers` definisce il numero di livelli del modello Transformer. + +### Diversi metodi di caricamento + +La creazione di un modello dalla configurazione predefinita lo inizializza con valori casuali: + +{#if fw === 'pt'} +```py +from transformers import BertConfig, BertModel + +config = BertConfig() +model = BertModel(config) + +# Il modello è inizializzato in modo casuale! +``` +{:else} +```py +from transformers import BertConfig, TFBertModel + +config = BertConfig() +model = TFBertModel(config) + +# Il modello è inizializzato in modo casuale! +``` +{/if} + +Il modello può essere utilizzato in questo stato, ma produrrà risultati incomprensibili; è necessario addestrarlo prima. + +Potremmo addestrare il modello da zero sul compito da svolgere, ma come si è visto in [Capitolo 1](/course/chapter1), questo richiederebbe molto tempo e molti dati, oltre ad avere un impatto ambientale non trascurabile. Per evitare sforzi inutili, è indispensabile poter condividere e riutilizzare modelli già addestrati. + +Caricare un modello Transformer già addestrato è semplice: lo si può fare usando il metodo `from_pretrained()`: + +{#if fw === 'pt'} +```py +from transformers import BertModel + +model = BertModel.from_pretrained("bert-base-cased") +``` + +Come abbiamo visto in precedenza, possiamo sostituire `BertModel` con la classe equivalente `AutoModel`. Lo faremo d'ora in poi, perché in questo modo si ottiene un codice cosiddetto "checkpoint-agnostic"; se il codice funziona per un checkpoint, dovrebbe funzionare senza problemi anche con un altro. Questo vale anche se l'architettura è diversa, purché il checkpoint sia stato addestrato per un compito simile (per esempio, un compito di sentiment analysis). + +{:else} +```py +from transformers import TFBertModel + +model = TFBertModel.from_pretrained("bert-base-cased") +``` +Come abbiamo visto in precedenza, possiamo sostituire `TFBertModel` con la classe equivalente `TFAutoModel`. Lo faremo d'ora in poi, perché in questo modo si ottiene un codice cosiddetto "checkpoint-agnostic"; se il codice funziona per un checkpoint, dovrebbe funzionare senza problemi anche con un altro. Questo vale anche se l'architettura è diversa, purché il checkpoint sia stato addestrato per un compito simile (per esempio, un compito di sentiment analysis). + +{/if} + +Nell'esempio di codice precedente non abbiamo usato `BertConfig` e abbiamo invece caricato un modello pre-addestrato tramite l'identificatore `bert-base-cased`. Si tratta di un checkpoint che è stato addestrato dagli stessi autori di BERT; si possono trovare maggiori dettagli su di esso nella sua [scheda modello](https://huggingface.co/bert-base-cased). + +Questo modello è ora inizializzato con tutti i pesi del checkpoint. Può essere utilizzato direttamente per effettuare inferenza sui compiti su cui è stato addestrato e può anche essere messo adattato ad un nuovo compito, tramite il fine tuning. Allenandosi con i pesi pre-addestrati piuttosto che partendo da zero, si possono ottenere rapidamente buoni risultati. + +I pesi sono stati scaricati e messi in cache (in modo che le future chiamate al metodo `from_pretrained()` non li scarichino di nuovo) nella cartella della cache, che per impostazione predefinita è *~/.cache/huggingface/transformers*. È possibile personalizzare la cartella della cache impostando la variabile d'ambiente `HF_HOME`. + +L'identificatore usato per caricare il modello può essere l'identificatore di qualsiasi modello presente nel Model Hub, purché sia compatibile con l'architettura del BERT. L'elenco completo dei checkpoint BERT disponibili è disponibile [qui](https://huggingface.co/models?filter=bert). + + +### Metodi di salvataggio + +Saving a model is as easy as loading one — we use the `save_pretrained()` method, which is analogous to the `from_pretrained()` method: + +```py +model.save_pretrained("directory_on_my_computer") +``` + +In questo modo si salvano due file sul disco: + +{#if fw === 'pt'} +``` +ls directory_on_my_computer + +config.json pytorch_model.bin +``` +{:else} +``` +ls directory_on_my_computer + +config.json tf_model.h5 +``` +{/if} + +Se si dà un'occhiata al file *config.json*, si riconoscono gli attributi necessari per costruire l'architettura del modello. Questo file contiene anche alcuni metadati, come l'origine del checkpoint e la versione di 🤗 Transformers utilizzata al momento dell'ultimo salvataggio del checkpoint. + +{#if fw === 'pt'} + +Il file *pytorch_model.bin* è noto come *state dictionary*; contiene tutti i pesi del modello. I due file vanno di pari passo: la configurazione è necessaria per conoscere l'architettura del modello, mentre i pesi del modello sono i suoi parametri. + +{:else} + +Il file *tf_model.h5* è noto come *state dictionary*; contiene tutti i pesi del modello. I due file vanno di pari passo: la configurazione è necessaria per conoscere l'architettura del modello, mentre i pesi del modello sono i suoi parametri. + +{/if} + +## Using a Transformer model for inference + +Ora che si sa come caricare e salvare un modello, proviamo a usarlo per fare delle previsioni. I modelli di trasformatori possono elaborare solo numeri - numeri generati dal tokenizer. Ma prima di parlare dei tokenizer, analizziamo quali sono gli input accettati dal modello. + +I tokenizer possono occuparsi di effettuare il casting degli input nei tensori del framework appropriato, ma per aiutarti a capire cosa sta succedendo, daremo una rapida occhiata a ciò che deve essere fatto prima di inviare gli input al modello. + +Supponiamo di avere un paio di sequenze: + +```py +sequences = ["Hello!", "Cool.", "Nice!"] +``` + +The tokenizer converts these to vocabulary indices which are typically called *input IDs*. Each sequence is now a list of numbers! The resulting output is: + +```py no-format +encoded_sequences = [ + [101, 7592, 999, 102], + [101, 4658, 1012, 102], + [101, 3835, 999, 102], +] +``` + +Si tratta di una lista di sequenze codificate: una lista di liste. I tensori accettano solo forme rettangolari (si pensi alle matrici). Questo "array" è già di forma rettangolare, quindi convertirlo in un tensore è facile: + +{#if fw === 'pt'} +```py +import torch + +model_inputs = torch.tensor(encoded_sequences) +``` +{:else} +```py +import tensorflow as tf + +model_inputs = tf.constant(encoded_sequences) +``` +{/if} + +### Uso dei tensori come input del modello + +Utilizzare i tensori con il modello è estremamente semplice: basta chiamare il modello con gli input: + +```py +output = model(model_inputs) +``` + +Il modello accetta molti argomenti diversi, ma solo gli ID degli ingressi sono necessari. Spiegheremo in seguito cosa fanno gli altri argomenti e quando sono necessari, ma prima dobbiamo dare un'occhiata più da vicino ai tokenizer che costruiscono gli input che un modello Transformer può comprendere. \ No newline at end of file diff --git a/chapters/it/chapter2/4.mdx b/chapters/it/chapter2/4.mdx new file mode 100644 index 000000000..5cd002b5e --- /dev/null +++ b/chapters/it/chapter2/4.mdx @@ -0,0 +1,240 @@ + + +# Tokenizers + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + + +I tokenizer sono uno dei componenti fondamentali della pipeline NLP. Servono a uno scopo: tradurre il testo in dati che possono essere elaborati dal modello. I modelli possono elaborare solo numeri, quindi i tokenizer devono convertire i nostri input testuali in dati numerici. In questa sezione analizzeremo cosa succede esattamente nella pipeline di tokenizzazione. + +Nelle attività di NLP, i dati che vengono generalmente processati sono testi non elaborati, grezzi. Ecco un esempio di testo grezzo: + +``` +Jim Henson was a puppeteer +``` + +Tuttavia, i modelli possono elaborare solo numeri, quindi dobbiamo trovare un modo per convertire il testo non elaborato in numeri. Questo è ciò che fanno i tokenizer, e ci sono molti modi per farlo. L'obiettivo è trovare la rappresentazione più significativa, cioè quella che ha più senso per il modello, e, se possibile, la rappresentazione più piccola. + +Vediamo alcuni esempi di algoritmi di tokenizzazione e cerchiamo di rispondere ad alcune domande sulla tokenizzazione. + +## Tokenizer basati sulle parole + + + +Il primo tipo di tokenizzatore che viene in mente è quello _basato sulle parole_. In genere è molto facile da configurare e utilizzare con poche regole e spesso produce risultati decenti. Ad esempio, nell'immagine qui sotto, l'obiettivo è dividere il testo non elaborato in parole e trovare una rappresentazione numerica per ciascuna di esse: + +
+ Un esempio di tokenizzazione basata sulle parole. + +
+ +Esistono diversi modi per dividere il testo. Ad esempio, si possono usare gli spazi bianchi per suddividere il testo in parole, applicando la funzione `split()` di Python: + +```py +tokenized_text = "Jim Henson was a puppeteer".split() +print(tokenized_text) +``` + +```python out +['Jim', 'Henson', 'was', 'a', 'puppeteer'] +``` + +Esistono anche varianti di tokenizzatori di parole che prevedono regole aggiuntive per la punteggiatura. Con questo tipo di tokenizer, possiamo ritrovarci con "vocabolari" piuttosto grandi, dove un vocabolario è definito dal numero totale di token indipendenti che abbiamo nel nostro corpus. + +A ogni parola viene assegnato un ID, a partire da 0 fino alla dimensione del vocabolario. Il modello utilizza questi ID per identificare ogni parola. + +Se vogliamo coprire completamente una lingua con un tokenizzatore basato sulle parole, dovremo avere un identificatore per ogni parola della lingua, il che genererà un'enorme quantità di token. Per esempio, nella lingua inglese ci sono più di 500.000 parole, quindi per costruire una mappa da ogni parola a un ID di input dovremmo tenere traccia di così tanti ID. Inoltre, parole come "cane" sono rappresentate in modo diverso da parole come "cani", e il modello inizialmente non avrà modo di sapere che "cane" e "cani" sono simili: identificherà le due parole come non correlate. Lo stesso vale per altre parole simili, come "correre" e "correndo", che il modello non vedrà inizialmente come simili. + +Infine, abbiamo bisogno di un token personalizzato per rappresentare le parole che non fanno parte del nostro vocabolario. Questo è noto come token "unknown", spesso rappresentato come "[UNK]" o "<unk>". Se il tokenizer produce molti token di questo tipo è generalmente un brutto segno, perché non è riuscito a trovare una rappresentazione sensata della parola e si stanno perdendo informazioni. L'obiettivo della creazione del vocabolario è quello di fare in modo che il tokenizzatore inserisca il minor numero possibile di parole nel token sconosciuto. + +Un modo per ridurre la quantità di token sconosciuti è quello di andare un livello più in profondità, usando un tokenizer _character-based_. + +## Character-based + + + +I tokenizer basati sui caratteri dividono il testo in caratteri, anziché in parole. Ciò comporta due vantaggi principali: + +- Il vocabolario è molto più ridotto. +- I token fuori vocabolario (sconosciuti) sono molto meno numerosi, poiché ogni parola può essere costruita a partire dai caratteri. + +Ma anche in questo caso sorgono alcune questioni relative agli spazi e alla punteggiatura: + +
+ Un esempio di tokenizzazione basata sui caratteri. + +
+ +Anche questo approccio non è perfetto. Poiché la rappresentazione è ora basata su caratteri anziché su parole, si potrebbe sostenere che, intuitivamente, è meno significativa: ogni carattere non significa molto da solo, mentre è così per le parole. Tuttavia, anche in questo caso il significato varia a seconda della lingua; in cinese, ad esempio, ogni carattere porta con sé più informazioni di un carattere in una lingua latina. + +Un'altra cosa da considerare è che ci ritroveremo con una quantità molto elevata di token da elaborare da parte del nostro modello: mentre una parola sarebbe un singolo token con un tokenizzatore basato sulle parole, può facilmente trasformarsi in 10 o più token quando viene convertita in caratteri. + +Per ottenere il meglio dei due mondi, possiamo utilizzare una terza tecnica che combina i due approcci: la *tokenizzazione delle sottoparole*. + +## Tokenizzazione delle sottoparole + + + +Gli algoritmi di tokenizzazione delle sottoparole si basano sul principio che le parole di uso frequente non devono essere suddivise in sottoparole più piccole, ma le parole rare devono essere scomposte in sottoparole significative. + +Ad esempio, "fastidiosamente" potrebbe essere considerata una parola rara e potrebbe essere scomposta in "fastidioso" e "mente". È probabile che queste due parole compaiano più frequentemente come sottoparole a sé stanti, mentre il significato di "fastidiosamente" viene mantenuto dal significato composito di "fastidioso" e "mente". + +Ecco un esempio che mostra come un algoritmo di tokenizzazione delle sottoparole tokenizzerebbe la sequenza "Let's do tokenization!": + +
+ Un algoritmo di tokenizzazione delle sottoparole. + +
+ +Queste sottoparole finiscono per fornire un significato semantico: per esempio, nell'esempio precedente "tokenization" è stato diviso in "token" e "ization", due token che hanno un significato semantico pur essendo efficienti dal punto di vista dello spazio (sono necessari solo due token per rappresentare una parola lunga). Questo ci permette di avere una copertura relativamente buona con vocabolari piccoli e quasi nessun token sconosciuto. + +Questo approccio è particolarmente utile nelle lingue agglutinanti come il turco, dove è possibile formare parole complesse (quasi) arbitrariamente lunghe mettendo insieme sottoparole. + +### E non solo! + +Non sorprende che esistano molte altre tecniche. Per citarne alcune: + +- Byte-level BPE, utilizzato in GPT-2 +- WordPiece, utilizzato in BERT +- SentencePiece o Unigram, utilizzato in diversi modelli multilingua. + +A questo punto dovresti avere una conoscenza sufficiente di come funzionano i tokenizer per iniziare a usare l'API. + +## Caricamento e salvataggio + +Caricare e salvare i tokenizer è semplice come per i modelli. In realtà, si basa sugli stessi due metodi: `from_pretrained()` e `save_pretrained()`. Questi metodi caricano o salvano l'algoritmo usato dal tokenizer (un po' come l'*architettura* del modello) ed il suo vocabolario (un po' come i *pesi* del modello). + +Il caricamento del tokenizer di BERT, addestrato con lo stesso checkpoint di BERT, avviene nello stesso modo in cui si carica il modello, con la differenza che si usa la classe `BertTokenizer`: + +```py +from transformers import BertTokenizer + +tokenizer = BertTokenizer.from_pretrained("bert-base-cased") +``` + +{#if fw === 'pt'} +In modo simile a `AutoModel`, la classe `AutoTokenizer` prenderà la classe tokenizer appropriata nella libreria in base al nome del checkpoint e può essere usata direttamente con qualsiasi checkpoint: + +{:else} +In modo simile a `TFAutoModel`, la classe `AutoTokenizer` prenderà la classe tokenizer appropriata nella libreria in base al nome del checkpoint e può essere usata direttamente con qualsiasi checkpoint: + +{/if} + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +``` + +Ora possiamo usare il tokenizer come mostrato nella sezione precedente: + +```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]} +``` + +Salvare un tokenizer è identico a salvare un modello: + +```py +tokenizer.save_pretrained("directory_on_my_computer") +``` + +Parleremo meglio dei `token_type_ids` nel [Capitolo 3](/course/chapter3) e spiegheremo la chiave `attention_mask` un po' più avanti. Per prima cosa, vediamo come vengono generati gli `input_ids`. Per farlo, dobbiamo esaminare i metodi intermedi del tokenizer. + +## Codifica + + + +La traduzione del testo in numeri è nota come _codifica_. La codifica avviene in due fasi: la tokenizzazione, seguita dalla conversione in input ID. + +Come abbiamo visto, il primo passo consiste nel dividere il testo in parole (o parti di parole, simboli di punteggiatura, ecc.), solitamente chiamate *token*. Ci sono diverse regole che possono governare questo processo, ed è per questo che dobbiamo istanziare il tokenizer usando il nome del modello, per assicurarci di usare le stesse regole che sono state usate quando il modello è stato preaddestrato. + +Il secondo passo consiste nel convertire i token in numeri, in modo da poterne costruire un tensore e darlo in pasto al modello. Per fare questo, il tokenizer ha un *vocabolario*, che è la parte che scarichiamo quando lo istanziamo con il metodo `from_pretrained()`. Anche in questo caso, dobbiamo utilizzare lo stesso vocabolario usato quando il modello è stato preaddestrato. + +Per comprendere meglio le due fasi, le esploreremo separatamente. Si noti che utilizzeremo alcuni metodi che eseguono parti della pipeline di tokenizzazione separatamente per mostrare i risultati intermedi di tali passaggi, ma in pratica si dovrebbe chiamare il tokenizzatore direttamente sui propri input (come mostrato nella sezione 2). + +### Processo di tokenizzazione + +Il processo di tokenizzazione viene eseguito dal metodo `tokenize()` del tokenizer: + +```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) +``` + +L'output di questo metodo è un elenco di stringhe, o token: + +```python out +['Using', 'a', 'transform', '##er', 'network', 'is', 'simple'] +``` + +Questo tokenizzatore è un tokenizzatore di sottoparole: divide le parole fino a ottenere token che possono essere rappresentati dal suo vocabolario. È il caso di `trasformatore`, che viene diviso in due token: `trasforma` e `##tore`. + +### Dai token agli input IDS + +La conversione in ID di input è gestita dal metodo del tokenizer `convert_tokens_to_ids()`: + +```py +ids = tokenizer.convert_tokens_to_ids(tokens) + +print(ids) +``` + +```python out +[7993, 170, 11303, 1200, 2443, 1110, 3014] +``` + +Questi risultati, una volta convertiti nel tensore quadro appropriato, possono essere successivamente utilizzati come input per un modello, come visto in precedenza in questo capitolo. + + + +✏️ **Provaci anche tu!** Replica gli ultimi due passaggi (tokenizzazione e conversione in ID di input) sulle frasi di input utilizzate nella sezione 2 ("I've been waiting for a HuggingFace course my whole life." e "I hate this so much!"). Verificate di ottenere gli stessi ID di input che abbiamo ottenuto in precedenza! + + + +## Decodifica + +La *decodifica* avviene al contrario: dagli indici del vocabolario si vuole ottenere una stringa. Questo può essere fatto con il metodo `decode()` come segue: + +```py +decoded_string = tokenizer.decode([7993, 170, 11303, 1200, 2443, 1110, 3014]) +print(decoded_string) +``` + +```python out +'Using a Transformer network is simple' +``` + +Si noti che il metodo `decode` non solo converte gli indici in token, ma raggruppa anche i token che fanno parte delle stesse parole per produrre una frase leggibile. Questo comportamento sarà estremamente utile quando utilizzeremo modelli che prevedono un nuovo testo (o un testo generato da un prompt, o per problemi di sequenza-sequenza come la traduzione o il riassunto). + +A questo punto si dovrebbero comprendere le operazioni atomiche che un tokenizer può gestire: tokenizzazione, conversione in ID e conversione degli ID in stringhe. Tuttavia, abbiamo solo raschiato la punta dell'iceberg. Nella sezione che segue, vedremo i limiti del nostro approccio e vedremo come superarli. diff --git a/chapters/ja/chapter7/2.mdx b/chapters/ja/chapter7/2.mdx index 82739dbda..5ad6ba398 100644 --- a/chapters/ja/chapter7/2.mdx +++ b/chapters/ja/chapter7/2.mdx @@ -409,7 +409,7 @@ tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( id2label` と `label2id` という 2 つの辞書型データがあり、ID からラベル、ラベルから ID へのマッピングを設定することができます。 ```py -id2label = {str(i): label for i, label in enumerate(label_names)} +id2label = {i: label for i, label in enumerate(label_names)} label2id = {v: k for k, v in id2label.items()} ``` @@ -675,7 +675,7 @@ metric.compute(predictions=[all_predictions], references=[all_labels]) id2label` と `label2id` という 2 つの辞書型データがあり、ID からラベル、ラベルから ID へのマッピングを設定することができます。 ```py -id2label = {str(i): label for i, label in enumerate(label_names)} +id2label = {i: label for i, label in enumerate(label_names)} label2id = {v: k for k, v in id2label.items()} ``` diff --git a/chapters/vi/chapter7/2.mdx b/chapters/vi/chapter7/2.mdx index 1346f837a..619d3e932 100644 --- a/chapters/vi/chapter7/2.mdx +++ b/chapters/vi/chapter7/2.mdx @@ -435,7 +435,7 @@ Vì chúng tôi đang giải quyết vấn đề phân loại token, chúng ta s Chúng phải được đặt bởi hai từ điển, `id2label` và `label2id`, chứa ánh xạ từ ID đến nhãn và ngược lại: ```py -id2label = {str(i): label for i, label in enumerate(label_names)} +id2label = {i: label for i, label in enumerate(label_names)} label2id = {v: k for k, v in id2label.items()} ``` @@ -683,7 +683,7 @@ Vì chúng ta đang giải quyết vấn đề phân loại token, chúng ta s Chúng phải được đặt bởi hai từ điển, `id2label` và `label2id`, chứa các ánh xạ từ ID đến nhãn và ngược lại: ```py -id2label = {str(i): label for i, label in enumerate(label_names)} +id2label = {i: label for i, label in enumerate(label_names)} label2id = {v: k for k, v in id2label.items()} ``` diff --git a/chapters/zh-CN/chapter7/2.mdx b/chapters/zh-CN/chapter7/2.mdx index 9ce606af6..c85dd51f8 100644 --- a/chapters/zh-CN/chapter7/2.mdx +++ b/chapters/zh-CN/chapter7/2.mdx @@ -403,7 +403,7 @@ tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( 它们应该由两个字典设置, `id2label` 和 `label2id` ,其中包含从 ID 到标签的映射,反之亦然: ```py -id2label = {str(i): label for i, label in enumerate(label_names)} +id2label = {i: label for i, label in enumerate(label_names)} label2id = {v: k for k, v in id2label.items()} ``` @@ -653,7 +653,7 @@ metric.compute(predictions=[all_predictions], references=[all_labels]) 它们应该由两个字典设置, `id2label` 和 `label2id` ,其中包含从 ID 到标签的映射,反之亦然: ```py -id2label = {str(i): label for i, label in enumerate(label_names)} +id2label = {i: label for i, label in enumerate(label_names)} label2id = {v: k for k, v in id2label.items()} ``` diff --git a/utils/validate_translation.py b/utils/validate_translation.py index b4a3f792b..ef28a00fa 100644 --- a/utils/validate_translation.py +++ b/utils/validate_translation.py @@ -14,7 +14,7 @@ def load_sections(language: str): for chapter in toc: for section in chapter["sections"]: sections.append(section["local"]) - return set(sorted(sections)) + return set(sections) if __name__ == "__main__": @@ -24,10 +24,14 @@ def load_sections(language: str): english_sections = load_sections("en") translation_sections = load_sections(args.language) - missing_sections = english_sections.difference(translation_sections) + missing_sections = sorted(english_sections.difference(translation_sections)) if len(missing_sections) > 0: - print("Missing sections:") + print("Completed sesions:\n") + for section in sorted(translation_sections): + print(section) + + print("\nMissing sections:\n") for section in missing_sections: print(section) else: From a8e9024874fe84b617f0be48b1882b953c61bcb5 Mon Sep 17 00:00:00 2001 From: lewtun Date: Fri, 28 Oct 2022 21:27:10 +0200 Subject: [PATCH 37/51] Bump release (#355) --- .github/workflows/build_documentation.yml | 2 +- .github/workflows/build_pr_documentation.yml | 2 +- README.md | 1 + chapters/en/chapter1/1.mdx | 47 +++ chapters/en/chapter7/2.mdx | 4 +- chapters/en/chapter7/3.mdx | 12 +- chapters/en/chapter7/4.mdx | 48 ++- chapters/en/chapter7/5.mdx | 42 ++- chapters/en/chapter7/6.mdx | 12 +- chapters/en/chapter7/7.mdx | 14 +- chapters/es/_toctree.yml | 6 + chapters/es/chapter2/6.mdx | 164 +++++++++ chapters/es/chapter2/7.mdx | 18 + chapters/es/chapter2/8.mdx | 310 +++++++++++++++++ chapters/id/_toctree.yml | 11 + chapters/id/chapter0/1.mdx | 110 ++++++ chapters/id/chapter1/1.mdx | 61 ++++ chapters/id/chapter1/2.mdx | 27 ++ chapters/it/_toctree.yml | 6 + chapters/it/chapter2/5.mdx | 339 +++++++++++++++++++ chapters/it/chapter2/6.mdx | 164 +++++++++ chapters/it/chapter2/7.mdx | 18 + chapters/ru/chapter3/4.mdx | 4 +- utils/generate_notebooks.py | 5 + 24 files changed, 1372 insertions(+), 55 deletions(-) create mode 100644 chapters/es/chapter2/6.mdx create mode 100644 chapters/es/chapter2/7.mdx create mode 100644 chapters/es/chapter2/8.mdx create mode 100644 chapters/id/_toctree.yml create mode 100644 chapters/id/chapter0/1.mdx create mode 100644 chapters/id/chapter1/1.mdx create mode 100644 chapters/id/chapter1/2.mdx create mode 100644 chapters/it/chapter2/5.mdx create mode 100644 chapters/it/chapter2/6.mdx create mode 100644 chapters/it/chapter2/7.mdx diff --git a/.github/workflows/build_documentation.yml b/.github/workflows/build_documentation.yml index b730778f2..b09b32f58 100644 --- a/.github/workflows/build_documentation.yml +++ b/.github/workflows/build_documentation.yml @@ -14,6 +14,6 @@ jobs: package: course path_to_docs: course/chapters/ additional_args: --not_python_module - languages: ar bn de en es fa fr gj he hi it ja ko pt ru th tr vi zh-CN zh-TW + 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 }} diff --git a/.github/workflows/build_pr_documentation.yml b/.github/workflows/build_pr_documentation.yml index 51347210f..45e3b3e09 100644 --- a/.github/workflows/build_pr_documentation.yml +++ b/.github/workflows/build_pr_documentation.yml @@ -16,5 +16,5 @@ jobs: package: course path_to_docs: course/chapters/ additional_args: --not_python_module - languages: ar bn de en es fa fr gj he hi it ja ko pt ru th tr vi zh-CN zh-TW + 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/README.md b/README.md index db63d526c..db20052fa 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ This repo contains the content that's used to create the **[Hugging Face course] | [Gujarati](https://huggingface.co/course/gu/chapter1/1) (WIP) | [`chapters/gu`](https://github.com/huggingface/course/tree/main/chapters/gu) | [@pandyaved98](https://github.com/pandyaved98) | | [Hebrew](https://huggingface.co/course/he/chapter1/1) (WIP) | [`chapters/he`](https://github.com/huggingface/course/tree/main/chapters/he) | [@omer-dor](https://github.com/omer-dor) | | [Hindi](https://huggingface.co/course/hi/chapter1/1) (WIP) | [`chapters/hi`](https://github.com/huggingface/course/tree/main/chapters/hi) | [@pandyaved98](https://github.com/pandyaved98) | +| [Bahasa Indonesia](https://huggingface.co/course/id/chapter1/1) (WIP) | [`chapters/id`](https://github.com/huggingface/course/tree/main/chapters/id) | [@gstdl](https://github.com/gstdl) | | [Italian](https://huggingface.co/course/it/chapter1/1) (WIP) | [`chapters/it`](https://github.com/huggingface/course/tree/main/chapters/it) | [@CaterinaBi](https://github.com/CaterinaBi), [@ClonedOne](https://github.com/ClonedOne), [@Nolanogenn](https://github.com/Nolanogenn), [@EdAbati](https://github.com/EdAbati), [@gdacciaro](https://github.com/gdacciaro) | | [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) | diff --git a/chapters/en/chapter1/1.mdx b/chapters/en/chapter1/1.mdx index 591e71ccf..2c66a5250 100644 --- a/chapters/en/chapter1/1.mdx +++ b/chapters/en/chapter1/1.mdx @@ -55,7 +55,54 @@ About the authors: **Leandro von Werra** is a machine learning engineer in the open-source team at Hugging Face and also a co-author of the O’Reilly book [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). He has several years of industry experience bringing NLP projects to production by working across the whole machine learning stack.. +## FAQ + +Here are some answers to frequently asked questions: + +- **Does taking this course lead to a certification?** +Currently we do not have any certification for this course. However, we are working on a certification program for the Hugging Face ecosystem -- stay tuned! + +- **How much time should I spend on this course?** +Each chapter in this course is designed to be completed in 1 week, with approximately 6-8 hours of work per week. However, you can take as much time as you need to complete the course. + +- **Where can I ask a question if I have one?** +If you have a question about any section of the course, just click on the "*Ask a question*" banner at the top of the page to be automatically redirected to the right section of the [Hugging Face forums](https://discuss.huggingface.co/): + +Link to the Hugging Face forums + +Note that a list of [project ideas](https://discuss.huggingface.co/c/course/course-event/25) is also available on the forums if you wish to practice more once you have completed the course. + +- **Where can I get the code for the course?** +For each section, click on the banner at the top of the page to run the code in either Google Colab or Amazon SageMaker Studio Lab: + +Link to the Hugging Face course notebooks + +The Jupyter notebooks containing all the code from the course are hosted on the [`huggingface/notebooks`](https://github.com/huggingface/notebooks) repo. If you wish to generate them locally, check out the instructions in the [`course`](https://github.com/huggingface/course#-jupyter-notebooks) repo on GitHub. + + +- **How can I contribute to the course?** +There are many ways to contribute to the course! If you find a typo or a bug, please open an issue on the [`course`](https://github.com/huggingface/course) repo. If you would like to help translate the course into your native language, check out the instructions [here](https://github.com/huggingface/course#translating-the-course-into-your-language). + +- ** What were the choices made for the each translation?** +Each translation has a glossary and `TRANSLATING.txt` file that details the choices that were made for machine learning jargon etc. You can find an example for German [here](https://github.com/huggingface/course/blob/main/chapters/de/TRANSLATING.txt). + + +- **Can I reuse this course?** +Of course! The course is released under the permissive [Apache 2 license](https://www.apache.org/licenses/LICENSE-2.0.html). This means that you must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. If you would like to cite the course, please use the following BibTeX: + +``` +@misc{huggingfacecourse, + author = {Hugging Face}, + title = {The Hugging Face Course, 2022}, + howpublished = "\url{https://huggingface.co/course}", + year = {2022}, + note = "[Online; accessed ]" +} +``` + Are you ready to roll? In this chapter, you will learn: + * How to use the `pipeline()` function to solve NLP tasks such as text generation and classification * About the Transformer architecture * How to distinguish between encoder, decoder, and encoder-decoder architectures and use cases + diff --git a/chapters/en/chapter7/2.mdx b/chapters/en/chapter7/2.mdx index af3527558..81ef920d6 100644 --- a/chapters/en/chapter7/2.mdx +++ b/chapters/en/chapter7/2.mdx @@ -371,7 +371,7 @@ As we can see, the second set of labels has been padded to the length of the fir {:else} -Our data collator is ready to go! Now let's use it to make a `tf.data.Dataset` with the `to_tf_dataset()` method. +Our data collator is ready to go! Now let's use it to make a `tf.data.Dataset` with the `to_tf_dataset()` method. You can also use `model.prepare_tf_dataset()` to do this with a bit less boilerplate code - you'll see this in some of the other sections of this chapter. ```py tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( @@ -616,7 +616,7 @@ import numpy as np all_predictions = [] all_labels = [] for batch in tf_eval_dataset: - logits = model.predict(batch)["logits"] + logits = model.predict_on_batch(batch)["logits"] labels = batch["labels"] predictions = np.argmax(logits, axis=-1) for prediction, label in zip(predictions, labels): diff --git a/chapters/en/chapter7/3.mdx b/chapters/en/chapter7/3.mdx index 30e4161e1..982d8beba 100644 --- a/chapters/en/chapter7/3.mdx +++ b/chapters/en/chapter7/3.mdx @@ -96,7 +96,6 @@ model = TFAutoModelForMaskedLM.from_pretrained(model_checkpoint) We can see how many parameters this model has by calling the `summary()` method: ```python -model(model.dummy_inputs) # Build the model model.summary() ``` @@ -636,18 +635,18 @@ in your favorite terminal and log in there. {#if fw === 'tf'} -Once we're logged in, we can create our `tf.data` datasets. We'll just use the standard data collator here, but you can also try the whole word masking collator and compare the results as an exercise: +Once we're logged in, we can create our `tf.data` datasets. To do so, we'll use the `prepare_tf_dataset()` method, which uses our model to automatically infer which columns should go into the dataset. If you want to control exactly which columns to use, you can use the `Dataset.to_tf_dataset()` method instead. To keep things simple, we'll just use the standard data collator here, but you can also try the whole word masking collator and compare the results as an exercise: ```python -tf_train_dataset = downsampled_dataset["train"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], +tf_train_dataset = model.prepare_tf_dataset( + downsampled_dataset["train"], collate_fn=data_collator, shuffle=True, batch_size=32, ) -tf_eval_dataset = downsampled_dataset["test"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], +tf_eval_dataset = model.prepare_tf_dataset( + downsampled_dataset["test"], collate_fn=data_collator, shuffle=False, batch_size=32, @@ -675,6 +674,7 @@ model.compile(optimizer=optimizer) # Train in mixed-precision 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 ) diff --git a/chapters/en/chapter7/4.mdx b/chapters/en/chapter7/4.mdx index d159a7a69..e7b2ad3fb 100644 --- a/chapters/en/chapter7/4.mdx +++ b/chapters/en/chapter7/4.mdx @@ -378,14 +378,14 @@ We will pass this `data_collator` along to the `Seq2SeqTrainer`. Next, let's hav We can now use this `data_collator` to convert each of our datasets to a `tf.data.Dataset`, ready for training: ```python -tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], +tf_train_dataset = model.prepare_tf_dataset( + tokenized_datasets["train"], collate_fn=data_collator, shuffle=True, batch_size=32, ) -tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], +tf_eval_dataset = model.prepare_tf_dataset( + tokenized_datasets["validation"], collate_fn=data_collator, shuffle=False, batch_size=16, @@ -495,28 +495,42 @@ The score can go from 0 to 100, and higher is better. {#if fw === 'tf'} -To get from the model outputs to texts the metric can use, we will use the `tokenizer.batch_decode()` method. We just have to clean up all the `-100`s in the labels; the tokenizer will automatically do the same for the padding token. Let's define a function that takes our model and a dataset and computes metrics on it. Because generation of long sequences can be slow, we subsample the validation set to make sure this doesn't take forever: +To get from the model outputs to texts the metric can use, we will use the `tokenizer.batch_decode()` method. We just have to clean up all the `-100`s in the labels; the tokenizer will automatically do the same for the padding token. Let's define a function that takes our model and a dataset and computes metrics on it. We're also going to use a trick that dramatically increases performance - compiling our generation code with [XLA](https://www.tensorflow.org/xla), TensorFlow's accelerated linear algebra compiler. XLA applies various optimizations to the model's computation graph, and results in significant improvements to speed and memory usage. As described in the Hugging Face [blog](https://huggingface.co/blog/tf-xla-generate), XLA works best when our input shapes don't vary too much. To handle this, we'll pad our inputs to multiples of 128, and make a new dataset with the padding collator, and then we'll apply the `@tf.function(jit_compile=True)` decorator to our generation function, which marks the whole function for compilation with 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 = [] - sampled_dataset = tokenized_datasets["validation"].shuffle().select(range(200)) - tf_generate_dataset = sampled_dataset.to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=False, - batch_size=4, - ) - for batch in tf_generate_dataset: - predictions = model.generate( - input_ids=batch["input_ids"], attention_mask=batch["attention_mask"] - ) + + for batch, labels in tqdm(tf_generate_dataset): + predictions = generate_with_xla(batch) decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) - labels = batch["labels"].numpy() + 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] diff --git a/chapters/en/chapter7/5.mdx b/chapters/en/chapter7/5.mdx index a45108bcf..4734bd700 100644 --- a/chapters/en/chapter7/5.mdx +++ b/chapters/en/chapter7/5.mdx @@ -289,7 +289,9 @@ def preprocess_function(examples): max_length=max_input_length, truncation=True, ) - labels = tokenizer(text_target=targets, max_length=max_target_length, truncation=True) + labels = tokenizer( + examples["review_title"], max_length=max_target_length, truncation=True + ) model_inputs["labels"] = labels["input_ids"] model_inputs["labels_mask"] = labels["attention_mask"] return model_inputs @@ -673,14 +675,14 @@ To wrap up this section, let's take a look at how we can also fine-tune mT5 usin We're almost ready to train! We just need to convert our datasets to `tf.data.Dataset`s using the data collator we defined above, and then `compile()` and `fit()` the model. First, the datasets: ```python -tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], +tf_train_dataset = model.prepare_tf_dataset( + tokenized_datasets["train"], collate_fn=data_collator, shuffle=True, batch_size=8, ) -tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], +tf_eval_dataset = model.prepare_tf_dataset( + tokenized_datasets["validation"], collate_fn=data_collator, shuffle=False, batch_size=8, @@ -727,18 +729,40 @@ model.fit( ) ``` -We got some loss values during training, but really we'd like to see the ROUGE metrics we computed earlier. To get those metrics, we'll need to generate outputs from the model and convert them to strings. Let's build some lists of labels and predictions for the ROUGE metric to compare (note that if you get import errors for this section, you may need to`!pip install tqdm`): +We got some loss values during training, but really we'd like to see the ROUGE metrics we computed earlier. To get those metrics, we'll need to generate outputs from the model and convert them to strings. Let's build some lists of labels and predictions for the ROUGE metric to compare (note that if you get import errors for this section, you may need to`!pip install tqdm`). We're also going to use a trick that dramatically increases performance - compiling our generation code with [XLA](https://www.tensorflow.org/xla), TensorFlow's accelerated linear algebra compiler. XLA applies various optimizations to the model's computation graph, and results in significant improvements to speed and memory usage. As described in the Hugging Face [blog](https://huggingface.co/blog/tf-xla-generate), XLA works best when our input shapes don't vary too much. To handle this, we'll pad our inputs to multiples of 128, and make a new dataset with the padding collator, and then we'll apply the `@tf.function(jit_compile=True)` decorator to our generation function, which marks the whole function for compilation with 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 in tqdm(tf_eval_dataset): - predictions = model.generate(**batch) +for batch, labels in tqdm(tf_generate_dataset): + predictions = generate_with_xla(batch) decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) - labels = batch["labels"].numpy() + 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] diff --git a/chapters/en/chapter7/6.mdx b/chapters/en/chapter7/6.mdx index 09f1cefcb..b5e48fdc8 100644 --- a/chapters/en/chapter7/6.mdx +++ b/chapters/en/chapter7/6.mdx @@ -379,17 +379,17 @@ We can see that the examples have been stacked and all the tensors have the same {#if fw === 'tf'} -Now we can use the `to_tf_dataset()` method to convert our datasets to TensorFlow datasets with the data collator we created above: +Now we can use the `prepare_tf_dataset()` method to convert our datasets to TensorFlow datasets with the data collator we created above: ```python -tf_train_dataset = tokenized_dataset["train"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], +tf_train_dataset = model.prepare_tf_dataset( + tokenized_dataset["train"], collate_fn=data_collator, shuffle=True, batch_size=32, ) -tf_eval_dataset = tokenized_dataset["valid"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], +tf_eval_dataset = model.prepare_tf_dataset( + tokenized_dataset["valid"], collate_fn=data_collator, shuffle=False, batch_size=32, @@ -515,7 +515,7 @@ model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback {:else} -💡 If you have access to a machine with multiple GPUs, you can try using a `MirroredStrategy` context to substantially speed up training. You'll need to create a `tf.distribute.MirroredStrategy` object, and make sure that the `to_tf_dataset` commands as well as model creation and the call to `fit()` are all run in its `scope()` context. You can see documentation on this [here](https://www.tensorflow.org/guide/distributed_training#use_tfdistributestrategy_with_keras_modelfit). +💡 If you have access to a machine with multiple GPUs, you can try using a `MirroredStrategy` context to substantially speed up training. You'll need to create a `tf.distribute.MirroredStrategy` object, and make sure that any `to_tf_dataset()` or `prepare_tf_dataset()` methods as well as model creation and the call to `fit()` are all run in its `scope()` context. You can see documentation on this [here](https://www.tensorflow.org/guide/distributed_training#use_tfdistributestrategy_with_keras_modelfit). {/if} diff --git a/chapters/en/chapter7/7.mdx b/chapters/en/chapter7/7.mdx index f103c7764..ed85f59c3 100644 --- a/chapters/en/chapter7/7.mdx +++ b/chapters/en/chapter7/7.mdx @@ -862,20 +862,14 @@ data_collator = DefaultDataCollator(return_tensors="tf") And now we create the datasets as usual. ```python -tf_train_dataset = train_dataset.to_tf_dataset( - columns=[ - "input_ids", - "start_positions", - "end_positions", - "attention_mask", - "token_type_ids", - ], +tf_train_dataset = model.prepare_tf_dataset( + train_dataset, collate_fn=data_collator, shuffle=True, batch_size=16, ) -tf_eval_dataset = validation_dataset.to_tf_dataset( - columns=["input_ids", "attention_mask", "token_type_ids"], +tf_eval_dataset = model.prepare_tf_dataset( + validation_dataset, collate_fn=data_collator, shuffle=False, batch_size=16, diff --git a/chapters/es/_toctree.yml b/chapters/es/_toctree.yml index 5129356c0..6289440b6 100644 --- a/chapters/es/_toctree.yml +++ b/chapters/es/_toctree.yml @@ -33,6 +33,12 @@ title: Tokenizadores - local: chapter2/5 title: Manejando Secuencias Múltiples + - local: chapter2/6 + title: Poniendo todo junto + - local: chapter2/7 + title: ¡Haz completado el uso básico! + - local: chapter2/8 + title: Quiz de final de capítulo - title: 3. Ajuste (fine-tuning) de un modelo preentrenado sections: diff --git a/chapters/es/chapter2/6.mdx b/chapters/es/chapter2/6.mdx new file mode 100644 index 000000000..443ee3ee2 --- /dev/null +++ b/chapters/es/chapter2/6.mdx @@ -0,0 +1,164 @@ + + +# Poniendo todo junto + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +En las últimas secciones, hemos hecho nuestro mejor esfuerzo para realizar la mayor parte del trabajo a mano. Exploramos como funcionan los tokenizadores y vimos la tokenización, conversión a IDs de entrada, relleno, truncado, y máscaras de atención. + +Sin embargo, como vimos en la sección 3, la API de transformadores 🤗 puede manejar todo esto por nosotros con una función de alto nivel la cual trataremos aquí. Cuando llamas a tu `tokenizer` directamente en una sentencia, obtienes entradas que están lista para pasar a tu modelo: + +```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) +``` + +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`. + +Como veremos en los ejemplos de abajo, este método es muy poderoso. Primero, puede tokenizar una sola secuencia: + +```py +sequence = "I've been waiting for a HuggingFace course my whole life." + +model_inputs = tokenizer(sequence) +``` + +También maneja múltiples secuencias a la vez, sin cambios en la API: + +```py +sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] + +model_inputs = tokenizer(sequences) +``` + +Puede rellenar de acuerdo a varios objetivos: + +```py +# Will pad the sequences up to the maximum sequence length +model_inputs = tokenizer(sequences, padding="longest") + +# Will pad the sequences up to the model max length +# (512 for BERT or DistilBERT) +model_inputs = tokenizer(sequences, padding="max_length") + +# Will pad the sequences up to the specified max length +model_inputs = tokenizer(sequences, padding="max_length", max_length=8) +``` + +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) +model_inputs = tokenizer(sequences, truncation=True) + +# Will truncate the sequences that are longer than the specified max length +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: + +```py +sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] + +# Returns PyTorch tensors +model_inputs = tokenizer(sequences, padding=True, return_tensors="pt") + +# Returns TensorFlow tensors +model_inputs = tokenizer(sequences, padding=True, return_tensors="tf") + +# Returns NumPy arrays +model_inputs = tokenizer(sequences, padding=True, return_tensors="np") +``` + +## Tokens especiales + +Si damos un vistazo a los IDs de entrada retornados por el tokenizer, veremos que son un poquito diferentes a lo que teníamos anteriormente: + +```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] +``` + +Se agregó un ID de token al principio, y uno al final. Decodifiquemos las dos secuencias de IDs de arriba para ver de que se trata: + +```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." +``` + +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 + +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 + +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/es/chapter2/7.mdx b/chapters/es/chapter2/7.mdx new file mode 100644 index 000000000..6d7c470c3 --- /dev/null +++ b/chapters/es/chapter2/7.mdx @@ -0,0 +1,18 @@ +# ¡Haz completado el uso básico! + + + +¡Buen trabajo siguiendo el curso hasta ahora! Para recapitular, en este capítulo tú: + +- Aprendiste los bloques de construcción básicos de un modelo Transformer. +- Aprendiste lo que compone a un pipeline de tokenización. +- Viste cómo usar un modelo Transformer en la práctica. +- Aprendiste cómo aprovechar un tokenizador para convertir texto a tensores que sean entendibles por el modelo. +- Configuraste un tokenizador y un modelo juntos para pasar dle texto a predicciones. +- Aprendiste las limitaciones de los IDs de entrada, y aprendiste acerca de máscaras de atención. +- Jugaste con los métodos del tokenizador versátiles y configurables. + +A partir de ahora, serás capaz de navegar libremente por la documentación de 🤗 Transformers: el vocabulario te sonará familiar, ya que has visto los métodos que usarás la mayor parte del tiempo. diff --git a/chapters/es/chapter2/8.mdx b/chapters/es/chapter2/8.mdx new file mode 100644 index 000000000..b935571c7 --- /dev/null +++ b/chapters/es/chapter2/8.mdx @@ -0,0 +1,310 @@ + + + + +# Quiz de final de capítulo + + + +### 1. ¿Cuál es el orden del pipeline de modelado del lenguaje? + + + +### 2. ¿Cuántas dimensiones tiene el tensor producido por el modelo base de Transformer y cuáles son? + + + +### 3. ¿Cuál de los siguientes es un ejemplo de tokenización de subpalabras? + + + +### 4. ¿Qué es una cabeza del modelo? + + + +{#if fw === 'pt'} +### 5. ¿Qué es un AutoModel? + +AutoTrain?" + }, + { + text: "Un objeto que devuelve la arquitectura correcta basado en el punto de control", + explain: "Exacto: el AutoModel sólo necesita conocer el punto de control desde el cual inicializar para devolver la arquitectura correcta.", + correct: true + }, + { + 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!" + } + ]} +/> + +{:else} +### 5. ¿Qué es un TFAutoModel? + +AutoTrain?" + }, + { + text: "Un objeto que devuelve la arquitectura correcta basado en el punto de control", + explain: "Exacto: el TFAutoModel sólo necesita conocer el punto de control desde el cual inicializar para devolver la arquitectura correcta.", + correct: true + }, + { + 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!" + } + ]} +/> + +{/if} + +### 6. ¿Cuáles son las técnicas a tener en cuenta al realizar batching de secuencias de diferentes longitudes juntas? + + + +### 7. ¿Cuál es el punto de aplicar una funcion SoftMax a las salidas logits por un modelo de clasificación de secuencias? + + + +### 8. ¿En qué método se centra la mayor parte de la API del tokenizador? + +encode, ya que puede codificar texto en IDs e IDs en predicciones", + explain: "¡Incorrecto! Aunque el método encode existe en los tokenizadores, no existe en los modelos." + }, + { + text: "Llamar al objeto tokenizador directamente.", + explain: "¡Exactamente! El método __call__ del tokenizador es un método muy poderoso el cual puede manejar casi cualquier cosa.También es el método usado para recuperar las predicciones de un modelo.", + correct: true + }, + { + text: "pad", + explain: "¡Incorrecto! El relleno es muy útil, pero es solo una parte de la API tokenizador." + }, + { + text: "tokenize", + explain: "El método tokenize es posiblemente uno de los métodos más útiles, pero no es el núcleo de la API tokenizador." + } + ]} +/> + +### 9. ¿Qué contiene la variable `result` en este código de ejemplo? + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +result = tokenizer.tokenize("Hello!") +``` + +__call__ o convert_tokens_to_ids!" + }, + { + text: "Una cadena que contiene todos los tokens", + explain: "Esto sería subóptimo, ya que el objetivo es dividir la cadena en varios tokens." + } + ]} +/> + +{#if fw === 'pt'} +### 10. ¿Hay algo mal con el siguiente código? + +```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. ¿Hay algo mal con el siguiente código? + +```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/id/_toctree.yml b/chapters/id/_toctree.yml new file mode 100644 index 000000000..09eb7b0e6 --- /dev/null +++ b/chapters/id/_toctree.yml @@ -0,0 +1,11 @@ +- title: 0. Persiapan + sections: + - local: chapter0/1 + title: Pendahuluan + +- title: 1. Model-model Transformer + sections: + - local: chapter1/1 + title: Pendahuluan + - local: chapter1/2 + title: Pemrosesan Bahasa Natural diff --git a/chapters/id/chapter0/1.mdx b/chapters/id/chapter0/1.mdx new file mode 100644 index 000000000..0f49326d9 --- /dev/null +++ b/chapters/id/chapter0/1.mdx @@ -0,0 +1,110 @@ +# Pendahuluan + +Selamat datang di kursus Hugging Face! Pada Bab ini, anda akan dibimbing untuk mempersiapkan _working environment_. Jika anda memulai kursus ini untuk pertama kali, anda sangat direkomendasikan untuk menyelesaikan [Bab 1](/course/chapter1) terlebih dahulu. Setelah menyelesaikan [Bab 1](/course/chapter1) anda bisa kembali ke page ini untuk mencoba eksplorasi kodenya secara independen. + +Semua modul yang digunakan dalam kursus ini tersedia dalam modul Python. Di kursus ini, anda juga akan dibimbing untuk mempersiapkan Python _environment_ dan menginstal modul-modul yang dibutuhkan. + +Ada 2 cara untuk jenis _working environment_ yang bisa anda gunakan, Colab notebook dan _virtual environment_ Python. Anda bebas memilih _working envrionment_, tapi untuk pemula, kami menyarankan untuk menggunakan Colab notebook. + +Sebagai catatan, kursus ini tidak mencakup instalasi untuk pengguna Windows. Jika anda menggunakan Windows, mohon menggunakan Colab notebook. Jika anda adalah pengguna Linux atau macOS, anda bebas memilih _working environment_ yang akan dijelaskan dibawah. + +Sebagian besar dari kursus ini akan mewajibkan anda untuk memiliki akun Hugging Face. Jika anda belum memiliki akun, silahkan mendaftar terlebih dahulu di tautan berikut [https://huggingface.co/join](https://huggingface.co/join). + +## Menggunakan Google Colab notebook + +Menggunakan Colab notebook sangatlah sederhana, cukup dengan membuat notebook baru anda sudah bisa mulai koding! + +Jika anda belum terbiasa menggunakan Colab, silahkan mengikuti [tutorial pengenalan Colab dari Google](https://colab.research.google.com/notebooks/intro.ipynb) (hanya tersedia dalam Bahasa Inggris). Saat menggunakan Colab, anda dapat mengakses hardware seperti GPU dan TPU yang dapat mengakselerasi proses pengolahan data. Hardware ini dapat anda gunakan secara gratis untuk proyek skala kecil. + +Setelah terbiasa dengan Colab, buatlah notebook baru dengan setup sebagai berikut: + +
+An empty colab notebook +
+ +Langkah berikutnya adalah menginstal modul-modul yang akan digunakan dalam kursus ini menggunakan `pip`. `pip` adalah modul manager untuk bahasa pemrograman Python. Di dalam notebook, anda dapat mengakses komando sistem dengan menambahkan tanda seru (`!`) sebelum kode instruksi anda. Contoh instalasi modul 🤗 adalah sebagai berikut: + +``` +!pip install transformers +``` + +Untuk memastikan bahwa modul telah terinstalasi dengan benar, anda perlu mencoba untuk meng-_import_ modul tersebut di _runtime_ Python anda: + +``` +import transformers +``` + +
+A gif showing the result of the two commands above: installation and import +
+ +Kode instruksi diatas menginstall versi ringan dari 🤗 Transformers. Versi ringan ini tidak mengistall modul _machine learning_ (seperti PyTorch atau TensorFlow). Sangat direkomendasikan untuk mengistal versi _development_ dari modul ini karena nanti anda akan menggunakan berbagai macam fitur yang tersedia didalam modul ini dan versi ini juga akan mencakup berbagai macam modul untuk segala macam kasus yang akan dihadapi dalam kursus ini. Untuk mengistal versi _development_, silahkan eksekusi kode dibawah: + +``` +!pip install transformers[sentencepiece] +``` + +Proses instalasi akan berlangsung cukup lama. Tapi saat instalasi selesai, anda sudah siap untuk menyelesaikan kursus ini! + +## Menggunakan Python _virtual environment_ + +Jika anda ingin menggunakan Python _virtual environment_, tentu saja langkah pertama yang harus anda lewati adalah menginstal Python. Untuk menginstal Python, bisa mengikuti referensi di tautan [ini](https://realpython.com/installing-python/). + +Setelah Python berhasil terinstalasi, anda bisa menjalankan kode Python di terminal anda. Anda bisa memulai dengan mengeksekusi instruksi berikut untuk memastikan bahwa Python terinstalasi dengan benar: `python --version`. Instruksi ini akan menampilkan versi Python yang terinstalasi di komputer anda. + +Python yang saat ini terinstalasi di sistem anda adalah versi Python *"utama"* untuk sistem anda. Sangat direkomendasikan untuk tidak mengotak-ngatik Python "utama" di sistem anda, dan untuk setiap aplikasi yang akan dikembangkan menggunakan Python akan lebih baik jika menggunakan versi Python berbeda. Pada umumnya, versi Python yang digunakan untuk pengembangan aplikasi bukanlah versi "utama". Ini dilakukan karena setiap aplikasi menggunakan modul yang berbeda-beda dan setiap modul memiliki ketergantugan satu sama lain. Dengan menggunakan versi berbeda, kekhawatiran terjadinya konflik antar modul dapat dihindari. + +Penggunaan versi berbeda dari Python dilakukan dengan menggunakan [*virtual environments*](https://docs.python.org/3/tutorial/venv.html). _Virtual environment_ adalah instalasi Python terpisah yang digunakan untuk keperluan tertentu aplikasi. Di dalam virtual environment, versi Python maupun modul-modul yang terinstal akan terisolasi dari versi Python "utama". Terdapata banyak cara untuk membuat _virtual environment_, tapi di kursus ini kita akan mengikuti arahan khusus dari dokumentasi resmi Python yang dinamai [`venv`](https://docs.python.org/3/library/venv.html#module-venv). + +Pertama, buatlah folder baru untuk menyimpan aplikasi yang akan dibuat. Sebagai contoh, anda mungkin akan membuat folder baru bernama *transformers-course* di root folder dari home directory komputer anda: + +``` +mkdir ~/transformers-course +cd ~/transformers-course +``` + +Setelah masuk ke folder baru tersebut, buatlah _virtual environment_ menggunakan modul `venv` Python: + +``` +python -m venv .env +``` + +Setelah menggunakan modul `venv`, anda akan memiliki folder baru bernama *.env*: + +``` +ls -a +``` + +```out +. .. .env +``` + +Instruksi dibawah adalah instruksi untuk mengaktifkan dan menonaktifkan _virtual environment_ yang baru saja dibuat: + +``` +# Mengaktifkan virtual environment +source .env/bin/activate + +# Menonaktifkan virtual environment +source .env/bin/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! + +``` +which python +``` + +```out +/home//transformers-course/.env/bin/python +``` + +### Instalasi modul + +Sama seperti di Google Colab, anda perlu menginstal modul-modul yang diperlukan. Kali ini, instalasi versi _development_ 🤗 Transformers dapat dilakukan menggunakan _package manager_ `pip`: + +``` +pip install "transformers[sentencepiece]" +``` + +Sekarang anda siap untuk mulai belajar! \ No newline at end of file diff --git a/chapters/id/chapter1/1.mdx b/chapters/id/chapter1/1.mdx new file mode 100644 index 000000000..bdce1e62e --- /dev/null +++ b/chapters/id/chapter1/1.mdx @@ -0,0 +1,61 @@ +# Pendahuluan + + + +## Selamat datang di Kursus 🤗! + + + +Pada kursus ini, anda akan belajar mengenai _natural language processing_ (pemrosesan bahasa natural) atau NLP menggunakan modul-modul dari ekosistem [Hugging Face](https://huggingface.co/) - [🤗 Transformers](https://github.com/huggingface/transformers), [🤗 Datasets](https://github.com/huggingface/datasets), [🤗 Tokenizers](https://github.com/huggingface/tokenizers), and [🤗 Accelerate](https://github.com/huggingface/accelerate) — as well as the [Hugging Face Hub](https://huggingface.co/models). Kursus ini 100% gratis tanpa iklan. + + +## Silabus + +Silabus kursus ini adalah sebagai berikut: + +
+Brief overview of the chapters of the course. + +
+ +- Bab 1-4 akan mencakup pengenalan konsep-konsep dasar modul 🤗 Transformers. Di akhir bab 4, anda akan tahu bagaimana menggunakan model-model _Transformer_ dari [Hugging Face Hub](https://huggingface.co/models), melakukan model _fine-tuning_ untuk dataset anda, dan membagikan model anda di Hugging Face Hub! +- Bab 5-8 akan mencakup dasar-dasar dari 🤗 Datasets dan 🤗 Tokenizers sebelum anda diperkenalkan ke kasus-kasus yang dapat ditangani dengan NLP. Diakhir kursus ini, anda akan mampu menangani dan menyelesaikan kasus-kasus NLP. +- Chapters 9 to 12 go beyond NLP, and explore how Transformer models can be used tackle tasks in speech processing and computer vision. Along the way, you'll learn how to build and share demos of your models, and optimize them for production environments. By the end of this part, you will be ready to apply 🤗 Transformers to (almost) any machine learning problem! +- Setelah NLP, di bab 9-12, anda akan mengeksplorasi bagaimana model-model Transformer dapat digunakan untuk menangani kasus-kasus lain seperti _speech processing_ (pemrosesan ucapan) dan _computer vision_ (penglihatan komputer). Selain itu, anda akan belajar cara membuat dan membagikan demo (prototype) dari model anda, serta cara mengoptimisasi model anda untuk _production environment_ (penerapan di kasus asli). Di akhir bab 12, anda akan siap mengimplementasikan 🤗 Transformers untuk (hampir) semua kasus _machine learning_ (pembelajaran mesin)! + +Syarat mengikuti kursus: + +* Requires a good knowledge of Python +* Pengetahuan mengenai Python +* Akan lebih baik jika sudah mengenal deep learning dengan mengambil kursus dari [fast.ai](https://www.fast.ai/) "[Practical Deep Learning for Coders](https://course.fast.ai/)" atau program-program yang dikembangkan oleh [DeepLearning.AI](https://www.deeplearning.ai/) +* Tidak perlu pengetahuan mengenai [PyTorch](https://pytorch.org/) atau [TensorFlow](https://www.tensorflow.org/). Tapi, akan lebih baik jika sudah terbiasa dengan salah satu framework tersebut. + +Setelah menyelesaikan kursus ini, sangat direkomendasikan untuk mengikuti kursus dari DeepLearning.AI [Natural Language Processing Specialization](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh) yang akan mencakup model-model NLP klasik seperti naive Bayes dan LSTM. Pengetahuan tersebut akan sangat berharga bagi anda! + +## Tentang penulis + +**Abubakar Abid** adalah lulusan PhD dari Stanford dengan konsentrasi aplikasi pembelajaran mesin. Sembari menyelesaikan pendidikan PhD, beliau menciptakan [Gradio](https://github.com/gradio-app/gradio), sebuah modul _open-source_ Python yang sudah digunakan untuk membuat lebih dari 600.000 demo (prototype) model _machine learning_. Gradio telah diakusisi oleh Hugging Face, tempat dimana Abubakar bekerja sebagai _machine learning team lead_. + +**Matthew Carrigan** bekerja sebagai _Machine Learning Engineer_ di Hugging Face. Beliau tinggal di Dublin, Irlandia, pernah bekerja sebagai _ML engineer_ di Parse.ly dan sebelumnya merupakan peneliti post-doctoral di Trinity College Dublin. Beliau tidak percaya kita akan mencapai Artificial general intelligence (AGI) dengan menambahkan skala dari arsitektur yang digunakan sekarang, namun memiliki optimisme mengenai imortalitas robot. + +**Lysandre Debut** bekerja sebagai _Machine Learning Engineer_ di Hugging Face dan berfokus mengembangkan modul 🤗 Transformers sejak seumur jagung. Beliau mempunya mimpi untuk agar NLP dapat diakses oleh semua orang dengan mengembangkan alat-alat atau aplikasi-aplikasi sederhana menggunkan API. + +**Sylvain Gugger** adalah _Research Engineer_ di Hugging Face dan merupakan salah satu _maintainer_ dari modul 🤗 Transformers. Beliau pernah bekerja sebagai _Research Scientist_ di fast.ai, dan bersama Jeremy Howard menulis _[Deep Learning for Coders with fastai and PyTorch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)_. Fokus utama dari penelitian beliau adalah membuat _deep learning_ lebih mudah diakses dengan mendesain dan memperbaiki teknik-teknik untuk melatih model dengan sumber daya terbatas. + +**Dawood Khan** bekerja sebagai _Machine Learning Engineer_ di Hugging Face. Beliau berasal dari NYC dan merupakan lulusan New York University jurusan _Computer Science_. Sempat bekerja sebagai iOS _Engineer_ untuk beberapa tahun, Dawood memutuskan untuk _resign_ dan mengembangkan Gradio bersama rekan-rekan co-foundernya. Seiring berjalannya waktu, Gradio diakusisi oleh Hugging Face. + +**Merve Noyan** adalah advokat _developer_ di Hugging Face, beliau bertugas untuk mengembangkan konten beserta medianya untuk mendemokrasikan _machine learning_ untuk semua orang. + +**Lucile Saulnier** adalah _machine learning engineer_ di Hugging Face, bertugas untuk mengembangkan dan mendukung penggunaan alat-alat _open source_. Beliau juga aktif dalam banyak riset mengenai _Natural Language Processing_ seperti _collaborative training_ dan BigScience. + +**Lewis Tunstall** merupakan _machine learning engineer_ di Hugging Face, bertugas untuk mengembangkan alat-alat _open source_ dan membuatnya dapat diakses oleh komunitas. Beliau juga merupakan salah satu penulis dari buku terbitan O’Reilly berjudul [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). + +**Leandro von Werra** bekerja sebagai _machine learning engineer_ untuk tim _open-source_ di Hugging Face dan juga merupkan salah satu penulis buku [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) yang diterbitkan oleh O'Reilly. Beliau memiliki memiliki pengalaman mengembangkan proyek-proyek NLP untuk kasus nyata pada berbagai macam _machine learning stack_ selama beberapa tahun. + +Sudah siap untuk belajar? Di bab ini anda akan belajar mengenai: +* Penggunaan fungsi `pipeline()` untuk memecahkan masalah-masalah NLP seperti _text generation_ (pembuatan teks) dan klasifikasi. +* Arsitektur Transformer +* Bagaimana membedakan arsitektur encoder, decoder, dan encoder-decoder beserta kasus-kasus terkait. diff --git a/chapters/id/chapter1/2.mdx b/chapters/id/chapter1/2.mdx new file mode 100644 index 000000000..9d73e7447 --- /dev/null +++ b/chapters/id/chapter1/2.mdx @@ -0,0 +1,27 @@ +# Pemrosesan Bahasa Natural (Natural Language Processing) + + + +Sebelum mempelajari mengenai model-model Transformer, mari menyamakan persepsi mengenai _natural language processing_ (NLP) terlebih dahulu. + +## NLP itu apa? + +NLP merupakan cabang ilmu linguistik dan pembelajaran mesin (_machine learning_) yang bertujuan untuk memahami bahasa manusia sehari-hari. Tujuan dari penerapan NLP tidak terbatas pada pemahaman kata per kata saja, tapi juga mengenai konteks yang terkandung dalam setiap ucapan/kata. + +Beberapa penerapan NLP yang umum diterapkan beserta contohnya dapat dilihat dibawah: + +- **Klasifikasi kalimat secara utuh**: Mengetahui sentimen dari sebuah review, mendeteksi email spam, menentukan ketepatan tata bahasa sebuah kalimat atau mencari tahu keterkaitan antar 2 kalimat +- **Classifying each word in a sentence**: Identifying the grammatical components of a sentence (noun, verb, adjective), or the named entities (person, location, organization) +- **Klasifikasi setiap kata dalam sebuah kalimat**: Pengelompokkan unsur kalimat (kata benda, kata kerja, kata sifat), atau pengelompokkan subjek kalimat (orang, lokasi, organisasi) +- **Menciptakan/menambahkan/memperkaya kalimat**: Menyelesaikan kalimat dengan teks yang diciptakan secara otomatis, mengisi titik-titik pada sebuah kuis +- **Menjawab pertanyaan**: Dengan memberi model daftar pertanyaan beserta konteks, menjawab pertanyaan berdasar informasi yang tersedia +- **Menciptakan kalimat baru dari teks**: Menerjemahkan suatu bahasa ke bahasa lain, menyimpulkan kalimat + +Penerapan NLP tidak hanya terbatas pada teks. NLP juga dapat diterapkan untuk menangani kasus pengelan suara (_speech recognition_) dan penglihatan komputer (_computer vision_) seperti menciptakan transkrip dari sampel suara (audio) atau deskripsi gambar. + +## Tantangan-tantangan dalam penerapan NLP + +Komputer tidak dapat memahami informasi secara langsung maupun secara harfiah seperti manusia. Sebagai contoh, ketika membaca kalimat "Saya lapar", manusia dapat dengan mudah memahami maknanya. Begitu juga jika ketika membaca kalimat "Saya lapar" dan "Saya sedih", manusia dapat dengan memudah menentukan apakah kedua kalimat memiliki kemiripan atau tidak. Tapi hal-hal tersebut sulit dipahami oleh model pembelajaran mesin (_machine learning_ (ML)). Kalimat-kalimat tersebut perlu direkayasa (diolah) sedimikian rupa sehingga dapat dipelajari oleh model ML. Ditambah dengan keunikan setiap bahasa/teks, kompleksitas rekayasa yang perlu dilakukan menjadi tantangan tersendiri. Sudah ada banyak riset yang meneliti mengenai bagaiman merekayasa teks untuk penerapan ML dan anda akan mempelajarinya di bab-bab berikutnya. diff --git a/chapters/it/_toctree.yml b/chapters/it/_toctree.yml index c0df904ad..9780143ac 100644 --- a/chapters/it/_toctree.yml +++ b/chapters/it/_toctree.yml @@ -37,6 +37,12 @@ title: Modelli - local: chapter2/4 title: Tokenizers + - local: chapter2/5 + title: Gestione di sequenze multiple + - local: chapter2/6 + title: Mettiamo insieme i pezzi + - local: chapter2/7 + title: Uso di base completato! - title: 3. Affinamento di un modello pre-addestrato sections: diff --git a/chapters/it/chapter2/5.mdx b/chapters/it/chapter2/5.mdx new file mode 100644 index 000000000..833f10a67 --- /dev/null +++ b/chapters/it/chapter2/5.mdx @@ -0,0 +1,339 @@ + + +# Gestione di sequenze multiple + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + +{:else} + +{/if} + +Nella sezione precedente abbiamo esplorato il più semplice dei casi d'uso: fare inferenza su una singola sequenza di lunghezza ridotta. Tuttavia, emergono già alcune domande: + +- Come si gestiscono le sequenze multiple? +- Come gestiamo sequenze multiple *di lunghezza diversa*? +- Gli indici del vocabolario sono gli unici input che permettono a un modello di funzionare bene? +- Esiste una sequenza troppo lunga? + +Vediamo quali tipi di problemi pongono queste domande e come possiamo risolverli utilizzando l'API 🤗 Transformers. + +## I modelli si aspettano un gruppo di input + +Nell'esercizio precedente abbiamo visto come le sequenze vengono tradotte in liste di numeri. Convertiamo questo elenco di numeri in un tensore e inviamolo al modello: + +{#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) +# This line will fail. +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) +# This line will fail. +model(input_ids) +``` + +```py out +InvalidArgumentError: Input to reshape is a tensor with 14 values, but the requested shape has 196 [Op:Reshape] +``` +{/if} + +Oh no! Perché non ha funzionato? + +Il problema è che abbiamo inviato una singola sequenza al modello, mentre i modelli 🤗 Transformers si aspettano frasi multiple per impostazione predefinita. Qui abbiamo cercato di fare tutto ciò che il tokenizer ha fatto dietro le quinte, quando lo abbiamo applicato a una `sequenza`. Ma se si osserva attentamente, si noterà che il tokenizer non si è limitato a convertire l'elenco degli ID in ingresso in un tensore, ma ha aggiunto una dimensione: + +{#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} + +Proviamo di nuovo e aggiungiamo una nuova dimensione: + +{#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} + +Stampiamo gli ID di input e i logit risultanti — ecco l'output: + + +{#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} + +*Il batch* è la procedura di invio di più frasi nel modello, tutte in una volta. Se si ha una sola frase, si può creare un batch con una sola sequenza: + +``` +batched_ids = [ids, ids] +``` + +Si tratta di un batch di due sequenze identiche! + + + +✏️ **Try it out!** Convert this `batched_ids` list into a tensor and pass it through your model. Check that you obtain the same logits as before (but twice)! + + + +Il batching consente al modello di funzionare quando si inseriscono più frasi. Utilizzare più sequenze è altrettanto semplice che creare un batch con una singola sequenza. C'è però un secondo problema. Quando si cerca di raggruppare due (o più) frasi, queste potrebbero essere di lunghezza diversa. Se si è già lavorato con i tensori, si sa che devono essere di forma rettangolare, quindi non è possibile convertire direttamente l'elenco degli ID in ingresso in un tensore. Per ovviare a questo problema, di solito, utilizziamo la tecnica del *padding* sugli input. + +## Aggiungere il padding all'input + +Il seguente elenco di liste non può essere convertito in un tensore: + +```py no-format +batched_ids = [ + [200, 200, 200], + [200, 200] +] +``` + +Per ovviare a questo problema, useremo il *padding* per dare ai nostri tensori una forma rettangolare. Il padding assicura che tutte le frasi abbiano la stessa lunghezza, aggiungendo una parola speciale chiamata *padding token* alle frasi con meno valori. Ad esempio, se si hanno 10 frasi con 10 parole e 1 frase con 20 parole, il padding assicura che tutte le frasi abbiano 20 parole. Nel nostro esempio, il tensore risultante ha il seguente aspetto: + +```py no-format +padding_id = 100 + +batched_ids = [ + [200, 200, 200], + [200, 200, padding_id], +] +``` + +L'ID del token di padding si trova in `tokenizer.pad_token_id`. Utilizziamolo e inviamo le nostre due frasi attraverso il modello singolarmente e insieme: + +{#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} + +C'è qualcosa che non va con i logit nelle nostre previsioni raggruppate: la seconda riga dovrebbe essere uguale ai logit della seconda frase, ma abbiamo valori completamente diversi! + +Questo perché la caratteristica principale dei modelli Transformer sono i livelli di attenzione che *contestualizzano* ogni token. Questi terranno conto dei token del padding, poiché si occupano di tutti i token di una sequenza. Per ottenere lo stesso risultato quando si passano nel modello singole frasi di lunghezza diversa o quando si passa un gruppo con le stesse frasi e l'applicazione di un padding, occorre dire a questi livelli di attenzione di ignorare i token del padding. Questo si ottiene utilizzando una maschera di attenzione. + +## Attention masks + +Le *maschere di attenzione* sono tensori con la stessa forma del tensore degli ID in ingresso, riempiti con 0 e 1: 1 indica che i token corrispondenti devono essere presi in considerazione, mentre 0 indica che i token corrispondenti non devono essere presi in considerazione (cioè, devono essere ignorati dagli strati di attenzione del modello). + +Completiamo l'esempio precedente con una maschera di attenzione: + +{#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} + +Ora otteniamo gli stessi logits per la seconda frase del batch. + +Si noti che l'ultimo valore della seconda sequenza è un ID di riempimento, che è un valore 0 nella maschera di attenzione. + + + +✏️ **Provaci anche tu** Applicate manualmente la tokenizzazione alle due frasi utilizzate nella sezione 2 ("I've been waiting for a HuggingFace course my whole life." e "I hate this so much!"). Passatele attraverso il modello e verificate che si ottengano gli stessi logits della sezione 2. A questo punto, batchateli insieme utilizzando il token di padding e successivamente create la maschera di attenzione appropriata. Verificate di ottenere gli stessi risultati passando attraverso il modello! + + + +## Sequenze più lunghe + +Con i modelli Transformer, c'è un limite alla lunghezza delle sequenze che possiamo passare ai modelli. La maggior parte dei modelli gestisce sequenze fino a 512 o 1024 token e si blocca quando viene chiesto di elaborare sequenze più lunghe. Esistono due soluzioni a questo problema: + +- Utilizzare un modello con una lunghezza di sequenza supportata maggiore. +- Troncare le sequenze. + +I modelli hanno diverse lunghezze di sequenza supportate e alcuni sono specializzati nella gestione di sequenze molto lunghe. [Longformer](https://huggingface.co/transformers/model_doc/longformer.html) è un esempio, un altro è [LED](https://huggingface.co/transformers/model_doc/led.html). Se state lavorando a un'attività che richiede sequenze molto lunghe, vi consigliamo di dare un'occhiata a questi modelli. + +Altrimenti, si consiglia di troncare le sequenze specificando il parametro `max_sequence_length`: + +```py +sequence = sequence[:max_sequence_length] +``` diff --git a/chapters/it/chapter2/6.mdx b/chapters/it/chapter2/6.mdx new file mode 100644 index 000000000..73d75f9b9 --- /dev/null +++ b/chapters/it/chapter2/6.mdx @@ -0,0 +1,164 @@ + + +# Mettiamo insieme i pezzi + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +Nelle ultime sezioni abbiamo fatto del nostro meglio per fare la maggior parte del lavoro a mano. Abbiamo esplorato il funzionamento dei tokenizer e abbiamo esaminato la tokenizzazione, la conversione in ID di input, il padding, il troncamento e le maschere di attenzione. + +Tuttavia, come abbiamo visto nella sezione 2, l'API 🤗 Transformers può gestire tutto questo con una funzione di alto livello che approfondiremo qui. Quando si chiama il `tokenizer` direttamente sulla frase, si ottengono input pronti per passare attraverso il modello: + +```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) +``` + +Qui, la variabile `model_inputs` contiene tutto ciò che è necessario per il buon funzionamento del modello. Per DistilBERT, questo include gli ID degli ingressi e la maschera di attenzione. Altri modelli che accettano input aggiuntivi avranno anche questi output dall'oggetto `tokenizer`. + +Come vedremo in alcuni esempi, questo metodo è molto potente. Innanzitutto, può tokenizzare una singola sequenza: + +```py +sequence = "I've been waiting for a HuggingFace course my whole life." + +model_inputs = tokenizer(sequence) +``` + +Gestisce anche più sequenze alla volta, senza alcuna modifica dell'API: + +```py +sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] + +model_inputs = tokenizer(sequences) +``` + +Possiamo implementare il padding in diversi modi + +```py +# Effettua il padding della sequenza fino allla massima lunghezza della sequenza +model_inputs = tokenizer(sequences, padding="longest") + +# Effettua il padding fino alla lunghezza massima del modello +# (512 per BERT o DistilBERT) +model_inputs = tokenizer(sequences, padding="max_length") + +# Effettua il padding fino alla lunghezza massima specificata +model_inputs = tokenizer(sequences, padding="max_length", max_length=8) +``` + +Può anche troncare le sequenze: + +```py +sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] + +# Tronca le sequenze più lunghe della lunghezza massima del modello. +# (512 per BERT o DistilBERT) +model_inputs = tokenizer(sequences, truncation=True) + +# Tronca le sequenze più lunghe della lunghezza massima specificata. +model_inputs = tokenizer(sequences, max_length=8, truncation=True) +``` + +L'oggetto `tokenizer` può gestire la conversione in tensori di framework specifici, che possono successivamente essere inviati direttamente al modello. Per esempio, nel seguente esempio di codice si chiede al tokenizer di restituire i tensori dei diversi framework: `"pt"` restituisce i tensori di PyTorch, `"tf"` restituisce i tensori di TensorFlow e `"np"` restituisce gli array di NumPy: + +```py +sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] + +# Ritorna tensori PyTorch +model_inputs = tokenizer(sequences, padding=True, return_tensors="pt") + +# Ritorna tensori TensorFlow +model_inputs = tokenizer(sequences, padding=True, return_tensors="tf") + +# Ritorna NumPy arrays +model_inputs = tokenizer(sequences, padding=True, return_tensors="np") +``` + +## Token speciali + +Se diamo un'occhiata agli ID di input restituiti dal tokenizer, noteremo che sono leggermente diversi da quelli che avevamo prima: + +```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] +``` + +Un ID token è stato aggiunto all'inizio e uno alla fine. Decodifichiamo le due sequenze di ID qui sopra per capire di cosa si tratta: + +```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." +``` + +Il tokenizer ha aggiunto la parola speciale `[CLS]` all'inizio e la parola speciale `[SEP]` alla fine. Questo perché il modello è stato preaddestrato con queste parole, quindi per ottenere gli stessi risultati per l'inferenza dobbiamo aggiungerle anche noi. Si noti che alcuni modelli non aggiungono parole speciali, o ne aggiungono di diverse; i modelli possono anche aggiungere queste parole speciali solo all'inizio o solo alla fine. In ogni caso, il tokenizer sa quali sono previste e se ne occuperà per voi. + +## Conclusione: Dal tokenizer al modello + +Ora che abbiamo visto tutti i singoli passaggi che l'oggetto `tokenizer` utilizza quando viene applicato ai testi, vediamo un'ultima volta come può gestire sequenze multiple (padding!), sequenze molto lunghe (troncamento!) e diversi tipi di tensori con la sua API principale: + +{#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/it/chapter2/7.mdx b/chapters/it/chapter2/7.mdx new file mode 100644 index 000000000..29d2b7d7a --- /dev/null +++ b/chapters/it/chapter2/7.mdx @@ -0,0 +1,18 @@ +# Uso di base completato! + + + +Ottimo lavoro per aver seguito il corso fino a questo punto! Per fare un riassunto, in questo capitolo abbiamo visto: + +- Imparare gli elementi di base di un modello Transformer. +- Imparare a conoscere gli elementi che compongono una pipeline di tokenizzazione. +- Hai visto come utilizzare un modello Transformer nella pratica. +- Imparare a sfruttare un tokenizer per convertire il testo in tensori comprensibili dal modello. +- Impostare un tokenizer e un modello insieme per passare dal testo alle previsioni. +- Imparare i limiti degli ID di input e conoscere le maschere di attenzione. +- Abbiamo giocato con metodi di tokenizzazione versatili e configurabili. + +D'ora in poi, dovreste essere in grado di navigare liberamente nella documentazione di Transformers 🤗: il vocabolario vi suonerà familiare e avrete già visto i metodi che userete la maggior parte delle volte. diff --git a/chapters/ru/chapter3/4.mdx b/chapters/ru/chapter3/4.mdx index 388040777..84a9bc7d8 100644 --- a/chapters/ru/chapter3/4.mdx +++ b/chapters/ru/chapter3/4.mdx @@ -131,7 +131,7 @@ print(num_training_steps) 1377 ``` -### Обучающий цикла +### Обучающий цикл Последний момент: мы хотим использовать GPU в случае, если у нас будет такая возможность (на CPU процесс может занять несколько часов вместо пары минут). Чтобы добиться этого, мы определим переменную `device` и «прикрепим» к видеокарте нашу модель и данные: @@ -344,8 +344,6 @@ accelerate config эта строка предложит вам ответить на несколько вопросов и сохранит ваши ответы в конфигурационный файл, который будет использоваться при вызове команды: -which will prompt you to answer a few questions and dump your answers in a configuration file used by this command: - ``` accelerate launch train.py ``` diff --git a/utils/generate_notebooks.py b/utils/generate_notebooks.py index 781fb19b3..d7f235243 100644 --- a/utils/generate_notebooks.py +++ b/utils/generate_notebooks.py @@ -21,6 +21,9 @@ frameworks = {"pt": "PyTorch", "tf": "TensorFlow"} PATH_TO_COURSE = Path("chapters/") +# Languages to exlude from the notebook generation because the notebooks were +# created manually +LANGS_TO_EXCLUDE = ["fr"] def read_and_split_frameworks(fname): @@ -277,6 +280,8 @@ def create_notebooks(language, output_dir): languages = [f.stem for f in PATH_TO_COURSE.iterdir() if f.is_dir()] for language in languages: + if language in LANGS_TO_EXCLUDE: + continue language_output_dir = f"{args.output_dir}/{language}" create_notebooks(language, language_output_dir) # Remove empty notebook folders From 79999ffd73bc77a79b3c59f2e1988836fd784b06 Mon Sep 17 00:00:00 2001 From: lewtun Date: Wed, 2 Nov 2022 16:53:22 +0100 Subject: [PATCH 38/51] Bump release (#358) --- chapters/de/_toctree.yml | 24 +++ chapters/de/chapter1/1.mdx | 102 +++++++++++ chapters/de/chapter1/10.mdx | 260 ++++++++++++++++++++++++++++ chapters/de/chapter1/2.mdx | 26 +++ chapters/de/chapter1/3.mdx | 329 ++++++++++++++++++++++++++++++++++++ chapters/de/chapter1/4.mdx | 176 +++++++++++++++++++ chapters/de/chapter1/5.mdx | 22 +++ chapters/de/chapter1/6.mdx | 21 +++ chapters/de/chapter1/7.mdx | 21 +++ chapters/de/chapter1/8.mdx | 32 ++++ chapters/de/chapter1/9.mdx | 16 ++ chapters/de/glossary/1.mdx | 170 ++++++++++++------- chapters/en/events/1.mdx | 4 +- chapters/fr/events/1.mdx | 2 +- 14 files changed, 1139 insertions(+), 66 deletions(-) create mode 100644 chapters/de/chapter1/1.mdx create mode 100644 chapters/de/chapter1/10.mdx create mode 100644 chapters/de/chapter1/2.mdx create mode 100644 chapters/de/chapter1/3.mdx create mode 100644 chapters/de/chapter1/4.mdx create mode 100644 chapters/de/chapter1/5.mdx create mode 100644 chapters/de/chapter1/6.mdx create mode 100644 chapters/de/chapter1/7.mdx create mode 100644 chapters/de/chapter1/8.mdx create mode 100644 chapters/de/chapter1/9.mdx diff --git a/chapters/de/_toctree.yml b/chapters/de/_toctree.yml index 7227717ef..c43192ca8 100644 --- a/chapters/de/_toctree.yml +++ b/chapters/de/_toctree.yml @@ -3,6 +3,30 @@ - local: chapter0/1 title: Einführung +- title: 1. Transformer-Modelle + sections: + - local: chapter1/1 + title: Einführung + - local: chapter1/2 + title: Natural Language Processing + - local: chapter1/3 + title: Transformer-Modelle - wozu sind sie imstande? + - local: chapter1/4 + title: Wie funktionieren Transformer-Modelle? + - local: chapter1/5 + title: Encoder-Modelle + - local: chapter1/6 + title: Decoder-Modelle + - local: chapter1/7 + title: Sequence-to-Sequence-Modelle + - local: chapter1/8 + title: Bias und Einschränkungen + - local: chapter1/9 + title: Zusammenfassung + - local: chapter1/10 + title: Quiz am Ende des Kapitels + quiz: 1 + - title: 3. Fine-tuning von vortrainierten Modellen sections: - local: chapter3/1 diff --git a/chapters/de/chapter1/1.mdx b/chapters/de/chapter1/1.mdx new file mode 100644 index 000000000..14e19601c --- /dev/null +++ b/chapters/de/chapter1/1.mdx @@ -0,0 +1,102 @@ +# Einführung + + + +## Willkommen zum 🤗 Kurs! + + + +In diesem Kurs lernst du verschiedene Teilbereiche der maschinellen Verarbeitung natürlicher Sprache (engl. Natural Language Processing, NLP) - im Deutschen auch als Maschinelle Sprachverarbeitung oder Computerlinguistik (CL) bezeichnet - unter Verwendung der Bibliotheken des Ökosystems von [Hugging Face](https://huggingface.co/) kennen: die [🤗 Transformers-](https://github.com/huggingface/transformers), die [🤗 Datasets-](https://github.com/huggingface/datasets), die [🤗 Tokenizers-](https://github.com/huggingface/tokenizers) sowie die [🤗 Accelerate-Bibliotheken](https://github.com/huggingface/accelerate) als auch der [Hugging Face Hub](https://huggingface.co/models). Der Kurs ist komplett kostenlos und frei von Werbung. + + +## Was erwartet dich? + +Hier ein kurzer Überblick über den Kurs: + +
+Brief overview of the chapters of the course. + +
+ +- Die Kapitel 1 bis 4 geben eine Einführung in die wichtigsten Konzepte der 🤗 Transformers-Bibliothek. Am Ende dieses Teils des Kurses wirst du mit der Funktionsweise von Transformer-Modellen vertraut sein und wissen, wie du ein Modell aus dem [Hugging Face Hub](https://huggingface.co/models) verwendest, es auf einem Datensatz feintunst und deine Ergebnisse mit anderen auf dem Hub teilst! +- In den Kapiteln 5 bis 8 lernst du die Grundlagen der 🤗 Datasets- und 🤗 Tokenizers-Bibliotheken kennen, bevor du in die typischen Problemstellungen des NLP eintauchst. Am Ende dieses Teils wirst du in der Lage sein, die gängisten Problemstellungen im NLP selbstständig zu lösen. +- Die Kapitel 9 bis 12 gehen über den Bereich des NLP hinaus und zeigen, wie Transformer-Modelle für Aufgaben bei der Verarbeitung gesprochener Sprache (engl. Speech Processing) und im Bereich Computer Vision (im Deutschen ungefähr mit computerbasiertem Sehen zu übersetzen) eingesetzt werden können. Nebenbei lernst du, wie du eigene Versionen deiner Modelle zu Demonstrationszwecken erstellen und sie mit anderen teilen kannst, und wie du sie für Produktionsumgebungen optimierst. Am Ende dieses Teils wirst du in der Lage sein, die 🤗 Transformers-Bibliothek auf (fast) jede Problemstellung, die dir im Bereich des Maschinellen Lernens begegnen, anzuwenden! + +Dieser Kurs: + +* Erfordert gute Kenntnisse in Python +* Sollte am besten nach einem Einführungskurs in Deep Learning gemacht werden, wie z. B. [fast.ai's Kurs](https://www.fast.ai/) [Practical Deep Learning for Coders](https://course.fast.ai/) oder eines der von [DeepLearning.AI](https://www.deeplearning.ai/) entwickelten Kursprogramme +* Setzt keine Vorkenntnisse in [PyTorch](https://pytorch.org/) oder [TensorFlow](https://www.tensorflow.org/) voraus, obwohl es hilfreich ist, wenn du bereits mit ihnen vertraut sein solltest. + +Nachdem du diesen Kurs abgeschlossen hast, empfehlen wir dir den [Spezialisierungskurs Natural Language Processing von DeepLearning.AI](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh), der eine breite Palette traditioneller NLP-Modelle wie Naive Bayes und LSTMs abdeckt, bei denen es sich lohnt, sich mit ihnen vertraut zu machen! + +## Wer sind wir? + +Über die Autorinnen und Autoren: + +**Matthew Carrigan** ist Machine Learning Engineer bei Hugging Face. Er lebt in der irischen Hauptstadt Dublin und hat zuvor als Machine Learning Engineer bei Parse.ly und als Post-Doktorand am Trinity College Dublin gearbeitet. Er glaubt nicht, dass wir eine künstliche allgemeine Intelligenz (engl. Artificial General Intelligence, AGI) durch eine zunehmende Skalierung bestehender Architekturen erreichen werden, hat aber dennoch die Hoffnung, dass Roboter auf dem Weg zur Unsterblichkeit sind. + +**Lysandre Debut** ist Machine Learning Engineer bei Hugging Face und arbeitet bereits seit Entstehung an der 🤗 Transformers-Bibliothek mit. Sein Ziel ist es, NLP für alle zugänglich zu machen, indem er Tools entwickelt, die eine sehr einfache API bieten. + +**Sylvain Gugger** ist Research Engineer bei Hugging Face und einer der Hauptverantwortlichen für die Pflege der 🤗 Transformers-Bibliothek. Zuvor war er Research Scientist bei fast.ai und hat zusammen mit Jeremy Howard das Buch _[Deep Learning for Coders with fastai and PyTorch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)_ verfasst. Seine Forschung ist darauf ausgerichtet, Deep Learning zugänglicher zu machen. Hierfür entwickelt und verbessert er Techniken, mit denen Modelle auch bei begrenzter Ressourcenausstattung auf schnelle Weise trainiert werden können. + +**Merve Noyan** ist Developer Advocate bei Hugging Face und arbeitet daran, Tools zu entwickeln und Inhalte zu erstellen, die Maschinelles Lernen für jeden zugänglich machen. + +**Lucile Saulnier** ist Machine Learning Engineer bei Hugging Face und entwickelt und unterstützt die Nutzung von Open-Source-Tools. Außerdem ist sie aktiv an vielen Forschungsprojekten im Bereich des NLP beteiligt, z. B. an kollaborativem Training und BigScience. + +**Lewis Tunstall** ist Machine Learning Engineer bei Hugging Face, und konzentriert sich darauf, Open-Source-Tools zu entwickeln und sie der breiten Community zugänglich zu machen. Zudem ist er Mitverfasser des O'Reilly-Buches [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). + +**Leandro von Werra** ist Machine Learning Engineer im Open-Source-Team von Hugging Face und ebenfalls einer der Autoren des O'Reilly-Buches [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). Er hat mehrere Jahre praktische Erfahrung darin gesammelt, NLP-Projekte in die Produktion zu bringen, und dabei den gesamten ML-Stack beackert. + +## Häufig gestellte Fragen (FAQ) + +Hier findest du einige Antworten auf häufig gestellte Fragen: + +- **Erhalte ich für die Teilnahme an diesem Kurs ein Zertifikat? +Derzeit gibt es für diesen Kurs noch kein Zertifikat. Wir arbeiten jedoch an einem Programm zur Erlangung eines Zertifikats für das Hugging-Face-Ökosystem - bleib' auf dem Laufenden! + +- **Wie viel Zeit sollte ich für diesen Kurs einplanen? +Jedes Kapitel dieses Kurses ist so konzipiert, dass es innerhalb einer Woche abgeschlossen werden kann, wenn du circa 6 bis 8 Stunden Arbeit einplanst. Du kannst dir jedoch so viel Zeit nehmen wie nötig. + +- **Wo kann ich Fragen stellen, wenn ich welche habe?** +Wenn du eine Frage zu einem Kursabschnitt hast, klicke einfach auf das sich oben auf der Seite befindende Banner "*Ask a question*" und du wirst automatisch zum entsprechenden Bereich des [Hugging-Face-Forums](https://discuss.huggingface.co/) weitergeleitet: + +Link to the Hugging Face forums + +Wenn du nach dem Kurs noch weiter üben möchtest, steht dir in den Foren eine Liste mit [Projektideen](https://discuss.huggingface.co/c/course/course-event/25) zur Verfügung. + +- **Wo finde ich den Code für den Kurs?** +In jedem Abschnitt kannst du auf das oben auf der Seite befindliche Banner klicken, um den Code entweder in Google Colab oder in Amazon SageMaker Studio Lab auszuführen: + +Link to the Hugging Face course notebooks + +Die Jupyter-Notebooks, die den gesamten Code des Kurses enthalten, befinden sich im [`huggingface/notebooks`-Repo](https://github.com/huggingface/notebooks). Wenn du sie lokal aufsetzen möchtest, schau dir die Anweisungen im [`course`-Repository](https://github.com/huggingface/course#-jupyter-notebooks) auf GitHub an. + + +- **Wie kann ich etwas zum Kurs beitragen?** +Es gibt mehrere Möglichkeiten, zum Kurs beizutragen! Wenn du einen Tippfehler oder einen Fehler entdeckst, eröffne bitte ein Issue in dem [`course`-Repository](https://github.com/huggingface/course). Wenn du uns dabei unterstützen möchtest, den Kurs in deine Muttersprache zu übersetzen, sieh dir bitte die [Anleitung](https://github.com/huggingface/course#translating-the-course-into-your-language) an. + +- **Welche Entscheidungen wurden bei den einzelnen Übersetzungen getroffen?** +Für jede Übersetzung gibt es ein Glossar und die Datei `TRANSLATING.txt`, in der die gewählten Fachtermini usw. festgehalten sind. Ein Beispiel für die deutsche Fassung findest du [hier](https://github.com/huggingface/course/blob/main/chapters/de/TRANSLATING.txt). + + +- **Kann ich diesen Kurs auch an anderer Stelle verwenden?** +Ja, natürlich! Der Kurs ist unter der permissiven [Apache-2-Lizenz](https://www.apache.org/licenses/LICENSE-2.0.html) veröffentlicht. Das bedeutet, dass du den Kurs in angemessener Weise erwähnen, einen Verweis zur Lizenz angeben und darauf hinweisen musst, wenn du Änderungen vorgenommen hast. Du kannst dies in jeder angemessenen Weise tun, allerdings nicht in einer Weise, die den Eindruck erweckt, dass der Lizenzgeber dich oder deine Nutzung unterstützt. Wenn du den Kurs zitieren möchtest, verwende bitte den folgenden BibTeX-Eintrag: + +``` +@misc{huggingfacecourse, + author = {Hugging Face}, + title = {The Hugging Face Course, 2022}, + howpublished = "\url{https://huggingface.co/course}", + year = {2022}, + note = "[Online; accessed ]" +} +``` + +Bist du bereit, loszulegen? In diesem Kapitel lernst du +* wie man die Funktion `pipeline()` benutzt, um computerlinguistische Aufgaben wie Textgenerierung und Klassifizierung zu lösen, +* mehr über die Transformer-Architektur und +* wie zwischen Encoder-, Decoder- und Encoder-Decoder-basierten Architekturen und -Anwendungsfällen unterschieden werden kann. diff --git a/chapters/de/chapter1/10.mdx b/chapters/de/chapter1/10.mdx new file mode 100644 index 000000000..0d6054011 --- /dev/null +++ b/chapters/de/chapter1/10.mdx @@ -0,0 +1,260 @@ + + + + + +# Quiz am Ende des Kapitels + +In diesem Kapitel hast du viel gelernt! Mach dir keine Sorgen, wenn du noch nicht alle Einzelheiten verstanden hast. In den nächsten Kapiteln wirst du mehr darüber erfahren, wie die Dinge im Einzelnen funktionieren. + +Doch zuerst wollen wir noch testen, was du in diesem Kapitel gelernt hast! + + +### 1. Erkunde den Hub und suche nach dem Checkpoint `roberta-large-mnli`. Welche Aufgabe unterstützt er? + + +roberta-large-mnli nach." + }, + { + text: "Text Classification (Textklassifizierung)", + explain: "Genauer gesagt, wird klassifiziert, ob zwei Sätze hinsichtlich dreier Labels (Widerspruch (engl. Contradiction), Neutral, Konsequenz (engl. Entailment)) logisch miteinander verbunden sind - eine Aufgabe, die auch als Natural Language Inference bezeichnet wird.", + correct: true + }, + { + text: "Text Generation (Textgenerierung)", + explain: "Sieh nochmal auf der Seite des Modells roberta-large-mnli nach." + } + ]} +/> + +### 2. Was gibt der folgende Code zurück? + +```py +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +sentiment-analysis-Pipeline verwenden." + }, + { + text: "Er wird einen generierten Text zurückgeben, der diesen Satz vervollständigt.", + explain: "Das ist nicht richtig - dafür müsstest du eine text-generation-Pipeline verwenden.", + }, + { + text: "Er gibt Begriffe zurück, die für Personen, Organisationen oder Orte stehen.", + explain: "Außerdem werden mit grouped_entities=True die Wörter, die zur selben Entität gehören, gruppiert, wie z. B. \"Hugging Face\".", + correct: true + } + ]} +/> + +### 3. Wodurch müsste ... in diesem Codebeispiel ersetzt werden? + +```py +from transformers import pipeline + +filler = pipeline("fill-mask", model="bert-base-cased") +result = filler("...") +``` + + has been waiting for you.", + explain: "Das stimmt nicht. Schau dir die bert-base-cased-Übersichtsseite des Modells an und versuche, deinen Fehler zu entdecken." + }, + { + text: "This [MASK] has been waiting for you.", + explain: "Richtig! Der Mask Token dieses Modells ist [MASK].", + correct: true + }, + { + text: "This man has been waiting for you.", + explain: "Leider falsch. Diese Pipeline füllt maskierte Wörter auf, also braucht sie irgendwo einen Mask Token." + } + ]} +/> + +### 4. Warum wird dieser Code nicht funktionieren? + +```py +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +result = classifier("This is a course about the Transformers library") +``` + +candidate_labels=[...] enthalten.", + correct: true + }, + { + text: "Diese Pipeline erfordert mehrere Sätze, nicht nur einen.", + explain: "Das ist falsch - obwohl diese Pipeline, wenn sie korrekt verwendet wird, eine Liste von Sätzen verarbeiten kann (wie alle anderen Pipelines)." + }, + { + text: "Die 🤗 Transformers-Bibliothek funktioniert wie immer nicht.", + explain: "Zu dieser Antwort erübrigt sich jeder Kommentar!" + }, + { + text: "Diese Pipeline erfordert längere Inputs; diese hier sind zu kurz.", + explain: "Das ist falsch. Übrigens wird ein sehr langer Text bei der Verarbeitung durch diese Pipeline gestutzt (engl. truncated) bzw. gekürzt." + } + ]} +/> + +### 5. Was bedeutet der Begriff "Transfer Learning"? + + + +### 6. Richtig oder falsch? Ein Sprachmodell benötigt im Rahmen des Pretraining in der Regel keine Labels. + + +selbstüberwacht (engl. self-supervised), d. h. die Labels werden automatisch aus den Inputs erstellt (wie z. B. die Vorhersage des nächsten Wortes oder das Auffüllen einiger maskierter Wörter).", + correct: true + }, + { + text: "Falsch", + explain: "Das ist nicht die richtige Antwort." + } + ]} +/> + +### 7. Wähle den Satz aus, der die Begriffe "Modell", "Architektur" und "Gewichte" bzw. "Gewichtung" am besten beschreibt. + + + + +### 8. Welche dieser Modelle würdest du nutzen, um einen Prompt bzw. Text-Input durch einen generierten Text vervollständigen zu lassen? + + + +### 9. Welche dieser Modelle würdest du für die Zusammenfassung von Texten verwenden? + + + +### 10. Welche Art von Modellen würdest du verwenden, um Text-Inputs entsprechend bestimmter Labels zu klassifizieren? + + + +### 11. Welche mögliche Ursache kann eine vom Modell zu beobachtende Voreingenommenheit (Bias) haben? + + diff --git a/chapters/de/chapter1/2.mdx b/chapters/de/chapter1/2.mdx new file mode 100644 index 000000000..9f36f8315 --- /dev/null +++ b/chapters/de/chapter1/2.mdx @@ -0,0 +1,26 @@ +# Computerlinguistik + + + +Bevor wir uns mit Transformer-Modellen beschäftigen, wollen wir dir einen kurzen Überblick darüber geben, was Computerlinguistik (engl. Natural Language Processing, NLP) ist und welche Gründe es gibt, sich damit zu befassen. + +## Was ist Computerlinguistik (CL)? + +CL ist ein Bereich der Linguistik und des Maschinellen Lernens (engl. Machine Learning, ML), der sich darauf konzentriert, alle mit menschlicher Sprache zusammenhängenden Dinge zu verstehen. Das Ziel bei CL-Aufgabenstellungen (engl. Tasks) ist es, nicht nur einzelne Wörter zu verstehen, sondern auch den Kontext dieser Wörter zu erfassen. + +Im Folgenden findest du eine Liste der häufigsten CL-Aufgabenstellungen mit jeweils einigen Beispielen: + +- **Ganze Sätze klassifizieren**: Die mit einer bestimmten Bewertung verbundene Stimmungslage ermitteln, erkennen, ob eine E-Mail Spam ist, bestimmen, ob ein Satz grammatikalisch korrekt ist oder ob zwei Sätze logisch zusammenhängen oder nicht +- **Jedes einzelne Wort in einem Satz klassifizieren**: Identifizieren der grammatikalischen Bestandteile eines Satzes (Substantiv, Verb, Adjektiv) oder der benannten Entitäten (Person, Ort, Organisation) (engl. Named Entities) +- **Generieren von Textinhalten**: Einen Prompt durch einen automatisch generierten Text vervollständigen oder Lücken in einem Text auffüllen, in dem einzelne Wörter maskiert sind +- **Eine Antwort aus einem Text extrahieren**: Auf Basis einer Frage und eines gegebenen Kontexts die Antwort auf die Frage anhand der im Kontext enthaltenen Informationen extrahieren +- **Generieren eines neuen Satzes auf Basis eines Input-Textes**: Einen Text in eine andere Sprache automatisch übersetzen, Zusammenfassen eines Textes + +Die Computerlinguistik ist jedoch nicht nur auf die Verarbeitung geschriebener Texte beschränkt. Sie stellt sich auch komplexen Herausforderungen in den Bereichen der Spracherkennung (engl. Speech Recognition) und Computer Vision, wie z. B. ein Transkript einer Audioaufnahme zu erstellen oder ein Bild zu beschreiben. + +## Warum ist Computerlinguistik so schwierig? + +Computer verarbeiten Informationen nicht auf die gleiche Weise wie Menschen. Wenn wir zum Beispiel den Satz "Ich bin hungrig" lesen, können wir seine Bedeutung leicht erfassen. Genauso können wir bei zwei Sätzen wie "Ich habe Hunger" und "Ich bin traurig" leicht feststellen, wie ähnlich sie sind. Für ML-Modelle sind solche Aufgaben schwieriger zu lösen. Der Text muss erst so verarbeitet werden, dass das Modell in der Lage ist, daraus zu lernen. Und weil Sprache komplex ist, müssen wir uns genau überlegen, wie diese Verarbeitung erfolgen sollte. Es wurde eine rege Forschung dazu betrieben, wie Texte repräsentiert werden können. Einige dieser Methoden werden wir uns im nächsten Kapitel ansehen. diff --git a/chapters/de/chapter1/3.mdx b/chapters/de/chapter1/3.mdx new file mode 100644 index 000000000..c58ba5d64 --- /dev/null +++ b/chapters/de/chapter1/3.mdx @@ -0,0 +1,329 @@ +# Transformer-Modelle - wozu sind sie imstande? + + + +In diesem Abschnitt schauen wir uns an, was Transformer-Modelle zu leisten imstande sind. Zudem verwenden wir unser erstes Werkzeug aus der 🤗 Transformers-Bibliothek: die Funktion `pipeline()`. + + +👀 Siehst du rechts oben die Schaltfläche Open in Colab? Klicke darauf, um ein Google Colab Notebook, das alle Codebeispiele dieses Abschnitts enthält, zu öffnen. Diese Schaltfläche ist in jedem Abschnitt, der Codebeispiele enthält, zu finden. + +Wenn du die Beispiele lieber lokal ausführen möchtest, empfehlen wir dir, einen Blick auf das Kapitel Einrichtung zu werfen. + + +## Transformer-Modelle sind überall anzutreffen! + +Transformer-Modelle werden verwendet, um alle Arten von CL-Aufgaben (engl. Tasks) zu lösen, u. a. die im vorherigen Abschnitt genannten. Hier sind einige der Unternehmen und Organisationen, die Hugging-Face- und Transformer-Modelle verwenden und ihre Modelle mit der Community teilen: + +Companies using Hugging Face + +Die [🤗 Transformers-Bibliothek](https://github.com/huggingface/transformers) bietet die Funktionalität, um diese geteilten Modelle zu erstellen und zu nutzen. Der [Model Hub](https://huggingface.co/models) enthält Tausende von vortrainierten Modellen, die jeder herunterladen und nutzen kann. Auch du kannst dort deine eigenen Modelle hochladen! + + +⚠️ Der Hugging Face Hub ist nicht auf Transformer-Modelle beschränkt. Jede bzw. jeder kann die von ihr bzw. ihm gewünschten Arten von Modellen oder Datensätzen teilen! Erstelle ein Konto auf huggingface.co, um alle verfügbaren Features nutzen zu können! + + +Bevor wir uns ansehen, wie Transformer-Modelle im Einzelnen funktionieren, widmen wir uns ein paar Beispielen, die veranschaulichen, wie sie zur Lösung interessanter CL-Problemstellungen eingesetzt werden können. + +## Mit Pipelines arbeiten + + + +Das grundlegendste Objekt in der 🤗 Transformers-Bibliothek ist die `pipeline()`-Funktion. Sie verbindet ein Modell mit den notwendigen Vor- und Nachverarbeitungsschritten (engl. Preprocessing/Postprocessing) und ermöglicht es uns, direkt einen beliebigen Text eingeben zu können und eine Antwort zu erhalten, die verständlich ist: + +```python +from transformers import pipeline + +classifier = pipeline("sentiment-analysis") +classifier("I've been waiting for a HuggingFace course my whole life.") +``` + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}] +``` + +Wir können sogar mehrere Sätze auf einmal übergeben! + +```python +classifier( + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!"] +) +``` + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}, + {'label': 'NEGATIVE', 'score': 0.9994558095932007}] +``` + +In der Voreinstellung wählt diese Pipeline ein bestimmtes vortrainiertes Modell aus, das bereits für die Sentiment-Analyse in englischer Sprache feingetunt wurde. Wenn du das `classifier`-Objekt erstellst, wird das Modell heruntergeladen und zwischengespeichert. Wenn du den Befehl erneut ausführst, wird stattdessen das zwischengespeicherte Modell verwendet und das Modell muss nicht erneut heruntergeladen werden. + +Wenn du einen Text an eine Pipeline übergibst, gibt es drei wichtige Schritte: + +1. Der Text wird im Rahmen der Vorverarbeitung in ein Format überführt, das das Modell verstehen kann. +2. Die vorverarbeiteten Inputs bzw. Eingaben werden an das Modell übergeben. +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: + +- `feature-extraction` (Vektordarstellung eines Textes erhalten) +- `fill-mask` +- `ner` (Named Entity Recognition) +- `question-answering` +- `sentiment-analysis` +- `summarization` +- `text-generation` +- `translation` +- `zero-shot-classification` + +Werfen wir doch gleich mal einen Blick auf ein paar von ihnen! + +## Zero-Shot-Klassifizierung + +Beginnen wir mit der recht anspruchsvollen Aufgabe, Texte zu klassifizieren, die noch nicht gelabelt wurden. Dieses Problem tritt häufig in realen Projekten auf, da das Labeln von Texten in der Regel zeitaufwendig ist und Fachwissen erfordert. Für diesen Anwendungsfall ist die Pipeline `zero-shot-classification` sehr vielversprechend: Mit ihr kannst du festlegen, welche Labels für die Klassifizierung verwendet werden sollen, und musst nicht auf die Labels des vortrainierten Modells zurückgreifen. Wie du bereits gesehen hast, kann das Modell einen Satz - entsprechend der beiden Labels - als positiv oder negativ klassifizieren. Es kann den Text aber auch auf der Grundlage einer beliebigen anderen Auswahl an Labels klassifizieren. + +```python +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +classifier( + "This is a course about the Transformers library", + candidate_labels=["education", "politics", "business"], +) +``` + +```python out +{'sequence': 'This is a course about the Transformers library', + 'labels': ['education', 'business', 'politics'], + 'scores': [0.8445963859558105, 0.111976258456707, 0.043427448719739914]} +``` + +Diese Pipeline heißt _zero-shot_, weil du das Modell nicht erst auf deine Daten feintunen musst, ehe du es verwenden kannst. Sie kann direkt die Wahrscheinlichkeiten für jede beliebige von dir vorgegebene Liste von Labels liefern! + + + +✏️ **Probiere es aus!** Spiel mit deinen eigenen Sequenzen und Labels herum und beobachte, wie sich das Modell verhält. + + + + +## Textgenerierung + +Sehen wir uns nun an, wie du eine Pipeline verwenden kannst, wenn du einen Text generieren möchtest. Der Grundgedanke dabei ist, dass du einen bestimmten Input (einen sog. Prompt) vorgibst und das Modell diesen automatisch vervollständigt, indem es den restlichen Text generiert. Das ist ähnlich wie die Textvorhersagefunktion, die auf vielen Handys zu finden ist. Die Textgenerierung erfolgt nach dem Zufallsprinzip - daher ist es normal, wenn du nicht die gleichen Ergebnisse wie die unten gezeigten erhältst. + +```python +from transformers import pipeline + +generator = pipeline("text-generation") +generator("In this course, we will teach you how to") +``` + +```python out +[{'generated_text': 'In this course, we will teach you how to understand and use ' + 'data flow and data interchange when handling user data. We ' + 'will be working with one or more of the most commonly used ' + 'data flows — data flows of various types, as seen by the ' + 'HTTP'}] +``` + +Mit dem Argument `num_return_sequences` kannst du steuern, wie viele verschiedene Sequenzen erzeugt werden und mit dem Argument `max_length`, wie lang der Ausgabetext insgesamt sein soll. + + + +✏️ **Probiere es aus!** Wähle die Argumente `num_return_sequences` und `max_length` so, dass zwei Sätze mit jeweils 15 Wörtern erzeugt werden. + + + + +## Verwendung eines beliebigen Modells vom Hub in einer Pipeline + +In den vorherigen Beispielen wurde für die jeweilige Aufgabe das voreingestellte Standardmodell verwendet. Du kannst aber auch ein bestimmtes Modell aus dem Hub auswählen und es in einer Pipeline für eine konkrete Aufgabe verwenden - zum Beispiel für die Textgenerierung. Gehe zum [Model Hub](https://huggingface.co/models) und klicke auf der linken Seite unter `Tasks` auf das entsprechende Tag, um dir lediglich die für diese Aufgabenstellung unterstützten Modelle anzeigen zu lassen. Du solltest anschließend auf eine Seite wie [diese](https://huggingface.co/models?pipeline_tag=text-generation) gelangen. + +Probieren wir nun das Modell [`distilgpt2`](https://huggingface.co/distilgpt2) aus! So kannst du es mit der gleichen Pipeline wie zuvor laden: + +```python +from transformers import pipeline + +generator = pipeline("text-generation", model="distilgpt2") +generator( + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, +) +``` + +```python out +[{'generated_text': 'In this course, we will teach you how to manipulate the world and ' + 'move your mental and physical capabilities to your advantage.'}, + {'generated_text': 'In this course, we will teach you how to become an expert and ' + 'practice realtime, and with a hands on experience on both real ' + 'time and real'}] +``` + +Du kannst deine Suche nach einem Modell verfeinern, indem du auf eines der `Languages`-Tags klickst und ein Modell auswählst, das Text in einer anderen Sprache generiert. Der Model Hub enthält sogar Checkpoints für mehrsprachige Modelle, die mehrere verschiedene Sprachen unterstützen. + +Nachdem du auf ein Modell geklickt und es ausgewählt hast, siehst du, dass es ein Widget gibt, mit dem du es direkt online ausprobieren kannst. Dementsprechend kannst du die Fähigkeiten eines Modells erst schnell testen, bevor du dich dazu entschließt, es herunterzuladen. + + + +✏️ **Probiere es aus!** Verwende die Filter, um ein Textgenerierungsmodell für eine andere Sprache zu finden. Experimentiere ruhig ein wenig mit dem Widget und verwende das Modell in einer Pipeline! + + + +### Die Inference API + +Alle Modelle können direkt über deinen Browser getestet werden, indem du die Inference API verwendest, die auf der [Webseite von Hugging Face](https://huggingface.co/) verfügbar ist. Auf dieser Seite kannst du direkt mit dem Modell experimentieren, indem du einen eigenen Text eingibst und beobachtest, wie das Modell die Input-Daten verarbeitet. + +Die Inference API, die dem Widget zugrunde liegt, ist auch als kostenpflichtiges Produkt erhältlich, was recht praktisch ist, wenn du sie für deine Workflows benötigst. Weitere Informationen findest du auf der [Preisseite](https://huggingface.co/pricing). + +## Mask Filling + +Die nächste Pipeline, die du ausprobieren wirst, ist `fill-mask`. Bei dieser Aufgabe geht es darum, Lücken in einem vorgegebenen Text zu füllen: + +```python +from transformers import pipeline + +unmasker = pipeline("fill-mask") +unmasker("This course will teach you all about models.", top_k=2) +``` + +```python out +[{'sequence': 'This course will teach you all about mathematical models.', + 'score': 0.19619831442832947, + 'token': 30412, + 'token_str': ' mathematical'}, + {'sequence': 'This course will teach you all about computational models.', + 'score': 0.04052725434303284, + 'token': 38163, + 'token_str': ' computational'}] +``` + +Mit dem Argument `top_k` kannst du bestimmen, wie viele Möglichkeiten dir ausgegeben werden sollen. Beachte, dass das Modell hier das spezielle Wort `` auffüllt, das oft als *Mask-Token* bezeichnet wird. Andere Modelle, die dazu dienen, Maskierungen aufzufüllen, können andere Mask Tokens haben. Deshalb ist es immer gut, erst das verwendete Mask Token zu ermitteln, wenn du andere Modelle nutzen möchtest. Eine Möglichkeit, zu überprüfen, welches Mask Token verwendet wird, ist das Widget. + + + +✏️ **Probiere es aus!** Suche im Hub nach dem Modell `bert-base-cased` und finde sein Mask Token im Widget, das auf der Inference API basiert, heraus. Was sagt dieses Modell für den oben in der Pipeline verwendeten Satz vorher? + + + +## Named Entity Recognition + +Bei der Eigennamenerkennung (engl. Named Entity Recognition, NER) handelt es sich um eine Aufgabenstellung, bei der das Modell herausfinden muss, welche Teile des Input-Textes Entitäten wie Personen, Orte oder Organisationen darstellen. Nehmen wir uns ein konkretes Beispiel zur Hand: + +```python +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +```python out +[{'entity_group': 'PER', 'score': 0.99816, 'word': 'Sylvain', 'start': 11, 'end': 18}, + {'entity_group': 'ORG', 'score': 0.97960, 'word': 'Hugging Face', 'start': 33, 'end': 45}, + {'entity_group': 'LOC', 'score': 0.99321, 'word': 'Brooklyn', 'start': 49, 'end': 57} +] +``` + +Hier hat das Modell richtig erkannt, dass Sylvain eine Person (PER), Hugging Face eine Organisation (ORG) und Brooklyn ein Ort (LOC) ist. + +In der Funktion zur Erstellung der Pipeline übergeben wir die Option `grouped_entities=True`, um die Pipeline anzuweisen, die Teile des Satzes, die der gleichen Entität entsprechen, zu gruppieren: Hier hat das Modell "Hugging" und "Face" richtigerweise als eine einzelne Organisation gruppiert, auch wenn der Name aus mehreren Wörtern besteht. Wie wir im nächsten Kapitel sehen werden, werden bei der Vorverarbeitung (engl. Preprocessing) sogar einige Wörter in kleinere Teile zerlegt. Zum Beispiel wird `Sylvain` in vier Teile zerlegt: `S`, `##yl`, `##va` und `##in`. Im Nachverarbeitungsschritt (engl. Post-Processing) hat die Pipeline diese Teile erfolgreich neu gruppiert. + + + +✏️ **Probiere es aus!** Suche im Model Hub nach einem Modell, das in der Lage ist, Part-of-Speech-Tagging (in der Regel als POS abgekürzt) im Englischen durchzuführen (Anm.: d. h. Wortarten zuzuordnen). Was sagt dieses Modell für den Satz im obigen Beispiel vorher? + + + +## Frage-Antwort-Systeme (Question Answering) + +Die Pipeline `question-answering` beantwortet Fragen anhand von Informationen, die aus einem bestimmten Kontext stammen: + +```python +from transformers import pipeline + +question_answerer = pipeline("question-answering") +question_answerer( + question="Where do I work?", + context="My name is Sylvain and I work at Hugging Face in Brooklyn", +) +``` + +```python out +{'score': 0.6385916471481323, 'start': 33, 'end': 45, 'answer': 'Hugging Face'} +``` + +Beachte, dass diese Pipeline Informationen aus dem gegebenen Kontext extrahiert; sie generiert nicht die Antwort. + +## Automatische Textzusammenfassung + +Bei der automatischen Textzusammenfassung (engl. Summarization) geht es darum, einen Text zu kürzen und dabei alle (oder die meisten) wichtigen Aspekte, auf die im Text verwiesen wird, beizubehalten. Hier ist ein Beispiel: + +```python +from transformers import pipeline + +summarizer = pipeline("summarization") +summarizer( + """ + America has changed dramatically during recent years. Not only has the number of + graduates in traditional engineering disciplines such as mechanical, civil, + electrical, chemical, and aeronautical engineering declined, but in most of + the premier American universities engineering curricula now concentrate on + and encourage largely the study of engineering science. As a result, there + are declining offerings in engineering subjects dealing with infrastructure, + the environment, and related issues, and greater concentration on high + technology subjects, largely supporting increasingly complex scientific + developments. While the latter is important, it should not be at the expense + of more traditional engineering. + + Rapidly developing economies such as China and India, as well as other + industrial countries in Europe and Asia, continue to encourage and advance + the teaching of engineering. Both China and India, respectively, graduate + six and eight times as many traditional engineers as does the United States. + Other industrial countries at minimum maintain their output, while America + suffers an increasingly serious decline in the number of engineering graduates + and a lack of well-educated engineers. +""" +) +``` + +```python out +[{'summary_text': ' America has changed dramatically during recent years . The ' + 'number of engineering graduates in the U.S. has declined in ' + 'traditional engineering disciplines such as mechanical, civil ' + ', electrical, chemical, and aeronautical engineering . Rapidly ' + 'developing economies such as China and India, as well as other ' + 'industrial countries in Europe and Asia, continue to encourage ' + 'and advance engineering .'}] +``` + +Wie bei der Textgenerierung kannst du eine maximale (`max_length`) oder minimale (`min_length`) Länge für das Ergebnis angeben. + + +## Maschinelle Übersetzung + +Für die Maschinelle Übersetzung (engl. Translation) kannst du ein vorgegebenes Standardmodell verwenden, indem du ein Sprachpaar im Aufgabennamen angibst (z. B. `"translation_en_to_fr"`). Am einfachsten ist es jedoch, das Modell, das du verwenden möchtest, im [Model Hub](https://huggingface.co/models) auszuwählen. Im folgenden Beispiel probieren wir die Übersetzung vom Französischen ins Englische aus: + +```python +from transformers import pipeline + +translator = pipeline("translation", model="Helsinki-NLP/opus-mt-fr-en") +translator("Ce cours est produit par Hugging Face.") +``` + +```python out +[{'translation_text': 'This course is produced by Hugging Face.'}] +``` + +Wie bei der Textgenerierung und -zusammenfassung kannst du auch hier `max_length` oder `min_length` als Argumente für das Ergebnis angeben. + + + +✏️ **Probiere es aus!** Suche nach Übersetzungsmodellen in anderen Sprachen und versuche, den vorangegangenen Satz in mehrere verschiedene Sprachen zu übersetzen. + + + +Die bisher gezeigten Pipelines dienen hauptsächlich zu Demonstrationszwecken. Sie wurden für bestimmte Aufgabenstellungen programmiert und sind nicht für Abwandlungen geeignet. Im nächsten Kapitel erfährst du, was sich hinter einer `pipeline()`-Funktion verbirgt und wie du ihr Verhalten anpassen kannst. diff --git a/chapters/de/chapter1/4.mdx b/chapters/de/chapter1/4.mdx new file mode 100644 index 000000000..c9641aa31 --- /dev/null +++ b/chapters/de/chapter1/4.mdx @@ -0,0 +1,176 @@ +# Wie funktionieren Transformer-Modelle? + + + +In diesem Abschnitt werfen wir einen Blick auf die Architektur von Transformer-Modellen. + +## Kurz zur Entwicklungsgeschichte der Transformer-Modelle + +Hier sind einige wichtige Meilensteine in der (kurzen) Geschichte der Transformer-Modelle: + +
+A brief chronology of Transformers models. + +
+ +Die [Transformer-Architektur](https://arxiv.org/abs/1706.03762) wurde erstmals im Juni 2017 veröffentlicht. Der Schwerpunkt der ursprünglichen Forschung lag auf Übersetzungsaufgaben. In der Folge wurden mehrere einflussreiche Modelle veröffentlicht, darunter: + +- **Juni 2018**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), das erste vortrainierte Transformer-Modell, wurde zum Feintuning für verschiedene CL-Aufgaben eingesetzt und erzielte Ergebnisse, die dem neuesten Stand der Technik entsprachen. + +- **Oktober 2018**: [BERT](https://arxiv.org/abs/1810.04805), ein weiteres großes vortrainiertes Modell, das dazu dient, bessere Zusammenfassungen von Sätzen zu erstellen (mehr dazu im nächsten Kapitel!) + +- **Februar 2019**: [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), eine verbesserte (und größere) Version von GPT, die aus ethischen Erwägungen nicht sofort veröffentlicht wurde + +- **Oktober 2019**: [DistilBERT](https://arxiv.org/abs/1910.01108), eine abgespeckte Version von BERT, die 60 % schneller ist, 40 % weniger Speicherplatz benötigt und dennoch 97 % der Leistung von BERT erreicht + +- **Oktober 2019**: [BART](https://arxiv.org/abs/1910.13461) und [T5](https://arxiv.org/abs/1910.10683), zwei große vortrainierte Modelle, die dieselbe Architektur wie das ursprüngliche Transformer-Modell verwenden (die ersten, die dies getan haben) + +- **Mai 2020**, [GPT-3](https://arxiv.org/abs/2005.14165), eine noch größere Version von GPT-2, die in der Lage ist, bei einer Vielzahl von Aufgabenstellungen gute Leistungen zu erbringen, ohne dass ein Feintuning erforderlich ist (auch _Zero-Shot Learning_ genannt) + +Diese Auflistung ist bei weitem nicht vollständig und soll nur einige der verschiedenen Arten von Transformer-Modellen aufzeigen. Sie lassen sich grob in drei Kategorien einteilen: + +- GPT-ähnliche (auch _autoregressive_-Transformer-Modelle genannt) +- BERT-ähnliche (auch _Auto-Encoding_-Transformer-Modelle genannt) +- BART-/T5-ähnliche (auch _Sequence-to-Sequence_-Transformer-Modelle genannt) + +Wir werden uns mit diesen unterschiedlichen Modellfamilien später noch eingehender beschäftigen. + +## Transformer-Modelle sind Sprachmodelle + +Alle oben genannten Transformer-Modelle (GPT, BERT, BART, T5, etc.) wurden als *Sprachmodelle* (engl. Language Models) trainiert. Das bedeutet, dass sie mit großen Mengen an Rohtext auf selbstüberwachte (engl. self-supervised) Weise trainiert wurden. Selbstüberwachtes Lernen ist eine Form des Trainings, bei der die vorherzusagende Variable, die sog. Zielvariable (engl. Target), automatisch aus den Inputs des Modells berechnet wird. Das bedeutet, dass kein menschliches Zutun nötig ist, um die Daten zu labeln! + +Diese Art von Modell entwickelt ein statistisches Verständnis der Sprache, auf die es trainiert wurde, ist aber für spezifische praktische Aufgaben nicht sehr nützlich. Aus diesem Grund durchläuft das allgemeine, vortrainierte Modell ein Vorgang namens *Transfer Learning*. Während dieses Vorgangs wird das Modell unter Überwachung - d. h. mit Hilfe von durch Menschen bereitgestellte Labels - für eine bestimmte Aufgabe feingetunt. + +Ein Beispiel für eine Aufgabe ist die Vorhersage des nächsten Wortes in einem Satz, nachdem man die *n* vorherigen Wörter gelesen hat. Dies nennt sich *kausale Sprachmodellierung* (engl. Causal Language Modeling), da der Output von den vergangenen und aktuellen Inputs abhängt, aber nicht von den zukünftigen. + +
+Example of causal language modeling in which the next word from a sentence is predicted. + +
+ +Ein weiteres Beispiel ist die *maskierte Sprachmodellierung* (engl. Masked Language Modeling), bei der das Modell ein Wort im Satz, das maskiert ist, vorhersagt. + +
+Example of masked language modeling in which a masked word from a sentence is predicted. + +
+ +## Transformer-Modelle sind groß + +Abgesehen von einigen wenigen Ausreißern (wie DistilBERT) besteht die allgemeine Strategie, um eine bessere Leistung zu erzielen, darin, die Modelle zu vergrößern und die Menge an Daten zu erhöhen, auf denen sie vortrainiert werden. + +
+Number of parameters of recent Transformers models +
+ +Leider erfordert das Training eines Modells, insbesondere eines großen, eine große Menge an Daten. Das ist sehr kostspielig in Bezug auf Zeit und Rechenleistung. Es hat sogar Auswirkungen auf die Umwelt, wie in der folgenden Grafik zu sehen ist. + +
+The carbon footprint of a large language model. + +
+ + + +Hier ist ein Projekt zu sehen, bei dem ein Team gezielt versucht, die Umweltauswirkungen des Pretrainings (sehr großer) Modelle zu reduzieren. Wenn man die vielen Versuche berücksichtigt, die dazu nötig sind, die besten Hyperparameter zu finden, wären die zu bemessenden ökologischen Konsequenzen noch größer. + +Stell dir vor, dass jedes Mal, wenn ein Forschungsteam, eine Bildungseinrichtung oder ein Unternehmen ein Modell trainieren möchte, dies von Grund auf tun müsste. Das würde zu enormen, unnötigen globalen Kosten führen! + +Deshalb ist die gemeinsame Nutzung von Sprachmodellen von größter Bedeutung: trainierte Gewichtungen gemeinsam zu nutzen und auf bereits trainierten Gewichtungen aufzubauen, reduziert die gesamten Rechenkosten und den CO2-Fußabdruck der Community. + + +## Transfer Learning + + + +Beim *Pretraining* wird ein Modell von Grund auf neu trainiert: Die Gewichte werden nach dem Zufallsprinzip initialisiert und das Training beginnt ohne jegliches Vorwissen. + +
+The pretraining of a language model is costly in both time and money. + +
+ +Dieses Pretraining wird normalerweise mit sehr großen Datenmengen durchgeführt. Daher wird ein sehr großer Korpus an Daten benötigt und das Training kann mehrere Wochen in Anspruch nehmen. + +*Feintuning* ist hingegen das Training, das **nach** dem Pretraining eines Modells durchgeführt wird. Für das Feintuning nimmst du zunächst ein vortrainiertes Sprachmodell und trainierst es dann mit einem aufgabenspezifischen Datensatz nach. Moment - warum trainierst du das Modell nicht gleich für die endgültige Aufgabe? Dafür gibt es mehrere Gründe: + +* Das vortrainierte Modell wurde bereits auf einem Datensatz trainiert, der einige Ähnlichkeiten mit dem Datensatz, der für das Feintuning verwendet wird, aufweist. Beim Feintuning kann also von dem Wissen profitiert werden, das das ursprüngliche Modell während des Pretrainings erlangt hat (bei CL-Problemstellungen verfügt das vortrainierte Modell zum Beispiel über eine Art statistisches Verständnis der Sprache, die du für deine Aufgabe verwendest). +* Da das vortrainierte Modell bereits auf vielen Daten trainiert wurde, sind zum Feintuning bedeutend weniger Daten erforderlich, um brauchbare Ergebnisse erzielen zu können. +* Aus demselben Grund sind der Zeitaufwand und die Ressourcen, die für gute Ergebnisse benötigt werden, bedeutend geringer. + +Man könnte zum Beispiel ein auf Englisch trainiertes Modell nutzen und es dann auf einem arXiv-Korpus feintunen, um ein auf wissenschaftliche Sprache ausgerichtetes Modell zu erstellen. Für das Feintuning wird nur eine begrenzte Menge an Daten benötigt: Das Wissen, das das vortrainierte Modell erworben hat, wird "übertragen" (engl. transferred), daher der Begriff *Transfer Learning*. + +
+The fine-tuning of a language model is cheaper than pretraining in both time and money. + +
+ +Das Feintuning eines Modells ist daher mit geringeren Zeit-, Daten-, Umwelt- und finanziellen Kosten verbunden. Es ist auch schneller und einfacher, verschiedene Modelle für das Feintuning auszuprobieren, da das Training mit geringeren Einschränkungen einhergeht als ein vollständiges Pretraining. + +Dieser Ansatz führt auch zu besseren Ergebnissen als ein Training von Grund auf (es sei denn, du hast viele Daten). Deshalb solltest du immer versuchen, ein vortrainiertes Modell zu nutzen - und zwar ein Modell, das so nah wie möglich an deiner Aufgabenstellung ist - und es für das Feintuning verwenden. + +## Grundlegende Architektur + +In diesem Abschnitt gehen wir auf die grundlegende Architektur des Transformer-Modells ein. Mach dir keine Sorgen, wenn du einige der Konzepte nicht verstehst. Im weiteren Verlauf folgen noch ausführliche Abschnitte zu den einzelnen Komponenten. + + + +## Einführung + +Das Modell besteht hauptsächlich aus zwei Blöcken: + +* **Encoder (links)**: Der Encoder, auch Kodierer genannt, empfängt einen Input und erstellt eine numerische Darstellung bzw. Repräsentation des Inputs (seiner Features, im Deutschen auch als Merkmale bezeichnet). Das bedeutet, dass das Modell darauf optimiert ist, ein Verständnis vom Input zu erlangen. +* **Decoder (rechts)**: Der Decoder, auch bekannt als Dekodierer, verwendet die Repräsentation des Encoders (Features) zusammen mit anderen Inputs, um eine Zielsequenz zu generieren. Das bedeutet, dass das Modell darauf optimiert ist, einen Output zu generieren. + +
+Architecture of a Transformers models + +
+ +Jede dieser Komponenten kann je nach Aufgabe unabhängig voneinander verwendet werden: + +* **Rein Encoder-basierte Modelle** ("Encoder-only Models"): Gut für Aufgaben, die ein Verständnis des Inputs erfordern, wie z. B. bei der Klassifizierung von Sätzen und der Eigennamenerkennung (NER). +* **Rein Decoder-basierte Modelle** ("Decoder-only Models"): Gut geeignet für generative Aufgaben wie die Textgenerierung. +* **Encoder-Decoder-basierte Modelle** bzw. **Sequence-to-Sequence-Modelle**: Gut für generative Aufgaben, die einen Input erfordern, wie z. B. Übersetzungen oder Zusammenfassungen. + +Wir werden diese Architekturen in späteren Abschnitten noch gesondert behandeln. + +## Attention-Layer + +Ein wesentliches Merkmal der Transformer-Modelle ist, dass sie mit speziellen Layern (im Deutschen auch als Schichten bezeichnet), den *Attention-Layern*, aufgebaut sind. Der Titel des Forschungsbeitrags, in dem die Transformer-Architektur vorgestellt wurde, lautete sogar ["Attention Is All You Need"](https://arxiv.org/abs/1706.03762)! Wir werden uns später im Kurs mit den Details von Attention-Layern befassen. Für den Moment musst du nur wissen, dass dieser Layer dem Modell sagt, dass es bei der Repräsentation eines jeden Worts in einem Satz, den du ihm übergeben hast, bestimmten Wörtern besondere Aufmerksamkeit schenken (und die anderen mehr oder weniger ignorieren) soll. + +Angenommen, du sollst einen Text aus dem Englischen ins Französische übersetzen. Bei dem Input "You like this course" muss ein Übersetzungsmodell auch das angrenzende Wort "You" berücksichtigen, um die richtige Übersetzung für das Wort "like" zu erhalten, denn im Französischen wird das Verb "like" je nach Subjekt unterschiedlich konjugiert. Der Rest des Satzes ist jedoch für die Übersetzung dieses Wortes nicht hilfreich. Genauso muss das Modell bei der Übersetzung von "this" auf das Wort "course" achten, denn "this" wird unterschiedlich übersetzt, je nachdem, ob das zugehörige Substantiv männlich oder weiblich ist. Auch hier spielen die anderen Wörter im Satz für die Übersetzung von "this" keine Rolle. Bei komplexeren Sätzen (und komplexeren Grammatikregeln) muss das Modell besonders auf Wörter achten, die weiter entfernt im Satz vorkommen, um jedes Wort richtig zu übersetzen. + +Das gleiche Konzept gilt für jede Aufgabenstellung, die mit natürlicher Sprache zu tun hat: Ein Wort an sich hat eine Bedeutung, aber diese Bedeutung hängt stark vom Kontext ab, der sich durch ein anderes Wort (oder Wörter) vor oder nach dem untersuchten Wort ergibt. + +Nachdem du nun eine Vorstellung davon hast, worum es bei Attention-Layern geht, nehmen wir die Transformer-Architektur genauer unter die Lupe. + +## Die ursprüngliche Architektur + +Die Transformer-Architektur wurde ursprünglich für die maschinelle Übersetzung entwickelt. Beim Training erhält der Encoder Inputs (Sätze) in einer bestimmten Sprache, während der Decoder die gleichen Sätze in der gewünschten Zielsprache erhält. Im Encoder können die Attention-Layer alle Wörter eines Satzes verwenden (denn wie wir gerade gesehen haben, kann die Übersetzung eines bestimmten Wortes sowohl von dem abhängen, was nach, als auch von dem, was vor dem Wort im Satz steht). Der Decoder arbeitet hingegen sequentiell und kann nur die Wörter im Satz berücksichtigen, die er bereits übersetzt hat (also nur die Wörter vor dem Wort, das gerade generiert wird). Wenn wir zum Beispiel die ersten drei Wörter der übersetzten Zielsequenz vorhergesagt haben, geben wir sie an den Decoder weiter, der dann alle Inputs des Encoders verwendet, um das vierte Wort vorherzusagen. + +Um das Training zu beschleunigen (insofern das Modell Zugriff auf die Zielsätze hat), wird der Decoder mit dem gesamten (vorherzusagenden) Zielsatz gefüttert, aber er darf keine nachfolgenden Wörter verwenden (wenn er Zugriff zum Wort an Position 2 hätte, während er versucht, das Wort an Position 2 vorherzusagen, wäre die Aufgabe nicht sonderlich schwer!). Wenn er zum Beispiel versucht, das vierte Wort vorherzusagen, hat der Attention-Layer nur Zugriff zu den Wörtern an den Positionen 1 bis 3. + +Die ursprüngliche Transformer-Architektur sah wie folgt aus - mit dem Encoder auf der linken und dem Decoder auf der rechten Seite: + +
+Architecture of a Transformers models + +
+ +Beachte, dass die Attention des ersten Attention-Layers in einem Decoder-Block alle (vorangegangenen) Inputs, die der Decoder erhalten hat, berücksichtigt, während der zweite Attention-Layer den Output des Encoders verwendet. Im Rahmen der Vorhersage des aktuellen Wortes kann er also auf den gesamten Input-Satz zugreifen. Das ist vor allem deshalb nützlich, da es in den verschiedenen Sprachen unterschiedliche grammatikalische Regeln geben kann, wodurch die Wörter in einer anderen Reihenfolge aneinandergereiht werden. Ebenso könnte ein erst später im Satz enthaltener Zusammenhang dabei hilfreich sein, die beste Übersetzung eines bestimmten Wortes zu bestimmen. + +Die *Attention-Mask* kann auch im Encoder bzw. Decoder verwendet werden, um zu verhindern, dass das Modell bestimmte Wörter beachtet - zum Beispiel das spezielle Füllwort (engl. Padding Word), das verwendet wird, um alle Inputs auf die gleiche Länge zu bringen, wenn die Sätze zu Batches zusammengeführt werden. + +## Architekturen vs. Checkpoints + +Wenn wir uns in diesem Kurs mit Transformer-Modellen beschäftigen, wirst du auf *Architekturen*, *Checkpoints* und auch auf *Modelle* stoßen. Diese Begriffe haben alle eine etwas unterschiedliche Bedeutung: + +* **Architektur**: Dies ist das Skelett des Modells - die Definition jedes Layers und jeder Operation, die innerhalb des Modells stattfindet. +* **Checkpoints**: Dies ist die Gewichtung, die für eine bestimmte Architektur geladen wird. +* **Modell**: Dies ist ein Oberbegriff, der nicht so präzise ist wie "Architektur" oder "Checkpoint": Er kann beides bedeuten. In diesem Kurs wird jeweils explizit spezifiziert, ob es sich um eine *Architektur* oder um einen *Checkpoint* handelt, um Zweideutigkeiten zu vermeiden. + +BERT ist zum Beispiel eine Architektur, während `bert-base-cased` - ein Satz von Gewichten, der vom Google-Team für die erste Version von BERT trainiert wurde - ein Checkpoint ist. Man kann aber auch "das BERT-Modell" oder "das `bert-base-cased`-Modell" sagen. diff --git a/chapters/de/chapter1/5.mdx b/chapters/de/chapter1/5.mdx new file mode 100644 index 000000000..502ab5d89 --- /dev/null +++ b/chapters/de/chapter1/5.mdx @@ -0,0 +1,22 @@ +# Encoder-Modelle + + + + + +Encoder-Modelle verwenden nur den Encoder eines Transformer-Modells. Die Attention-Layer können zu jeder Zeit auf alle Wörter des Ausgangssatzes zugreifen. Diese Modelle werden häufig als Modelle mit "bidirektionaler" (engl. bi-directional) Attention bezeichnet und oft *Auto-Encoding-Modelle* genannt. + +Beim Pretraining dieser Modelle geht es in der Regel darum, einen bestimmten Satz auf irgendeine Weise zu verfälschen (z. B. indem zufällig Wörter darin maskiert werden) und das Modell dann damit zu betrauen, den ursprünglichen Satz zu finden bzw. zu rekonstruieren. + +Rein Encoder-basierte Modelle eignen sich am besten für Aufgaben, die ein Verständnis des gesamten Satzes erfordern, wie z. B. die Klassifizierung von Sätzen, die Eigennamenerkennung (bzw. allgemeiner die Klassifikation von Wörtern) und extraktive Frage-Antwort-Systeme. + +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) diff --git a/chapters/de/chapter1/6.mdx b/chapters/de/chapter1/6.mdx new file mode 100644 index 000000000..c05ac8eb2 --- /dev/null +++ b/chapters/de/chapter1/6.mdx @@ -0,0 +1,21 @@ +# Decoder-Modelle + + + + + +Decoder-Modelle verwenden nur den Decoder eines Transformer-Modells. Die Attention-Layer können bei jedem Schritt hinsichtlich eines bestimmten Wortes nur auf die Wörter zugreifen, die vor diesem Wort im Satz stehen. Diese Modelle werden oft als *autoregressive Modelle* bezeichnet. + +Beim Pretraining von Decoder-Modellen geht es in der Regel um die Vorhersage des nächsten Wortes im Satz. + +Diese Modelle sind am besten für Aufgaben geeignet, bei denen es um die Generierung von Texten geht. + +Zu dieser Modellfamilie gehören unter anderem: + +- [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) +- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) +- [GPT-2](https://huggingface.co/transformers/model_doc/gpt2.html) +- [Transformer XL](https://huggingface.co/transformers/model_doc/transformerxl.html) diff --git a/chapters/de/chapter1/7.mdx b/chapters/de/chapter1/7.mdx new file mode 100644 index 000000000..0ce8561cf --- /dev/null +++ b/chapters/de/chapter1/7.mdx @@ -0,0 +1,21 @@ +# Sequence-to-Sequence-Modelle + + + + + +Encoder-Decoder-Modelle (auch *Sequence-to-Sequence-Modelle* genannt) verwenden beide Teile der Transformer-Architektur. Die Attention-Layer des Encoders können in jedem Schritt auf alle Wörter des Ausgangssatzes zugreifen, während die Attention-Layer des Decoders nur auf die Wörter zugreifen können, die vor einem bestimmten Wort des Inputs stehen. + +Das Pretraining dieser Modelle kann wie das Pretraining von rein Encoder- oder Decoder-basierten Modellen erfolgen, ist aber in der Regel etwas komplexer. Beim Pretraining von [T5](https://huggingface.co/t5-base) werden zum Beispiel zufällige Textabschnitte (die mehrere Wörter enthalten können) durch ein einzelnes spezielles Maskierungswort ersetzt, und das Ziel (engl. Pretraining Objective) besteht dann darin, den Text vorherzusagen, der durch dieses Maskierungswort ersetzt bzw. verdeckt wurde. + +Sequence-to-Sequence-Modelle eignen sich am besten für Aufgaben, bei denen es darum geht, neue Sätze in Abhängigkeit von einem bestimmten Input zu generieren, z. B. bei der Zusammenfassung, Übersetzung oder generativen Frage-Antwort-Systemen. + +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) diff --git a/chapters/de/chapter1/8.mdx b/chapters/de/chapter1/8.mdx new file mode 100644 index 000000000..5e15e56e9 --- /dev/null +++ b/chapters/de/chapter1/8.mdx @@ -0,0 +1,32 @@ +# Bias und Einschränkungen + + + +Wenn du vorhast, ein vortrainiertes Modell oder eine feingetunte Modellversion in der Produktion zu verwenden, sei dir bitte darüber im Klaren, dass diese zwar leistungsstarke Werkzeuge sind, allerdings aber auch ihre Grenzen haben. Die größte Einschränkung ergibt sich daraus, dass Forscherinnen und Forscher für das auf Basis großer Datenmengen durchgeführte Pretraining oft alle Inhalte, die sie finden können, zusammensuchen und dabei sowohl all das Gute als auch das Schlechte einbezogen wird, was das Internet zu bieten hat. + +Greifen wir zur Veranschaulichung noch einmal das Beispiel einer `fill-mask`-Pipeline mit dem BERT-Modell auf: + +```python +from transformers import pipeline + +unmasker = pipeline("fill-mask", model="bert-base-uncased") +result = unmasker("This man works as a [MASK].") +print([r["token_str"] for r in result]) + +result = unmasker("This woman works as a [MASK].") +print([r["token_str"] for r in result]) +``` + +```python out +['lawyer', 'carpenter', 'doctor', 'waiter', 'mechanic'] +['nurse', 'waitress', 'teacher', 'maid', 'prostitute'] +``` + +Wenn das Modell aufgefordert wird, das fehlende Wort in diesen beiden Sätzen zu ergänzen, gibt es lediglich eine geschlechtsneutrale Antwort (Kellnerin/Kellner - waitress/waiter). Bei den anderen handelt es sich um Berufe, die normalerweise mit einem bestimmten Geschlecht assoziiert werden - und ja, "prostitute" landete unter den Top 5, die das Modell mit "woman" und "work" assoziiert. Und das, obwohl BERT eines der wenigen Transformer-Modelle ist, das nicht auf Daten aus dem gesamten Internet beruht, sondern auf vermeintlich neutralen Daten (es wurde auf dem [englischsprachigen Wikipedia-](https://huggingface.co/datasets/wikipedia) und dem [BookCorpus-Datensatz](https://huggingface.co/datasets/bookcorpus) trainiert). + +Wenn du diese Werkzeuge verwendest, musst du daher im Hinterkopf behalten, dass das ursprüngliche Modell, das du verwendest, sehr leicht sexistische, rassistische oder homophobe Inhalte hervorbringen könnte. Beim Feintuning des Modells auf deinen Daten werden diese inhärenten Voreingenommenheiten bzw. Vorurteile (engl. Bias) nicht verschwinden. diff --git a/chapters/de/chapter1/9.mdx b/chapters/de/chapter1/9.mdx new file mode 100644 index 000000000..f65fc8fa4 --- /dev/null +++ b/chapters/de/chapter1/9.mdx @@ -0,0 +1,16 @@ +# Zusammenfassung + + + +In diesem Kapitel hast du gelernt, wie du verschiedene CL-Aufgaben mit der High-Level-Funktion `pipeline()` aus der 🤗 Transformers-Bibliothek angehen kannst. Du hast auch erfahren, wie du im Hub nach Modellen suchen und sie nutzen kannst, und wie du die Inference API verwenden kannst, um die Modelle direkt in deinem Browser zu testen. + +Wir haben besprochen, wie Transformer-Modelle im Großen und Ganzen funktionieren, und haben die Bedeutung von Tranfer Learning und Feintuning erläutert. Ein wichtiger Aspekt ist, dass du entweder die gesamte Architektur, nur den Encoder oder auch nur den Decoder verwenden kannst - je nachdem, welche Art von Aufgabe du lösen willst. Die nachfolgende Tabelle gibt noch einmal einen guten Überblick: + +| Modell | Beispiele | Aufgaben (Tasks) | +|-----------------|--------------------------------------------|----------------------------------------------------------------------------------| +| Encoder | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa | Klassifizierung von Sätzen, Eigennamenerkennung/NER, Extraktive Frage-Antwort-Systeme | +| Decoder | CTRL, GPT, GPT-2, Transformer XL | Textgenerierung | +| Encoder-Decoder | BART, T5, Marian, mBART | Automatische Textzusammenfassung, Maschinelle Übersetzung, Generative Frage-Antwort-Systeme | diff --git a/chapters/de/glossary/1.mdx b/chapters/de/glossary/1.mdx index 5f8f859c7..82c7d5fbd 100644 --- a/chapters/de/glossary/1.mdx +++ b/chapters/de/glossary/1.mdx @@ -1,68 +1,112 @@ # Wörterverzeichnis -| Original | Übersetzung | -|-----------------------------|---------------------------------| -| Abstraction | Abstraktion | -| Account | Account | -| Accuracy | Genauigkeit | -| Backward Pass | Rückwärtsalgorithmus berechnen | -| Batch | Batch | -| Bias | Bias (Voreingenommenheit) | -| Chapter | Kapitel | -| Class | Klasse | -| Code | Code | -| Colab Notebook | Colab Notebook | -| Command | Befehl | -| Configuration | Konfiguration | -| Course | Kurs | -| Dependency | Abhängigkeitsbeziehung | -| Deployment | Deployment | -| Development | Entwicklung | -| Dictionary | Dictionary | -| Distribution | Verteilung | -| Download | Download | -| F1 score | F1-Maß | -| Feature | Feature | -| Fine-tuning | Fein-tunen | -| Folder | Ordner | -| Forward Pass | Vorwärtsalgorithmus berechnen | -| Function | Funktion | -| Google | Google | -| Hugging Face | Hugging Face | -| Incompatibility | Inkompatibilität | -| Inference | Inferenz | -| Library | Bibliothek | -| Linux | Linux | -| Load | laden | -| Loss function | Verlustfunktion | -| macOS | macOS | -| Model | Modell | -| Model Hub | Model Hub | -| Module | Modul | -| Natural Language Processing | Computerlinguistik | -| Package | Paket | -| Package Manager | Paektverwaltung | -| Padding | das Padding / auffüllen | -| Parameter | Parameter | -| Python | Python | -| Pytorch | Pytorch | -| Save | speichern | -| Script | Script | -| Self-Contained | in sich abgeschlossen | -| Setup | Installation | -| TensorFlow | Tensorflow | -| Terminal | Terminal | -| Tokenizer | Tokenizer | -| Train | Training | -| Transformer | Transformer | -| Virtual Environment | Virtuelle Umgebung | -| Weight | Gewicht | -| Weights | Gewichtung | -| Windows | Windows | -| Working Environment | Arbeitsumgebung | -| Workload | Auslastung | -| Workspace | Workspace | - +| Original | Übersetzung | +|---------------------------------|-----------------------------------------| +| Abstraction | Abstraktion | +| Account | Account | +| Accuracy | Genauigkeit | +| Artificial General Intelligence | künstliche allgemeine Intelligenz | +| Attention | Attention | +| Attention mask (layer) | Attention-Mask (Layer) | +| Backward Pass | Rückwärtsalgorithmus berechnen | +| Batch | Batch | +| Bias | Bias (Voreingenommenheit) | +| Causal Language Modeling | kausale Sprachmodellierung | +| Chapter | Kapitel | +| Checkpoint(s) | Checkpoint(s) | +| Class | Klasse | +| Classification | Klassifizierung | +| Code | Code | +| Colab Notebook | Colab Notebook | +| Command | Befehl | +| Computer Vision | Computer Vision | +| Configuration | Konfiguration | +| Course | Kurs | +| Decoder | Decoder | +| Dependency | Abhängigkeitsbeziehung | +| Deployment | Deployment | +| Development | Entwicklung | +| Dictionary | Dictionary | +| Distribution | Verteilung | +| Download | Download | +| Encoder | Encoder | +| Extractive question answering | Extraktives Question Answering | +| F1 score | F1-Maß | +| Feature | Feature | +| Fine-tune | feintunen | +| Fine-tuning | Feintuning | +| Folder | Ordner | +| Forward Pass | Vorwärtsalgorithmus berechnen | +| Function | Funktion | +| Generative question answering | Generatives Question Answering | +| Google | Google | +| Hugging Face | Hugging Face | +| Incompatibility | Inkompatibilität | +| Inference | Inferenz | +| Input | Input | +| Input data | Input-Daten | +| Label (verb) | labeln (gelabelt), annotieren | +| Label (subj) | Label, das / Labels, die (plur.) | +| Layer | Layer (plur. Layer(n)) | +| Library | Bibliothek | +| Linux | Linux | +| Load | laden | +| Loss function | Verlustfunktion | +| Machine Learning | Maschinelles Lernen | +| macOS | macOS | +| Mask | Maskierung | +| Mask Filling | Mask Filling | +| Mask Token | Mask-Token | +| Masked Language Modeling | maskierte Sprachmodellierung | +| Model | Modell | +| Model Hub | Model Hub | +| Module | Modul | +| Named Entities | benannte Entitäten | +| Named Entity Recognition | Eigennamenerkennung | +| Natural Language Processing | Computerlinguistik | +| Output | Output | +| Package | Paket | +| Package Manager | Paketverwaltung | +| Padding | das Padding / auffüllen | +| Parameter | Parameter | +| Postprocessing | Nachverarveitung | +| Preprocessing | Vorverarbeitung | +| Pretraining | Pretraining | +| Pretrained model | vortrainiertes Modell | +| Prompt | Prompt | +| Python | Python | +| Pytorch | Pytorch | +| Question Answering | Question Answering | +| Save | speichern | +| Sample | Sample (auch Stichprobe) | +| Script | Script | +| Self-Contained | in sich abgeschlossen | +| Sentiment analysis | Sentiment-Analyse | +| Sequence-to-sequence models | Sequence-to-Sequence-Modelle | +| Setup | Installation | +| Speech Processing | Verarbeitung gesprochener Sprache | +| Speech Recognition | Spracherkennung | +| Summarization | Automatische Textzusammenfassung | +| Target | Zielvariable / vorherzusagende Variable | +| Task | Aufgabe / Aufgabenstellung | +| TensorFlow | Tensorflow | +| Terminal | Terminal | +| Text generation | Textgenerierung | +| Tokenizer | Tokenizer | +| Train | Training | +| Transfer Learning | Transfer Learning | +| Transformer | Transformer | +| Transformer models | Transformer-Modelle | +| Translation | Maschinelle Übersetzung | +| Virtual Environment | Virtuelle Umgebung | +| Weight | Gewicht | +| Weights | Gewichtung | +| Windows | Windows | +| Working Environment | Arbeitsumgebung | +| Workload | Auslastung | +| Workspace | Workspace | +| Zero-shot classification | Zero-Shot-Klassifizierung | +======= ## Abkürzungen diff --git a/chapters/en/events/1.mdx b/chapters/en/events/1.mdx index 889a487a8..3be6a8cdd 100644 --- a/chapters/en/events/1.mdx +++ b/chapters/en/events/1.mdx @@ -36,7 +36,7 @@ Finally, Omar concludes the live sessions related to the first part of the cours ## Workshops -In the first workshop, Merve welcomes Lewis to discuss section 7 of chapter 7 about [question anwsering]( https://huggingface.co/course/chapter7/7?fw=pt). +In the first workshop, Merve welcomes Lewis to discuss section 7 of chapter 7 about [question answering]( https://huggingface.co/course/chapter7/7?fw=pt).
@@ -46,4 +46,4 @@ For the second workshop, Merve hosts Leandro to talk about chapter 7, section 6
-
\ No newline at end of file +
diff --git a/chapters/fr/events/1.mdx b/chapters/fr/events/1.mdx index 5128c0159..1526edf81 100644 --- a/chapters/fr/events/1.mdx +++ b/chapters/fr/events/1.mdx @@ -36,7 +36,7 @@ Enfin, Omar conclut les sessions en direct liées à la première partie du cour ## Ateliers -Dans le premier atelier, Merve accueille Lewis pour discuter de la section 7 du chapitre 7 sur le [*question anwsering*]( https://huggingface.co/course/chapter7/7?fw=pt). +Dans le premier atelier, Merve accueille Lewis pour discuter de la section 7 du chapitre 7 sur le [*question answering*]( https://huggingface.co/course/chapter7/7?fw=pt).
From 48c5b618cc3db71dec50b2a3d7664fe9e0059f98 Mon Sep 17 00:00:00 2001 From: lewtun Date: Fri, 11 Nov 2022 16:17:38 +0100 Subject: [PATCH 39/51] Bump release (#371) --- .github/workflows/build_pr_documentation.yml | 5 +- .github/workflows/delete_doc_comment.yml | 7 +- chapters/en/chapter2/2.mdx | 2 +- chapters/en/chapter3/3.mdx | 2 +- chapters/es/_toctree.yml | 20 + chapters/es/chapter5/1.mdx | 22 + chapters/es/chapter5/2.mdx | 166 +++++ chapters/es/chapter5/3.mdx | 741 +++++++++++++++++++ chapters/es/chapter5/4.mdx | 286 +++++++ chapters/es/chapter5/5.mdx | 466 ++++++++++++ chapters/es/chapter5/6.mdx | 528 +++++++++++++ chapters/es/chapter5/7.mdx | 16 + chapters/es/chapter5/8.mdx | 231 ++++++ chapters/fr/_toctree.yml | 5 + chapters/fr/chapter1/1.mdx | 49 ++ chapters/fr/chapter1/3.mdx | 35 +- chapters/fr/chapter7/2.mdx | 4 +- chapters/fr/chapter7/3.mdx | 11 +- chapters/fr/chapter7/4.mdx | 47 +- chapters/fr/chapter7/5.mdx | 50 +- chapters/fr/chapter7/6.mdx | 12 +- chapters/fr/chapter7/7.mdx | 14 +- chapters/fr/glossary/1.mdx | 63 ++ chapters/ja/_toctree.yml | 18 + chapters/ja/chapter1/10.mdx | 262 +++++++ chapters/ja/chapter1/2.mdx | 26 + chapters/ja/chapter1/3.mdx | 332 +++++++++ chapters/ja/chapter1/4.mdx | 177 +++++ chapters/ja/chapter1/5.mdx | 22 + chapters/ja/chapter1/6.mdx | 22 + chapters/ja/chapter1/7.mdx | 23 + chapters/ja/chapter1/8.mdx | 35 + chapters/ja/chapter1/9.mdx | 16 + chapters/pt/_toctree.yml | 5 + chapters/pt/chapter3/1.mdx | 27 + chapters/ru/_toctree.yml | 4 +- chapters/ru/chapter3/2.mdx | 3 +- 37 files changed, 3673 insertions(+), 81 deletions(-) create mode 100644 chapters/es/chapter5/1.mdx create mode 100644 chapters/es/chapter5/2.mdx create mode 100644 chapters/es/chapter5/3.mdx create mode 100644 chapters/es/chapter5/4.mdx create mode 100644 chapters/es/chapter5/5.mdx create mode 100644 chapters/es/chapter5/6.mdx create mode 100644 chapters/es/chapter5/7.mdx create mode 100644 chapters/es/chapter5/8.mdx create mode 100644 chapters/fr/glossary/1.mdx create mode 100644 chapters/ja/chapter1/10.mdx create mode 100644 chapters/ja/chapter1/2.mdx create mode 100644 chapters/ja/chapter1/3.mdx create mode 100644 chapters/ja/chapter1/4.mdx create mode 100644 chapters/ja/chapter1/5.mdx create mode 100644 chapters/ja/chapter1/6.mdx create mode 100644 chapters/ja/chapter1/7.mdx create mode 100644 chapters/ja/chapter1/8.mdx create mode 100644 chapters/ja/chapter1/9.mdx create mode 100644 chapters/pt/chapter3/1.mdx diff --git a/.github/workflows/build_pr_documentation.yml b/.github/workflows/build_pr_documentation.yml index 45e3b3e09..5ae7db12d 100644 --- a/.github/workflows/build_pr_documentation.yml +++ b/.github/workflows/build_pr_documentation.yml @@ -9,7 +9,7 @@ concurrency: jobs: build: - uses: huggingface/doc-builder/.github/workflows/build_pr_documentation.yml@main + uses: huggingface/doc-builder/.github/workflows/build_pr_documentation.yml@use_hf_hub with: commit_sha: ${{ github.event.pull_request.head.sha }} pr_number: ${{ github.event.number }} @@ -18,3 +18,6 @@ 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 hub_base_path: https://moon-ci-docs.huggingface.co + secrets: + token: ${{ secrets.HF_DOC_PUSH }} + comment_bot_token: ${{ secrets.HUGGINGFACE_PUSH }} diff --git a/.github/workflows/delete_doc_comment.yml b/.github/workflows/delete_doc_comment.yml index 9ec2aaf44..0ec59d485 100644 --- a/.github/workflows/delete_doc_comment.yml +++ b/.github/workflows/delete_doc_comment.yml @@ -7,7 +7,10 @@ on: jobs: delete: - uses: huggingface/doc-builder/.github/workflows/delete_doc_comment.yml@main + uses: huggingface/doc-builder/.github/workflows/delete_doc_comment.yml@use_hf_hub with: pr_number: ${{ github.event.number }} - package: course \ No newline at end of file + package: course + secrets: + token: ${{ secrets.HF_DOC_PUSH }} + comment_bot_token: ${{ secrets.HUGGINGFACE_PUSH }} \ No newline at end of file diff --git a/chapters/en/chapter2/2.mdx b/chapters/en/chapter2/2.mdx index e0be8610e..f7cd4d4e8 100644 --- a/chapters/en/chapter2/2.mdx +++ b/chapters/en/chapter2/2.mdx @@ -257,7 +257,7 @@ outputs = model(inputs) ``` {/if} -Now if we look at the shape of our inputs, the dimensionality will be much lower: the model head takes as input the high-dimensional vectors we saw before, and outputs vectors containing two values (one per label): +Now if we look at the shape of our outputs, the dimensionality will be much lower: the model head takes as input the high-dimensional vectors we saw before, and outputs vectors containing two values (one per label): ```python print(outputs.logits.shape) diff --git a/chapters/en/chapter3/3.mdx b/chapters/en/chapter3/3.mdx index 85ec90cf4..b10c92965 100644 --- a/chapters/en/chapter3/3.mdx +++ b/chapters/en/chapter3/3.mdx @@ -154,7 +154,7 @@ trainer = Trainer( Note that we create a new `TrainingArguments` with its `evaluation_strategy` set to `"epoch"` and a new model — otherwise, we would just be continuing the training of the model we have already trained. To launch a new training run, we execute: -``` +```py trainer.train() ``` diff --git a/chapters/es/_toctree.yml b/chapters/es/_toctree.yml index 6289440b6..80ab8accd 100644 --- a/chapters/es/_toctree.yml +++ b/chapters/es/_toctree.yml @@ -49,6 +49,26 @@ - local: chapter3/4 title: Entrenamiento completo +- title: 5. La librería 🤗 Datasets + sections: + - local: chapter5/1 + title: Introducción + - local: chapter5/2 + title: ¿Y si mi dataset no está en el Hub? + - local: chapter5/3 + title: Es momento de subdividir + - local: chapter5/4 + title: ¿Big data? 🤗 ¡Datasets al rescate! + - local: chapter5/5 + title: Crea tu propio dataset + - local: chapter5/6 + title: Búsqueda semántica con FAISS + - local: chapter5/7 + title: 🤗 Datasets, ¡listo! + - local: chapter5/8 + title: Quiz + quiz: 5 + - title: 8. ¿Cómo solicitar ayuda? sections: - local: chapter8/1 diff --git a/chapters/es/chapter5/1.mdx b/chapters/es/chapter5/1.mdx new file mode 100644 index 000000000..31a9a4c07 --- /dev/null +++ b/chapters/es/chapter5/1.mdx @@ -0,0 +1,22 @@ +# Introducción + + + +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: + +1. Cargar un conjunto de datos del Hub de Hugging Face. +2. Preprocesar los datos con `Dataset.map()`. +3. Cargar y calcular métricas. + +¡Esto es apenas el principio de lo que 🤗 Datasets puede hacer! En este capítulo vamos a estudiar a profundidad esta librería y responderemos las siguientes preguntas: + +* ¿Qué hacer cuando tu dataset no está en el Hub? +* ¿Cómo puedes subdividir tu dataset? (¿Y qué hacer si _realmente_ necesitas usar Pandas?) +* ¿Qué hacer cuando tu dataset es enorme y consume toda la RAM de tu computador? +* ¿Qué es la proyección en memoria (_memory mapping_) y Apache Arrow? +* ¿Cómo puedes crear tu propio dataset y subirlo al Hub? + +Las técnicas que aprenderás aquí te van a preparar para las tareas de _tokenización_ avanzada y ajuste que verás en el [Capítulo 6](/course/chapter6) y el [Capítulo 7](/course/chapter7). ¡Así que ve por un café y arranquemos! \ No newline at end of file diff --git a/chapters/es/chapter5/2.mdx b/chapters/es/chapter5/2.mdx new file mode 100644 index 000000000..a239ddf53 --- /dev/null +++ b/chapters/es/chapter5/2.mdx @@ -0,0 +1,166 @@ +# ¿Y si mi dataset no está en el Hub? + + + +Ya sabes cómo usar el [Hub de Hugging Face](https://huggingface.co/datasets) para descargar datasets, pero usualmente vas a tener que trabajar con datos que están guardados en tu computador o en un servidor remoto. En esta sección te mostraremos cómo usar 🤗 Datasets para cargar conjuntos de datos que no están disponibles en el Hub de Hugging Face. + + + +## Trabajando con datos locales y remotos + +🤗 Datasets contiene scripts para cargar datasets locales y remotos que soportan formatos comunes de datos como: + +| Formato de datos | Script de carga | Ejemplo | +| :----------------: | :------------: | :-----------------------------------------------------: | +| CSV y TSV | `csv` | `load_dataset("csv", data_files="my_file.csv")` | +| Archivos de texto | `text` | `load_dataset("text", data_files="my_file.txt")` | +| JSON y JSON Lines | `json` | `load_dataset("json", data_files="my_file.jsonl")` | +| Pickled DataFrames | `pandas` | `load_dataset("pandas", data_files="my_dataframe.pkl")` | + +Como ves en la tabla, para cada formato de datos solo tenemos que especificar el tipo de script de carga en la función `load_dataset()`, así como el argumento `data_files` que contiene la ruta a uno o más archivos. Comencemos por cargar un dataset desde archivos locales y luego veremos cómo hacer lo propio para archivos remotos. + +## Cargando un dataset local + +Para este ejemplo, vamos a usar el [dataset SQuAD-it], que es un dataset de gran escala para responder preguntas en italiano. + +Los conjuntos de entrenamiento y de prueba están alojados en GitHub, así que podemos descargarlos fácilmente con el comando `wget`: + +```python +!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-train.json.gz +!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-test.json.gz +``` + +Esto va a descargar dos archivos comprimidos llamados *SQuAD_it-train.json.gz* y *SQuAD_it-test.json.gz*, que podemos descomprimir con el comando `gzip` de Linux: + +```python +!gzip -dkv SQuAD_it-*.json.gz +``` + +```bash +SQuAD_it-test.json.gz: 87.4% -- replaced with SQuAD_it-test.json +SQuAD_it-train.json.gz: 82.2% -- replaced with SQuAD_it-train.json +``` + +De este modo, podemos ver que los archivos comprimidos son reemplazados por los archuvos en formato JSON _SQuAD_it-train.json_ y _SQuAD_it-test.json_. + + + +✎ 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. + + + +Para cargar un archivo JSON con la función `load_dataset()`, necesitamos saber si estamos trabajando con un archivo JSON ordinario (parecido a un diccionario anidado) o con JSON Lines (JSON separado por líneas). Como muchos de los datasets de respuesta a preguntas que te vas a encontrar, SQuAD-it usa el formato anidado, en el que el texto está almacenado en un campo `data`. Esto significa que podemos cargar el dataset especificando el argumento `field` de la siguiente manera: + +```py +from datasets import load_dataset + +squad_it_dataset = load_dataset("json", data_files="SQuAD_it-train.json", field="data") +``` + +Por defecto, cuando cargas archivos locales se crea un objeto `DatasetDict` con un conjunto de entrenamiento –`train`–. Podemos verlo al inspeccionar el objeto `squad_it_dataset`: + +```py +squad_it_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 442 + }) +}) +``` + +Esto nos muestra el número de filas y los nombres de las columnas asociadas al conjunto de entrenamiento. Podemos ver uno de los ejemplos al poner un índice en el conjunto de entrenamiento así: + +```py +squad_it_dataset["train"][0] +``` + +```python out +{ + "title": "Terremoto del Sichuan del 2008", + "paragraphs": [ + { + "context": "Il terremoto del Sichuan del 2008 o il terremoto...", + "qas": [ + { + "answers": [{"answer_start": 29, "text": "2008"}], + "id": "56cdca7862d2951400fa6826", + "question": "In quale anno si è verificato il terremoto nel Sichuan?", + }, + ... + ], + }, + ... + ], +} +``` + +¡Genial, ya cargamos nuestro primer dataset local! Sin embargo, esto funcionó únicamente para el conjunto de entrenamiento. Realmente, queremos incluir tanto el conjunto `train` como el conjunto `test` en un único objeto `DatasetDict` para poder aplicar las funciones `Dataset.map()` en ambos conjuntos al mismo tiempo. Para hacerlo, podemos incluir un diccionario en el argumento `datafiles` que mapea cada nombre de conjunto a su archivo asociado: + + +```py +data_files = {"train": "SQuAD_it-train.json", "test": "SQuAD_it-test.json"} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +squad_it_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 442 + }) + test: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 48 + }) +}) +``` + +Esto es exactamente lo que queríamos. Ahora podemos aplicar varias técnicas de preprocesamiento para limpiar los datos, _tokenizar_ las reseñas, entre otras tareas. + + + +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. + + + +Los scripts de carga en 🤗 Datasets también pueden descomprimir los archivos de entrada automáticamente, así que podemos saltarnos el uso de `gzip` especificando el argumento `data_files` directamente a la ruta de los archivos comprimidos. + +```py +data_files = {"train": "SQuAD_it-train.json.gz", "test": "SQuAD_it-test.json.gz"} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +``` + +Esto puede ser útil si no quieres descomprimir manualmente muchos archivos GZIP. La descompresión automática también aplica para otros formatos de archivo comunes como TAR y ZIP, así que solo necesitas dirigir el argumento `data_files` a los archivos comprimidos y ¡listo!. + +Ahora que sabes cómo cargar archivos locales en tu computador portátil o de escritorio, veamos cómo cargar archivos remotos. + +## Cargando un dataset remoto + +Si estás trabajando como científico de datos o desarrollador en una compañía, hay una alta probabilidad de que los datasets que quieres analizar estén almacenados en un servidor remoto. Afortunadamente, ¡la carga de archivos remotos es tan fácil como cargar archivos locales! En vez de incluir una ruta de archivo, dirigimos el argumento `data_files` de la función `load_datasets()` a una o más URL en las que estén almacenados los archivos. Por ejemplo, para el dataset SQuAD-it alojado en GitHub, podemos apuntar `data_files` a las URL de _SQuAD_it-*.json.gz_ así: + +```py +url = "https://github.com/crux82/squad-it/raw/master/" +data_files = { + "train": url + "SQuAD_it-train.json.gz", + "test": url + "SQuAD_it-test.json.gz", +} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +``` + +Esto devuelve el mismo objeto `DatasetDict` que obtuvimos antes, pero nos ahorra el paso de descargar y descomprimir manualmente los archivos _SQuAD_it-*.json.gz_. Con esto concluimos nuestra exploración de las diferentes maneras de cargar datasets que no están alojados en el Hub de Hugging Face. Ahora que tenemos un dataset para experimentar, ¡pongámonos manos a la obra con diferentes técnicas de procesamiento de datos! + + + +✏️ **¡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). + + diff --git a/chapters/es/chapter5/3.mdx b/chapters/es/chapter5/3.mdx new file mode 100644 index 000000000..241916c9d --- /dev/null +++ b/chapters/es/chapter5/3.mdx @@ -0,0 +1,741 @@ +# Es momento de subdividir + + + +La mayor parte del tiempo tus datos no estarán perfectamente listos para entrenar modelos. En esta sección vamos a explorar distintas funciones que tiene 🤗 Datasets para limpiar tus conjuntos de datos. + + + +## Subdivdiendo 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. + +Para este ejemplo, vamos a usar el [Dataset de reseñas de medicamentos](https://archive.ics.uci.edu/ml/datasets/Drug+Review+Dataset+%28Drugs.com%29) alojado en el [Repositorio de Machine Learning de UC Irvine](https://archive.ics.uci.edu/ml/index.php), que contiene la evaluación de varios medicamentos por parte de pacientes, junto con la condición por la que los estaban tratando y una calificación en una escala de 10 estrellas sobre su satisfacción. + +Primero, tenemos que descargar y extraer los datos, que se puede hacer con los comandos `wget` y `unzip`: + +```py +!wget "https://archive.ics.uci.edu/ml/machine-learning-databases/00462/drugsCom_raw.zip" +!unzip drugsCom_raw.zip +``` + +Dado que TSV es una variación de CSV en la que se usan tabulaciones en vez de comas como separadores, podemos cargar estos archivos usando el script de carga `csv` y especificando el argumento `delimiter` en la función `load_dataset` de la siguiente manera: + +```py +from datasets import load_dataset + +data_files = {"train": "drugsComTrain_raw.tsv", "test": "drugsComTest_raw.tsv"} +# \t es el caracter para tabulaciones en Python +drug_dataset = load_dataset("csv", data_files=data_files, delimiter="\t") +``` + +Una buena práctica al hacer cualquier tipo de análisis de datos es tomar una muestra aleatoria del dataset para tener una vista rápida del tipo de datos con los que estás trabajando. En 🤗 Datasets, podemos crear una muestra aleatoria al encadenar las funciones `Dataset.shuffle()` y `Dataset.select()`: + +```py +drug_sample = drug_dataset["train"].shuffle(seed=42).select(range(1000)) +# Mirar los primeros ejemplos +drug_sample[:3] +``` + +```python out +{'Unnamed: 0': [87571, 178045, 80482], + 'drugName': ['Naproxen', 'Duloxetine', 'Mobic'], + 'condition': ['Gout, Acute', 'ibromyalgia', 'Inflammatory Conditions'], + 'review': ['"like the previous person mention, I'm a strong believer of aleve, it works faster for my gout than the prescription meds I take. No more going to the doctor for refills.....Aleve works!"', + '"I have taken Cymbalta for about a year and a half for fibromyalgia pain. It is great\r\nas a pain reducer and an anti-depressant, however, the side effects outweighed \r\nany benefit I got from it. I had trouble with restlessness, being tired constantly,\r\ndizziness, dry mouth, numbness and tingling in my feet, and horrible sweating. I am\r\nbeing weaned off of it now. Went from 60 mg to 30mg and now to 15 mg. I will be\r\noff completely in about a week. The fibro pain is coming back, but I would rather deal with it than the side effects."', + '"I have been taking Mobic for over a year with no side effects other than an elevated blood pressure. I had severe knee and ankle pain which completely went away after taking Mobic. I attempted to stop the medication however pain returned after a few days."'], + 'rating': [9.0, 3.0, 10.0], + 'date': ['September 2, 2015', 'November 7, 2011', 'June 5, 2013'], + '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: + +* 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. +* Las reseñas tienen longitud variable y contienen una mezcla de separadores de línea de Python (`\r\n`), así como caracteres de HTML como `&\#039;`. + +Veamos cómo podemos usar 🤗 Datasets para lidiar con cada uno de estos asuntos. Para probar la hipótesis de que la columna `Unnamed: 0` es un ID de los pacientes, podemos usar la función `Dataset.unique()` para verificar que el número de los ID corresponda con el número de filas de cada conjunto: + +```py +for split in drug_dataset.keys(): + assert len(drug_dataset[split]) == len(drug_dataset[split].unique("Unnamed: 0")) +``` + +Esto parece confirmar nuestra hipótesis, así que limpiemos el dataset un poco al cambiar el nombre de la columna `Unnamed: 0` a algo más legible. Podemos usar la función `DatasetDict.rename_column()` para renombrar la columna en ambos conjuntos en una sola operación: + +```py +drug_dataset = drug_dataset.rename_column( + original_column_name="Unnamed: 0", new_column_name="patient_id" +) +drug_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'], + num_rows: 161297 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'], + num_rows: 53766 + }) +}) +``` + + + +✏️ **¡Inténtalo!** Usa la función `Dataset.unique()` para encontrar el número de medicamentos y condiciones únicas en los conjuntos de entrenamiento y de prueba. + + + +Ahora normalicemos todas las etiquetas de `condition` usando `Dataset.map()`. Tal como lo hicimos con la tokenización en el [Capítulo 3](/course/chapter3), podemos definir una función simple que pueda ser aplicada en todas las filas de cada conjunto en el `drug_dataset`: + +```py +def lowercase_condition(example): + return {"condition": example["condition"].lower()} + + +drug_dataset.map(lowercase_condition) +``` + +```python out +AttributeError: 'NoneType' object has no attribute 'lower' +``` + +¡Tenemos un problema en nuestra función de mapeo! Del error podemos inferir que algunas de las entradas de la columna `condición` son `None`, que no puede transformarse en minúscula al no ser un string. Filtremos estas filas usando `Dataset.filter()`, que funciona de una forma similar `Dataset.map()` y recibe como argumento una función que toma un ejemplo particular del dataset. En vez de escribir una función explícita como: + +```py +def filter_nones(x): + return x["condition"] is not None +``` + +y luego ejecutar `drug_dataset.filter(filter_nones)`, podemos hacerlo en una línea usando una _función lambda_. En Python, las funciones lambda son funciones pequeñas que puedes definir sin nombrarlas explícitamente. Estas toman la forma general: + +``` +lambda : +``` + +en la que `lambda` es una de las [palabras especiales](https://docs.python.org/3/reference/lexical_analysis.html#keywords) de Python, `` es una lista o conjunto de valores separados con coma que definen los argumentos de la función y `` representa las operaciones que quieres ejecutar. Por ejemplo, podemos definir una función lambda simple que eleve un número al cuadrado de la siguiente manera: + +``` +lambda x : x * x +``` + +Para aplicar esta función a un _input_, tenemos que envolverla a ella y al _input_ en paréntesis: + +```py +(lambda x: x * x)(3) +``` + +```python out +9 +``` + +De manera similar, podemos definir funciones lambda con múltiples argumentos separándolos con comas. Por ejemplo, podemos calcular el área de un triángulo así: + +```py +(lambda base, height: 0.5 * base * height)(4, 8) +``` + +```python out +16.0 +``` + +Las funciones lambda son útiles cuando quieres definir funciones pequeñas de un único uso (para más información sobre ellas, te recomendamos leer este excelente [tutorial de Real Python](https://realpython.com/python-lambda/) escrito por Andre Burgaud). En el contexto de 🤗 Datasets, podemos usar las funciones lambda para definir operaciones simples de mapeo y filtrado, así que usemos este truco para eliminar las entradas `None` de nuestro dataset: + +```py +drug_dataset = drug_dataset.filter(lambda x: x["condition"] is not None) +``` + +Ahora que eliminamos los `None`, podemos normalizar nuestra columna `condition`: + +```py +drug_dataset = drug_dataset.map(lowercase_condition) +# Revisar que se pasaron a minúscula +drug_dataset["train"]["condition"][:3] +``` + +```python out +['left ventricular dysfunction', 'adhd', 'birth control'] +``` + +¡Funcionó! Como ya limpiamos las etiquetas, veamos cómo podemos limpiar las reseñas. + +## Creando nuevas columnas + +Cuando estás lidiando con reseñas de clientes, es una buena práctica revisar el número de palabras de cada reseña. Una reseña puede ser una única palabra como "¡Genial!" o un ensayo completo con miles de palabras y, según el caso de uso, tendrás que abordar estos extremos de forma diferente. Para calcular el número de palabras en cada reseña, usaremos una heurística aproximada basada en dividir cada texto por los espacios en blanco. + +Definamos una función simple que cuente el número de palabras en cada reseña: + +```py +def compute_review_length(example): + return {"review_length": len(example["review"].split())} +``` + +Contrario a la función `lowercase_condition()`, `compute_review_length()` devuelve un diccionario cuya llave no corresponde a uno de los nombres de las columnas en el conjunto de datos. En este caso, cuando se pasa `compute_review_length()` a `Dataset.map()`, la función se aplicará a todas las filas en el dataset para crear una nueva columna `review_length()`: + +```py +drug_dataset = drug_dataset.map(compute_review_length) +# Inspeccionar el primer ejemplo de entrenamiento +drug_dataset["train"][0] +``` + +```python out +{'patient_id': 206461, + 'drugName': 'Valsartan', + 'condition': 'left ventricular dysfunction', + 'review': '"It has no side effect, I take it in combination of Bystolic 5 Mg and Fish Oil"', + 'rating': 9.0, + 'date': 'May 20, 2012', + 'usefulCount': 27, + 'review_length': 17} +``` + +Tal como lo esperábamos, podemos ver que se añadió la columna `review_length` al conjunto de entrenamiento. Podemos ordenar esta columna nueva con `Dataset.sort()` para ver cómo son los valores extremos: + +```py +drug_dataset["train"].sort("review_length")[:3] +``` + +```python out +{'patient_id': [103488, 23627, 20558], + 'drugName': ['Loestrin 21 1 / 20', 'Chlorzoxazone', 'Nucynta'], + 'condition': ['birth control', 'muscle spasm', 'pain'], + 'review': ['"Excellent."', '"useless"', '"ok"'], + 'rating': [10.0, 1.0, 6.0], + 'date': ['November 4, 2008', 'March 24, 2017', 'August 20, 2016'], + 'usefulCount': [5, 2, 10], + '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. + + + +🙋 Una forma alternativa de añadir nuevas columnas al dataset es a través de la función `Dataset.add_column()`. Esta te permite incluir la columna como una lista de Python o un array de NumPy y puede ser útil en situaciones en las que `Dataset.map()` no se ajusta a tu caso de uso. + + + +Usemos la función `Dataset.filter()` para quitar las reseñas que contienen menos de 30 palabras. Similar a lo que hicimos con la columna `condition`, podemos filtrar las reseñas cortas al incluir una condición de que su longitud esté por encima de este umbral: + +```py +drug_dataset = drug_dataset.filter(lambda x: x["review_length"] > 30) +print(drug_dataset.num_rows) +``` + +```python out +{'train': 138514, 'test': 46108} +``` + +Como puedes ver, esto ha eliminado alrededor del 15% de las reseñas de nuestros conjuntos originales de entrenamiento y prueba. + + + +✏️ **¡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. + + + +Por último, tenemos que lidiar con la presencia de códigos de caracteres HTML en las reseñas. Podemos usar el módulo `html` de Python para transformar estos códigos así: + +```py +import html + +text = "I'm a transformer called BERT" +html.unescape(text) +``` + +```python out +"I'm a transformer called BERT" +``` + +Usaremos `Dataset.map()` para transformar todos los caracteres HTML en el corpus: + +```python +drug_dataset = drug_dataset.map(lambda x: {"review": html.unescape(x["review"])}) +``` + +Como puedes ver, el método `Dataset.map()` es muy útil para procesar datos y esta es apenas la punta del iceberg de lo que puede hacer. + +## Los superpoderes del método `map()` + +El método `Dataset.map()` recibe un argumento `matched` que, al definirse como `True`, envía un lote de ejemplos a la función de mapeo a la vez (el tamaño del lote se puede configurar, pero tiene un valor por defecto de 1.000). Por ejemplo, la función anterior de mapeo que transformó todos los HTML se demoró un poco en su ejecución (puedes leer el tiempo en las barras de progreso). Podemos reducir el tiempo al procesar varios elementos a la vez usando un _list comprehension_. + +Cuando especificas `batched=True`, la función recibe un diccionario con los campos del dataset, pero cada valor es ahora una _lista de valores_ y no un valor individual. La salida de `Dataset.map()` debería ser igual: un diccionario con los campos que queremos actualizar o añadir a nuestro dataset y una lista de valores. Por ejemplo, aquí puedes ver otra forma de transformar todos los caracteres HTML usando `batched=True`: + +```python +new_drug_dataset = drug_dataset.map( + lambda x: {"review": [html.unescape(o) for o in x["review"]]}, batched=True +) +``` + +Si estás ejecutando este código en un cuaderno, verás que este comando se ejecuta mucho más rápido que el anterior. Y no es porque los caracteres HTML de las reseñas ya se hubieran procesado; si vuelves a ejecutar la instrucción de la sección anterior (sin `batched=True`), se tomará el mismo tiempo de ejecución que antes. Esto es porque las _list comprehensions_ suelen ser más rápidas que ejecutar el mismo código en un ciclo `for` y porque también ganamos rendimiento al acceder a muchos elementos a la vez en vez de uno por uno. + +Usar `Dataset.map()` con `batched=True` será fundamental para desbloquear la velocidad de los tokenizadores "rápidos" que nos vamos a encontrar en el [Capítulo 6](/course/chapter6), que pueden tokenizar velozmente grandes listas de textos. Por ejemplo, para tokenizar todas las reseñas de medicamentos con un tokenizador rápido, podríamos usar una función como la siguiente: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") + + +def tokenize_function(examples): + return tokenizer(examples["review"], truncation=True) +``` + +Como viste en el [Capítulo 3](/course/chapter3), podemos pasar uno o varios ejemplos al tokenizador, así que podemos usar esta función con o sin `batched=True`. Aprovechemos esta oportunidad para comparar el desempeño de las distintas opciones. En un cuaderno, puedes medir el tiempo de ejecución de una instrucción de una línea añadiendo `%time` antes de la línea de código de tu interés: + +```python no-format +%time tokenized_dataset = drug_dataset.map(tokenize_function, batched=True) +``` + +También puedes medir el tiempo de una celda completa añadiendo `%%time` al inicio de la celda. En el hardware en el que lo ejecutamos, nos arrojó 10.8s para esta instrucción (es el número que aparece después de "Wall time"). + + + +✏️ **¡Inténtalo!** Ejecuta la misma instrucción con y sin `batched=True` y luego usa un tokenizador "lento" (añade `use_fast=False` en el método `AutoTokenizer.from_pretrained()`) para ver cuánto tiempo se toman en tu computador. + + + +Estos son los resultados que obtuvimos con y sin la ejecución por lotes, con un tokenizador rápido y lento: + +Opciones | Tokenizador rápido | Tokenizador lento +:--------------:|:--------------:|:-------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s + +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. + +`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()`: + +```py +slow_tokenizer = AutoTokenizer.from_pretrained("bert-base-cased", use_fast=False) + + +def slow_tokenize_function(examples): + return slow_tokenizer(examples["review"], truncation=True) + + +tokenized_dataset = drug_dataset.map(slow_tokenize_function, batched=True, num_proc=8) +``` + +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 +:--------------:|:--------------:|:-------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s +`batched=True`, `num_proc=8` | 6.52s | 41.3s +`batched=False`, `num_proc=8` | 9.49s | 45.2s + +Estos son resultados mucho más razonables para el tokenizador lento, aunque el desempeño del rápido también mejoró sustancialmente. Sin embargo, este no siempre será el caso: para valores de `num_proc` diferentes a 8, nuestras pruebas mostraron que era más rápido usar `batched=true` sin esta opción. En general, no recomendamos usar el multiprocesamiento de Python para tokenizadores rápidos con `batched=True`. + + + +Usar `num_proc` para acelerar tu procesamiento suele ser una buena idea, siempre y cuando la función que uses no esté usando multiples procesos por si misma. + + + +Que toda esta funcionalidad está incluida en un método es algo impresionante en si mismo, ¡pero hay más!. Con `Dataset.map()` y `batched=True` puedes cambiar el número de elementos en tu dataset. Esto es súper útil en situaciones en las que quieres crear varias características de entrenamiento de un ejemplo, algo que haremos en el preprocesamiento para varias de las tareas de PLN que abordaremos en el [Capítulo 7](/course/chapter7). + + + +💡 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-. + + + +¡Veamos cómo funciona! En este ejemplo vamos a tokenizar nuestros ejemplos y limitarlos a una longitud máxima de 128, pero le pediremos al tokenizador que devuelva *todos* los fragmentos de texto en vez de unicamente el primero. Esto se puede lograr con el argumento `return_overflowing_tokens=True`: + +```py +def tokenize_and_split(examples): + return tokenizer( + examples["review"], + truncation=True, + max_length=128, + return_overflowing_tokens=True, + ) +``` + +Probémoslo en un ejemplo puntual antes de usar `Dataset.map()` en todo el dataset: + +```py +result = tokenize_and_split(drug_dataset["train"][0]) +[len(inp) for inp in result["input_ids"]] +``` + +```python out +[128, 49] +``` + +El primer ejemplo en el conjunto de entrenamiento se convirtió en dos features porque fue tokenizado en un número superior de tokens al que especificamos: el primero de longitud 128 y el segundo de longitud 49. ¡Vamos a aplicarlo a todo el dataset! + +```py +tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) +``` + +```python out +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. + +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`: + +```py +tokenized_dataset = drug_dataset.map( + tokenize_and_split, batched=True, remove_columns=drug_dataset["train"].column_names +) +``` + +Ahora funciona sin errores. Podemos revisar que nuestro dataset nuevo tiene más elementos que el original al comparar sus longitudes: + +```py +len(tokenized_dataset["train"]), len(drug_dataset["train"]) +``` + +```python out +(206772, 138514) +``` + +También mencionamos que podemos trabajar con el problema de longitudes que no coinciden al convertir las columnas viejas en el mismo tamaño de las nuevas. Para eso, vamos a necesitar el campo `overflow_to_sample_mapping` que devuelve el tokenizer cuando definimos `return_overflowing_tokens=True`. Esto devuelve un mapeo del índice de un nuevo feature al índice de la muestra de la que se originó. Usando lo anterior, podemos asociar cada llave presente en el dataset original con una lista de valores del tamaño correcto al repetir los valores de cada ejemplo tantas veces como genere nuevos features: + +```py +def tokenize_and_split(examples): + result = tokenizer( + examples["review"], + truncation=True, + max_length=128, + return_overflowing_tokens=True, + ) + # Extraer el mapeo entre los índices nuevos y viejos + sample_map = result.pop("overflow_to_sample_mapping") + for key, values in examples.items(): + result[key] = [values[i] for i in sample_map] + return result +``` + +De esta forma, podemos ver que funciona con `Dataset.map()` sin necesidad de eliminar las columnas viejas. + +```py +tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) +tokenized_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'], + num_rows: 206772 + }) + test: Dataset({ + features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'], + num_rows: 68876 + }) +}) +``` + +Como resultado, tenemos el mismo número de features de entrenamiento que antes, pero conservando todos los campos anteriores. Quizás prefieras usar esta opción si necesitas conservarlos para algunas tareas de post-procesamiento después de aplicar tu modelo. + +Ya has visto como usar 🤗 Datasets para preprocesar un dataset de varias formas. Si bien las funciones de procesamiento de 🤗 Datasets van a suplir la mayor parte de tus necesidades de entrenamiento de modelos, hay ocasiones en las que puedes necesitar Pandas para tener acceso a herramientas más poderosas, como `DataFrame.groupby()` o algún API de alto nivel para visualización. Afortunadamente, 🤗 Datasets está diseñado para ser interoperable con librerías como Pandas, NumPy, PyTorch, TensoFlow y JAX. Veamos cómo funciona. + +## De `Dataset`s a `DataFrame`s y viceversa + + + +Para habilitar la conversión entre varias librerías de terceros, 🤗 Datasets provee la función `Dataset.set_format()`. Esta función sólo cambia el _formato de salida_ del dataset, de tal manera que puedas cambiar a otro formato sin cambiar el _formato de datos subyacente_, que es Apache Arrow. Este cambio de formato se hace _in place_. Para verlo en acción, convirtamos el dataset a Pandas: + +```py +drug_dataset.set_format("pandas") +``` + +Ahora, cuando accedemos a los elementos del dataset obtenemos un `pandas.DataFrame` en vez de un diccionario: + +```py +drug_dataset["train"][:3] +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
patient_iddrugNameconditionreviewratingdateusefulCountreview_length
095260Guanfacineadhd"My son is halfway through his fourth week of Intuniv..."8.0April 27, 2010192141
192703Lybrelbirth control"I used to take another oral contraceptive, which had 21 pill cycle, and was very happy- very light periods, max 5 days, no other side effects..."5.0December 14, 200917134
2138000Ortho Evrabirth control"This is my first time using any form of birth control..."8.0November 3, 20151089
+ +Creemos un `pandas.DataFrame` para el conjunto de entrenamiento entero al seleccionar los elementos de `drug_dataset["train"]`: + +```py +train_df = drug_dataset["train"][:] +``` + + + +🚨 Internamente, `Dataset.set_format()` cambia el formato de devolución del método _dunder_ `__getitem()__`. Esto significa que cuando queremos crear un objeto nuevo como `train_df` de un `Dataset` en formato `"pandas"`, tenemos que seleccionar el dataset completo para obtener un `pandas.DataFrame`. Puedes verificar por ti mismo que el tipo de `drug_dataset["train"]` es `Dataset` sin importar el formato de salida. + + + +De aquí en adelante podemos usar toda la funcionalidad de pandas cuando queramos. Por ejemplo, podemos hacer un encadenamiento sofisticado para calcular la distribución de clase entre las entradas de `condition`: + +```py +frequencies = ( + train_df["condition"] + .value_counts() + .to_frame() + .reset_index() + .rename(columns={"index": "condition", "condition": "frequency"}) +) +frequencies.head() +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
conditionfrequency
0birth control27655
1depression8023
2acne5209
3anxiety4991
4pain4744
+ +Y una vez hemos concluido el análisis con Pandas, tenemos la posibilidad de crear un nuevo objeto `Dataset` usando la función `Dataset.from_pandas()` de la siguiente manera: + +```py +from datasets import Dataset + +freq_dataset = Dataset.from_pandas(frequencies) +freq_dataset +``` + +```python out +Dataset({ + features: ['condition', 'frequency'], + num_rows: 819 +}) +``` + + + +✏️ **¡Inténtalo!** Calcula la calificación promedio por medicamento y guarda el resultado en un nuevo `Dataset`. + + + +Con esto terminamos nuestro tour de las múltiples técnicas de preprocesamiento disponibles en 🤗 Datasets. Para concluir, creemos un set de validación para preparar el conjunto de datos y entrenar el clasificador. Antes de hacerlo, vamos a reiniciar el formato de salida de `drug_dataset` de `"pandas"` a `"arrow"`: + +```python +drug_dataset.reset_format() +``` + +## Creando un conjunto de validación + +Si bien tenemos un conjunto de prueba que podríamos usar para la evaluación, es una buena práctica dejar el conjunto de prueba intacto y crear un conjunto de validación aparte durante el desarrollo. Una vez estés satisfecho con el desempeño de tus modelos en el conjunto de validación, puedes hacer un último chequeo con el conjunto de prueba. Este proceso ayuda a reducir el riesgo de sobreajustar al conjunto de prueba y desplegar un modelo que falle en datos reales. + +🤗 Datasets provee la función `Dataset.train_test_split()` que está basada en la famosa funcionalidad de `scikit-learn`. Usémosla para separar nuestro conjunto de entrenamiento en dos partes `train` y `validation` (definiendo el argumento `seed` por motivos de reproducibilidad): + +```py +drug_dataset_clean = drug_dataset["train"].train_test_split(train_size=0.8, seed=42) +# Renombrar el conjunto "test" a "validation" +drug_dataset_clean["validation"] = drug_dataset_clean.pop("test") +# Añadir el conjunto "test" al `DatasetDict` +drug_dataset_clean["test"] = drug_dataset["test"] +drug_dataset_clean +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 110811 + }) + validation: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 27703 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 46108 + }) +}) +``` + +Súper, ya preparamos un dataset que está listo para entrenar modelos. En la [sección 5](/course/chapter5/5) veremos cómo subir datasets al Hub de Hugging Face, pero por ahora terminemos el análisis estudiando algunas formas de guardarlos en tu máquina local. + +## Saving a dataset + + + +A pesar de que 🤗 Datasets va a guardar en caché todo dataset que descargues, así como las operaciones que se ejecutan en él, hay ocasiones en las que querrás guardar un dataset en memoria (e.g., en caso que el caché se elimine). Como se muestra en la siguiente tabla, 🤗 Datasets tiene 3 funciones para guardar tu dataset en distintos formatos: + + +| Formato | Función | +| :---------: | :--------------------: | +| Arrow | `Dataset.save_to_disk()` | +| CSV | `Dataset.to_csv()` | +| JSON | `Dataset.to_json()` | + +Por ejemplo, guardemos el dataset limpio en formato Arrow: + +```py +drug_dataset_clean.save_to_disk("drug-reviews") +``` + +Esto creará una carpeta con la siguiente estructura: + +``` +drug-reviews/ +├── dataset_dict.json +├── test +│ ├── dataset.arrow +│ ├── dataset_info.json +│ └── state.json +├── train +│ ├── dataset.arrow +│ ├── dataset_info.json +│ ├── indices.arrow +│ └── state.json +└── validation + ├── dataset.arrow + ├── dataset_info.json + ├── indices.arrow + └── state.json +``` + +en las que podemos ver que cada parte del dataset está asociada con una tabla *dataset.arrow* y algunos metadatos en *dataset_info.json* y *state.json*. Puedes pensar en el formato Arrow como una tabla sofisticada de columnas y filas que está optimizada para construir aplicaciones de alto rendimiento que procesan y transportan datasets grandes. + +Una vez el dataset está guardado, podemos cargarlo usando la función `load_from_disk()` así: + +```py +from datasets import load_from_disk + +drug_dataset_reloaded = load_from_disk("drug-reviews") +drug_dataset_reloaded +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 110811 + }) + validation: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 27703 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 46108 + }) +}) +``` + +Para los formatos CSV y JSON, tenemos que guardar cada parte en un archivo separado. Una forma de hacerlo es iterando sobre las llaves y valores del objeto `DatasetDict`: + +```py +for split, dataset in drug_dataset_clean.items(): + dataset.to_json(f"drug-reviews-{split}.jsonl") +``` + +Esto guarda cada parte en formato [JSON Lines](https://jsonlines.org), donde cada fila del dataset está almacenada como una única línea de JSON. Así se ve el primer ejemplo: + +```py +!head -n 1 drug-reviews-train.jsonl +``` + +```python out +{"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} +``` + +Podemos usar las técnicas de la [sección 2](/course/chapter5/2) para cargar los archivos JSON de la siguiente manera: + +```py +data_files = { + "train": "drug-reviews-train.jsonl", + "validation": "drug-reviews-validation.jsonl", + "test": "drug-reviews-test.jsonl", +} +drug_dataset_reloaded = load_dataset("json", data_files=data_files) +``` + +Esto es todo lo que vamos a ver en nuestra revisión del manejo de datos con 🤗 Datasets. Ahora que tenemos un dataset limpio para entrenar un modelo, aquí van algunas ideas que podrías intentar: + +1. Usa las técnicas del [Capítulo 3](/course/chapter3) para entrenar un clasificador que pueda predecir la condición del paciente con base en las reseñas de los medicamentos. +2. Usa el pipeline de `summarization` del [Capítulo 1](/course/chapter1) para generar resúmenes de las reseñas. + +En la siguiente sección veremos cómo 🤗 Datasets te puede ayudar a trabajar con datasets enormes ¡sin explotar tu computador! diff --git a/chapters/es/chapter5/4.mdx b/chapters/es/chapter5/4.mdx new file mode 100644 index 000000000..344fb0545 --- /dev/null +++ b/chapters/es/chapter5/4.mdx @@ -0,0 +1,286 @@ +# ¿Big data? 🤗 ¡Datasets al rescate! + + + +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! + +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. + + + +En esta sección vamos a explorar estas funcionalidades de 🤗 Datasets con un corpus enorme de 825 GB conocido como el [Pile](https://pile.eleuther.ai). ¡Comencemos! + +## ¿Qué es el Pile? + +El _Pile_ es un corpus de textos en inglés creado por [EleutherAI](https://www.eleuther.ai) para entrenar modelos de lenguaje de gran escala. Incluye una selección diversa de datasets que abarca artículos científicos, repositorios de código de Github y texto filtrado de la web. El corpus de entrenamiento está disponible en [partes de 14 GB](https://mystic.the-eye.eu/public/AI/pile/) y también puedes descargar varios de los [componentes individuales](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/). Arranquemos viendo el dataset de los abstracts de PubMed, un corpus de abstracts de 15 millones de publicaciones biomédicas en [PubMed](https://pubmed.ncbi.nlm.nih.gov/). Este dataset está en formato [JSON Lines](https://jsonlines.org) y está comprimido con la librería `zstandard`, así que primero tenemos que instalarla: + +```py +!pip install zstandard +``` + +A continuación, podemos cargar el dataset usando el método para archivos remotos que aprendimos en la [sección 2](/course/chapter5/2): + +```py +from datasets import load_dataset + +# Esto toma algunos minutos para ejecutarse, así que ve por un te o un café mientras esperas :) +data_files = "https://mystic.the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst" +pubmed_dataset = load_dataset("json", data_files=data_files, split="train") +pubmed_dataset +``` + +```python out +Dataset({ + features: ['meta', 'text'], + num_rows: 15518009 +}) +``` + +Como podemos ver, hay 15.518.009 filas y dos columnas en el dataset, ¡un montón! + + + +✎ 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. + + + +Veamos el contenido del primer ejemplo: + +```py +pubmed_dataset[0] +``` + +```python out +{'meta': {'pmid': 11409574, 'language': 'eng'}, + '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 ...'} +``` + +Ok, esto parece el abstract de un artículo médico. Ahora miremos cuánta RAM hemos usado para cargar el dataset. + +## La magia de la proyección en memoria + +Una forma simple de medir el uso de memoria en Python es con la librería [`psutil`](https://psutil.readthedocs.io/en/latest/), que se puede instalar con `pip` así: + +```python +!pip install psutil +``` + +Esta librería contiene una clase `Process` que nos permite revisar el uso de memoria del proceso actual: + +```py +import psutil + +# Process.memory_info está expresado en bytes, así que lo convertimos en megabytes +print(f"RAM used: {psutil.Process().memory_info().rss / (1024 * 1024):.2f} MB") +``` + +```python out +RAM used: 5678.33 MB +``` + +El atributo `rss` se refiere al _resident set size_, que es la fracción de memoria que un proceso ocupa en RAM. Esta medición también incluye la memoria usada por el intérprete de Python y las librerías que hemos cargado, así que la cantidad real de memoria usada para cargar el dataset es un poco más pequeña. A modo de comparación, veamos qué tan grande es el dataset en disco, usando el atributo `dataset_size`. Dado que el resultado está expresado en bytes, tenemos que convertirlo manualmente en gigabytes: + +```py +print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") +size_gb = pubmed_dataset.dataset_size / (1024**3) +print(f"Dataset size (cache file) : {size_gb:.2f} GB") +``` + +```python out +Number of files in dataset : 20979437051 +Dataset size (cache file) : 19.54 GB +``` + +Bien, a pesar de que el archivo es de casi 20 GB, ¡podemos cargarlo y acceder a su contenido con mucha menos RAM! + + + +✏️ **¡Inténtalo!** Escoge alguno de los [subconjuntos](https://mystic.the-eye.eu/public/AI/pile_preliminary_components/) del _Pile_ que sea más grande que la RAM de tu computador portátil o de escritorio, cárgalo con 🤗 Datasets y mide la cantidad de RAM utilizada. Recuerda que para tener una medición precisa, tienes que hacerlo en un nuevo proceso. Puedes encontrar los tamaños de cada uno de los subconjuntos sin comprimir en la Tabla 1 del [paper de _Pile_](https://arxiv.org/abs/2101.00027). + + + +Si estás familiarizado con Pandas, este resultado puede ser sorprendente por la famosa [regla de Wes Kinney](https://wesmckinney.com/blog/apache-arrow-pandas-internals/) que indica que típicamente necesitas de 5 a 10 veces la RAM que el tamaño del archivo de tu dataset. ¿Cómo resuelve entonces 🤗 Datasets este problema de manejo de memoria? 🤗 Datasets trata cada dataset como un [archivo proyectado en memoria](https://en.wikipedia.org/wiki/Memory-mapped_file), lo que permite un mapeo entre la RAM y el sistema de almacenamiento de archivos, que le permite a la librería acceder y operar los elementos del dataset sin necesidad de tenerlos cargados completamente en memoria. + +Los archivos proyectados en memoria también pueden ser compartidos por múltiples procesos, lo que habilita la paralelización de métodos como `Dataset.map()` sin que sea obligatorio mover o copiar el dataset. Internamente, estas capacidades se logran gracias al formato de memoria [Apache Arrow](https://arrow.apache.org) y la librería [`pyarrow`](https://arrow.apache.org/docs/python/index.html), que permiten la carga y procesamiento de datos a gran velocidad. (Para ahondar más en Apache Arrow y algunas comparaciones con Pandas, revisa el [blog de Dejan Simic](https://towardsdatascience.com/apache-arrow-read-dataframe-with-zero-memory-69634092b1a)). Para verlo en acción, ejecutemos un test de velocidad iterando sobre todos los elementos del dataset de abstracts de PubMed: + +```py +import timeit + +code_snippet = """batch_size = 1000 + +for idx in range(0, len(pubmed_dataset), batch_size): + _ = pubmed_dataset[idx:idx + batch_size] +""" + +time = timeit.timeit(stmt=code_snippet, number=1, globals=globals()) +print( + f"Iterated over {len(pubmed_dataset)} examples (about {size_gb:.1f} GB) in " + f"{time:.1f}s, i.e. {size_gb/time:.3f} GB/s" +) +``` + +```python out +'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: + + + +💡 En los cuadernos de Jupyter también puedes medir el tiempo de ejecución de las celdas usando [`%%timeit`](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit). + + + +## Haciendo _streaming_ de datasets + +Para habilitar el _streaming_ basta con pasar el argumento `streaming=True` a la función `load_dataset()`. Por ejemplo, carguemos el dataset de abstracts de PubMed de nuevo, pero en modo _streaming_. + +```py +pubmed_dataset_streamed = load_dataset( + "json", data_files=data_files, split="train", streaming=True +) +``` + +En vez del `Dataset` común y corriente que nos hemos encontrado en el resto del capítulo, el objeto devuelto con `streaming=True` es un `IterableDataset`. Como su nombre lo indica, para acceder a los elementos de un `IterableDataset` tenemos que iterar sobre él. Podemos acceder al primer elemento de nuestro dataset de la siguiente manera: + +```py +next(iter(pubmed_dataset_streamed)) +``` + +```python out +{'meta': {'pmid': 11409574, 'language': 'eng'}, + '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 ...'} +``` + +Los elementos de un dataset _streamed_ pueden ser procesados sobre la marcha usando `IterableDataset.map()`, lo que puede servirte si tienes que tokenizar los inputs. El proceso es exactamente el mismo que el que usamos para tokenizar nuestro dataset en el [Capítulo 3](/course/chapter3), con la única diferencia de que los outputs se devuelven uno por uno. + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased") +tokenized_dataset = pubmed_dataset_streamed.map(lambda x: tokenizer(x["text"])) +next(iter(tokenized_dataset)) +``` + +```python out +{'input_ids': [101, 4958, 5178, 4328, 6779, ...], 'attention_mask': [1, 1, 1, 1, 1, ...]} +``` + + + +💡 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`. + + + +También puedes aleatorizar el orden de un dataset _streamed_ usando `IterableDataset.shuffle()`, pero a diferencia de `Dataset.shuffle()` esto sólo afecta a los elementos en un `buffer_size` determinado: + +```py +shuffled_dataset = pubmed_dataset_streamed.shuffle(buffer_size=10_000, seed=42) +next(iter(shuffled_dataset)) +``` + +```python out +{'meta': {'pmid': 11410799, 'language': 'eng'}, + '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: + +```py +dataset_head = pubmed_dataset_streamed.take(5) +list(dataset_head) +``` + +```python out +[{'meta': {'pmid': 11409574, 'language': 'eng'}, + 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'}, + {'meta': {'pmid': 11409575, 'language': 'eng'}, + 'text': 'Clinical signs of hypoxaemia in children with acute lower respiratory infection: indicators of oxygen therapy ...'}, + {'meta': {'pmid': 11409576, 'language': 'eng'}, + 'text': "Hypoxaemia in children with severe pneumonia in Papua New Guinea ..."}, + {'meta': {'pmid': 11409577, 'language': 'eng'}, + 'text': 'Oxygen concentrators and cylinders ...'}, + {'meta': {'pmid': 11409578, 'language': 'eng'}, + '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í: + +```py +# Skip the first 1,000 examples and include the rest in the training set +train_dataset = shuffled_dataset.skip(1000) +# Take the first 1,000 examples for the validation set +validation_dataset = shuffled_dataset.take(1000) +``` + +Vamos a repasar la exploración del _streaming_ de datasets con una aplicación común: combinar múltiples datasets para crear un solo corpus. 🤗 Datasets provee una función `interleave_datasets()` que convierte una lista de objetos `IterableDataset` en un solo `IterableDataset`, donde la lista de elementos del nuevo dataset se obtiene al alternar entre los ejemplos originales. Esta función es particularmente útil cuando quieres combinar datasets grandes, así que como ejemplo hagamos _streaming_ del conjunto FreeLaw del _Pile_, que es un dataset de 51 GB con opiniones legales de las cortes en Estados Unidos. + +```py +law_dataset_streamed = load_dataset( + "json", + data_files="https://mystic.the-eye.eu/public/AI/pile_preliminary_components/FreeLaw_Opinions.jsonl.zst", + split="train", + streaming=True, +) +next(iter(law_dataset_streamed)) +``` + +```python out +{'meta': {'case_ID': '110921.json', + 'case_jurisdiction': 'scotus.tar.gz', + 'date_created': '2010-04-28T17:12:49Z'}, + 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'} +``` + +Este dataset es lo suficientemente grande como para llevar al límite la RAM de la mayoría de computadores portátiles. Sin embargo, ¡podemos cargarla y acceder a el sin esfuerzo! Ahora combinemos los ejemplos de FreeLaw y PubMed usando la función `interleave_datasets()`: + +```py +from itertools import islice +from datasets import interleave_datasets + +combined_dataset = interleave_datasets([pubmed_dataset_streamed, law_dataset_streamed]) +list(islice(combined_dataset, 2)) +``` + +```python out +[{'meta': {'pmid': 11409574, 'language': 'eng'}, + 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'}, + {'meta': {'case_ID': '110921.json', + 'case_jurisdiction': 'scotus.tar.gz', + 'date_created': '2010-04-28T17:12:49Z'}, + 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'}] +``` + +Usamos la función `islice()` del módulo `itertools` de Python para seleccionar los primeros dos ejemplos del dataset combinado y podemos ver que corresponden con los primeros dos ejemplos de cada uno de los dos datasets de origen. + +Finalmente, si quieres hacer _streaming_ del _Pile_ de 825 GB en su totalidad, puedes usar todos los archivos preparados de la siguiente manera: + +```py +base_url = "https://mystic.the-eye.eu/public/AI/pile/" +data_files = { + "train": [base_url + "train/" + f"{idx:02d}.jsonl.zst" for idx in range(30)], + "validation": base_url + "val.jsonl.zst", + "test": base_url + "test.jsonl.zst", +} +pile_dataset = load_dataset("json", data_files=data_files, streaming=True) +next(iter(pile_dataset["train"])) +``` + +```python out +{'meta': {'pile_set_name': 'Pile-CC'}, + 'text': 'It is done, and submitted. You can play “Survival of the Tastiest” on Android, and on the web...'} +``` + + + +✏️ **¡Inténtalo!** Usa alguno de los corpus grandes de Common Crawl como [`mc4`](https://huggingface.co/datasets/mc4) u [`oscar`](https://huggingface.co/datasets/oscar) para crear un dataset _streaming_ multilenguaje que represente las proporciones de lenguajes hablados en un país de tu elección. Por ejemplo, los 4 lenguajes nacionales en Suiza son alemán, francés, italiano y romanche, así que podrías crear un corpus suizo al hacer un muestreo de Oscar de acuerdo con su proporción de lenguaje. + + + +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 new file mode 100644 index 000000000..3d3cd21c2 --- /dev/null +++ b/chapters/es/chapter5/5.mdx @@ -0,0 +1,466 @@ +# Crea tu propio dataset + + + +Algunas veces el dataset que necesitas para crear una aplicación de procesamiento de lenguaje natural no existe, así que necesitas crearla. En esta sección vamos a mostrarte cómo crear un corpus de [issues de GitHub](https://github.com/features/issues/), que se usan comúnmente para rastrear bugs o features en repositorios de GitHub. Este corpus podría ser usado para varios propósitos como: + +* Explorar qué tanto se demora el cierre un issue abierto o un pull request +* 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. + +## Obteniendo los datos + +Puedes encontrar todos los issues de 🤗 Datasets yendo a la [pestaña de issues](https://github.com/huggingface/datasets/issues) del repositorio. Como se puede ver en la siguiente captura de pantalla, al momento de escribir esta sección habían 331 issues abiertos y 668 cerrados. + +
+The GitHub issues associated with 🤗 Datasets. +
+ +Si haces clic en alguno de estos issues te encontrarás con que incluyen un título, una descripción y un conjunto de etiquetas que lo caracterizan. Un ejemplo de esto se muestra en la siguiente captura de pantalla. + +
+A typical GitHub issue in the 🤗 Datasets repository. +
+ +Para descargar todos los issues del repositorio, usaremos el [API REST de GitHub](https://docs.github.com/en/rest) para obtener el [endpoint `Issues`](https://docs.github.com/en/rest/reference/issues#list-repository-issues). Este endpoint devuelve una lista de objetos JSON, en la que cada objeto contiene un gran número de campos que incluyen el título y la descripción, así como metadatos sobre el estado del issue, entre otros. + +Una forma conveniente de descargar los issues es a través de la librería `requests`, que es la manera estándar para hacer pedidos HTTP en Python. Puedes instalar esta librería instalando: + +```python +!pip install requests +``` + +Una vez la librería está instalada, puedes hacer pedidos GET al endpoint `Issues` ejecutando la función `requests.get()`. Por ejemplo, puedes correr el siguiente comando para obtener el primer issue de la primera página: + +```py +import requests + +url = "https://api.github.com/repos/huggingface/datasets/issues?page=1&per_page=1" +response = requests.get(url) +``` + +El objeto `response` contiene una gran cantidad de información útil sobre el pedido, incluyendo el código de status de HTTP: + +```py +response.status_code +``` + +```python out +200 +``` + +en el que un código de `200` significa que el pedido fue exitoso (puedes ver una lista de posibles códigos de status de HTTP [aquí](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes)). No obstante, en lo que estamos interesados realmente es el _payload_, que se puede acceder en varios formatos como bytes, strings o JSON. Como ya sabemos que los issues están en formato JSON, inspeccionemos el _payload_ de la siguiente manera: + +```py +response.json() +``` + +```python out +[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/2792', + 'repository_url': 'https://api.github.com/repos/huggingface/datasets', + 'labels_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/labels{/name}', + 'comments_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/comments', + 'events_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/events', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792', + 'id': 968650274, + 'node_id': 'MDExOlB1bGxSZXF1ZXN0NzEwNzUyMjc0', + 'number': 2792, + 'title': 'Update GooAQ', + 'user': {'login': 'bhavitvyamalik', + 'id': 19718818, + 'node_id': 'MDQ6VXNlcjE5NzE4ODE4', + 'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4', + 'gravatar_id': '', + 'url': 'https://api.github.com/users/bhavitvyamalik', + 'html_url': 'https://github.com/bhavitvyamalik', + 'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers', + 'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}', + 'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}', + 'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}', + 'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions', + 'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs', + 'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos', + 'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}', + 'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events', + 'type': 'User', + 'site_admin': False}, + 'labels': [], + 'state': 'open', + 'locked': False, + 'assignee': None, + 'assignees': [], + 'milestone': None, + 'comments': 1, + 'created_at': '2021-08-12T11:40:18Z', + 'updated_at': '2021-08-12T12:31:17Z', + 'closed_at': None, + 'author_association': 'CONTRIBUTOR', + 'active_lock_reason': None, + 'pull_request': {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/2792', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792', + 'diff_url': 'https://github.com/huggingface/datasets/pull/2792.diff', + 'patch_url': 'https://github.com/huggingface/datasets/pull/2792.patch'}, + 'body': '[GooAQ](https://github.com/allenai/gooaq) dataset was recently updated after splits were added for the same. This PR contains new updated GooAQ with train/val/test splits and updated README as well.', + 'performed_via_github_app': None}] +``` + +Wow, ¡es mucha información! Podemos ver campos útiles como `title`, `body` y `number`, que describen el issue, así como información del usuario de GitHub que lo abrió. + + + +✏️ **¡Inténtalo!** Haz clic en algunas de las URL en el _payload_ JSON de arriba para explorar la información que está enlazada al issue de GitHub. + + + +Tal como se describe en la [documentación](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting) de GitHub, los pedidos sin autenticación están limitados a 60 por hora. Si bien puedes incrementar el parámetro de búsqueda `per_page` para reducir el número de pedidos que haces, igual puedes alcanzar el límite de pedidos en cualquier repositorio que tenga más que un par de miles de issues. En vez de hacer eso, puedes seguir las [instrucciones](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) de GitHub para crear un _token de acceso personal_ y que puedas incrementar el límite de pedidos a 5.000 por hora. Una vez tengas tu token, puedes incluirlo como parte del encabezado del pedido: + +```py +GITHUB_TOKEN = xxx # Copy your GitHub token here +headers = {"Authorization": f"token {GITHUB_TOKEN}"} +``` + + + +⚠️ No compartas un cuaderno que contenga tu `GITHUB_TOKEN`. Te recomendamos eliminar la última celda una vez la has ejecutado para evitar filtrar accidentalmente esta información. Aún mejor, guarda el token en un archivo *.env* y usa la librería [`python-dotenv`](https://github.com/theskumar/python-dotenv) para cargarla automáticamente como una variable de ambiente. + + + +Ahora que tenemos nuestro token de acceso, creemos una función que descargue todos los issues de un repositorio de GitHub: + +```py +import time +import math +from pathlib import Path +import pandas as pd +from tqdm.notebook import tqdm + + +def fetch_issues( + owner="huggingface", + repo="datasets", + num_issues=10_000, + rate_limit=5_000, + issues_path=Path("."), +): + if not issues_path.is_dir(): + issues_path.mkdir(exist_ok=True) + + batch = [] + all_issues = [] + per_page = 100 # Número de issues por página + num_pages = math.ceil(num_issues / per_page) + base_url = "https://api.github.com/repos" + + for page in tqdm(range(num_pages)): + # Query con state=all para obtener tanto issues abiertos como cerrados + query = f"issues?page={page}&per_page={per_page}&state=all" + issues = requests.get(f"{base_url}/{owner}/{repo}/{query}", headers=headers) + batch.extend(issues.json()) + + if len(batch) > rate_limit and len(all_issues) < num_issues: + all_issues.extend(batch) + batch = [] # Vacía el batch para el siguiente periodo de tiempo + print(f"Reached GitHub rate limit. Sleeping for one hour ...") + time.sleep(60 * 60 + 1) + + all_issues.extend(batch) + df = pd.DataFrame.from_records(all_issues) + df.to_json(f"{issues_path}/{repo}-issues.jsonl", orient="records", lines=True) + print( + f"Downloaded all the issues for {repo}! Dataset stored at {issues_path}/{repo}-issues.jsonl" + ) +``` + +Cuando ejecutemos `fetch_issues()`, se descargarán todos los issues en lotes para evitar exceder el límite de GitHub sobre el número de pedidos por hora. El resultado se guardará en un archivo _repository_name-issues.jsonl_, donde cada línea es un objeto JSON que representa un issue. Usemos esta función para cargar todos los issues de 🤗 Datasets: + +```py +# Dependiendo de tu conexión a internet, esto puede tomar varios minutos para ejecutarse... +fetch_issues() +``` + +Una vez los issues estén descargados, los podemos cargar localmente usando las habilidades aprendidas en la [sección 2](/course/chaper5/2): + +```py +issues_dataset = load_dataset("json", data_files="datasets-issues.jsonl", split="train") +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'timeline_url', 'performed_via_github_app'], + num_rows: 3019 +}) +``` + +¡Genial! Hemos creado nuestro primer dataset desde cero. Pero, ¿por qué hay varios miles de issues cuando la [pestaña de Issues](https://github.com/huggingface/datasets/issues) del repositorio de 🤗 Datasets sólo muestra alrededor de 1.000 en total? Como se describe en la [documentación](https://docs.github.com/en/rest/reference/issues#list-issues-assigned-to-the-authenticated-user), esto sucede porque también descargamos todos los pull requests: + +> GitHub's REST API v3 considers every pull request an issue, but not every issue is a pull request. For this reason, "Issues" endpoints may return both issues and pull requests in the response. You can identify pull requests by the `pull_request` key. Be aware that the `id` of a pull request returned from "Issues" endpoints will be an issue id. + +Como el contenido de los issues y pull requests son diferentes, hagamos un preprocesamiento simple para distinguirlos entre sí. + +## Limpiando los datos + +El fragmento anterior de la documentación de GitHub nos dice que la columna `pull_request` puede usarse para diferenciar los issues de los pull requests. Veamos una muestra aleatoria para ver la diferencia. Como hicimos en la [sección 3](/course/chapter5/3), vamos a encadenar `Dataset.shuffle()` y `Dataset.select()` para crear una muestra aleatoria y luego unir las columnas de `html_url` y `pull_request` para comparar las distintas URL: + +```py +sample = issues_dataset.shuffle(seed=666).select(range(3)) + +# Imprime la URL y las entradas de pull_request +for url, pr in zip(sample["html_url"], sample["pull_request"]): + print(f">> URL: {url}") + print(f">> Pull request: {pr}\n") +``` + +```python out +>> URL: https://github.com/huggingface/datasets/pull/850 +>> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/850', 'html_url': 'https://github.com/huggingface/datasets/pull/850', 'diff_url': 'https://github.com/huggingface/datasets/pull/850.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/850.patch'} + +>> URL: https://github.com/huggingface/datasets/issues/2773 +>> Pull request: None + +>> URL: https://github.com/huggingface/datasets/pull/783 +>> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/783', 'html_url': 'https://github.com/huggingface/datasets/pull/783', 'diff_url': 'https://github.com/huggingface/datasets/pull/783.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/783.patch'} +``` + +Podemos ver que cada pull request está asociado con varias URL, mientras que los issues ordinarios tienen una entrada `None`. Podemos usar esta distinción para crear una nueva columna `is_pull_request` que revisa si el campo `pull_request` es `None` o no: + +```py +issues_dataset = issues_dataset.map( + lambda x: {"is_pull_request": False if x["pull_request"] is None else True} +) +``` + + + +✏️ **¡Inténtalo!** Calcula el tiempo promedio que toma cerrar issues en 🤗 Datasets. La función `Dataset.filter()` te será útil para filtrar los pull requests y los issues abiertos, y puedes usar la función `Dataset.set_format()` para convertir el dataset a un `DataFrame` para poder manipular fácilmente los timestamps de `created_at` y `closed_at`. Para puntos extra, calcula el tiempo promedio que toma cerrar pull requests. + + + +Si bien podemos limpiar aún más el dataset eliminando o renombrando algunas columnas, es una buena práctica mantener un dataset lo más parecido al original en esta etapa, para que se pueda usar fácilmente en varias aplicaciones. + +Antes de subir el dataset el Hub de Hugging Face, nos hace falta añadirle algo más: los comentarios asociados con cada issue y pull request. Los vamos a añadir con el API REST de GitHub. + +## Ampliando el dataset + +Como se muestra en la siguiente captura de pantalla, los comentarios asociados con un issue o un pull request son una fuente rica de información, especialmente si estamos interesados en construir un motor de búsqueda para responder preguntas de usuarios sobre la librería. + +
+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: + +```py +issue_number = 2792 +url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments" +response = requests.get(url, headers=headers) +response.json() +``` + +```python out +[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/comments/897594128', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792#issuecomment-897594128', + 'issue_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792', + 'id': 897594128, + 'node_id': 'IC_kwDODunzps41gDMQ', + 'user': {'login': 'bhavitvyamalik', + 'id': 19718818, + 'node_id': 'MDQ6VXNlcjE5NzE4ODE4', + 'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4', + 'gravatar_id': '', + 'url': 'https://api.github.com/users/bhavitvyamalik', + 'html_url': 'https://github.com/bhavitvyamalik', + 'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers', + 'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}', + 'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}', + 'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}', + 'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions', + 'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs', + 'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos', + 'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}', + 'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events', + 'type': 'User', + 'site_admin': False}, + 'created_at': '2021-08-12T12:21:52Z', + 'updated_at': '2021-08-12T12:31:17Z', + 'author_association': 'CONTRIBUTOR', + 'body': "@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?", + 'performed_via_github_app': None}] +``` + +Podemos ver que el comentario está almacenado en el campo `body`, así que escribamos una función simple que devuelva todos los comentarios asociados con un issue al extraer el contenido de `body` para cada elemento en el `response.json()`: + +```py +def get_comments(issue_number): + url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments" + response = requests.get(url, headers=headers) + return [r["body"] for r in response.json()] + + +# Revisar que el comportamiento de nuestra función es el esperado +get_comments(2792) +``` + +```python out +["@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?"] +``` + +Esto luce bien, así que usemos `Dataset.map()` para añadir una nueva columna `comments` a cada issue en el dataset: + +```py +# Dependiendo de tu conexión a internet, esto puede tomar varios minutos... +issues_with_comments_dataset = issues_dataset.map( + lambda x: {"comments": get_comments(x["number"])} +) +``` + +El último paso es guardar el dataset ampliado en el mismo lugar que los datos originales para poderlos subir al Hub: + +```py +issues_with_comments_dataset.to_json("issues-datasets-with-comments.jsonl") +``` + +## Subiendo un dataset al Hub de Hugging Face + + + +Ahora que tenemos nuestro dataset ampliado, es momento de subirlo al Hub para poder compartirlo con la comunidad. Para subir el dataset tenemos que usar la [librería 🤗 Hub](https://github.com/huggingface/huggingface_hub), que nos permite interactuar con el Hub de Hugging Face usando una API de Python. 🤗 Hub viene instalada con 🤗 Transformers, así que podemos usarla directamente. Por ejemplo, podemos usar la función `list_datasets()` para obtener información sobre todos los datasets públicos que están almacenados en el Hub: + +```py +from huggingface_hub import list_datasets + +all_datasets = list_datasets() +print(f"Number of datasets on Hub: {len(all_datasets)}") +print(all_datasets[0]) +``` + +```python out +Number of datasets on Hub: 1487 +Dataset Name: acronym_identification, Tags: ['annotations_creators:expert-generated', 'language_creators:found', 'languages:en', 'licenses:mit', 'multilinguality:monolingual', 'size_categories:10K + +✏️ **¡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. + + + +Ahora clonemos el repositorio del Hub a nuestra máquina local y copiemos nuestro dataset ahí. 🤗 Hub incluye una clase `Repositorio` que envuelve muchos de los comandos comunes de Git, así que para clonar el repositorio remoto solamente necesitamos dar la URL y la ruta local en la que lo queremos clonar: + +```py +from huggingface_hub import Repository + +repo = Repository(local_dir="github-issues", clone_from=repo_url) +!cp issues-datasets-with-comments.jsonl github-issues/ +``` + +Por defecto, varias extensiones de archivo (como *.bin*, *.gz*, and *.zip*) se siguen con Git LFS de tal manera que los archivos grandes se pueden versionar dentro del mismo flujo de trabajo de Git. Puedes encontrar una lista de extensiones que se van a seguir en el archivo *.gitattributes*. Para incluir el formato JSON Lines en la lista, puedes ejecutar el siguiente comando: + +```py +repo.lfs_track("*.jsonl") +``` + +Luego, podemos usar `$$Repository.push_to_hub()` para subir el dataset al Hub: + +```py +repo.push_to_hub() +``` + +Si navegamos a la URL que aparece en `repo_url`, deberíamos ver que el archivo del dataset se ha subido. + +
+Our dataset repository on the Hugging Face Hub. +
+ +Desde aqui, cualquier persona podrá descargar el dataset incluyendo el ID del repositorio en el argumento `path` de la función `load_dataset()`: + +```py +remote_dataset = load_dataset("lewtun/github-issues", split="train") +remote_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 2855 +}) +``` + +¡Genial, hemos subido el dataset al Hub y ya está disponible para que otras personas lo usen! Sólo hay una cosa restante por hacer: añadir una _tarjeta del dataset_ (_dataset card_) que explique cómo se creó el corpus y provea información útil para la comunidad. + + + +💡 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. + + + +## Creando una tarjeta del dataset + +Los datasets bien documentados tienen más probabilidades de ser útiles para otros (incluyéndote a ti en el futuro), dado que brindan la información necesaria para que los usuarios decidan si el dataset es útil para su tarea, así como para evaluar cualquier sesgo o riesgo potencial asociado a su uso. + +En el Hub de Hugging Face, esta información se almacena en el archivo *README.md* del repositorio del dataset. Hay dos pasos que deberías hacer antes de crear este archivo: + +1. Usa la [aplicación `datasets-tagging`](https://huggingface.co/datasets/tagging/) para crear etiquetas de metadatos en el formato YAML. Estas etiquetas se usan para una variedad de funciones de búsqueda en el Hub de Hugging Face y aseguran que otros miembros de la comunidad puedan encontrar tu dataset. Dado que creamos un dataset personalizado en esta sección, tendremos que clonar el repositorio `datasets-tagging` y correr la aplicación localmente. Así se ve la interfaz de la aplicación: + +
+The `datasets-tagging` interface. +
+ +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: + +
+A dataset card. +
+ + + +✏️ **¡Inténtalo!** Usa la aplicación `dataset-tagging` y la [guía de 🤗 Datasets](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) para completar el archivo *README.md* para tu dataset de issues de GitHub. + + + +¡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. + + + +✏️ **¡Inténtalo!** Sigue los pasos descritos en esta sección para crear un dataset de issues de GitHub de tu librería de código abierto favorita (¡por supuesto, escoge algo distinto a 🤗 Datasets!). Para puntos extra, ajusta un clasificador de etiquetas múltiples para predecir las etiquetas presentes en el campo `labels`. + + diff --git a/chapters/es/chapter5/6.mdx b/chapters/es/chapter5/6.mdx new file mode 100644 index 000000000..2c4cbd13f --- /dev/null +++ b/chapters/es/chapter5/6.mdx @@ -0,0 +1,528 @@ + + +# Búsqueda semántica con FAISS + +{#if fw === 'pt'} + + + +{:else} + + + +{/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. + + + +## Usando _embeddings_ para la búsqueda semántica + +Como vimos en el [Capítulo 1](/course/chapter1), los modelos de lenguaje basados en Transformers representan cada token en un texto como un _vector de embeddings_. Resulta que podemos agrupar los _embeddings_ individuales en representaciones vectoriales para oraciones, párrafos o (en algunos casos) documentos completos. Estos _embeddings_ pueden ser usados para encontrar documentos similares en el corpus al calcular la similaridad del producto punto (o alguna otra métrica de similaridad) entre cada _embedding_ y devolver los documentos con la mayor coincidencia. + +En esta sección vamos a usar _embeddings_ para desarrollar un motor de búsqueda semántica. Estos motores de búsqueda tienen varias ventajas sobre abordajes convencionales basados en la coincidencia de palabras clave en una búsqueda con los documentos. + +
+Semantic search. + +
+ +## Cargando y preparando el dataset + +Lo primero que tenemos que hacer es descargar el dataset de issues de GitHub, así que usaremos la librería 🤗 Hub para resolver la URL en la que está almacenado nuestro archivo en el Hub de Hugging Face: + +```py +from huggingface_hub import hf_hub_url + +data_files = hf_hub_url( + repo_id="lewtun/github-issues", + filename="datasets-issues-with-comments.jsonl", + repo_type="dataset", +) +``` + +Con la URL almacenada en `data_files`, podemos cargar el dataset remoto usando el método introducido en la [sección 2](/course/chapter5/2): + +```py +from datasets import load_dataset + +issues_dataset = load_dataset("json", data_files=data_files, split="train") +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 2855 +}) +``` + +Hemos especificado el conjunto `train` por defecto en `load_dataset()`, de tal manera que devuelva un objeto `Dataset` en vez de un `DatasetDict`. Lo primero que debemos hacer es filtrar los pull requests, dado que estos no se suelen usar para resolver preguntas de usuarios e introducirán ruido en nuestro motor de búsqueda. Como ya debe ser familiar para ti, podemos usar la función `Dataset.filter()` para excluir estas filas en nuestro dataset. A su vez, filtremos las filas que no tienen comentarios, dado que no van a darnos respuestas para las preguntas de los usuarios. + +```py +issues_dataset = issues_dataset.filter( + lambda x: (x["is_pull_request"] == False and len(x["comments"]) > 0) +) +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 771 +}) +``` + +Podemos ver que hay un gran número de columnas en nuestro dataset, muchas de las cuales no necesitamos para construir nuestro motor de búsqueda. Desde la perspectiva de la búsqueda, las columnas más informativas son `title`, `body` y `comments`, mientras que `html_url` nos indica un link al issue correspondiente. Usemos la función `Dataset.remove_columns()` para eliminar el resto: + +```py +columns = issues_dataset.column_names +columns_to_keep = ["title", "body", "html_url", "comments"] +columns_to_remove = set(columns_to_keep).symmetric_difference(columns) +issues_dataset = issues_dataset.remove_columns(columns_to_remove) +issues_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body'], + num_rows: 771 +}) +``` + +Para crear nuestros _embeddings_, vamos a ampliar cada comentario añadiéndole el título y el cuerpo del issue, dado que estos campos suelen incluir información de contexto útil. Dado que nuestra función `comments` es una lista de comentarios para cada issue, necesitamos "explotar" la columna para que cada fila sea una tupla `(html_url, title, body, comment)`. Podemos hacer esto en Pandas con la [función `DataFrame.explode()`](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html), que crea una nueva fila para cada elemento en una columna que está en forma de lista, al tiempo que replica el resto de los valores de las otras columnas. Para verlo en acción, primero debemos cambiar al formato `DataFrame` de Pandas: + +```py +issues_dataset.set_format("pandas") +df = issues_dataset[:] +``` + +Si inspeccionamos la primera fila en este `DataFrame` podemos ver que hay 4 comentarios asociados con este issue: + +```py +df["comments"][0].tolist() +``` + +```python out +['the bug code locate in :\r\n if data_args.task_name is not None:\r\n # Downloading and loading a dataset from the hub.\r\n datasets = load_dataset("glue", data_args.task_name, cache_dir=model_args.cache_dir)', + 'Hi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com\r\n\r\nNormally, it should work if you wait a little and then retry.\r\n\r\nCould you please confirm if the problem persists?', + 'cannot connect,even by Web browser,please check that there is some problems。', + 'I can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...'] +``` + +Cuando "explotamos" `df`, queremos obtener una fila para cada uno de estos comentarios. Veamos si este es el caso: + +```py +comments_df = df.explode("comments", ignore_index=True) +comments_df.head(4) +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
html_urltitlecommentsbody
0https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comthe bug code locate in :\r\n if data_args.task_name is not None...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
1https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comHi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
2https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comcannot connect,even by Web browser,please check that there is some problems。Hello,\r\nI am trying to run run_glue.py and it gives me this error...
3https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comI can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
+ +Genial, podemos ver que las filas se han replicado y que la columna `comments` incluye los comentarios individuales. Ahora que hemos terminado con Pandas, podemos volver a cambiar el formato a `Dataset` cargando el `DataFrame` en memoria: + +```py +from datasets import Dataset + +comments_dataset = Dataset.from_pandas(comments_df) +comments_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body'], + num_rows: 2842 +}) +``` + +¡Esto nos ha dado varios miles de comentarios con los que trabajar! + + + +✏️ **¡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. + + + +Ahora que tenemos un comentario para cada fila, creemos una columna `comments_length` que contenga el número de palabras por comentario: + +```py +comments_dataset = comments_dataset.map( + lambda x: {"comment_length": len(x["comments"].split())} +) +``` + +Podemos usar esta nueva columna para filtrar los comentarios cortos, que típicamente incluyen cosas como "cc @letwun" o "¡Gracias!", que no son relevantes para nuestro motor de búsqueda. No hay un número preciso que debamos filtrar, pero alrededor de 15 palabras es un buen comienzo: + +```py +comments_dataset = comments_dataset.filter(lambda x: x["comment_length"] > 15) +comments_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body', 'comment_length'], + num_rows: 2098 +}) +``` + +Ahora que hemos limpiado un poco el dataset, vamos a concatenar el título, la descripción y los comentarios del issue en una nueva columna `text`. Como lo hemos venido haciendo, escribiremos una función para pasarla a `Dataset.map()`: + +```py +def concatenate_text(examples): + return { + "text": examples["title"] + + " \n " + + examples["body"] + + " \n " + + examples["comments"] + } + + +comments_dataset = comments_dataset.map(concatenate_text) +``` + +¡Por fin estamos listos para crear _embeddings_! + +## Creando _embeddings_ de texto + +En el [Capítulo 2](/course/chapter2) vimos que podemos obtener _embeddings_ usando la clase `AutoModel`. Todo lo que tenemos que hacer es escoger un punto de control adecuado para cargar el modelo. Afortunadamente, existe una librería llamada `sentence-transformers` que se especializa en crear _embeddings_. Como se describe en la [documentación](https://www.sbert.net/examples/applications/semantic-search/README.html#symmetric-vs-asymmetric-semantic-search) de esta librería, nuestro caso de uso es un ejemplo de _búsqueda semántica asimétrica_ porque tenemos una pregunta corta cuya respuesta queremos encontrar en un documento más grande, como un comentario de un issue. La tabla de [resumen de modelos](https://www.sbert.net/docs/pretrained_models.html#model-overview) en la documentación nos indica que el punto de control `multi-qa-mpnet-base-dot-v1` tiene el mejor desempeño para la búsqueda semántica, así que lo usaremos para nuestra aplicación. También cargaremos el tokenizador usando el mismo punto de control: + +{#if fw === 'pt'} + +```py +from transformers import AutoTokenizer, AutoModel + +model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1" +tokenizer = AutoTokenizer.from_pretrained(model_ckpt) +model = AutoModel.from_pretrained(model_ckpt) +``` + +Para acelerar el proceso de _embedding_, es útil ubicar el modelo y los inputs en un dispositivo GPU, así que hagámoslo: + +```py +import torch + +device = torch.device("cuda") +model.to(device) +``` + +{:else} + +```py +from transformers import AutoTokenizer, TFAutoModel + +model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1" +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! + +{/if} + +Como mencionamos con anterioridad, queremos representar cada entrada en el corpus de issues de GitHub como un vector individual, así que necesitamos agrupar o promediar nuestros _embeddings_ de tokes de alguna manera. Un abordaje popular es ejecutar *CLS pooling* en los outputs de nuestro modelo, donde simplemente vamos a recolectar el último estado oculto para el token especial `[CLS]`. La siguiente función nos ayudará con esto: + +```py +def cls_pooling(model_output): + return model_output.last_hidden_state[:, 0] +``` + +Ahora crearemos una función que va a tokenizar una lista de documentos, ubicar los tensores en la GPU, alimentarlos al modelo y aplicar CLS pooling a los outputs: + +{#if fw === 'pt'} + +```py +def get_embeddings(text_list): + encoded_input = tokenizer( + text_list, padding=True, truncation=True, return_tensors="pt" + ) + encoded_input = {k: v.to(device) for k, v in encoded_input.items()} + model_output = model(**encoded_input) + return cls_pooling(model_output) +``` + +Podemos probar que la función sirve al pasarle la primera entrada de texto en el corpus e inspeccionando la forma de la salida: + +```py +embedding = get_embeddings(comments_dataset["text"][0]) +embedding.shape +``` + +```python out +torch.Size([1, 768]) +``` + +¡Hemos convertido la primera entrada del corpus en un vector de 768 dimensiones! Ahora podemos usar `Dataset.map()` para aplicar nuestra función `get_embeddings()` a cada fila del corpus, así que creemos una columna `embeddings` así: + +```py +embeddings_dataset = comments_dataset.map( + lambda x: {"embeddings": get_embeddings(x["text"]).detach().cpu().numpy()[0]} +) +``` + +{:else} + +```py +def get_embeddings(text_list): + encoded_input = tokenizer( + text_list, padding=True, truncation=True, return_tensors="tf" + ) + encoded_input = {k: v for k, v in encoded_input.items()} + model_output = model(**encoded_input) + return cls_pooling(model_output) +``` + +Podemos probar que la función sirve al pasarle la primera entrada de texto en el corpus e inspeccionando la forma de la salida: + +```py +embedding = get_embeddings(comments_dataset["text"][0]) +embedding.shape +``` + +```python out +TensorShape([1, 768]) +``` + +¡Hemos convertido la primera entrada del corpus en un vector de 768 dimensiones! Ahora podemos usar `Dataset.map()` para aplicar nuestra función `get_embeddings()` a cada fila del corpus, así que creemos una columna `embeddings` así: + +```py +embeddings_dataset = comments_dataset.map( + lambda x: {"embeddings": get_embeddings(x["text"]).numpy()[0]} +) +``` + +{/if} + +Los _embeddings_ se han convertido en arrays de NumPy, esto es porque 🤗 Datasets los necesita en este formato cuando queremos indexarlos con FAISS, que es lo que haremos a continuación. + +## Usando FAISS para una búsqueda eficiente por similaridad + +Ahora que tenemos un dataset de embeddings, necesitamos una manera de buscar sobre ellos. Para hacerlo, usaremos una estructura especial de datos en 🤗 Datasets llamada _índice FAISS_. [FAISS] (https://faiss.ai/) (siglas para _Facebook AI Similarity Search_) es una librería que contiene algoritmos eficientes para buscar y agrupar rápidamente vectores de _embeddings_. + +La idea básica detrás de FAISS es que crea una estructura especial de datos, llamada _índice_, que te permite encontrar cuáles embeddings son parecidos a un _embedding_ de entrada. La creación de un índice FAISS en 🤗 Datasets es muy simple: usamos la función `Dataset.add_faiss_index()` y especificamos cuál columna del dataset queremos indexar: + +```py +embeddings_dataset.add_faiss_index(column="embeddings") +``` + +Ahora podemos hacer búsquedas sobre este índice al hacer una búsqueda del vecino más cercano con la función `Dataset.get_nearest_examples()`. Probémoslo al hacer el _embedding_ de una pregunta de la siguiente manera: + +{#if fw === 'pt'} + +```py +question = "How can I load a dataset offline?" +question_embedding = get_embeddings([question]).cpu().detach().numpy() +question_embedding.shape +``` + +```python out +torch.Size([1, 768]) +``` + +{:else} + +```py +question = "How can I load a dataset offline?" +question_embedding = get_embeddings([question]).numpy() +question_embedding.shape +``` + +```python out +(1, 768) +``` + +{/if} + +Tal como en los documentos, ahora tenemos un vector de 768 dimensiones que representa la pregunta, que podemos comparar con el corpus entero para encontrar los _embeddings_ más parecidos: + +```py +scores, samples = embeddings_dataset.get_nearest_examples( + "embeddings", question_embedding, k=5 +) +``` + +La función `Dataset.get_nearest_examples()` devuelve una tupla de puntajes que calcula un ranking de la coincidencia entre la pregunta y el documento, así como un conjunto correspondiente de muestras (en este caso, los 5 mejores resultados). Recojámoslos en un `pandas.DataFrame` para ordenarlos fácilmente: + +```py +import pandas as pd + +samples_df = pd.DataFrame.from_dict(samples) +samples_df["scores"] = scores +samples_df.sort_values("scores", ascending=False, inplace=True) +``` + +Podemos iterar sobre las primeras filas para ver qué tanto coincide la pregunta con los comentarios disponibles: + +```py +for _, row in samples_df.iterrows(): + print(f"COMMENT: {row.comments}") + print(f"SCORE: {row.scores}") + print(f"TITLE: {row.title}") + print(f"URL: {row.html_url}") + print("=" * 50) + print() +``` + +```python out +""" +COMMENT: Requiring online connection is a deal breaker in some cases unfortunately so it'd be great if offline mode is added similar to how `transformers` loads models offline fine. + +@mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like? +SCORE: 25.505046844482422 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: The local dataset builders (csv, text , json and pandas) are now part of the `datasets` package since #1726 :) +You can now use them offline +\`\`\`python +datasets = load_dataset("text", data_files=data_files) +\`\`\` + +We'll do a new release soon +SCORE: 24.555509567260742 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: I opened a PR that allows to reload modules that have already been loaded once even if there's no internet. + +Let me know if you know other ways that can make the offline mode experience better. I'd be happy to add them :) + +I already note the "freeze" modules option, to prevent local modules updates. It would be a cool feature. + +---------- + +> @mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like? + +Indeed `load_dataset` allows to load remote dataset script (squad, glue, etc.) but also you own local ones. +For example if you have a dataset script at `./my_dataset/my_dataset.py` then you can do +\`\`\`python +load_dataset("./my_dataset") +\`\`\` +and the dataset script will generate your dataset once and for all. + +---------- + +About I'm looking into having `csv`, `json`, `text`, `pandas` dataset builders already included in the `datasets` package, so that they are available offline by default, as opposed to the other datasets that require the script to be downloaded. +cf #1724 +SCORE: 24.14896583557129 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: > here is my way to load a dataset offline, but it **requires** an online machine +> +> 1. (online machine) +> +> ``` +> +> import datasets +> +> data = datasets.load_dataset(...) +> +> data.save_to_disk(/YOUR/DATASET/DIR) +> +> ``` +> +> 2. copy the dir from online to the offline machine +> +> 3. (offline machine) +> +> ``` +> +> import datasets +> +> data = datasets.load_from_disk(/SAVED/DATA/DIR) +> +> ``` +> +> +> +> HTH. + + +SCORE: 22.893993377685547 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: here is my way to load a dataset offline, but it **requires** an online machine +1. (online machine) +\`\`\` +import datasets +data = datasets.load_dataset(...) +data.save_to_disk(/YOUR/DATASET/DIR) +\`\`\` +2. copy the dir from online to the offline machine +3. (offline machine) +\`\`\` +import datasets +data = datasets.load_from_disk(/SAVED/DATA/DIR) +\`\`\` + +HTH. +SCORE: 22.406635284423828 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== +""" +``` + +¡No está mal! El segundo comentario parece responder la pregunta. + + + +✏️ **¡Inténtalo!** Crea tu propia pregunta y prueba si puedes encontrar una respuesta en los documentos devueltos. Puede que tengas que incrementar el parámetro `k` en `Dataset.get_nearest_examples()` para aumentar la búsqueda. + + \ No newline at end of file diff --git a/chapters/es/chapter5/7.mdx b/chapters/es/chapter5/7.mdx new file mode 100644 index 000000000..1b13a989b --- /dev/null +++ b/chapters/es/chapter5/7.mdx @@ -0,0 +1,16 @@ +# 🤗 Datasets, ¡listo! + + + +Bueno, ese fue un gran tour de la librería 🤗 Datasets. ¡Felicitaciones por llegar hasta aquí! Con el conocimiento que adquiriste en este capítulo, deberías ser capaz de: + +- Cargar datasets de cualquier parte, sea del Hub de Hugging Face, tu computador o un servidor remoto en tu compañía. +- Preparar tus datos usando una combinación de las funciones `Dataset.map()` y `Dataset.filter()`. +- Cambiar rápidamente entre formatos de datos como Pandas y NumPy usando `Dataset.set_format()`. +- Crear tu propio dataset y subirlo al Hub de Hugging Face. +- Procesar tus documentos usando un modelo de Transformer y construir un motor de búsqueda semántica usando FAISS. + +En el [Capítulo 7](/course/chapter7) pondremos todo esto en práctica cuando veamos a profundidad las tareas de PLN en las que son buenos los modelos de Transformers. Antes de seguir, ¡es hora de poner a prueba tu conocimiento de 🤗 Datasets con un quiz! diff --git a/chapters/es/chapter5/8.mdx b/chapters/es/chapter5/8.mdx new file mode 100644 index 000000000..4718d8faf --- /dev/null +++ b/chapters/es/chapter5/8.mdx @@ -0,0 +1,231 @@ + + +# Quiz + + + +¡Vimos muchas cosas en este capítulo! No te preocupes si no te quedaron claros todos los detalles; los siguientes capítulos te ayudarán a entender cómo funcionan las cosas internamente. + +Antes de seguir, probemos lo que aprendiste en este capítulo: + +### 1. ¿Desde qué ubicaciones te permite cargar datasets la función `load_dataset()` en 🤗 Datasets? + +data_files de load_dataset() para cargar datasets locales.", + correct: true + }, + { + text: "El Hub de Hugging Face", + explain: "¡Correcto! Puedes cargar datasets del Hub pasando el ID del dataset, e.g. load_dataset('emotion').", + correct: true + }, + { + text: "Un servidor remoto", + explain: "¡Correcto! Puedes pasar URL al argumento data_files de la función load_dataset() psara cargar archivos remotos.", + correct: true + }, + ]} +/> + +### 2. Supón que cargas una de las tareas de GLUE así: + +```py +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`? + +dataset.sample(50)", + explain: "Esto es incorrecto. No hay un método Dataset.sample()." + }, + { + text: "dataset.shuffle().select(range(50))", + explain: "¡Correcto! Como viste en el capítulo, primero tienes que ordenar aleatoriamente el dataset y luego seleccionar las muestras.", + correct: true + }, + { + text: "dataset.select(range(50)).shuffle()", + explain: "Esto es incorrecto. Si bien el código se va a ejecutar, sólo va a ordenar aleatoriamente los primeros 50 elementos del dataset." + } + ]} +/> + +### 3. Supón que tienes un dataset sobre mascotas llamado `pets_dataset`, que tiene una columna `name` que contiene el nombre de cada mascota. ¿Cuál de los siguientes acercamientos te permitiría filtrar el dataset para todas las mascotas cuyos nombres comienzan con la letra "L"? + +pets_dataset.filter(lambda x : x['name'].startswith('L'))", + explain: "¡Correcto! Usar una función lambda de Python para este tipo de filtros es una gran idea. ¿Se te ocurre otra solución?", + correct: true + }, + { + text: "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." + }, + { + text: "Crear una funcióin 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 + } + ]} +/> + +### 4. ¿Qué es la proyección en memoria (_memory mapping_)? + + + +### 5. ¿Cuáles son los principales beneficios de la proyección en memoria? + + + +### 6. ¿Por qué no funciona el siguiente código? + +```py +from datasets import load_dataset + +dataset = load_dataset("allocine", streaming=True, split="train") +dataset[0] +``` + +IterableDataset.", + explain: "¡Correcto! Un IterableDataset es un generador, no un contenedor, así que deberías acceder a sus elementos usando next(iter(dataset)).", + correct: true + }, + { + text: "El dataset allocine no tiene un conjunto train.", + explain: "Incorrecto. Revisa la [tarjeta del dataset allocine](https://huggingface.co/datasets/allocine) en el Hub para ver qué conjuntos contiene." + } + ]} +/> + +### 7. ¿Cuáles son los principales beneficiones de crear una tarjeta para un dataset? + + + + +### 8. ¿Qué es la búsqueda semántica? + + + +### 9. Para la búsqueda semántica asimétrica, usualmente tienes: + + + +### 10. ¿Puedo usar 🤗 Datasets para cargar datos y usarlos en otras áreas, como procesamiento de habla? + +dataset MNIST en el Hub para un ejemplo de visión artificial." + }, + { + text: "Yes", + explain: "¡Correcto! Revisa los desarrollos con habla y visión en la librería 🤗 Transformers para ver cómo se puede usar 🤗 Datasets en estas áreas.", + correct : true + }, + ]} +/> diff --git a/chapters/fr/_toctree.yml b/chapters/fr/_toctree.yml index 2fd1d5873..58c4e9544 100644 --- a/chapters/fr/_toctree.yml +++ b/chapters/fr/_toctree.yml @@ -199,3 +199,8 @@ title: Événement de lancement de la partie 2 - local: events/3 title: Fête des blocs Gradio + +- title: Glossaire + sections: + - local: glossary/1 + title: Glossaire diff --git a/chapters/fr/chapter1/1.mdx b/chapters/fr/chapter1/1.mdx index 146e246ad..6ee2949cd 100644 --- a/chapters/fr/chapter1/1.mdx +++ b/chapters/fr/chapter1/1.mdx @@ -54,6 +54,55 @@ Après avoir terminé ce cours, nous vous recommandons de suivre la [Spécialisa **Leandro von Werra** est ingénieur en apprentissage machine dans l'équipe *open source* d'Hugging Face et également co-auteur du livre [*Natural Language Processing with Transformers*](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). Il a plusieurs années d'expérience dans l'industrie où il a pu déployer des projets de NLP en production et travailler sur toutes les étapes clefs du déploiement. + +## FAQ + +Voici quelques réponses aux questions fréquemment posées : + +- **Suivre ce cours mène-t-il à une certification ?** +Actuellement, nous n'avons pas de certification pour ce cours. Cependant, nous travaillons sur un programme de certification pour l'écosystème *Hugging Face*. Restez à l'écoute ! + +- **Combien de temps dois-je consacrer à ce cours ?** +Chaque chapitre de ce cours est conçu pour être complété en une semaine, avec environ 6 à 8 heures de travail par semaine. Cependant, vous pouvez prendre tout le temps nécessaire pour le suivre. + +- **Où puis-je poser une question si j'en ai une ?** +Si vous avez une question sur l'une des sections du cours, il vous suffit de cliquer sur la bannière « *Ask a question* » en haut de la page pour être automatiquement redirigé vers la bonne section du [forum d’*Hugging Face*] (https://discuss.huggingface.co/) : + +Link to the Hugging Face forums + +Notez qu'une liste d'[idées de projets](https://discuss.huggingface.co/c/course/course-event/25) est également disponible sur le forum si vous souhaitez pratiquer davantage une fois le cours terminé. + +- **Où puis-je obtenir le code du cours ?** +Pour chaque section, vous pouvez cliquer sur la bannière en haut de la page pour exécuter son code dans *Google Colab* ou *Amazon SageMaker Studio Lab* : + +Link to the Hugging Face course notebooks + +A noter que pour la version en français du cours, deux choix s’offrent à vous lorsque vous cliquez sur la bannière. Le premier est de sélectionner le *notebook* utilisant des modèles en anglais. L’intérêt est qu’il s’agit de celui sur lequel sont basées les explications du cours (interprétation des résultats, etc.). Le second est de sélectionner le *notebook* utilisant des modèles en français. Il s’agit alors d’une proposition d’adaptation (un modèle parmi tous ceux existant en français est utilisé). + +Si vous souhaitez accéder à l’ensemble des *notebooks* Jupyter du cours, il existe deux possibilités. La première est de cloner le dépôt [`huggingface/notebooks`](https://github.com/huggingface/notebooks) et de consulter les *notebooks* contenus dans le dossier *course*. La seconde est de générer les *notebooks* localement en suivant les instructions dans le *README* du dépôt [`course`](https://github.com/huggingface/course#-jupyter-notebooks) sur GitHub. + +- **Comment puis-je contribuer au cours ?** +Il existe de nombreuses façons de contribuer au cours ! Si vous trouvez une coquille ou un bug, veuillez ouvrir une « *Issue* » sur le dépôt [`course`](https://github.com/huggingface/course). Si vous souhaitez aider à traduire le cours dans votre langue maternelle, consultez les instructions [ici](https://github.com/huggingface/course#translating-the-course-into-your-language). + +- **Quels ont été les choix retenus pour la traduction ?** +Vous pouvez consulter le [glossaire](https://huggingface.co/course/fr/glossary/1) détaillant les choix retenus pour la traduction vers le français. + +- **Peut-on réutiliser ce cours?** +Bien sûr ! Le cours est publié sous la licence [Apache 2 license](https://www.apache.org/licenses/LICENSE-2.0.html). Cela signifie que vous devez créditer de manière appropriée, fournir un lien vers la licence et indiquer si des modifications ont été apportées. Vous pouvez le faire de toute manière raisonnable, mais pas d'une façon qui suggère que le distributeur de la licence vous approuve ou approuve votre utilisation. Si vous souhaitez citer le cours, veuillez utiliser le BibTeX suivant : + +``` +@misc{huggingfacecourse, + author = {Hugging Face}, + title = {The Hugging Face Course, 2022}, + howpublished = "\url{https://huggingface.co/course}", + year = {2022}, + note = "[Online; accessed ]" +} +``` + + +## C'est parti ! + Êtes-vous prêt à commencer ? Dans ce chapitre, vous apprendrez : * à utiliser la fonction `pipeline()` pour résoudre des problèmes de NLP comme la génération de texte et la classification, * l'architecture d'un *transformer*, diff --git a/chapters/fr/chapter1/3.mdx b/chapters/fr/chapter1/3.mdx index c61350d6c..5ba235a27 100644 --- a/chapters/fr/chapter1/3.mdx +++ b/chapters/fr/chapter1/3.mdx @@ -133,14 +133,15 @@ generator( ```python out [{'generated_text': 'In this course, we will teach you how to understand and use ' - # Dans ce cours, nous vous enseignerons comment comprendre et utiliser + # Dans ce cours, nous vous enseignerons comment comprendre et utiliser 'data flow and data interchange when handling user data. We ' - # flux de données et l'échange de données lors de la manipulation des données utilisateur. Nous + # flux de données et l'échange de données lors de la manipulation des données utilisateur. Nous 'will be working with one or more of the most commonly used ' - # travailleront avec un ou plusieurs des plus couramment utilisés + # travaillerons avec un ou plusieurs des plus couramment utilisés 'data flows — data flows of various types, as seen by the ' - # flux de données - flux de données de différents types, tels qu'ils sont vus par - 'HTTP'}] # HTTP + # flux de données - flux de données de différents types, tels qu'ils sont vus par + 'HTTP'}] + # HTTP ``` Il est possible de contrôler le nombre de séquences générées avec l'argument `num_return_sequences` et la longueur totale du texte généré avec l'argument `max_length`. @@ -172,15 +173,15 @@ generator( ```python out [{'generated_text': 'In this course, we will teach you how to manipulate the world and ' - # Dans ce cours, nous vous enseignerons comment manipuler le monde et + # Dans ce cours, nous vous enseignerons comment manipuler le monde et 'move your mental and physical capabilities to your advantage.'}, - # utiliser vos capacités mentales et physiques à votre avantage. + # utiliser vos capacités mentales et physiques à votre avantage. {'generated_text': 'In this course, we will teach you how to become an expert and ' - # Dans ce cours, nous vous apprendrons comment devenir un expert et + # Dans ce cours, nous vous apprendrons comment devenir un expert et 'practice realtime, and with a hands on experience on both real ' - # pratique en temps réel, et avec une expérience pratique à la fois sur de vrais + # pratique en temps réel, et avec une expérience pratique à la fois sur de vrais 'time and real'}] - # temps et réel + # temps et réel ``` Vous pouvez améliorer votre recherche de modèle en cliquant sur les *filtres* de langue et choisir un modèle qui génère du texte dans une autre langue. Le *Hub* contient également des *checkpoints* pour des modèles multilingues qui supportent plusieurs langues. @@ -337,19 +338,19 @@ summarizer( ```python out [{'summary_text': ' America has changed dramatically during recent years . The ' - # L'Amérique a changé de façon spectaculaire au cours des dernières années. Le + # L'Amérique a changé de façon spectaculaire au cours des dernières années. Le 'number of engineering graduates in the U.S. has declined in ' - # nombre de diplômés en ingénierie aux États-Unis a diminué dans + # nombre de diplômés en ingénierie aux États-Unis a diminué dans 'traditional engineering disciplines such as mechanical, civil ' - # dans les disciplines traditionnelles de l'ingénierie, telles que le génie mécanique, civil + # les disciplines traditionnelles de l'ingénierie, comme le génie mécanique, civil ', electrical, chemical, and aeronautical engineering . Rapidly ' - # l'électricité, la chimie et l'aéronautique. Les économies + # l'électricité, la chimie et l'aéronautique. Les économies 'developing economies such as China and India, as well as other ' - # en développement rapide comme la Chine et l'Inde, ainsi que d'autres + # en développement rapide comme la Chine et l'Inde, ainsi que d'autres 'industrial countries in Europe and Asia, continue to encourage ' - # pays industriels d'Europe et d'Asie, continuent d'encourager + # pays industriels d'Europe et d'Asie, continuent d'encourager 'and advance engineering.'}] - # et à faire progresser l'ingénierie. + # et à faire progresser l'ingénierie. ``` Comme pour la génération de texte, vous pouvez spécifier une `max_length` (longueur maximale) ou une `min_length` (longueur minimale) pour le résultat. diff --git a/chapters/fr/chapter7/2.mdx b/chapters/fr/chapter7/2.mdx index 8084e0df9..804e7fb82 100644 --- a/chapters/fr/chapter7/2.mdx +++ b/chapters/fr/chapter7/2.mdx @@ -371,7 +371,7 @@ Comme nous pouvons le voir, le deuxième jeu d'étiquettes a été complété à {:else} -Notre assembleur de données est prêt à fonctionner ! Maintenant, utilisons-le pour créer un `tf.data.Dataset` avec la méthode `to_tf_dataset()`. +Notre assembleur de données est prêt à fonctionner ! Maintenant, utilisons-le pour créer un `tf.data.Dataset` avec la méthode `to_tf_dataset()`. Une alternative est d'utiliser `model.prepare_tf_dataset()` pour faire cela qui prend un peu moins de code passe-partout. Vous verrez cela dans certaines des autres sections de ce chapitre. ```py tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( @@ -616,7 +616,7 @@ import numpy as np all_predictions = [] all_labels = [] for batch in tf_eval_dataset: - logits = model.predict(batch)["logits"] + logits = model.predict_on_batch(batch)["logits"] labels = batch["labels"] predictions = np.argmax(logits, axis=-1) for prediction, label in zip(predictions, labels): diff --git a/chapters/fr/chapter7/3.mdx b/chapters/fr/chapter7/3.mdx index 4ca41a4b5..0579e6af9 100644 --- a/chapters/fr/chapter7/3.mdx +++ b/chapters/fr/chapter7/3.mdx @@ -637,18 +637,18 @@ dans votre terminal préféré et connectez-vous là. {#if fw === 'tf'} -Une fois que nous sommes connectés, nous pouvons créer nos jeux de données `tf.data`. Nous n'utiliserons ici que le collecteur de données standard, mais vous pouvez également essayer le collecteur de masquage de mots entiers et comparer les résultats à titre d'exercice : +Une fois que nous sommes connectés, nous pouvons créer nos jeux de données `tf.data`. Pour ce faire, nous utiliserons la méthode `prepare_tf_dataset()`, qui utilise notre modèle pour déduire automatiquement quelles colonnes doivent aller dans le jeu de données. Si vous voulez contrôler exactement les colonnes à utiliser, vous pouvez utiliser la méthode `Dataset.to_tf_dataset()` à la place. Pour garder les choses simples, nous n'utiliserons ici que le l’assembleur de données standard, mais vous pouvez aussi essayer l’assembleur masquant des mots entiers et comparer les résultats à titre d'exercice : ```python -tf_train_dataset = downsampled_dataset["train"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], +tf_train_dataset = model.prepare_tf_dataset( + downsampled_dataset["train"], collate_fn=data_collator, shuffle=True, batch_size=32, ) -tf_eval_dataset = downsampled_dataset["test"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], +tf_eval_dataset = model.prepare_tf_dataset( + downsampled_dataset["test"], collate_fn=data_collator, shuffle=False, batch_size=32, @@ -676,6 +676,7 @@ model.compile(optimizer=optimizer) # Entraîner en mixed-precision 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 ) diff --git a/chapters/fr/chapter7/4.mdx b/chapters/fr/chapter7/4.mdx index 67a1f954b..db3d8640b 100644 --- a/chapters/fr/chapter7/4.mdx +++ b/chapters/fr/chapter7/4.mdx @@ -388,14 +388,14 @@ Nous allons transmettre ce `data_collator` au `Seq2SeqTrainer`. Ensuite, jetons Nous pouvons maintenant utiliser ce `data_collator` pour convertir chacun de nos jeux de données en un `tf.data.Dataset`, prêt pour l'entraînement : ```python -tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], +model.prepare_tf_dataset( + tokenized_datasets["train"], collate_fn=data_collator, shuffle=True, batch_size=32, ) -tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], +tf_eval_dataset = model.prepare_tf_dataset( + tokenized_datasets["validation"], collate_fn=data_collator, shuffle=False, batch_size=16, @@ -505,28 +505,41 @@ Le score peut aller de 0 à 100. Plus il est élevé, mieux c'est. {#if fw === 'tf'} -Pour passer des sorties du modèle aux textes que la métrique peut utiliser, nous allons utiliser la méthode `tokenizer.batch_decode()`. Nous devons juste nettoyer tous les `-100` dans les étiquettes. Le *tokenizer* fera automatiquement la même chose pour le *token* de *padding*. Définissons une fonction qui prend notre modèle et un jeu de données et calcule des métriques sur ceux-ci. Comme la génération de longues séquences peut être lente, nous sous-échantillonnons l'ensemble de validation pour nous assurer que cela ne prend pas une éternité : +Pour passer des sorties du modèle aux textes utilisables par la métrique, nous allons utiliser la méthode `tokenizer.batch_decode()`. Nous devons juste nettoyer tous les `-100` dans les étiquettes. Le *tokenizer* fera automatiquement la même chose pour le *token* de remplissage. Définissons une fonction qui prend notre modèle et un jeu de données et calcule des métriques sur ceux-ci. Nous allons également utiliser une astuce qui augmente considérablement les performances : compiler notre code de génération avec [XLA](https://www.tensorflow.org/xla), le compilateur d'algèbre linéaire accéléré de TensorFlow. XLA applique diverses optimisations au graphe de calcul du modèle, ce qui permet d'améliorer considérablement la vitesse et l'utilisation de la mémoire. Comme décrit dans un article du [blog d’Hugging Face](https://huggingface.co/blog/tf-xla-generate), XLA fonctionne mieux lorsque nos formes d'entrée ne varient pas trop. Pour gérer cela, nous allons rembourrer nos entrées à des multiples de 128, et créer un nouveau jeu de données avec l’assembleur de rembourrage. Puis nous appliquerons le décorateur `@tf.function(jit_compile=True)` à notre fonction de génération, qui marque la fonction entière pour la compilation avec 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 = [] - sampled_dataset = tokenized_datasets["validation"].shuffle().select(range(200)) - tf_generate_dataset = sampled_dataset.to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], - collate_fn=data_collator, - shuffle=False, - batch_size=4, - ) - for batch in tf_generate_dataset: - predictions = model.generate( - input_ids=batch["input_ids"], attention_mask=batch["attention_mask"] - ) + + for batch, labels in tqdm(tf_generate_dataset): + predictions = generate_with_xla(batch) decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) - labels = batch["labels"].numpy() + 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] diff --git a/chapters/fr/chapter7/5.mdx b/chapters/fr/chapter7/5.mdx index 3f317d0e9..148bc1af5 100644 --- a/chapters/fr/chapter7/5.mdx +++ b/chapters/fr/chapter7/5.mdx @@ -305,14 +305,13 @@ max_target_length = 30 def preprocess_function(examples): model_inputs = tokenizer( - examples["review_body"], max_length=max_input_length, truncation=True + examples["review_body"], + max_length=max_input_length, + truncation=True, + ) + labels = tokenizer( + examples["review_title"], max_length=max_target_length, truncation=True ) - # Configurer le tokenizer pour les cibles - with tokenizer.as_target_tokenizer(): - labels = tokenizer( - examples["review_title"], max_length=max_target_length, truncation=True - ) - model_inputs["labels"] = labels["input_ids"] return model_inputs ``` @@ -701,14 +700,14 @@ Pour conclure cette section, voyons comment nous pouvons également *finetuner* Nous sommes presque prêts à nous entraîner ! Nous devons juste convertir nos jeux de données en `tf.data.Dataset` en utilisant le assembleur de données que nous avons défini ci-dessus, puis utiliser `compile()` et `fit()`. D'abord, les jeux de données : ```python -tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], +tf_train_dataset = model.prepare_tf_dataset( + tokenized_datasets["train"], collate_fn=data_collator, shuffle=True, batch_size=8, ) -tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], +tf_eval_dataset = model.prepare_tf_dataset( + tokenized_datasets["validation"], collate_fn=data_collator, shuffle=False, batch_size=8, @@ -755,18 +754,39 @@ model.fit( ) ``` -Nous avons obtenu quelques valeurs de perte pendant l'entraînement, mais nous aimerions vraiment voir les métriques ROUGE que nous avons calculées plus tôt. Pour obtenir ces métriques, nous devons générer les sorties du modèle et les convertir en chaînes de caractères. Construisons quelques listes d'étiquettes et de prédictions pour comparer la métrique ROUGE (notez que si vous obtenez des erreurs d'importation pour cette section, vous pouvez avoir besoin de "pip install tqdm") : +Nous avons obtenu quelques valeurs de perte pendant l'entraînement mais nous aimerions voir les métriques ROUGE que nous avons calculées plus tôt. Pour obtenir ces métriques, nous devons générer les sorties du modèle et les convertir en chaînes de caractères. Construisons une liste d'étiquettes et une liste de prédictions pour la métrique ROUGE pour comparer (notez que si vous obtenez des erreurs d'importation pour cette section, vous pouvez avoir besoin de faire `pip install tqdm`). Nous allons également utiliser une astuce qui augmente considérablement les performances : compiler notre code de génération avec [XLA](https://www.tensorflow.org/xla), le compilateur d'algèbre linéaire accéléré de TensorFlow. XLA applique diverses optimisations au graphe de calcul du modèle, ce qui permet d'améliorer considérablement la vitesse et l'utilisation de la mémoire. Comme décrit dans un article du [blog d’Hugging Face](https://huggingface.co/blog/tf-xla-generate), XLA fonctionne mieux lorsque nos formes d'entrée ne varient pas trop. Pour gérer cela, nous allons rembourrer nos entrées à des multiples de 128, et créer un nouveau jeu de données avec l’assembleur de rembourrage. Puis nous appliquerons le décorateur `@tf.function(jit_compile=True)` à notre fonction de génération, qui marque la fonction entière pour la compilation avec 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 in tqdm(tf_eval_dataset): - predictions = model.generate(**batch) +for batch, labels in tqdm(tf_generate_dataset): + predictions = generate_with_xla(batch) decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) - labels = batch["labels"].numpy() + 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] diff --git a/chapters/fr/chapter7/6.mdx b/chapters/fr/chapter7/6.mdx index 4d1d27e59..0ff1ce37b 100644 --- a/chapters/fr/chapter7/6.mdx +++ b/chapters/fr/chapter7/6.mdx @@ -375,17 +375,17 @@ Nous pouvons voir que les exemples ont été empilés et que tous les tenseurs o {#if fw === 'tf'} -Maintenant nous pouvons utiliser la méthode `to_tf_dataset()` pour convertir nos jeux de données en jeux de données TensorFlow avec l'assembleur de données que nous avons créé ci-dessus : +Maintenant nous pouvons utiliser la méthode `prepare_tf_dataset()` pour convertir nos jeux de données en jeux de données TensorFlow avec l'assembleur de données que nous avons créé ci-dessus : ```python -tf_train_dataset = tokenized_dataset["train"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], +tf_train_dataset = model.prepare_tf_dataset( + tokenized_dataset["train"], collate_fn=data_collator, shuffle=True, batch_size=32, ) -tf_eval_dataset = tokenized_dataset["valid"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], +tf_eval_dataset = model.prepare_tf_dataset( + tokenized_dataset["valid"], collate_fn=data_collator, shuffle=False, batch_size=32, @@ -511,7 +511,7 @@ model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback {:else} -💡 Si vous avez accès à une machine avec plusieurs GPUs, vous pouvez essayer d'utiliser `MirroredStrategy` pour accélérer considérablement l'entraînement. Vous devrez créer un objet `tf.distribute.MirroredStrategy` et vous assurer que les commandes `to_tf_dataset` ainsi que la création du modèle et l'appel à `fit()` sont tous exécutés dans `scope()`. Vous pouvez consulter la documentation à ce sujet [ici](https://www.tensorflow.org/guide/distributed_training#use_tfdistributestrategy_with_keras_modelfit). +💡 Si vous avez accès à une machine avec plusieurs GPUs, vous pouvez essayer d'utiliser `MirroredStrategy` pour accélérer considérablement l'entraînement. Vous devrez créer un objet `tf.distribute.MirroredStrategy` et vous assurer que toutes les méthodes `to_tf_dataset()` ou `prepare_tf_dataset()` ainsi que la création du modèle et l'appel à `fit()` sont tous exécutés dans `scope()`. Vous pouvez consulter la documentation à ce sujet [ici](https://www.tensorflow.org/guide/distributed_training#use_tfdistributestrategy_with_keras_modelfit). {/if} diff --git a/chapters/fr/chapter7/7.mdx b/chapters/fr/chapter7/7.mdx index 9bb9b046b..5ba430b64 100644 --- a/chapters/fr/chapter7/7.mdx +++ b/chapters/fr/chapter7/7.mdx @@ -883,20 +883,14 @@ data_collator = DefaultDataCollator(return_tensors="tf") Et maintenant nous créons les jeux de données comme d'habitude. ```python -tf_train_dataset = train_dataset.to_tf_dataset( - columns=[ - "input_ids", - "start_positions", - "end_positions", - "attention_mask", - "token_type_ids", - ], +tf_train_dataset = model.prepare_tf_dataset( + train_dataset, collate_fn=data_collator, shuffle=True, batch_size=16, ) -tf_eval_dataset = validation_dataset.to_tf_dataset( - columns=["input_ids", "attention_mask", "token_type_ids"], +tf_eval_dataset = model.prepare_tf_dataset( + validation_dataset, collate_fn=data_collator, shuffle=False, batch_size=16, diff --git a/chapters/fr/glossary/1.mdx b/chapters/fr/glossary/1.mdx new file mode 100644 index 000000000..0c15c86b0 --- /dev/null +++ b/chapters/fr/glossary/1.mdx @@ -0,0 +1,63 @@ +# Glossaire + +| Original | Français | +|-----------------------------|--------------------------------- | +| Accuracy | Précision | +| Backward Pass | Passe arrière | +| Batch | *Batch* | +| Benchmark | *Benchmark* | +| Cache | Cache | +| Chapter | Chapitre | +| Checkpoint | *Checkpoint* (plus rarement « point de sauvegarde ») | +| Colab Notebook | *Notebook* Google Colab | +| Colator function | Fonction d'assemblement | +| Command | Commande | +| Configuration | Configuration | +| Course | Cours | +| Dataloader | Chargeur de données | +| Dependency | Dépendances | +| Deployment | Déploiement | +| Development | Développement | +| Dictionary | Dictionnaire | +| Download | Télécharger | +| Feature | Variable | +| Field | Champ | +| Fine-tuning | Finetuning | +| Folder | Dossier | +| Forward Pass | Passe avant | +| Google | *Google* | +| Hugging Face | *Hugging Face* | +| Inference | Inférence | +| Learning rate | Taux d’apprentissage | +| Library | Bibliothèque | +| Linux | Linux | +| Loss function | Fonction de perte/coût | +| Loop | Boucle | +| macOS | macOS | +| Model | Modèle | +| Hugging Face Hub | *Hub* d’*Hugging Face* | +| Module | Module | +| Natural Language Processing | Traitement du langage naturel | +| Package | Paquet | +| Padding | Rembourrage | +| Parameter | Paramètre | +| Python | Python | +| PyTorch | PyTorch | +| Samples | Echantillons | +| Scheduler | Planificateur | +| Script | Script | +| Setup | Installation | +| TensorFlow | TensorFlow | +| Terminal | Terminal | +| Tokenizer | Tokeniseur | +| Train | Entraîner | +| Transformer | *Transformer* | +| Virtual Environment | Environnement virtuel | +| Weight decay | Taux de décroissance des poids | +| Weights | Poids | +| Windows | *Windows* | +| Working Environment | Environnement de travail | + + +A noter que les mots anglais non traduits sont indiqués en italique dans le cours. +De plus, les abréviations techniques comme API, GPU, TPU, etc. ne sont pas traduites. diff --git a/chapters/ja/_toctree.yml b/chapters/ja/_toctree.yml index c1d0fcd7f..fb3be77b8 100644 --- a/chapters/ja/_toctree.yml +++ b/chapters/ja/_toctree.yml @@ -7,6 +7,24 @@ sections: - local: chapter1/1 title: イントロダクション + - local: chapter1/2 + title: 自然言語処理 / NLP(Natural Language Processing) + - local: chapter1/3 + title: Transformersで何ができる? + - local: chapter1/4 + title: Transformersの仕組みについて + - local: chapter1/5 + title: エンコーダーモデル + - local: chapter1/6 + title: デコーダーモデル + - local: chapter1/7 + title: Sequence-to-sequence モデル + - local: chapter1/8 + title: バイアスと限界 + - local: chapter1/9 + title: まとめ + - local: chapter1/10 + title: 章末クイズ - title: 4. モデルとトークナイザーの共有 sections: diff --git a/chapters/ja/chapter1/10.mdx b/chapters/ja/chapter1/10.mdx new file mode 100644 index 000000000..11b70518a --- /dev/null +++ b/chapters/ja/chapter1/10.mdx @@ -0,0 +1,262 @@ + + +# 章末クイズ + + + +この章では多くの物事を学びました!詳細を把握できなくても安心してください。次の章はどのようにこれらのツールが動いているか理解する上で役に立ちます。 + +まずは、この章で学んだことを確かめましょう! + + +### 1.Hubを探索して`roberta-large-mnli`チェックポイントを見つけましょう。 このモデルはどのタスクに適していますか? + +ページを見てみましょう。" + }, + { + text: "文章分類", + explain: "より正確には2つの文が論理的にどのような関係を持つか、3つのラベル(矛盾、中立、含意)について分類します。このタスクは自然言語推論とも呼ばれます。", + correct: true + }, + { + text: "文章生成", + explain: "もう一度roberta-large-mnliのページを見てみましょう。" + } + ]} +/> + +### 2.次のコードは何を返しますか? + +```py +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +sentiment-analysis パイプラインを用いたときの動作です。" + }, + { + text: "この文章を完結させるための生成された文を返します。", + explain: "間違いです。それは text-generation パイプラインを用いたときの動作です。" + }, + { + text: "この文中の人物、団体、場所を表す単語を返します。", + explain: "さらに、grouped_entities=Trueを用いると、同じエンティティに属する単語をグループ化します。", + correct: true + } + ]} +/> + +### 3. このサンプルコードでは...をどのように置き換えればよいでしょうか? + +```py +from transformers import pipeline + +filler = pipeline("fill-mask", model="bert-base-cased") +result = filler("...") +``` + + has been waiting for you.", + explain: "間違いです。bert-base-casedのモデルカードをチェックして、あなたの間違いを見つけましょう。" + }, + { + text: "This [MASK] has been waiting for you.", + explain: "正解!このモデルのマスクトークンは[MASK]です。", + correct: true + }, + { + text: "This man has been waiting for you.", + explain: "間違いです。このパイプラインはマスクされた単語を埋めるので、どこかにマスクトークンが必要です。" + } + ]} +/> + +### 4. なぜこのコードは動かないのでしょうか? + +```py +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +result = classifier("This is a course about the Transformers library") +``` + +candidate_labels=[...]を含める必要があります。", + correct: true + }, + { + text: "このパイプラインには、一つの文だけでなく複数の文が必要です。", + explain: "これは間違いです。しかし、適切に使用すれば、このパイプラインは処理する文のリストを受け取ることができます(他のパイプラインも同様です)。" + }, + { + text: "いつもどおり、この🤗Transformersライブラリは壊れています。", + explain: "ノーコメント!" + }, + { + text: "このパイプラインはもっと長い入力が必要です。この入力は短すぎます。", + explain: "これは間違いです。ただし、とても長い文をこのパイプラインで処理すると、切り捨てられることに注意してください。" + } + ]} +/> + +### 5. 転移学習とはどのような意味ですか? + + + + +### 6. マルバツクイズ、言語モデルの事前学習にラベルは通常は必要ない? + + +自己教師あり学習で行われます。つまり、ラベルは入力から自動的に作成されます(例えば、次の単語を予測したり、マスクされた単語を埋めたりといったように)。", + correct: true + }, + { + text: "バツ", + explain: "これは正しい回答ではありません。" + } + ]} +/> + +### 7.「モデル」、「アーキテクチャ」、「重み」という用語を最も適切に説明している文を選んでください。 + + + + +### 8. 生成された文でプロンプトを完成させるために使うモデルはどれでしょうか? + + + + +### 9. 文章要約タスクに使うモデルはどれでしょうか? + + + +### 10. 入力された文を特定のラベルに分類したいときに使うモデルはどれでしょうか? + + + + +### 11. モデルが持つバイアスはどのような要因で生じますか? + + + diff --git a/chapters/ja/chapter1/2.mdx b/chapters/ja/chapter1/2.mdx new file mode 100644 index 000000000..11fb226da --- /dev/null +++ b/chapters/ja/chapter1/2.mdx @@ -0,0 +1,26 @@ +# 自然言語処理 / NLP(Natural Language Processing) + + + +Transformerモデルの詳細に飛び込んでいく前に、自然言語処理とはどんなもので、かつ、なぜ我々が注目する必要があるのかの大まかな概要を知っていきましょう。 + +## 自然言語処理とはどんなもの? + +自然言語処理とは、人の言語に関連した全てのことへの理解に焦点を当てた、言語学と機械学習の分野です。自然言語処理タスクの目標は、文章を個別に一単語ずつ理解するだけでなく、それらの単語で構成された文章の文脈を理解することです。 + +以下のリストで、具体例付きで一般的な自然言語処理タスクを紹介します。 + +- **文章の分類**:レビューの評価、スパムメールの検出、文法的に正しいかどうかの判断、2つの文が論理的に関連しているかどうかの判断 +- **文の中の単語分類**:品詞(名詞、動詞、形容詞)や、固有表現(人、場所、組織)の識別 +- **文章内容の生成**:自動生成されたテキストによる入力テキストの補完、文章の穴埋め +- **文章からの情報抽出**:質問と文脈が与えられたときの、文脈からの情報に基づいた質問に対する答えの抽出 +- **文章の変換**:ある文章の他の言語への翻訳、文章の要約 + +さらに、自然言語処理は文章に限ったものではありません。音声認識やコンピュータビジョンの分野でも、音声サンプルの書き起こしや画像の説明文の生成など、複雑な課題に取り組んでいます。 + +## なぜ自然言語処理は困難なのか? + +コンピュータは人間と同じように情報を処理するわけではありません。例えば、「私はお腹が空いています。」という文章を読むと、人間はその意味を簡単に理解することができます。同様に、「私はお腹が空いています。」と「私は悲しいです。」という2つの文章があれば、その類似性を人間は簡単に判断することができます。しかし、機械学習(ML)モデルにおいては、このようなタスクはより困難です。機械学習モデルが学習できるように、テキストを処理する必要があります。また、言語は複雑なため、どのように処理すべきかを慎重に考える必要があります。テキストをどのように表現するかについては多くの研究がなされており、次の章ではいくつかの方法について見ていきます。 diff --git a/chapters/ja/chapter1/3.mdx b/chapters/ja/chapter1/3.mdx new file mode 100644 index 000000000..93803b215 --- /dev/null +++ b/chapters/ja/chapter1/3.mdx @@ -0,0 +1,332 @@ +# Transformersで何ができる? + + + + +このセクションでは、Transformerモデルができることを見ていき、🤗 Transformersライブラリの最初のツールとして `pipeline()` 関数を使ってみましょう。 + + +👀 右上にOpen in Colabというボタンがありますよね?それをクリックすると、このセクションのすべてのコードサンプルを含むGoogle Colabノートブックが開きます。このボタンは、コードサンプルを含むどのセクションにも存在します。 + +ローカルでサンプルを実行したい場合は、セットアップを参照することをお勧めします。 + + +## Transformersは至るところに! + +Transformerモデルは前節で述べたようなあらゆる種類のNLPタスクを解決するために使用されています。ここでは、Hugging FaceとTransformerモデルを使用している企業や組織を紹介します。それらの組織はまた、モデルを共有することでコミュニティに還元しています。 + +Companies using Hugging Face + +[🤗 Transformers library](https://github.com/huggingface/transformers)は、それらの共有モデルを作成し、使用するための機能を提供します。[Model Hub](https://huggingface.co/models)には、誰でもダウンロードして使用できる何千もの事前学習済みモデルが含まれています。また、あなた自身のモデルをModel Hubにアップロードすることも可能です。 + + +⚠️Hugging Face Hubはトランスフォーマーモデルに限定されるものではありません。誰でも好きな種類のモデルやデータセットを共有することができます!すべての利用可能な機能の恩恵を受けるためにhuggingface.coのアカウントを作成しましょう! + + +
+ +Transformerのモデルがどのように機能するのかを知る前に、いくつかの興味深いNLPの問題を解決するため、Transformerがどのように使われるのか、いくつかの例で見ていきましょう。 + +## pipelineを使ってタスクを実行する + + + +🤗 Transformers ライブラリの中で最も基本的なオブジェクトは `pipeline()` 関数です。これはモデルを必要な前処理と後処理のステップに接続し、任意のテキストを直接入力して理解しやすい答えを得ることを可能にします。 + +```python +from transformers import pipeline + +classifier = pipeline("sentiment-analysis") +classifier("I've been waiting for a HuggingFace course my whole life.") +``` + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}] +``` + +複数の文章を入力することも可能です。 + +```python +classifier( + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!"] +) +``` + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}, + {'label': 'NEGATIVE', 'score': 0.9994558095932007}] +``` + +デフォルトでは、このpipelineは英語の感情分析用にファインチューニングされた特定の事前学習モデルを使用します。このモデルは `classifier` オブジェクトを作成する際にダウンロードされ、キャッシュされます。コマンドを再実行すると、キャッシュされたモデルが代わりに使用され、モデルを再度ダウンロードする必要はありません。 + +pipelineにテキストを渡す場合、主に3つのステップがあります。 + +1. テキストはモデルが理解できる形式に前処理される。 +2. 前処理された入力がモデルに渡される。 +3. 予測結果を理解できるように、モデルの後処理が行われる。 + +現在[利用可能なpipeline](https://huggingface.co/transformers/main_classes/pipelines.html)の一部を紹介します。 + +- `feature-extraction` (テキストのベクトル表現を取得) +- `fill-mask` +- `ner` (固有表現認識) +- `question-answering` +- `sentiment-analysis` +- `summarization` +- `text-generation` +- `translation` +- `zero-shot-classification` + +では、いくつか見ていきましょう! + +## ゼロショット分類 + +まず、ラベル付けされていないテキストを分類する必要があるような、より困難なタスクに取り組むことから始めます。これは実際のプロジェクトでよくあるシナリオです。なぜなら、テキストにアノテーションをつけるのは通常時間がかかり、専門知識が必要だからです。このような場合、`zero-shot-classification` pipelineは非常に強力です。分類に使用するラベルを指定できるので、事前に学習したモデルのラベルに依存する必要がありません。肯定的か否定的かの2つのラベルを使って、モデルがどのようにテキストを分類するかは既に見たとおりです。しかし、他の任意のラベルセットを使ってテキストを分類することもできます。 + + +```python +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +classifier( + "This is a course about the Transformers library", + candidate_labels=["education", "politics", "business"], +) +``` + +```python out +{'sequence': 'This is a course about the Transformers library', + 'labels': ['education', 'business', 'politics'], + 'scores': [0.8445963859558105, 0.111976258456707, 0.043427448719739914]} +``` + +このpipelineは _zero-shot_ と呼ばれます。なぜなら、これを使うために自前のデータセットでモデルのファインチューニングをする必要がないからです。任意のラベルのリストに対して直接確率スコアを返すことができます。 + + + +✏️ **試してみよう!** 独自の入力とラベルで遊んでみて、モデルがどのように振る舞うか見てみましょう。 + + + +## 文章生成 + +では、pipelineを使ってテキストを生成する方法を見てみましょう。主なアイデアは、プロンプトを与えると、モデルが残りのテキストを生成してそれを補完することです。これは、多くの携帯電話に搭載されている予測入力機能に類似しています。テキスト生成にはランダム性が含まれるため、以下と同様な結果が得られないのが通常です。 + +```python +from transformers import pipeline + +generator = pipeline("text-generation") +generator("In this course, we will teach you how to") +``` + +```python out +[{'generated_text': 'In this course, we will teach you how to understand and use ' + 'data flow and data interchange when handling user data. We ' + 'will be working with one or more of the most commonly used ' + 'data flows — data flows of various types, as seen by the ' + 'HTTP'}] +``` + +引数`num_return_sequences`で異なるシーケンスの生成数を、引数`max_length`で出力テキストの合計の長さを制御することができます。 + + + + +✏️ **試してみよう!** `num_return_sequences` と `max_length` 引数を用いて、15語ずつの2つの文を生成してみましょう! + + + + +## pipelineでHubから任意のモデルを使用する + +これまでの例では、タスクに応じたデフォルトのモデルを使用しましたが、特定のタスク(例えばテキスト生成)のpipelineで使用するモデルをHubから選択することも可能です。[Model Hub](https://huggingface.co/models)にアクセスし、左側の対応するタグをクリックすると、そのタスクでサポートされているモデルのみが表示されます。[このようなページ](https://huggingface.co/models?pipeline_tag=text-generation)が表示されるはずです。 + +それでは、[`distilgpt2`](https://huggingface.co/distilgpt2)モデルを試してみましょう! 先ほどと同じpipelineでロードする方法を説明します。 + +```python +from transformers import pipeline + +generator = pipeline("text-generation", model="distilgpt2") +generator( + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, +) +``` + +```python out +[{'generated_text': 'In this course, we will teach you how to manipulate the world and ' + 'move your mental and physical capabilities to your advantage.'}, + {'generated_text': 'In this course, we will teach you how to become an expert and ' + 'practice realtime, and with a hands on experience on both real ' + 'time and real'}] +``` + +言語タグをクリックして検索するモデルを絞り込み、他の言語でテキストを生成するモデルを選ぶことができます。Model Hubには、複数の言語をサポートする多言語モデルのチェックポイントもあります。 + +モデルをクリックで選択すると、オンラインで直接試用できるウィジェットが表示されます。このようにして、ダウンロードする前にモデルの機能をすばやくテストすることができます。 + + + +✏️ **試してみよう!** フィルターを使って、他の言語のテキスト生成モデルを探してみましょう。ウィジェットで自由に遊んだり、pipelineで使ってみてください! + + + + +### 推論API + +すべてのモデルは、Hugging Face [ウェブサイト](https://huggingface.co/)で公開されているInference APIを使って、ブラウザから直接テストすることが可能です。このページでは、任意の文字列を入力し、モデルが入力データを処理する様子を見ることで、直接モデルで遊ぶことができます。 + +このウィジェットを動かすInference APIは、有料製品としても提供されており、ワークフローに必要な場合は便利です。詳しくは[価格ページ](https://huggingface.co/pricing)をご覧ください。 + +## 空所穴埋め + +次に試すpipelineは`fill-mask`です。このタスクのアイデアは、与えられたテキストの空白を埋めることです。 + +```python +from transformers import pipeline + +unmasker = pipeline("fill-mask") +unmasker("This course will teach you all about models.", top_k=2) +``` + +```python out +[{'sequence': 'This course will teach you all about mathematical models.', + 'score': 0.19619831442832947, + 'token': 30412, + 'token_str': ' mathematical'}, + {'sequence': 'This course will teach you all about computational models.', + 'score': 0.04052725434303284, + 'token': 38163, + 'token_str': ' computational'}] +``` + +`top_k` 引数は、いくつの可能性を表示させたいかをコントロールします。ここでは、モデルが特別な `` という単語を埋めていることに注意してください。これはしばしば *mask token* と呼ばれます。他の空所穴埋めモデルは異なるマスクトークンを持つかもしれないので、他のモデルを探索するときには常に適切なマスクワードを確認するのが良いでしょう。それを確認する1つの方法は、ウィジェットで使用されているマスクワードを見ることです。 + + + +✏️ **試してみよう!** Hub で `bert-base-cased` モデルを検索し、推論API ウィジェットでそのマスクワードを特定します。このモデルは上記の `pipeline` の例文に対して何を予測するでしょうか? + + + +## 固有表現認識 + +固有表現認識(NER)は、入力されたテキストのどの部分が人物、場所、組織などの固有表現に対応するかをモデルが見つけ出すタスクです。例を見てみましょう。 + +```python +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +```python out +[{'entity_group': 'PER', 'score': 0.99816, 'word': 'Sylvain', 'start': 11, 'end': 18}, + {'entity_group': 'ORG', 'score': 0.97960, 'word': 'Hugging Face', 'start': 33, 'end': 45}, + {'entity_group': 'LOC', 'score': 0.99321, 'word': 'Brooklyn', 'start': 49, 'end': 57} +] +``` + +ここでは、モデルはSylvainが人(PER)、Hugging Faceが組織(ORG)、Brooklynが場所(LOC)であることを正しく識別しています。 + +pipelineの作成機能でオプション `grouped_entities=True` を渡すと、同じエンティティに対応する文の部分を再グループ化するようpipelineに指示します。ここでは、名前が複数の単語で構成されていても、モデルは "Hugging" と "Face" を一つの組織として正しくグループ化しています。実際、次の章で説明するように、前処理ではいくつかの単語をより小さなパーツに分割することさえあります。例えば、`Sylvain`は4つの部分に分割されます。`S`, `##yl`, `##va`, and `##in`.です。後処理の段階で、pipelineはこれらの断片をうまく再グループ化しました。 + + + +✏️ **試してみよう!** Model Hubで英語の品詞タグ付け(通常POSと略される)を行えるモデルを検索してください。このモデルは、上の例の文に対して何を予測するでしょうか? + + + +## 質問応答 + +質問応答pipelineは、与えられた文脈から得た情報を使って質問に答えます。 + +```python +from transformers import pipeline + +question_answerer = pipeline("question-answering") +question_answerer( + question="Where do I work?", + context="My name is Sylvain and I work at Hugging Face in Brooklyn", +) +``` + +```python out +{'score': 0.6385916471481323, 'start': 33, 'end': 45, 'answer': 'Hugging Face'} +``` + +このpipelineは、提供されたコンテキストから情報を抽出することで動作し、答えを生成するわけではないことに注意してください。 + +## 要約 + +要約とは、文章中の重要な部分をすべて(あるいはほとんど)維持したまま、より短い文章にするタスクです。以下はその例です。 + +```python +from transformers import pipeline + +summarizer = pipeline("summarization") +summarizer( + """ + America has changed dramatically during recent years. Not only has the number of + graduates in traditional engineering disciplines such as mechanical, civil, + electrical, chemical, and aeronautical engineering declined, but in most of + the premier American universities engineering curricula now concentrate on + and encourage largely the study of engineering science. As a result, there + are declining offerings in engineering subjects dealing with infrastructure, + the environment, and related issues, and greater concentration on high + technology subjects, largely supporting increasingly complex scientific + developments. While the latter is important, it should not be at the expense + of more traditional engineering. + Rapidly developing economies such as China and India, as well as other + industrial countries in Europe and Asia, continue to encourage and advance + the teaching of engineering. Both China and India, respectively, graduate + six and eight times as many traditional engineers as does the United States. + Other industrial countries at minimum maintain their output, while America + suffers an increasingly serious decline in the number of engineering graduates + and a lack of well-educated engineers. +""" +) +``` + +```python out +[{'summary_text': ' America has changed dramatically during recent years . The ' + 'number of engineering graduates in the U.S. has declined in ' + 'traditional engineering disciplines such as mechanical, civil ' + ', electrical, chemical, and aeronautical engineering . Rapidly ' + 'developing economies such as China and India, as well as other ' + 'industrial countries in Europe and Asia, continue to encourage ' + 'and advance engineering .'}] +``` + +テキスト生成と同様に、結果に対して `max_length` や `min_length` を指定することができます。 + + +## 翻訳 + +翻訳の場合、タスク名に言語ペアを指定すれば(`"translation_en_to_fr"`など)デフォルトのモデルを使うこともできますが、一番簡単なのは [Model Hub](https://huggingface.co/models) で使いたいモデルを選ぶことです。ここでは、フランス語から英語への翻訳を試してみます。 + +```python +from transformers import pipeline + +translator = pipeline("translation", model="Helsinki-NLP/opus-mt-fr-en") +translator("Ce cours est produit par Hugging Face.") +``` + +```python out +[{'translation_text': 'This course is produced by Hugging Face.'}] +``` + +テキスト生成や要約と同様に、結果に対して `max_length` や `min_length` を指定することができます。 + + + +✏️ **試してみよう!** 他言語の翻訳モデルを検索して、前の文章をいくつかの異なる言語に翻訳してみましょう。 + + + +これまで紹介したpipelineは、ほとんどがデモンストレーションのためのものです。これらは特定のタスクのためにプログラムされたものであり、それらのバリエーションを実行することはできません。次の章では、`pipeline()`関数の中身と、その動作をカスタマイズする方法を学びます。 diff --git a/chapters/ja/chapter1/4.mdx b/chapters/ja/chapter1/4.mdx new file mode 100644 index 000000000..4c9f6e585 --- /dev/null +++ b/chapters/ja/chapter1/4.mdx @@ -0,0 +1,177 @@ +# Transformersの仕組みについて + + + +このセクションでは、Transformerモデルのアーキテクチャをざっくりと見ていきます。 + +## Transformerの歴史を簡単に + +Transformerモデルの(短い)歴史の中で、参考となるポイントをいくつか紹介します。 + +
+A brief chronology of Transformers models. + +
+ +[Transformerのアーキテクチャ](https://arxiv.org/abs/1706.03762)は2017年6月に登場しました。 当初の研究は翻訳タスクに焦点を置いていましたが、これに続くようにして以下のような影響力のあるモデルがいくつか登場します。 + +- **2018/6** [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf): 様々な自然言語処理タスクに対してfine-tuningすることでSoTAを達成した、史上初の事前学習済みモデルです。 + +- **2018/10** [BERT](https://arxiv.org/abs/1810.04805): これも大規模な事前学習済みモデルで、文についてのより良い要約を生成するように設計されています。(こちらについては次の章で詳しく説明します!) + +- **2019/2** [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf): これはGPTを改良 & 大規模化したものですが、倫理的な問題から一般公開までには時間がかかったモデルです。 + +- **2019/10** [DistilBERT](https://arxiv.org/abs/1910.01108): これはBERTを60%高速化し40%のメモリ軽量化をしながら、97%の性能を維持した蒸留モデルです。 + +- **2019/10** [BART](https://arxiv.org/abs/1910.13461), [T5](https://arxiv.org/abs/1910.10683): オリジナルのTransformerモデルと同じアーキテクチャを採用した大規模な事前学習済みモデルです。 + +- **2020/5** [GPT-3](https://arxiv.org/abs/2005.14165): GPT-2をさらに大規模化したもので、fine-tuningなし(_zero-shot学習_)で様々なタスクを解くことができるようにしたモデルです。 + +このリストは決して包括的なものではなく、Transformerのモデルの種類をざっくり分けることを意図しています。種類については大きく以下の3つのカテゴリーに分類することができます。 + +- GPT型 (_auto-regressive_ Transformerモデルとも呼ばれます) +- BERT型 (_auto-encoding_ Transformerモデルとも呼ばれます) +- BART/T5型 (_sequence-to-sequence_ Transformerモデルとも呼ばれます) + +これらの種類についてこれから深掘りしていきます。 + +## Transformers = 言語モデル + +GPT、BERT、T5などの上記の全てのモデルは*言語モデル*として学習されています。これは大量の生文に対して自己教師あり学習を行ったことを意味しています。自己教師あり学習は、学習の目的となるものを、モデルに入力するデータから自動で算出する学習方法です。つまりデータに対する人手のラベル付が必要ないことを意味します。 + +このタイプのモデルは、学習させた言語に対する統計的な理解を深めることができますが、特定のタスクにはあまり役に立ちません。従って、一般的な事前学習済みモデルは、この後に*転移学習*と呼ばれるプロセスを経ます。このプロセスでは人手でラベル付されたデータを用いた教師あり学習を行なって、特定のタスクに対してfine-tuningされます。 + +タスク例の1つに、前のいくつかの単語を読んで、それに続く次の単語を予測するものがあります。これは出力が過去と現在の入力にのみ依存し、将来の入力には依存しないため、 *Causal Language Modeling (CLM)* と呼ばれます。 + +
+Example of causal language modeling in which the next word from a sentence is predicted. + +
+ +他の例としては *Masked Language Modeling (MLM)* があり、これは文中のマスクされた(隠された)単語が何かを予測するタスクになっています。 + +
+Example of masked language modeling in which a masked word from a sentence is predicted. + +
+ +## Transformers = 大規模モデル + +前述のDistilBERTなどの例外を除けば、より良いパフォーマンスを達成するための一般的な戦略として、モデルサイズと学習データ量を大きくするというものがあります。 + +
+Number of parameters of recent Transformers models +
+ +残念ながらモデルの学習(特に大規模なモデルの学習)には大量のデータが必要になります。これは時間と計算資源の面で非常にコストがかかります。また、以下のグラフから分かるように、環境にも影響を及ぼすものになります。 + +
+The carbon footprint of a large language model. + +
+ + + +そしてこの図は、事前学習の環境負荷を意識的に減らすことを目的とするチームが率いる、(超大規模)モデルのプロジェクトを示しています。最適なハイパーパラメータを得るための多くの試行による環境負荷は、より大きなものになると考えられます。 + +もし研究チームや学生団体、企業がその度にモデルを一から学習していたらどうでしょうか。これでは膨大で不必要なコストがかかってしまいます。 + +従って、学習済み言語モデルの重みを共有しそれを利用することで、コミュニティ全体の計算コストや環境負荷を削減することができるのです。 + +## 転移学習 + + + +*事前学習*とはモデルを一から学習することです。重みはランダムに初期化され、事前知識なしに学習が開始されます。 + +
+The pretraining of a language model is costly in both time and money. + +
+ +事前学習は大量のデータを使って行われます。よって、非常に大きなデータコーパスを必要とし、学習には数週間かかることがあります。 + +一方で*ファインチューニング*は事前学習の**後に**行われるものです。ファインチューニングを行うには、まず最初に事前学習済みモデルを取得し、次にタスクに応じたデータセットを用いて追加の学習を行います。ここで、「(事前学習を行わずに)初めからこのタスクに対して学習を行えば良いのでは?」と思った方がいるかもしれませんが、これにはいくつかの理由があります。 + +* 事前学習済みモデルは、ファインチューニング用のデータセットと何らかの類似性を持ったデータで既に学習が行われています。このため、ファインチューニングの過程において、事前学習済みモデルが既に獲得した知識を利用することができます。(例えば自然言語処理の問題では、事前学習済みのモデルは言語に対する何らかの統計的な理解をしているはずです。) +* また事前学習済みモデルは大量のデータを使って学習されているので、ファインチューニングでははるかに少ないデータで適切な結果を得ることが可能になります。 +* これと同じ理由で、良い結果を得るために必要な時間や資源を大きく削減することができます。 + +例えば、英語で訓練された事前学習済みモデルをarXivコーパスでファインチューニングすることで、科学/研究ベースのモデルを作ることができます。ファインチューニングは少ないデータで実施できます。これは事前学習済みモデルが獲得していた知識が「転移」しているためで、この特徴から「*転移学習*」と呼ばれているという訳です。 + +
+The fine-tuning of a language model is cheaper than pretraining in both time and money. + +
+ +従って、モデルのファインチューニングに必要な時間、データ、経済的/環境的コストは少なく済みます。また事前学習よりも制約が少ないため、様々なファインチューニングのスキームを素早く簡単に試すことができます。 + +このプロセスは(大量のデータがある場合を除いて)ゼロから学習するよりも良い結果をもたらします。だからこそ(目的のタスクにできるだけ近い)事前学習済みモデルを活用し、それをファインチューニングするべきだと言えます。 + +## 一般的なアーキテクチャ + +このセクションでは、Transformerモデルの一般的なアーキテクチャについて見ていきます。各構成要素については後ほど詳しく説明するので、理解できない部分があっても心配ありません! + + + +## イントロダクション + +モデルは主に2つの要素で構成されます。 + +* **エンコーダー (左)**: エンコーダーは入力を受け取り、その特徴量を生成します。これは入力から理解を得るためにモデルが最適化されることを意味します。 +* **デコーダー (右)**: デコーダーではエンコーダーが生成した特徴量とその他の入力を受け取って、目的の系列を生成します。これは出力を生成するためにモデルが最適化されることを意味します。 + +
+Architecture of a Transformers models + +
+ +これらの構成要素はタスクに応じてそれぞれ別々に使用することができます。 + +* **Encoder-only モデル**: 文章分類や固有表現抽出など入力に対する理解が必要となるタスクに適しています。 +* **Decoder-only モデル**: 文生成などの生成タスクに適しています。 +* **Encoder-Decoder(sequence-to-sequence) モデル**: 翻訳や要約など、入力を要する生成タスクに適しています。 + +これらのアーキテクチャについてはのちのセクションで個別に紹介します。 + + +## アテンション層 + +Transformerモデルは*アテンション層*と呼ばれる特殊な層で構築されていることが大きな特徴となっています。実際にTransformerが登場した論文のタイトルも["Attention Is All You Need"](https://arxiv.org/abs/1706.03762)というものでした! アテンション層については後ほど詳しく説明します。現段階においては、モデルが各単語の特徴量を扱う際に、入力されたテキストのどの単語に注目すべきかをアテンション層が指示してくれる(多かれ少なかれその他の単語は無視される)ということだけ知っておいてもらえれば十分です。 + +このことを理解するために、英語からフランス語への翻訳タスクを考えてみます。"You like this course" という入力があるとき、翻訳モデルは "like" という単語を適切に翻訳するために "You" という隣接する単語に注目する必要があります。これはフランス語の動詞 "like" は主語によって異なる活用がされるためです。ただこのとき、"like" の翻訳に他の単語の情報は役に立ちません。同じように、モデルは "this" という単語を翻訳する際に "course" という単語に注意を払う必要があり、これは "this" という単語の翻訳が関連する名詞が男性か女性かによって変化するためです。この場合においてもその他の単語は "this" の翻訳には関係ありません。より複雑な文(および文法規則)では、モデルは各単語を適切に翻訳するために、文中のより離れた位置に出現する可能性のある単語に対して特別な注意を払う必要があります。 + +単語はそれ自体で意味を持ちますが、その意味は文脈(前後に現れるその他の単語)に大きな影響を受けます。このため、翻訳タスクと同じ考え方が自然言語に関する色々なタスクに対して当てはまります。 + +さて、アテンション層がどのようなものかを理解頂いた上で、Transformerのアーキテクチャをより詳しく見ていきましょう! + +## オリジナルのアーキテクチャ + +Transformerのアーキテクチャは翻訳用に設計されました。学習過程において、エンコーダーはある言語の入力(文章)を受け取り、デコーダーは別言語で書かれた同じ文章を受け取ります。エンコーダーのアテンション層は文中の全ての単語を使うことができます。(先ほど見たように、、ある単語を翻訳するためにはその前後の単語に注意を払う必要があるためです。)一方でデコーダーは逐次的に動作します。このため既に翻訳して生成した単語にしか注意を向けることができません。(言い換えればこれから翻訳して生成される単語に対しては注意が張られないということです。)例えば、翻訳対象の最初の3単語を予測したらそれをデコーダーに渡すことで、デコーダーはエンコーダーに入力された情報を全て使いながら4単語目を予測します。 + +モデルの学習中、学習速度を上げるためにデコーダーには答えとなる翻訳文(ターゲット文)全体が与えられていますが、処理対象となる単語の後に続く単語を使うことは許されていません。例えば、4番目の単語を予測する際、アテンション層はターゲット文の1〜3番目の位置にある単語にしかアクセスすることができません。 + +Transformerのオリジナルのアーキテクチャの概観は、このように左側のエンコーダーと右側のデコーダーからなります。 + +
+Architecture of a Transformers models + +
+ +デコーダーブロックの最初のアテンション層は、デコーダーに対する全ての入力を使うことができますが、2番目のアテンション層はエンコーダーの出力を利用します。従って、入力文全体にアクセスすることで現在の単語の最適な予測が可能になるという訳です。これは言語によって単語の登場順が異なるような文法規則があったり、文の後半で提供される文脈情報が、現在の単語の翻訳に役立つ場合があるので、非常に便利なものとなっています。 + +*attentionマスク*はエンコーダー・デコーダーで、ある特別な単語に注目しないようにするために使用されます。(例えば、文をまとめて入力するときに、全ての文を同じ長さに揃えるために使われるpadding tokenなどです。) + +## アーキテクチャ vs. チェックポイント + +このコースでTransformerモデルについて掘り下げていくと、*モデル*と同様に*アーキテクチャ*や*チェックポイント*という単語についても言及されていることがわかります。これらの用語はそれぞれ少しずつ異なる意味を持っています。 + + +* **アーキテクチャ**: これはモデルの骨格を意味し、モデル内の各層と内部で起こる操作を定義したものになります。 +* **チェックポイント**: これは与えられたアーキテクチャに対して読み込まれる重みを意味します。 +* **モデル**: これは「アーキテクチャ」や「チェックポイント」ほど正確ではない、より包括的な用語で両方を意味することがあります。このコースでは曖昧さを回避するために、重要な場合は*アーキテクチャ*や*チェックポイント*を使うことにします。 + +例えばBERTはアーキテクチャを指し、`bert-base-cased`はGoogleの開発チームがBERTの最初のリリースのために用意した重みを指したチェックポイントとなります。しかしながら"BERTモデル"や"`bert-base-cased`モデル"と呼ぶこともできます。 diff --git a/chapters/ja/chapter1/5.mdx b/chapters/ja/chapter1/5.mdx new file mode 100644 index 000000000..894cc748c --- /dev/null +++ b/chapters/ja/chapter1/5.mdx @@ -0,0 +1,22 @@ +# エンコーダーモデル + + + + + +エンコーダーモデルとは、Transformerモデルのエンコーダーのみを使用したモデルを指します。 処理の各段階で、attention層は最初の文の全ての単語にアクセスすることができます。 これらのモデルは "bi-directional"(双方向)のattentionを持つものとして特徴付けられ、*オートエンコーダーモデル*と呼ばれます。 + +これらのモデルの事前学習は、何らかの方法で(例えば文中の単語をランダムにマスクするなどで)文を壊し、この文の再構築をタスクとして解くことを中心に展開されます。 + +エンコーダーモデルは、文の分類 ・ 固有表現認識(より一般的には単語の分類) ・ 抽出的質問応答など、文全体の理解を必要とするタスクに最も適しています。 + +エンコーダーモデルでは以下のものが代表的です: + +- [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) diff --git a/chapters/ja/chapter1/6.mdx b/chapters/ja/chapter1/6.mdx new file mode 100644 index 000000000..6044ae35e --- /dev/null +++ b/chapters/ja/chapter1/6.mdx @@ -0,0 +1,22 @@ +# デコーダーモデル + + + + + +デコーダーモデルとは、Transformerモデルのデコーダーのみを使用したモデルを指します。 処理の各段階で、処理対象の単語について、attention層はその単語より前に出現した単語にのみアクセスすることができます。 このようなモデルは*自己回帰モデル*と呼ばれます。 + + デコーダーモデルの事前学習は、次に続く単語を予測するタスクを解くことを中心に展開されます。 + +これらのモデルは、文を生成するタスクに最も適しています。 + + +デコーダーモデルでは以下のものが代表的です: + +- [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) +- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) +- [GPT-2](https://huggingface.co/transformers/model_doc/gpt2.html) +- [Transformer XL](https://huggingface.co/transformers/model_doc/transfo-xl.html) diff --git a/chapters/ja/chapter1/7.mdx b/chapters/ja/chapter1/7.mdx new file mode 100644 index 000000000..e4bf8ea27 --- /dev/null +++ b/chapters/ja/chapter1/7.mdx @@ -0,0 +1,23 @@ +# Sequence-to-sequence モデル + + + + + +Encoder-decoderモデル(*sequence-to-sequence models*とも呼ばれる)はTransformerアーキテクチャのエンコーダーとデコーダーの両方を使用します。 +それぞれのステージにおいて、エンコーダーのアテンション層は入力文のすべての単語にアクセスできるのに対して、デコーダーのアテンション層は入力中のある単語の前に位置する単語にのみアクセスできます。 + +これらのモデルの事前学習は、エンコーダー、またはデコーダーの学習と同じように行われますが、通常はより複雑な方法を含みます。 +例えば、[T5](https://huggingface.co/t5-base) は、特殊な単語で文中のスパン(複数の単語を含むことができる)をランダムにマスクしたときに、そのマスクされた文を予測する事を目的として事前学習されています。 + +Sequence-to-sequenceモデルは、要約、翻訳、質問応答生成などのように、与えられた入力文に対して新しい文を生成するタスクにとても適しています。 + +これらの系統のモデルの代表は次のとおりです: + +- [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) diff --git a/chapters/ja/chapter1/8.mdx b/chapters/ja/chapter1/8.mdx new file mode 100644 index 000000000..e81b75dc4 --- /dev/null +++ b/chapters/ja/chapter1/8.mdx @@ -0,0 +1,35 @@ +# バイアスと限界 + + + +事前学習済みモデルやファインチューニング済みのモデルを使う場合、これらのモデルは強力なツールですが、一方で限界もあることに注意しなければなりません。 +その代表例は、大量のデータによる事前学習を行うために、研究者はインターネット上にある利用可能なデータを良いものから悪いものまで手当たりしだいに集めてしまうことです。 + +簡単に説明するために、BERTによる`fill-mask`パイプラインの例に戻りましょう: + + +```python +from transformers import pipeline + +unmasker = pipeline("fill-mask", model="bert-base-uncased") +result = unmasker("This man works as a [MASK].") +print([r["token_str"] for r in result]) + +result = unmasker("This woman works as a [MASK].") +print([r["token_str"] for r in result]) +``` + +```python out +['lawyer', 'carpenter', 'doctor', 'waiter', 'mechanic'] +['nurse', 'waitress', 'teacher', 'maid', 'prostitute'] +``` + +これらの2つの文の欠落した単語を埋めさせたときに、モデルはジェンダーフリーの回答を一つだけしか与えません(waiter/waitress)。他はたいていの場合、特定の性別と関連付けられる職業です。そして、モデルは「女性」と「仕事」から連想される可能性のある職業としてトップ5に「売春婦(prostitute)」を上げています。 +BERTはインターネット上のあらゆるところからデータをかき集めて構築されたのではなく、中立的なデータ([English Wikipedia](https://huggingface.co/datasets/wikipedia)と[BookCorpus](https://huggingface.co/datasets/bookcorpus)を用いて学習されています) を用いて構築されためずらしいTransformerモデルであるにも関わらず、このような現象が発生してしまいます。 + +したがって、これらのツールを使用する際は、オリジナルのモデルがとても簡単に性的、差別的、あるいは同性愛嫌悪のコンテンツを生成してしまうことを念頭に置く必要があります。この本質的なバイアスは、あるデータでファインチューニングしても消えることはありません。 \ No newline at end of file diff --git a/chapters/ja/chapter1/9.mdx b/chapters/ja/chapter1/9.mdx new file mode 100644 index 000000000..4299784d1 --- /dev/null +++ b/chapters/ja/chapter1/9.mdx @@ -0,0 +1,16 @@ +# まとめ + + + +この章では、🤗 Transformersが提供する高レベルな`pipeline()` 関数を用いて、異なるNLPタスクにアプローチする方法を学びました。また、同様にHubを用いてモデルを探す方法や、推論APIを使ってブラウザ上でモデルを直接テストする方法も学びました。 + +さらに、Transformerモデルがどのように動作するかを高いレベルで議論し、さらに転移学習やファインチューニングの重要性について話しました。一つの重要な観点は、解きたいタスクに応じてアーキテクチャ全体を用いることや、エンコーダーやデコーダーの一方だけを用いることができるという点です。以下の表はそのまとめです。 + +| モデル | 例 | タスク | +|-----------------|--------------------------------------------|----------------------------------------------------------------------------------| +| Encoder | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa |文章分類, 固有表現抽出, 抽出型質問応答 | +| Decoder | CTRL, GPT, GPT-2, Transformer XL | 文章生成 | +| Encoder-decoder | BART, T5, Marian, mBART | 文章要約, 翻訳, 生成型質問応答  | diff --git a/chapters/pt/_toctree.yml b/chapters/pt/_toctree.yml index 662e840c5..2078cf8df 100644 --- a/chapters/pt/_toctree.yml +++ b/chapters/pt/_toctree.yml @@ -47,6 +47,11 @@ - local: chapter2/8 title: Questionário de fim de capítulo quiz: 2 + +- title: 3. Ajustando um modelo pré treinado + sections: + - local: chapter3/1 + title: Introdução - title: 4. Compartilhamento de modelos e tokenizer sections: diff --git a/chapters/pt/chapter3/1.mdx b/chapters/pt/chapter3/1.mdx new file mode 100644 index 000000000..0166506f0 --- /dev/null +++ b/chapters/pt/chapter3/1.mdx @@ -0,0 +1,27 @@ + + +# Introdução + + + +No [Capítulo 2](/course/chapter2) exploramos como utilizar tokenizadores e modelos pré treinados para fazer previsões. Mas e se você quiser ajustar um modelo pré-treinado para seu próprio dataset? Esse é o tema deste capítulo! Você vai aprender: + +{#if fw === 'pt'} +* Como preparar um datset grande do Hub +* Como usar a API de alto nível `Trainer` para ajustar um modelo +* Como usar um loop de treinamento personalizado +* Como usar a biblioteca 🤗 Accelerate para executar facilmente esse loop de treinamento personalizado em qualquer configuração distribuída +{#else} + +{:else} +* Como preparar um dataset grande do Hub +* Como usar Keras para ajustar um modelo +* Como usar Keras para fazer previsões +* Como usar uma métrica personalizada + +{/if} + +Para fazer upload de seus checkpoints treinados para o Hugging Face Hub, você precisará de uma conta huggingface.co: [crie uma conta](https://huggingface.co/join) \ No newline at end of file diff --git a/chapters/ru/_toctree.yml b/chapters/ru/_toctree.yml index 12777ffce..643aef481 100644 --- a/chapters/ru/_toctree.yml +++ b/chapters/ru/_toctree.yml @@ -81,9 +81,9 @@ - local: chapter5/7 title: 🤗 Datasets, итоги! - local: chapter5/8 - title: Тест по главе 5 + title: Тест по главе 5 - title: 6. Бибилиотека 🤗 Tokenizers - sections: + sections: - local: chapter6/1 title: Введение - local: chapter6/2 diff --git a/chapters/ru/chapter3/2.mdx b/chapters/ru/chapter3/2.mdx index 231c33e65..250e553b1 100644 --- a/chapters/ru/chapter3/2.mdx +++ b/chapters/ru/chapter3/2.mdx @@ -23,8 +23,7 @@ {/if} {#if fw === 'pt'} -Продолжим с примером из [предыдущей главы](/course/ru/chapter2) -Continuing with the example from the [previous chapter](/course/ru/chapter2), вот как мы будем обучать классификатор последовательности на одном батче с помощью PyTorch: +Продолжим с примером из [предыдущей главы](/course/ru/chapter2), вот как мы будем обучать классификатор последовательности на одном батче с помощью PyTorch: ```python import torch From 707ae8d629f10b05f11d4f854cf003c6e98368a9 Mon Sep 17 00:00:00 2001 From: lewtun Date: Tue, 22 Nov 2022 16:27:53 +0100 Subject: [PATCH 40/51] Bump release (#381) --- .github/workflows/build_pr_documentation.yml | 5 +- .github/workflows/delete_doc_comment.yml | 7 +- README.md | 2 +- chapters/en/chapter0/1.mdx | 8 +- chapters/en/chapter1/1.mdx | 10 +- chapters/en/chapter1/10.mdx | 5 +- chapters/en/chapter1/2.mdx | 6 +- chapters/en/chapter1/3.mdx | 24 +- chapters/en/chapter1/4.mdx | 20 +- chapters/en/chapter1/5.mdx | 2 +- chapters/en/chapter1/6.mdx | 2 +- chapters/en/chapter1/7.mdx | 2 +- chapters/en/chapter1/8.mdx | 2 +- chapters/en/chapter1/9.mdx | 2 +- chapters/en/chapter2/1.mdx | 2 +- chapters/en/chapter2/2.mdx | 16 +- chapters/en/chapter2/3.mdx | 12 +- chapters/en/chapter2/4.mdx | 22 +- chapters/en/chapter2/5.mdx | 10 +- chapters/en/chapter2/6.mdx | 6 +- chapters/en/chapter2/7.mdx | 2 +- chapters/en/chapter2/8.mdx | 2 +- chapters/en/chapter3/1.mdx | 2 +- chapters/en/chapter3/2.mdx | 8 +- chapters/en/chapter3/3.mdx | 6 +- chapters/en/chapter3/3_tf.mdx | 8 +- chapters/en/chapter3/4.mdx | 10 +- chapters/en/chapter3/5.mdx | 2 +- chapters/en/chapter3/6.mdx | 2 +- chapters/en/chapter4/1.mdx | 2 +- chapters/en/chapter4/2.mdx | 2 +- chapters/en/chapter4/3.mdx | 16 +- chapters/en/chapter4/4.mdx | 28 +- chapters/en/chapter4/5.mdx | 2 +- chapters/en/chapter4/6.mdx | 2 +- chapters/en/chapter5/1.mdx | 2 +- chapters/en/chapter5/2.mdx | 8 +- chapters/en/chapter5/3.mdx | 14 +- chapters/en/chapter5/4.mdx | 8 +- chapters/en/chapter5/5.mdx | 12 +- chapters/en/chapter5/6.mdx | 10 +- chapters/en/chapter5/7.mdx | 2 +- chapters/en/chapter5/8.mdx | 2 +- chapters/en/chapter6/1.mdx | 2 +- chapters/en/chapter6/10.mdx | 2 +- chapters/en/chapter6/2.mdx | 8 +- chapters/en/chapter6/3.mdx | 12 +- chapters/en/chapter6/3b.mdx | 8 +- chapters/en/chapter6/4.mdx | 10 +- chapters/en/chapter6/5.mdx | 8 +- chapters/en/chapter6/6.mdx | 8 +- chapters/en/chapter6/7.mdx | 10 +- chapters/en/chapter6/8.mdx | 10 +- chapters/en/chapter6/9.mdx | 2 +- chapters/en/chapter7/1.mdx | 2 +- chapters/en/chapter7/2.mdx | 34 +- chapters/en/chapter7/3.mdx | 18 +- chapters/en/chapter7/4.mdx | 28 +- chapters/en/chapter7/5.mdx | 27 +- chapters/en/chapter7/6.mdx | 14 +- chapters/en/chapter7/7.mdx | 28 +- chapters/en/chapter7/8.mdx | 2 +- chapters/en/chapter7/9.mdx | 2 +- chapters/en/chapter8/1.mdx | 2 +- chapters/en/chapter8/2.mdx | 6 +- chapters/en/chapter8/3.mdx | 12 +- chapters/en/chapter8/4.mdx | 30 +- chapters/en/chapter8/4_tf.mdx | 24 +- chapters/en/chapter8/5.mdx | 16 +- chapters/en/chapter8/6.mdx | 2 +- chapters/en/chapter8/7.mdx | 2 +- chapters/en/chapter9/1.mdx | 8 +- chapters/en/chapter9/2.mdx | 10 +- chapters/en/chapter9/3.mdx | 18 +- chapters/en/chapter9/4.mdx | 14 +- chapters/en/chapter9/5.mdx | 12 +- chapters/en/chapter9/6.mdx | 10 +- chapters/en/chapter9/7.mdx | 24 +- chapters/en/chapter9/8.mdx | 4 +- chapters/en/chapter9/9.mdx | 2 +- chapters/en/events/1.mdx | 6 +- chapters/en/events/2.mdx | 6 +- chapters/en/events/3.mdx | 2 +- chapters/fr/chapter0/1.mdx | 220 ++++----- chapters/fr/chapter7/2.mdx | 4 +- chapters/fr/chapter7/3.mdx | 4 +- chapters/fr/chapter7/4.mdx | 4 +- chapters/fr/chapter7/5.mdx | 4 +- chapters/fr/chapter7/6.mdx | 4 +- chapters/fr/chapter7/7.mdx | 4 +- chapters/fr/chapter8/4.mdx | 4 +- chapters/fr/chapter9/2.mdx | 6 +- chapters/fr/chapter9/3.mdx | 6 +- chapters/fr/chapter9/4.mdx | 4 +- chapters/fr/chapter9/5.mdx | 2 +- chapters/fr/chapter9/6.mdx | 4 +- chapters/fr/chapter9/7.mdx | 10 +- chapters/fr/events/1.mdx | 98 ++-- chapters/hi/chapter0/1.mdx | 220 ++++----- chapters/it/_toctree.yml | 13 +- chapters/it/chapter8/4.mdx | 4 +- chapters/it/chapter9/1.mdx | 37 ++ chapters/it/chapter9/2.mdx | 118 +++++ chapters/it/chapter9/3.mdx | 186 ++++++++ chapters/ja/chapter7/2.mdx | 2 +- chapters/ja/chapter7/3.mdx | 2 +- chapters/ja/chapter7/4.mdx | 2 +- chapters/ja/chapter7/5.mdx | 2 +- chapters/ja/chapter7/6.mdx | 2 +- chapters/ja/chapter7/7.mdx | 2 +- chapters/ko/_toctree.yml | 6 +- chapters/ko/chapter2/1.mdx | 24 + chapters/vi/chapter7/2.mdx | 2 +- chapters/vi/chapter7/3.mdx | 2 +- chapters/vi/chapter7/4.mdx | 2 +- chapters/vi/chapter7/5.mdx | 2 +- chapters/vi/chapter7/6.mdx | 2 +- chapters/vi/chapter7/7.mdx | 2 +- chapters/vi/chapter8/4.mdx | 4 +- chapters/vi/chapter9/1.mdx | 6 +- chapters/vi/chapter9/2.mdx | 6 +- chapters/vi/chapter9/3.mdx | 328 ++++++------- chapters/vi/chapter9/4.mdx | 4 +- chapters/vi/chapter9/5.mdx | 6 +- chapters/vi/chapter9/6.mdx | 4 +- chapters/vi/chapter9/7.mdx | 10 +- chapters/vi/chapter9/9.mdx | 468 +++++++++---------- chapters/zh-CN/chapter7/2.mdx | 4 +- chapters/zh-CN/chapter7/3.mdx | 4 +- chapters/zh-CN/chapter7/4.mdx | 4 +- chapters/zh-CN/chapter7/5.mdx | 4 +- chapters/zh-CN/chapter7/6.mdx | 4 +- chapters/zh-CN/chapter7/7.mdx | 4 +- chapters/zh-CN/chapter8/4.mdx | 4 +- chapters/zh-CN/chapter9/1.mdx | 6 +- chapters/zh-CN/chapter9/2.mdx | 6 +- chapters/zh-CN/chapter9/3.mdx | 6 +- chapters/zh-CN/chapter9/4.mdx | 4 +- chapters/zh-CN/chapter9/5.mdx | 6 +- chapters/zh-CN/chapter9/6.mdx | 4 +- chapters/zh-CN/chapter9/7.mdx | 10 +- 141 files changed, 1525 insertions(+), 1153 deletions(-) create mode 100644 chapters/it/chapter9/1.mdx create mode 100644 chapters/it/chapter9/2.mdx create mode 100644 chapters/it/chapter9/3.mdx create mode 100644 chapters/ko/chapter2/1.mdx diff --git a/.github/workflows/build_pr_documentation.yml b/.github/workflows/build_pr_documentation.yml index 5ae7db12d..45e3b3e09 100644 --- a/.github/workflows/build_pr_documentation.yml +++ b/.github/workflows/build_pr_documentation.yml @@ -9,7 +9,7 @@ concurrency: jobs: build: - uses: huggingface/doc-builder/.github/workflows/build_pr_documentation.yml@use_hf_hub + uses: huggingface/doc-builder/.github/workflows/build_pr_documentation.yml@main with: commit_sha: ${{ github.event.pull_request.head.sha }} pr_number: ${{ github.event.number }} @@ -18,6 +18,3 @@ 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 hub_base_path: https://moon-ci-docs.huggingface.co - secrets: - token: ${{ secrets.HF_DOC_PUSH }} - comment_bot_token: ${{ secrets.HUGGINGFACE_PUSH }} diff --git a/.github/workflows/delete_doc_comment.yml b/.github/workflows/delete_doc_comment.yml index 0ec59d485..9ec2aaf44 100644 --- a/.github/workflows/delete_doc_comment.yml +++ b/.github/workflows/delete_doc_comment.yml @@ -7,10 +7,7 @@ on: jobs: delete: - uses: huggingface/doc-builder/.github/workflows/delete_doc_comment.yml@use_hf_hub + uses: huggingface/doc-builder/.github/workflows/delete_doc_comment.yml@main with: pr_number: ${{ github.event.number }} - package: course - secrets: - token: ${{ secrets.HF_DOC_PUSH }} - comment_bot_token: ${{ secrets.HUGGINGFACE_PUSH }} \ No newline at end of file + package: course \ No newline at end of file diff --git a/README.md b/README.md index db20052fa..67b42d9b4 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ This repo contains the content that's used to create the **[Hugging Face course] | [Bahasa Indonesia](https://huggingface.co/course/id/chapter1/1) (WIP) | [`chapters/id`](https://github.com/huggingface/course/tree/main/chapters/id) | [@gstdl](https://github.com/gstdl) | | [Italian](https://huggingface.co/course/it/chapter1/1) (WIP) | [`chapters/it`](https://github.com/huggingface/course/tree/main/chapters/it) | [@CaterinaBi](https://github.com/CaterinaBi), [@ClonedOne](https://github.com/ClonedOne), [@Nolanogenn](https://github.com/Nolanogenn), [@EdAbati](https://github.com/EdAbati), [@gdacciaro](https://github.com/gdacciaro) | | [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) | +| [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) | | [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) | | [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) | diff --git a/chapters/en/chapter0/1.mdx b/chapters/en/chapter0/1.mdx index 6ab7c8e23..0f8bac262 100644 --- a/chapters/en/chapter0/1.mdx +++ b/chapters/en/chapter0/1.mdx @@ -1,4 +1,4 @@ -# Introduction +# Introduction[[introduction]] Welcome to the Hugging Face course! This introduction will guide you through setting up a working environment. If you're just starting the course, we recommend you first take a look at [Chapter 1](/course/chapter1), then come back and set up your environment so you can try the code yourself. @@ -10,7 +10,7 @@ Note that we will not be covering the Windows system. If you're running on Windo Most of the course relies on you having a Hugging Face account. We recommend creating one now: [create an account](https://huggingface.co/join). -## Using a Google Colab notebook +## Using a Google Colab notebook[[using-a-google-colab-notebook]] Using a Colab notebook is the simplest possible setup; boot up a notebook in your browser and get straight to coding! @@ -46,7 +46,7 @@ This installs a very light version of 🤗 Transformers. In particular, no speci This will take a bit of time, but then you'll be ready to go for the rest of the course! -## Using a Python virtual environment +## Using a Python virtual environment[[using-a-python-virtual-environment]] If you prefer to use a Python virtual environment, the first step is to install Python on your system. We recommend following [this guide](https://realpython.com/installing-python/) to get started. @@ -99,7 +99,7 @@ which python /home//transformers-course/.env/bin/python ``` -### Installing dependencies +### Installing dependencies[[installing-dependencies]] As in the previous section on using Google Colab instances, you'll now need to install the packages required to continue. Again, you can install the development version of 🤗 Transformers using the `pip` package manager: diff --git a/chapters/en/chapter1/1.mdx b/chapters/en/chapter1/1.mdx index 2c66a5250..e828633c7 100644 --- a/chapters/en/chapter1/1.mdx +++ b/chapters/en/chapter1/1.mdx @@ -1,18 +1,18 @@ -# Introduction +# Introduction[[introduction]] -## Welcome to the 🤗 Course! +## Welcome to the 🤗 Course![[welcome-to-the-course]] This course will teach you about natural language processing (NLP) using libraries from the [Hugging Face](https://huggingface.co/) ecosystem — [🤗 Transformers](https://github.com/huggingface/transformers), [🤗 Datasets](https://github.com/huggingface/datasets), [🤗 Tokenizers](https://github.com/huggingface/tokenizers), and [🤗 Accelerate](https://github.com/huggingface/accelerate) — as well as the [Hugging Face Hub](https://huggingface.co/models). It's completely free and without ads. -## What to expect? +## What to expect?[[what-to-expect]] Here is a brief overview of the course: @@ -33,7 +33,7 @@ This course: After you've completed this course, we recommend checking out DeepLearning.AI's [Natural Language Processing Specialization](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh), which covers a wide range of traditional NLP models like naive Bayes and LSTMs that are well worth knowing about! -## Who are we? +## Who are we?[[who-are-we]] About the authors: @@ -55,7 +55,7 @@ About the authors: **Leandro von Werra** is a machine learning engineer in the open-source team at Hugging Face and also a co-author of the O’Reilly book [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). He has several years of industry experience bringing NLP projects to production by working across the whole machine learning stack.. -## FAQ +## FAQ[[faq]] Here are some answers to frequently asked questions: diff --git a/chapters/en/chapter1/10.mdx b/chapters/en/chapter1/10.mdx index 7c1b22080..cb0ca145c 100644 --- a/chapters/en/chapter1/10.mdx +++ b/chapters/en/chapter1/10.mdx @@ -1,6 +1,6 @@ -# End-of-chapter quiz +# End-of-chapter quiz[[end-of-chapter-quiz]] -### 7. Select the sentence that best describes the terms "model," "architecture," and "weights." +### 7. Select the sentence that best describes the terms "model", "architecture", and "weights". setup. -## Transformers are everywhere! +## Transformers are everywhere![[transformers-are-everywhere]] Transformer models are used to solve all kinds of NLP tasks, like the ones mentioned in the previous section. Here are some of the companies and organizations using Hugging Face and Transformer models, who also contribute back to the community by sharing their models: @@ -29,7 +29,7 @@ The [🤗 Transformers library](https://github.com/huggingface/transformers) pro Before diving into how Transformer models work under the hood, let's look at a few examples of how they can be used to solve some interesting NLP problems. -## Working with pipelines +## Working with pipelines[[working-with-pipelines]] @@ -82,7 +82,7 @@ Some of the currently [available pipelines](https://huggingface.co/transformers/ Let's have a look at a few of these! -## Zero-shot classification +## Zero-shot classification[[zero-shot-classification]] We'll start by tackling a more challenging task where we need to classify texts that haven't been labelled. This is a common scenario in real-world projects because annotating text is usually time-consuming and requires domain expertise. For this use case, the `zero-shot-classification` pipeline is very powerful: it allows you to specify which labels to use for the classification, so you don't have to rely on the labels of the pretrained model. You've already seen how the model can classify a sentence as positive or negative using those two labels — but it can also classify the text using any other set of labels you like. @@ -111,7 +111,7 @@ This pipeline is called _zero-shot_ because you don't need to fine-tune the mode -## Text generation +## Text generation[[text-generation]] Now let's see how to use a pipeline to generate some text. The main idea here is that you provide a prompt and the model will auto-complete it by generating the remaining text. This is similar to the predictive text feature that is found on many phones. Text generation involves randomness, so it's normal if you don't get the same results as shown below. @@ -139,7 +139,7 @@ You can control how many different sequences are generated with the argument `nu -## Using any model from the Hub in a pipeline +## Using any model from the Hub in a pipeline[[using-any-model-from-the-hub-in-a-pipeline]] The previous examples used the default model for the task at hand, but you can also choose a particular model from the Hub to use in a pipeline for a specific task — say, text generation. Go to the [Model Hub](https://huggingface.co/models) and click on the corresponding tag on the left to display only the supported models for that task. You should get to a page like [this one](https://huggingface.co/models?pipeline_tag=text-generation). @@ -174,13 +174,13 @@ Once you select a model by clicking on it, you'll see that there is a widget ena -### The Inference API +### The Inference API[[the-inference-api]] All the models can be tested directly through your browser using the Inference API, which is available on the Hugging Face [website](https://huggingface.co/). You can play with the model directly on this page by inputting custom text and watching the model process the input data. The Inference API that powers the widget is also available as a paid product, which comes in handy if you need it for your workflows. See the [pricing page](https://huggingface.co/pricing) for more details. -## Mask filling +## Mask filling[[mask-filling]] The next pipeline you'll try is `fill-mask`. The idea of this task is to fill in the blanks in a given text: @@ -210,7 +210,7 @@ The `top_k` argument controls how many possibilities you want to be displayed. N -## Named entity recognition +## Named entity recognition[[named-entity-recognition]] Named entity recognition (NER) is a task where the model has to find which parts of the input text correspond to entities such as persons, locations, or organizations. Let's look at an example: @@ -238,7 +238,7 @@ We pass the option `grouped_entities=True` in the pipeline creation function to -## Question answering +## Question answering[[question-answering]] The `question-answering` pipeline answers questions using information from a given context: @@ -258,7 +258,7 @@ question_answerer( Note that this pipeline works by extracting information from the provided context; it does not generate the answer. -## Summarization +## Summarization[[summarization]] Summarization is the task of reducing a text into a shorter text while keeping all (or most) of the important aspects referenced in the text. Here's an example: @@ -303,7 +303,7 @@ summarizer( Like with text generation, you can specify a `max_length` or a `min_length` for the result. -## Translation +## Translation[[translation]] For translation, you can use a default model if you provide a language pair in the task name (such as `"translation_en_to_fr"`), but the easiest way is to pick the model you want to use on the [Model Hub](https://huggingface.co/models). Here we'll try translating from French to English: diff --git a/chapters/en/chapter1/4.mdx b/chapters/en/chapter1/4.mdx index 6792a8a57..7097771f9 100644 --- a/chapters/en/chapter1/4.mdx +++ b/chapters/en/chapter1/4.mdx @@ -1,4 +1,4 @@ -# How do Transformers work? +# How do Transformers work?[[how-do-transformers-work]]
-## Transformers are big models +## Transformers are big models[[transformers-are-big-models]] Apart from a few outliers (like DistilBERT), the general strategy to achieve better performance is by increasing the models' sizes as well as the amount of data they are pretrained on. @@ -82,7 +82,7 @@ Imagine if each time a research team, a student organization, or a company wante This is why sharing language models is paramount: sharing the trained weights and building on top of already trained weights reduces the overall compute cost and carbon footprint of the community. -## Transfer Learning +## Transfer Learning[[transfer-learning]] @@ -112,13 +112,13 @@ Fine-tuning a model therefore has lower time, data, financial, and environmental This process will also achieve better results than training from scratch (unless you have lots of data), which is why you should always try to leverage a pretrained model -- one as close as possible to the task you have at hand -- and fine-tune it. -## General architecture +## General architecture[[general-architecture]] In this section, we'll go over the general architecture of the Transformer model. Don't worry if you don't understand some of the concepts; there are detailed sections later covering each of the components. -## Introduction +## Introduction[[introduction]] The model is primarily composed of two blocks: @@ -138,7 +138,7 @@ Each of these parts can be used independently, depending on the task: We will dive into those architectures independently in later sections. -## Attention layers +## Attention layers[[attention-layers]] 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. @@ -148,7 +148,7 @@ The same concept applies to any task associated with natural language: a word by Now that you have an idea of what attention layers are all about, let's take a closer look at the Transformer architecture. -## The original architecture +## The original architecture[[the-original-architecture]] The Transformer architecture was originally designed for translation. During training, the encoder receives inputs (sentences) in a certain language, while the decoder receives the same sentences in the desired target language. In the encoder, the attention layers can use all the words in a sentence (since, as we just saw, the translation of a given word can be dependent on what is after as well as before it in the sentence). The decoder, however, works sequentially and can only pay attention to the words in the sentence that it has already translated (so, only the words before the word currently being generated). For example, when we have predicted the first three words of the translated target, we give them to the decoder which then uses all the inputs of the encoder to try to predict the fourth word. @@ -165,7 +165,7 @@ Note that the first attention layer in a decoder block pays attention to all (pa The *attention mask* can also be used in the encoder/decoder to prevent the model from paying attention to some special words -- for instance, the special padding word used to make all the inputs the same length when batching together sentences. -## Architectures vs. checkpoints +## Architectures vs. checkpoints[[architecture-vs-checkpoints]] As we dive into Transformer models in this course, you'll see mentions of *architectures* and *checkpoints* as well as *models*. These terms all have slightly different meanings: diff --git a/chapters/en/chapter1/5.mdx b/chapters/en/chapter1/5.mdx index 59c9d3a5a..477dd128f 100644 --- a/chapters/en/chapter1/5.mdx +++ b/chapters/en/chapter1/5.mdx @@ -1,4 +1,4 @@ -# Encoder models +# Encoder models[[encoder-models]] -# Behind the pipeline +# Behind the pipeline[[behind-the-pipeline]] {#if fw === 'pt'} @@ -62,7 +62,7 @@ As we saw in [Chapter 1](/course/chapter1), this pipeline groups together three Let's quickly go over each of these. -## Preprocessing with a tokenizer +## Preprocessing with a tokenizer[[preprocessing-with-a-tokenizer]] Like other neural networks, Transformer models can't process raw text directly, so the first step of our pipeline is to convert the text inputs into numbers that the model can make sense of. To do this we use a *tokenizer*, which will be responsible for: @@ -121,7 +121,7 @@ Here's what the results look like as PyTorch tensors: ]), '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] ]) } ``` @@ -139,7 +139,7 @@ Here's what the results look like as TensorFlow tensors: 'attention_mask': } ``` @@ -147,7 +147,7 @@ Here's what the results look like as TensorFlow tensors: The output itself is a dictionary containing two keys, `input_ids` and `attention_mask`. `input_ids` contains two rows of integers (one for each sentence) that are the unique identifiers of the tokens in each sentence. We'll explain what the `attention_mask` is later in this chapter. -## Going through the model +## Going through the model[[going-through-the-model]] {#if fw === 'pt'} We can download our pretrained model the same way we did with our tokenizer. 🤗 Transformers provides an `AutoModel` class which also has a `from_pretrained()` method: @@ -177,7 +177,7 @@ If this doesn't make sense, don't worry about it. We'll explain it all later. While these hidden states can be useful on their own, they're usually inputs to another part of the model, known as the *head*. In [Chapter 1](/course/chapter1), the different tasks could have been performed with the same architecture, but each of these tasks will have a different head associated with it. -### A high-dimensional vector? +### A high-dimensional vector?[[a-high-dimensional-vector]] The vector output by the Transformer module is usually large. It generally has three dimensions: @@ -211,7 +211,7 @@ print(outputs.last_hidden_state.shape) Note that the outputs of 🤗 Transformers models behave like `namedtuple`s or dictionaries. You can access the elements by attributes (like we did) or by key (`outputs["last_hidden_state"]`), or even by index if you know exactly where the thing you are looking for is (`outputs[0]`). -### Model heads: Making sense out of numbers +### Model heads: Making sense out of numbers[[model-heads-making-sense-out-of-numbers]] The model heads take the high-dimensional vector of hidden states as input and project them onto a different dimension. They are usually composed of one or a few linear layers: @@ -275,7 +275,7 @@ torch.Size([2, 2]) Since we have just two sentences and two labels, the result we get from our model is of shape 2 x 2. -## Postprocessing the output +## Postprocessing the output[[postprocessing-the-output]] The values we get as output from our model don't necessarily make sense by themselves. Let's take a look: diff --git a/chapters/en/chapter2/3.mdx b/chapters/en/chapter2/3.mdx index 33ef89bcb..acc653704 100644 --- a/chapters/en/chapter2/3.mdx +++ b/chapters/en/chapter2/3.mdx @@ -1,6 +1,6 @@ -# Models +# Models[[models]] {#if fw === 'pt'} @@ -42,7 +42,7 @@ The `TFAutoModel` class and all of its relatives are actually simple wrappers ov However, if you know the type of model you want to use, you can use the class that defines its architecture directly. Let's take a look at how this works with a BERT model. -## Creating a Transformer +## Creating a Transformer[[creating-a-transformer]] The first thing we'll need to do to initialize a BERT model is load a configuration object: @@ -88,7 +88,7 @@ BertConfig { While you haven't seen what all of these attributes do yet, you should recognize some of them: the `hidden_size` attribute defines the size of the `hidden_states` vector, and `num_hidden_layers` defines the number of layers the Transformer model has. -### Different loading methods +### Different loading methods[[different-loading-methods]] Creating a model from the default configuration initializes it with random values: @@ -144,7 +144,7 @@ The weights have been downloaded and cached (so future calls to the `from_pretra The identifier used to load the model can be the identifier of any model on the Model Hub, as long as it is compatible with the BERT architecture. The entire list of available BERT checkpoints can be found [here](https://huggingface.co/models?filter=bert). -### Saving methods +### Saving methods[[saving-methods]] Saving a model is as easy as loading one — we use the `save_pretrained()` method, which is analogous to the `from_pretrained()` method: @@ -178,7 +178,7 @@ The *tf_model.h5* file is known as the *state dictionary*; it contains all your {/if} -## Using a Transformer model for inference +## Using a Transformer model for inference[[using-a-transformer-model-for-inference]] Now that you know how to load and save a model, let's try using it to make some predictions. Transformer models can only process numbers — numbers that the tokenizer generates. But before we discuss tokenizers, let's explore what inputs the model accepts. @@ -216,7 +216,7 @@ model_inputs = tf.constant(encoded_sequences) ``` {/if} -### Using the tensors as inputs to the model +### Using the tensors as inputs to the model[[using-the-tensors-as-inputs-to-the-model]] Making use of the tensors with the model is extremely simple — we just call the model with the inputs: diff --git a/chapters/en/chapter2/4.mdx b/chapters/en/chapter2/4.mdx index 4545d2403..30167ddbd 100644 --- a/chapters/en/chapter2/4.mdx +++ b/chapters/en/chapter2/4.mdx @@ -1,6 +1,6 @@ -# Tokenizers +# Tokenizers[[tokenizers]] {#if fw === 'pt'} @@ -36,7 +36,7 @@ However, models can only process numbers, so we need to find a way to convert th Let's take a look at some examples of tokenization algorithms, and try to answer some of the questions you may have about tokenization. -## Word-based +## Word-based[[word-based]] @@ -47,7 +47,7 @@ The first type of tokenizer that comes to mind is _word-based_. It's generally v -There are different ways to split the text. For example, we could could use whitespace to tokenize the text into words by applying Python's `split()` function: +There are different ways to split the text. For example, we could use whitespace to tokenize the text into words by applying Python's `split()` function: ```py tokenized_text = "Jim Henson was a puppeteer".split() @@ -68,7 +68,7 @@ Finally, we need a custom token to represent words that are not in our vocabular One way to reduce the amount of unknown tokens is to go one level deeper, using a _character-based_ tokenizer. -## Character-based +## Character-based[[character-based]] @@ -90,7 +90,7 @@ Another thing to consider is that we'll end up with a very large amount of token To get the best of both worlds, we can use a third technique that combines the two approaches: *subword tokenization*. -## Subword tokenization +## Subword tokenization[[subword-tokenization]] @@ -109,7 +109,7 @@ These subwords end up providing a lot of semantic meaning: for instance, in the This approach is especially useful in agglutinative languages such as Turkish, where you can form (almost) arbitrarily long complex words by stringing together subwords. -### And more! +### And more![[and-more]] Unsurprisingly, there are many more techniques out there. To name a few: @@ -119,7 +119,7 @@ Unsurprisingly, there are many more techniques out there. To name a few: You should now have sufficient knowledge of how tokenizers work to get started with the API. -## Loading and saving +## Loading and saving[[loading-and-saving]] Loading and saving tokenizers is as simple as it is with models. Actually, it's based on the same two methods: `from_pretrained()` and `save_pretrained()`. These methods will load or save the algorithm used by the tokenizer (a bit like the *architecture* of the model) as well as its vocabulary (a bit like the *weights* of the model). @@ -165,7 +165,7 @@ tokenizer.save_pretrained("directory_on_my_computer") We'll talk more about `token_type_ids` in [Chapter 3](/course/chapter3), and we'll explain the `attention_mask` key a little later. First, let's see how the `input_ids` are generated. To do this, we'll need to look at the intermediate methods of the tokenizer. -## Encoding +## Encoding[[encoding]] @@ -177,7 +177,7 @@ The second step is to convert those tokens into numbers, so we can build a tenso To get a better understanding of the two steps, we'll explore them separately. Note that we will use some methods that perform parts of the tokenization pipeline separately to show you the intermediate results of those steps, but in practice, you should call the tokenizer directly on your inputs (as shown in the section 2). -### Tokenization +### Tokenization[[tokenization]] The tokenization process is done by the `tokenize()` method of the tokenizer: @@ -200,7 +200,7 @@ The output of this method is a list of strings, or tokens: This tokenizer is a subword tokenizer: it splits the words until it obtains tokens that can be represented by its vocabulary. That's the case here with `transformer`, which is split into two tokens: `transform` and `##er`. -### From tokens to input IDs +### From tokens to input IDs[[from-tokens-to-input-ids]] The conversion to input IDs is handled by the `convert_tokens_to_ids()` tokenizer method: @@ -222,7 +222,7 @@ These outputs, once converted to the appropriate framework tensor, can then be u -## Decoding +## Decoding[[decoding]] *Decoding* is going the other way around: from vocabulary indices, we want to get a string. This can be done with the `decode()` method as follows: diff --git a/chapters/en/chapter2/5.mdx b/chapters/en/chapter2/5.mdx index 66eeb3090..81d496fef 100644 --- a/chapters/en/chapter2/5.mdx +++ b/chapters/en/chapter2/5.mdx @@ -1,6 +1,6 @@ -# Handling multiple sequences +# Handling multiple sequences[[handling-multiple-sequences]] {#if fw === 'pt'} @@ -37,7 +37,7 @@ In the previous section, we explored the simplest of use cases: doing inference Let's see what kinds of problems these questions pose, and how we can solve them using the 🤗 Transformers API. -## Models expect a batch of inputs +## Models expect a batch of inputs[[models-expect-a-batch-of-inputs]] In the previous exercise you saw how sequences get translated into lists of numbers. Let's convert this list of numbers to a tensor and send it to the model: @@ -188,7 +188,7 @@ This is a batch of two identical sequences! Batching allows the model to work when you feed it multiple sentences. Using multiple sequences is just as simple as building a batch with a single sequence. There's a second issue, though. When you're trying to batch together two (or more) sentences, they might be of different lengths. If you've ever worked with tensors before, you know that they need to be of rectangular shape, so you won't be able to convert the list of input IDs into a tensor directly. To work around this problem, we usually *pad* the inputs. -## Padding the inputs +## Padding the inputs[[padding-the-inputs]] The following list of lists cannot be converted to a tensor: @@ -263,7 +263,7 @@ There's something wrong with the logits in our batched predictions: the second r This is because the key feature of Transformer models is attention layers that *contextualize* each token. These will take into account the padding tokens since they attend to all of the tokens of a sequence. To get the same result when passing individual sentences of different lengths through the model or when passing a batch with the same sentences and padding applied, we need to tell those attention layers to ignore the padding tokens. This is done by using an attention mask. -## Attention masks +## Attention masks[[attention-masks]] *Attention masks* are tensors with the exact same shape as the input IDs tensor, filled with 0s and 1s: 1s indicate the corresponding tokens should be attended to, and 0s indicate the corresponding tokens should not be attended to (i.e., they should be ignored by the attention layers of the model). @@ -322,7 +322,7 @@ Notice how the last value of the second sequence is a padding ID, which is a 0 v -## Longer sequences +## Longer sequences[[longer-sequences]] With Transformer models, there is a limit to the lengths of the sequences we can pass the models. Most models handle sequences of up to 512 or 1024 tokens, and will crash when asked to process longer sequences. There are two solutions to this problem: diff --git a/chapters/en/chapter2/6.mdx b/chapters/en/chapter2/6.mdx index 27322c765..d26118501 100644 --- a/chapters/en/chapter2/6.mdx +++ b/chapters/en/chapter2/6.mdx @@ -1,6 +1,6 @@ -# Putting it all together +# Putting it all together[[putting-it-all-together]] {#if fw === 'pt'} @@ -97,7 +97,7 @@ model_inputs = tokenizer(sequences, padding=True, return_tensors="tf") model_inputs = tokenizer(sequences, padding=True, return_tensors="np") ``` -## Special tokens +## Special tokens[[special-tokens]] If we take a look at the input IDs returned by the tokenizer, we will see they are a tiny bit different from what we had earlier: @@ -131,7 +131,7 @@ print(tokenizer.decode(ids)) The tokenizer added the special word `[CLS]` at the beginning and the special word `[SEP]` at the end. This is because the model was pretrained with those, so to get the same results for inference we need to add them as well. Note that some models don't add special words, or add different ones; models may also add these special words only at the beginning, or only at the end. In any case, the tokenizer knows which ones are expected and will deal with this for you. -## Wrapping up: From tokenizer to model +## Wrapping up: From tokenizer to model[[wrapping-up-from-tokenizer-to-model]] Now that we've seen all the individual steps the `tokenizer` object uses when applied on texts, let's see one final time how it can handle multiple sequences (padding!), very long sequences (truncation!), and multiple types of tensors with its main API: diff --git a/chapters/en/chapter2/7.mdx b/chapters/en/chapter2/7.mdx index d5306c56f..657aa28e9 100644 --- a/chapters/en/chapter2/7.mdx +++ b/chapters/en/chapter2/7.mdx @@ -1,4 +1,4 @@ -# Basic usage completed! +# Basic usage completed![[basic-usage-completed]] -# End-of-chapter quiz +# End-of-chapter quiz[[end-of-chapter-quiz]] -# Introduction +# Introduction[[introduction]] -# Processing the data +# Processing the data[[processing-the-data]] {#if fw === 'pt'} @@ -76,7 +76,7 @@ Of course, just training the model on two sentences is not going to yield very g In this section we will use as an example the MRPC (Microsoft Research Paraphrase Corpus) dataset, introduced in a [paper](https://www.aclweb.org/anthology/I05-5002.pdf) by William B. Dolan and Chris Brockett. The dataset consists of 5,801 pairs of sentences, with a label indicating if they are paraphrases or not (i.e., if both sentences mean the same thing). We've selected it for this chapter because it's a small dataset, so it's easy to experiment with training on it. -### Loading a dataset from the Hub +### Loading a dataset from the Hub[[loading-a-dataset-from-the-hub]] {#if fw === 'pt'} @@ -151,7 +151,7 @@ Behind the scenes, `label` is of type `ClassLabel`, and the mapping of integers -### Preprocessing a dataset +### Preprocessing a dataset[[preprocessing-a-dataset]] {#if fw === 'pt'} @@ -278,7 +278,7 @@ Our `tokenize_function` returns a dictionary with the keys `input_ids`, `attenti The last thing we will need to do is pad all the examples to the length of the longest element when we batch elements together — a technique we refer to as *dynamic padding*. -### Dynamic padding +### Dynamic padding[[dynamic-padding]] diff --git a/chapters/en/chapter3/3.mdx b/chapters/en/chapter3/3.mdx index b10c92965..0ee013231 100644 --- a/chapters/en/chapter3/3.mdx +++ b/chapters/en/chapter3/3.mdx @@ -1,6 +1,6 @@ -# Fine-tuning a model with the Trainer API +# Fine-tuning a model with the Trainer API[[fine-tuning-a-model-with-the-trainer-api]] -# Fine-tuning a model with Keras +# Fine-tuning a model with Keras[[fine-tuning-a-model-with-keras]] -### Improving training performance +### Improving training performance[[improving-training-performance]] @@ -159,7 +159,7 @@ model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) -### Model predictions +### Model predictions[[model-predictions]] diff --git a/chapters/en/chapter3/4.mdx b/chapters/en/chapter3/4.mdx index 23688ea24..98d639163 100644 --- a/chapters/en/chapter3/4.mdx +++ b/chapters/en/chapter3/4.mdx @@ -1,4 +1,4 @@ -# A full training +# A full training[[a-full-training]] -### Supercharge your training loop with 🤗 Accelerate +### Supercharge your training loop with 🤗 Accelerate[[supercharge-your-training-loop-with-accelerate]] diff --git a/chapters/en/chapter3/5.mdx b/chapters/en/chapter3/5.mdx index bdbea1c52..5aa6b002d 100644 --- a/chapters/en/chapter3/5.mdx +++ b/chapters/en/chapter3/5.mdx @@ -1,6 +1,6 @@ -# Fine-tuning, Check! +# Fine-tuning, Check![[fine-tuning-check]] -# End-of-chapter quiz +# End-of-chapter quiz[[end-of-chapter-quiz]] -# Using pretrained models +# Using pretrained models[[using-pretrained-models]] {#if fw === 'pt'} diff --git a/chapters/en/chapter4/3.mdx b/chapters/en/chapter4/3.mdx index 6dd9ee601..a782152c6 100644 --- a/chapters/en/chapter4/3.mdx +++ b/chapters/en/chapter4/3.mdx @@ -1,6 +1,6 @@ -# Sharing pretrained models +# Sharing pretrained models[[sharing-pretrained-models]] {#if fw === 'pt'} @@ -37,7 +37,7 @@ There are three ways to go about creating new model repositories: Once you've created a repository, you can upload files to it via git and git-lfs. We'll walk you through creating model repositories and uploading files to them in the following sections. -## Using the `push_to_hub` API +## Using the `push_to_hub` API[[using-the-pushtohub-api]] {#if fw === 'pt'} @@ -184,7 +184,7 @@ The `push_to_hub()` method is backed by the [`huggingface_hub`](https://github.c Jump to the last section to see how to upload files to your newly created repository! -## Using the `huggingface_hub` Python library +## Using the `huggingface_hub` Python library[[using-the-huggingfacehub-python-library]] The `huggingface_hub` Python library is a package which offers a set of tools for the model and datasets hubs. It provides simple methods and classes for common tasks like getting information about repositories on the hub and managing them. It provides simple APIs that work on top of git to manage those repositories' content and to integrate the Hub @@ -249,7 +249,7 @@ Other arguments which may be useful are: Once the repository is created, we should add files to it! Jump to the next section to see the three ways this can be handled. -## Using the web interface +## Using the web interface[[using-the-web-interface]] The web interface offers tools to manage repositories directly in the Hub. Using the interface, you can easily create repositories, add files (even large ones!), explore models, visualize diffs, and much more. @@ -285,13 +285,13 @@ If you look at the "Files and versions" tab, you'll see that there aren't many f We'll take a look at how to add some new files next. -## Uploading the model files +## Uploading the model files[[uploading-the-model-files]] The system to manage files on the Hugging Face Hub is based on git for regular files, and git-lfs (which stands for [Git Large File Storage](https://git-lfs.github.com/)) for larger files. In the next section, we go over three different ways of uploading files to the Hub: through `huggingface_hub` and through git commands. -### The `upload_file` approach +### The `upload_file` approach[[the-uploadfile-approach]] Using `upload_file` does not require git and git-lfs to be installed on your system. It pushes files directly to the 🤗 Hub using HTTP POST requests. A limitation of this approach is that it doesn't handle files that are larger than 5GB in size. If your files are larger than 5GB, please follow the two other methods detailed below. @@ -315,7 +315,7 @@ Other arguments which may be useful are: - `repo_type`, if you would like to upload to a `dataset` or a `space` instead of a model. Accepted values are `"dataset"` and `"space"`. -### The `Repository` class +### The `Repository` class[[the-repository-class]] The `Repository` class manages a local repository in a git-like manner. It abstracts most of the pain points one may have with git to provide all features that we require. @@ -368,7 +368,7 @@ repo.git_push() Congratulations! You just pushed your first files on the hub. -### The git-based approach +### The git-based approach[[the-git-based-approach]] This is the very barebones approach to uploading files: we'll do so with git and git-lfs directly. Most of the difficulty is abstracted away by previous approaches, but there are a few caveats with the following method so we'll follow a more complex use-case. diff --git a/chapters/en/chapter4/4.mdx b/chapters/en/chapter4/4.mdx index b609b479a..15a38ecbb 100644 --- a/chapters/en/chapter4/4.mdx +++ b/chapters/en/chapter4/4.mdx @@ -1,4 +1,4 @@ -# Building a model card +# Building a model card[[building-a-model-card]] -# End-of-chapter quiz +# End-of-chapter quiz[[end-of-chapter-quiz]] -## Working with local and remote datasets +## Working with local and remote datasets[[working-with-local-and-remote-datasets]] 🤗 Datasets provides loading scripts to handle the loading of local and remote datasets. It supports several common data formats, such as: @@ -24,7 +24,7 @@ You know how to use the [Hugging Face Hub](https://huggingface.co/datasets) to d As shown in the table, for each data format we just need to specify the type of loading script in the `load_dataset()` function, along with a `data_files` argument that specifies the path to one or more files. Let's start by loading a dataset from local files; later we'll see how to do the same with remote files. -## Loading a local dataset +## Loading a local dataset[[loading-a-local-dataset]] For this example we'll use the [SQuAD-it dataset](https://github.com/crux82/squad-it/), which is a large-scale dataset for question answering in Italian. @@ -143,7 +143,7 @@ This can be useful if you don't want to manually decompress many GZIP files. The Now that you know how to load local files on your laptop or desktop, let's take a look at loading remote files. -## Loading a remote dataset +## Loading a remote dataset[[loading-a-remote-dataset]] If you're working as a data scientist or coder in a company, there's a good chance the datasets you want to analyze are stored on some remote server. Fortunately, loading remote files is just as simple as loading local ones! Instead of providing a path to local files, we point the `data_files` argument of `load_dataset()` to one or more URLs where the remote files are stored. For example, for the SQuAD-it dataset hosted on GitHub, we can just point `data_files` to the _SQuAD_it-*.json.gz_ URLs as follows: diff --git a/chapters/en/chapter5/3.mdx b/chapters/en/chapter5/3.mdx index a64a884c4..3f9c37dc2 100644 --- a/chapters/en/chapter5/3.mdx +++ b/chapters/en/chapter5/3.mdx @@ -1,4 +1,4 @@ -# Time to slice and dice +# Time to slice and dice[[time-to-slice-and-dice]] -## Slicing and dicing our data +## Slicing and dicing our data[[slicing-and-dicing-our-data]] Similar to Pandas, 🤗 Datasets provides several functions to manipulate the contents of `Dataset` and `DatasetDict` objects. We already encountered the `Dataset.map()` method in [Chapter 3](/course/chapter3), and in this section we'll explore some of the other functions at our disposal. @@ -168,7 +168,7 @@ drug_dataset["train"]["condition"][:3] It works! Now that we've cleaned up the labels, let's take a look at cleaning up the reviews themselves. -## Creating new columns +## Creating new columns[[creating-new-columns]] Whenever you're dealing with customer reviews, a good practice is to check the number of words in each review. A review might be just a single word like "Great!" or a full-blown essay with thousands of words, and depending on the use case you'll need to handle these extremes differently. To compute the number of words in each review, we'll use a rough heuristic based on splitting each text by whitespace. @@ -263,7 +263,7 @@ drug_dataset = drug_dataset.map(lambda x: {"review": html.unescape(x["review"])} As you can see, the `Dataset.map()` method is quite useful for processing data -- and we haven't even scratched the surface of everything it can do! -## The `map()` method's superpowers +## The `map()` method's superpowers[[the-map-methods-superpowers]] The `Dataset.map()` method takes a `batched` argument that, if set to `True`, causes it to send a batch of examples to the map function at once (the batch size is configurable but defaults to 1,000). For instance, the previous map function that unescaped all the HTML took a bit of time to run (you can read the time taken from the progress bars). We can speed this up by processing several elements at the same time using a list comprehension. @@ -447,7 +447,7 @@ We get the same number of training features as before, but here we've kept all t You've now seen how 🤗 Datasets can be used to preprocess a dataset in various ways. Although the processing functions of 🤗 Datasets will cover most of your model training needs, there may be times when you'll need to switch to Pandas to access more powerful features, like `DataFrame.groupby()` or high-level APIs for visualization. Fortunately, 🤗 Datasets is designed to be interoperable with libraries such as Pandas, NumPy, PyTorch, TensorFlow, and JAX. Let's take a look at how this works. -## From `Dataset`s to `DataFrame`s and back +## From `Dataset`s to `DataFrame`s and back[[from-datasets-to-dataframes-and-back]] @@ -607,7 +607,7 @@ This wraps up our tour of the various preprocessing techniques available in 🤗 drug_dataset.reset_format() ``` -## Creating a validation set +## Creating a validation set[[creating-a-validation-set]] Although we have a test set we could use for evaluation, it's a good practice to leave the test set untouched and create a separate validation set during development. Once you are happy with the performance of your models on the validation set, you can do a final sanity check on the test set. This process helps mitigate the risk that you'll overfit to the test set and deploy a model that fails on real-world data. @@ -641,7 +641,7 @@ DatasetDict({ Great, we've now prepared a dataset that's ready for training some models on! In [section 5](/course/chapter5/5) we'll show you how to upload datasets to the Hugging Face Hub, but for now let's cap off our analysis by looking at a few ways you can save datasets on your local machine. -## Saving a dataset +## Saving a dataset[[saving-a-dataset]] diff --git a/chapters/en/chapter5/4.mdx b/chapters/en/chapter5/4.mdx index 26eea8397..332e171c3 100644 --- a/chapters/en/chapter5/4.mdx +++ b/chapters/en/chapter5/4.mdx @@ -1,4 +1,4 @@ -# Big data? 🤗 Datasets to the rescue! +# Big data? 🤗 Datasets to the rescue![[big-data-datasets-to-the-rescue]] -## Streaming datasets +## Streaming datasets[[streaming-datasets]] To enable dataset streaming you just need to pass the `streaming=True` argument to the `load_dataset()` function. For example, let's load the PubMed Abstracts dataset again, but in streaming mode: diff --git a/chapters/en/chapter5/5.mdx b/chapters/en/chapter5/5.mdx index 5f57ea218..08ec45a0b 100644 --- a/chapters/en/chapter5/5.mdx +++ b/chapters/en/chapter5/5.mdx @@ -1,4 +1,4 @@ -# Creating your own dataset +# Creating your own dataset[[creating-your-own-dataset]] @@ -369,7 +369,7 @@ Cool, we've pushed our dataset to the Hub and it's available for others to use! -## Creating a dataset card +## Creating a dataset card[[creating-a-dataset-card]] Well-documented datasets are more likely to be useful to others (including your future self!), as they provide the context to enable users to decide whether the dataset is relevant to their task and to evaluate any potential biases in or risks associated with using the dataset. diff --git a/chapters/en/chapter5/6.mdx b/chapters/en/chapter5/6.mdx index f2927a0de..3da60cc96 100644 --- a/chapters/en/chapter5/6.mdx +++ b/chapters/en/chapter5/6.mdx @@ -1,6 +1,6 @@ -# Semantic search with FAISS +# Semantic search with FAISS[[semantic-search-with-faiss]] {#if fw === 'pt'} @@ -26,7 +26,7 @@ In [section 5](/course/chapter5/5), we created a dataset of GitHub issues and co -## Using embeddings for semantic search +## Using embeddings for semantic search[[using-embeddings-for-semantic-search]] As we saw in [Chapter 1](/course/chapter1), Transformer-based language models represent each token in a span of text as an _embedding vector_. It turns out that one can "pool" the individual embeddings to create a vector representation for whole sentences, paragraphs, or (in some cases) documents. These embeddings can then be used to find similar documents in the corpus by computing the dot-product similarity (or some other similarity metric) between each embedding and returning the documents with the greatest overlap. @@ -37,7 +37,7 @@ In this section we'll use embeddings to develop a semantic search engine. These -## Loading and preparing the dataset +## Loading and preparing the dataset[[loading-and-preparing-the-dataset]] The first thing we need to do is download our dataset of GitHub issues, so let's use `load_dataset()` function as usual: @@ -222,7 +222,7 @@ comments_dataset = comments_dataset.map(concatenate_text) We're finally ready to create some embeddings! Let's take a look. -## Creating text embeddings +## Creating text embeddings[[creating-text-embeddings]] We saw in [Chapter 2](/course/chapter2) that we can obtain token embeddings by using the `AutoModel` class. All we need to do is pick a suitable checkpoint to load the model from. Fortunately, there's a library called `sentence-transformers` that is dedicated to creating embeddings. As described in the library's [documentation](https://www.sbert.net/examples/applications/semantic-search/README.html#symmetric-vs-asymmetric-semantic-search), our use case is an example of _asymmetric semantic search_ because we have a short query whose answer we'd like to find in a longer document, like a an issue comment. The handy [model overview table](https://www.sbert.net/docs/pretrained_models.html#model-overview) in the documentation indicates that the `multi-qa-mpnet-base-dot-v1` checkpoint has the best performance for semantic search, so we'll use that for our application. We'll also load the tokenizer using the same checkpoint: @@ -335,7 +335,7 @@ embeddings_dataset = comments_dataset.map( Notice that we've converted the embeddings to NumPy arrays -- that's because 🤗 Datasets requires this format when we try to index them with FAISS, which we'll do next. -## Using FAISS for efficient similarity search +## Using FAISS for efficient similarity search[[using-faiss-for-efficient-similarity-search]] Now that we have a dataset of embeddings, we need some way to search over them. To do this, we'll use a special data structure in 🤗 Datasets called a _FAISS index_. [FAISS](https://faiss.ai/) (short for Facebook AI Similarity Search) is a library that provides efficient algorithms to quickly search and cluster embedding vectors. diff --git a/chapters/en/chapter5/7.mdx b/chapters/en/chapter5/7.mdx index d4f42a6a7..50ccc65be 100644 --- a/chapters/en/chapter5/7.mdx +++ b/chapters/en/chapter5/7.mdx @@ -1,4 +1,4 @@ -# 🤗 Datasets, check! +# 🤗 Datasets, check![[datasets-check]] -# End-of-chapter quiz +# End-of-chapter quiz[[end-of-chapter-quiz]] -# End-of-chapter quiz +# End-of-chapter quiz[[end-of-chapter-quiz]] -## Assembling a corpus +## Assembling a corpus[[assembling-a-corpus]] There's a very simple API in 🤗 Transformers that you can use to train a new tokenizer with the same characteristics as an existing one: `AutoTokenizer.train_new_from_iterator()`. To see this in action, let’s say we want to train GPT-2 from scratch, but in a language other than English. Our first task will be to gather lots of data in that language in a training corpus. To provide examples everyone will be able to understand, we won't use a language like Russian or Chinese here, but rather a specialized English language: Python code. @@ -129,7 +129,7 @@ def get_training_corpus(): which will produce the exact same generator as before, but allows you to use more complex logic than you can in a list comprehension. -## Training a new tokenizer +## Training a new tokenizer[[training-a-new-tokenizer]] Now that we have our corpus in the form of an iterator of batches of texts, we are ready to train a new tokenizer. To do this, we first need to load the tokenizer we want to pair with our model (here, GPT-2): @@ -219,7 +219,7 @@ tokenizer.tokenize(example) In addition to the token corresponding to an indentation, here we can also see a token for a double indentation: `ĊĠĠĠĠĠĠĠ`. The special Python words like `class`, `init`, `call`, `self`, and `return` are each tokenized as one token, and we can see that as well as splitting on `_` and `.` the tokenizer correctly splits even camel-cased names: `LinearLayer` is tokenized as `["ĠLinear", "Layer"]`. -## Saving the tokenizer +## Saving the tokenizer[[saving-the-tokenizer]] To make sure we can use it later, we need to save our new tokenizer. Like for models, this is done with the `save_pretrained()` method: diff --git a/chapters/en/chapter6/3.mdx b/chapters/en/chapter6/3.mdx index 0aa5aebb7..3739d4eb9 100644 --- a/chapters/en/chapter6/3.mdx +++ b/chapters/en/chapter6/3.mdx @@ -1,6 +1,6 @@ -# Fast tokenizers' special powers +# Fast tokenizers' special powers[[fast-tokenizers-special-powers]] {#if fw === 'pt'} @@ -39,7 +39,7 @@ In the following discussion, we will often make the distinction between "slow" a -## Batch encoding +## Batch encoding[[batch-encoding]] @@ -136,7 +136,7 @@ As we mentioned previously, this is all powered by the fact the fast tokenizer k -## Inside the `token-classification` pipeline +## Inside the `token-classification` pipeline[[inside-the-token-classification-pipeline]] In [Chapter 1](/course/chapter1) we got our first taste of applying NER -- where the task is to identify which parts of the text correspond to entities like persons, locations, or organizations -- with the 🤗 Transformers `pipeline()` function. Then, in [Chapter 2](/course/chapter2), we saw how a pipeline groups together the three stages necessary to get the predictions from a raw text: tokenization, passing the inputs through the model, and post-processing. The first two steps in the `token-classification` pipeline are the same as in any other pipeline, but the post-processing is a little more complex -- let's see how! @@ -150,7 +150,7 @@ In [Chapter 1](/course/chapter1) we got our first taste of applying NER -- where {/if} -### Getting the base results with the pipeline +### Getting the base results with the pipeline[[getting-the-base-results-with-the-pipeline]] First, let's grab a token classification pipeline so we can get some results to compare manually. The model used by default is [`dbmdz/bert-large-cased-finetuned-conll03-english`](https://huggingface.co/dbmdz/bert-large-cased-finetuned-conll03-english); it performs NER on sentences: @@ -195,7 +195,7 @@ The `aggregation_strategy` picked will change the scores computed for each group Now let's see how to obtain these results without using the `pipeline()` function! -### From inputs to predictions +### From inputs to predictions[[from-inputs-to-predictions]] {#if fw === 'pt'} @@ -402,7 +402,7 @@ print(results) This is the same as what we got from the first pipeline! -### Grouping entities +### Grouping entities[[grouping-entities]] Using the offsets to determine the start and end keys for each entity is handy, but that information isn't strictly necessary. When we want to group the entities together, however, the offsets will save us a lot of messy code. For example, if we wanted to group together the tokens `Hu`, `##gging`, and `Face`, we could make special rules that say the first two should be attached while removing the `##`, and the `Face` should be added with a space since it does not begin with `##` -- but that would only work for this particular type of tokenizer. We would have to write another set of rules for a SentencePiece or a Byte-Pair-Encoding tokenizer (discussed later in this chapter). diff --git a/chapters/en/chapter6/3b.mdx b/chapters/en/chapter6/3b.mdx index 5b902f068..d0affbcba 100644 --- a/chapters/en/chapter6/3b.mdx +++ b/chapters/en/chapter6/3b.mdx @@ -1,6 +1,6 @@ -# Fast tokenizers in the QA pipeline +# Fast tokenizers in the QA pipeline[[fast-tokenizers-in-the-qa-pipeline]] {#if fw === 'pt'} @@ -34,7 +34,7 @@ We will now dive into the `question-answering` pipeline and see how to leverage {/if} -## Using the `question-answering` pipeline +## Using the `question-answering` pipeline[[using-the-question-answering-pipeline]] As we saw in [Chapter 1](/course/chapter1), we can use the `question-answering` pipeline like this to get the answer to a question: @@ -109,7 +109,7 @@ question_answerer(question=question, context=long_context) Let's see how it does all of this! -## Using a model for question answering +## Using a model for question answering[[using-a-model-for-question-answering]] Like with any other pipeline, we start by tokenizing our input and then send it through the model. The checkpoint used by default for the `question-answering` pipeline is [`distilbert-base-cased-distilled-squad`](https://huggingface.co/distilbert-base-cased-distilled-squad) (the "squad" in the name comes from the dataset on which the model was fine-tuned; we'll talk more about the SQuAD dataset in [Chapter 7](/course/chapter7/7)): @@ -319,7 +319,7 @@ Great! That's the same as in our first example! -## Handling long contexts +## Handling long contexts[[handling-long-contexts]] If we try to tokenize the question and long context we used as an example previously, we'll get a number of tokens higher than the maximum length used in the `question-answering` pipeline (which is 384): diff --git a/chapters/en/chapter6/4.mdx b/chapters/en/chapter6/4.mdx index e42c6597d..a53611a60 100644 --- a/chapters/en/chapter6/4.mdx +++ b/chapters/en/chapter6/4.mdx @@ -1,4 +1,4 @@ -# Normalization and pre-tokenization +# Normalization and pre-tokenization[[normalization-and-pre-tokenization]] @@ -53,7 +53,7 @@ In this example, since we picked the `bert-base-uncased` checkpoint, the normali -## Pre-tokenization +## Pre-tokenization[[pre-tokenization]] @@ -102,13 +102,13 @@ Like the GPT-2 tokenizer, this one keeps spaces and replaces them with a specifi Now that we've seen a little of how some different tokenizers process text, we can start to explore the underlying algorithms themselves. We'll begin with a quick look at the broadly widely applicable SentencePiece; then, over the next three sections, we'll examine how the three main algorithms used for subword tokenization work. -## SentencePiece +## SentencePiece[[sentencepiece]] [SentencePiece](https://github.com/google/sentencepiece) is a tokenization algorithm for the preprocessing of text that you can use with any of the models we will see in the next three sections. It considers the text as a sequence of Unicode characters, and replaces spaces with a special character, `▁`. Used in conjunction with the Unigram algorithm (see [section 7](/course/chapter7/7)), it doesn't even require a pre-tokenization step, which is very useful for languages where the space character is not used (like Chinese or Japanese). The other main feature of SentencePiece is *reversible tokenization*: since there is no special treatment of spaces, decoding the tokens is done simply by concatenating them and replacing the `_`s with spaces -- this results in the normalized text. As we saw earlier, the BERT tokenizer removes repeating spaces, so its tokenization is not reversible. -## Algorithm overview +## Algorithm overview[[algorithm-overview]] In the following sections, we'll dive into the three main subword tokenization algorithms: BPE (used by GPT-2 and others), WordPiece (used for example by BERT), and Unigram (used by T5 and others). Before we get started, here's a quick overview of how they each work. Don't hesitate to come back to this table after reading each of the next sections if it doesn't make sense to you yet. diff --git a/chapters/en/chapter6/5.mdx b/chapters/en/chapter6/5.mdx index 68372b136..291518720 100644 --- a/chapters/en/chapter6/5.mdx +++ b/chapters/en/chapter6/5.mdx @@ -1,4 +1,4 @@ -# Byte-Pair Encoding tokenization +# Byte-Pair Encoding tokenization[[byte-pair-encoding-tokenization]] -## Training algorithm +## Training algorithm[[training-algorithm]] BPE training starts by computing the unique set of words used in the corpus (after the normalization and pre-tokenization steps are completed), then building the vocabulary by taking all the symbols used to write those words. As a very simple example, let's say our corpus uses these five words: @@ -80,7 +80,7 @@ And we continue like this until we reach the desired vocabulary size. -## Tokenization algorithm +## Tokenization algorithm[[tokenization-algorithm]] Tokenization follows the training process closely, in the sense that new inputs are tokenized by applying the following steps: @@ -105,7 +105,7 @@ The word `"bug"` will be tokenized as `["b", "ug"]`. `"mug"`, however, will be t -## Implementing BPE +## Implementing BPE[[implementing-bpe]] Now let's take a look at an implementation of the BPE algorithm. This won't be an optimized version you can actually use on a big corpus; we just want to show you the code so you can understand the algorithm a little bit better. diff --git a/chapters/en/chapter6/6.mdx b/chapters/en/chapter6/6.mdx index 6db222f36..eb0cbddeb 100644 --- a/chapters/en/chapter6/6.mdx +++ b/chapters/en/chapter6/6.mdx @@ -1,4 +1,4 @@ -# WordPiece tokenization +# WordPiece tokenization[[wordpiece-tokenization]] -## Training algorithm +## Training algorithm[[training-algorithm]] @@ -82,7 +82,7 @@ and we continue like this until we reach the desired vocabulary size. -## Tokenization algorithm +## Tokenization algorithm[[tokenization-algorithm]] Tokenization differs in WordPiece and BPE in that WordPiece only saves the final vocabulary, not the merge rules learned. Starting from the word to tokenize, WordPiece finds the longest subword that is in the vocabulary, then splits on it. For instance, if we use the vocabulary learned in the example above, for the word `"hugs"` the longest subword starting from the beginning that is inside the vocabulary is `"hug"`, so we split there and get `["hug", "##s"]`. We then continue with `"##s"`, which is in the vocabulary, so the tokenization of `"hugs"` is `["hug", "##s"]`. @@ -98,7 +98,7 @@ When the tokenization gets to a stage where it's not possible to find a subword -## Implementing WordPiece +## Implementing WordPiece[[implementing-wordpiece]] Now let's take a look at an implementation of the WordPiece algorithm. Like with BPE, this is just pedagogical, and you won't able to use this on a big corpus. diff --git a/chapters/en/chapter6/7.mdx b/chapters/en/chapter6/7.mdx index 735d1d401..505cf7588 100644 --- a/chapters/en/chapter6/7.mdx +++ b/chapters/en/chapter6/7.mdx @@ -1,4 +1,4 @@ -# Unigram tokenization +# Unigram tokenization[[unigram-tokenization]] -## Training algorithm +## Training algorithm[[training-algorithm]] Compared to BPE and WordPiece, Unigram works in the other direction: it starts from a big vocabulary and removes tokens from it until it reaches the desired vocabulary size. There are several options to use to build that base vocabulary: we can take the most common substrings in pre-tokenized words, for instance, or apply BPE on the initial corpus with a large vocabulary size. @@ -41,7 +41,7 @@ and for this example, we will take all strict substrings for the initial vocabul ["h", "u", "g", "hu", "ug", "p", "pu", "n", "un", "b", "bu", "s", "hug", "gs", "ugs"] ``` -## Tokenization algorithm +## Tokenization algorithm[[tokenization-algorithm]] A Unigram model is a type of language model that considers each token to be independent of the tokens before it. It's the simplest language model, in the sense that the probability of token X given the previous context is just the probability of token X. So, if we used a Unigram language model to generate text, we would always predict the most common token. @@ -104,7 +104,7 @@ Thus `"unhug"` would be tokenized as `["un", "hug"]`. -## Back to training +## Back to training[[back-to-training]] Now that we have seen how the tokenization works, we can dive a little more deeply into the loss used during training. At any given stage, this loss is computed by tokenizing every word in the corpus, using the current vocabulary and the Unigram model determined by the frequencies of each token in the corpus (as seen before). @@ -149,7 +149,7 @@ These changes will cause the loss to rise by: Therefore, the token `"pu"` will probably be removed from the vocabulary, but not `"hug"`. -## Implementing Unigram +## Implementing Unigram[[implementing-unigram]] Now let's implement everything we've seen so far in code. Like with BPE and WordPiece, this is not an efficient implementation of the Unigram algorithm (quite the opposite), but it should help you understand it a bit better. diff --git a/chapters/en/chapter6/8.mdx b/chapters/en/chapter6/8.mdx index 4fde33c7d..7caee98ed 100644 --- a/chapters/en/chapter6/8.mdx +++ b/chapters/en/chapter6/8.mdx @@ -1,4 +1,4 @@ -# Building a tokenizer, block by block +# Building a tokenizer, block by block[[building-a-tokenizer-block-by-block]] -# Introduction +# Introduction[[introduction]] -# Token classification +# Token classification[[token-classification]] {#if fw === 'pt'} @@ -32,7 +32,7 @@ The first application we'll explore is token classification. This generic task e Of course, there are many other types of token classification problem; those are just a few representative examples. In this section, we will fine-tune a model (BERT) on a NER task, which will then be able to compute predictions like this one: - + One-hot encoded labels for question answering. @@ -41,7 +41,7 @@ Of course, there are many other types of token classification problem; those are You can find the model we'll train and upload to the Hub and double-check its predictions [here](https://huggingface.co/huggingface-course/bert-finetuned-ner?text=My+name+is+Sylvain+and+I+work+at+Hugging+Face+in+Brooklyn). -## Preparing the data +## Preparing the data[[preparing-the-data]] First things first, we need a dataset suitable for token classification. In this section we will use the [CoNLL-2003 dataset](https://huggingface.co/datasets/conll2003), which contains news stories from Reuters. @@ -51,7 +51,7 @@ First things first, we need a dataset suitable for token classification. In this -### The CoNLL-2003 dataset +### The CoNLL-2003 dataset[[the-conll-2003-dataset]] To load the CoNLL-2003 dataset, we use the `load_dataset()` method from the 🤗 Datasets library: @@ -173,7 +173,7 @@ As we can see, entities spanning two words, like "European Union" and "Werner Zw -### Processing the data +### Processing the data[[processing-the-data]] @@ -302,20 +302,20 @@ We've done the hardest part! Now that the data has been preprocessed, the actual {#if fw === 'pt'} -## Fine-tuning the model with the `Trainer` API +## Fine-tuning the model with the `Trainer` API[[fine-tuning-the-model-with-the-trainer-api]] The actual code using the `Trainer` will be the same as before; the only changes are the way the data is collated into a batch and the metric computation function. {:else} -## Fine-tuning the model with Keras +## Fine-tuning the model with Keras[[fine-tuning-the-model-with-keras]] The actual code using Keras will be very similar to before; the only changes are the way the data is collated into a batch and the metric computation function. {/if} -### Data collation +### Data collation[[data-collation]] We can't just use a `DataCollatorWithPadding` like in [Chapter 3](/course/chapter3) because that only pads the inputs (input IDs, attention mask, and token type IDs). Here our labels should be padded the exact same way as the inputs so that they stay the same size, using `-100` as a value so that the corresponding predictions are ignored in the loss computation. @@ -396,7 +396,7 @@ tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( {#if fw === 'tf'} -### Defining the model +### Defining the model[[defining-the-model]] Since we are working on a token classification problem, we will use the `TFAutoModelForTokenClassification` class. The main thing to remember when defining this model is to pass along some information on the number of labels we have. The easiest way to do this is to pass that number with the `num_labels` argument, but if we want a nice inference widget working like the one we saw at the beginning of this section, it's better to set the correct label correspondences instead. @@ -435,7 +435,7 @@ model.config.num_labels -### Fine-tuning the model +### Fine-tuning the model[[fine-tuning-the-model]] We are now ready to train our model! We have just a little more housekeeping to do first, though: we should log in to Hugging Face and define our training hyperparameters. If you're working in a notebook, there's a convenience function to help you with this: @@ -510,7 +510,7 @@ At this stage, you can use the inference widget on the Model Hub to test your mo {/if} -### Metrics +### Metrics[[metrics]] {#if fw === 'pt'} @@ -646,7 +646,7 @@ How did your model do, compared to ours? If you got similar numbers, your traini {#if fw === 'pt'} -### Defining the model +### Defining the model[[defining-the-model]] Since we are working on a token classification problem, we will use the `AutoModelForTokenClassification` class. The main thing to remember when defining this model is to pass along some information on the number of labels we have. The easiest way to do this is to pass that number with the `num_labels` argument, but if we want a nice inference widget working like the one we saw at the beginning of this section, it's better to set the correct label correspondences instead. @@ -685,7 +685,7 @@ model.config.num_labels -### Fine-tuning the model +### Fine-tuning the model[[fine-tuning-the-model]] We are now ready to train our model! We just need to do two last things before we define our `Trainer`: log in to Hugging Face and define our training arguments. If you're working in a notebook, there's a convenience function to help you with this: @@ -762,11 +762,11 @@ The `Trainer` also drafts a model card with all the evaluation results and uploa If you want to dive a bit more deeply into the training loop, we will now show you how to do the same thing using 🤗 Accelerate. -## A custom training loop +## A custom training loop[[a-custom-training-loop]] Let's now take a look at the full training loop, so you can easily customize the parts you need. It will look a lot like what we did in [Chapter 3](/course/chapter3/4), with a few changes for the evaluation. -### Preparing everything for training +### Preparing everything for training[[preparing-everything-for-training]] First we need to build the `DataLoader`s from our datasets. We'll reuse our `data_collator` as a `collate_fn` and shuffle the training set, but not the validation set: @@ -859,7 +859,7 @@ repo = Repository(output_dir, clone_from=repo_name) We can now upload anything we save in `output_dir` by calling the `repo.push_to_hub()` method. This will help us upload the intermediate models at the end of each epoch. -### Training loop +### Training loop[[training-loop]] We are now ready to write the full training loop. To simplify its evaluation part, we define this `postprocess()` function that takes predictions and labels and converts them to lists of strings, like our `metric` object expects: @@ -957,7 +957,7 @@ Once this is done, you should have a model that produces results pretty similar {/if} -## Using the fine-tuned model +## Using the fine-tuned model[[using-the-fine-tuned-model]] We've already shown you how you can use the model we fine-tuned on the Model Hub with the inference widget. To use it locally in a `pipeline`, you just have to specify the proper model identifier: diff --git a/chapters/en/chapter7/3.mdx b/chapters/en/chapter7/3.mdx index 982d8beba..8053840b9 100644 --- a/chapters/en/chapter7/3.mdx +++ b/chapters/en/chapter7/3.mdx @@ -1,6 +1,6 @@ -# Fine-tuning a masked language model +# Fine-tuning a masked language model[[fine-tuning-a-masked-language-model]] {#if fw === 'pt'} @@ -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! @@ -47,7 +47,7 @@ Let's dive in! -## Picking a pretrained model for masked language modeling +## Picking a pretrained model for masked language modeling[[picking-a-pretrained-model-for-masked-language-modeling]] To get started, let's pick a suitable pretrained model for masked language modeling. As shown in the following screenshot, you can find a list of candidates by applying the "Fill-Mask" filter on the [Hugging Face Hub](https://huggingface.co/models?pipeline_tag=fill-mask&sort=downloads): @@ -185,7 +185,7 @@ for token in top_5_tokens: We can see from the outputs that the model's predictions refer to everyday terms, which is perhaps not surprising given the foundation of English Wikipedia. Let's see how we can change this domain to something a bit more niche -- highly polarized movie reviews! -## The dataset +## The dataset[[the-dataset]] To showcase domain adaptation, we'll use the famous [Large Movie Review Dataset](https://huggingface.co/datasets/imdb) (or IMDb for short), which is a corpus of movie reviews that is often used to benchmark sentiment analysis models. By fine-tuning DistilBERT on this corpus, we expect the language model will adapt its vocabulary from the factual data of Wikipedia that it was pretrained on to the more subjective elements of movie reviews. We can get the data from the Hugging Face Hub with the `load_dataset()` function from 🤗 Datasets: @@ -245,7 +245,7 @@ Yep, these are certainly movie reviews, and if you're old enough you may even un Now that we've had a quick look at the data, let's dive into preparing it for masked language modeling. As we'll see, there are some additional steps that one needs to take compared to the sequence classification tasks we saw in [Chapter 3](/course/chapter3). Let's go! -## Preprocessing the data +## Preprocessing the data[[preprocessing-the-data]] @@ -443,7 +443,7 @@ tokenizer.decode(lm_datasets["train"][1]["labels"]) As expected from our `group_texts()` function above, this looks identical to the decoded `input_ids` -- but then how can our model possibly learn anything? We're missing a key step: inserting `[MASK]` tokens at random positions in the inputs! Let's see how we can do this on the fly during fine-tuning using a special data collator. -## Fine-tuning DistilBERT with the `Trainer` API +## Fine-tuning DistilBERT with the `Trainer` API[[fine-tuning-distilbert-with-the-trainer-api]] Fine-tuning a masked language model is almost identical to fine-tuning a sequence classification model, like we did in [Chapter 3](/course/chapter3). The only difference is that we need a special data collator that can randomly mask some of the tokens in each batch of texts. Fortunately, 🤗 Transformers comes prepared with a dedicated `DataCollatorForLanguageModeling` for just this task. We just have to pass it the tokenizer and an `mlm_probability` argument that specifies what fraction of the tokens to mask. We'll pick 15%, which is the amount used for BERT and a common choice in the literature: @@ -730,7 +730,7 @@ We're now ready to run `trainer.train()` -- but before doing so let's briefly lo {/if} -### Perplexity for language models +### Perplexity for language models[[perplexity-for-language-models]] @@ -824,7 +824,7 @@ trainer.push_to_hub() In our use case we didn't need to do anything special with the training loop, but in some cases you might need to implement some custom logic. For these applications, you can use 🤗 Accelerate -- let's take a look! -## Fine-tuning DistilBERT with 🤗 Accelerate +## Fine-tuning DistilBERT with 🤗 Accelerate[[fine-tuning-distilbert-with-accelerate]] As we saw with the `Trainer`, fine-tuning a masked language model is very similar to the text classification example from [Chapter 3](/course/chapter3). In fact, the only subtlety is the use of a special data collator, and we've already covered that earlier in this section! @@ -1001,7 +1001,7 @@ Cool, we've been able to evaluate perplexity with each epoch and ensure that mul {/if} -## Using our fine-tuned model +## Using our fine-tuned model[[using-our-fine-tuned-model]] You can interact with your fine-tuned model either by using its widget on the Hub or locally with the `pipeline` from 🤗 Transformers. Let's use the latter to download our model using the `fill-mask` pipeline: diff --git a/chapters/en/chapter7/4.mdx b/chapters/en/chapter7/4.mdx index e7b2ad3fb..bc957c37b 100644 --- a/chapters/en/chapter7/4.mdx +++ b/chapters/en/chapter7/4.mdx @@ -1,6 +1,6 @@ -# Translation +# Translation[[translation]] {#if fw === 'pt'} @@ -35,7 +35,7 @@ In this section, we will fine-tune a Marian model pretrained to translate from E Once we're finished, we will have a model able to make predictions like this one: - + One-hot encoded labels for question answering. @@ -44,11 +44,11 @@ Once we're finished, we will have a model able to make predictions like this one As in the previous sections, you can find the actual model that we'll train and upload to the Hub using the code below and double-check its predictions [here](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 +## Preparing the data[[preparing-the-data]] To fine-tune or train a translation model from scratch, we will need a dataset suitable for the task. As mentioned previously, we'll use the [KDE4 dataset](https://huggingface.co/datasets/kde4) in this section, but you can adapt the code to use your own data quite easily, as long as you have pairs of sentences in the two languages you want to translate from and into. Refer back to [Chapter 5](/course/chapter5) if you need a reminder of how to load your custom data in a `Dataset`. -### The KDE4 dataset +### The KDE4 dataset[[the-kde4-dataset]] As usual, we download our dataset using the `load_dataset()` function: @@ -162,7 +162,7 @@ It will be interesting to see if our fine-tuned model picks up on those particul -### Processing the data +### Processing the data[[processing-the-data]] @@ -257,7 +257,7 @@ Now that the data has been preprocessed, we are ready to fine-tune our pretraine {#if fw === 'pt'} -## Fine-tuning the model with the `Trainer` API +## Fine-tuning the model with the `Trainer` API[[fine-tuning-the-model-with-the-trainer-api]] The actual code using the `Trainer` will be the same as before, with just one little change: we use a [`Seq2SeqTrainer`](https://huggingface.co/transformers/main_classes/trainer.html#seq2seqtrainer) here, which is a subclass of `Trainer` that will allow us to properly deal with the evaluation, using the `generate()` method to predict outputs from the inputs. We'll dive into that in more detail when we talk about the metric computation. @@ -271,7 +271,7 @@ model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) {:else} -## Fine-tuning the model with Keras +## Fine-tuning the model with Keras[[fine-tuning-the-model-with-keras]] First things first, we need an actual model to fine-tune. We'll use the usual `AutoModel` API: @@ -296,7 +296,7 @@ frameworks in 🤗 Transformers! Note that this time we are using a model that was trained on a translation task and can actually be used already, so there is no warning about missing weights or newly initialized ones. -### Data collation +### Data collation[[data-collation]] We'll need a data collator to deal with the padding for dynamic batching. We can't just use a `DataCollatorWithPadding` like in [Chapter 3](/course/chapter3) in this case, because that only pads the inputs (input IDs, attention mask, and token type IDs). Our labels should also be padded to the maximum length encountered in the labels. And, as mentioned previously, the padding value used to pad the labels should be `-100` and not the padding token of the tokenizer, to make sure those padded values are ignored in the loss computation. @@ -395,7 +395,7 @@ tf_eval_dataset = model.prepare_tf_dataset( {/if} -### Metrics +### Metrics[[metrics]] @@ -575,7 +575,7 @@ def compute_metrics(eval_preds): Now that this is done, we are ready to fine-tune our model! -### Fine-tuning the model +### Fine-tuning the model[[fine-tuning-the-model]] The first step is to log in to Hugging Face, so you're able to upload your results to the Model Hub. There's a convenience function to help you with this in a notebook: @@ -783,11 +783,11 @@ If you want to dive a bit more deeply into the training loop, we will now show y {#if fw === 'pt'} -## A custom training loop +## A custom training loop[[a-custom-training-loop]] Let's now take a look at the full training loop, so you can easily customize the parts you need. It will look a lot like what we did in [section 2](/course/chapter7/2) and [Chapter 3](/course/chapter3/4). -### Preparing everything for training +### Preparing everything for training[[preparing-everything-for-training]] You've seen all of this a few times now, so we'll go through the code quite quickly. First we'll build the `DataLoader`s from our datasets, after setting the datasets to the `"torch"` format so we get PyTorch tensors: @@ -871,7 +871,7 @@ repo = Repository(output_dir, clone_from=repo_name) We can now upload anything we save in `output_dir` by calling the `repo.push_to_hub()` method. This will help us upload the intermediate models at the end of each epoch. -### Training loop +### Training loop[[training-loop]] We are now ready to write the full training loop. To simplify its evaluation part, we define this `postprocess()` function that takes predictions and labels and converts them to the lists of strings our `metric` object will expect: @@ -964,7 +964,7 @@ Once this is done, you should have a model that has results pretty similar to th {/if} -## Using the fine-tuned model +## Using the fine-tuned model[[using-the-fine-tuned-model]] We've already shown you how you can use the model we fine-tuned on the Model Hub with the inference widget. To use it locally in a `pipeline`, we just have to specify the proper model identifier: diff --git a/chapters/en/chapter7/5.mdx b/chapters/en/chapter7/5.mdx index 4734bd700..b8afcfaa0 100644 --- a/chapters/en/chapter7/5.mdx +++ b/chapters/en/chapter7/5.mdx @@ -1,6 +1,6 @@ -# Summarization +# Summarization[[summarization]] {#if fw === 'pt'} @@ -29,11 +29,11 @@ In this section we'll take a look at how Transformer models can be used to conde Although there already exist various fine-tuned models for summarization on the [Hugging Face Hub](https://huggingface.co/models?pipeline_tag=summarization&sort=downloads), almost all of these are only suitable for English documents. So, to add a twist in this section, we'll train a bilingual model for English and Spanish. By the end of this section, you'll have a [model](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es) that can summarize customer reviews like the one shown here: - + As we'll see, these summaries are concise because they're learned from the titles that customers provide in their product reviews. Let's start by putting together a suitable bilingual corpus for this task. -## Preparing a multilingual corpus +## Preparing a multilingual corpus[[preparing-a-multilingual-corpus]] We'll use the [Multilingual Amazon Reviews Corpus](https://huggingface.co/datasets/amazon_reviews_multi) to create our bilingual summarizer. This corpus consists of Amazon product reviews in six languages and is typically used to benchmark multilingual classifiers. However, since each review is accompanied by a short title, we can use the titles as the target summaries for our model to learn from! To get started, let's download the English and Spanish subsets from the Hugging Face Hub: @@ -203,7 +203,7 @@ books_dataset = books_dataset.filter(lambda x: len(x["review_title"].split()) > Now that we've prepared our corpus, let's take a look at a few possible Transformer models that one might fine-tune on it! -## Models for text summarization +## Models for text summarization[[models-for-text-summarization]] If you think about it, text summarization is a similar sort of task to machine translation: we have a body of text like a review that we'd like to "translate" into a shorter version that captures the salient features of the input. Accordingly, most Transformer models for summarization adopt the encoder-decoder architecture that we first encountered in [Chapter 1](/course/chapter1), although there are some exceptions like the GPT family of models which can also be used for summarization in few-shot settings. The following table lists some popular pretrained models that can be fine-tuned for summarization. @@ -234,7 +234,7 @@ mT5 doesn't use prefixes, but shares much of the versatility of T5 and has the a -## Preprocessing the data +## Preprocessing the data[[preprocessing-the-data]] @@ -293,7 +293,6 @@ def preprocess_function(examples): examples["review_title"], max_length=max_target_length, truncation=True ) model_inputs["labels"] = labels["input_ids"] - model_inputs["labels_mask"] = labels["attention_mask"] return model_inputs ``` @@ -314,7 +313,7 @@ Now that the corpus has been preprocessed, let's take a look at some metrics tha -## Metrics for text summarization +## Metrics for text summarization[[metrics-for-text-summarization]] @@ -393,7 +392,7 @@ Great, the precision and recall numbers match up! Now what about those other ROU We'll use these ROUGE scores to track the performance of our model, but before doing that let's do something every good NLP practitioner should do: create a strong, yet simple baseline! -### Creating a strong baseline +### Creating a strong baseline[[creating-a-strong-baseline]] A common baseline for text summarization is to simply take the first three sentences of an article, often called the _lead-3_ baseline. We could use full stops to track the sentence boundaries, but this will fail on acronyms like "U.S." or "U.N." -- so instead we'll use the `nltk` library, which includes a better algorithm to handle these cases. You can install the package using `pip` as follows: @@ -455,7 +454,7 @@ We can see that the `rouge2` score is significantly lower than the rest; this li {#if fw === 'pt'} -## Fine-tuning mT5 with the `Trainer` API +## Fine-tuning mT5 with the `Trainer` API[[fine-tuning-mt5-with-the-trainer-api]] Fine-tuning a model for summarization is very similar to the other tasks we've covered in this chapter. The first thing we need to do is load the pretrained model from the `mt5-small` checkpoint. Since summarization is a sequence-to-sequence task, we can load the model with the `AutoModelForSeq2SeqLM` class, which will automatically download and cache the weights: @@ -467,7 +466,7 @@ model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) {:else} -## Fine-tuning mT5 with Keras +## Fine-tuning mT5 with Keras[[fine-tuning-mt5-with-keras]] Fine-tuning a model for summarization is very similar to the other tasks we've covered in this chapter. The first thing we need to do is load the pretrained model from the `mt5-small` checkpoint. Since summarization is a sequence-to-sequence task, we can load the model with the `TFAutoModelForSeq2SeqLM` class, which will automatically download and cache the weights: @@ -790,11 +789,11 @@ result = {key: value.mid.fmeasure * 100 for key, value in result.items()} {#if fw === 'pt'} -## Fine-tuning mT5 with 🤗 Accelerate +## Fine-tuning mT5 with 🤗 Accelerate[[fine-tuning-mt5-with-accelerate]] Fine-tuning our model with 🤗 Accelerate is very similar to the text classification example we encountered in [Chapter 3](/course/chapter3). The main differences will be the need to explicitly generate our summaries during training and define how we compute the ROUGE scores (recall that the `Seq2SeqTrainer` took care of the generation for us). Let's take a look how we can implement these two requirements within 🤗 Accelerate! -### Preparing everything for training +### Preparing everything for training[[preparing-everything-for-training]] The first thing we need to do is create a `DataLoader` for each of our splits. Since the PyTorch dataloaders expect batches of tensors, we need to set the format to `"torch"` in our datasets: @@ -914,7 +913,7 @@ repo = Repository(output_dir, clone_from=repo_name) This will allow us to push the artifacts back to the Hub by calling the `repo.push_to_hub()` method during training! Let's now wrap up our analysis by writing out the training loop. -### Training loop +### Training loop[[training-loop]] The training loop for summarization is quite similar to the other 🤗 Accelerate examples that we've encountered and is roughly split into four main steps: @@ -1017,7 +1016,7 @@ And that's it! Once you run this, you'll have a model and results that are prett {/if} -## Using your fine-tuned model +## Using your fine-tuned model[[using-your-fine-tuned-model]] Once you've pushed the model to the Hub, you can play with it either via the inference widget or with a `pipeline` object, as follows: diff --git a/chapters/en/chapter7/6.mdx b/chapters/en/chapter7/6.mdx index b5e48fdc8..2a42aa6b4 100644 --- a/chapters/en/chapter7/6.mdx +++ b/chapters/en/chapter7/6.mdx @@ -1,6 +1,6 @@ -# Training a causal language model from scratch +# Training a causal language model from scratch[[training-a-causal-language-model-from-scratch]] {#if fw === 'pt'} @@ -30,11 +30,11 @@ In this section we will build a scaled-down version of a code generation model: In [Chapter 6](/course/chapter6) we created an efficient tokenizer to process Python source code, but what we still need is a large-scale dataset to pretrain a model on. Here, we'll apply our tokenizer to a corpus of Python code derived from GitHub repositories. We will then use the `Trainer` API and 🤗 Accelerate to train the model. Let's get to it! - + This is actually showcasing the model that was trained and uploaded to the Hub using the code shown in this section. You can find it [here](https://huggingface.co/huggingface-course/codeparrot-ds?text=plt.imshow%28). Note that since there is some randomization happening in the text generation, you will probably get a slightly different result. -## Gathering the data +## Gathering the data[[gathering-the-data]] Python code is abundantly available from code repositories such as GitHub, which we can use to create a dataset by scraping for every Python repository. This was the approach taken in the [Transformers textbook](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/) to pretrain a large GPT-2 model. Using a GitHub dump of about 180 GB containing roughly 20 million Python files called `codeparrot`, the authors built a dataset that they then shared on the [Hugging Face Hub](https://huggingface.co/datasets/transformersbook/codeparrot). @@ -169,7 +169,7 @@ LICENSE: bsd-3-clause''' We can see that the `content` field contains the code that we want our model to train on. Now that we have a dataset, we need to prepare the texts so they're in a format suitable for pretraining. -## Preparing the dataset +## Preparing the dataset[[preparing-the-dataset]] @@ -259,7 +259,7 @@ Now that we have the dataset ready, let's set up the model! -## Initializing a new model +## Initializing a new model[[initializing-a-new-model]] Our first step is to freshly initialize a GPT-2 model. We'll use the same configuration for our model as for the small GPT-2 model, so we load the pretrained configuration, make sure that the tokenizer size matches the model vocabulary size and pass the `bos` and `eos` (beginning and end of sequence) token IDs: @@ -521,7 +521,7 @@ model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback -## Code generation with a pipeline +## Code generation with a pipeline[[code-generation-with-a-pipeline]] Now is the moment of truth: let's see how well the trained model actually works! We can see in the logs that the loss went down steadily, but to put the model to the test let's take a look at how well it works on some prompts. To do that we'll wrap the model in a text generation `pipeline`, and we'll put it on the GPU for fast generations if there is one available: @@ -655,7 +655,7 @@ Looking at these few examples, it seems that the model has learned some of the s {#if fw === 'pt'} -## Training with 🤗 Accelerate +## Training with 🤗 Accelerate[[training-with-accelerate]] We've seen how to train a model with the `Trainer`, which can allow for some customization. However, sometimes we want full control over the training loop, or we want to make some exotic changes. In this case 🤗 Accelerate is a great choice, and in this section we'll go through the steps to use it to train our model. To make things more interesting, we'll also add a twist to the training loop. diff --git a/chapters/en/chapter7/7.mdx b/chapters/en/chapter7/7.mdx index ed85f59c3..34556be21 100644 --- a/chapters/en/chapter7/7.mdx +++ b/chapters/en/chapter7/7.mdx @@ -1,6 +1,6 @@ -# Question answering +# Question answering[[question-answering]] {#if fw === 'pt'} @@ -28,7 +28,7 @@ Time to look at question answering! This task comes in many flavors, but the one We will fine-tune a BERT model on the [SQuAD dataset](https://rajpurkar.github.io/SQuAD-explorer/), which consists of questions posed by crowdworkers on a set of Wikipedia articles. This will give us a model able to compute predictions like this one: - + This is actually showcasing the model that was trained and uploaded to the Hub using the code shown in this section. You can find it and double-check the predictions [here](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). @@ -38,11 +38,11 @@ This is actually showcasing the model that was trained and uploaded to the Hub u -## Preparing the data +## Preparing the data[[preparing-the-data]] The dataset that is used the most as an academic benchmark for extractive question answering is [SQuAD](https://rajpurkar.github.io/SQuAD-explorer/), so that's the one we'll use here. There is also a harder [SQuAD v2](https://huggingface.co/datasets/squad_v2) benchmark, which includes questions that don't have an answer. As long as your own dataset contains a column for contexts, a column for questions, and a column for answers, you should be able to adapt the steps below. -### The SQuAD dataset +### The SQuAD dataset[[the-squad-dataset]] As usual, we can download and cache the dataset in just one step thanks to `load_dataset()`: @@ -126,7 +126,7 @@ print(raw_datasets["validation"][2]["question"]) we can see that the answer can indeed be one of the three possibilities we saw before. -### Processing the training data +### Processing the training data[[processing-the-training-data]] @@ -447,7 +447,7 @@ len(raw_datasets["train"]), len(train_dataset) As we can see, the preprocessing added roughly 1,000 features. Our training set is now ready to be used -- let's dig into the preprocessing of the validation set! -### Processing the validation data +### Processing the validation data[[processing-the-validation-data]] Preprocessing the validation data will be slightly easier as we don't need to generate labels (unless we want to compute a validation loss, but that number won't really help us understand how good the model is). The real joy will be to interpret the predictions of the model into spans of the original context. For this, we will just need to store both the offset mappings and some way to match each created feature to the original example it comes from. Since there is an ID column in the original dataset, we'll use that ID. @@ -505,19 +505,19 @@ Now that we have preprocessed all the data, we can get to the training. {#if fw === 'pt'} -## Fine-tuning the model with the `Trainer` API +## Fine-tuning the model with the `Trainer` API[[fine-tuning-the-model-with-the-trainer-api]] The training code for this example will look a lot like the code in the previous sections -- the hardest thing will be to write the `compute_metrics()` function. Since we padded all the samples to the maximum length we set, there is no data collator to define, so this metric computation is really the only thing we have to worry about. The difficult part will be to post-process the model predictions into spans of text in the original examples; once we have done that, the metric from the 🤗 Datasets library will do most of the work for us. {:else} -## Fine-tuning the model with Keras +## Fine-tuning the model with Keras[[fine-tuning-the-model-with-keras]] The training code for this example will look a lot like the code in the previous sections, but computing the metrics will be uniquely challenging. Since we padded all the samples to the maximum length we set, there is no data collator to define, so this metric computation is really the only thing we have to worry about. The hard part will be to post-process the model predictions into spans of text in the original examples; once we have done that, the metric from the 🤗 Datasets library will do most of the work for us. {/if} -### Post-processing +### Post-processing[[post-processing]] {#if fw === 'pt'} @@ -788,7 +788,7 @@ compute_metrics(start_logits, end_logits, eval_set, small_eval_set) Looking good! Now let's use this to fine-tune our model. -### Fine-tuning the model +### Fine-tuning the model[[fine-tuning-the-model]] {#if fw === 'pt'} @@ -1006,11 +1006,11 @@ At this stage, you can use the inference widget on the Model Hub to test the mod If you want to dive a bit more deeply into the training loop, we will now show you how to do the same thing using 🤗 Accelerate. -## A custom training loop +## A custom training loop[[a-custom-training-loop]] Let's now have a look at the full training loop, so you can easily customize the parts you need. It will look a lot like the training loop in [Chapter 3](/course/chapter3/4), with the exception of the evaluation loop. We will be able to evaluate the model regularly since we're not constrained by the `Trainer` class anymore. -### Preparing everything for training +### Preparing everything for training[[preparing-everything-for-training]] First we need to build the `DataLoader`s from our datasets. We set the format of those datasets to `"torch"`, and remove the columns in the validation set that are not used by the model. Then, we can use the `default_data_collator` provided by Transformers as a `collate_fn` and shuffle the training set, but not the validation set: @@ -1098,7 +1098,7 @@ repo = Repository(output_dir, clone_from=repo_name) We can now upload anything we save in `output_dir` by calling the `repo.push_to_hub()` method. This will help us upload the intermediate models at the end of each epoch. -## Training loop +## Training loop[[training-loop]] We are now ready to write the full training loop. After defining a progress bar to follow how training goes, the loop has three parts: @@ -1174,7 +1174,7 @@ Once this is done, you should have a model that produces results pretty similar {/if} -## Using the fine-tuned model +## Using the fine-tuned model[[using-the-fine-tuned-model]] We've already shown you how you can use the model we fine-tuned on the Model Hub with the inference widget. To use it locally in a `pipeline`, you just have to specify the model identifier: diff --git a/chapters/en/chapter7/8.mdx b/chapters/en/chapter7/8.mdx index dcda455a5..78693b25b 100644 --- a/chapters/en/chapter7/8.mdx +++ b/chapters/en/chapter7/8.mdx @@ -1,4 +1,4 @@ -# Mastering NLP +# Mastering NLP[[mastering-nlp]] -# End-of-chapter quiz +# End-of-chapter quiz[[end-of-chapter-quiz]] -# Debugging the training pipeline +# Debugging the training pipeline[[debugging-the-training-pipeline]] You've written a beautiful script to train or fine-tune a model on a given task, dutifully following the advice from [Chapter 7](/course/chapter7). But when you launch the command `trainer.train()`, something horrible happens: you get an error 😱! Or worse, everything seems to be fine and the training runs without error, but the resulting model is crappy. In this section, we will show you what you can do to debug these kinds of issues. -## Debugging the training pipeline +## Debugging the training pipeline[[debugging-the-training-pipeline]] @@ -77,7 +77,7 @@ If you try to execute it, you will be met with a rather cryptic error: 'ValueError: You have to specify either input_ids or inputs_embeds' ``` -### Check your data +### Check your data[[check-your-data]] This goes without saying, but if your data is corrupted, the `Trainer` is not going to be able to form batches, let alone train your model. So first things first, you need to have a look at what is inside your training set. @@ -255,7 +255,7 @@ We are only doing the check on the training set here, but you should of course d Now that we know our datasets look good, it's time to check the next step of the training pipeline. -### From datasets to dataloaders +### From datasets to dataloaders[[from-datasets-to-dataloaders]] The next thing that can go wrong in the training pipeline is when the `Trainer` tries to form batches from the training or validation set. Once you are sure the `Trainer`'s datasets are correct, you can try to manually form a batch by executing the following (replace `train` with `eval` for the validation dataloader): @@ -374,7 +374,7 @@ You should then be able to manually debug what happens inside the data collator Now that we've debugged the batch creation process, it's time to pass one through the model! -### Going through the model +### Going through the model[[going-through-the-model]] You should be able to get a batch by executing the following command: @@ -494,7 +494,7 @@ outputs = trainer.model.to(device)(**batch) If you still get an error, make sure you restart your notebook and only execute the last version of the script. -### Performing one optimization step +### Performing one optimization step[[performing-one-optimization-step]] Now that we know that we can build batches that actually go through the model, we are ready for the next step of the training pipeline: computing the gradients and performing an optimization step. @@ -516,7 +516,7 @@ trainer.optimizer.step() Again, if you're using the default optimizer in the `Trainer`, you shouldn't get an error at this stage, but if you have a custom optimizer, there might be some problems to debug here. Don't forget to go back to the CPU if you get a weird CUDA error at this stage. Speaking of CUDA errors, earlier we mentioned a special case. Let's have a look at that now. -### Dealing with CUDA out-of-memory errors +### Dealing with CUDA out-of-memory errors[[dealing-with-cuda-out-of-memory-errors]] Whenever you get an error message that starts with `RuntimeError: CUDA out of memory`, this indicates that you are out of GPU memory. This is not directly linked to your code, and it can happen with a script that runs perfectly fine. This error means that you tried to put too many things in the internal memory of your GPU, and that resulted in an error. Like with other CUDA errors, you will need to restart your kernel to be in a spot where you can run your training again. @@ -528,7 +528,7 @@ In the next part of the course, we'll look at more advanced techniques that can -### Evaluating the model +### Evaluating the model[[evaluating-the-model]] Now that we've solved all the issues with our code, everything is perfect and the training should run smoothly, right? Not so fast! If you run the `trainer.train()` command, everything will look good at first, but after a while you will get the following: @@ -693,11 +693,11 @@ In this instance, there are no more problems, and our script will fine-tune a mo -## Debugging silent errors during training +## Debugging silent errors during training[[debugging-silent-errors-during-training]] What can we do to debug a training that completes without error but doesn't get good results? We'll give you some pointers here, but be aware that this kind of debugging is the hardest part of machine learning, and there is no magical answer. -### Check your data (again!) +### Check your data (again!)[[check-your-data-again]] Your model will only learn something if it's actually possible to learn anything from your data. If there is a bug that corrupts the data or the labels are attributed randomly, it's very likely you won't get any model training on your dataset. So always start by double-checking your decoded inputs and labels, and ask yourself the following questions: @@ -718,7 +718,7 @@ If the loss/metric you get on your initial model is very different from the loss When you are sure your data is perfect, you can see if the model is capable of training on it with one simple test. -### Overfit your model on one batch +### Overfit your model on one batch[[overfit-your-model-on-one-batch]] Overfitting is usually something we try to avoid when training, as it means the model is not learning to recognize the general features we want it to but is instead just memorizing the training samples. However, trying to train your model on one batch over and over again is a good test to check if the problem as you framed it can be solved by the model you are attempting to train. It will also help you see if your initial learning rate is too high. @@ -770,7 +770,7 @@ If you don't manage to have your model obtain perfect results like this, it mean -### Don't tune anything until you have a first baseline +### Don't tune anything until you have a first baseline[[dont-tune-anything-until-you-have-a-first-baseline]] Hyperparameter tuning is always emphasized as being the hardest part of machine learning, but it's just the last step to help you gain a little bit on the metric. Most of the time, the default hyperparameters of the `Trainer` will work just fine to give you good results, so don't launch into a time-consuming and costly hyperparameter search until you have something that beats the baseline you have on your dataset. @@ -778,7 +778,7 @@ Once you have a good enough model, you can start tweaking a bit. Don't try launc If you are tweaking the model itself, keep it simple and don't try anything you can't reasonably justify. Always make sure you go back to the overfitting test to verify that your change hasn't had any unintended consequences. -### Ask for help +### Ask for help[[ask-for-help]] Hopefully you will have found some advice in this section that helped you solve your issue, but if that's not the case, remember you can always ask the community on the [forums](https://discuss.huggingface.co/). diff --git a/chapters/en/chapter8/4_tf.mdx b/chapters/en/chapter8/4_tf.mdx index 0e9a9e822..675219820 100644 --- a/chapters/en/chapter8/4_tf.mdx +++ b/chapters/en/chapter8/4_tf.mdx @@ -1,6 +1,6 @@ -# Debugging the training pipeline +# Debugging the training pipeline[[debugging-the-training-pipeline]] @@ -66,7 +66,7 @@ ValueError: No gradients provided for any variable: ['tf_distil_bert_for_sequenc What does that mean? We tried to train on our data, but we got no gradient? This is pretty perplexing; how do we even begin to debug something like that? When the error you get doesn't immediately suggest where the problem is, the best solution is often to walk through things in sequence, making sure at each stage that everything looks right. And of course, the place to start is always to... -### Check your data +### Check your data[[check-your-data]] This goes without saying, but if your data is corrupted, Keras is not going to be able to fix it for you. So first things first, you need to have a look at what is inside your training set. @@ -131,7 +131,7 @@ Oh no. `nan` is not a very encouraging loss value. Still, we've checked our data, and it looks pretty good. If that's not the problem, where can we go next? The obvious next step is to... -### Check your model +### Check your model[[check-your-model]] `model.fit()` is a really great convenience function in Keras, but it does a lot of things for you, and that can make it trickier to find exactly where a problem has occurred. If you're debugging your model, one strategy that can really help is to pass just a single batch to the model, and look at the outputs for that one batch in detail. Another really helpful tip if the model is throwing errors is to `compile()` the model with `run_eagerly=True`. This will make it a lot slower, but it will make the error messages much more comprehensible, because they'll indicate exactly where in your model's code the problem occurred. @@ -347,7 +347,7 @@ model.fit(train_dataset) We're training! No more `nan`s, and our loss is declining... sort of. If you watch it for a while, you might start to get a bit impatient, because the loss value stays stubbornly high. Let's stop training here and try to think about what could be causing this problem. At this point, we're pretty sure both the data and the model are okay, but our model isn't learning well. What else is left? It's time to... -### Check your hyperparameters +### Check your hyperparameters[[check-your-hyperparameters]] If you look back at the code above, you might not be able to see any hyperparameters at all, except perhaps the `batch_size`, and that doesn't seem like a likely culprit. Don't be fooled, though; there are always hyperparameters, and if you can't see them, it just means that you don't know what they're set to. In particular, remember a critical thing about Keras: if you set a loss, optimizer, or activation function with a string, _all of its arguments will be set to their default values_. This means that even though using strings for this is very convenient, you should be very careful when doing so, as it can easily hide critical things from you. (Anyone trying the optional challenge above should take careful note of this fact.) @@ -380,11 +380,11 @@ model.fit(train_dataset) Now our loss is really going somewhere! Training finally looks like it's working. There's a lesson here: when your model is running but loss isn't declining, and you're sure your data is okay, it's a good idea to check hyperparameters like the learning rate and weight decay. Setting either of those too high is very likely to cause training to "stall" at a high loss value. -## Other potential issues +## Other potential issues[[other-potential-issues]] We've covered the issues in the script above, but there are several other common errors you might face. Let's take a look at a (very incomplete) list. -### Dealing with out-of-memory errors +### Dealing with out-of-memory errors[[dealing-with-out-of-memory-errors]] The telltale sign of running out of memory is an error like "OOM when allocating tensor" -- OOM is short for "out of memory." This is a very common hazard when dealing with large language models. If you encounter this, a good strategy is to halve your batch size and try again. Bear in mind, though, that some models are *very* large. For example, the full-size GPT-2 has 1.5B parameters, which means you'll need 6 GB of memory just to store the model, and another 6 GB for its gradients! Training the full GPT-2 model will usually require over 20 GB of VRAM no matter what batch size you use, which only a few GPUs have. More lightweight models like `distilbert-base-cased` are much easier to run, and train much more quickly too. @@ -394,7 +394,7 @@ In the next part of the course, we'll look at more advanced techniques that can -### Hungry Hungry TensorFlow 🦛 +### Hungry Hungry TensorFlow 🦛[[hungry-hungry-tensorflow]] One particular quirk of TensorFlow that you should be aware of is that it allocates *all* of your GPU memory to itself as soon as you load a model or do any training, and then it divides up that memory as required. This is different from the behavior of other frameworks, like PyTorch, which allocate memory as required with CUDA rather than doing it internally. One advantage of the TensorFlow approach is that it can often give useful errors when you run out of memory, and it can recover from that state without crashing the whole CUDA kernel. But there's also an important downside: if you run two TensorFlow processes at once, then **you're going to have a bad time**. @@ -403,7 +403,7 @@ If you're running on Colab you don't need to worry about this, but if you're run If you start getting errors about CUDA, BLAS, or cuBLAS in code that worked before, this is very often the culprit. You can use a command like `nvidia-smi` to check -- when you shut down or restart your current notebook, is most of your memory free, or is it still in use? If it's still in use, something else is holding on to it! -### Check your data (again!) +### Check your data (again!)[[check-your-data-again]] Your model will only learn something if it's actually possible to learn anything from your data. If there is a bug that corrupts the data or the labels are attributed randomly, it's very likely you won't get any model training on your dataset. One helpful tool here is `tokenizer.decode()`. This will turn `input_ids` back into strings, so you can view the data and see if your training data is teaching what you want it to teach. For example, after you get a `batch` from your `tf.data.Dataset` like we did above, you can decode the first element like so: @@ -432,7 +432,7 @@ If the loss/metric you get on your initial model before any training is very dif When you are sure your data is perfect, you can see if the model is capable of training on it with one simple test. -### Overfit your model on one batch +### Overfit your model on one batch[[overfit-your-model-on-one-batch]] Overfitting is usually something we try to avoid when training, as it means the model is not learning to recognize the general features we want it to but is instead just memorizing the training samples. However, trying to train your model on one batch over and over again is a good test to check if the problem as you framed it can be solved by the model you are attempting to train. It will also help you see if your initial learning rate is too high. @@ -464,7 +464,7 @@ If you don't manage to have your model obtain perfect results like this, it mean -### Don't tune anything until you have a first baseline +### Don't tune anything until you have a first baseline[[dont-tune-anything-until-you-have-a-first-baseline]] Intense hyperparameter tuning is always emphasized as being the hardest part of machine learning, but it's just the last step to help you gain a little bit on the metric. *Very* bad values for your hyperparameters, like using the default Adam learning rate of 1e-3 with a Transformer model, will make learning proceed very slowly or completely stall, of course, but most of the time "reasonable" hyperparameters, like a learning rate from 1e-5 to 5e-5, will work just fine to give you good results. So, don't launch into a time-consuming and costly hyperparameter search until you have something that beats the baseline you have on your dataset. @@ -472,7 +472,7 @@ Once you have a good enough model, you can start tweaking a bit. Don't try launc If you are tweaking the model itself, keep it simple and don't try anything you can't reasonably justify. Always make sure you go back to the overfitting test to verify that your change hasn't had any unintended consequences. -### Ask for help +### Ask for help[[ask-for-help]] Hopefully you will have found some advice in this section that helped you solve your issue, but if that's not the case, remember you can always ask the community on the [forums](https://discuss.huggingface.co/). diff --git a/chapters/en/chapter8/5.mdx b/chapters/en/chapter8/5.mdx index 99a4c7b4b..a17b9c234 100644 --- a/chapters/en/chapter8/5.mdx +++ b/chapters/en/chapter8/5.mdx @@ -1,4 +1,4 @@ -# How to write a good issue +# How to write a good issue[[how-to-write-a-good-issue]] -# End-of-chapter quiz +# End-of-chapter quiz[[end-of-chapter-quiz]] + * An extractive **question answering** model that takes in a context paragraph and a quest and outputs a response and a probability score (we discussed this kind of model [in Chapter 7](/course/chapter7/7)): - + * A **background removal** model that takes in an image and outputs the image with the background removed: - + This chapter is broken down into sections which include both _concepts_ and _applications_. After you learn the concept in each section, you'll apply it to build a particular kind of demo, ranging from image classification to speech recognition. By the time you finish this chapter, you'll be able to build these demos (and many more!) in just a few lines of Python code. diff --git a/chapters/en/chapter9/2.mdx b/chapters/en/chapter9/2.mdx index 8dee73747..82081783d 100644 --- a/chapters/en/chapter9/2.mdx +++ b/chapters/en/chapter9/2.mdx @@ -1,4 +1,4 @@ -# Building your first demo +# Building your first demo[[building-your-first-demo]] + Try using this GUI right now with your own name or some other input! @@ -62,7 +62,7 @@ textbox = gr.Textbox(label="Type your name here:", placeholder="John Doe", lines gr.Interface(fn=greet, inputs=textbox, outputs="text").launch() ``` - + Here, we've created an input textbox with a label, a placeholder, and a set number of lines. You could do the same for the output textbox, but we'll leave that for now. @@ -72,7 +72,7 @@ with any kind of inputs or outputs. In this section, we've started with a simple textbox, but in the next sections, we'll cover other kinds of inputs and outputs. Let's now take a look at including some NLP in a Gradio application. -## 🤖 Including model predictions +## 🤖 Including model predictions[[including-model-predictions]] Let's now build a simple interface that allows you to demo a **text-generation** model like GPT-2. @@ -113,6 +113,6 @@ gr.Interface(fn=predict, inputs="text", outputs="text").launch() That's it! You can now use this interface to generate text using the GPT-2 model as shown below 🤯. - + Keep reading to see how to build other kinds of demos with Gradio! \ No newline at end of file diff --git a/chapters/en/chapter9/3.mdx b/chapters/en/chapter9/3.mdx index b3f18a27a..46bc2d88f 100644 --- a/chapters/en/chapter9/3.mdx +++ b/chapters/en/chapter9/3.mdx @@ -1,4 +1,4 @@ -# Understanding the Interface class +# Understanding the Interface class[[understanding-the-interface-class]] open the demo in a separate tab.) - + You should now be able to record your voice and hear yourself speaking in reverse - spooky 👻! -## Handling multiple inputs and outputs +## Handling multiple inputs and outputs[[handling-multiple-inputs-and-outputs]] Let's say we had a more complicated function, with multiple inputs and outputs. In the example below, we have a function that takes a dropdown index, a slider value, and number, @@ -118,10 +118,10 @@ gr.Interface( ).launch() ``` - + -### The `launch()` method +### The `launch()` method[[the-launch-method]] So far, we have used the `launch()` method to launch the interface, but we haven't really discussed what it does. @@ -138,7 +138,7 @@ You can customize the behavior of `launch()` through different parameters: We'll cover the `share` parameter in a lot more detail in the next section! -## ✏️ Let's apply it! +## ✏️ Let's apply it![[lets-apply-it]] Let's build an interface that allows you to demo a **speech-recognition** model. To make it interesting, we will accept *either* a mic input or an uploaded file. @@ -176,7 +176,7 @@ gr.Interface( If your browser doesn't ask you for microphone permissions, open the demo in a separate tab. - + That's it! You can now use this interface to transcribe audio. Notice here that diff --git a/chapters/en/chapter9/4.mdx b/chapters/en/chapter9/4.mdx index 2dcd458e6..912d5986d 100644 --- a/chapters/en/chapter9/4.mdx +++ b/chapters/en/chapter9/4.mdx @@ -1,4 +1,4 @@ -# Sharing demos with others +# Sharing demos with others[[sharing-demos-with-others]] Overview of a gradio interface @@ -50,9 +50,9 @@ gr.Interface( Using the options above, we end up with a more complete interface. Try the interface below: - + -### Sharing your demo with temporary links +### Sharing your demo with temporary links[[sharing-your-demo-with-temporary-links]] Now that we have a working demo of our machine learning model, let's learn how to easily share a link to our interface. Interfaces can be easily shared publicly by setting `share=True` in the `launch()` method: @@ -64,7 +64,7 @@ This generates a public, shareable link that you can send to anybody! When you s Keep in mind, however, that these links are publicly accessible, meaning that anyone can use your model for prediction! Therefore, make sure not to expose any sensitive information through the functions you write, or allow any critical changes to occur on your device. If you set `share=False` (the default), only a local link is created. -### Hosting your demo on Hugging Face Spaces +### Hosting your demo on Hugging Face Spaces[[hosting-your-demo-on-hugging-face-spaces]] A share link that you can pass around to collegues is cool, but how can you permanently host your demo and have it exist in its own "space" on the internet? @@ -74,7 +74,7 @@ interface code will exist in an `app.py` file. [Read a step-by-step tutorial](ht -## ✏️ Let's apply it! +## ✏️ Let's apply it![[lets-apply-it]] Using what we just learned in the sections so far, let's create the sketch recognition demo we saw in [section one of this chapter](/course/chapter9/1). Let's add some customization to our interface and set `share=True` to create a public link we can pass around. @@ -132,7 +132,7 @@ interface = gr.Interface( interface.launch(share=True) ``` - + Notice the `live=True` parameter in `Interface`, which means that the sketch demo makes diff --git a/chapters/en/chapter9/5.mdx b/chapters/en/chapter9/5.mdx index b7d61d08a..b32056e33 100644 --- a/chapters/en/chapter9/5.mdx +++ b/chapters/en/chapter9/5.mdx @@ -1,4 +1,4 @@ -# Integrations with the Hugging Face Hub +# Integrations with the Hugging Face Hub[[integrations-with-the-hugging-face-hub]] + Loading a model in this way uses Hugging Face's [Inference API](https://huggingface.co/inference-api), instead of loading the model in memory. This is ideal for huge models like GPT-J or T0pp which require lots of RAM. -### Loading from Hugging Face Spaces +### Loading from Hugging Face Spaces[[loading-from-hugging-face-spaces]] To load any Space from the Hugging Face Hub and recreate it locally, you can pass `spaces/` to the `Interface`, followed by the name of the Space. Remember the demo from section 1 that removes the background of an image? Let's load it from Hugging Face Spaces: @@ -56,7 +56,7 @@ Remember the demo from section 1 that removes the background of an image? Let's gr.Interface.load("spaces/abidlabs/remove-bg").launch() ``` - + One of the cool things about loading demos from the Hub or Spaces is that you customize them by overriding any of the @@ -68,6 +68,6 @@ gr.Interface.load( ).launch() ``` - + Now that we've explored a few ways to integrate Gradio with the Hugging Face Hub, let's take a look at some advanced features of the `Interface` class. That's the topic of the next section! \ No newline at end of file diff --git a/chapters/en/chapter9/6.mdx b/chapters/en/chapter9/6.mdx index 74ba03a08..e47d25541 100644 --- a/chapters/en/chapter9/6.mdx +++ b/chapters/en/chapter9/6.mdx @@ -1,4 +1,4 @@ -# Advanced Interface features +# Advanced Interface features[[advanced-interface-features]] + Notice how the state of the output component persists across submits. Note: you can pass in a default value to the state parameter, which is used as the initial value of the state. -### Using interpretation to understand predictions +### Using interpretation to understand predictions[[using-interpretation-to-understand-predictions]] Most machine learning models are black boxes and the internal logic of the function is hidden from the end user. To encourage transparency, we've made it very easy to add interpretation to your model by simply setting the interpretation keyword in the Interface class to default. This allows your users to understand what parts of the input are responsible for the output. Take a look at the simple interface below which shows an image classifier that also includes interpretation: @@ -94,7 +94,7 @@ gr.Interface( Test the interpretation function by submitting an input then clicking Interpret under the output component. - + Besides the default interpretation method Gradio provides, you can also specify `shap` for the `interpretation` parameter and set the `num_shap` parameter. This uses Shapley-based interpretation, which you can read more about [here](https://christophm.github.io/interpretable-ml-book/shap.html). Lastly, you can also pass in your own interpretation function into the `interpretation` parameter. See an example in Gradio's getting started page [here](https://gradio.app/getting_started/). diff --git a/chapters/en/chapter9/7.mdx b/chapters/en/chapter9/7.mdx index 3dc2bf4ca..3c74d8452 100644 --- a/chapters/en/chapter9/7.mdx +++ b/chapters/en/chapter9/7.mdx @@ -1,4 +1,4 @@ -# Introduction to Gradio Blocks +# Introduction to Gradio Blocks[[introduction-to-gradio-blocks]] + This simple example above introduces 4 concepts that underlie Blocks: @@ -76,7 +76,7 @@ The order in which you instantiate components matters as each element gets rende 4. Blocks automatically figures out whether a component should be interactive (accept user input) or not, based on the event triggers you define. In our example, the first textbox is interactive, since its value is used by the `flip_text()` function. The second textbox is not interactive, since its value is never used as an input. In some cases, you might want to override this, which you can do by passing a boolean to the `interactive` parameter of the component (e.g. `gr.Textbox(placeholder="Flip this text", interactive=True)`). -### Customizing the layout of your demo +### Customizing the layout of your demo[[customizing-the-layout-of-your-demo]] How can we use `Blocks` to customize the layout of our demo? By default, `Blocks` renders the components that you create vertically in one column. You can change that by creating additional columns `with gradio.Column():` or rows `with gradio.Row():` and creating components within those contexts. @@ -121,12 +121,12 @@ with demo: demo.launch() ``` - + You'll notice that in this example, we've also created a `Button` component in each tab, and we've assigned a click event to each button, which is what actually runs the function. -### Exploring events and state +### Exploring events and state[[exploring-events-and-state]] Just as you can control the layout, `Blocks` gives you fine-grained control over what events trigger function calls. Each component and many layouts have specific events that they support. @@ -160,9 +160,9 @@ with gr.Blocks() as demo: demo.launch() ``` - + -### Creating multi-step demos +### Creating multi-step demos[[creating-multi-step-demos]] In some cases, you might want a _multi-step demo_, in which you reuse the output of one function as the input to the next. This is really easy to do with `Blocks`, as you can use a component for the input of one event trigger but the output of another. Take a look at the text component in the example below, its value is the result of a speech-to-text model, but also gets passed into a sentiment analysis model: @@ -200,9 +200,9 @@ with demo: demo.launch() ``` - + -### Updating Component Properties +### Updating Component Properties[[updating-component-properties]] So far, we have seen how to create events to update the value of another component. But what happens if you want to change other properties of a component, like the visibility of a textbox or the choices in a radio button group? You can do this by returning a component class's `update()` method instead of a regular return value from your function. @@ -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/chapter9/8.mdx b/chapters/en/chapter9/8.mdx index 1c6dd04c2..4b5e5d924 100644 --- a/chapters/en/chapter9/8.mdx +++ b/chapters/en/chapter9/8.mdx @@ -1,4 +1,4 @@ -# Gradio, check! +# Gradio, check![[gradio-check]] -# End-of-chapter quiz +# End-of-chapter quiz[[end-of-chapter-quiz]] -## Workshops +## Workshops[[workshops]] In the first workshop, Merve welcomes Lewis to discuss section 7 of chapter 7 about [question answering]( https://huggingface.co/course/chapter7/7?fw=pt). diff --git a/chapters/en/events/2.mdx b/chapters/en/events/2.mdx index 2b02ed3cb..076a7e734 100644 --- a/chapters/en/events/2.mdx +++ b/chapters/en/events/2.mdx @@ -1,8 +1,8 @@ -# Part 2 Release Event +# Part 2 Release Event[[part-2-release-event]] For the release of part 2 of the course, we organized a live event with two days of talks before a fine-tuning sprint. If you missed it, you can catch up with the talks which are all listed below! -## Day 1: A high-level view of Transformers and how to train them +## Day 1: A high-level view of Transformers and how to train them[[day-1-a-high-level-view-of-transformers-and-how-to-train-them]] **Thomas Wolf:** *Transfer Learning and the birth of the Transformers library* @@ -78,7 +78,7 @@ Mark Saroufim is a Partner Engineer at Pytorch working on OSS production tools i Jakob Uszkoreit is the co-founder of Inceptive. Inceptive designs RNA molecules for vaccines and therapeutics using large-scale deep learning in a tight loop with high throughput experiments with the goal of making RNA-based medicines more accessible, more effective and more broadly applicable. Previously, Jakob worked at Google for more than a decade, leading research and development teams in Google Brain, Research and Search working on deep learning fundamentals, computer vision, language understanding and machine translation. -## Day 2: The tools to use +## Day 2: The tools to use[[day-2-the-tools-to-use]] **Lewis Tunstall:** *Simple Training with the 🤗 Transformers Trainer* diff --git a/chapters/en/events/3.mdx b/chapters/en/events/3.mdx index 4febfe8f2..b0770d38d 100644 --- a/chapters/en/events/3.mdx +++ b/chapters/en/events/3.mdx @@ -1,4 +1,4 @@ -# Gradio Blocks Party +# Gradio Blocks Party[[gradio-blocks-party]] Along with the release of the Gradio chapter of the course, Hugging Face hosted a community event on building cool machine learning demos using the new Gradio Blocks feature. diff --git a/chapters/fr/chapter0/1.mdx b/chapters/fr/chapter0/1.mdx index 5a1be8e8a..47bfa9809 100644 --- a/chapters/fr/chapter0/1.mdx +++ b/chapters/fr/chapter0/1.mdx @@ -1,110 +1,110 @@ -# Introduction - -Bienvenue au cours d'Hugging Face ! Cette introduction est là pour vous guider dans la mise en place d'un environnement de travail. Si vous venez de commencer le cours, nous vous recommandons de consulter d'abord le [chapitre 1](/course/fr/chapter1) puis de revenir ici et de configurer votre environnement afin de pouvoir essayer le code vous-même. - -Toutes les bibliothèques que nous utiliserons dans ce cours sont disponibles sous forme de *packages* Python. Nous allons donc vous montrer comment configurer un environnement Python et installer les bibliothèques spécifiques dont vous aurez besoin. - -Nous aborderons deux façons de configurer votre environnement de travail : soit en utilisant un *notebook* Google Colab, soit en utilisant un environnement virtuel Python. N'hésitez pas à choisir celle qui vous convient le mieux. Pour les débutants, nous vous recommandons vivement de commencer en utilisant un *notebook* Google Colab. - -Notez que nous ne couvrirons pas le système Windows. Si vous travaillez sous Windows, nous vous recommandons de suivre le cours en utilisant un *notebook* Google Colab. Si vous utilisez une distribution Linux ou macOS, vous pouvez utiliser l'une des deux approches décrites ci-dessous. - -La plupart du cours repose sur le fait que vous ayez un compte Hugging Face. Si vous n'en disposez pas d'un, nous vous recommandons d'en créer un dès maintenant : [créer un compte](https://huggingface.co/join). - -## Utilisation d'un notebook Google Colab - -L'utilisation d'un *notebook* Google Colab est la configuration la plus simple possible. Démarrez un *notebook* dans votre navigateur et passez directement au codage ! - -Si vous n'êtes pas familier avec Colab, nous vous recommandons de commencer par suivre l'[introduction](https://colab.research.google.com/notebooks/intro.ipynb). Colab vous permet d'utiliser du matériel comme les GPUs ou les TPUs et est gratuit pour les petites charges de travail. - -Une fois que vous vous sentez suffisamment à l'aise avec Colab, créez un nouveau *notebook* et commencez à le configurer : - -
-An empty colab notebook -
- -L'étape suivante consiste à installer les bibliothèques que nous allons utiliser dans ce cours. Nous utiliserons `pip` pour l'installation qui est le gestionnaire de *packages* pour Python. Dans les *notebooks*, vous pouvez exécuter des commandes système en les faisant précéder du caractère `!`. Vous pouvez donc installer la bibliothèque 🤗 *Transformers* comme suit : - -``` -!pip install transformers -``` - -Vous pouvez vous assurer que le paquet a été correctement installé en l'important dans votre runtime Python : - -``` -import transformers -``` - -
-A gif showing the result of the two commands above: installation and import -
- -Cela installe une version très légère de 🤗 *Transformers*. En particulier, aucun *framework* d'apprentissage automatique spécifique (comme PyTorch ou TensorFlow) n'est installé. Comme nous utiliserons de nombreuses fonctionnalités différentes de la bibliothèque, nous recommandons d'installer la version de développement qui est livrée avec toutes les dépendances requises pour à peu près tous les cas d'utilisation imaginables : - -``` -!pip install transformers[sentencepiece] -``` - -Cela prendra un peu de temps, mais vous serez alors prêt pour le reste du cours ! - - -## Utilisation d'un environnement virtuel Python - -Si vous préférez utiliser un environnement virtuel Python, la première étape consiste à installer Python sur votre système. Nous vous recommandons de suivre [ce guide](https://realpython.com/installing-python/) pour commencer. - -Une fois Python installé, vous devriez être en mesure d'exécuter des commandes Python dans votre terminal. Vous pouvez commencer par exécuter la commande suivante pour vous assurer qu'il est correctement installé avant de passer aux étapes suivantes : `python --version`. Cette commande devrait vous indiquer la version de Python disponible sur votre système. - -Lorsque vous exécutez une commande Python dans votre terminal, comme `python --version`, vous devez considérer le programme qui exécute votre commande comme la fonction « main » Python sur votre système. Nous vous recommandons de garder cette installation principale libre de tout *package* et de l'utiliser pour créer des environnements séparés pour chaque application sur laquelle vous travaillez. De cette façon, chaque application peut avoir ses propres dépendances et *packages*, et vous n'aurez pas à vous soucier de problèmes potentiels de compatibilité avec d'autres applications. - -En Python, cela se fait avec les [*environnements virtuels*](https://docs.python.org/3/tutorial/venv.html), qui sont des arbres de répertoires autonomes contenant chacun une installation Python avec une version particulière de Python ainsi que tous les *packages* dont l'application a besoin. La création d'un tel environnement virtuel peut se faire à l'aide d'un certain nombre d'outils différents, mais nous utiliserons le *package* officiel de Python : [`venv`](https://docs.python.org/3/library/venv.html#module-venv). - -Tout d'abord, créez le répertoire dans lequel vous souhaitez que votre application se trouve. Par exemple, vous pouvez créer un nouveau répertoire appelé *transformers-course* à la racine de votre répertoire personnel : -``` -mkdir ~/transformers-course -cd ~/transformers-course -``` - -A l'intérieur de ce répertoire, créez un environnement virtuel en utilisant le module Python `venv` : - -``` -python -m venv .env -``` - -Vous devriez maintenant avoir un répertoire appelé *.env* dans votre dossier autrement vide : - -``` -ls -a -``` - -```out -. .. .env -``` - -Vous pouvez entrer et sortir de votre environnement virtuel avec les scripts `activate` et `deactivate` : - -``` -# Activate the virtual environment -source .env/bin/activate - -# Deactivate the virtual environment -source .env/bin/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 ! - -``` -which python -``` - -```out -/home//transformers-course/.env/bin/python -``` - -### Installation des dépendances - -Comme dans la section précédente sur l'utilisation des instances Google Colab, vous devez maintenant installer les *packages* requis pour continuer. Encore une fois, vous pouvez installer la version de développement de 🤗 *Transformers* à l'aide du gestionnaire de packages `pip` : - -``` -pip install "transformers[sentencepiece]" -``` - -Vous êtes maintenant prêt ! +# Introduction + +Bienvenue au cours d'Hugging Face ! Cette introduction est là pour vous guider dans la mise en place d'un environnement de travail. Si vous venez de commencer le cours, nous vous recommandons de consulter d'abord le [chapitre 1](/course/fr/chapter1) puis de revenir ici et de configurer votre environnement afin de pouvoir essayer le code vous-même. + +Toutes les bibliothèques que nous utiliserons dans ce cours sont disponibles sous forme de *packages* Python. Nous allons donc vous montrer comment configurer un environnement Python et installer les bibliothèques spécifiques dont vous aurez besoin. + +Nous aborderons deux façons de configurer votre environnement de travail : soit en utilisant un *notebook* Google Colab, soit en utilisant un environnement virtuel Python. N'hésitez pas à choisir celle qui vous convient le mieux. Pour les débutants, nous vous recommandons vivement de commencer en utilisant un *notebook* Google Colab. + +Notez que nous ne couvrirons pas le système Windows. Si vous travaillez sous Windows, nous vous recommandons de suivre le cours en utilisant un *notebook* Google Colab. Si vous utilisez une distribution Linux ou macOS, vous pouvez utiliser l'une des deux approches décrites ci-dessous. + +La plupart du cours repose sur le fait que vous ayez un compte Hugging Face. Si vous n'en disposez pas d'un, nous vous recommandons d'en créer un dès maintenant : [créer un compte](https://huggingface.co/join). + +## Utilisation d'un notebook Google Colab + +L'utilisation d'un *notebook* Google Colab est la configuration la plus simple possible. Démarrez un *notebook* dans votre navigateur et passez directement au codage ! + +Si vous n'êtes pas familier avec Colab, nous vous recommandons de commencer par suivre l'[introduction](https://colab.research.google.com/notebooks/intro.ipynb). Colab vous permet d'utiliser du matériel comme les GPUs ou les TPUs et est gratuit pour les petites charges de travail. + +Une fois que vous vous sentez suffisamment à l'aise avec Colab, créez un nouveau *notebook* et commencez à le configurer : + +
+An empty colab notebook +
+ +L'étape suivante consiste à installer les bibliothèques que nous allons utiliser dans ce cours. Nous utiliserons `pip` pour l'installation qui est le gestionnaire de *packages* pour Python. Dans les *notebooks*, vous pouvez exécuter des commandes système en les faisant précéder du caractère `!`. Vous pouvez donc installer la bibliothèque 🤗 *Transformers* comme suit : + +``` +!pip install transformers +``` + +Vous pouvez vous assurer que le paquet a été correctement installé en l'important dans votre runtime Python : + +``` +import transformers +``` + +
+A gif showing the result of the two commands above: installation and import +
+ +Cela installe une version très légère de 🤗 *Transformers*. En particulier, aucun *framework* d'apprentissage automatique spécifique (comme PyTorch ou TensorFlow) n'est installé. Comme nous utiliserons de nombreuses fonctionnalités différentes de la bibliothèque, nous recommandons d'installer la version de développement qui est livrée avec toutes les dépendances requises pour à peu près tous les cas d'utilisation imaginables : + +``` +!pip install transformers[sentencepiece] +``` + +Cela prendra un peu de temps, mais vous serez alors prêt pour le reste du cours ! + + +## Utilisation d'un environnement virtuel Python + +Si vous préférez utiliser un environnement virtuel Python, la première étape consiste à installer Python sur votre système. Nous vous recommandons de suivre [ce guide](https://realpython.com/installing-python/) pour commencer. + +Une fois Python installé, vous devriez être en mesure d'exécuter des commandes Python dans votre terminal. Vous pouvez commencer par exécuter la commande suivante pour vous assurer qu'il est correctement installé avant de passer aux étapes suivantes : `python --version`. Cette commande devrait vous indiquer la version de Python disponible sur votre système. + +Lorsque vous exécutez une commande Python dans votre terminal, comme `python --version`, vous devez considérer le programme qui exécute votre commande comme la fonction « main » Python sur votre système. Nous vous recommandons de garder cette installation principale libre de tout *package* et de l'utiliser pour créer des environnements séparés pour chaque application sur laquelle vous travaillez. De cette façon, chaque application peut avoir ses propres dépendances et *packages*, et vous n'aurez pas à vous soucier de problèmes potentiels de compatibilité avec d'autres applications. + +En Python, cela se fait avec les [*environnements virtuels*](https://docs.python.org/3/tutorial/venv.html), qui sont des arbres de répertoires autonomes contenant chacun une installation Python avec une version particulière de Python ainsi que tous les *packages* dont l'application a besoin. La création d'un tel environnement virtuel peut se faire à l'aide d'un certain nombre d'outils différents, mais nous utiliserons le *package* officiel de Python : [`venv`](https://docs.python.org/3/library/venv.html#module-venv). + +Tout d'abord, créez le répertoire dans lequel vous souhaitez que votre application se trouve. Par exemple, vous pouvez créer un nouveau répertoire appelé *transformers-course* à la racine de votre répertoire personnel : +``` +mkdir ~/transformers-course +cd ~/transformers-course +``` + +A l'intérieur de ce répertoire, créez un environnement virtuel en utilisant le module Python `venv` : + +``` +python -m venv .env +``` + +Vous devriez maintenant avoir un répertoire appelé *.env* dans votre dossier autrement vide : + +``` +ls -a +``` + +```out +. .. .env +``` + +Vous pouvez entrer et sortir de votre environnement virtuel avec les scripts `activate` et `deactivate` : + +``` +# Activate the virtual environment +source .env/bin/activate + +# Deactivate the virtual environment +source .env/bin/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 ! + +``` +which python +``` + +```out +/home//transformers-course/.env/bin/python +``` + +### Installation des dépendances + +Comme dans la section précédente sur l'utilisation des instances Google Colab, vous devez maintenant installer les *packages* requis pour continuer. Encore une fois, vous pouvez installer la version de développement de 🤗 *Transformers* à l'aide du gestionnaire de packages `pip` : + +``` +pip install "transformers[sentencepiece]" +``` + +Vous êtes maintenant prêt ! diff --git a/chapters/fr/chapter7/2.mdx b/chapters/fr/chapter7/2.mdx index 804e7fb82..bd7f1c0a6 100644 --- a/chapters/fr/chapter7/2.mdx +++ b/chapters/fr/chapter7/2.mdx @@ -32,8 +32,8 @@ La première application que nous allons explorer est la classification de *toke Bien sûr, il existe de nombreux autres types de problèmes de classification de *tokens*. Ce ne sont là que quelques exemples représentatifs. Dans cette section, nous allons *finetuner* un modèle (BERT) sur la tâche de NER. Il sera alors capable de calculer des prédictions comme celle-ci : - - + + One-hot encoded labels for question answering. diff --git a/chapters/fr/chapter7/3.mdx b/chapters/fr/chapter7/3.mdx index 0579e6af9..87d31f385 100644 --- a/chapters/fr/chapter7/3.mdx +++ b/chapters/fr/chapter7/3.mdx @@ -35,8 +35,8 @@ Ce processus de *finetuning* d'un modèle de langage pré-entraîné sur des don À la fin de cette section, vous aurez un [modèle de langage masqué](https://huggingface.co/huggingface-course/distilbert-base-uncased-finetuned-imdb?text=This+is+a+great+%5BMASK%5D.) sur le *Hub* qui peut autocompléter des phrases comme indiqué ci-dessous : - - + + Allons-y ! diff --git a/chapters/fr/chapter7/4.mdx b/chapters/fr/chapter7/4.mdx index db3d8640b..236194972 100644 --- a/chapters/fr/chapter7/4.mdx +++ b/chapters/fr/chapter7/4.mdx @@ -35,8 +35,8 @@ Dans cette section, nous allons *finetuner* un modèle Marian pré-entraîné po Une fois que nous aurons terminé, nous aurons un modèle capable de faire des prédictions comme celle-ci : - - + + One-hot encoded labels for question answering. diff --git a/chapters/fr/chapter7/5.mdx b/chapters/fr/chapter7/5.mdx index 148bc1af5..cb24bcd28 100644 --- a/chapters/fr/chapter7/5.mdx +++ b/chapters/fr/chapter7/5.mdx @@ -29,8 +29,8 @@ Dans cette section, nous allons voir comment les *transformers* peuvent être ut Bien qu'il existe déjà plusieurs modèles *finetunés* pour le résumé sur le [*Hub*](https://huggingface.co/models?pipeline_tag=summarization&sort=downloads), la plupart d'entre eux ne sont adaptés qu'aux documents en anglais. Ainsi, pour ajouter une touche d'originalité à cette section, nous allons entraîner un modèle bilingue pour l'anglais et l'espagnol. À la fin de cette section, vous disposerez d'un [modèle](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es) capable de résumer les commentaires des clients comme celui présenté ici : - - + + Comme nous allons le voir, ces résumés sont concis car ils sont appris à partir des titres que les clients fournissent dans leurs commentaires sur les produits. Commençons par constituer un corpus bilingue approprié pour cette tâche. diff --git a/chapters/fr/chapter7/6.mdx b/chapters/fr/chapter7/6.mdx index 0ff1ce37b..f73f51eea 100644 --- a/chapters/fr/chapter7/6.mdx +++ b/chapters/fr/chapter7/6.mdx @@ -31,8 +31,8 @@ Dans cette section, nous allons construire une version réduite d'un modèle de Dans le [chapitre 6](/course/fr/chapter6), nous avons créé un *tokenizer* efficace pour traiter du code Python. Nous avons besoin d'un jeu de données à grande échelle pour pré-entraîner un modèle. Ici, nous allons appliquer notre *tokenizer* à un corpus de code Python provenant des dépôts GitHub. Nous utiliserons ensuite l'API `Trainer` et 🤗 *Accelerate* pour entraîner le modèle. C'est parti ! - - + + Il s'agit d'une présentation du modèle qui a été entraîné à l'aide du code présenté dans cette section et qui a ensuité été téléchargé sur le *Hub*. Vous pouvez le trouver [ici](https://huggingface.co/huggingface-course/codeparrot-ds?text=plt.imshow%28). Notez qu'étant donné qu'il y a un certains aléat dans la génération du texte, vous obtiendrez probablement un résultat légèrement différent. diff --git a/chapters/fr/chapter7/7.mdx b/chapters/fr/chapter7/7.mdx index 5ba430b64..885a0b126 100644 --- a/chapters/fr/chapter7/7.mdx +++ b/chapters/fr/chapter7/7.mdx @@ -28,8 +28,8 @@ Il est temps de s'intéresser à la réponse aux questions ! Cette tâche peut p Nous allons *finetuner* un modèle BERT sur le [jeu de données SQuAD](https://rajpurkar.github.io/SQuAD-explorer/), qui consiste en des questions posées par des *crowdworkers* sur un ensemble d'articles de Wikipedia. Cela nous donnera un modèle capable de calculer des prédictions comme celui-ci : - - + + Il s'agit d'une présentation du modèle qui a été entraîné à l'aide du code présenté dans cette section et qui a ensuité été téléchargé sur le *Hub*. Vous pouvez le trouver [ici](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) diff --git a/chapters/fr/chapter8/4.mdx b/chapters/fr/chapter8/4.mdx index f9a842c72..cae423fcd 100644 --- a/chapters/fr/chapter8/4.mdx +++ b/chapters/fr/chapter8/4.mdx @@ -5,8 +5,8 @@ Vous avez écrit un magnifique script pour entraîner ou *finetuner* un modèle sur une tâche donnée en suivant consciencieusement les conseils du [chapitre 7](/course/fr/chapter7). Mais lorsque vous lancez la commande `model.fit()`, quelque chose d'horrible se produit : vous obtenez une erreur 😱 ! Ou pire, tout semble aller bien et l'entraînement se déroule sans erreur mais le modèle résultant est mauvais. Dans cette section, nous allons vous montrer ce que vous pouvez faire pour déboguer ce genre de problèmes. diff --git a/chapters/fr/chapter9/2.mdx b/chapters/fr/chapter9/2.mdx index c2579c3f4..a25253a1a 100644 --- a/chapters/fr/chapter9/2.mdx +++ b/chapters/fr/chapter9/2.mdx @@ -37,7 +37,7 @@ Parcourons le code ci-dessus : Si vous exécutez ce code, l'interface ci-dessous apparaîtra automatiquement dans un *notebook* Jupyter/Colab ou dans un navigateur sur **[http://localhost:7860](http://localhost:7860/)** si vous l'exécutez à partir d'un script. - + Essayez d'utiliser cette interface maintenant avec votre propre nom ou une autre entrée ! @@ -60,7 +60,7 @@ textbox = gr.Textbox(label="Type your name here:", placeholder="John Doe", lines gr.Interface(fn=greet, inputs=textbox, outputs="text").launch() ``` - + Ici, nous avons créé une zone de texte d'entrée avec une étiquette, un espace réservé et un nombre de lignes défini. Vous pourriez faire la même chose pour la zone de texte de sortie, mais nous allons laisser cela pour le moment. @@ -112,6 +112,6 @@ gr.Interface(fn=predict, inputs="text", outputs="text").launch() C'est fait ! Vous pouvez maintenant utiliser cette interface pour générer du texte en utilisant le modèle GPT-2 comme indiqué ci-dessous 🤯. - + Continuez votre lecture du cours pour voir comment construire d'autres types de démos avec *Gradio* ! \ No newline at end of file diff --git a/chapters/fr/chapter9/3.mdx b/chapters/fr/chapter9/3.mdx index 31e19722e..bede014c7 100644 --- a/chapters/fr/chapter9/3.mdx +++ b/chapters/fr/chapter9/3.mdx @@ -59,7 +59,7 @@ gr.Interface(reverse_audio, mic, "audio").launch() Le code ci-dessus produira une interface comme celle qui suit (si votre navigateur ne vous demande pas l'autorisation pour accéder au microphone, ouvrez la démo dans un onglet séparé). - + Vous devriez maintenant être capable d'enregistrer votre voix et de vous entendre parler à l'envers. Effrayant 👻 ! @@ -104,7 +104,7 @@ gr.Interface( ).launch() ``` - + ### La méthode `launch()` @@ -159,7 +159,7 @@ gr.Interface( Si votre navigateur ne vous demande pas l'autorisation pour accéder au microphone, ouvrez la démo dans un onglet séparé. - + Voilà, c'est fait ! Vous pouvez maintenant utiliser cette interface pour transcrire de l'audio. Remarquez ici qu'en passant le paramètre `optional` à `True`, nous permettons à l'utilisateur de soit fournir un microphone ou un fichier audio (ou aucun des deux, mais cela retournera un message d'erreur). diff --git a/chapters/fr/chapter9/4.mdx b/chapters/fr/chapter9/4.mdx index 2065d3589..53e237e76 100644 --- a/chapters/fr/chapter9/4.mdx +++ b/chapters/fr/chapter9/4.mdx @@ -53,7 +53,7 @@ gr.Interface( En utilisant les options ci-dessus, nous obtenons une interface plus complète. Essayez l'interface ci-dessous : - + ### Partager votre démo avec des liens temporaires Maintenant que nous avons une démo fonctionnelle de notre modèle d'apprentissage automatique, apprenons à partager facilement un lien vers notre interface. @@ -135,7 +135,7 @@ interface = gr.Interface( interface.launch(share=True) ``` - + Remarquez le paramètre `live=True` dans `Interface`, qui signifie que la démo de sketchs fait une prédiction chaque fois que quelqu'un dessine sur le bloc (pas de bouton de soumission !). diff --git a/chapters/fr/chapter9/5.mdx b/chapters/fr/chapter9/5.mdx index b6126213b..228eee18f 100644 --- a/chapters/fr/chapter9/5.mdx +++ b/chapters/fr/chapter9/5.mdx @@ -70,6 +70,6 @@ gr.Interface.load( ).launch() ``` - + Maintenant que nous avons exploré quelques façons d'intégrer *Gradio* avec le *Hub*, jetons un coup d'oeil à certaines fonctionnalités avancées de la classe `Interface`. C'est le sujet de la prochaine section ! \ No newline at end of file diff --git a/chapters/fr/chapter9/6.mdx b/chapters/fr/chapter9/6.mdx index 69e645d1c..8c89be5e9 100644 --- a/chapters/fr/chapter9/6.mdx +++ b/chapters/fr/chapter9/6.mdx @@ -51,7 +51,7 @@ iface = gr.Interface( iface.launch() ``` - + Remarquez comment l'état du composant de sortie persiste entre les soumissions. Remarque : vous pouvez transmettre une valeur par défaut au paramètre state, qui est utilisée comme valeur initiale de l'état. @@ -133,6 +133,6 @@ gr.Interface( ).launch(auth=("admin", "pass1234")) ``` - + Ceci conclut notre plongée dans la classe `Interface` de *Gradio*. Comme nous l'avons vu, cette classe permet de créer facilement des démos d'apprentissage automatique en quelques lignes de code Python. Cependant, vous voudrez parfois personnaliser votre démo en changeant la mise en page ou en enchaînant plusieurs fonctions de prédiction. Ne serait-il pas agréable de pouvoir diviser l'interface en blocs personnalisables ? Heureusement, c'est possible ! C'est le sujet de la dernière section. diff --git a/chapters/fr/chapter9/7.mdx b/chapters/fr/chapter9/7.mdx index c199b347d..04a38803e 100644 --- a/chapters/fr/chapter9/7.mdx +++ b/chapters/fr/chapter9/7.mdx @@ -56,7 +56,7 @@ with demo: demo.launch() ``` - + Ce simple exemple ci-dessus introduit 4 concepts qui sous-tendent les *Blocks* : @@ -125,7 +125,7 @@ with demo: demo.launch() ``` - + Vous remarquerez que dans cet exemple, nous avons également créé un composant `Button` dans chaque onglet et avons assigné un événement de clic à chaque bouton qui est l'élément qui exécute réellement la fonction. @@ -164,7 +164,7 @@ with gr.Blocks() as demo: demo.launch() ``` - + ### Création de démos multi-étapes @@ -204,7 +204,7 @@ with demo: demo.launch() ``` - + ### Mise à jour des propriétés des composants @@ -235,6 +235,6 @@ with gr.Blocks() as block: block.launch() ``` - + Nous venons d'explorer tous les concepts de base des `Blocks` ! Tout comme avec `Interface`, vous pouvez créer des démos sympas qui peuvent être partagées en utilisant `share=True` dans la méthode `launch()` ou déployées sur [*Spaces*](https://huggingface.co/spaces). \ No newline at end of file diff --git a/chapters/fr/events/1.mdx b/chapters/fr/events/1.mdx index 1526edf81..9f9cbbcf2 100644 --- a/chapters/fr/events/1.mdx +++ b/chapters/fr/events/1.mdx @@ -1,49 +1,49 @@ -# Sessions en direct et ateliers - -Pour la parution des parties 1 et 2 du cours, nous avons organisé plusieurs sessions et ateliers de codage en direct. Vous trouverez ci-dessous les liens vers les enregistrements de ces sessions et ateliers. - -## Sessions de codage en direct - -Lors de la première session, Sylvain parcourt avec vous le chapitre 1 du cours, en l'expliquant étape par étape : - -
- -
- -Lors de la deuxième session, c'est au tour de Lewis de présenter le chapitre 2 : - -
- -
- -Parce que le chapitre 2 est tellement cool, Sylvain a également fourni une présentation de ce chapitre ! - -
- -
- -Pour le chapitre 3, Lewis revient pour vous guider dans le code : - -
- -
- -Enfin, Omar conclut les sessions en direct liées à la première partie du cours en abordant le chapitre 4 : - -
- -
- -## Ateliers - -Dans le premier atelier, Merve accueille Lewis pour discuter de la section 7 du chapitre 7 sur le [*question answering*]( https://huggingface.co/course/chapter7/7?fw=pt). - -
- -
- -Pour le deuxième atelier, Merve reçoit Leandro pour parler de la section 6 du chapitre 7 sur [entraîner un modèle de langage causal à partir de zéro]( https://huggingface.co/course/chapter7/6?fw=pt) avec une application avec [CodeParrot](https://huggingface.co/codeparrot). - -
- -
+# Sessions en direct et ateliers + +Pour la parution des parties 1 et 2 du cours, nous avons organisé plusieurs sessions et ateliers de codage en direct. Vous trouverez ci-dessous les liens vers les enregistrements de ces sessions et ateliers. + +## Sessions de codage en direct + +Lors de la première session, Sylvain parcourt avec vous le chapitre 1 du cours, en l'expliquant étape par étape : + +
+ +
+ +Lors de la deuxième session, c'est au tour de Lewis de présenter le chapitre 2 : + +
+ +
+ +Parce que le chapitre 2 est tellement cool, Sylvain a également fourni une présentation de ce chapitre ! + +
+ +
+ +Pour le chapitre 3, Lewis revient pour vous guider dans le code : + +
+ +
+ +Enfin, Omar conclut les sessions en direct liées à la première partie du cours en abordant le chapitre 4 : + +
+ +
+ +## Ateliers + +Dans le premier atelier, Merve accueille Lewis pour discuter de la section 7 du chapitre 7 sur le [*question answering*]( https://huggingface.co/course/chapter7/7?fw=pt). + +
+ +
+ +Pour le deuxième atelier, Merve reçoit Leandro pour parler de la section 6 du chapitre 7 sur [entraîner un modèle de langage causal à partir de zéro]( https://huggingface.co/course/chapter7/6?fw=pt) avec une application avec [CodeParrot](https://huggingface.co/codeparrot). + +
+ +
diff --git a/chapters/hi/chapter0/1.mdx b/chapters/hi/chapter0/1.mdx index 9377795e8..6a9490ee0 100644 --- a/chapters/hi/chapter0/1.mdx +++ b/chapters/hi/chapter0/1.mdx @@ -1,110 +1,110 @@ -# परिचय - -हगिंग फेस में आपका स्वागत है! यह परिचय कार्य वातावरण स्थापित करने में आपका मार्गदर्शन करेगा। यदि आप अभी पाठ्यक्रम शुरू कर रहे हैं, तो हम अनुशंसा करते हैं कि आप पहले [अध्याय 1](course/chapter1) पर एक नज़र डालें, फिर वापस आएं और अपना वातावरण सेट करें ताकि आप कोड को स्वयं आज़मा सकें। - -इस पाठ्यक्रम में हम जिन सभी पुस्तकालयों का उपयोग करेंगे, वे पायथन पैकेज के रूप में उपलब्ध हैं, इसलिए यहां हम आपको दिखाएंगे कि पायथन वातावरण कैसे स्थापित करें और विशिष्ट पुस्तकालयों को स्थापित करें जिनकी आपको आवश्यकता होगी। - -हम आपके कार्य परिवेश को स्थापित करने के दो तरीकों को कवर करेंगे, एक Colab नोटबुक या एक पायथन आभासी वातावरण का उपयोग करके। बेझिझक वह चुनें जो आपके साथ सबसे अधिक प्रतिध्वनित हो। शुरुआती लोगों के लिए, हम दृढ़ता से अनुशंसा करते हैं कि आप Colab नोटबुक का उपयोग करके शुरुआत करें। - -ध्यान दें कि हम विंडोज सिस्टम को कवर नहीं करेंगे। यदि आप Windows पर चल रहे हैं, तो हम अनुशंसा करते हैं कि Colab नोटबुक का उपयोग करने के साथ-साथ अनुसरण करें। यदि आप Linux वितरण या macOS का उपयोग कर रहे हैं, तो आप यहाँ वर्णित किसी भी दृष्टिकोण का उपयोग कर सकते हैं। - -अधिकांश पाठ्यक्रम आपके हगिंग फेस खाते पर निर्भर करता है। हम अभी एक बनाने की सलाह देते हैं: [एक खाता बनाएँ](https://huggingface.co/join)। - -## Google Colab नोटबुक का उपयोग करना - -Colab नोटबुक का उपयोग करना सबसे आसान संभव सेटअप है; अपने ब्राउज़र में एक नोटबुक बूट करें और सीधे कोडिंग पर जाएं! - -यदि आप Colab से परिचित नहीं हैं, तो हम अनुशंसा करते हैं कि आप [परिचय](https://colab.research.google.com/notebooks/intro.ipynb) का पालन करके शुरुआत करें। Colab आपको GPU या TPU जैसे कुछ त्वरित हार्डवेयर का उपयोग करने की अनुमति देता है, और यह छोटे कार्यभार के लिए मुफ़्त है। - -एक बार जब आप Colab में घूमने में सहज हो जाएं, तो एक नई नोटबुक बनाएं और स्थापना के साथ आरंभ करें: -
- एक खाली Colab नोटबुक -
- -अगला चरण उन पुस्तकालयों को स्थापित करना है जिनका हम इस पाठ्यक्रम में उपयोग करेंगे। हम स्थापना के लिए `pip` का उपयोग करेंगे, जो कि पायथन के लिए पैकेज मैनेजर है। नोटबुक्स में, आप `!` वर्ण से पहले सिस्टम कमांड चला सकते हैं, इसलिए आप ट्रान्सफ़ॉर्मर लाइब्रेरी को निम्नानुसार स्थापित कर सकते हैं: -अगला चरण उन पुस्तकालयों को स्थापित करना है जिनका हम इस पाठ्यक्रम में उपयोग करेंगे। हम स्थापना के लिए `pip` का उपयोग करेंगे, जो कि पायथन के लिए पैकेज मैनेजर है। नोटबुक्स में, आप `!` वर्ण से पहले सिस्टम कमांड चला सकते हैं, इसलिए आप 🤗 ट्रान्सफ़ॉर्मर लाइब्रेरी को निम्नानुसार स्थापित कर सकते हैं: - -``` -!pip install transformers -``` - -आप यह सुनिश्चित कर सकते हैं कि पैकेज आपके पायथन रनटाइम के भीतर आयात करके सही ढंग से स्थापित किया गया है: - -``` -import transformers -``` - -
- उपरोक्त दो आदेशों का परिणाम दिखाने वाला एक GIF: स्थापना और आयात -
- -यह 🤗 ट्रांसफॉर्मर का एक बहुत हल्का संस्करण स्थापित करता है। विशेष रूप से, कोई विशिष्ट मशीन लर्निंग फ्रेमवर्क (जैसे PyTorch या TensorFlow) स्थापित नहीं हैं। चूंकि हम पुस्तकालय की कई अलग-अलग विशेषताओं का उपयोग करेंगे, हम विकास संस्करण को स्थापित करने की सलाह देते हैं, जो किसी भी कल्पनाशील उपयोग के मामले के लिए सभी आवश्यक निर्भरताओं के साथ आता है: - -``` -!pip install transformers[sentencepiece] -``` - -इसमें थोड़ा समय लगेगा, लेकिन फिर आप बाकी पाठ्यक्रम के लिए तैयार हो जाएंगे। - -## पायथन आभासी वातावरण का उपयोग करना - -यदि आप एक पायथन आभासी वातावरण का उपयोग करना पसंद करते हैं, तो पहला कदम आपके सिस्टम पर पायथन को स्थापित करना है। हम आरंभ करने के लिए [इस गाइड](https://realpython.com/installing-python/) का पालन करने की सलाह देते हैं। - -एक बार जब आप पायथन स्थापित कर लेते हैं, तो आपको अपने टर्मिनल में पायथन आदेश चलाने में सक्षम होना चाहिए। अगले चरण पर आगे बढ़ने से पहले यह सुनिश्चित करने के लिए कि यह सही ढंग से स्थापित है, आप निम्न आदेश चलाकर प्रारंभ कर सकते हैं: `python --version`. यह आपके सिस्टम पर अब उपलब्ध पायथन संस्करण को प्रिंट करना चाहिए। - -अपने टर्मिनल में पायथन आदेश चलाते समय, जैसे `python --version` आदेश को चलाने वाले प्रोग्राम को अपने सिस्टम में "main" पायथन के रूप में सोचना चाहिए। हम अनुशंसा करते हैं कि इस मुख्य स्थापना को किसी भी पैकेज से मुक्त रखें, और इसका उपयोग प्रत्येक एप्लिकेशन के लिए अलग वातावरण बनाने के लिए करें, जिस पर आप काम कर रहे हैं - इस तरह, प्रत्येक एप्लिकेशन की अपनी निर्भरताएं और पैकेज होंगे, और आपको अन्य एप्लिकेशन के साथ संभावित संगतता समस्याओं के बारे में चिंता करने की आवश्यकता नहीं होगी। - -पायथन में यह [आभासी वातावरण](https://docs.python.org/3/tutorial/venv.html) के साथ किया जाता है, जो स्व-निहित निर्देशिका ट्री हैं जिनमें से प्रत्येक में एक विशेष पायथन संस्करण के साथ एक पायथन स्थापना होती है, जिसमें सभी पैकेजों के साथ एप्लिकेशन की आवश्यकता होती है। इस तरह के आभासी वातावरण का निर्माण कई अलग-अलग उपकरणों के साथ किया जा सकता है, लेकिन हम उस उद्देश्य के लिए आधिकारिक पायथन पैकेज का उपयोग करेंगे, जिसे कहा जाता है [`venv`](https://docs.python.org/3/library/venv.html#module-venv)। - -सबसे पहले, एक निर्देशिका बनाएं जिसमें आप अपने आवेदन में रहना चाहते हैं - उदाहरण के लिए, आप अपनी होम निर्देशिका के मूल में *transformers-course* नामक एक नई निर्देशिका बनाना चाहेंगे: - -``` -mkdir ~/transformers-course -cd ~/transformers-course -``` - -इस निर्देशिका के अंदर, पायथन `venv` मॉड्यूल का उपयोग करके एक आभासी वातावरण बनाएं: - -``` -python3 -m venv .env -``` - -अब आपके पास आपके अन्यथा खाली फ़ोल्डर में *.env* नामक एक निर्देशिका होनी चाहिए: - -``` -ls -a -``` - -```out -. .. .env -``` - -आप 'activate' और 'deactivate' स्क्रिप्ट के साथ अपने आभासी वातावरण में और बाहर कूद सकते हैं: - -``` -# Activate the virtual environment -source .env/bin/activate - -# Deactivate the virtual environment -source .env/bin/deactivate -``` - -आप यह सुनिश्चित कर सकते हैं कि `which python` आदेश चलाकर कौन सा पर्यावरण सक्रिय है: यदि यह आभासी वातावरण की ओर इशारा करता है, तो आपने इसे सफलतापूर्वक सक्रिय कर दिया है! - -``` -which python -``` - -```out -/home//transformers-course/.env/bin/python -``` - -## निर्भरता स्थापित करना - -Google Colab इंस्टेंस का उपयोग करने पर पिछले अनुभाग की तरह, अब आपको जारी रखने के लिए आवश्यक पैकेजों को स्थापित करने की आवश्यकता होगी। फिर से, आप `pip` पैकेज मैनेजर का उपयोग करके 🤗 ट्रांसफॉर्मर के विकास संस्करण को स्थापित कर सकते हैं: - -``` -pip install "transformers[sentencepiece]" -``` - -अब आप पूरी तरह से तैयार हैं! +# परिचय + +हगिंग फेस में आपका स्वागत है! यह परिचय कार्य वातावरण स्थापित करने में आपका मार्गदर्शन करेगा। यदि आप अभी पाठ्यक्रम शुरू कर रहे हैं, तो हम अनुशंसा करते हैं कि आप पहले [अध्याय 1](course/chapter1) पर एक नज़र डालें, फिर वापस आएं और अपना वातावरण सेट करें ताकि आप कोड को स्वयं आज़मा सकें। + +इस पाठ्यक्रम में हम जिन सभी पुस्तकालयों का उपयोग करेंगे, वे पायथन पैकेज के रूप में उपलब्ध हैं, इसलिए यहां हम आपको दिखाएंगे कि पायथन वातावरण कैसे स्थापित करें और विशिष्ट पुस्तकालयों को स्थापित करें जिनकी आपको आवश्यकता होगी। + +हम आपके कार्य परिवेश को स्थापित करने के दो तरीकों को कवर करेंगे, एक Colab नोटबुक या एक पायथन आभासी वातावरण का उपयोग करके। बेझिझक वह चुनें जो आपके साथ सबसे अधिक प्रतिध्वनित हो। शुरुआती लोगों के लिए, हम दृढ़ता से अनुशंसा करते हैं कि आप Colab नोटबुक का उपयोग करके शुरुआत करें। + +ध्यान दें कि हम विंडोज सिस्टम को कवर नहीं करेंगे। यदि आप Windows पर चल रहे हैं, तो हम अनुशंसा करते हैं कि Colab नोटबुक का उपयोग करने के साथ-साथ अनुसरण करें। यदि आप Linux वितरण या macOS का उपयोग कर रहे हैं, तो आप यहाँ वर्णित किसी भी दृष्टिकोण का उपयोग कर सकते हैं। + +अधिकांश पाठ्यक्रम आपके हगिंग फेस खाते पर निर्भर करता है। हम अभी एक बनाने की सलाह देते हैं: [एक खाता बनाएँ](https://huggingface.co/join)। + +## Google Colab नोटबुक का उपयोग करना + +Colab नोटबुक का उपयोग करना सबसे आसान संभव सेटअप है; अपने ब्राउज़र में एक नोटबुक बूट करें और सीधे कोडिंग पर जाएं! + +यदि आप Colab से परिचित नहीं हैं, तो हम अनुशंसा करते हैं कि आप [परिचय](https://colab.research.google.com/notebooks/intro.ipynb) का पालन करके शुरुआत करें। Colab आपको GPU या TPU जैसे कुछ त्वरित हार्डवेयर का उपयोग करने की अनुमति देता है, और यह छोटे कार्यभार के लिए मुफ़्त है। + +एक बार जब आप Colab में घूमने में सहज हो जाएं, तो एक नई नोटबुक बनाएं और स्थापना के साथ आरंभ करें: +
+ एक खाली Colab नोटबुक +
+ +अगला चरण उन पुस्तकालयों को स्थापित करना है जिनका हम इस पाठ्यक्रम में उपयोग करेंगे। हम स्थापना के लिए `pip` का उपयोग करेंगे, जो कि पायथन के लिए पैकेज मैनेजर है। नोटबुक्स में, आप `!` वर्ण से पहले सिस्टम कमांड चला सकते हैं, इसलिए आप ट्रान्सफ़ॉर्मर लाइब्रेरी को निम्नानुसार स्थापित कर सकते हैं: +अगला चरण उन पुस्तकालयों को स्थापित करना है जिनका हम इस पाठ्यक्रम में उपयोग करेंगे। हम स्थापना के लिए `pip` का उपयोग करेंगे, जो कि पायथन के लिए पैकेज मैनेजर है। नोटबुक्स में, आप `!` वर्ण से पहले सिस्टम कमांड चला सकते हैं, इसलिए आप 🤗 ट्रान्सफ़ॉर्मर लाइब्रेरी को निम्नानुसार स्थापित कर सकते हैं: + +``` +!pip install transformers +``` + +आप यह सुनिश्चित कर सकते हैं कि पैकेज आपके पायथन रनटाइम के भीतर आयात करके सही ढंग से स्थापित किया गया है: + +``` +import transformers +``` + +
+ उपरोक्त दो आदेशों का परिणाम दिखाने वाला एक GIF: स्थापना और आयात +
+ +यह 🤗 ट्रांसफॉर्मर का एक बहुत हल्का संस्करण स्थापित करता है। विशेष रूप से, कोई विशिष्ट मशीन लर्निंग फ्रेमवर्क (जैसे PyTorch या TensorFlow) स्थापित नहीं हैं। चूंकि हम पुस्तकालय की कई अलग-अलग विशेषताओं का उपयोग करेंगे, हम विकास संस्करण को स्थापित करने की सलाह देते हैं, जो किसी भी कल्पनाशील उपयोग के मामले के लिए सभी आवश्यक निर्भरताओं के साथ आता है: + +``` +!pip install transformers[sentencepiece] +``` + +इसमें थोड़ा समय लगेगा, लेकिन फिर आप बाकी पाठ्यक्रम के लिए तैयार हो जाएंगे। + +## पायथन आभासी वातावरण का उपयोग करना + +यदि आप एक पायथन आभासी वातावरण का उपयोग करना पसंद करते हैं, तो पहला कदम आपके सिस्टम पर पायथन को स्थापित करना है। हम आरंभ करने के लिए [इस गाइड](https://realpython.com/installing-python/) का पालन करने की सलाह देते हैं। + +एक बार जब आप पायथन स्थापित कर लेते हैं, तो आपको अपने टर्मिनल में पायथन आदेश चलाने में सक्षम होना चाहिए। अगले चरण पर आगे बढ़ने से पहले यह सुनिश्चित करने के लिए कि यह सही ढंग से स्थापित है, आप निम्न आदेश चलाकर प्रारंभ कर सकते हैं: `python --version`. यह आपके सिस्टम पर अब उपलब्ध पायथन संस्करण को प्रिंट करना चाहिए। + +अपने टर्मिनल में पायथन आदेश चलाते समय, जैसे `python --version` आदेश को चलाने वाले प्रोग्राम को अपने सिस्टम में "main" पायथन के रूप में सोचना चाहिए। हम अनुशंसा करते हैं कि इस मुख्य स्थापना को किसी भी पैकेज से मुक्त रखें, और इसका उपयोग प्रत्येक एप्लिकेशन के लिए अलग वातावरण बनाने के लिए करें, जिस पर आप काम कर रहे हैं - इस तरह, प्रत्येक एप्लिकेशन की अपनी निर्भरताएं और पैकेज होंगे, और आपको अन्य एप्लिकेशन के साथ संभावित संगतता समस्याओं के बारे में चिंता करने की आवश्यकता नहीं होगी। + +पायथन में यह [आभासी वातावरण](https://docs.python.org/3/tutorial/venv.html) के साथ किया जाता है, जो स्व-निहित निर्देशिका ट्री हैं जिनमें से प्रत्येक में एक विशेष पायथन संस्करण के साथ एक पायथन स्थापना होती है, जिसमें सभी पैकेजों के साथ एप्लिकेशन की आवश्यकता होती है। इस तरह के आभासी वातावरण का निर्माण कई अलग-अलग उपकरणों के साथ किया जा सकता है, लेकिन हम उस उद्देश्य के लिए आधिकारिक पायथन पैकेज का उपयोग करेंगे, जिसे कहा जाता है [`venv`](https://docs.python.org/3/library/venv.html#module-venv)। + +सबसे पहले, एक निर्देशिका बनाएं जिसमें आप अपने आवेदन में रहना चाहते हैं - उदाहरण के लिए, आप अपनी होम निर्देशिका के मूल में *transformers-course* नामक एक नई निर्देशिका बनाना चाहेंगे: + +``` +mkdir ~/transformers-course +cd ~/transformers-course +``` + +इस निर्देशिका के अंदर, पायथन `venv` मॉड्यूल का उपयोग करके एक आभासी वातावरण बनाएं: + +``` +python3 -m venv .env +``` + +अब आपके पास आपके अन्यथा खाली फ़ोल्डर में *.env* नामक एक निर्देशिका होनी चाहिए: + +``` +ls -a +``` + +```out +. .. .env +``` + +आप 'activate' और 'deactivate' स्क्रिप्ट के साथ अपने आभासी वातावरण में और बाहर कूद सकते हैं: + +``` +# Activate the virtual environment +source .env/bin/activate + +# Deactivate the virtual environment +source .env/bin/deactivate +``` + +आप यह सुनिश्चित कर सकते हैं कि `which python` आदेश चलाकर कौन सा पर्यावरण सक्रिय है: यदि यह आभासी वातावरण की ओर इशारा करता है, तो आपने इसे सफलतापूर्वक सक्रिय कर दिया है! + +``` +which python +``` + +```out +/home//transformers-course/.env/bin/python +``` + +## निर्भरता स्थापित करना + +Google Colab इंस्टेंस का उपयोग करने पर पिछले अनुभाग की तरह, अब आपको जारी रखने के लिए आवश्यक पैकेजों को स्थापित करने की आवश्यकता होगी। फिर से, आप `pip` पैकेज मैनेजर का उपयोग करके 🤗 ट्रांसफॉर्मर के विकास संस्करण को स्थापित कर सकते हैं: + +``` +pip install "transformers[sentencepiece]" +``` + +अब आप पूरी तरह से तैयार हैं! diff --git a/chapters/it/_toctree.yml b/chapters/it/_toctree.yml index 9780143ac..9f3d35666 100644 --- a/chapters/it/_toctree.yml +++ b/chapters/it/_toctree.yml @@ -114,4 +114,15 @@ title: Parte 2 completata! - local: chapter8/7 title: Quiz di fine capitolo - quiz: 8 \ No newline at end of file + quiz: 8 + +- title: 9. Creare e condividere demo + new: true + subtitle: Ho addestrato un modello, ma come posso esporlo? + sections: + - local: chapter9/1 + title: Introduzione a Gradio + - local: chapter9/2 + title: Creare la tua prima demo + - local: chapter9/3 + title: Capire la classe Interface diff --git a/chapters/it/chapter8/4.mdx b/chapters/it/chapter8/4.mdx index 3730721e3..d9336875b 100644 --- a/chapters/it/chapter8/4.mdx +++ b/chapters/it/chapter8/4.mdx @@ -5,8 +5,8 @@ Hai scritto un bello script per addestrare o affinare un modello su un determinato compito, seguendo scrupolosamente i consigli del [Capitolo 7](/course/chapter7). Ma quando lanci il comando `trainer.train()`, succede qualcosa di orribile: si ottiene un errore 😱! O peggio, tutto sembra andare bene e il training viene eseguito senza errori, ma il modello che ne risulta fa schifo. In questa sezione mostreremo cosa è possibile fare per eseguire il debug di questo tipo di problemi. diff --git a/chapters/it/chapter9/1.mdx b/chapters/it/chapter9/1.mdx new file mode 100644 index 000000000..b1b73ae4b --- /dev/null +++ b/chapters/it/chapter9/1.mdx @@ -0,0 +1,37 @@ +# Introduzione a Gradio + + + +In questo capitolo scopriremo come creare delle **demo interattive** per i tuoi modelli di machine learning. + +Perché costruire una demo o una GUI per il tuo modello di machine learning? Le demo permettono: + +- agli **sviluppatori di machine learning** di presentare facilmente il proprio lavoro ad un pubblico più ampio, tra cui i team non tecnici o i clienti +- ai **ricercatori** di riprodurre più facilmente i modelli di machine learning e i loro comportamenti +- ai **quality tester** o **utenti finali** per individuare e fare il debug dei difetti dei modelli con maggiore facilità +- a **utenti vari** per scoprire i bias degli algoritmi nei modelli + +Utilizzeremo la libreria Gradio per costruire le demo dei nostri modelli. Gradio consente di creare, personalizzare e condividere demo sul web per qualsiasi modello di machine learning, interamente in Python. + +Ecco alcuni esempi di demo di machine learning costruite con Gradio: + +* Un modello di **riconoscimento di disegni** che riceve uno schizzo di un disegno e restituisce il nome di ciò che pensa sia stato disegnato: + + + +* Un modello che estrae **risposte alle domande** che prende in considerazione un contesto e una domanda e produce una risposta e la sua probabilità (abbiamo discusso questo tipo di modello [nel capitolo 7](/course/chapter7/7)): + + + +* Un modello di **rimozione dello sfondo** che riceve un'immagine e la restituisce con lo sfondo rimosso: + + + +Questo capitolo è suddiviso in sezioni che comprendono sia _concetti_ che _applicazioni_. Dopo aver appreso il concetto in ogni sezione, lo applicherai per creare un particolare tipo di demo, dalla classificazione delle immagini al riconoscimento vocale. Al termine di questo capitolo, sarete in grado di creare queste demo (e molte altre!) con poche righe di Python. + + +👀 Dai un'occhiata a Hugging Face Spaces per vedere molti esempi recenti di demo di machine learning costruite dalla community! + diff --git a/chapters/it/chapter9/2.mdx b/chapters/it/chapter9/2.mdx new file mode 100644 index 000000000..8907fc06c --- /dev/null +++ b/chapters/it/chapter9/2.mdx @@ -0,0 +1,118 @@ +# Creare la tua prima demo + + + +Iniziamo installando Gradio! Essendo una libreria di Python, è sufficiente eseguire: + +`$ pip install gradio ` + +Puoi usare Gradio ovunque, dalla tua IDE Python preferita, ai Jupyter notebook o anche in Google Colab 🤯! +Quindi, installa Gradio in qualsiasi posto in cui usi Python! + +Iniziamo con un semplice esempio "Hello World" per prendere familiarità con la sintassi di Gradio: + +```py +import gradio as gr + + +def greet(name): + return "Hello " + name + + +demo = gr.Interface(fn=greet, inputs="text", outputs="text") + +demo.launch() +``` + +Analizziamo il codice qui sopra: + +- Per prima cosa, definiamo una funzione chiamata `greet()`. In questo caso, si tratta di una semplice funzione che aggiunge "Hello" prima di un nome (_name_), ma questa può essere in generale *qualsiasi* funzione in Python. Ad esempio, nelle applicazioni di machine learning, questa funzione *chiamerebbe un modello per fare una previsione* su un input e restituirebbe l'output. +- Creaiamo puoi una `Interface` (_Interfaccia_) di Gradio con tre argomenti, `fn`, `inputs`, e `outputs`. Questi argomenti definiscono la funzione di predizione e il _tipo_ di componenti di ingresso e di uscita che desideriamo. Nel nostro caso, entrambi i componenti sono semplici caselle di testo. +- Chiamiamo poi il metodo `launch()` sul `Interface` creata. + +Se si esegue questo codice, l'interfaccia qui sotto apparirà automaticamente all'interno di un Jupyter/Colab notebook, o comparirà in un browser **[http://localhost:7860](http://localhost:7860/)** se lanciato in uno script. + + + +Prova subito a utilizzare questa GUI con il tuo nome o con un altro input! + +Si noterà che in questa GUI, Gradio ha riconosciuto automaticamente il nome del parametro di input (`name`) +e lo applica come etichetta in cima alla casella di testo. E se si volesse cambiarlo? +O se si volesse personalizzare la casella di testo in qualche altro modo? In questo caso, si può +istanziare una classe che rappresenti il componente in input. + +Si osservi l'esempio seguente: + +```py +import gradio as gr + + +def greet(name): + return "Hello " + name + + +# We instantiate the Textbox class +textbox = gr.Textbox(label="Type your name here:", placeholder="John Doe", lines=2) + +gr.Interface(fn=greet, inputs=textbox, outputs="text").launch() +``` + + + +Qui abbiamo creato una casella di testo di input con un'etichetta (`label`), un segnaposto (`placeholder`) e un numero di righe stabilito (`lines`). +Si potrebbe fare lo stesso per la casella di testo in output, ma per ora ci fermiamo qui. + +Abbiamo visto che con poche righe di codice, Gradio consente di creare una semplice interfaccia intorno a qualsiasi funzione +con qualsiasi tipo di input o output. In questa sezione abbiamo iniziato con una +semplice casella di testo, ma nelle prossime sezioni tratteremo altri tipi di input e output. Vediamo ora di inserire un po' di NLP in un'applicazione Gradio. + + +## 🤖 Includere le predizioni del modello + +Costruiamo ora una semplice interfaccia che consenta di dimostrare come funziona un modello di **generazione del testo** come GPT-2. + +Caricheremo il nostro modello usando la funzione `pipeline()` di 🤗 Transformers. +Se hai bisogno di un rapido ripasso, puoi tornare a [quella sezione nel Capitolo 1](/course/chapter1/3#text-generation). + +Per prima cosa, definiamo una funzione di predizione che riceve un prompt di testo e restituisce il testo completato: + +```py +from transformers import pipeline + +model = pipeline("text-generation") + + +def predict(prompt): + completion = model(prompt)[0]["generated_text"] + return completion +``` + +Questa funzione completa le richieste fornite dall'utente e puoi eseguirla con qualche tuo input per vedere come funziona. Ecco un esempio (potresti ottenere un risultato diverso): + +``` +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. +``` + +Ora che abbiamo una funzione per generare previsioni, possiamo creare e lanciare una `Interface` nello stesso modo in cui abbiamo fatto prima: + +```py +import gradio as gr + +gr.Interface(fn=predict, inputs="text", outputs="text").launch() +``` + + +Ecco fatto! Ora è possibile utilizzare questa interfaccia per generare testo utilizzando il modello GPT-2 come mostrato qui sotto 🤯. + + + +Continua a leggere per scoprire come costruire altri tipi di demo con Gradio! \ No newline at end of file diff --git a/chapters/it/chapter9/3.mdx b/chapters/it/chapter9/3.mdx new file mode 100644 index 000000000..9f4dbc7a4 --- /dev/null +++ b/chapters/it/chapter9/3.mdx @@ -0,0 +1,186 @@ +# Capire la classe Interface + + + +In questa sezione, daremo un'occhiata più da vicino alla classe `Interface` e scopriremo i +parametri principali che si usano per crearne una. + +## Come creare una Interface + +Si può notare che la classe `Interface` (_interfaccia_) ha 3 parametri necessari: + +`Interface(fn, inputs, outputs, ...)` + +Questi parametri sono: + + - `fn`: la funzione per le predizione chi viene utilizzata dall'interfaccia di Gradio. Questa funzione può accettare uno o più parametri e restituire uno o più valori + - `inputs`: il/i tipo/i dei componenti in input. Gradio fornisce molti componenti predefiniti, come `"image"`(_immagine_) o `"mic"`(_microfono_). + - `outputs`: il/i tipo/i dei componenti in output. Anche in questo caso, Gradio fornisce molti componenti predefiniti, come `"image"` o `"label"`. + +Per un elenco completo dei componenti, [consultare la documentazione di Gradio](https://gradio.app/docs). Ogni componente predefinito può essere personalizzato istanziando la classe corrispondente al componente. + +Ad esempio, come abbiamo visto nella [sezione precedente](/course/chapter9/2), +invece di passare `"textbox"` al parametro `inputs`, si può passare un componente `Textbox(lines=7, label="Prompt")` per creare una casella di testo con 7 righe e un'etichetta. + +Diamo un'occhiata a un altro esempio, questa volta con un componente `Audio`. + +## Un semplice esempio con l'audio + +Come detto in precedenza, Gradio fornisce molti input e output differenti. +Costruiamo perciò una `Interface` che funziona con l'audio. + +In questo esempio, svilupperemo una funzione da audio ad audio che prende un +file audio e semplicemente lo inverte. + +Per l'input utilizzeremo il componente `Audio`. Quando si usa il componente `Audio`, +si può specificare se si vuole che la `source` (_sorgente_) dell'audio sia un file +caricato dall'utente o un microfono con cui l'utente può registrare la propria voce. In questo caso, +impostiamo `"microphone"`. Per divertimento, aggiungeremo un'etichetta al nostro `Audio` che dice +"Speak here..." (_"Parla qui..."_). + +Inoltre, vorremmo ricevere l'audio come un numpy array, in modo da poterlo facilmente +"invertire". Impostiamo quindi il `"type"` come `"numpy"`, che passa i dati in input +come una tupla di (`sample_rate`, `data`) alla nostra funzione. + +Utilizzeremo anche il componente di output `Audio`, il quale può convertire automaticamente +una tupla formata da una frequenza di campionamento e un numpy array di dati in un file audio riproducibile. +In questo caso, non abbiamo bisogno di fare alcuna personalizzazione, quindi useremo la stringa +`"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() +``` + +Il codice precedente produrrà un'interfaccia come quella qui sotto (se il tuo browser non +chiede il premesso per usare il microfono, apri il demo in una tab diversa.) + + + +A questo punto potresti registrare la tua voce e di sentirti parlare al contrario - spaventoso 👻! + +## Lavorare con più input e output + +Supponiamo di avere una funzione più complicata, con più input e output. +Nell'esempio seguente, abbiamo una funzione che richiede un elenco a tendina, il valore di uno slider e un numero, +e restituisce il campione audio di una nota musicale. + +Osserva come si passa un elenco di componenti di input e di output, +e vedi se riesci a seguire quello che succede. + +La questione fondamentale è che quando si passa: +* un elenco di componenti di input, ogni componente corrisponde in ordine a un parametro. +* un elenco di componenti di output, ogni componente corrisponde a un valore restituito. + +Lo snippet di codice qui sotto mostra come tre componenti di input si abbinano ai tre argomenti della funzione `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() +``` + + + + +### Il metodo `launch()` + +Per ora, abbiamo utilizzato il metodo `launch()` per avviare l'interfaccia, ma +non abbiamo discusso realmente cosa fa. + +Di default, il metodo `launch()` avvia la demo in un web server che +che viene eseguito in locale. Se si esegue il codice in un Jupyter o Colab notebook, +Gradio incorporerà l'interfaccia grafica della demo nel notebook, così da poterla usare facilmente. + +È possibile modificare il comportamento di `launch()` attraverso diversi parametri: + + - `inline` - per visualizzare l'interfaccia _inline_ sui notebook di Python. + - `inbrowser` - per avviare automaticamente l'interfaccia in una nuova scheda del browser di default. + - `share` - per create un link pubblico per l'interfaccia da condividere dal proprio computer. Un po' come un link di Google Drive! + +Il parametro `share` sarà trattato in modo molto più dettagliato nella prossima sezione! + +## ✏️ Mettiamolo in pratica! + +Costruiamo un'interfaccia che permetta di provare un modello di **riconoscimento vocale**. +Per renderlo interessante, accetteremo un input *qualisiasi* tra un microfono o un file caricato. + +Come al solito, caricheremo il nostro modello di riconoscimento vocale usando la funzione `pipeline()` da 🤗 Transformers. +Se si ha bisogno di un ripasso veloce, si può tornare a [quella sezione nel Capitolo 1](/course/chapter1/3). Quindi, implementeremo una funzione `transcribe_audio()` che elabora l'audio e restituisce la sua trascrizione. Infine, avvolgeremo questa funzione in una `Interface` con i componenti `Audio` per gli input e solo testo per l'output. Il codice completo per questa applicazione è il seguente: + +```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() +``` + +Se il tuo browser non ti chiede i permessi per il microfono, apri la demo in una scheda separata. + + + + +Ecco fatto! Ora è possibile utilizzare questa interfaccia per trascrivere l'audio. Si osservi che +passando il parametro `optional` come `True`, si permette all'utente di +fornire o un microfono o un file audio (o nessuno dei due, ma questo restituirà un messaggio di errore). + +Continua a leggere per scoprire come condividere la tua interfaccia con gli altri! \ No newline at end of file diff --git a/chapters/ja/chapter7/2.mdx b/chapters/ja/chapter7/2.mdx index 5ad6ba398..7d235c0b7 100644 --- a/chapters/ja/chapter7/2.mdx +++ b/chapters/ja/chapter7/2.mdx @@ -32,7 +32,7 @@ もちろん、トークン分類問題には他にも多くの問題があり、これらは代表的な例に過ぎません。このセクションでは、NERタスクでモデル(BERT)を微調整し、以下のような予測計算ができるようにします。 - + One-hot encoded labels for question answering. diff --git a/chapters/ja/chapter7/3.mdx b/chapters/ja/chapter7/3.mdx index afdb30047..e36e0d6cf 100644 --- a/chapters/ja/chapter7/3.mdx +++ b/chapters/ja/chapter7/3.mdx @@ -36,7 +36,7 @@ Transformerモデルを含む多くのNLPアプリケーションでは、ハギ このセクションの終わりには、以下のような文章を自動補完できる[マスク言語モデル](https://huggingface.co/huggingface-course/distilbert-base-uncased-finetuned-imdb?text=This+is+a+great+%5BMASK%5D.)がHub上にできていることでしょう。 - + それでは始めましょう! diff --git a/chapters/ja/chapter7/4.mdx b/chapters/ja/chapter7/4.mdx index 11e5fb724..509fe100f 100644 --- a/chapters/ja/chapter7/4.mdx +++ b/chapters/ja/chapter7/4.mdx @@ -38,7 +38,7 @@ これが終われば、以下のような予測が可能なモデルが完成します。 - + One-hot encoded labels for question answering. diff --git a/chapters/ja/chapter7/5.mdx b/chapters/ja/chapter7/5.mdx index 8aa12a0b4..1c6d9e224 100644 --- a/chapters/ja/chapter7/5.mdx +++ b/chapters/ja/chapter7/5.mdx @@ -28,7 +28,7 @@ [ハギングフェイス ハブ](https://huggingface.co/models?pipeline_tag=summarization&sort=downloads)には、要約用に微調整されたさまざまなモデルがすでに存在しますが、これらのほとんどは英語のドキュメントにのみ適しています。 したがって、このセクションにひねりを加えるために、英語とスペイン語のバイリンガルモデルをトレーニングします。 このセクションの終わりまでに、ここに示すようなカスタマーレビューを要約できる[モデル](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es)ができあがります。 - + これから説明するように、これらの要約は、顧客が製品レビュー投稿時につけたタイトル文を使って学習されているため、簡潔です。 このタスクに適した多言語コーパスをまとめることから始めましょう。 diff --git a/chapters/ja/chapter7/6.mdx b/chapters/ja/chapter7/6.mdx index ded614470..34d0a71b6 100644 --- a/chapters/ja/chapter7/6.mdx +++ b/chapters/ja/chapter7/6.mdx @@ -30,7 +30,7 @@ [第6章](/course/ja/chapter6)では、Pythonソースコードを処理するための効率的なトークナイザーを作成しましたが、モデルを事前学習するためには、やはり大規模なデータセットが必要です。ここでは、GitHub リポジトリから得た Python コードのコーパスにトークナイザを適用します。そして、`Trainer` API と 🤗 Accelerate を使ってモデルを学習します。さあ、始めましょう - + これは実際に、このセクションで示したコードを使って学習し、ハブにアップロードしたモデルを紹介しているものです。[こちら](https://huggingface.co/huggingface-course/codeparrot-ds?text=plt.imshow%28)をご覧ください。なお、テキスト生成の際にランダム化が行われているので、おそらく少し異なる結果が得られると思います。 diff --git a/chapters/ja/chapter7/7.mdx b/chapters/ja/chapter7/7.mdx index a8aa16f24..4a1aaa4b6 100644 --- a/chapters/ja/chapter7/7.mdx +++ b/chapters/ja/chapter7/7.mdx @@ -28,7 +28,7 @@ 私達は、Wikipediaの記事に対してクラウドワーカーによって作成された質問からなる[SQuADデータセット](https://rajpurkar.github.io/SQuAD-explorer/)のBERTモデルを微調整する予定です。これにより、以下のような予測を実行できるモデルができるでしょう。 - + これは実際にこのセクションで示したコードを使って学習し、ハブにアップロードしたモデルを紹介しているものです。 貴方は[ここ](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)でモデルを見つけて、予測を再確認することができます。 diff --git a/chapters/ko/_toctree.yml b/chapters/ko/_toctree.yml index fe4e110ef..bde0efee5 100644 --- a/chapters/ko/_toctree.yml +++ b/chapters/ko/_toctree.yml @@ -26,4 +26,8 @@ - local: chapter1/10 title: 단원 마무리 퀴즈 quiz: 1 - \ No newline at end of file + +- title: 2. 🤗 Transformers 사용하기 + sections: + - local: chapter2/1 + title: 단원 소개 diff --git a/chapters/ko/chapter2/1.mdx b/chapters/ko/chapter2/1.mdx new file mode 100644 index 000000000..1d28b3a27 --- /dev/null +++ b/chapters/ko/chapter2/1.mdx @@ -0,0 +1,24 @@ +# 단원 소개[[introduction]] + + + +[제1단원](/course/chapter1)에서 보았듯이, 트랜스포머 모델은 대부분 매우 큽니다. 수백만에서 *수백억*개의 파라미터를 가진 모델을 훈련시키고 배포하는 것은 만만치 않은데다가, 하루가 멀다하고 자체적으로 구현된 새로운 모델이 출시되어서, 모두 적용해보려고 한다면 쉽지는 않을 거예요. + +🤗 Transformers 라이브러리는 이 문제를 해결하기 위해 만들어졌습니다. Transformer 모델을 가져오고, 훈련시킨 후 저장할 수 있는 단일 API를 제공하는 것이 목표예요. 라이브러리의 주요 기능은 다음과 같습니다. + +- **사용 편의성**: 추론하기 위해 최첨단 NLP 모델을 다운로드한 다음 적재시켜 사용하고 싶다면, 단 2줄의 코드만으로 할 수 있어요. +- **유연성**: 기초적으로 보면 모든 모델은 단순한 PyTorch `nn.module` 또는 TensorFlow `tf.keras.Model` 클래스입니다. 각 머신러닝(ML) 프레임워크의 여타 다른 모델이나 마찬가지로 처리할 수 있다는 뜻이에요. +- **단순성**: 라이브러리 위에 추상화를 거의 하지 않았어요. "모든 것을 파일 하나에"가 핵심 개념입니다. 모델의 순전파(forward propagation) 부분이 파일 한 개에 모두 정의되어 있어서, 코드 자체를 이해하고 해킹할 수도 있어요. + +마지막 기능은 여타 ML 라이브러리들과는 다른 🤗 Transformers만의 차별점입니다. 모델은 파일 간에 공유되는 모듈로 만들어지지 않고, 모델마다 자체적인 레이어를 쌓습니다. 이렇게 하면 모델을 더 쉽게 보고 이해할 수 있으면서도, 다른 모델과는 상관없이 원하는 모델에서 마음껏 실험해볼 수 있습니다. + +이 단원은 모델과 토크나이저로 [제1단원](/course/chapter1)에서 소개된 `pipeline()` 함수를 처음부터 끝까지 만들어보는 것으로 시작합니다. 만들고나면 모델 API를 더 깊게 탐구해봅니다. model과 configuration 클래스를 알아보고, 모델을 적재하는 방법과 수치를 입력으로 제공해서 예측이 출력되는 처리 과정을 보여드리겠습니다. + +그런 다음 `pipeline()` 함수의 중요한 구성요소인 tokenizer API를 살펴보겠습니다. tokenizer는 처리의 첫 번째 단계인 텍스트를 신경망의 수치 입력으로 바꾸는 부분과 필요할 때 다시 텍스트로 바꾸는 마지막 단계, 즉 양끝을 다룹니다. 마지막으로 여러 문장을 묶어서 모델에게 제공하는 방법을 알아보고, 기존 `tokenizer()` 함수를 자세히 살펴봄으로써 마무리짓겠습니다. + + +⚠️ Model Hub와 🤗 Transformers에서 사용할 수 있는 모든 기능을 활용하려면 계정을 만드는 게 좋습니다. + diff --git a/chapters/vi/chapter7/2.mdx b/chapters/vi/chapter7/2.mdx index 619d3e932..5167b026f 100644 --- a/chapters/vi/chapter7/2.mdx +++ b/chapters/vi/chapter7/2.mdx @@ -51,7 +51,7 @@ Tất nhiên, có nhiều loại vấn đề phân loại token khác; đó chỉ là một vài ví dụ tiêu biểu. Trong phần này, chúng ta sẽ tinh chỉnh một mô hình (BERT) trên một tác vụ NER, sau đó sẽ có thể tính toán các dự đoán như sau: + Cùng đi sâu vào thôi! diff --git a/chapters/vi/chapter7/4.mdx b/chapters/vi/chapter7/4.mdx index c9a25dd1d..2456a58f6 100644 --- a/chapters/vi/chapter7/4.mdx +++ b/chapters/vi/chapter7/4.mdx @@ -35,7 +35,7 @@ Trong phần này, chúng ta sẽ tinh chỉnh mô hình Marian được huấn Sau khi hoàn thành, chúng ta sẽ có một mô hình có thể đưa ra các dự đoán như sau: - + One-hot encoded labels for question answering. diff --git a/chapters/vi/chapter7/5.mdx b/chapters/vi/chapter7/5.mdx index c72edb8d0..518c5217f 100644 --- a/chapters/vi/chapter7/5.mdx +++ b/chapters/vi/chapter7/5.mdx @@ -28,7 +28,7 @@ Trong phần này, chúng ta sẽ xem xét cách các mô hình Transformer có Mặc dù đã tồn tại nhiều mô hình được tinh chỉnh khác nhau để tóm tắt trên [Hugging Face Hub](https://huggingface.co/models?pipeline_tag=summarization&sort=downloads), hầu hết tất cả các mô hình này chỉ phù hợp với các tài liệu tiếng Anh. Vì vậy, để tạo thêm một điểm nhấn trong phần này, chúng tôi sẽ huấn luyện một mô hình song ngữ cho tiếng Anh và tiếng Tây Ban Nha. Đến cuối phần này, bạn sẽ có một [mô hình](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es) có thể tóm tắt các đánh giá của khách hàng như được hiển thị ở đây: - + Như chúng ta sẽ thấy, những bản tóm tắt này ngắn gọn vì chúng được học từ các tiêu đề mà khách hàng cung cấp trong các bài đánh giá sản phẩm của họ. Hãy bắt đầu bằng cách tập hợp một kho ngữ liệu song ngữ phù hợp cho tác vụ này. diff --git a/chapters/vi/chapter7/6.mdx b/chapters/vi/chapter7/6.mdx index 0dac41c13..0fc13dfd6 100644 --- a/chapters/vi/chapter7/6.mdx +++ b/chapters/vi/chapter7/6.mdx @@ -49,7 +49,7 @@ Trong phần này, chúng ta sẽ xây dựng một phiên bản thu nhỏ của Trong [Chương 6](/course/chapter6), chúng ta đã tạo một trình tokenize hiệu quả để xử lý mã nguồn Python, nhưng những gì chúng ta vẫn cần là một tập dữ liệu quy mô lớn để huấn luyện trước một mô hình. Ở đây, chúng ta sẽ áp dụng tokenizer cho một kho lưu trữ mã Python có nguồn gốc từ kho lưu trữ GitHub. Sau đó, chúng ta sẽ sử dụng API `Trainer` và 🤗 Accelerate để huấn luyện mô hình. Chúng ta hãy đi đến đó! + Đây thực sự cách mô hình đã được huấn luyện và tải lên Hub bằng cách sử dụng mã được hiển thị trong phần này. Bạn có thể tìm thấy nó và kiểm tra các dự đoạn [tại đây](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). diff --git a/chapters/vi/chapter8/4.mdx b/chapters/vi/chapter8/4.mdx index d6e547090..fd7a55996 100644 --- a/chapters/vi/chapter8/4.mdx +++ b/chapters/vi/chapter8/4.mdx @@ -5,8 +5,8 @@ Bạn đã viết một kịch bản tuyệt đẹp để huấn luyện hoặc tinh chỉnh một mô hình trong một tác vụ nhất định, tuân thủ một cách nghiêm túc lời khuyên từ [Chương 7](/course/chapter7). Nhưng khi bạn khởi chạy lệnh `trainr.train()`, một điều kinh khủng xảy ra: bạn gặp lỗi 😱! Hoặc tệ hơn, mọi thứ dường như ổn và quá trình huấn luyện chạy mà không có lỗi, nhưng mô hình kết quả là tồi tệ. Trong phần này, chúng tôi sẽ chỉ cho bạn những gì bạn có thể làm để gỡ lỗi các loại vấn đề này. diff --git a/chapters/vi/chapter9/1.mdx b/chapters/vi/chapter9/1.mdx index cf73b07a4..4bc7ab1a8 100644 --- a/chapters/vi/chapter9/1.mdx +++ b/chapters/vi/chapter9/1.mdx @@ -15,15 +15,15 @@ Dưới đây là một số ví dụ về demo học máy được xây dựng * Một mô hình **nhận dạng phác thảo** nhận bản phác thảo và xuất ra các nhãn của những gì nó cho là đang được vẽ: - + * Mô hình **hỏi đáp** khai thác lấy trong một đoạn ngữ cảnh và một câu hỏi và đưa ra một câu trả lời và điểm xác suất (chúng ta đã thảo luận về loại mô hình này [trong Chương 7](/course/chapter7/7)): - + * Một mô hình **xóa nền** nhận vào một hình ảnh và xuất ra hình ảnh với nền đã bị xóa: - + Chương này được chia thành các phần bao gồm cả _khái niệm_ và _ứng dụng_. Sau khi bạn tìm hiểu khái niệm trong mỗi phần, bạn sẽ áp dụng nó để xây dựng một loại bản demo cụ thể, từ phân loại hình ảnh đến nhận dạng giọng nói. Vào thời điểm bạn hoàn thành chương này, bạn sẽ có thể xây dựng các bản demo này (và nhiều hơn nữa!) Chỉ trong một vài dòng mã Python. diff --git a/chapters/vi/chapter9/2.mdx b/chapters/vi/chapter9/2.mdx index f70a32d2c..e71fd448d 100644 --- a/chapters/vi/chapter9/2.mdx +++ b/chapters/vi/chapter9/2.mdx @@ -37,7 +37,7 @@ Hãy xem qua đoạn mã trên: Nếu bạn chạy đoạn mã này, giao diện bên dưới sẽ tự động xuất hiện trong notebook Jupyter/Colab hoặc bật trong trình duyệt trên **[http://localhost:7860](http://localhost:7860/)** nếu đang chạy từ một tập lệnh. - + Hãy thử sử dụng GUI này ngay bây giờ với tên của chính bạn hoặc một số đầu vào khác! @@ -57,7 +57,7 @@ textbox = gr.Textbox(label="Type your name here:", placeholder="John Doe", lines gr.Interface(fn=greet, inputs=textbox, outputs="text").launch() ``` - + Ở đây, chúng ta đã tạo một hộp văn bản đầu vào với nhãn, trình giữ chỗ và một số dòng. Bạn có thể làm tương tự đối với hộp văn bản đầu ra, nhưng chúng ta sẽ để lại điều đó ngay bây giờ. @@ -104,6 +104,6 @@ gr.Interface(fn=predict, inputs="text", outputs="text").launch() Nó đó! Bây giờ bạn có thể sử dụng giao diện này để tạo văn bản bằng mô hình GPT-2 như hình bên dưới 🤯. - + Hãy tiếp tục đọc để biết cách tạo các loại demo khác với Gradio! diff --git a/chapters/vi/chapter9/3.mdx b/chapters/vi/chapter9/3.mdx index 1659fd788..79df19b79 100644 --- a/chapters/vi/chapter9/3.mdx +++ b/chapters/vi/chapter9/3.mdx @@ -1,164 +1,164 @@ -# Hiểu lớp Interface - - - -Trong phần này, chúng ta sẽ xem xét kỹ hơn về lớp `Interface` và hiểu các tham số chính được sử dụng để tạo ra nó. - -## Cách tạo một Interface - -Bạn sẽ nhận thấy rằng lớp `Interface` có 3 tham số bắt buộc: - -`Interface(fn, inputs, outputs, ...)` - -Các tham số này là: - - - `fn`: hàm dự đoán được bao bọc bởi giao diện Gradio. Hàm này có thể nhận một hoặc nhiều tham số và trả về một hoặc nhiều giá trị - - `inputs`: (các) loại thành phần đầu vào. Gradio cung cấp nhiều thành phần được tạo sẵn như`"image"` hay `"mic"`. - - `outputs`: (các) loại thành phần đầu ra. Một lần nữa, Gradio cung cấp nhiều thành phần được tạo sẵn, ví dụ: `"image"` hay `"label"`. - -Để có danh sách đầy đủ các thành phần, [xem tài liệu Gradio](https://gradio.app/docs). Mỗi thành phần được tạo sẵn có thể được tùy chỉnh bằng cách khởi tạo lớp tương ứng với thành phần. - -Ví dụ: như chúng ta đã thấy trong [phần trước](/course/chapter9/2), thay vì truyền tham số `input` vào trong `"textbox"`, bạn có thể truyền vào `Textbox(lines=7, label="Prompt")` để tạo một hộp văn bản có 7 dòng và một nhãn. - -Hãy xem một ví dụ khác, lần này với thành phần `Audio`. - -## Một ví dụ đơn giản với âm thanh - -Như đã đề cập trước đó, Gradio cung cấp nhiều đầu vào và đầu ra khác nhau. -Vì vậy, hãy xây dựng một `Interface` hoạt động với âm thanh. - -Trong ví dụ này, chúng tôi sẽ xây dựng một hàm chuyển đổi âm thanh sang âm thanh mà nhận tập tin âm thanh và chỉ cần đảo ngược nó. - -Chúng ta sẽ sử dụng thành phần `Audio` cho đầu vào. Khi sử dụng thành phần `Audio`, bạn có thể chỉ định xem bạn có muốn `source` của âm thanh là một tệp mà người dùng -tải lên hoặc micrô mà người dùng ghi lại giọng nói của họ. Trong trường hợp này, hãy đặt nó thành `"microphone"`. Chỉ cho vui thôi, chúng ta sẽ thêm một nhãn vào phần `Audio` của mình có nội dung "Speak here...", nghĩa là "Nói ở đây ...". - -Ngoài ra, chúng ta muốn nhận âm thanh dưới dạng mảng numpy để ta có thể dễ dàng "đảo ngược" nó lại. Vì vậy, chúng ta sẽ đặt `"type"` là `"numpy"`, chuyển đầu vào -dữ liệu dưới dạng một bộ (`sample_rate`, `data`) trong hàm của chúng ta. - -Chúng ta cũng sẽ sử dụng thành phần đầu ra `Audio` có thể tự động hiển thị một bộ tuple với tốc độ mẫu và mảng dữ liệu phức tạp dưới dạng tệp âm thanh có thể phát. Trong trường hợp này, chúng ta không cần thực hiện bất kỳ tùy chỉnh nào, vì vậy cta sẽ sử dụng chuỗi phím tắt `"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() -``` - -Đoạn mã trên sẽ tạo ra một giao diện giống như bên dưới (nếu trình duyệt của bạn không yêu cầu bạn cấp quyền đối với micrô, mở bản demo sang một tab khác.) - - - -Bây giờ bạn có thể ghi lại giọng nói của mình và nghe thấy chính mình đang nói ngược lại - thật ma quái 👻! - -## Xử lý nhiều đầu vào và đầu ra - -Giả sử chúng ta có một hàm phức tạp hơn, với nhiều đầu vào và đầu ra. Trong ví dụ dưới đây, chúng ta có một hàm lấy chỉ mục thả xuống, giá trị thanh trượt và số, và trả về một mẫu âm thanh của một giai điệu âm nhạc. - -Hãy xem cách chúng ta chuyển danh sách các thành phần đầu vào và đầu ra, và xem liệu bạn có thể theo dõi những gì đang xảy ra không. - -Chìa khóa ở đây là khi bạn truyền vào: -* danh sách các thành phần đầu vào, mỗi thành phần tương ứng với một tham số theo thứ tự. -* danh sách các thành phần đầu ra, mỗi thành phần tương ứng với một giá trị trả về. - -Đoạn mã bên dưới cho thấy cách ba thành phần đầu vào xếp hàng với ba tham số của hàm `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() -``` - - - -### Phương thức `launch()` - -Cho đến nay, chúng tôi đã sử dụng phương thức `launch()` để khởi chạy giao diện, nhưng chúng ta chưa thực sự thảo luận về những gì nó làm. - -Theo mặc định, phương thức `launch()` sẽ khởi chạy bản demo trong một máy chủ web đang chạy cục bộ. Nếu bạn đang chạy mã của mình trong notebook Jupyter hoặc Colab, thì Gradio sẽ nhúng GUI demo vào notebook để bạn có thể dễ dàng sử dụng. - -Bạn có thể tùy chỉnh hành vi của `launch()` thông qua các tham số khác nhau: - - - `inline` - có hiển thị giao diện nội tuyến trên notebook Python hay không. - - `inbrowser` - có tự động khởi chạy giao diện trong tab mới trên trình duyệt mặc định hay không. - - `share` - có tạo một liên kết có thể chia sẻ công khai từ máy tính của bạn cho giao diện hay không. Giống như một liên kết Google Drive! - -Chúng tôi sẽ trình bày chi tiết hơn về tham số `share` trong phần tiếp theo! - -## ✏️ Hãy áp dụng nó! - -Hãy xây dựng một giao diện cho phép bạn giới thiệu mô hình **nhận dạng giọng nói**. Để làm cho nó thú vị, chúng ta sẽ chấp nhận hoặc đầu vào micrô hoặc một tệp đã tải lên. - -Như thường lệ, chúng ta sẽ tải mô hình nhận dạng giọng nói của mình bằng cách sử dụng hàm `pipeline()` từ 🤗 Transformers. -Nếu bạn cần cập nhật nhanh, bạn có thể quay lại [phần đó trong Chương 1](/course/chapter1/3). Tiếp theo, chúng ta sẽ triển khai một hàm `transcribe_audio()` để xử lý âm thanh và trả về phiên âm. Cuối cùng, chúng ta sẽ gói hàm này trong một `Interface` với các thành phần `Audio` cho đầu vào và chỉ văn bản cho đầu ra. Nhìn chung, mã cho ứng dụng này như sau: - -```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() -``` - -Nếu trình duyệt của bạn không yêu cầu bạn cấp quyền đối với micrô, hãy mở bản demo trong một tab riêng. - - - -Nó đó! Bây giờ bạn có thể sử dụng giao diện này để phiên âm âm thanh. Chú ý ở đây rằng bằng cách đặt tham số `option` là `True`, chúng ta cho phép người dùng cung cấp micrô hoặc tệp âm thanh (hoặc không, nhưng điều đó sẽ trả lại thông báo lỗi). - -Tiếp tục xem cách chia sẻ giao diện của bạn với những người khác! +# Hiểu lớp Interface + + + +Trong phần này, chúng ta sẽ xem xét kỹ hơn về lớp `Interface` và hiểu các tham số chính được sử dụng để tạo ra nó. + +## Cách tạo một Interface + +Bạn sẽ nhận thấy rằng lớp `Interface` có 3 tham số bắt buộc: + +`Interface(fn, inputs, outputs, ...)` + +Các tham số này là: + + - `fn`: hàm dự đoán được bao bọc bởi giao diện Gradio. Hàm này có thể nhận một hoặc nhiều tham số và trả về một hoặc nhiều giá trị + - `inputs`: (các) loại thành phần đầu vào. Gradio cung cấp nhiều thành phần được tạo sẵn như`"image"` hay `"mic"`. + - `outputs`: (các) loại thành phần đầu ra. Một lần nữa, Gradio cung cấp nhiều thành phần được tạo sẵn, ví dụ: `"image"` hay `"label"`. + +Để có danh sách đầy đủ các thành phần, [xem tài liệu Gradio](https://gradio.app/docs). Mỗi thành phần được tạo sẵn có thể được tùy chỉnh bằng cách khởi tạo lớp tương ứng với thành phần. + +Ví dụ: như chúng ta đã thấy trong [phần trước](/course/chapter9/2), thay vì truyền tham số `input` vào trong `"textbox"`, bạn có thể truyền vào `Textbox(lines=7, label="Prompt")` để tạo một hộp văn bản có 7 dòng và một nhãn. + +Hãy xem một ví dụ khác, lần này với thành phần `Audio`. + +## Một ví dụ đơn giản với âm thanh + +Như đã đề cập trước đó, Gradio cung cấp nhiều đầu vào và đầu ra khác nhau. +Vì vậy, hãy xây dựng một `Interface` hoạt động với âm thanh. + +Trong ví dụ này, chúng tôi sẽ xây dựng một hàm chuyển đổi âm thanh sang âm thanh mà nhận tập tin âm thanh và chỉ cần đảo ngược nó. + +Chúng ta sẽ sử dụng thành phần `Audio` cho đầu vào. Khi sử dụng thành phần `Audio`, bạn có thể chỉ định xem bạn có muốn `source` của âm thanh là một tệp mà người dùng +tải lên hoặc micrô mà người dùng ghi lại giọng nói của họ. Trong trường hợp này, hãy đặt nó thành `"microphone"`. Chỉ cho vui thôi, chúng ta sẽ thêm một nhãn vào phần `Audio` của mình có nội dung "Speak here...", nghĩa là "Nói ở đây ...". + +Ngoài ra, chúng ta muốn nhận âm thanh dưới dạng mảng numpy để ta có thể dễ dàng "đảo ngược" nó lại. Vì vậy, chúng ta sẽ đặt `"type"` là `"numpy"`, chuyển đầu vào +dữ liệu dưới dạng một bộ (`sample_rate`, `data`) trong hàm của chúng ta. + +Chúng ta cũng sẽ sử dụng thành phần đầu ra `Audio` có thể tự động hiển thị một bộ tuple với tốc độ mẫu và mảng dữ liệu phức tạp dưới dạng tệp âm thanh có thể phát. Trong trường hợp này, chúng ta không cần thực hiện bất kỳ tùy chỉnh nào, vì vậy cta sẽ sử dụng chuỗi phím tắt `"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() +``` + +Đoạn mã trên sẽ tạo ra một giao diện giống như bên dưới (nếu trình duyệt của bạn không yêu cầu bạn cấp quyền đối với micrô, mở bản demo sang một tab khác.) + + + +Bây giờ bạn có thể ghi lại giọng nói của mình và nghe thấy chính mình đang nói ngược lại - thật ma quái 👻! + +## Xử lý nhiều đầu vào và đầu ra + +Giả sử chúng ta có một hàm phức tạp hơn, với nhiều đầu vào và đầu ra. Trong ví dụ dưới đây, chúng ta có một hàm lấy chỉ mục thả xuống, giá trị thanh trượt và số, và trả về một mẫu âm thanh của một giai điệu âm nhạc. + +Hãy xem cách chúng ta chuyển danh sách các thành phần đầu vào và đầu ra, và xem liệu bạn có thể theo dõi những gì đang xảy ra không. + +Chìa khóa ở đây là khi bạn truyền vào: +* danh sách các thành phần đầu vào, mỗi thành phần tương ứng với một tham số theo thứ tự. +* danh sách các thành phần đầu ra, mỗi thành phần tương ứng với một giá trị trả về. + +Đoạn mã bên dưới cho thấy cách ba thành phần đầu vào xếp hàng với ba tham số của hàm `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() +``` + + + +### Phương thức `launch()` + +Cho đến nay, chúng tôi đã sử dụng phương thức `launch()` để khởi chạy giao diện, nhưng chúng ta chưa thực sự thảo luận về những gì nó làm. + +Theo mặc định, phương thức `launch()` sẽ khởi chạy bản demo trong một máy chủ web đang chạy cục bộ. Nếu bạn đang chạy mã của mình trong notebook Jupyter hoặc Colab, thì Gradio sẽ nhúng GUI demo vào notebook để bạn có thể dễ dàng sử dụng. + +Bạn có thể tùy chỉnh hành vi của `launch()` thông qua các tham số khác nhau: + + - `inline` - có hiển thị giao diện nội tuyến trên notebook Python hay không. + - `inbrowser` - có tự động khởi chạy giao diện trong tab mới trên trình duyệt mặc định hay không. + - `share` - có tạo một liên kết có thể chia sẻ công khai từ máy tính của bạn cho giao diện hay không. Giống như một liên kết Google Drive! + +Chúng tôi sẽ trình bày chi tiết hơn về tham số `share` trong phần tiếp theo! + +## ✏️ Hãy áp dụng nó! + +Hãy xây dựng một giao diện cho phép bạn giới thiệu mô hình **nhận dạng giọng nói**. Để làm cho nó thú vị, chúng ta sẽ chấp nhận hoặc đầu vào micrô hoặc một tệp đã tải lên. + +Như thường lệ, chúng ta sẽ tải mô hình nhận dạng giọng nói của mình bằng cách sử dụng hàm `pipeline()` từ 🤗 Transformers. +Nếu bạn cần cập nhật nhanh, bạn có thể quay lại [phần đó trong Chương 1](/course/chapter1/3). Tiếp theo, chúng ta sẽ triển khai một hàm `transcribe_audio()` để xử lý âm thanh và trả về phiên âm. Cuối cùng, chúng ta sẽ gói hàm này trong một `Interface` với các thành phần `Audio` cho đầu vào và chỉ văn bản cho đầu ra. Nhìn chung, mã cho ứng dụng này như sau: + +```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() +``` + +Nếu trình duyệt của bạn không yêu cầu bạn cấp quyền đối với micrô, hãy mở bản demo trong một tab riêng. + + + +Nó đó! Bây giờ bạn có thể sử dụng giao diện này để phiên âm âm thanh. Chú ý ở đây rằng bằng cách đặt tham số `option` là `True`, chúng ta cho phép người dùng cung cấp micrô hoặc tệp âm thanh (hoặc không, nhưng điều đó sẽ trả lại thông báo lỗi). + +Tiếp tục xem cách chia sẻ giao diện của bạn với những người khác! diff --git a/chapters/vi/chapter9/4.mdx b/chapters/vi/chapter9/4.mdx index 0893954eb..5be9bff24 100644 --- a/chapters/vi/chapter9/4.mdx +++ b/chapters/vi/chapter9/4.mdx @@ -49,7 +49,7 @@ gr.Interface( Sử dụng các tùy chọn ở trên, chúng ta kết thúc với một giao diện hoàn chỉnh hơn. Hãy thử giao diện bên dưới: - + ### Chia sẻ bản demo của bạn với các liên kết tạm thời @@ -131,7 +131,7 @@ interface = gr.Interface( interface.launch(share=True) ``` - + Lưu ý tham số `live=True` trong `Interface`, có nghĩa là bản demo phác thảo tạo ra dự đoán mỗi khi ai đó vẽ trên sketchpad (không có nút gửi!). diff --git a/chapters/vi/chapter9/5.mdx b/chapters/vi/chapter9/5.mdx index 8e793e25d..f3376e2a6 100644 --- a/chapters/vi/chapter9/5.mdx +++ b/chapters/vi/chapter9/5.mdx @@ -39,7 +39,7 @@ gr.Interface.load( Đoạn mã trên sẽ tạo ra giao diện bên dưới: - + Tải mô hình theo cách này sử dụng [API luận suy](https://huggingface.co/inference-api) của Hugging Face, thay vì tải mô hình trong bộ nhớ. Điều này lý tưởng cho các mô hình lớn như GPT-J hoặc T0pp, những mô hình yêu cầu nhiều RAM. @@ -53,7 +53,7 @@ Bạn có nhớ bản demo từ phần 1 xóa nền của hình ảnh không? H gr.Interface.load("spaces/abidlabs/remove-bg").launch() ``` - + Một trong những điều thú vị khi tải các bản demo từ Hub hoặc Spaces là bạn tùy chỉnh chúng bằng cách ghi đè bất kỳ thông số nào. Ở đây, chúng ta thêm tiêu đề và làm cho tiêu đề đó hoạt động với webcam: @@ -63,6 +63,6 @@ gr.Interface.load( ).launch() ``` - + Bây giờ chúng ta đã khám phá một số cách để tích hợp Gradio với Hugging Face Hub, hãy cùng xem xét một số tính năng nâng cao của lớp `Interface`. Đó là chủ đề của phần tiếp theo! diff --git a/chapters/vi/chapter9/6.mdx b/chapters/vi/chapter9/6.mdx index d09c7ba9b..b5a39870e 100644 --- a/chapters/vi/chapter9/6.mdx +++ b/chapters/vi/chapter9/6.mdx @@ -51,7 +51,7 @@ iface = gr.Interface( iface.launch() ``` - + Lưu ý trạng thái của thành phần đầu ra vẫn tồn tại qua các lần gửi. Lưu ý: bạn có thể chuyển giá trị mặc định vào tham số trạng thái, được sử dụng làm giá trị ban đầu của trạng thái. @@ -91,7 +91,7 @@ gr.Interface( Kiểm tra hàm thông dịch bằng cách gửi đầu vào, sau đó nhấp vào Interpret tương ứng diễn giải bên dưới thành phần đầu ra. - + Bên cạnh phương pháp diễn giải mặc định mà Gradio cung cấp, bạn cũng có thể chỉ định `shap` cho tham số `interpretation` và đặt tham số `num_shap`. Điều này sử dụng diễn giải dựa trên Shapley, bạn có thể đọc thêm về [tại đây](https://christophm.github.io/interpretable-ml-book/shap.html). Cuối cùng, bạn cũng có thể chuyển hàm thông dịch của riêng mình vào tham số `interpretation`. Xem ví dụ trong trang bắt đầu của Gradio [tại đây](https://gradio.app/getting_started/). diff --git a/chapters/vi/chapter9/7.mdx b/chapters/vi/chapter9/7.mdx index f7e723fe4..91ab3a275 100644 --- a/chapters/vi/chapter9/7.mdx +++ b/chapters/vi/chapter9/7.mdx @@ -55,7 +55,7 @@ with demo: demo.launch() ``` - + Ví dụ đơn giản ở trên giới thiệu 4 khái niệm làm nền tảng cho Blocks: @@ -122,7 +122,7 @@ with demo: demo.launch() ``` - + Bạn sẽ nhận thấy rằng trong ví dụ này, chúng ta cũng đã tạo ra một thành phần `Button` trong mỗi tab và chỉ định một sự kiện nhấp chuột cho mỗi nút, đó là những gì thực sự chạy hàm. @@ -160,7 +160,7 @@ with gr.Blocks() as demo: demo.launch() ``` - + ### Tạo bản demo đa bước @@ -200,7 +200,7 @@ with demo: demo.launch() ``` - + ### Cập nhật Thuộc tính Thành phần @@ -231,6 +231,6 @@ with gr.Blocks() as block: block.launch() ``` - + Chúng ta vừa khám phá tất cả các khái niệm cốt lõi của `Blocks`! Cũng giống như với `Interfaces`, bạn có thể tạo các bản demo thú vị có thể được chia sẻ bằng cách sử dụng `share=True` trong phương thức `launch()` hoặc triển khai trên [Hugging Face Spaces](https://huggingface.co/spaces). diff --git a/chapters/vi/chapter9/9.mdx b/chapters/vi/chapter9/9.mdx index 135bae491..7adeec64f 100644 --- a/chapters/vi/chapter9/9.mdx +++ b/chapters/vi/chapter9/9.mdx @@ -1,234 +1,234 @@ - - -# Đố vui cuối chương - -Hãy kiểm tra những gì bạn đã học được trong chương này! - -### 1. Bạn có thể sử dụng Gradio để làm gì? - -share=True trong phương thức khởi chạy, bạn có thể tạo liên kết chia sẻ để gửi cho bất kỳ ai.", - correct: true - }, - { - text: "Gỡ lỗi mô hình của bạn", - explain: "Một lợi thế của bản demo gradio là có thể kiểm tra mô hình của bạn với dữ liệu thực mà bạn có thể thay đổi và quan sát sự thay đổi dự đoán của mô hình trong thời gian thực, giúp bạn gỡ lỗi mô hình của mình.", - correct: true - }, - { - text: "Huấn luyện mô hình của bạn", - explain: "Gradio được thiết kể để sử dụng cho việc luận suy mô hình, SAU KHI mô hình của bạn đã được huấn luyện.", - } - ]} -/> - -### 2. Gradio CHỈ hoạt động với các mô hình PyTorch - - - -### 3. Bạn có thể khởi chạy bản demo Gradio từ đâu? - - - -### 4. Gradio được thiết kế chủ yếu cho các mô hình NLP - - - -### 5. Tính năng nào sau đây được hỗ trợ bởi Gradio? - -gr.Interface.load()", - correct: true - } - ]} -/> - -### 6. Cách nào sau đây là cách hợp lệ để tải mô hìnhHugging Face từ Hub hoặc Spaces? - - - -### 7. Chọn tất cả các bước cần thiết để thêm trạng thái vào giao diện Gradio của bạn - - - -### 8. Những thành phần nào sau đây có trong thư viện Gradio? - - - -### 9. Gradio `Blocks` cho phép bạn làm gì? - - - -### 10. Bạn có thể chia sẻ liên kết công khai tới `Blocks` demo và tổ chức lưu trữ `Blocks` demo trên Hugging Face Spaces. - - + + +# Đố vui cuối chương + +Hãy kiểm tra những gì bạn đã học được trong chương này! + +### 1. Bạn có thể sử dụng Gradio để làm gì? + +share=True trong phương thức khởi chạy, bạn có thể tạo liên kết chia sẻ để gửi cho bất kỳ ai.", + correct: true + }, + { + text: "Gỡ lỗi mô hình của bạn", + explain: "Một lợi thế của bản demo gradio là có thể kiểm tra mô hình của bạn với dữ liệu thực mà bạn có thể thay đổi và quan sát sự thay đổi dự đoán của mô hình trong thời gian thực, giúp bạn gỡ lỗi mô hình của mình.", + correct: true + }, + { + text: "Huấn luyện mô hình của bạn", + explain: "Gradio được thiết kể để sử dụng cho việc luận suy mô hình, SAU KHI mô hình của bạn đã được huấn luyện.", + } + ]} +/> + +### 2. Gradio CHỈ hoạt động với các mô hình PyTorch + + + +### 3. Bạn có thể khởi chạy bản demo Gradio từ đâu? + + + +### 4. Gradio được thiết kế chủ yếu cho các mô hình NLP + + + +### 5. Tính năng nào sau đây được hỗ trợ bởi Gradio? + +gr.Interface.load()", + correct: true + } + ]} +/> + +### 6. Cách nào sau đây là cách hợp lệ để tải mô hìnhHugging Face từ Hub hoặc Spaces? + + + +### 7. Chọn tất cả các bước cần thiết để thêm trạng thái vào giao diện Gradio của bạn + + + +### 8. Những thành phần nào sau đây có trong thư viện Gradio? + + + +### 9. Gradio `Blocks` cho phép bạn làm gì? + + + +### 10. Bạn có thể chia sẻ liên kết công khai tới `Blocks` demo và tổ chức lưu trữ `Blocks` demo trên Hugging Face Spaces. + + diff --git a/chapters/zh-CN/chapter7/2.mdx b/chapters/zh-CN/chapter7/2.mdx index c85dd51f8..b328bbc0d 100644 --- a/chapters/zh-CN/chapter7/2.mdx +++ b/chapters/zh-CN/chapter7/2.mdx @@ -32,8 +32,8 @@ 当然,还有很多其他类型的token分类问题;这些只是几个有代表性的例子。在本节中,我们将在 NER 任务上微调模型 (BERT),然后该模型将能够计算如下预测: - - + + One-hot encoded labels for question answering. diff --git a/chapters/zh-CN/chapter7/3.mdx b/chapters/zh-CN/chapter7/3.mdx index d95d76313..a219af5f9 100644 --- a/chapters/zh-CN/chapter7/3.mdx +++ b/chapters/zh-CN/chapter7/3.mdx @@ -35,8 +35,8 @@ 在本节结束时, 你将在Hub上拥有一个[掩码语言模型(masked language model)](https://huggingface.co/huggingface-course/distilbert-base-uncased-finetuned-imdb?text=This+is+a+great+%5BMASK%5D.), 该模型可以自动完成句子, 如下所示: - - + + 让我们开始吧! diff --git a/chapters/zh-CN/chapter7/4.mdx b/chapters/zh-CN/chapter7/4.mdx index 07c79149c..32ea23dfa 100644 --- a/chapters/zh-CN/chapter7/4.mdx +++ b/chapters/zh-CN/chapter7/4.mdx @@ -35,8 +35,8 @@ 完成后,我们将拥有一个模型,可以进行这样的翻译: - - + + One-hot encoded labels for question answering. diff --git a/chapters/zh-CN/chapter7/5.mdx b/chapters/zh-CN/chapter7/5.mdx index 40c7111c5..e09659687 100644 --- a/chapters/zh-CN/chapter7/5.mdx +++ b/chapters/zh-CN/chapter7/5.mdx @@ -29,8 +29,8 @@ 尽管在[Hugging Face Hub](https://huggingface.co/models?pipeline_tag=summarization=downloads)上已经存在各种微调模型用于文本摘要,几乎所有这些都只适用于英文文档。因此,为了在本节中添加一些变化,我们将为英语和西班牙语训练一个双语模型。在本节结束时,您将有一个可以总结客户评论的[模型](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es)。 - - + + 如下所示:正如我们将看到的,这些摘要很简洁,因为它们是从客户在产品评论中提供的标题中学到的。让我们首先为这项任务准备一个合适的双语语料库。 diff --git a/chapters/zh-CN/chapter7/6.mdx b/chapters/zh-CN/chapter7/6.mdx index b8a5a8ede..494baba89 100644 --- a/chapters/zh-CN/chapter7/6.mdx +++ b/chapters/zh-CN/chapter7/6.mdx @@ -30,8 +30,8 @@ 在[第六章](/course/chapter6) 我们创建了一个高效的分词器来处理 Python 源代码,但我们仍然需要一个大规模数据集来预训练模型。在这里,我们将我们的分词器应用到源自 GitHub 存储库的 Python 代码语料库。然后我们将使用 `Trainer` API 和 🤗 Accelerate 来训练模型。让我们开始吧! - - + + 这实际上展示了使用本节中训练并上传到 Hub 的模型。你可以在[这里](https://huggingface.co/huggingface-course/codeparrot-ds?text=plt.imshow%28)找到。请注意,由于在文本生成过程中发生了一些随机化,您可能会得到略有不同的结果。 ## 收集数据 diff --git a/chapters/zh-CN/chapter7/7.mdx b/chapters/zh-CN/chapter7/7.mdx index 3874bc60f..8a823cadb 100644 --- a/chapters/zh-CN/chapter7/7.mdx +++ b/chapters/zh-CN/chapter7/7.mdx @@ -28,8 +28,8 @@ 我们将使用 [SQuAD 数据集](https://rajpurkar.github.io/SQuAD-explorer/) 微调一个BERT模型, 其中包括群众工作者对一组维基百科文章提出的问题。以下是一个小的测试样例: - - + + 本节使用的代码已经上传到了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) 找到它并尝试用它进行预测。 diff --git a/chapters/zh-CN/chapter8/4.mdx b/chapters/zh-CN/chapter8/4.mdx index 27f3614e7..528e27738 100644 --- a/chapters/zh-CN/chapter8/4.mdx +++ b/chapters/zh-CN/chapter8/4.mdx @@ -5,8 +5,8 @@ 你已经编写了一个漂亮的脚本来训练或微调给定任务的模型,尽职尽责地遵循 [Chapter 7](/course/chapter7) 中的建议。 但是当你启动命令 `trainer.train()` 时,可怕的事情发生了:你得到一个错误😱! 或者更糟糕的是,一切似乎都很好,训练运行没有错误,但生成的模型很糟糕。 在本节中,我们将向您展示如何调试此类问题。 diff --git a/chapters/zh-CN/chapter9/1.mdx b/chapters/zh-CN/chapter9/1.mdx index 2cb3dca85..88b53b355 100644 --- a/chapters/zh-CN/chapter9/1.mdx +++ b/chapters/zh-CN/chapter9/1.mdx @@ -15,15 +15,15 @@ * 一个**草图识别**模型,它接收草图并输出它认为正在绘制的标签: - + * 一个抽取式**问题回答**模型,它接受上下文段落和一个任务并输出一个结果和一个概率分数(我们在[第7章](/course/chapter7/7)中讨论了这种模型): - + * 一个**背景去除**模型,它接收图像并输出去除背景的图像: - + 本章分为两个部分,包括_概念_和_应用程序_。在您了解每个部分的概念后,您将应用它来构建特定类型的演示,范围从图像分类到语音识别。当你读完本章时,你将能够用几行 Python 代码构建这些演示(以及更多!)。 diff --git a/chapters/zh-CN/chapter9/2.mdx b/chapters/zh-CN/chapter9/2.mdx index 6c53eb619..f313f304d 100644 --- a/chapters/zh-CN/chapter9/2.mdx +++ b/chapters/zh-CN/chapter9/2.mdx @@ -37,7 +37,7 @@ demo.launch() 如果你运行这段代码,下面的界面会自动出现在 Jupyter/Colab notebook 中,或者在浏览器中弹出 **[http://localhost:7860](http://localhost:7860/)** 如果运行 从一个脚本。 - + 立即尝试使用您自己的姓名或其他输入来使用此 GUI! @@ -59,7 +59,7 @@ textbox = gr.Textbox(label="Type your name here:", placeholder="John Doe", lines gr.Interface(fn=greet, inputs=textbox, outputs="text").launch() ``` - + 在这里,我们创建了一个带有标签、占位符和一组行数的输入文本框。您可以对输出文本框执行相同的操作,但我们现在将其保留。 @@ -107,6 +107,6 @@ gr.Interface(fn=predict, inputs="text", outputs="text").launch() 就是这样! 您现在可以使用此接口使用 GPT-2 模型生成文本,如下所示 🤯. - + 继续阅读以了解如何使用 Gradio 构建其他类型的演示! \ No newline at end of file diff --git a/chapters/zh-CN/chapter9/3.mdx b/chapters/zh-CN/chapter9/3.mdx index 294b04a4b..1b9aa23ba 100644 --- a/chapters/zh-CN/chapter9/3.mdx +++ b/chapters/zh-CN/chapter9/3.mdx @@ -58,7 +58,7 @@ gr.Interface(reverse_audio, mic, "audio").launch() 上面的代码会产生一个类似下面的界面(如果你的浏览器没有 询问您的麦克风权限, open the demo in a separate tab.) - + 您现在应该能够录制自己的声音并听到自己在反向说话 - 怪异 👻! @@ -102,7 +102,7 @@ gr.Interface( ).launch() ``` - + ### `launch()` 方法 @@ -157,7 +157,7 @@ gr.Interface( 如果您的浏览器没有要求您提供麦克风权限,open the demo in a separate tab. - + 就是这样! 您现在可以使用此界面来转录音频。 注意这里 diff --git a/chapters/zh-CN/chapter9/4.mdx b/chapters/zh-CN/chapter9/4.mdx index 4e10fc77b..62c675d99 100644 --- a/chapters/zh-CN/chapter9/4.mdx +++ b/chapters/zh-CN/chapter9/4.mdx @@ -50,7 +50,7 @@ gr.Interface( 使用上面的选项,我们最终得到了一个更完整的界面。 试试下面的界面: - + ### 使用临时链接分享您的演示 现在我们已经有了机器学习模型的工作演示,让我们学习如何轻松共享指向我们界面的链接。 @@ -132,7 +132,7 @@ interface = gr.Interface( interface.launch(share=True) ``` - + 注意 `Interface` 中的 `live=True` 参数,这意味着草图演示使 diff --git a/chapters/zh-CN/chapter9/5.mdx b/chapters/zh-CN/chapter9/5.mdx index 71bc125a0..4fc5a67a0 100644 --- a/chapters/zh-CN/chapter9/5.mdx +++ b/chapters/zh-CN/chapter9/5.mdx @@ -38,7 +38,7 @@ gr.Interface.load( 上述代码将生成以下界面: - + 以这种方式加载模型使用 Hugging Face 的 [Inference API](https://huggingface.co/inference-api),而不是将模型加载到内存中。这对于像 GPT-J 或 T0pp这样需要大量 RAM 的大型模型是理想的。 @@ -51,7 +51,7 @@ gr.Interface.load( gr.Interface.load("spaces/abidlabs/remove-bg").launch() ``` - + 从Hub或Spaces加载演示的一个很酷的地方是, 你可以通过覆盖任何参数来自定义它们。在这里, 我们添加一个标题并让它与网络摄像头一起使用: @@ -61,6 +61,6 @@ gr.Interface.load( ).launch() ``` - + 现在我们已经探索了几种将Gradio与hugs Face Hub集成的方法, 让我们来看看 `Interface` 类的一些高级功能。这就是下一节的主题! \ No newline at end of file diff --git a/chapters/zh-CN/chapter9/6.mdx b/chapters/zh-CN/chapter9/6.mdx index 51f6ca0a8..a2593e842 100644 --- a/chapters/zh-CN/chapter9/6.mdx +++ b/chapters/zh-CN/chapter9/6.mdx @@ -51,7 +51,7 @@ iface = gr.Interface( iface.launch() ``` - + 请注意输出组件的状态如何在提交之间保持不变。注意: 可以给 state 参数传入一个默认值, 作为 state 的初始值。 @@ -90,7 +90,7 @@ gr.Interface( 通过提交一个输入, 然后单击输出组件下的Interpret来测试解释功能。 - + 除了Gradio提供的默认解释方法之外, 你还可以为 `interpretation` 参数指定 `shap`, 并设置 `num_shap` 参数。这使用基于 Shapley 的解释, 你可以在 [here](https://christophm.github.io/interpretable-ml-book/shap.html) 阅读更多信息。最后, 还可以将自己的解释函数传入 `interpretation` 参数。在Gradio的入门页面 [here](https://gradio.app/getting_started/) 中可以看到一个例子。 diff --git a/chapters/zh-CN/chapter9/7.mdx b/chapters/zh-CN/chapter9/7.mdx index 56b9eed58..68074009f 100644 --- a/chapters/zh-CN/chapter9/7.mdx +++ b/chapters/zh-CN/chapter9/7.mdx @@ -56,7 +56,7 @@ with demo: demo.launch() ``` - + 上述简单示例介绍了块的4个基本概念: @@ -121,7 +121,7 @@ with demo: demo.launch() ``` - + 你会注意到, 在这个示例中, 我们还在每个选项卡中创建了一个 `Button` 组件, 并且我们为每个按钮分配了一个点击事件,这是实际运行该函数的事件。 @@ -160,7 +160,7 @@ with gr.Blocks() as demo: demo.launch() ``` - + ### 创建多步demo @@ -200,7 +200,7 @@ with demo: demo.launch() ``` - + ### 更新组件属性 @@ -231,6 +231,6 @@ with gr.Blocks() as block: block.launch() ``` - + 我们刚刚探索了`块`的所有核心概念! 就像 `参数一样`, 你可以创建很酷的demo, 可以通过在`launch()`方法中使用`share=True`来共享, 或者部署在[Hugging Face Spaces](https://huggingface.co/spaces)上。 \ No newline at end of file From 0759c5556f20df32041e7409a79f9ab6a742939f Mon Sep 17 00:00:00 2001 From: lewtun Date: Wed, 30 Nov 2022 17:09:33 +0100 Subject: [PATCH 41/51] Bump release (#387) --- chapters/fr/chapter1/1.mdx | 2 +- chapters/fr/chapter1/3.mdx | 6 +- chapters/fr/chapter1/8.mdx | 6 +- chapters/fr/chapter2/2.mdx | 12 +- chapters/fr/chapter2/3.mdx | 12 +- chapters/fr/chapter2/4.mdx | 12 +- chapters/fr/chapter2/5.mdx | 12 +- chapters/fr/chapter2/6.mdx | 12 +- chapters/fr/chapter3/2.mdx | 13 +- chapters/fr/chapter3/3.mdx | 6 +- chapters/fr/chapter3/3_tf.mdx | 6 +- chapters/fr/chapter3/4.mdx | 6 +- chapters/fr/chapter4/2.mdx | 13 +- chapters/fr/chapter4/3.mdx | 14 +- chapters/fr/chapter5/2.mdx | 6 +- chapters/fr/chapter5/3.mdx | 7 +- chapters/fr/chapter5/4.mdx | 8 +- chapters/fr/chapter5/5.mdx | 7 +- chapters/fr/chapter5/6.mdx | 13 +- chapters/fr/chapter6/2.mdx | 8 +- chapters/fr/chapter6/3.mdx | 13 +- chapters/fr/chapter6/3b.mdx | 15 +- chapters/fr/chapter6/4.mdx | 6 +- chapters/fr/chapter6/5.mdx | 8 +- chapters/fr/chapter6/6.mdx | 6 +- chapters/fr/chapter6/7.mdx | 6 +- chapters/fr/chapter6/8.mdx | 7 +- chapters/fr/chapter7/2.mdx | 13 +- chapters/fr/chapter7/3.mdx | 13 +- chapters/fr/chapter7/4.mdx | 13 +- chapters/fr/chapter7/5.mdx | 13 +- chapters/fr/chapter7/6.mdx | 13 +- chapters/fr/chapter8/2.mdx | 6 +- chapters/fr/chapter8/3.mdx | 8 +- chapters/fr/chapter8/4.mdx | 6 +- chapters/fr/chapter8/4_tf.mdx | 8 +- chapters/fr/chapter8/5.mdx | 7 +- chapters/fr/chapter9/2.mdx | 8 +- chapters/fr/chapter9/3.mdx | 6 +- chapters/fr/chapter9/4.mdx | 8 +- chapters/fr/chapter9/5.mdx | 8 +- chapters/fr/chapter9/6.mdx | 6 +- chapters/fr/chapter9/7.mdx | 8 +- subtitles/README.md | 29 + .../00_welcome-to-the-hugging-face-course.srt | 455 ++++++++ subtitles/en/01_the-pipeline-function.srt | 445 ++++++++ ...2_the-carbon-footprint-of-transformers.srt | 581 ++++++++++ subtitles/en/03_what-is-transfer-learning.srt | 396 +++++++ .../en/04_the-transformer-architecture.srt | 280 +++++ .../en/05_transformer-models-encoders.srt | 454 ++++++++ .../en/06_transformer-models-decoders.srt | 395 +++++++ ...07_transformer-models-encoder-decoders.srt | 621 ++++++++++ ...inside-the-pipeline-function-(pytorch).srt | 471 ++++++++ ...ide-the-pipeline-function-(tensorflow).srt | 473 ++++++++ ...antiate-a-transformers-model-(pytorch).srt | 308 +++++ ...iate-a-transformers-model-(tensorflow).srt | 317 ++++++ subtitles/en/12_tokenizers-overview.srt | 99 ++ subtitles/en/13_word-based-tokenizers.srt | 264 +++++ .../en/14_character-based-tokenizers.srt | 278 +++++ subtitles/en/15_subword-based-tokenizers.srt | 323 ++++++ subtitles/en/16_the-tokenization-pipeline.srt | 339 ++++++ .../17_batching-inputs-together-(pytorch).srt | 291 +++++ ..._batching-inputs-together-(tensorflow).srt | 281 +++++ ...gging-face-datasets-overview-(pytorch).srt | 341 ++++++ ...ng-face-datasets-overview-(tensorflow).srt | 320 ++++++ ...preprocessing-sentence-pairs-(pytorch).srt | 294 +++++ ...processing-sentence-pairs-(tensorflow).srt | 309 +++++ subtitles/en/23_what-is-dynamic-padding.srt | 300 +++++ subtitles/en/24_the-trainer-api.srt | 382 +++++++ subtitles/en/25_keras-introduction.srt | 290 +++++ .../en/26_fine-tuning-with-tensorflow.srt | 567 +++++++++ ...arning-rate-scheduling-with-tensorflow.srt | 468 ++++++++ .../28_tensorflow-predictions-and-metrics.srt | 461 ++++++++ ...29_write-your-training-loop-in-pytorch.srt | 536 +++++++++ ...-pytorch-training-loop-with-accelerate.srt | 322 ++++++ subtitles/en/31_navigating-the-model-hub.srt | 343 ++++++ .../32_managing-a-repo-on-the-model-hub.srt | 750 ++++++++++++ .../en/33_the-push-to-hub-api-(pytorch).srt | 479 ++++++++ .../34_the-push-to-hub-api-(tensorflow).srt | 877 ++++++++++++++ subtitles/en/35_loading-a-custom-dataset.srt | 343 ++++++ ...e-and-dice-a-dataset-\360\237\224\252.srt" | 370 ++++++ ...dataframes-=-\342\235\244\357\270\217.srt" | 283 +++++ .../en/38_saving-and-reloading-a-dataset.srt | 359 ++++++ .../en/39_memory-mapping-&-streaming.srt | 370 ++++++ .../en/40_uploading-a-dataset-to-the-hub.srt | 228 ++++ .../41_text-embeddings-&-semantic-search.srt | 368 ++++++ subtitles/en/42_training-a-new-tokenizer.srt | 512 +++++++++ ...43_why-are-fast-tokenizers-called-fast.srt | 168 +++ .../en/44_fast-tokenizer-superpowers.srt | 335 ++++++ ...oken-classification-pipeline-(pytorch).srt | 333 ++++++ ...n-classification-pipeline-(tensorflow).srt | 320 ++++++ ...-question-answering-pipeline-(pytorch).srt | 342 ++++++ ...estion-answering-pipeline-(tensorflow).srt | 358 ++++++ subtitles/en/49_what-is-normalization.srt | 414 +++++++ subtitles/en/50_what-is-pre-tokenization.srt | 193 ++++ .../en/51_byte-pair-encoding-tokenization.srt | 377 ++++++ subtitles/en/52_wordpiece-tokenization.srt | 290 +++++ subtitles/en/53_unigram-tokenization.srt | 707 ++++++++++++ subtitles/en/54_building-a-new-tokenizer.srt | 396 +++++++ ...ta-processing-for-token-classification.srt | 326 ++++++ ...rocessing-for-masked-language-modeling.srt | 249 ++++ subtitles/en/57_what-is-perplexity.srt | 231 ++++ subtitles/en/58_what-is-domain-adaptation.srt | 185 +++ .../en/59_data-processing-for-translation.srt | 247 ++++ subtitles/en/60_what-is-the-bleu-metric.srt | 540 +++++++++ .../61_data-processing-for-summarization.srt | 221 ++++ subtitles/en/62_what-is-the-rouge-metric.srt | 455 ++++++++ ...rocessing-for-causal-language-modeling.srt | 415 +++++++ .../en/64_using-a-custom-loss-function.srt | 325 ++++++ ...data-processing-for-question-answering.srt | 277 +++++ ...g-step-in-question-answering-(pytorch).srt | 342 ++++++ ...tep-in-question-answering-(tensorflow).srt | 329 ++++++ subtitles/en/68_data-collators-a-tour.srt | 655 +++++++++++ .../69_what-to-do-when-you-get-an-error.srt | 271 +++++ .../en/70_using-a-debugger-in-a-notebook.srt | 319 ++++++ .../en/71_using-a-debugger-in-a-terminal.srt | 350 ++++++ .../en/72_asking-for-help-on-the-forums.srt | 346 ++++++ ...ugging-the-training-pipeline-(pytorch).srt | 448 ++++++++ ...ing-the-training-pipeline-(tensorflow).srt | 799 +++++++++++++ subtitles/en/75_writing-a-good-issue.srt | 330 ++++++ subtitles/en/metadata.csv | 77 ++ subtitles/en/raw/chapter1/01_welcome.md | 1 + .../raw/chapter1/03_the-pipeline-function.md | 1 + .../raw/chapter1/04a_the-carbon-footprint.md | 1 + .../chapter1/04b_what-is-transfer-learning.md | 1 + .../04c_the-transformer-architecture.md | 1 + subtitles/en/raw/chapter1/05_encoders.md | 2 + subtitles/en/raw/chapter1/06_decoders.md | 1 + .../en/raw/chapter1/07_encoder-decoders.md | 1 + .../en/raw/chapter2/02_inside-pipeline-pt.md | 1 + .../en/raw/chapter2/02_inside-pipeline-tf.md | 1 + subtitles/en/raw/chapter2/03_model-api-pt.md | 1 + subtitles/en/raw/chapter2/03_model-api-tf.md | 1 + .../raw/chapter2/04a_tokenizers-overview.md | 1 + .../raw/chapter2/04b_word-based-tokenizers.md | 1 + .../04c_character-based-tokenizers.md | 1 + .../chapter2/04d_subword-based-tokenizers.md | 1 + .../en/raw/chapter2/04e_tokenizer-pipeline.md | 1 + .../en/raw/chapter2/05_batching-inputs-pt.md | 1 + .../en/raw/chapter2/05_batching-inputs-tf.md | 1 + .../raw/chapter3/02a_datasets-overview-pt.md | 1 + .../raw/chapter3/02a_datasets-overview-tf.md | 1 + .../02c_preprocess-sentence-pairs-pt.md | 1 + .../02c_preprocess-sentence-pairs-tf.md | 1 + .../en/raw/chapter3/02d_dynamic-padding.md | 1 + subtitles/en/raw/chapter3/03a_trainer-api.md | 1 + subtitles/en/raw/chapter3/03b_keras-intro.md | 1 + .../en/raw/chapter3/03c_keras-finetuning.md | 1 + .../raw/chapter3/03d_keras-learning-rate.md | 1 + .../en/raw/chapter3/03e_keras-metrics.md | 1 + .../en/raw/chapter3/04a_raw-training-loop.md | 1 + subtitles/en/raw/chapter3/04b_accelerate.md | 1 + .../en/raw/chapter4/01_huggingface-hub.md | 1 + .../en/raw/chapter4/03a_managing-repos.md | 1 + .../en/raw/chapter4/03b_push-to-hub-pt.md | 1 + .../en/raw/chapter4/03b_push-to-hub-tf.md | 1 + .../en/raw/chapter5/02_custom-dataset.md | 1 + .../en/raw/chapter5/03a_slice-and-dice.md | 1 + subtitles/en/raw/chapter5/03b_dataframes.md | 1 + subtitles/en/raw/chapter5/03c_save-reload.md | 1 + .../en/raw/chapter5/04_memory-mapping.md | 6 + .../en/raw/chapter5/05_upload-dataset.md | 1 + .../en/raw/chapter5/06_text-embeddings.md | 1 + .../en/raw/chapter6/02_train-new-tokenizer.md | 1 + .../raw/chapter6/03a_why-fast-tokenizers.md | 1 + .../03b_fast-tokenizers-superpowers.md | 1 + .../chapter6/03c_tokenization-pipeline-pt.md | 1 + .../chapter6/03c_tokenization-pipeline-tf.md | 1 + .../04_question-answering-pipeline-pt.md | 1 + .../04_question-answering-pipeline-tf.md | 1 + .../en/raw/chapter6/05a_normalization.md | 1 + .../en/raw/chapter6/05b_pre-tokenization.md | 1 + subtitles/en/raw/chapter6/06_bpe.md | 1 + subtitles/en/raw/chapter6/07_wordpiece.md | 1 + subtitles/en/raw/chapter6/08_unigram.md | 5 + .../raw/chapter6/09_building-a-tokenizer.md | 1 + .../02_token-classification-processing.md | 1 + .../en/raw/chapter7/03a_mlm-processing.md | 1 + subtitles/en/raw/chapter7/03b_perplexity.md | 1 + .../en/raw/chapter7/03c_domain-adaptation.md | 1 + .../chapter7/04a_translation-processing.md | 1 + subtitles/en/raw/chapter7/04b_bleu.md | 1 + .../chapter7/05a_summarization-processing.md | 1 + subtitles/en/raw/chapter7/05b_rouge.md | 1 + .../en/raw/chapter7/06a_clm-processing.md | 1 + subtitles/en/raw/chapter7/06b_custom-loss.md | 1 + .../07a_question-answering-processing.md | 1 + ...b_question-answering-post-processing-pt.md | 1 + ...b_question-answering-post-processing-tf.md | 1 + .../en/raw/chapter7/08_data-collators.md | 1 + subtitles/en/raw/chapter8/02a_error.md | 1 + .../en/raw/chapter8/02b_debug-notebook.md | 1 + .../en/raw/chapter8/02c_debug-terminal.md | 1 + subtitles/en/raw/chapter8/03_forums.md | 19 + subtitles/en/raw/chapter8/04_debug-pt.md | 1 + subtitles/en/raw/chapter8/04_debug-tf.md | 1 + subtitles/en/raw/chapter8/05_issues.md | 1 + .../00_welcome-to-the-hugging-face-course.srt | 515 +++++++++ subtitles/zh-CN/01_the-pipeline-function.srt | 500 ++++++++ ...2_the-carbon-footprint-of-transformers.srt | 645 +++++++++++ .../zh-CN/03_what-is-transfer-learning.srt | 450 ++++++++ .../zh-CN/04_the-transformer-architecture.srt | 320 ++++++ .../zh-CN/05_transformer-models-encoders.srt | 510 +++++++++ .../zh-CN/06_transformer-models-decoders.srt | 435 +++++++ ...07_transformer-models-encoder-decoders.srt | 710 ++++++++++++ ...inside-the-pipeline-function-(pytorch).srt | 530 +++++++++ ...ide-the-pipeline-function-(tensorflow).srt | 535 +++++++++ ...antiate-a-transformers-model-(pytorch).srt | 340 ++++++ ...iate-a-transformers-model-(tensorflow).srt | 355 ++++++ subtitles/zh-CN/12_tokenizers-overview.srt | 115 ++ subtitles/zh-CN/13_word-based-tokenizers.srt | 300 +++++ .../zh-CN/14_character-based-tokenizers.srt | 305 +++++ .../zh-CN/15_subword-based-tokenizers.srt | 360 ++++++ .../zh-CN/16_the-tokenization-pipeline.srt | 380 +++++++ .../17_batching-inputs-together-(pytorch).srt | 331 ++++++ ..._batching-inputs-together-(tensorflow).srt | 315 +++++ ...gging-face-datasets-overview-(pytorch).srt | 375 ++++++ ...ng-face-datasets-overview-(tensorflow).srt | 355 ++++++ ...preprocessing-sentence-pairs-(pytorch).srt | 325 ++++++ ...processing-sentence-pairs-(tensorflow).srt | 355 ++++++ .../zh-CN/23_what-is-dynamic-padding.srt | 340 ++++++ subtitles/zh-CN/24_the-trainer-api.srt | 445 ++++++++ subtitles/zh-CN/25_keras-introduction.srt | 320 ++++++ .../zh-CN/26_fine-tuning-with-tensorflow.srt | 640 +++++++++++ ...arning-rate-scheduling-with-tensorflow.srt | 530 +++++++++ .../28_tensorflow-predictions-and-metrics.srt | 525 +++++++++ ...29_write-your-training-loop-in-pytorch.srt | 620 ++++++++++ ...-pytorch-training-loop-with-accelerate.srt | 370 ++++++ .../zh-CN/31_navigating-the-model-hub.srt | 385 +++++++ .../32_managing-a-repo-on-the-model-hub.srt | 850 ++++++++++++++ .../33_the-push-to-hub-api-(pytorch).srt | 545 +++++++++ .../34_the-push-to-hub-api-(tensorflow).srt | 1010 +++++++++++++++++ .../zh-CN/35_loading-a-custom-dataset.srt | 385 +++++++ ...e-and-dice-a-dataset-\360\237\224\252.srt" | 415 +++++++ ...dataframes-=-\342\235\244\357\270\217.srt" | 320 ++++++ .../38_saving-and-reloading-a-dataset.srt | 400 +++++++ .../zh-CN/39_memory-mapping-&-streaming.srt | 415 +++++++ .../40_uploading-a-dataset-to-the-hub.srt | 260 +++++ .../41_text-embeddings-&-semantic-search.srt | 405 +++++++ .../zh-CN/42_training-a-new-tokenizer.srt | 565 +++++++++ ...43_why-are-fast-tokenizers-called-fast.srt | 185 +++ .../zh-CN/44_fast-tokenizer-superpowers.srt | 375 ++++++ ...oken-classification-pipeline-(pytorch).srt | 385 +++++++ ...n-classification-pipeline-(tensorflow).srt | 375 ++++++ ...-question-answering-pipeline-(pytorch).srt | 380 +++++++ ...estion-answering-pipeline-(tensorflow).srt | 410 +++++++ subtitles/zh-CN/49_what-is-normalization.srt | 465 ++++++++ .../zh-CN/50_what-is-pre-tokenization.srt | 220 ++++ .../51_byte-pair-encoding-tokenization.srt | 435 +++++++ subtitles/zh-CN/52_wordpiece-tokenization.srt | 320 ++++++ subtitles/zh-CN/53_unigram-tokenization.srt | 800 +++++++++++++ .../zh-CN/54_building-a-new-tokenizer.srt | 435 +++++++ ...ta-processing-for-token-classification.srt | 365 ++++++ ...rocessing-for-masked-language-modeling.srt | 280 +++++ subtitles/zh-CN/57_what-is-perplexity.srt | 265 +++++ .../zh-CN/58_what-is-domain-adaptation.srt | 200 ++++ .../59_data-processing-for-translation.srt | 275 +++++ .../zh-CN/60_what-is-the-bleu-metric.srt | 600 ++++++++++ .../61_data-processing-for-summarization.srt | 240 ++++ .../zh-CN/62_what-is-the-rouge-metric.srt | 510 +++++++++ ...rocessing-for-causal-language-modeling.srt | 470 ++++++++ .../zh-CN/64_using-a-custom-loss-function.srt | 360 ++++++ ...data-processing-for-question-answering.srt | 300 +++++ ...g-step-in-question-answering-(pytorch).srt | 385 +++++++ ...tep-in-question-answering-(tensorflow).srt | 370 ++++++ subtitles/zh-CN/68_data-collators-a-tour.srt | 735 ++++++++++++ .../69_what-to-do-when-you-get-an-error.srt | 315 +++++ .../70_using-a-debugger-in-a-notebook.srt | 360 ++++++ .../71_using-a-debugger-in-a-terminal.srt | 390 +++++++ .../72_asking-for-help-on-the-forums.srt | 390 +++++++ ...ugging-the-training-pipeline-(pytorch).srt | 505 +++++++++ ...ing-the-training-pipeline-(tensorflow).srt | 900 +++++++++++++++ subtitles/zh-CN/75_writing-a-good-issue.srt | 365 ++++++ subtitles/zh-CN/metadata.csv | 77 ++ utils/generate_subtitles.py | 64 ++ 275 files changed, 61908 insertions(+), 136 deletions(-) create mode 100644 subtitles/README.md create mode 100644 subtitles/en/00_welcome-to-the-hugging-face-course.srt create mode 100644 subtitles/en/01_the-pipeline-function.srt create mode 100644 subtitles/en/02_the-carbon-footprint-of-transformers.srt create mode 100644 subtitles/en/03_what-is-transfer-learning.srt create mode 100644 subtitles/en/04_the-transformer-architecture.srt create mode 100644 subtitles/en/05_transformer-models-encoders.srt create mode 100644 subtitles/en/06_transformer-models-decoders.srt create mode 100644 subtitles/en/07_transformer-models-encoder-decoders.srt create mode 100644 subtitles/en/08_what-happens-inside-the-pipeline-function-(pytorch).srt create mode 100644 subtitles/en/09_what-happens-inside-the-pipeline-function-(tensorflow).srt create mode 100644 subtitles/en/10_instantiate-a-transformers-model-(pytorch).srt create mode 100644 subtitles/en/11_instantiate-a-transformers-model-(tensorflow).srt create mode 100644 subtitles/en/12_tokenizers-overview.srt create mode 100644 subtitles/en/13_word-based-tokenizers.srt create mode 100644 subtitles/en/14_character-based-tokenizers.srt create mode 100644 subtitles/en/15_subword-based-tokenizers.srt create mode 100644 subtitles/en/16_the-tokenization-pipeline.srt create mode 100644 subtitles/en/17_batching-inputs-together-(pytorch).srt create mode 100644 subtitles/en/18_batching-inputs-together-(tensorflow).srt create mode 100644 subtitles/en/19_hugging-face-datasets-overview-(pytorch).srt create mode 100644 subtitles/en/20_hugging-face-datasets-overview-(tensorflow).srt create mode 100644 subtitles/en/21_preprocessing-sentence-pairs-(pytorch).srt create mode 100644 subtitles/en/22_preprocessing-sentence-pairs-(tensorflow).srt create mode 100644 subtitles/en/23_what-is-dynamic-padding.srt create mode 100644 subtitles/en/24_the-trainer-api.srt create mode 100644 subtitles/en/25_keras-introduction.srt create mode 100644 subtitles/en/26_fine-tuning-with-tensorflow.srt create mode 100644 subtitles/en/27_learning-rate-scheduling-with-tensorflow.srt create mode 100644 subtitles/en/28_tensorflow-predictions-and-metrics.srt create mode 100644 subtitles/en/29_write-your-training-loop-in-pytorch.srt create mode 100644 subtitles/en/30_supercharge-your-pytorch-training-loop-with-accelerate.srt create mode 100644 subtitles/en/31_navigating-the-model-hub.srt create mode 100644 subtitles/en/32_managing-a-repo-on-the-model-hub.srt create mode 100644 subtitles/en/33_the-push-to-hub-api-(pytorch).srt create mode 100644 subtitles/en/34_the-push-to-hub-api-(tensorflow).srt create mode 100644 subtitles/en/35_loading-a-custom-dataset.srt create mode 100644 "subtitles/en/36_slice-and-dice-a-dataset-\360\237\224\252.srt" create mode 100644 "subtitles/en/37_datasets-+-dataframes-=-\342\235\244\357\270\217.srt" create mode 100644 subtitles/en/38_saving-and-reloading-a-dataset.srt create mode 100644 subtitles/en/39_memory-mapping-&-streaming.srt create mode 100644 subtitles/en/40_uploading-a-dataset-to-the-hub.srt create mode 100644 subtitles/en/41_text-embeddings-&-semantic-search.srt create mode 100644 subtitles/en/42_training-a-new-tokenizer.srt create mode 100644 subtitles/en/43_why-are-fast-tokenizers-called-fast.srt create mode 100644 subtitles/en/44_fast-tokenizer-superpowers.srt create mode 100644 subtitles/en/45_inside-the-token-classification-pipeline-(pytorch).srt create mode 100644 subtitles/en/46_inside-the-token-classification-pipeline-(tensorflow).srt create mode 100644 subtitles/en/47_inside-the-question-answering-pipeline-(pytorch).srt create mode 100644 subtitles/en/48_inside-the-question-answering-pipeline-(tensorflow).srt create mode 100644 subtitles/en/49_what-is-normalization.srt create mode 100644 subtitles/en/50_what-is-pre-tokenization.srt create mode 100644 subtitles/en/51_byte-pair-encoding-tokenization.srt create mode 100644 subtitles/en/52_wordpiece-tokenization.srt create mode 100644 subtitles/en/53_unigram-tokenization.srt create mode 100644 subtitles/en/54_building-a-new-tokenizer.srt create mode 100644 subtitles/en/55_data-processing-for-token-classification.srt create mode 100644 subtitles/en/56_data-processing-for-masked-language-modeling.srt create mode 100644 subtitles/en/57_what-is-perplexity.srt create mode 100644 subtitles/en/58_what-is-domain-adaptation.srt create mode 100644 subtitles/en/59_data-processing-for-translation.srt create mode 100644 subtitles/en/60_what-is-the-bleu-metric.srt create mode 100644 subtitles/en/61_data-processing-for-summarization.srt create mode 100644 subtitles/en/62_what-is-the-rouge-metric.srt create mode 100644 subtitles/en/63_data-processing-for-causal-language-modeling.srt create mode 100644 subtitles/en/64_using-a-custom-loss-function.srt create mode 100644 subtitles/en/65_data-processing-for-question-answering.srt create mode 100644 subtitles/en/66_the-post-processing-step-in-question-answering-(pytorch).srt create mode 100644 subtitles/en/67_the-post-processing-step-in-question-answering-(tensorflow).srt create mode 100644 subtitles/en/68_data-collators-a-tour.srt create mode 100644 subtitles/en/69_what-to-do-when-you-get-an-error.srt create mode 100644 subtitles/en/70_using-a-debugger-in-a-notebook.srt create mode 100644 subtitles/en/71_using-a-debugger-in-a-terminal.srt create mode 100644 subtitles/en/72_asking-for-help-on-the-forums.srt create mode 100644 subtitles/en/73_debugging-the-training-pipeline-(pytorch).srt create mode 100644 subtitles/en/74_debugging-the-training-pipeline-(tensorflow).srt create mode 100644 subtitles/en/75_writing-a-good-issue.srt create mode 100644 subtitles/en/metadata.csv create mode 100644 subtitles/en/raw/chapter1/01_welcome.md create mode 100644 subtitles/en/raw/chapter1/03_the-pipeline-function.md create mode 100644 subtitles/en/raw/chapter1/04a_the-carbon-footprint.md create mode 100644 subtitles/en/raw/chapter1/04b_what-is-transfer-learning.md create mode 100644 subtitles/en/raw/chapter1/04c_the-transformer-architecture.md create mode 100644 subtitles/en/raw/chapter1/05_encoders.md create mode 100644 subtitles/en/raw/chapter1/06_decoders.md create mode 100644 subtitles/en/raw/chapter1/07_encoder-decoders.md create mode 100644 subtitles/en/raw/chapter2/02_inside-pipeline-pt.md create mode 100644 subtitles/en/raw/chapter2/02_inside-pipeline-tf.md create mode 100644 subtitles/en/raw/chapter2/03_model-api-pt.md create mode 100644 subtitles/en/raw/chapter2/03_model-api-tf.md create mode 100644 subtitles/en/raw/chapter2/04a_tokenizers-overview.md create mode 100644 subtitles/en/raw/chapter2/04b_word-based-tokenizers.md create mode 100644 subtitles/en/raw/chapter2/04c_character-based-tokenizers.md create mode 100644 subtitles/en/raw/chapter2/04d_subword-based-tokenizers.md create mode 100644 subtitles/en/raw/chapter2/04e_tokenizer-pipeline.md create mode 100644 subtitles/en/raw/chapter2/05_batching-inputs-pt.md create mode 100644 subtitles/en/raw/chapter2/05_batching-inputs-tf.md create mode 100644 subtitles/en/raw/chapter3/02a_datasets-overview-pt.md create mode 100644 subtitles/en/raw/chapter3/02a_datasets-overview-tf.md create mode 100644 subtitles/en/raw/chapter3/02c_preprocess-sentence-pairs-pt.md create mode 100644 subtitles/en/raw/chapter3/02c_preprocess-sentence-pairs-tf.md create mode 100644 subtitles/en/raw/chapter3/02d_dynamic-padding.md create mode 100644 subtitles/en/raw/chapter3/03a_trainer-api.md create mode 100644 subtitles/en/raw/chapter3/03b_keras-intro.md create mode 100644 subtitles/en/raw/chapter3/03c_keras-finetuning.md create mode 100644 subtitles/en/raw/chapter3/03d_keras-learning-rate.md create mode 100644 subtitles/en/raw/chapter3/03e_keras-metrics.md create mode 100644 subtitles/en/raw/chapter3/04a_raw-training-loop.md create mode 100644 subtitles/en/raw/chapter3/04b_accelerate.md create mode 100644 subtitles/en/raw/chapter4/01_huggingface-hub.md create mode 100644 subtitles/en/raw/chapter4/03a_managing-repos.md create mode 100644 subtitles/en/raw/chapter4/03b_push-to-hub-pt.md create mode 100644 subtitles/en/raw/chapter4/03b_push-to-hub-tf.md create mode 100644 subtitles/en/raw/chapter5/02_custom-dataset.md create mode 100644 subtitles/en/raw/chapter5/03a_slice-and-dice.md create mode 100644 subtitles/en/raw/chapter5/03b_dataframes.md create mode 100644 subtitles/en/raw/chapter5/03c_save-reload.md create mode 100644 subtitles/en/raw/chapter5/04_memory-mapping.md create mode 100644 subtitles/en/raw/chapter5/05_upload-dataset.md create mode 100644 subtitles/en/raw/chapter5/06_text-embeddings.md create mode 100644 subtitles/en/raw/chapter6/02_train-new-tokenizer.md create mode 100644 subtitles/en/raw/chapter6/03a_why-fast-tokenizers.md create mode 100644 subtitles/en/raw/chapter6/03b_fast-tokenizers-superpowers.md create mode 100644 subtitles/en/raw/chapter6/03c_tokenization-pipeline-pt.md create mode 100644 subtitles/en/raw/chapter6/03c_tokenization-pipeline-tf.md create mode 100644 subtitles/en/raw/chapter6/04_question-answering-pipeline-pt.md create mode 100644 subtitles/en/raw/chapter6/04_question-answering-pipeline-tf.md create mode 100644 subtitles/en/raw/chapter6/05a_normalization.md create mode 100644 subtitles/en/raw/chapter6/05b_pre-tokenization.md create mode 100644 subtitles/en/raw/chapter6/06_bpe.md create mode 100644 subtitles/en/raw/chapter6/07_wordpiece.md create mode 100644 subtitles/en/raw/chapter6/08_unigram.md create mode 100644 subtitles/en/raw/chapter6/09_building-a-tokenizer.md create mode 100644 subtitles/en/raw/chapter7/02_token-classification-processing.md create mode 100644 subtitles/en/raw/chapter7/03a_mlm-processing.md create mode 100644 subtitles/en/raw/chapter7/03b_perplexity.md create mode 100644 subtitles/en/raw/chapter7/03c_domain-adaptation.md create mode 100644 subtitles/en/raw/chapter7/04a_translation-processing.md create mode 100644 subtitles/en/raw/chapter7/04b_bleu.md create mode 100644 subtitles/en/raw/chapter7/05a_summarization-processing.md create mode 100644 subtitles/en/raw/chapter7/05b_rouge.md create mode 100644 subtitles/en/raw/chapter7/06a_clm-processing.md create mode 100644 subtitles/en/raw/chapter7/06b_custom-loss.md create mode 100644 subtitles/en/raw/chapter7/07a_question-answering-processing.md create mode 100644 subtitles/en/raw/chapter7/07b_question-answering-post-processing-pt.md create mode 100644 subtitles/en/raw/chapter7/07b_question-answering-post-processing-tf.md create mode 100644 subtitles/en/raw/chapter7/08_data-collators.md create mode 100644 subtitles/en/raw/chapter8/02a_error.md create mode 100644 subtitles/en/raw/chapter8/02b_debug-notebook.md create mode 100644 subtitles/en/raw/chapter8/02c_debug-terminal.md create mode 100644 subtitles/en/raw/chapter8/03_forums.md create mode 100644 subtitles/en/raw/chapter8/04_debug-pt.md create mode 100644 subtitles/en/raw/chapter8/04_debug-tf.md create mode 100644 subtitles/en/raw/chapter8/05_issues.md create mode 100644 subtitles/zh-CN/00_welcome-to-the-hugging-face-course.srt create mode 100644 subtitles/zh-CN/01_the-pipeline-function.srt create mode 100644 subtitles/zh-CN/02_the-carbon-footprint-of-transformers.srt create mode 100644 subtitles/zh-CN/03_what-is-transfer-learning.srt create mode 100644 subtitles/zh-CN/04_the-transformer-architecture.srt create mode 100644 subtitles/zh-CN/05_transformer-models-encoders.srt create mode 100644 subtitles/zh-CN/06_transformer-models-decoders.srt create mode 100644 subtitles/zh-CN/07_transformer-models-encoder-decoders.srt create mode 100644 subtitles/zh-CN/08_what-happens-inside-the-pipeline-function-(pytorch).srt create mode 100644 subtitles/zh-CN/09_what-happens-inside-the-pipeline-function-(tensorflow).srt create mode 100644 subtitles/zh-CN/10_instantiate-a-transformers-model-(pytorch).srt create mode 100644 subtitles/zh-CN/11_instantiate-a-transformers-model-(tensorflow).srt create mode 100644 subtitles/zh-CN/12_tokenizers-overview.srt create mode 100644 subtitles/zh-CN/13_word-based-tokenizers.srt create mode 100644 subtitles/zh-CN/14_character-based-tokenizers.srt create mode 100644 subtitles/zh-CN/15_subword-based-tokenizers.srt create mode 100644 subtitles/zh-CN/16_the-tokenization-pipeline.srt create mode 100644 subtitles/zh-CN/17_batching-inputs-together-(pytorch).srt create mode 100644 subtitles/zh-CN/18_batching-inputs-together-(tensorflow).srt create mode 100644 subtitles/zh-CN/19_hugging-face-datasets-overview-(pytorch).srt create mode 100644 subtitles/zh-CN/20_hugging-face-datasets-overview-(tensorflow).srt create mode 100644 subtitles/zh-CN/21_preprocessing-sentence-pairs-(pytorch).srt create mode 100644 subtitles/zh-CN/22_preprocessing-sentence-pairs-(tensorflow).srt create mode 100644 subtitles/zh-CN/23_what-is-dynamic-padding.srt create mode 100644 subtitles/zh-CN/24_the-trainer-api.srt create mode 100644 subtitles/zh-CN/25_keras-introduction.srt create mode 100644 subtitles/zh-CN/26_fine-tuning-with-tensorflow.srt create mode 100644 subtitles/zh-CN/27_learning-rate-scheduling-with-tensorflow.srt create mode 100644 subtitles/zh-CN/28_tensorflow-predictions-and-metrics.srt create mode 100644 subtitles/zh-CN/29_write-your-training-loop-in-pytorch.srt create mode 100644 subtitles/zh-CN/30_supercharge-your-pytorch-training-loop-with-accelerate.srt create mode 100644 subtitles/zh-CN/31_navigating-the-model-hub.srt create mode 100644 subtitles/zh-CN/32_managing-a-repo-on-the-model-hub.srt create mode 100644 subtitles/zh-CN/33_the-push-to-hub-api-(pytorch).srt create mode 100644 subtitles/zh-CN/34_the-push-to-hub-api-(tensorflow).srt create mode 100644 subtitles/zh-CN/35_loading-a-custom-dataset.srt create mode 100644 "subtitles/zh-CN/36_slice-and-dice-a-dataset-\360\237\224\252.srt" create mode 100644 "subtitles/zh-CN/37_datasets-+-dataframes-=-\342\235\244\357\270\217.srt" create mode 100644 subtitles/zh-CN/38_saving-and-reloading-a-dataset.srt create mode 100644 subtitles/zh-CN/39_memory-mapping-&-streaming.srt create mode 100644 subtitles/zh-CN/40_uploading-a-dataset-to-the-hub.srt create mode 100644 subtitles/zh-CN/41_text-embeddings-&-semantic-search.srt create mode 100644 subtitles/zh-CN/42_training-a-new-tokenizer.srt create mode 100644 subtitles/zh-CN/43_why-are-fast-tokenizers-called-fast.srt create mode 100644 subtitles/zh-CN/44_fast-tokenizer-superpowers.srt create mode 100644 subtitles/zh-CN/45_inside-the-token-classification-pipeline-(pytorch).srt create mode 100644 subtitles/zh-CN/46_inside-the-token-classification-pipeline-(tensorflow).srt create mode 100644 subtitles/zh-CN/47_inside-the-question-answering-pipeline-(pytorch).srt create mode 100644 subtitles/zh-CN/48_inside-the-question-answering-pipeline-(tensorflow).srt create mode 100644 subtitles/zh-CN/49_what-is-normalization.srt create mode 100644 subtitles/zh-CN/50_what-is-pre-tokenization.srt create mode 100644 subtitles/zh-CN/51_byte-pair-encoding-tokenization.srt create mode 100644 subtitles/zh-CN/52_wordpiece-tokenization.srt create mode 100644 subtitles/zh-CN/53_unigram-tokenization.srt create mode 100644 subtitles/zh-CN/54_building-a-new-tokenizer.srt create mode 100644 subtitles/zh-CN/55_data-processing-for-token-classification.srt create mode 100644 subtitles/zh-CN/56_data-processing-for-masked-language-modeling.srt create mode 100644 subtitles/zh-CN/57_what-is-perplexity.srt create mode 100644 subtitles/zh-CN/58_what-is-domain-adaptation.srt create mode 100644 subtitles/zh-CN/59_data-processing-for-translation.srt create mode 100644 subtitles/zh-CN/60_what-is-the-bleu-metric.srt create mode 100644 subtitles/zh-CN/61_data-processing-for-summarization.srt create mode 100644 subtitles/zh-CN/62_what-is-the-rouge-metric.srt create mode 100644 subtitles/zh-CN/63_data-processing-for-causal-language-modeling.srt create mode 100644 subtitles/zh-CN/64_using-a-custom-loss-function.srt create mode 100644 subtitles/zh-CN/65_data-processing-for-question-answering.srt create mode 100644 subtitles/zh-CN/66_the-post-processing-step-in-question-answering-(pytorch).srt create mode 100644 subtitles/zh-CN/67_the-post-processing-step-in-question-answering-(tensorflow).srt create mode 100644 subtitles/zh-CN/68_data-collators-a-tour.srt create mode 100644 subtitles/zh-CN/69_what-to-do-when-you-get-an-error.srt create mode 100644 subtitles/zh-CN/70_using-a-debugger-in-a-notebook.srt create mode 100644 subtitles/zh-CN/71_using-a-debugger-in-a-terminal.srt create mode 100644 subtitles/zh-CN/72_asking-for-help-on-the-forums.srt create mode 100644 subtitles/zh-CN/73_debugging-the-training-pipeline-(pytorch).srt create mode 100644 subtitles/zh-CN/74_debugging-the-training-pipeline-(tensorflow).srt create mode 100644 subtitles/zh-CN/75_writing-a-good-issue.srt create mode 100644 subtitles/zh-CN/metadata.csv create mode 100644 utils/generate_subtitles.py diff --git a/chapters/fr/chapter1/1.mdx b/chapters/fr/chapter1/1.mdx index 6ee2949cd..1770ffe0e 100644 --- a/chapters/fr/chapter1/1.mdx +++ b/chapters/fr/chapter1/1.mdx @@ -66,7 +66,7 @@ Actuellement, nous n'avons pas de certification pour ce cours. Cependant, nous t Chaque chapitre de ce cours est conçu pour être complété en une semaine, avec environ 6 à 8 heures de travail par semaine. Cependant, vous pouvez prendre tout le temps nécessaire pour le suivre. - **Où puis-je poser une question si j'en ai une ?** -Si vous avez une question sur l'une des sections du cours, il vous suffit de cliquer sur la bannière « *Ask a question* » en haut de la page pour être automatiquement redirigé vers la bonne section du [forum d’*Hugging Face*] (https://discuss.huggingface.co/) : +Si vous avez une question sur l'une des sections du cours, il vous suffit de cliquer sur la bannière « *Ask a question* » en haut de la page pour être automatiquement redirigé vers la bonne section du [forum d’*Hugging Face*](https://discuss.huggingface.co/) : Link to the Hugging Face forums diff --git a/chapters/fr/chapter1/3.mdx b/chapters/fr/chapter1/3.mdx index 5ba235a27..810112fe2 100644 --- a/chapters/fr/chapter1/3.mdx +++ b/chapters/fr/chapter1/3.mdx @@ -3,8 +3,10 @@ Dans cette section, nous allons voir ce que peuvent faire les *transformers* et utiliser notre premier outil de la bibliothèque 🤗 *Transformers* : la fonction `pipeline()`. diff --git a/chapters/fr/chapter1/8.mdx b/chapters/fr/chapter1/8.mdx index cd9852330..44404a846 100644 --- a/chapters/fr/chapter1/8.mdx +++ b/chapters/fr/chapter1/8.mdx @@ -3,8 +3,10 @@ Si vous souhaitez utiliser un modèle pré-entraîné ou une version *finetunée* de celui-ci en production, il est important d'avoir conscience que, bien que ces modèles soient puissants, ils ont des limites. La plus importante de ces limitations est que, pour permettre le pré-entraînement des modèles sur de grandes quantités de données, les chercheurs récupèrent souvent tout le contenu qu'ils peuvent trouver et donc en prenant le meilleur et le pire de ce qui est disponible sur internet. diff --git a/chapters/fr/chapter2/2.mdx b/chapters/fr/chapter2/2.mdx index 188008f9a..1336799d4 100644 --- a/chapters/fr/chapter2/2.mdx +++ b/chapters/fr/chapter2/2.mdx @@ -7,8 +7,10 @@ {:else} @@ -16,8 +18,10 @@ {/if} diff --git a/chapters/fr/chapter2/3.mdx b/chapters/fr/chapter2/3.mdx index a0d6cf79e..c6dc2af76 100644 --- a/chapters/fr/chapter2/3.mdx +++ b/chapters/fr/chapter2/3.mdx @@ -7,8 +7,10 @@ {:else} @@ -16,8 +18,10 @@ {/if} diff --git a/chapters/fr/chapter2/4.mdx b/chapters/fr/chapter2/4.mdx index e8cef472e..bf36f6268 100644 --- a/chapters/fr/chapter2/4.mdx +++ b/chapters/fr/chapter2/4.mdx @@ -7,8 +7,10 @@ {:else} @@ -16,8 +18,10 @@ {/if} diff --git a/chapters/fr/chapter2/5.mdx b/chapters/fr/chapter2/5.mdx index 44124664b..9e3cff91e 100644 --- a/chapters/fr/chapter2/5.mdx +++ b/chapters/fr/chapter2/5.mdx @@ -7,8 +7,10 @@ {:else} @@ -16,8 +18,10 @@ {/if} diff --git a/chapters/fr/chapter2/6.mdx b/chapters/fr/chapter2/6.mdx index ad9ef9fdf..f7ca7a361 100644 --- a/chapters/fr/chapter2/6.mdx +++ b/chapters/fr/chapter2/6.mdx @@ -7,8 +7,10 @@ {:else} @@ -16,8 +18,10 @@ {/if} diff --git a/chapters/fr/chapter3/2.mdx b/chapters/fr/chapter3/2.mdx index ad6ef83fb..6e36b38df 100644 --- a/chapters/fr/chapter3/2.mdx +++ b/chapters/fr/chapter3/2.mdx @@ -4,11 +4,14 @@ {#if fw === 'pt'} + {:else} @@ -16,8 +19,10 @@ {/if} diff --git a/chapters/fr/chapter3/3.mdx b/chapters/fr/chapter3/3.mdx index f8f83360b..ba0061e7f 100644 --- a/chapters/fr/chapter3/3.mdx +++ b/chapters/fr/chapter3/3.mdx @@ -5,8 +5,10 @@ diff --git a/chapters/fr/chapter3/3_tf.mdx b/chapters/fr/chapter3/3_tf.mdx index f65f80470..41755c3cd 100644 --- a/chapters/fr/chapter3/3_tf.mdx +++ b/chapters/fr/chapter3/3_tf.mdx @@ -5,8 +5,10 @@ Une fois que vous avez fait tout le travail de prétraitement des données dans la dernière section, il ne vous reste que quelques étapes pour entraîner le modèle. Notez, cependant, que la commande `model.fit()` s'exécutera très lentement sur un CPU. Si vous n'avez pas de GPU, vous pouvez avoir accès à des GPUs ou TPUs gratuits sur [Google Colab](https://colab.research.google.com/). diff --git a/chapters/fr/chapter3/4.mdx b/chapters/fr/chapter3/4.mdx index 7f9a74724..96fce3610 100644 --- a/chapters/fr/chapter3/4.mdx +++ b/chapters/fr/chapter3/4.mdx @@ -3,8 +3,10 @@ diff --git a/chapters/fr/chapter4/2.mdx b/chapters/fr/chapter4/2.mdx index 7370f5273..0990c4663 100644 --- a/chapters/fr/chapter4/2.mdx +++ b/chapters/fr/chapter4/2.mdx @@ -7,17 +7,20 @@ {:else} - {/if} diff --git a/chapters/fr/chapter4/3.mdx b/chapters/fr/chapter4/3.mdx index ddff04740..f349d7d9d 100644 --- a/chapters/fr/chapter4/3.mdx +++ b/chapters/fr/chapter4/3.mdx @@ -7,17 +7,19 @@ - {:else} - {/if} diff --git a/chapters/fr/chapter5/2.mdx b/chapters/fr/chapter5/2.mdx index 7c27246fa..86a218a7c 100644 --- a/chapters/fr/chapter5/2.mdx +++ b/chapters/fr/chapter5/2.mdx @@ -3,8 +3,10 @@ Vous savez comment utiliser le [*Hub*](https://huggingface.co/datasets) pour télécharger des jeux de données mais en pratique vous vous retrouverez souvent à travailler avec des données stockées sur votre ordinateur portable ou sur un serveur distant. Dans cette section, nous allons vous montrer comment 🤗 *Datasets* peut être utilisé pour charger des jeux de données qui ne sont pas disponibles sur le *Hub* d’Hugging Face. diff --git a/chapters/fr/chapter5/3.mdx b/chapters/fr/chapter5/3.mdx index def2774c7..271396fcb 100644 --- a/chapters/fr/chapter5/3.mdx +++ b/chapters/fr/chapter5/3.mdx @@ -1,10 +1,13 @@ # Il est temps de trancher et de découper + La plupart du temps, les données avec lesquelles vous travaillez ne sont pas parfaitement préparées pour l’entraînements de modèles. Dans cette section, nous allons explorer les différentes fonctionnalités fournies par 🤗 *Datasets* pour nettoyer vos jeux de données. diff --git a/chapters/fr/chapter5/4.mdx b/chapters/fr/chapter5/4.mdx index 1c843b4e8..1fdc67418 100644 --- a/chapters/fr/chapter5/4.mdx +++ b/chapters/fr/chapter5/4.mdx @@ -1,13 +1,15 @@ # Données massives ? 🤗 Datasets à la rescousse ! + - De nos jours, il n'est pas rare de travailler avec des jeux de données de plusieurs gigaoctets surtout si vous envisagez de pré-entraîner un *transformer* comme BERT ou GPT-2 à partir de zéro. Dans ces cas, même _charger_ les données peut être un défi. Par exemple, le corpus WebText utilisé pour pré-entraîner GPT-2 se compose de plus de 8 millions de documents et de 40 Go de texte. Le charger dans la RAM de votre ordinateur portable est susceptible de lui donner une crise cardiaque ! Heureusement, 🤗 *Datasets* a été conçu pour surmonter ces limitations. Il vous libère des problèmes de gestion de la mémoire en traitant les jeux de données comme des fichiers _mappés en mémoire_, ainsi que des limites du disque dur en faisant du _streaming_ sur les entrées dans un corpus. diff --git a/chapters/fr/chapter5/5.mdx b/chapters/fr/chapter5/5.mdx index 36112a4a3..bd72c6ff8 100644 --- a/chapters/fr/chapter5/5.mdx +++ b/chapters/fr/chapter5/5.mdx @@ -3,10 +3,13 @@ + Parfois, le jeu de données dont vous avez besoin pour créer une application de NLP n'existe pas. Vous devrez donc le créer vous-même. Dans cette section, nous allons vous montrer comment créer un corpus de [problèmes GitHub](https://github.com/features/issues/), qui sont couramment utilisés pour suivre les bogues ou les fonctionnalités dans les dépôts GitHub. Ce corpus pourrait être utilisé à diverses fins, notamment : * explorer combien de temps il faut pour fermer les problèmes ouverts ou les *pull requests* diff --git a/chapters/fr/chapter5/6.mdx b/chapters/fr/chapter5/6.mdx index a59966d3d..610af1078 100644 --- a/chapters/fr/chapter5/6.mdx +++ b/chapters/fr/chapter5/6.mdx @@ -3,12 +3,13 @@ # Recherche sémantique avec FAISS {#if fw === 'pt'} - {:else} @@ -16,8 +17,10 @@ {/if} diff --git a/chapters/fr/chapter6/2.mdx b/chapters/fr/chapter6/2.mdx index 8b01f18f3..b77452523 100644 --- a/chapters/fr/chapter6/2.mdx +++ b/chapters/fr/chapter6/2.mdx @@ -3,8 +3,10 @@ Si un modèle de langue n'est pas disponible dans la langue qui vous intéresse ou si votre corpus est très différent de celui sur lequel votre modèle de langue a été entraîné, vous voudrez très probablement réentraîner le modèle à partir de zéro en utilisant un *tokenizer* adapté à vos données. Pour ce faire, vous devrez entraîner un nouveau *tokenizer* sur votre jeu de données. Mais qu'est-ce que cela signifie exactement ? Lorsque nous avons examiné pour la première fois les *tokenizers* dans le [chapitre 2](/course/fr/chapter2), nous avons vu que la plupart des *transformers* utilisent un _algorithme de tokenisation en sous-mots_. Pour identifier les sous-mots qui sont intéressants et qui apparaissent le plus fréquemment dans un corpus donné, le *tokenizer* doit examiner attentivement tous les textes du corpus. C'est un processus que nous appelons *entraînement*. Les règles exactes qui régissent cet apprentissage dépendent du type de *tokenizer* utilisé. Nous passerons en revue les trois principaux algorithmes plus loin dans ce chapitre. @@ -262,4 +264,4 @@ Cela créera un nouveau dépôt dans votre espace avec le nom `code-search-net-t tokenizer = AutoTokenizer.from_pretrained("huggingface-course/code-search-net-tokenizer") ``` -Vous êtes maintenant prêt à entraîner un modèle de langue à partir de zéro et à le *finetuner* sur votre tâche ! Nous verrons cela dans le [chapitre 7](/course/fr/chapter7), mais d'abord, dans le reste de ce chapitre, nous allons examiner de plus près les *tokenizers* rapides et explorer en détail ce qui se passe lorsque nous appelons la méthode `train_new_from_iterator()`. \ No newline at end of file +Vous êtes maintenant prêt à entraîner un modèle de langue à partir de zéro et à le *finetuner* sur votre tâche ! Nous verrons cela dans le [chapitre 7](/course/fr/chapter7), mais d'abord, dans le reste de ce chapitre, nous allons examiner de plus près les *tokenizers* rapides et explorer en détail ce qui se passe lorsque nous appelons la méthode `train_new_from_iterator()`. diff --git a/chapters/fr/chapter6/3.mdx b/chapters/fr/chapter6/3.mdx index 732346fbc..554d3705f 100644 --- a/chapters/fr/chapter6/3.mdx +++ b/chapters/fr/chapter6/3.mdx @@ -7,17 +7,20 @@ {:else} - {/if} diff --git a/chapters/fr/chapter6/3b.mdx b/chapters/fr/chapter6/3b.mdx index 06fd361c6..6e670483f 100644 --- a/chapters/fr/chapter6/3b.mdx +++ b/chapters/fr/chapter6/3b.mdx @@ -7,17 +7,20 @@ {:else} - {/if} @@ -717,4 +720,4 @@ Si nous ignorons le premier résultat, nous obtenons le même résultat que notr -Ceci conclut notre plongée en profondeur dans les capacités du *tokenizer*. Nous mettrons à nouveau tout cela en pratique dans le prochain chapitre, lorsque nous vous montrerons comment *finetuner* un modèle sur une série de tâches NLP courantes. \ No newline at end of file +Ceci conclut notre plongée en profondeur dans les capacités du *tokenizer*. Nous mettrons à nouveau tout cela en pratique dans le prochain chapitre, lorsque nous vous montrerons comment *finetuner* un modèle sur une série de tâches NLP courantes. diff --git a/chapters/fr/chapter6/4.mdx b/chapters/fr/chapter6/4.mdx index 37b3808e8..968c6578b 100644 --- a/chapters/fr/chapter6/4.mdx +++ b/chapters/fr/chapter6/4.mdx @@ -3,8 +3,10 @@ Avant de nous plonger plus profondément dans les trois algorithmes de tokénisation en sous-mots les plus courants utilisés avec les *transformers* (*Byte-Pair Encoding* (BPE), *WordPiece* et *Unigram*), nous allons d'abord examiner le prétraitement que chaque *tokenizer* applique au texte. Voici un aperçu de haut niveau des étapes du pipeline de tokenisation : diff --git a/chapters/fr/chapter6/5.mdx b/chapters/fr/chapter6/5.mdx index 3e81d81c6..083a8fd8a 100644 --- a/chapters/fr/chapter6/5.mdx +++ b/chapters/fr/chapter6/5.mdx @@ -3,8 +3,10 @@ Le *Byte-Pair Encoding* (BPE) a été initialement développé en tant qu'algorithme de compression de textes puis utilisé par OpenAI pour la tokenisation du pré-entraînement du modèle GPT. Il est utilisé par de nombreux *transformers* dont GPT, GPT-2, RoBERTa, BART et DeBERTa. @@ -361,4 +363,4 @@ tokenize("This is not a token.") -C'est tout pour l'algorithme BPE ! Nous allons nous intéresser à WordPiece dans la suite. \ No newline at end of file +C'est tout pour l'algorithme BPE ! Nous allons nous intéresser à WordPiece dans la suite. diff --git a/chapters/fr/chapter6/6.mdx b/chapters/fr/chapter6/6.mdx index c41f28dfa..0e5516588 100644 --- a/chapters/fr/chapter6/6.mdx +++ b/chapters/fr/chapter6/6.mdx @@ -3,8 +3,10 @@ *WordPiece* est l'algorithme de tokénisation développé par Google pour prétraîner BERT. Il a depuis été réutilisé dans un grand nombre de modèles de *transformers* basés sur BERT tels que DistilBERT, MobileBERT, Funnel Transformers et MPNET. Il est très similaire au BPE en termes d'entraînement mais la tokenisation réelle est effectuée différemment. diff --git a/chapters/fr/chapter6/7.mdx b/chapters/fr/chapter6/7.mdx index 90615b887..2240cc023 100644 --- a/chapters/fr/chapter6/7.mdx +++ b/chapters/fr/chapter6/7.mdx @@ -3,8 +3,10 @@ L'algorithme *Unigram* est souvent utilisé dans *SentencePiece*, qui est l'algorithme de tokenization utilisé par des modèles comme ALBERT, T5, mBART, Big Bird et XLNet. diff --git a/chapters/fr/chapter6/8.mdx b/chapters/fr/chapter6/8.mdx index c1095bfd0..f8a740502 100644 --- a/chapters/fr/chapter6/8.mdx +++ b/chapters/fr/chapter6/8.mdx @@ -1,10 +1,13 @@ # Construction d'un tokenizer, bloc par bloc + Comme nous l'avons vu dans les sections précédentes, la tokenisation comprend plusieurs étapes : diff --git a/chapters/fr/chapter7/2.mdx b/chapters/fr/chapter7/2.mdx index bd7f1c0a6..67bd0297e 100644 --- a/chapters/fr/chapter7/2.mdx +++ b/chapters/fr/chapter7/2.mdx @@ -3,12 +3,13 @@ # Classification de tokens {#if fw === 'pt'} - {:else} @@ -16,8 +17,10 @@ {/if} diff --git a/chapters/fr/chapter7/3.mdx b/chapters/fr/chapter7/3.mdx index 87d31f385..32b1fbf44 100644 --- a/chapters/fr/chapter7/3.mdx +++ b/chapters/fr/chapter7/3.mdx @@ -3,12 +3,13 @@ # Finetuner un modèle de langage masqué {#if fw === 'pt'} - {:else} @@ -16,8 +17,10 @@ {/if} diff --git a/chapters/fr/chapter7/4.mdx b/chapters/fr/chapter7/4.mdx index 236194972..6f62da3f5 100644 --- a/chapters/fr/chapter7/4.mdx +++ b/chapters/fr/chapter7/4.mdx @@ -3,12 +3,13 @@ # Traduction {#if fw === 'pt'} - {:else} @@ -16,8 +17,10 @@ {/if} diff --git a/chapters/fr/chapter7/5.mdx b/chapters/fr/chapter7/5.mdx index cb24bcd28..0a963e8cb 100644 --- a/chapters/fr/chapter7/5.mdx +++ b/chapters/fr/chapter7/5.mdx @@ -3,12 +3,13 @@ # Résumé de textes {#if fw === 'pt'} - {:else} @@ -16,8 +17,10 @@ {/if} diff --git a/chapters/fr/chapter7/6.mdx b/chapters/fr/chapter7/6.mdx index f73f51eea..e8dd9a3cd 100644 --- a/chapters/fr/chapter7/6.mdx +++ b/chapters/fr/chapter7/6.mdx @@ -3,12 +3,13 @@ # Entraîner un modèle de langage causal à partir de zéro {#if fw === 'pt'} - {:else} @@ -16,8 +17,10 @@ {/if} diff --git a/chapters/fr/chapter8/2.mdx b/chapters/fr/chapter8/2.mdx index 15812fa71..df721a540 100644 --- a/chapters/fr/chapter8/2.mdx +++ b/chapters/fr/chapter8/2.mdx @@ -3,8 +3,10 @@ Dans cette section, nous allons examiner certaines erreurs courantes qui peuvent se produire lorsque vous essayez de générer des prédictions à partir de votre *transformer* fraîchement *finetuné*. Cela vous préparera pour la [section 4](/course/chapter8/fr/section4) de ce chapitre où nous explorerons comment déboguer la phase d'entraînement elle-même. diff --git a/chapters/fr/chapter8/3.mdx b/chapters/fr/chapter8/3.mdx index b2bf35a17..f4e9626fb 100644 --- a/chapters/fr/chapter8/3.mdx +++ b/chapters/fr/chapter8/3.mdx @@ -1,12 +1,16 @@ # Demander de l'aide sur les forums + + Le [forum d'Hugging Face](https://discuss.huggingface.co) est un endroit idéal pour obtenir de l'aide de l'équipe open source d'Hugging Face et de la communauté au sens large. Voici à quoi ressemble la page principale : diff --git a/chapters/fr/chapter8/4.mdx b/chapters/fr/chapter8/4.mdx index cae423fcd..fb770c1e2 100644 --- a/chapters/fr/chapter8/4.mdx +++ b/chapters/fr/chapter8/4.mdx @@ -5,8 +5,10 @@ Vous avez écrit un magnifique script pour entraîner ou *finetuner* un modèle sur une tâche donnée en suivant consciencieusement les conseils du [chapitre 7](/course/fr/chapter7). Mais lorsque vous lancez la commande `model.fit()`, quelque chose d'horrible se produit : vous obtenez une erreur 😱 ! Ou pire, tout semble aller bien et l'entraînement se déroule sans erreur mais le modèle résultant est mauvais. Dans cette section, nous allons vous montrer ce que vous pouvez faire pour déboguer ce genre de problèmes. diff --git a/chapters/fr/chapter8/4_tf.mdx b/chapters/fr/chapter8/4_tf.mdx index ea66f1334..e35e9aadd 100644 --- a/chapters/fr/chapter8/4_tf.mdx +++ b/chapters/fr/chapter8/4_tf.mdx @@ -2,13 +2,17 @@ # Débogage du pipeline d'entraînement + + Vous avez écrit un magnifique script pour entraîner ou *finetuner* un modèle sur une tâche donnée en suivant consciencieusement les conseils du [chapitre 7](/course/fr/chapter7). Mais lorsque vous lancez la commande `model.fit()`, quelque chose d'horrible se produit : vous obtenez une erreur 😱 ! Ou pire, tout semble aller bien et l'entraînement se déroule sans erreur mais le modèle résultant est mauvais. Dans cette section, nous allons vous montrer ce que vous pouvez faire pour déboguer ce genre de problèmes. ## Déboguer le pipeline d'entraînement diff --git a/chapters/fr/chapter8/5.mdx b/chapters/fr/chapter8/5.mdx index 5d72c010a..19be6c405 100644 --- a/chapters/fr/chapter8/5.mdx +++ b/chapters/fr/chapter8/5.mdx @@ -3,10 +3,13 @@ + Lorsque vous rencontrez un problème avec l'une des bibliothèques d'Hugging Face, faites le nous savoir afin que nous puissions le corriger (il en va de même pour toute bibliothèque open source).
Si vous n'êtes pas complètement certain que le *bug* se trouve dans votre propre code ou dans l'une de nos bibliothèques, le premier endroit à vérifier est le [forum](https://discuss.huggingface.co/). La communauté vous aidera à résoudre votre problème et l'équipe d'Hugging Face y suit de près les discussions qui s'y déroulent. diff --git a/chapters/fr/chapter9/2.mdx b/chapters/fr/chapter9/2.mdx index a25253a1a..8d2ba3e71 100644 --- a/chapters/fr/chapter9/2.mdx +++ b/chapters/fr/chapter9/2.mdx @@ -3,8 +3,10 @@ Commençons par installer *Gradio* ! Comme il s'agit d'un *package* Python, il suffit de l'exécuter : @@ -114,4 +116,4 @@ C'est fait ! Vous pouvez maintenant utiliser cette interface pour générer du t -Continuez votre lecture du cours pour voir comment construire d'autres types de démos avec *Gradio* ! \ No newline at end of file +Continuez votre lecture du cours pour voir comment construire d'autres types de démos avec *Gradio* ! diff --git a/chapters/fr/chapter9/3.mdx b/chapters/fr/chapter9/3.mdx index bede014c7..caabfd317 100644 --- a/chapters/fr/chapter9/3.mdx +++ b/chapters/fr/chapter9/3.mdx @@ -3,8 +3,10 @@ Dans cette section, nous allons examiner de plus près la classe `Interface`, et comprendre les principaux paramètres utilisés pour en créer une. diff --git a/chapters/fr/chapter9/4.mdx b/chapters/fr/chapter9/4.mdx index 53e237e76..ea9509a02 100644 --- a/chapters/fr/chapter9/4.mdx +++ b/chapters/fr/chapter9/4.mdx @@ -3,8 +3,10 @@ Maintenant que vous avez construit une démo, vous voudrez probablement la partager à d'autres personnes. Les démos *Gradio* peuvent être partagées de deux façons : en utilisant un lien de partage temporaire (***temporary share link***) ou un hébergement permanent (***permanent hosting on Spaces***). @@ -143,4 +145,4 @@ Remarquez le paramètre `live=True` dans `Interface`, qui signifie que la démo De plus, nous avons également défini l'argument `share=True` dans la méthode `launch()`. Cela créera un lien public que vous pourrez envoyer à n'importe qui ! Lorsque vous envoyez ce lien, l'utilisateur de l'autre côté peut essayer le modèle de reconnaissance de croquis. Pour réitérer, vous pouvez également héberger le modèle sur *Hugging Face Spaces*, ce qui nous permet d'intégrer la démo ci-dessus. -La prochaine fois, nous couvrirons d'autres façons dont *Gradio* peut être utilisé avec l'écosystème d'*Hugging Face* ! \ No newline at end of file +La prochaine fois, nous couvrirons d'autres façons dont *Gradio* peut être utilisé avec l'écosystème d'*Hugging Face* ! diff --git a/chapters/fr/chapter9/5.mdx b/chapters/fr/chapter9/5.mdx index 228eee18f..81a91e9d5 100644 --- a/chapters/fr/chapter9/5.mdx +++ b/chapters/fr/chapter9/5.mdx @@ -3,8 +3,10 @@ Pour vous rendre la vie encore plus facile, *Gradio* s'intègre directement avec *Hub* et *Spaces*. @@ -72,4 +74,4 @@ gr.Interface.load( -Maintenant que nous avons exploré quelques façons d'intégrer *Gradio* avec le *Hub*, jetons un coup d'oeil à certaines fonctionnalités avancées de la classe `Interface`. C'est le sujet de la prochaine section ! \ No newline at end of file +Maintenant que nous avons exploré quelques façons d'intégrer *Gradio* avec le *Hub*, jetons un coup d'oeil à certaines fonctionnalités avancées de la classe `Interface`. C'est le sujet de la prochaine section ! diff --git a/chapters/fr/chapter9/6.mdx b/chapters/fr/chapter9/6.mdx index 8c89be5e9..49b7c78f1 100644 --- a/chapters/fr/chapter9/6.mdx +++ b/chapters/fr/chapter9/6.mdx @@ -3,8 +3,10 @@ Maintenant que nous pouvons construire et partager une interface de base, explorons quelques fonctionnalités plus avancées comme l'état, l'interprétation et l'authentification. diff --git a/chapters/fr/chapter9/7.mdx b/chapters/fr/chapter9/7.mdx index 04a38803e..34395d9e7 100644 --- a/chapters/fr/chapter9/7.mdx +++ b/chapters/fr/chapter9/7.mdx @@ -3,8 +3,10 @@ Dans les sections précédentes, nous avons exploré et créé des démos en utilisant la classe `Interface`. Dans cette section, nous allons présenter une **nouvelle** API de bas niveau appelée `gradio.Blocks`. @@ -237,4 +239,4 @@ with gr.Blocks() as block: -Nous venons d'explorer tous les concepts de base des `Blocks` ! Tout comme avec `Interface`, vous pouvez créer des démos sympas qui peuvent être partagées en utilisant `share=True` dans la méthode `launch()` ou déployées sur [*Spaces*](https://huggingface.co/spaces). \ No newline at end of file +Nous venons d'explorer tous les concepts de base des `Blocks` ! Tout comme avec `Interface`, vous pouvez créer des démos sympas qui peuvent être partagées en utilisant `share=True` dans la méthode `launch()` ou déployées sur [*Spaces*](https://huggingface.co/spaces). diff --git a/subtitles/README.md b/subtitles/README.md new file mode 100644 index 000000000..071b13c63 --- /dev/null +++ b/subtitles/README.md @@ -0,0 +1,29 @@ +# Subtitles for the course videos + +This folder contains all the subtitles for the course videos on YouTube. + +## How to translate the subtitles + +To translate the subtitles, we'll use two nifty libraries that can (a) grab all the YouTube videos associated with the course playlist and (b) translate them on the fly. + +To get started, install the following: + +```bash +python -m pip install youtube_transcript_api youtube-search-python pandas tqdm +``` + +Next, run the following script: + +```bash +python utils/generate_subtitles.py --language LANG_CODE +``` + +where `LANG_CODE` is the same language ID used to denote the chosen language the `chapters` folder. If everything goes well, you should end up with a set of translated `.srt` files with timestamps in the `subtitles/LANG_CODE` folder along with some metadata in `metadata.csv`. + +Some languages like Simplified Chinese have a different YouTube language code (`zh-Hans`) to the one used in the course (`zh-CN`). For these languages, you also need to specify the YouTube language code, e.g.: + +```bash +python utils/generate_subtitles.py --language zh-CN --youtube_language_code zh-Hans +``` + +Once you have the `.srt` files you can manually fix any translation errors and then open a pull request with the new files. \ No newline at end of file diff --git a/subtitles/en/00_welcome-to-the-hugging-face-course.srt b/subtitles/en/00_welcome-to-the-hugging-face-course.srt new file mode 100644 index 000000000..ae8eb7042 --- /dev/null +++ b/subtitles/en/00_welcome-to-the-hugging-face-course.srt @@ -0,0 +1,455 @@ +1 +00:00:05,850 --> 00:00:07,713 +- Welcome to the Hugging Face Course. + +2 +00:00:08,550 --> 00:00:10,320 +This course has been designed to teach you + +3 +00:00:10,320 --> 00:00:12,750 +all about the Hugging Face ecosystem, + +4 +00:00:12,750 --> 00:00:14,700 +how to use the dataset and model hub + +5 +00:00:14,700 --> 00:00:16,803 +as well as all our open-source libraries. + +6 +00:00:18,300 --> 00:00:19,950 +Here is the Table of Contents. + +7 +00:00:19,950 --> 00:00:22,770 +As you can see, it's +divided in three sections + +8 +00:00:22,770 --> 00:00:25,110 +which become progressively more advanced. + +9 +00:00:25,110 --> 00:00:28,500 +At this stage, the first two +sections have been released. + +10 +00:00:28,500 --> 00:00:30,120 +So first, we'll teach you the basics + +11 +00:00:30,120 --> 00:00:32,250 +of how to use a Transformer model, + +12 +00:00:32,250 --> 00:00:34,230 +fine-tune it on your own data set + +13 +00:00:34,230 --> 00:00:36,960 +and share the result with the community. + +14 +00:00:36,960 --> 00:00:39,420 +So second, we'll dive +deeper into our libraries + +15 +00:00:39,420 --> 00:00:42,360 +and teach you how to tackle any NLP task. + +16 +00:00:42,360 --> 00:00:44,430 +We're actively working on the last one + +17 +00:00:44,430 --> 00:00:47,280 +and hope to have it ready for +you for the spring of 2022. + +18 +00:00:48,510 --> 00:00:50,880 +The first chapter requires +no technical knowledge + +19 +00:00:50,880 --> 00:00:52,320 +and is a good introduction to learn + +20 +00:00:52,320 --> 00:00:54,180 +what Transformers models can do + +21 +00:00:54,180 --> 00:00:56,883 +and how it could be of use +to you or your company. + +22 +00:00:58,050 --> 00:01:01,110 +The next chapters require +a good knowledge of Python + +23 +00:01:01,110 --> 00:01:02,130 +and some basic knowledge of + +24 +00:01:02,130 --> 00:01:04,350 +Machine Learning and Deep Learning. + +25 +00:01:04,350 --> 00:01:07,110 +If you don't know what a +training and validation set are + +26 +00:01:07,110 --> 00:01:09,360 +or what gradient decent means, + +27 +00:01:09,360 --> 00:01:11,340 +you should look at an introductory course + +28 +00:01:11,340 --> 00:01:14,863 +such as the ones published by +deeplearning.ai or fast.ai. + +29 +00:01:16,200 --> 00:01:17,910 +It's also best if you have some basics + +30 +00:01:17,910 --> 00:01:21,150 +in one Deep Learning Framework, +PyTorch or TensorFlow. + +31 +00:01:21,150 --> 00:01:23,520 +Each part of the material +introduced in this course + +32 +00:01:23,520 --> 00:01:25,590 +has a version in both those frameworks, + +33 +00:01:25,590 --> 00:01:26,730 +so you will be able to pick the one + +34 +00:01:26,730 --> 00:01:28,230 +you are most comfortable with. + +35 +00:01:29,550 --> 00:01:31,740 +This is the team that +developed this course. + +36 +00:01:31,740 --> 00:01:33,120 +I'll now let each of the speakers + +37 +00:01:33,120 --> 00:01:34,570 +introduce themselves briefly. + +38 +00:01:37,230 --> 00:01:38,880 +- Hi, my name is Matthew, + +39 +00:01:38,880 --> 00:01:41,610 +and I'm a Machine Learning +Engineer at Hugging Face. + +40 +00:01:41,610 --> 00:01:43,200 +I work on the open-source team + +41 +00:01:43,200 --> 00:01:45,180 +and I'm responsible for +maintaining particularly + +42 +00:01:45,180 --> 00:01:47,280 +the TensorFlow code there. + +43 +00:01:47,280 --> 00:01:50,130 +Previously, I was a Machine +Learning Engineer at Parsley, + +44 +00:01:50,130 --> 00:01:52,620 +who've recently been +acquired by Automatic, + +45 +00:01:52,620 --> 00:01:54,210 +and I was a postdoctoral researcher + +46 +00:01:54,210 --> 00:01:57,000 +before that at Trinity +College, Dublin in Ireland + +47 +00:01:57,000 --> 00:02:00,093 +working on computational +genetics and retinal disease. + +48 +00:02:02,400 --> 00:02:03,870 +- Hi, I'm Lysandre. + +49 +00:02:03,870 --> 00:02:05,640 +I'm a Machine Learning +Engineer at Hugging Face + +50 +00:02:05,640 --> 00:02:08,700 +and I'm specifically part +of the open-source team. + +51 +00:02:08,700 --> 00:02:10,890 +I've been at Hugging +Face for a few years now + +52 +00:02:10,890 --> 00:02:12,300 +and alongside my team members, + +53 +00:02:12,300 --> 00:02:13,890 +I've been working on most of the tools + +54 +00:02:13,890 --> 00:02:15,790 +that you'll get to see in this course. + +55 +00:02:18,270 --> 00:02:20,130 +- Hi, I'm Sylvain. + +56 +00:02:20,130 --> 00:02:22,140 +I'm a Research Engineer at Hugging Face + +57 +00:02:22,140 --> 00:02:25,830 +and one of the main maintainers +of the Transformers Library. + +58 +00:02:25,830 --> 00:02:28,110 +Previously, I worked at fast.ai + +59 +00:02:28,110 --> 00:02:30,420 +where I helped develop the fast.ai Library + +60 +00:02:30,420 --> 00:02:32,220 +as well as the online book. + +61 +00:02:32,220 --> 00:02:35,340 +Before that, I was a math +and computer science teacher + +62 +00:02:35,340 --> 00:02:36,173 +in France. + +63 +00:02:38,550 --> 00:02:41,340 +- Hi, my name is Sasha and I'm +a Researcher at Hugging Face, + +64 +00:02:41,340 --> 00:02:42,420 +working on the ethical, + +65 +00:02:42,420 --> 00:02:46,230 +environmental and social impacts +of machine learning models. + +66 +00:02:46,230 --> 00:02:49,020 +Previously, I was a +postdoctoral researcher at Mila, + +67 +00:02:49,020 --> 00:02:50,400 +University in Montreal + +68 +00:02:50,400 --> 00:02:53,040 +and I also worked as an +Applied AI Researcher + +69 +00:02:53,040 --> 00:02:55,140 +for the United Nations Global Pulse. + +70 +00:02:55,140 --> 00:02:57,300 +I've been involved in +projects such as CodeCarbon + +71 +00:02:57,300 --> 00:02:59,790 +and the Machine Learning +Impacts Calculator + +72 +00:02:59,790 --> 00:03:02,390 +to measure the carbon +footprint of machine learning. + +73 +00:03:05,160 --> 00:03:07,650 +- Hi, I'm Merve and I'm +a Developer Advocate + +74 +00:03:07,650 --> 00:03:09,390 +at Hugging Face. + +75 +00:03:09,390 --> 00:03:12,480 +Previously, I was working as +a Machine Learning Engineer + +76 +00:03:12,480 --> 00:03:15,360 +building NLP tools and chat bots. + +77 +00:03:15,360 --> 00:03:17,670 +Currently, I'm working to improve the hub + +78 +00:03:17,670 --> 00:03:19,563 +and democratize machine learning. + +79 +00:03:22,140 --> 00:03:23,670 +- Hello everyone. + +80 +00:03:23,670 --> 00:03:27,210 +My name is Lucile and I'm +a Machine Learning Engineer + +81 +00:03:27,210 --> 00:03:28,353 +at Hugging Face. + +82 +00:03:29,580 --> 00:03:32,550 +To tell you in two sentences who I am, + +83 +00:03:32,550 --> 00:03:35,590 +I work on the development and +support of open-source tools + +84 +00:03:36,600 --> 00:03:39,595 +and I also participate in +several research project + +85 +00:03:39,595 --> 00:03:41,795 +in the field of Natural +Language Processing. + +86 +00:03:44,610 --> 00:03:45,540 +- Good day there. + +87 +00:03:45,540 --> 00:03:47,550 +I'm Lewis and I'm a +Machine Learning Engineer + +88 +00:03:47,550 --> 00:03:50,130 +in the open-source team at Hugging Face. + +89 +00:03:50,130 --> 00:03:53,490 +I'm passionate about developing +tools for the NLP community + +90 +00:03:53,490 --> 00:03:55,050 +and you'll see me at +many of Hugging Face's + +91 +00:03:55,050 --> 00:03:56,910 +outreach activities. + +92 +00:03:56,910 --> 00:03:58,470 +Before joining Hugging Face, + +93 +00:03:58,470 --> 00:03:59,790 +I spent several years developing + +94 +00:03:59,790 --> 00:04:01,860 +machine learning applications for startups + +95 +00:04:01,860 --> 00:04:04,230 +and enterprises in the domains of NLP, + +96 +00:04:04,230 --> 00:04:07,260 +topological data analysis and time series. + +97 +00:04:07,260 --> 00:04:10,110 +In a former life, I was +a theoretical physicist, + +98 +00:04:10,110 --> 00:04:11,760 +where I researched particle collisions + +99 +00:04:11,760 --> 00:04:13,560 +at the Large Hadron Collider and so. + +100 +00:04:15,900 --> 00:04:18,450 +- Hey, I'm Leandro and I'm +a Machine Learning Engineer + +101 +00:04:18,450 --> 00:04:21,030 +in the open-source team at Hugging Face. + +102 +00:04:21,030 --> 00:04:23,460 +Before joining Hugging Face, +I worked as a Data Scientist + +103 +00:04:23,460 --> 00:04:26,733 +in Switzerland and have taught +Data Science at University. + diff --git a/subtitles/en/01_the-pipeline-function.srt b/subtitles/en/01_the-pipeline-function.srt new file mode 100644 index 000000000..44c3bf8bb --- /dev/null +++ b/subtitles/en/01_the-pipeline-function.srt @@ -0,0 +1,445 @@ +1 +00:00:00,069 --> 00:00:01,341 +(screen whooshes) + +2 +00:00:01,341 --> 00:00:02,449 +(face logo whooshes) + +3 +00:00:02,449 --> 00:00:05,880 +(screen whooshes) + +4 +00:00:05,880 --> 00:00:07,080 +- The pipeline function. + +5 +00:00:09,540 --> 00:00:12,020 +The pipeline function is +the most high level API + +6 +00:00:12,020 --> 00:00:14,010 +of the Transformers library. + +7 +00:00:14,010 --> 00:00:16,050 +It regroups together all the steps + +8 +00:00:16,050 --> 00:00:18,873 +to go from raw texts +to usable predictions. + +9 +00:00:20,228 --> 00:00:22,980 +The model used is at +the core of a pipeline, + +10 +00:00:22,980 --> 00:00:24,390 +but the pipeline also include + +11 +00:00:24,390 --> 00:00:26,610 +all the necessary pre-processing, + +12 +00:00:26,610 --> 00:00:30,240 +since the model does not +expect texts, but number, + +13 +00:00:30,240 --> 00:00:32,040 +as well as some post-processing, + +14 +00:00:32,040 --> 00:00:34,533 +to make the output of +the model human-readable. + +15 +00:00:35,910 --> 00:00:37,593 +Let's look at a first example + +16 +00:00:37,593 --> 00:00:39,693 +with the sentiment analysis pipeline. + +17 +00:00:40,740 --> 00:00:44,670 +This pipeline performs text +classification on a given input + +18 +00:00:44,670 --> 00:00:46,953 +and determines if it's +positive or negative. + +19 +00:00:47,910 --> 00:00:51,750 +Here, it attributed the positive +label on the given text, + +20 +00:00:51,750 --> 00:00:54,413 +with a confidence of 95%. + +21 +00:00:55,650 --> 00:00:58,470 +You can pass multiple +texts to the same pipeline, + +22 +00:00:58,470 --> 00:01:00,270 +which will be processed and passed + +23 +00:01:00,270 --> 00:01:02,673 +through the model together as a batch. + +24 +00:01:03,570 --> 00:01:05,970 +The output is a list of individual results + +25 +00:01:05,970 --> 00:01:07,923 +in the same order as the input texts. + +26 +00:01:08,790 --> 00:01:12,270 +Here we find the same label +and score for the first text, + +27 +00:01:12,270 --> 00:01:14,443 +and the second text is judged negative + +28 +00:01:14,443 --> 00:01:17,243 +with a confidence of 99.9%. + +29 +00:01:18,720 --> 00:01:20,700 +The zero-shot classification pipeline + +30 +00:01:20,700 --> 00:01:23,610 +is a more general +text-classification pipeline, + +31 +00:01:23,610 --> 00:01:26,370 +it allows you to provide +the labels you want. + +32 +00:01:26,370 --> 00:01:29,850 +Here we want to classify our +input text along the labels, + +33 +00:01:29,850 --> 00:01:32,643 +education, politics, and business. + +34 +00:01:33,540 --> 00:01:35,580 +The pipeline successfully recognizes + +35 +00:01:35,580 --> 00:01:38,280 +it's more about education +than the other labels, + +36 +00:01:38,280 --> 00:01:40,643 +with a confidence of 84%. + +37 +00:01:41,670 --> 00:01:43,110 +Moving on to other tasks, + +38 +00:01:43,110 --> 00:01:45,030 +the text generation pipeline will + +39 +00:01:45,030 --> 00:01:46,533 +auto-complete a given prompt. + +40 +00:01:47,460 --> 00:01:49,980 +The output is generated +with a bit of randomness, + +41 +00:01:49,980 --> 00:01:52,800 +so it changes each time you +call the generator object + +42 +00:01:52,800 --> 00:01:53,763 +on a given prompt. + +43 +00:01:54,990 --> 00:01:57,123 +Up until now, we've used +the the pipeline API + +44 +00:01:57,123 --> 00:02:00,360 +with the default model +associated to each task, + +45 +00:02:00,360 --> 00:02:02,880 +but you can use it with any +model that has been pretrained + +46 +00:02:02,880 --> 00:02:04,263 +or fine-tuned on this task. + +47 +00:02:06,540 --> 00:02:10,350 +Going on the model hub, +huggingface.co/models + +48 +00:02:10,350 --> 00:02:13,350 +you can filter the +available models by task. + +49 +00:02:13,350 --> 00:02:17,190 +The default model used in our +previous example was gpt2, + +50 +00:02:17,190 --> 00:02:19,290 +but there are many more models available, + +51 +00:02:19,290 --> 00:02:20,523 +and not just in English. + +52 +00:02:21,450 --> 00:02:23,670 +Let's go back to the +text generation pipeline + +53 +00:02:23,670 --> 00:02:26,193 +and load it with another +model, distilgpt2. + +54 +00:02:27,060 --> 00:02:28,950 +This is a lighter version of gpt2 + +55 +00:02:28,950 --> 00:02:30,603 +created by the Hugging Face team. + +56 +00:02:31,740 --> 00:02:34,110 +When applying the pipeline +to a given prompt, + +57 +00:02:34,110 --> 00:02:36,360 +we can specify several arguments + +58 +00:02:36,360 --> 00:02:39,240 +such as the maximum length +of the generated texts, + +59 +00:02:39,240 --> 00:02:41,700 +or the number of sentences +we want to return, + +60 +00:02:41,700 --> 00:02:44,150 +since there is some +randomness in the generation. + +61 +00:02:46,080 --> 00:02:48,750 +Generating texts by guessing +the next word in a sentence + +62 +00:02:48,750 --> 00:02:51,450 +was the pretraining objective of GPT-2. + +63 +00:02:51,450 --> 00:02:55,140 +The fill mask pipeline is the +pretraining objective of BERT, + +64 +00:02:55,140 --> 00:02:57,363 +which is to guess the +value of masked word. + +65 +00:02:58,260 --> 00:03:01,020 +In this case, we ask the +two most likely values + +66 +00:03:01,020 --> 00:03:03,660 +for the missing words, +according to the model, + +67 +00:03:03,660 --> 00:03:07,053 +and get mathematical or +computational as possible answers. + +68 +00:03:08,280 --> 00:03:10,170 +Another task Transformers +model can perform + +69 +00:03:10,170 --> 00:03:12,660 +is to classify each word in the sentence + +70 +00:03:12,660 --> 00:03:14,970 +instead of the sentence as a whole. + +71 +00:03:14,970 --> 00:03:18,390 +One example of this is +Named Entity Recognition, + +72 +00:03:18,390 --> 00:03:20,820 +which is the task of identifying entities, + +73 +00:03:20,820 --> 00:03:25,323 +such as persons, organizations +or locations in a sentence. + +74 +00:03:26,400 --> 00:03:30,570 +Here, the model correctly +finds the person, Sylvain, + +75 +00:03:30,570 --> 00:03:32,453 +the organization, Hugging Face, + +76 +00:03:32,453 --> 00:03:35,010 +as well as the location, Brooklyn, + +77 +00:03:35,010 --> 00:03:36,303 +inside the input text. + +78 +00:03:37,661 --> 00:03:40,230 +The grouped_entities=True argument used + +79 +00:03:40,230 --> 00:03:42,330 +is to make the pipeline group together + +80 +00:03:42,330 --> 00:03:44,790 +the different words +linked to the same entity, + +81 +00:03:44,790 --> 00:03:46,353 +such as Hugging and Face here. + +82 +00:03:48,270 --> 00:03:50,670 +Another task available +with the pipeline API + +83 +00:03:50,670 --> 00:03:52,920 +is extractive question answering. + +84 +00:03:52,920 --> 00:03:55,380 +Providing a context and a question, + +85 +00:03:55,380 --> 00:03:58,290 +the model will identify the +span of text in the context + +86 +00:03:58,290 --> 00:04:00,190 +containing the answer to the question. + +87 +00:04:01,650 --> 00:04:03,960 +Getting short summaries +of very long articles + +88 +00:04:03,960 --> 00:04:06,540 +is also something the Transformers +library can help with, + +89 +00:04:06,540 --> 00:04:08,140 +with the summarization pipeline. + +90 +00:04:09,480 --> 00:04:12,570 +Finally, the last task +supported by the pipeline API + +91 +00:04:12,570 --> 00:04:14,130 +is translation. + +92 +00:04:14,130 --> 00:04:16,170 +Here we use a French/English model + +93 +00:04:16,170 --> 00:04:17,460 +found on the model hub + +94 +00:04:17,460 --> 00:04:19,893 +to get the English +version of our input text. + +95 +00:04:21,600 --> 00:04:23,490 +Here is a brief summary of all the tasks + +96 +00:04:23,490 --> 00:04:25,500 +we've looked into in this video. + +97 +00:04:25,500 --> 00:04:27,390 +Try then out through the inference widgets + +98 +00:04:27,390 --> 00:04:28,327 +in the model hub. + +99 +00:04:30,459 --> 00:04:33,475 +(screen whooshes) + +100 +00:04:33,475 --> 00:04:35,175 +(logo whooshes) + diff --git a/subtitles/en/02_the-carbon-footprint-of-transformers.srt b/subtitles/en/02_the-carbon-footprint-of-transformers.srt new file mode 100644 index 000000000..101d676a1 --- /dev/null +++ b/subtitles/en/02_the-carbon-footprint-of-transformers.srt @@ -0,0 +1,581 @@ +1 +00:00:05,580 --> 00:00:08,820 +- So let's talk about the carbon +footprint of transformers. + +2 +00:00:08,820 --> 00:00:10,530 +Maybe you've seen +headlines such as this one + +3 +00:00:10,530 --> 00:00:13,530 +that training a single AI +model can emit as much carbon + +4 +00:00:13,530 --> 00:00:16,020 +as five cars in their lifetimes. + +5 +00:00:16,020 --> 00:00:19,440 +So when is this true +and is it always true? + +6 +00:00:19,440 --> 00:00:21,803 +Well, it actually depends +on several things. + +7 +00:00:21,803 --> 00:00:23,430 +Most importantly, it depends + +8 +00:00:23,430 --> 00:00:24,960 +on the type of energy you're using. + +9 +00:00:24,960 --> 00:00:26,267 +If you're using renewable energy such as + +10 +00:00:26,267 --> 00:00:30,670 +solar, wind, hydroelectricity, +you're really + +11 +00:00:30,670 --> 00:00:33,810 +not emitting any carbon +at all, very, very little. + +12 +00:00:33,810 --> 00:00:36,769 +If you're using non-renewable +energy sources such as coal + +13 +00:00:36,769 --> 00:00:39,570 +then their carbon +footprint is a lot higher + +14 +00:00:39,570 --> 00:00:43,260 +'cuz essentially you are emitting +a lot of greenhouse gases. + +15 +00:00:43,260 --> 00:00:44,670 +Another aspect is training time. + +16 +00:00:44,670 --> 00:00:47,232 +So the longer you train, +the more energy you use + +17 +00:00:47,232 --> 00:00:50,250 +the more energy you use, the +more carbon you emit, right? + +18 +00:00:50,250 --> 00:00:51,270 +So this really adds up + +19 +00:00:51,270 --> 00:00:53,520 +especially if you're +training large models for + +20 +00:00:53,520 --> 00:00:56,460 +for hours and days and weeks. + +21 +00:00:56,460 --> 00:00:58,380 +The hardware you use also matters + +22 +00:00:58,380 --> 00:01:00,930 +because some GPUs, for +example, are more efficient + +23 +00:01:00,930 --> 00:01:05,460 +than others and utilizing +efficiency use properly. + +24 +00:01:05,460 --> 00:01:07,500 +So using them a hundred +percent all the time + +25 +00:01:07,500 --> 00:01:10,650 +can really reduce the energy +consumption that you have. + +26 +00:01:10,650 --> 00:01:13,290 +And then once again, reduce +your carbon footprint. + +27 +00:01:13,290 --> 00:01:15,870 +There's also other aspects such as IO + +28 +00:01:15,870 --> 00:01:17,730 +such as data, et cetera, et cetera. + +29 +00:01:17,730 --> 00:01:20,940 +But these are the main three +that you should focus on. + +30 +00:01:20,940 --> 00:01:23,340 +So when I talk about energy +sources and carbon intensity + +31 +00:01:23,340 --> 00:01:24,420 +what does that really mean? + +32 +00:01:24,420 --> 00:01:27,480 +So if you look at the top of the screen + +33 +00:01:27,480 --> 00:01:30,480 +you have a carbon footprint + +34 +00:01:30,480 --> 00:01:33,860 +of a cloud computing +instance in Mumbai, India + +35 +00:01:33,860 --> 00:01:38,700 +which emits 920 grams of +CO2 per kilowatt hour. + +36 +00:01:38,700 --> 00:01:40,110 +This is almost one kilogram + +37 +00:01:40,110 --> 00:01:43,680 +of CO2 per kilowatt hour +of electricity used. + +38 +00:01:43,680 --> 00:01:45,150 +If you compare that with Canada, Montreal + +39 +00:01:45,150 --> 00:01:48,720 +where I am right now, 20 +grams of CO2 per kilo hour. + +40 +00:01:48,720 --> 00:01:50,040 +So that's a really, really big difference. + +41 +00:01:50,040 --> 00:01:54,240 +Almost more than 40 +times more carbon emitted + +42 +00:01:54,240 --> 00:01:55,950 +in Mumbai versus Montreal. + +43 +00:01:55,950 --> 00:01:57,720 +And so this can really, really add up. + +44 +00:01:57,720 --> 00:01:59,820 +If you're training a model +for weeks, for example + +45 +00:01:59,820 --> 00:02:01,920 +you're multiplying times 40 + +46 +00:02:01,920 --> 00:02:03,450 +the carbon that you're emitting. + +47 +00:02:03,450 --> 00:02:05,070 +So choosing the right instance + +48 +00:02:05,070 --> 00:02:07,080 +choosing a low carbon compute instance + +49 +00:02:07,080 --> 00:02:09,690 +is really the most impactful +thing that you can do. + +50 +00:02:09,690 --> 00:02:13,020 +And this is where it can really add up + +51 +00:02:13,020 --> 00:02:15,930 +if you're training in a very intensive + +52 +00:02:15,930 --> 00:02:17,580 +in a very carbon intensive region + +53 +00:02:19,170 --> 00:02:21,750 +other elements to consider, for example + +54 +00:02:21,750 --> 00:02:22,770 +using pre-trained models + +55 +00:02:22,770 --> 00:02:25,590 +that's the machine learning +equivalent of recycling. + +56 +00:02:25,590 --> 00:02:28,292 +When you have pre-trained +models available using them + +57 +00:02:28,292 --> 00:02:30,120 +you're not emitting any +carbon at all, right? + +58 +00:02:30,120 --> 00:02:31,230 +You're not retraining anything. + +59 +00:02:31,230 --> 00:02:33,450 +So that's also doing your homework + +60 +00:02:33,450 --> 00:02:35,574 +and kind of looking around +what already exists. + +61 +00:02:35,574 --> 00:02:37,890 +Fine tuning instead of +training from scratch. + +62 +00:02:37,890 --> 00:02:38,723 +So once again + +63 +00:02:38,723 --> 00:02:40,590 +if you find a model that +is almost what you need + +64 +00:02:40,590 --> 00:02:43,530 +but not quite fine tuning +the last couple of layers + +65 +00:02:43,530 --> 00:02:45,210 +making it really fit your purpose instead + +66 +00:02:45,210 --> 00:02:46,500 +of training a large transformer + +67 +00:02:46,500 --> 00:02:48,810 +from scratch can really help, + +68 +00:02:48,810 --> 00:02:51,270 +starting with smaller experiments + +69 +00:02:51,270 --> 00:02:52,800 +and debugging as you go. + +70 +00:02:52,800 --> 00:02:54,630 +So that means, for example, training + +71 +00:02:54,630 --> 00:02:58,770 +figuring out data encoding, +figuring out, you know + +72 +00:02:58,770 --> 00:03:01,170 +making sure that there's +no small bugs, that you'll + +73 +00:03:01,170 --> 00:03:03,840 +you'll realize, you know, +16 hours into training + +74 +00:03:03,840 --> 00:03:05,820 +starting small and really making sure + +75 +00:03:05,820 --> 00:03:08,760 +that what you're doing, what +your code is, is stable. + +76 +00:03:08,760 --> 00:03:11,430 +And then finally doing +a literature review to + +77 +00:03:11,430 --> 00:03:13,740 +choose hyper parameter +ranges and then following + +78 +00:03:13,740 --> 00:03:15,900 +up with a random search +instead of a grid search. + +79 +00:03:15,900 --> 00:03:18,420 +So random searches for hyper parameters + +80 +00:03:18,420 --> 00:03:21,300 +combinations have actually +shown to be as efficient + +81 +00:03:21,300 --> 00:03:24,000 +in finding the optimal +configuration as grid search. + +82 +00:03:24,000 --> 00:03:27,510 +But obviously you're not trying +all possible combinations + +83 +00:03:27,510 --> 00:03:29,520 +you're only trying a subset of them. + +84 +00:03:29,520 --> 00:03:31,800 +So this can really help as well. + +85 +00:03:31,800 --> 00:03:32,760 +So now if we go back + +86 +00:03:32,760 --> 00:03:36,300 +to the original paper by +Strubell et all in 2019 + +87 +00:03:36,300 --> 00:03:39,180 +the infamous five cars +in their lifetimes paper. + +88 +00:03:39,180 --> 00:03:40,013 +If you just look + +89 +00:03:40,013 --> 00:03:43,606 +at a transformer of 200 +million perimeter transformer + +90 +00:03:43,606 --> 00:03:46,950 +it is carbon footprint is +around 200 pounds of CO2 + +91 +00:03:46,950 --> 00:03:47,940 +which is significant + +92 +00:03:47,940 --> 00:03:49,980 +but it's nowhere near five cars, right? + +93 +00:03:49,980 --> 00:03:52,893 +It's not even a transatlantic flight. + +94 +00:03:52,893 --> 00:03:55,020 +How it really adds up is when you're doing + +95 +00:03:55,020 --> 00:03:56,190 +neural architecture search + +96 +00:03:56,190 --> 00:03:58,560 +when you're doing hyper +parameter tuning, and + +97 +00:03:58,560 --> 00:04:00,930 +this is trying all possible combinations + +98 +00:04:00,930 --> 00:04:01,763 +et cetera, et cetera. + +99 +00:04:01,763 --> 00:04:02,596 +And this is where + +100 +00:04:02,596 --> 00:04:05,400 +like the 600,000 pounds of CO2 came from. + +101 +00:04:05,400 --> 00:04:08,490 +So this is really where things add up. + +102 +00:04:08,490 --> 00:04:11,880 +So, but if you're doing things +mindfully and conscientiously + +103 +00:04:11,880 --> 00:04:16,410 +then your carbon footprint +wont be as big as, + +104 +00:04:16,410 --> 00:04:20,040 +as the paper implied, some tools to figure + +105 +00:04:20,040 --> 00:04:22,111 +out how much CO2 exactly you're emitting. + +106 +00:04:22,111 --> 00:04:24,270 +There's a web-based tool called machine + +107 +00:04:24,270 --> 00:04:26,430 +learning submissions +calculator, which allows you + +108 +00:04:26,430 --> 00:04:29,010 +to manually input, for example, +which hardware you used + +109 +00:04:29,010 --> 00:04:30,488 +how many hours you used it for + +110 +00:04:30,488 --> 00:04:34,260 +where it was located +locally or in the cloud. + +111 +00:04:34,260 --> 00:04:35,640 +And then it's gonna give you an estimate + +112 +00:04:35,640 --> 00:04:37,560 +of how much CO2 you emitted. + +113 +00:04:37,560 --> 00:04:40,200 +Another tool that does +this programmatically, + +114 +00:04:40,200 --> 00:04:41,190 +is called code carbon. + +115 +00:04:41,190 --> 00:04:45,112 +So you can PIP install it, you +can, you can go to the GitHub + +116 +00:04:45,112 --> 00:04:48,120 +and essentially it runs +in parallel to your code. + +117 +00:04:48,120 --> 00:04:49,085 +So essentially you call it + +118 +00:04:49,085 --> 00:04:51,060 +and then you do all your training. + +119 +00:04:51,060 --> 00:04:53,760 +And then at the end it's +gonna give you an estimate + +120 +00:04:53,760 --> 00:04:57,210 +a CSV file with an +estimate of your emissions. + +121 +00:04:57,210 --> 00:04:59,250 +And it's gonna give you some comparisons. + +122 +00:04:59,250 --> 00:05:01,230 +It's got a visual UI +where you can really look + +123 +00:05:01,230 --> 00:05:04,680 +at how this compares to +driving a car or watching TV. + +124 +00:05:04,680 --> 00:05:06,060 +So it can give you an idea + +125 +00:05:06,060 --> 00:05:07,740 +of the scope of your emissions as well. + +126 +00:05:07,740 --> 00:05:09,930 +And actually, code carbon is +already integrated into auto + +127 +00:05:09,930 --> 00:05:12,270 +and LP and hopefully +people will be using it + +128 +00:05:12,270 --> 00:05:15,240 +out of the box and easily +tracking their emissions all + +129 +00:05:15,240 --> 00:05:17,523 +through training and +deploying transformers. + diff --git a/subtitles/en/03_what-is-transfer-learning.srt b/subtitles/en/03_what-is-transfer-learning.srt new file mode 100644 index 000000000..80c9ddeac --- /dev/null +++ b/subtitles/en/03_what-is-transfer-learning.srt @@ -0,0 +1,396 @@ +1 +00:00:00,189 --> 00:00:02,856 +(air whooshing) + +2 +00:00:05,550 --> 00:00:07,293 +- What is transfer learning? + +3 +00:00:09,480 --> 00:00:10,920 +The idea of transfer learning + +4 +00:00:10,920 --> 00:00:12,570 +is to leverage the knowledge acquired + +5 +00:00:12,570 --> 00:00:15,543 +by a model trained with lots +of data on another task. + +6 +00:00:16,410 --> 00:00:20,130 +The model A will be trained +specifically for task A. + +7 +00:00:20,130 --> 00:00:22,200 +Now let's say you want to train a model B + +8 +00:00:22,200 --> 00:00:23,970 +for a different task. + +9 +00:00:23,970 --> 00:00:27,330 +One option would be to train +the model from scratch. + +10 +00:00:27,330 --> 00:00:30,633 +This could take lots of +computation, time and data. + +11 +00:00:31,470 --> 00:00:34,260 +Instead, we could initialize model B + +12 +00:00:34,260 --> 00:00:36,570 +with the same weights as model A, + +13 +00:00:36,570 --> 00:00:39,213 +transferring the knowledge +of model A on task B. + +14 +00:00:41,040 --> 00:00:42,690 +When training from scratch, + +15 +00:00:42,690 --> 00:00:45,870 +all the model's weight +are initialized randomly. + +16 +00:00:45,870 --> 00:00:48,870 +In this example, we are +training a BERT model + +17 +00:00:48,870 --> 00:00:50,220 +on the task of recognizing + +18 +00:00:50,220 --> 00:00:52,203 +if two sentences are similar or not. + +19 +00:00:54,116 --> 00:00:56,730 +On the left, it's trained from scratch, + +20 +00:00:56,730 --> 00:01:00,000 +and on the right it's +fine-tuning a pretrained model. + +21 +00:01:00,000 --> 00:01:02,220 +As we can see, using transfer learning + +22 +00:01:02,220 --> 00:01:05,160 +and the pretrained model +yields better results. + +23 +00:01:05,160 --> 00:01:07,140 +And it doesn't matter if we train longer. + +24 +00:01:07,140 --> 00:01:10,620 +The training from scratch is +capped around 70% accuracy + +25 +00:01:10,620 --> 00:01:13,293 +while the pretrained model +beats the 86% easily. + +26 +00:01:14,460 --> 00:01:16,140 +This is because pretrained models + +27 +00:01:16,140 --> 00:01:18,420 +are usually trained on +large amounts of data + +28 +00:01:18,420 --> 00:01:21,000 +that provide the model with +a statistical understanding + +29 +00:01:21,000 --> 00:01:23,413 +of the language used during pretraining. + +30 +00:01:24,450 --> 00:01:25,950 +In computer vision, + +31 +00:01:25,950 --> 00:01:28,080 +transfer learning has +been applied successfully + +32 +00:01:28,080 --> 00:01:30,060 +for almost ten years. + +33 +00:01:30,060 --> 00:01:32,850 +Models are frequently +pretrained on ImageNet, + +34 +00:01:32,850 --> 00:01:36,153 +a dataset containing 1.2 +millions of photo images. + +35 +00:01:37,170 --> 00:01:41,130 +Each image is classified +by one of 1000 labels. + +36 +00:01:41,130 --> 00:01:44,010 +Training like this, on labeled data + +37 +00:01:44,010 --> 00:01:45,663 +is called supervised learning. + +38 +00:01:47,340 --> 00:01:49,140 +In Natural Language Processing, + +39 +00:01:49,140 --> 00:01:51,870 +transfer learning is a bit more recent. + +40 +00:01:51,870 --> 00:01:54,480 +A key difference with ImageNet +is that the pretraining + +41 +00:01:54,480 --> 00:01:56,460 +is usually self-supervised, + +42 +00:01:56,460 --> 00:01:58,770 +which means it doesn't +require humans annotations + +43 +00:01:58,770 --> 00:01:59,673 +for the labels. + +44 +00:02:00,780 --> 00:02:02,700 +A very common pretraining objective + +45 +00:02:02,700 --> 00:02:05,310 +is to guess the next word in a sentence. + +46 +00:02:05,310 --> 00:02:07,710 +Which only requires lots and lots of text. + +47 +00:02:07,710 --> 00:02:10,710 +GPT-2 for instance, +was pretrained this way + +48 +00:02:10,710 --> 00:02:12,900 +using the content of 45 millions links + +49 +00:02:12,900 --> 00:02:14,673 +posted by users on Reddit. + +50 +00:02:16,560 --> 00:02:19,590 +Another example of self-supervised +pretraining objective + +51 +00:02:19,590 --> 00:02:22,470 +is to predict the value +of randomly masked words. + +52 +00:02:22,470 --> 00:02:24,540 +Which is similar to +fill-in-the-blank tests + +53 +00:02:24,540 --> 00:02:26,760 +you may have done in school. + +54 +00:02:26,760 --> 00:02:29,880 +BERT was pretrained this way +using the English Wikipedia + +55 +00:02:29,880 --> 00:02:31,893 +and 11,000 unpublished books. + +56 +00:02:33,120 --> 00:02:36,450 +In practice, transfer learning +is applied on a given model + +57 +00:02:36,450 --> 00:02:39,090 +by throwing away its head, that is, + +58 +00:02:39,090 --> 00:02:42,150 +its last layers focused on +the pretraining objective, + +59 +00:02:42,150 --> 00:02:45,360 +and replacing it with a new, +randomly initialized head + +60 +00:02:45,360 --> 00:02:46,860 +suitable for the task at hand. + +61 +00:02:47,970 --> 00:02:51,570 +For instance, when we +fine-tuned a BERT model earlier, + +62 +00:02:51,570 --> 00:02:54,060 +we removed the head that +classified mask words + +63 +00:02:54,060 --> 00:02:56,790 +and replaced it with a +classifier with 2 outputs. + +64 +00:02:56,790 --> 00:02:58,563 +Since our task had two labels. + +65 +00:02:59,700 --> 00:03:02,490 +To be as efficient as possible, +the pretrained model used + +66 +00:03:02,490 --> 00:03:03,770 +should be as similar as possible + +67 +00:03:03,770 --> 00:03:06,270 +to the task it's fine-tuned on. + +68 +00:03:06,270 --> 00:03:08,190 +For instance, if the problem + +69 +00:03:08,190 --> 00:03:10,860 +is to classify German sentences, + +70 +00:03:10,860 --> 00:03:13,053 +it's best to use a +German pretrained model. + +71 +00:03:14,370 --> 00:03:16,649 +But with the good comes the bad. + +72 +00:03:16,649 --> 00:03:19,380 +The pretrained model does not +only transfer its knowledge, + +73 +00:03:19,380 --> 00:03:21,693 +but also any bias it may contain. + +74 +00:03:22,530 --> 00:03:24,300 +ImageNet mostly contains images + +75 +00:03:24,300 --> 00:03:26,850 +coming from the United +States and Western Europe. + +76 +00:03:26,850 --> 00:03:28,020 +So models fine-tuned with it + +77 +00:03:28,020 --> 00:03:31,710 +usually will perform better on +images from these countries. + +78 +00:03:31,710 --> 00:03:33,690 +OpenAI also studied the bias + +79 +00:03:33,690 --> 00:03:36,120 +in the predictions of its GPT-3 model + +80 +00:03:36,120 --> 00:03:36,953 +which was pretrained + +81 +00:03:36,953 --> 00:03:38,750 +using the guess the next word objective. + +82 +00:03:39,720 --> 00:03:41,040 +Changing the gender of the prompt + +83 +00:03:41,040 --> 00:03:44,250 +from he was very to she was very + +84 +00:03:44,250 --> 00:03:47,550 +changed the predictions from +mostly neutral adjectives + +85 +00:03:47,550 --> 00:03:49,233 +to almost only physical ones. + +86 +00:03:50,400 --> 00:03:52,367 +In their model card of the GPT-2 model, + +87 +00:03:52,367 --> 00:03:54,990 +OpenAI also acknowledges its bias + +88 +00:03:54,990 --> 00:03:56,730 +and discourages its use + +89 +00:03:56,730 --> 00:03:58,803 +in systems that interact with humans. + +90 +00:04:01,040 --> 00:04:03,707 +(air whooshing) + diff --git a/subtitles/en/04_the-transformer-architecture.srt b/subtitles/en/04_the-transformer-architecture.srt new file mode 100644 index 000000000..aecc03a08 --- /dev/null +++ b/subtitles/en/04_the-transformer-architecture.srt @@ -0,0 +1,280 @@ +1 +00:00:00,000 --> 00:00:02,750 +(logo whooshing) + +2 +00:00:05,010 --> 00:00:07,323 +- Let's study the +transformer architecture. + +3 +00:00:09,150 --> 00:00:12,030 +This video is the introductory +video to the encoders, + +4 +00:00:12,030 --> 00:00:15,510 +decoders, and encoder-decoder +series of videos. + +5 +00:00:15,510 --> 00:00:16,343 +In this series, + +6 +00:00:16,343 --> 00:00:18,900 +we'll try to understand what +makes a transformer network, + +7 +00:00:18,900 --> 00:00:22,770 +and we'll try to explain it +in simple, high-level terms. + +8 +00:00:22,770 --> 00:00:25,800 +No advanced understanding of +neural networks is necessary, + +9 +00:00:25,800 --> 00:00:29,343 +but an understanding of basic +vectors and tensors may help. + +10 +00:00:32,250 --> 00:00:33,270 +To get started, + +11 +00:00:33,270 --> 00:00:34,530 +we'll take up this diagram + +12 +00:00:34,530 --> 00:00:36,630 +from the original transformer paper, + +13 +00:00:36,630 --> 00:00:40,140 +entitled "Attention Is All +You Need", by Vaswani et al. + +14 +00:00:40,140 --> 00:00:41,010 +As we'll see here, + +15 +00:00:41,010 --> 00:00:42,780 +we can leverage only some parts of it, + +16 +00:00:42,780 --> 00:00:44,630 +according to what we're trying to do. + +17 +00:00:45,480 --> 00:00:47,610 +We want to dive into the specific layers, + +18 +00:00:47,610 --> 00:00:48,990 +building up that architecture, + +19 +00:00:48,990 --> 00:00:51,390 +but we'll try to understand +the different ways + +20 +00:00:51,390 --> 00:00:52,893 +this architecture can be used. + +21 +00:00:55,170 --> 00:00:56,003 +Let's first start + +22 +00:00:56,003 --> 00:00:58,260 +by splitting that +architecture into two parts. + +23 +00:00:58,260 --> 00:00:59,910 +On the left we have the encoder, + +24 +00:00:59,910 --> 00:01:01,980 +and on the right, the decoder. + +25 +00:01:01,980 --> 00:01:03,330 +These two can be used together, + +26 +00:01:03,330 --> 00:01:05,330 +but they can also be used independently. + +27 +00:01:06,180 --> 00:01:08,610 +Let's understand how these work. + +28 +00:01:08,610 --> 00:01:11,460 +The encoder accepts inputs +that represent text. + +29 +00:01:11,460 --> 00:01:13,620 +It converts this text, these words, + +30 +00:01:13,620 --> 00:01:15,675 +into numerical representations. + +31 +00:01:15,675 --> 00:01:17,400 +These numerical representations + +32 +00:01:17,400 --> 00:01:20,460 +can also be called +embeddings, or features. + +33 +00:01:20,460 --> 00:01:23,100 +We'll see that it uses the +self-attention mechanism + +34 +00:01:23,100 --> 00:01:24,483 +as its main component. + +35 +00:01:25,500 --> 00:01:27,120 +We recommend you check out the video + +36 +00:01:27,120 --> 00:01:29,700 +on encoders specifically to understand + +37 +00:01:29,700 --> 00:01:31,680 +what is this numerical representation, + +38 +00:01:31,680 --> 00:01:33,690 +as well as how it works. + +39 +00:01:33,690 --> 00:01:36,660 +We'll study the self-attention +mechanism in more detail, + +40 +00:01:36,660 --> 00:01:38,913 +as well as its bi-directional properties. + +41 +00:01:40,650 --> 00:01:42,780 +The decoder is similar to the encoder. + +42 +00:01:42,780 --> 00:01:45,630 +It can also accept text inputs. + +43 +00:01:45,630 --> 00:01:48,210 +It uses a similar +mechanism as the encoder, + +44 +00:01:48,210 --> 00:01:51,150 +which is the masked +self-attention as well. + +45 +00:01:51,150 --> 00:01:52,590 +It differs from the encoder + +46 +00:01:52,590 --> 00:01:54,990 +due to its uni-directional feature + +47 +00:01:54,990 --> 00:01:58,590 +and is traditionally used in +an auto-regressive manner. + +48 +00:01:58,590 --> 00:02:01,650 +Here too, we recommend you +check out the video on decoders + +49 +00:02:01,650 --> 00:02:04,000 +especially to understand +how all of this works. + +50 +00:02:06,810 --> 00:02:07,890 +Combining the two parts + +51 +00:02:07,890 --> 00:02:10,200 +results in what is known +as an encoder-decoder, + +52 +00:02:10,200 --> 00:02:12,720 +or a sequence-to-sequence transformer. + +53 +00:02:12,720 --> 00:02:14,280 +The encoder accepts inputs + +54 +00:02:14,280 --> 00:02:17,850 +and computes a high-level +representation of those inputs. + +55 +00:02:17,850 --> 00:02:20,252 +These outputs are then +passed to the decoder. + +56 +00:02:20,252 --> 00:02:22,860 +The decoder uses the encoder's output, + +57 +00:02:22,860 --> 00:02:26,370 +alongside other inputs +to generate a prediction. + +58 +00:02:26,370 --> 00:02:27,900 +It then predicts an output, + +59 +00:02:27,900 --> 00:02:30,248 +which it will re-use in future iterations, + +60 +00:02:30,248 --> 00:02:32,662 +hence the term, auto-regressive. + +61 +00:02:32,662 --> 00:02:34,740 +Finally, to get an understanding + +62 +00:02:34,740 --> 00:02:36,690 +of the encoder-decoders as a whole, + +63 +00:02:36,690 --> 00:02:39,670 +we recommend you check out +the video on encoder-decoders. + +64 +00:02:39,670 --> 00:02:42,420 +(logo whooshing) + diff --git a/subtitles/en/05_transformer-models-encoders.srt b/subtitles/en/05_transformer-models-encoders.srt new file mode 100644 index 000000000..1171958b2 --- /dev/null +++ b/subtitles/en/05_transformer-models-encoders.srt @@ -0,0 +1,454 @@ +1 +00:00:00,253 --> 00:00:03,003 +(intro striking) + +2 +00:00:04,440 --> 00:00:07,830 +- In this video, we'll study +the encoder architecture. + +3 +00:00:07,830 --> 00:00:11,070 +An example of a popular encoder +only architecture is BURT + +4 +00:00:11,070 --> 00:00:13,323 +which is the most popular +model of its kind. + +5 +00:00:14,550 --> 00:00:16,950 +Let's first start by +understanding how it works. + +6 +00:00:18,360 --> 00:00:20,910 +We'll use a small example +using three words. + +7 +00:00:20,910 --> 00:00:23,823 +We use these as inputs and +pass them through the encoder. + +8 +00:00:25,290 --> 00:00:28,173 +We retrieve a numerical +representation of each word. + +9 +00:00:29,970 --> 00:00:32,700 +Here, for example, the encoder +converts those three words, + +10 +00:00:32,700 --> 00:00:37,350 +welcome to NYC, in these +three sequences of numbers. + +11 +00:00:37,350 --> 00:00:40,350 +The encoder outputs exactly +one sequence of numbers + +12 +00:00:40,350 --> 00:00:41,493 +per input word. + +13 +00:00:42,330 --> 00:00:44,880 +This numerical representation +can also be called + +14 +00:00:44,880 --> 00:00:47,163 +a feature vector, or a feature tensor. + +15 +00:00:49,080 --> 00:00:51,030 +Let's dive into this representation. + +16 +00:00:51,030 --> 00:00:52,740 +It contains one vector per word + +17 +00:00:52,740 --> 00:00:54,540 +that was passed through the encoder. + +18 +00:00:56,130 --> 00:00:58,620 +Each of these vector is a +numerical representation + +19 +00:00:58,620 --> 00:01:00,033 +of the word in question. + +20 +00:01:01,080 --> 00:01:03,300 +The dimension of that vector is defined + +21 +00:01:03,300 --> 00:01:05,520 +by the architecture of the model. + +22 +00:01:05,520 --> 00:01:08,703 +For the base BERT model, it is 768. + +23 +00:01:10,650 --> 00:01:13,230 +These representations +contain the value of a word, + +24 +00:01:13,230 --> 00:01:15,240 +but contextualized. + +25 +00:01:15,240 --> 00:01:18,570 +For example, the vector +attributed to the word "to" + +26 +00:01:18,570 --> 00:01:22,290 +isn't the representation +of only the "to" word. + +27 +00:01:22,290 --> 00:01:25,650 +It also takes into account +the words around it + +28 +00:01:25,650 --> 00:01:27,363 +which we call the context. + +29 +00:01:28,650 --> 00:01:30,780 +As in it looks to the left context, + +30 +00:01:30,780 --> 00:01:32,970 +the words on the left of +the one we're studying, + +31 +00:01:32,970 --> 00:01:34,980 +here the word "Welcome", + +32 +00:01:34,980 --> 00:01:37,497 +and the context on the +right, here the word "NYC", + +33 +00:01:38,348 --> 00:01:42,000 +and it outputs a value for +the word given its context. + +34 +00:01:42,000 --> 00:01:45,420 +It is therefore a contextualized value. + +35 +00:01:45,420 --> 00:01:48,810 +One could say that the +vector of 768 values + +36 +00:01:48,810 --> 00:01:51,993 +holds the meaning of the +word within the text. + +37 +00:01:53,310 --> 00:01:56,073 +It does this thanks to the +self-attention mechanism. + +38 +00:01:57,240 --> 00:02:00,630 +The self-attention mechanism +relates to different positions, + +39 +00:02:00,630 --> 00:02:02,850 +or different words in a single sequence + +40 +00:02:02,850 --> 00:02:06,003 +in order to compute a +representation of that sequence. + +41 +00:02:07,200 --> 00:02:09,000 +As we've seen before, this means that + +42 +00:02:09,000 --> 00:02:11,130 +the resulting representation of a word + +43 +00:02:11,130 --> 00:02:13,983 +has been affected by other +words in the sequence. + +44 +00:02:15,840 --> 00:02:18,030 +We won't dive into the specifics here + +45 +00:02:18,030 --> 00:02:19,680 +which will offer some further readings + +46 +00:02:19,680 --> 00:02:21,330 +if you want to get a better understanding + +47 +00:02:21,330 --> 00:02:22,953 +at what happens under the hood. + +48 +00:02:25,050 --> 00:02:27,480 +So why should one use and encoder? + +49 +00:02:27,480 --> 00:02:29,370 +Encoders can be used as stand-alone models + +50 +00:02:29,370 --> 00:02:31,263 +in a wide variety of tasks. + +51 +00:02:32,100 --> 00:02:33,360 +For example, BERT, + +52 +00:02:33,360 --> 00:02:35,670 +arguably the most famous +transformer model, + +53 +00:02:35,670 --> 00:02:37,590 +is a standalone encoder model, + +54 +00:02:37,590 --> 00:02:38,820 +and at the time of release, + +55 +00:02:38,820 --> 00:02:40,440 +it'd be the state of the art + +56 +00:02:40,440 --> 00:02:42,780 +in many sequence classification tasks, + +57 +00:02:42,780 --> 00:02:44,190 +question answering tasks, + +58 +00:02:44,190 --> 00:02:46,743 +and mask language modeling +to only cite of few. + +59 +00:02:48,150 --> 00:02:50,460 +The idea is that encoders +are very powerful + +60 +00:02:50,460 --> 00:02:52,470 +at extracting vectors that carry + +61 +00:02:52,470 --> 00:02:55,350 +meaningful information about a sequence. + +62 +00:02:55,350 --> 00:02:57,870 +This vector can then be +handled down the road + +63 +00:02:57,870 --> 00:03:00,070 +by additional neurons +to make sense of them. + +64 +00:03:01,380 --> 00:03:02,850 +Let's take a look at some examples + +65 +00:03:02,850 --> 00:03:04,563 +where encoder really shine. + +66 +00:03:06,210 --> 00:03:09,900 +First of all, Masked +Language Modeling, or MLM. + +67 +00:03:09,900 --> 00:03:11,970 +It's the task of predicting a hidden word + +68 +00:03:11,970 --> 00:03:13,590 +in a sequence of word. + +69 +00:03:13,590 --> 00:03:15,630 +Here, for example, we have hidden the word + +70 +00:03:15,630 --> 00:03:17,247 +between "My" and "is". + +71 +00:03:18,270 --> 00:03:21,120 +This is one of the objectives +with which BERT was trained. + +72 +00:03:21,120 --> 00:03:24,393 +It was trained to predict +hidden words in a sequence. + +73 +00:03:25,230 --> 00:03:27,930 +Encoders shine in this +scenario in particular + +74 +00:03:27,930 --> 00:03:31,140 +as bi-directional +information is crucial here. + +75 +00:03:31,140 --> 00:03:32,947 +If we didn't have the words on the right, + +76 +00:03:32,947 --> 00:03:34,650 +"is", "Sylvain" and the ".", + +77 +00:03:34,650 --> 00:03:35,940 +then there is very little chance + +78 +00:03:35,940 --> 00:03:38,580 +that BERT would have been +able to identify name + +79 +00:03:38,580 --> 00:03:40,500 +as the correct word. + +80 +00:03:40,500 --> 00:03:42,270 +The encoder needs to +have a good understanding + +81 +00:03:42,270 --> 00:03:45,360 +of the sequence in order +to predict a masked word + +82 +00:03:45,360 --> 00:03:48,840 +as even if the text is +grammatically correct, + +83 +00:03:48,840 --> 00:03:50,610 +it does not necessarily make sense + +84 +00:03:50,610 --> 00:03:52,413 +in the context of the sequence. + +85 +00:03:55,230 --> 00:03:56,580 +As mentioned earlier, + +86 +00:03:56,580 --> 00:03:59,520 +encoders are good at doing +sequence classification. + +87 +00:03:59,520 --> 00:04:02,883 +Sentiment analysis is an example +of sequence classification. + +88 +00:04:04,410 --> 00:04:09,410 +The model's aim is to identify +the sentiment of a sequence. + +89 +00:04:09,540 --> 00:04:11,280 +It can range from giving a sequence, + +90 +00:04:11,280 --> 00:04:12,960 +a rating from one to five stars + +91 +00:04:12,960 --> 00:04:15,900 +if doing review analysis +to giving a positive, + +92 +00:04:15,900 --> 00:04:17,820 +or negative rating to a sequence + +93 +00:04:17,820 --> 00:04:19,220 +which is what is shown here. + +94 +00:04:20,280 --> 00:04:22,950 +For example, here, +given the two sequences, + +95 +00:04:22,950 --> 00:04:25,860 +we use the model to compute a prediction, + +96 +00:04:25,860 --> 00:04:27,420 +and to classify the sequences + +97 +00:04:27,420 --> 00:04:30,393 +among these two classes, +positive and negative. + +98 +00:04:31,230 --> 00:04:33,450 +While the two sequences are very similar + +99 +00:04:33,450 --> 00:04:35,220 +containing the same words, + +100 +00:04:35,220 --> 00:04:37,170 +the meaning is entirely different, + +101 +00:04:37,170 --> 00:04:40,143 +and the encoder model is able +to grasp that difference. + +102 +00:04:41,404 --> 00:04:44,154 +(outro striking) + diff --git a/subtitles/en/06_transformer-models-decoders.srt b/subtitles/en/06_transformer-models-decoders.srt new file mode 100644 index 000000000..0b0c84cde --- /dev/null +++ b/subtitles/en/06_transformer-models-decoders.srt @@ -0,0 +1,395 @@ +1 +00:00:03,750 --> 00:00:07,140 +- In this video, we'll study +the decoder architecture. + +2 +00:00:07,140 --> 00:00:07,973 +An example + +3 +00:00:07,973 --> 00:00:11,338 +of a popular decoder only +architecture is GPT two. + +4 +00:00:11,338 --> 00:00:14,160 +In order to understand how decoders work + +5 +00:00:14,160 --> 00:00:17,430 +we recommend taking a look at +the video regarding encoders. + +6 +00:00:17,430 --> 00:00:19,980 +They're extremely similar to decoders. + +7 +00:00:19,980 --> 00:00:21,210 +One can use a decoder + +8 +00:00:21,210 --> 00:00:23,760 +for most of the same tasks as an encoder + +9 +00:00:23,760 --> 00:00:27,330 +albeit with generally a +little loss of performance. + +10 +00:00:27,330 --> 00:00:28,890 +Let's take the same approach we have taken + +11 +00:00:28,890 --> 00:00:30,300 +with the encoder to try + +12 +00:00:30,300 --> 00:00:32,670 +and understand the +architectural differences + +13 +00:00:32,670 --> 00:00:34,803 +between an encoder and decoder. + +14 +00:00:35,777 --> 00:00:38,910 +We'll use a small example +using three words. + +15 +00:00:38,910 --> 00:00:41,050 +We pass them through their decoder. + +16 +00:00:41,050 --> 00:00:44,793 +We retrieve a numerical +representation for each word. + +17 +00:00:46,410 --> 00:00:49,350 +Here for example, the decoder +converts the three words. + +18 +00:00:49,350 --> 00:00:53,545 +Welcome to NYC, and these +three sequences of numbers. + +19 +00:00:53,545 --> 00:00:56,040 +The decoder outputs exactly one sequence + +20 +00:00:56,040 --> 00:00:58,740 +of numbers per input word. + +21 +00:00:58,740 --> 00:01:00,630 +This numerical representation can also + +22 +00:01:00,630 --> 00:01:03,783 +be called a feature vector +or a feature sensor. + +23 +00:01:04,920 --> 00:01:07,200 +Let's dive in this representation. + +24 +00:01:07,200 --> 00:01:08,490 +It contains one vector + +25 +00:01:08,490 --> 00:01:11,340 +per word that was passed +through the decoder. + +26 +00:01:11,340 --> 00:01:14,250 +Each of these vectors is +a numerical representation + +27 +00:01:14,250 --> 00:01:15,573 +of the word in question. + +28 +00:01:16,920 --> 00:01:18,562 +The dimension of that vector is defined + +29 +00:01:18,562 --> 00:01:20,703 +by the architecture of the model. + +30 +00:01:22,860 --> 00:01:26,040 +Where the decoder differs from +the encoder is principally + +31 +00:01:26,040 --> 00:01:28,200 +with its self attention mechanism. + +32 +00:01:28,200 --> 00:01:30,843 +It's using what is called +masked self attention. + +33 +00:01:31,860 --> 00:01:34,650 +Here, for example, if we +focus on the word "to" + +34 +00:01:34,650 --> 00:01:37,620 +we'll see that is vector +is absolutely unmodified + +35 +00:01:37,620 --> 00:01:39,690 +by the NYC word. + +36 +00:01:39,690 --> 00:01:41,731 +That's because all the words +on the right, also known + +37 +00:01:41,731 --> 00:01:45,276 +as the right context of +the word is masked rather + +38 +00:01:45,276 --> 00:01:49,230 +than benefiting from all the +words on the left and right. + +39 +00:01:49,230 --> 00:01:51,600 +So the bidirectional context. + +40 +00:01:51,600 --> 00:01:55,020 +Decoders only have access +to a single context + +41 +00:01:55,020 --> 00:01:58,203 +which can be the left +context or the right context. + +42 +00:01:59,539 --> 00:02:03,356 +The masked self attention +mechanism differs + +43 +00:02:03,356 --> 00:02:04,320 +from the self attention mechanism + +44 +00:02:04,320 --> 00:02:07,110 +by using an additional +mask to hide the context + +45 +00:02:07,110 --> 00:02:09,390 +on either side of the word + +46 +00:02:09,390 --> 00:02:12,810 +the words numerical representation +will not be affected + +47 +00:02:12,810 --> 00:02:14,853 +by the words in the hidden context. + +48 +00:02:16,260 --> 00:02:18,330 +So when should one use a decoder? + +49 +00:02:18,330 --> 00:02:22,380 +Decoders like encoders can +be used as standalone models + +50 +00:02:22,380 --> 00:02:25,020 +as they generate a +numerical representation. + +51 +00:02:25,020 --> 00:02:28,320 +They can also be used in +a wide variety of tasks. + +52 +00:02:28,320 --> 00:02:31,260 +However, the strength of +a decoder lies in the way. + +53 +00:02:31,260 --> 00:02:34,530 +A word can only have +access to its left context + +54 +00:02:34,530 --> 00:02:36,690 +having only access to their left context. + +55 +00:02:36,690 --> 00:02:39,120 +They're inherently good at text generation + +56 +00:02:39,120 --> 00:02:41,010 +the ability to generate a word + +57 +00:02:41,010 --> 00:02:45,000 +or a sequence of words given +a known sequence of words. + +58 +00:02:45,000 --> 00:02:45,833 +This is known + +59 +00:02:45,833 --> 00:02:49,083 +as causal language modeling or +natural language generation. + +60 +00:02:50,430 --> 00:02:53,520 +Here's an example of how +causal language modeling works. + +61 +00:02:53,520 --> 00:02:56,410 +We start with an initial word, which is my + +62 +00:02:57,339 --> 00:02:59,973 +we use this as input for the decoder. + +63 +00:03:00,810 --> 00:03:04,260 +The model outputs a vector of numbers + +64 +00:03:04,260 --> 00:03:07,230 +and this vector contains +information about the sequence + +65 +00:03:07,230 --> 00:03:08,733 +which is here a single word. + +66 +00:03:09,780 --> 00:03:11,430 +We apply a small transformation + +67 +00:03:11,430 --> 00:03:13,110 +to that vector so that it maps + +68 +00:03:13,110 --> 00:03:16,500 +to all the words known by +the model, which is a mapping + +69 +00:03:16,500 --> 00:03:19,890 +that we'll see later called +a language modeling head. + +70 +00:03:19,890 --> 00:03:21,930 +We identify that the model believes + +71 +00:03:21,930 --> 00:03:25,053 +that the most probable +following word is name. + +72 +00:03:26,250 --> 00:03:28,710 +We then take that new word and add it + +73 +00:03:28,710 --> 00:03:33,480 +to the initial sequence from +my, we are now at my name. + +74 +00:03:33,480 --> 00:03:36,870 +This is where the auto +regressive aspect comes in. + +75 +00:03:36,870 --> 00:03:38,490 +Auto regressive models. + +76 +00:03:38,490 --> 00:03:42,513 +We use their past outputs as +inputs and the following steps. + +77 +00:03:43,452 --> 00:03:46,980 +Once again, we do the +exact same operation. + +78 +00:03:46,980 --> 00:03:49,500 +We cast that sequence through the decoder + +79 +00:03:49,500 --> 00:03:51,993 +and retrieve the most +probable following word. + +80 +00:03:52,978 --> 00:03:57,978 +In this case, it is the word +"is", we repeat the operation + +81 +00:03:58,230 --> 00:04:02,040 +until we're satisfied, +starting from a single word. + +82 +00:04:02,040 --> 00:04:04,590 +We've now generated a full sentence. + +83 +00:04:04,590 --> 00:04:07,890 +We decide to stop there, but +we could continue for a while. + +84 +00:04:07,890 --> 00:04:12,890 +GPT two, for example, has a +maximum context size of 1,024. + +85 +00:04:13,170 --> 00:04:16,830 +We could eventually +generate up to a 1,024 words + +86 +00:04:16,830 --> 00:04:19,050 +and the decoder would +still have some memory + +87 +00:04:19,050 --> 00:04:21,003 +of the first words in this sequence. + diff --git a/subtitles/en/07_transformer-models-encoder-decoders.srt b/subtitles/en/07_transformer-models-encoder-decoders.srt new file mode 100644 index 000000000..e1b47aa21 --- /dev/null +++ b/subtitles/en/07_transformer-models-encoder-decoders.srt @@ -0,0 +1,621 @@ +1 +00:00:00,520 --> 00:00:02,603 +(swoosh) + +2 +00:00:04,230 --> 00:00:05,063 +- In this video, + +3 +00:00:05,063 --> 00:00:07,638 +we'll study the +encoder-decoder architecture. + +4 +00:00:07,638 --> 00:00:12,243 +An example of a popular +encoder-decoder model is T5. + +5 +00:00:13,770 --> 00:00:16,980 +In order to understand how +the encoder-decoder works, + +6 +00:00:16,980 --> 00:00:18,630 +we recommend you check out the videos + +7 +00:00:18,630 --> 00:00:22,590 +on encoders and decoders +as standalone models. + +8 +00:00:22,590 --> 00:00:24,990 +Understanding how they work individually + +9 +00:00:24,990 --> 00:00:28,323 +will help understanding how +an encoder-decoder works. + +10 +00:00:30,510 --> 00:00:33,390 +Let's start from what we've +seen about the encoder. + +11 +00:00:33,390 --> 00:00:36,240 +The encoder takes words as inputs, + +12 +00:00:36,240 --> 00:00:38,520 +casts them through the encoder, + +13 +00:00:38,520 --> 00:00:40,800 +and retrieves a numerical representation + +14 +00:00:40,800 --> 00:00:42,663 +for each word cast through it. + +15 +00:00:43,560 --> 00:00:46,470 +We now know that this +numerical representation + +16 +00:00:46,470 --> 00:00:49,473 +holds information about the +meaning of the sequence. + +17 +00:00:51,090 --> 00:00:54,243 +Let's put this aside and add +the decoder to the diagram. + +18 +00:00:56,610 --> 00:00:57,510 +In this scenario, + +19 +00:00:57,510 --> 00:00:59,190 +we're using the decoder in a manner + +20 +00:00:59,190 --> 00:01:00,960 +that we haven't seen before. + +21 +00:01:00,960 --> 00:01:04,173 +We're passing the outputs of +the encoder directly to it. + +22 +00:01:05,356 --> 00:01:07,770 +Additionally to the encoder outputs, + +23 +00:01:07,770 --> 00:01:10,800 +we also give the decoder a sequence. + +24 +00:01:10,800 --> 00:01:12,840 +When prompting the decoder for an output + +25 +00:01:12,840 --> 00:01:14,190 +with no initial sequence, + +26 +00:01:14,190 --> 00:01:16,140 +we can give it the value that indicates + +27 +00:01:16,140 --> 00:01:18,060 +the start of a sequence. + +28 +00:01:18,060 --> 00:01:20,919 +And that's where the +encoder-decoder magic happens. + +29 +00:01:20,919 --> 00:01:24,082 +The encoder accepts a sequence as input. + +30 +00:01:24,082 --> 00:01:25,980 +It computes a prediction, + +31 +00:01:25,980 --> 00:01:28,858 +and outputs a numerical representation. + +32 +00:01:28,858 --> 00:01:33,120 +Then, it sends that over to the decoder. + +33 +00:01:33,120 --> 00:01:36,300 +It has, in a sense, encoded that sequence. + +34 +00:01:36,300 --> 00:01:38,130 +And the decoder, in turn, + +35 +00:01:38,130 --> 00:01:40,847 +using this input alongside +its usual sequence input, + +36 +00:01:40,847 --> 00:01:43,906 +will take a stab at decoding the sequence. + +37 +00:01:43,906 --> 00:01:46,530 +The decoder decodes the sequence, + +38 +00:01:46,530 --> 00:01:48,360 +and outputs a word. + +39 +00:01:48,360 --> 00:01:51,300 +As of now, we don't need +to make sense of that word, + +40 +00:01:51,300 --> 00:01:53,100 +but we can understand that the decoder + +41 +00:01:53,100 --> 00:01:56,103 +is essentially decoding +what the encoder has output. + +42 +00:01:57,008 --> 00:02:00,000 +The start of sequence word here + +43 +00:02:00,000 --> 00:02:02,871 +indicates that it should +start decoding the sequence. + +44 +00:02:02,871 --> 00:02:06,870 +Now that we have both the +encoder numerical representation + +45 +00:02:06,870 --> 00:02:09,570 +and an initial generated word, + +46 +00:02:09,570 --> 00:02:11,343 +we don't need the encoder anymore. + +47 +00:02:12,269 --> 00:02:15,540 +As we have seen before with the decoder, + +48 +00:02:15,540 --> 00:02:18,720 +it can act in an auto-regressive manner. + +49 +00:02:18,720 --> 00:02:22,933 +The word it has just output +can now be used as an input. + +50 +00:02:22,933 --> 00:02:26,188 +This, in combination with +the numerical representation + +51 +00:02:26,188 --> 00:02:28,560 +output by the encoder, + +52 +00:02:28,560 --> 00:02:31,203 +can now be used to generate a second word. + +53 +00:02:33,040 --> 00:02:35,910 +Please note that the +first word is still here, + +54 +00:02:35,910 --> 00:02:37,770 +as the model still outputs it. + +55 +00:02:37,770 --> 00:02:39,240 +However, we have grayed it out + +56 +00:02:39,240 --> 00:02:40,940 +as we have no need for it anymore. + +57 +00:02:41,880 --> 00:02:44,070 +We can continue on and on, for example, + +58 +00:02:44,070 --> 00:02:46,320 +until the decoder outputs a value + +59 +00:02:46,320 --> 00:02:48,540 +that we consider a stopping value, + +60 +00:02:48,540 --> 00:02:51,093 +like a dot meaning the end of a sequence. + +61 +00:02:53,580 --> 00:02:55,926 +Here, we've seen the full mechanism + +62 +00:02:55,926 --> 00:02:57,540 +of the encoder-decoder transformer. + +63 +00:02:57,540 --> 00:02:59,280 +Let's go over it one more time. + +64 +00:02:59,280 --> 00:03:02,773 +We have an initial sequence +that is sent to the encoder. + +65 +00:03:02,773 --> 00:03:06,450 +That encoder output is +then sent to the decoder + +66 +00:03:06,450 --> 00:03:07,563 +for it to be decoded. + +67 +00:03:08,760 --> 00:03:12,450 +While it can now discard the +encoder after a single use, + +68 +00:03:12,450 --> 00:03:14,427 +the decoder will be used several times + +69 +00:03:14,427 --> 00:03:17,763 +until we have generated +every word that we need. + +70 +00:03:19,288 --> 00:03:21,510 +So let's see a concrete example + +71 +00:03:21,510 --> 00:03:23,460 +with Translation Language Modeling. + +72 +00:03:23,460 --> 00:03:24,930 +Also called transduction, + +73 +00:03:24,930 --> 00:03:28,200 +which is the act of +translating a sequence. + +74 +00:03:28,200 --> 00:03:30,577 +Here, we would like to +translate this English sequence + +75 +00:03:30,577 --> 00:03:33,067 +"Welcome to NYC" in French. + +76 +00:03:33,067 --> 00:03:35,460 +We're using a transformer model + +77 +00:03:35,460 --> 00:03:38,070 +that is trained for that task explicitly. + +78 +00:03:38,070 --> 00:03:40,560 +We use the encoder to +create a representation + +79 +00:03:40,560 --> 00:03:42,240 +of the English sentence. + +80 +00:03:42,240 --> 00:03:44,730 +We cast this to the decoder, + +81 +00:03:44,730 --> 00:03:46,620 +with the use of the +start of sequence word, + +82 +00:03:46,620 --> 00:03:49,173 +we ask it to output the first word. + +83 +00:03:50,029 --> 00:03:53,607 +It outputs bienvenue, which means welcome. + +84 +00:03:53,607 --> 00:03:56,640 +And we then use bienvenue + +85 +00:03:56,640 --> 00:03:59,283 +as the input sequence for the decoder. + +86 +00:04:00,188 --> 00:04:04,470 +This, alongside the encoder +numerical representation, + +87 +00:04:04,470 --> 00:04:07,440 +allows the decoder to +predict the second word, Ã, + +88 +00:04:07,440 --> 00:04:09,240 +which is to in English. + +89 +00:04:09,240 --> 00:04:13,590 +Finally, we ask the decoder +to predict a third word + +90 +00:04:13,590 --> 00:04:15,330 +It predicts NYC, which is correct. + +91 +00:04:15,330 --> 00:04:18,288 +We've translated the sentence. + +92 +00:04:18,288 --> 00:04:20,760 +Where the encoder-decoder really shines, + +93 +00:04:20,760 --> 00:04:23,550 +is that we have an encoder and a decoder, + +94 +00:04:23,550 --> 00:04:25,323 +which often do not share weights. + +95 +00:04:26,256 --> 00:04:29,460 +Therefore, we have an +entire block, the encoder, + +96 +00:04:29,460 --> 00:04:31,650 +that can be trained to +understand the sequence + +97 +00:04:31,650 --> 00:04:34,290 +and extract the relevant information. + +98 +00:04:34,290 --> 00:04:36,450 +For the translation +scenario we've seen earlier, + +99 +00:04:36,450 --> 00:04:38,760 +for example, this would mean parsing + +100 +00:04:38,760 --> 00:04:42,003 +and understanding what was +said in the English language. + +101 +00:04:42,900 --> 00:04:45,960 +It would mean extracting +information from that language, + +102 +00:04:45,960 --> 00:04:49,413 +and putting all of that in a +vector dense in information. + +103 +00:04:50,361 --> 00:04:53,370 +On the other hand, we have the decoder, + +104 +00:04:53,370 --> 00:04:56,850 +whose sole purpose is to decode +the numerical representation + +105 +00:04:56,850 --> 00:04:58,203 +output by the encoder. + +106 +00:04:59,460 --> 00:05:01,170 +This decoder can be specialized + +107 +00:05:01,170 --> 00:05:02,970 +in a completely different language, + +108 +00:05:02,970 --> 00:05:05,403 +or even modality like images or speech. + +109 +00:05:07,170 --> 00:05:10,473 +Encoders-decoders are +special for several reasons. + +110 +00:05:11,310 --> 00:05:15,570 +Firstly, they're able to manage +sequence to sequence tasks, + +111 +00:05:15,570 --> 00:05:18,358 +like translation that we have just seen. + +112 +00:05:18,358 --> 00:05:20,940 +Secondly, the weights between the encoder + +113 +00:05:20,940 --> 00:05:24,540 +and the decoder parts are +not necessarily shared. + +114 +00:05:24,540 --> 00:05:27,172 +Let's take another example of translation. + +115 +00:05:27,172 --> 00:05:30,810 +Here we're translating +"Transformers are powerful" + +116 +00:05:30,810 --> 00:05:32,048 +in French. + +117 +00:05:32,048 --> 00:05:35,258 +Firstly, this means that from +a sequence of three words, + +118 +00:05:35,258 --> 00:05:39,030 +we're able to generate a +sequence of four words. + +119 +00:05:39,030 --> 00:05:42,480 +One could argue that this +could be handled with a decoder + +120 +00:05:42,480 --> 00:05:44,160 +that would generate the translation + +121 +00:05:44,160 --> 00:05:46,260 +in an auto-regressive manner, + +122 +00:05:46,260 --> 00:05:47,460 +and they would be right. + +123 +00:05:49,980 --> 00:05:51,930 +Another example of where +sequence to sequence + +124 +00:05:51,930 --> 00:05:54,810 +transformers shine is in summarization. + +125 +00:05:54,810 --> 00:05:58,379 +Here we have a very long +sequence, generally a full text, + +126 +00:05:58,379 --> 00:06:01,020 +and we want to summarize it. + +127 +00:06:01,020 --> 00:06:04,020 +Since the encoder and +decoders are separated, + +128 +00:06:04,020 --> 00:06:06,300 +we can have different context lengths. + +129 +00:06:06,300 --> 00:06:08,910 +For example, a very long +context for the encoder, + +130 +00:06:08,910 --> 00:06:10,230 +which handles the text, + +131 +00:06:10,230 --> 00:06:12,210 +and a smaller context for the decoder + +132 +00:06:12,210 --> 00:06:14,223 +which handles the summarized sequence. + +133 +00:06:16,470 --> 00:06:18,840 +There are a lot of sequence +to sequence models. + +134 +00:06:18,840 --> 00:06:20,310 +This contains a few examples + +135 +00:06:20,310 --> 00:06:22,500 +of popular encoder-decoder models + +136 +00:06:22,500 --> 00:06:24,400 +available in the transformers library. + +137 +00:06:25,829 --> 00:06:29,940 +Additionally, you can load +an encoder and a decoder + +138 +00:06:29,940 --> 00:06:32,130 +inside an encoder-decoder model. + +139 +00:06:32,130 --> 00:06:35,190 +Therefore, according to the +specific task you are targeting, + +140 +00:06:35,190 --> 00:06:38,700 +you may choose to use specific +encoders and decoders, + +141 +00:06:38,700 --> 00:06:42,613 +which have proven their worth +on these specific tasks. + +142 +00:06:42,613 --> 00:06:44,696 +(swoosh) + diff --git a/subtitles/en/08_what-happens-inside-the-pipeline-function-(pytorch).srt b/subtitles/en/08_what-happens-inside-the-pipeline-function-(pytorch).srt new file mode 100644 index 000000000..dc405bae7 --- /dev/null +++ b/subtitles/en/08_what-happens-inside-the-pipeline-function-(pytorch).srt @@ -0,0 +1,471 @@ +1 +00:00:00,554 --> 00:00:03,304 +(logo whooshing) + +2 +00:00:05,340 --> 00:00:07,563 +- What happens inside +the pipeline function? + +3 +00:00:08,760 --> 00:00:11,580 +In this video, we will look +at what actually happens + +4 +00:00:11,580 --> 00:00:13,080 +when we use the pipeline function + +5 +00:00:13,080 --> 00:00:15,090 +of the Transformers library. + +6 +00:00:15,090 --> 00:00:16,860 +More specifically, we will look + +7 +00:00:16,860 --> 00:00:19,200 +at the sentiment analysis pipeline, + +8 +00:00:19,200 --> 00:00:22,020 +and how it went from the +two following sentences, + +9 +00:00:22,020 --> 00:00:23,970 +to the positive and negative labels + +10 +00:00:23,970 --> 00:00:25,420 +with their respective scores. + +11 +00:00:26,760 --> 00:00:29,190 +As we have seen in the +pipeline presentation, + +12 +00:00:29,190 --> 00:00:31,860 +there are three stages in the pipeline. + +13 +00:00:31,860 --> 00:00:34,620 +First, we convert the raw texts to numbers + +14 +00:00:34,620 --> 00:00:37,173 +the model can make sense +of using a tokenizer. + +15 +00:00:38,010 --> 00:00:40,530 +Then those numbers go through the model, + +16 +00:00:40,530 --> 00:00:41,943 +which outputs logits. + +17 +00:00:42,780 --> 00:00:45,600 +Finally, the post-processing +steps transforms + +18 +00:00:45,600 --> 00:00:48,150 +those logits into labels and scores. + +19 +00:00:48,150 --> 00:00:50,700 +Let's look in detail at those three steps + +20 +00:00:50,700 --> 00:00:53,640 +and how to replicate them +using the Transformers library, + +21 +00:00:53,640 --> 00:00:56,043 +beginning with the first +stage, tokenization. + +22 +00:00:57,915 --> 00:01:00,360 +The tokenization process +has several steps. + +23 +00:01:00,360 --> 00:01:04,950 +First, the text is split into +small chunks called tokens. + +24 +00:01:04,950 --> 00:01:08,550 +They can be words, parts of +words or punctuation symbols. + +25 +00:01:08,550 --> 00:01:11,580 +Then the tokenizer will +had some special tokens, + +26 +00:01:11,580 --> 00:01:13,500 +if the model expect them. + +27 +00:01:13,500 --> 00:01:16,860 +Here the model uses expects +a CLS token at the beginning + +28 +00:01:16,860 --> 00:01:19,743 +and a SEP token at the end +of the sentence to classify. + +29 +00:01:20,580 --> 00:01:24,180 +Lastly, the tokenizer matches +each token to its unique ID + +30 +00:01:24,180 --> 00:01:27,000 +in the vocabulary of the pretrained model. + +31 +00:01:27,000 --> 00:01:28,680 +To load such a tokenizer, + +32 +00:01:28,680 --> 00:01:31,743 +the Transformers library +provides the AutoTokenizer API. + +33 +00:01:32,730 --> 00:01:36,120 +The most important method of +this class is from_pretrained, + +34 +00:01:36,120 --> 00:01:38,910 +which will download and +cache the configuration + +35 +00:01:38,910 --> 00:01:41,853 +and the vocabulary associated +to a given checkpoint. + +36 +00:01:43,200 --> 00:01:45,360 +Here the checkpoint used by default + +37 +00:01:45,360 --> 00:01:47,280 +for the sentiment analysis pipeline + +38 +00:01:47,280 --> 00:01:51,986 +is +distilbert-base-uncased-finetuned-sst-2-English. + +39 +00:01:51,986 --> 00:01:53,700 +(indistinct) + +40 +00:01:53,700 --> 00:01:56,490 +We instantiate a tokenizer +associated with that checkpoint, + +41 +00:01:56,490 --> 00:01:59,490 +then feed it the two sentences. + +42 +00:01:59,490 --> 00:02:02,100 +Since those two sentences +are not of the same size, + +43 +00:02:02,100 --> 00:02:03,930 +we will need to pad the shortest one + +44 +00:02:03,930 --> 00:02:06,030 +to be able to build an array. + +45 +00:02:06,030 --> 00:02:09,840 +This is done by the tokenizer +with the option, padding=True. + +46 +00:02:09,840 --> 00:02:12,810 +With truncation=True, we +ensure that any sentence + +47 +00:02:12,810 --> 00:02:15,873 +longer than the maximum the +model can handle is truncated. + +48 +00:02:17,010 --> 00:02:19,620 +Lastly, the return_tensors option + +49 +00:02:19,620 --> 00:02:22,323 +tells the tokenizer to +return a PyTorch tensor. + +50 +00:02:23,190 --> 00:02:25,590 +Looking at the result, we +see we have a dictionary + +51 +00:02:25,590 --> 00:02:26,670 +with two keys. + +52 +00:02:26,670 --> 00:02:29,970 +Input IDs contains the +IDs of both sentences, + +53 +00:02:29,970 --> 00:02:32,550 +with zero where the padding is applied. + +54 +00:02:32,550 --> 00:02:34,260 +The second key, attention mask, + +55 +00:02:34,260 --> 00:02:36,150 +indicates where padding has been applied, + +56 +00:02:36,150 --> 00:02:38,940 +so the model does not pay attention to it. + +57 +00:02:38,940 --> 00:02:42,090 +This is all what is inside +the tokenization step. + +58 +00:02:42,090 --> 00:02:46,289 +Now, let's have a look at +the second step, the model. + +59 +00:02:46,289 --> 00:02:47,952 +As for the tokenizer, + +60 +00:02:47,952 --> 00:02:51,133 +there is an AutoModel API +with a from_pretrained method. + +61 +00:02:51,133 --> 00:02:53,954 +It will download and cache +the configuration of the model + +62 +00:02:53,954 --> 00:02:56,280 +as well as the pretrained weights. + +63 +00:02:56,280 --> 00:02:58,200 +However, the AutoModel API + +64 +00:02:58,200 --> 00:03:00,630 +will only instantiate +the body of the model, + +65 +00:03:00,630 --> 00:03:03,420 +that is the part of the model that is left + +66 +00:03:03,420 --> 00:03:06,090 +once the pretraining head is removed. + +67 +00:03:06,090 --> 00:03:08,610 +It will output a high-dimensional tensor + +68 +00:03:08,610 --> 00:03:11,220 +that is a representation +of the sentences passed, + +69 +00:03:11,220 --> 00:03:12,690 +but which is not directly useful + +70 +00:03:12,690 --> 00:03:15,030 +for our classification problem. + +71 +00:03:15,030 --> 00:03:19,230 +Here the tensor has two +sentences, each of 16 tokens, + +72 +00:03:19,230 --> 00:03:23,433 +and the last dimension is the +hidden size of our model, 768. + +73 +00:03:24,900 --> 00:03:27,510 +To get an output linked to +our classification problem, + +74 +00:03:27,510 --> 00:03:31,170 +we need to use the +AutoModelForSequenceClassification class. + +75 +00:03:31,170 --> 00:03:33,330 +It works exactly as the AutoModel class, + +76 +00:03:33,330 --> 00:03:35,130 +except that it will build a model + +77 +00:03:35,130 --> 00:03:36,543 +with a classification head. + +78 +00:03:37,483 --> 00:03:39,560 +There is one auto class +for each common NLP task + +79 +00:03:39,560 --> 00:03:40,960 +in the Transformers library. + +80 +00:03:42,150 --> 00:03:45,570 +Here after giving our +model the two sentences, + +81 +00:03:45,570 --> 00:03:47,820 +we get a tensor of size two by two, + +82 +00:03:47,820 --> 00:03:50,943 +one result for each sentence +and for each possible label. + +83 +00:03:51,840 --> 00:03:53,970 +Those outputs are not probabilities yet, + +84 +00:03:53,970 --> 00:03:56,100 +we can see they don't sum to 1. + +85 +00:03:56,100 --> 00:03:57,270 +This is because each model + +86 +00:03:57,270 --> 00:04:00,810 +of the Transformers +library returns logits. + +87 +00:04:00,810 --> 00:04:02,250 +To make sense of those logits, + +88 +00:04:02,250 --> 00:04:05,910 +we need to dig into the third +and last step of the pipeline. + +89 +00:04:05,910 --> 00:04:10,620 +Post-processing, to convert +logits into probabilities, + +90 +00:04:10,620 --> 00:04:13,470 +we need to apply a SoftMax layers to them. + +91 +00:04:13,470 --> 00:04:14,610 +As we can see, + +92 +00:04:14,610 --> 00:04:17,267 +this transforms them into positive number + +93 +00:04:17,267 --> 00:04:18,663 +that sum up to one. + +94 +00:04:18,663 --> 00:04:21,360 +The last step is to know +which of those corresponds + +95 +00:04:21,360 --> 00:04:23,580 +to the positive or the negative label. + +96 +00:04:23,580 --> 00:04:28,020 +This is given by the id2label +field of the model config. + +97 +00:04:28,020 --> 00:04:30,390 +The first probabilities, index zero, + +98 +00:04:30,390 --> 00:04:32,250 +correspond to the negative label, + +99 +00:04:32,250 --> 00:04:34,140 +and the seconds, index one, + +100 +00:04:34,140 --> 00:04:36,480 +correspond to the positive label. + +101 +00:04:36,480 --> 00:04:37,950 +This is how our classifier built + +102 +00:04:37,950 --> 00:04:40,230 +with the pipeline function +picked those labels + +103 +00:04:40,230 --> 00:04:42,240 +and computed those scores. + +104 +00:04:42,240 --> 00:04:44,220 +Now that you know how each steps works, + +105 +00:04:44,220 --> 00:04:46,220 +you can easily tweak them to your needs. + +106 +00:04:47,524 --> 00:04:50,274 +(logo whooshing) + diff --git a/subtitles/en/09_what-happens-inside-the-pipeline-function-(tensorflow).srt b/subtitles/en/09_what-happens-inside-the-pipeline-function-(tensorflow).srt new file mode 100644 index 000000000..21c8e3de5 --- /dev/null +++ b/subtitles/en/09_what-happens-inside-the-pipeline-function-(tensorflow).srt @@ -0,0 +1,473 @@ +1 +00:00:00,397 --> 00:00:02,980 +(subtle blast) + +2 +00:00:05,490 --> 00:00:07,953 +- What happens inside +the pipeline function? + +3 +00:00:09,930 --> 00:00:13,050 +In this video, we will look +at what actually happens + +4 +00:00:13,050 --> 00:00:14,820 +when we use the pipeline function + +5 +00:00:14,820 --> 00:00:16,920 +of the Transformers library. + +6 +00:00:16,920 --> 00:00:18,930 +More specifically, we will look at + +7 +00:00:18,930 --> 00:00:21,030 +the sentiment analysis pipeline, + +8 +00:00:21,030 --> 00:00:23,760 +and how it went from the +two following sentences + +9 +00:00:23,760 --> 00:00:25,800 +to the positive and negative labels + +10 +00:00:25,800 --> 00:00:27,250 +with their respective scores. + +11 +00:00:28,740 --> 00:00:31,110 +As we have seen in the pipeline video, + +12 +00:00:31,110 --> 00:00:33,900 +there are three stages in the pipeline. + +13 +00:00:33,900 --> 00:00:36,810 +First, we convert the raw texts to numbers + +14 +00:00:36,810 --> 00:00:39,160 +the model can make sense +of, using a tokenizer. + +15 +00:00:40,140 --> 00:00:42,600 +Then, those numbers go through the model, + +16 +00:00:42,600 --> 00:00:44,550 +which outputs logits. + +17 +00:00:44,550 --> 00:00:47,190 +Finally, the post-processing steps + +18 +00:00:47,190 --> 00:00:49,490 +transforms those logits +into labels and score. + +19 +00:00:51,000 --> 00:00:52,590 +Let's look in detail at those three steps, + +20 +00:00:52,590 --> 00:00:55,200 +and how to replicate them +using the Transformers library, + +21 +00:00:55,200 --> 00:00:57,903 +beginning with the first +stage, tokenization. + +22 +00:00:59,905 --> 00:01:02,520 +The tokenization process +has several steps. + +23 +00:01:02,520 --> 00:01:06,900 +First, the text is split into +small chunks called token. + +24 +00:01:06,900 --> 00:01:09,933 +They can be words, parts of +words or punctuation symbols. + +25 +00:01:10,800 --> 00:01:14,310 +Then the tokenizer will +had some special tokens + +26 +00:01:14,310 --> 00:01:15,573 +if the model expect them. + +27 +00:01:16,440 --> 00:01:20,430 +Here, the model used expects +a CLS token at the beginning + +28 +00:01:20,430 --> 00:01:23,910 +and a SEP token at the end +of the sentence to classify. + +29 +00:01:23,910 --> 00:01:27,630 +Lastly, the tokenizer matches +each token to its unique ID + +30 +00:01:27,630 --> 00:01:29,730 +in the vocabulary of the pretrained model. + +31 +00:01:30,660 --> 00:01:32,040 +To load such a tokenizer, + +32 +00:01:32,040 --> 00:01:34,983 +the Transformers library +provides the AutoTokenizer API. + +33 +00:01:35,880 --> 00:01:39,510 +The most important method of +this class is from_pretrained, + +34 +00:01:39,510 --> 00:01:41,940 +which will download and +cache the configuration + +35 +00:01:41,940 --> 00:01:44,913 +and the vocabulary associated +to a given checkpoint. + +36 +00:01:46,410 --> 00:01:48,180 +Here, the checkpoint used by default + +37 +00:01:48,180 --> 00:01:50,310 +for the sentiment analysis pipeline + +38 +00:01:50,310 --> 00:01:54,510 +is distilbert base uncased +finetuned sst2 English, + +39 +00:01:54,510 --> 00:01:55,960 +which is a bit of a mouthful. + +40 +00:01:56,820 --> 00:01:59,760 +We instantiate a tokenizer +associated with that checkpoint, + +41 +00:01:59,760 --> 00:02:01,833 +then feed it the two sentences. + +42 +00:02:02,790 --> 00:02:05,490 +Since those two sentences +are not of the same size, + +43 +00:02:05,490 --> 00:02:07,440 +we will need to pad the shortest one + +44 +00:02:07,440 --> 00:02:09,570 +to be able to build an array. + +45 +00:02:09,570 --> 00:02:10,403 +This is done by the tokenizer + +46 +00:02:10,403 --> 00:02:12,603 +with the option padding=True. + +47 +00:02:14,130 --> 00:02:17,340 +With truncation=True, we +ensure that any sentence longer + +48 +00:02:17,340 --> 00:02:19,953 +than the maximum the model +can handle is truncated. + +49 +00:02:20,820 --> 00:02:24,200 +Lastly, the return_tensors +option tells the tokenizer + +50 +00:02:24,200 --> 00:02:25,773 +to return a PyTorch tensor. + +51 +00:02:26,910 --> 00:02:28,050 +Looking at the result, + +52 +00:02:28,050 --> 00:02:30,450 +we see we have a dictionary with two keys. + +53 +00:02:30,450 --> 00:02:33,840 +Input IDs contains the +IDs of both sentences, + +54 +00:02:33,840 --> 00:02:35,840 +with zeros where the padding is applied. + +55 +00:02:36,750 --> 00:02:38,550 +The second key, attention mask, + +56 +00:02:38,550 --> 00:02:40,650 +indicates where padding has been applied, + +57 +00:02:40,650 --> 00:02:42,750 +so the model does not pay attention to it. + +58 +00:02:43,590 --> 00:02:46,380 +This is all what is inside +the tokenization step. + +59 +00:02:46,380 --> 00:02:49,653 +Now let's have a look at +the second step, the model. + +60 +00:02:51,090 --> 00:02:53,850 +As for the tokenizer, +there is an AutoModel API, + +61 +00:02:53,850 --> 00:02:55,890 +with a from_pretrained method. + +62 +00:02:55,890 --> 00:02:59,100 +It will download and cache +the configuration of the model + +63 +00:02:59,100 --> 00:03:01,560 +as well as the pretrained weights. + +64 +00:03:01,560 --> 00:03:04,830 +However, the AutoModel +API will only instantiate + +65 +00:03:04,830 --> 00:03:06,540 +the body of the model, + +66 +00:03:06,540 --> 00:03:09,120 +that is, the part of +the model that is left + +67 +00:03:09,120 --> 00:03:11,103 +once the pretraining head is removed. + +68 +00:03:12,210 --> 00:03:14,460 +It will output a high-dimensional tensor + +69 +00:03:14,460 --> 00:03:17,190 +that is a representation +of the sentences passed, + +70 +00:03:17,190 --> 00:03:18,930 +but which is not directly useful + +71 +00:03:18,930 --> 00:03:20,480 +for our classification problem. + +72 +00:03:21,930 --> 00:03:24,210 +Here the tensor has two sentences, + +73 +00:03:24,210 --> 00:03:26,070 +each of sixteen token, + +74 +00:03:26,070 --> 00:03:30,393 +and the last dimension is the +hidden size of our model, 768. + +75 +00:03:31,620 --> 00:03:34,020 +To get an output linked to +our classification problem, + +76 +00:03:34,020 --> 00:03:37,800 +we need to use the +AutoModelForSequenceClassification class. + +77 +00:03:37,800 --> 00:03:40,170 +It works exactly as the AutoModel class, + +78 +00:03:40,170 --> 00:03:41,970 +except that it will build a model + +79 +00:03:41,970 --> 00:03:43,353 +with a classification head. + +80 +00:03:44,520 --> 00:03:46,770 +There is one auto class +for each common NLP task + +81 +00:03:46,770 --> 00:03:48,170 +in the Transformers library. + +82 +00:03:49,050 --> 00:03:52,380 +Here, after giving our +model the two sentences, + +83 +00:03:52,380 --> 00:03:54,600 +we get a tensor of size two by two; + +84 +00:03:54,600 --> 00:03:57,783 +one result for each sentence +and for each possible label. + +85 +00:03:59,100 --> 00:04:01,470 +Those outputs are not probabilities yet. + +86 +00:04:01,470 --> 00:04:03,660 +We can see they don't sum to 1. + +87 +00:04:03,660 --> 00:04:06,090 +This is because each model +of the Transformers library + +88 +00:04:06,090 --> 00:04:07,830 +returns logits. + +89 +00:04:07,830 --> 00:04:09,480 +To make sense of those logits, + +90 +00:04:09,480 --> 00:04:10,980 +we need to dig into the third + +91 +00:04:10,980 --> 00:04:13,653 +and last step of the +pipeline, post-processing. + +92 +00:04:15,300 --> 00:04:17,310 +To convert logits into probabilities, + +93 +00:04:17,310 --> 00:04:19,950 +we need to apply a SoftMax layer to them. + +94 +00:04:19,950 --> 00:04:22,800 +As we can see, this transforms +them into positive numbers + +95 +00:04:22,800 --> 00:04:23,793 +that sum up to 1. + +96 +00:04:24,990 --> 00:04:27,030 +The last step is to know +which of those corresponds + +97 +00:04:27,030 --> 00:04:29,400 +to the positive or the negative label. + +98 +00:04:29,400 --> 00:04:33,480 +This is given by the id2label +field of the model config. + +99 +00:04:33,480 --> 00:04:36,000 +The first probabilities, index 0, + +100 +00:04:36,000 --> 00:04:37,740 +correspond to the negative label, + +101 +00:04:37,740 --> 00:04:42,060 +and the seconds, index 1, +correspond to the positive label. + +102 +00:04:42,060 --> 00:04:43,830 +This is how our classifier built + +103 +00:04:43,830 --> 00:04:46,260 +with the pipeline function +picked those labels + +104 +00:04:46,260 --> 00:04:47,560 +and computed those scores. + +105 +00:04:48,420 --> 00:04:50,400 +Now that you know how each steps works, + +106 +00:04:50,400 --> 00:04:52,533 +you can easily tweak them to your needs. + +107 +00:04:55,314 --> 00:04:57,897 +(subtle blast) + diff --git a/subtitles/en/10_instantiate-a-transformers-model-(pytorch).srt b/subtitles/en/10_instantiate-a-transformers-model-(pytorch).srt new file mode 100644 index 000000000..d29c6933f --- /dev/null +++ b/subtitles/en/10_instantiate-a-transformers-model-(pytorch).srt @@ -0,0 +1,308 @@ +1 +00:00:00,519 --> 00:00:03,186 +(logo swooshes) + +2 +00:00:05,310 --> 00:00:08,483 +- How to instantiate a Transformers model. + +3 +00:00:08,483 --> 00:00:11,790 +In this video, we'll look at +how we can create a user model + +4 +00:00:11,790 --> 00:00:13,290 +from the Transformers library. + +5 +00:00:14,310 --> 00:00:17,100 +As we have seen before +the AutoModel class allows + +6 +00:00:17,100 --> 00:00:19,140 +you to instantiate a pretrained model + +7 +00:00:19,140 --> 00:00:21,513 +from any checkpoint on +the Hugging Face Hub. + +8 +00:00:22,350 --> 00:00:23,910 +It'll pick the right model class + +9 +00:00:23,910 --> 00:00:26,654 +from the library to instantiate +the proper architecture + +10 +00:00:26,654 --> 00:00:29,793 +and loads of weights as the +pretrained model inside. + +11 +00:00:30,690 --> 00:00:33,810 +As we can see, when +given a BERT checkpoint + +12 +00:00:33,810 --> 00:00:38,043 +we end up with a BertModel and +similarly, for GPT-2 or BART. + +13 +00:00:40,020 --> 00:00:42,360 +Behind the scenes,this +API can take the name + +14 +00:00:42,360 --> 00:00:44,250 +of a checkpoint on the Hub + +15 +00:00:44,250 --> 00:00:46,980 +in which case it will download +and cache the configuration + +16 +00:00:46,980 --> 00:00:48,843 +file as well as a model weights file. + +17 +00:00:49,698 --> 00:00:52,710 +You can also specify the +path to a local folder + +18 +00:00:52,710 --> 00:00:55,290 +that contains a valid +configuration file and a + +19 +00:00:55,290 --> 00:00:56,390 +model of weights file. + +20 +00:00:57,600 --> 00:00:59,479 +To instantiate the pretrained model, + +21 +00:00:59,479 --> 00:01:01,950 +the AutoModel API will +first open the configuration + +22 +00:01:01,950 --> 00:01:05,403 +file to look at a configuration +class that should be used. + +23 +00:01:06,420 --> 00:01:08,580 +The configuration class +depends on the type + +24 +00:01:08,580 --> 00:01:12,663 +of the model BERT, GPT-2 +or BART for instance. + +25 +00:01:13,680 --> 00:01:15,930 +Once it has a proper configuration class, + +26 +00:01:15,930 --> 00:01:18,390 +it can instantiate that configuration + +27 +00:01:18,390 --> 00:01:21,900 +which is a blueprint to know +how to create the model. + +28 +00:01:21,900 --> 00:01:24,240 +It also uses this configuration class to + +29 +00:01:24,240 --> 00:01:27,150 +find the proper model class, +which is then combined + +30 +00:01:27,150 --> 00:01:29,823 +with the loaded configuration +to load the model. + +31 +00:01:30,904 --> 00:01:33,210 +This model is not yet a pretrained model + +32 +00:01:33,210 --> 00:01:35,883 +as it has just been initialized +with random weights. + +33 +00:01:36,840 --> 00:01:39,810 +The last step is to load the +weight from the model file + +34 +00:01:39,810 --> 00:01:40,923 +inside this model. + +35 +00:01:42,330 --> 00:01:44,250 +To easily load the +configuration of a model + +36 +00:01:44,250 --> 00:01:46,410 +from any checkpoint or folder containing + +37 +00:01:46,410 --> 00:01:48,210 +the configuration file. + +38 +00:01:48,210 --> 00:01:50,373 +We can use the AutoConfig class. + +39 +00:01:51,240 --> 00:01:52,693 +Like the AutoModel class, + +40 +00:01:52,693 --> 00:01:55,693 +it will pick the right configuration +class from the library. + +41 +00:01:57,060 --> 00:01:59,220 +We can also use a specific +class corresponding + +42 +00:01:59,220 --> 00:02:01,470 +to a checkpoint, but +we will need to change + +43 +00:02:01,470 --> 00:02:03,000 +the code each time we want to try + +44 +00:02:03,000 --> 00:02:04,550 +a different model architecture. + +45 +00:02:06,030 --> 00:02:07,860 +As we said before, the configuration + +46 +00:02:07,860 --> 00:02:10,350 +of a model is a blueprint +that contains all the + +47 +00:02:10,350 --> 00:02:13,830 +information necessary to +create the model architecture. + +48 +00:02:13,830 --> 00:02:15,990 +For instance, the BERT model associated + +49 +00:02:15,990 --> 00:02:19,980 +with the bert-base-cased +checkpoint has 12 layers, + +50 +00:02:19,980 --> 00:02:24,980 +a hidden side of 768 and a +vocabulary side of 28,996. + +51 +00:02:28,020 --> 00:02:29,910 +Once we have the configuration, + +52 +00:02:29,910 --> 00:02:31,950 +we can create a model that +does the same architecture + +53 +00:02:31,950 --> 00:02:35,280 +as our checkpoint, but +is randomly initialized. + +54 +00:02:35,280 --> 00:02:36,660 +We can then train it from scratch. + +55 +00:02:36,660 --> 00:02:38,010 +Like any bio PyTorch module + +56 +00:02:39,497 --> 00:02:40,380 +We can also change any part + +57 +00:02:40,380 --> 00:02:43,200 +of the configuration by +using keyword arguments. + +58 +00:02:43,200 --> 00:02:46,138 +The second snippet of code instantiates + +59 +00:02:46,138 --> 00:02:48,360 +a randomly initialized BERT model + +60 +00:02:48,360 --> 00:02:50,403 +with 10 layers instead of 12. + +61 +00:02:51,409 --> 00:02:55,051 +Saving a model once it's trained +or fine-tuned is very easy. + +62 +00:02:55,051 --> 00:02:57,603 +We just have to use a +safe pretrained method. + +63 +00:02:58,500 --> 00:03:01,417 +Here the model will be +saved in a folder named + +64 +00:03:01,417 --> 00:03:04,473 +"my-bert-model" inside the +current working directory. + +65 +00:03:05,400 --> 00:03:08,255 +Such a model can then be +reloaded using the form + +66 +00:03:08,255 --> 00:03:09,596 +pretrained method. + +67 +00:03:09,596 --> 00:03:11,250 +To learn how to easily approach this model + +68 +00:03:11,250 --> 00:03:13,473 +to that, check out the push to a video. + diff --git a/subtitles/en/11_instantiate-a-transformers-model-(tensorflow).srt b/subtitles/en/11_instantiate-a-transformers-model-(tensorflow).srt new file mode 100644 index 000000000..17a04807a --- /dev/null +++ b/subtitles/en/11_instantiate-a-transformers-model-(tensorflow).srt @@ -0,0 +1,317 @@ +1 +00:00:00,125 --> 00:00:02,958 +(whooshing sound) + +2 +00:00:05,463 --> 00:00:08,820 +- How to instantiate +the Transformers model? + +3 +00:00:08,820 --> 00:00:11,250 +In this video, we will +look at how we can create + +4 +00:00:11,250 --> 00:00:13,550 +and use a model from the +Transformers library. + +5 +00:00:15,000 --> 00:00:17,850 +As we've seen before, +the TFAutoModel class + +6 +00:00:17,850 --> 00:00:20,100 +allows you to instantiate +a pre-trained model + +7 +00:00:20,100 --> 00:00:22,503 +from any checkpoint on +the Hugging Face Hub. + +8 +00:00:23,430 --> 00:00:25,620 +It will pick the right +model class from the library + +9 +00:00:25,620 --> 00:00:27,750 +to instantiate the proper architecture + +10 +00:00:27,750 --> 00:00:31,200 +and load the weights of the +pre-trained model inside. + +11 +00:00:31,200 --> 00:00:34,020 +As we can see, when +given a BERT checkpoint, + +12 +00:00:34,020 --> 00:00:36,090 +we end up with a TFBertModel, + +13 +00:00:36,090 --> 00:00:38,553 +and similarly for GPT2 or BART. + +14 +00:00:40,170 --> 00:00:42,510 +Behind the scenes, this +API can take the name + +15 +00:00:42,510 --> 00:00:44,040 +of a checkpoint on the Hub, + +16 +00:00:44,040 --> 00:00:45,810 +in which case it will download and cache + +17 +00:00:45,810 --> 00:00:48,660 +the configuration file as well +as the model weights file. + +18 +00:00:49,590 --> 00:00:52,020 +You can also specify the +path to a local folder + +19 +00:00:52,020 --> 00:00:54,090 +that contains a valid configuration file + +20 +00:00:54,090 --> 00:00:55,340 +and a model weights file. + +21 +00:00:56,670 --> 00:00:58,167 +To instantiate the pre-trained model, + +22 +00:00:58,167 --> 00:01:02,400 +the TFAutoModel API will first +open the configuration file + +23 +00:01:02,400 --> 00:01:05,253 +to look at the configuration +class that should be used. + +24 +00:01:06,390 --> 00:01:09,660 +The configuration class depends +on the type of the model, + +25 +00:01:09,660 --> 00:01:12,333 +BERT, GPT2 or BART for instance. + +26 +00:01:13,320 --> 00:01:15,720 +Once it has the proper +configuration class, + +27 +00:01:15,720 --> 00:01:18,000 +it can instantiate that configuration, + +28 +00:01:18,000 --> 00:01:21,090 +which is a blueprint to know +how to create the model. + +29 +00:01:21,090 --> 00:01:22,770 +It also uses this configuration class + +30 +00:01:22,770 --> 00:01:24,750 +to find the proper model class, + +31 +00:01:24,750 --> 00:01:27,120 +which is combined with +the loaded configuration + +32 +00:01:27,120 --> 00:01:28,143 +to load the model. + +33 +00:01:29,250 --> 00:01:31,800 +This model is not yet +our pre-trained model + +34 +00:01:31,800 --> 00:01:34,560 +as it has just been initialized +with random weights. + +35 +00:01:34,560 --> 00:01:36,690 +The last step is to load the weights + +36 +00:01:36,690 --> 00:01:38,973 +from the model file inside this model. + +37 +00:01:40,230 --> 00:01:42,270 +To easily load the +configuration of a model + +38 +00:01:42,270 --> 00:01:44,220 +from any checkpoint or a folder + +39 +00:01:44,220 --> 00:01:46,170 +containing the configuration file, + +40 +00:01:46,170 --> 00:01:47,790 +we can use the AutoConfig class. + +41 +00:01:47,790 --> 00:01:50,460 +Like the TFAutoModel class, + +42 +00:01:50,460 --> 00:01:54,210 +it will pick the right configuration +class from the library. + +43 +00:01:54,210 --> 00:01:56,040 +We can also use the specific class + +44 +00:01:56,040 --> 00:01:57,840 +corresponding to a checkpoint, + +45 +00:01:57,840 --> 00:01:59,430 +but we will need to change the code + +46 +00:01:59,430 --> 00:02:02,230 +each time we want to try a +different model architecture. + +47 +00:02:03,180 --> 00:02:05,353 +As we said before, the +configuration of a model + +48 +00:02:05,353 --> 00:02:08,610 +is a blueprint that contains +all the information necessary + +49 +00:02:08,610 --> 00:02:11,070 +to create the model architecture. + +50 +00:02:11,070 --> 00:02:12,750 +For instance, the BERT model + +51 +00:02:12,750 --> 00:02:15,510 +associated with the +bert-base-cased checkpoint + +52 +00:02:15,510 --> 00:02:19,710 +has 12 layers, a hidden size of 768, + +53 +00:02:19,710 --> 00:02:23,403 +and a vocabulary size of 28,996. + +54 +00:02:24,810 --> 00:02:26,670 +Once we have the configuration, + +55 +00:02:26,670 --> 00:02:28,890 +we can create a model that +has the same architecture + +56 +00:02:28,890 --> 00:02:32,160 +as our checkpoint but +is randomly initialized. + +57 +00:02:32,160 --> 00:02:36,030 +We can then train it from scratch +like any TensorFlow model. + +58 +00:02:36,030 --> 00:02:38,063 +We can also change any +part of the configuration + +59 +00:02:38,063 --> 00:02:40,770 +by using keyword arguments. + +60 +00:02:40,770 --> 00:02:43,110 +The second snippet of code instantiates + +61 +00:02:43,110 --> 00:02:44,970 +a randomly initialized BERT model + +62 +00:02:44,970 --> 00:02:46,983 +with 10 layers instead of 12. + +63 +00:02:48,240 --> 00:02:51,360 +Saving a model once it's trained +or fine-tuned is very easy. + +64 +00:02:51,360 --> 00:02:53,880 +We just have to use the +save_pretrained method. + +65 +00:02:53,880 --> 00:02:55,980 +Here, the model will be saved in a folder + +66 +00:02:55,980 --> 00:02:59,463 +named my-bert-model inside +the current working directory. + +67 +00:03:00,480 --> 00:03:02,250 +Such a model can then be reloaded + +68 +00:03:02,250 --> 00:03:04,500 +using the from_pretrained method. + +69 +00:03:04,500 --> 00:03:06,600 +To run it to a projects model to the Hub, + +70 +00:03:06,600 --> 00:03:08,350 +check out the push (mumbles) video. + +71 +00:03:09,355 --> 00:03:12,188 +(whooshing sound) + diff --git a/subtitles/en/12_tokenizers-overview.srt b/subtitles/en/12_tokenizers-overview.srt new file mode 100644 index 000000000..cc5880413 --- /dev/null +++ b/subtitles/en/12_tokenizers-overview.srt @@ -0,0 +1,99 @@ +1 +00:00:00,450 --> 00:00:01,509 +(intro whooshing) + +2 +00:00:01,509 --> 00:00:02,720 +(smiley snapping) + +3 +00:00:02,720 --> 00:00:03,930 +(words whooshing) + +4 +00:00:03,930 --> 00:00:04,920 +- In the next few videos, + +5 +00:00:04,920 --> 00:00:06,720 +we'll take a look at the tokenizers. + +6 +00:00:07,860 --> 00:00:09,240 +In natural language processing, + +7 +00:00:09,240 --> 00:00:12,930 +most of the data that we +handle consists of raw text. + +8 +00:00:12,930 --> 00:00:14,280 +However, machine learning models + +9 +00:00:14,280 --> 00:00:17,103 +cannot read or understand +text in its raw form, + +10 +00:00:18,540 --> 00:00:20,253 +they can only work with numbers. + +11 +00:00:21,360 --> 00:00:23,220 +So the tokenizer's objective + +12 +00:00:23,220 --> 00:00:25,923 +will be to translate +the text into numbers. + +13 +00:00:27,600 --> 00:00:30,240 +There are several possible +approaches to this conversion, + +14 +00:00:30,240 --> 00:00:31,110 +and the objective + +15 +00:00:31,110 --> 00:00:33,453 +is to find the most +meaningful representation. + +16 +00:00:36,240 --> 00:00:39,390 +We'll take a look at three +distinct tokenization algorithms. + +17 +00:00:39,390 --> 00:00:40,530 +We compare them one to one, + +18 +00:00:40,530 --> 00:00:42,600 +so we recommend you take +a look at the videos + +19 +00:00:42,600 --> 00:00:44,040 +in the following order. + +20 +00:00:44,040 --> 00:00:45,390 +First, "Word-based," + +21 +00:00:45,390 --> 00:00:46,800 +followed by "Character-based," + +22 +00:00:46,800 --> 00:00:48,877 +and finally, "Subword-based." + +23 +00:00:48,877 --> 00:00:51,794 +(outro whooshing) + diff --git a/subtitles/en/13_word-based-tokenizers.srt b/subtitles/en/13_word-based-tokenizers.srt new file mode 100644 index 000000000..a2908fd3b --- /dev/null +++ b/subtitles/en/13_word-based-tokenizers.srt @@ -0,0 +1,264 @@ +1 +00:00:00,165 --> 00:00:01,416 +(screen whooshing) + +2 +00:00:01,416 --> 00:00:02,716 +(sticker popping) + +3 +00:00:02,716 --> 00:00:03,549 +(screen whooshing) + +4 +00:00:03,549 --> 00:00:05,603 +- Let's take a look at +word-based tokenization. + +5 +00:00:07,650 --> 00:00:09,780 +Word-based tokenization is the idea + +6 +00:00:09,780 --> 00:00:11,940 +of splitting the raw text into words + +7 +00:00:11,940 --> 00:00:14,673 +by splitting on spaces +or other specific rules, + +8 +00:00:16,020 --> 00:00:17,163 +like punctuation. + +9 +00:00:18,900 --> 00:00:21,810 +In this algorithm, each +word has a specific number + +10 +00:00:21,810 --> 00:00:23,463 +or ID attributed to it. + +11 +00:00:24,360 --> 00:00:27,270 +Here, let's has the ID 250, + +12 +00:00:27,270 --> 00:00:30,150 +do has 861, and tokenization + +13 +00:00:30,150 --> 00:00:33,393 +followed by an exclamation mark has 345. + +14 +00:00:34,380 --> 00:00:36,000 +This approach is interesting + +15 +00:00:36,000 --> 00:00:38,100 +as the model has representations + +16 +00:00:38,100 --> 00:00:40,233 +that are based on entire words. + +17 +00:00:42,720 --> 00:00:45,960 +The information held in +a single number is high, + +18 +00:00:45,960 --> 00:00:48,240 +as a word contains a lot of contextual + +19 +00:00:48,240 --> 00:00:49,803 +and semantic information. + +20 +00:00:53,070 --> 00:00:55,473 +However, this approach +does have its limits. + +21 +00:00:56,610 --> 00:01:00,570 +For example, the word dog and +the word dogs are very similar + +22 +00:01:00,570 --> 00:01:01,923 +and their meaning is close. + +23 +00:01:03,210 --> 00:01:05,550 +The word-based tokenization, however, + +24 +00:01:05,550 --> 00:01:08,520 +will attribute entirely +different IDs to these two words + +25 +00:01:08,520 --> 00:01:10,110 +and the model will therefore learn + +26 +00:01:10,110 --> 00:01:12,930 +two different embeddings +for these two words. + +27 +00:01:12,930 --> 00:01:15,090 +This is unfortunate as +we would like the model + +28 +00:01:15,090 --> 00:01:18,240 +to understand that these +words are indeed related, + +29 +00:01:18,240 --> 00:01:21,483 +and that dogs is simply the +plural form of the word dog. + +30 +00:01:22,980 --> 00:01:24,480 +Another issue with this approach, + +31 +00:01:24,480 --> 00:01:28,050 +is that there are a lot of +different words in the language. + +32 +00:01:28,050 --> 00:01:29,490 +If we want our model to understand + +33 +00:01:29,490 --> 00:01:32,160 +all possible sentences in that language, + +34 +00:01:32,160 --> 00:01:35,850 +then we will need to have an +ID for each different word. + +35 +00:01:35,850 --> 00:01:37,380 +And the total number of words, + +36 +00:01:37,380 --> 00:01:40,080 +which is also known as +the vocabulary size, + +37 +00:01:40,080 --> 00:01:41,913 +can quickly become very large. + +38 +00:01:44,400 --> 00:01:47,640 +This is an issue because each +ID is mapped to a large vector + +39 +00:01:47,640 --> 00:01:50,190 +that represents the word's meaning, + +40 +00:01:50,190 --> 00:01:52,170 +and keeping track of these mappings + +41 +00:01:52,170 --> 00:01:54,990 +requires an enormous number of weights + +42 +00:01:54,990 --> 00:01:57,123 +when the vocabulary size is very large. + +43 +00:01:59,160 --> 00:02:00,960 +If we want our models to stay lean, + +44 +00:02:00,960 --> 00:02:04,440 +we can opt for our tokenizer +to ignore certain words + +45 +00:02:04,440 --> 00:02:06,093 +that we don't necessarily need. + +46 +00:02:08,400 --> 00:02:11,970 +For example, here, when training +our tokenizer on a text, + +47 +00:02:11,970 --> 00:02:15,020 +we might want to take only +the 10,000 most frequent words + +48 +00:02:15,020 --> 00:02:16,320 +in that text. + +49 +00:02:16,320 --> 00:02:18,600 +Rather than taking all +words from in that text + +50 +00:02:18,600 --> 00:02:22,503 +or all languages words to +create our basic vocabulary. + +51 +00:02:23,790 --> 00:02:26,520 +The tokenizer will know how +to convert those 10,000 words + +52 +00:02:26,520 --> 00:02:29,370 +into numbers, but any other +word will be converted + +53 +00:02:29,370 --> 00:02:31,530 +to the out-of-vocabulary word, + +54 +00:02:31,530 --> 00:02:33,783 +or like shown here, the unknown word. + +55 +00:02:35,280 --> 00:02:37,440 +Unfortunately, this is a compromise. + +56 +00:02:37,440 --> 00:02:39,900 +The model will have the +exact same representation + +57 +00:02:39,900 --> 00:02:42,390 +for all words that it doesn't know, + +58 +00:02:42,390 --> 00:02:45,210 +which can result in a +lot of lost information + +59 +00:02:45,210 --> 00:02:47,664 +if many unknown words are present. + +60 +00:02:47,664 --> 00:02:50,581 +(screen whooshing) + diff --git a/subtitles/en/14_character-based-tokenizers.srt b/subtitles/en/14_character-based-tokenizers.srt new file mode 100644 index 000000000..c86407bd6 --- /dev/null +++ b/subtitles/en/14_character-based-tokenizers.srt @@ -0,0 +1,278 @@ +1 +00:00:00,234 --> 00:00:02,901 +(page whirring) + +2 +00:00:04,260 --> 00:00:07,200 +- Before diving in +character-based tokenization, + +3 +00:00:07,200 --> 00:00:10,350 +understanding why this kind +of tokenization is interesting + +4 +00:00:10,350 --> 00:00:13,533 +requires understanding the flaws +of word-based tokenization. + +5 +00:00:14,640 --> 00:00:16,320 +If you haven't seen the first video + +6 +00:00:16,320 --> 00:00:17,880 +on word-based tokenization + +7 +00:00:17,880 --> 00:00:21,450 +we recommend you check it out +before looking at this video. + +8 +00:00:21,450 --> 00:00:24,250 +Okay, let's take a look at +character-based tokenization. + +9 +00:00:25,650 --> 00:00:28,560 +We now split our text into +individual characters, + +10 +00:00:28,560 --> 00:00:29,673 +rather than words. + +11 +00:00:32,850 --> 00:00:35,550 +There are generally a lot of +different words in languages, + +12 +00:00:35,550 --> 00:00:37,743 +while the number of characters stays low. + +13 +00:00:38,610 --> 00:00:41,313 +To begin let's take a look +at the English language, + +14 +00:00:42,210 --> 00:00:45,540 +it has an estimated +170,000 different words, + +15 +00:00:45,540 --> 00:00:47,730 +so we would need a very large vocabulary + +16 +00:00:47,730 --> 00:00:49,413 +to encompass all words. + +17 +00:00:50,280 --> 00:00:52,200 +With a character-based vocabulary, + +18 +00:00:52,200 --> 00:00:55,440 +we can get by with only 256 characters, + +19 +00:00:55,440 --> 00:00:58,683 +which includes letters, +numbers and special characters. + +20 +00:00:59,760 --> 00:01:02,190 +Even languages with a lot +of different characters + +21 +00:01:02,190 --> 00:01:04,800 +like the Chinese languages +can have dictionaries + +22 +00:01:04,800 --> 00:01:08,130 +with up to 20,000 different characters + +23 +00:01:08,130 --> 00:01:11,523 +but more than 375,000 different words. + +24 +00:01:12,480 --> 00:01:14,310 +So character-based vocabularies + +25 +00:01:14,310 --> 00:01:16,293 +let us use fewer different tokens + +26 +00:01:16,293 --> 00:01:19,050 +than the word-based +tokenization dictionaries + +27 +00:01:19,050 --> 00:01:20,523 +we would otherwise use. + +28 +00:01:23,250 --> 00:01:25,830 +These vocabularies are also more complete + +29 +00:01:25,830 --> 00:01:28,950 +than their word-based +vocabularies counterparts. + +30 +00:01:28,950 --> 00:01:31,410 +As our vocabulary contains all characters + +31 +00:01:31,410 --> 00:01:33,960 +used in a language, even words unseen + +32 +00:01:33,960 --> 00:01:36,990 +during the tokenizer training +can still be tokenized, + +33 +00:01:36,990 --> 00:01:39,633 +so out-of-vocabulary tokens +will be less frequent. + +34 +00:01:40,680 --> 00:01:42,840 +This includes the ability +to correctly tokenize + +35 +00:01:42,840 --> 00:01:45,210 +misspelled words, rather +than discarding them + +36 +00:01:45,210 --> 00:01:46,623 +as unknown straight away. + +37 +00:01:48,240 --> 00:01:52,380 +However, this algorithm +isn't perfect either. + +38 +00:01:52,380 --> 00:01:54,360 +Intuitively, characters do not hold + +39 +00:01:54,360 --> 00:01:57,990 +as much information individually +as a word would hold. + +40 +00:01:57,990 --> 00:02:00,930 +For example, "Let's" +holds more information + +41 +00:02:00,930 --> 00:02:03,570 +than it's first letter "l". + +42 +00:02:03,570 --> 00:02:05,880 +Of course, this is not +true for all languages, + +43 +00:02:05,880 --> 00:02:08,880 +as some languages like +ideogram-based languages + +44 +00:02:08,880 --> 00:02:11,523 +have a lot of information +held in single characters, + +45 +00:02:12,750 --> 00:02:15,360 +but for others like roman-based languages, + +46 +00:02:15,360 --> 00:02:17,760 +the model will have to make +sense of multiple tokens + +47 +00:02:17,760 --> 00:02:20,670 +at a time to get the +information otherwise held + +48 +00:02:20,670 --> 00:02:21,753 +in a single word. + +49 +00:02:23,760 --> 00:02:27,000 +This leads to another issue +with character-based tokenizers, + +50 +00:02:27,000 --> 00:02:29,520 +their sequences are translated +into very large amount + +51 +00:02:29,520 --> 00:02:31,593 +of tokens to be processed by the model. + +52 +00:02:33,090 --> 00:02:36,810 +And this can have an impact +on the size of the context + +53 +00:02:36,810 --> 00:02:40,020 +the model will carry around, +and will reduce the size + +54 +00:02:40,020 --> 00:02:42,030 +of the text we can use +as input for our model, + +55 +00:02:42,030 --> 00:02:43,233 +which is often limited. + +56 +00:02:44,100 --> 00:02:46,650 +This tokenization, while +it has some issues, + +57 +00:02:46,650 --> 00:02:48,720 +has seen some very good +results in the past + +58 +00:02:48,720 --> 00:02:50,490 +and so it should be +considered when approaching + +59 +00:02:50,490 --> 00:02:52,680 +a new problem as it solves issues + +60 +00:02:52,680 --> 00:02:54,843 +encountered in the word-based algorithm. + +61 +00:02:56,107 --> 00:02:58,774 +(page whirring) + diff --git a/subtitles/en/15_subword-based-tokenizers.srt b/subtitles/en/15_subword-based-tokenizers.srt new file mode 100644 index 000000000..6c4ef7ff3 --- /dev/null +++ b/subtitles/en/15_subword-based-tokenizers.srt @@ -0,0 +1,323 @@ +1 +00:00:06,450 --> 00:00:09,540 +- Let's take a look at +subword based tokenization. + +2 +00:00:09,540 --> 00:00:11,610 +Understanding why subword +based tokenization is + +3 +00:00:11,610 --> 00:00:13,980 +interesting requires +understanding the flaws + +4 +00:00:13,980 --> 00:00:17,340 +of word based and corrector +based tokenization. + +5 +00:00:17,340 --> 00:00:18,780 +If you haven't seen the first videos + +6 +00:00:18,780 --> 00:00:22,020 +on word based and character +based tokenization + +7 +00:00:22,020 --> 00:00:23,130 +we recommend you check them + +8 +00:00:23,130 --> 00:00:24,780 +out before looking at this video. + +9 +00:00:27,840 --> 00:00:31,493 +Subword based tokenization +lies in between character based + +10 +00:00:31,493 --> 00:00:35,280 +and word based tokenization algorithms. + +11 +00:00:35,280 --> 00:00:37,410 +The idea is to find a middle ground + +12 +00:00:37,410 --> 00:00:39,486 +between very large vocabularies + +13 +00:00:39,486 --> 00:00:42,600 +a large quantity of out vocabulary tokens + +14 +00:00:42,600 --> 00:00:45,360 +and a loss of meaning +across very similar words + +15 +00:00:45,360 --> 00:00:48,630 +for word based tokenizers +and very long sequences + +16 +00:00:48,630 --> 00:00:51,330 +as well as less meaningful +individual tokens. + +17 +00:00:51,330 --> 00:00:53,133 +For character based tokenizers. + +18 +00:00:54,840 --> 00:00:57,960 +These algorithms rely on +the following principle. + +19 +00:00:57,960 --> 00:01:00,000 +Frequently used words should not be split + +20 +00:01:00,000 --> 00:01:01,500 +into smaller subwords + +21 +00:01:01,500 --> 00:01:03,433 +while rare words should be decomposed + +22 +00:01:03,433 --> 00:01:05,103 +into meaningful subwords. + +23 +00:01:06,510 --> 00:01:08,460 +An example is the word dog. + +24 +00:01:08,460 --> 00:01:11,190 +We would like to have our +tokenizer to have a single ID + +25 +00:01:11,190 --> 00:01:12,600 +for the word dog rather + +26 +00:01:12,600 --> 00:01:15,363 +than splitting it into +correctors D O and G. + +27 +00:01:16,650 --> 00:01:19,260 +However, when encountering the word dogs + +28 +00:01:19,260 --> 00:01:22,710 +we would like our tokenize to +understand that at the root + +29 +00:01:22,710 --> 00:01:24,120 +this is still the word dog. + +30 +00:01:24,120 --> 00:01:27,030 +With an added S, that +slightly changes the meaning + +31 +00:01:27,030 --> 00:01:28,923 +while keeping the original idea. + +32 +00:01:30,600 --> 00:01:34,080 +Another example is a complex +word like tokenization + +33 +00:01:34,080 --> 00:01:37,140 +which can be split into +meaningful subwords. + +34 +00:01:37,140 --> 00:01:37,973 +The root + +35 +00:01:37,973 --> 00:01:40,590 +of the word is token and +-ization completes the root + +36 +00:01:40,590 --> 00:01:42,870 +to give it a slightly different meaning. + +37 +00:01:42,870 --> 00:01:44,430 +It makes sense to split the word + +38 +00:01:44,430 --> 00:01:47,640 +into two, token as the root of the word, + +39 +00:01:47,640 --> 00:01:49,950 +labeled as the start of the word + +40 +00:01:49,950 --> 00:01:52,530 +and ization as additional +information labeled + +41 +00:01:52,530 --> 00:01:54,393 +as a completion of the word. + +42 +00:01:55,826 --> 00:01:58,740 +In turn, the model will +now be able to make sense + +43 +00:01:58,740 --> 00:02:01,080 +of token in different situations. + +44 +00:02:01,080 --> 00:02:04,602 +It will understand that the +word's token, tokens, tokenizing + +45 +00:02:04,602 --> 00:02:08,760 +and tokenization have a +similar meaning and are linked. + +46 +00:02:08,760 --> 00:02:12,450 +It's will also understand that +tokenization, modernization + +47 +00:02:12,450 --> 00:02:16,200 +and immunization, which +all have the same suffixes + +48 +00:02:16,200 --> 00:02:19,383 +are probably used in the +same syntactic situations. + +49 +00:02:20,610 --> 00:02:23,130 +Subword based tokenizers +generally have a way to + +50 +00:02:23,130 --> 00:02:25,890 +identify which tokens are a start of word + +51 +00:02:25,890 --> 00:02:28,443 +and which tokens complete start of words. + +52 +00:02:29,520 --> 00:02:31,140 +So here token as the start + +53 +00:02:31,140 --> 00:02:35,100 +of a ward and hash hash +ization as completion of award. + +54 +00:02:35,100 --> 00:02:38,103 +Here, the hash hash prefix +indicates that ization is part + +55 +00:02:38,103 --> 00:02:41,013 +of award rather than the beginning of it. + +56 +00:02:41,910 --> 00:02:43,110 +The hash hash comes + +57 +00:02:43,110 --> 00:02:47,013 +from the BERT tokenizer based +on the word piece algorithm. + +58 +00:02:47,850 --> 00:02:50,700 +Other tokenizes use other +prefixes which can be + +59 +00:02:50,700 --> 00:02:52,200 +placed to indicate part of words + +60 +00:02:52,200 --> 00:02:55,083 +like in here or start of words instead. + +61 +00:02:56,250 --> 00:02:57,083 +There are a lot + +62 +00:02:57,083 --> 00:02:58,740 +of different algorithms that can be used + +63 +00:02:58,740 --> 00:03:00,090 +for subword tokenization + +64 +00:03:00,090 --> 00:03:02,670 +and most models obtaining +state-of-the-art results + +65 +00:03:02,670 --> 00:03:03,780 +in English today + +66 +00:03:03,780 --> 00:03:06,663 +use some kind of subword +tokenization algorithms. + +67 +00:03:07,620 --> 00:03:10,953 +These approaches help in +reducing the vocabulary sizes + +68 +00:03:10,953 --> 00:03:13,636 +by sharing information +across different words + +69 +00:03:13,636 --> 00:03:15,960 +having the ability to have prefixes + +70 +00:03:15,960 --> 00:03:18,630 +and suffixes understood as such. + +71 +00:03:18,630 --> 00:03:20,700 +They keep meaning across +very similar words + +72 +00:03:20,700 --> 00:03:23,103 +by recognizing similar +tokens, making them up. + diff --git a/subtitles/en/16_the-tokenization-pipeline.srt b/subtitles/en/16_the-tokenization-pipeline.srt new file mode 100644 index 000000000..f0da01106 --- /dev/null +++ b/subtitles/en/16_the-tokenization-pipeline.srt @@ -0,0 +1,339 @@ +1 +00:00:00,479 --> 00:00:03,396 +(object whooshing) + +2 +00:00:05,610 --> 00:00:06,873 +- The tokenizer pipeline. + +3 +00:00:07,920 --> 00:00:10,570 +In this video, we'll look +at how a tokenizer converts + +4 +00:00:11,433 --> 00:00:12,480 +raw texts to numbers, + +5 +00:00:12,480 --> 00:00:14,970 +that a Transformer +model can make sense of, + +6 +00:00:14,970 --> 00:00:16,520 +like when we execute this code. + +7 +00:00:17,760 --> 00:00:18,690 +Here is a quick overview + +8 +00:00:18,690 --> 00:00:21,630 +of what happens inside +the tokenizer object: + +9 +00:00:21,630 --> 00:00:24,360 +first, the text is split into tokens, + +10 +00:00:24,360 --> 00:00:27,453 +which are words, parts of +words, or punctuation symbols. + +11 +00:00:28,440 --> 00:00:31,500 +Then the tokenizer adds +potential special tokens + +12 +00:00:31,500 --> 00:00:34,680 +and converts each token to +their unique respective ID + +13 +00:00:34,680 --> 00:00:36,843 +as defined by the tokenizer's vocabulary. + +14 +00:00:37,710 --> 00:00:40,380 +As we'll see, it doesn't +quite happen in this order, + +15 +00:00:40,380 --> 00:00:43,233 +but doing it like this is +better for understandings. + +16 +00:00:44,280 --> 00:00:47,670 +The first step is to split +our input text into tokens. + +17 +00:00:47,670 --> 00:00:49,653 +We use the tokenize method for this. + +18 +00:00:50,550 --> 00:00:54,030 +To do that, the tokenizer may +first perform some operations, + +19 +00:00:54,030 --> 00:00:56,880 +like lowercasing all words, +then follow a set of rules + +20 +00:00:56,880 --> 00:00:59,283 +to split the result in +small chunks of text. + +21 +00:01:00,480 --> 00:01:02,286 +Most of the Transformer models uses + +22 +00:01:02,286 --> 00:01:04,890 +a word tokenization algorithm, which means + +23 +00:01:04,890 --> 00:01:06,750 +that one given word can be split + +24 +00:01:06,750 --> 00:01:10,050 +in several tokens like tokenize here. + +25 +00:01:10,050 --> 00:01:12,570 +Look at the "Tokenization +algorithms" video link below + +26 +00:01:12,570 --> 00:01:13,743 +for more information. + +27 +00:01:14,760 --> 00:01:17,820 +The # # prefix we see in front of ize is + +28 +00:01:17,820 --> 00:01:19,830 +a convention used by Bert to indicate + +29 +00:01:19,830 --> 00:01:22,762 +this token is not the +beginning of the word. + +30 +00:01:22,762 --> 00:01:26,310 +Other tokenizers may use +different conventions however: + +31 +00:01:26,310 --> 00:01:29,984 +for instance, ALBERT tokenizers +will add a long underscore + +32 +00:01:29,984 --> 00:01:31,620 +in front of all the tokens + +33 +00:01:31,620 --> 00:01:34,920 +that added space before them, +which is a convention shared + +34 +00:01:34,920 --> 00:01:37,700 +by all sentencepiece tokenizers. + +35 +00:01:38,580 --> 00:01:41,040 +The second step of the +tokenization pipeline is + +36 +00:01:41,040 --> 00:01:43,470 +to map those tokens to +their respective IDs + +37 +00:01:43,470 --> 00:01:45,770 +as defined by the +vocabulary of the tokenizer. + +38 +00:01:46,770 --> 00:01:48,690 +This is why we need to download the file + +39 +00:01:48,690 --> 00:01:50,580 +when we instantiate a tokenizer + +40 +00:01:50,580 --> 00:01:52,400 +with the from_pretrained method. + +41 +00:01:52,400 --> 00:01:54,390 +We have to make sure +we use the same mapping + +42 +00:01:54,390 --> 00:01:56,520 +as when the model was pretrained. + +43 +00:01:56,520 --> 00:01:59,703 +To do this, we use the +convert_tokens_to_ids method. + +44 +00:02:01,050 --> 00:02:01,883 +We may have noticed + +45 +00:02:01,883 --> 00:02:03,540 +that we don't have the exact same results + +46 +00:02:03,540 --> 00:02:05,580 +as in our first slide, or not + +47 +00:02:05,580 --> 00:02:07,920 +as this looks like a list +of random numbers anyway, + +48 +00:02:07,920 --> 00:02:10,680 +in which case, allow me +to refresh your memory. + +49 +00:02:10,680 --> 00:02:12,350 +We had a the number at +the beginning and a number + +50 +00:02:12,350 --> 00:02:17,130 +at the end that are missing, +those are the special tokens. + +51 +00:02:17,130 --> 00:02:20,340 +The special tokens are added +by the prepare_for_model method + +52 +00:02:20,340 --> 00:02:22,350 +which knows the indices of this token + +53 +00:02:22,350 --> 00:02:25,680 +in the vocabulary and just +adds the proper numbers. + +54 +00:02:25,680 --> 00:02:27,243 +in the input IDs list. + +55 +00:02:28,590 --> 00:02:29,541 +You can look at the special tokens + +56 +00:02:29,541 --> 00:02:30,990 +and, more generally, + +57 +00:02:30,990 --> 00:02:33,870 +at how the tokenizer +has changed your text, + +58 +00:02:33,870 --> 00:02:35,280 +by using the decode method + +59 +00:02:35,280 --> 00:02:37,503 +on the outputs of the tokenizer object. + +60 +00:02:38,490 --> 00:02:39,423 +As for the prefix for beginning + +61 +00:02:39,423 --> 00:02:44,160 +of words/ part of words, for +special tokens vary depending + +62 +00:02:44,160 --> 00:02:46,500 +on which tokenizer you're using. + +63 +00:02:46,500 --> 00:02:48,810 +So that tokenizer uses CLS and SEP, + +64 +00:02:48,810 --> 00:02:52,417 +but the roberta tokenizer +uses HTML-like anchors + +65 +00:02:52,417 --> 00:02:55,230 + and . + +66 +00:02:55,230 --> 00:02:57,090 +Now that you know how the tokenizer works, + +67 +00:02:57,090 --> 00:02:59,390 +you can forget all those +intermediate methods, + +68 +00:03:00,283 --> 00:03:01,650 +and then you remember that +you just have to call it + +69 +00:03:01,650 --> 00:03:02,913 +on your input texts. + +70 +00:03:03,870 --> 00:03:05,310 +The output of a tokenizer don't + +71 +00:03:05,310 --> 00:03:07,853 +just contain the input IDs, however. + +72 +00:03:07,853 --> 00:03:09,750 +To learn what the attention mask is, + +73 +00:03:09,750 --> 00:03:12,360 +check out the "Batch +input together" video. + +74 +00:03:12,360 --> 00:03:14,220 +To learn about token type IDs, + +75 +00:03:14,220 --> 00:03:16,570 +look at the "Process +pairs of sentences" video. + +76 +00:03:18,003 --> 00:03:20,920 +(object whooshing) + diff --git a/subtitles/en/17_batching-inputs-together-(pytorch).srt b/subtitles/en/17_batching-inputs-together-(pytorch).srt new file mode 100644 index 000000000..ca7f696c8 --- /dev/null +++ b/subtitles/en/17_batching-inputs-together-(pytorch).srt @@ -0,0 +1,291 @@ +1 +00:00:00,373 --> 00:00:02,956 +(subtle blast) + +2 +00:00:05,400 --> 00:00:07,590 +- How to batch inputs together. + +3 +00:00:07,590 --> 00:00:09,240 +In this video, we will see how + +4 +00:00:09,240 --> 00:00:11,073 +to batch input sequences together. + +5 +00:00:12,137 --> 00:00:15,420 +In general, the sentences we +want to pass through our model + +6 +00:00:15,420 --> 00:00:17,670 +won't all have the same lengths. + +7 +00:00:17,670 --> 00:00:19,740 +Here, we are using the model we saw + +8 +00:00:19,740 --> 00:00:22,080 +in the sentiment analysis pipeline + +9 +00:00:22,080 --> 00:00:24,063 +and want to classify two sentences. + +10 +00:00:24,900 --> 00:00:27,360 +When tokenizing them +and mapping each token + +11 +00:00:27,360 --> 00:00:29,610 +to its corresponding input IDs, + +12 +00:00:29,610 --> 00:00:31,593 +we get two lists of different lengths. + +13 +00:00:33,240 --> 00:00:35,340 +Trying to create a tensor or a NumPy array + +14 +00:00:35,340 --> 00:00:38,220 +from those two lists +will result in an error, + +15 +00:00:38,220 --> 00:00:41,043 +because all arrays and +tensors should be rectangular. + +16 +00:00:42,240 --> 00:00:44,160 +One way to overcome this limit + +17 +00:00:44,160 --> 00:00:45,690 +is to make the second sentence + +18 +00:00:45,690 --> 00:00:47,640 +the same length as the first + +19 +00:00:47,640 --> 00:00:50,463 +by adding a special token +as many times as necessary. + +20 +00:00:51,600 --> 00:00:53,970 +Another way would be to +truncate the first sequence + +21 +00:00:53,970 --> 00:00:55,710 +to the length of the second, + +22 +00:00:55,710 --> 00:00:58,140 +but we would them lose +a lot of information + +23 +00:00:58,140 --> 00:01:01,083 +that might be necessary to +properly classify the sentence. + +24 +00:01:02,190 --> 00:01:04,830 +In general, we only truncate sentences + +25 +00:01:04,830 --> 00:01:06,840 +when they are longer +than the maximum length + +26 +00:01:06,840 --> 00:01:08,073 +the model can handle. + +27 +00:01:09,720 --> 00:01:11,850 +The value used to pad the second sentence + +28 +00:01:11,850 --> 00:01:13,740 +should not be picked randomly; + +29 +00:01:13,740 --> 00:01:16,680 +the model has been pretrained +with a certain padding ID, + +30 +00:01:16,680 --> 00:01:19,533 +which you can find in +tokenizer.pad_token_id. + +31 +00:01:21,090 --> 00:01:22,800 +Now that we have padded our sentences, + +32 +00:01:22,800 --> 00:01:24,303 +we can make a batch with them. + +33 +00:01:25,380 --> 00:01:28,320 +If we pass the two sentences +to the model separately + +34 +00:01:28,320 --> 00:01:30,120 +and batched together however, + +35 +00:01:30,120 --> 00:01:32,100 +we notice that we don't +get the same results + +36 +00:01:32,100 --> 00:01:34,060 +for the sentence that is padded, + +37 +00:01:34,060 --> 00:01:35,403 +here, the second one. + +38 +00:01:36,390 --> 00:01:39,420 +It's at the back in the +Transformers Library? No. + +39 +00:01:39,420 --> 00:01:40,770 +If you remember that Transformer models + +40 +00:01:40,770 --> 00:01:42,810 +make heavy use of attention layers, + +41 +00:01:42,810 --> 00:01:45,210 +this should not come as a total surprise; + +42 +00:01:45,210 --> 00:01:48,277 +when computing the contextual +representation of each token, + +43 +00:01:48,277 --> 00:01:50,910 +the attention layers look +at all the other words + +44 +00:01:50,910 --> 00:01:52,410 +in the sentence. + +45 +00:01:52,410 --> 00:01:53,850 +If we have just the sentence + +46 +00:01:53,850 --> 00:01:56,970 +or the sentence with several +padding tokens added, + +47 +00:01:56,970 --> 00:01:59,073 +it's logical we don't get the same values. + +48 +00:02:00,270 --> 00:02:03,030 +To get the same results +with or without padding, + +49 +00:02:03,030 --> 00:02:05,340 +we need to indicate to +the attention layers + +50 +00:02:05,340 --> 00:02:08,070 +that they should ignore +those padding tokens. + +51 +00:02:08,070 --> 00:02:10,620 +This is done by creating +an attention mask, + +52 +00:02:10,620 --> 00:02:13,320 +a tensor with the same +shape as the input IDs, + +53 +00:02:13,320 --> 00:02:14,733 +with zeros and ones. + +54 +00:02:15,780 --> 00:02:18,120 +Ones indicate the tokens +the attention layers + +55 +00:02:18,120 --> 00:02:20,100 +should consider in the context + +56 +00:02:20,100 --> 00:02:22,100 +and zeros the tokens they should ignore. + +57 +00:02:23,520 --> 00:02:26,760 +Now, passing this attention +mask along with the input ID + +58 +00:02:26,760 --> 00:02:28,170 +will give us the same results + +59 +00:02:28,170 --> 00:02:31,170 +as when we sent the two sentences +individually to the model. + +60 +00:02:32,400 --> 00:02:34,950 +This is all done behind +the scenes by the tokenizer + +61 +00:02:34,950 --> 00:02:36,900 +when you apply it to several sentences + +62 +00:02:36,900 --> 00:02:38,613 +with the flag padding=True. + +63 +00:02:39,599 --> 00:02:41,490 +It will apply the padding +with the proper value + +64 +00:02:41,490 --> 00:02:43,140 +to the smaller sentences + +65 +00:02:43,140 --> 00:02:45,423 +and create the appropriate attention mask. + +66 +00:02:46,993 --> 00:02:49,576 +(subtle blast) + diff --git a/subtitles/en/18_batching-inputs-together-(tensorflow).srt b/subtitles/en/18_batching-inputs-together-(tensorflow).srt new file mode 100644 index 000000000..c31449704 --- /dev/null +++ b/subtitles/en/18_batching-inputs-together-(tensorflow).srt @@ -0,0 +1,281 @@ +1 +00:00:00,458 --> 00:00:02,791 +(logo whooshes) + +2 +00:00:05,310 --> 00:00:07,590 +- How to batch inputs together. + +3 +00:00:07,590 --> 00:00:09,150 +In this video, we'll see + +4 +00:00:09,150 --> 00:00:11,050 +how to batch input sequences together. + +5 +00:00:12,630 --> 00:00:14,910 +In general, the sentences we want to pass + +6 +00:00:14,910 --> 00:00:18,000 +through our model won't +all have the same lengths. + +7 +00:00:18,000 --> 00:00:20,310 +Here, we are using the model we saw + +8 +00:00:20,310 --> 00:00:22,650 +in the sentiment analysis pipeline + +9 +00:00:22,650 --> 00:00:24,753 +and want to classify two sentences. + +10 +00:00:25,860 --> 00:00:27,870 +When tokenizing them +and mapping each token + +11 +00:00:27,870 --> 00:00:30,000 +to its corresponding input IDs, + +12 +00:00:30,000 --> 00:00:31,900 +we get two lists of different lengths. + +13 +00:00:33,360 --> 00:00:35,070 +Trying to create a tensor and NumPy array + +14 +00:00:35,070 --> 00:00:38,100 +from those two lists +will result in an error + +15 +00:00:38,100 --> 00:00:40,953 +because all arrays and +tensors should be rectangular. + +16 +00:00:42,510 --> 00:00:43,920 +One way to overcome this limit + +17 +00:00:43,920 --> 00:00:47,340 +is to make the second sentence +the same length as the first + +18 +00:00:47,340 --> 00:00:50,373 +by adding a special token +as many times as necessary. + +19 +00:00:51,300 --> 00:00:53,340 +Another way would be to +truncate the first sequence + +20 +00:00:53,340 --> 00:00:56,550 +to the length of the second, +but we would then lose a lot + +21 +00:00:56,550 --> 00:00:58,590 +of information that may be necessary + +22 +00:00:58,590 --> 00:01:01,230 +to properly classify the sentence. + +23 +00:01:01,230 --> 00:01:04,710 +In general, we only truncate +sentences when they are longer + +24 +00:01:04,710 --> 00:01:07,083 +than the maximum length +the model can handle. + +25 +00:01:08,310 --> 00:01:10,320 +The value used to pad the second sentence + +26 +00:01:10,320 --> 00:01:12,390 +should not be picked randomly. + +27 +00:01:12,390 --> 00:01:15,330 +The model has been pretrained +with a certain padding ID, + +28 +00:01:15,330 --> 00:01:18,093 +which you can find in +tokenizer.pad_token_id. + +29 +00:01:19,950 --> 00:01:21,630 +Now that we have padded our sentences, + +30 +00:01:21,630 --> 00:01:23,130 +we can make a batch with them. + +31 +00:01:24,210 --> 00:01:26,730 +If we pass the two sentences +to the model separately + +32 +00:01:26,730 --> 00:01:29,130 +or batched together, however, we notice + +33 +00:01:29,130 --> 00:01:30,630 +that we don't get the same results + +34 +00:01:30,630 --> 00:01:32,070 +for the sentence that is padded. + +35 +00:01:32,070 --> 00:01:34,440 +Here, the second one. + +36 +00:01:34,440 --> 00:01:36,690 +Expect the word in the +transformer library? + +37 +00:01:36,690 --> 00:01:37,620 +No. + +38 +00:01:37,620 --> 00:01:39,720 +If you remember that Transformer +models make heavy use + +39 +00:01:39,720 --> 00:01:43,800 +of attention layers, it should +not come as a total surprise. + +40 +00:01:43,800 --> 00:01:47,100 +When computing the contextual +representation of each token, + +41 +00:01:47,100 --> 00:01:49,440 +the attention layers look +at all the other words + +42 +00:01:49,440 --> 00:01:51,240 +in the sentence. + +43 +00:01:51,240 --> 00:01:52,252 +If we have just a sentence + +44 +00:01:52,252 --> 00:01:55,650 +or the sentence with several +padding tokens added, + +45 +00:01:55,650 --> 00:01:57,750 +it's logical we don't get the same values. + +46 +00:01:58,830 --> 00:02:01,410 +To get the same results +with or without padding, + +47 +00:02:01,410 --> 00:02:03,750 +we need to indicate to +the attention layers + +48 +00:02:03,750 --> 00:02:06,660 +that they should ignore +those padding tokens. + +49 +00:02:06,660 --> 00:02:08,970 +This is done by creating +an attention mask, + +50 +00:02:08,970 --> 00:02:11,700 +a tensor with the same +shape as the input IDs + +51 +00:02:11,700 --> 00:02:13,173 +with zeros and ones. + +52 +00:02:14,640 --> 00:02:16,830 +Ones indicate the tokens +the attention layers + +53 +00:02:16,830 --> 00:02:18,660 +should consider in the context, + +54 +00:02:18,660 --> 00:02:20,823 +and zeros, the tokens they should ignore. + +55 +00:02:21,810 --> 00:02:23,290 +Now, passing this attention mask + +56 +00:02:23,290 --> 00:02:26,460 +along with the input IDs +will give us the same results + +57 +00:02:26,460 --> 00:02:29,460 +as when we sent the two sentences +individually to the model. + +58 +00:02:30,870 --> 00:02:33,870 +This is all done behind +the scenes by the tokenizer + +59 +00:02:33,870 --> 00:02:35,583 +when you apply it to several sentences + +60 +00:02:35,583 --> 00:02:37,713 +with the flag padding equals true. + +61 +00:02:38,640 --> 00:02:39,690 +It will apply the padding + +62 +00:02:39,690 --> 00:02:42,180 +with the proper value +to the smaller sentences + +63 +00:02:42,180 --> 00:02:44,373 +and create the appropriate attention mask. + diff --git a/subtitles/en/19_hugging-face-datasets-overview-(pytorch).srt b/subtitles/en/19_hugging-face-datasets-overview-(pytorch).srt new file mode 100644 index 000000000..f05f6ceab --- /dev/null +++ b/subtitles/en/19_hugging-face-datasets-overview-(pytorch).srt @@ -0,0 +1,341 @@ +1 +00:00:00,213 --> 00:00:02,963 +(slide whooshes) + +2 +00:00:05,340 --> 00:00:08,373 +- The Hugging Face Datasets +library, a quick overview. + +3 +00:00:09,990 --> 00:00:11,670 +The Hugging Face Datasets library + +4 +00:00:11,670 --> 00:00:14,310 +is a library that provides +an API to quickly download + +5 +00:00:14,310 --> 00:00:17,610 +many public datasets and preprocess them. + +6 +00:00:17,610 --> 00:00:20,614 +In this video we will +explore how to do that. + +7 +00:00:20,614 --> 00:00:21,780 +The downloading part is easy, + +8 +00:00:21,780 --> 00:00:23,760 +with the load_dataset function. + +9 +00:00:23,760 --> 00:00:26,460 +You can directly download +and cache a dataset + +10 +00:00:26,460 --> 00:00:28,473 +from its identifier on the Dataset hub. + +11 +00:00:29,640 --> 00:00:33,570 +Here, we fetch the MRPC dataset +from the GLUE benchmark, + +12 +00:00:33,570 --> 00:00:36,390 +which is a dataset +containing pairs of sentences + +13 +00:00:36,390 --> 00:00:38,740 +where the task is to +determine the paraphrases. + +14 +00:00:39,810 --> 00:00:42,420 +The object returned by +the load_dataset function + +15 +00:00:42,420 --> 00:00:45,600 +is a DatasetDict, which +is a sort of dictionary + +16 +00:00:45,600 --> 00:00:47,463 +containing each split of our dataset. + +17 +00:00:48,946 --> 00:00:52,170 +We can access each split +by indexing with its name. + +18 +00:00:52,170 --> 00:00:55,047 +This split is then an +instance of the Dataset class, + +19 +00:00:55,047 --> 00:00:58,590 +with columns, here sentence1, sentence2, + +20 +00:00:58,590 --> 00:01:01,233 +label and idx, and rows. + +21 +00:01:02,400 --> 00:01:04,563 +We can access a given +element by its index. + +22 +00:01:05,460 --> 00:01:08,220 +The amazing thing about the +Hugging Face Datasets library + +23 +00:01:08,220 --> 00:01:11,880 +is that everything is saved +to disk using Apache Arrow, + +24 +00:01:11,880 --> 00:01:14,550 +which means that even +if your dataset is huge, + +25 +00:01:14,550 --> 00:01:16,350 +you won't get out of RAM. + +26 +00:01:16,350 --> 00:01:19,113 +Only the elements you +request are loaded in memory. + +27 +00:01:20,340 --> 00:01:23,940 +Accessing a slice of your dataset +is as easy as one element. + +28 +00:01:23,940 --> 00:01:26,220 +The result is then a +dictionary with list of values + +29 +00:01:26,220 --> 00:01:27,480 +for each keys. + +30 +00:01:27,480 --> 00:01:29,070 +Here the list of labels, + +31 +00:01:29,070 --> 00:01:30,147 +the list of first sentences + +32 +00:01:30,147 --> 00:01:31,923 +and the list of second sentences. + +33 +00:01:33,690 --> 00:01:35,580 +The features attribute of a Dataset + +34 +00:01:35,580 --> 00:01:37,470 +gives us more information +about its columns. + +35 +00:01:37,470 --> 00:01:40,020 +In particular, we can see here + +36 +00:01:40,020 --> 00:01:41,400 +it gives us the correspondence + +37 +00:01:41,400 --> 00:01:44,810 +between the integers and +names for the labels. + +38 +00:01:44,810 --> 00:01:48,543 +Zero stands for not equivalent +and one for equivalent. + +39 +00:01:49,830 --> 00:01:52,020 +To preprocess all the +elements of our dataset, + +40 +00:01:52,020 --> 00:01:53,850 +we need to tokenize them. + +41 +00:01:53,850 --> 00:01:56,160 +Have a look at the video +"Preprocess sentence pairs" + +42 +00:01:56,160 --> 00:01:57,570 +for a refresher, + +43 +00:01:57,570 --> 00:01:59,430 +but you just have to +send the two sentences + +44 +00:01:59,430 --> 00:02:02,733 +to the tokenizer with some +additional keyword arguments. + +45 +00:02:03,780 --> 00:02:06,600 +Here we indicate a maximum length of 128 + +46 +00:02:06,600 --> 00:02:08,820 +and pad inputs shorter than this length, + +47 +00:02:08,820 --> 00:02:10,420 +truncate inputs that are longer. + +48 +00:02:11,460 --> 00:02:13,470 +We put all of this in a tokenize_function + +49 +00:02:13,470 --> 00:02:16,710 +that we can directly apply to +all the splits in our dataset + +50 +00:02:16,710 --> 00:02:17,710 +with the map method. + +51 +00:02:18,840 --> 00:02:22,110 +As long as the function returns +a dictionary-like object, + +52 +00:02:22,110 --> 00:02:24,300 +the map method will add +new columns as needed + +53 +00:02:24,300 --> 00:02:26,043 +or update existing ones. + +54 +00:02:27,315 --> 00:02:28,830 +To speed up preprocessing + +55 +00:02:28,830 --> 00:02:30,870 +and take advantage of +the fact our tokenizer + +56 +00:02:30,870 --> 00:02:32,040 +is backed by Rust, + +57 +00:02:32,040 --> 00:02:34,770 +thanks to the Hugging +Face Tokenizers library, + +58 +00:02:34,770 --> 00:02:37,110 +we can process several +elements at the same time + +59 +00:02:37,110 --> 00:02:40,710 +to our tokenize function, using +the batched=True argument. + +60 +00:02:40,710 --> 00:02:42,120 +Since the tokenizer can handle + +61 +00:02:42,120 --> 00:02:44,610 +list of first sentences, +list of second sentences, + +62 +00:02:44,610 --> 00:02:47,493 +the tokenize_function does +not need to change for this. + +63 +00:02:48,360 --> 00:02:51,180 +You can also use multiprocessing +with the map method. + +64 +00:02:51,180 --> 00:02:53,583 +Check out its documentation +in the linked video. + +65 +00:02:54,840 --> 00:02:57,990 +Once this is done, we are +almost ready for training. + +66 +00:02:57,990 --> 00:02:59,970 +We just remove the columns +we don't need anymore + +67 +00:02:59,970 --> 00:03:02,190 +with the remove_columns method, + +68 +00:03:02,190 --> 00:03:03,750 +rename label to labels, + +69 +00:03:03,750 --> 00:03:05,790 +since the models from the +Hugging Face Transformers + +70 +00:03:05,790 --> 00:03:07,710 +library expect that, + +71 +00:03:07,710 --> 00:03:10,470 +and set the output format +to our desired backend, + +72 +00:03:10,470 --> 00:03:12,053 +Torch, TensorFlow or NumPy. + +73 +00:03:13,440 --> 00:03:16,800 +If needed, we can also generate +a short sample of a dataset + +74 +00:03:16,800 --> 00:03:18,000 +using the select method. + +75 +00:03:20,211 --> 00:03:22,961 +(slide whooshes) + diff --git a/subtitles/en/20_hugging-face-datasets-overview-(tensorflow).srt b/subtitles/en/20_hugging-face-datasets-overview-(tensorflow).srt new file mode 100644 index 000000000..5daa26c88 --- /dev/null +++ b/subtitles/en/20_hugging-face-datasets-overview-(tensorflow).srt @@ -0,0 +1,320 @@ +1 +00:00:00,170 --> 00:00:03,087 +(screen whooshing) + +2 +00:00:05,371 --> 00:00:09,690 +- The Hugging Face Datasets +library: A Quick overview. + +3 +00:00:09,690 --> 00:00:10,917 +The Hugging Face Datasets library + +4 +00:00:10,917 --> 00:00:12,870 +is a library that provides an API + +5 +00:00:12,870 --> 00:00:15,150 +to quickly download many public datasets + +6 +00:00:15,150 --> 00:00:16,200 +and pre-process them. + +7 +00:00:17,070 --> 00:00:19,473 +In this video we will explore to do that. + +8 +00:00:20,520 --> 00:00:23,730 +The downloading part is easy +with the load_dataset function, + +9 +00:00:23,730 --> 00:00:26,010 +you can directly download +and cache a dataset + +10 +00:00:26,010 --> 00:00:28,023 +from its identifier on the Dataset hub. + +11 +00:00:29,160 --> 00:00:33,690 +Here we fetch the MRPC dataset +from the GLUE benchmark, + +12 +00:00:33,690 --> 00:00:36,030 +is a dataset containing pairs of sentences + +13 +00:00:36,030 --> 00:00:38,380 +where the task is to +determine the paraphrases. + +14 +00:00:39,720 --> 00:00:42,120 +The object returned by +the load_dataset function + +15 +00:00:42,120 --> 00:00:45,090 +is a DatasetDict, which +is a sort of dictionary + +16 +00:00:45,090 --> 00:00:46,940 +containing each split of our dataset. + +17 +00:00:48,600 --> 00:00:51,780 +We can access each split +by indexing with its name. + +18 +00:00:51,780 --> 00:00:54,540 +This split is then an +instance of the Dataset class, + +19 +00:00:54,540 --> 00:00:57,423 +with columns, here sentence1, sentence2, + +20 +00:00:58,350 --> 00:01:00,813 +label and idx, and rows. + +21 +00:01:02,160 --> 00:01:05,220 +We can access a given +element by its index. + +22 +00:01:05,220 --> 00:01:08,220 +The amazing thing about the +Hugging Face Datasets library + +23 +00:01:08,220 --> 00:01:11,700 +is that everything is saved +to disk using Apache Arrow, + +24 +00:01:11,700 --> 00:01:14,460 +which means that even +if your dataset is huge + +25 +00:01:14,460 --> 00:01:16,219 +you won't get out of RAM, + +26 +00:01:16,219 --> 00:01:18,769 +only the elements you +request are loaded in memory. + +27 +00:01:19,920 --> 00:01:24,510 +Accessing a slice of your dataset +is as easy as one element. + +28 +00:01:24,510 --> 00:01:27,150 +The result is then a +dictionary with list of values + +29 +00:01:27,150 --> 00:01:30,630 +for each keys, here the list of labels, + +30 +00:01:30,630 --> 00:01:32,190 +the list of first sentences, + +31 +00:01:32,190 --> 00:01:33,840 +and the list of second sentences. + +32 +00:01:35,100 --> 00:01:37,080 +The features attribute of a Dataset + +33 +00:01:37,080 --> 00:01:39,840 +gives us more information +about its columns. + +34 +00:01:39,840 --> 00:01:42,150 +In particular, we can see here it gives us + +35 +00:01:42,150 --> 00:01:43,980 +a correspondence between the integers + +36 +00:01:43,980 --> 00:01:46,110 +and names for the labels. + +37 +00:01:46,110 --> 00:01:49,623 +0 stands for not equivalent +and 1 for equivalent. + +38 +00:01:51,630 --> 00:01:54,090 +To pre-process all the +elements of our dataset, + +39 +00:01:54,090 --> 00:01:55,980 +we need to tokenize them. + +40 +00:01:55,980 --> 00:01:58,470 +Have a look at the video +"Pre-process sentence pairs" + +41 +00:01:58,470 --> 00:02:01,800 +for a refresher, but you just +have to send the two sentences + +42 +00:02:01,800 --> 00:02:04,833 +to the tokenizer with some +additional keyword arguments. + +43 +00:02:05,880 --> 00:02:09,300 +Here we indicate a maximum length of 128 + +44 +00:02:09,300 --> 00:02:11,460 +and pad inputs shorter than this length, + +45 +00:02:11,460 --> 00:02:13,060 +truncate inputs that are longer. + +46 +00:02:14,040 --> 00:02:16,170 +We put all of this in a tokenize_function + +47 +00:02:16,170 --> 00:02:18,510 +that we can directly +apply to all the splits + +48 +00:02:18,510 --> 00:02:20,260 +in our dataset with the map method. + +49 +00:02:21,210 --> 00:02:24,120 +As long as the function returns +a dictionary-like object, + +50 +00:02:24,120 --> 00:02:26,580 +the map method will add +new columns as needed + +51 +00:02:26,580 --> 00:02:28,113 +or update existing ones. + +52 +00:02:30,060 --> 00:02:32,520 +To speed up pre-processing +and take advantage + +53 +00:02:32,520 --> 00:02:35,130 +of the fact our tokenizer +is backed by Rust + +54 +00:02:35,130 --> 00:02:38,160 +thanks to the Hugging +Face Tokenizers library, + +55 +00:02:38,160 --> 00:02:40,590 +we can process several +elements at the same time + +56 +00:02:40,590 --> 00:02:43,923 +in our tokenize function, using +the batched=True argument. + +57 +00:02:45,300 --> 00:02:46,980 +Since the tokenizer can handle a list + +58 +00:02:46,980 --> 00:02:50,280 +of first or second sentences, +the tokenize_function + +59 +00:02:50,280 --> 00:02:52,740 +does not need to change for this. + +60 +00:02:52,740 --> 00:02:55,410 +You can also use multiprocessing +with the map method, + +61 +00:02:55,410 --> 00:02:57,460 +check out its documentation linked below. + +62 +00:02:58,740 --> 00:03:02,130 +Once this is done, we are +almost ready for training, + +63 +00:03:02,130 --> 00:03:04,020 +we just remove the columns +we don't need anymore + +64 +00:03:04,020 --> 00:03:06,120 +with the remove_columns method, + +65 +00:03:06,120 --> 00:03:08,580 +rename label to labels, since the models + +66 +00:03:08,580 --> 00:03:11,430 +from the transformers library expect that, + +67 +00:03:11,430 --> 00:03:14,040 +and set the output format +to our desired backend, + +68 +00:03:14,040 --> 00:03:15,893 +torch, tensorflow or numpy. + +69 +00:03:16,800 --> 00:03:19,050 +If needed, we can also +generate a short sample + +70 +00:03:19,050 --> 00:03:21,377 +of a dataset using the select method. + +71 +00:03:22,817 --> 00:03:25,734 +(screen whooshing) + diff --git a/subtitles/en/21_preprocessing-sentence-pairs-(pytorch).srt b/subtitles/en/21_preprocessing-sentence-pairs-(pytorch).srt new file mode 100644 index 000000000..3199e76a7 --- /dev/null +++ b/subtitles/en/21_preprocessing-sentence-pairs-(pytorch).srt @@ -0,0 +1,294 @@ +1 +00:00:00,000 --> 00:00:03,083 +(graphics whooshing) + +2 +00:00:05,370 --> 00:00:07,413 +- How to pre-process pairs of sentences. + +3 +00:00:09,150 --> 00:00:11,340 +We have seen how to +tokenize single sentences + +4 +00:00:11,340 --> 00:00:12,877 +and batch them together in the, + +5 +00:00:12,877 --> 00:00:15,810 +"Batching inputs together video." + +6 +00:00:15,810 --> 00:00:18,330 +If this code look unfamiliar to you, + +7 +00:00:18,330 --> 00:00:20,030 +be sure to check that video again. + +8 +00:00:21,330 --> 00:00:24,543 +Here will focus on tasks that +classify pair of sentences. + +9 +00:00:25,620 --> 00:00:28,470 +For instance, we may want to +classify whether two texts + +10 +00:00:28,470 --> 00:00:30,360 +are paraphrased or not. + +11 +00:00:30,360 --> 00:00:32,880 +Here is an example taken +from the Quora Question Pairs + +12 +00:00:32,880 --> 00:00:37,530 +dataset, which focuses on +identifying duplicate questions. + +13 +00:00:37,530 --> 00:00:40,650 +In the first pair, the two +questions are duplicates, + +14 +00:00:40,650 --> 00:00:42,000 +in the second they are not. + +15 +00:00:43,283 --> 00:00:45,540 +Another pair classification problem is + +16 +00:00:45,540 --> 00:00:47,400 +when we want to know if two sentences are + +17 +00:00:47,400 --> 00:00:49,590 +logically related or not, + +18 +00:00:49,590 --> 00:00:53,970 +a problem called natural +language inference or NLI. + +19 +00:00:53,970 --> 00:00:57,000 +In this example, taken +from the MultiNLI data set, + +20 +00:00:57,000 --> 00:00:59,880 +we have a pair of sentences +for each possible label. + +21 +00:00:59,880 --> 00:01:02,490 +Contradiction, natural or entailment, + +22 +00:01:02,490 --> 00:01:04,680 +which is a fancy way of +saying the first sentence + +23 +00:01:04,680 --> 00:01:05,793 +implies the second. + +24 +00:01:06,930 --> 00:01:08,820 +So classifying pairs of +sentences is a problem + +25 +00:01:08,820 --> 00:01:10,260 +worth studying. + +26 +00:01:10,260 --> 00:01:12,630 +In fact, in the GLUE benchmark, + +27 +00:01:12,630 --> 00:01:15,750 +which is an academic benchmark +for text classification + +28 +00:01:15,750 --> 00:01:17,910 +eight of the 10 data sets are focused + +29 +00:01:17,910 --> 00:01:19,953 +on tasks using pairs of sentences. + +30 +00:01:20,910 --> 00:01:22,560 +That's why models like BERT + +31 +00:01:22,560 --> 00:01:25,320 +are often pre-trained +with a dual objective. + +32 +00:01:25,320 --> 00:01:27,660 +On top of the language modeling objective, + +33 +00:01:27,660 --> 00:01:31,230 +they often have an objective +related to sentence pairs. + +34 +00:01:31,230 --> 00:01:34,320 +For instance, during +pretraining BERT is shown + +35 +00:01:34,320 --> 00:01:36,810 +pairs of sentences and must predict both + +36 +00:01:36,810 --> 00:01:39,930 +the value of randomly masked +tokens, and whether the second + +37 +00:01:39,930 --> 00:01:41,830 +sentence follow from the first or not. + +38 +00:01:43,084 --> 00:01:45,930 +Fortunately, the tokenizer +from the Transformers library + +39 +00:01:45,930 --> 00:01:49,170 +has a nice API to deal +with pairs of sentences. + +40 +00:01:49,170 --> 00:01:51,270 +You just have to pass +them as two arguments + +41 +00:01:51,270 --> 00:01:52,120 +to the tokenizer. + +42 +00:01:53,430 --> 00:01:55,470 +On top of the input IDs +and the attention mask + +43 +00:01:55,470 --> 00:01:56,970 +we studied already, + +44 +00:01:56,970 --> 00:01:59,910 +it returns a new field +called token type IDs, + +45 +00:01:59,910 --> 00:02:01,790 +which tells the model which tokens belong + +46 +00:02:01,790 --> 00:02:03,630 +to the first sentence, + +47 +00:02:03,630 --> 00:02:05,943 +and which ones belong +to the second sentence. + +48 +00:02:07,290 --> 00:02:09,840 +Zooming in a little bit, +here has an input IDs + +49 +00:02:09,840 --> 00:02:12,180 +aligned with the tokens +they correspond to, + +50 +00:02:12,180 --> 00:02:15,213 +their respective token +type ID and attention mask. + +51 +00:02:16,080 --> 00:02:19,260 +We can see the tokenizer +also added special tokens. + +52 +00:02:19,260 --> 00:02:22,620 +So we have a CLS token, the +tokens from the first sentence, + +53 +00:02:22,620 --> 00:02:25,770 +a SEP token, the tokens +from the second sentence, + +54 +00:02:25,770 --> 00:02:27,003 +and a final SEP token. + +55 +00:02:28,500 --> 00:02:30,570 +If we have several pairs of sentences, + +56 +00:02:30,570 --> 00:02:32,840 +we can tokenize them +together by passing the list + +57 +00:02:32,840 --> 00:02:36,630 +of first sentences, then +the list of second sentences + +58 +00:02:36,630 --> 00:02:39,300 +and all the keyword +arguments we studied already + +59 +00:02:39,300 --> 00:02:40,353 +like padding=True. + +60 +00:02:41,940 --> 00:02:43,140 +Zooming in at the result, + +61 +00:02:43,140 --> 00:02:45,030 +we can see also tokenize added padding + +62 +00:02:45,030 --> 00:02:48,090 +to the second pair sentences +to make the two outputs + +63 +00:02:48,090 --> 00:02:51,360 +the same length, and properly +dealt with token type IDs + +64 +00:02:51,360 --> 00:02:53,643 +and attention masks for the two sentences. + +65 +00:02:54,900 --> 00:02:57,573 +This is then all ready to +pass through our model. + diff --git a/subtitles/en/22_preprocessing-sentence-pairs-(tensorflow).srt b/subtitles/en/22_preprocessing-sentence-pairs-(tensorflow).srt new file mode 100644 index 000000000..980986853 --- /dev/null +++ b/subtitles/en/22_preprocessing-sentence-pairs-(tensorflow).srt @@ -0,0 +1,309 @@ +1 +00:00:00,225 --> 00:00:02,892 +(air whooshing) + +2 +00:00:05,578 --> 00:00:09,180 +- How to preprocess pairs of sentences? + +3 +00:00:09,180 --> 00:00:11,490 +We have seen how to +tokenize single sentences + +4 +00:00:11,490 --> 00:00:13,020 +and batch them together + +5 +00:00:13,020 --> 00:00:15,660 +in the "Batching inputs together" video. + +6 +00:00:15,660 --> 00:00:18,060 +If this code looks unfamiliar to you, + +7 +00:00:18,060 --> 00:00:19,760 +be sure to check that video again! + +8 +00:00:21,101 --> 00:00:22,110 +Here, we will focus on tasks + +9 +00:00:22,110 --> 00:00:24,033 +that classify pairs of sentences. + +10 +00:00:24,900 --> 00:00:27,030 +For instance, we may want to classify + +11 +00:00:27,030 --> 00:00:29,820 +whether two texts are paraphrases or not. + +12 +00:00:29,820 --> 00:00:30,900 +Here is an example taken + +13 +00:00:30,900 --> 00:00:33,180 +from the Quora Question Pairs dataset, + +14 +00:00:33,180 --> 00:00:36,033 +which focuses on identifying +duplicate questions. + +15 +00:00:37,110 --> 00:00:40,650 +In the first pair, the two +questions are duplicates; + +16 +00:00:40,650 --> 00:00:43,620 +in the second, they are not. + +17 +00:00:43,620 --> 00:00:44,730 +Another classification problem + +18 +00:00:44,730 --> 00:00:46,980 +is when we want to know if two sentences + +19 +00:00:46,980 --> 00:00:49,290 +are logically related or not, + +20 +00:00:49,290 --> 00:00:52,173 +a problem called Natural +Language Inference or NLI. + +21 +00:00:53,100 --> 00:00:55,830 +In this example taken +from the MultiNLI dataset, + +22 +00:00:55,830 --> 00:00:59,460 +we have a pair of sentences +for each possible label: + +23 +00:00:59,460 --> 00:01:02,400 +contradiction, neutral or entailment, + +24 +00:01:02,400 --> 00:01:04,680 +which is a fancy way of +saying the first sentence + +25 +00:01:04,680 --> 00:01:05,853 +implies the second. + +26 +00:01:07,140 --> 00:01:09,000 +So classifying pairs of sentences + +27 +00:01:09,000 --> 00:01:10,533 +is a problem worth studying. + +28 +00:01:11,370 --> 00:01:13,770 +In fact, in the GLUE benchmark, + +29 +00:01:13,770 --> 00:01:16,830 +which is an academic benchmark +for text classification, + +30 +00:01:16,830 --> 00:01:19,680 +eight of the 10 datasets +are focused on tasks + +31 +00:01:19,680 --> 00:01:20,973 +using pairs of sentences. + +32 +00:01:22,110 --> 00:01:24,720 +That's why models like +BERT are often pretrained + +33 +00:01:24,720 --> 00:01:26,520 +with a dual objective: + +34 +00:01:26,520 --> 00:01:28,890 +on top of the language modeling objective, + +35 +00:01:28,890 --> 00:01:32,010 +they often have an objective +related to sentence pairs. + +36 +00:01:32,010 --> 00:01:34,560 +For instance, during pretraining, + +37 +00:01:34,560 --> 00:01:36,690 +BERT is shown pairs of sentences + +38 +00:01:36,690 --> 00:01:39,900 +and must predict both the +value of randomly masked tokens + +39 +00:01:39,900 --> 00:01:41,250 +and whether the second sentence + +40 +00:01:41,250 --> 00:01:42,903 +follows from the first or not. + +41 +00:01:44,070 --> 00:01:47,100 +Fortunately, the tokenizer +from the Transformers library + +42 +00:01:47,100 --> 00:01:50,550 +has a nice API to deal +with pairs of sentences: + +43 +00:01:50,550 --> 00:01:52,650 +you just have to pass +them as two arguments + +44 +00:01:52,650 --> 00:01:53,613 +to the tokenizer. + +45 +00:01:54,900 --> 00:01:56,040 +On top of the input IDs + +46 +00:01:56,040 --> 00:01:58,440 +and the attention mask we studied already, + +47 +00:01:58,440 --> 00:02:01,530 +it returns a new field +called token type IDs, + +48 +00:02:01,530 --> 00:02:03,210 +which tells the model which tokens + +49 +00:02:03,210 --> 00:02:05,100 +belong to the first sentence + +50 +00:02:05,100 --> 00:02:07,350 +and which ones belong +to the second sentence. + +51 +00:02:08,670 --> 00:02:11,430 +Zooming in a little bit, +here are the input IDs, + +52 +00:02:11,430 --> 00:02:13,710 +aligned with the tokens +they correspond to, + +53 +00:02:13,710 --> 00:02:17,193 +their respective token +type ID and attention mask. + +54 +00:02:18,540 --> 00:02:21,300 +We can see the tokenizer +also added special tokens + +55 +00:02:21,300 --> 00:02:25,230 +so we have a CLS token, the +tokens from the first sentence, + +56 +00:02:25,230 --> 00:02:28,590 +a SEP token, the tokens +from the second sentence, + +57 +00:02:28,590 --> 00:02:30,153 +and a final SEP token. + +58 +00:02:31,680 --> 00:02:33,720 +If we have several pairs of sentences, + +59 +00:02:33,720 --> 00:02:35,640 +we can tokenize them together + +60 +00:02:35,640 --> 00:02:38,280 +by passing the list of first sentences, + +61 +00:02:38,280 --> 00:02:40,710 +then the list of second sentences + +62 +00:02:40,710 --> 00:02:43,050 +and all the keyword +arguments we studied already, + +63 +00:02:43,050 --> 00:02:44,133 +like padding=True. + +64 +00:02:45,510 --> 00:02:46,770 +Zooming in at the result, + +65 +00:02:46,770 --> 00:02:49,050 +we can see how the tokenizer added padding + +66 +00:02:49,050 --> 00:02:50,940 +to the second pair of sentences, + +67 +00:02:50,940 --> 00:02:53,490 +to make the two outputs the same length. + +68 +00:02:53,490 --> 00:02:55,620 +It also properly dealt with token type IDS + +69 +00:02:55,620 --> 00:02:57,720 +and attention masks for the two sentences. + +70 +00:02:59,010 --> 00:03:01,460 +This is then all ready to +pass through our model! + +71 +00:03:03,799 --> 00:03:06,466 +(air whooshing) + diff --git a/subtitles/en/23_what-is-dynamic-padding.srt b/subtitles/en/23_what-is-dynamic-padding.srt new file mode 100644 index 000000000..64514035a --- /dev/null +++ b/subtitles/en/23_what-is-dynamic-padding.srt @@ -0,0 +1,300 @@ +1 +00:00:00,242 --> 00:00:02,909 +(air whooshing) + +2 +00:00:05,460 --> 00:00:06,963 +- What is dynamic padding? + +3 +00:00:08,630 --> 00:00:10,890 +In the "Batching Inputs together" video, + +4 +00:00:10,890 --> 00:00:12,720 +we have seen that to +be able to group inputs + +5 +00:00:12,720 --> 00:00:15,300 +of different lengths in the same batch, + +6 +00:00:15,300 --> 00:00:18,270 +we need to add padding tokens +to all the short inputs + +7 +00:00:18,270 --> 00:00:20,970 +until they are all of the same length. + +8 +00:00:20,970 --> 00:00:24,600 +Here, for instance, the longest +sentence is the third one, + +9 +00:00:24,600 --> 00:00:27,270 +and we need to add five, +two, or seven pad tokens + +10 +00:00:27,270 --> 00:00:30,090 +to the other sentences +to have four sentences + +11 +00:00:30,090 --> 00:00:31,090 +of the same lengths. + +12 +00:00:32,430 --> 00:00:33,900 +When dealing with a whole dataset, + +13 +00:00:33,900 --> 00:00:36,633 +there are various padding +strategies we can apply. + +14 +00:00:37,560 --> 00:00:39,540 +The most obvious one is +to pad all the elements + +15 +00:00:39,540 --> 00:00:40,923 +of the dataset to the same length: + +16 +00:00:40,923 --> 00:00:43,053 +the length of the longest sample. + +17 +00:00:44,070 --> 00:00:45,330 +This will then give us batches + +18 +00:00:45,330 --> 00:00:46,890 +that all have the same shape + +19 +00:00:46,890 --> 00:00:49,800 +determined by the maximum sequence length. + +20 +00:00:49,800 --> 00:00:52,893 +The downside is that batches +composed from short sentences + +21 +00:00:52,893 --> 00:00:54,960 +will have a lot of padding tokens + +22 +00:00:54,960 --> 00:00:57,660 +which will introduce more +computations in the model + +23 +00:00:57,660 --> 00:00:58,910 +we ultimately don't need. + +24 +00:01:00,060 --> 00:01:03,300 +To avoid this, another +strategy is to pad the elements + +25 +00:01:03,300 --> 00:01:05,280 +when we batch them together, + +26 +00:01:05,280 --> 00:01:08,190 +to the longest sentence inside the batch. + +27 +00:01:08,190 --> 00:01:12,000 +This way, batches composed of +short inputs will be smaller + +28 +00:01:12,000 --> 00:01:13,920 +than the batch containing +the longest sentence + +29 +00:01:13,920 --> 00:01:15,510 +in the dataset. + +30 +00:01:15,510 --> 00:01:18,063 +This will yield some nice +speedup on CPU and GPU. + +31 +00:01:19,110 --> 00:01:20,490 +The downside is that all batches + +32 +00:01:20,490 --> 00:01:22,140 +will then have different shapes, + +33 +00:01:22,140 --> 00:01:24,740 +which slows down training +on accelerators like TPUs. + +34 +00:01:26,160 --> 00:01:29,370 +Let's see how to apply both +strategies in practice. + +35 +00:01:29,370 --> 00:01:31,280 +We have actually seen how +to apply fixed padding + +36 +00:01:31,280 --> 00:01:33,390 +in the Datasets Overview video, + +37 +00:01:33,390 --> 00:01:36,030 +when we preprocessed the MRPC dataset: + +38 +00:01:36,030 --> 00:01:38,250 +after loading the dataset and tokenizer, + +39 +00:01:38,250 --> 00:01:40,680 +we applied the tokenization +to all the dataset + +40 +00:01:40,680 --> 00:01:42,480 +with padding and truncation + +41 +00:01:42,480 --> 00:01:45,273 +to make all samples of length 128. + +42 +00:01:46,530 --> 00:01:48,360 +As a result, if we pass this dataset + +43 +00:01:48,360 --> 00:01:50,520 +to a PyTorch DataLoader, + +44 +00:01:50,520 --> 00:01:55,503 +we get batches of shape +batch size, here 16, by 128. + +45 +00:01:57,060 --> 00:01:58,380 +To apply dynamic padding, + +46 +00:01:58,380 --> 00:02:01,440 +we must defer the padding +to the batch preparation, + +47 +00:02:01,440 --> 00:02:04,740 +so we remove that part +from our tokenize function. + +48 +00:02:04,740 --> 00:02:06,150 +We still leave the truncation part + +49 +00:02:06,150 --> 00:02:08,580 +so that inputs that are +bigger than the maximum length + +50 +00:02:08,580 --> 00:02:12,060 +accepted by the model, usually 512, + +51 +00:02:12,060 --> 00:02:13,510 +get truncated to that length. + +52 +00:02:14,940 --> 00:02:16,380 +Then we pad our samples dynamically + +53 +00:02:16,380 --> 00:02:18,330 +by using a data collator. + +54 +00:02:18,330 --> 00:02:20,280 +Those classes in the Transformers library + +55 +00:02:20,280 --> 00:02:22,740 +are responsible for applying +all the final processing + +56 +00:02:22,740 --> 00:02:25,290 +needed before forming a batch, + +57 +00:02:25,290 --> 00:02:28,470 +here DataCollatorWithPadding +will pad the samples + +58 +00:02:28,470 --> 00:02:31,083 +to the maximum length inside +the batch of sentences. + +59 +00:02:32,160 --> 00:02:35,310 +We pass it to the PyTorch +DataLoader as a collate function, + +60 +00:02:35,310 --> 00:02:37,620 +then observe that the batches generated + +61 +00:02:37,620 --> 00:02:38,850 +have various lengths, + +62 +00:02:38,850 --> 00:02:41,253 +all way below the 128 from before. + +63 +00:02:42,660 --> 00:02:44,820 +Dynamic batching will +almost always be faster + +64 +00:02:44,820 --> 00:02:47,913 +on CPUs and GPUs, so you +should apply it if you can. + +65 +00:02:48,930 --> 00:02:51,330 +Remember to switch back +to fixed padding, however, + +66 +00:02:51,330 --> 00:02:53,490 +if you run your training script on TPU + +67 +00:02:53,490 --> 00:02:55,293 +or need batches of fixed shapes. + +68 +00:02:56,917 --> 00:02:59,584 +(air whooshing) + diff --git a/subtitles/en/24_the-trainer-api.srt b/subtitles/en/24_the-trainer-api.srt new file mode 100644 index 000000000..55405374a --- /dev/null +++ b/subtitles/en/24_the-trainer-api.srt @@ -0,0 +1,382 @@ +1 +00:00:00,304 --> 00:00:01,285 +(air whooshing) + +2 +00:00:01,285 --> 00:00:02,345 +(air popping) + +3 +00:00:02,345 --> 00:00:05,698 +(air whooshing) + +4 +00:00:05,698 --> 00:00:06,548 +- So Trainer API. + +5 +00:00:08,070 --> 00:00:10,040 +So Transformers Library +provides a Trainer API + +6 +00:00:10,040 --> 00:00:13,320 +that allows you to easily +find tune transformers models + +7 +00:00:13,320 --> 00:00:14,193 +on your dataset. + +8 +00:00:15,150 --> 00:00:17,250 +So Trainer class takes your datasets, + +9 +00:00:17,250 --> 00:00:19,900 +your model as well as the +training hyperparameters + +10 +00:00:20,820 --> 00:00:23,310 +and can perform the training +on any kind of setup, + +11 +00:00:23,310 --> 00:00:26,654 +CPU, GPU, multiple GPUs, TPUs + +12 +00:00:26,654 --> 00:00:28,680 +can also compute the predictions + +13 +00:00:28,680 --> 00:00:31,710 +on any dataset and if you provided metrics + +14 +00:00:31,710 --> 00:00:33,813 +evaluate your model on any dataset. + +15 +00:00:34,950 --> 00:00:36,930 +You can also involve final data processing + +16 +00:00:36,930 --> 00:00:38,670 +such as dynamic padding, + +17 +00:00:38,670 --> 00:00:40,377 +as long as you provide the tokenizer + +18 +00:00:40,377 --> 00:00:42,693 +or given data collator. + +19 +00:00:43,572 --> 00:00:45,900 +We will try this API on the MRPC dataset, + +20 +00:00:45,900 --> 00:00:48,492 +since it's relatively small +and easy to preprocess. + +21 +00:00:48,492 --> 00:00:49,325 +As we saw in the Datasets overview video, + +22 +00:00:49,325 --> 00:00:54,325 +however we can preprocess it. + +23 +00:00:54,511 --> 00:00:57,030 +We do not apply padding +during the preprocessing, + +24 +00:00:57,030 --> 00:00:58,590 +as we will use dynamic padding + +25 +00:00:58,590 --> 00:01:00,083 +before DataCollatorWithPadding. + +26 +00:01:01,170 --> 00:01:02,790 +Note that we don't do the final steps + +27 +00:01:02,790 --> 00:01:04,830 +of renaming removing columns + +28 +00:01:04,830 --> 00:01:06,873 +or set the format to torch tensors. + +29 +00:01:07,710 --> 00:01:10,560 +So Trainer will do all of +this automatically for us + +30 +00:01:10,560 --> 00:01:12,633 +by analyzing the model signature. + +31 +00:01:14,054 --> 00:01:16,650 +The last step before +creating the Trainer are + +32 +00:01:16,650 --> 00:01:17,940 +to define a model + +33 +00:01:17,940 --> 00:01:20,250 +and some training hyperparameters. + +34 +00:01:20,250 --> 00:01:22,653 +We saw to do the first +in the model API video. + +35 +00:01:23,734 --> 00:01:26,790 +For the second we use the +TrainingArguments class. + +36 +00:01:26,790 --> 00:01:28,710 +It only takes a path to a folder + +37 +00:01:28,710 --> 00:01:30,900 +where results and +checkpoint will be saved, + +38 +00:01:30,900 --> 00:01:33,060 +but you can also customize +all the hyperparameters + +39 +00:01:33,060 --> 00:01:34,470 +your Trainer will use, + +40 +00:01:34,470 --> 00:01:37,270 +learning weight, number of +training impacts, et. cetera. + +41 +00:01:38,190 --> 00:01:39,660 +It's been very easy to create a Trainer + +42 +00:01:39,660 --> 00:01:41,400 +and launch a training. + +43 +00:01:41,400 --> 00:01:43,170 +You should display a progress bar + +44 +00:01:43,170 --> 00:01:45,900 +and after a few minutes +if you're running on a GPU + +45 +00:01:45,900 --> 00:01:48,000 +you should have the training finished. + +46 +00:01:48,000 --> 00:01:50,790 +The result will be rather +anticlimactic however, + +47 +00:01:50,790 --> 00:01:52,710 +as you will only get a training loss + +48 +00:01:52,710 --> 00:01:54,300 +which doesn't really tell you anything + +49 +00:01:54,300 --> 00:01:56,820 +about how well your model is performing. + +50 +00:01:56,820 --> 00:01:58,977 +This is because we +didn't specify any metric + +51 +00:01:58,977 --> 00:02:00,273 +for the evaluation. + +52 +00:02:01,200 --> 00:02:02,160 +To get those metrics, + +53 +00:02:02,160 --> 00:02:03,810 +we will first gather the predictions + +54 +00:02:03,810 --> 00:02:06,513 +on the whole evaluation set +using the predict method. + +55 +00:02:07,440 --> 00:02:10,020 +It returns a namedtuple with three fields, + +56 +00:02:10,020 --> 00:02:12,990 +Prediction, which contains +the model of predictions. + +57 +00:02:12,990 --> 00:02:15,030 +Label_IDs, which contains the labels + +58 +00:02:15,030 --> 00:02:16,800 +if your dataset had them + +59 +00:02:16,800 --> 00:02:18,570 +and metrics which is empty here. + +60 +00:02:18,570 --> 00:02:20,520 +We're trying to do that. + +61 +00:02:20,520 --> 00:02:22,470 +The predictions are the +logits of the models + +62 +00:02:22,470 --> 00:02:24,143 +for all the sentences in the dataset. + +63 +00:02:24,143 --> 00:02:27,513 +So a NumPy array of shape 408 by 2. + +64 +00:02:28,500 --> 00:02:30,270 +To match them with our labels, + +65 +00:02:30,270 --> 00:02:31,590 +we need to take the maximum logit + +66 +00:02:31,590 --> 00:02:32,850 +for each prediction + +67 +00:02:32,850 --> 00:02:35,820 +to know which of the two +classes was predicted. + +68 +00:02:35,820 --> 00:02:37,683 +We do this with the argmax function. + +69 +00:02:38,640 --> 00:02:41,550 +Then we can use a metric +from the Datasets library. + +70 +00:02:41,550 --> 00:02:43,500 +It can be loaded as easily as a dataset + +71 +00:02:43,500 --> 00:02:45,360 +with the load metric function + +72 +00:02:45,360 --> 00:02:49,500 +and each returns the evaluation +metric used for the dataset. + +73 +00:02:49,500 --> 00:02:51,600 +We can see our model did learn something + +74 +00:02:51,600 --> 00:02:54,363 +as it is 85.7% accurate. + +75 +00:02:55,440 --> 00:02:57,870 +To monitor the evaluation +matrix during training, + +76 +00:02:57,870 --> 00:02:59,829 +we need to define a +compute_metrics function + +77 +00:02:59,829 --> 00:03:02,670 +that does the same step as before. + +78 +00:03:02,670 --> 00:03:04,728 +It takes a namedtuple with +predictions and labels + +79 +00:03:04,728 --> 00:03:06,327 +and must return a dictionary + +80 +00:03:06,327 --> 00:03:08,427 +with the metrics we want to keep track of. + +81 +00:03:09,360 --> 00:03:11,490 +By passing the epoch evaluation strategy + +82 +00:03:11,490 --> 00:03:13,080 +to our training arguments, + +83 +00:03:13,080 --> 00:03:14,490 +we tell the Trainer to evaluate + +84 +00:03:14,490 --> 00:03:15,903 +at the end of every epoch. + +85 +00:03:17,280 --> 00:03:18,587 +Launching a training inside a notebook + +86 +00:03:18,587 --> 00:03:20,640 +will then display a progress bar + +87 +00:03:20,640 --> 00:03:23,643 +and complete the table you see +here as you pass every epoch. + +88 +00:03:25,400 --> 00:03:28,249 +(air whooshing) + +89 +00:03:28,249 --> 00:03:29,974 +(air decrescendos) + diff --git a/subtitles/en/25_keras-introduction.srt b/subtitles/en/25_keras-introduction.srt new file mode 100644 index 000000000..d2960bba1 --- /dev/null +++ b/subtitles/en/25_keras-introduction.srt @@ -0,0 +1,290 @@ +1 +00:00:00,430 --> 00:00:03,013 +(upbeat music) + +2 +00:00:05,160 --> 00:00:07,080 +- In this video, I'm going to give you + +3 +00:00:07,080 --> 00:00:10,350 +a very quick introduction to +how our transformer models + +4 +00:00:10,350 --> 00:00:14,040 +work together with Tensorflow and Keras. + +5 +00:00:14,040 --> 00:00:15,510 +The very short explanation + +6 +00:00:15,510 --> 00:00:17,310 +is that all of our Tensorflow models + +7 +00:00:17,310 --> 00:00:19,470 +are also Keras model objects, + +8 +00:00:19,470 --> 00:00:22,950 +and so they have the +standard Keras model API. + +9 +00:00:22,950 --> 00:00:24,960 +If you're an experienced +machine learning engineer + +10 +00:00:24,960 --> 00:00:28,230 +who's used Keras a lot, that's +probably all you need to know + +11 +00:00:28,230 --> 00:00:29,610 +to start working with them. + +12 +00:00:29,610 --> 00:00:30,900 +But for everyone else, + +13 +00:00:30,900 --> 00:00:34,170 +including the prodigal +PyTorch engineers out there + +14 +00:00:34,170 --> 00:00:35,910 +who are returning to the fold, + +15 +00:00:35,910 --> 00:00:38,430 +I'm going to quickly +introduce Keras models, + +16 +00:00:38,430 --> 00:00:40,440 +and how we work with them. + +17 +00:00:40,440 --> 00:00:43,080 +In other videos, which I'll link below, + +18 +00:00:43,080 --> 00:00:46,440 +I'll run through training with +Keras models in more detail. + +19 +00:00:46,440 --> 00:00:50,820 +But first, at a high level, +what is a Keras model? + +20 +00:00:50,820 --> 00:00:54,810 +So your model basically +contains your entire network. + +21 +00:00:54,810 --> 00:00:58,230 +It contains the layers, and +the weights for those layers, + +22 +00:00:58,230 --> 00:01:00,690 +and also tells the model +what to do with them + +23 +00:01:00,690 --> 00:01:02,880 +so it defines the whole path all the way + +24 +00:01:02,880 --> 00:01:05,460 +from your inputs to your outputs. + +25 +00:01:05,460 --> 00:01:07,380 +If you've used Keras before, + +26 +00:01:07,380 --> 00:01:09,480 +you probably started using model objects + +27 +00:01:09,480 --> 00:01:11,850 +by building them out by hand, + +28 +00:01:11,850 --> 00:01:14,250 +you added one layer after another + +29 +00:01:14,250 --> 00:01:18,690 +and maybe using the model.add() +or the functional approach. + +30 +00:01:18,690 --> 00:01:20,490 +And there's nothing wrong with that. + +31 +00:01:21,390 --> 00:01:23,430 +Lots of great models are built that way + +32 +00:01:23,430 --> 00:01:26,970 +but you can also pre-load an +entire model, weights and all. + +33 +00:01:26,970 --> 00:01:29,994 +And this is really +helpful, because if you, + +34 +00:01:29,994 --> 00:01:32,490 +as you can see here, if +you try reading the paper + +35 +00:01:32,490 --> 00:01:34,110 +or if you try looking at the code, + +36 +00:01:34,110 --> 00:01:37,350 +you'll see the inside of a +Transformer is pretty complex, + +37 +00:01:37,350 --> 00:01:40,110 +and writing it all out from +scratch and getting it right + +38 +00:01:40,110 --> 00:01:41,850 +would be hard even for an experienced + +39 +00:01:41,850 --> 00:01:43,500 +machine learning engineer. + +40 +00:01:43,500 --> 00:01:45,870 +But because it's all +packed inside a model, + +41 +00:01:45,870 --> 00:01:48,150 +you don't need to worry +about that complexity on that + +42 +00:01:48,150 --> 00:01:49,140 +if you don't want to. + +43 +00:01:49,140 --> 00:01:51,570 +If you're a researcher, if you +want to really dig in there + +44 +00:01:51,570 --> 00:01:55,650 +you can, but you can also +just load a pre-trained, + +45 +00:01:55,650 --> 00:01:59,013 +pre-configured transformer +model in just one line of code. + +46 +00:02:00,150 --> 00:02:03,480 +And when I mentioned +earlier about the Keras API, + +47 +00:02:03,480 --> 00:02:04,560 +the advantage of it is that + +48 +00:02:04,560 --> 00:02:06,690 +whether you write your +own model from scratch + +49 +00:02:06,690 --> 00:02:09,510 +or load a pre-trained one, +you interact with the model + +50 +00:02:09,510 --> 00:02:11,850 +through that same API, so you use exactly + +51 +00:02:11,850 --> 00:02:13,950 +the same few methods and +you're gonna see them + +52 +00:02:13,950 --> 00:02:16,380 +again and again, these methods like fit, + +53 +00:02:16,380 --> 00:02:19,650 +compile and predict, +and like I've mentioned + +54 +00:02:19,650 --> 00:02:22,530 +we'll cover concrete examples +of how to use those methods + +55 +00:02:22,530 --> 00:02:24,330 +in the videos I'll link below. + +56 +00:02:24,330 --> 00:02:27,000 +For now the key thing to +take away from this video, + +57 +00:02:27,000 --> 00:02:28,950 +if you've never seen Keras before, + +58 +00:02:28,950 --> 00:02:30,870 +is that this neat encapsulation means + +59 +00:02:30,870 --> 00:02:33,090 +that all the complexity +of a huge neural net + +60 +00:02:33,090 --> 00:02:35,430 +becomes manageable, because +you interact with it + +61 +00:02:35,430 --> 00:02:39,000 +in exactly the same way, using +exactly the same methods, + +62 +00:02:39,000 --> 00:02:41,700 +whether it's a huge +pre-trained language model + +63 +00:02:41,700 --> 00:02:43,950 +or a simple model that +you wrote out by hand. + +64 +00:02:45,466 --> 00:02:48,049 +(upbeat music) + diff --git a/subtitles/en/26_fine-tuning-with-tensorflow.srt b/subtitles/en/26_fine-tuning-with-tensorflow.srt new file mode 100644 index 000000000..fb2536667 --- /dev/null +++ b/subtitles/en/26_fine-tuning-with-tensorflow.srt @@ -0,0 +1,567 @@ +1 +00:00:00,253 --> 00:00:02,920 +(air whooshing) + +2 +00:00:06,060 --> 00:00:08,070 +- In this video, we're going to see + +3 +00:00:08,070 --> 00:00:11,430 +how to load and fine +tune a pre-trained model. + +4 +00:00:11,430 --> 00:00:12,510 +It's very quick. + +5 +00:00:12,510 --> 00:00:14,490 +And if you've watched our pipeline videos, + +6 +00:00:14,490 --> 00:00:18,150 +which I'll link below, the +process is very similar. + +7 +00:00:18,150 --> 00:00:20,940 +This time, though, we're going +to be using transfer learning + +8 +00:00:20,940 --> 00:00:23,040 +and doing some training ourselves, + +9 +00:00:23,040 --> 00:00:26,400 +rather than just loading a +model and using it as is. + +10 +00:00:26,400 --> 00:00:28,710 +So to learn more about transfer learning, + +11 +00:00:28,710 --> 00:00:31,320 +head to the 'What is +transfer learning?' video, + +12 +00:00:31,320 --> 00:00:33,420 +and we'll link that below as well. + +13 +00:00:33,420 --> 00:00:35,610 +But for now, let's look at this code. + +14 +00:00:35,610 --> 00:00:38,730 +To start, we pick which +model we want to start with. + +15 +00:00:38,730 --> 00:00:40,920 +In this case, we're +going to use the famous, + +16 +00:00:40,920 --> 00:00:42,060 +the original BERT, + +17 +00:00:42,060 --> 00:00:44,850 +as the foundation for our training today. + +18 +00:00:44,850 --> 00:00:46,770 +But what is this monstrosity line, + +19 +00:00:46,770 --> 00:00:48,797 +this +'TFAutoModelForSequenceClassification'? + +20 +00:00:49,860 --> 00:00:51,180 +What does that mean? + +21 +00:00:51,180 --> 00:00:53,130 +Well, the TF stands for TensorFlow. + +22 +00:00:53,130 --> 00:00:54,660 +And the rest means, + +23 +00:00:54,660 --> 00:00:55,950 +take a language model, + +24 +00:00:55,950 --> 00:00:58,380 +and stick a sequence +classification head onto it + +25 +00:00:58,380 --> 00:01:00,750 +if it doesn't have one already. + +26 +00:01:00,750 --> 00:01:02,880 +So this line of code loads BERT, + +27 +00:01:02,880 --> 00:01:05,040 +which is a general purpose language model, + +28 +00:01:05,040 --> 00:01:07,650 +it loads at weights, architecture, and all + +29 +00:01:07,650 --> 00:01:10,920 +and then adds a new sequence +classification head onto it + +30 +00:01:10,920 --> 00:01:13,440 +with randomly initialized weights. + +31 +00:01:13,440 --> 00:01:15,870 +So this method needs to know two things. + +32 +00:01:15,870 --> 00:01:18,270 +Firstly, it needs to know +the name of the model + +33 +00:01:18,270 --> 00:01:21,060 +you wanted to load, the +architecture and weights for. + +34 +00:01:21,060 --> 00:01:23,940 +And secondly, it needs +to know how many classes + +35 +00:01:23,940 --> 00:01:26,693 +your problem has, because +that will determine the size, + +36 +00:01:26,693 --> 00:01:29,610 +the number of neurons in the output head. + +37 +00:01:29,610 --> 00:01:31,530 +So if you want to follow +along with the data + +38 +00:01:31,530 --> 00:01:34,500 +from our datasets videos, +which I'll link below, + +39 +00:01:34,500 --> 00:01:37,440 +then you'll have two classes, +positive and negative, + +40 +00:01:37,440 --> 00:01:39,723 +and thus num_labels equals two. + +41 +00:01:40,830 --> 00:01:43,230 +But what about this compile line? + +42 +00:01:43,230 --> 00:01:44,970 +Well, if you're familiar with Keras, + +43 +00:01:44,970 --> 00:01:46,920 +you've probably seen this already. + +44 +00:01:46,920 --> 00:01:49,800 +But if not, this is one of +the core methods in Keras + +45 +00:01:49,800 --> 00:01:51,450 +that you're gonna see again, and again. + +46 +00:01:51,450 --> 00:01:54,900 +You always need to compile +your model before you train it. + +47 +00:01:54,900 --> 00:01:57,870 +And compile needs to know two things. + +48 +00:01:57,870 --> 00:02:00,090 +Firstly, it needs to +know the loss function, + +49 +00:02:00,090 --> 00:02:02,340 +which is what you're trying to optimize. + +50 +00:02:02,340 --> 00:02:05,910 +So here, we import the +SparseCategoricalCrossentropy + +51 +00:02:05,910 --> 00:02:07,260 +loss function. + +52 +00:02:07,260 --> 00:02:09,930 +So that's a mouthful, but it's +the standard loss function + +53 +00:02:09,930 --> 00:02:13,260 +for any neural network that's +doing a classification task. + +54 +00:02:13,260 --> 00:02:14,970 +It basically encourages the network + +55 +00:02:14,970 --> 00:02:17,730 +to output large values +for the right class, + +56 +00:02:17,730 --> 00:02:20,910 +and low values for the wrong classes. + +57 +00:02:20,910 --> 00:02:24,150 +Note that you can specify the +loss function as a string, + +58 +00:02:24,150 --> 00:02:26,010 +like we did with the optimizer. + +59 +00:02:26,010 --> 00:02:27,600 +But there's a risk there, + +60 +00:02:27,600 --> 00:02:30,090 +there's a very common +trap people fall into, + +61 +00:02:30,090 --> 00:02:32,580 +which is that by default, +this loss assumes + +62 +00:02:32,580 --> 00:02:36,510 +the output is probabilities +after a softmax layer. + +63 +00:02:36,510 --> 00:02:38,310 +But what our model has actually output + +64 +00:02:38,310 --> 00:02:40,770 +is the values before the softmax, + +65 +00:02:40,770 --> 00:02:43,800 +often called the logits, sometimes logits. + +66 +00:02:43,800 --> 00:02:46,110 +No one's quite sure how +to pronounce that one. + +67 +00:02:46,110 --> 00:02:47,790 +But you probably seen these before + +68 +00:02:47,790 --> 00:02:49,950 +in the video about pipelines. + +69 +00:02:49,950 --> 00:02:52,320 +So if you get this wrong, +your model won't train + +70 +00:02:52,320 --> 00:02:54,723 +and it'll be very annoying +to figure out why. + +71 +00:02:55,590 --> 00:02:57,540 +In future videos, we're gonna see + +72 +00:02:57,540 --> 00:03:00,540 +how to use the model's +internal loss computations, + +73 +00:03:00,540 --> 00:03:02,910 +so that you don't have to +specify the loss yourself + +74 +00:03:02,910 --> 00:03:05,340 +and you don't have to +worry about these details. + +75 +00:03:05,340 --> 00:03:09,480 +But for now, remember to +set from_logits to true. + +76 +00:03:09,480 --> 00:03:11,430 +The second thing compile needs to know + +77 +00:03:11,430 --> 00:03:13,230 +is the optimizer you want. + +78 +00:03:13,230 --> 00:03:15,120 +In our case, we use adam, + +79 +00:03:15,120 --> 00:03:16,830 +which is sort of the standard optimizer + +80 +00:03:16,830 --> 00:03:18,720 +for deep learning these days. + +81 +00:03:18,720 --> 00:03:20,520 +The one thing you might want to change + +82 +00:03:20,520 --> 00:03:21,780 +is the learning rate. + +83 +00:03:21,780 --> 00:03:24,630 +And to do that, we'll need to +import the actual optimizer + +84 +00:03:24,630 --> 00:03:26,910 +rather than just calling it by string. + +85 +00:03:26,910 --> 00:03:28,680 +But we'll talk about +that in another video, + +86 +00:03:28,680 --> 00:03:30,090 +which I'll link below. + +87 +00:03:30,090 --> 00:03:33,360 +For now, let's just +try training the model. + +88 +00:03:33,360 --> 00:03:35,580 +Well, so how do you train the model? + +89 +00:03:35,580 --> 00:03:37,950 +Again, if you've used Keras before, + +90 +00:03:37,950 --> 00:03:40,350 +this is all going to be +very familiar to you. + +91 +00:03:40,350 --> 00:03:42,210 +But if not, let's very quickly look + +92 +00:03:42,210 --> 00:03:43,710 +at what we're doing here. + +93 +00:03:43,710 --> 00:03:47,010 +fit is pretty much the central +method for Keras models. + +94 +00:03:47,010 --> 00:03:49,983 +It tells the model to train +on the data we're passing in. + +95 +00:03:50,820 --> 00:03:52,920 +So here we pass the datasets we made + +96 +00:03:52,920 --> 00:03:54,510 +in the previous section, + +97 +00:03:54,510 --> 00:03:57,990 +the dataset contains both +our inputs and our labels. + +98 +00:03:57,990 --> 00:04:00,420 +So we don't need to +specify separate labels, + +99 +00:04:00,420 --> 00:04:01,570 +when we're calling fit. + +100 +00:04:02,490 --> 00:04:05,340 +Then we do the same thing +with the validation_data. + +101 +00:04:05,340 --> 00:04:08,190 +And then we can if we want, +we can specify details, + +102 +00:04:08,190 --> 00:04:09,900 +like the number of epochs for training + +103 +00:04:09,900 --> 00:04:12,420 +where there's some other +arguments you can pass to fit. + +104 +00:04:12,420 --> 00:04:15,240 +But in the end, you just +pass all of this to model.fit + +105 +00:04:15,240 --> 00:04:16,440 +and you let it run. + +106 +00:04:16,440 --> 00:04:17,520 +If everything works out, + +107 +00:04:17,520 --> 00:04:19,320 +you should see a little training bar + +108 +00:04:19,320 --> 00:04:21,300 +progressing along as your loss goes down. + +109 +00:04:21,300 --> 00:04:22,290 +And that's it. + +110 +00:04:22,290 --> 00:04:23,123 +While that's running, + +111 +00:04:23,123 --> 00:04:25,380 +you know, you can call +your boss and tell them + +112 +00:04:25,380 --> 00:04:27,810 +you're a senior NLP machine +learning engineer now + +113 +00:04:27,810 --> 00:04:30,900 +and you're gonna want a +salary review next quarter. + +114 +00:04:30,900 --> 00:04:32,880 +These few lines of code +are really all it takes + +115 +00:04:32,880 --> 00:04:34,500 +to apply the power of a massive + +116 +00:04:34,500 --> 00:04:36,510 +pre-trained language problem, + +117 +00:04:36,510 --> 00:04:38,250 +massive pre-trained +language model, excuse me, + +118 +00:04:38,250 --> 00:04:40,080 +to your NLP problem. + +119 +00:04:40,080 --> 00:04:42,150 +But could we do better than this? + +120 +00:04:42,150 --> 00:04:43,920 +I mean, we certainly could. + +121 +00:04:43,920 --> 00:04:45,720 +With a few more advanced Keras features + +122 +00:04:45,720 --> 00:04:47,730 +like a tuned, scheduled learning rate, + +123 +00:04:47,730 --> 00:04:49,290 +we can get an even lower loss + +124 +00:04:49,290 --> 00:04:51,990 +and an even more accurate, +more useful model. + +125 +00:04:51,990 --> 00:04:54,120 +And what do we do with our +model after we train it? + +126 +00:04:54,120 --> 00:04:55,950 +So all of this is going to +be covered in the videos + +127 +00:04:55,950 --> 00:04:57,963 +that are coming up, so stay tuned. + +128 +00:04:59,220 --> 00:05:01,887 +(air whooshing) + diff --git a/subtitles/en/27_learning-rate-scheduling-with-tensorflow.srt b/subtitles/en/27_learning-rate-scheduling-with-tensorflow.srt new file mode 100644 index 000000000..4a5688bea --- /dev/null +++ b/subtitles/en/27_learning-rate-scheduling-with-tensorflow.srt @@ -0,0 +1,468 @@ +1 +00:00:00,288 --> 00:00:02,639 +(screen swishing) + +2 +00:00:02,639 --> 00:00:05,190 +(text swishing) + +3 +00:00:05,190 --> 00:00:06,780 +In our other videos, + +4 +00:00:06,780 --> 00:00:08,280 +we talked about the basics + +5 +00:00:08,280 --> 00:00:11,610 +of fine-tuning a language +model with Tensorflow, + +6 +00:00:11,610 --> 00:00:15,030 +and as always, when I refer to +videos I'll link them below. + +7 +00:00:15,030 --> 00:00:17,610 +Still, can we do better? + +8 +00:00:17,610 --> 00:00:20,700 +So here's the code from our +model fine-tuning video, + +9 +00:00:20,700 --> 00:00:21,600 +and while it works, + +10 +00:00:21,600 --> 00:00:24,390 +we could definitely +tweak a couple of things. + +11 +00:00:24,390 --> 00:00:27,540 +By far the most important +thing is the learning rate. + +12 +00:00:27,540 --> 00:00:29,940 +In this video we'll talk +about how to change it, + +13 +00:00:29,940 --> 00:00:31,080 +which will make your training + +14 +00:00:31,080 --> 00:00:33,303 +much more consistently successful. + +15 +00:00:34,440 --> 00:00:37,320 +In fact, really there are two things + +16 +00:00:37,320 --> 00:00:40,530 +we want to change about the +default learning rate for Adam. + +17 +00:00:40,530 --> 00:00:42,720 +So the first we want to change + +18 +00:00:42,720 --> 00:00:45,630 +is that it's way too high for our models, + +19 +00:00:45,630 --> 00:00:48,030 +by default, Adam uses a learning rate + +20 +00:00:48,030 --> 00:00:51,540 +of 10 to the minus 3, 1 E minus 3, + +21 +00:00:51,540 --> 00:00:54,660 +and that's very high for +training transformer models. + +22 +00:00:54,660 --> 00:00:58,200 +We're going to start at +5 by 10 to the minus 5, + +23 +00:00:58,200 --> 00:01:02,700 +5 E minus 5, which is 20 +times lower than the default. + +24 +00:01:02,700 --> 00:01:06,330 +And secondly, we don't just +want a constant learning rate, + +25 +00:01:06,330 --> 00:01:07,950 +we can get even better performance + +26 +00:01:07,950 --> 00:01:11,160 +if we decay the learning +rate down to a tiny value, + +27 +00:01:11,160 --> 00:01:13,920 +or even to zero , over +the course of training. + +28 +00:01:13,920 --> 00:01:15,510 +So that's what this thing here, + +29 +00:01:15,510 --> 00:01:18,540 +this Polynomial Decay +schedule thing is doing. + +30 +00:01:18,540 --> 00:01:21,570 +So I'll show you what that +decay looks like in a second, + +31 +00:01:21,570 --> 00:01:23,160 +but first we need to tell the scheduler + +32 +00:01:23,160 --> 00:01:25,290 +how long training is going to be, + +33 +00:01:25,290 --> 00:01:27,450 +so that it decays at the right speed, + +34 +00:01:27,450 --> 00:01:29,450 +and that's what this code here is doing. + +35 +00:01:30,300 --> 00:01:32,280 +We're computing how many minibatches + +36 +00:01:32,280 --> 00:01:35,520 +the model is going to see +over its entire training run, + +37 +00:01:35,520 --> 00:01:37,950 +which is the size of the training set, + +38 +00:01:37,950 --> 00:01:39,570 +and then we multiply that + +39 +00:01:39,570 --> 00:01:41,220 +by the number of epochs + +40 +00:01:41,220 --> 00:01:42,930 +to get the total number of batches + +41 +00:01:42,930 --> 00:01:45,060 +across the whole training run. + +42 +00:01:45,060 --> 00:01:47,880 +Once we know how many +training steps we're taking, + +43 +00:01:47,880 --> 00:01:50,580 +we just pass all that +information to the scheduler + +44 +00:01:50,580 --> 00:01:51,783 +and we're ready to go. + +45 +00:01:53,110 --> 00:01:57,510 +What does the polynomial +decay schedule look like? + +46 +00:01:57,510 --> 00:01:59,610 +Well, it looks like this, + +47 +00:01:59,610 --> 00:02:02,160 +it starts at 5 E minus 5, + +48 +00:02:02,160 --> 00:02:05,490 +which means 5 times 10 to the minus 5, + +49 +00:02:05,490 --> 00:02:08,190 +and then decays down at a constant rate + +50 +00:02:08,190 --> 00:02:11,310 +until it hits zero right at +the very end of training. + +51 +00:02:11,310 --> 00:02:13,200 +So hold on, I can already hear you + +52 +00:02:13,200 --> 00:02:14,640 +yelling at your monitor, though, + +53 +00:02:14,640 --> 00:02:16,020 +and yes, I know, + +54 +00:02:16,020 --> 00:02:18,690 +this is actually constant +or a linear decay, + +55 +00:02:18,690 --> 00:02:20,310 +and I know the name is polynomial, + +56 +00:02:20,310 --> 00:02:21,870 +and you're feeling cheated that, you know, + +57 +00:02:21,870 --> 00:02:24,390 +you were promised a polynomial +and haven't gotten it, + +58 +00:02:24,390 --> 00:02:26,550 +so calm down though, it's okay, + +59 +00:02:26,550 --> 00:02:28,830 +because, of course, +linear functions are just + +60 +00:02:28,830 --> 00:02:30,480 +first-order special cases + +61 +00:02:30,480 --> 00:02:32,850 +of the general polynomial functions, + +62 +00:02:32,850 --> 00:02:36,180 +and if you tweak the +options to this class, + +63 +00:02:36,180 --> 00:02:38,130 +you can get a truly polynomial, + +64 +00:02:38,130 --> 00:02:40,170 +a higher-order decay schedule, + +65 +00:02:40,170 --> 00:02:43,140 +but this linear schedule will +work fine for us for now, + +66 +00:02:43,140 --> 00:02:45,210 +we don't actually need all those + +67 +00:02:45,210 --> 00:02:47,610 +fancy tweaks and fancy gadgets. + +68 +00:02:47,610 --> 00:02:49,770 +So coming back, + +69 +00:02:49,770 --> 00:02:51,990 +how do we actually use +this learning rate schedule + +70 +00:02:51,990 --> 00:02:53,460 +once we've created it? + +71 +00:02:53,460 --> 00:02:55,650 +So it's simple, we just pass it to Adam. + +72 +00:02:55,650 --> 00:02:58,560 +So the first time we compiled the model, + +73 +00:02:58,560 --> 00:03:00,840 +we just passed the string Adam, + +74 +00:03:00,840 --> 00:03:02,250 +to get our optimizer. + +75 +00:03:02,250 --> 00:03:05,340 +So Keras recognizes the +names of common optimizers + +76 +00:03:05,340 --> 00:03:07,920 +and loss functions if +you pass them as strings, + +77 +00:03:07,920 --> 00:03:09,480 +so it saves time to do that + +78 +00:03:09,480 --> 00:03:11,460 +if you only want the default settings. + +79 +00:03:11,460 --> 00:03:13,320 +But now we're professional +machine learners, + +80 +00:03:13,320 --> 00:03:15,720 +and, you know, that +salary review is upcoming, + +81 +00:03:15,720 --> 00:03:17,790 +so we've got our very own +learning rate schedule, + +82 +00:03:17,790 --> 00:03:19,770 +and we're gonna do things properly. + +83 +00:03:19,770 --> 00:03:22,830 +So the first we do is +we import the optimizer, + +84 +00:03:22,830 --> 00:03:24,960 +and then we initialize +it with a scheduler, + +85 +00:03:24,960 --> 00:03:27,540 +which is getting passed to +to the learning rate argument + +86 +00:03:27,540 --> 00:03:29,100 +of that optimizer. + +87 +00:03:29,100 --> 00:03:32,190 +And now we compile the model +with this new optimizer, + +88 +00:03:32,190 --> 00:03:34,140 +and again, whatever +loss function you want, + +89 +00:03:34,140 --> 00:03:37,050 +so this is going to be sparse +categorical crossentropy + +90 +00:03:37,050 --> 00:03:39,840 +if you're following along +from the fine-tuning video. + +91 +00:03:39,840 --> 00:03:41,370 +And then, we're we're ready to go, + +92 +00:03:41,370 --> 00:03:43,710 +now we have a high-performance model, + +93 +00:03:43,710 --> 00:03:44,970 +and ready for training. + +94 +00:03:44,970 --> 00:03:46,830 +All that remains is to fit the model + +95 +00:03:46,830 --> 00:03:48,363 +just like we did before. + +96 +00:03:49,350 --> 00:03:51,600 +Remember, because we compiled the model + +97 +00:03:51,600 --> 00:03:54,300 +with the new optimizer and the +new learning rate schedule, + +98 +00:03:54,300 --> 00:03:56,190 +we actually don't need +to change anything at all + +99 +00:03:56,190 --> 00:03:57,360 +when we call fit, + +100 +00:03:57,360 --> 00:03:58,290 +we just call it again, + +101 +00:03:58,290 --> 00:04:00,540 +with exactly the same command as before, + +102 +00:04:00,540 --> 00:04:02,400 +but now we get a beautiful training, + +103 +00:04:02,400 --> 00:04:04,740 +with a nice, smooth learning rate decay, + +104 +00:04:04,740 --> 00:04:06,330 +starting from a good value, + +105 +00:04:06,330 --> 00:04:07,713 +and decaying down to zero. + +106 +00:04:08,867 --> 00:04:12,059 +(screen swishing) + +107 +00:04:12,059 --> 00:04:13,395 +(screen swishing) + diff --git a/subtitles/en/28_tensorflow-predictions-and-metrics.srt b/subtitles/en/28_tensorflow-predictions-and-metrics.srt new file mode 100644 index 000000000..24f75a7d7 --- /dev/null +++ b/subtitles/en/28_tensorflow-predictions-and-metrics.srt @@ -0,0 +1,461 @@ +1 +00:00:00,269 --> 00:00:02,936 +(air whooshing) + +2 +00:00:05,700 --> 00:00:07,110 +- In our other videos, + +3 +00:00:07,110 --> 00:00:09,000 +and as always, there'll be links below + +4 +00:00:09,000 --> 00:00:10,740 +if you want to check those out, + +5 +00:00:10,740 --> 00:00:13,230 +we showed you how to +initialize and fine-tune + +6 +00:00:13,230 --> 00:00:15,690 +a transformer model in TensorFlow. + +7 +00:00:15,690 --> 00:00:18,600 +So the question now is, +what can we do with a model + +8 +00:00:18,600 --> 00:00:20,070 +after we train it? + +9 +00:00:20,070 --> 00:00:21,390 +The obvious thing to try + +10 +00:00:21,390 --> 00:00:23,790 +is to use it to get +predictions for new data, + +11 +00:00:23,790 --> 00:00:25,560 +so let's see how to do that. + +12 +00:00:25,560 --> 00:00:28,320 +Again, if you're familiar +with Keras, the good news + +13 +00:00:28,320 --> 00:00:31,860 +is that because there are +just standard Keras models, + +14 +00:00:31,860 --> 00:00:34,770 +we can use the standard +Keras predict method, + +15 +00:00:34,770 --> 00:00:35,883 +as shown here. + +16 +00:00:36,990 --> 00:00:40,560 +You simply pass in tokenized +text to this method, + +17 +00:00:40,560 --> 00:00:42,330 +like you'd get from a tokenizer, + +18 +00:00:42,330 --> 00:00:44,280 +and you get your results. + +19 +00:00:44,280 --> 00:00:46,740 +Our models can output +several different things, + +20 +00:00:46,740 --> 00:00:48,510 +depending on the options you set, + +21 +00:00:48,510 --> 00:00:50,310 +but most of the time the thing you want + +22 +00:00:50,310 --> 00:00:52,290 +is the output logits. + +23 +00:00:52,290 --> 00:00:54,900 +If you haven't come +across them before logits, + +24 +00:00:54,900 --> 00:00:57,630 +sometimes pronounced to +logits because no one's sure, + +25 +00:00:57,630 --> 00:01:00,390 +are the outputs of the +last layer of the network + +26 +00:01:00,390 --> 00:01:03,150 +because before a softmax has been applied. + +27 +00:01:03,150 --> 00:01:04,710 +So if you want to turn the logits + +28 +00:01:04,710 --> 00:01:06,900 +into the model's probability outputs, + +29 +00:01:06,900 --> 00:01:09,423 +you just apply a softmax, like so. + +30 +00:01:10,981 --> 00:01:12,630 +What if we want to turn +those probabilities + +31 +00:01:12,630 --> 00:01:14,370 +into class predictions? + +32 +00:01:14,370 --> 00:01:16,410 +Again, it's very straightforward. + +33 +00:01:16,410 --> 00:01:19,470 +We just pick the biggest +probability for each output + +34 +00:01:19,470 --> 00:01:23,070 +and you can get that immediately +with the argmax function. + +35 +00:01:23,070 --> 00:01:24,870 +argmax will return the index + +36 +00:01:24,870 --> 00:01:27,120 +of the largest probability in each row + +37 +00:01:27,120 --> 00:01:30,360 +which means that we'll +get a vector of integers. + +38 +00:01:30,360 --> 00:01:34,950 +So zero if the largest probability +was in the zero position, + +39 +00:01:34,950 --> 00:01:37,350 +one in the first position, and so on. + +40 +00:01:37,350 --> 00:01:40,380 +So these are our class +predictions indicating class zero, + +41 +00:01:40,380 --> 00:01:42,300 +class one, and so on. + +42 +00:01:42,300 --> 00:01:45,090 +In fact, if class +predictions are all you want, + +43 +00:01:45,090 --> 00:01:47,310 +you can skip the softmax step entirely + +44 +00:01:47,310 --> 00:01:49,740 +because the largest logit +will always be the largest + +45 +00:01:49,740 --> 00:01:51,303 +probability as well. + +46 +00:01:52,500 --> 00:01:55,800 +So if probabilities and class +predictions are all you want, + +47 +00:01:55,800 --> 00:01:58,350 +then you've seen everything +you need at this point. + +48 +00:01:58,350 --> 00:02:00,630 +But if you're interested +in benchmarking your model + +49 +00:02:00,630 --> 00:02:02,190 +or using it for research, + +50 +00:02:02,190 --> 00:02:05,010 +you might want to delve deeper +into the results you get. + +51 +00:02:05,010 --> 00:02:07,230 +And one way to do that is +to compute some metrics + +52 +00:02:07,230 --> 00:02:09,060 +for the model's predictions. + +53 +00:02:09,060 --> 00:02:10,920 +If you're following +along with our datasets + +54 +00:02:10,920 --> 00:02:12,390 +and fine tuning videos, + +55 +00:02:12,390 --> 00:02:14,850 +we got our data from the MRPC dataset, + +56 +00:02:14,850 --> 00:02:17,130 +which is part of the GLUE benchmark. + +57 +00:02:17,130 --> 00:02:19,050 +Each of the GLUE datasets + +58 +00:02:19,050 --> 00:02:22,560 +as well as many other datasets +in our dataset, Light Hub + +59 +00:02:22,560 --> 00:02:24,510 +has some predefined metrics, + +60 +00:02:24,510 --> 00:02:26,940 +and we can load them easily + +61 +00:02:26,940 --> 00:02:29,880 +with the datasets load metric function. + +62 +00:02:29,880 --> 00:02:33,720 +For the MRPC dataset, the +built-in metrics are accuracy + +63 +00:02:33,720 --> 00:02:35,790 +which just measures the +percentage of the time + +64 +00:02:35,790 --> 00:02:37,830 +the model's prediction was correct, + +65 +00:02:37,830 --> 00:02:39,780 +and the F1 score, + +66 +00:02:39,780 --> 00:02:41,610 +which is a slightly more complex measure + +67 +00:02:41,610 --> 00:02:43,920 +that measures how well +the model trades off + +68 +00:02:43,920 --> 00:02:45,543 +precision and recall. + +69 +00:02:46,470 --> 00:02:49,110 +To compute those metrics +to benchmark our model, + +70 +00:02:49,110 --> 00:02:51,480 +we just pass them the model's predictions, + +71 +00:02:51,480 --> 00:02:53,220 +and to the ground truth labels, + +72 +00:02:53,220 --> 00:02:56,880 +and we get our results in a +straightforward Python dict. + +73 +00:02:56,880 --> 00:02:58,740 +If you're familiar with Keras though, + +74 +00:02:58,740 --> 00:03:00,870 +you might notice that this +is a slightly weird way + +75 +00:03:00,870 --> 00:03:01,800 +to compute metrics, + +76 +00:03:01,800 --> 00:03:02,970 +because we're only computing metrics + +77 +00:03:02,970 --> 00:03:04,440 +at the very end of training. + +78 +00:03:04,440 --> 00:03:06,480 +But in Keras, you have +this built-in ability + +79 +00:03:06,480 --> 00:03:08,790 +to compute a wide range of metrics + +80 +00:03:08,790 --> 00:03:10,470 +on the fly while you're training, + +81 +00:03:10,470 --> 00:03:11,910 +which gives you a very useful insight + +82 +00:03:11,910 --> 00:03:13,740 +into how training is going. + +83 +00:03:13,740 --> 00:03:15,900 +So if you want to use built-in metrics, + +84 +00:03:15,900 --> 00:03:17,280 +it's very straightforward + +85 +00:03:17,280 --> 00:03:19,350 +and you use the standard +Keras approach again. + +86 +00:03:19,350 --> 00:03:23,160 +You just pass a metric +argument to the compile method. + +87 +00:03:23,160 --> 00:03:25,740 +As with things like loss and optimizer, + +88 +00:03:25,740 --> 00:03:28,470 +you can specify the +metrics you want by string + +89 +00:03:28,470 --> 00:03:30,810 +or you can import the +actual metric objects + +90 +00:03:30,810 --> 00:03:33,240 +and pass specific arguments to them. + +91 +00:03:33,240 --> 00:03:35,610 +But note that unlike loss and accuracy, + +92 +00:03:35,610 --> 00:03:37,710 +you have to supply metrics as a list + +93 +00:03:37,710 --> 00:03:39,760 +even if there's only one metric you want. + +94 +00:03:40,770 --> 00:03:43,140 +Once a model has been +compiled with a metric, + +95 +00:03:43,140 --> 00:03:45,360 +it will report that metric for training, + +96 +00:03:45,360 --> 00:03:47,643 +validation, and predictions. + +97 +00:03:48,480 --> 00:03:50,820 +Assuming there are labels +passed to the predictions. + +98 +00:03:50,820 --> 00:03:53,400 +You can even write your +own metric classes. + +99 +00:03:53,400 --> 00:03:55,920 +Although this is a bit beyond +the scope of this course, + +100 +00:03:55,920 --> 00:03:58,200 +I'll link to the relevant TF docs below + +101 +00:03:58,200 --> 00:03:59,580 +because it can be very handy + +102 +00:03:59,580 --> 00:04:01,320 +if you want a metric that isn't supported + +103 +00:04:01,320 --> 00:04:02,850 +by default in Keras, + +104 +00:04:02,850 --> 00:04:04,473 +such as the F1 score. + +105 +00:04:06,076 --> 00:04:08,743 +(air whooshing) + diff --git a/subtitles/en/29_write-your-training-loop-in-pytorch.srt b/subtitles/en/29_write-your-training-loop-in-pytorch.srt new file mode 100644 index 000000000..a517fd436 --- /dev/null +++ b/subtitles/en/29_write-your-training-loop-in-pytorch.srt @@ -0,0 +1,536 @@ +1 +00:00:00,298 --> 00:00:01,511 +(air whooshing) + +2 +00:00:01,511 --> 00:00:02,769 +(smiley face popping) + +3 +00:00:02,769 --> 00:00:05,460 +(air whooshing) + +4 +00:00:05,460 --> 00:00:08,486 +- Write your own training +loop with PyTorch. + +5 +00:00:08,486 --> 00:00:09,960 +In this video, we'll look at + +6 +00:00:09,960 --> 00:00:12,750 +how we can do the same fine-tuning +as in the Trainer video, + +7 +00:00:12,750 --> 00:00:14,760 +but without relying on that class. + +8 +00:00:14,760 --> 00:00:17,790 +This way, you'll be able to +easily customize each step + +9 +00:00:17,790 --> 00:00:20,310 +to the training loop to your needs. + +10 +00:00:20,310 --> 00:00:21,660 +This is also very useful + +11 +00:00:21,660 --> 00:00:22,740 +to manually debug something + +12 +00:00:22,740 --> 00:00:24,590 +that went wrong with the Trainer API. + +13 +00:00:26,220 --> 00:00:28,020 +Before we dive into the code, + +14 +00:00:28,020 --> 00:00:30,481 +here is a sketch of a training loop. + +15 +00:00:30,481 --> 00:00:33,381 +We take a batch of training +data and feed it to the model. + +16 +00:00:34,223 --> 00:00:36,960 +With the labels, we can +then compute a loss. + +17 +00:00:36,960 --> 00:00:39,316 +That number is not useful in its own, + +18 +00:00:39,316 --> 00:00:40,260 +that is used to compute + +19 +00:00:40,260 --> 00:00:42,150 +the ingredients of our model weights, + +20 +00:00:42,150 --> 00:00:43,440 +that is the derivative of the loss + +21 +00:00:44,610 --> 00:00:47,160 +with respect to each model weight. + +22 +00:00:47,160 --> 00:00:49,800 +Those gradients are then +used by the optimizer + +23 +00:00:49,800 --> 00:00:51,210 +to update the model weights, + +24 +00:00:51,210 --> 00:00:53,550 +and make them a little bit better. + +25 +00:00:53,550 --> 00:00:54,510 +We then repeat the process + +26 +00:00:54,510 --> 00:00:56,880 +with a new batch of training data. + +27 +00:00:56,880 --> 00:00:58,620 +If any of this isn't clear, + +28 +00:00:58,620 --> 00:01:00,270 +don't hesitate to take a refresher + +29 +00:01:00,270 --> 00:01:02,170 +on your favorite deep learning course. + +30 +00:01:03,210 --> 00:01:06,000 +We'll use the GLUE MRPC +data set here again, + +31 +00:01:06,000 --> 00:01:07,680 +and we've seen how to prepropose the data + +32 +00:01:07,680 --> 00:01:11,130 +using the Datasets library +with dynamic padding. + +33 +00:01:11,130 --> 00:01:12,630 +Check out the videos link below + +34 +00:01:12,630 --> 00:01:14,280 +if you haven't seen them already. + +35 +00:01:15,480 --> 00:01:18,930 +With this done, we only have +to define PyTorch DataLoaders + +36 +00:01:18,930 --> 00:01:20,610 +which will be responsible to convert + +37 +00:01:20,610 --> 00:01:23,253 +the elements of our dataset into patches. + +38 +00:01:24,450 --> 00:01:27,960 +We use our DataColletorForPadding +as a collate function, + +39 +00:01:27,960 --> 00:01:29,460 +and shuffle the training set + +40 +00:01:29,460 --> 00:01:31,080 +to make sure we don't go over the samples + +41 +00:01:31,080 --> 00:01:33,870 +in the same order at a epoch*. + +42 +00:01:33,870 --> 00:01:36,390 +To check that everything +works as intended, + +43 +00:01:36,390 --> 00:01:38,883 +we try to grab a batch +of data, and inspect it. + +44 +00:01:40,080 --> 00:01:43,050 +Like our data set elements, +it's a dictionary, + +45 +00:01:43,050 --> 00:01:46,260 +but these times the values are +not a single list of integers + +46 +00:01:46,260 --> 00:01:49,053 +but a tensor of shape batch +size by sequence length. + +47 +00:01:50,460 --> 00:01:53,580 +The next step is to send the +training data in our model. + +48 +00:01:53,580 --> 00:01:56,730 +For that, we'll need to +actually create a model. + +49 +00:01:56,730 --> 00:01:58,740 +As seen in the Model API video, + +50 +00:01:58,740 --> 00:02:00,540 +we use the from_pretrained method, + +51 +00:02:00,540 --> 00:02:03,270 +and adjust the number of +labels to the number of classes + +52 +00:02:03,270 --> 00:02:06,810 +we have on this data set, here two. + +53 +00:02:06,810 --> 00:02:08,940 +Again to be sure everything is going well, + +54 +00:02:08,940 --> 00:02:11,100 +we pass the batch we grabbed to our model, + +55 +00:02:11,100 --> 00:02:13,320 +and check there is no error. + +56 +00:02:13,320 --> 00:02:14,940 +If the labels are provided, + +57 +00:02:14,940 --> 00:02:16,590 +the models of the Transformers library + +58 +00:02:16,590 --> 00:02:18,273 +always returns a loss directly. + +59 +00:02:19,525 --> 00:02:21,090 +We will be able to do loss.backward() + +60 +00:02:21,090 --> 00:02:22,860 +to compute all the gradients, + +61 +00:02:22,860 --> 00:02:26,460 +and will then need an optimizer +to do the training step. + +62 +00:02:26,460 --> 00:02:28,860 +We use the AdamW optimizer here, + +63 +00:02:28,860 --> 00:02:31,440 +which is a variant of Adam +with proper weight decay, + +64 +00:02:31,440 --> 00:02:33,840 +but you can pick any +PyTorch optimizer you like. + +65 +00:02:34,830 --> 00:02:36,150 +Using the previous loss, + +66 +00:02:36,150 --> 00:02:39,060 +and computing the gradients +with loss.backward(), + +67 +00:02:39,060 --> 00:02:41,130 +we check that we can do the optimizer step + +68 +00:02:41,130 --> 00:02:42,030 +without any error. + +69 +00:02:43,380 --> 00:02:45,870 +Don't forget to zero +your gradient afterwards, + +70 +00:02:45,870 --> 00:02:46,890 +or at the next step, + +71 +00:02:46,890 --> 00:02:49,343 +they will get added to the +gradients you computed. + +72 +00:02:50,490 --> 00:02:52,080 +We could already write our training loop, + +73 +00:02:52,080 --> 00:02:53,220 +but we will add two more things + +74 +00:02:53,220 --> 00:02:55,620 +to make it as good as it can be. + +75 +00:02:55,620 --> 00:02:57,690 +The first one is a +learning rate scheduler, + +76 +00:02:57,690 --> 00:03:00,140 +to progressively decay +our learning rate to zero. + +77 +00:03:01,195 --> 00:03:04,590 +The get_scheduler function +from the Transformers library + +78 +00:03:04,590 --> 00:03:06,150 +is just a convenience function + +79 +00:03:06,150 --> 00:03:07,800 +to easily build such a scheduler. + +80 +00:03:08,850 --> 00:03:09,683 +You can again use + +81 +00:03:09,683 --> 00:03:11,860 +any PyTorch learning +rate scheduler instead. + +82 +00:03:13,110 --> 00:03:14,850 +Finally, if we want our training + +83 +00:03:14,850 --> 00:03:17,610 +to take a couple of minutes +instead of a few hours, + +84 +00:03:17,610 --> 00:03:19,530 +we will need to use a GPU. + +85 +00:03:19,530 --> 00:03:21,270 +The first step is to get one, + +86 +00:03:21,270 --> 00:03:23,283 +for instance by using a collab notebook. + +87 +00:03:24,180 --> 00:03:26,040 +Then you need to actually send your model, + +88 +00:03:26,040 --> 00:03:28,923 +and training data on it +by using a torch device. + +89 +00:03:29,790 --> 00:03:30,840 +Double-check the following lines + +90 +00:03:30,840 --> 00:03:32,340 +print a CUDA device for you. + +91 +00:03:32,340 --> 00:03:35,640 +or be prepared for your training +to less, more than an hour. + +92 +00:03:35,640 --> 00:03:37,390 +We can now put everything together. + +93 +00:03:38,550 --> 00:03:40,860 +First, we put our model in training mode + +94 +00:03:40,860 --> 00:03:42,240 +which will activate the training behavior + +95 +00:03:42,240 --> 00:03:44,790 +for some layers, like Dropout. + +96 +00:03:44,790 --> 00:03:46,860 +Then go through the number +of epochs we picked, + +97 +00:03:46,860 --> 00:03:50,070 +and all the data in our +training dataloader. + +98 +00:03:50,070 --> 00:03:52,410 +Then we go through all the +steps we have seen already; + +99 +00:03:52,410 --> 00:03:54,240 +send the data to the GPU, + +100 +00:03:54,240 --> 00:03:55,560 +compute the model outputs, + +101 +00:03:55,560 --> 00:03:57,720 +and in particular the loss. + +102 +00:03:57,720 --> 00:03:59,850 +Use the loss to compute gradients, + +103 +00:03:59,850 --> 00:04:02,880 +then make a training +step with the optimizer. + +104 +00:04:02,880 --> 00:04:04,500 +Update the learning rate in our scheduler + +105 +00:04:04,500 --> 00:04:05,970 +for the next iteration, + +106 +00:04:05,970 --> 00:04:07,763 +and zero the gradients of the optimizer. + +107 +00:04:09,240 --> 00:04:10,500 +Once this is finished, + +108 +00:04:10,500 --> 00:04:12,150 +we can evaluate our model very easily + +109 +00:04:12,150 --> 00:04:14,283 +with a metric from the Datasets library. + +110 +00:04:15,180 --> 00:04:17,880 +First, we put our model +in evaluation mode, + +111 +00:04:17,880 --> 00:04:20,550 +to deactivate layers like Dropout, + +112 +00:04:20,550 --> 00:04:23,850 +then go through all the data +in the evaluation data loader. + +113 +00:04:23,850 --> 00:04:25,530 +As we have seen in the Trainer video, + +114 +00:04:25,530 --> 00:04:26,850 +the model outputs logits, + +115 +00:04:26,850 --> 00:04:28,530 +and we need to apply the argmax function + +116 +00:04:28,530 --> 00:04:30,213 +to convert them into predictions. + +117 +00:04:31,350 --> 00:04:33,420 +The metric object then +has an add_batch method, + +118 +00:04:33,420 --> 00:04:36,810 +we can use to send it those +intermediate predictions. + +119 +00:04:36,810 --> 00:04:38,700 +Once the evaluation loop is finished, + +120 +00:04:38,700 --> 00:04:40,320 +we just have to call the compute method + +121 +00:04:40,320 --> 00:04:42,180 +to get our final results. + +122 +00:04:42,180 --> 00:04:44,490 +Congratulations, you have +now fine-tuned a model + +123 +00:04:44,490 --> 00:04:45,633 +all by yourself. + +124 +00:04:47,253 --> 00:04:49,920 +(air whooshing) + diff --git a/subtitles/en/30_supercharge-your-pytorch-training-loop-with-accelerate.srt b/subtitles/en/30_supercharge-your-pytorch-training-loop-with-accelerate.srt new file mode 100644 index 000000000..35913784c --- /dev/null +++ b/subtitles/en/30_supercharge-your-pytorch-training-loop-with-accelerate.srt @@ -0,0 +1,322 @@ +1 +00:00:00,225 --> 00:00:02,892 +(air whooshing) + +2 +00:00:05,460 --> 00:00:07,470 +- Supercharge your PyTorch training loop + +3 +00:00:07,470 --> 00:00:08,943 +with Hugging Face Accelerate. + +4 +00:00:11,340 --> 00:00:12,600 +There are multiple setups + +5 +00:00:12,600 --> 00:00:14,580 +on which you can run your training: + +6 +00:00:14,580 --> 00:00:17,910 +it could be on CPU, GPUs, TPUs, + +7 +00:00:17,910 --> 00:00:20,610 +distributed on one machine +with several devices, + +8 +00:00:20,610 --> 00:00:23,220 +or even several machines, +often called nodes, + +9 +00:00:23,220 --> 00:00:25,173 +each with multiple devices. + +10 +00:00:26,340 --> 00:00:28,200 +On top of that, there are new tweaks + +11 +00:00:28,200 --> 00:00:30,810 +to make your training +faster or more efficient, + +12 +00:00:30,810 --> 00:00:32,763 +like mixed precision and DeepSpeed. + +13 +00:00:33,840 --> 00:00:36,600 +Each of those setups or training tweaks + +14 +00:00:36,600 --> 00:00:38,760 +requires you to change the +code of your training loop + +15 +00:00:38,760 --> 00:00:41,733 +in one way or another +and to learn a new API. + +16 +00:00:43,260 --> 00:00:45,940 +All those setups are +handled by the Trainer API, + +17 +00:00:45,940 --> 00:00:49,590 +and there are several third-party +libraries that can help. + +18 +00:00:49,590 --> 00:00:50,760 +The problem with those + +19 +00:00:50,760 --> 00:00:53,100 +is that they can feel like a black box + +20 +00:00:53,100 --> 00:00:55,320 +and that it might not be +easy to implement the tweak + +21 +00:00:55,320 --> 00:00:56,820 +to the training loop you need. + +22 +00:00:57,840 --> 00:00:59,760 +Accelerate has been designed specifically + +23 +00:00:59,760 --> 00:01:02,790 +to let you retain full control +over your training loop + +24 +00:01:02,790 --> 00:01:04,833 +and be as non-intrusive as possible. + +25 +00:01:05,760 --> 00:01:08,760 +With just four lines of code +to add to your training loop, + +26 +00:01:08,760 --> 00:01:11,733 +here shown on the example +of the training loop video, + +27 +00:01:12,630 --> 00:01:14,730 +Accelerate will handle all the setups + +28 +00:01:14,730 --> 00:01:17,180 +and training tweaks +mentioned on the first slide. + +29 +00:01:18,630 --> 00:01:20,400 +It's only one API to learn and master + +30 +00:01:20,400 --> 00:01:21,933 +instead of 10 different ones. + +31 +00:01:23,340 --> 00:01:25,980 +More specifically, you have +to import and instantiate + +32 +00:01:25,980 --> 00:01:27,360 +an accelerator object, + +33 +00:01:27,360 --> 00:01:29,100 +that will handle all the necessary code + +34 +00:01:29,100 --> 00:01:30,300 +for your specific setup. + +35 +00:01:31,380 --> 00:01:33,780 +Then you have to send it the model, + +36 +00:01:33,780 --> 00:01:36,000 +optimizer and dataloaders you are using + +37 +00:01:36,000 --> 00:01:39,633 +in the prepare method, which +is the main method to remember. + +38 +00:01:40,860 --> 00:01:42,870 +Accelerate handles device placement, + +39 +00:01:42,870 --> 00:01:44,370 +so you don't need to put your batch + +40 +00:01:44,370 --> 00:01:46,980 +on the specific device you are using. + +41 +00:01:46,980 --> 00:01:50,640 +Finally, you have to replace +the loss.backward line + +42 +00:01:50,640 --> 00:01:54,300 +by accelerator.backwardloss, + +43 +00:01:54,300 --> 00:01:55,500 +and that's all you need! + +44 +00:01:58,410 --> 00:02:01,710 +Accelerate also handles +distributed evaluation. + +45 +00:02:01,710 --> 00:02:04,020 +You can still use a +classic evaluation loop + +46 +00:02:04,020 --> 00:02:06,750 +such as the one we saw in +the training loop video, + +47 +00:02:06,750 --> 00:02:08,280 +in which case all processes + +48 +00:02:08,280 --> 00:02:10,083 +will perform the full evaluation. + +49 +00:02:11,340 --> 00:02:13,530 +To use a distributed evaluation, + +50 +00:02:13,530 --> 00:02:16,380 +you just have to adapt your +evaluation loop like this: + +51 +00:02:16,380 --> 00:02:17,657 +pass along the evaluation dataloader + +52 +00:02:17,657 --> 00:02:21,093 +to the accelerator.prepare +method, like for training. + +53 +00:02:22,170 --> 00:02:23,430 +Then you can dismiss the line + +54 +00:02:23,430 --> 00:02:26,160 +that places the batch +on the proper device, + +55 +00:02:26,160 --> 00:02:27,870 +and just before passing your predictions + +56 +00:02:27,870 --> 00:02:31,110 +and labels to your metric, +use accelerator.gather + +57 +00:02:31,110 --> 00:02:33,300 +to gather together the predictions + +58 +00:02:33,300 --> 00:02:34,803 +and labels from each process. + +59 +00:02:36,420 --> 00:02:37,890 +A distributed training script + +60 +00:02:37,890 --> 00:02:41,040 +has to be launched several +times on different processes, + +61 +00:02:41,040 --> 00:02:43,203 +for instance, one per GPU you are using. + +62 +00:02:44,070 --> 00:02:46,350 +You can use the PyTorch tools to do that + +63 +00:02:46,350 --> 00:02:48,210 +if you are familiar with them, + +64 +00:02:48,210 --> 00:02:50,520 +but Accelerate also provides an easy API + +65 +00:02:50,520 --> 00:02:53,523 +to configure your setup and +launch your training script. + +66 +00:02:54,540 --> 00:02:57,270 +In a terminal, run accelerate config + +67 +00:02:57,270 --> 00:02:58,650 +and answer the small questionnaire + +68 +00:02:58,650 --> 00:03:00,330 +to generate a configuration file + +69 +00:03:00,330 --> 00:03:02,073 +with all the relevant information, + +70 +00:03:03,240 --> 00:03:05,790 +then you can just run accelerate launch, + +71 +00:03:05,790 --> 00:03:08,580 +followed by the path to +your training script. + +72 +00:03:08,580 --> 00:03:12,000 +In a notebook, you can use +the notebook launcher function + +73 +00:03:12,000 --> 00:03:13,233 +to launch your training. + +74 +00:03:15,186 --> 00:03:17,853 +(air whooshing) + diff --git a/subtitles/en/31_navigating-the-model-hub.srt b/subtitles/en/31_navigating-the-model-hub.srt new file mode 100644 index 000000000..8facde57a --- /dev/null +++ b/subtitles/en/31_navigating-the-model-hub.srt @@ -0,0 +1,343 @@ +1 +00:00:00,468 --> 00:00:03,051 +(upbeat music) + +2 +00:00:04,050 --> 00:00:05,910 +- [Instructor] In this +video, we're going to go over + +3 +00:00:05,910 --> 00:00:08,013 +the HuggingFace Model Hub navigation. + +4 +00:00:10,140 --> 00:00:13,260 +This is the huggingface.co landing page. + +5 +00:00:13,260 --> 00:00:16,020 +To access the model hub, +click on the models tab + +6 +00:00:16,020 --> 00:00:17,463 +in the upper right corner. + +7 +00:00:18,960 --> 00:00:21,030 +You should be facing this web interface, + +8 +00:00:21,030 --> 00:00:23,193 +which can be split into several parts. + +9 +00:00:24,480 --> 00:00:26,790 +On the left, you'll find categories, + +10 +00:00:26,790 --> 00:00:29,090 +which you can use to +tailor your model search. + +11 +00:00:29,970 --> 00:00:32,970 +The first category is the tasks. + +12 +00:00:32,970 --> 00:00:36,660 +Models on the hub may be used +for a wide variety of tasks. + +13 +00:00:36,660 --> 00:00:39,030 +These include natural +language processing tasks, + +14 +00:00:39,030 --> 00:00:41,670 +such as question answering +or text classification, + +15 +00:00:41,670 --> 00:00:43,773 +but it isn't only limited to NLP. + +16 +00:00:44,850 --> 00:00:47,790 +Other tasks from other +fields are also available, + +17 +00:00:47,790 --> 00:00:50,340 +such as image classification +for computer vision, + +18 +00:00:50,340 --> 00:00:52,683 +or automatic speech +recognition for speech. + +19 +00:00:54,840 --> 00:00:57,870 +The second category is the libraries. + +20 +00:00:57,870 --> 00:01:00,990 +Models on the hub usually +share one of three backbones, + +21 +00:01:00,990 --> 00:01:03,900 +PyTorch, TensorFlow, or JAX. + +22 +00:01:03,900 --> 00:01:07,503 +However, other backbones, such +as rust or ONNX also exist. + +23 +00:01:09,540 --> 00:01:11,850 +Finally, this tab can also be used + +24 +00:01:11,850 --> 00:01:15,123 +to specify from which high-level +framework the models comes. + +25 +00:01:16,140 --> 00:01:19,260 +This includes Transformers, +but it isn't limited to it. + +26 +00:01:19,260 --> 00:01:21,060 +The model hub is used to host + +27 +00:01:21,060 --> 00:01:22,920 +a lot of different frameworks models, + +28 +00:01:22,920 --> 00:01:24,600 +and we're actively looking to host + +29 +00:01:24,600 --> 00:01:25,893 +other frameworks models. + +30 +00:01:28,530 --> 00:01:31,890 +The third category is the datasets tab. + +31 +00:01:31,890 --> 00:01:35,070 +Selecting a dataset from this +tab means filtering the models + +32 +00:01:35,070 --> 00:01:37,683 +so that they were trained +on that specific dataset. + +33 +00:01:39,180 --> 00:01:42,300 +The fourth category is the languages tab. + +34 +00:01:42,300 --> 00:01:43,800 +Selecting a language from this tab + +35 +00:01:43,800 --> 00:01:45,990 +means filtering the +models so that they handle + +36 +00:01:45,990 --> 00:01:47,090 +the language selected. + +37 +00:01:48,600 --> 00:01:51,750 +Finally, the last category +allows to choose the license + +38 +00:01:51,750 --> 00:01:53,313 +with which the model is shared. + +39 +00:01:56,700 --> 00:01:58,770 +On the right, you'll +find the models available + +40 +00:01:58,770 --> 00:02:00,480 +on the model hub. + +41 +00:02:00,480 --> 00:02:03,750 +The models are ordered +by downloads by default. + +42 +00:02:03,750 --> 00:02:04,890 +When clicking on a model, + +43 +00:02:04,890 --> 00:02:07,230 +you should be facing its model card. + +44 +00:02:07,230 --> 00:02:09,990 +The model card contains +information about the model, + +45 +00:02:09,990 --> 00:02:13,263 +its description, intended +use, limitations and biases. + +46 +00:02:14,310 --> 00:02:17,580 +It can also show code snippets +on how to use the model, + +47 +00:02:17,580 --> 00:02:20,070 +as well as any relevant information; + +48 +00:02:20,070 --> 00:02:22,080 +training procedure, data processing, + +49 +00:02:22,080 --> 00:02:24,213 +evaluation results or copyrights. + +50 +00:02:25,710 --> 00:02:28,350 +This information is crucial +for the model to be used. + +51 +00:02:28,350 --> 00:02:30,360 +The better crafted a model card is, + +52 +00:02:30,360 --> 00:02:33,270 +the easier it will be for other +users to leverage your model + +53 +00:02:33,270 --> 00:02:34,443 +in their applications. + +54 +00:02:35,820 --> 00:02:38,553 +On the right of the model +card is the inference API. + +55 +00:02:39,540 --> 00:02:41,040 +This inference API can be used + +56 +00:02:41,040 --> 00:02:43,290 +to play with the model directly. + +57 +00:02:43,290 --> 00:02:45,690 +Feel free to modify the +text and click on compute + +58 +00:02:45,690 --> 00:02:48,140 +to see how would the model +behave to your inputs. + +59 +00:02:50,370 --> 00:02:53,013 +At the top of your screen +lies the model tags. + +60 +00:02:53,850 --> 00:02:56,550 +These include the model task, +as well as any other tag + +61 +00:02:56,550 --> 00:02:59,200 +that is relevant to the +categories we have just seen. + +62 +00:03:01,320 --> 00:03:04,410 +The files & versions tab +displays the architecture + +63 +00:03:04,410 --> 00:03:06,213 +of the repository of that model. + +64 +00:03:07,230 --> 00:03:10,920 +Here, we can see all the +files that define this model. + +65 +00:03:10,920 --> 00:03:13,650 +You'll see all usual +features of a Git repository: + +66 +00:03:13,650 --> 00:03:15,093 +the branches available, + +67 +00:03:17,160 --> 00:03:18,520 +the commit history + +68 +00:03:20,760 --> 00:03:22,683 +as well as the commit diff. + +69 +00:03:25,740 --> 00:03:27,510 +Three different buttons are available + +70 +00:03:27,510 --> 00:03:29,760 +at the top of the model card. + +71 +00:03:29,760 --> 00:03:31,170 +The first one shows how to use + +72 +00:03:31,170 --> 00:03:33,093 +the inference API programmatically. + +73 +00:03:35,910 --> 00:03:38,913 +The second one shows how to +train this model in SageMaker. + +74 +00:03:42,870 --> 00:03:44,820 +And the last one shows +how to load that model + +75 +00:03:44,820 --> 00:03:46,860 +within the appropriate library. + +76 +00:03:46,860 --> 00:03:48,783 +For BERT, this is transformers. + +77 +00:03:50,208 --> 00:03:52,791 +(upbeat music) + diff --git a/subtitles/en/32_managing-a-repo-on-the-model-hub.srt b/subtitles/en/32_managing-a-repo-on-the-model-hub.srt new file mode 100644 index 000000000..d75814af0 --- /dev/null +++ b/subtitles/en/32_managing-a-repo-on-the-model-hub.srt @@ -0,0 +1,750 @@ +1 +00:00:04,200 --> 00:00:06,210 +- [Instructor] In this video, +we're going to understand how + +2 +00:00:06,210 --> 00:00:08,280 +to manage a model repository + +3 +00:00:08,280 --> 00:00:10,053 +on the Hugging Face Hub Model Hub. + +4 +00:00:10,920 --> 00:00:13,020 +In order to handle a repository + +5 +00:00:13,020 --> 00:00:15,450 +you should first have +a Hugging Face account. + +6 +00:00:15,450 --> 00:00:17,610 +A link to create a new +account is available + +7 +00:00:17,610 --> 00:00:18,573 +in the description. + +8 +00:00:20,130 --> 00:00:22,980 +Once you are logged in, you +can create a new repository + +9 +00:00:22,980 --> 00:00:25,890 +by clicking on the new model option. + +10 +00:00:25,890 --> 00:00:29,400 +You should be facing a similar +modal to the following. + +11 +00:00:29,400 --> 00:00:33,240 +In the owner input, you can +put either your own namespace + +12 +00:00:33,240 --> 00:00:35,703 +or any of your organization's namespaces. + +13 +00:00:36,660 --> 00:00:39,330 +The model name is the model identifier + +14 +00:00:39,330 --> 00:00:40,320 +that will then be used + +15 +00:00:40,320 --> 00:00:43,143 +to identify your model +on the chosen namespace. + +16 +00:00:44,130 --> 00:00:47,700 +The final choice is +between public and private. + +17 +00:00:47,700 --> 00:00:49,950 +Public models are accessible by anyone. + +18 +00:00:49,950 --> 00:00:51,840 +This is the recommended free option, + +19 +00:00:51,840 --> 00:00:54,960 +as this makes your model easily +accessible and shareable. + +20 +00:00:54,960 --> 00:00:57,630 +The owners of your +namespace are the only ones + +21 +00:00:57,630 --> 00:00:59,523 +who can update and change your model. + +22 +00:01:00,450 --> 00:01:03,660 +A more advanced option +is the private option. + +23 +00:01:03,660 --> 00:01:04,560 +In this case, + +24 +00:01:04,560 --> 00:01:06,000 +only the owners of your namespace + +25 +00:01:06,000 --> 00:01:08,280 +will have visibility over your model. + +26 +00:01:08,280 --> 00:01:10,260 +Other users won't know it exists + +27 +00:01:10,260 --> 00:01:11,810 +and will not be able to use it. + +28 +00:01:15,030 --> 00:01:17,030 +Let's create a dummy model to play with. + +29 +00:01:18,180 --> 00:01:19,710 +Once your model is created, + +30 +00:01:19,710 --> 00:01:22,230 +comes the management of that model. + +31 +00:01:22,230 --> 00:01:24,360 +Three tabs are available to you. + +32 +00:01:24,360 --> 00:01:27,960 +You're facing the first one, +which is the model card page. + +33 +00:01:27,960 --> 00:01:29,970 +This is the page you use +to showcase your model + +34 +00:01:29,970 --> 00:01:31,110 +to the world. + +35 +00:01:31,110 --> 00:01:33,260 +We'll see how it can +be completed in a bit. + +36 +00:01:34,500 --> 00:01:37,503 +The second one is the +files and versions tab. + +37 +00:01:38,340 --> 00:01:40,920 +Your model itself is a Git repository. + +38 +00:01:40,920 --> 00:01:43,230 +If you're unaware of +what is a Git repository, + +39 +00:01:43,230 --> 00:01:46,320 +you can think of it as a +folder containing files. + +40 +00:01:46,320 --> 00:01:48,120 +If you have never used Git before, + +41 +00:01:48,120 --> 00:01:50,100 +we recommend looking at an introduction + +42 +00:01:50,100 --> 00:01:52,600 +like the one provided in +this video's description. + +43 +00:01:53,850 --> 00:01:56,910 +The Git repository allows you +to see the changes happening + +44 +00:01:56,910 --> 00:02:00,900 +over time in this folder, +hence the term versions. + +45 +00:02:00,900 --> 00:02:03,453 +We'll see how to add files +and versions in a bit. + +46 +00:02:07,020 --> 00:02:09,570 +The final tab is the settings tab, + +47 +00:02:09,570 --> 00:02:12,120 +which allows you to manage +your model's visibility + +48 +00:02:12,120 --> 00:02:13,203 +and availability. + +49 +00:02:14,790 --> 00:02:17,673 +Let's first start by adding +files to the repository. + +50 +00:02:18,540 --> 00:02:19,560 +Files can be added + +51 +00:02:19,560 --> 00:02:23,340 +through the web interface +thanks to the add file button. + +52 +00:02:23,340 --> 00:02:27,060 +The added files can be of +any type, python, JSON, text, + +53 +00:02:27,060 --> 00:02:27,893 +you name it. + +54 +00:02:28,740 --> 00:02:31,170 +Alongside your added file and its content, + +55 +00:02:31,170 --> 00:02:33,363 +you should name your change or commit. + +56 +00:02:36,330 --> 00:02:38,400 +Generally, adding files is simpler + +57 +00:02:38,400 --> 00:02:40,770 +by using the Hugging +Face Hub Python library + +58 +00:02:40,770 --> 00:02:43,050 +or by using the command-line. + +59 +00:02:43,050 --> 00:02:44,310 +We'll showcase how to do this + +60 +00:02:44,310 --> 00:02:46,290 +using the Hugging Face Hub Python library, + +61 +00:02:46,290 --> 00:02:48,060 +and there is a link in the description + +62 +00:02:48,060 --> 00:02:49,800 +to the previous version of this video, + +63 +00:02:49,800 --> 00:02:52,743 +showcasing how to do this +using Git and the command-line. + +64 +00:02:53,610 --> 00:02:54,840 +First, make sure you're logged + +65 +00:02:54,840 --> 00:02:56,460 +into your Hugging Face account, + +66 +00:02:56,460 --> 00:02:59,523 +either through the command-line +or in a Python runtime. + +67 +00:03:04,634 --> 00:03:06,390 +The first approach we'll take a look at + +68 +00:03:06,390 --> 00:03:08,880 +is using the upload file method. + +69 +00:03:08,880 --> 00:03:10,770 +This offers an extremely simple API + +70 +00:03:10,770 --> 00:03:12,630 +to upload files through the hub. + +71 +00:03:12,630 --> 00:03:14,190 +The three required parameters + +72 +00:03:14,190 --> 00:03:16,083 +are the current location of the file, + +73 +00:03:18,570 --> 00:03:21,300 +the path of that file in the repository, + +74 +00:03:21,300 --> 00:03:24,050 +and the idea of the repository +to which you're pushing. + +75 +00:03:25,650 --> 00:03:27,930 +There are a few additional parameters. + +76 +00:03:27,930 --> 00:03:29,100 +The token parameter, + +77 +00:03:29,100 --> 00:03:31,200 +if you would like to +specify a different token + +78 +00:03:31,200 --> 00:03:33,650 +than the one saved in your +cache with your login, + +79 +00:03:34,830 --> 00:03:36,750 +the repo type parameter, + +80 +00:03:36,750 --> 00:03:40,503 +if you would like to push +to a data set or a space. + +81 +00:03:42,300 --> 00:03:45,690 +We'll upload a file called +readme.md to the repository + +82 +00:03:45,690 --> 00:03:47,190 +using this method. + +83 +00:03:47,190 --> 00:03:49,710 +We first start by saving +a file with that name, + +84 +00:03:49,710 --> 00:03:51,210 +which contains some information + +85 +00:03:51,210 --> 00:03:52,920 +about the repository itself. + +86 +00:03:52,920 --> 00:03:54,243 +Here, a title. + +87 +00:03:55,950 --> 00:03:57,420 +Now that the file is saved, + +88 +00:03:57,420 --> 00:04:00,513 +let's use the upload file +method to upload it to the hub. + +89 +00:04:01,560 --> 00:04:03,540 +If we switch to the web +interface for a second + +90 +00:04:03,540 --> 00:04:07,080 +and refresh the page, we'll +see that the README is shown. + +91 +00:04:07,080 --> 00:04:08,883 +The file upload was a success. + +92 +00:04:10,170 --> 00:04:13,500 +Alongside this method +exists a delete file method + +93 +00:04:13,500 --> 00:04:16,170 +so that you may manage +your repository fully. + +94 +00:04:16,170 --> 00:04:18,820 +We'll use it to delete the +file we have just created. + +95 +00:04:22,860 --> 00:04:25,320 +If we refresh the page once again, good, + +96 +00:04:25,320 --> 00:04:26,973 +the file was indeed deleted. + +97 +00:04:29,070 --> 00:04:32,730 +This approach using only these +two methods is super simple. + +98 +00:04:32,730 --> 00:04:35,400 +It doesn't need Git or Git LFS installed, + +99 +00:04:35,400 --> 00:04:37,650 +but it does come with a limitation. + +100 +00:04:37,650 --> 00:04:39,600 +The maximum file size one can upload + +101 +00:04:39,600 --> 00:04:41,313 +is limited to five gigabytes. + +102 +00:04:42,360 --> 00:04:43,890 +To overcome this limit, + +103 +00:04:43,890 --> 00:04:45,540 +let's take a look at the second method + +104 +00:04:45,540 --> 00:04:47,643 +which is the repository utility. + +105 +00:04:48,600 --> 00:04:51,840 +This class is a wrapper over +Git and Git LFS methods, + +106 +00:04:51,840 --> 00:04:53,850 +which abstracts most of the complexity + +107 +00:04:53,850 --> 00:04:55,500 +and offers a flexible API + +108 +00:04:55,500 --> 00:04:57,990 +to manage your online repositories. + +109 +00:04:57,990 --> 00:04:59,690 +Let's take a look at how it works. + +110 +00:05:03,870 --> 00:05:08,369 +We first start by instantiating +the repository utility. + +111 +00:05:08,369 --> 00:05:10,380 +We provide the clone from parameter, + +112 +00:05:10,380 --> 00:05:13,383 +in order to clone the +repository we just created. + +113 +00:05:14,400 --> 00:05:18,750 +The repository is now +cloned in the local folder. + +114 +00:05:18,750 --> 00:05:22,200 +The repo object that we +have just initialized + +115 +00:05:22,200 --> 00:05:24,873 +offers quite a few methods +which are useful for us. + +116 +00:05:25,920 --> 00:05:28,800 +We're interested in +pushing a model to the hub. + +117 +00:05:28,800 --> 00:05:31,170 +I'll start by loading +a model and tokenizer + +118 +00:05:31,170 --> 00:05:32,643 +I trained a few hours ago. + +119 +00:05:34,380 --> 00:05:36,810 +We'll now follow the +traditional Git approach + +120 +00:05:36,810 --> 00:05:38,670 +by first pulling latest changes + +121 +00:05:38,670 --> 00:05:40,053 +using the Git pull method. + +122 +00:05:40,980 --> 00:05:43,170 +We just cloned the repository, + +123 +00:05:43,170 --> 00:05:45,780 +so unless this is a +super active repository, + +124 +00:05:45,780 --> 00:05:48,660 +it's unlikely that new +changes are available. + +125 +00:05:48,660 --> 00:05:51,000 +But it's always a good idea +to pull the latest changes + +126 +00:05:51,000 --> 00:05:52,300 +before doing anything new. + +127 +00:05:53,220 --> 00:05:55,200 +Now that we have pulled the repository, + +128 +00:05:55,200 --> 00:05:58,500 +I'll save the model and +tokenizer inside that folder. + +129 +00:05:58,500 --> 00:06:01,200 +This includes the model +weights, configuration file, + +130 +00:06:01,200 --> 00:06:02,673 +and tokenizer files. + +131 +00:06:04,440 --> 00:06:05,820 +Now that the model is saved, + +132 +00:06:05,820 --> 00:06:07,890 +we'll continue with the +traditional Git approach + +133 +00:06:07,890 --> 00:06:10,620 +and push it to the remote repository. + +134 +00:06:10,620 --> 00:06:12,150 +If we were using the command-line, + +135 +00:06:12,150 --> 00:06:14,250 +there are a few Git LFS specific commands + +136 +00:06:14,250 --> 00:06:15,600 +we would have to invoke. + +137 +00:06:15,600 --> 00:06:17,940 +But here, the Hugging Face hub package + +138 +00:06:17,940 --> 00:06:20,070 +takes care of all of that. + +139 +00:06:20,070 --> 00:06:24,420 +We'll start by staging the +files using the Git add method. + +140 +00:06:24,420 --> 00:06:27,600 +We'll then commit these changes +using Git commit method, + +141 +00:06:27,600 --> 00:06:30,690 +and providing a helpful commit message. + +142 +00:06:30,690 --> 00:06:33,210 +Finally, we'll push the +changes to the remote, + +143 +00:06:33,210 --> 00:06:34,953 +using the Git push method. + +144 +00:06:45,090 --> 00:06:47,430 +If we go back to the +files and versions tab, + +145 +00:06:47,430 --> 00:06:49,950 +we can now see the newly committed files. + +146 +00:06:49,950 --> 00:06:52,600 +We can even play with the +model in the inference API. + +147 +00:06:53,790 --> 00:06:55,770 +Unfortunately, the front page of our model + +148 +00:06:55,770 --> 00:06:57,540 +is still very empty. + +149 +00:06:57,540 --> 00:06:59,280 +Let's add a README markdown file + +150 +00:06:59,280 --> 00:07:00,753 +to complete it a little bit. + +151 +00:07:01,710 --> 00:07:04,200 +This README is known as the model card + +152 +00:07:04,200 --> 00:07:06,030 +and it's arguably as important + +153 +00:07:06,030 --> 00:07:09,330 +as the model and tokenizer +files in the model repository. + +154 +00:07:09,330 --> 00:07:11,280 +It is the central definition + +155 +00:07:11,280 --> 00:07:13,200 +and documentation of your model, + +156 +00:07:13,200 --> 00:07:16,440 +ensuring reusability by +fellow community members + +157 +00:07:16,440 --> 00:07:18,480 +and reproducibility of results. + +158 +00:07:18,480 --> 00:07:20,760 +Providing a platform +on which other members + +159 +00:07:20,760 --> 00:07:22,293 +may build their artifacts. + +160 +00:07:23,220 --> 00:07:25,590 +We'll only add a title and +a small description here + +161 +00:07:25,590 --> 00:07:27,060 +for simplicity's sake, + +162 +00:07:27,060 --> 00:07:29,370 +but we encourage you to +add information relevant + +163 +00:07:29,370 --> 00:07:30,990 +to how was the model trained, + +164 +00:07:30,990 --> 00:07:33,120 +it's intended use and limitations, + +165 +00:07:33,120 --> 00:07:36,180 +as well as it's identified +potential biases, + +166 +00:07:36,180 --> 00:07:37,440 +evaluation results, + +167 +00:07:37,440 --> 00:07:39,843 +and code samples on how to use your model. + +168 +00:07:41,460 --> 00:07:44,130 +Great work contributing +a model to the Model Hub. + +169 +00:07:44,130 --> 00:07:46,440 +This model can now be used +in downstream libraries + +170 +00:07:46,440 --> 00:07:48,783 +simply by specifying +your model identifier. + diff --git a/subtitles/en/33_the-push-to-hub-api-(pytorch).srt b/subtitles/en/33_the-push-to-hub-api-(pytorch).srt new file mode 100644 index 000000000..a2fcf8caf --- /dev/null +++ b/subtitles/en/33_the-push-to-hub-api-(pytorch).srt @@ -0,0 +1,479 @@ +1 +00:00:00,321 --> 00:00:01,497 +(air whooshing) + +2 +00:00:01,497 --> 00:00:02,330 +(smiley face popping) + +3 +00:00:02,330 --> 00:00:05,130 +(air whooshing) + +4 +00:00:05,130 --> 00:00:06,830 +- [Instructor] So push to hub API. + +5 +00:00:08,310 --> 00:00:10,533 +Let's have a look at the push to hub API. + +6 +00:00:11,730 --> 00:00:14,640 +You will need to be logged in +with your Hugging Face account + +7 +00:00:14,640 --> 00:00:17,400 +which you can do by +executing this first cell, + +8 +00:00:17,400 --> 00:00:21,123 +or by typing huggingface-cli +login in a terminal. + +9 +00:00:21,990 --> 00:00:26,640 +Just enter you username and +password, then click login, + +10 +00:00:26,640 --> 00:00:28,620 +this will store a notification token + +11 +00:00:28,620 --> 00:00:30,670 +in the cache of the machine you're using. + +12 +00:00:31,890 --> 00:00:35,790 +Now, let's launch a fine +tuning of a BERT model + +13 +00:00:35,790 --> 00:00:37,920 +on the GLUE COLA dataset. + +14 +00:00:37,920 --> 00:00:39,600 +We won't go over the fine tuning code + +15 +00:00:39,600 --> 00:00:42,270 +because you can find it in +any transformer tutorial, + +16 +00:00:42,270 --> 00:00:44,670 +or by looking at the videos link below. + +17 +00:00:44,670 --> 00:00:46,470 +What interests us here is + +18 +00:00:46,470 --> 00:00:48,970 +how we can leverage the +model hub during training. + +19 +00:00:49,860 --> 00:00:52,980 +This is done with the +"push_to_hub=true" argument + +20 +00:00:52,980 --> 00:00:55,530 +passed in your TrainingArguments. + +21 +00:00:55,530 --> 00:00:57,240 +This will automatically upload your model + +22 +00:00:57,240 --> 00:00:59,400 +to the Hub each time it is saved, + +23 +00:00:59,400 --> 00:01:01,323 +so every epoch in our case. + +24 +00:01:02,280 --> 00:01:04,860 +This allows you to resume +training from a different machine + +25 +00:01:04,860 --> 00:01:06,873 +if the current one gets interrupted. + +26 +00:01:08,220 --> 00:01:10,440 +The model will be updated +in your name space + +27 +00:01:10,440 --> 00:01:14,640 +with the name of the output +directory you picked by default. + +28 +00:01:14,640 --> 00:01:16,020 +You can choose another name + +29 +00:01:16,020 --> 00:01:19,113 +by passing it to the +hub_model_id argument. + +30 +00:01:20,070 --> 00:01:23,370 +You can also push inside an +organization you are a member of + +31 +00:01:23,370 --> 00:01:25,740 +by passing a full repository name, + +32 +00:01:25,740 --> 00:01:28,933 +with the name of the organization/, + +33 +00:01:28,933 --> 00:01:30,433 +the model ID you want to pick. + +34 +00:01:32,250 --> 00:01:34,650 +With that done, we can +just launch training, + +35 +00:01:34,650 --> 00:01:36,093 +and wait a little bit. + +36 +00:01:36,960 --> 00:01:39,033 +I'll cut the waiting time from the video. + +37 +00:01:43,260 --> 00:01:46,350 +Note that the model is +pushed asynchronously, + +38 +00:01:46,350 --> 00:01:47,730 +meaning that the training continues + +39 +00:01:47,730 --> 00:01:49,730 +while your model is uploaded to the hub. + +40 +00:01:51,060 --> 00:01:52,950 +When your first commit is finished, + +41 +00:01:52,950 --> 00:01:55,650 +you can go inspect your model on the Hub + +42 +00:01:55,650 --> 00:01:57,960 +by looking inside your name space, + +43 +00:01:57,960 --> 00:01:59,943 +and you'll find it at the very top. + +44 +00:02:01,980 --> 00:02:04,200 +You can even start playing +with its inference widget + +45 +00:02:04,200 --> 00:02:06,630 +while it's continuing the training. + +46 +00:02:06,630 --> 00:02:09,270 +The Cola data set tasks +the model with determining + +47 +00:02:09,270 --> 00:02:11,970 +if the sentence is +grammatically correct on that. + +48 +00:02:11,970 --> 00:02:15,510 +So we pick an example of +incorrect sentence to test it. + +49 +00:02:15,510 --> 00:02:16,950 +Note that it'll take a bit of time + +50 +00:02:16,950 --> 00:02:18,750 +to load your model inside +the inference APIs, + +51 +00:02:18,750 --> 00:02:20,880 +so first time you try to use it. + +52 +00:02:20,880 --> 00:02:23,280 +We'll cut by time from the video. + +53 +00:02:23,280 --> 00:02:24,870 +There is something wrong with the labels, + +54 +00:02:24,870 --> 00:02:27,360 +but we'll fix it later in this video. + +55 +00:02:27,360 --> 00:02:29,520 +Once your training is finished, + +56 +00:02:29,520 --> 00:02:31,770 +you should do one last +push with the trainer + +57 +00:02:31,770 --> 00:02:33,840 +that pushed to a method. + +58 +00:02:33,840 --> 00:02:35,430 +This is for two reason. + +59 +00:02:35,430 --> 00:02:36,750 +First, this will make sure + +60 +00:02:36,750 --> 00:02:39,180 +you are predicting the +final version of your model + +61 +00:02:39,180 --> 00:02:40,680 +if you didn't already. + +62 +00:02:40,680 --> 00:02:42,480 +For instance, if you used to save + +63 +00:02:42,480 --> 00:02:46,980 +every in step strategy +instead of every second, + +64 +00:02:46,980 --> 00:02:48,180 +this will draft a model card + +65 +00:02:48,180 --> 00:02:51,120 +that will be the landing +page of your model repo. + +66 +00:02:51,120 --> 00:02:52,260 +Once the commit is done, + +67 +00:02:52,260 --> 00:02:54,810 +let's go back on our +model page and refresh. + +68 +00:02:54,810 --> 00:02:56,820 +We can see the drafters model card + +69 +00:02:56,820 --> 00:02:58,080 +which includes information, + +70 +00:02:58,080 --> 00:03:00,381 +and which one model we find tuned. + +71 +00:03:00,381 --> 00:03:03,570 +So final evaluation loss and metric, + +72 +00:03:03,570 --> 00:03:06,300 +the training hyperparameter used, + +73 +00:03:06,300 --> 00:03:08,670 +the intermediate training results, + +74 +00:03:08,670 --> 00:03:10,320 +and the framework versions we used + +75 +00:03:10,320 --> 00:03:13,173 +so that other people can +easily reproduce our results. + +76 +00:03:15,270 --> 00:03:16,860 +On top of all that information, + +77 +00:03:16,860 --> 00:03:19,740 +the trainer also included some +metadata that is interpreted + +78 +00:03:19,740 --> 00:03:22,650 +by the Hugging Face +website in the model cloud. + +79 +00:03:22,650 --> 00:03:26,010 +You get the value of the metrics +reported in a nice widget + +80 +00:03:26,010 --> 00:03:29,640 +as well as a link to a +leaderboard with paper with code. + +81 +00:03:29,640 --> 00:03:32,550 +So the Tensorboard runs +have also been pushed + +82 +00:03:32,550 --> 00:03:34,560 +to this report, and we can look at them + +83 +00:03:34,560 --> 00:03:36,000 +directly from the model hub + +84 +00:03:36,000 --> 00:03:38,850 +by clicking on the +training metrics sub menu. + +85 +00:03:38,850 --> 00:03:39,795 +If you are not using the Trainer API + +86 +00:03:39,795 --> 00:03:42,510 +to fine-tune your model, + +87 +00:03:42,510 --> 00:03:43,770 +you can use a push_to_hub method + +88 +00:03:43,770 --> 00:03:46,427 +on the model, and tokenizer directly. + +89 +00:03:46,427 --> 00:03:50,160 +Let's test this to fix all +labels in the inference widget. + +90 +00:03:50,160 --> 00:03:52,740 +The inference widget was using +different names for labels + +91 +00:03:52,740 --> 00:03:54,810 +because we did not +indicate the correspondence + +92 +00:03:54,810 --> 00:03:57,030 +between integer and label names. + +93 +00:03:57,030 --> 00:03:58,740 +We can fix this in the configuration + +94 +00:03:58,740 --> 00:04:01,350 +by sitting the label2id, + +95 +00:04:01,350 --> 00:04:04,170 +and id2label fields +through the proper values + +96 +00:04:04,170 --> 00:04:06,933 +when pushing the model config to the hub. + +97 +00:04:07,950 --> 00:04:10,620 +Once this is done, we +can check on the website, + +98 +00:04:10,620 --> 00:04:13,380 +and the model is now +showing the proper label. + +99 +00:04:13,380 --> 00:04:15,240 +Now that the model is on the hub, + +100 +00:04:15,240 --> 00:04:17,370 +we can use it from anywhere + +101 +00:04:17,370 --> 00:04:19,920 +as we would any other Transformer model + +102 +00:04:19,920 --> 00:04:21,113 +with the from_pretrained method + +103 +00:04:21,113 --> 00:04:22,923 +of with the pipeline function. + +104 +00:04:34,350 --> 00:04:36,780 +We just have to use the +identifier from the hub, + +105 +00:04:36,780 --> 00:04:39,450 +and we can see that the model +configuration and weights + +106 +00:04:39,450 --> 00:04:42,483 +as well as the tokenized files +are automatically downloaded. + +107 +00:04:53,880 --> 00:04:55,950 +Try the push_to_hub API +in the next training + +108 +00:04:55,950 --> 00:04:58,650 +to easily share your model +with the rest of the world. + +109 +00:05:01,151 --> 00:05:03,818 +(air whooshing) + diff --git a/subtitles/en/34_the-push-to-hub-api-(tensorflow).srt b/subtitles/en/34_the-push-to-hub-api-(tensorflow).srt new file mode 100644 index 000000000..d0f558ccc --- /dev/null +++ b/subtitles/en/34_the-push-to-hub-api-(tensorflow).srt @@ -0,0 +1,877 @@ +1 +00:00:00,587 --> 00:00:02,670 +(swoosh) + +2 +00:00:05,100 --> 00:00:07,080 +- [Narrator] Hi, this +is going to be a video + +3 +00:00:07,080 --> 00:00:09,420 +about the push_to_hub API + +4 +00:00:09,420 --> 00:00:10,670 +for Tensorflow and Keras. + +5 +00:00:11,820 --> 00:00:14,850 +So, to get started, we'll +open up our notebook. + +6 +00:00:14,850 --> 00:00:16,920 +And the first thing you'll +need to do is log in to + +7 +00:00:16,920 --> 00:00:18,170 +your HuggingFace account, + +8 +00:00:19,043 --> 00:00:20,663 +for example with the +notebook login function. + +9 +00:00:21,570 --> 00:00:24,630 +So to use that, you +simply call the function, + +10 +00:00:24,630 --> 00:00:26,010 +the popup will emerge. + +11 +00:00:26,010 --> 00:00:28,800 +You will enter your username and password, + +12 +00:00:28,800 --> 00:00:31,425 +which I'm going to pull out +of my password manager here, + +13 +00:00:31,425 --> 00:00:33,108 +and you log in. + +14 +00:00:33,108 --> 00:00:35,670 +The next two cells are just + +15 +00:00:35,670 --> 00:00:37,080 +getting everything ready for training. + +16 +00:00:37,080 --> 00:00:38,940 +So we're just going to load a dataset, + +17 +00:00:38,940 --> 00:00:41,100 +we're going to tokenize that dataset, + +18 +00:00:41,100 --> 00:00:42,990 +and then we're going to +load our model and compile + +19 +00:00:42,990 --> 00:00:45,660 +it with the standard Adam optimizer. + +20 +00:00:45,660 --> 00:00:47,560 +So I'm just going to run all of those. + +21 +00:00:49,830 --> 00:00:52,080 +We'll wait a few seconds, + +22 +00:00:52,080 --> 00:00:54,280 +and everything should +be ready for training. + +23 +00:00:57,983 --> 00:00:58,816 +Okay. + +24 +00:00:58,816 --> 00:01:01,440 +So now we're ready to train. + +25 +00:01:01,440 --> 00:01:03,030 +I'm going to show you the two ways + +26 +00:01:03,030 --> 00:01:05,130 +you can push your model to the Hub. + +27 +00:01:05,130 --> 00:01:08,190 +So the first is with +the PushToHubCallback. + +28 +00:01:08,190 --> 00:01:10,107 +So a callback in Keras + +29 +00:01:10,107 --> 00:01:13,710 +is a function that's called +regularly during training. + +30 +00:01:13,710 --> 00:01:17,400 +You can set it to be called +after a certain number of steps, + +31 +00:01:17,400 --> 00:01:21,427 +or every epoch, or even just +once at the end of training. + +32 +00:01:21,427 --> 00:01:25,080 +So a lot of callbacks +in Keras, for example, + +33 +00:01:25,080 --> 00:01:28,050 +control learning rate decaying on plateau, + +34 +00:01:28,050 --> 00:01:30,047 +and things like that. + +35 +00:01:30,047 --> 00:01:32,520 +So this callback, by default, + +36 +00:01:32,520 --> 00:01:35,760 +will save your model to +the Hub once every epoch. + +37 +00:01:35,760 --> 00:01:37,080 +And that's really helpful, + +38 +00:01:37,080 --> 00:01:38,790 +especially if your training is very long, + +39 +00:01:38,790 --> 00:01:40,800 +because that means you +can resume from that save, + +40 +00:01:40,800 --> 00:01:43,290 +so you get this automatic +cloud-saving of your model. + +41 +00:01:43,290 --> 00:01:45,027 +And you can even run inference + +42 +00:01:45,027 --> 00:01:47,730 +with the checkpoints of your model + +43 +00:01:47,730 --> 00:01:50,208 +that have been uploaded by this callback. + +44 +00:01:50,208 --> 00:01:52,260 +And that means you can, + +45 +00:01:52,260 --> 00:01:54,150 +y'know, run some test inputs + +46 +00:01:54,150 --> 00:01:56,100 +and actually see how your model works + +47 +00:01:56,100 --> 00:01:57,990 +at various stages during training, + +48 +00:01:57,990 --> 00:01:59,540 +which is a really nice feature. + +49 +00:02:00,390 --> 00:02:03,960 +So we're going to add +the PushToHubCallback, + +50 +00:02:03,960 --> 00:02:05,670 +and it takes just a few arguments. + +51 +00:02:05,670 --> 00:02:08,250 +So the first argument is +the temporary directory + +52 +00:02:08,250 --> 00:02:10,260 +that files are going to be saved to + +53 +00:02:10,260 --> 00:02:12,150 +before they're uploaded to the Hub. + +54 +00:02:12,150 --> 00:02:14,127 +The second argument is the tokenizer, + +55 +00:02:14,127 --> 00:02:15,808 +and the third argument here + +56 +00:02:15,808 --> 00:02:19,080 +is the keyword argument hub_model_id. + +57 +00:02:19,080 --> 00:02:21,330 +So that's the name it's +going to be saved under + +58 +00:02:21,330 --> 00:02:23,006 +on the HuggingFace Hub. + +59 +00:02:23,006 --> 00:02:26,267 +You can also upload to +an organization account + +60 +00:02:26,267 --> 00:02:29,370 +just by adding the organization name + +61 +00:02:29,370 --> 00:02:32,460 +before the repository name +with a slash, like this. + +62 +00:02:32,460 --> 00:02:34,020 +So you probably don't have permissions + +63 +00:02:34,020 --> 00:02:36,000 +to upload to the HuggingFace organization, + +64 +00:02:36,000 --> 00:02:37,170 +if you do please file a bug + +65 +00:02:37,170 --> 00:02:38,973 +and let us know extremely urgently. + +66 +00:02:40,830 --> 00:02:42,960 +But if you do have access +to your own organization, + +67 +00:02:42,960 --> 00:02:44,730 +then you can use that same approach + +68 +00:02:44,730 --> 00:02:46,650 +to upload models to their account + +69 +00:02:46,650 --> 00:02:50,760 +instead of to your own +personal set of models. + +70 +00:02:50,760 --> 00:02:53,520 +So, once you've made your callback, + +71 +00:02:53,520 --> 00:02:56,310 +you simply add it to the callbacks list + +72 +00:02:56,310 --> 00:02:58,080 +when you're calling model.fit. + +73 +00:02:58,080 --> 00:03:01,110 +And everything is uploaded +for you from there, + +74 +00:03:01,110 --> 00:03:02,610 +there's nothing else to worry about. + +75 +00:03:02,610 --> 00:03:04,530 +The second way to upload a model, though, + +76 +00:03:04,530 --> 00:03:07,020 +is to call model.push_to_hub. + +77 +00:03:07,020 --> 00:03:09,086 +So this is more of a once-off method. + +78 +00:03:09,086 --> 00:03:11,550 +It's not called regularly during training. + +79 +00:03:11,550 --> 00:03:13,680 +You can just call this +manually whenever you want to + +80 +00:03:13,680 --> 00:03:15,240 +upload a model to the hub. + +81 +00:03:15,240 --> 00:03:18,949 +So we recommend running this +after the end of training, + +82 +00:03:18,949 --> 00:03:21,870 +just to make sure that +you have a commit message + +83 +00:03:21,870 --> 00:03:24,060 +to guarantee that this +was the final version + +84 +00:03:24,060 --> 00:03:26,143 +of the model at the end of training. + +85 +00:03:26,143 --> 00:03:27,930 +And it just makes sure that, you know, + +86 +00:03:27,930 --> 00:03:30,480 +you're working with the +definitive end-of-training model + +87 +00:03:30,480 --> 00:03:32,190 +and not accidentally using a checkpoint + +88 +00:03:32,190 --> 00:03:34,224 +from somewhere along the way. + +89 +00:03:34,224 --> 00:03:37,173 +So I'm going to run both of these cells. + +90 +00:03:39,299 --> 00:03:41,716 +And then I'm going to cut the video here, + +91 +00:03:41,716 --> 00:03:43,080 +just because training is going +to take a couple of minutes. + +92 +00:03:43,080 --> 00:03:44,580 +So I'll skip forward to the end of that, + +93 +00:03:44,580 --> 00:03:46,320 +when the models have all been uploaded, + +94 +00:03:46,320 --> 00:03:48,390 +and I'm gonna show you how you can + +95 +00:03:48,390 --> 00:03:50,010 +access the models in the Hub, + +96 +00:03:50,010 --> 00:03:52,713 +and the other things you +can do with them from there. + +97 +00:03:55,440 --> 00:03:56,700 +Okay, we're back, + +98 +00:03:56,700 --> 00:03:59,160 +and our model was uploaded. + +99 +00:03:59,160 --> 00:04:00,750 +Both by the PushToHubCallback + +100 +00:04:00,750 --> 00:04:04,251 +and also by our call to +model.push_to_hub after training. + +101 +00:04:04,251 --> 00:04:05,910 +So everything's looking good. + +102 +00:04:05,910 --> 00:04:09,960 +So now if we drop over to +my profile on HuggingFace, + +103 +00:04:09,960 --> 00:04:12,630 +and you can get there just by +clicking the profile button + +104 +00:04:12,630 --> 00:04:13,680 +in the dropdown. + +105 +00:04:13,680 --> 00:04:16,860 +We can see that the +bert-fine-tuned-cola model is here, + +106 +00:04:16,860 --> 00:04:18,369 +and was updated 3 minutes ago. + +107 +00:04:18,369 --> 00:04:20,520 +So it'll always be at +the top of your list, + +108 +00:04:20,520 --> 00:04:23,340 +because they're sorted by how +recently they were updated. + +109 +00:04:23,340 --> 00:04:25,740 +And we can start querying +our model immediately. + +110 +00:04:30,564 --> 00:04:32,939 +So the dataset we were training on + +111 +00:04:32,939 --> 00:04:34,320 +is the Glue CoLA dataset, + +112 +00:04:34,320 --> 00:04:36,210 +and CoLA is an acronym standing for + +113 +00:04:36,210 --> 00:04:39,420 +the Corpus of Linguistic Acceptability. + +114 +00:04:39,420 --> 00:04:42,480 +So what that means is the model +is being trained to decide + +115 +00:04:42,480 --> 00:04:46,350 +if a sentence is grammatically +or linguistically okay, + +116 +00:04:46,350 --> 00:04:48,171 +or if there's a problem with it. + +117 +00:04:48,171 --> 00:04:52,890 +For example, we could say, +"This is a legitimate sentence." + +118 +00:04:52,890 --> 00:04:54,180 +And hopefully it realizes that + +119 +00:04:54,180 --> 00:04:56,080 +this is in fact a legitimate sentence. + +120 +00:04:57,630 --> 00:05:00,240 +So it might take a couple of +seconds for the model to load + +121 +00:05:00,240 --> 00:05:03,060 +when you call it for the first time. + +122 +00:05:03,060 --> 00:05:05,960 +So I might cut a couple of +seconds out of this video here. + +123 +00:05:07,860 --> 00:05:09,060 +Okay, we're back. + +124 +00:05:09,060 --> 00:05:12,407 +So the model loaded and we got an output, + +125 +00:05:12,407 --> 00:05:14,340 +but there's an obvious problem here. + +126 +00:05:14,340 --> 00:05:16,888 +So these labels aren't really telling us + +127 +00:05:16,888 --> 00:05:19,740 +what categories the model +has actually assigned + +128 +00:05:19,740 --> 00:05:21,655 +to this input sentence. + +129 +00:05:21,655 --> 00:05:23,520 +So if we want to fix that, + +130 +00:05:23,520 --> 00:05:26,010 +we want to make sure the model config + +131 +00:05:26,010 --> 00:05:28,980 +has the correct names for +each of the label classes, + +132 +00:05:28,980 --> 00:05:30,707 +and then we want to upload that config. + +133 +00:05:30,707 --> 00:05:32,220 +So we can do that down here. + +134 +00:05:32,220 --> 00:05:34,050 +To get the label names, + +135 +00:05:34,050 --> 00:05:36,547 +we can get that from +the dataset we loaded, + +136 +00:05:36,547 --> 00:05:39,627 +from the features attribute it has. + +137 +00:05:39,627 --> 00:05:42,217 +And then we can create dictionaries + +138 +00:05:42,217 --> 00:05:44,865 +"id2label" and "label2id", + +139 +00:05:44,865 --> 00:05:47,452 +and just assign them to the model config. + +140 +00:05:47,452 --> 00:05:50,790 +And then we can just +push our updated config, + +141 +00:05:50,790 --> 00:05:54,690 +and that'll override the +existing config in the Hub repo. + +142 +00:05:54,690 --> 00:05:56,368 +So that's just been done. + +143 +00:05:56,368 --> 00:05:58,320 +So now, if we go back here, + +144 +00:05:58,320 --> 00:06:00,000 +I'm going to use a +slightly different sentence + +145 +00:06:00,000 --> 00:06:03,540 +because the outputs for +sentences are sometimes cached. + +146 +00:06:03,540 --> 00:06:06,030 +And so, if we want to generate new results + +147 +00:06:06,030 --> 00:06:07,590 +I'm going to use something +slightly different. + +148 +00:06:07,590 --> 00:06:09,783 +So let's try an incorrect sentence. + +149 +00:06:10,830 --> 00:06:12,640 +So this is not valid English grammar + +150 +00:06:13,538 --> 00:06:15,030 +and hopefully the model will see that. + +151 +00:06:15,030 --> 00:06:16,958 +It's going to reload here, + +152 +00:06:16,958 --> 00:06:18,630 +so I'm going to cut a +couple of seconds here, + +153 +00:06:18,630 --> 00:06:20,933 +and then we'll see what +the model is going to say. + +154 +00:06:22,860 --> 00:06:23,820 +Okay. + +155 +00:06:23,820 --> 00:06:26,580 +So the model, it's +confidence isn't very good, + +156 +00:06:26,580 --> 00:06:28,830 +because of course we +didn't really optimize + +157 +00:06:28,830 --> 00:06:30,630 +our hyperparameters at all. + +158 +00:06:30,630 --> 00:06:32,190 +But it has decided that this sentence + +159 +00:06:32,190 --> 00:06:35,094 +is more likely to be +unacceptable than acceptable. + +160 +00:06:35,094 --> 00:06:38,160 +Presumably if we tried a +bit harder with training + +161 +00:06:38,160 --> 00:06:40,080 +we could get a much lower validation loss, + +162 +00:06:40,080 --> 00:06:43,830 +and therefore the model's +predictions would be more precise. + +163 +00:06:43,830 --> 00:06:46,260 +But let's try our original sentence again. + +164 +00:06:46,260 --> 00:06:49,140 +Of course, because of the caching issue, + +165 +00:06:49,140 --> 00:06:52,740 +we're seeing that the original +answers are unchanged. + +166 +00:06:52,740 --> 00:06:55,196 +So let's try a different, valid sentence. + +167 +00:06:55,196 --> 00:06:58,767 +So let's try, "This is a +valid English sentence". + +168 +00:07:00,150 --> 00:07:02,100 +And we see that now the +model correctly decides + +169 +00:07:02,100 --> 00:07:04,290 +that it has a very high +probability of being acceptable, + +170 +00:07:04,290 --> 00:07:06,900 +and a very low probability +of being unacceptable. + +171 +00:07:06,900 --> 00:07:09,930 +So you can use this inference API + +172 +00:07:09,930 --> 00:07:12,810 +even with the checkpoints that +are uploaded during training, + +173 +00:07:12,810 --> 00:07:14,546 +so it can be very interesting to see how + +174 +00:07:14,546 --> 00:07:17,690 +the model's predictions +for sample inputs change + +175 +00:07:17,690 --> 00:07:20,579 +with each epoch of training. + +176 +00:07:20,579 --> 00:07:23,370 +Also, the model we've uploaded + +177 +00:07:23,370 --> 00:07:25,740 +is going to be accessible to you and, + +178 +00:07:25,740 --> 00:07:28,046 +if it's shared publicly, to anyone else. + +179 +00:07:28,046 --> 00:07:29,788 +So if you want to load that model, + +180 +00:07:29,788 --> 00:07:32,500 +all you or anyone else needs to do + +181 +00:07:34,290 --> 00:07:37,440 +is just to load it in either a pipeline, + +182 +00:07:37,440 --> 00:07:40,925 +or you can just load it with, for example, + +183 +00:07:40,925 --> 00:07:43,203 +TFAutoModelForSequenceClassification. + +184 +00:07:46,920 --> 00:07:49,989 +And then for the name you +would just simply pass + +185 +00:07:49,989 --> 00:07:53,325 +the path to the repo you want to upload. + +186 +00:07:53,325 --> 00:07:55,890 +Or to download, excuse me. + +187 +00:07:55,890 --> 00:07:58,710 +So if I want to use this model again, + +188 +00:07:58,710 --> 00:08:00,667 +if I want to load it from the hub, + +189 +00:08:00,667 --> 00:08:01,763 +I just run this one line of code. + +190 +00:08:02,813 --> 00:08:03,773 +The model will be downloaded. + +191 +00:08:07,757 --> 00:08:10,080 +And, with any luck, it'll be ready to + +192 +00:08:10,080 --> 00:08:12,450 +fine-tune on a different +dataset, make predictions with, + +193 +00:08:12,450 --> 00:08:14,340 +or do anything else you wanna do. + +194 +00:08:14,340 --> 00:08:17,700 +So that was a quick overview of how, + +195 +00:08:17,700 --> 00:08:19,470 +after your training or +during your training, + +196 +00:08:19,470 --> 00:08:21,420 +you can upload models to the Hub, + +197 +00:08:21,420 --> 00:08:22,440 +you can checkpoint there, + +198 +00:08:22,440 --> 00:08:24,240 +you can resume training from there, + +199 +00:08:24,240 --> 00:08:26,790 +and you can get inference results + +200 +00:08:26,790 --> 00:08:28,384 +from the models you've uploaded. + +201 +00:08:28,384 --> 00:08:31,084 +So thank you, and I hope to +see you in a future video. + +202 +00:08:32,852 --> 00:08:34,935 +(swoosh) + diff --git a/subtitles/en/35_loading-a-custom-dataset.srt b/subtitles/en/35_loading-a-custom-dataset.srt new file mode 100644 index 000000000..290b96431 --- /dev/null +++ b/subtitles/en/35_loading-a-custom-dataset.srt @@ -0,0 +1,343 @@ +1 +00:00:00,195 --> 00:00:01,426 +(screen whooshing) + +2 +00:00:01,426 --> 00:00:02,614 +(sticker popping) + +3 +00:00:02,614 --> 00:00:06,150 +(screen whooshing) + +4 +00:00:06,150 --> 00:00:08,430 +- Loading a custom dataset. + +5 +00:00:08,430 --> 00:00:09,750 +Although the Hugging Face Hub hosts + +6 +00:00:09,750 --> 00:00:11,730 +over a thousand public datasets, + +7 +00:00:11,730 --> 00:00:12,930 +you'll often need to work with data + +8 +00:00:12,930 --> 00:00:15,900 +that is stored on your +laptop or some remote server. + +9 +00:00:15,900 --> 00:00:18,060 +In this video, we'll explore +how the Datasets library + +10 +00:00:18,060 --> 00:00:20,310 +can be used to load datasets +that aren't available + +11 +00:00:20,310 --> 00:00:21,510 +on the Hugging Face Hub. + +12 +00:00:22,980 --> 00:00:25,290 +As you can see in this +table, the Datasets library + +13 +00:00:25,290 --> 00:00:26,700 +provides several in-built scripts + +14 +00:00:26,700 --> 00:00:29,370 +to load datasets in several formats. + +15 +00:00:29,370 --> 00:00:31,200 +To load a dataset in one of these formats, + +16 +00:00:31,200 --> 00:00:32,730 +you just need to provide +the name of the format + +17 +00:00:32,730 --> 00:00:34,350 +to the load_dataset function, + +18 +00:00:34,350 --> 00:00:35,790 +along with a data_files argument + +19 +00:00:35,790 --> 00:00:37,610 +that points to one or +more filepaths or URLs. + +20 +00:00:40,350 --> 00:00:43,590 +To see this in action, let's +start by loading a CSV file. + +21 +00:00:43,590 --> 00:00:45,960 +In this example, we +first download a dataset + +22 +00:00:45,960 --> 00:00:48,963 +about wine quality from the UCI +machine learning repository. + +23 +00:00:50,220 --> 00:00:52,590 +Since this is a CSV file, we then specify + +24 +00:00:52,590 --> 00:00:53,943 +the CSV loading script. + +25 +00:00:55,320 --> 00:00:57,570 +Now, this script needs to know +where our data is located, + +26 +00:00:57,570 --> 00:00:58,650 +so we provide the filename + +27 +00:00:58,650 --> 00:01:00,483 +as part of the data_files argument. + +28 +00:01:01,860 --> 00:01:03,360 +And the loading script also allows you + +29 +00:01:03,360 --> 00:01:05,040 +to pass several keyword arguments, + +30 +00:01:05,040 --> 00:01:06,750 +so here we've also specified + +31 +00:01:06,750 --> 00:01:09,030 +that the separator is a semi-colon. + +32 +00:01:09,030 --> 00:01:10,380 +And with that, we can see the dataset + +33 +00:01:10,380 --> 00:01:13,020 +is loaded automatically +as a DatasetDict object, + +34 +00:01:13,020 --> 00:01:15,920 +with each column in the CSV +file represented as a feature. + +35 +00:01:17,610 --> 00:01:20,280 +If your dataset is located on +some remote server like GitHub + +36 +00:01:20,280 --> 00:01:22,050 +or some other repository, + +37 +00:01:22,050 --> 00:01:23,700 +the process is actually very similar. + +38 +00:01:23,700 --> 00:01:25,980 +The only difference is that +now the data_files argument + +39 +00:01:25,980 --> 00:01:28,623 +points to a URL instead +of a local filepath. + +40 +00:01:30,330 --> 00:01:33,270 +Let's now take a look at +loading raw text files. + +41 +00:01:33,270 --> 00:01:35,100 +This format is quite common in NLP, + +42 +00:01:35,100 --> 00:01:36,750 +and you'll typically find books and plays + +43 +00:01:36,750 --> 00:01:39,393 +are just a single file +with raw text inside. + +44 +00:01:40,410 --> 00:01:43,020 +In this example, we have a +text file of Shakespeare plays + +45 +00:01:43,020 --> 00:01:45,330 +that's stored on a GitHub repository. + +46 +00:01:45,330 --> 00:01:47,040 +And as we did for CSV files, + +47 +00:01:47,040 --> 00:01:49,020 +we simply choose the text loading script + +48 +00:01:49,020 --> 00:01:51,423 +and point the data_files +argument to the URL. + +49 +00:01:52,260 --> 00:01:55,110 +As you can see, these files +are processed line-by-line, + +50 +00:01:55,110 --> 00:01:57,690 +so empty lines in the raw +text are also represented + +51 +00:01:57,690 --> 00:01:58,953 +as a row in the dataset. + +52 +00:02:00,810 --> 00:02:04,230 +For JSON files, there are two +main formats to know about. + +53 +00:02:04,230 --> 00:02:06,060 +The first one is called JSON Lines, + +54 +00:02:06,060 --> 00:02:09,510 +where every row in the file +is a separate JSON object. + +55 +00:02:09,510 --> 00:02:11,100 +For these files, you can load the dataset + +56 +00:02:11,100 --> 00:02:13,020 +by selecting the JSON loading script + +57 +00:02:13,020 --> 00:02:16,143 +and pointing the data_files +argument to the file or URL. + +58 +00:02:17,160 --> 00:02:19,410 +In this example, we've +loaded a JSON lines files + +59 +00:02:19,410 --> 00:02:21,710 +based on Stack Exchange +questions and answers. + +60 +00:02:23,490 --> 00:02:26,610 +The other format is nested JSON files. + +61 +00:02:26,610 --> 00:02:29,100 +These files basically look +like one huge dictionary, + +62 +00:02:29,100 --> 00:02:31,200 +so the load_dataset function +allow you to specify + +63 +00:02:31,200 --> 00:02:32,733 +which specific key to load. + +64 +00:02:33,630 --> 00:02:35,910 +For example, the SQuAD dataset +for question and answering + +65 +00:02:35,910 --> 00:02:38,340 +has its format, and we +can load it by specifying + +66 +00:02:38,340 --> 00:02:40,340 +that we're interested in the data field. + +67 +00:02:41,400 --> 00:02:42,780 +There is just one last thing to mention + +68 +00:02:42,780 --> 00:02:44,910 +about all of these loading scripts. + +69 +00:02:44,910 --> 00:02:46,410 +You can have more than one split, + +70 +00:02:46,410 --> 00:02:49,080 +you can load them by treating +data files as a dictionary, + +71 +00:02:49,080 --> 00:02:52,140 +and map each split name +to its corresponding file. + +72 +00:02:52,140 --> 00:02:53,970 +Everything else stays completely unchanged + +73 +00:02:53,970 --> 00:02:55,350 +and you can see an example of loading + +74 +00:02:55,350 --> 00:02:58,283 +both the training and validation +splits for this SQuAD here. + +75 +00:02:59,550 --> 00:03:02,310 +And with that, you can now +load datasets from your laptop, + +76 +00:03:02,310 --> 00:03:04,653 +the Hugging Face Hub, +or anywhere else want. + +77 +00:03:06,277 --> 00:03:09,194 +(screen whooshing) + diff --git "a/subtitles/en/36_slice-and-dice-a-dataset-\360\237\224\252.srt" "b/subtitles/en/36_slice-and-dice-a-dataset-\360\237\224\252.srt" new file mode 100644 index 000000000..dd49935e2 --- /dev/null +++ "b/subtitles/en/36_slice-and-dice-a-dataset-\360\237\224\252.srt" @@ -0,0 +1,370 @@ +1 +00:00:00,215 --> 00:00:02,882 +(air whooshing) + +2 +00:00:05,760 --> 00:00:07,623 +- How to slice and dice the dataset? + +3 +00:00:08,760 --> 00:00:10,410 +Most of the time, the data you work with + +4 +00:00:10,410 --> 00:00:13,230 +won't be perfectly prepared +for training models. + +5 +00:00:13,230 --> 00:00:15,810 +In this video, we'll +explore various features + +6 +00:00:15,810 --> 00:00:18,660 +that the datasets library +provides to clean up your data. + +7 +00:00:19,915 --> 00:00:22,500 +The datasets library provides +several built-in methods + +8 +00:00:22,500 --> 00:00:25,350 +that allow you to wrangle +your data in various ways. + +9 +00:00:25,350 --> 00:00:27,360 +In this video, we'll +see how you can shuffle + +10 +00:00:27,360 --> 00:00:30,750 +and split your data, select +the rows you're interested in, + +11 +00:00:30,750 --> 00:00:32,070 +tweak the columns, + +12 +00:00:32,070 --> 00:00:34,620 +and apply processing +functions with the map method. + +13 +00:00:35,640 --> 00:00:37,620 +Let's start with shuffling. + +14 +00:00:37,620 --> 00:00:38,520 +It is generally a good idea + +15 +00:00:38,520 --> 00:00:40,140 +to apply shuffling to your training set + +16 +00:00:40,140 --> 00:00:41,250 +so that your model doesn't learn + +17 +00:00:41,250 --> 00:00:43,590 +any artificial ordering the data. + +18 +00:00:43,590 --> 00:00:45,360 +If you wanna shuffle the whole dataset, + +19 +00:00:45,360 --> 00:00:48,390 +you can apply the appropriately +named shuffle method. + +20 +00:00:48,390 --> 00:00:50,730 +You can see an example of +this method in action here, + +21 +00:00:50,730 --> 00:00:52,200 +where we've downloaded the training split + +22 +00:00:52,200 --> 00:00:55,000 +of the squad dataset and +shuffled all the rows randomly. + +23 +00:00:56,880 --> 00:00:58,230 +Another way to shuffle the data + +24 +00:00:58,230 --> 00:01:00,930 +is to create random train and test splits. + +25 +00:01:00,930 --> 00:01:02,280 +This can be useful if you have to create + +26 +00:01:02,280 --> 00:01:04,620 +your own test splits from raw data. + +27 +00:01:04,620 --> 00:01:07,620 +To do this, you just apply +the train_test_split method + +28 +00:01:07,620 --> 00:01:10,740 +and specify how large +the test split should be. + +29 +00:01:10,740 --> 00:01:14,310 +In this example, we specify +that the test set should be 10% + +30 +00:01:14,310 --> 00:01:15,963 +of the total dataset size. + +31 +00:01:16,890 --> 00:01:19,140 +You can see that the output +of the train_test_split method + +32 +00:01:19,140 --> 00:01:20,610 +is a DatasetDict object + +33 +00:01:20,610 --> 00:01:22,743 +whose keys correspond to the new splits. + +34 +00:01:25,170 --> 00:01:27,210 +Now that we know how +to shuffle the dataset, + +35 +00:01:27,210 --> 00:01:30,060 +let's take a look at returning +the rows we're interested in. + +36 +00:01:30,060 --> 00:01:33,180 +The most common way to do this +is with the select method. + +37 +00:01:33,180 --> 00:01:34,590 +This method expects a list + +38 +00:01:34,590 --> 00:01:36,750 +or a generator of the datasets indices, + +39 +00:01:36,750 --> 00:01:38,670 +and will then return a new dataset object + +40 +00:01:38,670 --> 00:01:40,143 +containing just those rows. + +41 +00:01:41,490 --> 00:01:43,740 +If you wanna create a +random sample of rows, + +42 +00:01:43,740 --> 00:01:45,360 +you can do this by chaining the shuffle + +43 +00:01:45,360 --> 00:01:47,310 +and select methods together. + +44 +00:01:47,310 --> 00:01:48,450 +In this example, + +45 +00:01:48,450 --> 00:01:50,250 +we've created a sample of five elements + +46 +00:01:50,250 --> 00:01:51,423 +from the squad dataset. + +47 +00:01:53,550 --> 00:01:56,010 +The last way to pick out +specific rows in a dataset + +48 +00:01:56,010 --> 00:01:58,290 +is by applying the filter method. + +49 +00:01:58,290 --> 00:02:00,120 +This method checks whether each row + +50 +00:02:00,120 --> 00:02:02,310 +fulfills some condition or not. + +51 +00:02:02,310 --> 00:02:05,130 +For example, here we've +created a small lambda function + +52 +00:02:05,130 --> 00:02:08,460 +that checks whether the title +starts with the letter L. + +53 +00:02:08,460 --> 00:02:11,040 +Once we apply this function +with the filter method, + +54 +00:02:11,040 --> 00:02:14,283 +we get a subset of the data +just containing these rows. + +55 +00:02:16,200 --> 00:02:18,600 +So far, we've been talking +about the rows of a dataset, + +56 +00:02:18,600 --> 00:02:20,490 +but what about the columns? + +57 +00:02:20,490 --> 00:02:22,320 +The datasets library has two main methods + +58 +00:02:22,320 --> 00:02:24,060 +for transforming columns, + +59 +00:02:24,060 --> 00:02:26,760 +a rename_column method to +change the name of the column + +60 +00:02:26,760 --> 00:02:29,460 +and a remove_columns +method to delete them. + +61 +00:02:29,460 --> 00:02:31,860 +You can see examples of +both these methods here. + +62 +00:02:34,140 --> 00:02:36,060 +Some datasets have nested columns, + +63 +00:02:36,060 --> 00:02:39,360 +and you can expand these by +applying the flatten method. + +64 +00:02:39,360 --> 00:02:41,430 +For example, in the squad dataset, + +65 +00:02:41,430 --> 00:02:45,150 +the answers column contains a +text and answer_start field. + +66 +00:02:45,150 --> 00:02:47,430 +If we wanna promote them to +their own separate columns, + +67 +00:02:47,430 --> 00:02:49,383 +we can apply flatten as shown here. + +68 +00:02:51,300 --> 00:02:53,760 +Now of course, no discussion +of the datasets library + +69 +00:02:53,760 --> 00:02:56,880 +would be complete without +mentioning the famous map method. + +70 +00:02:56,880 --> 00:02:59,160 +This method applies a +custom processing function + +71 +00:02:59,160 --> 00:03:01,140 +to each row in the dataset. + +72 +00:03:01,140 --> 00:03:03,360 +For example, here we first define + +73 +00:03:03,360 --> 00:03:04,890 +a lowercase title function, + +74 +00:03:04,890 --> 00:03:07,503 +that simply lowercases the +text in the title column. + +75 +00:03:08,640 --> 00:03:11,700 +And then we feed that +function to the map method, + +76 +00:03:11,700 --> 00:03:14,223 +and voila, we now have lowercase titles. + +77 +00:03:16,020 --> 00:03:18,360 +The map method can also be +used to feed batches of rows + +78 +00:03:18,360 --> 00:03:20,100 +to the processing function. + +79 +00:03:20,100 --> 00:03:22,410 +This is especially useful for tokenization + +80 +00:03:22,410 --> 00:03:25,290 +where the tokenizer is backed +by the Tokenizers library, + +81 +00:03:25,290 --> 00:03:26,910 +and they can use fast multithreading + +82 +00:03:26,910 --> 00:03:28,563 +to process batches in parallel. + +83 +00:03:30,056 --> 00:03:32,723 +(air whooshing) + diff --git "a/subtitles/en/37_datasets-+-dataframes-=-\342\235\244\357\270\217.srt" "b/subtitles/en/37_datasets-+-dataframes-=-\342\235\244\357\270\217.srt" new file mode 100644 index 000000000..5204eac28 --- /dev/null +++ "b/subtitles/en/37_datasets-+-dataframes-=-\342\235\244\357\270\217.srt" @@ -0,0 +1,283 @@ +1 +00:00:00,227 --> 00:00:01,432 +(whooshing sound) + +2 +00:00:01,432 --> 00:00:02,420 +(sticker popping) + +3 +00:00:02,420 --> 00:00:05,340 +(whooshing sound) + +4 +00:00:05,340 --> 00:00:07,833 +- Datasets and DataFrames equals love. + +5 +00:00:08,790 --> 00:00:11,010 +Although the processing +functions of the Datasets library + +6 +00:00:11,010 --> 00:00:14,040 +will cover most of the cases +needed to train a model, + +7 +00:00:14,040 --> 00:00:15,660 +there are times when you'll +need to switch to a library + +8 +00:00:15,660 --> 00:00:18,240 +like Pandas to access +more powerful features + +9 +00:00:18,240 --> 00:00:20,970 +or high level APIs for visualization. + +10 +00:00:20,970 --> 00:00:23,220 +Fortunately, the Datasets +library is designed + +11 +00:00:23,220 --> 00:00:25,710 +to be interoperable with +libraries like Pandas, + +12 +00:00:25,710 --> 00:00:29,790 +as well as NumPy, PyTorch, +TensorFlow and JAX. + +13 +00:00:29,790 --> 00:00:30,930 +In this video, we'll take a look + +14 +00:00:30,930 --> 00:00:32,550 +at how we can quickly switch our data + +15 +00:00:32,550 --> 00:00:34,263 +to Pandas DataFrames and back. + +16 +00:00:36,120 --> 00:00:38,310 +As an example, let's +suppose we're analyzing + +17 +00:00:38,310 --> 00:00:40,830 +Supreme Court cases from Switzerland. + +18 +00:00:40,830 --> 00:00:43,020 +As usual, we download +our dataset from the hub + +19 +00:00:43,020 --> 00:00:44,940 +using the load_dataset function. + +20 +00:00:44,940 --> 00:00:46,980 +And you can see that the first +element of the training set + +21 +00:00:46,980 --> 00:00:48,510 +is an ordinary Python dictionary + +22 +00:00:48,510 --> 00:00:50,110 +with various fields of interest. + +23 +00:00:51,690 --> 00:00:53,670 +Now, suppose that before +we train any models, + +24 +00:00:53,670 --> 00:00:55,590 +we'd like to explore the data a bit. + +25 +00:00:55,590 --> 00:00:57,390 +For example, we might +be interested in knowing + +26 +00:00:57,390 --> 00:00:59,820 +which legal areas are the most common + +27 +00:00:59,820 --> 00:01:01,380 +or we might wanna know how the languages + +28 +00:01:01,380 --> 00:01:02,930 +are distributed across regions. + +29 +00:01:04,500 --> 00:01:05,333 +Answering these questions + +30 +00:01:05,333 --> 00:01:07,530 +with the native Arrow format isn't easy, + +31 +00:01:07,530 --> 00:01:10,500 +but we can quickly switch to +Pandas to get our answers. + +32 +00:01:10,500 --> 00:01:13,500 +The way this works is that by +using the set_format method, + +33 +00:01:13,500 --> 00:01:15,480 +we will change the output +format of the dataset + +34 +00:01:15,480 --> 00:01:18,930 +from Python dictionaries +to Pandas DataFrames. + +35 +00:01:18,930 --> 00:01:20,130 +As you can see in this example, + +36 +00:01:20,130 --> 00:01:22,890 +each row in the dataset is +represented as a DataFrame, + +37 +00:01:22,890 --> 00:01:24,540 +so we can slice the whole dataset + +38 +00:01:24,540 --> 00:01:26,583 +to get a single DataFrame of the corpus. + +39 +00:01:28,080 --> 00:01:29,520 +The way this works under the hood, + +40 +00:01:29,520 --> 00:01:31,080 +is that the datasets library changes + +41 +00:01:31,080 --> 00:01:33,900 +the magic __getitem__ +method of the dataset. + +42 +00:01:33,900 --> 00:01:35,640 +The __getitem__ method is a special method + +43 +00:01:35,640 --> 00:01:37,320 +for Python containers that allows you + +44 +00:01:37,320 --> 00:01:39,870 +to specify how indexing works. + +45 +00:01:39,870 --> 00:01:42,540 +In this case, the __getitem__ +method of the raw dataset + +46 +00:01:42,540 --> 00:01:45,150 +starts off by returning +a Python dictionary + +47 +00:01:45,150 --> 00:01:47,520 +and then after applying set_format, + +48 +00:01:47,520 --> 00:01:50,283 +we change __getitem__ to +return DataFrames instead. + +49 +00:01:52,080 --> 00:01:54,690 +The Datasets library also +provides a to_pandas method + +50 +00:01:54,690 --> 00:01:56,250 +if you wanna do the format conversion + +51 +00:01:56,250 --> 00:01:58,113 +and slicing of the dataset in one go. + +52 +00:02:00,090 --> 00:02:01,590 +And once you have a DataFrame, + +53 +00:02:01,590 --> 00:02:03,990 +you can find the answers to +all sorts of complex questions + +54 +00:02:03,990 --> 00:02:06,740 +or make plots with your +favorite visualization library. + +55 +00:02:07,890 --> 00:02:08,850 +The only thing to remember + +56 +00:02:08,850 --> 00:02:10,830 +is that once you're done +with your Pandas analysis, + +57 +00:02:10,830 --> 00:02:14,460 +you should reset the output +format back to Arrow tables. + +58 +00:02:14,460 --> 00:02:16,350 +If you don't, you can run into problems + +59 +00:02:16,350 --> 00:02:17,910 +if you try to tokenize your text + +60 +00:02:17,910 --> 00:02:19,260 +because it is no longer represented + +61 +00:02:19,260 --> 00:02:20,610 +as strings in a dictionary. + +62 +00:02:21,750 --> 00:02:24,780 +By resetting the output format +we get back Arrow tables + +63 +00:02:24,780 --> 00:02:26,580 +and we can tokenize without problem. + +64 +00:02:27,513 --> 00:02:30,346 +(whooshing sound) + diff --git a/subtitles/en/38_saving-and-reloading-a-dataset.srt b/subtitles/en/38_saving-and-reloading-a-dataset.srt new file mode 100644 index 000000000..046a4b4f1 --- /dev/null +++ b/subtitles/en/38_saving-and-reloading-a-dataset.srt @@ -0,0 +1,359 @@ +1 +00:00:00,000 --> 00:00:02,917 +(transition music) + +2 +00:00:06,600 --> 00:00:08,283 +- Saving and reloading a dataset. + +3 +00:00:09,210 --> 00:00:10,320 +In this video, we'll take a look + +4 +00:00:10,320 --> 00:00:12,360 +at saving a dataset in various formats + +5 +00:00:12,360 --> 00:00:14,660 +and explore the ways to +reload the saved data. + +6 +00:00:17,310 --> 00:00:20,100 +When you download a dataset, +the processing scripts and data + +7 +00:00:20,100 --> 00:00:22,470 +are stored locally on your computer. + +8 +00:00:22,470 --> 00:00:24,000 +The cache allows the Datasets library + +9 +00:00:24,000 --> 00:00:25,230 +to avoid re-downloading + +10 +00:00:25,230 --> 00:00:28,620 +or processing the entire +dataset every time you use it. + +11 +00:00:28,620 --> 00:00:31,170 +Now, the data is stored in +the form of Arrow tables + +12 +00:00:31,170 --> 00:00:32,490 +whose location can be found + +13 +00:00:32,490 --> 00:00:35,730 +by accessing the dataset's +cache_files attribute. + +14 +00:00:35,730 --> 00:00:38,430 +In this example, we've +downloaded the allocine dataset + +15 +00:00:38,430 --> 00:00:40,080 +from the Hugging Face Hub, and you can see + +16 +00:00:40,080 --> 00:00:41,430 +that there are three Arrow files + +17 +00:00:41,430 --> 00:00:43,473 +stored in the cache, one for each split. + +18 +00:00:45,360 --> 00:00:47,460 +But in many cases, you'll +wanna save your dataset + +19 +00:00:47,460 --> 00:00:49,890 +in a different location or format. + +20 +00:00:49,890 --> 00:00:51,900 +As shown in the table, +the Datasets library + +21 +00:00:51,900 --> 00:00:54,870 +provides four main +functions to achieve this. + +22 +00:00:54,870 --> 00:00:56,130 +Now, you're probably already familiar + +23 +00:00:56,130 --> 00:00:58,770 +with the CSV and JSON formats, +both of which are great + +24 +00:00:58,770 --> 00:01:00,810 +if you just wanna quickly save a small + +25 +00:01:00,810 --> 00:01:02,790 +or medium-sized dataset. + +26 +00:01:02,790 --> 00:01:03,976 +But if your dataset is huge, + +27 +00:01:03,976 --> 00:01:07,860 +you'll wanna save it in either +the Arrow or Parquet formats. + +28 +00:01:07,860 --> 00:01:09,660 +Arrow files are great +if you plan to reload + +29 +00:01:09,660 --> 00:01:11,850 +or process the data in the near future. + +30 +00:01:11,850 --> 00:01:13,290 +While Parquet files are designed + +31 +00:01:13,290 --> 00:01:16,140 +for long-term storage and +are very space-efficient. + +32 +00:01:16,140 --> 00:01:18,140 +Let's take a closer look at each format. + +33 +00:01:19,800 --> 00:01:21,750 +To save a dataset or a dataset_dict object + +34 +00:01:21,750 --> 00:01:25,560 +in the Arrow format, we use +the save_to_disk function. + +35 +00:01:25,560 --> 00:01:26,910 +As you can see in this example, + +36 +00:01:26,910 --> 00:01:29,790 +we simply provide the path +we wish to save the data to + +37 +00:01:29,790 --> 00:01:30,720 +and the Datasets library + +38 +00:01:30,720 --> 00:01:32,340 +will automatically create a directory + +39 +00:01:32,340 --> 00:01:35,790 +for each split to store the +Arrow table and the metadata. + +40 +00:01:35,790 --> 00:01:37,680 +Since we're dealing with +a dataset_dict object + +41 +00:01:37,680 --> 00:01:39,090 +that has multiple splits, + +42 +00:01:39,090 --> 00:01:40,590 +this information is also stored + +43 +00:01:40,590 --> 00:01:42,243 +in the dataset_dict.json file. + +44 +00:01:44,250 --> 00:01:46,710 +Now, when we wanna reload +the Arrow datasets, + +45 +00:01:46,710 --> 00:01:48,870 +we use the load_from_disk function. + +46 +00:01:48,870 --> 00:01:51,210 +We simply pass the path +of our dataset directory, + +47 +00:01:51,210 --> 00:01:53,583 +and voila, the original +dataset is recovered. + +48 +00:01:55,594 --> 00:01:57,180 +If we wanna save our dataset + +49 +00:01:57,180 --> 00:02:00,990 +in the CSV format, we +use the to_csv function. + +50 +00:02:00,990 --> 00:02:02,280 +In this case, you'll need to loop + +51 +00:02:02,280 --> 00:02:04,170 +over the splits of the dataset_dict object + +52 +00:02:04,170 --> 00:02:07,710 +and save each dataset as +an individual CSV file. + +53 +00:02:07,710 --> 00:02:10,950 +Since the to_csv function is +based on the one from Pandas, + +54 +00:02:10,950 --> 00:02:13,980 +you can pass keyword arguments +to configure the output. + +55 +00:02:13,980 --> 00:02:16,230 +In this example, we've +set the index argument + +56 +00:02:16,230 --> 00:02:18,480 +to None to prevent the +dataset's index column + +57 +00:02:18,480 --> 00:02:20,553 +from being included in the CSV files. + +58 +00:02:22,470 --> 00:02:24,240 +To reload our CSV files, + +59 +00:02:24,240 --> 00:02:27,180 +we just then use the familiar +load_dataset function + +60 +00:02:27,180 --> 00:02:29,160 +together with the CSV loading script + +61 +00:02:29,160 --> 00:02:30,360 +and the data_files argument, + +62 +00:02:30,360 --> 00:02:34,020 +which specifies the file names +associated with each split. + +63 +00:02:34,020 --> 00:02:35,400 +As you can see in this example, + +64 +00:02:35,400 --> 00:02:37,320 +by providing all the splits +and their file names, + +65 +00:02:37,320 --> 00:02:39,770 +we've recovered the original +dataset_dict object. + +66 +00:02:41,880 --> 00:02:43,560 +Now, to save a dataset in the JSON + +67 +00:02:43,560 --> 00:02:46,710 +or Parquet formats is very +similar to the CSV case. + +68 +00:02:46,710 --> 00:02:49,890 +We use either the to_json +function for JSON files + +69 +00:02:49,890 --> 00:02:52,740 +or the to_parquet +function for Parquet ones. + +70 +00:02:52,740 --> 00:02:55,740 +And just like the CSV case, we +need to loop over the splits + +71 +00:02:55,740 --> 00:02:57,753 +to save each one as an individual file. + +72 +00:02:59,580 --> 00:03:02,940 +And once our datasets are +saved as JSON or Parquet files, + +73 +00:03:02,940 --> 00:03:03,990 +we can reload them again + +74 +00:03:03,990 --> 00:03:06,960 +with the appropriate script +in the load_dataset function. + +75 +00:03:06,960 --> 00:03:09,993 +And we just need to provide a +data_files argument as before. + +76 +00:03:10,860 --> 00:03:11,910 +This example shows + +77 +00:03:11,910 --> 00:03:14,560 +how we can reload our save +datasets in either format. + +78 +00:03:16,620 --> 00:03:17,970 +And with that, you now know + +79 +00:03:17,970 --> 00:03:20,220 +how to save your datasets +in various formats. + +80 +00:03:21,441 --> 00:03:24,358 +(transition music) + diff --git a/subtitles/en/39_memory-mapping-&-streaming.srt b/subtitles/en/39_memory-mapping-&-streaming.srt new file mode 100644 index 000000000..2efb51f8a --- /dev/null +++ b/subtitles/en/39_memory-mapping-&-streaming.srt @@ -0,0 +1,370 @@ +1 +00:00:00,511 --> 00:00:01,784 +(air whooshing) + +2 +00:00:01,784 --> 00:00:02,964 +(logo popping) + +3 +00:00:02,964 --> 00:00:05,640 +(metal sliding) + +4 +00:00:05,640 --> 00:00:07,203 +- Memory mapping and streaming. + +5 +00:00:08,040 --> 00:00:09,180 +In this video, we'll take a look + +6 +00:00:09,180 --> 00:00:11,520 +at two core features +of the Datasets library + +7 +00:00:11,520 --> 00:00:14,220 +that allow you to load +and process huge datasets + +8 +00:00:14,220 --> 00:00:16,263 +without blowing up your laptop's CPU. + +9 +00:00:18,300 --> 00:00:20,280 +Nowadays, it's not +uncommon to find yourself + +10 +00:00:20,280 --> 00:00:22,950 +working with multi-GB sized datasets, + +11 +00:00:22,950 --> 00:00:24,420 +especially if you're planning to pretrain + +12 +00:00:24,420 --> 00:00:28,110 +a transformer like BERT +or GPT-2 from scratch. + +13 +00:00:28,110 --> 00:00:31,260 +In these cases, even loading +the data can be a challenge. + +14 +00:00:31,260 --> 00:00:34,680 +For example, the c4 +corpus used to pretrain T5 + +15 +00:00:34,680 --> 00:00:36,903 +consists of over two terabytes of data. + +16 +00:00:38,400 --> 00:00:40,050 +To handle these large datasets, + +17 +00:00:40,050 --> 00:00:42,990 +the Datasets library is +built on two core features: + +18 +00:00:42,990 --> 00:00:46,350 +the Apache Arrow format +and a streaming API. + +19 +00:00:46,350 --> 00:00:49,110 +Arrow is designed for +high-performance data processing + +20 +00:00:49,110 --> 00:00:51,360 +and represents each table-like dataset + +21 +00:00:51,360 --> 00:00:52,773 +with a column-based format. + +22 +00:00:53,730 --> 00:00:56,130 +As you can see in this +example, column-based formats + +23 +00:00:56,130 --> 00:00:59,280 +group the elements of a table +in consecutive blocks of RAM + +24 +00:00:59,280 --> 00:01:01,563 +and this unlocks fast +access and processing. + +25 +00:01:02,760 --> 00:01:05,550 +Arrow is great at +processing data at any scale + +26 +00:01:05,550 --> 00:01:07,110 +but some datasets are so large + +27 +00:01:07,110 --> 00:01:09,600 +that you can't even fit +them on your hard disk. + +28 +00:01:09,600 --> 00:01:11,730 +So for these cases, the +Datasets library provides + +29 +00:01:11,730 --> 00:01:14,820 +a streaming API that allows +you to progressively download + +30 +00:01:14,820 --> 00:01:17,700 +the raw data one element at a time. + +31 +00:01:17,700 --> 00:01:20,430 +The result is a special object +called an IterableDataset + +32 +00:01:20,430 --> 00:01:22,180 +that we'll see in more detail soon. + +33 +00:01:23,700 --> 00:01:26,670 +Let's start by looking at +why Arrow is so powerful. + +34 +00:01:26,670 --> 00:01:28,860 +The first feature is that +it treats every dataset + +35 +00:01:28,860 --> 00:01:30,153 +as a memory-mapped file. + +36 +00:01:31,020 --> 00:01:32,430 +Now, memory mapping is a mechanism + +37 +00:01:32,430 --> 00:01:35,400 +that maps a portion of a file +or an entire file and disc + +38 +00:01:35,400 --> 00:01:37,410 +to a chunk of virtual memory. + +39 +00:01:37,410 --> 00:01:38,520 +This allows applications + +40 +00:01:38,520 --> 00:01:41,280 +to access segments of +an extremely large file + +41 +00:01:41,280 --> 00:01:44,080 +without having to read the +whole file into memory first. + +42 +00:01:45,150 --> 00:01:48,120 +Another cool feature of Arrow's +memory mapping capabilities + +43 +00:01:48,120 --> 00:01:49,860 +is that it allows multiple processes + +44 +00:01:49,860 --> 00:01:51,840 +to work with the same large dataset + +45 +00:01:51,840 --> 00:01:54,333 +without moving it or +copying it in any way. + +46 +00:01:55,680 --> 00:01:57,570 +This zero-copy feature of Arrow + +47 +00:01:57,570 --> 00:02:00,600 +makes it extremely fast for +iterating over a dataset. + +48 +00:02:00,600 --> 00:02:02,640 +And this example, you +can see that we iterate + +49 +00:02:02,640 --> 00:02:05,160 +over 15 million rows in about a minute + +50 +00:02:05,160 --> 00:02:06,780 +just using a standard laptop. + +51 +00:02:06,780 --> 00:02:08,080 +That's not too bad at all. + +52 +00:02:09,750 --> 00:02:12,660 +Let's now take a look at how +we can stream a large dataset. + +53 +00:02:12,660 --> 00:02:14,520 +The only change you need to make is to set + +54 +00:02:14,520 --> 00:02:17,910 +the streaming=True argument in +the load_dataset() function. + +55 +00:02:17,910 --> 00:02:20,580 +This will return a special +IterableDataset object + +56 +00:02:20,580 --> 00:02:22,260 +which is a bit different +to the Dataset objects + +57 +00:02:22,260 --> 00:02:24,330 +we've seen in other videos. + +58 +00:02:24,330 --> 00:02:25,980 +This object is an iterable, + +59 +00:02:25,980 --> 00:02:28,530 +which means we can't index +it to access elements, + +60 +00:02:28,530 --> 00:02:30,180 +but instead we iterate on it + +61 +00:02:30,180 --> 00:02:32,850 +using the iter and next methods. + +62 +00:02:32,850 --> 00:02:34,050 +This will download and access + +63 +00:02:34,050 --> 00:02:35,850 +a single example from the dataset, + +64 +00:02:35,850 --> 00:02:37,410 +which means you can progressively iterate + +65 +00:02:37,410 --> 00:02:40,360 +through a huge dataset without +having to download it first. + +66 +00:02:42,150 --> 00:02:43,590 +Tokenizing text with a map() method + +67 +00:02:43,590 --> 00:02:45,660 +also works in a similar way. + +68 +00:02:45,660 --> 00:02:47,160 +We first stream the dataset + +69 +00:02:47,160 --> 00:02:49,830 +and then apply the map() +method with the tokenizer. + +70 +00:02:49,830 --> 00:02:53,283 +To get the first tokenized +example, we apply iter and next. + +71 +00:02:54,750 --> 00:02:57,210 +The main difference with +an IterableDataset is that + +72 +00:02:57,210 --> 00:02:59,970 +instead of using a select() +method to return examples, + +73 +00:02:59,970 --> 00:03:01,530 +we use the take() and skip() methods + +74 +00:03:01,530 --> 00:03:03,573 +because we can't index into the dataset. + +75 +00:03:04,470 --> 00:03:05,460 +The take() method returns + +76 +00:03:05,460 --> 00:03:07,500 +the first N examples in the dataset, + +77 +00:03:07,500 --> 00:03:09,270 +while skip(), as you can imagine, + +78 +00:03:09,270 --> 00:03:12,480 +skips the first N and returns the rest. + +79 +00:03:12,480 --> 00:03:15,300 +You can see examples of both +of these methods in action + +80 +00:03:15,300 --> 00:03:16,710 +where we create a validation set + +81 +00:03:16,710 --> 00:03:18,660 +from the first 1000 examples + +82 +00:03:18,660 --> 00:03:21,010 +and then skip those to +create the training set. + +83 +00:03:23,012 --> 00:03:25,762 +(air whooshing) + diff --git a/subtitles/en/40_uploading-a-dataset-to-the-hub.srt b/subtitles/en/40_uploading-a-dataset-to-the-hub.srt new file mode 100644 index 000000000..ee571bef0 --- /dev/null +++ b/subtitles/en/40_uploading-a-dataset-to-the-hub.srt @@ -0,0 +1,228 @@ +1 +00:00:00,000 --> 00:00:02,917 +(transition music) + +2 +00:00:05,490 --> 00:00:07,950 +- Uploading a dataset to the hub. + +3 +00:00:07,950 --> 00:00:09,060 +In this video, we'll take a look + +4 +00:00:09,060 --> 00:00:10,860 +at how you can upload +your very own dataset + +5 +00:00:10,860 --> 00:00:12,060 +to the Hugging Face Hub. + +6 +00:00:13,680 --> 00:00:14,670 +The first thing you need to do + +7 +00:00:14,670 --> 00:00:17,400 +is create a new dataset +repository on the hub. + +8 +00:00:17,400 --> 00:00:19,260 +So, just click on your profile icon + +9 +00:00:19,260 --> 00:00:21,750 +and select the New Dataset button. + +10 +00:00:21,750 --> 00:00:24,750 +Next, we need to assign +an owner of the dataset. + +11 +00:00:24,750 --> 00:00:26,970 +By default, this will be your hub account, + +12 +00:00:26,970 --> 00:00:28,170 +but you can also create datasets + +13 +00:00:28,170 --> 00:00:30,585 +under any organization that you belong to. + +14 +00:00:30,585 --> 00:00:33,780 +Then, we just need to give +the dataset a good name + +15 +00:00:33,780 --> 00:00:36,513 +and specify whether it is a +public or private dataset. + +16 +00:00:37,410 --> 00:00:39,810 +Public datasets can be accessed by anyone + +17 +00:00:39,810 --> 00:00:41,670 +while private datasets +can only be accessed + +18 +00:00:41,670 --> 00:00:43,653 +by you or members of your organization. + +19 +00:00:44,580 --> 00:00:47,280 +And with that, we can go +ahead and create the dataset. + +20 +00:00:48,690 --> 00:00:51,060 +Now that you have an empty +dataset repository on the hub, + +21 +00:00:51,060 --> 00:00:53,880 +the next thing to do is +add some actual data to it. + +22 +00:00:53,880 --> 00:00:55,050 +You can do this with git, + +23 +00:00:55,050 --> 00:00:57,960 +but the easiest way is by +selecting the Upload file button. + +24 +00:00:57,960 --> 00:00:59,160 +And then, you can just go ahead + +25 +00:00:59,160 --> 00:01:02,243 +and upload the files +directly from your machine. + +26 +00:01:02,243 --> 00:01:03,846 +After you've uploaded your files, + +27 +00:01:03,846 --> 00:01:05,670 +you'll see them appear in the repository + +28 +00:01:05,670 --> 00:01:07,320 +under the Files and versions tab. + +29 +00:01:08,550 --> 00:01:11,370 +The last step is to create a dataset card. + +30 +00:01:11,370 --> 00:01:13,590 +Well-documented datasets +are more likely to be useful + +31 +00:01:13,590 --> 00:01:15,600 +to others as they provide +the context to decide + +32 +00:01:15,600 --> 00:01:17,370 +whether the dataset is relevant + +33 +00:01:17,370 --> 00:01:18,450 +or whether there are any biases + +34 +00:01:18,450 --> 00:01:20,673 +or risks associated +with using the dataset. + +35 +00:01:21,540 --> 00:01:22,710 +On the Hugging Face Hub, + +36 +00:01:22,710 --> 00:01:25,650 +this information is stored in +each repositories README file. + +37 +00:01:25,650 --> 00:01:27,988 +There are two main steps +that you should take. + +38 +00:01:27,988 --> 00:01:30,651 +First, you need to create some metadata + +39 +00:01:30,651 --> 00:01:32,010 +that will allow your dataset + +40 +00:01:32,010 --> 00:01:34,590 +to be easily found by others on the hub. + +41 +00:01:34,590 --> 00:01:35,670 +You can create this metadata + +42 +00:01:35,670 --> 00:01:37,860 +using the datasets tagging application, + +43 +00:01:37,860 --> 00:01:40,620 +which we'll link to in +the video description. + +44 +00:01:40,620 --> 00:01:42,240 +Once you've created the metadata, + +45 +00:01:42,240 --> 00:01:44,190 +you can fill out the +rest of the dataset card, + +46 +00:01:44,190 --> 00:01:45,240 +and we provide a template + +47 +00:01:45,240 --> 00:01:47,090 +that we'll also link to in the video. + +48 +00:01:48,480 --> 00:01:50,280 +And once your dataset is on the hub, + +49 +00:01:50,280 --> 00:01:53,400 +you can load it using the +trusty load_dataset function. + +50 +00:01:53,400 --> 00:01:55,015 +Just provide the name of your repository + +51 +00:01:55,015 --> 00:01:57,843 +and a data_files argument, +and you're good to go. + +52 +00:01:59,619 --> 00:02:02,536 +(transition music) + diff --git a/subtitles/en/41_text-embeddings-&-semantic-search.srt b/subtitles/en/41_text-embeddings-&-semantic-search.srt new file mode 100644 index 000000000..51c9d9b29 --- /dev/null +++ b/subtitles/en/41_text-embeddings-&-semantic-search.srt @@ -0,0 +1,368 @@ +1 +00:00:00,621 --> 00:00:03,204 +(upbeat music) + +2 +00:00:05,670 --> 00:00:08,520 +- Text embeddings and semantic search. + +3 +00:00:08,520 --> 00:00:10,770 +In this video we'll explore +how Transformer models + +4 +00:00:10,770 --> 00:00:12,810 +represent text as embedding vectors + +5 +00:00:12,810 --> 00:00:15,420 +and how these vectors can be +used to find similar documents + +6 +00:00:15,420 --> 00:00:16,293 +in a corpus. + +7 +00:00:17,730 --> 00:00:19,890 +Text embeddings are just +a fancy way of saying + +8 +00:00:19,890 --> 00:00:22,170 +that we can represent text +as an array of numbers + +9 +00:00:22,170 --> 00:00:23,640 +called a vector. + +10 +00:00:23,640 --> 00:00:25,710 +To create these embeddings we usually use + +11 +00:00:25,710 --> 00:00:27,393 +an encoder-based model like BERT. + +12 +00:00:28,530 --> 00:00:31,290 +In this example, you can see +how we feed three sentences + +13 +00:00:31,290 --> 00:00:34,830 +to the encoder and get +three vectors as the output. + +14 +00:00:34,830 --> 00:00:37,050 +Reading the text, we can +see that walking the dog + +15 +00:00:37,050 --> 00:00:39,450 +seems to be most similar +to walking the cat, + +16 +00:00:39,450 --> 00:00:41,350 +but let's see if we can quantify this. + +17 +00:00:42,810 --> 00:00:44,040 +The trick to do the comparison + +18 +00:00:44,040 --> 00:00:45,630 +is to compute a similarity metric + +19 +00:00:45,630 --> 00:00:48,210 +between each pair of embedding vectors. + +20 +00:00:48,210 --> 00:00:51,120 +These vectors usually live in +a very high-dimensional space, + +21 +00:00:51,120 --> 00:00:53,190 +so a similarity metric can +be anything that measures + +22 +00:00:53,190 --> 00:00:55,740 +some sort of distance between vectors. + +23 +00:00:55,740 --> 00:00:58,560 +One very popular metric +is cosine similarity, + +24 +00:00:58,560 --> 00:01:00,390 +which uses the angle between two vectors + +25 +00:01:00,390 --> 00:01:02,610 +to measure how close they are. + +26 +00:01:02,610 --> 00:01:05,250 +In this example, our +embedding vectors live in 3D + +27 +00:01:05,250 --> 00:01:07,110 +and we can see that the +orange and Grey vectors + +28 +00:01:07,110 --> 00:01:09,560 +are close to each other +and have a smaller angle. + +29 +00:01:11,130 --> 00:01:12,510 +Now one problem we have to deal with + +30 +00:01:12,510 --> 00:01:15,180 +is that Transformer models +like BERT will actually return + +31 +00:01:15,180 --> 00:01:16,983 +one embedding vector per token. + +32 +00:01:17,880 --> 00:01:20,700 +For example in the sentence, +"I took my dog for a walk," + +33 +00:01:20,700 --> 00:01:23,853 +we can expect several embedding +vectors, one for each word. + +34 +00:01:25,110 --> 00:01:27,870 +For example, here we can +see the output of our model + +35 +00:01:27,870 --> 00:01:30,540 +has produced 9 embedding +vectors per sentence, + +36 +00:01:30,540 --> 00:01:33,750 +and each vector has 384 dimensions. + +37 +00:01:33,750 --> 00:01:36,210 +But what we really want is +a single embedding vector + +38 +00:01:36,210 --> 00:01:37,353 +for each sentence. + +39 +00:01:38,940 --> 00:01:42,060 +To deal with this, we can use +a technique called pooling. + +40 +00:01:42,060 --> 00:01:43,050 +The simplest pooling method + +41 +00:01:43,050 --> 00:01:44,520 +is to just take the token embedding + +42 +00:01:44,520 --> 00:01:46,203 +of the special CLS token. + +43 +00:01:47,100 --> 00:01:49,650 +Alternatively, we can +average the token embeddings + +44 +00:01:49,650 --> 00:01:52,500 +which is called mean pooling +and this is what we do here. + +45 +00:01:53,370 --> 00:01:55,800 +With mean pooling the only +thing we need to make sure + +46 +00:01:55,800 --> 00:01:58,410 +is that we don't include the +padding tokens in the average, + +47 +00:01:58,410 --> 00:02:01,860 +which is why you can see the +attention mask being used here. + +48 +00:02:01,860 --> 00:02:05,100 +This gives us a 384 dimensional +vector for each sentence + +49 +00:02:05,100 --> 00:02:06,600 +which is exactly what we want. + +50 +00:02:07,920 --> 00:02:09,810 +And once we have our sentence embeddings, + +51 +00:02:09,810 --> 00:02:11,730 +we can compute the cosine similarity + +52 +00:02:11,730 --> 00:02:13,113 +for each pair of vectors. + +53 +00:02:13,993 --> 00:02:16,350 +In this example we use the +function from scikit-learn + +54 +00:02:16,350 --> 00:02:19,140 +and you can see that the sentence +"I took my dog for a walk" + +55 +00:02:19,140 --> 00:02:22,140 +has indeed a strong overlap +with "I took my cat for a walk". + +56 +00:02:22,140 --> 00:02:23,240 +Hooray! We've done it. + +57 +00:02:25,110 --> 00:02:27,180 +We can actually take this +idea one step further + +58 +00:02:27,180 --> 00:02:29,220 +by comparing the similarity +between a question + +59 +00:02:29,220 --> 00:02:31,170 +and a corpus of documents. + +60 +00:02:31,170 --> 00:02:33,810 +For example, suppose we embed every post + +61 +00:02:33,810 --> 00:02:35,430 +in the Hugging Face forums. + +62 +00:02:35,430 --> 00:02:37,800 +We can then ask a question, embed it, + +63 +00:02:37,800 --> 00:02:40,590 +and check which forum +posts are most similar. + +64 +00:02:40,590 --> 00:02:42,750 +This process is often +called semantic search, + +65 +00:02:42,750 --> 00:02:45,423 +because it allows us to +compare queries with context. + +66 +00:02:47,040 --> 00:02:48,450 +To create a semantic search engine + +67 +00:02:48,450 --> 00:02:51,030 +is actually quite simple +in the datasets library. + +68 +00:02:51,030 --> 00:02:53,340 +First we need to embed all the documents. + +69 +00:02:53,340 --> 00:02:56,070 +And in this example, +we take a small sample + +70 +00:02:56,070 --> 00:02:57,780 +from the SQUAD dataset and apply + +71 +00:02:57,780 --> 00:03:00,180 +the same embedding logic as before. + +72 +00:03:00,180 --> 00:03:02,280 +This gives us a new +column called embeddings, + +73 +00:03:02,280 --> 00:03:04,530 +which stores the embeddings +of every passage. + +74 +00:03:05,880 --> 00:03:07,260 +Once we have our embeddings, + +75 +00:03:07,260 --> 00:03:10,200 +we need a way to find nearest +neighbors for a query. + +76 +00:03:10,200 --> 00:03:13,170 +The datasets library provides +a special object called FAISS + +77 +00:03:13,170 --> 00:03:16,080 +which allows you to quickly +compare embedding vectors. + +78 +00:03:16,080 --> 00:03:19,950 +So we add the FAISS index, +embed a question and voila, + +79 +00:03:19,950 --> 00:03:21,870 +we've now found the 3 +most similar articles + +80 +00:03:21,870 --> 00:03:23,320 +which might store the answer. + +81 +00:03:25,182 --> 00:03:27,849 +(upbeat music) + diff --git a/subtitles/en/42_training-a-new-tokenizer.srt b/subtitles/en/42_training-a-new-tokenizer.srt new file mode 100644 index 000000000..d2d2ded51 --- /dev/null +++ b/subtitles/en/42_training-a-new-tokenizer.srt @@ -0,0 +1,512 @@ +1 +00:00:00,000 --> 00:00:02,667 +(air whooshing) + +2 +00:00:05,310 --> 00:00:08,700 +- In this video we will see together + +3 +00:00:08,700 --> 00:00:11,820 +what is the purpose of +training a tokenizer, + +4 +00:00:11,820 --> 00:00:14,400 +what are the key steps to follow, + +5 +00:00:14,400 --> 00:00:16,953 +and what is the easiest way to do it. + +6 +00:00:18,690 --> 00:00:20,677 +You will ask yourself the question, + +7 +00:00:20,677 --> 00:00:23,040 +"Should I train a new tokenizer?", + +8 +00:00:23,040 --> 00:00:25,773 +when you plan to train a +new model from scratch. + +9 +00:00:29,520 --> 00:00:34,020 +A trained tokenizer would not +be suitable for your corpus + +10 +00:00:34,020 --> 00:00:37,080 +if your corpus is in a different language, + +11 +00:00:37,080 --> 00:00:42,060 +uses new characters, such as +accents or upper cased letters, + +12 +00:00:42,060 --> 00:00:47,060 +has a specific vocabulary, +for example medical or legal, + +13 +00:00:47,100 --> 00:00:49,050 +or uses a different style, + +14 +00:00:49,050 --> 00:00:51,873 +a language from another +century for example. + +15 +00:00:56,490 --> 00:00:58,320 +If I take the tokenizer trained on + +16 +00:00:58,320 --> 00:01:00,780 +the bert-base-uncased model, + +17 +00:01:00,780 --> 00:01:03,213 +and ignore its normalization step, + +18 +00:01:04,260 --> 00:01:07,650 +then we can see that the +tokenization operation + +19 +00:01:07,650 --> 00:01:09,277 +on the English sentence, + +20 +00:01:09,277 --> 00:01:12,480 +"Here is a sentence +adapted to our tokenizer", + +21 +00:01:12,480 --> 00:01:15,600 +produces a rather +satisfactory list of tokens, + +22 +00:01:15,600 --> 00:01:18,510 +in the sense that this +sentence of eight words + +23 +00:01:18,510 --> 00:01:20,643 +is tokenized into nine tokens. + +24 +00:01:22,920 --> 00:01:26,340 +On the other hand, if I +use this same tokenizer + +25 +00:01:26,340 --> 00:01:29,370 +on a sentence in Bengali, we see that + +26 +00:01:29,370 --> 00:01:33,690 +either a word is divided +into many sub tokens, + +27 +00:01:33,690 --> 00:01:36,270 +or that the tokenizer does not know one of + +28 +00:01:36,270 --> 00:01:39,873 +the unicode characters and +returns only an unknown token. + +29 +00:01:41,220 --> 00:01:44,970 +The fact that a common word +is split into many tokens + +30 +00:01:44,970 --> 00:01:47,910 +can be problematic, +because language models + +31 +00:01:47,910 --> 00:01:51,903 +can only handle a sequence +of tokens of limited length. + +32 +00:01:52,830 --> 00:01:55,830 +A tokenizer that excessively +splits your initial text + +33 +00:01:55,830 --> 00:01:58,503 +may even impact the +performance of your model. + +34 +00:01:59,760 --> 00:02:02,280 +Unknown tokens are also problematic, + +35 +00:02:02,280 --> 00:02:04,530 +because the model will +not be able to extract + +36 +00:02:04,530 --> 00:02:07,563 +any information from the +unknown part of the text. + +37 +00:02:11,430 --> 00:02:13,440 +In this other example, we can see that + +38 +00:02:13,440 --> 00:02:17,100 +the tokenizer replaces +words containing characters + +39 +00:02:17,100 --> 00:02:20,973 +with accents and capital +letters with unknown tokens. + +40 +00:02:22,050 --> 00:02:24,770 +Finally, if we use again this tokenizer + +41 +00:02:24,770 --> 00:02:28,170 +to tokenize medical +vocabulary, we see again that + +42 +00:02:28,170 --> 00:02:31,800 +a single word is divided +into many sub tokens, + +43 +00:02:31,800 --> 00:02:34,803 +four for paracetamol, +and four for pharyngitis. + +44 +00:02:37,110 --> 00:02:39,360 +Most of the tokenizers used by the current + +45 +00:02:39,360 --> 00:02:42,540 +state of the art language +models need to be trained + +46 +00:02:42,540 --> 00:02:45,360 +on a corpus that is +similar to the one used + +47 +00:02:45,360 --> 00:02:47,463 +to pre-train the language model. + +48 +00:02:49,140 --> 00:02:51,150 +This training consists in learning rules + +49 +00:02:51,150 --> 00:02:53,250 +to divide the text into tokens. + +50 +00:02:53,250 --> 00:02:56,160 +And the way to learn +these rules and use them + +51 +00:02:56,160 --> 00:02:58,233 +depends on the chosen tokenizer model. + +52 +00:03:00,630 --> 00:03:04,590 +Thus, to train a new tokenizer, +it is first necessary + +53 +00:03:04,590 --> 00:03:07,653 +to build a training corpus +composed of raw texts. + +54 +00:03:08,910 --> 00:03:12,423 +Then, you have to choose an +architecture for your tokenizer. + +55 +00:03:13,410 --> 00:03:14,763 +Here there are two options. + +56 +00:03:15,900 --> 00:03:19,710 +The simplest is to reuse the +same architecture as the one + +57 +00:03:19,710 --> 00:03:22,863 +of a tokenizer used by +another model already trained. + +58 +00:03:24,210 --> 00:03:25,980 +Otherwise it is also possible + +59 +00:03:25,980 --> 00:03:28,560 +to completely design your tokenizer. + +60 +00:03:28,560 --> 00:03:31,683 +But it requires more +experience and attention. + +61 +00:03:33,750 --> 00:03:36,660 +Once the architecture +is chosen, you can thus + +62 +00:03:36,660 --> 00:03:39,513 +train this tokenizer on +your constituted corpus. + +63 +00:03:40,650 --> 00:03:43,440 +Finally, the last thing that +you need to do is to save + +64 +00:03:43,440 --> 00:03:46,443 +the learned rules to be +able to use this tokenizer. + +65 +00:03:49,530 --> 00:03:51,330 +Let's take an example. + +66 +00:03:51,330 --> 00:03:54,873 +Let's say you want to train +a GPT-2 model on Python code. + +67 +00:03:56,160 --> 00:03:59,640 +Even if the Python code +is usually in English + +68 +00:03:59,640 --> 00:04:02,386 +this type of text is very specific, + +69 +00:04:02,386 --> 00:04:04,473 +and deserves a tokenizer trained on it. + +70 +00:04:05,340 --> 00:04:07,980 +To convince you of this, +we will see at the end + +71 +00:04:07,980 --> 00:04:10,023 +the difference produced on an example. + +72 +00:04:11,400 --> 00:04:13,747 +For that we are going to use the method + +73 +00:04:13,747 --> 00:04:18,240 +"train_new_from_iterator" +that all the fast tokenizers + +74 +00:04:18,240 --> 00:04:20,040 +of the library have and thus, + +75 +00:04:20,040 --> 00:04:22,503 +in particular GPT2TokenizerFast. + +76 +00:04:23,880 --> 00:04:26,100 +This is the simplest method in our case + +77 +00:04:26,100 --> 00:04:28,983 +to have a tokenizer +adapted to Python code. + +78 +00:04:30,180 --> 00:04:34,140 +Remember, the first thing is +to gather a training corpus. + +79 +00:04:34,140 --> 00:04:37,320 +We will use a subpart of +the CodeSearchNet dataset + +80 +00:04:37,320 --> 00:04:39,360 +containing only Python functions + +81 +00:04:39,360 --> 00:04:42,360 +from open source libraries on Github. + +82 +00:04:42,360 --> 00:04:43,650 +It's good timing. + +83 +00:04:43,650 --> 00:04:46,980 +This dataset is known +by the datasets library + +84 +00:04:46,980 --> 00:04:49,203 +and we can load it in two lines of code. + +85 +00:04:50,760 --> 00:04:55,230 +Then, as the "train_new_from_iterator" +method expects + +86 +00:04:55,230 --> 00:04:57,150 +a iterator of lists of texts, + +87 +00:04:57,150 --> 00:04:59,970 +we create the +"get_training_corpus" function, + +88 +00:04:59,970 --> 00:05:01,743 +which will return an iterator. + +89 +00:05:03,870 --> 00:05:05,430 +Now that we have our iterator + +90 +00:05:05,430 --> 00:05:09,630 +on our Python functions +corpus, we can load + +91 +00:05:09,630 --> 00:05:12,351 +the GPT-2 tokenizer architecture. + +92 +00:05:12,351 --> 00:05:16,560 +Here old_tokenizer is not +adapted to our corpus. + +93 +00:05:16,560 --> 00:05:17,700 +But we only need + +94 +00:05:17,700 --> 00:05:20,733 +one more line to train +it on our new corpus. + +95 +00:05:21,780 --> 00:05:24,720 +An argument that is common +to most of the tokenization + +96 +00:05:24,720 --> 00:05:28,980 +algorithms used at the moment +is the size of the vocabulary. + +97 +00:05:28,980 --> 00:05:31,773 +We choose here the value 52,000. + +98 +00:05:32,820 --> 00:05:35,760 +Finally, once the training is finished, + +99 +00:05:35,760 --> 00:05:38,850 +we just have to save our +new tokenizer locally, + +100 +00:05:38,850 --> 00:05:41,730 +or send it to the hub +to be able to reuse it + +101 +00:05:41,730 --> 00:05:43,593 +very easily afterwards. + +102 +00:05:45,270 --> 00:05:48,990 +Finally, let's see together +on an example if it was useful + +103 +00:05:48,990 --> 00:05:53,073 +to re-train a tokenizer +similar to GPT-2 one. + +104 +00:05:55,110 --> 00:05:57,660 +With the original tokenizer of GPT-2 + +105 +00:05:57,660 --> 00:06:00,330 +we see that all spaces are isolated, + +106 +00:06:00,330 --> 00:06:01,920 +and the method name randn, + +107 +00:06:01,920 --> 00:06:04,833 +relatively common in Python +code, is split in two. + +108 +00:06:05,730 --> 00:06:09,060 +With our new tokenizer, +single and double indentations + +109 +00:06:09,060 --> 00:06:10,890 +have been learned and the method randn + +110 +00:06:10,890 --> 00:06:13,770 +is tokenized into one token. + +111 +00:06:13,770 --> 00:06:15,000 +And with that, + +112 +00:06:15,000 --> 00:06:18,123 +you now know how to train +your very own tokenizers now. + +113 +00:06:19,498 --> 00:06:22,165 +(air whooshing) + diff --git a/subtitles/en/43_why-are-fast-tokenizers-called-fast.srt b/subtitles/en/43_why-are-fast-tokenizers-called-fast.srt new file mode 100644 index 000000000..0253c1ace --- /dev/null +++ b/subtitles/en/43_why-are-fast-tokenizers-called-fast.srt @@ -0,0 +1,168 @@ +1 +00:00:00,418 --> 00:00:03,251 +(dramatic whoosh) + +2 +00:00:05,340 --> 00:00:08,460 +- Why are fast tokenizers called fast? + +3 +00:00:08,460 --> 00:00:10,950 +In this video, we'll see +exactly how much faster, + +4 +00:00:10,950 --> 00:00:13,800 +also, so-called fast +organizers are compared + +5 +00:00:13,800 --> 00:00:15,153 +to their slow counterparts. + +6 +00:00:16,200 --> 00:00:19,260 +For this benchmark, we'll +use the GLUE MNLI dataset + +7 +00:00:19,260 --> 00:00:23,160 +which contains 432,000 spells of text. + +8 +00:00:23,160 --> 00:00:25,890 +We'll see how long it takes +for the fast and slow versions + +9 +00:00:25,890 --> 00:00:28,143 +of a BERT tokenizer to process them all. + +10 +00:00:29,670 --> 00:00:31,380 +We define our fast and +slow token organizer + +11 +00:00:31,380 --> 00:00:33,717 +using the AutoTokenizer API. + +12 +00:00:33,717 --> 00:00:37,110 +The fast tokenizer is a +default when available. + +13 +00:00:37,110 --> 00:00:40,443 +So we pass along, use_fast=False +to define the slow one. + +14 +00:00:41,430 --> 00:00:43,530 +In a notebook, we can time the execution + +15 +00:00:43,530 --> 00:00:46,800 +of itself with a time +magic command, like this. + +16 +00:00:46,800 --> 00:00:49,350 +Processing the whole data +set is four times faster + +17 +00:00:49,350 --> 00:00:50,970 +with a fast tokenizer. + +18 +00:00:50,970 --> 00:00:54,000 +That quicker indeed, +but not very impressive. + +19 +00:00:54,000 --> 00:00:55,380 +This is because we passed along the texts + +20 +00:00:55,380 --> 00:00:57,240 +to the tokenizer one at a time. + +21 +00:00:57,240 --> 00:00:59,730 +This is a common mistake +to do with fast organizers + +22 +00:00:59,730 --> 00:01:02,550 +which are backed by Rust, +and thus able to prioritize + +23 +00:01:02,550 --> 00:01:05,370 +the tokenization of multiple texts. + +24 +00:01:05,370 --> 00:01:07,290 +Passing them only one text at a time, + +25 +00:01:07,290 --> 00:01:09,720 +is like sending a cargo +ship between two continents + +26 +00:01:09,720 --> 00:01:13,140 +with just one container, +it's very inefficient. + +27 +00:01:13,140 --> 00:01:15,810 +To unleash the full speed +of our fast tokenizers, + +28 +00:01:15,810 --> 00:01:18,840 +we need to send them batches +of texts, which we can do + +29 +00:01:18,840 --> 00:01:21,423 +with the batched=True +argument of the map method. + +30 +00:01:22,620 --> 00:01:25,950 +Now those are impressive +results, so the fast tokenizer + +31 +00:01:25,950 --> 00:01:28,410 +takes 12 second to process +the dataset that takes four + +32 +00:01:28,410 --> 00:01:30,093 +minute to the slow tokenizer. + +33 +00:01:31,440 --> 00:01:33,510 +Summarizing the results in this table, + +34 +00:01:33,510 --> 00:01:36,630 +you can see why we have +called those tokenizers fast. + +35 +00:01:36,630 --> 00:01:38,760 +And this is only for tokenizing texts. + +36 +00:01:38,760 --> 00:01:40,710 +If you ever need to train a new tokenizer, + +37 +00:01:40,710 --> 00:01:42,523 +they do this very quickly too. + diff --git a/subtitles/en/44_fast-tokenizer-superpowers.srt b/subtitles/en/44_fast-tokenizer-superpowers.srt new file mode 100644 index 000000000..a5453f675 --- /dev/null +++ b/subtitles/en/44_fast-tokenizer-superpowers.srt @@ -0,0 +1,335 @@ +1 +00:00:05,010 --> 00:00:06,270 +- The fast tokenizers + +2 +00:00:06,270 --> 00:00:08,580 +of the Transformers library are fast, + +3 +00:00:08,580 --> 00:00:11,490 +but they also implement features +that will be super useful + +4 +00:00:11,490 --> 00:00:14,536 +for data pre-processing +and post-processing. + +5 +00:00:14,536 --> 00:00:17,239 +Let's have a look at them! + +6 +00:00:17,239 --> 00:00:18,650 +First, let's have a look + +7 +00:00:18,650 --> 00:00:21,690 +at the usual output of a tokenizer. + +8 +00:00:21,690 --> 00:00:24,278 +We get input IDs that correspond to token, + +9 +00:00:24,278 --> 00:00:27,960 +but we lose a lot of +information in the process. + +10 +00:00:27,960 --> 00:00:29,010 +For instance, + +11 +00:00:29,010 --> 00:00:31,856 +here the tokenization is the +same for the two sentences + +12 +00:00:31,856 --> 00:00:35,373 +even if one has several +more spaces than the other. + +13 +00:00:36,300 --> 00:00:39,150 +Just having the input +IDs is thus not enough + +14 +00:00:39,150 --> 00:00:42,330 +if we want to match some +tokens with a span of text, + +15 +00:00:42,330 --> 00:00:43,320 +something we'll need to do + +16 +00:00:43,320 --> 00:00:46,111 +when tackling question +answering, for instance. + +17 +00:00:46,111 --> 00:00:47,592 +It's also difficult to know + +18 +00:00:47,592 --> 00:00:50,850 +when two tokens belong +to the same word or not. + +19 +00:00:50,850 --> 00:00:52,860 +It looks easy when you +just look at the output + +20 +00:00:52,860 --> 00:00:55,650 +of a BERT tokenizer where +we just need to look + +21 +00:00:55,650 --> 00:00:56,779 +for the hash hash. + +22 +00:00:56,779 --> 00:00:59,040 +But other tokenizers have different ways + +23 +00:00:59,040 --> 00:01:00,987 +to tokenize parts of words. + +24 +00:01:00,987 --> 00:01:04,470 +For instance, RoBERTa +adds this special G symbol + +25 +00:01:04,470 --> 00:01:06,491 +to mark the tokens at +the beginning of the word + +26 +00:01:06,491 --> 00:01:09,570 +and T5 uses this special underscore symbol + +27 +00:01:09,570 --> 00:01:11,150 +for the same purpose. + +28 +00:01:11,150 --> 00:01:14,760 +Thankfully, the fast tokenizers +keep track of the word + +29 +00:01:14,760 --> 00:01:16,230 +each token comes from, + +30 +00:01:16,230 --> 00:01:19,571 +with a word_ids method you +can use on their outputs. + +31 +00:01:19,571 --> 00:01:21,870 +The output is not necessarily clear, + +32 +00:01:21,870 --> 00:01:24,076 +but assembled together in +a nice table like this, + +33 +00:01:24,076 --> 00:01:26,853 +we can look at the word +position for each token. + +34 +00:01:27,930 --> 00:01:30,220 +Even better, the fast +tokenizers keep track + +35 +00:01:30,220 --> 00:01:33,198 +of the span of characters +each token comes from, + +36 +00:01:33,198 --> 00:01:35,760 +and we can get them when calling it on one + +37 +00:01:35,760 --> 00:01:37,221 +or several text by adding + +38 +00:01:37,221 --> 00:01:40,470 +the return_offsets_mapping=True argument. + +39 +00:01:40,470 --> 00:01:42,312 +In this instance, we can +see how we jump positions + +40 +00:01:42,312 --> 00:01:45,650 +between the hash hash +token and the super token, + +41 +00:01:45,650 --> 00:01:49,992 +because of the multiple spaces +in the initial sentence. + +42 +00:01:49,992 --> 00:01:52,110 +To enable this, the fast tokenizers + +43 +00:01:52,110 --> 00:01:54,270 +store additional information at each step + +44 +00:01:54,270 --> 00:01:55,440 +of their internal pipeline. + +45 +00:01:55,440 --> 00:01:57,951 +That internal pipeline +consists of normalization, + +46 +00:01:57,951 --> 00:02:00,360 +where we apply some cleaning to the text, + +47 +00:02:00,360 --> 00:02:02,621 +like lower casing or removing the accents; + +48 +00:02:02,621 --> 00:02:04,088 +pre-tokenization, + +49 +00:02:04,088 --> 00:02:06,530 +which is where we split +the texts into words; + +50 +00:02:06,530 --> 00:02:09,360 +then we apply the model of the tokenizer, + +51 +00:02:09,360 --> 00:02:11,725 +which is where the words +are split into tokens, + +52 +00:02:11,725 --> 00:02:13,748 +before finally doing the post processing, + +53 +00:02:13,748 --> 00:02:16,023 +where special tokens are added. + +54 +00:02:17,100 --> 00:02:19,050 +From the beginning to +the end of the pipeline, + +55 +00:02:19,050 --> 00:02:21,390 +the tokenizer keeps track +of each span of text + +56 +00:02:21,390 --> 00:02:23,853 +that corresponds to each +word, then each token. + +57 +00:02:24,990 --> 00:02:26,100 +We'll see how useful it is + +58 +00:02:26,100 --> 00:02:27,990 +when we tackle the following tasks: + +59 +00:02:27,990 --> 00:02:29,549 +when doing masked language modeling + +60 +00:02:29,549 --> 00:02:32,407 +one variation that gets +state-of-the-art results + +61 +00:02:32,407 --> 00:02:35,040 +is to mask all the tokens of a given word + +62 +00:02:35,040 --> 00:02:37,440 +instead of randomly chosen words. + +63 +00:02:37,440 --> 00:02:40,800 +This will require us to +use the word IDs we saw. + +64 +00:02:40,800 --> 00:02:42,329 +When doing token classification, + +65 +00:02:42,329 --> 00:02:45,090 +we'll need to convert the +labels we have on words, + +66 +00:02:45,090 --> 00:02:47,250 +to labels on each tokens. + +67 +00:02:47,250 --> 00:02:48,480 +As for the offset mappings, + +68 +00:02:48,480 --> 00:02:50,610 +it will be super useful +when we need to convert + +69 +00:02:50,610 --> 00:02:53,436 +token positions in a +sentence into a span of text, + +70 +00:02:53,436 --> 00:02:55,800 +which we'll need to +know when we're looking + +71 +00:02:55,800 --> 00:02:56,813 +at question answering + +72 +00:02:56,813 --> 00:02:58,680 +or when grouping the tokens corresponding + +73 +00:02:58,680 --> 00:03:01,023 +to the same entity in +token classification. + +74 +00:03:02,160 --> 00:03:03,450 +To have a look at these tasks, + +75 +00:03:03,450 --> 00:03:04,950 +check the videos linked below! + diff --git a/subtitles/en/45_inside-the-token-classification-pipeline-(pytorch).srt b/subtitles/en/45_inside-the-token-classification-pipeline-(pytorch).srt new file mode 100644 index 000000000..b248a28c8 --- /dev/null +++ b/subtitles/en/45_inside-the-token-classification-pipeline-(pytorch).srt @@ -0,0 +1,333 @@ +1 +00:00:00,076 --> 00:00:01,462 +(title whooshes) + +2 +00:00:01,462 --> 00:00:02,382 +(logo pops) + +3 +00:00:02,382 --> 00:00:05,340 +(title whooshes) + +4 +00:00:05,340 --> 00:00:06,210 +- Let's have a look + +5 +00:00:06,210 --> 00:00:08,283 +inside the token classification pipeline. + +6 +00:00:10,080 --> 00:00:11,580 +In the pipeline video, + +7 +00:00:11,580 --> 00:00:13,320 +we looked at the different applications + +8 +00:00:13,320 --> 00:00:15,960 +the Transformers library +supports out of the box, + +9 +00:00:15,960 --> 00:00:18,780 +one of them being token classification, + +10 +00:00:18,780 --> 00:00:21,810 +for instance predicting +for each word in a sentence + +11 +00:00:21,810 --> 00:00:24,510 +whether they correspond to +a person, an organization + +12 +00:00:24,510 --> 00:00:25,353 +or a location. + +13 +00:00:26,670 --> 00:00:28,920 +We can even group together +the tokens corresponding + +14 +00:00:28,920 --> 00:00:32,040 +to the same entity, for +instance all the tokens + +15 +00:00:32,040 --> 00:00:35,373 +that formed the word Sylvain +here, or Hugging and Face. + +16 +00:00:37,290 --> 00:00:40,230 +The token classification +pipeline works the same way + +17 +00:00:40,230 --> 00:00:42,630 +as the text classification +pipeline we studied + +18 +00:00:42,630 --> 00:00:44,430 +in the previous video. + +19 +00:00:44,430 --> 00:00:45,930 +There are three steps. + +20 +00:00:45,930 --> 00:00:49,623 +The tokenization, the model, +and the postprocessing. + +21 +00:00:50,940 --> 00:00:52,530 +The first two steps are identical + +22 +00:00:52,530 --> 00:00:54,630 +to the text classification pipeline, + +23 +00:00:54,630 --> 00:00:57,300 +except we use an auto +token classification model + +24 +00:00:57,300 --> 00:01:00,150 +instead of a sequence classification one. + +25 +00:01:00,150 --> 00:01:03,720 +We tokenize our text then +feed it to the model. + +26 +00:01:03,720 --> 00:01:05,877 +Instead of getting one number +for each possible label + +27 +00:01:05,877 --> 00:01:08,700 +for the whole sentence, we get one number + +28 +00:01:08,700 --> 00:01:10,770 +for each of the possible nine labels + +29 +00:01:10,770 --> 00:01:13,983 +for every token in the sentence, here 19. + +30 +00:01:15,300 --> 00:01:18,090 +Like all the other models +of the Transformers library, + +31 +00:01:18,090 --> 00:01:19,830 +our model outputs logits, + +32 +00:01:19,830 --> 00:01:23,073 +which we turn into predictions +by using a SoftMax. + +33 +00:01:23,940 --> 00:01:26,190 +We also get the predicted +label for each token + +34 +00:01:26,190 --> 00:01:27,990 +by taking the maximum prediction, + +35 +00:01:27,990 --> 00:01:29,880 +since the SoftMax function +preserves the orders, + +36 +00:01:29,880 --> 00:01:31,200 +we could have done it on the logits + +37 +00:01:31,200 --> 00:01:33,050 +if we had no need of the predictions. + +38 +00:01:33,930 --> 00:01:35,880 +The model config contains +the label mapping + +39 +00:01:35,880 --> 00:01:37,740 +in its id2label field. + +40 +00:01:37,740 --> 00:01:41,430 +Using it, we can map every token +to its corresponding label. + +41 +00:01:41,430 --> 00:01:43,950 +The label, O, correspond to no entity, + +42 +00:01:43,950 --> 00:01:45,985 +which is why we didn't +see it in our results + +43 +00:01:45,985 --> 00:01:47,547 +in the first slide. + +44 +00:01:47,547 --> 00:01:49,440 +On top of the label and the probability, + +45 +00:01:49,440 --> 00:01:51,000 +those results included the start + +46 +00:01:51,000 --> 00:01:53,103 +and end character in the sentence. + +47 +00:01:54,120 --> 00:01:55,380 +We'll need to use the offset mapping + +48 +00:01:55,380 --> 00:01:56,640 +of the tokenizer to get those. + +49 +00:01:56,640 --> 00:01:58,050 +Look at the video linked below + +50 +00:01:58,050 --> 00:02:00,300 +if you don't know about them already. + +51 +00:02:00,300 --> 00:02:02,280 +Then, looping through each token + +52 +00:02:02,280 --> 00:02:04,080 +that has a label distinct from O, + +53 +00:02:04,080 --> 00:02:06,120 +we can build the list of results we got + +54 +00:02:06,120 --> 00:02:07,320 +with our first pipeline. + +55 +00:02:08,460 --> 00:02:10,560 +The last step is to group together tokens + +56 +00:02:10,560 --> 00:02:12,310 +that correspond to the same entity. + +57 +00:02:13,264 --> 00:02:16,140 +This is why we had two labels +for each type of entity, + +58 +00:02:16,140 --> 00:02:18,450 +I-PER and B-PER, for instance. + +59 +00:02:18,450 --> 00:02:20,100 +It allows us to know if a token is + +60 +00:02:20,100 --> 00:02:22,323 +in the same entity as the previous one. + +61 +00:02:23,310 --> 00:02:25,350 +Note, that there are two +ways of labeling used + +62 +00:02:25,350 --> 00:02:26,850 +for token classification. + +63 +00:02:26,850 --> 00:02:29,420 +One, in pink here, uses the B-PER label + +64 +00:02:29,420 --> 00:02:30,810 +at the beginning of each new entity, + +65 +00:02:30,810 --> 00:02:32,760 +but the other, in blue, + +66 +00:02:32,760 --> 00:02:35,340 +only uses it to separate +two adjacent entities + +67 +00:02:35,340 --> 00:02:37,140 +of the same type. + +68 +00:02:37,140 --> 00:02:39,690 +In both cases, we can flag a new entity + +69 +00:02:39,690 --> 00:02:41,940 +each time we see a new label appearing, + +70 +00:02:41,940 --> 00:02:44,730 +either with the I or B prefix, + +71 +00:02:44,730 --> 00:02:47,130 +then take all the following +tokens labeled the same, + +72 +00:02:47,130 --> 00:02:48,870 +with an I-flag. + +73 +00:02:48,870 --> 00:02:51,330 +This, coupled with the offset +mapping to get the start + +74 +00:02:51,330 --> 00:02:54,210 +and end characters allows +us to get the span of texts + +75 +00:02:54,210 --> 00:02:55,233 +for each entity. + +76 +00:02:56,569 --> 00:02:59,532 +(title whooshes) + +77 +00:02:59,532 --> 00:03:01,134 +(title fizzles) + diff --git a/subtitles/en/46_inside-the-token-classification-pipeline-(tensorflow).srt b/subtitles/en/46_inside-the-token-classification-pipeline-(tensorflow).srt new file mode 100644 index 000000000..f628d7744 --- /dev/null +++ b/subtitles/en/46_inside-the-token-classification-pipeline-(tensorflow).srt @@ -0,0 +1,320 @@ +1 +00:00:00,180 --> 00:00:03,013 +(whooshing sound) + +2 +00:00:05,310 --> 00:00:06,143 +- Let's have a look + +3 +00:00:06,143 --> 00:00:08,133 +inside the token classification pipeline. + +4 +00:00:09,780 --> 00:00:11,430 +In the pipeline video, + +5 +00:00:11,430 --> 00:00:13,230 +we looked at the different applications + +6 +00:00:13,230 --> 00:00:16,050 +the Transformers library +supports out of the box. + +7 +00:00:16,050 --> 00:00:18,660 +One of them being token classification. + +8 +00:00:18,660 --> 00:00:22,050 +For instance, predicting +for each word in a sentence, + +9 +00:00:22,050 --> 00:00:23,790 +whether they correspond to a person, + +10 +00:00:23,790 --> 00:00:26,043 +an organization, or location. + +11 +00:00:27,690 --> 00:00:29,250 +We can even group together the tokens + +12 +00:00:29,250 --> 00:00:31,320 +corresponding to the same entity. + +13 +00:00:31,320 --> 00:00:34,890 +For instance, all the tokens +that form the word Sylvain here + +14 +00:00:34,890 --> 00:00:36,423 +or Hugging and Face. + +15 +00:00:37,320 --> 00:00:39,720 +So, token classification pipeline + +16 +00:00:39,720 --> 00:00:42,480 +works the same way as a +text classification pipeline + +17 +00:00:42,480 --> 00:00:44,910 +we studied in a previous video. + +18 +00:00:44,910 --> 00:00:46,500 +There are three steps. + +19 +00:00:46,500 --> 00:00:50,043 +Tokenization, the model, +and the post processing. + +20 +00:00:51,690 --> 00:00:53,190 +The first two steps are identical + +21 +00:00:53,190 --> 00:00:55,230 +to the text classification pipeline, + +22 +00:00:55,230 --> 00:00:58,230 +except we use an auto +token classification model + +23 +00:00:58,230 --> 00:01:00,303 +instead of a sequence classification one. + +24 +00:01:01,560 --> 00:01:04,593 +We tokenize our text, +then feed it to the model. + +25 +00:01:05,580 --> 00:01:08,160 +Instead of getting one number +for each possible level + +26 +00:01:08,160 --> 00:01:09,600 +for the whole sentence, + +27 +00:01:09,600 --> 00:01:12,270 +we get one number for each +of the possible nine levels + +28 +00:01:12,270 --> 00:01:14,250 +for every token in the sentence. + +29 +00:01:14,250 --> 00:01:15,573 +Here, 19. + +30 +00:01:17,070 --> 00:01:19,710 +Like all the other models +of the Transformers library, + +31 +00:01:19,710 --> 00:01:22,560 +our model outputs logits +which we need to turn + +32 +00:01:22,560 --> 00:01:24,663 +into predictions by using a SoftMax. + +33 +00:01:25,830 --> 00:01:28,170 +We also get the predicted +label for each token + +34 +00:01:28,170 --> 00:01:30,063 +by taking the maximum prediction. + +35 +00:01:31,080 --> 00:01:33,540 +Since the softmax function +preserves the order, + +36 +00:01:33,540 --> 00:01:34,980 +we could have done it on the logits + +37 +00:01:34,980 --> 00:01:36,830 +if we had no need of the predictions. + +38 +00:01:37,680 --> 00:01:40,050 +The model config contains +the label mapping + +39 +00:01:40,050 --> 00:01:42,090 +in its id2label field. + +40 +00:01:42,090 --> 00:01:45,600 +Using it, we can map every token +to its corresponding label. + +41 +00:01:45,600 --> 00:01:48,630 +The label O corresponds to "no entity" + +42 +00:01:48,630 --> 00:01:50,460 +which is why we didn't +see it in our results + +43 +00:01:50,460 --> 00:01:52,110 +in the first slide. + +44 +00:01:52,110 --> 00:01:54,150 +On top of the label and the probability, + +45 +00:01:54,150 --> 00:01:55,620 +those results included the start + +46 +00:01:55,620 --> 00:01:57,423 +and end character in the sentence. + +47 +00:01:58,294 --> 00:01:59,880 +We'll need to use the offset mapping + +48 +00:01:59,880 --> 00:02:01,110 +of the tokenizer to get those. + +49 +00:02:01,110 --> 00:02:03,090 +Look at the video link below + +50 +00:02:03,090 --> 00:02:05,340 +if you don't know about them already. + +51 +00:02:05,340 --> 00:02:06,990 +Then, looping through each token + +52 +00:02:06,990 --> 00:02:09,090 +that has a label distinct from O, + +53 +00:02:09,090 --> 00:02:10,590 +we can build the list of results + +54 +00:02:10,590 --> 00:02:12,140 +we got with our first pipeline. + +55 +00:02:13,650 --> 00:02:15,840 +The last step is to group together tokens + +56 +00:02:15,840 --> 00:02:17,640 +that corresponds to the same entity. + +57 +00:02:18,930 --> 00:02:21,540 +This is why we had two labels +for each type of entity, + +58 +00:02:21,540 --> 00:02:23,940 +I-PER and B-PER for instance. + +59 +00:02:23,940 --> 00:02:25,530 +It allows us to know if a token + +60 +00:02:25,530 --> 00:02:27,603 +is in the same entity as a previous one. + +61 +00:02:28,620 --> 00:02:29,850 +Note that there are two ways + +62 +00:02:29,850 --> 00:02:32,490 +of labeling used for token classification. + +63 +00:02:32,490 --> 00:02:35,360 +One, in pink here, uses the B-PER label + +64 +00:02:35,360 --> 00:02:37,530 +at the beginning of each new entity. + +65 +00:02:37,530 --> 00:02:39,990 +But the other in blue only uses it + +66 +00:02:39,990 --> 00:02:42,933 +to separate two adjacent +entities of the same types. + +67 +00:02:44,340 --> 00:02:46,560 +In both cases we can flag a new entity + +68 +00:02:46,560 --> 00:02:49,110 +each time we see a new label appearing, + +69 +00:02:49,110 --> 00:02:51,330 +either with the I or B prefix. + +70 +00:02:51,330 --> 00:02:53,850 +Then, take all the following +tokens labeled the same + +71 +00:02:53,850 --> 00:02:55,470 +with an I-flag. + +72 +00:02:55,470 --> 00:02:57,000 +This, coupled with the offset mapping + +73 +00:02:57,000 --> 00:02:59,010 +to get the start and end characters + +74 +00:02:59,010 --> 00:03:01,560 +allows us to get the span +of texts for each entity. + +75 +00:03:02,869 --> 00:03:05,702 +(whooshing sound) + diff --git a/subtitles/en/47_inside-the-question-answering-pipeline-(pytorch).srt b/subtitles/en/47_inside-the-question-answering-pipeline-(pytorch).srt new file mode 100644 index 000000000..135185eff --- /dev/null +++ b/subtitles/en/47_inside-the-question-answering-pipeline-(pytorch).srt @@ -0,0 +1,342 @@ +1 +00:00:04,230 --> 00:00:07,699 +- Let's have a look inside the +question answering pipeline. + +2 +00:00:07,699 --> 00:00:10,680 +The question answering +pipeline can extracts answers + +3 +00:00:10,680 --> 00:00:14,190 +to questions from a given +context or passage of text, + +4 +00:00:14,190 --> 00:00:16,540 +like this part of the +transformers repo README. + +5 +00:00:18,060 --> 00:00:20,310 +It also works for very long contexts, + +6 +00:00:20,310 --> 00:00:23,850 +even if the answer is at the +very end, like in this example. + +7 +00:00:23,850 --> 00:00:25,400 +In this video, we will see why. + +8 +00:00:26,820 --> 00:00:29,460 +The question answering +pipeline follows the same steps + +9 +00:00:29,460 --> 00:00:31,050 +as the other pipelines: + +10 +00:00:31,050 --> 00:00:34,200 +the question and context are +tokenized as a sentence pair, + +11 +00:00:34,200 --> 00:00:37,955 +fed to the model then some +post-processing is applied. + +12 +00:00:37,955 --> 00:00:41,730 +The tokenization and model +steps should be familiar. + +13 +00:00:41,730 --> 00:00:44,610 +We use the auto class suitable +for question answering + +14 +00:00:44,610 --> 00:00:47,070 +instead of sequence classification, + +15 +00:00:47,070 --> 00:00:49,392 +but one key difference +with text classification + +16 +00:00:49,392 --> 00:00:52,980 +is that our model outputs two +tensors named start logits + +17 +00:00:52,980 --> 00:00:54,570 +and end logits. + +18 +00:00:54,570 --> 00:00:55,830 +Why is that? + +19 +00:00:55,830 --> 00:00:57,930 +Well, this is the way the +model finds the answer + +20 +00:00:57,930 --> 00:00:58,803 +to the question. + +21 +00:00:59,790 --> 00:01:02,130 +First, let's have a look +at the model inputs. + +22 +00:01:02,130 --> 00:01:04,350 +Its numbers associated +with the tokenization + +23 +00:01:04,350 --> 00:01:06,843 +of the question followed by the context + +24 +00:01:06,843 --> 00:01:09,723 +with the usual CLS and SEP special tokens. + +25 +00:01:10,620 --> 00:01:13,320 +The answer is a part of those tokens. + +26 +00:01:13,320 --> 00:01:15,510 +So we ask the model to +predict which token starts + +27 +00:01:15,510 --> 00:01:17,373 +the answer and which ends the answer. + +28 +00:01:18,548 --> 00:01:19,650 +For our two logit outputs, + +29 +00:01:19,650 --> 00:01:22,803 +the theoretical labels are +the pink and purple vectors. + +30 +00:01:24,300 --> 00:01:26,430 +To convert those logits +into probabilities, + +31 +00:01:26,430 --> 00:01:28,436 +we will need to apply a SoftMax, + +32 +00:01:28,436 --> 00:01:30,360 +like in the text classification pipeline. + +33 +00:01:30,360 --> 00:01:33,390 +We just mask the tokens that +are not part of the context + +34 +00:01:33,390 --> 00:01:36,855 +before doing that, leaving +the initial CLS token unmasked + +35 +00:01:36,855 --> 00:01:39,303 +as we use it to predict +an impossible answer. + +36 +00:01:40,267 --> 00:01:43,500 +This is what it looks in terms of code. + +37 +00:01:43,500 --> 00:01:45,870 +We use a large negative +number for the masking, + +38 +00:01:45,870 --> 00:01:48,957 +since its exponential will then be zero. + +39 +00:01:48,957 --> 00:01:50,580 +Now, the probability for each start + +40 +00:01:50,580 --> 00:01:53,550 +and end position corresponding +to a possible answer, + +41 +00:01:53,550 --> 00:01:55,050 +we give a score that is the product + +42 +00:01:55,050 --> 00:01:57,630 +of the start probabilities +and end probabilities + +43 +00:01:57,630 --> 00:01:58,803 +at those positions. + +44 +00:02:00,120 --> 00:02:02,670 +Of course, a start index +greater than an end index + +45 +00:02:02,670 --> 00:02:04,503 +corresponds to an impossible answer. + +46 +00:02:05,430 --> 00:02:07,080 +Here is the code to find the best score + +47 +00:02:07,080 --> 00:02:08,820 +for a possible answer. + +48 +00:02:08,820 --> 00:02:11,430 +Once we have the start and +end positions of the tokens, + +49 +00:02:11,430 --> 00:02:14,130 +we use the offset mappings +provided by our tokenizer + +50 +00:02:14,130 --> 00:02:16,950 +to find the span of characters +in the initial context, + +51 +00:02:16,950 --> 00:02:17,900 +and get our answer. + +52 +00:02:19,470 --> 00:02:21,900 +Now, when the context is +long, it might get truncated + +53 +00:02:21,900 --> 00:02:22,750 +by the tokenizer. + +54 +00:02:23,760 --> 00:02:26,220 +This might result in part +of the answer, or worse, + +55 +00:02:26,220 --> 00:02:28,113 +the whole answer, being truncated. + +56 +00:02:29,100 --> 00:02:31,050 +So we don't discard the truncated tokens + +57 +00:02:31,050 --> 00:02:33,330 +but build new features with them. + +58 +00:02:33,330 --> 00:02:35,994 +Each of those features +contains the question, + +59 +00:02:35,994 --> 00:02:39,240 +then a chunk of text in the context. + +60 +00:02:39,240 --> 00:02:41,430 +If we take disjoint chunks of texts, + +61 +00:02:41,430 --> 00:02:43,530 +we might end up with +the answer being split + +62 +00:02:43,530 --> 00:02:45,330 +between two features. + +63 +00:02:45,330 --> 00:02:48,060 +So instead, we take +overlapping chunks of texts, + +64 +00:02:48,060 --> 00:02:50,640 +to make sure at least one of +the chunks will fully contain + +65 +00:02:50,640 --> 00:02:51,990 +the answer to the question. + +66 +00:02:52,830 --> 00:02:55,260 +The tokenizers do all of +this for us automatically + +67 +00:02:55,260 --> 00:02:58,170 +with the return overflowing tokens option. + +68 +00:02:58,170 --> 00:02:59,700 +The stride argument controls + +69 +00:02:59,700 --> 00:03:02,070 +the number of overlapping tokens. + +70 +00:03:02,070 --> 00:03:04,020 +Here is how our very long +context gets truncated + +71 +00:03:04,020 --> 00:03:05,850 +in two features with some overlap. + +72 +00:03:05,850 --> 00:03:07,950 +By applying the same +post-processing we saw before + +73 +00:03:07,950 --> 00:03:10,636 +for each feature, we get +the answer with a score + +74 +00:03:10,636 --> 00:03:12,453 +for each of them, + +75 +00:03:12,453 --> 00:03:14,910 +and we take the answer with the best score + +76 +00:03:14,910 --> 00:03:16,203 +as a final solution. + diff --git a/subtitles/en/48_inside-the-question-answering-pipeline-(tensorflow).srt b/subtitles/en/48_inside-the-question-answering-pipeline-(tensorflow).srt new file mode 100644 index 000000000..90725ddf5 --- /dev/null +++ b/subtitles/en/48_inside-the-question-answering-pipeline-(tensorflow).srt @@ -0,0 +1,358 @@ +1 +00:00:00,000 --> 00:00:03,417 +(light transition music) + +2 +00:00:05,490 --> 00:00:08,440 +- Let's have a look inside the +question answering pipeline. + +3 +00:00:09,780 --> 00:00:11,370 +The question answering pipeline + +4 +00:00:11,370 --> 00:00:13,710 +can extract answers to questions + +5 +00:00:13,710 --> 00:00:16,020 +from a given context or passage of text + +6 +00:00:16,020 --> 00:00:18,370 +like this part of the +Transformers repo README. + +7 +00:00:19,290 --> 00:00:21,180 +It also works for very long context, + +8 +00:00:21,180 --> 00:00:24,720 +even if the answer is at the +very end, like in this example. + +9 +00:00:24,720 --> 00:00:26,223 +In this video, we'll see why. + +10 +00:00:27,840 --> 00:00:29,310 +The question answering pipeline + +11 +00:00:29,310 --> 00:00:32,130 +follows the same steps +as the other pipelines. + +12 +00:00:32,130 --> 00:00:35,550 +The question and context are +tokenized as a sentence pair, + +13 +00:00:35,550 --> 00:00:38,463 +fed to the model then some +post-processing is applied. + +14 +00:00:39,540 --> 00:00:42,840 +So tokenization and model +steps should be familiar. + +15 +00:00:42,840 --> 00:00:45,000 +We use the auto class suitable +for question answering + +16 +00:00:45,000 --> 00:00:47,460 +instead of sequence classification, + +17 +00:00:47,460 --> 00:00:50,190 +but one key difference +with text classification + +18 +00:00:50,190 --> 00:00:52,380 +is that our model outputs two tensors + +19 +00:00:52,380 --> 00:00:55,230 +named start logits and end logits. + +20 +00:00:55,230 --> 00:00:56,160 +Why is that? + +21 +00:00:56,160 --> 00:00:58,170 +Well, this is the way the +model finds the answer + +22 +00:00:58,170 --> 00:00:59,043 +to the question. + +23 +00:01:00,090 --> 00:01:02,610 +First, let's have a look +at the model inputs. + +24 +00:01:02,610 --> 00:01:04,800 +It's numbers associated +with the tokenization + +25 +00:01:04,800 --> 00:01:05,850 +of the question, + +26 +00:01:05,850 --> 00:01:07,753 +followed by the context + +27 +00:01:07,753 --> 00:01:10,233 +with the usual CLS and SEP special tokens. + +28 +00:01:11,130 --> 00:01:13,203 +The answer is a part of those tokens. + +29 +00:01:14,040 --> 00:01:15,330 +So we ask the model to predict + +30 +00:01:15,330 --> 00:01:17,040 +which token starts the answer + +31 +00:01:17,040 --> 00:01:19,320 +and which ends the answer. + +32 +00:01:19,320 --> 00:01:20,910 +For our two logit outputs, + +33 +00:01:20,910 --> 00:01:23,823 +the theoretical labels are +the pink and purple vectors. + +34 +00:01:24,870 --> 00:01:26,700 +To convert those logits +into probabilities, + +35 +00:01:26,700 --> 00:01:28,596 +we will need to apply a SoftMax, + +36 +00:01:28,596 --> 00:01:31,020 +like in the text classification pipeline. + +37 +00:01:31,020 --> 00:01:32,310 +We just mask the tokens + +38 +00:01:32,310 --> 00:01:35,940 +that are not part of the +context before doing that, + +39 +00:01:35,940 --> 00:01:38,310 +leaving the initial CLS token unmasked + +40 +00:01:38,310 --> 00:01:40,773 +as we use it to predict +an impossible answer. + +41 +00:01:41,940 --> 00:01:44,730 +This is what it looks +like in terms of code. + +42 +00:01:44,730 --> 00:01:47,340 +We use a large negative +number for the masking + +43 +00:01:47,340 --> 00:01:49,533 +since its exponential will then be zero. + +44 +00:01:50,850 --> 00:01:53,160 +Now the probability for +each start and end position + +45 +00:01:53,160 --> 00:01:55,740 +corresponding to a possible answer + +46 +00:01:55,740 --> 00:01:57,540 +will give a score that is a product + +47 +00:01:57,540 --> 00:01:58,680 +of the start probabilities + +48 +00:01:58,680 --> 00:02:00,873 +and end probabilities at those position. + +49 +00:02:01,920 --> 00:02:04,530 +Of course, a start index +greater than an end index + +50 +00:02:04,530 --> 00:02:06,330 +corresponds to an impossible answer. + +51 +00:02:07,744 --> 00:02:09,510 +Here is the code to find the best score + +52 +00:02:09,510 --> 00:02:11,280 +for a possible answer. + +53 +00:02:11,280 --> 00:02:13,830 +Once we have the start and +end position for the tokens, + +54 +00:02:13,830 --> 00:02:16,650 +we use the offset mappings +provided by our tokenizer + +55 +00:02:16,650 --> 00:02:19,710 +to find the span of characters +in the initial context, + +56 +00:02:19,710 --> 00:02:20,810 +and we get our answer. + +57 +00:02:22,080 --> 00:02:23,700 +Now, when the context is long, + +58 +00:02:23,700 --> 00:02:25,977 +it might get truncated by the tokenizer. + +59 +00:02:26,834 --> 00:02:29,790 +This might result in part +of the answer, or worse, + +60 +00:02:29,790 --> 00:02:32,190 +the whole answer, being truncated. + +61 +00:02:32,190 --> 00:02:34,020 +So we don't discard the truncated tokens + +62 +00:02:34,020 --> 00:02:36,420 +but build new features with them. + +63 +00:02:36,420 --> 00:02:39,330 +Each of those features +contains the question, + +64 +00:02:39,330 --> 00:02:42,150 +then a chunk of text in the context. + +65 +00:02:42,150 --> 00:02:44,520 +If we take disjoint chunks of texts, + +66 +00:02:44,520 --> 00:02:45,840 +we might end up with the answer + +67 +00:02:45,840 --> 00:02:47,733 +being split between two features. + +68 +00:02:48,720 --> 00:02:52,050 +So instead, we take +overlapping chunks of text + +69 +00:02:52,050 --> 00:02:53,910 +to make sure at least one of the chunks + +70 +00:02:53,910 --> 00:02:56,940 +will fully contain the +answer to the question. + +71 +00:02:56,940 --> 00:02:59,220 +So, tokenizers does all of +this for us automatically + +72 +00:02:59,220 --> 00:03:01,920 +with the return overflowing tokens option. + +73 +00:03:01,920 --> 00:03:02,753 +The stride argument + +74 +00:03:02,753 --> 00:03:04,830 +controls the number of overlapping tokens. + +75 +00:03:05,940 --> 00:03:07,740 +Here is how our very long context + +76 +00:03:07,740 --> 00:03:10,323 +gets truncated in two +features with some overlap. + +77 +00:03:11,160 --> 00:03:12,720 +By applying the same post-processing + +78 +00:03:12,720 --> 00:03:14,850 +we saw before for each feature, + +79 +00:03:14,850 --> 00:03:17,970 +we get the answer with a +score for each of them, + +80 +00:03:17,970 --> 00:03:19,920 +and we take the answer with the best score + +81 +00:03:19,920 --> 00:03:21,303 +as a final solution. + +82 +00:03:23,089 --> 00:03:26,506 +(light transition music) + diff --git a/subtitles/en/49_what-is-normalization.srt b/subtitles/en/49_what-is-normalization.srt new file mode 100644 index 000000000..8087de3d1 --- /dev/null +++ b/subtitles/en/49_what-is-normalization.srt @@ -0,0 +1,414 @@ +1 +00:00:00,286 --> 00:00:02,869 +(subtle blast) + +2 +00:00:04,694 --> 00:00:07,380 +- In this video, we will see together + +3 +00:00:07,380 --> 00:00:09,930 +what is the normalizer component + +4 +00:00:09,930 --> 00:00:13,023 +that we'd find at the +beginning of each tokenizer. + +5 +00:00:14,550 --> 00:00:16,830 +The normalization operation consists + +6 +00:00:16,830 --> 00:00:19,890 +in applying a succession +of normalization rules + +7 +00:00:19,890 --> 00:00:20,853 +to the raw text. + +8 +00:00:21,870 --> 00:00:25,710 +We choose normalization rules +to remove noise in the text + +9 +00:00:25,710 --> 00:00:27,900 +which seem useless for the learning + +10 +00:00:27,900 --> 00:00:30,363 +and use of our language model. + +11 +00:00:33,090 --> 00:00:37,470 +Let's take a very diverse +sentence with different fonts, + +12 +00:00:37,470 --> 00:00:39,780 +upper and lower case characters, + +13 +00:00:39,780 --> 00:00:43,083 +accents, punctuation and multiple spaces, + +14 +00:00:43,920 --> 00:00:46,683 +to see how several tokenizer normalize it. + +15 +00:00:48,488 --> 00:00:50,730 +The tokenizer from the FNet model + +16 +00:00:50,730 --> 00:00:53,700 +has transformed the +letter with font variants + +17 +00:00:53,700 --> 00:00:57,480 +or circled into their basic version + +18 +00:00:57,480 --> 00:00:59,733 +and has removed the multiple spaces. + +19 +00:01:00,960 --> 00:01:03,960 +And now if we look at the normalization + +20 +00:01:03,960 --> 00:01:05,880 +with Retribert's tokenizer, + +21 +00:01:05,880 --> 00:01:08,010 +we can see that it keeps characters + +22 +00:01:08,010 --> 00:01:12,090 +with several font variants +and keeps the multiple spaces, + +23 +00:01:12,090 --> 00:01:14,223 +but it removes all the accents. + +24 +00:01:16,170 --> 00:01:18,870 +And if we continue to +test this normalization + +25 +00:01:18,870 --> 00:01:23,040 +of many other tokenizers +associated to models + +26 +00:01:23,040 --> 00:01:25,110 +that we can find on the Hub, + +27 +00:01:25,110 --> 00:01:28,833 +we see that they also propose +other kind of normalization. + +28 +00:01:33,900 --> 00:01:35,850 +With the fast tokenizers, + +29 +00:01:35,850 --> 00:01:39,060 +it's very easy to observe +the normalization chosen + +30 +00:01:39,060 --> 00:01:41,193 +for the currently loaded tokenizer. + +31 +00:01:42,330 --> 00:01:46,140 +Indeed, each instance of a fast tokenizer + +32 +00:01:46,140 --> 00:01:48,030 +has an underlying tokenizer + +33 +00:01:48,030 --> 00:01:51,390 +from the HuggingFace +Tokenizers library stored + +34 +00:01:51,390 --> 00:01:53,643 +in the backend_tokenizer attribute. + +35 +00:01:54,690 --> 00:01:58,470 +This object has itself +a normalizer attribute + +36 +00:01:58,470 --> 00:02:01,830 +that we can use thanks to +the normalize_str method + +37 +00:02:01,830 --> 00:02:03,153 +to normalize a string. + +38 +00:02:04,560 --> 00:02:08,700 +It is thus very practical +that this normalization, + +39 +00:02:08,700 --> 00:02:11,070 +which was used at the time of the training + +40 +00:02:11,070 --> 00:02:12,903 +of the tokenizer was saved, + +41 +00:02:13,857 --> 00:02:16,200 +and that it applies automatically + +42 +00:02:16,200 --> 00:02:19,233 +when you ask a trained +tokenizer to tokenize a text. + +43 +00:02:21,000 --> 00:02:25,500 +For example, if we hadn't +included the albert normalizer, + +44 +00:02:25,500 --> 00:02:28,770 +we would have had a lot of unknown tokens + +45 +00:02:28,770 --> 00:02:30,930 +by tokenizing this sentence + +46 +00:02:30,930 --> 00:02:33,213 +with accents and capital letters. + +47 +00:02:35,730 --> 00:02:38,370 +This transformation can +also be undetectable + +48 +00:02:38,370 --> 00:02:40,050 +with a simple print. + +49 +00:02:40,050 --> 00:02:42,810 +Indeed, keep in mind that for a computer, + +50 +00:02:42,810 --> 00:02:45,840 +text is only a succession of 0 and 1, + +51 +00:02:45,840 --> 00:02:47,820 +and it happens that different successions + +52 +00:02:47,820 --> 00:02:51,363 +of 0 and 1 render the +same printed character. + +53 +00:02:52,380 --> 00:02:56,403 +The 0 and 1 go in group +of 8 to form a byte. + +54 +00:02:57,480 --> 00:03:00,690 +The computer must then +decode this sequence of bytes + +55 +00:03:00,690 --> 00:03:02,493 +into a sequence of code points. + +56 +00:03:04,530 --> 00:03:09,530 +In our example, the 2 bytes +is decoded using UTF-8 + +57 +00:03:09,900 --> 00:03:11,403 +into a single code point. + +58 +00:03:12,450 --> 00:03:15,090 +The unicode standard then allows us + +59 +00:03:15,090 --> 00:03:18,191 +to find the character +corresponding to this code point, + +60 +00:03:18,191 --> 00:03:20,283 +the c cedilla. + +61 +00:03:21,499 --> 00:03:23,790 +Let's repeat the same operation + +62 +00:03:23,790 --> 00:03:26,577 +with this new sequence +composed of 3 bytes,. + +63 +00:03:27,420 --> 00:03:30,543 +This time it is transformed +into two code points, + +64 +00:03:31,410 --> 00:03:35,280 +which also correspond to +the c cedilla character. + +65 +00:03:35,280 --> 00:03:36,780 +It is in fact the composition + +66 +00:03:36,780 --> 00:03:39,810 +of the unicode Latin Small Letter C + +67 +00:03:39,810 --> 00:03:42,240 +and the combining cedilla. + +68 +00:03:42,240 --> 00:03:45,000 +But it's annoying because +what appears to us + +69 +00:03:45,000 --> 00:03:46,680 +to be a single character + +70 +00:03:46,680 --> 00:03:49,653 +is not at all the same +thing for the computer. + +71 +00:03:52,470 --> 00:03:57,240 +Fortunately, there are unicode +standardization standards + +72 +00:03:57,240 --> 00:04:02,130 +known as NFC, NFD, NFKC or NFKD + +73 +00:04:02,130 --> 00:04:04,893 +that allow erasing some +of these differences. + +74 +00:04:05,730 --> 00:04:08,223 +These standards are +often used by tokenizers. + +75 +00:04:09,900 --> 00:04:12,090 +On all these previous examples, + +76 +00:04:12,090 --> 00:04:15,510 +even if the normalizations +changed the look of the text, + +77 +00:04:15,510 --> 00:04:17,970 +they did not change the content; + +78 +00:04:17,970 --> 00:04:19,177 +you could still read, + +79 +00:04:19,177 --> 00:04:21,987 +"Hello world, let's +normalize this sentence." + +80 +00:04:22,980 --> 00:04:25,980 +However, you must be aware +that some normalizations + +81 +00:04:25,980 --> 00:04:30,363 +can be very harmful if they are +not adapted to their corpus. + +82 +00:04:31,620 --> 00:04:34,387 +For example, if you take +the French sentence, + +83 +00:04:34,387 --> 00:04:38,790 +"Un pere indigne," which +means "An indignant father," + +84 +00:04:38,790 --> 00:04:42,510 +and normalize it with the +bert-base-uncase tokenizer + +85 +00:04:42,510 --> 00:04:44,313 +which removes the accent, + +86 +00:04:45,150 --> 00:04:48,000 +then the sentence +becomes "Un pere indigne" + +87 +00:04:48,000 --> 00:04:49,707 +which means "An unworthy father". + +88 +00:04:53,460 --> 00:04:56,760 +If you watched this video +to build your own tokenizer, + +89 +00:04:56,760 --> 00:04:59,610 +there are no absolute +rules to choose or not + +90 +00:04:59,610 --> 00:05:02,970 +a normalization for a new tokenizer, + +91 +00:05:02,970 --> 00:05:06,210 +but I advise you to take +the time to select them + +92 +00:05:06,210 --> 00:05:10,743 +so that they do not make you +lose important information. + +93 +00:05:12,296 --> 00:05:14,879 +(subtle blast) + diff --git a/subtitles/en/50_what-is-pre-tokenization.srt b/subtitles/en/50_what-is-pre-tokenization.srt new file mode 100644 index 000000000..840595abc --- /dev/null +++ b/subtitles/en/50_what-is-pre-tokenization.srt @@ -0,0 +1,193 @@ +1 +00:00:05,550 --> 00:00:08,910 +- The tokenization pipeline +involves several steps + +2 +00:00:08,910 --> 00:00:11,073 +that converts raw text into numbers. + +3 +00:00:12,180 --> 00:00:14,280 +In this video, we will see what happens + +4 +00:00:14,280 --> 00:00:16,293 +during the pre-tokenization step. + +5 +00:00:18,390 --> 00:00:22,110 +The pre-tokenization operation +is the operation performed + +6 +00:00:22,110 --> 00:00:24,630 +after the normalization of the text + +7 +00:00:24,630 --> 00:00:27,633 +and before the application of +the tokenization algorithm. + +8 +00:00:29,112 --> 00:00:31,110 +This step consists in applying rules + +9 +00:00:31,110 --> 00:00:32,550 +that do not need to be learned + +10 +00:00:32,550 --> 00:00:34,563 +to perform a first division of the text. + +11 +00:00:38,160 --> 00:00:41,310 +Let's look at how several tokenizers + +12 +00:00:41,310 --> 00:00:43,143 +pre-tokenize in this example. + +13 +00:00:46,200 --> 00:00:50,820 +The gpt2 pre-tokenization +divides the text on spaces + +14 +00:00:50,820 --> 00:00:55,820 +and some punctuation, but +not on the apostrophe. + +15 +00:00:57,750 --> 00:01:01,170 +We also notice that +spaces have been replaced + +16 +00:01:01,170 --> 00:01:03,813 +by capital G with a dot above. + +17 +00:01:07,170 --> 00:01:09,540 +Albert's pre-tokenization divides the text + +18 +00:01:09,540 --> 00:01:11,043 +at the level of spaces, + +19 +00:01:11,970 --> 00:01:15,300 +adds a space at the +beginning of the sentence, + +20 +00:01:15,300 --> 00:01:18,873 +and replaces spaces with +a special underscore. + +21 +00:01:20,580 --> 00:01:24,780 +Finally, Bert's pre-tokenization +divides the text + +22 +00:01:24,780 --> 00:01:28,083 +at the level of punctuation and spaces. + +23 +00:01:28,920 --> 00:01:31,260 +But unlike the previous tokenizers, + +24 +00:01:31,260 --> 00:01:33,780 +spaces are not transformed + +25 +00:01:33,780 --> 00:01:37,293 +and integrated into tokens +produced with this pre-tokenizer. + +26 +00:01:40,080 --> 00:01:42,120 +Through this three example, + +27 +00:01:42,120 --> 00:01:45,330 +we could observe the two +main type of operation + +28 +00:01:45,330 --> 00:01:47,073 +brought by the pre-tokenization; + +29 +00:01:48,420 --> 00:01:49,900 +some change on the text + +30 +00:01:50,820 --> 00:01:54,180 +and the division of the string into tokens + +31 +00:01:54,180 --> 00:01:56,043 +that can be associated to words. + +32 +00:01:59,430 --> 00:02:04,230 +Finally, the backend tokenizer +of the fast tokenizers + +33 +00:02:04,230 --> 00:02:07,680 +also allows to test the +pre-tokenization operation + +34 +00:02:07,680 --> 00:02:11,253 +very easily, thanks to its +pre_tokenize_str method. + +35 +00:02:12,630 --> 00:02:14,970 +We notice that the +output of this operation + +36 +00:02:14,970 --> 00:02:18,450 +is composed of both tokens and offsets, + +37 +00:02:18,450 --> 00:02:21,960 +which allow to link the tokens +to its position in the text + +38 +00:02:21,960 --> 00:02:23,943 +given in input of the method. + +39 +00:02:25,650 --> 00:02:28,860 +This operation defines the largest tokens + +40 +00:02:28,860 --> 00:02:31,740 +that can be produced by the tokenization, + +41 +00:02:31,740 --> 00:02:36,090 +or in those words, the +barriers of the sub-tokens + +42 +00:02:36,090 --> 00:02:37,653 +which will be produced then. + +43 +00:02:40,050 --> 00:02:41,850 +And that's all for the characteristic + +44 +00:02:41,850 --> 00:02:43,203 +of the pre-tokenizers. + diff --git a/subtitles/en/51_byte-pair-encoding-tokenization.srt b/subtitles/en/51_byte-pair-encoding-tokenization.srt new file mode 100644 index 000000000..79c87c176 --- /dev/null +++ b/subtitles/en/51_byte-pair-encoding-tokenization.srt @@ -0,0 +1,377 @@ +1 +00:00:00,125 --> 00:00:05,125 +(air whooshing) + +2 +00:00:05,190 --> 00:00:06,720 +- You are at the right place + +3 +00:00:06,720 --> 00:00:10,464 +if you want to understand +what the Byte Pair Encoding + +4 +00:00:10,464 --> 00:00:13,263 +subword tokenization algorithm is, + +5 +00:00:14,160 --> 00:00:15,505 +how to train it + +6 +00:00:15,505 --> 00:00:17,790 +and how the tokenization of a text is done + +7 +00:00:17,790 --> 00:00:19,107 +with this algorithm. + +8 +00:00:21,417 --> 00:00:22,920 +The BPE algorithm + +9 +00:00:22,920 --> 00:00:26,820 +was initially proposed as a +text compression algorithm + +10 +00:00:26,820 --> 00:00:28,770 +but it is also very well suited + +11 +00:00:28,770 --> 00:00:31,143 +as a tokenizer for your language models. + +12 +00:00:32,910 --> 00:00:34,890 +The idea of BPE is to divide words + +13 +00:00:34,890 --> 00:00:36,933 +into a sequence of 'subword units' + +14 +00:00:38,100 --> 00:00:41,970 +which are units that appear +frequently in a reference corpus + +15 +00:00:41,970 --> 00:00:44,613 +which is, the corpus we used to train it. + +16 +00:00:46,701 --> 00:00:49,083 +How is a BPE tokenizer trained? + +17 +00:00:50,100 --> 00:00:53,340 +First of all, we have to +get a corpus of texts. + +18 +00:00:53,340 --> 00:00:56,940 +We will not train our +tokenizer on this raw text + +19 +00:00:56,940 --> 00:00:59,490 +but we will first normalize it + +20 +00:00:59,490 --> 00:01:00,873 +then pre-tokenize it. + +21 +00:01:01,890 --> 00:01:03,240 +As the pre-tokenization + +22 +00:01:03,240 --> 00:01:05,790 +divides the text into a list of words, + +23 +00:01:05,790 --> 00:01:08,400 +we can represent our corpus in another way + +24 +00:01:08,400 --> 00:01:10,350 +by gathering together the same words + +25 +00:01:10,350 --> 00:01:12,450 +and by maintaining a counter, + +26 +00:01:12,450 --> 00:01:14,223 +here represented in blue. + +27 +00:01:17,340 --> 00:01:19,860 +To understand how the training works, + +28 +00:01:19,860 --> 00:01:23,730 +we consider this toy corpus +composed of the following words: + +29 +00:01:23,730 --> 00:01:28,203 +huggingface, hugging, hug, hugger, etc. + +30 +00:01:29,100 --> 00:01:32,640 +BPE is an algorithm that starts +with an initial vocabulary + +31 +00:01:32,640 --> 00:01:35,583 +and then increases it to the desired size. + +32 +00:01:36,450 --> 00:01:38,460 +To build the initial vocabulary, + +33 +00:01:38,460 --> 00:01:41,550 +we start by separating +each word of the corpus + +34 +00:01:41,550 --> 00:01:44,253 +into a list of elementary +units that compose them, + +35 +00:01:45,210 --> 00:01:47,013 +here, the characters. + +36 +00:01:50,850 --> 00:01:54,310 +We list in our vocabulary all +the characters that appear + +37 +00:01:55,218 --> 00:01:58,053 +and that will constitute +our initial vocabulary. + +38 +00:02:00,420 --> 00:02:02,523 +Let's now see how to increase it. + +39 +00:02:05,520 --> 00:02:08,250 +We return to our split corpus, + +40 +00:02:08,250 --> 00:02:11,340 +we will go through the words one by one + +41 +00:02:11,340 --> 00:02:14,313 +and count all the +occurrences of token pairs. + +42 +00:02:15,450 --> 00:02:18,397 +The first pair is composed +of the token 'h' and 'u', + +43 +00:02:20,130 --> 00:02:23,067 +the second 'u' and 'g', + +44 +00:02:23,067 --> 00:02:26,253 +and we continue like that until +we have the complete list. + +45 +00:02:35,580 --> 00:02:37,724 +Once we know all the pairs + +46 +00:02:37,724 --> 00:02:40,140 +and their frequency of appearance, + +47 +00:02:40,140 --> 00:02:42,940 +we will choose the one that +appears the most frequently. + +48 +00:02:44,220 --> 00:02:47,697 +Here it is the pair composed +of the letters 'l' and 'e'. + +49 +00:02:51,930 --> 00:02:53,590 +We note our first merging rule + +50 +00:02:54,593 --> 00:02:57,243 +and we add the new +token to our vocabulary. + +51 +00:03:00,330 --> 00:03:04,260 +We can then apply this +merging rule to our splits. + +52 +00:03:04,260 --> 00:03:07,350 +You can see that we have +merged all the pairs of tokens + +53 +00:03:07,350 --> 00:03:09,793 +composed of the tokens 'l' and 'e'. + +54 +00:03:14,008 --> 00:03:18,150 +And now, we just have to +reproduce the same steps + +55 +00:03:18,150 --> 00:03:19,353 +with our new splits. + +56 +00:03:21,750 --> 00:03:23,460 +We calculate the frequency of occurrence + +57 +00:03:23,460 --> 00:03:25,023 +of each pair of tokens, + +58 +00:03:27,990 --> 00:03:30,603 +we select the pair with +the highest frequency, + +59 +00:03:32,190 --> 00:03:34,083 +we note it in our merge rules, + +60 +00:03:36,000 --> 00:03:39,360 +we add the new one token the vocabulary + +61 +00:03:39,360 --> 00:03:41,880 +and then we merge all the pairs of tokens + +62 +00:03:41,880 --> 00:03:46,503 +composed of the token 'le' +and 'a' into our splits. + +63 +00:03:50,323 --> 00:03:51,960 +And we can repeat this operation + +64 +00:03:51,960 --> 00:03:54,843 +until we reach the +desired vocabulary size. + +65 +00:04:05,671 --> 00:04:10,671 +Here, we stopped when our +vocabulary reached 21 tokens. + +66 +00:04:11,040 --> 00:04:13,920 +We can see now that +the words of our corpus + +67 +00:04:13,920 --> 00:04:17,040 +are now divided into far fewer tokens + +68 +00:04:17,040 --> 00:04:20,280 +than at the beginning of the training. + +69 +00:04:20,280 --> 00:04:21,720 +And that our algorithm + +70 +00:04:21,720 --> 00:04:24,990 +has learned the radicals 'hug' and 'learn' + +71 +00:04:24,990 --> 00:04:27,537 +and also the verbal ending 'ing'. + +72 +00:04:29,880 --> 00:04:32,160 +Now that we have learned our vocabulary + +73 +00:04:32,160 --> 00:04:35,943 +and merging rules, we +can tokenize new texts. + +74 +00:04:37,980 --> 00:04:39,210 +For example, + +75 +00:04:39,210 --> 00:04:41,160 +if we want to tokenize the word 'hugs', + +76 +00:04:42,960 --> 00:04:46,680 +first we'll divide it +into elementary units + +77 +00:04:46,680 --> 00:04:48,843 +so it became a sequence of characters. + +78 +00:04:50,040 --> 00:04:52,020 +Then, we'll go through our merge rules + +79 +00:04:52,020 --> 00:04:54,690 +until we have one we can apply. + +80 +00:04:54,690 --> 00:04:57,930 +Here, we can merge the +letters 'h' and 'u'. + +81 +00:04:57,930 --> 00:05:01,467 +And here, we can merge 2 tokens +to get the new token 'hug'. + +82 +00:05:02,400 --> 00:05:05,760 +When we get to the end of our merge rules, + +83 +00:05:05,760 --> 00:05:07,563 +the tokenization is finished. + +84 +00:05:10,650 --> 00:05:11,727 +And that's it. + +85 +00:05:12,846 --> 00:05:14,850 +I hope that now the BPE algorithm + +86 +00:05:14,850 --> 00:05:16,413 +has no more secret for you! + +87 +00:05:17,739 --> 00:05:20,406 +(air whooshing) + diff --git a/subtitles/en/52_wordpiece-tokenization.srt b/subtitles/en/52_wordpiece-tokenization.srt new file mode 100644 index 000000000..fb0b3a571 --- /dev/null +++ b/subtitles/en/52_wordpiece-tokenization.srt @@ -0,0 +1,290 @@ +1 +00:00:00,151 --> 00:00:02,818 +(air whooshing) + +2 +00:00:05,520 --> 00:00:08,370 +- Let's see together what +is the training strategy + +3 +00:00:08,370 --> 00:00:11,851 +of the WordPiece algorithm, +and how it performs + +4 +00:00:11,851 --> 00:00:15,150 +the tokenization of a text, once trained. + +5 +00:00:19,351 --> 00:00:23,580 +WordPiece is a tokenization +algorithm introduced by Google. + +6 +00:00:23,580 --> 00:00:25,653 +It is used, for example, by BERT. + +7 +00:00:26,640 --> 00:00:28,020 +To our knowledge, + +8 +00:00:28,020 --> 00:00:31,590 +the code of WordPiece +has not been open source. + +9 +00:00:31,590 --> 00:00:33,510 +So we base our explanations + +10 +00:00:33,510 --> 00:00:36,903 +on our own interpretation +of the published literature. + +11 +00:00:42,090 --> 00:00:44,883 +So, what is the training +strategy of WordPiece? + +12 +00:00:46,200 --> 00:00:48,663 +Similarly to the BPE algorithm, + +13 +00:00:48,663 --> 00:00:52,380 +WordPiece starts by establishing +an initial vocabulary + +14 +00:00:52,380 --> 00:00:54,660 +composed of elementary units, + +15 +00:00:54,660 --> 00:00:58,773 +and then increases this +vocabulary to the desired size. + +16 +00:00:59,970 --> 00:01:01,950 +To build the initial vocabulary, + +17 +00:01:01,950 --> 00:01:04,920 +we divide each word in the training corpus + +18 +00:01:04,920 --> 00:01:07,443 +into the sequence of +letters that make it up. + +19 +00:01:08,430 --> 00:01:11,820 +As you can see, there is a small subtlety. + +20 +00:01:11,820 --> 00:01:14,190 +We add two hashtags in +front of the letters + +21 +00:01:14,190 --> 00:01:16,083 +that do not start a word. + +22 +00:01:17,190 --> 00:01:20,430 +By keeping only one occurrence +per elementary unit, + +23 +00:01:20,430 --> 00:01:23,313 +we now have our initial vocabulary. + +24 +00:01:26,580 --> 00:01:29,823 +We will list all the +existing pairs in our corpus. + +25 +00:01:30,990 --> 00:01:32,640 +Once we have this list, + +26 +00:01:32,640 --> 00:01:35,253 +we will calculate a score +for each of these pairs. + +27 +00:01:36,630 --> 00:01:38,400 +As for the BPE algorithm, + +28 +00:01:38,400 --> 00:01:40,750 +we will select the pair +with the highest score. + +29 +00:01:43,260 --> 00:01:44,340 +Taking for example, + +30 +00:01:44,340 --> 00:01:47,343 +the first pair composed +of the letters H and U. + +31 +00:01:48,510 --> 00:01:51,390 +The score of a pair is +simply equal to the frequency + +32 +00:01:51,390 --> 00:01:54,510 +of appearance of the pair, +divided by the product + +33 +00:01:54,510 --> 00:01:57,330 +of the frequency of +appearance of the first token, + +34 +00:01:57,330 --> 00:02:00,063 +by the frequency of appearance +of the second token. + +35 +00:02:01,260 --> 00:02:05,550 +Thus, at a fixed frequency +of appearance of the pair, + +36 +00:02:05,550 --> 00:02:09,913 +if the subparts of the pair are +very frequent in the corpus, + +37 +00:02:09,913 --> 00:02:11,823 +then this score will be decreased. + +38 +00:02:13,140 --> 00:02:17,460 +In our example, the pair +HU appears four times, + +39 +00:02:17,460 --> 00:02:22,460 +the letter H four times, +and the letter U four times. + +40 +00:02:24,030 --> 00:02:26,733 +This gives us a score of 0.25. + +41 +00:02:28,410 --> 00:02:30,960 +Now that we know how to +calculate this score, + +42 +00:02:30,960 --> 00:02:33,360 +we can do it for all pairs. + +43 +00:02:33,360 --> 00:02:35,217 +We can now add to the vocabulary + +44 +00:02:35,217 --> 00:02:38,973 +the pair with the highest score, +after merging it of course. + +45 +00:02:40,140 --> 00:02:43,863 +And now we can apply this same +fusion to our split corpus. + +46 +00:02:45,780 --> 00:02:47,490 +As you can imagine, + +47 +00:02:47,490 --> 00:02:50,130 +we just have to repeat the same operations + +48 +00:02:50,130 --> 00:02:53,013 +until we have the vocabulary +at the desired size. + +49 +00:02:54,000 --> 00:02:55,800 +Let's look at a few more steps + +50 +00:02:55,800 --> 00:02:58,113 +to see the evolution of our vocabulary, + +51 +00:02:58,957 --> 00:03:01,773 +and also the evolution of +the length of the splits. + +52 +00:03:06,390 --> 00:03:09,180 +And now that we are happy +with our vocabulary, + +53 +00:03:09,180 --> 00:03:12,663 +you are probably wondering how +to use it to tokenize a text. + +54 +00:03:13,830 --> 00:03:17,640 +Let's say we want to tokenize +the word "huggingface". + +55 +00:03:17,640 --> 00:03:20,310 +WordPiece follows these rules. + +56 +00:03:20,310 --> 00:03:22,530 +We will look for the +longest possible token + +57 +00:03:22,530 --> 00:03:24,960 +at the beginning of the word. + +58 +00:03:24,960 --> 00:03:28,920 +Then we start again on the +remaining part of our word, + +59 +00:03:28,920 --> 00:03:31,143 +and so on until we reach the end. + +60 +00:03:32,100 --> 00:03:35,973 +And that's it. Huggingface is +divided into four sub-tokens. + +61 +00:03:37,200 --> 00:03:39,180 +This video is about to end. + +62 +00:03:39,180 --> 00:03:41,370 +I hope it helped you to understand better + +63 +00:03:41,370 --> 00:03:43,653 +what is behind the work, WordPiece. + +64 +00:03:45,114 --> 00:03:47,864 +(air whooshing) + diff --git a/subtitles/en/53_unigram-tokenization.srt b/subtitles/en/53_unigram-tokenization.srt new file mode 100644 index 000000000..cc6bae91e --- /dev/null +++ b/subtitles/en/53_unigram-tokenization.srt @@ -0,0 +1,707 @@ +1 +00:00:00,000 --> 00:00:02,667 +(air whooshing) + +2 +00:00:05,310 --> 00:00:06,420 +- In this video, + +3 +00:00:06,420 --> 00:00:09,881 +we will study together +'the Unigram Language Model + +4 +00:00:09,881 --> 00:00:13,288 +subword tokenization algorithm'. + +5 +00:00:13,288 --> 00:00:15,567 +The overall training strategy + +6 +00:00:15,567 --> 00:00:18,450 +of a Unigram Language Model tokenizer + +7 +00:00:18,450 --> 00:00:21,480 +is to start with a very large vocabulary + +8 +00:00:21,480 --> 00:00:24,240 +and then to remove +tokens at each iteration + +9 +00:00:24,240 --> 00:00:27,300 +until we reach the desired size. + +10 +00:00:27,300 --> 00:00:28,530 +At each iteration, + +11 +00:00:28,530 --> 00:00:30,930 +we will calculate a loss +on our training corpus + +12 +00:00:30,930 --> 00:00:33,480 +thanks to the Unigram model. + +13 +00:00:33,480 --> 00:00:37,470 +As the loss calculation depends +on the available vocabulary, + +14 +00:00:37,470 --> 00:00:40,563 +we can use it to choose how +to reduce the vocabulary. + +15 +00:00:41,550 --> 00:00:43,620 +So we look at the evolution of the loss + +16 +00:00:43,620 --> 00:00:47,103 +by removing in turn each +token from the vocabulary. + +17 +00:00:48,000 --> 00:00:50,430 +We will choose to remove the p-percents + +18 +00:00:50,430 --> 00:00:52,200 +which increase the loss the less. + +19 +00:00:56,310 --> 00:00:57,540 +Before going further + +20 +00:00:57,540 --> 00:01:00,240 +in the explanation of +the training algorithm, + +21 +00:01:00,240 --> 00:01:02,973 +I need to explain what +is an Unigram model. + +22 +00:01:04,183 --> 00:01:06,030 +The Unigram Language Model + +23 +00:01:06,030 --> 00:01:08,493 +is a type of Statistical Language Modem. + +24 +00:01:09,450 --> 00:01:10,980 +A Statistical Language Model + +25 +00:01:10,980 --> 00:01:13,530 +will assign a probability to a text + +26 +00:01:13,530 --> 00:01:18,090 +considering that the text is +in fact a sequence of tokens. + +27 +00:01:18,090 --> 00:01:21,090 +The simplest sequences +of tokens to imagine + +28 +00:01:21,090 --> 00:01:24,753 +are the words that compose the +sentence or the characters. + +29 +00:01:26,130 --> 00:01:28,890 +The particularity of +Unigram Language Model + +30 +00:01:28,890 --> 00:01:32,010 +is that it assumes that +the occurrence of each word + +31 +00:01:32,010 --> 00:01:34,533 +is independent of its previous word. + +32 +00:01:35,400 --> 00:01:37,620 +This assumption allows us to write + +33 +00:01:37,620 --> 00:01:39,570 +that the probability of a text + +34 +00:01:39,570 --> 00:01:42,210 +is equal to the product +of the probabilities + +35 +00:01:42,210 --> 00:01:43,953 +of the tokens that compose it. + +36 +00:01:45,840 --> 00:01:50,220 +It should be noted here that +it is a very simple model + +37 +00:01:50,220 --> 00:01:53,850 +which would not be adapted +to the generation of text + +38 +00:01:53,850 --> 00:01:57,840 +since this model would always +generate the same token, + +39 +00:01:57,840 --> 00:02:00,453 +the one which has the +greatest probability. + +40 +00:02:01,320 --> 00:02:03,360 +Nevertheless, to do tokenization, + +41 +00:02:03,360 --> 00:02:05,790 +this model is very useful to us + +42 +00:02:05,790 --> 00:02:07,440 +because it can be used + +43 +00:02:07,440 --> 00:02:10,893 +to estimate the relative +likelihood of different phrases. + +44 +00:02:14,100 --> 00:02:15,000 +We are now ready + +45 +00:02:15,000 --> 00:02:19,830 +to return to our explanation +of the training algorithm. + +46 +00:02:19,830 --> 00:02:21,690 +Let's say that we have +as a training corpus + +47 +00:02:21,690 --> 00:02:23,880 +with 10 times the word hug, + +48 +00:02:23,880 --> 00:02:25,410 +12 times the word pug, + +49 +00:02:25,410 --> 00:02:27,330 +5 times the word lug, + +50 +00:02:27,330 --> 00:02:28,560 +4 times bug + +51 +00:02:28,560 --> 00:02:29,943 +and 5 times dug. + +52 +00:02:33,120 --> 00:02:34,560 +As said earlier, + +53 +00:02:34,560 --> 00:02:37,473 +the training starts with a big vocabulary. + +54 +00:02:38,460 --> 00:02:41,400 +Obviously, as we are using a toy corpus, + +55 +00:02:41,400 --> 00:02:44,430 +this vocabulary will not be that big + +56 +00:02:44,430 --> 00:02:46,773 +but it should show you the principle. + +57 +00:02:47,610 --> 00:02:51,870 +A first method is to list all +the possible strict substrings + +58 +00:02:51,870 --> 00:02:53,823 +and that's what we'll do here. + +59 +00:02:54,780 --> 00:02:58,170 +We could also have used the BPE algorithm + +60 +00:02:58,170 --> 00:03:00,010 +with a very large vocabulary size + +61 +00:03:01,410 --> 00:03:05,103 +but for now, the strict +substrings are enough. + +62 +00:03:06,990 --> 00:03:09,120 +The training of the Unigram tokenizer + +63 +00:03:09,120 --> 00:03:12,093 +is based on the +Expectation-Maximization method. + +64 +00:03:13,320 --> 00:03:15,120 +At each iteration, + +65 +00:03:15,120 --> 00:03:17,430 +we estimate the +probabilities of the tokens + +66 +00:03:17,430 --> 00:03:18,430 +of the vocabulary + +67 +00:03:20,130 --> 00:03:23,100 +and then we remove the p-percent of tokens + +68 +00:03:23,100 --> 00:03:26,070 +that minimize the loss on the corpus + +69 +00:03:26,070 --> 00:03:28,900 +and which do not belong +to the basic character + +70 +00:03:29,880 --> 00:03:33,150 +as we want to keep in our final vocabulary + +71 +00:03:33,150 --> 00:03:36,693 +the basic characters to be +able to tokenize any word. + +72 +00:03:37,770 --> 00:03:39,641 +Let's go for it! + +73 +00:03:39,641 --> 00:03:42,360 +The probability of a +token simply estimated + +74 +00:03:42,360 --> 00:03:44,760 +by the number of appearance of this token + +75 +00:03:44,760 --> 00:03:46,440 +in our training corpus + +76 +00:03:46,440 --> 00:03:50,133 +divided by the total number of +appearance of all the tokens. + +77 +00:03:51,510 --> 00:03:54,390 +We could use this vocabulary +to tokenize our words + +78 +00:03:54,390 --> 00:03:56,283 +according to the Unigram model. + +79 +00:03:57,150 --> 00:04:00,892 +We will do it together +to understand two things: + +80 +00:04:00,892 --> 00:04:04,110 +how we tokenize a word +with a Unigram model + +81 +00:04:04,110 --> 00:04:07,803 +and how the loss is +calculated on our corpus. + +82 +00:04:09,088 --> 00:04:12,263 +The Unigram LM tokenization +of our text 'Hug' + +83 +00:04:12,263 --> 00:04:15,270 +will be the one with the highest +probability of occurrence + +84 +00:04:15,270 --> 00:04:17,403 +according to our Unigram model. + +85 +00:04:19,080 --> 00:04:21,750 +To find it, the simplest way to proceed + +86 +00:04:21,750 --> 00:04:24,120 +would be to list all the +possible segmentations + +87 +00:04:24,120 --> 00:04:25,800 +of our text 'Hug', + +88 +00:04:25,800 --> 00:04:29,340 +calculate the probability of +each of these segmentations + +89 +00:04:29,340 --> 00:04:32,043 +and then choose the one with +the highest probability. + +90 +00:04:33,210 --> 00:04:34,920 +With the current vocabulary, + +91 +00:04:34,920 --> 00:04:38,640 +two tokenizations get +exactly the same probability. + +92 +00:04:38,640 --> 00:04:40,080 +So we choose one of them + +93 +00:04:40,080 --> 00:04:42,603 +and keep in memory the +associated probability. + +94 +00:04:43,710 --> 00:04:46,380 +To compute the loss on +our training corpus, + +95 +00:04:46,380 --> 00:04:48,570 +we need to tokenize as we just did + +96 +00:04:48,570 --> 00:04:50,673 +all the remaining words in the corpus. + +97 +00:04:52,290 --> 00:04:56,430 +The loss is then the sum over +all the words in the corpus + +98 +00:04:56,430 --> 00:04:58,920 +of the frequency of occurrence of the word + +99 +00:04:58,920 --> 00:05:02,670 +multiplied by the opposite +of the log of the probability + +100 +00:05:02,670 --> 00:05:05,463 +associated with the +tokenization of the word. + +101 +00:05:07,620 --> 00:05:10,803 +We obtain here a loss of 170. + +102 +00:05:13,830 --> 00:05:18,630 +Remember, our initial goal +was to reduce the vocabulary. + +103 +00:05:18,630 --> 00:05:21,870 +To do this, we will remove +a token from the vocabulary + +104 +00:05:21,870 --> 00:05:24,213 +and calculate the associated loss. + +105 +00:05:27,630 --> 00:05:30,627 +Let's remove for example, the token 'ug'. + +106 +00:05:31,920 --> 00:05:35,370 +We notice that the tokenization for 'hug' + +107 +00:05:35,370 --> 00:05:39,990 +with the letter 'h' and the +tuple 'ug' is now impossible. + +108 +00:05:39,990 --> 00:05:42,240 +Nevertheless, as we saw earlier + +109 +00:05:42,240 --> 00:05:45,180 +that two tokenizations +had the same probability, + +110 +00:05:45,180 --> 00:05:47,730 +we can still choose the +remaining tokenization + +111 +00:05:47,730 --> 00:05:51,093 +with a probability of 1.10e-2. + +112 +00:05:52,410 --> 00:05:55,350 +The tokenizations of the +other words of the vocabulary + +113 +00:05:55,350 --> 00:05:57,060 +also remain unchanged. + +114 +00:05:57,060 --> 00:06:00,600 +And finally, even if we +remove the token 'ug' + +115 +00:06:00,600 --> 00:06:05,403 +from our vocabulary the +loss remains equal to 170. + +116 +00:06:06,630 --> 00:06:08,100 +For this first iteration, + +117 +00:06:08,100 --> 00:06:10,080 +if we continue the calculation, + +118 +00:06:10,080 --> 00:06:13,050 +we would notice that we +could remove any token + +119 +00:06:13,050 --> 00:06:16,110 +without it impacting the loss. + +120 +00:06:16,110 --> 00:06:19,200 +We will therefore choose at +random to remove the token 'ug' + +121 +00:06:19,200 --> 00:06:21,843 +before starting a second iteration. + +122 +00:06:24,240 --> 00:06:27,300 +So we estimate again the +probability of each token + +123 +00:06:27,300 --> 00:06:30,630 +before calculating the impact +of each token on the loss. + +124 +00:06:32,160 --> 00:06:33,990 +For example, if we remove now + +125 +00:06:33,990 --> 00:06:36,290 +the token composed of +the letters 'h' and 'u', + +126 +00:06:37,350 --> 00:06:41,013 +there is only one possible +tokenization left for hug. + +127 +00:06:41,940 --> 00:06:44,700 +The tokenization of the +other words of the vocabulary + +128 +00:06:44,700 --> 00:06:45,633 +is not changed. + +129 +00:06:46,560 --> 00:06:47,393 +In the end, + +130 +00:06:47,393 --> 00:06:49,200 +we obtain by removing the token + +131 +00:06:49,200 --> 00:06:52,749 +composed of the letters 'h' +and 'u' from the vocabulary, + +132 +00:06:52,749 --> 00:06:56,430 +a loss of 168. + +133 +00:06:56,430 --> 00:06:59,490 +Finally, to choose which token to remove, + +134 +00:06:59,490 --> 00:07:02,490 +we will for each remaining +token of the vocabulary, + +135 +00:07:02,490 --> 00:07:04,800 +which is not an elementary token, + +136 +00:07:04,800 --> 00:07:07,380 +calculate the associated loss. + +137 +00:07:07,380 --> 00:07:09,843 +Then, compare these losses between them. + +138 +00:07:11,730 --> 00:07:13,800 +The token which we will remove + +139 +00:07:13,800 --> 00:07:17,340 +is the token which impacts +the least the loss, + +140 +00:07:17,340 --> 00:07:18,870 +here the token 'bu'. + +141 +00:07:20,040 --> 00:07:22,380 +We had mentioned at the +beginning of the video + +142 +00:07:22,380 --> 00:07:24,930 +that at each iteration we could remove + +143 +00:07:24,930 --> 00:07:27,093 +p-percent of the tokens by iteration. + +144 +00:07:29,356 --> 00:07:33,000 +The second token that could +be removed at this iteration + +145 +00:07:33,000 --> 00:07:34,317 +is the token 'du'. + +146 +00:07:36,510 --> 00:07:37,920 +And that's it. + +147 +00:07:37,920 --> 00:07:39,720 +We just have to repeat these steps + +148 +00:07:39,720 --> 00:07:43,203 +until we get the vocabulary +of the desired size. + +149 +00:07:45,030 --> 00:07:46,500 +One last thing. + +150 +00:07:46,500 --> 00:07:50,310 +In practice, when we tokenize +a word with a Unigram model, + +151 +00:07:50,310 --> 00:07:53,130 +we don't compute the +set of probabilities of + +152 +00:07:53,130 --> 00:07:55,500 +all the possible splits of a word + +153 +00:07:55,500 --> 00:07:58,770 +before comparing them to keep the best one + +154 +00:07:58,770 --> 00:08:01,440 +but we use the Viterbi algorithm + +155 +00:08:01,440 --> 00:08:04,563 +which is much more efficient way to do it. + +156 +00:08:06,540 --> 00:08:07,680 +And that's it! + +157 +00:08:07,680 --> 00:08:09,270 +I hope that this example + +158 +00:08:09,270 --> 00:08:10,987 +has allowed you to better understand + +159 +00:08:10,987 --> 00:08:12,933 +the Unigram tokenization algorithm. + +160 +00:08:14,355 --> 00:08:17,022 +(air whooshing) + diff --git a/subtitles/en/54_building-a-new-tokenizer.srt b/subtitles/en/54_building-a-new-tokenizer.srt new file mode 100644 index 000000000..73d6ff9c9 --- /dev/null +++ b/subtitles/en/54_building-a-new-tokenizer.srt @@ -0,0 +1,396 @@ +1 +00:00:00,188 --> 00:00:02,855 +(air whooshing) + +2 +00:00:05,400 --> 00:00:07,500 +In this video, we will see how + +3 +00:00:07,500 --> 00:00:11,310 +you can create your own +tokenizer from scratch. + +4 +00:00:11,310 --> 00:00:15,000 +To create your own tokenizer, +you will have to think about + +5 +00:00:15,000 --> 00:00:18,180 +each of the operations +involved in tokenization. + +6 +00:00:18,180 --> 00:00:22,440 +Namely, the normalization, +the pre-tokenization, + +7 +00:00:22,440 --> 00:00:25,233 +the model, the post +processing, and the decoding. + +8 +00:00:26,100 --> 00:00:28,350 +If you don't know what normalization, + +9 +00:00:28,350 --> 00:00:30,900 +pre-tokenization, and the model are, + +10 +00:00:30,900 --> 00:00:34,531 +I advise you to go and see +the videos linked below. + +11 +00:00:34,531 --> 00:00:37,110 +The post processing gathers +all the modifications + +12 +00:00:37,110 --> 00:00:40,860 +that we will carry out +on the tokenized text. + +13 +00:00:40,860 --> 00:00:43,890 +It can include the +addition of special tokens, + +14 +00:00:43,890 --> 00:00:46,290 +the creation of an intention mask, + +15 +00:00:46,290 --> 00:00:48,903 +but also the generation +of a list of token IDs. + +16 +00:00:50,220 --> 00:00:53,487 +The decoding operation +occurs at the very end, + +17 +00:00:53,487 --> 00:00:54,660 +and will allow passing + +18 +00:00:54,660 --> 00:00:57,753 +from the sequence of IDs in a sentence. + +19 +00:00:58,890 --> 00:01:01,800 +For example, you can see that the hashtags + +20 +00:01:01,800 --> 00:01:04,260 +have been removed, and the tokens + +21 +00:01:04,260 --> 00:01:07,323 +composing the word today +have been grouped together. + +22 +00:01:10,860 --> 00:01:13,440 +In a fast tokenizer, all these components + +23 +00:01:13,440 --> 00:01:16,413 +are gathered in the +backend_tokenizer attribute. + +24 +00:01:17,370 --> 00:01:20,070 +As you can see with +this small code snippet, + +25 +00:01:20,070 --> 00:01:22,020 +it is an instance of a tokenizer + +26 +00:01:22,020 --> 00:01:23,763 +from the tokenizers library. + +27 +00:01:25,740 --> 00:01:28,263 +So, to create your own tokenizer, + +28 +00:01:29,970 --> 00:01:31,770 +you will have to follow these steps. + +29 +00:01:33,270 --> 00:01:35,433 +First, create a training dataset. + +30 +00:01:36,690 --> 00:01:39,000 +Second, create and train a tokenizer + +31 +00:01:39,000 --> 00:01:41,700 +with the transformer library. + +32 +00:01:41,700 --> 00:01:46,700 +And third, load this tokenizer +into a transformer tokenizer. + +33 +00:01:49,350 --> 00:01:50,850 +To understand these steps, + +34 +00:01:50,850 --> 00:01:54,573 +I propose that we recreate +a BERT tokenizer together. + +35 +00:01:56,460 --> 00:01:58,893 +The first thing to do +is to create a dataset. + +36 +00:01:59,970 --> 00:02:02,460 +With this code snippet +you can create an iterator + +37 +00:02:02,460 --> 00:02:05,430 +on the dataset wikitext-2-raw-V1, + +38 +00:02:05,430 --> 00:02:08,160 +which is a rather small +dataset in English, + +39 +00:02:08,160 --> 00:02:09,730 +perfect for the example. + +40 +00:02:12,210 --> 00:02:13,920 +We attack here the big part, + +41 +00:02:13,920 --> 00:02:17,373 +the design of our tokenizer +with the tokenizer library. + +42 +00:02:18,750 --> 00:02:22,020 +We start by initializing +a tokenizer instance + +43 +00:02:22,020 --> 00:02:26,133 +with a WordPiece model because +it is the model used by BERT. + +44 +00:02:29,100 --> 00:02:32,190 +Then we can define our normalizer. + +45 +00:02:32,190 --> 00:02:35,891 +We will define it as a +succession of two normalizations + +46 +00:02:35,891 --> 00:02:39,453 +used to clean up characters +not visible in the text. + +47 +00:02:40,590 --> 00:02:43,440 +One lowercasing normalization, + +48 +00:02:43,440 --> 00:02:47,253 +and two last normalizations +used to remove accents. + +49 +00:02:49,500 --> 00:02:53,553 +For the pre-tokenization, we +will chain two pre_tokenizers. + +50 +00:02:54,390 --> 00:02:58,200 +The first one separating the +text at the level of spaces, + +51 +00:02:58,200 --> 00:03:01,533 +and the second one isolating +the punctuation marks. + +52 +00:03:03,360 --> 00:03:06,360 +Now, we can define the +trainer that will allow us + +53 +00:03:06,360 --> 00:03:09,753 +to train the WordPiece model +chosen at the beginning. + +54 +00:03:11,160 --> 00:03:12,600 +To carry out the training, + +55 +00:03:12,600 --> 00:03:14,853 +we will have to choose a vocabulary size. + +56 +00:03:16,050 --> 00:03:17,910 +Here we choose 25,000. + +57 +00:03:17,910 --> 00:03:21,270 +And we also need to +announce the special tokens + +58 +00:03:21,270 --> 00:03:24,663 +that we absolutely want +to add to our vocabulary. + +59 +00:03:29,160 --> 00:03:33,000 +In one line of code, we can +train our WordPiece model + +60 +00:03:33,000 --> 00:03:35,553 +using the iterator we defined earlier. + +61 +00:03:39,060 --> 00:03:42,570 +Once the model has been +trained, we can retrieve + +62 +00:03:42,570 --> 00:03:46,560 +the IDs of the special +class and separation tokens, + +63 +00:03:46,560 --> 00:03:49,413 +because we will need them to +post-process our sequence. + +64 +00:03:50,820 --> 00:03:52,860 +Thanks to the TemplateProcessing class, + +65 +00:03:52,860 --> 00:03:57,210 +we can add the CLS token at +the beginning of each sequence, + +66 +00:03:57,210 --> 00:04:00,120 +and the SEP token at +the end of the sequence, + +67 +00:04:00,120 --> 00:04:03,873 +and between two sentences if +we tokenize a pair of text. + +68 +00:04:07,260 --> 00:04:10,500 +Finally, we just have +to define our decoder, + +69 +00:04:10,500 --> 00:04:12,690 +which will allow us to remove the hashtags + +70 +00:04:12,690 --> 00:04:14,610 +at the beginning of the tokens + +71 +00:04:14,610 --> 00:04:17,193 +that must be reattached +to the previous token. + +72 +00:04:21,300 --> 00:04:22,260 +And there it is. + +73 +00:04:22,260 --> 00:04:25,110 +You have all the necessary lines of code + +74 +00:04:25,110 --> 00:04:29,403 +to define your own tokenizer +with the tokenizer library. + +75 +00:04:30,960 --> 00:04:32,280 +Now that we have a brand new tokenizer + +76 +00:04:32,280 --> 00:04:35,400 +with the tokenizer library, +we just have to load it + +77 +00:04:35,400 --> 00:04:38,463 +into a fast tokenizer from +the transformers library. + +78 +00:04:39,960 --> 00:04:42,630 +Here again, we have several possibilities. + +79 +00:04:42,630 --> 00:04:44,430 +We can load it in the generic class, + +80 +00:04:44,430 --> 00:04:48,330 +PreTrainedTokenizerFast, or +in the BertTokenizerFast class + +81 +00:04:48,330 --> 00:04:52,353 +since we have built a +BERT like tokenizer here. + +82 +00:04:57,000 --> 00:04:59,670 +I really hope this video +has helped you understand + +83 +00:04:59,670 --> 00:05:02,133 +how you can create your own tokenizer, + +84 +00:05:03,178 --> 00:05:06,240 +and that you are ready now to navigate + +85 +00:05:06,240 --> 00:05:08,070 +the tokenizer library documentation + +86 +00:05:08,070 --> 00:05:11,367 +to choose the components for +your brand new tokenizer. + +87 +00:05:12,674 --> 00:05:15,341 +(air whooshing) + diff --git a/subtitles/en/55_data-processing-for-token-classification.srt b/subtitles/en/55_data-processing-for-token-classification.srt new file mode 100644 index 000000000..4ddd9102d --- /dev/null +++ b/subtitles/en/55_data-processing-for-token-classification.srt @@ -0,0 +1,326 @@ +1 +00:00:05,730 --> 00:00:07,590 +- Let's study how to preprocess a dataset + +2 +00:00:07,590 --> 00:00:09,063 +for token classification! + +3 +00:00:10,560 --> 00:00:12,660 +Token classification regroups any task + +4 +00:00:12,660 --> 00:00:14,940 +that can be framed as labeling each word + +5 +00:00:14,940 --> 00:00:17,190 +or token in a sentence, + +6 +00:00:17,190 --> 00:00:19,530 +like identifying the +persons, organizations + +7 +00:00:19,530 --> 00:00:21,093 +and locations for instance. + +8 +00:00:22,170 --> 00:00:25,470 +For our example, we will +use the Conll dataset, + +9 +00:00:25,470 --> 00:00:27,900 +in which we remove columns we won't use + +10 +00:00:27,900 --> 00:00:29,940 +and rename the other +ones to get to a dataset + +11 +00:00:29,940 --> 00:00:32,943 +with just two columns, words and labels. + +12 +00:00:34,200 --> 00:00:36,750 +If you have your own dataset +for token classification, + +13 +00:00:36,750 --> 00:00:39,870 +just make sure you clean your +data to get to the same point, + +14 +00:00:39,870 --> 00:00:43,290 +with one column containing +words as list of strings + +15 +00:00:43,290 --> 00:00:45,540 +and another containing labels as integers + +16 +00:00:45,540 --> 00:00:48,513 +spanning from zero to your +number of labels minus one. + +17 +00:00:49,740 --> 00:00:52,290 +Make sure you have your +label names stored somewhere. + +18 +00:00:52,290 --> 00:00:54,810 +Here we get them from +the dataset features. + +19 +00:00:54,810 --> 00:00:57,660 +So you are able to map the +integers to some real labels + +20 +00:00:57,660 --> 00:00:58,960 +when inspecting your data. + +21 +00:01:00,690 --> 00:01:03,510 +Here we are doing named +entity recognitions, + +22 +00:01:03,510 --> 00:01:05,430 +so ours labels are either O + +23 +00:01:05,430 --> 00:01:08,310 +for words that do not +belong to any entity. + +24 +00:01:08,310 --> 00:01:13,310 +LOC for location, PER for +person, ORG for organization + +25 +00:01:13,860 --> 00:01:15,603 +and MISC for miscellaneous. + +26 +00:01:16,650 --> 00:01:18,540 +Each label has two versions. + +27 +00:01:18,540 --> 00:01:21,960 +The B labels indicate a +word that begins an entity + +28 +00:01:21,960 --> 00:01:25,503 +while the I labels indicate a +word that is inside an entity. + +29 +00:01:27,180 --> 00:01:28,830 +The first step to preprocess our data + +30 +00:01:28,830 --> 00:01:30,660 +is to tokenize the words. + +31 +00:01:30,660 --> 00:01:33,120 +This is very easily +done with the tokenizer. + +32 +00:01:33,120 --> 00:01:35,370 +We just have to tell it we +have pre-tokenized the data + +33 +00:01:35,370 --> 00:01:37,503 +with the flag is_split_into_words=True. + +34 +00:01:38,520 --> 00:01:40,380 +Then comes the hard part. + +35 +00:01:40,380 --> 00:01:42,360 +Since we have added special tokens + +36 +00:01:42,360 --> 00:01:45,270 +and each word may have been +split into several tokens, + +37 +00:01:45,270 --> 00:01:48,090 +our labels won't match the tokens anymore. + +38 +00:01:48,090 --> 00:01:50,670 +This is where the word IDs +our fast tokenizer provides + +39 +00:01:50,670 --> 00:01:51,723 +come to the rescue. + +40 +00:01:53,040 --> 00:01:55,500 +They match each token to +the word it belongs to + +41 +00:01:55,500 --> 00:01:58,470 +which allows us to map +each token to its label. + +42 +00:01:58,470 --> 00:01:59,303 +We just have to make sure + +43 +00:01:59,303 --> 00:02:01,710 +we change the B labels +to their I counterparts + +44 +00:02:01,710 --> 00:02:03,450 +for tokens that are inside + +45 +00:02:03,450 --> 00:02:05,433 +but not at the beginning of a word. + +46 +00:02:06,330 --> 00:02:09,120 +The special tokens get a label of -100, + +47 +00:02:09,120 --> 00:02:11,070 +which is how we tell the +Transformer loss functions + +48 +00:02:11,070 --> 00:02:14,607 +to ignore them when computing the loss. + +49 +00:02:14,607 --> 00:02:16,890 +The code is then pretty straightforward. + +50 +00:02:16,890 --> 00:02:18,660 +We write a function that shifts the labels + +51 +00:02:18,660 --> 00:02:21,840 +for tokens that are inside a +word that you can customize + +52 +00:02:21,840 --> 00:02:24,490 +and use it when generating +the labels for each token. + +53 +00:02:25,830 --> 00:02:28,260 +Once that function to create +our labels is written, + +54 +00:02:28,260 --> 00:02:31,920 +we can preprocess the whole +dataset using the map function. + +55 +00:02:31,920 --> 00:02:33,360 +With the option batched=True, + +56 +00:02:33,360 --> 00:02:35,793 +we unleash the speed +of out fast tokenizers. + +57 +00:02:37,110 --> 00:02:40,350 +The last problem comes when +we need to create a batch. + +58 +00:02:40,350 --> 00:02:42,150 +Unless you changed the +preprocessing function + +59 +00:02:42,150 --> 00:02:43,890 +to apply some fixed padding, + +60 +00:02:43,890 --> 00:02:45,900 +we will get sentences of various lengths, + +61 +00:02:45,900 --> 00:02:47,900 +which we need to pad to the same length. + +62 +00:02:48,930 --> 00:02:50,730 +The padding needs to be +applied to the inputs + +63 +00:02:50,730 --> 00:02:51,900 +as well as the labels, + +64 +00:02:51,900 --> 00:02:53,950 +since we should have one label per token. + +65 +00:02:54,870 --> 00:02:58,260 +Again, -100 indicates the +labels that should be ignored + +66 +00:02:58,260 --> 00:02:59,510 +for the loss computation. + +67 +00:03:00,420 --> 00:03:01,560 +This is all done for us + +68 +00:03:01,560 --> 00:03:04,050 +by the DataCollatorForTokenClassification, + +69 +00:03:04,050 --> 00:03:06,740 +which you can use in +PyTorch or TensorFlow. + +70 +00:03:06,740 --> 00:03:08,880 +With all of this, you are +either ready to send your data + +71 +00:03:08,880 --> 00:03:11,190 +and this data collator to the Trainer, + +72 +00:03:11,190 --> 00:03:13,320 +or use the to_tf_dataset method + +73 +00:03:13,320 --> 00:03:15,333 +and the fit method of your model. + diff --git a/subtitles/en/56_data-processing-for-masked-language-modeling.srt b/subtitles/en/56_data-processing-for-masked-language-modeling.srt new file mode 100644 index 000000000..a411f55af --- /dev/null +++ b/subtitles/en/56_data-processing-for-masked-language-modeling.srt @@ -0,0 +1,249 @@ +1 +00:00:00,000 --> 00:00:02,333 +(whooshing) + +2 +00:00:05,250 --> 00:00:07,230 +- Let's see how we can preprocess our data + +3 +00:00:07,230 --> 00:00:08,703 +for masked language modeling. + +4 +00:00:10,230 --> 00:00:12,570 +As a reminder, masked language modeling + +5 +00:00:12,570 --> 00:00:15,333 +is when a model needs to fill +the blanks in a sentence. + +6 +00:00:16,530 --> 00:00:19,650 +To do this, you just +need texts, no labels, + +7 +00:00:19,650 --> 00:00:22,200 +as this is a self-supervised problem. + +8 +00:00:22,200 --> 00:00:23,670 +To apply this on your own data, + +9 +00:00:23,670 --> 00:00:25,740 +just make sure you have +all your texts gathered + +10 +00:00:25,740 --> 00:00:27,603 +in one column of your dataset. + +11 +00:00:28,440 --> 00:00:30,480 +Before we start randomly masking things, + +12 +00:00:30,480 --> 00:00:33,090 +we will need to somehow make +all those texts the same length + +13 +00:00:33,090 --> 00:00:34,263 +to batch them together. + +14 +00:00:35,640 --> 00:00:38,490 +The first way to make all +the texts the same length + +15 +00:00:38,490 --> 00:00:40,590 +is the one we used in text classification. + +16 +00:00:41,430 --> 00:00:44,163 +Let's pad the short texts +and truncate the long ones. + +17 +00:00:45,030 --> 00:00:45,900 +As we have seen + +18 +00:00:45,900 --> 00:00:48,690 +when we processed data +for text classification, + +19 +00:00:48,690 --> 00:00:49,923 +this is all done by our tokenizer + +20 +00:00:49,923 --> 00:00:53,130 +with the right options for +padding and truncation. + +21 +00:00:53,130 --> 00:00:56,100 +This will however make +us lose a lot of texts + +22 +00:00:56,100 --> 00:00:58,620 +if the examples in our +dataset are very long, + +23 +00:00:58,620 --> 00:01:00,960 +compared to the context length we picked. + +24 +00:01:00,960 --> 00:01:03,393 +Here, all the portion in gray is lost. + +25 +00:01:04,410 --> 00:01:06,660 +This is why a second way +to generate samples of text + +26 +00:01:06,660 --> 00:01:08,820 +with the same length is to chunk our text + +27 +00:01:08,820 --> 00:01:10,560 +in pieces of context lengths, + +28 +00:01:10,560 --> 00:01:14,010 +instead of discarding everything +after the first chunk. + +29 +00:01:14,010 --> 00:01:15,420 +There will probably be a remainder + +30 +00:01:15,420 --> 00:01:17,700 +of length smaller than the context size, + +31 +00:01:17,700 --> 00:01:20,493 +which we can choose to +keep and pad or ignore. + +32 +00:01:21,570 --> 00:01:23,790 +Here is how we can apply this in practice, + +33 +00:01:23,790 --> 00:01:26,460 +by just adding the return +overflowing tokens option + +34 +00:01:26,460 --> 00:01:28,200 +in our tokenizer call. + +35 +00:01:28,200 --> 00:01:30,243 +Note how this gives us a bigger dataset! + +36 +00:01:31,560 --> 00:01:34,260 +This second way of chunking +is ideal if all your texts + +37 +00:01:34,260 --> 00:01:36,270 +are very long, but it won't work + +38 +00:01:36,270 --> 00:01:39,900 +as nicely if you have a variety +of lengths in the texts. + +39 +00:01:39,900 --> 00:01:41,040 +In this case, + +40 +00:01:41,040 --> 00:01:44,280 +the best option is to concatenate +all your tokenized texts + +41 +00:01:44,280 --> 00:01:46,560 +in one big stream, with a special tokens + +42 +00:01:46,560 --> 00:01:49,800 +to indicate when you pass from +one document to the other, + +43 +00:01:49,800 --> 00:01:52,503 +and only then split the +big stream into chunks. + +44 +00:01:53,760 --> 00:01:55,620 +Here is how it can be done with code, + +45 +00:01:55,620 --> 00:01:58,230 +with one loop to concatenate all the texts + +46 +00:01:58,230 --> 00:01:59,673 +and another one to chunk it. + +47 +00:02:00,780 --> 00:02:02,850 +Notice how it reduces +the number of samples + +48 +00:02:02,850 --> 00:02:04,230 +in our dataset here, + +49 +00:02:04,230 --> 00:02:06,580 +there must have been +quite a few short entries! + +50 +00:02:07,710 --> 00:02:11,130 +Once this is done, the +masking is the easy part. + +51 +00:02:11,130 --> 00:02:13,400 +There is a data collator +designed specifically for this + +52 +00:02:13,400 --> 00:02:15,540 +in the Transformers library. + +53 +00:02:15,540 --> 00:02:17,700 +You can use it directly in the Trainer, + +54 +00:02:17,700 --> 00:02:20,400 +or when converting your +datasets to tensorflow datasets + +55 +00:02:20,400 --> 00:02:23,703 +before doing Keras.fit, with +the to_tf_dataset method. + +56 +00:02:24,992 --> 00:02:27,325 +(whooshing) + diff --git a/subtitles/en/57_what-is-perplexity.srt b/subtitles/en/57_what-is-perplexity.srt new file mode 100644 index 000000000..cdc14eb17 --- /dev/null +++ b/subtitles/en/57_what-is-perplexity.srt @@ -0,0 +1,231 @@ +1 +00:00:00,095 --> 00:00:01,582 +(screen whooshing) + +2 +00:00:01,582 --> 00:00:02,659 +(sticker popping) + +3 +00:00:02,659 --> 00:00:05,379 +(screen whooshing) + +4 +00:00:05,379 --> 00:00:06,720 +- In this video, we take a look + +5 +00:00:06,720 --> 00:00:09,483 +at the mysterious sounding +metric called perplexity. + +6 +00:00:11,070 --> 00:00:12,630 +You might have encountered perplexity + +7 +00:00:12,630 --> 00:00:14,970 +when reading about generative models. + +8 +00:00:14,970 --> 00:00:16,680 +You can see two examples here, + +9 +00:00:16,680 --> 00:00:18,577 +one from the original transformer paper, + +10 +00:00:18,577 --> 00:00:19,950 +"Attention is all you need," + +11 +00:00:19,950 --> 00:00:23,340 +and the other one from the +more recent GPT-2 paper. + +12 +00:00:23,340 --> 00:00:25,740 +Perplexity is a common metric +to measure the performance + +13 +00:00:25,740 --> 00:00:27,150 +of language models. + +14 +00:00:27,150 --> 00:00:30,000 +The smaller its value, the +better the performance. + +15 +00:00:30,000 --> 00:00:32,950 +But what does it actually mean +and how can we calculate it? + +16 +00:00:34,440 --> 00:00:36,180 +A very common quantity in machine learning + +17 +00:00:36,180 --> 00:00:37,650 +is the likelihood. + +18 +00:00:37,650 --> 00:00:39,240 +We can calculate the likelihood + +19 +00:00:39,240 --> 00:00:42,390 +as the product of each +token's probability. + +20 +00:00:42,390 --> 00:00:44,730 +What this means is that for each token, + +21 +00:00:44,730 --> 00:00:47,340 +we use the language model +to predict its probability + +22 +00:00:47,340 --> 00:00:49,560 +based on the previous tokens. + +23 +00:00:49,560 --> 00:00:52,050 +In the end, we multiply all probabilities + +24 +00:00:52,050 --> 00:00:53,253 +to get the likelihood. + +25 +00:00:55,892 --> 00:00:57,000 +With the likelihood, + +26 +00:00:57,000 --> 00:00:59,340 +we can calculate another +important quantity, + +27 +00:00:59,340 --> 00:01:01,200 +the cross-entropy. + +28 +00:01:01,200 --> 00:01:03,450 +You might have already +heard about cross-entropy + +29 +00:01:03,450 --> 00:01:05,670 +when looking at loss function. + +30 +00:01:05,670 --> 00:01:09,210 +It is often used as a loss +function in classification. + +31 +00:01:09,210 --> 00:01:11,610 +In language modeling, we +predict the next token + +32 +00:01:11,610 --> 00:01:12,930 +based on the previous token, + +33 +00:01:12,930 --> 00:01:15,810 +which is also a classification task. + +34 +00:01:15,810 --> 00:01:17,340 +Therefore, if we want to calculate + +35 +00:01:17,340 --> 00:01:19,290 +the cross-entropy of an example, + +36 +00:01:19,290 --> 00:01:21,090 +we can simply pass it to the model + +37 +00:01:21,090 --> 00:01:23,580 +with its inputs as labels. + +38 +00:01:23,580 --> 00:01:26,433 +The loss then corresponds +to the cross-entropy. + +39 +00:01:29,130 --> 00:01:31,110 +We are now only a single operation away + +40 +00:01:31,110 --> 00:01:33,510 +from calculating the perplexity. + +41 +00:01:33,510 --> 00:01:37,710 +By exponentiating the cross-entropy, +we get the perplexity. + +42 +00:01:37,710 --> 00:01:40,260 +So you see that the +perplexity is closely related + +43 +00:01:40,260 --> 00:01:41,163 +to the loss. + +44 +00:01:42,060 --> 00:01:43,380 +Plugging in previous results + +45 +00:01:43,380 --> 00:01:47,010 +shows that this is +equivalent to exponentiating + +46 +00:01:47,010 --> 00:01:51,033 +the negative average lock +probability of each token. + +47 +00:01:52,050 --> 00:01:54,630 +Keep in mind that the +loss is only a weak proxy + +48 +00:01:54,630 --> 00:01:57,360 +for a model's ability +to generate quality text + +49 +00:01:57,360 --> 00:02:00,510 +and the same is true for perplexity. + +50 +00:02:00,510 --> 00:02:02,550 +For this reason, one +usually also calculates + +51 +00:02:02,550 --> 00:02:03,840 +more sophisticated metrics + +52 +00:02:03,840 --> 00:02:07,413 +such as BLEU or ROUGE on generative tasks. + +53 +00:02:08,551 --> 00:02:11,468 +(screen whooshing) + diff --git a/subtitles/en/58_what-is-domain-adaptation.srt b/subtitles/en/58_what-is-domain-adaptation.srt new file mode 100644 index 000000000..5d4f8c9d4 --- /dev/null +++ b/subtitles/en/58_what-is-domain-adaptation.srt @@ -0,0 +1,185 @@ +1 +00:00:00,000 --> 00:00:01,402 +(air whooshing) + +2 +00:00:01,402 --> 00:00:02,720 +(smiley snapping) + +3 +00:00:02,720 --> 00:00:05,910 +(air whooshing) + +4 +00:00:05,910 --> 00:00:07,923 +- What is domain adaptation? + +5 +00:00:09,540 --> 00:00:12,540 +When fine-tuning a pre-trained +model on a new dataset, + +6 +00:00:12,540 --> 00:00:15,480 +the fine-tuned model we +obtain will make predictions + +7 +00:00:15,480 --> 00:00:17,433 +that are attuned to this new dataset. + +8 +00:00:18,840 --> 00:00:21,840 +When the two models are +trained with the same task, + +9 +00:00:21,840 --> 00:00:25,320 +we can then compare their +predictions on the same input. + +10 +00:00:25,320 --> 00:00:27,870 +The predictions of the two +models will be different + +11 +00:00:27,870 --> 00:00:29,790 +in a way that reflects the differences + +12 +00:00:29,790 --> 00:00:31,680 +between the two datasets, + +13 +00:00:31,680 --> 00:00:34,053 +a phenomenon we call domain adaptation. + +14 +00:00:35,310 --> 00:00:38,640 +Let's look at an example +with masked language modeling + +15 +00:00:38,640 --> 00:00:41,910 +by comparing the outputs of the +pre-trained DistilBERT model + +16 +00:00:41,910 --> 00:00:43,080 +with the version fine-tuned + +17 +00:00:43,080 --> 00:00:45,273 +in chapter 7 of the course, linked below. + +18 +00:00:46,500 --> 00:00:49,140 +The pre-trained model +makes generic predictions, + +19 +00:00:49,140 --> 00:00:50,580 +whereas the fine-tuned model + +20 +00:00:50,580 --> 00:00:53,253 +has its first two +predictions linked to cinema. + +21 +00:00:54,390 --> 00:00:57,210 +Since it was fine-tuned on +a movie reviews dataset, + +22 +00:00:57,210 --> 00:00:58,680 +it's perfectly normal to see + +23 +00:00:58,680 --> 00:01:01,440 +it adapted its suggestions like this. + +24 +00:01:01,440 --> 00:01:03,090 +Notice how it keeps the same prediction + +25 +00:01:03,090 --> 00:01:05,220 +as the pre-trained model afterward. + +26 +00:01:05,220 --> 00:01:08,100 +Even if the fine-tuned model +adapts to the new dataset, + +27 +00:01:08,100 --> 00:01:10,450 +it's not forgetting what +it was pre-trained on. + +28 +00:01:11,490 --> 00:01:14,220 +This is another example +on a translation task. + +29 +00:01:14,220 --> 00:01:17,310 +On top, we use a pre-trained +French/English model, + +30 +00:01:17,310 --> 00:01:21,330 +and at the bottom, the version +we fine-tuned in chapter 7. + +31 +00:01:21,330 --> 00:01:23,610 +The top model is pre-trained +on lots of texts, + +32 +00:01:23,610 --> 00:01:25,170 +and leaves technical English terms, + +33 +00:01:25,170 --> 00:01:28,350 +like plugin and email, +unchanged in the translation. + +34 +00:01:28,350 --> 00:01:31,350 +Both are perfectly +understood by French people. + +35 +00:01:31,350 --> 00:01:33,780 +The dataset picked for the +fine-tuning is a dataset + +36 +00:01:33,780 --> 00:01:36,660 +of technical texts where +special attention was picked + +37 +00:01:36,660 --> 00:01:39,150 +on translating everything in French. + +38 +00:01:39,150 --> 00:01:42,090 +As a result, the fine-tuned +model picked that habit + +39 +00:01:42,090 --> 00:01:44,193 +and translated both plugin and email. + +40 +00:01:45,942 --> 00:01:49,181 +(air whooshing) + +41 +00:01:49,181 --> 00:01:50,592 +(air whooshing) + diff --git a/subtitles/en/59_data-processing-for-translation.srt b/subtitles/en/59_data-processing-for-translation.srt new file mode 100644 index 000000000..aaddd1f56 --- /dev/null +++ b/subtitles/en/59_data-processing-for-translation.srt @@ -0,0 +1,247 @@ +1 +00:00:00,449 --> 00:00:01,559 +(air whooshing) + +2 +00:00:01,559 --> 00:00:02,767 +(logo popping) + +3 +00:00:02,767 --> 00:00:05,670 +(metal sliding) + +4 +00:00:05,670 --> 00:00:08,470 +- Let's see how to preprocess +a dataset for translation. + +5 +00:00:09,540 --> 00:00:12,420 +This is a task of well +translating a sentence + +6 +00:00:12,420 --> 00:00:14,310 +in another language. + +7 +00:00:14,310 --> 00:00:17,100 +This video will focus on how +to preprocess your dataset + +8 +00:00:17,100 --> 00:00:19,950 +once you've managed to put +it in the following format. + +9 +00:00:19,950 --> 00:00:23,730 +One column for input texts +and one for the target texts. + +10 +00:00:23,730 --> 00:00:25,980 +Here is how we can achieve +this with the Datasets library + +11 +00:00:25,980 --> 00:00:29,643 +and the KDE4 dataset for +English to French translation. + +12 +00:00:30,870 --> 00:00:33,240 +As long as you manage to have +your data look like this, + +13 +00:00:33,240 --> 00:00:35,440 +you should be able to +follow the same steps. + +14 +00:00:36,630 --> 00:00:39,210 +For once, our labels are not integers + +15 +00:00:39,210 --> 00:00:42,210 +corresponding to some +classes, but plain texts. + +16 +00:00:42,210 --> 00:00:45,810 +We will thus need to tokenize +them, like our inputs. + +17 +00:00:45,810 --> 00:00:47,370 +There is a trap there though, + +18 +00:00:47,370 --> 00:00:49,890 +as if you tokenize your +targets like your inputs, + +19 +00:00:49,890 --> 00:00:51,690 +you will hit a problem. + +20 +00:00:51,690 --> 00:00:54,090 +Even if you don't speak +French, you might notice + +21 +00:00:54,090 --> 00:00:57,270 +some weird things in the +tokenization of the targets. + +22 +00:00:57,270 --> 00:01:00,510 +Most of the words are +tokenized in several subtokens, + +23 +00:01:00,510 --> 00:01:03,180 +while fish, one of the only English word, + +24 +00:01:03,180 --> 00:01:05,670 +is tokenized as a single word. + +25 +00:01:05,670 --> 00:01:08,703 +That's because our inputs have +been tokenized as English. + +26 +00:01:09,660 --> 00:01:11,430 +Since our model knows two languages, + +27 +00:01:11,430 --> 00:01:13,800 +you have to warn it when +tokenizing the targets + +28 +00:01:13,800 --> 00:01:16,230 +so it switches in French mode. + +29 +00:01:16,230 --> 00:01:20,010 +This is done with the +as_target_tokenizer context manager. + +30 +00:01:20,010 --> 00:01:23,343 +You can see how it results in +a more compact tokenization. + +31 +00:01:24,810 --> 00:01:25,890 +Processing the whole dataset + +32 +00:01:25,890 --> 00:01:28,440 +is then super easy with the map function. + +33 +00:01:28,440 --> 00:01:30,207 +You can pick different maximum lengths + +34 +00:01:30,207 --> 00:01:32,100 +for the inputs and targets, + +35 +00:01:32,100 --> 00:01:34,530 +and choose to pad at this +stage to that maximum length + +36 +00:01:34,530 --> 00:01:36,273 +by setting padding=max_length. + +37 +00:01:37,230 --> 00:01:39,300 +Here we'll show you to pad dynamically + +38 +00:01:39,300 --> 00:01:41,013 +as it requires one more step. + +39 +00:01:42,450 --> 00:01:43,470 +Your inputs and targets + +40 +00:01:43,470 --> 00:01:46,080 +are all sentences of various lengths. + +41 +00:01:46,080 --> 00:01:48,510 +We will pad the inputs +and targets separately, + +42 +00:01:48,510 --> 00:01:50,460 +as the maximum lengths +of the inputs and targets + +43 +00:01:50,460 --> 00:01:51,483 +might be different. + +44 +00:01:52,620 --> 00:01:54,540 +Then we pad the inputs with the pad token + +45 +00:01:54,540 --> 00:01:57,060 +and the targets with the -100 index + +46 +00:01:57,060 --> 00:01:58,890 +to make sure they're +not taken into account + +47 +00:01:58,890 --> 00:02:00,123 +in the loss computation. + +48 +00:02:01,320 --> 00:02:02,153 +Once this is done, + +49 +00:02:02,153 --> 00:02:04,340 +batching inputs and +targets become super easy. + +50 +00:02:05,670 --> 00:02:08,220 +The Transformers library +provides us with data collator + +51 +00:02:08,220 --> 00:02:10,500 +to do this all automatically. + +52 +00:02:10,500 --> 00:02:13,800 +You can then pass it to the +Trainer with your datasets + +53 +00:02:13,800 --> 00:02:15,960 +or use it in the to_tf_dataset method + +54 +00:02:15,960 --> 00:02:18,560 +before using model.fit() +on your (indistinct) model. + +55 +00:02:21,057 --> 00:02:23,724 +(air whooshing) + diff --git a/subtitles/en/60_what-is-the-bleu-metric.srt b/subtitles/en/60_what-is-the-bleu-metric.srt new file mode 100644 index 000000000..6231d1ca5 --- /dev/null +++ b/subtitles/en/60_what-is-the-bleu-metric.srt @@ -0,0 +1,540 @@ +1 +00:00:00,147 --> 00:00:01,412 +(screen whooshing) + +2 +00:00:01,412 --> 00:00:02,698 +(sticker popping) + +3 +00:00:02,698 --> 00:00:05,670 +(screen whooshing) + +4 +00:00:05,670 --> 00:00:07,650 +- What is the BLEU metric? + +5 +00:00:07,650 --> 00:00:10,170 +For many NLP tasks we +can use common metrics + +6 +00:00:10,170 --> 00:00:12,810 +like accuracy or F1 +score, but what do you do + +7 +00:00:12,810 --> 00:00:14,340 +when you wanna measure the quality of text + +8 +00:00:14,340 --> 00:00:16,560 +that's been translated from a model? + +9 +00:00:16,560 --> 00:00:18,750 +In this video, we'll take a +look at a widely used metric + +10 +00:00:18,750 --> 00:00:20,613 +for machine translation called BLEU. + +11 +00:00:22,290 --> 00:00:23,940 +The basic idea behind BLEU is to assign + +12 +00:00:23,940 --> 00:00:26,250 +a single numerical score to a translation + +13 +00:00:26,250 --> 00:00:27,450 +that tells us how good it is + +14 +00:00:27,450 --> 00:00:30,199 +compared to one or more +reference translations. + +15 +00:00:30,199 --> 00:00:32,130 +In this example, we have +a sentence in Spanish + +16 +00:00:32,130 --> 00:00:35,340 +that has been translated +into English by some model. + +17 +00:00:35,340 --> 00:00:37,170 +If we compare the generated translation + +18 +00:00:37,170 --> 00:00:39,150 +to some reference human translations, + +19 +00:00:39,150 --> 00:00:41,190 +we can see that the model +is actually pretty good, + +20 +00:00:41,190 --> 00:00:43,260 +but has made a common error. + +21 +00:00:43,260 --> 00:00:46,050 +The Spanish word tengo +means have in English, + +22 +00:00:46,050 --> 00:00:48,700 +and this one-to-one translation +is not quite natural. + +23 +00:00:49,890 --> 00:00:51,270 +So how can we measure the quality + +24 +00:00:51,270 --> 00:00:54,270 +of a generated translation +in some automatic way? + +25 +00:00:54,270 --> 00:00:56,730 +The approach that BLEU takes +is to compare the n-grams + +26 +00:00:56,730 --> 00:00:58,550 +of the generated +translation to the n-grams + +27 +00:00:58,550 --> 00:01:00,390 +in the references. + +28 +00:01:00,390 --> 00:01:02,400 +Now, an n-gram is just +a fancy way of saying + +29 +00:01:02,400 --> 00:01:03,960 +a chunk of n words. + +30 +00:01:03,960 --> 00:01:05,220 +So let's start with unigrams, + +31 +00:01:05,220 --> 00:01:08,020 +which corresponds to the +individual words in a sentence. + +32 +00:01:08,880 --> 00:01:11,250 +In this example, you can +see that four of the words + +33 +00:01:11,250 --> 00:01:13,140 +in the generated +translation are also found + +34 +00:01:13,140 --> 00:01:14,990 +in one of the reference translations. + +35 +00:01:16,350 --> 00:01:18,240 +And once we've found our matches, + +36 +00:01:18,240 --> 00:01:20,130 +one way to assign a +score to the translation + +37 +00:01:20,130 --> 00:01:23,070 +is to compute the +precision of the unigrams. + +38 +00:01:23,070 --> 00:01:25,200 +This means we just count +the number of matching words + +39 +00:01:25,200 --> 00:01:27,360 +in the generated and +reference translations + +40 +00:01:27,360 --> 00:01:29,660 +and normalize the count by +dividing by the number of words + +41 +00:01:29,660 --> 00:01:30,753 +in the generation. + +42 +00:01:31,800 --> 00:01:34,080 +In this example, we +found four matching words + +43 +00:01:34,080 --> 00:01:36,033 +and our generation has five words. + +44 +00:01:37,140 --> 00:01:39,690 +Now, in general, precision +ranges from zero to one, + +45 +00:01:39,690 --> 00:01:42,390 +and higher precision scores +mean a better translation. + +46 +00:01:44,160 --> 00:01:45,570 +But this isn't really the whole story + +47 +00:01:45,570 --> 00:01:47,310 +because one problem with unigram precision + +48 +00:01:47,310 --> 00:01:49,140 +is that translation +models sometimes get stuck + +49 +00:01:49,140 --> 00:01:51,330 +in repetitive patterns and +just repeat the same word + +50 +00:01:51,330 --> 00:01:52,293 +several times. + +51 +00:01:53,160 --> 00:01:54,690 +If we just count the +number of word matches, + +52 +00:01:54,690 --> 00:01:56,370 +we can get really high precision scores + +53 +00:01:56,370 --> 00:01:57,840 +even though the translation is terrible + +54 +00:01:57,840 --> 00:01:59,090 +from a human perspective! + +55 +00:02:00,000 --> 00:02:02,970 +For example, if our model +just generates the word six, + +56 +00:02:02,970 --> 00:02:05,020 +we get a perfect unigram precision score. + +57 +00:02:06,960 --> 00:02:09,930 +So to handle this, BLEU +uses a modified precision + +58 +00:02:09,930 --> 00:02:12,210 +that clips the number of +times to count a word, + +59 +00:02:12,210 --> 00:02:13,680 +based on the maximum number of times + +60 +00:02:13,680 --> 00:02:16,399 +it appears in the reference translation. + +61 +00:02:16,399 --> 00:02:18,630 +In this example, the word +six only appears once + +62 +00:02:18,630 --> 00:02:21,360 +in the reference, so we +clip the numerator to one + +63 +00:02:21,360 --> 00:02:22,710 +and the modified unigram precision + +64 +00:02:22,710 --> 00:02:25,233 +now gives a much lower score as expected. + +65 +00:02:27,660 --> 00:02:29,400 +Another problem with unigram precision + +66 +00:02:29,400 --> 00:02:30,780 +is that it doesn't take into account + +67 +00:02:30,780 --> 00:02:33,900 +the order in which the words +appear in the translations. + +68 +00:02:33,900 --> 00:02:35,700 +For example, suppose we had Yoda + +69 +00:02:35,700 --> 00:02:37,410 +translate our Spanish sentence, + +70 +00:02:37,410 --> 00:02:39,457 +then we might get +something backwards like, + +71 +00:02:39,457 --> 00:02:42,450 +"Years sixty thirty have I." + +72 +00:02:42,450 --> 00:02:44,670 +In this case, the +modified unigram precision + +73 +00:02:44,670 --> 00:02:47,393 +gives a high precision which +is not really what we want. + +74 +00:02:48,480 --> 00:02:50,460 +So to deal with word ordering problems, + +75 +00:02:50,460 --> 00:02:52,020 +BLEU actually computes the precision + +76 +00:02:52,020 --> 00:02:55,410 +for several different n-grams +and then averages the result. + +77 +00:02:55,410 --> 00:02:57,300 +For example, if we compare 4-grams, + +78 +00:02:57,300 --> 00:02:58,830 +we can see that there +are no matching chunks + +79 +00:02:58,830 --> 00:03:01,020 +of four words in the translations, + +80 +00:03:01,020 --> 00:03:02,913 +and so the 4-gram precision is 0. + +81 +00:03:05,460 --> 00:03:07,560 +Now, to compute BLEU +scores in Datasets library + +82 +00:03:07,560 --> 00:03:09,120 +is really very simple. + +83 +00:03:09,120 --> 00:03:11,100 +You just use the load_metric function, + +84 +00:03:11,100 --> 00:03:13,290 +provide your model's predictions +with their references + +85 +00:03:13,290 --> 00:03:14,390 +and you're good to go! + +86 +00:03:16,470 --> 00:03:19,200 +The output will contain +several fields of interest. + +87 +00:03:19,200 --> 00:03:20,490 +The precisions field contains + +88 +00:03:20,490 --> 00:03:23,133 +all the individual precision +scores for each n-gram. + +89 +00:03:25,050 --> 00:03:26,940 +The BLEU score itself is then calculated + +90 +00:03:26,940 --> 00:03:30,090 +by taking the geometric mean +of the precision scores. + +91 +00:03:30,090 --> 00:03:32,790 +And by default, the mean of +all four n-gram precisions + +92 +00:03:32,790 --> 00:03:35,793 +is reported, a metric that is +sometimes also called BLEU-4. + +93 +00:03:36,660 --> 00:03:38,880 +In this example, we can +see the BLEU score is zero + +94 +00:03:38,880 --> 00:03:40,780 +because the 4-gram precision was zero. + +95 +00:03:43,290 --> 00:03:45,390 +Now, the BLEU metric has +some nice properties, + +96 +00:03:45,390 --> 00:03:47,520 +but it is far from a perfect metric. + +97 +00:03:47,520 --> 00:03:49,440 +The good properties are +that it's easy to compute + +98 +00:03:49,440 --> 00:03:50,970 +and it's widely used in research + +99 +00:03:50,970 --> 00:03:52,620 +so you can compare your +model against others + +100 +00:03:52,620 --> 00:03:54,630 +on common benchmarks. + +101 +00:03:54,630 --> 00:03:56,670 +On the other hand, there are +several big problems with BLEU, + +102 +00:03:56,670 --> 00:03:58,830 +including the fact it +doesn't incorporate semantics + +103 +00:03:58,830 --> 00:04:01,920 +and it struggles a lot +on non-English languages. + +104 +00:04:01,920 --> 00:04:02,790 +Another problem with BLEU + +105 +00:04:02,790 --> 00:04:04,620 +is that it assumes the human translations + +106 +00:04:04,620 --> 00:04:05,820 +have already been tokenized + +107 +00:04:05,820 --> 00:04:07,320 +and this makes it hard to compare models + +108 +00:04:07,320 --> 00:04:08,820 +that use different tokenizers. + +109 +00:04:10,590 --> 00:04:12,570 +So as we've seen, measuring +the quality of texts + +110 +00:04:12,570 --> 00:04:15,570 +is still a difficult and +open problem in NLP research. + +111 +00:04:15,570 --> 00:04:17,580 +For machine translation, +the current recommendation + +112 +00:04:17,580 --> 00:04:19,440 +is to use the SacreBLEU metric, + +113 +00:04:19,440 --> 00:04:22,830 +which addresses the tokenization +limitations of BLEU. + +114 +00:04:22,830 --> 00:04:24,360 +As you can see in this example, + +115 +00:04:24,360 --> 00:04:26,580 +computing the SacreBLEU +score is almost identical + +116 +00:04:26,580 --> 00:04:28,020 +to the BLEU one. + +117 +00:04:28,020 --> 00:04:30,360 +The main difference is that +we now pass a list of texts + +118 +00:04:30,360 --> 00:04:32,640 +instead of a list of +words to the translations, + +119 +00:04:32,640 --> 00:04:35,640 +and SacreBLEU takes care of the +tokenization under the hood. + +120 +00:04:36,582 --> 00:04:39,499 +(screen whooshing) + diff --git a/subtitles/en/61_data-processing-for-summarization.srt b/subtitles/en/61_data-processing-for-summarization.srt new file mode 100644 index 000000000..4ac57652f --- /dev/null +++ b/subtitles/en/61_data-processing-for-summarization.srt @@ -0,0 +1,221 @@ +1 +00:00:00,227 --> 00:00:01,359 +(air whooshing) + +2 +00:00:01,359 --> 00:00:02,610 +(smiley clicking) + +3 +00:00:02,610 --> 00:00:05,550 +(air whooshing) + +4 +00:00:05,550 --> 00:00:08,450 +- Let's see how to preprocess +a dataset for summarization. + +5 +00:00:09,750 --> 00:00:13,083 +This is the task of, well, +summarizing a long document. + +6 +00:00:14,040 --> 00:00:16,830 +This video will focus on how +to preprocess your dataset + +7 +00:00:16,830 --> 00:00:19,680 +once you have managed to put +it in the following format: + +8 +00:00:19,680 --> 00:00:21,510 +one column for the long documents, + +9 +00:00:21,510 --> 00:00:23,610 +and one for the summaries. + +10 +00:00:23,610 --> 00:00:24,930 +Here is how we can achieve this + +11 +00:00:24,930 --> 00:00:27,573 +with the Datasets library +on the XSUM dataset. + +12 +00:00:28,650 --> 00:00:30,810 +As long as you manage to have +your data look like this, + +13 +00:00:30,810 --> 00:00:33,690 +you should be able to +follow the same steps. + +14 +00:00:33,690 --> 00:00:35,880 +For once, our labels are not integers + +15 +00:00:35,880 --> 00:00:39,150 +corresponding to some +classes, but plain text. + +16 +00:00:39,150 --> 00:00:42,480 +We will thus need to tokenize +them, like our inputs. + +17 +00:00:42,480 --> 00:00:43,920 +There is a small trap there though, + +18 +00:00:43,920 --> 00:00:45,360 +as we need to tokenize our targets + +19 +00:00:45,360 --> 00:00:48,690 +inside the as_target_tokenizer +context manager. + +20 +00:00:48,690 --> 00:00:51,030 +This is because the special tokens we add + +21 +00:00:51,030 --> 00:00:54,000 +might be slightly different +for the inputs and the target, + +22 +00:00:54,000 --> 00:00:57,300 +so the tokenizer has to know +which one it is processing. + +23 +00:00:57,300 --> 00:00:59,550 +Processing the whole +dataset is then super easy + +24 +00:00:59,550 --> 00:01:01,290 +with the map function. + +25 +00:01:01,290 --> 00:01:03,450 +Since the summaries are +usually much shorter + +26 +00:01:03,450 --> 00:01:05,400 +than the documents, you +should definitely pick + +27 +00:01:05,400 --> 00:01:08,880 +different maximum lengths +for the inputs and targets. + +28 +00:01:08,880 --> 00:01:11,730 +You can choose to pad at this +stage to that maximum length + +29 +00:01:11,730 --> 00:01:14,070 +by setting padding=max_length. + +30 +00:01:14,070 --> 00:01:16,170 +Here we'll show you +how to pad dynamically, + +31 +00:01:16,170 --> 00:01:17,620 +as it requires one more step. + +32 +00:01:18,840 --> 00:01:20,910 +Your inputs and targets are all sentences + +33 +00:01:20,910 --> 00:01:22,620 +of various lengths. + +34 +00:01:22,620 --> 00:01:24,960 +We'll pad the inputs +and targets separately + +35 +00:01:24,960 --> 00:01:27,030 +as the maximum lengths +of the inputs and targets + +36 +00:01:27,030 --> 00:01:28,280 +are completely different. + +37 +00:01:29,130 --> 00:01:31,170 +Then, we pad the inputs +to the maximum lengths + +38 +00:01:31,170 --> 00:01:33,813 +among the inputs, and same for the target. + +39 +00:01:34,860 --> 00:01:36,630 +We pad the input with the pad token, + +40 +00:01:36,630 --> 00:01:39,000 +and the targets with the -100 index + +41 +00:01:39,000 --> 00:01:40,980 +to make sure they are +not taken into account + +42 +00:01:40,980 --> 00:01:42,180 +in the loss computation. + +43 +00:01:43,440 --> 00:01:45,180 +The Transformers library provide us + +44 +00:01:45,180 --> 00:01:48,510 +with a data collator to +do this all automatically. + +45 +00:01:48,510 --> 00:01:51,690 +You can then pass it to the +Trainer with your datasets, + +46 +00:01:51,690 --> 00:01:55,710 +or use it in the to_tf_dataset +method before using model.fit + +47 +00:01:55,710 --> 00:01:56,823 +on your current model. + +48 +00:01:58,339 --> 00:02:01,520 +(air whooshing) + +49 +00:02:01,520 --> 00:02:02,876 +(air whooshing) + diff --git a/subtitles/en/62_what-is-the-rouge-metric.srt b/subtitles/en/62_what-is-the-rouge-metric.srt new file mode 100644 index 000000000..5450615b0 --- /dev/null +++ b/subtitles/en/62_what-is-the-rouge-metric.srt @@ -0,0 +1,455 @@ +1 +00:00:00,624 --> 00:00:03,374 +(logo whooshing) + +2 +00:00:05,700 --> 00:00:07,740 +- What is the ROUGE metric? + +3 +00:00:07,740 --> 00:00:08,880 +For many NLP tasks + +4 +00:00:08,880 --> 00:00:12,270 +we can use common metrics +like accuracy or F1 score. + +5 +00:00:12,270 --> 00:00:13,650 +But what do you do when +you wanna measure something + +6 +00:00:13,650 --> 00:00:16,920 +like the quality of a +summary from a model like T5? + +7 +00:00:16,920 --> 00:00:18,180 +In this video, we'll take a look + +8 +00:00:18,180 --> 00:00:21,180 +at a widely used metric for +tech summarization called ROUGE. + +9 +00:00:22,740 --> 00:00:24,660 +There are actually +several variants of ROUGE + +10 +00:00:24,660 --> 00:00:26,190 +but the basic idea behind all of them + +11 +00:00:26,190 --> 00:00:27,840 +is to assign a single numerical score + +12 +00:00:27,840 --> 00:00:30,000 +to a summary that tells us how good it is + +13 +00:00:30,000 --> 00:00:32,774 +compared to one or more +reference summaries. + +14 +00:00:32,774 --> 00:00:34,020 +In this example, we have a book review + +15 +00:00:34,020 --> 00:00:36,570 +that has been summarized by some model. + +16 +00:00:36,570 --> 00:00:38,320 +If we compare the generated summary + +17 +00:00:39,168 --> 00:00:40,260 +to some reference human +summaries, we can see + +18 +00:00:40,260 --> 00:00:42,841 +that the model is actually pretty good + +19 +00:00:42,841 --> 00:00:44,063 +and only differs by a word or two. + +20 +00:00:45,060 --> 00:00:46,260 +So how can we measure the quality + +21 +00:00:46,260 --> 00:00:49,050 +of a generated summary +in an automatic way? + +22 +00:00:49,050 --> 00:00:51,510 +The approach that ROUGE takes +is to compare the n-grams + +23 +00:00:51,510 --> 00:00:55,200 +of the generated summary to +the n-grams of the references. + +24 +00:00:55,200 --> 00:00:58,590 +And n-gram is just a fancy way +of saying a chunk of N words. + +25 +00:00:58,590 --> 00:01:00,030 +So let's start with unigrams + +26 +00:01:00,030 --> 00:01:02,780 +which correspond to the +individual words in a sentence. + +27 +00:01:03,780 --> 00:01:05,250 +In this example, you can see that six + +28 +00:01:05,250 --> 00:01:07,650 +of the words in the generated +summary are also found + +29 +00:01:07,650 --> 00:01:09,420 +in one of the reference summaries. + +30 +00:01:09,420 --> 00:01:11,310 +And the rouge metric +that compares unigrams + +31 +00:01:11,310 --> 00:01:12,260 +is called ROUGE-1. + +32 +00:01:14,533 --> 00:01:16,770 +Now that we found our matches, +one way to assign a score + +33 +00:01:16,770 --> 00:01:20,280 +to the summary is to compute +the recall of the unigrams. + +34 +00:01:20,280 --> 00:01:21,540 +This means we just count the number + +35 +00:01:21,540 --> 00:01:22,950 +of matching words in the generated + +36 +00:01:22,950 --> 00:01:25,290 +and reference summaries +and normalize the count + +37 +00:01:25,290 --> 00:01:28,200 +by dividing by the number +of words in the reference. + +38 +00:01:28,200 --> 00:01:30,450 +In this example, we +found six matching words + +39 +00:01:30,450 --> 00:01:32,160 +and our reference has six words. + +40 +00:01:32,160 --> 00:01:33,933 +So our unigram recall is perfect. + +41 +00:01:34,800 --> 00:01:35,810 +This means that all of the words + +42 +00:01:35,810 --> 00:01:37,500 +in the reference summary +have been produced + +43 +00:01:37,500 --> 00:01:38,550 +in the generated one. + +44 +00:01:40,050 --> 00:01:42,360 +Now, perfect recall +sounds great, but imagine + +45 +00:01:42,360 --> 00:01:44,520 +if our generated summary +have been something like + +46 +00:01:44,520 --> 00:01:45,720 +I really, really, really, + +47 +00:01:45,720 --> 00:01:48,150 +really loved reading the Hunger Games. + +48 +00:01:48,150 --> 00:01:49,378 +This would also have perfect recall + +49 +00:01:49,378 --> 00:01:51,330 +but is arguably a worse summary, + +50 +00:01:51,330 --> 00:01:52,653 +since it is verbose. + +51 +00:01:53,550 --> 00:01:54,600 +To deal with these scenarios, + +52 +00:01:54,600 --> 00:01:56,190 +we can also compute precision, + +53 +00:01:56,190 --> 00:01:58,380 +which in the ROUGE +context measures how much + +54 +00:01:58,380 --> 00:02:00,810 +of the generator summary was relevant. + +55 +00:02:00,810 --> 00:02:03,630 +In practice, both precision +and recall are usually computed + +56 +00:02:03,630 --> 00:02:05,493 +and then the F1 score is reported. + +57 +00:02:07,170 --> 00:02:08,542 +Now we can change the granularity + +58 +00:02:08,542 --> 00:02:13,020 +of the comparison by comparing +bigrams instead of unigrams. + +59 +00:02:13,020 --> 00:02:15,090 +With bigrams, we chunk +the sentence into pairs + +60 +00:02:15,090 --> 00:02:17,910 +of consecutive words and +then count how many pairs + +61 +00:02:17,910 --> 00:02:21,360 +in the generated summary are +present in the reference one. + +62 +00:02:21,360 --> 00:02:23,880 +This gives us ROUGE-2 precision and recall + +63 +00:02:23,880 --> 00:02:24,780 +which as we can see, + +64 +00:02:24,780 --> 00:02:27,780 +is lower than the ROUGE-1 +scores from earlier. + +65 +00:02:27,780 --> 00:02:29,400 +Now, if the summaries are long, + +66 +00:02:29,400 --> 00:02:31,740 +the ROUGE-2 scores will generally be small + +67 +00:02:31,740 --> 00:02:34,290 +because there are fewer bios to match. + +68 +00:02:34,290 --> 00:02:36,870 +And this is also true for +abstracter summarization. + +69 +00:02:36,870 --> 00:02:39,993 +So both ROUGE-1 and ROUGE-2 +scores are usually reported. + +70 +00:02:42,000 --> 00:02:45,330 +The last ROUGE variant we +will discuss is ROUGE L. + +71 +00:02:45,330 --> 00:02:47,160 +ROUGE L doesn't compare ngrams + +72 +00:02:47,160 --> 00:02:49,572 +but instead treats each +summary as a sequence of words + +73 +00:02:49,572 --> 00:02:53,403 +and then looks for the longest +common subsequence or LCS. + +74 +00:02:54,775 --> 00:02:56,130 +A subsequence is a sequence that appears + +75 +00:02:56,130 --> 00:02:59,760 +in the same relative order, +but not necessarily contiguous. + +76 +00:02:59,760 --> 00:03:03,210 +So in this example, I loved +reading the Hunger Games, + +77 +00:03:03,210 --> 00:03:06,930 +is the longest common subsequence +between the two summaries. + +78 +00:03:06,930 --> 00:03:08,610 +And the main advantage of ROUGE L + +79 +00:03:08,610 --> 00:03:11,670 +over ROUGE-1 or ROUGE-2 +is that it doesn't depend + +80 +00:03:11,670 --> 00:03:14,100 +on consecutive n-gram +matches, and so it tends + +81 +00:03:14,100 --> 00:03:16,650 +to capture sentence structure +much more accurately. + +82 +00:03:18,150 --> 00:03:19,440 +Now to compute ROUGE scores + +83 +00:03:19,440 --> 00:03:21,660 +in the data sets library is very simple. + +84 +00:03:21,660 --> 00:03:23,910 +You just use the load metric function, + +85 +00:03:23,910 --> 00:03:26,400 +provide your model summaries +along with the references + +86 +00:03:26,400 --> 00:03:27,500 +and you're good to go. + +87 +00:03:28,770 --> 00:03:30,120 +The output from the calculation + +88 +00:03:30,120 --> 00:03:31,507 +contains a lot of information. + +89 +00:03:31,507 --> 00:03:34,560 +The first thing we can see is +that the confidence intervals + +90 +00:03:34,560 --> 00:03:36,090 +of each ROUGE score are provided + +91 +00:03:36,090 --> 00:03:39,030 +in the low, mid and high fields. + +92 +00:03:39,030 --> 00:03:40,980 +This is really useful if +you wanna know the spread + +93 +00:03:40,980 --> 00:03:43,730 +of your ROUGE scores when +comparing two or more models. + +94 +00:03:45,090 --> 00:03:46,050 +The second thing to notice + +95 +00:03:46,050 --> 00:03:48,330 +is that we have four types of ROUGE score. + +96 +00:03:48,330 --> 00:03:51,480 +We've already seen ROUGE-1, +ROUGE-2 and ROUGE-L + +97 +00:03:51,480 --> 00:03:53,760 +So what is ROUGE-L sum? + +98 +00:03:53,760 --> 00:03:55,410 +Well, the sum in ROUGEL's sum + +99 +00:03:55,410 --> 00:03:57,630 +refers to the fact that +this metric is computed + +100 +00:03:57,630 --> 00:04:00,240 +over a whole summary +while ROUGE-L is computed + +101 +00:04:00,240 --> 00:04:02,493 +as the average of individual sentences. + +102 +00:04:04,166 --> 00:04:06,916 +(logo whooshing) + diff --git a/subtitles/en/63_data-processing-for-causal-language-modeling.srt b/subtitles/en/63_data-processing-for-causal-language-modeling.srt new file mode 100644 index 000000000..d5d544dee --- /dev/null +++ b/subtitles/en/63_data-processing-for-causal-language-modeling.srt @@ -0,0 +1,415 @@ +1 +00:00:00,000 --> 00:00:02,917 +(transition music) + +2 +00:00:05,364 --> 00:00:08,310 +- In this video, we take a +look at the data processing + +3 +00:00:08,310 --> 00:00:10,803 +necessary to train causal language models. + +4 +00:00:12,690 --> 00:00:14,400 +Causal language modeling is the task + +5 +00:00:14,400 --> 00:00:17,820 +of predicting the next token +based on the previous ones. + +6 +00:00:17,820 --> 00:00:19,680 +Another term for causal language modeling + +7 +00:00:19,680 --> 00:00:21,000 +is autoregressive modeling. + +8 +00:00:21,000 --> 00:00:23,940 +In the example that you can see here, + +9 +00:00:23,940 --> 00:00:25,560 +the next token could, for example, + +10 +00:00:25,560 --> 00:00:28,263 +be NLP or it could be machine learning. + +11 +00:00:29,460 --> 00:00:31,457 +A popular example of +causal language models + +12 +00:00:31,457 --> 00:00:33,693 +is the GPT family of models. + +13 +00:00:35,561 --> 00:00:38,010 +To train models such as GPT, + +14 +00:00:38,010 --> 00:00:41,460 +we usually start with a +large corpus of text files. + +15 +00:00:41,460 --> 00:00:43,890 +These files can be webpages +scraped from the internet + +16 +00:00:43,890 --> 00:00:46,020 +such as the Common Crawl dataset + +17 +00:00:46,020 --> 00:00:47,940 +or they can be Python files from GitHub, + +18 +00:00:47,940 --> 00:00:49,490 +like the ones you can see here. + +19 +00:00:50,400 --> 00:00:52,680 +As a first step, we need +to tokenize these files + +20 +00:00:52,680 --> 00:00:55,380 +such that we can feed +them through the model. + +21 +00:00:55,380 --> 00:00:58,500 +Here, we show the tokenized +texts as bars of various length, + +22 +00:00:58,500 --> 00:01:02,188 +illustrating that they're +shorter and longer ones. + +23 +00:01:02,188 --> 00:01:05,910 +This is very common +when working with text. + +24 +00:01:05,910 --> 00:01:09,270 +However, transform models +have a limited context window + +25 +00:01:09,270 --> 00:01:10,770 +and depending on the data source, + +26 +00:01:10,770 --> 00:01:13,140 +it is possible that the tokenized texts + +27 +00:01:13,140 --> 00:01:15,183 +are much longer than this window. + +28 +00:01:16,080 --> 00:01:18,870 +In this case, we could +just truncate the sequences + +29 +00:01:18,870 --> 00:01:20,182 +to the context length, + +30 +00:01:20,182 --> 00:01:22,650 +but this would mean +that we lose everything + +31 +00:01:22,650 --> 00:01:24,513 +after the first context window. + +32 +00:01:25,500 --> 00:01:28,410 +Using the return overflowing token flag, + +33 +00:01:28,410 --> 00:01:30,960 +we can use the tokenizer to create chunks + +34 +00:01:30,960 --> 00:01:33,510 +with each one being the +size of the context length. + +35 +00:01:34,860 --> 00:01:36,180 +Sometimes, it can still happen + +36 +00:01:36,180 --> 00:01:37,590 +that the last chunk is too short + +37 +00:01:37,590 --> 00:01:39,900 +if there aren't enough tokens to fill it. + +38 +00:01:39,900 --> 00:01:41,793 +In this case, we can just remove it. + +39 +00:01:42,990 --> 00:01:45,960 +With the return_length keyword, + +40 +00:01:45,960 --> 00:01:49,173 +we also get the length of +each chunk from the tokenizer. + +41 +00:01:51,960 --> 00:01:53,640 +This function shows all the steps + +42 +00:01:53,640 --> 00:01:56,280 +necessary to prepare the dataset. + +43 +00:01:56,280 --> 00:01:57,960 +First, we tokenize the dataset + +44 +00:01:57,960 --> 00:02:00,330 +with the flags I just mentioned. + +45 +00:02:00,330 --> 00:02:02,190 +Then, we go through each chunk + +46 +00:02:02,190 --> 00:02:04,680 +and if it's length matches +the context length, + +47 +00:02:04,680 --> 00:02:06,663 +we add it to the inputs we return. + +48 +00:02:07,590 --> 00:02:10,260 +We can apply this function +to the whole dataset. + +49 +00:02:10,260 --> 00:02:11,700 +In addition, we make sure + +50 +00:02:11,700 --> 00:02:15,450 +that to use batches and +remove the existing columns. + +51 +00:02:15,450 --> 00:02:17,670 +We need to remove the existing columns, + +52 +00:02:17,670 --> 00:02:21,330 +because we can create +multiple samples per text, + +53 +00:02:21,330 --> 00:02:22,890 +and the shapes in the dataset + +54 +00:02:22,890 --> 00:02:24,753 +would not match anymore in that case. + +55 +00:02:26,832 --> 00:02:30,330 +If the context length is of +similar lengths as the files, + +56 +00:02:30,330 --> 00:02:32,733 +this approach doesn't +work so well anymore. + +57 +00:02:33,660 --> 00:02:36,420 +In this example, both sample 1 and 2 + +58 +00:02:36,420 --> 00:02:38,400 +are shorter than the context size + +59 +00:02:38,400 --> 00:02:41,610 +and will be discarded with +the previous approach. + +60 +00:02:41,610 --> 00:02:45,150 +In this case, it is better +to first tokenize each sample + +61 +00:02:45,150 --> 00:02:46,590 +without truncation + +62 +00:02:46,590 --> 00:02:49,290 +and then concatenate the tokenized samples + +63 +00:02:49,290 --> 00:02:52,353 +with an end of string +or EOS token in between. + +64 +00:02:53,546 --> 00:02:56,220 +Finally, we can chunk this long sequence + +65 +00:02:56,220 --> 00:02:59,490 +with the context length and we +don't lose too many sequences + +66 +00:02:59,490 --> 00:03:01,263 +because they're too short anymore. + +67 +00:03:04,170 --> 00:03:05,760 +So far, we have only talked + +68 +00:03:05,760 --> 00:03:08,370 +about the inputs for +causal language modeling, + +69 +00:03:08,370 --> 00:03:11,850 +but not the labels needed +for supervised training. + +70 +00:03:11,850 --> 00:03:13,380 +When we do causal language modeling, + +71 +00:03:13,380 --> 00:03:16,710 +we don't require any extra +labels for the input sequences + +72 +00:03:16,710 --> 00:03:20,610 +as the input sequences +themselves are the labels. + +73 +00:03:20,610 --> 00:03:24,240 +In this example, when we feed +the token trans to the model, + +74 +00:03:24,240 --> 00:03:27,510 +the next token we wanted +to predict is formers. + +75 +00:03:27,510 --> 00:03:30,780 +In the next step, we feed +trans and formers to the model + +76 +00:03:30,780 --> 00:03:33,903 +and the label we wanted to predict is are. + +77 +00:03:35,460 --> 00:03:38,130 +This pattern continues, +and as you can see, + +78 +00:03:38,130 --> 00:03:41,220 +the input sequence is the label sequence + +79 +00:03:41,220 --> 00:03:42,663 +just shifted by one. + +80 +00:03:43,590 --> 00:03:47,310 +Since the model only makes +prediction after the first token, + +81 +00:03:47,310 --> 00:03:49,350 +the first element of the input sequence, + +82 +00:03:49,350 --> 00:03:52,980 +in this case, trans, +is not used as a label. + +83 +00:03:52,980 --> 00:03:55,530 +Similarly, we don't have a label + +84 +00:03:55,530 --> 00:03:57,600 +for the last token in the sequence + +85 +00:03:57,600 --> 00:04:00,843 +since there is no token +after the sequence ends. + +86 +00:04:04,110 --> 00:04:06,300 +Let's have a look at what we need to do + +87 +00:04:06,300 --> 00:04:10,200 +to create the labels for causal +language modeling in code. + +88 +00:04:10,200 --> 00:04:12,360 +If we want to calculate a loss on a batch, + +89 +00:04:12,360 --> 00:04:15,120 +we can just pass the input_ids as labels + +90 +00:04:15,120 --> 00:04:18,933 +and all the shifting is handled +in the model internally. + +91 +00:04:20,032 --> 00:04:22,170 +So, you see, there's no matching involved + +92 +00:04:22,170 --> 00:04:24,870 +in processing data for +causal language modeling, + +93 +00:04:24,870 --> 00:04:27,723 +and it only requires a few simple steps. + +94 +00:04:28,854 --> 00:04:31,771 +(transition music) + diff --git a/subtitles/en/64_using-a-custom-loss-function.srt b/subtitles/en/64_using-a-custom-loss-function.srt new file mode 100644 index 000000000..bd75982b9 --- /dev/null +++ b/subtitles/en/64_using-a-custom-loss-function.srt @@ -0,0 +1,325 @@ +1 +00:00:00,573 --> 00:00:01,636 +(air whooshing) + +2 +00:00:01,636 --> 00:00:02,594 +(logo popping) + +3 +00:00:02,594 --> 00:00:05,550 +(metal sliding) + +4 +00:00:05,550 --> 00:00:07,500 +- In this video, we take +a look at setting up + +5 +00:00:07,500 --> 00:00:09,303 +a custom loss function for training. + +6 +00:00:10,980 --> 00:00:13,260 +In the default loss function, all samples, + +7 +00:00:13,260 --> 00:00:15,840 +such as these code snippets, +are treated the same + +8 +00:00:15,840 --> 00:00:18,960 +irrespective of their content +but there are scenarios + +9 +00:00:18,960 --> 00:00:21,660 +where it could make sense to +weight the samples differently. + +10 +00:00:21,660 --> 00:00:24,570 +If, for example, one sample +contains a lot of tokens + +11 +00:00:24,570 --> 00:00:26,160 +that are of interest to us + +12 +00:00:26,160 --> 00:00:29,910 +or if a sample has a +favorable diversity of tokens. + +13 +00:00:29,910 --> 00:00:31,950 +We can also implement other heuristics + +14 +00:00:31,950 --> 00:00:33,963 +with pattern matching or other rules. + +15 +00:00:35,993 --> 00:00:39,150 +For each sample, we get a +loss value during training + +16 +00:00:39,150 --> 00:00:41,850 +and we can combine that +loss with a weight. + +17 +00:00:41,850 --> 00:00:43,860 +Then we can create a weighted sum + +18 +00:00:43,860 --> 00:00:45,660 +or average over all samples + +19 +00:00:45,660 --> 00:00:47,613 +to get the final loss for the batch. + +20 +00:00:48,690 --> 00:00:51,240 +Let's have a look at a specific example. + +21 +00:00:51,240 --> 00:00:52,830 +We want to set up a language model + +22 +00:00:52,830 --> 00:00:56,073 +that helps us autocomplete +common data science code. + +23 +00:00:57,030 --> 00:01:01,830 +For that task, we would like +to weight samples stronger + +24 +00:01:01,830 --> 00:01:04,110 +where tokens related to +the data science stack, + +25 +00:01:04,110 --> 00:01:07,353 +such as pd or np, occur more frequently. + +26 +00:01:10,140 --> 00:01:13,080 +Here you see a loss function +that does exactly that + +27 +00:01:13,080 --> 00:01:15,180 +for causal language modeling. + +28 +00:01:15,180 --> 00:01:18,030 +It takes the model's input +and predicted logits, + +29 +00:01:18,030 --> 00:01:20,343 +as well as the key tokens, as input. + +30 +00:01:21,869 --> 00:01:25,113 +First, the inputs and logits are aligned. + +31 +00:01:26,490 --> 00:01:29,310 +Then the loss per sample is calculated, + +32 +00:01:29,310 --> 00:01:30,843 +followed by the weights. + +33 +00:01:32,430 --> 00:01:35,583 +Finally, the loss and the weights +are combined and returned. + +34 +00:01:36,540 --> 00:01:39,150 +This is a pretty big function, +so let's take a closer look + +35 +00:01:39,150 --> 00:01:40,953 +at the loss and the weight blocks. + +36 +00:01:43,380 --> 00:01:45,600 +During the calculation +of the standard loss, + +37 +00:01:45,600 --> 00:01:48,930 +the logits and labels are +flattened over the batch. + +38 +00:01:48,930 --> 00:01:52,590 +With the view, we unflatten +the tensor to get the matrix + +39 +00:01:52,590 --> 00:01:55,320 +with a row for each sample +in the batch and a column + +40 +00:01:55,320 --> 00:01:57,723 +for each position in the +sequence of the sample. + +41 +00:01:58,920 --> 00:02:00,600 +We don't need the loss per position, + +42 +00:02:00,600 --> 00:02:04,083 +so we average the loss over +all positions for each sample. + +43 +00:02:06,150 --> 00:02:08,970 +For the weights, we use +Boolean logic to get a tensor + +44 +00:02:08,970 --> 00:02:12,483 +with 1s where a keyword +occurred and 0s where not. + +45 +00:02:13,440 --> 00:02:15,690 +This tensor has an additional dimension + +46 +00:02:15,690 --> 00:02:18,540 +as the loss tensor we +just saw because we get + +47 +00:02:18,540 --> 00:02:21,693 +the information for each +keyword in a separate matrix. + +48 +00:02:22,770 --> 00:02:24,120 +We only want to know + +49 +00:02:24,120 --> 00:02:26,880 +how many times keywords +occurred per sample, + +50 +00:02:26,880 --> 00:02:30,693 +so we can sum overall keywords +and all positions per sample. + +51 +00:02:33,450 --> 00:02:35,010 +Now we're almost there. + +52 +00:02:35,010 --> 00:02:38,850 +We only need to combine the +loss with the weight per sample. + +53 +00:02:38,850 --> 00:02:41,790 +We do this with element +wise multiplication + +54 +00:02:41,790 --> 00:02:45,233 +and then average overall +samples in the batch. + +55 +00:02:45,233 --> 00:02:46,066 +In the end, + +56 +00:02:46,066 --> 00:02:49,110 +we have exactly one loss +value for the whole batch + +57 +00:02:49,110 --> 00:02:51,330 +and this is the whole necessary logic + +58 +00:02:51,330 --> 00:02:53,223 +to create a custom weighted loss. + +59 +00:02:56,250 --> 00:02:59,010 +Let's see how we can make +use of that custom loss + +60 +00:02:59,010 --> 00:03:00,753 +with Accelerate and the Trainer. + +61 +00:03:01,710 --> 00:03:04,656 +In Accelerate, we just pass the input_ids + +62 +00:03:04,656 --> 00:03:05,730 +to the model to get the logits + +63 +00:03:05,730 --> 00:03:08,103 +and then we can call the +custom loss function. + +64 +00:03:09,000 --> 00:03:11,310 +After that, we continue with +the normal training loop + +65 +00:03:11,310 --> 00:03:13,083 +by, for example, calling backward. + +66 +00:03:14,010 --> 00:03:15,570 +For the Trainer, we can overwrite + +67 +00:03:15,570 --> 00:03:19,260 +the compute loss function +of the standard trainer. + +68 +00:03:19,260 --> 00:03:20,970 +We just need to make sure that we return + +69 +00:03:20,970 --> 00:03:24,450 +the loss and the model +outputs in the same format. + +70 +00:03:24,450 --> 00:03:27,570 +With that, you can integrate +your own awesome loss function + +71 +00:03:27,570 --> 00:03:29,763 +with both the Trainer and Accelerate. + +72 +00:03:31,389 --> 00:03:34,056 +(air whooshing) + diff --git a/subtitles/en/65_data-processing-for-question-answering.srt b/subtitles/en/65_data-processing-for-question-answering.srt new file mode 100644 index 000000000..c0ea48326 --- /dev/null +++ b/subtitles/en/65_data-processing-for-question-answering.srt @@ -0,0 +1,277 @@ +1 +00:00:05,580 --> 00:00:07,177 +- Let's study how to preprocess a dataset + +2 +00:00:07,177 --> 00:00:08,643 +for question answering. + +3 +00:00:10,200 --> 00:00:11,640 +Question answering is a task + +4 +00:00:11,640 --> 00:00:14,343 +of finding answers to a +question in some context. + +5 +00:00:15,270 --> 00:00:17,550 +For example, we'll use the SQuAD dataset + +6 +00:00:17,550 --> 00:00:19,860 +in which we remove columns we won't use + +7 +00:00:19,860 --> 00:00:21,660 +and just extract the +information we will need + +8 +00:00:21,660 --> 00:00:22,950 +for the labels, + +9 +00:00:22,950 --> 00:00:26,370 +the start and the end of +the answer in the context. + +10 +00:00:26,370 --> 00:00:28,690 +If you have your own dataset +for question answering, + +11 +00:00:28,690 --> 00:00:31,680 +just make sure you clean your +data to get to the same point, + +12 +00:00:31,680 --> 00:00:33,900 +with one column containing the questions, + +13 +00:00:33,900 --> 00:00:35,940 +one column containing the context, + +14 +00:00:35,940 --> 00:00:38,610 +one column for the index of +the start and end character + +15 +00:00:38,610 --> 00:00:40,473 +of the answer in the context. + +16 +00:00:41,610 --> 00:00:44,520 +Note that the answer must +be part of the context. + +17 +00:00:44,520 --> 00:00:47,160 +If you want to perform +generative question answering, + +18 +00:00:47,160 --> 00:00:50,160 +look at one of the sequence to +sequence videos linked below. + +19 +00:00:51,600 --> 00:00:53,430 +Now, if we have a look at the tokens + +20 +00:00:53,430 --> 00:00:54,750 +we will feed our model, + +21 +00:00:54,750 --> 00:00:58,320 +we'll see the answer lies +somewhere inside the context. + +22 +00:00:58,320 --> 00:01:01,080 +For very long context, that +answer may get truncated + +23 +00:01:01,080 --> 00:01:02,580 +by the tokenizer. + +24 +00:01:02,580 --> 00:01:05,970 +In this case, we won't have any +proper labels for our model, + +25 +00:01:05,970 --> 00:01:07,680 +so we should keep the truncated part + +26 +00:01:07,680 --> 00:01:10,203 +as a separate feature +instead of discarding it. + +27 +00:01:11,100 --> 00:01:12,990 +The only thing we need to be careful with + +28 +00:01:12,990 --> 00:01:15,660 +is to allow some overlap +between separate chunks + +29 +00:01:15,660 --> 00:01:17,670 +so that the answer is not truncated + +30 +00:01:17,670 --> 00:01:19,920 +and that the feature containing the answer + +31 +00:01:19,920 --> 00:01:22,623 +gets sufficient context +to be able to predict it. + +32 +00:01:23,490 --> 00:01:26,040 +Here is how it can be +done by the tokenizer. + +33 +00:01:26,040 --> 00:01:29,370 +We pass it the question, +context, set a truncation + +34 +00:01:29,370 --> 00:01:33,240 +for the context only, and the +padding to the maximum length. + +35 +00:01:33,240 --> 00:01:35,340 +The stride argument is +where we set the number + +36 +00:01:35,340 --> 00:01:36,900 +of overlapping tokens, + +37 +00:01:36,900 --> 00:01:39,600 +and the return overflowing +tokens equals true + +38 +00:01:39,600 --> 00:01:42,630 +means we don't want to +discard the truncated part. + +39 +00:01:42,630 --> 00:01:45,210 +Lastly, we also return the offset mappings + +40 +00:01:45,210 --> 00:01:47,220 +to be able to find the +tokens corresponding + +41 +00:01:47,220 --> 00:01:48,693 +to the answer start and end. + +42 +00:01:49,860 --> 00:01:52,290 +We want those tokens because +they will be the labels + +43 +00:01:52,290 --> 00:01:53,970 +we pass through our model. + +44 +00:01:53,970 --> 00:01:56,870 +In a one-hot encoded version, +here is what they look like. + +45 +00:01:57,930 --> 00:02:00,480 +If the context we have does +not contain the answer, + +46 +00:02:00,480 --> 00:02:03,799 +we set the two labels to +the index of the CLS token. + +47 +00:02:03,799 --> 00:02:05,700 +We also do this if the context + +48 +00:02:05,700 --> 00:02:07,713 +only partially contains the answer. + +49 +00:02:08,580 --> 00:02:11,400 +In terms of code, here +is how we can do it. + +50 +00:02:11,400 --> 00:02:13,710 +Using the sequence IDs of an input, + +51 +00:02:13,710 --> 00:02:17,220 +we can determine the beginning +and the end of the context. + +52 +00:02:17,220 --> 00:02:19,800 +Then, we know if we have to +return to the CLS position + +53 +00:02:19,800 --> 00:02:22,290 +for the two labels or we +determine the position + +54 +00:02:22,290 --> 00:02:25,050 +of the first and last +tokens of the answer. + +55 +00:02:25,050 --> 00:02:27,800 +We can check it works properly +on our previous example. + +56 +00:02:28,680 --> 00:02:31,380 +Putting it all together +looks like this big function, + +57 +00:02:31,380 --> 00:02:34,233 +which we can apply to our +datasets with the map method. + +58 +00:02:35,310 --> 00:02:37,920 +Since we applied padding +during the tokenization, + +59 +00:02:37,920 --> 00:02:40,680 +we can then use this +directly as the trainer + +60 +00:02:40,680 --> 00:02:44,133 +or apply the to_tf_dataset +method to use Keras.fit. + diff --git a/subtitles/en/66_the-post-processing-step-in-question-answering-(pytorch).srt b/subtitles/en/66_the-post-processing-step-in-question-answering-(pytorch).srt new file mode 100644 index 000000000..d4a6fd6db --- /dev/null +++ b/subtitles/en/66_the-post-processing-step-in-question-answering-(pytorch).srt @@ -0,0 +1,342 @@ +1 +00:00:00,315 --> 00:00:02,982 +(air whooshing) + +2 +00:00:05,940 --> 00:00:08,913 +- The post-processing step +in a question answering task. + +3 +00:00:10,440 --> 00:00:12,180 +When doing question answering, + +4 +00:00:12,180 --> 00:00:14,550 +the processing of the initial dataset + +5 +00:00:14,550 --> 00:00:17,370 +implies splitting examples +in several features, + +6 +00:00:17,370 --> 00:00:19,773 +which may or may not contain the answer. + +7 +00:00:21,000 --> 00:00:22,740 +Passing those features through the model + +8 +00:00:22,740 --> 00:00:25,830 +will give us logits for the +start and end positions, + +9 +00:00:25,830 --> 00:00:28,650 +since our labels are +the indices of the token + +10 +00:00:28,650 --> 00:00:31,050 +that correspond to the +start and end the answer. + +11 +00:00:32,664 --> 00:00:35,490 +We must then somehow convert +those logits into an answer, + +12 +00:00:35,490 --> 00:00:38,610 +and then pick one of the various +answers each feature gives + +13 +00:00:38,610 --> 00:00:40,893 +to be the answer for a given example. + +14 +00:00:42,300 --> 00:00:43,500 +For the processing step, + +15 +00:00:43,500 --> 00:00:45,750 +you should refer to +the video linked below. + +16 +00:00:45,750 --> 00:00:47,820 +It's not very different for validation, + +17 +00:00:47,820 --> 00:00:50,820 +we just need to add a few lines +to keep track of two things. + +18 +00:00:51,660 --> 00:00:54,960 +Instead of discarding the +offset mappings, we keep them, + +19 +00:00:54,960 --> 00:00:55,793 +and also include in them + +20 +00:00:55,793 --> 00:00:58,350 +the information of where the context is + +21 +00:00:58,350 --> 00:01:00,690 +by setting the offsets +of the special tokens + +22 +00:01:00,690 --> 00:01:02,253 +and the question to None. + +23 +00:01:03,480 --> 00:01:06,630 +Then we also keep track of the +example ID for each feature, + +24 +00:01:06,630 --> 00:01:08,280 +to be able to map back feature + +25 +00:01:08,280 --> 00:01:10,503 +to the examples that they originated from. + +26 +00:01:11,940 --> 00:01:14,100 +If you don't want to +compute the validation loss, + +27 +00:01:14,100 --> 00:01:15,990 +you won't need to include +all the special code + +28 +00:01:15,990 --> 00:01:18,420 +that we used to create the labels. + +29 +00:01:18,420 --> 00:01:21,090 +With this done, we can apply +that preprocessing function + +30 +00:01:21,090 --> 00:01:22,890 +using the map method. + +31 +00:01:22,890 --> 00:01:24,090 +We take the SQUAD dataset + +32 +00:01:24,090 --> 00:01:26,840 +like in the preprocessing +for question-answering video. + +33 +00:01:27,810 --> 00:01:30,540 +Once this is done, the next +step is to create our model. + +34 +00:01:30,540 --> 00:01:31,710 +We use the default model + +35 +00:01:31,710 --> 00:01:33,930 +behind the question-answering +pipeline here, + +36 +00:01:33,930 --> 00:01:36,960 +but you should use any +model you want to evaluate. + +37 +00:01:36,960 --> 00:01:38,850 +We'll run a manual evaluation loop, + +38 +00:01:38,850 --> 00:01:41,583 +so we create a PyTorch +DataLoader with our features. + +39 +00:01:42,657 --> 00:01:44,520 +With it, we can compute and gather + +40 +00:01:44,520 --> 00:01:46,650 +all the start and end logits like this, + +41 +00:01:46,650 --> 00:01:49,653 +with a standard PyTorch evaluation loop. + +42 +00:01:49,653 --> 00:01:53,220 +With this done, we can really +dive into the post-processing. + +43 +00:01:53,220 --> 00:01:56,340 +First, we'll need a map +from example to features, + +44 +00:01:56,340 --> 00:01:57,873 +which we can create like this. + +45 +00:01:58,800 --> 00:02:00,810 +Now, for the main part +of the post-processing, + +46 +00:02:00,810 --> 00:02:04,230 +let's see how to extract +an answer from the logits. + +47 +00:02:04,230 --> 00:02:05,760 +We could just take the best index + +48 +00:02:05,760 --> 00:02:07,980 +for the start and end logits and be done, + +49 +00:02:07,980 --> 00:02:10,380 +but if our model predicts +something impossible, + +50 +00:02:10,380 --> 00:02:12,150 +like tokens in the question, + +51 +00:02:12,150 --> 00:02:13,940 +we'll look at more of the logits. + +52 +00:02:15,270 --> 00:02:17,070 +Note that in the +question-answering pipeline, + +53 +00:02:17,070 --> 00:02:18,870 +we attributed score to each answer + +54 +00:02:18,870 --> 00:02:20,430 +based on the probabilities, + +55 +00:02:20,430 --> 00:02:22,350 +which we did not compute here. + +56 +00:02:22,350 --> 00:02:25,560 +In terms of logits, the +multiplication we had in the scores + +57 +00:02:25,560 --> 00:02:26,853 +becomes an addition. + +58 +00:02:28,110 --> 00:02:29,010 +To go fast, + +59 +00:02:29,010 --> 00:02:31,800 +we don't look at all possible +start and end logits, + +60 +00:02:31,800 --> 00:02:34,050 +but the 20 best one is enough. + +61 +00:02:34,050 --> 00:02:36,570 +We ignore the logits that +spawn impossible answers + +62 +00:02:36,570 --> 00:02:38,550 +or answer that are too long. + +63 +00:02:38,550 --> 00:02:41,430 +As we saw in the +preprocessing, the labels 0,0 + +64 +00:02:41,430 --> 00:02:43,230 +correspond to a no answer. + +65 +00:02:43,230 --> 00:02:45,090 +Otherwise we use the offsets + +66 +00:02:45,090 --> 00:02:46,940 +to get the answer inside the context. + +67 +00:02:47,910 --> 00:02:49,107 +Let's have a look at the predicted answer + +68 +00:02:49,107 --> 00:02:50,370 +for the first feature, + +69 +00:02:50,370 --> 00:02:51,930 +which is the answer with the best score + +70 +00:02:51,930 --> 00:02:53,640 +or the best logit score + +71 +00:02:53,640 --> 00:02:56,280 +since the SoftMax is +an increasing function. + +72 +00:02:56,280 --> 00:02:58,230 +The model got it right. + +73 +00:02:58,230 --> 00:03:00,690 +Next we just have to loop +this for every example, + +74 +00:03:00,690 --> 00:03:03,720 +picking for each the answer +with the best logit score + +75 +00:03:03,720 --> 00:03:06,750 +in all the features the example generated. + +76 +00:03:06,750 --> 00:03:09,700 +Now you know how to get answers +from your model prediction. + +77 +00:03:11,007 --> 00:03:13,674 +(air whooshing) + diff --git a/subtitles/en/67_the-post-processing-step-in-question-answering-(tensorflow).srt b/subtitles/en/67_the-post-processing-step-in-question-answering-(tensorflow).srt new file mode 100644 index 000000000..59c0957ce --- /dev/null +++ b/subtitles/en/67_the-post-processing-step-in-question-answering-(tensorflow).srt @@ -0,0 +1,329 @@ +1 +00:00:00,367 --> 00:00:02,950 +(subtle blast) + +2 +00:00:05,850 --> 00:00:08,913 +- The post-processing step +in a question-answering task. + +3 +00:00:10,830 --> 00:00:11,790 +When doing question answering, + +4 +00:00:11,790 --> 00:00:14,670 +the processing of the initial dataset + +5 +00:00:14,670 --> 00:00:18,090 +implies splitting examples +in several features, + +6 +00:00:18,090 --> 00:00:20,850 +which may or may not contain the answer. + +7 +00:00:20,850 --> 00:00:22,530 +Passing those features through the model + +8 +00:00:22,530 --> 00:00:25,860 +will give us logits for the +start and end positions, + +9 +00:00:25,860 --> 00:00:28,620 +since our labels are the +indices of the tokens + +10 +00:00:28,620 --> 00:00:31,020 +that correspond to the +start and end the answer. + +11 +00:00:31,860 --> 00:00:34,740 +We must then somehow convert +those logits into an answer, + +12 +00:00:34,740 --> 00:00:38,070 +and then pick one of the various +answers each feature gives + +13 +00:00:38,070 --> 00:00:40,473 +to be the answer for a given example. + +14 +00:00:41,683 --> 00:00:43,200 +For the processing step, + +15 +00:00:43,200 --> 00:00:45,450 +you should refer to +the video linked below. + +16 +00:00:45,450 --> 00:00:47,310 +It's not very different for validation, + +17 +00:00:47,310 --> 00:00:50,053 +we just need to add a few lines +to keep track of two things: + +18 +00:00:50,053 --> 00:00:52,620 +instead of discarding the offset mappings, + +19 +00:00:52,620 --> 00:00:55,380 +we keep them, and also include +in them the information + +20 +00:00:55,380 --> 00:00:58,410 +of where the context is +by setting the offsets + +21 +00:00:58,410 --> 00:01:01,821 +of the special tokens +and the question to None. + +22 +00:01:01,821 --> 00:01:05,370 +Then we also keep track of the +example ID for each feature, + +23 +00:01:05,370 --> 00:01:07,020 +to be able to map back feature + +24 +00:01:07,020 --> 00:01:09,243 +to the examples that they originated from. + +25 +00:01:10,470 --> 00:01:12,660 +If you don't want to +compute the validation loss, + +26 +00:01:12,660 --> 00:01:14,610 +you won't need to include +all the special code + +27 +00:01:14,610 --> 00:01:17,010 +that we used to create the labels. + +28 +00:01:17,010 --> 00:01:19,650 +With this done, we can apply +that preprocessing function + +29 +00:01:19,650 --> 00:01:21,480 +using the map method. + +30 +00:01:21,480 --> 00:01:23,610 +We take the SQUAD dataset +like in the preprocessing + +31 +00:01:23,610 --> 00:01:25,060 +for question-answering video. + +32 +00:01:26,400 --> 00:01:29,310 +Once this is done, the next +step is to create our model. + +33 +00:01:29,310 --> 00:01:30,570 +We use the default model behind + +34 +00:01:30,570 --> 00:01:32,640 +the question-answering pipeline here, + +35 +00:01:32,640 --> 00:01:35,880 +but you should use any +model you want to evaluate. + +36 +00:01:35,880 --> 00:01:37,680 +With the to_tf_dataset method, + +37 +00:01:37,680 --> 00:01:41,370 +we can just sent our processed +dataset to model.predict, + +38 +00:01:41,370 --> 00:01:43,350 +and we directly get our +start and end logits + +39 +00:01:43,350 --> 00:01:45,930 +for the whole dataset as NumPy arrays. + +40 +00:01:45,930 --> 00:01:49,230 +With this done, we can really +dive into the post-processing. + +41 +00:01:49,230 --> 00:01:52,380 +First, we'll need a map +from example to features, + +42 +00:01:52,380 --> 00:01:53,883 +which we can create like this. + +43 +00:01:54,780 --> 00:01:56,700 +Now, for the main part +of the post-processing, + +44 +00:01:56,700 --> 00:02:00,270 +let's see how to extract +an answer from the logits. + +45 +00:02:00,270 --> 00:02:01,650 +We could just take the best index + +46 +00:02:01,650 --> 00:02:03,690 +for the start and end logits and be done, + +47 +00:02:03,690 --> 00:02:06,180 +but if our model predicts +something impossible, + +48 +00:02:06,180 --> 00:02:07,920 +like tokens in the questions, + +49 +00:02:07,920 --> 00:02:09,670 +we will look at more of the logits. + +50 +00:02:10,800 --> 00:02:12,570 +Note that in the +question-answering pipeline, + +51 +00:02:12,570 --> 00:02:14,160 +we attributed the score to each answer + +52 +00:02:14,160 --> 00:02:17,880 +based on the probabilities, +which we did not compute here. + +53 +00:02:17,880 --> 00:02:19,860 +In terms of logits, the +multiplication we had + +54 +00:02:19,860 --> 00:02:21,663 +in the scores becomes an addition. + +55 +00:02:22,650 --> 00:02:23,910 +To go fast, we don't look + +56 +00:02:23,910 --> 00:02:25,343 +at all possible start and end logits, + +57 +00:02:25,343 --> 00:02:26,973 +but the 20 best ones. + +58 +00:02:27,810 --> 00:02:30,386 +We ignore the logits that +spawn impossible answers + +59 +00:02:30,386 --> 00:02:32,370 +or answer that are too long. + +60 +00:02:32,370 --> 00:02:33,720 +As we saw in the preprocessing, + +61 +00:02:33,720 --> 00:02:36,240 +the label "0, 0" correspond to no answer, + +62 +00:02:36,240 --> 00:02:37,440 +otherwise we use the offset + +63 +00:02:37,440 --> 00:02:39,290 +to get the answer inside the context. + +64 +00:02:40,260 --> 00:02:41,580 +Let's have a look at the predicted answer + +65 +00:02:41,580 --> 00:02:43,200 +for the first feature, + +66 +00:02:43,200 --> 00:02:44,790 +which is the answer with the best score, + +67 +00:02:44,790 --> 00:02:46,860 +or the best logit score since the SoftMax + +68 +00:02:46,860 --> 00:02:48,810 +is an increasing function. + +69 +00:02:48,810 --> 00:02:49,960 +The model got it right. + +70 +00:02:51,210 --> 00:02:54,180 +Next, we just have to loop +this for every example, + +71 +00:02:54,180 --> 00:02:56,700 +picking for each the answer +with the best logit score + +72 +00:02:56,700 --> 00:02:59,133 +in all the features the example generated. + +73 +00:03:00,030 --> 00:03:03,030 +Now you know how to get answers +from your model predictions. + +74 +00:03:04,214 --> 00:03:06,797 +(subtle blast) + diff --git a/subtitles/en/68_data-collators-a-tour.srt b/subtitles/en/68_data-collators-a-tour.srt new file mode 100644 index 000000000..56895ea7e --- /dev/null +++ b/subtitles/en/68_data-collators-a-tour.srt @@ -0,0 +1,655 @@ +1 +00:00:00,670 --> 00:00:01,503 +(whooshing sound) + +2 +00:00:01,503 --> 00:00:02,469 +(sticker popping) + +3 +00:00:02,469 --> 00:00:05,302 +(whooshing sound) + +4 +00:00:06,240 --> 00:00:08,220 +In a lot of our examples, + +5 +00:00:08,220 --> 00:00:12,150 +you're going to see DataCollators +popping up over and over. + +6 +00:00:12,150 --> 00:00:16,020 +They're used in both PyTorch +and TensorFlow workflows, + +7 +00:00:16,020 --> 00:00:17,460 +and maybe even in JAX, + +8 +00:00:17,460 --> 00:00:20,130 +but no-one really knows +what's happening in JAX. + +9 +00:00:20,130 --> 00:00:21,840 +We do have a research +team working on it though, + +10 +00:00:21,840 --> 00:00:23,970 +so maybe they'll tell us soon. + +11 +00:00:23,970 --> 00:00:25,620 +But coming back on topic. + +12 +00:00:25,620 --> 00:00:27,600 +What are data collators? + +13 +00:00:27,600 --> 00:00:30,480 +Data collators collate data. + +14 +00:00:30,480 --> 00:00:31,800 +That's not that helpful. + +15 +00:00:31,800 --> 00:00:35,023 +But to be more specific, they +put together a list of samples + +16 +00:00:35,023 --> 00:00:37,830 +into a single training minibatch. + +17 +00:00:37,830 --> 00:00:38,910 +For some tasks, + +18 +00:00:38,910 --> 00:00:41,670 +the data collator can +be very straightforward. + +19 +00:00:41,670 --> 00:00:44,820 +For example, when you're +doing sequence classification, + +20 +00:00:44,820 --> 00:00:47,010 +all you really need +from your data collator + +21 +00:00:47,010 --> 00:00:49,860 +is that it pads your +samples to the same length + +22 +00:00:49,860 --> 00:00:52,413 +and concatenates them +into a single Tensor. + +23 +00:00:53,340 --> 00:00:57,750 +But for other workflows, data +collators can be quite complex + +24 +00:00:57,750 --> 00:00:59,910 +as they handle some of the preprocessing + +25 +00:00:59,910 --> 00:01:02,340 +needed for that particular task. + +26 +00:01:02,340 --> 00:01:04,800 +So, if you want to use a data collator, + +27 +00:01:04,800 --> 00:01:07,860 +for PyTorch users, you +usually pass the data collator + +28 +00:01:07,860 --> 00:01:09,780 +to your Trainer object. + +29 +00:01:09,780 --> 00:01:11,310 +In TensorFlow, it's a bit different. + +30 +00:01:11,310 --> 00:01:12,960 +The easiest way to use a data collator + +31 +00:01:12,960 --> 00:01:16,860 +is to pass it to the to_tf_dataset +method of your dataset. + +32 +00:01:16,860 --> 00:01:20,198 +And this will give you a +tensorflow_tf_data.dataset + +33 +00:01:20,198 --> 00:01:22,743 +that you can then pass to model.fit. + +34 +00:01:23,580 --> 00:01:25,890 +You'll see these approaches +used in the examples + +35 +00:01:25,890 --> 00:01:28,068 +and notebooks throughout this course. + +36 +00:01:28,068 --> 00:01:30,180 +Also note that all of our collators + +37 +00:01:30,180 --> 00:01:32,610 +take a return_tensors argument. + +38 +00:01:32,610 --> 00:01:35,737 +You can set this to "pt" +to get PyTorch Tensors, + +39 +00:01:35,737 --> 00:01:37,920 +"tf" to get TensorFlow Tensors, + +40 +00:01:37,920 --> 00:01:40,404 +or "np" to get Numpy arrays. + +41 +00:01:40,404 --> 00:01:42,450 +For backward compatibility reasons, + +42 +00:01:42,450 --> 00:01:44,460 +the default value is "pt", + +43 +00:01:44,460 --> 00:01:47,160 +so PyTorch users don't even +have to set this argument + +44 +00:01:47,160 --> 00:01:48,270 +most of the time. + +45 +00:01:48,270 --> 00:01:50,820 +And so as a result, they're +often totally unaware + +46 +00:01:50,820 --> 00:01:52,713 +that this argument even exists. + +47 +00:01:53,730 --> 00:01:55,050 +We can learn something from this + +48 +00:01:55,050 --> 00:01:57,120 +which is that the +beneficiaries of privilege + +49 +00:01:57,120 --> 00:01:59,793 +are often the most blind to its existence. + +50 +00:02:00,690 --> 00:02:01,920 +But okay, coming back. + +51 +00:02:01,920 --> 00:02:06,540 +Let's see how some specific +data collators work in action. + +52 +00:02:06,540 --> 00:02:08,070 +Although again, remember if none + +53 +00:02:08,070 --> 00:02:09,900 +of the built-in data +collators do what you need, + +54 +00:02:09,900 --> 00:02:13,650 +you can always write your own +and they're often quite short. + +55 +00:02:13,650 --> 00:02:16,950 +So first, we'll see the +"basic" data collators. + +56 +00:02:16,950 --> 00:02:20,433 +These are DefaultDataCollator +and DataCollatorWithPadding. + +57 +00:02:21,420 --> 00:02:22,830 +These are the ones you should use + +58 +00:02:22,830 --> 00:02:24,720 +if your labels are straightforward + +59 +00:02:24,720 --> 00:02:27,300 +and your data doesn't need +any special processing + +60 +00:02:27,300 --> 00:02:29,673 +before being ready for training. + +61 +00:02:29,673 --> 00:02:31,272 +Notice that because different models + +62 +00:02:31,272 --> 00:02:33,690 +have different padding tokens, + +63 +00:02:33,690 --> 00:02:37,170 +DataCollatorWithPadding will +need your model's Tokenizer + +64 +00:02:37,170 --> 00:02:40,150 +so it knows how to pad sequences properly. + +65 +00:02:40,150 --> 00:02:44,790 +The default data collator +doesn't need a Tokenizer to work, + +66 +00:02:44,790 --> 00:02:46,710 +but it will as a result throw an error + +67 +00:02:46,710 --> 00:02:48,900 +unless all of your sequences +are the same length. + +68 +00:02:48,900 --> 00:02:50,500 +So, you should be aware of that. + +69 +00:02:51,480 --> 00:02:52,860 +Moving on though. + +70 +00:02:52,860 --> 00:02:54,300 +A lot of the other data collators + +71 +00:02:54,300 --> 00:02:56,130 +aside from the basic two are, + +72 +00:02:56,130 --> 00:02:59,490 +they're usually designed to +handle one specific task. + +73 +00:02:59,490 --> 00:03:01,050 +And so, I'm going to show a couple here. + +74 +00:03:01,050 --> 00:03:04,320 +These are +DataCollatorForTokenClassification + +75 +00:03:04,320 --> 00:03:06,447 +and DataCollatorForSeqToSeq. + +76 +00:03:06,447 --> 00:03:09,540 +And the reason these tasks +need special collators + +77 +00:03:09,540 --> 00:03:12,600 +is because their labels +are variable in length. + +78 +00:03:12,600 --> 00:03:15,960 +In token classification there's +one label for each token, + +79 +00:03:15,960 --> 00:03:17,400 +and so the length of the labels + +80 +00:03:17,400 --> 00:03:18,993 +is the length of the sequence. + +81 +00:03:20,280 --> 00:03:23,520 +While in SeqToSeq the labels +are a sequence of tokens + +82 +00:03:23,520 --> 00:03:24,780 +that can be variable length, + +83 +00:03:24,780 --> 00:03:25,800 +that can be very different + +84 +00:03:25,800 --> 00:03:28,200 +from the length of the input sequence. + +85 +00:03:28,200 --> 00:03:32,880 +So in both of these cases, we +handle collating that batch + +86 +00:03:32,880 --> 00:03:35,280 +by padding the labels as well, + +87 +00:03:35,280 --> 00:03:37,410 +as you can see here in this example. + +88 +00:03:37,410 --> 00:03:40,770 +So, inputs and the labels +will need to be padded + +89 +00:03:40,770 --> 00:03:43,860 +if we want to join +samples of variable length + +90 +00:03:43,860 --> 00:03:45,120 +into the same minibatch. + +91 +00:03:45,120 --> 00:03:47,520 +That's exactly what the data collators + +92 +00:03:47,520 --> 00:03:50,460 +and that's exactly what these +data collators will do for us + +93 +00:03:50,460 --> 00:03:52,383 +you know, for this particular task. + +94 +00:03:53,820 --> 00:03:56,070 +So, there's one final data collator + +95 +00:03:56,070 --> 00:03:58,560 +I want to show you as +well just in this lecture. + +96 +00:03:58,560 --> 00:04:00,473 +And that's the +DataCollatorForLanguageModeling. + +97 +00:04:01,410 --> 00:04:03,390 +So, it's very important, and it's firstly, + +98 +00:04:03,390 --> 00:04:05,820 +because language models +are just so foundational + +99 +00:04:05,820 --> 00:04:09,720 +to do for everything we +do with NLP these days. + +100 +00:04:09,720 --> 00:04:12,060 +But secondly, because it has two modes + +101 +00:04:12,060 --> 00:04:14,760 +that do two very different things. + +102 +00:04:14,760 --> 00:04:19,230 +So you choose which mode you +want with the mlm argument. + +103 +00:04:19,230 --> 00:04:22,470 +Set it to True for +masked language modeling, + +104 +00:04:22,470 --> 00:04:26,190 +and set it to False for +causal language modeling. + +105 +00:04:26,190 --> 00:04:28,620 +So, collating data for +causal language modeling + +106 +00:04:28,620 --> 00:04:30,750 +is actually quite straightforward. + +107 +00:04:30,750 --> 00:04:32,640 +The model is just making predictions + +108 +00:04:32,640 --> 00:04:35,460 +for what token comes +next, and so your labels + +109 +00:04:35,460 --> 00:04:37,800 +are more or less just +a copy of your inputs, + +110 +00:04:37,800 --> 00:04:39,090 +and the collator will handle that + +111 +00:04:39,090 --> 00:04:42,240 +and ensure that the inputs and +labels are padded correctly. + +112 +00:04:42,240 --> 00:04:44,910 +When you set mlm to True though, + +113 +00:04:44,910 --> 00:04:46,786 +you get quite different behavior, + +114 +00:04:46,786 --> 00:04:49,200 +that's different from +any other data collator, + +115 +00:04:49,200 --> 00:04:51,660 +and that's because setting mlm to True + +116 +00:04:51,660 --> 00:04:53,550 +means masked language modeling + +117 +00:04:53,550 --> 00:04:55,680 +and that means the labels need to be, + +118 +00:04:55,680 --> 00:04:58,080 +you know, the inputs need to be masked. + +119 +00:04:58,080 --> 00:05:00,093 +So, what does that look like? + +120 +00:05:01,050 --> 00:05:03,900 +So, recall that in +masked language modeling, + +121 +00:05:03,900 --> 00:05:06,570 +the model is not predicting the next word, + +122 +00:05:06,570 --> 00:05:09,240 +instead we randomly mask out some tokens + +123 +00:05:09,240 --> 00:05:11,130 +and the model predicts +all of them at once. + +124 +00:05:11,130 --> 00:05:12,780 +So, it tries to kinda fill in the blanks + +125 +00:05:12,780 --> 00:05:14,790 +for those masked tokens. + +126 +00:05:14,790 --> 00:05:18,210 +But the process of random +masking is surprisingly complex. + +127 +00:05:18,210 --> 00:05:21,330 +If we follow the protocol +from the original BERT paper, + +128 +00:05:21,330 --> 00:05:23,970 +we need to replace some +tokens with a masked token, + +129 +00:05:23,970 --> 00:05:26,190 +some other tokens with a random token, + +130 +00:05:26,190 --> 00:05:29,820 +and then keep a third +set of tokens unchanged. + +131 +00:05:29,820 --> 00:05:30,840 +Yeah, this is not the lecture + +132 +00:05:30,840 --> 00:05:33,903 +to go into the specifics +of that or why we do it. + +133 +00:05:33,903 --> 00:05:36,660 +You can always check out +the original BERT paper + +134 +00:05:36,660 --> 00:05:37,493 +if you're curious. + +135 +00:05:37,493 --> 00:05:39,620 +It's well written. It's +easy to understand. + +136 +00:05:40,650 --> 00:05:44,190 +The main thing to know here +is that it can be a real pain + +137 +00:05:44,190 --> 00:05:46,770 +and quite complex to +implement that yourself. + +138 +00:05:46,770 --> 00:05:49,740 +But DataCollatorForLanguageModeling +will do it for you + +139 +00:05:49,740 --> 00:05:51,750 +when you set mlm to True. + +140 +00:05:51,750 --> 00:05:54,690 +And that's an example +of the more intricate + +141 +00:05:54,690 --> 00:05:57,870 +preprocessing that some +of our data collators do. + +142 +00:05:57,870 --> 00:05:59,430 +And that's it! + +143 +00:05:59,430 --> 00:06:01,920 +So, this covers the most +commonly used data collators + +144 +00:06:01,920 --> 00:06:03,480 +and the tasks they're used for. + +145 +00:06:03,480 --> 00:06:06,990 +And hopefully, now you'll know +when to use data collators + +146 +00:06:06,990 --> 00:06:10,833 +and which one to choose +for your specific task. + +147 +00:06:11,765 --> 00:06:14,598 +(whooshing sound) + diff --git a/subtitles/en/69_what-to-do-when-you-get-an-error.srt b/subtitles/en/69_what-to-do-when-you-get-an-error.srt new file mode 100644 index 000000000..f3907616f --- /dev/null +++ b/subtitles/en/69_what-to-do-when-you-get-an-error.srt @@ -0,0 +1,271 @@ +1 +00:00:00,380 --> 00:00:02,463 +(whoosh) + +2 +00:00:05,550 --> 00:00:07,590 +- In this video we'll +learn the first things to + +3 +00:00:07,590 --> 00:00:09,330 +do when you get an error. + +4 +00:00:09,330 --> 00:00:11,930 +This is not throwing your +laptop through the window. + +5 +00:00:13,320 --> 00:00:15,450 +Let's say we want to use the +question answering pipeline + +6 +00:00:15,450 --> 00:00:19,470 +on a particular model and +we get the following error. + +7 +00:00:19,470 --> 00:00:21,750 +Errors in Python can appear overwhelming + +8 +00:00:21,750 --> 00:00:24,390 +because you get so much +information printed out + +9 +00:00:24,390 --> 00:00:26,610 +but that's because Python +is trying to help you + +10 +00:00:26,610 --> 00:00:29,070 +the best it can to solve your problem. + +11 +00:00:29,070 --> 00:00:31,260 +In this video, we'll see how to interpret + +12 +00:00:31,260 --> 00:00:32,460 +the error report we get. + +13 +00:00:33,510 --> 00:00:35,700 +The first thing to notice at the very top + +14 +00:00:35,700 --> 00:00:38,070 +is that Python shows +you with a clear arrow + +15 +00:00:38,070 --> 00:00:40,320 +the line of code that triggers the error + +16 +00:00:40,320 --> 00:00:42,210 +so you don't have to fiddle with your code + +17 +00:00:42,210 --> 00:00:43,800 +and remove random lines to figure out + +18 +00:00:43,800 --> 00:00:45,540 +where the error comes from. + +19 +00:00:45,540 --> 00:00:47,890 +You have the answer in +front of you right here. + +20 +00:00:49,140 --> 00:00:51,360 +The errors you see below +are a part of the code + +21 +00:00:51,360 --> 00:00:54,930 +Python tried to execute while +running the instruction. + +22 +00:00:54,930 --> 00:00:57,750 +Here we are inside the pipeline function + +23 +00:00:57,750 --> 00:00:59,490 +and zero came on this line + +24 +00:00:59,490 --> 00:01:02,520 +while trying to execute +the function "check_tasks," + +25 +00:01:02,520 --> 00:01:05,103 +which then raised the +KeyError we see displayed. + +26 +00:01:06,630 --> 00:01:08,580 +Note that Python tells you exactly + +27 +00:01:08,580 --> 00:01:11,190 +where the function it's executing lives, + +28 +00:01:11,190 --> 00:01:12,810 +so if you feel adventurous + +29 +00:01:12,810 --> 00:01:14,810 +you can even go inspect the source code. + +30 +00:01:15,900 --> 00:01:18,447 +This whole thing is +called the "Traceback." + +31 +00:01:20,010 --> 00:01:21,870 +If you're running your code on Colab + +32 +00:01:21,870 --> 00:01:23,820 +the Traceback is automatically minimized, + +33 +00:01:23,820 --> 00:01:25,833 +so you have to click to expand it. + +34 +00:01:26,820 --> 00:01:28,530 +At the very end of the Traceback + +35 +00:01:28,530 --> 00:01:31,890 +you finally get the actual error message. + +36 +00:01:31,890 --> 00:01:33,660 +The first thing you should +do when encountering + +37 +00:01:33,660 --> 00:01:36,480 +an error is to read that error message. + +38 +00:01:36,480 --> 00:01:38,640 +Here it's telling us it doesn't know + +39 +00:01:38,640 --> 00:01:40,230 +the question answering task + +40 +00:01:40,230 --> 00:01:41,760 +and helpfully gives us the list + +41 +00:01:41,760 --> 00:01:44,850 +of supported tasks in which we can see + +42 +00:01:44,850 --> 00:01:47,520 +that "question-answering" actually is. + +43 +00:01:47,520 --> 00:01:49,200 +Looking more closely though, + +44 +00:01:49,200 --> 00:01:52,020 +we used an underscore to +surprise the two words + +45 +00:01:52,020 --> 00:01:54,300 +when the task is written with a minus, + +46 +00:01:54,300 --> 00:01:55,413 +so we should fix that. + +47 +00:01:57,510 --> 00:02:00,360 +Now let's retry our code with +the tags properly written + +48 +00:02:00,360 --> 00:02:01,920 +and what is happening today? + +49 +00:02:01,920 --> 00:02:03,210 +Another error. + +50 +00:02:03,210 --> 00:02:05,670 +As we said before, we +go look at the bottom + +51 +00:02:05,670 --> 00:02:07,560 +to read the actual error message. + +52 +00:02:07,560 --> 00:02:09,000 +It's telling us that we should check + +53 +00:02:09,000 --> 00:02:11,340 +our model is a correct model identifier, + +54 +00:02:11,340 --> 00:02:14,760 +so let's hop onto hf.co/models. + +55 +00:02:14,760 --> 00:02:16,440 +We can see our model listed there + +56 +00:02:16,440 --> 00:02:19,440 +in the ones available +for question answering. + +57 +00:02:19,440 --> 00:02:21,720 +The difference is that +it's spelled "distilbert" + +58 +00:02:21,720 --> 00:02:24,240 +with one L, and we use two, + +59 +00:02:24,240 --> 00:02:25,650 +so let's fix that. + +60 +00:02:25,650 --> 00:02:27,570 +We finally get our results. + +61 +00:02:27,570 --> 00:02:29,160 +If our error is more complex, + +62 +00:02:29,160 --> 00:02:31,290 +you might need to use the Python debugger. + +63 +00:02:31,290 --> 00:02:33,483 +Check out the videos below to learn how. + diff --git a/subtitles/en/70_using-a-debugger-in-a-notebook.srt b/subtitles/en/70_using-a-debugger-in-a-notebook.srt new file mode 100644 index 000000000..f543f4b30 --- /dev/null +++ b/subtitles/en/70_using-a-debugger-in-a-notebook.srt @@ -0,0 +1,319 @@ +1 +00:00:05,400 --> 00:00:08,150 +- [Instructor] Using the +Python debugger in a notebook. + +2 +00:00:09,540 --> 00:00:12,330 +In this video, we'll learn +how to use the Python debugger + +3 +00:00:12,330 --> 00:00:15,027 +in a Jupyter Notebook or a Colab. + +4 +00:00:15,027 --> 00:00:17,070 +For this example, we are running code + +5 +00:00:17,070 --> 00:00:19,775 +from the token classification section, + +6 +00:00:19,775 --> 00:00:21,513 +downloading the Conll dataset, + +7 +00:00:23,670 --> 00:00:25,503 +looking a little bit at data, + +8 +00:00:27,840 --> 00:00:29,250 +before loading a tokenizer + +9 +00:00:29,250 --> 00:00:31,173 +to preprocess the whole dataset. + +10 +00:00:32,880 --> 00:00:34,740 +Check out the section of +the course linked below + +11 +00:00:34,740 --> 00:00:35,823 +for more information. + +12 +00:00:37,080 --> 00:00:38,520 +Once this is done, + +13 +00:00:38,520 --> 00:00:41,580 +we try to load eight features +of the training dataset, + +14 +00:00:41,580 --> 00:00:43,080 +and then batch them together, + +15 +00:00:43,080 --> 00:00:45,210 +using tokenizer.pad, + +16 +00:00:45,210 --> 00:00:46,760 +and we get the following error. + +17 +00:00:48,090 --> 00:00:49,230 +We use PyTorch here, + +18 +00:00:49,230 --> 00:00:51,330 +with return_tensors="pt" + +19 +00:00:51,330 --> 00:00:53,273 +but you will get the same +error with TensorFlow. + +20 +00:00:54,120 --> 00:00:55,897 +As we have seen in the "How +to debug an error?" video, + +21 +00:00:55,897 --> 00:00:59,160 +the error message is at +the end of the traceback. + +22 +00:00:59,160 --> 00:01:01,710 +Here, it indicates us +we should use padding, + +23 +00:01:01,710 --> 00:01:04,290 +which we are actually trying to do. + +24 +00:01:04,290 --> 00:01:05,610 +So this is not useful at all, + +25 +00:01:05,610 --> 00:01:06,990 +and we will need to go a little deeper + +26 +00:01:06,990 --> 00:01:08,610 +to debug the problem. + +27 +00:01:08,610 --> 00:01:10,650 +Fortunately, you can +use the Python debugger + +28 +00:01:10,650 --> 00:01:13,170 +at any time you get an +error in a Jupyter Notebook + +29 +00:01:13,170 --> 00:01:16,350 +by typing the magic +command, debug, in a cell. + +30 +00:01:16,350 --> 00:01:18,450 +Don't forget the percent at the beginning. + +31 +00:01:20,400 --> 00:01:21,870 +When executing that cell, + +32 +00:01:21,870 --> 00:01:23,910 +you go to the very bottom of the traceback + +33 +00:01:23,910 --> 00:01:25,320 +where you can type commands + +34 +00:01:25,320 --> 00:01:27,690 +that will help you debug your script. + +35 +00:01:27,690 --> 00:01:29,250 +The first two commands you should learn, + +36 +00:01:29,250 --> 00:01:32,040 +are u and d, for up and down. + +37 +00:01:32,040 --> 00:01:36,090 +Typing u and enter will +take you up one step + +38 +00:01:36,090 --> 00:01:38,910 +in the traceback to the +previous instruction. + +39 +00:01:38,910 --> 00:01:41,190 +Typing d and then enter will take you + +40 +00:01:41,190 --> 00:01:43,023 +one step down in the traceback. + +41 +00:01:44,130 --> 00:01:47,910 +Going up twice, we get to the +point the error was reached. + +42 +00:01:47,910 --> 00:01:51,510 +The third command to learn for +the debugger is p, for print. + +43 +00:01:51,510 --> 00:01:54,780 +It allows you to print any value you want. + +44 +00:01:54,780 --> 00:01:58,740 +For instance, typing p +return_tensors and enter, + +45 +00:01:58,740 --> 00:02:02,893 +we see the value pt that we +pass to the bad function. + +46 +00:02:02,893 --> 00:02:05,370 +We can also have a look +at the batch outputs + +47 +00:02:05,370 --> 00:02:07,353 +this batch line coding object gets. + +48 +00:02:09,480 --> 00:02:12,600 +The batch outputs dictionary +is a bit hard to dig in to, + +49 +00:02:12,600 --> 00:02:15,360 +so let's dive into smaller pieces of it. + +50 +00:02:15,360 --> 00:02:18,390 +Inside the debugger you can +not only print any variable + +51 +00:02:18,390 --> 00:02:20,970 +but also evaluate any expression, + +52 +00:02:20,970 --> 00:02:23,610 +for instance, we can have a +look at the input_ids keys + +53 +00:02:23,610 --> 00:02:25,203 +this batch_outputs object. + +54 +00:02:27,600 --> 00:02:30,693 +Or at the labels keys of +this batch_outputs object. + +55 +00:02:35,730 --> 00:02:37,320 +Those labels are definitely weird: + +56 +00:02:37,320 --> 00:02:38,970 +they are of various sizes, + +57 +00:02:38,970 --> 00:02:41,340 +which we can actually confirm, if we want, + +58 +00:02:41,340 --> 00:02:43,983 +by printing the size with +the least compression. + +59 +00:02:52,290 --> 00:02:54,913 +This is because the pad +method of the tokenizer + +60 +00:02:54,913 --> 00:02:57,090 +only takes care of the tokenizer outputs: + +61 +00:02:57,090 --> 00:03:00,450 +input IDs, attention +mask, and token type IDs, + +62 +00:03:00,450 --> 00:03:02,340 +so we have to pad the labels ourselves + +63 +00:03:02,340 --> 00:03:05,310 +before trying to create +a tensor with them. + +64 +00:03:05,310 --> 00:03:07,260 +Once you are ready to +exit the Python debugger, + +65 +00:03:07,260 --> 00:03:09,453 +you can press q and enter for quit. + +66 +00:03:10,320 --> 00:03:11,670 +One way to fix the error + +67 +00:03:11,670 --> 00:03:14,313 +is to manually pad the +labels to the longest. + +68 +00:03:15,300 --> 00:03:17,400 +Another way is to use a data collator + +69 +00:03:17,400 --> 00:03:19,863 +specifically designed +for token classification. + +70 +00:03:20,970 --> 00:03:22,950 +You can also use a +Python debugger directly + +71 +00:03:22,950 --> 00:03:23,850 +in the terminal. + +72 +00:03:23,850 --> 00:03:25,943 +Check out the video +link below to learn how. + diff --git a/subtitles/en/71_using-a-debugger-in-a-terminal.srt b/subtitles/en/71_using-a-debugger-in-a-terminal.srt new file mode 100644 index 000000000..acc0af0be --- /dev/null +++ b/subtitles/en/71_using-a-debugger-in-a-terminal.srt @@ -0,0 +1,350 @@ +1 +00:00:00,459 --> 00:00:03,542 +(wind swiping sound) + +2 +00:00:05,880 --> 00:00:08,910 +- [Instructor] Using the +Python debugger in a terminal. + +3 +00:00:08,910 --> 00:00:11,580 +In this video, we'll learn +how to use a Python debugger + +4 +00:00:11,580 --> 00:00:13,140 +in a terminal. + +5 +00:00:13,140 --> 00:00:15,390 +For this example, we're running code + +6 +00:00:15,390 --> 00:00:17,760 +from the token classification section, + +7 +00:00:17,760 --> 00:00:19,950 +downloading the Conll dataset + +8 +00:00:19,950 --> 00:00:23,340 +before loading a tokenizer +to pre-process it. + +9 +00:00:23,340 --> 00:00:25,140 +Check out the section +of the course link below + +10 +00:00:25,140 --> 00:00:26,223 +for more information. + +11 +00:00:27,600 --> 00:00:28,500 +Once this is done, + +12 +00:00:28,500 --> 00:00:30,630 +we try to batch together some features + +13 +00:00:30,630 --> 00:00:33,180 +of the training dataset by padding them + +14 +00:00:33,180 --> 00:00:34,330 +and returning a tensor. + +15 +00:00:36,810 --> 00:00:39,510 +If we try to execute our +scripts in a terminal + +16 +00:00:39,510 --> 00:00:40,413 +we get an error. + +17 +00:00:42,630 --> 00:00:44,260 +Note that we use PyTorch here + +18 +00:00:44,260 --> 00:00:45,600 +we return tensors equal pity. + +19 +00:00:45,600 --> 00:00:47,753 +But you would get the same +error with TensorFlow. + +20 +00:00:49,500 --> 00:00:51,990 +As we have seen in the, 'How +to debug an error?' video, + +21 +00:00:51,990 --> 00:00:54,780 +The raw message is at the +end and it indicates we + +22 +00:00:54,780 --> 00:00:58,260 +should use pairing, which +we're actually trying to do. + +23 +00:00:58,260 --> 00:01:00,630 +So this is not useful and +we need to go little deeper + +24 +00:01:00,630 --> 00:01:02,310 +to debug the problem. + +25 +00:01:02,310 --> 00:01:04,830 +Fortunately, you can use the +Python debugger quite easily + +26 +00:01:04,830 --> 00:01:09,830 +in a terminal by launching +your script with Python -m PDB + +27 +00:01:09,930 --> 00:01:11,980 +and then the name of the training script. + +28 +00:01:13,410 --> 00:01:15,030 +When executing that comment, you are sent + +29 +00:01:15,030 --> 00:01:17,340 +to the first instruction of your script. + +30 +00:01:17,340 --> 00:01:20,733 +You can run just the next +instruction by typing N and enter. + +31 +00:01:22,530 --> 00:01:27,423 +Or you can continue directly +to zero by typing C and enter. + +32 +00:01:29,850 --> 00:01:31,560 +Once there, you go to the very bottom + +33 +00:01:31,560 --> 00:01:34,050 +of the traceback and +you can type commands. + +34 +00:01:34,050 --> 00:01:36,360 +The first two commands you +should learn are U and D, + +35 +00:01:36,360 --> 00:01:38,160 +for up and down. + +36 +00:01:38,160 --> 00:01:41,223 +This allows you to get up +and down in the traceback. + +37 +00:01:42,990 --> 00:01:46,623 +Going up twice, we get to the +point the error was reached. + +38 +00:01:47,910 --> 00:01:50,190 +The first command to learn is P for print. + +39 +00:01:50,190 --> 00:01:52,830 +It allows you to print any value you want. + +40 +00:01:52,830 --> 00:01:56,280 +For instance, here we can see +the value of return_tensors + +41 +00:01:56,280 --> 00:02:00,210 +or batch_outputs to try to +understand what triggered zero. + +42 +00:02:00,210 --> 00:02:03,000 +The batch outputs dictionary +is a bit hard to see + +43 +00:02:03,000 --> 00:02:05,520 +so let's dive into smaller pieces of it. + +44 +00:02:05,520 --> 00:02:08,460 +Inside the debugger, you can +not only print any variable + +45 +00:02:08,460 --> 00:02:10,740 +but also evaluate any expression, + +46 +00:02:10,740 --> 00:02:13,713 +so we can look +independently at the inputs. + +47 +00:02:15,060 --> 00:02:15,993 +Also labels. + +48 +00:02:22,350 --> 00:02:24,300 +Those labels are definitely weird. + +49 +00:02:24,300 --> 00:02:26,880 +They are various size, +which we can confirm + +50 +00:02:26,880 --> 00:02:29,553 +by printing the sites using +a release compression. + +51 +00:02:35,880 --> 00:02:37,800 +No wonder the tokenizer +wasn't able to create + +52 +00:02:37,800 --> 00:02:39,270 +a tensor with them. + +53 +00:02:39,270 --> 00:02:41,460 +This is because the pad +method only takes care + +54 +00:02:41,460 --> 00:02:44,850 +of the tokenizer outputs, the +input IDs, the attention mask + +55 +00:02:44,850 --> 00:02:46,560 +and the token type IDs. + +56 +00:02:46,560 --> 00:02:48,390 +So we have to pad the level ourselves + +57 +00:02:48,390 --> 00:02:51,300 +before trying to create +a new sensor with them. + +58 +00:02:51,300 --> 00:02:54,030 +Once you're ready to +execute the Python debugger, + +59 +00:02:54,030 --> 00:02:56,640 +you can press Q for quit and enter. + +60 +00:02:56,640 --> 00:02:59,790 +Another way we can access +the Python debugger, + +61 +00:02:59,790 --> 00:03:02,310 +is to put a breaking point in our script. + +62 +00:03:02,310 --> 00:03:05,913 +We can do this using the +PDB that set_trace method. + +63 +00:03:07,920 --> 00:03:09,870 +As long as we import the PDB module + +64 +00:03:09,870 --> 00:03:11,420 +at the beginning of our script. + +65 +00:03:12,510 --> 00:03:17,283 +Saving and then relaunching +our script, with just Python. + +66 +00:03:19,710 --> 00:03:23,310 +We'll stop the execution at +the breaking point we set. + +67 +00:03:23,310 --> 00:03:24,660 +We can inspect all the variable + +68 +00:03:24,660 --> 00:03:27,030 +before the next instruction +is executed again. + +69 +00:03:27,030 --> 00:03:29,253 +For instance, here, the features. + +70 +00:03:30,270 --> 00:03:33,090 +Typing N and enter execute +the next instruction + +71 +00:03:33,090 --> 00:03:35,700 +which takes us back inside traceback. + +72 +00:03:35,700 --> 00:03:37,530 +When going to fix zero manually is to + +73 +00:03:37,530 --> 00:03:39,873 +pad all the labels to the longest. + +74 +00:03:42,000 --> 00:03:45,120 +Another way is to use +the data creator suitable + +75 +00:03:45,120 --> 00:03:46,443 +for token classification. + +76 +00:03:48,330 --> 00:03:50,340 +If you want to learn how to use the Python + +77 +00:03:50,340 --> 00:03:53,273 +debugger in a notebook, check +out the video in link below. + +78 +00:03:54,698 --> 00:03:57,781 +(wind swiping sound) + diff --git a/subtitles/en/72_asking-for-help-on-the-forums.srt b/subtitles/en/72_asking-for-help-on-the-forums.srt new file mode 100644 index 000000000..e34751109 --- /dev/null +++ b/subtitles/en/72_asking-for-help-on-the-forums.srt @@ -0,0 +1,346 @@ +1 +00:00:00,125 --> 00:00:01,455 +(title whooshes) + +2 +00:00:01,455 --> 00:00:02,789 +(logo pops) + +3 +00:00:02,789 --> 00:00:05,700 +(title whooshes) + +4 +00:00:05,700 --> 00:00:08,433 +- How to ask a question on +the Hugging Face forums? + +5 +00:00:10,020 --> 00:00:11,640 +If you have a general question + +6 +00:00:11,640 --> 00:00:13,110 +or are looking to debug your code, + +7 +00:00:13,110 --> 00:00:15,540 +the forums are the place to ask. + +8 +00:00:15,540 --> 00:00:16,710 +In this video we will teach you + +9 +00:00:16,710 --> 00:00:18,030 +how to write a good question, + +10 +00:00:18,030 --> 00:00:20,380 +to maximize the chances +you will get an answer. + +11 +00:00:21,570 --> 00:00:23,970 +First things first, to +login on the forums, + +12 +00:00:23,970 --> 00:00:25,920 +you need a Hugging Face account. + +13 +00:00:25,920 --> 00:00:27,750 +If you haven't created one already, + +14 +00:00:27,750 --> 00:00:31,080 +go to hf.co and click sign up. + +15 +00:00:31,080 --> 00:00:32,780 +There is also a direct link below. + +16 +00:00:33,750 --> 00:00:35,160 +Fill your email and password, + +17 +00:00:35,160 --> 00:00:37,410 +then continue the steps +to pick your username + +18 +00:00:37,410 --> 00:00:38,860 +and update a profile picture. + +19 +00:00:39,720 --> 00:00:43,200 +Once this is done, go to +discuss.huggingface.co, + +20 +00:00:43,200 --> 00:00:45,630 +link below, and click log in. + +21 +00:00:45,630 --> 00:00:47,033 +Use the same login information as + +22 +00:00:47,033 --> 00:00:48,693 +for the Hugging Face website. + +23 +00:00:49,890 --> 00:00:51,300 +You can search the forums by clicking + +24 +00:00:51,300 --> 00:00:52,800 +on the magnifying glass. + +25 +00:00:52,800 --> 00:00:55,710 +Someone may have already asked +your question in a topic. + +26 +00:00:55,710 --> 00:00:58,260 +If you find you can't post +a new topic as a new user, + +27 +00:00:58,260 --> 00:01:01,290 +it may be because of the antispam filters. + +28 +00:01:01,290 --> 00:01:03,750 +Make sure you spend some +time reading existing topics + +29 +00:01:03,750 --> 00:01:05,370 +to deactivate it. + +30 +00:01:05,370 --> 00:01:07,590 +When you're sure your question +hasn't been asked yet, + +31 +00:01:07,590 --> 00:01:09,660 +click on the new topic button. + +32 +00:01:09,660 --> 00:01:12,600 +For this example, we'll +use the following code, + +33 +00:01:12,600 --> 00:01:13,860 +that produces an error, + +34 +00:01:13,860 --> 00:01:16,660 +as we saw in the "What to do +when I get an error" video. + +35 +00:01:18,330 --> 00:01:21,330 +The first step is to pick a +category for our new topic. + +36 +00:01:21,330 --> 00:01:23,790 +Since our error has to do +with the Transformers library, + +37 +00:01:23,790 --> 00:01:24,903 +we pick this category. + +38 +00:01:26,070 --> 00:01:29,880 +Next, choose a title that +summarizes your error well. + +39 +00:01:29,880 --> 00:01:32,300 +Don't be too vague or users +that get the same error you did + +40 +00:01:32,300 --> 00:01:34,773 +in the future won't be +able to find your topic. + +41 +00:01:36,150 --> 00:01:38,370 +Once you have finished +typing your topic title, + +42 +00:01:38,370 --> 00:01:40,170 +make sure the question +hasn't been answered + +43 +00:01:40,170 --> 00:01:42,690 +in the topics Discourse suggests you. + +44 +00:01:42,690 --> 00:01:44,190 +Click on the cross to remove that window + +45 +00:01:44,190 --> 00:01:46,230 +when you have double-checked. + +46 +00:01:46,230 --> 00:01:49,710 +This is an example of what not +to do when posting an error. + +47 +00:01:49,710 --> 00:01:51,120 +The message is very vague, + +48 +00:01:51,120 --> 00:01:53,370 +so no one else will be able +to guess what went wrong + +49 +00:01:53,370 --> 00:01:55,623 +for you, and it tags too many people. + +50 +00:01:56,490 --> 00:01:58,740 +Tagging people, especially moderators, + +51 +00:01:58,740 --> 00:02:01,470 +might have the opposite +effect of what you want. + +52 +00:02:01,470 --> 00:02:04,380 +As you send them a notification, +and they get plenty, + +53 +00:02:04,380 --> 00:02:06,300 +they will probably not +bother replying to you, + +54 +00:02:06,300 --> 00:02:09,300 +and users you didn't tag will +probably ignore the questions, + +55 +00:02:09,300 --> 00:02:11,430 +since they see tagged users. + +56 +00:02:11,430 --> 00:02:13,697 +Only tag a user when you +are completely certain + +57 +00:02:13,697 --> 00:02:16,097 +they are the best place +to answer your question. + +58 +00:02:17,730 --> 00:02:20,370 +Be precise in your text, and +if you have an error coming + +59 +00:02:20,370 --> 00:02:22,710 +from a specific piece of +code, include that code + +60 +00:02:22,710 --> 00:02:24,030 +in your post. + +61 +00:02:24,030 --> 00:02:27,210 +To make sure your post looks +good, place your question + +62 +00:02:27,210 --> 00:02:30,060 +between three backticks like this. + +63 +00:02:30,060 --> 00:02:30,990 +You can check on the right + +64 +00:02:30,990 --> 00:02:32,943 +how your post will appear once posted. + +65 +00:02:34,320 --> 00:02:35,850 +If your question is about an error, + +66 +00:02:35,850 --> 00:02:38,640 +it's even better to +include the full traceback. + +67 +00:02:38,640 --> 00:02:41,610 +As explained in the "What to +do when I get an error" video, + +68 +00:02:41,610 --> 00:02:43,763 +expand the traceback if you're on Colab. + +69 +00:02:44,769 --> 00:02:45,990 +Like for the code, put it + +70 +00:02:45,990 --> 00:02:48,300 +between two lines +containing three backticks + +71 +00:02:48,300 --> 00:02:50,160 +for proper formatting. + +72 +00:02:50,160 --> 00:02:52,740 +Our last advice is to remember to be nice. + +73 +00:02:52,740 --> 00:02:54,540 +A "Please," and a "Thank +you" will go a long way + +74 +00:02:54,540 --> 00:02:56,490 +into getting others to help you. + +75 +00:02:56,490 --> 00:02:57,780 +With all that done properly, + +76 +00:02:57,780 --> 00:03:00,143 +your question should get +an answer pretty quickly. + +77 +00:03:01,293 --> 00:03:04,344 +(title whooshes) + +78 +00:03:04,344 --> 00:03:06,034 +(title fizzles) + diff --git a/subtitles/en/73_debugging-the-training-pipeline-(pytorch).srt b/subtitles/en/73_debugging-the-training-pipeline-(pytorch).srt new file mode 100644 index 000000000..6463b1fc6 --- /dev/null +++ b/subtitles/en/73_debugging-the-training-pipeline-(pytorch).srt @@ -0,0 +1,448 @@ +1 +00:00:06,210 --> 00:00:08,760 +- In this video, we will +see how to debug an error + +2 +00:00:08,760 --> 00:00:11,896 +you encounter when running Trainer.train + +3 +00:00:11,896 --> 00:00:15,066 +As an example, we will use +this script that finetunes + +4 +00:00:15,066 --> 00:00:17,760 +a bert model on the GLUE MNLI dataset. + +5 +00:00:17,760 --> 00:00:19,470 +Checkout the videos linked below + +6 +00:00:19,470 --> 00:00:21,840 +to see how we came to such a script. + +7 +00:00:21,840 --> 00:00:24,540 +Here we want to learn how +to debug the problems in it. + +8 +00:00:25,470 --> 00:00:28,110 +Running the script gives +us an error pretty quickly. + +9 +00:00:28,110 --> 00:00:29,040 +It happens at the line + +10 +00:00:29,040 --> 00:00:30,990 +where we feed the inputs to the model, + +11 +00:00:30,990 --> 00:00:32,850 +according to the traceback. + +12 +00:00:32,850 --> 00:00:34,702 +That tells us there is a problem there, + +13 +00:00:34,702 --> 00:00:37,881 +but the problem could come +from many different causes. + +14 +00:00:37,881 --> 00:00:39,330 +To debug an error in a training, + +15 +00:00:39,330 --> 00:00:41,760 +you need to make sure each +step of the training pipeline + +16 +00:00:41,760 --> 00:00:43,440 +works as intended. + +17 +00:00:43,440 --> 00:00:45,780 +This means checking that +the inputs of your dataset + +18 +00:00:45,780 --> 00:00:47,040 +are correct, + +19 +00:00:47,040 --> 00:00:48,720 +you can batch them together, + +20 +00:00:48,720 --> 00:00:50,790 +feed them through the model to get a loss, + +21 +00:00:50,790 --> 00:00:52,500 +then compute the gradients of that loss + +22 +00:00:52,500 --> 00:00:54,303 +before performing an optimizer step. + +23 +00:00:55,470 --> 00:00:57,810 +So let's start by looking +at the training dataset + +24 +00:00:57,810 --> 00:00:59,043 +this Trainer is using. + +25 +00:00:59,910 --> 00:01:02,190 +There is definitely a problem here. + +26 +00:01:02,190 --> 00:01:04,293 +We see texts and not number. + +27 +00:01:05,130 --> 00:01:06,660 +The error message was telling us the model + +28 +00:01:06,660 --> 00:01:08,220 +did not get input IDs + +29 +00:01:08,220 --> 00:01:11,100 +and we do not have those +in the dataset indeed. + +30 +00:01:11,100 --> 00:01:12,660 +Looking back at our code, + +31 +00:01:12,660 --> 00:01:14,400 +we can see we made a mistake + +32 +00:01:14,400 --> 00:01:17,400 +and passed the wrong +datasets to the Trainer. + +33 +00:01:17,400 --> 00:01:19,173 +So let's fix that and run again. + +34 +00:01:20,490 --> 00:01:21,840 +Now we have a new error. + +35 +00:01:21,840 --> 00:01:23,130 +Inspecting the traceback + +36 +00:01:23,130 --> 00:01:25,860 +tells us it happens when +we try to create a batch, + +37 +00:01:25,860 --> 00:01:28,743 +specifically to group +the features in a tensor. + +38 +00:01:29,700 --> 00:01:32,610 +We can confirm this by asking +the Trainer to get us a batch + +39 +00:01:32,610 --> 00:01:34,230 +of the training data loader, + +40 +00:01:34,230 --> 00:01:35,913 +which reproduces the same error. + +41 +00:01:36,780 --> 00:01:39,064 +Either by inspecting +the inputs or debugging, + +42 +00:01:39,064 --> 00:01:42,870 +we can then see they are +not all of the same size. + +43 +00:01:42,870 --> 00:01:45,120 +This is because we have +not passed a data collator + +44 +00:01:45,120 --> 00:01:46,890 +to do the padding to the Trainer + +45 +00:01:46,890 --> 00:01:49,443 +and didn't pad when +preprocessing the data either. + +46 +00:01:50,430 --> 00:01:52,710 +Padding inside the Trainer +is normally the default, + +47 +00:01:52,710 --> 00:01:55,380 +but only if you provide your +tokenizer to the Trainer, + +48 +00:01:55,380 --> 00:01:57,270 +and we forgot to do that. + +49 +00:01:57,270 --> 00:01:59,120 +So let's fix the issue and run again. + +50 +00:02:00,510 --> 00:02:02,883 +This time we get a nasty CUDA error. + +51 +00:02:03,765 --> 00:02:06,285 +They are very difficult +to debug because for one, + +52 +00:02:06,285 --> 00:02:10,530 +they put your kernel in a +state that is not recoverable + +53 +00:02:10,530 --> 00:02:13,260 +so you have to restart your +notebook from the beginning + +54 +00:02:13,260 --> 00:02:16,950 +and two, the traceback is +completely useless for those. + +55 +00:02:16,950 --> 00:02:19,230 +Here the traceback tells +us the error happens + +56 +00:02:19,230 --> 00:02:22,500 +when we do the gradient +computation with loss.backward, + +57 +00:02:22,500 --> 00:02:25,113 +but as we will see later +on that is not the case. + +58 +00:02:26,520 --> 00:02:28,920 +This is because everything +that happens on the GPU + +59 +00:02:28,920 --> 00:02:30,720 +is done asynchronously. + +60 +00:02:30,720 --> 00:02:32,880 +When you execute the model call, + +61 +00:02:32,880 --> 00:02:34,457 +what the program does +is just stacking that + +62 +00:02:34,457 --> 00:02:36,600 +in the queue of GPU, + +63 +00:02:36,600 --> 00:02:39,856 +then if the GPU didn't +have any current job to do, + +64 +00:02:39,856 --> 00:02:41,850 +the work will start on +the GPU at the same time + +65 +00:02:41,850 --> 00:02:45,000 +as the CPU moves to the next instruction. + +66 +00:02:45,000 --> 00:02:47,040 +Continuing with the +extraction of the loss, + +67 +00:02:47,040 --> 00:02:49,170 +this is stacked into the GPU queue + +68 +00:02:49,170 --> 00:02:51,953 +while the CPU moves to the +instruction loss.backward. + +69 +00:02:51,953 --> 00:02:54,180 +But the GPU still hasn't finished + +70 +00:02:54,180 --> 00:02:55,710 +the forward pass of the model + +71 +00:02:55,710 --> 00:02:57,603 +since all that took no time at all. + +72 +00:02:58,440 --> 00:03:00,210 +The CPU stops moving forward, + +73 +00:03:00,210 --> 00:03:03,240 +because loss.backward as an +instruction telling it to wait + +74 +00:03:03,240 --> 00:03:04,830 +for the GPUs to be finished, + +75 +00:03:04,830 --> 00:03:06,780 +to make sure the gradients are correct. + +76 +00:03:07,650 --> 00:03:09,570 +When the GPU encounters an error, + +77 +00:03:09,570 --> 00:03:13,140 +it gives it back to the +CPU with a cryptic message + +78 +00:03:13,140 --> 00:03:15,423 +who raises the error at the wrong place. + +79 +00:03:16,350 --> 00:03:18,720 +So to debug this, we will +need to execute the next steps + +80 +00:03:18,720 --> 00:03:21,211 +of the training pipeline on the CPU. + +81 +00:03:21,211 --> 00:03:22,380 +It is very easy to do, + +82 +00:03:22,380 --> 00:03:25,350 +and we get a traceback +we can trust this time. + +83 +00:03:25,350 --> 00:03:26,520 +As we said before, + +84 +00:03:26,520 --> 00:03:28,620 +the error actually happens +during the forward pass + +85 +00:03:28,620 --> 00:03:29,453 +of the model, + +86 +00:03:29,453 --> 00:03:30,993 +and not loss.backward. + +87 +00:03:31,920 --> 00:03:33,680 +It's an index error. + +88 +00:03:33,680 --> 00:03:34,950 +With a bit of debugging, + +89 +00:03:34,950 --> 00:03:37,410 +we see we have labels ranging from 0 to 2, + +90 +00:03:37,410 --> 00:03:39,000 +so three different values, + +91 +00:03:39,000 --> 00:03:42,191 +but our outputs have a +shape of batch size per 2. + +92 +00:03:42,191 --> 00:03:45,600 +It looks like our model has +the wrong number of labels. + +93 +00:03:45,600 --> 00:03:47,190 +We can indeed confirm that, + +94 +00:03:47,190 --> 00:03:49,860 +and now that we know it's +easy to fix it in the code + +95 +00:03:49,860 --> 00:03:53,969 +by adding num_labels=3 +when we create the model. + +96 +00:03:53,969 --> 00:03:56,883 +Now the training script +will run to completion. + +97 +00:03:58,440 --> 00:03:59,430 +We did not need it yet, + +98 +00:03:59,430 --> 00:04:00,960 +but here is how we would +debug the next step + +99 +00:04:00,960 --> 00:04:02,944 +of the pipeline, gradient computation, + +100 +00:04:02,944 --> 00:04:05,850 +as well as the optimizer step. + +101 +00:04:05,850 --> 00:04:08,823 +With all of this, good luck +debugging your own trainings! + diff --git a/subtitles/en/74_debugging-the-training-pipeline-(tensorflow).srt b/subtitles/en/74_debugging-the-training-pipeline-(tensorflow).srt new file mode 100644 index 000000000..aaac0136f --- /dev/null +++ b/subtitles/en/74_debugging-the-training-pipeline-(tensorflow).srt @@ -0,0 +1,799 @@ +1 +00:00:00,212 --> 00:00:02,879 +(air whooshing) + +2 +00:00:04,680 --> 00:00:08,130 +- Some bugs in your code +are very straightforward. + +3 +00:00:08,130 --> 00:00:11,580 +You try running it, you get +a syntax error somewhere, + +4 +00:00:11,580 --> 00:00:14,490 +Python tells you exactly +where, and you fix it. + +5 +00:00:14,490 --> 00:00:17,760 +This is great, it's simple +and it's satisfying. + +6 +00:00:17,760 --> 00:00:20,310 +Sometimes, though, things crash + +7 +00:00:20,310 --> 00:00:23,670 +and the error is impossible to understand. + +8 +00:00:23,670 --> 00:00:26,700 +This happens a lot in machine +learning for a few reasons, + +9 +00:00:26,700 --> 00:00:29,310 +you're working with big data structures, + +10 +00:00:29,310 --> 00:00:31,440 +you're using these big, complex libraries + +11 +00:00:31,440 --> 00:00:33,420 +with a lot of moving parts, + +12 +00:00:33,420 --> 00:00:35,310 +and also you're doing +a lot of GPU computing, + +13 +00:00:35,310 --> 00:00:38,490 +and that in general is much +more difficult to debug. + +14 +00:00:38,490 --> 00:00:40,260 +In Keras there's the additional problem + +15 +00:00:40,260 --> 00:00:43,140 +that your models are often +compiled before execution, + +16 +00:00:43,140 --> 00:00:44,400 +which is great for performance + +17 +00:00:44,400 --> 00:00:47,430 +but it makes debugging them +very difficult as well. + +18 +00:00:47,430 --> 00:00:50,370 +So, this is going to be +a video about what to do + +19 +00:00:50,370 --> 00:00:52,410 +when you run into one +of those nightmare bugs + +20 +00:00:52,410 --> 00:00:55,210 +and you just have no idea +where to begin with fixing it. + +21 +00:00:56,370 --> 00:00:58,920 +So, to give you some intuitions for + +22 +00:00:58,920 --> 00:01:01,530 +the most common things that go wrong + +23 +00:01:01,530 --> 00:01:03,573 +and cause these weird issues, + +24 +00:01:04,800 --> 00:01:07,530 +and show you where to look +for the sources of bugs + +25 +00:01:07,530 --> 00:01:10,560 +that you encounter, let's +use this example script. + +26 +00:01:10,560 --> 00:01:12,900 +So, I'll show it to you here in two parts. + +27 +00:01:12,900 --> 00:01:16,410 +First, we do all our +imports, we load a dataset, + +28 +00:01:16,410 --> 00:01:20,280 +we create our tokenizer and +we tokenize the dataset. + +29 +00:01:20,280 --> 00:01:23,640 +Next, we convert our datasets +to TensorFlow datasets, + +30 +00:01:23,640 --> 00:01:26,100 +so that's tf.data.Dataset, + +31 +00:01:26,100 --> 00:01:28,500 +and that's so that we can run fit on them, + +32 +00:01:28,500 --> 00:01:31,170 +and then we load our model +from a pretrained checkpoint, + +33 +00:01:31,170 --> 00:01:33,870 +we compile it and we fit +it with those datasets. + +34 +00:01:33,870 --> 00:01:35,970 +So, this seems straightforward enough, + +35 +00:01:35,970 --> 00:01:38,220 +it's similar to what we've +done in the course before. + +36 +00:01:38,220 --> 00:01:40,650 +But beware, this is spooky code + +37 +00:01:40,650 --> 00:01:43,590 +and hides many dark +and mysterious secrets. + +38 +00:01:43,590 --> 00:01:46,050 +So, what happens when we run it? + +39 +00:01:46,050 --> 00:01:48,840 +Well, it's not great. + +40 +00:01:48,840 --> 00:01:52,320 +So, we get this error message, +but what does it mean? + +41 +00:01:52,320 --> 00:01:55,470 +We tried to train on our +data, but we got no gradient? + +42 +00:01:55,470 --> 00:01:59,130 +It's pretty perplexing, I +mean, how do we even begin + +43 +00:01:59,130 --> 00:02:01,500 +to debug not getting a gradient? + +44 +00:02:01,500 --> 00:02:03,930 +So, when the error you get +doesn't immediately suggest + +45 +00:02:03,930 --> 00:02:06,630 +where the problem is, the best solution + +46 +00:02:06,630 --> 00:02:09,180 +is often to walk through +things in sequence, + +47 +00:02:09,180 --> 00:02:12,900 +making sure at each stage +that the outputs look right, + +48 +00:02:12,900 --> 00:02:15,300 +that everything looks okay at that point. + +49 +00:02:15,300 --> 00:02:17,730 +And, of course, that +means the place to start + +50 +00:02:17,730 --> 00:02:19,473 +is always to check your data. + +51 +00:02:20,670 --> 00:02:22,050 +So, the best way to make sure + +52 +00:02:22,050 --> 00:02:24,480 +that the data you're +giving the model is good, + +53 +00:02:24,480 --> 00:02:27,690 +is to grab a batch from +the tf.data.Dataset + +54 +00:02:27,690 --> 00:02:29,520 +that your model is training on, + +55 +00:02:29,520 --> 00:02:31,560 +and that's because it's right at the end + +56 +00:02:31,560 --> 00:02:33,990 +of the data pipeline. + +57 +00:02:33,990 --> 00:02:36,990 +And so that means that if +those outputs are good, + +58 +00:02:36,990 --> 00:02:39,990 +you're guaranteed that your +data pipeline is working well. + +59 +00:02:39,990 --> 00:02:42,600 +So, we can do that by +looping over the dataset + +60 +00:02:42,600 --> 00:02:44,790 +for one iteration and then breaking, + +61 +00:02:44,790 --> 00:02:46,980 +and that gives us a single batch. + +62 +00:02:46,980 --> 00:02:49,443 +So, what do we get when +we inspect that batch? + +63 +00:02:50,460 --> 00:02:52,500 +We'll see that we're +not getting any gradient + +64 +00:02:52,500 --> 00:02:55,530 +because we're not passing labels to Keras. + +65 +00:02:55,530 --> 00:02:57,510 +So, our labels are in the batch, + +66 +00:02:57,510 --> 00:02:59,670 +but they're a key in the input dictionary + +67 +00:02:59,670 --> 00:03:02,340 +and they're not a separate +label as Keras expects, + +68 +00:03:02,340 --> 00:03:04,830 +so this is one of the most +common issues you'll encounter + +69 +00:03:04,830 --> 00:03:07,590 +when training Transformers +models with TensorFlow. + +70 +00:03:07,590 --> 00:03:10,980 +Our models can all +compute loss internally, + +71 +00:03:10,980 --> 00:03:13,140 +but to use that loss for training + +72 +00:03:13,140 --> 00:03:15,960 +the labels need to be passed +in the input dictionary, + +73 +00:03:15,960 --> 00:03:17,940 +where the model can see them. + +74 +00:03:17,940 --> 00:03:20,280 +This internal loss is the loss that we use + +75 +00:03:20,280 --> 00:03:23,760 +when we don't specify a +loss when we call compile, + +76 +00:03:23,760 --> 00:03:25,660 +when we don't specify a loss argument. + +77 +00:03:26,520 --> 00:03:27,870 +So, Keras, on the other hand, + +78 +00:03:27,870 --> 00:03:30,570 +usually expects labels +to be passed separately + +79 +00:03:30,570 --> 00:03:32,130 +from the input dictionary, + +80 +00:03:32,130 --> 00:03:34,110 +and not to be visible to the model, + +81 +00:03:34,110 --> 00:03:36,600 +and loss computations will usually fail + +82 +00:03:36,600 --> 00:03:38,220 +if you don't do that + +83 +00:03:38,220 --> 00:03:40,380 +So we need to choose one or the other, + +84 +00:03:40,380 --> 00:03:42,780 +either we use the model's internal loss + +85 +00:03:42,780 --> 00:03:44,940 +and keep the labels where they are, + +86 +00:03:44,940 --> 00:03:46,980 +or we keep using Keras losses + +87 +00:03:46,980 --> 00:03:50,520 +but we move the labels to +the place Keras expects them. + +88 +00:03:50,520 --> 00:03:53,310 +So, or simplicity here, +let's fix this issue + +89 +00:03:53,310 --> 00:03:55,860 +by using the model's internal losses, + +90 +00:03:55,860 --> 00:03:57,900 +and we do that by +removing the loss argument + +91 +00:03:57,900 --> 00:03:59,343 +from the call to compile. + +92 +00:04:00,540 --> 00:04:03,000 +So, what happens if we try training now? + +93 +00:04:03,000 --> 00:04:08,000 +So we recompile with that, we +call model.fit, what happens? + +94 +00:04:08,220 --> 00:04:13,050 +Well, it runs this time but +now we get a loss of NaN. + +95 +00:04:13,050 --> 00:04:16,440 +So, that's not good, +NaN means not a number + +96 +00:04:16,440 --> 00:04:19,140 +and it's not a good +loss to have in general. + +97 +00:04:19,140 --> 00:04:21,000 +In fact, if we inspect our model now, + +98 +00:04:21,000 --> 00:04:23,970 +we'll see that not only +are all the outputs NaN, + +99 +00:04:23,970 --> 00:04:27,600 +all the weights are NaN as +well, as well as the loss. + +100 +00:04:27,600 --> 00:04:30,810 +So once a single NaN creeps +into your computations, + +101 +00:04:30,810 --> 00:04:34,530 +it tends to spread, because +it propagates from the loss + +102 +00:04:34,530 --> 00:04:36,420 +and once it's at the loss +it's at the gradient, + +103 +00:04:36,420 --> 00:04:37,530 +it gets to the gradient, + +104 +00:04:37,530 --> 00:04:38,910 +and then once it's in the gradient + +105 +00:04:38,910 --> 00:04:41,280 +it enters the weight updates, + +106 +00:04:41,280 --> 00:04:43,980 +and then all your weight +updates end up as NaN as well. + +107 +00:04:43,980 --> 00:04:46,950 +So NaN just completely +destroyed our model here, + +108 +00:04:46,950 --> 00:04:49,560 +but where did it creep in first? + +109 +00:04:49,560 --> 00:04:52,140 +So to find out, we need to back to a point + +110 +00:04:52,140 --> 00:04:53,490 +before the model was destroyed, + +111 +00:04:53,490 --> 00:04:55,440 +we need to re-initialize the model + +112 +00:04:55,440 --> 00:04:58,590 +and look at the outputs +for just the first batch. + +113 +00:04:58,590 --> 00:04:59,850 +And when we do that, + +114 +00:04:59,850 --> 00:05:02,790 +we see that NaN first appears in the loss, + +115 +00:05:02,790 --> 00:05:04,980 +but only in some samples. + +116 +00:05:04,980 --> 00:05:06,540 +So you can see this in more detail + +117 +00:05:06,540 --> 00:05:09,090 +in the accompanying section +of the course notes, + +118 +00:05:09,090 --> 00:05:11,220 +I am moving fairly quickly here, + +119 +00:05:11,220 --> 00:05:13,500 +but we find that if we look at the labels, + +120 +00:05:13,500 --> 00:05:17,790 +the samples with a loss of +NaN all have a label of two. + +121 +00:05:17,790 --> 00:05:19,950 +So this gives us a very strong clue, + +122 +00:05:19,950 --> 00:05:24,060 +if we check the model with +model.config.num_labels, + +123 +00:05:24,060 --> 00:05:26,760 +we see that the model thinks +there's only two labels, + +124 +00:05:26,760 --> 00:05:28,950 +but if we see a value of two, + +125 +00:05:28,950 --> 00:05:31,200 +that means there's at least three labels + +126 +00:05:31,200 --> 00:05:33,630 +because 0 is a label as well. + +127 +00:05:33,630 --> 00:05:35,070 +So we got a loss of NaN + +128 +00:05:35,070 --> 00:05:37,887 +because we got an "impossible" +label in our label set, + +129 +00:05:37,887 --> 00:05:41,010 +and to fix that we need to +go back and set the model + +130 +00:05:41,010 --> 00:05:43,650 +to expect the right number of labels, + +131 +00:05:43,650 --> 00:05:45,870 +so we can set num_labels=3 + +132 +00:05:45,870 --> 00:05:48,540 +when we initialize the +model but from_pretrained, + +133 +00:05:48,540 --> 00:05:51,450 +and now hopefully we can avoid this issue. + +134 +00:05:51,450 --> 00:05:54,660 +So, now we think our data is +good and our model is good + +135 +00:05:54,660 --> 00:05:56,220 +and so training should work + +136 +00:05:56,220 --> 00:06:00,510 +but if we try running +model.fit, we, well... + +137 +00:06:00,510 --> 00:06:02,040 +I mean, we do get a loss, + +138 +00:06:02,040 --> 00:06:03,930 +it is a number and it is going down + +139 +00:06:03,930 --> 00:06:06,090 +but it's not going down very quickly + +140 +00:06:06,090 --> 00:06:07,770 +and if we keep running this out, + +141 +00:06:07,770 --> 00:06:10,980 +we'll find that it stalls +at a fairly high loss value. + +142 +00:06:10,980 --> 00:06:12,450 +So, what's going on? + +143 +00:06:12,450 --> 00:06:14,130 +Well, when things are mostly working, + +144 +00:06:14,130 --> 00:06:16,620 +but training is just slow or a bit odd, + +145 +00:06:16,620 --> 00:06:19,470 +that can often be a good time +to look at your optimizer + +146 +00:06:19,470 --> 00:06:22,020 +and your training hyperparameters. + +147 +00:06:22,020 --> 00:06:23,460 +And this is where I want to mention + +148 +00:06:23,460 --> 00:06:25,320 +one of the most common sources of issues + +149 +00:06:25,320 --> 00:06:27,000 +when you're working with Keras, + +150 +00:06:27,000 --> 00:06:30,870 +you can name things like +optimizers with strings, + +151 +00:06:30,870 --> 00:06:33,180 +so Keras supports that +and it's very convenient, + +152 +00:06:33,180 --> 00:06:35,460 +but if you do that all of the options + +153 +00:06:35,460 --> 00:06:38,400 +get silently set to their default values. + +154 +00:06:38,400 --> 00:06:41,190 +So we specified our optimizer as Adam, + +155 +00:06:41,190 --> 00:06:43,110 +but in the process we invisibly got + +156 +00:06:43,110 --> 00:06:46,260 +the default learning rate, which is 1e-3, + +157 +00:06:46,260 --> 00:06:48,630 +or 10 to the power of -3. + +158 +00:06:48,630 --> 00:06:50,550 +So this learning rate is way too high + +159 +00:06:50,550 --> 00:06:52,530 +for training transformer models, + +160 +00:06:52,530 --> 00:06:55,620 +we should go back and specify +the learning rate directly, + +161 +00:06:55,620 --> 00:06:57,060 +not using a string. + +162 +00:06:57,060 --> 00:07:01,290 +So, good values here are +between 1e-5 and 1e-4 + +163 +00:07:01,290 --> 00:07:04,233 +so let's split the +difference and pick 5e-5. + +164 +00:07:05,310 --> 00:07:06,990 +So if you recompile with that, + +165 +00:07:06,990 --> 00:07:09,840 +you'll find that training +actually works, at last. + +166 +00:07:09,840 --> 00:07:11,700 +The loss goes down efficiently + +167 +00:07:11,700 --> 00:07:14,070 +and it converges to a lower value. + +168 +00:07:14,070 --> 00:07:16,410 +So, again, I did go +through this quite quickly + +169 +00:07:16,410 --> 00:07:18,720 +and I strongly recommend +checking out the course notes + +170 +00:07:18,720 --> 00:07:20,040 +to see this in more detail, + +171 +00:07:20,040 --> 00:07:21,600 +and to experiment with the code yourself + +172 +00:07:21,600 --> 00:07:23,490 +and see what the errors look like + +173 +00:07:23,490 --> 00:07:25,380 +and how you can approach them, + +174 +00:07:25,380 --> 00:07:27,930 +but I hope I've given +you here a quick summary + +175 +00:07:27,930 --> 00:07:30,510 +of the most common bugs + +176 +00:07:30,510 --> 00:07:32,880 +and maybe the most common +debugging approaches + +177 +00:07:32,880 --> 00:07:33,960 +to dealing with them. + +178 +00:07:33,960 --> 00:07:37,020 +So, good luck, and remember +to take plenty of breaks + +179 +00:07:37,020 --> 00:07:38,970 +if your code is giving you a hard time. + +180 +00:07:39,805 --> 00:07:42,472 +(air whooshing) + diff --git a/subtitles/en/75_writing-a-good-issue.srt b/subtitles/en/75_writing-a-good-issue.srt new file mode 100644 index 000000000..5a03d5820 --- /dev/null +++ b/subtitles/en/75_writing-a-good-issue.srt @@ -0,0 +1,330 @@ +1 +00:00:05,610 --> 00:00:08,557 +- How to write a good issue on GitHub? + +2 +00:00:08,557 --> 00:00:10,080 +GitHub is the main place + +3 +00:00:10,080 --> 00:00:12,000 +for the Hugging Face +open source libraries, + +4 +00:00:12,000 --> 00:00:14,010 +and you should always +go there to report a bug + +5 +00:00:14,010 --> 00:00:16,020 +or ask for a new feature. + +6 +00:00:16,020 --> 00:00:18,660 +For more general questions +or to debug your own code + +7 +00:00:18,660 --> 00:00:21,707 +use the forums, see +the video linked below. + +8 +00:00:21,707 --> 00:00:23,677 +It's very important to write good issues + +9 +00:00:23,677 --> 00:00:27,232 +as it will help the bug you +uncovered be fixed in no time. + +10 +00:00:27,232 --> 00:00:29,750 +For this video, we have created +a version of Transformers + +11 +00:00:29,750 --> 00:00:31,066 +with a bug. + +12 +00:00:31,066 --> 00:00:33,783 +You can install it by executing +this command in a notebook, + +13 +00:00:33,783 --> 00:00:37,239 +remove the exclamation mark +to execute it in a terminal. + +14 +00:00:37,239 --> 00:00:41,016 +In this version, the +following example fails. + +15 +00:00:41,016 --> 00:00:42,472 +The error is rather cryptic + +16 +00:00:42,472 --> 00:00:45,184 +and does not seem to come +from anything in our code, + +17 +00:00:45,184 --> 00:00:48,157 +so it seems we have a bug to report. + +18 +00:00:48,157 --> 00:00:49,858 +The first thing to do in this case + +19 +00:00:49,858 --> 00:00:52,053 +is to try to find the smallest +amount of code possible + +20 +00:00:52,053 --> 00:00:54,059 +that reproduces the bug. + +21 +00:00:54,059 --> 00:00:56,802 +In our case, inspecting the traceback, + +22 +00:00:56,802 --> 00:00:59,645 +we see the failure happens +inside the pipeline function + +23 +00:00:59,645 --> 00:01:03,158 +when it calls +AutoTokenizer.from_pretrained. + +24 +00:01:03,158 --> 00:01:06,609 +Using the debugger, we find the +values passed to that method + +25 +00:01:06,609 --> 00:01:08,849 +and can thus create a small sample of code + +26 +00:01:08,849 --> 00:01:12,802 +that hopefully generates the same error. + +27 +00:01:12,802 --> 00:01:14,726 +It's very important to go though this step + +28 +00:01:14,726 --> 00:01:16,770 +as you may realize the +error was on your side + +29 +00:01:16,770 --> 00:01:18,360 +and not a bug in the library, + +30 +00:01:18,360 --> 00:01:20,610 +but it also will make it +easier for the maintainers + +31 +00:01:20,610 --> 00:01:22,320 +to fix your problem. + +32 +00:01:22,320 --> 00:01:24,030 +Here we can play around +a bit more with this code + +33 +00:01:24,030 --> 00:01:26,460 +and notice the error happens +for different checkpoints + +34 +00:01:26,460 --> 00:01:28,050 +and not just this one, + +35 +00:01:28,050 --> 00:01:31,262 +and that it disappears +when we use use_fast=False + +36 +00:01:31,262 --> 00:01:33,240 +inside our tokenizer call. + +37 +00:01:33,240 --> 00:01:35,190 +The important part is to have something + +38 +00:01:35,190 --> 00:01:38,640 +that does not depend on +any external files or data. + +39 +00:01:38,640 --> 00:01:40,350 +Try to replace your data by fake values + +40 +00:01:40,350 --> 00:01:41,450 +if you can't share it. + +41 +00:01:42,750 --> 00:01:43,620 +With all of this done, + +42 +00:01:43,620 --> 00:01:46,260 +we are ready to start writing our issue. + +43 +00:01:46,260 --> 00:01:48,600 +Click on the button next to Bug Report + +44 +00:01:48,600 --> 00:01:51,300 +and you will discover that +there is a template to fill. + +45 +00:01:51,300 --> 00:01:53,940 +It will only take you a couple of minutes. + +46 +00:01:53,940 --> 00:01:56,460 +The first thing is to +properly name your issue. + +47 +00:01:56,460 --> 00:01:59,100 +Don't pick a title that is too vague. + +48 +00:01:59,100 --> 00:02:02,160 +Then you have to fill your +environment information. + +49 +00:02:02,160 --> 00:02:03,330 +There is a command provided + +50 +00:02:03,330 --> 00:02:05,700 +by the Transformers library to do this. + +51 +00:02:05,700 --> 00:02:08,550 +Just execute it in your +notebook or in a terminal + +52 +00:02:08,550 --> 00:02:10,203 +and copy paste the results. + +53 +00:02:11,070 --> 00:02:13,530 +There are two last +questions to fill manually, + +54 +00:02:13,530 --> 00:02:16,023 +to which the answers are +no and no in our case. + +55 +00:02:17,550 --> 00:02:20,460 +Next, we need to determine who to tag. + +56 +00:02:20,460 --> 00:02:23,010 +There is a full list of +usernames in the template. + +57 +00:02:23,010 --> 00:02:25,043 +Since our issue has to do with tokenizers, + +58 +00:02:25,043 --> 00:02:28,170 +we pick the maintainer +associated with them. + +59 +00:02:28,170 --> 00:02:30,210 +There is no point tagging +more than 3 people, + +60 +00:02:30,210 --> 00:02:32,010 +they will redirect you to the right person + +61 +00:02:32,010 --> 00:02:33,110 +if you made a mistake. + +62 +00:02:34,410 --> 00:02:36,600 +Next, we have to give +the information necessary + +63 +00:02:36,600 --> 00:02:38,220 +to reproduce the bug. + +64 +00:02:38,220 --> 00:02:41,010 +We paste our sample, and +put it between two lines + +65 +00:02:41,010 --> 00:02:44,073 +with three backticks so that +it's formatted properly. + +66 +00:02:45,210 --> 00:02:47,010 +We also paste the full traceback, + +67 +00:02:47,010 --> 00:02:49,740 +still between two lines +of three backticks. + +68 +00:02:49,740 --> 00:02:52,650 +Lastly, we can add any +additional information + +69 +00:02:52,650 --> 00:02:55,200 +about what we tried to +debug the issue at hand. + +70 +00:02:55,200 --> 00:02:57,030 +With all of this, you +should expect an answer + +71 +00:02:57,030 --> 00:02:59,880 +to your issue pretty fast +and hopefully a quick fix. + +72 +00:02:59,880 --> 00:03:01,770 +Note that all the advise in this video + +73 +00:03:01,770 --> 00:03:04,203 +applies for almost every +open-source project. + diff --git a/subtitles/en/metadata.csv b/subtitles/en/metadata.csv new file mode 100644 index 000000000..75dda63b6 --- /dev/null +++ b/subtitles/en/metadata.csv @@ -0,0 +1,77 @@ +id,title,link,srt_filename +00GKzGyWFEs,Welcome to the Hugging Face course,https://www.youtube.com/watch?v=00GKzGyWFEs&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=1,subtitles/es/00_welcome-to-the-hugging-face-course.srt +tiZFewofSLM,The pipeline function,https://www.youtube.com/watch?v=tiZFewofSLM&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=2,subtitles/es/01_the-pipeline-function.srt +ftWlj4FBHTg,The carbon footprint of Transformers,https://www.youtube.com/watch?v=ftWlj4FBHTg&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=3,subtitles/es/02_the-carbon-footprint-of-transformers.srt +BqqfQnyjmgg,What is Transfer Learning?,https://www.youtube.com/watch?v=BqqfQnyjmgg&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=4,subtitles/es/03_what-is-transfer-learning.srt +H39Z_720T5s,The Transformer architecture,https://www.youtube.com/watch?v=H39Z_720T5s&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=5,subtitles/es/04_the-transformer-architecture.srt +MUqNwgPjJvQ,Transformer models: Encoders,https://www.youtube.com/watch?v=MUqNwgPjJvQ&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=6,subtitles/es/05_transformer-models-encoders.srt +d_ixlCubqQw,Transformer models: Decoders,https://www.youtube.com/watch?v=d_ixlCubqQw&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=7,subtitles/es/06_transformer-models-decoders.srt +0_4KEb08xrE,Transformer models: Encoder-Decoders,https://www.youtube.com/watch?v=0_4KEb08xrE&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=8,subtitles/es/07_transformer-models-encoder-decoders.srt +1pedAIvTWXk,What happens inside the pipeline function? (PyTorch),https://www.youtube.com/watch?v=1pedAIvTWXk&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=9,subtitles/es/08_what-happens-inside-the-pipeline-function-(pytorch).srt +wVN12smEvqg,What happens inside the pipeline function? (TensorFlow),https://www.youtube.com/watch?v=wVN12smEvqg&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=10,subtitles/es/09_what-happens-inside-the-pipeline-function-(tensorflow).srt +AhChOFRegn4,Instantiate a Transformers model (PyTorch),https://www.youtube.com/watch?v=AhChOFRegn4&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=11,subtitles/es/10_instantiate-a-transformers-model-(pytorch).srt +d3JVgghSOew,Instantiate a Transformers model (TensorFlow),https://www.youtube.com/watch?v=d3JVgghSOew&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=12,subtitles/es/11_instantiate-a-transformers-model-(tensorflow).srt +VFp38yj8h3A,Tokenizers overview,https://www.youtube.com/watch?v=VFp38yj8h3A&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=13,subtitles/es/12_tokenizers-overview.srt +nhJxYji1aho,Word-based tokenizers,https://www.youtube.com/watch?v=nhJxYji1aho&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=14,subtitles/es/13_word-based-tokenizers.srt +ssLq_EK2jLE,Character-based tokenizers,https://www.youtube.com/watch?v=ssLq_EK2jLE&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=15,subtitles/es/14_character-based-tokenizers.srt +zHvTiHr506c,Subword-based tokenizers,https://www.youtube.com/watch?v=zHvTiHr506c&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=16,subtitles/es/15_subword-based-tokenizers.srt +Yffk5aydLzg,The tokenization pipeline,https://www.youtube.com/watch?v=Yffk5aydLzg&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=17,subtitles/es/16_the-tokenization-pipeline.srt +M6adb1j2jPI,Batching inputs together (PyTorch),https://www.youtube.com/watch?v=M6adb1j2jPI&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=18,subtitles/es/17_batching-inputs-together-(pytorch).srt +ROxrFOEbsQE,Batching inputs together (TensorFlow),https://www.youtube.com/watch?v=ROxrFOEbsQE&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=19,subtitles/es/18_batching-inputs-together-(tensorflow).srt +_BZearw7f0w,Hugging Face Datasets overview (Pytorch),https://www.youtube.com/watch?v=_BZearw7f0w&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=20,subtitles/es/19_hugging-face-datasets-overview-(pytorch).srt +W_gMJF0xomE,Hugging Face Datasets overview (Tensorflow),https://www.youtube.com/watch?v=W_gMJF0xomE&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=21,subtitles/es/20_hugging-face-datasets-overview-(tensorflow).srt +0u3ioSwev3s,Preprocessing sentence pairs (PyTorch),https://www.youtube.com/watch?v=0u3ioSwev3s&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=22,subtitles/es/21_preprocessing-sentence-pairs-(pytorch).srt +P-rZWqcB6CE,Preprocessing sentence pairs (TensorFlow),https://www.youtube.com/watch?v=P-rZWqcB6CE&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=23,subtitles/es/22_preprocessing-sentence-pairs-(tensorflow).srt +7q5NyFT8REg,What is dynamic padding?,https://www.youtube.com/watch?v=7q5NyFT8REg&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=24,subtitles/es/23_what-is-dynamic-padding.srt +nvBXf7s7vTI,The Trainer API,https://www.youtube.com/watch?v=nvBXf7s7vTI&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=25,subtitles/es/24_the-trainer-api.srt +rnTGBy2ax1c,Keras introduction,https://www.youtube.com/watch?v=rnTGBy2ax1c&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=26,subtitles/es/25_keras-introduction.srt +AUozVp78dhk,Fine-tuning with TensorFlow,https://www.youtube.com/watch?v=AUozVp78dhk&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=27,subtitles/es/26_fine-tuning-with-tensorflow.srt +cpzq6ESSM5c,Learning rate scheduling with TensorFlow,https://www.youtube.com/watch?v=cpzq6ESSM5c&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=28,subtitles/es/27_learning-rate-scheduling-with-tensorflow.srt +nx10eh4CoOs,TensorFlow Predictions and metrics,https://www.youtube.com/watch?v=nx10eh4CoOs&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=29,subtitles/es/28_tensorflow-predictions-and-metrics.srt +Dh9CL8fyG80,Write your training loop in PyTorch,https://www.youtube.com/watch?v=Dh9CL8fyG80&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=30,subtitles/es/29_write-your-training-loop-in-pytorch.srt +s7dy8QRgjJ0,Supercharge your PyTorch training loop with Accelerate,https://www.youtube.com/watch?v=s7dy8QRgjJ0&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=31,subtitles/es/30_supercharge-your-pytorch-training-loop-with-accelerate.srt +XvSGPZFEjDY,Navigating the Model Hub,https://www.youtube.com/watch?v=XvSGPZFEjDY&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=32,subtitles/es/31_navigating-the-model-hub.srt +9yY3RB_GSPM,Managing a repo on the Model Hub,https://www.youtube.com/watch?v=9yY3RB_GSPM&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=33,subtitles/es/32_managing-a-repo-on-the-model-hub.srt +Zh0FfmVrKX0,The Push to Hub API (PyTorch),https://www.youtube.com/watch?v=Zh0FfmVrKX0&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=34,subtitles/es/33_the-push-to-hub-api-(pytorch).srt +pUh5cGmNV8Y,The Push to Hub API (TensorFlow),https://www.youtube.com/watch?v=pUh5cGmNV8Y&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=35,subtitles/es/34_the-push-to-hub-api-(tensorflow).srt +HyQgpJTkRdE,Loading a custom dataset,https://www.youtube.com/watch?v=HyQgpJTkRdE&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=36,subtitles/es/35_loading-a-custom-dataset.srt +tqfSFcPMgOI,Slice and dice a dataset 🔪,https://www.youtube.com/watch?v=tqfSFcPMgOI&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=37,subtitles/es/36_slice-and-dice-a-dataset-🔪.srt +tfcY1067A5Q,Datasets + DataFrames = ❤️,https://www.youtube.com/watch?v=tfcY1067A5Q&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=38,subtitles/es/37_datasets-+-dataframes-=-❤️.srt +blF9uxYcKHo,Saving and reloading a dataset,https://www.youtube.com/watch?v=blF9uxYcKHo&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=39,subtitles/es/38_saving-and-reloading-a-dataset.srt +JwISwTCPPWo,Memory mapping & streaming,https://www.youtube.com/watch?v=JwISwTCPPWo&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=40,subtitles/es/39_memory-mapping-&-streaming.srt +HaN6qCr_Afc,Uploading a dataset to the Hub,https://www.youtube.com/watch?v=HaN6qCr_Afc&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=41,subtitles/es/40_uploading-a-dataset-to-the-hub.srt +OATCgQtNX2o,Text embeddings & semantic search,https://www.youtube.com/watch?v=OATCgQtNX2o&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=42,subtitles/es/41_text-embeddings-&-semantic-search.srt +DJimQynXZsQ,Training a new tokenizer,https://www.youtube.com/watch?v=DJimQynXZsQ&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=43,subtitles/es/42_training-a-new-tokenizer.srt +g8quOxoqhHQ,Why are fast tokenizers called fast?,https://www.youtube.com/watch?v=g8quOxoqhHQ&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=44,subtitles/es/43_why-are-fast-tokenizers-called-fast.srt +3umI3tm27Vw,Fast tokenizer superpowers,https://www.youtube.com/watch?v=3umI3tm27Vw&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=45,subtitles/es/44_fast-tokenizer-superpowers.srt +0E7ltQB7fM8,Inside the Token classification pipeline (PyTorch),https://www.youtube.com/watch?v=0E7ltQB7fM8&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=46,subtitles/es/45_inside-the-token-classification-pipeline-(pytorch).srt +PrX4CjrVnNc,Inside the Token classification pipeline (TensorFlow),https://www.youtube.com/watch?v=PrX4CjrVnNc&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=47,subtitles/es/46_inside-the-token-classification-pipeline-(tensorflow).srt +_wxyB3j3mk4,Inside the Question answering pipeline (PyTorch),https://www.youtube.com/watch?v=_wxyB3j3mk4&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=48,subtitles/es/47_inside-the-question-answering-pipeline-(pytorch).srt +b3u8RzBCX9Y,Inside the Question answering pipeline (TensorFlow),https://www.youtube.com/watch?v=b3u8RzBCX9Y&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=49,subtitles/es/48_inside-the-question-answering-pipeline-(tensorflow).srt +4IIC2jI9CaU,What is normalization?,https://www.youtube.com/watch?v=4IIC2jI9CaU&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=50,subtitles/es/49_what-is-normalization.srt +grlLV8AIXug,What is pre-tokenization?,https://www.youtube.com/watch?v=grlLV8AIXug&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=51,subtitles/es/50_what-is-pre-tokenization.srt +HEikzVL-lZU,Byte Pair Encoding Tokenization,https://www.youtube.com/watch?v=HEikzVL-lZU&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=52,subtitles/es/51_byte-pair-encoding-tokenization.srt +qpv6ms_t_1A,WordPiece Tokenization,https://www.youtube.com/watch?v=qpv6ms_t_1A&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=53,subtitles/es/52_wordpiece-tokenization.srt +TGZfZVuF9Yc,Unigram Tokenization,https://www.youtube.com/watch?v=TGZfZVuF9Yc&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=54,subtitles/es/53_unigram-tokenization.srt +MR8tZm5ViWU,Building a new tokenizer,https://www.youtube.com/watch?v=MR8tZm5ViWU&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=55,subtitles/es/54_building-a-new-tokenizer.srt +iY2AZYdZAr0,Data processing for Token Classification,https://www.youtube.com/watch?v=iY2AZYdZAr0&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=56,subtitles/es/55_data-processing-for-token-classification.srt +8PmhEIXhBvI,Data processing for Masked Language Modeling,https://www.youtube.com/watch?v=8PmhEIXhBvI&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=57,subtitles/es/56_data-processing-for-masked-language-modeling.srt +NURcDHhYe98,What is perplexity?,https://www.youtube.com/watch?v=NURcDHhYe98&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=58,subtitles/es/57_what-is-perplexity.srt +0Oxphw4Q9fo,What is domain adaptation?,https://www.youtube.com/watch?v=0Oxphw4Q9fo&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=59,subtitles/es/58_what-is-domain-adaptation.srt +XAR8jnZZuUs,Data processing for Translation,https://www.youtube.com/watch?v=XAR8jnZZuUs&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=60,subtitles/es/59_data-processing-for-translation.srt +M05L1DhFqcw,What is the BLEU metric?,https://www.youtube.com/watch?v=M05L1DhFqcw&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=61,subtitles/es/60_what-is-the-bleu-metric.srt +1m7BerpSq8A,Data processing for Summarization,https://www.youtube.com/watch?v=1m7BerpSq8A&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=62,subtitles/es/61_data-processing-for-summarization.srt +TMshhnrEXlg,What is the ROUGE metric?,https://www.youtube.com/watch?v=TMshhnrEXlg&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=63,subtitles/es/62_what-is-the-rouge-metric.srt +ma1TrR7gE7I,Data processing for Causal Language Modeling,https://www.youtube.com/watch?v=ma1TrR7gE7I&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=64,subtitles/es/63_data-processing-for-causal-language-modeling.srt +Hm8_PgVTFuc,Using a custom loss function,https://www.youtube.com/watch?v=Hm8_PgVTFuc&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=65,subtitles/es/64_using-a-custom-loss-function.srt +qgaM0weJHpA,Data processing for Question Answering,https://www.youtube.com/watch?v=qgaM0weJHpA&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=66,subtitles/es/65_data-processing-for-question-answering.srt +BNy08iIWVJM,The Post processing step in Question Answering (PyTorch),https://www.youtube.com/watch?v=BNy08iIWVJM&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=67,subtitles/es/66_the-post-processing-step-in-question-answering-(pytorch).srt +VN67ZpN33Ss,The Post processing step in Question Answering (TensorFlow),https://www.youtube.com/watch?v=VN67ZpN33Ss&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=68,subtitles/es/67_the-post-processing-step-in-question-answering-(tensorflow).srt +-RPeakdlHYo,Data Collators: A Tour,https://www.youtube.com/watch?v=-RPeakdlHYo&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=69,subtitles/es/68_data-collators-a-tour.srt +DQ-CpJn6Rc4,What to do when you get an error?,https://www.youtube.com/watch?v=DQ-CpJn6Rc4&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=70,subtitles/es/69_what-to-do-when-you-get-an-error.srt +rSPyvPw0p9k,Using a debugger in a notebook,https://www.youtube.com/watch?v=rSPyvPw0p9k&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=71,subtitles/es/70_using-a-debugger-in-a-notebook.srt +5PkZ4rbHL6c,Using a debugger in a terminal,https://www.youtube.com/watch?v=5PkZ4rbHL6c&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=72,subtitles/es/71_using-a-debugger-in-a-terminal.srt +S2EEG3JIt2A,Asking for help on the forums,https://www.youtube.com/watch?v=S2EEG3JIt2A&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=73,subtitles/es/72_asking-for-help-on-the-forums.srt +L-WSwUWde1U,Debugging the Training Pipeline (PyTorch),https://www.youtube.com/watch?v=L-WSwUWde1U&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=74,subtitles/es/73_debugging-the-training-pipeline-(pytorch).srt +N9kO52itd0Q,Debugging the Training Pipeline (TensorFlow),https://www.youtube.com/watch?v=N9kO52itd0Q&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=75,subtitles/es/74_debugging-the-training-pipeline-(tensorflow).srt +_PAli-V4wj0,Writing a good issue,https://www.youtube.com/watch?v=_PAli-V4wj0&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=76,subtitles/es/75_writing-a-good-issue.srt diff --git a/subtitles/en/raw/chapter1/01_welcome.md b/subtitles/en/raw/chapter1/01_welcome.md new file mode 100644 index 000000000..623440b17 --- /dev/null +++ b/subtitles/en/raw/chapter1/01_welcome.md @@ -0,0 +1 @@ +Welcome to the Hugging Face Course! This course has been designed to teach you all about the Hugging Face ecosystem: how to use the dataset and model hub as well as all our open source libraries. Here is the Table of Contents. As you can see, it's divided in three sections which become progressively more advanced. At this stage, the first two sections have been released. The first will teach you the basics of how to use a Transformer model, fine-tune it on your own dataset and share the result with the community. The second will dive deeper into our libraries and teach you how to tackle any NLP task. We are actively working on the last one and hope to have it ready for you for the spring of 2022. The first chapter requires no technical knowledge and is a good introduction to learn what Transformers models can do and how they could be of use to you or your company. The next chapters require a good knowledge of Python and some basic knowledge of Machine Learning and Deep Learning. If you don't know what a training and validation set is or what gradient descent means, you should look at an introductory course such as the ones published by deeplearning.ai or fast.ai. It's also best if you have some basics in one Deep Learning Framework (PyTorch or TensorFlow). Each part of the material introduced in this course has a version in both those frameworks, so you will be able to pick the one you are most comfortable with. This is the team that developed this course. I'll now let each of the speakers introduce themselves briefly. \ No newline at end of file diff --git a/subtitles/en/raw/chapter1/03_the-pipeline-function.md b/subtitles/en/raw/chapter1/03_the-pipeline-function.md new file mode 100644 index 000000000..85602a47b --- /dev/null +++ b/subtitles/en/raw/chapter1/03_the-pipeline-function.md @@ -0,0 +1 @@ +The pipeline function. The pipeline function is the most high-level API of the Transformers library. It regroups together all the steps to go from raw texts to usable predictions. The model used is at the core of a pipeline, but the pipeline also include all the necessary pre-processing (since the model does not expect texts, but numbers) as well as some post-processing to make the output of the model human-readable. Let's look at a first example with the sentiment analysis pipeline. This pipeline performs text classification on a given input, and determines if it's positive or negative. Here, it attributed the positive label on the given text, with a confidence of 95%. You can pass multiple texts to the same pipeline, which will be processed and passed through the model together, as a batch. The output is a list of individual results, in the same order as the input texts. Here we find the same label and score for the first text, and the second text is judged positive with a confidence of 99.99%. The zero-shot classification pipeline is a more general text-classification pipeline: it allows you to provide the labels you want. Here we want to classify our input text along the labels "education", "politics" and "business". The pipeline successfully recognizes it's more about education than the other labels, with a confidence of 84%. Moving on to other tasks, the text generation pipeline will auto-complete a given prompt. The output is generated with a bit of randomness, so it changes each time you call the generator object on a given prompt. Up until now, we have used the pipeline API with the default model associated to each task, but you can use it with any model that has been pretrained or fine-tuned on this task. Going on the model hub (huggingface.co/models), you can filter the available models by task. The default model used in our previous example was gpt2, but there are many more models available, and not just in English! Let's go back to the text generation pipeline and load it with another model, distilgpt2. This is a lighter version of gpt2 created by the Hugging Face team. When applying the pipeline to a given prompt, we can specify several arguments, such as the maximum length of the generated texts, or the number of sentences we want to return (since there is some randomness in the generation). Generating text by guessing the next word in a sentence was the pretraining objective of GPT-2, the fill mask pipeline is the pretraining objective of BERT, which is to guess the value of masked word. In this case, we ask the two most likely values for the missing words (according to the model) and get mathematical or computational as possible answers. Another task Transformers model can perform is to classify each word in the sentence instead of the sentence as a whole. One example of this is Named Entity Recognition, which is the task of identifying entities, such as persons, organizations or locations in a sentence. Here, the model correctly finds the person (Sylvain), the organization (Hugging Face) as well as the location (Brooklyn) inside the input text. The grouped_entities=True argument used is to make the pipeline group together the different words linked to the same entity (such as Hugging and Face here). Another task available with the pipeline API is extractive question answering. Providing a context and a question, the model will identify the span of text in the context containing the answer to the question. Getting short summaries of very long articles is also something the Transformers library can help with, with the summarization pipeline. Finally, the last task supported by the pipeline API is translation. Here we use a French/English model found on the model hub to get the English version of our input text. Here is a brief summary of all the tasks we looked into in this video. Try then out through the inference widgets in the model hub! \ No newline at end of file diff --git a/subtitles/en/raw/chapter1/04a_the-carbon-footprint.md b/subtitles/en/raw/chapter1/04a_the-carbon-footprint.md new file mode 100644 index 000000000..8b666a239 --- /dev/null +++ b/subtitles/en/raw/chapter1/04a_the-carbon-footprint.md @@ -0,0 +1 @@ +Was recorded adlib - need to generate transcript with Whisper :) \ No newline at end of file diff --git a/subtitles/en/raw/chapter1/04b_what-is-transfer-learning.md b/subtitles/en/raw/chapter1/04b_what-is-transfer-learning.md new file mode 100644 index 000000000..ee620e56b --- /dev/null +++ b/subtitles/en/raw/chapter1/04b_what-is-transfer-learning.md @@ -0,0 +1 @@ +What is transfer learning? The idea of Transfer Learning is to leverage the knowledge acquired by a model trained with lots of data on another task. The model A will be trained specifically for task A. Now, let's say you want to train a model B for a different task. One option would be to train the model from scratch. This could take lots of computation, time and data. Instead, we could initialize model B with the same weights as model A, transferring the knowledge of model A on task B. When training from scratch, all the model’s weight are initialized randomly. In this example, we are training a BERT model on the task of recognizing if two sentences are similar or not. On the left, it’s trained from scratch, and on the right, it’s fine-tuning a pretrained model. As we can see, using transfer learning and the pretrained model yields better results. And it doesn’t matter if we train longer, the training from scratch is capped around 70% accuracy while the pretrained model beats the 86% easily. This is because pretrained models are usually trained on large amounts of data that provide the model with a statistical understanding of the language used during pretraining. In computer vision, transfer learning has been applied successfully for almost ten years. Models are frequently pretrained on ImageNet, a dataset containing 1.2 millions of photo images. Each image is classified by one of 1000 labels. Training like this, on labeled data is called supervised learning. In Natural Language Processing, transfer learning is a bit more recent. A key difference with ImageNet is that the pretraining is usually self-supervised, which means it doesn’t require humans annotations for the labels. A very common pretraining objective is to guess the next word in a sentence, which only requires lots and lots of text. GPT-2 for instance, was pretrained this way using the content of 45 millions links posted by users on Reddit. Another example of self-supervised pretraining objective is to predict the value of randomly masked words, which is similar to fill-in-the-blank tests you may have done in school. BERT was pretrained this way using the English Wikipedia and 11,000 unpublished books. In practice, transfer learning is applied on a given model by throwing away its head, that is, its last layers focused on the pretraining objective, and replacing it with a new, randomly initialized, head suitable for the task at hand. For instance, when we fine-tuned a BERT model earlier, we removed the head that classified mask words and replaced it with a classifier with 2 outputs, since our task had two labels. To be as efficient as possible, the pretrained model used should be as similar as possible to the task it’s fine-tuned on. For instance, if the problem it’s to classify German sentences, it’s best to use a German pretrained model. But with the good comes the bad. The pretrained model does not only transfer its knowledge, but also any bias it may contain. ImageNet mostly contains images coming from the United States and Western Europe, so models fine-tuned with it usually will perform better on images from these countries. OpenAI also studied the bias in the predictions of its GPT-3 model (which was pretrained using the guess the next work objective). Changing the gender of the prompt from "He was very" to "She was very" changed the predictions from mostly neutral adjectives to almost only physical ones. In their model card of the GPT-2 model, OpenAI also acknowledges its bias and discourages its use in systems that interact with humans. \ No newline at end of file diff --git a/subtitles/en/raw/chapter1/04c_the-transformer-architecture.md b/subtitles/en/raw/chapter1/04c_the-transformer-architecture.md new file mode 100644 index 000000000..3709f2f1a --- /dev/null +++ b/subtitles/en/raw/chapter1/04c_the-transformer-architecture.md @@ -0,0 +1 @@ +Let's study the transformer architecture. This video is the introductory video to the encoders, decoders, and encoder-decoder series of videos. In this series, we'll try to understand what makes a Transformer network, and we'll try to explain it in simple, high-level terms. No understanding of neural networks is necessary, only an understanding of basic vectors and tensors may help. To get started, we'll take up this diagram from the original transformer paper, entitled "Attention is all you need". As we'll see here we can leverage only some parts of it, according to what we're trying to do. We won't dive into the specific layers building up that architecture, but we'll try to understand the different ways this architecture can be used. Let's first start by splitting that architecture into two parts. On the left we have the encoder, and on the right, the decoder. These two can be used together, but they can also be used independently! Let's understand how these work: The encoder accepts inputs that represent text. It converts this text, these words, into numerical representations. These numerical representations can also be called embeddings, or features. We'll see that it uses the self-attention mechanism as its main component. We recommend you check out the video on encoders especially to understand what is this numerical representation, as well as how it works. We'll study the self-attention mechanism as well as its bi-directional properties. The decoder is similar to the encoder: it can also accept the same inputs as the encoder: inputs that represent text. It uses a similar mechanism as the encoder, which is the masked self-attention as well. It differs from the encoder due to its uni-directional property, and is traditionally used in an auto-regressive manner. Here too, we recommend you check out the video on decoders especially to understand how all of this works. Combining the two parts results in what is known as an encoder-decoder, or a sequence-to-sequence transformer. The encoder accepts inputs and computes a high-level representation of those inputs. These outputs are then passed to the decoder. The decoder uses the encoder's output alongside other inputs, in order to generate a prediction. It then predicts an output, which it will re-use in future iterations, hence the term "auto-regressive". Finally, to get an understanding of the encoder-decoders as a whole, we recommend you check out the video on encoder-decoders. \ No newline at end of file diff --git a/subtitles/en/raw/chapter1/05_encoders.md b/subtitles/en/raw/chapter1/05_encoders.md new file mode 100644 index 000000000..32337eeb0 --- /dev/null +++ b/subtitles/en/raw/chapter1/05_encoders.md @@ -0,0 +1,2 @@ +In this video, we'll study the encoder architecture. An example of a popular encoder-only architecture is BERT, which is the most popular model of its kind. Let's first start by understanding how it works. We'll use a small example, using three words. We use these as inputs, and pass them through the encoder. We retrieve a numerical representation of each word. Here, for example, the encoder converts the three words “Welcome to NYC” in these three sequences of numbers. The encoder outputs exactly one sequence of numbers per input word. This numerical representation can also be called a "Feature vector", or "Feature tensor". +Let's dive in this representation. It contains one vector per word that was passed through the encoder. Each of these vector is a numerical representation of the word in question. The dimension of that vector is defined by the architecture of the model, for the base BERT model, it is 768. These representations contain the value of a word; but contextualized. For example, the vector attributed to the word "to", isn't the representation of only the "to" word. It also takes into account the words around it, which we call the “context”.As in, it looks to the left context, the word on the left of the one we're studying (here the word "Welcome") and the context on the right (here the word "NYC") and outputs a value for the word, within its context. It is therefore a contextualized value. One could say that the vector of 768 values holds the "meaning" of that word in the text. How it does this is thanks to the self-attention mechanism. The self-attention mechanism relates to different positions (or different words) in a single sequence, in order to compute a representation of that sequence. As we've seen before, this means that the resulting representation of a word has been affected by other words in the sequence. We won't dive into the specifics here, but we'll offer some further readings if you want to get a better understanding at what happens under the hood. So when should one use an encoder? Encoders can be used as standalone models in a wide variety of tasks. For example BERT, arguably the most famous transformer model, is a standalone encoder model and at the time of release, beat the state of the art in many sequence classification tasks, question answering tasks, and masked language modeling, to only cite a few. The idea is that encoders are very powerful at extracting vectors that carry meaningful information about a sequence. This vector can then be handled down the road by additional layers of neurons to make sense of them. Let's take a look at some examples where encoders really shine. First of all, Masked Language Modeling, or MLM. It's the task of predicting a hidden word in a sequence of words. Here, for example, we have hidden the word between "My" and "is". This is one of the objectives with which BERT was trained: it was trained to predict hidden words in a sequence. Encoders shine in this scenario in particular, as bidirectional information is crucial here. If we didn't have the words on the right (is, Sylvain, and the dot), then there is very little chance that BERT would have been able to identify "name" as the correct word. The encoder needs to have a good understanding of the sequence in order to predict a masked word, as even if the text is grammatically correct, It does not necessarily make sense in the context of the sequence. As mentioned earlier, encoders are good at doing sequence classification. Sentiment analysis is an example of a sequence classification task. The model's aim is to identify the sentiment of a sequence – it can range from giving a sequence a rating from one to five stars if doing review analysis, to giving a positive or negative rating to a sequence, which is what is shown here. For example here, given the two sequences, we use the model to compute a prediction and to classify the sequences among these two classes: positive and negative. While the two sequences are very similar, containing the same words, the meaning is different – and the encoder model is able to grasp that difference. \ No newline at end of file diff --git a/subtitles/en/raw/chapter1/06_decoders.md b/subtitles/en/raw/chapter1/06_decoders.md new file mode 100644 index 000000000..d05250f39 --- /dev/null +++ b/subtitles/en/raw/chapter1/06_decoders.md @@ -0,0 +1 @@ +In this video, we'll study the decoder architecture. An example of a popular decoder-only architecture is GPT-2. In order to understand how decoders work, we recommend taking a look at the video regarding encoders: they're extremely similar to decoders. One can use a decoder for most of the same tasks as an encoder, albeit with, generally, a little loss of performance. Let's take the same approach we have taken with the encoder to try and understand the architectural differences between an encoder and a decoder. We'll use a small example, using three words. We pass them through the decoder. We retrieve a numerical representation of each word. Here, for example, the decoder converts the three words “Welcome to NYC” in these three sequences of numbers. The decoder outputs exactly one sequence of numbers per input word. This numerical representation can also be called a "Feature vector", or "Feature tensor". Let's dive in this representation. It contains one vector per word that was passed through the decoder. Each of these vector is a numerical representation of the word in question. The dimension of that vector is defined by the architecture of the model. Where the decoder differs from the encoder is principally with its self-attention mechanism. It's using what is called "masked self-attention". Here for example, if we focus on the word "to", we'll see that its vector is absolutely unmodified by the "NYC" word. That's because all the words on the right (also known as the right context) of the word is masked. Rather than benefitting from all the words on the left and right, I.e., the bidirectional context, decoders only have access to the words on their left. The masked self-attention mechanism differs from the self-attention mechanism by using an additional mask to hide the context on either side of the word: the word's numerical representation will not be affected by the words in the hidden context. So when should one use a decoder? Decoders, like encoders, can be used as standalone models. As they generate a numerical representation, they can also be used in a wide variety of tasks. However, the strength of a decoder lies in the way a word has access to its left context. The decoders, having only access to their left context, are inherently good at text generation: the ability to generate a word, or a sequence of words, given a known sequence of words. In NLP, this is known as Causal Language Modeling. Let's look at an example. Here's an example of how causal language modeling works: we start with an initial word, which is "My". We use this as input for the decoder. The model outputs a vectors of dimension 768. This vector contains information about the sequence, which is here a single word, or word. We apply a small transformation to that vector so that it maps to all the words known by the model (mapping which we'll see later, called a language modeling head). We identify that the model believes the most probable following word is "name". We then take that new word, and add it to the initial sequence. From "My", we are now at "My name". This is where the "autoregressive" aspect comes in. Auto-regressive models re-use their past outputs as inputs in the following steps. Once again, we do that the exact same operation: we cast that sequence through the decoder, and retrieve the most probable following word. In this case, it is the word "is". We repeat the operation until we're satisfied. Starting from a single word, we've now generated a full sentence. We decide to stop there, but we could continue for a while; GPT-2, for example, has a maximum context size of 1024. We could eventually generate up to 1024 words, and the decoder would still have some memory of the first words of the sequence! If we go back several levels higher, back to the full transformer model, we can see what we learned about the decoder part of the full transformer model. It is what we call, auto-regressive: it outputs values that are then used as its input values. We repeat this operations as we like. It is based off of the masked self-attention layer, which allows to have word embeddings which have access to the context on the left side of the word. If you look at the diagram however, you'll see that we haven't seen one of the aspects of the decoder. That is: cross-attention. There is a second aspect we haven't seen, which is it's ability to convert features to words; heavily linked to the cross attention mechanism. However, these only apply in the "encoder-decoder" transformer, or the "sequence-to-sequence" transformer (which can generally be used interchangeably). We recommend you check out the video on encoder-decoders to get an idea of how the decoder can be used as a component of a larger architecture! \ No newline at end of file diff --git a/subtitles/en/raw/chapter1/07_encoder-decoders.md b/subtitles/en/raw/chapter1/07_encoder-decoders.md new file mode 100644 index 000000000..a96d95038 --- /dev/null +++ b/subtitles/en/raw/chapter1/07_encoder-decoders.md @@ -0,0 +1 @@ +In this video, we'll study the encoder-decoder architecture. An example of a popular encoder-decoder model is T5. In order to understand how the encoder-decoder works, we recommend you check out the videos on encoders and decoders as standalone models. Understanding how they behave individually will help understanding how an encoder-decoder behaves. Let's start from what we've seen about the encoder. The encoder takes words as inputs, casts them through the encoder, and retrieves a numerical representation for each word cast through it. We now know that the numerical representation holds information about the meaning of the sequence. Let's put this aside and add the decoder to the diagram. In this scenario, we're using the decoder in a manner that we haven't seen before. We're passing the outputs of the encoder directly to it! Additionally to the encoder outputs, we also give the decoder a sequence. When prompting the decoder for an output with no initial sequence, we can give it the value that indicates the start of a sequence. And that's where the encoder-decoder magic happens. The encoder accepts a sequence as input. It computes a prediction, and outputs a numerical representation. Then, it sends that over to the decoder. It has, in a sense, encoded the sequence. And the decoder, in turn, using this input alongside its usual sequence input, will take a stab at decoding the sequence. The decoder decodes the sequence, and outputs a word. As of now, we don't need to make sense of that word, but we can understand that the decoder is essentially decoding what the encoder has output. The "start of sequence word" indicates that it should start decoding the sequence. Now that we have both the feature vector and an initial generated word, we don't need the encoder anymore. As we have seen before with the decoder, it can act in an auto-regressive manner; the word it has just output can now be used as an input. This, in combination with the numerical representation output by the encoder, can now be used to generate a second word. Please note that the first word is still here; as the model still outputs it. However, it is greyed out as we have no need for it anymore. We can continue on and on; for example until the decoder outputs a value that we consider a "stopping value", like a dot, meaning the end of a sequence. Here, we've seen the full mechanism of the encoder-decoder transformer: let's go over it one more time. We have an initial sequence, that is sent to the encoder. That encoder output is then sent to the decoder, for it to be decoded. While we can now discard the encoder after a single use, the decoder will be used several times: until we have generated every word that we need. Let's see a concrete example; with Translation Language Modeling; also called transduction; the act of translating a sequence. Here, we would like to translate this English sequence "Welcome to NYC" in French. We're using a transformer model that is trained for that task explicitly. We use the encoder to create a representation of the English sentence. We cast this to the decoder and, with the use of the start of sequence word, we ask it to output the first word. It outputs Bienvenue, which means "Welcome". We then use "Bienvenue" as the input sequence for the decoder. This, alongside the feature vector, allows the decoder to predict the second word, "à", which is "to" in English. Finally, we ask the decoder to predict a third word; it predicts "NYC", which is, once again, correct. We've translated the sentence! Where the encoder-decoder really shines, is that we have an encoder and a decoder; which often do not share weights. We, therefore, have an entire block (the encoder) that can be trained to understand the sequence, and extract the relevant information. For the translation scenario we've seen earlier, for example, this would mean parsing and understanding what was said in the English language; extracting information from that language, and putting all of that in a vector dense in information. On the other hand, we have the decoder, whose sole purpose is to decode the feature output by the encoder. This decoder can be specialized in a completely different language, or even modality like images or speech. Encoders-decoders are special for several reasons. Firstly, they're able to manage sequence to sequence tasks, like translation that we have just seen. Secondly, the weights between the encoder and the decoder parts are not necessarily shared. Let's take another example of translation. Here we're translating "Transformers are powerful" in French. Firstly, this means that from a sequence of three words, we're able to generate a sequence of four words. One could argue that this could be handled with a decoder; that would generate the translation in an auto-regressive manner; and they would be right! Another example of where sequence to sequence transformers shine is in summarization. Here we have a very long sequence, generally a full text, and we want to summarize it. Since the encoder and decoders are separated, we can have different context lengths (for example a very long context for the encoder which handles the text, and a smaller context for the decoder which handles the summarized sequence). There are a lot of sequence to sequence models. This contains a few examples of popular encoder-decoder models available in the transformers library. Additionally, you can load an encoder and a decoder inside an encoder-decoder model! Therefore, according to the specific task you are targeting, you may choose to use specific encoders and decoders, which have proven their worth on these specific tasks. This wraps things up for the encoder-decoders. Thanks for watching! \ No newline at end of file diff --git a/subtitles/en/raw/chapter2/02_inside-pipeline-pt.md b/subtitles/en/raw/chapter2/02_inside-pipeline-pt.md new file mode 100644 index 000000000..55c1a5da2 --- /dev/null +++ b/subtitles/en/raw/chapter2/02_inside-pipeline-pt.md @@ -0,0 +1 @@ +What happens inside the pipeline function? In this video, we will look at what actually happens when we use the pipeline function of the Transformers library. More specifically, we will look at the sentiment analysis pipeline, and how it went from the two following sentences to the positive labels with their respective scores. As we have seen in the pipeline presentation, there are three stages in the pipeline. First, we convert the raw texts to numbers the model can make sense of, using a tokenizer. Then, those numbers go through the model, which outputs logits. Finally, the post-processing steps transforms those logits into labels and scores. Let's look in detail at those three steps, and how to replicate them using the Transformers library, beginning with the first stage, tokenization. The tokenization process has several steps. First, the text is split into small chunks called tokens. They can be words, parts of words or punctuation symbols. Then the tokenizer will had some special tokens (if the model expect them). Here the model uses expects a CLS token at the beginning and a SEP token at the end of the sentence to classify. Lastly, the tokenizer matches each token to its unique ID in the vocabulary of the pretrained model. To load such a tokenizer, the Transformers library provides the AutoTokenizer API. The most important method of this class is from_pretrained, which will download and cache the configuration and the vocabulary associated to a given checkpoint. Here, the checkpoint used by default for the sentiment analysis pipeline is distilbert base uncased finetuned sst2 english. We instantiate a tokenizer associated with that checkpoint, then feed it the two sentences. Since those two sentences are not of the same size, we will need to pad the shortest one to be able to build an array. This is done by the tokenizer with the option padding=True. With truncation=True, we ensure that any sentence longer than the maximum the model can handle is truncated. Lastly, the return_tensors option tells the tokenizer to return a PyTorch tensor. Looking at the result, we see we have a dictionary with two keys. Input IDs contains the IDs of both sentences, with 0s where the padding is applied. The second key, attention mask, indicates where padding has been applied, so the model does not pay attention to it. This is all what is inside the tokenization step. Now let's have a look at the second step, the model. As for the tokenizer, there is an AutoModel API, with a from_pretrained method. It will download and cache the configuration of the model as well as the pretrained weights. However, the AutoModel API will only instantiate the body of the model, that is, the part of the model that is left once the pretraining head is removed. It will output a high-dimensional tensor that is a representation of the sentences passed, but which is not directly useful for our classification problem. Here the tensor has two sentences, each of sixteen tokens and the last dimension is the hidden size of our model 768. To get an output linked to our classification problem, we need to use the AutoModelForSequenceClassification class. It works exactly as the AutoModel class, except that it will build a model with a classification head. There is one auto class for each common NLP task in the Transformers library. Here, after giving our model the two sentences, we get a tensor of size two by two: one result for each sentence and for each possible label. Those outputs are not probabilities yet (we can see they don't sum to 1). This is because each model of the Transformers library returns logits. To make sense of those logits, we need to dig into the third and last step of the pipeline: post-processing. To convert logits into probabilities, we need to apply a SoftMax layer to them. As we can see, this transforms them into positive numbers that sum up to 1. The last step is to know which of those corresponds to the positive or the negative label. This is given by the id2label field of the model config. The first probabilities (index 0) correspond to the negative label, and the seconds (index 1) correspond to the positive label. This is how our classifier built with the pipeline function picked those labels and computed those scores. Now that you know how each steps works, you can easily tweak them to your needs. \ No newline at end of file diff --git a/subtitles/en/raw/chapter2/02_inside-pipeline-tf.md b/subtitles/en/raw/chapter2/02_inside-pipeline-tf.md new file mode 100644 index 000000000..0eae98b25 --- /dev/null +++ b/subtitles/en/raw/chapter2/02_inside-pipeline-tf.md @@ -0,0 +1 @@ +What happens inside the pipeline function? In this video, we will look at what actually happens when we use the pipeline function of the Transformers library. More specifically, we will look at the sentiment analysis pipeline, and how it went from the two following sentences to the positive labels with their respective scores. As we have seen in the pipeline presentation, there are three stages in the pipeline. First, we convert the raw texts to numbers the model can make sense of, using a tokenizer. Then, those numbers go through the model, which outputs logits. Finally, the post-processing steps transforms those logits into labels and scores. Let's look in detail at those three steps, and how to replicate them using the Transformers library, beginning with the first stage, tokenization. The tokenization process has several steps. First, the text is split into small chunks called tokens. They can be words, parts of words or punctuation symbols. Then the tokenizer will had some special tokens (if the model expect them). Here the model uses expects a CLS token at the beginning and a SEP token at the end of the sentence to classify. Lastly, the tokenizer matches each token to its unique ID in the vocabulary of the pretrained model. To load such a tokenizer, the Transformers library provides the AutoTokenizer API. The most important method of this class is from_pretrained, which will download and cache the configuration and the vocabulary associated to a given checkpoint. Here, the checkpoint used by default for the sentiment analysis pipeline is distilbert base uncased finetuned sst2 english. We instantiate a tokenizer associated with that checkpoint, then feed it the two sentences. Since those two sentences are not of the same size, we will need to pad the shortest one to be able to build an array. This is done by the tokenizer with the option padding=True. With truncation=True, we ensure that any sentence longer than the maximum the model can handle is truncated. Lastly, the return_tensors option tells the tokenizer to return a TensorFlow tensor. Looking at the result, we see we have a dictionary with two keys. Input IDs contains the IDs of both sentences, with 0s where the padding is applied. The second key, attention mask, indicates where padding has been applied, so the model does not pay attention to it. This is all what is inside the tokenization step. Now let's have a look at the second step, the model. As for the tokenizer, there is an TFAutoModel API, with a from_pretrained method. It will download and cache the configuration of the model as well as the pretrained weights. However, the TFAutoModel API will only instantiate the body of the model, that is, the part of the model that is left once the pretraining head is removed. It will output a high-dimensional tensor that is a representation of the sentences passed, but which is not directly useful for our classification problem. Here the tensor has two sentences, each of sixteen tokens and the last dimension is the hidden size of our model 768. To get an output linked to our classification problem, we need to use the TFAutoModelForSequenceClassification class. It works exactly as the AutoModel class, except that it will build a model with a classification head. There is one auto class for each common NLP task in the Transformers library. Here, after giving our model the two sentences, we get a tensor of size two by two: one result for each sentence and for each possible label. Those outputs are not probabilities yet (we can see they don't sum to 1). This is because each model of the Transformers library returns logits. To make sense of those logits, we need to dig into the third and last step of the pipeline: post-processing. To convert logits into probabilities, we need to apply a SoftMax layer to them. As we can see, this transforms them into positive numbers that sum up to 1. The last step is to know which of those corresponds to the positive or the negative label. This is given by the id2label field of the model config. The first probabilities (index 0) correspond to the negative label, and the seconds (index 1) correspond to the positive label. This is how our classifier built with the pipeline function picked those labels and computed those scores. Now that you know how each steps works, you can easily tweak them to your needs. \ No newline at end of file diff --git a/subtitles/en/raw/chapter2/03_model-api-pt.md b/subtitles/en/raw/chapter2/03_model-api-pt.md new file mode 100644 index 000000000..e41cf031e --- /dev/null +++ b/subtitles/en/raw/chapter2/03_model-api-pt.md @@ -0,0 +1 @@ +How to instantiate a Transformers model? In this video we will look at how we can create and use a model from the Transformers library. As we've seen before, the AutoModel class allows you to instantiate a pretrained model from any checkpoint on the Hugging Face Hub. It will pick the right model class from the library to instantiate the proper architecture and load the weights of the pretrained model inside it. As we can see, when given a BERT checkpoint, we end up with a BertModel, and similarly for GPT-2 or BART. Behind the scenes, this API can take the name of a checkpoint on the Hub, in which case it will download and cache the configuration file as well as the model weights file. You can also specify the path to a local folder that contains a valid configuration file and a model weights file. To instantiate the pretrained model, the AutoModel API will first open the configuration file to look at the configuration class that should be used. The configuration class depends on the type of the model (BERT, GPT-2 or BART for instance). Once it has the proper configuration class, it can instantiate that configuration, which is a blueprint to know how to create the model. It also uses this configuration class to find the proper model class, which is combined with the loaded configuration, to load the model. This model is not yet our pretrained model as it has just been initialized with random weights. The last step is to load the weights from the model file inside this model. To easily load the configuration of a model from any checkpoint or a folder containing the configuration folder, we can use the AutoConfig class. Like the AutoModel class, it will pick the right configuration class from the library. We can also use the specific class corresponding to a checkpoint, but we will need to change the code each time we want to try a different model. As we said before, the configuration of a model is a blueprint that contains all the information necessary to create the model architecture. For instance the BERT model associated with the bert-base-cased checkpoint has 12 layers, a hidden size of 768, and a vocabulary size of 28,996. Once we have the configuration, we can create a model that has the same architecture as our checkpoint but is randomly initialized. We can then train it from scratch like any PyTorch module/TensorFlow model. We can also change any part of the configuration by using keyword arguments. The second snippet of code instantiates a randomly initialized BERT model with ten layers instead of 12. Saving a model once it's trained or fine-tuned is very easy: we just have to use the save_pretrained method. Here the model will be saved in a folder named my-bert-model inside the current working directory. Such a model can then be reloaded using the from_pretrained method. \ No newline at end of file diff --git a/subtitles/en/raw/chapter2/03_model-api-tf.md b/subtitles/en/raw/chapter2/03_model-api-tf.md new file mode 100644 index 000000000..b1bcf5b46 --- /dev/null +++ b/subtitles/en/raw/chapter2/03_model-api-tf.md @@ -0,0 +1 @@ +How to instantiate a Transformers model? In this video we will look at how we can create and use a model from the Transformers library. As we've seen before, the TFAutoModel class allows you to instantiate a pretrained model from any checkpoint on the Hugging Face Hub. It will pick the right model class from the library to instantiate the proper architecture and load the weights of the pretrained model inside it. As we can see, when given a BERT checkpoint, we end up with a TFBertModel, and similarly for GPT-2 or BART. Behind the scenes, this API can take the name of a checkpoint on the Hub, in which case it will download and cache the configuration file as well as the model weights file. You can also specify the path to a local folder that contains a valid configuration file and a model weights file. To instantiate the pretrained model, the AutoModel API will first open the configuration file to look at the configuration class that should be used. The configuration class depends on the type of the model (BERT, GPT-2 or BART for instance). Once it has the proper configuration class, it can instantiate that configuration, which is a blueprint to know how to create the model. It also uses this configuration class to find the proper model class, which is combined with the loaded configuration, to load the model. This model is not yet our pretrained model as it has just been initialized with random weights. The last step is to load the weights from the model file inside this model. To easily load the configuration of a model from any checkpoint or a folder containing the configuration folder, we can use the AutoConfig class. Like the TFAutoModel class, it will pick the right configuration class from the library. We can also use the specific class corresponding to a checkpoint, but we will need to change the code each time we want to try a different model. As we said before, the configuration of a model is a blueprint that contains all the information necessary to create the model architecture. For instance the BERT model associated with the bert-base-cased checkpoint has 12 layers, a hidden size of 768, and a vocabulary size of 28,996. Once we have the configuration, we can create a model that has the same architecture as our checkpoint but is randomly initialized. We can then train it from scratch like any PyTorch module/TensorFlow model. We can also change any part of the configuration by using keyword arguments. The second snippet of code instantiates a randomly initialized BERT model with ten layers instead of 12. Saving a model once it's trained or fine-tuned is very easy: we just have to use the save_pretrained method. Here the model will be saved in a folder named my-bert-model inside the current working directory. Such a model can then be reloaded using the from_pretrained method. \ No newline at end of file diff --git a/subtitles/en/raw/chapter2/04a_tokenizers-overview.md b/subtitles/en/raw/chapter2/04a_tokenizers-overview.md new file mode 100644 index 000000000..120ec01ec --- /dev/null +++ b/subtitles/en/raw/chapter2/04a_tokenizers-overview.md @@ -0,0 +1 @@ +In these few videos, we'll take a look at the tokenizers. In Natural Language Processing, most of the data that we handle consists of raw text. However, machine learning models cannot read and understand text in its raw form they can only work with numbers. The tokenizer's objective will be to translate the text into numbers. There are several possible approaches to this conversion, and the objective is to find the most meaningful representation. We'll take a look at three distinct tokenization algorithms. We compare them one to one, so we recommend you look at the videos in the following order: Word-based, Character-based, and Subword-based. \ No newline at end of file diff --git a/subtitles/en/raw/chapter2/04b_word-based-tokenizers.md b/subtitles/en/raw/chapter2/04b_word-based-tokenizers.md new file mode 100644 index 000000000..27eb4811d --- /dev/null +++ b/subtitles/en/raw/chapter2/04b_word-based-tokenizers.md @@ -0,0 +1 @@ +Let's take a look at word-based tokenization. Word-based tokenization is the idea of splitting the raw text into words, by splitting on spaces or other specific rules like punctuation. In this algorithm, each word has a specific number, an "ID", attributed to it. In this example, "Let's" has the ID 250, do has ID 861, and tokenization followed by an exclamation point has the ID 345. This approach is interesting, as the model has representations that are based on entire words. The information held in a single number is high as a word contains a lot of contextual and semantic information in a sentence. However, this approach does have its limits. For example, the word dog and the word dogs are very similar, and their meaning is close. However, the word-based tokenization will attribute entirely different IDs to these two words, and the model will therefore learn different meanings for these two words. This is unfortunate, as we would like the model to understand that these words are indeed related and that dogs is the plural form of the word dog. Another issue with this approach is that there are a lot of different words in a language. If we want our model to understand all possible sentences in that language, then we will need to have an ID for each different word, and the total number of words, which is also known as the vocabulary size, can quickly become very large. This is an issue because each ID is mapped to a large vector that represents the word's meaning, and keeping track of these mappings requires an enormous number of weights when the vocabulary size is large. If we want our models to stay lean, we can opt for our tokenizer to ignore certain words that we don't necessarily need. For example, when training our tokenizer on a text, we might want to take the 10,000 most frequent words in that text to create our basic vocabulary, instead of taking all of that language's words. The tokenizer will know how to convert those 10,000 words into numbers, but any other word will be converted to the out-of-vocabulary word, or the "unknown" word. This can rapidly become an issue: the model will have the exact same representation for all words that it doesn't know, which will result in a lot of lost information. \ No newline at end of file diff --git a/subtitles/en/raw/chapter2/04c_character-based-tokenizers.md b/subtitles/en/raw/chapter2/04c_character-based-tokenizers.md new file mode 100644 index 000000000..3095ed668 --- /dev/null +++ b/subtitles/en/raw/chapter2/04c_character-based-tokenizers.md @@ -0,0 +1 @@ +Before diving in character-based tokenization, understanding why this kind of tokenization is interesting requires understanding the flaws of word-based tokenization. If you haven't seen the first video on word-based tokenization we recommend you check it out before looking at this video. Let's take a look at character-based tokenization. We now split our text into individual characters, rather than words. There are generally a lot of different words in languages, while the number of characters stays low. Here for example, for the English language that has an estimated 170,000 different words, we would need a very large vocabulary to encompass all words. With a character-based vocabulary, we can get by with only 256 characters! Even languages with a lot of different characters like the Chinese languages have dictionaries with ~20,000 different characters but more than 375,000 different words. Character-based vocabularies let us fewer different tokens than the word-based tokenization dictionaries we would otherwise use. These vocabularies are also more complete than their word-based vocabularies counterparts. As our vocabulary contains all characters used in a language, even words unseen during the tokenizer training can still be tokenized, so out-of-vocabulary tokens will be less frequent. This includes the ability to correctly tokenize misspelled words, rather than discarding them as unknown straight away. However, this algorithm isn't perfect either! Intuitively, characters do not hold as much information individually as a word would hold. For example, "Let's" holds more information than "l". Of course, this is not true for all languages, as some languages like ideogram-based languages have a lot of information held in single characters, but for others like roman-based languages, the model will have to make sense of multiple tokens at a time to get the information held in a single word. This leads to another issue with character-based tokenizers: their sequences are translated into very large amount of tokens to be processed by the model. This can have an impact on the size of the context the model will carry around, and will reduce the size of the text we can use as input for our model. This tokenization, while it has some issues, has seen some very good results in the past and should be considered when approaching a new problem as it solves some issues encountered in the word-based algorithm. \ No newline at end of file diff --git a/subtitles/en/raw/chapter2/04d_subword-based-tokenizers.md b/subtitles/en/raw/chapter2/04d_subword-based-tokenizers.md new file mode 100644 index 000000000..c222e430f --- /dev/null +++ b/subtitles/en/raw/chapter2/04d_subword-based-tokenizers.md @@ -0,0 +1 @@ +Let's take a look at subword-based tokenization. Understanding why subword-based tokenization is interesting requires understanding the flaws of word-based and character-based tokenization. If you haven't seen the first videos on word-based and character-based tokenization, we recommend you check them out before looking at this video. Subword-tokenization lies in between character-based and word-based tokenization algorithms. The idea is to find a middle ground between very large vocabularies, large quantity of out-of-vocabulary tokens, loss of meaning across very similar words, for word-based tokenizers, and very long sequences, less meaningful individual tokens for character-based tokenizers. These algorithms rely on the following principle: frequently used words should not be split into smaller subwords, but rare words should be decomposed into meaningful subwords. An example is the word dog: we would like to have our tokenizer to have a single ID for the word dog, rather than splitting it into characters: d, o, and g. However, when encountering the word dogs, we would like our tokenizer to understand that at the root, this is still the word dog, with an added s while slightly changes the meaning while keeping the original idea. Another example is a complex word like tokenization, which can be split into meaningful subwords. The root of the word is token, and ization completes the root to give it a slightly different meaning. It makes sense to split the word into two: token, as the root of the word (labeled as the "start" of the word). ization as additional information (labeled as a "completion" of the word). In turn, the model will now be able to make sense of token in different situations. It will understand that the words token, tokens, tokenizing, and tokenization are linked and have a similar meaning. It will also understand that tokenization, modernization, and immunization, which all have the same suffixes, are probably used in the same syntactic situations. Subword-based tokenizers generally have a way to identify which tokens are start of words, and which tokens complete start of words: token as the start of a word. ##ization as completing a word. Here the ## prefix indicates that ization is part of a word rather than the beginning of it. The ## comes from the BERT tokenizer, based on the WordPiece algorithm. Other tokenizers use other prefixes, which can be placed to indicate part of words like seen here, or start of words instead! There are a lot of different algorithms that can be used for subword tokenization, and most models obtaining state-of-the-art results in English today use some kind of subword-tokenization algorithm. These approaches help in reducing the vocabulary sizes by sharing information across different words, having the ability to have prefixes and suffixes understood as such. They keep meaning across very similar words, by recognizing similar tokens making them up. \ No newline at end of file diff --git a/subtitles/en/raw/chapter2/04e_tokenizer-pipeline.md b/subtitles/en/raw/chapter2/04e_tokenizer-pipeline.md new file mode 100644 index 000000000..baa99b65a --- /dev/null +++ b/subtitles/en/raw/chapter2/04e_tokenizer-pipeline.md @@ -0,0 +1 @@ +The tokenizer pipeline. In this video, we'll look at how a tokenizer converts raw text to numbers that a Transformer model can make sense of, like when we execute this code. Here is a quick overview of what happens inside the tokenizer object: first the text is split into tokens, which are words, parts of words, or punctuation symbols. Then the tokenizer adds potential special tokens and converts each token to their unique respective ID as defined by the tokenizer's vocabulary. As we'll see it doesn't actually happen in this order, but viewing it like this is better for understanding what happens. The first step is to split our input text into tokens with the tokenize method. To do this, the tokenizer may first perform some operations like lowercasing all words, then follow a set of rules to split the result in small chunks of text. Most of the Transformers models use a subword tokenization algorithm, which means that one given word can be split in several tokens, like tokenize here. Look at the "Tokenization algorithms" videos linked below for more information! The ## prefix we see in front of ize is the convention used by BERT to indicate this token is not the beginning of a word. Other tokenizers may use different conventions however: for instance ALBERT tokenizers will add a long underscore in front of all the tokens that had a space before them, which is a convention used by sentencepiece tokenizers. The second step of the tokenization pipeline is to map those tokens to their respective IDs as defined by the vocabulary of the tokenizer. This is why we need to download a file when we instantiate a tokenizer with the from_pretrained method: we have to make sure we use the same mapping as when the model was pretrained. To do this, we use the convert_tokens_to_ids method. You may have noticed that we don't have the exact same result as in our first slide — or not, as this looks like a list of random numbers, in which case allow me to refresh your memory. We had a number at the beginning and at the end that are missing, those are the special tokens. The special tokens are added by the prepare_for_model method, which knows the indices of those tokens in the vocabulary and just adds the proper numbers. You can look at the special tokens (and more generally at how the tokenizer has changed your text) by using the decode method on the outputs of the tokenizer object. As for the prefix for beginning of words/part of words, those special tokens vary depending on which tokenizer you are using. The BERT tokenizer uses [CLS] and [SEP] but the roberta tokenizer uses html-like anchors and . Now that you know how the tokenizer works, you can forget all those intermediaries methods and only remember that you just have to call it on your input texts. The inputs don't contain the inputs IDs however, to learn what the attention mask is, check out the "Batch inputs together" video. To learn about token type IDs, look at the "Process pairs of sentences" video. \ No newline at end of file diff --git a/subtitles/en/raw/chapter2/05_batching-inputs-pt.md b/subtitles/en/raw/chapter2/05_batching-inputs-pt.md new file mode 100644 index 000000000..e5d7dadab --- /dev/null +++ b/subtitles/en/raw/chapter2/05_batching-inputs-pt.md @@ -0,0 +1 @@ +How to batch inputs together? In this video, we will see how to batch input sequences together. In general, the sentences we want to pass through our model won't all have the same lengths. Here we are using the model we saw in the sentiment analysis pipeline and want to classify two sentences. When tokenizing them and mapping each token to its corresponding input IDs, we get two lists of different lengths. Trying to create a tensor or a NumPy array from those two lists will result in an error, because all arrays and tensors should be rectangular. One way to overcome this limit is to make the second sentence the same length as the first by adding a special token as many times as necessary. Another way would be to truncate the first sequence to the length of the second, but we would them lose a lot of information that might be necessary to properly classify the sentence. In general, we only truncate sentences when they are longer than the maximum length the model can handle. The value used to pad the second sentence should not be picked randomly: the model has been pretrained with a certain padding ID, which you can find in tokenizer.pad_token_id. Now that we have padded our sentences, we can make a batch with them. If we pass the two sentences to the model separately and batched together however, we notice that we don't get the same results for the sentence that is padded (here the second one). If you remember that Transformer models make heavy use of attention layers, this should not come as a total surprise: when computing the contextual representation of each token, the attention layers look at all the other words in the sentence. If we have just the sentence or the sentence with several padding tokens added, it's logical we don't get the same values. To get the same results with or without padding, we need to indicate to the attention layers that they should ignore those padding tokens. This is done by creating an attention mask, a tensor with the same shape as the input IDs, with zeros and ones. Ones indicate the tokens the attention layers should consider in the context and zeros the tokens they should ignore. Now passing this attention mask along with the input ids will give us the same results as when we sent the two sentences individually to the model! This is all done behind the scenes by the tokenizer when you apply it to several sentences with the flag padding=True. It will apply the padding with the proper value to the smaller sentences and create the appropriate attention mask. \ No newline at end of file diff --git a/subtitles/en/raw/chapter2/05_batching-inputs-tf.md b/subtitles/en/raw/chapter2/05_batching-inputs-tf.md new file mode 100644 index 000000000..e5d7dadab --- /dev/null +++ b/subtitles/en/raw/chapter2/05_batching-inputs-tf.md @@ -0,0 +1 @@ +How to batch inputs together? In this video, we will see how to batch input sequences together. In general, the sentences we want to pass through our model won't all have the same lengths. Here we are using the model we saw in the sentiment analysis pipeline and want to classify two sentences. When tokenizing them and mapping each token to its corresponding input IDs, we get two lists of different lengths. Trying to create a tensor or a NumPy array from those two lists will result in an error, because all arrays and tensors should be rectangular. One way to overcome this limit is to make the second sentence the same length as the first by adding a special token as many times as necessary. Another way would be to truncate the first sequence to the length of the second, but we would them lose a lot of information that might be necessary to properly classify the sentence. In general, we only truncate sentences when they are longer than the maximum length the model can handle. The value used to pad the second sentence should not be picked randomly: the model has been pretrained with a certain padding ID, which you can find in tokenizer.pad_token_id. Now that we have padded our sentences, we can make a batch with them. If we pass the two sentences to the model separately and batched together however, we notice that we don't get the same results for the sentence that is padded (here the second one). If you remember that Transformer models make heavy use of attention layers, this should not come as a total surprise: when computing the contextual representation of each token, the attention layers look at all the other words in the sentence. If we have just the sentence or the sentence with several padding tokens added, it's logical we don't get the same values. To get the same results with or without padding, we need to indicate to the attention layers that they should ignore those padding tokens. This is done by creating an attention mask, a tensor with the same shape as the input IDs, with zeros and ones. Ones indicate the tokens the attention layers should consider in the context and zeros the tokens they should ignore. Now passing this attention mask along with the input ids will give us the same results as when we sent the two sentences individually to the model! This is all done behind the scenes by the tokenizer when you apply it to several sentences with the flag padding=True. It will apply the padding with the proper value to the smaller sentences and create the appropriate attention mask. \ No newline at end of file diff --git a/subtitles/en/raw/chapter3/02a_datasets-overview-pt.md b/subtitles/en/raw/chapter3/02a_datasets-overview-pt.md new file mode 100644 index 000000000..1ccdd9f4a --- /dev/null +++ b/subtitles/en/raw/chapter3/02a_datasets-overview-pt.md @@ -0,0 +1 @@ +The Hugging Face Datasets library: A Quick overview. The Hugging Face Datasets library is a library that provides an API to quickly download many public datasets and preprocess them. In this video we will explore how to do that. The downloading part is easy: with the load_dataset function, you can directly download and cache a dataset from its identifier on the Dataset hub. Here we fetch the MRPC dataset from the GLUE benchmark, which is a dataset containing pairs of sentences where the task is to determine the paraphrases. The object returned by the load_dataset function is a DatasetDict, which is a sort of dictionary containing each split of our dataset. We can access each split by indexing with its name. This split is then an instance of the Dataset class, with columns (here sentence1, sentence2. label and idx) and rows. We can access a given element by its index. The amazing thing about the Hugging Face Datasets library is that everything is saved to disk using Apache Arrow, which means that even if your dataset is huge you won't get out of RAM: only the elements you request are loaded in memory. Accessing a slice of your dataset is as easy as one element. The result is then a dictionary with list of values for each keys (here the list of labels, the list of first sentences and the list of second sentences). The features attribute of a Dataset gives us more information about its columns. In particular, we can see here it gives us the correspondence between the integers and names for the labels. 0 stands for not equivalent and 1 for equivalent. To preprocess all the elements of our dataset, we need to tokenize them. Have a look at the video "Preprocess sentence pairs" for a refresher, but you just have to send the two sentences to the tokenizer with some additional keyword arguments. Here we indicate a maximum length of 128 and pad inputs shorter than this length, truncate inputs that are longer. We put all of this in a tokenize_function that we can directly apply to all the splits in our dataset with the map method. As long as the function returns a dictionary-like object, the map method will add new columns as needed or update existing ones. To speed up preprocessing and take advantage of the fact our tokenizer is backed by Rust thanks to the Hugging Face Tokenizers library, we can process several elements at the same time to our tokenize function, using the batched=True argument. Since the tokenizer can handle list of first/second sentences, the tokenize_function does not need to change for this. You can also use multiprocessing with the map method, check out its documentation! Once this is done, we are almost ready for training: we just remove the columns we don't need anymore with the remove_columns method, rename label to labels (since the models from Hugging Face Transformers expect that) and set the output format to our desired backend: torch, tensorflow or numpy. If needed, we can also generate a short sample of a dataset using the select method. \ No newline at end of file diff --git a/subtitles/en/raw/chapter3/02a_datasets-overview-tf.md b/subtitles/en/raw/chapter3/02a_datasets-overview-tf.md new file mode 100644 index 000000000..1ccdd9f4a --- /dev/null +++ b/subtitles/en/raw/chapter3/02a_datasets-overview-tf.md @@ -0,0 +1 @@ +The Hugging Face Datasets library: A Quick overview. The Hugging Face Datasets library is a library that provides an API to quickly download many public datasets and preprocess them. In this video we will explore how to do that. The downloading part is easy: with the load_dataset function, you can directly download and cache a dataset from its identifier on the Dataset hub. Here we fetch the MRPC dataset from the GLUE benchmark, which is a dataset containing pairs of sentences where the task is to determine the paraphrases. The object returned by the load_dataset function is a DatasetDict, which is a sort of dictionary containing each split of our dataset. We can access each split by indexing with its name. This split is then an instance of the Dataset class, with columns (here sentence1, sentence2. label and idx) and rows. We can access a given element by its index. The amazing thing about the Hugging Face Datasets library is that everything is saved to disk using Apache Arrow, which means that even if your dataset is huge you won't get out of RAM: only the elements you request are loaded in memory. Accessing a slice of your dataset is as easy as one element. The result is then a dictionary with list of values for each keys (here the list of labels, the list of first sentences and the list of second sentences). The features attribute of a Dataset gives us more information about its columns. In particular, we can see here it gives us the correspondence between the integers and names for the labels. 0 stands for not equivalent and 1 for equivalent. To preprocess all the elements of our dataset, we need to tokenize them. Have a look at the video "Preprocess sentence pairs" for a refresher, but you just have to send the two sentences to the tokenizer with some additional keyword arguments. Here we indicate a maximum length of 128 and pad inputs shorter than this length, truncate inputs that are longer. We put all of this in a tokenize_function that we can directly apply to all the splits in our dataset with the map method. As long as the function returns a dictionary-like object, the map method will add new columns as needed or update existing ones. To speed up preprocessing and take advantage of the fact our tokenizer is backed by Rust thanks to the Hugging Face Tokenizers library, we can process several elements at the same time to our tokenize function, using the batched=True argument. Since the tokenizer can handle list of first/second sentences, the tokenize_function does not need to change for this. You can also use multiprocessing with the map method, check out its documentation! Once this is done, we are almost ready for training: we just remove the columns we don't need anymore with the remove_columns method, rename label to labels (since the models from Hugging Face Transformers expect that) and set the output format to our desired backend: torch, tensorflow or numpy. If needed, we can also generate a short sample of a dataset using the select method. \ No newline at end of file diff --git a/subtitles/en/raw/chapter3/02c_preprocess-sentence-pairs-pt.md b/subtitles/en/raw/chapter3/02c_preprocess-sentence-pairs-pt.md new file mode 100644 index 000000000..f7cac5fc8 --- /dev/null +++ b/subtitles/en/raw/chapter3/02c_preprocess-sentence-pairs-pt.md @@ -0,0 +1 @@ +How to preprocess pairs of sentences? We have seen how to tokenize single sentences and batch them together in the "Batching inputs together" video. If this code look unfamiliar to you, be sure to check that video again! Here we will focus on tasks that classify pairs of sentences. For instance, we may want to classify whether two texts are paraphrases or not. Here is an example taken from the Quora Question Pairs dataset, which focuses on identifying duplicate questions. In the first pair, the two questions are duplicates; in the second, they are not. Another pair classification problem is when we want to know if two sentences are logically related or not (a problem called Natural Language Inference or NLI). In this example taken from the MultiNLI dataset, we have a pair of sentences for each possible label: contradiction, neutral or entailment (which is a fancy way of saying the first sentence implies the second). So classifying pairs of sentences is a problem worth studying. In fact, in the GLUE benchmark (which is an academic benchmark for text classification), 8 of the 10 datasets are focused on tasks using pairs of sentences. That's why models like BERT are often pretrained with a dual objective: on top of the language modeling objective, they often have an objective related to sentence pairs. For instance, during pretraining, BERT is shown pairs of sentences and must predict both the value of randomly masked tokens and whether the second sentence follows from the first. Fortunately, the tokenizer from the Transformers library has a nice API to deal with pairs of sentences: you just have to pass them as two arguments to the tokenizer. On top of the input IDs and the attention mask we studied already, it returns a new field called token type IDs, which tells the model which tokens belong to the first sentence and which ones belong to the second sentence. Zooming in a little bit, here are the input IDs, aligned with the tokens they correspond to, their respective token type ID and attention mask. We can see the tokenizer also added special tokens so we have a CLS token, the tokens from the first sentence, a SEP token, the tokens from the second sentence, and a final SEP token. If we have several pairs of sentences, we can tokenize them together by passing the list of first sentences, then the list of second sentences and all the keyword arguments we studied already, like padding=True. Zooming in at the result, we can see how the tokenizer added padding to the second pair of sentences, to make the two outputs the same length, and properly dealt with token type IDS and attention masks for the two sentences. This is then all ready to pass through our model! \ No newline at end of file diff --git a/subtitles/en/raw/chapter3/02c_preprocess-sentence-pairs-tf.md b/subtitles/en/raw/chapter3/02c_preprocess-sentence-pairs-tf.md new file mode 100644 index 000000000..f7cac5fc8 --- /dev/null +++ b/subtitles/en/raw/chapter3/02c_preprocess-sentence-pairs-tf.md @@ -0,0 +1 @@ +How to preprocess pairs of sentences? We have seen how to tokenize single sentences and batch them together in the "Batching inputs together" video. If this code look unfamiliar to you, be sure to check that video again! Here we will focus on tasks that classify pairs of sentences. For instance, we may want to classify whether two texts are paraphrases or not. Here is an example taken from the Quora Question Pairs dataset, which focuses on identifying duplicate questions. In the first pair, the two questions are duplicates; in the second, they are not. Another pair classification problem is when we want to know if two sentences are logically related or not (a problem called Natural Language Inference or NLI). In this example taken from the MultiNLI dataset, we have a pair of sentences for each possible label: contradiction, neutral or entailment (which is a fancy way of saying the first sentence implies the second). So classifying pairs of sentences is a problem worth studying. In fact, in the GLUE benchmark (which is an academic benchmark for text classification), 8 of the 10 datasets are focused on tasks using pairs of sentences. That's why models like BERT are often pretrained with a dual objective: on top of the language modeling objective, they often have an objective related to sentence pairs. For instance, during pretraining, BERT is shown pairs of sentences and must predict both the value of randomly masked tokens and whether the second sentence follows from the first. Fortunately, the tokenizer from the Transformers library has a nice API to deal with pairs of sentences: you just have to pass them as two arguments to the tokenizer. On top of the input IDs and the attention mask we studied already, it returns a new field called token type IDs, which tells the model which tokens belong to the first sentence and which ones belong to the second sentence. Zooming in a little bit, here are the input IDs, aligned with the tokens they correspond to, their respective token type ID and attention mask. We can see the tokenizer also added special tokens so we have a CLS token, the tokens from the first sentence, a SEP token, the tokens from the second sentence, and a final SEP token. If we have several pairs of sentences, we can tokenize them together by passing the list of first sentences, then the list of second sentences and all the keyword arguments we studied already, like padding=True. Zooming in at the result, we can see how the tokenizer added padding to the second pair of sentences, to make the two outputs the same length, and properly dealt with token type IDS and attention masks for the two sentences. This is then all ready to pass through our model! \ No newline at end of file diff --git a/subtitles/en/raw/chapter3/02d_dynamic-padding.md b/subtitles/en/raw/chapter3/02d_dynamic-padding.md new file mode 100644 index 000000000..2e6fe7264 --- /dev/null +++ b/subtitles/en/raw/chapter3/02d_dynamic-padding.md @@ -0,0 +1 @@ +What is dynamic padding? In the "Batching Inputs together" video, we have seen that to be able to group inputs of different lengths in the same batch, we need to add padding tokens to all the short inputs until they are all of the same length. Here for instance, the longest sentence is the third one, and we need to add 5, 2 and 7 pad tokens to the other to have four sentences of the same lengths. When dealing with a whole dataset, there are various padding strategies we can apply. The most obvious one is to pad all the elements of the dataset to the same length: the length of the longest sample. This will then give us batches that all have the same shape determined by the maximum sequence length. The downside is that batches composed from short sentences will have a lot of padding tokens which introduce more computations in the model we ultimately don't need. To avoid this, another strategy is to pad the elements when we batch them together, to the longest sentence inside the batch. This way batches composed of short inputs will be smaller than the batch containing the longest sentence in the dataset. This will yield some nice speedup on CPU and GPU. The downside is that all batches will then have different shapes, which slows down training on other accelerators like TPUs. Let's see how to apply both strategies in practice. We have actually seen how to apply fixed padding in the Datasets Overview video, when we preprocessed the MRPC dataset: after loading the dataset and tokenizer, we applied the tokenization to all the dataset with padding and truncation to make all samples of length 128. As a result, if we pass this dataset to a PyTorch DataLoader, we get batches of shape batch size (here 16) by 128. To apply dynamic padding, we must defer the padding to the batch preparation, so we remove that part from our tokenize function. We still leave the truncation part so that inputs that are bigger than the maximum length accepted by the model (usually 512) get truncated to that length. Then we pad our samples dynamically by using a data collator. Those classes in the Transformers library are responsible for applying all the final processing needed before forming a batch, here DataCollatorWithPadding will pad the samples to the maximum length inside the batch of sentences. We pass it to the PyTorch DataLoader as a collate function, then observe that the batches generated have various lenghs, all way below the 128 from before. Dynamic batching will almost always be faster on CPUs and GPUs, so you should apply it if you can. Remember to switch back to fixed padding however if you run your training script on TPU or need batches of fixed shapes. \ No newline at end of file diff --git a/subtitles/en/raw/chapter3/03a_trainer-api.md b/subtitles/en/raw/chapter3/03a_trainer-api.md new file mode 100644 index 000000000..543ac0eef --- /dev/null +++ b/subtitles/en/raw/chapter3/03a_trainer-api.md @@ -0,0 +1 @@ +The Trainer API. The Transformers library provides a Trainer API that allows you to easily fine-tune transformer models on your own dataset. The Trainer class take your datasets, your model as well as the training hyperparameters and can perform the training on any kind of setup (CPU, GPU, multi GPUs, TPUs). It can also compute the predictions on any dataset, and if you provided metrics, evaluate your model on any dataset. It can also handle final data-processing such as dynamic padding as long as you provide the tokenizer or a given data collator. We will try this API on the MRPC dataset, since it's relatively small and easy to preprocess. As we saw in the Datasets overview video, here is how we can preprocess it. We do not apply padding during the preprocessing as we will use dynamic padding with our DataCollatorWithPadding. Note that we don't do the final steps of renaming/removing columns or set the format to torch tensors: the Trainer will do all of this automatically for us by analyzing the model signature. The last steps before creating the Trainer are to define our model and some training hyperparameters. We saw how to do the first in the model API video. For the second, we use the TrainingArguments class. It only needs a path to a folder where results and checkpoints will be saved, but you can also customize all the hyperparameters the Trainer will use: learning rate, number of training epochs etc. It's then very easy to create a Trainer and launch a training. This should display a progress bar and after a few minutes (if you are running on a GPU) you should have the training finished. The result will be rather anticlimatic however, as you will only get a training loss which doesn't really tell you anything about how you model is performing. This is because we didn't specify anything metric for the evaluation. To get those metrics, we will first gather the predictions on the whole evaluation set using the predict method. It returns a namedtuple with three fields: predictions (which contains the model predictions), label_ids (which contains the labels if your dataset had them) and metrics (which is empty here). The predictions are the logits of the models for all the sentences in the dataset, so a NumPy array of shape 408 by 2. To match them with our labels, we need to take the maximum logit for each prediction (to know which of the two classes was predicted), which we do with the argmax function. Then we can use a Metric from the Datasets library: it can be loaded as easily as our dataset with the load_metric function, and it returns the evaluation metric used for the dataser we are using. We can see our model did learn something as it is 85.7% accurate. To monitor the evaluation metrics during training we need to define a compute_metrics function that does the same step as before: it takes a namedtuple with predictions and labels and must return a dictionary with the metric we want to keep track of. By passing the epoch evaluation strategy to our TrainingArguments, we tell the Trainer to evaluate at the end of every epoch. Launching a training inside a notebook will then display a progress bar and complete the table you see here as you pass every epoch. \ No newline at end of file diff --git a/subtitles/en/raw/chapter3/03b_keras-intro.md b/subtitles/en/raw/chapter3/03b_keras-intro.md new file mode 100644 index 000000000..30c7eb8fd --- /dev/null +++ b/subtitles/en/raw/chapter3/03b_keras-intro.md @@ -0,0 +1 @@ +In this video, I'm going to give you a very quick introduction to how our transformers models work together with Tensorflow and Keras! The very short explanation is that all of our Tensorflow models are also Keras model objects, and so they have the standard Keras model API. If you're an experienced ML engineer who's used Keras a lot, that's probably all you need to know to start working with them. But for everyone else, including the prodigal PyTorch engineers out there who are returning to the fold, I'm going to quickly introduce Keras models, and how we work with them. In other videos, which I'll link below, I'll run through training with Keras models in more detail. But first, what is a Keras model? Your model basically contains your entire network: It contains the layers, and the weights for those layers, and also tells the model what to do with them; it defines the whole path all the way from your inputs to your outputs. If you've used Keras before, you probably started by building your model out by hand - you added one layer after another, maybe using model.add() or the functional approach. And there's nothing wrong with that! But you can also pre-load an entire model, weights and all. This is really helpful, because if you try reading the paper or looking at the code, you'll see the inside of a Transformer is pretty complex, and writing it all out from scratch and getting it right would be hard even for an experienced machine learning engineer. But because it's all packed inside a Model, you don't need to worry about that complexity if you don't want to! You have the flexibility to write any model you like, but you can also just load a pre-trained, pre-configured transformer model in one line of code. And whether you write your own model from scratch or load a pre-trained one, you interact with the model in the same way - through the same few methods you're going to see again and again, like *fit*, *compile* and *predict,* and we'll cover concrete examples of how to use those methods in other videos that I'll link below. For now the key thing to take away from this video, if you've never seen Keras before, is that this neat encapsulation means that all of the complexity of a huge neural net becomes manageable, because you interact with it in exactly the same way, using exactly the same methods, as you would with a simple model that you wrote out by hand. \ No newline at end of file diff --git a/subtitles/en/raw/chapter3/03c_keras-finetuning.md b/subtitles/en/raw/chapter3/03c_keras-finetuning.md new file mode 100644 index 000000000..0ee34e260 --- /dev/null +++ b/subtitles/en/raw/chapter3/03c_keras-finetuning.md @@ -0,0 +1 @@ +In this video, we're going to see how to load and fine-tune a pre-trained model. It's very quick, and if you've watched our pipeline videos, which I'll link below, the process is very similar. This time, though, we're going to be using transfer learning and doing some training ourselves, rather than just loading a model and using it as-is. To learn more about transfer learning, head to the 'What is transfer learning?' video, which we'll link below too! So now let's look at this code. To start, we pick which model we want to start with - in this case we're going to use the famous, the original BERT. But what does this monstrosity, 'TFAutoModelForSequenceClassification' mean? Well, the TF stands for TensorFlow, and the rest means "take a language model, and stick a sequence classification head onto it if it doesn't have one already". So what we're going to do here is load BERT, a general language model, and then do some transfer learning to use it on our task of interest. We load the language model with this one line of code here, using the "from_pretrained" method. That method needs to know two things: Firstly the name of the model you want it to load, and secondly how many classes your problem has. If you want to follow along with the data from our datasets videos, which I'll link below, then you'll have two classes, positive and negative, and thus num_labels equals two. What about this "compile" thing? If you're familiar with Keras, you've probably seen this already, but if not, this is one of its core methods - you always need to "compile" your model before you train it. Compile needs to know two things: Firstly, the loss function - what are we trying to optimize? Here, we import the sparse categorical crossentropy loss function - that's a mouthful, but it's the standard loss function for any neural network that's doing a classification task. It basically encourages the network to output large values for the right class, and low values for the wrong classes. Note that you can specify the loss function as a string, like we did with the optimizer, but there's a very common pitfall there - by default, this loss assumes the output is probabilities after a softmax layer, but what our model has actually output is the values before the softmax, often called the "logits" - you saw these before in the videos about pipelines. If you get this wrong, your model won't train and it'll be very annoying to figure out why. In fact, if you remember absolutely nothing else from this video, remember to always check whether your model is outputting logits or probabilities, and to make sure your loss is set up to match that. It'll save you a lot of debugging headaches in your career! The second thing compile needs to know is the optimizer you want. In our case, we use Adam, which is sort of the standard optimizer for deep learning these days. The one thing you might want to change is the learning rate, and to do that we'll need to import the actual optimizer rather than just calling it by string, but we'll talk about that in another video, which I'll link below. For now, let's just try training the model! So how do you train a model? Well, if you’ve used Keras before, this will all be very familiar to you - but if not, let's look at what we're doing here. Fit() is pretty much the central method for Keras models - it tells the model to break the data into batches and train on it. So the first input is tokenized text - you will almost always be getting this from a tokenizer, and if you want to learn more about that process, and what exactly the outputs look like, please check out our videos on tokenizers - there'll be links below for those too! So that's our inputs, and then the second input is our labels - this is just a one-dimensional Numpy or Tensorflow array of integers, corresponding to the classes for our examples, and that’s it. If you're following along with the data from our datasets video, there'll only be two classes, so this will just be zeroes and ones. Once we have our inputs and our labels, we do the same thing with the validation data, we pass the validation inputs and the validation labels in a tuple, then we can, if we want, specify details like the batch_size for training, and then you just pass it all to model.fit() and let it rip. If everything works out, you should see a little training progress bar as your loss goes down. And while that's running you call your boss and tell him you’re a senior NLP machine learning engineer now and you’re going to want a salary review next quarter. This is really all it takes to apply the power of a massive pretrained language model to your NLP problem. Could we do better, though? We certainly could, with a few more advanced Keras features like a tuned, scheduled learning rate we can get an even lower loss, and an even more accurate model. And what do we do with our model once it's trained? I'll cover this and more in the videos linked below! \ No newline at end of file diff --git a/subtitles/en/raw/chapter3/03d_keras-learning-rate.md b/subtitles/en/raw/chapter3/03d_keras-learning-rate.md new file mode 100644 index 000000000..08581a532 --- /dev/null +++ b/subtitles/en/raw/chapter3/03d_keras-learning-rate.md @@ -0,0 +1 @@ +In our other videos we talked about the basics of fine-tuning a language model with Tensorflow (and as always, when I refer to videos I'll link them below). Still, can we do better? So here's the code from our model fine-tuning video, and while it works, we could definitely tweak a couple of things. By far the most important thing is the learning rate. In this video we'll talk about how to change it, which will make your training much more consistently successful. In fact, there are two things we want to change about the default learning rate for Adam. The first is that it's way too high for our models - by default Adam uses a learning rate of 10^-3 1 e minus 3, which is very high for training Transformers. We're going to start at 5 by 10^-5 5 e minus 5, which is 20 times lower than the default. And secondly, we don't just want a constant learning rate - we can get even better performance if we 'decay' the learning rate down to a tiny value, or even 0, over the course of training. That's what this PolynomialDecay schedule thing is doing. That name might be intimidating, especially if you only vaguely remember what a polynomial is from maths class. However, all we need to do is tell it how long training is going to be, so it decays at the right speed - that's what this code here is doing. We're computing how many minibatches the model is going to see over its entire training run, which is the size of the training set, divided by the batch_size to get the number of batches per epoch, and then multiplied by the number of epochs to get the total number of batches across the whole training run. Once we know how many training steps we're taking, we just pass all that information to the scheduler and we're ready to go. What does the polynomial decay schedule look like? With default options, it's actually just a linear schedule, so it looks like this - it starts at 5e-5, which means 5 times ten to the minus 5, and then decays down at a constant rate until it hits zero right at the very end of training. So why do they call it polynomial and not linear? Because if you tweak the options, you can get a higher-order decay schedule, but there's no need to do that right now. Now, how do we use our learning rate schedule? Easy, we just pass it to Adam! You'll notice the first time when we compiled the model, we just passed it the string "adam". Keras recognizes the names of common optimizers and loss functions if you pass them as strings, so it saves time to do that if you only want the default settings. But we're professional machine learners now, with our very own learning rate schedule, so we have to do things properly. So first we import the optimizer, then we initialize it with our scheduler, and then we compile the model using the new optimizer, and whatever loss function you want - this will be sparse categorical crossentropy if you're following along from the fine-tuning video. And now we have a high-performance model, ready to go. All that remains is to fit the model just like we did before! Remember, because we compiled the model with the new optimizer with the new learning rate schedule, we don't need to change anything here. We just call fit again, with exactly the same command as before, but now we get beautiful training with a nice, smooth learning rate decay. \ No newline at end of file diff --git a/subtitles/en/raw/chapter3/03e_keras-metrics.md b/subtitles/en/raw/chapter3/03e_keras-metrics.md new file mode 100644 index 000000000..e43153f58 --- /dev/null +++ b/subtitles/en/raw/chapter3/03e_keras-metrics.md @@ -0,0 +1 @@ +In our other videos, and as always, there'll be links below if you want to check those out, we showed you how to initialize and fine-tune a transformer model in TensorFlow, so the question now is: What can we do with a model after we train it? The obvious thing to try is to use it to get predictions for new data, so let's see how to do that. Again, if you're familiar with Keras, the good news is that because there are just standard Keras models, we can use the standard Keras predict() method, as shown here. You simply pass in tokenized text to this method, like you'd get from a tokenizer, and you get your results. Our models can output several different things, depending on the options you set, but most of the time the thing you want is the output logits. If you haven’t come across them before, logits are the outputs of the last layer of the network, before a softmax has been applied. So if you want to turn the logits into the model’s probability outputs, you just apply a softmax, like so. What if we want to turn those probabilities into class predictions? Simple, we just pick the biggest probability for each output! The easiest way to do that is with the argmax function. Argmax will return the index of the largest probability in each row, which means in this case that we’ll get a vector of 0 and 1 values. Those are our class predictions! In fact, if class predictions are all you want, you can skip the softmax step entirely, because the largest logit will always be the largest probability too. If probabilities and class predictions are all you want, then you’ve seen everything you need at this point! But if you’re interested in benchmarking your model or using it for research, you might want to delve deeper into the results you get. And one way to do that is to compute some metrics for the model’s predictions. If you're following along with our datasets and fine-tuning videos, we got our data from the MRPC dataset, which is part of the GLUE benchmark. Each of the GLUE datasets, as well as many of our other datasets, has some predefined metrics, and we can load them easily with the datasets load_metric() function. For the MRPC dataset, the built-in metrics are accuracy, which just measures the percentage of the time the model’s prediction was correct, and the F1 score, which is a slightly more complex measure that measures how well the model trades off precision and recall. To compute those metrics to benchmark our model, we just pass them the model’s predictions, and the ground truth labels, and we get our results. If you’re familiar with Keras, though, you’ll notice that this is a weird way to compute metrics - we’re only computing metrics at the end of training, but Keras has the built-in ability to compute a wide range of metrics on the fly while you're training. If you want to use built-in metric computations, it's very straightforward - you just pass a 'metric' argument to compile(). As with things like loss and optimizer, you can specify the metrics you want by string, or you can import the actual metric objects if you want to pass specific arguments to them, but note that unlike loss and accuracy, you have to supply a list of metrics, even if you only have one. Once a model has been compiled with a metric, it will report that metric for training, validation and predictions. You can even write your own Metric classes. Though this is a bit beyond the scope of this course, I'll link to the relevant TF docs below because it can be very handy if you want a metric that isn't supported by default in Keras, such as the F1 score. \ No newline at end of file diff --git a/subtitles/en/raw/chapter3/04a_raw-training-loop.md b/subtitles/en/raw/chapter3/04a_raw-training-loop.md new file mode 100644 index 000000000..4554c6613 --- /dev/null +++ b/subtitles/en/raw/chapter3/04a_raw-training-loop.md @@ -0,0 +1 @@ +Write your own training loop in PyTorch. In this video, we will look at how we can do the same fine-tuning as in the Trainer video, but without relying on that class. This way you will be able to easily customize each step of the training loop to your needs. This is also very useful to manually debug something that went wrong with the Trainer API. Before we dive into the code, here is a sketch of a training loop: we take a batch of training data and feed it to the model. With the labels, we can then compute a loss. That number is not useful on its own, but is used to compute the gradients of our model weights, that is the derivative of the loss with respect to each model weight. Those gradients are then used by the optimizer to update the model weights and make them a little bit better. We then repeat the process with a new batch of training data. If any of this is unclear, don't hesitate to take a refresher on your favorite deep learning course. We will use the GLUE MRPC dataset here again, and we have seen how to preprocess the data using the Datasets library with dynamic padding. Checkout the videos linked below if you haven't seen them already. With this done, we only have to define PyTorch DataLoaders, which will be responsible to convert the elements of our dataset into batches. We use our DataCollatorForPadding as the collate function, and shuffle the training set. To check that everything works as intended, we try to grab a batch of data and inspect it. Like our dataset elements, it's a dictionary, but this time the values are not a single list of integers, but a tensor of shape batch size by sequence length. The next step is to send the training data in our model. For that, we will need to create our model. As seen in the model API video, we use the from_pretrained method and adjust the number of labels to the number of classes we have on this dataset, here two. Again, to be sure everything is going well, we pass the batch we grabbed to our model and check there is no error. If the labels are provided, the models of the Transformers library always return the loss directly. We will be able to do loss.backward() to compute all the gradients, and will then need an optimizer to do the training step. We use the AdamW optimizer here, which is a variant of Adam with proper weight decay, but you can pick any PyTorch optimizer you like. Using the previous loss and computing the gradients with loss.backward(), we check that we can do the optimizer step without any error. Don't forget to zero your gradient afterward, or at the next step they will get added to the gradients you compute! We could already write our training loop, but we will add two more things to make it as good as it can be. The first one is a learning rate scheduler, to progressively decay our learning rate to zero. The get_scheduler function from the Transformers library is just a convenience function to easily build such a scheduler, you can again use any PyTorch learning rate scheduler instead. Finally, if we want our training to take a couple of minutes instead of a few hours, we will need to use a GPU. The first step is to get one, for instance by using a colab notebook. Then you need to actually send your model and training data on it by using a torch device. Double-check the following lines print a CUDA device for you! We can now put everything together! First we put our model in training mode (which will activate the training behavior for some layers like Dropout) then go through the number of epochs we picked and all the data in our training dataloader. Then we go through all the steps we have seen already: send the data to the GPU, compute the model outputs, and in particular the loss. Use the loss to compute gradients, then make a training step with the optimizer. Update the learning rate in our scheduler for the next iteration and zero the gradients of the optimizer. Once this is finished, we can evaluate our model very easily with a metric from the Datasets library. First we put our model in evaluation mode, then go through all the data in the evaluation data loader. As we have seen in the Trainer video, the model outputs logits and we need to apply the argmax function to convert them into predictions. The metric object then has an add_batch method we can use to send it those intermediate predictions. Once the evaluation loop is finished, we just have to call the compute method to get our final results! Congratulations, you have now fine-tuned a model all by yourself! \ No newline at end of file diff --git a/subtitles/en/raw/chapter3/04b_accelerate.md b/subtitles/en/raw/chapter3/04b_accelerate.md new file mode 100644 index 000000000..8cb94f94b --- /dev/null +++ b/subtitles/en/raw/chapter3/04b_accelerate.md @@ -0,0 +1 @@ +Supercharge your Pytorch training loop with Hugging Face Accelerate. There are multiple setups on which you can run your training: it could be on CPU, GPUs, TPUs. Distributed on one machine with several devices, or several machines (often called nodes) each with multiple devices. On top of that there are new tweaks to make your training faster or more memory efficient, like mixed precision and DeepSpeed. Each of those setups or training tweaks, requires you to change the code of your training loop in one way or another and to learn a new API. All those setups are handled by the Trainer API, and there are several third-party libraries that can also help you with that. The problem with those is that they can feel like a black box and that it might not be easy to implement the tweak to the training loop you need. Accelerate has been designed specifically to let you retain full control over your training loop and be as non-intrusive as possible. With just four lines to add to your training loop (here shown on the code of the training loop from the "Raw training loop" video), Accelerate will handle all the setups and training tweaks mentioned on the first slide. It's only one API to learn and master instead of ten different ones. More specifically, you have to import and instantiate an accelerator object, that will handle all the necessary code for your specific setup. Then you have to send it the model, optimizer and dataloaders you are using in the prepare method, which is the main method to remember. Accelerate handles device placement, so you don't need to put your batch on the specific device you are using. Finally, you have to replace the loss.backward line by accelerate.backward(loss), and that's all you need! Accelerate also handles distributed evaluation. You can still use a classic evaluation loop such as the one we saw in the "Raw training loop" video, in which case all processes will each perform the full evaluation. To use a distributed evaluation, you just have to adapt your evaluation loop like this: pass along the evaluation dataloader to the accelerator.prepare method, like for training. Then you can dismiss the line that places the batch on the proper device, and just before passing your predictions and labels to your metric, use accelerator.gather to gather together the predictions and labels from each process. A distributed training script has to be launched several times on different processes (for instance one per GPU you are using). You can use the PyTorch tools if you are familiar with them, but Accelerate also provides an easy API to configure your setup and launch your training script. In a terminal, run accelerate config and answer the small questionnaire to generate a configuration file with all the relevant information, then you can just run accelerate launch, followed by the path to your training script. In a notebook, you can use the notebook_launcher function to launch your training function. \ No newline at end of file diff --git a/subtitles/en/raw/chapter4/01_huggingface-hub.md b/subtitles/en/raw/chapter4/01_huggingface-hub.md new file mode 100644 index 000000000..77f29e968 --- /dev/null +++ b/subtitles/en/raw/chapter4/01_huggingface-hub.md @@ -0,0 +1 @@ +In this video, we're going to go over the HuggingFace Model Hub navigation. This is the huggingface.co landing page. To access the model hub, click on the "Models" tab in the upper right corner. You should be facing this web interface, which can be split into several parts. On the left, you'll find categories, which you can use to tailor your model search. The first category is the "Tasks". Models on the hub may be used for a wide variety of tasks. These include natural language processing tasks, such as question answering or text classification, but it isn't only limited to NLP. Other tasks from other fields are also available, such as image classification for computer vision, or automatic speech recognition for speech. The second category is the "libraries". Models on the hub usually share one of three backbones: PyTorch, TensorFlow, or JAX. However, other backbones, such as rust or ONNX also exist. Finally, this tab can also be used to specify from which high-level framework the model comes. This includes Transformers, but it isn't limited to it. The model Hub is used to host a lot of different frameworks' models, and we are actively looking to host other frameworks' models. The third category is the "Datasets" tab. Selecting a dataset from this tab means filtering the models so that they were trained on that specific dataset. The fourth category is the "Languages" tab. Selecting a language from this tab means filtering the models so that they handle the language selected. Finally, the last category allows to choose the license with which the model is shared. On the right, you'll find the models available on the model Hub! The models are ordered by downloads. When clicking on a model, you should be facing its model card. The model card contains information about the model: its description, intended use, limitations and biases. It can also show code snippets on how to use the model, as well as any relevant information: training procedure, data processing, evaluation results, copyrights. This information is crucial for the model to be used. The better crafted a model card is, the easier it will be for other users to leverage your model in their applications. On the right of the model card is the inference API. This inference API can be used to play with the model directly. Feel free to modify the text and click on compute to see how would the model behave to your inputs. At the top of the screen lie the model tags. These include the model task, as well as any other tag that is relevant to the categories we have just seen. The "Files & Versions tab" displays the architecture of the repository of that model. Here, we can see all the files that define this model. You'll see all usual features of a git repository: the branches available, the commit history as well as the commit diff. Three different buttons are available at the top of the model card. The first one shows how to use the inference API programmatically. The second one shows how to train this model in SageMaker, and the last one shows how to load that model within the appropriate library. For BERT, this is transformers. \ No newline at end of file diff --git a/subtitles/en/raw/chapter4/03a_managing-repos.md b/subtitles/en/raw/chapter4/03a_managing-repos.md new file mode 100644 index 000000000..22f97d6f4 --- /dev/null +++ b/subtitles/en/raw/chapter4/03a_managing-repos.md @@ -0,0 +1 @@ +In this video, we're going to understand how to manage a model repository on the HuggingFace model hub. In order to handle a repository, you should first have a Hugging Face account. A link to create a new account is available in the description. Once you are logged in, you can create a new repository by clicking on the "New model" option. You should be facing a similar modal to the following. In the "Owner" input, you can put either your own namespace or any of your organisations namespaces. The model name is the model identifier that will then be used to identify your model on your chosen namespace. The final choice is between public and private. Public models are accessible by anyone. This is the recommended, free option, as this makes your model easily accessible and shareable. The owners of your namespace are the only ones who can update and change your model. A more advanced option is the private option. In this case, only the owners of your namespace will have visibility over your model. Other users won't know it exists and will not be able to use it. Let's create a dummy model to play with. Once your model is created, comes the management of that model! Three tabs are available to you. You're facing the first one, which is the model card page; this is the page used to showcase your model to the world. We'll see how it can be completed in a bit. The second one is the "Files & Versions". Your model itself is a git repository - if you're unaware of what is a git repository, you can think of it as a folder containing files. If you have never used git before, we recommend looking at an introduction like the one provided in this video's description. The git repository allows you to see the changes happening over time in this folder, hence the term "Versions". We'll see how to add files and versions in a bit. The final tab is the "Settings" tab, which allow you to manage your model's visibility and availability. Let's first start by adding files to the repository. Files can be added through the web interface thanks to the "Add File" button. The added files can be of any type: python, json, text, you name it! Alongside your added file and its content, you should name your change, or commit. Generally, adding files is simpler when using the command line. We'll showcase how to do this using git. In addition to git, we're using git-lfs, which stands for large file storage in order to manage large model files. First, I make sure that both git and git-lfs are correctly installed on my system. Links to install git & git-lfs are provided in the video description. Then, we can get to work by cloning the repository locally. We have a repository with a single file! The file that we have just added to the repository using the web interface. We can edit it to see the contents of this file and update these. It just turns out I have a model handy, that can be used for sentiment analysis. I'll simply copy over the contents to this folder. This includes the model weights, configuration file and tokenizer to the repository. I can then track these two files with the git add command. Then, I commit the changes. I'm giving this commit the title of "Add model weights and configuration". Finally, I can push the new commit to the huggingface.co remote. When going back to the files & versions tab, we can now see the newly added commit with the updated files. We have seen two ways of adding files to a repository, a third way is explored in the video about the push to hub API. A link to this video is in the description. Go back to readme. Unfortunately, the front page of our model is still very empty. Let's add a README markdown file to complete it a little bit. This README is known as the modelcard, and it's arguably as important as the model and tokenizer files in a model repository. It is the central definition of the model, ensuring reusability by fellow community members and reproducibility of results, and providing a platform on which other members may build their artifacts. We'll only add a title and a small description here for simplicity's sake, but we encourage you to add information relevant to how was the model trained, its intended uses and limitations, as well as its identified and potential biases, evaluation results and code samples on how your model should be used. Great work contributing a model to the model hub! This model can now be used in downstream libraries simply by specifying your model identifier. \ No newline at end of file diff --git a/subtitles/en/raw/chapter4/03b_push-to-hub-pt.md b/subtitles/en/raw/chapter4/03b_push-to-hub-pt.md new file mode 100644 index 000000000..3963cf31e --- /dev/null +++ b/subtitles/en/raw/chapter4/03b_push-to-hub-pt.md @@ -0,0 +1 @@ +The Push to Hub API. Let's have a look at the push_to_hub API. You will need to be logged in with your Hugging Face account, which you can do by executing this first cell or typing huggingface-cli login in a terminal. Just enter your username and password and click login, which will store an authentication token in the cache of the machine you're using. Now, let's launch the fine-tuning of a BERT model on the GLUE COLA dataset. We won't go over the fine-tuning code because you can find it in any Transformers tutorial, or by looking at the videos linked below. What interests us here, is how we can leverage the Model Hub during training. This is done with the push_to_hub=True passed in your TrainingArguments . This will automatically upload your model to the Hub each time it is saved (so every epoch in our case), which allows you to resume training from a different machine if the current one gets interrupted. The model will be uploaded in your namespace, with the name of the output directory as a repository name. You can pick another name by passing it to the hub_model_id argument, and you can also push inside an organization you are a member of by passing a full repository name. With that done, we can just launch training and wait a little bit. Note that the model is pushed asynchronously, meaning that the training continues while your model is uploaded to the Hub. When your first commit is finished, you can go inspect your model on the Hub and even start playing with its inference widget while it's training! There is something wrong with the labels, but we will fix this later on in this video. When the training is finished, we should do one last push with trainer.push_to_hub for two reasons. One this will make sure we are uploading the final version of our models if we didn't already (for instance if we saved every n steps instead of every epoch). Two, this will draft a model card that will be the landing page of your model repo. Going back to the model page, you can see the Trainer included some metadata that is interpreted by the Hugging Face website in the model card. On top of informations about the training, the intermediate results or the hyperparameter used, we get the values of the metrics automatically displayed in a small widget, and a link to a leaderboard in Paper with Code. The Tensorboard runs have also been pushed to this repo, and we can look at them directly from the Model Hub. If you were not using the Trainer API to fine-tune your model, you can use the push_to_hub method on the model and tokenizer directly. Let's test this to fix our labels in the inference widget! The inference widget was using default names for labels because we did not indicate the correspondence between integers and label names. We can fix in the configuration by setting the label2id and id2label fields to their proper value then we can push the fixed config to our repo using the push_to_hub method. Once this is done and we can check on the website the model is now showing the proper labels! Now that the model is on the hub, we can use it from anywhere with the from_pretrained method. We just have to use the identifier from the hub and we can see that the model configuration and weights are automatically downloaded. We can use this model as we would any other Transformers model, for instance by loading it in a pipeline. Try the push_to_hub API on your next training to easily share your model with the rest of the world! \ No newline at end of file diff --git a/subtitles/en/raw/chapter4/03b_push-to-hub-tf.md b/subtitles/en/raw/chapter4/03b_push-to-hub-tf.md new file mode 100644 index 000000000..21d911172 --- /dev/null +++ b/subtitles/en/raw/chapter4/03b_push-to-hub-tf.md @@ -0,0 +1 @@ +Hi, this is going to be a video about the push_to_hub API for Tensorflow and Keras. So, to get started, we'll open up our notebook, and the first thing you'll need to do is log in to your HuggingFace account, for example with the notebook login function. So to do that, you simply call the function, the popup will emerge, you enter your username and password, which I'm going to pull out of my password manager here, and you're logged in. The next two cells are just getting everything ready for training. So we're just going to load a dataset, we're going to tokenize that dataset, and then we're going to load our model and compile it with the standard Adam optimizer. So I'm just going to run all of those, we'll wait a few seconds, and everything should be ready for training. Okay, so now we're ready to train I'm going to show you the two ways you can push your model to the Hub. So the first is with the PushToHubCallback. So a callback in Keras is a function that's called regularly during training. You can set it to be called after a certain number of steps, or every epoch, or even just once at the end of training. So a lot of callbacks in Keras, for example, control learning rate decaying on plateau and things like that. And so this callback, by default, will save your model to the Hub once every epoch. And that's really helpful especially if your training is very long, because that means you can resume from that save, so you get this automatic cloud-saving of your model, and you can even run inference with the checkpoints of your model that have been uploaded by this callback, and that means you can, y'know, actually run some test inputs and actually see how your model works at various stages during training, which is a really nice feature. So we're going to add the PushToHubCallback, and it takes just a few arguments. So the first argument is the temporary directory that files are going to be saved to before they're uploaded to the Hub. The second argument is the tokenizer, and the third argument here is the keyword argument hub_model_id. So that's the name it's going to be saved under on the HuggingFace Hub. You can also upload to an organization account just by adding the organization name before the repository name with a slash like this. So you probably don't have permissions to upload to the Hugging Face organization, if you do please file a bug and let us know extremely urgently. But if you do have access to your own organization then you can use that same approach to upload models to their account instead of to your own personal set of models. So, once you've made your callback you simply add it to the callbacks list when you're called model.fit() and everything is uploaded for you from there, and there's nothing else to worry about. The second way to upload a model, though, is to call model.push_to_hub(). So this is more of a once-off method - it's not called regularly during training. You can just call this manually whenever you want to upload a model to the hub. So we recommend running this after the end of training, just to make sure that you have a commit message just to guarantee that this was the final version of the model at the end of training. And it just makes sure that you're working with the definitive end-of-training model and not accidentally using a model that's from a checkpoint somewhere along the way. So I'm going to run both of these cells and then I'm going to cut the video here, just because training is going to take a couple of minutes, and so I'll skip forward to the end of that, when the models have all been uploaded, and I'm gonna show you how you can access the models in the Hub and the other things you can do with them from there. Okay, we're back and our model was uploaded, both by the PushToHubCallback and also by our call to model.push_to_hub() after training. So everything's looking good! So now if we drop over to my profile on HuggingFace, and you can get there just by clicking the profile button in the dropdown, we can see that the bert-fine-tuned-cola model is here, and was updated 3 minutes ago. So it'll always be at the top of your list, because they're sorted by how recently they were updated. And we can start querying our model immediately! So the dataset we were training on is the Glue CoLA dataset, and CoLA is an acronym for Corpus of Linguistic Acceptability. So what that means is that the model is being trained to decide if a sentence is grammatically or linguistically okay, or if there's a problem with it. For example, we could say "This is a legitimate sentence" and hopefully it realizes that this is in fact a legitimate sentence. So it might take a couple of seconds for the model to load when you call it for the first time, so I might cut a couple of seconds out of this video here. Okay, we're back! The model loaded and we got an output, but there's an obvious problem here. So these labels aren't really telling us what categories the model has actually assigned to this input sentence. So if we want to fix that, we want to make sure the model config has the correct names for each of the label classes, and then we want to upload that config. So we can do that down here. To get the label_names, we can get that from the dataset we loaded, from the 'features' attribute it has. And then we can create dictionaries "id2label" and "label2id" and just assign them to the model config, and then we can just push our updated config and that'll override the existing config in the Hub repo. So that's just been done, so now if we go back here, I'm going to use a slightly different sentence because the outputs for sentences are sometimes cached, and so if we want to generate new results I'm going to use something slightly different. So let's try an incorrect sentence, so this is not valid English grammar and hopefully the model will see that. It's going to reload here, so I'm going to cut a couple of seconds here, and then we'll see what the model is going to say. Okay! So the model's confidence isn't very good, because of course we didn't really optimize our hyperparameters at all, but it has decided that this sentence is more likely to be unacceptable than acceptable. Presumably if we tried a bit harder with training we could get a much lower validation loss and therefore the model's predictions would be more precise. But let's try our original sentence again - of course, because of the caching issue we're seeing that the original answers are unchanged. So let's try a different, valid sentence. So let's try "This is a valid English sentence". And we see that now the model correctly decides that it has a very high probability of being acceptable and a very low probability of being unacceptable. So you can use this inference API even with the checkpoints that are uploaded during training, so it can be very interesting to see how the model's predictions for sample inputs change with each epoch of training. Also, the model we've uploaded is going to be accessible to you and, if it's shared publicly, to anyone else. So if you want to load that model all you, or anyone else, needs to do is just to load it in either a pipeline or you can just load it with, for example, TFAutoModelForSequenceClassification and then for the name you would just simply pass the path to the repo you want to upload - or to download, excuse me. So if I want to use this model again, if I want to load it from the hub, I just run this one line of code, the model will be downloaded and with any luck it'll be ready to fine-tune on a different dataset, make predictions with, or do anything else you wanna do. So that was a quick overview of how, after your training or during your training, you can upload models to the Hub, you can checkpoint there, you can resume training from there, and you can get inference results from the models you've uploaded. So thank you, and I hope to see you in a future video! \ No newline at end of file diff --git a/subtitles/en/raw/chapter5/02_custom-dataset.md b/subtitles/en/raw/chapter5/02_custom-dataset.md new file mode 100644 index 000000000..251b392d6 --- /dev/null +++ b/subtitles/en/raw/chapter5/02_custom-dataset.md @@ -0,0 +1 @@ +Loading a custom dataset. Although the Hugging Face Hub hosts over a thousand public datasets, you'll often need to work with data that is stored on your laptop or some remote server. In this video we'll explore how the Datasets library can be used to load datasets that aren’t available on the Hugging Face Hub. As you can see in this table, the Datasets library provides several in-built scripts to load datasets in several formats. To load a dataset in one of these formats, you just need to provide the name of the format to the load_dataset function, along with a data_files argument that points to one or more filepaths or URLs. To see this in action, let's start by loading a local CSV file. In this example, we first download a dataset about wine quality from the UCI machine learning repository. Since this is a CSV file, we then specify the csv loading script. This script needs to know where our data is located, so we provide the filename as part of the data_files argument. The CSV loading script also allows you to pass several keyword arguments, so here we've also specified the separator as a semi-colon. And with that we can see the dataset is loaded automatically as a DatasetDict object, with each column in the CSV file represented as a feature. If your dataset is located on some remote server like GitHub or some other repository, the process is very similar. The only difference is that now the data_files argument points to a URL instead of a local filepath. Let's now take a look at loading raw text files. This format is quite common in NLP and you'll typically find books and plays are just a single file with raw text inside. In this example, we have a text file of Shakespeare plays that's stored on a GitHub repository. As we did for CSV files, we simply choose the text loading script and point the data_files argument to the URL. As you can see, these files are processed line-by-line, so empty lines in the raw text are also represented as a row in the dataset. For JSON files, there are two main formats to know about. The first one is called JSON Lines, where every row in the file is a separate JSON object. For these files, you can load the dataset by selecting the json loading script and pointing the data_files argument to the file or URL. In this example, we've loaded a JSON lines files based on Stack Exchange questions and answers. \ No newline at end of file diff --git a/subtitles/en/raw/chapter5/03a_slice-and-dice.md b/subtitles/en/raw/chapter5/03a_slice-and-dice.md new file mode 100644 index 000000000..747ee4e30 --- /dev/null +++ b/subtitles/en/raw/chapter5/03a_slice-and-dice.md @@ -0,0 +1 @@ +How to slice and dice a dataset. Most of the time, the data you work with won’t be perfectly prepared for training models. In this video we’ll explore various features that Datasets provides to clean up your datasets. The Datasets library provides several built-in methods that allow you to wrangle your data. In this video we'll see how you can shuffle and split your data, select the rows you're interested in, tweak the columns, and apply processing functions with the map() method. Let's start with shuffling. It is generally a good idea to apply shuffling to the training set so that your model doesn't learn any artificial ordering in the data. If you want to shuffle the whole dataset, you can apply the appropriately named shuffle() method to your dataset. You can see an example of this method in action here, where we've downloaded the training split of the SQUAD dataset and shuffled all the rows randomly.Another way to shuffle the data is to create random train and test splits. This can be useful if you have to create your own test splits from raw data. To do this, you just apply the train_test_split method and specify how large the test split should be. In this example, we've specified that the test set should be 10% of the total dataset size. You can see that the output of train_test_split is a DatasetDict object, whose keys correspond to the new splits. Now that we know how to shuffle a dataset, let's take a look at returning the rows we're interested in. The most common way to do this is with the select method. This method expects a list or generator of the dataset's indices, and will then return a new Dataset object containing just those rows. If you want to create a random sample of rows, you can do this by chaining the shuffle and select methods together. In this example, we've created a sample of 5 elements from the SQuAD dataset. The last way to pick out specific rows in a dataset is by applying the filter method. This method checks whether each rows fulfills some condition or not. For example, here we've created a small lambda function that checks whether the title starts with the letter "L". Once we apply this function with the filter method, we get a subset of the data consisting of just these titles. So far we've been talking about the rows of a dataset, but what about the columns? The Datasets library has two main methods for transforming columns: a rename_column method to change the name of a column, and a remove_columns method to delete them. You can see examples of both these method here. Some datasets have nested columns and you can expand these by applying the flatten method. For example in the SQUAD dataset, the answers column contains a text and answer_start field. If we want to promote them to their own separate columns, we can apply flatten as shown here. Of course, no discussion of the Datasets library would be complete without mentioning the famous map method. This method applies a custom processing function to each row in the dataset. For example,here we first define a lowercase_title function that simply lowercases the text in the title column and then we feed that to the map method and voila! we now have lowercase titles. The map method can also be used to feed batches of rows to the processing function. This is especially useful for tokenization, where the tokenizers are backed by the Tokenizers library can use fast multithreading to process batches in parallel. \ No newline at end of file diff --git a/subtitles/en/raw/chapter5/03b_dataframes.md b/subtitles/en/raw/chapter5/03b_dataframes.md new file mode 100644 index 000000000..596d1e402 --- /dev/null +++ b/subtitles/en/raw/chapter5/03b_dataframes.md @@ -0,0 +1 @@ +Datasets and DataFrames equals love. Although the processing functions of Datasets will cover most the cases needed to train a model, there are times when you’ll need to switch to a library like Pandas to access more powerful features or high-level APIs for visualisation. Fortunately, Datasets is designed to be interoperable with libraries like Pandas, as well as NumPy, PyTorch, TensorFlow, and JAX. In this video, we'll take a look at how we can quickly switch our data to Pandas DataFrames and back. As an example, let's suppose we're analysing Supreme Court cases from Switzerland. As usual we download our dataset from the Hub using the load_dataset() function, and you can see that the first element of the training set is an ordinary Python dictionary with various fields of interest. Now suppose that before we train any models, we'd like to explore the data a bit. For example we might be interested in knowing which legal area is most common or we might want to know how the languages are distributed across regions. Answering these questions with the native Arrow format isn't easy, but we can easily switch to Pandas to get our answers! The way this works is by using the set_format() method, which will change the output format of the dataset from Python dictionaries to Pandas DataFrames. As you can see in this example, each row in the dataset is represented as a DataFrame, so we can slice the whole dataset to get a single DataFrame of the dataset. The way this works under the hood is that the Datasets library changes the magic __getitem__() method of the dataset. The __getitem__() method is a special method for Python containers that allows you to specify how indexing works. In this case, the __getitem__() method of the raw dataset starts off by returning Python dictionaries and then after applying set_format() we change __getitem__() to return DataFrames instead. The Datasets library also provides a to_pandas() method if you want to do the format conversion and slicing of the dataset in one go. And once you have a DataFrame, you can find answers to all sorts of complex questions or make plots with your favourite visualisation library and so on. The only thing to remember is that once you are done with your Pandas analysis, you should reset the output format back to Arrow tables. If you don't, you can run into problems if you try to tokenize your text because it is no longer represented as strings in a dictionary. By resetting the output format, we get back Arrow tables and can tokenize without problem! \ No newline at end of file diff --git a/subtitles/en/raw/chapter5/03c_save-reload.md b/subtitles/en/raw/chapter5/03c_save-reload.md new file mode 100644 index 000000000..ca22d1c4e --- /dev/null +++ b/subtitles/en/raw/chapter5/03c_save-reload.md @@ -0,0 +1 @@ +Saving and reloading a dataset. In this video we'll take a look saving a dataset in various formats, and explore the ways to reload the saved data. When you download a dataset, the processing scripts and data are stored locally on your computer. The cache allows the Datasets library to avoid re-downloading or processing the entire dataset every time you use it. The data is stored in the form of Arrow tables whose location can be found by accessing the dataset's cache_files attribute. In this example, we've downloaded the allocine dataset from the Hugging Face Hub and you can see there are three Arrow files stored in the cache, one for each split. But in many cases, you'll want to save your dataset in a different location or format. As shown in the table, the Datasets library provides four main functions to achieve this. You're probably familiar with the CSV and JSON formats, both of which are great if you want to save small to medium-sized datasets. But if your dataset is huge, you'll want to save it in either the Arrow or Parquet formats. Arrow files are great if you plan to reload or process the data in the near future. Parquet files are designed for long-term disk storage and are very space efficient. Let's take a closer look at each format. To save a Dataset or a DatasetDict object in the Arrow format we use the save_to_disk function. As you can see in this example, we simply provide the path we wish to save the data to, and the Datasets library will automatically create a directory for each split to store the Arrow table and metadata. Since we're dealing with a DatasetDict object that has multiple splits, this information is also stored in the dataset_dict.json file. Now when we want to reload the Arrow datasets, we use the load_from_disk function. We simply pass the path of our dataset directory and voila the original dataset is recovered! If we want to save our datasets in the CSV format we use the to_csv function. In this case you'll need to loop over the splits of the DatasetDict object and save each dataset as an individual CSV file. Since the to_csv file is based on the one from Pandas, you can pass keyword arguments to configure the output. In this example, we've set the index argument to None to prevent the dataset's index column from being included in the CSV files. To reload our CSV files, we use the load_dataset function together with the csv loading script and data_files argument which specifies the filenames associated with each split. As you can see in this example, by providing all the splits and their filenames, we've recovered the original DatasetDict object. To save a dataset in the JSON or Parquet formats is very similar to the CSV case. We use either the to_json function for JSON files or the to_parquet function for Parquet ones. And just like the CSV case, we need to loop over the splits and save each one as an individual file. Once our datasets are saved as JSON or Parquet files, we can reload them again with the appropriate script in the load_dataset function, and a data_files argument as before. This example shows how we can reload our saved datasets in either format. And with that you now know how to save your datasets in various formats! \ No newline at end of file diff --git a/subtitles/en/raw/chapter5/04_memory-mapping.md b/subtitles/en/raw/chapter5/04_memory-mapping.md new file mode 100644 index 000000000..5f75c8e70 --- /dev/null +++ b/subtitles/en/raw/chapter5/04_memory-mapping.md @@ -0,0 +1,6 @@ +Memory mapping and streaming. In this video we'll take a look at two core features of the Datasets library that allow you to load and process huge datasets without blowing up your laptop's CPU. + +Nowadays it is not uncommon to find yourself working with multi-GB sized datasets, especially if you’re planning to pretrain a transformer like BERT or GPT-2 from scratch. In these cases, even *loading* the data can be a challenge. For example, the C4 corpus used to +pretrain T5 consists of over 2 terabytes of data! + +To handle these large datasets, the Datasets library is built on two core features: the Apache Arrow format and a streaming API. Arrow is designed for high-performance data processing and represents each table-like dataset with an in-memory columnar format. As you can see in this example, columnar formats group the elements of a table in consecutive blocks of RAM and this unlocks fast access and processing. Arrow is great at processing data at any scale, but some datasets are so large that you can't even fit them on your hard disk. For these cases, the Datasets library provides a streaming API that allows you to progressively download the raw data one element at a time. The result is a special object called an IterableDataset that we'll see in more detail soon. Let's start by looking at why Arrow is so powerful. The first feature is that it treat every dataset as a memory-mapped file. Memory mapping is a mechanism that maps a portion of a file or an entire file on disk to a chunk of virtual memory. This allows applications to access can access segments in an extremely large file without having to read the entire file into memory first. Another cool feature of Arrow's memory mapping capability is that it allows multiple processes to work with the same large dataset without moving it or copying it in any way. This "zero-copy" feature of Arrow makes it extremely fast for iterating over a dataset. In this example you can see that we iterate over 15 million rows in about a minute using a standard laptop - that's not too bad at all! Let's now take a look at how we can stream a large dataset. The only change you need to make is to set the streaming=True argument in the load_dataset() function. This will return a special IterableDataset object, which is a bit different to the Dataset objects we've seen in other videos. This object is an iterable, which means we can't index it to access elements, but instead iterate on it using the iter and next methods. This will download and access a single example from the dataset, which means you can progressively iterate through a huge dataset without having to download it first. Tokenizing text with the map() method also works in a similar way. We first stream the dataset and then apply the map() method with the tokenizer. To get the first tokenized example we apply iter and next. The main difference with an IterableDataset is that instead of using the select() method to return example, we use the take() and skip() methods because we can't index into the dataset. The take() method returns the first N examples in the dataset, while skip() skips the first N and returns the rest. You can see examples of both in action here, where we create a validation set from the first 1000 examples and then skip those to create the training set. \ No newline at end of file diff --git a/subtitles/en/raw/chapter5/05_upload-dataset.md b/subtitles/en/raw/chapter5/05_upload-dataset.md new file mode 100644 index 000000000..3abd0ee4d --- /dev/null +++ b/subtitles/en/raw/chapter5/05_upload-dataset.md @@ -0,0 +1 @@ +In this video we'll take a look at how you upload your very own dataset to the Hub. The first you'll need to do is create a new dataset repository on the Hub. Just click on your profile icon and select the "New Dataset" button. Next we need to assign an owner of the dataset. By default, this will be your Hub account, but you can also create datasets under any organisation that you belong to. Then we just need to give the dataset a name and specify whether it is a public or private dataset. Public datasets can be accessed by anyone, while private datasets can only be accessed by you or members of your organisation. And with that we can go ahead and create the dataset! Now that you have an empty dataset repository on the Hub, the next thing to do is add some data to it! You can do this with Git, but the easiest way is by selecting "Upload file" and uploading the files directly from your machine. After you've uploaded the files, you'll see them appear in the repository under the "Files and versions" tab. The last step is to create a dataset card. Well documented datasets are more likely to be useful to others (including your future self!) as they provide the context to decide whether the dataset is relevant or whether there are any biases or risks associated with using the dataset. On the Hugging Face Hub, this information is stored in each repository’s README.md file and there are two main steps you should take. First you need to create some metadata that will allow your dataset to be easily found by others on the Hub. You can create this metadata using the Datasets Tagging Application which we'll link to in the video description. Once you have created the metadata, you can fill out the rest of the dataset card and we provide a template that is also linked in the video. And once your dataset is up on the Hub, you can load it using the trusty load_dataset() function! Just provide the name of your repository and a data_files argument for the files and you're good to go! \ No newline at end of file diff --git a/subtitles/en/raw/chapter5/06_text-embeddings.md b/subtitles/en/raw/chapter5/06_text-embeddings.md new file mode 100644 index 000000000..7121eca84 --- /dev/null +++ b/subtitles/en/raw/chapter5/06_text-embeddings.md @@ -0,0 +1 @@ +Text embeddings and semantic search. In this video we’ll explore how Transformer models represent text as embedding vectors and how these vectors can be used to find similar documents in a corpus. Text embeddings are just a fancy way of saying that we can represent text as an array of numbers called a vector. To create these embeddings we usually use an encoder-based model like BERT. In this example, you can see how we feed three sentences to the encoder and get three vectors as the output. Reading the text, we can see that walking the dog seems to be most similar to walking the cat, but let's see if we can quantify this! The trick to do the comparison is to compute a similarity metric between each pair of embedding vectors. These vectors usually live in a high-dimensional space, so a similarity metric can be anything that measures some sort of distance between vectors. One popular metric is cosine similarity, which uses the angle between two vectors to measure how close they are. In this example, our embedding vectors live in 3D and we can see that the orange and grey vectors are close to each other and have a smaller angle. Now one problem we have to deal with is that Transformer models like BERT will actually return one embedding vector per token. For example in the sentence "I took my dog for a walk", we can expect several embedding vectors, one for each word. For example, here we can see the output of our model has produced 9 embedding vectors per sentence, and each vector has 384 dimensions. But what we really want is a single embedding vector for the whole sentence. To deal with this, we can use a technique called pooling. The simplest pooling method is to just take the token embedding of the CLS token. Alternatively, we can average the token embeddings which is called mean pooling. With mean pooling only thing we need to make sure is that we don't include the padding tokens in the average, which is why you can see the attention mask being used here. This now gives us one 384 dimensional vector per sentence which is exactly what we want! And once we have our sentence embeddings, we can compute the cosine similarity for each pair of vectors. In this example we use the function from scikit-learn and you can see that the sentence "I took my dog for a walk" has an overlap of 0.83 with "I took my cat for a walk". Hooray! We can take this idea one step further by comparing the similarity between a question and a corpus of documents. For example, suppose we embed every post in the Hugging Face forums. We can then ask a question, embed it, and check which forum posts are most similar. This process is often called semantic search, because it allows us to compare queries with context. To create a semantic search engine is quite simple in Datasets. First we need to embed all the documents. In this example, we take a small sample from the SQUAD dataset and apply the same embedding logic as before. This gives us a new column called "embeddings" that stores the embedding of every passage. Once we have our embeddings, we need a way to find nearest neighbours to a query. Datasets provides a special object called a FAISS index that allows you to quickly compare embedding vectors. So we add the FAISS index, embed a question and voila! we've now found the 3 most similar articles which might store the answer. \ No newline at end of file diff --git a/subtitles/en/raw/chapter6/02_train-new-tokenizer.md b/subtitles/en/raw/chapter6/02_train-new-tokenizer.md new file mode 100644 index 000000000..40a228e11 --- /dev/null +++ b/subtitles/en/raw/chapter6/02_train-new-tokenizer.md @@ -0,0 +1 @@ +In this video we will see together what is the purpose of training a tokenizer, what are the key steps to follow and what is the easiest way to do it. You will ask yourself the question "Should I train a new tokenizer?" when you plan to train a new model from scratch. A trained tokenizer would not be suitable for your corpus if your corpus is in a different language, uses new characters such as accents or upper cased letters, has a specific vocabulary, for example medical or legal, or uses a different style, a language from another century for instance. For example, if I take the tokenizer trained for the bert-base-uncased model and ignore its normalization step then we can see that the tokenization operation on the English sentence "here is a sentence adapted to our tokenizer" produces a rather satisfactory list of tokens in the sense that this sentence of 8 words is tokenized into 9 tokens. On the other hand if we use this same tokenizer on a sentence in Bengali, we see that either a word is divided into many sub tokens or that the tokenizer does not know one of the unicode characters and returns only an unknown token. The fact that a "common" word is split into many tokens can be problematic because language models can only handle a sequence of tokens of limited length. A tokenizer that excessively splits your initial text may even impact the performance of your model. Unknown tokens are also problematic because the model will not be able to extract any information from the "unknown" part of the text. In this other example, we can see that the tokenizer replaces words containing characters with accents and capital letters with unknown tokens. Finally, if we use again this tokenizer to tokenize medical vocabulary we see again that a single word is divided into many sub tokens: 4 for "paracetamol" and "pharyngitis". Most of the tokenizers used by the current state of the art language models need to be trained on a corpus that is similar to the one used to pre-train the language model. This training consists in learning rules to divide the text into tokens and the way to learn these rules and use them depends on the chosen tokenizer model. Thus, to train a new tokenizer it is first necessary to build a training corpus composed of raw texts. Then, you have to choose an architecture for your tokenizer. Here there are two options: the simplest is to reuse the same architecture as the one of a tokenizer used by another model already trained,otherwise it is also possible to completely design your tokenizer but it requires more experience and attention. Once the architecture is chosen, one can thus train this tokenizer on your constituted corpus. Finally, the last thing that you need to do is to save the learned rules to be able to use this tokenizer which is now ready to be used. Let's take an example: let's say you want to train a GPT-2 model on Python code. Even if the python code is in English this type of text is very specific and deserves a tokenizer trained on it - to convince you of this we will see at the end the difference produced on an example. For that we are going to use the method "train_new_from_iterator" that all the fast tokenizers of the library have and thus in particular GPT2TokenizerFast. This is the simplest method in our case to have a tokenizer adapted to python code. Remember, the first step is to gather a training corpus. We will use a subpart of the CodeSearchNet dataset containing only python functions from open source libraries on Github. It's good timing, this dataset is known by the datasets library and we can load it in two lines of code. Then, as the "train_new_from_iterator" method expects a iterator of lists of texts we create the "get_training_corpus" function which will return an iterator. Now that we have our iterator on our python functions corpus, we can load the gpt-2 tokenizer architecture. Here "old_tokenizer" is not adapted to our corpus but we only need one more line to train it on our new corpus. An argument that is common to most of the tokenization algorithms used at the moment is the size of the vocabulary, we choose here the value 52 thousand. Finally, once the training is finished, we just have to save our new tokenizer locally or send it to the hub to be able to reuse it very easily afterwards. Finally, let's see together on an example if it was useful to re-train a tokenizer similar to gpt2 one. With the original tokenizer of GPT-2 we see that all spaces are isolated and the method name "randn" relatively common in python code is split in 2. With our new tokenizer, single and double indentations have been learned and the method "randn" is tokenized into 1 token. And with that, you now know how to train your very own tokenizers! \ No newline at end of file diff --git a/subtitles/en/raw/chapter6/03a_why-fast-tokenizers.md b/subtitles/en/raw/chapter6/03a_why-fast-tokenizers.md new file mode 100644 index 000000000..41d98f436 --- /dev/null +++ b/subtitles/en/raw/chapter6/03a_why-fast-tokenizers.md @@ -0,0 +1 @@ +Why are fast tokenizers called fast? In this video we will see exactly how much faster the so-called fast tokenizers are compared to their slow counterparts. For this benchmark, we will use the GLUE MNLI dataset, which contains 432 thousands pairs of texts. We will see how long it takes for the fast and slow versions of a BERT tokenizer to process them all. We define our fast and slow tokenizer using the AutoTokenizer API. The fast tokenizer is the default (when available), so we pass along use_fast=False to define the slow one. In a notebook, we can time the execution of a cell with the time magic command, like this. Processing the whole dataset is four times faster with a fast tokenizer. That's quicker indeed, but not very impressive however. That's because we passed along the texts to the tokenizer one at a time. This is a common mistake to do with fast tokenizers, which are backed by Rust and thus able to parallelize the tokenization of multiple texts. Passing them only one text at a time is like sending a cargo ship between two continents with just one container, it's very inefficient. To unleash the full speed of our fast tokenizers, we need to send them batches of texts, which we can do with the batched=True argument of the map method. Now those results are impressive! The fast tokenizer takes 12 seconds to process a dataset that takes 4 minutes to the slow tokenizer. Summarizing the results in this table, you can see why we have called those tokenizers fast. And this is only for tokenizing texts. If you ever need to train a new tokenizer, they do this very quickly too! \ No newline at end of file diff --git a/subtitles/en/raw/chapter6/03b_fast-tokenizers-superpowers.md b/subtitles/en/raw/chapter6/03b_fast-tokenizers-superpowers.md new file mode 100644 index 000000000..848abea21 --- /dev/null +++ b/subtitles/en/raw/chapter6/03b_fast-tokenizers-superpowers.md @@ -0,0 +1 @@ +The fast tokenizers of the Transformers library are fast, but they also implement features that will be super useful for data pre-processing and post-processing. Let's have a look at them! First, let's have a look at the usual output of a tokenizer. We get input IDs that correspond to tokens, but we lose a lot of information in the process. For instance, here the tokenization is the same for the two sentences, even if one has several more spaces than the other. Just having the input IDs is thus not enough if we want to match some tokens with a span of text (something we will need to do when tackling question answering for instance). It's also difficult to know when two tokens belong to the same word or not: it looks easy when you just look at the output of a BERT tokenizer, we just need to look for the ##. But other tokenizers have different ways to tokenize parts of words. For instance RoBERTa adds this special G symbol to mark the tokens at the beginning of a word, and T5 uses this special underscore symbol for the same purpose. Thankfully, the fast tokenizers keep track of the word each token comes from, with a word_ids method you can use on their outputs. The output is not necessarily clear, but assembled together in a nice table like this, we can look at the word position for each token. Even better, the fast tokenizers keep track of the span of characters each token comes from, and we can get them when calling it on one (or several) text by adding the return_offsets_mapping=True argument. In this instance, we can see how we jump positions between the ##s token and the super token, because of the multiple spaces in the initial sentence. To enable this, the fast tokenizers store additional information at each step of their internal pipeline. That internal pipeline consists of normalization, where we apply some cleaning to the text, like lowercasing or removing the accents;() pre-tokenization, which is where we split the texts into words;() then we apply the model of the tokenizer, which is where the words are splits into tokens,() before finally doing the post-processing, where special tokens are added. From the beginning to the end of the pipeline, the tokenizer keeps track of each span of text that corresponds to each word, then each token. We will see how useful it is when we tackle the following tasks: when doing masked language modeling, one variation that gets state-of-the-art results is to mask all the tokens of a given word instead of randomly chosen tokens. This will require us to use the word IDs we saw. When doing token classification, we'll need to convert the labels we have on words, to labels on each tokens. As for the offset mappings, it will be super useful when we need to convert token positions in a sentence into a span of text, which we will need to know when looking at question answering or when grouping the tokens corresponding to the same entity in token classification. To have a look at these tasks, check the videos linked below! \ No newline at end of file diff --git a/subtitles/en/raw/chapter6/03c_tokenization-pipeline-pt.md b/subtitles/en/raw/chapter6/03c_tokenization-pipeline-pt.md new file mode 100644 index 000000000..7ae578cc5 --- /dev/null +++ b/subtitles/en/raw/chapter6/03c_tokenization-pipeline-pt.md @@ -0,0 +1 @@ +Let's have a look inside the token classification pipeline. In the pipeline video, we looked at the different applications the Transformers library supports out of the box, one of them being token classification, for instance predicting for each word in a sentence whether they correspond to a person, an organization or a location. We can even group together the tokens corresponding to the same entity, for instance all the tokens that formed the word Sylvain here, or Hugging and Face. The token classification pipeline works the same way as the text classification pipeline we studied in a previous video. There are three steps: the tokenization, the model, and the post processing. The first two steps are identical to the text classification pipeline, except we use an auto token classification model instead of a sequence classification one. We tokenize our text then feed it to the model. Instead of getting one number for each possible label for the whole sentence, we get one number for each of the possible 9 labels for every token in the sentence, here 19. Like all the other models of the Transformers library, our model outputs logits, which we turn into predictions by using a SoftMax. We also get the predicted label for each token by taking the maximum prediction (since the softmax function preserves the order, we could have done it on the logits if we had no need of the predictions). The model config contains the label mapping in its id2label field. Using it, we can map every token to its corresponding label. The label O correspond to "no entity", which is why we didn't see it in our results in the first slide. On top of the label and the probability, those results included the start and end character in the sentence. We will need to use the offset mapping of the tokenizer to get those (look at the video linked below if you don't know about them already). Then, looping through each token that has a label distinct from O, we can build the list of results we got with our first pipeline. The last step is to group together tokens that correspond to the same entity.This is why we had two labels for each type of entity: I-PER and B-PER for instance. It allows us to know if a token is in the same entity as the previous one.() Note that there are two ways of labelling used for token classification, one (in pink here) uses the B-PER label at the beginning of each new entity, but the other (in blue) only uses it to separate two adjacent entities of the same type. In both cases, we can flag a new entity each time we see a new label appearing (either with the I or B prefix) then take all the following tokens labelled the same, with an I-flag. This, coupled with the offset mapping to get the start and end characters allows us to get the span of texts for each entity. \ No newline at end of file diff --git a/subtitles/en/raw/chapter6/03c_tokenization-pipeline-tf.md b/subtitles/en/raw/chapter6/03c_tokenization-pipeline-tf.md new file mode 100644 index 000000000..6aebba9de --- /dev/null +++ b/subtitles/en/raw/chapter6/03c_tokenization-pipeline-tf.md @@ -0,0 +1 @@ +Let's have a look inside the token classification pipeline. In the pipeline video, we looked at the different applications the Transformers library supports out of the box, one of them being token classification, for instance predicting for each word in a sentence whether they correspond to a person, an organization or a location. We can even group together the tokens corresponding to the same entity, for instance all the tokens that formed the word Sylvain here, or Hugging and Face. The token classification pipeline works the same way as the text classification pipeline we studied in a previous video. There are three steps: the tokenization, the model, and the post processing. The first two steps are identical to the text classification pipeline, except we use an auto token classification model instead of a sequence classification one. We tokenize our text then feed it to the model. Instead of getting one number for each possible label for the whole sentence, we get one number for each of the possible 9 labels for every token in the sentence, here 19. Like all the other models of the Transformers library, our model outputs logits, which we turn into predictions by using a SoftMax. We also get the predicted label for each token by taking the maximum prediction (since the softmax function preserves the order, we could have done it on the logits if we had no need of the predictions). The model config contains the label mapping in its id2label field. Using it, we can map every token to its corresponding label. The label O correspond to "no entity", which is why we didn't see it in our results in the first slide. On top of the label and the probability, those results included the start and end character in the sentence. We will need to use the offset mapping of the tokenizer to get those (look at the video linked below if you don't know about them already). Then, looping through each token that has a label distinct from O, we can build the list of results we got with our first pipeline. The last step is to group together tokens that correspond to the same entity. This is why we had two labels for each type of entity: I-PER and B-PER for instance. It allows us to know if a token is in the same entity as the previous one.() Note that there are two ways of labelling used for token classification, one (in pink here) uses the B-PER label at the beginning of each new entity, but the other (in blue) only uses it to separate two adjacent entities of the same type. In both cases, we can flag a new entity each time we see a new label appearing (either with the I or B prefix) then take all the following tokens labelled the same, with an I-flag. This, coupled with the offset mapping to get the start and end characters allows us to get the span of texts for each entity. \ No newline at end of file diff --git a/subtitles/en/raw/chapter6/04_question-answering-pipeline-pt.md b/subtitles/en/raw/chapter6/04_question-answering-pipeline-pt.md new file mode 100644 index 000000000..91cdbf0c9 --- /dev/null +++ b/subtitles/en/raw/chapter6/04_question-answering-pipeline-pt.md @@ -0,0 +1 @@ +Let's have a look inside the question answering pipeline. The question answering pipeline can extracts answers to questions from a given context or passage of text, like this part of the Transformers repo README. It also works for very long contexts, even if the answer is at the very end, like in this example. In this video, we will see why! The question answering pipeline follows the same steps as the other pipelines: the question and context are tokenized as a sentence pair, fed to the model then some post-processing is applied. The tokenization and model steps should be familiar. We use the auto class suitable for Question Answering instead of sequence classification, but one key difference with text classification is that our model outputs two tensors named start logits and end logits. Why is that? Well this is the way the model finds the answer to the question. First let's have a look at the model inputs. It's numbers associated with the tokenization of the question followed by the context (with the usual CLS and SEP special tokens). The answer is a part of those tokens. So we ask the model to predict which token starts the answer and which ends the answer. For our two logit outputs, the theoretical labels are the pink and purple vectors. To convert those logits into probabilities, we will need to apply a SoftMax, like in the text classification pipeline. We just mask the tokens that are not part of the context before doing that, leaving the initial CLS token unmasked as we use it to predict an impossible answer. This is what it looks in terms of code. We use a large negative number for the masking, since its exponential will then be 0. Now the probability for each start and end position corresponding to a possible answer, we give a score that is the product of the start probabilities and end probabilities at those positions. Of course, a start index greater than an end index corresponds to an impossible answer. Here is the code to find the best score for a possible answer. Once we have the start and end positions of the tokens, we use the offset mappings provided by our tokenizer to find the span of characters in the initial context, and get our answer! Now, when the context is long, it might get truncated by the tokenizer. This might result in part of the answer, or worse, the whole answer, being truncated. So we don't discard the truncated tokens but build new features with them. Each of those features contains the question, then a chunk of text in the context. If we take disjoint chunks of texts, we might end up with the answer being split between two features. So instead, we take overlapping chunks of texts, to make sure at least one of the chunks will fully contain the answer to the question. The tokenizers do all of this for us automatically with the return overflowing tokens option. The stride argument controls the number of overlapping tokens. Here is how our very long context gets truncated in two features with some overlap. By applying the same post-processing we saw before for each feature, we get the answer with a score for each of them, and we take the answer with the best score as a final solution. \ No newline at end of file diff --git a/subtitles/en/raw/chapter6/04_question-answering-pipeline-tf.md b/subtitles/en/raw/chapter6/04_question-answering-pipeline-tf.md new file mode 100644 index 000000000..91cdbf0c9 --- /dev/null +++ b/subtitles/en/raw/chapter6/04_question-answering-pipeline-tf.md @@ -0,0 +1 @@ +Let's have a look inside the question answering pipeline. The question answering pipeline can extracts answers to questions from a given context or passage of text, like this part of the Transformers repo README. It also works for very long contexts, even if the answer is at the very end, like in this example. In this video, we will see why! The question answering pipeline follows the same steps as the other pipelines: the question and context are tokenized as a sentence pair, fed to the model then some post-processing is applied. The tokenization and model steps should be familiar. We use the auto class suitable for Question Answering instead of sequence classification, but one key difference with text classification is that our model outputs two tensors named start logits and end logits. Why is that? Well this is the way the model finds the answer to the question. First let's have a look at the model inputs. It's numbers associated with the tokenization of the question followed by the context (with the usual CLS and SEP special tokens). The answer is a part of those tokens. So we ask the model to predict which token starts the answer and which ends the answer. For our two logit outputs, the theoretical labels are the pink and purple vectors. To convert those logits into probabilities, we will need to apply a SoftMax, like in the text classification pipeline. We just mask the tokens that are not part of the context before doing that, leaving the initial CLS token unmasked as we use it to predict an impossible answer. This is what it looks in terms of code. We use a large negative number for the masking, since its exponential will then be 0. Now the probability for each start and end position corresponding to a possible answer, we give a score that is the product of the start probabilities and end probabilities at those positions. Of course, a start index greater than an end index corresponds to an impossible answer. Here is the code to find the best score for a possible answer. Once we have the start and end positions of the tokens, we use the offset mappings provided by our tokenizer to find the span of characters in the initial context, and get our answer! Now, when the context is long, it might get truncated by the tokenizer. This might result in part of the answer, or worse, the whole answer, being truncated. So we don't discard the truncated tokens but build new features with them. Each of those features contains the question, then a chunk of text in the context. If we take disjoint chunks of texts, we might end up with the answer being split between two features. So instead, we take overlapping chunks of texts, to make sure at least one of the chunks will fully contain the answer to the question. The tokenizers do all of this for us automatically with the return overflowing tokens option. The stride argument controls the number of overlapping tokens. Here is how our very long context gets truncated in two features with some overlap. By applying the same post-processing we saw before for each feature, we get the answer with a score for each of them, and we take the answer with the best score as a final solution. \ No newline at end of file diff --git a/subtitles/en/raw/chapter6/05a_normalization.md b/subtitles/en/raw/chapter6/05a_normalization.md new file mode 100644 index 000000000..c5529f602 --- /dev/null +++ b/subtitles/en/raw/chapter6/05a_normalization.md @@ -0,0 +1 @@ +In this video we will see together what is the normalizer component that we find at the beginning of each tokenizer. The normalization operation consists in applying a succession of normalization rules to the raw text. We choose normalization rules to remove noise in the text which seems useless for the learning and use of our language model. Let's take a very diverse sentence with different fonts, upper and lower case characters, accents, punctuation and multiple spaces, to see how several tokenizers normalize it. The tokenizer from the FNet model has transformed the letters with font variants or circled into their basic version and has removed the multiple spaces. And now if we look at the normalization with Retribert's tokenizer, we can see that it keeps characters with several font variants and keeps the multiple spaces but it removes all the accents. And if we continue to test the normalization of many other tokenizers associated to models that you can find on the Hub we can see that they also propose other normalizations. With the fast tokenizers, it is very easy to observe the normalization chosen for the currently loaded tokenizer. Indeed, each instance of a fast tokenizer has an underlying tokenizer from the Tokenizers library stored in the backend_tokenizer attribute. This object has itself a normalizer attribute that we can use thanks to the "normalize_str" method to normalize a string. It is thus very practical that this normalization which was used at the time of the training of the tokenizer was saved and that it applies automatically when you asks a trained tokenizer to tokenize a text. For example, if we hadn't included the albert normalizer we would have had a lot of unknown tokens by tokenizing this sentence with accents and capital letters. These transformations can also be undetectable with a simple "print". Indeed, keep in mind that for a computer, text is only a succession of 0 and 1 and it happens that different successions of 0 and 1 render the same printed character. The 0s and 1s go in groups of 8 to form a byte. The computer must then decode this sequence of bytes into a sequence of "code points". In our example the 2 bytes are transformed into a single "code point" by UTF-8. The unicode standard then allows us to find the character corresponding to this code point: the c cedilla. Let's repeat the same operation with this new sequence composed of 3 bytes, this time it is transformed into 2 "code points" .... which also correspond to the c cedilla character! It is in fact the composition of the unicode Latin Small Letter Cand the combining cedilla. But it's annoying because what appears to us to be a single character is not at all the same thing for the computer. Fortunately, there are unicode standardization standards known as NFC, NFD, NFKC and NFKD that allow erasing some of these differences. These standards are often used by tokenizers! On all these previous examples, even if the normalizations changed the look of the text, they did not change the content: you could still read "Hello world, let's normalize this sentence". However, you must be aware that some normalizations can be very harmful if they are not adapted to their corpus. For example, if you take the French sentence "un père indigné", which means "An indignant father", and normalize it with the bert-base-uncase tokenizer which removes the accents then the sentence becomes "un père indigne" which means "An unworthy father". If you watch this video to build your own tokenizer, there are no absolute rules to choose or not a normalization for your brand new tokenizer but I advise you to take the time to select them so that they do not make you lose important information. \ No newline at end of file diff --git a/subtitles/en/raw/chapter6/05b_pre-tokenization.md b/subtitles/en/raw/chapter6/05b_pre-tokenization.md new file mode 100644 index 000000000..7ae1cbe0e --- /dev/null +++ b/subtitles/en/raw/chapter6/05b_pre-tokenization.md @@ -0,0 +1 @@ +The tokenization pipeline involves several steps that convert raw text into numbers. In this video, we will see what happens during the pre-tokenization step. The pre-tokenization operation is the operation performed after the normalization of the text and before the application of the tokenization algorithm. This step consists in applying rules that do not need to be learned to perform a first division of the text. Let's look at how several tokenizers pre_tokenize this example. The gpt 2 pretokenization divides the text on spaces and some punctuation - but the apostrophe is not a division criterion for example. We also notice that spaces have been replaced by a capital G with a dot above. Albert's pre-tokenization divides the text at the level of spaces, adds a space at the beginning of the sentence and replaces spaces with a special underscore. Finally, Bert's pre-tokenization divides the text at the level of punctuation and spaces. Unlike the previous tokenizers, spaces are not transformed and integrated to the tokens produced with this pre-tokenizer. Through these 3 examples, we could observe the two main types of operations brought by the pre-tokenization: some changes on the text and the division of the string into tokens that can be associated to words. Finally, the "backend_tokenizer" of the fast tokenizers also allows to test the pre-tokenization operation very easily thanks to its "pre_tokenize_str" method. We notice that the output of this operation is composed of both tokens and offsets which allow to link the token to its position in the text given in input of the method. This operation defines the largest tokens that can be produced by the tokenization or in other words the barriers of the sub-tokens which will be produced then. \ No newline at end of file diff --git a/subtitles/en/raw/chapter6/06_bpe.md b/subtitles/en/raw/chapter6/06_bpe.md new file mode 100644 index 000000000..3afbe6e98 --- /dev/null +++ b/subtitles/en/raw/chapter6/06_bpe.md @@ -0,0 +1 @@ +You are at the right place if you want to understand what the Byte pair Encoding subword tokenization algorithm is, how to train it and how the tokenization of a text is done with this algorithm. The BPE algorithm was initially proposed as a text compression algorithm but it is also very well suited as a tokenizer for your language models. The idea of BPE is to divide words into a sequence of "subword units" which are units that appear frequently in a reference corpus - that is, the corpus we used to train it. How is a BPE tokenizer trained? First of all, we have to get a corpus of texts. We will not train our tokenizer on this raw text but we will first normalize it then pre-tokenize it. As the pre-tokenization divides the text into a list of words, we can represent our corpus in another way by gathering together the same words and by maintaining a counter, here represented in blue. To understand how the training works, we consider this toy corpus composed of the following words: huggingface, hugging, hug, hugger, etc. BPE is an algorithm that starts with an initial vocabulary and then increases it to the desired size. To build the initial vocabulary, we start by separating each word of the corpus into a list of elementary units that compose them -here the characters. We could also have chosen bytes as elementary units but it would have been less visual. We list in our vocabulary all the characters that appear and that will constitute our initial vocabulary! Let's now see how to increase it. We return to our split corpus, we will go through the words one by one and count all the occurrences of token pairs. The first pair is composed of the token "h" and "u", the second 'u' and "g", and we continue like that until we have the complete list. Once we know all the pairs and their frequency of appearance, we will choose the one that appears the most frequently: here it is the pair composed of the letters 'l' and 'e'. We note our first merging rule and we add the new token to our vocabulary. We can then apply this merging rule to our splits: you can see that we have merged all the pairs of tokens composed of the tokens "l" and "e". And now we just have to reproduce the same steps with our new splits: we calculate the frequency of occurrence of each pair of tokens, we select the pair with the highest frequency, we note it in our merge rules, we add the new one to the vocabulary and then we merge all the pairs of tokens composed of the token "le" and "a" into our splits. And we can repeat this operation until we reach the desired vocabulary size. Here we stopped when our vocabulary reached 21 tokens. We can see now that the words of our corpus are now divided into far fewer tokens than at the beginning of the training. We can see that our algorithm has learned the radicals "hug" and "learn" and also the verbal ending "ing". Now that we have learned our vocabulary and our merging rules, we can tokenize new texts. For example, if we want to tokenize the word hugs: first we'll divide it into elementary units so it became a sequence of characters. Then we'll go through our merge rules until we have one that we can apply. Here we can merge the letters h and u. And here we can merge 2 tokens to get the new token hug. When we get to the end of our merge rule the tokenization is finished. ßAnd that's it, I hope that now the BPE algorithm has no more secret for you! \ No newline at end of file diff --git a/subtitles/en/raw/chapter6/07_wordpiece.md b/subtitles/en/raw/chapter6/07_wordpiece.md new file mode 100644 index 000000000..1def0d8a1 --- /dev/null +++ b/subtitles/en/raw/chapter6/07_wordpiece.md @@ -0,0 +1 @@ +Let's see together what is the training strategy of the WordPiece algorithm and how it performs the tokenization of a text once trained WordPiece is a tokenization algorithm introduced by Google. It is used for example by Bert. To our knowledge, the code of Word Pieces has not been open sourced, so we base our explanations on our own interpretation of the published literature. What is the training strategy of WordPiece? Similarly to the BPE algorithm, WordPiece starts by establishing an initial vocabulary composed of elementary units and then increases this vocabulary to the desired size. To build the initial vocabulary, we divide each word in the training corpus into the sequence of letters that make it up. As you can see, there is a small subtlety: we add a 2 hashtags in front of the letters that do not start a word. By keeping only one occurrence per elementary unit we now have our initial vocabulary. We will list all the existing pairs in our corpus. Once we have this list, we will calculate a score for each of these pairs. As for the BPE algorithm, we will select the pair with the highest score. Taking for example the first pair composed of H and U. The score of a pair is simply equal to the frequency of appearance of the pair divided by the product of the frequency of appearance of the first token by the frequency of appearance of the second token. Thus at a fixed frequency of appearance of the pair, if the subparts of the pair are very frequent in the corpus then this score will be decreased. In our example, the pair "hu" appears 4 times, the letter "h" 4 times and the letter u 4 times. This gives us a score of 0.25. Now that we know how to calculate this score, we can do it for all pairs. We can now add to the vocabulary the pair with the highest score, after merging it of course! And now we can apply this same fusion to our split corpus. As you can imagine, we just have to repeat the same operations until we have the vocabulary at the desired size! Let's look at a few more steps to see the evolution of our vocabulary and the length of the splits getting shorter. Now that we are happy with our vocabulary, you are probably wondering how to use it to tokenize a text. Let's say we want to tokenize the word "huggingface". WordPiece follows these rules: We will look for the longest possible token at the beginning of our word. Then we start again on the remaining part of our word. And so on until we reach the end! And that's it, huggingface is divided into 4 sub-tokens. ßThis video is about to end, I hope it helped you to understand better what is behind the word WordPiece! \ No newline at end of file diff --git a/subtitles/en/raw/chapter6/08_unigram.md b/subtitles/en/raw/chapter6/08_unigram.md new file mode 100644 index 000000000..1c821cf41 --- /dev/null +++ b/subtitles/en/raw/chapter6/08_unigram.md @@ -0,0 +1,5 @@ +In this video, we will study together "the Unigram Language Model subword tokenization algorithm". + +The overall training strategy of a Unigram LM tokenizer is to start with a very large vocabulary and then to remove tokens at each iteration until we reach the desired size. At each iteration, we will calculate a loss on our training corpus thanks to the Unigram model. As the loss calculation depends on the available vocabulary, we can use it to choose how to reduce the vocabulary. So we look at the evolution of the loss by removing in turn each token from the vocabulary. We will choose to remove the p percents which increase the loss the less. + +Before going further in the explanation of the training algorithm, I need to explain what is an Unigram model. The Unigram LM model is a type of statistical Language Modem. A statistical LM will assign a probability to a text considering that the text is in fact a sequence of tokens. The simplest sequences of tokens to imagine are the words that compose the sentence or the characters. The particularity of Unigram LM is that it assumes that the occurrence of each word is independent of its previous word. This "assumption" allows us to write that the probability of a text is equal to the product of the probabilities of the tokens that compose it. It should be noted here that this is a very simple model which would not be adapted to the generation of text since this model would always generate the same token, the one which has the greatest probability. Nevertheless, to do tokenization, this model is very useful to us because it can be used to estimate the relative likelihood of different phrases. We are now ready to return to our explanation of the training algorithm. Let's say that we have as a training corpus 10 times the word hug, 12 times the word pug, 5 times the word lug, 4 times bug and 5 times dug. As said at the beginning of the video, the training starts with a big vocabulary. Obviously, as we are using a toy corpus, this vocabulary will not be that big but it should show you the principle. A first method is to list all the possible strict substrings that's what we'll do here. We could also have used the BPE algorithm with a very large vocabulary size. So we have our initial vocabulary. The training of the Unigram tokenizer is based on the Expectation-Maximization method: at each iteration. We estimate the probabilities of the tokens of the vocabulary. Then we remove the p percent of tokens that minimize the loss on the corpus and which do not belong to the basic characters as we want to keep in our final vocabulary the basic characters to be able to tokenize any word. Let's go for it! The probability of a token is simply estimated by the number of appearance of this token in our training corpus divided by the total number of appearance of all the tokens. We could use this vocabulary to tokenize our words according to the unigram model. We will do it together to understand two things: how we tokenize a word with a Unigram model and how the loss is calculated on our corpus. The Unigram LM tokenization of our text "Hug" will be the one with the highest probability of occurrence according to our Unigram model. To find it, the simplest way to proceed would be to list all the possible segmentations of our text "Hug", calculate the probability of each of these segmentations and then choose the one with the highest probability. With the current vocabulary, 2 tokenizations get exactly the same probability. So we choose one of them and keep in memory the associated probability. To compute the loss on our training corpus, we need to tokenize as we just did all the remaining words in the corpus. The loss is then the sum over all the words in the corpus of the frequency of occurrence of the word multiplied by the opposite of the log of the probability associated with the tokenization of the word. We obtain here a loss of one hundred and seventy. Remember, our initial goal was to reduce the vocabulary. To do this, we will remove a token from the vocabulary and calculate the associated loss. Let's remove for example the token 'ug'. We notice that the tokenization for "hug" with the letter h and the tuple ug is now impossible. Nevertheless, as we saw earlier that two tokenizations had the same probability and we can still choose the remaining tokenization with a probability of one point ten minus two. The tokenizations of the other words of the vocabulary also remain unchanged and finally even if we remove the token "ug" from our vocabulary the loss remains equal to 170. For this first iteration, if we continue the calculation, we would notice that we could remove any token without it impacting the loss. We will therefore choose at random to remove the token "ug" before starting a second iteration. We estimate again the probability of each token before calculating the impact of each token on the loss. For example, if we remove now the token composed of the letters "h" and "u", there is only one possible tokenization left for hug. The tokenization of the other words of the vocabulary is not changed. In the end, we obtain by removing the token composed of the letters "h" and "u" from the vocabulary a loss of one hundred and sixty-eight. Finally, to choose which token to remove, we will for each remaining token of the vocabulary which is not an elementary token calculate the associated loss then compare these losses between them. The token which we will remove is the token which impacts the least the loss: here the token "bu". We had mentioned at the beginning of the video that at each iteration we could remove p % of the tokens by iteration. The second token that could be removed at this iteration is the "du" token. And that's it, we just have to repeat these steps until we get the vocabulary of the desired size. One last thing, in practice, when we tokenize a word with a Unigram model we don't compute the set of probabilities of the possible splits of a word before comparing them to keep the best one but we use the Viterbi algorithm which is much more efficient. And that's it! I hope that this example has allowed you to better understand the Unigram tokenization algorithm. \ No newline at end of file diff --git a/subtitles/en/raw/chapter6/09_building-a-tokenizer.md b/subtitles/en/raw/chapter6/09_building-a-tokenizer.md new file mode 100644 index 000000000..5a3ae13d9 --- /dev/null +++ b/subtitles/en/raw/chapter6/09_building-a-tokenizer.md @@ -0,0 +1 @@ +In this video we will see how you can create your own tokenizer from scratch! To create your own tokenizer you will have to think about each of the operations involved in tokenization, namely: normalization, pre-tokenization, model, post-processing and decoding. If you don't know what normalization, pre-tokenization and the model are, I advise you to go and see the videos linked below. The post processing gathers all the modifications that we will carry out on the tokenized text. It can include the addition of special tokens, the creation of an attention mask but also the generation of a list of token ids. The decoding operation occurs at the very end and will allow passing from the sequence of ids in a sentence. For example, in our example, we can see that the hashtags have been removed and the tokens composing the word "today" have been grouped together. In a fast tokenizer, all these components are gathered in the backend_tokenizer attribute. As you can see with this small code snippet, it is an instance of a tokenizer from the tokenizers library. So, to create your own transformers tokenizer you will have to follow these steps: first create a training dataset; second create and train a tokenizer with the tokenizers library and third load this tokenizer into transformers tokenizer. To understand these steps, I propose that we recreate a BERT tokenizer. The first thing to do is to create a dataset. With this code snippet you can create an iterator on the dataset wikitext-2-raw-v1 which is a rather small dataset in English. We attack here the big part: the design of our tokenizer with the tokenizers library. We start by initializing a tokenizer instance with a WordPiece model because it is the model used by BERT. Then we can define our normalizer. We will define it as a succession of 2 normalizations used to clean up characters not visible in the text, 1 lowercasing normalization and 2 normalizations used to remove accents. For the pre-tokenization, we will chain two pre_tokenizer. The first one separating the text at the level of spaces and the second one isolating the punctuation marks. Now, we can define the trainer that will allow us to train the WordPiece model chosen at the beginning. To carry out the training, we will have to choose a vocabulary size, here we choose twenty-five thousand and also announce the special tokens that we absolutely want to add to our vocabulary. In one line of code, we can train our WordPiece model using the iterator we defined earlier. Once the model has been trained, we can retrieve the ids of the special class and separation tokens because we will need them to post-process our sequence. Thanks to the TemplateProcessing class, we can add the CLS token at the beginning of each sequence and the SEP token at the end of the sequence and between two sentences if we tokenize a text pair. Finally, we just have to define our decoder which will allow us to remove the hashtags at the beginning of the tokens that must be reattached to the previous token. And there it ist, you have all the necessary lines of code to define your own tokenizer. Now that we have a brand new tokenizer with the tokenizers library we just have to load it into a fast tokenizer from the transformers library. Here again we have several possibilities. We can load it in the generic class "PreTrainedTokenizerFast" or in the BertTokenizerFast class since we have built a bert type tokenizer here. I hope this video has helped you understand how you can create your own tokenizer and that you are ready to navigate the tokenizers library documentation to choose the components for your brand-new tokenizer! \ No newline at end of file diff --git a/subtitles/en/raw/chapter7/02_token-classification-processing.md b/subtitles/en/raw/chapter7/02_token-classification-processing.md new file mode 100644 index 000000000..4f41e8de1 --- /dev/null +++ b/subtitles/en/raw/chapter7/02_token-classification-processing.md @@ -0,0 +1 @@ +Let's study how to preprocess a dataset for token classification! Token classification regroups any task that can be framed as labelling each word (or token) in a sentence, like identifying the persons, organizations and locations for instance. For our example, we will use the Conll dataset, in which we remove columns we won't use and rename the other ones to get to a dataset with just two columns: words and labels. If you have your own dataset for token classification, just make sure you clean your data to get to the same point, with one column containing words (as list of strings) and another containing labels (as integers spanning from to to your number of labels -1).() Make sure you have your label names stored somewhere - here we get them from the dataset features - so you are able to map the integers to some real labels when inspecting your data! Here we are doing named entity recognitions, so ours labels are either O for words that do not belong to any entity, LOC, for location, PER, for person, ORG for organization and MISC for miscellaneous. Each label has two versions: the B- labels indicate a word that begins an entity while the I- labels indicate a word that is inside an entity. The first step to preprocess our data is to tokenize the words. This is very easily done with a tokenizer, we just have to tell it we have pre-tokenized the data with the flag is_split_into_words. Then comes the hard part. Since we have added special tokens and each word may have been split into several tokens, our labels won't match the tokens anymore. This is where the word IDs our fast tokenizer provide come to the rescue. They match each token to the word it belongs to which allows us to map each token to its label. We just have to make sure we change the B- labels to their I- counterparts for tokens that are inside (but not at the beginning) of a word. The special tokens get a label of -100, which is how we tell the Transformer loss functions to ignore them when computing the loss. The code is then pretty straightforward, we write a function that shifts the labels for tokens that are inside a word (that you can customize) and use it when generating the labels for each token. Once that function to create our labels is written, we can preprocess the whole dataset using the map function. With the option batched=True, we unleash the speed of out fast tokenizers. The last problem comes when we need to create a batch. Unless you changed the preprocessing function to apply some fixed padding, we will get sentences of various lengths, which we need to pad to the same length. The padding needs to be applied to the inputs as well as the labels, since we should have one label per token. Again, -100 indicates the labels that should be ignored for the loss computation. This is all done for us by the DataCollatorForTokenClassification, which you can use in PyTorch or TensorFlow. With all of this, you are either ready to send your data and this data collator to the Trainer, or to use the to_tf_dataset method and use the fit method of your model. \ No newline at end of file diff --git a/subtitles/en/raw/chapter7/03a_mlm-processing.md b/subtitles/en/raw/chapter7/03a_mlm-processing.md new file mode 100644 index 000000000..76ff27f53 --- /dev/null +++ b/subtitles/en/raw/chapter7/03a_mlm-processing.md @@ -0,0 +1 @@ +Let's see how we can preprocess our data for masked language modeling. As a reminder, masked language modeling is when a model needs to fill the blanks in a sentence. To do this, you just need texts, no labels, as this is a self-supervised problem. To apply this on your own data, just make sure you have all your texts gathered in one column of your dataset. Before we start randomly masking things, we will need to somehow make all those texts the same length to batch them together. The first way to make all the texts the same length is the one we used in text classification. let's pad the short texts and truncate the long ones. As we have seen when we processed data for text classification, this is all done by our tokenizer with the right options for padding and truncation. This will however make us lose a lot of texts if the examples in our dataset are very long, compared to the context length we picked. Here, all the portion in gray is lost. This is why a second way to generate samples of text with the same length is to chunk our text in pieces of context lengths, instead of discarding everything after the first chunk. There will probably be a remainder of length smaller than the context size, which we can choose to keep and pad or ignore. Here is how we can apply this in practice, by just adding the return overflowing tokens option in our tokenizer call. Note how this gives us a bigger dataset! This second way of chunking is ideal if all your texts are very long, but it won't work as nicely if you have a variety of lengths in the texts. In this case, the best option is to concatenate all your tokenized texts in one big stream, with a special tokens to indicate when you pass from one document to the other, and only then split the big stream into chunks. Here is how it can be done with code, with one loop to concatenate all the texts and another one to chunk it. Notice how it reduces the number of samples in our dataset here, there must have been quite a few short entries! Once this is done, the masking is the easy part. There is a data collator designed specifically for this in the Transformers library. You can use it directly in the Trainer, or when converting your datasets to tensorflow datasets before doing Keras.fit, with the to_tf_dataset method. \ No newline at end of file diff --git a/subtitles/en/raw/chapter7/03b_perplexity.md b/subtitles/en/raw/chapter7/03b_perplexity.md new file mode 100644 index 000000000..a610c64aa --- /dev/null +++ b/subtitles/en/raw/chapter7/03b_perplexity.md @@ -0,0 +1 @@ +In this video we take a look at the mysterious sounding metric called Perplexity. You might have encountered perplexity when reading about generative models. You can see two examples here from the original transformer paper “Attention is all you need” as well as the more recent GPT-2 paper. Perplexity is a common metric to measure the performance of language models. The smaller the value the better the performance. But what does it actually mean and how can we calculate it? A very common quantity in machine learning is the likelihood. We can calculate the likelihood as the product of each token’s probability What this means is that for each token we use the language model to predict its probability based on the previous tokens. In the end we multiply all probabilities to get the Likelihood. With the likelihood we can calculate another important quantity: the cross entropy. You might already have heard about cross-entropy when looking at loss function. Cross-entropy is often used as a loss function in classification. In language modeling we predict the next token which also is a classification task. Therefore, if we want to calculate the cross entropy of an example we can simply pass it to the model with the inputs as labels. The loss return by the model then corresponds the cross entropy. We are now only a single operation away from calculating the perplexity. By exponentiating the cross-entropy we get the perplexity. So you see that the perplexity is closely related to the loss. Keep in mind that the loss is only a weak proxy for a model’s ability to generate quality text and the same is true for perplexity. For this reason one usually also calculates more sophisticated metrics such as BLEU or ROUGE on generative tasks. \ No newline at end of file diff --git a/subtitles/en/raw/chapter7/03c_domain-adaptation.md b/subtitles/en/raw/chapter7/03c_domain-adaptation.md new file mode 100644 index 000000000..d5bed95cd --- /dev/null +++ b/subtitles/en/raw/chapter7/03c_domain-adaptation.md @@ -0,0 +1 @@ +What is domain adaptation? When fine-tuning a pretrained model on a new dataset, the fine-tuned model we obtain will make predictions that are attuned to this new dataset. When the two models are trained with the same task, we can then compare their predictions on the same input. The predictions of the two models will be different, in a way that reflects the differences between the two datasets, a phenomenon we call domain adaptation. Let's look at an example with mask language modeling, by comparing the outputs of the pretrained distilBERT model with the version fine-tuned in chapter 7 of the course (linked below). The pretrained model makes generic predictions, whereas the fine-tuned model has its first two predictions linked to cinema. Since it was fine-tuned on a movie reviews dataset, it's perfectly normal to see it adapted its suggestions like this. Notice how it keeps the same predictions as the pretrained model afterward. Even if the fine-tuned model adapts to the new dataset, it's not forgetting what it was pretrained on. This is another example on a translation task. On top we use a pretrained French/English model and at the bottom, the version we fine-tuned in chapter 7. The top model is pretrained on lots of texts, and leaves technical English terms like plugin and email unchanged in the translation (both are perfectly understood by French people). The dataset picked for the fine-tuning is a dataset of technical texts where special attention was picked to translate everything in French. As a result, the fine-tuned model picked that habit and translated both plugin and email. \ No newline at end of file diff --git a/subtitles/en/raw/chapter7/04a_translation-processing.md b/subtitles/en/raw/chapter7/04a_translation-processing.md new file mode 100644 index 000000000..be2a537b5 --- /dev/null +++ b/subtitles/en/raw/chapter7/04a_translation-processing.md @@ -0,0 +1 @@ +Let's see how to preprocess a dataset for translation. This is the task of well translating a sentence in another language. This video will focus on how to preprocess your dataset once you have managed to put it in the following format: one column for the input texts, and one for the target texts. Here is how we can achieve this with the Datasets library on the KDE4 dataset for English to French translation. As long as you manage to have your data look like this, you should be able to follow the same steps. For once, our labels are not integers corresponding to some classes, but plain text. We will thus need to tokenize them, like our inputs. There is a trap there though, as if you tokenize your targets like your inputs, you will hit a problem. Even if you don't speak French, you might notice some weird things in the tokenization of the targets: most of the words are tokenized in several subtokens, while "fish", one of the only English word, is tokenized as a single word. That's because our inputs have been tokenized as English. Since our model knows two languages, you have to warn it when tokenizing the targets, so it switches in French mode. This is done with the as_target_tokenizer context manager. You can see how it results in a more compact tokenization. Processing the whole dataset is then super easy with the map function. You can pick different maximum lengths for the input and targets, and choose to pad at this stage to that maximum length by setting padding=max_length. Here we will show you how to pad dynamically as it requires one more step. Your inputs and targets are all sentence of various lengths. We will pad the inputs and targets separately as the maximum length of the inputs and targets might be different. Then we pad the inputs with the pad token and the targets with the -100 index, to make sure they are not taken into account in the loss computation. Once this is done, batching inputs and targets become super easy! The Transformers library provides us with a data collator to do this all automatically. You can then pass it to the Trainer with your datasets, or use it in the to_tf_dataset method before using model.fit(). \ No newline at end of file diff --git a/subtitles/en/raw/chapter7/04b_bleu.md b/subtitles/en/raw/chapter7/04b_bleu.md new file mode 100644 index 000000000..e62273430 --- /dev/null +++ b/subtitles/en/raw/chapter7/04b_bleu.md @@ -0,0 +1 @@ +What is the BLEU metric? For many NLP tasks we can use common metrics like accuracy or F1 score, but what do you do when you want to measure the quality of text that's generated from a model like GPT-2? In this video, we'll take a look at a widely used metric for machine translation called BLEU, which is short for BiLingual Evaluation Understudy. The basic idea behind BLEU is to assign a single numerical score to a translation that tells us how "good" it is compared to one or more reference translations. In this example we have a sentence in Spanish that has been translated into English by some model. If we compare the generated translation to some reference human translations, we can see that the model is pretty good, but has made a common error: the Spanish word "tengo" means "have" in English and this 1-1 translation is not quite natural. So how can we measure the quality of a generated translation in an automatic way? The approach that BLEU takes is to compare the n-grams of the generated translation to the n-grams of the references. An n-gram is just a fancy way of saying "a chunk of n words", so let's start with unigrams, which correspond to the individual words in a sentence. In this example you can see that four of the words in the generated translation are also found in one of the reference translations. Now that we've found our matches, one way to assign a score to the translation is to compute the precision of the unigrams. This means we just count the number of matching words in the generated and reference translations and normalize the count by dividing by the number of word in the generation. In this example, we found 4 matching words and our generation has 5 words, so our unigram precision is 4/5 or 0.8. In general precision ranges from 0 to 1, and higher precision scores mean a better translation. One problem with unigram precision is that translation models sometimes get stuck in repetitive patterns and repeat the same word several times. If we just count the number of word matches, we can get really high precision scores even though the translation is terrible from a human perspective! For example, if our model just generates the word "six", we get a perfect unigram precision score. To handle this, BLEU uses a modified precision that clips the number of times to count a word, based on the maximum number of times it appears in the reference translation. In this example, the word "six" only appears once in the reference, so we clip the numerator to one and the modified unigram precision now gives a much lower score. Another problem with unigram precision is that it doesn't take into account the order of the words in the translations. For example, suppose we had Yoda translate our Spanish sentence, then we might get something backwards like "years six thirty have I". In this case, the modified unigram precision gives a high precision which is not what we want. So to deal with word ordering problems, BLEU actually computes the precision for several different n-grams and then averages the result. For example, if we compare 4-grams, then we can see there are no matching chunks of 4 words in translations and so the 4-gram precision is 0. To compute BLEU scores in Hugging Face Datasets is very simple: just use the load_metric() function, provide your model's predictions along with the references and you're good to go! The output contains several fields of interest. The precisions field contains all the individual precision scores for each n-gram. The BLEU score itself is then calculated by taking the geometric mean of the precision scores. By default, the mean of all four n-gram precisions is reported, a metric that is sometimes also called BLEU-4. In this example we can see the BLEU score is zero because the 4-gram precision was zero. The BLEU metric has some nice properties, but it is far from a perfect metric. The good properties are that it's easy to compute and widely used in research so you can compare your model against others on a benchmark. On the other hand, there are several problems with BLEU, including the fact it doesn't incorporate semantics and struggles on non-English languages. Another problem with BLEU is that it assumes the human translations have already been tokenized and this makes it hard to compare models with different tokenizers. Measuring the quality of texts is still a difficult, open problem in NLP research. For machine translation, the current recommendation is to use the SacreBLEU metric which addresses the tokenization limitations of BLEU. As you can see in this example, computing the SacreBLEU score is almost identical to the BLEU one. The main difference is that we now pass a list of texts instead of a list of words for the translations, and SacreBLEU takes care of the tokenization under the hood. \ No newline at end of file diff --git a/subtitles/en/raw/chapter7/05a_summarization-processing.md b/subtitles/en/raw/chapter7/05a_summarization-processing.md new file mode 100644 index 000000000..1012dfa70 --- /dev/null +++ b/subtitles/en/raw/chapter7/05a_summarization-processing.md @@ -0,0 +1 @@ +Let's see how to preprocess a dataset for summarization. This is the task of well summarizing a long document. This video will focus on how to preprocess your dataset once you have managed to put it in the following format: one column for the long documents, and one for the summaries. Here is how we can achieve this with the Datasets library on the XSUM dataset. As long as you manage to have your data look like this, you should be able to follow the same steps. For once, our labels are not integers corresponding to some classes, but plain text. We will thus need to tokenize them, like our inputs. There is a small trap there though, as we need to tokenize our targets inside the as_target_tokenzier context manager. This is because the special tokens we add might be slightly different for the inputs and the targets, so the tokenizer has to know which one it is processing. Processing the whole dataset is then super easy with the map function. Since the summaries are usually much shorter than the documents, you should definitely pick different maximum lengths for the inputs and targets. You can choose to pad at this stage to that maximum length by setting padding=max_length. Here we will show you how to pad dynamically as it requires one more step. Your inputs and targets are all sentence of various lengths. We will pad the inputs and targets separately as the maximum length of the inputs and targets are completely different. Then we pad the inputs to the maximum lengths among the inputs, and same for the targets. We pad the inputs with the pad token and the targets with the -100 index, to make sure they are not taken into account in the loss computation. The Transformers library provides us with a data collator to do this all automatically. You can then pass it to the Trainer with your datasets, or use it in the to_tf_dataset method before using model.fit(). \ No newline at end of file diff --git a/subtitles/en/raw/chapter7/05b_rouge.md b/subtitles/en/raw/chapter7/05b_rouge.md new file mode 100644 index 000000000..e03cffc1f --- /dev/null +++ b/subtitles/en/raw/chapter7/05b_rouge.md @@ -0,0 +1 @@ +What is the ROUGE metric? For many NLP tasks we can use common metrics like accuracy or F1 score, but what do you do when you want to measure the quality of a summary from a model like T5? In this video, we'll take a look at a widely used metric for text summarization called ROUGE, which is short for Recall-Oriented Understudy for Gisting Evaluation. There are actually several variants of ROUGE, but the basic idea behind all of them is to assign a single numerical score to a summary that tells us how "good" it is compared to one or more reference summaries. In this example we have a book review that has been summarized by some model. If we compare the generated summary to some reference human summaries, we can see that the model is pretty good, and only differs by a word or two. So how can we measure the quality of a generated summary in an automatic way? The approach that ROUGE takes is to compare the n-grams of the generated summary to the n-grams of the references. An n-gram is just a fancy way of saying "a chunk of n words", so let's start with unigrams, which correspond to the individual words in a sentence. In this example you can see that six of the words in the generated summary are also found in one of the reference summaries. The ROUGE metric that compares unigrams is called ROUGE-1. Now that we've found our matches, one way to assign a score to the summary is to compute the recall of the unigrams. This means we just count the number of matching words in the generated and reference summaries and normalize the count by dividing by the number of word in the reference. In this example, we found 6 matching words and our reference has 6 words, so our unigram recall is perfect! This means that all of words in the reference summary have produced in the generated one. Perfect recall sounds great, but imagine if our generated summary had been “I really really really really loved reading the Hunger Games”. This would also have perfect recall, but is arguably a worse summary since it is verbose. To deal with these scenarios we can also compute precision, which in the ROUGE context measures how much of the generated summary was relevant. In this example, the precision is 6/7. In practice, both precision and recall are usually computed and then the F1-score is reported. We can change the granularity of the comparison by comparing bigrams instead of unigrams. With bigrams we chunk the sentence into pairs of consecutive words and then count how many pairs in the generated summary are present in the reference one. This gives us ROUGE-2 precision and recall, which we can see is lower than the ROUGE-1 scores we saw earlier. Note that if the summaries are long, the ROUGE-2 score will be small as there are typically fewer bigrams to match. This is also true for abstractive summarization, so both ROUGE-1 and ROUGE-2 scores are usually reported. The last ROUGE variant we'll discuss is ROUGE-L. ROUGE-L doesn't compare n-grams, but instead treats each summary as a sequence of words and then looks for the longest common subsequence or LCS. A subsequence is a sequence that appears in the same relative order, but not necessarily contiguous. So in this example, "I loved reading the Hunger Games" is the longest common subsequence. The main advantage of ROUGE-L over ROUGE-1 or ROUGE-2 is that is doesn't depend on consecutive n-gram matches, so it tends to capture sentence structure more accurately. To compute ROUGE scores in Hugging Face Datasets is very simple: just use the load_metric() function, provide your model's summaries along with the references and you're good to go! The output from the calculation contains a lot of information! The first thing we can see here is that the confidence intervals of each ROUGE score are provided in the low, mid, and high fields. This is really useful if you want to know the spread of your ROUGE scores when comparing two or more models. The second thing to notice is that we have four types of ROUGE score. We've already seen ROUGE-1, ROUGE-2 and ROUGE-L, so what is ROUGE-LSUM? Well, the “sum” in ROUGE-LSUM refers to the fact that this metric is computed over a whole summary, while ROUGE-L is computed as the average over individual sentences. \ No newline at end of file diff --git a/subtitles/en/raw/chapter7/06a_clm-processing.md b/subtitles/en/raw/chapter7/06a_clm-processing.md new file mode 100644 index 000000000..d1722b71f --- /dev/null +++ b/subtitles/en/raw/chapter7/06a_clm-processing.md @@ -0,0 +1 @@ +In this video we take a look at the data processing necessary to train causal language models. Causal Language Modeling is the task of predicting the next token based on the previous token. Another term for Causal Language Modeling is Autoregressive Modeling. In the example that you see here the next token could for example be NLP or machine learning. A popular example of a Causal Language Model is the GPT family of models. To train such models such as GPT-2 we usually start with a large corpus of text files. These files can webpages scraped from the internet such as the Common Crawl dataset or they can be Python files from GitHub like you can see here. As a first step we need to tokenize these files such that we can feed them through a model. Here we show the tokenized texts as bars of various length illustrating the different sequence lengths. Normally, the text files come in various sizes and which results in various sequence length of the tokenized texts. Transformer models have a limited context length and depending on the data source it is possible that the tokenized texts are much longer than this context length. In this case we could just truncate the sequence to the context length but this would mean that we loose everything after the context length. Using the return overflowing tokens flag in the we can use the tokenizer to create chunks with each one being the size of the context length. Sometimes it can happen that the last chunk is too short if there aren’t enough tokens to fill it. In this case we would like to remove it. With the return_length keyword we also get the length of each chunk from the tokenizer. This function shows all the steps necessary to prepare the dataset. First we tokenize the dataset with the flags I just mentioned. Then we go through each chunk and if its length matches the context length we add it to the inputs we return. We can apply this function to the whole dataset and we make sure to use batches and remove the existing columns. We need to remove columns because we can create multiple samples per text and the shapes in the dataset would not match. If the context length is of similar length as the files this approach doesn't so well anymore. In this example both sample 1 and 2 are shorter than the context size and would be discarded with the previous approach. In this case it is better to first tokenize each sample without truncation and then concatenate the tokenized samples with an end of string, or EOS for short, token in between. Finally we can chunk this long sequence with the context length and we don’t loose any sequences because they are too short. So far we have only talked about the inputs for causal language modeling but not the labels needed for supervised training. When we do causal language modeling we don’t require any extra labels for the input sequences as the input sequences themselves are the labels. In this example when we feed the token “Trans” to the next token we want the model to predict is “formers”. In the next step we feed “Trans” and “formers” to the model and the label is the token “are”. This pattern continues and as you can see the input sequence is the label just shifted by one. Since the model only makes a prediction after the first token, the first element of the input sequence, in this case “Trans”, is not used as a label. Similarly, we do not have a label for the last token in the sequence since there is no token after the sequence ends. Let’s have a look at what we need to do to create the labels for causal language modeling in code.If we want to calculate the loss on a batch we can just pass the input_ids as labels and all the shifting is handled in the model internally. And the dataset is also ready to be used directly in the Trainer or keras.fit if you are using TensorFlow. So you see there is no magic involved in processing data for causal language modeling and only requires a few simple steps! \ No newline at end of file diff --git a/subtitles/en/raw/chapter7/06b_custom-loss.md b/subtitles/en/raw/chapter7/06b_custom-loss.md new file mode 100644 index 000000000..f3ec57f38 --- /dev/null +++ b/subtitles/en/raw/chapter7/06b_custom-loss.md @@ -0,0 +1 @@ +In this video we take a look at setting up a custom loss function for training. In the default loss functions all samples such as these code snippets are treated the same irrespective of their content, but there are scenarios where you it could make sense to weight the samples differently. If for example one sample contains a lot of tokens that or of interest to us or it has a favourable diversity of tokens. We can also think of other heuristics we can implement with pattern matching or other rules. For each sample we get a loss value during training and we can combine that loss with a weight. Then we can for example create a weighted sum to get the final loss for a batch. Let’s have a look at a specific example: we want to setup a language model that helps us autocomplete complete common data science code. For that task we would like to weight samples stronger where tokens related to the data science stack, such as pd or np, occur more frequently. Here you see a loss function that does exactly that for causal language modeling. It takes the models it takes the model’s inputs and predicted logits as well as the key tokens as input. First the inputs and logits are aligned, then the loss per sample is calculate followed by the weights. Finally the loss and weights are combined and returned. This is a pretty big function so let’s take a closer look at the loss and weight blocks. During the calculation of the standard loss the logits and labels are flattened over the batch. With the view we unflatten the tensor to get a matrix with a row for each sample in the batch and a column for each position in the sequence of the samples. We don’t need the loss per position so we average the loss over all positions for each sample. For the weights we use boolean logic to get a tensor with 1s where a keyword occurred and 0s where not. This tensor has an additional dimension as the loss tensor we just saw because we get the information for each keyword in a separate matrix. Only want to know how many times keywords occurred per sample so we can sum over all keywords and all positions per sample. Now we are almost there, we only need to combine the loss with the weight per sample. We do this with element wise multiplication and then average over all samples in the batch. In the end we have exactly one loss value for the whole batch. And this is the whole necessary logic to create a custom weighted loss. Let’s see how we can make use of that custom loss with Accelerate and the Trainer In Accelerate we just pass the input_ids to the models to get the logits and can then call the custom loss function. After that we continue with the normal training loop by for example calling backward. For the Trainer we can overwrite the compute loss function of the standard trainer. We just need to make sure that that we return the loss and the model outputs in the same format. With that you can integrate your own awesome loss function with both the trainer and accelerates. \ No newline at end of file diff --git a/subtitles/en/raw/chapter7/07a_question-answering-processing.md b/subtitles/en/raw/chapter7/07a_question-answering-processing.md new file mode 100644 index 000000000..8bd735b9c --- /dev/null +++ b/subtitles/en/raw/chapter7/07a_question-answering-processing.md @@ -0,0 +1 @@ +Let's study how to preprocess a dataset for question answering! Question answering is the task of finding answers to a question in some context. For our example, we will use the squad dataset, in which we remove columns we won't use and just extract the information we will need for the labels: the start and the end of the answer in the context. If you have your own dataset for question answering, just make sure you clean your data to get to the same point, with one column containing the questions, one column containing the contexts, one column for the index of the start and end character of the answer in the context. Note that the answer must be part of the context. If you want to perform generative question answering, look at one of the sequence to sequence videos linked below. Now if we have a look at the tokens we will feed our model we will see the answer lies somewhere inside the context. For very long context that answer may get truncated by the tokenizer. In this case, we wont have any proper labels for our model. So we should keep the truncated part as a separate feature instead of discarding it. The only thing we need to be careful with, is to allow some overlap between separate chunks so that the answer is not truncated, and that the feature containing the answer gets sufficient context to be able to predict it. Here is how it can be done by the tokenizer: we pass it the question, context, set the truncation for the context only and the padding to the maximum length. The stride argument is where we set the number of overlapping tokens, and the return_overflowing_tokens means we don't want to discard the truncated part. Lastly, we also return the offset mappings to be able to find the tokens corresponding to the answer start and end. We want those two tokens, because there will be the labels we pass to our model. In a one-hot encoded version, here is what they look like. If the context we have does not contain the answer, we set the two labels to the index of the CLS token. We also do this if the context only partially contains the answer. In terms of code, here is how we can do it: using the sequence IDs of an input, we can determine the beginning and the end of the context. Then we know if have to return the CLS position for the two labels or we determine the positions of the first and last tokens of the answer. We can check it works properly on our previous example. Putting it all together looks like this big function, which we can apply to our datasets. Since we applied padding during the tokenization, we can then use this directly in the Trainer or apply the to_tf_dataset method to use Keras.fit. \ No newline at end of file diff --git a/subtitles/en/raw/chapter7/07b_question-answering-post-processing-pt.md b/subtitles/en/raw/chapter7/07b_question-answering-post-processing-pt.md new file mode 100644 index 000000000..a542c25c8 --- /dev/null +++ b/subtitles/en/raw/chapter7/07b_question-answering-post-processing-pt.md @@ -0,0 +1 @@ +The post-processing step in a question answering task. When doing question answering, the processing of the initial dataset implies splitting examples in several features, which may or may not contain the answer. Passing those features through the model will give us logits for the start and end positions, since our labels are the indices of the tokens that correspond to the start and end the answer. We must then somehow convert those logits into an answer, and then pick one of the various answers each feature gives to be THE answer for a given example. For the processing step, you should refer to the video linked below. It's not very different for validation, we just need to add a few lines to keep track of two things: instead of discarding the offset mapping, we keep them, and also include in them the information of where the context is by setting the offsets of the special tokens and the question to None. Then we also keep track of the example ID for each feature, to be able to map back feature to the examples that they originated from. If you don't want to compute the validation loss, you won't need to include all the special code that we used to create the labels. With this done, we can apply that preprocessing function using the map method. We take the SQUAD dataset like in the preprocessing for question-answering video. Once this is done, the next step is to create our model. We use the default model behind the question-answering pipeline here, but you should use any model you want to evaluate. We will run a manual evaluation loop, so we create a PyTorch DataLoader with our features. With it, we can compute and gather all the start and end logits like this, with a standard PyTorch evaluation loop. With this done, we can really dive into the post-processing. We will need a map from examples to features, which we can create like this. Now, for the main part of the post-processing, let's see how to extract an answer from the logits. We could just take the best index for the start and end logits and be done, but if our model predicts something impossible, like tokens in the question, we will look at more of the logits. Note that in the question-answering pipeline, we attributed score to each answer based on the probabilities, which we did not compute here. In terms of logits, the multiplication we had in the scores becomes an addition. To go fast, we don't look at all possible start and end logits, but the twenty best ones. We ignore the logits that spawn impossible answers or answer that are too long. As we saw in the preprocessing, the labels (0, 0) correspond to no answer, otherwise we use the offsets to get the answer inside the context. Let's have a look at the predicted answer for the first feature, which is the answer with the best score (or the best logit score since the SoftMax is an increasing function). The model got it right! Next we just have to loop this for every example, picking for each the answer with the best logit score in all the features the example generated. Now you know how to get answers from your model predictions! \ No newline at end of file diff --git a/subtitles/en/raw/chapter7/07b_question-answering-post-processing-tf.md b/subtitles/en/raw/chapter7/07b_question-answering-post-processing-tf.md new file mode 100644 index 000000000..221435463 --- /dev/null +++ b/subtitles/en/raw/chapter7/07b_question-answering-post-processing-tf.md @@ -0,0 +1 @@ +The post-processing step in a question answering task. When doing question answering, the processing of the initial dataset implies splitting examples in several features, which may or may not contain the answer. Passing those features through the model will give us logits for the start and end positions, since our labels are the indices of the tokens that correspond to the start and end the answer. We must then somehow convert those logits into an answer, and then pick one of the various answers each feature gives to be THE answer for a given example. For the processing step, you should refer to the video linked below. It's not very different for validation, we just need to add a few lines to keep track of two things: instead of discarding the offset mapping, we keep them, and also include in them the information of where the context is by setting the offsets of the special tokens and the question to None. Then we also keep track of the example ID for each feature, to be able to map back feature to the examples that they originated from. If you don't want to compute the validation loss, you won't need to include all the special code that we used to create the labels. With this done, we can apply that preprocessing function using the map method. We take the SQUAD dataset like in the preprocessing for question-answering video. Once this is done, the next step is to create our model. We use the default model behind the question-answering pipeline here, but you should use any model you want to evaluate. With the to_tf_dataset method, we can just sent our processed dataset to model.predict, and we directly get our start and end logits for the whole dataset as NumPy arrays. With this done, we can really dive into the post-processing. We will need a map from examples to features, which we can create like this. Now, for the main part of the post-processing, let's see how to extract an answer from the logits. We could just take the best index for the start and end logits and be done, but if our model predicts something impossible, like tokens in the question, we will look at more of the logits. Note that in the question-answering pipeline, we attributed score to each answer based on the probabilities, which we did not compute here. In terms of logits, the multiplication we had in the scores becomes an addition. To go fast, we don't look at all possible start and end logits, but the twenty best ones. We ignore the logits that spawn impossible answers or answer that are too long. As we saw in the preprocessing, the labels (0, 0) correspond to no answer, otherwise we use the offsets to get the answer inside the context. Let's have a look at the predicted answer for the first feature, which is the answer with the best score (or the best logit score since the SoftMax is an increasing function). The model got it right! Next we just have to loop this for every example, picking for each the answer with the best logit score in all the features the example generated. Now you know how to get answers from your model predictions! \ No newline at end of file diff --git a/subtitles/en/raw/chapter7/08_data-collators.md b/subtitles/en/raw/chapter7/08_data-collators.md new file mode 100644 index 000000000..45ceec1c6 --- /dev/null +++ b/subtitles/en/raw/chapter7/08_data-collators.md @@ -0,0 +1 @@ +In a lot of our examples, you're going to see DataCollators popping up over and over. They're used in both PyTorch and TensorFlow workflows, and maybe even in JAX, but no-one really knows what's happening in JAX. We have a research team working on that, so maybe they'll tell us soon. But what are data collators? Data collators collate data. More specifically, they put together a list of samples into a single training minibatch. For some tasks, the data collator can be very straightforward. For example, when you're doing sequence classification, all you really need from your data collator is that it pads your samples to the same length and concatenates them into a single Tensor. But for other workflows, data collators can be more complex, as they handle some of the preprocessing needed for that particular task. For PyTorch users, you usually pass the DataCollator to your Trainer object. In TensorFlow, the easiest way to use a DataCollator is to pass it to the to_tf_dataset method of your dataset. You'll see these approaches used in the examples and notebooks throughout this course. In both cases, you end up with an iterable that's going to output collated batches, ready for training. Note that all of our collators take a return_tensors argument - you can set this to "pt" to get PyTorch Tensors, "tf" to get TensorFlow Tensors, or "np" to get Numpy arrays. For backward compatibility reasons, the default value is "pt", so PyTorch users don't even have to set this argument most of the time, and so are often totally unaware that this option exists. This is a valuable lesson about how the beneficiaries of privilege are often the most blind to its existence. So now let's see some specific DataCollators in action, though remember that if none of them do what you need, you can always write your own! First, we'll see the "basic" data collators. These are DefaultDataCollator and DataCollatorWithPadding. These are the ones you should use if your labels are straightforward and your data doesn't need any special processing before being ready for training. Most sequence classification tasks, for example, would use one of these data collators. Remember that because different models have different padding tokens, DataCollatorWithPadding will need your model's Tokenizer so it knows how to pad sequences properly! So how do you choose one of these? Simple: As you can see here, if you have variable sequence lengths then you should use DataCollatorWithPadding, which will pad all your sequences to the same length. If you're sure all your sequences are the same length then you can use the even simpler DefaultDataCollator, but it'll give you an error if that assumption is wrong! Moving on, though, many of the other data collators are often designed to handle one specific task, and that's the case with DataCollatorForTokenClassification and DataCollatorForSeqToSeq. These tasks need special collators because the labels are variable in length. In token classification there's one label for each token, and that means the length of the labels can be variable, while in SeqToSeq the labels are also a sequence of tokens that can have variable length. In both of these cases, we handle that by padding the labels too, as you can see here. Inputs and the labels will need to be padded if we want to join samples of variable length into the same minibatch, and that's exactly what the data collators will do. The final data collator I want to show you is the DataCollatorForLanguageModeling. It's very important, firstly because language models are so foundational to everything we do in NLP, and secondly because it has two modes that do two very different things. You choose which mode you want with the mlm argument - set it to True for masked language modeling, and False for causal language modeling. Collating data for causal language modeling is actually quite straightforward - the model is just making predictions for what token comes next, so your labels are more or less just a copy of your inputs, and the collator handles that and ensures your inputs and labels are padded correctly. When you set mlm to True, though, you get quite different behaviour! That's because masked language modeling requires the labels to be, well... masked. So what does that look like? Recall that in masked language modeling, the model is not predicting "the next word"; instead we randomly mask out multiple tokens and the model makes predictions for all of them at once. The process of random masking is surprisingly complex, though - that's because if we follow the protocol from the original BERT paper, we need to replace some tokens with a masking token, other tokens with a random token and then keep a third set of tokens unchanged. This isn't the lecture to go into *why* we do that - you should check out the original BERT paper if you're curious. The main thing to know here is that it can be a real pain to implement yourself, but DataCollatorForLanguageModeling will do it for you. And that's it! That covers the most commonly used data collators and the tasks they're used for. \ No newline at end of file diff --git a/subtitles/en/raw/chapter8/02a_error.md b/subtitles/en/raw/chapter8/02a_error.md new file mode 100644 index 000000000..7ff6375cb --- /dev/null +++ b/subtitles/en/raw/chapter8/02a_error.md @@ -0,0 +1 @@ +In this video, we will learn the first things to do when you get an error. Let's say we want to use the question answering pipeline on a particular model and we get the following error. Errors in Python can appear overwhelming because you get so much information printed out, but that's because Python is trying to help you the best it can to solve your problem. In this video we will see how to interpret the error report we get. The first thing to notice at the very top is that Python shows you with a clear arrow the line of code that triggered the error. So you don't have to fiddle with your code and remove random lines to figure out where the error comes from, you have the answer in front right here. The arrows you see below are the parts of the code Python tried to execute while running the instruction: here we are inside the pipeline function and the error came on this line while trying to execute the function check_tasks, which then raised the KeyError we see displayed. Note that Python tells you exactly where the functions it's executing live, so if you feel adventurous, you can even go inspect the source code. This whole thing is called the traceback. If you are running your code on Colab, the Traceback is automatically minimized, so you have to click to expand it. At the very end of the traceback, you finally get the actual error message. The first thing you should do when encountering an error is to read that error message. Here it's telling us it doesn't know the question answering task, and helpfully gives us the list of supported tasks... in which we can see that question answering is. Looking more closely though, we used an underscore to separate the two words when the task is written with a minus, so we should fix that! Now let's retry our code with the task properly written and what is happening today? Another error! As we saw before, we go look at the bottom to read the actual error message. It's telling us that we should check our model is a correct model identifier, so let's hop on to hf.co/models. We can see our model listed there in the ones available for question answering. The difference is that it's spelled distilbert with one l, and we used two. So let's fix that. We finally get our results! If your error is more complex, you might need to use the Python debugger, check out the videos linked below to learn how! \ No newline at end of file diff --git a/subtitles/en/raw/chapter8/02b_debug-notebook.md b/subtitles/en/raw/chapter8/02b_debug-notebook.md new file mode 100644 index 000000000..3f0422b07 --- /dev/null +++ b/subtitles/en/raw/chapter8/02b_debug-notebook.md @@ -0,0 +1 @@ +Using the Python debugger in a notebook. In this video, we'll learn how to use the Python debugger in a Jupyter Notebook or a Colab. For this example, we are running code from the token classification section, downloading the Conll dataset , having a look at it before loading a tokenizer to preprocess it. Checkout the section of the course linked below for more information. Once this is done, we try to batch together some features of the training dataset by padding them and returning a tensor, then we get the following error. We use PyTorch here but you will get the same error with TensorFlow. As we have seen in the "How to debug an error?" video, the error message is at the end and it indicates we should use padding, which we are actually trying to do. So this is not useful and we will need to go a little deeper to debug the problem. Fortunately, you can use the Python debugger at any time you get an error in a Jupyter Notebook by typing %debug in any cell. When executing that cell, you go to the very bottom of the traceback where you can type commands and you can type commands. The first two commands you should learn are u and d (for up and down), which allow you to go up in the Traceback or down. Going up twice, we get to the point the error was reached. The third command to learn is p, for print. It allows you to print any value you want. For instance here, we can see the value of return_tensors or batch_outputs to try to understand what triggered the error. The batch outputs dictionary is a bit hard to see, so let's dive into smaller pieces of it. Inside the debugger you can not only print any variable but also evaluate any expression, so we can look independently at the inputs or labels. Those labels are definitely weird: they are of various size, which we can actually confirm by printing the sizes. No wonder the tokenizer wasn't able to create a tensor with them! This is because the pad method only takes care of the tokenizer outptus: input IDs, attention mask and token type IDs, so we have to pad the labels ourselves before trying to create a tensor with them. Once you are ready to exit the Python debugger, you can press q for quit. One way to fix the error is to manually pad all labels to the longest, or we can use the data collator designed for this. \ No newline at end of file diff --git a/subtitles/en/raw/chapter8/02c_debug-terminal.md b/subtitles/en/raw/chapter8/02c_debug-terminal.md new file mode 100644 index 000000000..fe890dff4 --- /dev/null +++ b/subtitles/en/raw/chapter8/02c_debug-terminal.md @@ -0,0 +1 @@ +Using the Python debugger in a terminal. In this video, we'll learn how to use the Python debugger in a terminal. For this example, we are running code from the token classification section, downloading the Conll dataset before loading a tokenizer to preprocess it. Checkout the section of the course linked below for more information. Once this is done, we try to batch together some features of the training dataset by padding them and returning a tensor, then we get the following error. We use PyTorch here but you will get the same error with TensorFlow. As we have seen in the "How to debug an error?" video, the error message is at the end and it indicates we should use padding... which we are actually trying to do. So this is not useful and we will need to go a little deeper to debug the problem. Fortunately, you can use the Python debugger quite easily in a terminal by launching your script with python -m pdb instead of just python. When executing that command, you are sent to the first instruction of your script. You can run just the next instruction by typing n, or continue to the error by directly typing c. Once there, you go to the very bottom of the traceback, and you can type commands. The first two commands you should learn are u and d (for up and down), which allow you to go up in the Traceback or down. Going up twice, we get to the point the error was reached. The third command to learn is p, for print. It allows you to print any value you want. For instance here, we can see the value of return_tensors or batch_outputs to try to understand what triggered the error. The batch outputs dictionary is a bit hard to see, so let's dive into smaller pieces of it. Inside the debugger you can not only print any variable but also evaluate any expression, so we can look independently at the inputs or labels. Those labels are definitely weird: they are of various size, which we can actually confirm by printing the sizes. No wonder the tokenizer wasn't able to create a tensor with them! This is because the pad method only takes care of the tokenizer outputs: input IDs, attention mask and token type IDs, so we have to pad the labels ourselves before trying to create a tensor with them. Once you are ready to exit the Python debugger, you can press q for quit. Another way we can access the Python debugger is to set a "set_trace" instruction where we want in the script. It will interrupt the execution and launch the Python debugger at this place, and we can inspect all the variables before the next instruction is executed. Typing n executes the next instruction, which takes us back inside the traceback. One way to fix the error is to manually pad all labels to the longest, or we can use the data collator designed for this. \ No newline at end of file diff --git a/subtitles/en/raw/chapter8/03_forums.md b/subtitles/en/raw/chapter8/03_forums.md new file mode 100644 index 000000000..1cde189b0 --- /dev/null +++ b/subtitles/en/raw/chapter8/03_forums.md @@ -0,0 +1,19 @@ +How to ask a question on the Hugging Face forums? + +If you have a general question or are looking to debug your code, the forums are the place to ask. In this video we will teach you how to write a good question, to maximize the chances you will get an answer. + +First things first, to login on the forums, you need a Hugging Face account. If you haven't created one yet, go to hf.co and click Sign Up. There is also a direct link below. + +Fill your email and password, then continue the steps ot pick a username and update a profile picture. + +Once this is done, go to discuss.huggingface.co (linked below) and click Log In. Use the same login information as for the Hugging Face website. + +You can search the forums by clicking on the magnifying glass. Someone may have already asked your question in a topic! If you find you can't post a new topic as a new user, it may be because of the antispam filters. Make sure you spend some time reading existing topics to deactivate it. + +When you are sure your question hasn't been asked yet, click on the New Topic button. + +For this example, we will use the following code, + +that produces an error, as we saw in the "What to do when I get an error?" video. + +The first step is to pick a category for our new topic. Since our error has to do with the Transformers library, we pick this category. New, choose a title that summarizes your error well. Don't be too vague or users that get the same error you did in the future won't be able to find your topic. Once you have finished typing your topic, make sure the question hasn't been answered in the topics Discourse suggests you. Click on the cross to remove that window when you have double-checked. This is an example of what not to do when posting an error: the message is very vague so no one else will be able to guess what went wrong for you, and it tags too many people. Tagging people (especially moderators) might have the opposite effect of what you want. As you send them a notification (and they get plenty), they will probably not bother replying to you, and users you didn't tag will probably ignore the question since they see tagged users. Only tag a user when you are completely certain they are the best placed to answer your question. Be precise in your text, and if you have an error coming from a specific piece of code, include that code in your post. To make sure your post looks good, place your question between three backticks like this. You can check on the right how your post will appear once posted. If your question is about an error, it's even better to include the full traceback. As explained in the "what to do when I get an error?' video, expand the traceback if you are on Colab. like for the code, put it between two lines containing three backticks for proper formatting. Our last advice is to remember to be nice, a please and a thank you will go a long way into getting others to help you. With all that done properly, your question should get an answer pretty quickly! \ No newline at end of file diff --git a/subtitles/en/raw/chapter8/04_debug-pt.md b/subtitles/en/raw/chapter8/04_debug-pt.md new file mode 100644 index 000000000..f782929d0 --- /dev/null +++ b/subtitles/en/raw/chapter8/04_debug-pt.md @@ -0,0 +1 @@ +In this video, we will see how to debug an error you encounter when running trainer.train(). As an example, we will use this script that finetunes a bert model on the GLUE MNLI dataset. Checkout the videos linked below to see how we came to such a script, here we want to learn how to debug the problems in it. Running the script gives us an error pretty fast. It happens at the line where we feed the inputs to the model, according to the traceback. That tells us there is a problem there, but the problem could come from many different causes. To debug an error in a training, you need to make sure each step of the training pipeline works as intended. This means checking that the inputs of your dataset are correct, you can batch them together, feed them through the model to get a loss, then compute the gradients of that loss before performing an optimizer step. So let's start by looking at the training dataset this Trainer is using. There is definitely a problem there as we see texts and not numbers. The error message was telling us the model did not get input IDs and we do not have those in the dataset indeed. Looking back at our code, we can see we made a mistake and passed the wrong datasets to the Trainer. So let's fix that and run again. Now we have a new error. Inspecting the traceback tells us it happens when we try to create a batch, specifically to group the features in a tensor. We can confirm this by asking the Trainer to get us a batch of the training data loader, which reproduces the same error. Either by inspecting the inputs or debugging, we can then see they are not all of the same size. This is because we have not passed a data collator to do the padding in the Trainer and didn't pad when preprocessing the data either. Padding inside the Trainer is normally the default, but only if you provide your tokenizer to the Trainer, and we forgot to do that. So let's fix the issue and run again. This time we get a nasty CUDA error. They are very difficult to debug because for one, they put your kernel in a state that is not recoverable (so you have to restart your notebook from the beginning) and two, the traceback is completely useless for those. Here the traceback tells us the error happens when we do the gradient computation with loss.backward, but as we will see later on that is not the case. This is because everything that happens on the GPU is done asynchronously: when you execute the model call, what the program does is just stacking that in the queue of GPU, then (if the GPU didn't have any current job to do), the work will start on the GPU at the same time as the CPU will move to the next instruction. Continuing with the extraction of the loss, this is stacked into the GPU queue while the CPU moves to the instruction loss.backward. But the GPU still hasn't finished the forward pass of the model since all that took no time at all. The CPU stops moving forward, because loss.backward as an instruction telling it to wait for the GPUs to be finished, and when the GPU encounters an error, it gives with a cryptic message back to the CPU, who raises the error at the wrong place. So to debug this, we will need to execute the next steps of the training pipeline on the CPU. It is very easy to do, and we get a traceback we can trust this time. As we said before, the error happens during the forward pass of the model, and it's an index error. With a bit of debugging, we see we have labels ranging from 0 to 2, so three different values, but our outputs have a shape of batch size per 2. It looks like our model has the wrong number of labels! We can indeed confirm that, and now that we know it's easy to fix it in the code by adding num_labels=3 when we create the model. Now the training script will run to completion! We did not need it yet, but here is how we would debug the next step of the pipeline, gradient computation, as well as the optimizer step. With all of this, good luck debugging your own trainings! \ No newline at end of file diff --git a/subtitles/en/raw/chapter8/04_debug-tf.md b/subtitles/en/raw/chapter8/04_debug-tf.md new file mode 100644 index 000000000..f8b1361f8 --- /dev/null +++ b/subtitles/en/raw/chapter8/04_debug-tf.md @@ -0,0 +1 @@ +Some bugs in your code are very straightforward. You try running it, you get a syntax error somewhere, Python tells you exactly where, and you fix it. This is great - it's simple and satisfying. Sometimes, though, things crash and the error is impossible to understand. This happens a lot in machine learning for a few reasons - you're working with big data structures, using big, complex libraries with a lot of moving parts, and also you're doing a lot of GPU computing. In Keras there's the added bonus problem that your models are often compiled before execution, which is great for performance but makes debugging them very difficult. This is going to be a video about what to do when you run into one of those nightmare bugs. To give you some intuitions for what can go wrong, and where to look for the source of bugs that you encounter, let's use this example script, and I'll show it to you here in two parts. First, we do all our imports, we load a dataset, we create our tokenizer and we tokenize the dataset. Next, we convert our datasets to TensorFlow datasets, so that we can run fit() on them, and then we load our model from a pretrained checkpoint, compile it and fit it. It seems straightforward enough, but beware! This spooky code hides many dark and mysterious secrets. What happens when we run it? Well, this isn't great. What does that mean? We tried to train on our data, but we got no gradient? This is pretty perplexing - how do we even begin to debug something like that? When the error you get doesn't immediately suggest where the problem is, the best solution is often to walk through things in sequence, making sure at each stage that things look right. And of course, the place to start is always to check your data. The best way to do that to grab a batch from the tf.data.Dataset that your model is training on, right at the end of the training pipeline. And we can do that like so, by looping over the dataset for one iteration and then breaking. So what do we get when we inspect that batch? We see that we're not getting any gradient because we're not passing labels to Keras! Our labels are in the batch, but they're a key in the input dictionary, not a separate label. This is one of the most common issues you'll encounter when training Transformers models with TensorFlow. Our models can all compute loss internally, but to use that loss for training the labels need to be passed in the input dictionary, where the model can see them. This internal loss is the loss that we use when we don't specify a loss value to compile(). Keras, on the other hand, usually expects labels to be passed separately from the input dictionary and not to be visible to the model, and loss computations will usually fail if you don't do that. We need to choose one or the other: Either we use the model's internal loss and keep the labels where they are, or we keep using Keras losses, but we move the labels to the place Keras expects them. For simplicity, let's use the model internal losses, by removing the loss argument from the call to compile(). So what happens if we try training with model.fit() after fixing the loss function! Well, it runs this time... but now we get a loss of nan. This isn't good. NaN is not a good loss. In fact, if we inspect our model now, we'll see that not only are all the outputs nan , all the weights are nan too. Once a single nan creeps into your computations, it tends to spread, because it propagates from the loss back through your gradient and then into the weight updates. So nan destroyed our model. But where did it creep in first? To find out, we need to re-initialize the model and look at the outputs for just the first batch. And when we do that, we see that nan first appears in the loss, but only in some samples! You can see this in more detail in the accompanying section of the course notes, but we find that if we look at the labels, the samples with a loss of nan all have a label of 2! This gives us a very strong clue - if we check the model, with model.config.num_labels, we see the model thinks there's only 2 labels, but if we see a value of 2, that means there's at least 3 labels, because 0 is a label too! So we got a loss of nan because we got an "impossible" label. To fix that, we need to go back and set the model to have the right number of labels. We can set num_labels=3 when we initialize the model with from_pretrained. So now we think our data is good and our model is good, so training should work. And if we try running model.fit(), we get... hmm. The loss goes down, but it's not very quick. And if we keep running it out, we'll find that it stalls at a fairly high value. What's going on? Well, when things are mostly working, but training is just slow, that can often be a good time to look at your optimizer and training hyperparameters. And this is where I want to mention one of the most common sources of issues when you're working with Keras - you can name things like optimizers with strings, but if you do that, all of the options get silently set to their default values. So we specified our optimizer as Adam, but in the process we invisibly got the default learning rate, which is 1e-3, or ten to the power of minus 3. This is way too high for training transformer models! We should go back and specify the learning rate directly - good values are between 1e-5 and 1e-4. Let's split the difference and pick 5e-5. And if you recompile with that, you'll find that training actually works, at last. Again, I went through this quite quickly, and I recommend checking out the course notes for this to see this in more detail and to experiment with the code yourself. Good luck, and remember to take breaks if your code is giving you a hard time! \ No newline at end of file diff --git a/subtitles/en/raw/chapter8/05_issues.md b/subtitles/en/raw/chapter8/05_issues.md new file mode 100644 index 000000000..ce81ddc1f --- /dev/null +++ b/subtitles/en/raw/chapter8/05_issues.md @@ -0,0 +1 @@ +How to write a good issue on GitHub? GitHub is the main place for the Hugging Face open source libraries, and should always go there to report a bug or ask for a new feature. For more general questions or to debug your own code, use the forums (see the video linked below). It's very important to write good issues as it will help the bug you uncovered be fixed in no time. For this video, we have created a version of Transformers with a bug. You can install it by executing this command in a notebook (remove the exclamation mark to execute it in a terminal). In this version, the following example fails. The error is rather cryptic and does not seem to come from anything in our code, so it seems we have a bug to report! The first thing to do in this case is to try to find the smallest amount of code possible that reproduces the bug. In our case, inspecting the traceback, we see the failure happens inside the pipeline function when it calls AutoTokenizer.from_pretrained. Using the debugger, we find the values passed to that method and can thus create a small sample of code that hopefully generates the same error. It's very important to go though this step as you may realize the error was on your side and not a bug in the library, but it also will make it easier for the maintainers to fix your problem. Here we can play around a bit more with this code and notice the error happens for different checkpoints and not just this one, and that it disappears when we use use_fast=False inside our tokenizer call. The important part is to have something that does not depend on any external files or data. Try to replace your data by fake values if you can't share it. With all of this done, we are ready to start writing our issue. Click on the button next to Bug Report and you will discover there is a template to fill. It will only take you a couple of minutes. The first thing is to properly name your issue. Don't pick a title that is too vague! Then you have to fill your environment information. There is a command provided by the Transformers library to do this. Just execute it in your notebook or in a terminal, and copy paste the results. There are two last questions to fill manually (to which the answers are no and no in our case). Next, we need to determine who to tag. There is a full list of usernames. Since our issue has to do with tokenizers, we pick the maintainer associated with them. There is no point tagging more than 3 people, they will redirect you to the right person if you made a mistake. Next, we have to give the information necessary to reproduce the bug. We paste our sample, and put it between two lines with three backticks so it's formatted properly. We also paste the full traceback, still between two lines of three backticks. Lastly, we can add any additional information about what we tried to debug the issue at hand. With all of this, you should expect an answer to your issue pretty fast, and hopefully, a quick fix! Note that all the advise in this video applies for almost every open-source project. \ No newline at end of file diff --git a/subtitles/zh-CN/00_welcome-to-the-hugging-face-course.srt b/subtitles/zh-CN/00_welcome-to-the-hugging-face-course.srt new file mode 100644 index 000000000..0e8575089 --- /dev/null +++ b/subtitles/zh-CN/00_welcome-to-the-hugging-face-course.srt @@ -0,0 +1,515 @@ +1 +00:00:05,850 --> 00:00:07,713 +- 欢迎来到 Hugging Face 课程。 +- Welcome to the Hugging Face Course. + +2 +00:00:08,550 --> 00:00:10,320 +本课程旨在带你了解 +This course has been designed to teach you + +3 +00:00:10,320 --> 00:00:12,750 +关于 Hugging Face 生态系统的一切 +all about the Hugging Face ecosystem, + +4 +00:00:12,750 --> 00:00:14,700 +如何使用数据集和模型中心 +how to use the dataset and model hub + +5 +00:00:14,700 --> 00:00:16,803 +以及我们所有的开源库。 +as well as all our open-source libraries. + +6 +00:00:18,300 --> 00:00:19,950 +这是目录。 +Here is the Table of Contents. + +7 +00:00:19,950 --> 00:00:22,770 +如你所见,它分为三个部分 +As you can see, it's divided in three sections + +8 +00:00:22,770 --> 00:00:25,110 +逐渐变得更先进。 +which become progressively more advanced. + +9 +00:00:25,110 --> 00:00:28,500 +现阶段,前两部分已经发布。 +At this stage, the first two sections have been released. + +10 +00:00:28,500 --> 00:00:30,120 +所以首先,我们会教你基础知识 +So first, we'll teach you the basics + +11 +00:00:30,120 --> 00:00:32,250 +如何使用 Transformer 模型, +of how to use a Transformer model, + +12 +00:00:32,250 --> 00:00:34,230 +在你自己的数据集上微调 +fine-tune it on your own data set + +13 +00:00:34,230 --> 00:00:36,960 +并与社区分享结果。 +and share the result with the community. + +14 +00:00:36,960 --> 00:00:39,420 +其次,我们将更深入地研究我们的图书馆 +So second, we'll dive deeper into our libraries + +15 +00:00:39,420 --> 00:00:42,360 +并教你如何处理任何 NLP 任务。 +and teach you how to tackle any NLP task. + +16 +00:00:42,360 --> 00:00:44,430 +我们正在积极研究最后一个 +We're actively working on the last one + +17 +00:00:44,430 --> 00:00:47,280 +并希望在 2022 年春季为你准备好它。 +and hope to have it ready for you for the spring of 2022. + +18 +00:00:48,510 --> 00:00:50,880 +第一章不需要技术知识 +The first chapter requires no technical knowledge + +19 +00:00:50,880 --> 00:00:52,320 +是一个很基础的介绍 +and is a good introduction to learn + +20 +00:00:52,320 --> 00:00:54,180 +关于 Transformers 模型可以做什么 +what Transformers models can do + +21 +00:00:54,180 --> 00:00:56,883 +以及它如何对你或你的公司有用。 +and how it could be of use to you or your company. + +22 +00:00:58,050 --> 00:01:01,110 +接下来的章节需要对 Python 有很好的了解 +The next chapters require a good knowledge of Python + +23 +00:01:01,110 --> 00:01:02,130 +以及一些基础知识 +and some basic knowledge of + +24 +00:01:02,130 --> 00:01:04,350 +机器学习和深度学习。 +Machine Learning and Deep Learning. + +25 +00:01:04,350 --> 00:01:07,110 +如果你不知道什么是训练集和验证集 +If you don't know what a training and validation set are + +26 +00:01:07,110 --> 00:01:09,360 +或者梯度体面意味着什么, +or what gradient decent means, + +27 +00:01:09,360 --> 00:01:11,340 +你应该看看入门课程 +you should look at an introductory course + +28 +00:01:11,340 --> 00:01:14,863 +例如 deeplearning.ai 或 fast.ai 发布的那些。 +such as the ones published by deeplearning.ai or fast.ai. + +29 +00:01:16,200 --> 00:01:17,910 +如果你有一些基础知识也是最好的 +It's also best if you have some basics + +30 +00:01:17,910 --> 00:01:21,150 +在一个深度学习框架、PyTorch 或 TensorFlow 中。 +in one Deep Learning Framework, PyTorch or TensorFlow. + +31 +00:01:21,150 --> 00:01:23,520 +本课程介绍的各部分资料 +Each part of the material introduced in this course + +32 +00:01:23,520 --> 00:01:25,590 +在这两个框架中都有一个版本, +has a version in both those frameworks, + +33 +00:01:25,590 --> 00:01:26,730 +这样你就可以选择一个 +so you will be able to pick the one + +34 +00:01:26,730 --> 00:01:28,230 +你最舒服。 +you are most comfortable with. + +35 +00:01:29,550 --> 00:01:31,740 +这是开发这门课程的团队。 +This is the team that developed this course. + +36 +00:01:31,740 --> 00:01:33,120 +我现在让每个发言者 +I'll now let each of the speakers + +37 +00:01:33,120 --> 00:01:34,570 +简单介绍一下自己。 +introduce themselves briefly. + +38 +00:01:37,230 --> 00:01:38,880 +- 你好,我叫马修, +- Hi, my name is Matthew, + +39 +00:01:38,880 --> 00:01:41,610 +我是 Hugging Face 的机器学习工程师。 +and I'm a Machine Learning Engineer at Hugging Face. + +40 +00:01:41,610 --> 00:01:43,200 +我在开源团队工作 +I work on the open-source team + +41 +00:01:43,200 --> 00:01:45,180 +我负责特别维护 +and I'm responsible for maintaining particularly + +42 +00:01:45,180 --> 00:01:47,280 +那里的 TensorFlow 代码。 +the TensorFlow code there. + +43 +00:01:47,280 --> 00:01:50,130 +之前,我是 Parsley 的机器学习工程师, +Previously, I was a Machine Learning Engineer at Parsley, + +44 +00:01:50,130 --> 00:01:52,620 +最近被 Automatic 收购, +who've recently been acquired by Automatic, + +45 +00:01:52,620 --> 00:01:54,210 +我是一名博士后研究员 +and I was a postdoctoral researcher + +46 +00:01:54,210 --> 00:01:57,000 +之前在爱尔兰都柏林三一学院 +before that at Trinity College, Dublin in Ireland + +47 +00:01:57,000 --> 00:02:00,093 +致力于计算遗传学和视网膜疾病。 +working on computational genetics and retinal disease. + +48 +00:02:02,400 --> 00:02:03,870 +- 你好,我是莱桑德尔。 +- Hi, I'm Lysandre. + +49 +00:02:03,870 --> 00:02:05,640 +我是 Hugging Face 的机器学习工程师 +I'm a Machine Learning Engineer at Hugging Face + +50 +00:02:05,640 --> 00:02:08,700 +我特别是开源团队的一员。 +and I'm specifically part of the open-source team. + +51 +00:02:08,700 --> 00:02:10,890 +我已经在 Hugging Face 工作了几年 +I've been at Hugging Face for a few years now + +52 +00:02:10,890 --> 00:02:12,300 +和我的团队成员一起, +and alongside my team members, + +53 +00:02:12,300 --> 00:02:13,890 +我一直在研究大多数工具 +I've been working on most of the tools + +54 +00:02:13,890 --> 00:02:15,790 +你将在本课程中看到。 +that you'll get to see in this course. + +55 +00:02:18,270 --> 00:02:20,130 +- 你好,我是西尔万。 +- Hi, I'm Sylvain. + +56 +00:02:20,130 --> 00:02:22,140 +我是 Hugging Face 的研究工程师 +I'm a Research Engineer at Hugging Face + +57 +00:02:22,140 --> 00:02:25,830 +也是 Transformers 库的主要维护者之一。 +and one of the main maintainers of the Transformers Library. + +58 +00:02:25,830 --> 00:02:28,110 +之前,我在 fast.ai 工作 +Previously, I worked at fast.ai + +59 +00:02:28,110 --> 00:02:30,420 +我帮助开发了 fast.ai 库 +where I helped develop the fast.ai Library + +60 +00:02:30,420 --> 00:02:32,220 +以及在线图书。 +as well as the online book. + +61 +00:02:32,220 --> 00:02:35,340 +在那之前,我是一名数学和计算机科学老师 +Before that, I was a math and computer science teacher + +62 +00:02:35,340 --> 00:02:36,173 +在法国。 +in France. + +63 +00:02:38,550 --> 00:02:41,340 +- 你好,我叫 Sasha,是 Hugging Face 的一名研究员, +- Hi, my name is Sasha and I'm a Researcher at Hugging Face, + +64 +00:02:41,340 --> 00:02:42,420 +致力于道德, +working on the ethical, + +65 +00:02:42,420 --> 00:02:46,230 +机器学习模型的环境和社会影响。 +environmental and social impacts of machine learning models. + +66 +00:02:46,230 --> 00:02:49,020 +之前,我是 Mila 的博士后研究员, +Previously, I was a postdoctoral researcher at Mila, + +67 +00:02:49,020 --> 00:02:50,400 +蒙特利尔大学 +University in Montreal + +68 +00:02:50,400 --> 00:02:53,040 +我还担任过应用人工智能研究员 +and I also worked as an Applied AI Researcher + +69 +00:02:53,040 --> 00:02:55,140 +为联合国全球脉搏。 +for the United Nations Global Pulse. + +70 +00:02:55,140 --> 00:02:57,300 +参与过 CodeCarbon 等项目 +I've been involved in projects such as CodeCarbon + +71 +00:02:57,300 --> 00:02:59,790 +和机器学习影响计算器 +and the Machine Learning Impacts Calculator + +72 +00:02:59,790 --> 00:03:02,390 +衡量机器学习的碳足迹。 +to measure the carbon footprint of machine learning. + +73 +00:03:05,160 --> 00:03:07,650 +- 大家好,我是 Merve,我是 Hugging Face 团队的开发技术推广工程师 +- Hi, I'm Merve and I'm a Developer Advocate + +74 +00:03:07,650 --> 00:03:09,390 +- 大家好,我是 Merve,我是 Hugging Face 团队的开发技术推广工程师 +at Hugging Face. + +75 +00:03:09,390 --> 00:03:12,480 +以前,我是一名机器学习工程师 +Previously, I was working as a Machine Learning Engineer + +76 +00:03:12,480 --> 00:03:15,360 +构建 NLP 工具和聊天机器人。 +building NLP tools and chat bots. + +77 +00:03:15,360 --> 00:03:17,670 +目前,我正在努力改进中心 +Currently, I'm working to improve the hub + +78 +00:03:17,670 --> 00:03:19,563 +并使机器学习民主化。 +and democratize machine learning. + +79 +00:03:22,140 --> 00:03:23,670 +- 大家好。 +- Hello everyone. + +80 +00:03:23,670 --> 00:03:27,210 +我叫 Lucile,是 Hugging Face 团队的一名机器学习工程师 +My name is Lucile and I'm a Machine Learning Engineer + +81 +00:03:27,210 --> 00:03:28,353 +我叫 Lucile,是 Hugging Face 团队的一名机器学习工程师 +at Hugging Face. + +82 +00:03:29,580 --> 00:03:32,550 +用两句话告诉你我是谁, +To tell you in two sentences who I am, + +83 +00:03:32,550 --> 00:03:35,590 +我致力于开源工具的开发和支持 +I work on the development and support of open-source tools + +84 +00:03:36,600 --> 00:03:39,595 +我也参与了几个研究项目 +and I also participate in several research project + +85 +00:03:39,595 --> 00:03:41,795 +在自然语言处理领域。 +in the field of Natural Language Processing. + +86 +00:03:44,610 --> 00:03:45,540 +- 大家好。 +- Good day there. + +87 +00:03:45,540 --> 00:03:47,550 +我是刘易斯,我是一名机器学习工程师 +I'm Lewis and I'm a Machine Learning Engineer + +88 +00:03:47,550 --> 00:03:50,130 +在 Hugging Face 的开源团队中。 +in the open-source team at Hugging Face. + +89 +00:03:50,130 --> 00:03:53,490 +我热衷于为 NLP 社区开发工具 +I'm passionate about developing tools for the NLP community + +90 +00:03:53,490 --> 00:03:55,050 +你能在很多 Hugging Face 对外的活动里见到我 +and you'll see me at many of Hugging Face's + +91 +00:03:55,050 --> 00:03:56,910 +你能在很多 Hugging Face 对外的活动里见到我 +outreach activities. + +92 +00:03:56,910 --> 00:03:58,470 +在加入 Hugging Face 之前, +Before joining Hugging Face, + +93 +00:03:58,470 --> 00:03:59,790 +我花了几年时间开发 +I spent several years developing + +94 +00:03:59,790 --> 00:04:01,860 +初创公司的机器学习应用程序 +machine learning applications for startups + +95 +00:04:01,860 --> 00:04:04,230 +和 NLP 领域的企业, +and enterprises in the domains of NLP, + +96 +00:04:04,230 --> 00:04:07,260 +拓扑数据分析和时间序列。 +topological data analysis and time series. + +97 +00:04:07,260 --> 00:04:10,110 +前世,我是一名理论物理学家, +In a former life, I was a theoretical physicist, + +98 +00:04:10,110 --> 00:04:11,760 +我在哪里研究粒子碰撞 +where I researched particle collisions + +99 +00:04:11,760 --> 00:04:13,560 +在大型强子对撞机等。 +at the Large Hadron Collider and so. + +100 +00:04:15,900 --> 00:04:18,450 +- 嘿,我是 Leandro,我是一名机器学习工程师 +- Hey, I'm Leandro and I'm a Machine Learning Engineer + +101 +00:04:18,450 --> 00:04:21,030 +在 Hugging Face 的开源团队中。 +in the open-source team at Hugging Face. + +102 +00:04:21,030 --> 00:04:23,460 +在加入 Hugging Face 之前,我是一名数据科学家 +Before joining Hugging Face, I worked as a Data Scientist + +103 +00:04:23,460 --> 00:04:26,733 +在瑞士,并在大学教授数据科学。 +in Switzerland and have taught Data Science at University. + diff --git a/subtitles/zh-CN/01_the-pipeline-function.srt b/subtitles/zh-CN/01_the-pipeline-function.srt new file mode 100644 index 000000000..67579b6c7 --- /dev/null +++ b/subtitles/zh-CN/01_the-pipeline-function.srt @@ -0,0 +1,500 @@ +1 +00:00:00,069 --> 00:00:01,341 +(屏幕嗖嗖声) +(screen whooshes) + +2 +00:00:01,341 --> 00:00:02,449 +(面部标志呼啸而过) +(face logo whooshes) + +3 +00:00:02,449 --> 00:00:05,880 +(屏幕嗖嗖声) +(screen whooshes) + +4 +00:00:05,880 --> 00:00:07,080 +- 本节课内容是: Pipeline 函数 +- The pipeline function. + +5 +00:00:09,540 --> 00:00:12,020 +Pipeline 函数是 Transformers 库中的 +The pipeline function is the most high level API + +6 +00:00:12,020 --> 00:00:14,010 +最顶层的 API +of the Transformers library. + +7 +00:00:14,010 --> 00:00:16,050 +它将所有步骤重新组合在一起 +It regroups together all the steps + +8 +00:00:16,050 --> 00:00:18,873 +从原始文本到可用的预测。 +to go from raw texts to usable predictions. + +9 +00:00:20,228 --> 00:00:22,980 +使用的模型是管道的核心, +The model used is at the core of a pipeline, + +10 +00:00:22,980 --> 00:00:24,390 +但管道还包括 +but the pipeline also include + +11 +00:00:24,390 --> 00:00:26,610 +所有必要的预处理, +all the necessary pre-processing, + +12 +00:00:26,610 --> 00:00:30,240 +因为模型不期望文本,而是数字, +since the model does not expect texts, but number, + +13 +00:00:30,240 --> 00:00:32,040 +以及一些后期处理, +as well as some post-processing, + +14 +00:00:32,040 --> 00:00:34,533 +使模型的输出可读。 +to make the output of the model human-readable. + +15 +00:00:35,910 --> 00:00:37,593 +让我们看第一个例子 +Let's look at a first example + +16 +00:00:37,593 --> 00:00:39,693 +与情绪分析管道。 +with the sentiment analysis pipeline. + +17 +00:00:40,740 --> 00:00:44,670 +此管道对给定输入执行文本分类 +This pipeline performs text classification on a given input + +18 +00:00:44,670 --> 00:00:46,953 +并确定它是正面的还是负面的。 +and determines if it's positive or negative. + +19 +00:00:47,910 --> 00:00:51,750 +在这里,它将正面标签归因于给定文本, +Here, it attributed the positive label on the given text, + +20 +00:00:51,750 --> 00:00:54,413 +置信度为 95%。 +with a confidence of 95%. + +21 +00:00:55,650 --> 00:00:58,470 +你可以将多个文本传递到同一个管道, +You can pass multiple texts to the same pipeline, + +22 +00:00:58,470 --> 00:01:00,270 +将被处理并通过 +which will be processed and passed + +23 +00:01:00,270 --> 00:01:02,673 +通过模型一起作为一个批次。 +through the model together as a batch. + +24 +00:01:03,570 --> 00:01:05,970 +输出是单个结果的列表 +The output is a list of individual results + +25 +00:01:05,970 --> 00:01:07,923 +与输入文本的顺序相同。 +in the same order as the input texts. + +26 +00:01:08,790 --> 00:01:12,270 +在这里,我们找到了第一个文本的相同标签和分数, +Here we find the same label and score for the first text, + +27 +00:01:12,270 --> 00:01:14,443 +第二个文本被判断为否定的 +and the second text is judged negative + +28 +00:01:14,443 --> 00:01:17,243 +置信度为 99.9%。 +with a confidence of 99.9%. + +29 +00:01:18,720 --> 00:01:20,700 +零样本分类流水线 +The zero-shot classification pipeline + +30 +00:01:20,700 --> 00:01:23,610 +是一个更通用的文本分类管道, +is a more general text-classification pipeline, + +31 +00:01:23,610 --> 00:01:26,370 +它允许你提供所需的标签。 +it allows you to provide the labels you want. + +32 +00:01:26,370 --> 00:01:29,850 +在这里,我们想根据标签对输入文本进行分类, +Here we want to classify our input text along the labels, + +33 +00:01:29,850 --> 00:01:32,643 +教育、政治和商业。 +education, politics, and business. + +34 +00:01:33,540 --> 00:01:35,580 +管道成功识别 +The pipeline successfully recognizes + +35 +00:01:35,580 --> 00:01:38,280 +与其他标签相比,它更多地是关于教育, +it's more about education than the other labels, + +36 +00:01:38,280 --> 00:01:40,643 +置信度为 84%。 +with a confidence of 84%. + +37 +00:01:41,670 --> 00:01:43,110 +继续执行其他任务, +Moving on to other tasks, + +38 +00:01:43,110 --> 00:01:45,030 +文本生成管道将 +the text generation pipeline will + +39 +00:01:45,030 --> 00:01:46,533 +自动完成给定的提示。 +auto-complete a given prompt. + +40 +00:01:47,460 --> 00:01:49,980 +输出带有一点随机性, +The output is generated with a bit of randomness, + +41 +00:01:49,980 --> 00:01:52,800 +所以每次调用生成器对象时它都会改变 +so it changes each time you call the generator object + +42 +00:01:52,800 --> 00:01:53,763 +在给定的提示上。 +on a given prompt. + +43 +00:01:54,990 --> 00:01:57,123 +到目前为止,我们已经使用了管道 API +Up until now, we've used the the pipeline API + +44 +00:01:57,123 --> 00:02:00,360 +使用与每个任务关联的默认模型, +with the default model associated to each task, + +45 +00:02:00,360 --> 00:02:02,880 +但你可以将它与任何经过预训练的模型一起使用 +but you can use it with any model that has been pretrained + +46 +00:02:02,880 --> 00:02:04,263 +或微调此任务。 +or fine-tuned on this task. + +47 +00:02:06,540 --> 00:02:10,350 +进入模型中心 huggingface.co/models +Going on the model hub, huggingface.co/models + +48 +00:02:10,350 --> 00:02:13,350 +你可以按任务过滤可用模型。 +you can filter the available models by task. + +49 +00:02:13,350 --> 00:02:17,190 +我们之前示例中使用的默认模型是 gpt2, +The default model used in our previous example was gpt2, + +50 +00:02:17,190 --> 00:02:19,290 +但还有更多型号可供选择, +but there are many more models available, + +51 +00:02:19,290 --> 00:02:20,523 +而不仅仅是英语。 +and not just in English. + +52 +00:02:21,450 --> 00:02:23,670 +让我们回到文本生成管道 +Let's go back to the text generation pipeline + +53 +00:02:23,670 --> 00:02:26,193 +并用另一个模型 distilgpt2 加载它。 +and load it with another model, distilgpt2. + +54 +00:02:27,060 --> 00:02:28,950 +这是 gpt2 的轻量级版本 +This is a lighter version of gpt2 + +55 +00:02:28,950 --> 00:02:30,603 +由 Hugging Face 团队创建。 +created by the Hugging Face team. + +56 +00:02:31,740 --> 00:02:34,110 +将管道应用于给定提示时, +When applying the pipeline to a given prompt, + +57 +00:02:34,110 --> 00:02:36,360 +我们可以指定几个参数 +we can specify several arguments + +58 +00:02:36,360 --> 00:02:39,240 +例如生成文本的最大长度, +such as the maximum length of the generated texts, + +59 +00:02:39,240 --> 00:02:41,700 +或者我们想要返回的句子数量, +or the number of sentences we want to return, + +60 +00:02:41,700 --> 00:02:44,150 +因为这一代有一些随机性。 +since there is some randomness in the generation. + +61 +00:02:46,080 --> 00:02:48,750 +通过猜测句子中的下一个单词生成文本 +Generating texts by guessing the next word in a sentence + +62 +00:02:48,750 --> 00:02:51,450 +是 GPT-2 的预训练目标。 +was the pretraining objective of GPT-2. + +63 +00:02:51,450 --> 00:02:55,140 +fill mask pipeline 是 BERT 的预训练目标, +The fill mask pipeline is the pretraining objective of BERT, + +64 +00:02:55,140 --> 00:02:57,363 +这是猜测掩码词的值。 +which is to guess the value of masked word. + +65 +00:02:58,260 --> 00:03:01,020 +在这种情况下,我们询问两个最可能的值 +In this case, we ask the two most likely values + +66 +00:03:01,020 --> 00:03:03,660 +对于缺失的词,根据模型, +for the missing words, according to the model, + +67 +00:03:03,660 --> 00:03:07,053 +并获得数学或计算作为可能的答案。 +and get mathematical or computational as possible answers. + +68 +00:03:08,280 --> 00:03:10,170 +Transformers 模型可以执行的另一项任务 +Another task Transformers model can perform + +69 +00:03:10,170 --> 00:03:12,660 +就是对句子中的每一个词进行分类 +is to classify each word in the sentence + +70 +00:03:12,660 --> 00:03:14,970 +而不是整个句子。 +instead of the sentence as a whole. + +71 +00:03:14,970 --> 00:03:18,390 +其中一个例子是命名实体识别, +One example of this is Named Entity Recognition, + +72 +00:03:18,390 --> 00:03:20,820 +这是识别实体的任务, +which is the task of identifying entities, + +73 +00:03:20,820 --> 00:03:25,323 +例如句子中的人、组织或地点。 +such as persons, organizations or locations in a sentence. + +74 +00:03:26,400 --> 00:03:30,570 +在这里,模型正确地找到了人 Sylvain, +Here, the model correctly finds the person, Sylvain, + +75 +00:03:30,570 --> 00:03:32,453 +组织,是 Hugging Face +the organization, Hugging Face, + +76 +00:03:32,453 --> 00:03:35,010 +以及位置,布鲁克林, +as well as the location, Brooklyn, + +77 +00:03:35,010 --> 00:03:36,303 +在输入文本中。 +inside the input text. + +78 +00:03:37,661 --> 00:03:40,230 +使用的 grouped_entities=True 参数 +The grouped_entities=True argument used + +79 +00:03:40,230 --> 00:03:42,330 +就是把管道组在一起 +is to make the pipeline group together + +80 +00:03:42,330 --> 00:03:44,790 +链接到同一实体的不同词, +the different words linked to the same entity, + +81 +00:03:44,790 --> 00:03:46,353 +比如这里的 Hugging 和 Face。 +such as Hugging and Face here. + +82 +00:03:48,270 --> 00:03:50,670 +管道 API 可用的另一个任务 +Another task available with the pipeline API + +83 +00:03:50,670 --> 00:03:52,920 +是抽取式问答。 +is extractive question answering. + +84 +00:03:52,920 --> 00:03:55,380 +提供上下文和问题, +Providing a context and a question, + +85 +00:03:55,380 --> 00:03:58,290 +该模型将识别上下文中的文本范围 +the model will identify the span of text in the context + +86 +00:03:58,290 --> 00:04:00,190 +包含问题的答案。 +containing the answer to the question. + +87 +00:04:01,650 --> 00:04:03,960 +获取非常长的文章的简短摘要 +Getting short summaries of very long articles + +88 +00:04:03,960 --> 00:04:06,540 +也是 Transformers 库可以提供帮助的东西, +is also something the Transformers library can help with, + +89 +00:04:06,540 --> 00:04:08,140 +与摘要管道。 +with the summarization pipeline. + +90 +00:04:09,480 --> 00:04:12,570 +最后是 pipeline API 支持的最后一个任务 +Finally, the last task supported by the pipeline API + +91 +00:04:12,570 --> 00:04:14,130 +是翻译。 +is translation. + +92 +00:04:14,130 --> 00:04:16,170 +这里我们使用法语 / 英语模型 +Here we use a French/English model + +93 +00:04:16,170 --> 00:04:17,460 +在模型中心找到 +found on the model hub + +94 +00:04:17,460 --> 00:04:19,893 +获取我们输入文本的英文版本。 +to get the English version of our input text. + +95 +00:04:21,600 --> 00:04:23,490 +这是所有任务的简要总结 +Here is a brief summary of all the tasks + +96 +00:04:23,490 --> 00:04:25,500 +我们在这段视频中进行了调查。 +we've looked into in this video. + +97 +00:04:25,500 --> 00:04:27,390 +然后通过推理小部件尝试 +Try then out through the inference widgets + +98 +00:04:27,390 --> 00:04:28,327 +在模型中心。 +in the model hub. + +99 +00:04:30,459 --> 00:04:33,475 +(屏幕嗖嗖声) +(screen whooshes) + +100 +00:04:33,475 --> 00:04:35,175 +(徽标嗖嗖声) +(logo whooshes) + diff --git a/subtitles/zh-CN/02_the-carbon-footprint-of-transformers.srt b/subtitles/zh-CN/02_the-carbon-footprint-of-transformers.srt new file mode 100644 index 000000000..9d2211f64 --- /dev/null +++ b/subtitles/zh-CN/02_the-carbon-footprint-of-transformers.srt @@ -0,0 +1,645 @@ +1 +00:00:05,580 --> 00:00:08,820 +- 让我们谈谈变压器的碳足迹。 +- So let's talk about the carbon footprint of transformers. + +2 +00:00:08,820 --> 00:00:10,530 +也许你看过这样的头条新闻 +Maybe you've seen headlines such as this one + +3 +00:00:10,530 --> 00:00:13,530 +训练单个 AI 模型可以排放尽可能多的碳 +that training a single AI model can emit as much carbon + +4 +00:00:13,530 --> 00:00:16,020 +作为他们一生中的五辆汽车。 +as five cars in their lifetimes. + +5 +00:00:16,020 --> 00:00:19,440 +那么什么时候是真的,而且总是真的吗? +So when is this true and is it always true? + +6 +00:00:19,440 --> 00:00:21,803 +好吧,这实际上取决于几件事。 +Well, it actually depends on several things. + +7 +00:00:21,803 --> 00:00:23,430 +最重要的是,这取决于 +Most importantly, it depends + +8 +00:00:23,430 --> 00:00:24,960 +关于你使用的能源类型。 +on the type of energy you're using. + +9 +00:00:24,960 --> 00:00:26,267 +如果你使用的是可再生能源,例如 +If you're using renewable energy such as + +10 +00:00:26,267 --> 00:00:30,670 +太阳能、风能、水力发电,你真的 +solar, wind, hydroelectricity, you're really + +11 +00:00:30,670 --> 00:00:33,810 +根本不排放任何碳,非常非常少。 +not emitting any carbon at all, very, very little. + +12 +00:00:33,810 --> 00:00:36,769 +如果你使用的是煤炭等不可再生能源 +If you're using non-renewable energy sources such as coal + +13 +00:00:36,769 --> 00:00:39,570 +那么他们的碳足迹要高得多 +then their carbon footprint is a lot higher + +14 +00:00:39,570 --> 00:00:43,260 +因为本质上你正在排放大量的温室气体。 +'cuz essentially you are emitting a lot of greenhouse gases. + +15 +00:00:43,260 --> 00:00:44,670 +另一个方面是训练时间。 +Another aspect is training time. + +16 +00:00:44,670 --> 00:00:47,232 +所以你训练的时间越长,消耗的能量就越多 +So the longer you train, the more energy you use + +17 +00:00:47,232 --> 00:00:50,250 +你使用的能源越多,你排放的碳就越多,对吗? +the more energy you use, the more carbon you emit, right? + +18 +00:00:50,250 --> 00:00:51,270 +所以这真的加起来 +So this really adds up + +19 +00:00:51,270 --> 00:00:53,520 +特别是如果你正在训练大型模型 +especially if you're training large models for + +20 +00:00:53,520 --> 00:00:56,460 +数小时、数天和数周。 +for hours and days and weeks. + +21 +00:00:56,460 --> 00:00:58,380 +你使用的硬件也很重要 +The hardware you use also matters + +22 +00:00:58,380 --> 00:01:00,930 +因为例如某些 GPU 效率更高 +because some GPUs, for example, are more efficient + +23 +00:01:00,930 --> 00:01:05,460 +比其他人和利用效率使用得当。 +than others and utilizing efficiency use properly. + +24 +00:01:05,460 --> 00:01:07,500 +所以一直百分百地使用它们 +So using them a hundred percent all the time + +25 +00:01:07,500 --> 00:01:10,650 +可以真正减少你的能源消耗。 +can really reduce the energy consumption that you have. + +26 +00:01:10,650 --> 00:01:13,290 +然后再次减少你的碳足迹。 +And then once again, reduce your carbon footprint. + +27 +00:01:13,290 --> 00:01:15,870 +还有其他方面比如 IO +There's also other aspects such as IO + +28 +00:01:15,870 --> 00:01:17,730 +比如数据,等等,等等。 +such as data, et cetera, et cetera. + +29 +00:01:17,730 --> 00:01:20,940 +但这些是你应该关注的主要三个。 +But these are the main three that you should focus on. + +30 +00:01:20,940 --> 00:01:23,340 +所以当我谈论能源和碳强度时 +So when I talk about energy sources and carbon intensity + +31 +00:01:23,340 --> 00:01:24,420 +那个的真实意义是什么? +what does that really mean? + +32 +00:01:24,420 --> 00:01:27,480 +所以如果你看屏幕顶部 +So if you look at the top of the screen + +33 +00:01:27,480 --> 00:01:30,480 +你有碳足迹 +you have a carbon footprint + +34 +00:01:30,480 --> 00:01:33,860 +印度孟买的云计算实例 +of a cloud computing instance in Mumbai, India + +35 +00:01:33,860 --> 00:01:38,700 +每千瓦时排放 920 克二氧化碳。 +which emits 920 grams of CO2 per kilowatt hour. + +36 +00:01:38,700 --> 00:01:40,110 +这差不多一公斤 +This is almost one kilogram + +37 +00:01:40,110 --> 00:01:43,680 +每千瓦时电力使用的二氧化碳排放量。 +of CO2 per kilowatt hour of electricity used. + +38 +00:01:43,680 --> 00:01:45,150 +如果你把它与加拿大相比,蒙特利尔 +If you compare that with Canada, Montreal + +39 +00:01:45,150 --> 00:01:48,720 +我现在所在的位置,每千克小时排放 20 克二氧化碳。 +where I am right now, 20 grams of CO2 per kilo hour. + +40 +00:01:48,720 --> 00:01:50,040 +所以这是一个非常非常大的区别。 +So that's a really, really big difference. + +41 +00:01:50,040 --> 00:01:54,240 +碳排放量几乎增加了 40 倍 +Almost more than 40 times more carbon emitted + +42 +00:01:54,240 --> 00:01:55,950 +在孟买对蒙特利尔。 +in Mumbai versus Montreal. + +43 +00:01:55,950 --> 00:01:57,720 +所以这真的可以加起来。 +And so this can really, really add up. + +44 +00:01:57,720 --> 00:01:59,820 +例如,如果你要训练一个模型数周 +If you're training a model for weeks, for example + +45 +00:01:59,820 --> 00:02:01,920 +你乘以 40 +you're multiplying times 40 + +46 +00:02:01,920 --> 00:02:03,450 +你排放的碳。 +the carbon that you're emitting. + +47 +00:02:03,450 --> 00:02:05,070 +所以选择合适的实例 +So choosing the right instance + +48 +00:02:05,070 --> 00:02:07,080 +选择低碳计算实例 +choosing a low carbon compute instance + +49 +00:02:07,080 --> 00:02:09,690 +这真的是你能做的最有影响力的事情。 +is really the most impactful thing that you can do. + +50 +00:02:09,690 --> 00:02:13,020 +这就是它真正可以加起来的地方 +And this is where it can really add up + +51 +00:02:13,020 --> 00:02:15,930 +如果你正在接受非常密集的训练 +if you're training in a very intensive + +52 +00:02:15,930 --> 00:02:17,580 +在一个非常碳密集的地区 +in a very carbon intensive region + +53 +00:02:19,170 --> 00:02:21,750 +其他要考虑的因素,例如 +other elements to consider, for example + +54 +00:02:21,750 --> 00:02:22,770 +使用预训练模型 +using pre-trained models + +55 +00:02:22,770 --> 00:02:25,590 +这就是回收的机器学习等价物。 +that's the machine learning equivalent of recycling. + +56 +00:02:25,590 --> 00:02:28,292 +当你有可用的预训练模型时 +When you have pre-trained models available using them + +57 +00:02:28,292 --> 00:02:30,120 +你根本没有排放任何碳,对吧? +you're not emitting any carbon at all, right? + +58 +00:02:30,120 --> 00:02:31,230 +你没有再训练任何东西。 +You're not retraining anything. + +59 +00:02:31,230 --> 00:02:33,450 +所以这也在做你的功课 +So that's also doing your homework + +60 +00:02:33,450 --> 00:02:35,574 +并环顾四周已经存在的东西。 +and kind of looking around what already exists. + +61 +00:02:35,574 --> 00:02:37,890 +微调而不是从头开始训练。 +Fine tuning instead of training from scratch. + +62 +00:02:37,890 --> 00:02:38,723 +所以再一次 +So once again + +63 +00:02:38,723 --> 00:02:40,590 +如果你找到几乎是你需要的模型 +if you find a model that is almost what you need + +64 +00:02:40,590 --> 00:02:43,530 +但对最后几层的调整不是很精细 +but not quite fine tuning the last couple of layers + +65 +00:02:43,530 --> 00:02:45,210 +让它真正适合你的目的 +making it really fit your purpose instead + +66 +00:02:45,210 --> 00:02:46,500 +培训大型变压器 +of training a large transformer + +67 +00:02:46,500 --> 00:02:48,810 +从头开始真的很有帮助, +from scratch can really help, + +68 +00:02:48,810 --> 00:02:51,270 +从较小的实验开始 +starting with smaller experiments + +69 +00:02:51,270 --> 00:02:52,800 +并边调试边调试。 +and debugging as you go. + +70 +00:02:52,800 --> 00:02:54,630 +这意味着,例如,培训 +So that means, for example, training + +71 +00:02:54,630 --> 00:02:58,770 +弄清楚数据编码,弄清楚,你知道 +figuring out data encoding, figuring out, you know + +72 +00:02:58,770 --> 00:03:01,170 +确保没有小错误,你会 +making sure that there's no small bugs, that you'll + +73 +00:03:01,170 --> 00:03:03,840 +你会意识到,你知道,经过 16 个小时的训练 +you'll realize, you know, 16 hours into training + +74 +00:03:03,840 --> 00:03:05,820 +从小事做起,真正确保 +starting small and really making sure + +75 +00:03:05,820 --> 00:03:08,760 +你在做什么,你的代码是什么,是稳定的。 +that what you're doing, what your code is, is stable. + +76 +00:03:08,760 --> 00:03:11,430 +然后最后做一个文献综述 +And then finally doing a literature review to + +77 +00:03:11,430 --> 00:03:13,740 +选择超参数范围,然后跟随 +choose hyper parameter ranges and then following + +78 +00:03:13,740 --> 00:03:15,900 +使用随机搜索而不是网格搜索。 +up with a random search instead of a grid search. + +79 +00:03:15,900 --> 00:03:18,420 +所以随机搜索超参数 +So random searches for hyper parameters + +80 +00:03:18,420 --> 00:03:21,300 +组合实际上被证明是有效的 +combinations have actually shown to be as efficient + +81 +00:03:21,300 --> 00:03:24,000 +在寻找最佳配置作为网格搜索时。 +in finding the optimal configuration as grid search. + +82 +00:03:24,000 --> 00:03:27,510 +但显然你并没有尝试所有可能的组合 +But obviously you're not trying all possible combinations + +83 +00:03:27,510 --> 00:03:29,520 +你只是在尝试其中的一部分。 +you're only trying a subset of them. + +84 +00:03:29,520 --> 00:03:31,800 +所以这也很有帮助。 +So this can really help as well. + +85 +00:03:31,800 --> 00:03:32,760 +所以现在如果我们回去 +So now if we go back + +86 +00:03:32,760 --> 00:03:36,300 +2019 年 Strubell 等人的原始论文 +to the original paper by Strubell et all in 2019 + +87 +00:03:36,300 --> 00:03:39,180 +臭名昭著的五辆车在他们一生的论文中。 +the infamous five cars in their lifetimes paper. + +88 +00:03:39,180 --> 00:03:40,013 +如果你只是看 +If you just look + +89 +00:03:40,013 --> 00:03:43,606 +在一个 2 亿周边变压器的变压器处 +at a transformer of 200 million perimeter transformer + +90 +00:03:43,606 --> 00:03:46,950 +它的碳足迹约为 200 磅二氧化碳 +it is carbon footprint is around 200 pounds of CO2 + +91 +00:03:46,950 --> 00:03:47,940 +这很重要 +which is significant + +92 +00:03:47,940 --> 00:03:49,980 +但它离五辆汽车还差得很远,对吧? +but it's nowhere near five cars, right? + +93 +00:03:49,980 --> 00:03:52,893 +这甚至不是跨大西洋航班。 +It's not even a transatlantic flight. + +94 +00:03:52,893 --> 00:03:55,020 +它真正加起来的方式是当你在做的时候 +How it really adds up is when you're doing + +95 +00:03:55,020 --> 00:03:56,190 +神经架构搜索 +neural architecture search + +96 +00:03:56,190 --> 00:03:58,560 +当你进行超参数调整时,以及 +when you're doing hyper parameter tuning, and + +97 +00:03:58,560 --> 00:04:00,930 +这是在尝试所有可能的组合 +this is trying all possible combinations + +98 +00:04:00,930 --> 00:04:01,763 +等等,等等。 +et cetera, et cetera. + +99 +00:04:01,763 --> 00:04:02,596 +这是哪里 +And this is where + +100 +00:04:02,596 --> 00:04:05,400 +就像来自 600,000 磅的二氧化碳一样。 +like the 600,000 pounds of CO2 came from. + +101 +00:04:05,400 --> 00:04:08,490 +所以这真的是事情加起来的地方。 +So this is really where things add up. + +102 +00:04:08,490 --> 00:04:11,880 +所以,但如果你正念认真地做事 +So, but if you're doing things mindfully and conscientiously + +103 +00:04:11,880 --> 00:04:16,410 +那么你的碳足迹就不会那么大, +then your carbon footprint wont be as big as, + +104 +00:04:16,410 --> 00:04:20,040 +正如本文所暗示的,一些工具可以用来计算 +as the paper implied, some tools to figure + +105 +00:04:20,040 --> 00:04:22,111 +计算出你排放的 CO2 量。 +out how much CO2 exactly you're emitting. + +106 +00:04:22,111 --> 00:04:24,270 +有一个基于网络的工具叫做 machine +There's a web-based tool called machine + +107 +00:04:24,270 --> 00:04:26,430 +学习提交计算器,它可以让你 +learning submissions calculator, which allows you + +108 +00:04:26,430 --> 00:04:29,010 +手动输入,例如,你使用的硬件 +to manually input, for example, which hardware you used + +109 +00:04:29,010 --> 00:04:30,488 +你用了多少小时 +how many hours you used it for + +110 +00:04:30,488 --> 00:04:34,260 +它位于本地或云端。 +where it was located locally or in the cloud. + +111 +00:04:34,260 --> 00:04:35,640 +然后它会给你一个估计 +And then it's gonna give you an estimate + +112 +00:04:35,640 --> 00:04:37,560 +你排放了多少二氧化碳。 +of how much CO2 you emitted. + +113 +00:04:37,560 --> 00:04:40,200 +另一个以编程方式执行此操作的工具, +Another tool that does this programmatically, + +114 +00:04:40,200 --> 00:04:41,190 +称为代号碳。 +is called code carbon. + +115 +00:04:41,190 --> 00:04:45,112 +所以你可以 PIP 安装它,你可以,你可以去 GitHub +So you can PIP install it, you can, you can go to the GitHub + +116 +00:04:45,112 --> 00:04:48,120 +本质上它与你的代码并行运行。 +and essentially it runs in parallel to your code. + +117 +00:04:48,120 --> 00:04:49,085 +所以基本上你称之为 +So essentially you call it + +118 +00:04:49,085 --> 00:04:51,060 +然后你做所有的训练。 +and then you do all your training. + +119 +00:04:51,060 --> 00:04:53,760 +然后最后它会给你一个估计 +And then at the end it's gonna give you an estimate + +120 +00:04:53,760 --> 00:04:57,210 +包含排放量估算值的 CSV 文件。 +a CSV file with an estimate of your emissions. + +121 +00:04:57,210 --> 00:04:59,250 +它会给你一些比较。 +And it's gonna give you some comparisons. + +122 +00:04:59,250 --> 00:05:01,230 +它有一个可视化用户界面,你可以在其中真正看到 +It's got a visual UI where you can really look + +123 +00:05:01,230 --> 00:05:04,680 +这与开车或看电视相比如何。 +at how this compares to driving a car or watching TV. + +124 +00:05:04,680 --> 00:05:06,060 +所以它可以给你一个想法 +So it can give you an idea + +125 +00:05:06,060 --> 00:05:07,740 +你的排放范围也是如此。 +of the scope of your emissions as well. + +126 +00:05:07,740 --> 00:05:09,930 +实际上,code carbon 已经集成到汽车中 +And actually, code carbon is already integrated into auto + +127 +00:05:09,930 --> 00:05:12,270 +和 LP,希望人们会使用它 +and LP and hopefully people will be using it + +128 +00:05:12,270 --> 00:05:15,240 +开箱即用,轻松跟踪所有排放 +out of the box and easily tracking their emissions all + +129 +00:05:15,240 --> 00:05:17,523 +通过培训和部署变压器。 +through training and deploying transformers. + diff --git a/subtitles/zh-CN/03_what-is-transfer-learning.srt b/subtitles/zh-CN/03_what-is-transfer-learning.srt new file mode 100644 index 000000000..8a8a0b509 --- /dev/null +++ b/subtitles/zh-CN/03_what-is-transfer-learning.srt @@ -0,0 +1,450 @@ +1 +00:00:00,189 --> 00:00:02,856 +(空气呼啸) +(air whooshing) + +2 +00:00:05,550 --> 00:00:07,293 +- 什么是迁移学习? +- What is transfer learning? + +3 +00:00:09,480 --> 00:00:10,920 +迁移学习的思想 +The idea of transfer learning + +4 +00:00:10,920 --> 00:00:12,570 +是利用所获得的知识 +is to leverage the knowledge acquired + +5 +00:00:12,570 --> 00:00:15,543 +通过在另一项任务上使用大量数据训练的模型。 +by a model trained with lots of data on another task. + +6 +00:00:16,410 --> 00:00:20,130 +模型 A 将专门针对任务 A 进行训练。 +The model A will be trained specifically for task A. + +7 +00:00:20,130 --> 00:00:22,200 +现在假设你想训练模型 B +Now let's say you want to train a model B + +8 +00:00:22,200 --> 00:00:23,970 +为了不同的任务。 +for a different task. + +9 +00:00:23,970 --> 00:00:27,330 +一种选择是从头开始训练模型。 +One option would be to train the model from scratch. + +10 +00:00:27,330 --> 00:00:30,633 +这可能需要大量的计算、时间和数据。 +This could take lots of computation, time and data. + +11 +00:00:31,470 --> 00:00:34,260 +相反,我们可以初始化模型 B +Instead, we could initialize model B + +12 +00:00:34,260 --> 00:00:36,570 +与模型 A 具有相同的权重, +with the same weights as model A, + +13 +00:00:36,570 --> 00:00:39,213 +将模型 A 的知识转移到任务 B 上。 +transferring the knowledge of model A on task B. + +14 +00:00:41,040 --> 00:00:42,690 +从头开始训练时, +When training from scratch, + +15 +00:00:42,690 --> 00:00:45,870 +所有模型的权重都是随机初始化的。 +all the model's weight are initialized randomly. + +16 +00:00:45,870 --> 00:00:48,870 +在这个例子中,我们正在训练一个 BERT 模型 +In this example, we are training a BERT model + +17 +00:00:48,870 --> 00:00:50,220 +在识别任务上 +on the task of recognizing + +18 +00:00:50,220 --> 00:00:52,203 +两个句子是否相似。 +if two sentences are similar or not. + +19 +00:00:54,116 --> 00:00:56,730 +在左边,它是从头开始训练的, +On the left, it's trained from scratch, + +20 +00:00:56,730 --> 00:01:00,000 +在右侧,它正在微调预训练模型。 +and on the right it's fine-tuning a pretrained model. + +21 +00:01:00,000 --> 00:01:02,220 +正如我们所见,使用迁移学习 +As we can see, using transfer learning + +22 +00:01:02,220 --> 00:01:05,160 +并且预训练模型产生了更好的结果。 +and the pretrained model yields better results. + +23 +00:01:05,160 --> 00:01:07,140 +如果我们训练更长时间也没关系。 +And it doesn't matter if we train longer. + +24 +00:01:07,140 --> 00:01:10,620 +从头开始的训练准确率上限在 70% 左右 +The training from scratch is capped around 70% accuracy + +25 +00:01:10,620 --> 00:01:13,293 +而预训练模型轻松击败了 86%。 +while the pretrained model beats the 86% easily. + +26 +00:01:14,460 --> 00:01:16,140 +这是因为预训练模型 +This is because pretrained models + +27 +00:01:16,140 --> 00:01:18,420 +通常接受大量数据的训练 +are usually trained on large amounts of data + +28 +00:01:18,420 --> 00:01:21,000 +为模型提供统计理解 +that provide the model with a statistical understanding + +29 +00:01:21,000 --> 00:01:23,413 +预训练期间使用的语言。 +of the language used during pretraining. + +30 +00:01:24,450 --> 00:01:25,950 +在计算机视觉中, +In computer vision, + +31 +00:01:25,950 --> 00:01:28,080 +迁移学习已成功应用 +transfer learning has been applied successfully + +32 +00:01:28,080 --> 00:01:30,060 +将近十年。 +for almost ten years. + +33 +00:01:30,060 --> 00:01:32,850 +模型经常在 ImageNet 上进行预训练, +Models are frequently pretrained on ImageNet, + +34 +00:01:32,850 --> 00:01:36,153 +包含 120 万张照片图像的数据集。 +a dataset containing 1.2 millions of photo images. + +35 +00:01:37,170 --> 00:01:41,130 +每个图像都按 1000 个标签中的一个进行分类。 +Each image is classified by one of 1000 labels. + +36 +00:01:41,130 --> 00:01:44,010 +像这样在标记数据上训练 +Training like this, on labeled data + +37 +00:01:44,010 --> 00:01:45,663 +称为监督学习。 +is called supervised learning. + +38 +00:01:47,340 --> 00:01:49,140 +在自然语言处理中, +In Natural Language Processing, + +39 +00:01:49,140 --> 00:01:51,870 +迁移学习是最近才出现的。 +transfer learning is a bit more recent. + +40 +00:01:51,870 --> 00:01:54,480 +与 ImageNet 的一个关键区别是预训练 +A key difference with ImageNet is that the pretraining + +41 +00:01:54,480 --> 00:01:56,460 +通常是自我监督的, +is usually self-supervised, + +42 +00:01:56,460 --> 00:01:58,770 +这意味着它不需要人工注释 +which means it doesn't require humans annotations + +43 +00:01:58,770 --> 00:01:59,673 +对于标签。 +for the labels. + +44 +00:02:00,780 --> 00:02:02,700 +一个非常常见的预训练目标 +A very common pretraining objective + +45 +00:02:02,700 --> 00:02:05,310 +是猜测句子中的下一个单词。 +is to guess the next word in a sentence. + +46 +00:02:05,310 --> 00:02:07,710 +这只需要大量的文本。 +Which only requires lots and lots of text. + +47 +00:02:07,710 --> 00:02:10,710 +例如 GPT-2,就是这样预训练的 +GPT-2 for instance, was pretrained this way + +48 +00:02:10,710 --> 00:02:12,900 +使用 4500 万个链接的内容 +using the content of 45 millions links + +49 +00:02:12,900 --> 00:02:14,673 +用户在 Reddit 上发布。 +posted by users on Reddit. + +50 +00:02:16,560 --> 00:02:19,590 +自监督预训练目标的另一个例子 +Another example of self-supervised pretraining objective + +51 +00:02:19,590 --> 00:02:22,470 +是预测随机屏蔽词的值。 +is to predict the value of randomly masked words. + +52 +00:02:22,470 --> 00:02:24,540 +这类似于填空测试 +Which is similar to fill-in-the-blank tests + +53 +00:02:24,540 --> 00:02:26,760 +你可能在学校做过。 +you may have done in school. + +54 +00:02:26,760 --> 00:02:29,880 +BERT 是使用英文维基百科以这种方式进行预训练的 +BERT was pretrained this way using the English Wikipedia + +55 +00:02:29,880 --> 00:02:31,893 +和 11,000 本未出版的书籍。 +and 11,000 unpublished books. + +56 +00:02:33,120 --> 00:02:36,450 +在实践中,迁移学习应用于给定模型 +In practice, transfer learning is applied on a given model + +57 +00:02:36,450 --> 00:02:39,090 +通过扔掉它的头,也就是说, +by throwing away its head, that is, + +58 +00:02:39,090 --> 00:02:42,150 +它的最后一层专注于预训练目标, +its last layers focused on the pretraining objective, + +59 +00:02:42,150 --> 00:02:45,360 +并用一个新的、随机初始化的头替换它 +and replacing it with a new, randomly initialized head + +60 +00:02:45,360 --> 00:02:46,860 +适合手头的任务。 +suitable for the task at hand. + +61 +00:02:47,970 --> 00:02:51,570 +例如,当我们之前微调 BERT 模型时, +For instance, when we fine-tuned a BERT model earlier, + +62 +00:02:51,570 --> 00:02:54,060 +我们删除了分类掩码词的头部 +we removed the head that classified mask words + +63 +00:02:54,060 --> 00:02:56,790 +并将其替换为具有 2 个输出的分类器。 +and replaced it with a classifier with 2 outputs. + +64 +00:02:56,790 --> 00:02:58,563 +因为我们的任务有两个标签。 +Since our task had two labels. + +65 +00:02:59,700 --> 00:03:02,490 +为了尽可能高效,使用预训练模型 +To be as efficient as possible, the pretrained model used + +66 +00:03:02,490 --> 00:03:03,770 +应该尽可能相似 +should be as similar as possible + +67 +00:03:03,770 --> 00:03:06,270 +对其进行微调的任务。 +to the task it's fine-tuned on. + +68 +00:03:06,270 --> 00:03:08,190 +例如,如果问题 +For instance, if the problem + +69 +00:03:08,190 --> 00:03:10,860 +是对德语句子进行分类, +is to classify German sentences, + +70 +00:03:10,860 --> 00:03:13,053 +最好使用德国预训练模型。 +it's best to use a German pretrained model. + +71 +00:03:14,370 --> 00:03:16,649 +但好事也有坏事。 +But with the good comes the bad. + +72 +00:03:16,649 --> 00:03:19,380 +预训练模型不仅转移了它的知识, +The pretrained model does not only transfer its knowledge, + +73 +00:03:19,380 --> 00:03:21,693 +以及它可能包含的任何偏见。 +but also any bias it may contain. + +74 +00:03:22,530 --> 00:03:24,300 +ImageNet 主要包含图像 +ImageNet mostly contains images + +75 +00:03:24,300 --> 00:03:26,850 +来自美国和西欧。 +coming from the United States and Western Europe. + +76 +00:03:26,850 --> 00:03:28,020 +所以模型用它微调 +So models fine-tuned with it + +77 +00:03:28,020 --> 00:03:31,710 +通常会在来自这些国家 / 地区的图像上表现更好。 +usually will perform better on images from these countries. + +78 +00:03:31,710 --> 00:03:33,690 +OpenAI 还研究了偏差 +OpenAI also studied the bias + +79 +00:03:33,690 --> 00:03:36,120 +在其 GPT-3 模型的预测中 +in the predictions of its GPT-3 model + +80 +00:03:36,120 --> 00:03:36,953 +这是预训练的 +which was pretrained + +81 +00:03:36,953 --> 00:03:38,750 +使用猜测下一个单词目标。 +using the guess the next word objective. + +82 +00:03:39,720 --> 00:03:41,040 +更改提示的性别 +Changing the gender of the prompt + +83 +00:03:41,040 --> 00:03:44,250 +从他非常到她非常 +from he was very to she was very + +84 +00:03:44,250 --> 00:03:47,550 +改变了大多数中性形容词的预测 +changed the predictions from mostly neutral adjectives + +85 +00:03:47,550 --> 00:03:49,233 +几乎只有物理的。 +to almost only physical ones. + +86 +00:03:50,400 --> 00:03:52,367 +在他们的 GPT-2 模型的模型卡中, +In their model card of the GPT-2 model, + +87 +00:03:52,367 --> 00:03:54,990 +OpenAI 也承认它的偏见 +OpenAI also acknowledges its bias + +88 +00:03:54,990 --> 00:03:56,730 +并且不鼓励使用它 +and discourages its use + +89 +00:03:56,730 --> 00:03:58,803 +在与人类交互的系统中。 +in systems that interact with humans. + +90 +00:04:01,040 --> 00:04:03,707 +(空气呼啸) +(air whooshing) + diff --git a/subtitles/zh-CN/04_the-transformer-architecture.srt b/subtitles/zh-CN/04_the-transformer-architecture.srt new file mode 100644 index 000000000..d1b1586b0 --- /dev/null +++ b/subtitles/zh-CN/04_the-transformer-architecture.srt @@ -0,0 +1,320 @@ +1 +00:00:00,000 --> 00:00:02,750 +(徽标呼啸而过) +(logo whooshing) + +2 +00:00:05,010 --> 00:00:07,323 +- 让我们 Transformer 的架构。 +- Let's study the transformer architecture. + +3 +00:00:09,150 --> 00:00:12,030 +该视频是编码器的介绍视频, +This video is the introductory video to the encoders, + +4 +00:00:12,030 --> 00:00:15,510 +解码器和编码器 - 解码器系列视频。 +decoders, and encoder-decoder series of videos. + +5 +00:00:15,510 --> 00:00:16,343 +在这个系列中, +In this series, + +6 +00:00:16,343 --> 00:00:18,900 +我们将尝试了解是什么构成了 transformer 网络, +we'll try to understand what makes a transformer network, + +7 +00:00:18,900 --> 00:00:22,770 +我们将尝试用简单、高层次的术语来解释它。 +and we'll try to explain it in simple, high-level terms. + +8 +00:00:22,770 --> 00:00:25,800 +无需深入了解神经网络, +No advanced understanding of neural networks is necessary, + +9 +00:00:25,800 --> 00:00:29,343 +但了解基本向量和张量可能会有所帮助。 +but an understanding of basic vectors and tensors may help. + +10 +00:00:32,250 --> 00:00:33,270 +开始, +To get started, + +11 +00:00:33,270 --> 00:00:34,530 +我们将处理这张图 +we'll take up this diagram + +12 +00:00:34,530 --> 00:00:36,630 +从原来的变压器纸, +from the original transformer paper, + +13 +00:00:36,630 --> 00:00:40,140 +Vaswani 等人题为 “注意力就是你所需要的”。 +entitled "Attention Is All You Need", by Vaswani et al. + +14 +00:00:40,140 --> 00:00:41,010 +正如我们将在这里看到的, +As we'll see here, + +15 +00:00:41,010 --> 00:00:42,780 +我们只能利用它的一部分, +we can leverage only some parts of it, + +16 +00:00:42,780 --> 00:00:44,630 +根据我们正在尝试做的事情。 +according to what we're trying to do. + +17 +00:00:45,480 --> 00:00:47,610 +我们想深入到特定的层次, +We want to dive into the specific layers, + +18 +00:00:47,610 --> 00:00:48,990 +建立那个架构, +building up that architecture, + +19 +00:00:48,990 --> 00:00:51,390 +但我们会尝试理解不同的方式 +but we'll try to understand the different ways + +20 +00:00:51,390 --> 00:00:52,893 +可以使用此架构。 +this architecture can be used. + +21 +00:00:55,170 --> 00:00:56,003 +让我们先开始 +Let's first start + +22 +00:00:56,003 --> 00:00:58,260 +通过将该架构分成两部分。 +by splitting that architecture into two parts. + +23 +00:00:58,260 --> 00:00:59,910 +在左边我们有编码器, +On the left we have the encoder, + +24 +00:00:59,910 --> 00:01:01,980 +右边是解码器。 +and on the right, the decoder. + +25 +00:01:01,980 --> 00:01:03,330 +这两个可以一起使用, +These two can be used together, + +26 +00:01:03,330 --> 00:01:05,330 +但它们也可以独立使用。 +but they can also be used independently. + +27 +00:01:06,180 --> 00:01:08,610 +让我们了解这些是如何工作的。 +Let's understand how these work. + +28 +00:01:08,610 --> 00:01:11,460 +编码器接受表示文本的输入。 +The encoder accepts inputs that represent text. + +29 +00:01:11,460 --> 00:01:13,620 +它转换这个文本,这些词, +It converts this text, these words, + +30 +00:01:13,620 --> 00:01:15,675 +成数值表示。 +into numerical representations. + +31 +00:01:15,675 --> 00:01:17,400 +这些数值表示 +These numerical representations + +32 +00:01:17,400 --> 00:01:20,460 +也可以称为嵌入或特征。 +can also be called embeddings, or features. + +33 +00:01:20,460 --> 00:01:23,100 +我们会看到它使用了 self-attention 机制 +We'll see that it uses the self-attention mechanism + +34 +00:01:23,100 --> 00:01:24,483 +作为其主要组成部分。 +as its main component. + +35 +00:01:25,500 --> 00:01:27,120 +我们建议你查看视频 +We recommend you check out the video + +36 +00:01:27,120 --> 00:01:29,700 +关于编码器具体要了解 +on encoders specifically to understand + +37 +00:01:29,700 --> 00:01:31,680 +这个数字表示是什么, +what is this numerical representation, + +38 +00:01:31,680 --> 00:01:33,690 +以及它是如何工作的。 +as well as how it works. + +39 +00:01:33,690 --> 00:01:36,660 +我们将更详细地研究自注意力机制, +We'll study the self-attention mechanism in more detail, + +40 +00:01:36,660 --> 00:01:38,913 +以及它的双向属性。 +as well as its bi-directional properties. + +41 +00:01:40,650 --> 00:01:42,780 +解码器类似于编码器。 +The decoder is similar to the encoder. + +42 +00:01:42,780 --> 00:01:45,630 +它还可以接受文本输入。 +It can also accept text inputs. + +43 +00:01:45,630 --> 00:01:48,210 +它使用与编码器类似的机制, +It uses a similar mechanism as the encoder, + +44 +00:01:48,210 --> 00:01:51,150 +这也是掩蔽的自我关注。 +which is the masked self-attention as well. + +45 +00:01:51,150 --> 00:01:52,590 +它不同于编码器 +It differs from the encoder + +46 +00:01:52,590 --> 00:01:54,990 +由于其单向特性 +due to its uni-directional feature + +47 +00:01:54,990 --> 00:01:58,590 +并且传统上以自回归方式使用。 +and is traditionally used in an auto-regressive manner. + +48 +00:01:58,590 --> 00:02:01,650 +在这里,我们也建议你查看有关解码器的视频 +Here too, we recommend you check out the video on decoders + +49 +00:02:01,650 --> 00:02:04,000 +特别是要了解所有这些是如何工作的。 +especially to understand how all of this works. + +50 +00:02:06,810 --> 00:02:07,890 +结合两部分 +Combining the two parts + +51 +00:02:07,890 --> 00:02:10,200 +结果就是所谓的编码器 - 解码器, +results in what is known as an encoder-decoder, + +52 +00:02:10,200 --> 00:02:12,720 +或序列到序列转换器。 +or a sequence-to-sequence transformer. + +53 +00:02:12,720 --> 00:02:14,280 +编码器接受输入 +The encoder accepts inputs + +54 +00:02:14,280 --> 00:02:17,850 +并计算这些输入的高级表示。 +and computes a high-level representation of those inputs. + +55 +00:02:17,850 --> 00:02:20,252 +然后将这些输出传递给解码器。 +These outputs are then passed to the decoder. + +56 +00:02:20,252 --> 00:02:22,860 +解码器使用编码器的输出, +The decoder uses the encoder's output, + +57 +00:02:22,860 --> 00:02:26,370 +与其他输入一起生成预测。 +alongside other inputs to generate a prediction. + +58 +00:02:26,370 --> 00:02:27,900 +然后它预测输出, +It then predicts an output, + +59 +00:02:27,900 --> 00:02:30,248 +它将在未来的迭代中重复使用, +which it will re-use in future iterations, + +60 +00:02:30,248 --> 00:02:32,662 +因此,术语自回归。 +hence the term, auto-regressive. + +61 +00:02:32,662 --> 00:02:34,740 +最后,为了理解 +Finally, to get an understanding + +62 +00:02:34,740 --> 00:02:36,690 +编码器 - 解码器作为一个整体, +of the encoder-decoders as a whole, + +63 +00:02:36,690 --> 00:02:39,670 +我们建议你查看有关编码器 - 解码器的视频。 +we recommend you check out the video on encoder-decoders. + +64 +00:02:39,670 --> 00:02:42,420 +(徽标呼啸而过) +(logo whooshing) + diff --git a/subtitles/zh-CN/05_transformer-models-encoders.srt b/subtitles/zh-CN/05_transformer-models-encoders.srt new file mode 100644 index 000000000..7c3118cf1 --- /dev/null +++ b/subtitles/zh-CN/05_transformer-models-encoders.srt @@ -0,0 +1,510 @@ +1 +00:00:00,253 --> 00:00:03,003 +(介绍引人注目) +(intro striking) + +2 +00:00:04,440 --> 00:00:07,830 +- 在本视频中,我们将研究编码器架构。 +- In this video, we'll study the encoder architecture. + +3 +00:00:07,830 --> 00:00:11,070 +一个流行的仅编码器架构的例子是 BURT +An example of a popular encoder only architecture is BURT + +4 +00:00:11,070 --> 00:00:13,323 +这是同类产品中最受欢迎的型号。 +which is the most popular model of its kind. + +5 +00:00:14,550 --> 00:00:16,950 +让我们首先了解它是如何工作的。 +Let's first start by understanding how it works. + +6 +00:00:18,360 --> 00:00:20,910 +我们将使用一个使用三个词的小例子。 +We'll use a small example using three words. + +7 +00:00:20,910 --> 00:00:23,823 +我们使用这些作为输入并将它们传递给编码器。 +We use these as inputs and pass them through the encoder. + +8 +00:00:25,290 --> 00:00:28,173 +我们检索每个单词的数字表示。 +We retrieve a numerical representation of each word. + +9 +00:00:29,970 --> 00:00:32,700 +例如,在这里,编码器转换这三个词, +Here, for example, the encoder converts those three words, + +10 +00:00:32,700 --> 00:00:37,350 +欢迎来到纽约,在这三个数字序列中。 +welcome to NYC, in these three sequences of numbers. + +11 +00:00:37,350 --> 00:00:40,350 +编码器只输出一个数字序列 +The encoder outputs exactly one sequence of numbers + +12 +00:00:40,350 --> 00:00:41,493 +每个输入词。 +per input word. + +13 +00:00:42,330 --> 00:00:44,880 +这种数值表示也可以称为 +This numerical representation can also be called + +14 +00:00:44,880 --> 00:00:47,163 +特征向量或特征张量。 +a feature vector, or a feature tensor. + +15 +00:00:49,080 --> 00:00:51,030 +让我们深入研究这种表示。 +Let's dive into this representation. + +16 +00:00:51,030 --> 00:00:52,740 +每个词包含一个向量 +It contains one vector per word + +17 +00:00:52,740 --> 00:00:54,540 +这是通过编码器传递的。 +that was passed through the encoder. + +18 +00:00:56,130 --> 00:00:58,620 +这些向量中的每一个都是一个数字表示 +Each of these vector is a numerical representation + +19 +00:00:58,620 --> 00:01:00,033 +有问题的词。 +of the word in question. + +20 +00:01:01,080 --> 00:01:03,300 +该向量的维度被定义 +The dimension of that vector is defined + +21 +00:01:03,300 --> 00:01:05,520 +通过模型的架构。 +by the architecture of the model. + +22 +00:01:05,520 --> 00:01:08,703 +对于基本 BERT 模型,它是 768。 +For the base BERT model, it is 768. + +23 +00:01:10,650 --> 00:01:13,230 +这些表示包含一个词的值, +These representations contain the value of a word, + +24 +00:01:13,230 --> 00:01:15,240 +但语境化。 +but contextualized. + +25 +00:01:15,240 --> 00:01:18,570 +例如,归因于单词 “to” 的向量 +For example, the vector attributed to the word "to" + +26 +00:01:18,570 --> 00:01:22,290 +不只是 “to” 这个词的代表。 +isn't the representation of only the "to" word. + +27 +00:01:22,290 --> 00:01:25,650 +它还考虑了它周围的词 +It also takes into account the words around it + +28 +00:01:25,650 --> 00:01:27,363 +我们称之为上下文。 +which we call the context. + +29 +00:01:28,650 --> 00:01:30,780 +正如它在左侧上下文中所看到的那样, +As in it looks to the left context, + +30 +00:01:30,780 --> 00:01:32,970 +我们正在学习的左边的单词, +the words on the left of the one we're studying, + +31 +00:01:32,970 --> 00:01:34,980 +这里是 “欢迎” 这个词, +here the word "Welcome", + +32 +00:01:34,980 --> 00:01:37,497 +和右边的上下文,这里是 “NYC” 这个词, +and the context on the right, here the word "NYC", + +33 +00:01:38,348 --> 00:01:42,000 +并在给定上下文的情况下输出单词的值。 +and it outputs a value for the word given its context. + +34 +00:01:42,000 --> 00:01:45,420 +因此,它是一个上下文化的值。 +It is therefore a contextualized value. + +35 +00:01:45,420 --> 00:01:48,810 +可以说 768 个值的向量 +One could say that the vector of 768 values + +36 +00:01:48,810 --> 00:01:51,993 +保留文本中单词的含义。 +holds the meaning of the word within the text. + +37 +00:01:53,310 --> 00:01:56,073 +由于自我注意机制,它做到了这一点。 +It does this thanks to the self-attention mechanism. + +38 +00:01:57,240 --> 00:02:00,630 +自注意力机制涉及到不同的位置, +The self-attention mechanism relates to different positions, + +39 +00:02:00,630 --> 00:02:02,850 +或单个序列中的不同单词 +or different words in a single sequence + +40 +00:02:02,850 --> 00:02:06,003 +为了计算该序列的表示。 +in order to compute a representation of that sequence. + +41 +00:02:07,200 --> 00:02:09,000 +正如我们之前所见,这意味着 +As we've seen before, this means that + +42 +00:02:09,000 --> 00:02:11,130 +一个词的结果表示 +the resulting representation of a word + +43 +00:02:11,130 --> 00:02:13,983 +已被序列中的其他词影响。 +has been affected by other words in the sequence. + +44 +00:02:15,840 --> 00:02:18,030 +我们不会在这里深入细节 +We won't dive into the specifics here + +45 +00:02:18,030 --> 00:02:19,680 +这将提供一些进一步的阅读 +which will offer some further readings + +46 +00:02:19,680 --> 00:02:21,330 +如果你想获得更好的理解 +if you want to get a better understanding + +47 +00:02:21,330 --> 00:02:22,953 +在引擎盖下发生的事情。 +at what happens under the hood. + +48 +00:02:25,050 --> 00:02:27,480 +那么为什么要使用编码器呢? +So why should one use and encoder? + +49 +00:02:27,480 --> 00:02:29,370 +编码器可用作独立模型 +Encoders can be used as stand-alone models + +50 +00:02:29,370 --> 00:02:31,263 +在各种各样的任务中。 +in a wide variety of tasks. + +51 +00:02:32,100 --> 00:02:33,360 +例如,伯特, +For example, BERT, + +52 +00:02:33,360 --> 00:02:35,670 +可以说是最著名的变压器模型, +arguably the most famous transformer model, + +53 +00:02:35,670 --> 00:02:37,590 +是一个独立的编码器模型, +is a standalone encoder model, + +54 +00:02:37,590 --> 00:02:38,820 +并且在发布时, +and at the time of release, + +55 +00:02:38,820 --> 00:02:40,440 +这将是最先进的 +it'd be the state of the art + +56 +00:02:40,440 --> 00:02:42,780 +在许多序列分类任务中, +in many sequence classification tasks, + +57 +00:02:42,780 --> 00:02:44,190 +问答任务, +question answering tasks, + +58 +00:02:44,190 --> 00:02:46,743 +掩码语言建模仅举几例。 +and mask language modeling to only cite of few. + +59 +00:02:48,150 --> 00:02:50,460 +这个想法是编码器非常强大 +The idea is that encoders are very powerful + +60 +00:02:50,460 --> 00:02:52,470 +在提取携带载体 +at extracting vectors that carry + +61 +00:02:52,470 --> 00:02:55,350 +关于序列的有意义的信息。 +meaningful information about a sequence. + +62 +00:02:55,350 --> 00:02:57,870 +然后可以在路上处理这个向量 +This vector can then be handled down the road + +63 +00:02:57,870 --> 00:03:00,070 +通过额外的神经元来理解它们。 +by additional neurons to make sense of them. + +64 +00:03:01,380 --> 00:03:02,850 +让我们看一些例子 +Let's take a look at some examples + +65 +00:03:02,850 --> 00:03:04,563 +编码器真正闪耀的地方。 +where encoder really shine. + +66 +00:03:06,210 --> 00:03:09,900 +首先,掩码语言建模或 MLM。 +First of all, Masked Language Modeling, or MLM. + +67 +00:03:09,900 --> 00:03:11,970 +这是预测隐藏词的任务 +It's the task of predicting a hidden word + +68 +00:03:11,970 --> 00:03:13,590 +在一个单词序列中。 +in a sequence of word. + +69 +00:03:13,590 --> 00:03:15,630 +在这里,例如,我们隐藏了这个词 +Here, for example, we have hidden the word + +70 +00:03:15,630 --> 00:03:17,247 +在 “我的” 和 “是” 之间。 +between "My" and "is". + +71 +00:03:18,270 --> 00:03:21,120 +这是训练 BERT 的目标之一。 +This is one of the objectives with which BERT was trained. + +72 +00:03:21,120 --> 00:03:24,393 +它被训练来预测序列中的隐藏单词。 +It was trained to predict hidden words in a sequence. + +73 +00:03:25,230 --> 00:03:27,930 +编码器尤其在这种情况下大放异彩 +Encoders shine in this scenario in particular + +74 +00:03:27,930 --> 00:03:31,140 +因为双向信息在这里至关重要。 +as bi-directional information is crucial here. + +75 +00:03:31,140 --> 00:03:32,947 +如果我们没有右边的话, +If we didn't have the words on the right, + +76 +00:03:32,947 --> 00:03:34,650 +“是”、“Sylvain” 和 “.”, +"is", "Sylvain" and the ".", + +77 +00:03:34,650 --> 00:03:35,940 +那么机会就很小 +then there is very little chance + +78 +00:03:35,940 --> 00:03:38,580 +BERT 将能够识别名称 +that BERT would have been able to identify name + +79 +00:03:38,580 --> 00:03:40,500 +作为正确的词。 +as the correct word. + +80 +00:03:40,500 --> 00:03:42,270 +编码器需要有很好的理解 +The encoder needs to have a good understanding + +81 +00:03:42,270 --> 00:03:45,360 +序列以预测掩码词 +of the sequence in order to predict a masked word + +82 +00:03:45,360 --> 00:03:48,840 +即使文本在语法上是正确的, +as even if the text is grammatically correct, + +83 +00:03:48,840 --> 00:03:50,610 +它不一定有意义 +it does not necessarily make sense + +84 +00:03:50,610 --> 00:03:52,413 +在序列的上下文中。 +in the context of the sequence. + +85 +00:03:55,230 --> 00:03:56,580 +如前面提到的, +As mentioned earlier, + +86 +00:03:56,580 --> 00:03:59,520 +编码器擅长做序列分类。 +encoders are good at doing sequence classification. + +87 +00:03:59,520 --> 00:04:02,883 +情感分析是序列分类的一个例子。 +Sentiment analysis is an example of sequence classification. + +88 +00:04:04,410 --> 00:04:09,410 +该模型的目的是识别序列的情绪。 +The model's aim is to identify the sentiment of a sequence. + +89 +00:04:09,540 --> 00:04:11,280 +它的范围可以从给出一个序列, +It can range from giving a sequence, + +90 +00:04:11,280 --> 00:04:12,960 +从一颗星到五颗星的评级 +a rating from one to five stars + +91 +00:04:12,960 --> 00:04:15,900 +如果进行评论分析以给予肯定, +if doing review analysis to giving a positive, + +92 +00:04:15,900 --> 00:04:17,820 +或对序列的负面评价 +or negative rating to a sequence + +93 +00:04:17,820 --> 00:04:19,220 +这就是这里显示的内容。 +which is what is shown here. + +94 +00:04:20,280 --> 00:04:22,950 +例如,在这里,给定两个序列, +For example, here, given the two sequences, + +95 +00:04:22,950 --> 00:04:25,860 +我们使用模型来计算预测, +we use the model to compute a prediction, + +96 +00:04:25,860 --> 00:04:27,420 +并对序列进行分类 +and to classify the sequences + +97 +00:04:27,420 --> 00:04:30,393 +在这两个类别中,正面和负面。 +among these two classes, positive and negative. + +98 +00:04:31,230 --> 00:04:33,450 +虽然这两个序列非常相似 +While the two sequences are very similar + +99 +00:04:33,450 --> 00:04:35,220 +包含相同的词, +containing the same words, + +100 +00:04:35,220 --> 00:04:37,170 +意义完全不同, +the meaning is entirely different, + +101 +00:04:37,170 --> 00:04:40,143 +并且编码器模型能够掌握这种差异。 +and the encoder model is able to grasp that difference. + +102 +00:04:41,404 --> 00:04:44,154 +(结尾引人注目) +(outro striking) + diff --git a/subtitles/zh-CN/06_transformer-models-decoders.srt b/subtitles/zh-CN/06_transformer-models-decoders.srt new file mode 100644 index 000000000..4f81ed010 --- /dev/null +++ b/subtitles/zh-CN/06_transformer-models-decoders.srt @@ -0,0 +1,435 @@ +1 +00:00:03,750 --> 00:00:07,140 +- 在本视频中,我们将研究解码器架构。 +- In this video, we'll study the decoder architecture. + +2 +00:00:07,140 --> 00:00:07,973 +一个例子 +An example + +3 +00:00:07,973 --> 00:00:11,338 +一种流行的解码器唯一架构是 GPT 两种。 +of a popular decoder only architecture is GPT two. + +4 +00:00:11,338 --> 00:00:14,160 +为了了解解码器的工作原理 +In order to understand how decoders work + +5 +00:00:14,160 --> 00:00:17,430 +我们建议你观看有关编码器的视频。 +we recommend taking a look at the video regarding encoders. + +6 +00:00:17,430 --> 00:00:19,980 +它们与解码器极为相似。 +They're extremely similar to decoders. + +7 +00:00:19,980 --> 00:00:21,210 +可以使用解码器 +One can use a decoder + +8 +00:00:21,210 --> 00:00:23,760 +对于大多数与编码器相同的任务 +for most of the same tasks as an encoder + +9 +00:00:23,760 --> 00:00:27,330 +尽管通常会有一点性能损失。 +albeit with generally a little loss of performance. + +10 +00:00:27,330 --> 00:00:28,890 +让我们采用相同的方法 +Let's take the same approach we have taken + +11 +00:00:28,890 --> 00:00:30,300 +用编码器试试 +with the encoder to try + +12 +00:00:30,300 --> 00:00:32,670 +并了解架构差异 +and understand the architectural differences + +13 +00:00:32,670 --> 00:00:34,803 +在编码器和解码器之间。 +between an encoder and decoder. + +14 +00:00:35,777 --> 00:00:38,910 +我们将使用一个使用三个词的小例子。 +We'll use a small example using three words. + +15 +00:00:38,910 --> 00:00:41,050 +我们通过他们的解码器传递它们。 +We pass them through their decoder. + +16 +00:00:41,050 --> 00:00:44,793 +我们检索每个单词的数字表示。 +We retrieve a numerical representation for each word. + +17 +00:00:46,410 --> 00:00:49,350 +这里举例来说,解码器对三个词进行转换。 +Here for example, the decoder converts the three words. + +18 +00:00:49,350 --> 00:00:53,545 +欢迎来到纽约,欢迎来到这三个数字序列。 +Welcome to NYC, and these three sequences of numbers. + +19 +00:00:53,545 --> 00:00:56,040 +解码器只输出一个序列 +The decoder outputs exactly one sequence + +20 +00:00:56,040 --> 00:00:58,740 +每个输入词的数字。 +of numbers per input word. + +21 +00:00:58,740 --> 00:01:00,630 +这个数值表示也可以 +This numerical representation can also + +22 +00:01:00,630 --> 00:01:03,783 +称为特征向量或特征传感器。 +be called a feature vector or a feature sensor. + +23 +00:01:04,920 --> 00:01:07,200 +让我们深入了解这种表现形式。 +Let's dive in this representation. + +24 +00:01:07,200 --> 00:01:08,490 +它包含一个向量 +It contains one vector + +25 +00:01:08,490 --> 00:01:11,340 +每个通过解码器的单词。 +per word that was passed through the decoder. + +26 +00:01:11,340 --> 00:01:14,250 +这些向量中的每一个都是一个数字表示 +Each of these vectors is a numerical representation + +27 +00:01:14,250 --> 00:01:15,573 +有问题的词。 +of the word in question. + +28 +00:01:16,920 --> 00:01:18,562 +该向量的维度被定义 +The dimension of that vector is defined + +29 +00:01:18,562 --> 00:01:20,703 +通过模型的架构。 +by the architecture of the model. + +30 +00:01:22,860 --> 00:01:26,040 +解码器与编码器的主要区别在于 +Where the decoder differs from the encoder is principally + +31 +00:01:26,040 --> 00:01:28,200 +具有自我注意机制。 +with its self attention mechanism. + +32 +00:01:28,200 --> 00:01:30,843 +它使用所谓的掩蔽自我关注。 +It's using what is called masked self attention. + +33 +00:01:31,860 --> 00:01:34,650 +例如,在这里,如果我们关注 “to” 这个词 +Here, for example, if we focus on the word "to" + +34 +00:01:34,650 --> 00:01:37,620 +我们会看到 vector 是绝对未修改的 +we'll see that is vector is absolutely unmodified + +35 +00:01:37,620 --> 00:01:39,690 +用纽约的话来说。 +by the NYC word. + +36 +00:01:39,690 --> 00:01:41,731 +那是因为右边所有的话,也都知道 +That's because all the words on the right, also known + +37 +00:01:41,731 --> 00:01:45,276 +因为这个词的正确上下文被掩盖了 +as the right context of the word is masked rather + +38 +00:01:45,276 --> 00:01:49,230 +而不是受益于左右所有的话。 +than benefiting from all the words on the left and right. + +39 +00:01:49,230 --> 00:01:51,600 +所以双向上下文。 +So the bidirectional context. + +40 +00:01:51,600 --> 00:01:55,020 +解码器只能访问一个上下文 +Decoders only have access to a single context + +41 +00:01:55,020 --> 00:01:58,203 +可以是左上下文或右上下文。 +which can be the left context or the right context. + +42 +00:01:59,539 --> 00:02:03,356 +Masked self attention 机制不同 +The masked self attention mechanism differs + +43 +00:02:03,356 --> 00:02:04,320 +来自 self attention 机制 +from the self attention mechanism + +44 +00:02:04,320 --> 00:02:07,110 +通过使用额外的掩码来隐藏上下文 +by using an additional mask to hide the context + +45 +00:02:07,110 --> 00:02:09,390 +在单词的两边 +on either side of the word + +46 +00:02:09,390 --> 00:02:12,810 +单词数值表示不会受到影响 +the words numerical representation will not be affected + +47 +00:02:12,810 --> 00:02:14,853 +通过隐藏上下文中的单词。 +by the words in the hidden context. + +48 +00:02:16,260 --> 00:02:18,330 +那么什么时候应该使用解码器呢? +So when should one use a decoder? + +49 +00:02:18,330 --> 00:02:22,380 +像编码器这样的解码器可以用作独立模型 +Decoders like encoders can be used as standalone models + +50 +00:02:22,380 --> 00:02:25,020 +因为它们生成数字表示。 +as they generate a numerical representation. + +51 +00:02:25,020 --> 00:02:28,320 +它们还可以用于各种各样的任务。 +They can also be used in a wide variety of tasks. + +52 +00:02:28,320 --> 00:02:31,260 +然而,解码器的力量在于方式。 +However, the strength of a decoder lies in the way. + +53 +00:02:31,260 --> 00:02:34,530 +一个词只能访问其左侧上下文 +A word can only have access to its left context + +54 +00:02:34,530 --> 00:02:36,690 +只能访问他们的左上下文。 +having only access to their left context. + +55 +00:02:36,690 --> 00:02:39,120 +他们天生擅长文本生成 +They're inherently good at text generation + +56 +00:02:39,120 --> 00:02:41,010 +生成单词的能力 +the ability to generate a word + +57 +00:02:41,010 --> 00:02:45,000 +或给定已知单词序列的单词序列。 +or a sequence of words given a known sequence of words. + +58 +00:02:45,000 --> 00:02:45,833 +这是众所周知的 +This is known + +59 +00:02:45,833 --> 00:02:49,083 +作为因果语言建模或自然语言生成。 +as causal language modeling or natural language generation. + +60 +00:02:50,430 --> 00:02:53,520 +这是因果语言建模如何工作的示例。 +Here's an example of how causal language modeling works. + +61 +00:02:53,520 --> 00:02:56,410 +我们从一个词开始,这是我的 +We start with an initial word, which is my + +62 +00:02:57,339 --> 00:02:59,973 +我们将其用作解码器的输入。 +we use this as input for the decoder. + +63 +00:03:00,810 --> 00:03:04,260 +该模型输出一个数字向量 +The model outputs a vector of numbers + +64 +00:03:04,260 --> 00:03:07,230 +这个向量包含有关序列的信息 +and this vector contains information about the sequence + +65 +00:03:07,230 --> 00:03:08,733 +这是一个词。 +which is here a single word. + +66 +00:03:09,780 --> 00:03:11,430 +我们应用一个小的转换 +We apply a small transformation + +67 +00:03:11,430 --> 00:03:13,110 +到那个向量,以便它映射 +to that vector so that it maps + +68 +00:03:13,110 --> 00:03:16,500 +到模型已知的所有单词,这是一个映射 +to all the words known by the model, which is a mapping + +69 +00:03:16,500 --> 00:03:19,890 +我们稍后会看到称为语言建模头。 +that we'll see later called a language modeling head. + +70 +00:03:19,890 --> 00:03:21,930 +我们确定该模型相信 +We identify that the model believes + +71 +00:03:21,930 --> 00:03:25,053 +最有可能的后续单词是 name。 +that the most probable following word is name. + +72 +00:03:26,250 --> 00:03:28,710 +然后我们取那个新词并添加它 +We then take that new word and add it + +73 +00:03:28,710 --> 00:03:33,480 +到我的初始序列,我们现在以我的名字命名。 +to the initial sequence from my, we are now at my name. + +74 +00:03:33,480 --> 00:03:36,870 +这就是自回归方面的用武之地。 +This is where the auto regressive aspect comes in. + +75 +00:03:36,870 --> 00:03:38,490 +自回归模型。 +Auto regressive models. + +76 +00:03:38,490 --> 00:03:42,513 +我们使用他们过去的输出作为输入和以下步骤。 +We use their past outputs as inputs and the following steps. + +77 +00:03:43,452 --> 00:03:46,980 +我们再次执行完全相同的操作。 +Once again, we do the exact same operation. + +78 +00:03:46,980 --> 00:03:49,500 +我们通过解码器投射那个序列 +We cast that sequence through the decoder + +79 +00:03:49,500 --> 00:03:51,993 +并检索最有可能的后续词。 +and retrieve the most probable following word. + +80 +00:03:52,978 --> 00:03:57,978 +本例中就是 “是” 这个词,我们重复操作 +In this case, it is the word "is", we repeat the operation + +81 +00:03:58,230 --> 00:04:02,040 +直到我们满意为止,从一个词开始。 +until we're satisfied, starting from a single word. + +82 +00:04:02,040 --> 00:04:04,590 +我们现在已经生成了一个完整的句子。 +We've now generated a full sentence. + +83 +00:04:04,590 --> 00:04:07,890 +我们决定就此打住,但我们可以继续一段时间。 +We decide to stop there, but we could continue for a while. + +84 +00:04:07,890 --> 00:04:12,890 +例如,GPT 2 的最大上下文大小为 1,024。 +GPT two, for example, has a maximum context size of 1,024. + +85 +00:04:13,170 --> 00:04:16,830 +我们最终可以生成多达 1,024 个单词 +We could eventually generate up to a 1,024 words + +86 +00:04:16,830 --> 00:04:19,050 +并且解码器仍然会有一些记忆 +and the decoder would still have some memory + +87 +00:04:19,050 --> 00:04:21,003 +这个序列中的第一个单词。 +of the first words in this sequence. + diff --git a/subtitles/zh-CN/07_transformer-models-encoder-decoders.srt b/subtitles/zh-CN/07_transformer-models-encoder-decoders.srt new file mode 100644 index 000000000..28b847a27 --- /dev/null +++ b/subtitles/zh-CN/07_transformer-models-encoder-decoders.srt @@ -0,0 +1,710 @@ +1 +00:00:00,520 --> 00:00:02,603 +(嗖嗖) +(swoosh) + +2 +00:00:04,230 --> 00:00:05,063 +- 在这个视频中, +- In this video, + +3 +00:00:05,063 --> 00:00:07,638 +我们将研究编码器 - 解码器架构。 +we'll study the encoder-decoder architecture. + +4 +00:00:07,638 --> 00:00:12,243 +流行的编码器 - 解码器模型的一个示例是 T5。 +An example of a popular encoder-decoder model is T5. + +5 +00:00:13,770 --> 00:00:16,980 +为了理解编码器 - 解码器是如何工作的, +In order to understand how the encoder-decoder works, + +6 +00:00:16,980 --> 00:00:18,630 +我们建议你查看视频 +we recommend you check out the videos + +7 +00:00:18,630 --> 00:00:22,590 +“Encoders and Decoders as standalone models”。 +on encoders and decoders as standalone models. + +8 +00:00:22,590 --> 00:00:24,990 +了解他们如何单独工作 +Understanding how they work individually + +9 +00:00:24,990 --> 00:00:28,323 +将有助于理解编码器 - 解码器的工作原理。 +will help understanding how an encoder-decoder works. + +10 +00:00:30,510 --> 00:00:33,390 +让我们从我们所看到的编码器开始。 +Let's start from what we've seen about the encoder. + +11 +00:00:33,390 --> 00:00:36,240 +编码器将单词作为输入, +The encoder takes words as inputs, + +12 +00:00:36,240 --> 00:00:38,520 +通过编码器投射它们, +casts them through the encoder, + +13 +00:00:38,520 --> 00:00:40,800 +并检索数字表示 +and retrieves a numerical representation + +14 +00:00:40,800 --> 00:00:42,663 +对于通过它的每个单词。 +for each word cast through it. + +15 +00:00:43,560 --> 00:00:46,470 +我们现在知道这个数字表示 +We now know that this numerical representation + +16 +00:00:46,470 --> 00:00:49,473 +包含有关序列含义的信息。 +holds information about the meaning of the sequence. + +17 +00:00:51,090 --> 00:00:54,243 +让我们把这个放在一边,将解码器添加到图中。 +Let's put this aside and add the decoder to the diagram. + +18 +00:00:56,610 --> 00:00:57,510 +在这种情况下, +In this scenario, + +19 +00:00:57,510 --> 00:00:59,190 +我们以某种方式使用解码器 +we're using the decoder in a manner + +20 +00:00:59,190 --> 00:01:00,960 +我们以前没见过。 +that we haven't seen before. + +21 +00:01:00,960 --> 00:01:04,173 +我们将编码器的输出直接传递给它。 +We're passing the outputs of the encoder directly to it. + +22 +00:01:05,356 --> 00:01:07,770 +除了编码器输出, +Additionally to the encoder outputs, + +23 +00:01:07,770 --> 00:01:10,800 +我们还给解码器一个序列。 +we also give the decoder a sequence. + +24 +00:01:10,800 --> 00:01:12,840 +当提示解码器输出时 +When prompting the decoder for an output + +25 +00:01:12,840 --> 00:01:14,190 +没有初始序列, +with no initial sequence, + +26 +00:01:14,190 --> 00:01:16,140 +我们可以给它指示的值 +we can give it the value that indicates + +27 +00:01:16,140 --> 00:01:18,060 +序列的开始。 +the start of a sequence. + +28 +00:01:18,060 --> 00:01:20,919 +这就是编码器 - 解码器魔术发生的地方。 +And that's where the encoder-decoder magic happens. + +29 +00:01:20,919 --> 00:01:24,082 +编码器接受一个序列作为输入。 +The encoder accepts a sequence as input. + +30 +00:01:24,082 --> 00:01:25,980 +它计算一个预测, +It computes a prediction, + +31 +00:01:25,980 --> 00:01:28,858 +并输出一个数字表示。 +and outputs a numerical representation. + +32 +00:01:28,858 --> 00:01:33,120 +然后,它将其发送到解码器。 +Then, it sends that over to the decoder. + +33 +00:01:33,120 --> 00:01:36,300 +从某种意义上说,它编码了那个序列。 +It has, in a sense, encoded that sequence. + +34 +00:01:36,300 --> 00:01:38,130 +反过来,解码器, +And the decoder, in turn, + +35 +00:01:38,130 --> 00:01:40,847 +将此输入与其通常的序列输入一起使用, +using this input alongside its usual sequence input, + +36 +00:01:40,847 --> 00:01:43,906 +将尝试解码序列。 +will take a stab at decoding the sequence. + +37 +00:01:43,906 --> 00:01:46,530 +解码器解码序列, +The decoder decodes the sequence, + +38 +00:01:46,530 --> 00:01:48,360 +并输出一个词。 +and outputs a word. + +39 +00:01:48,360 --> 00:01:51,300 +到目前为止,我们不需要理解这个词, +As of now, we don't need to make sense of that word, + +40 +00:01:51,300 --> 00:01:53,100 +但我们可以理解解码器 +but we can understand that the decoder + +41 +00:01:53,100 --> 00:01:56,103 +本质上是解码编码器输出的内容。 +is essentially decoding what the encoder has output. + +42 +00:01:57,008 --> 00:02:00,000 +序列词的开头在这里 +The start of sequence word here + +43 +00:02:00,000 --> 00:02:02,871 +表示它应该开始解码序列。 +indicates that it should start decoding the sequence. + +44 +00:02:02,871 --> 00:02:06,870 +现在我们有了编码器的数字表示 +Now that we have both the encoder numerical representation + +45 +00:02:06,870 --> 00:02:09,570 +和一个初始生成的词, +and an initial generated word, + +46 +00:02:09,570 --> 00:02:11,343 +我们不再需要编码器了。 +we don't need the encoder anymore. + +47 +00:02:12,269 --> 00:02:15,540 +正如我们之前在解码器中看到的那样, +As we have seen before with the decoder, + +48 +00:02:15,540 --> 00:02:18,720 +它可以以自动回归的方式起作用。 +it can act in an auto-regressive manner. + +49 +00:02:18,720 --> 00:02:22,933 +它刚刚输出的单词现在可以用作输入。 +The word it has just output can now be used as an input. + +50 +00:02:22,933 --> 00:02:26,188 +这个,结合数值表示 +This, in combination with the numerical representation + +51 +00:02:26,188 --> 00:02:28,560 +编码器输出, +output by the encoder, + +52 +00:02:28,560 --> 00:02:31,203 +现在可用于生成第二个单词。 +can now be used to generate a second word. + +53 +00:02:33,040 --> 00:02:35,910 +请注意,第一个词仍然在这里, +Please note that the first word is still here, + +54 +00:02:35,910 --> 00:02:37,770 +因为模型仍然输出它。 +as the model still outputs it. + +55 +00:02:37,770 --> 00:02:39,240 +但是,我们已将其变灰 +However, we have grayed it out + +56 +00:02:39,240 --> 00:02:40,940 +因为我们不再需要它了。 +as we have no need for it anymore. + +57 +00:02:41,880 --> 00:02:44,070 +我们可以继续下去,例如, +We can continue on and on, for example, + +58 +00:02:44,070 --> 00:02:46,320 +直到解码器输出一个值 +until the decoder outputs a value + +59 +00:02:46,320 --> 00:02:48,540 +我们考虑一个停止值, +that we consider a stopping value, + +60 +00:02:48,540 --> 00:02:51,093 +就像一个点,表示序列的结尾。 +like a dot meaning the end of a sequence. + +61 +00:02:53,580 --> 00:02:55,926 +在这里,我们已经看到了完整的机制 +Here, we've seen the full mechanism + +62 +00:02:55,926 --> 00:02:57,540 +编码器 - 解码器变压器。 +of the encoder-decoder transformer. + +63 +00:02:57,540 --> 00:02:59,280 +让我们再看一遍。 +Let's go over it one more time. + +64 +00:02:59,280 --> 00:03:02,773 +我们有一个发送到编码器的初始序列。 +We have an initial sequence that is sent to the encoder. + +65 +00:03:02,773 --> 00:03:06,450 +然后将该编码器输出发送到解码器 +That encoder output is then sent to the decoder + +66 +00:03:06,450 --> 00:03:07,563 +以便对其进行解码。 +for it to be decoded. + +67 +00:03:08,760 --> 00:03:12,450 +虽然它现在可以在一次使用后丢弃编码器, +While it can now discard the encoder after a single use, + +68 +00:03:12,450 --> 00:03:14,427 +解码器将被多次使用 +the decoder will be used several times + +69 +00:03:14,427 --> 00:03:17,763 +直到我们生成了我们需要的每一个词。 +until we have generated every word that we need. + +70 +00:03:19,288 --> 00:03:21,510 +那么让我们看一个具体的例子 +So let's see a concrete example + +71 +00:03:21,510 --> 00:03:23,460 +与翻译语言建模。 +with Translation Language Modeling. + +72 +00:03:23,460 --> 00:03:24,930 +也称为转导, +Also called transduction, + +73 +00:03:24,930 --> 00:03:28,200 +这是翻译序列的行为。 +which is the act of translating a sequence. + +74 +00:03:28,200 --> 00:03:30,577 +在这里,我们想翻译这个英文序列 +Here, we would like to translate this English sequence + +75 +00:03:30,577 --> 00:03:33,067 +法语 “欢迎来到纽约”。 +"Welcome to NYC" in French. + +76 +00:03:33,067 --> 00:03:35,460 +我们正在使用变压器模型 +We're using a transformer model + +77 +00:03:35,460 --> 00:03:38,070 +明确针对该任务进行了培训。 +that is trained for that task explicitly. + +78 +00:03:38,070 --> 00:03:40,560 +我们使用编码器来创建表示 +We use the encoder to create a representation + +79 +00:03:40,560 --> 00:03:42,240 +的英语句子。 +of the English sentence. + +80 +00:03:42,240 --> 00:03:44,730 +我们把它投给解码器, +We cast this to the decoder, + +81 +00:03:44,730 --> 00:03:46,620 +使用序列字的开头, +with the use of the start of sequence word, + +82 +00:03:46,620 --> 00:03:49,173 +我们要求它输出第一个单词。 +we ask it to output the first word. + +83 +00:03:50,029 --> 00:03:53,607 +输出 bienvenue,表示欢迎。 +It outputs bienvenue, which means welcome. + +84 +00:03:53,607 --> 00:03:56,640 +然后我们使用 bienvenue +And we then use bienvenue + +85 +00:03:56,640 --> 00:03:59,283 +作为解码器的输入序列。 +as the input sequence for the decoder. + +86 +00:04:00,188 --> 00:04:04,470 +这与编码器数字表示一起, +This, alongside the encoder numerical representation, + +87 +00:04:04,470 --> 00:04:07,440 +允许解码器预测第二个词, +allows the decoder to predict the second word, Ã, + +88 +00:04:07,440 --> 00:04:09,240 +这是英文的。 +which is to in English. + +89 +00:04:09,240 --> 00:04:13,590 +最后,我们要求解码器预测第三个词 +Finally, we ask the decoder to predict a third word + +90 +00:04:13,590 --> 00:04:15,330 +它预测纽约市,这是正确的。 +It predicts NYC, which is correct. + +91 +00:04:15,330 --> 00:04:18,288 +我们已经翻译了这句话。 +We've translated the sentence. + +92 +00:04:18,288 --> 00:04:20,760 +编码器 - 解码器真正发挥作用的地方, +Where the encoder-decoder really shines, + +93 +00:04:20,760 --> 00:04:23,550 +是我们有一个编码器和一个解码器, +is that we have an encoder and a decoder, + +94 +00:04:23,550 --> 00:04:25,323 +通常不共享权重。 +which often do not share weights. + +95 +00:04:26,256 --> 00:04:29,460 +因此,我们有一个完整的块,编码器, +Therefore, we have an entire block, the encoder, + +96 +00:04:29,460 --> 00:04:31,650 +可以训练以理解序列 +that can be trained to understand the sequence + +97 +00:04:31,650 --> 00:04:34,290 +并提取相关信息。 +and extract the relevant information. + +98 +00:04:34,290 --> 00:04:36,450 +对于我们之前看到的翻译场景, +For the translation scenario we've seen earlier, + +99 +00:04:36,450 --> 00:04:38,760 +例如,这意味着解析 +for example, this would mean parsing + +100 +00:04:38,760 --> 00:04:42,003 +并理解用英语说的内容。 +and understanding what was said in the English language. + +101 +00:04:42,900 --> 00:04:45,960 +这意味着从该语言中提取信息, +It would mean extracting information from that language, + +102 +00:04:45,960 --> 00:04:49,413 +并将所有这些放在一个信息密集的向量中。 +and putting all of that in a vector dense in information. + +103 +00:04:50,361 --> 00:04:53,370 +另一方面,我们有解码器, +On the other hand, we have the decoder, + +104 +00:04:53,370 --> 00:04:56,850 +其唯一目的是解码数字表示 +whose sole purpose is to decode the numerical representation + +105 +00:04:56,850 --> 00:04:58,203 +编码器输出。 +output by the encoder. + +106 +00:04:59,460 --> 00:05:01,170 +这个解码器可以专门 +This decoder can be specialized + +107 +00:05:01,170 --> 00:05:02,970 +用完全不同的语言, +in a completely different language, + +108 +00:05:02,970 --> 00:05:05,403 +甚至像图像或语音这样的模态。 +or even modality like images or speech. + +109 +00:05:07,170 --> 00:05:10,473 +编码器 - 解码器之所以特殊,有几个原因。 +Encoders-decoders are special for several reasons. + +110 +00:05:11,310 --> 00:05:15,570 +首先,他们能够管理任务的顺序, +Firstly, they're able to manage sequence to sequence tasks, + +111 +00:05:15,570 --> 00:05:18,358 +就像我们刚刚看到的翻译一样。 +like translation that we have just seen. + +112 +00:05:18,358 --> 00:05:20,940 +其次,编码器之间的权重 +Secondly, the weights between the encoder + +113 +00:05:20,940 --> 00:05:24,540 +并且解码器部分不一定共享。 +and the decoder parts are not necessarily shared. + +114 +00:05:24,540 --> 00:05:27,172 +再举一个翻译的例子。 +Let's take another example of translation. + +115 +00:05:27,172 --> 00:05:30,810 +这里我们用法语翻译 Transformers are powerful +Here we're translating "Transformers are powerful" + +116 +00:05:30,810 --> 00:05:32,048 +这里我们用法语翻译 Transformers are powerful +in French. + +117 +00:05:32,048 --> 00:05:35,258 +首先,这意味着从三个单词的序列中, +Firstly, this means that from a sequence of three words, + +118 +00:05:35,258 --> 00:05:39,030 +我们能够生成一个包含四个单词的序列。 +we're able to generate a sequence of four words. + +119 +00:05:39,030 --> 00:05:42,480 +有人可能会争辩说这可以用解码器来处理 +One could argue that this could be handled with a decoder + +120 +00:05:42,480 --> 00:05:44,160 +那会产生翻译 +that would generate the translation + +121 +00:05:44,160 --> 00:05:46,260 +以自回归的方式, +in an auto-regressive manner, + +122 +00:05:46,260 --> 00:05:47,460 +他们是对的。 +and they would be right. + +123 +00:05:49,980 --> 00:05:51,930 +基于 Transformers 的 Seq2Seq 模型的 +Another example of where sequence to sequence + +124 +00:05:51,930 --> 00:05:54,810 +另一个亮点是总结 +transformers shine is in summarization. + +125 +00:05:54,810 --> 00:05:58,379 +这里我们有一个很长的序列,通常是全文, +Here we have a very long sequence, generally a full text, + +126 +00:05:58,379 --> 00:06:01,020 +我们想总结一下。 +and we want to summarize it. + +127 +00:06:01,020 --> 00:06:04,020 +由于编码器和解码器是分开的, +Since the encoder and decoders are separated, + +128 +00:06:04,020 --> 00:06:06,300 +我们可以有不同的上下文长度。 +we can have different context lengths. + +129 +00:06:06,300 --> 00:06:08,910 +例如,编码器的一个非常长的上下文, +For example, a very long context for the encoder, + +130 +00:06:08,910 --> 00:06:10,230 +处理文本, +which handles the text, + +131 +00:06:10,230 --> 00:06:12,210 +和解码器的较小上下文 +and a smaller context for the decoder + +132 +00:06:12,210 --> 00:06:14,223 +它处理汇总序列。 +which handles the summarized sequence. + +133 +00:06:16,470 --> 00:06:18,840 +有很多序列模型。 +There are a lot of sequence to sequence models. + +134 +00:06:18,840 --> 00:06:20,310 +这包含一些例子 +This contains a few examples + +135 +00:06:20,310 --> 00:06:22,500 +流行的编码器 - 解码器模型 +of popular encoder-decoder models + +136 +00:06:22,500 --> 00:06:24,400 +在 Transformers 库中可用。 +available in the transformers library. + +137 +00:06:25,829 --> 00:06:29,940 +此外,你可以加载编码器和解码器 +Additionally, you can load an encoder and a decoder + +138 +00:06:29,940 --> 00:06:32,130 +在编码器 - 解码器模型中。 +inside an encoder-decoder model. + +139 +00:06:32,130 --> 00:06:35,190 +因此,根据你针对的具体任务, +Therefore, according to the specific task you are targeting, + +140 +00:06:35,190 --> 00:06:38,700 +你可以选择使用特定的编码器和解码器, +you may choose to use specific encoders and decoders, + +141 +00:06:38,700 --> 00:06:42,613 +在这些特定任务中证明了它们的价值。 +which have proven their worth on these specific tasks. + +142 +00:06:42,613 --> 00:06:44,696 +(嗖嗖) +(swoosh) + diff --git a/subtitles/zh-CN/08_what-happens-inside-the-pipeline-function-(pytorch).srt b/subtitles/zh-CN/08_what-happens-inside-the-pipeline-function-(pytorch).srt new file mode 100644 index 000000000..ca6c0276f --- /dev/null +++ b/subtitles/zh-CN/08_what-happens-inside-the-pipeline-function-(pytorch).srt @@ -0,0 +1,530 @@ +1 +00:00:00,554 --> 00:00:03,304 +(徽标呼啸而过) +(logo whooshing) + +2 +00:00:05,340 --> 00:00:07,563 +- 管道函数内部发生了什么? +- What happens inside the pipeline function? + +3 +00:00:08,760 --> 00:00:11,580 +在这段视频中,我们将看看实际发生了什么 +In this video, we will look at what actually happens + +4 +00:00:11,580 --> 00:00:13,080 +当我们使用 Transformers 库的 +when we use the pipeline function + +5 +00:00:13,080 --> 00:00:15,090 +pipeline 函数时 +of the Transformers library. + +6 +00:00:15,090 --> 00:00:16,860 +更具体地说,我们将看看 +More specifically, we will look + +7 +00:00:16,860 --> 00:00:19,200 +在情绪分析管道中, +at the sentiment analysis pipeline, + +8 +00:00:19,200 --> 00:00:22,020 +以及它是如何从以下两个句子开始的, +and how it went from the two following sentences, + +9 +00:00:22,020 --> 00:00:23,970 +正负标签 +to the positive and negative labels + +10 +00:00:23,970 --> 00:00:25,420 +加上各自的分数。 +with their respective scores. + +11 +00:00:26,760 --> 00:00:29,190 +正如我们在管道演示中看到的那样, +As we have seen in the pipeline presentation, + +12 +00:00:29,190 --> 00:00:31,860 +管道分为三个阶段。 +there are three stages in the pipeline. + +13 +00:00:31,860 --> 00:00:34,620 +首先,我们将原始文本转换为数字 +First, we convert the raw texts to numbers + +14 +00:00:34,620 --> 00:00:37,173 +该模型可以理解使用分词器。 +the model can make sense of using a tokenizer. + +15 +00:00:38,010 --> 00:00:40,530 +然后这些数字通过模型, +Then those numbers go through the model, + +16 +00:00:40,530 --> 00:00:41,943 +输出逻辑。 +which outputs logits. + +17 +00:00:42,780 --> 00:00:45,600 +最后,后处理步骤变换 +Finally, the post-processing steps transforms + +18 +00:00:45,600 --> 00:00:48,150 +那些登录到标签和分数。 +those logits into labels and scores. + +19 +00:00:48,150 --> 00:00:50,700 +让我们详细看看这三个步骤 +Let's look in detail at those three steps + +20 +00:00:50,700 --> 00:00:53,640 +以及如何使用 Transformers 库复制它们, +and how to replicate them using the Transformers library, + +21 +00:00:53,640 --> 00:00:56,043 +从第一阶段开始,标记化。 +beginning with the first stage, tokenization. + +22 +00:00:57,915 --> 00:01:00,360 +令牌化过程有几个步骤。 +The tokenization process has several steps. + +23 +00:01:00,360 --> 00:01:04,950 +首先,文本被分成称为标记的小块。 +First, the text is split into small chunks called tokens. + +24 +00:01:04,950 --> 00:01:08,550 +它们可以是单词、单词的一部分或标点符号。 +They can be words, parts of words or punctuation symbols. + +25 +00:01:08,550 --> 00:01:11,580 +然后 tokenizer 将有一些特殊的标记, +Then the tokenizer will had some special tokens, + +26 +00:01:11,580 --> 00:01:13,500 +如果模型期望它们。 +if the model expect them. + +27 +00:01:13,500 --> 00:01:16,860 +这里的模型在开头使用期望 CLS 令牌 +Here the model uses expects a CLS token at the beginning + +28 +00:01:16,860 --> 00:01:19,743 +以及用于分类的句子末尾的 SEP 标记。 +and a SEP token at the end of the sentence to classify. + +29 +00:01:20,580 --> 00:01:24,180 +最后,标记器将每个标记与其唯一 ID 匹配 +Lastly, the tokenizer matches each token to its unique ID + +30 +00:01:24,180 --> 00:01:27,000 +在预训练模型的词汇表中。 +in the vocabulary of the pretrained model. + +31 +00:01:27,000 --> 00:01:28,680 +要加载这样的分词器, +To load such a tokenizer, + +32 +00:01:28,680 --> 00:01:31,743 +Transformers 库提供了 AutoTokenizer API。 +the Transformers library provides the AutoTokenizer API. + +33 +00:01:32,730 --> 00:01:36,120 +这个类最重要的方法是 from_pretrained, +The most important method of this class is from_pretrained, + +34 +00:01:36,120 --> 00:01:38,910 +这将下载并缓存配置 +which will download and cache the configuration + +35 +00:01:38,910 --> 00:01:41,853 +以及与给定检查点相关联的词汇表。 +and the vocabulary associated to a given checkpoint. + +36 +00:01:43,200 --> 00:01:45,360 +这里默认使用的 checkpoint +Here the checkpoint used by default + +37 +00:01:45,360 --> 00:01:47,280 +用于情绪分析管道 +for the sentiment analysis pipeline + +38 +00:01:47,280 --> 00:01:51,986 +是 distilbert-base-uncased-finetuned-sst-2-English。 +is distilbert-base-uncased-finetuned-sst-2-English. + +39 +00:01:51,986 --> 00:01:53,700 +(模糊) +(indistinct) + +40 +00:01:53,700 --> 00:01:56,490 +我们实例化一个与该检查点关联的分词器, +We instantiate a tokenizer associated with that checkpoint, + +41 +00:01:56,490 --> 00:01:59,490 +然后给它输入两个句子。 +then feed it the two sentences. + +42 +00:01:59,490 --> 00:02:02,100 +由于这两个句子的大小不同, +Since those two sentences are not of the same size, + +43 +00:02:02,100 --> 00:02:03,930 +我们需要填充最短的一个 +we will need to pad the shortest one + +44 +00:02:03,930 --> 00:02:06,030 +能够构建一个数组。 +to be able to build an array. + +45 +00:02:06,030 --> 00:02:09,840 +这是由标记器使用选项 padding=True 完成的。 +This is done by the tokenizer with the option, padding=True. + +46 +00:02:09,840 --> 00:02:12,810 +使用 truncation=True,我们确保任何句子 +With truncation=True, we ensure that any sentence + +47 +00:02:12,810 --> 00:02:15,873 +超过模型可以处理的最大值的长度将被截断。 +longer than the maximum the model can handle is truncated. + +48 +00:02:17,010 --> 00:02:19,620 +最后, return_tensors 选项 +Lastly, the return_tensors option + +49 +00:02:19,620 --> 00:02:22,323 +告诉分词器返回一个 PyTorch 张量。 +tells the tokenizer to return a PyTorch tensor. + +50 +00:02:23,190 --> 00:02:25,590 +查看结果,我们看到我们有一本字典 +Looking at the result, we see we have a dictionary + +51 +00:02:25,590 --> 00:02:26,670 +用两把钥匙。 +with two keys. + +52 +00:02:26,670 --> 00:02:29,970 +输入 ID 包含两个句子的 ID, +Input IDs contains the IDs of both sentences, + +53 +00:02:29,970 --> 00:02:32,550 +应用填充的位置为零。 +with zero where the padding is applied. + +54 +00:02:32,550 --> 00:02:34,260 +第二把钥匙,注意面具, +The second key, attention mask, + +55 +00:02:34,260 --> 00:02:36,150 +指示已应用填充的位置, +indicates where padding has been applied, + +56 +00:02:36,150 --> 00:02:38,940 +所以模型不会关注它。 +so the model does not pay attention to it. + +57 +00:02:38,940 --> 00:02:42,090 +这就是标记化步骤中的全部内容。 +This is all what is inside the tokenization step. + +58 +00:02:42,090 --> 00:02:46,289 +现在,让我们来看看第二步,模型。 +Now, let's have a look at the second step, the model. + +59 +00:02:46,289 --> 00:02:47,952 +至于分词器, +As for the tokenizer, + +60 +00:02:47,952 --> 00:02:51,133 +有一个带有 from_pretrained 方法的 AutoModel API。 +there is an AutoModel API with a from_pretrained method. + +61 +00:02:51,133 --> 00:02:53,954 +它将下载并缓存模型的配置 +It will download and cache the configuration of the model + +62 +00:02:53,954 --> 00:02:56,280 +以及预训练的权重。 +as well as the pretrained weights. + +63 +00:02:56,280 --> 00:02:58,200 +然而,AutoModel API +However, the AutoModel API + +64 +00:02:58,200 --> 00:03:00,630 +只会实例化模型的主体, +will only instantiate the body of the model, + +65 +00:03:00,630 --> 00:03:03,420 +那是模型剩下的部分 +that is the part of the model that is left + +66 +00:03:03,420 --> 00:03:06,090 +一旦预训练头被移除。 +once the pretraining head is removed. + +67 +00:03:06,090 --> 00:03:08,610 +它会输出一个高维张量 +It will output a high-dimensional tensor + +68 +00:03:08,610 --> 00:03:11,220 +这是通过的句子的表示, +that is a representation of the sentences passed, + +69 +00:03:11,220 --> 00:03:12,690 +但这不是直接有用的 +but which is not directly useful + +70 +00:03:12,690 --> 00:03:15,030 +对于我们的分类问题。 +for our classification problem. + +71 +00:03:15,030 --> 00:03:19,230 +这里的张量有两个句子,每个句子有 16 个标记, +Here the tensor has two sentences, each of 16 tokens, + +72 +00:03:19,230 --> 00:03:23,433 +最后一个维度是我们模型的隐藏大小,768。 +and the last dimension is the hidden size of our model, 768. + +73 +00:03:24,900 --> 00:03:27,510 +要获得与我们的分类问题相关的输出, +To get an output linked to our classification problem, + +74 +00:03:27,510 --> 00:03:31,170 +我们需要使用 AutoModelForSequenceClassification 类。 +we need to use the AutoModelForSequenceClassification class. + +75 +00:03:31,170 --> 00:03:33,330 +它与 AutoModel 类完全一样工作, +It works exactly as the AutoModel class, + +76 +00:03:33,330 --> 00:03:35,130 +除了它会建立一个模型 +except that it will build a model + +77 +00:03:35,130 --> 00:03:36,543 +带分类头。 +with a classification head. + +78 +00:03:37,483 --> 00:03:39,560 +每个常见的 NLP 任务在 Transformers 库中 +There is one auto class for each common NLP task + +79 +00:03:39,560 --> 00:03:40,960 +都有一个自动类 +in the Transformers library. + +80 +00:03:42,150 --> 00:03:45,570 +在给我们的模型两个句子之后, +Here after giving our model the two sentences, + +81 +00:03:45,570 --> 00:03:47,820 +我们得到一个大小为二乘二的张量, +we get a tensor of size two by two, + +82 +00:03:47,820 --> 00:03:50,943 +每个句子和每个可能的标签都有一个结果。 +one result for each sentence and for each possible label. + +83 +00:03:51,840 --> 00:03:53,970 +这些输出还不是概率, +Those outputs are not probabilities yet, + +84 +00:03:53,970 --> 00:03:56,100 +我们可以看到它们的总和不为 1。 +we can see they don't sum to 1. + +85 +00:03:56,100 --> 00:03:57,270 +这是因为 Transformers 库中 +This is because each model + +86 +00:03:57,270 --> 00:04:00,810 +每个模型都会返回 logits。 +of the Transformers library returns logits. + +87 +00:04:00,810 --> 00:04:02,250 +为了理解这些逻辑, +To make sense of those logits, + +88 +00:04:02,250 --> 00:04:05,910 +我们需要深入研究管道的第三步也是最后一步。 +we need to dig into the third and last step of the pipeline. + +89 +00:04:05,910 --> 00:04:10,620 +后处理,将 logits 转换为概率, +Post-processing, to convert logits into probabilities, + +90 +00:04:10,620 --> 00:04:13,470 +我们需要对它们应用 SoftMax 层。 +we need to apply a SoftMax layers to them. + +91 +00:04:13,470 --> 00:04:14,610 +正如我们所见, +As we can see, + +92 +00:04:14,610 --> 00:04:17,267 +这会将它们转换为正数 +this transforms them into positive number + +93 +00:04:17,267 --> 00:04:18,663 +总结为一个。 +that sum up to one. + +94 +00:04:18,663 --> 00:04:21,360 +最后一步是知道哪些对应 +The last step is to know which of those corresponds + +95 +00:04:21,360 --> 00:04:23,580 +正面或负面的标签。 +to the positive or the negative label. + +96 +00:04:23,580 --> 00:04:28,020 +这是由模型配置的 id2label 字段给出的。 +This is given by the id2label field of the model config. + +97 +00:04:28,020 --> 00:04:30,390 +第一概率,指数零, +The first probabilities, index zero, + +98 +00:04:30,390 --> 00:04:32,250 +对应负标签, +correspond to the negative label, + +99 +00:04:32,250 --> 00:04:34,140 +秒,索引一, +and the seconds, index one, + +100 +00:04:34,140 --> 00:04:36,480 +对应正标签。 +correspond to the positive label. + +101 +00:04:36,480 --> 00:04:37,950 +这就是我们的分类器的构建方式 +This is how our classifier built + +102 +00:04:37,950 --> 00:04:40,230 +使用管道功能选择了那些标签 +with the pipeline function picked those labels + +103 +00:04:40,230 --> 00:04:42,240 +并计算出这些分数。 +and computed those scores. + +104 +00:04:42,240 --> 00:04:44,220 +既然你知道每个步骤是如何工作的, +Now that you know how each steps works, + +105 +00:04:44,220 --> 00:04:46,220 +你可以轻松地根据需要调整它们。 +you can easily tweak them to your needs. + +106 +00:04:47,524 --> 00:04:50,274 +(徽标呼啸而过) +(logo whooshing) + diff --git a/subtitles/zh-CN/09_what-happens-inside-the-pipeline-function-(tensorflow).srt b/subtitles/zh-CN/09_what-happens-inside-the-pipeline-function-(tensorflow).srt new file mode 100644 index 000000000..1983a6ea6 --- /dev/null +++ b/subtitles/zh-CN/09_what-happens-inside-the-pipeline-function-(tensorflow).srt @@ -0,0 +1,535 @@ +1 +00:00:00,397 --> 00:00:02,980 +(微妙的爆炸) +(subtle blast) + +2 +00:00:05,490 --> 00:00:07,953 +- 管道函数内部发生了什么? +- What happens inside the pipeline function? + +3 +00:00:09,930 --> 00:00:13,050 +在这段视频中,我们将看看实际发生了什么 +In this video, we will look at what actually happens + +4 +00:00:13,050 --> 00:00:14,820 +当我们使用 Transformers 库的 +when we use the pipeline function + +5 +00:00:14,820 --> 00:00:16,920 +pipeline 函数时 +of the Transformers library. + +6 +00:00:16,920 --> 00:00:18,930 +更具体地说,我们将看看 +More specifically, we will look at + +7 +00:00:18,930 --> 00:00:21,030 +情绪分析管道, +the sentiment analysis pipeline, + +8 +00:00:21,030 --> 00:00:23,760 +以及它是如何从以下两个句子中得出的 +and how it went from the two following sentences + +9 +00:00:23,760 --> 00:00:25,800 +正负标签 +to the positive and negative labels + +10 +00:00:25,800 --> 00:00:27,250 +加上各自的分数。 +with their respective scores. + +11 +00:00:28,740 --> 00:00:31,110 +正如我们在管道视频中看到的那样, +As we have seen in the pipeline video, + +12 +00:00:31,110 --> 00:00:33,900 +管道分为三个阶段。 +there are three stages in the pipeline. + +13 +00:00:33,900 --> 00:00:36,810 +首先,我们将原始文本转换为数字 +First, we convert the raw texts to numbers + +14 +00:00:36,810 --> 00:00:39,160 +该模型可以使用分词器来理解。 +the model can make sense of, using a tokenizer. + +15 +00:00:40,140 --> 00:00:42,600 +然后,这些数字通过模型, +Then, those numbers go through the model, + +16 +00:00:42,600 --> 00:00:44,550 +输出逻辑。 +which outputs logits. + +17 +00:00:44,550 --> 00:00:47,190 +最后是后处理步骤 +Finally, the post-processing steps + +18 +00:00:47,190 --> 00:00:49,490 +将这些 logits 转换为标签和分数。 +transforms those logits into labels and score. + +19 +00:00:51,000 --> 00:00:52,590 +让我们详细看看这三个步骤, +Let's look in detail at those three steps, + +20 +00:00:52,590 --> 00:00:55,200 +以及如何使用 Transformers 库复制它们, +and how to replicate them using the Transformers library, + +21 +00:00:55,200 --> 00:00:57,903 +从第一阶段开始,标记化。 +beginning with the first stage, tokenization. + +22 +00:00:59,905 --> 00:01:02,520 +令牌化过程有几个步骤。 +The tokenization process has several steps. + +23 +00:01:02,520 --> 00:01:06,900 +首先,文本被分成称为标记的小块。 +First, the text is split into small chunks called token. + +24 +00:01:06,900 --> 00:01:09,933 +它们可以是单词、单词的一部分或标点符号。 +They can be words, parts of words or punctuation symbols. + +25 +00:01:10,800 --> 00:01:14,310 +然后 tokenizer 将有一些特殊的标记 +Then the tokenizer will had some special tokens + +26 +00:01:14,310 --> 00:01:15,573 +如果模型期望它们。 +if the model expect them. + +27 +00:01:16,440 --> 00:01:20,430 +在这里,所使用的模型在开头需要一个 CLS 令牌 +Here, the model used expects a CLS token at the beginning + +28 +00:01:20,430 --> 00:01:23,910 +以及用于分类的句子末尾的 SEP 标记。 +and a SEP token at the end of the sentence to classify. + +29 +00:01:23,910 --> 00:01:27,630 +最后,标记器将每个标记与其唯一 ID 匹配 +Lastly, the tokenizer matches each token to its unique ID + +30 +00:01:27,630 --> 00:01:29,730 +在预训练模型的词汇表中。 +in the vocabulary of the pretrained model. + +31 +00:01:30,660 --> 00:01:32,040 +要加载这样的分词器, +To load such a tokenizer, + +32 +00:01:32,040 --> 00:01:34,983 +Transformers 库提供了 AutoTokenizer API。 +the Transformers library provides the AutoTokenizer API. + +33 +00:01:35,880 --> 00:01:39,510 +这个类最重要的方法是 from_pretrained, +The most important method of this class is from_pretrained, + +34 +00:01:39,510 --> 00:01:41,940 +这将下载并缓存配置 +which will download and cache the configuration + +35 +00:01:41,940 --> 00:01:44,913 +以及与给定检查点相关联的词汇表。 +and the vocabulary associated to a given checkpoint. + +36 +00:01:46,410 --> 00:01:48,180 +这里默认使用的 checkpoint +Here, the checkpoint used by default + +37 +00:01:48,180 --> 00:01:50,310 +用于情绪分析管道 +for the sentiment analysis pipeline + +38 +00:01:50,310 --> 00:01:54,510 +是 distilbert base uncased finetuned sst2 英语, +is distilbert base uncased finetuned sst2 English, + +39 +00:01:54,510 --> 00:01:55,960 +这有点含糊。 +which is a bit of a mouthful. + +40 +00:01:56,820 --> 00:01:59,760 +我们实例化一个与该检查点关联的分词器, +We instantiate a tokenizer associated with that checkpoint, + +41 +00:01:59,760 --> 00:02:01,833 +然后给它输入两个句子。 +then feed it the two sentences. + +42 +00:02:02,790 --> 00:02:05,490 +由于这两个句子的大小不同, +Since those two sentences are not of the same size, + +43 +00:02:05,490 --> 00:02:07,440 +我们需要填充最短的一个 +we will need to pad the shortest one + +44 +00:02:07,440 --> 00:02:09,570 +能够构建一个数组。 +to be able to build an array. + +45 +00:02:09,570 --> 00:02:10,403 +这是由分词器完成的 +This is done by the tokenizer + +46 +00:02:10,403 --> 00:02:12,603 +使用选项 padding=True。 +with the option padding=True. + +47 +00:02:14,130 --> 00:02:17,340 +使用 truncation=True,我们确保任何句子更长 +With truncation=True, we ensure that any sentence longer + +48 +00:02:17,340 --> 00:02:19,953 +比模型可以处理的最大值被截断。 +than the maximum the model can handle is truncated. + +49 +00:02:20,820 --> 00:02:24,200 +最后,return_tensors 选项告诉分词器 +Lastly, the return_tensors option tells the tokenizer + +50 +00:02:24,200 --> 00:02:25,773 +返回 PyTorch 张量。 +to return a PyTorch tensor. + +51 +00:02:26,910 --> 00:02:28,050 +看看结果, +Looking at the result, + +52 +00:02:28,050 --> 00:02:30,450 +我们看到我们有一个有两个键的字典。 +we see we have a dictionary with two keys. + +53 +00:02:30,450 --> 00:02:33,840 +输入 ID 包含两个句子的 ID, +Input IDs contains the IDs of both sentences, + +54 +00:02:33,840 --> 00:02:35,840 +在应用填充的地方使用零。 +with zeros where the padding is applied. + +55 +00:02:36,750 --> 00:02:38,550 +第二把钥匙,注意面具, +The second key, attention mask, + +56 +00:02:38,550 --> 00:02:40,650 +指示已应用填充的位置, +indicates where padding has been applied, + +57 +00:02:40,650 --> 00:02:42,750 +所以模型不会关注它。 +so the model does not pay attention to it. + +58 +00:02:43,590 --> 00:02:46,380 +这就是标记化步骤中的全部内容。 +This is all what is inside the tokenization step. + +59 +00:02:46,380 --> 00:02:49,653 +现在让我们来看看第二步,模型。 +Now let's have a look at the second step, the model. + +60 +00:02:51,090 --> 00:02:53,850 +至于分词器,有一个 AutoModel API, +As for the tokenizer, there is an AutoModel API, + +61 +00:02:53,850 --> 00:02:55,890 +使用 from_pretrained 方法。 +with a from_pretrained method. + +62 +00:02:55,890 --> 00:02:59,100 +它将下载并缓存模型的配置 +It will download and cache the configuration of the model + +63 +00:02:59,100 --> 00:03:01,560 +以及预训练的权重。 +as well as the pretrained weights. + +64 +00:03:01,560 --> 00:03:04,830 +但是,AutoModel API 只会实例化 +However, the AutoModel API will only instantiate + +65 +00:03:04,830 --> 00:03:06,540 +模特的身体, +the body of the model, + +66 +00:03:06,540 --> 00:03:09,120 +也就是说,模型中剩下的部分 +that is, the part of the model that is left + +67 +00:03:09,120 --> 00:03:11,103 +一旦预训练头被移除。 +once the pretraining head is removed. + +68 +00:03:12,210 --> 00:03:14,460 +它会输出一个高维张量 +It will output a high-dimensional tensor + +69 +00:03:14,460 --> 00:03:17,190 +这是通过的句子的表示, +that is a representation of the sentences passed, + +70 +00:03:17,190 --> 00:03:18,930 +但这不是直接有用的 +but which is not directly useful + +71 +00:03:18,930 --> 00:03:20,480 +对于我们的分类问题。 +for our classification problem. + +72 +00:03:21,930 --> 00:03:24,210 +这里张量有两个句子, +Here the tensor has two sentences, + +73 +00:03:24,210 --> 00:03:26,070 +每十六个令牌, +each of sixteen token, + +74 +00:03:26,070 --> 00:03:30,393 +最后一个维度是我们模型的隐藏大小,768。 +and the last dimension is the hidden size of our model, 768. + +75 +00:03:31,620 --> 00:03:34,020 +要获得与我们的分类问题相关的输出, +To get an output linked to our classification problem, + +76 +00:03:34,020 --> 00:03:37,800 +我们需要使用 AutoModelForSequenceClassification 类。 +we need to use the AutoModelForSequenceClassification class. + +77 +00:03:37,800 --> 00:03:40,170 +它与 AutoModel 类完全一样工作, +It works exactly as the AutoModel class, + +78 +00:03:40,170 --> 00:03:41,970 +除了它会建立一个模型 +except that it will build a model + +79 +00:03:41,970 --> 00:03:43,353 +带分类头。 +with a classification head. + +80 +00:03:44,520 --> 00:03:46,770 +每个常见的 NLP 任务在 Transformers 库 +There is one auto class for each common NLP task + +81 +00:03:46,770 --> 00:03:48,170 +都有一个自动类 +in the Transformers library. + +82 +00:03:49,050 --> 00:03:52,380 +在这里,在给我们的模型两个句子之后, +Here, after giving our model the two sentences, + +83 +00:03:52,380 --> 00:03:54,600 +我们得到一个大小为二乘二的张量; +we get a tensor of size two by two; + +84 +00:03:54,600 --> 00:03:57,783 +每个句子和每个可能的标签都有一个结果。 +one result for each sentence and for each possible label. + +85 +00:03:59,100 --> 00:04:01,470 +这些输出还不是概率。 +Those outputs are not probabilities yet. + +86 +00:04:01,470 --> 00:04:03,660 +我们可以看到它们的总和不为 1。 +We can see they don't sum to 1. + +87 +00:04:03,660 --> 00:04:06,090 +这是因为 Transformers 库的每个模型 +This is because each model of the Transformers library + +88 +00:04:06,090 --> 00:04:07,830 +返回逻辑。 +returns logits. + +89 +00:04:07,830 --> 00:04:09,480 +为了理解这些逻辑, +To make sense of those logits, + +90 +00:04:09,480 --> 00:04:10,980 +我们需要深入研究第三个 +we need to dig into the third + +91 +00:04:10,980 --> 00:04:13,653 +管道的最后一步,后处理。 +and last step of the pipeline, post-processing. + +92 +00:04:15,300 --> 00:04:17,310 +要将 logits 转换为概率, +To convert logits into probabilities, + +93 +00:04:17,310 --> 00:04:19,950 +我们需要对它们应用一个 SoftMax 层。 +we need to apply a SoftMax layer to them. + +94 +00:04:19,950 --> 00:04:22,800 +正如我们所见,这会将它们转换为正数 +As we can see, this transforms them into positive numbers + +95 +00:04:22,800 --> 00:04:23,793 +总和为 1。 +that sum up to 1. + +96 +00:04:24,990 --> 00:04:27,030 +最后一步是知道哪些对应 +The last step is to know which of those corresponds + +97 +00:04:27,030 --> 00:04:29,400 +正面或负面的标签。 +to the positive or the negative label. + +98 +00:04:29,400 --> 00:04:33,480 +这是由模型配置的 id2label 字段给出的。 +This is given by the id2label field of the model config. + +99 +00:04:33,480 --> 00:04:36,000 +第一概率,索引 0, +The first probabilities, index 0, + +100 +00:04:36,000 --> 00:04:37,740 +对应负标签, +correspond to the negative label, + +101 +00:04:37,740 --> 00:04:42,060 +秒,索引 1,对应于正标签。 +and the seconds, index 1, correspond to the positive label. + +102 +00:04:42,060 --> 00:04:43,830 +这就是我们的分类器的构建方式 +This is how our classifier built + +103 +00:04:43,830 --> 00:04:46,260 +使用管道功能选择了那些标签 +with the pipeline function picked those labels + +104 +00:04:46,260 --> 00:04:47,560 +并计算出这些分数。 +and computed those scores. + +105 +00:04:48,420 --> 00:04:50,400 +既然你知道每个步骤是如何工作的, +Now that you know how each steps works, + +106 +00:04:50,400 --> 00:04:52,533 +你可以轻松地根据需要调整它们。 +you can easily tweak them to your needs. + +107 +00:04:55,314 --> 00:04:57,897 +(微妙的爆炸) +(subtle blast) + diff --git a/subtitles/zh-CN/10_instantiate-a-transformers-model-(pytorch).srt b/subtitles/zh-CN/10_instantiate-a-transformers-model-(pytorch).srt new file mode 100644 index 000000000..f058fdebf --- /dev/null +++ b/subtitles/zh-CN/10_instantiate-a-transformers-model-(pytorch).srt @@ -0,0 +1,340 @@ +1 +00:00:00,519 --> 00:00:03,186 +(标志嗖嗖声) +(logo swooshes) + +2 +00:00:05,310 --> 00:00:08,483 +- 如何实例化 Transformers 模型。 +- How to instantiate a Transformers model. + +3 +00:00:08,483 --> 00:00:11,790 +在本视频中,我们将了解如何创建用户模型 +In this video, we'll look at how we can create a user model + +4 +00:00:11,790 --> 00:00:13,290 +来自变形金刚图书馆。 +from the Transformers library. + +5 +00:00:14,310 --> 00:00:17,100 +正如我们之前看到的 AutoModel 类允许 +As we have seen before the AutoModel class allows + +6 +00:00:17,100 --> 00:00:19,140 +你实例化一个预训练模型 +you to instantiate a pretrained model + +7 +00:00:19,140 --> 00:00:21,513 +从 Hugging Face Hub 上的任何检查站。 +from any checkpoint on the Hugging Face Hub. + +8 +00:00:22,350 --> 00:00:23,910 +它会选择正确的模型类 +It'll pick the right model class + +9 +00:00:23,910 --> 00:00:26,654 +从库中实例化适当的体系结构 +from the library to instantiate the proper architecture + +10 +00:00:26,654 --> 00:00:29,793 +和大量的权重作为内部的预训练模型。 +and loads of weights as the pretrained model inside. + +11 +00:00:30,690 --> 00:00:33,810 +正如我们所见,当给定一个 BERT 检查点时 +As we can see, when given a BERT checkpoint + +12 +00:00:33,810 --> 00:00:38,043 +我们最终得到一个 BertModel,类似地,对于 GPT-2 或 BART。 +we end up with a BertModel and similarly, for GPT-2 or BART. + +13 +00:00:40,020 --> 00:00:42,360 +在幕后,这个 API 可以取名字 +Behind the scenes,this API can take the name + +14 +00:00:42,360 --> 00:00:44,250 +集线器上的检查点 +of a checkpoint on the Hub + +15 +00:00:44,250 --> 00:00:46,980 +在这种情况下,它将下载并缓存配置 +in which case it will download and cache the configuration + +16 +00:00:46,980 --> 00:00:48,843 +文件以及模型权重文件。 +file as well as a model weights file. + +17 +00:00:49,698 --> 00:00:52,710 +你还可以指定本地文件夹的路径 +You can also specify the path to a local folder + +18 +00:00:52,710 --> 00:00:55,290 +包含一个有效的配置文件和一个 +that contains a valid configuration file and a + +19 +00:00:55,290 --> 00:00:56,390 +权重文件的模型。 +model of weights file. + +20 +00:00:57,600 --> 00:00:59,479 +要实例化预训练模型, +To instantiate the pretrained model, + +21 +00:00:59,479 --> 00:01:01,950 +AutoModel API 将首先打开配置 +the AutoModel API will first open the configuration + +22 +00:01:01,950 --> 00:01:05,403 +文件来查看应该使用的配置类。 +file to look at a configuration class that should be used. + +23 +00:01:06,420 --> 00:01:08,580 +配置类取决于类型 +The configuration class depends on the type + +24 +00:01:08,580 --> 00:01:12,663 +例如模型 BERT、GPT-2 或 BART。 +of the model BERT, GPT-2 or BART for instance. + +25 +00:01:13,680 --> 00:01:15,930 +一旦它有一个合适的配置类, +Once it has a proper configuration class, + +26 +00:01:15,930 --> 00:01:18,390 +它可以实例化该配置 +it can instantiate that configuration + +27 +00:01:18,390 --> 00:01:21,900 +这是了解如何创建模型的蓝图。 +which is a blueprint to know how to create the model. + +28 +00:01:21,900 --> 00:01:24,240 +它还使用这个配置类来 +It also uses this configuration class to + +29 +00:01:24,240 --> 00:01:27,150 +找到合适的模型类,然后合并 +find the proper model class, which is then combined + +30 +00:01:27,150 --> 00:01:29,823 +使用加载的配置加载模型。 +with the loaded configuration to load the model. + +31 +00:01:30,904 --> 00:01:33,210 +该模型还不是预训练模型 +This model is not yet a pretrained model + +32 +00:01:33,210 --> 00:01:35,883 +因为它刚刚用随机权重初始化。 +as it has just been initialized with random weights. + +33 +00:01:36,840 --> 00:01:39,810 +最后一步是从模型文件加载权重 +The last step is to load the weight from the model file + +34 +00:01:39,810 --> 00:01:40,923 +在这个模型里面。 +inside this model. + +35 +00:01:42,330 --> 00:01:44,250 +轻松加载模型的配置 +To easily load the configuration of a model + +36 +00:01:44,250 --> 00:01:46,410 +从任何检查点或文件夹包含 +from any checkpoint or folder containing + +37 +00:01:46,410 --> 00:01:48,210 +配置文件。 +the configuration file. + +38 +00:01:48,210 --> 00:01:50,373 +我们可以使用 AutoConfig 类。 +We can use the AutoConfig class. + +39 +00:01:51,240 --> 00:01:52,693 +像 AutoModel 类一样, +Like the AutoModel class, + +40 +00:01:52,693 --> 00:01:55,693 +它将从库中选择正确的配置类。 +it will pick the right configuration class from the library. + +41 +00:01:57,060 --> 00:01:59,220 +我们也可以使用特定的类对应 +We can also use a specific class corresponding + +42 +00:01:59,220 --> 00:02:01,470 +到一个检查站,但我们需要改变 +to a checkpoint, but we will need to change + +43 +00:02:01,470 --> 00:02:03,000 +每次我们想尝试的代码 +the code each time we want to try + +44 +00:02:03,000 --> 00:02:04,550 +不同的模型架构。 +a different model architecture. + +45 +00:02:06,030 --> 00:02:07,860 +正如我们之前所说,配置 +As we said before, the configuration + +46 +00:02:07,860 --> 00:02:10,350 +模型的蓝图包含所有 +of a model is a blueprint that contains all the + +47 +00:02:10,350 --> 00:02:13,830 +创建模型架构所需的信息。 +information necessary to create the model architecture. + +48 +00:02:13,830 --> 00:02:15,990 +例如,关联的 BERT 模型 +For instance, the BERT model associated + +49 +00:02:15,990 --> 00:02:19,980 +bert-base-cased 检查点有 12 层, +with the bert-base-cased checkpoint has 12 layers, + +50 +00:02:19,980 --> 00:02:24,980 +768 的隐藏面和 28,996 的词汇面。 +a hidden side of 768 and a vocabulary side of 28,996. + +51 +00:02:28,020 --> 00:02:29,910 +一旦我们有了配置, +Once we have the configuration, + +52 +00:02:29,910 --> 00:02:31,950 +我们可以创建一个具有相同架构的模型 +we can create a model that does the same architecture + +53 +00:02:31,950 --> 00:02:35,280 +作为我们的检查点,但是是随机初始化的。 +as our checkpoint, but is randomly initialized. + +54 +00:02:35,280 --> 00:02:36,660 +然后我们可以从头开始训练它。 +We can then train it from scratch. + +55 +00:02:36,660 --> 00:02:38,010 +像任何生物 PyTorch 模块一样 +Like any bio PyTorch module + +56 +00:02:39,497 --> 00:02:40,380 +我们也可以改变任何部分 +We can also change any part + +57 +00:02:40,380 --> 00:02:43,200 +通过使用关键字参数的配置。 +of the configuration by using keyword arguments. + +58 +00:02:43,200 --> 00:02:46,138 +第二段代码实例化 +The second snippet of code instantiates + +59 +00:02:46,138 --> 00:02:48,360 +随机初始化的 BERT 模型 +a randomly initialized BERT model + +60 +00:02:48,360 --> 00:02:50,403 +有 10 层而不是 12 层。 +with 10 layers instead of 12. + +61 +00:02:51,409 --> 00:02:55,051 +训练或微调后保存模型非常容易。 +Saving a model once it's trained or fine-tuned is very easy. + +62 +00:02:55,051 --> 00:02:57,603 +我们只需要使用一种安全的预训练方法。 +We just have to use a safe pretrained method. + +63 +00:02:58,500 --> 00:03:01,417 +此处模型将保存在名为 +Here the model will be saved in a folder named + +64 +00:03:01,417 --> 00:03:04,473 +当前工作目录中的 “my-bert-model”。 +"my-bert-model" inside the current working directory. + +65 +00:03:05,400 --> 00:03:08,255 +然后可以使用表单重新加载这样的模型 +Such a model can then be reloaded using the form + +66 +00:03:08,255 --> 00:03:09,596 +预训练方法。 +pretrained method. + +67 +00:03:09,596 --> 00:03:11,250 +了解如何轻松处理此模型 +To learn how to easily approach this model + +68 +00:03:11,250 --> 00:03:13,473 +为此,请查看对视频的推送。 +to that, check out the push to a video. + diff --git a/subtitles/zh-CN/11_instantiate-a-transformers-model-(tensorflow).srt b/subtitles/zh-CN/11_instantiate-a-transformers-model-(tensorflow).srt new file mode 100644 index 000000000..c14d2347f --- /dev/null +++ b/subtitles/zh-CN/11_instantiate-a-transformers-model-(tensorflow).srt @@ -0,0 +1,355 @@ +1 +00:00:00,125 --> 00:00:02,958 +(嘶嘶声) +(whooshing sound) + +2 +00:00:05,463 --> 00:00:08,820 +- 如何实例化 Transformers 模型? +- How to instantiate the Transformers model? + +3 +00:00:08,820 --> 00:00:11,250 +在本视频中,我们将了解如何创建 +In this video, we will look at how we can create + +4 +00:00:11,250 --> 00:00:13,550 +并使用 Transformers 库中的模型。 +and use a model from the Transformers library. + +5 +00:00:15,000 --> 00:00:17,850 +正如我们之前看到的,TFAutoModel 类 +As we've seen before, the TFAutoModel class + +6 +00:00:17,850 --> 00:00:20,100 +允许你实例化预训练模型 +allows you to instantiate a pre-trained model + +7 +00:00:20,100 --> 00:00:22,503 +从 Hugging Face Hub 上的任何检查站。 +from any checkpoint on the Hugging Face Hub. + +8 +00:00:23,430 --> 00:00:25,620 +它将从库中选择正确的模型类 +It will pick the right model class from the library + +9 +00:00:25,620 --> 00:00:27,750 +实例化适当的架构 +to instantiate the proper architecture + +10 +00:00:27,750 --> 00:00:31,200 +并在里面加载预训练模型的权重。 +and load the weights of the pre-trained model inside. + +11 +00:00:31,200 --> 00:00:34,020 +正如我们所见,当给定一个 BERT 检查点时, +As we can see, when given a BERT checkpoint, + +12 +00:00:34,020 --> 00:00:36,090 +我们最终得到一个 TFBertModel, +we end up with a TFBertModel, + +13 +00:00:36,090 --> 00:00:38,553 +GPT2 或 BART 也类似。 +and similarly for GPT2 or BART. + +14 +00:00:40,170 --> 00:00:42,510 +在幕后,这个 API 可以取名字 +Behind the scenes, this API can take the name + +15 +00:00:42,510 --> 00:00:44,040 +集线器上的检查点, +of a checkpoint on the Hub, + +16 +00:00:44,040 --> 00:00:45,810 +在这种情况下,它将下载并缓存 +in which case it will download and cache + +17 +00:00:45,810 --> 00:00:48,660 +配置文件以及模型权重文件。 +the configuration file as well as the model weights file. + +18 +00:00:49,590 --> 00:00:52,020 +你还可以指定本地文件夹的路径 +You can also specify the path to a local folder + +19 +00:00:52,020 --> 00:00:54,090 +包含有效的配置文件 +that contains a valid configuration file + +20 +00:00:54,090 --> 00:00:55,340 +和模型权重文件。 +and a model weights file. + +21 +00:00:56,670 --> 00:00:58,167 +要实例化预训练模型, +To instantiate the pre-trained model, + +22 +00:00:58,167 --> 00:01:02,400 +TFAutoModel API 将首先打开配置文件 +the TFAutoModel API will first open the configuration file + +23 +00:01:02,400 --> 00:01:05,253 +查看应该使用的配置类。 +to look at the configuration class that should be used. + +24 +00:01:06,390 --> 00:01:09,660 +配置类取决于模型的类型, +The configuration class depends on the type of the model, + +25 +00:01:09,660 --> 00:01:12,333 +例如 BERT、GPT2 或 BART。 +BERT, GPT2 or BART for instance. + +26 +00:01:13,320 --> 00:01:15,720 +一旦它有了正确的配置类, +Once it has the proper configuration class, + +27 +00:01:15,720 --> 00:01:18,000 +它可以实例化该配置, +it can instantiate that configuration, + +28 +00:01:18,000 --> 00:01:21,090 +这是了解如何创建模型的蓝图。 +which is a blueprint to know how to create the model. + +29 +00:01:21,090 --> 00:01:22,770 +它也使用这个配置类 +It also uses this configuration class + +30 +00:01:22,770 --> 00:01:24,750 +找到合适的模型类, +to find the proper model class, + +31 +00:01:24,750 --> 00:01:27,120 +它与加载的配置相结合 +which is combined with the loaded configuration + +32 +00:01:27,120 --> 00:01:28,143 +加载模型。 +to load the model. + +33 +00:01:29,250 --> 00:01:31,800 +这个模型还不是我们的预训练模型 +This model is not yet our pre-trained model + +34 +00:01:31,800 --> 00:01:34,560 +因为它刚刚用随机权重初始化。 +as it has just been initialized with random weights. + +35 +00:01:34,560 --> 00:01:36,690 +最后一步是加载权重 +The last step is to load the weights + +36 +00:01:36,690 --> 00:01:38,973 +来自该模型中的模型文件。 +from the model file inside this model. + +37 +00:01:40,230 --> 00:01:42,270 +轻松加载模型的配置 +To easily load the configuration of a model + +38 +00:01:42,270 --> 00:01:44,220 +从任何检查点或文件夹 +from any checkpoint or a folder + +39 +00:01:44,220 --> 00:01:46,170 +包含配置文件, +containing the configuration file, + +40 +00:01:46,170 --> 00:01:47,790 +我们可以使用 AutoConfig 类。 +we can use the AutoConfig class. + +41 +00:01:47,790 --> 00:01:50,460 +与 TFAutoModel 类一样, +Like the TFAutoModel class, + +42 +00:01:50,460 --> 00:01:54,210 +它将从库中选择正确的配置类。 +it will pick the right configuration class from the library. + +43 +00:01:54,210 --> 00:01:56,040 +我们也可以使用特定的类 +We can also use the specific class + +44 +00:01:56,040 --> 00:01:57,840 +对应一个检查点, +corresponding to a checkpoint, + +45 +00:01:57,840 --> 00:01:59,430 +但我们需要更改代码 +but we will need to change the code + +46 +00:01:59,430 --> 00:02:02,230 +每次我们都想尝试不同的模型架构。 +each time we want to try a different model architecture. + +47 +00:02:03,180 --> 00:02:05,353 +正如我们之前所说,模型的配置 +As we said before, the configuration of a model + +48 +00:02:05,353 --> 00:02:08,610 +是包含所有必要信息的蓝图 +is a blueprint that contains all the information necessary + +49 +00:02:08,610 --> 00:02:11,070 +创建模型架构。 +to create the model architecture. + +50 +00:02:11,070 --> 00:02:12,750 +例如,BERT 模型 +For instance, the BERT model + +51 +00:02:12,750 --> 00:02:15,510 +与 bert-base-cased 检查点关联 +associated with the bert-base-cased checkpoint + +52 +00:02:15,510 --> 00:02:19,710 +有 12 层,隐藏大小为 768, +has 12 layers, a hidden size of 768, + +53 +00:02:19,710 --> 00:02:23,403 +词汇量为 28,996。 +and a vocabulary size of 28,996. + +54 +00:02:24,810 --> 00:02:26,670 +一旦我们有了配置, +Once we have the configuration, + +55 +00:02:26,670 --> 00:02:28,890 +我们可以创建一个具有相同架构的模型 +we can create a model that has the same architecture + +56 +00:02:28,890 --> 00:02:32,160 +作为我们的检查点,但随机初始化。 +as our checkpoint but is randomly initialized. + +57 +00:02:32,160 --> 00:02:36,030 +然后我们可以像任何 TensorFlow 模型一样从头开始训练它。 +We can then train it from scratch like any TensorFlow model. + +58 +00:02:36,030 --> 00:02:38,063 +我们还可以更改配置的任何部分 +We can also change any part of the configuration + +59 +00:02:38,063 --> 00:02:40,770 +通过使用关键字参数。 +by using keyword arguments. + +60 +00:02:40,770 --> 00:02:43,110 +第二段代码实例化 +The second snippet of code instantiates + +61 +00:02:43,110 --> 00:02:44,970 +随机初始化的 BERT 模型 +a randomly initialized BERT model + +62 +00:02:44,970 --> 00:02:46,983 +有 10 层而不是 12 层。 +with 10 layers instead of 12. + +63 +00:02:48,240 --> 00:02:51,360 +训练或微调后保存模型非常容易。 +Saving a model once it's trained or fine-tuned is very easy. + +64 +00:02:51,360 --> 00:02:53,880 +我们只需要使用 save_pretrained 方法。 +We just have to use the save_pretrained method. + +65 +00:02:53,880 --> 00:02:55,980 +在这里,模型将保存在一个文件夹中 +Here, the model will be saved in a folder + +66 +00:02:55,980 --> 00:02:59,463 +在当前工作目录中命名为 my-bert-model。 +named my-bert-model inside the current working directory. + +67 +00:03:00,480 --> 00:03:02,250 +然后可以重新加载这样的模型 +Such a model can then be reloaded + +68 +00:03:02,250 --> 00:03:04,500 +使用 from_pretrained 方法。 +using the from_pretrained method. + +69 +00:03:04,500 --> 00:03:06,600 +要将其运行到 Hub 的项目模型, +To run it to a projects model to the Hub, + +70 +00:03:06,600 --> 00:03:08,350 +查看推送(咕哝)视频。 +check out the push (mumbles) video. + +71 +00:03:09,355 --> 00:03:12,188 +(嘶嘶声) +(whooshing sound) + diff --git a/subtitles/zh-CN/12_tokenizers-overview.srt b/subtitles/zh-CN/12_tokenizers-overview.srt new file mode 100644 index 000000000..92326f9b7 --- /dev/null +++ b/subtitles/zh-CN/12_tokenizers-overview.srt @@ -0,0 +1,115 @@ +1 +00:00:00,450 --> 00:00:01,509 +(介绍呼呼) +(intro whooshing) + +2 +00:00:01,509 --> 00:00:02,720 +(笑脸拍打) +(smiley snapping) + +3 +00:00:02,720 --> 00:00:03,930 +(话语嘶哑) +(words whooshing) + +4 +00:00:03,930 --> 00:00:04,920 +- 在接下来的几个视频中, +- In the next few videos, + +5 +00:00:04,920 --> 00:00:06,720 +我们将看一下分词器。 +we'll take a look at the tokenizers. + +6 +00:00:07,860 --> 00:00:09,240 +在自然语言处理中, +In natural language processing, + +7 +00:00:09,240 --> 00:00:12,930 +我们处理的大部分数据都是原始文本。 +most of the data that we handle consists of raw text. + +8 +00:00:12,930 --> 00:00:14,280 +然而,机器学习模型 +However, machine learning models + +9 +00:00:14,280 --> 00:00:17,103 +无法阅读或理解原始形式的文本, +cannot read or understand text in its raw form, + +10 +00:00:18,540 --> 00:00:20,253 +他们只能使用数字。 +they can only work with numbers. + +11 +00:00:21,360 --> 00:00:23,220 +所以分词器的目标 +So the tokenizer's objective + +12 +00:00:23,220 --> 00:00:25,923 +将文本翻译成数字。 +will be to translate the text into numbers. + +13 +00:00:27,600 --> 00:00:30,240 +这种转换有几种可能的方法, +There are several possible approaches to this conversion, + +14 +00:00:30,240 --> 00:00:31,110 +和目标 +and the objective + +15 +00:00:31,110 --> 00:00:33,453 +就是找到最有意义的表示。 +is to find the most meaningful representation. + +16 +00:00:36,240 --> 00:00:39,390 +我们将看看三种不同的标记化算法。 +We'll take a look at three distinct tokenization algorithms. + +17 +00:00:39,390 --> 00:00:40,530 +我们一对一比较, +We compare them one to one, + +18 +00:00:40,530 --> 00:00:42,600 +所以我们建议你看一下视频 +so we recommend you take a look at the videos + +19 +00:00:42,600 --> 00:00:44,040 +按以下顺序。 +in the following order. + +20 +00:00:44,040 --> 00:00:45,390 +首先,“基于单词”, +First, "Word-based," + +21 +00:00:45,390 --> 00:00:46,800 +其次是 “基于字符”, +followed by "Character-based," + +22 +00:00:46,800 --> 00:00:48,877 +最后,“基于子词”。 +and finally, "Subword-based." + +23 +00:00:48,877 --> 00:00:51,794 +(结尾嘶嘶声) +(outro whooshing) + diff --git a/subtitles/zh-CN/13_word-based-tokenizers.srt b/subtitles/zh-CN/13_word-based-tokenizers.srt new file mode 100644 index 000000000..2fcf95891 --- /dev/null +++ b/subtitles/zh-CN/13_word-based-tokenizers.srt @@ -0,0 +1,300 @@ +1 +00:00:00,165 --> 00:00:01,416 +(屏幕呼啸) +(screen whooshing) + +2 +00:00:01,416 --> 00:00:02,716 +(贴纸弹出) +(sticker popping) + +3 +00:00:02,716 --> 00:00:03,549 +(屏幕呼啸) +(screen whooshing) + +4 +00:00:03,549 --> 00:00:05,603 +- 让我们来看看基于单词的分词。 +- Let's take a look at word-based tokenization. + +5 +00:00:07,650 --> 00:00:09,780 +基于单词的标记化是这个想法 +Word-based tokenization is the idea + +6 +00:00:09,780 --> 00:00:11,940 +将原始文本拆分成单词 +of splitting the raw text into words + +7 +00:00:11,940 --> 00:00:14,673 +通过按空格或其他特定规则拆分, +by splitting on spaces or other specific rules, + +8 +00:00:16,020 --> 00:00:17,163 +像标点符号。 +like punctuation. + +9 +00:00:18,900 --> 00:00:21,810 +在这个算法中,每个单词都有一个特定的数字 +In this algorithm, each word has a specific number + +10 +00:00:21,810 --> 00:00:23,463 +或归因于它的 ID。 +or ID attributed to it. + +11 +00:00:24,360 --> 00:00:27,270 +在这里,我们有 ID 250, +Here, let's has the ID 250, + +12 +00:00:27,270 --> 00:00:30,150 +确实有 861,并且标记化 +do has 861, and tokenization + +13 +00:00:30,150 --> 00:00:33,393 +后面跟感叹号的有 345。 +followed by an exclamation mark has 345. + +14 +00:00:34,380 --> 00:00:36,000 +这个方法很有趣 +This approach is interesting + +15 +00:00:36,000 --> 00:00:38,100 +因为模型有表示 +as the model has representations + +16 +00:00:38,100 --> 00:00:40,233 +是基于整个单词的。 +that are based on entire words. + +17 +00:00:42,720 --> 00:00:45,960 +单个号码所持有的信息量高, +The information held in a single number is high, + +18 +00:00:45,960 --> 00:00:48,240 +因为一个词包含很多上下文 +as a word contains a lot of contextual + +19 +00:00:48,240 --> 00:00:49,803 +和语义信息。 +and semantic information. + +20 +00:00:53,070 --> 00:00:55,473 +然而,这种方法确实有其局限性。 +However, this approach does have its limits. + +21 +00:00:56,610 --> 00:01:00,570 +比如 dog 这个词和 dogs 这个词很相似 +For example, the word dog and the word dogs are very similar + +22 +00:01:00,570 --> 00:01:01,923 +他们的意思很接近。 +and their meaning is close. + +23 +00:01:03,210 --> 00:01:05,550 +然而,基于单词的标记化, +The word-based tokenization, however, + +24 +00:01:05,550 --> 00:01:08,520 +会给这两个词赋予完全不同的 ID +will attribute entirely different IDs to these two words + +25 +00:01:08,520 --> 00:01:10,110 +因此模型将学习 +and the model will therefore learn + +26 +00:01:10,110 --> 00:01:12,930 +这两个词的两个不同的嵌入。 +two different embeddings for these two words. + +27 +00:01:12,930 --> 00:01:15,090 +这很不幸,因为我们想要这个模型 +This is unfortunate as we would like the model + +28 +00:01:15,090 --> 00:01:18,240 +了解这些词确实相关, +to understand that these words are indeed related, + +29 +00:01:18,240 --> 00:01:21,483 +而 dogs 只是 dog 这个词的复数形式。 +and that dogs is simply the plural form of the word dog. + +30 +00:01:22,980 --> 00:01:24,480 +这种方法的另一个问题是, +Another issue with this approach, + +31 +00:01:24,480 --> 00:01:28,050 +是语言中有很多不同的词。 +is that there are a lot of different words in the language. + +32 +00:01:28,050 --> 00:01:29,490 +如果我们想让我们的模型理解 +If we want our model to understand + +33 +00:01:29,490 --> 00:01:32,160 +该语言中所有可能的句子, +all possible sentences in that language, + +34 +00:01:32,160 --> 00:01:35,850 +那么我们需要为每个不同的词设置一个 ID。 +then we will need to have an ID for each different word. + +35 +00:01:35,850 --> 00:01:37,380 +以及总字数, +And the total number of words, + +36 +00:01:37,380 --> 00:01:40,080 +也称为词汇量大小, +which is also known as the vocabulary size, + +37 +00:01:40,080 --> 00:01:41,913 +可以很快变得非常大。 +can quickly become very large. + +38 +00:01:44,400 --> 00:01:47,640 +这是一个问题,因为每个 ID 都映射到一个大向量 +This is an issue because each ID is mapped to a large vector + +39 +00:01:47,640 --> 00:01:50,190 +代表这个词的意思, +that represents the word's meaning, + +40 +00:01:50,190 --> 00:01:52,170 +并跟踪这些映射 +and keeping track of these mappings + +41 +00:01:52,170 --> 00:01:54,990 +需要大量的权重 +requires an enormous number of weights + +42 +00:01:54,990 --> 00:01:57,123 +当词汇量很大时。 +when the vocabulary size is very large. + +43 +00:01:59,160 --> 00:02:00,960 +如果我们希望我们的模型保持精简, +If we want our models to stay lean, + +44 +00:02:00,960 --> 00:02:04,440 +我们可以选择让分词器忽略某些词 +we can opt for our tokenizer to ignore certain words + +45 +00:02:04,440 --> 00:02:06,093 +我们不一定需要。 +that we don't necessarily need. + +46 +00:02:08,400 --> 00:02:11,970 +例如,在这里,当在文本上训练我们的分词器时, +For example, here, when training our tokenizer on a text, + +47 +00:02:11,970 --> 00:02:15,020 +我们可能只想使用 10,000 个最常用的单词 +we might want to take only the 10,000 most frequent words + +48 +00:02:15,020 --> 00:02:16,320 +在该文本中。 +in that text. + +49 +00:02:16,320 --> 00:02:18,600 +而不是从该文本中提取所有单词 +Rather than taking all words from in that text + +50 +00:02:18,600 --> 00:02:22,503 +或所有语言的单词来创建我们的基本词汇。 +or all languages words to create our basic vocabulary. + +51 +00:02:23,790 --> 00:02:26,520 +分词器将知道如何转换这 10,000 个单词 +The tokenizer will know how to convert those 10,000 words + +52 +00:02:26,520 --> 00:02:29,370 +转换成数字,但任何其他词都会被转换 +into numbers, but any other word will be converted + +53 +00:02:29,370 --> 00:02:31,530 +到词汇外的词, +to the out-of-vocabulary word, + +54 +00:02:31,530 --> 00:02:33,783 +或者像这里显示的那样,未知的词。 +or like shown here, the unknown word. + +55 +00:02:35,280 --> 00:02:37,440 +不幸的是,这是一种妥协。 +Unfortunately, this is a compromise. + +56 +00:02:37,440 --> 00:02:39,900 +该模型将具有完全相同的表示 +The model will have the exact same representation + +57 +00:02:39,900 --> 00:02:42,390 +对于它不知道的所有单词, +for all words that it doesn't know, + +58 +00:02:42,390 --> 00:02:45,210 +这可能会导致大量信息丢失 +which can result in a lot of lost information + +59 +00:02:45,210 --> 00:02:47,664 +如果存在许多未知单词。 +if many unknown words are present. + +60 +00:02:47,664 --> 00:02:50,581 +(屏幕呼啸) +(screen whooshing) + diff --git a/subtitles/zh-CN/14_character-based-tokenizers.srt b/subtitles/zh-CN/14_character-based-tokenizers.srt new file mode 100644 index 000000000..0b07937f9 --- /dev/null +++ b/subtitles/zh-CN/14_character-based-tokenizers.srt @@ -0,0 +1,305 @@ +1 +00:00:00,234 --> 00:00:02,901 +(翻页) +(page whirring) + +2 +00:00:04,260 --> 00:00:07,200 +- 在深入研究基于字符的标记化之前, +- Before diving in character-based tokenization, + +3 +00:00:07,200 --> 00:00:10,350 +理解为什么这种标记化很有趣 +understanding why this kind of tokenization is interesting + +4 +00:00:10,350 --> 00:00:13,533 +需要了解基于单词的标记化的缺陷。 +requires understanding the flaws of word-based tokenization. + +5 +00:00:14,640 --> 00:00:16,320 +如果你还没有看过第一个视频 +If you haven't seen the first video + +6 +00:00:16,320 --> 00:00:17,880 +基于词的分词 +on word-based tokenization + +7 +00:00:17,880 --> 00:00:21,450 +我们建议你在观看此视频之前检查一下。 +we recommend you check it out before looking at this video. + +8 +00:00:21,450 --> 00:00:24,250 +好的,让我们看一下基于字符的标记化。 +Okay, let's take a look at character-based tokenization. + +9 +00:00:25,650 --> 00:00:28,560 +我们现在将文本拆分为单个字符, +We now split our text into individual characters, + +10 +00:00:28,560 --> 00:00:29,673 +而不是文字。 +rather than words. + +11 +00:00:32,850 --> 00:00:35,550 +语言中通常有很多不同的词, +There are generally a lot of different words in languages, + +12 +00:00:35,550 --> 00:00:37,743 +而字符数保持较低。 +while the number of characters stays low. + +13 +00:00:38,610 --> 00:00:41,313 +首先让我们看一下英语, +To begin let's take a look at the English language, + +14 +00:00:42,210 --> 00:00:45,540 +它估计有 170,000 个不同的词, +it has an estimated 170,000 different words, + +15 +00:00:45,540 --> 00:00:47,730 +所以我们需要非常大的词汇量 +so we would need a very large vocabulary + +16 +00:00:47,730 --> 00:00:49,413 +包含所有单词。 +to encompass all words. + +17 +00:00:50,280 --> 00:00:52,200 +使用基于字符的词汇表, +With a character-based vocabulary, + +18 +00:00:52,200 --> 00:00:55,440 +我们可以只用 256 个字符, +we can get by with only 256 characters, + +19 +00:00:55,440 --> 00:00:58,683 +其中包括字母、数字和特殊字符。 +which includes letters, numbers and special characters. + +20 +00:00:59,760 --> 00:01:02,190 +即使是有很多不同字符的语言 +Even languages with a lot of different characters + +21 +00:01:02,190 --> 00:01:04,800 +就像中文可以有字典一样 +like the Chinese languages can have dictionaries + +22 +00:01:04,800 --> 00:01:08,130 +多达 20,000 个不同的字符 +with up to 20,000 different characters + +23 +00:01:08,130 --> 00:01:11,523 +但超过 375,000 个不同的单词。 +but more than 375,000 different words. + +24 +00:01:12,480 --> 00:01:14,310 +所以基于字符的词汇 +So character-based vocabularies + +25 +00:01:14,310 --> 00:01:16,293 +让我们使用更少的不同标记 +let us use fewer different tokens + +26 +00:01:16,293 --> 00:01:19,050 +比基于单词的分词词典 +than the word-based tokenization dictionaries + +27 +00:01:19,050 --> 00:01:20,523 +否则我们会使用。 +we would otherwise use. + +28 +00:01:23,250 --> 00:01:25,830 +这些词汇也比较全 +These vocabularies are also more complete + +29 +00:01:25,830 --> 00:01:28,950 +比他们基于单词的词汇对应物。 +than their word-based vocabularies counterparts. + +30 +00:01:28,950 --> 00:01:31,410 +由于我们的词汇表包含所有字符 +As our vocabulary contains all characters + +31 +00:01:31,410 --> 00:01:33,960 +用在一种语言中,甚至是看不见的词 +used in a language, even words unseen + +32 +00:01:33,960 --> 00:01:36,990 +在分词器训练期间仍然可以分词, +during the tokenizer training can still be tokenized, + +33 +00:01:36,990 --> 00:01:39,633 +因此词汇表外的标记将不那么频繁。 +so out-of-vocabulary tokens will be less frequent. + +34 +00:01:40,680 --> 00:01:42,840 +这包括正确标记化的能力 +This includes the ability to correctly tokenize + +35 +00:01:42,840 --> 00:01:45,210 +拼错的单词,而不是丢弃它们 +misspelled words, rather than discarding them + +36 +00:01:45,210 --> 00:01:46,623 +立即未知。 +as unknown straight away. + +37 +00:01:48,240 --> 00:01:52,380 +然而,这个算法也不完美。 +However, this algorithm isn't perfect either. + +38 +00:01:52,380 --> 00:01:54,360 +直觉上,字符不成立 +Intuitively, characters do not hold + +39 +00:01:54,360 --> 00:01:57,990 +一个词所能包含的信息量。 +as much information individually as a word would hold. + +40 +00:01:57,990 --> 00:02:00,930 +例如,“让我们” 包含更多信息 +For example, "Let's" holds more information + +41 +00:02:00,930 --> 00:02:03,570 +比它的第一个字母 “l”。 +than it's first letter "l". + +42 +00:02:03,570 --> 00:02:05,880 +当然,并非所有语言都如此, +Of course, this is not true for all languages, + +43 +00:02:05,880 --> 00:02:08,880 +作为一些语言,比如基于表意文字的语言 +as some languages like ideogram-based languages + +44 +00:02:08,880 --> 00:02:11,523 +有很多信息保存在单个字符中, +have a lot of information held in single characters, + +45 +00:02:12,750 --> 00:02:15,360 +但对于其他像基于罗马的语言, +but for others like roman-based languages, + +46 +00:02:15,360 --> 00:02:17,760 +该模型必须理解多个标记 +the model will have to make sense of multiple tokens + +47 +00:02:17,760 --> 00:02:20,670 +一次获取以其他方式持有的信息 +at a time to get the information otherwise held + +48 +00:02:20,670 --> 00:02:21,753 +一句话。 +in a single word. + +49 +00:02:23,760 --> 00:02:27,000 +这导致了基于字符的分词器的另一个问题, +This leads to another issue with character-based tokenizers, + +50 +00:02:27,000 --> 00:02:29,520 +他们的序列被翻译成非常大量 +their sequences are translated into very large amount + +51 +00:02:29,520 --> 00:02:31,593 +模型要处理的标记数。 +of tokens to be processed by the model. + +52 +00:02:33,090 --> 00:02:36,810 +这会对上下文的大小产生影响 +And this can have an impact on the size of the context + +53 +00:02:36,810 --> 00:02:40,020 +该模型将随身携带,并会减小尺寸 +the model will carry around, and will reduce the size + +54 +00:02:40,020 --> 00:02:42,030 +我们可以用作模型输入的文本, +of the text we can use as input for our model, + +55 +00:02:42,030 --> 00:02:43,233 +这通常是有限的。 +which is often limited. + +56 +00:02:44,100 --> 00:02:46,650 +这种标记化虽然存在一些问题, +This tokenization, while it has some issues, + +57 +00:02:46,650 --> 00:02:48,720 +在过去看到了一些非常好的结果 +has seen some very good results in the past + +58 +00:02:48,720 --> 00:02:50,490 +所以在接近时应该考虑 +and so it should be considered when approaching + +59 +00:02:50,490 --> 00:02:52,680 +解决问题的新问题 +a new problem as it solves issues + +60 +00:02:52,680 --> 00:02:54,843 +在基于词的算法中遇到。 +encountered in the word-based algorithm. + +61 +00:02:56,107 --> 00:02:58,774 +(翻页) +(page whirring) + diff --git a/subtitles/zh-CN/15_subword-based-tokenizers.srt b/subtitles/zh-CN/15_subword-based-tokenizers.srt new file mode 100644 index 000000000..2fa836c93 --- /dev/null +++ b/subtitles/zh-CN/15_subword-based-tokenizers.srt @@ -0,0 +1,360 @@ +1 +00:00:06,450 --> 00:00:09,540 +- 让我们来看看基于子词的分词。 +- Let's take a look at subword based tokenization. + +2 +00:00:09,540 --> 00:00:11,610 +了解为什么基于子词的分词是 +Understanding why subword based tokenization is + +3 +00:00:11,610 --> 00:00:13,980 +有趣需要理解缺陷 +interesting requires understanding the flaws + +4 +00:00:13,980 --> 00:00:17,340 +基于单词和基于校正器的标记化。 +of word based and corrector based tokenization. + +5 +00:00:17,340 --> 00:00:18,780 +如果你还没有看过第一个视频 +If you haven't seen the first videos + +6 +00:00:18,780 --> 00:00:22,020 +基于单词和基于字符的标记化 +on word based and character based tokenization + +7 +00:00:22,020 --> 00:00:23,130 +我们建议你检查它们 +we recommend you check them + +8 +00:00:23,130 --> 00:00:24,780 +在看这个视频之前。 +out before looking at this video. + +9 +00:00:27,840 --> 00:00:31,493 +基于子词的标记化介于基于字符之间 +Subword based tokenization lies in between character based + +10 +00:00:31,493 --> 00:00:35,280 +和基于词的分词算法。 +and word based tokenization algorithms. + +11 +00:00:35,280 --> 00:00:37,410 +这个想法是找到一个中间立场 +The idea is to find a middle ground + +12 +00:00:37,410 --> 00:00:39,486 +在非常大的词汇表之间 +between very large vocabularies + +13 +00:00:39,486 --> 00:00:42,600 +大量的词汇标记 +a large quantity of out vocabulary tokens + +14 +00:00:42,600 --> 00:00:45,360 +并且在非常相似的词中失去意义 +and a loss of meaning across very similar words + +15 +00:00:45,360 --> 00:00:48,630 +用于基于词的分词器和非常长的序列 +for word based tokenizers and very long sequences + +16 +00:00:48,630 --> 00:00:51,330 +以及意义不大的单个标记。 +as well as less meaningful individual tokens. + +17 +00:00:51,330 --> 00:00:53,133 +对于基于字符的分词器。 +For character based tokenizers. + +18 +00:00:54,840 --> 00:00:57,960 +这些算法依赖于以下原则。 +These algorithms rely on the following principle. + +19 +00:00:57,960 --> 00:01:00,000 +常用词不宜拆分 +Frequently used words should not be split + +20 +00:01:00,000 --> 00:01:01,500 +分成更小的子词 +into smaller subwords + +21 +00:01:01,500 --> 00:01:03,433 +而稀有词应该被分解 +while rare words should be decomposed + +22 +00:01:03,433 --> 00:01:05,103 +成有意义的子词。 +into meaningful subwords. + +23 +00:01:06,510 --> 00:01:08,460 +一个例子是狗这个词。 +An example is the word dog. + +24 +00:01:08,460 --> 00:01:11,190 +我们想让我们的分词器有一个单一的 ID +We would like to have our tokenizer to have a single ID + +25 +00:01:11,190 --> 00:01:12,600 +对于狗这个词 +for the word dog rather + +26 +00:01:12,600 --> 00:01:15,363 +而不是将其拆分为校正器 DO 和 G。 +than splitting it into correctors D O and G. + +27 +00:01:16,650 --> 00:01:19,260 +然而,当遇到狗这个词时 +However, when encountering the word dogs + +28 +00:01:19,260 --> 00:01:22,710 +我们希望我们的标记化从根本上理解这一点 +we would like our tokenize to understand that at the root + +29 +00:01:22,710 --> 00:01:24,120 +这还是狗这个词。 +this is still the word dog. + +30 +00:01:24,120 --> 00:01:27,030 +添加 S 后,意思略有改变 +With an added S, that slightly changes the meaning + +31 +00:01:27,030 --> 00:01:28,923 +同时保持最初的想法。 +while keeping the original idea. + +32 +00:01:30,600 --> 00:01:34,080 +另一个例子是像标记化这样的复杂词 +Another example is a complex word like tokenization + +33 +00:01:34,080 --> 00:01:37,140 +可以拆分成有意义的子词。 +which can be split into meaningful subwords. + +34 +00:01:37,140 --> 00:01:37,973 +根 +The root + +35 +00:01:37,973 --> 00:01:40,590 +这个词的是记号,-ization 完成根 +of the word is token and -ization completes the root + +36 +00:01:40,590 --> 00:01:42,870 +赋予它稍微不同的含义。 +to give it a slightly different meaning. + +37 +00:01:42,870 --> 00:01:44,430 +拆分这个词是有道理的 +It makes sense to split the word + +38 +00:01:44,430 --> 00:01:47,640 +一分为二,token 作为词根, +into two, token as the root of the word, + +39 +00:01:47,640 --> 00:01:49,950 +标记为单词的开头 +labeled as the start of the word + +40 +00:01:49,950 --> 00:01:52,530 +和化作为标记的附加信息 +and ization as additional information labeled + +41 +00:01:52,530 --> 00:01:54,393 +作为单词的完成。 +as a completion of the word. + +42 +00:01:55,826 --> 00:01:58,740 +反过来,该模型现在将能够有意义 +In turn, the model will now be able to make sense + +43 +00:01:58,740 --> 00:02:01,080 +不同情况下的令牌。 +of token in different situations. + +44 +00:02:01,080 --> 00:02:04,602 +它会理解这个词的 token, tokens, tokenizing +It will understand that the word's token, tokens, tokenizing + +45 +00:02:04,602 --> 00:02:08,760 +和标记化具有相似的含义并且是相关联的。 +and tokenization have a similar meaning and are linked. + +46 +00:02:08,760 --> 00:02:12,450 +它还将理解标记化、现代化 +It's will also understand that tokenization, modernization + +47 +00:02:12,450 --> 00:02:16,200 +和免疫,它们都有相同的后缀 +and immunization, which all have the same suffixes + +48 +00:02:16,200 --> 00:02:19,383 +可能在相同的句法情况下使用。 +are probably used in the same syntactic situations. + +49 +00:02:20,610 --> 00:02:23,130 +基于子词的分词器通常有办法 +Subword based tokenizers generally have a way to + +50 +00:02:23,130 --> 00:02:25,890 +识别哪些标记是单词的开头 +identify which tokens are a start of word + +51 +00:02:25,890 --> 00:02:28,443 +以及哪些标记完成单词的开头。 +and which tokens complete start of words. + +52 +00:02:29,520 --> 00:02:31,140 +所以这里以令牌为开始 +So here token as the start + +53 +00:02:31,140 --> 00:02:35,100 +病房和哈希哈希化作为奖励的完成。 +of a ward and hash hash ization as completion of award. + +54 +00:02:35,100 --> 00:02:38,103 +这里 hash 哈希前缀表示化是一部分 +Here, the hash hash prefix indicates that ization is part + +55 +00:02:38,103 --> 00:02:41,013 +奖项而不是它的开始。 +of award rather than the beginning of it. + +56 +00:02:41,910 --> 00:02:43,110 +hash 散列来了 +The hash hash comes + +57 +00:02:43,110 --> 00:02:47,013 +来自基于词片算法的 BERT 分词器。 +from the BERT tokenizer based on the word piece algorithm. + +58 +00:02:47,850 --> 00:02:50,700 +其他标记化使用其他前缀可以是 +Other tokenizes use other prefixes which can be + +59 +00:02:50,700 --> 00:02:52,200 +放置以表示单词的一部分 +placed to indicate part of words + +60 +00:02:52,200 --> 00:02:55,083 +喜欢在这里或单词的开头。 +like in here or start of words instead. + +61 +00:02:56,250 --> 00:02:57,083 +有很多 +There are a lot + +62 +00:02:57,083 --> 00:02:58,740 +可以使用的不同算法 +of different algorithms that can be used + +63 +00:02:58,740 --> 00:03:00,090 +用于子词标记化 +for subword tokenization + +64 +00:03:00,090 --> 00:03:02,670 +大多数模型都获得了最先进的结果 +and most models obtaining state-of-the-art results + +65 +00:03:02,670 --> 00:03:03,780 +今日英语 +in English today + +66 +00:03:03,780 --> 00:03:06,663 +使用某种子词标记化算法。 +use some kind of subword tokenization algorithms. + +67 +00:03:07,620 --> 00:03:10,953 +这些方法有助于减少词汇量 +These approaches help in reducing the vocabulary sizes + +68 +00:03:10,953 --> 00:03:13,636 +通过跨不同的词共享信息 +by sharing information across different words + +69 +00:03:13,636 --> 00:03:15,960 +有前缀的能力 +having the ability to have prefixes + +70 +00:03:15,960 --> 00:03:18,630 +和后缀这样理解。 +and suffixes understood as such. + +71 +00:03:18,630 --> 00:03:20,700 +他们在非常相似的词中保留意义 +They keep meaning across very similar words + +72 +00:03:20,700 --> 00:03:23,103 +通过识别相似的标记,将它们组合起来。 +by recognizing similar tokens, making them up. + diff --git a/subtitles/zh-CN/16_the-tokenization-pipeline.srt b/subtitles/zh-CN/16_the-tokenization-pipeline.srt new file mode 100644 index 000000000..a359fd213 --- /dev/null +++ b/subtitles/zh-CN/16_the-tokenization-pipeline.srt @@ -0,0 +1,380 @@ +1 +00:00:00,479 --> 00:00:03,396 +(物体呼啸) +(object whooshing) + +2 +00:00:05,610 --> 00:00:06,873 +- 分词器管道。 +- The tokenizer pipeline. + +3 +00:00:07,920 --> 00:00:10,570 +在本视频中,我们将了解分词器如何将 +In this video, we'll look at how a tokenizer converts + +4 +00:00:11,433 --> 00:00:12,480 +原始文本到数字, +raw texts to numbers, + +5 +00:00:12,480 --> 00:00:14,970 +Transformer 模型可以理解, +that a Transformer model can make sense of, + +6 +00:00:14,970 --> 00:00:16,520 +就像我们执行这段代码时一样。 +like when we execute this code. + +7 +00:00:17,760 --> 00:00:18,690 +这是一个快速概述 +Here is a quick overview + +8 +00:00:18,690 --> 00:00:21,630 +tokenizer 对象内部发生的事情: +of what happens inside the tokenizer object: + +9 +00:00:21,630 --> 00:00:24,360 +首先,文本被分成标记, +first, the text is split into tokens, + +10 +00:00:24,360 --> 00:00:27,453 +它们是单词、单词的一部分或标点符号。 +which are words, parts of words, or punctuation symbols. + +11 +00:00:28,440 --> 00:00:31,500 +然后标记器添加潜在的特殊标记 +Then the tokenizer adds potential special tokens + +12 +00:00:31,500 --> 00:00:34,680 +并将每个令牌转换为各自唯一的 ID +and converts each token to their unique respective ID + +13 +00:00:34,680 --> 00:00:36,843 +由分词器的词汇表定义。 +as defined by the tokenizer's vocabulary. + +14 +00:00:37,710 --> 00:00:40,380 +正如我们将要看到的,它并不是按照这个顺序发生的, +As we'll see, it doesn't quite happen in this order, + +15 +00:00:40,380 --> 00:00:43,233 +但这样做更利于理解。 +but doing it like this is better for understandings. + +16 +00:00:44,280 --> 00:00:47,670 +第一步是将我们的输入文本拆分为标记。 +The first step is to split our input text into tokens. + +17 +00:00:47,670 --> 00:00:49,653 +为此,我们使用 tokenize 方法。 +We use the tokenize method for this. + +18 +00:00:50,550 --> 00:00:54,030 +为此,分词器可能首先执行一些操作, +To do that, the tokenizer may first perform some operations, + +19 +00:00:54,030 --> 00:00:56,880 +喜欢将所有单词小写,然后遵循一组规则 +like lowercasing all words, then follow a set of rules + +20 +00:00:56,880 --> 00:00:59,283 +将结果分成小块文本。 +to split the result in small chunks of text. + +21 +00:01:00,480 --> 00:01:02,286 +大多数 Transformer 模型使用 +Most of the Transformer models uses + +22 +00:01:02,286 --> 00:01:04,890 +单词标记化算法,这意味着 +a word tokenization algorithm, which means + +23 +00:01:04,890 --> 00:01:06,750 +一个给定的单词可以被拆分 +that one given word can be split + +24 +00:01:06,750 --> 00:01:10,050 +在几个标记中,例如 tokenize here。 +in several tokens like tokenize here. + +25 +00:01:10,050 --> 00:01:12,570 +查看下面的“标记化算法”视频链接 +Look at the "Tokenization algorithms" video link below + +26 +00:01:12,570 --> 00:01:13,743 +了解更多信息。 +for more information. + +27 +00:01:14,760 --> 00:01:17,820 +我们在ize前面看到的##前缀是 +The # # prefix we see in front of ize is + +28 +00:01:17,820 --> 00:01:19,830 +Bert 用来表示的约定 +a convention used by Bert to indicate + +29 +00:01:19,830 --> 00:01:22,762 +这个标记不是单词的开头。 +this token is not the beginning of the word. + +30 +00:01:22,762 --> 00:01:26,310 +然而,其他分词器可能使用不同的约定: +Other tokenizers may use different conventions however: + +31 +00:01:26,310 --> 00:01:29,984 +例如,ALBERT 分词器会添加一个长下划线 +for instance, ALBERT tokenizers will add a long underscore + +32 +00:01:29,984 --> 00:01:31,620 +在所有令牌前 +in front of all the tokens + +33 +00:01:31,620 --> 00:01:34,920 +在他们之前增加了空间,这是一个共同的约定 +that added space before them, which is a convention shared + +34 +00:01:34,920 --> 00:01:37,700 +由所有句子分词器。 +by all sentencepiece tokenizers. + +35 +00:01:38,580 --> 00:01:41,040 +标记化管道的第二步是 +The second step of the tokenization pipeline is + +36 +00:01:41,040 --> 00:01:43,470 +将这些令牌映射到它们各自的 ID +to map those tokens to their respective IDs + +37 +00:01:43,470 --> 00:01:45,770 +由分词器的词汇定义。 +as defined by the vocabulary of the tokenizer. + +38 +00:01:46,770 --> 00:01:48,690 +这就是我们需要下载文件的原因 +This is why we need to download the file + +39 +00:01:48,690 --> 00:01:50,580 +当我们实例化一个分词器时 +when we instantiate a tokenizer + +40 +00:01:50,580 --> 00:01:52,400 +使用 from_pretrained 方法。 +with the from_pretrained method. + +41 +00:01:52,400 --> 00:01:54,390 +我们必须确保我们使用相同的映射 +We have to make sure we use the same mapping + +42 +00:01:54,390 --> 00:01:56,520 +就像模型被预训练时一样。 +as when the model was pretrained. + +43 +00:01:56,520 --> 00:01:59,703 +为此,我们使用 convert_tokens_to_ids 方法。 +To do this, we use the convert_tokens_to_ids method. + +44 +00:02:01,050 --> 00:02:01,883 +我们可能已经注意到 +We may have noticed + +45 +00:02:01,883 --> 00:02:03,540 +我们没有完全相同的结果 +that we don't have the exact same results + +46 +00:02:03,540 --> 00:02:05,580 +就像我们的第一张幻灯片一样 +as in our first slide, or not + +47 +00:02:05,580 --> 00:02:07,920 +因为这看起来像是一个随机数列表, +as this looks like a list of random numbers anyway, + +48 +00:02:07,920 --> 00:02:10,680 +在这种情况下,请允许我重温一下你的记忆。 +in which case, allow me to refresh your memory. + +49 +00:02:10,680 --> 00:02:12,350 +我们有一个开头的数字和一个数字 +We had a the number at the beginning and a number + +50 +00:02:12,350 --> 00:02:17,130 +最后缺少的是特殊标记。 +at the end that are missing, those are the special tokens. + +51 +00:02:17,130 --> 00:02:20,340 +特殊标记由 prepare_for_model 方法添加 +The special tokens are added by the prepare_for_model method + +52 +00:02:20,340 --> 00:02:22,350 +它知道这个令牌的索引 +which knows the indices of this token + +53 +00:02:22,350 --> 00:02:25,680 +在词汇表中并添加适当的数字。 +in the vocabulary and just adds the proper numbers. + +54 +00:02:25,680 --> 00:02:27,243 +在输入 ID 列表中。 +in the input IDs list. + +55 +00:02:28,590 --> 00:02:29,541 +你可以查看特殊标记 +You can look at the special tokens + +56 +00:02:29,541 --> 00:02:30,990 +更一般地说, +and, more generally, + +57 +00:02:30,990 --> 00:02:33,870 +关于分词器如何更改你的文本, +at how the tokenizer has changed your text, + +58 +00:02:33,870 --> 00:02:35,280 +通过使用解码方法 +by using the decode method + +59 +00:02:35,280 --> 00:02:37,503 +在分词器对象的输出上。 +on the outputs of the tokenizer object. + +60 +00:02:38,490 --> 00:02:39,423 +至于开头的前缀 +As for the prefix for beginning + +61 +00:02:39,423 --> 00:02:44,160 +词的/词的一部分,特殊标记因情况而异 +of words/ part of words, for special tokens vary depending + +62 +00:02:44,160 --> 00:02:46,500 +你正在使用哪个分词器。 +on which tokenizer you're using. + +63 +00:02:46,500 --> 00:02:48,810 +因此分词器使用 CLS 和 SEP, +So that tokenizer uses CLS and SEP, + +64 +00:02:48,810 --> 00:02:52,417 +但是 roberta tokenizer 使用类似 HTML 的锚点 +but the roberta tokenizer uses HTML-like anchors + +65 +00:02:52,417 --> 00:02:55,230 + + and . + +66 +00:02:55,230 --> 00:02:57,090 +既然你知道分词器是如何工作的, +Now that you know how the tokenizer works, + +67 +00:02:57,090 --> 00:02:59,390 +你可以忘记所有那些中间方法, +you can forget all those intermediate methods, + +68 +00:03:00,283 --> 00:03:01,650 +然后你记得你只需要调用它 +and then you remember that you just have to call it + +69 +00:03:01,650 --> 00:03:02,913 +在你的输入文本上。 +on your input texts. + +70 +00:03:03,870 --> 00:03:05,310 +分词器的输出不 +The output of a tokenizer don't + +71 +00:03:05,310 --> 00:03:07,853 +但是,只包含输入 ID。 +just contain the input IDs, however. + +72 +00:03:07,853 --> 00:03:09,750 +要了解注意力掩码是什么, +To learn what the attention mask is, + +73 +00:03:09,750 --> 00:03:12,360 +查看“一起批量输入”视频。 +check out the "Batch input together" video. + +74 +00:03:12,360 --> 00:03:14,220 +要了解令牌类型 ID, +To learn about token type IDs, + +75 +00:03:14,220 --> 00:03:16,570 +观看“处理句子对”视频。 +look at the "Process pairs of sentences" video. + +76 +00:03:18,003 --> 00:03:20,920 +(物体呼啸) +(object whooshing) + diff --git a/subtitles/zh-CN/17_batching-inputs-together-(pytorch).srt b/subtitles/zh-CN/17_batching-inputs-together-(pytorch).srt new file mode 100644 index 000000000..b4fdd4d2b --- /dev/null +++ b/subtitles/zh-CN/17_batching-inputs-together-(pytorch).srt @@ -0,0 +1,331 @@ +1 +00:00:00,373 --> 00:00:02,956 +(微妙的爆炸) +(subtle blast) + +2 +00:00:05,400 --> 00:00:07,590 +- 如何一起批量输入。 +- How to batch inputs together. + +3 +00:00:07,590 --> 00:00:09,240 +在本视频中,我们将看到如何 +In this video, we will see how + +4 +00:00:09,240 --> 00:00:11,073 +将输入序列一起批处理。 +to batch input sequences together. + +5 +00:00:12,137 --> 00:00:15,420 +一般来说,我们想要通过我们的模型的句子 +In general, the sentences we want to pass through our model + +6 +00:00:15,420 --> 00:00:17,670 +不会都有相同的长度。 +won't all have the same lengths. + +7 +00:00:17,670 --> 00:00:19,740 +在这里,我们使用我们看到的模型 +Here, we are using the model we saw + +8 +00:00:19,740 --> 00:00:22,080 +在情绪分析管道中 +in the sentiment analysis pipeline + +9 +00:00:22,080 --> 00:00:24,063 +并想对两个句子进行分类。 +and want to classify two sentences. + +10 +00:00:24,900 --> 00:00:27,360 +将它们标记化并映射每个标记时 +When tokenizing them and mapping each token + +11 +00:00:27,360 --> 00:00:29,610 +到其相应的输入 ID, +to its corresponding input IDs, + +12 +00:00:29,610 --> 00:00:31,593 +我们得到两个不同长度的列表。 +we get two lists of different lengths. + +13 +00:00:33,240 --> 00:00:35,340 +尝试创建张量或 NumPy 数组 +Trying to create a tensor or a NumPy array + +14 +00:00:35,340 --> 00:00:38,220 +从这两个列表中将导致错误, +from those two lists will result in an error, + +15 +00:00:38,220 --> 00:00:41,043 +因为所有数组和张量都应该是矩形的。 +because all arrays and tensors should be rectangular. + +16 +00:00:42,240 --> 00:00:44,160 +克服此限制的一种方法 +One way to overcome this limit + +17 +00:00:44,160 --> 00:00:45,690 +是造第二句 +is to make the second sentence + +18 +00:00:45,690 --> 00:00:47,640 +与第一个长度相同 +the same length as the first + +19 +00:00:47,640 --> 00:00:50,463 +通过根据需要多次添加特殊令牌。 +by adding a special token as many times as necessary. + +20 +00:00:51,600 --> 00:00:53,970 +另一种方法是截断第一个序列 +Another way would be to truncate the first sequence + +21 +00:00:53,970 --> 00:00:55,710 +到第二个的长度, +to the length of the second, + +22 +00:00:55,710 --> 00:00:58,140 +但我们会让他们失去很多信息 +but we would them lose a lot of information + +23 +00:00:58,140 --> 00:01:01,083 +这可能是正确分类句子所必需的。 +that might be necessary to properly classify the sentence. + +24 +00:01:02,190 --> 00:01:04,830 +一般来说,我们只截断句子 +In general, we only truncate sentences + +25 +00:01:04,830 --> 00:01:06,840 +当它们长于最大长度时 +when they are longer than the maximum length + +26 +00:01:06,840 --> 00:01:08,073 +该模型可以处理。 +the model can handle. + +27 +00:01:09,720 --> 00:01:11,850 +用于填充第二句的值 +The value used to pad the second sentence + +28 +00:01:11,850 --> 00:01:13,740 +不应随意挑选; +should not be picked randomly; + +29 +00:01:13,740 --> 00:01:16,680 +该模型已经用特定的填充 ID 进行了预训练, +the model has been pretrained with a certain padding ID, + +30 +00:01:16,680 --> 00:01:19,533 +你可以在 tokenizer.pad_token_id 中找到它。 +which you can find in tokenizer.pad_token_id. + +31 +00:01:21,090 --> 00:01:22,800 +现在我们已经填充了句子, +Now that we have padded our sentences, + +32 +00:01:22,800 --> 00:01:24,303 +我们可以和他们一起做一批。 +we can make a batch with them. + +33 +00:01:25,380 --> 00:01:28,320 +如果我们分别将两个句子传递给模型 +If we pass the two sentences to the model separately + +34 +00:01:28,320 --> 00:01:30,120 +然而,并批在一起, +and batched together however, + +35 +00:01:30,120 --> 00:01:32,100 +我们注意到我们没有得到相同的结果 +we notice that we don't get the same results + +36 +00:01:32,100 --> 00:01:34,060 +对于被填充的句子, +for the sentence that is padded, + +37 +00:01:34,060 --> 00:01:35,403 +在这里,第二个。 +here, the second one. + +38 +00:01:36,390 --> 00:01:39,420 +它在 Transformers 库的后面?不。 +这是 Transformers +It's at the back in the Transformers Library? No. + +39 +00:01:39,420 --> 00:01:40,770 +如果你还记得 Transformer 模型 +If you remember that Transformer models + +40 +00:01:40,770 --> 00:01:42,810 +大量使用注意力层, +make heavy use of attention layers, + +41 +00:01:42,810 --> 00:01:45,210 +这应该不足为奇; +this should not come as a total surprise; + +42 +00:01:45,210 --> 00:01:48,277 +在计算每个标记的上下文表示时, +when computing the contextual representation of each token, + +43 +00:01:48,277 --> 00:01:50,910 +注意层查看所有其他词 +the attention layers look at all the other words + +44 +00:01:50,910 --> 00:01:52,410 +在句子中。 +in the sentence. + +45 +00:01:52,410 --> 00:01:53,850 +如果我们只有这句话 +If we have just the sentence + +46 +00:01:53,850 --> 00:01:56,970 +或者添加了几个填充标记的句子, +or the sentence with several padding tokens added, + +47 +00:01:56,970 --> 00:01:59,073 +我们没有得到相同的值是合乎逻辑的。 +it's logical we don't get the same values. + +48 +00:02:00,270 --> 00:02:03,030 +要在有或没有填充的情况下获得相同的结果, +To get the same results with or without padding, + +49 +00:02:03,030 --> 00:02:05,340 +我们需要向注意力层表明 +we need to indicate to the attention layers + +50 +00:02:05,340 --> 00:02:08,070 +他们应该忽略那些填充标记。 +that they should ignore those padding tokens. + +51 +00:02:08,070 --> 00:02:10,620 +这是通过创建一个注意力掩码来完成的, +This is done by creating an attention mask, + +52 +00:02:10,620 --> 00:02:13,320 +与输入 ID 具有相同形状的张量, +a tensor with the same shape as the input IDs, + +53 +00:02:13,320 --> 00:02:14,733 +用零和一个。 +with zeros and ones. + +54 +00:02:15,780 --> 00:02:18,120 +那些表示注意层的标记 +Ones indicate the tokens the attention layers + +55 +00:02:18,120 --> 00:02:20,100 +应结合上下文考虑 +should consider in the context + +56 +00:02:20,100 --> 00:02:22,100 +并将他们应该忽略的标记归零。 +and zeros the tokens they should ignore. + +57 +00:02:23,520 --> 00:02:26,760 +现在,将这个注意掩码与输入 ID 一起传递 +Now, passing this attention mask along with the input ID + +58 +00:02:26,760 --> 00:02:28,170 +会给我们相同的结果 +will give us the same results + +59 +00:02:28,170 --> 00:02:31,170 +就像我们将两个句子分别发送给模型一样。 +as when we sent the two sentences individually to the model. + +60 +00:02:32,400 --> 00:02:34,950 +这一切都是由分词器在幕后完成的 +This is all done behind the scenes by the tokenizer + +61 +00:02:34,950 --> 00:02:36,900 +当你将它应用于几个句子时 +when you apply it to several sentences + +62 +00:02:36,900 --> 00:02:38,613 +标志 padding=True。 +with the flag padding=True. + +63 +00:02:39,599 --> 00:02:41,490 +它将应用具有适当值的填充 +It will apply the padding with the proper value + +64 +00:02:41,490 --> 00:02:43,140 +到较小的句子 +to the smaller sentences + +65 +00:02:43,140 --> 00:02:45,423 +并创建适当的注意掩码。 +and create the appropriate attention mask. + +66 +00:02:46,993 --> 00:02:49,576 +(微妙的爆炸) +(subtle blast) + diff --git a/subtitles/zh-CN/18_batching-inputs-together-(tensorflow).srt b/subtitles/zh-CN/18_batching-inputs-together-(tensorflow).srt new file mode 100644 index 000000000..a672d8f66 --- /dev/null +++ b/subtitles/zh-CN/18_batching-inputs-together-(tensorflow).srt @@ -0,0 +1,315 @@ +1 +00:00:00,458 --> 00:00:02,791 +(徽标嗖嗖声) +(logo whooshes) + +2 +00:00:05,310 --> 00:00:07,590 +- 如何一起批量输入。 +- How to batch inputs together. + +3 +00:00:07,590 --> 00:00:09,150 +在这个视频中,我们将看到 +In this video, we'll see + +4 +00:00:09,150 --> 00:00:11,050 +如何将输入序列一起批处理。 +how to batch input sequences together. + +5 +00:00:12,630 --> 00:00:14,910 +一般来说,我们要传递的句子 +In general, the sentences we want to pass + +6 +00:00:14,910 --> 00:00:18,000 +通过我们的模型不会都具有相同的长度。 +through our model won't all have the same lengths. + +7 +00:00:18,000 --> 00:00:20,310 +在这里,我们使用我们看到的模型 +Here, we are using the model we saw + +8 +00:00:20,310 --> 00:00:22,650 +在情绪分析管道中 +in the sentiment analysis pipeline + +9 +00:00:22,650 --> 00:00:24,753 +并想对两个句子进行分类。 +and want to classify two sentences. + +10 +00:00:25,860 --> 00:00:27,870 +将它们标记化并映射每个标记时 +When tokenizing them and mapping each token + +11 +00:00:27,870 --> 00:00:30,000 +到其相应的输入 ID, +to its corresponding input IDs, + +12 +00:00:30,000 --> 00:00:31,900 +我们得到两个不同长度的列表。 +we get two lists of different lengths. + +13 +00:00:33,360 --> 00:00:35,070 +尝试创建张量和 NumPy 数组 +Trying to create a tensor and NumPy array + +14 +00:00:35,070 --> 00:00:38,100 +从这两个列表中将导致错误 +from those two lists will result in an error + +15 +00:00:38,100 --> 00:00:40,953 +因为所有数组和张量都应该是矩形的。 +because all arrays and tensors should be rectangular. + +16 +00:00:42,510 --> 00:00:43,920 +克服此限制的一种方法 +One way to overcome this limit + +17 +00:00:43,920 --> 00:00:47,340 +就是让第二句和第一句一样长 +is to make the second sentence the same length as the first + +18 +00:00:47,340 --> 00:00:50,373 +通过根据需要多次添加特殊令牌。 +by adding a special token as many times as necessary. + +19 +00:00:51,300 --> 00:00:53,340 +另一种方法是截断第一个序列 +Another way would be to truncate the first sequence + +20 +00:00:53,340 --> 00:00:56,550 +到秒的长度,但我们会失去很多 +to the length of the second, but we would then lose a lot + +21 +00:00:56,550 --> 00:00:58,590 +可能需要的信息 +of information that may be necessary + +22 +00:00:58,590 --> 00:01:01,230 +正确地对句子进行分类。 +to properly classify the sentence. + +23 +00:01:01,230 --> 00:01:04,710 +一般来说,我们只会截断更长的句子 +In general, we only truncate sentences when they are longer + +24 +00:01:04,710 --> 00:01:07,083 +超过模型可以处理的最大长度。 +than the maximum length the model can handle. + +25 +00:01:08,310 --> 00:01:10,320 +用于填充第二句的值 +The value used to pad the second sentence + +26 +00:01:10,320 --> 00:01:12,390 +不应随意挑选。 +should not be picked randomly. + +27 +00:01:12,390 --> 00:01:15,330 +该模型已经使用特定的填充 ID 进行了预训练, +The model has been pretrained with a certain padding ID, + +28 +00:01:15,330 --> 00:01:18,093 +你可以在 tokenizer.pad_token_id 中找到它。 +which you can find in tokenizer.pad_token_id. + +29 +00:01:19,950 --> 00:01:21,630 +现在我们已经填充了句子, +Now that we have padded our sentences, + +30 +00:01:21,630 --> 00:01:23,130 +我们可以和他们一起做一批。 +we can make a batch with them. + +31 +00:01:24,210 --> 00:01:26,730 +如果我们分别将两个句子传递给模型 +If we pass the two sentences to the model separately + +32 +00:01:26,730 --> 00:01:29,130 +或批在一起,但是,我们注意到 +or batched together, however, we notice + +33 +00:01:29,130 --> 00:01:30,630 +我们没有得到相同的结果 +that we don't get the same results + +34 +00:01:30,630 --> 00:01:32,070 +对于被填充的句子。 +for the sentence that is padded. + +35 +00:01:32,070 --> 00:01:34,440 +在这里,第二个。 +Here, the second one. + +36 +00:01:34,440 --> 00:01:36,690 +期待 transformer 库中的单词? +Expect the word in the transformer library? + +37 +00:01:36,690 --> 00:01:37,620 +不。 +No. + +38 +00:01:37,620 --> 00:01:39,720 +如果你还记得 Transformer 模型大量使用 +If you remember that Transformer models make heavy use + +39 +00:01:39,720 --> 00:01:43,800 +注意力层,它不应该完全令人惊讶。 +of attention layers, it should not come as a total surprise. + +40 +00:01:43,800 --> 00:01:47,100 +在计算每个标记的上下文表示时, +When computing the contextual representation of each token, + +41 +00:01:47,100 --> 00:01:49,440 +注意层查看所有其他词 +the attention layers look at all the other words + +42 +00:01:49,440 --> 00:01:51,240 +在句子中。 +in the sentence. + +43 +00:01:51,240 --> 00:01:52,252 +如果我们只有一句话 +If we have just a sentence + +44 +00:01:52,252 --> 00:01:55,650 +或者添加了几个填充标记的句子, +or the sentence with several padding tokens added, + +45 +00:01:55,650 --> 00:01:57,750 +我们没有得到相同的值是合乎逻辑的。 +it's logical we don't get the same values. + +46 +00:01:58,830 --> 00:02:01,410 +要在有或没有填充的情况下获得相同的结果, +To get the same results with or without padding, + +47 +00:02:01,410 --> 00:02:03,750 +我们需要向注意力层表明 +we need to indicate to the attention layers + +48 +00:02:03,750 --> 00:02:06,660 +他们应该忽略那些填充标记。 +that they should ignore those padding tokens. + +49 +00:02:06,660 --> 00:02:08,970 +这是通过创建一个注意力掩码来完成的, +This is done by creating an attention mask, + +50 +00:02:08,970 --> 00:02:11,700 +与输入 ID 具有相同形状的张量 +a tensor with the same shape as the input IDs + +51 +00:02:11,700 --> 00:02:13,173 +用零和一个。 +with zeros and ones. + +52 +00:02:14,640 --> 00:02:16,830 +那些表示注意层的标记 +Ones indicate the tokens the attention layers + +53 +00:02:16,830 --> 00:02:18,660 +应该在上下文中考虑, +should consider in the context, + +54 +00:02:18,660 --> 00:02:20,823 +和零,他们应该忽略的标记。 +and zeros, the tokens they should ignore. + +55 +00:02:21,810 --> 00:02:23,290 +现在,通过这个注意力面具 +Now, passing this attention mask + +56 +00:02:23,290 --> 00:02:26,460 +连同输入 ID 会给我们相同的结果 +along with the input IDs will give us the same results + +57 +00:02:26,460 --> 00:02:29,460 +就像我们将两个句子分别发送给模型一样。 +as when we sent the two sentences individually to the model. + +58 +00:02:30,870 --> 00:02:33,870 +这一切都是由分词器在幕后完成的 +This is all done behind the scenes by the tokenizer + +59 +00:02:33,870 --> 00:02:35,583 +当你将它应用于几个句子时 +when you apply it to several sentences + +60 +00:02:35,583 --> 00:02:37,713 +标志填充等于 true。 +with the flag padding equals true. + +61 +00:02:38,640 --> 00:02:39,690 +它将应用填充 +It will apply the padding + +62 +00:02:39,690 --> 00:02:42,180 +对较小的句子具有适当的价值 +with the proper value to the smaller sentences + +63 +00:02:42,180 --> 00:02:44,373 +并创建适当的注意掩码。 +and create the appropriate attention mask. + diff --git a/subtitles/zh-CN/19_hugging-face-datasets-overview-(pytorch).srt b/subtitles/zh-CN/19_hugging-face-datasets-overview-(pytorch).srt new file mode 100644 index 000000000..c12c1cd40 --- /dev/null +++ b/subtitles/zh-CN/19_hugging-face-datasets-overview-(pytorch).srt @@ -0,0 +1,375 @@ +1 +00:00:00,213 --> 00:00:02,963 +(滑动嗖嗖声) +(slide whooshes) + +2 +00:00:05,340 --> 00:00:08,373 +- 本节将带来 Hugging Face Datasets 库的快速概览。 +- The Hugging Face Datasets library, a quick overview. + +3 +00:00:09,990 --> 00:00:11,670 +Hugging Face Datasets 库 +The Hugging Face Datasets library + +4 +00:00:11,670 --> 00:00:14,310 +是一个提供 API 来快速下载的库 +is a library that provides an API to quickly download + +5 +00:00:14,310 --> 00:00:17,610 +许多公共数据集并对其进行预处理。 +many public datasets and preprocess them. + +6 +00:00:17,610 --> 00:00:20,614 +在本视频中,我们将探索如何做到这一点。 +In this video we will explore how to do that. + +7 +00:00:20,614 --> 00:00:21,780 +下载部分很简单, +The downloading part is easy, + +8 +00:00:21,780 --> 00:00:23,760 +使用 load_dataset 函数。 +with the load_dataset function. + +9 +00:00:23,760 --> 00:00:26,460 +你可以直接下载并缓存数据集 +You can directly download and cache a dataset + +10 +00:00:26,460 --> 00:00:28,473 +来自其在数据集中心的标识符。 +from its identifier on the Dataset hub. + +11 +00:00:29,640 --> 00:00:33,570 +在这里,我们从 GLUE 基准中获取 MRPC 数据集, +Here, we fetch the MRPC dataset from the GLUE benchmark, + +12 +00:00:33,570 --> 00:00:36,390 +这是一个包含成对句子的数据集 +which is a dataset containing pairs of sentences + +13 +00:00:36,390 --> 00:00:38,740 +任务是确定释义。 +where the task is to determine the paraphrases. + +14 +00:00:39,810 --> 00:00:42,420 +load_dataset 函数返回的对象 +The object returned by the load_dataset function + +15 +00:00:42,420 --> 00:00:45,600 +是一个 DatasetDict,它是一种字典 +is a DatasetDict, which is a sort of dictionary + +16 +00:00:45,600 --> 00:00:47,463 +包含我们数据集的每个分割。 +containing each split of our dataset. + +17 +00:00:48,946 --> 00:00:52,170 +我们可以通过使用其名称进行索引来访问每个拆分。 +We can access each split by indexing with its name. + +18 +00:00:52,170 --> 00:00:55,047 +这个拆分然后是 Dataset 类的一个实例, +This split is then an instance of the Dataset class, + +19 +00:00:55,047 --> 00:00:58,590 +有列,这里是 sentence1,sentence2, +with columns, here sentence1, sentence2, + +20 +00:00:58,590 --> 00:01:01,233 +标签和 idx,以及行。 +label and idx, and rows. + +21 +00:01:02,400 --> 00:01:04,563 +我们可以通过索引访问给定的元素。 +We can access a given element by its index. + +22 +00:01:05,460 --> 00:01:08,220 +Hugging Face Datasets 库的神奇之处 +The amazing thing about the Hugging Face Datasets library + +23 +00:01:08,220 --> 00:01:11,880 +是所有内容都使用 Apache Arrow 保存到磁盘, +is that everything is saved to disk using Apache Arrow, + +24 +00:01:11,880 --> 00:01:14,550 +这意味着即使你的数据集很大, +which means that even if your dataset is huge, + +25 +00:01:14,550 --> 00:01:16,350 +你不会离开 RAM。 +you won't get out of RAM. + +26 +00:01:16,350 --> 00:01:19,113 +只有你请求的元素才会加载到内存中。 +Only the elements you request are loaded in memory. + +27 +00:01:20,340 --> 00:01:23,940 +访问数据集的一部分就像访问一个元素一样简单。 +Accessing a slice of your dataset is as easy as one element. + +28 +00:01:23,940 --> 00:01:26,220 +结果是一个包含值列表的字典 +The result is then a dictionary with list of values + +29 +00:01:26,220 --> 00:01:27,480 +对于每个键。 +for each keys. + +30 +00:01:27,480 --> 00:01:29,070 +这里是标签列表, +Here the list of labels, + +31 +00:01:29,070 --> 00:01:30,147 +第一句话列表 +the list of first sentences + +32 +00:01:30,147 --> 00:01:31,923 +和第二句话的列表。 +and the list of second sentences. + +33 +00:01:33,690 --> 00:01:35,580 +数据集的特征属性 +The features attribute of a Dataset + +34 +00:01:35,580 --> 00:01:37,470 +为我们提供有关其专栏的更多信息。 +gives us more information about its columns. + +35 +00:01:37,470 --> 00:01:40,020 +特别是,我们可以在这里看到 +In particular, we can see here + +36 +00:01:40,020 --> 00:01:41,400 +它给了我们信件 +it gives us the correspondence + +37 +00:01:41,400 --> 00:01:44,810 +在标签的整数和名称之间。 +between the integers and names for the labels. + +38 +00:01:44,810 --> 00:01:48,543 +零代表不等价,一代表等价。 +Zero stands for not equivalent and one for equivalent. + +39 +00:01:49,830 --> 00:01:52,020 +要预处理数据集的所有元素, +To preprocess all the elements of our dataset, + +40 +00:01:52,020 --> 00:01:53,850 +我们需要将它们标记化。 +we need to tokenize them. + +41 +00:01:53,850 --> 00:01:56,160 +看看视频 “预处理句子对” +Have a look at the video "Preprocess sentence pairs" + +42 +00:01:56,160 --> 00:01:57,570 +复习一下, +for a refresher, + +43 +00:01:57,570 --> 00:01:59,430 +但你只需要发送这两个句子 +but you just have to send the two sentences + +44 +00:01:59,430 --> 00:02:02,733 +带有一些额外的关键字参数的分词器。 +to the tokenizer with some additional keyword arguments. + +45 +00:02:03,780 --> 00:02:06,600 +这里我们表示最大长度为 128 +Here we indicate a maximum length of 128 + +46 +00:02:06,600 --> 00:02:08,820 +和垫输入短于这个长度, +and pad inputs shorter than this length, + +47 +00:02:08,820 --> 00:02:10,420 +截断更长的输入。 +truncate inputs that are longer. + +48 +00:02:11,460 --> 00:02:13,470 +我们把所有这些都放在一个 tokenize_function 中 +We put all of this in a tokenize_function + +49 +00:02:13,470 --> 00:02:16,710 +我们可以直接应用于数据集中的所有拆分 +that we can directly apply to all the splits in our dataset + +50 +00:02:16,710 --> 00:02:17,710 +用地图的方法。 +with the map method. + +51 +00:02:18,840 --> 00:02:22,110 +只要函数返回一个类似字典的对象, +As long as the function returns a dictionary-like object, + +52 +00:02:22,110 --> 00:02:24,300 +map 方法将根据需要添加新列 +the map method will add new columns as needed + +53 +00:02:24,300 --> 00:02:26,043 +或更新现有的。 +or update existing ones. + +54 +00:02:27,315 --> 00:02:28,830 +加快预处理 +To speed up preprocessing + +55 +00:02:28,830 --> 00:02:30,870 +并利用我们的分词器这一事实 +and take advantage of the fact our tokenizer + +56 +00:02:30,870 --> 00:02:32,040 +由 Rust 支持, +is backed by Rust, + +57 +00:02:32,040 --> 00:02:34,770 +感谢 Hugging Face Tokenizers 库, +thanks to the Hugging Face Tokenizers library, + +58 +00:02:34,770 --> 00:02:37,110 +我们可以同时处理多个元素 +we can process several elements at the same time + +59 +00:02:37,110 --> 00:02:40,710 +到我们的 tokenize 函数,使用 batched=True 参数。 +to our tokenize function, using the batched=True argument. + +60 +00:02:40,710 --> 00:02:42,120 +由于分词器可以处理 +Since the tokenizer can handle + +61 +00:02:42,120 --> 00:02:44,610 +第一句话列表,第二句列表, +list of first sentences, list of second sentences, + +62 +00:02:44,610 --> 00:02:47,493 +tokenize_function 不需要为此更改。 +the tokenize_function does not need to change for this. + +63 +00:02:48,360 --> 00:02:51,180 +你还可以将多处理与 map 方法一起使用。 +You can also use multiprocessing with the map method. + +64 +00:02:51,180 --> 00:02:53,583 +在链接的视频中查看其文档。 +Check out its documentation in the linked video. + +65 +00:02:54,840 --> 00:02:57,990 +完成后,我们几乎可以进行培训了。 +Once this is done, we are almost ready for training. + +66 +00:02:57,990 --> 00:02:59,970 +我们只是删除不再需要的列 +We just remove the columns we don't need anymore + +67 +00:02:59,970 --> 00:03:02,190 +使用 remove_columns 方法, +with the remove_columns method, + +68 +00:03:02,190 --> 00:03:03,750 +将标签重命名为标签, +rename label to labels, + +69 +00:03:03,750 --> 00:03:05,790 +因为来自 Hugging Face Transformers 的模型 +since the models from the Hugging Face Transformers + +70 +00:03:05,790 --> 00:03:07,710 +图书馆期望, +library expect that, + +71 +00:03:07,710 --> 00:03:10,470 +并将输出格式设置为我们想要的后端, +and set the output format to our desired backend, + +72 +00:03:10,470 --> 00:03:12,053 +火炬、TensorFlow 或 NumPy。 +Torch, TensorFlow or NumPy. + +73 +00:03:13,440 --> 00:03:16,800 +如果需要,我们还可以生成一个简短的数据集样本 +If needed, we can also generate a short sample of a dataset + +74 +00:03:16,800 --> 00:03:18,000 +使用选择方法。 +using the select method. + +75 +00:03:20,211 --> 00:03:22,961 +(滑动嗖嗖声) +(slide whooshes) + diff --git a/subtitles/zh-CN/20_hugging-face-datasets-overview-(tensorflow).srt b/subtitles/zh-CN/20_hugging-face-datasets-overview-(tensorflow).srt new file mode 100644 index 000000000..8d655495b --- /dev/null +++ b/subtitles/zh-CN/20_hugging-face-datasets-overview-(tensorflow).srt @@ -0,0 +1,355 @@ +1 +00:00:00,170 --> 00:00:03,087 +(屏幕呼啸) +(screen whooshing) + +2 +00:00:05,371 --> 00:00:09,690 +- 本节将带来 Hugging Face Datasets 库的快速概览。 +- The Hugging Face Datasets library: A Quick overview. + +3 +00:00:09,690 --> 00:00:10,917 +Hugging Face Datasets 库 +The Hugging Face Datasets library + +4 +00:00:10,917 --> 00:00:12,870 +是一个提供 API 的库 +is a library that provides an API + +5 +00:00:12,870 --> 00:00:15,150 +快速下载许多公共数据集 +to quickly download many public datasets + +6 +00:00:15,150 --> 00:00:16,200 +并对它们进行预处理。 +and pre-process them. + +7 +00:00:17,070 --> 00:00:19,473 +在本视频中,我们将探索如何做到这一点。 +In this video we will explore to do that. + +8 +00:00:20,520 --> 00:00:23,730 +下载部分使用 load_dataset 函数很容易, +The downloading part is easy with the load_dataset function, + +9 +00:00:23,730 --> 00:00:26,010 +你可以直接下载并缓存数据集 +you can directly download and cache a dataset + +10 +00:00:26,010 --> 00:00:28,023 +来自其在数据集中心的标识符。 +from its identifier on the Dataset hub. + +11 +00:00:29,160 --> 00:00:33,690 +这里我们从 GLUE 基准中获取 MRPC 数据集, +Here we fetch the MRPC dataset from the GLUE benchmark, + +12 +00:00:33,690 --> 00:00:36,030 +是一个包含句子对的数据集 +is a dataset containing pairs of sentences + +13 +00:00:36,030 --> 00:00:38,380 +任务是确定释义。 +where the task is to determine the paraphrases. + +14 +00:00:39,720 --> 00:00:42,120 +load_dataset 函数返回的对象 +The object returned by the load_dataset function + +15 +00:00:42,120 --> 00:00:45,090 +是一个 DatasetDict,它是一种字典 +is a DatasetDict, which is a sort of dictionary + +16 +00:00:45,090 --> 00:00:46,940 +包含我们数据集的每个分割。 +containing each split of our dataset. + +17 +00:00:48,600 --> 00:00:51,780 +我们可以通过使用其名称进行索引来访问每个拆分。 +We can access each split by indexing with its name. + +18 +00:00:51,780 --> 00:00:54,540 +这个拆分然后是 Dataset 类的一个实例, +This split is then an instance of the Dataset class, + +19 +00:00:54,540 --> 00:00:57,423 +有列,这里是 sentence1,sentence2, +with columns, here sentence1, sentence2, + +20 +00:00:58,350 --> 00:01:00,813 +标签和 idx,以及行。 +label and idx, and rows. + +21 +00:01:02,160 --> 00:01:05,220 +我们可以通过索引访问给定的元素。 +We can access a given element by its index. + +22 +00:01:05,220 --> 00:01:08,220 +Hugging Face Datasets 库的神奇之处 +The amazing thing about the Hugging Face Datasets library + +23 +00:01:08,220 --> 00:01:11,700 +是所有内容都使用 Apache Arrow 保存到磁盘, +is that everything is saved to disk using Apache Arrow, + +24 +00:01:11,700 --> 00:01:14,460 +这意味着即使你的数据集很大 +which means that even if your dataset is huge + +25 +00:01:14,460 --> 00:01:16,219 +你不会离开 RAM, +you won't get out of RAM, + +26 +00:01:16,219 --> 00:01:18,769 +只有你请求的元素才会加载到内存中。 +only the elements you request are loaded in memory. + +27 +00:01:19,920 --> 00:01:24,510 +访问数据集的一部分就像访问一个元素一样简单。 +Accessing a slice of your dataset is as easy as one element. + +28 +00:01:24,510 --> 00:01:27,150 +结果是一个包含值列表的字典 +The result is then a dictionary with list of values + +29 +00:01:27,150 --> 00:01:30,630 +对于每个键,这里是标签列表, +for each keys, here the list of labels, + +30 +00:01:30,630 --> 00:01:32,190 +第一句话列表, +the list of first sentences, + +31 +00:01:32,190 --> 00:01:33,840 +和第二句话的列表。 +and the list of second sentences. + +32 +00:01:35,100 --> 00:01:37,080 +数据集的特征属性 +The features attribute of a Dataset + +33 +00:01:37,080 --> 00:01:39,840 +为我们提供有关其专栏的更多信息。 +gives us more information about its columns. + +34 +00:01:39,840 --> 00:01:42,150 +特别是,我们可以在这里看到它给了我们 +In particular, we can see here it gives us + +35 +00:01:42,150 --> 00:01:43,980 +整数之间的对应关系 +a correspondence between the integers + +36 +00:01:43,980 --> 00:01:46,110 +和标签的名称。 +and names for the labels. + +37 +00:01:46,110 --> 00:01:49,623 +0 代表不等价,1 代表等价。 +0 stands for not equivalent and 1 for equivalent. + +38 +00:01:51,630 --> 00:01:54,090 +要预处理数据集的所有元素, +To pre-process all the elements of our dataset, + +39 +00:01:54,090 --> 00:01:55,980 +我们需要将它们标记化。 +we need to tokenize them. + +40 +00:01:55,980 --> 00:01:58,470 +看看视频 “预处理句子对” +Have a look at the video "Pre-process sentence pairs" + +41 +00:01:58,470 --> 00:02:01,800 +复习一下,但你只需要发送这两个句子 +for a refresher, but you just have to send the two sentences + +42 +00:02:01,800 --> 00:02:04,833 +带有一些额外的关键字参数的分词器。 +to the tokenizer with some additional keyword arguments. + +43 +00:02:05,880 --> 00:02:09,300 +这里我们表示最大长度为 128 +Here we indicate a maximum length of 128 + +44 +00:02:09,300 --> 00:02:11,460 +和垫输入短于这个长度, +and pad inputs shorter than this length, + +45 +00:02:11,460 --> 00:02:13,060 +截断更长的输入。 +truncate inputs that are longer. + +46 +00:02:14,040 --> 00:02:16,170 +我们把所有这些都放在一个 tokenize_function 中 +We put all of this in a tokenize_function + +47 +00:02:16,170 --> 00:02:18,510 +我们可以直接应用于所有拆分 +that we can directly apply to all the splits + +48 +00:02:18,510 --> 00:02:20,260 +在我们的数据集中使用 map 方法。 +in our dataset with the map method. + +49 +00:02:21,210 --> 00:02:24,120 +只要函数返回一个类似字典的对象, +As long as the function returns a dictionary-like object, + +50 +00:02:24,120 --> 00:02:26,580 +map 方法将根据需要添加新列 +the map method will add new columns as needed + +51 +00:02:26,580 --> 00:02:28,113 +或更新现有的。 +or update existing ones. + +52 +00:02:30,060 --> 00:02:32,520 +加快预处理并利用 +To speed up pre-processing and take advantage + +53 +00:02:32,520 --> 00:02:35,130 +事实上,我们的分词器由 Rust 支持 +of the fact our tokenizer is backed by Rust + +54 +00:02:35,130 --> 00:02:38,160 +感谢 Hugging Face Tokenizers 库, +thanks to the Hugging Face Tokenizers library, + +55 +00:02:38,160 --> 00:02:40,590 +我们可以同时处理多个元素 +we can process several elements at the same time + +56 +00:02:40,590 --> 00:02:43,923 +在我们的 tokenize 函数中,使用 batched=True 参数。 +in our tokenize function, using the batched=True argument. + +57 +00:02:45,300 --> 00:02:46,980 +由于分词器可以处理列表 +Since the tokenizer can handle a list + +58 +00:02:46,980 --> 00:02:50,280 +第一句话或第二句话的 tokenize_function +of first or second sentences, the tokenize_function + +59 +00:02:50,280 --> 00:02:52,740 +不需要为此改变。 +does not need to change for this. + +60 +00:02:52,740 --> 00:02:55,410 +你还可以将多处理与 map 方法一起使用, +You can also use multiprocessing with the map method, + +61 +00:02:55,410 --> 00:02:57,460 +查看下面链接的文档。 +check out its documentation linked below. + +62 +00:02:58,740 --> 00:03:02,130 +完成后,我们几乎可以开始训练了, +Once this is done, we are almost ready for training, + +63 +00:03:02,130 --> 00:03:04,020 +我们只是删除我们不再需要的列 +we just remove the columns we don't need anymore + +64 +00:03:04,020 --> 00:03:06,120 +使用 remove_columns 方法, +with the remove_columns method, + +65 +00:03:06,120 --> 00:03:08,580 +将标签重命名为标签,因为模型 +rename label to labels, since the models + +66 +00:03:08,580 --> 00:03:11,430 +从变形金刚图书馆期望, +from the transformers library expect that, + +67 +00:03:11,430 --> 00:03:14,040 +并将输出格式设置为我们想要的后端, +and set the output format to our desired backend, + +68 +00:03:14,040 --> 00:03:15,893 +手电筒、tensorflow 或 numpy。 +torch, tensorflow or numpy. + +69 +00:03:16,800 --> 00:03:19,050 +如果需要,我们还可以生成一个简短的示例 +If needed, we can also generate a short sample + +70 +00:03:19,050 --> 00:03:21,377 +使用 select 方法的数据集。 +of a dataset using the select method. + +71 +00:03:22,817 --> 00:03:25,734 +(屏幕呼啸) +(screen whooshing) + diff --git a/subtitles/zh-CN/21_preprocessing-sentence-pairs-(pytorch).srt b/subtitles/zh-CN/21_preprocessing-sentence-pairs-(pytorch).srt new file mode 100644 index 000000000..8541dbc5e --- /dev/null +++ b/subtitles/zh-CN/21_preprocessing-sentence-pairs-(pytorch).srt @@ -0,0 +1,325 @@ +1 +00:00:00,000 --> 00:00:03,083 +(图形嘶嘶作响) +(graphics whooshing) + +2 +00:00:05,370 --> 00:00:07,413 +- 如何预处理句子对。 +- How to pre-process pairs of sentences. + +3 +00:00:09,150 --> 00:00:11,340 +我们已经看到了如何标记单个句子 +We have seen how to tokenize single sentences + +4 +00:00:11,340 --> 00:00:12,877 +并将它们放在一起, +and batch them together in the, + +5 +00:00:12,877 --> 00:00:15,810 +“批量输入视频。” +"Batching inputs together video." + +6 +00:00:15,810 --> 00:00:18,330 +如果你觉得这段代码不熟悉, +If this code look unfamiliar to you, + +7 +00:00:18,330 --> 00:00:20,030 +请务必再次检查该视频。 +be sure to check that video again. + +8 +00:00:21,330 --> 00:00:24,543 +这里将重点关注对句子进行分类的任务。 +Here will focus on tasks that classify pair of sentences. + +9 +00:00:25,620 --> 00:00:28,470 +例如,我们可能想要对两个文本进行分类 +For instance, we may want to classify whether two texts + +10 +00:00:28,470 --> 00:00:30,360 +是否被释义。 +are paraphrased or not. + +11 +00:00:30,360 --> 00:00:32,880 +这是从 Quora 问题对中获取的示例 +Here is an example taken from the Quora Question Pairs + +12 +00:00:32,880 --> 00:00:37,530 +数据集,它专注于识别重复的问题。 +dataset, which focuses on identifying duplicate questions. + +13 +00:00:37,530 --> 00:00:40,650 +在第一对中,两个问题是重复的, +In the first pair, the two questions are duplicates, + +14 +00:00:40,650 --> 00:00:42,000 +在第二个他们不是。 +in the second they are not. + +15 +00:00:43,283 --> 00:00:45,540 +另一个对分类问题是 +Another pair classification problem is + +16 +00:00:45,540 --> 00:00:47,400 +当我们想知道两个句子是否 +when we want to know if two sentences are + +17 +00:00:47,400 --> 00:00:49,590 +逻辑上相关与否, +logically related or not, + +18 +00:00:49,590 --> 00:00:53,970 +一个称为自然语言推理或 NLI 的问题。 +a problem called natural language inference or NLI. + +19 +00:00:53,970 --> 00:00:57,000 +在这个例子中,取自 MultiNLI 数据集, +In this example, taken from the MultiNLI data set, + +20 +00:00:57,000 --> 00:00:59,880 +对于每个可能的标签,我们都有一对句子。 +we have a pair of sentences for each possible label. + +21 +00:00:59,880 --> 00:01:02,490 +矛盾,自然的或必然的, +Contradiction, natural or entailment, + +22 +00:01:02,490 --> 00:01:04,680 +这是第一句话的奇特表达方式 +which is a fancy way of saying the first sentence + +23 +00:01:04,680 --> 00:01:05,793 +暗示第二种。 +implies the second. + +24 +00:01:06,930 --> 00:01:08,820 +所以分类成对的句子是一个问题 +So classifying pairs of sentences is a problem + +25 +00:01:08,820 --> 00:01:10,260 +值得研究。 +worth studying. + +26 +00:01:10,260 --> 00:01:12,630 +事实上,在 GLUE 基准测试中, +In fact, in the GLUE benchmark, + +27 +00:01:12,630 --> 00:01:15,750 +这是文本分类的学术基准 +which is an academic benchmark for text classification + +28 +00:01:15,750 --> 00:01:17,910 +10 个数据集中有 8 个是重点 +eight of the 10 data sets are focused + +29 +00:01:17,910 --> 00:01:19,953 +在使用句子对的任务上。 +on tasks using pairs of sentences. + +30 +00:01:20,910 --> 00:01:22,560 +这就是为什么像 BERT 这样的模型 +That's why models like BERT + +31 +00:01:22,560 --> 00:01:25,320 +通常预先训练有双重目标。 +are often pre-trained with a dual objective. + +32 +00:01:25,320 --> 00:01:27,660 +在语言建模目标之上, +On top of the language modeling objective, + +33 +00:01:27,660 --> 00:01:31,230 +他们通常有一个与句子对相关的目标。 +they often have an objective related to sentence pairs. + +34 +00:01:31,230 --> 00:01:34,320 +例如,在预训练期间显示 BERT +For instance, during pretraining BERT is shown + +35 +00:01:34,320 --> 00:01:36,810 +成对的句子,必须同时预测 +pairs of sentences and must predict both + +36 +00:01:36,810 --> 00:01:39,930 +随机屏蔽令牌的价值,以及第二个是否 +the value of randomly masked tokens, and whether the second + +37 +00:01:39,930 --> 00:01:41,830 +句子是否从头开始。 +sentence follow from the first or not. + +38 +00:01:43,084 --> 00:01:45,930 +幸运的是,来自 Transformers 库的分词器 +Fortunately, the tokenizer from the Transformers library + +39 +00:01:45,930 --> 00:01:49,170 +有一个很好的 API 来处理成对的句子。 +has a nice API to deal with pairs of sentences. + +40 +00:01:49,170 --> 00:01:51,270 +你只需要将它们作为两个参数传递 +You just have to pass them as two arguments + +41 +00:01:51,270 --> 00:01:52,120 +到分词器。 +to the tokenizer. + +42 +00:01:53,430 --> 00:01:55,470 +在输入 ID 和注意掩码之上 +On top of the input IDs and the attention mask + +43 +00:01:55,470 --> 00:01:56,970 +我们已经研究过, +we studied already, + +44 +00:01:56,970 --> 00:01:59,910 +它返回一个名为令牌类型 ID 的新字段, +it returns a new field called token type IDs, + +45 +00:01:59,910 --> 00:02:01,790 +它告诉模型哪些令牌属于 +which tells the model which tokens belong + +46 +00:02:01,790 --> 00:02:03,630 +对于第一句话, +to the first sentence, + +47 +00:02:03,630 --> 00:02:05,943 +哪些属于第二句。 +and which ones belong to the second sentence. + +48 +00:02:07,290 --> 00:02:09,840 +放大一点,这里有一个输入 ID +Zooming in a little bit, here has an input IDs + +49 +00:02:09,840 --> 00:02:12,180 +与它们对应的标记对齐, +aligned with the tokens they correspond to, + +50 +00:02:12,180 --> 00:02:15,213 +它们各自的令牌类型 ID 和注意掩码。 +their respective token type ID and attention mask. + +51 +00:02:16,080 --> 00:02:19,260 +我们可以看到标记器还添加了特殊标记。 +We can see the tokenizer also added special tokens. + +52 +00:02:19,260 --> 00:02:22,620 +所以我们有一个 CLS 标记,来自第一句话的标记, +So we have a CLS token, the tokens from the first sentence, + +53 +00:02:22,620 --> 00:02:25,770 +一个 SEP 令牌,第二句话中的令牌, +a SEP token, the tokens from the second sentence, + +54 +00:02:25,770 --> 00:02:27,003 +和最终的 SEP 令牌。 +and a final SEP token. + +55 +00:02:28,500 --> 00:02:30,570 +如果我们有几对句子, +If we have several pairs of sentences, + +56 +00:02:30,570 --> 00:02:32,840 +我们可以通过传递列表将它们标记在一起 +we can tokenize them together by passing the list + +57 +00:02:32,840 --> 00:02:36,630 +第一句话,然后是第二句话的列表 +of first sentences, then the list of second sentences + +58 +00:02:36,630 --> 00:02:39,300 +以及我们已经研究过的所有关键字参数 +and all the keyword arguments we studied already + +59 +00:02:39,300 --> 00:02:40,353 +像填充 = 真。 +like padding=True. + +60 +00:02:41,940 --> 00:02:43,140 +放大结果, +Zooming in at the result, + +61 +00:02:43,140 --> 00:02:45,030 +我们还可以看到标记化添加的填充 +we can see also tokenize added padding + +62 +00:02:45,030 --> 00:02:48,090 +到第二对句子来制作两个输出 +to the second pair sentences to make the two outputs + +63 +00:02:48,090 --> 00:02:51,360 +相同的长度,并正确处理令牌类型 ID +the same length, and properly dealt with token type IDs + +64 +00:02:51,360 --> 00:02:53,643 +和两个句子的注意力掩码。 +and attention masks for the two sentences. + +65 +00:02:54,900 --> 00:02:57,573 +然后就可以通过我们的模型了。 +This is then all ready to pass through our model. + diff --git a/subtitles/zh-CN/22_preprocessing-sentence-pairs-(tensorflow).srt b/subtitles/zh-CN/22_preprocessing-sentence-pairs-(tensorflow).srt new file mode 100644 index 000000000..9bccf528e --- /dev/null +++ b/subtitles/zh-CN/22_preprocessing-sentence-pairs-(tensorflow).srt @@ -0,0 +1,355 @@ +1 +00:00:00,225 --> 00:00:02,892 +(空气呼啸) +(air whooshing) + +2 +00:00:05,578 --> 00:00:09,180 +- 如何预处理成对的句子? +- How to preprocess pairs of sentences? + +3 +00:00:09,180 --> 00:00:11,490 +我们已经看到了如何标记单个句子 +We have seen how to tokenize single sentences + +4 +00:00:11,490 --> 00:00:13,020 +并将它们一起批处理 +and batch them together + +5 +00:00:13,020 --> 00:00:15,660 +在 “一起批量输入” 视频中。 +in the "Batching inputs together" video. + +6 +00:00:15,660 --> 00:00:18,060 +如果你觉得这段代码不熟悉, +If this code looks unfamiliar to you, + +7 +00:00:18,060 --> 00:00:19,760 +请务必再次检查该视频! +be sure to check that video again! + +8 +00:00:21,101 --> 00:00:22,110 +在这里,我们将专注于任务 +Here, we will focus on tasks + +9 +00:00:22,110 --> 00:00:24,033 +对句子对进行分类。 +that classify pairs of sentences. + +10 +00:00:24,900 --> 00:00:27,030 +例如,我们可能想要分类 +For instance, we may want to classify + +11 +00:00:27,030 --> 00:00:29,820 +两个文本是否是释义。 +whether two texts are paraphrases or not. + +12 +00:00:29,820 --> 00:00:30,900 +这是一个例子 +Here is an example taken + +13 +00:00:30,900 --> 00:00:33,180 +来自 Quora Question Pairs 数据集, +from the Quora Question Pairs dataset, + +14 +00:00:33,180 --> 00:00:36,033 +它侧重于识别重复的问题。 +which focuses on identifying duplicate questions. + +15 +00:00:37,110 --> 00:00:40,650 +在第一对中,两个问题是重复的; +In the first pair, the two questions are duplicates; + +16 +00:00:40,650 --> 00:00:43,620 +第二,他们不是。 +in the second, they are not. + +17 +00:00:43,620 --> 00:00:44,730 +另一个分类问题 +Another classification problem + +18 +00:00:44,730 --> 00:00:46,980 +是当我们想知道是否有两个句子 +is when we want to know if two sentences + +19 +00:00:46,980 --> 00:00:49,290 +逻辑上是否相关, +are logically related or not, + +20 +00:00:49,290 --> 00:00:52,173 +一个称为自然语言推理或 NLI 的问题。 +a problem called Natural Language Inference or NLI. + +21 +00:00:53,100 --> 00:00:55,830 +在这个取自 MultiNLI 数据集的例子中, +In this example taken from the MultiNLI dataset, + +22 +00:00:55,830 --> 00:00:59,460 +对于每个可能的标签,我们都有一对句子: +we have a pair of sentences for each possible label: + +23 +00:00:59,460 --> 00:01:02,400 +矛盾,中性或蕴涵, +contradiction, neutral or entailment, + +24 +00:01:02,400 --> 00:01:04,680 +这是第一句话的奇特表达方式 +which is a fancy way of saying the first sentence + +25 +00:01:04,680 --> 00:01:05,853 +暗示第二种。 +implies the second. + +26 +00:01:07,140 --> 00:01:09,000 +所以对句子对进行分类 +So classifying pairs of sentences + +27 +00:01:09,000 --> 00:01:10,533 +是一个值得研究的问题。 +is a problem worth studying. + +28 +00:01:11,370 --> 00:01:13,770 +事实上,在 GLUE 基准测试中, +In fact, in the GLUE benchmark, + +29 +00:01:13,770 --> 00:01:16,830 +这是文本分类的学术基准, +which is an academic benchmark for text classification, + +30 +00:01:16,830 --> 00:01:19,680 +10 个数据集中有 8 个专注于任务 +eight of the 10 datasets are focused on tasks + +31 +00:01:19,680 --> 00:01:20,973 +使用成对的句子。 +using pairs of sentences. + +32 +00:01:22,110 --> 00:01:24,720 +这就是为什么像 BERT 这样的模型经常被预训练的原因 +That's why models like BERT are often pretrained + +33 +00:01:24,720 --> 00:01:26,520 +具有双重目标: +with a dual objective: + +34 +00:01:26,520 --> 00:01:28,890 +在语言建模目标之上, +on top of the language modeling objective, + +35 +00:01:28,890 --> 00:01:32,010 +他们通常有一个与句子对相关的目标。 +they often have an objective related to sentence pairs. + +36 +00:01:32,010 --> 00:01:34,560 +例如,在预训练期间, +For instance, during pretraining, + +37 +00:01:34,560 --> 00:01:36,690 +BERT 显示成对的句子 +BERT is shown pairs of sentences + +38 +00:01:36,690 --> 00:01:39,900 +并且必须预测随机屏蔽标记的值 +and must predict both the value of randomly masked tokens + +39 +00:01:39,900 --> 00:01:41,250 +以及第二句是否 +and whether the second sentence + +40 +00:01:41,250 --> 00:01:42,903 +是否从第一个开始。 +follows from the first or not. + +41 +00:01:44,070 --> 00:01:47,100 +幸运的是,来自 Transformers 库的分词器 +Fortunately, the tokenizer from the Transformers library + +42 +00:01:47,100 --> 00:01:50,550 +有一个很好的 API 来处理成对的句子: +has a nice API to deal with pairs of sentences: + +43 +00:01:50,550 --> 00:01:52,650 +你只需要将它们作为两个参数传递 +you just have to pass them as two arguments + +44 +00:01:52,650 --> 00:01:53,613 +到分词器。 +to the tokenizer. + +45 +00:01:54,900 --> 00:01:56,040 +在输入 ID 之上 +On top of the input IDs + +46 +00:01:56,040 --> 00:01:58,440 +和我们已经研究过的注意力面具, +and the attention mask we studied already, + +47 +00:01:58,440 --> 00:02:01,530 +它返回一个名为令牌类型 ID 的新字段, +it returns a new field called token type IDs, + +48 +00:02:01,530 --> 00:02:03,210 +它告诉模型哪些标记 +which tells the model which tokens + +49 +00:02:03,210 --> 00:02:05,100 +属于第一句 +belong to the first sentence + +50 +00:02:05,100 --> 00:02:07,350 +哪些属于第二句。 +and which ones belong to the second sentence. + +51 +00:02:08,670 --> 00:02:11,430 +放大一点,这里是输入 ID, +Zooming in a little bit, here are the input IDs, + +52 +00:02:11,430 --> 00:02:13,710 +与它们对应的标记对齐, +aligned with the tokens they correspond to, + +53 +00:02:13,710 --> 00:02:17,193 +它们各自的令牌类型 ID 和注意掩码。 +their respective token type ID and attention mask. + +54 +00:02:18,540 --> 00:02:21,300 +我们可以看到标记器还添加了特殊标记 +We can see the tokenizer also added special tokens + +55 +00:02:21,300 --> 00:02:25,230 +所以我们有一个 CLS 标记,来自第一句话的标记, +so we have a CLS token, the tokens from the first sentence, + +56 +00:02:25,230 --> 00:02:28,590 +一个 SEP 令牌,第二句话中的令牌, +a SEP token, the tokens from the second sentence, + +57 +00:02:28,590 --> 00:02:30,153 +和最终的 SEP 令牌。 +and a final SEP token. + +58 +00:02:31,680 --> 00:02:33,720 +如果我们有几对句子, +If we have several pairs of sentences, + +59 +00:02:33,720 --> 00:02:35,640 +我们可以将它们标记在一起 +we can tokenize them together + +60 +00:02:35,640 --> 00:02:38,280 +通过传递第一句话的列表, +by passing the list of first sentences, + +61 +00:02:38,280 --> 00:02:40,710 +然后是第二句话的列表 +then the list of second sentences + +62 +00:02:40,710 --> 00:02:43,050 +以及我们已经研究过的所有关键字参数, +and all the keyword arguments we studied already, + +63 +00:02:43,050 --> 00:02:44,133 +像填充 = 真。 +like padding=True. + +64 +00:02:45,510 --> 00:02:46,770 +放大结果, +Zooming in at the result, + +65 +00:02:46,770 --> 00:02:49,050 +我们可以看到分词器是如何添加填充的 +we can see how the tokenizer added padding + +66 +00:02:49,050 --> 00:02:50,940 +对于第二对句子, +to the second pair of sentences, + +67 +00:02:50,940 --> 00:02:53,490 +使两个输出的长度相同。 +to make the two outputs the same length. + +68 +00:02:53,490 --> 00:02:55,620 +它还可以正确处理令牌类型 IDS +It also properly dealt with token type IDS + +69 +00:02:55,620 --> 00:02:57,720 +和两个句子的注意力掩码。 +and attention masks for the two sentences. + +70 +00:02:59,010 --> 00:03:01,460 +然后就可以通过我们的模型了! +This is then all ready to pass through our model! + +71 +00:03:03,799 --> 00:03:06,466 +(空气呼啸) +(air whooshing) + diff --git a/subtitles/zh-CN/23_what-is-dynamic-padding.srt b/subtitles/zh-CN/23_what-is-dynamic-padding.srt new file mode 100644 index 000000000..30efe21df --- /dev/null +++ b/subtitles/zh-CN/23_what-is-dynamic-padding.srt @@ -0,0 +1,340 @@ +1 +00:00:00,242 --> 00:00:02,909 +(空气呼啸) +(air whooshing) + +2 +00:00:05,460 --> 00:00:06,963 +- 什么是动态填充? +- What is dynamic padding? + +3 +00:00:08,630 --> 00:00:10,890 +在 “一起批量输入” 视频中, +In the "Batching Inputs together" video, + +4 +00:00:10,890 --> 00:00:12,720 +我们已经看到能够对输入进行分组 +we have seen that to be able to group inputs + +5 +00:00:12,720 --> 00:00:15,300 +同一批不同长度的, +of different lengths in the same batch, + +6 +00:00:15,300 --> 00:00:18,270 +我们需要向所有短输入添加填充标记 +we need to add padding tokens to all the short inputs + +7 +00:00:18,270 --> 00:00:20,970 +直到它们的长度都相同。 +until they are all of the same length. + +8 +00:00:20,970 --> 00:00:24,600 +例如,这里最长的句子是第三句, +Here, for instance, the longest sentence is the third one, + +9 +00:00:24,600 --> 00:00:27,270 +我们需要添加五个、两个或七个填充令牌 +and we need to add five, two, or seven pad tokens + +10 +00:00:27,270 --> 00:00:30,090 +到其他句子有四个句子 +to the other sentences to have four sentences + +11 +00:00:30,090 --> 00:00:31,090 +相同的长度。 +of the same lengths. + +12 +00:00:32,430 --> 00:00:33,900 +在处理整个数据集时, +When dealing with a whole dataset, + +13 +00:00:33,900 --> 00:00:36,633 +我们可以应用各种填充策略。 +there are various padding strategies we can apply. + +14 +00:00:37,560 --> 00:00:39,540 +最明显的一种是填充所有元素 +The most obvious one is to pad all the elements + +15 +00:00:39,540 --> 00:00:40,923 +数据集的相同长度: +of the dataset to the same length: + +16 +00:00:40,923 --> 00:00:43,053 +最长样本的长度。 +the length of the longest sample. + +17 +00:00:44,070 --> 00:00:45,330 +这会给我们批次 +This will then give us batches + +18 +00:00:45,330 --> 00:00:46,890 +都具有相同的形状 +that all have the same shape + +19 +00:00:46,890 --> 00:00:49,800 +由最大序列长度决定。 +determined by the maximum sequence length. + +20 +00:00:49,800 --> 00:00:52,893 +缺点是批次由短句组成 +The downside is that batches composed from short sentences + +21 +00:00:52,893 --> 00:00:54,960 +会有很多填充令牌 +will have a lot of padding tokens + +22 +00:00:54,960 --> 00:00:57,660 +这将在模型中引入更多计算 +which will introduce more computations in the model + +23 +00:00:57,660 --> 00:00:58,910 +我们最终不需要。 +we ultimately don't need. + +24 +00:01:00,060 --> 00:01:03,300 +为了避免这种情况,另一种策略是填充元素 +To avoid this, another strategy is to pad the elements + +25 +00:01:03,300 --> 00:01:05,280 +当我们把它们批在一起时, +when we batch them together, + +26 +00:01:05,280 --> 00:01:08,190 +到批次中最长的句子。 +to the longest sentence inside the batch. + +27 +00:01:08,190 --> 00:01:12,000 +这样,由短输入组成的批次会更小 +This way, batches composed of short inputs will be smaller + +28 +00:01:12,000 --> 00:01:13,920 +比包含最长句子的批次 +than the batch containing the longest sentence + +29 +00:01:13,920 --> 00:01:15,510 +在数据集中。 +in the dataset. + +30 +00:01:15,510 --> 00:01:18,063 +这将在 CPU 和 GPU 上产生一些不错的加速。 +This will yield some nice speedup on CPU and GPU. + +31 +00:01:19,110 --> 00:01:20,490 +缺点是所有批次 +The downside is that all batches + +32 +00:01:20,490 --> 00:01:22,140 +然后会有不同的形状, +will then have different shapes, + +33 +00:01:22,140 --> 00:01:24,740 +这会减慢 TPU 等加速器的训练速度。 +which slows down training on accelerators like TPUs. + +34 +00:01:26,160 --> 00:01:29,370 +让我们看看如何在实践中应用这两种策略。 +Let's see how to apply both strategies in practice. + +35 +00:01:29,370 --> 00:01:31,280 +我们实际上已经看到了如何应用固定填充 +We have actually seen how to apply fixed padding + +36 +00:01:31,280 --> 00:01:33,390 +在数据集概述视频中, +in the Datasets Overview video, + +37 +00:01:33,390 --> 00:01:36,030 +当我们预处理 MRPC 数据集时: +when we preprocessed the MRPC dataset: + +38 +00:01:36,030 --> 00:01:38,250 +加载数据集和分词器后, +after loading the dataset and tokenizer, + +39 +00:01:38,250 --> 00:01:40,680 +我们将标记化应用于所有数据集 +we applied the tokenization to all the dataset + +40 +00:01:40,680 --> 00:01:42,480 +带填充和截断 +with padding and truncation + +41 +00:01:42,480 --> 00:01:45,273 +制作所有长度为 128 的样本。 +to make all samples of length 128. + +42 +00:01:46,530 --> 00:01:48,360 +结果,如果我们传递这个数据集 +As a result, if we pass this dataset + +43 +00:01:48,360 --> 00:01:50,520 +到 PyTorch DataLoader, +to a PyTorch DataLoader, + +44 +00:01:50,520 --> 00:01:55,503 +我们得到形状批量大小的批次,这里是 16,乘以 128。 +we get batches of shape batch size, here 16, by 128. + +45 +00:01:57,060 --> 00:01:58,380 +要应用动态填充, +To apply dynamic padding, + +46 +00:01:58,380 --> 00:02:01,440 +我们必须将填充推迟到批量准备, +we must defer the padding to the batch preparation, + +47 +00:02:01,440 --> 00:02:04,740 +所以我们从标记化函数中删除了那部分。 +so we remove that part from our tokenize function. + +48 +00:02:04,740 --> 00:02:06,150 +我们仍然保留截断部分 +We still leave the truncation part + +49 +00:02:06,150 --> 00:02:08,580 +以便大于最大长度的输入 +so that inputs that are bigger than the maximum length + +50 +00:02:08,580 --> 00:02:12,060 +被模型接受,通常是 512, +accepted by the model, usually 512, + +51 +00:02:12,060 --> 00:02:13,510 +被截断到那个长度。 +get truncated to that length. + +52 +00:02:14,940 --> 00:02:16,380 +然后我们动态地填充我们的样本 +Then we pad our samples dynamically + +53 +00:02:16,380 --> 00:02:18,330 +通过使用数据整理器。 +by using a data collator. + +54 +00:02:18,330 --> 00:02:20,280 +Transformers 库中的那些类 +Those classes in the Transformers library + +55 +00:02:20,280 --> 00:02:22,740 +负责应用所有的最终处理 +are responsible for applying all the final processing + +56 +00:02:22,740 --> 00:02:25,290 +在形成批次之前需要, +needed before forming a batch, + +57 +00:02:25,290 --> 00:02:28,470 +这里 DataCollatorWithPadding 将填充样本 +here DataCollatorWithPadding will pad the samples + +58 +00:02:28,470 --> 00:02:31,083 +到这批句子中的最大长度。 +to the maximum length inside the batch of sentences. + +59 +00:02:32,160 --> 00:02:35,310 +我们将它作为整理函数传递给 PyTorch DataLoader, +We pass it to the PyTorch DataLoader as a collate function, + +60 +00:02:35,310 --> 00:02:37,620 +然后观察生成的批次 +then observe that the batches generated + +61 +00:02:37,620 --> 00:02:38,850 +有不同的长度, +have various lengths, + +62 +00:02:38,850 --> 00:02:41,253 +一直低于之前的 128。 +all way below the 128 from before. + +63 +00:02:42,660 --> 00:02:44,820 +动态批处理几乎总是更快 +Dynamic batching will almost always be faster + +64 +00:02:44,820 --> 00:02:47,913 +在 CPU 和 GPU 上,所以如果可以的话你应该应用它。 +on CPUs and GPUs, so you should apply it if you can. + +65 +00:02:48,930 --> 00:02:51,330 +但是,请记住切换回固定填充 +Remember to switch back to fixed padding, however, + +66 +00:02:51,330 --> 00:02:53,490 +如果你在 TPU 上运行你的训练脚本 +if you run your training script on TPU + +67 +00:02:53,490 --> 00:02:55,293 +或者需要成批的固定形状。 +or need batches of fixed shapes. + +68 +00:02:56,917 --> 00:02:59,584 +(空气呼啸) +(air whooshing) + diff --git a/subtitles/zh-CN/24_the-trainer-api.srt b/subtitles/zh-CN/24_the-trainer-api.srt new file mode 100644 index 000000000..2ffaf8c72 --- /dev/null +++ b/subtitles/zh-CN/24_the-trainer-api.srt @@ -0,0 +1,445 @@ +1 +00:00:00,304 --> 00:00:01,285 +(空气呼啸) +(air whooshing) + +2 +00:00:01,285 --> 00:00:02,345 +(空气爆裂声) +(air popping) + +3 +00:00:02,345 --> 00:00:05,698 +(空气呼啸) +(air whooshing) + +4 +00:00:05,698 --> 00:00:06,548 +- 所以培训师 API。 +- So Trainer API. + +5 +00:00:08,070 --> 00:00:10,040 +所以 Transformers Library 提供了一个 Trainer API +So Transformers Library provides a Trainer API + +6 +00:00:10,040 --> 00:00:13,320 +让你轻松找到调谐变压器模型 +that allows you to easily find tune transformers models + +7 +00:00:13,320 --> 00:00:14,193 +在你的数据集上。 +on your dataset. + +8 +00:00:15,150 --> 00:00:17,250 +所以 Trainer 类接受你的数据集, +So Trainer class takes your datasets, + +9 +00:00:17,250 --> 00:00:19,900 +你的模型以及训练超参数 +your model as well as the training hyperparameters + +10 +00:00:20,820 --> 00:00:23,310 +并且可以在任何类型的设置上进行训练, +and can perform the training on any kind of setup, + +11 +00:00:23,310 --> 00:00:26,654 +CPU、GPU、多个 GPU、TPU +CPU, GPU, multiple GPUs, TPUs + +12 +00:00:26,654 --> 00:00:28,680 +也可以计算预测 +can also compute the predictions + +13 +00:00:28,680 --> 00:00:31,710 +在任何数据集上,如果你提供了指标 +on any dataset and if you provided metrics + +14 +00:00:31,710 --> 00:00:33,813 +在任何数据集上评估你的模型。 +evaluate your model on any dataset. + +15 +00:00:34,950 --> 00:00:36,930 +你还可以涉及最终数据处理 +You can also involve final data processing + +16 +00:00:36,930 --> 00:00:38,670 +比如动态填充, +such as dynamic padding, + +17 +00:00:38,670 --> 00:00:40,377 +只要你提供分词器 +as long as you provide the tokenizer + +18 +00:00:40,377 --> 00:00:42,693 +或给定数据整理器。 +or given data collator. + +19 +00:00:43,572 --> 00:00:45,900 +我们将在 MRPC 数据集上尝试这个 API, +We will try this API on the MRPC dataset, + +20 +00:00:45,900 --> 00:00:48,492 +因为它相对较小且易于预处理。 +since it's relatively small and easy to preprocess. + +21 +00:00:48,492 --> 00:00:49,325 +正如我们在数据集概述视频中看到的那样, +As we saw in the Datasets overview video, + +22 +00:00:49,325 --> 00:00:54,325 +但是我们可以对其进行预处理。 +however we can preprocess it. + +23 +00:00:54,511 --> 00:00:57,030 +我们在预处理期间不应用填充, +We do not apply padding during the preprocessing, + +24 +00:00:57,030 --> 00:00:58,590 +因为我们将使用动态填充 +as we will use dynamic padding + +25 +00:00:58,590 --> 00:01:00,083 +在 DataCollatorWithPadding 之前。 +before DataCollatorWithPadding. + +26 +00:01:01,170 --> 00:01:02,790 +请注意,我们不执行最后的步骤 +Note that we don't do the final steps + +27 +00:01:02,790 --> 00:01:04,830 +重命名删除列 +of renaming removing columns + +28 +00:01:04,830 --> 00:01:06,873 +或将格式设置为火炬张量。 +or set the format to torch tensors. + +29 +00:01:07,710 --> 00:01:10,560 +所以 Trainer 会自动为我们做这一切 +So Trainer will do all of this automatically for us + +30 +00:01:10,560 --> 00:01:12,633 +通过分析模型签名。 +by analyzing the model signature. + +31 +00:01:14,054 --> 00:01:16,650 +创建培训师之前的最后一步是 +The last step before creating the Trainer are + +32 +00:01:16,650 --> 00:01:17,940 +定义模型 +to define a model + +33 +00:01:17,940 --> 00:01:20,250 +和一些训练超参数。 +and some training hyperparameters. + +34 +00:01:20,250 --> 00:01:22,653 +我们在模型 API 视频中看到了第一个。 +We saw to do the first in the model API video. + +35 +00:01:23,734 --> 00:01:26,790 +第二次我们使用 TrainingArguments 类。 +For the second we use the TrainingArguments class. + +36 +00:01:26,790 --> 00:01:28,710 +它只需要一个文件夹的路径 +It only takes a path to a folder + +37 +00:01:28,710 --> 00:01:30,900 +结果和检查点将保存在哪里, +where results and checkpoint will be saved, + +38 +00:01:30,900 --> 00:01:33,060 +但你也可以自定义所有超参数 +but you can also customize all the hyperparameters + +39 +00:01:33,060 --> 00:01:34,470 +你的教练会使用, +your Trainer will use, + +40 +00:01:34,470 --> 00:01:37,270 +学习权重、训练影响数等。等等。 +learning weight, number of training impacts, et. cetera. + +41 +00:01:38,190 --> 00:01:39,660 +创建培训师非常容易 +It's been very easy to create a Trainer + +42 +00:01:39,660 --> 00:01:41,400 +并开展培训。 +and launch a training. + +43 +00:01:41,400 --> 00:01:43,170 +你应该显示一个进度条 +You should display a progress bar + +44 +00:01:43,170 --> 00:01:45,900 +如果你在 GPU 上运行,几分钟后 +and after a few minutes if you're running on a GPU + +45 +00:01:45,900 --> 00:01:48,000 +你应该完成培训。 +you should have the training finished. + +46 +00:01:48,000 --> 00:01:50,790 +然而,结果将是相当虎头蛇尾, +The result will be rather anticlimactic however, + +47 +00:01:50,790 --> 00:01:52,710 +因为你只会得到训练损失 +as you will only get a training loss + +48 +00:01:52,710 --> 00:01:54,300 +这并没有真正告诉你任何事情 +which doesn't really tell you anything + +49 +00:01:54,300 --> 00:01:56,820 +关于你的模型表现如何。 +about how well your model is performing. + +50 +00:01:56,820 --> 00:01:58,977 +这是因为我们没有指定任何指标 +This is because we didn't specify any metric + +51 +00:01:58,977 --> 00:02:00,273 +用于评估。 +for the evaluation. + +52 +00:02:01,200 --> 00:02:02,160 +要获得这些指标, +To get those metrics, + +53 +00:02:02,160 --> 00:02:03,810 +我们将首先收集预测 +we will first gather the predictions + +54 +00:02:03,810 --> 00:02:06,513 +使用预测方法在整个评估集上。 +on the whole evaluation set using the predict method. + +55 +00:02:07,440 --> 00:02:10,020 +它返回一个包含三个字段的命名元组, +It returns a namedtuple with three fields, + +56 +00:02:10,020 --> 00:02:12,990 +预测,其中包含预测模型。 +Prediction, which contains the model of predictions. + +57 +00:02:12,990 --> 00:02:15,030 +Label_IDs,其中包含标签 +Label_IDs, which contains the labels + +58 +00:02:15,030 --> 00:02:16,800 +如果你的数据集有它们 +if your dataset had them + +59 +00:02:16,800 --> 00:02:18,570 +和此处为空的指标。 +and metrics which is empty here. + +60 +00:02:18,570 --> 00:02:20,520 +我们正在努力做到这一点。 +We're trying to do that. + +61 +00:02:20,520 --> 00:02:22,470 +预测是模型的对数 +The predictions are the logits of the models + +62 +00:02:22,470 --> 00:02:24,143 +对于数据集中的所有句子。 +for all the sentences in the dataset. + +63 +00:02:24,143 --> 00:02:27,513 +所以一个形状为 408 x 2 的 NumPy 数组。 +So a NumPy array of shape 408 by 2. + +64 +00:02:28,500 --> 00:02:30,270 +为了将它们与我们的标签相匹配, +To match them with our labels, + +65 +00:02:30,270 --> 00:02:31,590 +我们需要采取最大的 logit +we need to take the maximum logit + +66 +00:02:31,590 --> 00:02:32,850 +对于每个预测 +for each prediction + +67 +00:02:32,850 --> 00:02:35,820 +知道预测了这两个类别中的哪一个。 +to know which of the two classes was predicted. + +68 +00:02:35,820 --> 00:02:37,683 +我们使用 argmax 函数来做到这一点。 +We do this with the argmax function. + +69 +00:02:38,640 --> 00:02:41,550 +然后我们可以使用数据集库中的指标。 +Then we can use a metric from the Datasets library. + +70 +00:02:41,550 --> 00:02:43,500 +它可以像数据集一样轻松加载 +It can be loaded as easily as a dataset + +71 +00:02:43,500 --> 00:02:45,360 +具有负载度量功能 +with the load metric function + +72 +00:02:45,360 --> 00:02:49,500 +并且每个返回用于数据集的评估指标。 +and each returns the evaluation metric used for the dataset. + +73 +00:02:49,500 --> 00:02:51,600 +我们可以看到我们的模型确实学到了一些东西 +We can see our model did learn something + +74 +00:02:51,600 --> 00:02:54,363 +因为它的准确率为 85.7%。 +as it is 85.7% accurate. + +75 +00:02:55,440 --> 00:02:57,870 +在训练期间监控评估矩阵, +To monitor the evaluation matrix during training, + +76 +00:02:57,870 --> 00:02:59,829 +我们需要定义一个 compute_metrics 函数 +we need to define a compute_metrics function + +77 +00:02:59,829 --> 00:03:02,670 +和以前一样的步骤。 +that does the same step as before. + +78 +00:03:02,670 --> 00:03:04,728 +它需要一个带有预测和标签的命名元组 +It takes a namedtuple with predictions and labels + +79 +00:03:04,728 --> 00:03:06,327 +并且必须返回一个字典 +and must return a dictionary + +80 +00:03:06,327 --> 00:03:08,427 +使用我们想要跟踪的指标。 +with the metrics we want to keep track of. + +81 +00:03:09,360 --> 00:03:11,490 +通过 epoch 评估策略 +By passing the epoch evaluation strategy + +82 +00:03:11,490 --> 00:03:13,080 +对于我们的训练论点, +to our training arguments, + +83 +00:03:13,080 --> 00:03:14,490 +我们告诉培训师评估 +we tell the Trainer to evaluate + +84 +00:03:14,490 --> 00:03:15,903 +在每个纪元的末尾。 +at the end of every epoch. + +85 +00:03:17,280 --> 00:03:18,587 +在笔记本中启动培训 +Launching a training inside a notebook + +86 +00:03:18,587 --> 00:03:20,640 +然后会显示一个进度条 +will then display a progress bar + +87 +00:03:20,640 --> 00:03:23,643 +并在你通过每个纪元时完成你在这里看到的表格。 +and complete the table you see here as you pass every epoch. + +88 +00:03:25,400 --> 00:03:28,249 +(空气呼啸) +(air whooshing) + +89 +00:03:28,249 --> 00:03:29,974 +(空气渐弱) +(air decrescendos) + diff --git a/subtitles/zh-CN/25_keras-introduction.srt b/subtitles/zh-CN/25_keras-introduction.srt new file mode 100644 index 000000000..4c24568c6 --- /dev/null +++ b/subtitles/zh-CN/25_keras-introduction.srt @@ -0,0 +1,320 @@ +1 +00:00:00,430 --> 00:00:03,013 +(欢快的音乐) +(upbeat music) + +2 +00:00:05,160 --> 00:00:07,080 +- 在这个视频中,我会给你 +- In this video, I'm going to give you + +3 +00:00:07,080 --> 00:00:10,350 +快速介绍我们的变压器模型 +a very quick introduction to how our transformer models + +4 +00:00:10,350 --> 00:00:14,040 +与 Tensorflow 和 Keras 一起工作。 +work together with Tensorflow and Keras. + +5 +00:00:14,040 --> 00:00:15,510 +非常简短的解释 +The very short explanation + +6 +00:00:15,510 --> 00:00:17,310 +是我们所有的 Tensorflow 模型 +is that all of our Tensorflow models + +7 +00:00:17,310 --> 00:00:19,470 +也是 Keras 模型对象, +are also Keras model objects, + +8 +00:00:19,470 --> 00:00:22,950 +因此他们拥有标准的 Keras 模型 API。 +and so they have the standard Keras model API. + +9 +00:00:22,950 --> 00:00:24,960 +如果你是一位经验丰富的机器学习工程师 +If you're an experienced machine learning engineer + +10 +00:00:24,960 --> 00:00:28,230 +谁经常使用 Keras,这可能就是你需要知道的全部 +who's used Keras a lot, that's probably all you need to know + +11 +00:00:28,230 --> 00:00:29,610 +开始与他们合作。 +to start working with them. + +12 +00:00:29,610 --> 00:00:30,900 +但对其他人来说, +But for everyone else, + +13 +00:00:30,900 --> 00:00:34,170 +包括那里的浪子 PyTorch 工程师 +including the prodigal PyTorch engineers out there + +14 +00:00:34,170 --> 00:00:35,910 +正在回归的人, +who are returning to the fold, + +15 +00:00:35,910 --> 00:00:38,430 +我将快速介绍 Keras 模型, +I'm going to quickly introduce Keras models, + +16 +00:00:38,430 --> 00:00:40,440 +以及我们如何与他们合作。 +and how we work with them. + +17 +00:00:40,440 --> 00:00:43,080 +在我将在下面链接的其他视频中, +In other videos, which I'll link below, + +18 +00:00:43,080 --> 00:00:46,440 +我将更详细地介绍 Keras 模型的训练。 +I'll run through training with Keras models in more detail. + +19 +00:00:46,440 --> 00:00:50,820 +但首先,从高层次上讲,什么是 Keras 模型? +But first, at a high level, what is a Keras model? + +20 +00:00:50,820 --> 00:00:54,810 +所以你的模型基本上包含了你的整个网络。 +So your model basically contains your entire network. + +21 +00:00:54,810 --> 00:00:58,230 +它包含层,以及这些层的权重, +It contains the layers, and the weights for those layers, + +22 +00:00:58,230 --> 00:01:00,690 +并告诉模型如何处理它们 +and also tells the model what to do with them + +23 +00:01:00,690 --> 00:01:02,880 +所以它一路定义了整个路径 +so it defines the whole path all the way + +24 +00:01:02,880 --> 00:01:05,460 +从你的输入到你的输出。 +from your inputs to your outputs. + +25 +00:01:05,460 --> 00:01:07,380 +如果你以前使用过 Keras, +If you've used Keras before, + +26 +00:01:07,380 --> 00:01:09,480 +你可能开始使用模型对象 +you probably started using model objects + +27 +00:01:09,480 --> 00:01:11,850 +通过手工构建它们, +by building them out by hand, + +28 +00:01:11,850 --> 00:01:14,250 +你一层又一层地添加 +you added one layer after another + +29 +00:01:14,250 --> 00:01:18,690 +并且可能使用 model.add () 或功能方法。 +and maybe using the model.add () or the functional approach. + +30 +00:01:18,690 --> 00:01:20,490 +这并没有错。 +And there's nothing wrong with that. + +31 +00:01:21,390 --> 00:01:23,430 +许多伟大的模型都是以这种方式构建的 +Lots of great models are built that way + +32 +00:01:23,430 --> 00:01:26,970 +但你也可以预加载整个模型、权重和所有内容。 +but you can also pre-load an entire model, weights and all. + +33 +00:01:26,970 --> 00:01:29,994 +这真的很有帮助,因为如果你, +And this is really helpful, because if you, + +34 +00:01:29,994 --> 00:01:32,490 +正如你在这里看到的,如果你尝试阅读这篇论文 +as you can see here, if you try reading the paper + +35 +00:01:32,490 --> 00:01:34,110 +或者如果你尝试查看代码, +or if you try looking at the code, + +36 +00:01:34,110 --> 00:01:37,350 +你会看到 Transformer 的内部非常复杂, +you'll see the inside of a Transformer is pretty complex, + +37 +00:01:37,350 --> 00:01:40,110 +从头开始把它写出来并把它做好 +and writing it all out from scratch and getting it right + +38 +00:01:40,110 --> 00:01:41,850 +即使对于有经验的人来说也很难 +would be hard even for an experienced + +39 +00:01:41,850 --> 00:01:43,500 +机器学习工程师。 +machine learning engineer. + +40 +00:01:43,500 --> 00:01:45,870 +但因为它都装在一个模型里, +But because it's all packed inside a model, + +41 +00:01:45,870 --> 00:01:48,150 +你不需要担心那个的复杂性 +you don't need to worry about that complexity on that + +42 +00:01:48,150 --> 00:01:49,140 +如果你不想。 +if you don't want to. + +43 +00:01:49,140 --> 00:01:51,570 +如果你是一名研究人员,如果你想真正深入研究 +If you're a researcher, if you want to really dig in there + +44 +00:01:51,570 --> 00:01:55,650 +你可以,但你也可以只加载一个预训练的, +you can, but you can also just load a pre-trained, + +45 +00:01:55,650 --> 00:01:59,013 +只需一行代码即可预配置变压器模型。 +pre-configured transformer model in just one line of code. + +46 +00:02:00,150 --> 00:02:03,480 +当我之前提到 Keras API 时, +And when I mentioned earlier about the Keras API, + +47 +00:02:03,480 --> 00:02:04,560 +它的优点是 +the advantage of it is that + +48 +00:02:04,560 --> 00:02:06,690 +你是否从头开始编写自己的模型 +whether you write your own model from scratch + +49 +00:02:06,690 --> 00:02:09,510 +或者加载一个预训练的,你与模型交互 +or load a pre-trained one, you interact with the model + +50 +00:02:09,510 --> 00:02:11,850 +通过相同的 API,所以你使用 +through that same API, so you use exactly + +51 +00:02:11,850 --> 00:02:13,950 +同样的几种方法,你会看到它们 +the same few methods and you're gonna see them + +52 +00:02:13,950 --> 00:02:16,380 +一次又一次,这些方法就像适合, +again and again, these methods like fit, + +53 +00:02:16,380 --> 00:02:19,650 +编译和预测,就像我提到的 +compile and predict, and like I've mentioned + +54 +00:02:19,650 --> 00:02:22,530 +我们将介绍如何使用这些方法的具体示例 +we'll cover concrete examples of how to use those methods + +55 +00:02:22,530 --> 00:02:24,330 +在我将在下面链接的视频中。 +in the videos I'll link below. + +56 +00:02:24,330 --> 00:02:27,000 +现在要从这个视频中拿走的关键是, +For now the key thing to take away from this video, + +57 +00:02:27,000 --> 00:02:28,950 +如果你以前从未见过 Keras, +if you've never seen Keras before, + +58 +00:02:28,950 --> 00:02:30,870 +是这种简洁的封装意味着 +is that this neat encapsulation means + +59 +00:02:30,870 --> 00:02:33,090 +一个巨大的神经网络的所有复杂性 +that all the complexity of a huge neural net + +60 +00:02:33,090 --> 00:02:35,430 +变得易于管理,因为你与它互动 +becomes manageable, because you interact with it + +61 +00:02:35,430 --> 00:02:39,000 +以完全相同的方式,使用完全相同的方法, +in exactly the same way, using exactly the same methods, + +62 +00:02:39,000 --> 00:02:41,700 +是否是一个庞大的预训练语言模型 +whether it's a huge pre-trained language model + +63 +00:02:41,700 --> 00:02:43,950 +或你手写的简单模型。 +or a simple model that you wrote out by hand. + +64 +00:02:45,466 --> 00:02:48,049 +(欢快的音乐) +(upbeat music) + diff --git a/subtitles/zh-CN/26_fine-tuning-with-tensorflow.srt b/subtitles/zh-CN/26_fine-tuning-with-tensorflow.srt new file mode 100644 index 000000000..a4c18d3a4 --- /dev/null +++ b/subtitles/zh-CN/26_fine-tuning-with-tensorflow.srt @@ -0,0 +1,640 @@ +1 +00:00:00,253 --> 00:00:02,920 +(空气呼啸) +(air whooshing) + +2 +00:00:06,060 --> 00:00:08,070 +- 在这段视频中,我们将看到 +- In this video, we're going to see + +3 +00:00:08,070 --> 00:00:11,430 +如何加载和微调预训练模型。 +how to load and fine tune a pre-trained model. + +4 +00:00:11,430 --> 00:00:12,510 +它非常快。 +It's very quick. + +5 +00:00:12,510 --> 00:00:14,490 +如果你看过我们的管道视频, +And if you've watched our pipeline videos, + +6 +00:00:14,490 --> 00:00:18,150 +我将在下面链接,过程非常相似。 +which I'll link below, the process is very similar. + +7 +00:00:18,150 --> 00:00:20,940 +不过这一次,我们将使用迁移学习 +This time, though, we're going to be using transfer learning + +8 +00:00:20,940 --> 00:00:23,040 +并自己做一些训练, +and doing some training ourselves, + +9 +00:00:23,040 --> 00:00:26,400 +而不是仅仅加载模型并按原样使用它。 +rather than just loading a model and using it as is. + +10 +00:00:26,400 --> 00:00:28,710 +所以要了解更多关于迁移学习的信息, +So to learn more about transfer learning, + +11 +00:00:28,710 --> 00:00:31,320 +前往 “什么是迁移学习?” 视频, +head to the 'What is transfer learning?' video, + +12 +00:00:31,320 --> 00:00:33,420 +我们也会在下面链接它。 +and we'll link that below as well. + +13 +00:00:33,420 --> 00:00:35,610 +但现在,让我们看看这段代码。 +But for now, let's look at this code. + +14 +00:00:35,610 --> 00:00:38,730 +首先,我们选择我们想要开始的模型。 +To start, we pick which model we want to start with. + +15 +00:00:38,730 --> 00:00:40,920 +在这种情况下,我们将使用著名的, +In this case, we're going to use the famous, + +16 +00:00:40,920 --> 00:00:42,060 +原始的 BERT, +the original BERT, + +17 +00:00:42,060 --> 00:00:44,850 +作为我们今天训练的基础。 +as the foundation for our training today. + +18 +00:00:44,850 --> 00:00:46,770 +但这条怪物线是什么, +But what is this monstrosity line, + +19 +00:00:46,770 --> 00:00:48,797 +这个 “TFAutoModelForSequenceClassification”? +this 'TFAutoModelForSequenceClassification'? + +20 +00:00:49,860 --> 00:00:51,180 +这意味着什么? +What does that mean? + +21 +00:00:51,180 --> 00:00:53,130 +好吧,TF 代表 TensorFlow。 +Well, the TF stands for TensorFlow. + +22 +00:00:53,130 --> 00:00:54,660 +剩下的意味着, +And the rest means, + +23 +00:00:54,660 --> 00:00:55,950 +采用语言模型, +take a language model, + +24 +00:00:55,950 --> 00:00:58,380 +并在上面贴上一个序列分类头 +and stick a sequence classification head onto it + +25 +00:00:58,380 --> 00:01:00,750 +如果它还没有的话。 +if it doesn't have one already. + +26 +00:01:00,750 --> 00:01:02,880 +所以这行代码加载了 BERT, +So this line of code loads BERT, + +27 +00:01:02,880 --> 00:01:05,040 +这是一个通用语言模型, +which is a general purpose language model, + +28 +00:01:05,040 --> 00:01:07,650 +它加载重量、架构和所有 +it loads at weights, architecture, and all + +29 +00:01:07,650 --> 00:01:10,920 +然后在上面添加一个新的序列分类头 +and then adds a new sequence classification head onto it + +30 +00:01:10,920 --> 00:01:13,440 +具有随机初始化的权重。 +with randomly initialized weights. + +31 +00:01:13,440 --> 00:01:15,870 +所以这个方法需要知道两件事。 +So this method needs to know two things. + +32 +00:01:15,870 --> 00:01:18,270 +首先,它需要知道模型的名称 +Firstly, it needs to know the name of the model + +33 +00:01:18,270 --> 00:01:21,060 +你想要加载的架构和权重。 +you wanted to load, the architecture and weights for. + +34 +00:01:21,060 --> 00:01:23,940 +其次,它需要知道有多少类 +And secondly, it needs to know how many classes + +35 +00:01:23,940 --> 00:01:26,693 +你的问题有,因为这将决定大小, +your problem has, because that will determine the size, + +36 +00:01:26,693 --> 00:01:29,610 +输出头中的神经元数量。 +the number of neurons in the output head. + +37 +00:01:29,610 --> 00:01:31,530 +所以如果你想跟随数据 +So if you want to follow along with the data + +38 +00:01:31,530 --> 00:01:34,500 +来自我们的数据集视频,我将在下面链接, +from our datasets videos, which I'll link below, + +39 +00:01:34,500 --> 00:01:37,440 +那么你将有两个班级,积极的和消极的, +then you'll have two classes, positive and negative, + +40 +00:01:37,440 --> 00:01:39,723 +因此 num_labels 等于二。 +and thus num_labels equals two. + +41 +00:01:40,830 --> 00:01:43,230 +但是这个编译行呢? +But what about this compile line? + +42 +00:01:43,230 --> 00:01:44,970 +好吧,如果你熟悉 Keras, +Well, if you're familiar with Keras, + +43 +00:01:44,970 --> 00:01:46,920 +你可能已经看过了。 +you've probably seen this already. + +44 +00:01:46,920 --> 00:01:49,800 +但如果不是,这是 Keras 中的核心方法之一 +But if not, this is one of the core methods in Keras + +45 +00:01:49,800 --> 00:01:51,450 +你会一次又一次地看到。 +that you're gonna see again, and again. + +46 +00:01:51,450 --> 00:01:54,900 +你总是需要在训练之前编译你的模型。 +You always need to compile your model before you train it. + +47 +00:01:54,900 --> 00:01:57,870 +编译需要知道两件事。 +And compile needs to know two things. + +48 +00:01:57,870 --> 00:02:00,090 +首先,它需要知道损失函数, +Firstly, it needs to know the loss function, + +49 +00:02:00,090 --> 00:02:02,340 +这就是你要优化的内容。 +which is what you're trying to optimize. + +50 +00:02:02,340 --> 00:02:05,910 +所以在这里,我们导入 SparseCategoricalCrossentropy +So here, we import the SparseCategoricalCrossentropy + +51 +00:02:05,910 --> 00:02:07,260 +损失函数。 +loss function. + +52 +00:02:07,260 --> 00:02:09,930 +所以这是一口,但它是标准的损失函数 +So that's a mouthful, but it's the standard loss function + +53 +00:02:09,930 --> 00:02:13,260 +对于任何正在执行分类任务的神经网络。 +for any neural network that's doing a classification task. + +54 +00:02:13,260 --> 00:02:14,970 +它基本上鼓励网络 +It basically encourages the network + +55 +00:02:14,970 --> 00:02:17,730 +为正确的类输出大的值, +to output large values for the right class, + +56 +00:02:17,730 --> 00:02:20,910 +以及错误类别的低值。 +and low values for the wrong classes. + +57 +00:02:20,910 --> 00:02:24,150 +请注意,你可以将损失函数指定为字符串, +Note that you can specify the loss function as a string, + +58 +00:02:24,150 --> 00:02:26,010 +就像我们对优化器所做的那样。 +like we did with the optimizer. + +59 +00:02:26,010 --> 00:02:27,600 +但这里有一个风险, +But there's a risk there, + +60 +00:02:27,600 --> 00:02:30,090 +人们容易掉入一个非常普遍的陷阱, +there's a very common trap people fall into, + +61 +00:02:30,090 --> 00:02:32,580 +这是默认情况下,这种损失假设 +which is that by default, this loss assumes + +62 +00:02:32,580 --> 00:02:36,510 +输出是 softmax 层之后的概率。 +the output is probabilities after a softmax layer. + +63 +00:02:36,510 --> 00:02:38,310 +但是我们的模型实际输出了什么 +But what our model has actually output + +64 +00:02:38,310 --> 00:02:40,770 +是 softmax 之前的值, +is the values before the softmax, + +65 +00:02:40,770 --> 00:02:43,800 +通常称为 logits,有时称为 logits。 +often called the logits, sometimes logits. + +66 +00:02:43,800 --> 00:02:46,110 +没有人十分确定如何发音。 +No one's quite sure how to pronounce that one. + +67 +00:02:46,110 --> 00:02:47,790 +但你可能以前见过这些 +But you probably seen these before + +68 +00:02:47,790 --> 00:02:49,950 +在关于管道的视频中。 +in the video about pipelines. + +69 +00:02:49,950 --> 00:02:52,320 +所以如果你弄错了,你的模型就不会训练 +So if you get this wrong, your model won't train + +70 +00:02:52,320 --> 00:02:54,723 +弄清楚原因会很烦人。 +and it'll be very annoying to figure out why. + +71 +00:02:55,590 --> 00:02:57,540 +在以后的视频中,我们会看到 +In future videos, we're gonna see + +72 +00:02:57,540 --> 00:03:00,540 +如何使用模型的内部损失计算, +how to use the model's internal loss computations, + +73 +00:03:00,540 --> 00:03:02,910 +这样你就不必自己指定损失 +so that you don't have to specify the loss yourself + +74 +00:03:02,910 --> 00:03:05,340 +而且你不必担心这些细节。 +and you don't have to worry about these details. + +75 +00:03:05,340 --> 00:03:09,480 +但是现在,请记住将 from_logits 设置为 true。 +But for now, remember to set from_logits to true. + +76 +00:03:09,480 --> 00:03:11,430 +compile 需要知道的第二件事 +The second thing compile needs to know + +77 +00:03:11,430 --> 00:03:13,230 +是你想要的优化器。 +is the optimizer you want. + +78 +00:03:13,230 --> 00:03:15,120 +在我们的例子中,我们使用亚当, +In our case, we use adam, + +79 +00:03:15,120 --> 00:03:16,830 +这是一种标准的优化器 +which is sort of the standard optimizer + +80 +00:03:16,830 --> 00:03:18,720 +这些天用于深度学习。 +for deep learning these days. + +81 +00:03:18,720 --> 00:03:20,520 +你可能想要改变的一件事 +The one thing you might want to change + +82 +00:03:20,520 --> 00:03:21,780 +是学习率。 +is the learning rate. + +83 +00:03:21,780 --> 00:03:24,630 +为此,我们需要导入实际的优化器 +And to do that, we'll need to import the actual optimizer + +84 +00:03:24,630 --> 00:03:26,910 +而不仅仅是通过字符串调用它。 +rather than just calling it by string. + +85 +00:03:26,910 --> 00:03:28,680 +但我们会在另一个视频中讨论这个, +But we'll talk about that in another video, + +86 +00:03:28,680 --> 00:03:30,090 +我将在下面链接。 +which I'll link below. + +87 +00:03:30,090 --> 00:03:33,360 +现在,让我们尝试训练模型。 +For now, let's just try training the model. + +88 +00:03:33,360 --> 00:03:35,580 +那么,你如何训练模型呢? +Well, so how do you train the model? + +89 +00:03:35,580 --> 00:03:37,950 +同样,如果你以前使用过 Keras, +Again, if you've used Keras before, + +90 +00:03:37,950 --> 00:03:40,350 +这一切对你来说都很熟悉。 +this is all going to be very familiar to you. + +91 +00:03:40,350 --> 00:03:42,210 +但如果没有,让我们快速看看 +But if not, let's very quickly look + +92 +00:03:42,210 --> 00:03:43,710 +我们在这里做什么。 +at what we're doing here. + +93 +00:03:43,710 --> 00:03:47,010 +fit 几乎是 Keras 模型的核心方法。 +fit is pretty much the central method for Keras models. + +94 +00:03:47,010 --> 00:03:49,983 +它告诉模型根据我们传入的数据进行训练。 +It tells the model to train on the data we're passing in. + +95 +00:03:50,820 --> 00:03:52,920 +所以这里我们传递我们制作的数据集 +So here we pass the datasets we made + +96 +00:03:52,920 --> 00:03:54,510 +在上一节中, +in the previous section, + +97 +00:03:54,510 --> 00:03:57,990 +数据集包含我们的输入和标签。 +the dataset contains both our inputs and our labels. + +98 +00:03:57,990 --> 00:04:00,420 +所以我们不需要指定单独的标签, +So we don't need to specify separate labels, + +99 +00:04:00,420 --> 00:04:01,570 +当我们称呼健康时。 +when we're calling fit. + +100 +00:04:02,490 --> 00:04:05,340 +然后我们对 validation_data 做同样的事情。 +Then we do the same thing with the validation_data. + +101 +00:04:05,340 --> 00:04:08,190 +然后我们可以,如果我们愿意,我们可以指定细节, +And then we can if we want, we can specify details, + +102 +00:04:08,190 --> 00:04:09,900 +比如训练的次数 +like the number of epochs for training + +103 +00:04:09,900 --> 00:04:12,420 +你可以传递一些其他参数来适应。 +where there's some other arguments you can pass to fit. + +104 +00:04:12,420 --> 00:04:15,240 +但最后,你只需将所有这些传递给 model.fit +But in the end, you just pass all of this to model.fit + +105 +00:04:15,240 --> 00:04:16,440 +然后你让它运行。 +and you let it run. + +106 +00:04:16,440 --> 00:04:17,520 +如果一切顺利, +If everything works out, + +107 +00:04:17,520 --> 00:04:19,320 +你应该看到一个小训练栏 +you should see a little training bar + +108 +00:04:19,320 --> 00:04:21,300 +随着损失的减少而进步。 +progressing along as your loss goes down. + +109 +00:04:21,300 --> 00:04:22,290 +就是这样。 +And that's it. + +110 +00:04:22,290 --> 00:04:23,123 +在运行时, +While that's running, + +111 +00:04:23,123 --> 00:04:25,380 +你知道,你可以打电话给你的老板并告诉他们 +you know, you can call your boss and tell them + +112 +00:04:25,380 --> 00:04:27,810 +你现在是高级 NLP 机器学习工程师 +you're a senior NLP machine learning engineer now + +113 +00:04:27,810 --> 00:04:30,900 +你会想要下个季度的薪水审查。 +and you're gonna want a salary review next quarter. + +114 +00:04:30,900 --> 00:04:32,880 +这几行代码真的够用了 +These few lines of code are really all it takes + +115 +00:04:32,880 --> 00:04:34,500 +应用大量的力量 +to apply the power of a massive + +116 +00:04:34,500 --> 00:04:36,510 +预训练语言问题, +pre-trained language problem, + +117 +00:04:36,510 --> 00:04:38,250 +大规模的预训练语言模型,不好意思, +massive pre-trained language model, excuse me, + +118 +00:04:38,250 --> 00:04:40,080 +到你的 NLP 问题。 +to your NLP problem. + +119 +00:04:40,080 --> 00:04:42,150 +但我们能做得更好吗? +But could we do better than this? + +120 +00:04:42,150 --> 00:04:43,920 +我的意思是,我们当然可以。 +I mean, we certainly could. + +121 +00:04:43,920 --> 00:04:45,720 +具有一些更高级的 Keras 功能 +With a few more advanced Keras features + +122 +00:04:45,720 --> 00:04:47,730 +就像一个经过调整的、预定的学习率, +like a tuned, scheduled learning rate, + +123 +00:04:47,730 --> 00:04:49,290 +我们可以获得更低的损失 +we can get an even lower loss + +124 +00:04:49,290 --> 00:04:51,990 +以及一个更准确、更有用的模型。 +and an even more accurate, more useful model. + +125 +00:04:51,990 --> 00:04:54,120 +训练模型后,我们如何处理模型? +And what do we do with our model after we train it? + +126 +00:04:54,120 --> 00:04:55,950 +所以所有这些都将包含在视频中 +So all of this is going to be covered in the videos + +127 +00:04:55,950 --> 00:04:57,963 +即将推出,敬请期待。 +that are coming up, so stay tuned. + +128 +00:04:59,220 --> 00:05:01,887 +(空气呼啸) +(air whooshing) + diff --git a/subtitles/zh-CN/27_learning-rate-scheduling-with-tensorflow.srt b/subtitles/zh-CN/27_learning-rate-scheduling-with-tensorflow.srt new file mode 100644 index 000000000..700402578 --- /dev/null +++ b/subtitles/zh-CN/27_learning-rate-scheduling-with-tensorflow.srt @@ -0,0 +1,530 @@ +1 +00:00:00,288 --> 00:00:02,639 +(画面沙沙作响) +(screen swishing) + +2 +00:00:02,639 --> 00:00:05,190 +(文字嗖嗖) +(text swishing) + +3 +00:00:05,190 --> 00:00:06,780 +在我们的其他视频中, +In our other videos, + +4 +00:00:06,780 --> 00:00:08,280 +我们讨论了基础知识 +we talked about the basics + +5 +00:00:08,280 --> 00:00:11,610 +使用 Tensorflow 微调语言模型, +of fine-tuning a language model with Tensorflow, + +6 +00:00:11,610 --> 00:00:15,030 +和往常一样,当我提到视频时,我会在下面链接它们。 +and as always, when I refer to videos I'll link them below. + +7 +00:00:15,030 --> 00:00:17,610 +不过,我们可以做得更好吗? +Still, can we do better? + +8 +00:00:17,610 --> 00:00:20,700 +这是我们模型微调视频中的代码, +So here's the code from our model fine-tuning video, + +9 +00:00:20,700 --> 00:00:21,600 +在它起作用的同时, +and while it works, + +10 +00:00:21,600 --> 00:00:24,390 +我们绝对可以调整一些东西。 +we could definitely tweak a couple of things. + +11 +00:00:24,390 --> 00:00:27,540 +到目前为止,最重要的是学习率。 +By far the most important thing is the learning rate. + +12 +00:00:27,540 --> 00:00:29,940 +在本视频中,我们将讨论如何更改它, +In this video we'll talk about how to change it, + +13 +00:00:29,940 --> 00:00:31,080 +这将使你的训练 +which will make your training + +14 +00:00:31,080 --> 00:00:33,303 +更加一致地成功。 +much more consistently successful. + +15 +00:00:34,440 --> 00:00:37,320 +其实真的有两件事 +In fact, really there are two things + +16 +00:00:37,320 --> 00:00:40,530 +我们想改变 Adam 的默认学习率。 +we want to change about the default learning rate for Adam. + +17 +00:00:40,530 --> 00:00:42,720 +所以首先我们要改变 +So the first we want to change + +18 +00:00:42,720 --> 00:00:45,630 +是它对我们的模型来说太高了, +is that it's way too high for our models, + +19 +00:00:45,630 --> 00:00:48,030 +默认情况下,Adam 使用学习率 +by default, Adam uses a learning rate + +20 +00:00:48,030 --> 00:00:51,540 +10 的负 3,1 E 负 3, +of 10 to the minus 3, 1 E minus 3, + +21 +00:00:51,540 --> 00:00:54,660 +这对于训练变压器模型来说非常高。 +and that's very high for training transformer models. + +22 +00:00:54,660 --> 00:00:58,200 +我们将从 5 乘 10 到负 5 开始, +We're going to start at 5 by 10 to the minus 5, + +23 +00:00:58,200 --> 00:01:02,700 +5 E - 5,比默认值低 20 倍。 +5 E minus 5, which is 20 times lower than the default. + +24 +00:01:02,700 --> 00:01:06,330 +其次,我们不只是想要一个恒定的学习率, +And secondly, we don't just want a constant learning rate, + +25 +00:01:06,330 --> 00:01:07,950 +我们可以获得更好的性能 +we can get even better performance + +26 +00:01:07,950 --> 00:01:11,160 +如果我们将学习率降低到一个很小的值, +if we decay the learning rate down to a tiny value, + +27 +00:01:11,160 --> 00:01:13,920 +甚至在培训过程中为零。 +or even to zero , over the course of training. + +28 +00:01:13,920 --> 00:01:15,510 +这就是这里的东西, +So that's what this thing here, + +29 +00:01:15,510 --> 00:01:18,540 +这个 Polynomial Decay schedule 事情正在做。 +this Polynomial Decay schedule thing is doing. + +30 +00:01:18,540 --> 00:01:21,570 +等会儿我会告诉你衰变是什么样子的, +So I'll show you what that decay looks like in a second, + +31 +00:01:21,570 --> 00:01:23,160 +但首先我们需要告诉调度程序 +but first we need to tell the scheduler + +32 +00:01:23,160 --> 00:01:25,290 +培训将持续多长时间, +how long training is going to be, + +33 +00:01:25,290 --> 00:01:27,450 +以便它以正确的速度衰减, +so that it decays at the right speed, + +34 +00:01:27,450 --> 00:01:29,450 +这就是这里的代码所做的。 +and that's what this code here is doing. + +35 +00:01:30,300 --> 00:01:32,280 +我们正在计算有多少小批量 +We're computing how many minibatches + +36 +00:01:32,280 --> 00:01:35,520 +该模型将在其整个训练过程中进行观察, +the model is going to see over its entire training run, + +37 +00:01:35,520 --> 00:01:37,950 +这是训练集的大小, +which is the size of the training set, + +38 +00:01:37,950 --> 00:01:39,570 +然后我们乘以它 +and then we multiply that + +39 +00:01:39,570 --> 00:01:41,220 +按纪元数 +by the number of epochs + +40 +00:01:41,220 --> 00:01:42,930 +获得批次总数 +to get the total number of batches + +41 +00:01:42,930 --> 00:01:45,060 +在整个训练过程中。 +across the whole training run. + +42 +00:01:45,060 --> 00:01:47,880 +一旦我们知道我们进行了多少训练步骤, +Once we know how many training steps we're taking, + +43 +00:01:47,880 --> 00:01:50,580 +我们只是将所有这些信息传递给调度程序 +we just pass all that information to the scheduler + +44 +00:01:50,580 --> 00:01:51,783 +我们准备好了。 +and we're ready to go. + +45 +00:01:53,110 --> 00:01:57,510 +多项式衰减时间表是什么样的? +What does the polynomial decay schedule look like? + +46 +00:01:57,510 --> 00:01:59,610 +嗯,看起来像这样, +Well, it looks like this, + +47 +00:01:59,610 --> 00:02:02,160 +它从 5 E 减 5 开始, +it starts at 5 E minus 5, + +48 +00:02:02,160 --> 00:02:05,490 +这意味着 10 的 5 乘以负 5, +which means 5 times 10 to the minus 5, + +49 +00:02:05,490 --> 00:02:08,190 +然后以恒定速率衰减 +and then decays down at a constant rate + +50 +00:02:08,190 --> 00:02:11,310 +直到它在训练结束时达到零。 +until it hits zero right at the very end of training. + +51 +00:02:11,310 --> 00:02:13,200 +所以等一下,我已经能听到你的声音了 +So hold on, I can already hear you + +52 +00:02:13,200 --> 00:02:14,640 +不过,对着你的显示器大喊大叫, +yelling at your monitor, though, + +53 +00:02:14,640 --> 00:02:16,020 +是的,我知道, +and yes, I know, + +54 +00:02:16,020 --> 00:02:18,690 +这实际上是常数或线性衰减, +this is actually constant or a linear decay, + +55 +00:02:18,690 --> 00:02:20,310 +我知道这个名字是多项式的, +and I know the name is polynomial, + +56 +00:02:20,310 --> 00:02:21,870 +你感觉被骗了,你知道的, +and you're feeling cheated that, you know, + +57 +00:02:21,870 --> 00:02:24,390 +你被许诺了一个多项式但还没有得到它, +you were promised a polynomial and haven't gotten it, + +58 +00:02:24,390 --> 00:02:26,550 +所以冷静下来,没关系, +so calm down though, it's okay, + +59 +00:02:26,550 --> 00:02:28,830 +因为,当然,线性函数只是 +because, of course, linear functions are just + +60 +00:02:28,830 --> 00:02:30,480 +一阶特例 +first-order special cases + +61 +00:02:30,480 --> 00:02:32,850 +的一般多项式函数, +of the general polynomial functions, + +62 +00:02:32,850 --> 00:02:36,180 +如果你调整这个类的选项, +and if you tweak the options to this class, + +63 +00:02:36,180 --> 00:02:38,130 +你可以获得一个真正的多项式, +you can get a truly polynomial, + +64 +00:02:38,130 --> 00:02:40,170 +高阶衰减时间表, +a higher-order decay schedule, + +65 +00:02:40,170 --> 00:02:43,140 +但现在这个线性时间表对我们来说还行, +but this linear schedule will work fine for us for now, + +66 +00:02:43,140 --> 00:02:45,210 +我们实际上并不需要所有这些 +we don't actually need all those + +67 +00:02:45,210 --> 00:02:47,610 +花哨的调整和花哨的小工具。 +fancy tweaks and fancy gadgets. + +68 +00:02:47,610 --> 00:02:49,770 +所以回来, +So coming back, + +69 +00:02:49,770 --> 00:02:51,990 +我们如何实际使用这个学习率计划 +how do we actually use this learning rate schedule + +70 +00:02:51,990 --> 00:02:53,460 +一旦我们创造了它? +once we've created it? + +71 +00:02:53,460 --> 00:02:55,650 +所以很简单,我们只需将其传递给 Adam。 +So it's simple, we just pass it to Adam. + +72 +00:02:55,650 --> 00:02:58,560 +所以我们第一次编译模型时, +So the first time we compiled the model, + +73 +00:02:58,560 --> 00:03:00,840 +我们刚刚传递了字符串 Adam, +we just passed the string Adam, + +74 +00:03:00,840 --> 00:03:02,250 +得到我们的优化器。 +to get our optimizer. + +75 +00:03:02,250 --> 00:03:05,340 +所以 Keras 识别常见优化器的名称 +So Keras recognizes the names of common optimizers + +76 +00:03:05,340 --> 00:03:07,920 +和损失函数,如果你将它们作为字符串传递, +and loss functions if you pass them as strings, + +77 +00:03:07,920 --> 00:03:09,480 +这样可以节省时间 +so it saves time to do that + +78 +00:03:09,480 --> 00:03:11,460 +如果你只想要默认设置。 +if you only want the default settings. + +79 +00:03:11,460 --> 00:03:13,320 +但现在我们是专业的机器学习者, +But now we're professional machine learners, + +80 +00:03:13,320 --> 00:03:15,720 +而且,你知道,薪资审查即将到来, +and, you know, that salary review is upcoming, + +81 +00:03:15,720 --> 00:03:17,790 +所以我们有自己的学习率时间表, +so we've got our very own learning rate schedule, + +82 +00:03:17,790 --> 00:03:19,770 +我们会把事情做好。 +and we're gonna do things properly. + +83 +00:03:19,770 --> 00:03:22,830 +所以我们首先要做的是导入优化器, +So the first we do is we import the optimizer, + +84 +00:03:22,830 --> 00:03:24,960 +然后我们用调度程序初始化它, +and then we initialize it with a scheduler, + +85 +00:03:24,960 --> 00:03:27,540 +它被传递给学习率参数 +which is getting passed to to the learning rate argument + +86 +00:03:27,540 --> 00:03:29,100 +该优化器。 +of that optimizer. + +87 +00:03:29,100 --> 00:03:32,190 +现在我们用这个新的优化器编译模型, +And now we compile the model with this new optimizer, + +88 +00:03:32,190 --> 00:03:34,140 +再一次,无论你想要什么损失函数, +and again, whatever loss function you want, + +89 +00:03:34,140 --> 00:03:37,050 +所以这将是稀疏的分类交叉熵 +so this is going to be sparse categorical crossentropy + +90 +00:03:37,050 --> 00:03:39,840 +如果你正在关注微调视频。 +if you're following along from the fine-tuning video. + +91 +00:03:39,840 --> 00:03:41,370 +然后,我们准备好了, +And then, we're we're ready to go, + +92 +00:03:41,370 --> 00:03:43,710 +现在我们有了一个高性能模型, +now we have a high-performance model, + +93 +00:03:43,710 --> 00:03:44,970 +并准备接受训练。 +and ready for training. + +94 +00:03:44,970 --> 00:03:46,830 +剩下的就是拟合模型 +All that remains is to fit the model + +95 +00:03:46,830 --> 00:03:48,363 +就像我们以前做的一样。 +just like we did before. + +96 +00:03:49,350 --> 00:03:51,600 +记住,因为我们编译了模型 +Remember, because we compiled the model + +97 +00:03:51,600 --> 00:03:54,300 +使用新的优化器和新的学习率计划, +with the new optimizer and the new learning rate schedule, + +98 +00:03:54,300 --> 00:03:56,190 +我们实际上根本不需要改变任何东西 +we actually don't need to change anything at all + +99 +00:03:56,190 --> 00:03:57,360 +当我们称呼合适时, +when we call fit, + +100 +00:03:57,360 --> 00:03:58,290 +我们只是再次调用它, +we just call it again, + +101 +00:03:58,290 --> 00:04:00,540 +使用与之前完全相同的命令, +with exactly the same command as before, + +102 +00:04:00,540 --> 00:04:02,400 +但现在我们得到了很好的训练, +but now we get a beautiful training, + +103 +00:04:02,400 --> 00:04:04,740 +有一个很好的,平滑的学习率衰减, +with a nice, smooth learning rate decay, + +104 +00:04:04,740 --> 00:04:06,330 +从好的价值开始, +starting from a good value, + +105 +00:04:06,330 --> 00:04:07,713 +并衰减到零。 +and decaying down to zero. + +106 +00:04:08,867 --> 00:04:13,395 +(画面沙沙作响) +(screen swishing) + diff --git a/subtitles/zh-CN/28_tensorflow-predictions-and-metrics.srt b/subtitles/zh-CN/28_tensorflow-predictions-and-metrics.srt new file mode 100644 index 000000000..9d157d4d1 --- /dev/null +++ b/subtitles/zh-CN/28_tensorflow-predictions-and-metrics.srt @@ -0,0 +1,525 @@ +1 +00:00:00,269 --> 00:00:02,936 +(空气呼啸) +(air whooshing) + +2 +00:00:05,700 --> 00:00:07,110 +- 在我们的其他视频中, +- In our other videos, + +3 +00:00:07,110 --> 00:00:09,000 +和往常一样,下面会有链接 +and as always, there'll be links below + +4 +00:00:09,000 --> 00:00:10,740 +如果你想检查那些, +if you want to check those out, + +5 +00:00:10,740 --> 00:00:13,230 +我们向你展示了如何初始化和微调 +we showed you how to initialize and fine-tune + +6 +00:00:13,230 --> 00:00:15,690 +TensorFlow 中的转换器模型。 +a transformer model in TensorFlow. + +7 +00:00:15,690 --> 00:00:18,600 +所以现在的问题是,我们可以用模型做什么 +So the question now is, what can we do with a model + +8 +00:00:18,600 --> 00:00:20,070 +在我们训练之后? +after we train it? + +9 +00:00:20,070 --> 00:00:21,390 +显而易见的尝试 +The obvious thing to try + +10 +00:00:21,390 --> 00:00:23,790 +是用它来获得对新数据的预测, +is to use it to get predictions for new data, + +11 +00:00:23,790 --> 00:00:25,560 +所以让我们看看如何做到这一点。 +so let's see how to do that. + +12 +00:00:25,560 --> 00:00:28,320 +同样,如果你熟悉 Keras,那么好消息 +Again, if you're familiar with Keras, the good news + +13 +00:00:28,320 --> 00:00:31,860 +是因为只有标准的 Keras 模型, +is that because there are just standard Keras models, + +14 +00:00:31,860 --> 00:00:34,770 +我们可以使用标准的 Keras 预测方法, +we can use the standard Keras predict method, + +15 +00:00:34,770 --> 00:00:35,883 +如此处所示。 +as shown here. + +16 +00:00:36,990 --> 00:00:40,560 +你只需将标记化的文本传递给此方法, +You simply pass in tokenized text to this method, + +17 +00:00:40,560 --> 00:00:42,330 +就像你从分词器那里得到的一样, +like you'd get from a tokenizer, + +18 +00:00:42,330 --> 00:00:44,280 +你得到你的结果。 +and you get your results. + +19 +00:00:44,280 --> 00:00:46,740 +我们的模型可以输出几种不同的东西, +Our models can output several different things, + +20 +00:00:46,740 --> 00:00:48,510 +根据你设置的选项, +depending on the options you set, + +21 +00:00:48,510 --> 00:00:50,310 +但大多数时候你想要的东西 +but most of the time the thing you want + +22 +00:00:50,310 --> 00:00:52,290 +是输出逻辑。 +is the output logits. + +23 +00:00:52,290 --> 00:00:54,900 +如果你在登录之前没有遇到过它们, +If you haven't come across them before logits, + +24 +00:00:54,900 --> 00:00:57,630 +有时对 logits 发音,因为没有人确定, +sometimes pronounced to logits because no one's sure, + +25 +00:00:57,630 --> 00:01:00,390 +是网络最后一层的输出 +are the outputs of the last layer of the network + +26 +00:01:00,390 --> 00:01:03,150 +因为在应用 softmax 之前。 +because before a softmax has been applied. + +27 +00:01:03,150 --> 00:01:04,710 +所以如果你想把 logits +So if you want to turn the logits + +28 +00:01:04,710 --> 00:01:06,900 +进入模型的概率输出, +into the model's probability outputs, + +29 +00:01:06,900 --> 00:01:09,423 +你只需应用一个 softmax,就像这样。 +you just apply a softmax, like so. + +30 +00:01:10,981 --> 00:01:12,630 +如果我们想改变这些概率怎么办 +What if we want to turn those probabilities + +31 +00:01:12,630 --> 00:01:14,370 +进入课堂预测? +into class predictions? + +32 +00:01:14,370 --> 00:01:16,410 +同样,它非常简单。 +Again, it's very straightforward. + +33 +00:01:16,410 --> 00:01:19,470 +我们只是为每个输出选择最大的概率 +We just pick the biggest probability for each output + +34 +00:01:19,470 --> 00:01:23,070 +你可以使用 argmax 函数立即获得它。 +and you can get that immediately with the argmax function. + +35 +00:01:23,070 --> 00:01:24,870 +argmax 将返回索引 +argmax will return the index + +36 +00:01:24,870 --> 00:01:27,120 +每行中的最大概率 +of the largest probability in each row + +37 +00:01:27,120 --> 00:01:30,360 +这意味着我们将得到一个整数向量。 +which means that we'll get a vector of integers. + +38 +00:01:30,360 --> 00:01:34,950 +如果最大概率在零位置,则为零, +So zero if the largest probability was in the zero position, + +39 +00:01:34,950 --> 00:01:37,350 +一个在第一个位置,依此类推。 +one in the first position, and so on. + +40 +00:01:37,350 --> 00:01:40,380 +所以这些是我们的类预测表明类零, +So these are our class predictions indicating class zero, + +41 +00:01:40,380 --> 00:01:42,300 +第一类,等等。 +class one, and so on. + +42 +00:01:42,300 --> 00:01:45,090 +事实上,如果你想要的只是课堂预测, +In fact, if class predictions are all you want, + +43 +00:01:45,090 --> 00:01:47,310 +你可以完全跳过 softmax 步骤 +you can skip the softmax step entirely + +44 +00:01:47,310 --> 00:01:49,740 +因为最大的 logit 永远是最大的 +because the largest logit will always be the largest + +45 +00:01:49,740 --> 00:01:51,303 +概率也一样。 +probability as well. + +46 +00:01:52,500 --> 00:01:55,800 +所以如果你想要概率和类别预测, +So if probabilities and class predictions are all you want, + +47 +00:01:55,800 --> 00:01:58,350 +那么此时你已经看到了所需的一切。 +then you've seen everything you need at this point. + +48 +00:01:58,350 --> 00:02:00,630 +但是如果你有兴趣对你的模型进行基准测试 +But if you're interested in benchmarking your model + +49 +00:02:00,630 --> 00:02:02,190 +或将其用于研究, +or using it for research, + +50 +00:02:02,190 --> 00:02:05,010 +你可能想更深入地研究你得到的结果。 +you might want to delve deeper into the results you get. + +51 +00:02:05,010 --> 00:02:07,230 +一种方法是计算一些指标 +And one way to do that is to compute some metrics + +52 +00:02:07,230 --> 00:02:09,060 +用于模型的预测。 +for the model's predictions. + +53 +00:02:09,060 --> 00:02:10,920 +如果你关注我们的数据集 +If you're following along with our datasets + +54 +00:02:10,920 --> 00:02:12,390 +和微调视频, +and fine tuning videos, + +55 +00:02:12,390 --> 00:02:14,850 +我们从 MRPC 数据集中获取数据, +we got our data from the MRPC dataset, + +56 +00:02:14,850 --> 00:02:17,130 +这是 GLUE 基准的一部分。 +which is part of the GLUE benchmark. + +57 +00:02:17,130 --> 00:02:19,050 +每个 GLUE 数据集 +Each of the GLUE datasets + +58 +00:02:19,050 --> 00:02:22,560 +以及我们数据集 Light Hub 中的许多其他数据集 +as well as many other datasets in our dataset, Light Hub + +59 +00:02:22,560 --> 00:02:24,510 +有一些预定义的指标, +has some predefined metrics, + +60 +00:02:24,510 --> 00:02:26,940 +我们可以轻松加载它们 +and we can load them easily + +61 +00:02:26,940 --> 00:02:29,880 +使用数据集加载度量函数。 +with the datasets load metric function. + +62 +00:02:29,880 --> 00:02:33,720 +对于 MRPC 数据集,内置指标是准确性 +For the MRPC dataset, the built-in metrics are accuracy + +63 +00:02:33,720 --> 00:02:35,790 +它只是衡量时间的百分比 +which just measures the percentage of the time + +64 +00:02:35,790 --> 00:02:37,830 +模型的预测是正确的, +the model's prediction was correct, + +65 +00:02:37,830 --> 00:02:39,780 +和 F1 分数, +and the F1 score, + +66 +00:02:39,780 --> 00:02:41,610 +这是一个稍微复杂的措施 +which is a slightly more complex measure + +67 +00:02:41,610 --> 00:02:43,920 +衡量模型权衡的程度 +that measures how well the model trades off + +68 +00:02:43,920 --> 00:02:45,543 +准确率和召回率。 +precision and recall. + +69 +00:02:46,470 --> 00:02:49,110 +要计算这些指标以对我们的模型进行基准测试, +To compute those metrics to benchmark our model, + +70 +00:02:49,110 --> 00:02:51,480 +我们只是将模型的预测传递给他们, +we just pass them the model's predictions, + +71 +00:02:51,480 --> 00:02:53,220 +和真实标签, +and to the ground truth labels, + +72 +00:02:53,220 --> 00:02:56,880 +我们在一个简单的 Python dict 中得到我们的结果。 +and we get our results in a straightforward Python dict. + +73 +00:02:56,880 --> 00:02:58,740 +如果你熟悉 Keras, +If you're familiar with Keras though, + +74 +00:02:58,740 --> 00:03:00,870 +你可能会注意到这是一种有点奇怪的方式 +you might notice that this is a slightly weird way + +75 +00:03:00,870 --> 00:03:01,800 +计算指标, +to compute metrics, + +76 +00:03:01,800 --> 00:03:02,970 +因为我们只计算指标 +because we're only computing metrics + +77 +00:03:02,970 --> 00:03:04,440 +在训练的最后。 +at the very end of training. + +78 +00:03:04,440 --> 00:03:06,480 +但是在 Keras 中,你有这个内置的能力 +But in Keras, you have this built-in ability + +79 +00:03:06,480 --> 00:03:08,790 +计算范围广泛的指标 +to compute a wide range of metrics + +80 +00:03:08,790 --> 00:03:10,470 +在你训练的过程中, +on the fly while you're training, + +81 +00:03:10,470 --> 00:03:11,910 +这给了你一个非常有用的见解 +which gives you a very useful insight + +82 +00:03:11,910 --> 00:03:13,740 +了解培训的进展情况。 +into how training is going. + +83 +00:03:13,740 --> 00:03:15,900 +所以如果你想使用内置指标, +So if you want to use built-in metrics, + +84 +00:03:15,900 --> 00:03:17,280 +这很简单 +it's very straightforward + +85 +00:03:17,280 --> 00:03:19,350 +然后你再次使用标准的 Keras 方法。 +and you use the standard Keras approach again. + +86 +00:03:19,350 --> 00:03:23,160 +你只需将一个度量参数传递给编译方法。 +You just pass a metric argument to the compile method. + +87 +00:03:23,160 --> 00:03:25,740 +与损失和优化器之类的东西一样, +As with things like loss and optimizer, + +88 +00:03:25,740 --> 00:03:28,470 +你可以通过字符串指定你想要的指标 +you can specify the metrics you want by string + +89 +00:03:28,470 --> 00:03:30,810 +或者你可以导入实际的指标对象 +or you can import the actual metric objects + +90 +00:03:30,810 --> 00:03:33,240 +并向他们传递具体的论点。 +and pass specific arguments to them. + +91 +00:03:33,240 --> 00:03:35,610 +但请注意,与损失和准确性不同, +But note that unlike loss and accuracy, + +92 +00:03:35,610 --> 00:03:37,710 +你必须以列表形式提供指标 +you have to supply metrics as a list + +93 +00:03:37,710 --> 00:03:39,760 +即使你只需要一个指标。 +even if there's only one metric you want. + +94 +00:03:40,770 --> 00:03:43,140 +一旦用度量标准编译了模型, +Once a model has been compiled with a metric, + +95 +00:03:43,140 --> 00:03:45,360 +它将报告该训练指标, +it will report that metric for training, + +96 +00:03:45,360 --> 00:03:47,643 +验证和预测。 +validation, and predictions. + +97 +00:03:48,480 --> 00:03:50,820 +假设有标签传递给预测。 +Assuming there are labels passed to the predictions. + +98 +00:03:50,820 --> 00:03:53,400 +你甚至可以编写自己的度量类。 +You can even write your own metric classes. + +99 +00:03:53,400 --> 00:03:55,920 +虽然这有点超出本课程的范围, +Although this is a bit beyond the scope of this course, + +100 +00:03:55,920 --> 00:03:58,200 +我将链接到下面的相关 TF 文档 +I'll link to the relevant TF docs below + +101 +00:03:58,200 --> 00:03:59,580 +因为它可以非常方便 +because it can be very handy + +102 +00:03:59,580 --> 00:04:01,320 +如果你想要一个不受支持的指标 +if you want a metric that isn't supported + +103 +00:04:01,320 --> 00:04:02,850 +默认情况下,在 Keras 中, +by default in Keras, + +104 +00:04:02,850 --> 00:04:04,473 +比如 F1 分数。 +such as the F1 score. + +105 +00:04:06,076 --> 00:04:08,743 +(空气呼啸) +(air whooshing) + diff --git a/subtitles/zh-CN/29_write-your-training-loop-in-pytorch.srt b/subtitles/zh-CN/29_write-your-training-loop-in-pytorch.srt new file mode 100644 index 000000000..732483f13 --- /dev/null +++ b/subtitles/zh-CN/29_write-your-training-loop-in-pytorch.srt @@ -0,0 +1,620 @@ +1 +00:00:00,298 --> 00:00:01,511 +(空气呼啸) +(air whooshing) + +2 +00:00:01,511 --> 00:00:02,769 +(笑脸弹出) +(smiley face popping) + +3 +00:00:02,769 --> 00:00:05,460 +(空气呼啸) +(air whooshing) + +4 +00:00:05,460 --> 00:00:08,486 +- 使用 PyTorch 编写你自己的训练循环。 +- Write your own training loop with PyTorch. + +5 +00:00:08,486 --> 00:00:09,960 +在本视频中,我们将了解 +In this video, we'll look at + +6 +00:00:09,960 --> 00:00:12,750 +我们如何进行与培训师视频中相同的微调, +how we can do the same fine-tuning as in the Trainer video, + +7 +00:00:12,750 --> 00:00:14,760 +但不依赖那个课程。 +but without relying on that class. + +8 +00:00:14,760 --> 00:00:17,790 +这样,你就可以轻松自定义每个步骤 +This way, you'll be able to easily customize each step + +9 +00:00:17,790 --> 00:00:20,310 +到你需要的训练循环。 +to the training loop to your needs. + +10 +00:00:20,310 --> 00:00:21,660 +这个也很有用 +This is also very useful + +11 +00:00:21,660 --> 00:00:22,740 +手动调试某些东西 +to manually debug something + +12 +00:00:22,740 --> 00:00:24,590 +Trainer API 出了问题。 +that went wrong with the Trainer API. + +13 +00:00:26,220 --> 00:00:28,020 +在我们深入研究代码之前, +Before we dive into the code, + +14 +00:00:28,020 --> 00:00:30,481 +这是训练循环的草图。 +here is a sketch of a training loop. + +15 +00:00:30,481 --> 00:00:33,381 +我们获取一批训练数据并将其提供给模型。 +We take a batch of training data and feed it to the model. + +16 +00:00:34,223 --> 00:00:36,960 +有了标签,我们就可以计算损失。 +With the labels, we can then compute a loss. + +17 +00:00:36,960 --> 00:00:39,316 +这个数字本身没有用, +That number is not useful in its own, + +18 +00:00:39,316 --> 00:00:40,260 +用于计算 +that is used to compute + +19 +00:00:40,260 --> 00:00:42,150 +我们模型权重的成分, +the ingredients of our model weights, + +20 +00:00:42,150 --> 00:00:43,440 +那是损失的导数 +that is the derivative of the loss + +21 +00:00:44,610 --> 00:00:47,160 +关于每个模型的重量。 +with respect to each model weight. + +22 +00:00:47,160 --> 00:00:49,800 +然后优化器使用这些梯度 +Those gradients are then used by the optimizer + +23 +00:00:49,800 --> 00:00:51,210 +更新模型权重, +to update the model weights, + +24 +00:00:51,210 --> 00:00:53,550 +让他们变得更好一点。 +and make them a little bit better. + +25 +00:00:53,550 --> 00:00:54,510 +然后我们重复这个过程 +We then repeat the process + +26 +00:00:54,510 --> 00:00:56,880 +与一批新的训练数据。 +with a new batch of training data. + +27 +00:00:56,880 --> 00:00:58,620 +如果有任何不清楚的地方, +If any of this isn't clear, + +28 +00:00:58,620 --> 00:01:00,270 +不要犹豫,复习一下 +don't hesitate to take a refresher + +29 +00:01:00,270 --> 00:01:02,170 +在你最喜欢的深度学习课程上。 +on your favorite deep learning course. + +30 +00:01:03,210 --> 00:01:06,000 +我们将在这里再次使用 GLUE MRPC 数据集, +We'll use the GLUE MRPC data set here again, + +31 +00:01:06,000 --> 00:01:07,680 +我们已经看到了如何预先提出数据 +and we've seen how to prepropose the data + +32 +00:01:07,680 --> 00:01:11,130 +使用具有动态填充的数据集库。 +using the Datasets library with dynamic padding. + +33 +00:01:11,130 --> 00:01:12,630 +查看下面的视频链接 +Check out the videos link below + +34 +00:01:12,630 --> 00:01:14,280 +如果你还没有看到它们。 +if you haven't seen them already. + +35 +00:01:15,480 --> 00:01:18,930 +完成后,我们只需要定义 PyTorch DataLoaders +With this done, we only have to define PyTorch DataLoaders + +36 +00:01:18,930 --> 00:01:20,610 +这将负责转换 +which will be responsible to convert + +37 +00:01:20,610 --> 00:01:23,253 +我们数据集的元素到补丁中。 +the elements of our dataset into patches. + +38 +00:01:24,450 --> 00:01:27,960 +我们将 DataColletorForPadding 用作整理函数, +We use our DataColletorForPadding as a collate function, + +39 +00:01:27,960 --> 00:01:29,460 +并洗牌训练集 +and shuffle the training set + +40 +00:01:29,460 --> 00:01:31,080 +确保我们不会检查样品 +to make sure we don't go over the samples + +41 +00:01:31,080 --> 00:01:33,870 +在一个时代 * 以相同的顺序。 +in the same order at a epoch*. + +42 +00:01:33,870 --> 00:01:36,390 +要检查一切是否按预期工作, +To check that everything works as intended, + +43 +00:01:36,390 --> 00:01:38,883 +我们尝试获取一批数据,并对其进行检查。 +we try to grab a batch of data, and inspect it. + +44 +00:01:40,080 --> 00:01:43,050 +就像我们的数据集元素一样,它是一个字典, +Like our data set elements, it's a dictionary, + +45 +00:01:43,050 --> 00:01:46,260 +但这些时候值不是一个整数列表 +but these times the values are not a single list of integers + +46 +00:01:46,260 --> 00:01:49,053 +但是按序列长度形状批量大小的张量。 +but a tensor of shape batch size by sequence length. + +47 +00:01:50,460 --> 00:01:53,580 +下一步是在我们的模型中发送训练数据。 +The next step is to send the training data in our model. + +48 +00:01:53,580 --> 00:01:56,730 +为此,我们需要实际创建一个模型。 +For that, we'll need to actually create a model. + +49 +00:01:56,730 --> 00:01:58,740 +如 Model API 视频中所示, +As seen in the Model API video, + +50 +00:01:58,740 --> 00:02:00,540 +我们使用 from_pretrained 方法, +we use the from_pretrained method, + +51 +00:02:00,540 --> 00:02:03,270 +并将标签数量调整为类别数量 +and adjust the number of labels to the number of classes + +52 +00:02:03,270 --> 00:02:06,810 +我们有这个数据集,这里有两个。 +we have on this data set, here two. + +53 +00:02:06,810 --> 00:02:08,940 +再次确保一切顺利, +Again to be sure everything is going well, + +54 +00:02:08,940 --> 00:02:11,100 +我们将我们抓取的批次传递给我们的模型, +we pass the batch we grabbed to our model, + +55 +00:02:11,100 --> 00:02:13,320 +并检查没有错误。 +and check there is no error. + +56 +00:02:13,320 --> 00:02:14,940 +如果提供标签, +If the labels are provided, + +57 +00:02:14,940 --> 00:02:16,590 +Transformers 库的模型 +the models of the Transformers library + +58 +00:02:16,590 --> 00:02:18,273 +总是直接返回损失。 +always returns a loss directly. + +59 +00:02:19,525 --> 00:02:21,090 +我们将能够做 loss.backward () +We will be able to do loss.backward () + +60 +00:02:21,090 --> 00:02:22,860 +计算所有梯度, +to compute all the gradients, + +61 +00:02:22,860 --> 00:02:26,460 +然后需要一个优化器来完成训练步骤。 +and will then need an optimizer to do the training step. + +62 +00:02:26,460 --> 00:02:28,860 +我们在这里使用 AdamW 优化器, +We use the AdamW optimizer here, + +63 +00:02:28,860 --> 00:02:31,440 +这是具有适当权重衰减的 Adam 变体, +which is a variant of Adam with proper weight decay, + +64 +00:02:31,440 --> 00:02:33,840 +但你可以选择任何你喜欢的 PyTorch 优化器。 +but you can pick any PyTorch optimizer you like. + +65 +00:02:34,830 --> 00:02:36,150 +使用之前的损失, +Using the previous loss, + +66 +00:02:36,150 --> 00:02:39,060 +并使用 loss.backward () 计算梯度, +and computing the gradients with loss.backward (), + +67 +00:02:39,060 --> 00:02:41,130 +我们检查我们是否可以执行优化器步骤 +we check that we can do the optimizer step + +68 +00:02:41,130 --> 00:02:42,030 +没有任何错误。 +without any error. + +69 +00:02:43,380 --> 00:02:45,870 +之后不要忘记将梯度归零, +Don't forget to zero your gradient afterwards, + +70 +00:02:45,870 --> 00:02:46,890 +或者在下一步, +or at the next step, + +71 +00:02:46,890 --> 00:02:49,343 +它们将被添加到你计算的梯度中。 +they will get added to the gradients you computed. + +72 +00:02:50,490 --> 00:02:52,080 +我们已经可以编写我们的训练循环, +We could already write our training loop, + +73 +00:02:52,080 --> 00:02:53,220 +但我们还要添加两件事 +but we will add two more things + +74 +00:02:53,220 --> 00:02:55,620 +使它尽可能好。 +to make it as good as it can be. + +75 +00:02:55,620 --> 00:02:57,690 +第一个是学习率调度器, +The first one is a learning rate scheduler, + +76 +00:02:57,690 --> 00:03:00,140 +逐步将我们的学习率降低到零。 +to progressively decay our learning rate to zero. + +77 +00:03:01,195 --> 00:03:04,590 +Transformers 库中的 get_scheduler 函数 +The get_scheduler function from the Transformers library + +78 +00:03:04,590 --> 00:03:06,150 +只是一个方便的功能 +is just a convenience function + +79 +00:03:06,150 --> 00:03:07,800 +轻松构建这样的调度程序。 +to easily build such a scheduler. + +80 +00:03:08,850 --> 00:03:09,683 +你可以再次使用 +You can again use + +81 +00:03:09,683 --> 00:03:11,860 +取而代之的是任何 PyTorch 学习率调度程序。 +any PyTorch learning rate scheduler instead. + +82 +00:03:13,110 --> 00:03:14,850 +最后,如果我们想要我们的培训 +Finally, if we want our training + +83 +00:03:14,850 --> 00:03:17,610 +花几分钟而不是几个小时, +to take a couple of minutes instead of a few hours, + +84 +00:03:17,610 --> 00:03:19,530 +我们需要使用 GPU。 +we will need to use a GPU. + +85 +00:03:19,530 --> 00:03:21,270 +第一步是得到一个, +The first step is to get one, + +86 +00:03:21,270 --> 00:03:23,283 +例如通过使用协作笔记本。 +for instance by using a collab notebook. + +87 +00:03:24,180 --> 00:03:26,040 +然后你需要实际发送你的模型, +Then you need to actually send your model, + +88 +00:03:26,040 --> 00:03:28,923 +并使用火炬设备对其进行训练数据。 +and training data on it by using a torch device. + +89 +00:03:29,790 --> 00:03:30,840 +仔细检查以下行 +Double-check the following lines + +90 +00:03:30,840 --> 00:03:32,340 +为你打印一个 CUDA 设备。 +print a CUDA device for you. + +91 +00:03:32,340 --> 00:03:35,640 +或准备将你的训练减少到一个多小时。 +or be prepared for your training to less, more than an hour. + +92 +00:03:35,640 --> 00:03:37,390 +我们现在可以把所有东西放在一起。 +We can now put everything together. + +93 +00:03:38,550 --> 00:03:40,860 +首先,我们将模型置于训练模式 +First, we put our model in training mode + +94 +00:03:40,860 --> 00:03:42,240 +这将激活训练行为 +which will activate the training behavior + +95 +00:03:42,240 --> 00:03:44,790 +对于某些层,例如 Dropout。 +for some layers, like Dropout. + +96 +00:03:44,790 --> 00:03:46,860 +然后遍历我们选择的纪元数, +Then go through the number of epochs we picked, + +97 +00:03:46,860 --> 00:03:50,070 +以及我们训练数据加载器中的所有数据。 +and all the data in our training dataloader. + +98 +00:03:50,070 --> 00:03:52,410 +然后我们完成我们已经看到的所有步骤; +Then we go through all the steps we have seen already; + +99 +00:03:52,410 --> 00:03:54,240 +将数据发送到 GPU, +send the data to the GPU, + +100 +00:03:54,240 --> 00:03:55,560 +计算模型输出, +compute the model outputs, + +101 +00:03:55,560 --> 00:03:57,720 +尤其是损失。 +and in particular the loss. + +102 +00:03:57,720 --> 00:03:59,850 +使用损失来计算梯度, +Use the loss to compute gradients, + +103 +00:03:59,850 --> 00:04:02,880 +然后使用优化器进行训练。 +then make a training step with the optimizer. + +104 +00:04:02,880 --> 00:04:04,500 +在我们的调度器中更新学习率 +Update the learning rate in our scheduler + +105 +00:04:04,500 --> 00:04:05,970 +对于下一次迭代, +for the next iteration, + +106 +00:04:05,970 --> 00:04:07,763 +并将优化器的梯度归零。 +and zero the gradients of the optimizer. + +107 +00:04:09,240 --> 00:04:10,500 +一旦完成, +Once this is finished, + +108 +00:04:10,500 --> 00:04:12,150 +我们可以很容易地评估我们的模型 +we can evaluate our model very easily + +109 +00:04:12,150 --> 00:04:14,283 +使用数据集库中的指标。 +with a metric from the Datasets library. + +110 +00:04:15,180 --> 00:04:17,880 +首先,我们将模型置于评估模式, +First, we put our model in evaluation mode, + +111 +00:04:17,880 --> 00:04:20,550 +停用像 Dropout 这样的层, +to deactivate layers like Dropout, + +112 +00:04:20,550 --> 00:04:23,850 +然后遍历评估数据加载器中的所有数据。 +then go through all the data in the evaluation data loader. + +113 +00:04:23,850 --> 00:04:25,530 +正如我们在培训师视频中看到的那样, +As we have seen in the Trainer video, + +114 +00:04:25,530 --> 00:04:26,850 +模型输出 logits, +the model outputs logits, + +115 +00:04:26,850 --> 00:04:28,530 +我们需要应用 argmax 函数 +and we need to apply the argmax function + +116 +00:04:28,530 --> 00:04:30,213 +将它们转化为预测。 +to convert them into predictions. + +117 +00:04:31,350 --> 00:04:33,420 +然后度量对象有一个 add_batch 方法, +The metric object then has an add_batch method, + +118 +00:04:33,420 --> 00:04:36,810 +我们可以用来向它发送那些中间预测。 +we can use to send it those intermediate predictions. + +119 +00:04:36,810 --> 00:04:38,700 +一旦评估循环完成, +Once the evaluation loop is finished, + +120 +00:04:38,700 --> 00:04:40,320 +我们只需要调用计算方法 +we just have to call the compute method + +121 +00:04:40,320 --> 00:04:42,180 +得到我们的最终结果。 +to get our final results. + +122 +00:04:42,180 --> 00:04:44,490 +恭喜,你现在已经微调了一个模型 +Congratulations, you have now fine-tuned a model + +123 +00:04:44,490 --> 00:04:45,633 +靠你自己。 +all by yourself. + +124 +00:04:47,253 --> 00:04:49,920 +(空气呼啸) +(air whooshing) + diff --git a/subtitles/zh-CN/30_supercharge-your-pytorch-training-loop-with-accelerate.srt b/subtitles/zh-CN/30_supercharge-your-pytorch-training-loop-with-accelerate.srt new file mode 100644 index 000000000..0402b95da --- /dev/null +++ b/subtitles/zh-CN/30_supercharge-your-pytorch-training-loop-with-accelerate.srt @@ -0,0 +1,370 @@ +1 +00:00:00,225 --> 00:00:02,892 +(空气呼啸) +(air whooshing) + +2 +00:00:05,460 --> 00:00:07,470 +- 使用 Hugging Face Accelerate 增强 +- Supercharge your PyTorch training loop + +3 +00:00:07,470 --> 00:00:08,943 +你的 PyTorch 训练循环 +with Hugging Face Accelerate. + +4 +00:00:11,340 --> 00:00:12,600 +有多个设置 +There are multiple setups + +5 +00:00:12,600 --> 00:00:14,580 +你可以在其上进行培训: +on which you can run your training: + +6 +00:00:14,580 --> 00:00:17,910 +它可能在 CPU、GPU、TPU 上, +it could be on CPU, GPUs, TPUs, + +7 +00:00:17,910 --> 00:00:20,610 +分布在具有多个设备的一台机器上, +distributed on one machine with several devices, + +8 +00:00:20,610 --> 00:00:23,220 +甚至几台机器,通常称为节点, +or even several machines, often called nodes, + +9 +00:00:23,220 --> 00:00:25,173 +每个都有多个设备。 +each with multiple devices. + +10 +00:00:26,340 --> 00:00:28,200 +最重要的是,还有新的调整 +On top of that, there are new tweaks + +11 +00:00:28,200 --> 00:00:30,810 +使你的训练更快或更有效, +to make your training faster or more efficient, + +12 +00:00:30,810 --> 00:00:32,763 +比如混合精度和 DeepSpeed。 +like mixed precision and DeepSpeed. + +13 +00:00:33,840 --> 00:00:36,600 +每一个设置或训练调整 +Each of those setups or training tweaks + +14 +00:00:36,600 --> 00:00:38,760 +要求你更改训练循环的代码 +requires you to change the code of your training loop + +15 +00:00:38,760 --> 00:00:41,733 +以某种方式学习新的 API。 +in one way or another and to learn a new API. + +16 +00:00:43,260 --> 00:00:45,940 +所有这些设置都由 Trainer API 处理, +All those setups are handled by the Trainer API, + +17 +00:00:45,940 --> 00:00:49,590 +并且有几个第三方库可以提供帮助。 +and there are several third-party libraries that can help. + +18 +00:00:49,590 --> 00:00:50,760 +那些问题 +The problem with those + +19 +00:00:50,760 --> 00:00:53,100 +是他们感觉像个黑盒子 +is that they can feel like a black box + +20 +00:00:53,100 --> 00:00:55,320 +并且实施调整可能并不容易 +and that it might not be easy to implement the tweak + +21 +00:00:55,320 --> 00:00:56,820 +到你需要的训练循环。 +to the training loop you need. + +22 +00:00:57,840 --> 00:00:59,760 +Accelerate 是专门设计的 +Accelerate has been designed specifically + +23 +00:00:59,760 --> 00:01:02,790 +让你保持对训练循环的完全控制 +to let you retain full control over your training loop + +24 +00:01:02,790 --> 00:01:04,833 +并尽可能不打扰。 +and be as non-intrusive as possible. + +25 +00:01:05,760 --> 00:01:08,760 +只需四行代码即可添加到你的训练循环中, +With just four lines of code to add to your training loop, + +26 +00:01:08,760 --> 00:01:11,733 +这里显示在训练循环视频的例子中, +here shown on the example of the training loop video, + +27 +00:01:12,630 --> 00:01:14,730 +Accelerate 将处理所有设置 +Accelerate will handle all the setups + +28 +00:01:14,730 --> 00:01:17,180 +和第一张幻灯片中提到的培训调整。 +and training tweaks mentioned on the first slide. + +29 +00:01:18,630 --> 00:01:20,400 +只需学习和掌握一个 API +It's only one API to learn and master + +30 +00:01:20,400 --> 00:01:21,933 +而不是 10 个不同的。 +instead of 10 different ones. + +31 +00:01:23,340 --> 00:01:25,980 +更具体地说,你必须导入和实例化 +More specifically, you have to import and instantiate + +32 +00:01:25,980 --> 00:01:27,360 +加速器对象, +an accelerator object, + +33 +00:01:27,360 --> 00:01:29,100 +这将处理所有必要的代码 +that will handle all the necessary code + +34 +00:01:29,100 --> 00:01:30,300 +为你的特定设置。 +for your specific setup. + +35 +00:01:31,380 --> 00:01:33,780 +然后你必须把模型发给它, +Then you have to send it the model, + +36 +00:01:33,780 --> 00:01:36,000 +你正在使用的优化器和数据加载器 +optimizer and dataloaders you are using + +37 +00:01:36,000 --> 00:01:39,633 +在 prepare 方法中,这是要记住的主要方法。 +in the prepare method, which is the main method to remember. + +38 +00:01:40,860 --> 00:01:42,870 +加速处理设备放置, +Accelerate handles device placement, + +39 +00:01:42,870 --> 00:01:44,370 +所以你不需要把你的批次 +so you don't need to put your batch + +40 +00:01:44,370 --> 00:01:46,980 +在你使用的特定设备上。 +on the specific device you are using. + +41 +00:01:46,980 --> 00:01:50,640 +最后,你必须更换 loss.backward 行 +Finally, you have to replace the loss.backward line + +42 +00:01:50,640 --> 00:01:54,300 +通过 accelerator.backwardloss, +by accelerator.backwardloss, + +43 +00:01:54,300 --> 00:01:55,500 +这就是你所需要的! +and that's all you need! + +44 +00:01:58,410 --> 00:02:01,710 +Accelerate 还处理分布式评估。 +Accelerate also handles distributed evaluation. + +45 +00:02:01,710 --> 00:02:04,020 +你仍然可以使用经典的评估循环 +You can still use a classic evaluation loop + +46 +00:02:04,020 --> 00:02:06,750 +比如我们在训练循环视频中看到的那个, +such as the one we saw in the training loop video, + +47 +00:02:06,750 --> 00:02:08,280 +在这种情况下所有进程 +in which case all processes + +48 +00:02:08,280 --> 00:02:10,083 +将进行全面评估。 +will perform the full evaluation. + +49 +00:02:11,340 --> 00:02:13,530 +要使用分布式评估, +To use a distributed evaluation, + +50 +00:02:13,530 --> 00:02:16,380 +你只需要像这样调整你的评估循环: +you just have to adapt your evaluation loop like this: + +51 +00:02:16,380 --> 00:02:17,657 +传递评估数据加载器 +pass along the evaluation dataloader + +52 +00:02:17,657 --> 00:02:21,093 +到 accelerator.prepare 方法,比如训练。 +to the accelerator.prepare method, like for training. + +53 +00:02:22,170 --> 00:02:23,430 +然后你可以关闭这条线 +Then you can dismiss the line + +54 +00:02:23,430 --> 00:02:26,160 +将批次放在适当的设备上, +that places the batch on the proper device, + +55 +00:02:26,160 --> 00:02:27,870 +在通过你的预测之前 +and just before passing your predictions + +56 +00:02:27,870 --> 00:02:31,110 +和指标的标签,使用 accelerator.gather +and labels to your metric, use accelerator.gather + +57 +00:02:31,110 --> 00:02:33,300 +收集预测 +to gather together the predictions + +58 +00:02:33,300 --> 00:02:34,803 +和每个过程的标签。 +and labels from each process. + +59 +00:02:36,420 --> 00:02:37,890 +分布式训练脚本 +A distributed training script + +60 +00:02:37,890 --> 00:02:41,040 +必须在不同的过程中多次启动, +has to be launched several times on different processes, + +61 +00:02:41,040 --> 00:02:43,203 +例如,你使用的每个 GPU 一个。 +for instance, one per GPU you are using. + +62 +00:02:44,070 --> 00:02:46,350 +你可以使用 PyTorch 工具来做到这一点 +You can use the PyTorch tools to do that + +63 +00:02:46,350 --> 00:02:48,210 +如果你熟悉他们, +if you are familiar with them, + +64 +00:02:48,210 --> 00:02:50,520 +但 Accelerate 还提供了一个简单的 API +but Accelerate also provides an easy API + +65 +00:02:50,520 --> 00:02:53,523 +配置你的设置并启动你的训练脚本。 +to configure your setup and launch your training script. + +66 +00:02:54,540 --> 00:02:57,270 +在终端中,运行加速配置 +In a terminal, run accelerate config + +67 +00:02:57,270 --> 00:02:58,650 +并回答小问卷 +and answer the small questionnaire + +68 +00:02:58,650 --> 00:03:00,330 +生成配置文件 +to generate a configuration file + +69 +00:03:00,330 --> 00:03:02,073 +与所有相关信息, +with all the relevant information, + +70 +00:03:03,240 --> 00:03:05,790 +然后你可以运行加速启动, +then you can just run accelerate launch, + +71 +00:03:05,790 --> 00:03:08,580 +然后是训练脚本的路径。 +followed by the path to your training script. + +72 +00:03:08,580 --> 00:03:12,000 +在笔记本中,你可以使用笔记本启动器功能 +In a notebook, you can use the notebook launcher function + +73 +00:03:12,000 --> 00:03:13,233 +开始你的训练。 +to launch your training. + +74 +00:03:15,186 --> 00:03:17,853 +(空气呼啸) +(air whooshing) + diff --git a/subtitles/zh-CN/31_navigating-the-model-hub.srt b/subtitles/zh-CN/31_navigating-the-model-hub.srt new file mode 100644 index 000000000..3b9e03f36 --- /dev/null +++ b/subtitles/zh-CN/31_navigating-the-model-hub.srt @@ -0,0 +1,385 @@ +1 +00:00:00,468 --> 00:00:03,051 +(欢快的音乐) +(upbeat music) + +2 +00:00:04,050 --> 00:00:05,910 +- [Instructor] 在这段视频中,我们将回顾 +- [Instructor] In this video, we're going to go over + +3 +00:00:05,910 --> 00:00:08,013 +HuggingFace 模型中心导航。 +the HuggingFace Model Hub navigation. + +4 +00:00:10,140 --> 00:00:13,260 +这是 huggingface.co 登陆页面。 +This is the huggingface.co landing page. + +5 +00:00:13,260 --> 00:00:16,020 +要访问模型中心,请单击模型选项卡 +To access the model hub, click on the models tab + +6 +00:00:16,020 --> 00:00:17,463 +在右上角。 +in the upper right corner. + +7 +00:00:18,960 --> 00:00:21,030 +你应该面对这个网络界面, +You should be facing this web interface, + +8 +00:00:21,030 --> 00:00:23,193 +可以分成几个部分。 +which can be split into several parts. + +9 +00:00:24,480 --> 00:00:26,790 +在左侧,你会找到类别, +On the left, you'll find categories, + +10 +00:00:26,790 --> 00:00:29,090 +你可以使用它来定制你的模型搜索。 +which you can use to tailor your model search. + +11 +00:00:29,970 --> 00:00:32,970 +第一类是任务。 +The first category is the tasks. + +12 +00:00:32,970 --> 00:00:36,660 +集线器上的模型可用于各种各样的任务。 +Models on the hub may be used for a wide variety of tasks. + +13 +00:00:36,660 --> 00:00:39,030 +这些包括自然语言处理任务, +These include natural language processing tasks, + +14 +00:00:39,030 --> 00:00:41,670 +比如问答或者文本分类, +such as question answering or text classification, + +15 +00:00:41,670 --> 00:00:43,773 +但它不仅限于 NLP。 +but it isn't only limited to NLP. + +16 +00:00:44,850 --> 00:00:47,790 +其他领域的其他任务也可用, +Other tasks from other fields are also available, + +17 +00:00:47,790 --> 00:00:50,340 +例如计算机视觉的图像分类, +such as image classification for computer vision, + +18 +00:00:50,340 --> 00:00:52,683 +或语音的自动语音识别。 +or automatic speech recognition for speech. + +19 +00:00:54,840 --> 00:00:57,870 +第二类是图书馆。 +The second category is the libraries. + +20 +00:00:57,870 --> 00:01:00,990 +集线器上的模型通常共享三个主干之一, +Models on the hub usually share one of three backbones, + +21 +00:01:00,990 --> 00:01:03,900 +PyTorch、TensorFlow 或 JAX。 +PyTorch, TensorFlow, or JAX. + +22 +00:01:03,900 --> 00:01:07,503 +但是,也存在其他主干,例如 rust 或 ONNX。 +However, other backbones, such as rust or ONNX also exist. + +23 +00:01:09,540 --> 00:01:11,850 +最后,这个选项卡也可以使用 +Finally, this tab can also be used + +24 +00:01:11,850 --> 00:01:15,123 +指定模型来自哪个高级框架。 +to specify from which high-level framework the models comes. + +25 +00:01:16,140 --> 00:01:19,260 +这包括变形金刚,但不限于此。 +This includes Transformers, but it isn't limited to it. + +26 +00:01:19,260 --> 00:01:21,060 +模型集线器用于托管 +The model hub is used to host + +27 +00:01:21,060 --> 00:01:22,920 +许多不同的框架模型, +a lot of different frameworks models, + +28 +00:01:22,920 --> 00:01:24,600 +我们正在积极寻求举办 +and we're actively looking to host + +29 +00:01:24,600 --> 00:01:25,893 +其他框架模型。 +other frameworks models. + +30 +00:01:28,530 --> 00:01:31,890 +第三类是数据集选项卡。 +The third category is the datasets tab. + +31 +00:01:31,890 --> 00:01:35,070 +从此选项卡中选择数据集意味着过滤模型 +Selecting a dataset from this tab means filtering the models + +32 +00:01:35,070 --> 00:01:37,683 +这样他们就可以在该特定数据集上接受培训。 +so that they were trained on that specific dataset. + +33 +00:01:39,180 --> 00:01:42,300 +第四类是语言选项卡。 +The fourth category is the languages tab. + +34 +00:01:42,300 --> 00:01:43,800 +从此选项卡中选择一种语言 +Selecting a language from this tab + +35 +00:01:43,800 --> 00:01:45,990 +意味着过滤模型以便它们处理 +means filtering the models so that they handle + +36 +00:01:45,990 --> 00:01:47,090 +选择的语言。 +the language selected. + +37 +00:01:48,600 --> 00:01:51,750 +最后,最后一个类别允许选择许可证 +Finally, the last category allows to choose the license + +38 +00:01:51,750 --> 00:01:53,313 +与之共享模型。 +with which the model is shared. + +39 +00:01:56,700 --> 00:01:58,770 +在右侧,你会找到可用的型号 +On the right, you'll find the models available + +40 +00:01:58,770 --> 00:02:00,480 +在模型中心。 +on the model hub. + +41 +00:02:00,480 --> 00:02:03,750 +默认情况下,模型按下载排序。 +The models are ordered by downloads by default. + +42 +00:02:03,750 --> 00:02:04,890 +单击模型时, +When clicking on a model, + +43 +00:02:04,890 --> 00:02:07,230 +你应该面对它的模型卡。 +you should be facing its model card. + +44 +00:02:07,230 --> 00:02:09,990 +模型卡包含有关模型的信息, +The model card contains information about the model, + +45 +00:02:09,990 --> 00:02:13,263 +它的描述、预期用途、限制和偏见。 +its description, intended use, limitations and biases. + +46 +00:02:14,310 --> 00:02:17,580 +它还可以显示有关如何使用模型的代码片段, +It can also show code snippets on how to use the model, + +47 +00:02:17,580 --> 00:02:20,070 +以及任何相关信息; +as well as any relevant information; + +48 +00:02:20,070 --> 00:02:22,080 +培训程序,数据处理, +training procedure, data processing, + +49 +00:02:22,080 --> 00:02:24,213 +评估结果或版权。 +evaluation results or copyrights. + +50 +00:02:25,710 --> 00:02:28,350 +此信息对于要使用的模型至关重要。 +This information is crucial for the model to be used. + +51 +00:02:28,350 --> 00:02:30,360 +制作得越好的模型卡, +The better crafted a model card is, + +52 +00:02:30,360 --> 00:02:33,270 +其他用户越容易利用你的模型 +the easier it will be for other users to leverage your model + +53 +00:02:33,270 --> 00:02:34,443 +在他们的应用程序中。 +in their applications. + +54 +00:02:35,820 --> 00:02:38,553 +模型卡右侧是推理 API。 +On the right of the model card is the inference API. + +55 +00:02:39,540 --> 00:02:41,040 +可以使用此推理 API +This inference API can be used + +56 +00:02:41,040 --> 00:02:43,290 +直接玩模型。 +to play with the model directly. + +57 +00:02:43,290 --> 00:02:45,690 +随意修改文字,点击计算 +Feel free to modify the text and click on compute + +58 +00:02:45,690 --> 00:02:48,140 +看看模型对你的输入有何反应。 +to see how would the model behave to your inputs. + +59 +00:02:50,370 --> 00:02:53,013 +屏幕顶部是模型标签。 +At the top of your screen lies the model tags. + +60 +00:02:53,850 --> 00:02:56,550 +这些包括模型任务,以及任何其他标签 +These include the model task, as well as any other tag + +61 +00:02:56,550 --> 00:02:59,200 +这与我们刚才看到的类别相关。 +that is relevant to the categories we have just seen. + +62 +00:03:01,320 --> 00:03:04,410 +文件和版本选项卡显示体系结构 +The files & versions tab displays the architecture + +63 +00:03:04,410 --> 00:03:06,213 +该模型的存储库。 +of the repository of that model. + +64 +00:03:07,230 --> 00:03:10,920 +在这里,我们可以看到定义此模型的所有文件。 +Here, we can see all the files that define this model. + +65 +00:03:10,920 --> 00:03:13,650 +你将看到 Git 存储库的所有常用功能: +You'll see all usual features of a Git repository: + +66 +00:03:13,650 --> 00:03:15,093 +可用的分支机构, +the branches available, + +67 +00:03:17,160 --> 00:03:18,520 +提交历史 +the commit history + +68 +00:03:20,760 --> 00:03:22,683 +以及提交差异。 +as well as the commit diff. + +69 +00:03:25,740 --> 00:03:27,510 +三个不同的按钮可用 +Three different buttons are available + +70 +00:03:27,510 --> 00:03:29,760 +在模型卡的顶部。 +at the top of the model card. + +71 +00:03:29,760 --> 00:03:31,170 +第一个显示如何使用 +The first one shows how to use + +72 +00:03:31,170 --> 00:03:33,093 +以编程方式推理 API。 +the inference API programmatically. + +73 +00:03:35,910 --> 00:03:38,913 +第二个展示了如何在 SageMaker 中训练这个模型。 +The second one shows how to train this model in SageMaker. + +74 +00:03:42,870 --> 00:03:44,820 +最后一个显示了如何加载该模型 +And the last one shows how to load that model + +75 +00:03:44,820 --> 00:03:46,860 +在适当的库中。 +within the appropriate library. + +76 +00:03:46,860 --> 00:03:48,783 +对于 BERT,这是 transformers。 +For BERT, this is transformers. + +77 +00:03:50,208 --> 00:03:52,791 +(欢快的音乐) +(upbeat music) + diff --git a/subtitles/zh-CN/32_managing-a-repo-on-the-model-hub.srt b/subtitles/zh-CN/32_managing-a-repo-on-the-model-hub.srt new file mode 100644 index 000000000..e59ddd96f --- /dev/null +++ b/subtitles/zh-CN/32_managing-a-repo-on-the-model-hub.srt @@ -0,0 +1,850 @@ +1 +00:00:04,200 --> 00:00:06,210 +- [Instructor] 在这段视频中,我们将了解如何 +- [Instructor] In this video, we're going to understand how + +2 +00:00:06,210 --> 00:00:08,280 +管理模型存储库 +to manage a model repository + +3 +00:00:08,280 --> 00:00:10,053 +在 Hugging Face Hub 模型中心。 +on the Hugging Face Hub Model Hub. + +4 +00:00:10,920 --> 00:00:13,020 +为了处理存储库 +In order to handle a repository + +5 +00:00:13,020 --> 00:00:15,450 +你应该首先拥有一个 Hugging Face 帐户。 +you should first have a Hugging Face account. + +6 +00:00:15,450 --> 00:00:17,610 +创建新帐户的链接可用 +A link to create a new account is available + +7 +00:00:17,610 --> 00:00:18,573 +在说明中。 +in the description. + +8 +00:00:20,130 --> 00:00:22,980 +登录后,你可以创建一个新的存储库 +Once you are logged in, you can create a new repository + +9 +00:00:22,980 --> 00:00:25,890 +通过单击新模型选项。 +by clicking on the new model option. + +10 +00:00:25,890 --> 00:00:29,400 +你应该面对与以下类似的模式。 +You should be facing a similar modal to the following. + +11 +00:00:29,400 --> 00:00:33,240 +在所有者输入中,你可以放置自己的命名空间 +In the owner input, you can put either your own namespace + +12 +00:00:33,240 --> 00:00:35,703 +或你组织的任何命名空间。 +or any of your organization's namespaces. + +13 +00:00:36,660 --> 00:00:39,330 +模型名称是模型标识符 +The model name is the model identifier + +14 +00:00:39,330 --> 00:00:40,320 +然后将被使用 +that will then be used + +15 +00:00:40,320 --> 00:00:43,143 +在所选命名空间上识别你的模型。 +to identify your model on the chosen namespace. + +16 +00:00:44,130 --> 00:00:47,700 +最后的选择是在公共和私人之间。 +The final choice is between public and private. + +17 +00:00:47,700 --> 00:00:49,950 +任何人都可以访问公共模型。 +Public models are accessible by anyone. + +18 +00:00:49,950 --> 00:00:51,840 +这是推荐的免费选项, +This is the recommended free option, + +19 +00:00:51,840 --> 00:00:54,960 +因为这使你的模型易于访问和共享。 +as this makes your model easily accessible and shareable. + +20 +00:00:54,960 --> 00:00:57,630 +你的命名空间的所有者是唯一的 +The owners of your namespace are the only ones + +21 +00:00:57,630 --> 00:00:59,523 +谁可以更新和更改你的模型。 +who can update and change your model. + +22 +00:01:00,450 --> 00:01:03,660 +一个更高级的选项是私有选项。 +A more advanced option is the private option. + +23 +00:01:03,660 --> 00:01:04,560 +在这种情况下, +In this case, + +24 +00:01:04,560 --> 00:01:06,000 +只有你的命名空间的所有者 +only the owners of your namespace + +25 +00:01:06,000 --> 00:01:08,280 +将对你的模型有可见性。 +will have visibility over your model. + +26 +00:01:08,280 --> 00:01:10,260 +其他用户不会知道它的存在 +Other users won't know it exists + +27 +00:01:10,260 --> 00:01:11,810 +并且将无法使用它。 +and will not be able to use it. + +28 +00:01:15,030 --> 00:01:17,030 +让我们创建一个虚拟模型来玩。 +Let's create a dummy model to play with. + +29 +00:01:18,180 --> 00:01:19,710 +创建模型后, +Once your model is created, + +30 +00:01:19,710 --> 00:01:22,230 +来自该模型的管理。 +comes the management of that model. + +31 +00:01:22,230 --> 00:01:24,360 +你可以使用三个选项卡。 +Three tabs are available to you. + +32 +00:01:24,360 --> 00:01:27,960 +你面对的是第一个,这是模型卡页面。 +You're facing the first one, which is the model card page. + +33 +00:01:27,960 --> 00:01:29,970 +这是你用来展示模型的页面 +This is the page you use to showcase your model + +34 +00:01:29,970 --> 00:01:31,110 +致全世界。 +to the world. + +35 +00:01:31,110 --> 00:01:33,260 +我们稍后会看到它是如何完成的。 +We'll see how it can be completed in a bit. + +36 +00:01:34,500 --> 00:01:37,503 +第二个是文件和版本选项卡。 +The second one is the files and versions tab. + +37 +00:01:38,340 --> 00:01:40,920 +你的模型本身就是一个 Git 存储库。 +Your model itself is a Git repository. + +38 +00:01:40,920 --> 00:01:43,230 +如果你不知道什么是 Git 存储库, +If you're unaware of what is a Git repository, + +39 +00:01:43,230 --> 00:01:46,320 +你可以将其视为包含文件的文件夹。 +you can think of it as a folder containing files. + +40 +00:01:46,320 --> 00:01:48,120 +如果你以前从未使用过 Git, +If you have never used Git before, + +41 +00:01:48,120 --> 00:01:50,100 +我们建议看介绍 +we recommend looking at an introduction + +42 +00:01:50,100 --> 00:01:52,600 +就像本视频描述中提供的那样。 +like the one provided in this video's description. + +43 +00:01:53,850 --> 00:01:56,910 +Git 存储库允许你查看发生的更改 +The Git repository allows you to see the changes happening + +44 +00:01:56,910 --> 00:02:00,900 +随着时间的推移在此文件夹中,因此术语版本。 +over time in this folder, hence the term versions. + +45 +00:02:00,900 --> 00:02:03,453 +我们稍后会看到如何添加文件和版本。 +We'll see how to add files and versions in a bit. + +46 +00:02:07,020 --> 00:02:09,570 +最后一个选项卡是设置选项卡, +The final tab is the settings tab, + +47 +00:02:09,570 --> 00:02:12,120 +这使你可以管理模型的可见性 +which allows you to manage your model's visibility + +48 +00:02:12,120 --> 00:02:13,203 +和可用性。 +and availability. + +49 +00:02:14,790 --> 00:02:17,673 +让我们首先从将文件添加到存储库开始。 +Let's first start by adding files to the repository. + +50 +00:02:18,540 --> 00:02:19,560 +可以添加文件 +Files can be added + +51 +00:02:19,560 --> 00:02:23,340 +多亏了添加文件按钮,通过网络界面。 +through the web interface thanks to the add file button. + +52 +00:02:23,340 --> 00:02:27,060 +添加的文件可以是任何类型,python,JSON,文本, +The added files can be of any type, python, JSON, text, + +53 +00:02:27,060 --> 00:02:27,893 +你的名字。 +you name it. + +54 +00:02:28,740 --> 00:02:31,170 +除了你添加的文件及其内容, +Alongside your added file and its content, + +55 +00:02:31,170 --> 00:02:33,363 +你应该命名你的更改或提交。 +you should name your change or commit. + +56 +00:02:36,330 --> 00:02:38,400 +一般添加文件比较简单 +Generally, adding files is simpler + +57 +00:02:38,400 --> 00:02:40,770 +通过使用 Hugging Face Hub Python 库 +by using the Hugging Face Hub Python library + +58 +00:02:40,770 --> 00:02:43,050 +或使用命令行。 +or by using the command-line. + +59 +00:02:43,050 --> 00:02:44,310 +我们将展示如何做到这一点 +We'll showcase how to do this + +60 +00:02:44,310 --> 00:02:46,290 +使用 Hugging Face Hub Python 库, +using the Hugging Face Hub Python library, + +61 +00:02:46,290 --> 00:02:48,060 +并且在描述中有一个链接 +and there is a link in the description + +62 +00:02:48,060 --> 00:02:49,800 +到这个视频的前一个版本, +to the previous version of this video, + +63 +00:02:49,800 --> 00:02:52,743 +展示如何使用 Git 和命令行执行此操作。 +showcasing how to do this using Git and the command-line. + +64 +00:02:53,610 --> 00:02:54,840 +首先,确保你已登录 +First, make sure you're logged + +65 +00:02:54,840 --> 00:02:56,460 +进入你的 Hugging Face 帐户, +into your Hugging Face account, + +66 +00:02:56,460 --> 00:02:59,523 +通过命令行或在 Python 运行时中。 +either through the command-line or in a Python runtime. + +67 +00:03:04,634 --> 00:03:06,390 +我们要看的第一种方法 +The first approach we'll take a look at + +68 +00:03:06,390 --> 00:03:08,880 +正在使用上传文件的方法。 +is using the upload file method. + +69 +00:03:08,880 --> 00:03:10,770 +这提供了一个极其简单的 API +This offers an extremely simple API + +70 +00:03:10,770 --> 00:03:12,630 +通过集线器上传文件。 +to upload files through the hub. + +71 +00:03:12,630 --> 00:03:14,190 +三个必需的参数 +The three required parameters + +72 +00:03:14,190 --> 00:03:16,083 +是文件的当前位置, +are the current location of the file, + +73 +00:03:18,570 --> 00:03:21,300 +该文件在存储库中的路径, +the path of that file in the repository, + +74 +00:03:21,300 --> 00:03:24,050 +以及你要推送到的存储库的想法。 +and the idea of the repository to which you're pushing. + +75 +00:03:25,650 --> 00:03:27,930 +还有一些额外的参数。 +There are a few additional parameters. + +76 +00:03:27,930 --> 00:03:29,100 +令牌参数, +The token parameter, + +77 +00:03:29,100 --> 00:03:31,200 +如果你想指定一个不同的令牌 +if you would like to specify a different token + +78 +00:03:31,200 --> 00:03:33,650 +比登录时保存在缓存中的那个, +than the one saved in your cache with your login, + +79 +00:03:34,830 --> 00:03:36,750 +回购类型参数, +the repo type parameter, + +80 +00:03:36,750 --> 00:03:40,503 +如果你想推送到数据集或空间。 +if you would like to push to a data set or a space. + +81 +00:03:42,300 --> 00:03:45,690 +我们将上传一个名为 readme.md 的文件到存储库 +We'll upload a file called readme.md to the repository + +82 +00:03:45,690 --> 00:03:47,190 +使用这种方法。 +using this method. + +83 +00:03:47,190 --> 00:03:49,710 +我们首先用那个名字保存一个文件, +We first start by saving a file with that name, + +84 +00:03:49,710 --> 00:03:51,210 +其中包含一些信息 +which contains some information + +85 +00:03:51,210 --> 00:03:52,920 +关于存储库本身。 +about the repository itself. + +86 +00:03:52,920 --> 00:03:54,243 +在这里,一个标题。 +Here, a title. + +87 +00:03:55,950 --> 00:03:57,420 +现在文件已保存, +Now that the file is saved, + +88 +00:03:57,420 --> 00:04:00,513 +让我们使用上传文件方法将其上传到集线器。 +let's use the upload file method to upload it to the hub. + +89 +00:04:01,560 --> 00:04:03,540 +如果我们切换到 Web 界面一秒钟 +If we switch to the web interface for a second + +90 +00:04:03,540 --> 00:04:07,080 +并刷新页面,我们会看到显示了 README。 +and refresh the page, we'll see that the README is shown. + +91 +00:04:07,080 --> 00:04:08,883 +文件上传成功。 +The file upload was a success. + +92 +00:04:10,170 --> 00:04:13,500 +除了这个方法之外还有一个删除文件的方法 +Alongside this method exists a delete file method + +93 +00:04:13,500 --> 00:04:16,170 +这样你就可以完全管理你的存储库。 +so that you may manage your repository fully. + +94 +00:04:16,170 --> 00:04:18,820 +我们将使用它来删除我们刚刚创建的文件。 +We'll use it to delete the file we have just created. + +95 +00:04:22,860 --> 00:04:25,320 +如果我们再次刷新页面,很好, +If we refresh the page once again, good, + +96 +00:04:25,320 --> 00:04:26,973 +该文件确实被删除了。 +the file was indeed deleted. + +97 +00:04:29,070 --> 00:04:32,730 +这种只使用这两种方法的方法非常简单。 +This approach using only these two methods is super simple. + +98 +00:04:32,730 --> 00:04:35,400 +它不需要安装 Git 或 Git LFS, +It doesn't need Git or Git LFS installed, + +99 +00:04:35,400 --> 00:04:37,650 +但它确实有一个限制。 +but it does come with a limitation. + +100 +00:04:37,650 --> 00:04:39,600 +一个人可以上传的最大文件大小 +The maximum file size one can upload + +101 +00:04:39,600 --> 00:04:41,313 +限制为 5 GB。 +is limited to five gigabytes. + +102 +00:04:42,360 --> 00:04:43,890 +为了克服这个限制, +To overcome this limit, + +103 +00:04:43,890 --> 00:04:45,540 +我们来看看第二种方法 +let's take a look at the second method + +104 +00:04:45,540 --> 00:04:47,643 +这是存储库实用程序。 +which is the repository utility. + +105 +00:04:48,600 --> 00:04:51,840 +此类是 Git 和 Git LFS 方法的包装器, +This class is a wrapper over Git and Git LFS methods, + +106 +00:04:51,840 --> 00:04:53,850 +它抽象了大部分的复杂性 +which abstracts most of the complexity + +107 +00:04:53,850 --> 00:04:55,500 +并提供灵活的 API +and offers a flexible API + +108 +00:04:55,500 --> 00:04:57,990 +管理你的在线存储库。 +to manage your online repositories. + +109 +00:04:57,990 --> 00:04:59,690 +让我们来看看它是如何工作的。 +Let's take a look at how it works. + +110 +00:05:03,870 --> 00:05:08,369 +我们首先从实例化存储库实用程序开始。 +We first start by instantiating the repository utility. + +111 +00:05:08,369 --> 00:05:10,380 +我们从参数中提供克隆, +We provide the clone from parameter, + +112 +00:05:10,380 --> 00:05:13,383 +为了克隆我们刚刚创建的存储库。 +in order to clone the repository we just created. + +113 +00:05:14,400 --> 00:05:18,750 +存储库现已克隆到本地文件夹中。 +The repository is now cloned in the local folder. + +114 +00:05:18,750 --> 00:05:22,200 +我们刚刚初始化的 repo 对象 +The repo object that we have just initialized + +115 +00:05:22,200 --> 00:05:24,873 +提供了很多对我们有用的方法。 +offers quite a few methods which are useful for us. + +116 +00:05:25,920 --> 00:05:28,800 +我们有兴趣将模型推送到中心。 +We're interested in pushing a model to the hub. + +117 +00:05:28,800 --> 00:05:31,170 +我将从加载模型和分词器开始 +I'll start by loading a model and tokenizer + +118 +00:05:31,170 --> 00:05:32,643 +我几个小时前训练过。 +I trained a few hours ago. + +119 +00:05:34,380 --> 00:05:36,810 +我们现在将遵循传统的 Git 方法 +We'll now follow the traditional Git approach + +120 +00:05:36,810 --> 00:05:38,670 +首先提取最新的更改 +by first pulling latest changes + +121 +00:05:38,670 --> 00:05:40,053 +使用 Git pull 方法。 +using the Git pull method. + +122 +00:05:40,980 --> 00:05:43,170 +我们刚刚克隆了存储库, +We just cloned the repository, + +123 +00:05:43,170 --> 00:05:45,780 +所以除非这是一个超级活跃的存储库, +so unless this is a super active repository, + +124 +00:05:45,780 --> 00:05:48,660 +不太可能有新的变化可用。 +it's unlikely that new changes are available. + +125 +00:05:48,660 --> 00:05:51,000 +但拉取最新的更改总是一个好主意 +But it's always a good idea to pull the latest changes + +126 +00:05:51,000 --> 00:05:52,300 +在做任何新的事情之前。 +before doing anything new. + +127 +00:05:53,220 --> 00:05:55,200 +现在我们已经拉取了存储库, +Now that we have pulled the repository, + +128 +00:05:55,200 --> 00:05:58,500 +我会将模型和分词器保存在该文件夹中。 +I'll save the model and tokenizer inside that folder. + +129 +00:05:58,500 --> 00:06:01,200 +这包括模型权重、配置文件、 +This includes the model weights, configuration file, + +130 +00:06:01,200 --> 00:06:02,673 +和分词器文件。 +and tokenizer files. + +131 +00:06:04,440 --> 00:06:05,820 +现在模型已保存, +Now that the model is saved, + +132 +00:06:05,820 --> 00:06:07,890 +我们将继续使用传统的 Git 方法 +we'll continue with the traditional Git approach + +133 +00:06:07,890 --> 00:06:10,620 +并将其推送到远程仓库。 +and push it to the remote repository. + +134 +00:06:10,620 --> 00:06:12,150 +如果我们使用命令行, +If we were using the command-line, + +135 +00:06:12,150 --> 00:06:14,250 +有一些 Git LFS 特定的命令 +there are a few Git LFS specific commands + +136 +00:06:14,250 --> 00:06:15,600 +我们将不得不调用。 +we would have to invoke. + +137 +00:06:15,600 --> 00:06:17,940 +但是在这里,Hugging Face 枢纽包 +But here, the Hugging Face hub package + +138 +00:06:17,940 --> 00:06:20,070 +负责所有这些。 +takes care of all of that. + +139 +00:06:20,070 --> 00:06:24,420 +我们将从使用 Git add 方法暂存文件开始。 +We'll start by staging the files using the Git add method. + +140 +00:06:24,420 --> 00:06:27,600 +然后我们将使用 Git commit 方法提交这些更改, +We'll then commit these changes using Git commit method, + +141 +00:06:27,600 --> 00:06:30,690 +并提供有用的提交信息。 +and providing a helpful commit message. + +142 +00:06:30,690 --> 00:06:33,210 +最后,我们将更改推送到远程, +Finally, we'll push the changes to the remote, + +143 +00:06:33,210 --> 00:06:34,953 +使用 Git 推送方法。 +using the Git push method. + +144 +00:06:45,090 --> 00:06:47,430 +如果我们回到文件和版本选项卡, +If we go back to the files and versions tab, + +145 +00:06:47,430 --> 00:06:49,950 +我们现在可以看到新提交的文件。 +we can now see the newly committed files. + +146 +00:06:49,950 --> 00:06:52,600 +我们甚至可以在推理 API 中使用模型。 +We can even play with the model in the inference API. + +147 +00:06:53,790 --> 00:06:55,770 +不幸的是,我们模型的首页 +Unfortunately, the front page of our model + +148 +00:06:55,770 --> 00:06:57,540 +还是很空虚。 +is still very empty. + +149 +00:06:57,540 --> 00:06:59,280 +让我们添加一个 README markdown 文件 +Let's add a README markdown file + +150 +00:06:59,280 --> 00:07:00,753 +完成它一点点。 +to complete it a little bit. + +151 +00:07:01,710 --> 00:07:04,200 +这个 README 被称为模型卡 +This README is known as the model card + +152 +00:07:04,200 --> 00:07:06,030 +可以说它同样重要 +and it's arguably as important + +153 +00:07:06,030 --> 00:07:09,330 +作为模型存储库中的模型和标记器文件。 +as the model and tokenizer files in the model repository. + +154 +00:07:09,330 --> 00:07:11,280 +这是中心定义 +It is the central definition + +155 +00:07:11,280 --> 00:07:13,200 +和模型文档, +and documentation of your model, + +156 +00:07:13,200 --> 00:07:16,440 +确保社区成员的可重用性 +ensuring reusability by fellow community members + +157 +00:07:16,440 --> 00:07:18,480 +和结果的可重复性。 +and reproducibility of results. + +158 +00:07:18,480 --> 00:07:20,760 +提供一个平台,让其他成员 +Providing a platform on which other members + +159 +00:07:20,760 --> 00:07:22,293 +可以构建他们的工件。 +may build their artifacts. + +160 +00:07:23,220 --> 00:07:25,590 +我们只会在此处添加标题和简短描述 +We'll only add a title and a small description here + +161 +00:07:25,590 --> 00:07:27,060 +为了简单起见, +for simplicity's sake, + +162 +00:07:27,060 --> 00:07:29,370 +但我们鼓励你添加相关信息 +but we encourage you to add information relevant + +163 +00:07:29,370 --> 00:07:30,990 +模型是如何训练的, +to how was the model trained, + +164 +00:07:30,990 --> 00:07:33,120 +它的预期用途和限制, +it's intended use and limitations, + +165 +00:07:33,120 --> 00:07:36,180 +以及它识别出的潜在偏见, +as well as it's identified potential biases, + +166 +00:07:36,180 --> 00:07:37,440 +评估结果, +evaluation results, + +167 +00:07:37,440 --> 00:07:39,843 +以及有关如何使用你的模型的代码示例。 +and code samples on how to use your model. + +168 +00:07:41,460 --> 00:07:44,130 +为模型中心贡献模型的出色工作。 +Great work contributing a model to the Model Hub. + +169 +00:07:44,130 --> 00:07:46,440 +该模型现在可以在下游库中使用 +This model can now be used in downstream libraries + +170 +00:07:46,440 --> 00:07:48,783 +只需指定你的模型标识符。 +simply by specifying your model identifier. + diff --git a/subtitles/zh-CN/33_the-push-to-hub-api-(pytorch).srt b/subtitles/zh-CN/33_the-push-to-hub-api-(pytorch).srt new file mode 100644 index 000000000..e27897b99 --- /dev/null +++ b/subtitles/zh-CN/33_the-push-to-hub-api-(pytorch).srt @@ -0,0 +1,545 @@ +1 +00:00:00,321 --> 00:00:01,497 +(空气呼啸) +(air whooshing) + +2 +00:00:01,497 --> 00:00:02,330 +(笑脸弹出) +(smiley face popping) + +3 +00:00:02,330 --> 00:00:05,130 +(空气呼啸) +(air whooshing) + +4 +00:00:05,130 --> 00:00:06,830 +- [Instructor] 所以推送到 hub API。 +- [Instructor] So push to hub API. + +5 +00:00:08,310 --> 00:00:10,533 +让我们看一下推送到集线器 API。 +Let's have a look at the push to hub API. + +6 +00:00:11,730 --> 00:00:14,640 +你需要使用你的 Hugging Face 帐户登录 +You will need to be logged in with your Hugging Face account + +7 +00:00:14,640 --> 00:00:17,400 +你可以通过执行第一个单元格来做到这一点, +which you can do by executing this first cell, + +8 +00:00:17,400 --> 00:00:21,123 +或者在终端中输入 huggingface-cli login。 +or by typing huggingface-cli login in a terminal. + +9 +00:00:21,990 --> 00:00:26,640 +只需输入你的用户名和密码,然后点击登录, +Just enter you username and password, then click login, + +10 +00:00:26,640 --> 00:00:28,620 +这将存储一个通知令牌 +this will store a notification token + +11 +00:00:28,620 --> 00:00:30,670 +在你正在使用的机器的缓存中。 +in the cache of the machine you're using. + +12 +00:00:31,890 --> 00:00:35,790 +现在,让我们对 BERT 模型进行微调 +Now, let's launch a fine tuning of a BERT model + +13 +00:00:35,790 --> 00:00:37,920 +在 GLUE COLA 数据集上。 +on the GLUE COLA dataset. + +14 +00:00:37,920 --> 00:00:39,600 +我们不会讨论微调代码 +We won't go over the fine tuning code + +15 +00:00:39,600 --> 00:00:42,270 +因为你可以在任何变压器教程中找到它, +because you can find it in any transformer tutorial, + +16 +00:00:42,270 --> 00:00:44,670 +或通过查看下面的视频链接。 +or by looking at the videos link below. + +17 +00:00:44,670 --> 00:00:46,470 +我们感兴趣的是 +What interests us here is + +18 +00:00:46,470 --> 00:00:48,970 +我们如何在训练期间利用模型中心。 +how we can leverage the model hub during training. + +19 +00:00:49,860 --> 00:00:52,980 +这是通过 “push_to_hub=true” 参数完成的 +This is done with the "push_to_hub=true" argument + +20 +00:00:52,980 --> 00:00:55,530 +传入你的 TrainingArguments。 +passed in your TrainingArguments. + +21 +00:00:55,530 --> 00:00:57,240 +这将自动上传你的模型 +This will automatically upload your model + +22 +00:00:57,240 --> 00:00:59,400 +每次保存到集线器, +to the Hub each time it is saved, + +23 +00:00:59,400 --> 00:01:01,323 +所以我们案例中的每个时代。 +so every epoch in our case. + +24 +00:01:02,280 --> 00:01:04,860 +这允许你从不同的机器恢复训练 +This allows you to resume training from a different machine + +25 +00:01:04,860 --> 00:01:06,873 +如果当前的被打断。 +if the current one gets interrupted. + +26 +00:01:08,220 --> 00:01:10,440 +该模型将在你的名称空间中更新 +The model will be updated in your name space + +27 +00:01:10,440 --> 00:01:14,640 +使用你默认选择的输出目录的名称。 +with the name of the output directory you picked by default. + +28 +00:01:14,640 --> 00:01:16,020 +你可以选择其他名称 +You can choose another name + +29 +00:01:16,020 --> 00:01:19,113 +通过将其传递给 hub_model_id 参数。 +by passing it to the hub_model_id argument. + +30 +00:01:20,070 --> 00:01:23,370 +你还可以推动你所属的组织内部 +You can also push inside an organization you are a member of + +31 +00:01:23,370 --> 00:01:25,740 +通过传递完整的存储库名称, +by passing a full repository name, + +32 +00:01:25,740 --> 00:01:28,933 +以组织名称 /, +with the name of the organization/, + +33 +00:01:28,933 --> 00:01:30,433 +你要选择的型号 ID。 +the model ID you want to pick. + +34 +00:01:32,250 --> 00:01:34,650 +完成后,我们就可以开始训练了, +With that done, we can just launch training, + +35 +00:01:34,650 --> 00:01:36,093 +稍等一下。 +and wait a little bit. + +36 +00:01:36,960 --> 00:01:39,033 +我会从视频中缩短等待时间。 +I'll cut the waiting time from the video. + +37 +00:01:43,260 --> 00:01:46,350 +请注意,模型是异步推送的, +Note that the model is pushed asynchronously, + +38 +00:01:46,350 --> 00:01:47,730 +意味着训练继续 +meaning that the training continues + +39 +00:01:47,730 --> 00:01:49,730 +当你的模型上传到集线器时。 +while your model is uploaded to the hub. + +40 +00:01:51,060 --> 00:01:52,950 +当你的第一次提交完成时, +When your first commit is finished, + +41 +00:01:52,950 --> 00:01:55,650 +你可以去中心检查你的模型 +you can go inspect your model on the Hub + +42 +00:01:55,650 --> 00:01:57,960 +通过查看你的名称空间, +by looking inside your name space, + +43 +00:01:57,960 --> 00:01:59,943 +你会在最上面找到它。 +and you'll find it at the very top. + +44 +00:02:01,980 --> 00:02:04,200 +你甚至可以开始使用它的推理小部件 +You can even start playing with its inference widget + +45 +00:02:04,200 --> 00:02:06,630 +在继续训练的同时。 +while it's continuing the training. + +46 +00:02:06,630 --> 00:02:09,270 +Cola 数据集让模型确定 +The Cola data set tasks the model with determining + +47 +00:02:09,270 --> 00:02:11,970 +如果句子在语法上是正确的。 +if the sentence is grammatically correct on that. + +48 +00:02:11,970 --> 00:02:15,510 +所以我们选择一个错误句子的例子来测试它。 +So we pick an example of incorrect sentence to test it. + +49 +00:02:15,510 --> 00:02:16,950 +请注意,这需要一些时间 +Note that it'll take a bit of time + +50 +00:02:16,950 --> 00:02:18,750 +在推理 API 中加载模型, +to load your model inside the inference APIs, + +51 +00:02:18,750 --> 00:02:20,880 +所以第一次尝试使用它。 +so first time you try to use it. + +52 +00:02:20,880 --> 00:02:23,280 +我们将按时间从视频中删减。 +We'll cut by time from the video. + +53 +00:02:23,280 --> 00:02:24,870 +标签有问题, +There is something wrong with the labels, + +54 +00:02:24,870 --> 00:02:27,360 +但我们稍后会在本视频中修复它。 +but we'll fix it later in this video. + +55 +00:02:27,360 --> 00:02:29,520 +一旦你的训练结束, +Once your training is finished, + +56 +00:02:29,520 --> 00:02:31,770 +你应该和教练一起做最后一击 +you should do one last push with the trainer + +57 +00:02:31,770 --> 00:02:33,840 +推到一个方法。 +that pushed to a method. + +58 +00:02:33,840 --> 00:02:35,430 +这是有两个原因的。 +This is for two reason. + +59 +00:02:35,430 --> 00:02:36,750 +首先,这将确保 +First, this will make sure + +60 +00:02:36,750 --> 00:02:39,180 +你正在预测模型的最终版本 +you are predicting the final version of your model + +61 +00:02:39,180 --> 00:02:40,680 +如果你还没有。 +if you didn't already. + +62 +00:02:40,680 --> 00:02:42,480 +例如,如果你曾经保存 +For instance, if you used to save + +63 +00:02:42,480 --> 00:02:46,980 +每一步策略而不是每秒, +every in step strategy instead of every second, + +64 +00:02:46,980 --> 00:02:48,180 +这将起草一张模型卡 +this will draft a model card + +65 +00:02:48,180 --> 00:02:51,120 +那将是你的模型回购的登陆页面。 +that will be the landing page of your model repo. + +66 +00:02:51,120 --> 00:02:52,260 +提交完成后, +Once the commit is done, + +67 +00:02:52,260 --> 00:02:54,810 +让我们回到我们的模型页面并刷新。 +let's go back on our model page and refresh. + +68 +00:02:54,810 --> 00:02:56,820 +我们可以看到制图者模型卡 +We can see the drafters model card + +69 +00:02:56,820 --> 00:02:58,080 +其中包括信息, +which includes information, + +70 +00:02:58,080 --> 00:03:00,381 +我们发现调整了哪一种模型。 +and which one model we find tuned. + +71 +00:03:00,381 --> 00:03:03,570 +所以最终评估损失和指标, +So final evaluation loss and metric, + +72 +00:03:03,570 --> 00:03:06,300 +使用的训练超参数, +the training hyperparameter used, + +73 +00:03:06,300 --> 00:03:08,670 +中间训练结果, +the intermediate training results, + +74 +00:03:08,670 --> 00:03:10,320 +以及我们使用的框架版本 +and the framework versions we used + +75 +00:03:10,320 --> 00:03:13,173 +以便其他人可以轻松地重现我们的结果。 +so that other people can easily reproduce our results. + +76 +00:03:15,270 --> 00:03:16,860 +在所有这些信息之上, +On top of all that information, + +77 +00:03:16,860 --> 00:03:19,740 +培训师还包括一些解释的元数据 +the trainer also included some metadata that is interpreted + +78 +00:03:19,740 --> 00:03:22,650 +通过模型云中的 Hugging Face 网站。 +by the Hugging Face website in the model cloud. + +79 +00:03:22,650 --> 00:03:26,010 +你获得了一个漂亮的小部件中报告的指标的价值 +You get the value of the metrics reported in a nice widget + +80 +00:03:26,010 --> 00:03:29,640 +以及带有代码的论文排行榜的链接。 +as well as a link to a leaderboard with paper with code. + +81 +00:03:29,640 --> 00:03:32,550 +所以 Tensorboard runs 也被推送了 +So the Tensorboard runs have also been pushed + +82 +00:03:32,550 --> 00:03:34,560 +到这份报告,我们可以看看他们 +to this report, and we can look at them + +83 +00:03:34,560 --> 00:03:36,000 +直接从模型中心 +directly from the model hub + +84 +00:03:36,000 --> 00:03:38,850 +通过单击训练指标子菜单。 +by clicking on the training metrics sub menu. + +85 +00:03:38,850 --> 00:03:39,795 +如果你不使用 Trainer API +If you are not using the Trainer API + +86 +00:03:39,795 --> 00:03:42,510 +微调你的模型, +to fine-tune your model, + +87 +00:03:42,510 --> 00:03:43,770 +你可以使用 push_to_hub 方法 +you can use a push_to_hub method + +88 +00:03:43,770 --> 00:03:46,427 +在模型上,并直接标记器。 +on the model, and tokenizer directly. + +89 +00:03:46,427 --> 00:03:50,160 +让我们测试一下以修复推理小部件中的所有标签。 +Let's test this to fix all labels in the inference widget. + +90 +00:03:50,160 --> 00:03:52,740 +推理小部件使用不同的标签名称 +The inference widget was using different names for labels + +91 +00:03:52,740 --> 00:03:54,810 +因为我们没有注明对应 +because we did not indicate the correspondence + +92 +00:03:54,810 --> 00:03:57,030 +在整数和标签名称之间。 +between integer and label names. + +93 +00:03:57,030 --> 00:03:58,740 +我们可以在配置中解决这个问题 +We can fix this in the configuration + +94 +00:03:58,740 --> 00:04:01,350 +通过坐在 label2id, +by sitting the label2id, + +95 +00:04:01,350 --> 00:04:04,170 +和 id2label 字段通过适当的值 +and id2label fields through the proper values + +96 +00:04:04,170 --> 00:04:06,933 +将模型配置推送到集线器时。 +when pushing the model config to the hub. + +97 +00:04:07,950 --> 00:04:10,620 +完成后,我们可以在网站上查看, +Once this is done, we can check on the website, + +98 +00:04:10,620 --> 00:04:13,380 +模型现在显示正确的标签。 +and the model is now showing the proper label. + +99 +00:04:13,380 --> 00:04:15,240 +现在模型在集线器上, +Now that the model is on the hub, + +100 +00:04:15,240 --> 00:04:17,370 +我们可以在任何地方使用它 +we can use it from anywhere + +101 +00:04:17,370 --> 00:04:19,920 +就像我们对任何其他 Transformer 模型一样 +as we would any other Transformer model + +102 +00:04:19,920 --> 00:04:21,113 +使用 from_pretrained 方法 +with the from_pretrained method + +103 +00:04:21,113 --> 00:04:22,923 +具有管道功能。 +of with the pipeline function. + +104 +00:04:34,350 --> 00:04:36,780 +我们只需要使用集线器的标识符, +We just have to use the identifier from the hub, + +105 +00:04:36,780 --> 00:04:39,450 +我们可以看到模型配置和权重 +and we can see that the model configuration and weights + +106 +00:04:39,450 --> 00:04:42,483 +以及标记化的文件会自动下载。 +as well as the tokenized files are automatically downloaded. + +107 +00:04:53,880 --> 00:04:55,950 +在下一次培训中尝试 push_to_hub API +Try the push_to_hub API in the next training + +108 +00:04:55,950 --> 00:04:58,650 +轻松与世界其他地方分享你的模型。 +to easily share your model with the rest of the world. + +109 +00:05:01,151 --> 00:05:03,818 +(空气呼啸) +(air whooshing) + diff --git a/subtitles/zh-CN/34_the-push-to-hub-api-(tensorflow).srt b/subtitles/zh-CN/34_the-push-to-hub-api-(tensorflow).srt new file mode 100644 index 000000000..e05f61932 --- /dev/null +++ b/subtitles/zh-CN/34_the-push-to-hub-api-(tensorflow).srt @@ -0,0 +1,1010 @@ +1 +00:00:00,587 --> 00:00:02,670 +(嗖嗖) +(swoosh) + +2 +00:00:05,100 --> 00:00:07,080 +- [旁白] 嗨,这将是一个视频 +- [Narrator] Hi, this is going to be a video + +3 +00:00:07,080 --> 00:00:09,420 +关于 push_to_hub API +about the push_to_hub API + +4 +00:00:09,420 --> 00:00:10,670 +适用于 Tensorflow 和 Keras。 +for Tensorflow and Keras. + +5 +00:00:11,820 --> 00:00:14,850 +因此,首先,我们将打开我们的笔记本。 +So, to get started, we'll open up our notebook. + +6 +00:00:14,850 --> 00:00:16,920 +你需要做的第一件事就是登录 +And the first thing you'll need to do is log in to + +7 +00:00:16,920 --> 00:00:18,170 +你的 HuggingFace 帐户, +your HuggingFace account, + +8 +00:00:19,043 --> 00:00:20,663 +例如笔记本登录功能。 +for example with the notebook login function. + +9 +00:00:21,570 --> 00:00:24,630 +所以要使用它,你只需调用函数, +So to use that, you simply call the function, + +10 +00:00:24,630 --> 00:00:26,010 +弹出窗口将出现。 +the popup will emerge. + +11 +00:00:26,010 --> 00:00:28,800 +你将输入你的用户名和密码, +You will enter your username and password, + +12 +00:00:28,800 --> 00:00:31,425 +我要在这里从我的密码管理器中取出, +which I'm going to pull out of my password manager here, + +13 +00:00:31,425 --> 00:00:33,108 +然后你登录。 +and you log in. + +14 +00:00:33,108 --> 00:00:35,670 +接下来的两个单元格只是 +The next two cells are just + +15 +00:00:35,670 --> 00:00:37,080 +为训练做好一切准备。 +getting everything ready for training. + +16 +00:00:37,080 --> 00:00:38,940 +所以我们只是要加载一个数据集, +So we're just going to load a dataset, + +17 +00:00:38,940 --> 00:00:41,100 +我们要标记那个数据集, +we're going to tokenize that dataset, + +18 +00:00:41,100 --> 00:00:42,990 +然后我们将加载我们的模型并编译 +and then we're going to load our model and compile + +19 +00:00:42,990 --> 00:00:45,660 +它与标准的 Adam 优化器。 +it with the standard Adam optimizer. + +20 +00:00:45,660 --> 00:00:47,560 +所以我将运行所有这些。 +So I'm just going to run all of those. + +21 +00:00:49,830 --> 00:00:52,080 +我们等几秒钟, +We'll wait a few seconds, + +22 +00:00:52,080 --> 00:00:54,280 +一切都应该为训练做好准备。 +and everything should be ready for training. + +23 +00:00:57,983 --> 00:00:58,816 +好的。 +Okay. + +24 +00:00:58,816 --> 00:01:01,440 +所以现在我们准备好训练了。 +So now we're ready to train. + +25 +00:01:01,440 --> 00:01:03,030 +我将向你展示两种方法 +I'm going to show you the two ways + +26 +00:01:03,030 --> 00:01:05,130 +你可以将你的模型推送到 Hub。 +you can push your model to the Hub. + +27 +00:01:05,130 --> 00:01:08,190 +所以第一个是 PushToHubCallback。 +So the first is with the PushToHubCallback. + +28 +00:01:08,190 --> 00:01:10,107 +所以在 Keras 中回调 +So a callback in Keras + +29 +00:01:10,107 --> 00:01:13,710 +是在训练期间定期调用的函数。 +is a function that's called regularly during training. + +30 +00:01:13,710 --> 00:01:17,400 +你可以将其设置为在一定数量的步骤后调用, +You can set it to be called after a certain number of steps, + +31 +00:01:17,400 --> 00:01:21,427 +或每个时期,甚至只是在训练结束时一次。 +or every epoch, or even just once at the end of training. + +32 +00:01:21,427 --> 00:01:25,080 +所以 Keras 中有很多回调,例如, +So a lot of callbacks in Keras, for example, + +33 +00:01:25,080 --> 00:01:28,050 +控制学习率在高原上衰减, +control learning rate decaying on plateau, + +34 +00:01:28,050 --> 00:01:30,047 +诸如此类的事情。 +and things like that. + +35 +00:01:30,047 --> 00:01:32,520 +所以这个回调,默认情况下, +So this callback, by default, + +36 +00:01:32,520 --> 00:01:35,760 +每个时期都会将你的模型保存到 Hub 一次。 +will save your model to the Hub once every epoch. + +37 +00:01:35,760 --> 00:01:37,080 +这真的很有帮助, +And that's really helpful, + +38 +00:01:37,080 --> 00:01:38,790 +特别是如果你的训练时间很长, +especially if your training is very long, + +39 +00:01:38,790 --> 00:01:40,800 +因为那意味着你可以从那个保存中恢复, +because that means you can resume from that save, + +40 +00:01:40,800 --> 00:01:43,290 +所以你得到了你的模型的自动云保存。 +so you get this automatic cloud-saving of your model. + +41 +00:01:43,290 --> 00:01:45,027 +你甚至可以进行推理 +And you can even run inference + +42 +00:01:45,027 --> 00:01:47,730 +使用模型的检查点 +with the checkpoints of your model + +43 +00:01:47,730 --> 00:01:50,208 +已通过此回调上传。 +that have been uploaded by this callback. + +44 +00:01:50,208 --> 00:01:52,260 +这意味着你可以, +And that means you can, + +45 +00:01:52,260 --> 00:01:54,150 +你知道,运行一些测试输入 +y'know, run some test inputs + +46 +00:01:54,150 --> 00:01:56,100 +并实际查看你的模型是如何工作的 +and actually see how your model works + +47 +00:01:56,100 --> 00:01:57,990 +在训练的各个阶段, +at various stages during training, + +48 +00:01:57,990 --> 00:01:59,540 +这是一个非常好的功能。 +which is a really nice feature. + +49 +00:02:00,390 --> 00:02:03,960 +所以我们要添加 PushToHubCallback, +So we're going to add the PushToHubCallback, + +50 +00:02:03,960 --> 00:02:05,670 +它只需要几个参数。 +and it takes just a few arguments. + +51 +00:02:05,670 --> 00:02:08,250 +所以第一个参数是临时目录 +So the first argument is the temporary directory + +52 +00:02:08,250 --> 00:02:10,260 +该文件将被保存到 +that files are going to be saved to + +53 +00:02:10,260 --> 00:02:12,150 +在将它们上传到 Hub 之前。 +before they're uploaded to the Hub. + +54 +00:02:12,150 --> 00:02:14,127 +第二个参数是分词器, +The second argument is the tokenizer, + +55 +00:02:14,127 --> 00:02:15,808 +第三个参数在这里 +and the third argument here + +56 +00:02:15,808 --> 00:02:19,080 +是关键字参数 hub_model_id。 +is the keyword argument hub_model_id. + +57 +00:02:19,080 --> 00:02:21,330 +所以这就是它将被保存的名称 +So that's the name it's going to be saved under + +58 +00:02:21,330 --> 00:02:23,006 +在 HuggingFace Hub 上。 +on the HuggingFace Hub. + +59 +00:02:23,006 --> 00:02:26,267 +你还可以上传到组织帐户 +You can also upload to an organization account + +60 +00:02:26,267 --> 00:02:29,370 +只需添加组织名称 +just by adding the organization name + +61 +00:02:29,370 --> 00:02:32,460 +在带有斜杠的存储库名称之前,就像这样。 +before the repository name with a slash, like this. + +62 +00:02:32,460 --> 00:02:34,020 +所以你可能没有权限 +So you probably don't have permissions + +63 +00:02:34,020 --> 00:02:36,000 +上传到 HuggingFace 组织, +to upload to the HuggingFace organization, + +64 +00:02:36,000 --> 00:02:37,170 +如果你这样做请提交错误 +if you do please file a bug + +65 +00:02:37,170 --> 00:02:38,973 +并非常紧急地通知我们。 +and let us know extremely urgently. + +66 +00:02:40,830 --> 00:02:42,960 +但如果你确实有权访问你自己的组织, +But if you do have access to your own organization, + +67 +00:02:42,960 --> 00:02:44,730 +那么你可以使用相同的方法 +then you can use that same approach + +68 +00:02:44,730 --> 00:02:46,650 +将模型上传到他们的帐户 +to upload models to their account + +69 +00:02:46,650 --> 00:02:50,760 +而不是你自己的个人模型集。 +instead of to your own personal set of models. + +70 +00:02:50,760 --> 00:02:53,520 +所以,一旦你回电了, +So, once you've made your callback, + +71 +00:02:53,520 --> 00:02:56,310 +你只需将它添加到回调列表 +you simply add it to the callbacks list + +72 +00:02:56,310 --> 00:02:58,080 +当你调用 model.fit 时。 +when you're calling model.fit. + +73 +00:02:58,080 --> 00:03:01,110 +一切都从那里为你上传, +And everything is uploaded for you from there, + +74 +00:03:01,110 --> 00:03:02,610 +没有什么可担心的。 +there's nothing else to worry about. + +75 +00:03:02,610 --> 00:03:04,530 +上传模型的第二种方式, +The second way to upload a model, though, + +76 +00:03:04,530 --> 00:03:07,020 +就是调用 model.push_to_hub。 +is to call model.push_to_hub. + +77 +00:03:07,020 --> 00:03:09,086 +所以这更像是一种一次性的方法。 +So this is more of a once-off method. + +78 +00:03:09,086 --> 00:03:11,550 +在训练期间不会定期调用它。 +It's not called regularly during training. + +79 +00:03:11,550 --> 00:03:13,680 +你可以随时手动调用它 +You can just call this manually whenever you want to + +80 +00:03:13,680 --> 00:03:15,240 +将模型上传到集线器。 +upload a model to the hub. + +81 +00:03:15,240 --> 00:03:18,949 +所以我们建议在训练结束后运行这个, +So we recommend running this after the end of training, + +82 +00:03:18,949 --> 00:03:21,870 +只是为了确保你有一条提交消息 +just to make sure that you have a commit message + +83 +00:03:21,870 --> 00:03:24,060 +保证这是最终版本 +to guarantee that this was the final version + +84 +00:03:24,060 --> 00:03:26,143 +训练结束时的模型。 +of the model at the end of training. + +85 +00:03:26,143 --> 00:03:27,930 +它只是确保,你知道, +And it just makes sure that, you know, + +86 +00:03:27,930 --> 00:03:30,480 +你正在使用最终的训练结束模型 +you're working with the definitive end-of-training model + +87 +00:03:30,480 --> 00:03:32,190 +而不是不小心使用检查点 +and not accidentally using a checkpoint + +88 +00:03:32,190 --> 00:03:34,224 +从沿途的某个地方。 +from somewhere along the way. + +89 +00:03:34,224 --> 00:03:37,173 +所以我要运行这两个单元格。 +So I'm going to run both of these cells. + +90 +00:03:39,299 --> 00:03:41,716 +然后我要在这里剪视频, +And then I'm going to cut the video here, + +91 +00:03:41,716 --> 00:03:43,080 +只是因为训练需要几分钟。 +just because training is going to take a couple of minutes. + +92 +00:03:43,080 --> 00:03:44,580 +所以我会跳到最后, +So I'll skip forward to the end of that, + +93 +00:03:44,580 --> 00:03:46,320 +当模型全部上传后, +when the models have all been uploaded, + +94 +00:03:46,320 --> 00:03:48,390 +我会告诉你怎么做 +and I'm gonna show you how you can + +95 +00:03:48,390 --> 00:03:50,010 +访问 Hub 中的模型, +access the models in the Hub, + +96 +00:03:50,010 --> 00:03:52,713 +以及你可以从那里用它们做的其他事情。 +and the other things you can do with them from there. + +97 +00:03:55,440 --> 00:03:56,700 +好的,我们回来了, +Okay, we're back, + +98 +00:03:56,700 --> 00:03:59,160 +我们的模型已上传。 +and our model was uploaded. + +99 +00:03:59,160 --> 00:04:00,750 +两者都由 PushToHubCallback +Both by the PushToHubCallback + +100 +00:04:00,750 --> 00:04:04,251 +以及我们在训练后调用 model.push_to_hub。 +and also by our call to model.push_to_hub after training. + +101 +00:04:04,251 --> 00:04:05,910 +所以一切看起来都很好。 +So everything's looking good. + +102 +00:04:05,910 --> 00:04:09,960 +所以现在如果我们转到我在 HuggingFace 上的个人资料, +So now if we drop over to my profile on HuggingFace, + +103 +00:04:09,960 --> 00:04:12,630 +你只需点击个人资料按钮就可以到达那里 +and you can get there just by clicking the profile button + +104 +00:04:12,630 --> 00:04:13,680 +在下拉列表中。 +in the dropdown. + +105 +00:04:13,680 --> 00:04:16,860 +我们可以看到 bert-fine-tuned-cola 模型在这里, +We can see that the bert-fine-tuned-cola model is here, + +106 +00:04:16,860 --> 00:04:18,369 +并于 3 分钟前更新。 +and was updated 3 minutes ago. + +107 +00:04:18,369 --> 00:04:20,520 +所以它总是在你的列表的顶部, +So it'll always be at the top of your list, + +108 +00:04:20,520 --> 00:04:23,340 +因为它们是按最近更新时间排序的。 +because they're sorted by how recently they were updated. + +109 +00:04:23,340 --> 00:04:25,740 +我们可以立即开始查询我们的模型。 +And we can start querying our model immediately. + +110 +00:04:30,564 --> 00:04:32,939 +所以我们训练的数据集 +So the dataset we were training on + +111 +00:04:32,939 --> 00:04:34,320 +是 Glue CoLA 数据集, +is the Glue CoLA dataset, + +112 +00:04:34,320 --> 00:04:36,210 +CoLA 是代表 +and CoLA is an acronym standing for + +113 +00:04:36,210 --> 00:04:39,420 +语言可接受性语料库。 +the Corpus of Linguistic Acceptability. + +114 +00:04:39,420 --> 00:04:42,480 +所以这意味着正在训练模型来决定 +So what that means is the model is being trained to decide + +115 +00:04:42,480 --> 00:04:46,350 +如果一个句子在语法或语言上没问题, +if a sentence is grammatically or linguistically okay, + +116 +00:04:46,350 --> 00:04:48,171 +或者它是否有问题。 +or if there's a problem with it. + +117 +00:04:48,171 --> 00:04:52,890 +例如,我们可以说,“这是一个合法的句子。” +For example, we could say, "This is a legitimate sentence." + +118 +00:04:52,890 --> 00:04:54,180 +希望它意识到 +And hopefully it realizes that + +119 +00:04:54,180 --> 00:04:56,080 +这实际上是一个合法的判决。 +this is in fact a legitimate sentence. + +120 +00:04:57,630 --> 00:05:00,240 +所以加载模型可能需要几秒钟 +So it might take a couple of seconds for the model to load + +121 +00:05:00,240 --> 00:05:03,060 +当你第一次调用它时。 +when you call it for the first time. + +122 +00:05:03,060 --> 00:05:05,960 +所以我可能会在这里从这个视频中剪掉几秒钟。 +So I might cut a couple of seconds out of this video here. + +123 +00:05:07,860 --> 00:05:09,060 +好的,我们回来了。 +Okay, we're back. + +124 +00:05:09,060 --> 00:05:12,407 +所以模型加载了,我们得到了一个输出, +So the model loaded and we got an output, + +125 +00:05:12,407 --> 00:05:14,340 +但这里有一个明显的问题。 +but there's an obvious problem here. + +126 +00:05:14,340 --> 00:05:16,888 +所以这些标签并没有真正告诉我们 +So these labels aren't really telling us + +127 +00:05:16,888 --> 00:05:19,740 +模型实际分配了哪些类别 +what categories the model has actually assigned + +128 +00:05:19,740 --> 00:05:21,655 +到这个输入句子。 +to this input sentence. + +129 +00:05:21,655 --> 00:05:23,520 +所以如果我们想解决这个问题, +So if we want to fix that, + +130 +00:05:23,520 --> 00:05:26,010 +我们要确保模型配置 +we want to make sure the model config + +131 +00:05:26,010 --> 00:05:28,980 +每个标签类别都有正确的名称, +has the correct names for each of the label classes, + +132 +00:05:28,980 --> 00:05:30,707 +然后我们要上传该配置。 +and then we want to upload that config. + +133 +00:05:30,707 --> 00:05:32,220 +所以我们可以在这里做。 +So we can do that down here. + +134 +00:05:32,220 --> 00:05:34,050 +要获取标签名称, +To get the label names, + +135 +00:05:34,050 --> 00:05:36,547 +我们可以从我们加载的数据集中得到它, +we can get that from the dataset we loaded, + +136 +00:05:36,547 --> 00:05:39,627 +从它具有的特性属性。 +from the features attribute it has. + +137 +00:05:39,627 --> 00:05:42,217 +然后我们可以创建字典 +And then we can create dictionaries + +138 +00:05:42,217 --> 00:05:44,865 +“id2label” 和 “label2id”, +"id2label" and "label2id", + +139 +00:05:44,865 --> 00:05:47,452 +并将它们分配给模型配置。 +and just assign them to the model config. + +140 +00:05:47,452 --> 00:05:50,790 +然后我们可以推送我们更新的配置, +And then we can just push our updated config, + +141 +00:05:50,790 --> 00:05:54,690 +这将覆盖 Hub 存储库中的现有配置。 +and that'll override the existing config in the Hub repo. + +142 +00:05:54,690 --> 00:05:56,368 +所以这已经完成了。 +So that's just been done. + +143 +00:05:56,368 --> 00:05:58,320 +所以现在,如果我们回到这里, +So now, if we go back here, + +144 +00:05:58,320 --> 00:06:00,000 +我要用一个稍微不同的句子 +I'm going to use a slightly different sentence + +145 +00:06:00,000 --> 00:06:03,540 +因为句子的输出有时会被缓存。 +because the outputs for sentences are sometimes cached. + +146 +00:06:03,540 --> 00:06:06,030 +所以,如果我们想产生新的结果 +And so, if we want to generate new results + +147 +00:06:06,030 --> 00:06:07,590 +我将使用一些稍微不同的东西。 +I'm going to use something slightly different. + +148 +00:06:07,590 --> 00:06:09,783 +因此,让我们尝试一个不正确的句子。 +So let's try an incorrect sentence. + +149 +00:06:10,830 --> 00:06:12,640 +所以这不是有效的英语语法 +So this is not valid English grammar + +150 +00:06:13,538 --> 00:06:15,030 +希望模型能看到这一点。 +and hopefully the model will see that. + +151 +00:06:15,030 --> 00:06:16,958 +它会在这里重新加载, +It's going to reload here, + +152 +00:06:16,958 --> 00:06:18,630 +所以我要在这里缩短几秒钟, +so I'm going to cut a couple of seconds here, + +153 +00:06:18,630 --> 00:06:20,933 +然后我们会看到模型会说什么。 +and then we'll see what the model is going to say. + +154 +00:06:22,860 --> 00:06:23,820 +好的。 +Okay. + +155 +00:06:23,820 --> 00:06:26,580 +所以这个模型,它的信心不是很好, +So the model, it's confidence isn't very good, + +156 +00:06:26,580 --> 00:06:28,830 +因为我们当然没有真正优化 +because of course we didn't really optimize + +157 +00:06:28,830 --> 00:06:30,630 +我们的超参数。 +our hyperparameters at all. + +158 +00:06:30,630 --> 00:06:32,190 +但它决定了这句话 +But it has decided that this sentence + +159 +00:06:32,190 --> 00:06:35,094 +不可接受的可能性大于可接受的可能性。 +is more likely to be unacceptable than acceptable. + +160 +00:06:35,094 --> 00:06:38,160 +大概如果我们更努力地训练 +Presumably if we tried a bit harder with training + +161 +00:06:38,160 --> 00:06:40,080 +我们可以获得更低的验证损失, +we could get a much lower validation loss, + +162 +00:06:40,080 --> 00:06:43,830 +因此模型的预测会更准确。 +and therefore the model's predictions would be more precise. + +163 +00:06:43,830 --> 00:06:46,260 +但是让我们再试一次我们原来的句子。 +But let's try our original sentence again. + +164 +00:06:46,260 --> 00:06:49,140 +当然,因为缓存的问题, +Of course, because of the caching issue, + +165 +00:06:49,140 --> 00:06:52,740 +我们看到原来的答案没有改变。 +we're seeing that the original answers are unchanged. + +166 +00:06:52,740 --> 00:06:55,196 +所以让我们尝试一个不同的,有效的句子。 +So let's try a different, valid sentence. + +167 +00:06:55,196 --> 00:06:58,767 +所以让我们试试,“This is a valid English sentence”。 +So let's try, "This is a valid English sentence". + +168 +00:07:00,150 --> 00:07:02,100 +我们看到现在模型正确地决定了 +And we see that now the model correctly decides + +169 +00:07:02,100 --> 00:07:04,290 +它被接受的可能性非常高, +that it has a very high probability of being acceptable, + +170 +00:07:04,290 --> 00:07:06,900 +并且被拒绝的可能性非常低。 +and a very low probability of being unacceptable. + +171 +00:07:06,900 --> 00:07:09,930 +所以你可以使用这个推理 API +So you can use this inference API + +172 +00:07:09,930 --> 00:07:12,810 +即使有训练期间上传的检查点, +even with the checkpoints that are uploaded during training, + +173 +00:07:12,810 --> 00:07:14,546 +所以看看如何 +so it can be very interesting to see how + +174 +00:07:14,546 --> 00:07:17,690 +模型对样本输入的预测发生变化 +the model's predictions for sample inputs change + +175 +00:07:17,690 --> 00:07:20,579 +在每个训练阶段。 +with each epoch of training. + +176 +00:07:20,579 --> 00:07:23,370 +另外,我们上传的模型 +Also, the model we've uploaded + +177 +00:07:23,370 --> 00:07:25,740 +你将可以访问,并且 +is going to be accessible to you and, + +178 +00:07:25,740 --> 00:07:28,046 +如果公开分享给其他任何人。 +if it's shared publicly, to anyone else. + +179 +00:07:28,046 --> 00:07:29,788 +所以如果你想加载那个模型, +So if you want to load that model, + +180 +00:07:29,788 --> 00:07:32,500 +你或其他任何人需要做的一切 +all you or anyone else needs to do + +181 +00:07:34,290 --> 00:07:37,440 +只是将它加载到管道中, +is just to load it in either a pipeline, + +182 +00:07:37,440 --> 00:07:40,925 +或者你可以加载它,例如, +or you can just load it with, for example, + +183 +00:07:40,925 --> 00:07:43,203 +TFAutoModelForSequenceClassification。 +TFAutoModelForSequenceClassification. + +184 +00:07:46,920 --> 00:07:49,989 +然后对于名称,你只需通过 +And then for the name you would just simply pass + +185 +00:07:49,989 --> 00:07:53,325 +你要上传的存储库的路径。 +the path to the repo you want to upload. + +186 +00:07:53,325 --> 00:07:55,890 +或者下载,不好意思。 +Or to download, excuse me. + +187 +00:07:55,890 --> 00:07:58,710 +所以如果我想再次使用这个模型, +So if I want to use this model again, + +188 +00:07:58,710 --> 00:08:00,667 +如果我想从集线器加载它, +if I want to load it from the hub, + +189 +00:08:00,667 --> 00:08:01,763 +我只是运行这一行代码。 +I just run this one line of code. + +190 +00:08:02,813 --> 00:08:03,773 +该模型将被下载。 +The model will be downloaded. + +191 +00:08:07,757 --> 00:08:10,080 +而且,运气好的话,它会准备好 +And, with any luck, it'll be ready to + +192 +00:08:10,080 --> 00:08:12,450 +对不同的数据集进行微调,进行预测, +fine-tune on a different dataset, make predictions with, + +193 +00:08:12,450 --> 00:08:14,340 +或者做任何你想做的事情。 +or do anything else you wanna do. + +194 +00:08:14,340 --> 00:08:17,700 +所以这是对如何的快速概述, +So that was a quick overview of how, + +195 +00:08:17,700 --> 00:08:19,470 +在你训练之后或训练期间, +after your training or during your training, + +196 +00:08:19,470 --> 00:08:21,420 +你可以将模型上传到 Hub, +you can upload models to the Hub, + +197 +00:08:21,420 --> 00:08:22,440 +你可以在那里检查站, +you can checkpoint there, + +198 +00:08:22,440 --> 00:08:24,240 +你可以从那里恢复训练, +you can resume training from there, + +199 +00:08:24,240 --> 00:08:26,790 +你可以得到推理结果 +and you can get inference results + +200 +00:08:26,790 --> 00:08:28,384 +来自你上传的模型。 +from the models you've uploaded. + +201 +00:08:28,384 --> 00:08:31,084 +所以谢谢你,我希望在以后的视频中见到你。 +So thank you, and I hope to see you in a future video. + +202 +00:08:32,852 --> 00:08:34,935 +(嗖嗖) +(swoosh) + diff --git a/subtitles/zh-CN/35_loading-a-custom-dataset.srt b/subtitles/zh-CN/35_loading-a-custom-dataset.srt new file mode 100644 index 000000000..fd3a794e2 --- /dev/null +++ b/subtitles/zh-CN/35_loading-a-custom-dataset.srt @@ -0,0 +1,385 @@ +1 +00:00:00,195 --> 00:00:01,426 +(屏幕呼啸) +(screen whooshing) + +2 +00:00:01,426 --> 00:00:02,614 +(贴纸弹出) +(sticker popping) + +3 +00:00:02,614 --> 00:00:06,150 +(屏幕呼啸) +(screen whooshing) + +4 +00:00:06,150 --> 00:00:08,430 +- 加载自定义数据集。 +- Loading a custom dataset. + +5 +00:00:08,430 --> 00:00:09,750 +尽管 Hugging Face Hub 主持 +Although the Hugging Face Hub hosts + +6 +00:00:09,750 --> 00:00:11,730 +超过一千个公共数据集, +over a thousand public datasets, + +7 +00:00:11,730 --> 00:00:12,930 +你经常需要处理数据 +you'll often need to work with data + +8 +00:00:12,930 --> 00:00:15,900 +存储在你的笔记本电脑或某些远程服务器上。 +that is stored on your laptop or some remote server. + +9 +00:00:15,900 --> 00:00:18,060 +在本视频中,我们将探讨数据集库如何 +In this video, we'll explore how the Datasets library + +10 +00:00:18,060 --> 00:00:20,310 +可用于加载不可用的数据集 +can be used to load datasets that aren't available + +11 +00:00:20,310 --> 00:00:21,510 +在 Hugging Face Hub 上。 +on the Hugging Face Hub. + +12 +00:00:22,980 --> 00:00:25,290 +正如你在此表中所见,Datasets 库 +As you can see in this table, the Datasets library + +13 +00:00:25,290 --> 00:00:26,700 +提供了几个内置脚本 +provides several in-built scripts + +14 +00:00:26,700 --> 00:00:29,370 +以多种格式加载数据集。 +to load datasets in several formats. + +15 +00:00:29,370 --> 00:00:31,200 +要以其中一种格式加载数据集, +To load a dataset in one of these formats, + +16 +00:00:31,200 --> 00:00:32,730 +你只需要提供格式的名称 +you just need to provide the name of the format + +17 +00:00:32,730 --> 00:00:34,350 +到 load_dataset 函数, +to the load_dataset function, + +18 +00:00:34,350 --> 00:00:35,790 +连同 data_files 参数 +along with a data_files argument + +19 +00:00:35,790 --> 00:00:37,610 +指向一个或多个文件路径或 URL。 +that points to one or more filepaths or URLs. + +20 +00:00:40,350 --> 00:00:43,590 +要查看实际效果,让我们从加载 CSV 文件开始。 +To see this in action, let's start by loading a CSV file. + +21 +00:00:43,590 --> 00:00:45,960 +在这个例子中,我们首先下载一个数据集 +In this example, we first download a dataset + +22 +00:00:45,960 --> 00:00:48,963 +关于来自 UCI 机器学习库的葡萄酒质量。 +about wine quality from the UCI machine learning repository. + +23 +00:00:50,220 --> 00:00:52,590 +由于这是一个 CSV 文件,因此我们指定 +Since this is a CSV file, we then specify + +24 +00:00:52,590 --> 00:00:53,943 +CSV 加载脚本。 +the CSV loading script. + +25 +00:00:55,320 --> 00:00:57,570 +现在,这个脚本需要知道我们的数据在哪里, +Now, this script needs to know where our data is located, + +26 +00:00:57,570 --> 00:00:58,650 +所以我们提供文件名 +so we provide the filename + +27 +00:00:58,650 --> 00:01:00,483 +作为 data_files 参数的一部分。 +as part of the data_files argument. + +28 +00:01:01,860 --> 00:01:03,360 +并且加载脚本还允许你 +And the loading script also allows you + +29 +00:01:03,360 --> 00:01:05,040 +传递几个关键字参数, +to pass several keyword arguments, + +30 +00:01:05,040 --> 00:01:06,750 +所以在这里我们也指定了 +so here we've also specified + +31 +00:01:06,750 --> 00:01:09,030 +分隔符是分号。 +that the separator is a semi-colon. + +32 +00:01:09,030 --> 00:01:10,380 +这样,我们就可以看到数据集 +And with that, we can see the dataset + +33 +00:01:10,380 --> 00:01:13,020 +作为 DatasetDict 对象自动加载, +is loaded automatically as a DatasetDict object, + +34 +00:01:13,020 --> 00:01:15,920 +CSV 文件中的每一列都表示为一个特征。 +with each column in the CSV file represented as a feature. + +35 +00:01:17,610 --> 00:01:20,280 +如果你的数据集位于 GitHub 等远程服务器上 +If your dataset is located on some remote server like GitHub + +36 +00:01:20,280 --> 00:01:22,050 +或其他一些存储库, +or some other repository, + +37 +00:01:22,050 --> 00:01:23,700 +这个过程实际上非常相似。 +the process is actually very similar. + +38 +00:01:23,700 --> 00:01:25,980 +唯一的区别是现在 data_files 参数 +The only difference is that now the data_files argument + +39 +00:01:25,980 --> 00:01:28,623 +指向 URL 而不是本地文件路径。 +points to a URL instead of a local filepath. + +40 +00:01:30,330 --> 00:01:33,270 +现在让我们看一下加载原始文本文件。 +Let's now take a look at loading raw text files. + +41 +00:01:33,270 --> 00:01:35,100 +这种格式在 NLP 中很常见, +This format is quite common in NLP, + +42 +00:01:35,100 --> 00:01:36,750 +你通常会找到书籍和戏剧 +and you'll typically find books and plays + +43 +00:01:36,750 --> 00:01:39,393 +只是一个包含原始文本的文件。 +are just a single file with raw text inside. + +44 +00:01:40,410 --> 00:01:43,020 +在这个例子中,我们有一个莎士比亚戏剧的文本文件 +In this example, we have a text file of Shakespeare plays + +45 +00:01:43,020 --> 00:01:45,330 +存储在 GitHub 存储库中。 +that's stored on a GitHub repository. + +46 +00:01:45,330 --> 00:01:47,040 +正如我们对 CSV 文件所做的那样, +And as we did for CSV files, + +47 +00:01:47,040 --> 00:01:49,020 +我们只需选择文本加载脚本 +we simply choose the text loading script + +48 +00:01:49,020 --> 00:01:51,423 +并将 data_files 参数指向 URL。 +and point the data_files argument to the URL. + +49 +00:01:52,260 --> 00:01:55,110 +如你所见,这些文件是逐行处理的, +As you can see, these files are processed line-by-line, + +50 +00:01:55,110 --> 00:01:57,690 +所以原始文本中的空行也被表示 +so empty lines in the raw text are also represented + +51 +00:01:57,690 --> 00:01:58,953 +作为数据集中的一行。 +as a row in the dataset. + +52 +00:02:00,810 --> 00:02:04,230 +对于 JSON 文件,有两种主要格式需要了解。 +For JSON files, there are two main formats to know about. + +53 +00:02:04,230 --> 00:02:06,060 +第一个叫做 JSON 行, +The first one is called JSON Lines, + +54 +00:02:06,060 --> 00:02:09,510 +文件中的每一行都是一个单独的 JSON 对象。 +where every row in the file is a separate JSON object. + +55 +00:02:09,510 --> 00:02:11,100 +对于这些文件,你可以加载数据集 +For these files, you can load the dataset + +56 +00:02:11,100 --> 00:02:13,020 +通过选择 JSON 加载脚本 +by selecting the JSON loading script + +57 +00:02:13,020 --> 00:02:16,143 +并将 data_files 参数指向文件或 URL。 +and pointing the data_files argument to the file or URL. + +58 +00:02:17,160 --> 00:02:19,410 +在这个例子中,我们加载了一个 JSON 行文件 +In this example, we've loaded a JSON lines files + +59 +00:02:19,410 --> 00:02:21,710 +基于 Stack Exchange 问题和答案。 +based on Stack Exchange questions and answers. + +60 +00:02:23,490 --> 00:02:26,610 +另一种格式是嵌套的 JSON 文件。 +The other format is nested JSON files. + +61 +00:02:26,610 --> 00:02:29,100 +这些文件基本上看起来像一本巨大的字典, +These files basically look like one huge dictionary, + +62 +00:02:29,100 --> 00:02:31,200 +所以 load_dataset 函数允许你指定 +so the load_dataset function allow you to specify + +63 +00:02:31,200 --> 00:02:32,733 +要加载哪个特定密钥。 +which specific key to load. + +64 +00:02:33,630 --> 00:02:35,910 +例如,用于问答的 SQuAD 数据集 +For example, the SQuAD dataset for question and answering + +65 +00:02:35,910 --> 00:02:38,340 +有它的格式,我们可以通过指定来加载它 +has its format, and we can load it by specifying + +66 +00:02:38,340 --> 00:02:40,340 +我们对数据字段感兴趣。 +that we're interested in the data field. + +67 +00:02:41,400 --> 00:02:42,780 +最后一件事要提 +There is just one last thing to mention + +68 +00:02:42,780 --> 00:02:44,910 +关于所有这些加载脚本。 +about all of these loading scripts. + +69 +00:02:44,910 --> 00:02:46,410 +你可以有不止一次分裂, +You can have more than one split, + +70 +00:02:46,410 --> 00:02:49,080 +你可以通过将数据文件视为字典来加载它们, +you can load them by treating data files as a dictionary, + +71 +00:02:49,080 --> 00:02:52,140 +并将每个拆分名称映射到其对应的文件。 +and map each split name to its corresponding file. + +72 +00:02:52,140 --> 00:02:53,970 +其他一切都保持完全不变 +Everything else stays completely unchanged + +73 +00:02:53,970 --> 00:02:55,350 +你可以看到一个加载的例子 +and you can see an example of loading + +74 +00:02:55,350 --> 00:02:58,283 +此 SQuAD 的训练和验证拆分均在此处。 +both the training and validation splits for this SQuAD here. + +75 +00:02:59,550 --> 00:03:02,310 +这样,你现在可以从笔记本电脑加载数据集, +And with that, you can now load datasets from your laptop, + +76 +00:03:02,310 --> 00:03:04,653 +Hugging Face Hub,或任何其他地方。 +the Hugging Face Hub, or anywhere else want. + +77 +00:03:06,277 --> 00:03:09,194 +(屏幕呼啸) +(screen whooshing) + diff --git "a/subtitles/zh-CN/36_slice-and-dice-a-dataset-\360\237\224\252.srt" "b/subtitles/zh-CN/36_slice-and-dice-a-dataset-\360\237\224\252.srt" new file mode 100644 index 000000000..3a9be5dbe --- /dev/null +++ "b/subtitles/zh-CN/36_slice-and-dice-a-dataset-\360\237\224\252.srt" @@ -0,0 +1,415 @@ +1 +00:00:00,215 --> 00:00:02,882 +(空气呼啸) +(air whooshing) + +2 +00:00:05,760 --> 00:00:07,623 +- 如何对数据集进行切片和切块? +- How to slice and dice the dataset? + +3 +00:00:08,760 --> 00:00:10,410 +大多数时候,你使用的数据 +Most of the time, the data you work with + +4 +00:00:10,410 --> 00:00:13,230 +不会为训练模型做好充分准备。 +won't be perfectly prepared for training models. + +5 +00:00:13,230 --> 00:00:15,810 +在本视频中,我们将探索各种功能 +In this video, we'll explore various features + +6 +00:00:15,810 --> 00:00:18,660 +数据集库提供的用于清理数据的数据集。 +that the datasets library provides to clean up your data. + +7 +00:00:19,915 --> 00:00:22,500 +数据集库提供了几种内置方法 +The datasets library provides several built-in methods + +8 +00:00:22,500 --> 00:00:25,350 +允许你以各种方式处理数据。 +that allow you to wrangle your data in various ways. + +9 +00:00:25,350 --> 00:00:27,360 +在本视频中,我们将了解如何随机播放 +In this video, we'll see how you can shuffle + +10 +00:00:27,360 --> 00:00:30,750 +并拆分你的数据,选择你感兴趣的行, +and split your data, select the rows you're interested in, + +11 +00:00:30,750 --> 00:00:32,070 +调整列, +tweak the columns, + +12 +00:00:32,070 --> 00:00:34,620 +并使用 map 方法应用处理函数。 +and apply processing functions with the map method. + +13 +00:00:35,640 --> 00:00:37,620 +让我们从洗牌开始。 +Let's start with shuffling. + +14 +00:00:37,620 --> 00:00:38,520 +这通常是个好主意 +It is generally a good idea + +15 +00:00:38,520 --> 00:00:40,140 +将改组应用于你的训练集 +to apply shuffling to your training set + +16 +00:00:40,140 --> 00:00:41,250 +这样你的模型就不会学习 +so that your model doesn't learn + +17 +00:00:41,250 --> 00:00:43,590 +任何人工排序数据。 +any artificial ordering the data. + +18 +00:00:43,590 --> 00:00:45,360 +如果你想洗牌整个数据集, +If you wanna shuffle the whole dataset, + +19 +00:00:45,360 --> 00:00:48,390 +你可以应用适当命名的 shuffle 方法。 +you can apply the appropriately named shuffle method. + +20 +00:00:48,390 --> 00:00:50,730 +你可以在这里看到这个方法的一个例子, +You can see an example of this method in action here, + +21 +00:00:50,730 --> 00:00:52,200 +我们在哪里下载了训练拆分 +where we've downloaded the training split + +22 +00:00:52,200 --> 00:00:55,000 +小队数据集并随机打乱所有行。 +of the squad dataset and shuffled all the rows randomly. + +23 +00:00:56,880 --> 00:00:58,230 +另一种打乱数据的方法 +Another way to shuffle the data + +24 +00:00:58,230 --> 00:01:00,930 +是创建随机训练和测试拆分。 +is to create random train and test splits. + +25 +00:01:00,930 --> 00:01:02,280 +如果你必须创建,这将很有用 +This can be useful if you have to create + +26 +00:01:02,280 --> 00:01:04,620 +你自己的测试从原始数据中分离出来。 +your own test splits from raw data. + +27 +00:01:04,620 --> 00:01:07,620 +为此,你只需应用 train_test_split 方法 +To do this, you just apply the train_test_split method + +28 +00:01:07,620 --> 00:01:10,740 +并指定测试拆分应该有多大。 +and specify how large the test split should be. + +29 +00:01:10,740 --> 00:01:14,310 +在这个例子中,我们指定测试集应该是 10% +In this example, we specify that the test set should be 10% + +30 +00:01:14,310 --> 00:01:15,963 +总数据集大小。 +of the total dataset size. + +31 +00:01:16,890 --> 00:01:19,140 +可以看到 train_test_split 方法的输出 +You can see that the output of the train_test_split method + +32 +00:01:19,140 --> 00:01:20,610 +是一个 DatasetDict 对象 +is a DatasetDict object + +33 +00:01:20,610 --> 00:01:22,743 +其键对应于新的拆分。 +whose keys correspond to the new splits. + +34 +00:01:25,170 --> 00:01:27,210 +现在我们知道如何打乱数据集了, +Now that we know how to shuffle the dataset, + +35 +00:01:27,210 --> 00:01:30,060 +让我们来看看返回我们感兴趣的行。 +let's take a look at returning the rows we're interested in. + +36 +00:01:30,060 --> 00:01:33,180 +最常见的方法是使用 select 方法。 +The most common way to do this is with the select method. + +37 +00:01:33,180 --> 00:01:34,590 +此方法需要一个列表 +This method expects a list + +38 +00:01:34,590 --> 00:01:36,750 +或数据集索引的生成器, +or a generator of the datasets indices, + +39 +00:01:36,750 --> 00:01:38,670 +然后将返回一个新的数据集对象 +and will then return a new dataset object + +40 +00:01:38,670 --> 00:01:40,143 +只包含那些行。 +containing just those rows. + +41 +00:01:41,490 --> 00:01:43,740 +如果你想创建一个随机的行样本, +If you wanna create a random sample of rows, + +42 +00:01:43,740 --> 00:01:45,360 +你可以通过链接洗牌来做到这一点 +you can do this by chaining the shuffle + +43 +00:01:45,360 --> 00:01:47,310 +并一起选择方法。 +and select methods together. + +44 +00:01:47,310 --> 00:01:48,450 +在这个例子中, +In this example, + +45 +00:01:48,450 --> 00:01:50,250 +我们创建了一个包含五个元素的样本 +we've created a sample of five elements + +46 +00:01:50,250 --> 00:01:51,423 +来自小队数据集。 +from the squad dataset. + +47 +00:01:53,550 --> 00:01:56,010 +在数据集中挑选特定行的最后一种方法 +The last way to pick out specific rows in a dataset + +48 +00:01:56,010 --> 00:01:58,290 +是通过应用过滤器方法。 +is by applying the filter method. + +49 +00:01:58,290 --> 00:02:00,120 +此方法检查每一行是否 +This method checks whether each row + +50 +00:02:00,120 --> 00:02:02,310 +是否满足某种条件。 +fulfills some condition or not. + +51 +00:02:02,310 --> 00:02:05,130 +例如,这里我们创建了一个小的 lambda 函数 +For example, here we've created a small lambda function + +52 +00:02:05,130 --> 00:02:08,460 +检查标题是否以字母 L 开头。 +that checks whether the title starts with the letter L. + +53 +00:02:08,460 --> 00:02:11,040 +一旦我们用 filter 方法应用这个函数, +Once we apply this function with the filter method, + +54 +00:02:11,040 --> 00:02:14,283 +我们得到仅包含这些行的数据子集。 +we get a subset of the data just containing these rows. + +55 +00:02:16,200 --> 00:02:18,600 +到目前为止,我们一直在谈论数据集的行, +So far, we've been talking about the rows of a dataset, + +56 +00:02:18,600 --> 00:02:20,490 +但是列呢? +but what about the columns? + +57 +00:02:20,490 --> 00:02:22,320 +数据集库有两个主要方法 +The datasets library has two main methods + +58 +00:02:22,320 --> 00:02:24,060 +用于转换列, +for transforming columns, + +59 +00:02:24,060 --> 00:02:26,760 +用于更改列名称的 rename_column 方法 +a rename_column method to change the name of the column + +60 +00:02:26,760 --> 00:02:29,460 +以及删除它们的 remove_columns 方法。 +and a remove_columns method to delete them. + +61 +00:02:29,460 --> 00:02:31,860 +你可以在此处查看这两种方法的示例。 +You can see examples of both these methods here. + +62 +00:02:34,140 --> 00:02:36,060 +一些数据集有嵌套的列, +Some datasets have nested columns, + +63 +00:02:36,060 --> 00:02:39,360 +你可以通过应用展平方法来扩展它们。 +and you can expand these by applying the flatten method. + +64 +00:02:39,360 --> 00:02:41,430 +例如,在小队数据集中, +For example, in the squad dataset, + +65 +00:02:41,430 --> 00:02:45,150 +answers 列包含文本和 answer_start 字段。 +the answers column contains a text and answer_start field. + +66 +00:02:45,150 --> 00:02:47,430 +如果我们想将它们提升到各自的专栏中, +If we wanna promote them to their own separate columns, + +67 +00:02:47,430 --> 00:02:49,383 +我们可以应用扁平化,如图所示。 +we can apply flatten as shown here. + +68 +00:02:51,300 --> 00:02:53,760 +当然,现在不讨论数据集库 +Now of course, no discussion of the datasets library + +69 +00:02:53,760 --> 00:02:56,880 +不提著名的 map 方法就完整了。 +would be complete without mentioning the famous map method. + +70 +00:02:56,880 --> 00:02:59,160 +此方法应用自定义处理功能 +This method applies a custom processing function + +71 +00:02:59,160 --> 00:03:01,140 +到数据集中的每一行。 +to each row in the dataset. + +72 +00:03:01,140 --> 00:03:03,360 +比如这里我们先定义 +For example, here we first define + +73 +00:03:03,360 --> 00:03:04,890 +小写标题函数, +a lowercase title function, + +74 +00:03:04,890 --> 00:03:07,503 +这只是将标题列中的文本小写。 +that simply lowercases the text in the title column. + +75 +00:03:08,640 --> 00:03:11,700 +然后我们将该函数提供给 map 方法, +And then we feed that function to the map method, + +76 +00:03:11,700 --> 00:03:14,223 +瞧,我们现在有了小写标题。 +and voila, we now have lowercase titles. + +77 +00:03:16,020 --> 00:03:18,360 +map 方法也可用于批量输入行 +The map method can also be used to feed batches of rows + +78 +00:03:18,360 --> 00:03:20,100 +到处理函数。 +to the processing function. + +79 +00:03:20,100 --> 00:03:22,410 +这对于标记化特别有用 +This is especially useful for tokenization + +80 +00:03:22,410 --> 00:03:25,290 +其中分词器由分词器库支持, +where the tokenizer is backed by the Tokenizers library, + +81 +00:03:25,290 --> 00:03:26,910 +他们可以使用快速多线程 +and they can use fast multithreading + +82 +00:03:26,910 --> 00:03:28,563 +并行处理批次。 +to process batches in parallel. + +83 +00:03:30,056 --> 00:03:32,723 +(空气呼啸) +(air whooshing) + diff --git "a/subtitles/zh-CN/37_datasets-+-dataframes-=-\342\235\244\357\270\217.srt" "b/subtitles/zh-CN/37_datasets-+-dataframes-=-\342\235\244\357\270\217.srt" new file mode 100644 index 000000000..2964fc8ab --- /dev/null +++ "b/subtitles/zh-CN/37_datasets-+-dataframes-=-\342\235\244\357\270\217.srt" @@ -0,0 +1,320 @@ +1 +00:00:00,227 --> 00:00:01,432 +(嘶嘶声) +(whooshing sound) + +2 +00:00:01,432 --> 00:00:02,420 +(贴纸弹出) +(sticker popping) + +3 +00:00:02,420 --> 00:00:05,340 +(嘶嘶声) +(whooshing sound) + +4 +00:00:05,340 --> 00:00:07,833 +- 数据集和数据帧等于爱。 +- Datasets and DataFrames equals love. + +5 +00:00:08,790 --> 00:00:11,010 +虽然 Datasets 库的处理函数 +Although the processing functions of the Datasets library + +6 +00:00:11,010 --> 00:00:14,040 +将涵盖训练模型所需的大部分情况, +will cover most of the cases needed to train a model, + +7 +00:00:14,040 --> 00:00:15,660 +有时你需要切换到图书馆 +there are times when you'll need to switch to a library + +8 +00:00:15,660 --> 00:00:18,240 +像 Pandas 一样访问更强大的功能 +like Pandas to access more powerful features + +9 +00:00:18,240 --> 00:00:20,970 +或用于可视化的高级 API。 +or high level APIs for visualization. + +10 +00:00:20,970 --> 00:00:23,220 +幸运的是,Datasets 库是专为 +Fortunately, the Datasets library is designed + +11 +00:00:23,220 --> 00:00:25,710 +与 Pandas 等库互操作, +to be interoperable with libraries like Pandas, + +12 +00:00:25,710 --> 00:00:29,790 +以及 NumPy、PyTorch、TensorFlow 和 JAX。 +as well as NumPy, PyTorch, TensorFlow and JAX. + +13 +00:00:29,790 --> 00:00:30,930 +在本视频中,我们将了解 +In this video, we'll take a look + +14 +00:00:30,930 --> 00:00:32,550 +我们如何快速切换数据 +at how we can quickly switch our data + +15 +00:00:32,550 --> 00:00:34,263 +到 Pandas DataFrames 并返回。 +to Pandas DataFrames and back. + +16 +00:00:36,120 --> 00:00:38,310 +例如,假设我们正在分析 +As an example, let's suppose we're analyzing + +17 +00:00:38,310 --> 00:00:40,830 +来自瑞士的最高法院案件。 +Supreme Court cases from Switzerland. + +18 +00:00:40,830 --> 00:00:43,020 +像往常一样,我们从集线器下载我们的数据集 +As usual, we download our dataset from the hub + +19 +00:00:43,020 --> 00:00:44,940 +使用 load_dataset 函数。 +using the load_dataset function. + +20 +00:00:44,940 --> 00:00:46,980 +你可以看到训练集的第一个元素 +And you can see that the first element of the training set + +21 +00:00:46,980 --> 00:00:48,510 +是一个普通的 Python 字典 +is an ordinary Python dictionary + +22 +00:00:48,510 --> 00:00:50,110 +与各种兴趣领域。 +with various fields of interest. + +23 +00:00:51,690 --> 00:00:53,670 +现在,假设在我们训练任何模型之前, +Now, suppose that before we train any models, + +24 +00:00:53,670 --> 00:00:55,590 +我们想稍微探索一下数据。 +we'd like to explore the data a bit. + +25 +00:00:55,590 --> 00:00:57,390 +例如,我们可能有兴趣知道 +For example, we might be interested in knowing + +26 +00:00:57,390 --> 00:00:59,820 +哪些法律领域最常见 +which legal areas are the most common + +27 +00:00:59,820 --> 00:01:01,380 +或者我们可能想知道语言 +or we might wanna know how the languages + +28 +00:01:01,380 --> 00:01:02,930 +分布在各个地区。 +are distributed across regions. + +29 +00:01:04,500 --> 00:01:05,333 +回答这些问题 +Answering these questions + +30 +00:01:05,333 --> 00:01:07,530 +使用原生 Arrow 格式并不容易, +with the native Arrow format isn't easy, + +31 +00:01:07,530 --> 00:01:10,500 +但我们可以快速切换到 Pandas 来获得答案。 +but we can quickly switch to Pandas to get our answers. + +32 +00:01:10,500 --> 00:01:13,500 +它的工作方式是通过使用 set_format 方法, +The way this works is that by using the set_format method, + +33 +00:01:13,500 --> 00:01:15,480 +我们将更改数据集的输出格式 +we will change the output format of the dataset + +34 +00:01:15,480 --> 00:01:18,930 +从 Python 字典到 Pandas DataFrame。 +from Python dictionaries to Pandas DataFrames. + +35 +00:01:18,930 --> 00:01:20,130 +正如你在此示例中所见, +As you can see in this example, + +36 +00:01:20,130 --> 00:01:22,890 +数据集中的每一行都表示为一个 DataFrame, +each row in the dataset is represented as a DataFrame, + +37 +00:01:22,890 --> 00:01:24,540 +所以我们可以对整个数据集进行切片 +so we can slice the whole dataset + +38 +00:01:24,540 --> 00:01:26,583 +获取语料库的单个 DataFrame。 +to get a single DataFrame of the corpus. + +39 +00:01:28,080 --> 00:01:29,520 +这在引擎盖下的工作方式, +The way this works under the hood, + +40 +00:01:29,520 --> 00:01:31,080 +是数据集库发生了变化 +is that the datasets library changes + +41 +00:01:31,080 --> 00:01:33,900 +数据集的神奇 __getitem__ 方法。 +the magic __getitem__ method of the dataset. + +42 +00:01:33,900 --> 00:01:35,640 +__getitem__ 方法是一种特殊的方法 +The __getitem__ method is a special method + +43 +00:01:35,640 --> 00:01:37,320 +对于允许你的 Python 容器 +for Python containers that allows you + +44 +00:01:37,320 --> 00:01:39,870 +指定索引的工作方式。 +to specify how indexing works. + +45 +00:01:39,870 --> 00:01:42,540 +在这种情况下,原始数据集的 __getitem__ 方法 +In this case, the __getitem__ method of the raw dataset + +46 +00:01:42,540 --> 00:01:45,150 +首先返回一个 Python 字典 +starts off by returning a Python dictionary + +47 +00:01:45,150 --> 00:01:47,520 +然后在应用 set_format 之后, +and then after applying set_format, + +48 +00:01:47,520 --> 00:01:50,283 +我们更改 __getitem__ 以返回 DataFrames。 +we change __getitem__ to return DataFrames instead. + +49 +00:01:52,080 --> 00:01:54,690 +Datasets 库还提供了一个 to_pandas 方法 +The Datasets library also provides a to_pandas method + +50 +00:01:54,690 --> 00:01:56,250 +如果你想做格式转换 +if you wanna do the format conversion + +51 +00:01:56,250 --> 00:01:58,113 +并一次性对数据集进行切片。 +and slicing of the dataset in one go. + +52 +00:02:00,090 --> 00:02:01,590 +一旦你有了一个 DataFrame, +And once you have a DataFrame, + +53 +00:02:01,590 --> 00:02:03,990 +你可以找到各种复杂问题的答案 +you can find the answers to all sorts of complex questions + +54 +00:02:03,990 --> 00:02:06,740 +或使用你最喜欢的可视化库绘制图表。 +or make plots with your favorite visualization library. + +55 +00:02:07,890 --> 00:02:08,850 +唯一要记住的 +The only thing to remember + +56 +00:02:08,850 --> 00:02:10,830 +是一旦你完成了 Pandas 分析, +is that once you're done with your Pandas analysis, + +57 +00:02:10,830 --> 00:02:14,460 +你应该将输出格式重置回箭头表。 +you should reset the output format back to Arrow tables. + +58 +00:02:14,460 --> 00:02:16,350 +如果你不这样做,你可能会遇到问题 +If you don't, you can run into problems + +59 +00:02:16,350 --> 00:02:17,910 +如果你尝试标记化你的文本 +if you try to tokenize your text + +60 +00:02:17,910 --> 00:02:19,260 +因为它不再代表 +because it is no longer represented + +61 +00:02:19,260 --> 00:02:20,610 +作为字典中的字符串。 +as strings in a dictionary. + +62 +00:02:21,750 --> 00:02:24,780 +通过重置输出格式,我们得到箭头表 +By resetting the output format we get back Arrow tables + +63 +00:02:24,780 --> 00:02:26,580 +我们可以毫无问题地标记化。 +and we can tokenize without problem. + +64 +00:02:27,513 --> 00:02:30,346 +(嘶嘶声) +(whooshing sound) + diff --git a/subtitles/zh-CN/38_saving-and-reloading-a-dataset.srt b/subtitles/zh-CN/38_saving-and-reloading-a-dataset.srt new file mode 100644 index 000000000..4039d68b0 --- /dev/null +++ b/subtitles/zh-CN/38_saving-and-reloading-a-dataset.srt @@ -0,0 +1,400 @@ +1 +00:00:00,000 --> 00:00:02,917 +(过渡音乐) +(transition music) + +2 +00:00:06,600 --> 00:00:08,283 +- 保存和重新加载数据集。 +- Saving and reloading a dataset. + +3 +00:00:09,210 --> 00:00:10,320 +在本视频中,我们将了解 +In this video, we'll take a look + +4 +00:00:10,320 --> 00:00:12,360 +以各种格式保存数据集 +at saving a dataset in various formats + +5 +00:00:12,360 --> 00:00:14,660 +并探索重新加载已保存数据的方法。 +and explore the ways to reload the saved data. + +6 +00:00:17,310 --> 00:00:20,100 +下载数据集时,处理脚本和数据 +When you download a dataset, the processing scripts and data + +7 +00:00:20,100 --> 00:00:22,470 +本地存储在你的计算机上。 +are stored locally on your computer. + +8 +00:00:22,470 --> 00:00:24,000 +缓存允许数据集库 +The cache allows the Datasets library + +9 +00:00:24,000 --> 00:00:25,230 +避免重新下载 +to avoid re-downloading + +10 +00:00:25,230 --> 00:00:28,620 +或每次使用时处理整个数据集。 +or processing the entire dataset every time you use it. + +11 +00:00:28,620 --> 00:00:31,170 +现在,数据以箭头表的形式存储 +Now, the data is stored in the form of Arrow tables + +12 +00:00:31,170 --> 00:00:32,490 +可以找到谁的位置 +whose location can be found + +13 +00:00:32,490 --> 00:00:35,730 +通过访问数据集的 cache_files 属性。 +by accessing the dataset's cache_files attribute. + +14 +00:00:35,730 --> 00:00:38,430 +在这个例子中,我们下载了 allocine 数据集 +In this example, we've downloaded the allocine dataset + +15 +00:00:38,430 --> 00:00:40,080 +来自 Hugging Face Hub,你可以看到 +from the Hugging Face Hub, and you can see + +16 +00:00:40,080 --> 00:00:41,430 +一共有三个 Arrow 文件 +that there are three Arrow files + +17 +00:00:41,430 --> 00:00:43,473 +存储在缓存中,每个拆分一个。 +stored in the cache, one for each split. + +18 +00:00:45,360 --> 00:00:47,460 +但在很多情况下,你会想保存你的数据集 +But in many cases, you'll wanna save your dataset + +19 +00:00:47,460 --> 00:00:49,890 +在不同的位置或格式。 +in a different location or format. + +20 +00:00:49,890 --> 00:00:51,900 +如表所示,Datasets 库 +As shown in the table, the Datasets library + +21 +00:00:51,900 --> 00:00:54,870 +提供了四个主要功能来实现这一点。 +provides four main functions to achieve this. + +22 +00:00:54,870 --> 00:00:56,130 +现在,你可能已经很熟悉了 +Now, you're probably already familiar + +23 +00:00:56,130 --> 00:00:58,770 +使用 CSV 和 JSON 格式,这两种格式都很棒 +with the CSV and JSON formats, both of which are great + +24 +00:00:58,770 --> 00:01:00,810 +如果你只是想快速节省一小笔钱 +if you just wanna quickly save a small + +25 +00:01:00,810 --> 00:01:02,790 +或中型数据集。 +or medium-sized dataset. + +26 +00:01:02,790 --> 00:01:03,976 +但是如果你的数据集很大, +But if your dataset is huge, + +27 +00:01:03,976 --> 00:01:07,860 +你需要将其保存为 Arrow 或 Parquet 格式。 +you'll wanna save it in either the Arrow or Parquet formats. + +28 +00:01:07,860 --> 00:01:09,660 +如果你打算重新加载,箭头文件很棒 +Arrow files are great if you plan to reload + +29 +00:01:09,660 --> 00:01:11,850 +或在不久的将来处理数据。 +or process the data in the near future. + +30 +00:01:11,850 --> 00:01:13,290 +虽然 Parquet 文件被设计成 +While Parquet files are designed + +31 +00:01:13,290 --> 00:01:16,140 +用于长期存储并且非常节省空间。 +for long-term storage and are very space-efficient. + +32 +00:01:16,140 --> 00:01:18,140 +让我们仔细看看每种格式。 +Let's take a closer look at each format. + +33 +00:01:19,800 --> 00:01:21,750 +保存数据集或 dataset_dict 对象 +To save a dataset or a dataset_dict object + +34 +00:01:21,750 --> 00:01:25,560 +在 Arrow 格式中,我们使用 save_to_disk 函数。 +in the Arrow format, we use the save_to_disk function. + +35 +00:01:25,560 --> 00:01:26,910 +正如你在此示例中所见, +As you can see in this example, + +36 +00:01:26,910 --> 00:01:29,790 +我们只需提供我们希望将数据保存到的路径 +we simply provide the path we wish to save the data to + +37 +00:01:29,790 --> 00:01:30,720 +和数据集库 +and the Datasets library + +38 +00:01:30,720 --> 00:01:32,340 +会自动创建一个目录 +will automatically create a directory + +39 +00:01:32,340 --> 00:01:35,790 +对于每个拆分来存储箭头表和元数据。 +for each split to store the Arrow table and the metadata. + +40 +00:01:35,790 --> 00:01:37,680 +因为我们正在处理一个 dataset_dict 对象 +Since we're dealing with a dataset_dict object + +41 +00:01:37,680 --> 00:01:39,090 +有多个拆分, +that has multiple splits, + +42 +00:01:39,090 --> 00:01:40,590 +此信息也被存储 +this information is also stored + +43 +00:01:40,590 --> 00:01:42,243 +在 dataset_dict.json 文件中。 +in the dataset_dict.json file. + +44 +00:01:44,250 --> 00:01:46,710 +现在,当我们想要重新加载 Arrow 数据集时, +Now, when we wanna reload the Arrow datasets, + +45 +00:01:46,710 --> 00:01:48,870 +我们使用 load_from_disk 函数。 +we use the load_from_disk function. + +46 +00:01:48,870 --> 00:01:51,210 +我们只需传递数据集目录的路径, +We simply pass the path of our dataset directory, + +47 +00:01:51,210 --> 00:01:53,583 +瞧,原始数据集已恢复。 +and voila, the original dataset is recovered. + +48 +00:01:55,594 --> 00:01:57,180 +如果我们想保存我们的数据集 +If we wanna save our dataset + +49 +00:01:57,180 --> 00:02:00,990 +在 CSV 格式中,我们使用 to_csv 函数。 +in the CSV format, we use the to_csv function. + +50 +00:02:00,990 --> 00:02:02,280 +在这种情况下,你需要循环 +In this case, you'll need to loop + +51 +00:02:02,280 --> 00:02:04,170 +在 dataset_dict 对象的拆分上 +over the splits of the dataset_dict object + +52 +00:02:04,170 --> 00:02:07,710 +并将每个数据集保存为单独的 CSV 文件。 +and save each dataset as an individual CSV file. + +53 +00:02:07,710 --> 00:02:10,950 +由于 to_csv 函数基于 Pandas 中的函数, +Since the to_csv function is based on the one from Pandas, + +54 +00:02:10,950 --> 00:02:13,980 +你可以传递关键字参数来配置输出。 +you can pass keyword arguments to configure the output. + +55 +00:02:13,980 --> 00:02:16,230 +在这个例子中,我们设置了索引参数 +In this example, we've set the index argument + +56 +00:02:16,230 --> 00:02:18,480 +为 None 以防止数据集的索引列 +to None to prevent the dataset's index column + +57 +00:02:18,480 --> 00:02:20,553 +不包含在 CSV 文件中。 +from being included in the CSV files. + +58 +00:02:22,470 --> 00:02:24,240 +要重新加载我们的 CSV 文件, +To reload our CSV files, + +59 +00:02:24,240 --> 00:02:27,180 +然后我们就使用熟悉的 load_dataset 函数 +we just then use the familiar load_dataset function + +60 +00:02:27,180 --> 00:02:29,160 +连同 CSV 加载脚本 +together with the CSV loading script + +61 +00:02:29,160 --> 00:02:30,360 +和 data_files 参数, +and the data_files argument, + +62 +00:02:30,360 --> 00:02:34,020 +它指定与每个拆分关联的文件名。 +which specifies the file names associated with each split. + +63 +00:02:34,020 --> 00:02:35,400 +正如你在此示例中所见, +As you can see in this example, + +64 +00:02:35,400 --> 00:02:37,320 +通过提供所有拆分及其文件名, +by providing all the splits and their file names, + +65 +00:02:37,320 --> 00:02:39,770 +我们已经恢复了原始的 dataset_dict 对象。 +we've recovered the original dataset_dict object. + +66 +00:02:41,880 --> 00:02:43,560 +现在,将数据集保存在 JSON 中 +Now, to save a dataset in the JSON + +67 +00:02:43,560 --> 00:02:46,710 +或 Parquet 格式与 CSV 的情况非常相似。 +or Parquet formats is very similar to the CSV case. + +68 +00:02:46,710 --> 00:02:49,890 +我们对 JSON 文件使用 to_json 函数 +We use either the to_json function for JSON files + +69 +00:02:49,890 --> 00:02:52,740 +或 Parquet 的 to_parquet 函数。 +or the to_parquet function for Parquet ones. + +70 +00:02:52,740 --> 00:02:55,740 +就像 CSV 案例一样,我们需要遍历拆分 +And just like the CSV case, we need to loop over the splits + +71 +00:02:55,740 --> 00:02:57,753 +将每个保存为单独的文件。 +to save each one as an individual file. + +72 +00:02:59,580 --> 00:03:02,940 +一旦我们的数据集被保存为 JSON 或 Parquet 文件, +And once our datasets are saved as JSON or Parquet files, + +73 +00:03:02,940 --> 00:03:03,990 +我们可以重新加载它们 +we can reload them again + +74 +00:03:03,990 --> 00:03:06,960 +使用 load_dataset 函数中的适当脚本。 +with the appropriate script in the load_dataset function. + +75 +00:03:06,960 --> 00:03:09,993 +我们只需要像以前一样提供一个 data_files 参数。 +And we just need to provide a data_files argument as before. + +76 +00:03:10,860 --> 00:03:11,910 +这个例子表明 +This example shows + +77 +00:03:11,910 --> 00:03:14,560 +我们如何以任何一种格式重新加载我们的保存数据集。 +how we can reload our save datasets in either format. + +78 +00:03:16,620 --> 00:03:17,970 +有了这个,你现在知道 +And with that, you now know + +79 +00:03:17,970 --> 00:03:20,220 +如何以各种格式保存数据集。 +how to save your datasets in various formats. + +80 +00:03:21,441 --> 00:03:24,358 +(过渡音乐) +(transition music) + diff --git a/subtitles/zh-CN/39_memory-mapping-&-streaming.srt b/subtitles/zh-CN/39_memory-mapping-&-streaming.srt new file mode 100644 index 000000000..e39a21f86 --- /dev/null +++ b/subtitles/zh-CN/39_memory-mapping-&-streaming.srt @@ -0,0 +1,415 @@ +1 +00:00:00,511 --> 00:00:01,784 +(空气呼啸) +(air whooshing) + +2 +00:00:01,784 --> 00:00:02,964 +(徽标弹出) +(logo popping) + +3 +00:00:02,964 --> 00:00:05,640 +(金属滑动) +(metal sliding) + +4 +00:00:05,640 --> 00:00:07,203 +- 内存映射和流。 +- Memory mapping and streaming. + +5 +00:00:08,040 --> 00:00:09,180 +在本视频中,我们将了解 +In this video, we'll take a look + +6 +00:00:09,180 --> 00:00:11,520 +Datasets 库的两个核心特性 +at two core features of the Datasets library + +7 +00:00:11,520 --> 00:00:14,220 +允许你加载和处理庞大的数据集 +that allow you to load and process huge datasets + +8 +00:00:14,220 --> 00:00:16,263 +而不会炸毁笔记本电脑的 CPU。 +without blowing up your laptop's CPU. + +9 +00:00:18,300 --> 00:00:20,280 +如今,发现自己并不罕见 +Nowadays, it's not uncommon to find yourself + +10 +00:00:20,280 --> 00:00:22,950 +使用多 GB 大小的数据集, +working with multi-GB sized datasets, + +11 +00:00:22,950 --> 00:00:24,420 +特别是如果你打算预训练 +especially if you're planning to pretrain + +12 +00:00:24,420 --> 00:00:28,110 +从头开始像 BERT 或 GPT-2 这样的转换器。 +a transformer like BERT or GPT-2 from scratch. + +13 +00:00:28,110 --> 00:00:31,260 +在这些情况下,即使加载数据也可能是一个挑战。 +In these cases, even loading the data can be a challenge. + +14 +00:00:31,260 --> 00:00:34,680 +例如,用于预训练 T5 的 c4 语料库 +For example, the c4 corpus used to pretrain T5 + +15 +00:00:34,680 --> 00:00:36,903 +包含超过 2 TB 的数据。 +consists of over two terabytes of data. + +16 +00:00:38,400 --> 00:00:40,050 +为了处理这些大型数据集, +To handle these large datasets, + +17 +00:00:40,050 --> 00:00:42,990 +Datasets 库建立在两个核心特性之上: +the Datasets library is built on two core features: + +18 +00:00:42,990 --> 00:00:46,350 +Apache Arrow 格式和流式 API。 +the Apache Arrow format and a streaming API. + +19 +00:00:46,350 --> 00:00:49,110 +Arrow 专为高性能数据处理而设计 +Arrow is designed for high-performance data processing + +20 +00:00:49,110 --> 00:00:51,360 +并代表每个类似表格的数据集 +and represents each table-like dataset + +21 +00:00:51,360 --> 00:00:52,773 +使用基于列的格式。 +with a column-based format. + +22 +00:00:53,730 --> 00:00:56,130 +正如你在此示例中所见,基于列的格式 +As you can see in this example, column-based formats + +23 +00:00:56,130 --> 00:00:59,280 +将表格的元素分组到连续的 RAM 块中 +group the elements of a table in consecutive blocks of RAM + +24 +00:00:59,280 --> 00:01:01,563 +这解锁了快速访问和处理。 +and this unlocks fast access and processing. + +25 +00:01:02,760 --> 00:01:05,550 +Arrow 擅长处理任何规模的数据 +Arrow is great at processing data at any scale + +26 +00:01:05,550 --> 00:01:07,110 +但有些数据集很大 +but some datasets are so large + +27 +00:01:07,110 --> 00:01:09,600 +你甚至不能把它们放在你的硬盘上。 +that you can't even fit them on your hard disk. + +28 +00:01:09,600 --> 00:01:11,730 +所以对于这些情况,Datasets 库提供了 +So for these cases, the Datasets library provides + +29 +00:01:11,730 --> 00:01:14,820 +允许你逐步下载的流式 API +a streaming API that allows you to progressively download + +30 +00:01:14,820 --> 00:01:17,700 +原始数据一次一个元素。 +the raw data one element at a time. + +31 +00:01:17,700 --> 00:01:20,430 +结果是一个称为 IterableDataset 的特殊对象 +The result is a special object called an IterableDataset + +32 +00:01:20,430 --> 00:01:22,180 +我们很快就会看到更多细节。 +that we'll see in more detail soon. + +33 +00:01:23,700 --> 00:01:26,670 +让我们先来看看为什么 Arrow 如此强大。 +Let's start by looking at why Arrow is so powerful. + +34 +00:01:26,670 --> 00:01:28,860 +第一个特点是它处理每个数据集 +The first feature is that it treats every dataset + +35 +00:01:28,860 --> 00:01:30,153 +作为内存映射文件。 +as a memory-mapped file. + +36 +00:01:31,020 --> 00:01:32,430 +现在,内存映射是一种机制 +Now, memory mapping is a mechanism + +37 +00:01:32,430 --> 00:01:35,400 +映射文件的一部分或整个文件和光盘 +that maps a portion of a file or an entire file and disc + +38 +00:01:35,400 --> 00:01:37,410 +到一大块虚拟内存。 +to a chunk of virtual memory. + +39 +00:01:37,410 --> 00:01:38,520 +这允许应用程序 +This allows applications + +40 +00:01:38,520 --> 00:01:41,280 +访问一个非常大的文件的片段 +to access segments of an extremely large file + +41 +00:01:41,280 --> 00:01:44,080 +无需先将整个文件读入内存。 +without having to read the whole file into memory first. + +42 +00:01:45,150 --> 00:01:48,120 +Arrow 内存映射功能的另一个很酷的特性 +Another cool feature of Arrow's memory mapping capabilities + +43 +00:01:48,120 --> 00:01:49,860 +是它允许多个进程 +is that it allows multiple processes + +44 +00:01:49,860 --> 00:01:51,840 +使用相同的大型数据集 +to work with the same large dataset + +45 +00:01:51,840 --> 00:01:54,333 +不以任何方式移动或复制它。 +without moving it or copying it in any way. + +46 +00:01:55,680 --> 00:01:57,570 +Arrow 的这种零拷贝功能 +This zero-copy feature of Arrow + +47 +00:01:57,570 --> 00:02:00,600 +使得迭代数据集的速度非常快。 +makes it extremely fast for iterating over a dataset. + +48 +00:02:00,600 --> 00:02:02,640 +这个例子,你可以看到我们迭代 +And this example, you can see that we iterate + +49 +00:02:02,640 --> 00:02:05,160 +大约一分钟内超过 1500 万行 +over 15 million rows in about a minute + +50 +00:02:05,160 --> 00:02:06,780 +仅使用标准笔记本电脑。 +just using a standard laptop. + +51 +00:02:06,780 --> 00:02:08,080 +这还不算太糟糕。 +That's not too bad at all. + +52 +00:02:09,750 --> 00:02:12,660 +现在让我们看一下如何流式传输大型数据集。 +Let's now take a look at how we can stream a large dataset. + +53 +00:02:12,660 --> 00:02:14,520 +你需要做的唯一更改是设置 +The only change you need to make is to set + +54 +00:02:14,520 --> 00:02:17,910 +load_dataset () 函数中的 streaming=True 参数。 +the streaming=True argument in the load_dataset () function. + +55 +00:02:17,910 --> 00:02:20,580 +这将返回一个特殊的 IterableDataset 对象 +This will return a special IterableDataset object + +56 +00:02:20,580 --> 00:02:22,260 +这与 Dataset 对象有点不同 +which is a bit different to the Dataset objects + +57 +00:02:22,260 --> 00:02:24,330 +我们在其他视频中看到过。 +we've seen in other videos. + +58 +00:02:24,330 --> 00:02:25,980 +这个对象是一个可迭代的, +This object is an iterable, + +59 +00:02:25,980 --> 00:02:28,530 +这意味着我们不能索引它来访问元素, +which means we can't index it to access elements, + +60 +00:02:28,530 --> 00:02:30,180 +但相反我们迭代它 +but instead we iterate on it + +61 +00:02:30,180 --> 00:02:32,850 +使用 iter 和 next 方法。 +using the iter and next methods. + +62 +00:02:32,850 --> 00:02:34,050 +这将下载并访问 +This will download and access + +63 +00:02:34,050 --> 00:02:35,850 +来自数据集的单个示例, +a single example from the dataset, + +64 +00:02:35,850 --> 00:02:37,410 +这意味着你可以逐步迭代 +which means you can progressively iterate + +65 +00:02:37,410 --> 00:02:40,360 +通过庞大的数据集,而无需先下载它。 +through a huge dataset without having to download it first. + +66 +00:02:42,150 --> 00:02:43,590 +使用 map () 方法标记文本 +Tokenizing text with a map () method + +67 +00:02:43,590 --> 00:02:45,660 +也以类似的方式工作。 +also works in a similar way. + +68 +00:02:45,660 --> 00:02:47,160 +我们首先流式传输数据集 +We first stream the dataset + +69 +00:02:47,160 --> 00:02:49,830 +然后将 map () 方法与分词器一起应用。 +and then apply the map () method with the tokenizer. + +70 +00:02:49,830 --> 00:02:53,283 +要获得第一个标记化示例,我们应用 iter 和 next。 +To get the first tokenized example, we apply iter and next. + +71 +00:02:54,750 --> 00:02:57,210 +与 IterableDataset 的主要区别在于 +The main difference with an IterableDataset is that + +72 +00:02:57,210 --> 00:02:59,970 +而不是使用 select () 方法返回示例, +instead of using a select () method to return examples, + +73 +00:02:59,970 --> 00:03:01,530 +我们使用 take () 和 skip () 方法 +we use the take () and skip () methods + +74 +00:03:01,530 --> 00:03:03,573 +因为我们无法索引数据集。 +because we can't index into the dataset. + +75 +00:03:04,470 --> 00:03:05,460 +take () 方法返回 +The take () method returns + +76 +00:03:05,460 --> 00:03:07,500 +数据集中的前 N 个示例, +the first N examples in the dataset, + +77 +00:03:07,500 --> 00:03:09,270 +而 skip (),如你所想, +while skip (), as you can imagine, + +78 +00:03:09,270 --> 00:03:12,480 +跳过第一个 N 并返回其余的。 +skips the first N and returns the rest. + +79 +00:03:12,480 --> 00:03:15,300 +你可以看到这两种方法的实际示例 +You can see examples of both of these methods in action + +80 +00:03:15,300 --> 00:03:16,710 +我们在哪里创建验证集 +where we create a validation set + +81 +00:03:16,710 --> 00:03:18,660 +来自前 1000 个示例 +from the first 1000 examples + +82 +00:03:18,660 --> 00:03:21,010 +然后跳过那些来创建训练集。 +and then skip those to create the training set. + +83 +00:03:23,012 --> 00:03:25,762 +(空气呼啸) +(air whooshing) + diff --git a/subtitles/zh-CN/40_uploading-a-dataset-to-the-hub.srt b/subtitles/zh-CN/40_uploading-a-dataset-to-the-hub.srt new file mode 100644 index 000000000..860785521 --- /dev/null +++ b/subtitles/zh-CN/40_uploading-a-dataset-to-the-hub.srt @@ -0,0 +1,260 @@ +1 +00:00:00,000 --> 00:00:02,917 +(过渡音乐) +(transition music) + +2 +00:00:05,490 --> 00:00:07,950 +- 将数据集上传到中心。 +- Uploading a dataset to the hub. + +3 +00:00:07,950 --> 00:00:09,060 +在本视频中,我们将了解 +In this video, we'll take a look + +4 +00:00:09,060 --> 00:00:10,860 +如何上传你自己的数据集 +at how you can upload your very own dataset + +5 +00:00:10,860 --> 00:00:12,060 +到 Hugging Face Hub。 +to the Hugging Face Hub. + +6 +00:00:13,680 --> 00:00:14,670 +你需要做的第一件事 +The first thing you need to do + +7 +00:00:14,670 --> 00:00:17,400 +是在集线器上创建一个新的数据集存储库。 +is create a new dataset repository on the hub. + +8 +00:00:17,400 --> 00:00:19,260 +所以,只需点击你的个人资料图标 +So, just click on your profile icon + +9 +00:00:19,260 --> 00:00:21,750 +并选择新建数据集按钮。 +and select the New Dataset button. + +10 +00:00:21,750 --> 00:00:24,750 +接下来,我们需要指定数据集的所有者。 +Next, we need to assign an owner of the dataset. + +11 +00:00:24,750 --> 00:00:26,970 +默认情况下,这将是你的中心帐户, +By default, this will be your hub account, + +12 +00:00:26,970 --> 00:00:28,170 +但你也可以创建数据集 +but you can also create datasets + +13 +00:00:28,170 --> 00:00:30,585 +在你所属的任何组织下。 +under any organization that you belong to. + +14 +00:00:30,585 --> 00:00:33,780 +然后,我们只需要给数据集起个好名字 +Then, we just need to give the dataset a good name + +15 +00:00:33,780 --> 00:00:36,513 +并指定它是公共数据集还是私有数据集。 +and specify whether it is a public or private dataset. + +16 +00:00:37,410 --> 00:00:39,810 +任何人都可以访问公共数据集 +Public datasets can be accessed by anyone + +17 +00:00:39,810 --> 00:00:41,670 +而私人数据集只能被访问 +while private datasets can only be accessed + +18 +00:00:41,670 --> 00:00:43,653 +由你或你的组织成员。 +by you or members of your organization. + +19 +00:00:44,580 --> 00:00:47,280 +这样,我们就可以继续创建数据集了。 +And with that, we can go ahead and create the dataset. + +20 +00:00:48,690 --> 00:00:51,060 +现在你在集线器上有一个空的数据集存储库, +Now that you have an empty dataset repository on the hub, + +21 +00:00:51,060 --> 00:00:53,880 +接下来要做的是向其中添加一些实际数据。 +the next thing to do is add some actual data to it. + +22 +00:00:53,880 --> 00:00:55,050 +你可以用 git 做到这一点, +You can do this with git, + +23 +00:00:55,050 --> 00:00:57,960 +但最简单的方法是选择上传文件按钮。 +but the easiest way is by selecting the Upload file button. + +24 +00:00:57,960 --> 00:00:59,160 +然后,你可以继续 +And then, you can just go ahead + +25 +00:00:59,160 --> 00:01:02,243 +并直接从你的机器上传文件。 +and upload the files directly from your machine. + +26 +00:01:02,243 --> 00:01:03,846 +上传文件后, +After you've uploaded your files, + +27 +00:01:03,846 --> 00:01:05,670 +你会看到它们出现在存储库中 +you'll see them appear in the repository + +28 +00:01:05,670 --> 00:01:07,320 +在文件和版本选项卡下。 +under the Files and versions tab. + +29 +00:01:08,550 --> 00:01:11,370 +最后一步是创建数据集卡。 +The last step is to create a dataset card. + +30 +00:01:11,370 --> 00:01:13,590 +记录良好的数据集更有用 +Well-documented datasets are more likely to be useful + +31 +00:01:13,590 --> 00:01:15,600 +给其他人,因为他们提供了决定的背景 +to others as they provide the context to decide + +32 +00:01:15,600 --> 00:01:17,370 +数据集是否相关 +whether the dataset is relevant + +33 +00:01:17,370 --> 00:01:18,450 +或者有没有偏见 +or whether there are any biases + +34 +00:01:18,450 --> 00:01:20,673 +或与使用数据集相关的风险。 +or risks associated with using the dataset. + +35 +00:01:21,540 --> 00:01:22,710 +在 Hugging Face Hub 上, +On the Hugging Face Hub, + +36 +00:01:22,710 --> 00:01:25,650 +此信息存储在每个存储库的自述文件中。 +this information is stored in each repositories README file. + +37 +00:01:25,650 --> 00:01:27,988 +你应该采取两个主要步骤。 +There are two main steps that you should take. + +38 +00:01:27,988 --> 00:01:30,651 +首先,你需要创建一些元数据 +First, you need to create some metadata + +39 +00:01:30,651 --> 00:01:32,010 +这将允许你的数据集 +that will allow your dataset + +40 +00:01:32,010 --> 00:01:34,590 +其他人可以在集线器上轻松找到。 +to be easily found by others on the hub. + +41 +00:01:34,590 --> 00:01:35,670 +你可以创建此元数据 +You can create this metadata + +42 +00:01:35,670 --> 00:01:37,860 +使用数据集标记应用程序, +using the datasets tagging application, + +43 +00:01:37,860 --> 00:01:40,620 +我们将在视频说明中链接到它。 +which we'll link to in the video description. + +44 +00:01:40,620 --> 00:01:42,240 +创建元数据后, +Once you've created the metadata, + +45 +00:01:42,240 --> 00:01:44,190 +你可以填写数据集卡的其余部分, +you can fill out the rest of the dataset card, + +46 +00:01:44,190 --> 00:01:45,240 +我们提供了一个模板 +and we provide a template + +47 +00:01:45,240 --> 00:01:47,090 +我们还将在视频中链接到。 +that we'll also link to in the video. + +48 +00:01:48,480 --> 00:01:50,280 +一旦你的数据集在集线器上, +And once your dataset is on the hub, + +49 +00:01:50,280 --> 00:01:53,400 +你可以使用可信赖的 load_dataset 函数加载它。 +you can load it using the trusty load_dataset function. + +50 +00:01:53,400 --> 00:01:55,015 +只需提供你的存储库的名称 +Just provide the name of your repository + +51 +00:01:55,015 --> 00:01:57,843 +和一个 data_files 参数,你就可以开始了。 +and a data_files argument, and you're good to go. + +52 +00:01:59,619 --> 00:02:02,536 +(过渡音乐) +(transition music) + diff --git a/subtitles/zh-CN/41_text-embeddings-&-semantic-search.srt b/subtitles/zh-CN/41_text-embeddings-&-semantic-search.srt new file mode 100644 index 000000000..0b8958068 --- /dev/null +++ b/subtitles/zh-CN/41_text-embeddings-&-semantic-search.srt @@ -0,0 +1,405 @@ +1 +00:00:00,621 --> 00:00:03,204 +(欢快的音乐) +(upbeat music) + +2 +00:00:05,670 --> 00:00:08,520 +- 文本嵌入和语义搜索。 +- Text embeddings and semantic search. + +3 +00:00:08,520 --> 00:00:10,770 +在本视频中,我们将探索 Transformer 如何建模 +In this video we'll explore how Transformer models + +4 +00:00:10,770 --> 00:00:12,810 +将文本表示为嵌入向量 +represent text as embedding vectors + +5 +00:00:12,810 --> 00:00:15,420 +以及如何使用这些向量来查找相似文档 +and how these vectors can be used to find similar documents + +6 +00:00:15,420 --> 00:00:16,293 +在语料库中。 +in a corpus. + +7 +00:00:17,730 --> 00:00:19,890 +文本嵌入只是一种奇特的说法 +Text embeddings are just a fancy way of saying + +8 +00:00:19,890 --> 00:00:22,170 +我们可以将文本表示为数字数组 +that we can represent text as an array of numbers + +9 +00:00:22,170 --> 00:00:23,640 +称为矢量。 +called a vector. + +10 +00:00:23,640 --> 00:00:25,710 +为了创建这些嵌入,我们通常使用 +To create these embeddings we usually use + +11 +00:00:25,710 --> 00:00:27,393 +基于编码器的模型,如 BERT。 +an encoder-based model like BERT. + +12 +00:00:28,530 --> 00:00:31,290 +在此示例中,你可以看到我们如何提供三个句子 +In this example, you can see how we feed three sentences + +13 +00:00:31,290 --> 00:00:34,830 +到编码器并获得三个向量作为输出。 +to the encoder and get three vectors as the output. + +14 +00:00:34,830 --> 00:00:37,050 +读课文,我们可以看到遛狗 +Reading the text, we can see that walking the dog + +15 +00:00:37,050 --> 00:00:39,450 +好像跟遛猫最像, +seems to be most similar to walking the cat, + +16 +00:00:39,450 --> 00:00:41,350 +但让我们看看我们是否可以对此进行量化。 +but let's see if we can quantify this. + +17 +00:00:42,810 --> 00:00:44,040 +进行比较的技巧 +The trick to do the comparison + +18 +00:00:44,040 --> 00:00:45,630 +是计算相似性度量 +is to compute a similarity metric + +19 +00:00:45,630 --> 00:00:48,210 +在每对嵌入向量之间。 +between each pair of embedding vectors. + +20 +00:00:48,210 --> 00:00:51,120 +这些向量通常存在于一个非常高维的空间中, +These vectors usually live in a very high-dimensional space, + +21 +00:00:51,120 --> 00:00:53,190 +所以相似性度量可以是任何可以衡量的东西 +so a similarity metric can be anything that measures + +22 +00:00:53,190 --> 00:00:55,740 +矢量之间的某种距离。 +some sort of distance between vectors. + +23 +00:00:55,740 --> 00:00:58,560 +一个非常流行的指标是余弦相似度, +One very popular metric is cosine similarity, + +24 +00:00:58,560 --> 00:01:00,390 +它使用两个向量之间的角度 +which uses the angle between two vectors + +25 +00:01:00,390 --> 00:01:02,610 +来衡量他们有多接近。 +to measure how close they are. + +26 +00:01:02,610 --> 00:01:05,250 +在这个例子中,我们的嵌入向量存在于 3D 中 +In this example, our embedding vectors live in 3D + +27 +00:01:05,250 --> 00:01:07,110 +我们可以看到橙色和灰色向量 +and we can see that the orange and Grey vectors + +28 +00:01:07,110 --> 00:01:09,560 +彼此靠近并且具有较小的角度。 +are close to each other and have a smaller angle. + +29 +00:01:11,130 --> 00:01:12,510 +现在我们必须处理一个问题 +Now one problem we have to deal with + +30 +00:01:12,510 --> 00:01:15,180 +是像 BERT 这样的 Transformer 模型实际上会返回 +is that Transformer models like BERT will actually return + +31 +00:01:15,180 --> 00:01:16,983 +每个标记一个嵌入向量。 +one embedding vector per token. + +32 +00:01:17,880 --> 00:01:20,700 +例如在句子中,“我带我的狗去散步,” +For example in the sentence, "I took my dog for a walk," + +33 +00:01:20,700 --> 00:01:23,853 +我们可以期待几个嵌入向量,每个词一个。 +we can expect several embedding vectors, one for each word. + +34 +00:01:25,110 --> 00:01:27,870 +例如,在这里我们可以看到模型的输出 +For example, here we can see the output of our model + +35 +00:01:27,870 --> 00:01:30,540 +每个句子产生了 9 个嵌入向量, +has produced 9 embedding vectors per sentence, + +36 +00:01:30,540 --> 00:01:33,750 +每个向量有 384 个维度。 +and each vector has 384 dimensions. + +37 +00:01:33,750 --> 00:01:36,210 +但我们真正想要的是一个单一的嵌入向量 +But what we really want is a single embedding vector + +38 +00:01:36,210 --> 00:01:37,353 +对于每个句子。 +for each sentence. + +39 +00:01:38,940 --> 00:01:42,060 +为了解决这个问题,我们可以使用一种称为池化的技术。 +To deal with this, we can use a technique called pooling. + +40 +00:01:42,060 --> 00:01:43,050 +最简单的池化方法 +The simplest pooling method + +41 +00:01:43,050 --> 00:01:44,520 +就是把令牌嵌入 +is to just take the token embedding + +42 +00:01:44,520 --> 00:01:46,203 +特殊的 CLS 令牌。 +of the special CLS token. + +43 +00:01:47,100 --> 00:01:49,650 +或者,我们可以对令牌嵌入进行平均 +Alternatively, we can average the token embeddings + +44 +00:01:49,650 --> 00:01:52,500 +这就是所谓的均值池,这就是我们在这里所做的。 +which is called mean pooling and this is what we do here. + +45 +00:01:53,370 --> 00:01:55,800 +使用均值池是我们唯一需要确保的事情 +With mean pooling the only thing we need to make sure + +46 +00:01:55,800 --> 00:01:58,410 +是我们不在平均值中包含填充标记, +is that we don't include the padding tokens in the average, + +47 +00:01:58,410 --> 00:02:01,860 +这就是为什么你可以看到这里使用的注意力掩码。 +which is why you can see the attention mask being used here. + +48 +00:02:01,860 --> 00:02:05,100 +这为每个句子提供了一个 384 维向量 +This gives us a 384 dimensional vector for each sentence + +49 +00:02:05,100 --> 00:02:06,600 +这正是我们想要的。 +which is exactly what we want. + +50 +00:02:07,920 --> 00:02:09,810 +一旦我们有了句子嵌入, +And once we have our sentence embeddings, + +51 +00:02:09,810 --> 00:02:11,730 +我们可以计算余弦相似度 +we can compute the cosine similarity + +52 +00:02:11,730 --> 00:02:13,113 +对于每对向量。 +for each pair of vectors. + +53 +00:02:13,993 --> 00:02:16,350 +在此示例中,我们使用 scikit-learn 中的函数 +In this example we use the function from scikit-learn + +54 +00:02:16,350 --> 00:02:19,140 +你可以看到 “I tok my dog for a walk” 这句话 +and you can see that the sentence "I took my dog for a walk" + +55 +00:02:19,140 --> 00:02:22,140 +确实与 “我带我的猫去散步” 有很强的重叠。 +has indeed a strong overlap with "I took my cat for a walk". + +56 +00:02:22,140 --> 00:02:23,240 +万岁!我们做到了。 +Hooray! We've done it. + +57 +00:02:25,110 --> 00:02:27,180 +我们实际上可以将这个想法更进一步 +We can actually take this idea one step further + +58 +00:02:27,180 --> 00:02:29,220 +通过比较问题之间的相似性 +by comparing the similarity between a question + +59 +00:02:29,220 --> 00:02:31,170 +和文档语料库。 +and a corpus of documents. + +60 +00:02:31,170 --> 00:02:33,810 +例如,假设我们嵌入每个帖子 +For example, suppose we embed every post + +61 +00:02:33,810 --> 00:02:35,430 +在 Hugging Face 论坛中。 +in the Hugging Face forums. + +62 +00:02:35,430 --> 00:02:37,800 +然后我们可以问一个问题,嵌入它, +We can then ask a question, embed it, + +63 +00:02:37,800 --> 00:02:40,590 +并检查哪些论坛帖子最相似。 +and check which forum posts are most similar. + +64 +00:02:40,590 --> 00:02:42,750 +这个过程通常称为语义搜索, +This process is often called semantic search, + +65 +00:02:42,750 --> 00:02:45,423 +因为它允许我们将查询与上下文进行比较。 +because it allows us to compare queries with context. + +66 +00:02:47,040 --> 00:02:48,450 +创建语义搜索引擎 +To create a semantic search engine + +67 +00:02:48,450 --> 00:02:51,030 +在数据集库中其实很简单。 +is actually quite simple in the datasets library. + +68 +00:02:51,030 --> 00:02:53,340 +首先我们需要嵌入所有文档。 +First we need to embed all the documents. + +69 +00:02:53,340 --> 00:02:56,070 +在这个例子中,我们取了一个小样本 +And in this example, we take a small sample + +70 +00:02:56,070 --> 00:02:57,780 +来自 SQUAD 数据集并应用 +from the SQUAD dataset and apply + +71 +00:02:57,780 --> 00:03:00,180 +与以前相同的嵌入逻辑。 +the same embedding logic as before. + +72 +00:03:00,180 --> 00:03:02,280 +这为我们提供了一个名为嵌入的新列, +This gives us a new column called embeddings, + +73 +00:03:02,280 --> 00:03:04,530 +它存储每个段落的嵌入。 +which stores the embeddings of every passage. + +74 +00:03:05,880 --> 00:03:07,260 +一旦我们有了嵌入, +Once we have our embeddings, + +75 +00:03:07,260 --> 00:03:10,200 +我们需要一种方法来为查询找到最近的邻居。 +we need a way to find nearest neighbors for a query. + +76 +00:03:10,200 --> 00:03:13,170 +数据集库提供了一个名为 FAISS 的特殊对象 +The datasets library provides a special object called FAISS + +77 +00:03:13,170 --> 00:03:16,080 +这使你可以快速比较嵌入向量。 +which allows you to quickly compare embedding vectors. + +78 +00:03:16,080 --> 00:03:19,950 +所以我们添加 FAISS 索引,嵌入一个问题,瞧, +So we add the FAISS index, embed a question and voila, + +79 +00:03:19,950 --> 00:03:21,870 +我们现在找到了 3 篇最相似的文章 +we've now found the 3 most similar articles + +80 +00:03:21,870 --> 00:03:23,320 +这可能会存储答案。 +which might store the answer. + +81 +00:03:25,182 --> 00:03:27,849 +(欢快的音乐) +(upbeat music) + diff --git a/subtitles/zh-CN/42_training-a-new-tokenizer.srt b/subtitles/zh-CN/42_training-a-new-tokenizer.srt new file mode 100644 index 000000000..2b0d28498 --- /dev/null +++ b/subtitles/zh-CN/42_training-a-new-tokenizer.srt @@ -0,0 +1,565 @@ +1 +00:00:00,000 --> 00:00:02,667 +(空气呼啸) +(air whooshing) + +2 +00:00:05,310 --> 00:00:08,700 +- 在这段视频中,我们将一起看到 +- In this video we will see together + +3 +00:00:08,700 --> 00:00:11,820 +训练分词器的目的是什么, +what is the purpose of training a tokenizer, + +4 +00:00:11,820 --> 00:00:14,400 +要遵循的关键步骤是什么, +what are the key steps to follow, + +5 +00:00:14,400 --> 00:00:16,953 +什么是最简单的方法。 +and what is the easiest way to do it. + +6 +00:00:18,690 --> 00:00:20,677 +你会问自己这个问题, +You will ask yourself the question, + +7 +00:00:20,677 --> 00:00:23,040 +“我应该训练一个新的分词器吗?”, +"Should I train a new tokenizer?", + +8 +00:00:23,040 --> 00:00:25,773 +当你计划从头开始训练新模型时。 +when you plan to train a new model from scratch. + +9 +00:00:29,520 --> 00:00:34,020 +训练有素的分词器不适合你的语料库 +A trained tokenizer would not be suitable for your corpus + +10 +00:00:34,020 --> 00:00:37,080 +如果你的语料库使用不同的语言, +if your corpus is in a different language, + +11 +00:00:37,080 --> 00:00:42,060 +使用新字符,例如重音符号或大写字母, +uses new characters, such as accents or upper cased letters, + +12 +00:00:42,060 --> 00:00:47,060 +有特定的词汇,例如医学或法律, +has a specific vocabulary, for example medical or legal, + +13 +00:00:47,100 --> 00:00:49,050 +或使用不同的风格, +or uses a different style, + +14 +00:00:49,050 --> 00:00:51,873 +例如,来自另一个世纪的语言。 +a language from another century for example. + +15 +00:00:56,490 --> 00:00:58,320 +如果我接受训练的分词器 +If I take the tokenizer trained on + +16 +00:00:58,320 --> 00:01:00,780 +bert-base-uncased 模型, +the bert-base-uncased model, + +17 +00:01:00,780 --> 00:01:03,213 +并忽略其归一化步骤, +and ignore its normalization step, + +18 +00:01:04,260 --> 00:01:07,650 +然后我们可以看到标记化操作 +then we can see that the tokenization operation + +19 +00:01:07,650 --> 00:01:09,277 +关于英语句子, +on the English sentence, + +20 +00:01:09,277 --> 00:01:12,480 +“这是一个适合我们分词器的句子”, +"Here is a sentence adapted to our tokenizer", + +21 +00:01:12,480 --> 00:01:15,600 +产生一个相当令人满意的令牌列表, +produces a rather satisfactory list of tokens, + +22 +00:01:15,600 --> 00:01:18,510 +从某种意义上说,这八个字的句子 +in the sense that this sentence of eight words + +23 +00:01:18,510 --> 00:01:20,643 +被标记为九个标记。 +is tokenized into nine tokens. + +24 +00:01:22,920 --> 00:01:26,340 +另一方面,如果我使用相同的分词器 +On the other hand, if I use this same tokenizer + +25 +00:01:26,340 --> 00:01:29,370 +在孟加拉语的一个句子中,我们看到 +on a sentence in Bengali, we see that + +26 +00:01:29,370 --> 00:01:33,690 +要么一个词被分成许多子标记, +either a word is divided into many sub tokens, + +27 +00:01:33,690 --> 00:01:36,270 +或者分词器不知道其中之一 +or that the tokenizer does not know one of + +28 +00:01:36,270 --> 00:01:39,873 +unicode 字符并仅返回未知标记。 +the unicode characters and returns only an unknown token. + +29 +00:01:41,220 --> 00:01:44,970 +一个常用词被分成许多标记的事实 +The fact that a common word is split into many tokens + +30 +00:01:44,970 --> 00:01:47,910 +可能会有问题,因为语言模型 +can be problematic, because language models + +31 +00:01:47,910 --> 00:01:51,903 +只能处理有限长度的令牌序列。 +can only handle a sequence of tokens of limited length. + +32 +00:01:52,830 --> 00:01:55,830 +过度拆分初始文本的分词器 +A tokenizer that excessively splits your initial text + +33 +00:01:55,830 --> 00:01:58,503 +甚至可能影响模型的性能。 +may even impact the performance of your model. + +34 +00:01:59,760 --> 00:02:02,280 +未知的令牌也有问题, +Unknown tokens are also problematic, + +35 +00:02:02,280 --> 00:02:04,530 +因为模型将无法提取 +because the model will not be able to extract + +36 +00:02:04,530 --> 00:02:07,563 +来自文本未知部分的任何信息。 +any information from the unknown part of the text. + +37 +00:02:11,430 --> 00:02:13,440 +在另一个例子中,我们可以看到 +In this other example, we can see that + +38 +00:02:13,440 --> 00:02:17,100 +分词器替换包含字符的单词 +the tokenizer replaces words containing characters + +39 +00:02:17,100 --> 00:02:20,973 +带有重音和带有未知标记的大写字母。 +with accents and capital letters with unknown tokens. + +40 +00:02:22,050 --> 00:02:24,770 +最后,如果我们再次使用这个分词器 +Finally, if we use again this tokenizer + +41 +00:02:24,770 --> 00:02:28,170 +为了标记医学词汇,我们再次看到 +to tokenize medical vocabulary, we see again that + +42 +00:02:28,170 --> 00:02:31,800 +一个单词被分成许多子标记, +a single word is divided into many sub tokens, + +43 +00:02:31,800 --> 00:02:34,803 +四个用于扑热息痛,四个用于咽炎。 +four for paracetamol, and four for pharyngitis. + +44 +00:02:37,110 --> 00:02:39,360 +当前使用的大多数分词器 +Most of the tokenizers used by the current + +45 +00:02:39,360 --> 00:02:42,540 +需要训练最先进的语言模型 +state of the art language models need to be trained + +46 +00:02:42,540 --> 00:02:45,360 +在与使用的语料库相似的语料库上 +on a corpus that is similar to the one used + +47 +00:02:45,360 --> 00:02:47,463 +预训练语言模型。 +to pre-train the language model. + +48 +00:02:49,140 --> 00:02:51,150 +这种训练包括学习规则 +This training consists in learning rules + +49 +00:02:51,150 --> 00:02:53,250 +将文本分成标记。 +to divide the text into tokens. + +50 +00:02:53,250 --> 00:02:56,160 +以及学习和使用这些规则的方法 +And the way to learn these rules and use them + +51 +00:02:56,160 --> 00:02:58,233 +取决于所选的分词器模型。 +depends on the chosen tokenizer model. + +52 +00:03:00,630 --> 00:03:04,590 +因此,要训练一个新的分词器,首先需要 +Thus, to train a new tokenizer, it is first necessary + +53 +00:03:04,590 --> 00:03:07,653 +构建由原始文本组成的训练语料库。 +to build a training corpus composed of raw texts. + +54 +00:03:08,910 --> 00:03:12,423 +然后,你必须为你的分词器选择一种架构。 +Then, you have to choose an architecture for your tokenizer. + +55 +00:03:13,410 --> 00:03:14,763 +这里有两个选项。 +Here there are two options. + +56 +00:03:15,900 --> 00:03:19,710 +最简单的是重用与那个相同的架构 +The simplest is to reuse the same architecture as the one + +57 +00:03:19,710 --> 00:03:22,863 +另一个已经训练过的模型使用的分词器。 +of a tokenizer used by another model already trained. + +58 +00:03:24,210 --> 00:03:25,980 +否则也可以 +Otherwise it is also possible + +59 +00:03:25,980 --> 00:03:28,560 +完全设计你的分词器。 +to completely design your tokenizer. + +60 +00:03:28,560 --> 00:03:31,683 +但这需要更多的经验和关注。 +But it requires more experience and attention. + +61 +00:03:33,750 --> 00:03:36,660 +一旦选择了架构,你就可以 +Once the architecture is chosen, you can thus + +62 +00:03:36,660 --> 00:03:39,513 +在你构成的语料库上训练这个分词器。 +train this tokenizer on your constituted corpus. + +63 +00:03:40,650 --> 00:03:43,440 +最后,你需要做的最后一件事就是保存 +Finally, the last thing that you need to do is to save + +64 +00:03:43,440 --> 00:03:46,443 +能够使用此分词器的学习规则。 +the learned rules to be able to use this tokenizer. + +65 +00:03:49,530 --> 00:03:51,330 +让我们举个例子。 +Let's take an example. + +66 +00:03:51,330 --> 00:03:54,873 +假设你想在 Python 代码上训练 GPT-2 模型。 +Let's say you want to train a GPT-2 model on Python code. + +67 +00:03:56,160 --> 00:03:59,640 +即使 Python 代码通常是英文的 +Even if the Python code is usually in English + +68 +00:03:59,640 --> 00:04:02,386 +这种类型的文本非常具体, +this type of text is very specific, + +69 +00:04:02,386 --> 00:04:04,473 +并值得一个训练有素的分词器。 +and deserves a tokenizer trained on it. + +70 +00:04:05,340 --> 00:04:07,980 +为了让你相信这一点,我们将在最后看到 +To convince you of this, we will see at the end + +71 +00:04:07,980 --> 00:04:10,023 +一个例子产生的差异。 +the difference produced on an example. + +72 +00:04:11,400 --> 00:04:13,747 +为此,我们将使用该方法 +For that we are going to use the method + +73 +00:04:13,747 --> 00:04:18,240 +“train_new_from_iterator”,所有快速分词器 +"train_new_from_iterator" that all the fast tokenizers + +74 +00:04:18,240 --> 00:04:20,040 +图书馆有,因此, +of the library have and thus, + +75 +00:04:20,040 --> 00:04:22,503 +特别是 GPT2TokenizerFast。 +in particular GPT2TokenizerFast. + +76 +00:04:23,880 --> 00:04:26,100 +这是我们案例中最简单的方法 +This is the simplest method in our case + +77 +00:04:26,100 --> 00:04:28,983 +有一个适合 Python 代码的分词器。 +to have a tokenizer adapted to Python code. + +78 +00:04:30,180 --> 00:04:34,140 +请记住,第一件事是收集训练语料库。 +Remember, the first thing is to gather a training corpus. + +79 +00:04:34,140 --> 00:04:37,320 +我们将使用 CodeSearchNet 数据集的一个子部分 +We will use a subpart of the CodeSearchNet dataset + +80 +00:04:37,320 --> 00:04:39,360 +仅包含 Python 函数 +containing only Python functions + +81 +00:04:39,360 --> 00:04:42,360 +来自 Github 上的开源库。 +from open source libraries on Github. + +82 +00:04:42,360 --> 00:04:43,650 +这是个好时机。 +It's good timing. + +83 +00:04:43,650 --> 00:04:46,980 +该数据集为数据集库所知 +This dataset is known by the datasets library + +84 +00:04:46,980 --> 00:04:49,203 +我们可以用两行代码加载它。 +and we can load it in two lines of code. + +85 +00:04:50,760 --> 00:04:55,230 +然后,正如 “train_new_from_iterator” 方法所期望的那样 +Then, as the "train_new_from_iterator" method expects + +86 +00:04:55,230 --> 00:04:57,150 +文本列表的迭代器, +a iterator of lists of texts, + +87 +00:04:57,150 --> 00:04:59,970 +我们创建 “get_training_corpus” 函数, +we create the "get_training_corpus" function, + +88 +00:04:59,970 --> 00:05:01,743 +这将返回一个迭代器。 +which will return an iterator. + +89 +00:05:03,870 --> 00:05:05,430 +现在我们有了迭代器 +Now that we have our iterator + +90 +00:05:05,430 --> 00:05:09,630 +在我们的 Python 函数语料库中,我们可以加载 +on our Python functions corpus, we can load + +91 +00:05:09,630 --> 00:05:12,351 +GPT-2 分词器架构。 +the GPT-2 tokenizer architecture. + +92 +00:05:12,351 --> 00:05:16,560 +这里 old_tokenizer 不适应我们的语料库。 +Here old_tokenizer is not adapted to our corpus. + +93 +00:05:16,560 --> 00:05:17,700 +但我们只需要 +But we only need + +94 +00:05:17,700 --> 00:05:20,733 +再用一行在我们的新语料库上训练它。 +one more line to train it on our new corpus. + +95 +00:05:21,780 --> 00:05:24,720 +大多数标记化的共同论点 +An argument that is common to most of the tokenization + +96 +00:05:24,720 --> 00:05:28,980 +目前使用的算法是词汇表的大小。 +algorithms used at the moment is the size of the vocabulary. + +97 +00:05:28,980 --> 00:05:31,773 +我们在这里选择值 52,000。 +We choose here the value 52,000. + +98 +00:05:32,820 --> 00:05:35,760 +最后,一旦训练结束, +Finally, once the training is finished, + +99 +00:05:35,760 --> 00:05:38,850 +我们只需要在本地保存我们的新分词器, +we just have to save our new tokenizer locally, + +100 +00:05:38,850 --> 00:05:41,730 +或将其发送到集线器以便能够重用它 +or send it to the hub to be able to reuse it + +101 +00:05:41,730 --> 00:05:43,593 +之后很容易。 +very easily afterwards. + +102 +00:05:45,270 --> 00:05:48,990 +最后大家一起看个例子有没有用 +Finally, let's see together on an example if it was useful + +103 +00:05:48,990 --> 00:05:53,073 +重新训练一个类似于 GPT-2 的分词器。 +to re-train a tokenizer similar to GPT-2 one. + +104 +00:05:55,110 --> 00:05:57,660 +使用 GPT-2 的原始分词器 +With the original tokenizer of GPT-2 + +105 +00:05:57,660 --> 00:06:00,330 +我们看到所有的空间都是孤立的, +we see that all spaces are isolated, + +106 +00:06:00,330 --> 00:06:01,920 +和方法名称 randn, +and the method name randn, + +107 +00:06:01,920 --> 00:06:04,833 +在 Python 代码中比较常见,分为两部分。 +relatively common in Python code, is split in two. + +108 +00:06:05,730 --> 00:06:09,060 +使用我们新的分词器,单缩进和双缩进 +With our new tokenizer, single and double indentations + +109 +00:06:09,060 --> 00:06:10,890 +已经学习和方法 randn +have been learned and the method randn + +110 +00:06:10,890 --> 00:06:13,770 +被代币化为一个代币。 +is tokenized into one token. + +111 +00:06:13,770 --> 00:06:15,000 +有了这个, +And with that, + +112 +00:06:15,000 --> 00:06:18,123 +你现在知道如何训练你自己的分词器了。 +you now know how to train your very own tokenizers now. + +113 +00:06:19,498 --> 00:06:22,165 +(空气呼啸) +(air whooshing) + diff --git a/subtitles/zh-CN/43_why-are-fast-tokenizers-called-fast.srt b/subtitles/zh-CN/43_why-are-fast-tokenizers-called-fast.srt new file mode 100644 index 000000000..26a06bbaf --- /dev/null +++ b/subtitles/zh-CN/43_why-are-fast-tokenizers-called-fast.srt @@ -0,0 +1,185 @@ +1 +00:00:00,418 --> 00:00:03,251 +(戏剧性的嗖嗖声) +(dramatic whoosh) + +2 +00:00:05,340 --> 00:00:08,460 +- 为什么快速分词器被称为快速? +- Why are fast tokenizers called fast? + +3 +00:00:08,460 --> 00:00:10,950 +在这个视频中,我们将看到到底有多快, +In this video, we'll see exactly how much faster, + +4 +00:00:10,950 --> 00:00:13,800 +另外,比较了所谓的快速组织者 +also, so-called fast organizers are compared + +5 +00:00:13,800 --> 00:00:15,153 +给他们慢的同行。 +to their slow counterparts. + +6 +00:00:16,200 --> 00:00:19,260 +对于这个基准测试,我们将使用 GLUE MNLI 数据集 +For this benchmark, we'll use the GLUE MNLI dataset + +7 +00:00:19,260 --> 00:00:23,160 +其中包含 432,000 个拼写的文本。 +which contains 432,000 spells of text. + +8 +00:00:23,160 --> 00:00:25,890 +我们将看看快速和慢速版本需要多长时间 +We'll see how long it takes for the fast and slow versions + +9 +00:00:25,890 --> 00:00:28,143 +一个 BERT 分词器来处理它们。 +of a BERT tokenizer to process them all. + +10 +00:00:29,670 --> 00:00:31,380 +我们定义我们的快速和慢速令牌组织者 +We define our fast and slow token organizer + +11 +00:00:31,380 --> 00:00:33,717 +使用 AutoTokenizer API。 +using the AutoTokenizer API. + +12 +00:00:33,717 --> 00:00:37,110 +快速分词器在可用时是默认的。 +The fast tokenizer is a default when available. + +13 +00:00:37,110 --> 00:00:40,443 +所以我们通过,使用_fast=False 来定义慢速的。 +So we pass along, use_fast=False to define the slow one. + +14 +00:00:41,430 --> 00:00:43,530 +在笔记本中,我们可以为执行计时 +In a notebook, we can time the execution + +15 +00:00:43,530 --> 00:00:46,800 +本身带有时间魔法命令,就像这样。 +of itself with a time magic command, like this. + +16 +00:00:46,800 --> 00:00:49,350 +处理整个数据集快四倍 +Processing the whole data set is four times faster + +17 +00:00:49,350 --> 00:00:50,970 +使用快速分词器。 +with a fast tokenizer. + +18 +00:00:50,970 --> 00:00:54,000 +确实更快,但不是很令人印象深刻。 +That quicker indeed, but not very impressive. + +19 +00:00:54,000 --> 00:00:55,380 +这是因为我们传递了文本 +This is because we passed along the texts + +20 +00:00:55,380 --> 00:00:57,240 +一次一个到分词器。 +to the tokenizer one at a time. + +21 +00:00:57,240 --> 00:00:59,730 +这是快速组织者的常见错误 +This is a common mistake to do with fast organizers + +22 +00:00:59,730 --> 00:01:02,550 +由 Rust 支持,因此能够确定优先级 +which are backed by Rust, and thus able to prioritize + +23 +00:01:02,550 --> 00:01:05,370 +多个文本的标记化。 +the tokenization of multiple texts. + +24 +00:01:05,370 --> 00:01:07,290 +一次只向他们传递一个文本, +Passing them only one text at a time, + +25 +00:01:07,290 --> 00:01:09,720 +就像在两大洲之间发送一艘货船 +is like sending a cargo ship between two continents + +26 +00:01:09,720 --> 00:01:13,140 +只有一个容器,效率非常低。 +with just one container, it's very inefficient. + +27 +00:01:13,140 --> 00:01:15,810 +为了释放我们快速分词器的全部速度, +To unleash the full speed of our fast tokenizers, + +28 +00:01:15,810 --> 00:01:18,840 +我们需要向他们发送成批的文本,我们可以做到 +we need to send them batches of texts, which we can do + +29 +00:01:18,840 --> 00:01:21,423 +使用 map 方法的 batched=True 参数。 +with the batched=True argument of the map method. + +30 +00:01:22,620 --> 00:01:25,950 +现在这些都是令人印象深刻的结果,所以快速分词器 +Now those are impressive results, so the fast tokenizer + +31 +00:01:25,950 --> 00:01:28,410 +处理需要 4 秒的数据集需要 12 秒 +takes 12 second to process the dataset that takes four + +32 +00:01:28,410 --> 00:01:30,093 +分钟到慢分词器。 +minute to the slow tokenizer. + +33 +00:01:31,440 --> 00:01:33,510 +总结此表中的结果, +Summarizing the results in this table, + +34 +00:01:33,510 --> 00:01:36,630 +你可以看到为什么我们快速调用这些分词器。 +you can see why we have called those tokenizers fast. + +35 +00:01:36,630 --> 00:01:38,760 +这仅用于标记化文本。 +And this is only for tokenizing texts. + +36 +00:01:38,760 --> 00:01:40,710 +如果你需要训练一个新的分词器, +If you ever need to train a new tokenizer, + +37 +00:01:40,710 --> 00:01:42,523 +他们也很快做到这一点。 +they do this very quickly too. + diff --git a/subtitles/zh-CN/44_fast-tokenizer-superpowers.srt b/subtitles/zh-CN/44_fast-tokenizer-superpowers.srt new file mode 100644 index 000000000..70ea63f74 --- /dev/null +++ b/subtitles/zh-CN/44_fast-tokenizer-superpowers.srt @@ -0,0 +1,375 @@ +1 +00:00:05,010 --> 00:00:06,270 +- 快速分词器 +- The fast tokenizers + +2 +00:00:06,270 --> 00:00:08,580 +变形金刚图书馆的速度很快, +of the Transformers library are fast, + +3 +00:00:08,580 --> 00:00:11,490 +但他们也实现了非常有用的功能 +but they also implement features that will be super useful + +4 +00:00:11,490 --> 00:00:14,536 +用于数据预处理和后处理。 +for data pre-processing and post-processing. + +5 +00:00:14,536 --> 00:00:17,239 +让我们来看看吧! +Let's have a look at them! + +6 +00:00:17,239 --> 00:00:18,650 +首先,让我们来看看 +First, let's have a look + +7 +00:00:18,650 --> 00:00:21,690 +在分词器的通常输出中。 +at the usual output of a tokenizer. + +8 +00:00:21,690 --> 00:00:24,278 +我们得到对应于 token 的输入 ID, +We get input IDs that correspond to token, + +9 +00:00:24,278 --> 00:00:27,960 +但是我们在这个过程中丢失了很多信息。 +but we lose a lot of information in the process. + +10 +00:00:27,960 --> 00:00:29,010 +例如, +For instance, + +11 +00:00:29,010 --> 00:00:31,856 +这里两个句子的标记化是相同的 +here the tokenization is the same for the two sentences + +12 +00:00:31,856 --> 00:00:35,373 +即使一个比另一个多几个空间。 +even if one has several more spaces than the other. + +13 +00:00:36,300 --> 00:00:39,150 +因此,仅仅拥有输入 ID 是不够的 +Just having the input IDs is thus not enough + +14 +00:00:39,150 --> 00:00:42,330 +如果我们想将一些标记与一段文本相匹配, +if we want to match some tokens with a span of text, + +15 +00:00:42,330 --> 00:00:43,320 +我们需要做的事情 +something we'll need to do + +16 +00:00:43,320 --> 00:00:46,111 +例如,在处理问题回答时。 +when tackling question answering, for instance. + +17 +00:00:46,111 --> 00:00:47,592 +也很难知道 +It's also difficult to know + +18 +00:00:47,592 --> 00:00:50,850 +当两个标记是否属于同一个词时。 +when two tokens belong to the same word or not. + +19 +00:00:50,850 --> 00:00:52,860 +当你只看输出时看起来很容易 +It looks easy when you just look at the output + +20 +00:00:52,860 --> 00:00:55,650 +一个 BERT 分词器,我们只需要看一下 +of a BERT tokenizer where we just need to look + +21 +00:00:55,650 --> 00:00:56,779 +对于散列散列。 +for the hash hash. + +22 +00:00:56,779 --> 00:00:59,040 +但是其他分词器有不同的方式 +But other tokenizers have different ways + +23 +00:00:59,040 --> 00:01:00,987 +标记部分单词。 +to tokenize parts of words. + +24 +00:01:00,987 --> 00:01:04,470 +例如,RoBERTa 添加了这个特殊的 G 符号 +For instance, RoBERTa adds this special G symbol + +25 +00:01:04,470 --> 00:01:06,491 +在单词的开头标记标记 +to mark the tokens at the beginning of the word + +26 +00:01:06,491 --> 00:01:09,570 +T5 使用这个特殊的下划线符号 +and T5 uses this special underscore symbol + +27 +00:01:09,570 --> 00:01:11,150 +为了同样的目的。 +for the same purpose. + +28 +00:01:11,150 --> 00:01:14,760 +值得庆幸的是,快速分词器会跟踪这个词 +Thankfully, the fast tokenizers keep track of the word + +29 +00:01:14,760 --> 00:01:16,230 +每个令牌来自, +each token comes from, + +30 +00:01:16,230 --> 00:01:19,571 +使用 word_ids 方法,你可以在它们的输出上使用。 +with a word_ids method you can use on their outputs. + +31 +00:01:19,571 --> 00:01:21,870 +输出不一定清楚, +The output is not necessarily clear, + +32 +00:01:21,870 --> 00:01:24,076 +但像这样聚集在一张漂亮的桌子上, +but assembled together in a nice table like this, + +33 +00:01:24,076 --> 00:01:26,853 +我们可以查看每个标记的单词位置。 +we can look at the word position for each token. + +34 +00:01:27,930 --> 00:01:30,220 +更好的是,快速标记器会跟踪 +Even better, the fast tokenizers keep track + +35 +00:01:30,220 --> 00:01:33,198 +每个标记来自的字符范围, +of the span of characters each token comes from, + +36 +00:01:33,198 --> 00:01:35,760 +我们可以在调用它时得到它们 +and we can get them when calling it on one + +37 +00:01:35,760 --> 00:01:37,221 +或通过添加几个文本 +or several text by adding + +38 +00:01:37,221 --> 00:01:40,470 +return_offsets_mapping=True 参数。 +the return_offsets_mapping=True argument. + +39 +00:01:40,470 --> 00:01:42,312 +在这种情况下,我们可以看到我们是如何跳仓的 +In this instance, we can see how we jump positions + +40 +00:01:42,312 --> 00:01:45,650 +在 hash hash token 和 super token 之间, +between the hash hash token and the super token, + +41 +00:01:45,650 --> 00:01:49,992 +因为首句中有多个空格。 +because of the multiple spaces in the initial sentence. + +42 +00:01:49,992 --> 00:01:52,110 +为了实现这一点,快速分词器 +To enable this, the fast tokenizers + +43 +00:01:52,110 --> 00:01:54,270 +在每一步存储附加信息 +store additional information at each step + +44 +00:01:54,270 --> 00:01:55,440 +他们的内部管道。 +of their internal pipeline. + +45 +00:01:55,440 --> 00:01:57,951 +该内部管道包括规范化, +That internal pipeline consists of normalization, + +46 +00:01:57,951 --> 00:02:00,360 +我们对文本进行一些清洁, +where we apply some cleaning to the text, + +47 +00:02:00,360 --> 00:02:02,621 +喜欢小写或删除口音; +like lower casing or removing the accents; + +48 +00:02:02,621 --> 00:02:04,088 +预标记化, +pre-tokenization, + +49 +00:02:04,088 --> 00:02:06,530 +这是我们将文本分成单词的地方; +which is where we split the texts into words; + +50 +00:02:06,530 --> 00:02:09,360 +然后我们应用分词器的模型, +then we apply the model of the tokenizer, + +51 +00:02:09,360 --> 00:02:11,725 +这是单词被分成标记的地方, +which is where the words are split into tokens, + +52 +00:02:11,725 --> 00:02:13,748 +在最终进行后期处理之前, +before finally doing the post processing, + +53 +00:02:13,748 --> 00:02:16,023 +添加特殊标记的地方。 +where special tokens are added. + +54 +00:02:17,100 --> 00:02:19,050 +从管道的开始到结束, +From the beginning to the end of the pipeline, + +55 +00:02:19,050 --> 00:02:21,390 +标记器跟踪每个文本范围 +the tokenizer keeps track of each span of text + +56 +00:02:21,390 --> 00:02:23,853 +对应于每个单词,然后是每个标记。 +that corresponds to each word, then each token. + +57 +00:02:24,990 --> 00:02:26,100 +我们会看到它有多有用 +We'll see how useful it is + +58 +00:02:26,100 --> 00:02:27,990 +当我们处理以下任务时: +when we tackle the following tasks: + +59 +00:02:27,990 --> 00:02:29,549 +在进行掩码语言建模时 +when doing masked language modeling + +60 +00:02:29,549 --> 00:02:32,407 +一种获得最先进结果的变体 +one variation that gets state-of-the-art results + +61 +00:02:32,407 --> 00:02:35,040 +是屏蔽给定单词的所有标记 +is to mask all the tokens of a given word + +62 +00:02:35,040 --> 00:02:37,440 +而不是随机选择的单词。 +instead of randomly chosen words. + +63 +00:02:37,440 --> 00:02:40,800 +这将要求我们使用我们看到的单词 ID。 +This will require us to use the word IDs we saw. + +64 +00:02:40,800 --> 00:02:42,329 +在做 token 分类的时候, +When doing token classification, + +65 +00:02:42,329 --> 00:02:45,090 +我们需要转换我们在单词上的标签, +we'll need to convert the labels we have on words, + +66 +00:02:45,090 --> 00:02:47,250 +每个标记上的标签。 +to labels on each tokens. + +67 +00:02:47,250 --> 00:02:48,480 +至于偏移映射, +As for the offset mappings, + +68 +00:02:48,480 --> 00:02:50,610 +当我们需要转换时它会非常有用 +it will be super useful when we need to convert + +69 +00:02:50,610 --> 00:02:53,436 +将句子中的标记位置转换为一段文本, +token positions in a sentence into a span of text, + +70 +00:02:53,436 --> 00:02:55,800 +我们在寻找时需要知道 +which we'll need to know when we're looking + +71 +00:02:55,800 --> 00:02:56,813 +在回答问题时 +at question answering + +72 +00:02:56,813 --> 00:02:58,680 +或者在对相应的标记进行分组时 +or when grouping the tokens corresponding + +73 +00:02:58,680 --> 00:03:01,023 +到令牌分类中的同一实体。 +to the same entity in token classification. + +74 +00:03:02,160 --> 00:03:03,450 +要查看这些任务, +To have a look at these tasks, + +75 +00:03:03,450 --> 00:03:04,950 +查看下面链接的视频! +check the videos linked below! + diff --git a/subtitles/zh-CN/45_inside-the-token-classification-pipeline-(pytorch).srt b/subtitles/zh-CN/45_inside-the-token-classification-pipeline-(pytorch).srt new file mode 100644 index 000000000..4f9310845 --- /dev/null +++ b/subtitles/zh-CN/45_inside-the-token-classification-pipeline-(pytorch).srt @@ -0,0 +1,385 @@ +1 +00:00:00,076 --> 00:00:01,462 +(标题嘶嘶作响) +(title whooshes) + +2 +00:00:01,462 --> 00:00:02,382 +(标志弹出) +(logo pops) + +3 +00:00:02,382 --> 00:00:05,340 +(标题嘶嘶作响) +(title whooshes) + +4 +00:00:05,340 --> 00:00:06,210 +- 我们来看一下 +- Let's have a look + +5 +00:00:06,210 --> 00:00:08,283 +在令牌分类管道内。 +inside the token classification pipeline. + +6 +00:00:10,080 --> 00:00:11,580 +在管道视频中, +In the pipeline video, + +7 +00:00:11,580 --> 00:00:13,320 +我们研究了不同的应用 +we looked at the different applications + +8 +00:00:13,320 --> 00:00:15,960 +Transformers 库支持开箱即用, +the Transformers library supports out of the box, + +9 +00:00:15,960 --> 00:00:18,780 +其中之一是令牌分类, +one of them being token classification, + +10 +00:00:18,780 --> 00:00:21,810 +例如预测句子中的每个单词 +for instance predicting for each word in a sentence + +11 +00:00:21,810 --> 00:00:24,510 +是否对应于一个人,一个组织 +whether they correspond to a person, an organization + +12 +00:00:24,510 --> 00:00:25,353 +或位置。 +or a location. + +13 +00:00:26,670 --> 00:00:28,920 +我们甚至可以将相应的标记组合在一起 +We can even group together the tokens corresponding + +14 +00:00:28,920 --> 00:00:32,040 +到同一个实体,例如所有令牌 +to the same entity, for instance all the tokens + +15 +00:00:32,040 --> 00:00:35,373 +在这里形成了 Sylvain 这个词,或 Hugging 和 Face。 +that formed the word Sylvain here, or Hugging and Face. + +16 +00:00:37,290 --> 00:00:40,230 +令牌分类管道的工作方式相同 +The token classification pipeline works the same way + +17 +00:00:40,230 --> 00:00:42,630 +作为我们研究的文本分类管道 +as the text classification pipeline we studied + +18 +00:00:42,630 --> 00:00:44,430 +在上一个视频中。 +in the previous video. + +19 +00:00:44,430 --> 00:00:45,930 +分为三个步骤。 +There are three steps. + +20 +00:00:45,930 --> 00:00:49,623 +标记化、模型和后处理。 +The tokenization, the model, and the postprocessing. + +21 +00:00:50,940 --> 00:00:52,530 +前两个步骤相同 +The first two steps are identical + +22 +00:00:52,530 --> 00:00:54,630 +到文本分类管道, +to the text classification pipeline, + +23 +00:00:54,630 --> 00:00:57,300 +除了我们使用自动标记分类模型 +except we use an auto token classification model + +24 +00:00:57,300 --> 00:01:00,150 +而不是序列分类。 +instead of a sequence classification one. + +25 +00:01:00,150 --> 00:01:03,720 +我们标记我们的文本,然后将其提供给模型。 +We tokenize our text then feed it to the model. + +26 +00:01:03,720 --> 00:01:05,877 +而不是为每个可能的标签获取一个数字 +Instead of getting one number for each possible label + +27 +00:01:05,877 --> 00:01:08,700 +对于整个句子,我们得到一个数字 +for the whole sentence, we get one number + +28 +00:01:08,700 --> 00:01:10,770 +对于可能的九个标签中的每一个 +for each of the possible nine labels + +29 +00:01:10,770 --> 00:01:13,983 +对于句子中的每个标记,此处为 19。 +for every token in the sentence, here 19. + +30 +00:01:15,300 --> 00:01:18,090 +与 Transformers 库的所有其他模型一样, +Like all the other models of the Transformers library, + +31 +00:01:18,090 --> 00:01:19,830 +我们的模型输出 logits, +our model outputs logits, + +32 +00:01:19,830 --> 00:01:23,073 +我们使用 SoftMax 将其转化为预测。 +which we turn into predictions by using a SoftMax. + +33 +00:01:23,940 --> 00:01:26,190 +我们还获得了每个标记的预测标签 +We also get the predicted label for each token + +34 +00:01:26,190 --> 00:01:27,990 +通过最大预测, +by taking the maximum prediction, + +35 +00:01:27,990 --> 00:01:29,880 +因为 SoftMax 函数保留了顺序, +since the SoftMax function preserves the orders, + +36 +00:01:29,880 --> 00:01:31,200 +我们本可以在 logits 上完成 +we could have done it on the logits + +37 +00:01:31,200 --> 00:01:33,050 +如果我们不需要预测。 +if we had no need of the predictions. + +38 +00:01:33,930 --> 00:01:35,880 +模型配置包含标签映射 +The model config contains the label mapping + +39 +00:01:35,880 --> 00:01:37,740 +在其 id2label 字段中。 +in its id2label field. + +40 +00:01:37,740 --> 00:01:41,430 +使用它,我们可以将每个标记映射到其相应的标签。 +Using it, we can map every token to its corresponding label. + +41 +00:01:41,430 --> 00:01:43,950 +标签 O 不对应任何实体, +The label, O, correspond to no entity, + +42 +00:01:43,950 --> 00:01:45,985 +这就是为什么我们没有在结果中看到它 +which is why we didn't see it in our results + +43 +00:01:45,985 --> 00:01:47,547 +在第一张幻灯片中。 +in the first slide. + +44 +00:01:47,547 --> 00:01:49,440 +在标签和概率之上, +On top of the label and the probability, + +45 +00:01:49,440 --> 00:01:51,000 +这些结果包括开始 +those results included the start + +46 +00:01:51,000 --> 00:01:53,103 +和句末字符。 +and end character in the sentence. + +47 +00:01:54,120 --> 00:01:55,380 +我们需要使用偏移映射 +We'll need to use the offset mapping + +48 +00:01:55,380 --> 00:01:56,640 +分词器得到那些。 +of the tokenizer to get those. + +49 +00:01:56,640 --> 00:01:58,050 +看看下面链接的视频 +Look at the video linked below + +50 +00:01:58,050 --> 00:02:00,300 +如果你还不知道它们。 +if you don't know about them already. + +51 +00:02:00,300 --> 00:02:02,280 +然后,遍历每个标记 +Then, looping through each token + +52 +00:02:02,280 --> 00:02:04,080 +具有不同于 O 的标签, +that has a label distinct from O, + +53 +00:02:04,080 --> 00:02:06,120 +我们可以建立我们得到的结果列表 +we can build the list of results we got + +54 +00:02:06,120 --> 00:02:07,320 +用我们的第一条管道。 +with our first pipeline. + +55 +00:02:08,460 --> 00:02:10,560 +最后一步是将标记组合在一起 +The last step is to group together tokens + +56 +00:02:10,560 --> 00:02:12,310 +对应于同一个实体。 +that correspond to the same entity. + +57 +00:02:13,264 --> 00:02:16,140 +这就是为什么我们为每种类型的实体设置了两个标签, +This is why we had two labels for each type of entity, + +58 +00:02:16,140 --> 00:02:18,450 +例如,I-PER 和 B-PER。 +I-PER and B-PER, for instance. + +59 +00:02:18,450 --> 00:02:20,100 +它让我们知道一个令牌是否是 +It allows us to know if a token is + +60 +00:02:20,100 --> 00:02:22,323 +在与前一个相同的实体中。 +in the same entity as the previous one. + +61 +00:02:23,310 --> 00:02:25,350 +请注意,有两种标记方式 +Note, that there are two ways of labeling used + +62 +00:02:25,350 --> 00:02:26,850 +用于令牌分类。 +for token classification. + +63 +00:02:26,850 --> 00:02:29,420 +一个,这里是粉红色的,使用 B-PER 标签 +One, in pink here, uses the B-PER label + +64 +00:02:29,420 --> 00:02:30,810 +在每个新实体的开始, +at the beginning of each new entity, + +65 +00:02:30,810 --> 00:02:32,760 +但另一个,蓝色的, +but the other, in blue, + +66 +00:02:32,760 --> 00:02:35,340 +只用它来分隔两个相邻的实体 +only uses it to separate two adjacent entities + +67 +00:02:35,340 --> 00:02:37,140 +同类型的。 +of the same type. + +68 +00:02:37,140 --> 00:02:39,690 +在这两种情况下,我们都可以标记一个新实体 +In both cases, we can flag a new entity + +69 +00:02:39,690 --> 00:02:41,940 +每次我们看到一个新标签出现时, +each time we see a new label appearing, + +70 +00:02:41,940 --> 00:02:44,730 +带有 I 或 B 前缀, +either with the I or B prefix, + +71 +00:02:44,730 --> 00:02:47,130 +然后将以下所有标记为相同的标记, +then take all the following tokens labeled the same, + +72 +00:02:47,130 --> 00:02:48,870 +带有 I 标志。 +with an I-flag. + +73 +00:02:48,870 --> 00:02:51,330 +这与偏移映射一起开始 +This, coupled with the offset mapping to get the start + +74 +00:02:51,330 --> 00:02:54,210 +和结束字符允许我们获得文本的跨度 +and end characters allows us to get the span of texts + +75 +00:02:54,210 --> 00:02:55,233 +对于每个实体。 +for each entity. + +76 +00:02:56,569 --> 00:02:59,532 +(标题嘶嘶作响) +(title whooshes) + +77 +00:02:59,532 --> 00:03:01,134 +(标题失败) +(title fizzles) + diff --git a/subtitles/zh-CN/46_inside-the-token-classification-pipeline-(tensorflow).srt b/subtitles/zh-CN/46_inside-the-token-classification-pipeline-(tensorflow).srt new file mode 100644 index 000000000..e216a08ff --- /dev/null +++ b/subtitles/zh-CN/46_inside-the-token-classification-pipeline-(tensorflow).srt @@ -0,0 +1,375 @@ +1 +00:00:00,180 --> 00:00:03,013 +(嘶嘶声) +(whooshing sound) + +2 +00:00:05,310 --> 00:00:06,143 +- 我们来看一下 +- Let's have a look + +3 +00:00:06,143 --> 00:00:08,133 +在令牌分类管道内。 +inside the token classification pipeline. + +4 +00:00:09,780 --> 00:00:11,430 +在管道视频中, +In the pipeline video, + +5 +00:00:11,430 --> 00:00:13,230 +我们研究了不同的应用 +we looked at the different applications + +6 +00:00:13,230 --> 00:00:16,050 +Transformers 库支持开箱即用。 +the Transformers library supports out of the box. + +7 +00:00:16,050 --> 00:00:18,660 +其中之一是令牌分类。 +One of them being token classification. + +8 +00:00:18,660 --> 00:00:22,050 +例如,预测句子中的每个单词, +For instance, predicting for each word in a sentence, + +9 +00:00:22,050 --> 00:00:23,790 +他们是否对应于一个人, +whether they correspond to a person, + +10 +00:00:23,790 --> 00:00:26,043 +一个组织或地点。 +an organization, or location. + +11 +00:00:27,690 --> 00:00:29,250 +我们甚至可以将令牌组合在一起 +We can even group together the tokens + +12 +00:00:29,250 --> 00:00:31,320 +对应同一个实体。 +corresponding to the same entity. + +13 +00:00:31,320 --> 00:00:34,890 +例如,这里构成单词 Sylvain 的所有标记 +For instance, all the tokens that form the word Sylvain here + +14 +00:00:34,890 --> 00:00:36,423 +或 Hugging 和 Face。 +or Hugging and Face. + +15 +00:00:37,320 --> 00:00:39,720 +因此,令牌分类管道 +So, token classification pipeline + +16 +00:00:39,720 --> 00:00:42,480 +与文本分类管道的工作方式相同 +works the same way as a text classification pipeline + +17 +00:00:42,480 --> 00:00:44,910 +我们在之前的视频中学习过。 +we studied in a previous video. + +18 +00:00:44,910 --> 00:00:46,500 +分为三个步骤。 +There are three steps. + +19 +00:00:46,500 --> 00:00:50,043 +标记化、模型和后处理。 +Tokenization, the model, and the post processing. + +20 +00:00:51,690 --> 00:00:53,190 +前两个步骤相同 +The first two steps are identical + +21 +00:00:53,190 --> 00:00:55,230 +到文本分类管道, +to the text classification pipeline, + +22 +00:00:55,230 --> 00:00:58,230 +除了我们使用自动标记分类模型 +except we use an auto token classification model + +23 +00:00:58,230 --> 00:01:00,303 +而不是序列分类。 +instead of a sequence classification one. + +24 +00:01:01,560 --> 00:01:04,593 +我们标记我们的文本,然后将其提供给模型。 +We tokenize our text, then feed it to the model. + +25 +00:01:05,580 --> 00:01:08,160 +而不是为每个可能的级别获得一个数字 +Instead of getting one number for each possible level + +26 +00:01:08,160 --> 00:01:09,600 +对于整个句子, +for the whole sentence, + +27 +00:01:09,600 --> 00:01:12,270 +我们为可能的九个级别中的每一个获得一个数字 +we get one number for each of the possible nine levels + +28 +00:01:12,270 --> 00:01:14,250 +对于句子中的每个标记。 +for every token in the sentence. + +29 +00:01:14,250 --> 00:01:15,573 +在这里,19。 +Here, 19. + +30 +00:01:17,070 --> 00:01:19,710 +与 Transformers 库的所有其他模型一样, +Like all the other models of the Transformers library, + +31 +00:01:19,710 --> 00:01:22,560 +我们的模型输出我们需要转换的逻辑 +our model outputs logits which we need to turn + +32 +00:01:22,560 --> 00:01:24,663 +使用 SoftMax 进行预测。 +into predictions by using a SoftMax. + +33 +00:01:25,830 --> 00:01:28,170 +我们还获得了每个标记的预测标签 +We also get the predicted label for each token + +34 +00:01:28,170 --> 00:01:30,063 +通过最大预测。 +by taking the maximum prediction. + +35 +00:01:31,080 --> 00:01:33,540 +由于 softmax 函数保留顺序, +Since the softmax function preserves the order, + +36 +00:01:33,540 --> 00:01:34,980 +我们本可以在 logits 上完成 +we could have done it on the logits + +37 +00:01:34,980 --> 00:01:36,830 +如果我们不需要预测。 +if we had no need of the predictions. + +38 +00:01:37,680 --> 00:01:40,050 +模型配置包含标签映射 +The model config contains the label mapping + +39 +00:01:40,050 --> 00:01:42,090 +在其 id2label 字段中。 +in its id2label field. + +40 +00:01:42,090 --> 00:01:45,600 +使用它,我们可以将每个标记映射到其相应的标签。 +Using it, we can map every token to its corresponding label. + +41 +00:01:45,600 --> 00:01:48,630 +标签 O 对应 “无实体” +The label O corresponds to "no entity" + +42 +00:01:48,630 --> 00:01:50,460 +这就是为什么我们没有在结果中看到它 +which is why we didn't see it in our results + +43 +00:01:50,460 --> 00:01:52,110 +在第一张幻灯片中。 +in the first slide. + +44 +00:01:52,110 --> 00:01:54,150 +在标签和概率之上, +On top of the label and the probability, + +45 +00:01:54,150 --> 00:01:55,620 +这些结果包括开始 +those results included the start + +46 +00:01:55,620 --> 00:01:57,423 +和句末字符。 +and end character in the sentence. + +47 +00:01:58,294 --> 00:01:59,880 +我们需要使用偏移映射 +We'll need to use the offset mapping + +48 +00:01:59,880 --> 00:02:01,110 +分词器得到那些。 +of the tokenizer to get those. + +49 +00:02:01,110 --> 00:02:03,090 +看看下面的视频链接 +Look at the video link below + +50 +00:02:03,090 --> 00:02:05,340 +如果你还不知道它们。 +if you don't know about them already. + +51 +00:02:05,340 --> 00:02:06,990 +然后,遍历每个标记 +Then, looping through each token + +52 +00:02:06,990 --> 00:02:09,090 +具有不同于 O 的标签, +that has a label distinct from O, + +53 +00:02:09,090 --> 00:02:10,590 +我们可以建立结果列表 +we can build the list of results + +54 +00:02:10,590 --> 00:02:12,140 +我们得到了我们的第一条管道。 +we got with our first pipeline. + +55 +00:02:13,650 --> 00:02:15,840 +最后一步是将标记组合在一起 +The last step is to group together tokens + +56 +00:02:15,840 --> 00:02:17,640 +对应于同一个实体。 +that corresponds to the same entity. + +57 +00:02:18,930 --> 00:02:21,540 +这就是为什么我们为每种类型的实体设置了两个标签, +This is why we had two labels for each type of entity, + +58 +00:02:21,540 --> 00:02:23,940 +例如 I-PER 和 B-PER。 +I-PER and B-PER for instance. + +59 +00:02:23,940 --> 00:02:25,530 +它让我们知道一个令牌是否 +It allows us to know if a token + +60 +00:02:25,530 --> 00:02:27,603 +与前一个实体处于同一实体中。 +is in the same entity as a previous one. + +61 +00:02:28,620 --> 00:02:29,850 +注意有两种方式 +Note that there are two ways + +62 +00:02:29,850 --> 00:02:32,490 +用于标记分类的标签。 +of labeling used for token classification. + +63 +00:02:32,490 --> 00:02:35,360 +一个,这里是粉红色的,使用 B-PER 标签 +One, in pink here, uses the B-PER label + +64 +00:02:35,360 --> 00:02:37,530 +在每个新实体的开头。 +at the beginning of each new entity. + +65 +00:02:37,530 --> 00:02:39,990 +但另一个蓝色的只使用它 +But the other in blue only uses it + +66 +00:02:39,990 --> 00:02:42,933 +分隔两个相邻的相同类型的实体。 +to separate two adjacent entities of the same types. + +67 +00:02:44,340 --> 00:02:46,560 +在这两种情况下,我们都可以标记一个新实体 +In both cases we can flag a new entity + +68 +00:02:46,560 --> 00:02:49,110 +每次我们看到一个新标签出现时, +each time we see a new label appearing, + +69 +00:02:49,110 --> 00:02:51,330 +带有 I 或 B 前缀。 +either with the I or B prefix. + +70 +00:02:51,330 --> 00:02:53,850 +然后,将以下所有标记为相同的标记 +Then, take all the following tokens labeled the same + +71 +00:02:53,850 --> 00:02:55,470 +带有 I 标志。 +with an I-flag. + +72 +00:02:55,470 --> 00:02:57,000 +这与偏移映射相结合 +This, coupled with the offset mapping + +73 +00:02:57,000 --> 00:02:59,010 +获取开始和结束字符 +to get the start and end characters + +74 +00:02:59,010 --> 00:03:01,560 +允许我们获得每个实体的文本跨度。 +allows us to get the span of texts for each entity. + +75 +00:03:02,869 --> 00:03:05,702 +(嘶嘶声) +(whooshing sound) + diff --git a/subtitles/zh-CN/47_inside-the-question-answering-pipeline-(pytorch).srt b/subtitles/zh-CN/47_inside-the-question-answering-pipeline-(pytorch).srt new file mode 100644 index 000000000..5ce8d0e78 --- /dev/null +++ b/subtitles/zh-CN/47_inside-the-question-answering-pipeline-(pytorch).srt @@ -0,0 +1,380 @@ +1 +00:00:04,230 --> 00:00:07,699 +- 让我们来看看问答管道的内部情况。 +- Let's have a look inside the question answering pipeline. + +2 +00:00:07,699 --> 00:00:10,680 +问答管道可以提取答案 +The question answering pipeline can extracts answers + +3 +00:00:10,680 --> 00:00:14,190 +从给定的上下文或文本段落中提出问题, +to questions from a given context or passage of text, + +4 +00:00:14,190 --> 00:00:16,540 +就像变形金刚回购自述文件的这一部分。 +like this part of the transformers repo README. + +5 +00:00:18,060 --> 00:00:20,310 +它也适用于很长的上下文, +It also works for very long contexts, + +6 +00:00:20,310 --> 00:00:23,850 +即使答案在最后,就像这个例子一样。 +even if the answer is at the very end, like in this example. + +7 +00:00:23,850 --> 00:00:25,400 +在本视频中,我们将了解原因。 +In this video, we will see why. + +8 +00:00:26,820 --> 00:00:29,460 +问答管道遵循相同的步骤 +The question answering pipeline follows the same steps + +9 +00:00:29,460 --> 00:00:31,050 +与其他管道一样: +as the other pipelines: + +10 +00:00:31,050 --> 00:00:34,200 +问题和上下文被标记为一个句子对, +the question and context are tokenized as a sentence pair, + +11 +00:00:34,200 --> 00:00:37,955 +提供给模型,然后应用一些后处理。 +fed to the model then some post-processing is applied. + +12 +00:00:37,955 --> 00:00:41,730 +令牌化和模型步骤应该很熟悉。 +The tokenization and model steps should be familiar. + +13 +00:00:41,730 --> 00:00:44,610 +我们使用适合问答的 auto 类 +We use the auto class suitable for question answering + +14 +00:00:44,610 --> 00:00:47,070 +而不是序列分类, +instead of sequence classification, + +15 +00:00:47,070 --> 00:00:49,392 +但与文本分类的一个关键区别 +but one key difference with text classification + +16 +00:00:49,392 --> 00:00:52,980 +是我们的模型输出两个名为 start logits 的张量 +is that our model outputs two tensors named start logits + +17 +00:00:52,980 --> 00:00:54,570 +并结束登录。 +and end logits. + +18 +00:00:54,570 --> 00:00:55,830 +这是为什么? +Why is that? + +19 +00:00:55,830 --> 00:00:57,930 +嗯,这就是模型找到答案的方式 +Well, this is the way the model finds the answer + +20 +00:00:57,930 --> 00:00:58,803 +到这个问题。 +to the question. + +21 +00:00:59,790 --> 00:01:02,130 +首先,让我们看一下模型输入。 +First, let's have a look at the model inputs. + +22 +00:01:02,130 --> 00:01:04,350 +它的数字与令牌化相关联 +Its numbers associated with the tokenization + +23 +00:01:04,350 --> 00:01:06,843 +问题后跟上下文 +of the question followed by the context + +24 +00:01:06,843 --> 00:01:09,723 +使用通常的 CLS 和 SEP 特殊令牌。 +with the usual CLS and SEP special tokens. + +25 +00:01:10,620 --> 00:01:13,320 +答案是那些令牌的一部分。 +The answer is a part of those tokens. + +26 +00:01:13,320 --> 00:01:15,510 +所以我们要求模型预测哪个令牌开始 +So we ask the model to predict which token starts + +27 +00:01:15,510 --> 00:01:17,373 +答案并结束答案。 +the answer and which ends the answer. + +28 +00:01:18,548 --> 00:01:19,650 +对于我们的两个 logit 输出, +For our two logit outputs, + +29 +00:01:19,650 --> 00:01:22,803 +理论标签是粉色和紫色的向量。 +the theoretical labels are the pink and purple vectors. + +30 +00:01:24,300 --> 00:01:26,430 +要将这些 logits 转换为概率, +To convert those logits into probabilities, + +31 +00:01:26,430 --> 00:01:28,436 +我们需要应用 SoftMax, +we will need to apply a SoftMax, + +32 +00:01:28,436 --> 00:01:30,360 +就像在文本分类管道中一样。 +like in the text classification pipeline. + +33 +00:01:30,360 --> 00:01:33,390 +我们只是屏蔽不属于上下文的标记 +We just mask the tokens that are not part of the context + +34 +00:01:33,390 --> 00:01:36,855 +在此之前,不屏蔽初始 CLS 令牌 +before doing that, leaving the initial CLS token unmasked + +35 +00:01:36,855 --> 00:01:39,303 +因为我们用它来预测一个不可能的答案。 +as we use it to predict an impossible answer. + +36 +00:01:40,267 --> 00:01:43,500 +这就是它在代码方面的样子。 +This is what it looks in terms of code. + +37 +00:01:43,500 --> 00:01:45,870 +我们使用一个大的负数作为掩码, +We use a large negative number for the masking, + +38 +00:01:45,870 --> 00:01:48,957 +因为它的指数将为零。 +since its exponential will then be zero. + +39 +00:01:48,957 --> 00:01:50,580 +现在,每个开始的概率 +Now, the probability for each start + +40 +00:01:50,580 --> 00:01:53,550 +和对应于可能答案的结束位置, +and end position corresponding to a possible answer, + +41 +00:01:53,550 --> 00:01:55,050 +我们给的分数就是产品 +we give a score that is the product + +42 +00:01:55,050 --> 00:01:57,630 +开始概率和结束概率 +of the start probabilities and end probabilities + +43 +00:01:57,630 --> 00:01:58,803 +在那些位置。 +at those positions. + +44 +00:02:00,120 --> 00:02:02,670 +当然,开始索引大于结束索引 +Of course, a start index greater than an end index + +45 +00:02:02,670 --> 00:02:04,503 +对应一个不可能的答案。 +corresponds to an impossible answer. + +46 +00:02:05,430 --> 00:02:07,080 +这是找到最佳分数的代码 +Here is the code to find the best score + +47 +00:02:07,080 --> 00:02:08,820 +一个可能的答案。 +for a possible answer. + +48 +00:02:08,820 --> 00:02:11,430 +一旦我们有了令牌的开始和结束位置, +Once we have the start and end positions of the tokens, + +49 +00:02:11,430 --> 00:02:14,130 +我们使用分词器提供的偏移量映射 +we use the offset mappings provided by our tokenizer + +50 +00:02:14,130 --> 00:02:16,950 +找到初始上下文中的字符范围, +to find the span of characters in the initial context, + +51 +00:02:16,950 --> 00:02:17,900 +并得到我们的答案。 +and get our answer. + +52 +00:02:19,470 --> 00:02:21,900 +现在,当上下文很长时,它可能会被截断 +Now, when the context is long, it might get truncated + +53 +00:02:21,900 --> 00:02:22,750 +由分词器。 +by the tokenizer. + +54 +00:02:23,760 --> 00:02:26,220 +这可能会导致部分答案,或者更糟的是, +This might result in part of the answer, or worse, + +55 +00:02:26,220 --> 00:02:28,113 +整个答案,被截断了。 +the whole answer, being truncated. + +56 +00:02:29,100 --> 00:02:31,050 +所以我们不丢弃截断的标记 +So we don't discard the truncated tokens + +57 +00:02:31,050 --> 00:02:33,330 +但与他们一起构建新功能。 +but build new features with them. + +58 +00:02:33,330 --> 00:02:35,994 +这些功能中的每一个都包含问题, +Each of those features contains the question, + +59 +00:02:35,994 --> 00:02:39,240 +然后是上下文中的一大块文本。 +then a chunk of text in the context. + +60 +00:02:39,240 --> 00:02:41,430 +如果我们采用不相交的文本块, +If we take disjoint chunks of texts, + +61 +00:02:41,430 --> 00:02:43,530 +我们最终可能会得到分裂的答案 +we might end up with the answer being split + +62 +00:02:43,530 --> 00:02:45,330 +两个特征之间。 +between two features. + +63 +00:02:45,330 --> 00:02:48,060 +因此,我们取而代之的是重叠的文本块, +So instead, we take overlapping chunks of texts, + +64 +00:02:48,060 --> 00:02:50,640 +确保至少一个块将完全包含 +to make sure at least one of the chunks will fully contain + +65 +00:02:50,640 --> 00:02:51,990 +问题的答案。 +the answer to the question. + +66 +00:02:52,830 --> 00:02:55,260 +标记器自动为我们完成所有这些 +The tokenizers do all of this for us automatically + +67 +00:02:55,260 --> 00:02:58,170 +使用 return overflowing tokens 选项。 +with the return overflowing tokens option. + +68 +00:02:58,170 --> 00:02:59,700 +步幅参数控制 +The stride argument controls + +69 +00:02:59,700 --> 00:03:02,070 +重叠标记的数量。 +the number of overlapping tokens. + +70 +00:03:02,070 --> 00:03:04,020 +这是我们非常长的上下文如何被截断的 +Here is how our very long context gets truncated + +71 +00:03:04,020 --> 00:03:05,850 +在两个有一些重叠的特征中。 +in two features with some overlap. + +72 +00:03:05,850 --> 00:03:07,950 +通过应用我们之前看到的相同的后处理 +By applying the same post-processing we saw before + +73 +00:03:07,950 --> 00:03:10,636 +对于每个特征,我们得到一个分数的答案 +for each feature, we get the answer with a score + +74 +00:03:10,636 --> 00:03:12,453 +对于他们每个人, +for each of them, + +75 +00:03:12,453 --> 00:03:14,910 +我们选择得分最高的答案 +and we take the answer with the best score + +76 +00:03:14,910 --> 00:03:16,203 +作为最终解决方案。 +as a final solution. + diff --git a/subtitles/zh-CN/48_inside-the-question-answering-pipeline-(tensorflow).srt b/subtitles/zh-CN/48_inside-the-question-answering-pipeline-(tensorflow).srt new file mode 100644 index 000000000..cdb7d661a --- /dev/null +++ b/subtitles/zh-CN/48_inside-the-question-answering-pipeline-(tensorflow).srt @@ -0,0 +1,410 @@ +1 +00:00:00,000 --> 00:00:03,417 +(轻过渡音乐) +(light transition music) + +2 +00:00:05,490 --> 00:00:08,440 +- 让我们来看看问答管道的内部情况。 +- Let's have a look inside the question answering pipeline. + +3 +00:00:09,780 --> 00:00:11,370 +问答管道 +The question answering pipeline + +4 +00:00:11,370 --> 00:00:13,710 +可以提取问题的答案 +can extract answers to questions + +5 +00:00:13,710 --> 00:00:16,020 +来自给定的上下文或文本段落 +from a given context or passage of text + +6 +00:00:16,020 --> 00:00:18,370 +就像变形金刚回购自述文件的这一部分。 +like this part of the Transformers repo README. + +7 +00:00:19,290 --> 00:00:21,180 +它也适用于很长的上下文, +It also works for very long context, + +8 +00:00:21,180 --> 00:00:24,720 +即使答案在最后,就像这个例子一样。 +even if the answer is at the very end, like in this example. + +9 +00:00:24,720 --> 00:00:26,223 +在本视频中,我们将了解原因。 +In this video, we'll see why. + +10 +00:00:27,840 --> 00:00:29,310 +问答管道 +The question answering pipeline + +11 +00:00:29,310 --> 00:00:32,130 +遵循与其他管道相同的步骤。 +follows the same steps as the other pipelines. + +12 +00:00:32,130 --> 00:00:35,550 +问题和上下文被标记为一个句子对, +The question and context are tokenized as a sentence pair, + +13 +00:00:35,550 --> 00:00:38,463 +提供给模型,然后应用一些后处理。 +fed to the model then some post-processing is applied. + +14 +00:00:39,540 --> 00:00:42,840 +所以标记化和模型步骤应该很熟悉。 +So tokenization and model steps should be familiar. + +15 +00:00:42,840 --> 00:00:45,000 +我们使用适合问答的 auto 类 +We use the auto class suitable for question answering + +16 +00:00:45,000 --> 00:00:47,460 +而不是序列分类, +instead of sequence classification, + +17 +00:00:47,460 --> 00:00:50,190 +但与文本分类的一个关键区别 +but one key difference with text classification + +18 +00:00:50,190 --> 00:00:52,380 +是我们的模型输出两个张量 +is that our model outputs two tensors + +19 +00:00:52,380 --> 00:00:55,230 +命名开始 logits 和结束 logits。 +named start logits and end logits. + +20 +00:00:55,230 --> 00:00:56,160 +这是为什么? +Why is that? + +21 +00:00:56,160 --> 00:00:58,170 +嗯,这就是模型找到答案的方式 +Well, this is the way the model finds the answer + +22 +00:00:58,170 --> 00:00:59,043 +到这个问题。 +to the question. + +23 +00:01:00,090 --> 00:01:02,610 +首先,让我们看一下模型输入。 +First, let's have a look at the model inputs. + +24 +00:01:02,610 --> 00:01:04,800 +它是与标记化相关的数字 +It's numbers associated with the tokenization + +25 +00:01:04,800 --> 00:01:05,850 +的问题, +of the question, + +26 +00:01:05,850 --> 00:01:07,753 +其次是上下文 +followed by the context + +27 +00:01:07,753 --> 00:01:10,233 +使用通常的 CLS 和 SEP 特殊令牌。 +with the usual CLS and SEP special tokens. + +28 +00:01:11,130 --> 00:01:13,203 +答案是那些令牌的一部分。 +The answer is a part of those tokens. + +29 +00:01:14,040 --> 00:01:15,330 +所以我们要求模型预测 +So we ask the model to predict + +30 +00:01:15,330 --> 00:01:17,040 +哪个标记开始回答 +which token starts the answer + +31 +00:01:17,040 --> 00:01:19,320 +并结束答案。 +and which ends the answer. + +32 +00:01:19,320 --> 00:01:20,910 +对于我们的两个 logit 输出, +For our two logit outputs, + +33 +00:01:20,910 --> 00:01:23,823 +理论标签是粉色和紫色的向量。 +the theoretical labels are the pink and purple vectors. + +34 +00:01:24,870 --> 00:01:26,700 +要将这些 logits 转换为概率, +To convert those logits into probabilities, + +35 +00:01:26,700 --> 00:01:28,596 +我们需要应用 SoftMax, +we will need to apply a SoftMax, + +36 +00:01:28,596 --> 00:01:31,020 +就像在文本分类管道中一样。 +like in the text classification pipeline. + +37 +00:01:31,020 --> 00:01:32,310 +我们只是屏蔽令牌 +We just mask the tokens + +38 +00:01:32,310 --> 00:01:35,940 +在这样做之前不属于上下文的一部分, +that are not part of the context before doing that, + +39 +00:01:35,940 --> 00:01:38,310 +不屏蔽初始 CLS 令牌 +leaving the initial CLS token unmasked + +40 +00:01:38,310 --> 00:01:40,773 +因为我们用它来预测一个不可能的答案。 +as we use it to predict an impossible answer. + +41 +00:01:41,940 --> 00:01:44,730 +这就是它在代码方面的样子。 +This is what it looks like in terms of code. + +42 +00:01:44,730 --> 00:01:47,340 +我们使用一个大的负数作为掩码 +We use a large negative number for the masking + +43 +00:01:47,340 --> 00:01:49,533 +因为它的指数将为零。 +since its exponential will then be zero. + +44 +00:01:50,850 --> 00:01:53,160 +现在每个开始和结束位置的概率 +Now the probability for each start and end position + +45 +00:01:53,160 --> 00:01:55,740 +对应一个可能的答案 +corresponding to a possible answer + +46 +00:01:55,740 --> 00:01:57,540 +会给一个分数是一个产品 +will give a score that is a product + +47 +00:01:57,540 --> 00:01:58,680 +开始概率 +of the start probabilities + +48 +00:01:58,680 --> 00:02:00,873 +并在这些位置结束概率。 +and end probabilities at those position. + +49 +00:02:01,920 --> 00:02:04,530 +当然,开始索引大于结束索引 +Of course, a start index greater than an end index + +50 +00:02:04,530 --> 00:02:06,330 +对应一个不可能的答案。 +corresponds to an impossible answer. + +51 +00:02:07,744 --> 00:02:09,510 +这是找到最佳分数的代码 +Here is the code to find the best score + +52 +00:02:09,510 --> 00:02:11,280 +一个可能的答案。 +for a possible answer. + +53 +00:02:11,280 --> 00:02:13,830 +一旦我们有了令牌的开始和结束位置, +Once we have the start and end position for the tokens, + +54 +00:02:13,830 --> 00:02:16,650 +我们使用分词器提供的偏移量映射 +we use the offset mappings provided by our tokenizer + +55 +00:02:16,650 --> 00:02:19,710 +找到初始上下文中的字符范围, +to find the span of characters in the initial context, + +56 +00:02:19,710 --> 00:02:20,810 +我们得到了答案。 +and we get our answer. + +57 +00:02:22,080 --> 00:02:23,700 +现在,当上下文很长时, +Now, when the context is long, + +58 +00:02:23,700 --> 00:02:25,977 +它可能会被分词器截断。 +it might get truncated by the tokenizer. + +59 +00:02:26,834 --> 00:02:29,790 +这可能会导致部分答案,或者更糟的是, +This might result in part of the answer, or worse, + +60 +00:02:29,790 --> 00:02:32,190 +整个答案,被截断了。 +the whole answer, being truncated. + +61 +00:02:32,190 --> 00:02:34,020 +所以我们不丢弃截断的标记 +So we don't discard the truncated tokens + +62 +00:02:34,020 --> 00:02:36,420 +但与他们一起构建新功能。 +but build new features with them. + +63 +00:02:36,420 --> 00:02:39,330 +这些功能中的每一个都包含问题, +Each of those features contains the question, + +64 +00:02:39,330 --> 00:02:42,150 +然后是上下文中的一大块文本。 +then a chunk of text in the context. + +65 +00:02:42,150 --> 00:02:44,520 +如果我们采用不相交的文本块, +If we take disjoint chunks of texts, + +66 +00:02:44,520 --> 00:02:45,840 +我们可能会得到答案 +we might end up with the answer + +67 +00:02:45,840 --> 00:02:47,733 +被分成两个特征。 +being split between two features. + +68 +00:02:48,720 --> 00:02:52,050 +因此,我们取而代之的是重叠的文本块 +So instead, we take overlapping chunks of text + +69 +00:02:52,050 --> 00:02:53,910 +确保至少其中一个块 +to make sure at least one of the chunks + +70 +00:02:53,910 --> 00:02:56,940 +将完整包含问题的答案。 +will fully contain the answer to the question. + +71 +00:02:56,940 --> 00:02:59,220 +所以,分词器会自动为我们完成所有这些 +So, tokenizers does all of this for us automatically + +72 +00:02:59,220 --> 00:03:01,920 +使用 return overflowing tokens 选项。 +with the return overflowing tokens option. + +73 +00:03:01,920 --> 00:03:02,753 +步幅论证 +The stride argument + +74 +00:03:02,753 --> 00:03:04,830 +控制重叠标记的数量。 +controls the number of overlapping tokens. + +75 +00:03:05,940 --> 00:03:07,740 +这是我们很长的上下文 +Here is how our very long context + +76 +00:03:07,740 --> 00:03:10,323 +在一些重叠的两个特征中被截断。 +gets truncated in two features with some overlap. + +77 +00:03:11,160 --> 00:03:12,720 +通过应用相同的后处理 +By applying the same post-processing + +78 +00:03:12,720 --> 00:03:14,850 +我们之前看到的每个功能, +we saw before for each feature, + +79 +00:03:14,850 --> 00:03:17,970 +我们得到每个分数的答案, +we get the answer with a score for each of them, + +80 +00:03:17,970 --> 00:03:19,920 +我们选择得分最高的答案 +and we take the answer with the best score + +81 +00:03:19,920 --> 00:03:21,303 +作为最终解决方案。 +as a final solution. + +82 +00:03:23,089 --> 00:03:26,506 +(轻过渡音乐) +(light transition music) + diff --git a/subtitles/zh-CN/49_what-is-normalization.srt b/subtitles/zh-CN/49_what-is-normalization.srt new file mode 100644 index 000000000..edff41bf9 --- /dev/null +++ b/subtitles/zh-CN/49_what-is-normalization.srt @@ -0,0 +1,465 @@ +1 +00:00:00,286 --> 00:00:02,869 +(微妙的爆炸) +(subtle blast) + +2 +00:00:04,694 --> 00:00:07,380 +- 在这个视频中,我们将一起看到 +- In this video, we will see together + +3 +00:00:07,380 --> 00:00:09,930 +什么是标准化组件 +what is the normalizer component + +4 +00:00:09,930 --> 00:00:13,023 +我们会在每个分词器的开头找到它。 +that we'd find at the beginning of each tokenizer. + +5 +00:00:14,550 --> 00:00:16,830 +归一化操作包括 +The normalization operation consists + +6 +00:00:16,830 --> 00:00:19,890 +在应用一系列规范化规则时 +in applying a succession of normalization rules + +7 +00:00:19,890 --> 00:00:20,853 +到原始文本。 +to the raw text. + +8 +00:00:21,870 --> 00:00:25,710 +我们选择规范化规则来去除文本中的噪音 +We choose normalization rules to remove noise in the text + +9 +00:00:25,710 --> 00:00:27,900 +这似乎对学习没有用 +which seem useless for the learning + +10 +00:00:27,900 --> 00:00:30,363 +以及使用我们的语言模型。 +and use of our language model. + +11 +00:00:33,090 --> 00:00:37,470 +让我们用不同的字体来看一个非常多样化的句子, +Let's take a very diverse sentence with different fonts, + +12 +00:00:37,470 --> 00:00:39,780 +大写和小写字符, +upper and lower case characters, + +13 +00:00:39,780 --> 00:00:43,083 +重音符号、标点符号和多个空格, +accents, punctuation and multiple spaces, + +14 +00:00:43,920 --> 00:00:46,683 +看看几个分词器是如何规范化它的。 +to see how several tokenizer normalize it. + +15 +00:00:48,488 --> 00:00:50,730 +来自 FNet 模型的分词器 +The tokenizer from the FNet model + +16 +00:00:50,730 --> 00:00:53,700 +用字体变体改变了字母 +has transformed the letter with font variants + +17 +00:00:53,700 --> 00:00:57,480 +或圈入他们的基本版本 +or circled into their basic version + +18 +00:00:57,480 --> 00:00:59,733 +并删除了多个空格。 +and has removed the multiple spaces. + +19 +00:01:00,960 --> 00:01:03,960 +现在,如果我们看一下规范化 +And now if we look at the normalization + +20 +00:01:03,960 --> 00:01:05,880 +使用 Retribert 的分词器, +with Retribert's tokenizer, + +21 +00:01:05,880 --> 00:01:08,010 +我们可以看到它保留了字符 +we can see that it keeps characters + +22 +00:01:08,010 --> 00:01:12,090 +具有多种字体变体并保留多个空格, +with several font variants and keeps the multiple spaces, + +23 +00:01:12,090 --> 00:01:14,223 +但它删除了所有的口音。 +but it removes all the accents. + +24 +00:01:16,170 --> 00:01:18,870 +如果我们继续测试这种标准化 +And if we continue to test this normalization + +25 +00:01:18,870 --> 00:01:23,040 +与模型相关的许多其他分词器 +of many other tokenizers associated to models + +26 +00:01:23,040 --> 00:01:25,110 +我们可以在集线器上找到, +that we can find on the Hub, + +27 +00:01:25,110 --> 00:01:28,833 +我们看到他们还提出了其他类型的正常化。 +we see that they also propose other kind of normalization. + +28 +00:01:33,900 --> 00:01:35,850 +使用快速分词器, +With the fast tokenizers, + +29 +00:01:35,850 --> 00:01:39,060 +很容易观察到选择的归一化 +it's very easy to observe the normalization chosen + +30 +00:01:39,060 --> 00:01:41,193 +对于当前加载的分词器。 +for the currently loaded tokenizer. + +31 +00:01:42,330 --> 00:01:46,140 +事实上,每个快速分词器的实例 +Indeed, each instance of a fast tokenizer + +32 +00:01:46,140 --> 00:01:48,030 +有一个底层分词器 +has an underlying tokenizer + +33 +00:01:48,030 --> 00:01:51,390 +来自存储的 HuggingFace Tokenizers 库 +from the HuggingFace Tokenizers library stored + +34 +00:01:51,390 --> 00:01:53,643 +在 backend_tokenizer 属性中。 +in the backend_tokenizer attribute. + +35 +00:01:54,690 --> 00:01:58,470 +这个对象本身有一个规范器属性 +This object has itself a normalizer attribute + +36 +00:01:58,470 --> 00:02:01,830 +我们可以使用 normalize_str 方法 +that we can use thanks to the normalize_str method + +37 +00:02:01,830 --> 00:02:03,153 +规范化一个字符串。 +to normalize a string. + +38 +00:02:04,560 --> 00:02:08,700 +因此,这种标准化非常实用, +It is thus very practical that this normalization, + +39 +00:02:08,700 --> 00:02:11,070 +这是在训练时使用的 +which was used at the time of the training + +40 +00:02:11,070 --> 00:02:12,903 +分词器的保存, +of the tokenizer was saved, + +41 +00:02:13,857 --> 00:02:16,200 +并且它会自动应用 +and that it applies automatically + +42 +00:02:16,200 --> 00:02:19,233 +当你要求训练有素的分词器对文本进行分词时。 +when you ask a trained tokenizer to tokenize a text. + +43 +00:02:21,000 --> 00:02:25,500 +例如,如果我们没有包含 albert 标准化器, +For example, if we hadn't included the albert normalizer, + +44 +00:02:25,500 --> 00:02:28,770 +我们会有很多未知的代币 +we would have had a lot of unknown tokens + +45 +00:02:28,770 --> 00:02:30,930 +通过标记这个句子 +by tokenizing this sentence + +46 +00:02:30,930 --> 00:02:33,213 +带有重音符号和大写字母。 +with accents and capital letters. + +47 +00:02:35,730 --> 00:02:38,370 +这种转变也可能是检测不到的 +This transformation can also be undetectable + +48 +00:02:38,370 --> 00:02:40,050 +带有简单的打印。 +with a simple print. + +49 +00:02:40,050 --> 00:02:42,810 +确实,请记住,对于计算机来说, +Indeed, keep in mind that for a computer, + +50 +00:02:42,810 --> 00:02:45,840 +文本只是连续的 0 和 1, +text is only a succession of 0 and 1, + +51 +00:02:45,840 --> 00:02:47,820 +碰巧不同的继承 +and it happens that different successions + +52 +00:02:47,820 --> 00:02:51,363 +0 和 1 呈现相同的打印字符。 +of 0 and 1 render the same printed character. + +53 +00:02:52,380 --> 00:02:56,403 +0 和 1 以 8 个为一组组成一个字节。 +The 0 and 1 go in group of 8 to form a byte. + +54 +00:02:57,480 --> 00:03:00,690 +然后计算机必须解码这个字节序列 +The computer must then decode this sequence of bytes + +55 +00:03:00,690 --> 00:03:02,493 +成一系列代码点。 +into a sequence of code points. + +56 +00:03:04,530 --> 00:03:09,530 +在我们的示例中,2 个字节使用 UTF-8 解码 +In our example, the 2 bytes is decoded using UTF-8 + +57 +00:03:09,900 --> 00:03:11,403 +成一个单一的代码点。 +into a single code point. + +58 +00:03:12,450 --> 00:03:15,090 +然后 unicode 标准允许我们 +The unicode standard then allows us + +59 +00:03:15,090 --> 00:03:18,191 +找到与此代码点对应的字符, +to find the character corresponding to this code point, + +60 +00:03:18,191 --> 00:03:20,283 +c 音符。 +the c cedilla. + +61 +00:03:21,499 --> 00:03:23,790 +让我们重复同样的操作 +Let's repeat the same operation + +62 +00:03:23,790 --> 00:03:26,577 +有了这个由 3 个字节组成的新序列,。 +with this new sequence composed of 3 bytes,. + +63 +00:03:27,420 --> 00:03:30,543 +这次转化为两个码点, +This time it is transformed into two code points, + +64 +00:03:31,410 --> 00:03:35,280 +这也对应于 c cedilla 字符。 +which also correspond to the c cedilla character. + +65 +00:03:35,280 --> 00:03:36,780 +它实际上是组成 +It is in fact the composition + +66 +00:03:36,780 --> 00:03:39,810 +unicode 拉丁文小写字母 C +of the unicode Latin Small Letter C + +67 +00:03:39,810 --> 00:03:42,240 +和组合的音符。 +and the combining cedilla. + +68 +00:03:42,240 --> 00:03:45,000 +但这很烦人,因为在我们看来 +But it's annoying because what appears to us + +69 +00:03:45,000 --> 00:03:46,680 +成为一个单一的角色 +to be a single character + +70 +00:03:46,680 --> 00:03:49,653 +对于计算机来说完全不是一回事。 +is not at all the same thing for the computer. + +71 +00:03:52,470 --> 00:03:57,240 +幸好有 unicode 标准化标准 +Fortunately, there are unicode standardization standards + +72 +00:03:57,240 --> 00:04:02,130 +称为 NFC、NFD、NFKC 或 NFKD +known as NFC, NFD, NFKC or NFKD + +73 +00:04:02,130 --> 00:04:04,893 +这允许消除其中的一些差异。 +that allow erasing some of these differences. + +74 +00:04:05,730 --> 00:04:08,223 +这些标准通常由分词器使用。 +These standards are often used by tokenizers. + +75 +00:04:09,900 --> 00:04:12,090 +在所有这些前面的例子中, +On all these previous examples, + +76 +00:04:12,090 --> 00:04:15,510 +即使规范化改变了文本的外观, +even if the normalizations changed the look of the text, + +77 +00:04:15,510 --> 00:04:17,970 +他们没有改变内容; +they did not change the content; + +78 +00:04:17,970 --> 00:04:19,177 +你仍然可以阅读, +you could still read, + +79 +00:04:19,177 --> 00:04:21,987 +“Hello world,让我们规范一下这句话。” +"Hello world, let's normalize this sentence." + +80 +00:04:22,980 --> 00:04:25,980 +但是,你必须知道一些规范化 +However, you must be aware that some normalizations + +81 +00:04:25,980 --> 00:04:30,363 +如果他们不适应他们的语料库,可能会非常有害。 +can be very harmful if they are not adapted to their corpus. + +82 +00:04:31,620 --> 00:04:34,387 +例如,如果你使用法语句子, +For example, if you take the French sentence, + +83 +00:04:34,387 --> 00:04:38,790 +“Un pere indigne”,意思是 “愤怒的父亲”, +"Un pere indigne," which means "An indignant father," + +84 +00:04:38,790 --> 00:04:42,510 +并使用 bert-base-uncase 分词器对其进行规范化 +and normalize it with the bert-base-uncase tokenizer + +85 +00:04:42,510 --> 00:04:44,313 +这消除了口音, +which removes the accent, + +86 +00:04:45,150 --> 00:04:48,000 +然后句子变成 “Un pere indigne” +then the sentence becomes "Un pere indigne" + +87 +00:04:48,000 --> 00:04:49,707 +意思是 “一个不称职的父亲”。 +which means "An unworthy father". + +88 +00:04:53,460 --> 00:04:56,760 +如果你观看此视频是为了构建自己的分词器, +If you watched this video to build your own tokenizer, + +89 +00:04:56,760 --> 00:04:59,610 +选择与否没有绝对的规则 +there are no absolute rules to choose or not + +90 +00:04:59,610 --> 00:05:02,970 +新分词器的规范化, +a normalization for a new tokenizer, + +91 +00:05:02,970 --> 00:05:06,210 +但我建议你花时间选择它们 +but I advise you to take the time to select them + +92 +00:05:06,210 --> 00:05:10,743 +这样它们就不会让你丢失重要信息。 +so that they do not make you lose important information. + +93 +00:05:12,296 --> 00:05:14,879 +(微妙的爆炸) +(subtle blast) + diff --git a/subtitles/zh-CN/50_what-is-pre-tokenization.srt b/subtitles/zh-CN/50_what-is-pre-tokenization.srt new file mode 100644 index 000000000..6b2870fe3 --- /dev/null +++ b/subtitles/zh-CN/50_what-is-pre-tokenization.srt @@ -0,0 +1,220 @@ +1 +00:00:05,550 --> 00:00:08,910 +- 标记化管道涉及几个步骤 +- The tokenization pipeline involves several steps + +2 +00:00:08,910 --> 00:00:11,073 +将原始文本转换为数字。 +that converts raw text into numbers. + +3 +00:00:12,180 --> 00:00:14,280 +在这段视频中,我们将看到会发生什么 +In this video, we will see what happens + +4 +00:00:14,280 --> 00:00:16,293 +在预标记化步骤中。 +during the pre-tokenization step. + +5 +00:00:18,390 --> 00:00:22,110 +pre-tokenization 操作是执行的操作 +The pre-tokenization operation is the operation performed + +6 +00:00:22,110 --> 00:00:24,630 +文本归一化后 +after the normalization of the text + +7 +00:00:24,630 --> 00:00:27,633 +在应用标记化算法之前。 +and before the application of the tokenization algorithm. + +8 +00:00:29,112 --> 00:00:31,110 +此步骤包括应用规则 +This step consists in applying rules + +9 +00:00:31,110 --> 00:00:32,550 +不需要学习的 +that do not need to be learned + +10 +00:00:32,550 --> 00:00:34,563 +执行文本的第一部分。 +to perform a first division of the text. + +11 +00:00:38,160 --> 00:00:41,310 +让我们看看几个分词器是如何 +Let's look at how several tokenizers + +12 +00:00:41,310 --> 00:00:43,143 +在此示例中进行预标记。 +pre-tokenize in this example. + +13 +00:00:46,200 --> 00:00:50,820 +gpt2 pre-tokenization 在空格上划分文本 +The gpt2 pre-tokenization divides the text on spaces + +14 +00:00:50,820 --> 00:00:55,820 +和一些标点符号,但不是撇号。 +and some punctuation, but not on the apostrophe. + +15 +00:00:57,750 --> 00:01:01,170 +我们还注意到空格已被替换 +We also notice that spaces have been replaced + +16 +00:01:01,170 --> 00:01:03,813 +上面有一个点的大写字母 G。 +by capital G with a dot above. + +17 +00:01:07,170 --> 00:01:09,540 +Albert 的预标记化将文本划分 +Albert's pre-tokenization divides the text + +18 +00:01:09,540 --> 00:01:11,043 +在空间层面, +at the level of spaces, + +19 +00:01:11,970 --> 00:01:15,300 +在句子的开头加一个空格, +adds a space at the beginning of the sentence, + +20 +00:01:15,300 --> 00:01:18,873 +并用特殊下划线替换空格。 +and replaces spaces with a special underscore. + +21 +00:01:20,580 --> 00:01:24,780 +最后,Bert 的 pre-tokenization 对文本进行分词 +Finally, Bert's pre-tokenization divides the text + +22 +00:01:24,780 --> 00:01:28,083 +在标点符号和空格的级别。 +at the level of punctuation and spaces. + +23 +00:01:28,920 --> 00:01:31,260 +但与之前的分词器不同, +But unlike the previous tokenizers, + +24 +00:01:31,260 --> 00:01:33,780 +空间没有变换 +spaces are not transformed + +25 +00:01:33,780 --> 00:01:37,293 +并集成到使用此预标记器生成的令牌中。 +and integrated into tokens produced with this pre-tokenizer. + +26 +00:01:40,080 --> 00:01:42,120 +通过这三个例子, +Through this three example, + +27 +00:01:42,120 --> 00:01:45,330 +我们可以观察到两种主要的操作类型 +we could observe the two main type of operation + +28 +00:01:45,330 --> 00:01:47,073 +预标记化带来的; +brought by the pre-tokenization; + +29 +00:01:48,420 --> 00:01:49,900 +对案文进行一些改动 +some change on the text + +30 +00:01:50,820 --> 00:01:54,180 +并将字符串划分为标记 +and the division of the string into tokens + +31 +00:01:54,180 --> 00:01:56,043 +可以与单词相关联。 +that can be associated to words. + +32 +00:01:59,430 --> 00:02:04,230 +最后,快速分词器的后端分词器 +Finally, the backend tokenizer of the fast tokenizers + +33 +00:02:04,230 --> 00:02:07,680 +还允许测试预标记化操作 +also allows to test the pre-tokenization operation + +34 +00:02:07,680 --> 00:02:11,253 +非常容易,这要归功于它的 pre_tokenize_str 方法。 +very easily, thanks to its pre_tokenize_str method. + +35 +00:02:12,630 --> 00:02:14,970 +我们注意到这个操作的输出 +We notice that the output of this operation + +36 +00:02:14,970 --> 00:02:18,450 +由令牌和偏移量组成, +is composed of both tokens and offsets, + +37 +00:02:18,450 --> 00:02:21,960 +允许将标记链接到它在文本中的位置 +which allow to link the tokens to its position in the text + +38 +00:02:21,960 --> 00:02:23,943 +在方法的输入中给出。 +given in input of the method. + +39 +00:02:25,650 --> 00:02:28,860 +此操作定义了最大的令牌 +This operation defines the largest tokens + +40 +00:02:28,860 --> 00:02:31,740 +可以通过标记化产生, +that can be produced by the tokenization, + +41 +00:02:31,740 --> 00:02:36,090 +或者换句话说,子令牌的障碍 +or in those words, the barriers of the sub-tokens + +42 +00:02:36,090 --> 00:02:37,653 +届时将生产。 +which will be produced then. + +43 +00:02:40,050 --> 00:02:41,850 +这就是特性的全部 +And that's all for the characteristic + +44 +00:02:41,850 --> 00:02:43,203 +预分词器。 +of the pre-tokenizers. + diff --git a/subtitles/zh-CN/51_byte-pair-encoding-tokenization.srt b/subtitles/zh-CN/51_byte-pair-encoding-tokenization.srt new file mode 100644 index 000000000..513b464a9 --- /dev/null +++ b/subtitles/zh-CN/51_byte-pair-encoding-tokenization.srt @@ -0,0 +1,435 @@ +1 +00:00:00,125 --> 00:00:05,125 +(空气呼啸) +(air whooshing) + +2 +00:00:05,190 --> 00:00:06,720 +- 你是在正确的地方 +- You are at the right place + +3 +00:00:06,720 --> 00:00:10,464 +如果你想了解什么是字节对编码 +if you want to understand what the Byte Pair Encoding + +4 +00:00:10,464 --> 00:00:13,263 +子词标记化算法是, +subword tokenization algorithm is, + +5 +00:00:14,160 --> 00:00:15,505 +如何训练它 +how to train it + +6 +00:00:15,505 --> 00:00:17,790 +以及文本的标记化是如何完成的 +and how the tokenization of a text is done + +7 +00:00:17,790 --> 00:00:19,107 +用这个算法。 +with this algorithm. + +8 +00:00:21,417 --> 00:00:22,920 +BPE 算法 +The BPE algorithm + +9 +00:00:22,920 --> 00:00:26,820 +最初被提出作为文本压缩算法 +was initially proposed as a text compression algorithm + +10 +00:00:26,820 --> 00:00:28,770 +但它也非常适合 +but it is also very well suited + +11 +00:00:28,770 --> 00:00:31,143 +作为你的语言模型的分词器。 +as a tokenizer for your language models. + +12 +00:00:32,910 --> 00:00:34,890 +BPE 的思想是分词 +The idea of BPE is to divide words + +13 +00:00:34,890 --> 00:00:36,933 +进入一系列 “子词单元” +into a sequence of'subword units' + +14 +00:00:38,100 --> 00:00:41,970 +哪些是参考语料库中频繁出现的单位 +which are units that appear frequently in a reference corpus + +15 +00:00:41,970 --> 00:00:44,613 +也就是我们用来训练它的语料库。 +which is, the corpus we used to train it. + +16 +00:00:46,701 --> 00:00:49,083 +BPE 分词器是如何训练的? +How is a BPE tokenizer trained? + +17 +00:00:50,100 --> 00:00:53,340 +首先,我们必须得到一个文本语料库。 +First of all, we have to get a corpus of texts. + +18 +00:00:53,340 --> 00:00:56,940 +我们不会在这个原始文本上训练我们的分词器 +We will not train our tokenizer on this raw text + +19 +00:00:56,940 --> 00:00:59,490 +但我们首先将其标准化 +but we will first normalize it + +20 +00:00:59,490 --> 00:01:00,873 +然后对其进行预标记。 +then pre-tokenize it. + +21 +00:01:01,890 --> 00:01:03,240 +作为预标记化 +As the pre-tokenization + +22 +00:01:03,240 --> 00:01:05,790 +将文本分成单词列表, +divides the text into a list of words, + +23 +00:01:05,790 --> 00:01:08,400 +我们可以用另一种方式表示我们的语料库 +we can represent our corpus in another way + +24 +00:01:08,400 --> 00:01:10,350 +通过收集相同的词 +by gathering together the same words + +25 +00:01:10,350 --> 00:01:12,450 +并通过维护一个柜台, +and by maintaining a counter, + +26 +00:01:12,450 --> 00:01:14,223 +这里用蓝色表示。 +here represented in blue. + +27 +00:01:17,340 --> 00:01:19,860 +要了解培训的工作原理, +To understand how the training works, + +28 +00:01:19,860 --> 00:01:23,730 +我们认为这个玩具语料库由以下单词组成: +we consider this toy corpus composed of the following words: + +29 +00:01:23,730 --> 00:01:28,203 +huggingface, hugging, hug, hugger, 等 +huggingface, hugging, hug, hugger, etc. + +30 +00:01:29,100 --> 00:01:32,640 +BPE 是一种从初始词汇表开始的算法 +BPE is an algorithm that starts with an initial vocabulary + +31 +00:01:32,640 --> 00:01:35,583 +然后将其增加到所需的大小。 +and then increases it to the desired size. + +32 +00:01:36,450 --> 00:01:38,460 +为了建立初始词汇表, +To build the initial vocabulary, + +33 +00:01:38,460 --> 00:01:41,550 +我们从分离语料库的每个词开始 +we start by separating each word of the corpus + +34 +00:01:41,550 --> 00:01:44,253 +组成它们的基本单元列表, +into a list of elementary units that compose them, + +35 +00:01:45,210 --> 00:01:47,013 +在这里,人物。 +here, the characters. + +36 +00:01:50,850 --> 00:01:54,310 +我们在词汇表中列出所有出现的字符 +We list in our vocabulary all the characters that appear + +37 +00:01:55,218 --> 00:01:58,053 +这将构成我们最初的词汇表。 +and that will constitute our initial vocabulary. + +38 +00:02:00,420 --> 00:02:02,523 +现在让我们看看如何增加它。 +Let's now see how to increase it. + +39 +00:02:05,520 --> 00:02:08,250 +我们回到我们分裂的语料库, +We return to our split corpus, + +40 +00:02:08,250 --> 00:02:11,340 +我们将逐字逐句 +we will go through the words one by one + +41 +00:02:11,340 --> 00:02:14,313 +并计算令牌对的所有出现次数。 +and count all the occurrences of token pairs. + +42 +00:02:15,450 --> 00:02:18,397 +第一对由标记 “h” 和 “u” 组成, +The first pair is composed of the token 'h' and 'u', + +43 +00:02:20,130 --> 00:02:23,067 +第二个 'u' 和 'g', +the second 'u' and 'g', + +44 +00:02:23,067 --> 00:02:26,253 +我们继续这样,直到我们有完整的列表。 +and we continue like that until we have the complete list. + +45 +00:02:35,580 --> 00:02:37,724 +一旦我们知道所有的对 +Once we know all the pairs + +46 +00:02:37,724 --> 00:02:40,140 +以及它们出现的频率, +and their frequency of appearance, + +47 +00:02:40,140 --> 00:02:42,940 +我们将选择出现频率最高的那个。 +we will choose the one that appears the most frequently. + +48 +00:02:44,220 --> 00:02:47,697 +这是由字母 “l” 和 “e” 组成的一对。 +Here it is the pair composed of the letters 'l' and 'e'. + +49 +00:02:51,930 --> 00:02:53,590 +我们注意到我们的第一个合并规则 +We note our first merging rule + +50 +00:02:54,593 --> 00:02:57,243 +然后我们将新标记添加到我们的词汇表中。 +and we add the new token to our vocabulary. + +51 +00:03:00,330 --> 00:03:04,260 +然后我们可以将此合并规则应用于我们的拆分。 +We can then apply this merging rule to our splits. + +52 +00:03:04,260 --> 00:03:07,350 +你可以看到我们已经合并了所有的令牌对 +You can see that we have merged all the pairs of tokens + +53 +00:03:07,350 --> 00:03:09,793 +由标记 “l” 和 “e” 组成。 +composed of the tokens 'l' and 'e'. + +54 +00:03:14,008 --> 00:03:18,150 +现在,我们只需要重现相同的步骤 +And now, we just have to reproduce the same steps + +55 +00:03:18,150 --> 00:03:19,353 +与我们的新分裂。 +with our new splits. + +56 +00:03:21,750 --> 00:03:23,460 +我们计算出现频率 +We calculate the frequency of occurrence + +57 +00:03:23,460 --> 00:03:25,023 +每对标记, +of each pair of tokens, + +58 +00:03:27,990 --> 00:03:30,603 +我们选择频率最高的一对, +we select the pair with the highest frequency, + +59 +00:03:32,190 --> 00:03:34,083 +我们在我们的合并规则中注意到它, +we note it in our merge rules, + +60 +00:03:36,000 --> 00:03:39,360 +我们将新的标记添加到词汇表中 +we add the new one token the vocabulary + +61 +00:03:39,360 --> 00:03:41,880 +然后我们合并所有的标记对 +and then we merge all the pairs of tokens + +62 +00:03:41,880 --> 00:03:46,503 +由标记 “le” 和 “a” 组成,进入我们的拆分。 +composed of the token 'le' and 'a' into our splits. + +63 +00:03:50,323 --> 00:03:51,960 +我们可以重复这个操作 +And we can repeat this operation + +64 +00:03:51,960 --> 00:03:54,843 +直到我们达到所需的词汇量。 +until we reach the desired vocabulary size. + +65 +00:04:05,671 --> 00:04:10,671 +在这里,当我们的词汇量达到 21 个标记时,我们就停止了。 +Here, we stopped when our vocabulary reached 21 tokens. + +66 +00:04:11,040 --> 00:04:13,920 +我们现在可以看到,比起训练开始时 +We can see now that the words of our corpus + +67 +00:04:13,920 --> 00:04:17,040 +我们的语料库中的单词 +are now divided into far fewer tokens + +68 +00:04:17,040 --> 00:04:20,280 +被分成了更少的 tokens +than at the beginning of the training. + +69 +00:04:20,280 --> 00:04:21,720 +而我们的算法 +And that our algorithm + +70 +00:04:21,720 --> 00:04:24,990 +学会了部首 “hug” 和 “learn” +has learned the radicals 'hug' and 'learn' + +71 +00:04:24,990 --> 00:04:27,537 +以及动词结尾 “ing”。 +and also the verbal ending 'ing'. + +72 +00:04:29,880 --> 00:04:32,160 +现在我们已经学会了我们的词汇 +Now that we have learned our vocabulary + +73 +00:04:32,160 --> 00:04:35,943 +和合并规则,我们可以标记新文本。 +and merging rules, we can tokenize new texts. + +74 +00:04:37,980 --> 00:04:39,210 +例如, +For example, + +75 +00:04:39,210 --> 00:04:41,160 +如果我们想标记 “hugs” 这个词, +if we want to tokenize the word 'hugs', + +76 +00:04:42,960 --> 00:04:46,680 +首先我们将它分成基本单元 +first we'll divide it into elementary units + +77 +00:04:46,680 --> 00:04:48,843 +所以它变成了一个字符序列。 +so it became a sequence of characters. + +78 +00:04:50,040 --> 00:04:52,020 +然后,我们将通过我们的合并规则 +Then, we'll go through our merge rules + +79 +00:04:52,020 --> 00:04:54,690 +直到我们有一个我们可以申请。 +until we have one we can apply. + +80 +00:04:54,690 --> 00:04:57,930 +在这里,我们可以合并字母 “h” 和 “u”。 +Here, we can merge the letters 'h' and 'u'. + +81 +00:04:57,930 --> 00:05:01,467 +在这里,我们可以合并 2 个令牌以获得新令牌 “hug”。 +And here, we can merge 2 tokens to get the new token 'hug'. + +82 +00:05:02,400 --> 00:05:05,760 +当我们到达合并规则的末尾时, +When we get to the end of our merge rules, + +83 +00:05:05,760 --> 00:05:07,563 +标记化完成。 +the tokenization is finished. + +84 +00:05:10,650 --> 00:05:11,727 +就是这样。 +And that's it. + +85 +00:05:12,846 --> 00:05:14,850 +我希望现在的 BPE 算法 +I hope that now the BPE algorithm + +86 +00:05:14,850 --> 00:05:16,413 +没有更多的秘密给你! +has no more secret for you! + +87 +00:05:17,739 --> 00:05:20,406 +(空气呼啸) +(air whooshing) + diff --git a/subtitles/zh-CN/52_wordpiece-tokenization.srt b/subtitles/zh-CN/52_wordpiece-tokenization.srt new file mode 100644 index 000000000..26873d619 --- /dev/null +++ b/subtitles/zh-CN/52_wordpiece-tokenization.srt @@ -0,0 +1,320 @@ +1 +00:00:00,151 --> 00:00:02,818 +(空气呼啸) +(air whooshing) + +2 +00:00:05,520 --> 00:00:08,370 +- 一起来看看什么是训练策略 +- Let's see together what is the training strategy + +3 +00:00:08,370 --> 00:00:11,851 +WordPiece 算法及其执行方式 +of the WordPiece algorithm, and how it performs + +4 +00:00:11,851 --> 00:00:15,150 +一旦训练,文本的标记化。 +the tokenization of a text, once trained. + +5 +00:00:19,351 --> 00:00:23,580 +WordPiece 是 Google 推出的一种分词算法。 +WordPiece is a tokenization algorithm introduced by Google. + +6 +00:00:23,580 --> 00:00:25,653 +例如,它被 BERT 使用。 +It is used, for example, by BERT. + +7 +00:00:26,640 --> 00:00:28,020 +据我们所知, +To our knowledge, + +8 +00:00:28,020 --> 00:00:31,590 +WordPiece 的代码还没有开源。 +the code of WordPiece has not been open source. + +9 +00:00:31,590 --> 00:00:33,510 +所以我们根据我们的解释 +So we base our explanations + +10 +00:00:33,510 --> 00:00:36,903 +根据我们自己对已发表文献的解释。 +on our own interpretation of the published literature. + +11 +00:00:42,090 --> 00:00:44,883 +那么,WordPiece 的训练策略是怎样的呢? +So, what is the training strategy of WordPiece? + +12 +00:00:46,200 --> 00:00:48,663 +与 BPE 算法类似, +Similarly to the BPE algorithm, + +13 +00:00:48,663 --> 00:00:52,380 +WordPiece 从建立初始词汇表开始 +WordPiece starts by establishing an initial vocabulary + +14 +00:00:52,380 --> 00:00:54,660 +由基本单元组成, +composed of elementary units, + +15 +00:00:54,660 --> 00:00:58,773 +然后将这个词汇量增加到所需的大小。 +and then increases this vocabulary to the desired size. + +16 +00:00:59,970 --> 00:01:01,950 +为了建立初始词汇表, +To build the initial vocabulary, + +17 +00:01:01,950 --> 00:01:04,920 +我们划分训练语料库中的每个单词 +we divide each word in the training corpus + +18 +00:01:04,920 --> 00:01:07,443 +进入构成它的字母序列。 +into the sequence of letters that make it up. + +19 +00:01:08,430 --> 00:01:11,820 +如你所见,有一个小细节。 +As you can see, there is a small subtlety. + +20 +00:01:11,820 --> 00:01:14,190 +我们在字母前添加两个标签 +We add two hashtags in front of the letters + +21 +00:01:14,190 --> 00:01:16,083 +那不开始一个词。 +that do not start a word. + +22 +00:01:17,190 --> 00:01:20,430 +通过每个基本单元只保留一次出现, +By keeping only one occurrence per elementary unit, + +23 +00:01:20,430 --> 00:01:23,313 +我们现在有了最初的词汇表。 +we now have our initial vocabulary. + +24 +00:01:26,580 --> 00:01:29,823 +我们将列出语料库中所有现有的对。 +We will list all the existing pairs in our corpus. + +25 +00:01:30,990 --> 00:01:32,640 +一旦我们有了这份清单, +Once we have this list, + +26 +00:01:32,640 --> 00:01:35,253 +我们将为每一对计算一个分数。 +we will calculate a score for each of these pairs. + +27 +00:01:36,630 --> 00:01:38,400 +至于 BPE 算法, +As for the BPE algorithm, + +28 +00:01:38,400 --> 00:01:40,750 +我们将选择得分最高的一对。 +we will select the pair with the highest score. + +29 +00:01:43,260 --> 00:01:44,340 +举个例子, +Taking for example, + +30 +00:01:44,340 --> 00:01:47,343 +第一对由字母 H 和 U 组成。 +the first pair composed of the letters H and U. + +31 +00:01:48,510 --> 00:01:51,390 +一对的分数简单地等于频率 +The score of a pair is simply equal to the frequency + +32 +00:01:51,390 --> 00:01:54,510 +一对的外观除以产品 +of appearance of the pair, divided by the product + +33 +00:01:54,510 --> 00:01:57,330 +第一个标记出现的频率, +of the frequency of appearance of the first token, + +34 +00:01:57,330 --> 00:02:00,063 +通过第二个标记的出现频率。 +by the frequency of appearance of the second token. + +35 +00:02:01,260 --> 00:02:05,550 +因此,在一对固定的出现频率下, +Thus, at a fixed frequency of appearance of the pair, + +36 +00:02:05,550 --> 00:02:09,913 +如果该对的子部分在语料库中非常频繁, +if the subparts of the pair are very frequent in the corpus, + +37 +00:02:09,913 --> 00:02:11,823 +那么这个分数就会降低。 +then this score will be decreased. + +38 +00:02:13,140 --> 00:02:17,460 +在我们的示例中,HU 对出现了四次, +In our example, the pair HU appears four times, + +39 +00:02:17,460 --> 00:02:22,460 +字母 H 四次,字母 U 四次。 +the letter H four times, and the letter U four times. + +40 +00:02:24,030 --> 00:02:26,733 +这给了我们 0.25 的分数。 +This gives us a score of 0.25. + +41 +00:02:28,410 --> 00:02:30,960 +现在我们知道如何计算这个分数了, +Now that we know how to calculate this score, + +42 +00:02:30,960 --> 00:02:33,360 +我们可以为所有对做。 +we can do it for all pairs. + +43 +00:02:33,360 --> 00:02:35,217 +我们现在可以添加到词汇表中 +We can now add to the vocabulary + +44 +00:02:35,217 --> 00:02:38,973 +得分最高的一对,当然是在合并之后。 +the pair with the highest score, after merging it of course. + +45 +00:02:40,140 --> 00:02:43,863 +现在我们可以将同样的融合应用于我们的拆分语料库。 +And now we can apply this same fusion to our split corpus. + +46 +00:02:45,780 --> 00:02:47,490 +你可以想象, +As you can imagine, + +47 +00:02:47,490 --> 00:02:50,130 +我们只需要重复相同的操作 +we just have to repeat the same operations + +48 +00:02:50,130 --> 00:02:53,013 +直到我们拥有所需大小的词汇表。 +until we have the vocabulary at the desired size. + +49 +00:02:54,000 --> 00:02:55,800 +让我们再看几个步骤 +Let's look at a few more steps + +50 +00:02:55,800 --> 00:02:58,113 +看看我们词汇的演变, +to see the evolution of our vocabulary, + +51 +00:02:58,957 --> 00:03:01,773 +以及劈叉长度的演变。 +and also the evolution of the length of the splits. + +52 +00:03:06,390 --> 00:03:09,180 +现在我们对自己的词汇感到满意了, +And now that we are happy with our vocabulary, + +53 +00:03:09,180 --> 00:03:12,663 +你可能想知道如何使用它来标记文本。 +you are probably wondering how to use it to tokenize a text. + +54 +00:03:13,830 --> 00:03:17,640 +假设我们想要标记 “huggingface” 这个词。 +Let's say we want to tokenize the word "huggingface". + +55 +00:03:17,640 --> 00:03:20,310 +WordPiece 遵循这些规则。 +WordPiece follows these rules. + +56 +00:03:20,310 --> 00:03:22,530 +我们将寻找最长的令牌 +We will look for the longest possible token + +57 +00:03:22,530 --> 00:03:24,960 +在单词的开头。 +at the beginning of the word. + +58 +00:03:24,960 --> 00:03:28,920 +然后我们重新开始我们的话的剩余部分, +Then we start again on the remaining part of our word, + +59 +00:03:28,920 --> 00:03:31,143 +依此类推,直到我们到达终点。 +and so on until we reach the end. + +60 +00:03:32,100 --> 00:03:35,973 +就是这样。 Huggingface 分为四个子令牌。 +And that's it. Huggingface is divided into four sub-tokens. + +61 +00:03:37,200 --> 00:03:39,180 +本视频即将结束。 +This video is about to end. + +62 +00:03:39,180 --> 00:03:41,370 +我希望它能帮助你更好地理解 +I hope it helped you to understand better + +63 +00:03:41,370 --> 00:03:43,653 +工作的背后是什么,WordPiece。 +what is behind the work, WordPiece. + +64 +00:03:45,114 --> 00:03:47,864 +(空气呼啸) +(air whooshing) + diff --git a/subtitles/zh-CN/53_unigram-tokenization.srt b/subtitles/zh-CN/53_unigram-tokenization.srt new file mode 100644 index 000000000..48f3099f5 --- /dev/null +++ b/subtitles/zh-CN/53_unigram-tokenization.srt @@ -0,0 +1,800 @@ +1 +00:00:00,000 --> 00:00:02,667 +(空气呼啸) +(air whooshing) + +2 +00:00:05,310 --> 00:00:06,420 +- 在这个视频中, +- In this video, + +3 +00:00:06,420 --> 00:00:09,881 +我们将一起研究 “Unigram 语言模型” +we will study together 'the Unigram Language Model + +4 +00:00:09,881 --> 00:00:13,288 +子词标记化算法 '。 +subword tokenization algorithm'. + +5 +00:00:13,288 --> 00:00:15,567 +整体训练策略 +The overall training strategy + +6 +00:00:15,567 --> 00:00:18,450 +Unigram 语言模型分词器 +of a Unigram Language Model tokenizer + +7 +00:00:18,450 --> 00:00:21,480 +是从一个非常大的词汇量开始 +is to start with a very large vocabulary + +8 +00:00:21,480 --> 00:00:24,240 +然后在每次迭代中删除标记 +and then to remove tokens at each iteration + +9 +00:00:24,240 --> 00:00:27,300 +直到我们达到所需的大小。 +until we reach the desired size. + +10 +00:00:27,300 --> 00:00:28,530 +在每次迭代中, +At each iteration, + +11 +00:00:28,530 --> 00:00:30,930 +我们将计算训练语料库的损失 +we will calculate a loss on our training corpus + +12 +00:00:30,930 --> 00:00:33,480 +感谢 Unigram 模型。 +thanks to the Unigram model. + +13 +00:00:33,480 --> 00:00:37,470 +由于损失计算取决于可用的词汇表, +As the loss calculation depends on the available vocabulary, + +14 +00:00:37,470 --> 00:00:40,563 +我们可以用它来选择如何减少词汇量。 +we can use it to choose how to reduce the vocabulary. + +15 +00:00:41,550 --> 00:00:43,620 +所以我们看看损失的演变 +So we look at the evolution of the loss + +16 +00:00:43,620 --> 00:00:47,103 +通过依次从词汇表中删除每个标记。 +by removing in turn each token from the vocabulary. + +17 +00:00:48,000 --> 00:00:50,430 +我们将选择删除 p 百分比 +We will choose to remove the p-percents + +18 +00:00:50,430 --> 00:00:52,200 +增加的损失越少。 +which increase the loss the less. + +19 +00:00:56,310 --> 00:00:57,540 +在进一步之前 +Before going further + +20 +00:00:57,540 --> 00:01:00,240 +在训练算法的解释中, +in the explanation of the training algorithm, + +21 +00:01:00,240 --> 00:01:02,973 +我需要解释什么是 Unigram 模型。 +I need to explain what is an Unigram model. + +22 +00:01:04,183 --> 00:01:06,030 +Unigram 语言模型 +The Unigram Language Model + +23 +00:01:06,030 --> 00:01:08,493 +是一种统计语言调制解调器。 +is a type of Statistical Language Modem. + +24 +00:01:09,450 --> 00:01:10,980 +统计语言模型 +A Statistical Language Model + +25 +00:01:10,980 --> 00:01:13,530 +将为文本分配概率 +will assign a probability to a text + +26 +00:01:13,530 --> 00:01:18,090 +考虑到文本实际上是一系列标记。 +considering that the text is in fact a sequence of tokens. + +27 +00:01:18,090 --> 00:01:21,090 +可以想象的最简单的标记序列 +The simplest sequences of tokens to imagine + +28 +00:01:21,090 --> 00:01:24,753 +是组成句子或字符的单词。 +are the words that compose the sentence or the characters. + +29 +00:01:26,130 --> 00:01:28,890 +Unigram 语言模型的特殊性 +The particularity of Unigram Language Model + +30 +00:01:28,890 --> 00:01:32,010 +是它假设每个词的出现 +is that it assumes that the occurrence of each word + +31 +00:01:32,010 --> 00:01:34,533 +独立于它的前一个词。 +is independent of its previous word. + +32 +00:01:35,400 --> 00:01:37,620 +这个假设允许我们写 +This assumption allows us to write + +33 +00:01:37,620 --> 00:01:39,570 +一个文本的概率 +that the probability of a text + +34 +00:01:39,570 --> 00:01:42,210 +等于概率的乘积 +is equal to the product of the probabilities + +35 +00:01:42,210 --> 00:01:43,953 +组成它的令牌。 +of the tokens that compose it. + +36 +00:01:45,840 --> 00:01:50,220 +这里需要注意的是,它是一个非常简单的模型 +It should be noted here that it is a very simple model + +37 +00:01:50,220 --> 00:01:53,850 +这不会适应文本的生成 +which would not be adapted to the generation of text + +38 +00:01:53,850 --> 00:01:57,840 +因为这个模型总是会生成相同的令牌, +since this model would always generate the same token, + +39 +00:01:57,840 --> 00:02:00,453 +概率最大的那个。 +the one which has the greatest probability. + +40 +00:02:01,320 --> 00:02:03,360 +然而,要进行标记化, +Nevertheless, to do tokenization, + +41 +00:02:03,360 --> 00:02:05,790 +这个模型对我们很有用 +this model is very useful to us + +42 +00:02:05,790 --> 00:02:07,440 +因为它可以使用 +because it can be used + +43 +00:02:07,440 --> 00:02:10,893 +估计不同短语的相对可能性。 +to estimate the relative likelihood of different phrases. + +44 +00:02:14,100 --> 00:02:15,000 +我们现在准备好了 +We are now ready + +45 +00:02:15,000 --> 00:02:19,830 +回到我们对训练算法的解释。 +to return to our explanation of the training algorithm. + +46 +00:02:19,830 --> 00:02:21,690 +假设我们有一个训练语料库 +Let's say that we have as a training corpus + +47 +00:02:21,690 --> 00:02:23,880 +用 10 次 hug 这个词, +with 10 times the word hug, + +48 +00:02:23,880 --> 00:02:25,410 +12 次 pug 这个词, +12 times the word pug, + +49 +00:02:25,410 --> 00:02:27,330 +5 次 lug 这个词, +5 times the word lug, + +50 +00:02:27,330 --> 00:02:28,560 +4 次 bug +4 times bug + +51 +00:02:28,560 --> 00:02:29,943 +5 次 dug。 +and 5 times dug. + +52 +00:02:33,120 --> 00:02:34,560 +如前所述, +As said earlier, + +53 +00:02:34,560 --> 00:02:37,473 +训练从大量词汇开始。 +the training starts with a big vocabulary. + +54 +00:02:38,460 --> 00:02:41,400 +显然,由于我们使用的是玩具语料库, +Obviously, as we are using a toy corpus, + +55 +00:02:41,400 --> 00:02:44,430 +这个词汇量不会那么大 +this vocabulary will not be that big + +56 +00:02:44,430 --> 00:02:46,773 +但它应该告诉你原理。 +but it should show you the principle. + +57 +00:02:47,610 --> 00:02:51,870 +第一种方法是列出所有可能的严格子串 +A first method is to list all the possible strict substrings + +58 +00:02:51,870 --> 00:02:53,823 +这就是我们在这里要做的。 +and that's what we'll do here. + +59 +00:02:54,780 --> 00:02:58,170 +我们也可以使用 BPE 算法 +We could also have used the BPE algorithm + +60 +00:02:58,170 --> 00:03:00,010 +词汇量非常大 +with a very large vocabulary size + +61 +00:03:01,410 --> 00:03:05,103 +但就目前而言,严格的子字符串就足够了。 +but for now, the strict substrings are enough. + +62 +00:03:06,990 --> 00:03:09,120 +Unigram 分词器的训练 +The training of the Unigram tokenizer + +63 +00:03:09,120 --> 00:03:12,093 +基于期望最大化方法。 +is based on the Expectation-Maximization method. + +64 +00:03:13,320 --> 00:03:15,120 +在每次迭代中, +At each iteration, + +65 +00:03:15,120 --> 00:03:17,430 +我们估计代币的概率 +we estimate the probabilities of the tokens + +66 +00:03:17,430 --> 00:03:18,430 +词汇的 +of the vocabulary + +67 +00:03:20,130 --> 00:03:23,100 +然后我们删除 p 百分比的标记 +and then we remove the p-percent of tokens + +68 +00:03:23,100 --> 00:03:26,070 +最小化语料库的损失 +that minimize the loss on the corpus + +69 +00:03:26,070 --> 00:03:28,900 +而哪些不属于基本字 +and which do not belong to the basic character + +70 +00:03:29,880 --> 00:03:33,150 +因为我们想保留在我们的最终词汇表中 +as we want to keep in our final vocabulary + +71 +00:03:33,150 --> 00:03:36,693 +能够标记任何单词的基本字符。 +the basic characters to be able to tokenize any word. + +72 +00:03:37,770 --> 00:03:39,641 +让我们开始吧! +Let's go for it! + +73 +00:03:39,641 --> 00:03:42,360 +简单估计一个 token 的概率 +The probability of a token simply estimated + +74 +00:03:42,360 --> 00:03:44,760 +按此令牌出现的次数 +by the number of appearance of this token + +75 +00:03:44,760 --> 00:03:46,440 +在我们的训练语料库中 +in our training corpus + +76 +00:03:46,440 --> 00:03:50,133 +除以所有令牌出现的总数。 +divided by the total number of appearance of all the tokens. + +77 +00:03:51,510 --> 00:03:54,390 +我们可以使用这个词汇表来标记我们的单词 +We could use this vocabulary to tokenize our words + +78 +00:03:54,390 --> 00:03:56,283 +根据 Unigram 模型。 +according to the Unigram model. + +79 +00:03:57,150 --> 00:04:00,892 +我们将一起做,以了解两件事: +We will do it together to understand two things: + +80 +00:04:00,892 --> 00:04:04,110 +我们如何使用 Unigram 模型对单词进行分词 +how we tokenize a word with a Unigram model + +81 +00:04:04,110 --> 00:04:07,803 +以及如何在我们的语料库上计算损失。 +and how the loss is calculated on our corpus. + +82 +00:04:09,088 --> 00:04:12,263 +我们的文本 “Hug” 的 Unigram LM 标记化 +The Unigram LM tokenization of our text 'Hug' + +83 +00:04:12,263 --> 00:04:15,270 +将是发生概率最高的那个 +will be the one with the highest probability of occurrence + +84 +00:04:15,270 --> 00:04:17,403 +根据我们的 Unigram 模型。 +according to our Unigram model. + +85 +00:04:19,080 --> 00:04:21,750 +找到它,最简单的方法 +To find it, the simplest way to proceed + +86 +00:04:21,750 --> 00:04:24,120 +将列出所有可能的细分 +would be to list all the possible segmentations + +87 +00:04:24,120 --> 00:04:25,800 +我们的文字 Hug +of our text 'Hug', + +88 +00:04:25,800 --> 00:04:29,340 +计算每个细分的概率 +calculate the probability of each of these segmentations + +89 +00:04:29,340 --> 00:04:32,043 +然后选择概率最高的那个。 +and then choose the one with the highest probability. + +90 +00:04:33,210 --> 00:04:34,920 +以现在的词汇量, +With the current vocabulary, + +91 +00:04:34,920 --> 00:04:38,640 +两个标记化获得完全相同的概率。 +two tokenizations get exactly the same probability. + +92 +00:04:38,640 --> 00:04:40,080 +所以我们选择其中之一 +So we choose one of them + +93 +00:04:40,080 --> 00:04:42,603 +并记住相关的概率。 +and keep in memory the associated probability. + +94 +00:04:43,710 --> 00:04:46,380 +为了计算我们训练语料库的损失, +To compute the loss on our training corpus, + +95 +00:04:46,380 --> 00:04:48,570 +我们需要像刚才那样进行标记化 +we need to tokenize as we just did + +96 +00:04:48,570 --> 00:04:50,673 +语料库中所有剩余的单词。 +all the remaining words in the corpus. + +97 +00:04:52,290 --> 00:04:56,430 +损失就是语料库中所有单词的总和 +The loss is then the sum over all the words in the corpus + +98 +00:04:56,430 --> 00:04:58,920 +词的出现频率 +of the frequency of occurrence of the word + +99 +00:04:58,920 --> 00:05:02,670 +乘以概率对数的相反数 +multiplied by the opposite of the log of the probability + +100 +00:05:02,670 --> 00:05:05,463 +与单词的标记化相关联。 +associated with the tokenization of the word. + +101 +00:05:07,620 --> 00:05:10,803 +我们在这里得到了 170 的损失。 +We obtain here a loss of 170. + +102 +00:05:13,830 --> 00:05:18,630 +请记住,我们最初的目标是减少词汇量。 +Remember, our initial goal was to reduce the vocabulary. + +103 +00:05:18,630 --> 00:05:21,870 +为此,我们将从词汇表中删除一个标记 +To do this, we will remove a token from the vocabulary + +104 +00:05:21,870 --> 00:05:24,213 +并计算相关损失。 +and calculate the associated loss. + +105 +00:05:27,630 --> 00:05:30,627 +例如,让我们删除标记 “ug”。 +Let's remove for example, the token 'ug'. + +106 +00:05:31,920 --> 00:05:35,370 +我们注意到 “hug” 的标记化 +We notice that the tokenization for 'hug' + +107 +00:05:35,370 --> 00:05:39,990 +字母 h 和元组 ug 现在是不可能的。 +with the letter 'h' and the tuple 'ug' is now impossible. + +108 +00:05:39,990 --> 00:05:42,240 +尽管如此,正如我们之前看到的 +Nevertheless, as we saw earlier + +109 +00:05:42,240 --> 00:05:45,180 +两个标记化具有相同的概率, +that two tokenizations had the same probability, + +110 +00:05:45,180 --> 00:05:47,730 +我们仍然可以选择剩余的标记化 +we can still choose the remaining tokenization + +111 +00:05:47,730 --> 00:05:51,093 +概率为 1.10e-2。 +with a probability of 1.10e-2. + +112 +00:05:52,410 --> 00:05:55,350 +词汇表中其他词的分词 +The tokenizations of the other words of the vocabulary + +113 +00:05:55,350 --> 00:05:57,060 +也保持不变。 +also remain unchanged. + +114 +00:05:57,060 --> 00:06:00,600 +最后,即使我们删除了标记 “ug” +And finally, even if we remove the token 'ug' + +115 +00:06:00,600 --> 00:06:05,403 +从我们的词汇表来看,损失仍然等于 170。 +from our vocabulary the loss remains equal to 170. + +116 +00:06:06,630 --> 00:06:08,100 +对于第一次迭代, +For this first iteration, + +117 +00:06:08,100 --> 00:06:10,080 +如果我们继续计算, +if we continue the calculation, + +118 +00:06:10,080 --> 00:06:13,050 +我们会注意到我们可以删除任何令牌 +we would notice that we could remove any token + +119 +00:06:13,050 --> 00:06:16,110 +在不影响损失的情况下。 +without it impacting the loss. + +120 +00:06:16,110 --> 00:06:19,200 +因此,我们将随机选择删除标记 “ug” +We will therefore choose at random to remove the token 'ug' + +121 +00:06:19,200 --> 00:06:21,843 +在开始第二次迭代之前。 +before starting a second iteration. + +122 +00:06:24,240 --> 00:06:27,300 +所以我们再次估计每个 token 的概率 +So we estimate again the probability of each token + +123 +00:06:27,300 --> 00:06:30,630 +在计算每个代币对损失的影响之前。 +before calculating the impact of each token on the loss. + +124 +00:06:32,160 --> 00:06:33,990 +例如,如果我们现在删除 +For example, if we remove now + +125 +00:06:33,990 --> 00:06:36,290 +由字母 “h” 和 “u” 组成的令牌, +the token composed of the letters 'h' and 'u', + +126 +00:06:37,350 --> 00:06:41,013 +hug 只剩下一种可能的标记化。 +there is only one possible tokenization left for hug. + +127 +00:06:41,940 --> 00:06:44,700 +词汇表中其他词的标记化 +The tokenization of the other words of the vocabulary + +128 +00:06:44,700 --> 00:06:45,633 +没有改变。 +is not changed. + +129 +00:06:46,560 --> 00:06:47,393 +到底, +In the end, + +130 +00:06:47,393 --> 00:06:49,200 +我们通过删除令牌获得 +we obtain by removing the token + +131 +00:06:49,200 --> 00:06:52,749 +由词汇表中的字母 “h” 和 “u” 组成, +composed of the letters 'h' and 'u' from the vocabulary, + +132 +00:06:52,749 --> 00:06:56,430 +亏损 168。 +a loss of 168. + +133 +00:06:56,430 --> 00:06:59,490 +最后,要选择要删除的令牌, +Finally, to choose which token to remove, + +134 +00:06:59,490 --> 00:07:02,490 +我们将为词汇表的每个剩余标记, +we will for each remaining token of the vocabulary, + +135 +00:07:02,490 --> 00:07:04,800 +这不是基本令牌, +which is not an elementary token, + +136 +00:07:04,800 --> 00:07:07,380 +计算相关损失。 +calculate the associated loss. + +137 +00:07:07,380 --> 00:07:09,843 +然后,比较它们之间的这些损失。 +Then, compare these losses between them. + +138 +00:07:11,730 --> 00:07:13,800 +我们将删除的令牌 +The token which we will remove + +139 +00:07:13,800 --> 00:07:17,340 +是对损失影响最小的代币, +is the token which impacts the least the loss, + +140 +00:07:17,340 --> 00:07:18,870 +这里是令牌 “bu”。 +here the token 'bu'. + +141 +00:07:20,040 --> 00:07:22,380 +我们在视频开头提到过 +We had mentioned at the beginning of the video + +142 +00:07:22,380 --> 00:07:24,930 +在每次迭代中我们可以删除 +that at each iteration we could remove + +143 +00:07:24,930 --> 00:07:27,093 +迭代中标记的 p 百分比。 +p-percent of the tokens by iteration. + +144 +00:07:29,356 --> 00:07:33,000 +可以在本次迭代中删除的第二个标记 +The second token that could be removed at this iteration + +145 +00:07:33,000 --> 00:07:34,317 +是标记 “du”。 +is the token 'du'. + +146 +00:07:36,510 --> 00:07:37,920 +就是这样。 +And that's it. + +147 +00:07:37,920 --> 00:07:39,720 +我们只需要重复这些步骤 +We just have to repeat these steps + +148 +00:07:39,720 --> 00:07:43,203 +直到我们得到所需大小的词汇表。 +until we get the vocabulary of the desired size. + +149 +00:07:45,030 --> 00:07:46,500 +最后一件事。 +One last thing. + +150 +00:07:46,500 --> 00:07:50,310 +在实践中,当我们用 Unigram 模型标记一个词时, +In practice, when we tokenize a word with a Unigram model, + +151 +00:07:50,310 --> 00:07:53,130 +我们不计算概率的集合 +we don't compute the set of probabilities of + +152 +00:07:53,130 --> 00:07:55,500 +一个词所有可能的拆分 +all the possible splits of a word + +153 +00:07:55,500 --> 00:07:58,770 +在比较它们以保留最好的之前 +before comparing them to keep the best one + +154 +00:07:58,770 --> 00:08:01,440 +但是我们使用维特比算法 +but we use the Viterbi algorithm + +155 +00:08:01,440 --> 00:08:04,563 +这是更有效的方法。 +which is much more efficient way to do it. + +156 +00:08:06,540 --> 00:08:07,680 +就是这样! +And that's it! + +157 +00:08:07,680 --> 00:08:09,270 +我希望这个例子 +I hope that this example + +158 +00:08:09,270 --> 00:08:10,987 +已经让你更好的了解 +has allowed you to better understand + +159 +00:08:10,987 --> 00:08:12,933 +Unigram 分词算法。 +the Unigram tokenization algorithm. + +160 +00:08:14,355 --> 00:08:17,022 +(空气呼啸) +(air whooshing) + diff --git a/subtitles/zh-CN/54_building-a-new-tokenizer.srt b/subtitles/zh-CN/54_building-a-new-tokenizer.srt new file mode 100644 index 000000000..7faa3a529 --- /dev/null +++ b/subtitles/zh-CN/54_building-a-new-tokenizer.srt @@ -0,0 +1,435 @@ +1 +00:00:00,188 --> 00:00:02,855 +(空气呼啸) +(air whooshing) + +2 +00:00:05,400 --> 00:00:07,500 +在本视频中,我们将看到如何 +In this video, we will see how + +3 +00:00:07,500 --> 00:00:11,310 +你可以从头开始创建自己的分词器。 +you can create your own tokenizer from scratch. + +4 +00:00:11,310 --> 00:00:15,000 +要创建自己的分词器,你必须考虑 +To create your own tokenizer, you will have to think about + +5 +00:00:15,000 --> 00:00:18,180 +令牌化中涉及的每个操作。 +each of the operations involved in tokenization. + +6 +00:00:18,180 --> 00:00:22,440 +即,规范化,预标记化, +Namely, the normalization, the pre-tokenization, + +7 +00:00:22,440 --> 00:00:25,233 +模型、后处理和解码。 +the model, the post processing, and the decoding. + +8 +00:00:26,100 --> 00:00:28,350 +如果你不知道什么是规范化, +If you don't know what normalization, + +9 +00:00:28,350 --> 00:00:30,900 +预标记化,模型是, +pre-tokenization, and the model are, + +10 +00:00:30,900 --> 00:00:34,531 +我建议你去看下面链接的视频。 +I advise you to go and see the videos linked below. + +11 +00:00:34,531 --> 00:00:37,110 +后处理收集所有修改 +The post processing gathers all the modifications + +12 +00:00:37,110 --> 00:00:40,860 +我们将对标记化文本执行。 +that we will carry out on the tokenized text. + +13 +00:00:40,860 --> 00:00:43,890 +它可以包括添加特殊令牌, +It can include the addition of special tokens, + +14 +00:00:43,890 --> 00:00:46,290 +创建一个意图面具, +the creation of an intention mask, + +15 +00:00:46,290 --> 00:00:48,903 +还会生成令牌 ID 列表。 +but also the generation of a list of token IDs. + +16 +00:00:50,220 --> 00:00:53,487 +解码操作发生在最后, +The decoding operation occurs at the very end, + +17 +00:00:53,487 --> 00:00:54,660 +并将允许通过 +and will allow passing + +18 +00:00:54,660 --> 00:00:57,753 +来自句子中的 ID 序列。 +from the sequence of IDs in a sentence. + +19 +00:00:58,890 --> 00:01:01,800 +例如,你可以看到主题标签 +For example, you can see that the hashtags + +20 +00:01:01,800 --> 00:01:04,260 +已被删除,并且令牌 +have been removed, and the tokens + +21 +00:01:04,260 --> 00:01:07,323 +今天的作文词都归在了一起。 +composing the word today have been grouped together. + +22 +00:01:10,860 --> 00:01:13,440 +在快速分词器中,所有这些组件 +In a fast tokenizer, all these components + +23 +00:01:13,440 --> 00:01:16,413 +收集在 backend_tokenizer 属性中。 +are gathered in the backend_tokenizer attribute. + +24 +00:01:17,370 --> 00:01:20,070 +正如你在这个小代码片段中看到的那样, +As you can see with this small code snippet, + +25 +00:01:20,070 --> 00:01:22,020 +它是分词器的一个实例 +it is an instance of a tokenizer + +26 +00:01:22,020 --> 00:01:23,763 +来自 tokenizers 库。 +from the tokenizers library. + +27 +00:01:25,740 --> 00:01:28,263 +因此,要创建自己的分词器, +So, to create your own tokenizer, + +28 +00:01:29,970 --> 00:01:31,770 +你将必须遵循这些步骤。 +you will have to follow these steps. + +29 +00:01:33,270 --> 00:01:35,433 +首先,创建一个训练数据集。 +First, create a training dataset. + +30 +00:01:36,690 --> 00:01:39,000 +二、创建和训练分词器 +Second, create and train a tokenizer + +31 +00:01:39,000 --> 00:01:41,700 +与变压器库。 +with the transformer library. + +32 +00:01:41,700 --> 00:01:46,700 +第三,将此分词器加载到转换器分词器中。 +And third, load this tokenizer into a transformer tokenizer. + +33 +00:01:49,350 --> 00:01:50,850 +要了解这些步骤, +To understand these steps, + +34 +00:01:50,850 --> 00:01:54,573 +我建议我们一起重新创建一个 BERT 分词器。 +I propose that we recreate a BERT tokenizer together. + +35 +00:01:56,460 --> 00:01:58,893 +首先要做的是创建一个数据集。 +The first thing to do is to create a dataset. + +36 +00:01:59,970 --> 00:02:02,460 +使用此代码片段,你可以创建一个迭代器 +With this code snippet you can create an iterator + +37 +00:02:02,460 --> 00:02:05,430 +在数据集 wikitext-2-raw-V1 上, +on the dataset wikitext-2-raw-V1, + +38 +00:02:05,430 --> 00:02:08,160 +这是一个相当小的英语数据集, +which is a rather small dataset in English, + +39 +00:02:08,160 --> 00:02:09,730 +完美的例子。 +perfect for the example. + +40 +00:02:12,210 --> 00:02:13,920 +我们主要攻击这里, +We attack here the big part, + +41 +00:02:13,920 --> 00:02:17,373 +使用标记器库设计我们的标记器。 +the design of our tokenizer with the tokenizer library. + +42 +00:02:18,750 --> 00:02:22,020 +我们首先初始化一个分词器实例 +We start by initializing a tokenizer instance + +43 +00:02:22,020 --> 00:02:26,133 +使用 WordPiece 模型,因为它是 BERT 使用的模型。 +with a WordPiece model because it is the model used by BERT. + +44 +00:02:29,100 --> 00:02:32,190 +然后我们可以定义我们的规范化器。 +Then we can define our normalizer. + +45 +00:02:32,190 --> 00:02:35,891 +我们将其定义为连续的两个规范化 +We will define it as a succession of two normalizations + +46 +00:02:35,891 --> 00:02:39,453 +用于清理文本中不可见的字符。 +used to clean up characters not visible in the text. + +47 +00:02:40,590 --> 00:02:43,440 +一个小写归一化, +One lowercasing normalization, + +48 +00:02:43,440 --> 00:02:47,253 +最后两个标准化用于删除重音。 +and two last normalizations used to remove accents. + +49 +00:02:49,500 --> 00:02:53,553 +对于预标记化,我们将链接两个 pre_tokenizers。 +For the pre-tokenization, we will chain two pre_tokenizers. + +50 +00:02:54,390 --> 00:02:58,200 +第一个在空格级别分隔文本, +The first one separating the text at the level of spaces, + +51 +00:02:58,200 --> 00:03:01,533 +第二个隔离标点符号。 +and the second one isolating the punctuation marks. + +52 +00:03:03,360 --> 00:03:06,360 +现在,我们可以定义允许我们的培训师 +Now, we can define the trainer that will allow us + +53 +00:03:06,360 --> 00:03:09,753 +训练一开始选择的 WordPiece 模型。 +to train the WordPiece model chosen at the beginning. + +54 +00:03:11,160 --> 00:03:12,600 +为了开展培训, +To carry out the training, + +55 +00:03:12,600 --> 00:03:14,853 +我们将不得不选择词汇量。 +we will have to choose a vocabulary size. + +56 +00:03:16,050 --> 00:03:17,910 +这里我们选择 25,000。 +Here we choose 25,000. + +57 +00:03:17,910 --> 00:03:21,270 +我们还需要公布特殊代币 +And we also need to announce the special tokens + +58 +00:03:21,270 --> 00:03:24,663 +我们绝对想添加到我们的词汇表中。 +that we absolutely want to add to our vocabulary. + +59 +00:03:29,160 --> 00:03:33,000 +在一行代码中,我们可以训练我们的 WordPiece 模型 +In one line of code, we can train our WordPiece model + +60 +00:03:33,000 --> 00:03:35,553 +使用我们之前定义的迭代器。 +using the iterator we defined earlier. + +61 +00:03:39,060 --> 00:03:42,570 +模型训练完成后,我们可以检索 +Once the model has been trained, we can retrieve + +62 +00:03:42,570 --> 00:03:46,560 +特殊类别和分离标记的 ID, +the IDs of the special class and separation tokens, + +63 +00:03:46,560 --> 00:03:49,413 +因为我们需要它们来对我们的序列进行后期处理。 +because we will need them to post-process our sequence. + +64 +00:03:50,820 --> 00:03:52,860 +感谢 TemplateProcessing 类, +Thanks to the TemplateProcessing class, + +65 +00:03:52,860 --> 00:03:57,210 +我们可以在每个序列的开头添加 CLS 标记, +we can add the CLS token at the beginning of each sequence, + +66 +00:03:57,210 --> 00:04:00,120 +和序列末尾的 SEP 令牌, +and the SEP token at the end of the sequence, + +67 +00:04:00,120 --> 00:04:03,873 +如果我们标记一对文本,则在两个句子之间。 +and between two sentences if we tokenize a pair of text. + +68 +00:04:07,260 --> 00:04:10,500 +最后,我们只需要定义我们的解码器, +Finally, we just have to define our decoder, + +69 +00:04:10,500 --> 00:04:12,690 +这将允许我们删除主题标签 +which will allow us to remove the hashtags + +70 +00:04:12,690 --> 00:04:14,610 +在令牌的开头 +at the beginning of the tokens + +71 +00:04:14,610 --> 00:04:17,193 +必须重新附加到以前的令牌。 +that must be reattached to the previous token. + +72 +00:04:21,300 --> 00:04:22,260 +它就在那里。 +And there it is. + +73 +00:04:22,260 --> 00:04:25,110 +你拥有所有必要的代码行 +You have all the necessary lines of code + +74 +00:04:25,110 --> 00:04:29,403 +使用分词器库定义你自己的分词器。 +to define your own tokenizer with the tokenizer library. + +75 +00:04:30,960 --> 00:04:32,280 +现在我们有了一个全新的分词器 +Now that we have a brand new tokenizer + +76 +00:04:32,280 --> 00:04:35,400 +使用 tokenizer 库,我们只需要加载它 +with the tokenizer library, we just have to load it + +77 +00:04:35,400 --> 00:04:38,463 +从 transformers 库中转换为快速分词器。 +into a fast tokenizer from the transformers library. + +78 +00:04:39,960 --> 00:04:42,630 +同样,我们有几种可能性。 +Here again, we have several possibilities. + +79 +00:04:42,630 --> 00:04:44,430 +我们可以在泛型类中加载它, +We can load it in the generic class, + +80 +00:04:44,430 --> 00:04:48,330 +PreTrainedTokenizerFast,或在 BertTokenizerFast 类中 +PreTrainedTokenizerFast, or in the BertTokenizerFast class + +81 +00:04:48,330 --> 00:04:52,353 +因为我们在这里构建了一个类似 BERT 的分词器。 +since we have built a BERT like tokenizer here. + +82 +00:04:57,000 --> 00:04:59,670 +我真的希望这个视频能帮助你理解 +I really hope this video has helped you understand + +83 +00:04:59,670 --> 00:05:02,133 +如何创建自己的分词器, +how you can create your own tokenizer, + +84 +00:05:03,178 --> 00:05:06,240 +并且你现在已准备好导航 +and that you are ready now to navigate + +85 +00:05:06,240 --> 00:05:08,070 +分词器库文档 +the tokenizer library documentation + +86 +00:05:08,070 --> 00:05:11,367 +为你的全新分词器选择组件。 +to choose the components for your brand new tokenizer. + +87 +00:05:12,674 --> 00:05:15,341 +(空气呼啸) +(air whooshing) + diff --git a/subtitles/zh-CN/55_data-processing-for-token-classification.srt b/subtitles/zh-CN/55_data-processing-for-token-classification.srt new file mode 100644 index 000000000..7f6ec1d15 --- /dev/null +++ b/subtitles/zh-CN/55_data-processing-for-token-classification.srt @@ -0,0 +1,365 @@ +1 +00:00:05,730 --> 00:00:07,590 +- 让我们研究如何预处理数据集 +- Let's study how to preprocess a dataset + +2 +00:00:07,590 --> 00:00:09,063 +用于令牌分类! +for token classification! + +3 +00:00:10,560 --> 00:00:12,660 +令牌分类重新组合任何任务 +Token classification regroups any task + +4 +00:00:12,660 --> 00:00:14,940 +可以框架为标记每个单词 +that can be framed as labeling each word + +5 +00:00:14,940 --> 00:00:17,190 +或标记在一个句子中, +or token in a sentence, + +6 +00:00:17,190 --> 00:00:19,530 +比如识别人、组织 +like identifying the persons, organizations + +7 +00:00:19,530 --> 00:00:21,093 +和位置,例如。 +and locations for instance. + +8 +00:00:22,170 --> 00:00:25,470 +对于我们的示例,我们将使用 Conll 数据集, +For our example, we will use the Conll dataset, + +9 +00:00:25,470 --> 00:00:27,900 +在其中我们删除了我们不会使用的列 +in which we remove columns we won't use + +10 +00:00:27,900 --> 00:00:29,940 +并重命名其他的以获取数据集 +and rename the other ones to get to a dataset + +11 +00:00:29,940 --> 00:00:32,943 +只有两列,单词和标签。 +with just two columns, words and labels. + +12 +00:00:34,200 --> 00:00:36,750 +如果你有自己的令牌分类数据集, +If you have your own dataset for token classification, + +13 +00:00:36,750 --> 00:00:39,870 +只要确保你清理数据以达到同一点, +just make sure you clean your data to get to the same point, + +14 +00:00:39,870 --> 00:00:43,290 +一列包含单词作为字符串列表 +with one column containing words as list of strings + +15 +00:00:43,290 --> 00:00:45,540 +另一个包含标签作为整数 +and another containing labels as integers + +16 +00:00:45,540 --> 00:00:48,513 +从零到你的标签数量减一。 +spanning from zero to your number of labels minus one. + +17 +00:00:49,740 --> 00:00:52,290 +确保将标签名称存储在某处。 +Make sure you have your label names stored somewhere. + +18 +00:00:52,290 --> 00:00:54,810 +在这里,我们从数据集特征中获取它们。 +Here we get them from the dataset features. + +19 +00:00:54,810 --> 00:00:57,660 +所以你可以将整数映射到一些真实的标签 +So you are able to map the integers to some real labels + +20 +00:00:57,660 --> 00:00:58,960 +在检查你的数据时。 +when inspecting your data. + +21 +00:01:00,690 --> 00:01:03,510 +这里我们正在做命名实体识别, +Here we are doing named entity recognitions, + +22 +00:01:03,510 --> 00:01:05,430 +所以我们的标签要么是 O +so ours labels are either O + +23 +00:01:05,430 --> 00:01:08,310 +对于不属于任何实体的词。 +for words that do not belong to any entity. + +24 +00:01:08,310 --> 00:01:13,310 +LOC 代表位置,PER 代表个人,ORG 代表组织 +LOC for location, PER for person, ORG for organization + +25 +00:01:13,860 --> 00:01:15,603 +和 MISC 杂项。 +and MISC for miscellaneous. + +26 +00:01:16,650 --> 00:01:18,540 +每个标签有两个版本。 +Each label has two versions. + +27 +00:01:18,540 --> 00:01:21,960 +B 标签表示一个词开始一个实体 +The B labels indicate a word that begins an entity + +28 +00:01:21,960 --> 00:01:25,503 +而 I 标签表示实体内部的单词。 +while the I labels indicate a word that is inside an entity. + +29 +00:01:27,180 --> 00:01:28,830 +预处理我们数据的第一步 +The first step to preprocess our data + +30 +00:01:28,830 --> 00:01:30,660 +是将单词标记化。 +is to tokenize the words. + +31 +00:01:30,660 --> 00:01:33,120 +使用分词器很容易做到这一点。 +This is very easily done with the tokenizer. + +32 +00:01:33,120 --> 00:01:35,370 +我们只需要告诉它我们已经预先标记了数据 +We just have to tell it we have pre-tokenized the data + +33 +00:01:35,370 --> 00:01:37,503 +带有标志 is_split_into_words=True。 +with the flag is_split_into_words=True. + +34 +00:01:38,520 --> 00:01:40,380 +然后是困难的部分。 +Then comes the hard part. + +35 +00:01:40,380 --> 00:01:42,360 +由于我们添加了特殊标记 +Since we have added special tokens + +36 +00:01:42,360 --> 00:01:45,270 +每个单词可能被拆分成几个标记, +and each word may have been split into several tokens, + +37 +00:01:45,270 --> 00:01:48,090 +我们的标签将不再与标记匹配。 +our labels won't match the tokens anymore. + +38 +00:01:48,090 --> 00:01:50,670 +这是我们的快速分词器提供的单词 ID +This is where the word IDs our fast tokenizer provides + +39 +00:01:50,670 --> 00:01:51,723 +前来救援。 +come to the rescue. + +40 +00:01:53,040 --> 00:01:55,500 +他们将每个标记与其所属的单词匹配 +They match each token to the word it belongs to + +41 +00:01:55,500 --> 00:01:58,470 +这允许我们将每个标记映射到它的标签。 +which allows us to map each token to its label. + +42 +00:01:58,470 --> 00:01:59,303 +我们只需要确保 +We just have to make sure + +43 +00:01:59,303 --> 00:02:01,710 +我们将 B 标签更改为对应的 I +we change the B labels to their I counterparts + +44 +00:02:01,710 --> 00:02:03,450 +对于里面的令牌 +for tokens that are inside + +45 +00:02:03,450 --> 00:02:05,433 +但不是在单词的开头。 +but not at the beginning of a word. + +46 +00:02:06,330 --> 00:02:09,120 +特殊标记的标签为 -100, +The special tokens get a label of -100, + +47 +00:02:09,120 --> 00:02:11,070 +这就是我们告诉 Transformer 损失函数的方式 +which is how we tell the Transformer loss functions + +48 +00:02:11,070 --> 00:02:14,607 +在计算损失时忽略它们。 +to ignore them when computing the loss. + +49 +00:02:14,607 --> 00:02:16,890 +然后代码非常简单。 +The code is then pretty straightforward. + +50 +00:02:16,890 --> 00:02:18,660 +我们写了一个函数来移动标签 +We write a function that shifts the labels + +51 +00:02:18,660 --> 00:02:21,840 +对于可以自定义的单词内的标记 +for tokens that are inside a word that you can customize + +52 +00:02:21,840 --> 00:02:24,490 +并在为每个标记生成标签时使用它。 +and use it when generating the labels for each token. + +53 +00:02:25,830 --> 00:02:28,260 +一旦编写了创建标签的函数, +Once that function to create our labels is written, + +54 +00:02:28,260 --> 00:02:31,920 +我们可以使用 map 函数预处理整个数据集。 +we can preprocess the whole dataset using the map function. + +55 +00:02:31,920 --> 00:02:33,360 +使用选项 batched=True, +With the option batched=True, + +56 +00:02:33,360 --> 00:02:35,793 +我们释放了快速分词器的速度。 +we unleash the speed of out fast tokenizers. + +57 +00:02:37,110 --> 00:02:40,350 +当我们需要创建一个批次时,最后一个问题就来了。 +The last problem comes when we need to create a batch. + +58 +00:02:40,350 --> 00:02:42,150 +除非你改变了预处理功能 +Unless you changed the preprocessing function + +59 +00:02:42,150 --> 00:02:43,890 +应用一些固定的填充, +to apply some fixed padding, + +60 +00:02:43,890 --> 00:02:45,900 +我们会得到各种长度的句子, +we will get sentences of various lengths, + +61 +00:02:45,900 --> 00:02:47,900 +我们需要将其填充到相同的长度。 +which we need to pad to the same length. + +62 +00:02:48,930 --> 00:02:50,730 +填充需要应用于输入 +The padding needs to be applied to the inputs + +63 +00:02:50,730 --> 00:02:51,900 +以及标签, +as well as the labels, + +64 +00:02:51,900 --> 00:02:53,950 +因为每个标记我们应该有一个标签。 +since we should have one label per token. + +65 +00:02:54,870 --> 00:02:58,260 +同样,-100 表示应忽略的标签 +Again, -100 indicates the labels that should be ignored + +66 +00:02:58,260 --> 00:02:59,510 +用于损失计算。 +for the loss computation. + +67 +00:03:00,420 --> 00:03:01,560 +这一切都为我们完成了 +This is all done for us + +68 +00:03:01,560 --> 00:03:04,050 +通过 DataCollatorForTokenClassification, +by the DataCollatorForTokenClassification, + +69 +00:03:04,050 --> 00:03:06,740 +你可以在 PyTorch 或 TensorFlow 中使用它。 +which you can use in PyTorch or TensorFlow. + +70 +00:03:06,740 --> 00:03:08,880 +有了所有这些,你就可以准备发送数据了 +With all of this, you are either ready to send your data + +71 +00:03:08,880 --> 00:03:11,190 +并将此数据整理器交给培训师, +and this data collator to the Trainer, + +72 +00:03:11,190 --> 00:03:13,320 +或者使用 to_tf_dataset 方法 +or use the to_tf_dataset method + +73 +00:03:13,320 --> 00:03:15,333 +以及模型的拟合方法。 +and the fit method of your model. + diff --git a/subtitles/zh-CN/56_data-processing-for-masked-language-modeling.srt b/subtitles/zh-CN/56_data-processing-for-masked-language-modeling.srt new file mode 100644 index 000000000..5aabd947f --- /dev/null +++ b/subtitles/zh-CN/56_data-processing-for-masked-language-modeling.srt @@ -0,0 +1,280 @@ +1 +00:00:00,000 --> 00:00:02,333 +(嘶嘶声) +(whooshing) + +2 +00:00:05,250 --> 00:00:07,230 +- 让我们看看如何预处理我们的数据 +- Let's see how we can preprocess our data + +3 +00:00:07,230 --> 00:00:08,703 +用于掩码语言建模。 +for masked language modeling. + +4 +00:00:10,230 --> 00:00:12,570 +提醒一下,屏蔽语言建模 +As a reminder, masked language modeling + +5 +00:00:12,570 --> 00:00:15,333 +是当模型需要填补句子中的空白时。 +is when a model needs to fill the blanks in a sentence. + +6 +00:00:16,530 --> 00:00:19,650 +为此,你只需要文本,不需要标签, +To do this, you just need texts, no labels, + +7 +00:00:19,650 --> 00:00:22,200 +因为这是一个自我监督的问题。 +as this is a self-supervised problem. + +8 +00:00:22,200 --> 00:00:23,670 +要将其应用于你自己的数据, +To apply this on your own data, + +9 +00:00:23,670 --> 00:00:25,740 +只要确保你收集了所有的文本 +just make sure you have all your texts gathered + +10 +00:00:25,740 --> 00:00:27,603 +在数据集的一列中。 +in one column of your dataset. + +11 +00:00:28,440 --> 00:00:30,480 +在我们开始随机掩盖事物之前, +Before we start randomly masking things, + +12 +00:00:30,480 --> 00:00:33,090 +我们需要以某种方式使所有这些文本的长度相同 +we will need to somehow make all those texts the same length + +13 +00:00:33,090 --> 00:00:34,263 +将它们一起批处理。 +to batch them together. + +14 +00:00:35,640 --> 00:00:38,490 +使所有文本长度相同的第一种方法 +The first way to make all the texts the same length + +15 +00:00:38,490 --> 00:00:40,590 +是我们在文本分类中使用的那个。 +is the one we used in text classification. + +16 +00:00:41,430 --> 00:00:44,163 +让我们填充短文本并截断长文本。 +Let's pad the short texts and truncate the long ones. + +17 +00:00:45,030 --> 00:00:45,900 +正如我们所看到的 +As we have seen + +18 +00:00:45,900 --> 00:00:48,690 +当我们处理文本分类数据时, +when we processed data for text classification, + +19 +00:00:48,690 --> 00:00:49,923 +这都是由我们的分词器完成的 +this is all done by our tokenizer + +20 +00:00:49,923 --> 00:00:53,130 +具有正确的填充和截断选项。 +with the right options for padding and truncation. + +21 +00:00:53,130 --> 00:00:56,100 +但是,这会使我们丢失很多文本 +This will however make us lose a lot of texts + +22 +00:00:56,100 --> 00:00:58,620 +如果我们数据集中的示例很长, +if the examples in our dataset are very long, + +23 +00:00:58,620 --> 00:01:00,960 +与我们选择的上下文长度相比。 +compared to the context length we picked. + +24 +00:01:00,960 --> 00:01:03,393 +在这里,所有灰色部分都丢失了。 +Here, all the portion in gray is lost. + +25 +00:01:04,410 --> 00:01:06,660 +这就是为什么第二种生成文本样本的方法 +This is why a second way to generate samples of text + +26 +00:01:06,660 --> 00:01:08,820 +具有相同的长度是分块我们的文本 +with the same length is to chunk our text + +27 +00:01:08,820 --> 00:01:10,560 +在上下文长度中, +in pieces of context lengths, + +28 +00:01:10,560 --> 00:01:14,010 +而不是在第一个块之后丢弃所有内容。 +instead of discarding everything after the first chunk. + +29 +00:01:14,010 --> 00:01:15,420 +大概会有余数 +There will probably be a remainder + +30 +00:01:15,420 --> 00:01:17,700 +长度小于上下文大小, +of length smaller than the context size, + +31 +00:01:17,700 --> 00:01:20,493 +我们可以选择保留和填充或忽略。 +which we can choose to keep and pad or ignore. + +32 +00:01:21,570 --> 00:01:23,790 +这是我们如何在实践中应用它, +Here is how we can apply this in practice, + +33 +00:01:23,790 --> 00:01:26,460 +只需添加 return overflowing tokens 选项 +by just adding the return overflowing tokens option + +34 +00:01:26,460 --> 00:01:28,200 +在我们的分词器调用中。 +in our tokenizer call. + +35 +00:01:28,200 --> 00:01:30,243 +请注意这如何为我们提供更大的数据集! +Note how this gives us a bigger dataset! + +36 +00:01:31,560 --> 00:01:34,260 +这第二种分块方式是理想的,如果你所有的文本 +This second way of chunking is ideal if all your texts + +37 +00:01:34,260 --> 00:01:36,270 +很长,但行不通 +are very long, but it won't work + +38 +00:01:36,270 --> 00:01:39,900 +如果你的课文有不同的长度,那也不错。 +as nicely if you have a variety of lengths in the texts. + +39 +00:01:39,900 --> 00:01:41,040 +在这种情况下, +In this case, + +40 +00:01:41,040 --> 00:01:44,280 +最好的选择是连接所有标记化的文本 +the best option is to concatenate all your tokenized texts + +41 +00:01:44,280 --> 00:01:46,560 +在一个大流中,有一个特殊的标记 +in one big stream, with a special tokens + +42 +00:01:46,560 --> 00:01:49,800 +指示你何时从一份文件转到另一份文件, +to indicate when you pass from one document to the other, + +43 +00:01:49,800 --> 00:01:52,503 +然后才将大流分成块。 +and only then split the big stream into chunks. + +44 +00:01:53,760 --> 00:01:55,620 +这是用代码完成的方法, +Here is how it can be done with code, + +45 +00:01:55,620 --> 00:01:58,230 +用一个循环连接所有文本 +with one loop to concatenate all the texts + +46 +00:01:58,230 --> 00:01:59,673 +另一个将它分块。 +and another one to chunk it. + +47 +00:02:00,780 --> 00:02:02,850 +注意它是如何减少样本数量的 +Notice how it reduces the number of samples + +48 +00:02:02,850 --> 00:02:04,230 +在我们这里的数据集中, +in our dataset here, + +49 +00:02:04,230 --> 00:02:06,580 +一定有不少短条目! +there must have been quite a few short entries! + +50 +00:02:07,710 --> 00:02:11,130 +完成此操作后,掩码就很容易了。 +Once this is done, the masking is the easy part. + +51 +00:02:11,130 --> 00:02:13,400 +有专门为此设计的数据整理器 +There is a data collator designed specifically for this + +52 +00:02:13,400 --> 00:02:15,540 +在变形金刚图书馆。 +in the Transformers library. + +53 +00:02:15,540 --> 00:02:17,700 +你可以直接在 Trainer 中使用它, +You can use it directly in the Trainer, + +54 +00:02:17,700 --> 00:02:20,400 +或者将你的数据集转换为张量流数据集时 +or when converting your datasets to tensorflow datasets + +55 +00:02:20,400 --> 00:02:23,703 +在执行 Keras.fit 之前,使用 to_tf_dataset 方法。 +before doing Keras.fit, with the to_tf_dataset method. + +56 +00:02:24,992 --> 00:02:27,325 +(嘶嘶声) +(whooshing) + diff --git a/subtitles/zh-CN/57_what-is-perplexity.srt b/subtitles/zh-CN/57_what-is-perplexity.srt new file mode 100644 index 000000000..8c1141ddd --- /dev/null +++ b/subtitles/zh-CN/57_what-is-perplexity.srt @@ -0,0 +1,265 @@ +1 +00:00:00,095 --> 00:00:01,582 +(屏幕呼啸) +(screen whooshing) + +2 +00:00:01,582 --> 00:00:02,659 +(贴纸弹出) +(sticker popping) + +3 +00:00:02,659 --> 00:00:05,379 +(屏幕呼啸) +(screen whooshing) + +4 +00:00:05,379 --> 00:00:06,720 +- 在这段视频中,我们来看看 +- In this video, we take a look + +5 +00:00:06,720 --> 00:00:09,483 +在称为困惑度的神秘测深指标上。 +at the mysterious sounding metric called perplexity. + +6 +00:00:11,070 --> 00:00:12,630 +你可能遇到过困惑 +You might have encountered perplexity + +7 +00:00:12,630 --> 00:00:14,970 +在阅读生成模型时。 +when reading about generative models. + +8 +00:00:14,970 --> 00:00:16,680 +你可以在这里看到两个例子, +You can see two examples here, + +9 +00:00:16,680 --> 00:00:18,577 +一张来自原始变压器纸, +one from the original transformer paper, + +10 +00:00:18,577 --> 00:00:19,950 +“你需要的只是注意力,” +"Attention is all you need," + +11 +00:00:19,950 --> 00:00:23,340 +另一篇来自最近的 GPT-2 论文。 +and the other one from the more recent GPT-2 paper. + +12 +00:00:23,340 --> 00:00:25,740 +困惑度是衡量绩效的常用指标 +Perplexity is a common metric to measure the performance + +13 +00:00:25,740 --> 00:00:27,150 +的语言模型。 +of language models. + +14 +00:00:27,150 --> 00:00:30,000 +它的值越小,性能越好。 +The smaller its value, the better the performance. + +15 +00:00:30,000 --> 00:00:32,950 +但它究竟意味着什么,我们又该如何计算呢? +But what does it actually mean and how can we calculate it? + +16 +00:00:34,440 --> 00:00:36,180 +机器学习中非常常见的量 +A very common quantity in machine learning + +17 +00:00:36,180 --> 00:00:37,650 +是可能性。 +is the likelihood. + +18 +00:00:37,650 --> 00:00:39,240 +我们可以计算可能性 +We can calculate the likelihood + +19 +00:00:39,240 --> 00:00:42,390 +作为每个标记概率的乘积。 +as the product of each token's probability. + +20 +00:00:42,390 --> 00:00:44,730 +这意味着对于每个令牌, +What this means is that for each token, + +21 +00:00:44,730 --> 00:00:47,340 +我们使用语言模型来预测它的概率 +we use the language model to predict its probability + +22 +00:00:47,340 --> 00:00:49,560 +基于之前的标记。 +based on the previous tokens. + +23 +00:00:49,560 --> 00:00:52,050 +最后,我们将所有概率相乘 +In the end, we multiply all probabilities + +24 +00:00:52,050 --> 00:00:53,253 +得到的可能性。 +to get the likelihood. + +25 +00:00:55,892 --> 00:00:57,000 +有可能, +With the likelihood, + +26 +00:00:57,000 --> 00:00:59,340 +我们可以计算另一个重要的量, +we can calculate another important quantity, + +27 +00:00:59,340 --> 00:01:01,200 +交叉熵。 +the cross-entropy. + +28 +00:01:01,200 --> 00:01:03,450 +你可能已经听说过交叉熵 +You might have already heard about cross-entropy + +29 +00:01:03,450 --> 00:01:05,670 +在查看损失函数时。 +when looking at loss function. + +30 +00:01:05,670 --> 00:01:09,210 +它通常用作分类中的损失函数。 +It is often used as a loss function in classification. + +31 +00:01:09,210 --> 00:01:11,610 +在语言建模中,我们预测下一个标记 +In language modeling, we predict the next token + +32 +00:01:11,610 --> 00:01:12,930 +基于之前的令牌, +based on the previous token, + +33 +00:01:12,930 --> 00:01:15,810 +这也是一个分类任务。 +which is also a classification task. + +34 +00:01:15,810 --> 00:01:17,340 +因此,如果我们想计算 +Therefore, if we want to calculate + +35 +00:01:17,340 --> 00:01:19,290 +一个例子的交叉熵, +the cross-entropy of an example, + +36 +00:01:19,290 --> 00:01:21,090 +我们可以简单地将它传递给模型 +we can simply pass it to the model + +37 +00:01:21,090 --> 00:01:23,580 +以其输入作为标签。 +with its inputs as labels. + +38 +00:01:23,580 --> 00:01:26,433 +然后损失对应于交叉熵。 +The loss then corresponds to the cross-entropy. + +39 +00:01:29,130 --> 00:01:31,110 +我们现在只差一个手术了 +We are now only a single operation away + +40 +00:01:31,110 --> 00:01:33,510 +从计算困惑度。 +from calculating the perplexity. + +41 +00:01:33,510 --> 00:01:37,710 +通过对交叉熵取幂,我们得到了困惑。 +By exponentiating the cross-entropy, we get the perplexity. + +42 +00:01:37,710 --> 00:01:40,260 +所以你看到困惑是密切相关的 +So you see that the perplexity is closely related + +43 +00:01:40,260 --> 00:01:41,163 +到损失。 +to the loss. + +44 +00:01:42,060 --> 00:01:43,380 +插入以前的结果 +Plugging in previous results + +45 +00:01:43,380 --> 00:01:47,010 +表明这相当于求幂 +shows that this is equivalent to exponentiating + +46 +00:01:47,010 --> 00:01:51,033 +每个令牌的负平均锁定概率。 +the negative average lock probability of each token. + +47 +00:01:52,050 --> 00:01:54,630 +请记住,损失只是一个弱代理 +Keep in mind that the loss is only a weak proxy + +48 +00:01:54,630 --> 00:01:57,360 +用于模型生成高质量文本的能力 +for a model's ability to generate quality text + +49 +00:01:57,360 --> 00:02:00,510 +困惑也是如此。 +and the same is true for perplexity. + +50 +00:02:00,510 --> 00:02:02,550 +出于这个原因,人们通常也计算 +For this reason, one usually also calculates + +51 +00:02:02,550 --> 00:02:03,840 +更复杂的指标 +more sophisticated metrics + +52 +00:02:03,840 --> 00:02:07,413 +例如生成任务上的 BLEU 或 ROUGE。 +such as BLEU or ROUGE on generative tasks. + +53 +00:02:08,551 --> 00:02:11,468 +(屏幕呼啸) +(screen whooshing) + diff --git a/subtitles/zh-CN/58_what-is-domain-adaptation.srt b/subtitles/zh-CN/58_what-is-domain-adaptation.srt new file mode 100644 index 000000000..7f3757b1e --- /dev/null +++ b/subtitles/zh-CN/58_what-is-domain-adaptation.srt @@ -0,0 +1,200 @@ +1 +00:00:00,000 --> 00:00:01,402 +(空气呼啸) +(air whooshing) + +2 +00:00:01,402 --> 00:00:02,720 +(笑脸拍打) +(smiley snapping) + +3 +00:00:02,720 --> 00:00:05,910 +(空气呼啸) +(air whooshing) + +4 +00:00:05,910 --> 00:00:07,923 +- 什么是领域适应? +- What is domain adaptation? + +5 +00:00:09,540 --> 00:00:12,540 +在新数据集上微调预训练模型时, +When fine-tuning a pre-trained model on a new dataset, + +6 +00:00:12,540 --> 00:00:15,480 +我们获得的微调模型将做出预测 +the fine-tuned model we obtain will make predictions + +7 +00:00:15,480 --> 00:00:17,433 +适应这个新数据集。 +that are attuned to this new dataset. + +8 +00:00:18,840 --> 00:00:21,840 +当两个模型用相同的任务训练时, +When the two models are trained with the same task, + +9 +00:00:21,840 --> 00:00:25,320 +然后我们可以比较他们对相同输入的预测。 +we can then compare their predictions on the same input. + +10 +00:00:25,320 --> 00:00:27,870 +两个模型的预测会有所不同 +The predictions of the two models will be different + +11 +00:00:27,870 --> 00:00:29,790 +以反映差异的方式 +in a way that reflects the differences + +12 +00:00:29,790 --> 00:00:31,680 +在两个数据集之间, +between the two datasets, + +13 +00:00:31,680 --> 00:00:34,053 +我们称之为领域适应的现象。 +a phenomenon we call domain adaptation. + +14 +00:00:35,310 --> 00:00:38,640 +让我们看一个带有掩码语言建模的例子 +Let's look at an example with masked language modeling + +15 +00:00:38,640 --> 00:00:41,910 +通过比较预训练的 DistilBERT 模型的输出 +by comparing the outputs of the pre-trained DistilBERT model + +16 +00:00:41,910 --> 00:00:43,080 +版本微调 +with the version fine-tuned + +17 +00:00:43,080 --> 00:00:45,273 +在课程的第 7 章中,链接如下。 +in chapter 7 of the course, linked below. + +18 +00:00:46,500 --> 00:00:49,140 +预训练模型进行通用预测, +The pre-trained model makes generic predictions, + +19 +00:00:49,140 --> 00:00:50,580 +而微调模型 +whereas the fine-tuned model + +20 +00:00:50,580 --> 00:00:53,253 +它的前两个预测与电影有关。 +has its first two predictions linked to cinema. + +21 +00:00:54,390 --> 00:00:57,210 +由于它在电影评论数据集上进行了微调, +Since it was fine-tuned on a movie reviews dataset, + +22 +00:00:57,210 --> 00:00:58,680 +看到是完全正常的 +it's perfectly normal to see + +23 +00:00:58,680 --> 00:01:01,440 +它像这样调整了它的建议。 +it adapted its suggestions like this. + +24 +00:01:01,440 --> 00:01:03,090 +注意它如何保持相同的预测 +Notice how it keeps the same prediction + +25 +00:01:03,090 --> 00:01:05,220 +作为之后的预训练模型。 +as the pre-trained model afterward. + +26 +00:01:05,220 --> 00:01:08,100 +即使微调后的模型适应了新的数据集, +Even if the fine-tuned model adapts to the new dataset, + +27 +00:01:08,100 --> 00:01:10,450 +它不会忘记预先训练的内容。 +it's not forgetting what it was pre-trained on. + +28 +00:01:11,490 --> 00:01:14,220 +这是翻译任务的另一个例子。 +This is another example on a translation task. + +29 +00:01:14,220 --> 00:01:17,310 +最重要的是,我们使用预训练的法语 / 英语模型, +On top, we use a pre-trained French/English model, + +30 +00:01:17,310 --> 00:01:21,330 +在底部,我们在第 7 章中微调的版本。 +and at the bottom, the version we fine-tuned in chapter 7. + +31 +00:01:21,330 --> 00:01:23,610 +顶级模型在大量文本上进行了预训练, +The top model is pre-trained on lots of texts, + +32 +00:01:23,610 --> 00:01:25,170 +并留下技术英语术语, +and leaves technical English terms, + +33 +00:01:25,170 --> 00:01:28,350 +像插件和电子邮件,翻译不变。 +like plugin and email, unchanged in the translation. + +34 +00:01:28,350 --> 00:01:31,350 +两者都被法国人完全理解。 +Both are perfectly understood by French people. + +35 +00:01:31,350 --> 00:01:33,780 +为微调选择的数据集是一个数据集 +The dataset picked for the fine-tuning is a dataset + +36 +00:01:33,780 --> 00:01:36,660 +特别注意的技术文本 +of technical texts where special attention was picked + +37 +00:01:36,660 --> 00:01:39,150 +用法语翻译一切。 +on translating everything in French. + +38 +00:01:39,150 --> 00:01:42,090 +结果,经过微调的模型选择了那个习惯 +As a result, the fine-tuned model picked that habit + +39 +00:01:42,090 --> 00:01:44,193 +并翻译了插件和电子邮件。 +and translated both plugin and email. + +40 +00:01:45,942 --> 00:01:50,592 +(空气呼啸) +(air whooshing) + diff --git a/subtitles/zh-CN/59_data-processing-for-translation.srt b/subtitles/zh-CN/59_data-processing-for-translation.srt new file mode 100644 index 000000000..99a02c07d --- /dev/null +++ b/subtitles/zh-CN/59_data-processing-for-translation.srt @@ -0,0 +1,275 @@ +1 +00:00:00,449 --> 00:00:01,559 +(空气呼啸) +(air whooshing) + +2 +00:00:01,559 --> 00:00:02,767 +(徽标弹出) +(logo popping) + +3 +00:00:02,767 --> 00:00:05,670 +(金属滑动) +(metal sliding) + +4 +00:00:05,670 --> 00:00:08,470 +- 让我们看看如何预处理翻译数据集。 +- Let's see how to preprocess a dataset for translation. + +5 +00:00:09,540 --> 00:00:12,420 +这是一个翻译好句子的任务 +This is a task of well translating a sentence + +6 +00:00:12,420 --> 00:00:14,310 +用另一种语言。 +in another language. + +7 +00:00:14,310 --> 00:00:17,100 +该视频将重点介绍如何预处理你的数据集 +This video will focus on how to preprocess your dataset + +8 +00:00:17,100 --> 00:00:19,950 +一旦你设法把它放在下面的格式中。 +once you've managed to put it in the following format. + +9 +00:00:19,950 --> 00:00:23,730 +一列用于输入文本,一列用于目标文本。 +One column for input texts and one for the target texts. + +10 +00:00:23,730 --> 00:00:25,980 +以下是我们如何使用数据集库实现此目的 +Here is how we can achieve this with the Datasets library + +11 +00:00:25,980 --> 00:00:29,643 +以及用于英语到法语翻译的 KDE4 数据集。 +and the KDE4 dataset for English to French translation. + +12 +00:00:30,870 --> 00:00:33,240 +只要你设法让你的数据看起来像这样, +As long as you manage to have your data look like this, + +13 +00:00:33,240 --> 00:00:35,440 +你应该能够按照相同的步骤操作。 +you should be able to follow the same steps. + +14 +00:00:36,630 --> 00:00:39,210 +这一次,我们的标签不是整数 +For once, our labels are not integers + +15 +00:00:39,210 --> 00:00:42,210 +对应于某些类,但纯文本。 +corresponding to some classes, but plain texts. + +16 +00:00:42,210 --> 00:00:45,810 +因此,我们需要将它们标记化,就像我们的输入一样。 +We will thus need to tokenize them, like our inputs. + +17 +00:00:45,810 --> 00:00:47,370 +虽然那里有一个陷阱, +There is a trap there though, + +18 +00:00:47,370 --> 00:00:49,890 +就好像你像你的输入一样标记你的目标, +as if you tokenize your targets like your inputs, + +19 +00:00:49,890 --> 00:00:51,690 +你会遇到一个问题。 +you will hit a problem. + +20 +00:00:51,690 --> 00:00:54,090 +即使你不会说法语,你也可能会注意到 +Even if you don't speak French, you might notice + +21 +00:00:54,090 --> 00:00:57,270 +目标标记化中的一些奇怪的事情。 +some weird things in the tokenization of the targets. + +22 +00:00:57,270 --> 00:01:00,510 +大多数单词都被标记为几个子标记, +Most of the words are tokenized in several subtokens, + +23 +00:01:00,510 --> 00:01:03,180 +而鱼,唯一的英语单词之一, +while fish, one of the only English word, + +24 +00:01:03,180 --> 00:01:05,670 +被标记为一个词。 +is tokenized as a single word. + +25 +00:01:05,670 --> 00:01:08,703 +那是因为我们的输入已被标记为英语。 +That's because our inputs have been tokenized as English. + +26 +00:01:09,660 --> 00:01:11,430 +由于我们的模型知道两种语言, +Since our model knows two languages, + +27 +00:01:11,430 --> 00:01:13,800 +你必须在标记目标时警告它 +you have to warn it when tokenizing the targets + +28 +00:01:13,800 --> 00:01:16,230 +所以它切换到法语模式。 +so it switches in French mode. + +29 +00:01:16,230 --> 00:01:20,010 +这是通过 as_target_tokenizer 上下文管理器完成的。 +This is done with the as_target_tokenizer context manager. + +30 +00:01:20,010 --> 00:01:23,343 +你可以看到它如何导致更紧凑的标记化。 +You can see how it results in a more compact tokenization. + +31 +00:01:24,810 --> 00:01:25,890 +处理整个数据集 +Processing the whole dataset + +32 +00:01:25,890 --> 00:01:28,440 +然后使用地图功能非常容易。 +is then super easy with the map function. + +33 +00:01:28,440 --> 00:01:30,207 +你可以选择不同的最大长度 +You can pick different maximum lengths + +34 +00:01:30,207 --> 00:01:32,100 +对于输入和目标, +for the inputs and targets, + +35 +00:01:32,100 --> 00:01:34,530 +并选择在此阶段填充到最大长度 +and choose to pad at this stage to that maximum length + +36 +00:01:34,530 --> 00:01:36,273 +通过设置 padding=max_length。 +by setting padding=max_length. + +37 +00:01:37,230 --> 00:01:39,300 +这里我们将向你展示动态填充 +Here we'll show you to pad dynamically + +38 +00:01:39,300 --> 00:01:41,013 +因为它还需要一步。 +as it requires one more step. + +39 +00:01:42,450 --> 00:01:43,470 +你的投入和目标 +Your inputs and targets + +40 +00:01:43,470 --> 00:01:46,080 +都是各种长度的句子。 +are all sentences of various lengths. + +41 +00:01:46,080 --> 00:01:48,510 +我们将分别填充输入和目标, +We will pad the inputs and targets separately, + +42 +00:01:48,510 --> 00:01:50,460 +作为输入和目标的最大长度 +as the maximum lengths of the inputs and targets + +43 +00:01:50,460 --> 00:01:51,483 +可能不同。 +might be different. + +44 +00:01:52,620 --> 00:01:54,540 +然后我们用 pad token 填充输入 +Then we pad the inputs with the pad token + +45 +00:01:54,540 --> 00:01:57,060 +以及索引为 -100 的目标 +and the targets with the -100 index + +46 +00:01:57,060 --> 00:01:58,890 +确保它们不被考虑在内 +to make sure they're not taken into account + +47 +00:01:58,890 --> 00:02:00,123 +在损失计算中。 +in the loss computation. + +48 +00:02:01,320 --> 00:02:02,153 +一旦完成, +Once this is done, + +49 +00:02:02,153 --> 00:02:04,340 +批处理输入和目标变得超级容易。 +batching inputs and targets become super easy. + +50 +00:02:05,670 --> 00:02:08,220 +Transformers 库为我们提供了数据整理器 +The Transformers library provides us with data collator + +51 +00:02:08,220 --> 00:02:10,500 +自动完成这一切。 +to do this all automatically. + +52 +00:02:10,500 --> 00:02:13,800 +然后你可以将它与你的数据集一起传递给培训师 +You can then pass it to the Trainer with your datasets + +53 +00:02:13,800 --> 00:02:15,960 +或者在 to_tf_dataset 方法中使用它 +or use it in the to_tf_dataset method + +54 +00:02:15,960 --> 00:02:18,560 +在你的(模糊的)模型上使用 model.fit () 之前。 +before using model.fit () on your (indistinct) model. + +55 +00:02:21,057 --> 00:02:23,724 +(空气呼啸) +(air whooshing) + diff --git a/subtitles/zh-CN/60_what-is-the-bleu-metric.srt b/subtitles/zh-CN/60_what-is-the-bleu-metric.srt new file mode 100644 index 000000000..e3fce657e --- /dev/null +++ b/subtitles/zh-CN/60_what-is-the-bleu-metric.srt @@ -0,0 +1,600 @@ +1 +00:00:00,147 --> 00:00:01,412 +(屏幕呼啸) +(screen whooshing) + +2 +00:00:01,412 --> 00:00:02,698 +(贴纸弹出) +(sticker popping) + +3 +00:00:02,698 --> 00:00:05,670 +(屏幕呼啸) +(screen whooshing) + +4 +00:00:05,670 --> 00:00:07,650 +- 什么是 BLEU 指标? +- What is the BLEU metric? + +5 +00:00:07,650 --> 00:00:10,170 +对于许多 NLP 任务,我们可以使用通用指标 +For many NLP tasks we can use common metrics + +6 +00:00:10,170 --> 00:00:12,810 +比如准确性或 F1 分数,但你会做什么 +like accuracy or F1 score, but what do you do + +7 +00:00:12,810 --> 00:00:14,340 +当你想衡量文本的质量时 +when you wanna measure the quality of text + +8 +00:00:14,340 --> 00:00:16,560 +那是从模型翻译过来的? +that's been translated from a model? + +9 +00:00:16,560 --> 00:00:18,750 +在本视频中,我们将了解一个广泛使用的指标 +In this video, we'll take a look at a widely used metric + +10 +00:00:18,750 --> 00:00:20,613 +用于称为 BLEU 的机器翻译。 +for machine translation called BLEU. + +11 +00:00:22,290 --> 00:00:23,940 +BLEU 背后的基本思想是分配 +The basic idea behind BLEU is to assign + +12 +00:00:23,940 --> 00:00:26,250 +翻译的单一数字分数 +a single numerical score to a translation + +13 +00:00:26,250 --> 00:00:27,450 +这告诉我们它有多好 +that tells us how good it is + +14 +00:00:27,450 --> 00:00:30,199 +与一个或多个参考翻译相比。 +compared to one or more reference translations. + +15 +00:00:30,199 --> 00:00:32,130 +在这个例子中,我们有一个西班牙语句子 +In this example, we have a sentence in Spanish + +16 +00:00:32,130 --> 00:00:35,340 +已通过某种模型翻译成英文。 +that has been translated into English by some model. + +17 +00:00:35,340 --> 00:00:37,170 +如果我们比较生成的翻译 +If we compare the generated translation + +18 +00:00:37,170 --> 00:00:39,150 +一些参考人工翻译, +to some reference human translations, + +19 +00:00:39,150 --> 00:00:41,190 +我们可以看到这个模型其实还不错, +we can see that the model is actually pretty good, + +20 +00:00:41,190 --> 00:00:43,260 +但犯了一个常见的错误。 +but has made a common error. + +21 +00:00:43,260 --> 00:00:46,050 +西班牙语单词 tengo 在英语中的意思是, +The Spanish word tengo means have in English, + +22 +00:00:46,050 --> 00:00:48,700 +这种一对一的翻译不太自然。 +and this one-to-one translation is not quite natural. + +23 +00:00:49,890 --> 00:00:51,270 +那么我们如何衡量质量 +So how can we measure the quality + +24 +00:00:51,270 --> 00:00:54,270 +以某种自动方式生成的翻译? +of a generated translation in some automatic way? + +25 +00:00:54,270 --> 00:00:56,730 +BLEU 采用的方法是比较 n-gram +The approach that BLEU takes is to compare the n-grams + +26 +00:00:56,730 --> 00:00:58,550 +生成的 n-gram 翻译 +of the generated translation to the n-grams + +27 +00:00:58,550 --> 00:01:00,390 +在参考资料中。 +in the references. + +28 +00:01:00,390 --> 00:01:02,400 +现在,n-gram 只是一种奇特的说法 +Now, an n-gram is just a fancy way of saying + +29 +00:01:02,400 --> 00:01:03,960 +一大块 n 个单词。 +a chunk of n words. + +30 +00:01:03,960 --> 00:01:05,220 +所以让我们从 unigrams 开始, +So let's start with unigrams, + +31 +00:01:05,220 --> 00:01:08,020 +对应于句子中的单个单词。 +which corresponds to the individual words in a sentence. + +32 +00:01:08,880 --> 00:01:11,250 +在此示例中,你可以看到其中四个单词 +In this example, you can see that four of the words + +33 +00:01:11,250 --> 00:01:13,140 +在生成的翻译中也发现 +in the generated translation are also found + +34 +00:01:13,140 --> 00:01:14,990 +在其中一个参考翻译中。 +in one of the reference translations. + +35 +00:01:16,350 --> 00:01:18,240 +一旦我们找到了我们的比赛, +And once we've found our matches, + +36 +00:01:18,240 --> 00:01:20,130 +一种给译文打分的方法 +one way to assign a score to the translation + +37 +00:01:20,130 --> 00:01:23,070 +是计算 unigrams 的精度。 +is to compute the precision of the unigrams. + +38 +00:01:23,070 --> 00:01:25,200 +这意味着我们只计算匹配词的数量 +This means we just count the number of matching words + +39 +00:01:25,200 --> 00:01:27,360 +在生成的和参考的翻译中 +in the generated and reference translations + +40 +00:01:27,360 --> 00:01:29,660 +并通过除以单词数来归一化计数 +and normalize the count by dividing by the number of words + +41 +00:01:29,660 --> 00:01:30,753 +在这一代。 +in the generation. + +42 +00:01:31,800 --> 00:01:34,080 +在这个例子中,我们找到了四个匹配的词 +In this example, we found four matching words + +43 +00:01:34,080 --> 00:01:36,033 +而我们这一代人有五个字。 +and our generation has five words. + +44 +00:01:37,140 --> 00:01:39,690 +现在,一般来说,精度范围从零到一, +Now, in general, precision ranges from zero to one, + +45 +00:01:39,690 --> 00:01:42,390 +更高的精度分数意味着更好的翻译。 +and higher precision scores mean a better translation. + +46 +00:01:44,160 --> 00:01:45,570 +但这并不是故事的全部 +But this isn't really the whole story + +47 +00:01:45,570 --> 00:01:47,310 +因为 unigram 精度有一个问题 +because one problem with unigram precision + +48 +00:01:47,310 --> 00:01:49,140 +翻译模型有时会卡住吗 +is that translation models sometimes get stuck + +49 +00:01:49,140 --> 00:01:51,330 +以重复的方式重复同一个词 +in repetitive patterns and just repeat the same word + +50 +00:01:51,330 --> 00:01:52,293 +几次。 +several times. + +51 +00:01:53,160 --> 00:01:54,690 +如果我们只计算单词匹配的数量, +If we just count the number of word matches, + +52 +00:01:54,690 --> 00:01:56,370 +我们可以获得非常高的精度分数 +we can get really high precision scores + +53 +00:01:56,370 --> 00:01:57,840 +虽然翻译很烂 +even though the translation is terrible + +54 +00:01:57,840 --> 00:01:59,090 +从人的角度来看! +from a human perspective! + +55 +00:02:00,000 --> 00:02:02,970 +例如,如果我们的模型只生成单词 six, +For example, if our model just generates the word six, + +56 +00:02:02,970 --> 00:02:05,020 +我们得到了完美的 unigram 精度分数。 +we get a perfect unigram precision score. + +57 +00:02:06,960 --> 00:02:09,930 +所以为了处理这个问题,BLEU 使用了修改后的精度 +So to handle this, BLEU uses a modified precision + +58 +00:02:09,930 --> 00:02:12,210 +剪掉计算一个单词的次数, +that clips the number of times to count a word, + +59 +00:02:12,210 --> 00:02:13,680 +基于最大次数 +based on the maximum number of times + +60 +00:02:13,680 --> 00:02:16,399 +它出现在参考翻译中。 +it appears in the reference translation. + +61 +00:02:16,399 --> 00:02:18,630 +在这个例子中,单词 six 只出现了一次 +In this example, the word six only appears once + +62 +00:02:18,630 --> 00:02:21,360 +在参考中,所以我们把分子剪成一 +in the reference, so we clip the numerator to one + +63 +00:02:21,360 --> 00:02:22,710 +和修改后的 unigram 精度 +and the modified unigram precision + +64 +00:02:22,710 --> 00:02:25,233 +现在给出的分数比预期的要低得多。 +now gives a much lower score as expected. + +65 +00:02:27,660 --> 00:02:29,400 +unigram 精度的另一个问题 +Another problem with unigram precision + +66 +00:02:29,400 --> 00:02:30,780 +是它没有考虑到 +is that it doesn't take into account + +67 +00:02:30,780 --> 00:02:33,900 +单词在翻译中出现的顺序。 +the order in which the words appear in the translations. + +68 +00:02:33,900 --> 00:02:35,700 +例如,假设我们有 Yoda +For example, suppose we had Yoda + +69 +00:02:35,700 --> 00:02:37,410 +翻译我们的西班牙语句子, +translate our Spanish sentence, + +70 +00:02:37,410 --> 00:02:39,457 +那么我们可能会得到一些倒退的东西,比如, +then we might get something backwards like, + +71 +00:02:39,457 --> 00:02:42,450 +“我已经六十岁了。” +"Years sixty thirty have I." + +72 +00:02:42,450 --> 00:02:44,670 +在这种情况下,修改后的 unigram 精度 +In this case, the modified unigram precision + +73 +00:02:44,670 --> 00:02:47,393 +给出了高精度,这并不是我们真正想要的。 +gives a high precision which is not really what we want. + +74 +00:02:48,480 --> 00:02:50,460 +所以要处理词序问题, +So to deal with word ordering problems, + +75 +00:02:50,460 --> 00:02:52,020 +BLEU 实际计算精度 +BLEU actually computes the precision + +76 +00:02:52,020 --> 00:02:55,410 +对于几个不同的 n-gram,然后对结果进行平均。 +for several different n-grams and then averages the result. + +77 +00:02:55,410 --> 00:02:57,300 +例如,如果我们比较 4-grams, +For example, if we compare 4-grams, + +78 +00:02:57,300 --> 00:02:58,830 +我们可以看到没有匹配的块 +we can see that there are no matching chunks + +79 +00:02:58,830 --> 00:03:01,020 +翻译中的四个词, +of four words in the translations, + +80 +00:03:01,020 --> 00:03:02,913 +所以 4 克精度为 0。 +and so the 4-gram precision is 0. + +81 +00:03:05,460 --> 00:03:07,560 +现在,计算数据集库中的 BLEU 分数 +Now, to compute BLEU scores in Datasets library + +82 +00:03:07,560 --> 00:03:09,120 +真的很简单。 +is really very simple. + +83 +00:03:09,120 --> 00:03:11,100 +你只需使用 load_metric 函数, +You just use the load_metric function, + +84 +00:03:11,100 --> 00:03:13,290 +为模型的预测提供参考 +provide your model's predictions with their references + +85 +00:03:13,290 --> 00:03:14,390 +你很高兴去! +and you're good to go! + +86 +00:03:16,470 --> 00:03:19,200 +输出将包含几个感兴趣的字段。 +The output will contain several fields of interest. + +87 +00:03:19,200 --> 00:03:20,490 +精度字段包含 +The precisions field contains + +88 +00:03:20,490 --> 00:03:23,133 +每个 n-gram 的所有单独精度分数。 +all the individual precision scores for each n-gram. + +89 +00:03:25,050 --> 00:03:26,940 +然后计算 BLEU 分数本身 +The BLEU score itself is then calculated + +90 +00:03:26,940 --> 00:03:30,090 +通过取精度分数的几何平均值。 +by taking the geometric mean of the precision scores. + +91 +00:03:30,090 --> 00:03:32,790 +默认情况下,所有四个 n-gram 精度的平均值 +And by default, the mean of all four n-gram precisions + +92 +00:03:32,790 --> 00:03:35,793 +据报道,该指标有时也称为 BLEU-4。 +is reported, a metric that is sometimes also called BLEU-4. + +93 +00:03:36,660 --> 00:03:38,880 +在此示例中,我们可以看到 BLEU 分数为零 +In this example, we can see the BLEU score is zero + +94 +00:03:38,880 --> 00:03:40,780 +因为 4 克精度为零。 +because the 4-gram precision was zero. + +95 +00:03:43,290 --> 00:03:45,390 +现在,BLEU 指标有一些不错的特性, +Now, the BLEU metric has some nice properties, + +96 +00:03:45,390 --> 00:03:47,520 +但这远非一个完美的指标。 +but it is far from a perfect metric. + +97 +00:03:47,520 --> 00:03:49,440 +好的特性是它很容易计算 +The good properties are that it's easy to compute + +98 +00:03:49,440 --> 00:03:50,970 +它被广泛用于研究 +and it's widely used in research + +99 +00:03:50,970 --> 00:03:52,620 +这样你就可以将你的模型与其他模型进行比较 +so you can compare your model against others + +100 +00:03:52,620 --> 00:03:54,630 +在共同的基准上。 +on common benchmarks. + +101 +00:03:54,630 --> 00:03:56,670 +另一方面,BLEU 有几个大问题, +On the other hand, there are several big problems with BLEU, + +102 +00:03:56,670 --> 00:03:58,830 +包括它不包含语义的事实 +including the fact it doesn't incorporate semantics + +103 +00:03:58,830 --> 00:04:01,920 +它在非英语语言上很挣扎。 +and it struggles a lot on non-English languages. + +104 +00:04:01,920 --> 00:04:02,790 +BLEU 的另一个问题 +Another problem with BLEU + +105 +00:04:02,790 --> 00:04:04,620 +是它假定人工翻译 +is that it assumes the human translations + +106 +00:04:04,620 --> 00:04:05,820 +已经被代币化 +have already been tokenized + +107 +00:04:05,820 --> 00:04:07,320 +这使得比较模型变得困难 +and this makes it hard to compare models + +108 +00:04:07,320 --> 00:04:08,820 +使用不同的分词器。 +that use different tokenizers. + +109 +00:04:10,590 --> 00:04:12,570 +所以正如我们所见,衡量文本的质量 +So as we've seen, measuring the quality of texts + +110 +00:04:12,570 --> 00:04:15,570 +仍然是 NLP 研究中的一个困难和开放的问题。 +is still a difficult and open problem in NLP research. + +111 +00:04:15,570 --> 00:04:17,580 +对于机器翻译,目前的推荐 +For machine translation, the current recommendation + +112 +00:04:17,580 --> 00:04:19,440 +是使用 SacreBLEU 指标, +is to use the SacreBLEU metric, + +113 +00:04:19,440 --> 00:04:22,830 +它解决了 BLEU 的标记化限制。 +which addresses the tokenization limitations of BLEU. + +114 +00:04:22,830 --> 00:04:24,360 +正如你在此示例中所见, +As you can see in this example, + +115 +00:04:24,360 --> 00:04:26,580 +计算 SacreBLEU 分数几乎相同 +computing the SacreBLEU score is almost identical + +116 +00:04:26,580 --> 00:04:28,020 +到 BLEU 一个。 +to the BLEU one. + +117 +00:04:28,020 --> 00:04:30,360 +主要区别在于我们现在传递一个文本列表 +The main difference is that we now pass a list of texts + +118 +00:04:30,360 --> 00:04:32,640 +而不是翻译的单词列表, +instead of a list of words to the translations, + +119 +00:04:32,640 --> 00:04:35,640 +SacreBLEU 负责底层的代币化。 +and SacreBLEU takes care of the tokenization under the hood. + +120 +00:04:36,582 --> 00:04:39,499 +(屏幕呼啸) +(screen whooshing) + diff --git a/subtitles/zh-CN/61_data-processing-for-summarization.srt b/subtitles/zh-CN/61_data-processing-for-summarization.srt new file mode 100644 index 000000000..1a567a7ed --- /dev/null +++ b/subtitles/zh-CN/61_data-processing-for-summarization.srt @@ -0,0 +1,240 @@ +1 +00:00:00,227 --> 00:00:01,359 +(空气呼啸) +(air whooshing) + +2 +00:00:01,359 --> 00:00:02,610 +(笑脸点击) +(smiley clicking) + +3 +00:00:02,610 --> 00:00:05,550 +(空气呼啸) +(air whooshing) + +4 +00:00:05,550 --> 00:00:08,450 +- 让我们看看如何预处理数据集以进行汇总。 +- Let's see how to preprocess a dataset for summarization. + +5 +00:00:09,750 --> 00:00:13,083 +这是总结一份长文档的任务。 +This is the task of, well, summarizing a long document. + +6 +00:00:14,040 --> 00:00:16,830 +该视频将重点介绍如何预处理你的数据集 +This video will focus on how to preprocess your dataset + +7 +00:00:16,830 --> 00:00:19,680 +一旦你设法将其放入以下格式: +once you have managed to put it in the following format: + +8 +00:00:19,680 --> 00:00:21,510 +一栏用于长文件, +one column for the long documents, + +9 +00:00:21,510 --> 00:00:23,610 +和一个摘要。 +and one for the summaries. + +10 +00:00:23,610 --> 00:00:24,930 +这是我们如何实现这一目标 +Here is how we can achieve this + +11 +00:00:24,930 --> 00:00:27,573 +使用 XSUM 数据集上的数据集库。 +with the Datasets library on the XSUM dataset. + +12 +00:00:28,650 --> 00:00:30,810 +只要你设法让你的数据看起来像这样, +As long as you manage to have your data look like this, + +13 +00:00:30,810 --> 00:00:33,690 +你应该能够按照相同的步骤操作。 +you should be able to follow the same steps. + +14 +00:00:33,690 --> 00:00:35,880 +这一次,我们的标签不是整数 +For once, our labels are not integers + +15 +00:00:35,880 --> 00:00:39,150 +对应于某些类,但纯文本。 +corresponding to some classes, but plain text. + +16 +00:00:39,150 --> 00:00:42,480 +因此,我们需要将它们标记化,就像我们的输入一样。 +We will thus need to tokenize them, like our inputs. + +17 +00:00:42,480 --> 00:00:43,920 +虽然那里有一个小陷阱, +There is a small trap there though, + +18 +00:00:43,920 --> 00:00:45,360 +因为我们需要标记我们的目标 +as we need to tokenize our targets + +19 +00:00:45,360 --> 00:00:48,690 +在 as_target_tokenizer 上下文管理器中。 +inside the as_target_tokenizer context manager. + +20 +00:00:48,690 --> 00:00:51,030 +这是因为我们添加的特殊标记 +This is because the special tokens we add + +21 +00:00:51,030 --> 00:00:54,000 +输入和目标可能略有不同, +might be slightly different for the inputs and the target, + +22 +00:00:54,000 --> 00:00:57,300 +所以 tokenizer 必须知道它正在处理哪个。 +so the tokenizer has to know which one it is processing. + +23 +00:00:57,300 --> 00:00:59,550 +处理整个数据集非常容易 +Processing the whole dataset is then super easy + +24 +00:00:59,550 --> 00:01:01,290 +与地图功能。 +with the map function. + +25 +00:01:01,290 --> 00:01:03,450 +由于摘要通常要短得多 +Since the summaries are usually much shorter + +26 +00:01:03,450 --> 00:01:05,400 +比文件,你绝对应该选择 +than the documents, you should definitely pick + +27 +00:01:05,400 --> 00:01:08,880 +输入和目标的不同最大长度。 +different maximum lengths for the inputs and targets. + +28 +00:01:08,880 --> 00:01:11,730 +你可以选择在此阶段填充到最大长度 +You can choose to pad at this stage to that maximum length + +29 +00:01:11,730 --> 00:01:14,070 +通过设置 padding=max_length。 +by setting padding=max_length. + +30 +00:01:14,070 --> 00:01:16,170 +在这里,我们将向你展示如何动态填充, +Here we'll show you how to pad dynamically, + +31 +00:01:16,170 --> 00:01:17,620 +因为它还需要一步。 +as it requires one more step. + +32 +00:01:18,840 --> 00:01:20,910 +你的输入和目标都是句子 +Your inputs and targets are all sentences + +33 +00:01:20,910 --> 00:01:22,620 +各种长度。 +of various lengths. + +34 +00:01:22,620 --> 00:01:24,960 +我们将分别填充输入和目标 +We'll pad the inputs and targets separately + +35 +00:01:24,960 --> 00:01:27,030 +作为输入和目标的最大长度 +as the maximum lengths of the inputs and targets + +36 +00:01:27,030 --> 00:01:28,280 +是完全不同的。 +are completely different. + +37 +00:01:29,130 --> 00:01:31,170 +然后,我们将输入填充到最大长度 +Then, we pad the inputs to the maximum lengths + +38 +00:01:31,170 --> 00:01:33,813 +在输入之间,对于目标也是如此。 +among the inputs, and same for the target. + +39 +00:01:34,860 --> 00:01:36,630 +我们用 pad token 填充输入, +We pad the input with the pad token, + +40 +00:01:36,630 --> 00:01:39,000 +以及索引为 -100 的目标 +and the targets with the -100 index + +41 +00:01:39,000 --> 00:01:40,980 +确保不考虑它们 +to make sure they are not taken into account + +42 +00:01:40,980 --> 00:01:42,180 +在损失计算中。 +in the loss computation. + +43 +00:01:43,440 --> 00:01:45,180 +变形金刚库为我们提供 +The Transformers library provide us + +44 +00:01:45,180 --> 00:01:48,510 +使用数据整理器自动完成这一切。 +with a data collator to do this all automatically. + +45 +00:01:48,510 --> 00:01:51,690 +然后你可以将它与你的数据集一起传递给培训师, +You can then pass it to the Trainer with your datasets, + +46 +00:01:51,690 --> 00:01:55,710 +或者在使用 model.fit 之前在 to_tf_dataset 方法中使用它 +or use it in the to_tf_dataset method before using model.fit + +47 +00:01:55,710 --> 00:01:56,823 +在你当前的模型上。 +on your current model. + +48 +00:01:58,339 --> 00:02:02,876 +(空气呼啸) +(air whooshing) + diff --git a/subtitles/zh-CN/62_what-is-the-rouge-metric.srt b/subtitles/zh-CN/62_what-is-the-rouge-metric.srt new file mode 100644 index 000000000..ea7ea6666 --- /dev/null +++ b/subtitles/zh-CN/62_what-is-the-rouge-metric.srt @@ -0,0 +1,510 @@ +1 +00:00:00,624 --> 00:00:03,374 +(徽标呼啸而过) +(logo whooshing) + +2 +00:00:05,700 --> 00:00:07,740 +- 什么是 ROUGE 指标? +- What is the ROUGE metric? + +3 +00:00:07,740 --> 00:00:08,880 +对于许多 NLP 任务 +For many NLP tasks + +4 +00:00:08,880 --> 00:00:12,270 +我们可以使用常见的指标,如准确性或 F1 分数。 +we can use common metrics like accuracy or F1 score. + +5 +00:00:12,270 --> 00:00:13,650 +但是当你想测量某样东西时你会怎么做 +But what do you do when you wanna measure something + +6 +00:00:13,650 --> 00:00:16,920 +喜欢 T5 这样的模型的摘要质量? +like the quality of a summary from a model like T5? + +7 +00:00:16,920 --> 00:00:18,180 +在本视频中,我们将了解 +In this video, we'll take a look + +8 +00:00:18,180 --> 00:00:21,180 +在广泛使用的技术摘要指标中,称为 ROUGE。 +at a widely used metric for tech summarization called ROUGE. + +9 +00:00:22,740 --> 00:00:24,660 +ROUGE 实际上有几种变体 +There are actually several variants of ROUGE + +10 +00:00:24,660 --> 00:00:26,190 +但所有这些背后的基本思想 +but the basic idea behind all of them + +11 +00:00:26,190 --> 00:00:27,840 +是分配一个单一的数字分数 +is to assign a single numerical score + +12 +00:00:27,840 --> 00:00:30,000 +总结告诉我们它有多好 +to a summary that tells us how good it is + +13 +00:00:30,000 --> 00:00:32,774 +与一个或多个参考摘要相比。 +compared to one or more reference summaries. + +14 +00:00:32,774 --> 00:00:34,020 +在这个例子中,我们有一个书评 +In this example, we have a book review + +15 +00:00:34,020 --> 00:00:36,570 +这已经被一些模型总结了。 +that has been summarized by some model. + +16 +00:00:36,570 --> 00:00:38,320 +如果我们比较生成的摘要 +If we compare the generated summary + +17 +00:00:39,168 --> 00:00:40,260 +参考一些人类总结,我们可以看到 +to some reference human summaries, we can see + +18 +00:00:40,260 --> 00:00:42,841 +该模型实际上非常好 +that the model is actually pretty good + +19 +00:00:42,841 --> 00:00:44,063 +并且只相差一两个词。 +and only differs by a word or two. + +20 +00:00:45,060 --> 00:00:46,260 +那么我们如何衡量质量 +So how can we measure the quality + +21 +00:00:46,260 --> 00:00:49,050 +以自动方式生成的摘要? +of a generated summary in an automatic way? + +22 +00:00:49,050 --> 00:00:51,510 +ROUGE 采用的方法是比较 n-gram +The approach that ROUGE takes is to compare the n-grams + +23 +00:00:51,510 --> 00:00:55,200 +生成的摘要到参考文献的 n-gram。 +of the generated summary to the n-grams of the references. + +24 +00:00:55,200 --> 00:00:58,590 +n-gram 只是一种表达 N 个词块的奇特方式。 +And n-gram is just a fancy way of saying a chunk of N words. + +25 +00:00:58,590 --> 00:01:00,030 +所以让我们从 unigrams 开始 +So let's start with unigrams + +26 +00:01:00,030 --> 00:01:02,780 +对应于句子中的各个单词。 +which correspond to the individual words in a sentence. + +27 +00:01:03,780 --> 00:01:05,250 +在这个例子中,你可以看到六个 +In this example, you can see that six + +28 +00:01:05,250 --> 00:01:07,650 +生成的摘要中的单词也被发现 +of the words in the generated summary are also found + +29 +00:01:07,650 --> 00:01:09,420 +在其中一个参考摘要中。 +in one of the reference summaries. + +30 +00:01:09,420 --> 00:01:11,310 +以及比较 unigrams 的 rouge metric +And the rouge metric that compares unigrams + +31 +00:01:11,310 --> 00:01:12,260 +被称为 ROUGE-1。 +is called ROUGE-1. + +32 +00:01:14,533 --> 00:01:16,770 +现在我们找到了我们的比赛,一种分配分数的方法 +Now that we found our matches, one way to assign a score + +33 +00:01:16,770 --> 00:01:20,280 +总结是计算 unigrams 的召回率。 +to the summary is to compute the recall of the unigrams. + +34 +00:01:20,280 --> 00:01:21,540 +这意味着我们只计算数量 +This means we just count the number + +35 +00:01:21,540 --> 00:01:22,950 +生成的匹配词的 +of matching words in the generated + +36 +00:01:22,950 --> 00:01:25,290 +和参考摘要并规范化计数 +and reference summaries and normalize the count + +37 +00:01:25,290 --> 00:01:28,200 +除以参考文献中的单词数。 +by dividing by the number of words in the reference. + +38 +00:01:28,200 --> 00:01:30,450 +在这个例子中,我们找到了六个匹配的词 +In this example, we found six matching words + +39 +00:01:30,450 --> 00:01:32,160 +我们的参考有六个词。 +and our reference has six words. + +40 +00:01:32,160 --> 00:01:33,933 +所以我们的 unigram 召回是完美的。 +So our unigram recall is perfect. + +41 +00:01:34,800 --> 00:01:35,810 +这意味着所有的词 +This means that all of the words + +42 +00:01:35,810 --> 00:01:37,500 +在参考摘要中已经产生 +in the reference summary have been produced + +43 +00:01:37,500 --> 00:01:38,550 +在生成的那个。 +in the generated one. + +44 +00:01:40,050 --> 00:01:42,360 +现在,完美回忆听起来不错,但想象一下 +Now, perfect recall sounds great, but imagine + +45 +00:01:42,360 --> 00:01:44,520 +如果我们生成的摘要是这样的 +if our generated summary have been something like + +46 +00:01:44,520 --> 00:01:45,720 +我真的,真的,真的, +I really, really, really, + +47 +00:01:45,720 --> 00:01:48,150 +真的很喜欢读饥饿游戏。 +really loved reading the Hunger Games. + +48 +00:01:48,150 --> 00:01:49,378 +这也会有完美的召回 +This would also have perfect recall + +49 +00:01:49,378 --> 00:01:51,330 +但可以说是一个更糟糕的总结, +but is arguably a worse summary, + +50 +00:01:51,330 --> 00:01:52,653 +因为它很冗长。 +since it is verbose. + +51 +00:01:53,550 --> 00:01:54,600 +为了应对这些场景, +To deal with these scenarios, + +52 +00:01:54,600 --> 00:01:56,190 +我们还可以计算精度, +we can also compute precision, + +53 +00:01:56,190 --> 00:01:58,380 +在 ROUGE 上下文中,它衡量了多少 +which in the ROUGE context measures how much + +54 +00:01:58,380 --> 00:02:00,810 +生成器摘要的相关性。 +of the generator summary was relevant. + +55 +00:02:00,810 --> 00:02:03,630 +在实践中,通常计算精度和召回率 +In practice, both precision and recall are usually computed + +56 +00:02:03,630 --> 00:02:05,493 +然后报告 F1 分数。 +and then the F1 score is reported. + +57 +00:02:07,170 --> 00:02:08,542 +现在我们可以改变粒度 +Now we can change the granularity + +58 +00:02:08,542 --> 00:02:13,020 +通过比较双字母组而不是单字母组来进行比较。 +of the comparison by comparing bigrams instead of unigrams. + +59 +00:02:13,020 --> 00:02:15,090 +使用双字母组,我们将句子成对地分块 +With bigrams, we chunk the sentence into pairs + +60 +00:02:15,090 --> 00:02:17,910 +连续的单词,然后计算有多少对 +of consecutive words and then count how many pairs + +61 +00:02:17,910 --> 00:02:21,360 +在生成的摘要中出现在参考摘要中。 +in the generated summary are present in the reference one. + +62 +00:02:21,360 --> 00:02:23,880 +这给了我们 ROUGE-2 精确度和召回率 +This gives us ROUGE-2 precision and recall + +63 +00:02:23,880 --> 00:02:24,780 +正如我们所见, +which as we can see, + +64 +00:02:24,780 --> 00:02:27,780 +低于之前的 ROUGE-1 分数。 +is lower than the ROUGE-1 scores from earlier. + +65 +00:02:27,780 --> 00:02:29,400 +现在,如果摘要很长, +Now, if the summaries are long, + +66 +00:02:29,400 --> 00:02:31,740 +ROUGE-2 分数通常会很小 +the ROUGE-2 scores will generally be small + +67 +00:02:31,740 --> 00:02:34,290 +因为要匹配的 bios 更少。 +because there are fewer bios to match. + +68 +00:02:34,290 --> 00:02:36,870 +这也适用于摘要摘要。 +And this is also true for abstracter summarization. + +69 +00:02:36,870 --> 00:02:39,993 +所以通常会报告 ROUGE-1 和 ROUGE-2 分数。 +So both ROUGE-1 and ROUGE-2 scores are usually reported. + +70 +00:02:42,000 --> 00:02:45,330 +我们将讨论的最后一个 ROUGE 变体是 ROUGE L。 +The last ROUGE variant we will discuss is ROUGE L. + +71 +00:02:45,330 --> 00:02:47,160 +ROUGE L 不比较 ngrams +ROUGE L doesn't compare ngrams + +72 +00:02:47,160 --> 00:02:49,572 +而是将每个摘要视为一系列单词 +but instead treats each summary as a sequence of words + +73 +00:02:49,572 --> 00:02:53,403 +然后寻找最长的公共子序列或 LCS。 +and then looks for the longest common subsequence or LCS. + +74 +00:02:54,775 --> 00:02:56,130 +子序列是出现的序列 +A subsequence is a sequence that appears + +75 +00:02:56,130 --> 00:02:59,760 +以相同的相对顺序,但不一定是连续的。 +in the same relative order, but not necessarily contiguous. + +76 +00:02:59,760 --> 00:03:03,210 +所以在这个例子中,我喜欢阅读饥饿游戏, +So in this example, I loved reading the Hunger Games, + +77 +00:03:03,210 --> 00:03:06,930 +是两个摘要之间最长的公共子序列。 +is the longest common subsequence between the two summaries. + +78 +00:03:06,930 --> 00:03:08,610 +而 ROUGE L 的主要优势 +And the main advantage of ROUGE L + +79 +00:03:08,610 --> 00:03:11,670 +超过 ROUGE-1 或 ROUGE-2 的是它不依赖于 +over ROUGE-1 or ROUGE-2 is that it doesn't depend + +80 +00:03:11,670 --> 00:03:14,100 +在连续的 n-gram 匹配上,所以它倾向于 +on consecutive n-gram matches, and so it tends + +81 +00:03:14,100 --> 00:03:16,650 +更准确地捕捉句子结构。 +to capture sentence structure much more accurately. + +82 +00:03:18,150 --> 00:03:19,440 +现在计算 ROUGE 分数 +Now to compute ROUGE scores + +83 +00:03:19,440 --> 00:03:21,660 +在数据集库中很简单。 +in the data sets library is very simple. + +84 +00:03:21,660 --> 00:03:23,910 +你只需使用负载度量函数, +You just use the load metric function, + +85 +00:03:23,910 --> 00:03:26,400 +提供你的模型摘要以及参考资料 +provide your model summaries along with the references + +86 +00:03:26,400 --> 00:03:27,500 +你可以开始了。 +and you're good to go. + +87 +00:03:28,770 --> 00:03:30,120 +计算的输出 +The output from the calculation + +88 +00:03:30,120 --> 00:03:31,507 +包含很多信息。 +contains a lot of information. + +89 +00:03:31,507 --> 00:03:34,560 +我们首先可以看到的是置信区间 +The first thing we can see is that the confidence intervals + +90 +00:03:34,560 --> 00:03:36,090 +提供每个 ROUGE 分数 +of each ROUGE score are provided + +91 +00:03:36,090 --> 00:03:39,030 +在低、中、高领域。 +in the low, mid and high fields. + +92 +00:03:39,030 --> 00:03:40,980 +如果你想知道传播,这真的很有用 +This is really useful if you wanna know the spread + +93 +00:03:40,980 --> 00:03:43,730 +比较两个或多个模型时的 ROUGE 分数。 +of your ROUGE scores when comparing two or more models. + +94 +00:03:45,090 --> 00:03:46,050 +第二点要注意 +The second thing to notice + +95 +00:03:46,050 --> 00:03:48,330 +是我们有四种类型的 ROUGE 分数。 +is that we have four types of ROUGE score. + +96 +00:03:48,330 --> 00:03:51,480 +我们已经看过 ROUGE-1、ROUGE-2 和 ROUGE-L +We've already seen ROUGE-1, ROUGE-2 and ROUGE-L + +97 +00:03:51,480 --> 00:03:53,760 +那么什么是 ROUGE-L sum 呢? +So what is ROUGE-L sum? + +98 +00:03:53,760 --> 00:03:55,410 +好吧,ROUGEL 的总和 +Well, the sum in ROUGEL's sum + +99 +00:03:55,410 --> 00:03:57,630 +指的是这个指标是计算出来的 +refers to the fact that this metric is computed + +100 +00:03:57,630 --> 00:04:00,240 +在计算 ROUGE-L 时在整个摘要上 +over a whole summary while ROUGE-L is computed + +101 +00:04:00,240 --> 00:04:02,493 +作为单个句子的平均值。 +as the average of individual sentences. + +102 +00:04:04,166 --> 00:04:06,916 +(徽标呼啸而过) +(logo whooshing) + diff --git a/subtitles/zh-CN/63_data-processing-for-causal-language-modeling.srt b/subtitles/zh-CN/63_data-processing-for-causal-language-modeling.srt new file mode 100644 index 000000000..b6615e106 --- /dev/null +++ b/subtitles/zh-CN/63_data-processing-for-causal-language-modeling.srt @@ -0,0 +1,470 @@ +1 +00:00:00,000 --> 00:00:02,917 +(过渡音乐) +(transition music) + +2 +00:00:05,364 --> 00:00:08,310 +- 在这个视频中,我们来看看数据处理 +- In this video, we take a look at the data processing + +3 +00:00:08,310 --> 00:00:10,803 +训练因果语言模型所必需的。 +necessary to train causal language models. + +4 +00:00:12,690 --> 00:00:14,400 +因果语言建模是任务 +Causal language modeling is the task + +5 +00:00:14,400 --> 00:00:17,820 +基于先前的标记预测下一个标记。 +of predicting the next token based on the previous ones. + +6 +00:00:17,820 --> 00:00:19,680 +因果语言建模的另一个术语 +Another term for causal language modeling + +7 +00:00:19,680 --> 00:00:21,000 +是自回归建模。 +is autoregressive modeling. + +8 +00:00:21,000 --> 00:00:23,940 +在你可以在此处看到的示例中, +In the example that you can see here, + +9 +00:00:23,940 --> 00:00:25,560 +例如,下一个标记可以是 +the next token could, for example, + +10 +00:00:25,560 --> 00:00:28,263 +是 NLP,也可能是机器学习。 +be NLP or it could be machine learning. + +11 +00:00:29,460 --> 00:00:31,457 +因果语言模型的一个流行示例 +A popular example of causal language models + +12 +00:00:31,457 --> 00:00:33,693 +是 GPT 系列模型。 +is the GPT family of models. + +13 +00:00:35,561 --> 00:00:38,010 +训练 GPT 等模型, +To train models such as GPT, + +14 +00:00:38,010 --> 00:00:41,460 +我们通常从大量文本文件开始。 +we usually start with a large corpus of text files. + +15 +00:00:41,460 --> 00:00:43,890 +这些文件可以是从互联网上抓取的网页 +These files can be webpages scraped from the internet + +16 +00:00:43,890 --> 00:00:46,020 +例如 Common Crawl 数据集 +such as the Common Crawl dataset + +17 +00:00:46,020 --> 00:00:47,940 +或者它们可以是来自 GitHub 的 Python 文件, +or they can be Python files from GitHub, + +18 +00:00:47,940 --> 00:00:49,490 +就像你在这里看到的一样。 +like the ones you can see here. + +19 +00:00:50,400 --> 00:00:52,680 +作为第一步,我们需要标记这些文件 +As a first step, we need to tokenize these files + +20 +00:00:52,680 --> 00:00:55,380 +这样我们就可以通过模型喂养他们。 +such that we can feed them through the model. + +21 +00:00:55,380 --> 00:00:58,500 +在这里,我们将标记化的文本显示为不同长度的条, +Here, we show the tokenized texts as bars of various length, + +22 +00:00:58,500 --> 00:01:02,188 +说明它们越来越短。 +illustrating that they're shorter and longer ones. + +23 +00:01:02,188 --> 00:01:05,910 +这在处理文本时很常见。 +This is very common when working with text. + +24 +00:01:05,910 --> 00:01:09,270 +但是,转换模型的上下文窗口有限 +However, transform models have a limited context window + +25 +00:01:09,270 --> 00:01:10,770 +并根据数据源, +and depending on the data source, + +26 +00:01:10,770 --> 00:01:13,140 +标记化的文本可能 +it is possible that the tokenized texts + +27 +00:01:13,140 --> 00:01:15,183 +比这个窗口长得多。 +are much longer than this window. + +28 +00:01:16,080 --> 00:01:18,870 +在这种情况下,我们可以截断序列 +In this case, we could just truncate the sequences + +29 +00:01:18,870 --> 00:01:20,182 +上下文长度, +to the context length, + +30 +00:01:20,182 --> 00:01:22,650 +但这意味着我们将失去一切 +but this would mean that we lose everything + +31 +00:01:22,650 --> 00:01:24,513 +在第一个上下文窗口之后。 +after the first context window. + +32 +00:01:25,500 --> 00:01:28,410 +使用返回溢出令牌标志, +Using the return overflowing token flag, + +33 +00:01:28,410 --> 00:01:30,960 +我们可以使用分词器来创建块 +we can use the tokenizer to create chunks + +34 +00:01:30,960 --> 00:01:33,510 +每个都是上下文长度的大小。 +with each one being the size of the context length. + +35 +00:01:34,860 --> 00:01:36,180 +有时,它仍然会发生 +Sometimes, it can still happen + +36 +00:01:36,180 --> 00:01:37,590 +最后一块太短了 +that the last chunk is too short + +37 +00:01:37,590 --> 00:01:39,900 +如果没有足够的令牌来填充它。 +if there aren't enough tokens to fill it. + +38 +00:01:39,900 --> 00:01:41,793 +在这种情况下,我们可以将其删除。 +In this case, we can just remove it. + +39 +00:01:42,990 --> 00:01:45,960 +使用 return_length 关键字, +With the return_length keyword, + +40 +00:01:45,960 --> 00:01:49,173 +我们还从分词器中获取每个块的长度。 +we also get the length of each chunk from the tokenizer. + +41 +00:01:51,960 --> 00:01:53,640 +此函数显示所有步骤 +This function shows all the steps + +42 +00:01:53,640 --> 00:01:56,280 +准备数据集所必需的。 +necessary to prepare the dataset. + +43 +00:01:56,280 --> 00:01:57,960 +首先,我们标记数据集 +First, we tokenize the dataset + +44 +00:01:57,960 --> 00:02:00,330 +用我刚才提到的标志。 +with the flags I just mentioned. + +45 +00:02:00,330 --> 00:02:02,190 +然后,我们遍历每个块 +Then, we go through each chunk + +46 +00:02:02,190 --> 00:02:04,680 +如果它的长度与上下文长度匹配, +and if it's length matches the context length, + +47 +00:02:04,680 --> 00:02:06,663 +我们将它添加到我们返回的输入中。 +we add it to the inputs we return. + +48 +00:02:07,590 --> 00:02:10,260 +我们可以将此函数应用于整个数据集。 +We can apply this function to the whole dataset. + +49 +00:02:10,260 --> 00:02:11,700 +此外,我们确保 +In addition, we make sure + +50 +00:02:11,700 --> 00:02:15,450 +使用批处理并删除现有列。 +that to use batches and remove the existing columns. + +51 +00:02:15,450 --> 00:02:17,670 +我们需要删除现有的列, +We need to remove the existing columns, + +52 +00:02:17,670 --> 00:02:21,330 +因为我们可以为每个文本创建多个样本, +because we can create multiple samples per text, + +53 +00:02:21,330 --> 00:02:22,890 +和数据集中的形状 +and the shapes in the dataset + +54 +00:02:22,890 --> 00:02:24,753 +在那种情况下将不再匹配。 +would not match anymore in that case. + +55 +00:02:26,832 --> 00:02:30,330 +如果上下文长度与文件长度相似, +If the context length is of similar lengths as the files, + +56 +00:02:30,330 --> 00:02:32,733 +这种方法不再那么有效了。 +this approach doesn't work so well anymore. + +57 +00:02:33,660 --> 00:02:36,420 +在这个例子中,样本 1 和 2 +In this example, both sample 1 and 2 + +58 +00:02:36,420 --> 00:02:38,400 +比上下文大小短 +are shorter than the context size + +59 +00:02:38,400 --> 00:02:41,610 +并且将被以前的方法丢弃。 +and will be discarded with the previous approach. + +60 +00:02:41,610 --> 00:02:45,150 +在这种情况下,最好先标记每个样本 +In this case, it is better to first tokenize each sample + +61 +00:02:45,150 --> 00:02:46,590 +没有截断 +without truncation + +62 +00:02:46,590 --> 00:02:49,290 +然后连接标记化样本 +and then concatenate the tokenized samples + +63 +00:02:49,290 --> 00:02:52,353 +中间有字符串结尾或 EOS 令牌。 +with an end of string or EOS token in between. + +64 +00:02:53,546 --> 00:02:56,220 +最后,我们可以分块这个长序列 +Finally, we can chunk this long sequence + +65 +00:02:56,220 --> 00:02:59,490 +使用上下文长度,我们不会丢失太多序列 +with the context length and we don't lose too many sequences + +66 +00:02:59,490 --> 00:03:01,263 +因为它们太短了。 +because they're too short anymore. + +67 +00:03:04,170 --> 00:03:05,760 +到目前为止,我们只谈过 +So far, we have only talked + +68 +00:03:05,760 --> 00:03:08,370 +关于因果语言建模的输入, +about the inputs for causal language modeling, + +69 +00:03:08,370 --> 00:03:11,850 +但不是监督培训所需的标签。 +but not the labels needed for supervised training. + +70 +00:03:11,850 --> 00:03:13,380 +当我们进行因果语言建模时, +When we do causal language modeling, + +71 +00:03:13,380 --> 00:03:16,710 +我们不需要输入序列的任何额外标签 +we don't require any extra labels for the input sequences + +72 +00:03:16,710 --> 00:03:20,610 +因为输入序列本身就是标签。 +as the input sequences themselves are the labels. + +73 +00:03:20,610 --> 00:03:24,240 +在这个例子中,当我们将 token trans 提供给模型时, +In this example, when we feed the token trans to the model, + +74 +00:03:24,240 --> 00:03:27,510 +我们要预测的下一个标记是前者。 +the next token we wanted to predict is formers. + +75 +00:03:27,510 --> 00:03:30,780 +在下一步中,我们将 trans 和 formers 提供给模型 +In the next step, we feed trans and formers to the model + +76 +00:03:30,780 --> 00:03:33,903 +我们想要预测的标签是 are。 +and the label we wanted to predict is are. + +77 +00:03:35,460 --> 00:03:38,130 +这种模式仍在继续,如你所见, +This pattern continues, and as you can see, + +78 +00:03:38,130 --> 00:03:41,220 +输入序列是标签序列 +the input sequence is the label sequence + +79 +00:03:41,220 --> 00:03:42,663 +只是移动了一个。 +just shifted by one. + +80 +00:03:43,590 --> 00:03:47,310 +由于模型仅在第一个标记之后进行预测, +Since the model only makes prediction after the first token, + +81 +00:03:47,310 --> 00:03:49,350 +输入序列的第一个元素, +the first element of the input sequence, + +82 +00:03:49,350 --> 00:03:52,980 +在这种情况下,反式不用作标签。 +in this case, trans, is not used as a label. + +83 +00:03:52,980 --> 00:03:55,530 +同样,我们没有标签 +Similarly, we don't have a label + +84 +00:03:55,530 --> 00:03:57,600 +对于序列中的最后一个标记 +for the last token in the sequence + +85 +00:03:57,600 --> 00:04:00,843 +因为序列结束后没有令牌。 +since there is no token after the sequence ends. + +86 +00:04:04,110 --> 00:04:06,300 +让我们看看我们需要做什么 +Let's have a look at what we need to do + +87 +00:04:06,300 --> 00:04:10,200 +在代码中为因果语言建模创建标签。 +to create the labels for causal language modeling in code. + +88 +00:04:10,200 --> 00:04:12,360 +如果我们想计算一批的损失, +If we want to calculate a loss on a batch, + +89 +00:04:12,360 --> 00:04:15,120 +我们可以将 input_ids 作为标签传递 +we can just pass the input_ids as labels + +90 +00:04:15,120 --> 00:04:18,933 +所有的转移都在模型内部处理。 +and all the shifting is handled in the model internally. + +91 +00:04:20,032 --> 00:04:22,170 +所以,你看,不涉及任何匹配 +So, you see, there's no matching involved + +92 +00:04:22,170 --> 00:04:24,870 +在处理因果语言建模的数据时, +in processing data for causal language modeling, + +93 +00:04:24,870 --> 00:04:27,723 +它只需要几个简单的步骤。 +and it only requires a few simple steps. + +94 +00:04:28,854 --> 00:04:31,771 +(过渡音乐) +(transition music) + diff --git a/subtitles/zh-CN/64_using-a-custom-loss-function.srt b/subtitles/zh-CN/64_using-a-custom-loss-function.srt new file mode 100644 index 000000000..efcbaf454 --- /dev/null +++ b/subtitles/zh-CN/64_using-a-custom-loss-function.srt @@ -0,0 +1,360 @@ +1 +00:00:00,573 --> 00:00:01,636 +(空气呼啸) +(air whooshing) + +2 +00:00:01,636 --> 00:00:02,594 +(徽标弹出) +(logo popping) + +3 +00:00:02,594 --> 00:00:05,550 +(金属滑动) +(metal sliding) + +4 +00:00:05,550 --> 00:00:07,500 +- 在本视频中,我们将介绍如何设置 +- In this video, we take a look at setting up + +5 +00:00:07,500 --> 00:00:09,303 +用于训练的自定义损失函数。 +a custom loss function for training. + +6 +00:00:10,980 --> 00:00:13,260 +在默认损失函数中,所有样本, +In the default loss function, all samples, + +7 +00:00:13,260 --> 00:00:15,840 +例如这些代码片段,都被同等对待 +such as these code snippets, are treated the same + +8 +00:00:15,840 --> 00:00:18,960 +不管他们的内容如何,但有一些场景 +irrespective of their content but there are scenarios + +9 +00:00:18,960 --> 00:00:21,660 +对样本进行不同加权可能有意义。 +where it could make sense to weight the samples differently. + +10 +00:00:21,660 --> 00:00:24,570 +例如,如果一个样本包含很多标记 +If, for example, one sample contains a lot of tokens + +11 +00:00:24,570 --> 00:00:26,160 +我们感兴趣的 +that are of interest to us + +12 +00:00:26,160 --> 00:00:29,910 +或者样本是否具有有利的标记多样性。 +or if a sample has a favorable diversity of tokens. + +13 +00:00:29,910 --> 00:00:31,950 +我们还可以实施其他启发式 +We can also implement other heuristics + +14 +00:00:31,950 --> 00:00:33,963 +与模式匹配或其他规则。 +with pattern matching or other rules. + +15 +00:00:35,993 --> 00:00:39,150 +对于每个样本,我们在训练过程中得到一个损失值 +For each sample, we get a loss value during training + +16 +00:00:39,150 --> 00:00:41,850 +我们可以将损失与重量结合起来。 +and we can combine that loss with a weight. + +17 +00:00:41,850 --> 00:00:43,860 +然后我们可以创建一个加权和 +Then we can create a weighted sum + +18 +00:00:43,860 --> 00:00:45,660 +或对所有样本取平均值 +or average over all samples + +19 +00:00:45,660 --> 00:00:47,613 +获得该批次的最终损失。 +to get the final loss for the batch. + +20 +00:00:48,690 --> 00:00:51,240 +让我们看一个具体的例子。 +Let's have a look at a specific example. + +21 +00:00:51,240 --> 00:00:52,830 +我们要建立一个语言模型 +We want to set up a language model + +22 +00:00:52,830 --> 00:00:56,073 +这有助于我们自动完成常见的数据科学代码。 +that helps us autocomplete common data science code. + +23 +00:00:57,030 --> 00:01:01,830 +对于那个任务,我们想给样本赋予更强的权重 +For that task, we would like to weight samples stronger + +24 +00:01:01,830 --> 00:01:04,110 +其中与数据科学堆栈相关的令牌, +where tokens related to the data science stack, + +25 +00:01:04,110 --> 00:01:07,353 +如 pd 或 np,出现的频率更高。 +such as pd or np, occur more frequently. + +26 +00:01:10,140 --> 00:01:13,080 +在这里你看到一个损失函数正是这样做的 +Here you see a loss function that does exactly that + +27 +00:01:13,080 --> 00:01:15,180 +用于因果语言建模。 +for causal language modeling. + +28 +00:01:15,180 --> 00:01:18,030 +它采用模型的输入和预测的逻辑, +It takes the model's input and predicted logits, + +29 +00:01:18,030 --> 00:01:20,343 +以及作为输入的密钥标记。 +as well as the key tokens, as input. + +30 +00:01:21,869 --> 00:01:25,113 +首先,输入和逻辑对齐。 +First, the inputs and logits are aligned. + +31 +00:01:26,490 --> 00:01:29,310 +然后计算每个样本的损失, +Then the loss per sample is calculated, + +32 +00:01:29,310 --> 00:01:30,843 +其次是重量。 +followed by the weights. + +33 +00:01:32,430 --> 00:01:35,583 +最后,将损失和权重合并并返回。 +Finally, the loss and the weights are combined and returned. + +34 +00:01:36,540 --> 00:01:39,150 +这是一个相当大的函数,让我们仔细看看 +This is a pretty big function, so let's take a closer look + +35 +00:01:39,150 --> 00:01:40,953 +在损失和重量块。 +at the loss and the weight blocks. + +36 +00:01:43,380 --> 00:01:45,600 +在计算标准损失时, +During the calculation of the standard loss, + +37 +00:01:45,600 --> 00:01:48,930 +logits 和标签在批次上变平。 +the logits and labels are flattened over the batch. + +38 +00:01:48,930 --> 00:01:52,590 +有了视图,我们展开张量得到矩阵 +With the view, we unflatten the tensor to get the matrix + +39 +00:01:52,590 --> 00:01:55,320 +批次中的每个样本都有一行和一列 +with a row for each sample in the batch and a column + +40 +00:01:55,320 --> 00:01:57,723 +对于样本序列中的每个位置。 +for each position in the sequence of the sample. + +41 +00:01:58,920 --> 00:02:00,600 +我们不需要每个头寸的损失, +We don't need the loss per position, + +42 +00:02:00,600 --> 00:02:04,083 +所以我们对每个样本的所有头寸的损失进行平均。 +so we average the loss over all positions for each sample. + +43 +00:02:06,150 --> 00:02:08,970 +对于权重,我们使用布尔逻辑得到一个张量 +For the weights, we use Boolean logic to get a tensor + +44 +00:02:08,970 --> 00:02:12,483 +出现关键字的位置为 1,未出现的位置为 0。 +with 1s where a keyword occurred and 0s where not. + +45 +00:02:13,440 --> 00:02:15,690 +这个张量有一个额外的维度 +This tensor has an additional dimension + +46 +00:02:15,690 --> 00:02:18,540 +作为我们刚刚看到的损失张量,因为我们得到 +as the loss tensor we just saw because we get + +47 +00:02:18,540 --> 00:02:21,693 +单独矩阵中每个关键字的信息。 +the information for each keyword in a separate matrix. + +48 +00:02:22,770 --> 00:02:24,120 +我们只想知道 +We only want to know + +49 +00:02:24,120 --> 00:02:26,880 +每个样本出现多少次关键字, +how many times keywords occurred per sample, + +50 +00:02:26,880 --> 00:02:30,693 +因此我们可以对每个样本的总体关键字和所有位置求和。 +so we can sum overall keywords and all positions per sample. + +51 +00:02:33,450 --> 00:02:35,010 +现在我们快到了。 +Now we're almost there. + +52 +00:02:35,010 --> 00:02:38,850 +我们只需要将损失与每个样本的权重结合起来。 +We only need to combine the loss with the weight per sample. + +53 +00:02:38,850 --> 00:02:41,790 +我们用元素明智的乘法来做到这一点 +We do this with element wise multiplication + +54 +00:02:41,790 --> 00:02:45,233 +然后对批次中的总体样本进行平均。 +and then average overall samples in the batch. + +55 +00:02:45,233 --> 00:02:46,066 +到底, +In the end, + +56 +00:02:46,066 --> 00:02:49,110 +我们对整批只有一个损失值 +we have exactly one loss value for the whole batch + +57 +00:02:49,110 --> 00:02:51,330 +这是整个必要的逻辑 +and this is the whole necessary logic + +58 +00:02:51,330 --> 00:02:53,223 +创建自定义加权损失。 +to create a custom weighted loss. + +59 +00:02:56,250 --> 00:02:59,010 +让我们看看如何利用自定义损失 +Let's see how we can make use of that custom loss + +60 +00:02:59,010 --> 00:03:00,753 +与 Accelerate 和 Trainer 一起。 +with Accelerate and the Trainer. + +61 +00:03:01,710 --> 00:03:04,656 +在 Accelerate 中,我们只传递 input_ids +In Accelerate, we just pass the input_ids + +62 +00:03:04,656 --> 00:03:05,730 +到模型以获得 logits +to the model to get the logits + +63 +00:03:05,730 --> 00:03:08,103 +然后我们可以调用自定义损失函数。 +and then we can call the custom loss function. + +64 +00:03:09,000 --> 00:03:11,310 +之后,我们继续正常的训练循环 +After that, we continue with the normal training loop + +65 +00:03:11,310 --> 00:03:13,083 +例如,向后调用。 +by, for example, calling backward. + +66 +00:03:14,010 --> 00:03:15,570 +对于 Trainer,我们可以覆盖 +For the Trainer, we can overwrite + +67 +00:03:15,570 --> 00:03:19,260 +标准训练器的计算损失函数。 +the compute loss function of the standard trainer. + +68 +00:03:19,260 --> 00:03:20,970 +我们只需要确保我们返回 +We just need to make sure that we return + +69 +00:03:20,970 --> 00:03:24,450 +损失和模型以相同的格式输出。 +the loss and the model outputs in the same format. + +70 +00:03:24,450 --> 00:03:27,570 +这样,你就可以集成自己的出色损失函数 +With that, you can integrate your own awesome loss function + +71 +00:03:27,570 --> 00:03:29,763 +与培训师和加速。 +with both the Trainer and Accelerate. + +72 +00:03:31,389 --> 00:03:34,056 +(空气呼啸) +(air whooshing) + diff --git a/subtitles/zh-CN/65_data-processing-for-question-answering.srt b/subtitles/zh-CN/65_data-processing-for-question-answering.srt new file mode 100644 index 000000000..d57dd83ef --- /dev/null +++ b/subtitles/zh-CN/65_data-processing-for-question-answering.srt @@ -0,0 +1,300 @@ +1 +00:00:05,580 --> 00:00:07,177 +- 让我们研究如何预处理数据集 +- Let's study how to preprocess a dataset + +2 +00:00:07,177 --> 00:00:08,643 +用于答疑。 +for question answering. + +3 +00:00:10,200 --> 00:00:11,640 +回答问题是一项任务 +Question answering is a task + +4 +00:00:11,640 --> 00:00:14,343 +在某些情况下找到问题的答案。 +of finding answers to a question in some context. + +5 +00:00:15,270 --> 00:00:17,550 +例如,我们将使用 SQuAD 数据集 +For example, we'll use the SQuAD dataset + +6 +00:00:17,550 --> 00:00:19,860 +在其中我们删除了我们不会使用的列 +in which we remove columns we won't use + +7 +00:00:19,860 --> 00:00:21,660 +并提取我们需要的信息 +and just extract the information we will need + +8 +00:00:21,660 --> 00:00:22,950 +对于标签, +for the labels, + +9 +00:00:22,950 --> 00:00:26,370 +上下文中答案的开始和结束。 +the start and the end of the answer in the context. + +10 +00:00:26,370 --> 00:00:28,690 +如果你有自己的问答数据集, +If you have your own dataset for question answering, + +11 +00:00:28,690 --> 00:00:31,680 +只要确保你清理数据以达到同一点, +just make sure you clean your data to get to the same point, + +12 +00:00:31,680 --> 00:00:33,900 +一栏包含问题, +with one column containing the questions, + +13 +00:00:33,900 --> 00:00:35,940 +一列包含上下文, +one column containing the context, + +14 +00:00:35,940 --> 00:00:38,610 +一列为开始和结束字符的索引 +one column for the index of the start and end character + +15 +00:00:38,610 --> 00:00:40,473 +上下文中的答案。 +of the answer in the context. + +16 +00:00:41,610 --> 00:00:44,520 +请注意,答案必须是上下文的一部分。 +Note that the answer must be part of the context. + +17 +00:00:44,520 --> 00:00:47,160 +如果你想进行生成式问答, +If you want to perform generative question answering, + +18 +00:00:47,160 --> 00:00:50,160 +查看下面链接的视频序列之一。 +look at one of the sequence to sequence videos linked below. + +19 +00:00:51,600 --> 00:00:53,430 +现在,如果我们看一下令牌 +Now, if we have a look at the tokens + +20 +00:00:53,430 --> 00:00:54,750 +我们将喂养我们的模型, +we will feed our model, + +21 +00:00:54,750 --> 00:00:58,320 +我们会看到答案就在上下文中的某个地方。 +we'll see the answer lies somewhere inside the context. + +22 +00:00:58,320 --> 00:01:01,080 +对于很长的上下文,该答案可能会被截断 +For very long context, that answer may get truncated + +23 +00:01:01,080 --> 00:01:02,580 +由分词器。 +by the tokenizer. + +24 +00:01:02,580 --> 00:01:05,970 +在这种情况下,我们的模型将没有任何合适的标签, +In this case, we won't have any proper labels for our model, + +25 +00:01:05,970 --> 00:01:07,680 +所以我们应该保留截断的部分 +so we should keep the truncated part + +26 +00:01:07,680 --> 00:01:10,203 +作为一个单独的功能而不是丢弃它。 +as a separate feature instead of discarding it. + +27 +00:01:11,100 --> 00:01:12,990 +我们唯一需要小心的是 +The only thing we need to be careful with + +28 +00:01:12,990 --> 00:01:15,660 +是允许不同的块之间有一些重叠 +is to allow some overlap between separate chunks + +29 +00:01:15,660 --> 00:01:17,670 +这样答案就不会被截断 +so that the answer is not truncated + +30 +00:01:17,670 --> 00:01:19,920 +以及包含答案的特征 +and that the feature containing the answer + +31 +00:01:19,920 --> 00:01:22,623 +获得足够的上下文以能够预测它。 +gets sufficient context to be able to predict it. + +32 +00:01:23,490 --> 00:01:26,040 +这是分词器如何完成的。 +Here is how it can be done by the tokenizer. + +33 +00:01:26,040 --> 00:01:29,370 +我们将问题、上下文传递给它,设置截断 +We pass it the question, context, set a truncation + +34 +00:01:29,370 --> 00:01:33,240 +仅针对上下文,填充到最大长度。 +for the context only, and the padding to the maximum length. + +35 +00:01:33,240 --> 00:01:35,340 +stride 参数是我们设置数字的地方 +The stride argument is where we set the number + +36 +00:01:35,340 --> 00:01:36,900 +重叠的标记, +of overlapping tokens, + +37 +00:01:36,900 --> 00:01:39,600 +并且返回的溢出标记等于 true +and the return overflowing tokens equals true + +38 +00:01:39,600 --> 00:01:42,630 +意味着我们不想丢弃截断的部分。 +means we don't want to discard the truncated part. + +39 +00:01:42,630 --> 00:01:45,210 +最后,我们还返回偏移映射 +Lastly, we also return the offset mappings + +40 +00:01:45,210 --> 00:01:47,220 +能够找到相应的令牌 +to be able to find the tokens corresponding + +41 +00:01:47,220 --> 00:01:48,693 +到答案的开始和结束。 +to the answer start and end. + +42 +00:01:49,860 --> 00:01:52,290 +我们想要这些标记,因为它们将成为标签 +We want those tokens because they will be the labels + +43 +00:01:52,290 --> 00:01:53,970 +我们通过我们的模型。 +we pass through our model. + +44 +00:01:53,970 --> 00:01:56,870 +在单热编码版本中,这是它们的样子。 +In a one-hot encoded version, here is what they look like. + +45 +00:01:57,930 --> 00:02:00,480 +如果我们的上下文不包含答案, +If the context we have does not contain the answer, + +46 +00:02:00,480 --> 00:02:03,799 +我们将这两个标签设置为 CLS 令牌的索引。 +we set the two labels to the index of the CLS token. + +47 +00:02:03,799 --> 00:02:05,700 +如果上下文,我们也会这样做 +We also do this if the context + +48 +00:02:05,700 --> 00:02:07,713 +仅部分包含答案。 +only partially contains the answer. + +49 +00:02:08,580 --> 00:02:11,400 +在代码方面,这是我们如何做到的。 +In terms of code, here is how we can do it. + +50 +00:02:11,400 --> 00:02:13,710 +使用输入的序列 ID, +Using the sequence IDs of an input, + +51 +00:02:13,710 --> 00:02:17,220 +我们可以确定上下文的开始和结束。 +we can determine the beginning and the end of the context. + +52 +00:02:17,220 --> 00:02:19,800 +然后,我们知道是否必须返回到 CLS 位置 +Then, we know if we have to return to the CLS position + +53 +00:02:19,800 --> 00:02:22,290 +对于两个标签或者我们确定位置 +for the two labels or we determine the position + +54 +00:02:22,290 --> 00:02:25,050 +答案的第一个和最后一个标记。 +of the first and last tokens of the answer. + +55 +00:02:25,050 --> 00:02:27,800 +我们可以在前面的示例中检查它是否正常工作。 +We can check it works properly on our previous example. + +56 +00:02:28,680 --> 00:02:31,380 +把它们放在一起看起来就像这个大函数, +Putting it all together looks like this big function, + +57 +00:02:31,380 --> 00:02:34,233 +我们可以使用 map 方法将其应用于我们的数据集。 +which we can apply to our datasets with the map method. + +58 +00:02:35,310 --> 00:02:37,920 +由于我们在标记化过程中应用了填充, +Since we applied padding during the tokenization, + +59 +00:02:37,920 --> 00:02:40,680 +然后我们可以直接使用它作为训练器 +we can then use this directly as the trainer + +60 +00:02:40,680 --> 00:02:44,133 +或者应用 to_tf_dataset 方法来使用 Keras.fit。 +or apply the to_tf_dataset method to use Keras.fit. + diff --git a/subtitles/zh-CN/66_the-post-processing-step-in-question-answering-(pytorch).srt b/subtitles/zh-CN/66_the-post-processing-step-in-question-answering-(pytorch).srt new file mode 100644 index 000000000..ea2b861e6 --- /dev/null +++ b/subtitles/zh-CN/66_the-post-processing-step-in-question-answering-(pytorch).srt @@ -0,0 +1,385 @@ +1 +00:00:00,315 --> 00:00:02,982 +(空气呼啸) +(air whooshing) + +2 +00:00:05,940 --> 00:00:08,913 +- 问答任务中的后处理步骤。 +- The post-processing step in a question answering task. + +3 +00:00:10,440 --> 00:00:12,180 +在做答题时, +When doing question answering, + +4 +00:00:12,180 --> 00:00:14,550 +初始数据集的处理 +the processing of the initial dataset + +5 +00:00:14,550 --> 00:00:17,370 +意味着将示例拆分为多个功能, +implies splitting examples in several features, + +6 +00:00:17,370 --> 00:00:19,773 +其中可能包含也可能不包含答案。 +which may or may not contain the answer. + +7 +00:00:21,000 --> 00:00:22,740 +通过模型传递这些特征 +Passing those features through the model + +8 +00:00:22,740 --> 00:00:25,830 +将为我们提供开始和结束位置的 logits, +will give us logits for the start and end positions, + +9 +00:00:25,830 --> 00:00:28,650 +因为我们的标签是令牌的索引 +since our labels are the indices of the token + +10 +00:00:28,650 --> 00:00:31,050 +对应于开始和结束的答案。 +that correspond to the start and end the answer. + +11 +00:00:32,664 --> 00:00:35,490 +然后我们必须以某种方式将这些 logits 转换为答案, +We must then somehow convert those logits into an answer, + +12 +00:00:35,490 --> 00:00:38,610 +然后从每个功能给出的各种答案中选择一个 +and then pick one of the various answers each feature gives + +13 +00:00:38,610 --> 00:00:40,893 +成为给定示例的答案。 +to be the answer for a given example. + +14 +00:00:42,300 --> 00:00:43,500 +对于处理步骤, +For the processing step, + +15 +00:00:43,500 --> 00:00:45,750 +你应该参考下面链接的视频。 +you should refer to the video linked below. + +16 +00:00:45,750 --> 00:00:47,820 +验证并没有太大的不同, +It's not very different for validation, + +17 +00:00:47,820 --> 00:00:50,820 +我们只需要添加几行来跟踪两件事。 +we just need to add a few lines to keep track of two things. + +18 +00:00:51,660 --> 00:00:54,960 +我们保留它们,而不是丢弃偏移映射, +Instead of discarding the offset mappings, we keep them, + +19 +00:00:54,960 --> 00:00:55,793 +也包括在其中 +and also include in them + +20 +00:00:55,793 --> 00:00:58,350 +上下文在哪里的信息 +the information of where the context is + +21 +00:00:58,350 --> 00:01:00,690 +通过设置特殊标记的偏移量 +by setting the offsets of the special tokens + +22 +00:01:00,690 --> 00:01:02,253 +和无的问题。 +and the question to None. + +23 +00:01:03,480 --> 00:01:06,630 +然后我们还跟踪每个功能的示例 ID, +Then we also keep track of the example ID for each feature, + +24 +00:01:06,630 --> 00:01:08,280 +能够映射回特征 +to be able to map back feature + +25 +00:01:08,280 --> 00:01:10,503 +他们起源的例子。 +to the examples that they originated from. + +26 +00:01:11,940 --> 00:01:14,100 +如果你不想计算验证损失, +If you don't want to compute the validation loss, + +27 +00:01:14,100 --> 00:01:15,990 +你不需要包含所有特殊代码 +you won't need to include all the special code + +28 +00:01:15,990 --> 00:01:18,420 +我们用来创建标签的。 +that we used to create the labels. + +29 +00:01:18,420 --> 00:01:21,090 +完成后,我们可以应用该预处理功能 +With this done, we can apply that preprocessing function + +30 +00:01:21,090 --> 00:01:22,890 +使用映射方法。 +using the map method. + +31 +00:01:22,890 --> 00:01:24,090 +我们采用 SQUAD 数据集 +We take the SQUAD dataset + +32 +00:01:24,090 --> 00:01:26,840 +比如问答视频的预处理。 +like in the preprocessing for question-answering video. + +33 +00:01:27,810 --> 00:01:30,540 +一旦完成,下一步就是创建我们的模型。 +Once this is done, the next step is to create our model. + +34 +00:01:30,540 --> 00:01:31,710 +我们使用默认模型 +We use the default model + +35 +00:01:31,710 --> 00:01:33,930 +在这里的问答管道背后, +behind the question-answering pipeline here, + +36 +00:01:33,930 --> 00:01:36,960 +但你应该使用你想要评估的任何模型。 +but you should use any model you want to evaluate. + +37 +00:01:36,960 --> 00:01:38,850 +我们将运行一个手动评估循环, +We'll run a manual evaluation loop, + +38 +00:01:38,850 --> 00:01:41,583 +所以我们用我们的特性创建了一个 PyTorch DataLoader。 +so we create a PyTorch DataLoader with our features. + +39 +00:01:42,657 --> 00:01:44,520 +有了它,我们可以计算和收集 +With it, we can compute and gather + +40 +00:01:44,520 --> 00:01:46,650 +所有的开始和结束都是这样的, +all the start and end logits like this, + +41 +00:01:46,650 --> 00:01:49,653 +使用标准的 PyTorch 评估循环。 +with a standard PyTorch evaluation loop. + +42 +00:01:49,653 --> 00:01:53,220 +完成后,我们就可以真正深入到后期处理中了。 +With this done, we can really dive into the post-processing. + +43 +00:01:53,220 --> 00:01:56,340 +首先,我们需要一张从示例到功能的映射, +First, we'll need a map from example to features, + +44 +00:01:56,340 --> 00:01:57,873 +我们可以这样创建。 +which we can create like this. + +45 +00:01:58,800 --> 00:02:00,810 +现在,对于后处理的主要部分, +Now, for the main part of the post-processing, + +46 +00:02:00,810 --> 00:02:04,230 +让我们看看如何从 logits 中提取答案。 +let's see how to extract an answer from the logits. + +47 +00:02:04,230 --> 00:02:05,760 +我们可以只取最好的索引 +We could just take the best index + +48 +00:02:05,760 --> 00:02:07,980 +对于开始和结束登录并完成, +for the start and end logits and be done, + +49 +00:02:07,980 --> 00:02:10,380 +但如果我们的模型预测了一些不可能的事情, +but if our model predicts something impossible, + +50 +00:02:10,380 --> 00:02:12,150 +就像问题中的标记, +like tokens in the question, + +51 +00:02:12,150 --> 00:02:13,940 +我们将查看更多的 logits。 +we'll look at more of the logits. + +52 +00:02:15,270 --> 00:02:17,070 +请注意,在问答管道中, +Note that in the question-answering pipeline, + +53 +00:02:17,070 --> 00:02:18,870 +我们将分数归因于每个答案 +we attributed score to each answer + +54 +00:02:18,870 --> 00:02:20,430 +基于概率, +based on the probabilities, + +55 +00:02:20,430 --> 00:02:22,350 +我们这里没有计算。 +which we did not compute here. + +56 +00:02:22,350 --> 00:02:25,560 +就逻辑而言,我们在分数中的乘法 +In terms of logits, the multiplication we had in the scores + +57 +00:02:25,560 --> 00:02:26,853 +成为加法。 +becomes an addition. + +58 +00:02:28,110 --> 00:02:29,010 +要走得快, +To go fast, + +59 +00:02:29,010 --> 00:02:31,800 +我们不会查看所有可能的开始和结束日志, +we don't look at all possible start and end logits, + +60 +00:02:31,800 --> 00:02:34,050 +但是最好的 20 个就足够了。 +but the 20 best one is enough. + +61 +00:02:34,050 --> 00:02:36,570 +我们忽略产生不可能答案的逻辑 +We ignore the logits that spawn impossible answers + +62 +00:02:36,570 --> 00:02:38,550 +或回答太长。 +or answer that are too long. + +63 +00:02:38,550 --> 00:02:41,430 +正如我们在预处理中看到的,标签 0,0 +As we saw in the preprocessing, the labels 0,0 + +64 +00:02:41,430 --> 00:02:43,230 +对应不回答。 +correspond to a no answer. + +65 +00:02:43,230 --> 00:02:45,090 +否则我们使用偏移量 +Otherwise we use the offsets + +66 +00:02:45,090 --> 00:02:46,940 +在上下文中得到答案。 +to get the answer inside the context. + +67 +00:02:47,910 --> 00:02:49,107 +我们来看看预测答案 +Let's have a look at the predicted answer + +68 +00:02:49,107 --> 00:02:50,370 +对于第一个功能, +for the first feature, + +69 +00:02:50,370 --> 00:02:51,930 +这是得分最高的答案 +which is the answer with the best score + +70 +00:02:51,930 --> 00:02:53,640 +或最好的逻辑分数 +or the best logit score + +71 +00:02:53,640 --> 00:02:56,280 +因为 SoftMax 是递增函数。 +since the SoftMax is an increasing function. + +72 +00:02:56,280 --> 00:02:58,230 +模型做对了。 +The model got it right. + +73 +00:02:58,230 --> 00:03:00,690 +接下来我们只需要为每个例子循环这个, +Next we just have to loop this for every example, + +74 +00:03:00,690 --> 00:03:03,720 +为每个选择具有最佳 logit 分数的答案 +picking for each the answer with the best logit score + +75 +00:03:03,720 --> 00:03:06,750 +在示例生成的所有功能中。 +in all the features the example generated. + +76 +00:03:06,750 --> 00:03:09,700 +现在你知道如何从模型预测中获得答案了。 +Now you know how to get answers from your model prediction. + +77 +00:03:11,007 --> 00:03:13,674 +(空气呼啸) +(air whooshing) + diff --git a/subtitles/zh-CN/67_the-post-processing-step-in-question-answering-(tensorflow).srt b/subtitles/zh-CN/67_the-post-processing-step-in-question-answering-(tensorflow).srt new file mode 100644 index 000000000..5991da907 --- /dev/null +++ b/subtitles/zh-CN/67_the-post-processing-step-in-question-answering-(tensorflow).srt @@ -0,0 +1,370 @@ +1 +00:00:00,367 --> 00:00:02,950 +(微妙的爆炸) +(subtle blast) + +2 +00:00:05,850 --> 00:00:08,913 +- 问答任务中的后处理步骤。 +- The post-processing step in a question-answering task. + +3 +00:00:10,830 --> 00:00:11,790 +在做答题时, +When doing question answering, + +4 +00:00:11,790 --> 00:00:14,670 +初始数据集的处理 +the processing of the initial dataset + +5 +00:00:14,670 --> 00:00:18,090 +意味着将示例拆分为多个功能, +implies splitting examples in several features, + +6 +00:00:18,090 --> 00:00:20,850 +其中可能包含也可能不包含答案。 +which may or may not contain the answer. + +7 +00:00:20,850 --> 00:00:22,530 +通过模型传递这些特征 +Passing those features through the model + +8 +00:00:22,530 --> 00:00:25,860 +将为我们提供开始和结束位置的 logits, +will give us logits for the start and end positions, + +9 +00:00:25,860 --> 00:00:28,620 +因为我们的标签是令牌的索引 +since our labels are the indices of the tokens + +10 +00:00:28,620 --> 00:00:31,020 +对应于开始和结束的答案。 +that correspond to the start and end the answer. + +11 +00:00:31,860 --> 00:00:34,740 +然后我们必须以某种方式将这些 logits 转换为答案, +We must then somehow convert those logits into an answer, + +12 +00:00:34,740 --> 00:00:38,070 +然后从每个功能给出的各种答案中选择一个 +and then pick one of the various answers each feature gives + +13 +00:00:38,070 --> 00:00:40,473 +成为给定示例的答案。 +to be the answer for a given example. + +14 +00:00:41,683 --> 00:00:43,200 +对于处理步骤, +For the processing step, + +15 +00:00:43,200 --> 00:00:45,450 +你应该参考下面链接的视频。 +you should refer to the video linked below. + +16 +00:00:45,450 --> 00:00:47,310 +验证并没有太大的不同, +It's not very different for validation, + +17 +00:00:47,310 --> 00:00:50,053 +我们只需要添加几行来跟踪两件事: +we just need to add a few lines to keep track of two things: + +18 +00:00:50,053 --> 00:00:52,620 +而不是丢弃偏移映射, +instead of discarding the offset mappings, + +19 +00:00:52,620 --> 00:00:55,380 +我们保留它们,并在其中包含信息 +we keep them, and also include in them the information + +20 +00:00:55,380 --> 00:00:58,410 +通过设置偏移量来确定上下文的位置 +of where the context is by setting the offsets + +21 +00:00:58,410 --> 00:01:01,821 +特殊标记和无的问题。 +of the special tokens and the question to None. + +22 +00:01:01,821 --> 00:01:05,370 +然后我们还跟踪每个功能的示例 ID, +Then we also keep track of the example ID for each feature, + +23 +00:01:05,370 --> 00:01:07,020 +能够映射回特征 +to be able to map back feature + +24 +00:01:07,020 --> 00:01:09,243 +他们起源的例子。 +to the examples that they originated from. + +25 +00:01:10,470 --> 00:01:12,660 +如果你不想计算验证损失, +If you don't want to compute the validation loss, + +26 +00:01:12,660 --> 00:01:14,610 +你不需要包含所有特殊代码 +you won't need to include all the special code + +27 +00:01:14,610 --> 00:01:17,010 +我们用来创建标签的。 +that we used to create the labels. + +28 +00:01:17,010 --> 00:01:19,650 +完成后,我们可以应用该预处理功能 +With this done, we can apply that preprocessing function + +29 +00:01:19,650 --> 00:01:21,480 +使用映射方法。 +using the map method. + +30 +00:01:21,480 --> 00:01:23,610 +我们在预处理中采用 SQUAD 数据集 +We take the SQUAD dataset like in the preprocessing + +31 +00:01:23,610 --> 00:01:25,060 +用于问答视频。 +for question-answering video. + +32 +00:01:26,400 --> 00:01:29,310 +一旦完成,下一步就是创建我们的模型。 +Once this is done, the next step is to create our model. + +33 +00:01:29,310 --> 00:01:30,570 +我们使用后面的默认模型 +We use the default model behind + +34 +00:01:30,570 --> 00:01:32,640 +这里的问答管道, +the question-answering pipeline here, + +35 +00:01:32,640 --> 00:01:35,880 +但你应该使用你想要评估的任何模型。 +but you should use any model you want to evaluate. + +36 +00:01:35,880 --> 00:01:37,680 +使用 to_tf_dataset 方法, +With the to_tf_dataset method, + +37 +00:01:37,680 --> 00:01:41,370 +我们可以将处理过的数据集发送到 model.predict, +we can just sent our processed dataset to model.predict, + +38 +00:01:41,370 --> 00:01:43,350 +我们直接得到我们的开始和结束 logits +and we directly get our start and end logits + +39 +00:01:43,350 --> 00:01:45,930 +将整个数据集作为 NumPy 数组。 +for the whole dataset as NumPy arrays. + +40 +00:01:45,930 --> 00:01:49,230 +完成后,我们就可以真正深入到后期处理中了。 +With this done, we can really dive into the post-processing. + +41 +00:01:49,230 --> 00:01:52,380 +首先,我们需要一张从示例到功能的映射, +First, we'll need a map from example to features, + +42 +00:01:52,380 --> 00:01:53,883 +我们可以这样创建。 +which we can create like this. + +43 +00:01:54,780 --> 00:01:56,700 +现在,对于后处理的主要部分, +Now, for the main part of the post-processing, + +44 +00:01:56,700 --> 00:02:00,270 +让我们看看如何从 logits 中提取答案。 +let's see how to extract an answer from the logits. + +45 +00:02:00,270 --> 00:02:01,650 +我们可以只取最好的索引 +We could just take the best index + +46 +00:02:01,650 --> 00:02:03,690 +对于开始和结束登录并完成, +for the start and end logits and be done, + +47 +00:02:03,690 --> 00:02:06,180 +但如果我们的模型预测了一些不可能的事情, +but if our model predicts something impossible, + +48 +00:02:06,180 --> 00:02:07,920 +就像问题中的标记, +like tokens in the questions, + +49 +00:02:07,920 --> 00:02:09,670 +我们将查看更多的 logits。 +we will look at more of the logits. + +50 +00:02:10,800 --> 00:02:12,570 +请注意,在问答管道中, +Note that in the question-answering pipeline, + +51 +00:02:12,570 --> 00:02:14,160 +我们将分数归因于每个答案 +we attributed the score to each answer + +52 +00:02:14,160 --> 00:02:17,880 +基于我们没有在这里计算的概率。 +based on the probabilities, which we did not compute here. + +53 +00:02:17,880 --> 00:02:19,860 +在 logits 方面,我们有乘法 +In terms of logits, the multiplication we had + +54 +00:02:19,860 --> 00:02:21,663 +在分数中成为加法。 +in the scores becomes an addition. + +55 +00:02:22,650 --> 00:02:23,910 +为了走得快,我们不看 +To go fast, we don't look + +56 +00:02:23,910 --> 00:02:25,343 +在所有可能的开始和结束日志中, +at all possible start and end logits, + +57 +00:02:25,343 --> 00:02:26,973 +但最好的 20 个。 +but the 20 best ones. + +58 +00:02:27,810 --> 00:02:30,386 +我们忽略产生不可能答案的逻辑 +We ignore the logits that spawn impossible answers + +59 +00:02:30,386 --> 00:02:32,370 +或回答太长。 +or answer that are too long. + +60 +00:02:32,370 --> 00:02:33,720 +正如我们在预处理中看到的, +As we saw in the preprocessing, + +61 +00:02:33,720 --> 00:02:36,240 +标签 “0, 0” 对应于没有答案, +the label "0, 0" correspond to no answer, + +62 +00:02:36,240 --> 00:02:37,440 +否则我们使用偏移量 +otherwise we use the offset + +63 +00:02:37,440 --> 00:02:39,290 +在上下文中得到答案。 +to get the answer inside the context. + +64 +00:02:40,260 --> 00:02:41,580 +我们来看看预测答案 +Let's have a look at the predicted answer + +65 +00:02:41,580 --> 00:02:43,200 +对于第一个功能, +for the first feature, + +66 +00:02:43,200 --> 00:02:44,790 +这是得分最高的答案, +which is the answer with the best score, + +67 +00:02:44,790 --> 00:02:46,860 +或自 SoftMax 以来最好的 logit 分数 +or the best logit score since the SoftMax + +68 +00:02:46,860 --> 00:02:48,810 +是增函数。 +is an increasing function. + +69 +00:02:48,810 --> 00:02:49,960 +模型做对了。 +The model got it right. + +70 +00:02:51,210 --> 00:02:54,180 +接下来,我们只需要为每个示例循环这个, +Next, we just have to loop this for every example, + +71 +00:02:54,180 --> 00:02:56,700 +为每个选择具有最佳 logit 分数的答案 +picking for each the answer with the best logit score + +72 +00:02:56,700 --> 00:02:59,133 +在示例生成的所有功能中。 +in all the features the example generated. + +73 +00:03:00,030 --> 00:03:03,030 +现在你知道如何从模型预测中获得答案了。 +Now you know how to get answers from your model predictions. + +74 +00:03:04,214 --> 00:03:06,797 +(微妙的爆炸) +(subtle blast) + diff --git a/subtitles/zh-CN/68_data-collators-a-tour.srt b/subtitles/zh-CN/68_data-collators-a-tour.srt new file mode 100644 index 000000000..c5d93c002 --- /dev/null +++ b/subtitles/zh-CN/68_data-collators-a-tour.srt @@ -0,0 +1,735 @@ +1 +00:00:00,670 --> 00:00:01,503 +(嘶嘶声) +(whooshing sound) + +2 +00:00:01,503 --> 00:00:02,469 +(贴纸弹出) +(sticker popping) + +3 +00:00:02,469 --> 00:00:05,302 +(嘶嘶声) +(whooshing sound) + +4 +00:00:06,240 --> 00:00:08,220 +在我们的很多例子中, +In a lot of our examples, + +5 +00:00:08,220 --> 00:00:12,150 +你将看到 DataCollators 一遍又一遍地弹出。 +you're going to see DataCollators popping up over and over. + +6 +00:00:12,150 --> 00:00:16,020 +它们用于 PyTorch 和 TensorFlow 工作流程, +They're used in both PyTorch and TensorFlow workflows, + +7 +00:00:16,020 --> 00:00:17,460 +甚至在 JAX 中, +and maybe even in JAX, + +8 +00:00:17,460 --> 00:00:20,130 +但是没有人真正知道 JAX 中发生了什么。 +but no-one really knows what's happening in JAX. + +9 +00:00:20,130 --> 00:00:21,840 +不过,我们确实有一个研究团队正在研究它, +We do have a research team working on it though, + +10 +00:00:21,840 --> 00:00:23,970 +所以也许他们很快就会告诉我们。 +so maybe they'll tell us soon. + +11 +00:00:23,970 --> 00:00:25,620 +但是回到主题。 +But coming back on topic. + +12 +00:00:25,620 --> 00:00:27,600 +什么是数据整理器? +What are data collators? + +13 +00:00:27,600 --> 00:00:30,480 +数据整理者整理数据。 +Data collators collate data. + +14 +00:00:30,480 --> 00:00:31,800 +那不是很有帮助。 +That's not that helpful. + +15 +00:00:31,800 --> 00:00:35,023 +但更具体地说,他们整理了一份样本清单 +But to be more specific, they put together a list of samples + +16 +00:00:35,023 --> 00:00:37,830 +成一个单一的训练小批量。 +into a single training minibatch. + +17 +00:00:37,830 --> 00:00:38,910 +对于某些任务, +For some tasks, + +18 +00:00:38,910 --> 00:00:41,670 +数据整理器可以非常简单。 +the data collator can be very straightforward. + +19 +00:00:41,670 --> 00:00:44,820 +例如,当你进行序列分类时, +For example, when you're doing sequence classification, + +20 +00:00:44,820 --> 00:00:47,010 +数据整理器提供你真正需要的一切 +all you really need from your data collator + +21 +00:00:47,010 --> 00:00:49,860 +是它将你的样品填充到相同的长度 +is that it pads your samples to the same length + +22 +00:00:49,860 --> 00:00:52,413 +并将它们连接成一个张量。 +and concatenates them into a single Tensor. + +23 +00:00:53,340 --> 00:00:57,750 +但对于其他工作流程,数据整理器可能非常复杂 +But for other workflows, data collators can be quite complex + +24 +00:00:57,750 --> 00:00:59,910 +因为他们处理一些预处理 +as they handle some of the preprocessing + +25 +00:00:59,910 --> 00:01:02,340 +该特定任务所需。 +needed for that particular task. + +26 +00:01:02,340 --> 00:01:04,800 +所以,如果你想使用数据整理器, +So, if you want to use a data collator, + +27 +00:01:04,800 --> 00:01:07,860 +对于 PyTorch 用户,你通常通过数据整理器 +for PyTorch users, you usually pass the data collator + +28 +00:01:07,860 --> 00:01:09,780 +到你的 Trainer 对象。 +to your Trainer object. + +29 +00:01:09,780 --> 00:01:11,310 +在 TensorFlow 中,情况有点不同。 +In TensorFlow, it's a bit different. + +30 +00:01:11,310 --> 00:01:12,960 +使用数据整理器的最简单方法 +The easiest way to use a data collator + +31 +00:01:12,960 --> 00:01:16,860 +是将它传递给数据集的 to_tf_dataset 方法。 +is to pass it to the to_tf_dataset method of your dataset. + +32 +00:01:16,860 --> 00:01:20,198 +这会给你一个 tensorflow_tf_data.dataset +And this will give you a tensorflow_tf_data.dataset + +33 +00:01:20,198 --> 00:01:22,743 +然后你可以将其传递给 model.fit。 +that you can then pass to model.fit. + +34 +00:01:23,580 --> 00:01:25,890 +你将在示例中看到这些方法 +You'll see these approaches used in the examples + +35 +00:01:25,890 --> 00:01:28,068 +和整个课程的笔记本。 +and notebooks throughout this course. + +36 +00:01:28,068 --> 00:01:30,180 +另请注意,我们所有的整理器 +Also note that all of our collators + +37 +00:01:30,180 --> 00:01:32,610 +采用 return_tensors 参数。 +take a return_tensors argument. + +38 +00:01:32,610 --> 00:01:35,737 +你可以将其设置为 “pt” 以获取 PyTorch 张量, +You can set this to "pt" to get PyTorch Tensors, + +39 +00:01:35,737 --> 00:01:37,920 +"tf" 获取 TensorFlow 张量, +"tf" to get TensorFlow Tensors, + +40 +00:01:37,920 --> 00:01:40,404 +或 “np” 获取 Numpy 数组。 +or "np" to get Numpy arrays. + +41 +00:01:40,404 --> 00:01:42,450 +出于向后兼容的原因, +For backward compatibility reasons, + +42 +00:01:42,450 --> 00:01:44,460 +默认值为 “pt”, +the default value is "pt", + +43 +00:01:44,460 --> 00:01:47,160 +所以 PyTorch 用户甚至不必设置这个参数 +so PyTorch users don't even have to set this argument + +44 +00:01:47,160 --> 00:01:48,270 +大多数时候。 +most of the time. + +45 +00:01:48,270 --> 00:01:50,820 +因此,他们通常完全没有意识到 +And so as a result, they're often totally unaware + +46 +00:01:50,820 --> 00:01:52,713 +这个论点甚至存在。 +that this argument even exists. + +47 +00:01:53,730 --> 00:01:55,050 +我们可以从中学到一些东西 +We can learn something from this + +48 +00:01:55,050 --> 00:01:57,120 +也就是特权的受益人 +which is that the beneficiaries of privilege + +49 +00:01:57,120 --> 00:01:59,793 +往往最无视它的存在。 +are often the most blind to its existence. + +50 +00:02:00,690 --> 00:02:01,920 +不过还好,回来了。 +But okay, coming back. + +51 +00:02:01,920 --> 00:02:06,540 +让我们看看一些特定的数据整理器是如何工作的。 +Let's see how some specific data collators work in action. + +52 +00:02:06,540 --> 00:02:08,070 +虽然再次记住如果没有 +Although again, remember if none + +53 +00:02:08,070 --> 00:02:09,900 +的内置数据整理器可以满足你的需求, +of the built-in data collators do what you need, + +54 +00:02:09,900 --> 00:02:13,650 +你总是可以自己写,而且它们通常很短。 +you can always write your own and they're often quite short. + +55 +00:02:13,650 --> 00:02:16,950 +所以首先,我们将看到 “基本” 数据整理器。 +So first, we'll see the "basic" data collators. + +56 +00:02:16,950 --> 00:02:20,433 +它们是 DefaultDataCollator 和 DataCollatorWithPadding。 +These are DefaultDataCollator and DataCollatorWithPadding. + +57 +00:02:21,420 --> 00:02:22,830 +这些是你应该使用的 +These are the ones you should use + +58 +00:02:22,830 --> 00:02:24,720 +如果你的标签很简单 +if your labels are straightforward + +59 +00:02:24,720 --> 00:02:27,300 +并且你的数据不需要任何特殊处理 +and your data doesn't need any special processing + +60 +00:02:27,300 --> 00:02:29,673 +在准备训练之前。 +before being ready for training. + +61 +00:02:29,673 --> 00:02:31,272 +请注意,因为不同的模型 +Notice that because different models + +62 +00:02:31,272 --> 00:02:33,690 +有不同的填充标记, +have different padding tokens, + +63 +00:02:33,690 --> 00:02:37,170 +DataCollatorWithPadding 将需要你模型的 Tokenizer +DataCollatorWithPadding will need your model's Tokenizer + +64 +00:02:37,170 --> 00:02:40,150 +所以它知道如何正确填充序列。 +so it knows how to pad sequences properly. + +65 +00:02:40,150 --> 00:02:44,790 +默认的数据整理器不需要 Tokenizer 来工作, +The default data collator doesn't need a Tokenizer to work, + +66 +00:02:44,790 --> 00:02:46,710 +但它会因此抛出错误 +but it will as a result throw an error + +67 +00:02:46,710 --> 00:02:48,900 +除非你所有的序列都是相同的长度。 +unless all of your sequences are the same length. + +68 +00:02:48,900 --> 00:02:50,500 +所以,你应该意识到这一点。 +So, you should be aware of that. + +69 +00:02:51,480 --> 00:02:52,860 +继续前进。 +Moving on though. + +70 +00:02:52,860 --> 00:02:54,300 +许多其他数据整理器 +A lot of the other data collators + +71 +00:02:54,300 --> 00:02:56,130 +除了基本的两个, +aside from the basic two are, + +72 +00:02:56,130 --> 00:02:59,490 +它们通常旨在处理一项特定任务。 +they're usually designed to handle one specific task. + +73 +00:02:59,490 --> 00:03:01,050 +所以,我要在这里展示一对。 +And so, I'm going to show a couple here. + +74 +00:03:01,050 --> 00:03:04,320 +这些是 DataCollatorForTokenClassification +These are DataCollatorForTokenClassification + +75 +00:03:04,320 --> 00:03:06,447 +和 DataCollatorForSeqToSeq。 +and DataCollatorForSeqToSeq. + +76 +00:03:06,447 --> 00:03:09,540 +以及这些任务需要特殊整理器的原因 +And the reason these tasks need special collators + +77 +00:03:09,540 --> 00:03:12,600 +是因为它们的标签长度可变。 +is because their labels are variable in length. + +78 +00:03:12,600 --> 00:03:15,960 +在令牌分类中,每个令牌都有一个标签, +In token classification there's one label for each token, + +79 +00:03:15,960 --> 00:03:17,400 +标签的长度 +and so the length of the labels + +80 +00:03:17,400 --> 00:03:18,993 +是序列的长度。 +is the length of the sequence. + +81 +00:03:20,280 --> 00:03:23,520 +而在 SeqToSeq 中,标签是一系列标记 +While in SeqToSeq the labels are a sequence of tokens + +82 +00:03:23,520 --> 00:03:24,780 +可以是可变长度, +that can be variable length, + +83 +00:03:24,780 --> 00:03:25,800 +那可能会非常不同 +that can be very different + +84 +00:03:25,800 --> 00:03:28,200 +从输入序列的长度。 +from the length of the input sequence. + +85 +00:03:28,200 --> 00:03:32,880 +所以在这两种情况下,我们处理整理那批 +So in both of these cases, we handle collating that batch + +86 +00:03:32,880 --> 00:03:35,280 +也通过填充标签, +by padding the labels as well, + +87 +00:03:35,280 --> 00:03:37,410 +正如你在此示例中看到的那样。 +as you can see here in this example. + +88 +00:03:37,410 --> 00:03:40,770 +因此,需要填充输入和标签 +So, inputs and the labels will need to be padded + +89 +00:03:40,770 --> 00:03:43,860 +如果我们想加入可变长度的样本 +if we want to join samples of variable length + +90 +00:03:43,860 --> 00:03:45,120 +进入同一个小批量。 +into the same minibatch. + +91 +00:03:45,120 --> 00:03:47,520 +这正是数据整理者所做的 +That's exactly what the data collators + +92 +00:03:47,520 --> 00:03:50,460 +而这正是这些数据整理者将为我们做的 +and that's exactly what these data collators will do for us + +93 +00:03:50,460 --> 00:03:52,383 +你知道,为了这个特定的任务。 +you know, for this particular task. + +94 +00:03:53,820 --> 00:03:56,070 +所以,有一个最终的数据整理器 +So, there's one final data collator + +95 +00:03:56,070 --> 00:03:58,560 +我也想在本次讲座中向你展示。 +I want to show you as well just in this lecture. + +96 +00:03:58,560 --> 00:04:00,473 +这就是 DataCollatorForLanguageModeling。 +And that's the DataCollatorForLanguageModeling. + +97 +00:04:01,410 --> 00:04:03,390 +所以,这非常重要,首先, +So, it's very important, and it's firstly, + +98 +00:04:03,390 --> 00:04:05,820 +因为语言模型是如此基础 +because language models are just so foundational + +99 +00:04:05,820 --> 00:04:09,720 +这些天我们用 NLP 所做的一切。 +to do for everything we do with NLP these days. + +100 +00:04:09,720 --> 00:04:12,060 +但其次,因为它有两种模式 +But secondly, because it has two modes + +101 +00:04:12,060 --> 00:04:14,760 +做两件截然不同的事情。 +that do two very different things. + +102 +00:04:14,760 --> 00:04:19,230 +因此,你可以使用 mlm 参数选择所需的模式。 +So you choose which mode you want with the mlm argument. + +103 +00:04:19,230 --> 00:04:22,470 +将其设置为 True 以进行掩码语言建模, +Set it to True for masked language modeling, + +104 +00:04:22,470 --> 00:04:26,190 +并将其设置为 False 以进行因果语言建模。 +and set it to False for causal language modeling. + +105 +00:04:26,190 --> 00:04:28,620 +因此,为因果语言建模整理数据 +So, collating data for causal language modeling + +106 +00:04:28,620 --> 00:04:30,750 +其实很简单。 +is actually quite straightforward. + +107 +00:04:30,750 --> 00:04:32,640 +该模型只是做出预测 +The model is just making predictions + +108 +00:04:32,640 --> 00:04:35,460 +接下来是什么标记,所以你的标签 +for what token comes next, and so your labels + +109 +00:04:35,460 --> 00:04:37,800 +或多或少只是你输入的副本, +are more or less just a copy of your inputs, + +110 +00:04:37,800 --> 00:04:39,090 +整理人会处理 +and the collator will handle that + +111 +00:04:39,090 --> 00:04:42,240 +并确保正确填充输入和标签。 +and ensure that the inputs and labels are padded correctly. + +112 +00:04:42,240 --> 00:04:44,910 +但是,当你将 mlm 设置为 True 时, +When you set mlm to True though, + +113 +00:04:44,910 --> 00:04:46,786 +你会得到完全不同的行为, +you get quite different behavior, + +114 +00:04:46,786 --> 00:04:49,200 +这与任何其他数据整理器不同, +that's different from any other data collator, + +115 +00:04:49,200 --> 00:04:51,660 +那是因为将 mlm 设置为 True +and that's because setting mlm to True + +116 +00:04:51,660 --> 00:04:53,550 +表示掩码语言建模 +means masked language modeling + +117 +00:04:53,550 --> 00:04:55,680 +这意味着标签必须是 +and that means the labels need to be, + +118 +00:04:55,680 --> 00:04:58,080 +你知道,输入需要被屏蔽。 +you know, the inputs need to be masked. + +119 +00:04:58,080 --> 00:05:00,093 +那么,那看起来像什么? +So, what does that look like? + +120 +00:05:01,050 --> 00:05:03,900 +所以,回想一下,在屏蔽语言建模中, +So, recall that in masked language modeling, + +121 +00:05:03,900 --> 00:05:06,570 +该模型没有预测下一个词, +the model is not predicting the next word, + +122 +00:05:06,570 --> 00:05:09,240 +相反,我们随机屏蔽掉一些标记 +instead we randomly mask out some tokens + +123 +00:05:09,240 --> 00:05:11,130 +模型会同时预测所有这些。 +and the model predicts all of them at once. + +124 +00:05:11,130 --> 00:05:12,780 +所以,它试图填补空白 +So, it tries to kinda fill in the blanks + +125 +00:05:12,780 --> 00:05:14,790 +对于那些被屏蔽的令牌。 +for those masked tokens. + +126 +00:05:14,790 --> 00:05:18,210 +但是随机掩蔽的过程出奇地复杂。 +But the process of random masking is surprisingly complex. + +127 +00:05:18,210 --> 00:05:21,330 +如果我们遵循原始 BERT 论文中的协议, +If we follow the protocol from the original BERT paper, + +128 +00:05:21,330 --> 00:05:23,970 +我们需要用掩码标记替换一些标记, +we need to replace some tokens with a masked token, + +129 +00:05:23,970 --> 00:05:26,190 +一些其他带有随机令牌的令牌, +some other tokens with a random token, + +130 +00:05:26,190 --> 00:05:29,820 +然后保持第三组标记不变。 +and then keep a third set of tokens unchanged. + +131 +00:05:29,820 --> 00:05:30,840 +是的,这不是讲座 +Yeah, this is not the lecture + +132 +00:05:30,840 --> 00:05:33,903 +进入细节或我们为什么这样做。 +to go into the specifics of that or why we do it. + +133 +00:05:33,903 --> 00:05:36,660 +你可以随时查看原始的 BERT 论文 +You can always check out the original BERT paper + +134 +00:05:36,660 --> 00:05:37,493 +如果你好奇的话。 +if you're curious. + +135 +00:05:37,493 --> 00:05:39,620 +写得很好。这很容易理解。 +It's well written. It's easy to understand. + +136 +00:05:40,650 --> 00:05:44,190 +这里要知道的主要事情是它可能是一个真正的痛苦 +The main thing to know here is that it can be a real pain + +137 +00:05:44,190 --> 00:05:46,770 +并且自己实施起来非常复杂。 +and quite complex to implement that yourself. + +138 +00:05:46,770 --> 00:05:49,740 +但是 DataCollatorForLanguageModeling 会为你做 +But DataCollatorForLanguageModeling will do it for you + +139 +00:05:49,740 --> 00:05:51,750 +当你将 mlm 设置为 True 时。 +when you set mlm to True. + +140 +00:05:51,750 --> 00:05:54,690 +这是一个更复杂的例子 +And that's an example of the more intricate + +141 +00:05:54,690 --> 00:05:57,870 +我们的一些数据整理人员所做的预处理。 +preprocessing that some of our data collators do. + +142 +00:05:57,870 --> 00:05:59,430 +就是这样! +And that's it! + +143 +00:05:59,430 --> 00:06:01,920 +因此,这涵盖了最常用的数据整理器 +So, this covers the most commonly used data collators + +144 +00:06:01,920 --> 00:06:03,480 +以及它们用于执行的任务。 +and the tasks they're used for. + +145 +00:06:03,480 --> 00:06:06,990 +希望现在你会知道何时使用数据整理器 +And hopefully, now you'll know when to use data collators + +146 +00:06:06,990 --> 00:06:10,833 +以及为你的特定任务选择哪一个。 +and which one to choose for your specific task. + +147 +00:06:11,765 --> 00:06:14,598 +(嘶嘶声) +(whooshing sound) + diff --git a/subtitles/zh-CN/69_what-to-do-when-you-get-an-error.srt b/subtitles/zh-CN/69_what-to-do-when-you-get-an-error.srt new file mode 100644 index 000000000..28f305040 --- /dev/null +++ b/subtitles/zh-CN/69_what-to-do-when-you-get-an-error.srt @@ -0,0 +1,315 @@ +1 +00:00:00,380 --> 00:00:02,463 +(呼呼) +(whoosh) + +2 +00:00:05,550 --> 00:00:07,590 +- 在本视频中,我们将学习第一件事 +- In this video we'll learn the first things to + +3 +00:00:07,590 --> 00:00:09,330 +当你得到一个错误时做。 +do when you get an error. + +4 +00:00:09,330 --> 00:00:11,930 +这不是将笔记本电脑扔出窗外。 +This is not throwing your laptop through the window. + +5 +00:00:13,320 --> 00:00:15,450 +假设我们要使用问答管道 +Let's say we want to use the question answering pipeline + +6 +00:00:15,450 --> 00:00:19,470 +在特定型号上,我们收到以下错误。 +on a particular model and we get the following error. + +7 +00:00:19,470 --> 00:00:21,750 +Python 中的错误可能看起来势不可挡 +Errors in Python can appear overwhelming + +8 +00:00:21,750 --> 00:00:24,390 +因为你得到了很多信息打印出来 +because you get so much information printed out + +9 +00:00:24,390 --> 00:00:26,610 +但那是因为 Python 试图帮助你 +but that's because Python is trying to help you + +10 +00:00:26,610 --> 00:00:29,070 +尽其所能解决你的问题。 +the best it can to solve your problem. + +11 +00:00:29,070 --> 00:00:31,260 +在本视频中,我们将了解如何解读 +In this video, we'll see how to interpret + +12 +00:00:31,260 --> 00:00:32,460 +我们得到的错误报告。 +the error report we get. + +13 +00:00:33,510 --> 00:00:35,700 +首先要注意的是顶部 +The first thing to notice at the very top + +14 +00:00:35,700 --> 00:00:38,070 +是 Python 给你显示了一个清晰的箭头 +is that Python shows you with a clear arrow + +15 +00:00:38,070 --> 00:00:40,320 +触发错误的代码行 +the line of code that triggers the error + +16 +00:00:40,320 --> 00:00:42,210 +所以你不必摆弄你的代码 +so you don't have to fiddle with your code + +17 +00:00:42,210 --> 00:00:43,800 +并删除随机线以找出 +and remove random lines to figure out + +18 +00:00:43,800 --> 00:00:45,540 +错误来自哪里。 +where the error comes from. + +19 +00:00:45,540 --> 00:00:47,890 +答案就在你面前。 +You have the answer in front of you right here. + +20 +00:00:49,140 --> 00:00:51,360 +你在下面看到的错误是代码的一部分 +The errors you see below are a part of the code + +21 +00:00:51,360 --> 00:00:54,930 +Python 试图在运行指令时执行。 +Python tried to execute while running the instruction. + +22 +00:00:54,930 --> 00:00:57,750 +我们在管道函数中 +Here we are inside the pipeline function + +23 +00:00:57,750 --> 00:00:59,490 +零出现在这条线上 +and zero came on this line + +24 +00:00:59,490 --> 00:01:02,520 +在尝试执行函数 “check_tasks” 时, +while trying to execute the function "check_tasks," + +25 +00:01:02,520 --> 00:01:05,103 +然后引发了我们看到的 KeyError 显示。 +which then raised the KeyError we see displayed. + +26 +00:01:06,630 --> 00:01:08,580 +请注意,Python 会准确地告诉你 +Note that Python tells you exactly + +27 +00:01:08,580 --> 00:01:11,190 +它正在执行的功能所在的位置, +where the function it's executing lives, + +28 +00:01:11,190 --> 00:01:12,810 +所以如果你喜欢冒险 +so if you feel adventurous + +29 +00:01:12,810 --> 00:01:14,810 +你甚至可以去检查源代码。 +you can even go inspect the source code. + +30 +00:01:15,900 --> 00:01:18,447 +这整件事被称为 “追溯”。 +This whole thing is called the "Traceback." + +31 +00:01:20,010 --> 00:01:21,870 +如果你在 Colab 上运行代码 +If you're running your code on Colab + +32 +00:01:21,870 --> 00:01:23,820 +Traceback 自动最小化, +the Traceback is automatically minimized, + +33 +00:01:23,820 --> 00:01:25,833 +所以你必须点击展开它。 +so you have to click to expand it. + +34 +00:01:26,820 --> 00:01:28,530 +在回溯的最后 +At the very end of the Traceback + +35 +00:01:28,530 --> 00:01:31,890 +你终于得到了实际的错误信息。 +you finally get the actual error message. + +36 +00:01:31,890 --> 00:01:33,660 +遇到你应该做的第一件事 +The first thing you should do when encountering + +37 +00:01:33,660 --> 00:01:36,480 +错误是阅读该错误消息。 +an error is to read that error message. + +38 +00:01:36,480 --> 00:01:38,640 +它告诉我们它不知道 +Here it's telling us it doesn't know + +39 +00:01:38,640 --> 00:01:40,230 +问答任务 +the question answering task + +40 +00:01:40,230 --> 00:01:41,760 +并帮助我们列出了清单 +and helpfully gives us the list + +41 +00:01:41,760 --> 00:01:44,850 +我们可以看到支持的任务 +of supported tasks in which we can see + +42 +00:01:44,850 --> 00:01:47,520 +那个 “问答” 实际上是。 +that "question-answering" actually is. + +43 +00:01:47,520 --> 00:01:49,200 +仔细一看, +Looking more closely though, + +44 +00:01:49,200 --> 00:01:52,020 +我们用下划线来惊奇这两个词 +we used an underscore to surprise the two words + +45 +00:01:52,020 --> 00:01:54,300 +当任务写有减号时, +when the task is written with a minus, + +46 +00:01:54,300 --> 00:01:55,413 +所以我们应该解决这个问题。 +so we should fix that. + +47 +00:01:57,510 --> 00:02:00,360 +现在让我们用正确编写的标签重试我们的代码 +Now let's retry our code with the tags properly written + +48 +00:02:00,360 --> 00:02:01,920 +今天发生了什么? +and what is happening today? + +49 +00:02:01,920 --> 00:02:03,210 +另一个错误。 +Another error. + +50 +00:02:03,210 --> 00:02:05,670 +正如我们之前所说,我们去看看底部 +As we said before, we go look at the bottom + +51 +00:02:05,670 --> 00:02:07,560 +阅读实际的错误信息。 +to read the actual error message. + +52 +00:02:07,560 --> 00:02:09,000 +它告诉我们应该检查 +It's telling us that we should check + +53 +00:02:09,000 --> 00:02:11,340 +我们的模型是正确的模型标识符, +our model is a correct model identifier, + +54 +00:02:11,340 --> 00:02:14,760 +所以让我们跳到 hf.co/models。 +so let's hop onto hf.co/models. + +55 +00:02:14,760 --> 00:02:16,440 +我们可以看到我们的模型列在那里 +We can see our model listed there + +56 +00:02:16,440 --> 00:02:19,440 +在可用于问答的那些。 +in the ones available for question answering. + +57 +00:02:19,440 --> 00:02:21,720 +不同之处在于它拼写为 “distilbert” +The difference is that it's spelled "distilbert" + +58 +00:02:21,720 --> 00:02:24,240 +用一个 L,我们用两个, +with one L, and we use two, + +59 +00:02:24,240 --> 00:02:25,650 +所以让我们解决这个问题。 +so let's fix that. + +60 +00:02:25,650 --> 00:02:27,570 +我们终于得到了结果。 +We finally get our results. + +61 +00:02:27,570 --> 00:02:29,160 +如果我们的错误更复杂, +If our error is more complex, + +62 +00:02:29,160 --> 00:02:31,290 +你可能需要使用 Python 调试器。 +you might need to use the Python debugger. + +63 +00:02:31,290 --> 00:02:33,483 +查看下面的视频以了解操作方法。 +Check out the videos below to learn how. + diff --git a/subtitles/zh-CN/70_using-a-debugger-in-a-notebook.srt b/subtitles/zh-CN/70_using-a-debugger-in-a-notebook.srt new file mode 100644 index 000000000..658ffbb28 --- /dev/null +++ b/subtitles/zh-CN/70_using-a-debugger-in-a-notebook.srt @@ -0,0 +1,360 @@ +1 +00:00:05,400 --> 00:00:08,150 +- [讲师] 在笔记本中使用 Python 调试器。 +- [Instructor] Using the Python debugger in a notebook. + +2 +00:00:09,540 --> 00:00:12,330 +在本视频中,我们将学习如何使用 Python 调试器 +In this video, we'll learn how to use the Python debugger + +3 +00:00:12,330 --> 00:00:15,027 +在 Jupyter Notebook 或 Colab 中。 +in a Jupyter Notebook or a Colab. + +4 +00:00:15,027 --> 00:00:17,070 +对于这个例子,我们正在运行代码 +For this example, we are running code + +5 +00:00:17,070 --> 00:00:19,775 +从令牌分类部分, +from the token classification section, + +6 +00:00:19,775 --> 00:00:21,513 +下载 Conll 数据集, +downloading the Conll dataset, + +7 +00:00:23,670 --> 00:00:25,503 +稍微看一下数据, +looking a little bit at data, + +8 +00:00:27,840 --> 00:00:29,250 +在加载分词器之前 +before loading a tokenizer + +9 +00:00:29,250 --> 00:00:31,173 +预处理整个数据集。 +to preprocess the whole dataset. + +10 +00:00:32,880 --> 00:00:34,740 +查看下面链接的课程部分 +Check out the section of the course linked below + +11 +00:00:34,740 --> 00:00:35,823 +了解更多信息。 +for more information. + +12 +00:00:37,080 --> 00:00:38,520 +一旦完成, +Once this is done, + +13 +00:00:38,520 --> 00:00:41,580 +我们尝试加载训练数据集的八个特征, +we try to load eight features of the training dataset, + +14 +00:00:41,580 --> 00:00:43,080 +然后将它们批在一起, +and then batch them together, + +15 +00:00:43,080 --> 00:00:45,210 +使用 tokenizer.pad, +using tokenizer.pad, + +16 +00:00:45,210 --> 00:00:46,760 +我们得到以下错误。 +and we get the following error. + +17 +00:00:48,090 --> 00:00:49,230 +我们在这里使用 PyTorch, +We use PyTorch here, + +18 +00:00:49,230 --> 00:00:51,330 +使用 return_tensors="pt" +with return_tensors="pt" + +19 +00:00:51,330 --> 00:00:53,273 +但是你会在 TensorFlow 中遇到同样的错误。 +but you will get the same error with TensorFlow. + +20 +00:00:54,120 --> 00:00:55,897 +正如我们在 “如何调试错误?” 中看到的那样。视频, +As we have seen in the "How to debug an error?" video, + +21 +00:00:55,897 --> 00:00:59,160 +错误消息在回溯的末尾。 +the error message is at the end of the traceback. + +22 +00:00:59,160 --> 00:01:01,710 +在这里,它表明我们应该使用填充, +Here, it indicates us we should use padding, + +23 +00:01:01,710 --> 00:01:04,290 +我们实际上正在尝试这样做。 +which we are actually trying to do. + +24 +00:01:04,290 --> 00:01:05,610 +所以这根本没有用, +So this is not useful at all, + +25 +00:01:05,610 --> 00:01:06,990 +我们需要更深入一点 +and we will need to go a little deeper + +26 +00:01:06,990 --> 00:01:08,610 +调试问题。 +to debug the problem. + +27 +00:01:08,610 --> 00:01:10,650 +幸运的是,你可以使用 Python 调试器 +Fortunately, you can use the Python debugger + +28 +00:01:10,650 --> 00:01:13,170 +任何时候你在 Jupyter Notebook 中遇到错误 +at any time you get an error in a Jupyter Notebook + +29 +00:01:13,170 --> 00:01:16,350 +通过在单元格中键入魔法命令 debug。 +by typing the magic command, debug, in a cell. + +30 +00:01:16,350 --> 00:01:18,450 +不要忘记开头的百分比。 +Don't forget the percent at the beginning. + +31 +00:01:20,400 --> 00:01:21,870 +执行该单元格时, +When executing that cell, + +32 +00:01:21,870 --> 00:01:23,910 +你走到回溯的最底部 +you go to the very bottom of the traceback + +33 +00:01:23,910 --> 00:01:25,320 +你可以在其中键入命令 +where you can type commands + +34 +00:01:25,320 --> 00:01:27,690 +这将帮助你调试脚本。 +that will help you debug your script. + +35 +00:01:27,690 --> 00:01:29,250 +你应该学习的前两个命令, +The first two commands you should learn, + +36 +00:01:29,250 --> 00:01:32,040 +是 u 和 d,代表向上和向下。 +are u and d, for up and down. + +37 +00:01:32,040 --> 00:01:36,090 +输入 u 和 enter 会让你更上一层楼 +Typing u and enter will take you up one step + +38 +00:01:36,090 --> 00:01:38,910 +在上一条指令的回溯中。 +in the traceback to the previous instruction. + +39 +00:01:38,910 --> 00:01:41,190 +输入 d 然后输入将带你 +Typing d and then enter will take you + +40 +00:01:41,190 --> 00:01:43,023 +在追溯中向下迈出一步。 +one step down in the traceback. + +41 +00:01:44,130 --> 00:01:47,910 +上升两次,我们到达了错误发生的地步。 +Going up twice, we get to the point the error was reached. + +42 +00:01:47,910 --> 00:01:51,510 +为调试器学习的第三个命令是 p,用于打印。 +The third command to learn for the debugger is p, for print. + +43 +00:01:51,510 --> 00:01:54,780 +它允许你打印任何你想要的值。 +It allows you to print any value you want. + +44 +00:01:54,780 --> 00:01:58,740 +例如,键入 p return_tensors 并输入, +For instance, typing p return_tensors and enter, + +45 +00:01:58,740 --> 00:02:02,893 +我们看到传递给错误函数的值 pt。 +we see the value pt that we pass to the bad function. + +46 +00:02:02,893 --> 00:02:05,370 +我们还可以查看批处理输出 +We can also have a look at the batch outputs + +47 +00:02:05,370 --> 00:02:07,353 +此批处理行编码对象获取。 +this batch line coding object gets. + +48 +00:02:09,480 --> 00:02:12,600 +批量输出字典有点难以挖掘, +The batch outputs dictionary is a bit hard to dig in to, + +49 +00:02:12,600 --> 00:02:15,360 +因此,让我们深入研究它的较小部分。 +so let's dive into smaller pieces of it. + +50 +00:02:15,360 --> 00:02:18,390 +在调试器内部,你不仅可以打印任何变量 +Inside the debugger you can not only print any variable + +51 +00:02:18,390 --> 00:02:20,970 +还要评估任何表达式, +but also evaluate any expression, + +52 +00:02:20,970 --> 00:02:23,610 +例如,我们可以查看 input_ids 键 +for instance, we can have a look at the input_ids keys + +53 +00:02:23,610 --> 00:02:25,203 +这个 batch_outputs 对象。 +this batch_outputs object. + +54 +00:02:27,600 --> 00:02:30,693 +或者在这个 batch_outputs 对象的标签键处。 +Or at the labels keys of this batch_outputs object. + +55 +00:02:35,730 --> 00:02:37,320 +这些标签肯定很奇怪: +Those labels are definitely weird: + +56 +00:02:37,320 --> 00:02:38,970 +它们大小不一, +they are of various sizes, + +57 +00:02:38,970 --> 00:02:41,340 +我们实际上可以确认,如果我们愿意的话, +which we can actually confirm, if we want, + +58 +00:02:41,340 --> 00:02:43,983 +通过以最小压缩打印尺寸。 +by printing the size with the least compression. + +59 +00:02:52,290 --> 00:02:54,913 +这是因为 tokenizer 的 pad 方法 +This is because the pad method of the tokenizer + +60 +00:02:54,913 --> 00:02:57,090 +只处理分词器输出: +only takes care of the tokenizer outputs: + +61 +00:02:57,090 --> 00:03:00,450 +输入 ID、注意掩码和令牌类型 ID, +input IDs, attention mask, and token type IDs, + +62 +00:03:00,450 --> 00:03:02,340 +所以我们必须自己填充标签 +so we have to pad the labels ourselves + +63 +00:03:02,340 --> 00:03:05,310 +在尝试用它们创建张量之前。 +before trying to create a tensor with them. + +64 +00:03:05,310 --> 00:03:07,260 +一旦你准备好退出 Python 调试器, +Once you are ready to exit the Python debugger, + +65 +00:03:07,260 --> 00:03:09,453 +你可以按 q 并输入退出。 +you can press q and enter for quit. + +66 +00:03:10,320 --> 00:03:11,670 +修复错误的一种方法 +One way to fix the error + +67 +00:03:11,670 --> 00:03:14,313 +就是手动把 labels 补到最长。 +is to manually pad the labels to the longest. + +68 +00:03:15,300 --> 00:03:17,400 +另一种方法是使用数据整理器 +Another way is to use a data collator + +69 +00:03:17,400 --> 00:03:19,863 +专为令牌分类而设计。 +specifically designed for token classification. + +70 +00:03:20,970 --> 00:03:22,950 +你也可以直接使用 Python 调试器 +You can also use a Python debugger directly + +71 +00:03:22,950 --> 00:03:23,850 +在终端。 +in the terminal. + +72 +00:03:23,850 --> 00:03:25,943 +查看下面的视频链接以了解操作方法。 +Check out the video link below to learn how. + diff --git a/subtitles/zh-CN/71_using-a-debugger-in-a-terminal.srt b/subtitles/zh-CN/71_using-a-debugger-in-a-terminal.srt new file mode 100644 index 000000000..712f36240 --- /dev/null +++ b/subtitles/zh-CN/71_using-a-debugger-in-a-terminal.srt @@ -0,0 +1,390 @@ +1 +00:00:00,459 --> 00:00:03,542 +(风拂过的声音) +(wind swiping sound) + +2 +00:00:05,880 --> 00:00:08,910 +- [讲师] 在终端中使用 Python 调试器。 +- [Instructor] Using the Python debugger in a terminal. + +3 +00:00:08,910 --> 00:00:11,580 +在本视频中,我们将学习如何使用 Python 调试器 +In this video, we'll learn how to use a Python debugger + +4 +00:00:11,580 --> 00:00:13,140 +在一个终端。 +in a terminal. + +5 +00:00:13,140 --> 00:00:15,390 +对于这个例子,我们正在运行代码 +For this example, we're running code + +6 +00:00:15,390 --> 00:00:17,760 +从令牌分类部分, +from the token classification section, + +7 +00:00:17,760 --> 00:00:19,950 +下载 Conll 数据集 +downloading the Conll dataset + +8 +00:00:19,950 --> 00:00:23,340 +在加载标记器以对其进行预处理之前。 +before loading a tokenizer to pre-process it. + +9 +00:00:23,340 --> 00:00:25,140 +查看下面的课程链接部分 +Check out the section of the course link below + +10 +00:00:25,140 --> 00:00:26,223 +了解更多信息。 +for more information. + +11 +00:00:27,600 --> 00:00:28,500 +一旦完成, +Once this is done, + +12 +00:00:28,500 --> 00:00:30,630 +我们尝试将一些功能组合在一起 +we try to batch together some features + +13 +00:00:30,630 --> 00:00:33,180 +通过填充训练数据集 +of the training dataset by padding them + +14 +00:00:33,180 --> 00:00:34,330 +并返回一个张量。 +and returning a tensor. + +15 +00:00:36,810 --> 00:00:39,510 +如果我们尝试在终端中执行我们的脚本 +If we try to execute our scripts in a terminal + +16 +00:00:39,510 --> 00:00:40,413 +我们得到一个错误。 +we get an error. + +17 +00:00:42,630 --> 00:00:44,260 +请注意,我们在这里使用 PyTorch +Note that we use PyTorch here + +18 +00:00:44,260 --> 00:00:45,600 +我们返回张量等于可惜。 +we return tensors equal pity. + +19 +00:00:45,600 --> 00:00:47,753 +但是你会在 TensorFlow 中遇到同样的错误。 +But you would get the same error with TensorFlow. + +20 +00:00:49,500 --> 00:00:51,990 +正如我们在 “如何调试错误?” 中看到的那样视频, +As we have seen in the, 'How to debug an error?' video, + +21 +00:00:51,990 --> 00:00:54,780 +原始消息在最后,它表明我们 +The raw message is at the end and it indicates we + +22 +00:00:54,780 --> 00:00:58,260 +应该使用配对,我们实际上正在尝试这样做。 +should use pairing, which we're actually trying to do. + +23 +00:00:58,260 --> 00:01:00,630 +所以这没有用,我们需要更深入一点 +So this is not useful and we need to go little deeper + +24 +00:01:00,630 --> 00:01:02,310 +调试问题。 +to debug the problem. + +25 +00:01:02,310 --> 00:01:04,830 +幸运的是,你可以很容易地使用 Python 调试器 +Fortunately, you can use the Python debugger quite easily + +26 +00:01:04,830 --> 00:01:09,830 +在终端中通过使用 Python -m PDB 启动脚本 +in a terminal by launching your script with Python -m PDB + +27 +00:01:09,930 --> 00:01:11,980 +然后是训练脚本的名称。 +and then the name of the training script. + +28 +00:01:13,410 --> 00:01:15,030 +执行该评论时,你将被发送 +When executing that comment, you are sent + +29 +00:01:15,030 --> 00:01:17,340 +到脚本的第一条指令。 +to the first instruction of your script. + +30 +00:01:17,340 --> 00:01:20,733 +你可以通过键入 N 并回车来运行下一条指令。 +You can run just the next instruction by typing N and enter. + +31 +00:01:22,530 --> 00:01:27,423 +或者你可以通过键入 C 并回车直接继续归零。 +Or you can continue directly to zero by typing C and enter. + +32 +00:01:29,850 --> 00:01:31,560 +一旦到了那里,你就会走到最底层 +Once there, you go to the very bottom + +33 +00:01:31,560 --> 00:01:34,050 +回溯,你可以键入命令。 +of the traceback and you can type commands. + +34 +00:01:34,050 --> 00:01:36,360 +你应该学习的前两个命令是 U 和 D, +The first two commands you should learn are U and D, + +35 +00:01:36,360 --> 00:01:38,160 +对于上下。 +for up and down. + +36 +00:01:38,160 --> 00:01:41,223 +这允许你在回溯中上下移动。 +This allows you to get up and down in the traceback. + +37 +00:01:42,990 --> 00:01:46,623 +上升两次,我们到达了错误发生的地步。 +Going up twice, we get to the point the error was reached. + +38 +00:01:47,910 --> 00:01:50,190 +要学习的第一个命令是 P,表示打印。 +The first command to learn is P for print. + +39 +00:01:50,190 --> 00:01:52,830 +它允许你打印任何你想要的值。 +It allows you to print any value you want. + +40 +00:01:52,830 --> 00:01:56,280 +例如,这里我们可以看到 return_tensors 的值 +For instance, here we can see the value of return_tensors + +41 +00:01:56,280 --> 00:02:00,210 +或 batch_outputs 试图了解触发零的原因。 +or batch_outputs to try to understand what triggered zero. + +42 +00:02:00,210 --> 00:02:03,000 +批量输出字典有点难看 +The batch outputs dictionary is a bit hard to see + +43 +00:02:03,000 --> 00:02:05,520 +因此,让我们深入研究它的较小部分。 +so let's dive into smaller pieces of it. + +44 +00:02:05,520 --> 00:02:08,460 +在调试器内部,你不仅可以打印任何变量 +Inside the debugger, you can not only print any variable + +45 +00:02:08,460 --> 00:02:10,740 +还要评估任何表达式, +but also evaluate any expression, + +46 +00:02:10,740 --> 00:02:13,713 +所以我们可以独立地查看输入。 +so we can look independently at the inputs. + +47 +00:02:15,060 --> 00:02:15,993 +还有标签。 +Also labels. + +48 +00:02:22,350 --> 00:02:24,300 +这些标签肯定很奇怪。 +Those labels are definitely weird. + +49 +00:02:24,300 --> 00:02:26,880 +它们有各种尺寸,我们可以确认 +They are various size, which we can confirm + +50 +00:02:26,880 --> 00:02:29,553 +通过使用释放压缩打印站点。 +by printing the sites using a release compression. + +51 +00:02:35,880 --> 00:02:37,800 +难怪分词器无法创建 +No wonder the tokenizer wasn't able to create + +52 +00:02:37,800 --> 00:02:39,270 +与他们的张量。 +a tensor with them. + +53 +00:02:39,270 --> 00:02:41,460 +这是因为 pad 方法只关心 +This is because the pad method only takes care + +54 +00:02:41,460 --> 00:02:44,850 +分词器的输出、输入 ID、注意掩码 +of the tokenizer outputs, the input IDs, the attention mask + +55 +00:02:44,850 --> 00:02:46,560 +和令牌类型 ID。 +and the token type IDs. + +56 +00:02:46,560 --> 00:02:48,390 +所以我们必须自己填充关卡 +So we have to pad the level ourselves + +57 +00:02:48,390 --> 00:02:51,300 +在尝试与他们一起创建新传感器之前。 +before trying to create a new sensor with them. + +58 +00:02:51,300 --> 00:02:54,030 +一旦你准备好执行 Python 调试器, +Once you're ready to execute the Python debugger, + +59 +00:02:54,030 --> 00:02:56,640 +你可以按 Q 退出并进入。 +you can press Q for quit and enter. + +60 +00:02:56,640 --> 00:02:59,790 +我们可以通过另一种方式访问 Python 调试器, +Another way we can access the Python debugger, + +61 +00:02:59,790 --> 00:03:02,310 +是在我们的脚本中设置一个断点。 +is to put a breaking point in our script. + +62 +00:03:02,310 --> 00:03:05,913 +我们可以使用 PDB 中的 set_trace 方法来做到这一点。 +We can do this using the PDB that set_trace method. + +63 +00:03:07,920 --> 00:03:09,870 +只要我们导入 PDB 模块 +As long as we import the PDB module + +64 +00:03:09,870 --> 00:03:11,420 +在我们脚本的开头。 +at the beginning of our script. + +65 +00:03:12,510 --> 00:03:17,283 +保存并重新启动我们的脚本,仅使用 Python。 +Saving and then relaunching our script, with just Python. + +66 +00:03:19,710 --> 00:03:23,310 +我们将在我们设置的断点处停止执行。 +We'll stop the execution at the breaking point we set. + +67 +00:03:23,310 --> 00:03:24,660 +我们可以检查所有变量 +We can inspect all the variable + +68 +00:03:24,660 --> 00:03:27,030 +在再次执行下一条指令之前。 +before the next instruction is executed again. + +69 +00:03:27,030 --> 00:03:29,253 +例如,这里的功能。 +For instance, here, the features. + +70 +00:03:30,270 --> 00:03:33,090 +输入 N 并回车执行下一条指令 +Typing N and enter execute the next instruction + +71 +00:03:33,090 --> 00:03:35,700 +这将我们带回到回溯中。 +which takes us back inside traceback. + +72 +00:03:35,700 --> 00:03:37,530 +当要手动修复零是 +When going to fix zero manually is to + +73 +00:03:37,530 --> 00:03:39,873 +将所有标签填充到最长。 +pad all the labels to the longest. + +74 +00:03:42,000 --> 00:03:45,120 +另一种方法是使用合适的数据创建器 +Another way is to use the data creator suitable + +75 +00:03:45,120 --> 00:03:46,443 +用于令牌分类。 +for token classification. + +76 +00:03:48,330 --> 00:03:50,340 +如果你想学习如何使用 Python +If you want to learn how to use the Python + +77 +00:03:50,340 --> 00:03:53,273 +笔记本中的调试器,查看下面链接中的视频。 +debugger in a notebook, check out the video in link below. + +78 +00:03:54,698 --> 00:03:57,781 +(风拂过的声音) +(wind swiping sound) + diff --git a/subtitles/zh-CN/72_asking-for-help-on-the-forums.srt b/subtitles/zh-CN/72_asking-for-help-on-the-forums.srt new file mode 100644 index 000000000..3ccb3dda1 --- /dev/null +++ b/subtitles/zh-CN/72_asking-for-help-on-the-forums.srt @@ -0,0 +1,390 @@ +1 +00:00:00,125 --> 00:00:01,455 +(标题嘶嘶作响) +(title whooshes) + +2 +00:00:01,455 --> 00:00:02,789 +(标志弹出) +(logo pops) + +3 +00:00:02,789 --> 00:00:05,700 +(标题嘶嘶作响) +(title whooshes) + +4 +00:00:05,700 --> 00:00:08,433 +- 如何在 Hugging Face 论坛上提问? +- How to ask a question on the Hugging Face forums? + +5 +00:00:10,020 --> 00:00:11,640 +如果你有一般性问题 +If you have a general question + +6 +00:00:11,640 --> 00:00:13,110 +或者正在调试你的代码, +or are looking to debug your code, + +7 +00:00:13,110 --> 00:00:15,540 +论坛是提问的地方。 +the forums are the place to ask. + +8 +00:00:15,540 --> 00:00:16,710 +在这个视频中,我们将教你 +In this video we will teach you + +9 +00:00:16,710 --> 00:00:18,030 +如何写出一个好问题, +how to write a good question, + +10 +00:00:18,030 --> 00:00:20,380 +以最大限度地提高你获得答案的机会。 +to maximize the chances you will get an answer. + +11 +00:00:21,570 --> 00:00:23,970 +首先,登录论坛, +First things first, to login on the forums, + +12 +00:00:23,970 --> 00:00:25,920 +你需要一个 Hugging Face 帐户。 +you need a Hugging Face account. + +13 +00:00:25,920 --> 00:00:27,750 +如果你还没有创建一个, +If you haven't created one already, + +14 +00:00:27,750 --> 00:00:31,080 +转到 hf.co 并单击注册。 +go to hf.co and click sign up. + +15 +00:00:31,080 --> 00:00:32,780 +下面还有一个直接链接。 +There is also a direct link below. + +16 +00:00:33,750 --> 00:00:35,160 +填写你的邮箱和密码, +Fill your email and password, + +17 +00:00:35,160 --> 00:00:37,410 +然后继续选择你的用户名的步骤 +then continue the steps to pick your username + +18 +00:00:37,410 --> 00:00:38,860 +并更新头像。 +and update a profile picture. + +19 +00:00:39,720 --> 00:00:43,200 +完成后,去 discuss.huggingface.co, +Once this is done, go to discuss.huggingface.co, + +20 +00:00:43,200 --> 00:00:45,630 +下方链接,点击登录。 +link below, and click log in. + +21 +00:00:45,630 --> 00:00:47,033 +使用与以下相同的登录信息 +Use the same login information as + +22 +00:00:47,033 --> 00:00:48,693 +Hugging Face 网站。 +for the Hugging Face website. + +23 +00:00:49,890 --> 00:00:51,300 +你可以通过单击搜索论坛 +You can search the forums by clicking + +24 +00:00:51,300 --> 00:00:52,800 +在放大镜上。 +on the magnifying glass. + +25 +00:00:52,800 --> 00:00:55,710 +可能有人已经在某个主题中问过你的问题。 +Someone may have already asked your question in a topic. + +26 +00:00:55,710 --> 00:00:58,260 +如果你发现你无法作为新用户发布新主题, +If you find you can't post a new topic as a new user, + +27 +00:00:58,260 --> 00:01:01,290 +这可能是因为反垃圾邮件过滤器。 +it may be because of the antispam filters. + +28 +00:01:01,290 --> 00:01:03,750 +确保你花一些时间阅读现有主题 +Make sure you spend some time reading existing topics + +29 +00:01:03,750 --> 00:01:05,370 +停用它。 +to deactivate it. + +30 +00:01:05,370 --> 00:01:07,590 +当你确定你的问题还没有被问到时, +When you're sure your question hasn't been asked yet, + +31 +00:01:07,590 --> 00:01:09,660 +单击新主题按钮。 +click on the new topic button. + +32 +00:01:09,660 --> 00:01:12,600 +对于此示例,我们将使用以下代码, +For this example, we'll use the following code, + +33 +00:01:12,600 --> 00:01:13,860 +产生错误, +that produces an error, + +34 +00:01:13,860 --> 00:01:16,660 +正如我们在 “遇到错误时该怎么办” 视频中看到的那样。 +as we saw in the "What to do when I get an error" video. + +35 +00:01:18,330 --> 00:01:21,330 +第一步是为我们的新主题选择一个类别。 +The first step is to pick a category for our new topic. + +36 +00:01:21,330 --> 00:01:23,790 +由于我们的错误与 Transformers 库有关, +Since our error has to do with the Transformers library, + +37 +00:01:23,790 --> 00:01:24,903 +我们选择这个类别。 +we pick this category. + +38 +00:01:26,070 --> 00:01:29,880 +接下来,选择一个能够很好地总结你的错误的标题。 +Next, choose a title that summarizes your error well. + +39 +00:01:29,880 --> 00:01:32,300 +不要太含糊,否则用户会遇到与你相同的错误 +Don't be too vague or users that get the same error you did + +40 +00:01:32,300 --> 00:01:34,773 +以后将无法找到你的主题。 +in the future won't be able to find your topic. + +41 +00:01:36,150 --> 00:01:38,370 +输入完主题标题后, +Once you have finished typing your topic title, + +42 +00:01:38,370 --> 00:01:40,170 +确保问题没有得到回答 +make sure the question hasn't been answered + +43 +00:01:40,170 --> 00:01:42,690 +在 Discourse 主题中建议你。 +in the topics Discourse suggests you. + +44 +00:01:42,690 --> 00:01:44,190 +单击十字删除该窗口 +Click on the cross to remove that window + +45 +00:01:44,190 --> 00:01:46,230 +当你仔细检查时。 +when you have double-checked. + +46 +00:01:46,230 --> 00:01:49,710 +这是发布错误时不应执行的操作的示例。 +This is an example of what not to do when posting an error. + +47 +00:01:49,710 --> 00:01:51,120 +消息非常模糊, +The message is very vague, + +48 +00:01:51,120 --> 00:01:53,370 +所以没有人能猜出哪里出了问题 +so no one else will be able to guess what went wrong + +49 +00:01:53,370 --> 00:01:55,623 +对你来说,它标记了太多人。 +for you, and it tags too many people. + +50 +00:01:56,490 --> 00:01:58,740 +标记人,尤其是版主, +Tagging people, especially moderators, + +51 +00:01:58,740 --> 00:02:01,470 +可能会产生与你想要的相反的效果。 +might have the opposite effect of what you want. + +52 +00:02:01,470 --> 00:02:04,380 +当你向他们发送通知时,他们会收到很多通知, +As you send them a notification, and they get plenty, + +53 +00:02:04,380 --> 00:02:06,300 +他们可能不会费心回复你, +they will probably not bother replying to you, + +54 +00:02:06,300 --> 00:02:09,300 +而你没有标记的用户可能会忽略这些问题, +and users you didn't tag will probably ignore the questions, + +55 +00:02:09,300 --> 00:02:11,430 +因为他们看到被标记的用户。 +since they see tagged users. + +56 +00:02:11,430 --> 00:02:13,697 +仅在你完全确定时才标记用户 +Only tag a user when you are completely certain + +57 +00:02:13,697 --> 00:02:16,097 +他们是回答你问题的最佳场所。 +they are the best place to answer your question. + +58 +00:02:17,730 --> 00:02:20,370 +文本要准确,如果出现错误 +Be precise in your text, and if you have an error coming + +59 +00:02:20,370 --> 00:02:22,710 +从一段特定的代码,包括该代码 +from a specific piece of code, include that code + +60 +00:02:22,710 --> 00:02:24,030 +在你的帖子中。 +in your post. + +61 +00:02:24,030 --> 00:02:27,210 +为了确保你的帖子看起来不错,请提出你的问题 +To make sure your post looks good, place your question + +62 +00:02:27,210 --> 00:02:30,060 +在像这样的三个反引号之间。 +between three backticks like this. + +63 +00:02:30,060 --> 00:02:30,990 +你可以在右边查看 +You can check on the right + +64 +00:02:30,990 --> 00:02:32,943 +发布后你的帖子将如何显示。 +how your post will appear once posted. + +65 +00:02:34,320 --> 00:02:35,850 +如果你的问题是关于错误的, +If your question is about an error, + +66 +00:02:35,850 --> 00:02:38,640 +最好包括完整的回溯。 +it's even better to include the full traceback. + +67 +00:02:38,640 --> 00:02:41,610 +正如 “遇到错误时该怎么办” 视频中所述, +As explained in the "What to do when I get an error" video, + +68 +00:02:41,610 --> 00:02:43,763 +如果你使用的是 Colab,请展开回溯。 +expand the traceback if you're on Colab. + +69 +00:02:44,769 --> 00:02:45,990 +就像代码一样,把它 +Like for the code, put it + +70 +00:02:45,990 --> 00:02:48,300 +在包含三个反引号的两行之间 +between two lines containing three backticks + +71 +00:02:48,300 --> 00:02:50,160 +正确格式化。 +for proper formatting. + +72 +00:02:50,160 --> 00:02:52,740 +我们最后的建议是记住要友善。 +Our last advice is to remember to be nice. + +73 +00:02:52,740 --> 00:02:54,540 +一句 “请” 和一句 “谢谢” 将大有帮助 +A "Please," and a "Thank you" will go a long way + +74 +00:02:54,540 --> 00:02:56,490 +让别人帮助你。 +into getting others to help you. + +75 +00:02:56,490 --> 00:02:57,780 +一切都做好后, +With all that done properly, + +76 +00:02:57,780 --> 00:03:00,143 +你的问题应该很快就会得到答案。 +your question should get an answer pretty quickly. + +77 +00:03:01,293 --> 00:03:04,344 +(标题嘶嘶作响) +(title whooshes) + +78 +00:03:04,344 --> 00:03:06,034 +(标题失败) +(title fizzles) + diff --git a/subtitles/zh-CN/73_debugging-the-training-pipeline-(pytorch).srt b/subtitles/zh-CN/73_debugging-the-training-pipeline-(pytorch).srt new file mode 100644 index 000000000..d12bee78e --- /dev/null +++ b/subtitles/zh-CN/73_debugging-the-training-pipeline-(pytorch).srt @@ -0,0 +1,505 @@ +1 +00:00:06,210 --> 00:00:08,760 +- 在本视频中,我们将了解如何调试错误 +- In this video, we will see how to debug an error + +2 +00:00:08,760 --> 00:00:11,896 +你在运行 Trainer.train 时遇到 +you encounter when running Trainer.train + +3 +00:00:11,896 --> 00:00:15,066 +例如,我们将使用这个微调脚本 +As an example, we will use this script that finetunes + +4 +00:00:15,066 --> 00:00:17,760 +GLUE MNLI 数据集上的 bert 模型。 +a bert model on the GLUE MNLI dataset. + +5 +00:00:17,760 --> 00:00:19,470 +查看下面链接的视频 +Checkout the videos linked below + +6 +00:00:19,470 --> 00:00:21,840 +看看我们是如何得出这样一个脚本的。 +to see how we came to such a script. + +7 +00:00:21,840 --> 00:00:24,540 +这里我们要学习如何调试其中的问题。 +Here we want to learn how to debug the problems in it. + +8 +00:00:25,470 --> 00:00:28,110 +运行脚本很快就会给我们一个错误。 +Running the script gives us an error pretty quickly. + +9 +00:00:28,110 --> 00:00:29,040 +它发生在线上 +It happens at the line + +10 +00:00:29,040 --> 00:00:30,990 +我们将输入提供给模型, +where we feed the inputs to the model, + +11 +00:00:30,990 --> 00:00:32,850 +根据回溯。 +according to the traceback. + +12 +00:00:32,850 --> 00:00:34,702 +这告诉我们那里有问题, +That tells us there is a problem there, + +13 +00:00:34,702 --> 00:00:37,881 +但问题可能来自许多不同的原因。 +but the problem could come from many different causes. + +14 +00:00:37,881 --> 00:00:39,330 +要调试训练中的错误, +To debug an error in a training, + +15 +00:00:39,330 --> 00:00:41,760 +你需要确保训练管道的每一步 +you need to make sure each step of the training pipeline + +16 +00:00:41,760 --> 00:00:43,440 +按预期工作。 +works as intended. + +17 +00:00:43,440 --> 00:00:45,780 +这意味着检查数据集的输入 +This means checking that the inputs of your dataset + +18 +00:00:45,780 --> 00:00:47,040 +是正确的, +are correct, + +19 +00:00:47,040 --> 00:00:48,720 +你可以把它们批在一起, +you can batch them together, + +20 +00:00:48,720 --> 00:00:50,790 +通过模型喂养他们以获得损失, +feed them through the model to get a loss, + +21 +00:00:50,790 --> 00:00:52,500 +然后计算该损失的梯度 +then compute the gradients of that loss + +22 +00:00:52,500 --> 00:00:54,303 +在执行优化器步骤之前。 +before performing an optimizer step. + +23 +00:00:55,470 --> 00:00:57,810 +因此,让我们从查看训练数据集开始 +So let's start by looking at the training dataset + +24 +00:00:57,810 --> 00:00:59,043 +这个培训师正在使用。 +this Trainer is using. + +25 +00:00:59,910 --> 00:01:02,190 +这里肯定有问题。 +There is definitely a problem here. + +26 +00:01:02,190 --> 00:01:04,293 +我们看到的是文字而不是数字。 +We see texts and not number. + +27 +00:01:05,130 --> 00:01:06,660 +错误消息告诉我们模型 +The error message was telling us the model + +28 +00:01:06,660 --> 00:01:08,220 +没有得到输入 ID +did not get input IDs + +29 +00:01:08,220 --> 00:01:11,100 +我们确实没有数据集中的那些。 +and we do not have those in the dataset indeed. + +30 +00:01:11,100 --> 00:01:12,660 +回顾我们的代码, +Looking back at our code, + +31 +00:01:12,660 --> 00:01:14,400 +我们可以看到我们犯了一个错误 +we can see we made a mistake + +32 +00:01:14,400 --> 00:01:17,400 +并将错误的数据集传递给培训师。 +and passed the wrong datasets to the Trainer. + +33 +00:01:17,400 --> 00:01:19,173 +所以让我们修复它并再次运行。 +So let's fix that and run again. + +34 +00:01:20,490 --> 00:01:21,840 +现在我们有一个新的错误。 +Now we have a new error. + +35 +00:01:21,840 --> 00:01:23,130 +检查回溯 +Inspecting the traceback + +36 +00:01:23,130 --> 00:01:25,860 +告诉我们当我们尝试创建批处理时会发生这种情况, +tells us it happens when we try to create a batch, + +37 +00:01:25,860 --> 00:01:28,743 +专门用于对张量中的特征进行分组。 +specifically to group the features in a tensor. + +38 +00:01:29,700 --> 00:01:32,610 +我们可以通过要求培训师给我们一批来确认这一点 +We can confirm this by asking the Trainer to get us a batch + +39 +00:01:32,610 --> 00:01:34,230 +训练数据加载器, +of the training data loader, + +40 +00:01:34,230 --> 00:01:35,913 +重现相同的错误。 +which reproduces the same error. + +41 +00:01:36,780 --> 00:01:39,064 +通过检查输入或调试, +Either by inspecting the inputs or debugging, + +42 +00:01:39,064 --> 00:01:42,870 +然后我们可以看到它们的大小并不相同。 +we can then see they are not all of the same size. + +43 +00:01:42,870 --> 00:01:45,120 +这是因为我们还没有通过数据整理器 +This is because we have not passed a data collator + +44 +00:01:45,120 --> 00:01:46,890 +对培训师进行填充 +to do the padding to the Trainer + +45 +00:01:46,890 --> 00:01:49,443 +并且在预处理数据时也没有填充。 +and didn't pad when preprocessing the data either. + +46 +00:01:50,430 --> 00:01:52,710 +Trainer 内部的填充通常是默认设置, +Padding inside the Trainer is normally the default, + +47 +00:01:52,710 --> 00:01:55,380 +但前提是你将分词器提供给培训师, +but only if you provide your tokenizer to the Trainer, + +48 +00:01:55,380 --> 00:01:57,270 +我们忘了这样做。 +and we forgot to do that. + +49 +00:01:57,270 --> 00:01:59,120 +因此,让我们解决问题并再次运行。 +So let's fix the issue and run again. + +50 +00:02:00,510 --> 00:02:02,883 +这次我们得到了一个讨厌的 CUDA 错误。 +This time we get a nasty CUDA error. + +51 +00:02:03,765 --> 00:02:06,285 +它们很难调试,因为一方面, +They are very difficult to debug because for one, + +52 +00:02:06,285 --> 00:02:10,530 +他们将你的内核置于不可恢复的状态 +they put your kernel in a state that is not recoverable + +53 +00:02:10,530 --> 00:02:13,260 +所以你必须从头开始重启你的笔记本 +so you have to restart your notebook from the beginning + +54 +00:02:13,260 --> 00:02:16,950 +第二,追溯对那些人来说完全没用。 +and two, the traceback is completely useless for those. + +55 +00:02:16,950 --> 00:02:19,230 +这里的回溯告诉我们错误发生了 +Here the traceback tells us the error happens + +56 +00:02:19,230 --> 00:02:22,500 +当我们使用 loss.backward 进行梯度计算时, +when we do the gradient computation with loss.backward, + +57 +00:02:22,500 --> 00:02:25,113 +但正如我们稍后将看到的那样,情况并非如此。 +but as we will see later on that is not the case. + +58 +00:02:26,520 --> 00:02:28,920 +这是因为在 GPU 上发生的一切 +This is because everything that happens on the GPU + +59 +00:02:28,920 --> 00:02:30,720 +是异步完成的。 +is done asynchronously. + +60 +00:02:30,720 --> 00:02:32,880 +当你执行模型调用时, +When you execute the model call, + +61 +00:02:32,880 --> 00:02:34,457 +该程序所做的只是堆叠它 +what the program does is just stacking that + +62 +00:02:34,457 --> 00:02:36,600 +在 GPU 的队列中, +in the queue of GPU, + +63 +00:02:36,600 --> 00:02:39,856 +那么如果 GPU 当前没有任何工作要做, +then if the GPU didn't have any current job to do, + +64 +00:02:39,856 --> 00:02:41,850 +工作将同时在 GPU 上开始 +the work will start on the GPU at the same time + +65 +00:02:41,850 --> 00:02:45,000 +当 CPU 移动到下一条指令时。 +as the CPU moves to the next instruction. + +66 +00:02:45,000 --> 00:02:47,040 +继续提取损失, +Continuing with the extraction of the loss, + +67 +00:02:47,040 --> 00:02:49,170 +这被堆叠到 GPU 队列中 +this is stacked into the GPU queue + +68 +00:02:49,170 --> 00:02:51,953 +同时 CPU 移动到指令 loss.backward。 +while the CPU moves to the instruction loss.backward. + +69 +00:02:51,953 --> 00:02:54,180 +但是 GPU 还没有完成 +But the GPU still hasn't finished + +70 +00:02:54,180 --> 00:02:55,710 +模型的前向传递 +the forward pass of the model + +71 +00:02:55,710 --> 00:02:57,603 +因为这一切根本不需要时间。 +since all that took no time at all. + +72 +00:02:58,440 --> 00:03:00,210 +CPU 停止前进, +The CPU stops moving forward, + +73 +00:03:00,210 --> 00:03:03,240 +因为 loss.backward 作为指令告诉它等待 +because loss.backward as an instruction telling it to wait + +74 +00:03:03,240 --> 00:03:04,830 +为了完成 GPU, +for the GPUs to be finished, + +75 +00:03:04,830 --> 00:03:06,780 +以确保梯度是正确的。 +to make sure the gradients are correct. + +76 +00:03:07,650 --> 00:03:09,570 +当 GPU 遇到错误时, +When the GPU encounters an error, + +77 +00:03:09,570 --> 00:03:13,140 +它通过一条神秘的消息将它返回给 CPU +it gives it back to the CPU with a cryptic message + +78 +00:03:13,140 --> 00:03:15,423 +谁在错误的地方提出错误。 +who raises the error at the wrong place. + +79 +00:03:16,350 --> 00:03:18,720 +所以要调试这个,我们需要执行接下来的步骤 +So to debug this, we will need to execute the next steps + +80 +00:03:18,720 --> 00:03:21,211 +CPU 上的训练流水线。 +of the training pipeline on the CPU. + +81 +00:03:21,211 --> 00:03:22,380 +这很容易做到, +It is very easy to do, + +82 +00:03:22,380 --> 00:03:25,350 +这次我们得到了可以信任的回溯。 +and we get a traceback we can trust this time. + +83 +00:03:25,350 --> 00:03:26,520 +正如我们之前所说, +As we said before, + +84 +00:03:26,520 --> 00:03:28,620 +错误实际上发生在前向传播过程中 +the error actually happens during the forward pass + +85 +00:03:28,620 --> 00:03:29,453 +模型的, +of the model, + +86 +00:03:29,453 --> 00:03:30,993 +而不是 loss.backward。 +and not loss.backward. + +87 +00:03:31,920 --> 00:03:33,680 +这是一个索引错误。 +It's an index error. + +88 +00:03:33,680 --> 00:03:34,950 +经过一些调试, +With a bit of debugging, + +89 +00:03:34,950 --> 00:03:37,410 +我们看到我们有从 0 到 2 的标签, +we see we have labels ranging from 0 to 2, + +90 +00:03:37,410 --> 00:03:39,000 +所以三个不同的值, +so three different values, + +91 +00:03:39,000 --> 00:03:42,191 +但我们的输出具有每 2 个批量大小的形状。 +but our outputs have a shape of batch size per 2. + +92 +00:03:42,191 --> 00:03:45,600 +看起来我们的模型有错误的标签数量。 +It looks like our model has the wrong number of labels. + +93 +00:03:45,600 --> 00:03:47,190 +我们确实可以确认, +We can indeed confirm that, + +94 +00:03:47,190 --> 00:03:49,860 +现在我们知道在代码中修复它很容易 +and now that we know it's easy to fix it in the code + +95 +00:03:49,860 --> 00:03:53,969 +通过在创建模型时添加 num_labels=3。 +by adding num_labels=3 when we create the model. + +96 +00:03:53,969 --> 00:03:56,883 +现在训练脚本将运行完成。 +Now the training script will run to completion. + +97 +00:03:58,440 --> 00:03:59,430 +我们还不需要它, +We did not need it yet, + +98 +00:03:59,430 --> 00:04:00,960 +但这是我们调试下一步的方式 +but here is how we would debug the next step + +99 +00:04:00,960 --> 00:04:02,944 +管道,梯度计算, +of the pipeline, gradient computation, + +100 +00:04:02,944 --> 00:04:05,850 +以及优化器步骤。 +as well as the optimizer step. + +101 +00:04:05,850 --> 00:04:08,823 +有了所有这些,祝你在调试自己的训练时好运! +With all of this, good luck debugging your own trainings! + diff --git a/subtitles/zh-CN/74_debugging-the-training-pipeline-(tensorflow).srt b/subtitles/zh-CN/74_debugging-the-training-pipeline-(tensorflow).srt new file mode 100644 index 000000000..0bf09b45a --- /dev/null +++ b/subtitles/zh-CN/74_debugging-the-training-pipeline-(tensorflow).srt @@ -0,0 +1,900 @@ +1 +00:00:00,212 --> 00:00:02,879 +(空气呼啸) +(air whooshing) + +2 +00:00:04,680 --> 00:00:08,130 +- 你代码中的一些错误非常简单。 +- Some bugs in your code are very straightforward. + +3 +00:00:08,130 --> 00:00:11,580 +你尝试运行它,某处出现语法错误, +You try running it, you get a syntax error somewhere, + +4 +00:00:11,580 --> 00:00:14,490 +Python 准确地告诉你在哪里,然后你修复它。 +Python tells you exactly where, and you fix it. + +5 +00:00:14,490 --> 00:00:17,760 +这很棒,简单而且令人满意。 +This is great, it's simple and it's satisfying. + +6 +00:00:17,760 --> 00:00:20,310 +但有时,事情会崩溃 +Sometimes, though, things crash + +7 +00:00:20,310 --> 00:00:23,670 +错误是无法理解的。 +and the error is impossible to understand. + +8 +00:00:23,670 --> 00:00:26,700 +由于一些原因,这种情况在机器学习中经常发生, +This happens a lot in machine learning for a few reasons, + +9 +00:00:26,700 --> 00:00:29,310 +你正在处理大数据结构, +you're working with big data structures, + +10 +00:00:29,310 --> 00:00:31,440 +你正在使用这些大而复杂的库 +you're using these big, complex libraries + +11 +00:00:31,440 --> 00:00:33,420 +有很多活动部件, +with a lot of moving parts, + +12 +00:00:33,420 --> 00:00:35,310 +而且你正在做大量的 GPU 计算, +and also you're doing a lot of GPU computing, + +13 +00:00:35,310 --> 00:00:38,490 +而且一般来说调试起来要困难得多。 +and that in general is much more difficult to debug. + +14 +00:00:38,490 --> 00:00:40,260 +在 Keras 中还有一个问题 +In Keras there's the additional problem + +15 +00:00:40,260 --> 00:00:43,140 +你的模型通常在执行前编译, +that your models are often compiled before execution, + +16 +00:00:43,140 --> 00:00:44,400 +这对性能很有帮助 +which is great for performance + +17 +00:00:44,400 --> 00:00:47,430 +但这也使调试它们变得非常困难。 +but it makes debugging them very difficult as well. + +18 +00:00:47,430 --> 00:00:50,370 +所以,这将是一个关于该怎么做的视频 +So, this is going to be a video about what to do + +19 +00:00:50,370 --> 00:00:52,410 +当你遇到那些噩梦中的一个错误时 +when you run into one of those nightmare bugs + +20 +00:00:52,410 --> 00:00:55,210 +而且你只是不知道从哪里开始修复它。 +and you just have no idea where to begin with fixing it. + +21 +00:00:56,370 --> 00:00:58,920 +所以,给你一些直觉 +So, to give you some intuitions for + +22 +00:00:58,920 --> 00:01:01,530 +最常见的错误 +the most common things that go wrong + +23 +00:01:01,530 --> 00:01:03,573 +并导致这些奇怪的问题, +and cause these weird issues, + +24 +00:01:04,800 --> 00:01:07,530 +并告诉你在哪里寻找错误的来源 +and show you where to look for the sources of bugs + +25 +00:01:07,530 --> 00:01:10,560 +你遇到的,让我们使用这个示例脚本。 +that you encounter, let's use this example script. + +26 +00:01:10,560 --> 00:01:12,900 +因此,我将在这里分两部分向你展示。 +So, I'll show it to you here in two parts. + +27 +00:01:12,900 --> 00:01:16,410 +首先,我们做所有的导入,我们加载一个数据集, +First, we do all our imports, we load a dataset, + +28 +00:01:16,410 --> 00:01:20,280 +我们创建了分词器并对数据集进行分词。 +we create our tokenizer and we tokenize the dataset. + +29 +00:01:20,280 --> 00:01:23,640 +接下来,我们将数据集转换为 TensorFlow 数据集, +Next, we convert our datasets to TensorFlow datasets, + +30 +00:01:23,640 --> 00:01:26,100 +这就是 tf.data.Dataset, +so that's tf.data.Dataset, + +31 +00:01:26,100 --> 00:01:28,500 +这样我们就可以适应它们, +and that's so that we can run fit on them, + +32 +00:01:28,500 --> 00:01:31,170 +然后我们从预训练的检查点加载我们的模型, +and then we load our model from a pretrained checkpoint, + +33 +00:01:31,170 --> 00:01:33,870 +我们对其进行编译,并将其与这些数据集相匹配。 +we compile it and we fit it with those datasets. + +34 +00:01:33,870 --> 00:01:35,970 +所以,这看起来很简单, +So, this seems straightforward enough, + +35 +00:01:35,970 --> 00:01:38,220 +这与我们之前在课程中所做的类似。 +it's similar to what we've done in the course before. + +36 +00:01:38,220 --> 00:01:40,650 +但请注意,这是令人毛骨悚然的代码 +But beware, this is spooky code + +37 +00:01:40,650 --> 00:01:43,590 +并隐藏着许多黑暗而神秘的秘密。 +and hides many dark and mysterious secrets. + +38 +00:01:43,590 --> 00:01:46,050 +那么,当我们运行它时会发生什么? +So, what happens when we run it? + +39 +00:01:46,050 --> 00:01:48,840 +好吧,这不是很好。 +Well, it's not great. + +40 +00:01:48,840 --> 00:01:52,320 +所以,我们收到了这个错误信息,但它是什么意思呢? +So, we get this error message, but what does it mean? + +41 +00:01:52,320 --> 00:01:55,470 +我们试图训练我们的数据,但我们没有梯度? +We tried to train on our data, but we got no gradient? + +42 +00:01:55,470 --> 00:01:59,130 +这很令人困惑,我的意思是,我们如何开始 +It's pretty perplexing, I mean, how do we even begin + +43 +00:01:59,130 --> 00:02:01,500 +调试没有得到梯度? +to debug not getting a gradient? + +44 +00:02:01,500 --> 00:02:03,930 +所以,当你得到的错误没有立即提示 +So, when the error you get doesn't immediately suggest + +45 +00:02:03,930 --> 00:02:06,630 +问题出在哪里,最好的解决办法 +where the problem is, the best solution + +46 +00:02:06,630 --> 00:02:09,180 +通常是按顺序遍历事物, +is often to walk through things in sequence, + +47 +00:02:09,180 --> 00:02:12,900 +确保在每个阶段输出看起来都是正确的, +making sure at each stage that the outputs look right, + +48 +00:02:12,900 --> 00:02:15,300 +那时一切看起来都很好。 +that everything looks okay at that point. + +49 +00:02:15,300 --> 00:02:17,730 +而且,当然,这意味着开始的地方 +And, of course, that means the place to start + +50 +00:02:17,730 --> 00:02:19,473 +总是检查你的数据。 +is always to check your data. + +51 +00:02:20,670 --> 00:02:22,050 +所以,最好的办法是确保 +So, the best way to make sure + +52 +00:02:22,050 --> 00:02:24,480 +你给模型的数据是好的, +that the data you're giving the model is good, + +53 +00:02:24,480 --> 00:02:27,690 +是从 tf.data.Dataset 中抓取一批 +is to grab a batch from the tf.data.Dataset + +54 +00:02:27,690 --> 00:02:29,520 +你的模型正在训练, +that your model is training on, + +55 +00:02:29,520 --> 00:02:31,560 +那是因为它就在最后 +and that's because it's right at the end + +56 +00:02:31,560 --> 00:02:33,990 +的数据管道。 +of the data pipeline. + +57 +00:02:33,990 --> 00:02:36,990 +所以这意味着如果这些输出是好的, +And so that means that if those outputs are good, + +58 +00:02:36,990 --> 00:02:39,990 +你可以保证你的数据管道运行良好。 +you're guaranteed that your data pipeline is working well. + +59 +00:02:39,990 --> 00:02:42,600 +所以,我们可以通过遍历数据集来做到这一点 +So, we can do that by looping over the dataset + +60 +00:02:42,600 --> 00:02:44,790 +进行一次迭代然后中断, +for one iteration and then breaking, + +61 +00:02:44,790 --> 00:02:46,980 +这给了我们一个批次。 +and that gives us a single batch. + +62 +00:02:46,980 --> 00:02:49,443 +那么,当我们检查该批次时,我们得到了什么? +So, what do we get when we inspect that batch? + +63 +00:02:50,460 --> 00:02:52,500 +我们会看到我们没有得到任何梯度 +We'll see that we're not getting any gradient + +64 +00:02:52,500 --> 00:02:55,530 +因为我们没有将标签传递给 Keras。 +because we're not passing labels to Keras. + +65 +00:02:55,530 --> 00:02:57,510 +所以,我们的标签在批次中, +So, our labels are in the batch, + +66 +00:02:57,510 --> 00:02:59,670 +但它们是输入字典中的键 +but they're a key in the input dictionary + +67 +00:02:59,670 --> 00:03:02,340 +而且它们不像 Keras 期望的那样是一个单独的标签, +and they're not a separate label as Keras expects, + +68 +00:03:02,340 --> 00:03:04,830 +所以这是你会遇到的最常见的问题之一 +so this is one of the most common issues you'll encounter + +69 +00:03:04,830 --> 00:03:07,590 +使用 TensorFlow 训练 Transformers 模型时。 +when training Transformers models with TensorFlow. + +70 +00:03:07,590 --> 00:03:10,980 +我们的模型都可以在内部计算损失, +Our models can all compute loss internally, + +71 +00:03:10,980 --> 00:03:13,140 +但要将损失用于训练 +but to use that loss for training + +72 +00:03:13,140 --> 00:03:15,960 +标签需要在输入字典中传递, +the labels need to be passed in the input dictionary, + +73 +00:03:15,960 --> 00:03:17,940 +模型可以看到它们的地方。 +where the model can see them. + +74 +00:03:17,940 --> 00:03:20,280 +这种内部损失是我们使用的损失 +This internal loss is the loss that we use + +75 +00:03:20,280 --> 00:03:23,760 +当我们在调用编译时没有指定损失时, +when we don't specify a loss when we call compile, + +76 +00:03:23,760 --> 00:03:25,660 +当我们不指定损失参数时。 +when we don't specify a loss argument. + +77 +00:03:26,520 --> 00:03:27,870 +所以,另一方面,Keras, +So, Keras, on the other hand, + +78 +00:03:27,870 --> 00:03:30,570 +通常期望标签单独传递 +usually expects labels to be passed separately + +79 +00:03:30,570 --> 00:03:32,130 +从输入字典, +from the input dictionary, + +80 +00:03:32,130 --> 00:03:34,110 +并且对模型不可见, +and not to be visible to the model, + +81 +00:03:34,110 --> 00:03:36,600 +损失计算通常会失败 +and loss computations will usually fail + +82 +00:03:36,600 --> 00:03:38,220 +如果你不那样做 +if you don't do that + +83 +00:03:38,220 --> 00:03:40,380 +所以我们需要选择其中之一, +So we need to choose one or the other, + +84 +00:03:40,380 --> 00:03:42,780 +要么我们使用模型的内部损失 +either we use the model's internal loss + +85 +00:03:42,780 --> 00:03:44,940 +并将标签保留在原处, +and keep the labels where they are, + +86 +00:03:44,940 --> 00:03:46,980 +或者我们继续使用 Keras 损失 +or we keep using Keras losses + +87 +00:03:46,980 --> 00:03:50,520 +但我们将标签移动到 Keras 期望的位置。 +but we move the labels to the place Keras expects them. + +88 +00:03:50,520 --> 00:03:53,310 +所以,还是简单点,让我们解决这个问题 +So, or simplicity here, let's fix this issue + +89 +00:03:53,310 --> 00:03:55,860 +通过使用模型的内部损失, +by using the model's internal losses, + +90 +00:03:55,860 --> 00:03:57,900 +我们通过删除损失参数来做到这一点 +and we do that by removing the loss argument + +91 +00:03:57,900 --> 00:03:59,343 +从调用编译。 +from the call to compile. + +92 +00:04:00,540 --> 00:04:03,000 +那么,如果我们现在尝试训练会发生什么? +So, what happens if we try training now? + +93 +00:04:03,000 --> 00:04:08,000 +所以我们用它重新编译,我们调用 model.fit,会发生什么? +So we recompile with that, we call model.fit, what happens? + +94 +00:04:08,220 --> 00:04:13,050 +好吧,这次它运行了,但现在我们失去了 NaN。 +Well, it runs this time but now we get a loss of NaN. + +95 +00:04:13,050 --> 00:04:16,440 +所以,这不好,NaN 表示不是数字 +So, that's not good, NaN means not a number + +96 +00:04:16,440 --> 00:04:19,140 +总的来说,这不是一个好损失。 +and it's not a good loss to have in general. + +97 +00:04:19,140 --> 00:04:21,000 +事实上,如果我们现在检查我们的模型, +In fact, if we inspect our model now, + +98 +00:04:21,000 --> 00:04:23,970 +我们会看到不仅所有的输出都是 NaN, +we'll see that not only are all the outputs NaN, + +99 +00:04:23,970 --> 00:04:27,600 +所有的权重和损失也是 NaN。 +all the weights are NaN as well, as well as the loss. + +100 +00:04:27,600 --> 00:04:30,810 +所以一旦一个 NaN 悄悄进入你的计算, +So once a single NaN creeps into your computations, + +101 +00:04:30,810 --> 00:04:34,530 +它倾向于传播,因为它从损失传播 +it tends to spread, because it propagates from the loss + +102 +00:04:34,530 --> 00:04:36,420 +一旦它处于损失状态,它就会处于梯度状态, +and once it's at the loss it's at the gradient, + +103 +00:04:36,420 --> 00:04:37,530 +它达到梯度, +it gets to the gradient, + +104 +00:04:37,530 --> 00:04:38,910 +然后一旦它处于渐变中 +and then once it's in the gradient + +105 +00:04:38,910 --> 00:04:41,280 +它进入重量更新, +it enters the weight updates, + +106 +00:04:41,280 --> 00:04:43,980 +然后你所有的体重更新最终也都是 NaN 。 +and then all your weight updates end up as NaN as well. + +107 +00:04:43,980 --> 00:04:46,950 +所以 NaN 在这里完全破坏了我们的模型, +So NaN just completely destroyed our model here, + +108 +00:04:46,950 --> 00:04:49,560 +但它首先潜入何处? +but where did it creep in first? + +109 +00:04:49,560 --> 00:04:52,140 +所以要找出答案,我们需要回到一个点 +So to find out, we need to back to a point + +110 +00:04:52,140 --> 00:04:53,490 +在模型被摧毁之前, +before the model was destroyed, + +111 +00:04:53,490 --> 00:04:55,440 +我们需要重新初始化模型 +we need to re-initialize the model + +112 +00:04:55,440 --> 00:04:58,590 +并查看第一批的输出。 +and look at the outputs for just the first batch. + +113 +00:04:58,590 --> 00:04:59,850 +当我们这样做时, +And when we do that, + +114 +00:04:59,850 --> 00:05:02,790 +我们看到 NaN 首先出现在损失中, +we see that NaN first appears in the loss, + +115 +00:05:02,790 --> 00:05:04,980 +但仅在某些样本中。 +but only in some samples. + +116 +00:05:04,980 --> 00:05:06,540 +所以你可以更详细地看到这个 +So you can see this in more detail + +117 +00:05:06,540 --> 00:05:09,090 +在课程笔记的随附部分中, +in the accompanying section of the course notes, + +118 +00:05:09,090 --> 00:05:11,220 +我在这里移动得相当快, +I am moving fairly quickly here, + +119 +00:05:11,220 --> 00:05:13,500 +但我们发现如果我们看一下标签, +but we find that if we look at the labels, + +120 +00:05:13,500 --> 00:05:17,790 +损失为 NaN 的样本的标签均为 2。 +the samples with a loss of NaN all have a label of two. + +121 +00:05:17,790 --> 00:05:19,950 +所以这给了我们一个非常有力的线索, +So this gives us a very strong clue, + +122 +00:05:19,950 --> 00:05:24,060 +如果我们使用 model.config.num_labels 检查模型, +if we check the model with model.config.num_labels, + +123 +00:05:24,060 --> 00:05:26,760 +我们看到模型认为只有两个标签, +we see that the model thinks there's only two labels, + +124 +00:05:26,760 --> 00:05:28,950 +但如果我们看到值为二, +but if we see a value of two, + +125 +00:05:28,950 --> 00:05:31,200 +这意味着至少有三个标签 +that means there's at least three labels + +126 +00:05:31,200 --> 00:05:33,630 +因为 0 也是一个标签。 +because 0 is a label as well. + +127 +00:05:33,630 --> 00:05:35,070 +所以我们失去了 NaN +So we got a loss of NaN + +128 +00:05:35,070 --> 00:05:37,887 +因为我们的标签集中有一个 “不可能” 的标签, +because we got an "impossible" label in our label set, + +129 +00:05:37,887 --> 00:05:41,010 +并修复我们需要返回并设置模型 +and to fix that we need to go back and set the model + +130 +00:05:41,010 --> 00:05:43,650 +期待正确数量的标签, +to expect the right number of labels, + +131 +00:05:43,650 --> 00:05:45,870 +所以我们可以设置 num_labels=3 +so we can set num_labels=3 + +132 +00:05:45,870 --> 00:05:48,540 +当我们初始化模型但来自_pretrained 时, +when we initialize the model but from_pretrained, + +133 +00:05:48,540 --> 00:05:51,450 +现在希望我们可以避免这个问题。 +and now hopefully we can avoid this issue. + +134 +00:05:51,450 --> 00:05:54,660 +所以,现在我们认为我们的数据很好,我们的模型也很好 +So, now we think our data is good and our model is good + +135 +00:05:54,660 --> 00:05:56,220 +所以培训应该有效 +and so training should work + +136 +00:05:56,220 --> 00:06:00,510 +但是如果我们尝试运行 model.fit,我们,嗯…… +but if we try running model.fit, we, well... + +137 +00:06:00,510 --> 00:06:02,040 +我的意思是,我们确实有损失, +I mean, we do get a loss, + +138 +00:06:02,040 --> 00:06:03,930 +这是一个数字,它正在下降 +it is a number and it is going down + +139 +00:06:03,930 --> 00:06:06,090 +但它不会很快下降 +but it's not going down very quickly + +140 +00:06:06,090 --> 00:06:07,770 +如果我们一直用完这个, +and if we keep running this out, + +141 +00:06:07,770 --> 00:06:10,980 +我们会发现它停在相当高的损失值处。 +we'll find that it stalls at a fairly high loss value. + +142 +00:06:10,980 --> 00:06:12,450 +发生什么了? +So, what's going on? + +143 +00:06:12,450 --> 00:06:14,130 +好吧,当一切正常时, +Well, when things are mostly working, + +144 +00:06:14,130 --> 00:06:16,620 +但训练只是很慢或有点奇怪, +but training is just slow or a bit odd, + +145 +00:06:16,620 --> 00:06:19,470 +这通常是查看优化器的好时机 +that can often be a good time to look at your optimizer + +146 +00:06:19,470 --> 00:06:22,020 +和你的训练超参数。 +and your training hyperparameters. + +147 +00:06:22,020 --> 00:06:23,460 +这就是我想提的地方 +And this is where I want to mention + +148 +00:06:23,460 --> 00:06:25,320 +最常见的问题来源之一 +one of the most common sources of issues + +149 +00:06:25,320 --> 00:06:27,000 +当你使用 Keras 时, +when you're working with Keras, + +150 +00:06:27,000 --> 00:06:30,870 +你可以用字符串命名优化器, +you can name things like optimizers with strings, + +151 +00:06:30,870 --> 00:06:33,180 +所以 Keras 支持它,而且非常方便, +so Keras supports that and it's very convenient, + +152 +00:06:33,180 --> 00:06:35,460 +但如果你这样做所有的选择 +but if you do that all of the options + +153 +00:06:35,460 --> 00:06:38,400 +默默地设置为默认值。 +get silently set to their default values. + +154 +00:06:38,400 --> 00:06:41,190 +所以我们将优化器指定为 Adam, +So we specified our optimizer as Adam, + +155 +00:06:41,190 --> 00:06:43,110 +但在这个过程中我们无形中得到了 +but in the process we invisibly got + +156 +00:06:43,110 --> 00:06:46,260 +默认学习率,即 1e-3, +the default learning rate, which is 1e-3, + +157 +00:06:46,260 --> 00:06:48,630 +或 10 的 -3 次方。 +or 10 to the power of -3. + +158 +00:06:48,630 --> 00:06:50,550 +所以这个学习率太高了 +So this learning rate is way too high + +159 +00:06:50,550 --> 00:06:52,530 +用于训练变压器模型, +for training transformer models, + +160 +00:06:52,530 --> 00:06:55,620 +我们应该回去直接指定学习率, +we should go back and specify the learning rate directly, + +161 +00:06:55,620 --> 00:06:57,060 +不使用字符串。 +not using a string. + +162 +00:06:57,060 --> 00:07:01,290 +所以,这里的好值在 1e-5 和 1e-4 之间 +So, good values here are between 1e-5 and 1e-4 + +163 +00:07:01,290 --> 00:07:04,233 +所以让我们平分差价并选择 5e-5。 +so let's split the difference and pick 5e-5. + +164 +00:07:05,310 --> 00:07:06,990 +所以如果你用那个重新编译, +So if you recompile with that, + +165 +00:07:06,990 --> 00:07:09,840 +你最终会发现培训确实有效。 +you'll find that training actually works, at last. + +166 +00:07:09,840 --> 00:07:11,700 +损失有效减少 +The loss goes down efficiently + +167 +00:07:11,700 --> 00:07:14,070 +并且收敛到一个较低的值。 +and it converges to a lower value. + +168 +00:07:14,070 --> 00:07:16,410 +所以,再一次,我确实很快地完成了这个 +So, again, I did go through this quite quickly + +169 +00:07:16,410 --> 00:07:18,720 +我强烈建议查看课程笔记 +and I strongly recommend checking out the course notes + +170 +00:07:18,720 --> 00:07:20,040 +要更详细地了解这一点, +to see this in more detail, + +171 +00:07:20,040 --> 00:07:21,600 +并自己试验代码 +and to experiment with the code yourself + +172 +00:07:21,600 --> 00:07:23,490 +看看错误是什么样的 +and see what the errors look like + +173 +00:07:23,490 --> 00:07:25,380 +以及如何接近他们, +and how you can approach them, + +174 +00:07:25,380 --> 00:07:27,930 +但我希望我在这里给了你一个简短的总结 +but I hope I've given you here a quick summary + +175 +00:07:27,930 --> 00:07:30,510 +最常见的错误 +of the most common bugs + +176 +00:07:30,510 --> 00:07:32,880 +也许是最常见的调试方法 +and maybe the most common debugging approaches + +177 +00:07:32,880 --> 00:07:33,960 +来对付他们。 +to dealing with them. + +178 +00:07:33,960 --> 00:07:37,020 +所以,祝你好运,记得多休息 +So, good luck, and remember to take plenty of breaks + +179 +00:07:37,020 --> 00:07:38,970 +如果你的代码给你带来困难。 +if your code is giving you a hard time. + +180 +00:07:39,805 --> 00:07:42,472 +(空气呼啸) +(air whooshing) + diff --git a/subtitles/zh-CN/75_writing-a-good-issue.srt b/subtitles/zh-CN/75_writing-a-good-issue.srt new file mode 100644 index 000000000..1afee7ceb --- /dev/null +++ b/subtitles/zh-CN/75_writing-a-good-issue.srt @@ -0,0 +1,365 @@ +1 +00:00:05,610 --> 00:00:08,557 +- 如何在 GitHub 上写出好的 issue? +- How to write a good issue on GitHub? + +2 +00:00:08,557 --> 00:00:10,080 +GitHub 是主要的地方 +GitHub is the main place + +3 +00:00:10,080 --> 00:00:12,000 +对于 Hugging Face 开源库, +for the Hugging Face open source libraries, + +4 +00:00:12,000 --> 00:00:14,010 +你应该经常去那里报告错误 +and you should always go there to report a bug + +5 +00:00:14,010 --> 00:00:16,020 +或要求新功能。 +or ask for a new feature. + +6 +00:00:16,020 --> 00:00:18,660 +对于更一般的问题或调试你自己的代码 +For more general questions or to debug your own code + +7 +00:00:18,660 --> 00:00:21,707 +使用论坛,请参阅下面链接的视频。 +use the forums, see the video linked below. + +8 +00:00:21,707 --> 00:00:23,677 +写好 issue 很重要 +It's very important to write good issues + +9 +00:00:23,677 --> 00:00:27,232 +因为它将帮助你立即修复发现的错误。 +as it will help the bug you uncovered be fixed in no time. + +10 +00:00:27,232 --> 00:00:29,750 +对于这个视频,我们创建了一个版本的变形金刚 +For this video, we have created a version of Transformers + +11 +00:00:29,750 --> 00:00:31,066 +有一个错误。 +with a bug. + +12 +00:00:31,066 --> 00:00:33,783 +你可以通过在笔记本中执行此命令来安装它, +You can install it by executing this command in a notebook, + +13 +00:00:33,783 --> 00:00:37,239 +删除感叹号以在终端中执行它。 +remove the exclamation mark to execute it in a terminal. + +14 +00:00:37,239 --> 00:00:41,016 +在此版本中,以下示例失败。 +In this version, the following example fails. + +15 +00:00:41,016 --> 00:00:42,472 +错误相当神秘 +The error is rather cryptic + +16 +00:00:42,472 --> 00:00:45,184 +并且似乎不是来自我们代码中的任何内容, +and does not seem to come from anything in our code, + +17 +00:00:45,184 --> 00:00:48,157 +所以我们似乎有一个错误要报告。 +so it seems we have a bug to report. + +18 +00:00:48,157 --> 00:00:49,858 +在这种情况下要做的第一件事 +The first thing to do in this case + +19 +00:00:49,858 --> 00:00:52,053 +是试图找到尽可能少的代码 +is to try to find the smallest amount of code possible + +20 +00:00:52,053 --> 00:00:54,059 +重现错误。 +that reproduces the bug. + +21 +00:00:54,059 --> 00:00:56,802 +在我们的例子中,检查回溯, +In our case, inspecting the traceback, + +22 +00:00:56,802 --> 00:00:59,645 +我们看到失败发生在管道函数内部 +we see the failure happens inside the pipeline function + +23 +00:00:59,645 --> 00:01:03,158 +当它调用 AutoTokenizer.from_pretrained 时。 +when it calls AutoTokenizer.from_pretrained. + +24 +00:01:03,158 --> 00:01:06,609 +使用调试器,我们找到传递给该方法的值 +Using the debugger, we find the values passed to that method + +25 +00:01:06,609 --> 00:01:08,849 +因此可以创建一个小的代码示例 +and can thus create a small sample of code + +26 +00:01:08,849 --> 00:01:12,802 +希望产生相同的错误。 +that hopefully generates the same error. + +27 +00:01:12,802 --> 00:01:14,726 +完成这一步非常重要 +It's very important to go though this step + +28 +00:01:14,726 --> 00:01:16,770 +你可能意识到错误在你这边 +as you may realize the error was on your side + +29 +00:01:16,770 --> 00:01:18,360 +而不是库中的错误, +and not a bug in the library, + +30 +00:01:18,360 --> 00:01:20,610 +但它也会让维护者更容易 +but it also will make it easier for the maintainers + +31 +00:01:20,610 --> 00:01:22,320 +解决你的问题。 +to fix your problem. + +32 +00:01:22,320 --> 00:01:24,030 +在这里我们可以更多地使用这段代码 +Here we can play around a bit more with this code + +33 +00:01:24,030 --> 00:01:26,460 +并注意错误发生在不同的检查点 +and notice the error happens for different checkpoints + +34 +00:01:26,460 --> 00:01:28,050 +不只是这个, +and not just this one, + +35 +00:01:28,050 --> 00:01:31,262 +当我们使用 use_fast=False 时它会消失 +and that it disappears when we use use_fast=False + +36 +00:01:31,262 --> 00:01:33,240 +在我们的分词器调用中。 +inside our tokenizer call. + +37 +00:01:33,240 --> 00:01:35,190 +重要的是要有东西 +The important part is to have something + +38 +00:01:35,190 --> 00:01:38,640 +不依赖于任何外部文件或数据。 +that does not depend on any external files or data. + +39 +00:01:38,640 --> 00:01:40,350 +尝试用假值替换你的数据 +Try to replace your data by fake values + +40 +00:01:40,350 --> 00:01:41,450 +如果你不能分享它。 +if you can't share it. + +41 +00:01:42,750 --> 00:01:43,620 +完成所有这些后, +With all of this done, + +42 +00:01:43,620 --> 00:01:46,260 +我们准备开始写我们的问题。 +we are ready to start writing our issue. + +43 +00:01:46,260 --> 00:01:48,600 +单击错误报告旁边的按钮 +Click on the button next to Bug Report + +44 +00:01:48,600 --> 00:01:51,300 +你会发现有一个模板可以填写。 +and you will discover that there is a template to fill. + +45 +00:01:51,300 --> 00:01:53,940 +只需几分钟。 +It will only take you a couple of minutes. + +46 +00:01:53,940 --> 00:01:56,460 +首先是正确命名你的问题。 +The first thing is to properly name your issue. + +47 +00:01:56,460 --> 00:01:59,100 +不要选择过于模糊的标题。 +Don't pick a title that is too vague. + +48 +00:01:59,100 --> 00:02:02,160 +然后你必须填写你的环境信息。 +Then you have to fill your environment information. + +49 +00:02:02,160 --> 00:02:03,330 +提供了一个命令 +There is a command provided + +50 +00:02:03,330 --> 00:02:05,700 +由 Transformers 库来执行此操作。 +by the Transformers library to do this. + +51 +00:02:05,700 --> 00:02:08,550 +只需在笔记本或终端中执行即可 +Just execute it in your notebook or in a terminal + +52 +00:02:08,550 --> 00:02:10,203 +并复制粘贴结果。 +and copy paste the results. + +53 +00:02:11,070 --> 00:02:13,530 +最后两个问题需要手动填写, +There are two last questions to fill manually, + +54 +00:02:13,530 --> 00:02:16,023 +在我们的案例中,答案是否定的。 +to which the answers are no and no in our case. + +55 +00:02:17,550 --> 00:02:20,460 +接下来,我们需要确定标记谁。 +Next, we need to determine who to tag. + +56 +00:02:20,460 --> 00:02:23,010 +模板中有完整的用户名列表。 +There is a full list of usernames in the template. + +57 +00:02:23,010 --> 00:02:25,043 +由于我们的问题与分词器有关, +Since our issue has to do with tokenizers, + +58 +00:02:25,043 --> 00:02:28,170 +我们选择与他们相关的维护者。 +we pick the maintainer associated with them. + +59 +00:02:28,170 --> 00:02:30,210 +标记超过 3 个人是没有意义的, +There is no point tagging more than 3 people, + +60 +00:02:30,210 --> 00:02:32,010 +他们会将你重定向到合适的人 +they will redirect you to the right person + +61 +00:02:32,010 --> 00:02:33,110 +如果你犯了错误。 +if you made a mistake. + +62 +00:02:34,410 --> 00:02:36,600 +接下来,我们必须提供必要的信息 +Next, we have to give the information necessary + +63 +00:02:36,600 --> 00:02:38,220 +重现错误。 +to reproduce the bug. + +64 +00:02:38,220 --> 00:02:41,010 +我们粘贴样本,并将其放在两行之间 +We paste our sample, and put it between two lines + +65 +00:02:41,010 --> 00:02:44,073 +带有三个反引号,以便正确格式化。 +with three backticks so that it's formatted properly. + +66 +00:02:45,210 --> 00:02:47,010 +我们还粘贴了完整的回溯, +We also paste the full traceback, + +67 +00:02:47,010 --> 00:02:49,740 +仍然在两行三个反引号之间。 +still between two lines of three backticks. + +68 +00:02:49,740 --> 00:02:52,650 +最后,我们可以添加任何其他信息 +Lastly, we can add any additional information + +69 +00:02:52,650 --> 00:02:55,200 +关于我们试图调试手头问题的内容。 +about what we tried to debug the issue at hand. + +70 +00:02:55,200 --> 00:02:57,030 +有了这一切,你应该期待一个答案 +With all of this, you should expect an answer + +71 +00:02:57,030 --> 00:02:59,880 +非常快地解决你的问题,希望能快速解决。 +to your issue pretty fast and hopefully a quick fix. + +72 +00:02:59,880 --> 00:03:01,770 +请注意,此视频中的所有建议 +Note that all the advise in this video + +73 +00:03:01,770 --> 00:03:04,203 +适用于几乎所有的开源项目。 +applies for almost every open-source project. + diff --git a/subtitles/zh-CN/metadata.csv b/subtitles/zh-CN/metadata.csv new file mode 100644 index 000000000..210d5405e --- /dev/null +++ b/subtitles/zh-CN/metadata.csv @@ -0,0 +1,77 @@ +id,title,link,srt_filename +00GKzGyWFEs,Welcome to the Hugging Face course,https://www.youtube.com/watch?v=00GKzGyWFEs&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=1,subtitles/zh-CN/00_welcome-to-the-hugging-face-course.srt +tiZFewofSLM,The pipeline function,https://www.youtube.com/watch?v=tiZFewofSLM&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=2,subtitles/zh-CN/01_the-pipeline-function.srt +ftWlj4FBHTg,The carbon footprint of Transformers,https://www.youtube.com/watch?v=ftWlj4FBHTg&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=3,subtitles/zh-CN/02_the-carbon-footprint-of-transformers.srt +BqqfQnyjmgg,What is Transfer Learning?,https://www.youtube.com/watch?v=BqqfQnyjmgg&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=4,subtitles/zh-CN/03_what-is-transfer-learning.srt +H39Z_720T5s,The Transformer architecture,https://www.youtube.com/watch?v=H39Z_720T5s&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=5,subtitles/zh-CN/04_the-transformer-architecture.srt +MUqNwgPjJvQ,Transformer models: Encoders,https://www.youtube.com/watch?v=MUqNwgPjJvQ&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=6,subtitles/zh-CN/05_transformer-models-encoders.srt +d_ixlCubqQw,Transformer models: Decoders,https://www.youtube.com/watch?v=d_ixlCubqQw&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=7,subtitles/zh-CN/06_transformer-models-decoders.srt +0_4KEb08xrE,Transformer models: Encoder-Decoders,https://www.youtube.com/watch?v=0_4KEb08xrE&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=8,subtitles/zh-CN/07_transformer-models-encoder-decoders.srt +1pedAIvTWXk,What happens inside the pipeline function? (PyTorch),https://www.youtube.com/watch?v=1pedAIvTWXk&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=9,subtitles/zh-CN/08_what-happens-inside-the-pipeline-function-(pytorch).srt +wVN12smEvqg,What happens inside the pipeline function? (TensorFlow),https://www.youtube.com/watch?v=wVN12smEvqg&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=10,subtitles/zh-CN/09_what-happens-inside-the-pipeline-function-(tensorflow).srt +AhChOFRegn4,Instantiate a Transformers model (PyTorch),https://www.youtube.com/watch?v=AhChOFRegn4&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=11,subtitles/zh-CN/10_instantiate-a-transformers-model-(pytorch).srt +d3JVgghSOew,Instantiate a Transformers model (TensorFlow),https://www.youtube.com/watch?v=d3JVgghSOew&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=12,subtitles/zh-CN/11_instantiate-a-transformers-model-(tensorflow).srt +VFp38yj8h3A,Tokenizers overview,https://www.youtube.com/watch?v=VFp38yj8h3A&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=13,subtitles/zh-CN/12_tokenizers-overview.srt +nhJxYji1aho,Word-based tokenizers,https://www.youtube.com/watch?v=nhJxYji1aho&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=14,subtitles/zh-CN/13_word-based-tokenizers.srt +ssLq_EK2jLE,Character-based tokenizers,https://www.youtube.com/watch?v=ssLq_EK2jLE&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=15,subtitles/zh-CN/14_character-based-tokenizers.srt +zHvTiHr506c,Subword-based tokenizers,https://www.youtube.com/watch?v=zHvTiHr506c&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=16,subtitles/zh-CN/15_subword-based-tokenizers.srt +Yffk5aydLzg,The tokenization pipeline,https://www.youtube.com/watch?v=Yffk5aydLzg&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=17,subtitles/zh-CN/16_the-tokenization-pipeline.srt +M6adb1j2jPI,Batching inputs together (PyTorch),https://www.youtube.com/watch?v=M6adb1j2jPI&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=18,subtitles/zh-CN/17_batching-inputs-together-(pytorch).srt +ROxrFOEbsQE,Batching inputs together (TensorFlow),https://www.youtube.com/watch?v=ROxrFOEbsQE&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=19,subtitles/zh-CN/18_batching-inputs-together-(tensorflow).srt +_BZearw7f0w,Hugging Face Datasets overview (Pytorch),https://www.youtube.com/watch?v=_BZearw7f0w&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=20,subtitles/zh-CN/19_hugging-face-datasets-overview-(pytorch).srt +W_gMJF0xomE,Hugging Face Datasets overview (Tensorflow),https://www.youtube.com/watch?v=W_gMJF0xomE&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=21,subtitles/zh-CN/20_hugging-face-datasets-overview-(tensorflow).srt +0u3ioSwev3s,Preprocessing sentence pairs (PyTorch),https://www.youtube.com/watch?v=0u3ioSwev3s&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=22,subtitles/zh-CN/21_preprocessing-sentence-pairs-(pytorch).srt +P-rZWqcB6CE,Preprocessing sentence pairs (TensorFlow),https://www.youtube.com/watch?v=P-rZWqcB6CE&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=23,subtitles/zh-CN/22_preprocessing-sentence-pairs-(tensorflow).srt +7q5NyFT8REg,What is dynamic padding?,https://www.youtube.com/watch?v=7q5NyFT8REg&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=24,subtitles/zh-CN/23_what-is-dynamic-padding.srt +nvBXf7s7vTI,The Trainer API,https://www.youtube.com/watch?v=nvBXf7s7vTI&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=25,subtitles/zh-CN/24_the-trainer-api.srt +rnTGBy2ax1c,Keras introduction,https://www.youtube.com/watch?v=rnTGBy2ax1c&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=26,subtitles/zh-CN/25_keras-introduction.srt +AUozVp78dhk,Fine-tuning with TensorFlow,https://www.youtube.com/watch?v=AUozVp78dhk&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=27,subtitles/zh-CN/26_fine-tuning-with-tensorflow.srt +cpzq6ESSM5c,Learning rate scheduling with TensorFlow,https://www.youtube.com/watch?v=cpzq6ESSM5c&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=28,subtitles/zh-CN/27_learning-rate-scheduling-with-tensorflow.srt +nx10eh4CoOs,TensorFlow Predictions and metrics,https://www.youtube.com/watch?v=nx10eh4CoOs&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=29,subtitles/zh-CN/28_tensorflow-predictions-and-metrics.srt +Dh9CL8fyG80,Write your training loop in PyTorch,https://www.youtube.com/watch?v=Dh9CL8fyG80&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=30,subtitles/zh-CN/29_write-your-training-loop-in-pytorch.srt +s7dy8QRgjJ0,Supercharge your PyTorch training loop with Accelerate,https://www.youtube.com/watch?v=s7dy8QRgjJ0&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=31,subtitles/zh-CN/30_supercharge-your-pytorch-training-loop-with-accelerate.srt +XvSGPZFEjDY,Navigating the Model Hub,https://www.youtube.com/watch?v=XvSGPZFEjDY&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=32,subtitles/zh-CN/31_navigating-the-model-hub.srt +9yY3RB_GSPM,Managing a repo on the Model Hub,https://www.youtube.com/watch?v=9yY3RB_GSPM&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=33,subtitles/zh-CN/32_managing-a-repo-on-the-model-hub.srt +Zh0FfmVrKX0,The Push to Hub API (PyTorch),https://www.youtube.com/watch?v=Zh0FfmVrKX0&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=34,subtitles/zh-CN/33_the-push-to-hub-api-(pytorch).srt +pUh5cGmNV8Y,The Push to Hub API (TensorFlow),https://www.youtube.com/watch?v=pUh5cGmNV8Y&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=35,subtitles/zh-CN/34_the-push-to-hub-api-(tensorflow).srt +HyQgpJTkRdE,Loading a custom dataset,https://www.youtube.com/watch?v=HyQgpJTkRdE&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=36,subtitles/zh-CN/35_loading-a-custom-dataset.srt +tqfSFcPMgOI,Slice and dice a dataset 🔪,https://www.youtube.com/watch?v=tqfSFcPMgOI&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=37,subtitles/zh-CN/36_slice-and-dice-a-dataset-🔪.srt +tfcY1067A5Q,Datasets + DataFrames = ❤️,https://www.youtube.com/watch?v=tfcY1067A5Q&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=38,subtitles/zh-CN/37_datasets-+-dataframes-=-❤️.srt +blF9uxYcKHo,Saving and reloading a dataset,https://www.youtube.com/watch?v=blF9uxYcKHo&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=39,subtitles/zh-CN/38_saving-and-reloading-a-dataset.srt +JwISwTCPPWo,Memory mapping & streaming,https://www.youtube.com/watch?v=JwISwTCPPWo&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=40,subtitles/zh-CN/39_memory-mapping-&-streaming.srt +HaN6qCr_Afc,Uploading a dataset to the Hub,https://www.youtube.com/watch?v=HaN6qCr_Afc&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=41,subtitles/zh-CN/40_uploading-a-dataset-to-the-hub.srt +OATCgQtNX2o,Text embeddings & semantic search,https://www.youtube.com/watch?v=OATCgQtNX2o&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=42,subtitles/zh-CN/41_text-embeddings-&-semantic-search.srt +DJimQynXZsQ,Training a new tokenizer,https://www.youtube.com/watch?v=DJimQynXZsQ&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=43,subtitles/zh-CN/42_training-a-new-tokenizer.srt +g8quOxoqhHQ,Why are fast tokenizers called fast?,https://www.youtube.com/watch?v=g8quOxoqhHQ&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=44,subtitles/zh-CN/43_why-are-fast-tokenizers-called-fast.srt +3umI3tm27Vw,Fast tokenizer superpowers,https://www.youtube.com/watch?v=3umI3tm27Vw&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=45,subtitles/zh-CN/44_fast-tokenizer-superpowers.srt +0E7ltQB7fM8,Inside the Token classification pipeline (PyTorch),https://www.youtube.com/watch?v=0E7ltQB7fM8&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=46,subtitles/zh-CN/45_inside-the-token-classification-pipeline-(pytorch).srt +PrX4CjrVnNc,Inside the Token classification pipeline (TensorFlow),https://www.youtube.com/watch?v=PrX4CjrVnNc&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=47,subtitles/zh-CN/46_inside-the-token-classification-pipeline-(tensorflow).srt +_wxyB3j3mk4,Inside the Question answering pipeline (PyTorch),https://www.youtube.com/watch?v=_wxyB3j3mk4&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=48,subtitles/zh-CN/47_inside-the-question-answering-pipeline-(pytorch).srt +b3u8RzBCX9Y,Inside the Question answering pipeline (TensorFlow),https://www.youtube.com/watch?v=b3u8RzBCX9Y&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=49,subtitles/zh-CN/48_inside-the-question-answering-pipeline-(tensorflow).srt +4IIC2jI9CaU,What is normalization?,https://www.youtube.com/watch?v=4IIC2jI9CaU&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=50,subtitles/zh-CN/49_what-is-normalization.srt +grlLV8AIXug,What is pre-tokenization?,https://www.youtube.com/watch?v=grlLV8AIXug&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=51,subtitles/zh-CN/50_what-is-pre-tokenization.srt +HEikzVL-lZU,Byte Pair Encoding Tokenization,https://www.youtube.com/watch?v=HEikzVL-lZU&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=52,subtitles/zh-CN/51_byte-pair-encoding-tokenization.srt +qpv6ms_t_1A,WordPiece Tokenization,https://www.youtube.com/watch?v=qpv6ms_t_1A&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=53,subtitles/zh-CN/52_wordpiece-tokenization.srt +TGZfZVuF9Yc,Unigram Tokenization,https://www.youtube.com/watch?v=TGZfZVuF9Yc&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=54,subtitles/zh-CN/53_unigram-tokenization.srt +MR8tZm5ViWU,Building a new tokenizer,https://www.youtube.com/watch?v=MR8tZm5ViWU&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=55,subtitles/zh-CN/54_building-a-new-tokenizer.srt +iY2AZYdZAr0,Data processing for Token Classification,https://www.youtube.com/watch?v=iY2AZYdZAr0&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=56,subtitles/zh-CN/55_data-processing-for-token-classification.srt +8PmhEIXhBvI,Data processing for Masked Language Modeling,https://www.youtube.com/watch?v=8PmhEIXhBvI&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=57,subtitles/zh-CN/56_data-processing-for-masked-language-modeling.srt +NURcDHhYe98,What is perplexity?,https://www.youtube.com/watch?v=NURcDHhYe98&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=58,subtitles/zh-CN/57_what-is-perplexity.srt +0Oxphw4Q9fo,What is domain adaptation?,https://www.youtube.com/watch?v=0Oxphw4Q9fo&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=59,subtitles/zh-CN/58_what-is-domain-adaptation.srt +XAR8jnZZuUs,Data processing for Translation,https://www.youtube.com/watch?v=XAR8jnZZuUs&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=60,subtitles/zh-CN/59_data-processing-for-translation.srt +M05L1DhFqcw,What is the BLEU metric?,https://www.youtube.com/watch?v=M05L1DhFqcw&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=61,subtitles/zh-CN/60_what-is-the-bleu-metric.srt +1m7BerpSq8A,Data processing for Summarization,https://www.youtube.com/watch?v=1m7BerpSq8A&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=62,subtitles/zh-CN/61_data-processing-for-summarization.srt +TMshhnrEXlg,What is the ROUGE metric?,https://www.youtube.com/watch?v=TMshhnrEXlg&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=63,subtitles/zh-CN/62_what-is-the-rouge-metric.srt +ma1TrR7gE7I,Data processing for Causal Language Modeling,https://www.youtube.com/watch?v=ma1TrR7gE7I&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=64,subtitles/zh-CN/63_data-processing-for-causal-language-modeling.srt +Hm8_PgVTFuc,Using a custom loss function,https://www.youtube.com/watch?v=Hm8_PgVTFuc&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=65,subtitles/zh-CN/64_using-a-custom-loss-function.srt +qgaM0weJHpA,Data processing for Question Answering,https://www.youtube.com/watch?v=qgaM0weJHpA&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=66,subtitles/zh-CN/65_data-processing-for-question-answering.srt +BNy08iIWVJM,The Post processing step in Question Answering (PyTorch),https://www.youtube.com/watch?v=BNy08iIWVJM&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=67,subtitles/zh-CN/66_the-post-processing-step-in-question-answering-(pytorch).srt +VN67ZpN33Ss,The Post processing step in Question Answering (TensorFlow),https://www.youtube.com/watch?v=VN67ZpN33Ss&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=68,subtitles/zh-CN/67_the-post-processing-step-in-question-answering-(tensorflow).srt +-RPeakdlHYo,Data Collators: A Tour,https://www.youtube.com/watch?v=-RPeakdlHYo&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=69,subtitles/zh-CN/68_data-collators-a-tour.srt +DQ-CpJn6Rc4,What to do when you get an error?,https://www.youtube.com/watch?v=DQ-CpJn6Rc4&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=70,subtitles/zh-CN/69_what-to-do-when-you-get-an-error.srt +rSPyvPw0p9k,Using a debugger in a notebook,https://www.youtube.com/watch?v=rSPyvPw0p9k&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=71,subtitles/zh-CN/70_using-a-debugger-in-a-notebook.srt +5PkZ4rbHL6c,Using a debugger in a terminal,https://www.youtube.com/watch?v=5PkZ4rbHL6c&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=72,subtitles/zh-CN/71_using-a-debugger-in-a-terminal.srt +S2EEG3JIt2A,Asking for help on the forums,https://www.youtube.com/watch?v=S2EEG3JIt2A&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=73,subtitles/zh-CN/72_asking-for-help-on-the-forums.srt +L-WSwUWde1U,Debugging the Training Pipeline (PyTorch),https://www.youtube.com/watch?v=L-WSwUWde1U&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=74,subtitles/zh-CN/73_debugging-the-training-pipeline-(pytorch).srt +N9kO52itd0Q,Debugging the Training Pipeline (TensorFlow),https://www.youtube.com/watch?v=N9kO52itd0Q&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=75,subtitles/zh-CN/74_debugging-the-training-pipeline-(tensorflow).srt +_PAli-V4wj0,Writing a good issue,https://www.youtube.com/watch?v=_PAli-V4wj0&list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o&index=76,subtitles/zh-CN/75_writing-a-good-issue.srt diff --git a/utils/generate_subtitles.py b/utils/generate_subtitles.py new file mode 100644 index 000000000..f5d1d4a05 --- /dev/null +++ b/utils/generate_subtitles.py @@ -0,0 +1,64 @@ +import pandas as pd +from tqdm.auto import tqdm +from youtube_transcript_api import YouTubeTranscriptApi +from youtube_transcript_api.formatters import SRTFormatter +from youtubesearchpython import Playlist +from pathlib import Path +import argparse +import sys + +def generate_subtitles(language: str, youtube_language_code: str=None): + metadata = [] + formatter = SRTFormatter() + path = Path(f"subtitles/{language}") + path.mkdir(parents=True, exist_ok=True) + playlist_videos = Playlist.getVideos( + "https://youtube.com/playlist?list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o" + ) + + for idx, video in enumerate(playlist_videos["videos"]): + video_id = video["id"] + title = video["title"] + title_formatted = title.lower().replace(" ", "-").replace(":", "").replace("?", "") + id_str = f"{idx}".zfill(2) + srt_filename = f"subtitles/{language}/{id_str}_{title_formatted}.srt" + + # Skip course events + if "Event Day" in title: + continue + + # Get transcript + transcript_list = YouTubeTranscriptApi.list_transcripts(video_id) + english_transcript = transcript_list.find_transcript(language_codes=["en", "en-US"]) + languages = pd.DataFrame(english_transcript.translation_languages)["language_code"].tolist() + # Map mismatched language codes + if language not in languages: + if youtube_language_code is None: + raise ValueError(f"Language code {language} not found in YouTube's list of supported language: {languages}. Please provide a value for `youtube_language_code` and try again.") + language_code = youtube_language_code + else: + language_code = language + try: + translated_transcript = english_transcript.translate(language_code) + translated_transcript = translated_transcript.fetch() + srt_formatted = formatter.format_transcript(translated_transcript) + with open(srt_filename, "w", encoding="utf-8") as f: + f.write(srt_formatted) + except: + print(f"Problem generating transcript for {title} with ID {video_id} at {video['link']}.") + with open(srt_filename, "w", encoding="utf-8") as f: + f.write("No transcript found for this video!") + + metadata.append({"id": video_id, "title": title, "link": video["link"], "srt_filename": srt_filename}) + break + + df = pd.DataFrame(metadata) + df.to_csv(f"subtitles/{language}/metadata.csv", index=False) + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("--language", type=str, help="Language to generate subtitles for") + parser.add_argument("--youtube_language_code", type=str, help="YouTube language code") + args = parser.parse_args() + generate_subtitles(args.language, args.youtube_language_code) + print(f"All done! Subtitles stored at subtitles/{args.language}") \ No newline at end of file From 802c97f1ffa87bb0eb8790ad1f804a5a55e7e565 Mon Sep 17 00:00:00 2001 From: lewtun Date: Wed, 7 Dec 2022 18:54:25 +0100 Subject: [PATCH 42/51] Bump release (#404) --- .github/workflows/quality.yml | 6 +- README.md | 2 + chapters/en/chapter1/1.mdx | 4 +- chapters/ja/_toctree.yml | 13 ++ chapters/ja/chapter2/1.mdx | 25 +++ chapters/ja/chapter2/2.mdx | 359 ++++++++++++++++++++++++++++++++++ chapters/ja/chapter2/3.mdx | 230 ++++++++++++++++++++++ chapters/ja/chapter2/4.mdx | 240 +++++++++++++++++++++++ chapters/ja/chapter2/5.mdx | 339 ++++++++++++++++++++++++++++++++ 9 files changed, 1213 insertions(+), 5 deletions(-) create mode 100644 chapters/ja/chapter2/1.mdx create mode 100644 chapters/ja/chapter2/2.mdx create mode 100644 chapters/ja/chapter2/3.mdx create mode 100644 chapters/ja/chapter2/4.mdx create mode 100644 chapters/ja/chapter2/5.mdx diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 6b7afd288..06b296e3e 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -11,11 +11,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Set up Python 3.6 + - name: Set up Python 3.8 uses: actions/setup-python@v2 with: - python-version: 3.6 + python-version: 3.8 - name: Install Python dependencies run: pip install black - name: Run Quality check - run: make quality \ No newline at end of file + run: make quality diff --git a/README.md b/README.md index 67b42d9b4..8e247358e 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,8 @@ pip install hf-doc-builder doc-builder preview course ../course/chapters/LANG-ID --not_python_module ``` +**`preview` command does not work with Windows. + This will build and render the course on [http://localhost:3000/](http://localhost:3000/). Although the content looks much nicer on the Hugging Face website, this step will still allow you to check that everything is formatted correctly. **🚀 Submit a pull request** diff --git a/chapters/en/chapter1/1.mdx b/chapters/en/chapter1/1.mdx index e828633c7..5136d0fe4 100644 --- a/chapters/en/chapter1/1.mdx +++ b/chapters/en/chapter1/1.mdx @@ -23,7 +23,7 @@ Here is a brief overview of the course: - Chapters 1 to 4 provide an introduction to the main concepts of the 🤗 Transformers library. By the end of this part of the course, you will be familiar with how Transformer models work and will know how to use a model from the [Hugging Face Hub](https://huggingface.co/models), fine-tune it on a dataset, and share your results on the Hub! - Chapters 5 to 8 teach the basics of 🤗 Datasets and 🤗 Tokenizers before diving into classic NLP tasks. By the end of this part, you will be able to tackle the most common NLP problems by yourself. -- Chapters 9 to 12 go beyond NLP, and explore how Transformer models can be used tackle tasks in speech processing and computer vision. Along the way, you'll learn how to build and share demos of your models, and optimize them for production environments. By the end of this part, you will be ready to apply 🤗 Transformers to (almost) any machine learning problem! +- Chapters 9 to 12 go beyond NLP, and explore how Transformer models can be used to tackle tasks in speech processing and computer vision. Along the way, you'll learn how to build and share demos of your models, and optimize them for production environments. By the end of this part, you will be ready to apply 🤗 Transformers to (almost) any machine learning problem! This course: @@ -83,7 +83,7 @@ The Jupyter notebooks containing all the code from the course are hosted on the - **How can I contribute to the course?** There are many ways to contribute to the course! If you find a typo or a bug, please open an issue on the [`course`](https://github.com/huggingface/course) repo. If you would like to help translate the course into your native language, check out the instructions [here](https://github.com/huggingface/course#translating-the-course-into-your-language). -- ** What were the choices made for the each translation?** +- ** What were the choices made for each translation?** Each translation has a glossary and `TRANSLATING.txt` file that details the choices that were made for machine learning jargon etc. You can find an example for German [here](https://github.com/huggingface/course/blob/main/chapters/de/TRANSLATING.txt). diff --git a/chapters/ja/_toctree.yml b/chapters/ja/_toctree.yml index fb3be77b8..bffdbc572 100644 --- a/chapters/ja/_toctree.yml +++ b/chapters/ja/_toctree.yml @@ -26,6 +26,19 @@ - local: chapter1/10 title: 章末クイズ +- title: 2. 🤗 Transformersの使用 + sections: + - local: chapter2/1 + title: イントロダクション + - local: chapter2/2 + title: pipelineの裏側 + - local: chapter2/3 + title: モデル + - local: chapter2/4 + title: トークナイザ + - local: chapter2/5 + title: 複数系列の処理 + - title: 4. モデルとトークナイザーの共有 sections: - local: chapter4/1 diff --git a/chapters/ja/chapter2/1.mdx b/chapters/ja/chapter2/1.mdx new file mode 100644 index 000000000..4621ae318 --- /dev/null +++ b/chapters/ja/chapter2/1.mdx @@ -0,0 +1,25 @@ +# イントロダクション + + + +[第1章](/course/chapter1)で見たように、Transformerのモデルは通常、非常に大きなものです。数百万から数百億のパラメータを持つこれらのモデルをトレーニングし、デプロイすることは複雑な仕事です。さらに、ほぼ毎日新しいモデルがリリースされ、それぞれが独自の実装を持っているため、それらをすべて試すことは簡単なことではありません。 + +🤗 Transformersライブラリはこの問題を解決するために作成されました。その目的は、どんなTransformerモデルでもロード、学習、保存ができる単一のAPIを提供することです。このライブラリの主な機能は以下の通りです。 + +- **使いやすさ**: 推論のためには、2行のコードで最先端のNLPモデルをダウンロードして使用できます! +- **柔軟さ**: 実装のコアでは、すべてのモデルが単純なPyTorchの`nn.Module`またはTensorFlowの`tf.keras.Model`クラスであり、それぞれの機械学習(ML)フレームワークの他のモデルと同様に扱うことができます! +- **単純さ**: ライブラリ全体でほとんど抽象化は行われていません。"All in one file"がコアコンセプトです。例えば、モデルの順伝播は1つのファイルで完全に定義されているため、コードそのものが理解しやすく、変更しやすいです! + +この最後の特徴が🤗 Transformersを他のMLライブラリとは全く違うものにしています。モデルはファイル間で共有されるモジュールで構築されるのではなく、その代わり、各モデルはそれ自身のレイヤーを持っています。モデルをより親しみやすく理解しやすくすることに加えて、これにより、他のモデルに影響を与えることなく、一つのモデルで簡単に実験することができます。 + +この章ではまず、[第1章](/course/chapter1) で紹介した `pipeline()` 関数を再現するために、モデルとトークナイザを一緒に使ったエンドツーエンドの例を紹介します。次に、モデルAPIについて説明します。モデルとコンフィギュレーションについて詳しく説明し、モデルをロードする方法と、モデルがどのように数値入力を処理して予測を出力するかを説明します。 + +次に、`pipeline()`関数のもう一つの主要な構成要素であるトークナイザーAPIについて見ていきます。トークナイザは、テキストからニューラルネットワークの数値入力への変換を処理し、必要に応じてテキストに戻す、最初と最後の処理ステップを担います。最後に、複数の文章をバッチ処理でモデルに送る方法を紹介し、`tokenizer()` 関数を詳しく見て、まとめに移ります。 + + +⚠️ Model Hubと🤗 Transformersで利用可能なすべての機能を活用するには、アカウントを作成することをお勧めします。 + + diff --git a/chapters/ja/chapter2/2.mdx b/chapters/ja/chapter2/2.mdx new file mode 100644 index 000000000..e03614571 --- /dev/null +++ b/chapters/ja/chapter2/2.mdx @@ -0,0 +1,359 @@ + + +# pipelineの裏側 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + +ここは、PyTorchとTensorFlowのどちらを使うかによって内容が少し異なる最初のセクションです。タイトルの上にあるスイッチを切り替えて、好きなプラットフォームを選んでください! + + +{#if fw === 'pt'} + +{:else} + +{/if} + +まずは例として、[Chapter1](/course/chapter1)で次のコードを実行したときに、その裏で何が起こったかを見てみましょう。 + +```python +from transformers import pipeline + +classifier = pipeline("sentiment-analysis") +classifier( + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] +) +``` + +そして、その出力は以下のようになります。 + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}, + {'label': 'NEGATIVE', 'score': 0.9994558095932007}] +``` + +[第1章](/course/chapter1)で見たように、このpipelineは、前処理、モデルへの入力、後処理の3つのステップをグループ化したものです。 + +
+The full NLP pipeline: tokenization of text, conversion to IDs, and inference through the Transformer model and the model head. + +
+ +早速、それぞれを確認してみましょう。 + + +## トークナイザを用いた前処理 + +他のニューラルネットワークのように、Transformerのモデルは生のテキストを直接処理できないので、今回のpipelineの最初のステップは、テキストの入力をモデルが理解できる数値に変換することです。これを行うために我々は*トークナイザ* を使用します。*トークナイザ* は、以下の処理を担います。 + +- 入力を単語、サブワード、記号(句読点など)に分割する。それらを*トークン*と呼ぶ。 +- 各トークンを整数にマッピングする。 +- モデルにとって有用な追加の入力を付け足す。 + +この前処理はモデルが事前学習されたときと全く同じ方法で行われる必要があるので、まず[Model Hub](https://huggingface.co/models)から情報をダウンロードする必要があります。これを行うには、`AutoTokenizer` クラスとその `from_pretrained()` メソッドを使用します。モデルのチェックポイント名を使って、モデルのトークナイザに関連するデータを自動的に取得し、キャッシュします (従って、以下のコードを最初に実行したときにのみダウンロードされます)。 + +`sentiment-analysis` のpipelineのデフォルトのチェックポイントは、`distilbert-base-uncased-finetuned-sst-2-english` なので (そのモデルカードは [ここ](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english) で見られます)、次のように実行します。 + +```python +from transformers import AutoTokenizer + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` + +トークナイザがあれば、文章を直接トークナイザに渡し、モデルに与える準備ができた辞書データに変換することができます! あとは入力IDのリストをテンソルに変換するだけです。 + +🤗 Transformerは、どのMLフレームワークをバックエンドとして使うかを気にせず使うことができます。PyTorchやTensorFlow、モデルによってはFlaxかもしれません。しかし、Transformerのモデルは、入力として*テンソル*しか受け付けません。もしテンソルに聞き馴染みがない場合は、NumPy配列を代わりに考えてもらうと良いかもしれません。NumPyの配列はスカラー(0次元)、ベクトル(1次元)、行列(2次元)、あるいはもっと多くの次元を持つことができます。これは事実上テンソルです。他のMLフレームワークのテンソルも同様な振る舞いをし、通常はNumPy配列と同じくらい簡単にインスタンス化することができます。 + +返したいテンソルの種類(PyTorch、TensorFlow、あるいは素のNumPy)を指定するには、`return_tensors` 引数を用います。 + +{#if fw === 'pt'} +```python +raw_inputs = [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", +] +inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="pt") +print(inputs) +``` +{:else} +```python +raw_inputs = [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", +] +inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="tf") +print(inputs) +``` +{/if} + +パディングや切り捨てなどの処理についてはまだ心配しなくて大丈夫です。これらは後で説明します。ここで覚えておくべきことは、1つの文か文のリストを渡せることと、返したいテンソルの型を指定することです(型を渡さなければ、結果としてリストのリストを得ることになります)。 + +{#if fw === 'pt'} + +PyTorchのテンソルとしての結果は、以下のようになります。 + + +```python out +{ + 'input_ids': tensor([ + [ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012, 102], + [ 101, 1045, 5223, 2023, 2061, 2172, 999, 102, 0, 0, 0, 0, 0, 0, 0, 0] + ]), + '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] + ]) +} +``` +{:else} + +TensorFlowのテンソルとしての結果は、以下のようになります。 + +```python out +{ + 'input_ids': , + 'attention_mask': +} +``` +{/if} + +出力自体は、`input_ids` と `attention_mask` という2つのキーを持つ辞書です。`input_ids` には2行の整数値(各文章に1つずつ)が含まれており、各文章に含まれるトークンを一意に識別することができます。`attention_mask` が何であるかは、この章の後半で説明します。 + +## モデルの使い方 + +{#if fw === 'pt'} + +トークナイザで行ったのと同じ方法で事前学習したモデルをダウンロードすることができます。🤗 Transformersは `AutoModel` クラスを提供しており、このクラスは `from_pretrained()` メソッドも持っています。 + +```python +from transformers import AutoModel + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = AutoModel.from_pretrained(checkpoint) +``` +{:else} + +トークナイザで行ったのと同じ方法で事前学習したモデルをダウンロードすることができます。🤗 Transformers は `TFAutoModel` クラスを提供しており、このクラスは `from_pretrained` メソッドも持っています。 + + +```python +from transformers import TFAutoModel + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = TFAutoModel.from_pretrained(checkpoint) +``` +{/if} + +このコードスニペットでは、以前pipelineで使用したのと同じチェックポイントをダウンロードし(実際には既にキャッシュされているはずです)、それを使ってモデルをインスタンス化しています。 + +このアーキテクチャには基本的なTransformerモジュールだけが含まれており、いくつかの入力が与えられると、*隠れ状態(hidden states)*(*特徴量*とも呼ばれます)と呼ばれるものを出力します。各モデルの入力に対して、我々は**Transformerモデルによるその入力の文脈的理解**を表す高次元ベクトルを取り出します。 + +もしこれが理解できなくても、心配しないでください。後ですべて説明します。 + +これらの隠れ状態はそれ自体で役に立つこともありますが、通常は*head*と呼ばれるモデルの別の部分への入力となります。[第1章](/course/chapter1) では、異なるタスクでも同じアーキテクチャで実行されたかもしれませんが、これらのタスクにはそれぞれ異なるヘッドが使用されています。 + +### 高次元ベクトル? + +Transformerモジュールが出力するベクトルは通常大きなものです。一般的に3つの次元を持ちます。 + +- **Batch size**: 一度に処理する系列の数(この例では2)。 +- **Sequence length**: 系列の数値表現の長さ(この例では16)。 +- **Hidden size**: 各モデル入力のベクトル次元。 + +Hidden sizeが大きいと「高次元」と言われます。Hidden sizeは非常に大きくすることができます(小さいモデルでは768が一般的で、大きいモデルでは3072以上に達することがあります)。 + +前処理をした入力をモデルに与えてみると、このことがわかります。 + +{#if fw === 'pt'} +```python +outputs = model(**inputs) +print(outputs.last_hidden_state.shape) +``` + +```python out +torch.Size([2, 16, 768]) +``` +{:else} +```py +outputs = model(inputs) +print(outputs.last_hidden_state.shape) +``` + +```python out +(2, 16, 768) +``` +{/if} + +🤗 Transformersモデルの出力は `namedtuple` や辞書のように動作することに注意してください。(私たちが行った)属性や(`outputs["last_hidden_state"]`のような)キー、あるいは探しているものがどこにあるか正確に知っていれば(`outputs[0]`のような)インデックスによって要素にアクセスすることが可能です。 + +### モデルヘッド(Model heads): 数値の意味を理解する + +モデルヘッドは高次元の隠れ状態ベクトルを入力として受け取り、それを異なる次元に射影します。モデルヘッドは、通常、1つまたはいくつかの線形層で構成されます。 + +
+A Transformer network alongside its head. + +
+ +Transformerモデルの出力は直接モデルヘッドに送られ、処理されます。 + +この図では、モデルはその埋め込み層とそれに続く層で表現されています。埋め込み層は、トークン化された入力の各入力IDを、紐付いたトークンを表すベクトルに変換します。後続の層はアテンション機構を用いてそれらのベクトルを操作し、最終的な文の表現を生成します。 + +🤗 Transformers には多くの異なるアーキテクチャがあり、それぞれが特定のタスクに取り組むために設計されています。以下はその一部のリストです。 + +- `*Model` (隠れ状態を取り出す) +- `*ForCausalLM` +- `*ForMaskedLM` +- `*ForMultipleChoice` +- `*ForQuestionAnswering` +- `*ForSequenceClassification` +- `*ForTokenClassification` + +{#if fw === 'pt'} + +今回の例では、文章分類のヘッドを持つモデルが必要になります(文章をポジティブかネガティブかに分類できるようにするため)。そのため、実際には `AutoModel` クラスではなく、 `AutoModelForSequenceClassification` クラスを使用することになります。 + +```python +from transformers import AutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +outputs = model(**inputs) +``` +{:else} + +今回の例では、文章分類のヘッドを持つモデルが必要になります(文章をポジティブかネガティブかに分類することができます)。そこで、実際には `TFAutoModel` クラスではなく、 `TFAutoModelForSequenceClassification` クラスを利用することになります。 + +```python +from transformers import TFAutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +outputs = model(inputs) +``` +{/if} + +ここで、入力の形を見ると、次元がかなり小さくなっています。モデルヘッドは、先ほどの高次元ベクトルを入力とし、2つの値(1ラベルにつき1つ)を含むベクトルを出力します。 + +```python +print(outputs.logits.shape) +``` + +{#if fw === 'pt'} +```python out +torch.Size([2, 2]) +``` +{:else} +```python out +(2, 2) +``` +{/if} + +2つの文と2つのラベルがあるので、このモデルから得られる結果は2x2の形状です。 + +## 出力の後処理 + +モデルから出力される値は、それ自体では必ずしも意味をなしません。その例を見てみましょう。 + +```python +print(outputs.logits) +``` + +{#if fw === 'pt'} +```python out +tensor([[-1.5607, 1.6123], + [ 4.1692, -3.3464]], grad_fn=) +``` +{:else} +```python out + +``` +{/if} + +我々のモデルは最初の文を`[-1.5607, 1.6123]`、2番目の文を`[ 4.1692, -3.3464]`と予測しました。これは確率ではなく*logits*であり、モデルの最終層が出力した正規化されていない生のスコアです。確率に変換するためには、[SoftMax](https://en.wikipedia.org/wiki/Softmax_function)層を通る必要があります(すべての🤗 Transformersモデルはlogitsを出力します。学習用の損失関数は通常、SoftMaxなどの最後の活性化関数とクロスエントロピーのような実際の損失関数が融合されるためです)。 + +{#if fw === 'pt'} +```py +import torch + +predictions = torch.nn.functional.softmax(outputs.logits, dim=-1) +print(predictions) +``` +{:else} +```py +import tensorflow as tf + +predictions = tf.math.softmax(outputs.logits, axis=-1) +print(predictions) +``` +{/if} + +{#if fw === 'pt'} +```python out +tensor([[4.0195e-02, 9.5980e-01], + [9.9946e-01, 5.4418e-04]], grad_fn=) +``` +{:else} +```python out +tf.Tensor( +[[4.01951671e-02 9.59804833e-01] + [9.9945587e-01 5.4418424e-04]], shape=(2, 2), dtype=float32) +``` +{/if} + +このモデルは、最初の文は`[0.0402, 0.9598]`、2番目の文は`[0.9995, 0.0005]`と予測したことがわかるでしょう。これらは確率のスコアです。 + +以下では、各ポジションに対応するラベルを得るには、モデル設定の `id2label` 属性を調べます(これについては次のセクションで詳しく説明します)。 + +```python +model.config.id2label +``` + +```python out +{0: 'NEGATIVE', 1: 'POSITIVE'} +``` + +これで、このモデルは次のように予測したと結論づけることができます。 + +- 最初の文: ネガティブ: 0.0402、ポジティブ: 0.9598 +- 第二文: ネガティブ: 0.9995、 ポジティブ: 0.0005 + +これで、pipelineの3つのステップ、すなわち、トークナイザによる前処理、モデルへの入力、そして後処理がうまく再現できました。この先では、それぞれのステップをより深く掘り下げていきましょう。 + + + +✏️ **試してみよう!** 自分でテキストを2つ(またはそれ以上)用意し、`sentiment-analysis` pipelineで実行します。そして、ここで見た手順を自分で再現して、同様な結果が得られるかどうか確認してみましょう! + + diff --git a/chapters/ja/chapter2/3.mdx b/chapters/ja/chapter2/3.mdx new file mode 100644 index 000000000..1c25a3b8f --- /dev/null +++ b/chapters/ja/chapter2/3.mdx @@ -0,0 +1,230 @@ + + +# モデル + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + +{:else} + +{/if} + +{#if fw === 'pt'} + +このセクションでは、モデルの作成と使用について詳しく見ていきます。ここでは、チェックポイントから任意のモデルをインスタンス化する際に便利な `AutoModel` クラスを使用します。 + +`AutoModel` クラスとその関連クラスは、実際にはライブラリで利用可能な様々なモデルを覆うシンプルなラッパーです。このクラスは、チェックポイントに適したモデルのアーキテクチャを自動的に推測し、そのアーキテクチャを持つモデルをインスタンス化することができる賢いラッパーです。 + +{:else} + +このセクションでは、モデルの作成と使用について詳しく見ていきます。ここでは、チェックポイントから任意のモデルをインスタンス化する際に便利な `TFAutoModel` クラスを使用します。 + +`TFAutoModel` クラスとその関連クラスは、実際にはライブラリで利用可能な様々なモデルに対する単純なラッパーです。このクラスは、チェックポイントに適したモデルのアーキテクチャを自動的に推測し、そのアーキテクチャを持つモデルをインスタンス化することができる賢いラッパーです。 + +{/if} + +しかし、使用したいモデルの種類がわかっている場合は、そのアーキテクチャを定義するクラスを直接使用することができます。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) +``` + +```python out +BertConfig { + [...] + "hidden_size": 768, + "intermediate_size": 3072, + "max_position_embeddings": 512, + "num_attention_heads": 12, + "num_hidden_layers": 12, + [...] +} +``` + +我々は、これらの属性のすべてが何をするかまだ見ていませんが、そのうちのいくつかを認識する必要があります: `hidden_​​size` 属性は `hidden_​​states` ベクトルのサイズを定義し、`num_hidden_​​layers` は Transformer モデルが持つレイヤーの数を定義します。 + +### さまざまなローディング方法 + +デフォルトのコンフィギュレーション(設定)からモデルを作成すると、ランダムな値で初期化されます。 + +{#if fw === 'pt'} +```py +from transformers import BertConfig, BertModel + +config = BertConfig() +model = BertModel(config) + +# Model is randomly initialized! +``` +{:else} +```py +from transformers import BertConfig, TFBertModel + +config = BertConfig() +model = TFBertModel(config) + +# Model is randomly initialized! +``` +{/if} + +この状態でもモデルは使えますが、意味不明な出力になってしまうので、まず学習させる必要があります。手元のタスクに対して一からモデルを学習させることもできますが、[第1章](/course/chapter1)で見たように、長い時間と多くのデータを必要とし、環境負荷も無視できないものになるでしょう。無駄な努力や重複を避けるためには、既に学習済みのモデルを共有・再利用できることが必須です。 + +既に学習済みのTransformerモデルをロードするのは簡単です。`from_pretrained()` メソッドを使ってこれを行うことができます。 + +{#if fw === 'pt'} +```py +from transformers import BertModel + +model = BertModel.from_pretrained("bert-base-cased") +``` + +先ほど見たように、 `BertModel` を同等のクラスである `AutoModel` に置き換えることができます。チェックポイントに依存しないコードを生成するため、今後はこの方法をとります。あるチェックポイントで動作するコードは、別のチェックポイントでもシームレスに動作するはずです。これはアーキテクチャが異なっていても、チェックポイントが同様のタスク (例えばセンチメント分析タスク) に対して学習されたものである限り、当てはまります。 + +{:else} +```py +from transformers import TFBertModel + +model = TFBertModel.from_pretrained("bert-base-cased") +``` + +先ほど見たように、 `TFBertModel` を同等のクラスである `TFAutoModel` に置き換えることができます。チェックポイントに依存しないコードを生成するため、今後はこの方法をとります。あるチェックポイントで動作するコードは、別のチェックポイントでもシームレスに動作するはずです。これはアーキテクチャが異なっていても、チェックポイントが同様のタスク (例えばセンチメント分析タスク) に対して学習されたものである限り、当てはまります。 + +{/if} + +上記のコードサンプルでは `BertConfig` を使用せず、代わりに `bert-base-cased` を介して事前に学習されたモデルをロードしています。これはBERTの作者自身によって学習されたモデルのチェックポイントです。これについての詳細はその[モデルカード](https://huggingface.co/bert-base-cased)に記載されています。 + +このモデルは現在、チェックポイントのすべての重みで初期化されています。これは訓練されたタスクの推論に直接使用することができ、また、新しいタスクでファインチューニングすることができます。ゼロからではなく、事前に学習させた重みを用いて学習させることで、迅速に良い結果を得ることができます。 + +重みはダウンロードされ、キャッシュフォルダ(デフォルトは *~/.cache/huggingface/transformers* )に保存されます(将来 `from_pretrained()` メソッドを呼び出したときに再ダウンロードされないようにするため)。環境変数 `HF_HOME` を設定することで、キャッシュフォルダをカスタマイズすることができます。 + +モデルをロードするために使用される識別子は、BERTアーキテクチャと互換性がある限り、モデルハブ上の任意のモデルの識別子を使用することができます。利用可能なBERTチェックポイントの全リストは、[ここ](https://huggingface.co/models?filter=bert)で確認できます。 + +### 保存方法 + +モデルの保存はロードするのと同じくらい簡単で、`from_pretrained()` メソッドに類似した `save_pretrained()` メソッドを使用します。 + +```py +model.save_pretrained("directory_on_my_computer") +``` + +これにより、2つのファイルがディスクに保存されます。 + +{#if fw === 'pt'} +``` +ls directory_on_my_computer + +config.json pytorch_model.bin +``` +{:else} +``` +ls directory_on_my_computer + +config.json tf_model.h5 +``` +{/if} + +*config.json* ファイルを見てみると、モデルアーキテクチャを構築するために必要な属性がわかると思います。このファイルには、チェックポイントがどこで発生したのか、最後にチェックポイントを保存したときに使用していた🤗 Transformersのバージョンなどのメタデータも含まれています。 + +{#if fw === 'pt'} +*pytorch_model.bin* ファイルは *state dictionary* として知られており、モデルのすべての重みが含まれています。この2つのファイルは密接に関係しています。コンフィギュレーションはモデルのアーキテクチャを知るために必要であり、モデルの重みはモデルのパラメータです。 + +{:else} +*tf_model.h5*ファイルは*state dictionary*として知られており、すべてのモデルの重みが含まれています。この2つのファイルは密接に関係しています。設定はモデルのアーキテクチャを知るために必要であり、モデルの重みはモデルのパラメータです。 + +{/if} + +## 推論のためのトランスフォーマーモデルの使用 + +さて、モデルをロードして保存する方法がわかったので、それを使って予測をしてみましょう。トランスフォーマーモデルは数値、つまりトークナイザが生成する数値だけを処理することができます。しかし、トークナイザについて説明する前に、モデルがどのような入力を受け入れるかを探ってみましょう。 + +トークナイザは入力を適切なフレームワークのテンソルにキャストすることを引き受けてくれますが、何が起こっているかを理解するために、入力をモデルに送る前に行わなければならないことを簡単に見てみましょう。 + +例えば、いくつかの文があるとします。 + +```py +sequences = ["Hello!", "Cool.", "Nice!"] +``` + +トークナイザはこれらを一般に*入力ID*と呼ばれる語彙のインデックスに変換します。各文は今は数字の羅列です。その結果、出力は次のようになります。 + +```py no-format +encoded_sequences = [ + [101, 7592, 999, 102], + [101, 4658, 1012, 102], + [101, 3835, 999, 102], +] +``` + +これはエンコードされた文のリストであり、リストのリストです。テンソルは長方形の形しか受け付けません(行列を考えてみましょう)。この「配列」はすでに矩形であるので、テンソルに変換するのは簡単です。 + +{#if fw === 'pt'} +```py +import torch + +model_inputs = torch.tensor(encoded_sequences) +``` +{:else} +```py +import tensorflow as tf + +model_inputs = tf.constant(encoded_sequences) +``` +{/if} + +### モデルの入力としてのテンソルの使用 + +モデルでテンソルを使うのは非常に簡単で、モデルの呼び出し時に入力として渡すだけです。 + +```py +output = model(model_inputs) +``` + +このモデルは多くの異なる引数を受け付けますが、必要なのは入力IDだけです。他の引数が何をするのか、いつ必要なのかは後で説明します。 +しかし、まずはTransformerモデルが理解できる入力を構築するトークナイザを詳しく見る必要があります。 diff --git a/chapters/ja/chapter2/4.mdx b/chapters/ja/chapter2/4.mdx new file mode 100644 index 000000000..630e58989 --- /dev/null +++ b/chapters/ja/chapter2/4.mdx @@ -0,0 +1,240 @@ + + +# トークナイザ + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + + +トークナイザはNLPパイプラインの重要な構成要素の1つです。トークナイザの目的は1つで、テキストをモデルが処理できるデータに変換することです。モデルが処理できるのは数値のみなので、トークナイザは入力されたテキストを数値データに変換する必要があります。このセクションでは、トークン化パイプラインで何が起きているのかを具体的に説明します。 + +NLPのタスクにおいて、一般的に処理されるデータは生文で、以下はその例です。 + +``` +Jim Henson was a puppeteer (Jim Hensonは人形師でした) +``` + +しかしながらモデルが処理できるのは数値のみなので、生文を数値に変換する方法を考える必要があります。トークナイザはまさにこの役割を担っているものであり、変換にはさまざまな方法があります。目的はモデルにとって最も意味のある表現を見つけることです。そして可能な限り、コンパクトな表現を見つけることも目的としています。 + +ここではトークン化アルゴリズムの例をいくつか見ながら、トークン化に関する疑問を解消していきます。 + +## 単語ベース + + + +最初に思い浮かぶトークナイズ方法は、_単語ベース_ のものです。一般に、いくつかのルールを設定するだけで非常に簡単に使用でき、そして多くの場合において適切な結果を得ることができます。例えば、以下の画像のように生のテキストを単語に分割し、それぞれの数値表現を見つけることが目的です。 + +
+ An example of word-based tokenization. + +
+ +テキストの分け方にはさまざまな種類があります。例えば、Pythonの `split()` 関数を適用して、テキストを空白で区切ることで単語に分割することができます。 + +```py +tokenized_text = "Jim Henson was a puppeteer".split() +print(tokenized_text) +``` + +```python out +['Jim', 'Henson', 'was', 'a', 'puppeteer'] +``` + +また、単語トークン化には句読点に関する特別なルールを持つものもあります。この種のトークナイザを使用すると、かなり大きな「語彙」が作成されることになります。語彙は、コーパスに含まれるトークンの総数で定義されます。 + +各単語には個別のID(0〜語彙のサイズの数値)が割り当てられます。モデルはこれらのIDを使用して各単語を識別します。 + +単語ベースのトークナイザで言語を完全にカバーしようとすると、その言語の各単語に対応する識別子(ID)が必要になり、膨大な量のトークンが生成されることになります。例えば、英語には50万語以上の単語があるので、各単語から入力IDへのマップ(対応表)を作るには、それだけのIDを記録しておく必要があります。また、「dog」のような単語と「dogs」のような単語は表現が異なるため、モデルは初め "dog" と "dogs" が似ていることを知ることができず、無関係な単語として認識してしまいます。また、"run" と "running" のような類似した単語についても同様で、モデルは初期状態では類似しているとは認識できません。 + +最後に、語彙にない単語 (未知語)を表すためのトークンが必要です。これは "unknown" トークンと呼ばれ、"[UNK]" や "<unk>" として表されます。トークナイザが多くの unknown トークンを生成している場合、単語の適切な表現を取得できず、情報が失われていると解釈できます。語彙を作成する際の目標は、unknownトークンにトークン化されてしまう単語(未知語)がより少なくなるようにすることです。 + +unknown トークンの総数を減らす方法の1つは、_文字ベース_ のトークナイザを使用することです。 + +## 文字ベース + + + +_文字ベース_ トークナイザはテキストを単語単位ではなく文字単位で分割します。これには2つの主な利点があります。 + +- 語彙サイズがはるかに小さくなります +- すべての単語は文字で構成されるため、語彙外のトークン(未知語)がはるかに少なくなります + +しかし、ここでも空白と句読点に関する問題が発生します。 + +
+ An example of character-based tokenization. + +
+ +このアプローチも先と同様、完璧なものではありません。ここでは、表現が単語ではなく文字に基づいているので、直感的にはテキストの意味をうまく汲み取れないとも考えられます。各文字は単独ではあまり意味を持たないのに対し、単語はそのようなことはありません。しかし、言語によってはここでも違いがあります。例えば中国語の各文字は、ラテン語の文字よりも情報を持っています。(漢字1文字とアルファベット1文字では、表現している情報量が異なる場合がありますね。) + +考慮すべきもう1つの点としては、モデルが処理する必要があるトークンの数が非常に多くなってしまうことです。単語ベースのトークナイザでは、単語は1つのトークンになりますが、文字ベースのトークナイザでは、単語は10個以上のトークンに変換される可能性があります。 + +両者のいいとこ取りをするために、これらのアプローチを組み合わせた第3の手法を使用することができます。それが *サブワードトークン化* です。 + +## サブワードトークン化 + + + +サーブワードトークン化アルゴリズムは、「出現頻度の高い単語は小さなサブワードに分割されるべきではないが、出現頻度の低い単語は、意味を持ったサブワードに分割されるべきである」という原理に基づいています。 + +例えば "annoyingly" は出現頻度の低い単語として扱われ、"annoying" と "ly" に分割されることがあります。これら2つのサブワードは、それぞれ単独で頻繁に出現する可能性がありますが、一方で "annoyingly" は稀な単語なので、その意味を "annoying" と "ly" の合成語として表現しようという考え方になります。 + +それではここで、サブワードトークン化アルゴリズムが "Let's do tokenization!" という系列をトークン化する様子を見てみましょう。 + +
+ A subword tokenization algorithm. + +
+ +これらのサブワードは最終的に、うまく意味を表現したものとして機能します。例えば上の例では "tokenization" は "token" と "ization" に分割されていましたが、これら2つのトークンは、空間効率が良く(2つのトークンだけで長い単語を表現できている)、意味論的にも有意なものとなっています。これにより、比較的小さな語彙で多くの単語をカバーすることができ、未知語がほとんど出ないようになります。 + +このアプローチはトルコ語などの膠着語(機能語が自立語にくっついて文が構成される言語)において特に有効です。トルコ語では、サブワードを繋げることで(ほぼ)任意の長さの合成語を作ることができます。 + +### さらなるトークン化手法! + +実は他にも多くのトークン化手法が存在し、例えば以下のようなものがあります。 + +- Byte-level BPE: GPT-2で使用される手法 +- WordPiece: BERTで使用される手法 +- SentencePiece もしくは Unigram: いくつかの多言語モデルで使用される手法 + +ここまで読めば、APIを使ってトークナイザを使い始めるために必要な知識は十分に身についていると思います! + +## 読み込みと保存 + +トークナイザの読み込みと保存は、モデルと同様に簡単です。実際、これらは同じ2つのメソッド `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` については[Chapter3](/course/chapter3)で詳しく説明し、`attention_mask` についても後ほど説明します。まずは `input_ids` がどのように生成されるかを見てみましょう。 + +## エンコーディング + + + +テキストを数値に変換することを _エンコード_ と呼びます。エンコードはトークン化とその後の入力IDへの変換の2段階のプロセスで行われます。 + +ここまで見てきたように、最初のステップはテキストをトークン(単語や単語の一部、句読点など)に分割することです。このプロセスを管理するためのルールがいくつか存在します。まずは、モデルの名前を使ってトークナイザをインスタンス化する必要があります。これにより、モデルが事前学習されたときに使用されたものと同じルールを使用することができます。 + +2番目のステップはトークンを数値に変換することです。これにより、テンソルを構築し、モデルに入力することができます。これを行うために、トークナイザは *語彙* を有しています。これは、`from_pretrained()` メソッドでインスタンス化するときにダウンロードされる部分です。繰り返しになりますが、モデルの事前学習で使用された語彙と同じものを使用する必要があることに注意してください。 + +この2つの理解を深めるために、それぞれのステップを別々に見ていきます。ステップの中間結果を表示するために、トークン化パイプラインの一部を別々に実行するメソッドを使用しますが、実際には(セクション2で見たように)入力に対して直接トークナイザを呼び出す必要があります。 + +### トークン化 + +トークン化のプロセスは `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` に分割されているのがわかります。 + +### トークンからIDへの変換 + +トークンからIDへの変換は `convert_tokens_to_ids()` のトークナイザメソッドによって行われます。 + +```py +ids = tokenizer.convert_tokens_to_ids(tokens) + +print(ids) +``` + +```python out +[7993, 170, 11303, 1200, 2443, 1110, 3014] +``` + +これらの出力は、適切なフレームワークのテンソルに変換された後、前述のようにモデルの入力として使用できます。 + + + +✏️ **試してみよう!** 最後の2つのステップ(トークン化と入力IDへの変換)を、セクション2で使った入力文("I've been waiting for a HuggingFace course my whole life." と "I hate this so much!")に対して再現してみましょう。先ほどと同じ入力IDが得られるかどうかを確認してみてください。 + + + +## デコーディング + +*デコーディング* はエンコーディングとは逆の処理になります。`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` メソッドは語彙のインデックスをトークンに戻すだけでなく、同じ単語の一部であったトークンをまとめて、読みやすい文章に変換するところも担っています。この挙動は、プロンプトから生成されたテキストや、翻訳や要約などの系列から系列への変換などの問題を解くモデルを使うときに非常に役に立ちます。 + +ここまでで、トークナイザでできる基本的な処理(トークン化、IDへの変換、IDから文字列への変換)を理解できたのではないでしょうか。しかし、これは氷山の一角に過ぎません。次のセクションでは、これらの処理を限界まで拡張していき、その限界を超える方法を見ていきましょう。 diff --git a/chapters/ja/chapter2/5.mdx b/chapters/ja/chapter2/5.mdx new file mode 100644 index 000000000..66c7342ae --- /dev/null +++ b/chapters/ja/chapter2/5.mdx @@ -0,0 +1,339 @@ + + +# 複数系列の処理 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + +{:else} + +{/if} + +前のセクションでは、最も単純な使用例である、単一の短い系列(テキスト)に対して推論を行う方法を見てきました。しかし、これについて以下のような疑問をお持ちの方もいるかもしれません。 + +- 複数の系列をどのように処理するのか? +- 長さの異なる複数の系列をどのように処理するのか? +- モデルがうまく機能するためには、単語のインデックスだけが入力として必要なのか? +- 系列が長すぎてしまうということはあるのか? + +これらの疑問について、実際はどのような問題があるのか、そして🤗 Transformers 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) + +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) +# This line will fail. +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) +# This line will fail. +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モデルは、デフォルトでは複数の系列を入力として受け付けます。ここでは、`sequence`に対してトークナイザを適用したときに、トークナイザがその背後で行ったすべての処理を行おうとしました。しかし、もう少し詳しく見てみると、トークナイザは入力IDのリストをテンソルに変換するだけでなく、それに対して次元を追加していることがわかります。 + + +{#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} + +ここで入力IDと結果のロジット(モデルの出力)を見てみましょう。 + +{#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} + +*バッチ処理*とは、複数の系列をまとめてモデルに入力することです。系列が1つしかない場合でも、バッチを構築することができます。 + +``` +batched_ids = [ids, ids] +``` + +これは2つの同じ系列からなるバッチとなっています。 + + + +✏️ **試してみよう!** この `batch_ids` をテンソルに変換し、モデルに入力してみましょう。前と同じロジット(モデル出力)が得られることを確認してください(ただし、二重になっていることに注意してください)。 + + + +バッチ処理により、複数の系列をモデルに入力できるようになります。単一の系列でバッチを構築するのと同じように、簡単に複数の系列を使用することができます。ただし、ここで1つ問題があります。2つ以上の系列をバッチ処理する場合、系列の長さがそれぞれ異なる場合があります。これまでテンソルを扱ったことがある場合は、テンソルの形状は長方形である必要があることをご存知なのではないでしょうか。従って、異なる長さの系列の入力IDリストを直接テンソルに変換することはできません。この問題を回避するための方法として、入力を*パディング*することが一般的です。 + +## 入力のパディング + +以下の二重のリストはテンソルには変換できません。 + +```py no-format +batched_ids = [ + [200, 200, 200], + [200, 200] +] +``` + +この問題を回避するために、*パディング*を使用して、テンソルの形状を長方形にしてみましょう。パディングは、*パディングトークン*と呼ばれる特別な単語を短い系列に対して追加することで、すべての系列の長さを同じにします。例えば、10語の系列が10個、20語の系列が1個ある場合、パディングにより、すべての系列の長さが20語になります。上記の例では、結果として得られるテンソルは次のようになります。 + +```py no-format +padding_id = 100 + +batched_ids = [ + [200, 200, 200], + [200, 200, padding_id], +] +``` + +パティングトークンのIDは `tokenizer.pad_token_id` で見つけることができます。それでは、これを使って2つの系列を個別にモデルに入力する場合と、バッチ処理した場合の結果を比較してみましょう。 + +{#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} + +バッチ処理した予測のロジットについて何か違いがあるようです。2行目は2つ目の系列のロジットと同じであるべきですが、完全に異なる値となってしまっています! + +これは、Transformerモデルの代表的な特徴であるアテンション層が、それぞれのトークンに対して*コンテクスト化*を行っていることに起因します。アテンション層は、系列のすべてのトークンに注意(アテンション)を向けるため、パディングトークンも考慮の対象として扱います。異なる長さの系列を個別にモデルに入力する場合と、同じ系列をバッチ処理した場合の両方で同じ結果を得るためには、アテンション層にパディングトークンを無視するように指示する必要があります。これは、アテンションマスクを使用することで実現できます。 + +## アテンションマスク + +*アテンションマスク*とは入力IDのテンソルと全く同じ形をしたテンソルのことで、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} + +これで、バッチ内の2つ目の系列について同じロジットが得られました。 + +2つ目の系列の最後の値がパディングIDであることに注目してください。これは、アテンションマスクの0の値となっています。 + + + +✏️ **試してみよう!** セクション2で使用した2つの文 ("I've been waiting for a HuggingFace course my whole life." と "I hate this so much!") を手動でトークン化してみましょう。そしてこれらをモデルに入力し、セクション2で得られたロジットと同じ結果となることを確認してみましょう。次に、パディングトークンを使用してこれらをバッチ処理し、適切なアテンションマスクを作成してみましょう。また同様にモデルに入力した際、セクション2で得られた結果と同じものになることを確認してみましょう。 + + + +## より長い系列 + +トランスフォーマーモデルでは、モデルに入力できる系列の長さに制限があります。ほとんどのモデルは512トークンまたは1024トークンの系列を処理できますが、これより長い系列を処理しようとするとクラッシュしてしまいます。この問題に対しては、2つの解決策があります。 + +- 長い系列を処理できるモデルを使用する +- 系列を途中で区切って短くする + +処理できる系列長はモデルによって異なり、非常に長い系列の処理に特化したモデルも存在します。[Longformer](https://huggingface.co/transformers/model_doc/longformer.html) はその一例です。また、[LED](https://huggingface.co/transformers/model_doc/led.html) も長い系列を処理できるモデルです。非常に長い系列を処理する必要があるタスクに取り組んでいる場合は、これらのモデルを見てみて下さい。 + +もう1つの手法として、`max_sequence_length` パラメータを指定して系列を途中で区切ることをお勧めします。 + +```py +sequence = sequence[:max_sequence_length] +``` From 62beb5a517a10cbabf303e480180ff4f3aef873e Mon Sep 17 00:00:00 2001 From: lewtun Date: Thu, 15 Dec 2022 11:31:33 +0100 Subject: [PATCH 43/51] Bump release (#413) --- chapters/en/chapter7/3.mdx | 2 +- chapters/fr/chapter1/1.mdx | 18 +- chapters/fr/chapter1/4.mdx | 3 + chapters/fr/chapter7/3.mdx | 2 +- chapters/ja/chapter7/3.mdx | 2 +- chapters/vi/chapter7/3.mdx | 2 +- chapters/zh-CN/chapter7/3.mdx | 2 +- subtitles/README.md | 15 +- .../en/59_data-processing-for-translation.srt | 2 +- .../00_welcome-to-the-hugging-face-course.srt | 155 +++ subtitles/fr/01_the-pipeline-function.srt | 195 +++ ...2_the-carbon-footprint-of-transformers.srt | 514 +++++++ subtitles/fr/03_what-is-transfer-learning.srt | 131 ++ .../fr/04_the-transformer-architecture.srt | 111 ++ .../fr/05_transformer-models-encoders.srt | 179 +++ .../fr/06_transformer-models-decoders.srt | 167 +++ ...07_transformer-models-encoder-decoders.srt | 255 ++++ ...inside-the-pipeline-function-(pytorch).srt | 195 +++ ...ide-the-pipeline-function-(tensorflow).srt | 191 +++ ...antiate-a-transformers-model-(pytorch).srt | 131 ++ ...iate-a-transformers-model-(tensorflow).srt | 155 +++ subtitles/fr/12_tokenizers-overview.srt | 31 + subtitles/fr/13_word-based-tokenizers.srt | 99 ++ .../fr/14_character-based-tokenizers.srt | 107 ++ subtitles/fr/15_subword-based-tokenizers.srt | 127 ++ subtitles/fr/16_the-tokenization-pipeline.srt | 135 ++ .../17_batching-inputs-together-(pytorch).srt | 115 ++ ..._batching-inputs-together-(tensorflow).srt | 111 ++ ...gging-face-datasets-overview-(pytorch).srt | 131 ++ ...ng-face-datasets-overview-(tensorflow).srt | 131 ++ ...preprocessing-sentence-pairs-(pytorch).srt | 119 ++ ...processing-sentence-pairs-(tensorflow).srt | 123 ++ subtitles/fr/23_what-is-dynamic-padding.srt | 159 +++ subtitles/fr/24_the-trainer-api.srt | 139 ++ subtitles/fr/25_keras-introduction.srt | 103 ++ .../fr/26_fine-tuning-with-tensorflow.srt | 247 ++++ ...arning-rate-scheduling-with-tensorflow.srt | 159 +++ .../28_tensorflow-predictions-and-metrics.srt | 155 +++ ...29_write-your-training-loop-in-pytorch.srt | 287 ++++ ...-pytorch-training-loop-with-accelerate.srt | 139 ++ subtitles/fr/31_navigating-the-model-hub.srt | 147 ++ .../32_managing-a-repo-on-the-model-hub.srt | 355 +++++ .../fr/33_the-push-to-hub-api-(pytorch).srt | 179 +++ .../34_the-push-to-hub-api-(tensorflow).srt | 347 +++++ subtitles/fr/35_loading-a-custom-dataset.srt | 131 ++ ...e-and-dice-a-dataset-\360\237\224\252.srt" | 163 +++ ...dataframes-=-\342\235\244\357\270\217.srt" | 115 ++ .../fr/38_saving-and-reloading-a-dataset.srt | 143 ++ .../fr/39_memory-mapping-&-streaming.srt | 147 ++ .../fr/40_uploading-a-dataset-to-the-hub.srt | 87 ++ .../41_text-embeddings-&-semantic-search.srt | 147 ++ subtitles/fr/42_training-a-new-tokenizer.srt | 267 ++++ ...43_why-are-fast-tokenizers-called-fast.srt | 71 + .../fr/44_fast-tokenizer-superpowers.srt | 167 +++ ...oken-classification-pipeline-(pytorch).srt | 123 ++ ...n-classification-pipeline-(tensorflow).srt | 159 +++ ...-question-answering-pipeline-(pytorch).srt | 187 +++ ...estion-answering-pipeline-(tensorflow).srt | 143 ++ subtitles/fr/49_what-is-normalization.srt | 211 +++ subtitles/fr/50_what-is-pre-tokenization.srt | 99 ++ .../fr/51_byte-pair-encoding-tokenization.srt | 167 +++ subtitles/fr/52_wordpiece-tokenization.srt | 123 ++ subtitles/fr/53_unigram-tokenization.srt | 379 +++++ subtitles/fr/54_building-a-new-tokenizer.srt | 207 +++ ...ta-processing-for-token-classification.srt | 139 ++ ...rocessing-for-masked-language-modeling.srt | 99 ++ subtitles/fr/57_what-is-perplexity.srt | 83 ++ subtitles/fr/58_what-is-domain-adaptation.srt | 71 + .../fr/59_data-processing-for-translation.srt | 131 ++ subtitles/fr/60_what-is-the-bleu-metric.srt | 220 +++ .../61_data-processing-for-summarization.srt | 83 ++ subtitles/fr/62_what-is-the-rouge-metric.srt | 187 +++ ...rocessing-for-causal-language-modeling.srt | 171 +++ .../fr/64_using-a-custom-loss-function.srt | 135 ++ ...data-processing-for-question-answering.srt | 154 +++ ...g-step-in-question-answering-(pytorch).srt | 135 ++ ...tep-in-question-answering-(tensorflow).srt | 135 ++ subtitles/fr/68_data-collators-a-tour.srt | 233 ++++ .../69_what-to-do-when-you-get-an-error.srt | 107 ++ .../fr/70_using-a-debugger-in-a-notebook.srt | 111 ++ .../fr/71_using-a-debugger-in-a-terminal.srt | 127 ++ .../fr/72_asking-for-help-on-the-forums.srt | 143 ++ ...ugging-the-training-pipeline-(pytorch).srt | 171 +++ ...ing-the-training-pipeline-(tensorflow).srt | 251 ++++ subtitles/fr/75_writing-a-good-issue.srt | 131 ++ subtitles/fr/Titles and descriptions.txt | 1228 +++++++++++++++++ ...ta-processing-for-token-classification.srt | 90 +- ...rocessing-for-masked-language-modeling.srt | 72 +- subtitles/zh-CN/57_what-is-perplexity.srt | 68 +- .../zh-CN/58_what-is-domain-adaptation.srt | 58 +- .../59_data-processing-for-translation.srt | 82 +- .../zh-CN/60_what-is-the-bleu-metric.srt | 150 +- .../61_data-processing-for-summarization.srt | 78 +- utils/convert_bilingual_monolingual.py | 61 + 94 files changed, 13897 insertions(+), 315 deletions(-) create mode 100644 subtitles/fr/00_welcome-to-the-hugging-face-course.srt create mode 100644 subtitles/fr/01_the-pipeline-function.srt create mode 100644 subtitles/fr/02_the-carbon-footprint-of-transformers.srt create mode 100644 subtitles/fr/03_what-is-transfer-learning.srt create mode 100644 subtitles/fr/04_the-transformer-architecture.srt create mode 100644 subtitles/fr/05_transformer-models-encoders.srt create mode 100644 subtitles/fr/06_transformer-models-decoders.srt create mode 100644 subtitles/fr/07_transformer-models-encoder-decoders.srt create mode 100644 subtitles/fr/08_what-happens-inside-the-pipeline-function-(pytorch).srt create mode 100644 subtitles/fr/09_what-happens-inside-the-pipeline-function-(tensorflow).srt create mode 100644 subtitles/fr/10_instantiate-a-transformers-model-(pytorch).srt create mode 100644 subtitles/fr/11_instantiate-a-transformers-model-(tensorflow).srt create mode 100644 subtitles/fr/12_tokenizers-overview.srt create mode 100644 subtitles/fr/13_word-based-tokenizers.srt create mode 100644 subtitles/fr/14_character-based-tokenizers.srt create mode 100644 subtitles/fr/15_subword-based-tokenizers.srt create mode 100644 subtitles/fr/16_the-tokenization-pipeline.srt create mode 100644 subtitles/fr/17_batching-inputs-together-(pytorch).srt create mode 100644 subtitles/fr/18_batching-inputs-together-(tensorflow).srt create mode 100644 subtitles/fr/19_hugging-face-datasets-overview-(pytorch).srt create mode 100644 subtitles/fr/20_hugging-face-datasets-overview-(tensorflow).srt create mode 100644 subtitles/fr/21_preprocessing-sentence-pairs-(pytorch).srt create mode 100644 subtitles/fr/22_preprocessing-sentence-pairs-(tensorflow).srt create mode 100644 subtitles/fr/23_what-is-dynamic-padding.srt create mode 100644 subtitles/fr/24_the-trainer-api.srt create mode 100644 subtitles/fr/25_keras-introduction.srt create mode 100644 subtitles/fr/26_fine-tuning-with-tensorflow.srt create mode 100644 subtitles/fr/27_learning-rate-scheduling-with-tensorflow.srt create mode 100644 subtitles/fr/28_tensorflow-predictions-and-metrics.srt create mode 100644 subtitles/fr/29_write-your-training-loop-in-pytorch.srt create mode 100644 subtitles/fr/30_supercharge-your-pytorch-training-loop-with-accelerate.srt create mode 100644 subtitles/fr/31_navigating-the-model-hub.srt create mode 100644 subtitles/fr/32_managing-a-repo-on-the-model-hub.srt create mode 100644 subtitles/fr/33_the-push-to-hub-api-(pytorch).srt create mode 100644 subtitles/fr/34_the-push-to-hub-api-(tensorflow).srt create mode 100644 subtitles/fr/35_loading-a-custom-dataset.srt create mode 100644 "subtitles/fr/36_slice-and-dice-a-dataset-\360\237\224\252.srt" create mode 100644 "subtitles/fr/37_datasets-+-dataframes-=-\342\235\244\357\270\217.srt" create mode 100644 subtitles/fr/38_saving-and-reloading-a-dataset.srt create mode 100644 subtitles/fr/39_memory-mapping-&-streaming.srt create mode 100644 subtitles/fr/40_uploading-a-dataset-to-the-hub.srt create mode 100644 subtitles/fr/41_text-embeddings-&-semantic-search.srt create mode 100644 subtitles/fr/42_training-a-new-tokenizer.srt create mode 100644 subtitles/fr/43_why-are-fast-tokenizers-called-fast.srt create mode 100644 subtitles/fr/44_fast-tokenizer-superpowers.srt create mode 100644 subtitles/fr/45_inside-the-token-classification-pipeline-(pytorch).srt create mode 100644 subtitles/fr/46_inside-the-token-classification-pipeline-(tensorflow).srt create mode 100644 subtitles/fr/47_inside-the-question-answering-pipeline-(pytorch).srt create mode 100644 subtitles/fr/48_inside-the-question-answering-pipeline-(tensorflow).srt create mode 100644 subtitles/fr/49_what-is-normalization.srt create mode 100644 subtitles/fr/50_what-is-pre-tokenization.srt create mode 100644 subtitles/fr/51_byte-pair-encoding-tokenization.srt create mode 100644 subtitles/fr/52_wordpiece-tokenization.srt create mode 100644 subtitles/fr/53_unigram-tokenization.srt create mode 100644 subtitles/fr/54_building-a-new-tokenizer.srt create mode 100644 subtitles/fr/55_data-processing-for-token-classification.srt create mode 100644 subtitles/fr/56_data-processing-for-masked-language-modeling.srt create mode 100644 subtitles/fr/57_what-is-perplexity.srt create mode 100644 subtitles/fr/58_what-is-domain-adaptation.srt create mode 100644 subtitles/fr/59_data-processing-for-translation.srt create mode 100644 subtitles/fr/60_what-is-the-bleu-metric.srt create mode 100644 subtitles/fr/61_data-processing-for-summarization.srt create mode 100644 subtitles/fr/62_what-is-the-rouge-metric.srt create mode 100644 subtitles/fr/63_data-processing-for-causal-language-modeling.srt create mode 100644 subtitles/fr/64_using-a-custom-loss-function.srt create mode 100644 subtitles/fr/65_data-processing-for-question-answering.srt create mode 100644 subtitles/fr/66_the-post-processing-step-in-question-answering-(pytorch).srt create mode 100644 subtitles/fr/67_the-post-processing-step-in-question-answering-(tensorflow).srt create mode 100644 subtitles/fr/68_data-collators-a-tour.srt create mode 100644 subtitles/fr/69_what-to-do-when-you-get-an-error.srt create mode 100644 subtitles/fr/70_using-a-debugger-in-a-notebook.srt create mode 100644 subtitles/fr/71_using-a-debugger-in-a-terminal.srt create mode 100644 subtitles/fr/72_asking-for-help-on-the-forums.srt create mode 100644 subtitles/fr/73_debugging-the-training-pipeline-(pytorch).srt create mode 100644 subtitles/fr/74_debugging-the-training-pipeline-(tensorflow).srt create mode 100644 subtitles/fr/75_writing-a-good-issue.srt create mode 100644 subtitles/fr/Titles and descriptions.txt create mode 100644 utils/convert_bilingual_monolingual.py diff --git a/chapters/en/chapter7/3.mdx b/chapters/en/chapter7/3.mdx index 8053840b9..a1387158d 100644 --- a/chapters/en/chapter7/3.mdx +++ b/chapters/en/chapter7/3.mdx @@ -56,7 +56,7 @@ To get started, let's pick a suitable pretrained model for masked language model Although the BERT and RoBERTa family of models are the most downloaded, we'll use a model called [DistilBERT](https://huggingface.co/distilbert-base-uncased) -that can be trained much faster with little to no loss in downstream performance. This model was trained using a special technique called [_knowledge distillation_](https://en.wikipedia.org/wiki/Knowledge_distillation), where a large "teacher model" like BERT is used to guide the training of a "student model" that has far fewer parameters. An explanation of the details of knowledge distillation would take us too far afield in this section, but if you're interested you can read all about it in [_Natural Language Processing with Transformers_](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/ch05.html) (colloquially known as the Transformers textbook). +that can be trained much faster with little to no loss in downstream performance. This model was trained using a special technique called [_knowledge distillation_](https://en.wikipedia.org/wiki/Knowledge_distillation), where a large "teacher model" like BERT is used to guide the training of a "student model" that has far fewer parameters. An explanation of the details of knowledge distillation would take us too far afield in this section, but if you're interested you can read all about it in [_Natural Language Processing with Transformers_](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) (colloquially known as the Transformers textbook). {#if fw === 'pt'} diff --git a/chapters/fr/chapter1/1.mdx b/chapters/fr/chapter1/1.mdx index 1770ffe0e..c631bce23 100644 --- a/chapters/fr/chapter1/1.mdx +++ b/chapters/fr/chapter1/1.mdx @@ -36,23 +36,23 @@ Après avoir terminé ce cours, nous vous recommandons de suivre la [Spécialisa À propos des auteurs de ce cours : -**Abubakar Abid** a obtenu son doctorat en apprentissage automatique appliqué à Stanford. Pendant son doctorat, il a fondé [Gradio](https://github.com/gradio-app/gradio), une bibliothèque Python *open source* qui a été utilisée pour construire plus de 600 000 démos d'apprentissage automatique. Gradio a été rachetée par Hugging Face, où Abubakar occupe désormais le poste de responsable de l'équipe d'apprentissage automatique. +[**Abubakar Abid**](https://huggingface.co/abidlabs) a obtenu son doctorat en apprentissage automatique appliqué à Stanford. Pendant son doctorat, il a fondé [Gradio](https://github.com/gradio-app/gradio), une bibliothèque Python *open source* qui a été utilisée pour construire plus de 600 000 démos d'apprentissage automatique. Gradio a été rachetée par Hugging Face, où Abubakar occupe désormais le poste de responsable de l'équipe d'apprentissage automatique. -**Matthew Carrigan** est ingénieur en apprentissage machine chez Hugging Face. Il vit à Dublin en Irlande. Il a travaillé auparavant comme ingénieur en apprentissage machine chez Parse.ly et avant cela comme chercheur postdoctoral au Trinity College Dublin. Il ne croit pas que nous arrivions à l'*AGI* en mettant à l'échelle les architectures existantes mais a tout de même beaucoup d'espoir dans l'immortalité des robots. +[**Matthew Carrigan**](https://huggingface.co/Rocketknight1) est ingénieur en apprentissage machine chez Hugging Face. Il vit à Dublin en Irlande. Il a travaillé auparavant comme ingénieur en apprentissage machine chez Parse.ly et avant cela comme chercheur postdoctoral au Trinity College Dublin. Il ne croit pas que nous arrivions à l'*AGI* en mettant à l'échelle les architectures existantes mais a tout de même beaucoup d'espoir dans l'immortalité des robots. -**Lysandre Debut** est ingénieur en apprentissage machine chez Hugging Face et a travaillé sur la bibliothèque 🤗 *Transformers* depuis les premières phases de développement. Son but est de rendre le NLP accessible à tous en développant des outils disposant d'une API très simple. +[**Lysandre Debut**](https://huggingface.co/lysandre) est ingénieur en apprentissage machine chez Hugging Face et a travaillé sur la bibliothèque 🤗 *Transformers* depuis les premières phases de développement. Son but est de rendre le NLP accessible à tous en développant des outils disposant d'une API très simple. -**Sylvain Gugger** est ingénieur de recherche chez Hugging Face et un des principaux responsables de la bibliothèque 🤗 *Transformers*. Avant cela, il était chercheur en apprentissage machine chez fast.ai et a écrit le livre [*Deep Learning for Coders with fastai and PyTorch*](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/) avec Jeremy Howard. Son but est de rendre l'apprentissage profond plus accessible en développant et en améliorant des techniques permettant aux modèles d'apprendre rapidement sur des ressources limitées. +[**Sylvain Gugger**](https://huggingface.co/sgugger) est ingénieur de recherche chez Hugging Face et un des principaux responsables de la bibliothèque 🤗 *Transformers*. Avant cela, il était chercheur en apprentissage machine chez fast.ai et a écrit le livre [*Deep Learning for Coders with fastai and PyTorch*](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/) avec Jeremy Howard. Son but est de rendre l'apprentissage profond plus accessible en développant et en améliorant des techniques permettant aux modèles d'apprendre rapidement sur des ressources limitées. -**Dawood Khan** est un ingénieur en apprentissage automatique chez Hugging Face. Il vient de New York et est diplômé en informatique de l’Université de New York. Après avoir travaillé comme ingénieur iOS pendant quelques années, Dawood a quitté son poste pour créer Gradio avec ses cofondateurs. Gradio a finalement été acquis par Hugging Face. +[**Dawood Khan**](https://huggingface.co/dawoodkhan82) est un ingénieur en apprentissage automatique chez Hugging Face. Il vient de New York et est diplômé en informatique de l’Université de New York. Après avoir travaillé comme ingénieur iOS pendant quelques années, Dawood a quitté son poste pour créer Gradio avec ses cofondateurs. Gradio a finalement été acquis par Hugging Face. -**Merve Noyan** est développeuse *advocate* chez Hugging Face et travaille à la création d'outils et de contenus visant à démocratiser l'apprentissage machine pour tous. +[**Merve Noyan**](https://huggingface.co/merve) est développeuse *advocate* chez Hugging Face et travaille à la création d'outils et de contenus visant à démocratiser l'apprentissage machine pour tous. -**Lucile Saulnier** est ingénieure en apprentissage machine chez Hugging Face et travaille au développement et à l'implémentation de nombreux outils *open source*. Elle est également activement impliquée dans de nombreux projets de recherche dans le domaine du NLP comme l'entraînement collaboratif de modèles et le projet [BigScience](https://bigscience.huggingface.co/). +[**Lucile Saulnier**](https://huggingface.co/SaulLu) est ingénieure en apprentissage machine chez Hugging Face et travaille au développement et à l'implémentation de nombreux outils *open source*. Elle est également activement impliquée dans de nombreux projets de recherche dans le domaine du NLP comme l'entraînement collaboratif de modèles et le projet [BigScience](https://bigscience.huggingface.co/). -**Lewis Tunstall** est ingénieur en apprentissage machine chez Hugging Face et dévoué au développement d'outils *open source* avec la volonté de les rendre accessibles à une communauté plus large. Il est également co-auteur du livre [*Natural Language Processing with Transformers*](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). +[**Lewis Tunstall**](https://huggingface.co/lewtun) est ingénieur en apprentissage machine chez Hugging Face et dévoué au développement d'outils *open source* avec la volonté de les rendre accessibles à une communauté plus large. Il est également co-auteur du livre [*Natural Language Processing with Transformers*](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). -**Leandro von Werra** est ingénieur en apprentissage machine dans l'équipe *open source* d'Hugging Face et également co-auteur du livre [*Natural Language Processing with Transformers*](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). Il a plusieurs années d'expérience dans l'industrie où il a pu déployer des projets de NLP en production et travailler sur toutes les étapes clefs du déploiement. +[**Leandro von Werra**](https://huggingface.co/lvwerra) est ingénieur en apprentissage machine dans l'équipe *open source* d'Hugging Face et également co-auteur du livre [*Natural Language Processing with Transformers*](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). Il a plusieurs années d'expérience dans l'industrie où il a pu déployer des projets de NLP en production et travailler sur toutes les étapes clefs du déploiement. ## FAQ diff --git a/chapters/fr/chapter1/4.mdx b/chapters/fr/chapter1/4.mdx index 28de8aa80..12362dac8 100644 --- a/chapters/fr/chapter1/4.mdx +++ b/chapters/fr/chapter1/4.mdx @@ -80,6 +80,9 @@ Imaginez qu'à chaque fois qu'une équipe de recherche, une association d'étudi C'est pourquoi le partage des modèles du langage est primordial : partager les poids d'entraînement et construire à partir de ces poids permet de réduire les coûts de calcul globaux ainsi que l'empreinte carbone de toute la communauté. +A noter que vous pouvez d’ailleurs évaluer l’empreinte carbone de l’entraînement de vos modèles à travers plusieurs outils. Par exemple [ML CO2 Impact](https://mlco2.github.io/impact/) ou bien [Code Carbon]( https://codecarbon.io/) qui est intégré dans 🤗 Transformers. Pour en savoir plus à ce sujet, vous pouvez lire cet [article de blog](https://huggingface.co/blog/carbon-emissions-on-the-hub) qui vous montrera comment faire pour générer un fichier `emissions.csv` comportant une estimation de l’empreinte de votre entraînement, ainsi que la [documentation](https://huggingface.co/docs/hub/model-cards-co2) de 🤗 Transformers abordant ce thème. + + ## L'apprentissage par transfert diff --git a/chapters/fr/chapter7/3.mdx b/chapters/fr/chapter7/3.mdx index 32b1fbf44..675965901 100644 --- a/chapters/fr/chapter7/3.mdx +++ b/chapters/fr/chapter7/3.mdx @@ -59,7 +59,7 @@ Pour commencer, nous allons choisir un modèle pré-entraîné approprié pour l Hub models. -Bien que les modèles de la famille BERT et RoBERTa soient les plus téléchargés, nous utiliserons un modèle appelé [DistilBERT](https://huggingface.co/distilbert-base-uncased) qui peut être entraîné beaucoup plus rapidement avec peu ou pas de perte de performance en aval. Ce modèle a été entraîné à l'aide d'une technique spéciale appelée [_distillation de connaissances_](https://en.wikipedia.org/wiki/Knowledge_distillation), où un grand modèle *enseignant* comme BERT est utilisé pour guider l'entraînement d'un modèle *étudiant* qui a beaucoup moins de paramètres. Une explication des détails de la distillation de connaissances nous mènerait trop loin dans cette section mais si vous êtes intéressé, vous pouvez lire tout cela dans le livre [_Natural Language Processing with Transformers_](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/ch05.html). +Bien que les modèles de la famille BERT et RoBERTa soient les plus téléchargés, nous utiliserons un modèle appelé [DistilBERT](https://huggingface.co/distilbert-base-uncased) qui peut être entraîné beaucoup plus rapidement avec peu ou pas de perte de performance en aval. Ce modèle a été entraîné à l'aide d'une technique spéciale appelée [_distillation de connaissances_](https://en.wikipedia.org/wiki/Knowledge_distillation), où un grand modèle *enseignant* comme BERT est utilisé pour guider l'entraînement d'un modèle *étudiant* qui a beaucoup moins de paramètres. Une explication des détails de la distillation de connaissances nous mènerait trop loin dans cette section mais si vous êtes intéressé, vous pouvez lire tout cela dans le livre [_Natural Language Processing with Transformers_](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). {#if fw === 'pt'} diff --git a/chapters/ja/chapter7/3.mdx b/chapters/ja/chapter7/3.mdx index e36e0d6cf..b550203cc 100644 --- a/chapters/ja/chapter7/3.mdx +++ b/chapters/ja/chapter7/3.mdx @@ -60,7 +60,7 @@ BERT と RoBERTa モデルのファミリーが最もダウンロードされて このモデルは、[_知識蒸留_](https://en.wikipedia.org/wiki/Knowledge_distillation)と呼ばれる特別な技術を使用して訓練されました。この手法は、BERTのような大きな「教師モデル」が、それよりはるかに少ないパラメータを持つ「生徒モデル」の訓練を導くために使用されています。 -知識蒸溜の詳細を説明すると、この章の内容から離れすぎてしまいますが、もし興味があれば、[_Natural Language Processing with Transformers_](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/ch05.html) (通称Transformers教科書)でそれについてすべて読むことができます。 +知識蒸溜の詳細を説明すると、この章の内容から離れすぎてしまいますが、もし興味があれば、[_Natural Language Processing with Transformers_](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/)(通称Transformers教科書)でそれについてすべて読むことができます。 {#if fw === 'pt'} diff --git a/chapters/vi/chapter7/3.mdx b/chapters/vi/chapter7/3.mdx index 38e775401..96d819c08 100644 --- a/chapters/vi/chapter7/3.mdx +++ b/chapters/vi/chapter7/3.mdx @@ -56,7 +56,7 @@ Cùng đi sâu vào thôi! Mặc dù dòng mô hình BERT và RoBERTa được tải xuống nhiều nhất, chúng ta sẽ sử dụng mô hình có tên [DistilBERT](https://huggingface.co/distilbert-base-uncased) -có thể huấn luyện nhanh hơn nhiều mà ít hoặc không bị mất hiệu suất. Mô hình này được huấn luyện bằng cách sử dụng một kỹ thuật đặc biệt có tên là [_knowledge distillation_](https://en.wikipedia.org/wiki/Knowledge_distillation), trong đó một "mô hình giáo viên" lớn như BERT được sử dụng để hướng dẫn huấn luyện "mô hình sinh viên" có ít tham số hơn nhiều. Phần giải thích chi tiết về quá trình chắt lọc kiến ​​thức sẽ đưa chúng ta đi quá xa trong phần này, nhưng nếu bạn quan tâm, bạn có thể đọc tất cả về nó trong [_Natural Language Processing with Transformers_](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/ch05.html) (thường được gọi là sách giáo khoa về Transformer). +có thể huấn luyện nhanh hơn nhiều mà ít hoặc không bị mất hiệu suất. Mô hình này được huấn luyện bằng cách sử dụng một kỹ thuật đặc biệt có tên là [_knowledge distillation_](https://en.wikipedia.org/wiki/Knowledge_distillation), trong đó một "mô hình giáo viên" lớn như BERT được sử dụng để hướng dẫn huấn luyện "mô hình sinh viên" có ít tham số hơn nhiều. Phần giải thích chi tiết về quá trình chắt lọc kiến ​​thức sẽ đưa chúng ta đi quá xa trong phần này, nhưng nếu bạn quan tâm, bạn có thể đọc tất cả về nó trong [_Natural Language Processing with Transformers_](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) (thường được gọi là sách giáo khoa về Transformer). {#if fw === 'pt'} diff --git a/chapters/zh-CN/chapter7/3.mdx b/chapters/zh-CN/chapter7/3.mdx index a219af5f9..abf328d1f 100644 --- a/chapters/zh-CN/chapter7/3.mdx +++ b/chapters/zh-CN/chapter7/3.mdx @@ -57,7 +57,7 @@ 尽管 BERT 和 RoBERTa 系列模型的下载量最大, 但我们将使用名为 [DistilBERT](https://huggingface.co/distilbert-base-uncased)的模型。 -可以更快地训练, 而下游性能几乎没有损失。这个模型使用一种称为[_知识蒸馏_](https://en.wikipedia.org/wiki/Knowledge_distillation)的特殊技术进行训练, 其中使用像 BERT 这样的大型“教师模型”来指导参数少得多的“学生模型”的训练。在本节中对知识蒸馏细节的解释会使我们离题太远, 但如果你有兴趣, 可以阅读所有相关内容 [_Natural Language Processing with Transformers_](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/ch05.html) (俗称Transformers教科书)。 +可以更快地训练, 而下游性能几乎没有损失。这个模型使用一种称为[_知识蒸馏_](https://en.wikipedia.org/wiki/Knowledge_distillation)的特殊技术进行训练, 其中使用像 BERT 这样的大型“教师模型”来指导参数少得多的“学生模型”的训练。在本节中对知识蒸馏细节的解释会使我们离题太远, 但如果你有兴趣, 可以阅读所有相关内容 [_Natural Language Processing with Transformers_](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) (俗称Transformers教科书)。 {#if fw === 'pt'} diff --git a/subtitles/README.md b/subtitles/README.md index 071b13c63..53d87db37 100644 --- a/subtitles/README.md +++ b/subtitles/README.md @@ -26,4 +26,17 @@ Some languages like Simplified Chinese have a different YouTube language code (` python utils/generate_subtitles.py --language zh-CN --youtube_language_code zh-Hans ``` -Once you have the `.srt` files you can manually fix any translation errors and then open a pull request with the new files. \ No newline at end of file +Once you have the `.srt` files you can manually fix any translation errors and then open a pull request with the new files. + +# How to convert bilingual subtitle to monolingual subtitle + +# Logic + +The english caption line is conventionally placed at the last line of each subtitle block in srt files. So removing the last line of each subtitle block would make the bilingual subtitle a monolingual subtitle. + +# Usage +> python3 convert_bilingual_monolingual.py -i \ -o \ + +**Example** +* For instance, the input file name is "test.cn.en.srt", and you name your output file as "output_test.cn.srt" * +> python3 convert_bilingual_monolingual.py -i test.cn.en.srt -o output_test.cn.srt \ No newline at end of file diff --git a/subtitles/en/59_data-processing-for-translation.srt b/subtitles/en/59_data-processing-for-translation.srt index aaddd1f56..65298ffb3 100644 --- a/subtitles/en/59_data-processing-for-translation.srt +++ b/subtitles/en/59_data-processing-for-translation.srt @@ -239,7 +239,7 @@ or use it in the to_tf_dataset method 54 00:02:15,960 --> 00:02:18,560 before using model.fit() -on your (indistinct) model. +on your Keras model. 55 00:02:21,057 --> 00:02:23,724 diff --git a/subtitles/fr/00_welcome-to-the-hugging-face-course.srt b/subtitles/fr/00_welcome-to-the-hugging-face-course.srt new file mode 100644 index 000000000..73960fe7a --- /dev/null +++ b/subtitles/fr/00_welcome-to-the-hugging-face-course.srt @@ -0,0 +1,155 @@ +1 +0:00:05.000 --> 0:00:07.000 +Bienvenue au cours d'Hugging Face. + +2 +0:00:07.000 --> 0:00:12.559 +Ce cours a été conçu pour vous enseigner tout ce qu'il faut savoir à propos de l'écosystème d'Hugging Face. + +3 +0:00:12.559,0:00:18.080 +Comment utiliser le Hub de jeux de données et de modèles ainsi que toutes nos bibliothèques open source. + +4 +0:00:18.080 --> 0:00:24.960 +Voici la table des matières. Comme vous pouvez le voir, elle est divisée en trois sections qui deviennent progressivement plus avancées. + +5 +0:00:24.960 --> 0:00:28.320 +A ce stade, les deux premières sections ont été mises en ligne. + +6 +0:00:28.320 --> 0:00:36.800 +La première vous apprendra les bases sur comment utiliser un transformer finetuné sur votre propre jeu de données et partager le résultat avec la communauté. + +7 +0:00:36.800 --> 0:00:42.079 +La deuxième est une plongée plus profonde dans nos bibliothèques et vous apprendra à aborder n'importe quelle tâche de NLP. + +8 +0:00:42.079,0:00:48.320 +Nous travaillons activement sur la dernière partie et nous espérons qu'elle sera prête pour le printemps 2022. + +9 +0:00:48.320 --> 0:00:57.840 +Le premier chapitre ne requiert aucune connaissance et constitue une bonne introduction pour apprendre ce que les modèles de transformers peuvent faire et comment ils peuvent vous être utiles, à vous ou à votre entreprise. + +10 +0:00:57.840 --> 0:01:04.159 +Les chapitres suivants nécessitent une bonne connaissance de Python et quelques notions de base de l'apprentissage automatique et de l'apprentissage profond. + +11 +0:01:04.159,0:01:09.840 +Si vous ne savez pas ce qu'un entraînement et une validation sont ou encore ce qu'une descente de gradient signifie, + +12 +0:01:09.840 --> 0:01:16.000 +vous devriez regarder un cours d'introduction tels que ceux publiés par deeplearning.ai ou fast.ai. + +13 +0:01:16.000 --> 0:01:20.960 +Il est également préférable que vous ayez quelques notions de base dans un framework d'apprentissage profond : PyTorch ou TensorFlow. + +14 +0:01:20.960 --> 0:01:29.280 +Chaque partie abordée dans ce cours a une version dans ces deux frameworks. Vous pourrez donc choisir celui avec lequel vous êtes le plus à l'aise. + +15 +0:01:29.280 --> 0:01:37.119 +Voici l'équipe qui a développé ce cours. Je vais maintenant laisser chacun des intervenants se présenter brièvement. + +16 +0:01:37.119,0:01:41.000 +Bonjour, je m'appelle Matthew et je suis ingénieur en apprentissage machine chez Hugging Face. + +17 +0:01:41.000 --> 0:01:47.119 +Je travaille dans l'équipe open source et je suis responsable de la maintenance en particulier des codes en TensorFlow. + +18 +0:01:47.119,0:01:52.960 +Auparavant, j'étais ingénieur en apprentissage automatique chez Parse.ly qui a récemment été acquis par Automattic. + +19 +0:01:52.960 --> 0:02:02.240 +Avant cela j'étais chercheur en post-doc au Trinity College Dublin en Irlande, travaillant sur la génétique computationnelle et les maladies de la rétine. + +20 +0:02:02.240 --> 0:02:08.479 +Bonjour, je suis Lysandre, je suis ingénieur en apprentissage automatique chez Hugging Face et je fais spécifiquement partie de l'équipe open source. + +21 +0:02:08.479,0:02:18.080 +Je suis à Hugging Face depuis quelques années maintenant et aux côtés des membres de mon équipe j'ai travaillé sur la plupart des outils que vous verrez dans ce cours. + +22 +0:02:18.080 --> 0:02:25.599 +Bonjour, je m'appelle Sylvain, je suis ingénieur de recherche chez Hugging Face et l'un des principaux mainteneurs de la bibliothèque Transformers. + +23 +0:02:25.599,0:02:32.000 +Auparavant, j'ai travaillé chez Fast.ai où j'ai aidé à développer la bibliothèque Fastai ainsi que le livre en ligne. + +24 +0:02:32.000 --> 0:02:38.400 +Avant cela, j'étais professeur de mathématiques et d'informatique en France. + +25 +0:02:38.400 --> 0:02:46.080 +Bonjour, je m'appelle Sasha et je suis chercheur à Hugging Face. Je travaille sur les impacts éthiques, environnementaux et sociaux des modèles d'apprentissage automatique. + +26 +0:02:46.080 --> 0:02:54.000 +Auparavant, j'étais postdoctorante au Mila à l'Université de Montréal et j'ai aussi travaillé comme chercheuse en IA appliquée pour le « United Nations Global Pulse ». + +27 +0:02:54.000 --> 0:03:05.040 +J'ai été impliqué dans des projets tels que le code carbone et le calculateur d'impact de l'apprentissage machine pour mesurer l'empreinte carbone de l'apprentissage automatique. + +28 +0:03:05.040 --> 0:03:09.280 +Bonjour, je m'appelle Merve et je suis développeuse « advocate » chez Hugging Face. + +29 +0:03:09.280 --> 0:03:15.200 +Auparavant, je travaillais en tant qu'ingénieure en apprentissage automatique en construisant des outils de NLP et des chatbots. + +30 +0:03:15.200 --> 0:03:21.920 +Actuellement, je travaille à l'amélioration du Hub et à démocratiser l'apprentissage automatique. + +31 +0:03:21.920 --> 0:03:29.360 +Bonjour à tous, je m'appelle Lucille et je suis ingénieure en apprentissage automatique chez Hugging Face. + +32 +0:03:29.360 --> 0:03:34.560 +Pour vous dire en deux phrases qui je suis, je travaille sur le développement et le soutien des + +33 +0:03:34.560 --> 0:03:44.400 +outils open source et je participe également à plusieurs projets de recherche dans le domaine du traitement du langage naturel. + +34 +0:03:44.400 --> 0:03:46.720 +Bonjour à tous, je suis Lewis et je suis ingénieur en apprentissage automatique dans l'équipe open source d'Hugging Face. + +35 +0:03:49.920 --> 0:03:56.640 +Je suis passionné par le développement d'outils pour la communauté NLP et vous me verrez à de nombreuses activités de sensibilisation de Hugging Face. + +36 +0:03:56.640 --> 0:04:07.040 +Avant de rejoindre Hugging Face, j'ai passé plusieurs années à développer des applications d'apprentissage automatique pour des startups et entreprises dans les domaines du NLP, des données topologiques, de l'analyse et des séries temporelles. + +37 +0:04:07.040 --> 0:04:15.680 +Dans une vie antérieure, j'étais physicien théoricien et je faisais des recherches sur les collisions de particules au Grand collisionneur de hadrons (LHC) au CERN. + +38 +0:04:15.680 --> 0:04:20.799 +Je m'appelle Leandro et je suis ingénieur en apprentissage automatique dans le domaine de l'équipe open source d'Hugging Face. + +39 +0:04:20.799,0:04:28.680 +Avant de rejoindre Hugging Face, j'ai travaillé comme data scientist en Suisse et j'ai enseigné la science des données à l'université. \ No newline at end of file diff --git a/subtitles/fr/01_the-pipeline-function.srt b/subtitles/fr/01_the-pipeline-function.srt new file mode 100644 index 000000000..8628ceb2d --- /dev/null +++ b/subtitles/fr/01_the-pipeline-function.srt @@ -0,0 +1,195 @@ +1 +0:00:05.680 --> 0:00:09.360 +La fonction pipeline. + +2 +0:00:09.360 --> 0:00:13.840 +La fonction pipeline est l'API de plus haut niveau de la bibliothèque Transformers. + +3 +0:00:13.840 --> 0:00:20.960 +Elle regroupe toutes les étapes pour passer des textes bruts à des prédictions utilisables. Le modèle utilisé + +4 +0:00:20.960 --> 0:00:26.480 +est au cœur d'un pipeline mais ce dernier comprend également tous les pré-traitements nécessaires + +5 +0:00:26.480 --> 0:00:30.000 +(puisque le modèle n'attend pas des textes, mais des chiffres) + +6 +0:00:30.000 --> 0:00:35.680 +ainsi qu'un post-traitement pour rendre la sortie du modèle lisible par l'homme. + +7 +0:00:35.680 --> 0:00:41.200 +Prenons un premier exemple avec le pipeline d'analyse des sentiments. Ce pipeline + +8 +0:00:41.200 --> 0:00:47.760 +effectue une classification de texte sur une entrée donnée et détermine si elle est positive ou négative. + +9 +0:00:47.760 --> 0:00:55.440 +Ici, il a attribué l'étiquette positive sur le texte donné, avec une confiance de 95%. + +10 +0:00:55.440 --> 0:00:59.520 +Vous pouvez transmettre plusieurs textes au même pipeline qui seront traités + +11 +0:00:59.520 --> 0:01:05.840 +et passés ensembe dans le modèle, comme un batch. La sortie est une liste de résultats individuels, + +12 +0:01:05.840 --> 0:01:12.080 +dans le même ordre que les textes en entrée. Ici, nous trouvons la même étiquette et le même score pour le premier texte + +13 +0:01:12.080 --> 0:01:18.480 +et le second texte est jugé négatif avec un niveau de confiance de 99,99%. + +14 +0:01:18.480 --> 0:01:23.360 +Le pipeline de classification « zero-shot » est un pipeline de classification de texte plus général + +15 +0:01:23.360 --> 0:01:28.320 +qui permet de fournir les étiquettes que vous voulez. Ici, nous voulons classer notre entrée + +16 +0:01:28.320 --> 0:01:35.360 +textuelle le long des étiquettes « éducation », « politique » et « affaires ». Le pipeline reconnaît avec succès + +17 +0:01:35.360 --> 0:01:41.440 +qu'il s'agit davantage d'« éducation » que des autres étiquettes, avec un taux de confiance de 84%. + +18 +0:01:41.440 --> 0:01:47.200 +Pour passer à d'autres tâches, le pipeline de génération de texte complétera automatiquement un « prompt » donné. + +19 +0:01:47.200 --> 0:01:49.760 +La sortie est générée avec un peu de hasard, + +20 +0:01:49.760 --> 0:01:54.800 +de sorte qu'il change chaque fois que vous appelez l'objet générateur sur un « prompt » donné. + +21 +0:01:54.800 --> 0:02:00.080 +Jusqu'à présent, nous avons utilisé l'API de pipeline avec le modèle par défaut associé à chaque tâche, + +22 +0:02:00.080 --> 0:02:06.320 +mais vous pouvez l'utiliser avec n'importe quel modèle qui a été pré-entraîné ou finetuné pour cette tâche. + +23 +0:02:06.320 --> 0:02:10.080 +En allant sur le Hub des modèles (https://huggingface.co/models), + +24 +0:02:10.080 --> 0:02:14.320 +vous pouvez filtrer les modèles disponibles par tâche. Le modèle par défaut utilisé dans notre + +25 +0:02:14.320 --> 0:02:21.280 +exemple précédent était le GPT-2, mais il existe de nombreux autres modèles disponibles, et pas seulement en anglais ! + +26 +0:02:21.280 --> 0:02:27.120 +Revenons au pipeline de génération de texte et chargeons-le avec un autre modèle, le distilgpt2. C'est + +27 +0:02:27.120 --> 0:02:33.120 +une version allégée de GPT-2 créée par l'équipe d'Hugging Face. Lorsque l'on applique le pipeline à un + +28 +0:02:33.120 --> 0:02:39.280 +« prompt », nous pouvons spécifier plusieurs arguments, tels que la longueur maximale des textes générés, ou + +29 +0:02:39.280 --> 0:02:45.920 +le nombre de phrases que l'on veut retourner (puisqu'il y a une part d'aléatoire dans la génération). + +30 +0:02:45.920 --> 0:02:51.200 +Générer du texte en devinant le mot suivant dans une phrase était l'objectif de pré-entraînement de GPT-2, + +31 +0:02:51.200 --> 0:02:54.880 +le pipeline de masques de remplissage est l'objectif de pré-entraînement de BERT + +32 +0:02:54.880 --> 0:03:00.960 +consiste à deviner la valeur du mot masqué. Dans ce cas, nous demandons les deux valeurs les plus probables pour + +33 +0:03:00.960 --> 0:03:08.000 +les mots manquants (selon le modèle) et obtenons des réponses mathématiques ou informatiques comme réponses possibles. + +34 +0:03:08.000 --> 0:03:12.480 +Une autre tâche que les transformers peuvent effectuer est de classer chaque mot de la phrase + +35 +0:03:12.480 --> 0:03:18.640 +au lieu de la phrase dans son ensemble. La reconnaissance des entités nommées en est un exemple. Il s'agit + +36 +0:03:18.640 --> 0:03:27.200 +de la tâche consistant à identifier des entités, telles que des personnes, des organisations ou des lieux, dans une phrase. Ici, + +37 +0:03:27.200 --> 0:03:30.240 +le modèle trouve correctement la personne (Sylvain), + +38 +0:03:30.240 --> 0:03:37.440 +l'organisation (Hugging Face) ainsi que la localisation (Brooklyn) dans le texte d'entrée. + +39 +0:03:37.440 --> 0:03:42.080 +L'argument « grouped_entities=True » est utilisé pour que le pipeline regroupe + +40 +0:03:42.080 --> 0:03:48.000 +les différents mots liés à une même entité (comme « Hugging » et « Face » ici). + +41 +0:03:48.000 --> 0:03:52.720 +Une autre tâche disponible avec l'API de pipeline est la réponse aux questions. + +42 +0:03:52.720 --> 0:03:57.280 +En fournissant un contexte et une question, le modèle identifiera dans le contexte + +43 +0:03:57.280 --> 0:04:01.440 +l'étendue du texte contenant la réponse à la question. + +44 +0:04:01.440 --> 0:04:03.920 +Obtenir de courts résumés de très longs articles est + +45 +0:04:03.920 --> 0:04:09.360 +aussi quelque chose que la bibliothèque Transformers peut aider, avec le pipeline de résumé. + +46 +0:04:09.360 --> 0:04:15.040 +Enfin, la dernière tâche prise en charge par l'API de pipeline est la traduction. Ici, nous utilisons un + +47 +0:04:15.040 --> 0:04:21.360 +modèle français/anglais trouvé sur le Hub des modèles pour obtenir la traduction en anglais de notre texte d'entrée. + +48 +0:04:21.360 --> 0:04:25.280 +Voici un bref résumé de toutes les tâches que nous avons examinées dans cette vidéo. + +49 +0:04:25.280 --> 0:04:28.280 +Essayez grâce aux widgets d'inférence du Hub des modèles ! \ No newline at end of file diff --git a/subtitles/fr/02_the-carbon-footprint-of-transformers.srt b/subtitles/fr/02_the-carbon-footprint-of-transformers.srt new file mode 100644 index 000000000..a6fd8a380 --- /dev/null +++ b/subtitles/fr/02_the-carbon-footprint-of-transformers.srt @@ -0,0 +1,514 @@ +1 +00:00:05,580 --> 00:00:08,820 +Parlons donc de l'empreinte carbone des transformers. + +2 +00:00:08,820 --> 00:00:10,530 +Vous avez peut-être vu des titres tels que celui-ci + +3 +00:00:10,530 --> 00:00:13,530 +indiquant qu'entraîner un seul modèle d'IA peut entraîner autant d'émissions de CO2 + +4 +00:00:13,530 --> 00:00:16,020 +que cinq voitures dans leur vie. + +5 +00:00:16,020 --> 00:00:19,440 +Alors quand est-ce vrai et est-ce toujours vrai ? + +6 +00:00:19,440 --> 00:00:21,803 +En fait, cela dépend de plusieurs choses. + +7 +00:00:21,803 --> 00:00:23,430 +Le plus important, c'est que cela dépend +8 +00:00:23,430 --> 00:00:24,960 +sur le type d'énergie que vous utilisez. + +9 +00:00:24,960 --> 00:00:26,267 +Si vous utilisez une énergie renouvelable telle que le + +10 +00:00:26,267 --> 00:00:30,670 +solaire, l'éolien, l'hydroélectrique, vous n'éméttez + +11 +00:00:30,670 --> 00:00:33,810 +pas vraiment de carbone du tout. Très, très peu. + +12 +00:00:33,810 --> 00:00:36,769 +Si vous utilisez des sources d'énergie non renouvelables telles que le charbon + +13 +00:00:36,769 --> 00:00:39,570 +alors l'empreinte carbone est beaucoup plus élevée + +14 +00:00:39,570 --> 00:00:43,260 +parce qu'en fait, vous émettez beaucoup de gaz à effet de serre. + +15 +00:00:43,260 --> 00:00:44,670 +Un autre aspect est le temps d'entraînement. + +16 +00:00:44,670 --> 00:00:47,232 +Donc, plus vous entraînez longtemps, plus vous dépensez d'énergie. + +17 +00:00:47,232 --> 00:00:50,250 +Plus vous utilisez d'énergie, plus vous émettez de carbone. + +18 +00:00:50,250 --> 00:00:51,270 +Donc, cela s'additionne. + +19 +00:00:51,270 --> 00:00:53,520 +Surtout si vous entraînez de grands modèles + +20 +00:00:53,520 --> 00:00:56,460 +pendant des heures, des jours et des semaines. + +21 +00:00:56,460 --> 00:00:58,380 +Le matériel que vous utilisez a également son importance + +22 +00:00:58,380 --> 00:01:00,930 +car certains GPU, par exemple, sont plus efficaces + +23 +00:01:00,930 --> 00:01:05,460 +que d'autres et donc utiliser des GPU + +24 +00:01:05,460 --> 00:01:07,500 +efficiement, correctement, à 100% tout le temps, + +25 +00:01:07,500 --> 00:01:10,650 +peut vraiment réduire la consommation d'énergie que vous avez. + +26 +00:01:10,650 --> 00:01:13,290 +Et encore une fois, réduire votre empreinte carbone. + +27 +00:01:13,290 --> 00:01:15,870 +Il y a aussi d'autres aspects comme l'IO + +28 +00:01:15,870 --> 00:01:17,730 +comme les données, etc., etc. + +29 +00:01:17,730 --> 00:01:20,940 +Mais ce sont les trois principaux sur lesquels vous devez vous concentrer. + +30 +00:01:20,940 --> 00:01:23,340 +Donc quand je parle de sources d'énergie et d'intensité de carbone + +31 +00:01:23,340 --> 00:01:24,420 +Qu'est-ce que cela signifie vraiment ? + +32 +00:01:24,420 --> 00:01:27,480 +Donc si vous regardez en haut de l'écran + +33 +00:01:27,480 --> 00:01:30,480 +vous avez une empreinte carbone + +34 +00:01:30,480 --> 00:01:33,860 +d'une instance de cloud computing à Mumbai en Inde + +35 +00:01:33,860 --> 00:01:38,700 +qui émet 920 grammes de CO2 par kilowattheure. + +36 +00:01:38,700 --> 00:01:40,110 +C'est presque un kilo + +37 +00:01:40,110 --> 00:01:43,680 +de CO2 par kilowattheure d'électricité utilisé. + +38 +00:01:43,680 --> 00:01:45,150 +Si vous comparez cela avec Montréal au Canada, + +39 +00:01:45,150 --> 00:01:48,720 +où je suis en ce moment, 20 grammes de CO2 par kilo heure. + +40 +00:01:48,720 --> 00:01:50,040 +C'est donc une très, très grande différence. + +41 +00:01:50,040 --> 00:01:54,240 +Près de 40 fois plus de carbone émis + +42 +00:01:54,240 --> 00:01:55,950 +à Mumbai qu'à Montréal. + +43 +00:01:55,950 --> 00:01:57,720 +Et donc cela peut vraiment, vraiment s'accumuler. + +44 +00:01:57,720 --> 00:01:59,820 +Si vous entraînez un modèle pendant plusieurs semaines, par exemple + +45 +00:01:59,820 --> 00:02:01,920 +vous multipliez par 40 + +46 +00:02:01,920 --> 00:02:03,450 +le carbone que vous émettez. + +47 +00:02:03,450 --> 00:02:05,070 +Donc, choisir la bonne instance, + +48 +00:02:05,070 --> 00:02:07,080 +choisir une instance de calcul à faible émission de carbone, + +49 +00:02:07,080 --> 00:02:09,690 +est vraiment la chose la plus importante que vous puissiez faire. + +50 +00:02:09,690 --> 00:02:13,020 +Et c'est là que cela peut vraiment s'accumuler + +51 +00:02:13,020 --> 00:02:15,930 +si vous vous entrainez de manière très intensive + +52 +00:02:15,930 --> 00:02:17,580 +dans une région à forte intensité de carbone. + +53 +00:02:19,170 --> 00:02:21,750 +D'autres éléments à prendre en compte, par exemple + +54 +00:02:21,750 --> 00:02:22,770 +utiliser des modèles pré-entraînés. + +55 +00:02:22,770 --> 00:02:25,590 +C'est l'équivalent du recyclage pour l'apprentissage automatique. + +56 +00:02:25,590 --> 00:02:28,292 +Lorsque vous disposez de modèles pré-entraînés, vous pouvez les utiliser. + +57 +00:02:28,292 --> 00:02:30,120 +Vous n'émettez pas de carbone du tout. + +58 +00:02:30,120 --> 00:02:31,230 +Vous ne ré-entraînez rien. + +59 +00:02:31,230 --> 00:02:33,450 +Donc c'est aussi faire ses devoirs + +60 +00:02:33,450 --> 00:02:35,574 +de regarder ce qui existe déjà. + +61 +00:02:35,574 --> 00:02:37,890 +Finetuner au lieu d'entraîner à partir de zéro. + +62 +00:02:37,890 --> 00:02:38,723 +Donc, une fois de plus, + +63 +00:02:38,723 --> 00:02:40,590 +si vous trouvez un modèle qui correspond presque à ce dont vous avez besoin + +64 +00:02:40,590 --> 00:02:43,530 +mais pas tout à fait, finetunez les dernières couches + +65 +00:02:43,530 --> 00:02:45,210 +pour qu'il corresponde vraiment à votre objectif. + +66 +00:02:45,210 --> 00:02:46,500 +au lieu d'entrâiner un gros transformer + +67 +00:02:46,500 --> 00:02:48,810 +à partir de zéro. Cela peut vraiment aider. + +68 +00:02:48,810 --> 00:02:51,270 +Commencer par de petites expériences + +69 +00:02:51,270 --> 00:02:52,800 +et déboguer au fur et à mesure. + +70 +00:02:52,800 --> 00:02:54,630 +Cela signifie, par exemple, + +71 +00:02:54,630 --> 00:02:58,770 +comprendre l'encodage des données, + +72 +00:02:58,770 --> 00:03:01,170 +s'assurer qu'il n'y a pas de petits bugs, que vous allez + +73 +00:03:01,170 --> 00:03:03,840 +réalisez, après 16 heures d'entraînement. + +74 +00:03:03,840 --> 00:03:05,820 +Commencer petit et vraiment s'assurer + +75 +00:03:05,820 --> 00:03:08,760 +de ce que vous faites, que votre code est stable. + +76 +00:03:08,760 --> 00:03:11,430 +Et enfin, faire une revue de la littérature pour + +77 +00:03:11,430 --> 00:03:13,740 +choisir des plages d'hyperparamètres pour ensuite poursuivre + +78 +00:03:13,740 --> 00:03:15,900 +avec une recherche aléatoire au lieu d'une recherche par grille. + +79 +00:03:15,900 --> 00:03:18,420 +Les recherches de combinaisons d'hyperparamètres + +80 +00:03:18,420 --> 00:03:21,300 +aléatoires se sont avérés être aussi efficaces + +81 +00:03:21,300 --> 00:03:24,000 +pour trouver la configuration optimale, que la recherche par grille. + +82 +00:03:24,000 --> 00:03:27,510 +Mais évidemment, vous n'essayez pas toutes les combinaisons possibles, + +83 +00:03:27,510 --> 00:03:29,520 +vous n'en essayez qu'un sous-ensemble. + +84 +00:03:29,520 --> 00:03:31,800 +Cela peut donc être très utile. + +85 +00:03:31,800 --> 00:03:32,760 +Donc maintenant, nous revenons + +86 +00:03:32,760 --> 00:03:36,300 +à l'article original de Strubell et al. de 2019, + +87 +00:03:36,300 --> 00:03:39,180 +l'infâme papier sur la vie des cinq voitures. + +88 +00:03:39,180 --> 00:03:40,013 +Si vous regardez + +89 +00:03:40,013 --> 00:03:43,606 +un transformer de 200 millions de paramètres, + +90 +00:03:43,606 --> 00:03:46,950 +son empreinte carbone est d'environ 200 livres [87 kg] de CO2, + +91 +00:03:46,950 --> 00:03:47,940 +ce qui est important. + +92 +00:03:47,940 --> 00:03:49,980 +Mais c'est loin des cinq voitures. + +93 +00:03:49,980 --> 00:03:52,893 +Ce n'est même pas un vol transatlantique. + +94 +00:03:52,893 --> 00:03:55,020 +Ce qui compte vraiment, c'est lorsque vous faites + +95 +00:03:55,020 --> 00:03:56,190 +vos recherches d'architectures neuronales, + +96 +00:03:56,190 --> 00:03:58,560 +quand vous faites le réglage des hyperparamétres, + +97 +00:03:58,560 --> 00:04:00,930 +en essayant toutes les combinaisons possibles, + +98 +00:04:00,930 --> 00:04:01,763 +etc., etc. + +99 +00:04:01,763 --> 00:04:02,596 +Et c'est là que + +100 +00:04:02,596 --> 00:04:05,400 +d'où proviennent les 600 000 livres [272,16 t] de CO2. + +101 +00:04:05,400 --> 00:04:08,490 +C'est donc là que les choses s'additionnent. + +102 +00:04:08,490 --> 00:04:11,880 +Mais si vous faites les choses consciemment et consciencieusement, + +103 +00:04:11,880 --> 00:04:16,410 +alors votre empreinte carbone ne sera pas aussi importante, + +104 +00:04:16,410 --> 00:04:20,040 +comme le laissait entendre le papier. Quelques outils + +105 +00:04:20,040 --> 00:04:22,111 +pour savoir combien de CO2 exactement vous émettez. + +106 +00:04:22,111 --> 00:04:24,270 +Il y a un outil en ligne appelé « machine + +107 +00:04:24,270 --> 00:04:26,430 +learning submissions calculator » qui vous permet + +108 +00:04:26,430 --> 00:04:29,010 +de saisir manuellement, par exemple, le matériel que vous avez utilisé, + +109 +00:04:29,010 --> 00:04:30,488 +le nombre d'heures pendant lesquelles vous l'avez utilisé, + +110 +00:04:30,488 --> 00:04:34,260 +où il était situé : localement ou dans le cloud. + +111 +00:04:34,260 --> 00:04:35,640 +Et puis il va vous donner une estimation + +112 +00:04:35,640 --> 00:04:37,560 +de la quantité de CO2 que vous avez émise. + +113 +00:04:37,560 --> 00:04:40,200 +Un autre outil qui fait cela de manière programmatique, + +114 +00:04:40,200 --> 00:04:41,190 +s'appelle codecarbon. + +115 +00:04:41,190 --> 00:04:45,112 +Vous pouvez l'installer avec pip, vous pouvez aller sur leur GitHub, + +116 +00:04:45,112 --> 00:04:48,120 +et essentiellement il s'exécute en parallèle de votre code. + +117 +00:04:48,120 --> 00:04:49,085 +Donc, vous l'appelez + +118 +00:04:49,085 --> 00:04:51,060 +et ensuite vous faites tous vos entraînements. + +119 +00:04:51,060 --> 00:04:53,760 +Et puis à la fin, il va vous donner une estimation : + +120 +00:04:53,760 --> 00:04:57,210 +un fichier CSV contenant une estimation de vos émissions. + +121 +00:04:57,210 --> 00:04:59,250 +Et ça va vous permettre de faire des comparaisons. + +122 +00:04:59,250 --> 00:05:01,230 +Il y a une interface visuelle où vous pouvez + +123 +00:05:01,230 --> 00:05:04,680 +comparer avec la conduite d'une voiture ou la télévision. + +124 +00:05:04,680 --> 00:05:06,060 +Ainsi, cela peut vous donner une idée + +125 +00:05:06,060 --> 00:05:07,740 +de la portée de vos émissions également. + +126 +00:05:07,740 --> 00:05:09,930 +Et en fait, codecarbon est déjà intégré dans AutoNLP. + +127 +00:05:09,930 --> 00:05:12,270 +et j'espère que les gens l'utiliseront + +128 +00:05:12,270 --> 00:05:15,240 +pour suivre facilement leurs émissions tout au long + +129 +00:05:15,240 --> 00:05:17,523 +de l'entraînement et le déploiement des transformers. \ No newline at end of file diff --git a/subtitles/fr/03_what-is-transfer-learning.srt b/subtitles/fr/03_what-is-transfer-learning.srt new file mode 100644 index 000000000..05849f178 --- /dev/null +++ b/subtitles/fr/03_what-is-transfer-learning.srt @@ -0,0 +1,131 @@ +0:00:05.440,0:00:09.360 +Qu'est-ce que l'apprentissage par transfert ? + +0:00:09.360,0:00:15.320 +L'idée de l'apprentissage par transfert est d'exploiter les connaissances requises par un modèle entraîné avec beaucoup de données sur une autre tâche. + +0:00:15.320,0:00:19.840 +Le modèle A est entraîné spécifiquement pour la tâche A. + +0:00:19.840,0:00:23.760 +Supposons maintenant que vous vouliez entraîner le modèle B pour une autre tâche. + +0:00:23.760,0:00:27.119 +Une option serait d'entraîner le modèle à partir de zéro. + +0:00:27.119,0:00:31.279 +Cela pourrait demander beaucoup de temps de calcul et de données. + +0:00:31.279,0:00:36.399 +Au lieu de cela, nous pourrions initialiser le modèle B avec les mêmes poids que le modèle A. + +0:00:36.399,0:00:40.800 +Transférer les connaissances du modèle A sur la tâche B. + +0:00:40.800,0:00:45.680 +Lors de l'entraînement à partir de zéro, tous les poids des modèles sont initialisés de façon aléatoire. + +0:00:45.680,0:00:53.680 +Dans cet exemple, nous entraînons un modèle BERT à reconnaître si deux phrases sont similaires ou non. + +0:00:53.680,0:00:56.480 +A gauche, il est entraîné à partir de zéro. + +0:00:56.480,0:00:59.920 +Et à droite, c'est un modèle pré-entraîné finetuné. + +0:00:59.920,0:01:04.360 +Comme nous pouvons le constater, l'utilisation de l'apprentissage par transfert sur le modèle pré-entraîné donne de meilleurs résultats. + +0:01:04.360,0:01:06.159 +Et ce n'est même pas important si vous entraînez plus longtemps. + +0:01:06.159,0:01:14.240 +L'entraînement à partir de zéro est maintenu autour de 70% de précision alors que le modèle de ré-entraîné bat facilement les 86%. + +0:01:14.240,0:01:24.240 +En effet, les modèles pré-entraînés sont généralement entraînés sur de grandes quantités de données, mais fournissent un modèle avec une compréhension statistique de la langue utilisée pendant le pré-entraînement. + +0:01:24.240,0:01:30.000 +Dans le domaine de la vision par ordinateur, l'apprentissage par transfert est appliqué avec succès depuis près de 10 ans. + +0:01:30.000,0:01:36.880 +Les modèles sont fréquemment ré-entraînés sur ImageNet, un jeu de données contenant 1,2 million d'images photographiques. + +0:01:36.880,0:01:40.880 +Chaque image est classée par l'une des 1000 étiquettes suivantes. + +0:01:40.880,0:01:46.399 +L'entraînement de ce type sur des données étiquetées est appelé apprentissage supervisé. + +0:01:44.399,0:01:51.500 +Dans le traitement du langage naturel, l'apprentissage par transfert est un peu plus récent. + +0:01:51.500,0:01:54.000 +Une différence essentielle avec ImageNet est que l'entraînement est généralement autosupervisé. + +0:01:54.000,0:02:00.479 +Ce qui signifie les étiquettes ne nécessite pas d'annotation humaine. + +0:02:00.479,0:02:05.040 +Un objectif de pré-entraînement très courant consiste à deviner le mot suivant dans une phrase. + +0:02:05.040,0:02:06.860 +Ce qui ne demande que beaucoup, beaucoup de textes. + +0:02:06.860,0:02:14.000 +Le GPT-2, par exemple, a été pré-entraîné de cette manière en utilisant le contenu de 45 millions de liens postés par des utilisateurs sur Reddit. + +0:02:14.000,0:02:21.000 +Un autre exemple d'objectif d'entraînement autosupervisé est de prédire la valeur des mots masqués au hasard. + +0:02:20.879,0:02:25.000 +Ce qui est similaire aux tests de « remplissage des trous » que vous avez pu faire à l'école. + +0:02:25.000,0:02:32.959 +BERT a été pré-entrainé de cette manière en utilisant la version anglaise de Wikipedia et 11 000 livres non publiés. + +0:02:32.959,0:02:36.239 +En pratique, l'apprentissage par transfert est appliqué sur un modèle donné + +0:02:36.239,0:02:42.120 +en jetant sa tête, c'est-à-dire ses dernières couches, en se concentrant sur l'objectif de pré-entraînement + +0:02:42.120,0:02:47.760 +et la remplacer par une nouvelle tête initialisée aléatoirement et adaptée à la tâche souhaitée. + +0:02:47.760,0:02:56.560 +Par exemple, lorsque nous avons finetuné le BERT, nous avons supprimé les mots masqués classés par la tête et les avons remplacés par un classifieur à deux sorties. + +0:02:56.560,0:02:59.650 +Puisque nos tâches ajoutent deux étiquettes. + +0:02:59.650,0:03:06.080 +Pour être le plus efficace possible, le modèle pré-entraîné utilisé doit être aussi similaire que possible à la tâche sur laquelle il est finetuné. + +0:03:06.080,0:03:14.159 +Par exemple, si le problème est de classer des phrases en allemand, il est préférable d'utiliser un modèle allemand pré-entraîné. + +0:03:14.159,0:03:16.400 +Mais avec le bon vient le mauvais. + +0:03:16.400,0:03:22.319 +Le modèle pré-entraîné ne transfère pas seulement ses connaissances mais aussi les préjugés qu'il peut contenir. + +0:03:22.319,0:03:24.400 +ImageNet contient principalement des images provenant des États-Unis et de l'Europe occidentale. + +0:03:26.560,0:03:31.519 +Les modèles finetunés avec ce système sont généralement plus performants sur les images provenant de ces pays. + +0:03:31.519,0:03:35.840 +OpenAI a également étudié le biais de prédiction de son modèle GPT-3 + +0:03:35.840,0:03:39.519 +qui a été pré-entrainé en utilisant l'objectif de deviner le mot suivant. + +0:03:39.5190:03:50.000 +En changeant le genre du prompt de « Il était très » à « Elle était très », les prédictions majoritairement neutres sont devenues presque uniquement axées sur le physique. + +0:03:50.000,0:03:59.640 +Dans la carte de modèle du GPT-2, OpenAI reconnaît également son caractère biaisé et déconseille son utilisation dans les systèmes qui interagissent avec les humains. \ No newline at end of file diff --git a/subtitles/fr/04_the-transformer-architecture.srt b/subtitles/fr/04_the-transformer-architecture.srt new file mode 100644 index 000000000..b51d77aec --- /dev/null +++ b/subtitles/fr/04_the-transformer-architecture.srt @@ -0,0 +1,111 @@ +1 +00:00:04,960 --> 00:00:07,120 +Étudions l'architecture du transformer. + +2 +00:00:08,960 --> 00:00:13,840 +Cette vidéo est la vidéo de présentation de la série de vidéos sur les encodeurs, les décodeurs et les encodeurs-décodeurs. + +3 +00:00:13,840 --> 00:00:18,640 +Dans cette série, nous essaierons de comprendre ce qui constitue un transformer + +4 +00:00:18,640 --> 00:00:24,720 +et nous essaierons de l'expliquer en termes simples et de haut niveau. Aucune compréhension des réseaux de neurones n'est + +5 +00:00:24,720 --> 00:00:29,840 +nécessaire, seule une compréhension des vecteurs et des tenseurs de base peut être utile. + +6 +00:00:32,320 --> 00:00:36,480 +Pour commencer, nous allons reprendre ce schéma de l'article original sur le transformer, + +7 +00:00:36,480 --> 00:00:42,640 +intitulé « Attention is All You Need ». Comme nous le verrons ici, nous ne pouvons en exploiter que certaines parties, en + +8 +00:00:42,640 --> 00:00:48,080 +fonction de ce que nous essayons de faire. Nous n'aborderons pas les couches spécifiques constituant cette + +9 +00:00:48,080 --> 00:00:52,560 +architecture, mais nous essaierons de comprendre les différentes manières dont cette architecture peut être utilisée. + +10 +00:00:54,960 --> 00:00:59,760 +Commençons par diviser cette architecture en deux parties. À gauche, nous avons l'encodeur, + +11 +00:00:59,760 --> 00:01:04,320 +et à droite, le décodeur. Ces deux éléments peuvent être utilisés ensemble, mais ils peuvent également être utilisés + +12 +00:01:04,320 --> 00:01:11,280 +indépendamment ! Voyons comment cela fonctionne. L'encodeur accepte les entrées qui représentent du texte. + +13 +00:01:11,280 --> 00:01:17,200 +Il convertit ce texte, ces mots, en représentations numériques. Ces représentations numériques + +14 +00:01:17,200 --> 00:01:23,120 +peuvent également être appelées enchâssements ou caractéristiques. Nous verrons qu'il utilise le mécanisme d'auto-attention comme + +15 +00:01:23,120 --> 00:01:29,840 +composant principal. Nous vous recommandons de consulter la vidéo sur les encodeurs en particulier pour comprendre ce qu'est + +16 +00:01:29,840 --> 00:01:36,640 +cette représentation numérique, ainsi que son fonctionnement. Nous étudierons le mécanisme d'auto-attention avec plus de détails + +17 +00:01:36,640 --> 00:01:44,000 +ainsi que ses propriétés bidirectionnelles. Le décodeur est similaire à l'encodeur : il peut aussi accepter + +18 +00:01:44,000 --> 00:01:47,200 +des entrées représentant du texte. Il utilise un mécanisme similaire à + +19 +00:01:47,200 --> 00:01:53,200 +l'encodeur, qui est également l'auto-attention masquée. Il diffère de l'encodeur par sa + +20 +00:01:53,200 --> 00:01:59,200 +propriété unidirectionnelle et est traditionnellement utilisé de manière autorégressive. Là aussi, + +21 +00:01:59,200 --> 00:02:03,600 +nous vous recommandons de consulter la vidéo sur les décodeurs, notamment pour comprendre comment tout cela fonctionne. + +22 +00:02:06,560 --> 00:02:11,120 +La combinaison des deux parties donne ce que l'on appelle un encodeur-décodeur ou un transformer séquence-à-séquence. + +23 +00:02:11,120 --> 00:02:16,640 +L'encodeur accepte les entrées et calcule une représentation de haut niveau de ces + +24 +00:02:16,640 --> 00:02:22,640 +entrées. Ces sorties sont ensuite transmises au décodeur. Le décodeur utilise la sortie de l'encodeur ainsi que + +25 +00:02:22,640 --> 00:02:27,680 +d'autres entrées pour générer une prédiction. Il prédit ensuite une sortie, + +26 +00:02:27,680 --> 00:02:32,000 +qu'il réutilisera dans les itérations futures,  d'où le terme « auto-régressif ». + +27 +00:02:33,040 --> 00:02:36,480 +Enfin, pour comprendre l'encodeur-décodeur dans son ensemble, + +28 +00:02:36,480 --> 00:02:44,080 +nous vous recommandons de regarder la vidéo sur les encodeurs-décodeurs. \ No newline at end of file diff --git a/subtitles/fr/05_transformer-models-encoders.srt b/subtitles/fr/05_transformer-models-encoders.srt new file mode 100644 index 000000000..824b99298 --- /dev/null +++ b/subtitles/fr/05_transformer-models-encoders.srt @@ -0,0 +1,179 @@ +1 +00:00:04,320 --> 00:00:09,120 +Dans cette vidéo, nous allons étudier l'architecture de l'encodeur. Un exemple d'architecture populaire + +2 +00:00:09,120 --> 00:00:13,120 +composée uniquement d'un encodeur est BERT, qui est le modèle le plus populaire de ce type. + +3 +00:00:14,400 --> 00:00:20,880 +Commençons d'abord par comprendre comment cela fonctionne. Nous allons utiliser un petit exemple, en utilisant trois mots. Nous les + +4 +00:00:20,880 --> 00:00:27,040 +utilisons comme entrées et les transmettons via l'encodeur. Nous récupérons une représentation numérique + +5 +00:00:27,040 --> 00:00:34,160 +de chaque mot. Ici, par exemple, l'encodeur convertit les trois mots « Welcome to NYC » + +6 +00:00:34,160 --> 00:00:40,880 +en ces trois séquences de chiffres. L'encodeur génère exactement une séquence de nombres par + +7 +00:00:40,880 --> 00:00:46,880 +mot d'entrée. Cette représentation numérique peut également être appelée « vecteur de caractéristiques » ou « tenseur de caractéristiques ». + +8 +00:00:48,880 --> 00:00:53,680 +Plongeons dans cette représentation. Elle contient un vecteur par mot passé dans l'encodeur. + +9 +00:00:53,680 --> 00:00:59,680 +Chacun de ces vecteurs est une représentation numérique du mot en question. + +10 +00:01:00,880 --> 00:01:06,400 +La dimension de ce vecteur est définie par l'architecture du modèle, pour le modèle BERT de base, + +11 +00:01:06,400 --> 00:01:15,280 +elle est de 768. Ces représentations contiennent la valeur d'un mot mais contextualisé. + +12 +00:01:15,280 --> 00:01:21,280 +Par exemple, le vecteur attribué au mot « to », n'est pas la représentation du seul mot « to ». + +13 +00:01:22,160 --> 00:01:29,680 +Il prend aussi en compte les mots qui l'entourent, que nous appelons le « contexte ». Il regarde le + +14 +00:01:29,680 --> 00:01:34,960 +contexte de gauche, le mot à gauche de celui que nous étudions (ici le mot « Welcome ») et + +15 +00:01:34,960 --> 00:01:41,120 +le context à droite (ici le mot « NYC ») et génère une valeur pour le mot, dans son contexte. + +16 +00:01:41,840 --> 00:01:49,280 +Il s'agit donc d'une valeur contextualisée. On pourrait dire que le vecteur de 768 valeurs contient le + +17 +00:01:49,280 --> 00:01:55,840 +« sens » de ce mot dans le texte. C'est grâce au mécanisme d'auto-attention. + +18 +00:01:57,120 --> 00:02:02,240 +Le mécanisme d'auto-attention concerne différentes positions (ou différents mots) dans une seule + +19 +00:02:02,240 --> 00:02:08,320 +séquence, afin de calculer une représentation de cette séquence. Comme nous l'avons vu précédemment, cela + +20 +00:02:08,320 --> 00:02:13,600 +signifie que la représentation résultante d'un mot a été affectée par d'autres mots dans la séquence. + +21 +00:02:15,600 --> 00:02:20,160 +Nous n'entrerons pas dans les détails ici, mais nous vous proposerons d'autres lectures si vous + +22 +00:02:20,160 --> 00:02:26,480 +souhaitez mieux comprendre ce qui se passe sous le capot. Alors quand faut-il utiliser un encodeur ? + +23 +00:02:27,040 --> 00:02:33,680 +Les encodeurs peuvent être utilisés comme modèles autonomes dans une grande variété de tâches. Par exemple, BERT, sans doute + +24 +00:02:33,680 --> 00:02:38,800 +le modèle de transformer le plus célèbre, est un modèle encodeur et, au moment de sa sortie, + +25 +00:02:38,800 --> 00:02:44,000 +surpassait l'état de l'art dans de nombreuses tâches de classification de séquences, tâches de questions-réponses + +26 +00:02:44,000 --> 00:02:50,240 +et modélisation de langage masqué, pour n'en citer que quelques-unes. L'idée est que les encodeurs sont très puissants + +27 +00:02:50,240 --> 00:02:55,920 +pour extraire des vecteurs contenant des informations significatives sur une séquence. Ce vecteur peut + +28 +00:02:55,920 --> 00:02:59,680 +ensuite être géré par des couches supplémentaires de neurones pour leur donner un sens. + +29 +00:03:01,200 --> 00:03:04,240 +Examinons quelques exemples où les encodeurs brillent vraiment. + +30 +00:03:06,080 --> 00:03:11,760 +Tout d'abord, « Masked Language Modeling » ou MLM. Il s'agit de prédire un mot caché + +31 +00:03:11,760 --> 00:03:18,560 +dans une séquence de mots. Ici, par exemple, nous avons masqué le mot entre « My » et « is ». C'est l'un + +32 +00:03:18,560 --> 00:03:24,000 +des objectifs avec lesquels le BERT a été entraîné : il a été entraîné pour prédire les mots cachés dans une séquence. + +33 +00:03:25,040 --> 00:03:30,160 +Les encodeurs brillent dans ce scénario en particulier car les informations bidirectionnelles sont cruciales ici. + +34 +00:03:30,960 --> 00:03:35,520 +Si nous n'avions pas les mots à droite (« is », « Sylvain. » et « . »), il y a très peu de + +35 +00:03:35,520 --> 00:03:41,200 +chances que BERT ait pu identifier « name » comme le mot correct. L'encodeur doit + +36 +00:03:41,200 --> 00:03:46,720 +avoir une bonne compréhension de la séquence afin de prédire un mot masqué, car même si le texte est + +37 +00:03:46,720 --> 00:03:52,080 +grammaticalement correct, il n'a pas nécessairement de sens dans le contexte de la séquence. + +38 +00:03:54,960 --> 00:03:58,720 +Comme mentionné précédemment, les encodeurs sont efficaces pour effectuer la classification des séquences. + +39 +00:03:59,360 --> 00:04:03,560 +L'analyse des sentiments est un exemple de tâche de classification de séquences. + +40 +00:04:04,240 --> 00:04:11,040 +L'objectif du modèle est d'identifier le sentiment d'une séquence. Cela peut aller de l'attribution + +41 +00:04:11,040 --> 00:04:16,720 +d'une note d'1 à 5 étoiles à une séquence si vous effectuez une analyse d'avis, à l'attribution d'une note positive ou négative + +42 +00:04:16,720 --> 00:04:22,800 +à une séquence, comme c'est le cas ici. Par exemple ici, étant donné les deux séquences, + +43 +00:04:22,800 --> 00:04:28,800 +nous utilisons le modèle pour calculer une prédiction et classer les séquences parmi ces deux classes : + +44 +00:04:28,800 --> 00:04:35,040 +positive et négative. Bien que les deux séquences sont très similaires, contenant les mêmes mots, + +45 +00:04:35,040 --> 00:04:41,840 +la signification est différente et le modèle d'encodeur est capable de saisir cette différence. \ No newline at end of file diff --git a/subtitles/fr/06_transformer-models-decoders.srt b/subtitles/fr/06_transformer-models-decoders.srt new file mode 100644 index 000000000..a35b0d94f --- /dev/null +++ b/subtitles/fr/06_transformer-models-decoders.srt @@ -0,0 +1,167 @@ +1 +00:00:03,860 --> 00:00:09,750 +Dans cette vidéo, nous allons étudier l'architecture du décodeur. Un exemple d'architecture populaire composée uniquement d'un + +2 +00:00:09,750 --> 00:00:15,809 +décodeur est le GPT-2. Afin de comprendre le fonctionnement les décodeurs, nous vous recommandons de regarder la vidéo + +3 +00:00:15,809 --> 00:00:21,640 +concernant les encodeurs : ils sont extrêmement similaires aux décodeurs. On peut utiliser un décodeur pour la plupart + +4 +00:00:21,640 --> 00:00:26,429 +des mêmes tâches qu'un encodeur, mais avec, généralement, une petite perte de performances. + +5 +00:00:26,429 --> 00:00:31,769 +Prenons la même approche que nous avons adoptée avec l'encodeur pour essayer de comprendre les + +6 +00:00:31,769 --> 00:00:38,969 +différences architecturales entre un encodeur et un décodeur. Nous allons utiliser un petit exemple, en utilisant trois mots. + +7 +00:00:38,969 --> 00:00:46,550 +Nous les passons par le décodeur. Nous récupérons une représentation numérique de chaque mot. Ici, + +8 +00:00:46,550 --> 00:00:51,739 +par exemple, le décodeur convertit les trois mots « Welcome to NYC » en ces trois + +9 +00:00:51,739 --> 00:00:57,750 +séquences de chiffres. Le décodeur sort exactement une séquence de nombres par + +10 +00:00:57,750 --> 00:01:05,290 +mot d'entrée. Cette représentation numérique peut également être appelée « vecteur de caractéristiques » ou « tenseur de caractéristiques ». + +11 +00:01:05,290 --> 00:01:09,590 +Plongeons dans cette représentation. Il contient un vecteur par mot passé + +12 +00:01:09,590 --> 00:01:14,830 +par le décodeur. Chacun de ces vecteurs est une représentation numérique du mot + +13 +00:01:14,830 --> 00:01:21,810 +en question. La dimension de ce vecteur est définie par l'architecture du modèle. + +14 +00:01:21,810 --> 00:01:28,400 +Là où le décodeur diffère de l'encodeur, c'est principalement par son mécanisme d'auto-attention. + +15 +00:01:28,400 --> 00:01:34,090 +Il utilise ce qu'on appelle l'auto-attention masquée. Ici par exemple, si nous nous concentrons sur le mot + +16 +00:01:34,090 --> 00:01:40,170 +« to », nous verrons que son vecteur n'est absolument pas modifié par le mot « NYC ». C'est parce que + +17 +00:01:40,170 --> 00:01:45,560 +tous les mots à droite (également appelés le contexte droit) du mot sont masqués. + +18 +00:01:45,560 --> 00:01:50,729 +Plutôt que de bénéficier de tous les mots à gauche et à droite, c'est-à-dire le + +19 +00:01:50,729 --> 00:02:01,229 +contexte bidirectionnel, les décodeurs n'ont accès qu'aux mots à leur gauche. Le mécanisme d'auto-attention masquée + +20 +00:02:01,229 --> 00:02:06,310 +diffère du mécanisme d'auto-attention en utilisant un masque supplémentaire pour masquer + +21 +00:02:06,310 --> 00:02:12,110 +le contexte de chaque côté du mot : la représentation numérique du mot ne sera pas + +22 +00:02:12,110 --> 00:02:18,730 +affectée par les mots dans le contexte masqué. Alors quand faut-il utiliser un décodeur ? Les décodeurs, + +23 +00:02:18,730 --> 00:02:24,610 +comme les encodeurs, peuvent être utilisés comme modèles autonomes. Comme ils génèrent une représentation numérique, + +24 +00:02:24,610 --> 00:02:30,410 +ils peuvent également être utilisés dans une grande variété de tâches. Cependant, la force d'un décodeur + +25 +00:02:30,410 --> 00:02:35,420 +réside dans la manière dont un mot a accès à son contexte gauche. Les décodeurs, n'ayant accès + +26 +00:02:35,420 --> 00:02:40,280 +qu'à leur contexte de gauche, sont intrinsèquement bons pour la génération de texte : la capacité de générer + +27 +00:02:40,280 --> 00:02:46,120 +un mot, ou une séquence de mots, à partir d'une séquence de mots connue. En NLP, c'est ce qu'on appelle la + +28 +00:02:46,120 --> 00:02:52,150 +modélisation causale du langage. Prenons un exemple. Voici un exemple du fonctionnement de la modélisation causale du langage + +29 +00:02:52,150 --> 00:02:59,240 +: nous commençons par un mot initial, qui est « My ». Nous l'utilisons comme entrée pour le + +30 +00:02:59,240 --> 00:03:06,330 +décodeur. Le modèle sort un vecteur de dimension 768. Ce vecteur contient des informations sur + +31 +00:03:06,330 --> 00:03:11,650 +la séquence, qui est ici un seul mot. Nous appliquons une petite transformation à + +32 +00:03:11,650 --> 00:03:17,019 +ce vecteur pour qu'il corresponde à tous les mots connus par le modèle (une association que nous verrons + +33 +00:03:17,019 --> 00:03:22,650 +plus tard, appelée tête de modélisation de langage). Nous identifions que le modèle croit que le + +34 +00:03:22,650 --> 00:03:29,720 +mot suivant le plus probable est « name ». Nous prenons ensuite ce nouveau mot et l'ajoutons à la + +35 +00:03:29,720 --> 00:03:35,560 +séquence initiale. De « Mon », nous sommes maintenant à « Mon nom ». C'est là qu'intervient l'aspect autorégressif. Les + +36 +00:03:35,560 --> 00:03:42,689 +modèles autorégressifs réutilisent leurs sorties passées comme entrées dans les étapes suivantes. + +37 +00:03:42,689 --> 00:03:49,280 +Encore une fois, nous effectuons exactement la même opération : nous analysons cette séquence dans le décodeur + +38 +00:03:49,280 --> 00:03:57,459 +et récupérons le mot suivant le plus probable. Dans ce cas, c'est le mot « is ». Nous répétons + +39 +00:03:57,459 --> 00:04:03,049 +l'opération jusqu'à ce que nous soyons satisfaits. À partir d'un seul mot, nous avons maintenant généré une + +40 +00:04:03,049 --> 00:04:08,870 +phrase complète. Nous décidons de nous arrêter là, mais nous pourrions continuer un moment. Par exemple + +41 +00:04:08,870 --> 00:04:16,919 +le GPT-2 a une taille de contexte maximale de 1024. Nous pourrions donc éventuellement générer jusqu'à 1024 mots, + +42 +00:04:16,919 --> 00:04:20,125 +et le décodeur aurait encore une mémoire des premiers mots de la séquence ! \ No newline at end of file diff --git a/subtitles/fr/07_transformer-models-encoder-decoders.srt b/subtitles/fr/07_transformer-models-encoder-decoders.srt new file mode 100644 index 000000000..3e1e5cafd --- /dev/null +++ b/subtitles/fr/07_transformer-models-encoder-decoders.srt @@ -0,0 +1,255 @@ +1 +00:00:04,000 --> 00:00:07,200 +Dans cette vidéo, nous allons étudier l'architecture encodeur-décodeur. Le T5 + +2 +00:00:08,160 --> 00:00:15,000 +est un exemple de modèle d'encodeur-décodeur populaire. Afin de comprendre le fonctionnement de + +3 +00:00:15,000 --> 00:00:21,680 +l'encodeur-décodeur, nous vous recommandons de consulter les vidéos sur les encodeurs et les décodeurs en tant que modèles autonomes. + +4 +00:00:22,400 --> 00:00:30,320 +Comprendre comment ils se comportent individuellement aidera à comprendre le comportement d'un encodeur-décodeur. + +5 +00:00:30,320 --> 00:00:35,360 +Commençons par ce que nous avons vu à propos de l'encodeur. L'encodeur prend des mots en entrée, les + +6 +00:00:36,000 --> 00:00:40,640 +transfère dans l'encodeur et récupère une représentation numérique + +7 +00:00:40,640 --> 00:00:47,360 +pour chaque mot qui y est diffusé. Nous savons maintenant que la représentation numérique contient des informations + +8 +00:00:47,360 --> 00:00:54,000 +sur la signification de la séquence. Laissons cela de côté et ajoutons le décodeur au schéma. + +9 +00:00:56,480 --> 00:01:00,160 +Dans ce scénario, nous utilisons le décodeur d'une manière inédite. + +10 +00:01:00,720 --> 00:01:07,600 +Nous lui transmettons directement les sorties de l'encodeur. En plus des sorties de l'encodeur, + +11 +00:01:07,600 --> 00:01:13,040 +nous donnons également une séquence au décodeur. Lorsque vous demandez au décodeur une sortie sans + +12 +00:01:13,040 --> 00:01:17,360 +séquence initiale, nous pouvons lui donner la valeur qui indique le début d'une séquence. + +13 +00:01:18,000 --> 00:01:23,520 +Et c'est là que la magie de l'encodeur-décodeur opère. L'encodeur accepte une séquence en entrée. + +14 +00:01:24,560 --> 00:01:30,480 +Il calcule une prédiction et génère une représentation numérique. Ensuite, il + +15 +00:01:30,480 --> 00:01:38,000 +l'envoie au décodeur. Il a, en un sens, encodé la séquence. Et le décodeur, à son tour, + +16 +00:01:38,000 --> 00:01:42,960 +utilisant cette entrée parallèlement à son entrée de séquence habituelle, tentera de décoder la séquence. + +17 +00:01:44,720 --> 00:01:50,400 +Le décodeur décode la séquence et génère un mot. Pour l'instant, nous n'avons pas besoin de donner un sens à + +18 +00:01:50,400 --> 00:01:55,440 +ce mot, mais nous pouvons comprendre que le décodeur décode essentiellement ce que l'encodeur a en + +19 +00:01:55,440 --> 00:02:02,160 +sortie. Le mot de début de séquence indique qu'il doit commencer à décoder la séquence. + +20 +00:02:03,600 --> 00:02:10,240 +Maintenant que nous avons à la fois le vecteur de caractéristiques et un mot généré initial, nous n'avons plus besoin de + +21 +00:02:10,240 --> 00:02:17,760 +l'encodeur. Comme nous l'avons vu précédemment avec le décodeur, il peut agir de manière auto-régressive. + +22 +00:02:18,640 --> 00:02:24,960 +Le mot qu'il vient de sortir peut maintenant être utilisé comme entrée. Ceci, en combinaison avec la + +23 +00:02:24,960 --> 00:02:30,800 +représentation numérique générée par l'encodeur, peut maintenant être utilisé pour générer un deuxième mot. + +24 +00:02:33,200 --> 00:02:38,880 +Veuillez noter que le premier mot est toujours là car le modèle le produit toujours. Cependant, il est + +25 +00:02:38,880 --> 00:02:45,120 +grisé car nous n'en avons plus besoin. Nous pouvons continuer encore et encore, par exemple jusqu'à ce que le décodeur + +26 +00:02:45,120 --> 00:02:50,720 +génère une valeur que nous considérons comme une valeur d'arrêt, comme un point, signifiant la fin d'une séquence. + +27 +00:02:53,440 --> 00:02:58,080 +Ici, nous avons vu le mécanisme complet du transformer encodeur-décodeur. Reprenons-le une + +28 +00:02:58,080 --> 00:03:05,120 +fois de plus. Nous avons une séquence initiale, qui est envoyée à l'encodeur. Cette sortie d'encodeur est ensuite + +29 +00:03:05,120 --> 00:03:12,240 +envoyée au décodeur, pour qu'elle soit décodée. Bien que nous puissions désormais jeter l'encodeur après une seule utilisation, + +30 +00:03:12,240 --> 00:03:17,840 +le décodeur sera utilisé plusieurs fois. Jusqu'à ce que nous ayons généré tous les mots dont nous avons besoin. + +31 +00:03:20,000 --> 00:03:25,120 +Voyons un exemple concret avec la modélisation du langage pour la traduction, c'est-à-dire + +32 +00:03:25,120 --> 00:03:30,800 +l'action de traduire une séquence. Ici, nous aimerions traduire cette séquence en anglais « Welcome + +33 +00:03:30,800 --> 00:03:38,400 +to NYC » en français. Nous utilisons un transformer spécifiquement entraîné pour cette tâche. Nous utilisons + +34 +00:03:38,400 --> 00:03:43,520 +l'encodeur pour créer une représentation de la phrase en anglais. Nous transmettons ceci + +35 +00:03:43,520 --> 00:03:48,880 +au décodeur et, avec l'utilisation du mot de début de séquence, nous lui demandons de sortir le premier mot. + +36 +00:03:50,720 --> 00:03:52,960 +Il produit Bienvenue, qui signifie « Welcome ». + +37 +00:03:55,280 --> 00:04:02,480 +Nous utilisons ensuite « Bienvenue » comme séquence d'entrée pour le décodeur. Ceci, en plus du vecteur de caractéristiques, + +38 +00:04:04,320 --> 00:04:08,480 +permet au décodeur de prédire le deuxième mot, « à », qui est « to » en anglais. + +39 +00:04:10,160 --> 00:04:14,400 +Enfin, nous demandons au décodeur de prédire un troisième mot. Il prédit « NYC », + +40 +00:04:14,400 --> 00:04:20,240 +ce qui est, encore une fois, correct. Nous avons traduit la phrase ! Là où l'encodeur-décodeur + +41 +00:04:20,240 --> 00:04:24,880 +brille vraiment, c'est que nous avons un encodeur et un décodeur qui souvent ne partagent pas les poids. + +42 +00:04:27,280 --> 00:04:31,440 +Nous avons donc un bloc entier (l'encodeur) qui peut être entraîné pour comprendre la séquence, + +43 +00:04:31,440 --> 00:04:36,480 +et extraire les informations pertinentes. Pour le scénario de traduction que nous avons vu précédemment, par + +44 +00:04:36,480 --> 00:04:44,160 +exemple, cela signifierait analyser et comprendre ce qui a été dit en anglais, extraire des + +45 +00:04:44,160 --> 00:04:49,040 +informations de cette langue et mettre tout cela dans un vecteur dense en informations. + +46 +00:04:50,880 --> 00:04:57,280 +D'autre part, nous avons le décodeur, dont le seul but est de décoder la représentation numérique sortie par + +47 +00:04:57,280 --> 00:05:03,760 +l'encodeur. Ce décodeur peut être spécialisé dans une langue complètement différente, ou même dans une modalité + +48 +00:05:03,760 --> 00:05:11,760 +comme les images ou la parole. Les encodeurs-décodeurs sont spéciaux pour plusieurs raisons. Tout d'abord, + +49 +00:05:11,760 --> 00:05:17,040 +ils sont capables de gérer des tâches de séquence-à-séquence, comme la traduction que nous venons de voir. + +50 +00:05:18,640 --> 00:05:23,880 +Deuxièmement, les poids entre les parties encodeur et décodeur ne sont pas nécessairement partagés. + +51 +00:05:24,480 --> 00:05:31,200 +Prenons un autre exemple de traduction. Ici, nous traduisons « Transformers are powerful » en français. + +52 +00:05:32,240 --> 00:05:36,560 +Premièrement, cela signifie qu'à partir d'une séquence de trois mots, nous sommes capables de générer + +53 +00:05:36,560 --> 00:05:42,240 +une séquence de quatre mots. On pourrait dire que cela pourrait être géré avec un décodeur + +54 +00:05:42,240 --> 00:05:46,960 +qui générerait la traduction de manière autorégressive et ils auraient raison ! + +55 +00:05:49,840 --> 00:05:53,840 +Un autre exemple où les transformers de séquence-à-séquence brillent est le résumé de textes. + +56 +00:05:54,640 --> 00:05:58,560 +Ici, nous avons une très longue séquence, généralement un texte intégral, + +57 +00:05:58,560 --> 00:06:03,840 +et nous voulons la résumer. Comme l'encodeur et les décodeurs sont séparés, + +58 +00:06:03,840 --> 00:06:08,880 +nous pouvons avoir des longueurs de contexte différentes (par exemple, un contexte très long pour l'encodeur qui + +59 +00:06:08,880 --> 00:06:13,840 +gère le texte et un contexte plus petit pour le décodeur qui gère la séquence résumée). + +60 +00:06:16,240 --> 00:06:20,480 +Il existe de nombreux modèles de séquence-à-séquence. Celui-ci contient quelques exemples de + +61 +00:06:20,480 --> 00:06:24,160 +modèles encodeur-décodeur populaires disponibles dans la bibliothèque Transformers. + +62 +00:06:26,320 --> 00:06:31,200 +De plus, vous pouvez charger un encodeur et un décodeur dans un + +63 +00:06:31,200 --> 00:06:35,040 +modèle d'encodeur-décodeur ! Par conséquent, selon la tâche spécifique que vous ciblez, + +64 +00:06:35,040 --> 00:06:40,240 +vous pouvez choisir d'utiliser des encodeurs et des décodeurs spécifiques, qui ont fait leurs preuves sur ces tâches spécifiques. \ No newline at end of file diff --git a/subtitles/fr/08_what-happens-inside-the-pipeline-function-(pytorch).srt b/subtitles/fr/08_what-happens-inside-the-pipeline-function-(pytorch).srt new file mode 100644 index 000000000..b69b8b4e5 --- /dev/null +++ b/subtitles/fr/08_what-happens-inside-the-pipeline-function-(pytorch).srt @@ -0,0 +1,195 @@ +1 +00:00:05,200 --> 00:00:09,680 +Que se passe-t-il dans la fonction pipeline ? Dans cette vidéo, + +2 +00:00:09,680 --> 00:00:14,240 +nous allons voir ce qui se passe réellement lorsque nous utilisons la fonction pipeline de la bibliothèque Transformers. + +3 +00:00:14,880 --> 00:00:19,440 +Plus précisément, nous examinerons le pipeline d'analyse des sentiments et comment il + +4 +00:00:19,440 --> 00:00:24,960 +est passé des deux phrases suivantes aux étiquettes positives et négatives avec leurs scores respectifs. + +5 +00:00:26,560 --> 00:00:30,720 +Comme nous l'avons vu dans la présentation du pipeline, il y a trois étapes dans le pipeline. + +6 +00:00:31,520 --> 00:00:35,920 +Tout d'abord, nous convertissons les textes bruts en nombres que le modèle peut comprendre, à l' + +7 +00:00:35,920 --> 00:00:41,520 +aide d'un tokenizer. Ensuite, ces chiffres passent par le modèle, qui génère des logits. + +8 +00:00:42,640 --> 00:00:47,040 +Enfin, les étapes de post-traitement transforment ces logits en étiquettes et scores. + +9 +00:00:47,920 --> 00:00:53,440 +Examinons en détail ces trois étapes et comment les reproduire à l'aide de la bibliothèque Transformers,  en + +10 +00:00:53,440 --> 00:01:01,040 +commençant par la première étape, la tokenisation. Le processus de tokenisation comporte plusieurs étapes. Tout d'abord, + +11 +00:01:01,040 --> 00:01:07,360 +le texte est divisé en petits morceaux appelés tokens. Il peut s'agir de mots, de parties de mots ou de + +12 +00:01:07,360 --> 00:01:14,160 +symboles de ponctuation. Ensuite, le tokenizer ajoutera des tokens spéciaux (si le modèle les attend). Ici, le modèle + +13 +00:01:14,160 --> 00:01:19,440 +utilise attend un token [CLS] au début et un token [SEP] à la fin de la phrase à classer. + +14 +00:01:20,400 --> 00:01:25,440 +Enfin, le tokenizer associe chaque token à son ID unique dans le vocabulaire du modèle pré-entraîné. + +15 +00:01:25,440 --> 00:01:31,360 +Pour charger un tel tokenizer, la bibliothèque Transformers fournit l'API AutoTokenizer. + +16 +00:01:32,400 --> 00:01:36,320 +La méthode la plus importante de cette classe est `from_pretrained`, qui + +17 +00:01:36,320 --> 00:01:41,680 +télécharge et met en cache la configuration et le vocabulaire associés à un checkpoint donné. + +18 +00:01:43,040 --> 00:01:48,880 +Ici, le checkpoint utilisé par défaut pour le pipeline d'analyse des sentiments est `distilbert base + +19 +00:01:48,880 --> 00:01:56,080 +uncased finetuned sst2 english`. Nous instancions un tokenizer associé à ce checkpoint, + +20 +00:01:56,640 --> 00:02:01,920 +puis lui envoyons les deux phrases. Étant donné que ces deux phrases n'ont pas la même taille, + +21 +00:02:01,920 --> 00:02:05,040 +nous devrons remplir la plus courte pour pouvoir construire un tableau. + +22 +00:02:05,760 --> 00:02:08,240 +Cette opération est effectuée par le tokenizer avec l'option `padding=True`. + +23 +00:02:09,600 --> 00:02:14,800 +Avec `truncation=True`, nous nous assurons que toute phrase plus longue que le maximum que le modèle peut gérer + +24 +00:02:14,800 --> 00:02:21,840 +est tronquée. Enfin, l'option `return_tensors` indique au tokenizer de renvoyer un tenseur PyTorch. + +25 +00:02:23,040 --> 00:02:29,040 +En regardant le résultat, nous voyons que nous avons un dictionnaire avec deux clés. Les ID d'entrée contiennent les ID des deux + +26 +00:02:29,040 --> 00:02:34,080 +phrases, avec des 0 où le rembourrage est appliqué. La deuxième clé, le masque d'attention, + +27 +00:02:34,080 --> 00:02:37,840 +indique où le rembourrage a été appliqué, afin que le modèle n'y prête pas attention. + +28 +00:02:38,640 --> 00:02:43,040 +C'est tout ce qui se trouve à l'intérieur de l'étape de tokenisation. Examinons maintenant la deuxième étape, + +29 +00:02:43,760 --> 00:02:50,560 +le modèle. Comme pour le tokenizer, il existe une API AutoModel, avec une méthode `from_pretrained`. + +30 +00:02:50,560 --> 00:02:54,720 +Il téléchargera et mettra en cache la configuration du modèle ainsi que les poids pré-entraînés. + +31 +00:02:55,840 --> 00:03:00,480 +Cependant, l'API AutoModel n'instanciera que le corps du modèle, + +32 +00:03:00,480 --> 00:03:05,120 +c'est-à-dire la partie du modèle qui reste une fois la tête de pré-entraînement retirée. + +33 +00:03:05,840 --> 00:03:11,360 +Il produira un tenseur de grande dimension qui est une représentation des phrases passées, mais qui + +34 +00:03:11,360 --> 00:03:17,200 +n'est pas directement utile pour notre problème de classification. Ici, le tenseur a deux phrases, + +35 +00:03:17,200 --> 00:03:25,440 +chacune de 16 tokens et la dernière dimension est la taille cachée de notre modèle 768. Pour obtenir une sortie + +36 +00:03:25,440 --> 00:03:30,240 +liée à notre problème de classification, nous devons utiliser la classe AutoModelForSequenceClassification. + +37 +00:03:30,960 --> 00:03:35,200 +Elle fonctionne exactement comme la classe AutoModel, sauf qu'elle créera un modèle avec une + +38 +00:03:35,200 --> 00:03:40,720 +tête de classification. Il existe une classe automatique pour chaque tâche NLP courante dans la bibliothèque Transformers. + +39 +00:03:42,000 --> 00:03:47,600 +Ici, après avoir donné à notre modèle les deux phrases, nous obtenons un tenseur de taille 2 par 2 : + +40 +00:03:47,600 --> 00:03:53,680 +un résultat pour chaque phrase et pour chaque étiquette possible. Ces sorties ne sont pas encore des probabilités + +41 +00:03:53,680 --> 00:03:59,120 +(nous pouvons voir qu'elles ne somment pas à 1). En effet, chaque modèle de la bibliothèque Transformers renvoie des + +42 +00:03:59,120 --> 00:04:05,120 +logits. Pour donner un sens à ces logits, nous devons approfondir la troisième et dernière étape du pipeline : le + +43 +00:04:05,680 --> 00:04:11,840 +post-traitement. Pour convertir les logits en probabilités, nous devons leur appliquer une + +44 +00:04:11,840 --> 00:04:17,760 +couche SoftMax. Comme nous pouvons le voir, cela les transforme en nombres positifs qui somment à 1. + +45 +00:04:18,960 --> 00:04:22,800 +La dernière étape consiste à savoir lequel de ces correspond à l'étiquette positive ou négative. + +46 +00:04:23,360 --> 00:04:30,160 +Ceci est donné par le champ `id2label` de la configuration du modèle. Les premières probabilités (indice 0) + +47 +00:04:30,160 --> 00:04:35,360 +correspondent à l'étiquette négative, et les secondes (indice 1) correspondent à l'étiquette positive. + +48 +00:04:36,240 --> 00:04:40,560 +C'est ainsi que notre classifieur construit avec la fonction de pipeline a sélectionné ces étiquettes et calculé + +49 +00:04:40,560 --> 00:04:52,080 +ces scores. Maintenant que vous connaissez le fonctionnement de chaque étape, vous pouvez facilement les adapter à vos besoins. \ No newline at end of file diff --git a/subtitles/fr/09_what-happens-inside-the-pipeline-function-(tensorflow).srt b/subtitles/fr/09_what-happens-inside-the-pipeline-function-(tensorflow).srt new file mode 100644 index 000000000..1ec7ecc71 --- /dev/null +++ b/subtitles/fr/09_what-happens-inside-the-pipeline-function-(tensorflow).srt @@ -0,0 +1,191 @@ +1 +00:00:05,360 --> 00:00:07,680 +Que se passe-t-il dans la fonction pipeline ? + +2 +00:00:09,840 --> 00:00:14,800 +Dans cette vidéo, nous allons voir ce qui se passe réellement lorsque nous utilisons la fonction pipeline de + +3 +00:00:14,800 --> 00:00:20,880 +la bibliothèque Transformers. Plus précisément, nous examinerons le pipeline d'analyse des sentiments et + +4 +00:00:20,880 --> 00:00:26,720 +comment il est passé des deux phrases suivantes aux étiquettes positives et négatives avec leurs scores respectifs. + +5 +00:00:28,560 --> 00:00:34,160 +Comme nous l'avons vu dans la présentation du pipeline, il y a trois étapes dans le pipeline. Tout d'abord, + +6 +00:00:34,800 --> 00:00:38,880 +nous convertissons les textes bruts en nombres que le modèle peut comprendre, à l'aide d'un tokenizer. + +7 +00:00:40,000 --> 00:00:43,520 +Ensuite, ces chiffres passent par le modèle, qui génère des logits. + +8 +00:00:44,400 --> 00:00:49,120 +Enfin, les étapes de post-traitement transforment ces logits en étiquettes et scores. + +9 +00:00:50,720 --> 00:00:54,960 +Examinons en détail ces trois étapes et comment les reproduire à l'aide de la bibliothèque Transformers, en + +10 +00:00:54,960 --> 00:01:03,280 +commençant par la première étape, la tokenisation. Le processus de tokenisation comporte plusieurs étapes. Tout d'abord, + +11 +00:01:03,280 --> 00:01:09,120 +le texte est divisé en petits morceaux appelés tokens. Il peut s'agir de mots, de parties de mots ou de + +12 +00:01:09,120 --> 00:01:17,440 +symboles de ponctuation. Ensuite, le tokenizer aura des tokens spéciaux (si le modèle les attend). Ici, le modèle + +13 +00:01:17,440 --> 00:01:22,800 +utilise attend un token [CLS] au début et un token [SEP] à la fin de la phrase à classer. + +14 +00:01:23,760 --> 00:01:28,880 +Enfin, le tokenizer associe chaque token à son ID unique dans le vocabulaire du modèle pré-entraîné. + +15 +00:01:28,880 --> 00:01:34,640 +Pour charger un tel tokenizer de tokens, la bibliothèque Transformers fournit l'API AutoTokenizer. + +16 +00:01:35,680 --> 00:01:40,640 +La méthode la plus importante de cette classe est `from_pretrained`, qui téléchargera et mettra en cache + +17 +00:01:40,640 --> 00:01:47,200 +la configuration et le vocabulaire associés à un checkpoint donné. Ici, le checkpoint utilisé + +18 +00:01:47,200 --> 00:01:53,840 +par défaut pour le pipeline d'analyse des sentiments est `distilbert base uncased finetuned sst2 english`. + +19 +00:01:56,560 --> 00:02:01,440 +Nous instancions un tokenizer associé à ce checkpoint, puis lui envoyons les deux phrases. + +20 +00:02:02,640 --> 00:02:07,360 +Étant donné que ces deux phrases n'ont pas la même taille, nous devrons remplir la plus courte pour + +21 +00:02:07,360 --> 00:02:11,680 +être en mesure de créer un tableau. Cette opération est effectuée par le tokenizer avec l'option `padding=True`. + +22 +00:02:13,840 --> 00:02:18,960 +Avec `truncation=True`, nous nous assurons que toute phrase plus longue que le maximum que le modèle peut gérer + +23 +00:02:18,960 --> 00:02:25,600 +est tronquée. Enfin, l'option `return_tensors` indique au tokenizer de renvoyer un tenseur TensorFlow. + +24 +00:02:26,720 --> 00:02:29,680 +En regardant le résultat, nous voyons que nous avons un dictionnaire avec deux clés. + +25 +00:02:30,240 --> 00:02:37,280 +Les ID d'entrée contiennent les ID des deux phrases, avec des 0 où le rembourrage est appliqué. La deuxième clé, + +26 +00:02:37,280 --> 00:02:42,080 +masque d'attention, indique où le rembourrage a été appliqué, de sorte que le modèle n'y prête pas + +27 +00:02:42,080 --> 00:02:48,000 +attention. C'est tout ce qui se trouve à l'intérieur de l'étape de tokenisation. Examinons maintenant la deuxième étape, + +28 +00:02:48,640 --> 00:02:54,960 +le modèle. Comme pour le tokenizer, il existe une API TFAutoModel, avec une méthode `from_pretrained`. + +29 +00:02:55,600 --> 00:02:59,840 +Il téléchargera et mettra en cache la configuration du modèle ainsi que les pondérations pré-entraînées. + +30 +00:02:59,840 --> 00:03:05,600 +Cependant, l'API TFAutoModel n'instanciera que le corps du modèle, + +31 +00:03:06,320 --> 00:03:10,640 +c'est-à-dire la partie du modèle qui reste une fois la tête de pré-entraînement retirée. + +32 +00:03:12,000 --> 00:03:16,960 +Il produira un tenseur de grande dimension qui est une représentation des phrases passées, + +33 +00:03:16,960 --> 00:03:20,080 +mais qui n'est pas directement utile pour notre problème de classification. + +34 +00:03:21,760 --> 00:03:28,080 +Ici, le tenseur a deux phrases, chacune de 16 tokens et la dernière dimension est la taille cachée + +35 +00:03:28,080 --> 00:03:34,320 +de notre modèle 768. Pour obtenir une sortie liée à notre problème de classification, nous devons + +36 +00:03:34,320 --> 00:03:40,000 +utiliser la classe TFAutoModelForSequenceClassification. Elle fonctionne exactement comme la classe AutoModel, + +37 +00:03:40,000 --> 00:03:45,440 +sauf qu'elle créera un modèle avec une tête de classification. Il existe une classe automatique pour + +38 +00:03:45,440 --> 00:03:52,160 +chaque tâche NLP courante dans la bibliothèque Transformers. Ici, après avoir donné à notre modèle les deux phrases, + +39 +00:03:52,160 --> 00:03:59,120 +nous obtenons un tenseur de taille 2 par 2 : un résultat pour chaque phrase et pour chaque étiquette possible. Ces + +40 +00:03:59,120 --> 00:04:04,800 +sorties ne sont pas encore des probabilités (nous pouvons voir qu'elles ne somment pas à 1). En effet, chaque modèle de la + +41 +00:04:04,800 --> 00:04:10,960 +bibliothèque Transformers renvoie des logits. Pour donner un sens à ces logits, nous devons approfondir la troisième et + +42 +00:04:10,960 --> 00:04:17,520 +dernière étape du pipeline : le post-traitement. Pour convertir les logits en probabilités, nous devons leur + +43 +00:04:17,520 --> 00:04:22,800 +appliquer une couche SoftMax. Comme nous pouvons le voir, cela les transforme en nombres positifs qui + +44 +00:04:22,800 --> 00:04:28,160 +somment à 1. La dernière étape consiste à savoir lequel d'entre eux correspond à l'étiquette positive ou négative. + +45 +00:04:28,160 --> 00:04:34,720 +Ceci est donné par le champ `id2label` de la configuration du modèle. Les premières probabilités + +46 +00:04:34,720 --> 00:04:40,800 +(indice 0) correspondent à l'étiquette négative et les secondes (indice 1) correspondent à l' + +47 +00:04:40,800 --> 00:04:46,640 +étiquette positive. C'est ainsi que notre classifieur construit avec la fonction de pipeline a sélectionné ces étiquettes et calculé + +48 +00:04:46,640 --> 00:04:55,840 +ces scores. Maintenant que vous connaissez le fonctionnement de chaque étape, vous pouvez facilement les adapter à vos besoins. \ No newline at end of file diff --git a/subtitles/fr/10_instantiate-a-transformers-model-(pytorch).srt b/subtitles/fr/10_instantiate-a-transformers-model-(pytorch).srt new file mode 100644 index 000000000..be92da08d --- /dev/null +++ b/subtitles/fr/10_instantiate-a-transformers-model-(pytorch).srt @@ -0,0 +1,131 @@ +1 +00:00:05,120 --> 00:00:07,440 +Comment instancier un transformer ? + +2 +00:00:08,640 --> 00:00:12,960 +Dans cette vidéo, nous verrons comment créer et utiliser un modèle de la bibliothèque Transformers. + +3 +00:00:14,160 --> 00:00:19,440 +Comme nous l'avons vu précédemment, la classe AutoModel vous permet d'instancier un modèle pré-entraîné à partir de n'importe quel + +4 +00:00:19,440 --> 00:00:24,960 +checkpoint sur le Hub d'Hugging Face. Elle choisira la bonne classe de modèle dans la bibliothèque pour + +5 +00:00:24,960 --> 00:00:30,800 +instancier l'architecture appropriée et charger les poids du modèle pré-entraîné à l'intérieur. Comme nous + +6 +00:00:30,800 --> 00:00:37,760 +pouvons le voir, lorsqu'on nous donne un checkpoint BERT, nous nous retrouvons avec un BertModel, et de même pour GPT-2 ou BART. + +7 +00:00:39,680 --> 00:00:43,440 +En coulisses, cette API peut prendre le nom d'un checkpoint sur le Hub, + +8 +00:00:44,080 --> 00:00:48,400 +auquel cas elle téléchargera et mettra en cache le fichier de configuration ainsi que le fichier de poids du modèle. + +9 +00:00:48,400 --> 00:00:54,800 +Vous pouvez également spécifier le chemin d'accès à un dossier local contenant un fichier de configuration valide + +10 +00:00:54,800 --> 00:01:00,720 +et un fichier de poids de modèle. Pour instancier le modèle pré-entraîné, l'API AutoModel + +11 +00:01:00,720 --> 00:01:04,960 +ouvre d'abord le fichier de configuration pour examiner la classe de configuration à utiliser. + +12 +00:01:06,080 --> 00:01:12,240 +La classe de configuration dépend du type de modèle (BERT, GPT-2 ou BART par exemple). + +13 +00:01:13,440 --> 00:01:18,160 +Une fois qu'il a la classe de configuration appropriée, il peut instancier cette configuration, + +14 +00:01:18,160 --> 00:01:23,920 +qui est un plan pour savoir comment créer le modèle. Il utilise également cette classe de configuration + +15 +00:01:23,920 --> 00:01:29,360 +pour trouver la classe de modèle appropriée, qui est combinée avec la configuration chargée, pour charger le modèle. + +16 +00:01:30,800 --> 00:01:35,520 +Ce modèle n'est pas encore notre modèle pré-entraîné car il vient d'être initialisé avec des poids aléatoires. + +17 +00:01:36,560 --> 00:01:42,960 +La dernière étape consiste à charger les poids à partir du fichier de modèle dans ce modèle. Pour charger facilement + +18 +00:01:42,960 --> 00:01:47,360 +la configuration d'un modèle à partir de n'importe quel checkpoint ou d'un dossier contenant le dossier de configuration, + +19 +00:01:48,000 --> 00:01:49,920 +nous pouvons utiliser la classe AutoConfig. + +20 +00:01:51,040 --> 00:01:55,360 +Comme la classe AutoModel, elle sélectionne la bonne classe de configuration dans la bibliothèque. + +21 +00:01:56,800 --> 00:02:01,360 +Nous pouvons également utiliser la classe spécifique correspondant à un checkpoint, mais nous devrons modifier le + +22 +00:02:01,360 --> 00:02:08,320 +code chaque fois que nous voulons essayer un modèle différent. Comme nous l'avons déjà dit, la configuration d'un modèle est + +23 +00:02:08,320 --> 00:02:12,720 +un plan qui contient toutes les informations nécessaires pour créer l'architecture du modèle. + +24 +00:02:13,600 --> 00:02:19,680 +Par exemple, le modèle BERT associé au checkpoint basé sur bert a 12 couches, + +25 +00:02:19,680 --> 00:02:29,120 +une taille cachée de 768 et une taille de vocabulaire de 28 996. Une fois que nous avons la configuration, + +26 +00:02:29,680 --> 00:02:33,120 +nous pouvons créer un modèle qui a la même architecture que notre checkpoint, mais qui est + +27 +00:02:33,120 --> 00:02:37,840 +initialisé de manière aléatoire. Nous pouvons ensuite l'entraîner à partir de zéro comme n'importe quel module PyTorch/modèle TensorFlow. + +28 +00:02:38,800 --> 00:02:42,960 +Nous pouvons également modifier n'importe quelle partie de la configuration à l'aide d'arguments de mots clés. + +29 +00:02:43,920 --> 00:02:49,280 +Le deuxième extrait de code instancie un modèle BERT initialisé de manière aléatoire avec dix couches + +30 +00:02:49,280 --> 00:02:56,160 +au lieu de 12. L'enregistrement d'un modèle une fois qu'il est entraîné ou finetuné est très simple : il suffit d'utiliser + +31 +00:02:56,160 --> 00:03:02,880 +la méthode `save_pretrained`. Ici, le modèle sera enregistré dans un dossier nommé `my-bert-model` à + +32 +00:03:02,880 --> 00:03:08,240 +l'intérieur du répertoire de travail actuel. Un tel modèle peut ensuite être rechargé à l'aide de la méthode `from_pretrained`. + +33 +00:03:08,880 --> 00:03:13,240 +Pour apprendre comment envoyer ce modèle sur le Hub, regardez la vidéo sur l'API `push_to_hub`. \ No newline at end of file diff --git a/subtitles/fr/11_instantiate-a-transformers-model-(tensorflow).srt b/subtitles/fr/11_instantiate-a-transformers-model-(tensorflow).srt new file mode 100644 index 000000000..b710a0d86 --- /dev/null +++ b/subtitles/fr/11_instantiate-a-transformers-model-(tensorflow).srt @@ -0,0 +1,155 @@ +1 +00:00:05,540 --> 00:00:07,870 +Comment instancier un transformer ? + +2 +00:00:07,870 --> 00:00:14,800 +Dans cette vidéo, nous verrons comment créer et utiliser un modèle de la bibliothèque Transformers. + +3 +00:00:14,800 --> 00:00:20,130 +Comme nous l'avons vu précédemment, la classe TFAutoModel vous permet d'instancier un modèle pré-entraîné à + +4 +00:00:20,130 --> 00:00:23,490 +partir de n'importe quel checkpoint sur le Hub d'Hugging Face. + +5 +00:00:23,490 --> 00:00:27,740 +Elle choisira la bonne classe de modèle dans la bibliothèque pour instancier l'architecture appropriée + +6 +00:00:27,740 --> 00:00:31,310 +et charger les poids du modèle pré-entraîné à l'intérieur. + +7 +00:00:31,310 --> 00:00:36,630 +Comme nous pouvons le voir, lorsqu'on nous donne un checkpoint BERT, nous nous retrouvons avec un TFBertModel, et de même + +8 +00:00:36,630 --> 00:00:39,890 +pour GPT-2 ou BART. + +9 +00:00:39,890 --> 00:00:44,489 +En coulisse, cette API peut prendre le nom d'un checkpoint sur le Hub, auquel cas + +10 +00:00:44,489 --> 00:00:49,649 +elle téléchargera et mettra en cache le fichier de configuration ainsi que le fichier de poids du modèle. + +11 +00:00:49,649 --> 00:00:54,059 +Vous pouvez également spécifier le chemin d'accès à un dossier local qui contient un fichier de configuration valide et + +12 +00:00:54,059 --> 00:00:56,739 +un fichier de poids de modèle. + +13 +00:00:56,739 --> 00:01:02,480 +Pour instancier le modèle pré-entraîné, l'API AutoModel ouvre d'abord le fichier de configuration + +14 +00:01:02,480 --> 00:01:06,409 +pour examiner la classe de configuration à utiliser. + +15 +00:01:06,409 --> 00:01:13,509 +La classe de configuration dépend du type de modèle (BERT, GPT-2 ou BART par exemple). + +16 +00:01:13,509 --> 00:01:18,130 +Une fois qu'il a la classe de configuration appropriée, il peut instancier cette configuration, qui + +17 +00:01:18,130 --> 00:01:20,420 +est un plan pour savoir comment créer le modèle. + +18 +00:01:20,420 --> 00:01:25,420 +Il utilise également cette classe de configuration pour trouver la classe de modèle appropriée, qui est combinée + +19 +00:01:25,420 --> 00:01:28,470 +avec la configuration chargée, pour charger le modèle. + +20 +00:01:28,470 --> 00:01:34,759 +Ce modèle n'est pas encore notre modèle pré-entraîné car il vient d'être initialisé avec des poids aléatoires. + +21 +00:01:34,759 --> 00:01:40,299 +La dernière étape consiste à charger les poids du fichier de modèle à l'intérieur de ce modèle. + +22 +00:01:40,299 --> 00:01:44,659 +Pour charger facilement la configuration d'un modèle à partir de n'importe quel checkpoint ou d'un dossier contenant + +23 +00:01:44,659 --> 00:01:48,100 +le dossier de configuration, nous pouvons utiliser la classe AutoConfig. + +24 +00:01:48,100 --> 00:01:54,270 +Comme la classe TFAutoModel, elle choisira la bonne classe de configuration dans la bibliothèque. + +25 +00:01:54,270 --> 00:01:58,869 +Nous pouvons également utiliser la classe spécifique correspondant à un checkpoint, mais nous devrons changer + +26 +00:01:58,869 --> 00:02:03,280 +le code chaque fois que nous voudrons essayer un modèle différent. + +27 +00:02:03,280 --> 00:02:07,490 +Comme nous l'avons dit précédemment, la configuration d'un modèle est un plan qui contient toutes les + +28 +00:02:07,490 --> 00:02:11,190 +informations nécessaires pour créer l'architecture du modèle. + +29 +00:02:11,190 --> 00:02:14,629 +Par exemple, le modèle BERT associé au checkpoint basé sur bert a 12 couches, + +30 +00:02:14,629 --> 00:02:21,790 +une taille cachée de 768 et une taille de vocabulaire de 28 996. + +31 +00:02:21,790 --> 00:02:28,959 +Une fois que nous avons la configuration, nous pouvons créer un modèle qui a la même architecture que + +32 +00:02:28,959 --> 00:02:31,420 +notre checkpoint mais qui est initialisé de manière aléatoire. + +33 +00:02:31,420 --> 00:02:36,080 +Nous pouvons ensuite l'entraîner à partir de zéro comme n'importe quel module PyTorch/modèle TensorFlow. + +34 +00:02:36,080 --> 00:02:40,870 +Nous pouvons également modifier n'importe quelle partie de la configuration en utilisant des arguments de mots clés. + +35 +00:02:40,870 --> 00:02:48,379 +Le deuxième extrait de code instancie un modèle BERT initialisé de manière aléatoire avec dix couches au lieu de 12. + +36 +00:02:48,379 --> 00:02:53,019 +Enregistrer un modèle une fois qu'il est entraîné ou finetuné est très simple : il suffit d'utiliser la + +37 +00:02:53,019 --> 00:02:54,019 +méthode `save_pretrained`. + +38 +00:02:54,019 --> 00:03:00,510 +Ici, le modèle sera enregistré dans un dossier nommé `my-bert-model` dans le répertoire de travail actuel. + +39 +00:03:00,510 --> 00:03:13,120 +Un tel modèle peut ensuite être rechargé à l'aide de la méthode `from_pretrained`. Pour apprendre comment envoyer ce modèle sur le Hub, regardez la vidéo sur l'API `push_to_hub`. \ No newline at end of file diff --git a/subtitles/fr/12_tokenizers-overview.srt b/subtitles/fr/12_tokenizers-overview.srt new file mode 100644 index 000000000..eb9d9ff3d --- /dev/null +++ b/subtitles/fr/12_tokenizers-overview.srt @@ -0,0 +1,31 @@ +1 +00:00:03,840 --> 00:00:09,200 +Dans ces quelques vidéos, nous allons jeter un œil aux tokenizers. Dans le traitement du langage naturel, la + +2 +00:00:09,200 --> 00:00:14,880 +plupart des données que nous traitons sont constituées de texte brut. Cependant, les modèles d'apprentissage automatique ne peuvent pas lire + +3 +00:00:14,880 --> 00:00:23,200 +et comprendre le texte dans sa forme brute, ils ne peuvent travailler qu'avec des nombres. L'objectif du tokenizer + +4 +00:00:23,200 --> 00:00:30,080 +sera de traduire le texte en chiffres. Il existe plusieurs approches possibles pour cette conversion, + +5 +00:00:30,080 --> 00:00:33,120 +et l'objectif est de trouver la représentation la plus significative. + +6 +00:00:36,000 --> 00:00:40,400 +Nous allons examiner trois algorithmes de tokenisation distincts. Nous les comparons un à un, + +7 +00:00:40,400 --> 00:00:44,880 +nous vous recommandons donc de regarder les vidéos dans l'ordre suivant : basé sur les mots, basé sur les + +8 +00:00:45,680 --> 00:00:55,680 +caractères et enfin basé sur les sous-mots. \ No newline at end of file diff --git a/subtitles/fr/13_word-based-tokenizers.srt b/subtitles/fr/13_word-based-tokenizers.srt new file mode 100644 index 000000000..ce9e0358c --- /dev/null +++ b/subtitles/fr/13_word-based-tokenizers.srt @@ -0,0 +1,99 @@ +1 +00:00:03,120 --> 00:00:10,240 +Jetons un coup d'œil à la tokenisation basée sur les mots. La tokenisation basée sur les mots est l'idée de diviser + +2 +00:00:10,240 --> 00:00:19,040 +le texte brut en mots, en divisant sur des espaces ou d'autres règles spécifiques comme la ponctuation. Dans cet + +3 +00:00:19,040 --> 00:00:25,040 +algorithme, chaque mot est associé à un numéro spécifique, un ID, qui lui est attribué. Dans cet exemple, « Let's » + +4 +00:00:25,040 --> 00:00:33,120 +a l'ID 250, « do » a l'ID 861 et « tokenization! » a l'ID 345. + +5 +00:00:34,160 --> 00:00:39,840 +Cette approche est intéressante, car le modèle a des représentations basées sur des mots entiers. + +6 +00:00:42,560 --> 00:00:45,680 +Les informations contenues dans un seul nombre sont élevées + +7 +00:00:45,680 --> 00:00:52,880 +car un mot contient de nombreuses informations contextuelles et sémantiques dans une phrase. + +8 +00:00:52,880 --> 00:00:58,720 +Cependant, cette approche a ses limites. Par exemple, le mot « dog » et le mot + +9 +00:00:58,720 --> 00:01:04,320 +« dogs » sont très similaires et leur sens est proche. Cependant, la tokenisation basée sur les mots + +10 +00:01:05,280 --> 00:01:10,320 +attribuera des identifiants entièrement différents à ces deux mots, et le modèle apprendra donc + +11 +00:01:10,320 --> 00:01:14,880 +des significations différentes pour ces deux mots. C'est malheureux, car nous aimerions que le + +12 +00:01:14,880 --> 00:01:21,120 +modèle comprenne que ces mots sont en effet liés et que « dogs » est la forme plurielle du mot « dog ». + +13 +00:01:22,800 --> 00:01:26,400 +Un autre problème avec cette approche est qu'il y a beaucoup de mots différents dans une langue. + +14 +00:01:27,840 --> 00:01:31,920 +Si nous voulons que notre modèle comprenne toutes les phrases possibles dans cette langue, + +15 +00:01:31,920 --> 00:01:37,200 +nous aurons besoin d'un identifiant pour chaque mot différent, et le nombre total de mots, + +16 +00:01:37,200 --> 00:01:41,440 +également connu sous le nom de taille du vocabulaire, peut rapidement devenir très important. + +17 +00:01:44,160 --> 00:01:48,800 +C'est un problème car chaque ID est associé à un grand vecteur qui représente la signification du mot. + +18 +00:01:50,000 --> 00:01:55,840 +Et le suivi de ces associations nécessite un nombre énorme de poids lorsque la taille du vocabulaire + +19 +00:01:55,840 --> 00:02:03,360 +est importante. Si nous voulons que nos modèles restent légers, nous pouvons opter pour que notre tokenizer ignore + +20 +00:02:03,360 --> 00:02:11,760 +certains mots dont nous n'avons pas nécessairement besoin. Par exemple ici, lors d'entraînement de notre tokenizer sur un texte, + +21 +00:02:11,760 --> 00:02:23,520 +nous pouvons vouloir prendre les 10 000 mots les plus fréquents dans ce texte pour créer notre vocabulaire de base, au lieu de prendre tous les mots de cette langue. + +22 +00:02:23,520 --> 00:02:27,200 +Le tokenizer saura comment convertir ces 10 000 mots en nombres, + +23 +00:02:27,200 --> 00:02:33,520 +mais tout autre mot sera converti en mot hors vocabulaire ou en « UNKNOWN ». + +24 +00:02:36,000 --> 00:02:39,760 +Cela peut rapidement devenir un problème : le modèle aura exactement la même représentation + +25 +00:02:39,760 --> 00:02:46,720 +pour tous les mots qu'il ne connaît pas, ce qui entraînera de nombreuses pertes d'informations si beaucoup de mots inconnus sont présents. \ No newline at end of file diff --git a/subtitles/fr/14_character-based-tokenizers.srt b/subtitles/fr/14_character-based-tokenizers.srt new file mode 100644 index 000000000..b6c8b3164 --- /dev/null +++ b/subtitles/fr/14_character-based-tokenizers.srt @@ -0,0 +1,107 @@ +1 +00:00:04,160 --> 00:00:09,440 +Avant de plonger dans la tokenisation basée sur les caractères, pour comprendre pourquoi ce type de tokenisation + +2 +00:00:09,440 --> 00:00:13,680 +est intéressant, il faut comprendre les défauts de la tokenisation basée sur les mots. + +3 +00:00:14,560 --> 00:00:18,400 +Si vous n'avez pas vu la première vidéo sur la tokenisation basée sur des mots, nous vous recommandons de la + +4 +00:00:18,400 --> 00:00:23,920 +regarder avant de regarder cette vidéo. Examinons la tokenisation basée sur les caractères. + +5 +00:00:25,440 --> 00:00:29,840 +Nous divisons maintenant notre texte en caractères individuels plutôt qu'en mots. + +6 +00:00:32,720 --> 00:00:37,200 +Il y a généralement beaucoup de mots différents dans les langues, tandis que le nombre de caractères reste + +7 +00:00:37,200 --> 00:00:45,520 +faible. Commençons avec la langue anglaise qui compte environ 170 000 mots différents. + +8 +00:00:45,520 --> 00:00:48,960 +Nous aurions besoin d'un très grand vocabulaire pour englober tous les mots. + +9 +00:00:50,080 --> 00:00:59,600 +Avec un vocabulaire basé sur les caractères, nous pouvons nous débrouiller avec seulement 256 caractères incluant les lettres, les chiffres et caractères spéciaux. + +10 +00:00:59,600 --> 00:01:04,880 +Même les langues contenant de nombreux caractères différents, comme les langues chinoises, ont des dictionnaires contenant + +11 +00:01:06,160 --> 00:01:14,160 +environ 20 000 caractères différents, mais plus de 375 000 mots différents. Les vocabulaires + +12 +00:01:14,160 --> 00:01:20,240 +basés sur des caractères nous permettent d'utiliser moins de tokens différents que les dictionnaires de tokenisation basés sur des mots que nous utiliserions autrement. + +13 +00:01:23,040 --> 00:01:28,000 +Ces vocabulaires sont également plus complets que leurs homologues basés sur des mots. + +14 +00:01:28,720 --> 00:01:34,160 +Étant donné que notre vocabulaire contient tous les caractères utilisés dans une langue, même les mots non vus lors de + +15 +00:01:34,160 --> 00:01:39,840 +l'entraînement du tokenizer peuvent toujours être tokenisés, de sorte que les tokens hors vocabulaire seront moins fréquents. + +16 +00:01:40,480 --> 00:01:45,200 +Cela inclut la possibilité de tokeniser correctement les mots mal orthographiés, plutôt que de les rejeter immédiatement comme + +17 +00:01:45,200 --> 00:01:53,600 +inconnus. Cependant, cet algorithme n'est pas parfait non plus ! Intuitivement, les caractères + +18 +00:01:53,600 --> 00:01:59,760 +ne contiennent pas autant d'informations individuellement qu'un mot en contiendrait. Par exemple, « Let's » contient + +19 +00:01:59,760 --> 00:02:07,040 +plus d'informations que sa première lettre « L ». Bien sûr, cela n'est pas vrai pour toutes les langues, car certaines langues comme les + +20 +00:02:07,040 --> 00:02:11,280 +langues basées sur des idéogrammes contiennent beaucoup d'informations contenues dans des caractères uniques. + +21 +00:02:12,480 --> 00:02:17,200 +Mais pour d'autres, comme les langues romanes, le modèle devra donner un sens à plusieurs + +22 +00:02:17,200 --> 00:02:25,120 +tokens à la fois le temps d'obtenir l'information contenue dans un seul mot. Cela conduit à un autre problème avec les + +23 +00:02:25,120 --> 00:02:30,320 +tokenizers basés sur des caractères : leurs séquences sont traduites en une très grande quantité de tokens à + +24 +00:02:30,320 --> 00:02:37,680 +traiter par le modèle. Cela peut avoir un impact sur la taille du contexte que le modèle + +25 +00:02:37,680 --> 00:02:45,120 +peut traiter et réduira la taille du texte que nous pouvons utiliser comme entrée pour notre modèle. Cette tokenisation, + +26 +00:02:45,120 --> 00:02:49,920 +bien qu'elle présente quelques problèmes, a donné de très bons résultats dans le passé et doit être prise en compte lors de l' + +27 +00:02:49,920 --> 00:03:00,720 +approche d'un nouveau problème car elle résout certains problèmes rencontrés dans l'algorithme basé sur les mots. \ No newline at end of file diff --git a/subtitles/fr/15_subword-based-tokenizers.srt b/subtitles/fr/15_subword-based-tokenizers.srt new file mode 100644 index 000000000..6c78dad2d --- /dev/null +++ b/subtitles/fr/15_subword-based-tokenizers.srt @@ -0,0 +1,127 @@ +1 +00:00:06,320 --> 00:00:11,440 +Jetons un coup d'œil à la tokenisation en sous-mots. Comprendre pourquoi la tokenisation en sous-mots + +2 +00:00:11,440 --> 00:00:16,320 +est intéressante nécessite de comprendre les défauts de la tokenisation basée sur les mots et sur les caractères. + +3 +00:00:17,200 --> 00:00:21,760 +Si vous n'avez pas vu les premières vidéos sur la tokenisation basée sur les mots et les caractères, + +4 +00:00:21,760 --> 00:00:24,400 +nous vous recommandons de les consulter avant de regarder cette vidéo. + +5 +00:00:27,680 --> 00:00:33,440 +La tokenisation en sous-mots se situe entre les algorithmes de tokenisation basés sur les caractères et les mots. + +6 +00:00:33,440 --> 00:00:40,960 +L'idée est de trouver un terrain d'entente entre de très grands vocabulaires, une grande quantité de + +7 +00:00:40,960 --> 00:00:47,040 +tokens hors vocabulaire et une perte de sens entre des mots très similaires pour les tokenizers basés sur des mots, + +8 +00:00:47,040 --> 00:00:52,800 +et de très longues séquences, des tokens individuels moins significatifs pour les tokenizers basés sur des caractères. + +9 +00:00:54,720 --> 00:00:59,360 +Ces algorithmes reposent sur le principe suivant : les mots fréquemment utilisés ne doivent pas + +10 +00:00:59,360 --> 00:01:04,800 +être divisés en sous-mots plus petits, mais les mots rares doivent être décomposés en sous-mots significatifs. + +11 +00:01:06,320 --> 00:01:11,520 +Un exemple est le mot « dog » : nous aimerions que notre tokenizer ait un seul identifiant pour le mot + +12 +00:01:11,520 --> 00:01:18,480 +« dog », plutôt que de le diviser en caractères : « d », « o » et « g ». Cependant, lorsque nous rencontrons le mot + +13 +00:01:18,480 --> 00:01:23,920 +« dogs », nous aimerions que notre tokenizer comprenne qu'à la racine, il s'agit toujours du mot « dog » + +14 +00:01:23,920 --> 00:01:31,280 +avec un « s » ajouté tout en modifiant légèrement le sens tout en gardant l'idée originale. Un autre exemple + +15 +00:01:31,280 --> 00:01:37,520 +est un mot complexe comme « tokenization » qui peut être divisé en sous-mots significatifs. La racine + +16 +00:01:37,520 --> 00:01:42,000 +du mot est « token » et « ization » complète la racine pour lui donner un sens légèrement différent. + +17 +00:01:42,720 --> 00:01:48,960 +Il est logique de diviser le mot en deux : « token » en tant que racine du mot (étiqueté comme le « début » + +18 +00:01:48,960 --> 00:01:53,840 +du mot) et « ization » en tant qu'informations supplémentaires (étiquetées comme « complétion » du mot). + +19 +00:01:56,240 --> 00:02:00,320 +À son tour, le modèle pourra désormais donner un sens à « token » dans différentes situations. + +20 +00:02:00,880 --> 00:02:06,400 +Il comprendra que les mots « token », « tokens », « tokenizing » et « tokenization » sont liés et ont + +21 +00:02:06,400 --> 00:02:14,000 +une signification similaire. Il comprendra également que « tokenization », « modernization » et « immunization », + +22 +00:02:14,000 --> 00:02:18,960 +qui ont toutes les mêmes suffixes, sont probablement utilisées dans les mêmes situations syntaxiques. + +23 +00:02:20,320 --> 00:02:25,920 +Les tokenizers basés sur des sous-mots ont généralement un moyen d'identifier quels tokens sont des débuts de mots et + +24 +00:02:25,920 --> 00:02:34,320 +quels tokens complètent le début de mots. DOnc ici « token » est le début d'un mot et « ##isation » est la complétion d'un mot. + +25 +00:02:34,960 --> 00:02:40,800 +Ici, le préfixe « ## » indique que « ization » fait partie d'un mot plutôt que son début. + +26 +00:02:41,760 --> 00:02:49,440 +Le « ## » provient du tokenizer de BERT, basé sur l'algorithme WordPiece. D'autres tokenizers utilisent d'autres + +27 +00:02:49,440 --> 00:02:54,720 +préfixes pouvant être placés pour indiquer une partie de mots comme ici, ou le début de mots à la place ! + +28 +00:02:56,000 --> 00:03:01,040 +Il existe de nombreux algorithmes différents qui peuvent être utilisés pour la tokenisation en sous-mots, et la plupart des modèles + +29 +00:03:01,040 --> 00:03:05,760 +obtenant des résultats de pointe en anglais utilisent aujourd'hui une sorte d'algorithme de tokenisation en sous-mots. + +30 +00:03:05,760 --> 00:03:12,320 +Ces approches aident à réduire la taille du vocabulaire en partageant des informations + +31 +00:03:12,320 --> 00:03:17,840 +entre différents mots, en ayant la possibilité de comprendre les préfixes et les suffixes comme tels. + +32 +00:03:18,480 --> 00:03:27,760 +Ils conservent le sens de mots très similaires, en reconnaissant les tokens similaires qui les composent. \ No newline at end of file diff --git a/subtitles/fr/16_the-tokenization-pipeline.srt b/subtitles/fr/16_the-tokenization-pipeline.srt new file mode 100644 index 000000000..4621eb53b --- /dev/null +++ b/subtitles/fr/16_the-tokenization-pipeline.srt @@ -0,0 +1,135 @@ +1 +00:00:05,440 --> 00:00:12,320 +Le pipeline de tokenisation. Dans cette vidéo, nous verrons comment un tokenizer convertit le texte brut en nombres + +2 +00:00:12,320 --> 00:00:18,080 +pour qu'un transformer puisse y donner un sens, comme lorsque nous exécutons ce code. Voici un bref + +3 +00:00:18,080 --> 00:00:24,400 +aperçu de ce qui se passe à l'intérieur de l'objet tokenizer. Le texte est d'abord divisé en tokens, qui + +4 +00:00:24,400 --> 00:00:31,280 +sont des mots, des parties de mots ou des symboles de ponctuation. Ensuite, le tokenizer ajoute des tokens spéciaux potentiels + +5 +00:00:31,280 --> 00:00:36,560 +et convertit chaque token en leur ID unique respectif, tel que défini par le vocabulaire du tokenizer. + +6 +00:00:37,520 --> 00:00:41,440 +Comme nous le verrons, cela ne se produit pas réellement dans cet ordre, mais le voir ainsi + +7 +00:00:41,440 --> 00:00:46,320 +est préférable pour comprendre ce qui se passe. La première étape consiste à diviser notre texte d'entrée + +8 +00:00:46,320 --> 00:00:53,840 +en tokens avec la méthode `tokenize`. Pour ce faire, le tokenizer peut d'abord effectuer certaines opérations + +9 +00:00:53,840 --> 00:00:58,000 +comme la mise en minuscules de tous les mots, puis suivre un ensemble de règles pour diviser le résultat en petits + +10 +00:00:58,000 --> 00:01:03,520 +morceaux de texte. La plupart des transformers utilisent un algorithme de tokenisation en sous-mots, + +11 +00:01:04,160 --> 00:01:08,720 +ce qui signifie qu'un mot donné peut être divisé en plusieurs tokens, comme « tokenize » + +12 +00:01:08,720 --> 00:01:13,360 +ici. Regardez les vidéos sur les algorithmes de tokenisation disponibles en description pour plus d'informations ! + +13 +00:01:14,480 --> 00:01:19,600 +Le préfixe « ## » que nous voyons devant « ize » est la convention utilisée par BERT pour indiquer que + +14 +00:01:19,600 --> 00:01:26,080 +ce token n'est pas le début d'un mot. Cependant, d'autres tokenizers peuvent utiliser des conventions différentes : + +15 +00:01:26,080 --> 00:01:31,040 +par exemple, le tokenizer d'ALBERT ajoutera un « __ » devant tous les + +16 +00:01:31,040 --> 00:01:36,640 +tokens précédés d'un espace. C'est une convention utilisée par les tokenizers de phrases. + +17 +00:01:38,320 --> 00:01:43,280 +La deuxième étape du pipeline de tokenisation consiste à associer ces tokens à leurs identifiants respectifs + +18 +00:01:43,280 --> 00:01:48,960 +tels que définis par le vocabulaire du tokenizer. C'est pourquoi nous devons télécharger un fichier lorsque nous + +19 +00:01:48,960 --> 00:01:53,600 +instancions un tokenizer avec la méthode `from_pretrained` : nous devons nous assurer que nous utilisons la même + +20 +00:01:53,600 --> 00:01:59,520 +association que lorsque le modèle a été pré-entraîné. Pour ce faire, nous utilisons la méthode `convert_tokens_to_ids`. + +21 +00:02:00,720 --> 00:02:05,360 +Vous avez peut-être remarqué que nous n'obtenons pas exactement le même résultat que dans notre première diapositive. Ou pas, + +22 +00:02:05,360 --> 00:02:09,840 +car cela ressemble à une liste de nombres aléatoires et dans ce cas, permettez-moi de vous rafraîchir la mémoire. + +23 +00:02:10,480 --> 00:02:13,680 +Nous avions un numéro au début et à la fin qui sont ici absents. + +24 +00:02:14,400 --> 00:02:20,160 +Ce sont les tokens spéciaux. Les tokens spéciaux sont ajoutés par la `méthode prepare_for_model`, + +25 +00:02:20,160 --> 00:02:25,280 +qui connaît les indices de ces tokens dans le vocabulaire et ajoute simplement les numéros appropriés. + +26 +00:02:28,320 --> 00:02:32,480 +Vous pouvez examiner les tokens spéciaux (et plus généralement la façon dont le tokenizer a modifié + +27 +00:02:32,480 --> 00:02:37,120 +votre texte) en utilisant la méthode `decode` sur les sorties de l'objet `tokenizer`. + +28 +00:02:38,240 --> 00:02:44,080 +En ce qui concerne le préfixe pour le début des mots/parties de mots, ces tokens spéciaux varient en fonction + +29 +00:02:44,080 --> 00:02:50,080 +du tokenizer que vous utilisez. Le tokenizer de BERT utilise [CLS] et [SEP] mais le tokenizer de RoBERTa + +30 +00:02:50,080 --> 00:02:57,520 +utilise des ancres de type html et . Maintenant que vous savez comment fonctionne le tokenizer, vous pouvez oublier + +31 +00:02:57,520 --> 00:03:02,560 +toutes ces méthodes intermédiaires et n'oubliez pas qu'il vous suffit de l'appeler sur vos textes d'entrée. + +32 +00:03:03,600 --> 00:03:06,880 +Cependant, les entrées ne contiennent pas les ID des entrées. + +33 +00:03:07,520 --> 00:03:11,600 +Pour savoir ce qu'est le masque d'attention, consultez la vidéo « Regroupement des entrées ». + +34 +00:03:12,160 --> 00:03:17,840 +Pour en savoir plus sur les token de type ID de type de token, regardez la vidéo « Comment prétraiter des paires de phrases ». \ No newline at end of file diff --git a/subtitles/fr/17_batching-inputs-together-(pytorch).srt b/subtitles/fr/17_batching-inputs-together-(pytorch).srt new file mode 100644 index 000000000..0564af0f3 --- /dev/null +++ b/subtitles/fr/17_batching-inputs-together-(pytorch).srt @@ -0,0 +1,115 @@ +1 +00:00:05,200 --> 00:00:10,880 +Comment grouper les entrées ensemble ? Dans cette vidéo, nous verrons comment regrouper des séquences d'entrée par batchs. + +2 +00:00:12,320 --> 00:00:16,560 +En général, les phrases que nous voulons faire passer dans notre modèle n'auront pas toutes la même longueur. + +3 +00:00:17,520 --> 00:00:21,280 +Ici, nous utilisons le modèle que nous avons vu dans le pipeline d'analyse des sentiments + +4 +00:00:21,840 --> 00:00:26,800 +et souhaitons classer deux phrases. Lors de la tokenisation et de l'association de chaque + +5 +00:00:26,800 --> 00:00:31,280 +token à ses ID d'entrée correspondants, nous obtenons deux listes de longueurs différentes. + +6 +00:00:33,040 --> 00:00:38,400 +Essayer de créer un tenseur ou un tableau NumPy à partir de ces deux listes entraînera une erreur, car + +7 +00:00:38,400 --> 00:00:44,560 +tous les tableaux et tenseurs doivent être rectangulaires. Une façon de dépasser cette limite consiste à faire en sorte que la + +8 +00:00:44,560 --> 00:00:50,160 +deuxième phrase ait la même longueur que la première en ajoutant un token spécial autant de fois que nécessaire. + +9 +00:00:51,360 --> 00:00:55,760 +Une autre façon serait de tronquer la première séquence à la longueur de la seconde mais nous + +10 +00:00:55,760 --> 00:01:00,720 +lui ferions perdre beaucoup d'informations qui pourraient être nécessaires pour classer correctement la phrase. + +11 +00:01:02,000 --> 00:01:06,720 +En général, nous ne tronquons les phrases que lorsqu'elles dépassent la longueur maximale que le + +12 +00:01:06,720 --> 00:01:14,000 +modèle peut gérer. La valeur utilisée pour remplir la deuxième phrase ne doit pas être choisie au hasard : le modèle + +13 +00:01:14,000 --> 00:01:19,200 +a été pré-entraîné avec un certain ID de rembourrage, que vous pouvez trouver dans `tokenizer.pad_token_id`. + +14 +00:01:20,800 --> 00:01:25,200 +Maintenant que nous avons complété nos phrases, nous pouvons en faire un batch. Si + +15 +00:01:25,200 --> 00:01:29,840 +nous passons les deux phrases au modèle séparément et regroupées, + +16 +00:01:29,840 --> 00:01:39,120 +nous remarquons que nous n'obtenons pas les mêmes résultats pour la phrase qui est rembourrée (ici la seconde). Y a t'il un bug dans la bibliothèque Transformers ? Non. + +17 +00:01:39,120 --> 00:01:42,880 +Si vous vous souvenez que les transformers utilisent beaucoup les couches d'attention, cela + +18 +00:01:42,880 --> 00:01:47,760 +ne devrait pas être une surprise totale. Lors du calcul de la représentation contextuelle de chaque token, + +19 +00:01:48,560 --> 00:01:54,320 +les couches d'attention examinent tous les autres mots de la phrase. Si nous n'avons que la phrase ou + +20 +00:01:54,320 --> 00:01:58,720 +la phrase avec plusieurs tokens de rembourrage ajoutés, il est logique que nous n'obtenions pas les mêmes valeurs. + +21 +00:02:00,000 --> 00:02:05,120 +Pour obtenir les mêmes résultats avec ou sans rembourrage, nous devons indiquer aux couches d'attention + +22 +00:02:05,120 --> 00:02:10,320 +qu'elles doivent ignorer ces tokens de rembourrage. Pour ce faire, créez un masque d'attention, + +23 +00:02:10,320 --> 00:02:16,560 +un tenseur ayant la même forme que les ID d'entrée, avec des 0 et des 1. Les 1 indiquent les + +24 +00:02:16,560 --> 00:02:21,840 +tokens que les couches d'attention doivent prendre en compte dans le contexte et les 0 les tokens qu'elles doivent ignorer. + +25 +00:02:23,360 --> 00:02:26,560 +Maintenant, passer ce masque d'attention avec les identifiants d'entrée + +26 +00:02:26,560 --> 00:02:30,720 +nous donnera les mêmes résultats que lorsque nous avons envoyé les deux phrases individuellement au modèle ! + +27 +00:02:32,160 --> 00:02:36,640 +Tout cela est fait en coulisses par le tokenizer lorsque vous l'appliquez à plusieurs phrases + +28 +00:02:36,640 --> 00:02:41,280 +avec l'argument `padding=True`. Il appliquera le rembourrage avec la valeur appropriée + +29 +00:02:41,280 --> 00:02:49,840 +aux phrases plus petites et créera le masque d'attention approprié. \ No newline at end of file diff --git a/subtitles/fr/18_batching-inputs-together-(tensorflow).srt b/subtitles/fr/18_batching-inputs-together-(tensorflow).srt new file mode 100644 index 000000000..26a8f98a8 --- /dev/null +++ b/subtitles/fr/18_batching-inputs-together-(tensorflow).srt @@ -0,0 +1,111 @@ +1 +00:00:05,120 --> 00:00:10,880 +Comment grouper les entrées ensemble ? Dans cette vidéo, nous verrons comment regrouper des séquences d'entrée par batchs. + +2 +00:00:12,480 --> 00:00:16,560 +En général, les phrases que nous souhaitons faire passer dans notre modèle n'auront pas toutes la même + +3 +00:00:16,560 --> 00:00:23,520 +longueur. Ici, nous utilisons le modèle que nous avons vu dans le pipeline d'analyse des sentiments et souhaitons classer + +4 +00:00:23,520 --> 00:00:29,760 +deux phrases. Lors de la tokenisation et de l'association de chaque token à ses ID d'entrée correspondants, + +5 +00:00:29,760 --> 00:00:31,680 +nous obtenons deux listes de longueurs différentes. + +6 +00:00:33,120 --> 00:00:38,240 +Essayer de créer un tenseur ou un tableau NumPy à partir de ces deux listes entraînera une erreur, car + +7 +00:00:38,240 --> 00:00:44,320 +tous les tableaux et tenseurs doivent être rectangulaires. Une façon de dépasser cette limite consiste à faire en sorte que la + +8 +00:00:44,320 --> 00:00:50,080 +deuxième phrase ait la même longueur que la première en ajoutant un token spécial autant de fois que nécessaire. + +9 +00:00:51,040 --> 00:00:55,360 +Une autre façon serait de tronquer la première séquence à la longueur de la seconde, mais nous + +10 +00:00:55,360 --> 00:01:00,080 +leur ferions perdre beaucoup d'informations qui pourraient être nécessaires pour classer correctement la phrase. + +11 +00:01:01,040 --> 00:01:05,760 +En général, nous ne tronquons les phrases que lorsqu'elles dépassent la longueur maximale que le + +12 +00:01:05,760 --> 00:01:12,560 +modèle peut gérer. La valeur utilisée pour remplir la deuxième phrase ne doit pas être choisie au hasard : le modèle + +13 +00:01:12,560 --> 00:01:18,000 +a été pré-entraîné avec un certain ID de rembourrage, que vous pouvez trouver dans `tokenizer.pad_token_id`. + +14 +00:01:19,760 --> 00:01:22,640 +Maintenant que nous avons complété nos phrases, nous pouvons en faire un batch. + +15 +00:01:23,920 --> 00:01:28,400 +Si nous passons les deux phrases au modèle séparément et regroupées, + +16 +00:01:28,400 --> 00:01:37,360 +nous remarquons que nous n'obtenons pas les mêmes résultats pour la phrase qui est rembourrée (ici la seconde). Y a t'il un bug dans la bibliothèque Transformers ? Non. + +17 +00:01:37,360 --> 00:01:41,440 +Si vous vous souvenez que les transformers utilisent beaucoup les couches d'attention, cela + +18 +00:01:41,440 --> 00:01:46,800 +ne devrait pas être une surprise totale : lors du calcul de la représentation contextuelle de chaque token, + +19 +00:01:46,800 --> 00:01:52,800 +les couches d'attention examinent tous les autres mots de la phrase. Si nous n'avons que la phrase ou + +20 +00:01:52,800 --> 00:01:57,200 +la phrase avec plusieurs tokens de remplissage ajoutés, il est logique que nous n'obtenions pas les mêmes valeurs. + +21 +00:01:58,560 --> 00:02:03,520 +Pour obtenir les mêmes résultats avec ou sans rembourrage, nous devons indiquer aux couches d'attention + +22 +00:02:03,520 --> 00:02:08,640 +qu'elles doivent ignorer ces tokens de rembourrage. Pour ce faire, créez un masque d'attention, + +23 +00:02:08,640 --> 00:02:15,920 +un tenseur ayant la même forme que les ID d'entrée, avec des 0 et des 1. Les 1 indiquent les tokens que les + +24 +00:02:15,920 --> 00:02:22,160 +couches d'attention doivent prendre en compte dans le contexte et les 0 les tokens qu'ils doivent ignorer. Maintenant, + +25 +00:02:22,160 --> 00:02:27,040 +transmettre ce masque d'attention avec les identifiants d'entrée nous donnera les mêmes résultats que lorsque nous avons envoyé + +26 +00:02:27,040 --> 00:02:33,600 +les deux phrases individuellement au modèle ! Tout cela est fait en coulisses par le tokenizer + +27 +00:02:33,600 --> 00:02:39,680 +lorsque vous l'appliquez à plusieurs phrases avec l'indicateur `padding=True`. Il appliquera le rembourrage avec + +28 +00:02:39,680 --> 00:02:49,840 +la valeur appropriée aux phrases plus petites et créera le masque d'attention approprié. \ No newline at end of file diff --git a/subtitles/fr/19_hugging-face-datasets-overview-(pytorch).srt b/subtitles/fr/19_hugging-face-datasets-overview-(pytorch).srt new file mode 100644 index 000000000..3a368a693 --- /dev/null +++ b/subtitles/fr/19_hugging-face-datasets-overview-(pytorch).srt @@ -0,0 +1,131 @@ +1 +00:00:05,120 --> 00:00:11,520 +La bibliothèque Datasets de Hugging Face : un aperçu rapide. La bibliothèque Hugging Face Datasets + +2 +00:00:11,520 --> 00:00:16,560 +est une bibliothèque qui fournit une API pour télécharger rapidement de nombreux jeux de données publics et les prétraiter. + +3 +00:00:17,360 --> 00:00:22,560 +Dans cette vidéo, nous allons explorer comment faire cela. La partie téléchargement est simple : avec la + +4 +00:00:22,560 --> 00:00:28,400 +fonction `load_dataset`, vous pouvez directement télécharger et mettre en cache un jeu de données à partir de son identifiant sur le Hub des jeux de données. + +5 +00:00:29,520 --> 00:00:32,720 +Ici, nous récupérons le jeu de données MRPC à partir du benchmark GLUE, + +6 +00:00:33,360 --> 00:00:38,320 +qui est un jeu de données contenant des paires de phrases dont la tâche consiste à déterminer les paraphrases. + +7 +00:00:39,520 --> 00:00:45,440 +L'objet renvoyé par la fonction `load_dataset` est un `DatasetDict`, qui est une sorte de dictionnaire + +8 +00:00:45,440 --> 00:00:51,120 +contenant chaque échantillon de notre jeu de données. Nous pouvons accéder à chaque échantillon en l'indexant avec son nom. + +9 +00:00:52,000 --> 00:00:57,440 +Cette échantillon est alors une instance de la classe `Dataset`, avec des colonnes (ici `sentence1`, + +10 +00:00:57,440 --> 00:01:04,240 +`sentence2`, `label` et `idx`) et des lignes. Nous pouvons accéder à un élément donné par son index. + +11 +00:01:05,200 --> 00:01:10,000 +La chose formidable à propos de la bibliothèque Datasets d'Hugging Face est que tout est enregistré sur le disque à l' + +12 +00:01:10,000 --> 00:01:15,520 +aide d'Apache Arrow, ce qui signifie que même si votre jeu de données est énorme, vous ne manquerez pas de RAM. + +13 +00:01:16,080 --> 00:01:21,920 +Seuls les éléments que vous demandez sont chargés en mémoire. Accéder à une tranche de votre jeu de données est + +14 +00:01:21,920 --> 00:01:26,720 +aussi simple qu'un élément. Le résultat est alors un dictionnaire avec une liste de valeurs pour chaque clé + +15 +00:01:27,280 --> 00:01:31,600 +(ici la liste des étiquettes, la liste des premières phrases et la liste des secondes phrases). + +16 +00:01:33,440 --> 00:01:38,880 +L'attribut `features` d'un jeu de données nous donne plus d'informations sur ses colonnes. En particulier, + +17 +00:01:38,880 --> 00:01:45,280 +nous pouvons voir ici qu'il nous donne la correspondance entre les nombres entiers et les noms des étiquettes. 0 + +18 +00:01:45,280 --> 00:01:51,760 +signifie non équivalent et 1 pour équivalent. Pour prétraiter tous les éléments de notre jeu de données, + +19 +00:01:51,760 --> 00:01:56,800 +nous devons les tokeniser. Jetez un œil à la vidéo « Comment prétraiter les paires de phrases » pour un rappel, + +20 +00:01:57,360 --> 00:02:02,320 +mais il vous suffit d'envoyer les deux phrases au tokenizer avec quelques arguments de mots clés supplémentaires. + +21 +00:02:03,520 --> 00:02:08,560 +Ici, nous indiquons une longueur maximale de 128 et de rembourrer les entrées plus courtes que cette longueur, + +22 +00:02:08,560 --> 00:02:14,320 +tronquer les entrées qui sont plus longues. Nous mettons tout cela dans une `tokenize_function` que nous pouvons + +23 +00:02:14,320 --> 00:02:20,240 +appliquer directement à toutes les divisions de notre jeu de données avec la méthode `map`. Tant que la fonction renvoie un + +24 +00:02:20,240 --> 00:02:25,680 +objet de type dictionnaire, la méthode `map` ajoutera de nouvelles colonnes si nécessaire ou mettra à jour celles existantes. + +25 +00:02:27,360 --> 00:02:31,840 +Pour accélérer le prétraitement et tirer parti du fait que notre tokenizer est soutenu par Rust + +26 +00:02:31,840 --> 00:02:36,880 +grâce à la bibliothèque Tokenizers d'Hugging Face, nous pouvons traiter plusieurs éléments en même temps pour + +27 +00:02:36,880 --> 00:02:42,160 +notre `tokenize_function`, en utilisant l'argument `batched=True`. Étant donné que le tokenizer peut gérer la liste + +28 +00:02:42,160 --> 00:02:48,880 +des premières/secondes phrases, la `tokenize_function` n'a pas besoin de changer pour cela. Vous pouvez également utiliser le + +29 +00:02:49,440 --> 00:02:56,400 +multitraitement avec la méthode `map`, consultez sa documentation ! Une fois cela fait, nous sommes presque + +30 +00:02:56,400 --> 00:03:01,920 +prêts pour l'entraînement : nous supprimons simplement les colonnes dont nous n'avons plus besoin avec la méthode `remove_columns`, renommons + +31 +00:03:01,920 --> 00:03:06,640 +`label` en `labels` (puisque les modèles de la bibliothèque Transformers de Hugging Face attendent cela) + +32 +00:03:07,440 --> 00:03:14,000 +et définissons le format de sortie sur notre « backend » souhaité : Torch, TensorFlow ou Numpy. Si nécessaire, + +33 +00:03:14,000 --> 00:03:17,840 +nous pouvons également générer un court échantillon d'un jeu de données à l'aide de la méthode `select`. \ No newline at end of file diff --git a/subtitles/fr/20_hugging-face-datasets-overview-(tensorflow).srt b/subtitles/fr/20_hugging-face-datasets-overview-(tensorflow).srt new file mode 100644 index 000000000..a0cdbe1c0 --- /dev/null +++ b/subtitles/fr/20_hugging-face-datasets-overview-(tensorflow).srt @@ -0,0 +1,131 @@ +1 +00:00:05,200 --> 00:00:11,200 +La bibliothèque Datasets de Hugging Face : un aperçu rapide. La bibliothèque Hugging Face Datasets + +2 +00:00:11,200 --> 00:00:15,920 +est une bibliothèque qui fournit une API pour télécharger rapidement de nombreux jeux de données publics et les prétraiter. + +3 +00:00:16,880 --> 00:00:22,480 +Dans cette vidéo, nous allons explorer comment faire cela. La partie téléchargement est simple : avec la + +4 +00:00:22,480 --> 00:00:27,760 +fonction `load_dataset`, vous pouvez directement télécharger et mettre en cache un jeu de données à partir de son identifiant sur le Dataset hub. + +5 +00:00:29,040 --> 00:00:34,160 +Ici, nous récupérons le jeu de données MRPC à partir du benchmark GLUE, qui est un jeu de données + +6 +00:00:34,160 --> 00:00:38,000 +contenant des paires de phrases dont la tâche consiste à déterminer les paraphrases. + +7 +00:00:39,360 --> 00:00:44,880 +L'objet renvoyé par la fonction `load_dataset` est un `DatasetDict`, qui est une sorte de dictionnaire + +8 +00:00:44,880 --> 00:00:50,800 +contenant chaque échantillon de notre jeu de données. Nous pouvons accéder à chaque échantillon en l'indexant avec son nom. + +9 +00:00:51,520 --> 00:00:55,120 +Cette échantillon est alors une instance de la classe `Dataset`, avec des colonnes + +10 +00:00:55,680 --> 00:01:04,000 +(ici `sentence1`, `sentence2`, `label` et `idx`) et des lignes. On peut accéder à un élément donné par son index. + +11 +00:01:04,880 --> 00:01:10,240 +La chose formidable à propos de la bibliothèque Datasets d'Hugging Face est que tout est enregistré sur le disque à l'aide d' + +12 +00:01:10,240 --> 00:01:15,280 +Apache Arrow, ce qui signifie que même si votre jeu de données est énorme, vous ne manquerez pas de RAM. + +13 +00:01:15,920 --> 00:01:21,760 +Seuls les éléments que vous demandez sont chargés en mémoire. Accéder à une tranche de votre jeu de données est + +14 +00:01:21,760 --> 00:01:27,760 +aussi simple qu'un élément. Le résultat est alors un dictionnaire avec une liste de valeurs pour chaque clé + +15 +00:01:28,480 --> 00:01:35,040 +(ici la liste des étiquettes, la liste des premières phrases et la liste des secondes phrases). L' + +16 +00:01:35,040 --> 00:01:40,720 +attribut `features` d'un jeu de données nous donne plus d'informations sur ses colonnes. En particulier, + +17 +00:01:40,720 --> 00:01:45,040 +nous pouvons voir ici qu'il nous donne la correspondance entre les nombres entiers et les noms des étiquettes. + +18 +00:01:45,920 --> 00:01:53,840 +0 signifie non équivalent et 1 pour équivalent. Pour prétraiter tous les éléments de notre jeu de données, + +19 +00:01:53,840 --> 00:01:59,120 +nous devons les tokeniser. Jetez un œil à la vidéo « Comment prétraiter des paires de phrases » pour un rappel, + +20 +00:01:59,840 --> 00:02:04,480 +mais il vous suffit d'envoyer les deux phrases au tokenizer avec quelques arguments de mots clés supplémentaires. + +21 +00:02:05,760 --> 00:02:11,200 +Ici, nous indiquons une longueur maximale de 128 et de rembourrer les entrées plus courtes que cette longueur, + +22 +00:02:11,200 --> 00:02:17,040 +tronquer les entrées qui sont plus longues. Nous mettons tout cela dans une fonction `tokenize_function` que nous pouvons + +23 +00:02:17,040 --> 00:02:22,320 +appliquer directement à toutes les divisions de notre jeu de données avec la méthode `map`. Tant que la fonction renvoie un + +24 +00:02:22,320 --> 00:02:27,760 +objet de type dictionnaire, la méthode `map` ajoutera de nouvelles colonnes si nécessaire ou mettra à jour celles existantes. + +25 +00:02:29,840 --> 00:02:34,960 +Pour accélérer le prétraitement et tirer parti du fait que notre tokenizer est soutenu par Rust + +26 +00:02:34,960 --> 00:02:40,320 +grâce à la bibliothèque Tokenizers d'Hugging Face, nous pouvons traiter plusieurs éléments en même temps + +27 +00:02:40,320 --> 00:02:46,800 +vers notre `tokenize_function`, en utilisant l'argument `batched=True`. Étant donné que le tokenizer peut gérer la liste + +28 +00:02:46,800 --> 00:02:53,360 +des premières/secondes phrases, la `tokenize_function` n'a pas besoin de changer pour cela. Vous pouvez également utiliser le + +29 +00:02:53,360 --> 00:03:00,320 +multitraitement avec la méthode `map`, consultez sa documentation ! Une fois cela fait, nous sommes presque + +30 +00:03:00,320 --> 00:03:05,360 +prêts pour l'entraînement : nous supprimons simplement les colonnes dont nous n'avons plus besoin avec la méthode `remove_columns`, renommons + +31 +00:03:05,920 --> 00:03:10,320 +`label` en `labels` (puisque les modèles de la bibliothèque Transformers de Hugging Face attendent cela) + +32 +00:03:11,200 --> 00:03:17,280 +et définissons le format de sortie sur notre « backend » souhaité : Torch, TensorFlow ou Numpy. Si nécessaire, + +33 +00:03:17,280 --> 00:03:27,440 +nous pouvons également générer un court échantillon d'un jeu de données à l'aide de la méthode `select`. \ No newline at end of file diff --git a/subtitles/fr/21_preprocessing-sentence-pairs-(pytorch).srt b/subtitles/fr/21_preprocessing-sentence-pairs-(pytorch).srt new file mode 100644 index 000000000..6b46f4318 --- /dev/null +++ b/subtitles/fr/21_preprocessing-sentence-pairs-(pytorch).srt @@ -0,0 +1,119 @@ +1 +00:00:05,200 --> 00:00:11,680 +Comment prétraiter des paires de phrases ? Nous avons vu comment tokeniser des phrases simples et + +2 +00:00:11,680 --> 00:00:18,080 +les regrouper dans la vidéo « Regroupement des entrées ». Si ce code ne vous semble pas familier, + +3 +00:00:18,080 --> 00:00:24,160 +assurez-vous de revoir cette vidéo ! Ici, nous nous concentrerons sur les tâches qui classent des paires de phrases. + +4 +00:00:25,440 --> 00:00:30,960 +Par exemple, nous pouvons vouloir classer si deux textes sont des paraphrases ou non. Voici un exemple + +5 +00:00:30,960 --> 00:00:36,320 +tiré du jeu de données Quora Question Pairs, qui se concentre sur l'identification de questions en double. + +6 +00:00:37,360 --> 00:00:42,200 +Dans la première paire, les deux questions sont des doublons ; dans la seconde, elles + +7 +00:00:43,360 --> 00:00:47,120 +ne le sont pas. Un autre problème de classification de paires est lorsque nous voulons savoir si deux phrases + +8 +00:00:47,120 --> 00:00:54,000 +sont logiquement liées ou non (un problème appelé « Natural Language Inference » ou NLI). Dans cet + +9 +00:00:54,000 --> 00:00:59,680 +exemple tiré du jeu de données MultiNLI, nous avons une paire de phrases pour chaque étiquette possible : + +10 +00:00:59,680 --> 00:01:04,560 +contradiction, neutre ou implication (ce qui est une façon élégante de dire que la première phrase + +11 +00:01:04,560 --> 00:01:09,280 +implique la seconde). La classification des paires de phrases est donc un problème qui mérite d'être étudié. + +12 +00:01:10,080 --> 00:01:14,880 +En fait, dans le benchmark GLUE (qui est un benchmark académique pour la classification de texte), + +13 +00:01:15,600 --> 00:01:19,600 +8 des 10 ensembles de données sont axés sur des tâches utilisant des paires de phrases. + +14 +00:01:20,720 --> 00:01:24,240 +C'est pourquoi les modèles comme BERT sont souvent pré-entraînés avec un double objectif : + +15 +00:01:25,120 --> 00:01:29,920 +en plus de l'objectif de modélisation du langage, ils ont souvent un objectif lié aux paires de phrases. + +16 +00:01:31,040 --> 00:01:36,720 +Par exemple, lors de la pré-entraînement, BERT voit des paires de phrases et doit prédire à la fois la + +17 +00:01:36,720 --> 00:01:41,040 +valeur des tokens masqués de manière aléatoire et si la deuxième phrase découle de la première. + +18 +00:01:42,800 --> 00:01:46,640 +Heureusement, le tokenizer de la bibliothèque Transformers dispose d'une API sympa + +19 +00:01:46,640 --> 00:01:52,000 +pour gérer les paires de phrases : il vous suffit de les passer en tant que deux arguments au tokenizer. + +20 +00:01:53,200 --> 00:01:57,600 +En plus des ID d'entrée et du masque d'attention que nous avons déjà étudiés, il renvoie un nouveau + +21 +00:01:57,600 --> 00:02:02,800 +champ appelé `token_type_ids`, qui indique au modèle quels tokens appartiennent à la première phrase + +22 +00:02:03,440 --> 00:02:09,680 +et lesquels appartiennent à la deuxième phrase. En zoomant un peu, voici les `input_ids`, + +23 +00:02:09,680 --> 00:02:14,480 +alignés avec les tokens auxquels ils correspondent, leur `token_type_ids` et `attention_mask` + +24 +00:02:14,480 --> 00:02:21,360 +respectifs. Nous pouvons voir que le tokenizer a également ajouté des tokens spéciaux, nous avons donc un token [CLS], les tokens de la + +25 +00:02:21,360 --> 00:02:28,720 +première phrase, un token [SEP], les tokens de la deuxième phrase et un token [SEP] final. Si nous avons + +26 +00:02:28,720 --> 00:02:33,760 +plusieurs paires de phrases, nous pouvons les tokeniser ensemble en passant la liste des premières phrases, + +27 +00:02:34,480 --> 00:02:39,360 +puis la liste des secondes phrases et tous les arguments mots-clés que nous avons déjà étudiés, comme + +28 +00:02:39,360 --> 00:02:45,600 +`padding=True`. En zoomant sur le résultat, nous pouvons voir comment le tokenizer a ajouté un rembourrage à la deuxième paire + +29 +00:02:45,600 --> 00:02:51,200 +de phrases, pour que les deux sorties aient la même longueur, et ait correctement traité les `token_type_ids` + +30 +00:02:51,200 --> 00:03:03,520 +et les `attention_mask` pour les deux phrases. Tout est alors prêt à passer par notre modèle ! \ No newline at end of file diff --git a/subtitles/fr/22_preprocessing-sentence-pairs-(tensorflow).srt b/subtitles/fr/22_preprocessing-sentence-pairs-(tensorflow).srt new file mode 100644 index 000000000..69d6a4af3 --- /dev/null +++ b/subtitles/fr/22_preprocessing-sentence-pairs-(tensorflow).srt @@ -0,0 +1,123 @@ +1 +00:00:05,440 --> 00:00:11,760 +Comment prétraiter des paires de phrases ? Nous avons vu comment tokeniser des phrases simples et + +2 +00:00:11,760 --> 00:00:17,280 +les regrouper dans la vidéo « Regroupement des entrées ». Si ce code ne vous semble pas familier, + +3 +00:00:17,840 --> 00:00:23,680 +assurez-vous de revoir cette vidéo ! Ici, nous nous concentrerons sur les tâches qui classent des paires de phrases. + +4 +00:00:24,720 --> 00:00:30,400 +Par exemple, nous pouvons vouloir classer si deux textes sont des paraphrases ou non. Voici un exemple + +5 +00:00:30,400 --> 00:00:35,520 +tiré du jeu de données Quora Question Pairs, qui se concentre sur l'identification de questions en double. + +6 +00:00:36,960 --> 00:00:39,760 +Dans la première paire, les deux questions sont des doublons ; + +7 +00:00:40,400 --> 00:00:45,520 +dans la seconde, elles ne le sont pas. Un autre problème de classification de paires est lorsque nous voulons savoir + +8 +00:00:45,520 --> 00:00:51,920 +si deux phrases sont logiquement liées ou non (un problème appelé « Natural Language Inference » ou NLI). + +9 +00:00:52,880 --> 00:00:58,480 +Dans cet exemple tiré du jeu de données MultiNLI, nous avons une paire de phrases pour chaque + +10 +00:00:58,480 --> 00:01:04,560 +étiquette possible : contradiction, neutre ou implication (ce qui est une façon élégante de dire que la première phrase + +11 +00:01:04,560 --> 00:01:12,240 +implique la seconde). Ainsi, classer des paires de phrases est un problème qui mérite d'être étudié. En fait, + +12 +00:01:12,240 --> 00:01:15,840 +dans le benchmark GLUE (qui est un benchmark académique pour la classification de texte), + +13 +00:01:16,640 --> 00:01:20,800 +8 des 10 ensembles de données sont axés sur des tâches utilisant des paires de phrases. + +14 +00:01:21,920 --> 00:01:26,320 +C'est pourquoi les modèles comme BERT sont souvent pré-entraînés avec un double objectif : + +15 +00:01:26,320 --> 00:01:31,040 +en plus de l'objectif de modélisation du langage, ils ont souvent un objectif lié aux paires de phrases. + +16 +00:01:31,840 --> 00:01:37,520 +Par exemple, lors de la pré-entraînement, BERT voit des paires de phrases et doit prédire à la fois la + +17 +00:01:37,520 --> 00:01:42,080 +valeur des tokens masqués de manière aléatoire et si la deuxième phrase découle de la première. + +18 +00:01:43,840 --> 00:01:47,920 +Heureusement, le tokenizer de la bibliothèque Transformers dispose d'une API sympa + +19 +00:01:47,920 --> 00:01:53,840 +pour gérer les paires de phrases : il vous suffit de les passer en tant que deux arguments au tokenizer. + +20 +00:01:54,640 --> 00:01:59,040 +En plus des ID d'entrée et du masque d'attention que nous avons déjà étudiés, il renvoie un nouveau + +21 +00:01:59,040 --> 00:02:04,320 +champ appelé `token_type_ids`, qui indique au modèle quels tokens appartiennent à la première phrase + +22 +00:02:04,880 --> 00:02:11,280 +et lesquels appartiennent à la deuxième phrase. En zoomant un peu, voici les `input_ids`, + +23 +00:02:11,280 --> 00:02:16,800 +alignés sur les tokens auxquels ils correspondent, leur `token_type_ids` et leur `attention_mask` respectifs. + +24 +00:02:18,240 --> 00:02:23,440 +Nous pouvons voir que le tokenizer a également ajouté des tokens spéciaux, nous avons donc un token [CLS], les tokens + +25 +00:02:23,440 --> 00:02:29,920 +de la première phrase, un token [SEP], les tokens de la deuxième phrase et un token [SEP] final. + +26 +00:02:31,440 --> 00:02:36,640 +Si nous avons plusieurs paires de phrases, nous pouvons les tokeniser ensemble en passant la liste des + +27 +00:02:36,640 --> 00:02:42,880 +premières phrases, puis la liste des secondes phrases et tous les arguments mots-clés que nous avons déjà étudiés, + +28 +00:02:42,880 --> 00:02:48,800 +comme `padding=True`. En zoomant sur le résultat, nous pouvons voir comment le tokenizer a ajouté un rembourrage + +29 +00:02:48,800 --> 00:02:52,480 +à la deuxième paire de phrases, pour faire en sorte que les deux sorties aient la même longueur, + +30 +00:02:53,440 --> 00:02:57,280 +et traiter correctement les `token_type_ids` et les `attention_mask` pour les deux phrases. + +31 +00:02:58,720 --> 00:03:03,840 +Tout est alors prêt à passer dans notre modèle ! \ No newline at end of file diff --git a/subtitles/fr/23_what-is-dynamic-padding.srt b/subtitles/fr/23_what-is-dynamic-padding.srt new file mode 100644 index 000000000..0ccdd2c22 --- /dev/null +++ b/subtitles/fr/23_what-is-dynamic-padding.srt @@ -0,0 +1,159 @@ +1 +00:00:05,270 --> 00:00:07,640 +Qu'est-ce que le rembourrage dynamique ? + +2 +00:00:07,640 --> 00:00:12,620 +Dans la vidéo « Regroupement des entrées », nous avons vu que pour pouvoir regrouper des entrées + +3 +00:00:12,620 --> 00:00:17,320 +de différentes longueurs dans le même batch, nous devons ajouter des tokens de rembourrage à toutes les + +4 +00:00:17,320 --> 00:00:20,520 +entrées courtes jusqu'à ce qu'elles aient toutes la même longueur. + +5 +00:00:20,520 --> 00:00:26,300 +Ici par exemple, la phrase la plus longue est la troisième, et nous devons ajouter 5, 2 et + +6 +00:00:26,300 --> 00:00:32,509 +7 tokens [PAD] aux autres pour avoir quatre phrases de même longueur. + +7 +00:00:32,509 --> 00:00:37,530 +Lorsque vous traitez un jeu de données complet, il existe différentes stratégies de rembourrage que nous pouvons appliquer. + +8 +00:00:37,530 --> 00:00:41,870 +La plus évidente consiste à remplir tous les éléments du jeu de données à la même longueur : la longueur + +9 +00:00:41,870 --> 00:00:44,129 +de l'échantillon le plus long. + +10 +00:00:44,129 --> 00:00:48,450 +Cela nous donnera alors des batchs qui ont tous la même forme déterminée par la longueur maximale de la séquence + +11 +00:00:48,450 --> 00:00:49,450 +. + +12 +00:00:49,450 --> 00:00:54,039 +L'inconvénient est que les batchs composés de phrases courtes auront beaucoup de tokens de rembourrage + +13 +00:00:54,039 --> 00:01:00,080 +qui introduisent plus de calculs dans le modèle dont nous n'avons finalement pas besoin. + +14 +00:01:00,080 --> 00:01:05,320 +Pour éviter cela, une autre stratégie consiste à remplir les éléments lorsque nous les regroupons, + +15 +00:01:05,320 --> 00:01:08,240 +jusqu'à la phrase la plus longue à l'intérieur du batch. + +16 +00:01:08,240 --> 00:01:12,880 +De cette façon, les batchs composés d'entrées courtes seront plus petits que le batch contenant + +17 +00:01:12,880 --> 00:01:15,600 +la phrase la plus longue de l'jeu de données. + +18 +00:01:15,600 --> 00:01:19,090 +Cela donnera une belle accélération sur CPU et GPU. + +19 +00:01:19,090 --> 00:01:23,130 +L'inconvénient est que tous les batchs auront alors des formes différentes, ce qui ralentit l'entraînement + +20 +00:01:23,130 --> 00:01:24,790 +sur d'autres accélérateurs comme les TPU. + +21 +00:01:24,790 --> 00:01:28,850 +Voyons comment appliquer les deux stratégies en pratique. + +22 +00:01:28,850 --> 00:01:34,750 +Nous avons en fait vu comment appliquer un rembourrage fixe dans la vidéo « Vue d'ensemble de Datasets », lorsque nous avons prétraité + +23 +00:01:34,750 --> 00:01:39,320 +le jeu de données MRPC. Après avoir chargé le jeu de données et le tokenizer, nous avons appliqué la segmentation + +24 +00:01:39,320 --> 00:01:45,260 +à tous les jeux de données avec rembourrage et tronquez pour que tous les échantillons de longueur 128. + +25 +00:01:45,260 --> 00:01:51,630 +En conséquence, si nous passons ce jeu de données à un PyTorch `DataLoader`, nous obtenons des batchs de + +26 +00:01:51,630 --> 00:01:57,079 +forme `batch_siez` (ici 16) par 128. + +27 +00:01:57,079 --> 00:02:01,950 +Pour appliquer un rembourrage dynamique, nous devons reporter le rembourrage à la préparation du batch, nous supprimons donc + +28 +00:02:01,950 --> 00:02:04,789 +cette partie de notre `tokenize_fonction`. + +29 +00:02:04,789 --> 00:02:08,569 +Nous laissons toujours la partie troncature afin que les entrées qui sont plus grandes que la longueur maximale + +30 +00:02:08,569 --> 00:02:14,069 +acceptée par le modèle (généralement 512) soient tronquées à cette longueur. + +31 +00:02:14,069 --> 00:02:17,629 +Ensuite, nous remplissons nos échantillons dynamiquement en utilisant un assembleur de données. + +32 +00:02:17,629 --> 00:02:22,110 +Ces classes de la bibliothèque Transformers sont chargées d'appliquer tout le + +33 +00:02:22,110 --> 00:02:27,970 +traitement final nécessaire avant de former un batch. Ici `DataCollatorWithPadding` remplira les + +34 +00:02:27,970 --> 00:02:32,200 +échantillons à la longueur maximale à l'intérieur du batch de phrases. + +35 +00:02:32,200 --> 00:02:36,790 +Nous le transmettons au `DataLoader` de PyTorch en tant que fonction d'assemblement, puis observons que les batchs + +36 +00:02:36,790 --> 00:02:42,950 +générés ont des longueurs différentes, bien en dessous des 128 d'avant. + +37 +00:02:42,950 --> 00:02:48,200 +Le traitement par batchs dynamique sera presque toujours plus rapide sur CPU et GPU, vous devez donc l'appliquer si + +38 +00:02:48,200 --> 00:02:49,200 +vous le pouvez. + +39 +00:02:49,200 --> 00:02:53,879 +N'oubliez pas de revenir au rembourrage fixe si vous exécutez votre script d'entraînement sur TPU ou si vous avez + +40 +00:02:53,879 --> 00:03:00,599 +besoin de batchs de formes fixes. \ No newline at end of file diff --git a/subtitles/fr/24_the-trainer-api.srt b/subtitles/fr/24_the-trainer-api.srt new file mode 100644 index 000000000..ca39c82d6 --- /dev/null +++ b/subtitles/fr/24_the-trainer-api.srt @@ -0,0 +1,139 @@ +1 +00:00:05,280 --> 00:00:11,200 +L'API Trainer. La bibliothèque Transformers fournit une API Trainer qui vous permet de + +2 +00:00:11,200 --> 00:00:17,040 +finetuner facilement des transformers sur votre propre jeu de données. La classe Trainer prend vos jeux de données, + +3 +00:00:17,040 --> 00:00:22,240 +votre modèle ainsi que les hyperparamètres d'entraînement et peut effectuer l'entraînement sur n'importe quel type de + +4 +00:00:22,240 --> 00:00:30,160 +configuration (CPU, GPU, multi-GPU, TPU). Elle peut également calculer les prédictions sur n'importe quel jeu de données et, si + +5 +00:00:30,160 --> 00:00:36,720 +vous avez fourni des métriques, évaluer votre modèle sur n'importe quel jeu de données. Elle peut également gérer le traitement final des données + +6 +00:00:36,720 --> 00:00:41,760 +comme le rembourrage dynamique tant que vous fournissez le tokenizer ou un assembleur de données donné. + +7 +00:00:43,040 --> 00:00:48,160 +Nous allons essayer cette API sur le jeu de données MRPC car il est relativement petit et facile à prétraiter. + +8 +00:00:49,520 --> 00:00:54,800 +Comme nous l'avons vu dans la vidéo « Vue d'ensemble de Datasets », voici comment nous pouvons le prétraiter. Nous n'appliquons pas de + +9 +00:00:54,800 --> 00:00:59,840 +rembourrage pendant le prétraitement car nous utiliserons un rembourrage dynamique avec notre `DataCollatorWithPadding`. + +10 +00:01:00,960 --> 00:01:05,440 +Notez que nous n'effectuons pas les étapes finales consistant à renommer/supprimer des colonnes ou à définir le format + +11 +00:01:05,440 --> 00:01:11,280 +sur les tenseurs de Torch : Trainer fera tout cela automatiquement pour nous en analysant la + +12 +00:01:11,280 --> 00:01:18,080 +signature du modèle. Les dernières étapes avant de créer le Trainer consistent à définir notre modèle et certains + +13 +00:01:18,080 --> 00:01:24,400 +hyperparamètres d'entraînement. Nous avons vu comment effectuer la première dans la vidéo d'API des modèles. Pour le second, + +14 +00:01:24,400 --> 00:01:29,600 +nous utilisons la classe `TrainingArguments`. Elle n'a besoin que d'un chemin vers un dossier où les résultats et les checkpoints + +15 +00:01:29,600 --> 00:01:34,240 +seront enregistrés, mais vous pouvez également personnaliser tous les hyperparamètres que le Trainer utilisera : + +16 +00:01:34,240 --> 00:01:39,600 +le taux d'apprentissage, le nombre d'époques d'entraînement, etc. Il est alors très facile de créer un Trainer et de + +17 +00:01:39,600 --> 00:01:44,720 +lancer une entraînement. Cela devrait afficher une barre de progression et après quelques minutes (si vous exécutez + +18 +00:01:44,720 --> 00:01:50,480 +sur un GPU), vous devriez avoir terminé l'entraînement. Cependant, le résultat sera plutôt anticlimatique, + +19 +00:01:50,480 --> 00:01:54,880 +car vous n'obtiendrez qu'une perte d'entraînement qui ne vous dit rien sur les performances de votre + +20 +00:01:54,880 --> 00:01:59,920 +modèle. En effet, nous n'avons spécifié aucune statistique pour l'évaluation. + +21 +00:02:00,960 --> 00:02:05,520 +Pour obtenir ces statistiques, nous allons d'abord rassembler les prédictions sur l'ensemble d'évaluation à l'aide de la + +22 +00:02:05,520 --> 00:02:11,760 +méthode `predict`. Cela renvoie un tuple nommé avec trois champs : `predictions` (qui contient les prédictions du modèle), + +23 +00:02:11,760 --> 00:02:17,760 +`label_ids` (qui contient les étiquettes si votre jeu de données en avait) et `metrics` (qui est + +24 +00:02:17,760 --> 00:02:24,480 +vide ici). Les prédictions sont les logits des modèles pour toutes les phrases du jeu de données, + +25 +00:02:24,480 --> 00:02:31,440 +donc un tableau NumPy de forme 408 par 2. Pour les faire correspondre à nos étiquettes, nous devons prendre le logit maximum + +26 +00:02:31,440 --> 00:02:36,560 +pour chaque prédiction (pour savoir laquelle des deux classes a été prédite), ce que nous faisons avec la + +27 +00:02:36,560 --> 00:02:42,480 +fonction argmax. Ensuite, nous pouvons utiliser une métrique de la bibliothèque Datasets : elle peut être chargée aussi facilement + +28 +00:02:42,480 --> 00:02:47,200 +que notre jeu de données avec la fonction `load_metric`, et elle renvoie la métrique d'évaluation utilisée pour + +29 +00:02:47,200 --> 00:02:54,080 +le jeu de données que nous utilisons. Nous pouvons voir que notre modèle a appris quelque chose car il est précis à 85,7%. + +30 +00:02:55,200 --> 00:02:59,920 +Pour surveiller les métriques d'évaluation pendant l'entraînement, nous devons définir une fonction `compute_metrics` + +31 +00:02:59,920 --> 00:03:05,200 +qui effectue la même étape qu'auparavant : elle prend un tuple nommé avec des prédictions et des étiquettes + +32 +00:03:05,200 --> 00:03:08,000 +et doit renvoyer un dictionnaire avec la métrique dont nous voulons garder une trace. + +33 +00:03:09,120 --> 00:03:14,400 +En transmettant la stratégie d'évaluation d'époque à nos arguments d'entraînement, nous disons au Trainer d'évaluer + +34 +00:03:14,400 --> 00:03:20,400 +à la fin de chaque époque. Le lancement d'un entraînement dans un notebook affichera alors une barre de progression + +35 +00:03:20,400 --> 00:03:29,920 +et complétera le tableau que vous voyez ici au fur et à mesure que vous parcourez chaque époque. \ No newline at end of file diff --git a/subtitles/fr/25_keras-introduction.srt b/subtitles/fr/25_keras-introduction.srt new file mode 100644 index 000000000..64426e907 --- /dev/null +++ b/subtitles/fr/25_keras-introduction.srt @@ -0,0 +1,103 @@ +1 +00:00:05,120 --> 00:00:10,640 +Dans cette vidéo, je vais vous présenter très rapidement comment nos transformers fonctionnent + +2 +00:00:10,640 --> 00:00:17,120 +avec Tensorflow et Keras ! L'explication très courte est que tous nos modèles Tensorflow + +3 +00:00:17,120 --> 00:00:23,760 +sont également des objets de modèle Keras, et qu'ils disposent donc de l'API de modèle Keras standard. Si vous êtes un + +4 +00:00:23,760 --> 00:00:28,640 +ingénieur en ML expérimenté qui utilise beaucoup Keras, c'est probablement tout ce que vous devez savoir pour commencer à travailler + +5 +00:00:28,640 --> 00:00:34,160 +avec eux. Mais pour tous les autres, y compris les ingénieurs prodigues de PyTorch + +6 +00:00:34,160 --> 00:00:39,360 +qui reviennent au bercail, je vais présenter rapidement les modèles Keras et la façon dont nous travaillons avec eux. + +7 +00:00:40,320 --> 00:00:46,240 +Dans d'autres vidéos, que je mettrai en lien ci-dessous, j'expliquerai plus en détail l'entraînement avec les modèles Keras. + +8 +00:00:46,240 --> 00:00:54,640 +Mais d'abord, qu'est-ce qu'un modèle Keras ? Votre modèle contient essentiellement l'intégralité de votre réseau : + +9 +00:00:54,640 --> 00:00:59,600 +il contient les couches et les poids de ces couches, et indique aussi au modèle ce + +10 +00:00:59,600 --> 00:01:04,560 +qu'il faut en faire ; il définit l'intégralité du chemin de vos entrées à vos sorties. + +11 +00:01:05,280 --> 00:01:10,880 +Si vous avez déjà utilisé Keras, vous avez probablement commencé par créer votre modèle à la + +12 +00:01:10,880 --> 00:01:17,600 +main : vous avez ajouté une couche après l'autre, peut-être en utilisant `model.add()` ou l'approche fonctionnelle. + +13 +00:01:18,480 --> 00:01:26,240 +Et il n'y a rien de mal à ça ! Beaucoup de bons modèles sont construits de cette façon. Mais vous pouvez également précharger un modèle entier, poids et tout. + +14 +00:01:26,960 --> 00:01:33,920 +C'est vraiment utile, car si vous essayez de lire le papier ou de regarder le code, + +15 +00:01:33,920 --> 00:01:38,400 +vous verrez que l'intérieur d'un transformer est assez complexe, et tout écrire à partir de + +16 +00:01:38,400 --> 00:01:43,280 +zéro et le faire correctement serait difficile même pour un ingénieur en ML expérimenté. + +17 +00:01:43,280 --> 00:01:48,080 +Mais comme tout est regroupé dans un modèle, vous n'avez pas à vous soucier de cette complexité si + +18 +00:01:48,080 --> 00:01:53,840 +vous ne le souhaitez pas ! Vous avez la possibilité d'écrire le modèle de votre choix, mais vous pouvez également + +19 +00:01:54,400 --> 00:01:58,640 +charger un transformer pré-entraîné et préconfiguré en une seule ligne de code. + +20 +00:02:00,000 --> 00:02:09,040 +Et quand je mentionnais l'API Keras avant, l'avantage est que vous écriviez votre propre modèle à partir de zéro ou que vous chargiez un modèle pré-entraîné, vous interagissez avec le + +21 +00:02:09,040 --> 00:02:14,560 +modèle de la même manière : grâce aux mêmes méthodes que vous verrez encore et encore, + +22 +00:02:15,200 --> 00:02:22,000 +comme `fit`, `compile` et `prédict`, et nous couvrirons des exemples concrets d'utilisation de ces + +23 +00:02:22,000 --> 00:02:26,960 +méthodes dans d'autres vidéos que je mettrai en lien ci-dessous. Pour l'instant, l'élément clé à retenir de cette vidéo, si + +24 +00:02:26,960 --> 00:02:31,920 +vous n'avez jamais vu Keras auparavant, est que cette encapsulation soignée signifie que toute la complexité d' + +25 +00:02:31,920 --> 00:02:36,560 +un énorme réseau de neurones devient gérable, car vous interagissez avec lui exactement de la même manière, en + +26 +00:02:36,560 --> 00:02:49,760 +utilisant exactement les mêmes méthodes, comme vous le feriez avec un gros modèle de langue ou avec un modèle simple que vous avez écrit à la main. \ No newline at end of file diff --git a/subtitles/fr/26_fine-tuning-with-tensorflow.srt b/subtitles/fr/26_fine-tuning-with-tensorflow.srt new file mode 100644 index 000000000..6b52ac5fa --- /dev/null +++ b/subtitles/fr/26_fine-tuning-with-tensorflow.srt @@ -0,0 +1,247 @@ +1 +00:00:06,069 --> 00:00:11,580 +Dans cette vidéo, nous allons voir comment charger et finetuner un modèle pré-entraîné. + +2 +00:00:11,580 --> 00:00:16,010 +C'est très rapide, et si vous avez regardé nos vidéos sur le pipeline, que je mettrai en lien ci-dessous, le + +3 +00:00:16,010 --> 00:00:18,330 +processus est très similaire. + +4 +00:00:18,330 --> 00:00:21,990 +Cette fois, cependant, nous allons utiliser l'apprentissage par transfert et faire l'entraînement + +5 +00:00:21,990 --> 00:00:26,660 +nous-mêmes, plutôt que de simplement charger un modèle et de l'utiliser tel quel. + +6 +00:00:26,660 --> 00:00:30,610 +Pour en savoir plus sur l'apprentissage par transfert, rendez-vous sur la vidéo « Qu'est-ce que l'apprentissage par transfert ? » + +7 +00:00:30,610 --> 00:00:33,000 +que nous mettrons également en lien ci-dessous ! + +8 +00:00:33,000 --> 00:00:35,660 +Mais maintenant regardons ce code. + +9 +00:00:35,660 --> 00:00:40,340 +Pour commencer, nous choisissons le modèle avec lequel nous voulons commencer. Dans ce cas, nous allons utiliser le + +10 +00:00:40,340 --> 00:00:42,540 +célèbre BERT original. + +11 +00:00:42,540 --> 00:00:50,500 +Mais que signifie cette monstruosité, `TFAutoModelForSequenceClassification` ? + +12 +00:00:50,500 --> 00:00:56,460 +Eh bien, le TF signifie TensorFlow, et le reste signifie « prenez un modèle de langue et collez-y + +13 +00:00:56,460 --> 00:01:00,879 +une tête de classification de séquence s'il n'en a pas déjà une ». + +14 +00:01:00,879 --> 00:01:05,420 +Donc, ce que nous allons faire ici, c'est charger BERT, un modèle de langue général, puis faire un + +15 +00:01:05,420 --> 00:01:09,490 +apprentissage par transfert pour l'utiliser sur notre tâche d'intérêt. + +16 +00:01:09,490 --> 00:01:13,530 +Nous chargeons ici le modèle de langue avec cette seule ligne de code, en utilisant la méthode `from_pretrained` + +17 +00:01:13,530 --> 00:01:14,530 +. + +18 +00:01:14,530 --> 00:01:21,230 +Cette méthode doit connaître deux choses : premièrement, le nom du modèle que vous souhaitez charger, + +19 +00:01:21,230 --> 00:01:29,840 +et deuxièmement, le nombre de classes de votre problème. + +20 +00:01:29,840 --> 00:01:33,500 +Si vous souhaitez suivre avec vidéos sur Datasets, que je vais lier + +21 +00:01:33,500 --> 00:01:41,200 +ci-dessous, vous aurez alors deux classes, positive et négative, et donc `num_labels` est égal à deux. + +22 +00:01:41,200 --> 00:01:43,590 +Qu'en est-il de cette chose `.compile` ? + +23 +00:01:43,590 --> 00:01:47,909 +Si vous connaissez Keras, vous l'avez probablement déjà vu, mais si ce n'est pas le cas, c'est l'une + +24 +00:01:47,909 --> 00:01:55,520 +de ses méthodes de base que vous allez voir encore et encore. Vous devez toujours compiler votre modèle avant de l'entraîner. + +25 +00:01:55,520 --> 00:02:01,240 +La compilation doit connaître deux choses. Premièrement la fonction de perte : qu'essayons-nous d' + +26 +00:02:01,240 --> 00:02:02,240 +optimiser ? + +27 +00:02:02,240 --> 00:02:08,509 +Ici, nous importons la fonction de perte `SparseCategoricalCrossentropy` c'est une bouchée, mais c'est + +28 +00:02:08,509 --> 00:02:13,390 +la fonction de perte standard pour tout réseau de neurones qui effectue une tâche de classification. + +29 +00:02:13,390 --> 00:02:18,170 +Cela encourage essentiellement le réseau à produire des valeurs élevées pour la bonne classe et des + +30 +00:02:18,170 --> 00:02:21,080 +valeurs faibles pour les mauvaises classes. + +31 +00:02:21,080 --> 00:02:26,140 +Notez que vous pouvez spécifier la fonction de perte sous forme de chaîne, comme nous l'avons fait avec l'optimiseur, + +32 +00:02:26,140 --> 00:02:34,319 +mais il y a un risque ici, il y a un piège dans lequel les gens tombent très courant comme quoi par défaut, cette perte suppose que la sortie est des probabilités + +33 +00:02:34,319 --> 00:02:39,650 +après une couche softmax, mais ce que notre modèle a réellement produit est les valeurs avant le + +34 +00:02:39,650 --> 00:02:50,140 +softmax, souvent appelées logits. Vous les avez déjà vues dans les vidéos sur le pipeline. + +35 +00:02:50,140 --> 00:02:54,580 +Si vous vous trompez, votre modèle ne s'entraînera pas et il sera très ennuyeux de comprendre pourquoi. + +36 +00:02:54,580 --> 00:03:09,460 +Dans de prochaies vidéos, nous verrons comment utiliser les pertes internes au modèle, de sorte que vous n'aurez pas à spécifier de perte vous-même, vous n'aurez pas à vous soucier de ce détail. Mais pour maintenant n'oubliez pas de mettre `from_logits=True` + +40 +00:03:09,460 --> 00:03:13,340 +La deuxième chose que `.compile` doit savoir est l'optimiseur que vous voulez. + +41 +00:03:13,340 --> 00:03:17,570 +Dans notre cas, nous utilisons Adam, qui est en quelque sorte l'optimiseur standard en apprentissage profond de nos + +42 +00:03:17,570 --> 00:03:18,730 +jours. + +43 +00:03:18,730 --> 00:03:22,770 +La seule chose que vous voudrez peut-être changer est le taux d'apprentissage, et pour ce faire, nous + +44 +00:03:22,770 --> 00:03:27,330 +devrons importer l'optimiseur réel plutôt que de simplement l'appeler par chaîne, mais nous en + +45 +00:03:27,330 --> 00:03:30,050 +parlerons dans une autre vidéo, que j'indiquerais en dessous. + +46 +00:03:30,050 --> 00:03:33,610 +Pour l'instant, essayons simplement d'entraîner le modèle ! + +47 +00:03:33,610 --> 00:03:35,830 +Alors, comment entraîner un modèle ? + +48 +00:03:35,830 --> 00:03:40,670 +Eh bien, si vous avez déjà utilisé Keras, tout cela vous sera très familier - mais + +49 +00:03:40,670 --> 00:03:43,370 +sinon, regardons ce que nous faisons ici. + +50 +00:03:43,370 --> 00:03:50,371 +`.fit()` est à peu près la méthode centrale pour les modèles Keras : elle indique au modèle de s'entraîner sur les modèles qu'on lui passe. + +51 +00:03:50,371 --> 00:03:57,000 +Donc ici nous passons le jeu de données que nous avons crée dans la section précédente. Il contient les entrées et les étiquettes. + +52 +00:03:57,000 --> 00:04:01,000 +Donc nous n'avons pas besoin de spécifier les étiquettes séparemment quand on appelle `.fit()`. + +53 +00:04:01,000 --> 00:04:13,000 +Puis nous faisons la même chose avec les données de validation et nous pouvons spécifier des détails comme le nombre d'epochs ou autre argument que l'on peut passer à `.fit()`. + +54 +00:04:01,000 --> 00:04:16,540 +A la fin, on passe le tout et on exécute. + +55 +00:04:16,540 --> 00:04:20,449 +Si tout fonctionne, vous devriez voir une petite barre de progression de l'entraînement au fur et à mesure que votre perte + +56 +00:04:20,449 --> 00:04:21,670 +diminue. + +57 +00:04:21,670 --> 00:04:26,870 +Et c'est tout. Pendant que cela tourne, vous pouvez appelez votre supérieur et lui dire que vous êtes maintenant un ingénieur en ML/NLP senior maintenant + +58 +00:04:26,870 --> 00:04:30,509 +et que vous allez vouloir une révision de salaire le trimestre prochain. + +59 +00:04:30,509 --> 00:04:38,470 +C'est vraiment tout ce qu'il faut pour appliquer la puissance d'un énorme modèle de langage pré-entraîné à + +60 +00:04:38,470 --> 00:04:40,770 +votre problème de NLP. + +61 +00:04:40,770 --> 00:04:42,440 +Pouvons-nous faire mieux que ça ? + +62 +00:04:42,440 --> 00:04:47,180 +Nous pourrions certainement, avec quelques fonctionnalités Keras plus avancées comme un taux d'apprentissage réglé et programmé, + +63 +00:04:47,180 --> 00:04:50,889 +nous pouvons obtenir une perte encore plus faible et un modèle encore plus précis. + +64 +00:04:50,889 --> 00:04:54,039 +Et qu'est-ce qu'on fait de notre modèle une fois qu'il est entraîné ? + +65 +00:04:54,039 --> 00:05:02,919 +Je couvrirai cela et plus encore dans les vidéos liées qui suivent donc restez connectés ! \ No newline at end of file diff --git a/subtitles/fr/27_learning-rate-scheduling-with-tensorflow.srt b/subtitles/fr/27_learning-rate-scheduling-with-tensorflow.srt new file mode 100644 index 000000000..3ead1ca40 --- /dev/null +++ b/subtitles/fr/27_learning-rate-scheduling-with-tensorflow.srt @@ -0,0 +1,159 @@ +1 +00:00:05,120 --> 00:00:11,440 +Dans nos autres vidéos, nous avons parlé des principes de base pour finetuner un modèle de langage avec Tensorflow + +2 +00:00:11,440 --> 00:00:18,000 +(et comme toujours, lorsque je fais référence à des vidéos, je les mets en lien ci-dessous). Pouvons-nous faire mieux quand même ? Voici donc + +3 +00:00:18,000 --> 00:00:23,040 +le code de notre vidéo du finetuning du modèle, et pendant que cela tourne, nous pourrions certainement modifier + +4 +00:00:23,040 --> 00:00:29,040 +quelques choses. Le taux d'apprentissage est de loin le plus important. Dans cette vidéo, nous expliquerons + +5 +00:00:29,040 --> 00:00:34,800 +comment le modifier, ce qui rendra votre entraînement beaucoup plus systématiquement réussie. En fait, + +6 +00:00:36,080 --> 00:00:42,880 +nous souhaitons modifier deux choses concernant le taux d'apprentissage par défaut d'Adam. Le premier est + +7 +00:00:42,880 --> 00:00:51,520 +qu'il est bien trop élevé pour nos modèles - par défaut Adam utilise un taux d'apprentissage de 10^-3 (1 e moins 3), + +8 +00:00:51,520 --> 00:00:59,600 +ce qui est très élevé pour l'entraînement des transformers. Nous allons commencer à 5 par 10^-5 (5 e moins 5), + +9 +00:00:59,600 --> 00:01:05,520 +ce qui est 20 fois inférieur à la valeur par défaut. Et deuxièmement, nous ne voulons pas seulement un taux d'apprentissage + +10 +00:01:05,520 --> 00:01:10,960 +constant : nous pouvons obtenir des performances encore meilleures si nous réduisons le taux d'apprentissage jusqu'à une valeur infime, + +11 +00:01:10,960 --> 00:01:17,760 +ou même 0, au cours de l'entraînement. C'est ce que fait ce planificateur `PolynomialDecay`. + +12 +00:01:17,800 --> 00:01:23,880 +Nous verrons à quoi cela ressemble dans une seconde. Mais d'abord nous devons spécifier au planificateur combien de + +13 +00:01:23,880 --> 00:01:25,120 +temps l'entraînement va durer, + +14 +00:01:25,120 --> 00:01:29,040 +pour qu'il décroisse à la bonne vitesse. C'est ce que fait ce code ici. + +15 +00:01:30,080 --> 00:01:35,280 +Nous calculons le nombre de mini-batchs que le modèle va voir sur l'ensemble de son parcours d'entraînement, + +16 +00:01:35,280 --> 00:01:37,640 +qui correspond à la taille de l'ensemble d'entraînement, divisé par batch_size pour obtenir le nombre debatchs + +17 +00:01:37,640 --> 00:01:42,080 +par époque, puis multiplié par le nombre d'époques pour obtenir le nombre total + +18 +00:01:42,080 --> 00:01:47,680 +debatchs sur l'ensemble de l'exécution de l'entraînement. Une fois que nous savons combien d'étapes de entraînement nous suivons, + +19 +00:01:47,680 --> 00:01:51,360 +nous transmettons simplement toutes ces informations au planificateur et nous sommes prêts à commencer. + +20 +00:01:54,000 --> 00:01:57,360 +À quoi ressemble le planificateur de décroissance polynomiale ? + +21 +00:01:57,360 --> 00:02:04,720 +Bien, il ressemble à ceci : il commence à 5e-5, ce qui signifie 5 fois dix jusqu'au moins 5, + +22 +00:02:05,280 --> 00:02:11,120 +puis diminue à un rythme constant jusqu'à qu'il atteigne 0 juste au moment même fin de entraînement. + +23 +00:02:05,280 --> 00:02:11,120 +Je peux déjà vous entendre réagir. Oui, je sais, c'est en fait une décroissance constante ou linéaire. + +24 +00:02:11,120 --> 00:02:24,120 +Je sais que le nom est « polynomial » et vous vous sentez trompés, on vous avez promis un polynôme et vous ne l'avez pas eu. + +25 +00:02:24,120 --> 00:02:34,120 +Calmez-vous, c'est ok. Car bien sûr, les fonctions linéaires sont justes des cas particuliers avec un polynôme de premier ordre. + +26 +00:02:34,120 --> 00:02:40,120 +Si vous modifier les options de cette classe, vous pouvez vraiment avoir un polynôme d'ordre supérieur pour votre décroissance. + +27 +00:02:40,120 --> 00:02:43,120 +Mais ce planificateur linéaire va bien fonctionner pour nous pour le moment. + +28 +00:02:43,120 --> 00:02:47,120 +Nous n'avons pas besoin de toutes ces astuces et gadgets. + +29 +00:02:49,840 --> 00:02:56,400 +Revenons à comment utilisons-nous notre barème de taux d'apprentissage ? Facile, nous le passons simplement à Adam ! Vous remarquerez que la + +30 +00:02:56,400 --> 00:03:00,480 +première fois que nous avons compilé le modèle, nous lui avons juste passé la chaîne « Adam » pour avoir notre optimiseur. + +31 +00:03:02,320 --> 00:03:07,760 +Donc Keras reconnaît les noms des optimiseurs courants et des fonctions de perte si vous les transmettez sous forme de chaînes, + +32 +00:03:07,760 --> 00:03:12,320 +ce qui vous fait gagner du temps si vous ne souhaitez que les paramètres par défaut. Mais nous sommes + +33 +00:03:12,320 --> 00:03:19,600 +maintenant des apprenants en ML professionnels, avec notre propre planificateur de taux d'apprentissage, nous devons donc faire les choses correctement. + +34 +00:03:19,600 --> 00:03:26,080 +Donc, d'abord, nous importons l'optimiseur, puis nous l'initialisons avec notre planificateur qui est passé comme argument `learning_rate` de cet optimiseur. + +35 +00:03:29,200 --> 00:03:34,720 +Maintenant nous compilons le modèle à l'aide du nouvel optimiseur, et quelle que soit la fonction de perte que vous voulez - ce + +36 +00:03:34,720 --> 00:03:39,040 +sera une `SparseCategoricalCrossentropy` si vous avez suivi la vidéo sur le finetuning. + +37 +00:03:39,680 --> 00:03:47,120 +Et on est prêt à y aller. Maintenant, nous avons un modèle à hautes performances, prêt à l'emploi. Il ne reste plus qu'à adapter le modèle comme + +38 +00:03:47,120 --> 00:03:53,280 +nous l'avons fait auparavant. N'oubliez pas que, comme nous avons compilé le modèle avec le nouvel optimiseur avec le nouveau + +39 +00:03:53,280 --> 00:03:58,800 +planificateur de taux d'apprentissage, nous n'avons rien à changer ici. Nous appelons à nouveau `.fit`, avec exactement la + +40 +00:03:58,800 --> 00:04:04,320 +même commande qu'auparavant, mais nous obtenons maintenant un bel entraînement avec une belle décroissance du taux d'apprentissage, partant de la bonne valeur et décroissant jusqu'à 0. \ No newline at end of file diff --git a/subtitles/fr/28_tensorflow-predictions-and-metrics.srt b/subtitles/fr/28_tensorflow-predictions-and-metrics.srt new file mode 100644 index 000000000..b46d05a60 --- /dev/null +++ b/subtitles/fr/28_tensorflow-predictions-and-metrics.srt @@ -0,0 +1,155 @@ +1 +00:00:05,600 --> 00:00:10,080 +Dans nos autres vidéos, et comme toujours, il y aura des liens ci-dessous si vous souhaitez les consulter, + +2 +00:00:10,640 --> 00:00:15,600 +nous vous avons montré comment initialiser et finetuner un transformer dans TensorFlow. + +3 +00:00:15,600 --> 00:00:20,800 +Donc la question est maintenant : que pouvons-nous faire avec un modèle après l'avoir entraîné ? La chose évidente à + +4 +00:00:20,800 --> 00:00:26,080 +essayer est de l'utiliser pour obtenir des prédictions pour de nouvelles données, alors voyons comment procéder. Encore une fois, + +5 +00:00:26,080 --> 00:00:31,120 +si vous connaissez Keras, la bonne nouvelle est que, comme il n'y a que des modèles Keras standard, + +6 +00:00:31,680 --> 00:00:35,440 +nous pouvons utiliser la méthode Keras `predict()` standard, comme illustré ici. + +7 +00:00:36,800 --> 00:00:42,800 +Vous transmettez simplement du texte tokenisé à cette méthode, comme vous le feriez avec un tokenizer, et vous obtenez vos + +8 +00:00:42,800 --> 00:00:48,320 +résultats. Nos modèles peuvent produire plusieurs choses différentes, selon les options que vous définissez, + +9 +00:00:48,320 --> 00:00:53,280 +mais la plupart du temps, ce que vous voulez, ce sont les logits de sortie. Si vous ne les avez jamais rencontrés + +10 +00:00:53,280 --> 00:01:02,960 +auparavant, les logits sont les sorties de la dernière couche du réseau, avant l'application d'une softmax. + +11 +00:01:02,960 --> 00:01:08,400 +Donc, si vous voulez transformer les logits en sorties de probabilité du modèle, il vous suffit d'appliquer une softmax + +12 +00:01:08,400 --> 00:01:13,840 +comme ceci. Et si nous voulions transformer ces probabilités en prédictions de classe ? + +13 +00:01:14,853 --> 00:01:20,960 +C'est simple : nous sélectionnons simplement la probabilité la plus élevée pour chaque sortie ! Pour ce faire, le moyen le plus simple consiste à utiliser + +14 +00:01:20,960 --> 00:01:26,960 +la fonction argmax. Argmax renverra l'indice de la probabilité la plus élevée dans chaque ligne, + +15 +00:01:26,960 --> 00:01:36,400 +ce qui signifie dans ce cas que nous obtiendrons un 0 si la probabilité est la plus évelée à l'indice 0, un 1 si la probabilité est la plus évelée à l'indice 0, etc. + +16 +00:01:37,360 --> 00:01:45,440 +Ce sont des prédictions de classes : classe 0, classe 1, etc. En fait, si les prédictions de classe sont tout ce que vous voulez, vous pouvez + +17 +00:01:45,440 --> 00:01:50,240 +entièrement ignorer l'étape softmax, car le logit le plus grand sera toujours la probabilité la plus élevée + +18 +00:01:52,400 --> 00:01:56,800 +également. Si les probabilités et les prédictions de classe sont tout ce que vous voulez, alors vous avez vu tout ce dont vous   avez + +19 +00:01:56,800 --> 00:02:02,000 +besoin à ce stade ! Mais si vous souhaitez comparer votre modèle ou l'utiliser pour la recherche, + +20 +00:02:02,000 --> 00:02:06,320 +vous voudrez peut-être approfondir les résultats que vous obtenez. Et l'une des manières d'y parvenir consiste à calculer + +21 +00:02:06,320 --> 00:02:10,880 +des métriques pour les prédictions du modèle. Si vous suivez nos vidéos sur Datasets + +22 +00:02:10,880 --> 00:02:16,400 +et le finetuning, nous avons obtenu nos données à partir du jeu de données MRPC, qui fait partie du benchmark GLUE. + +23 +00:02:16,960 --> 00:02:24,480 +Chacun des jeux de données GLUE, ainsi que bon nombre de nos autres jeux de données sur le Hub, possède des métriques prédéfinies, + +24 +00:02:24,480 --> 00:02:31,520 +et nous pouvons les charger facilement avec la fonction `load_metric()`. Pour le jeu de données MRPC, + +25 +00:02:31,520 --> 00:02:36,080 +les métriques intégrées sont la précision, qui mesure simplement le pourcentage de fois où la prédiction du modèle + +26 +00:02:36,080 --> 00:02:42,160 +était correcte, et le score F1, qui est une mesure légèrement plus complexe qui mesure à quel + +27 +00:02:42,160 --> 00:02:48,960 +point le modèle compense précision et rappel. Pour calculer ces métriques afin de comparer notre modèle, + +28 +00:02:48,960 --> 00:02:54,000 +nous leur transmettons simplement les prédictions du modèle et les étiquettes de vérité terrain, et nous obtenons nos résultats simplement avec `.predict()`. + +29 +00:02:56,720 --> 00:03:01,120 +Si vous connaissez Keras, vous remarquerez qu'il s'agit d'une façon étrange de calculer les + +30 +00:03:01,120 --> 00:03:06,880 +métriques : nous ne calculons les métriques qu'à la fin de l'entraînement, mais Keras a la capacité intégrée de + +31 +00:03:06,880 --> 00:03:14,960 +calculer un large éventail de statistiques à la volée pendant votre entraînement. Si vous souhaitez utiliser + +32 +00:03:14,960 --> 00:03:21,920 +des calculs de métriques intégrés, c'est très simple et vous utilisez à nouveau l'approche standard de Keras. Il vous suffit de transmettre un argument `metric` à la méthode `compile()`. + +33 +00:03:22,960 --> 00:03:28,240 +Comme pour des choses comme la perte et l'optimiseur, vous pouvez spécifier les métriques que vous voulez par chaîne, + +34 +00:03:28,240 --> 00:03:33,520 +ou vous pouvez importer les objets de métrique réels si vous souhaitez leur transmettre des arguments spécifiques, mais notez + +35 +00:03:33,520 --> 00:03:40,880 +que contrairement à la perte et à la précision, vous devez fournir les métriques comme une liste, même si vous n'en avez qu'une. Une fois + +36 +00:03:40,880 --> 00:03:46,320 +qu'un modèle a été compilé avec une métrique, cela rapporte cette métrique pour l'entraînement, la validation et les + +37 +00:03:49,840 --> 00:03:54,880 +prédictions en assumant avoir des étiquettes pour les prédictions. Vous pouvez même écrire vos propres classes de métriques. Bien que cela dépasse un peu le cadre + +38 +00:03:54,880 --> 00:03:59,440 +de ce cours, je vais créer un lien vers les documents TF pertinents ci-dessous, car cela peut être très pratique + +39 +00:03:59,440 --> 00:04:10,800 +si vous souhaitez une statistique qui n'est pas prise en charge par défaut dans Keras, comme le score F1. \ No newline at end of file diff --git a/subtitles/fr/29_write-your-training-loop-in-pytorch.srt b/subtitles/fr/29_write-your-training-loop-in-pytorch.srt new file mode 100644 index 000000000..78d08b626 --- /dev/null +++ b/subtitles/fr/29_write-your-training-loop-in-pytorch.srt @@ -0,0 +1,287 @@ +1 +00:00:05,430 --> 00:00:07,240 +Écrivez votre propre boucle d'entraînement dans PyTorch. + +2 +00:00:07,240 --> 00:00:11,759 +Dans cette vidéo, nous verrons comment nous pouvons faire le même finetuning que dans la + +3 +00:00:11,759 --> 00:00:14,120 +vidéo sur l'API Trainer, mais sans compter sur cette classe. + +4 +00:00:14,120 --> 00:00:20,369 +De cette façon, vous pourrez facilement personnaliser chaque étape de la boucle d'entraînement en fonction de vos besoins. + +5 +00:00:20,369 --> 00:00:23,859 +Ceci est également très utile pour déboguer manuellement quelque chose qui n'allait pas avec l' + +6 +00:00:23,859 --> 00:00:26,189 +API Trainer. + +7 +00:00:26,189 --> 00:00:31,200 +Avant de nous plonger dans le code, voici une esquisse d'une boucle d'entraînement : nous prenons un batch de + +8 +00:00:31,200 --> 00:00:33,469 +données d'entraînement et le transmettons au modèle. + +9 +00:00:33,469 --> 00:00:36,600 +Avec les étiquettes, nous pouvons alors calculer une perte. + +10 +00:00:36,600 --> 00:00:41,130 +Ce nombre n'est pas utile en soi, mais est utilisé pour calculer les gradients de nos + +11 +00:00:41,130 --> 00:00:46,750 +poids de modèle, c'est-à-dire la dérivée de la perte par rapport à chaque poids de modèle. + +12 +00:00:46,750 --> 00:00:51,920 +Ces gradients sont ensuite utilisés par l'optimiseur pour mettre à jour les poids du modèle et les + +13 +00:00:51,920 --> 00:00:53,360 +améliorer un peu. + +14 +00:00:53,360 --> 00:00:56,170 +Nous répétons ensuite le processus avec un nouveau batch de données d'apprentissage. + +15 +00:00:56,170 --> 00:01:00,969 +Si tout cela n'est pas clair, n'hésitez pas à reprendre votre + +16 +00:01:00,969 --> 00:01:02,240 +cours d'apprentissage profond préféré. + +17 +00:01:02,240 --> 00:01:07,560 +Nous utiliserons à nouveau le jeu de données GLUE MRPC, et nous avons vu comment prétraiter les données + +18 +00:01:07,560 --> 00:01:10,439 +à l'aide de la bibliothèque Datasets avec le rembourrage dynamique. + +19 +00:01:10,439 --> 00:01:15,549 +Découvrez les vidéos liées ci-dessous si vous ne les avez pas déjà vues. + +20 +00:01:15,549 --> 00:01:20,060 +Cela fait, nous n'avons plus qu'à définir les DataLoaders PyTorch, qui seront chargés de + +21 +00:01:20,060 --> 00:01:24,480 +convertir les éléments de notre jeu de données en batchs. + +22 +00:01:24,480 --> 00:01:33,890 +Nous utilisons notre `DataCollatorForPadding` comme fonction d'assemblement et mélangeons l'ensemble d'entraînementpour s'assurer que nous voyons pas tous les échantillons dans le même ordre. + +23 +00:01:33,890 --> 00:01:39,460 +Pour vérifier que tout fonctionne comme prévu, nous essayons de récupérer un batch de données et de l' + +24 +00:01:39,460 --> 00:01:40,460 +inspecter. + +25 +00:01:40,460 --> 00:01:44,790 +Comme nos éléments de jeu de données, c'est un dictionnaire, mais cette fois les valeurs ne sont pas une seule + +26 +00:01:44,790 --> 00:01:50,460 +liste d'entiers, mais un tenseur de forme de taille de batch par longueur de séquence. + +27 +00:01:50,460 --> 00:01:52,869 +L'étape suivante consiste à envoyer les données d'entraînement dans notre modèle. + +28 +00:01:52,869 --> 00:01:56,790 +Pour cela, nous devrons créer notre modèle. + +29 +00:01:56,790 --> 00:02:01,240 +Comme on l'a vu dans la vidéo sur l'API des modèles, nous utilisons la méthode `from_pretrained` et ajustons le nombre + +30 +00:02:01,240 --> 00:02:06,159 +d'étiquettes au nombre de classes que nous avons sur cet jeu de données, ici deux. + +31 +00:02:06,159 --> 00:02:11,020 +Encore une fois, pour être sûr que tout se passe bien, nous passons le batch que nous avons saisi à notre modèle + +32 +00:02:11,020 --> 00:02:12,640 +et vérifions qu'il n'y a pas d'erreur. + +33 +00:02:12,640 --> 00:02:17,780 +Si les étiquettes sont fournies, les modèles de la bibliothèque Transformers renvoient toujours + +34 +00:02:17,780 --> 00:02:18,840 +directement la perte. + +35 +00:02:18,840 --> 00:02:24,129 +Nous pourrons faire `loss.backward()` pour calculer tous les gradients, et nous aurons alors besoin d'un optimiseur + +36 +00:02:24,129 --> 00:02:26,480 +pour faire l'étape d'apprentissage. + +37 +00:02:26,480 --> 00:02:30,800 +Nous utilisons ici l'optimiseur AdamW, qui est une variante d'Adam avec une décroissance de poids appropriée, + +38 +00:02:30,800 --> 00:02:35,040 +mais vous pouvez choisir n'importe quel optimiseur PyTorch que vous aimez. + +39 +00:02:35,040 --> 00:02:39,519 +En utilisant la perte précédente et en calculant les gradients avec `loss.backward()`, nous vérifions que + +40 +00:02:39,519 --> 00:02:43,510 +nous pouvons faire l'étape d'optimisation sans aucune erreur. + +41 +00:02:43,510 --> 00:02:47,580 +N'oubliez pas de mettre `zero_grad()` par la suite, sinon à l'étape suivante, ils seront ajoutés + +42 +00:02:47,580 --> 00:02:49,659 +aux gradients que vous calculez ! + +43 +00:02:49,659 --> 00:02:53,620 +Nous pourrions déjà écrire notre boucle d'entraînement, mais nous ajouterons deux autres choses pour la rendre + +44 +00:02:53,620 --> 00:02:55,590 +aussi bonne que possible. + +45 +00:02:55,590 --> 00:03:01,150 +Le premier est un planificateur de taux d'apprentissage, pour réduire progressivement notre taux d'apprentissage à + +46 +00:03:01,150 --> 00:03:02,150 +0. + +47 +00:03:02,150 --> 00:03:06,180 +La fonction `get_scheduler` de la bibliothèque Transformers n'est qu'une fonction pratique pour + +48 +00:03:06,180 --> 00:03:12,760 +créer facilement un tel planificateur0 Vous pouvez à nouveau utiliser n'importe quel planificateur de taux d'apprentissage PyTorch à la place. + +49 +00:03:12,760 --> 00:03:17,299 +Enfin, si nous voulons que notre entraînement prenne quelques minutes au lieu de quelques heures, + +50 +00:03:17,299 --> 00:03:19,580 +nous devrons utiliser un GPU. + +51 +00:03:19,580 --> 00:03:24,340 +La première étape consiste à en obtenir un, par exemple en utilisant un notebook Colab. + +52 +00:03:24,340 --> 00:03:29,090 +Ensuite, vous devez réellement envoyer votre modèle et les données d'entraînement à l'aide d'un appareil Torch. + +53 +00:03:29,090 --> 00:03:35,659 +Vérifiez les lignes suivantes pour avoir un appareil CUDA ou préparez vous à ce que votre entraînement dure plus d'une heure. + +54 +00:03:35,659 --> 00:03:38,450 +Nous pouvons maintenant tout assembler ! + +55 +00:03:38,450 --> 00:03:42,470 +Nous mettons d'abord notre modèle en mode d'entraînement (ce qui activera le comportement d'entraînement pour certaines + +56 +00:03:42,470 --> 00:03:47,900 +couches comme la Dropout), puis parcourons le nombre d'époques que nous avons sélectionnées et toutes les données dans + +57 +00:03:47,900 --> 00:03:50,130 +notre chargeur de données d'entraînement. + +58 +00:03:50,130 --> 00:03:54,560 +Ensuite, nous passons par toutes les étapes que nous avons déjà vues : envoyer les données au GPU, calculer + +59 +00:03:54,560 --> 00:03:57,870 +les sorties du modèle, et en particulier la perte. + +60 +00:03:57,870 --> 00:04:02,040 +Utilisez la perte pour calculer les gradients, puis effectuez une étape d'apprentissage avec l'optimiseur. + +61 +00:04:02,040 --> 00:04:06,760 +Mettez à jour le taux d'apprentissage dans notre planificateur pour la prochaine itération et mettez à 0 les gradients + +62 +00:04:06,760 --> 00:04:09,340 +de l'optimiseur. + +63 +00:04:09,340 --> 00:04:13,590 +Une fois cela terminé, nous pouvons évaluer notre modèle très facilement avec une métrique de la + +64 +00:04:13,590 --> 00:04:14,730 +bibliothèque Datasets. + +65 +00:04:14,730 --> 00:04:22,470 +Nous mettons d'abord notre modèle en mode d'évaluation, puis parcourons toutes les données dans le + +66 +00:04:22,470 --> 00:04:23,900 +chargeur de données d'évaluation. + +67 +00:04:23,900 --> 00:04:27,480 +Comme nous l'avons vu dans la vidéo sur l'API Trainer, le modèle génère des logits et nous devons appliquer + +68 +00:04:27,480 --> 00:04:31,350 +la fonction argmax pour les convertir en prédictions. + +69 +00:04:31,350 --> 00:04:36,910 +L'objet `metric` a alors une méthode `add_batch` que nous pouvons utiliser pour lui envoyer ces prédictions intermédiaires. + +70 +00:04:36,910 --> 00:04:40,590 +Une fois la boucle d'évaluation terminée, nous n'avons plus qu'à appeler la méthode de calcul pour obtenir nos + +71 +00:04:40,590 --> 00:04:41,620 +résultats finaux ! + +72 +00:04:41,620 --> 00:04:50,760 +Félicitations, vous avez maintenant finetuné un modèle tout seul ! \ No newline at end of file diff --git a/subtitles/fr/30_supercharge-your-pytorch-training-loop-with-accelerate.srt b/subtitles/fr/30_supercharge-your-pytorch-training-loop-with-accelerate.srt new file mode 100644 index 000000000..fce53ba19 --- /dev/null +++ b/subtitles/fr/30_supercharge-your-pytorch-training-loop-with-accelerate.srt @@ -0,0 +1,139 @@ +1 +00:00:05,360 --> 00:00:08,800 +Boostez votre boucle d'entraînement Pytorch avec Accelerate d'Hugging Face. + +2 +00:00:11,120 --> 00:00:17,040 +Il existe plusieurs configurations sur lesquelles vous pouvez exécuter votre entraînement : il peut s'agir de CPU, de GPU ou de TPU. + +3 +00:00:17,680 --> 00:00:22,640 +Distribué sur une machine avec plusieurs appareils, ou sur plusieurs machines (souvent appelées + +4 +00:00:22,640 --> 00:00:29,360 +nœuds), chacune avec plusieurs appareils. En plus de cela, il existe de nouveaux ajustements pour rendre votre entraînement plus rapide + +5 +00:00:29,360 --> 00:00:35,680 +ou plus efficace en termes de mémoire, comme la précision mixte et DeepSpeed. Chacune de ces configurations ou + +6 +00:00:35,680 --> 00:00:40,080 +modifications d'entraînement vous oblige à modifier le code de votre boucle d'entraînement d'une manière ou d'une autre + +7 +00:00:40,080 --> 00:00:46,480 +et à apprendre une nouvelle API. Toutes ces configurations sont gérées par l'API Trainer, et plusieurs + +8 +00:00:46,480 --> 00:00:51,440 +bibliothèques tierces peuvent également vous aider. Le problème avec ceux-ci est qu'ils peuvent ressembler + +9 +00:00:51,440 --> 00:00:56,320 +à une boîte noire et qu'il n'est peut-être pas facile de mettre en œuvre le réglage de la boucle d'entraînement dont vous avez besoin. + +10 +00:00:57,680 --> 00:01:02,000 +Accelerate a été spécialement conçu pour vous permettre de garder le contrôle total de votre boucle d'entraînement + +11 +00:01:02,560 --> 00:01:08,000 +et d'être aussi discret que possible. Avec seulement quatre lignes à ajouter à votre boucle d'entraînement + +12 +00:01:08,640 --> 00:01:11,840 +(affichées ici sur le code de la boucle d'entraînement de la vidéo « Boucle d'entraînement »), + +13 +00:01:12,480 --> 00:01:16,800 +Accelerate gérera toutes les configurations et les ajustements d'entraînement mentionnés sur la première diapositive. + +14 +00:01:18,320 --> 00:01:21,600 +Il n'y a qu'une seule API à apprendre et à maîtriser au lieu de dix API différentes. + +15 +00:01:23,120 --> 00:01:27,120 +Plus précisément, vous devez importer et instancier un objet `Accelerator`, + +16 +00:01:27,120 --> 00:01:30,000 +qui gérera tout le code nécessaire pour votre configuration spécifique. + +17 +00:01:31,200 --> 00:01:36,880 +Ensuite, vous devez lui envoyer le modèle, l'optimiseur et les chargeurs de données que vous utilisez dans la méthode de préparation, + +18 +00:01:37,760 --> 00:01:43,600 +qui est la principale méthode à retenir. Accelerate gère le placement de l'appareil, vous n'avez donc pas besoin de placer + +19 +00:01:43,600 --> 00:01:49,840 +votre batch sur l'appareil spécifique que vous utilisez. Enfin, vous devez remplacer la + +20 +00:01:49,840 --> 00:01:54,880 +ligne `loss.backward` par `accelerator.backward(loss)`, et c'est tout ce dont vous avez besoin ! + +21 +00:01:58,240 --> 00:02:00,480 +Accelerate gère également l'évaluation distribuée. + +22 +00:02:01,440 --> 00:02:05,280 +Vous pouvez toujours utiliser une boucle d'évaluation classique telle que celle que nous avons vue dans la vidéo « Boucle + +23 +00:02:05,280 --> 00:02:09,760 +d'entraînement », auquel cas tous les processus effectueront chacun l'évaluation complète. + +24 +00:02:11,040 --> 00:02:15,360 +Pour utiliser une évaluation distribuée, il vous suffit d'adapter votre boucle d'évaluation comme suit : + +25 +00:02:16,080 --> 00:02:19,920 +transmettre le chargeur de données d'évaluation à la méthode `accelerator.prepare`, + +26 +00:02:19,920 --> 00:02:25,200 +comme pour l'entraînement. Ensuite, vous pouvez ignorer la ligne qui place le lot sur le bon appareil, + +27 +00:02:25,920 --> 00:02:28,800 +et juste avant de transmettre vos prédictions et étiquettes à votre métrique, + +28 +00:02:29,440 --> 00:02:36,160 +utilisez `accelerator.gather` pour rassembler les prédictions et les étiquettes de chaque processus. + +29 +00:02:36,160 --> 00:02:41,440 +Un script d'entraînement distribué doit être lancé plusieurs fois sur différents processus (par exemple + +30 +00:02:41,440 --> 00:02:47,360 +un par GPU que vous utilisez). Vous pouvez utiliser les outils PyTorch si vous les connaissez, + +31 +00:02:48,000 --> 00:02:51,760 +mais Accelerate fournit également une API simple pour configurer votre configuration + +32 +00:02:51,760 --> 00:02:58,000 +et lancer votre script d'entraînement. Dans un terminal, exécutez la configuration accélérée et répondez au petit + +33 +00:02:58,000 --> 00:03:01,680 +questionnaire pour générer un fichier de configuration avec toutes les informations pertinentes. + +34 +00:03:03,120 --> 00:03:07,360 +Puis vous pouvez simplement exécuter `accelerate launch`, suivi du chemin d'accès à votre script d'entraînement. + +35 +00:03:08,400 --> 00:03:19,840 +Dans un notebook, vous pouvez utiliser la fonction `notebook_launcher` pour lancer votre fonction d'entraînement. \ No newline at end of file diff --git a/subtitles/fr/31_navigating-the-model-hub.srt b/subtitles/fr/31_navigating-the-model-hub.srt new file mode 100644 index 000000000..78f12d4cf --- /dev/null +++ b/subtitles/fr/31_navigating-the-model-hub.srt @@ -0,0 +1,147 @@ +1 +00:00:04,000 --> 00:00:07,760 +Dans cette vidéo, nous allons passer en revue la navigation dans le Hub d'HuggingFace. + +2 +00:00:10,080 --> 00:00:16,160 +Ceci est la page d'accueil huggingface.co. Pour accéder au Hub des modèles, cliquez sur l'onglet « Models » dans l' + +3 +00:00:16,160 --> 00:00:22,720 +angle supérieur droit. Vous devriez être face à cette interface Web, qui peut être divisée en plusieurs parties. + +4 +00:00:24,240 --> 00:00:28,560 +Sur la gauche, vous trouverez des catégories que vous pouvez utiliser pour personnaliser votre recherche de modèles. + +5 +00:00:29,760 --> 00:00:35,840 +La première catégorie est celle des « Tasks ». Les modèles sur le Hub peuvent être utilisés pour une grande variété de tâches. + +6 +00:00:36,480 --> 00:00:41,440 +Celles-ci incluent des tâches de traitement du langage naturel, telles que la réponse aux questions ou la classification de texte, + +7 +00:00:41,440 --> 00:00:47,600 +mais elles ne se limitent pas au NLP. D'autres tâches d'autres domaines sont également disponibles, + +8 +00:00:47,600 --> 00:00:52,240 +telles que la classification d'images pour la vision par ordinateur ou la reconnaissance vocale automatique pour la parole. + +9 +00:00:54,720 --> 00:01:00,400 +La deuxième catégorie est celle des « Libraries ». Les modèles partagent généralement l'un des trois backbones : + +10 +00:01:01,040 --> 00:01:07,040 +PyTorch, TensorFlow ou JAX. Cependant, d'autres backbones, tels que Rust ou ONNX, existent également. + +11 +00:01:09,440 --> 00:01:14,720 +Enfin, cet onglet peut également être utilisé pour spécifier de quel framework provient le modèle. + +12 +00:01:15,920 --> 00:01:20,880 +Cela inclut Transformers, mais n'y est pas limité. Le Hub des modèles est utilisé pour héberger + +13 +00:01:20,880 --> 00:01:25,840 +de nombreux modèles de frameworks différents, et nous cherchons activement à héberger les modèles d'autres frameworks. + +14 +00:01:28,400 --> 00:01:33,440 +La troisième catégorie est l'onglet « Datasets ». Sélectionner un jeu de données dans cet onglet + +15 +00:01:33,440 --> 00:01:37,360 +signifie filtrer les modèles afin qu'ils aient été entraînés sur ce jeu de données spécifique. + +16 +00:01:39,040 --> 00:01:43,600 +La quatrième catégorie est l'onglet « Languages » . Sélectionner une langue dans cet onglet + +17 +00:01:43,600 --> 00:01:46,800 +signifie filtrer les modèles afin qu'ils gèrent la langue sélectionnée. + +18 +00:01:48,480 --> 00:01:53,840 +Enfin, la dernière catégorie permet de choisir la licence avec laquelle le modèle est partagé. + +19 +00:01:56,480 --> 00:01:59,440 +Sur la droite, vous trouverez les modèles disponibles sur le Hub des modèles ! + +20 +00:02:00,320 --> 00:02:06,400 +Les modèles sont classés par téléchargements. Lorsque vous cliquez sur un modèle, vous devez faire face à sa carte de modèle. + +21 +00:02:07,040 --> 00:02:11,520 +La carte du modèle contient des informations sur le modèle : sa description, l'utilisation prévue,  les + +22 +00:02:11,520 --> 00:02:18,240 +limites et les biais. Elle peut également afficher des extraits de code expliquant comment utiliser le modèle, ainsi que + +23 +00:02:18,240 --> 00:02:23,840 +toute information pertinente : procédure d'entraînement, traitement des données, résultats d'évaluation, droits d'auteur. + +24 +00:02:25,440 --> 00:02:30,160 +Ces informations sont cruciales pour que le modèle soit utilisé. Plus une carte de modèle est bien conçue, + +25 +00:02:30,160 --> 00:02:34,000 +plus il sera facile pour les autres utilisateurs d'exploiter votre modèle dans leurs applications. + +26 +00:02:35,600 --> 00:02:41,440 +À droite de la carte du modèle se trouve l'API d'inférence. Cette API d'inférence peut être utilisée pour jouer + +27 +00:02:41,440 --> 00:02:46,640 +directement avec le modèle. N'hésitez pas à modifier le texte et à cliquer sur calculer pour voir comment le modèle + +28 +00:02:46,640 --> 00:02:55,200 +se comporterait avec vos entrées. En haut de l'écran se trouvent les balises du modèle. Celles-ci incluent la tâche + +29 +00:02:55,200 --> 00:02:58,640 +ainsi que toute autre balise pertinente pour les catégories que nous venons de voir. + +30 +00:03:01,200 --> 00:03:05,920 +L'onglet « Files and versions » affiche l'architecture du dépôt de ce modèle. + +31 +00:03:07,120 --> 00:03:12,080 +Ici, nous pouvons voir tous les fichiers qui définissent ce modèle. Vous verrez toutes les fonctionnalités habituelles + +32 +00:03:12,080 --> 00:03:22,320 +d'un dépôt git : les branches disponibles, l'historique des commits ainsi que la comparaison des commits. + +33 +00:03:25,600 --> 00:03:28,800 +Trois boutons différents sont disponibles en haut de la carte du modèle. + +34 +00:03:29,600 --> 00:03:32,800 +Le premier montre comment utiliser l'API d'inférence par programmation. + +35 +00:03:35,760 --> 00:03:38,640 +Le deuxième montre comment entraîner ce modèle dans SageMaker, + +36 +00:03:42,720 --> 00:03:45,840 +et le dernier montre comment charger ce modèle dans la bibliothèque appropriée. + +37 +00:03:45,840 --> 00:03:49,000 +Pour BERT, il s'agit de transformers. \ No newline at end of file diff --git a/subtitles/fr/32_managing-a-repo-on-the-model-hub.srt b/subtitles/fr/32_managing-a-repo-on-the-model-hub.srt new file mode 100644 index 000000000..90d7fd09c --- /dev/null +++ b/subtitles/fr/32_managing-a-repo-on-the-model-hub.srt @@ -0,0 +1,355 @@ +1 +00:00:02,560 --> 00:00:09,130 +Dans cette vidéo, nous allons comprendre comment gérer un dépôt de modèles sur le Hub des + +2 +00:00:09,130 --> 00:00:10,920 +modèles d'Hugging Face. + +3 +00:00:10,920 --> 00:00:15,370 +Afin de gérer un dépôt, vous devez d'abord avoir un compte Hugging Face. + +4 +00:00:15,370 --> 00:00:20,310 +Un lien pour créer un nouveau compte est disponible dans la description. + +5 +00:00:20,310 --> 00:00:26,279 +Une fois connecté, vous pouvez créer un nouveau dépôt en cliquant sur l'option « New Model ». + +6 +00:00:26,279 --> 00:00:28,910 +Vous devriez faire face à un modal similaire à celui-ci. + +7 +00:00:28,910 --> 00:00:34,900 +Dans l'entrée « Owner », vous pouvez mettre votre propre nom d'espace ou l'un des + +8 +00:00:34,900 --> 00:00:36,719 +noms d'espaces de votre organisation. + +9 +00:00:36,719 --> 00:00:41,739 +Le nom du modèle est l'identifiant du modèle qui sera ensuite utilisé pour identifier votre modèle sur + +10 +00:00:41,739 --> 00:00:44,250 +le nom d'espace que vous avez choisi. + +11 +00:00:44,250 --> 00:00:47,450 +Le choix final se fait entre public et privé. + +12 +00:00:47,450 --> 00:00:50,100 +Les modèles publics sont accessibles à tous. + +13 +00:00:50,100 --> 00:00:55,030 +Il s'agit de l'option gratuite recommandée, car elle rend votre modèle facilement accessible et partageable. + +14 +00:00:55,030 --> 00:01:00,440 +Les propriétaires de votre espace sont les seuls à pouvoir mettre à jour et modifier votre modèle. + +15 +00:01:00,440 --> 00:01:03,210 +Une option plus avancée est l'option privée. + +16 +00:01:03,210 --> 00:01:08,460 +Dans ce cas, seuls les propriétaires de votre espace auront une visibilité sur votre modèle. + +17 +00:01:08,460 --> 00:01:15,010 +Les autres utilisateurs ne sauront pas qu'il existe et ne pourront pas l'utiliser. + +18 +00:01:15,010 --> 00:01:18,320 +Créons un modèle factice avec lequel jouer. + +19 +00:01:18,320 --> 00:01:22,360 +Une fois votre modèle créé, vient la gestion de ce modèle ! + +20 +00:01:22,360 --> 00:01:24,170 +Trois onglets s'offrent à vous. + +21 +00:01:24,170 --> 00:01:29,070 +Vous faites face au premier, qui est la page de la carte du modèle. C'est la page utilisée pour + +22 +00:01:29,070 --> 00:01:31,170 +présenter votre modèle au monde. + +23 +00:01:31,170 --> 00:01:34,600 +Nous verrons comment cela peut être complété dans un instant. + +24 +00:01:34,600 --> 00:01:38,479 +Le second est « Files and versions ». + +25 +00:01:38,479 --> 00:01:43,310 +Votre modèle lui-même est un dépôt git. Si vous ne savez pas ce qu'est un dépôt git, + +26 +00:01:43,310 --> 00:01:46,439 +vous pouvez le considérer comme un dossier contenant des fichiers. + +27 +00:01:46,439 --> 00:01:51,000 +Si vous n'avez jamais utilisé git auparavant, nous vous recommandons de consulter une introduction comme celle fournie + +28 +00:01:51,000 --> 00:01:53,960 +dans la description de cette vidéo. + +29 +00:01:53,960 --> 00:01:59,020 +Le dépôt git vous permet de voir les changements qui se produisent au fil du temps dans ce dossier, d'où + +30 +00:01:59,020 --> 00:02:00,960 +le terme « versions ». + +31 +00:02:00,960 --> 00:02:07,130 +Nous verrons plus loin comment ajouter des fichiers et des versions. + +32 +00:02:07,130 --> 00:02:12,069 +Le dernier onglet est l'onglet « Seetings » qui vous permet de gérer la visibilité + +33 +00:02:12,069 --> 00:02:14,780 +et la disponibilité de votre modèle. + +34 +00:02:14,780 --> 00:02:18,860 +Commençons d'abord par ajouter des fichiers au dépôt. + +35 +00:02:18,860 --> 00:02:23,459 +Les fichiers peuvent être ajoutés via l'interface web grâce au bouton « Add file ». + +36 +00:02:23,459 --> 00:02:28,849 +Les fichiers ajoutés peuvent être de n'importe quel type : python, json, texte, etc. + +37 +00:02:28,849 --> 00:02:35,110 +En plus de votre fichier ajouté et de son contenu, vous devez nommer votre modification ou commit. + +38 +00:02:35,110 --> 00:02:42,670 +Généralement, l'ajout de fichiers est plus simple lorsque vous utilisez la bibliothèque Python `huggingface-hub` ou en utilisant la ligne de commande. + +39 +00:02:42,670 --> 00:02:47,310 +Nous allons montrer comment faire cela en utilisant la bibliothèque Python `huggingface-hub` + +40 +00:02:47,310 --> 00:02:53,560 +Il y a un lien dans la description d'une vidéo précédente montrant comment faire en utilisant git ou une ligne de commande. + +41 +00:02:53,560 --> 00:03:00.000 +Premièrement, assurez-vous que vous êtes connecté à votre compte Hugging Face, soit par la ligne de commande, soit dans un runtime python. + +42 +00:03:04.000 --> 00:03:08.640 +La première approche que nous allons examiner est l'utilisation de la méthode de téléchargement de fichiers. + +43 +00:03:08.64 --> 00:03:12.480 +Il s'agit d'une API extrêmement simple permettant de télécharger des fichiers vers le Hub. + +44 +00:03:12.480 --> 00:03:18.400 +Les trois paramètres requis sont l'emplacement actuel du fichier, + +45 +00:03:18.400 --> 00:03:23.040 +le chemin de ce fichier dans le dépôt et l'identifiant du dépôt à pousser. + +46 +00:03:25.440 --> 00:03:27.760 +Il y a quelques paramètres supplémentaires. + +47 +00:03:27.760 --> 00:03:34.640 +Le paramètre `token` si vous souhaitez spécifier un token différent de celui enregistré dans votre cache avec votre login. + +48 +00:03:34.640 --> 00:03:42.080 +Le paramètre `repo_type` si vous voulez pousser vers un `dataset` ou un `space`. + +49 +00:03:42.080 --> 00:03:47.040 +On va télécharger un fichier appelé readme.md dans le dépôt en utilisant cette méthode. + +50 +00:03:47.040 --> 00:03:55.760 +Nous commençons par enregistrer un fichier avec ce nom qui contient quelques informations sur le dépôt lui-même, ici un titre. + +51 +00:03:55.760 --> 00:04:01.360 +Maintenant que le fichier est enregistré, utilisons la méthode `upload_file` pour le télécharger sur le Hub. + +52 +00:04:01.360 --> 00:04:06.879 +Si nous passons à l'interface web pendant une seconde et rafraîchissons la page, nous verrons que le fichier readme est affiché. + +53 +00:04:06.879 --> 00:04:10.000 +Le téléchargement du fichier a été un succès. + +54 +00:04:10.000 --> 00:04:16.000 +Parallèlement à cette méthode, il existe une méthode `delete_file` qui vous permet de gérer entièrement votre dépôt. + +55 +00:04:16.000 --> 00:04:18.639 +Nous allons l'utiliser pour supprimer le fichier que nous venons de créer. + +56 +00:04:22.639 --> 00:04:27.500 +Si nous rafraîchissons à nouveau la page, le fichier a bien été supprimé. + +57 +00:04:28.880 --> 00:04:30.639 +Cette approche utilisant uniquement ces deux méthodes est super simple. + +58 +00:04:30.639 --> 00:04:37.440 +Il n'est pas nécessaire d'installer git ou git-lfs mais il y a une limitation. + +59 +00:04:37.440 --> 00:04:42.160 +La taille maximale des fichiers que l'on peut télécharger est limitée à 5 gigaoctets. + +60 +00:04:42.160 --> 00:04:48.400 +Pour surmonter cette limite, examinons la deuxième méthode qui consiste à utiliser l'utilitaire de dépôt. + +61 +00:04:48.400 --> 00:04:57.759 +Cette classe est une enveloppe sur les méthodes git et git-lfs qui abstrait la plupart de la complexité et offre une api flexible pour gérer vos dépôts en ligne. + +62 +00:04:57.759 --> 00:04:59.999 +Voyons comment cela fonctionne. + +63 +00:05:03.680 --> 00:05:07.919 +Nous commençons par instancier l'utilitaire de dépôt. + +64 +00:05:07.919 --> 00:05:14.240 +Nous fournissons le paramètre `clone_from` afin de cloner le dépôt que nous venons de créer. + +65 +00:05:14.240 --> 00:05:16.240 +Le dépôt est maintenant cloné dans le dossier local_folder. + +66 +00:05:16.240 --> 00:05:25.759 +L'objet repo que nous venons d'initialiser offre un certain nombre de méthodes qui nous sont utiles. + +67 +00:05:25.759 --> 00:05:28.560 +Nous sommes intéressés par pousser un modèle sur le Hub. + +68 +00:05:28.560 --> 00:05:34.240 +Je vais commencer par charger un modèle et un tokenizer que j'ai entraîné il y a quelques heures. + +69 +00:05:34.240 --> 00:05:40.800 +Nous allons maintenant suivre l'approche traditionnelle de git en commençant par extraire les derniers changements en utilisant la méthode `git_pull`. + +70 +00:05:40.800 --> 00:05:48.400 +Nous venons de cloner le dépôt, donc à moins qu'il s'agisse d'un dépôt super actif, il est peu probable que de nouvelles modifications soient disponibles. + +71 +00:05:48.400 --> 00:05:53.039 +Mais c'est toujours une bonne idée de récupérer les dernières modifications avant de faire quelque chose de nouveau. + +72 +00:05:53.039 --> 00:05:58.500 +Maintenant que nous avons récupéré le dépôt, je vais sauvegarder le modèle et le tokenizer dans ce dossier. + +73 +00:05:58.500 --> 00:06:02.919 +Cela inclut le fichier de configuration des poids du modèle et les fichiers du tokenizer. + +74 +00:06:04.240 --> 00:06:10.479 +Maintenant que le modèle est sauvegardé, nous allons continuer avec l'approche traditionnelle de git et le pousser vers le dépôt distant. + +75 +00:06:10.479 --> 00:06:15.160 +Si nous utilisions la ligne de commande, il y a quelques commandes spécifiques à git lfs que nous devrions invoquer. + +76 +00:06:15.160 --> 00:06:24.240 +Mais ici, le package `huggingface-hub` s'occupe de tout cela. On comme par mettre en scène les fichiers en utilisant la méthode `git_add`. + +77 +00:06:24.240 --> 00:06:30.639 +Nous allons ensuite commiter ces changements en utilisant la méthode `git_commit` et en fournissant un message de commit utile. + +78 +00:06:30.639 --> 00:06:34.960 +Enfin, nous poussons les changements vers le site distant en utilisant la méthode `git_push`. + +79 +00:06:44.960 --> 00:06:49.759 +Si nous revenons à l'onglet « Files and versions », nous pouvons maintenant voir les fichiers nouvellement commités. + +80 +00:06:49.759 --> 00:06:53.599 +Nous pouvons même jouer avec le modèle dans l'api d'inférence. + +81 +00:06:53.599 --> 00:06:57.360 +Malheureusement, la page d'accueil de notre modèle est encore très vide. + +82 +00:06:57.360 --> 00:07:01.520 +Ajoutons un fichier markdown readme pour la compléter un peu. + +83 +00:07:01.520 --> 00:07:08.919 +Ce fichier readme est connu sous le nom de carte de modèle et il est sans doute aussi important que les fichiers de modèle et de tokenizer dans un dépôt de modèles. + +84 +00:07:08.919 --> 00:07:13.759 +Il s'agit de la définition et de la documentation centrale de votre modèle. + +85 +00:07:13.759 --> 00:07:18.000 +Assurer la réutilisation par les autres membres de la communauté et la reproductibilité des résultats, + +86 +00:07:18.000 --> 00:07:23.039 +fournissant une plateforme sur laquelle les autres membres peuvent construire leurs artefacts. + +87 +00:07:23.039 --> 00:07:30.800 +Pour des raisons de simplicité, nous n'ajouterons qu'un titre et une petite description, mais nous vous encourageons à ajouter des informations sur la manière dont le modèle a été entraîné, + +88 +00:07:30.800 --> 00:07:41.280 +son utilisation prévue et ses limites, ainsi que ses biais potentiels identifiés, les résultats de l'évaluation et des exemples de code sur la façon d'utiliser votre modèle. + +89 +00:07:41.280 --> 00:07:46.879 +Excellent travail de contribution d'un modèle au Hub des modèles. Ce modèle peut maintenant être utilisé dans les bibliothèques en aval en spécifiant simplement l'identifiant de votre modèle. \ No newline at end of file diff --git a/subtitles/fr/33_the-push-to-hub-api-(pytorch).srt b/subtitles/fr/33_the-push-to-hub-api-(pytorch).srt new file mode 100644 index 000000000..18e785d62 --- /dev/null +++ b/subtitles/fr/33_the-push-to-hub-api-(pytorch).srt @@ -0,0 +1,179 @@ +1 +00:00:05,130 --> 00:00:06,130 +L'API `push_to_hub`. + +2 +00:00:06,130 --> 00:00:10,310 +Jetons un coup d'œil à l'API `push_to_hub`. + +3 +00:00:10,310 --> 00:00:16,209 +Vous devrez être connecté avec votre compte Hugging Face, ce que vous pouvez faire en exécutant + +4 +00:00:16,209 --> 00:00:22,220 +cette première cellule ou en tapant `huggingface-cli login` dans un terminal. + +5 +00:00:22,220 --> 00:00:27,480 +Entrez simplement votre nom d'utilisateur et votre mot de passe et cliquez sur connexion. Cela stockera un + +6 +00:00:27,480 --> 00:00:31,230 +jeton d'authentification dans le cache de la machine que vous utilisez. + +7 +00:00:31,230 --> 00:00:37,990 +Maintenant, lançons le finetuning d'un modèle BERT sur le jeu de données GLUE COLA. + +8 +00:00:37,990 --> 00:00:41,900 +Nous n'aborderons pas le code du finetuning car vous pouvez le trouver dans n'importe quel tutoriel sur Transformers + +9 +00:00:41,900 --> 00:00:44,350 +ou en regardant les vidéos liées ci-dessous. + +10 +00:00:44,350 --> 00:00:49,920 +Ce qui nous intéresse ici, c'est comment nous pouvons tirer parti du Hub de modèles pendant l'entraînement. + +11 +00:00:49,920 --> 00:00:56,500 +Cela se fait avec le `push_to_hub=True` passé dans vos `TrainingArguments`. Cela + +12 +00:00:56,500 --> 00:01:02,149 +téléchargera automatiquement votre modèle sur le Hub chaque fois qu'il est enregistré (donc à chaque époque dans notre cas), ce + +13 +00:01:02,149 --> 00:01:08,260 +qui vous permet de reprendre l'entraînement à partir d'une machine différente si la machine actuelle est interrompue. + +14 +00:01:08,260 --> 00:01:13,610 +Le modèle sera téléchargé dans votre espace, avec le nom du dépôt de sortie comme + +15 +00:01:13,610 --> 00:01:14,690 +nom de dépôt. + +16 +00:01:14,690 --> 00:01:20,580 +Vous pouvez choisir un autre nom en le transmettant à l'argument `hub_model_id` et vous pouvez aussi + +17 +00:01:20,580 --> 00:01:32,420 +pousser à l'intérieur d'une organisation dont vous êtes membre en transmettant un nom de dépôt complet. + +18 +00:01:32,420 --> 00:01:43,290 +Ceci fait, nous pouvons simplement lancer l'entraînement et attendre un peu. + +19 +00:01:43,290 --> 00:01:47,820 +Notez que le modèle est poussé de manière asynchrone, ce qui signifie que l'entraînement se poursuit pendant que + +20 +00:01:47,820 --> 00:01:50,119 +votre modèle est téléchargé sur le Hub. + +21 +00:01:50,119 --> 00:02:02,399 +Lorsque votre premier commit est terminé, vous pouvez aller inspecter votre modèle sur le Hub et même + +22 +00:02:02,399 --> 00:02:11,000 +commencer à jouer avec son widget d'inférence pendant qu'il s'entraîne ! + +23 +00:02:11,000 --> 00:02:27,370 +Il y a quelque chose qui ne va pas avec les étiquettes, mais nous corrigerons cela plus tard dans cette vidéo. + +24 +00:02:27,370 --> 00:02:33,590 +Lorsque l'entraînement est terminé, nous devrions faire un dernier push avec `trainer.push_to_hub` pour + +25 +00:02:33,590 --> 00:02:35,330 +deux raisons. + +26 +00:02:35,330 --> 00:02:39,980 +Cela garantira que nous téléchargeons la version finale de nos modèles si nous ne l'avons pas déjà fait + +27 +00:02:39,980 --> 00:02:45,860 +(par exemple, si nous avons enregistré toutes les n étapes au lieu de toutes les époques). + +28 +00:02:45,860 --> 00:02:51,310 +Deuxièmement, cela rédigera une carte de modèle qui sera la page de destination de votre dépôt de modèles. + +29 +00:02:51,310 --> 00:03:00,000 +Quand le commit est fait, revenons à la page du modèle et rafraichissons. Vous pouvez voir le brouillon d'une carte de modèle qui inclus des informations sur quel modèle pré-entraîné nous avons finetuné, + +30 +00:03:00,000 --> 00:03:15,350 +Les hyperparamètres utilisés, les résultats intermédiaires de l'entraînement et les versions des frameworks utilisés pour que les autres personnes puissent reproduirent facilement les résultats. + +31 +00:03:15,350 --> 00:03:22,120 +En haut de toutes ces informations, le `Trainer` fournit aussi des métadonnées qui sont interprétées par le site web d'Hugging Face dans la carte du modèle. + +32 +00:03:22,120 --> 00:03:26,770 +On obtient les valeurs des métriques affichées automatiquement dans un petit widget, et un lien vers + +33 +00:03:26,770 --> 00:03:28,860 +un classement dans Paper with Code. + +34 +00:03:28,860 --> 00:03:36,000 +Les exécutions de Tensorboard ont aussi été poussées vers ce dépôt et nous pouvons les consulter directement depuis le Hub. + +35 +00:03:36,000 --> 00:03:43,709 +Si vous n'utilisiez pas l'API `Trainer` pour finetuner votre modèle, vous pouvez utiliser la méthode + +36 +00:03:43,709 --> 00:03:45,319 +`push_to_hub` directement sur le modèle et le tokenizer. + +37 +00:03:45,319 --> 00:03:49,340 +Testons cela pour corriger nos étiquettes dans le widget d'inférence ! + +38 +00:03:49,340 --> 00:03:54,140 +Le widget d'inférence utilisait des noms par défaut pour les étiquettes car nous n'indiquions pas la + +39 +00:03:54,140 --> 00:03:57,100 +correspondance entre les entiers et les noms d'étiquettes. + +40 +00:03:57,100 --> 00:04:02,909 +Nous pouvons corriger la configuration en définissant les champs `label2id` et `id2label` à leur + +41 +00:04:02,909 --> 00:04:07,370 +valeur appropriée, puis nous pouvons pousser la configuration fixe vers notre dépôt en utilisant la méthode `push_to_hub`. + +43 +00:04:07,370 --> 00:04:13,440 +Une fois que cela est fait et que nous pouvons vérifier sur le site web, le modèle affiche maintenant les bonnes étiquettes ! + +44 +00:04:13,440 --> 00:04:22,370 +Maintenant que le modèle est sur le Hub, nous pouvons l'utiliser de n'importe où avec la méthode `from_pretrained` ou la fonction `pipeline`. + +45 +00:04:35,000 --> 00:04:39,880 +Nous n'avons qu'à utiliser l'identifiant du Hub et nous pouvons voir que la configuration et les poids du modèle sont automatiquement téléchargés. + +46 +00:04:53,949 --> 00:04:57,999 +Essayez l'API `push_to_hub` lors de votre prochain entraînement pour partager facilement votre modèle avec le reste du monde ! \ No newline at end of file diff --git a/subtitles/fr/34_the-push-to-hub-api-(tensorflow).srt b/subtitles/fr/34_the-push-to-hub-api-(tensorflow).srt new file mode 100644 index 000000000..1d29e8ddc --- /dev/null +++ b/subtitles/fr/34_the-push-to-hub-api-(tensorflow).srt @@ -0,0 +1,347 @@ +1 +00:00:05,040 --> 00:00:12,000 +Bonjour, il s'agit d'une vidéo sur l'API `push_to_hub` pour Tensorflow et Keras. Donc, + +2 +00:00:12,000 --> 00:00:16,240 +pour commencer, nous allons ouvrir notre notebook, et la première chose que vous devrez faire est de vous + +3 +00:00:16,240 --> 00:00:22,480 +connecter à votre compte HuggingFace, par exemple avec la fonction de connexion du notebook. Donc, pour ce faire, + +4 +00:00:23,040 --> 00:00:28,560 +vous appelez simplement la fonction, la fenêtre contextuelle apparaîtra, vous entrez votre nom d'utilisateur et votre mot de passe, + +5 +00:00:28,560 --> 00:00:34,160 +que je vais retirer de mon gestionnaire de mots de passe ici, et vous êtes connecté. Les + +6 +00:00:34,160 --> 00:00:38,720 +deux cellules suivantes préparent tout ce qu'il faut pour l'entraînement. Nous allons donc simplement charger un jeu de données, + +7 +00:00:38,720 --> 00:00:42,960 +nous allons tokeniser ce jeu de données, puis nous allons charger notre modèle et le compiler + +8 +00:00:42,960 --> 00:00:47,040 +avec l'optimiseur Adam standard. Je vais donc tout exécuter. + +9 +00:00:49,600 --> 00:00:53,760 +Nous attendrons quelques secondes et tout devrait être prêt pour l'entraînement. + +10 +00:00:56,600 --> 00:01:03,200 +D'accord, nous sommes maintenant prêts pour entraîner. Je vais vous montrer les deux manières de + +11 +00:01:03,200 --> 00:01:07,520 +transférer votre modèle vers le Hub. Donc, la première est avec `PushToHubCallback`. + +12 +00:01:08,080 --> 00:01:14,640 +Ainsi, un « Callback » dans Keras est une fonction qui est appelée régulièrement pendant l'entraînement. Vous pouvez la configurer pour qu'elle soit + +13 +00:01:14,640 --> 00:01:20,640 +appelé après un certain nombre d'étapes, ou à chaque époque, ou même une seule fois à la fin de l'entraînement. + +14 +00:01:22,480 --> 00:01:27,600 +Ainsi, de nombreux « Callback » dans Keras, par exemple, contrôlent le taux 'apprentissage de décroissance sur plateau + +15 +00:01:28,320 --> 00:01:34,400 +et des choses comme ça. Et donc ce « Callback », par défaut, enregistrera votre modèle dans le Hub une fois à + +16 +00:01:34,400 --> 00:01:39,200 +chaque époque. Et c'est vraiment utile, surtout si votre entraînement est très long, car cela signifie que + +17 +00:01:39,200 --> 00:01:43,680 +vous pouvez reprendre à partir de cette sauvegarde, donc vous obtenez cette sauvegarde automatique de votre modèle dans le cloud, et vous pouvez + +18 +00:01:43,680 --> 00:01:49,760 +même exécuter une inférence avec les checkpoints de votre modèle qui ont été téléchargés par ce « Callback ». + +19 +00:01:50,720 --> 00:01:55,120 +Et cela signifie que vous pouvez, vous savez, exécuter des entrées de test et voir réellement comment + +20 +00:01:55,120 --> 00:02:00,560 +votre modèle fonctionne à différentes étapes de l'entraînement, ce qui est une fonctionnalité vraiment intéressante. + +21 +00:02:01,280 --> 00:02:06,560 +Nous allons donc ajouter le `PushToHubCallback` qui ne prend que quelques arguments. Le premier argument + +22 +00:02:06,560 --> 00:02:11,920 +est donc le répertoire temporaire dans lequel les fichiers vont être enregistrés avant d'être téléchargés sur le Hub. + +23 +00:02:11,920 --> 00:02:16,880 +Le deuxième argument est le tokenizer, et le troisième argument ici est l'argument mot clé + +24 +00:02:17,600 --> 00:02:22,160 +`hub_model_id`. C'est donc le nom sous lequel il sera enregistré sur le Hub d'HuggingFace. + +25 +00:02:23,200 --> 00:02:29,760 +Vous pouvez également télécharger vers un compte d'organisation en ajoutant simplement le nom de l'organisation avant + +26 +00:02:29,760 --> 00:02:34,320 +le nom du dépôt avec une barre oblique comme celle-ci. Vous n'avez donc probablement pas les autorisations pour télécharger vers + +27 +00:02:34,320 --> 00:02:38,640 +l'organisation Hugging Face, si vous le faites, veuillez signaler un bogue et nous le faire savoir de toute urgence. + +28 +00:02:40,640 --> 00:02:44,000 +Mais si vous avez accès à votre propre organisation, vous pouvez utiliser la + +29 +00:02:44,000 --> 00:02:47,600 +même approche pour télécharger des modèles sur leur compte plutôt que sur votre propre + +30 +00:02:49,280 --> 00:02:56,080 +jeu de modèles. Ainsi, une fois que vous avez effectué votre `callbacks`, vous l'ajoutez simplement à la liste `callbacks` + +31 +00:02:56,080 --> 00:03:01,280 +lorsque vous êtes appelé `model.fit()` et tout est téléchargé pour vous à partir de là, et vous n'avez + +32 +00:03:01,280 --> 00:03:06,320 +plus à vous inquiéter. La deuxième façon d'importer un modèle consiste à appeler `model.push_to_hub()`. + +33 +00:03:06,880 --> 00:03:11,920 +Il s'agit donc plutôt d'une méthode ponctuelle : elle n'est pas appelée régulièrement pendant l'entraînement. Vous pouvez simplement + +34 +00:03:11,920 --> 00:03:17,680 +l'appeler manuellement chaque fois que vous souhaitez télécharger un modèle sur le Hub. Nous vous recommandons donc de l'exécuter + +35 +00:03:17,680 --> 00:03:22,720 +après la fin de l'entraînement, juste vous assurer que vous avez un message de commit pour garantir + +36 +00:03:22,720 --> 00:03:27,280 +qu'il s'agissait de la version finale du modèle, à la fin de l'entraînement. Et cela garantit simplement que + +37 +00:03:28,160 --> 00:03:32,000 +vous travaillez avec le modèle d'entraînement définitif et que vous n'utilisez pas accidentellement un modèle + +38 +00:03:32,000 --> 00:03:36,720 +provenant d'un checkpoint quelque part en cours de route. Je vais donc exécuter ces deux cellules + +39 +00:03:38,800 --> 00:03:42,320 +puis je vais couper la vidéo ici, simplement parce que l'entraînement va prendre quelques + +40 +00:03:42,320 --> 00:03:46,160 +minutes, et je vais donc passer à la fin de cela, lorsque les modèles ont tous été téléchargés, + +41 +00:03:46,160 --> 00:03:50,880 +et je vais vous montrer comment vous pouvez accéder aux modèles dans le Hub et les autres choses que + +42 +00:03:50,880 --> 00:03:58,400 +vous pouvez faire avec eux à partir de là. D'accord, nous sommes de retour et notre modèle a été téléchargé, à la + +43 +00:03:58,960 --> 00:04:03,760 +fois par le `PushToHubCallback` et également par notre appel à `model.push_to_hub()` après l'entraînement. + +44 +00:04:04,720 --> 00:04:10,320 +Donc tout s'annonce bien ! Alors maintenant, si nous passons sur mon profil sur HuggingFace, que vous pouvez + +45 +00:04:10,320 --> 00:04:15,760 +y accéder en cliquant simplement sur le bouton de profil dans le menu déroulant, nous pouvons voir que le modèle `bert-fine-tuned- + +46 +00:04:15,760 --> 00:04:20,560 +cola` est ici et a été mis à jour il y a 3 minutes. Il figurera donc toujours en haut de votre liste, car + +47 +00:04:20,560 --> 00:04:25,280 +ils sont triés en fonction de leur date de mise à jour récente. Et nous pouvons commencer à interroger notre modèle immédiatement ! + +48 +00:04:26,640 --> 00:04:36,720 +Le jeu de données sur lequel nous avons entraîné est donc le jeu de données GLUE CoLA, et CoLA est l'acronyme de « Corpus + +49 +00:04:36,720 --> 00:04:42,560 +of Linguistic Acceptability ». Cela signifie donc que le modèle est entraîné pour décider si une + +50 +00:04:42,560 --> 00:04:49,040 +phrase est grammaticalement ou linguistiquement correcte, ou s'il y a un problème avec elle. Par exemple, + +51 +00:04:49,680 --> 00:04:54,400 +nous pourrions dire « This is a legitimate sentence » et nous espérons qu'il réalise qu'il s'agit en + +52 +00:04:54,400 --> 00:05:00,880 +fait d'une phrase légitime. Donc, le chargement du modèle peut prendre quelques secondes lorsque vous + +53 +00:05:00,880 --> 00:05:05,200 +l'appelez pour la première fois. Je peux donc supprimer quelques secondes de cette vidéo ici. + +54 +00:05:07,680 --> 00:05:14,160 +Ok, nous sommes de retour ! Le modèle a été chargé et nous avons obtenu un résultat, mais il y a un problème évident ici. + +55 +00:05:14,160 --> 00:05:19,680 +Ces étiquettes ne nous indiquent donc pas vraiment quelles catégories le modèle a réellement attribuées à + +56 +00:05:19,680 --> 00:05:26,720 +cette phrase d'entrée. Donc, si nous voulons résoudre ce problème, nous voulons nous assurer que la configuration du modèle a les + +57 +00:05:26,720 --> 00:05:31,920 +noms corrects pour chacune des classes d'étiquettes, puis nous voulons importer cette configuration. Nous pouvons donc le faire + +58 +00:05:31,920 --> 00:05:38,480 +ici. Pour obtenir les `label_names`, nous pouvons les obtenir à partir du jeu de données que nous avons chargé, à partir de l'attribut `.features` dont + +59 +00:05:38,480 --> 00:05:44,160 +il dispose. Et puis nous pouvons créer des dictionnaires `id2label` et `label2id` + +60 +00:05:45,200 --> 00:05:51,040 +et les attribuer simplement à la configuration du modèle. Puis nous pouvons simplement pousser notre configuration mise à jour et cela + +61 +00:05:51,040 --> 00:05:58,080 +remplacera la configuration existante dans le dépôt sur le Hub. Donc cela vient d'être fait. Donc maintenant si nous revenons ici, + +62 +00:05:58,080 --> 00:06:02,720 +je vais utiliser une phrase légèrement différente parce que les sorties pour les phrases sont parfois + +63 +00:06:02,720 --> 00:06:07,600 +mises en cache, et donc si nous voulons générer de nouveaux résultats je vais utiliser quelque chose de légèrement différent. + +64 +00:06:07,600 --> 00:06:13,840 +Essayons donc une phrase incorrecte, donc ce n'est pas une grammaire anglaise valide et j'espère que le modèle le + +65 +00:06:13,840 --> 00:06:17,360 +verra. Il va se recharger ici, donc je vais couper quelques secondes ici, + +66 +00:06:18,480 --> 00:06:26,400 +et ensuite nous verrons ce que le modèle va dire. Ok ! La confiance du modèle n'est donc pas très bonne + +67 +00:06:26,400 --> 00:06:31,440 +car bien sûr nous n'avons pas du tout optimisé nos hyperparamètres, mais il a décidé que + +68 +00:06:31,440 --> 00:06:37,200 +cette phrase est plus susceptible d'être inacceptable qu'acceptable. Vraisemblablement, si nous essayions un peu + +69 +00:06:37,200 --> 00:06:41,280 +plus avec l'entraînement, nous pourrions obtenir une perte de validation beaucoup plus faible et donc les prédictions du modèle + +70 +00:06:41,280 --> 00:06:47,040 +seraient plus précises. Mais essayons à nouveau notre phrase d'origine. Bien sûr, en + +71 +00:06:47,040 --> 00:06:52,560 +raison du problème de mise en cache, nous constatons que les réponses d'origine sont inchangées. + +72 +00:06:52,560 --> 00:06:58,160 +Essayons donc une phrase différente et valide. Essayons donc « This is a valid English sentence. ». + +73 +00:06:59,920 --> 00:07:03,680 +Et nous voyons que maintenant le modèle décide correctement qu'il a une très forte probabilité d'être + +74 +00:07:03,680 --> 00:07:09,840 +acceptable et une très faible probabilité d'être inacceptable. Vous pouvez donc utiliser cette API d'inférence + +75 +00:07:09,840 --> 00:07:14,320 +même avec les checkpoints téléchargés pendant l'entraînement. Il peut donc être très intéressant de voir comment + +76 +00:07:15,200 --> 00:07:19,680 +les prédictions du modèle pour les exemples d'entrées changent à chaque époque d'entraînement. + +77 +00:07:21,920 --> 00:07:27,040 +De plus, le modèle que nous avons importé sera accessible pour vous et, s'il est partagé publiquement, + +78 +00:07:27,040 --> 00:07:32,240 +par n'importe qui d'autre. Donc, si vous voulez charger ce modèle, tout ce que vous ou quelqu'un d'autre devez faire + +79 +00:07:34,160 --> 00:07:40,640 +est de le charger dans un pipeline ou vous pouvez simplement le charger avec, par exemple, + +80 +00:07:40,640 --> 00:07:50,960 +`TFAutoModelForSequenceClassification`. Puis pour le nom, vous passerez simplement le chemin + +81 +00:07:50,960 --> 00:07:58,560 +vers le dépôt que vous souhaitez charger - ou télécharger, excusez-moi. Donc, si je veux utiliser à nouveau ce modèle, + +82 +00:07:58,560 --> 00:08:02,880 +si je veux le télécharger à partir du Hub, j'exécute simplement cette une ligne de code, le modèle sera téléchargé + +83 +00:08:05,280 --> 00:08:11,200 +et avec un peu de chance, il sera prêt à être finetuner sur un autre jeu de données, + +84 +00:08:11,200 --> 00:08:17,760 +faire des prédictions avec ou faire tout ce que vous voulez faire. C'était donc un bref aperçu de la manière dont, + +85 +00:08:17,760 --> 00:08:21,280 +après ou pendant votre entraînement, vous pouvez télécharger des modèles dans le Hub. + +86 +00:08:21,280 --> 00:08:26,800 +Vous pouvez le sauvegarder, vous pouvez reprendre l'entraînement à partir de là et vous pouvez obtenir des résultats d'inférence à partir + +87 +00:08:26,800 --> 00:08:37,040 +des modèles que vous avez téléchargés. Alors merci, et j'espère vous voir dans une future vidéo ! \ No newline at end of file diff --git a/subtitles/fr/35_loading-a-custom-dataset.srt b/subtitles/fr/35_loading-a-custom-dataset.srt new file mode 100644 index 000000000..f147c232e --- /dev/null +++ b/subtitles/fr/35_loading-a-custom-dataset.srt @@ -0,0 +1,131 @@ +1 +00:00:06,080 --> 00:00:11,600 +Chargement d'un jeu de données personnalisé. Bien que le Hub d'Hugging Face héberge plus d'un millier de jeux de données publics, + +2 +00:00:11,600 --> 00:00:15,040 +vous devrez souvent travailler avec des données stockées sur votre ordinateur portable ou sur un serveur distant. + +3 +00:00:15,760 --> 00:00:19,520 +Dans cette vidéo, nous allons découvrir comment la bibliothèque Datasets peut être utilisée pour charger des jeux de données qui + +4 +00:00:19,520 --> 00:00:24,800 +ne sont pas disponibles sur le Hub d'Hugging Face. Comme vous pouvez le voir dans ce tableau, la bibliothèque Datasets + +5 +00:00:24,800 --> 00:00:30,080 +fournit plusieurs scripts intégrés pour charger des jeux de données dans plusieurs formats. Pour charger un jeu de données dans + +6 +00:00:30,080 --> 00:00:34,160 +l'un de ces formats, il vous suffit de fournir le nom du format à la fonction `load_dataset`, + +7 +00:00:34,160 --> 00:00:38,000 +avec un argument `data_files` qui pointe vers un ou plusieurs chemins de fichier ou URL. + +8 +00:00:40,080 --> 00:00:44,400 +Pour voir cela en action, commençons par charger un fichier CSV local. Dans cet exemple, + +9 +00:00:44,400 --> 00:00:48,720 +nous téléchargeons d'abord un jeu de données sur la qualité du vin à partir du dépôt d'apprentissage automatique de l'UCI. + +10 +00:00:50,080 --> 00:00:56,000 +Comme il s'agit d'un fichier CSV, nous spécifions ensuite le script de chargement `csv`. Ce script doit savoir + +11 +00:00:56,000 --> 00:01:00,160 +où se trouvent nos données, nous fournissons donc le nom de fichier dans le cadre de l'argument `data_files`. + +12 +00:01:01,920 --> 00:01:05,760 +Le script de chargement CSV vous permet également de transmettre plusieurs arguments mots clés. Nous avons donc également + +13 +00:01:05,760 --> 00:01:10,640 +spécifié ici le séparateur sous la forme d'un point-virgule. Et avec cela, nous pouvons voir que le jeu de données est automatiquement chargé en + +14 +00:01:10,640 --> 00:01:15,360 +tant qu'objet `DatasetDict`, avec chaque colonne du fichier CSV représentée comme une caractéristique. + +15 +00:01:17,360 --> 00:01:21,760 +Si votre jeu de données se trouve sur un serveur distant comme GitHub ou un autre dépôt, + +16 +00:01:21,760 --> 00:01:26,320 +le processus est très similaire. La seule différence est que l'argument `data_files` pointe désormais vers une + +17 +00:01:26,320 --> 00:01:33,600 +URL au lieu d'un chemin de fichier local. Voyons maintenant comment charger des fichiers texte bruts. Ce format + +18 +00:01:33,600 --> 00:01:37,840 +est assez courant en NLP et vous constaterez généralement que les livres et les pièces de théâtre ne sont qu'un seul fichier + +19 +00:01:37,840 --> 00:01:43,040 +avec du texte brut à l'intérieur. Dans cet exemple, nous avons un fichier texte de pièces de Shakespeare qui est + +20 +00:01:43,040 --> 00:01:48,880 +stocké sur un dépôt GitHub. Comme nous l'avons fait pour les fichiers CSV, nous choisissons simplement le script de chargement de texte + +21 +00:01:48,880 --> 00:01:54,080 +et faisons pointer l'argument data_files vers l'URL. Comme vous pouvez le voir, ces fichiers sont traités + +22 +00:01:54,080 --> 00:01:58,640 +ligne par ligne, de sorte que les lignes vides dans le texte brut sont également représentées sous forme de ligne dans le jeu de données. + +23 +00:02:00,560 --> 00:02:05,840 +Pour les fichiers JSON, il existe deux formats principaux à connaître. Le premier s'appelle JSON Lines, + +24 +00:02:05,840 --> 00:02:10,880 +où chaque ligne du fichier est un objet JSON distinct. Pour ces fichiers, vous pouvez charger le + +25 +00:02:10,880 --> 00:02:15,760 +jeu de données en sélectionnant le script de chargement `json` et en faisant pointer l'argument `data_files` vers le fichier ou l'URL. + +26 +00:02:16,960 --> 00:02:21,840 +Dans cet exemple, nous avons chargé un fichier de lignes JSON basé sur les questions et réponses Stack Exchange. + +27 +00:02:22,840 --> 00:02:26.400 +L'autre format est celui des fichiers json imbriqués. + +28 +00:02:26.400 --> 00:02:33.440 +Ces fichiers ressemblent à un énorme dictionnaire. La fonction `load_dataset` vous permet donc de spécifier la clé spécifique à charger. + +29 +00:02:33.440 --> 00:02:41.200 +Par exemple, le jeu de données SQUAD pour les réponses aux questions a ce format et nous pouvons le charger en spécifiant que nous sommes intéressés par le champ de données. + +30 +00:02:41.200 --> 00:02:44.720 +Il y a juste une dernière chose à mentionner à propos de tous ces scripts de chargement. + +31 +00:02:44.720 --> 00:02:51.920 +Si vous avez plus d'un split, vous pouvez les charger en traitant les fichiers de données comme un dictionnaire qui fait correspondre chaque nom de split à son fichier correspondant. + +32 +00:02:51.920 --> 00:02:59.360 +Tout le reste reste inchangé et vous pouvez voir un exemple de chargement des splits d'entraînement et de validation de SQUAD ici. + +33 +00:02:59.360 --> 00:03:03.440 +Et avec cela, vous pouvez maintenant charger des jeux de données depuis votre ordinateur portable, le Hub d'Hugging Face ou n'importe où ailleurs. \ No newline at end of file diff --git "a/subtitles/fr/36_slice-and-dice-a-dataset-\360\237\224\252.srt" "b/subtitles/fr/36_slice-and-dice-a-dataset-\360\237\224\252.srt" new file mode 100644 index 000000000..10abbdff6 --- /dev/null +++ "b/subtitles/fr/36_slice-and-dice-a-dataset-\360\237\224\252.srt" @@ -0,0 +1,163 @@ +1 +00:00:05,680 --> 00:00:07,440 +Comment découper et trancher un jeu de données. + +2 +00:00:08,640 --> 00:00:12,320 +La plupart du temps, les données avec lesquelles vous travaillez ne sont pas parfaitement préparées pour les modèles d'entraînement. + +3 +00:00:13,120 --> 00:00:17,920 +Dans cette vidéo, nous allons explorer différentes fonctionnalités fournies par Datasets pour nettoyer vos données. + +4 +00:00:19,760 --> 00:00:23,520 +La bibliothèque Datasets fournit plusieurs méthodes intégrées qui vous permettent d'organiser vos données de diverses manières. + +5 +00:00:25,200 --> 00:00:29,360 +Dans cette vidéo, nous verrons comment mélanger et diviser vos données, sélectionner les lignes qui vous + +6 +00:00:29,360 --> 00:00:33,840 +intéressent, modifier les colonnes et appliquer des fonctions de traitement avec la méthode `map()`. + +7 +00:00:35,440 --> 00:00:39,920 +Commençons par mélanger. C'est généralement une bonne idée d'appliquer un mélange au jeu d'entraînement + +8 +00:00:39,920 --> 00:00:42,640 +afin que votre modèle n'apprenne aucun classement artificiel dans les données. + +9 +00:00:43,360 --> 00:00:46,880 +Si vous souhaitez mélanger l'intégralité de le jeu de données, vous pouvez appliquer la méthode `shuffle()` correctement nommée + +10 +00:00:46,880 --> 00:00:51,280 +à votre jeu de données. Vous pouvez voir un exemple de cette méthode en action ici, où nous avons téléchargé + +11 +00:00:51,280 --> 00:00:56,960 +l'échantillon d'entraînement du jeu de données SQUAD et mélangé toutes les lignes de manière aléatoire. Une autre + +12 +00:00:56,960 --> 00:01:00,000 +façon de mélanger les données consiste à créer des échantillons d'entraînement et de test aléatoires. + +13 +00:01:00,720 --> 00:01:05,600 +Cela peut être utile si vous devez créer vos propres échantillons de test à partir de données brutes. Pour ce faire, il vous suffit + +14 +00:01:05,600 --> 00:01:11,760 +d'appliquer la méthode `train_test_split` et de spécifier la taille de la répartition de test. Dans cet exemple, + +15 +00:01:11,760 --> 00:01:17,280 +nous avons spécifié que le jeu de test doit représenter 10 % de la taille totale du jeu de données. Vous pouvez voir que + +16 +00:01:17,280 --> 00:01:22,400 +la sortie de `train_test_split` est un objet `DatasetDict`, dont les clés correspondent aux nouveaux échantillons. + +17 +00:01:24,960 --> 00:01:28,400 +Maintenant que nous savons comment mélanger un jeu de données, examinons comment renvoyer les lignes qui + +18 +00:01:28,400 --> 00:01:32,080 +nous intéressent. La méthode la plus courante consiste à utiliser la méthode `select`. + +19 +00:01:32,960 --> 00:01:36,560 +Cette méthode attend une liste ou un générateur d'indices du jeu de données, + +20 +00:01:36,560 --> 00:01:39,840 +et renverra ensuite un nouvel objet Dataset contenant uniquement ces lignes. + +21 +00:01:41,280 --> 00:01:45,600 +Si vous souhaitez créer un échantillon aléatoire de lignes, vous pouvez le faire en enchaînant les méthodes de mélange et de sélection + +22 +00:01:45,600 --> 00:01:51,120 +Dans cet exemple, nous avons créé un échantillon de cinq éléments à partir du jeu de données SQuAD. + +23 +00:01:53,280 --> 00:01:57,360 +La dernière façon de sélectionner des lignes spécifiques dans un jeu de données consiste à appliquer la méthode `filter`. + +24 +00:01:58,080 --> 00:02:01,360 +Cette méthode vérifie si chaque ligne remplit une condition ou non. + +25 +00:02:02,080 --> 00:02:05,840 +Par exemple, nous avons créé ici une petite fonction lambda qui vérifie si le + +26 +00:02:05,840 --> 00:02:10,800 +titre commence par la lettre « L ». Une fois que nous appliquons cette fonction avec la méthode `filter`, + +27 +00:02:10,800 --> 00:02:13,840 +nous obtenons un sous-ensemble de données composé uniquement de ces lignes. + +28 +00:02:16,080 --> 00:02:19,360 +Jusqu'à présent, nous avons parlé des lignes d'un jeu de données, mais qu'en est-il des colonnes ? + +29 +00:02:20,240 --> 00:02:23,280 +La bibliothèque Datasets propose deux méthodes principales pour transformer les colonnes : + +30 +00:02:23,840 --> 00:02:26,480 +une méthode `rename_column` pour modifier le nom d'une colonne, + +31 +00:02:26,480 --> 00:02:31,360 +et une méthode `remove_columns` pour les supprimer. Vous pouvez voir des exemples de ces deux méthodes ici. + +32 +00:02:34,000 --> 00:02:38,400 +Certains jeux de données ont des colonnes imbriquées et vous pouvez les développer en appliquant la méthode `flatten`. + +33 +00:02:39,120 --> 00:02:44,240 +Par exemple, dans le jeu de données SQUAD, la colonne des réponses contient un texte et un champ answer_start. + +34 +00:02:44,960 --> 00:02:49,840 +Si nous voulons les promouvoir dans leurs propres colonnes distinctes, nous pouvons appliquer `flatten` comme indiqué ici. + +35 +00:02:51,280 --> 00:02:55,040 +Bien sûr, aucune discussion sur la bibliothèque Datasets ne serait complète sans mentionner la + +36 +00:02:55,040 --> 00:03:00,240 +fameuse méthode `map`. Cette méthode applique une fonction de traitement personnalisée à chaque ligne du jeu de données. + +37 +00:03:00,960 --> 00:03:06,480 +Par exemple, ici, nous définissons d'abord une fonction `lowercase_title` qui met simplement le texte en minuscules dans la + +38 +00:03:06,480 --> 00:03:13,760 +colonne de titre, puis nous transmettons cela à la méthode `map` et voilà ! Nous avons maintenant des titres en minuscules. + +39 +00:03:15,760 --> 00:03:19,280 +La méthode `map` peut également être utilisée pour fournir des batchs de lignes à la fonction de traitement. + +40 +00:03:19,840 --> 00:03:24,240 +Ceci est particulièrement utile pour la tokenisation, où les tokenizers sont soutenus par la + +41 +00:03:24,240 --> 00:03:31,840 +bibliothèque Tokenizers peut utiliser le multithreading rapide pour traiter les batchs en parallèle. \ No newline at end of file diff --git "a/subtitles/fr/37_datasets-+-dataframes-=-\342\235\244\357\270\217.srt" "b/subtitles/fr/37_datasets-+-dataframes-=-\342\235\244\357\270\217.srt" new file mode 100644 index 000000000..4b7f17563 --- /dev/null +++ "b/subtitles/fr/37_datasets-+-dataframes-=-\342\235\244\357\270\217.srt" @@ -0,0 +1,115 @@ +1 +00:00:05,200 --> 00:00:11,680 +Datasets + DataFrames = amour. Bien que les fonctions de traitement de la bibliothèque Datasets couvrent la plupart + +2 +00:00:11,680 --> 00:00:15,600 +des cas nécessaires pour entraîner un modèle, il y a des moments où vous devrez passer à une bibliothèque + +3 +00:00:15,600 --> 00:00:21,840 +comme Pandas pour accéder à des fonctionnalités plus puissantes ou à des API de haut niveau pour la visualisation. Heureusement, + +4 +00:00:21,840 --> 00:00:25,520 +Datasets est conçu pour être interopérable avec des bibliothèques telles que Pandas, + +5 +00:00:25,520 --> 00:00:30,560 +ainsi que NumPy, PyTorch, TensorFlow et JAX. Dans cette vidéo, nous allons + +6 +00:00:30,560 --> 00:00:33,920 +voir comment nous pouvons rapidement basculer nos données vers Pandas DataFrames et inversement. + +7 +00:00:35,920 --> 00:00:41,280 +Supposons, par exemple, que nous analysions des affaires de la Cour suprême en Suisse. Comme d'habitude, + +8 +00:00:41,280 --> 00:00:45,440 +nous téléchargeons notre jeu de données à partir du Hub à l'aide de la fonction `load_dataset()` et vous pouvez voir que le + +9 +00:00:45,440 --> 00:00:49,600 +premier élément du jeu d'entraînement est un dictionnaire Python ordinaire avec divers domaines d'intérêt. + +10 +00:00:51,440 --> 00:00:54,800 +Supposons maintenant qu'avant d'entraîner des modèles, nous souhaitions explorer un peu les données. + +11 +00:00:55,360 --> 00:00:58,720 +Par exemple, nous pourrions être intéressés à savoir quel domaine juridique est le plus courant + +12 +00:00:59,600 --> 00:01:02,480 +ou nous pourrions vouloir savoir comment les langues sont réparties entre les régions. + +13 +00:01:04,320 --> 00:01:07,920 +Répondre à ces questions avec le format Arrow natif n'est pas facile, mais nous pouvons facilement + +14 +00:01:07,920 --> 00:01:13,280 +passer à Pandas pour obtenir nos réponses ! Cela fonctionne en utilisant la méthode `set_format()`, + +15 +00:01:13,280 --> 00:01:17,600 +qui changera le format de sortie du jeu de données des dictionnaires Python aux Pandas DataFrames. + +16 +00:01:18,720 --> 00:01:22,720 +Comme vous pouvez le voir dans cet exemple, chaque ligne du jeu de données est représentée sous la forme d'un DataFrame, + +17 +00:01:22,720 --> 00:01:26,160 +afin que nous puissions découper le jeu de données pour obtenir un seul DataFrame du corpus. + +18 +00:01:27,840 --> 00:01:31,040 +La façon dont cela fonctionne sous le capot est que la bibliothèque Datasets modifie la + +19 +00:01:31,040 --> 00:01:35,440 +méthode magique `__getitem__()` de Datasets. La méthode `__getitem__()` est une méthode spéciale + +20 +00:01:35,440 --> 00:01:40,320 +pour les conteneurs Python qui vous permet de spécifier le fonctionnement de l'indexation. Dans ce cas, + +21 +00:01:40,320 --> 00:01:44,320 +la méthode `__getitem__()` du jeu de données brut commence par renvoyer des dictionnaires Python, + +22 +00:01:45,120 --> 00:01:49,920 +puis après avoir appliqué `set_format()`, nous modifions `__getitem__()` pour renvoyer des DataFrames à la place. + +23 +00:01:51,840 --> 00:01:56,240 +La bibliothèque Datasets fournit également une méthode `to_pandas()` si vous souhaitez effectuer la conversion de format et le + +24 +00:01:56,240 --> 00:02:02,640 +découpage du jeu de données en une seule fois. Et une fois que vous disposez d'un DataFrame, vous pouvez trouver des réponses à toutes + +25 +00:02:02,640 --> 00:02:07,840 +sortes de questions complexes ou créer des graphiques avec votre bibliothèque de visualisation préférée. + +26 +00:02:07,840 --> 00:02:10,800 +La seule chose à retenir est qu'une fois que vous avez terminé votre analyse Pandas, + +27 +00:02:10,800 --> 00:02:16,240 +vous devez réinitialiser le format de sortie en Arrow tables. Si vous ne le faites pas, vous pouvez rencontrer des problèmes si + +28 +00:02:16,240 --> 00:02:20,240 +vous essayez de tokeniser votre texte car il n'est plus représenté sous forme de chaînes dans un dictionnaire. + +29 +00:02:21,520 --> 00:02:32,160 +En réinitialisant le format de sortie, nous récupérons les Arrow tables et pouvons tokeniser sans problème ! \ No newline at end of file diff --git a/subtitles/fr/38_saving-and-reloading-a-dataset.srt b/subtitles/fr/38_saving-and-reloading-a-dataset.srt new file mode 100644 index 000000000..a4f3a7d97 --- /dev/null +++ b/subtitles/fr/38_saving-and-reloading-a-dataset.srt @@ -0,0 +1,143 @@ +1 +00:00:06,560 --> 00:00:11,600 +Enregistrement et rechargement d'un jeu de données. Dans cette vidéo, nous allons examiner l'enregistrement d'un jeu de données dans différents + +2 +00:00:11,600 --> 00:00:19,200 +formats et explorer les moyens de recharger les données enregistrées. Lorsque vous téléchargez un jeu de données, les + +3 +00:00:19,200 --> 00:00:23,920 +scripts de traitement et les données sont stockés localement sur votre ordinateur. Le cache permet à la bibliothèque Datasets + +4 +00:00:23,920 --> 00:00:29,600 +d'éviter de retélécharger ou de traiter l'intégralité du jeu de données à chaque fois que vous l'utilisez. Les données sont stockées + +5 +00:00:29,600 --> 00:00:34,080 +sous la forme d'Arrow tables dont l'emplacement peut être trouvé en accédant à l'attribut `cache_files`. + +6 +00:00:34,080 --> 00:00:39,360 +Dans cet exemple, nous avons téléchargé le jeu de données allocine à partir du Hub d'Hugging Face + +7 +00:00:39,360 --> 00:00:43,840 +et vous pouvez voir qu'il y a trois fichiers Arrow stockés dans le cache, un pour chaque échantillon. + +8 +00:00:45,120 --> 00:00:48,720 +Mais dans de nombreux cas, vous souhaiterez enregistrer votre jeu de données dans un emplacement ou un format différent. + +9 +00:00:49,600 --> 00:00:53,760 +Comme indiqué dans le tableau, la bibliothèque Datasets fournit quatre fonctions principales pour y parvenir. + +10 +00:00:54,880 --> 00:00:59,040 +Vous connaissez probablement les formats CSV et JSON , qui sont tous deux parfaits si vous + +11 +00:00:59,040 --> 00:01:04,800 +souhaitez enregistrer des jeux de données de petite à moyenne taille. Mais si votre jeu de données est volumineux, vous souhaiterez + +12 +00:01:04,800 --> 00:01:09,520 +l'enregistrer au format Arrow ou Parquet. Les fichiers Arrow sont parfaits si vous prévoyez de recharger + +13 +00:01:09,520 --> 00:01:14,080 +ou de traiter les données dans un avenir proche. Les fichiers Parquet sont conçus pour un stockage sur disque à long terme + +14 +00:01:14,080 --> 00:01:17,440 +et sont très économes en espace. Examinons de plus près chaque format. + +15 +00:01:19,520 --> 00:01:25,520 +Pour enregistrer un jeu de données ou un objet DatasetDict au format Arrow, nous utilisons la fonction `save_to_disk`. Comme + +16 +00:01:25,520 --> 00:01:30,240 +vous pouvez le voir dans cet exemple, nous fournissons simplement le chemin dans lequel nous souhaitons enregistrer les données, et la bibliothèque Datasets + +17 +00:01:30,240 --> 00:01:34,720 +créera automatiquement un dépôt pour chaque échantillon afin de stocker l'Arrow table et les métadonnées. + +18 +00:01:35,600 --> 00:01:38,880 +Étant donné que nous avons affaire à un objet DatasetDict comportant plusieurs divisions, + +19 +00:01:38,880 --> 00:01:41,920 +ces informations sont également stockées dans le fichier `dataset_dict.json`. + +20 +00:01:44,160 --> 00:01:48,000 +Désormais, lorsque nous souhaitons recharger les jeux de données Arrow, nous utilisons la fonction `load_from_disk`. + +21 +00:01:48,640 --> 00:01:53,840 +Nous passons simplement le chemin de notre dépôt de jeu de données et voilà ! Le jeu de données d'origine est récupéré. + +22 +00:01:55,760 --> 00:01:59,920 +Si nous voulons enregistrer nos jeux de données au format CSV, nous utilisons la fonction `to_csv`. + +23 +00:02:00,800 --> 00:02:05,280 +Dans ce cas, vous devrez boucler sur les échantillons de l'objet DatasetDict et enregistrer chaque jeu de données en tant + +24 +00:02:05,280 --> 00:02:11,280 +que fichier CSV individuel. Étant donné que le fichier `to_csv` est basé sur celui de Pandas, vous pouvez transmettre + +25 +00:02:11,280 --> 00:02:16,240 +des arguments mots clés pour configurer la sortie. Dans cet exemple, nous avons défini l'argument `index` sur + +26 +00:02:16,240 --> 00:02:23,440 +`None` pour empêcher la colonne d'index du jeu de données d'être incluse dans les fichiers CSV. Pour recharger nos + +27 +00:02:23,440 --> 00:02:29,760 +fichiers CSV, nous utilisons la fonction familière `load_dataset` avec le script de chargement csv et + +28 +00:02:29,760 --> 00:02:35,120 +l'argument `data_files` qui spécifie les noms de fichiers associés à chaque échantillon. Comme vous pouvez le voir dans cet exemple, + +29 +00:02:35,120 --> 00:02:39,280 +en fournissant toutes les échantillons et leurs noms de fichiers, nous avons récupéré l'objet DatasetDict d'origine. + +30 +00:02:41,840 --> 00:02:45,920 +L'enregistrement d'un jeu de données aux formats JSON ou Parquet est très similaire au cas CSV. + +31 +00:02:46,480 --> 00:02:52,720 +Nous utilisons soit la fonction `to_json` pour les fichiers JSON, soit la fonction `to_parquet` pour les fichiers Parquet. Et + +32 +00:02:52,720 --> 00:02:57,440 +tout comme dans le cas CSV, nous devons boucler sur les échantillons et enregistrer chacune dans un fichier individuel. + +33 +00:02:59,680 --> 00:03:03,760 +Une fois que nos jeux de données sont enregistrés en tant que fichiers JSON ou Parquet, nous pouvons les recharger à nouveau + +34 +00:03:03,760 --> 00:03:09,680 +avec le script approprié dans la fonction `load_dataset` et nous devons juste donner l'argument `data_files` comme auparavant. + +35 +00:03:10,640 --> 00:03:14,160 +Cet exemple montre comment recharger nos jeux de données enregistrés dans l'un ou l'autre format. + +36 +00:03:16,400 --> 00:03:26,000 +Et avec cela, vous savez maintenant comment enregistrer vos jeux de données dans différents formats ! \ No newline at end of file diff --git a/subtitles/fr/39_memory-mapping-&-streaming.srt b/subtitles/fr/39_memory-mapping-&-streaming.srt new file mode 100644 index 000000000..a5d7bb1fb --- /dev/null +++ b/subtitles/fr/39_memory-mapping-&-streaming.srt @@ -0,0 +1,147 @@ +1 +00:00:05,520 --> 00:00:10,720 +Association de la mémoire et streaming. Dans cette vidéo, nous allons examiner deux fonctionnalités principales de la bibliothèque Datasets + +2 +00:00:10,720 --> 00:00:15,920 +qui vous permettent de charger et de traiter d'énormes jeux de données sans faire exploser le processeur de votre ordinateur portable. + +3 +00:00:18,160 --> 00:00:22,720 +De nos jours, il n'est pas rare de travailler avec des jeux de données de plusieurs Go, en + +4 +00:00:22,720 --> 00:00:26,880 +particulier si vous envisagez de pré-entraîner un transformer comme BERT ou GPT-2 à partir de zéro. + +5 +00:00:27,920 --> 00:00:30,480 +Dans ces cas, même le chargement des données peut être un défi. + +6 +00:00:31,040 --> 00:00:36,560 +Par exemple, le corpus C4 utilisé pour pré-entraîner T5 se compose de plus de 2 téraoctets de données ! + +7 +00:00:38,160 --> 00:00:42,720 +Pour gérer ces grands jeux de données, la bibliothèque Datasets repose sur deux fonctionnalités principales : + +8 +00:00:42,720 --> 00:00:45,120 +le format Apache Arrow et une API de streaming. + +9 +00:00:46,160 --> 00:00:51,120 +Arrow est conçu pour le traitement de données hautes performances et représente chaque jeu + +10 +00:00:51,120 --> 00:00:56,240 +de données sous forme de tableau avec un format de colonne en mémoire. Comme vous pouvez le voir dans cet exemple, les formats en colonnes regroupent + +11 +00:00:56,240 --> 00:01:01,280 +les éléments d'une table dans des blocs consécutifs de RAM, ce qui permet un accès et un traitement rapides. + +12 +00:01:02,560 --> 00:01:07,600 +Arrow est idéal pour traiter des données à n'importe quelle échelle, mais certains jeux de données sont si volumineux que vous ne pouvez même + +13 +00:01:07,600 --> 00:01:12,480 +pas les faire tenir sur votre disque dur. Dans ces cas, la bibliothèque Datasets fournit une API de streaming + +14 +00:01:13,040 --> 00:01:18,080 +qui vous permet de télécharger progressivement les données brutes un élément à la fois. Le résultat est + +15 +00:01:18,080 --> 00:01:21,600 +un objet spécial appelé `IterableDataset` que nous verrons plus en détail bientôt. + +16 +00:01:23,520 --> 00:01:28,160 +Commençons par examiner pourquoi Arrow est si puissant. La première fonctionnalité est qu'il traite chaque + +17 +00:01:28,160 --> 00:01:34,000 +jeu de données comme un fichier associé en mémoire. L'association de la mémoire est un mécanisme qui associe une partie d'un fichier ou + +18 +00:01:34,000 --> 00:01:38,967 +un fichier entier sur le disque à un morceau de mémoire virtuelle. Cela permet aux applications + +19 +00:01:38,967 --> 00:01:43,360 +d'accéder à des segments d'un fichier extrêmement volumineux sans avoir à lire d'abord l'intégralité du fichier en mémoire. + +20 +00:01:44,960 --> 00:01:49,040 +Une autre fonctionnalité intéressante de la capacité d'association de la mémoire d'Arrow est qu'elle permet à plusieurs + +21 +00:01:49,040 --> 00:01:53,840 +processus de travailler avec le même grand jeu de données sans le déplacer ni le copier de quelque manière que ce soit. + +22 +00:01:55,520 --> 00:01:59,920 +Cette fonctionnalité « zéro copie » d'Arrow rend l'itération extrêmement rapide sur un jeu de données. + +23 +00:02:00,480 --> 00:02:05,920 +Dans cet exemple, vous pouvez voir que nous parcourons plus de 15 millions de lignes en une minute environ à l'aide d'un + +24 +00:02:05,920 --> 00:02:12,480 +ordinateur portable standard. Ce n'est pas si mal du tout ! Voyons maintenant comment streamer un grand jeu de données. + +25 +00:02:12,480 --> 00:02:16,720 +La seule modification que vous devez apporter est de définir l'argument `streaming=True` dans la fonction `load_dataset()`.   + +26 +00:02:16,720 --> 00:02:21,120 +Cela renverra un objet `IterableDataset` spécial, qui est un peu + +27 +00:02:21,120 --> 00:02:26,160 +différent des objets Dataset que nous avons vus dans d'autres vidéos. Cet objet est un itérable, ce qui signifie que + +28 +00:02:26,160 --> 00:02:31,680 +nous ne pouvons pas l'indexer pour accéder aux éléments, mais plutôt itérer dessus à l'aide des méthodes `iter` et `next`. + +29 +00:02:32,640 --> 00:02:36,080 +Cela téléchargera et accédera à un exemple unique à partir de le jeu de données, ce qui signifie que + +30 +00:02:36,080 --> 00:02:39,760 +vous pouvez parcourir progressivement un énorme jeu de données sans avoir à le télécharger au préalable. + +31 +00:02:41,840 --> 00:02:47,040 +La tokenisation de texte avec la méthode `map()` fonctionne également de la même manière. Nous streamons d'abord le jeu de données, + +32 +00:02:47,040 --> 00:02:52,480 +puis appliquons la méthode `map()` avec le tokenizer. Pour obtenir le premier exemple tokenisé, nous appliquons iter et + +33 +00:02:52,480 --> 00:02:58,560 +next. La principale différence avec un `IterableDataset est` qu'au lieu d'utiliser la méthode `select()` pour + +34 +00:02:58,560 --> 00:03:04,240 +renvoyer l'exemple, nous utilisons les méthodes `take()` et skip()` car nous ne pouvons pas indexer dans le jeu de données. + +35 +00:03:04,240 --> 00:03:10,320 +La méthode `take()` renvoie les N premiers exemples du jeu de données, tandis que `skip()` ignore les N premiers + +36 +00:03:10,320 --> 00:03:15,680 +et renvoie le reste. Vous pouvez voir des exemples des deux en action ici, où nous créons + +37 +00:03:15,680 --> 00:03:27,040 +un jeu de validation à partir des 1 000 premiers exemples, puis nous les ignorons pour créer le jeu d'entrapinement. \ No newline at end of file diff --git a/subtitles/fr/40_uploading-a-dataset-to-the-hub.srt b/subtitles/fr/40_uploading-a-dataset-to-the-hub.srt new file mode 100644 index 000000000..15caa795b --- /dev/null +++ b/subtitles/fr/40_uploading-a-dataset-to-the-hub.srt @@ -0,0 +1,87 @@ +1 +00:00:07,760 --> 00:00:11,760 +Dans cette vidéo, nous verrons comment importer votre propre jeu de données dans le Hub d'Hugging Face. + +2 +00:00:13,520 --> 00:00:16,560 +La première chose à faire est de créer un nouveau dépôt de jeu de données sur le Hub. + +3 +00:00:17,360 --> 00:00:20,480 +Cliquez simplement sur l'icône de votre profil et sélectionnez le bouton « New Dataset ». + +4 +00:00:21,600 --> 00:00:26,720 +Ensuite, nous devons attribuer un propriétaire au jeu de données. Par défaut, il s'agira de votre compte, + +5 +00:00:26,720 --> 00:00:29,840 +mais vous pouvez également créer des jeux de données sous n'importe quelle organisation à laquelle vous appartenez. + +6 +00:00:30,720 --> 00:00:36,160 +Ensuite, nous devons simplement donner un nom au jeu de données et indiquer s'il s'agit d'un jeu de données public ou privé. + +7 +00:00:37,200 --> 00:00:41,520 +Les jeux de données publics sont accessibles à tous, tandis que les jeux de données privés ne sont accessibles que + +8 +00:00:41,520 --> 00:00:46,800 +par vous ou les membres de votre organisation. Et avec cela, nous pouvons continuer et créer le jeu de données ! + +9 +00:00:48,480 --> 00:00:52,800 +Maintenant que vous disposez d'un dépôt de jeux de données vide sur le Hub, la prochaine chose à faire est d'y ajouter des + +10 +00:00:52,800 --> 00:00:59,360 +données. Vous pouvez le faire avec Git, mais le moyen le plus simple consiste à sélectionner « Upload file » et à télécharger + +11 +00:00:59,360 --> 00:01:04,880 +les fichiers directement depuis votre machine. Une fois les fichiers importés, ils s'affichent dans le + +12 +00:01:04,880 --> 00:01:11,360 +dépôt sous l'onglet « Files and versions ». La dernière étape consiste à créer une carte de jeu de données. Des + +13 +00:01:11,360 --> 00:01:14,160 +jeux de données bien documentés sont plus susceptibles d'être utiles aux autres car ils + +14 +00:01:14,160 --> 00:01:18,400 +fournissent le contexte pour décider si le jeu de données est pertinent ou s'il existe des biais ou des + +15 +00:01:18,400 --> 00:01:23,680 +risques associés à l'utilisation du jeu de données. Sur le Hub d'Hugging Face, ces informations sont stockées dans + +16 +00:01:23,680 --> 00:01:29,440 +le fichier README.md de chaque dépôt et vous devez suivre deux étapes principales. Vous devez + +17 +00:01:29,440 --> 00:01:33,360 +d'abord créer des métadonnées qui permettront à d'autres personnes de trouver facilement votre jeu de données sur le Hub. + +18 +00:01:34,400 --> 00:01:38,560 +Vous pouvez créer ces métadonnées à l'aide de l'application de marquage des jeux de données, dont le lien est trouvable dans la + +19 +00:01:38,560 --> 00:01:43,040 +description de la vidéo. Une fois que vous avez créé les métadonnées, vous pouvez remplir le reste de la carte du + +20 +00:01:43,040 --> 00:01:49,200 +jeu de données et nous fournissons un patron qui est également lié dans la vidéo. Et une fois que votre jeu de données + +21 +00:01:49,200 --> 00:01:53,680 +est sur le Hub, vous pouvez le charger à l'aide de la fonction fiable `load_dataset()` Fournissez simplement + +22 +00:01:53,680 --> 00:02:04,000 +le nom de votre dépôt et un argument `data_files`  pour les fichiers et vous êtes prêt ! \ No newline at end of file diff --git a/subtitles/fr/41_text-embeddings-&-semantic-search.srt b/subtitles/fr/41_text-embeddings-&-semantic-search.srt new file mode 100644 index 000000000..fe1eb8e8f --- /dev/null +++ b/subtitles/fr/41_text-embeddings-&-semantic-search.srt @@ -0,0 +1,147 @@ +1 +00:00:05,520 --> 00:00:11,200 +Enchâssements de texte et recherche sémantique. Dans cette vidéo, nous allons explorer comment les transformers représentent le + +2 +00:00:11,200 --> 00:00:15,920 +texte en tant que vecteurs d'enchâssement et comment ces vecteurs peuvent être utilisés pour rechercher des documents similaires dans un corpus. + +3 +00:00:17,520 --> 00:00:22,000 +Les enchâssements de texte ne sont qu'une façon élégante de dire que nous pouvons représenter le texte sous la forme d'un tableau de nombres + +4 +00:00:22,000 --> 00:00:27,120 +appelé vecteur. Pour créer ces enchâssements, nous utilisons généralement un modèle basé sur un encodeur tel que BERT. + +5 +00:00:28,320 --> 00:00:32,320 +Dans cet exemple, vous pouvez voir comment nous envoyons trois phrases à l'encodeur et obtenons + +6 +00:00:32,320 --> 00:00:36,400 +trois vecteurs en sortie. En lisant le texte, nous pouvons voir que « promener le + +7 +00:00:36,400 --> 00:00:40,880 +chien » ressemble le plus à « promener le chat ». Mais voyons si nous pouvons quantifier cela ! + +8 +00:00:42,560 --> 00:00:46,080 +L'astuce pour effectuer la comparaison consiste à calculer une métrique de similarité entre chaque + +9 +00:00:46,080 --> 00:00:50,880 +paire de vecteurs d'enchâssements. Ces vecteurs vivent généralement dans un espace de grande dimension. + +10 +00:00:50,880 --> 00:00:54,640 +Donc une métrique de similarité peut donc être tout ce qui mesure une sorte de distance entre les vecteurs. + +11 +00:00:55,520 --> 00:01:00,560 +Une métrique populaire est la similarité cosinus, qui utilise l'angle entre deux vecteurs pour mesurer + +12 +00:01:00,560 --> 00:01:06,160 +leur proximité. Dans cet exemple, nos vecteurs d'enchâssements vivent en 3D et nous pouvons voir que les + +13 +00:01:06,160 --> 00:01:12,080 +vecteurs orange et gris sont proches l'un de l'autre et ont un angle plus petit. Maintenant, un problème auquel nous devons faire + +14 +00:01:12,080 --> 00:01:16,640 +face est que les transformers comme BERT renverront en fait un vecteur d'enchâssement par token. + +15 +00:01:17,680 --> 00:01:22,560 +Par exemple, dans la phrase « J'ai emmené mon chien en promenade », on peut s'attendre à plusieurs vecteurs d'enchâssement, + +16 +00:01:22,560 --> 00:01:28,880 +un pour chaque mot. Par exemple, nous pouvons voir ici que la sortie de notre modèle a produit 9 + +17 +00:01:28,880 --> 00:01:35,200 +vecteurs d'enchâssement par phrase, et chaque vecteur a 384 dimensions. Mais ce que nous voulons vraiment, c'est un seul + +18 +00:01:35,200 --> 00:01:41,040 +vecteur d'enchâssement pour toute la phrase. Pour résoudre ce problème, nous pouvons utiliser une technique appelée « pooling ». + +19 +00:01:41,760 --> 00:01:45,840 +La méthode de pooling la plus simple consiste à prendre simplement l'enchâssement de token du token [CLS]. + +20 +00:01:46,880 --> 00:01:50,160 +Alternativement, nous pouvons faire la moyenne des enchâssements des tokens, ce que l'on appelle + +21 +00:01:50,160 --> 00:01:56,400 +le « mean-pooling ». Avec le mean-pooling, la seule chose dont nous devons nous assurer est que nous n'incluons pas + +22 +00:01:56,400 --> 00:02:00,640 +les tokens de rembourrage dans la moyenne, c'est pourquoi vous pouvez voir le masque d'attention utilisé ici. + +23 +00:02:01,680 --> 00:02:07,160 +Cela nous donne maintenant un vecteur de 384 dimensions par phrase, ce qui correspond exactement à ce que nous voulons ! Et + +24 +00:02:07,840 --> 00:02:12,240 +une fois que nous avons nos enchâssements de phrases, nous pouvons calculer la similarité cosinus pour chaque paire de + +25 +00:02:12,240 --> 00:02:17,520 +vecteurs. Dans cet exemple, nous utilisons la fonction de scikit-learn et vous pouvez voir que la phrase « J'ai + +26 +00:02:17,520 --> 00:02:22,400 +emmené mon chien en promenade » a un chevauchement de 0,83 avec « J'ai emmené mon chat en promenade ». Hourra ! Nous + +27 +00:02:25,040 --> 00:02:29,600 +pouvons pousser cette idée un peu plus loin en comparant la similarité entre une question et un corpus + +28 +00:02:29,600 --> 00:02:36,000 +de documents. Par exemple, supposons que nous enchâssions chaque message dans les forums d'Hugging Face. Nous pouvons ensuite poser une + +29 +00:02:36,000 --> 00:02:41,600 +question, l'enchâsser et vérifier quels messages du forum sont les plus similaires. Ce processus est souvent appelé + +30 +00:02:41,600 --> 00:02:48,000 +recherche sémantique car il nous permet de comparer les requêtes avec le contexte. Créer un + +31 +00:02:48,000 --> 00:02:54,400 +moteur de recherche sémantique est assez simple dans Datasets. Nous devons d'abord enchâsser tous les documents. Dans cet exemple, + +32 +00:02:54,400 --> 00:02:59,120 +nous prenons un petit échantillon du jeu de données SQUAD et appliquons la même logique d'enchâssement qu'auparavant. + +33 +00:03:00,000 --> 00:03:03,840 +Cela nous donne une nouvelle colonne appelée `embeddings` qui stocke l'enchâssement de chaque passage. + +34 +00:03:05,680 --> 00:03:09,280 +Une fois que nous avons nos enchâssements, nous avons besoin d'un moyen de trouver les voisins les plus proches d'une requête. + +35 +00:03:10,080 --> 00:03:14,320 +La bibliothèque Datasets fournit un objet spécial appelé index FAISS qui vous permet de comparer rapidement les + +36 +00:03:14,320 --> 00:03:18,880 +enchâssements. Nous ajoutons donc l'index FAISS, enchâssons une question et voilà ! + +37 +00:03:18,880 --> 00:03:29,360 +Nous avons maintenant trouvé les trois articles les plus similaires susceptibles de stocker la réponse. \ No newline at end of file diff --git a/subtitles/fr/42_training-a-new-tokenizer.srt b/subtitles/fr/42_training-a-new-tokenizer.srt new file mode 100644 index 000000000..6a34c0a20 --- /dev/null +++ b/subtitles/fr/42_training-a-new-tokenizer.srt @@ -0,0 +1,267 @@ +1 +00:00:05,310 --> 00:00:12,220 +Dans cette vidéo nous allons voir ensemble quel est le but d'entraîner un tokenizer, quelles + +2 +00:00:12,220 --> 00:00:18,770 +sont les étapes clés à suivre et quelle est la manière la plus simple de le faire. + +3 +00:00:18,770 --> 00:00:23,039 +Vous vous poserez la question « Dois-je entraîner un nouveau tokenizer ? » + +4 +00:00:23,039 --> 00:00:27,369 +lorsque vous envisagez de entraîner un nouveau modèle à partir de zéro. + +5 +00:00:27,369 --> 00:00:36,600 +Un tokenizer entraîné ne serait pas adapté à votre corpus si votre corpus est dans une + +6 +00:00:36,600 --> 00:00:43,640 +langue différente, utilise de nouveaux caractères tels que des accents ou des lettres majuscules, a un vocabulaire spécifique, + +7 +00:00:43,640 --> 00:00:50,980 +par exemple médical ou juridique, ou utilise un style différent, une langue d'un autre siècle par + +8 +00:00:50,980 --> 00:00:51,989 +exemple. + +9 +00:00:51,989 --> 00:01:00,719 +Si je prends le tokenizer entraîné pour le modèle `bert-base-uncased` et ignore + +10 +00:01:00,719 --> 00:01:08,580 +son étape de normalisation alors nous pouvons voir que l'opération de tokenisation sur la + +11 +00:01:08,580 --> 00:01:14,310 +phrase anglaise « here is a sentence adapted to our tokenizer » produit une liste plutôt satisfaisante + +12 +00:01:14,310 --> 00:01:20,820 +de tokens dans le sens où cette phrase de 8 mots est tokenisée en 9 tokens. + +13 +00:01:20,820 --> 00:01:29,909 +Par contre si on utilise ce même tokenizer sur une phrase en bengali, on voit que soit + +14 +00:01:29,909 --> 00:01:36,320 +un mot est divisé en plusieurs sous tokens soit que le tokenizer ne connait pas un des + +15 +00:01:36,320 --> 00:01:41,359 +caractères unicode et ne retourne qu'un token inconnu. + +16 +00:01:41,359 --> 00:01:47,350 +Le fait qu'un mot commun soit divisé en plusieurs tokens peut être problématique car les + +17 +00:01:47,350 --> 00:01:52,750 +modèles de langage ne peuvent gérer qu'une séquence de tokens de longueur limitée. + +18 +00:01:52,750 --> 00:01:59,290 +Un tokenizer qui divise excessivement votre texte initial peut même avoir un impact sur les performances de votre + +19 +00:01:59,290 --> 00:02:00,290 +modèle. + +20 +00:02:00,290 --> 00:02:05,060 +Les tokens [UNK] sont également problématiques car le modèle ne pourra extraire aucune + +21 +00:02:05,060 --> 00:02:11,440 +information de la partie inconnue du texte. + +22 +00:02:11,440 --> 00:02:16,910 +Dans cet autre exemple, on peut voir que le tokenizer remplace les mots contenant des caractères + +23 +00:02:16,910 --> 00:02:21,230 +avec des accents et des majuscules par des tokens inconnus. + +24 +00:02:21,230 --> 00:02:28,140 +Enfin, si nous réutilisons ce tokenizer pour tokeniser du vocabulaire médical nous constatons à nouveau + +25 +00:02:28,140 --> 00:02:37,349 +qu'un même mot est divisé en plusieurs sous-tokens : 4 pour « paracetamol » et « pharyngitis ». + +26 +00:02:37,349 --> 00:02:42,050 +La plupart des tokenizers utilisés par les modèles de langage de pointe actuels doivent être + +27 +00:02:42,050 --> 00:02:48,160 +entraînés sur un corpus similaire à celui utilisé pour pré-entraîner le modèle de langage. + +28 +00:02:48,160 --> 00:02:54,390 +Cette entraînement consiste à apprendre des règles pour diviser le texte en tokens et la manière + +29 +00:02:54,390 --> 00:03:00,510 +d'apprendre ces règles et de les utiliser dépend du modèle de tokenizer choisi. + +30 +00:03:00,510 --> 00:03:06,710 +Ainsi, pour entraîner un nouveau tokenizer il faut d'abord construire un corpus d'apprentissage composé + +31 +00:03:06,710 --> 00:03:09,239 +de textes bruts. + +32 +00:03:09,239 --> 00:03:13,440 +Ensuite, vous devez choisir une architecture pour votre tokenizer. + +33 +00:03:13,440 --> 00:03:19,640 +Ici deux options s'offrent à vous : la plus simple est de réutiliser la même architecture que celle + +34 +00:03:19,640 --> 00:03:26,760 +d'un tokenizer utilisé par un autre modèle déjà entraîné, sinon il est également possible de + +35 +00:03:26,760 --> 00:03:33,950 +concevoir entièrement votre tokenizer mais cela demande plus d'expérience et d'attention. + +36 +00:03:33,950 --> 00:03:39,620 +Une fois l'architecture choisie, on peut ainsi entraîner ce tokenizer sur votre corpus constitué. + +37 +00:03:39,620 --> 00:03:44,870 +Enfin, la dernière chose que vous devez faire est de sauvegarder les règles apprises pour pouvoir + +38 +00:03:44,870 --> 00:03:49,780 +utiliser ce tokenizer qui est maintenant prêt à être utilisé. + +39 +00:03:49,780 --> 00:03:55,120 +Prenons un exemple : supposons que vous souhaitiez entraîner un modèle GPT-2 sur du code Python. + +40 +00:03:55,120 --> 00:04:03,000 +Même si le code Python est en anglais ce type de texte est très spécifique et mériterait + +41 +00:04:03,000 --> 00:04:09,800 +un tokenizer entraîné dessus. Pour vous en convaincre nous verrons à la fin la différence + +42 +00:04:09,800 --> 00:04:11,319 +produite sur un exemple. + +43 +00:04:11,319 --> 00:04:18,889 +Pour cela nous allons utiliser la méthode `train_new_from_iterator` que possèdent tous les tokenizers rapides de la + +44 +00:04:18,889 --> 00:04:22,530 +librairie et donc notamment `GPT2TokenizerFast`. + +45 +00:04:22,530 --> 00:04:28,389 +C'est la méthode la plus simple dans notre cas pour avoir un tokenizer adapté au code Python. + +46 +00:04:28,389 --> 00:04:34,229 +Rappelez-vous, la première étape consiste à rassembler un corpus de entraînement. + +47 +00:04:34,229 --> 00:04:39,639 +Nous utiliserons une sous-partie de le jeu de données CodeSearchNet contenant uniquement des fonctions Python provenant de + +48 +00:04:39,639 --> 00:04:42,039 +bibliothèques open source sur GitHub. + +49 +00:04:42,039 --> 00:04:48,890 +Ça tombe bien, ce jeu de données est connu par la bibliothèque Datasets et on peut le charger en + +50 +00:04:48,890 --> 00:04:51,190 +deux lignes de code. + +51 +00:04:51,190 --> 00:04:57,940 +Ensuite, comme la méthode `train_new_from_iterator` attend un itérateur de listes de textes nous créons + +52 +00:04:57,940 --> 00:05:04,030 +la fonction `get_training_corpus` qui retournera un itérateur. + +53 +00:05:04,030 --> 00:05:10,861 +Maintenant que nous avons notre itérateur sur notre corpus de fonctions Python, nous pouvons charger l'architecture du tokenizer de GPT-2 + +54 +00:05:10,861 --> 00:05:12,490 +. + +55 +00:05:12,490 --> 00:05:19,450 +Ici `old_tokenizer` n'est pas adapté à notre corpus mais nous n'avons besoin que d'une ligne de plus pour + +56 +00:05:19,450 --> 00:05:21,850 +l'entraîner sur notre nouveau corpus. + +57 +00:05:21,850 --> 00:05:29,310 +Un argument commun à la plupart des algorithmes de tokenisation utilisés à l'heure actuelle + +58 +00:05:29,310 --> 00:05:33,370 +est la taille du vocabulaire, nous choisissons ici la valeur 52 000. + +59 +00:05:33,370 --> 00:05:38,780 +Enfin, une fois l'entraînement terminée, il suffit de sauvegarder notre nouveau tokenizer en local + +60 +00:05:38,780 --> 00:05:45,430 +ou de l'envoyer au Hub pour pouvoir le réutiliser très facilement par la suite. + +61 +00:05:45,430 --> 00:05:49,962 +Enfin, voyons ensemble sur un exemple s'il était utile de ré-entraîner un tokenizer similaire + +62 +00:05:49,962 --> 00:05:55,259 +à celui de GTP-2. + +63 +00:05:55,259 --> 00:06:01,610 +Avec le tokenizer original de GPT-2 on voit que tous les espaces sont isolés et le + +64 +00:06:01,610 --> 00:06:05,860 +nom de méthode `randn` relativement courant dans le code python est divisé en 2. + +65 +00:06:05,860 --> 00:06:10,919 +Avec notre nouveau tokenizer, les indentations simples et doubles ont été apprises et la méthode + +66 +00:06:10,919 --> 00:06:13,410 +`randn` est tokenisé en 1 token. + +67 +00:06:13,410 --> 00:06:23,190 +Et avec cela, vous savez maintenant comment entraîner vos propres tokenizers ! \ No newline at end of file diff --git a/subtitles/fr/43_why-are-fast-tokenizers-called-fast.srt b/subtitles/fr/43_why-are-fast-tokenizers-called-fast.srt new file mode 100644 index 000000000..4c991e41a --- /dev/null +++ b/subtitles/fr/43_why-are-fast-tokenizers-called-fast.srt @@ -0,0 +1,71 @@ +1 +00:00:05,200 --> 00:00:11,600 +Pourquoi les tokenizers rapides sont-ils appelés rapides ? Dans cette vidéo, nous verrons exactement à quel point les tokenizers dits + +2 +00:00:11,600 --> 00:00:16,960 +rapides sont plus rapides que leurs homologues lents. Pour ce benchmark, + +3 +00:00:16,960 --> 00:00:22,160 +nous utiliserons le jeu de données GLUE MNLI, qui contient 432 000 paires de textes. + +4 +00:00:22,880 --> 00:00:27,040 +Nous verrons combien de temps il faut aux versions rapides et lentes d'un tokenizer BERT + +5 +00:00:27,040 --> 00:00:34,080 +pour les traiter toutes. Nous définissons notre tokenizer rapide et lent à l'aide de l'API `AutoTokenizer`. Le + +6 +00:00:34,080 --> 00:00:40,160 +tokeniser rapide est la valeur par défaut (lorsqu'il est disponible), nous transmettons donc `use_fast=False` pour définir le lent. + +7 +00:00:41,200 --> 00:00:45,760 +Dans un notebook, nous pouvons chronométrer l'exécution d'une cellule avec la commande magique `%time`, comme ceci. + +8 +00:00:46,560 --> 00:00:50,720 +Le traitement du jeu de données complet est quatre fois plus rapide avec un tokeniser rapide. + +9 +00:00:50,720 --> 00:00:54,960 +C'est en effet plus rapide, mais pas très impressionnant. C'est parce que nous avons transmis les + +10 +00:00:54,960 --> 00:00:59,520 +textes au tokenizer un par un. C'est une erreur courante à faire avec les tokenizers rapides, + +11 +00:00:59,520 --> 00:01:04,320 +qui sont soutenus par Rust et donc capables de paralléliser la tokenisation de plusieurs textes. + +12 +00:01:05,120 --> 00:01:09,520 +Leur transmettre un seul texte à la fois, c'est comme envoyer un cargo entre deux continents + +13 +00:01:09,520 --> 00:01:15,600 +avec un seul conteneur, c'est très inefficace. Pour libérer toute la vitesse de nos tokenizers rapides, + +14 +00:01:15,600 --> 00:01:20,320 +nous devons leur envoyer des batchs de textes, ce que nous pouvons faire avec l'argument `batched=True` + +15 +00:01:20,320 --> 00:01:26,720 +de la méthode `map`. Maintenant, ces résultats sont impressionnants ! Le tokenizer rapide prend 12 secondes pour + +16 +00:01:26,720 --> 00:01:33,280 +traiter un jeu de données qui prend 4 minutes pour le tokenizer lent. En résumant les résultats dans ce tableau, + +17 +00:01:33,280 --> 00:01:37,200 +vous pouvez voir pourquoi nous avons appelé ces tokenizers rapides. Et ce n'est que pour la + +18 +00:01:37,200 --> 00:01:48,160 +tokenisation des textes. Si jamais vous avez besoin d'entraîner un nouveau tokenizer, ils le font aussi très rapidement ! \ No newline at end of file diff --git a/subtitles/fr/44_fast-tokenizer-superpowers.srt b/subtitles/fr/44_fast-tokenizer-superpowers.srt new file mode 100644 index 000000000..db6633778 --- /dev/null +++ b/subtitles/fr/44_fast-tokenizer-superpowers.srt @@ -0,0 +1,167 @@ +1 +00:00:05,109 --> 00:00:10,089 +Les tokenizers rapides de la bibliothèque Transformers sont rapides, mais ils implémentent également des fonctionnalités + +2 +00:00:10,089 --> 00:00:14,610 +qui seront très utiles pour le pré-traitement et le post-traitement des données. + +3 +00:00:14,610 --> 00:00:16,750 +Jetons y un coup d'œil ! + +4 +00:00:16,750 --> 00:00:21,759 +Voyons d'abord la sortie habituelle d'un tokenizer. + +5 +00:00:21,759 --> 00:00:28,039 +Nous obtenons des `input_ids` qui correspondent à des tokens, mais nous perdons beaucoup d'informations au cours du processus. + +6 +00:00:28,039 --> 00:00:33,270 +Par exemple, ici la tokenisation est la même pour les deux phrases, même si l'une a + +7 +00:00:33,270 --> 00:00:36,510 +plusieurs espaces de plus que l'autre. + +8 +00:00:36,510 --> 00:00:41,090 +Le simple fait d'avoir les identifiants d'entrée n'est donc pas suffisant si nous voulons faire correspondre certains tokens avec une étendue + +9 +00:00:41,090 --> 00:00:46,610 +de texte (ce que nous devrons faire lorsque nous aborderons la question-réponse par exemple). + +10 +00:00:46,610 --> 00:00:51,699 +Il est également difficile de savoir quand deux tokens appartiennent ou non au même mot : cela semble facile + +11 +00:00:51,699 --> 00:00:57,250 +quand on regarde simplement la sortie d'un tokenizer BERT, il suffit de chercher le ##. Mais + +12 +00:00:57,250 --> 00:01:01,490 +d'autres tokenizers ont différentes façons de tokeniser des parties de mots. + +13 +00:01:01,490 --> 00:01:06,910 +Par exemple, RoBERTa ajoute ce symbole spécial G pour marquer les tokens au début d'un mot, + +14 +00:01:06,910 --> 00:01:12,160 +et T5 utilise ce symbole spécial de soulignement dans le même but. + +15 +00:01:12,160 --> 00:01:16,759 +Heureusement, les tokenizers rapides gardent une trace du mot d'où provient chaque token, avec une + +16 +00:01:16,759 --> 00:01:20,090 +méthode `word_ids` que vous pouvez utiliser sur leurs sorties. + +17 +00:01:20,090 --> 00:01:24,799 +La sortie n'est pas nécessairement claire, mais assemblées dans un joli tableau comme celui-ci, nous pouvons + +18 +00:01:24,799 --> 00:01:28,119 +regarder la position du mot pour chaque token. + +19 +00:01:28,119 --> 00:01:32,659 +Mieux encore, les tokenisers rapides gardent une trace de l'étendue des caractères d'où provient chaque token, + +20 +00:01:32,659 --> 00:01:38,780 +et nous pouvons les obtenir en l'appelant sur un (ou plusieurs) texte en ajoutant l'argument `return_offsets_mapping=True` + +21 +00:01:38,780 --> 00:01:39,780 +. + +22 +00:01:39,780 --> 00:01:46,469 +Dans ce cas, nous pouvons voir comment nous sautons des positions entre le token `##s` et le super token, en + +23 +00:01:46,469 --> 00:01:50,579 +raison des multiples espaces dans la phrase initiale. + +24 +00:01:50,579 --> 00:01:54,470 +Pour ce faire, les tokenizers rapides stockent des informations supplémentaires à chaque étape de leur + +25 +00:01:54,470 --> 00:01:55,470 +pipeline interne. + +26 +00:01:55,470 --> 00:02:00,899 +Ce pipeline interne consiste en une normalisation, où nous appliquons un peu de nettoyage au texte, + +27 +00:02:00,899 --> 00:02:05,600 +comme la mise en minuscules ou la suppression des accents. La pré-tokénisation, où nous divisons + +28 +00:02:05,600 --> 00:02:09,940 +les textes en mots puis nous appliquons le modèle du tokenizer, c'est là que les + +29 +00:02:09,940 --> 00:02:15,300 +mots sont divisés en tokens avant de faire finalement le post-traitement, où des tokens spéciaux + +30 +00:02:15,300 --> 00:02:17,110 +sont ajoutés. + +31 +00:02:17,110 --> 00:02:20,730 +Du début à la fin du pipeline, le tokenizer garde une trace de chaque étendue de + +32 +00:02:20,730 --> 00:02:23,680 +texte qui correspond à chaque mot, puis à chaque token. + +33 +00:02:23,680 --> 00:02:29,099 +Nous verrons à quel point cela est utile lorsque nous aborderons les tâches suivantes : lors de la + +34 +00:02:29,099 --> 00:02:34,360 +modélisation d'un langage masqué, une variante qui obtient des résultats de pointe consiste à masquer tous les tokens d'un + +35 +00:02:34,360 --> 00:02:37,600 +mot donné au lieu de tokens choisis au hasard. + +36 +00:02:37,600 --> 00:02:40,909 +Cela nous obligera à utiliser les identifiants de mots que nous avons vus. + +37 +00:02:40,909 --> 00:02:45,209 +Lors de la classification des tokens, nous devrons convertir les étiquettes que nous avons sur les mots en + +38 +00:02:45,209 --> 00:02:47,230 +étiquettes sur chaque token. + +39 +00:02:47,230 --> 00:02:51,360 +Quant aux mappages de décalage, ils seront très utiles lorsque nous aurons besoin de convertir des positions de token + +40 +00:02:51,360 --> 00:02:56,330 +dans une phrase en une étendue de texte, ce que nous aurons besoin de savoir lorsque nous examinerons la réponse à une + +41 +00:02:56,330 --> 00:03:01,200 +question ou lors du regroupement des tokens correspondant à la même entité dans la classification de token. + +42 +00:03:01,200 --> 00:03:09,730 +Pour jeter un œil à ces tâches, consultez les vidéos en description ! \ No newline at end of file diff --git a/subtitles/fr/45_inside-the-token-classification-pipeline-(pytorch).srt b/subtitles/fr/45_inside-the-token-classification-pipeline-(pytorch).srt new file mode 100644 index 000000000..2a7569af4 --- /dev/null +++ b/subtitles/fr/45_inside-the-token-classification-pipeline-(pytorch).srt @@ -0,0 +1,123 @@ +1 +00:00:05,200 --> 00:00:08,080 +Examinons le pipeline de classification de tokens. + +2 +00:00:10,000 --> 00:00:13,920 +Dans la vidéo sur le pipeline, nous avons examiné les différentes applications prêtes à l'emploi + +3 +00:00:13,920 --> 00:00:19,840 +prises en charge par la bibliothèque Transformers, l'une d'entre elles étant la classification de tokens. Par exemple en prédisant pour + +4 +00:00:19,840 --> 00:00:24,960 +chaque mot d'une phrase s'il correspond à une personne, une organisation ou un lieu. + +5 +00:00:26,400 --> 00:00:30,240 +On peut même regrouper les tokens correspondant à une même entité, + +6 +00:00:30,240 --> 00:00:34,960 +par exemple tous les tokens qui ont formé ici le mot « Sylvain », ou « Hugging » et « Face ». + +7 +00:00:36,960 --> 00:00:42,480 +Le pipeline de classification de tokens fonctionne de la même manière que le pipeline de classification de texte que nous avons étudié + +8 +00:00:42,480 --> 00:00:49,360 +dans une vidéo précédente. Il y a trois étapes : la tokenisation, le modèle et le post-traitement. + +9 +00:00:50,720 --> 00:00:55,680 +Les deux premières étapes sont identiques au pipeline de classification de texte, sauf que nous utilisons un + +10 +00:00:55,680 --> 00:01:01,760 +modèle de classification de token automatique au lieu d'un modèle de classification de séquence. Nous tokenisons notre texte, puis nous + +11 +00:01:01,760 --> 00:01:07,360 +le donnons au modèle. Au lieu d'obtenir un numéro pour chaque étiquette possible pour la phrase entière, + +12 +00:01:07,360 --> 00:01:13,760 +nous obtenons un numéro pour chacune des 9 étiquettes possibles pour chaque token de la phrase, ici 19. + +13 +00:01:15,120 --> 00:01:19,600 +Comme tous les autres modèles de la bibliothèque Transformers, notre modèle génère des logits, + +14 +00:01:19,600 --> 00:01:26,160 +que nous transformons en prédictions en utilisant une SoftMax. Nous obtenons également l'étiquette prédite pour chaque token en + +15 +00:01:26,160 --> 00:01:30,000 +prenant la prédiction maximale (puisque la fonction softmax préserve l'ordre, nous aurions pu le + +16 +00:01:30,000 --> 00:01:35,200 +faire sur les logits si nous n'avions pas besoin des prédictions). La configuration du modèle contient + +17 +00:01:35,200 --> 00:01:41,200 +l'application des étiquettes dans son champ `id2label`. En l'utilisant, nous pouvons associer chaque token à son étiquette correspondante. Le + +18 +00:01:41,200 --> 00:01:46,400 +L'étiquette `O` correspond à « aucune entité », c'est pourquoi nous ne l'avons pas vu dans nos résultats de la première diapositive. + +19 +00:01:47,040 --> 00:01:51,360 +En plus de l'étiquette et de la probabilité, ces résultats incluaient le caractère de début et de + +20 +00:01:51,360 --> 00:01:56,960 +fin dans la phrase. Nous devrons utiliser l'association de décalage du tokenizer pour les obtenir + +21 +00:01:56,960 --> 00:02:02,080 +(regardez la vidéo en lien ci-dessous si vous ne les connaissez pas déjà). Ensuite, en parcourant chaque + +22 +00:02:02,080 --> 00:02:08,240 +token ayant une étiquette distincte de `O`, nous pouvons créer la liste des résultats que nous avons obtenus avec notre premier pipeline. + +23 +00:02:08,240 --> 00:02:13,360 +La dernière étape consiste à regrouper les tokens correspondant à la même entité. + +24 +00:02:13,360 --> 00:02:17,680 +C'est pourquoi nous avions deux étiquettes pour chaque type d'entité : `I-PER` et `B-PER` par exemple. + +25 +00:02:18,240 --> 00:02:21,840 +Cela nous permet de savoir si un token est dans la même entité que le précédent. + +26 +00:02:23,120 --> 00:02:26,720 +Notez qu'il existe deux manières d'étiqueter utilisées pour la classification des tokens, + +27 +00:02:26,720 --> 00:02:31,680 +l'une (en rose ici) utilise l'étiquette `B-PER` au début de chaque nouvelle entité, mais l'autre + +28 +00:02:31,680 --> 00:02:38,320 +(en bleu) ne l'utilise que pour séparer deux entités adjacentes du même type. Dans les deux cas, nous pouvons + +29 +00:02:38,320 --> 00:02:44,720 +marquer une nouvelle entité chaque fois que nous voyons apparaître une nouvelle étiquette (avec le préfixe `I` ou `B`), puis + +30 +00:02:44,720 --> 00:02:50,160 +prendre tous les tokens suivants étiquetés de la même manière, avec un drapeau `I`. Ceci, associé + +31 +00:02:50,160 --> 00:03:01,040 +mapl'association de décalage pour obtenir les caractères de début et de fin, nous permet d'obtenir l'étendue des textes pour chaque entité. \ No newline at end of file diff --git a/subtitles/fr/46_inside-the-token-classification-pipeline-(tensorflow).srt b/subtitles/fr/46_inside-the-token-classification-pipeline-(tensorflow).srt new file mode 100644 index 000000000..e87d03494 --- /dev/null +++ b/subtitles/fr/46_inside-the-token-classification-pipeline-(tensorflow).srt @@ -0,0 +1,159 @@ +1 +00:00:05,250 --> 00:00:09,840 +Jetons un coup d'œil à l'intérieur du pipeline de classification de tokens. + +2 +00:00:09,840 --> 00:00:14,690 +Dans la vidéo du pipeline, nous avons examiné les différentes applications + +3 +00:00:14,690 --> 00:00:20,820 +prises en charge par la bibliothèque Transformers, l'une d'entre elles étant la classification de tokens, par exemple en prédisant pour chaque mot d'une + +4 +00:00:20,820 --> 00:00:27,760 +phrase s'il correspond à une personne, une organisation ou un lieu. + +5 +00:00:27,760 --> 00:00:32,660 +On peut même regrouper les tokens correspondant à une même entité, par exemple tous les tokens + +6 +00:00:32,660 --> 00:00:37,950 +qui ont formé ici le mot « Sylvain », ou « Hugging » et « Face ». + +7 +00:00:37,950 --> 00:00:42,480 +Le pipeline de classification de tokens fonctionne de la même manière que le pipeline de classification de texte que + +8 +00:00:42,480 --> 00:00:44,379 +nous avons étudié dans une vidéo précédente. + +9 +00:00:44,379 --> 00:00:49,600 +Il y a trois étapes : la tokenisation, le modèle et le post-traitement. + +10 +00:00:49,600 --> 00:00:56,340 +Les deux premières étapes sont identiques au pipeline de classification de texte, sauf que nous utilisons un + +11 +00:00:56,340 --> 00:01:01,640 +modèle de classification de token automatique au lieu d'un modèle de classification de séquence. + +12 +00:01:01,640 --> 00:01:05,840 +Nous tokenisons notre texte puis le transmettons au modèle. + +13 +00:01:05,840 --> 00:01:10,400 +Au lieu d'obtenir un numéro pour chaque étiquette possible pour toute la phrase, nous obtenons un numéro + +14 +00:01:10,400 --> 00:01:16,690 +pour chacune des 9 étiquettes possibles pour chaque token de la phrase, ici 19. + +15 +00:01:16,690 --> 00:01:22,299 +Comme tous les autres modèles de la bibliothèque Transformers, notre modèle génère des logits, que nous + +16 +00:01:22,299 --> 00:01:25,900 +transformons en prédictions en utilisant une SoftMax. + +17 +00:01:25,900 --> 00:01:31,430 +Nous obtenons également l'étiquette prédite pour chaque token en prenant la prédiction maximale (puisque la + +18 +00:01:31,430 --> 00:01:35,470 +fonction softmax préserve l'ordre, nous aurions pu le faire sur les logits si nous n'avions pas besoin + +19 +00:01:35,470 --> 00:01:37,759 +des prédictions). + +20 +00:01:37,759 --> 00:01:42,340 +La configuration du modèle contient l'association d'étiquette dans son champ `id2label`. + +21 +00:01:42,340 --> 00:01:45,331 +En l'utilisant, nous pouvons mapper chaque token à son étiquette correspondante. + +22 +00:01:45,331 --> 00:01:50,490 +L'étiquette `O` correspond à « aucune entité », c'est pourquoi nous ne l'avons pas vu dans nos résultats dans + +23 +00:01:50,490 --> 00:01:51,579 +la première diapositive. + +24 +00:01:51,579 --> 00:01:57,430 +En plus de l'étiquette et de la probabilité, ces résultats incluaient le caractère de début et de fin + +25 +00:01:57,430 --> 00:01:58,570 +dans la phrase. + +26 +00:01:58,570 --> 00:02:02,610 +Nous devrons utiliser l'association de décalage du tokenizer pour les obtenir (regardez la vidéo + +27 +00:02:02,610 --> 00:02:04,820 +liée ci-dessous si vous ne les connaissez pas déjà). + +28 +00:02:04,820 --> 00:02:09,750 +Ensuite, en parcourant chaque token qui a une étiquette distincte de `O`, nous pouvons construire la + +29 +00:02:09,750 --> 00:02:12,440 +liste des résultats que nous avons obtenus avec notre premier pipeline. + +30 +00:02:12,440 --> 00:02:18,920 +La dernière étape consiste à regrouper les tokens qui correspondent à la même entité. + +31 +00:02:18,920 --> 00:02:23,290 +C'est pourquoi nous avions deux labels pour chaque type d'entité : `I-PER` et `B-PER` par exemple. + +32 +00:02:23,290 --> 00:02:29,190 +Il nous permet de savoir si un token est dans la même entité que le précédent. A noter + +33 +00:02:29,190 --> 00:02:34,750 +qu'il existe deux manières d'étiqueter utilisées pour la classification des tokens, l'une (en rose ici) utilise l' + +34 +00:02:34,750 --> 00:02:40,380 +étiquette `B-PER` au début de chaque nouvelle entité, mais l'autre (en bleu) ne l'utilise que pour séparer + +35 +00:02:40,380 --> 00:02:43,380 +deux entités adjacentes du même type. + +36 +00:02:43,380 --> 00:02:48,880 +Dans les deux cas, nous pouvons marquer une nouvelle entité chaque fois que nous voyons apparaître une nouvelle étiquette + +37 +00:02:48,880 --> 00:02:54,051 +(avec le préfixe `I` ou `B`) puis prendre tous les tokens suivants étiquetés de la même manière, avec un + +38 +00:02:54,051 --> 00:02:55,051 +drapeau `I`. + +39 +00:02:55,051 --> 00:02:59,360 +Ceci, couplé à l'association de décalage pour obtenir les caractères de début et de fin, nous permet d' + +40 +00:02:59,360 --> 00:03:02,000 +obtenir l'étendue des textes pour chaque entité. \ No newline at end of file diff --git a/subtitles/fr/47_inside-the-question-answering-pipeline-(pytorch).srt b/subtitles/fr/47_inside-the-question-answering-pipeline-(pytorch).srt new file mode 100644 index 000000000..ed9b993b4 --- /dev/null +++ b/subtitles/fr/47_inside-the-question-answering-pipeline-(pytorch).srt @@ -0,0 +1,187 @@ +1 +00:00:04,130 --> 00:00:08,390 +Jetons un coup d'œil à l'intérieur du pipeline de de réponse aux questions. + +2 +00:00:08,390 --> 00:00:12,630 +Le pipeline de réponse aux questions peut extraire les réponses aux questions d'un contexte + +3 +00:00:12,630 --> 00:00:18,150 +ou d'un passage de texte donné, comme cette partie du README du dépôt Transformers. + +4 +00:00:18,150 --> 00:00:22,440 +Cela fonctionne aussi pour des contextes très longs, même si la réponse est à la toute fin, comme dans + +5 +00:00:22,440 --> 00:00:23,440 +cet exemple. + +6 +00:00:23,440 --> 00:00:24,680 +Dans cette vidéo, nous allons voir pourquoi ! + +7 +00:00:24,680 --> 00:00:31,540 +Le pipeline de réponse aux questions suit les mêmes étapes que les autres pipelines : la question + +8 +00:00:31,540 --> 00:00:36,380 +et le contexte sont tokenisés sous la forme d'une paire de phrases, transmis au modèle, puis un post-traitement + +9 +00:00:36,380 --> 00:00:37,649 +est appliqué. + +10 +00:00:37,649 --> 00:00:41,790 +Les étapes de tokenisation et de modèle doivent être familières. + +11 +00:00:41,790 --> 00:00:47,020 +Nous utilisons la classe automatique adaptée à la réponse aux questions au lieu de la classification de séquence, + +12 +00:00:47,020 --> 00:00:52,039 +mais une différence clé avec la classification de texte est que notre modèle génère deux tenseurs nommés + +13 +00:00:52,039 --> 00:00:54,559 +logits de début et logits de fin. + +14 +00:00:54,559 --> 00:00:55,559 +Pourquoi donc? + +15 +00:00:55,559 --> 00:00:59,850 +Eh bien, c'est ainsi que le modèle trouve la réponse à la question. + +16 +00:00:59,850 --> 00:01:02,270 +Examinons d'abord les entrées du modèle. + +17 +00:01:02,270 --> 00:01:07,160 +Il s'agit de chiffres associés à la tokenisation de la question suivis du contexte (avec + +18 +00:01:07,160 --> 00:01:10,710 +les tokens spéciaux habituels [CLS] et [SEP]). + +19 +00:01:10,710 --> 00:01:13,310 +La réponse fait partie de ces tokens. + +20 +00:01:13,310 --> 00:01:17,759 +Nous demandons donc au modèle de prédire quel token commence la réponse et lequel termine la réponse. + +21 +00:01:17,759 --> 00:01:24,380 +Pour nos deux sorties logit, les étiquettes théoriques sont les vecteurs rose et violet. + +22 +00:01:24,380 --> 00:01:28,360 +Pour convertir ces logits en probabilités, nous devrons appliquer une SoftMax, comme dans le + +23 +00:01:28,360 --> 00:01:30,439 +pipeline de classification de texte. + +24 +00:01:30,439 --> 00:01:35,070 +Nous masquons simplement les tokens qui ne font pas partie du contexte avant de le faire, laissant + +25 +00:01:35,070 --> 00:01:41,009 +le token [CLS] initial démasqué car nous l'utilisons pour prédire une réponse impossible. + +26 +00:01:41,009 --> 00:01:43,579 +Voici à quoi cela ressemble en termes de code. + +27 +00:01:43,579 --> 00:01:47,729 +On utilise un grand nombre négatif pour le masquage, puisque son exponentielle sera alors 0. + +28 +00:01:47,729 --> 00:01:53,610 +Maintenant la probabilité pour chaque position de début et de fin correspondant à une réponse possible, + +29 +00:01:53,610 --> 00:01:57,600 +on donne un score qui est le produit des probabilités de début et des probabilités de fin + +30 +00:01:57,600 --> 00:02:00,180 +à ces positions. + +31 +00:02:00,180 --> 00:02:05,430 +Bien entendu, un indice de début supérieur à un indice de fin correspond à une réponse impossible. + +32 +00:02:05,430 --> 00:02:08,940 +Voici le code pour trouver le meilleur score pour une réponse possible. + +33 +00:02:08,940 --> 00:02:13,070 +Une fois que nous avons les positions de début et de fin des tokens, nous utilisons l'association de décalage fournis + +34 +00:02:13,070 --> 00:02:18,270 +par notre tokenizer pour trouver la plage de caractères dans le contexte initial et obtenir notre réponse ! + +35 +00:02:18,270 --> 00:02:23,820 +Désormais, lorsque le contexte est long, il peut être tronqué par le tokenizer. + +36 +00:02:23,820 --> 00:02:29,099 +Cela pourrait entraîner une partie de la réponse, ou pire, la réponse entière, étant tronquée. + +37 +00:02:29,099 --> 00:02:33,319 +Nous ne supprimons donc pas les tokens tronqués, mais construisons de nouvelles caractéristiques avec eux. + +38 +00:02:33,319 --> 00:02:39,320 +Chacune de ces caractéristiques contient la question, puis un morceau de texte dans le contexte. + +39 +00:02:39,320 --> 00:02:43,760 +Si nous prenons des morceaux de textes disjoints, nous pourrions nous retrouver avec la réponse divisée entre + +40 +00:02:43,760 --> 00:02:45,330 +deux caractéristiques. + +41 +00:02:45,330 --> 00:02:49,709 +Donc, à la place, nous prenons des morceaux de textes qui se chevauchent, pour nous assurer qu'au moins un des morceaux + +42 +00:02:49,709 --> 00:02:51,650 +contiendra entièrement la réponse à la question. + +43 +00:02:51,650 --> 00:02:56,920 +Les tokenizers font tout cela pour nous automatiquement avec l'option `return_overflowing_tokens`. + +44 +00:02:56,920 --> 00:03:02,069 +L'argument `stride` contrôle le nombre de tokens qui se chevauchent. + +45 +00:03:02,069 --> 00:03:05,930 +Voici comment notre très long contexte est tronqué en deux caractéristiques avec un certain chevauchement. + +46 +00:03:05,930 --> 00:03:10,051 +En appliquant le même post-traitement que nous avons vu précédemment pour chaque caractéristique, nous obtenons la réponse + +47 +00:03:10,051 --> 00:03:20,349 +avec un score pour chacun d'eux, et nous prenons la réponse avec le meilleur score comme solution finale. \ No newline at end of file diff --git a/subtitles/fr/48_inside-the-question-answering-pipeline-(tensorflow).srt b/subtitles/fr/48_inside-the-question-answering-pipeline-(tensorflow).srt new file mode 100644 index 000000000..b9c1b4aec --- /dev/null +++ b/subtitles/fr/48_inside-the-question-answering-pipeline-(tensorflow).srt @@ -0,0 +1,143 @@ +1 +00:00:05,360 --> 00:00:07,920 +Jetons un coup d'œil à l'intérieur du pipeline de réponse aux questions. + +2 +00:00:09,600 --> 00:00:13,520 +Le pipeline de réponse aux questions peut extraire les réponses aux questions à + +3 +00:00:13,520 --> 00:00:17,840 +partir d'un contexte ou d'un passage de texte donné, comme cette partie du README du dépôt Transformers. + +4 +00:00:19,040 --> 00:00:23,680 +Cela fonctionne aussi pour les contextes très longs, même si la réponse se trouve à la toute fin, comme dans cet exemple. + +5 +00:00:24,480 --> 00:00:25,840 +Dans cette vidéo, nous allons voir pourquoi ! + +6 +00:00:27,600 --> 00:00:32,720 +Le pipeline de réponse aux questions suit les mêmes étapes que les autres pipelines : la question et le + +7 +00:00:32,720 --> 00:00:38,160 +contexte sont tokenisés sous la forme d'une paire de phrases, transmis au modèle, puis un post-traitement est appliqué. + +8 +00:00:39,280 --> 00:00:44,160 +Les étapes de tokenisation et de modèle doivent être familières. Nous utilisons la classe automatique adaptée à la + +9 +00:00:44,160 --> 00:00:48,240 +réponse aux questions au lieu de la classification de séquence, mais une différence clé + +10 +00:00:48,240 --> 00:00:53,680 +avec la classification de texte est que notre modèle génère deux tenseurs nommés logits de début et + +11 +00:00:53,680 --> 00:00:58,640 +logits de fin. Pourquoi donc? C'est ainsi que le modèle trouve la réponse à la question. + +12 +00:00:59,920 --> 00:01:04,880 +Examinons d'abord les entrées du modèle. Il s'agit de numéros associés à la tokenisation de la + +13 +00:01:04,880 --> 00:01:12,160 +question suivis du contexte (avec les tokens spéciaux [CLS] et [SEP] habituels). La réponse fait partie + +14 +00:01:12,160 --> 00:01:17,920 +de ces tokens. Nous demandons donc au modèle de prédire quel token commence la réponse et lequel termine la + +15 +00:01:17,920 --> 00:01:25,040 +réponse. Pour nos deux sorties logit, les libellés théoriques sont les vecteurs rose et violet. Pour convertir + +16 +00:01:25,040 --> 00:01:29,520 +ces logits en probabilités, nous devrons appliquer une SoftMax, comme dans le pipeline de classification de texte + +17 +00:01:29,520 --> 00:01:36,240 +Nous masquons simplement les tokens qui ne font pas partie du contexte avant de le faire, en laissant le + +18 +00:01:36,240 --> 00:01:43,200 +token [CLS] initial démasqué car nous l'utilisons pour prédire une réponse impossible. Voici à quoi cela ressemble en + +19 +00:01:43,200 --> 00:01:49,200 +termes de code. Nous utilisons un grand nombre négatif pour le masquage, puisque son exponentielle sera alors 0. + +20 +00:01:50,480 --> 00:01:54,640 +Maintenant la probabilité pour chaque position de début et de fin correspondant à une réponse possible, + +21 +00:01:55,520 --> 00:02:00,000 +nous donnons un score qui est le produit des probabilités de début et des probabilités de fin à ces + +22 +00:02:00,000 --> 00:02:06,000 +positions. Bien entendu, un indice de début supérieur à un indice de fin correspond à une réponse impossible. + +23 +00:02:07,360 --> 00:02:12,080 +Voici le code permettant de trouver le meilleur score pour une réponse possible. Une fois que nous avons les positions de début et de + +24 +00:02:12,080 --> 00:02:17,040 +fin des tokens, nous utilisons l'association de décalage fournis par notre générateur de tokens pour trouver la + +25 +00:02:17,040 --> 00:02:23,520 +plage de caractères dans le contexte initial et obtenir notre réponse ! Désormais, lorsque le contexte est long, + +26 +00:02:23,520 --> 00:02:29,440 +il peut être tronqué par le tokenizer. Cela peut entraîner la troncature d'une partie de la réponse, ou pire, de la + +27 +00:02:29,440 --> 00:02:34,800 +totalité de la réponse. Nous ne supprimons donc pas les tokens tronqués, mais créons de nouvelles caractéristiques + +28 +00:02:34,800 --> 00:02:42,080 +avec eux. Chacune de ces caractéristiques contient la question, puis un morceau de texte dans le contexte. Si + +29 +00:02:42,080 --> 00:02:47,280 +nous prenons des morceaux de textes disjoints, nous pourrions nous retrouver avec la réponse divisée entre deux caractéristiques. + +30 +00:02:48,560 --> 00:02:51,840 +Donc, à la place, nous prenons des morceaux de textes qui se chevauchent, + +31 +00:02:51,840 --> 00:02:55,520 +pour nous assurer qu'au moins l'un des morceaux contiendra entièrement la réponse à la question. + +32 +00:02:56,720 --> 00:03:00,880 +Les tokenizers font tout cela pour nous automatiquement avec l'option `return_overflowing_tokens`. + +33 +00:03:01,680 --> 00:03:04,320 +L'argument `stride` contrôle le nombre de tokens qui se chevauchent. + +34 +00:03:05,680 --> 00:03:10,160 +Voici comment notre très long contexte est tronqué en deux caractéristiques avec un certain chevauchement. + +35 +00:03:10,960 --> 00:03:15,520 +En appliquant le même post-traitement que nous avons vu avant pour chaque caractéristique, nous obtenons la réponse + +36 +00:03:15,520 --> 00:03:27,600 +avec un score pour chacune d'elles, et nous prenons la réponse avec le meilleur score comme solution finale. \ No newline at end of file diff --git a/subtitles/fr/49_what-is-normalization.srt b/subtitles/fr/49_what-is-normalization.srt new file mode 100644 index 000000000..58df8c7ca --- /dev/null +++ b/subtitles/fr/49_what-is-normalization.srt @@ -0,0 +1,211 @@ +1 +00:00:05,130 --> 00:00:11,060 +Dans cette vidéo nous allons voir ensemble quel est le composant normalizer que l'on retrouve au + +2 +00:00:11,060 --> 00:00:12,240 +début de chaque tokenizer. + +3 +00:00:12,240 --> 00:00:20,610 +L'opération de normalisation consiste à appliquer une succession de règles de normalisation au + +4 +00:00:20,610 --> 00:00:21,960 +texte brut. + +5 +00:00:21,960 --> 00:00:27,510 +Nous choisissons des règles de normalisation pour supprimer le bruit dans le texte qui semble inutile pour l'apprentissage + +6 +00:00:27,510 --> 00:00:31,420 +et l'utilisation de notre modèle de langage. + +7 +00:00:31,420 --> 00:00:40,790 +Prenons une phrase très diversifiée avec différentes polices, caractères majuscules et minuscules, accents, + +8 +00:00:40,790 --> 00:00:48,490 +ponctuation et espaces multiples, pour voir comment plusieurs tokenizers la normalisent. + +9 +00:00:48,490 --> 00:00:55,039 +Le tokenizer du modèle FNet a transformé les lettres avec des variantes de police ou encerclées + +10 +00:00:55,039 --> 00:01:00,230 +dans leur version de base et a supprimé les espaces multiples. + +11 +00:01:00,230 --> 00:01:07,090 +Et maintenant si nous regardons la normalisation avec le tokenizer de Retribert, nous pouvons voir qu'il + +12 +00:01:07,090 --> 00:01:12,990 +conserve les caractères avec plusieurs variantes de police et conserve les multiples espaces mais il supprime + +13 +00:01:12,990 --> 00:01:15,659 +tous les accents. + +14 +00:01:15,659 --> 00:01:23,050 +Et si nous continuons à tester la normalisation de nombreux autres tokenizers associés à des modèles + +15 +00:01:23,050 --> 00:01:34,079 +que vous pouvez trouver sur le Hub, nous pouvons voir qu'ils proposent également d'autres normalisations. + +16 +00:01:34,079 --> 00:01:39,310 +Avec les tokenizers rapides, il est très facile d'observer la normalisation choisie pour le + +17 +00:01:39,310 --> 00:01:42,500 +tokenizer actuellement chargé. + +18 +00:01:42,500 --> 00:01:49,250 +En effet, chaque instance d'un fast tokenizer a un tokenizer sous-jacent de la + +19 +00:01:49,250 --> 00:01:54,820 +bibliothèque Tokenizers stocké dans l'attribut backend_tokenizer. + +20 +00:01:54,820 --> 00:02:01,070 +Cet objet possède lui-même un attribut normalizer que nous pouvons utiliser grâce à la méthode `normalize_str` + +21 +00:02:01,070 --> 00:02:04,670 +pour normaliser une chaîne. + +22 +00:02:04,670 --> 00:02:11,000 +Il est donc très pratique que cette normalisation qui a été utilisée lors de l'apprentissage + +23 +00:02:11,000 --> 00:02:17,870 +du tokenizer ait été sauvegardée et qu'elle s'applique automatiquement lorsque vous demandez à un tokenizer entraîné + +24 +00:02:17,870 --> 00:02:21,120 +de tokeniser un texte. + +25 +00:02:21,120 --> 00:02:28,130 +Par exemple, si nous n'avions pas inclus le tokenizer albert, nous aurions eu beaucoup de + +26 +00:02:28,130 --> 00:02:35,870 +tokens inconnus en tokenisant cette phrase avec des accents et des majuscules. + +27 +00:02:35,870 --> 00:02:40,319 +Ces transformations peuvent aussi être indétectables avec un simple « print ». + +28 +00:02:40,319 --> 00:02:46,069 +En effet, gardez à l'esprit que pour un ordinateur, le texte n'est qu'une succession de 0 et de 1 et il + +29 +00:02:46,069 --> 00:02:51,230 +arrive que des successions différentes de 0 et de 1 rendent le même caractère. + +30 +00:02:51,230 --> 00:02:57,459 +Les 0 et les 1 vont par groupes de 8 pour entraîner un octet. + +31 +00:02:57,459 --> 00:03:04,490 +L'ordinateur doit alors décoder cette séquence d'octets en une séquence de « points de code ». + +32 +00:03:04,490 --> 00:03:10,959 +Dans notre exemple, les 2 octets sont transformés en un seul « point de code » par UTF-8. + +33 +00:03:10,959 --> 00:03:18,860 +La norme unicode permet alors de trouver le caractère correspondant à ce « point de code » : + +34 +00:03:18,860 --> 00:03:22,140 +la cédille c. + +35 +00:03:22,140 --> 00:03:28,060 +Répétons la même opération avec cette nouvelle séquence composée de 3 octets, cette fois + +36 +00:03:28,060 --> 00:03:34,450 +elle est transformée en 2 « point de code », ce qui correspondent aussi au caractère c cédille ! + +37 +00:03:34,450 --> 00:03:41,510 +Il s'agit en fait de la composition de la lettre minuscule latine C unicode et de la cédille combinatoire. + +38 +00:03:41,510 --> 00:03:47,819 +Mais c'est embêtant car ce qui nous apparaît comme un personnage unique n'est pas du tout + +39 +00:03:47,819 --> 00:03:52,379 +la même chose pour l'ordinateur. + +40 +00:03:52,379 --> 00:04:02,269 +Heureusement, il existe des normes de normalisation unicode connues sous le nom de NFC, NFD, NFKC et NFKD + +41 +00:04:02,269 --> 00:04:05,430 +qui permettent d'effacer certaines de ces différences. + +42 +00:04:05,430 --> 00:04:10,019 +Ces standards sont souvent utilisés par les tokenizers ! + +43 +00:04:10,019 --> 00:04:15,239 +Sur tous ces exemples précédents, même si les normalisations changeaient l'apparence du texte, + +44 +00:04:15,239 --> 00:04:21,229 +elles ne changeaient pas le contenu : on pouvait toujours lire « Hello world, normalize this + +45 +00:04:21,229 --> 00:04:22,540 +phrase ». + +46 +00:04:22,540 --> 00:04:30,120 +Cependant, il faut être conscient que certaines normalisations peuvent être très néfastes si elles ne sont pas adaptées + +47 +00:04:30,120 --> 00:04:31,720 +à leur corpus. + +48 +00:04:31,720 --> 00:04:37,360 +Par exemple, si vous prenez la phrase française « un père indigné » + +49 +00:04:37,360 --> 00:04:45,660 +et que vous la normalisez avec le tokenizer bert-base-uncase qui supprime les accents, la + +50 +00:04:45,660 --> 00:04:53,550 +phrase devient « un père indigne ». + +51 +00:04:53,550 --> 00:04:58,699 +Si vous regardez cette vidéo pour construire votre propre tokenizer, il n'y a pas de règles absolues pour + +52 +00:04:58,699 --> 00:05:04,580 +choisir ou non une normalisation pour votre tout nouveau tokenizer mais je vous conseille de prendre le + +53 +00:05:04,580 --> 00:05:15,960 +temps de les sélectionner afin qu'ils ne vous fassent pas perdre des informations importantes. \ No newline at end of file diff --git a/subtitles/fr/50_what-is-pre-tokenization.srt b/subtitles/fr/50_what-is-pre-tokenization.srt new file mode 100644 index 000000000..d8d3dab89 --- /dev/null +++ b/subtitles/fr/50_what-is-pre-tokenization.srt @@ -0,0 +1,99 @@ +1 +00:00:05,549 --> 00:00:12,309 +Le pipeline de tokenisation implique plusieurs étapes qui convertissent le texte brut en nombres. + +2 +00:00:12,309 --> 00:00:15,990 +Dans cette vidéo, nous verrons ce qui se passe lors de l'étape de pré-tokénisation. + +3 +00:00:15,990 --> 00:00:23,840 +L'opération de pré-tokénisation est l'opération effectuée après la normalisation du texte + +4 +00:00:23,840 --> 00:00:28,830 +et avant l'application de l'algorithme de tokénisation. + +5 +00:00:28,830 --> 00:00:33,489 +Cette étape consiste à appliquer des règles qui n'ont pas besoin d'être apprises pour effectuer une première + +6 +00:00:33,489 --> 00:00:38,270 +division du texte. + +7 +00:00:38,270 --> 00:00:46,270 +Regardons comment plusieurs tokenizers prétokenise cet exemple. + +8 +00:00:46,270 --> 00:00:53,430 +La prétokénisation du GPT-2 divise le texte sur des espaces et quelques signes de ponctuation + +9 +00:00:53,430 --> 00:00:57,840 +mais pas sur l'apostrophe. + +10 +00:00:57,840 --> 00:01:06,580 +On remarque également que les espaces ont été remplacés par un G majuscule avec un point au-dessus. + +11 +00:01:06,580 --> 00:01:12,900 +La prétokénisation d'Albert divise le texte au niveau des espaces, ajoute un espace au + +12 +00:01:12,900 --> 00:01:19,610 +début de la phrase et remplace les espaces par un trait de soulignement spécial. + +13 +00:01:19,610 --> 00:01:29,320 +Enfin, la prétokénisation de BERT divise le texte au niveau de la ponctuation et des espaces. + +14 +00:01:29,320 --> 00:01:35,460 +Mais contrairement aux tokenizers précédents, les espaces ne sont pas transformés et intégrés aux tokens + +15 +00:01:35,460 --> 00:01:40,079 +produits avec ce pretokenizer. + +16 +00:01:40,079 --> 00:01:45,860 +A travers ces 3 exemples, nous avons pu observer les deux principaux types d'opérations apportées par + +17 +00:01:45,860 --> 00:01:54,210 +la prétokénisation : quelques changements sur le texte et la division de la chaîne en + +18 +00:01:54,210 --> 00:01:57,259 +tokens pouvant être associés à des mots. + +19 +00:01:57,259 --> 00:02:06,729 +Enfin, le `backend_tokenizer` des tokenizers rapides permet également de tester + +20 +00:02:06,729 --> 00:02:12,739 +très facilement l'opération de pr-tokenisation grâce à sa méthode `pre_tokenize_str`. + +21 +00:02:12,739 --> 00:02:18,740 +On remarque que la sortie de cette opération est composée à la fois de tokens et d'offsets qui + +22 +00:02:18,740 --> 00:02:24,830 +permettent de lier le token à sa position dans le texte donné en entrée de la méthode. + +23 +00:02:24,830 --> 00:02:32,269 +Cette opération définit les plus gros tokens pouvant être produits par la tokenisation + +24 +00:02:32,269 --> 00:02:40,000 +ou autrement dit les barrières des sous-tokens qui seront alors produits. + +25 +00:02:40,000 --> 00:02:42,269 +Et c'est tou pour les caractéristiques clés des prétokenisers. \ No newline at end of file diff --git a/subtitles/fr/51_byte-pair-encoding-tokenization.srt b/subtitles/fr/51_byte-pair-encoding-tokenization.srt new file mode 100644 index 000000000..b94731daf --- /dev/null +++ b/subtitles/fr/51_byte-pair-encoding-tokenization.srt @@ -0,0 +1,167 @@ +1 +00:00:05,120 --> 00:00:07,440 +Vous êtes au bon endroit si vous voulez + +2 +00:00:07,440 --> 00:00:15,360 +comprendre ce qu'est l'algorithme de tokenisation en sous-mots « Byte Pair Encoding », comment l'entraîner + +3 +00:00:15,360 --> 00:00:18,640 +et comment la tokenisation d'un texte est effectuée avec cet algorithme. + +4 +00:00:21,600 --> 00:00:25,920 +L'algorithme BPE a été initialement proposé en tant qu'algorithme de compression de texte + +5 +00:00:26,640 --> 00:00:30,800 +mais il est également très bien adapté en tant que tokenizer pour vos modèles de langage. + +6 +00:00:32,560 --> 00:00:38,720 +L'idée de BPE est de diviser les mots en une séquence d'unités de sous-mots qui sont des unités + +7 +00:00:38,720 --> 00:00:44,400 +qui apparaissent fréquemment dans un corpus de référence, qui est le corpus que nous avons utilisé pour l'entraîner. + +8 +00:00:46,560 --> 00:00:53,680 +Comment un tokenizer BPE est-il entrapiné ? Tout d'abord, nous devons obtenir un corpus de textes. Nous n'entraînerons pas + +9 +00:00:54,480 --> 00:01:02,080 +notre tokenizer sur ce texte brut, mais nous le normaliserons d'abord, puis le prétokeniserons. Comme la + +10 +00:01:02,080 --> 00:01:07,520 +prétokénisation divise le texte en une liste de mots, nous pouvons représenter notre corpus d'une autre + +11 +00:01:07,520 --> 00:01:14,000 +manière en rassemblant les mêmes mots et en maintenant un compteur, ici représenté en bleu. + +12 +00:01:17,120 --> 00:01:22,960 +Pour comprendre le fonctionnement de l'entraînement, nous considérons ce corpus jouet composé des mots suivants : + +13 +00:01:23,520 --> 00:01:32,480 +huggingface, hugging, hug, hugger, etc. BPE est un algorithme qui commence par un vocabulaire initial + +14 +00:01:32,480 --> 00:01:35,200 +puis l'augmente jusqu'à la taille souhaitée. + +15 +00:01:36,240 --> 00:01:41,360 +Pour construire le vocabulaire initial, on commence par séparer chaque mot du corpus + +16 +00:01:41,360 --> 00:01:46,640 +en une liste d'unités élémentaires qui les composent, ici les caractères. + +17 +00:01:50,800 --> 00:01:50,900 + + +18 +00:01:51,360 --> 00:01:57,760 +Nous listons dans notre vocabulaire tous les caractères qui apparaissent et qui constitueront notre vocabulaire initial ! + +19 +00:02:00,240 --> 00:02:09,840 +Voyons maintenant comment l'augmenter. Nous revenons à notre corpus divisé, nous allons parcourir les mots + +20 +00:02:09,840 --> 00:02:18,480 +un par un et compter toutes les occurrences de paires de tokens. La première paire est composée du token « h » + +21 +00:02:18,480 --> 00:02:26,080 +et « u », la seconde « u » et « g », et nous continuons ainsi jusqu'à ce que nous ayons la liste complète. + +22 +00:02:35,440 --> 00:02:41,200 +Une fois que nous connaissons tous les couples et leur fréquence d'apparition, nous choisirons celui qui + +23 +00:02:41,200 --> 00:02:49,840 +apparaît le plus fréquemment : ici, il s'agit du couple composé des lettres « l » et « e ». + +24 +00:02:51,680 --> 00:02:57,040 +Nous notons notre première règle de fusion et nous ajoutons le nouveau token à notre vocabulaire. + +25 +00:03:00,080 --> 00:03:04,080 +Nous pouvons ensuite appliquer cette règle de fusion à nos divisions : + +26 +00:03:04,080 --> 00:03:09,280 +vous pouvez voir que nous avons fusionné toutes les paires de tokens composées des tokens « l » et « e ». + +27 +00:03:13,840 --> 00:03:19,040 +Et maintenant il ne nous reste plus qu'à reproduire les mêmes étapes avec nos nouvelles divisions : + +28 +00:03:21,520 --> 00:03:24,640 +on calcule la fréquence d'occurrence de chaque paire de tokens, + +29 +00:03:27,760 --> 00:03:33,680 +on sélectionne la paire avec la fréquence la plus élevée, on la note dans nos règles de fusion, + +30 +00:03:35,760 --> 00:03:38,720 +on ajoute la nouvelle à le vocabulaire, + +31 +00:03:39,600 --> 00:03:46,160 +puis nous fusionnons toutes les paires de tokens composées du token « le » et « a » dans nos divisions. + +32 +00:03:50,160 --> 00:03:59,840 +Et nous pouvons répéter cette opération jusqu'à atteindre la taille de vocabulaire souhaitée. + +33 +00:04:05,600 --> 00:04:13,200 +Ici, nous nous sommes arrêtés lorsque notre vocabulaire a atteint 21 tokens. Nous pouvons voir maintenant que les mots de notre + +34 +00:04:13,200 --> 00:04:20,560 +corpus sont maintenant divisés en beaucoup moins de tokens qu'au début de la entraînement. Nous pouvons voir que + +35 +00:04:20,560 --> 00:04:27,840 +notre algorithme a appris les radicaux « hug » et « learn » ainsi que la terminaison verbale « ing ». + +36 +00:04:29,760 --> 00:04:35,600 +Maintenant que nous avons appris notre vocabulaire et nos règles de fusion, nous pouvons tokeniser de nouveaux textes. + +37 +00:04:37,840 --> 00:04:41,120 +Par exemple, si nous voulons symboliser le mot + +38 +00:04:41,120 --> 00:04:48,480 +« hugs » : nous allons d'abord le diviser en unités élémentaires afin qu'il devienne une séquence de caractères. + +39 +00:04:49,840 --> 00:04:53,680 +Ensuite, nous passerons en revue nos règles de fusion jusqu'à ce que nous en ayons une que nous puissions appliquer. + +40 +00:04:54,480 --> 00:05:01,040 +Ici, nous pouvons fusionner les lettres « h » et « u ». Et ici, nous pouvons fusionner 2 tokens pour obtenir le nouveau token « hug ». + +41 +00:05:02,240 --> 00:05:09,840 +Lorsque nous arrivons à la fin de notre règle de fusion, la tokenisation est terminée. + +42 +00:05:10,640 --> 00:05:22,400 +Et ça y est, j'espère que maintenant l'algorithme BPE n'a plus de secret pour vous ! \ No newline at end of file diff --git a/subtitles/fr/52_wordpiece-tokenization.srt b/subtitles/fr/52_wordpiece-tokenization.srt new file mode 100644 index 000000000..1787bb155 --- /dev/null +++ b/subtitles/fr/52_wordpiece-tokenization.srt @@ -0,0 +1,123 @@ +1 +00:00:05,520 --> 00:00:10,000 +Voyons ensemble quelle est la stratégie d'entraînement de l'algorithme WordPiece + +2 +00:00:10,560 --> 00:00:15,920 +et comment il effectue la tokénisation d'un texte une fois entraîné + +3 +00:00:19,200 --> 00:00:25,280 +WordPiece est un algorithme de tokénisation introduit par Google. Il est utilisé par exemple par BERT. + +4 +00:00:26,480 --> 00:00:30,640 +À notre connaissance, le code de WordPiece n'a pas été mis en open source. + +5 +00:00:31,360 --> 00:00:36,640 +Nous basons donc nos explications sur notre propre interprétation de la littérature publiée. + +6 +00:00:42,480 --> 00:00:48,480 +Quelle est la stratégie d'entraînement de WordPiece ? Comme pour l'algorithme BPE, + +7 +00:00:48,480 --> 00:00:54,480 +WordPiece commence par établir un vocabulaire initial composé d'unités élémentaires, + +8 +00:00:54,480 --> 00:01:01,760 +puis augmente ce vocabulaire jusqu'à la taille souhaitée. Pour construire le vocabulaire initial, + +9 +00:01:01,760 --> 00:01:07,120 +nous divisons chaque mot du corpus d'apprentissage en la séquence de lettres qui le compose. + +10 +00:01:08,240 --> 00:01:14,000 +Comme vous pouvez le voir, il y a une petite subtilité : nous ajoutons deux ## devant les lettres + +11 +00:01:14,000 --> 00:01:20,240 +qui ne commencent pas un mot. En ne gardant qu'une seule occurrence par unité élémentaire + +12 +00:01:20,240 --> 00:01:29,440 +nous avons maintenant notre vocabulaire initial. Nous allons lister toutes les paires existantes dans notre corpus. + +13 +00:01:30,800 --> 00:01:34,960 +Une fois que nous aurons cette liste, nous calculerons un score pour chacune de ces paires. + +14 +00:01:36,400 --> 00:01:40,400 +Comme pour l'algorithme BPE, nous sélectionnerons la paire avec le score le plus élevé. + +15 +00:01:43,040 --> 00:01:50,000 +Prenons par exemple la première paire composée de « h » et « ##u ». Le score d'une paire est simplement + +16 +00:01:50,000 --> 00:01:54,720 +égal à la fréquence d'apparition de la paire divisée par le produit de la + +17 +00:01:54,720 --> 00:01:59,840 +fréquence d'apparition du premier token par la fréquence d'apparition du deuxième token. + +18 +00:02:01,120 --> 00:02:04,560 +Ainsi à fréquence fixe d'apparition du couple, + +19 +00:02:05,360 --> 00:02:11,440 +si les sous-parties du couple sont très fréquentes dans le corpus alors ce score sera diminué. + +20 +00:02:12,960 --> 00:02:24,000 +Dans notre exemple, la paire « hu » apparaît 4 fois, la lettre « h » 4 fois et la lettre « u » 4 fois. Cela + +21 +00:02:24,000 --> 00:02:32,320 +nous donne un score de 0,25. Maintenant que nous savons comment calculer ce score, nous pouvons le faire pour toutes les paires. + +22 +00:02:33,200 --> 00:02:36,480 +On peut maintenant ajouter au vocabulaire la paire avec le score le plus élevé, + +23 +00:02:37,120 --> 00:02:43,520 +après l'avoir fusionnée bien sûr ! Et maintenant, nous pouvons appliquer cette même fusion à notre corpus divisé. + +24 +00:02:45,600 --> 00:02:51,520 +Comme vous pouvez l'imaginer, nous n'avons qu'à répéter les mêmes opérations jusqu'à ce que nous ayons le vocabulaire à + +25 +00:02:51,520 --> 00:03:00,320 +la taille souhaitée ! Examinons quelques étapes supplémentaires pour voir l'évolution de notre vocabulaire et la + +26 +00:03:00,320 --> 00:03:09,840 +longueur des fractionnements qui se raccourcit. Maintenant que nous sommes satisfaits de notre vocabulaire, vous vous + +27 +00:03:09,840 --> 00:03:16,400 +demandez probablement comment l'utiliser pour marquer un texte. Supposons que nous souhaitions tokeniser le mot « huggingface ». + +28 +00:03:17,760 --> 00:03:23,280 +WordPiece suit ces règles : nous chercherons le token le plus long possible au début de + +29 +00:03:23,280 --> 00:03:30,560 +notre mot. Ensuite, nous recommençons sur la partie restante de notre mot. Et ainsi de suite jusqu'à la + +30 +00:03:30,560 --> 00:03:38,240 +fin ! Et c'est tout, « huggingface » est divisé en 4 sous-tokens. Cette vidéo est sur le point de se + +31 +00:03:38,240 --> 00:03:43,040 +terminer, j'espère qu'elle vous a aidé à mieux comprendre ce qui se cache derrière le mot WordPiece ! \ No newline at end of file diff --git a/subtitles/fr/53_unigram-tokenization.srt b/subtitles/fr/53_unigram-tokenization.srt new file mode 100644 index 000000000..342e5cdae --- /dev/null +++ b/subtitles/fr/53_unigram-tokenization.srt @@ -0,0 +1,379 @@ +1 +00:00:05,330 --> 00:00:11,090 +Dans cette vidéo, nous étudierons ensemble l'algorithme de tokenisation en sous-mots du modèle de langage + +2 +00:00:11,090 --> 00:00:12,090 +Unigram. + +3 +00:00:12,090 --> 00:00:20,080 +La stratégie globale d'entraînement d'un tokenizer Unigram LM est de commencer avec un vocabulaire très large, + +4 +00:00:20,080 --> 00:00:27,439 +puis de supprimer des tokens à chaque itération jusqu'à ce que nous atteignions la taille souhaitée. + +5 +00:00:27,439 --> 00:00:32,250 +A chaque itération, nous calculerons une perte sur notre corpus d'apprentissage grâce au modèle Unigram + +6 +00:00:32,250 --> 00:00:33,250 +. + +7 +00:00:33,250 --> 00:00:39,160 +Comme le calcul de la perte dépend du vocabulaire disponible, nous pouvons l'utiliser pour choisir comment + +8 +00:00:39,160 --> 00:00:41,590 +réduire le vocabulaire. + +9 +00:00:41,590 --> 00:00:48,090 +On regarde donc l'évolution de la perte en supprimant tour à tour chaque token du vocabulaire. + +10 +00:00:48,090 --> 00:00:56,730 +On choisira de supprimer les p pourcents qui augmentent le moins la perte. + +11 +00:00:56,730 --> 00:01:01,030 +Avant d'aller plus loin dans l'explication de l'algorithme d'entraînement, je dois expliquer + +12 +00:01:01,030 --> 00:01:04,199 +ce qu'est un modèle Unigram. + +13 +00:01:04,199 --> 00:01:08,119 +Le modèle Unigram LM est un type de modèle linguistique statistique. + +14 +00:01:08,119 --> 00:01:15,550 +Un LM statistique attribuera une probabilité à un texte en considérant que le texte est en + +15 +00:01:15,550 --> 00:01:18,189 +fait une séquence de tokens. + +16 +00:01:18,189 --> 00:01:23,900 +Les séquences de tokens les plus simples à imaginer sont les mots qui composent la phrase ou + +17 +00:01:23,900 --> 00:01:25,410 +les caractères. + +18 +00:01:25,410 --> 00:01:32,080 +La particularité de l'Unigram LM est qu'il suppose que l'occurrence de chaque mot est + +19 +00:01:32,080 --> 00:01:34,670 +indépendante de son mot précédent. + +20 +00:01:34,670 --> 00:01:40,271 +Cette hypothèse nous permet d'écrire que la probabilité d'un texte est égale au + +21 +00:01:40,271 --> 00:01:44,430 +produit des probabilités des tokens qui le composent. + +22 +00:01:44,430 --> 00:01:51,880 +Il faut noter ici qu'il s'agit d'un modèle très simple qui ne serait pas adapté à + +23 +00:01:51,880 --> 00:01:58,630 +la génération de texte puisque ce modèle générerait toujours le même token, celui qui + +24 +00:01:58,630 --> 00:02:00,140 +a la plus grande probabilité. + +25 +00:02:00,140 --> 00:02:07,409 +Néanmoins, pour faire de la tokenisation, ce modèle nous est très utile car il peut être utilisé + +26 +00:02:07,409 --> 00:02:14,209 +pour estimer la vraissemblance relative de différentes phrases. + +27 +00:02:14,209 --> 00:02:20,000 +Nous sommes maintenant prêts à revenir à notre explication de l'entraînement de l'algorithme. + +28 +00:02:20,000 --> 00:02:25,349 +Disons que nous avons comme corpus d'entraînement 10 fois le mot « hug », 12 fois le mot « pug », + +29 +00:02:25,349 --> 00:02:33,270 +5 fois le mot « lug », 4 fois « bug » et 5 fois « dug ». + +30 +00:02:33,270 --> 00:02:38,910 +Comme dit plus tôt, l'entraînement commence par un gros vocabulaire. + +31 +00:02:38,910 --> 00:02:45,280 +Évidemment, comme nous utilisons un corpus jouet, ce vocabulaire ne sera pas si gros mais il devrait + +32 +00:02:45,280 --> 00:02:46,840 +vous montrer le principe. + +33 +00:02:46,840 --> 00:02:54,870 +Une première méthode consiste à lister toutes les sous-chaînes strictes possibles, c'est ce que nous allons faire ici. + +34 +00:02:54,870 --> 00:03:00,379 +On aurait aussi pu utiliser l'algorithme BPE avec une très grande taille de vocabulaire. + +35 +00:03:00,379 --> 00:03:07,200 +Mais maintenant les sous-chaînes strictes suffisent. + +36 +00:03:07,200 --> 00:03:13,629 +L'entraînement du tokenizer Unigram est basé sur la méthode Expectation-Maximisation : à + +37 +00:03:13,629 --> 00:03:15,210 +chaque itération, + +38 +00:03:15,210 --> 00:03:19,190 +nous estimons les probabilités des tokens du vocabulaire. + +39 +00:03:19,190 --> 00:03:26,430 +Puis on enlève les p pourcents de tokens qui minimisent la perte sur le corpus et qui + +40 +00:03:26,430 --> 00:03:33,500 +n'appartiennent pas aux caractères de base car on veut garder dans notre vocabulaire final les + +41 +00:03:33,500 --> 00:03:37,980 +caractères de base pour pouvoir tokeniser n'importe quel mot. + +42 +00:03:37,980 --> 00:03:39,230 +Allons y pour ça ! + +43 +00:03:39,230 --> 00:03:44,660 +La probabilité d'un token est simplement estimée par le nombre d'apparition de ce token + +44 +00:03:44,660 --> 00:03:51,590 +dans notre corpus d'apprentissage divisé par le nombre total d'apparition de tous les tokens. + +45 +00:03:51,590 --> 00:03:57,239 +Nous pourrions utiliser ce vocabulaire pour tokeniser nos mots selon le modèle Unigram. + +46 +00:03:57,239 --> 00:04:04,080 +Nous allons le faire ensemble pour comprendre deux choses : comment on tokenise un mot avec un modèle Unigram + +47 +00:04:04,080 --> 00:04:09,160 +et comment la perte est calculée sur notre corpus. + +48 +00:04:09,160 --> 00:04:14,610 +La tokenisation Unigram LM de notre texte « hug » sera celle qui aura la probabilité d'occurrence la plus élevée + +49 +00:04:14,610 --> 00:04:19,140 +selon notre modèle Unigram. + +50 +00:04:19,140 --> 00:04:24,090 +Pour le trouver, la manière la plus simple de procéder serait de lister toutes les segmentations possibles + +51 +00:04:24,090 --> 00:04:29,949 +de notre texte « hug », de calculer la probabilité de chacune de ces segmentations puis de + +52 +00:04:29,949 --> 00:04:32,490 +choisir celle qui a la probabilité la plus élevée. + +53 +00:04:32,490 --> 00:04:38,630 +Avec le vocabulaire actuel, 2 tokenisations obtiennent exactement la même probabilité. + +54 +00:04:38,630 --> 00:04:43,789 +Nous en choisissons donc une et gardons en mémoire la probabilité associée. + +55 +00:04:43,789 --> 00:04:48,850 +Pour calculer la perte sur notre corpus d'entraînement, nous devons tokeniser comme nous venons de le faire tous les + +56 +00:04:48,850 --> 00:04:52,810 +mots restants dans le corpus. + +57 +00:04:52,810 --> 00:04:57,930 +La perte est alors la somme sur tous les mots du corpus de la fréquence d'occurrence + +58 +00:04:57,930 --> 00:05:04,220 +du mot multipliée par l'opposé du logarithme de la probabilité associée à + +59 +00:05:04,220 --> 00:05:07,720 +la tokenisation du mot. + +60 +00:05:07,720 --> 00:05:12,700 +On obtient ici une perte de 170. + +61 +00:05:12,700 --> 00:05:18,750 +Rappelez-vous, notre objectif initial était de réduire le vocabulaire. + +62 +00:05:18,750 --> 00:05:27,810 +Pour cela, nous allons retirer un token du vocabulaire et calculer la perte associée. + +63 +00:05:27,810 --> 00:05:32,020 +Supprimons par exemple le token « ug ». + +64 +00:05:32,020 --> 00:05:38,569 +On remarque que la tokenisation pour « hug » avec la lettre « h » et le tuple « ug » est désormais + +65 +00:05:38,569 --> 00:05:39,970 +impossible. + +66 +00:05:39,970 --> 00:05:45,810 +Néanmoins, comme nous l'avons vu précédemment, deux tokenisations avaient la même probabilité et nous pouvons toujours + +67 +00:05:45,810 --> 00:05:50,870 +choisir la tokenisation restante avec une probabilité 1e-02. + +68 +00:05:50,870 --> 00:05:58,210 +Les tokenisations des autres mots du vocabulaire restent également inchangées et finalement + +69 +00:05:58,210 --> 00:06:06,710 +même si on enlève le token « ug » de notre vocabulaire la perte reste égale à 170. + +70 +00:06:06,710 --> 00:06:11,550 +Pour cette première itération, si on continue le calcul, on s'apercevrait qu'on pourrait + +71 +00:06:11,550 --> 00:06:16,190 +retirez n'importe quel token sans que cela n'affecte la perte. + +72 +00:06:16,190 --> 00:06:24,620 +On choisira donc au hasard de retirer le token « ug » avant d'entamer une deuxième itération. + +73 +00:06:24,620 --> 00:06:29,600 +Nous estimons à nouveau la probabilité de chaque token avant de calculer l'impact de chaque + +74 +00:06:29,600 --> 00:06:32,280 +token sur la perte. + +75 +00:06:32,280 --> 00:06:37,840 +Par exemple, si nous supprimons maintenant le token composé des lettres « h » et « u », il ne reste plus qu'une + +76 +00:06:37,840 --> 00:06:42,020 +seule tokenisation possible pour « hug ». + +77 +00:06:42,020 --> 00:06:46,580 +La tokenisation des autres mots du vocabulaire n'est pas modifiée. + +78 +00:06:46,580 --> 00:06:51,880 +Au final, on obtient en supprimant du vocabulaire le token composé des lettres « h » et « u » + +79 +00:06:51,880 --> 00:06:54,650 +une perte de 168. + +80 +00:06:54,650 --> 00:07:02,550 +Enfin, pour choisir quel token supprimer, on va pour chaque token restant du vocabulaire + +81 +00:07:02,550 --> 00:07:10,090 +qui n'est pas un token élémentaire calculer la perte associée puis comparer ces pertes + +82 +00:07:10,090 --> 00:07:11,850 +entre elles. + +83 +00:07:11,850 --> 00:07:18,100 +Le token que nous allons supprimer est le token qui impacte le moins la perte : ici le + +84 +00:07:18,100 --> 00:07:20,129 +token « bu ». + +85 +00:07:20,129 --> 00:07:25,710 +Nous avions mentionné au début de la vidéo qu'à chaque itération nous pouvions retirer p pourcent + +86 +00:07:25,710 --> 00:07:29,540 +des tokens par itération. + +87 +00:07:29,540 --> 00:07:35,850 +Le deuxième token qui pourrait être supprimé à cette itération est le token « du ». + +88 +00:07:35,850 --> 00:07:42,690 +Et voilà, il ne nous reste plus qu'à répéter ces étapes jusqu'à obtenir le vocabulaire de la + +89 +00:07:42,690 --> 00:07:45,240 +taille souhaitée. + +90 +00:07:45,240 --> 00:07:51,129 +Une dernière chose. En pratique, quand on tokenise un mot avec un modèle Unigram on ne calcule pas + +91 +00:07:51,129 --> 00:07:57,210 +l'ensemble des probabilités des découpages possibles d'un mot avant de les comparer pour garder le + +92 +00:07:57,210 --> 00:08:05,560 +meilleur. Mais on utilise l'algorithme de Viterbi qui est beaucoup plus efficace. + +93 +00:08:05,560 --> 00:08:07,300 +Et c'est tout! + +94 +00:08:07,300 --> 00:08:15,000 +J'espère que cet exemple vous a permis de mieux comprendre l' + +95 +00:08:15,000 --> 00:08:18,190 +algorithme de tokenisation Unigram. \ No newline at end of file diff --git a/subtitles/fr/54_building-a-new-tokenizer.srt b/subtitles/fr/54_building-a-new-tokenizer.srt new file mode 100644 index 000000000..7a42b808d --- /dev/null +++ b/subtitles/fr/54_building-a-new-tokenizer.srt @@ -0,0 +1,207 @@ +1 +00:00:05,350 --> 00:00:11,360 +Dans cette vidéo, nous verrons comment vous pouvez créer votre propre tokenizer à partir de zéro ! + +2 +00:00:11,360 --> 00:00:18,370 +Pour créer votre propre tokenizer vous devrez réfléchir à chacune des opérations impliquées + +3 +00:00:18,370 --> 00:00:25,220 +dans la tokenisation, à savoir : la normalisation, la prétokénisation, le modèle, le post-traitement et le décodage. + +4 +00:00:25,220 --> 00:00:32,310 +Si vous ne savez pas ce que sont la normalisation, la prétokénisation et le modèle, je vous conseille d'aller + +5 +00:00:32,310 --> 00:00:34,800 +voir les vidéos en lien ci-dessous. + +6 +00:00:34,800 --> 00:00:40,329 +Le post-traitement regroupe toutes les modifications que nous allons effectuer sur le texte tokenisé. + +7 +00:00:40,329 --> 00:00:46,690 +Cela peut inclure l'ajout de tokens spéciaux, la création d'un masque d'attention mais aussi + +8 +00:00:46,690 --> 00:00:50,200 +la génération d'une liste d'identifiants de tokens. + +9 +00:00:50,200 --> 00:00:55,350 +L'opération de décodage se produit à la toute fin et permettra de passer de la séquence + +10 +00:00:55,350 --> 00:00:59,000 +des identifiants dans une phrase. + +11 +00:00:59,000 --> 00:01:04,220 +Par exemple, vous pouvez voir que les # ont été supprimés et les tokens + +12 +00:01:04,220 --> 00:01:10,820 +composant le mot « .today » ont été regroupés. + +13 +00:01:10,820 --> 00:01:17,472 +Dans un tokeniseur rapide, tous ces composants sont regroupés dans l'attribut `backend_tokenizer`. + +14 +00:01:17,472 --> 00:01:22,720 +Comme vous pouvez le voir avec ce petit extrait de code, il s'agit d'une instance d'un tokenizer de la + +15 +00:01:22,720 --> 00:01:24,860 +bibliothèque Tokenizers. + +16 +00:01:24,860 --> 00:01:33,799 +Ainsi, pour créer votre propre tokenizer, vous devrez suivre ces étapes. + +17 +00:01:33,799 --> 00:01:40,510 +Créez d'abord un jeu de données d'entraînement. Deuxièmement, créez et entraînez un tokenizer avec la bibliothèque Tokenizers + +18 +00:01:40,510 --> 00:01:49,430 +et troisièmement chargez ce tokenizer dans le tokenizer de la bibliothèque Transformers. + +19 +00:01:49,430 --> 00:01:56,510 +Pour comprendre ces étapes, je propose de recréer ensemble un tokenizer BERT. + +20 +00:01:56,510 --> 00:01:59,500 +La première chose à faire est de créer un jeu de données. + +21 +00:01:59,500 --> 00:02:05,650 +Avec cet extrait de code, vous pouvez créer un itérateur sur le jeu de données wikitext-2-raw-v1 qui est + +22 +00:02:05,650 --> 00:02:08,610 +un jeu de données plutôt petit en anglais, parfait pour un exemple. + +23 +00:02:08,610 --> 00:02:18,830 +On attaque ici le gros morceau : la conception de notre tokenizer avec la bibliothèque Tokenizers. + +24 +00:02:18,830 --> 00:02:25,349 +Nous commençons par initialiser une instance de tokenizer avec un modèle WordPiece car c'est le modèle + +25 +00:02:25,349 --> 00:02:29,240 +utilisé par BERT. + +26 +00:02:29,240 --> 00:02:32,110 +Ensuite, nous pouvons définir notre normaliseur. + +27 +00:02:32,110 --> 00:02:39,930 +Nous le définirons comme une succession de deux normalisations servant à nettoyer les caractères non visibles dans + +28 +00:02:39,930 --> 00:02:46,659 +le texte, 1 normalisation en minuscule et 2 normalisations servant à supprimer les accents. + +29 +00:02:46,659 --> 00:02:54,459 +Pour la pré-tokenisation, nous allons chaîner deux prétokenizer. + +30 +00:02:54,459 --> 00:02:59,959 +Le premier séparant le texte au niveau des espaces et le second isolant les + +31 +00:02:59,959 --> 00:03:02,450 +signes de ponctuation. + +32 +00:03:02,450 --> 00:03:08,430 +Maintenant, nous pouvons définir le `trainer` qui nous permettra d'entraîner le modèle WordPiece choisi + +33 +00:03:08,430 --> 00:03:11,209 +au départ. + +34 +00:03:11,209 --> 00:03:17,280 +Pour réaliser l'entraînement, il va falloir choisir une taille de vocabulaire. Ici on en choisit 25 000. + +35 +00:03:17,280 --> 00:03:29,099 +Et on annonce aussi les tokens spéciaux qu'on veut absolument ajouter à notre vocabulaire. + +36 +00:03:29,099 --> 00:03:39,209 +En une ligne de code, nous pouvons entraîner notre modèle WordPiece en utilisant l'itérateur que nous avons défini précédemment. + +37 +00:03:39,209 --> 00:03:45,800 +Une fois le modèle entraîné, nous pouvons récupérer les identifiants des tokens de classe spéciale et de séparation + +38 +00:03:45,800 --> 00:03:49,750 +car nous en aurons besoin pour post-traiter notre séquence. + +39 +00:03:49,750 --> 00:03:55,790 +Grâce à la classe `TemplateProcessing`, nous pouvons ajouter le token [CLS] au début de + +40 +00:03:55,790 --> 00:04:01,780 +chaque séquence et le token [SEP] à la fin de la séquence et entre deux phrases + +41 +00:04:01,780 --> 00:04:07,060 +si nous tokenisons une paire de texte. + +42 +00:04:07,060 --> 00:04:12,099 +Enfin, il ne nous reste plus qu'à définir notre décodeur qui nous permettra de supprimer les ## + +43 +00:04:12,099 --> 00:04:17,810 +au début des tokens qu'il faut rattacher au token précédent. + +44 +00:04:17,810 --> 00:04:30,930 +Et voilà, vous avez toutes les lignes de code nécessaires pour définir votre propre tokenizer avec la bibliothèque Tokenizers. + +45 +00:04:30,930 --> 00:04:35,120 +Maintenant que nous avons un tout nouveau tokenizer avec la bibliothèque Tokenizers, il nous suffit de le + +46 +00:04:35,120 --> 00:04:40,070 +charger dans un tokenizer rapide de la bibliothèque Transformers. + +47 +00:04:40,070 --> 00:04:42,660 +Là encore nous avons plusieurs possibilités. + +48 +00:04:42,660 --> 00:04:48,830 +Nous pouvons le charger dans la classe générique `PreTrainedTokenizerFast` ou dans la classe `BertTokenizerFast` puisque nous + +49 +00:04:48,830 --> 00:04:56,380 +avons ici construit un tokenizer de type BERT. + +50 +00:04:56,380 --> 00:05:01,600 +J'espère que cette vidéo vous a aidé à comprendre comment vous pouvez créer votre propre tokenizer et + +51 +00:05:01,600 --> 00:05:10,669 +que vous êtes prêt à naviguer dans la documentation de la bibliothèque Tokenizers pour choisir les composants + +52 +00:05:10,669 --> 00:05:16,490 +de votre tout nouveau tokenizer ! \ No newline at end of file diff --git a/subtitles/fr/55_data-processing-for-token-classification.srt b/subtitles/fr/55_data-processing-for-token-classification.srt new file mode 100644 index 000000000..1e9312d78 --- /dev/null +++ b/subtitles/fr/55_data-processing-for-token-classification.srt @@ -0,0 +1,139 @@ +1 +00:00:05,600 --> 00:00:08,720 +Étudions comment prétraiter un jeu de données pour la classification de tokens ! + +2 +00:00:10,400 --> 00:00:15,840 +La classification de tokens regroupe toutes les tâches pouvant être définies comme l'étiquetage de chaque mot (ou token) dans + +3 +00:00:15,840 --> 00:00:20,640 +une phrase, comme l'identification des personnes, des organisations et des lieux, par exemple. + +4 +00:00:21,920 --> 00:00:26,720 +Pour notre exemple, nous utiliserons le jeu de données Conll, dans lequel nous supprimons les colonnes que nous + +5 +00:00:26,720 --> 00:00:30,720 +n'utiliserons pas et renommerons les autres pour obtenir un jeu de données avec seulement deux colonnes : + +6 +00:00:31,360 --> 00:00:37,280 +mots et étiquette. Si vous disposez de votre propre jeu de données pour la classification de tokens, assurez-vous simplement de + +7 +00:00:37,280 --> 00:00:43,040 +nettoyer vos données pour arriver au même point, avec une colonne contenant des mots (sous forme de liste de chaînes) + +8 +00:00:43,040 --> 00:00:48,240 +et une autre contenant des étiquettes (sous forme d'entiers s'étendant de 0 à votre nombre d'étiquettes -1). + +9 +00:00:49,520 --> 00:00:53,520 +Assurez-vous que vos noms d'étiquettes sont stockés quelque part. Ici, nous les obtenons à partir des caractéristiques du jeu de + +10 +00:00:53,520 --> 00:00:58,640 +données. Donc vous pouvez associer les nombres entiers à de véritables étiquettes lors de l'inspection de vos données ! + +11 +00:01:00,480 --> 00:01:06,000 +Ici, nous faisons des reconnaissances d'entités nommées. Donc nos étiquettes sont soit `O` pour les mots qui + +12 +00:01:06,000 --> 00:01:11,040 +n'appartiennent à aucune entité, `LOC`, pour l'emplacement, `PER`, pour la personne, + +13 +00:01:11,680 --> 00:01:19,200 +`ORG` pour l'organisation et `MISC` pour divers. Chaque étiquette a deux versions : les étiquettes `B` + +14 +00:01:19,200 --> 00:01:25,840 +indiquent un mot qui commence une entité, tandis que les étiquettes `I` indiquent un mot qui se trouve à l'intérieur d'une entité. + +15 +00:01:26,880 --> 00:01:29,840 +La première étape du prétraitement de nos données consiste à tokeniser les mots. + +16 +00:01:30,400 --> 00:01:35,200 +Cela se fait très facilement avec un tokenizer. Il suffit de lui dire que nous avons prétokénisé les données + +17 +00:01:35,200 --> 00:01:42,160 +avec le drapeau `is_split_into_words=True`. Vient ensuite la partie la plus difficile. Étant donné que nous avons ajouté des tokens spéciaux + +18 +00:01:42,160 --> 00:01:47,200 +et que chaque mot peut avoir été divisé en plusieurs tokens, nos étiquette ne correspondent plus aux tokens. + +19 +00:01:47,840 --> 00:01:51,520 +C'est là que les identifiants de mots fournis par notre tokenizer rapide viennent à la rescousse. + +20 +00:01:52,800 --> 00:01:57,440 +Ils associent chaque token au mot auquel il appartient, ce qui nous permet d'associer chaque token à son étiquette. + +21 +00:01:58,160 --> 00:02:02,080 +Nous devons simplement nous assurer que nous remplaçons les étiquettes `B-` par leurs homologues `I-` pour les tokens + +22 +00:02:02,080 --> 00:02:08,880 +qui se trouvent à l'intérieur (mais pas au début) d'un mot. Les tokens spéciaux reçoivent une étiquette de -100, + +23 +00:02:08,880 --> 00:02:12,960 +c'est ainsi que nous disons aux fonctions de perte du transformer de les ignorer lors du calcul de la perte. + +24 +00:02:14,560 --> 00:02:19,120 +Le code est alors assez simple, nous écrivons une fonction qui décale les étiquettes des tokens qui + +25 +00:02:19,120 --> 00:02:23,920 +sont à l'intérieur d'un mot (que vous pouvez personnaliser) et l'utilisons lors de la génération des étiquettes pour chaque token. + +26 +00:02:25,600 --> 00:02:29,840 +Une fois que cette fonction pour créer nos étiquettes est écrite, nous pouvons prétraiter le jeu de données à l'aide de + +27 +00:02:29,840 --> 00:02:35,840 +la fonction `map`. Avec l'option `batched=True`, nous libérons la vitesse de nos tokenizers rapides. + +28 +00:02:36,720 --> 00:02:39,360 +Le dernier problème survient lorsque nous devons créer un batch. + +29 +00:02:40,160 --> 00:02:43,680 +À moins que vous n'ayez modifié la fonction de prétraitement pour appliquer un rembourrage fixe, + +30 +00:02:43,680 --> 00:02:49,280 +nous obtiendrons des phrases de différentes longueurs, que nous devons rembourrer à la même longueur. Le rembourrage + +31 +00:02:49,280 --> 00:02:55,280 +doit être appliqué aux entrées ainsi qu'aux étiquettes, puisque nous devrions avoir une étiquette par token. Là encore, + +32 +00:02:55,280 --> 00:03:01,200 +-100 indique les étiquette à ignorer pour le calcul de la perte. Tout cela est fait pour + +33 +00:03:01,200 --> 00:03:05,760 +nous par `DataCollatorForTokenClassification`, que vous pouvez utiliser dans PyTorch ou TensorFlow. + +34 +00:03:06,400 --> 00:03:10,960 +Avec tout cela, vous êtes soit prêt à envoyer vos données et ce assembleur de données au Trainer, + +35 +00:03:10,960 --> 00:03:17,840 +soit à utiliser la méthode `to_tf_dataset` et à utiliser la méthode `fit` de votre modèle Keras. \ No newline at end of file diff --git a/subtitles/fr/56_data-processing-for-masked-language-modeling.srt b/subtitles/fr/56_data-processing-for-masked-language-modeling.srt new file mode 100644 index 000000000..43f5214d3 --- /dev/null +++ b/subtitles/fr/56_data-processing-for-masked-language-modeling.srt @@ -0,0 +1,99 @@ +1 +00:00:05,120 --> 00:00:11,360 +Voyons comment nous pouvons prétraiter nos données pour la modélisation du langage masqué. Pour rappel, la + +2 +00:00:11,360 --> 00:00:16,320 +modélisation du langage masqué se produit lorsqu'un modèle doit remplir les blancs dans une phrase. + +3 +00:00:16,320 --> 00:00:22,400 +Pour ce faire, vous n'avez besoin que de textes, pas d'étiquettes, car il s'agit d'un problème autosupervisé. Pour l' + +4 +00:00:22,400 --> 00:00:27,280 +appliquer à vos propres données, assurez-vous simplement que tous vos textes sont rassemblés dans une colonne de votre jeu de données. + +5 +00:00:28,160 --> 00:00:32,320 +Avant de commencer à masquer des choses au hasard, nous devrons en quelque sorte faire en sorte que tous ces textes aient la + +6 +00:00:32,320 --> 00:00:38,400 +même longueur pour les regrouper. La première façon de faire en sorte que tous les textes aient la même longueur + +7 +00:00:38,400 --> 00:00:43,840 +est celle que nous avons utilisée dans la classification de texte. Remplissons les textes courts et tronquons les longs. + +8 +00:00:44,800 --> 00:00:48,400 +Comme nous l'avons vu lorsque nous avons traité les données pour la classification du texte, tout + +9 +00:00:48,400 --> 00:00:51,840 +cela est fait par notre tokenizer avec les bonnes options de rembourrage et de troncature. + +10 +00:00:52,880 --> 00:00:57,840 +Cela nous fera cependant perdre beaucoup de textes si les exemples de notre jeu de données sont très longs, + +11 +00:00:58,400 --> 00:01:03,040 +par rapport à la longueur de contexte que nous avons choisie. Ici, toute la partie en gris est perdue. + +12 +00:01:04,160 --> 00:01:08,320 +C'est pourquoi une deuxième façon de générer des échantillons de texte de même longueur consiste à découper notre + +13 +00:01:08,320 --> 00:01:12,720 +texte en morceaux de longueurs de contexte, au lieu de tout supprimer après le premier morceau. + +14 +00:01:13,760 --> 00:01:17,920 +Il y aura probablement un reste de longueur plus petit que la taille du contexte, que nous pouvons + +15 +00:01:17,920 --> 00:01:24,480 +choisir de conserver et de rembourrer ou d'ignorer. Voici comment nous pouvons appliquer cela dans la pratique, en ajoutant simplement l' + +16 +00:01:24,480 --> 00:01:30,080 +option `tokenize_and_chunk` dans notre appel de tokenizer. Notez comment cela nous donne un plus grand jeu de données ! + +17 +00:01:31,280 --> 00:01:36,720 +Cette deuxième méthode de segmentation est idéale si tous vos textes sont très longs, mais elle ne fonctionnera pas aussi bien + +18 +00:01:36,720 --> 00:01:42,640 +si vous avez une variété de longueurs dans les textes. Dans ce cas, la meilleure option consiste à concaténer + +19 +00:01:42,640 --> 00:01:47,600 +tous vos textes tokenisés dans un seul grand flux, avec des tokens spéciaux pour indiquer quand vous passez d' d' + +20 +00:01:47,600 --> 00:01:54,560 +un document à l'autre, puis de diviser le grand flux en morceaux. Voici comment cela peut être fait + +21 +00:01:54,560 --> 00:02:01,200 +avec du code, avec une boucle pour concaténer tous les textes et une autre pour le fragmenter. Remarquez comment cela + +22 +00:02:01,200 --> 00:02:05,920 +réduit le nombre d'échantillons dans notre jeu de données ici, il doit y avoir eu pas mal d'entrées courtes ! + +23 +00:02:07,520 --> 00:02:12,960 +Une fois cela fait, le masquage est la partie la plus facile. Il existe un assembleur de données spécialement conçu à + +24 +00:02:12,960 --> 00:02:18,240 +cet effet dans la bibliothèque Transformers. Vous pouvez l'utiliser directement dans le Trainer, ou lors de la conversion de + +25 +00:02:18,240 --> 00:02:29,600 +vos ensembles de données en ensembles de données TensorFlow avant de faire `Keras.fit`, avec la méthode `to_tf_dataset`. \ No newline at end of file diff --git a/subtitles/fr/57_what-is-perplexity.srt b/subtitles/fr/57_what-is-perplexity.srt new file mode 100644 index 000000000..800e59b79 --- /dev/null +++ b/subtitles/fr/57_what-is-perplexity.srt @@ -0,0 +1,83 @@ +1 +00:00:05,280 --> 00:00:09,200 +Dans cette vidéo, nous examinons la mystérieuse métrique appelée perplexité. + +2 +00:00:10,880 --> 00:00:14,880 +Vous avez peut-être rencontré la perplexité en lisant des articles sur les modèles génératifs. + +3 +00:00:14,880 --> 00:00:19,760 +Vous pouvez voir ici deux exemples. Un venant de l'article original sur les transformers, « Attention is all you need », + +4 +00:00:19,760 --> 00:00:25,600 +ainsi que de l'article plus récent, GPT-2. La perplexité est une métrique courante pour mesurer les performances + +5 +00:00:25,600 --> 00:00:30,880 +des modèles de langage. Plus la valeur est petite, meilleures sont les performances. Mais qu'est-ce que cela + +6 +00:00:30,880 --> 00:00:36,880 +signifie réellement et comment pouvons-nous le calculer ? Une quantité très courante en apprentissage automatique est la vraissemblance. + +7 +00:00:37,440 --> 00:00:41,280 +Nous pouvons calculer la vraissemblance comme le produit de la probabilité de chaque token. + +8 +00:00:42,160 --> 00:00:47,200 +Cela signifie que pour chaque token, nous utilisons le modèle de langage pour prédire sa probabilité en + +9 +00:00:47,200 --> 00:00:52,960 +fonction des tokens précédents. Au final, nous multiplions toutes les probabilités pour obtenir la vraissemblance. + +10 +00:00:55,680 --> 00:00:59,120 +Avec la vraisemblance, nous pouvons calculer une autre quantité importante : + +11 +00:00:59,120 --> 00:01:04,560 +l'entropie croisée. Vous avez peut-être déjà entendu parler de l'entropie croisée en examinant la fonction de perte. + +12 +00:01:05,440 --> 00:01:08,480 +L'entropie croisée est souvent utilisée comme fonction de perte dans la classification. + +13 +00:01:09,040 --> 00:01:14,720 +Dans la modélisation du langage, nous prédisons le token suivant, qui est aussi une tâche de classification. + +14 +00:01:15,600 --> 00:01:20,400 +Par conséquent, si nous voulons calculer l'entropie croisée d'un exemple, nous pouvons simplement la transmettre au + +15 +00:01:20,400 --> 00:01:25,840 +modèle avec les entrées comme étiquettes. La perte correspond alors à l'entropie croisée. + +16 +00:01:28,880 --> 00:01:32,640 +Nous ne sommes plus qu'à une opération du calcul de la perplexité. + +17 +00:01:33,280 --> 00:01:39,360 +En exponentiant l'entropie croisée, nous obtenons la perplexité. Vous voyez donc que la perplexité est + +18 +00:01:39,360 --> 00:01:55,040 +étroitement liée à la perte. En faisant le lien avec les résultats précédents, c'est équivalent à l'exponentielle du négatif de la moyenne du logarithme de chaque probabilité. + +19 +00:01:50,360 --> 00:01:55,040 +Gardez à l'esprit que la perte n'est qu'un faible indicateur de la capacité d'un modèle + +20 +00:01:55,040 --> 00:02:01,600 +à générer un texte de qualité et qu'il en va de même pour la perplexité. Pour cette raison, on + +21 +00:02:01,600 --> 00:02:07,840 +calcule généralement également des métriques plus sophistiquées telles que BLEU ou ROUGE sur les tâches génératives. \ No newline at end of file diff --git a/subtitles/fr/58_what-is-domain-adaptation.srt b/subtitles/fr/58_what-is-domain-adaptation.srt new file mode 100644 index 000000000..a10484546 --- /dev/null +++ b/subtitles/fr/58_what-is-domain-adaptation.srt @@ -0,0 +1,71 @@ +1 +00:00:05,840 --> 00:00:12,400 +Qu'est-ce que l'adaptation au domaine ? Lors du finetuning d'un modèle pré-entraîné sur un nouveau jeu de données, + +2 +00:00:12,400 --> 00:00:17,200 +le modèle finetuné que nous obtenons fera des prédictions adaptées à ce nouvel jeu de données. + +3 +00:00:18,640 --> 00:00:23,440 +Lorsque les deux modèles sont entraînés avec la même tâche, nous pouvons alors comparer leurs prédictions + +4 +00:00:23,440 --> 00:00:27,600 +sur la même entrée. Les prédictions des deux modèles seront différentes, + +5 +00:00:27,600 --> 00:00:32,640 +d'une manière qui reflète les différences entre les deux jeux de données, un phénomène que nous appelons + +6 +00:00:32,640 --> 00:00:39,840 +« adaptation au domaine ». Regardons un exemple avec la modélisation du langage masqué, en comparant les sorties + +7 +00:00:39,840 --> 00:00:44,400 +du modèle distillBERT pré-entraîné avec la version finetunée au chapitre 7 du cours + +8 +00:00:44,400 --> 00:00:50,800 +(lien ci-dessous). Le modèle pré-entraîné fait des prédictions génériques, tandis que le modèle finetuné a ses + +9 +00:00:50,800 --> 00:00:57,040 +deux premières prédictions liées au cinéma. Puisqu'il a été finetuné sur un jeu de données de critiques de films, + +10 +00:00:57,040 --> 00:01:00,320 +il est tout à fait normal de le voir adapter ses suggestions de cette manière. + +11 +00:01:01,200 --> 00:01:05,520 +Remarquez comment il conserve les mêmes prédictions que le modèle pré-entraîné par la suite. Même si le + +12 +00:01:05,520 --> 00:01:09,920 +modèle finetuné s'adapte au nouveau jeu de données, il n'oublie pas sur quoi il a été pré-entraîné. + +13 +00:01:11,200 --> 00:01:17,120 +Ceci est un autre exemple sur une tâche de traduction. En haut, nous utilisons un modèle français/anglais pré-entraîné + +14 +00:01:17,120 --> 00:01:22,720 +et en bas, la version que nous avons finetunée au chapitre 7. Le modèle du haut est pré-entraîné sur de nombreux + +15 +00:01:22,720 --> 00:01:27,440 +textes et laisse les termes anglais techniques tels que « plugin » et « e-mail » inchangés dans la traduction + +16 +00:01:28,160 --> 00:01:33,360 +(les deux sont parfaitement comprises par les francophones). Le jeu de données sélectionné pour le finetuning est un + +17 +00:01:33,360 --> 00:01:38,240 +jeu de données de textes techniques où une attention particulière a été choisie pour tout traduire en français. + +18 +00:01:38,960 --> 00:01:50,560 +En conséquence, le modèle finetuné a choisi cette habitude et a traduit à la fois « plugin » et « e-mail ». \ No newline at end of file diff --git a/subtitles/fr/59_data-processing-for-translation.srt b/subtitles/fr/59_data-processing-for-translation.srt new file mode 100644 index 000000000..643fa295b --- /dev/null +++ b/subtitles/fr/59_data-processing-for-translation.srt @@ -0,0 +1,131 @@ +1 +00:00:05,670 --> 00:00:09,630 +Voyons comment prétraiter un jeu de données pour la traduction. + +2 +00:00:09,630 --> 00:00:13,269 +C'est la tâche de bien traduire une phrase dans une autre langue. + +3 +00:00:13,269 --> 00:00:18,110 +Cette vidéo se concentrera sur la façon de prétraiter votre jeu de données une fois que vous avez réussi à le + +4 +00:00:18,110 --> 00:00:23,090 +mettre au format suivant : une colonne pour les textes d'entrée et une pour les textes cibles. + +5 +00:00:23,090 --> 00:00:28,439 +Voici comment nous pouvons y parvenir avec la bibliothèque Datasets sur le jeu de données KDE4 pour + +6 +00:00:28,439 --> 00:00:30,960 +la traduction de l'anglais vers le français. + +7 +00:00:30,960 --> 00:00:35,360 +Tant que vous parvenez à faire ressembler vos données à ceci, vous devriez pouvoir suivre les + +8 +00:00:35,360 --> 00:00:36,769 +mêmes étapes. + +9 +00:00:36,769 --> 00:00:41,550 +Pour une fois, nos étiquettes ne sont pas des entiers correspondant à certaines classes, mais du texte brut. + +10 +00:00:41,550 --> 00:00:44,760 +Nous devrons donc les tokeniser, comme nos entrées. + +11 +00:00:44,760 --> 00:00:50,820 +Il y a cependant un piège, car si vous tokenisez vos cibles comme vos entrées, vous rencontrerez + +12 +00:00:50,820 --> 00:00:51,820 +un problème. + +13 +00:00:51,820 --> 00:00:55,829 +Même si vous ne parlez pas français, vous remarquerez peut-être des choses bizarres dans la tokenisation + +14 +00:00:55,829 --> 00:01:01,800 +des cibles : la plupart des mots sont tokenisés en plusieurs sous-tokens, tandis que « fish », l'un + +15 +00:01:01,800 --> 00:01:05,799 +des seuls mots anglais, est tokenisé en un seul mot. + +16 +00:01:05,799 --> 00:01:09,760 +C'est parce que nos entrées ont été tokenisées en anglais. + +17 +00:01:09,760 --> 00:01:13,939 +Comme notre modèle connaît deux langues, il faut le prévenir lors de la tokenisation des cibles, + +18 +00:01:13,939 --> 00:01:16,360 +il bascule donc en mode français. + +19 +00:01:16,360 --> 00:01:20,090 +Cela se fait avec le gestionnaire de contexte `as_target_tokenizer`. + +20 +00:01:20,090 --> 00:01:24,900 +Vous pouvez voir comment cela se traduit par une tokenisation plus compacte. + +21 +00:01:24,900 --> 00:01:28,509 +Le traitement du jeu de données est alors super facile avec la fonction de `map`. + +22 +00:01:28,509 --> 00:01:32,900 +Vous pouvez choisir différentes longueurs maximales pour l'entrée et les cibles, et choisir de rembourrer à + +23 +00:01:32,900 --> 00:01:37,210 +ce stade cette longueur maximale en définissant `padding=max_length`. + +24 +00:01:37,210 --> 00:01:42,540 +Ici, nous allons vous montrer comment rembourrer dynamiquement car cela nécessite une étape de plus. + +25 +00:01:42,540 --> 00:01:45,560 +Vos entrées et cibles sont toutes des phrases de différentes longueurs. + +26 +00:01:45,560 --> 00:01:50,470 +Nous rembourrons les entrées et les cibles séparément car la longueur maximale des entrées et des cibles + +27 +00:01:50,470 --> 00:01:52,740 +peut être différente. + +28 +00:01:52,740 --> 00:01:57,259 +Ensuite, nous rembourrons les entrées avec le token et les cibles avec l'indice -100, pour nous + +29 +00:01:57,259 --> 00:02:01,470 +assurer qu'elles ne sont pas prises en compte dans le calcul de la perte. + +30 +00:02:01,470 --> 00:02:04,869 +Une fois cela fait, regrouper les entrées et les cibles devient super facile ! + +31 +00:02:04,869 --> 00:02:10,220 +La bibliothèque Transformers nous fournit un assembleur de données pour faire tout cela automatiquement. + +32 +00:02:10,220 --> 00:02:15,920 +Vous pouvez ensuite le transmettre au Trainer avec vos jeux de données, ou l'utiliser dans la méthode `to_tf_dataset` + +33 +00:02:15,920 --> 00:02:17,410 +avant d'utiliser model.fit()` dans vos modèles en Keras. \ No newline at end of file diff --git a/subtitles/fr/60_what-is-the-bleu-metric.srt b/subtitles/fr/60_what-is-the-bleu-metric.srt new file mode 100644 index 000000000..1ade63062 --- /dev/null +++ b/subtitles/fr/60_what-is-the-bleu-metric.srt @@ -0,0 +1,220 @@ +1 +00:00:05,520 --> 00:00:12,080 +Qu'est-ce que la métrique BLEU ? Pour de nombreuses tâches de NLP, nous pouvons utiliser des métriques courantes telles que la précision ou le score F1, + +2 +00:00:12,080 --> 00:00:15,280 +mais que faites-vous lorsque vous souhaitez mesurer la qualité du texte généré à partir d'un + +3 +00:00:15,280 --> 00:00:19,680 +modèle ? Dans cette vidéo, nous allons examiner une métrique largement utilisée pour la traduction automatique + +4 +00:00:19,680 --> 00:00:22,960 +appelée BLEU. L'idée de base de + +5 +00:00:22,960 --> 00:00:27,280 +BLEU est d'attribuer un score numérique unique à une traduction qui nous indique à quel point elle est bonne + +6 +00:00:27,280 --> 00:00:32,080 +par rapport à une ou plusieurs traductions de référence. Dans cet exemple, nous avons une phrase en espagnol qui + +7 +00:00:32,080 --> 00:00:37,280 +a été traduite en anglais par un modèle. Si nous comparons la traduction générée à certaines + +8 +00:00:37,280 --> 00:00:42,160 +traductions humaines de référence, nous pouvons voir que le modèle est plutôt bon, mais a fait une erreur courante : + +9 +00:00:42,960 --> 00:00:48,000 +le mot espagnol « tengo » signifie « avoir » en anglais et cette traduction un à un n'est pas tout à fait naturel. + +10 +00:00:49,680 --> 00:00:53,280 +Alors, comment mesurer automatiquement la qualité d'une traduction générée ? + +11 +00:00:54,080 --> 00:00:58,000 +L'approche adoptée par BLEU consiste à comparer les n-grammes de la traduction générée aux + +12 +00:00:58,000 --> 00:01:03,760 +n-grammes des références. Un n-gramme n'est qu'une façon élégante de dire « un morceau de n mots ». + + +13 +00:01:03,760 --> 00:01:07,280 +Donc commençons par les unigrammes, qui correspondent aux mots individuels d'une phrase. + +14 +00:01:08,720 --> 00:01:12,160 +Dans cet exemple, vous pouvez voir que quatre des mots de la traduction générée + +15 +00:01:12,160 --> 00:01:18,000 +sont également trouvés dans l'une des traductions de référence. Maintenant que nous avons trouvé nos correspondances, + +16 +00:01:18,000 --> 00:01:21,920 +une façon d'attribuer un score à la traduction consiste à calculer la précision des unigrammes. + +17 +00:01:22,880 --> 00:01:27,200 +Cela signifie que nous comptons simplement le nombre de mots correspondants dans les traductions générées et de référence + +18 +00:01:27,200 --> 00:01:30,400 +et que nous normalisons le nombre en divisant par le nombre de mots dans la génération. + +19 +00:01:31,600 --> 00:01:35,600 +Dans cet exemple, nous avons trouvé 4 mots correspondants et notre génération a 5 mots, + +20 +00:01:36,960 --> 00:01:40,320 +donc notre précision unigramme est de 4/5 ou 0,8. En général, la précision est comprise entre 0 et 1, et des + +21 +00:01:40,320 --> 00:01:48,160 +scores de précision plus élevés signifient une meilleure traduction. L'un des problèmes de la précision des unigrammes est que les modèles de traduction restent + +22 +00:01:48,160 --> 00:01:51,840 +parfois bloqués dans des schémas répétitifs et répètent plusieurs fois le même mot. + +23 +00:01:52,960 --> 00:01:56,240 +Si nous comptons simplement le nombre de correspondances de mots, nous pouvons obtenir des scores de très haute précision + +24 +00:01:56,240 --> 00:01:58,720 +même si la traduction est terrible d'un point de vue humain ! + +25 +00:01:59,840 --> 00:02:04,640 +Par exemple, si notre modèle génère simplement le mot « six », nous obtenons un score de précision unigramme parfait. + +26 +00:02:07,040 --> 00:02:12,000 +Pour gérer cela, BLEU utilise une précision modifiée qui limite le nombre de fois pour compter un mot, en + +27 +00:02:12,000 --> 00:02:14,960 +fonction du nombre maximal de fois qu'il apparaît dans la traduction de référence. + +28 +00:02:16,160 --> 00:02:19,360 +Dans cet exemple, le mot « six » n'apparaît qu'une seule fois dans la référence. + +29 +00:02:19,360 --> 00:02:23,840 +Nous coupons donc le numérateur à un et la précision de l'unigramme modifié donne désormais un score beaucoup plus faible. + +30 +00:02:27,440 --> 00:02:31,600 +Un autre problème avec la précision unigramme est qu'elle ne tient pas compte de l'ordre des + +31 +00:02:31,600 --> 00:02:37,200 +mots dans les traductions. Par exemple, supposons que nous ayons demandé à Yoda de traduire notre phrase en espagnol, + +32 +00:02:37,200 --> 00:02:43,120 +nous pourrions alors obtenir quelque chose à l'envers comme « années trente six j'ai ». Dans ce cas, + +33 +00:02:43,120 --> 00:02:46,560 +la précision unigramme modifiée donne une haute précision qui n'est pas ce que nous voulons. + +34 +00:02:48,240 --> 00:02:52,400 +Ainsi, pour traiter les problèmes d'ordre des mots, BLEU calcule en fait la précision pour plusieurs + +35 +00:02:52,400 --> 00:02:57,360 +n-grammes différents, puis calcule la moyenne du résultat. Par exemple, si nous comparons 4-grammes, nous pouvons + +36 +00:02:57,360 --> 00:03:03,840 +voir qu'il n'y a pas de blocs correspondants de 4 mots dans les traductions et donc la précision de 4-grammes est de 0. + +37 +00:03:05,440 --> 00:03:10,880 +Pour calculer les scores BLEU dans la bibliothèque Datasets, c'est très simple : il suffit d'utiliser la fonction `load_metric(), + +38 +00:03:10,880 --> 00:03:13,840 +fournir les prédictions de votre modèle avec les références et vous êtes prêt ! + +39 +00:03:16,240 --> 00:03:19,920 +Le résultat contient plusieurs champs d'intérêt. Le champ `précisions` + +40 +00:03:19,920 --> 00:03:22,800 +contient tous les scores de précision individuels pour chaque n-gramme. + +41 +00:03:24,800 --> 00:03:30,320 +Le score `bleu` lui-même est ensuite calculé en prenant la moyenne géométrique des scores de précision. Par + +42 +00:03:30,320 --> 00:03:34,880 +défaut, la moyenne des précisions des quatre n-grammes est rapportée, une métrique parfois également appelée + +43 +00:03:34,880 --> 00:03:40,480 +BLEU-4. Dans cet exemple, nous pouvons voir que le score BLEU est égal à 0 car la précision du 4-grammes était de 0. + +44 +00:03:43,440 --> 00:03:46,640 +La métrique BLEU a de belles propriétés, mais elle est loin d'être une métrique parfaite. + +45 +00:03:47,280 --> 00:03:51,520 +Les bonnes propriétés sont qu'il est facile à calculer et largement utilisé dans la recherche afin que vous puissiez comparer + +46 +00:03:51,520 --> 00:03:56,560 +votre modèle à d'autres sur un benchmark. D'autre part, il y a plusieurs problèmes avec BLEU, + +47 +00:03:56,560 --> 00:04:00,560 +y compris le fait qu'elle n'intègre pas la sémantique et les difficultés sur les langues autres que l'anglais. + +48 +00:04:01,680 --> 00:04:04,560 +Un autre problème avec BLEU est qu'elle suppose que les traductions humaines ont + +49 +00:04:04,560 --> 00:04:08,400 +déjà été tokenisées, ce qui rend difficile la comparaison de modèles avec différents tokenizers. + +50 +00:04:11,200 --> 00:04:15,280 +Comme nous avons vu, mesurer la qualité des textes reste un problème difficile et ouvert en recherche en NLP. + +51 +00:04:15,280 --> 00:04:17,680 +Pour la traduction automatique, la recommandation actuelle est + +52 +00:04:17,680 --> 00:04:21,600 +d'utiliser la métrique SacreBLEU qui résout les limites de tokenisation de BLEU. + +53 +00:04:22,640 --> 00:04:26,560 +Comme vous pouvez le voir dans cet exemple, le calcul du score SacreBLEU est presque identique à celui + +54 +00:04:26,560 --> 00:04:30,800 +du BLEU. La principale différence est que nous passons maintenant une liste de textes au lieu d'une liste + +55 +00:04:30,800 --> 00:04:41,200 +de mots pour les traductions, et SacreBLEU s'occupe de la tokenisation sous le capot. \ No newline at end of file diff --git a/subtitles/fr/61_data-processing-for-summarization.srt b/subtitles/fr/61_data-processing-for-summarization.srt new file mode 100644 index 000000000..6326bd7f4 --- /dev/null +++ b/subtitles/fr/61_data-processing-for-summarization.srt @@ -0,0 +1,83 @@ +1 +00:00:05,360 --> 00:00:10,720 +Voyons comment prétraiter un jeu de données pour le résumé de texte. C'est la tâche de + +2 +00:00:10,720 --> 00:00:16,976 +bien résumer un long document. Cette vidéo se concentrera sur la façon de prétraiter votre jeu de données une fois que vous + +3 +00:00:16,976 --> 00:00:21,840 +avez réussi à le mettre dans le format suivant : une colonne pour les documents longs et une pour + +4 +00:00:21,840 --> 00:00:27,360 +les résumés. Voici comment nous pouvons y parvenir avec la bibliothèque Datasets sur le jeu de données XSUM. + +5 +00:00:28,400 --> 00:00:32,400 +Tant que vous parvenez à faire en sorte que vos données ressemblent à ceci, vous devriez pouvoir suivre les mêmes étapes. + +6 +00:00:33,520 --> 00:00:37,280 +Pour une fois, nos étiquettes ne sont pas des entiers correspondant à certaines classes, + +7 +00:00:37,280 --> 00:00:43,120 +mais du texte brut. Nous devrons donc les tokeniser, comme nos entrées. Il y a cependant un petit piège + +8 +00:00:43,120 --> 00:00:47,760 +car nous devons tokeniser nos cibles dans le gestionnaire de contexte `as_target_tokenzier`. + +9 +00:00:48,480 --> 00:00:53,200 +En effet, les tokens spéciaux que nous ajoutons peuvent être légèrement différents pour les entrées et les cibles. + +10 +00:00:53,760 --> 00:00:58,320 +Le tokenizer doit donc savoir lequel il traite. Le traitement du jeu de données + +11 +00:00:58,320 --> 00:01:03,520 +est alors très facile avec la fonction `map`. Les résumés étant généralement beaucoup plus courts que les + +12 +00:01:03,520 --> 00:01:07,840 +documents, vous devez absolument choisir des longueurs maximales différentes pour les entrées et les cibles. + +13 +00:01:08,640 --> 00:01:12,640 +À ce stade, vous pouvez choisir de rembourrer cette longueur maximale en définissant `padding=max_length`. + +14 +00:01:13,840 --> 00:01:17,360 +Ici, nous allons vous montrer comment rembourrer dynamiquement car cela nécessite une étape supplémentaire. + +15 +00:01:18,640 --> 00:01:23,360 +Vos entrées et cibles sont toutes des phrases de différentes longueurs. Nous rembourrons les entrées et les + +16 +00:01:23,360 --> 00:01:27,920 +cibles séparément car la longueur maximale des entrées et des cibles est complètement différente. + +17 +00:01:28,880 --> 00:01:32,320 +Ensuite, nous rembourrons les entrées aux longueurs maximales parmi les entrées, + +18 +00:01:32,320 --> 00:01:38,800 +et de même pour les cibles. Nous rembourrons les entrées avec le token et les cibles avec l'indice -100 + +19 +00:01:38,800 --> 00:01:44,400 +pour nous assurer qu'elles ne sont pas prises en compte dans le calcul de la perte. La bibliothèque Transformers + +20 +00:01:44,400 --> 00:01:49,200 +nous fournit un assembleur de données pour faire tout cela automatiquement. Vous pouvez ensuite le transmettre + +21 +00:01:49,200 --> 00:01:55,440 +au Trainer avec vos jeux de données ou l'utiliser dans la méthode `to_tf_dataset` avant d'utiliser `model.fit()` dans vos modèles en Keras. \ No newline at end of file diff --git a/subtitles/fr/62_what-is-the-rouge-metric.srt b/subtitles/fr/62_what-is-the-rouge-metric.srt new file mode 100644 index 000000000..ffd7494c5 --- /dev/null +++ b/subtitles/fr/62_what-is-the-rouge-metric.srt @@ -0,0 +1,187 @@ +1 +00:00:05,520 --> 00:00:12,080 +Qu'est-ce que la métrique ROUGE ? Pour de nombreuses tâches de NLP, nous pouvons utiliser des métriques courantes telles que la précision ou le score F1, + +2 +00:00:12,080 --> 00:00:15,920 +mais que faites-vous lorsque vous souhaitez mesurer la qualité d'un résumé à partir d'un modèle comme T5 ? + +3 +00:00:16,720 --> 00:00:20,265 +Dans cette vidéo, nous allons examiner une métrique largement utilisée pour la synthèse de texte appelée ROUGE. + +4 +00:00:20,265 --> 00:00:23,360 +Il existe en fait plusieurs + +5 +00:00:23,360 --> 00:00:27,280 +variantes de ROUGE mais l'idée de base derrière chacune d'elles est d'attribuer un seul + +6 +00:00:27,280 --> 00:00:31,360 +score numérique à un résumé qui nous indique à quel point il est bon par rapport à un ou plusieurs résumés de référence. + +7 +00:00:32,320 --> 00:00:35,360 +Dans cet exemple, nous avons une critique de livre qui a été résumée par un modèle. + +8 +00:00:36,400 --> 00:00:39,600 +Si nous comparons le résumé généré à certains résumés humains de référence, + +9 +00:00:39,600 --> 00:00:43,840 +nous pouvons voir que le modèle est plutôt bon et ne diffère que d'un mot ou deux. + +10 +00:00:44,800 --> 00:00:48,000 +Alors, comment mesurer automatiquement la qualité d'un résumé généré ? + +11 +00:00:48,800 --> 00:00:52,880 +L'approche adoptée par ROUGE consiste à comparer les n-grammes du résumé généré aux + +12 +00:00:52,880 --> 00:00:58,400 +n-grammes des références. Un n-gramme n'est qu'une façon élégante de dire « un morceau de n mots ». + +13 +00:00:58,400 --> 00:01:02,080 +Donc commençons par les unigrammes, qui correspondent aux mots individuels d'une phrase. + +14 +00:01:03,600 --> 00:01:07,760 +Dans cet exemple, vous pouvez voir que six des mots du résumé généré se retrouvent également dans l'un des + +15 +00:01:07,760 --> 00:01:11,840 +résumés de référence. La métrique ROUGE qui compare les unigrammes est appelée ROUGE-1. + +16 +00:01:14,000 --> 00:01:18,000 +Maintenant que nous avons trouvé nos correspondances, une façon d'attribuer un score au résumé consiste à calculer le + +17 +00:01:18,000 --> 00:01:22,880 +rappel des unigrammes. Cela signifie que nous comptons simplement le nombre de mots correspondants dans les résumés générés et de + +18 +00:01:22,880 --> 00:01:27,040 +référence et normalisons le nombre en divisant par le nombre de mots dans la référence. + +19 +00:01:28,000 --> 00:01:31,920 +Dans cet exemple, nous avons trouvé 6 mots correspondants et notre référence a 6 mots, + +20 +00:01:31,920 --> 00:01:36,240 +donc notre rappel d'unigramme est parfait ! Cela signifie que tous les mots du + +21 +00:01:36,240 --> 00:01:42,320 +résumé de référence ont été produits dans celui généré. Un rappel parfait sonne bien mais imaginez si + +22 +00:01:42,320 --> 00:01:47,120 +notre résumé généré avait été « J'ai vraiment vraiment vraiment adoré lire Hunger Games ». + +23 +00:01:47,920 --> 00:01:52,240 +Cela aurait également un rappel parfait mais c'est sans doute un pire résumé car il est verbeux. + +24 +00:01:53,280 --> 00:01:57,840 +Pour gérer ces scénarios, nous pouvons également calculer la précision, qui dans le contexte ROUGE mesure + +25 +00:01:57,840 --> 00:02:01,200 +la proportion du résumé généré qui était pertinente. Dans cet exemple, la précision est de 6/7. En pratique, + +26 +00:02:01,200 --> 00:02:05,200 +la précision et le rappel sont généralement calculés, puis le score F1 est rapporté. + +27 +00:02:07,360 --> 00:02:12,000 +Nous pouvons modifier la granularité de la comparaison en comparant des bigrammes au lieu d'unigrammes. + +28 +00:02:12,800 --> 00:02:17,760 +Avec les bigrammes, nous décomposons la phrase en paires de mots consécutifs puis comptons le nombre de paires dans + +29 +00:02:17,760 --> 00:02:23,600 +le résumé généré qui sont présentes dans celui de référence. Cela nous donne une précision et un rappel ROUGE-2, + +30 +00:02:23,600 --> 00:02:28,800 +dont nous pouvons constater qu'ils sont inférieurs aux scores ROUGE-1 que nous avons vus précédemment. Notez que si les résumés sont + +31 +00:02:28,800 --> 00:02:34,560 +longs, le score ROUGE-2 sera faible car il y a généralement moins de bigrammes à faire correspondre. Cela est + +32 +00:02:34,560 --> 00:02:39,680 +également vrai pour la synthèse abstraite, donc les scores ROUGE-1 et ROUGE-2 sont généralement rapportés. + +33 +00:02:41,760 --> 00:02:46,880 +La dernière variante ROUGE dont nous parlerons est ROUGE-L. ROUGE-L ne compare pas les n-grammes, + +34 +00:02:46,880 --> 00:02:51,360 +mais traite plutôt chaque résumé comme une séquence de mots, puis recherche la plus longue sous-séquence + +35 +00:02:51,360 --> 00:02:57,280 +commune ou « LCS ». Une sous-séquence est une séquence qui apparaît dans le même ordre relatif, + +36 +00:02:57,280 --> 00:03:03,280 +mais pas nécessairement contiguë. Ainsi, dans cet exemple, « J'ai adoré lire Hunger Games » est la sous- + +37 +00:03:03,280 --> 00:03:11,120 +séquence commune la plus longue. Le principal avantage de ROUGE-L par rapport à ROUGE-1 ou ROUGE-2 est qu'il ne + +38 +00:03:11,120 --> 00:03:18,400 +dépend pas de correspondances consécutives de n-grammes, il a donc tendance à capturer la structure de la phrase avec plus de précision. + +39 +00:03:18,400 --> 00:03:23,200 +Calculer les scores ROUGE dans Datasets d'Hugging Face est très simple : utilisez simplement la fonction `load_metric()`, + +40 +00:03:23,760 --> 00:03:26,960 +fournissez les résumés de votre modèle avec les références et vous êtes prêt ! + +41 +00:03:28,560 --> 00:03:32,480 +Le résultat du calcul contient de nombreuses informations ! La première chose que nous + +42 +00:03:32,480 --> 00:03:36,880 +pouvons voir ici est que les intervalles de confiance de chaque score ROUGE sont fournis dans les champs `low`, + +43 +00:03:36,880 --> 00:03:41,680 +`mid` et `high`. Ceci est très utile si vous voulez connaître la répartition de vos scores ROUGE lorsque vous + +44 +00:03:41,680 --> 00:03:48,080 +comparez deux modèles ou plus. La deuxième chose à remarquer est que nous avons quatre types de score ROUGE. + +45 +00:03:48,080 --> 00:03:53,840 +Nous avons déjà vu ROUGE-1, ROUGE-2 et ROUGE-L, alors qu'est-ce que ROUGE-LSUM ? Eh bien, + +46 +00:03:53,840 --> 00:03:58,800 +la somme dans ROUGE-LSUM fait référence au fait que cette métrique est calculée sur l'ensemble d'un résumé, + +47 +00:03:58,800 --> 00:04:08,480 +tandis que ROUGE-L est calculée comme la moyenne sur des phrases individuelles. \ No newline at end of file diff --git a/subtitles/fr/63_data-processing-for-causal-language-modeling.srt b/subtitles/fr/63_data-processing-for-causal-language-modeling.srt new file mode 100644 index 000000000..ebf9da64e --- /dev/null +++ b/subtitles/fr/63_data-processing-for-causal-language-modeling.srt @@ -0,0 +1,171 @@ +1 +00:00:05,520 --> 00:00:09,360 +Dans cette vidéo, nous examinons le traitement des données nécessaire pour entraîner + +2 +00:00:09,360 --> 00:00:15,920 +des modèles de langage causal. La modélisation du langage causal consiste à prédire le token suivant en fonction + +3 +00:00:15,920 --> 00:00:20,880 +du token précédent. Un autre terme pour la modélisation du langage causal est la modélisation autorégressive. + +4 +00:00:21,760 --> 00:00:26,560 +Dans l'exemple que vous voyez ici, le token suivant pourrait par exemple être « NLP » + +5 +00:00:26,560 --> 00:00:33,280 +ou « apprentissage automatique ». Un exemple populaire de modèle de langage causal est la famille de modèles GPT. + +6 +00:00:35,680 --> 00:00:40,400 +Pour entraîner des modèles tels que GPT-2, nous commençons généralement avec un grand corpus de fichiers texte. + +7 +00:00:41,280 --> 00:00:45,760 +Ces fichiers peuvent être des pages Web extraites d'Internet, telles que le jeu de données Common Crawl, + +8 +00:00:45,760 --> 00:00:51,920 +ou des fichiers Python de GitHub, comme vous pouvez le voir ici. Dans un premier temps, nous devons tokeniser + +9 +00:00:51,920 --> 00:00:57,520 +ces fichiers afin de pouvoir les donner au modèle. Ici, nous montrons les textes tokenisés sous forme de barres de + +10 +00:00:57,520 --> 00:01:06,000 +illustrant qu'ils y en a des longs et des courts. Ceci est trian commun lorsque l'on travail avec du texte. + +11 +00:01:06,000 --> 00:01:07,440 +Cependant les transformers ont une + +12 +00:01:07,440 --> 00:01:12,960 +longueur de contexte limitée et, selon la source de données, il est possible que les textes tokenisés + +13 +00:01:12,960 --> 00:01:18,640 +soient beaucoup plus longs que cette longueur de contexte. Dans ce cas, nous pourrions simplement tronquer la séquence + +14 +00:01:18,640 --> 00:01:24,160 +à la longueur du contexte, mais cela signifierait que nous perdons tout après la longueur du contexte. + +15 +00:01:25,360 --> 00:01:30,960 +À l'aide de `return_overflowing_tokens`, nous pouvons utiliser le tokenizer pour créer des morceaux + +16 +00:01:30,960 --> 00:01:36,960 +chacun étant de la taille de la longueur du contexte. Parfois, il peut arriver que le dernier morceau soit + +17 +00:01:36,960 --> 00:01:41,440 +trop court s'il n'y a pas assez de tokens pour le remplir. Dans ce cas, nous aimerions le supprimer. + +18 +00:01:43,440 --> 00:01:48,800 +Avec le mot-clé `return_length`, nous obtenons également la longueur de chaque bloc du tokenizer. + +19 +00:01:51,760 --> 00:01:57,280 +Cette fonction affiche toutes les étapes nécessaires pour préparer le jeu de données. D'abord, nous tokenisons le + +20 +00:01:57,280 --> 00:02:03,520 +jeu de données avec les indicateurs que je viens de mentionner. Ensuite, nous parcourons chaque morceau et si sa longueur correspond à + +21 +00:02:03,520 --> 00:02:08,960 +la longueur du contexte, nous l'ajoutons aux entrées que nous renvoyons. Nous pouvons appliquer cette fonction à l'ensemble du jeu de + +22 +00:02:08,960 --> 00:02:17,520 +données et nous nous assurons d'utiliser des batchs et de supprimer les colonnes existantes. Nous devons supprimer des colonnes + +23 +00:02:17,520 --> 00:02:23,280 +car nous pouvons créer plusieurs échantillons par texte et les formes du jeu de données ne correspondraient pas dans ce cas. + +24 +00:02:26,960 --> 00:02:32,400 +Si la longueur du contexte est similaire à celle des fichiers, cette approche ne fonctionne plus aussi bien. + +25 +00:02:33,520 --> 00:02:39,440 +Dans cet exemple, les échantillons 1 et 2 sont plus courts que la taille du contexte et seraient ignorés avec + +26 +00:02:39,440 --> 00:02:46,400 +l'approche précédente. Dans ce cas, il est préférable de commencer par segmenter chaque échantillon sans troncature + +27 +00:02:46,400 --> 00:02:52,000 +puis de concaténer les échantillons segmentés avec un token de fin de chaîne, ou « EOS », entre les deux. + +28 +00:02:53,840 --> 00:02:57,440 +Enfin, nous pouvons fragmenter cette longue séquence avec la longueur du contexte + +29 +00:02:57,440 --> 00:03:05,840 +et nous ne perdons aucune séquence car elles sont trop courtes. Jusqu'à présent, nous n'avons parlé que + +30 +00:03:05,840 --> 00:03:10,720 +des entrées pour la modélisation causale du langage, mais pas des étiquettes nécessaires à l'entraînement supervisée. + +31 +00:03:11,600 --> 00:03:16,480 +Lorsque nous effectuons une modélisation causale du langage, nous n'avons pas besoin d'étiquettes supplémentaires pour les séquences d'entrée + +32 +00:03:16,480 --> 00:03:22,080 +car les séquences d'entrée elles-mêmes sont les étiquettes. Dans cet exemple, lorsque nous transmettons + +33 +00:03:22,080 --> 00:03:26,560 +le token « Trans » au prochain token que nous voulons que le modèle prédise est « formers ». + +34 +00:03:27,280 --> 00:03:33,360 +À l'étape suivante, nous donnons « Trans » et « formers » au modèle et l'étiquette que nous voulons prédire est « are ». + +35 +00:03:35,280 --> 00:03:42,400 +Ce schéma se poursuit et, comme vous pouvez le voir, la séquence d'entrée est l'étiquette qui vient d'être décalée d'une unité. + +36 +00:03:43,440 --> 00:03:48,000 +Étant donné que le modèle n'effectue une prédiction qu'après le premier token, le premier élément + +37 +00:03:48,000 --> 00:03:54,480 +de la séquence d'entrée, dans ce cas « Trans », n'est pas utilisé comme étiquette. De même, nous n'avons pas + +38 +00:03:54,480 --> 00:04:00,400 +d'étiquette pour le dernier token de la séquence puisqu'il n'y a pas de token après la fin de la séquence. + +39 +00:04:03,920 --> 00:04:09,200 +Voyons ce que nous devons faire pour créer les étiquettes pour la modélisation du langage causal dans le code. Si + +40 +00:04:10,160 --> 00:04:15,600 +nous voulons calculer la perte sur un batch, nous pouvons simplement transmettre les `input_ids` en tant qu'étiquettes et tout le + +41 +00:04:15,600 --> 00:04:19,432 +décalage est géré dans le modèle en interne. + +42 +00:04:19,432 --> 00:04:21,600 +Donc vous voyez qu'il n'y a pas de magie + +43 +00:04:21,600 --> 00:04:27,840 +impliquée dans le traitement des données pour la modélisation du langage causal et ne nécessite que quelques étapes simples ! \ No newline at end of file diff --git a/subtitles/fr/64_using-a-custom-loss-function.srt b/subtitles/fr/64_using-a-custom-loss-function.srt new file mode 100644 index 000000000..f0e444c32 --- /dev/null +++ b/subtitles/fr/64_using-a-custom-loss-function.srt @@ -0,0 +1,135 @@ +1 +00:00:05,440 --> 00:00:09,040 +Dans cette vidéo, nous examinons la configuration d'une fonction de perte personnalisée pour l'entraînement. + +2 +00:00:10,800 --> 00:00:14,800 +Dans les fonctions de perte par défaut, tous les échantillons tels que ces extraits de code + +3 +00:00:14,800 --> 00:00:19,040 +sont traités de la même manière, quel que soit leur contenu. Mais il existe des scénarios dans lesquels il + +4 +00:00:19,040 --> 00:00:22,880 +peut être judicieux de pondérer les échantillons différemment. Si, par exemple, un échantillon + +5 +00:00:22,880 --> 00:00:28,800 +contient un grand nombre de tokens qui nous intéressent ou s'il présente une diversité favorable de tokens. + +6 +00:00:29,680 --> 00:00:33,520 +Nous pouvons également penser à d'autres heuristiques que nous pouvons implémenter avec l'appariement de patrons ou d'autres règles. + +7 +00:00:36,080 --> 00:00:40,400 +Pour chaque échantillon, nous obtenons une valeur de perte pendant l'entraînement et nous pouvons combiner cette perte avec + +8 +00:00:40,400 --> 00:00:47,200 +un poids. Ensuite, nous pouvons créer une somme pondérée pour obtenir la perte finale d'un batch. + +9 +00:00:48,480 --> 00:00:53,280 +Examinons un exemple spécifique : nous voulons configurer un modèle de langage qui nous aide à + +10 +00:00:53,280 --> 00:01:00,800 +compléter automatiquement du code commun en science des données. Pour cette tâche, nous aimerions pondérer + +11 +00:01:00,800 --> 00:01:06,960 +plus fortement les échantillons lorsque les tokens liés à la pile de science des données, tels que pandas ou numpy, se produisent plus fréquemment. + +12 +00:01:10,000 --> 00:01:14,788 +Ici, vous voyez une fonction de perte qui fait exactement cela pour la modélisation du langage causal. + +13 +00:01:14,788 --> 00:01:22,800 +Elle prend les entrées du modèle et les logits prédits ainsi que les tokens clés comme entrée. + +14 +00:01:22,800 --> 00:01:30,320 +Les entrées et les logits sont d'abord alignés, puis la perte par échantillon est calculée, suivie des poids. + +15 +00:01:32,320 --> 00:01:35,280 +Enfin, la perte et les poids sont combinés et renvoyés. + +16 +00:01:36,320 --> 00:01:40,480 +Il s'agit d'une fonction assez importante, alors examinons de plus près les blocs de perte et de poids. + +17 +00:01:43,200 --> 00:01:47,920 +Lors du calcul de la perte standard, les logits et les étiquettes sont aplatis sur le batch. + +18 +00:01:48,720 --> 00:01:53,280 +Avec `view`, nous désaplatissons le tenseur pour obtenir une matrice avec une ligne pour chaque + +19 +00:01:53,280 --> 00:01:57,280 +échantillon du batch et une colonne pour chaque position dans la séquence des échantillons. + +20 +00:01:58,720 --> 00:02:03,600 +Nous n'avons pas besoin de la perte par position, nous faisons donc la moyenne de la perte sur toutes les positions pour chaque échantillon. + +21 +00:02:06,000 --> 00:02:10,960 +Pour les poids, nous utilisons la logique booléenne pour obtenir un tenseur avec des 1 là où un mot-clé + +22 +00:02:10,960 --> 00:02:17,840 +apparaît et des 0 là où ce n'est pas le cas. Ce tenseur a une dimension supplémentaire en tant que tenseur de perte que nous venons de voir car + +23 +00:02:17,840 --> 00:02:24,480 +nous obtenons les informations pour chaque mot clé dans une matrice distincte. Nous voulons seulement savoir combien de + +24 +00:02:24,480 --> 00:02:30,320 +fois les mots clés sont apparus par échantillon afin que nous puissions additionner tous les mots clés et toutes les positions par échantillon. + +25 +00:02:33,280 --> 00:02:39,760 +Maintenant nous y sommes presque, nous n'avons qu'à combiner la perte avec le poids par échantillon. Nous faisons cela + +26 +00:02:39,760 --> 00:02:43,920 +avec une multiplication par élément, puis une moyenne sur tous les échantillons du batch. + +27 +00:02:44,720 --> 00:02:48,000 +Au final, nous avons exactement une valeur de perte pour l'ensemble du batch. + +28 +00:02:48,880 --> 00:02:52,800 +Et c'est toute la logique nécessaire pour créer une perte de poids personnalisée. + +29 +00:02:56,080 --> 00:03:02,640 +Voyons comment nous pouvons utiliser cette perte personnalisée avec Accelerate et le Trainer. Dans Accelerate, nous + +30 +00:03:02,640 --> 00:03:07,680 +donnons simplement les `input_ids` au modèle pour obtenir les logits et pouvons ensuite appeler la fonction de perte personnalisée. + +31 +00:03:08,800 --> 00:03:12,800 +Après cela, nous continuons avec la boucle d'entraînement normale en appelant par exemple `backward`. + +32 +00:03:13,840 --> 00:03:19,200 +Pour le Trainer, nous pouvons écraser le calcul de la fonction de perte du Trainer standard. Nous + +33 +00:03:19,200 --> 00:03:23,360 +devons simplement nous assurer que nous renvoyons la perte et les sorties du modèle dans le même format. + +34 +00:03:24,240 --> 00:03:31,840 +Avec cela, vous pouvez intégrer votre propre fonction de perte impressionnante avec Trainer et Accelerate. \ No newline at end of file diff --git a/subtitles/fr/65_data-processing-for-question-answering.srt b/subtitles/fr/65_data-processing-for-question-answering.srt new file mode 100644 index 000000000..cd3969de0 --- /dev/null +++ b/subtitles/fr/65_data-processing-for-question-answering.srt @@ -0,0 +1,154 @@ +1 +00:00:05,569 --> 00:00:10,490 +Étudions comment prétraiter un jeu de données pour répondre aux questions ! + +2 +00:00:10,490 --> 00:00:14,260 +La réponse aux questions consiste à trouver des réponses à une question dans un certain contexte. + +3 +00:00:14,260 --> 00:00:19,970 +Pour notre exemple, nous utiliserons le jeu de données SQUAD, dans lequel nous supprimons les colonnes que nous n'utiliserons pas et + +4 +00:00:19,970 --> 00:00:24,390 +extrairons simplement les informations dont nous aurons besoin pour les étiquettes : le début et la fin de la + +5 +00:00:24,390 --> 00:00:25,390 +réponse dans le contexte. + +6 +00:00:25,390 --> 00:00:30,279 +Si vous avez votre propre jeu de données pour la réponse aux questions, assurez-vous simplement de nettoyer vos données + +7 +00:00:30,279 --> 00:00:34,800 +pour arriver au même point, avec une colonne contenant les questions, une colonne contenant + +8 +00:00:34,800 --> 00:00:39,350 +les contextes, une colonne pour l'index du caractère de début et de fin du répondre + +9 +00:00:39,350 --> 00:00:41,700 +dans le contexte. + +10 +00:00:41,700 --> 00:00:44,610 +Notez que la réponse doit faire partie du contexte. + +11 +00:00:44,610 --> 00:00:48,360 +Si vous souhaitez effectuer une réponse générative aux questions, regardez l'une des vidéos + +12 +00:00:48,360 --> 00:00:50,890 +sur les séquences à séquences dans les liens ci-dessous. + +13 +00:00:50,890 --> 00:00:55,860 +Maintenant, si nous examinons les tokens que nous donnons à notre modèle, nous verrons que la réponse se trouve + +14 +00:00:55,860 --> 00:00:58,450 +quelque part dans le contexte. + +15 +00:00:58,450 --> 00:01:02,239 +Pour un contexte très long, cette réponse peut être tronquée par le tokenizer. + +16 +00:01:02,239 --> 00:01:06,050 +Dans ce cas, nous n'aurons pas d'étiquettes appropriées pour notre modèle. + +17 +00:01:06,050 --> 00:01:11,159 +Nous devons donc conserver la partie tronquée en tant qu'entité distincte au lieu de la supprimer. + +18 +00:01:11,159 --> 00:01:14,720 +La seule chose à laquelle nous devons faire attention est de permettre un certain chevauchement entre des + +19 +00:01:14,720 --> 00:01:19,900 +morceaux séparés afin que la réponse ne soit pas tronquée et que l'entité contenant la réponse + +20 +00:01:19,900 --> 00:01:22,670 +obtienne suffisamment de contexte pour pouvoir la prédire. + +21 +00:01:22,670 --> 00:01:28,790 +Voici comment cela peut être fait par le tokenizer : nous lui passons la question, le contexte, définissons la + +22 +00:01:28,790 --> 00:01:32,750 +troncature pour le contexte uniquement et le rembourrage à la longueur maximale. + +23 +00:01:32,750 --> 00:01:39,590 +L'argument `stride` est l'endroit où nous définissons le nombre de tokens qui se chevauchent, et le + +24 +00:01:39,590 --> 00:01:42,869 +`return_overflowing_tokens=True` signifie que nous ne voulons pas supprimer la partie tronquée. + +25 +00:01:42,869 --> 00:01:47,140 +Enfin, nous renvoyons également les correspondances d'offset pour pouvoir trouver les tokens correspondant + +26 +00:01:47,140 --> 00:01:48,649 +au début et à la fin de la réponse. + +27 +00:01:48,649 --> 00:01:53,990 +Nous voulons ces deux tokens car il y aura les étiquettes que nous transmettrons à notre modèle. + +28 +00:01:53,990 --> 00:01:57,200 +Dans une version en base canonique, voici à quoi ils ressemblent. + +29 +00:01:57,200 --> 00:02:02,119 +Si le contexte que nous avons ne contient pas la réponse, nous définissons les deux étiquettes sur l'index + +30 +00:02:02,119 --> 00:02:04,329 +du token [CLS]. + +31 +00:02:04,329 --> 00:02:08,629 +Nous le faisons également si le contexte ne contient que partiellement la réponse. + +32 +00:02:08,629 --> 00:02:13,950 +En termes de code, voici comment nous pouvons le faire : en utilisant les `séquence_ids` d'une entrée, nous pouvons + +33 +00:02:13,950 --> 00:02:17,390 +déterminer le début et la fin du contexte. + +34 +00:02:17,390 --> 00:02:22,290 +Ensuite, nous savons s'il faut retourner la position [CLS] pour les deux étiquettes ou nous déterminons les positions + +35 +00:02:22,290 --> 00:02:25,120 +des premier et dernier tokens de la réponse. + +36 +00:02:25,120 --> 00:02:28,670 +Nous pouvons vérifier que cela fonctionne correctement sur notre exemple précédent. + +37 +00:02:28,670 --> 00:02:35,319 +Tout mettre ensemble ressemble à cette grande fonction, que nous pouvons appliquer à nos jeux de données avec la méthode `map`. + +38 +00:02:35,319 --> 00:02:40,010 +Puisque nous avons appliqué le rembourrage lors de la tokenisation, nous pouvons ensuite l'utiliser directement dans le Trainer. +39 +00:02:40,010 --> 00:02:43,920 +ou appliquer la méthode `to_tf_dataset` pour utiliser `Keras.fit`. \ No newline at end of file diff --git a/subtitles/fr/66_the-post-processing-step-in-question-answering-(pytorch).srt b/subtitles/fr/66_the-post-processing-step-in-question-answering-(pytorch).srt new file mode 100644 index 000000000..7c7ef2605 --- /dev/null +++ b/subtitles/fr/66_the-post-processing-step-in-question-answering-(pytorch).srt @@ -0,0 +1,135 @@ +1 +00:00:05,680 --> 00:00:12,000 +L'étape de post-traitement d'une tâche de réponse aux questions. Lors de la réponse aux questions, + +2 +00:00:12,000 --> 00:00:17,440 +le traitement du jeu de données initial implique de diviser les exemples en plusieurs caractéristiques, qui + +3 +00:00:17,440 --> 00:00:23,760 +peuvent ou non contenir la réponse. Passer ces caractéristiques à travers le modèle nous donnera des logits pour + +4 +00:00:23,760 --> 00:00:29,280 +les positions de début et de fin, puisque nos étiquettes sont les indices des tokens qui correspondent au + +5 +00:00:29,280 --> 00:00:35,600 +début et à la fin de la réponse. Nous devons ensuite ces logits en une réponse, puis + +6 +00:00:35,600 --> 00:00:40,480 +choisir l'une des différentes réponses que chaque caractéristique donne pour être LA réponse pour un exemple donné. + +7 +00:00:42,080 --> 00:00:46,080 +Pour l'étape de traitement, vous devez vous référer à la vidéo ci-dessous. Ce n'est pas très + +8 +00:00:46,080 --> 00:00:50,240 +différent pour la validation, nous avons juste besoin d'ajouter quelques lignes pour garder une trace de deux choses : + +9 +00:00:51,440 --> 00:00:56,000 +au lieu de supprimer la correspondance de décalage, nous les conservons, et y incluons également les + +10 +00:00:56,000 --> 00:01:01,440 +informations sur l'emplacement du contexte en définissant les décalages de les tokens spéciaux et la question + +11 +00:01:01,440 --> 00:01:06,400 +sur `None`. Puis, nous gardons aussi une trace de l'`exemple_id`` pour chaque caractéristique, + +12 +00:01:06,400 --> 00:01:10,160 +afin de pouvoir faire correspondre la caractéristique aux exemples dont elle est issue. + +13 +00:01:11,680 --> 00:01:15,840 +Si vous ne souhaitez pas calculer la perte de validation, vous n'aurez pas besoin d'inclure tout le code spécial + +14 +00:01:15,840 --> 00:01:21,360 +que nous avons utilisé pour créer les étiquettes. Ceci fait, nous pouvons appliquer cette fonction de prétraitement à l'aide de la + +15 +00:01:21,360 --> 00:01:26,160 +méthode `map`. Nous prenons le jeu de données SQUAD comme dans la vidéo « Traitement des données pour la réponse aux questions ». + +16 +00:01:27,520 --> 00:01:31,920 +Une fois cela fait, l'étape suivante consiste à créer notre modèle. Nous utilisons ici le modèle par défaut du + +17 +00:01:31,920 --> 00:01:36,000 +pipeline de réponses aux questions, mais vous devez utiliser n'importe quel modèle que vous souhaitez évaluer. + +18 +00:01:36,720 --> 00:01:41,200 +Nous allons exécuter une boucle d'évaluation manuelle, nous créons donc un PyTorch DataLoader avec nos caractéristiques. + +19 +00:01:42,240 --> 00:01:46,400 +Avec lui, nous pouvons calculer et rassembler tous les logits de début et de fin comme celui-ci, + +20 +00:01:46,400 --> 00:01:52,240 +avec une boucle d'évaluation PyTorch standard. Ceci fait, nous pouvons vraiment plonger dans le post-traitement. + +21 +00:01:53,680 --> 00:01:57,440 +Nous aurons besoin d'une correspondance des exemples aux caractéristiques, que nous pouvons créer comme ceci. + +22 +00:01:58,560 --> 00:02:02,720 +Maintenant, pour la partie principale du post-traitement, voyons comment extraire une réponse des + +23 +00:02:02,720 --> 00:02:08,480 +logits. Nous pourrions simplement prendre le meilleur indice pour les logits de début et de fin et terminer, mais si notre modèle + +24 +00:02:08,480 --> 00:02:13,200 +prédit quelque chose d'impossible, comme des tokens dans la question, nous examinerons davantage de logits. + +25 +00:02:15,040 --> 00:02:19,120 +Notez que dans le pipeline de réponses aux questions, nous avons attribué un score à chaque réponse en fonction des + +26 +00:02:19,120 --> 00:02:24,560 +probabilités, que nous n'avons pas calculées ici. En termes de logits, la multiplication que nous avions + +27 +00:02:24,560 --> 00:02:31,520 +dans les scores devient une addition. Pour aller vite, nous ne regardons pas tous les logits de début et de fin possibles, + +28 +00:02:31,520 --> 00:02:37,120 +mais les vingt meilleurs suffissent. Nous ignorons les logits qui génèrent des réponses impossibles ou des réponses + +29 +00:02:37,120 --> 00:02:43,040 +trop longues. Comme nous l'avons vu dans le prétraitement, les étiquettes (0, 0) correspondent à aucune réponse, + +30 +00:02:43,040 --> 00:02:48,400 +sinon nous utilisons les décalages pour obtenir la réponse à l'intérieur du contexte. Examinons la + +31 +00:02:48,400 --> 00:02:52,640 +réponse prédite pour la première caractéristique, qui est la réponse avec le meilleur score (ou le meilleur + +32 +00:02:52,640 --> 00:02:58,720 +score logit puisque la SoftMax est une fonction croissante). Le modèle a bien compris ! Ensuite, nous + +33 +00:02:58,720 --> 00:03:03,920 +devons simplement boucler cela pour chaque exemple, en choisissant pour chacun la réponse avec le meilleur score logit dans toutes + +34 +00:03:03,920 --> 00:03:15,440 +les caractéristiques générées par l'exemple. Vous savez maintenant comment obtenir des réponses à partir des prédictions de votre modèle ! \ No newline at end of file diff --git a/subtitles/fr/67_the-post-processing-step-in-question-answering-(tensorflow).srt b/subtitles/fr/67_the-post-processing-step-in-question-answering-(tensorflow).srt new file mode 100644 index 000000000..7c23df8e8 --- /dev/null +++ b/subtitles/fr/67_the-post-processing-step-in-question-answering-(tensorflow).srt @@ -0,0 +1,135 @@ +1 +00:00:05,760 --> 00:00:08,560 +Étape de post-traitement d'une tâche de réponse aux questions. + +2 +00:00:10,640 --> 00:00:14,640 +Lors de la réponse aux questions, le traitement du jeu de données initial + +3 +00:00:14,640 --> 00:00:20,960 +implique de diviser les exemples en plusieurs caractéristiques, qui peuvent ou non contenir la réponse. Passer + +4 +00:00:20,960 --> 00:00:25,680 +ces caractéristiques à travers le modèle nous donnera des logits pour les positions de début et de fin, + +5 +00:00:25,680 --> 00:00:30,640 +puisque nos étiquettes sont les indices des tokens qui correspondent au début et à la fin de la réponse. + +6 +00:00:31,600 --> 00:00:36,560 +Nous devons ensuite convertir ces logits en une réponse, puis choisir l'une des différentes réponses que + +7 +00:00:36,560 --> 00:00:43,280 +chaque caractéristique donne pour être LA réponse pour un exemple donné. Pour l'étape de traitement, vous devez vous + +8 +00:00:43,280 --> 00:00:47,840 +référer à la vidéo ci-dessous. Ce n'est pas très différent pour la validation, nous avons juste besoin d'ajouter + +9 +00:00:47,840 --> 00:00:53,520 +quelques lignes pour garder une trace de deux choses : au lieu de supprimer la correspondance de décalage, nous les conservons, + +10 +00:00:53,520 --> 00:00:58,240 +et y incluons également les informations sur l'emplacement du contexte en définissant les décalages + +11 +00:00:58,240 --> 00:01:04,240 +de les tokens spéciaux et la question à `None`.Puis, nous gardons aussi une trace de l'`exemple_id`` pour + +12 +00:01:04,240 --> 00:01:08,880 +chaque caractéristique, afin de pouvoir faire correspondre la caractéristique aux exemples dont elle est issue. + +13 +00:01:10,240 --> 00:01:14,400 +Si vous ne souhaitez pas calculer la perte de validation, vous n'aurez pas besoin d'inclure tout le code spécial + +14 +00:01:14,400 --> 00:01:19,840 +que nous avons utilisé pour créer les étiquettes. Ceci fait, nous pouvons appliquer cette fonction de prétraitement à l'aide de la + +15 +00:01:19,840 --> 00:01:26,160 +méthode `map`. Nous prenons le jeu de données SQUAD comme dans la vidéo « Traitement des données pour la réponse aux questions ». + +16 +00:01:26,160 --> 00:01:30,560 +Une fois cela fait, l'étape suivante consiste à créer notre modèle. Nous utilisons ici le modèle par défaut du + +17 +00:01:30,560 --> 00:01:34,560 +pipeline de réponses aux questions, mais vous devez utiliser n'importe quel modèle que vous souhaitez évaluer. + +18 +00:01:35,600 --> 00:01:40,560 +Avec la méthode `to_tf_dataset`, nous pouvons simplement envoyer notre jeu de données traité à `model.predict`, + +19 +00:01:41,120 --> 00:01:44,880 +et nous obtenons directement nos logits de début et de fin pour le jeu de données sous forme de tableaux NumPy. + +20 +00:01:45,600 --> 00:01:51,040 +Ceci fait, nous pouvons vraiment plonger dans le post-traitement. Nous aurons besoin d'une correspondance des exemples + +21 +00:01:51,040 --> 00:01:57,040 +aux caractéristiques, que nous pouvons créer comme ceci. Maintenant, pour la partie principale du post-traitement, + +22 +00:01:57,040 --> 00:02:02,080 +voyons comment extraire une réponse des logits. Nous pourrions simplement prendre le meilleur index pour les logits de début et de + +23 +00:02:02,080 --> 00:02:07,680 +fin et terminer, mais si notre modèle prédit quelque chose d'impossible, comme des tokens dans la question, + +24 +00:02:07,680 --> 00:02:13,040 +nous examinerons davantage de logits. Notez que dans le pipeline de réponses aux questions, nous avons attribué un + +25 +00:02:13,040 --> 00:02:18,560 +score à chaque réponse en fonction des probabilités, que nous n'avons pas calculées ici. En termes de logits, + +26 +00:02:18,560 --> 00:02:24,080 +la multiplication que nous avions dans les scores devient une addition. Pour aller vite, nous ne regardons pas tous + +27 +00:02:24,080 --> 00:02:29,040 +les logits de début et de fin possibles, mais les vingt meilleurs. Nous ignorons les logits qui génèrent + +28 +00:02:29,040 --> 00:02:34,240 +des réponses impossibles ou des réponses trop longues. Comme nous l'avons vu dans le prétraitement, les étiquettes (0, + +29 +00:02:34,240 --> 00:02:38,880 +0) correspondent à aucune réponse, sinon nous utilisons les décalages pour obtenir la réponse dans le contexte. + +30 +00:02:39,920 --> 00:02:43,760 +Examinons la réponse prédite pour la première caractéristique, qui est la réponse + +31 +00:02:43,760 --> 00:02:47,680 +avec le meilleur score (ou le meilleur score logit puisque la SoftMax est une fonction croissante). + +32 +00:02:48,480 --> 00:02:54,000 +Le modèle a bien compris ! Ensuite, nous devons simplement boucler cela pour chaque exemple, + +33 +00:02:54,000 --> 00:02:58,880 +choisir pour chacun la réponse avec le meilleur score logit dans toutes les caractéristiques générées par l'exemple. + +34 +00:02:59,840 --> 00:03:03,840 +Vous savez maintenant comment obtenir des réponses à partir des prédictions de votre modèle ! \ No newline at end of file diff --git a/subtitles/fr/68_data-collators-a-tour.srt b/subtitles/fr/68_data-collators-a-tour.srt new file mode 100644 index 000000000..9075987e8 --- /dev/null +++ b/subtitles/fr/68_data-collators-a-tour.srt @@ -0,0 +1,233 @@ +1 +00:00:06,220 --> 00:00:12,290 +Dans beaucoup de nos exemples, vous verrez des assembleurs de données apparaître encore et encore. + +2 +00:00:12,290 --> 00:00:18,010 +Ils sont utilisés à la fois dans les flux de travail PyTorch et TensorFlow, et peut-être même dans JAX, mais personne + +3 +00:00:18,010 --> 00:00:20,260 +ne sait vraiment ce qui se passe dans JAX. + +4 +00:00:20,260 --> 00:00:24,590 +Nous avons une équipe de recherche qui travaille là-dessus, alors peut-être qu'ils nous le diront bientôt. + +5 +00:00:24,590 --> 00:00:27,869 +Mais que sont les assembleurs de données ? + +6 +00:00:27,869 --> 00:00:32,230 +Les assembleurs de données rassemblent les données. Ce n'est pas très utile. + +7 +00:00:32,230 --> 00:00:37,930 +Plus précisément, ils rassemblent une liste d'échantillons dans un seul mini-batch de entraînement. + +8 +00:00:37,930 --> 00:00:41,820 +Pour certaines tâches, l'assembleur de données peut être très simple. + +9 +00:00:41,820 --> 00:00:47,010 +Par exemple, lorsque vous effectuez une classification de séquences, tout ce dont vous avez vraiment besoin de votre assembleur de données, + +10 +00:00:47,010 --> 00:00:53,480 +c'est qu'il rembourre vos échantillons à la même longueur et les concatène en un seul tenseur. + +11 +00:00:53,480 --> 00:00:58,989 +Mais pour d'autres flux de travail, les assembleurs de données peuvent être plus complexes car ils gèrent une partie du + +12 +00:00:58,989 --> 00:01:04,879 +prétraitement nécessaire à cette tâche particulière. Donc quand vous voulez utilisez un assembleur de données, + +13 +00:01:04,879 --> 00:01:09,600 +pour les utilisateurs de PyTorch, vous transmettez généralement le `DataCollator` à votre objet Trainer. + +14 +00:01:09,600 --> 00:01:15,549 +Dans TensorFlow, c'ets un peu différent. Le moyen le plus simple d'utiliser un assembleur de données consiste à le transmettre à la méthode `to_tf_dataset` + +15 +00:01:15,549 --> 00:01:23,700 +de votre jeu de données. Cela vous donne un `tf.dataset` que vous pouvez passer à `fit`. + +16 +00:01:23,700 --> 00:01:27,420 +Vous verrez ces approches utilisées dans les exemples et les notebooks tout au long de ce cours. + +17 +00:01:27,820 --> 00:01:34,360 +Notez que tous nos assembleurs prennent un argument `return_tensors` : vous pouvez le définir sur `"pt"` pour obtenir des + +18 +00:01:34,360 --> 00:01:40,820 +tenseurs PyTorch, `"tf"` pour obtenir des tenseurs TensorFlow ou `"np"` pour obtenir des tableaux Numpy. + +21 +00:01:40,820 --> 00:01:46,060 +Pour des raisons de compatibilité descendante, la valeur par défaut est `"pt"`, donc les utilisateurs de PyTorch n'ont même pas + +22 +00:01:46,060 --> 00:01:51,110 +besoin de définir cet argument la plupart du temps, et sont donc souvent totalement inconscients de l'existence de cette + +23 +00:01:51,110 --> 00:01:52,110 +option. + +24 +00:01:52,110 --> 00:01:59,160 +C'est une leçon précieuse sur la façon dont les bénéficiaires du privilège sont souvent les plus aveugles à son + +25 +00:01:59,160 --> 00:02:00,160 +existence. + +26 +00:02:00,160 --> 00:02:08,130 +Voyons maintenant quelques `DataCollators` spécifiques en action, mais rappelez-vous que si aucun d' + +27 +00:02:08,130 --> 00:02:12,069 +entre eux ne fait ce dont vous avez besoin, vous pouvez toujours écrire le vôtre ! Et ils sont souvent courts. + +28 +00:02:12,069 --> 00:02:17,120 +Tout d'abord, nous verrons les assembleurs de données de base. + +29 +00:02:17,120 --> 00:02:21,550 +Ce sont `DefaultDataCollator` et `DataCollatorWithPadding`. + +30 +00:02:21,550 --> 00:02:25,550 +Ce sont ceux que vous devez utiliser si vos étiquettes sont simples et si vos données ne + +31 +00:02:25,550 --> 00:02:28,780 +nécessitent aucun traitement spécial avant d'être prêtes pour l'entraînement. + +32 +00:02:28,780 --> 00:02:40.480 +Et notez que, comme les différents modèles ont des tokens de rembourrage différents, le collateur de données avec rembourrage aura besoin du tokenizer de votre modèle pour savoir comment rembourrer les séquences correctement. + +33 +00:02:40.480 --> 00:02:49.040 +Le collateur de données par défaut n'a pas besoin d'un tokenizer pour fonctionner mais il produira une erreur si toutes vos séquences ne sont pas de la même longueur. + +34 +00:02:49.040 --> 00:02:51.360 +Vous devez donc être conscient de cela. + +35 +00:02:51.360 --> 00:02:59.280 +Cependant, un grand nombre d'assembleurs de données autres que ceux de base sont généralement conçus pour accomplir une tâche spécifique. + +36 +00:02:59.280 --> 00:03:05.280 +Et donc je vais vous en montrer deux ici, ce sont `DataCollatorForTokenClassification` et `DataCollatorForSeq2Seq`. + +37 +00:03:05.280 --> 00:03:12.640 +Et la raison pour laquelle ces tâches nécessitent des assembleurs spéciaux est que leurs étiquettes sont de longueur variable. + +38 +00:03:12.640 --> 00:03:20.159 +Dans la classification de tokens, il y a une étiquette pour chaque token et donc la longueur des étiquettes est la longueur de la séquence. + +39 +00:03:20.159 --> 00:03:28.080 +Alors qu'en séquence à séquence, les étiquettes sont une séquence de tokens qui peuvent être de longueur variable et très différente de la longueur de la séquence d'entrée. + +40 +00:03:28.080 --> 00:03:38.080 +Dans ces deux cas, nous nous occupons de l'assemblage de ce batch en rembourrant également les étiquettes, comme vous pouvez le voir dans cet exemple. + +41 +00:03:38.080 --> 00:03:40.560 +So inputs and labels will need to be padded. + +42 +00:03:40.560 --> 00:03:53.760 +Si nous voulons réunir des échantillons de longueur variable dans le même mini-batch, c'est toujours le cas pour tous les assembleurs de données et c'est exactement ce que ces assembleurs de données feront pour nous dans le cadre de ces tâches particulières. + +43 +00:03:53.760 --> 00:04:00.000 +Il y a donc un dernier assembleur de données que je veux vous montrer dans cette conférence et c'est le `DataCollatorForLanguageModeling`. + +44 +00:04:00.000 --> 00:04:09.519 +C'est donc très important d'abord parce que les modèles de langage sont tellement fondamentaux pour tout ce que nous faisons en NLP de nos jours. + +45 +00:04:09.519 --> 00:04:14.640 +Ensuite parce qu'il possède deux modes qui font deux choses très différentes. + +46 +00:04:14.640 --> 00:04:26.000 +Vous choisissez donc le mode que vous voulez avec l'argument `mlm`. Mettez-le à `True` pour la modélisation du langage masqué et mettez-le à `False` pour la modélisation du langage causal. + +47 +00:04:26.000 --> 00:04:30.000 +Donc l'assemblage de données pour la modélisation du langage causal est en fait assez simple. + +48 +00:04:30.000 --> 00:04:42.080 +Le modèle fait juste des prédictions sur quel sera le prochain token et donc vos étiquettes sont plus ou moins juste une copie de vos entrées et l'assembleur va gérer cela et s'assurer que les entrées et les étiquettes sont rembourrées correctement. + +49 +00:04:42.080 --> 00:04:48.160 +Cependant, lorsque vous définissez mlm à `True`, vous obtenez un comportement très différent, différent de celui de tout autre assembleur de données. + +50 +00:04:48.160 --> 00:04:58.000 +Et c'est parce que mettre mlm à `True` signifie une modélisation du langage masqué et cela signifie que les étiquettes doivent être masquées. + +51 +00:04:58.000 --> 00:05:00.000 +Alors, à quoi cela ressemble-t-il ? + +52 +00:05:00.000 --> 00:05:14.560 +Rappelez-vous que dans la modélisation du langage masqué, le modèle ne prédit pas le mot suivant, au lieu de cela, nous masquons aléatoirement certains tokens et le modèle les prédit tous en même temps et essaie de remplir les blancs pour ces tokens masqués. + +53 +00:05:14.560 --> 00:05:18.080 +Mais le processus de masquage aléatoire est étonnamment complexe. + +54 +00:05:18.080 --> 00:05:29.840 +Si nous suivons le protocole de l'article original de BERT, nous devons remplacer certains tokens par un token masqué, d'autres tokens par un token aléatoire, puis nous gardons un troisième ensemble de tokens inchangé. + +55 +00:05:29.840 --> 00:05:33.919 +Ce n'est pas le moment d'entrer dans les détails de ce que nous faisons ou pourquoi nous le faisons. + +56 +00:05:33.919 --> 00:05:40.479 +Vous pouvez toujours vérifier l'article original de BERT si vous êtes curieux, il est bien écrit et facile à comprendre. + +57 +00:05:40.479 --> 00:05:46.560 +Ce qu'il faut savoir ici, c'est que la mise en œuvre de ce système par vous-même peut s'avérer très difficile et très complexe. + +58 +00:05:46.560 --> 00:05:57.680 +Mais l'assembleur de données pour la modélisation du langage le fera pour vous si vous mettez mlm à `True` et c'est un exemple de prétraitement plus complexe que certains de nos assembleurs de données font. + +59 +00:05:57.680 --> 00:05:59.280 +Et c'est tout. + +00:05:59.280 --> 00:06:02.560 +Ceci couvre donc les assembleurs de données les plus couramment utilisés et les tâches pour lesquelles ils sont utilisés. + +00:06:02.560 --> 00:06:08.720 +Nous espérons que vous savez maintenant quand utiliser les assembleurs de données et lequel choisir pour votre tâche spécifique. \ No newline at end of file diff --git a/subtitles/fr/69_what-to-do-when-you-get-an-error.srt b/subtitles/fr/69_what-to-do-when-you-get-an-error.srt new file mode 100644 index 000000000..83d7a827f --- /dev/null +++ b/subtitles/fr/69_what-to-do-when-you-get-an-error.srt @@ -0,0 +1,107 @@ +1 +00:00:05,440 --> 00:00:13,760 +Dans cette vidéo, nous allons apprendre les premières choses à faire lorsque vous obtenez une erreur. Pour ne pas jeter votre ordinateur portable par la fenêtre. + +2 +00:00:13,760 --> 00:00:18,320 +Supposons que nous souhaitions utiliser le pipeline de réponse aux questions sur un modèle particulier et que nous obtenions l' + +3 +00:00:18,320 --> 00:00:24,160 +erreur suivante. Les erreurs dans Python peuvent sembler écrasantes car vous obtenez tellement d'informations d'affichées, + +4 +00:00:24,160 --> 00:00:28,160 +mais c'est parce que Python essaie de vous aider du mieux qu'il peut pour résoudre votre problème. + +5 +00:00:28,880 --> 00:00:32,000 +Dans cette vidéo, nous verrons comment interpréter le rapport d'erreur que nous recevons. + +6 +00:00:33,280 --> 00:00:37,920 +La première chose à remarquer tout en haut est que Python vous montre avec une flèche claire la + +7 +00:00:37,920 --> 00:00:42,400 +ligne de code qui a déclenché l'erreur. Ainsi, vous n'avez pas à manipuler votre code et à supprimer + +8 +00:00:42,400 --> 00:00:47,520 +des lignes aléatoires pour déterminer d'où vient l'erreur, vous avez la réponse juste devant vous, ici. + +9 +00:00:48,880 --> 00:00:53,280 +Les flèches que vous voyez ci-dessous sont les parties du code que Python a essayé d'exécuter lors de l'exécution de l' + +10 +00:00:53,280 --> 00:00:59,600 +instruction. Nous sommes ici dans la fonction `pipeline` et l'erreur s'est produite sur cette ligne lors de la + +11 +00:00:59,600 --> 00:01:04,800 +tentative d'exécution de la fonction `check_tasks`, qui a ensuite déclenché l'erreur « KeyError » que nous voyons affichée. + +12 +00:01:06,480 --> 00:01:11,600 +Notez que Python vous indique exactement où les fonctions qu'il exécute vivent. Donc si vous vous sentez + +13 +00:01:11,600 --> 00:01:17,680 +aventureux, vous pouvez même aller inspecter le code source. Tout cela s'appelle le « Traceback ». + +14 +00:01:19,840 --> 00:01:23,600 +Si vous exécutez votre code sur Colab, le « traceback » est automatiquement réduit, + +15 +00:01:23,600 --> 00:01:29,920 +vous devez donc cliquer pour le développer. À sa toute fin, vous obtenez enfin le + +16 +00:01:29,920 --> 00:01:34,960 +message d'erreur réel. La première chose à faire lorsque vous rencontrez une erreur est de lire ce + +17 +00:01:34,960 --> 00:01:40,640 +message d'erreur. Ici, il nous dit qu'il ne connaît pas la tâche de réponse aux questions et + +18 +00:01:40,640 --> 00:01:46,560 +nous donne utilement la liste des tâches prises en charge... dans laquelle nous pouvons voir que la tâche de réponse aux questions est bien présente. + +19 +00:01:47,280 --> 00:01:51,680 +En y regardant de plus près, nous avons utilisé un trait de soulignement pour séparer les deux mots + +20 +00:01:51,680 --> 00:01:55,040 +alors que la tâche est écrite avec un moins. Nous devrions donc corriger cela ! + +21 +00:01:57,280 --> 00:02:02,160 +Maintenant, réessayons notre code avec la tâche correctement écrite. Et que se passe-t-il aujourd'hui ? Encore une + +22 +00:02:02,160 --> 00:02:08,000 +erreur ! Comme nous l'avons vu précédemment, nous allons regarder en bas pour lire le message d'erreur réel. Il nous dit + +23 +00:02:08,000 --> 00:02:13,600 +que nous devons vérifier que notre modèle est un identifiant de modèle correct. Alors allons sur hf.co/models. + +24 +00:02:14,480 --> 00:02:18,320 +Nous pouvons voir notre modèle répertorié ici parmi ceux disponibles pour la réponse aux questions. + +25 +00:02:19,120 --> 00:02:22,480 +La différence est qu'il s'écrit distilBERT avec un « l », + +26 +00:02:22,480 --> 00:02:28,960 +et nous en avons utilisé deux. Alors réparons ça. Nous obtenons enfin nos résultats ! Si votre erreur est plus complexe, + +27 +00:02:28,960 --> 00:02:35,840 +vous devrez peut-être utiliser le débogueur Python, consultez les vidéos liées ci-dessous pour savoir comment ! \ No newline at end of file diff --git a/subtitles/fr/70_using-a-debugger-in-a-notebook.srt b/subtitles/fr/70_using-a-debugger-in-a-notebook.srt new file mode 100644 index 000000000..ac5a65bda --- /dev/null +++ b/subtitles/fr/70_using-a-debugger-in-a-notebook.srt @@ -0,0 +1,111 @@ +1 +00:00:05,280 --> 00:00:11,760 +Utilisation du débogueur Python dans un notebook. Dans cette vidéo, nous allons apprendre à utiliser le + +2 +00:00:11,760 --> 00:00:17,040 +débogueur Python dans un notebook Jupyter ou Colab. Pour cet exemple, nous exécutons le code de + +3 +00:00:17,040 --> 00:00:24,640 +la section de classification des tokens. Téléchargeons le jeu de données Conll. Examinons un peu les données. + +4 +00:00:27,600 --> 00:00:30,080 +Avant de charger un tokenizer pour prétraiter tout le jeu de données. + +5 +00:00:32,640 --> 00:00:35,440 +Consultez la section du cours liée ci-dessous pour plus d'informations. + +6 +00:00:36,800 --> 00:00:44,240 +Une fois cela fait, nous essayons de regrouper certaines fonctionnalités du jeu de données d'entraînement avec `tokenizer.pad` + +7 +00:00:44,960 --> 00:00:51,280 +Et nous obtenons l'erreur suivante. Nous utilisons PyTorch ici avec `return_tensors="pt"`, mais + +8 +00:00:51,280 --> 00:00:56,240 +vous obtiendrez la même erreur avec TensorFlow. Comme nous l'avons vu dans la vidéo « Comment déboguer une erreur ? », + +9 +00:00:56,240 --> 00:01:02,480 +le message d'erreur est à la fin du « Traceback » et il indique que nous devrions utiliser le rembourrage, ce que nous + +10 +00:01:02,480 --> 00:01:07,680 +essayons en fait de faire. Ce n'est donc pas utile du tout et nous devrons aller un peu plus loin pour déboguer le problème. + +11 +00:01:08,400 --> 00:01:13,040 +Heureusement, vous pouvez utiliser le débogueur Python chaque fois que vous obtenez une erreur dans un notebook Jupyter + +12 +00:01:13,040 --> 00:01:23,680 +en saisissant `%debug` dans n'importe quelle cellule. N'oubliez pas le « % » au début. Lors de l'exécution de cette cellule, vous accédez tout en bas du « traceback » + +13 +00:01:23,680 --> 00:01:28,560 +où vous pouvez saisir des commandes pouvant aider à debogger votre script. Les deux premières commandes que vous devez + +14 +00:01:28,560 --> 00:01:41,760 +apprendre sont « u » et « d » (pour monter et descendre). Tapez « u » puis « entrée » vous fait monter d'une étape dans le « Traceback ». Tapez « d » puis « entrée » vous fait descendre d'une étape dans le « Traceback ». + +15 +00:01:43,920 --> 00:01:46,720 +En remontant deux fois, nous arrivons au point où l'erreur a été atteinte. + +16 +00:01:47,600 --> 00:01:53,840 +La troisième commande à apprendre est « p », pour « print ». Elle vous permet d'imprimer n'importe quelle valeur que vous voulez. + +17 +00:01:54,560 --> 00:02:02,720 +Par exemple, en tapant « p return_tensors » et « entrée » montre « pt » que nous avons renseigné dans la fonction `pad`. + +18 +00:02:02,720 --> 00:02:12,720 +On peut aussi regarder ce qui se trouve dans l'objet `batch_outputs`. Le dictionnaire de `batch_outputs` est un peu difficile à voir, + +19 +00:02:12,720 --> 00:02:18,160 +alors plongeons-y dans de plus petits morceaux. À l'intérieur du débogueur, vous pouvez non seulement imprimer n'importe quelle variable, + +20 +00:02:18,160 --> 00:02:28,240 +mais également évaluer n'importe quelle expression. Par exemple, on peut regarder ce que contient l'objet `batch_outputs["inputs_ids"]` ou encore l'objet `batch_outputs["labels"]` + +21 +00:02:35,440 --> 00:02:41,360 +Ces étiquettes sont vraiment bizarres : elles sont de différentes tailles, ce que nous pouvons en fait confirmer en + +22 +00:02:41,360 --> 00:02:49,840 +imprimant les tailles avec une liste compressée. + +23 +00:02:52,160 --> 00:02:56,880 +C'est parce que la méthode `pad` du tokenizer ne s'occupe que de la sortie du tokenizer : + +24 +00:02:56,880 --> 00:02:59,680 +`input_ids`, `attention_mask` et `token_type_ids`. + +25 +00:03:00,240 --> 00:03:03,840 +Nous devons donc rembourrer nous-mêmes les étiquettes avant d'essayer de créer un tenseur avec elles. + +26 +00:03:05,040 --> 00:03:11,440 +Une fois que vous êtes prêt à quitter le débogueur Python, vous pouvez appuyer sur « q » puis « entrée » pour quitter. Une façon de corriger l'erreur + +27 +00:03:11,440 --> 00:03:21,600 +consiste à rembourrer manuellement toutes les étiquettes à la plus longue. Une autre façon, est d'utiliser un assembleur de données spécialement pour la classification de tokens. + +28 +00:03:21,600 --> 00:03:26,000 +Vous pouvez également le débogueur Python directement dans le terminal. Regardez la vidéo en description pour savoir comment \ No newline at end of file diff --git a/subtitles/fr/71_using-a-debugger-in-a-terminal.srt b/subtitles/fr/71_using-a-debugger-in-a-terminal.srt new file mode 100644 index 000000000..16b0b3086 --- /dev/null +++ b/subtitles/fr/71_using-a-debugger-in-a-terminal.srt @@ -0,0 +1,127 @@ +1 +00:00:05,840 --> 00:00:11,520 +Utilisation du débogueur Python dans un terminal. Dans cette vidéo, nous allons apprendre à utiliser le débogueur Python + +2 +00:00:11,520 --> 00:00:16,800 +dans un terminal. Pour cet exemple, nous exécutons le code de la section de classification de tokens, + +3 +00:00:17,600 --> 00:00:22,320 +téléchargeant le jeu de données Conll avant de charger un tokenizer pour le prétraiter. + +4 +00:00:23,200 --> 00:00:28,720 +Consultez la section du cours liée ci-dessous pour plus d'informations. Une fois cela fait, nous + +5 +00:00:28,720 --> 00:00:34,240 +essayons de regrouper certaines fonctionnalités du jeu de données d'entraînement en les rembourrant et en renvoyant un tenseur. + +6 +00:00:37,200 --> 00:00:40,160 +Si nous essayons d'exécuter notre script dans le terminal, nous obtenons l'erreur suivante. + +7 +00:00:42,800 --> 00:00:47,280 +Notez que nous utilisons PyTorch ici, mais vous obtiendrez la même erreur avec TensorFlow. + +8 +00:00:49,280 --> 00:00:53,680 +Comme nous l'avons vu dans la vidéo « Comment déboguer une erreur ? », le message d'erreur est à la fin + +9 +00:00:53,680 --> 00:00:58,640 +et indique que nous devrions utiliser le rembourrage... ce que nous essayons actuellement de faire. Ce n'est donc pas + +10 +00:00:58,640 --> 00:01:03,360 +utile et nous devrons aller un peu plus loin pour déboguer le problème. Heureusement, vous pouvez utiliser + +11 +00:01:03,360 --> 00:01:10,400 +le débogueur Python assez facilement dans un terminal en lançant votre script avec « python -m pdb » puis le nom de script d'entraînement. + +12 +00:01:10,400 --> 00:01:17,200 +Lors de l'exécution de cette commande, vous êtes renvoyé à la première instruction de votre script. + +13 +00:01:17,200 --> 00:01:28,840 +Vous pouvez exécuter uniquement l'instruction suivante en saisissant « n » puis « entrée » ou continuer jusqu'à l'erreur en saisissant directement « c » puis « entrée ». + +14 +00:01:29,680 --> 00:01:33,120 +Une fois là, vous allez tout en bas du « traceback » et vous pouvez saisir des commandes. + +15 +00:01:34,000 --> 00:01:40,160 +Les deux premières commandes que vous devez apprendre sont « u » et « d » (pour monter et descendre), qui vous permettent de monter dans + +16 +00:01:40,160 --> 00:01:48,320 +le « Traceback » ou vers le bas. En remontant deux fois, nous arrivons au point où l'erreur a été atteinte. La troisième commande + +17 +00:01:48,320 --> 00:01:54,000 +à apprendre est « p » , pour « print ». Elle vous permet d'imprimer la valeur de votre choix. Par exemple ici, nous pouvons voir + +18 +00:01:54,000 --> 00:01:59,120 +la valeur de `return_tensors` ou `batch_outputs` pour essayer de comprendre ce qui a déclenché l'erreur. + +19 +00:02:00,000 --> 00:02:04,720 +Le dictionnaire de `batch_outputs` est un peu difficile à voir, alors plongeons-y dans de plus petits morceaux. + +20 +00:02:05,360 --> 00:02:10,560 +Dans le débogueur, vous pouvez non seulement imprimer n'importe quelle variable, mais également évaluer n'importe quelle expression, + +21 +00:02:10,560 --> 00:02:23,600 +afin que nous puissions examiner indépendamment les entrées ou les étiquettes. Ces étiquettes sont vraiment bizarres : + +22 +00:02:24,160 --> 00:02:27,920 +elles sont de différentes tailles, ce que nous pouvons en fait confirmer en imprimant les tailles avec une liste compressée. + +23 +00:02:35,760 --> 00:02:40,160 +Pas étonnant que le tokenizer n'ait pas été en mesure de créer un tenseur avec eux ! C'est parce que la + +24 +00:02:40,160 --> 00:02:45,840 +méthode `pad` ne s'occupe que des sorties du tokenizer : `input_ids`, `attention_mask`, `token_type_ids`. + +25 +00:02:46,400 --> 00:02:50,080 +Nous devons donc rembourrer nous-mêmes les étiquettes avant d'essayer de créer un tenseur avec elles. + +26 +00:02:51,120 --> 00:02:56,880 +Une fois que vous êtes prêt à quitter le débogueur Python, vous pouvez appuyer sur « q » pour quitter puis « entrée ». Une autre + +27 +00:02:56,880 --> 00:03:11,000 +manière d'accéder au débogueur Python et de mettre un point de cassage dans notre script. Nous pouvons faire cela en utilisant la méthode `pdb.set_trace` du moment que nous importons le module pdb au début de notre script. + +28 +00:03:11,000 --> 00:03:23,280 +On sauvegarde et relance notre script avec `python`. Cela interrompra l'exécution au point d'erreur. + +29 +00:03:23,280 --> 00:03:32,080 +Nous pourrons inspecter toutes les variables avant que la prochaine instruction ne soit exécutée, par exemple, ici `features`. Taper « n » puis « entrée » exécute + +30 +00:03:32,080 --> 00:03:37,280 +l'instruction suivante, qui nous ramène à l'intérieur du « traceback ». Une façon de corriger l'erreur consiste + +31 +00:03:37,280 --> 00:03:49,760 +à rembourrer manuellement toutes les étiquettes jusqu'à la plus longue. Une autre façon est d'utiliser le assembleur de données pour la classification de tokens. + +32 +00:03:49,760 --> 00:03:53,760 +Si vous voulez apprendre comment utiliser le débogueur Python dans un notebook, regardez le lien ci-dessous. \ No newline at end of file diff --git a/subtitles/fr/72_asking-for-help-on-the-forums.srt b/subtitles/fr/72_asking-for-help-on-the-forums.srt new file mode 100644 index 000000000..bb4ca3084 --- /dev/null +++ b/subtitles/fr/72_asking-for-help-on-the-forums.srt @@ -0,0 +1,143 @@ +1 +00:00:05,520 --> 00:00:08,080 +Comment poser une question sur les forums d'Hugging Face ? + +2 +00:00:09,840 --> 00:00:15,360 +Si vous avez une question d'ordre général ou si vous cherchez à déboguer votre code, les forums sont l'endroit idéal pour la poser. + +3 +00:00:15,360 --> 00:00:17,760 +Dans cette vidéo, nous vous apprendrons à rédiger une bonne question + +4 +00:00:17,760 --> 00:00:20,080 +afin de maximiser les chances d'obtenir une réponse. + +5 +00:00:21,360 --> 00:00:25,120 +Tout d'abord, pour vous connecter aux forums, vous avez besoin d'un compte Hugging Face. + +6 +00:00:25,680 --> 00:00:32,560 +Si vous n'en avez pas encore créé, accédez à hf.co et cliquez sur « Sign Up ». Il y a aussi un lien direct ci-dessous. + +7 +00:00:33,520 --> 00:00:34,880 +Saisissez votre adresse e-mail et votre mot de passe, + +8 +00:00:34,880 --> 00:00:38,480 +puis continuez les étapes pour choisir un nom d'utilisateur et mettre à jour une photo de profil. + +9 +00:00:39,440 --> 00:00:43,040 +Une fois cela fait, accédez à discut.huggingface.co + +10 +00:00:43,040 --> 00:00:48,480 +(lien ci-dessous) et cliquez sur « Login ». Utilisez les mêmes informations de connexion que pour le site web d'Hugging Face. + +11 +00:00:49,600 --> 00:00:53,840 +Vous pouvez effectuer une recherche dans les forums en cliquant sur la loupe. Quelqu'un a peut-être déjà posé + +12 +00:00:53,840 --> 00:00:59,040 +votre question dans un sujet ! Si vous constatez que vous ne pouvez pas publier un nouveau sujet en tant que nouvel utilisateur, c'est peut-être à cause + +13 +00:00:59,040 --> 00:01:04,480 +des filtres antispam. Assurez-vous de passer du temps à lire les sujets existants pour le désactiver. + +14 +00:01:05,120 --> 00:01:09,440 +Lorsque vous êtes sûr que votre question n'a pas encore été posée, cliquez sur le bouton « New Topic ». + +15 +00:01:09,440 --> 00:01:11,840 +Pour cet exemple, nous utiliserons le code suivant, + +16 +00:01:12,400 --> 00:01:16,320 +qui génère une erreur, comme nous l'avons vu dans la vidéo « Que faire lorsque vous obtenez une erreur ? ». + +17 +00:01:18,080 --> 00:01:22,400 +La première étape consiste à choisir une catégorie pour notre nouveau sujet. Étant donné que notre erreur concerne la + +18 +00:01:22,400 --> 00:01:29,040 +bibliothèque Transformers, nous choisissons cette catégorie. Ensuite, choisissez un titre qui résume bien votre erreur. + +19 +00:01:29,680 --> 00:01:33,040 +Ne soyez pas trop vague, sinon les utilisateurs qui obtiendront la même erreur que vous à l'avenir + +20 +00:01:33,040 --> 00:01:37,600 +ne pourront pas trouver votre sujet. Une fois que vous avez fini de saisir le titre de votre sujet, + +21 +00:01:38,160 --> 00:01:41,680 +assurez-vous que la question n'a pas trouvé de réponse dans les sujets que Discourse vous propose. + +22 +00:01:42,480 --> 00:01:44,960 +Cliquez sur la croix pour supprimer cette fenêtre après avoir vérifié. + +23 +00:01:46,000 --> 00:01:50,880 +Voici un exemple de ce qu'il ne faut pas faire lors de la publication d'une erreur : le message est très + +24 +00:01:50,880 --> 00:01:55,120 +vague, personne d'autre ne pourra donc deviner ce qui s'est passé pour vous, et il tague trop de personnes. + +25 +00:01:56,320 --> 00:02:00,560 +Taguer des personnes (en particulier des modérateurs) peut avoir l'effet inverse de ce que vous souhaitez. + +26 +00:02:01,200 --> 00:02:05,520 +Lorsque vous leur envoyez une notification (et qu'ils en reçoivent beaucoup), ils ne prendront probablement pas la peine + +27 +00:02:05,520 --> 00:02:10,480 +de vous répondre, et les utilisateurs que vous n'avez pas tagués ignoreront probablement la question car ils voient les utilisateurs tagués. + +28 +00:02:11,200 --> 00:02:15,520 +N'identifiez un utilisateur que lorsque vous êtes absolument certain qu'il est le mieux placé pour répondre à votre question. + +29 +00:02:17,520 --> 00:02:21,760 +Soyez précis dans votre texte, et si vous rencontrez une erreur provenant d'un morceau de code spécifique, + +30 +00:02:21,760 --> 00:02:27,760 +incluez ce code dans votre message. Pour vous assurer que votre message a l'air bien, placez votre question entre trois + +31 +00:02:27,760 --> 00:02:32,720 +« backticks » comme celui-ci. Vous pouvez vérifier à droite comment votre message apparaîtra une fois publié. + +32 +00:02:34,080 --> 00:02:39,040 +Si votre question concerne une erreur, il est encore mieux d'inclure le « Traceback » complet. Comme expliqué + +33 +00:02:39,040 --> 00:02:44,560 +dans la vidéo « Que faire lorsque vous obtenez une erreur ? », développez le « Traceback » si vous êtes sur Colab. + +34 +00:02:44,560 --> 00:02:50,320 +Comme pour le code, placez-le entre deux lignes contenant trois « backticks » pour un formatage correct. Notre dernier + +35 +00:02:50,320 --> 00:02:55,120 +conseil est de vous rappeler d'être gentil, un s'il vous plaît et un merci iront loin dans + +36 +00:02:55,120 --> 00:03:03,840 +demander à d'autres de vous aider. Avec tout cela fait correctement, votre question devrait recevoir une réponse assez rapidement ! \ No newline at end of file diff --git a/subtitles/fr/73_debugging-the-training-pipeline-(pytorch).srt b/subtitles/fr/73_debugging-the-training-pipeline-(pytorch).srt new file mode 100644 index 000000000..11ffe4309 --- /dev/null +++ b/subtitles/fr/73_debugging-the-training-pipeline-(pytorch).srt @@ -0,0 +1,171 @@ +1 +00:00:06,080 --> 00:00:10,960 +Dans cette vidéo, nous verrons comment déboguer une erreur que vous rencontrez lors de l'exécution de `trainer.train()`. + +2 +00:00:12,240 --> 00:00:17,280 +À titre d'exemple, nous utiliserons ce script qui finetune un modèle BERT sur le jeu de données GLUE MNLI. + +3 +00:00:17,840 --> 00:00:20,880 +Consultez les vidéos liées ci-dessous pour voir comment nous sommes arrivés à un tel script, + +4 +00:00:21,680 --> 00:00:26,960 +ici, nous voulons apprendre à déboguer les problèmes qu'il contient. L'exécution du script génère une erreur assez + +5 +00:00:26,960 --> 00:00:31,920 +rapidement. Cela se produit à la ligne où nous donnons les entrées au modèle, selon le « Traceback ». + +6 +00:00:32,640 --> 00:00:36,720 +Cela nous indique qu'il y a un problème, mais le problème peut provenir de nombreuses causes différentes. + +7 +00:00:37,520 --> 00:00:41,600 +Pour déboguer une erreur dans une entraînement, vous devez vous assurer que chaque étape du pipeline d'entraînement + +8 +00:00:41,600 --> 00:00:46,160 +fonctionne comme prévu. Cela signifie vérifier que les entrées de votre jeu de données sont correctes, + +9 +00:00:46,800 --> 00:00:50,560 +vous pouvez les regrouper, les donner au modèle pour obtenir une perte, + +10 +00:00:50,560 --> 00:00:54,080 +puis calculer les gradients de cette perte avant d'effectuer une étape d'optimisation. + +11 +00:00:55,280 --> 00:01:00,480 +Commençons donc par examiner le jeu de données d'entraînement utilisé par ce `trainer`. Il y a certainement un + +12 +00:01:00,480 --> 00:01:07,040 +problème car nous voyons des textes et non des chiffres. Le message d'erreur nous indiquait que le modèle n'avait pas reçu + +13 +00:01:07,040 --> 00:01:13,120 +d'`input_ids` et que nous n'en avons pas dans le jeu de données. En examinant notre code, nous pouvons constater que nous avons + +14 +00:01:13,120 --> 00:01:18,800 +fait une erreur et transmis les mauvais jeux de données au Trainer. Alors réparons cela et exécutons à nouveau. + +15 +00:01:20,240 --> 00:01:25,680 +Nous avons maintenant une nouvelle erreur. L'inspection du « Traceback » nous indique que cela se produit lorsque nous essayons de créer un batch, + +16 +00:01:25,680 --> 00:01:31,920 +spécifiquement pour regrouper les caractéristiques dans un tenseur. Nous pouvons le confirmer en demandant au Trainer de + +17 +00:01:31,920 --> 00:01:37,600 +nous fournir un batch du chargeur de données d'entraînement, qui reproduit la même erreur. Soit en inspectant + +18 +00:01:37,600 --> 00:01:43,600 +les entrées, soit en déboguant, nous pouvons alors voir qu'elles ne sont pas toutes de la même taille. En effet, nous n'avons + +19 +00:01:43,600 --> 00:01:48,240 +pas passé d'assembleur de données pour effectuer le rembourrage dans le Trainer et nous n'avons pas non plus rembourré + +20 +00:01:48,240 --> 00:01:53,440 +le prétraitement des données. Le rembourrage à l'intérieur du Trainer est normalement la valeur par défaut, mais uniquement si vous fournissez + +21 +00:01:53,440 --> 00:01:58,800 +votre tokenizer au Trainer, et nous avons oublié de le faire. Alors résolvons le problème et recommençons. + +22 +00:02:00,320 --> 00:02:06,400 +Cette fois, nous obtenons une méchante erreur CUDA. Elles sont très difficiles à déboguer car d'une part, + +23 +00:02:07,120 --> 00:02:11,280 +elles mettent votre noyau dans un état irrécupérable (vous devez donc redémarrer votre + +24 +00:02:11,280 --> 00:02:15,840 +notebook depuis le début) et d'autre part, le « Traceback » est complètement inutile pour celles-là. + +25 +00:02:16,800 --> 00:02:22,240 +Ici, le « Traceback » nous indique que l'erreur se produit lorsque nous effectuons le calcul du gradient avec `loss.backward`, + +26 +00:02:22,240 --> 00:02:27,840 +mais comme nous le verrons plus tard, ce n'est pas le cas. En effet, tout ce qui se passe + +27 +00:02:27,840 --> 00:02:33,520 +sur le GPU est effectué de manière asynchrone : lorsque vous exécutez l'appel de modèle, ce que fait le programme + +28 +00:02:33,520 --> 00:02:39,280 +est simplement d'empiler cela dans la file d'attente du GPU, puis (si le GPU n'avait aucune tâche en cours à faire), + +29 +00:02:39,280 --> 00:02:43,920 +le travail commencera sur le GPU en même temps que le CPU passera à l'instruction suivante. + +30 +00:02:44,800 --> 00:02:50,000 +En continuant avec l'extraction de la perte, celle-ci est empilée dans la file d'attente du GPU pendant que le CPU passe + +31 +00:02:50,000 --> 00:02:54,960 +à l'instruction `loss.backward`. Mais le GPU n'a toujours pas terminé la passe avant du + +32 +00:02:54,960 --> 00:03:01,760 +modèle puisque tout cela n'a pris aucun temps. Le CPU arrête d'avancer, car `loss.backward` en tant + +33 +00:03:01,760 --> 00:03:09,360 +qu'instruction lui indiquant d'attendre que les GPU soient finis pour être sûr que les gradients sont corrects. Et lorsque le GPU rencontre une erreur, + +34 +00:03:09,360 --> 00:03:15,040 +il renvoie un message au CPU, qui génère l'erreur au mauvais endroit. + +35 +00:03:16,080 --> 00:03:20,320 +Donc, pour déboguer cela, nous devrons exécuter les prochaines étapes du pipeline d'entraînement sur le CPU. + +36 +00:03:20,960 --> 00:03:26,320 +C'est très facile à faire, et nous obtenons un « Traceback » auquel nous pouvons faire confiance cette fois. Comme nous l'avons déjà dit, + +37 +00:03:26,320 --> 00:03:32,720 +l'erreur se produit lors de la passe avant du modèle et nons dans la passe arrière. C'est une erreur d'index. + +38 +00:03:33,360 --> 00:03:38,800 +Avec un peu de débogage, nous voyons que nous avons des étiquettes allant de 0 à 2, donc trois valeurs différentes, + +39 +00:03:38,800 --> 00:03:44,240 +mais nos sorties ont une forme de taille de batch par 2. Il semble que notre modèle ait le mauvais nombre + +40 +00:03:44,240 --> 00:03:50,320 +d'étiquettes ! Nous pouvons en effet le confirmer, et maintenant que nous savons qu'il est facile de le corriger dans le code en ajoutant + +41 +00:03:50,320 --> 00:03:58,720 +`num_labels=3` lorsque nous créons le modèle. Le script d'entraînement va maintenant s'exécuter jusqu'à la fin ! Nous n'en avions pas + +42 +00:03:58,720 --> 00:04:02,640 +encore besoin, mais voici comment déboguer l'étape suivante du pipeline, le calcul du gradient, + +43 +00:04:03,360 --> 00:04:13,840 +ainsi que l'étape d'optimisation. Avec tout cela, bonne chance pour déboguer vos propres entraînements ! \ No newline at end of file diff --git a/subtitles/fr/74_debugging-the-training-pipeline-(tensorflow).srt b/subtitles/fr/74_debugging-the-training-pipeline-(tensorflow).srt new file mode 100644 index 000000000..03086b10b --- /dev/null +++ b/subtitles/fr/74_debugging-the-training-pipeline-(tensorflow).srt @@ -0,0 +1,251 @@ +1 +00:00:04,720 --> 00:00:09,280 +Certains bogues dans votre code sont très simples. Vous essayez de l'exécuter, + +2 +00:00:09,280 --> 00:00:14,720 +vous obtenez une erreur de syntaxe quelque part, Python vous indique exactement où et vous la corrigez. C'est + +3 +00:00:14,720 --> 00:00:22,240 +génial : c'est simple et satisfaisant. Parfois, cependant, les choses se bloquent et l'erreur est impossible + +4 +00:00:22,240 --> 00:00:27,360 +à comprendre. Cela se produit souvent en apprentissage automatique pour plusieurs raisons : vous travaillez avec + +5 +00:00:27,360 --> 00:00:33,920 +des structures de données volumineuses, utilisez des bibliothèques volumineuses et complexes avec de nombreuses pièces mobiles, et vous faites également + +6 +00:00:33,920 --> 00:00:41,600 +beaucoup de calcul GPU qui sont en génrénal les plus durs à corriger. Dans Keras, il y a le problème supplémentaire que vos modèles sont souvent compilés + +7 +00:00:41,600 --> 00:00:46,080 +avant l'exécution, ce qui est excellent pour les performances mais rend leur débogage très difficile. + +8 +00:00:47,920 --> 00:00:56,160 +Ce sera une vidéo sur ce qu'il faut faire lorsque vous rencontrez l'un de ces bugs cauchemardesques et avez aucune idée de comment les déboguer. + +9 +00:00:56,400 --> 00:01:07,600 +Pour vous donner une idée des choses les plus courantes de ce qui peut mal tourner et où chercher la source des bogues que + +10 +00:01:07,600 --> 00:01:13,360 +vous rencontrez, utilisons cet exemple de script. Je vais vous le montrer ici en deux parties. Tout d'abord, + +11 +00:01:13,360 --> 00:01:19,280 +nous effectuons toutes nos importations, nous chargeons un jeu de données, nous créons notre tokenizer et nous tokenisons le jeu de données. + +12 +00:01:20,160 --> 00:01:28,320 +Ensuite, nous convertissons nos jeux de données en jeux de données TensorFlow, c'est le `to_tf_dataset`, afin de pouvoir leur appliquer `fit()`, + +13 +00:01:28,320 --> 00:01:34,640 +puis nous chargeons notre modèle à partir d'un checkpoint pré-entraîné, le compilons et pouvons appliquer `fit` sur le jeu de données. Cela semble + +14 +00:01:34,640 --> 00:01:42,880 +assez simple, similaire à ce qu'on a vu dans le cours avant, mais attention ! Ce code effrayant cache de nombreux secrets sombres et mystérieux. + +15 +00:01:43,760 --> 00:01:52,880 +Que se passe-t-il lorsque nous l'exécutons ? Eh bien, ce n'est pas génial. Nous obtenons ce message d'erreur. Qu'est-ce que cela signifie? Nous avons essayé de nous entraîner + +16 +00:01:52,880 --> 00:01:59,600 +sur nos données, mais nous n'avons pas obtenu de gradient ? C'est assez déroutant. Comment pouvons-nous même commencer à déboguer + +17 +00:02:00,400 --> 00:02:04,880 +quelque chose comme ça ? Lorsque l'erreur que vous obtenez ne suggère pas immédiatement où se situe le problème, + +18 +00:02:05,440 --> 00:02:11,040 +la meilleure solution consiste souvent à parcourir les éléments dans l'ordre, en s'assurant à chaque étape + +19 +00:02:11,040 --> 00:02:19,120 +que tout semble correct. Que tout est ok à ce point. Et bien sûr, le point de départ consiste toujours à vérifier vos données. + +20 +00:02:20,720 --> 00:02:28,960 +La meilleure façon de le faire est de récupérer un batch à partir de `tf.data.Dataset` sur lequel votre modèle s'entraîne, + +21 +00:02:30,560 --> 00:02:41,840 +juste à la fin du pipeline d'entraînement. Et si votre sortie est bonne, cela garanti que votre pipeline de données fonctionne bien. Et nous pouvons faire cela comme ça, en bouclant + +22 +00:02:41,840 --> 00:02:50,320 +le jeu de données pendant une itération, puis en interrompant. Alors, qu'obtenons-nous lorsque nous inspectons ce batch ? + +23 +00:02:50,320 --> 00:02:54,800 +Nous constatons que nous n'obtenons aucun gradient car nous ne transmettons pas d'étiquettes à Keras ! + +24 +00:02:55,520 --> 00:03:00,800 +Nos étiquettes se trouvent dans le batch, mais il s'agit d'une clé dans le dictionnaire d'entrée, et non d'une étiquette distincte. + +25 +00:03:02,400 --> 00:03:06,160 +Il s'agit de l'un des problèmes les plus courants que vous rencontrerez lors de l'entraînement de vos transformers + +26 +00:03:06,160 --> 00:03:12,960 +avec TensorFlow. Nos modèles peuvent tous calculer la perte en interne, mais pour utiliser cette perte pour l'entraînement, + +27 +00:03:12,960 --> 00:03:16,880 +les étiquettes doivent être transmises dans le dictionnaire d'entrée, où le modèle peut les voir. + +28 +00:03:17,760 --> 00:03:23,200 +Cette perte interne est la perte que nous utilisons lorsque nous ne spécifions pas de valeur de perte à `compile()`. + +29 +00:03:26,640 --> 00:03:30,960 +Keras, d'autre part, s'attend généralement à ce que les étiquettes soient transmises séparément du + +30 +00:03:30,960 --> 00:03:36,720 +dictionnaire d'entrée et qu'elles ne soient pas visibles par le modèle, et les calculs de perte échouent généralement si vous + +31 +00:03:36,720 --> 00:03:43,040 +ne le faites pas. Nous devons choisir l'un ou l'autre. Soit nous utilisons la perte interne du modèle et conservons + +32 +00:03:43,040 --> 00:03:49,120 +les étiquettes là où elles se trouvent, soit nous continuons à utiliser les pertes de Keras, mais nous déplaçons les étiquettes à l'endroit où Keras les + +33 +00:03:49,120 --> 00:03:57,680 +attend. Pour plus de simplicité, utilisons les pertes internes du modèle, en supprimant l'argument + +34 +00:03:57,680 --> 00:04:06,000 +de perte lors de l'appel de `compile()`. Donc que se passe-t-il si nous essayons ré-entraînons. Et bien nous recompilons en appelant `model.fit()` à nouveau. + +35 +00:04:06,560 --> 00:04:13,840 +Que se passe t'il ? Eh bien, il fonctionne cette fois... mais maintenant nous obtenons une perte de `nan`. Ce n'est pas bon. + +36 +00:04:16,240 --> 00:04:22,160 +`nan` signifique « not a number » et ce n'est pas une bonne perte à avoir en général. En fait, si nous inspectons notre modèle maintenant, nous verrons que non seulement toutes + +37 +00:04:22,160 --> 00:04:30,640 +les sorties sont `nan` , mais que toutes les poids sont également `nan` comme pour la perte. Une fois qu'un seul `nan` se glisse dans vos calculs, + +38 +00:04:30,640 --> 00:04:37,280 +il a tendance à se propager, car il se propage depuis la perte jusqu'à votre gradient + +39 +00:04:37,280 --> 00:04:48,320 +puis dans les mises à jour de poids et donc à chaque mise à jour vous avez des `nan`. Donc `nan` détruit notre modèle. Mais où s'est-il glissé en premier ? + +40 +00:04:49,600 --> 00:04:57,760 +Pour le savoir, nous devons réinitialiser le modèle et examiner les résultats du premier batch uniquement. + +41 +00:04:58,400 --> 00:05:04,160 +Et lorsque nous faisons cela, nous voyons que `nan` apparaît d'abord dans la perte, mais seulement dans certains échantillons ! + +42 +00:05:04,960 --> 00:05:08,480 +Vous pouvez voir cela plus en détail dans la section du cours qui nous accompagneme, je vais très vite ici. + +43 +00:05:11,040 --> 00:05:17,120 +Mais nous constatons que si nous regardons les étiquettes, les échantillons avec une perte de `nan` ont tous une étiquette de 2 ! + +44 +00:05:17,760 --> 00:05:24,400 +Cela nous donne un indice très fort. Si nous vérifions le modèle, avec `model.config.num_labels`, nous voyons que + +45 +00:05:24,400 --> 00:05:30,080 +le modèle pense qu'il n'y a que 2 étiquettes, mais si nous voyons une valeur de 2, cela signifie qu'il y a au moins + +46 +00:05:30,080 --> 00:05:36,400 +3 étiquettes, car 0 c'est aussi une étiquette ! Nous avons donc perdu `nan` parce que nous avons obtenu une étiquette impossible dans notre jeu de données + +47 +00:05:36,400 --> 00:05:43,040 +Pour résoudre ce problème, nous devons revenir en arrière et configurer le modèle pour qu'il ait le bon nombre d'étiquettes. + +48 +00:05:43,680 --> 00:05:52,240 +Nous pouvons définir `num_labels=3` lorsque nous initialisons le modèle avec `from_pretrained`. Alors maintenant, nous pensons que nos + +49 +00:05:52,240 --> 00:05:58,000 +données sont bonnes et que notre modèle est bon, donc l'entraînement devrait fonctionner. Et si nous essayons d'exécuter `model.fit()`, + +50 +00:05:58,000 --> 00:06:07,600 +nous obtenons... hmm. Nous obtenons une perte, c'est un nombre, cela diminue, mais cela ne diminue pas très rapidement. Et si nous continuons, + +51 +00:06:07,600 --> 00:06:13,600 +nous constaterons que cela se fige à une valeur assez élevée. Que se passe-t-il ? Eh bien, lorsque les choses fonctionnent pour la + +52 +00:06:13,600 --> 00:06:19,280 +plupart mais que l'entraînement est simplement lent, cela peut être un bon moment pour examiner votre optimiseur + +53 +00:06:19,280 --> 00:06:24,560 +et les hyperparamètres d'entraînement. Et c'est là que je veux mentionner l'une des sources les plus courantes + +54 +00:06:24,560 --> 00:06:30,480 +de problèmes lorsque vous travaillez avec Keras. Vous pouvez nommer des choses comme des optimiseurs avec des chaînes, Keras supporte ça et c'est très utile, + +55 +00:06:32,960 --> 00:06:37,680 +mais si vous faites cela, toutes les options sont silencieusement définies sur leurs valeurs par défaut. + +56 +00:06:38,240 --> 00:06:43,920 +Nous avons donc spécifié Adam comme optimiseur, mais au cours du processus, nous avons obtenu de manière invisible le taux d'apprentissage par défaut, + +57 +00:06:43,920 --> 00:06:51,440 +qui est de 1e-3, soit 10 à la puissance moins 3. C'est bien trop élevé pour entraîner des transformers. + +58 +00:06:51,440 --> 00:06:59,120 +Nous devrions revenir en arrière et spécifier directement le taux d'apprentissage sans utiliser une chaîne. Les bonnes valeurs se situent entre 1e-5 + +59 +00:06:59,760 --> 00:07:06,800 +et 1e-4. Séparons en deux et choisissons 5e-5. Et si vous recompilez avec cela, + +60 +00:07:06,800 --> 00:07:16,880 +vous constaterez que l'entraînement fonctionne enfin. La perte diminue efficacement et converge vers une valeur basse. Encore une fois, je suis passé ici assez rapidement, et je vous + +61 +00:07:16,880 --> 00:07:20,720 +recommande de consulter les notes de cours pour voir cela plus en détail et d'expérimenter + +62 +00:07:20,720 --> 00:07:28,840 +le code vous-même pour regarder à quoi l'erreur ressemble et comment l'approcher. + +63 +00:07:20,720 --> 00:07:28,840 +J'espère que cela vous a donné un bref résumé des bugs les plus fréquents et des approches de deboguage les plus fréquentes pour les résoudre. Bonne chance et n'oubliez pas de faire des pauses si votre code vous donne du fil à retordre ! \ No newline at end of file diff --git a/subtitles/fr/75_writing-a-good-issue.srt b/subtitles/fr/75_writing-a-good-issue.srt new file mode 100644 index 000000000..472e5c237 --- /dev/null +++ b/subtitles/fr/75_writing-a-good-issue.srt @@ -0,0 +1,131 @@ +1 +00:00:05,440 --> 00:00:11,040 +Comment rédiger un bon ticket sur GitHub ? GitHub est l'endroit principal pour les + +2 +00:00:11,040 --> 00:00:15,920 +bibliothèques open source de Hugging Face, et vous devriez toujours vous y rendre pour signaler un bogue ou demander une nouvelle fonctionnalité. Pour + +3 +00:00:15,920 --> 00:00:21,680 +des questions plus générales ou pour déboguer votre propre code, utilisez les forums (voir la vidéo ci-dessous). Il est + +4 +00:00:21,680 --> 00:00:25,920 +très important d'écrire de bons tickets, car cela aidera le bogue que vous avez découvert à être corrigé en un rien de temps. + +5 +00:00:26,960 --> 00:00:31,760 +Pour cette vidéo, nous avons créé une version de Transformers avec un bogue. Vous pouvez l'installer en + +6 +00:00:31,760 --> 00:00:36,080 +exécutant cette commande dans un notebook (supprimez le point d'exclamation pour l'exécuter dans un terminal). + +7 +00:00:37,040 --> 00:00:43,440 +Dans cette version, l'exemple suivant échoue. L'erreur est plutôt énigmatique et ne semble pas provenir + +8 +00:00:43,440 --> 00:00:49,600 +de quoi que ce soit dans notre code. Il semble donc que nous ayons un bogue à signaler ! Dans ce cas, la première chose à faire + +9 +00:00:49,600 --> 00:00:53,200 +est d'essayer de trouver la plus petite quantité de code possible reproduisant le bogue. + +10 +00:00:54,000 --> 00:00:59,680 +Dans notre cas, en inspectant le « Traceback », nous constatons que l'échec se produit à l'intérieur de la fonction pipeline + +11 +00:00:59,680 --> 00:01:06,400 +lorsqu'elle appelle `AutoTokenizer.from_pretrained`. À l'aide du débogueur, nous trouvons les valeurs transmises à cette méthode + +12 +00:01:06,400 --> 00:01:11,840 +et pouvons ainsi créer un petit échantillon de code qui, espérons-le, génère la même erreur. + +13 +00:01:12,560 --> 00:01:16,800 +Il est très important de suivre cette étape car vous pouvez réaliser que l'erreur était de votre côté et non + +14 +00:01:16,800 --> 00:01:21,280 +un bogue dans la bibliothèque, mais cela permettra également aux responsables de résoudre plus facilement votre problème. + +15 +00:01:22,080 --> 00:01:24,880 +Ici, nous pouvons jouer un peu plus avec ce code et remarquer que l'erreur se produit + +16 +00:01:24,880 --> 00:01:31,040 +pour différents checkpoints et pas seulement celui-ci, et qu'elle disparaît lorsque nous utilisons `use_fast=False` + +17 +00:01:31,040 --> 00:01:36,400 +dans notre appel de tokenizer. L'important est d'avoir quelque chose qui ne dépend d'aucun + +18 +00:01:36,400 --> 00:01:42,800 +fichier ou donnée externe. Essayez de remplacer vos données par de fausses valeurs si vous ne pouvez pas les partager. Avec + +19 +00:01:42,800 --> 00:01:48,480 +tout cela fait, nous sommes prêts à commencer à écrire notre ticket. Cliquez sur le bouton à côté de « Bug Report » et + +20 +00:01:48,480 --> 00:01:54,160 +vous découvrirez qu'il y a un patron à remplir. Cela ne vous prendra que quelques minutes. La première + +21 +00:01:54,160 --> 00:02:00,000 +chose à faire est de nommer correctement votre ticket. Ne choisissez pas un titre trop vague ! Ensuite, vous devez remplir + +22 +00:02:00,000 --> 00:02:04,800 +les informations de votre environnement. Il existe une commande fournie par la bibliothèque Transformers pour effectuer cette opération. + +23 +00:02:05,520 --> 00:02:09,840 +Exécutez-la simplement dans votre notebook ou dans un terminal, puis copiez-collez les résultats. + +24 +00:02:10,800 --> 00:02:15,600 +Il reste deux dernières questions à remplir manuellement (auxquelles les réponses sont non et non dans notre cas). + +25 +00:02:17,440 --> 00:02:23,680 +Ensuite, nous devons déterminer qui taguer. Il existe une liste complète des noms d'utilisateur. Étant donné que notre problème + +26 +00:02:23,680 --> 00:02:28,880 +concerne les tokenizers, nous choisissons le responsable qui leur est associé. Inutile de taguer + +27 +00:02:28,880 --> 00:02:32,640 +plus de 3 personnes, elles vous redirigeront vers la bonne personne si vous avez fait une erreur. + +28 +00:02:34,320 --> 00:02:37,280 +Ensuite, nous devons fournir les informations nécessaires pour reproduire le bug. + +29 +00:02:38,000 --> 00:02:43,280 +Nous collons notre exemple et le plaçons entre deux lignes avec trois « backticks » afin qu'il soit + +30 +00:02:43,280 --> 00:02:49,840 +correctement formaté. Nous collons également le « Traceback » complet, toujours entre deux lignes de trois « backticks ». Enfin, + +31 +00:02:50,400 --> 00:02:54,400 +nous pouvons ajouter toute information supplémentaire sur ce que nous avons essayé de déboguer pour le problème en question. + +32 +00:02:54,960 --> 00:02:58,800 +Avec tout cela, vous devriez vous attendre à une réponse assez rapide à votre ticket et, espérons-le, à une solution rapide ! + +33 +00:02:58,800 --> 00:03:03,840 +Notez que tous les conseils de cette vidéo s'appliquent à presque tous les projets open source. \ No newline at end of file diff --git a/subtitles/fr/Titles and descriptions.txt b/subtitles/fr/Titles and descriptions.txt new file mode 100644 index 000000000..cf9c832c8 --- /dev/null +++ b/subtitles/fr/Titles and descriptions.txt @@ -0,0 +1,1228 @@ +Bienvenue au cours d’Hugging Face + +Ceci est une introduction au cours d’Hugging Face : http://huggingface.co/course/fr +Intervenants : Matthew Carrigan, Lysandre Debut, Sylvain Gugger, Sacha Luccioni, Merve Noyan, Lucile Saulnier, Lewis Tunstall, Leandro von Werra +Traduction : Loïck Bourdois +Vous voulez commencer par des vidéos ? Pourquoi ne pas essayer : +- Qu'est-ce que l'apprentissage par transfert ? https://youtu.be/BqqfQnyjmgg +- La fonction pipeline : https://youtu.be/tiZFewofSLM +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +La fonction pipeline + +La fonction pipeline et toutes les tâches NLP qu'elle peut effectuer. +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter1 +Ouvrir les codes de la vidéo dans Colab : +- En anglais : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter1/section3.ipynb +- En français : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter1/section3.ipynb +Vidéos connexes : +- Derrière la fonction pipeline : https://youtu.be/1pedAIvTWXk +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +L'empreinte carbone des transformers + +Vous êtes-vous déjà demandé quelle était l'empreinte carbone de l'entraînement d'un transformers ? Et comment vous pouvez la réduire ? Alors ne cherchez pas plus loin que cette vidéo. +Intervenant : Sacha Luccioni +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter1 +Vidéos connexes : +- Qu'est-ce que l'apprentissage par transfert ? https://youtu.be/BqqfQnyjmgg +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Qu'est-ce que l'apprentissage par transfert ? + +Qu'est-ce que l'apprentissage par transfert, pourquoi et quand est-il utile ? +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter1 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/training_loop.ipynb +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +L'architecture du transformer + +Une introduction générale de haut niveau à l'architecture du transformer. +Intervenant : Lysandre Debut +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter1 +Vidéos connexes : +- Modèles encodeur : https://youtu.be/MUqNwgPjJvQ +- Modèles décodeur : https://youtu.be/d_ixlCubqQw +- Modèles encodeur-décodeur : https://youtu.be/0_4KEb08xrE +Pour mieux comprendre ce qui se passe à l'intérieur du transformer, nous vous recommandons les articles de blog suivants de Jay Alammar : +- Le Transformateur illustré : https://jalammar.github.io/illustrated-transformer/ +- Le GPT-2 illustré : https://jalammar.github.io/illustrated-gpt2/ +- Comprendre l'attention : https://jalammar.github.io/visualizing-neural-machine-translation-mechanics-of-seq2seq-models-with-attention/ +En outre, pour une perspective axée sur le code, nous vous recommandons de jeter un coup d'œil à l'article suivant : +- The Annotated Transformer, par Harvard NLP : https://nlp.seas.harvard.edu/2018/04/03/attention.html +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Transformer : l’encodeur + +Une introduction générale de haut niveau à la partie Encodeur du transformer. +Qu'est-ce que c'est, quand faut-il l'utiliser ? +Intervenant : Lysandre Debut +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter1 +Vidéos connexes : +- Modèles encodeur : https://youtu.be/MUqNwgPjJvQ +- Modèles décodeur : https://youtu.be/d_ixlCubqQw +- Modèles encodeur-décodeur : https://youtu.be/0_4KEb08xrE +Pour mieux comprendre ce qui se passe à l'intérieur du transformer, nous vous recommandons les articles de blog suivants de Jay Alammar : +- Le Transformateur illustré : https://jalammar.github.io/illustrated-transformer/ +- Le GPT-2 illustré : https://jalammar.github.io/illustrated-gpt2/ +- Comprendre l'attention : https://jalammar.github.io/visualizing-neural-machine-translation-mechanics-of-seq2seq-models-with-attention/ +En outre, pour une perspective axée sur le code, nous vous recommandons de jeter un coup d'œil à l'article suivant : +- The Annotated Transformer, par Harvard NLP : https://nlp.seas.harvard.edu/2018/04/03/attention.html +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Transformer : l’encodeur + +Une introduction générale de haut niveau à la partie Encodeur du transformer. +Qu'est-ce que c'est, quand faut-il l'utiliser ? +Intervenant : Lysandre Debut +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter1 +Vidéos connexes : +- Modèles encodeur : https://youtu.be/MUqNwgPjJvQ +- Modèles décodeur : https://youtu.be/d_ixlCubqQw +- Modèles encodeur-décodeur : https://youtu.be/0_4KEb08xrE +Pour mieux comprendre ce qui se passe à l'intérieur du transformer, nous vous recommandons les articles de blog suivants de Jay Alammar : +- Le Transformateur illustré : https://jalammar.github.io/illustrated-transformer/ +- Le GPT-2 illustré : https://jalammar.github.io/illustrated-gpt2/ +- Comprendre l'attention : https://jalammar.github.io/visualizing-neural-machine-translation-mechanics-of-seq2seq-models-with-attention/ +En outre, pour une perspective axée sur le code, nous vous recommandons de jeter un coup d'œil à l'article suivant : +- The Annotated Transformer, par Harvard NLP : https://nlp.seas.harvard.edu/2018/04/03/attention.html +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + +Transformer : le décodeur +Une introduction générale de haut niveau à la partie décodeur du transformer. +Qu'est-ce que c'est, quand faut-il l'utiliser ? +Intervenant : Lysandre Debut +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter1 +Vidéos connexes : +- Modèles encodeur : https://youtu.be/MUqNwgPjJvQ +- Modèles décodeur : https://youtu.be/d_ixlCubqQw +- Modèles encodeur-décodeur : https://youtu.be/0_4KEb08xrE +Pour mieux comprendre ce qui se passe à l'intérieur du transformer, nous vous recommandons les articles de blog suivants de Jay Alammar : +- Le Transformateur illustré : https://jalammar.github.io/illustrated-transformer/ +- Le GPT-2 illustré : https://jalammar.github.io/illustrated-gpt2/ +- Comprendre l'attention : https://jalammar.github.io/visualizing-neural-machine-translation-mechanics-of-seq2seq-models-with-attention/ +En outre, pour une perspective axée sur le code, nous vous recommandons de jeter un coup d'œil à l'article suivant : +- The Annotated Transformer, par Harvard NLP : https://nlp.seas.harvard.edu/2018/04/03/attention.html +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Transformer : l’encodeur-décodeur +Une introduction générale de haut niveau à l'encodeur-décodeur, ou modèles de séquence à séquence utilisant l'architecture Transformer. Qu'est-ce que c'est, quand faut-il l'utiliser ? +Intervenant : Lysandre Debut +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter1 +Vidéos connexes : +- Modèles encodeur : https://youtu.be/MUqNwgPjJvQ +- Modèles décodeur : https://youtu.be/d_ixlCubqQw +- Modèles encodeur-décodeur : https://youtu.be/0_4KEb08xrE +Pour mieux comprendre ce qui se passe à l'intérieur du transformer, nous vous recommandons les articles de blog suivants de Jay Alammar : +- Le Transformateur illustré : https://jalammar.github.io/illustrated-transformer/ +- Le GPT-2 illustré : https://jalammar.github.io/illustrated-gpt2/ +- Comprendre l'attention : https://jalammar.github.io/visualizing-neural-machine-translation-mechanics-of-seq2seq-models-with-attention/ +En outre, pour une perspective axée sur le code, nous vous recommandons de jeter un coup d'œil à l'article suivant : +- The Annotated Transformer, par Harvard NLP : https://nlp.seas.harvard.edu/2018/04/03/attention.html +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Que se passe-t-il dans la fonction pipeline ? (PyTorch) + +Que se passe-t-il vraiment lorsque nous exécutons un texte dans un pipeline créé avec la bibliothèque 🤗 Transformers ? +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter2 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/inside_pipeline_pt.ipynb +Version TensorFlow : https://youtu.be/wVN12smEvqg +Vidéos connexes : +- La fonction pipeline : https://youtu.be/tiZFewofSLM +- En savoir plus sur le pipeline de tokenization : https://youtu.be/Yffk5aydLzg +- En savoir plus sur l'instanciation d'un transformer : https://youtu.be/AhChOFRegn4 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Que se passe-t-il dans la fonction pipeline ? (TensorFlow) + +Que se passe-t-il vraiment lorsque nous exécutons un texte dans un pipeline créé avec la bibliothèque 🤗 Transformers ? +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter2 +Ouvrir les codes de la vidéo dans Colab : Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/inside_pipeline_pt.ipynb +Version PyTorch : https://youtu.be/1pedAIvTWXk +Vidéos connexes : +- La fonction pipeline : https://youtu.be/tiZFewofSLM +- En savoir plus sur le pipeline de tokénisation : https://youtu.be/Yffk5aydLzg +- En savoir plus sur l'instanciation d'un transformer : https://youtu.be/d3JVgghSOew +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Instancier un transformer (PyTorch) + +Instanciez n'importe quel modèle de la bibliothèque 🤗 Transformers, en utilisant un checkpoint pré-entraîné ou simplement des poids aléatoires. +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter2 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/model_api_pt.ipynb +Version TensorFlow : https://youtu.be/d3JVgghSOew +Vidéos connexes : +- La fonction pipeline : https://youtu.be/tiZFewofSLM +- L'API push_to_hub : https://youtu.be/A5IWIxsHLUw +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Instancier un transformers (TensorFlow) + +Instanciez n'importe quel modèle de la bibliothèque 🤗 Transformers, en utilisant un checkpoint pré-entraîné ou simplement des poids aléatoires. +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter2 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/model_api_pt.ipynb +Version PyTorch : https://youtu.be/AhChOFRegn4 +Vidéos connexes : +- La fonction pipeline : https://youtu.be/tiZFewofSLM +- L'API push_to_hub : https://youtu.be/A5IWIxsHLUw +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Vue d'ensemble des tokenizers + +Une introduction générale sur les différents types de tokenizers. +Intervenant : Lysandre Debut +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter2 +Vidéos connexes : +- Tokenizers à base de mots : https://youtu.be/nhJxYji1aho +- Tokenizers à base de caractères : https://youtu.be/ssLq_EK2jLE +- Tokenizers à base de sous-mots : https://youtu.be/zHvTiHr506c +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Tokenizers à base de mots + +Qu'est-ce qu'un tokenizer à base de mots, et quelles sont les forces et les faiblesses de ces tokenizers. Une introduction générale sur les différents types de tokenizers. +Intervenant : Lysandre Debut +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter2 +Vidéos connexes : +- Tokenizers à base de mots : https://youtu.be/nhJxYji1aho +- Tokenizers à base de caractères : https://youtu.be/ssLq_EK2jLE +- Tokenizers à base de sous-mots : https://youtu.be/zHvTiHr506c +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Tokenizers à base de caractères + +Qu'est-ce qu'un tokenizer à base de caractères, et quelles sont les forces et les faiblesses de ces tokenizers. +Intervenant : Lysandre Debut +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter2 +Vidéos connexes : +- Tokenizers à base de mots : https://youtu.be/nhJxYji1aho +- Tokenizers à base de caractères : https://youtu.be/ssLq_EK2jLE +- Tokenizers à base de sous-mots : https://youtu.be/zHvTiHr506c +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Tokenizers à base de sous-mots + +Qu'est-ce qu'un tokenizer à base de sous-mots, et quelles sont les forces et les faiblesses de ces tokenizers. +Intervenant : Lysandre Debut +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter2 +Vidéos connexes : +- Tokenizers à base de mots : https://youtu.be/nhJxYji1aho +- Tokenizers à base de caractères : https://youtu.be/ssLq_EK2jLE +- Tokenizers à base de sous-mots : https://youtu.be/zHvTiHr506c +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Le pipeline de tokénisation + +Que se passe-t-il lorsque nous appelons un tokenizer sur certains textes et comment calcule-t-il les nombres qu'il produit ? +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter2 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/tokenizer_pipeline.ipynb +Vidéos connexes : +- La fonction pipeline : https://youtu.be/tiZFewofSLM +- En savoir plus sur les algorithmes du tokenizer : https://youtu.be/VFp38yj8h3A +- En savoir plus sur les masques d'attention : https://youtu.be/M6adb1j2jPI +- En savoir plus sur les token de type identifiant : https://youtu.be/0u3ioSwev3s +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Regroupement des entrées (PyTorch) + +Comment pouvons-nous regrouper différentes phrases de différentes longueurs et nous assurer que nous obtenons le même résultat lorsque nous les passons dans le modèle sous forme de batch ou de phrases individuelles. +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter2 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/batch_inputs_pt.ipynb +Version TensorFlow : https://youtu.be/ROxrFOEbsQE +Vidéos connexes : +- La fonction pipeline : https://youtu.be/tiZFewofSLM +- Le pipeline de tokenisation : https://youtu.be/Yffk5aydLzg +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Regroupement des entrées (TensorFlow) + +Comment pouvons-nous regrouper différentes phrases de différentes longueurs et nous assurer que nous obtenons le même résultat lorsque nous les passons dans le modèle sous forme de batch ou de phrases individuelles. +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter2 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/batch_inputs_pt.ipynb +Version PyTorch : https://youtu.be/M6adb1j2jPI +Vidéos connexes : +- La fonction pipeline : https://youtu.be/tiZFewofSLM +- Le pipeline de tokenisation : https://youtu.be/Yffk5aydLzg +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Vue d'ensemble de Datasets d'Hugging Face (PyTorch) + +Une introduction rapide à la bibliothèque 🤗 Datasets : comment l'utiliser pour télécharger et prétraiter un jeu de données. +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter3 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/datasets_overview_pt.ipynb +TensorFlow version: https://youtu.be/W_gMJF0xomE +Vidéos connexes : +- Comment prétraiter des paires de phrases : https://youtu.be/0u3ioSwev3s 🤗 +- Documentation de Datasets: https://huggingface.co/docs/datasets/ +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Vue d'ensemble de Datasets d'Hugging Face (Tensorflow) + +Une introduction rapide à la bibliothèque 🤗 Datasets : comment l'utiliser pour télécharger et prétraiter un jeu de données. +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter3 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/datasets_overview_pt.ipynb +Version PyTorch : https://youtu.be/_BZearw7f0w +Vidéos connexes : +- Comment prétraiter des paires de phrases : https://youtu.be/0u3ioSwev3s 🤗 +- Documentation de Datasets: https://huggingface.co/docs/datasets/ +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Prétraitement de paires de phrases (PyTorch) + +Beaucoup de problèmes de classification de textes traitent de paires de phrases, comment utiliser la bibliothèque Transformers et ses tokenizers pour les prétraiter ? +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter3 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/sentence_pairs_pt.ipynb +Version TensorFlow : https://youtu.be/P-rZWqcB6CE +Vidéos connexes : +- Le pipeline de tokenisation : https://youtu.be/Yffk5aydLzg +- Comment regrouper les entrées : https://youtu.be/M6adb1j2jPI +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Prétraitement de paires de phrases (TensorFlow) + +Beaucoup de problèmes de classification de textes traitent de paires de phrases, comment utiliser la bibliothèque Transformers et ses tokenizers pour les prétraiter ? +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter3 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/sentence_pairs_pt.ipynb +Version PyTorch : https://youtu.be/0u3ioSwev3s +Vidéos connexes : +- Le pipeline de tokenisation : https://youtu.be/Yffk5aydLzg +- Comment regrouper les entrées : https://youtu.be/M6adb1j2jPI +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Qu'est-ce que le rembourrage dynamique ? + +Qu'est-ce que le rembourrage dynamique et diffère-t-il du rembourrage fixe traditionnel ? +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter3 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/dynamic_padding.ipynb +Vidéos connexes : +- Comment prétraiter des paires de phrases : https://youtu.be/0u3ioSwev3s +- La bibliothèque 🤗 Datasets : https://youtu.be/_BZearw7f0w +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +L'API Trainer + +L'API Trainer de la bibliothèque Transformers, et comment l'utiliser pour affiner un modèle. +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter3 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/trainer_api.ipynb +Vidéos connexes : +- Comment instancier un transformer : https://youtu.be/AhChOFRegn4 +- Comment prétraiter des paires de phrases : https://youtu.be/0u3ioSwev3s +- La bibliothèque 🤗 Datasets : https://youtu.be/_BZearw7f0w +- Qu'est-ce que le rembourrage dynamique : https://youtu.be/7q5NyFT8REg +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Introduction à Keras + +Qu'est-ce que Keras et comment l'utiliser avec les transformers. +Intervenant : Matthew Carrigan +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter3 +Vidéos connexes : +- Vue d’ensemble de Datasets : https://youtu.be/W_gMJF0xomE +- Finetuning avec TensorFlow : https://youtu.be/alq1l8Lv9GA +- Plannification du taux d'apprentissage dans TensorFlow : https://youtu.be/eKv4rRcCNX0 +- Prédiction et métriques : https://youtu.be/nx10eh4CoOs +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Finetuning avec TensorFlow + +Finetunons un transformer dans TensorFlow, en utilisant Keras. +Intervenant : Matthew Carrigan +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter3 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/tensorflow_finetuning.ipynb +Vidéos connexes : +- Qu'est-ce que l'apprentissage par transfert : https://youtu.be/BqqfQnyjmgg +- Que se passe-t-il dans la fonction pipeline ? : https://youtu.be/wVN12smEvqg +- Le pipeline de tokenization : https://youtu.be/Yffk5aydLzg +- Vue d’ensemble de Datasets : https://youtu.be/W_gMJF0xomE +- Introduction à Keras : https://youtu.be/rnTGBy2ax1c +- Planification du taux d'apprentissage dans TensorFlow : https://youtu.be/eKv4rRcCNX0 +- Prédiction et métriques : https://youtu.be/nx10eh4CoOs +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Planification du taux d'apprentissage avec TensorFlow + +Comment planifier le taux d'apprentissage en utilisant TensorFlow et Keras. +Intervenant : Matthew Carrigan +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter3 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/tf_lr_scheduling.ipynb +Vidéos connexes : +- Introduction à Keras: https://youtu.be/rnTGBy2ax1c +- Finetuning avec TensorFlow: https://youtu.be/alq1l8Lv9GA +- Prediction et métriques : https://youtu.be/nx10eh4CoOs +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Prédictions et métriques TensorFlow + +Utilisez TensorFlow et Keras pour calculer des prédictions avec notre modèle finetuné, et l'évaluer. +Intervenant : Matthew Carrigan +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter3 +Vidéos connexes : +- Vue d’ensemble de Datasets : https://youtu.be/W_gMJF0xomE +- Introduction à Keras: https://youtu.be/rnTGBy2ax1c +- Finetuning avec TensorFlow: https://youtu.be/alq1l8Lv9GA +- Planification du taux d'apprentissage avec TensorFlow : https://youtu.be/eKv4rRcCNX0 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Écrire votre boucle d'entraînement dans PyTorch + +Finetunons un transformer avec PyTorch sans utiliser d'outils spéciaux. +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter3 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/training_loop.ipynb +Vidéos connexes : +- Comment instancier un transformer : https://youtu.be/AhChOFRegn4 +- Comment prétraiter des paires de phrases : https://youtu.be/0u3ioSwev3s +- La bibliothèque 🤗 Datasets : https://youtu.be/_BZearw7f0w +- Qu'est-ce que le rembourrage dynamique : https://youtu.be/7q5NyFT8REg +- L'API Trainer : https://youtu.be/nvBXf7s7vTI +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Optimisez votre boucle d'entraînement PyTorch avec Accelerate + +Comment faire fonctionner une boucle d'entraînement sur n'importe quelle configuration distribuée avec 🤗 Accelerate. +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter3 +Vidéos connexes : +- L’API Trainer : https://youtu.be/nvBXf7s7vTI +- Écrire votre boucle d'entraînement en PyTorch : https://youtu.be/Dh9CL8fyG80 +- Documentation d’🤗 Accelerate: https://huggingface.co/docs/accelerate/ +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Naviguer sur le Hub des modèles + +Visitez le Hub des modèles d’Hugging Face ! +Intervenant : Lysandre Debut +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter4 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/git... You will need a Hugging Face account to manage a repo on the Model Hub. Join now: http://huggingface.co/join Vidéos connexes : - The push to hub API: https://youtu.be/A5IWIxsHLUw - Managing a repo on the Model Hub: https://youtu.be/rkCly_cbMBk +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Gestion d'un dépôt sur le Hub des modèles + +Apprenez à gérer un dépôt sur le Hub de modèles d'Hugging Face. Cette vidéo couvre la création d'un dépôt, les ajouts de fichiers via l'interface web ainsi que via la ligne de commande. +Intervenant : Lysandre Debut +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter4 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/training_loop.ipynb +Afin de pouvoir suivre cette vidéo, nous vous recommandons de vous familiariser au préalable avec git et git-lfs. Installation de git : https://git-scm.com/book/en/v2/Gettin... Installer git-lfs : https://git-lfs.github.com/ Tutoriel Git : https://git-scm.com/docs/gittutorial +Vidéos connexes : +- L'API push to hub : https://youtu.be/A5IWIxsHLUw +- Naviguer dans le Hub des modèles : https://youtu.be/XvSGPZFEjDY +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +L'API Push to Hub (PyTorch) + +Partagez facilement vos modèles finetunés sur le Hub d’Hugging Face en utilisant l'API push to hub. +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter4 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/push_to_hub_pt.ipynb + Version TensorFlow : https://youtu.be/pUh5cGmNV8Y +Vidéos connexes : +- Naviguez dans le Hub des modèles : https://youtu.be/XvSGPZFEjDY +- Comment instancier un transformer : https://youtu.be/AhChOFRegn4 +- L’API Trainer : https://youtu.be/nvBXf7s7vTI +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +L'API Push to Hub (TensorFlow) + +Partagez facilement vos modèles finetunés sur le Hub d’Hugging Face en utilisant l'API push to hub. +Intervenant : Matthew Carrigan +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter4 +Version PyTorch : https://youtu.be/Zh0FfmVrKX0 +Vidéos connexes : +- Naviguez dans le Hub des modèles : https://youtu.be/XvSGPZFEjDY +- Comment instancier un transformer : https://youtu.be/AhChOFRegn4 +- L’API Trainer : https://youtu.be/nvBXf7s7vTI +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Chargement d'un jeu de données personnalisé + +Découvrez comment charger un jeu de données personnalisé avec la bibliothèque 🤗 Datasets. +Intervenant : Lewis Tunstall +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter5 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/load_custom_dataset.ipynb +Vidéos connexes : +- Sauvegarde et rechargement d'un jeu de données : https://youtu.be/blF9uxYcKHo +- Découper et trancher un jeu de données 🔪 : https://youtu.be/tqfSFcPMgOI +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Découper et trancher un jeu de données 🔪 + +Cette vidéo vous apprendra toutes les opérations de base à connaître pour préparer vos données pour l'entraînement avec la bibliothèque 🤗 Datasets. +Intervenant : Lewis Tunstall +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter5 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/slice_and_dice.ipynb +Vidéos connexes : +- Chargement d'un jeu de données personnalisé : https://youtu.be/HyQgpJTkRdE +- Sauvegarde et rechargement d'un jeu de données : https://youtu.be/blF9uxYcKHo +- Datasets + DataFrames = ❤️ : https://youtu.be/tfcY1067A5Q +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Datasets + DataFrames = ❤️ + +Convertir un jeu de données de la bibliothèque 🤗 Datasets en un DataFrame pandas et inversement est très facile ! Cette vidéo vous montrera comment ces deux classes interagissent entre elles de manière transparente. +Intervenant : Lewis Tunstall +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter5 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/datasets_and_dataframes.ipynb +Vidéos connexes : +- Chargement d'un jeu de données personnalisé : https://youtu.be/HyQgpJTkRdE +- Découper et trancher un jeu de données 🔪 : https://youtu.be/tqfSFcPMgOI +- Sauvegarde et rechargement d'un jeu de données : https://youtu.be/blF9uxYcKHo +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Sauvegarde et rechargement d'un jeu de données + +Apprenez à enregistrer votre jeu de données et à le recharger ultérieurement avec la bibliothèque 🤗 Datasets. +Intervenant : Lewis Tunstall +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter5 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/save_load_dataset.ipynb +Vidéos connexes : +- Chargement d'un jeu de données personnalisé : https://youtu.be/HyQgpJTkRdE +- Découper et trancher un jeu de données 🔪 : https://youtu.be/tqfSFcPMgOI +- Téléchargement d'un jeu de données sur le Hub : https://youtu.be/HaN6qCr_Afc +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Association de la mémoire et streaming + +La bibliothèque 🤗 Datasets vous permet d'utiliser et de traiter des jeux de données qui ne tiennent pas dans la mémoire vive. Découvrez comment elle peut le faire grâce à l’association mémoire et comment utiliser la fonctionnalité de streaming. +Intervenant : Lewis Tunstall +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter5 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/memory_mapping_streaming.ipynb +Vidéos connexes : +- Chargement d'un jeu de données personnalisé : https://youtu.be/HyQgpJTkRdE +- Découper et trancher un jeu de données 🔪 : https://youtu.be/tqfSFcPMgOI +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Téléchargement d'un jeu de données vers le Hub + +Dans cette vidéo, vous apprendrez à télécharger vos propres jeux de données sur le Hub. +Intervenant : Lewis Tunstall +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter5 +Vidéos connexes : +- Chargement d'un jeu de données personnalisé : https://youtu.be/HyQgpJTkRdE +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Enchâssement de texte et recherche sémantique + +Découvrez comment les transformers peuvent être utilisés pour représenter des documents et des requêtes sous forme de vecteurs appelés enchâssements. Dans cette vidéo, nous appliquons cette technique pour créer un moteur de recherche sémantique ! +Intervenant : Lewis Tunstall +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter5 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/semantic_search.ipynb +Vidéos connexes : +- Chargement d'un jeu de données personnalisé : https://youtu.be/HyQgpJTkRdE +- Découper et trancher un jeu de données 🔪 : https://youtu.be/tqfSFcPMgOI +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Entraîner un nouveau tokenizer + +Vous êtes-vous déjà demandé comment créer un tokenizer BERT ou GPT2 dans votre propre langue ou sur votre propre corpus ? Cette vidéo vous apprendra à le faire avec n'importe quel tokenizer de la bibliothèque 🤗 Transformers. +Intervenant : Lucile Saulnier +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter5 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/train_new_tokenizer.ipynb +Vidéos connexes : +- Construire un nouveau tokenizer : https://youtu.be/MR8tZm5ViWU +Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Pourquoi les tokenizers rapides sont-ils appelés rapides ? + +Les tokenizers rapides sont rapides, mais de combien exactement ? Cette vidéo vous le dira. +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter6 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/fast_tokenizers.ipynb +Vidéos connexes : +- Les superpouvoirs des tokenizers rapides : https://youtu.be/3umI3tm27Vw +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Les superpouvoirs des tokenizers rapides + +Les tokenizers rapides sont rapides, mais ils disposent également de fonctionnalités supplémentaires permettant de faire correspondre les tokens aux mots dont ils proviennent ou à la portée originale des caractères dans le texte brut. Cette vidéo explore ces fonctionnalités. +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter6 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/offset_mapping.ipynb +Vidéos connexes : +- Pourquoi les tokenizers rapides sont-ils appelés rapides ? : https://youtu.be/g8quOxoqhHQ +- Entraîner un nouveau tokenizer: https://youtu.be/DJimQynXZsQ +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Dans le pipeline de classification de tokens (PyTorch) + +Que se passe-t-il dans le pipeline de classification de tokens, et comment passe-t-on des logits aux étiquettes d'entités ? Cette vidéo vous le montrera. +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter6 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/token_pipeline_pt.ipynb +Version TensorFlow : https://youtu.be/0E7ltQB7fM8 +Vidéos connexes : +- Que se passe-t-il à l'intérieur de la fonction pipeline ? : https://youtu.be/1pedAIvTWXk +- Les superpouvoirs des tokenizers rapides : https://youtu.be/3umI3tm27Vw +- Traitement des données pour la classification de tokens : https://youtu.be/iY2AZYdZAr0 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Dans le pipeline de classification de tokens (TensorFlow) + +Que se passe-t-il dans le pipeline de classification de tokens, et comment passe-t-on des logits aux étiquettes d'entités ? Cette vidéo vous le montrera. +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter6 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/token_pipeline_pt.ipynb +Version PyTorch : https://youtu.be/0E7ltQB7fM8 +Vidéos connexes : +- Que se passe-t-il à l'intérieur de la fonction pipeline ? : https://youtu.be/1pedAIvTWXk +- Les superpouvoirs des tokenizers rapides : https://youtu.be/3umI3tm27Vw +- Traitement des données pour la classification de tokens : https://youtu.be/iY2AZYdZAr0 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Dans le pipeline de réponse aux questions (PyTorch) + +Comment le pipeline de réponse aux questions fonctionne-t-il réellement ? Dans cette vidéo, nous explorons comment nous passons des prédictions du modèle à la recherche de la réponse dans le contexte initial. +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter6 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/git... +Version TensorFlow : https://youtu.be/b3u8RzBCX9Y +Vidéos connexes : +- Que se passe-t-il à l'intérieur de la fonction pipeline ? : https://youtu.be/1pedAIvTWXk +- Les superpouvoirs des tokenizers rapides : https://youtu.be/3umI3tm27Vw +- Traitement des données pour la réponse aux questions : https://youtu.be/qgaM0weJHpA +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +A l'intérieur du pipeline de réponse aux questions (TensorFlow) + +Comment le pipeline de réponse aux questions fonctionne-t-il réellement ? Dans cette vidéo, nous explorons comment nous passons des prédictions du modèle à la recherche de la réponse dans le contexte initial. +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter6 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/git... +Version PyTorch : https://youtu.be/_wxyB3j3mk4 +Vidéos connexes : +- Que se passe-t-il à l'intérieur de la fonction pipeline ? : https://youtu.be/1pedAIvTWXk +- Les superpouvoirs des tokenizers rapides : https://youtu.be/3umI3tm27Vw +- Traitement des données pour la réponse aux questions : https://youtu.be/qgaM0weJHpA +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Qu'est-ce que la normalisation ? + +La première étape de la tokenisation des textes est appelée normalisation. Mais qu'est-ce que cela signifie ? Cette vidéo vous dira tout à ce sujet. +Intervenant : Lucile Saulnier +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter6 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/normalization.ipynb +Vidéos connexes : +- Qu'est-ce que la prétokénisation ? https://youtu.be/grlLV8AIXug +- Entraîner un nouveau tokenizer : https://youtu.be/DJimQynXZsQ +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Qu'est-ce que la prétokénisation ? + +La prétokénisation est la deuxième étape de la tokénisation des textes. Mais qu'est-ce que cela signifie ? Cette vidéo vous dira tout à ce sujet. +Intervenant : Lucile Saulnier +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter6 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/pre_tokenization.ipynb +Vidéos connexes : +- Qu’est-ce que la normalization ? https://youtu.be/4IIC2jI9CaU +- Entraîner un nouveau tokenizer : https://youtu.be/DJimQynXZsQ +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Tokenisation Byte Pair Encoding + +Cette vidéo vous apprendra tout ce qu'il y a à savoir sur l'algorithme de tokenisation Byte Pair Encoding. Comment il est entraîné sur un corpus de textes et comment il est appliqué pour tokeniser des textes. +Intervenant : Lucile Saulnier +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter6 +Vidéos connexes : +- Tokenisation Unigram : https://youtu.be/TGZfZVuF9Yc +- Tokenisation WordPiece : https://youtu.be/qpv6ms_t_1A +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Tokénisation WordPiece + +Cette vidéo vous apprendra tout ce qu'il y a à savoir sur l'algorithme de tokenisation WordPiece. Comment il est entraîné sur un corpus de textes et comment il est appliqué pour tokeniser des textes. +Intervenant : Lucile Saulnier +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter6 +Vidéos connexes : +- Tokenisation Byte Pair Encoding : https://youtu.be/HEikzVL-lZU +- Tokenisation Unigram : https://youtu.be/TGZfZVuF9Yc +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Tokenization Unigram + +Cette vidéo vous apprendra tout ce qu'il y a à savoir sur l'algorithme de tokenisation Unigram. Comment il est entraîné sur un corpus de textes et comment il est appliqué pour tokeniser des textes. +Intervenant : Lucile Saulnier +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter6 +Vidéos connexes : +- Tokenisation Byte Pair Encoding : https://youtu.be/HEikzVL-lZU +- Tokenisation WordPiece : https://youtu.be/qpv6ms_t_1A +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Construction d'un nouveau tokenizer + +Découvrez comment utiliser la bibliothèque 🤗 Tokenizers pour construire votre propre tokenizer, l'entraîner, puis comment l'utiliser dans la bibliothèque 🤗 Transformers. +Intervenant : Lucile Saulnier +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter6 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/building_tokenizer.ipynb +Vidéos connexes : +- Entraîner un nouveau tokenizer : https://youtu.be/DJimQynXZsQ +- Tokenisation Byte Pair Encoding : https://youtu.be/HEikzVL-lZU +- Tokenisation WordPiece : https://youtu.be/qpv6ms_t_1A +- Tokenisation Unigram : https://youtu.be/TGZfZVuF9Yc +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Traitement des données pour la classification de tokens + +Cette vidéo vous explique comment prétraiter un jeu de données pour une tâche de classification de tokens. +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter7 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/token_processing.ipynb +Vidéos connexes : +- 🤗 Tâches : classification de tokens : https://youtu.be/wVHdVlPScxA +- À l'intérieur du pipeline de classification de tokens (PyTorch): https://youtu.be/0E7ltQB7fM8 +- À l'intérieur du pipeline de classification de tokens (TensorFlow): https://youtu.be/PrX4CjrVnNc +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Traitement des données pour la modélisation du langage masqué + +Comment prétraiter un jeu de données pour une tâche de modélisation du langage masqué, par exemple pour remplir les blancs d'une phrase. +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter7 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/mlm_processing.ipynb +Vidéos connexes : +- 🤗 Tâches : modélisation du langage masqué : https://youtu.be/mqElG5QJWUg +- Finetuning avec TensorFlow: https://youtu.be/AUozVp78dhk +- L’API Trainer : https://youtu.be/nvBXf7s7vTI +- Écrire votre boucle d'entraînement en PyTorch: https://youtu.be/Dh9CL8fyG80 +- Optimisez votre boucle d'entraînement PyTorch avec Accelerate : https://youtu.be/s7dy8QRgjJ0 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Qu'est-ce que la perplexité ? + +Les modèles de langage sont souvent évalués à l'aide d'une métrique appelée perplexité. Vous vous sentez perplexe à ce sujet ? Regardez cette vidéo pour tout savoir. +Intervenant : Leandro von Werra +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter7 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/perplexity.ipynb +Vidéos connexes : +- 🤗 Tâches : modélisation du langage causal : https://youtu.be/Vpjb1lu0MDk +- 🤗 Tâches : modélisation du langage masqué : https://youtu.be/mqElG5QJWUg +- Traitement des données pour la modélisation du langage causal: https://youtu.be/ma1TrR7gE7I +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Qu'est-ce que l'adaptation au domaine ? + +Dans cette vidéo, nous expliquons ce qu'est l'adaptation au domaine et nous examinons des exemples de versions finetunées de modèles qui se sont adaptés à leur corpus de finetuning. +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter7 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/domain_adaptation.ipynb +Vidéos connexes : +- Qu'est-ce que l'apprentissage par transfert ? : https://youtu.be/BqqfQnyjmgg +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Traitement des données pour la traduction + +Comment prétraiter un jeu de données pour une tâche de traduction ? Cette vidéo vous aidera à le faire. +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter7 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/translation_processing.ipynb +Vidéos connexes : +- 🤗 Tâches : traduction : https://youtu.be/1JvfrvZgi6c +- Traitement des données pour le résumé de texte : https://youtu.be/1m7BerpSq8A +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Qu'est-ce que la métrique BLEU ? + +La métrique BLEU est souvent utilisée pour évaluer les modèles de traduction. Cette vidéo vous explique comment elle fonctionne. +Intervenant : Lewis Tunstall +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter7 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/bleu_metric.ipynb +Vidéos connexes : +- 🤗 Tâches : traduction : https://youtu.be/1JvfrvZgi6c +- Traitement des données pour la traduction : https://youtu.be/XAR8jnZZuUs +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Traitement des données pour le résumé + +Cette vidéo montre comment prétraiter des données pour une tâche de résumé. +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter7 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/summarization_processing.ipynb +Vidéos connexes : +- 🤗 Tâches : Résumé de textes : https://youtu.be/yHnr5Dk2zCI +- Traitement des données pour la traduction : https://youtu.be/XAR8jnZZuUs + Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Qu'est-ce que la métrique ROUGE ? + +La métrique ROUGE est souvent utilisée dans les tâches de compression, mais comment est-elle calculée exactement ? +Intervenant : Lewis Tunstall +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter7 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/rouge_metric.ipynb +Vidéos connexes : +- 🤗 Tâches : Résumé de textes : https://youtu.be/yHnr5Dk2zCI +- Traitement des données pour la traduction : https://youtu.be/XAR8jnZZuUs +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Traitement des données pour la modélisation causale du langage + +Dans cette vidéo, nous allons voir comment prétraiter un jeu de données pour une tâche de modélisation causale du langage. +Intervenant : Leandro von Werra +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter7 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/clm_processing.ipynb +Vidéos connexes : +- Finetuning avec TensorFlow : https://youtu.be/AUozVp78dhk +- L’API Trainer : https://youtu.be/nvBXf7s7vTI +- Écrivez votre boucle d'entraînement dans PyTorch : https://youtu.be/Dh9CL8fyG80 +- Optimisez votre boucle d'entraînement PyTorch avec Accelerate : https://youtu.be/s7dy8QRgjJ0 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Utilisation d'une fonction de perte personnalisée + +Dans cette vidéo, nous allons voir comment utiliser une fonction de perte personnalisée. La plupart des modèles de 🤗 Transformers renvoient automatiquement la perte lorsque vous leur fournissez des étiquettes, mais parfois, vous ne voulez pas la perte par défaut. Nous montrons ici comment pondérer dynamiquement les échantillons dans la perte en utilisant l'API Trainer et 🤗 Accelerate. +Intervenant : Leandro von Werra +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter7 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/custom_loss.ipynb +Vidéos connexes : +- L’API Trainer : https://youtu.be/nvBXf7s7vTI +- Écrivez votre boucle d'entraînement dans PyTorch : https://youtu.be/Dh9CL8fyG80 +- Optimisez votre boucle d'entraînement PyTorch avec Accelerate : https://youtu.be/s7dy8QRgjJ0 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Traitement des données pour la réponse aux questions + +Cette vidéo explore comment prétraiter un jeu de données pour la réponse aux questions et le préparer pour un transformer. +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter7 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/qa_processing.ipynb +Vidéos connexes : +- 🤗 Tâches : Réponse aux questions : https://youtu.be/ajPx5LwJD-I +- L'étape de post-traitement dans la réponse aux questions (PyTorch) : https://youtu.be/BNy08iIWVJM +- L'étape de post-traitement dans la réponse aux questions (TensorFlow) : https://youtu.be/VN67ZpN33Ss +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +L'étape de post-traitement en réponse aux questions (PyTorch) + +L'évaluation dans les tâches de réponse aux questions peut être délicate car il est difficile de convertir la sortie du modèle en réponses dans les contextes originaux. Cette vidéo va (espérons-le) rendre les choses plus claires ! +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter7 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/qa_postprocessing_pt.ipynb +Version TensorFlow: https://youtu.be/VN67ZpN33Ss +Vidéos connexes : +- Traitement des données pour la réponse aux questions : https://youtu.be/qgaM0weJHpA +- Dans le pipeline de réponse aux questions : https://youtu.be/_wxyB3j3mk4 +- Les superpouvoirs des tokenizers rapides : https://youtu.be/3umI3tm27Vw +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +L'étape de post-traitement en réponse aux questions (TensorFlow) + +L'évaluation dans les tâches de réponse aux questions peut être délicate car il est difficile de convertir la sortie du modèle en réponses dans les contextes originaux. Cette vidéo va (espérons-le) rendre les choses plus claires ! +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter7 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/qa_postprocessing_tf.ipynb +Version PyTorch : https://youtu.be/BNy08iIWVJM +Vidéos connexes : +- Traitement des données pour la réponse aux questions : https://youtu.be/qgaM0weJHpA +- Dans le pipeline de réponse aux questions : https://youtu.be/_wxyB3j3mk4 +- Les superpouvoirs des tokenizers rapides : https://youtu.be/3umI3tm27Vw +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Assembleurs de données : une visite + +La bibliothèque 🤗 Transformers fournit de nombreux assembleurs de données que vous pouvez utiliser pour regrouper vos échantillons dans un batch. Si vous êtes perdu entre toutes les possibilités, cette vidéo est pour vous ! +Intervenant : Matthew Carrigan +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter7 +Vidéos connexes : +- Finetuning avec TensorFlow : https://youtu.be/AUozVp78dhk +- L’API Trainer : https://youtu.be/nvBXf7s7vTI +- Écrivez votre boucle d'entraînement dans PyTorch : https://youtu.be/Dh9CL8fyG80 +- Optimisez votre boucle d'entraînement PyTorch avec Accelerate : https://youtu.be/s7dy8QRgjJ0 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Que faire lorsque vous obtenez une erreur ? + +Vous vous sentez dépassé par ce message d'erreur que Python vient de vous envoyer ? Pas de panique, nous allons y voir plus clair ensemble. +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter8 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/debug_error.ipynb +Vidéos connexes : +- Utilisation d'un débogueur dans un notebook : https://youtu.be/rSPyvPw0p9k +- Utilisation d'un débogueur dans un terminal : https://youtu.be/5PkZ4rbHL6c +- Déboguer le pipeline d'entraînement (PyTorch): https://youtu.be/L-WSwUWde1U +- Déboguer le pipeline d'entraînement (TensorFlow): https://youtu.be/N9kO52itd0Q +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Utilisation d'un débogueur dans un notebook + +Saviez-vous qu'il existe un débogueur Python que vous pouvez facilement utiliser dans un Jupyter Notebook ou un Google Colab ? Laissez-nous vous montrer comment l'utiliser. +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter8 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/debug_notebook.ipynb +Vidéos connexes : +- Que faire lorsque vous obtenez une erreur ? https://youtu.be/DQ-CpJn6Rc4 +- Utilisation d'un débogueur dans un terminal : https://youtu.be/5PkZ4rbHL6c +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Utiliser un débogueur dans un terminal + +Saviez-vous qu'il existe un débogueur Python que vous pouvez facilement utiliser en ligne de commande ? Laissez-nous vous montrer comment l'utiliser. +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter8 +Vidéos connexes : +- Que faire lorsque vous obtenez une erreur ? https://youtu.be/DQ-CpJn6Rc4 +- Utilisation d'un débogueur dans un notebook : https://youtu.be/rSPyvPw0p9k +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Demander de l'aide sur les forums + +Cette vidéo vous montrera comment poser au mieux une question sur les 🤗 Forums, et maximiser les chances d'obtenir une réponse rapide. +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter8 +Vidéos connexes : +- Que faire lorsque vous obtenez une erreur ? https://youtu.be/DQ-CpJn6Rc4 +- Ouvrir un bon ticket : https://youtu.be/_PAli-V4wj0 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Déboguer le pipeline d'entraînement (PyTorch) + +Vous obtenez une erreur lorsque vous appelez Trainer.train() ? Dans cette vidéo, nous allons vous apprendre à déboguer l'ensemble du pipeline d'entraînement, et nous espérons faire disparaître cette erreur. +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter8 +Version TensorFlow : https://youtu.be/N9kO52itd0Q +Vidéos connexes : +- L’API Trainer : https://youtu.be/nvBXf7s7vTI +- Que faire lorsque vous obtenez une erreur ? https://youtu.be/DQ-CpJn6Rc4 +- Utilisation d'un débogueur dans un notebook : https://youtu.be/rSPyvPw0p9k +- Utilisation d'un débogueur dans un terminal: https://youtu.be/5PkZ4rbHL6c +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Déboguer le pipeline d'entraînement (TensorFlow) + +Vous obtenez une erreur lorsque vous appelez model.fit() ? Dans cette vidéo, nous allons vous apprendre à déboguer l'ensemble du pipeline d'entraînement et, si possible, à faire disparaître cette erreur. +Intervenant : Matthew Carrigan +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter8 +Version PyTorch : https://youtu.be/L-WSwUWde1U +Vidéos connexes : +- Finetuning avec TensorFlow: https://youtu.be/AUozVp78dhk +- Que faire lorsque vous obtenez une erreur ? https://youtu.be/DQ-CpJn6Rc4 +- Utilisation d'un débogueur dans un notebook : https://youtu.be/rSPyvPw0p9k +- Utilisation d'un débogueur dans un terminal : https://youtu.be/5PkZ4rbHL6c +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +Rédiger un bon ticket + +Dans cette vidéo, nous allons vous montrer comment rédiger un bon ticket sur GitHub, afin de maximiser les chances que quelqu'un vous aide et résolve votre bug le plus rapidement possible. +Intervenant : Sylvain Gugger +Traduction : Loïck Bourdois +Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter8 +Vidéos connexes : +- Que faire lorsque vous obtenez une erreur ? https://youtu.be/DQ-CpJn6Rc4 +- Utilisation d'un débogueur dans un notebook : https://youtu.be/rSPyvPw0p9k +- Utilisation d'un débogueur dans un terminal : https://youtu.be/5PkZ4rbHL6c +- Demander de l'aide sur les forums : https://youtu.be/S2EEG3JIt2A +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join \ No newline at end of file diff --git a/subtitles/zh-CN/55_data-processing-for-token-classification.srt b/subtitles/zh-CN/55_data-processing-for-token-classification.srt index 7f6ec1d15..ebf92d940 100644 --- a/subtitles/zh-CN/55_data-processing-for-token-classification.srt +++ b/subtitles/zh-CN/55_data-processing-for-token-classification.srt @@ -1,41 +1,41 @@ 1 00:00:05,730 --> 00:00:07,590 -- 让我们研究如何预处理数据集 +- 让我们研究如何为词元分类 - Let's study how to preprocess a dataset 2 00:00:07,590 --> 00:00:09,063 -用于令牌分类! +预处理数据集! for token classification! 3 00:00:10,560 --> 00:00:12,660 -令牌分类重新组合任何任务 +任何对于单词进行标记 Token classification regroups any task 4 00:00:12,660 --> 00:00:14,940 -可以框架为标记每个单词 +或者对词元进行标记的任务 that can be framed as labeling each word 5 00:00:14,940 --> 00:00:17,190 -或标记在一个句子中, +均可以由词元分类进行重新组合, or token in a sentence, 6 00:00:17,190 --> 00:00:19,530 -比如识别人、组织 +比如识别人物、组织 like identifying the persons, organizations 7 00:00:19,530 --> 00:00:21,093 -和位置,例如。 +和位置之类。 and locations for instance. 8 00:00:22,170 --> 00:00:25,470 -对于我们的示例,我们将使用 Conll 数据集, +对于本例来说,我们将使用 Conll 数据集, For our example, we will use the Conll dataset, 9 @@ -50,27 +50,27 @@ and rename the other ones to get to a dataset 11 00:00:29,940 --> 00:00:32,943 -只有两列,单词和标签。 +其中只有两列,即 words 和 labels。 with just two columns, words and labels. 12 00:00:34,200 --> 00:00:36,750 -如果你有自己的令牌分类数据集, +如果你有自己的词元分类数据集, If you have your own dataset for token classification, 13 00:00:36,750 --> 00:00:39,870 -只要确保你清理数据以达到同一点, +请确保你清理数据以达到相同的效果, just make sure you clean your data to get to the same point, 14 00:00:39,870 --> 00:00:43,290 -一列包含单词作为字符串列表 +包括 words 列内含字符列表 with one column containing words as list of strings 15 00:00:43,290 --> 00:00:45,540 -另一个包含标签作为整数 +和另一个 labels 列内含整数 and another containing labels as integers 16 @@ -90,12 +90,12 @@ Here we get them from the dataset features. 19 00:00:54,810 --> 00:00:57,660 -所以你可以将整数映射到一些真实的标签 +所以你检查数据时 So you are able to map the integers to some real labels 20 00:00:57,660 --> 00:00:58,960 -在检查你的数据时。 +可以将整数映射到一些真实的标签。 when inspecting your data. 21 @@ -105,12 +105,12 @@ Here we are doing named entity recognitions, 22 00:01:03,510 --> 00:01:05,430 -所以我们的标签要么是 O +所以对于不属于任何实体的词 so ours labels are either O 23 00:01:05,430 --> 00:01:08,310 -对于不属于任何实体的词。 +我们的标签可能是 O 。 for words that do not belong to any entity. 24 @@ -130,7 +130,7 @@ Each label has two versions. 27 00:01:18,540 --> 00:01:21,960 -B 标签表示一个词开始一个实体 +B 标签表示以实体开始的单词 The B labels indicate a word that begins an entity 28 @@ -145,7 +145,7 @@ The first step to preprocess our data 30 00:01:28,830 --> 00:01:30,660 -是将单词标记化。 +是将单词词元化。 is to tokenize the words. 31 @@ -155,7 +155,7 @@ This is very easily done with the tokenizer. 32 00:01:33,120 --> 00:01:35,370 -我们只需要告诉它我们已经预先标记了数据 +我们只需要告诉它我们已经预先词元化了数据 We just have to tell it we have pre-tokenized the data 33 @@ -170,17 +170,17 @@ Then comes the hard part. 35 00:01:40,380 --> 00:01:42,360 -由于我们添加了特殊标记 +由于我们添加了特殊词元 Since we have added special tokens 36 00:01:42,360 --> 00:01:45,270 -每个单词可能被拆分成几个标记, +每个单词可能被拆分成几个词元, and each word may have been split into several tokens, 37 00:01:45,270 --> 00:01:48,090 -我们的标签将不再与标记匹配。 +我们的标签将不再与词元匹配。 our labels won't match the tokens anymore. 38 @@ -195,12 +195,12 @@ come to the rescue. 40 00:01:53,040 --> 00:01:55,500 -他们将每个标记与其所属的单词匹配 +他们将每个词元与其所属的单词匹配 They match each token to the word it belongs to 41 00:01:55,500 --> 00:01:58,470 -这允许我们将每个标记映射到它的标签。 +这允许我们将每个词元映射到它的标签。 which allows us to map each token to its label. 42 @@ -210,12 +210,12 @@ We just have to make sure 43 00:01:59,303 --> 00:02:01,710 -我们将 B 标签更改为对应的 I +我们针对里面的词元将 B 标签 we change the B labels to their I counterparts 44 00:02:01,710 --> 00:02:03,450 -对于里面的令牌 +更改为对应的 I for tokens that are inside 45 @@ -225,37 +225,37 @@ but not at the beginning of a word. 46 00:02:06,330 --> 00:02:09,120 -特殊标记的标签为 -100, +特殊词元的标签为 -100, The special tokens get a label of -100, 47 00:02:09,120 --> 00:02:11,070 -这就是我们告诉 Transformer 损失函数的方式 +这就是当计算损失时我们告诉 Transformer 损失函数 which is how we tell the Transformer loss functions 48 00:02:11,070 --> 00:02:14,607 -在计算损失时忽略它们。 +要忽略它们。 to ignore them when computing the loss. 49 00:02:14,607 --> 00:02:16,890 -然后代码非常简单。 +然后代码非常简单明了。 The code is then pretty straightforward. 50 00:02:16,890 --> 00:02:18,660 -我们写了一个函数来移动标签 +我们针对可以自定义的单词内的词元 We write a function that shifts the labels 51 00:02:18,660 --> 00:02:21,840 -对于可以自定义的单词内的标记 +写了一个函数来移动标签 for tokens that are inside a word that you can customize 52 00:02:21,840 --> 00:02:24,490 -并在为每个标记生成标签时使用它。 +并在为每个词元生成标签时调用它。 and use it when generating the labels for each token. 53 @@ -280,22 +280,22 @@ we unleash the speed of out fast tokenizers. 57 00:02:37,110 --> 00:02:40,350 -当我们需要创建一个批次时,最后一个问题就来了。 +当我们需要创建一个批处理任务时,最后一个问题就来了。 The last problem comes when we need to create a batch. 58 00:02:40,350 --> 00:02:42,150 -除非你改变了预处理功能 +除非你改变了预处理函数 Unless you changed the preprocessing function 59 00:02:42,150 --> 00:02:43,890 -应用一些固定的填充, +增加一些固定的填充, to apply some fixed padding, 60 00:02:43,890 --> 00:02:45,900 -我们会得到各种长度的句子, +否则我们会得到各种长度的句子, we will get sentences of various lengths, 61 @@ -305,17 +305,17 @@ which we need to pad to the same length. 62 00:02:48,930 --> 00:02:50,730 -填充需要应用于输入 +填充处理需要在输入 The padding needs to be applied to the inputs 63 00:02:50,730 --> 00:02:51,900 -以及标签, +以及标签上进行, as well as the labels, 64 00:02:51,900 --> 00:02:53,950 -因为每个标记我们应该有一个标签。 +因为每个词元应该有一个标签。 since we should have one label per token. 65 @@ -330,12 +330,12 @@ for the loss computation. 67 00:03:00,420 --> 00:03:01,560 -这一切都为我们完成了 +所有这一切 This is all done for us 68 00:03:01,560 --> 00:03:04,050 -通过 DataCollatorForTokenClassification, +都通过 DataCollatorForTokenClassification 为我们完成了, by the DataCollatorForTokenClassification, 69 @@ -345,12 +345,12 @@ which you can use in PyTorch or TensorFlow. 70 00:03:06,740 --> 00:03:08,880 -有了所有这些,你就可以准备发送数据了 +完成上述内容后,你就可以准备发送数据了 With all of this, you are either ready to send your data 71 00:03:08,880 --> 00:03:11,190 -并将此数据整理器交给培训师, +并将此数据整理器传递给 Trainer, and this data collator to the Trainer, 72 diff --git a/subtitles/zh-CN/56_data-processing-for-masked-language-modeling.srt b/subtitles/zh-CN/56_data-processing-for-masked-language-modeling.srt index 5aabd947f..b94c74449 100644 --- a/subtitles/zh-CN/56_data-processing-for-masked-language-modeling.srt +++ b/subtitles/zh-CN/56_data-processing-for-masked-language-modeling.srt @@ -5,12 +5,12 @@ 2 00:00:05,250 --> 00:00:07,230 -- 让我们看看如何预处理我们的数据 +- 让我们看一下如何针对掩码语言建模 - Let's see how we can preprocess our data 3 00:00:07,230 --> 00:00:08,703 -用于掩码语言建模。 +预处理我们的数据。 for masked language modeling. 4 @@ -20,7 +20,7 @@ As a reminder, masked language modeling 5 00:00:12,570 --> 00:00:15,333 -是当模型需要填补句子中的空白时。 +主要在模型需要填补句子中的空白时使用。 is when a model needs to fill the blanks in a sentence. 6 @@ -30,27 +30,27 @@ To do this, you just need texts, no labels, 7 00:00:19,650 --> 00:00:22,200 -因为这是一个自我监督的问题。 +因为这是一个自监督的问题。 as this is a self-supervised problem. 8 00:00:22,200 --> 00:00:23,670 -要将其应用于你自己的数据, +要将其应用于您自己的数据, To apply this on your own data, 9 00:00:23,670 --> 00:00:25,740 -只要确保你收集了所有的文本 +只要确保您在数据集的一列中 just make sure you have all your texts gathered 10 00:00:25,740 --> 00:00:27,603 -在数据集的一列中。 +收集了所有的文本。 in one column of your dataset. 11 00:00:28,440 --> 00:00:30,480 -在我们开始随机掩盖事物之前, +在开始随机掩码处理之前, Before we start randomly masking things, 12 @@ -60,7 +60,7 @@ we will need to somehow make all those texts the same length 13 00:00:33,090 --> 00:00:34,263 -将它们一起批处理。 +从而将它们一起批处理。 to batch them together. 14 @@ -70,7 +70,7 @@ The first way to make all the texts the same length 15 00:00:38,490 --> 00:00:40,590 -是我们在文本分类中使用的那个。 +和我们在文本分类中所使用的相同。 is the one we used in text classification. 16 @@ -95,27 +95,27 @@ this is all done by our tokenizer 20 00:00:49,923 --> 00:00:53,130 -具有正确的填充和截断选项。 +并且配置相应的填充和截断选项。 with the right options for padding and truncation. 21 00:00:53,130 --> 00:00:56,100 -但是,这会使我们丢失很多文本 +如果与我们选择的上下文长度相比, This will however make us lose a lot of texts 22 00:00:56,100 --> 00:00:58,620 -如果我们数据集中的示例很长, +我们数据集的示例很长, if the examples in our dataset are very long, 23 00:00:58,620 --> 00:01:00,960 -与我们选择的上下文长度相比。 +就会使我们丢失很多文本。 compared to the context length we picked. 24 00:01:00,960 --> 00:01:03,393 -在这里,所有灰色部分都丢失了。 +在这里,所有标记灰色部分都丢失了。 Here, all the portion in gray is lost. 25 @@ -125,17 +125,17 @@ This is why a second way to generate samples of text 26 00:01:06,660 --> 00:01:08,820 -具有相同的长度是分块我们的文本 +具有相同的长度是为了在上下文长度中 with the same length is to chunk our text 27 00:01:08,820 --> 00:01:10,560 -在上下文长度中, +为我们的文本分块 in pieces of context lengths, 28 00:01:10,560 --> 00:01:14,010 -而不是在第一个块之后丢弃所有内容。 +而不是在第一个数据块之后丢弃所有内容。 instead of discarding everything after the first chunk. 29 @@ -150,7 +150,7 @@ of length smaller than the context size, 31 00:01:17,700 --> 00:01:20,493 -我们可以选择保留和填充或忽略。 +我们可以选择保留并填充或者忽略。 which we can choose to keep and pad or ignore. 32 @@ -160,32 +160,32 @@ Here is how we can apply this in practice, 33 00:01:23,790 --> 00:01:26,460 -只需添加 return overflowing tokens 选项 +只需在我们调用分词器时添加 return overflowing tokens by just adding the return overflowing tokens option 34 00:01:26,460 --> 00:01:28,200 -在我们的分词器调用中。 +选项 in our tokenizer call. 35 00:01:28,200 --> 00:01:30,243 -请注意这如何为我们提供更大的数据集! +请注意这样会为我们提供更大的数据集! Note how this gives us a bigger dataset! 36 00:01:31,560 --> 00:01:34,260 -这第二种分块方式是理想的,如果你所有的文本 +如果你所有的文本很长, This second way of chunking is ideal if all your texts 37 00:01:34,260 --> 00:01:36,270 -很长,但行不通 +这里第二种分块方式是理想的, are very long, but it won't work 38 00:01:36,270 --> 00:01:39,900 -如果你的课文有不同的长度,那也不错。 +但如果你的课文有不同的长度,那么效果就不尽人意。 as nicely if you have a variety of lengths in the texts. 39 @@ -195,22 +195,22 @@ In this case, 40 00:01:41,040 --> 00:01:44,280 -最好的选择是连接所有标记化的文本 +最好的选择是将所有标记的文本组合成为一个大的数据流 the best option is to concatenate all your tokenized texts 41 00:01:44,280 --> 00:01:46,560 -在一个大流中,有一个特殊的标记 +附加一个特殊的标记 in one big stream, with a special tokens 42 00:01:46,560 --> 00:01:49,800 -指示你何时从一份文件转到另一份文件, +表明你何时从一份文件转到另一份文件, to indicate when you pass from one document to the other, 43 00:01:49,800 --> 00:01:52,503 -然后才将大流分成块。 +然后才将该数据流分成数据块。 and only then split the big stream into chunks. 44 @@ -230,32 +230,32 @@ and another one to chunk it. 47 00:02:00,780 --> 00:02:02,850 -注意它是如何减少样本数量的 +注意在我们这里的数据集中, Notice how it reduces the number of samples 48 00:02:02,850 --> 00:02:04,230 -在我们这里的数据集中, +它是如何减少样本数量的 in our dataset here, 49 00:02:04,230 --> 00:02:06,580 -一定有不少短条目! +一定有大量短条目! there must have been quite a few short entries! 50 00:02:07,710 --> 00:02:11,130 -完成此操作后,掩码就很容易了。 +完成此操作后,掩码处理就很容易了。 Once this is done, the masking is the easy part. 51 00:02:11,130 --> 00:02:13,400 -有专门为此设计的数据整理器 +在 Transformers 库中有专门为此设计的 There is a data collator designed specifically for this 52 00:02:13,400 --> 00:02:15,540 -在变形金刚图书馆。 +数据整理器。 in the Transformers library. 53 @@ -265,7 +265,7 @@ You can use it directly in the Trainer, 54 00:02:17,700 --> 00:02:20,400 -或者将你的数据集转换为张量流数据集时 +或者将你的数据集转换为 tensorflow 数据集时 or when converting your datasets to tensorflow datasets 55 diff --git a/subtitles/zh-CN/57_what-is-perplexity.srt b/subtitles/zh-CN/57_what-is-perplexity.srt index 8c1141ddd..b78d4fe1f 100644 --- a/subtitles/zh-CN/57_what-is-perplexity.srt +++ b/subtitles/zh-CN/57_what-is-perplexity.srt @@ -15,22 +15,22 @@ 4 00:00:05,379 --> 00:00:06,720 -- 在这段视频中,我们来看看 +- 在这段视频中,我们来了解一下 - In this video, we take a look 5 00:00:06,720 --> 00:00:09,483 -在称为困惑度的神秘测深指标上。 +在称为困惑度的评估指标上。 at the mysterious sounding metric called perplexity. 6 00:00:11,070 --> 00:00:12,630 -你可能遇到过困惑 +你在研究生成模型时 You might have encountered perplexity 7 00:00:12,630 --> 00:00:14,970 -在阅读生成模型时。 +可能遇到过困惑度。 when reading about generative models. 8 @@ -40,7 +40,7 @@ You can see two examples here, 9 00:00:16,680 --> 00:00:18,577 -一张来自原始变压器纸, +一个来自最初的 transformer 论文, one from the original transformer paper, 10 @@ -50,17 +50,17 @@ one from the original transformer paper, 11 00:00:19,950 --> 00:00:23,340 -另一篇来自最近的 GPT-2 论文。 +另一个来自最近的 GPT-2 论文。 and the other one from the more recent GPT-2 paper. 12 00:00:23,340 --> 00:00:25,740 -困惑度是衡量绩效的常用指标 +困惑度是衡量语言模型的性能 Perplexity is a common metric to measure the performance 13 00:00:25,740 --> 00:00:27,150 -的语言模型。 +的常用指标。 of language models. 14 @@ -70,7 +70,7 @@ The smaller its value, the better the performance. 15 00:00:30,000 --> 00:00:32,950 -但它究竟意味着什么,我们又该如何计算呢? +但它究竟意味着什么,我们又该如何计算得到它呢? But what does it actually mean and how can we calculate it? 16 @@ -80,32 +80,32 @@ A very common quantity in machine learning 17 00:00:36,180 --> 00:00:37,650 -是可能性。 +是相似度。 is the likelihood. 18 00:00:37,650 --> 00:00:39,240 -我们可以计算可能性 +我们可以计算似然性 We can calculate the likelihood 19 00:00:39,240 --> 00:00:42,390 -作为每个标记概率的乘积。 +作为每个词元的概率的乘积。 as the product of each token's probability. 20 00:00:42,390 --> 00:00:44,730 -这意味着对于每个令牌, +这意味着对于每个词元, What this means is that for each token, 21 00:00:44,730 --> 00:00:47,340 -我们使用语言模型来预测它的概率 +我们使用语言模型基于之前的词元 we use the language model to predict its probability 22 00:00:47,340 --> 00:00:49,560 -基于之前的标记。 +来预测它的概率。 based on the previous tokens. 23 @@ -115,12 +115,12 @@ In the end, we multiply all probabilities 24 00:00:52,050 --> 00:00:53,253 -得到的可能性。 +从而得到似然性。 to get the likelihood. 25 00:00:55,892 --> 00:00:57,000 -有可能, +通过似然性, With the likelihood, 26 @@ -135,27 +135,27 @@ the cross-entropy. 28 00:01:01,200 --> 00:01:03,450 -你可能已经听说过交叉熵 +当你接触损失函数时 You might have already heard about cross-entropy 29 00:01:03,450 --> 00:01:05,670 -在查看损失函数时。 +可能已经听说过交叉熵。 when looking at loss function. 30 00:01:05,670 --> 00:01:09,210 -它通常用作分类中的损失函数。 +它通常在分类中作为损失函数使用。 It is often used as a loss function in classification. 31 00:01:09,210 --> 00:01:11,610 -在语言建模中,我们预测下一个标记 +在语言建模中,我们基于之前的词元 In language modeling, we predict the next token 32 00:01:11,610 --> 00:01:12,930 -基于之前的令牌, +预测下一个词元, based on the previous token, 33 @@ -185,32 +185,32 @@ with its inputs as labels. 38 00:01:23,580 --> 00:01:26,433 -然后损失对应于交叉熵。 +其损失与交叉熵相关。 The loss then corresponds to the cross-entropy. 39 00:01:29,130 --> 00:01:31,110 -我们现在只差一个手术了 +现在对于计算困惑度 We are now only a single operation away 40 00:01:31,110 --> 00:01:33,510 -从计算困惑度。 +我们现在只差一个操作了。 from calculating the perplexity. 41 00:01:33,510 --> 00:01:37,710 -通过对交叉熵取幂,我们得到了困惑。 +通过对交叉熵取幂,我们得到了困惑度。 By exponentiating the cross-entropy, we get the perplexity. 42 00:01:37,710 --> 00:01:40,260 -所以你看到困惑是密切相关的 +所以你可以发现困惑度和损失 So you see that the perplexity is closely related 43 00:01:40,260 --> 00:01:41,163 -到损失。 +是密切相关的。 to the loss. 44 @@ -220,27 +220,27 @@ Plugging in previous results 45 00:01:43,380 --> 00:01:47,010 -表明这相当于求幂 +表明这相当于对每个词元 shows that this is equivalent to exponentiating 46 00:01:47,010 --> 00:01:51,033 -每个令牌的负平均锁定概率。 +的负平均锁定概率取幂值。 the negative average lock probability of each token. 47 00:01:52,050 --> 00:01:54,630 -请记住,损失只是一个弱代理 +请记住,损失只是针对模型 Keep in mind that the loss is only a weak proxy 48 00:01:54,630 --> 00:01:57,360 -用于模型生成高质量文本的能力 +生成高质量文本的能力的一个弱代理 for a model's ability to generate quality text 49 00:01:57,360 --> 00:02:00,510 -困惑也是如此。 +困惑度也是如此。 and the same is true for perplexity. 50 @@ -255,7 +255,7 @@ more sophisticated metrics 52 00:02:03,840 --> 00:02:07,413 -例如生成任务上的 BLEU 或 ROUGE。 +例如生成式任务上的 BLEU 或 ROUGE。 such as BLEU or ROUGE on generative tasks. 53 diff --git a/subtitles/zh-CN/58_what-is-domain-adaptation.srt b/subtitles/zh-CN/58_what-is-domain-adaptation.srt index 7f3757b1e..b623e5bde 100644 --- a/subtitles/zh-CN/58_what-is-domain-adaptation.srt +++ b/subtitles/zh-CN/58_what-is-domain-adaptation.srt @@ -15,7 +15,7 @@ 4 00:00:05,910 --> 00:00:07,923 -- 什么是领域适应? +- 什么是域适配? - What is domain adaptation? 5 @@ -25,12 +25,12 @@ When fine-tuning a pre-trained model on a new dataset, 6 00:00:12,540 --> 00:00:15,480 -我们获得的微调模型将做出预测 +我们适配新的数据集所获得的微调模型 the fine-tuned model we obtain will make predictions 7 00:00:15,480 --> 00:00:17,433 -适应这个新数据集。 +将做出预测。 that are attuned to this new dataset. 8 @@ -40,47 +40,47 @@ When the two models are trained with the same task, 9 00:00:21,840 --> 00:00:25,320 -然后我们可以比较他们对相同输入的预测。 +我们可以使用相同的输入比较他们的预测结果。 we can then compare their predictions on the same input. 10 00:00:25,320 --> 00:00:27,870 -两个模型的预测会有所不同 +两个模型的预测结果 The predictions of the two models will be different 11 00:00:27,870 --> 00:00:29,790 -以反映差异的方式 +会以一种方式反映 in a way that reflects the differences 12 00:00:29,790 --> 00:00:31,680 -在两个数据集之间, +两个数据集之间的差别 between the two datasets, 13 00:00:31,680 --> 00:00:34,053 -我们称之为领域适应的现象。 +就是我们称之为域适配的现象。 a phenomenon we call domain adaptation. 14 00:00:35,310 --> 00:00:38,640 -让我们看一个带有掩码语言建模的例子 +让我们通过带有版本微调 Let's look at an example with masked language modeling 15 00:00:38,640 --> 00:00:41,910 -通过比较预训练的 DistilBERT 模型的输出 +比较预训练的 DistilBERT 模型的输出 by comparing the outputs of the pre-trained DistilBERT model 16 00:00:41,910 --> 00:00:43,080 -版本微调 +看一个和掩码语言建模相关的例子 with the version fine-tuned 17 00:00:43,080 --> 00:00:45,273 -在课程的第 7 章中,链接如下。 +该内容在课程的第 7 章中,链接如下。 in chapter 7 of the course, linked below. 18 @@ -100,27 +100,27 @@ has its first two predictions linked to cinema. 21 00:00:54,390 --> 00:00:57,210 -由于它在电影评论数据集上进行了微调, +由于它是基于电影评论数据集上进行了微调, Since it was fine-tuned on a movie reviews dataset, 22 00:00:57,210 --> 00:00:58,680 -看到是完全正常的 +因此它像这样调整它的推荐结果 it's perfectly normal to see 23 00:00:58,680 --> 00:01:01,440 -它像这样调整了它的建议。 +是完全正常的。 it adapted its suggestions like this. 24 00:01:01,440 --> 00:01:03,090 -注意它如何保持相同的预测 +注意它作为之后的预训练模型 Notice how it keeps the same prediction 25 00:01:03,090 --> 00:01:05,220 -作为之后的预训练模型。 +如何保持相同的预测。 as the pre-trained model afterward. 26 @@ -130,7 +130,7 @@ Even if the fine-tuned model adapts to the new dataset, 27 00:01:08,100 --> 00:01:10,450 -它不会忘记预先训练的内容。 +它不会遗失预先训练的内容。 it's not forgetting what it was pre-trained on. 28 @@ -140,57 +140,57 @@ This is another example on a translation task. 29 00:01:14,220 --> 00:01:17,310 -最重要的是,我们使用预训练的法语 / 英语模型, +在上面的代码里,我们使用预训练的法语 / 英语模型, On top, we use a pre-trained French/English model, 30 00:01:17,310 --> 00:01:21,330 -在底部,我们在第 7 章中微调的版本。 +在下面的代码里,是我们在第 7 章中微调的版本。 and at the bottom, the version we fine-tuned in chapter 7. 31 00:01:21,330 --> 00:01:23,610 -顶级模型在大量文本上进行了预训练, +上面的模型在大量文本上进行了预训练, The top model is pre-trained on lots of texts, 32 00:01:23,610 --> 00:01:25,170 -并留下技术英语术语, +并保留了英文中的技术术语, and leaves technical English terms, 33 00:01:25,170 --> 00:01:28,350 -像插件和电子邮件,翻译不变。 +像 plugin 和 email 这样的单词,是不会被翻译的。 like plugin and email, unchanged in the translation. 34 00:01:28,350 --> 00:01:31,350 -两者都被法国人完全理解。 +法国用户都可以很好地理解两者。 Both are perfectly understood by French people. 35 00:01:31,350 --> 00:01:33,780 -为微调选择的数据集是一个数据集 +为微调模型选择的数据集是 The dataset picked for the fine-tuning is a dataset 36 00:01:33,780 --> 00:01:36,660 -特别注意的技术文本 +一个包含技术文本的数据集 of technical texts where special attention was picked 37 00:01:36,660 --> 00:01:39,150 -用法语翻译一切。 +其中特别将所有内容都翻译为法语。 on translating everything in French. 38 00:01:39,150 --> 00:01:42,090 -结果,经过微调的模型选择了那个习惯 +结果,经过微调的模型适应了该特征 As a result, the fine-tuned model picked that habit 39 00:01:42,090 --> 00:01:44,193 -并翻译了插件和电子邮件。 +并翻译了 plugin 和 email 两个词。 and translated both plugin and email. 40 diff --git a/subtitles/zh-CN/59_data-processing-for-translation.srt b/subtitles/zh-CN/59_data-processing-for-translation.srt index 99a02c07d..baae0eb6f 100644 --- a/subtitles/zh-CN/59_data-processing-for-translation.srt +++ b/subtitles/zh-CN/59_data-processing-for-translation.srt @@ -20,22 +20,22 @@ 5 00:00:09,540 --> 00:00:12,420 -这是一个翻译好句子的任务 +这个任务需要将句子用另一种语言 This is a task of well translating a sentence 6 00:00:12,420 --> 00:00:14,310 -用另一种语言。 +进行恰当的翻译。 in another language. 7 00:00:14,310 --> 00:00:17,100 -该视频将重点介绍如何预处理你的数据集 +本视频将重点介绍当你把句子按照下面的格式组织时 This video will focus on how to preprocess your dataset 8 00:00:17,100 --> 00:00:19,950 -一旦你设法把它放在下面的格式中。 +如何预处理你的数据集。 once you've managed to put it in the following format. 9 @@ -45,7 +45,7 @@ One column for input texts and one for the target texts. 10 00:00:23,730 --> 00:00:25,980 -以下是我们如何使用数据集库实现此目的 +以下是我们如何使用 Datasets 库实现此目的 Here is how we can achieve this with the Datasets library 11 @@ -55,27 +55,27 @@ and the KDE4 dataset for English to French translation. 12 00:00:30,870 --> 00:00:33,240 -只要你设法让你的数据看起来像这样, +只要你能够让数据按照这样的形式组织, As long as you manage to have your data look like this, 13 00:00:33,240 --> 00:00:35,440 -你应该能够按照相同的步骤操作。 +就应该可以按照相同的步骤操作。 you should be able to follow the same steps. 14 00:00:36,630 --> 00:00:39,210 -这一次,我们的标签不是整数 +这一次,我们对于某些类的标签不是整数 For once, our labels are not integers 15 00:00:39,210 --> 00:00:42,210 -对应于某些类,但纯文本。 +而是纯文本。 corresponding to some classes, but plain texts. 16 00:00:42,210 --> 00:00:45,810 -因此,我们需要将它们标记化,就像我们的输入一样。 +因此,我们需要将它们词元化,就像我们的输入一样。 We will thus need to tokenize them, like our inputs. 17 @@ -85,12 +85,12 @@ There is a trap there though, 18 00:00:47,370 --> 00:00:49,890 -就好像你像你的输入一样标记你的目标, +好像你按照输入那样词元化你的目标, as if you tokenize your targets like your inputs, 19 00:00:49,890 --> 00:00:51,690 -你会遇到一个问题。 +就会遇到一个问题。 you will hit a problem. 20 @@ -100,42 +100,42 @@ Even if you don't speak French, you might notice 21 00:00:54,090 --> 00:00:57,270 -目标标记化中的一些奇怪的事情。 +目标词元化后出现了一些奇怪的东西。 some weird things in the tokenization of the targets. 22 00:00:57,270 --> 00:01:00,510 -大多数单词都被标记为几个子标记, +大多数单词都被处理为几个子词元, Most of the words are tokenized in several subtokens, 23 00:01:00,510 --> 00:01:03,180 -而鱼,唯一的英语单词之一, +而 fish,文本中唯一的英语单词, while fish, one of the only English word, 24 00:01:03,180 --> 00:01:05,670 -被标记为一个词。 +被词元化为一个单独的词。 is tokenized as a single word. 25 00:01:05,670 --> 00:01:08,703 -那是因为我们的输入已被标记为英语。 +那是因为我们的输入已经按照英语进行词元化。 That's because our inputs have been tokenized as English. 26 00:01:09,660 --> 00:01:11,430 -由于我们的模型知道两种语言, +由于我们的模型涉及两种语言, Since our model knows two languages, 27 00:01:11,430 --> 00:01:13,800 -你必须在标记目标时警告它 +你必须在词元化目标时提示它 you have to warn it when tokenizing the targets 28 00:01:13,800 --> 00:01:16,230 -所以它切换到法语模式。 +从而切换到法语模式。 so it switches in French mode. 29 @@ -145,7 +145,7 @@ This is done with the as_target_tokenizer context manager. 30 00:01:20,010 --> 00:01:23,343 -你可以看到它如何导致更紧凑的标记化。 +你可以看到它如何导致更紧凑的词元化。 You can see how it results in a more compact tokenization. 31 @@ -155,42 +155,42 @@ Processing the whole dataset 32 00:01:25,890 --> 00:01:28,440 -然后使用地图功能非常容易。 +然后配合 map 函数使用非常简单。 is then super easy with the map function. 33 00:01:28,440 --> 00:01:30,207 -你可以选择不同的最大长度 +你可以为输入和目标输出 You can pick different maximum lengths 34 00:01:30,207 --> 00:01:32,100 -对于输入和目标, +选择不同的最大长度, for the inputs and targets, 35 00:01:32,100 --> 00:01:34,530 -并选择在此阶段填充到最大长度 +并通过设置 padding=max_length and choose to pad at this stage to that maximum length 36 00:01:34,530 --> 00:01:36,273 -通过设置 padding=max_length。 +在此阶段填充到最大长度。 by setting padding=max_length. 37 00:01:37,230 --> 00:01:39,300 -这里我们将向你展示动态填充 +由于还需要再多一步 Here we'll show you to pad dynamically 38 00:01:39,300 --> 00:01:41,013 -因为它还需要一步。 +所以这里我们将向你展示动态填充。 as it requires one more step. 39 00:01:42,450 --> 00:01:43,470 -你的投入和目标 +你的输入和目标结果 Your inputs and targets 40 @@ -200,17 +200,17 @@ are all sentences of various lengths. 41 00:01:46,080 --> 00:01:48,510 -我们将分别填充输入和目标, +由于输入和目标结果的最大长度可能会不同, We will pad the inputs and targets separately, 42 00:01:48,510 --> 00:01:50,460 -作为输入和目标的最大长度 +我们将分别填充输入 as the maximum lengths of the inputs and targets 43 00:01:50,460 --> 00:01:51,483 -可能不同。 +和目标结果。 might be different. 44 @@ -220,27 +220,27 @@ Then we pad the inputs with the pad token 45 00:01:54,540 --> 00:01:57,060 -以及索引为 -100 的目标 +以及索引为 -100 的目标输出 and the targets with the -100 index 46 00:01:57,060 --> 00:01:58,890 -确保它们不被考虑在内 +确保它们不被包含在 to make sure they're not taken into account 47 00:01:58,890 --> 00:02:00,123 -在损失计算中。 +损失计算中。 in the loss computation. 48 00:02:01,320 --> 00:02:02,153 -一旦完成, +一旦完成上述操作, Once this is done, 49 00:02:02,153 --> 00:02:04,340 -批处理输入和目标变得超级容易。 +批处理输入和目标输出变得超级容易。 batching inputs and targets become super easy. 50 @@ -255,18 +255,18 @@ to do this all automatically. 52 00:02:10,500 --> 00:02:13,800 -然后你可以将它与你的数据集一起传递给培训师 +之后你可以在你的 Keras 模型上使用 model.fit () 之前 You can then pass it to the Trainer with your datasets 53 00:02:13,800 --> 00:02:15,960 -或者在 to_tf_dataset 方法中使用它 +将它与你的数据集一起传递给 Trainer or use it in the to_tf_dataset method 54 00:02:15,960 --> 00:02:18,560 -在你的(模糊的)模型上使用 model.fit () 之前。 -before using model.fit () on your (indistinct) model. +或者在 to_tf_dataset 方法中使用它。 +before using model.fit () on your Keras model. 55 00:02:21,057 --> 00:02:23,724 diff --git a/subtitles/zh-CN/60_what-is-the-bleu-metric.srt b/subtitles/zh-CN/60_what-is-the-bleu-metric.srt index e3fce657e..7de1b2163 100644 --- a/subtitles/zh-CN/60_what-is-the-bleu-metric.srt +++ b/subtitles/zh-CN/60_what-is-the-bleu-metric.srt @@ -20,52 +20,52 @@ 5 00:00:07,650 --> 00:00:10,170 -对于许多 NLP 任务,我们可以使用通用指标 +对于许多 NLP 任务,我们可以使用常见指标 For many NLP tasks we can use common metrics 6 00:00:10,170 --> 00:00:12,810 -比如准确性或 F1 分数,但你会做什么 +比如准确性或 F1 分数, like accuracy or F1 score, but what do you do 7 00:00:12,810 --> 00:00:14,340 -当你想衡量文本的质量时 +但是当你想衡量模型所翻译的文本的质量时 when you wanna measure the quality of text 8 00:00:14,340 --> 00:00:16,560 -那是从模型翻译过来的? +该如何评估呢? that's been translated from a model? 9 00:00:16,560 --> 00:00:18,750 -在本视频中,我们将了解一个广泛使用的指标 +在本视频中,我们将为大家介绍一个 In this video, we'll take a look at a widely used metric 10 00:00:18,750 --> 00:00:20,613 -用于称为 BLEU 的机器翻译。 +广泛使用于机器翻译的指标,叫做 BLEU 。 for machine translation called BLEU. 11 00:00:22,290 --> 00:00:23,940 -BLEU 背后的基本思想是分配 +BLEU 背后的基本逻辑是 The basic idea behind BLEU is to assign 12 00:00:23,940 --> 00:00:26,250 -翻译的单一数字分数 +为每个翻译分配一个单独的数字评分 a single numerical score to a translation 13 00:00:26,250 --> 00:00:27,450 -这告诉我们它有多好 +用于评估 that tells us how good it is 14 00:00:27,450 --> 00:00:30,199 -与一个或多个参考翻译相比。 +它与一个或者多个翻译相比,其质量的优劣。 compared to one or more reference translations. 15 @@ -80,12 +80,12 @@ that has been translated into English by some model. 17 00:00:35,340 --> 00:00:37,170 -如果我们比较生成的翻译 +如果我们将生成的翻译 If we compare the generated translation 18 00:00:37,170 --> 00:00:39,150 -一些参考人工翻译, +与一些用于参照的人工翻译进行比较, to some reference human translations, 19 @@ -100,47 +100,47 @@ but has made a common error. 21 00:00:43,260 --> 00:00:46,050 -西班牙语单词 tengo 在英语中的意思是, +西班牙语单词 tengo 在英语中的意思是 have, The Spanish word tengo means have in English, 22 00:00:46,050 --> 00:00:48,700 -这种一对一的翻译不太自然。 +这种一一对应的直译不太自然。 and this one-to-one translation is not quite natural. 23 00:00:49,890 --> 00:00:51,270 -那么我们如何衡量质量 +那么对于使用某种自动的方法生成的翻译 So how can we measure the quality 24 00:00:51,270 --> 00:00:54,270 -以某种自动方式生成的翻译? +我们如何来评估它的质量呢? of a generated translation in some automatic way? 25 00:00:54,270 --> 00:00:56,730 -BLEU 采用的方法是比较 n-gram +BLEU 采用的方法是 The approach that BLEU takes is to compare the n-grams 26 00:00:56,730 --> 00:00:58,550 -生成的 n-gram 翻译 +将所生成翻译的 n-gram 和参照的翻译的 n-gram of the generated translation to the n-grams 27 00:00:58,550 --> 00:01:00,390 -在参考资料中。 +进行比较。 in the references. 28 00:01:00,390 --> 00:01:02,400 -现在,n-gram 只是一种奇特的说法 +现在,n-gram 只是用于描述 n 个单词的 Now, an n-gram is just a fancy way of saying 29 00:01:02,400 --> 00:01:03,960 -一大块 n 个单词。 +一种奇特的说法。 a chunk of n words. 30 @@ -150,32 +150,32 @@ So let's start with unigrams, 31 00:01:05,220 --> 00:01:08,020 -对应于句子中的单个单词。 +它对应于句子中的单个单词。 which corresponds to the individual words in a sentence. 32 00:01:08,880 --> 00:01:11,250 -在此示例中,你可以看到其中四个单词 +在此示例中,你可以看到所生成的翻译中有四个单词 In this example, you can see that four of the words 33 00:01:11,250 --> 00:01:13,140 -在生成的翻译中也发现 +在参照的翻译的其中一个 in the generated translation are also found 34 00:01:13,140 --> 00:01:14,990 -在其中一个参考翻译中。 +也出现了。 in one of the reference translations. 35 00:01:16,350 --> 00:01:18,240 -一旦我们找到了我们的比赛, +一旦我们找到了匹配项, And once we've found our matches, 36 00:01:18,240 --> 00:01:20,130 -一种给译文打分的方法 +给译文打分的一种方法 one way to assign a score to the translation 37 @@ -185,22 +185,22 @@ is to compute the precision of the unigrams. 38 00:01:23,070 --> 00:01:25,200 -这意味着我们只计算匹配词的数量 +这意味着我们在生成的和参考的翻译中 This means we just count the number of matching words 39 00:01:25,200 --> 00:01:27,360 -在生成的和参考的翻译中 +只计算匹配词的数量 in the generated and reference translations 40 00:01:27,360 --> 00:01:29,660 -并通过除以单词数来归一化计数 +并且通过除以生成结果的单词数 and normalize the count by dividing by the number of words 41 00:01:29,660 --> 00:01:30,753 -在这一代。 +来归一化计数值。 in the generation. 42 @@ -210,7 +210,7 @@ In this example, we found four matching words 43 00:01:34,080 --> 00:01:36,033 -而我们这一代人有五个字。 +而我们的生成结果中有五个单词。 and our generation has five words. 44 @@ -225,7 +225,7 @@ and higher precision scores mean a better translation. 46 00:01:44,160 --> 00:01:45,570 -但这并不是故事的全部 +但是到这里还没有结束 But this isn't really the whole story 47 @@ -235,17 +235,17 @@ because one problem with unigram precision 48 00:01:47,310 --> 00:01:49,140 -翻译模型有时会卡住吗 +翻译模型有时会在重复的句式上卡住 is that translation models sometimes get stuck 49 00:01:49,140 --> 00:01:51,330 -以重复的方式重复同一个词 +这样的句式会很多次重复 in repetitive patterns and just repeat the same word 50 00:01:51,330 --> 00:01:52,293 -几次。 +某一个单词。 several times. 51 @@ -260,12 +260,12 @@ we can get really high precision scores 53 00:01:56,370 --> 00:01:57,840 -虽然翻译很烂 +即使从人类的角度来看 even though the translation is terrible 54 00:01:57,840 --> 00:01:59,090 -从人的角度来看! +这个翻译很糟糕! from a human perspective! 55 @@ -280,32 +280,32 @@ we get a perfect unigram precision score. 57 00:02:06,960 --> 00:02:09,930 -所以为了处理这个问题,BLEU 使用了修改后的精度 +所以为了解决这个问题,BLEU 使用了修改后的精度 So to handle this, BLEU uses a modified precision 58 00:02:09,930 --> 00:02:12,210 -剪掉计算一个单词的次数, +基于它出现在参考翻译中 that clips the number of times to count a word, 59 00:02:12,210 --> 00:02:13,680 -基于最大次数 +出现的最大次数 based on the maximum number of times 60 00:02:13,680 --> 00:02:16,399 -它出现在参考翻译中。 +再减掉计算一个单词的次数。 it appears in the reference translation. 61 00:02:16,399 --> 00:02:18,630 -在这个例子中,单词 six 只出现了一次 +在这个例子中,单词 six 只在参考翻译中出现了一次 In this example, the word six only appears once 62 00:02:18,630 --> 00:02:21,360 -在参考中,所以我们把分子剪成一 +所以我们把分子改为 1 in the reference, so we clip the numerator to one 63 @@ -335,27 +335,27 @@ the order in which the words appear in the translations. 68 00:02:33,900 --> 00:02:35,700 -例如,假设我们有 Yoda +例如,假设我们有 Yoda 为我们 For example, suppose we had Yoda 69 00:02:35,700 --> 00:02:37,410 -翻译我们的西班牙语句子, +翻译西班牙语句子, translate our Spanish sentence, 70 00:02:37,410 --> 00:02:39,457 -那么我们可能会得到一些倒退的东西,比如, +那么我们可能会得到一些退步的结果, then we might get something backwards like, 71 00:02:39,457 --> 00:02:42,450 -“我已经六十岁了。” +比如,“Years sixty thirty have I.” "Years sixty thirty have I." 72 00:02:42,450 --> 00:02:44,670 -在这种情况下,修改后的 unigram 精度 +在这种情况下,修改后的 unigram 精度值 In this case, the modified unigram precision 73 @@ -370,12 +370,12 @@ So to deal with word ordering problems, 75 00:02:50,460 --> 00:02:52,020 -BLEU 实际计算精度 +BLEU 实际上计算几个不同的 n-gram 精度值, BLEU actually computes the precision 76 00:02:52,020 --> 00:02:55,410 -对于几个不同的 n-gram,然后对结果进行平均。 +然后对结果计算平均值。 for several different n-grams and then averages the result. 77 @@ -385,22 +385,22 @@ For example, if we compare 4-grams, 78 00:02:57,300 --> 00:02:58,830 -我们可以看到没有匹配的块 +我们可以看到翻译中 we can see that there are no matching chunks 79 00:02:58,830 --> 00:03:01,020 -翻译中的四个词, +没有匹配四个词的语块, of four words in the translations, 80 00:03:01,020 --> 00:03:02,913 -所以 4 克精度为 0。 +所以 4-gram 精度为 0。 and so the 4-gram precision is 0. 81 00:03:05,460 --> 00:03:07,560 -现在,计算数据集库中的 BLEU 分数 +现在,使用 Datasets 库计算 BLEU 分数 Now, to compute BLEU scores in Datasets library 82 @@ -420,12 +420,12 @@ provide your model's predictions with their references 85 00:03:13,290 --> 00:03:14,390 -你很高兴去! +然后就一切就绪! and you're good to go! 86 00:03:16,470 --> 00:03:19,200 -输出将包含几个感兴趣的字段。 +输出将包含几个重点的字段。 The output will contain several fields of interest. 87 @@ -435,27 +435,27 @@ The precisions field contains 88 00:03:20,490 --> 00:03:23,133 -每个 n-gram 的所有单独精度分数。 +每个 n-gram 的全部单体精度分数。 all the individual precision scores for each n-gram. 89 00:03:25,050 --> 00:03:26,940 -然后计算 BLEU 分数本身 +然后 BLEU 分数本身 The BLEU score itself is then calculated 90 00:03:26,940 --> 00:03:30,090 -通过取精度分数的几何平均值。 +通过取精度分数的几何平均值进行计算。 by taking the geometric mean of the precision scores. 91 00:03:30,090 --> 00:03:32,790 -默认情况下,所有四个 n-gram 精度的平均值 +默认情况下,所有四个 n-gram 精度的平均值都会输出, And by default, the mean of all four n-gram precisions 92 00:03:32,790 --> 00:03:35,793 -据报道,该指标有时也称为 BLEU-4。 +该指标有时也称为 BLEU-4。 is reported, a metric that is sometimes also called BLEU-4. 93 @@ -465,7 +465,7 @@ In this example, we can see the BLEU score is zero 94 00:03:38,880 --> 00:03:40,780 -因为 4 克精度为零。 +因为 4-gram 精度为零。 because the 4-gram precision was zero. 95 @@ -475,7 +475,7 @@ Now, the BLEU metric has some nice properties, 96 00:03:45,390 --> 00:03:47,520 -但这远非一个完美的指标。 +但这离作为完美评估指标的距离还很远。 but it is far from a perfect metric. 97 @@ -490,12 +490,12 @@ and it's widely used in research 99 00:03:50,970 --> 00:03:52,620 -这样你就可以将你的模型与其他模型进行比较 +这样你就可以基于普遍的基准将你的模型 so you can compare your model against others 100 00:03:52,620 --> 00:03:54,630 -在共同的基准上。 +与其他模型进行比较。 on common benchmarks. 101 @@ -505,12 +505,12 @@ On the other hand, there are several big problems with BLEU, 102 00:03:56,670 --> 00:03:58,830 -包括它不包含语义的事实 +包括实际上它不包含语义 including the fact it doesn't incorporate semantics 103 00:03:58,830 --> 00:04:01,920 -它在非英语语言上很挣扎。 +它不适用于非英语语言。 and it struggles a lot on non-English languages. 104 @@ -525,17 +525,17 @@ is that it assumes the human translations 106 00:04:04,620 --> 00:04:05,820 -已经被代币化 +已经被词元化 have already been tokenized 107 00:04:05,820 --> 00:04:07,320 -这使得比较模型变得困难 +这使得在基于不同的分词器的情况下 and this makes it hard to compare models 108 00:04:07,320 --> 00:04:08,820 -使用不同的分词器。 +比较模型变得困难。 that use different tokenizers. 109 @@ -560,7 +560,7 @@ is to use the SacreBLEU metric, 113 00:04:19,440 --> 00:04:22,830 -它解决了 BLEU 的标记化限制。 +它解决了 BLEU 的词元化限制。 which addresses the tokenization limitations of BLEU. 114 @@ -570,12 +570,12 @@ As you can see in this example, 115 00:04:24,360 --> 00:04:26,580 -计算 SacreBLEU 分数几乎相同 +计算 SacreBLEU 分数几乎与 BLEU computing the SacreBLEU score is almost identical 116 00:04:26,580 --> 00:04:28,020 -到 BLEU 一个。 +完全一致。 to the BLEU one. 117 @@ -590,7 +590,7 @@ instead of a list of words to the translations, 119 00:04:32,640 --> 00:04:35,640 -SacreBLEU 负责底层的代币化。 +SacreBLEU 负责底层的词元化。 and SacreBLEU takes care of the tokenization under the hood. 120 diff --git a/subtitles/zh-CN/61_data-processing-for-summarization.srt b/subtitles/zh-CN/61_data-processing-for-summarization.srt index 1a567a7ed..79a0eddf2 100644 --- a/subtitles/zh-CN/61_data-processing-for-summarization.srt +++ b/subtitles/zh-CN/61_data-processing-for-summarization.srt @@ -15,12 +15,12 @@ 4 00:00:05,550 --> 00:00:08,450 -- 让我们看看如何预处理数据集以进行汇总。 +- 让我们看看如何预处理数据集以进行文本摘要。 - Let's see how to preprocess a dataset for summarization. 5 00:00:09,750 --> 00:00:13,083 -这是总结一份长文档的任务。 +这是概括一份长文档的任务。 This is the task of, well, summarizing a long document. 6 @@ -30,32 +30,32 @@ This video will focus on how to preprocess your dataset 7 00:00:16,830 --> 00:00:19,680 -一旦你设法将其放入以下格式: +一旦你成功将其按照以下格式处理: once you have managed to put it in the following format: 8 00:00:19,680 --> 00:00:21,510 -一栏用于长文件, +用一列表示长文件, one column for the long documents, 9 00:00:21,510 --> 00:00:23,610 -和一个摘要。 +和一列表示摘要。 and one for the summaries. 10 00:00:23,610 --> 00:00:24,930 -这是我们如何实现这一目标 +这是我们如何使用 XSUM 数据集上的 Here is how we can achieve this 11 00:00:24,930 --> 00:00:27,573 -使用 XSUM 数据集上的数据集库。 +Datasets 库实现这一效果。 with the Datasets library on the XSUM dataset. 12 00:00:28,650 --> 00:00:30,810 -只要你设法让你的数据看起来像这样, +只要你能够让你的数据以如下形式呈现, As long as you manage to have your data look like this, 13 @@ -65,17 +65,17 @@ you should be able to follow the same steps. 14 00:00:33,690 --> 00:00:35,880 -这一次,我们的标签不是整数 +这一次,我们的标签对于某些类不再是整数 For once, our labels are not integers 15 00:00:35,880 --> 00:00:39,150 -对应于某些类,但纯文本。 +而是纯文本。 corresponding to some classes, but plain text. 16 00:00:39,150 --> 00:00:42,480 -因此,我们需要将它们标记化,就像我们的输入一样。 +因此,我们需要将它们词元化,就像我们的输入数据一样。 We will thus need to tokenize them, like our inputs. 17 @@ -85,22 +85,22 @@ There is a small trap there though, 18 00:00:43,920 --> 00:00:45,360 -因为我们需要标记我们的目标 +因为我们需要 as we need to tokenize our targets 19 00:00:45,360 --> 00:00:48,690 -在 as_target_tokenizer 上下文管理器中。 +在 as_target_tokenizer 上下文管理器中词元化我们的目标输出。 inside the as_target_tokenizer context manager. 20 00:00:48,690 --> 00:00:51,030 -这是因为我们添加的特殊标记 +这是因为我们添加的特殊词元 This is because the special tokens we add 21 00:00:51,030 --> 00:00:54,000 -输入和目标可能略有不同, +其输入和目标输出可能略有不同, might be slightly different for the inputs and the target, 22 @@ -110,82 +110,82 @@ so the tokenizer has to know which one it is processing. 23 00:00:57,300 --> 00:00:59,550 -处理整个数据集非常容易 +通过 map 函数处理整个数据集 Processing the whole dataset is then super easy 24 00:00:59,550 --> 00:01:01,290 -与地图功能。 +非常容易。 with the map function. 25 00:01:01,290 --> 00:01:03,450 -由于摘要通常要短得多 +由于摘要相比文件, Since the summaries are usually much shorter 26 00:01:03,450 --> 00:01:05,400 -比文件,你绝对应该选择 +通常要短得多, than the documents, you should definitely pick 27 00:01:05,400 --> 00:01:08,880 -输入和目标的不同最大长度。 +你应该针对输入和目标输出选择不同的最大长度设定。 different maximum lengths for the inputs and targets. 28 00:01:08,880 --> 00:01:11,730 -你可以选择在此阶段填充到最大长度 +你可以通过设置 padding=max_length 在此阶段 You can choose to pad at this stage to that maximum length 29 00:01:11,730 --> 00:01:14,070 -通过设置 padding=max_length。 +选择填充到最大长度。 by setting padding=max_length. 30 00:01:14,070 --> 00:01:16,170 -在这里,我们将向你展示如何动态填充, +因为它还需要一步,在这里, Here we'll show you how to pad dynamically, 31 00:01:16,170 --> 00:01:17,620 -因为它还需要一步。 +我们将向你展示如何动态填充。 as it requires one more step. 32 00:01:18,840 --> 00:01:20,910 -你的输入和目标都是句子 +你的输入和目标输出 Your inputs and targets are all sentences 33 00:01:20,910 --> 00:01:22,620 -各种长度。 +都是各种长度的句子。 of various lengths. 34 00:01:22,620 --> 00:01:24,960 -我们将分别填充输入和目标 +由于输入和目标输出的最大长度均不相同 We'll pad the inputs and targets separately 35 00:01:24,960 --> 00:01:27,030 -作为输入和目标的最大长度 +我们将分别填充输入 as the maximum lengths of the inputs and targets 36 00:01:27,030 --> 00:01:28,280 -是完全不同的。 +和目标输出。 are completely different. 37 00:01:29,130 --> 00:01:31,170 -然后,我们将输入填充到最大长度 +然后,我们将输入数据 Then, we pad the inputs to the maximum lengths 38 00:01:31,170 --> 00:01:33,813 -在输入之间,对于目标也是如此。 +填充到最大长度,对于目标输出数据也是如此。 among the inputs, and same for the target. 39 @@ -195,42 +195,42 @@ We pad the input with the pad token, 40 00:01:36,630 --> 00:01:39,000 -以及索引为 -100 的目标 +和索引为 -100 的目标输出 and the targets with the -100 index 41 00:01:39,000 --> 00:01:40,980 -确保不考虑它们 +确保在损失计算中 to make sure they are not taken into account 42 00:01:40,980 --> 00:01:42,180 -在损失计算中。 +不会包含它们。 in the loss computation. 43 00:01:43,440 --> 00:01:45,180 -变形金刚库为我们提供 +Transformers 库为我们提供 The Transformers library provide us 44 00:01:45,180 --> 00:01:48,510 -使用数据整理器自动完成这一切。 +数据整理器以自动完成这一切。 with a data collator to do this all automatically. 45 00:01:48,510 --> 00:01:51,690 -然后你可以将它与你的数据集一起传递给培训师, +然后你可以将它与你的数据集一起传递给 Trainer, You can then pass it to the Trainer with your datasets, 46 00:01:51,690 --> 00:01:55,710 -或者在使用 model.fit 之前在 to_tf_dataset 方法中使用它 +或者在你当前的模型上使用 model.fit 之前通过 to_tf_dataset 方法 or use it in the to_tf_dataset method before using model.fit 47 00:01:55,710 --> 00:01:56,823 -在你当前的模型上。 +使用它。 on your current model. 48 diff --git a/utils/convert_bilingual_monolingual.py b/utils/convert_bilingual_monolingual.py new file mode 100644 index 000000000..4a8004cdb --- /dev/null +++ b/utils/convert_bilingual_monolingual.py @@ -0,0 +1,61 @@ +#!/usr/bin/python3 +import getopt +import re +import sys + +PATTERN_TIMESTAMP = re.compile('^[0-9][0-9]:[0-9][0-9]:[0-9][0-9],[0-9][0-9][0-9] --> [0-9][0-9]:[0-9][0-9]:[0-9][0-9],[0-9][0-9][0-9]') +PATTERN_NUM = re.compile('\\d+') + + +def main(argv): + inputfile = '' + outputfile = '' + try: + opts, args = getopt.getopt(argv, "hi:o:", ["ifile=", "ofile="]) + except getopt.GetoptError: + print('srt_worker.py -i -o ') + sys.exit(2) + for opt, arg in opts: + if opt == '-h': + print( 'Usage: convert_bilingual_monolingual.py -i -o ') + sys.exit(-2) + elif opt in ("-i", "--ifile"): + inputfile = arg + elif opt in ("-o", "--ofile"): + outputfile = arg + + if not inputfile: + print('no input file is specified.\nUsage: convert_bilingual_monolingual.py -i -o ') + elif not outputfile: + print('no output file is specified.\nUsage: convert_bilingual_monolingual.py -i -o ') + else: + process(inputfile, outputfile) + + +def process(input_file, output): + """ + Convert bilingual caption file to monolingual caption, supported caption file type is srt. + """ + line_count = 0 + with open(input_file) as file: + with open(output, 'a') as output: + for line in file: + if line_count == 0: + line_count += 1 + output.write(line) + elif PATTERN_TIMESTAMP.match(line): + line_count += 1 + output.write(line) + elif line == '\n': + line_count = 0 + output.write(line) + else: + if line_count == 2: + output.write(line) + line_count += 1 + output.close() + print('conversion completed!') + + +if __name__ == "__main__": + main(sys.argv[1:]) From 278c410f3c2cd3ee20cb3c2f2cdd566bf4d0d305 Mon Sep 17 00:00:00 2001 From: lewtun Date: Sat, 24 Dec 2022 22:04:18 +0100 Subject: [PATCH 44/51] Bump release (#426) --- README.md | 2 +- chapters/ko/_toctree.yml | 22 + chapters/ko/chapter2/2.mdx | 353 ++++++++ chapters/ko/chapter8/1.mdx | 17 + chapters/ko/chapter8/2.mdx | 364 ++++++++ chapters/ko/chapter8/3.mdx | 163 ++++ chapters/ko/chapter8/4.mdx | 775 ++++++++++++++++++ chapters/ko/chapter8/4_tf.mdx | 488 +++++++++++ chapters/ko/chapter8/5.mdx | 90 ++ chapters/ko/chapter8/6.mdx | 11 + chapters/ko/chapter8/7.mdx | 204 +++++ subtitles/en/62_what-is-the-rouge-metric.srt | 6 +- .../zh-CN/62_what-is-the-rouge-metric.srt | 138 ++-- ...rocessing-for-causal-language-modeling.srt | 110 +-- .../zh-CN/64_using-a-custom-loss-function.srt | 86 +- ...data-processing-for-question-answering.srt | 80 +- 16 files changed, 2698 insertions(+), 211 deletions(-) create mode 100644 chapters/ko/chapter2/2.mdx create mode 100644 chapters/ko/chapter8/1.mdx create mode 100644 chapters/ko/chapter8/2.mdx create mode 100644 chapters/ko/chapter8/3.mdx create mode 100644 chapters/ko/chapter8/4.mdx create mode 100644 chapters/ko/chapter8/4_tf.mdx create mode 100644 chapters/ko/chapter8/5.mdx create mode 100644 chapters/ko/chapter8/6.mdx create mode 100644 chapters/ko/chapter8/7.mdx diff --git a/README.md b/README.md index 8e247358e..8c820e0e8 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ This repo contains the content that's used to create the **[Hugging Face course] | [Bahasa Indonesia](https://huggingface.co/course/id/chapter1/1) (WIP) | [`chapters/id`](https://github.com/huggingface/course/tree/main/chapters/id) | [@gstdl](https://github.com/gstdl) | | [Italian](https://huggingface.co/course/it/chapter1/1) (WIP) | [`chapters/it`](https://github.com/huggingface/course/tree/main/chapters/it) | [@CaterinaBi](https://github.com/CaterinaBi), [@ClonedOne](https://github.com/ClonedOne), [@Nolanogenn](https://github.com/Nolanogenn), [@EdAbati](https://github.com/EdAbati), [@gdacciaro](https://github.com/gdacciaro) | | [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) | +| [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) | | [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) | | [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) | diff --git a/chapters/ko/_toctree.yml b/chapters/ko/_toctree.yml index bde0efee5..d1d952c7d 100644 --- a/chapters/ko/_toctree.yml +++ b/chapters/ko/_toctree.yml @@ -31,3 +31,25 @@ sections: - local: chapter2/1 title: 단원 소개 + - local: chapter2/2 + title: 파이프라인 내부 동작 과정 + +- title: 8. 도움을 요청하는 방법 + sections: + - local: chapter8/1 + title: 단원 소개 + - local: chapter8/2 + title: 에러가 발생했을 때 대응 방법 + - local: chapter8/3 + title: 포럼에서 도움 요청하기 + - local: chapter8/4 + title: 학습 파이프라인 디버깅(파이토치) + - local: chapter8/4_tf + title: 학습 파이프라인 디버깅(텐서플로우) + - local: chapter8/5 + title: 이슈 리포트 작성하는 법 + - local: chapter8/6 + title: Part 2 완료! + - local: chapter8/7 + title: 단원 마무리 퀴즈 + quiz: 1 diff --git a/chapters/ko/chapter2/2.mdx b/chapters/ko/chapter2/2.mdx new file mode 100644 index 000000000..718ea976d --- /dev/null +++ b/chapters/ko/chapter2/2.mdx @@ -0,0 +1,353 @@ + + +# 파이프라인 내부 동작 과정[[behind-the-pipeline]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + +PyTorch를 사용하는지, TensorFlow를 사용하는지에 따라 내용이 약간 달라지는 첫 번째 섹션입니다. 제목 상단 스위치를 이용해 선호하는 플랫폼을 선택하세요! + + +{#if fw === 'pt'} + +{:else} + +{/if} + +완전한 예제를 이용해, 아래의 [제1단원](/course/chapter1) 코드를 수행했을 때 뒤에서 어떤 일이 일어나고 있는지 알아봅시다. + +```python +from transformers import pipeline + +classifier = pipeline("sentiment-analysis") +classifier( + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] +) +``` + +다음과 같은 출력이 나오게 됩니다. + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}, + {'label': 'NEGATIVE', 'score': 0.9994558095932007}] +``` + +[제1단원](/course/chapter1)에서 확인했듯이, 이 파이프라인 그룹은 세 단계(전처리, 입력을 모델에 넘겨주는 것, 후처리)를 함께 수행합니다. + +
+The full NLP pipeline: tokenization of text, conversion to IDs, and inference through the Transformer model and the model head. + +
+ +각 단게에 대해 빠르게 살펴보겠습니다. + +## 토크나이저를 이용한 전처리[[preprocessing-with-a-tokenizer]] + +다른 신경망처럼 Transformer 모델도 원시 텍스트를 바로 처리할 수 없기 때문에 파이프라인의 첫 번째 단계는 텍스트 입력을 모델이 이해할 수 있는 숫자로 변환하는 것입니다. 이 과정을 위해 다음 기능들을 수행하는 *토크나이저*를 사용합니다. + +- 입력을 *토큰*이라고 부르는 단어나 하위 단어, 또는 심볼(예-구두점)로 분할 +- 각 토큰을 하나의 정수에 매핑 +- 모델에 유용할 수 있는 부가적인 입력 추가 + +이 모든 전처리 과정은 모델이 사전학습될 때와 완전히 동일한 방식으로 진행되어야 하기 때문에 [Model Hub](https://huggingface.co/models)에서 정보를 다운로드 해야합니다. 전처리를 위해 `AutoTokenizer` 클래스와 AutoTokenizer의 `from_pretrained()` 메서드를 사용합니다. 모델의 체크포인트 이름을 사용하여 모델의 토크나이저와 연관된 데이터를 자동으로 가져와 저장합니다. (따라서 아래 코드를 처음 실행할 때만 다운로드됩니다.) + +`sentiment-analysis` 파이프라인의 기본 체크포인트는 `distilbert-base-uncased-finetuned-sst-2-english`(모델 카드 확인은 [여기](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english)서 가능)이므로 아래 코드를 실행합니다. + +```python +from transformers import AutoTokenizer + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` + +토크나이저가 있다면 문장을 토크나이저에 입력하여 우리의 모델에 전달할 준비가 된 딕셔너리를 출력으로 얻게 됩니다! 남은 것은 입력 ID 목록을 tensor로 변환하는 것입니다. + +여러분은 어떤 ML 프레임워크-PyTorch나 TensorFlow, 또는 몇몇 모델을 위한 Flax-가 백엔드로 사용되는지 걱정하지 않고 🤗 Transformers를 사용할 수 있습니다. 하지만 Transformer 모델은 *tensor*만을 입력으로 받습니다. 만약 tensor에 대해 처음 들어봤다면, NumPy 배열을 생각하면 됩니다. NumPy 배열은 스칼라(0D), 벡터(1D), 행렬(2D) 또는 더 많은 차원을 가질 수 있습니다. 이것은 사실상 텐서입니다. 다른 ML 프레임워크의 텐서도 유사하게 동작하며, NumPy 배열처럼 인스턴스화가 쉽습니다. + +얻고자 하는 tensor의 타입(PyTorch, TensorFlow, 일반 NumPy)을 지정하기 위해, `return_tensors` 전달인자를 사용합니다. + +{#if fw === 'pt'} +```python +raw_inputs = [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", +] +inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="pt") +print(inputs) +``` +{:else} +```python +raw_inputs = [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", +] +inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="tf") +print(inputs) +``` +{/if} + +padding과 truncation에 대해 벌써 걱정하지 마세요. 나중에 설명하도록 하겠습니다. 여기서 기억해야 할 중요한 점은 하나의 문장 또는 여러 개의 문장 리스트를 토크나이저 함수로 전달할 수 있을 뿐만 아니라 얻고 싶은 텐서 유형까지 지정할 수 있다는 것입니다. (텐서 유형이 지정되지 않으면 이중 리스트를 결과로 얻게 됩니다) + +{#if fw === 'pt'} + +PyTorch tensor로 지정했을 때의 결과입니다. + +```python out +{ + 'input_ids': tensor([ + [ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012, 102], + [ 101, 1045, 5223, 2023, 2061, 2172, 999, 102, 0, 0, 0, 0, 0, 0, 0, 0] + ]), + '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, 0, 0, 0, 0, 0, 0, 0, 0] + ]) +} +``` +{:else} + +TensorFlow tensor로 지정했을 때의 결과입니다. + +```python out +{ + 'input_ids': , + 'attention_mask': +} +``` +{/if} + +출력은 `input_ids`와 `attention_mask` 두 개의 키를 갖는 딕셔너리입니다. `input_ids`는 각 문장 내 토큰의 고유 식별자인 정수로 이루어진 2개의 행(한 행이 하나의 문장)을 가지고 있습니다. `attention_mask`는 이 챕터의 뒤쪽에서 설명할 것입니다. + +## 모델 살펴보기[[going-through-the-model]] + +{#if fw === 'pt'} +토크나이저를 다운받은 방식과 동일한 방식으로 사전학습된 모델을 다운받을 수 있습니다. 🤗 Transformers는 `from_pretrained` 메서드를 가진 `AutoModel` 클래스를 제공합니다. + +```python +from transformers import AutoModel + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = AutoModel.from_pretrained(checkpoint) +``` +{:else} +토크나이저를 다운받은 방식과 동일한 방식으로 사전학습된 모델을 다운받을 수 있습니다. 🤗 Transformers는 `from_pretrained` 메서드를 가진 `TFAutoModel` 클래스를 제공합니다. + +```python +from transformers import TFAutoModel + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = TFAutoModel.from_pretrained(checkpoint) +``` +{/if} + +이 코드 스니펫에서 이전 파이프라인에서 사용된 것과 동일한 체크포인트를 다운로드(실제로는 이미 저장되어 있어야 합니다.)하고 그것으로 모델을 인스턴스화했습니다. + +해당 아키텍처는 기본 Transformer 모듈만 포함하고 있습니다. 입력이 주어지면 *features*라고도 불리는 *hidden states*를 출력합니다. 각 모델의 입력에 대해 **Transformer 모델에 의해 수행된 입력의 문맥적 이해**로 표현할 수 있는 고차원 벡터를 가져옵니다. + +이 내용이 이해되지 않더라도 걱정하지 마세요. 뒤에서 설명할 것입니다. + +이러한 hidden states는 그 자체로 유용할 수 있지만 일반적으로 *head*라고 알려진 모델의 다른 부분에 입력으로 들어갑니다. [제1단원](/course/chapter1)에서, 동일한 구조로 다른 태스크를 수행할 수 있었는데 이 태스크들은 서로 다른 헤드와 연관되어 있습니다. + +### 고차원 벡터란?[[a-high-dimensional-vector]] + +Transformer 모듈에 의한 출력 벡터는 일반적으로 크며 보통 3개의 차원을 가집니다. + +- **Batch size**: 한 번에 처리되는 시퀀스의 수 (예제에서는 2) +- **Sequence length**: 시퀀스의 숫자 표현 길이 (예제에서는 16) +- **Hidden size**: 각 모델 입력 벡터 차원 + +위에서 마지막 값으로 인해 "고차원"이라고 불립니다. hidden size는 매우 클 수 있습니다 (작은 모델은 768이 일반적이며 큰 모델은 3072나 그 이상의 값이 될 수 있습니다) + +전처리 과정을 거친 입력을 모델로 넘기면 아래 결과를 확인할 수 있습니다. + +{#if fw === 'pt'} +```python +outputs = model(**inputs) +print(outputs.last_hidden_state.shape) +``` + +```python out +torch.Size([2, 16, 768]) +``` +{:else} +```py +outputs = model(inputs) +print(outputs.last_hidden_state.shape) +``` + +```python out +(2, 16, 768) +``` +{/if} + +🤗 Transformers 모델의 출력은 `namedtuple` 또는 딕셔너리 형태입니다. 속성이나 키(`outputs["last_hidden_state"]`)를 이용해 요소에 접근할 수 있고 찾고자 하는 것의 정확한 위치를 안다면 인덱스(`outputs[0]`)도 사용할 수 있습니다. + +### 모델 헤드: 숫자로 이해하기[[model-heads-making-sense-out-of-numbers]] + +모델 헤드는 hidden state의 고차원 벡터를 입력으로 받아 다른 차원으로 투영합니다. 모델 헤드는 보통 하나 이상의 선형 레이어로 이루어져 있습니다. + +
+A Transformer network alongside its head. + +
+ +Transformer 모델의 출력은 처리할 모델 헤드로 바로 전달됩니다. + +이 다이어그램에서, 모델은 모델의 임베딩 레이어와 후속 레이어로 표현됩니다. 임베딩 레이어는 토큰화된 각각의 입력 ID를 연관된 토큰을 나타내는 벡터로 변환합니다. 후속 레이어는 문장의 최종 표현을 만들기 위해 어텐션 메커니즘을 이용해 이 벡터들을 처리합니다. + +🤗 Transformer에는 다양한 아키텍처가 있으며, 각각의 아키텍처는 특정 작업을 처리하도록 설계되었습니다. 아래는 일부 아키텍처입니다. + +- `*Model` (retrieve the hidden states) +- `*ForCausalLM` +- `*ForMaskedLM` +- `*ForMultipleChoice` +- `*ForQuestionAnswering` +- `*ForSequenceClassification` +- `*ForTokenClassification` +- 그 외 🤗 + +{#if fw === 'pt'} +이 예제를 위해서는 문장을 긍정 또는 부정으로 분류할 수 있게 하는 시퀀스 분류 헤드를 가진 모델이 필요합니다. 따라서 `AutoModel` 클래스가 아닌 `AutoModelForSequenceClassification`을 사용할 것입니다. + +```python +from transformers import AutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +outputs = model(**inputs) +``` +{:else} +이 예제를 위해서는 문장을 긍정 또는 부정으로 분류할 수 있게 하는 시퀀스 분류 헤드를 가진 모델이 필요합니다. 따라서 `AutoModel` 클래스가 아닌 `TFAutoModelForSequenceClassification`을 사용할 것입니다. + +```python +from transformers import TFAutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +outputs = model(inputs) +``` +{/if} + +출력 형태를 보면 차원이 훨씬 적은 것을 알 수 있습니다. 모델 헤드는 이전에 봤던 고차원 벡터를 입력으로 받아 2개의 값(레이블 당 하나)으로 이루어진 벡터를 출력합니다. + +```python +print(outputs.logits.shape) +``` + +{#if fw === 'pt'} +```python out +torch.Size([2, 2]) +``` +{:else} +```python out +(2, 2) +``` +{/if} + +우리는 2개의 문장과 2개의 레이블만 있기 때문에 모델로부터 얻은 출력 형태는 2 x 2입니다. + +## 출력 후처리[[postprocessing-the-output]] + +모델의 출력 값이 그 자체로 의미있는 것은 아닙니다. 한 번 보도록 합시다. + +```python +print(outputs.logits) +``` + +{#if fw === 'pt'} +```python out +tensor([[-1.5607, 1.6123], + [ 4.1692, -3.3464]], grad_fn=) +``` +{:else} +```python out + +``` +{/if} + +모델은 첫 번째 문장에 대해 `[-1.5607, 1.6123]`으로 예측했고 두 번째 문장에 대해 `[ 4.1692, -3.3464]`으로 예측했습니다. 이 값들은 확률이 아니라 모델의 마지막 층에 의해 출력된 정규화되지 않은 점수인 *logits*입니다. 확률로 변환되기 위해 logits은 [SoftMax](https://en.wikipedia.org/wiki/Softmax_function) 층을 거쳐야 합니다. 학습을 위한 손실 함수가 일반적으로 SoftMax와 같은 마지막 활성함수와 교차 엔트로피와 같은 실제 손실 함수를 모두 사용하기 때문에 모든 🤗 Transformers 모델의 출력은 logit입니다. + +{#if fw === 'pt'} +```py +import torch + +predictions = torch.nn.functional.softmax(outputs.logits, dim=-1) +print(predictions) +``` +{:else} +```py +import tensorflow as tf + +predictions = tf.math.softmax(outputs.logits, axis=-1) +print(predictions) +``` +{/if} + +{#if fw === 'pt'} +```python out +tensor([[4.0195e-02, 9.5980e-01], + [9.9946e-01, 5.4418e-04]], grad_fn=) +``` +{:else} +```python out +tf.Tensor( +[[4.01951671e-02 9.59804833e-01] + [9.9945587e-01 5.4418424e-04]], shape=(2, 2), dtype=float32) +``` +{/if} + +모델은 첫 번째 문장에 대해 `[0.0402, 0.9598]`로 예측했고 두 번째 모델에 대해 `[0.9995, 0.0005]`로 예측했습니다. 이 값들은 확실하게 확률값입니다. + +각 위치에 해당하는 레이블을 얻기 위해, 모델 config의 `id2label` 속성을 살펴봅시다. Config에 대한 더 많은 내용은 다음 섹션에서 진행됩니다. + +```python +model.config.id2label +``` + +```python out +{0: 'NEGATIVE', 1: 'POSITIVE'} +``` + +모델의 예측 결과를 아래처럼 결론 지을 수 있습니다. + +- 첫 번째 문장: NEGATIVE: 0.0402, POSITIVE: 0.9598 +- 두 번째 문장: NEGATIVE: 0.9995, POSITIVE: 0.0005 + +파이프라인 세 단게-토크나이저를 이용한 전처리, 모델에 입력 넣어주기, 후처리-를 성공적으로 재현했습니다! 이제 각 단계별로 좀 더 깊게 알아보는 시간을 가져봅시다. + + + +✏️ **직접 해보세요!** 2개 이상의 문장을 골라 `sentiment-analysis` 파이프라인을 적용해보세요. 이 챕터에서 본 내용을 그대로 수행해보고 같은 결과가 나오는지 확인해보세요! + + diff --git a/chapters/ko/chapter8/1.mdx b/chapters/ko/chapter8/1.mdx new file mode 100644 index 000000000..5a316e670 --- /dev/null +++ b/chapters/ko/chapter8/1.mdx @@ -0,0 +1,17 @@ +# 단원 소개 + + + +🤗 Transformers를 활용해 가장 일반적인 NLP 과제를 해결하는 방법을 배웠기 때문에, 이제 스스로 프로젝트를 진행할 차례입니다! 이번 단원에서는 문제가 생겼을 때 해야할 일을 살펴보겠습니다. 이 단원을 읽고 나면 학습 코드를 완벽하게 디버그 하는 방법과, 혼자서 해결할 수 없는 문제에 대해서 커뮤니티에 도움을 청하는 법을 배울 수 있으며, 만약 Huggingface 라이브러리에서 버그를 발견했을 시 해당 버그를 빠르게 해결 하기 위해 리포트하는 가장 좋은 방법을 제시하고자 합니다. + +이번 단원에서 배우게 될 상세한 목차입니다. + +- 에러가 발생했을 때 처음으로 할 일 +- [포럼](https://discuss.huggingface.co/)에 도움을 요청하는 방법 +- 학습 파이프라인을 디버그 하는 방법 +- 이슈 리포트 작성하는 법 + +위 내용은 🤗 Transformers나 Huggingface 에코시스템과 특별히 관련 있는 것은 아니지만 대부분의 오픈 소스 프로젝트에 적용할 수 있습니다 diff --git a/chapters/ko/chapter8/2.mdx b/chapters/ko/chapter8/2.mdx new file mode 100644 index 000000000..1970013e5 --- /dev/null +++ b/chapters/ko/chapter8/2.mdx @@ -0,0 +1,364 @@ +# 에러가 발생했을 때 대응 방법 + + + +이번 장에서는 Transformer 모델을 새롭게 튜닝 후 예측을 하려고 할 때 발생할 수 있는 몇가지 일반적인 에러를 살펴보겠습니다. + + + +이번 장에서 [모델의 저장소 템플릿](https://huggingface.co/lewtun/distilbert-base-uncased-finetuned-squad-d5716d28)이 준비되어 있습니다. +만약 이번 단원에서 코드를 실행하려면 모델을 [Huggingface Hub](https://huggingface.co)의 개인 계정에 모델을 복사해야 합니다. +모델을 계정의 저장소에 복제하기 위해 주피터 노트북에서 아래의 코드를 실행하거나: + +```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(): + # Clone the repo and extract the local path + template_repo_id = "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28" + commit_hash = "be3eaffc28669d7932492681cd5f3e8905e358b4" + template_repo_dir = snapshot_download(template_repo_id, revision=commit_hash) + # Create an empty repo on the Hub + model_name = template_repo_id.split("/")[1] + create_repo(model_name, exist_ok=True) + # Clone the empty repo + new_repo_id = get_full_repo_name(model_name) + new_repo_dir = model_name + repo = Repository(local_dir=new_repo_dir, clone_from=new_repo_id) + # Copy files + copy_tree(template_repo_dir, new_repo_dir) + # Push to Hub + repo.push_to_hub() +``` +이제 `copy_repository_template()`를 호출하면 모델 저장소의 템플릿이 계정에 복사 됩니다. + +## 🤗 Transformers의 파이프라인 디버깅 + +Transformer 모델들의 멋진 디버깅 세계로 여정을 떠나기 위해, 다음의 시나리오를 생각해보세요: 여러분은 E-commerce 사이트의 고객이 소비자 상품에 대한 답변을 찾기 위한 질문 및 답변 프로젝트에서 동료와 함께 일하고 있으며, 동료가 당신에게 다음과 같은 메세지를 보냈습니다: + +> 안녕하세요! Hugging Face 코스에 있는 [7 단원](/course/chapter7/7)의 기술을 활용해서 실험을 해봤는데, SQuAD에서 좋은 결과를 얻었습니다. 저희 프로젝트를 이 모델로 시작할 수 있다고 생각이 됩니다. 허브에 있는 모델 아이디는 "lewtun/distillbert-base-uncased-finetuned-squad-d5716d28" 입니다. 마음 껏 테스트 해보세요. :) + +🤗 Transformers의 `pipeline`을 사용는 모델을 불러오기 위해 우선 고려해야 할 것이 있습니다: + +```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'이라는 모델명이 'https://huggingface.co/models'에 존재하는지 확인하거나 + +'lewtun/distillbert-base-uncased-finetuned-squad-d5716d28'이라는 경로 또는 폴더가 config.json 파일 포함하고 있는지 확인하세요. +""" +``` + +아 이런, 뭔가 잘못된 것 같네요! 만약 프로그래밍이 처음이라면, 이런 종류의 에러가 처음에는 다소 신비하게(`OSError`란 도대체..) 보일 수도 있습니다. 여기에서 보이는 에러는 파이썬의 traceback(stack trace로 알려져있음)으로 불리는 좀 더 큰 에러 리포트의 마지막 부분 입니다. 예를 들어 이 코드를 Google의 Colab에서 실행한다면 아래와 같은 스크린샷을 보게 될겁니다: + +
+A Python traceback. +
+ +이 리포트에는 많은 정보를 담고 있으니, 같이 핵심 부분을 살펴보겠습니다. 우선 명심해야할 것은 tracebacks은 _아래부터 위로_ 읽어야 합니다. 이러한 말은 영어 텍스트를 위에서 아래로 읽어오곤 했다면 이상하게 들릴 수 있겠지만 모델과 토크나이저를 다운로드 할 때 `pipeline`이 만드는 함수 호출 순서를 보여주는 traceback을 반영했기 때문입니다. 내부에서 `pipeline`이 작동하는 방식에 대한 자세한 내용은 [단원 2](/course/chapter2)를 참고하세요. + + + +Google Colab의 traceback에서 "6 frames" 주변의 파란 상자를 보셨나요? traceback을 "frames"로 압축하는 Colab의 특별한 기능입니다. 만약 오류의 원인을 찾을 수 없다면, 두개의 작은 화살표를 클릭해서 전체 traceback을 확장되어 있는지 여부를 확인하세요. + + + +즉 마지막 에러 메시지와 발생한 예외의 이름을 가리키는 traceback의 마지막 줄을 뜻합니다. 이 경우의 예외 유형은 시스템 관련 오류를 나타내는 OS Error 입니다. 첨부된 오류 메시지를 읽으면 모델의 *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 또는 [스택오버플로우](https://stackoverflow.com/) 검색창에 붙여 넣기만 하세요(네 진짭니다!). 이는 오류가 발생한 첫 사람이 아닐 가능성이 높을뿐더러, 커뮤니티의 다른 사람들이 게시한 솔루션을 찾는 좋은 방법입니다. 예를 들어, 스택오버플로우에서 'OSError: Can't load config for'를 검색하면 여러 [해답](https://stackoverflow.com/search?q=OSError%3A+Can%27t+load+config+for+)을 제공하며 문제 해결을 위한 출발점으로 사용할 수 있습니다. + + + +첫 번째 제안은 모델 ID가 실제로 정확한지 확인하도록 요청하는 것으로 비즈니스의 첫 순서는 식별자(모델 이름)를 복사하여 Hub의 검색 창에 붙여넣는 것입니다: + +
+The wrong model name. +
+ +음, 동료의 모델이 허브에 없는 것 같습니다... 아하, 모델의 이름에 오타가 있었습니다! DistilBERT는 이름에 "l"이 하나만 있으므로 이를 수정하고 대신 "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28"을 찾아보겠습니다: + +
+The right model name. +
+ +좋습니다, 성공했군요. 이제 올바른 모델 ID로 모델을 다시 다운로드 해봅시다: + +```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 +""" +``` + +아오 또 실패입니다. 머신러닝 엔지니어의 일상에 오신 것을 환영합니다! 모델 ID를 수정했으므로 문제는 저장소 자체에 있어야 합니다. 🤗 Hub의 저장소 컨텐츠에 빠르게 액세스하는 방법은 `huggingface_hub` 라이브러리의 `list_repo_files()` 함수를 사용하는 것입니다: + +```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`이 모델을 불러올 수 없는 것이 당연했군요; 동료가 파인튜닝 후에 허브에 푸시하는 것을 잊어버린 모양입니다. 이 경우, 문제는 매우 간단하게 해결할 수 있습니다: 동료에게 파일을 추가하도록 요청하거나, 사전 훈련(pretrained)된 모델이 [`distilbert-base-uncased`](https://huggingface.co/distilbert-base-uncased)인 것을 확인 할 수 있으므로, 이 모델에 대한 config를 다운로드하고 저장소에 푸시하여 문제가 해결되는지 확인할 수 있습니다. 시도 해봅시다. [단원 2](/course/chapter2)에서 배운 기술을 사용해 `AutoConfig` 클래스로 모델의 config 파일을 다운로드할 수 있습니다. + +```python +from transformers import AutoConfig + +pretrained_checkpoint = "distilbert-base-uncased" +config = AutoConfig.from_pretrained(pretrained_checkpoint) +``` + + + +🚨 여기에서 하는 접근 방식은 동료가 'distilbert-base-uncased'의 config를 수정했을 수 있으므로 이 접근 방식은 완전하지 않습니다. 우리는 동료에게 먼저 확인하고 싶겠지만, 이번 장에서의 목적상, 동료가 디폴트 config를 사용했다고 가정하겠습니다. + + + +그런 다음 config 클래스의 `push_to_hub()` 기능을 사용해서 config 파일을 모델 저장소로 푸시할 수 있습니다: +We can then push this to our model repository with the configuration's `push_to_hub()` function: + +```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_로 알려져 있으며 아래에서 위로 읽습니다. 오류 메시지의 마지막 줄에는 일반적으로 문제의 원인을 찾는 데 필요한 정보가 포함되어 있습니다. +- 마지막 줄에 충분한 정보가 포함되어 있지 않으면 traceback을 위로 훑어보고 소스 코드에서 오류가 발생한 위치를 식별할 수 있는지 확인합니다. +- 오류 메시지가 문제를 디버그하는 데 도움이 되지 않으면 온라인에서 유사한 문제에 대한 해결책을 검색해 보세요. +- `huggingface_hub` +// 🤗 Hub? +이 라이브러리는 허브의 저장소와 상호 작용하고 디버그하는 데 사용할 수 있는 툴들을 제공합니다. + +이제 파이프라인을 디버깅하는 방법을 알았으니 모델 자체의 forward pass에서 더 까다로운 예를 살펴보겠습니다. + +## 모델의 foward pass 디버깅 + +'pipeline'은 빠르게 예측을 생성해야 하는 대부분의 애플리케이션에 적합하지만 때로는 모델의 logits값에 접근해야 할 수도 있습니다(예: 적용하려는 커스텀 후처리 과정이 있는 경우). 이 경우 무엇이 잘못될 수 있는지 알아보기 위해 먼저 `pipeline`에서 모델과 토크나이저를 가져와 보겠습니다: + +```python +tokenizer = reader.tokenizer +model = reader.model +``` +다음으로 질문이 필요합니다. 선호하는 프레임워크가 지원되는지 살펴보겠습니다: + +```python +question = "Which frameworks can I use?" +``` +[단원 7](/course/chapter7)에서 보았듯이 일반적인 단계는 입력을 토큰화하고 시작과 마지막 토큰의 logits를 추출한 다음 응답 부분을 디코딩하는 것입니다: + +```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 +# Get the most likely beginning of answer with the argmax of the score +answer_start = torch.argmax(answer_start_scores) +# Get the most likely end of answer with the argmax of the score +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' +""" +``` + +이런, 코드에 버그가 있는 것 같네요! 하지만 약간의 디버깅은 두렵지 않습니다. 노트북에서 파이썬 디버거를 사용 할 수 있습니다: + + + +또는 터미널에서: + + + +여기에서 오류 메시지를 읽으면 `'list' 객체에는 'size' 속성이 없으며 `model(**inputs)'에서 문제가 발생한 라인을 가리키는 `-->` 화살표를 볼 수 있습니다. `.Python 디버거를 사용하여 대화식으로 디버그할 수 있지만 지금은 단순히 `inputs` 부분을 슬라이스하여 어떤 값이 있는지 볼 것입니다: + +```python +inputs["input_ids"][:5] +``` + +```python out +[101, 2029, 7705, 2015, 2064] +``` + +확실히 일반적인 Python `list`처럼 보이지만 타입을 다시 확인합시다: + +```python +type(inputs["input_ids"]) +``` + +```python out +list +``` + +네, 확실히 파이썬의 `list`입니다. 무엇이 잘못되었을까요? [단원 2](/course/chapter2)에서 🤗 Transformers의 `AutoModelForXxx` 클래스는 _tensors_(PyTorch 또는 TensorFlow 포함)에서 작동하며 tensor의 dimensions를 추출하기 위해 일반적인 방법으로 PyTorch의 `Tensor.size()`를 활용합니다. 어떤 라인이 예외를 발생시켰는지 알아보기 위해 traceback을 다시 살펴보겠습니다: + +``` +~/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`에서는 절대 동작하지 않습니다. 이 문제를 어떻게 해결할 수 있을까요? 스택오버플로우에서 오류 메시지를 검색하면 꽤 많은 관련 [해결책](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 +# Get the most likely beginning of answer with the argmax of the score +answer_start = torch.argmax(answer_start_scores) +# Get the most likely end of answer with the argmax of the score +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 +""" +``` + +잘 동작하네요! 이게 바로 스택오버플로우가 얼마나 유용한지 보여주는 좋은 예입니다. 유사한 문제를 식별하여 커뮤니티의 다른 사람들의 경험을 활용할 수 있었습니다. 그러나 이와 같은 검색이 항상 적절한 답변을 제공하는 것은 아닙니다. 이러한 경우에 무엇을 할 수 있을까요? 다행히도 [Hugging Face forums](https://discuss.huggingface.co/)에 여러분을 반기고 도와줄 수 있는 개발자 커뮤니티가 있습니다! 다음 장에서는 답변을 얻을 수 있는 좋은 포럼 질문을 만드는 방법을 살펴보겠습니다. \ No newline at end of file diff --git a/chapters/ko/chapter8/3.mdx b/chapters/ko/chapter8/3.mdx new file mode 100644 index 000000000..a5185455a --- /dev/null +++ b/chapters/ko/chapter8/3.mdx @@ -0,0 +1,163 @@ +# 포럼에서 도움 요청하기 + + + + + +[HuggingFace 포럼](https://discuss.huggingface.co)은 오픈 소스 팀과 더 거대한 Hugging Face 커뮤니티의 도움을 받을 수 있는 좋은 공간입니다. 특정 날짜의 메인 페이지는 다음과 같습니다: + +
+The Hugging Face forums. +
+ +왼쪽에는 그룹화된 다양한 주제의 카테고리가 ​있으며 오른쪽에는 가장 최근 주제가 표시됩니다. 주제는 제목, 카테고리 및 설명이 포함된 게시물입니다. 이는 [단원 5](/course/chapter5)에서 자체 데이터 세트를 생성할 때 본 GitHub 문제 형식과 매우 유사합니다. 이름에서 알 수 있듯이 [초보자](https://discuss.huggingface.co/c/beginners/5) 카테고리는 주로 HuggingFace 라이브러리 및 에코시스템을 처음 시작하는 사람들을 대상으로 합니다. 라이브러리에 대한 모든 질문은 환영하는 곳이며 일부 코드를 디버그하거나 무언가를 수행하는 방법에 대한 도움을 요청하는 곳 입니다. (즉, 질문이 특히 한 라이브러리에 관한 것이라면 포럼에서 해당 라이브러리 카테고리로 이동해야 할 것입니다.) + +마찬가지로 [중급](https://discuss.huggingface.co/c/intermediate/6) 및 [연구](https://discuss.huggingface.co/c/research/7) 카테고리는 어려운 질문용입니다. 멋진 새 NLP 연구나 라이브러리들에 대해 토론하고 싶을 때의 내용을 예로 들 수 있습니다. + +이어서 허깅페이스 코스와 관련된 질문을 할 수 있는 [코스](https://discuss.huggingface.co/c/course/20) 카테고리도 언급 안할 수가 없네요!! + +카테고리를 선택하고나면 첫 번째 주제를 작성할 준비가 되었습니다. 이 포스트를 작성하는 방법은 포럼의 [가이드라인](https://discuss.huggingface.co/t/how-to-request-support/3128)에서 찾을 수 있으며 이 장에서는 좋은 주제를 작성하는 몇 가지 방법을 알아보겠습니다. + +## 포럼에 포스팅 잘쓰는 법 + +실습 예제로 Wikipedia 기사에서 임베딩을 생성하여 커스텀 검색 엔진을 만든다고 가정해 보겠습니다. 평소처럼 토크나이저와 모델을 다음과 같이 로드합니다: + +```python +from transformers import AutoTokenizer, AutoModel + +model_checkpoint = "distilbert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +model = AutoModel.from_pretrained(model_checkpoint) +``` + +이제 [Wikipedia 기사](https://en.wikipedia.org/wiki/Transformers)의 전체 섹션을 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장](/course/chapter8/section2)에서 본 것보다 훨씬 더 어렵네요! 우리는 전체 traceback을 이해할 수 없으므로 HuggingFace 포럼에 도움을 요청하기로 결정합니다. 주제를 어떻게 만들 수 있을까요? + +주제를 작성하려면 오른쪽 상단 모서리에 있는 "새 주제" 버튼을 클릭해야 합니다(주제를 만들려면 로그인해야 함): + +
+Creating a new forum topic. +
+ +그러면 주제의 제목과 카테고리, 콘텐츠를 입력할 수 있는 화면이 나타나게 됩니다: + +
+The interface for creating a forum topic. +
+ +에러는 🤗 Transformers에 대한 것이므로 Transformers 카테고리로 선택하겠습니다. 문제를 설명하는 우리의 첫 시도는 아마 아래와 같을 겁니다: + +
+Drafting the content for a new forum topic. +
+ +이 주제는 도움에 필요한 오류 메시지가 포함되어 있지만 작성 방식에 몇 가지 문제가 있습니다: + +1. 제목이 그다지 기술적이지 않으므로 포럼을 탐색하는 사람은 본문을 읽지 않고는 주제가 무엇인지 알 수 없습니다. +2. 본문이 오류가 _어디에서_ 발생하고 _어떻게_ 에러를 재현하는지에 대한 충분한 정보를 제공하지 않습니다. +3. 주제는 다소 요구하는 어조로 몇몇 사람들을 직접적으로 태그합니다. + +이와 같은 주제는 빠른 답변을 얻지 못할 것이므로(적어도 하나라도 받을 경우) 이를 개선할 수 있는 방법을 살펴보겠습니다. 좋은 타이틀을 선택하기 위해 첫번째 이슈글 부터 보겠습니다. + +### 기술적인 제목 선택하기 + +코드의 버그에 대한 도움을 받으려는 경우 다른 사람들이 질문에 답할 수 있는지 여부를 빠르게 결정할 수 있도록 제목에 충분한 정보를 포함하는 것이 좋습니다. 실습 예제에서 발생하는 예외의 이름을 알고 있으며 `model(**inputs)`을 호출하는 모델의 forward 과정에서 발생한다는 힌트를 얻었습니다. 이를 소통하기 위해 다음과 같은 제목이 가능하겠네요: + +> Source of IndexError in the AutoModel forward pass? + +이 제목은 읽는 사람에게 _어디에서_ 버그가 발생했다고 생각하는지 알려주고, 이전에 'IndexError'를 본 적이 있다면 디버그 방법을 알고 있을 가능성이 큽니다. 물론 제목은 원하는 대로 지정할 수 있으며 다음과 같은 변형도 가능합니다: + +> Why does my model produce an IndexError? + +와 같은 제목도 괜찮을 수 있습니다. 기술적인 제목을 만들었으니 본문 개선 방법을 살펴보겠습니다. + +### 코드 스니펫 형식 지정하기 + +IDE에서 소스 코드를 읽는 것은 충분히 어렵지만 코드를 일반 텍스트로 복사하여 붙여넣으면 훨씬 더 어렵습니다! 다행히 HuggingFace 포럼은 Markdown 사용을 지원하므로 코드 블록을 항상 세 개의 백틱(```)으로 묶어서 더 쉽게 읽을 수 있도록 해야 합니다. 에러 메시지를 보기 좋게 만들기 위해 백틱안에 코드를 넣으면서 원본 보다 본문을 좀 더 정중하게 만들어 보겠습니다: + +
+Our revised forum topic, with proper code formatting. +
+ +스크린샷에서 볼 수 있듯이 코드 블록을 백틱으로 묶으면 일반 텍스트가 코드로 변환되고 컬러 스타일링이 완성됩니다! 또한 `distilbert-base-uncased`에 했던 것처럼 하나의 백틱을 사용해 인라인 변수의 형식을 지정할 수 있습니다. 이 주제는 훨씬 나아지고 있으며 운이 좋으면 커뮤니티에서 에러가 무엇인지 추측할 수 있는 사람을 찾을 수 있습니다. 그러나 운에 맡기기 보단 traceback을 철저하고 세부적으로 포함해서 삶을 더 쉽게 만들도록 하죠! + +### 전체 traceback 포함하기 + +traceback의 마지막 줄은 종종 코드를 디버그하기에 충분하기 때문에 주제에 "공간 절약"을 하고 싶을 수도 있습니다. 의도는 좋지만 traceback에서 상위에 있는 정보도 정말 유용할 수 있기 때문에 때론 다른 사람들이 문제를 디버그하는 것을 실제로 _더 어렵게_ 합니다. 따라서 좋은 방법은 _전체_ traceback을 복사하여 붙여넣으면서 형식이 제대로 지정되었는지 확인하는 것입니다. 이러한 traceback은 다소 길어질 수 있으므로 일부 사람들은 소스 코드를 설명한 후에 표시하는 것을 선호합니다. 이제 작성해봅시다. 포럼 주제는 다음과 같습니다: + +
+Our example forum topic, with the complete traceback. +
+ +이게 훨씬 더 많은 정보를 제공하며, 섬세한 독자는 traceback에 있는 특정 행을 보고 긴 입력을 전달하기 때문에 문제가 있는 것으로 보인다는 점을 지적할 수 있습니다: + +> Token indices sequence length is longer than the specified maximum sequence length for this model (583 > 512). + +하지만 에러를 발생한 실제 코드를 제공해 문제를 훨씬 더 쉽게 만들 수 있습니다. 같이 방법을 알아보죠. + +### 재현 가능한 예제코드 제공 + +다른 사람의 코드를 디버그하려고 시도한 적이 있다면 아마 그 사람이 보고한 문제를 먼저 재현해서 traceback을 통해 오류를 정확하게 찾아내는 작업을 할 수 있습니다. 포럼에서 도움을 받는(또는 제공하는) 경우에도 다르지 않으므로 오류를 재현하는 작은 예시를 제공할 수 있다면 정말 도움이 됩니다. 절반은 이 연습을 진행하는 것만으로도 무엇이 문제인지 파악하는 데 도움이 될 것입니다. 어쨌든 우리 예제에서 누락된 부분은 모델에 입력한 _inputs_를 표시하는 것입니다. 그러면 다음과 같은 완성된 예가 나타납니다: + +
+The final version of our forum topic. +
+ +이 주제는 이제 상당히 많은 정보가 포함되어 있으며 커뮤니티의 관심을 끌고 유용한 답변을 얻을 가능성이 훨씬 높은 방식으로 작성되었습니다. 이 기본 가이드라인을 통해 이제 멋진 주제를 포스팅해서 🤗 Transformers 질문에 대한 답변을 찾을 수 있습니다! \ No newline at end of file diff --git a/chapters/ko/chapter8/4.mdx b/chapters/ko/chapter8/4.mdx new file mode 100644 index 000000000..ee408de5b --- /dev/null +++ b/chapters/ko/chapter8/4.mdx @@ -0,0 +1,775 @@ + + +# 학습 파이프라인 디버깅 + + + +[단원 7](/course/chapter7)의 조언을 충실히 따라 주어진 작업에서 모델을 학습하거나 파인튜닝 하는 아름다운 스크립트를 작성했습니다. 하지만 `trainer.train()` 명령을 실행하면 끔찍한 일이 발생합니다. 에러가 발생합니다 😱! 또는 더 나쁜 것은 모든 것이 정상인 것처럼 보이고 학습이 에러 없이 실행되지만 결과 모델은 엉망입니다. 이 섹션에서는 이러한 종류의 문제를 디버그하기 위해 수행할 수 있는 것들을 보여줍니다. + +## 학습 파이프라인 디버깅 + + + +`trainer.train()`에서 에러가 발생했을 때의 문제는 `Trainer`가 일반적으로 많은 것을 결합하기 때문에 여러 소스에서 올 수 있다는 것입니다. 데이터 세트를 데이터 로더로 변환하므로 데이터 세트에 문제가 있거나 데이터 세트의 요소를 함께 일괄 처리하려고 할 때 문제가 발생할 수 있습니다. 그런 다음 데이터 배치를 가져와 모델에 공급하므로 문제가 모델 코드에 있을 수 있습니다. 그 다음 기울기를 계산하고 최적화 단계를 수행하므로 문제가 옵티마이저에도 있을 수 있습니다. 모든 것이 학습에 적합하더라도 평가 함수에 문제가 있으면 평가 중에 문제가 발생할 수 있습니다. + +`trainer.train()`에서 발생하는 오류를 디버그하는 가장 좋은 방법은 이 전체 파이프라인을 직접 살펴보고 문제가 발생한 부분을 확인하는 것입니다. 그러면 에러를 해결하기가 매우 쉬운 경우가 많습니다. + +이를 시연하기 위해 [MNLI 데이터 세트](https://huggingface.co/datasets/glue)에서 DistilBERT 모델을 파인튜닝(시도하는)하는 다음 스크립트를 사용합니다.: + +```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' +``` + +### 데이터 확인 + +말할 필요도 없이, 데이터가 손상되면 `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)' +``` +traceback을 보면 데이터 정렬 단계에서 에러가 발생하는 것을 볼 수 있습니다.: + +```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`는 버리는 열도 기록합니다. + +input IDs를 디코딩하여 올바른지 확인했습니다. 다음은 `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] +``` +전처리에서 패딩을 적용하지 않았기 때문에 위 값은 완벽하게 자연스러워 보입니다. attention mask에 문제가 없는지 확인하기 위해 input IDs 와 길이가 같은지 확인합니다.: + +```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 +``` + +input IDs 와 마찬가지로 이 숫자만으로는 의미가 없습니다. 이전에 보았듯이 정수와 레이블 이름 사이의 맵은 데이터세트의 해당 *feature*의 `names` 속성 내부에 저장됩니다.: + +```py +trainer.train_dataset.features["label"].names +``` + +```python out +['entailment', 'neutral', 'contradiction'] +``` + +따라서 `1`은 `중립`을 의미합니다. 즉, 위에서 본 두 문장이 모순되지 않고 첫 번째 문장이 두 번째 문장을 의미하지 않습니다. 맞는 것 같습니다! + +여기에는 token type IDs 가 없습니다. DistilBERT는 사용하지 않기 때문입니다. token type IDs를 사용하는 모델의 경우 입력에서 첫 번째 및 두 번째 문장이 있는 위치와 올바르게 일치하는지 확인해야 합니다. + + + +✏️ **여러분 차례입니다!** 학습 데이터 세트의 두 번째 원소가 정상적인지 확인해보세요. + + + +여기에선 학습 세트에 대해서만 확인하지만, 동일한 방식으로 검증 및 평가 세트를 다시 확인해야 합니다. + +데이터 세트가 문제 없으므로 이제 학습 파이프라인의 다음 단계를 확인할 차례입니다. + +### 데이터세트에서 데이터로더까지 + +학습 파이프라인에서 다음으로 잘못될 수 있는 사항은 `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) +``` + +Traceback의 마지막 프레임을 조사하면 단서를 제공하기 충분할테지만 조금 더 파헤쳐 보겠습니다. 배치 생성 중 대부분의 문제는 예제를 단일 배치로 조합하기 때문에 발생하므로 의심스러운 경우 가장 먼저 확인해야 할 것은 `DataLoader`가 사용하는 `collate_fn`입니다.: + +```py +data_collator = trainer.get_train_dataloader().collate_fn +data_collator +``` + +```python out + Dict[str, Any]> +``` + +위 코드는 `default_data_collator`이지만, 이 경우에는 우리가 원하는 것이 아닙니다. 'DataCollatorWithPadding' collator에 의해 수행되는 배치에서 가장 긴 문장으로 패딩을 하고 싶습니다. 그리고 이 데이터 콜레이터는 기본적으로 `Trainer`에 의해 사용된다고 하는데 여기에서는 사용하지 않는 이유는 무엇일까요? + +그 이유는 우리가 `Trainer`에 `tokenizer`를 전달하지 않았기 때문에 우리가 원하는 `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 ​​내부에서 발생하는 일을 직접 디버그할 수 있어야 합니다. + +배치 생성 프로세스를 디버깅했으므로 이제 모델을 통해 전달할 차례입니다! + +### 모델 살펴보기 + +아래 명렁어를 실행함으로써 배치를 얻을 수 있어야 합니다.: + +```py +for batch in trainer.get_train_dataloader(): + break +``` + +노트북에서 이 코드를 실행하는 경우 이전에 본 것과 유사한 CUDA 오류가 발생할 수 있습니다. 이 경우 노트북을 다시 시작하고 `trainer.train()` 행 없이 마지막 스니펫을 다시 실행해야 합니다. CUDA 오류에 대해 두 번째로 짜증나는 점은 커널을 복구할 수 없을 정도로 망가뜨리는 것입니다. 가장 짜증나는 점은 디버깅하기 어렵다는 사실입니다. + +왜 그럴까요? 이건 GPU 작동 방식과 관련이 있습니다. 많은 작업을 병렬로 실행하는 데 매우 효율적이지만, 이러한 명령 중 하나의 에러가 발생했을 때 이를 즉시 알 수 없다는 단점이 있습니다. 프로그램이 GPU에서 여러 프로세스의 동기화를 호출할 때만 문제가 발생했음을 깨닫게 되므로 실제로 에러를 만든 곳과 관련이 없는 위치에서 오류가 발생합니다. 예를 들어, 이전 Traceback을 보면 역방향 패스 중에 오류가 발생했지만 실제로는 순방향 중 어딘가에서 비롯된 에러임을 곧 알 수 있습니다. + +그렇다면 이러한 에러를 어떻게 디버깅할까요? 답은 간단합니다. 하지 않습니다. 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를 얻는 것은 실제로 일반적입니다. 이전에 추출한 레이블 이름을 기억해보면 3개가 있었으므로 인덱스는 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) +``` +다음 단계는 GPU로 돌아가 모든 것이 여전히 작동하는지 확인하는 것입니다.: + +```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) +``` +여전히 에러가 발생하면 노트북을 다시 시작하고 스크립트의 마지막 버전만 실행해야 합니다. + +### 한번의 최적화 단계 수행 + +이제 실제로 모델을 통과하는 배치를 빌드할 수 있다는 것을 알았으므로 학습 파이프라인의 다음 단계인 그래디언트 계산 및 최적화 단계를 수행할 준비가 되었습니다.: + +첫 번째 부분은 로스에 대해 `backward()` 메서드를 호출하는 것입니다.: + +```py +loss = outputs.loss +loss.backward() +``` +이 단계에서 에러가 발생하는 것은 매우 드물지만 에러가 발생하면 CPU로 돌아가 유용한 에러 메시지를 받으세요. + +최적화 단계를 수행하려면 `optimizer`를 만들고 `step()` 메서드를 호출하기만 하면 됩니다.: + +```py +trainer.create_optimizer() +trainer.optimizer.step() +``` +다시 말하지만, `Trainer`에서 기본 옵티마이저를 사용하는 경우 이 단계에서 오류가 발생하지 않아야 하지만 사용자 지정 옵티마이저가 있는 경우 여기에서 디버깅에 몇 가지 문제가 있을 수 있습니다. 이 단계에서 이상한 CUDA 오류가 발생하면 CPU로 돌아가는 것을 잊지 마세요. CUDA 오류에 대해 말하자면, 앞서 우리는 특별한 경우를 언급했습니다. 그 부분을 지금부터 살펴보겠습니다. + +### CUDA 메모리 부족 오류 다루기 + +`RuntimeError: CUDA out of memory`로 시작하는 에러 메시지는 GPU 메모리가 부족하다는 의미입니다. 이 부분은 코드에 직접 연결되지 않으며 완벽하게 실행되는 스크립트에서 발생할 수 있습니다. 이 에러는 GPU의 내부 메모리에 너무 많은 것을 넣으려고 해서 에러가 발생했음을 의미합니다. 다른 CUDA 오류와 마찬가지로 학습을 다시 실행할 수 있는 위치에서 커널을 다시 시작해야 합니다. + +이 문제를 해결하려면 GPU 공간을 적게 사용하면 됩니다. 먼저 GPU에 동시에 두 개의 모델이 있지 않은지 확인합니다(물론 문제 해결에 필요한 경우 제외). 그런 다음 배치 크기를 줄여야 합니다. 이는 모델의 모든 중간 결과값 크기와 기울기에 직접적인 영향을 미치기 때문입니다. 문제가 지속되면 더 작은 모델 버전을 사용하는 것이 좋습니다. + + + +코스의 다음 부분에서는 메모리 사용량을 줄이고 가장 큰 모델을 파인 튜닝할 수 있는 고급 기술을 살펴보겠습니다. + + + +### 모델 평가하기 + +이제 코드의 모든 문제를 해결했으므로 모든 것이 완벽하고 학습이 원활하게 실행되어야 합니다. 그렇죠? 그렇게 빠르진 않습니다! `trainer.train()` 명령을 실행하면 처음에는 모든 것이 좋아 보이지만 잠시 후 다음의 출력을 보게 됩니다.: +Now that we've solved all the issues with our code, everything is perfect and the training should run smoothly, right? Not so fast! If you run the `trainer.train()` command, everything will look good at first, but after a while you will get the following: + +```py +# This will take a long time and error out, so you shouldn't run this cell +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) +``` + +에러는 나중에 평가 단계가 끝날 때 발생하며 Traceback을 보면 다음과 같이 표시됩니다.: + +```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,)) +``` +우리의 예측은 실제 예측값이 아니라 여전히 로짓입니다. 이것이 메트릭이 이 (다소 모호한) 에러를 반환하는 이유입니다. 수정은 매우 쉽습니다. `compute_metrics()` 함수에 argmax를 추가하기만 하면 됩니다.: + +```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()`를 잊지 않았는지 확인하세요! + + + +## 학습 중 조용한 에러 디버깅 + +에러 없이 완료되지만 좋은 결과를 얻지 못하는 학습을 디버그하려면 어떻게 해야 할까요? 여기에서 몇 가지 지침을 제공하겠지만 이러한 종류의 디버깅은 기계 학습에서 가장 어려운 부분이며 마법 같은 답은 없다는 점을 기억하세요. + +### 데이터 확인(다시!) + +모델은 데이터에서 실제로 뭔가 학습할 수 있는 경우에만 학습합니다. 데이터를 손상시키는 버그가 있거나 레이블이 무작위로 지정된 경우 데이터 세트에 대한 학습을 제대로 진행하지 못할 가능성이 매우 높습니다. 따라서 항상 디코딩된 입력과 레이블을 다시 확인하는 것으로 시작하고 다음의 질문을 스스로에게 물어보세요.: + +- 디코딩된 데이터가 이해할만한지? +- 레이블에 납득할 수 있는지? +- 다른 레이블보다 정답에 가까운 레이블이 있는지? +- 모델이 무작위 답, 언제나 같은 답을 예측할 경우 어떤 손실 함수와 매트릭을 정해야할지? + + + +분산 학습을 수행하는 경우 각 프로세스에서 데이터 세트의 샘플을 출력하고 동일한 결과를 얻었는지 세 번 확인하세요. 한 가지 일반적인 버그는 데이터 생성 시 각 프로세스가 서로 다른 버전의 데이터 세트를 갖도록 만드는 임의성의 원인이 있다는 것입니다. + + + +데이터를 살펴본 후 모델의 몇 가지 예측을 살펴보고 디코딩합니다. 모델이 항상 동일한 것을 예측하는 경우 데이터 세트가 하나의 범주(분류 문제의 경우)로 편향되어 있기 때문일 수 있습니다. 희귀 클래스를 오버샘플링하는 것과 같은 기술이 도움이 될 수 있습니다. + +초기 모델에서 얻은 손실값/메트릭값이 무작위 예측에 대해 예상한 손실값/메트릭값과 매우 다른 경우 버그가 있을 수 있으므로 손실값 또는 메트릭값이 계산되는 방식을 다시 확인하세요. 마지막에 추가적인 여러 손실함수를 사용하는 경우 동일한 크기인지 확인하세요. + +데이터가 완벽하다고 확신하는 경우, 모델이 학습을 진행할 수 있는지 한번 간단하게 테스트 해보세요. + +### 한번의 배치에 모델 과적합 해보기 + +과적합은 일반적으로 훈련할 때 피하려고 합니다. 과적합은 모델이 우리가 원하는 일반적인 기능을 인식하는 방법을 배우는 것이 아니라 훈련 샘플을 암기하는 것임을 의미하기 때문입니다. 그러나 하나의 배치에서 반복해서 모델을 훈련시키려는 시도는 훈련하려는 모델에서 설정한 문제를 해결할 수 있는지 확인하는 좋은 테스트입니다. 또한 초기 학습률이 너무 높은지 확인하는 데 도움이 됩니다. + +`Trainer`를 정의한 후에는 이 작업을 수행하는 것이 정말 쉽습니다. 학습 데이터 배치를 사용하여 20단계 정도로 작은 수동 학습 루프를 실행해보세요.: + +Doing this once you have defined your `Trainer` is really easy; just grab a batch of training data, then run a small manual training loop only using that batch for something like 20 steps: + +```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() +``` + + + +💡 학습 데이터가 불균형한 경우 모든 레이블을 포함하는 학습 데이터 배치를 빌드해야 합니다. + + + +결과 모델은 동일한 `batch`에서 완벽에 가까운 결과를 가져야 합니다. 결과 예측에 대한 메트릭을 계산해 보겠습니다.: + +```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`를 다시 만들어야 합니다. 학습한 모델은 전체 데이터 세트에서 유용한 것을 재구성하거나 학습할 수 없기 때문입니다. + + + +### 첫 기준 모델을 만들기전엔 튜닝하지 마세요. + +하이퍼파라미터 튜닝은 머신 러닝에서 가장 어려운 부분으로 항상 강조되지만 메트릭에 대해 약간의 정보를 얻는 데 도움이 되는 마지막 단계일 뿐입니다. 대부분의 경우 `Trainer`의 기본 하이퍼파라미터가 잘 작동하여 좋은 결과를 얻을 수 있으므로 데이터 세트에 해당하는 기준 모델을 능가하는 항목을 찾을 때까지 시간과 비용이 많이 드는 하이퍼파라미터 검색을 하지마세요. + +적합한 모델이 있으면 약간의 튜닝을 시작할 수 있습니다. 서로 다른 하이퍼파라미터로 수천 번의 실행을 시도하지 말고 하나의 하이퍼파라미터에 대해 서로 다른 값을 가진 두 번의 실행을 비교하여 가장 큰 영향을 미치는 아이디어를 얻어보세요. + +모델 자체를 튜닝하는 경우 간단하게 유지하고 합리적으로 정당화할 수 없는 것은 시도하지 마세요. 항상 과적합 테스트로 돌아가 변경 사항이 의도하지 않은 결과를 가져오지 않았는지 확인하세요. + +### 도움 요청하기 + +이 장에서 문제를 해결하는 데 도움이 되는 몇 가지 조언을 찾으셨기를 바랍니다. 그렇지 않은 경우 언제든지 [포럼](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/ko/chapter8/4_tf.mdx b/chapters/ko/chapter8/4_tf.mdx new file mode 100644 index 000000000..5640c6aba --- /dev/null +++ b/chapters/ko/chapter8/4_tf.mdx @@ -0,0 +1,488 @@ + + +# 학습 파이프라인 디버깅 + + + +[단원 7](/course/chapter7)의 조언에 충실히 따라 주어진 작업에서 모델을 훈련하거나 파인튜닝하는 멋진 스크립트를 작성했습니다. 하지만 `model.fit()` 명령을 실행하면 끔찍한 일이 발생합니다. 오류가 발생했네요 😱! 또는 더 좋지 못한 일은 모든 것이 정상인 것처럼 보이고 훈련은 오류 없이 실행되었지만 결과 모델이 엉망인 경우입니다. 이 장에서는 이러한 종류의 이슈를 디버그하기 위해 수행할 수 있는 작업을 보여줍니다. + +## 학습 파이프라인 디버깅 + + + +`model.fit()`에서 오류가 발생했을 때 문제는 여러 곳에서 발생할 수 있습니다. 학습은 일반적으로 시작부터 해당 에러 지점까지 많은 작업이 진행되기 때문인데, 문제는 데이터세트가 뭔가 잘못 되었거나, 데이터세트의 배치 데이터를 함께 일괄 처리하려고 할 때 발생하는 문제일 수 있습니다. 또는 모델 코드, 손실 함수 또는 옵티마이저에 문제가 있을 수 있습니다. 모든 것이 훈련에 적합하더라도 메트릭에 문제가 있는 경우 평가 중에 문제가 발생할 수 있습니다. + +`model.fit()`에서 발생하는 오류를 디버그하는 가장 좋은 방법은 이 전체 파이프라인을 직접 살펴보고 문제가 발생한 부분을 확인하는 것입니다. 가끔 에러는 해결하기 쉬울 때도 있습니다. + +예시를 위해서 [MNLI 데이터세트](https://huggingface.co/datasets/glue)에서 DistilBERT 모델을 파인튜닝(을 시도하는) 아래 스크립트를 사용합니다.: + +```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년 11월 이후에 이 코스를 읽고 있는데 여전히 진행 중이라면 수정 할 때까지 @carrigmat로 분노의 트윗을 보내주세요. + +위 문제보단 누가봐도 명백한 에러가 발생한다는 것인데, 정말 끔찍하게 깁니다: + +```python out +ValueError: No gradients provided for any variable: ['tf_distil_bert_for_sequence_classification/distilbert/embeddings/word_embeddings/weight:0', '...'] +``` + +이게 무슨 뜻일까요? 데이터로 학습하려고 하는데 그라디언트가 없다니? 꽤나 당황스러운 일입니다; 이럴 경우 어떻게 디버깅을 할 수 있을까요? 에러가 발생했을 때 문제가 어디에 있는지 바로 알 수 없는 경우 가장 좋은 해결책은 각 단계에서 모든 것이 올바르게 보이는지 확인하면서 순서대로 살펴보는 것이 효과적인 경우가 있습니다. 물론 시작하는 곳은 항상... + +### 데이터 확인 + +굳이 언급하자면, 데이터가 손상된 경우 Keras는 스스로 수정할 수 없습니다. 따라서 가장 먼저 해야 할 일은 학습 세트 내부가 어떤지 살피는 것입니다. + +`raw_datasets` 및 `tokenized_datasets` 내부를 살펴보면 좋겠지만 왠만하면 모델에 들어가기 전 지점의 데이터를 확인하는 것이 좋습니다. 즉, `to_tf_dataset()` 함수로 생성한 `tf.data.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'를 전달하고 있는데, 이는 출력값과 손실값을 계산하는 데 필요한 모든 것이 있어야 합니다. 그럼 왜 그래디언트가 없는 걸까요? 자세히 보면 단일 Dictionary 구조로 입력값을 전달하지만 학습 배치는 일반적으로 텐서 또는 Dictionary, 그리고 레이블 텐서입니다. Dictionary 입력에서 레이블은 키로 되어있습니다. + +이것이 문제일까요? 항상 그런 것은 아닙니다! 그렇지만 TensorFlow로 Transformer 모델을 훈련할 때 접하게 되는 가장 일반적인 문제 중 하나입니다. 우리 모델은 모두 내부적으로 손실값을 계산할 수 있지만 그렇게 하려면 레이블이 입력 Dictionary에 전달되어야 합니다. 이것은 `compile()`에 손실 파라미터 값을 지정하지 않을 때 사용되는 손실값 입니다. 반면에 Keras는 일반적으로 레이블이 입력 Dictionary와 별도로 전달될 것으로 예상하며 그렇게 하지 않으면 일반적으로 손실값 계산이 실패합니다. + +문제는 이제 명확해졌습니다. 우리는 'loss' 파라미터값을 설정 했었습니다. 즉, Keras에게 손실값을 계산하도록 요청했지만, Keras가 예상하는 위치가 모델에다가 레이블을 직접 전달했습니다! 따라서 둘 중 하나를 선택해야 합니다. 모델의 내부 손실 함수를 사용하고 레이블을 그대로 유지하거나, Keras 손실함수를 사용하고 레이블을 Keras가 예상하는 위치로 이동해야 합니다. 심플하게 첫 번째 접근 방식을 취하겠습니다. `compile()`에 대한 호출을 다음과 같이 변경합니다.: + +```py +model.compile(optimizer="adam") +``` + +이제 모델의 내부 손실 함수를 사용하게 될 것이고 문제가 해결 될 겁니다! +Now we'll use the model's internal loss, and this problem should be resolved! + + + +✏️ **여러분 차례입니다!** 다른 문제를 해결 후 추가 도전으로, 이 단계로 돌아와 모델이 내부 손실 대신 원래 Keras 손실함수로 작동하도록 할 수 있습니다. 레이블이 올바르게 출력되도록 하려면 `to_tf_dataset()`의 `label_cols` 인수에 `"labels"`를 추가해야 합니다. 그러면 그래디언트가 계산됩니다. 하지만 우리가 지정한 손실함수에는 한 가지 문제가 더 있습니다. 학습은 문제가 있음에도 불구하고 계속 실행되지만 학습이 매우 느리고 높은 train 손실값에서 정체 될 수 있습니다. 왜 그런지 알아 볼까요? + +만약 어렵다면 ROT13 인코드 방식의 힌트를 보세요 Transformers에서 SequenceClassification 모델의 출력을 보면 첫 번째 출력은 'logits'입니다. logits란 무엇일까요?(살면서 ROT13 인코딩은 처음 봤네요, 궁금하면 영어 원본으로 보세요.) + +두번째 힌트: 옵티마이저, 활성함수 또는 손실함수를 문자열로 지정하면 Keras는 해당 함수의 모든 인수 값을 기본값으로 설정합니다. SparseCategoricalCrossentropy에는 어떤 인수가 있으며 기본값은 무엇일까요? + + + +이제 훈련을 해보죠. 그라디언트를 가져와야 하므로 부디(갑자기 불길한 음악이 재생됨) `model.fit()`을 호출하면 모든 것이 잘 작동할 것입니다! + +```python out + 246/24543 [..............................] - ETA: 15:52 - loss: nan +``` + +이런. + +`nan`은 썩 내키지 않는 손실값 입니다. 데이터를 확인했었지만 괜찮아 보였는데 말이죠. 이게 문제가 아니라면 다음은 어떤 것을 확인해야할까요? 다음 단계는 분명히... + +### 모델 확인 + +`model.fit()`은 Keras에서 정말 훌륭한 함수이지만 많은 일을 수행하므로 문제가 발생한 위치를 정확히 찾기가 더 어려울 수 있습니다. 모델을 디버깅하는 경우 진짜 도움이 될만한 전략으로 모델에 단일 배치를 전달하고 해당 배치에 대한 출력을 자세히 살펴보는 방법이 있습니다. 모델에서 오류가 발생하는 경우 정말 유용한 또 다른 팁은 `run_eagerly=True`로 모델을 `compile()`하는 것입니다. 이렇게 하면 속도가 훨씬 느려지지만 모델 코드에서 문제가 발생한 위치를 정확히 나타내기 때문에 에러 메시지를 훨씬 더 이해하기 쉽게 만듭니다. + +하지만 지금은 아직 `run_eagerly`가 필요하지 않습니다. 모델을 통해 이전에 얻은 `batch`를 실행하고 출력이 어떻게 보이는지 봅시다: +For now, though, we don't need `run_eagerly` just yet. Let's run the `batch` we got before through the model and see what the outputs look like: + +```py +model(batch) +``` + +```python out +TFSequenceClassifierOutput(loss=, logits=, hidden_states=None, attentions=None) +``` + +흠, 무언가 복잡하네요. 모든 것이 `nan` 입니다! 하지만 이상하지 않나요? 모든 로짓은 어떻게 `nan`이 될까요? `nan`은 "not a number.", 즉 숫자가 아님을 뜻합니다. `nan` 값은 0으로 나누는 것과 같은 금지된 연산을 수행할 때 종종 발생합니다. 그러나 기계 학습에서 `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` 값이 없습니다. 그러나 우리는 손실값에서 몇 개의 '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를 가지고 있습니다. 이건 강력한 힌트입니다. 레이블이 2일 때 'nan'손실값이 발생한다는 사실은 우리 모델의 레이블 수를 확인하기에 아주 좋은 시기임을 뜻합니다.: + +```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` 없으며, 손실값이 감소하는 듯 보입니다... 한참을 보다보면 손실값이 완고하게 높은 상태를 유지하기 때문에 조금 조바심이 나기 시작할지도 모릅니다. 여기서 학습을 중단하고 이 문제의 원인이 무엇인지 생각해 보겠습니다. 이 시점에서 우리는 데이터와 모델이 모두 괜찮다고 확신하지만 우리 모델은 잘 학습하지 못하고 있습니다. 무엇이 더 남았을까요? 그건 바로... + +### 하이퍼파라미터 확인 + +위의 코드를 다시 보면 `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)) +``` + + + +🤗 Transformers에서 `create_optimizer()` 함수를 가져올 수도 있습니다. 이렇게 하면 적합한 가중치 감쇠(weight decay)와 학습률 워밍업과 감쇠가 포함된 AdamW 옵티마이저가 제공됩니다. 이 옵티마이저는 기본 Adam 옵티마이저로 얻은 결과보다 약간 더 나은 결과를 만들어내는 경우가 많습니다. + + + +이제 새롭고, 개선된 학습률로 모델 학습에 도전해보겠습니다.: + +```python +model.fit(train_dataset) +``` + +```python out +319/24543 [..............................] - ETA: 16:07 - loss: 0.9718 +``` + +이제 손실값은 정말로 움직이고 있습니다! 학습이 마침내 효과가 있는 것 같습니다. 여기에는 교훈이 있습니다. 모델이 실행 중이지만 손실값이 감소하지 않고 데이터가 괜찮다고 확신하는 경우 학습률 및 가중치 감쇠와 같은 하이퍼파라미터를 확인하는 것이 좋습니다. 둘 중 하나를 너무 높게 설정하면 학습이 높은 손실 값에서 "정지"될 가능성이 매우 높습니다. + + +## 다른 잠재적 이슈들 + +위의 스크립트에서 문제를 다루었지만 직면할 수 있는 여러가지 다른 일반적인 오류가 있습니다. (매우 불완전한) 목록을 살펴보겠습니다. + +### 메모리 부족 에러 처리 + +메모리 부족을 알리는 신호는 "OOM when allocating tensor"와 같은 에러입니다. OOM은 "out of memory"의 줄임말입니다. 이것은 큰 언어 모델을 다룰 때 매우 흔한 위험입니다. 이 문제가 발생하면 배치 크기를 절반으로 줄이고 다시 시도하는 것이 좋습니다. 그러나 일부 모델은 *매우* 큽니다. 예를 들어, 최대크기 GPT-2는 1.5B개의 매개변수가 있습니다. 즉, 모델을 저장하는 데만 6GB의 메모리가 필요하고 그라디언트에는 6GB가 추가로 필요합니다! 최대 크기 GPT-2 모델을 학습하려면 사용하는 배치 크기에 관계없이 일반적으로 20GB 이상의 VRAM이 필요하며, 소수의 GPU만 해당합니다. 'distilbert-base-cased'와 같은 더 가벼운 모델은 실행하기가 훨씬 쉽고 훨씬 빠르게 학습할 수 있습니다. + + + +다음 장에서는 메모리 사용량을 줄이고 가장 큰 모델을 파인튜닝 할 수 있는 고급 기술을 살펴보겠습니다. + + + +### 몹시 배고픈 TensorFlow 🦛 + +TensorFlow의 특별한 특징 중 하나는 모델을 로드하거나 학습을 수행하는 즉시 GPU 메모리의 *모든*을 자체적으로 할당한 다음 필요에 따라 해당 메모리를 분할한다는 것입니다. 이것은 PyTorch와 같은 다른 프레임워크의 동작과 다른데, 이는 내부적으로 수행하지 않고 CUDA에서 필요에 따라 메모리를 할당합니다. TensorFlow 접근 방식의 한 가지 장점은 메모리가 부족할 때 종종 유용한 오류를 제공할 수 있고 전체 CUDA 커널을 충돌시키지 않고 해당 상태에서 복구할 수 있다는 것입니다. 그러나 중요한 단점도 있습니다. 한 번에 두 개의 TensorFlow 프로세스를 실행하면 **나쁜 시간을 보내게 됩니다**. + +Colab에서 실행하는 경우에는 이에 대해 걱정할 필요가 없지만 로컬에서 실행하는 경우에는 확실히 주의해야 합니다. 특히 노트북(주피터 노트북 등) 탭을 닫아도 해당 노트북이 반드시 종료되는 것은 아닙니다. 실행 중인 노트북(녹색 아이콘이 있는 노트북)을 선택하고 디렉토리 목록에서 수동으로 종료해야 할 수도 있습니다. TensorFlow를 사용하는 실행 중인 노트북은 여전히 ​​많은 GPU 메모리를 보유하고 있을 수 있으며, 이는 새로 시작하는 노트북에서 매우 이상한 문제가 발생할 수 있음을 의미합니다. + +이전에 작동했던 코드에서 CUDA, BLAS 또는 cuBLAS에 대한 오류가 발생하기 시작하면 이 현상이 범인인 경우가 많습니다. `nvidia-smi`와 같은 명령을 사용하여 현재 노트북을 종료하거나 다시 시작할 때 대부분의 메모리가 사용 가능한지, 아니면 여전히 사용 중인지 확인할 수 있습니다. 아직 사용 중이라면 다른 무언가가 GPU를 붙잡고 있는 것입니다! + +### 데이터 확인 (다시!) + +모델은 데이터에서 실제로 무엇이든 학습할 수 있는 경우에만 무언가를 학습합니다. 데이터를 손상시키는 버그가 있거나 레이블이 무작위로 지정된 경우 데이터 세트에 대해 모델이 학습을 하지 못할 가능성이 매우 높습니다. 여기에서 유용한 도구 중 하나는 `tokenizer.decode()`입니다. 이렇게 하면 `input_ids`가 다시 문자열로 바뀌므로 데이터를 보고 학습 데이터가 원하는 내용을 가르치는지 확인할 수 있습니다. 예를 들어, 위에서 했던 것처럼 `tf.data.Dataset`에서 `batch`를 얻은 후 다음과 같이 첫 번째 요소를 디코딩할 수 있습니다.: + +```py +input_ids = batch["input_ids"].numpy() +tokenizer.decode(input_ids[0]) +``` + +그리고 첫번째 레이블과 함께 다음처럼 비교해볼 수 있습니다.: + +```py +labels = batch["labels"].numpy() +label = labels[0] +``` + +이처럼 데이터를 볼 수 있게 되면 스스로에게 다음과 같은 질문을 할 수 있습니다.: + +- 디코드된 데이터가 이해할만한가? +- 디코드된 데이터에 대한 레이블이 적합한가? +- 다른 레이블보다 더 일반적인 레이블이 있는가? +- 모델이 임의의 답변을 내거나, 같은 답변만 한다면 어떤 손실/평가 함수를 사용해야 할까? + +데이터를 살펴본 후 모델의 예측을 몇 가지 살펴보세요. 모델이 토큰을 출력하는 경우 디코딩도 시도해 보세요. 모델이 항상 동일한 것을 예측하는 경우 데이터 세트가 하나의 범주(분류 문제의 경우)로 편향되어 있으므로 희귀 클래스를 오버샘플링하는 것과 같은 기술이 도움이 될 수 있습니다. 또는 잘못된 하이퍼파라미터 설정과 같은 학습 문제로 인해 발생할 수도 있습니다. + +학습 전에 초기 모델에서 얻은 손실값/평가값이 무작위 예측에 대해 예상하는 손실값/평가값과 매우 다른 경우 버그가 있을 수 있으므로 손실값 또는 평가값이 계산되는 방식을 다시 확인하세요. 마지막에 여러 손실함수를 추가해 사용하는 경우 동일한 스케일의 값인지 확인하세요. + +데이터가 완벽하다고 확신한다면 한 번의 간단한 테스트로 모델이 데이터를 학습할 수 있는지 확인할 수 있습니다. + +### 한 배치에서 모델 과대적합 + +과적합은 일반적으로 학습할 때 피하려고 합니다. 이는 모델이 우리가 원하는 일반적인 기능을 인식하는 것을 학습하지 않고 대신 학습 샘플을 기억한다는 것을 의미하기 때문입니다. 그러나 한 배치에서 모델을 계속해서 훈련시키려고 시도하는 것은 학습하려는 모델로 우리가 구성한 문제를 해결할 수 있는지 확인하는 좋은 테스트입니다. 또한 초기 학습률이 너무 높은지 확인하는 데 도움이 됩니다. + +'모델'을 정의한 후에 이 작업을 수행하는 것은 정말 쉽습니다. 학습 데이터 배치를 가져온 다음 해당 `배치`를 전체 데이터 세트로 처리하고 여러 에포크에 걸쳐 학습합니다.: + +```py +for batch in train_dataset: + break + +# Make sure you have run model.compile() and set your optimizer, +# and your loss/metrics if you're using them + +model.fit(batch, epochs=20) +``` + + + +💡 학습 데이터가 불균형한 경우 모든 레이블을 포함하는 학습 데이터 배치를 구성해야 합니다. + + + +결과 모델은 `배치`에서 완벽에 가까운 결과를 가져야 하며 손실은 0(또는 사용 중인 손실의 최소값)으로 빠르게 감소합니다. + +모델이 이와 같은 완벽한 결과를 얻도록 관리하지 못한다면 문제 또는 데이터를 구성하는 방식에 문제가 있음을 의미하므로 수정해야 합니다. 과적합 테스트를 통과해야만 모델이 실제로 무언가를 학습할 수 있다고 확신할 수 있습니다. + + + +⚠️ 과적합 테스트 후에 모델을 다시 만들고 다시 컴파일해야 합니다. 학습 모델이 전체 데이터 세트에서 유용한 것을 복구하고 학습할 수 없기 때문입니다. + + + +### 첫 번째 기준이 생길 때까지 아무 것도 조정하지 마세요. + +강렬한 하이퍼파라미터 조정은 항상 기계 학습의 가장 어려운 부분으로 강조되지만 지표에서 약간의 이득을 얻는 데 도움이 되는 마지막 단계일 뿐입니다. Transformer 모델과 함께 기본 Adam 학습률 1e-3을 사용하는 것과 같이 하이퍼파라미터에 대한 *매우* 나쁜 값은 학습 진행을 매우 느리게 또는 완전히 중단하게 만들지만 대부분의 경우 다음과 같은 "합리적인" 하이퍼파라미터 1e-5에서 5e-5까지의 학습률을 사용하면 좋은 결과를 얻을 수 있습니다. 따라서 데이터 세트의 기준을 능가하는 결과를 얻을 때까지 시간과 비용이 많이 드는 하이퍼파라미터 검색을 시작하지 마세요. + +적당한 모델을 갖고나면 약간 조정을 시작할 수 있습니다. 다른 하이퍼파라미터로 천 개의 실행을 시작하려고 하지 말고 하나의 하이퍼파라미터에 대해 다른 값을 가진 몇 개의 실행을 비교하여 가장 큰 영향력을 미치는 아이디어를 얻어보세요. + +모델 자체를 조정하는 경우 단순하게 유지하고 합리적으로 정당화할 수 없는 것은 시도하지 마세요. 항상 과적합 테스트로 돌아가서 변경 사항이 의도하지 않은 결과를 초래하지 않았는지 확인하세요. + +### 도움 요청하기 + +이 단원에서 문제를 해결하는 데 도움이 되는 몇 가지 조언을 찾았으면 좋겠지만, 그렇지 않은 경우 [포럼](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 라이브러리에서 이상한 것을 본다면 버그가 발생했을 수 있습니다. 버그를 봤을 경우 우리에게 상세하게 말해야 하며, 다음 섹션에서 그 방법을 정확히 설명할 것입니다. \ No newline at end of file diff --git a/chapters/ko/chapter8/5.mdx b/chapters/ko/chapter8/5.mdx new file mode 100644 index 000000000..229cb67da --- /dev/null +++ b/chapters/ko/chapter8/5.mdx @@ -0,0 +1,90 @@ +# 좋은 이슈를 작성하는 법 + + + +Hugging Face 라이브러리 중에서 이상한 것을 발견하면 고칠 수 있도록 반드시 알려주셔야 합니다(해당 문제에 대해서는 모든 오픈 소스 라이브러리가 동일). 버그가 자신의 코드에 있는지 또는 라이브러리에 있는지 확실하지 않은 경우 가장 먼저 [포럼](https://discuss.huggingface.co/)에서 확인하세요. 커뮤니티에서 이를 파악하는 데 도움을 줄 것이며 Hugging Face 팀도 그곳에서 토론을 면밀히 관찰합니다. + + + +버그가 있다고 확신하는 경우 첫 번째 단계는 최소한의 재현 가능한 예제를 빌드하는 것입니다. + +## 재현 가능한 최소한의 예제 만들기 + +버그를 생성하는 코드 조각을 분리하는 것이 매우 중요합니다. Hugging Face 팀의 어느 누구도 (아직) 마술사가 아니며 볼 수 없는 것을 고칠 수 없기 때문입니다. 최소한의 재현 가능한 예는 이름에서 알 수 있듯이 재현 가능해야 합니다. 즉, 가지고 있는 외부 파일이나 데이터에 의존해서는 안 됩니다. 사용 중인 데이터를 실제 값처럼 보이지만 여전히 동일한 오류를 생성하는 일부 더미 값으로 바꾸세요. + + + +🚨 🤗 Transformers 저장소의 많은 문제는 트랜스포머를 재현하는 데 사용된 데이터에 액세스할 수 없기 때문에 해결되지 않습니다. + + + +코드가 준비되었더라도 더 적은 수의 코드로 줄여서 우리가 _최소 재현 가능한 예제_라고 부르는 것을 만들 수 있습니다. 여기에는 약간의 추가 작업이 필요하지만 멋지고 짧은 버그 재현 코드를 제공하면 코드 수정에 대한 도움이 보장됩니다. + +어렵지 않다면 버그가 발생한 소스 코드를 검사해보세요. 문제에 대한 해결책을 찾을 수 있지만(이 경우 문제를 해결하기 위해 직접 풀 요청을 제안할 수도 있음) 일반적으로 이것은 소스코드 메인테이너가 보고서를 읽을 때 소스를 더 잘 이해하는 데 도움을 줄 수 있습니다. + +## 이슈 템플릿 작성하기 + +이슈를 제출하면 작성할 템플릿이 있음을 알 수 있습니다. 여기에서 [🤗 Transformers issues](https://github.com/huggingface/transformers/issues/new/choose)에 대한 정보를 따르지만 다른 프로젝트의 저장소에서 문제를 보고하는 경우에도 동일한 종류의 정보가 필요합니다. 템플릿을 비워두지 마세요. 시간을 들여 템플릿을 작성하면 답을 얻고 문제를 해결할 가능성이 극대화됩니다. + +일반적으로 문제를 제기할 때는 항상 정중하게 행동하세요. 이 프로젝트는 오픈소스 이므로 여러분은 무료 소프트웨어를 사용하고 있으며 아무도 여러분을 도울 의무는 없습니다. 이슈에 정당한 비판이라고 생각하는 것을 포함할 수 있지만, 그러면 메인테이너는 그것을 나쁘게 받아들이고 서두르지 않을 것입니다. 프로젝트의 [행동 강령](https://github.com/huggingface/transformers/blob/master/CODE_OF_CONDUCT.md)을 반드시 읽어보세요. + +### 개발 환경 정보 포함 + +🤗 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` 명령 시작 부분에 `!`를 추가하여 주피터 노트북 셀에서 실행한 다음 문제 시작 부분에 결과를 복사하여 붙여넣을 수도 있습니다. + +### 사람 태깅하기 + +`@`를 입력하여 사람들을 태그하면 깃허브를 통해 알림이 전송되어 문제를 확인하고 더 빨리 응답할 수 있습니다. 당신이 태그한 사람들이 직접적인 링크가 없는 것에 대해 알림을 받는 것을 달가워하지 않을 수 있기 때문에 이것을 적당히 사용하세요. 버그와 관련된 소스 파일을 살펴본 경우 문제의 원인이라고 생각되는 줄에서 마지막으로 변경한 사람을 태그해야 합니다(이 정보는 GitHub에서 해당 줄을 보고 선택한 다음 "View git blame" 클릭). + +아니면 템플릿에서 태그를 지정할 사람에 대한 제안을 받을 수 있습니다. 일반적으로 3명 이상 태그하지 마세요! + +### 재현 가능한 예제 포함하기 + +버그를 생성하는 예제를 생성했다면 이제 버그를 포함할 때입니다! 다음과 같이 백틱 세 개 뒤에 `python`이 오는 행을 입력하세요.: + +``` +```python +``` + +그런 다음 최소한의 재현 가능한 예를 붙여넣고 세 개의 백틱이 있는 새 줄을 입력합니다. 이렇게 하면 코드의 형식이 올바르게 지정됩니다. + +재현 가능한 예제를 생성하지 못한 경우 문제가 발생하는 방법을 명확한 단계로 설명하세요. 가능한 경우 오류가 발생한 Google Colab 노트북에 대한 링크를 포함합니다. 더 많은 정보를 공유할수록 메인테이너가 더 잘 답변할 수 있습니다. + +모든 경우에 표시되는 전체 오류 메시지를 복사하여 붙여넣어야 합니다. Colab에서 작업하는 경우 일부 프레임이 Traceback에서 자동으로 축소될 수 있으므로 복사하기 전에 프레임을 확장했는지 확인하세요. 코드 샘플과 마찬가지로 세 개의 백틱이 있는 두 줄 사이에 해당 오류 메시지를 넣어 형식이 올바르게 지정되도록 합니다. + +### 기대하는 동작 설명하기 + +메인테이너가 문제를 완전히 이해할 수 있도록 기대했던 것을 몇 줄로 설명하세요. 이 부분은 일반적으로 꽤 뻔하기 때문에 한 문장에 정리가 되어야 겠지만 경우에 따라 할 말이 많을 수도 있습니다. + +## 그 다음? + +이슈를 제출하고나서 모든 것이 괜찮은지 간단하게 확인하세요. 실수를 한 경우 이슈를 편집하거나 문제가 처음 생각한 것과 다르다는 것을 알게 된 경우 제목을 변경할 수도 있습니다. + +답을 얻지 못하면 사람들에게 핑을 날릴 이유가 없습니다. 며칠 동안 아무도 당신을 도와주지 않는다면 아무도 이 문제를 이해할 수 없다는 이야기 입니다. 재현 가능한 예로 돌아가는 것을 주저하지 마십시오. 더 짧고 더 요점을 만들 수 있습니까? 일주일 안에 답변을 받지 못한 경우, 특히 문제에 대한 자세한 정보를 포함하도록 문제를 편집한 경우 부드럽게 도움을 요청하는 메시지를 남길 수 있습니다. \ No newline at end of file diff --git a/chapters/ko/chapter8/6.mdx b/chapters/ko/chapter8/6.mdx new file mode 100644 index 000000000..04d373b4b --- /dev/null +++ b/chapters/ko/chapter8/6.mdx @@ -0,0 +1,11 @@ +# 2단원 완료! + + +축하합니다. 코스의 두 번째 부분을 통과했습니다! 세 번째 소식을 열심히 준비 중이니 [뉴스레터](https://huggingface.curated.co/)를 구독하여 소식을 놓치지 않도록 하세요. + +이제 다양한 NLP 작업을 처리하고 이에 대한 모델을 파인 튜닝하거나 사전 학습할 수 있습니다. [모델 허브](https://huggingface.co/models)에서 커뮤니티와 결과를 공유하는 것을 잊지 마세요. + +여러분이 얻은 지식으로 무엇을 구축할지 기대됩니다! \ No newline at end of file diff --git a/chapters/ko/chapter8/7.mdx b/chapters/ko/chapter8/7.mdx new file mode 100644 index 000000000..44966b8db --- /dev/null +++ b/chapters/ko/chapter8/7.mdx @@ -0,0 +1,204 @@ + + +# 단원 마무리 퀴즈 + + + +이 챕터에서 배운 내용을 테스트해 봅시다! + +### 1. 파이썬 traceback을 어떤 순서로 읽어야 할까요? + + + +### 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: "Traceback의 마지막 줄을 포함하는 것은 설명적일 수 있지만 주제의 본문에 더 적합합니다. 다시 풀어보세요!" + }, + { + text: "문제가 있습니다. from transformers import GPT3ForSequenceClassification", + explain: "다시 풀어보세요. 유용한 정보를 제공하지만 텍스트의 본문용으로 사용하는 것이 가장 좋습니다.", + }, + { + text: "왜 GPT3ForSequenceClassification import가 안될까요? ?", + explain: "훌륭한 선택입니다! 이 제목은 간결하고 독자에게 무엇이 잘못되었는지에 대한 단서를 제공합니다(GPT-3는 🤗 트랜스포머에서 지원되지 않음).", + correct: true + }, + { + text: "🤗 Transformers에서 GPT-3가 지원되나요?", + explain: "잘했습니다! 질문을 주제 제목으로 사용하는 것은 문제를 커뮤니티에 전달하는 좋은 방법입니다.", + correct: true + } + ]} +/> + +### 4. `trainer.train()`을 실행하려고 시도했는데 정확히 어디서 에러가 발생했는지 알려주지 않는 알 수 없는 에러에 직면했다고 가정합니다. 다음 중 학습 파이프라인에서 에러를 찾아야 하는 첫 번째 위치는 어디일까요? + + + +### 5. CUDA 에러를 디버그하는 최고의 방법은 무엇일까요? + + + +### 6. GitHub에서 문제를 해결하는 가장 좋은 방법은 무엇일까요? + + + +### 7. 하나의 배치에 과적합하는 것이 일반적으로 좋은 디버깅 기술인 이유는 무엇일까요? + + + +### 8. 🤗 Transformers 저장소에서 새 이슈를 생성할 때 `transformers-cli env`를 사용하여 컴퓨팅 환경에 대한 세부 정보를 포함하는 것이 좋은 이유는 무엇일까요? + + diff --git a/subtitles/en/62_what-is-the-rouge-metric.srt b/subtitles/en/62_what-is-the-rouge-metric.srt index 5450615b0..91bc51a13 100644 --- a/subtitles/en/62_what-is-the-rouge-metric.srt +++ b/subtitles/en/62_what-is-the-rouge-metric.srt @@ -32,7 +32,7 @@ In this video, we'll take a look 8 00:00:18,180 --> 00:00:21,180 at a widely used metric for -tech summarization called ROUGE. +text summarization called ROUGE. 9 00:00:22,740 --> 00:00:24,660 @@ -298,7 +298,7 @@ because there are fewer bios to match. 68 00:02:34,290 --> 00:02:36,870 And this is also true for -abstracter summarization. +abstractive summarization. 69 00:02:36,870 --> 00:02:39,993 @@ -372,7 +372,7 @@ in the data sets library is very simple. 84 00:03:21,660 --> 00:03:23,910 -You just use the load metric function, +You just use the load_metric function, 85 00:03:23,910 --> 00:03:26,400 diff --git a/subtitles/zh-CN/62_what-is-the-rouge-metric.srt b/subtitles/zh-CN/62_what-is-the-rouge-metric.srt index ea7ea6666..e0e00486c 100644 --- a/subtitles/zh-CN/62_what-is-the-rouge-metric.srt +++ b/subtitles/zh-CN/62_what-is-the-rouge-metric.srt @@ -20,12 +20,12 @@ we can use common metrics like accuracy or F1 score. 5 00:00:12,270 --> 00:00:13,650 -但是当你想测量某样东西时你会怎么做 +但是当你想评估类似从像 T5 这样的模型上 But what do you do when you wanna measure something 6 00:00:13,650 --> 00:00:16,920 -喜欢 T5 这样的模型的摘要质量? +所获得的文本摘要的质量,该如何操作呢? like the quality of a summary from a model like T5? 7 @@ -35,8 +35,8 @@ In this video, we'll take a look 8 00:00:18,180 --> 00:00:21,180 -在广泛使用的技术摘要指标中,称为 ROUGE。 -at a widely used metric for tech summarization called ROUGE. +被广泛用于文本摘要的评估指标,称为 ROUGE。 +at a widely used metric for text summarization called ROUGE. 9 00:00:22,740 --> 00:00:24,660 @@ -45,22 +45,22 @@ There are actually several variants of ROUGE 10 00:00:24,660 --> 00:00:26,190 -但所有这些背后的基本思想 +但所有这些变体背后的基本思想 but the basic idea behind all of them 11 00:00:26,190 --> 00:00:27,840 -是分配一个单一的数字分数 +是为每个摘要分配一个单独的分数 is to assign a single numerical score 12 00:00:27,840 --> 00:00:30,000 -总结告诉我们它有多好 +来告诉我们相比一个或者多个参考的摘要 to a summary that tells us how good it is 13 00:00:30,000 --> 00:00:32,774 -与一个或多个参考摘要相比。 +当前的摘要有多好。 compared to one or more reference summaries. 14 @@ -70,23 +70,23 @@ In this example, we have a book review 15 00:00:34,020 --> 00:00:36,570 -这已经被一些模型总结了。 +它是通过某些模型摘要获得。 that has been summarized by some model. 16 00:00:36,570 --> 00:00:38,320 -如果我们比较生成的摘要 +如果我们将生成的摘要 If we compare the generated summary 17 00:00:39,168 --> 00:00:40,260 -参考一些人类总结,我们可以看到 -to some reference human summaries, we can see +和一些人工的摘要相比较, +to some reference human summaries, 18 00:00:40,260 --> 00:00:42,841 -该模型实际上非常好 -that the model is actually pretty good +我们可以看到该模型实际上非常好 +we can see that the model is actually pretty good 19 00:00:42,841 --> 00:00:44,063 @@ -95,57 +95,57 @@ and only differs by a word or two. 20 00:00:45,060 --> 00:00:46,260 -那么我们如何衡量质量 +那么我们如何通过自动的方式 So how can we measure the quality 21 00:00:46,260 --> 00:00:49,050 -以自动方式生成的摘要? +评估生成的摘要的质量呢? of a generated summary in an automatic way? 22 00:00:49,050 --> 00:00:51,510 -ROUGE 采用的方法是比较 n-gram +ROUGE 采用的方法是比较生成的摘要的 n-gram The approach that ROUGE takes is to compare the n-grams 23 00:00:51,510 --> 00:00:55,200 -生成的摘要到参考文献的 n-gram。 +和参考文献的 n-gram。 of the generated summary to the n-grams of the references. 24 00:00:55,200 --> 00:00:58,590 -n-gram 只是一种表达 N 个词块的奇特方式。 +n-gram 只是一种表达 N 个单词的词块的流行说法。 And n-gram is just a fancy way of saying a chunk of N words. 25 00:00:58,590 --> 00:01:00,030 -所以让我们从 unigrams 开始 +所以让我们从 unigram 开始 So let's start with unigrams 26 00:01:00,030 --> 00:01:02,780 -对应于句子中的各个单词。 +它对应于句子中的各个单词。 which correspond to the individual words in a sentence. 27 00:01:03,780 --> 00:01:05,250 -在这个例子中,你可以看到六个 +在这个例子中,你可以看到 In this example, you can see that six 28 00:01:05,250 --> 00:01:07,650 -生成的摘要中的单词也被发现 +在生成的摘要中有 6 个单词,也出现在 of the words in the generated summary are also found 29 00:01:07,650 --> 00:01:09,420 -在其中一个参考摘要中。 +其中一个参考的摘要中。 in one of the reference summaries. 30 00:01:09,420 --> 00:01:11,310 -以及比较 unigrams 的 rouge metric +而比较 unigram 的 rouge metric And the rouge metric that compares unigrams 31 @@ -155,32 +155,32 @@ is called ROUGE-1. 32 00:01:14,533 --> 00:01:16,770 -现在我们找到了我们的比赛,一种分配分数的方法 +现在我们找到了我们的匹配项,一种为摘要分配分数的方法 Now that we found our matches, one way to assign a score 33 00:01:16,770 --> 00:01:20,280 -总结是计算 unigrams 的召回率。 +是计算 unigram 的被召回次数。 to the summary is to compute the recall of the unigrams. 34 00:01:20,280 --> 00:01:21,540 -这意味着我们只计算数量 +这意味着我们只计算 This means we just count the number 35 00:01:21,540 --> 00:01:22,950 -生成的匹配词的 +生成摘要和参考摘要的匹配词 of matching words in the generated 36 00:01:22,950 --> 00:01:25,290 -和参考摘要并规范化计数 +并将计数通过除以参考文本中的单词数 and reference summaries and normalize the count 37 00:01:25,290 --> 00:01:28,200 -除以参考文献中的单词数。 +进行规范化处理。 by dividing by the number of words in the reference. 38 @@ -190,7 +190,7 @@ In this example, we found six matching words 39 00:01:30,450 --> 00:01:32,160 -我们的参考有六个词。 +我们的参考文本有六个词。 and our reference has six words. 40 @@ -200,22 +200,22 @@ So our unigram recall is perfect. 41 00:01:34,800 --> 00:01:35,810 -这意味着所有的词 +这意味着在参考摘要中 This means that all of the words 42 00:01:35,810 --> 00:01:37,500 -在参考摘要中已经产生 +的所有的词都会出现 in the reference summary have been produced 43 00:01:37,500 --> 00:01:38,550 -在生成的那个。 +在生成的摘要中。 in the generated one. 44 00:01:40,050 --> 00:01:42,360 -现在,完美回忆听起来不错,但想象一下 +现在,完美的召回听起来不错,但想象一下 Now, perfect recall sounds great, but imagine 45 @@ -230,7 +230,7 @@ I really, really, really, 47 00:01:45,720 --> 00:01:48,150 -真的很喜欢读饥饿游戏。 +真的很喜欢阅读 Hunger Games。 really loved reading the Hunger Games. 48 @@ -260,17 +260,17 @@ we can also compute precision, 53 00:01:56,190 --> 00:01:58,380 -在 ROUGE 上下文中,它衡量了多少 +在 ROUGE 上下文中,它衡量了 which in the ROUGE context measures how much 54 00:01:58,380 --> 00:02:00,810 -生成器摘要的相关性。 +在生成器摘要中具有多少相关性。 of the generator summary was relevant. 55 00:02:00,810 --> 00:02:03,630 -在实践中,通常计算精度和召回率 +在实际操作中,通常计算精度和召回率 In practice, both precision and recall are usually computed 56 @@ -280,27 +280,27 @@ and then the F1 score is reported. 57 00:02:07,170 --> 00:02:08,542 -现在我们可以改变粒度 +现在我们可以改变比较的粒度 Now we can change the granularity 58 00:02:08,542 --> 00:02:13,020 -通过比较双字母组而不是单字母组来进行比较。 +将比较的对象由 unigram 改变为 bigram。 of the comparison by comparing bigrams instead of unigrams. 59 00:02:13,020 --> 00:02:15,090 -使用双字母组,我们将句子成对地分块 +使用 bigram,我们将句子 With bigrams, we chunk the sentence into pairs 60 00:02:15,090 --> 00:02:17,910 -连续的单词,然后计算有多少对 +切分为成对的连续单词, of consecutive words and then count how many pairs 61 00:02:17,910 --> 00:02:21,360 -在生成的摘要中出现在参考摘要中。 +然后计算生成的摘要中有多少对出现在参考摘要中。 in the generated summary are present in the reference one. 62 @@ -335,8 +335,8 @@ because there are fewer bios to match. 68 00:02:34,290 --> 00:02:36,870 -这也适用于摘要摘要。 -And this is also true for abstracter summarization. +这也适用于重写式摘要。 +And this is also true for abstractive summarization. 69 00:02:36,870 --> 00:02:39,993 @@ -350,7 +350,7 @@ The last ROUGE variant we will discuss is ROUGE L. 71 00:02:45,330 --> 00:02:47,160 -ROUGE L 不比较 ngrams +ROUGE L 不比较 ngram ROUGE L doesn't compare ngrams 72 @@ -365,17 +365,17 @@ and then looks for the longest common subsequence or LCS. 74 00:02:54,775 --> 00:02:56,130 -子序列是出现的序列 +子序列是以相同的相对顺序 A subsequence is a sequence that appears 75 00:02:56,130 --> 00:02:59,760 -以相同的相对顺序,但不一定是连续的。 +出现的序列,但不一定是连续的。 in the same relative order, but not necessarily contiguous. 76 00:02:59,760 --> 00:03:03,210 -所以在这个例子中,我喜欢阅读饥饿游戏, +所以在这个例子中,我喜欢阅读 Hunger Games, So in this example, I loved reading the Hunger Games, 77 @@ -385,42 +385,42 @@ is the longest common subsequence between the two summaries. 78 00:03:06,930 --> 00:03:08,610 -而 ROUGE L 的主要优势 +而相比 ROUGE-1 或 ROUGE-2 And the main advantage of ROUGE L 79 00:03:08,610 --> 00:03:11,670 -超过 ROUGE-1 或 ROUGE-2 的是它不依赖于 +ROUGE L 的主要优势是它不依赖于 over ROUGE-1 or ROUGE-2 is that it doesn't depend 80 00:03:11,670 --> 00:03:14,100 -在连续的 n-gram 匹配上,所以它倾向于 +在连续的 n-gram 匹配上, on consecutive n-gram matches, and so it tends 81 00:03:14,100 --> 00:03:16,650 -更准确地捕捉句子结构。 +所以它倾向于更准确地捕捉句子结构。 to capture sentence structure much more accurately. 82 00:03:18,150 --> 00:03:19,440 -现在计算 ROUGE 分数 +现在在 Dataset 库中 Now to compute ROUGE scores 83 00:03:19,440 --> 00:03:21,660 -在数据集库中很简单。 +计算 ROUGE 分数很简单。 in the data sets library is very simple. 84 00:03:21,660 --> 00:03:23,910 -你只需使用负载度量函数, -You just use the load metric function, +你只需使用 load_metric 函数, +You just use the load_metric function, 85 00:03:23,910 --> 00:03:26,400 -提供你的模型摘要以及参考资料 +提供你的模型摘要以及参考摘要 provide your model summaries along with the references 86 @@ -440,27 +440,27 @@ contains a lot of information. 89 00:03:31,507 --> 00:03:34,560 -我们首先可以看到的是置信区间 +我们首先可以看到的是 The first thing we can see is that the confidence intervals 90 00:03:34,560 --> 00:03:36,090 -提供每个 ROUGE 分数 +每个 ROUGE 分数的置信区间 of each ROUGE score are provided 91 00:03:36,090 --> 00:03:39,030 -在低、中、高领域。 +每个 ROUGE 分数的置信区间。 in the low, mid and high fields. 92 00:03:39,030 --> 00:03:40,980 -如果你想知道传播,这真的很有用 +当比较两个或多个模型时, This is really useful if you wanna know the spread 93 00:03:40,980 --> 00:03:43,730 -比较两个或多个模型时的 ROUGE 分数。 +如果你想知道 ROUGE 分数的范围,这就真的很有用。 of your ROUGE scores when comparing two or more models. 94 @@ -485,22 +485,22 @@ So what is ROUGE-L sum? 98 00:03:53,760 --> 00:03:55,410 -好吧,ROUGEL 的总和 +其实就是 ROUGEL 的总和 Well, the sum in ROUGEL's sum 99 00:03:55,410 --> 00:03:57,630 -指的是这个指标是计算出来的 +指的是这个指标是 refers to the fact that this metric is computed 100 00:03:57,630 --> 00:04:00,240 -在计算 ROUGE-L 时在整个摘要上 +当 ROUGE-L 作为单个句子的平均值计算时, over a whole summary while ROUGE-L is computed 101 00:04:00,240 --> 00:04:02,493 -作为单个句子的平均值。 +基于整个摘要计算出来的。 as the average of individual sentences. 102 diff --git a/subtitles/zh-CN/63_data-processing-for-causal-language-modeling.srt b/subtitles/zh-CN/63_data-processing-for-causal-language-modeling.srt index b6615e106..7dee719f3 100644 --- a/subtitles/zh-CN/63_data-processing-for-causal-language-modeling.srt +++ b/subtitles/zh-CN/63_data-processing-for-causal-language-modeling.srt @@ -5,22 +5,22 @@ 2 00:00:05,364 --> 00:00:08,310 -- 在这个视频中,我们来看看数据处理 +- 在这个视频中,我们来看看 - In this video, we take a look at the data processing 3 00:00:08,310 --> 00:00:10,803 -训练因果语言模型所必需的。 +训练因果语言模型所必需的数据处理。 necessary to train causal language models. 4 00:00:12,690 --> 00:00:14,400 -因果语言建模是任务 +因果语言建模是 Causal language modeling is the task 5 00:00:14,400 --> 00:00:17,820 -基于先前的标记预测下一个标记。 +基于先前的词元预测下一个词元的任务。 of predicting the next token based on the previous ones. 6 @@ -35,12 +35,12 @@ is autoregressive modeling. 8 00:00:21,000 --> 00:00:23,940 -在你可以在此处看到的示例中, +在这里的示例中, In the example that you can see here, 9 00:00:23,940 --> 00:00:25,560 -例如,下一个标记可以是 +例如,下一个词元可以是 the next token could, for example, 10 @@ -65,7 +65,7 @@ To train models such as GPT, 14 00:00:38,010 --> 00:00:41,460 -我们通常从大量文本文件开始。 +我们通常从大量文本文件组成的语料库开始。 we usually start with a large corpus of text files. 15 @@ -90,22 +90,22 @@ like the ones you can see here. 19 00:00:50,400 --> 00:00:52,680 -作为第一步,我们需要标记这些文件 +作为第一步,我们需要词元化这些文件 As a first step, we need to tokenize these files 20 00:00:52,680 --> 00:00:55,380 -这样我们就可以通过模型喂养他们。 +这样我们就可以将它们输入给模型。 such that we can feed them through the model. 21 00:00:55,380 --> 00:00:58,500 -在这里,我们将标记化的文本显示为不同长度的条, +在这里,我们将词元化的文本显示为不同长度的条, Here, we show the tokenized texts as bars of various length, 22 00:00:58,500 --> 00:01:02,188 -说明它们越来越短。 +表明它们有些长一些有些短一些。 illustrating that they're shorter and longer ones. 23 @@ -120,12 +120,12 @@ However, transform models have a limited context window 25 00:01:09,270 --> 00:01:10,770 -并根据数据源, +并根据数据源的不同, and depending on the data source, 26 00:01:10,770 --> 00:01:13,140 -标记化的文本可能 +词元化的文本可能 it is possible that the tokenized texts 27 @@ -135,28 +135,28 @@ are much longer than this window. 28 00:01:16,080 --> 00:01:18,870 -在这种情况下,我们可以截断序列 +在这种情况下,我们可以将序列 In this case, we could just truncate the sequences 29 00:01:18,870 --> 00:01:20,182 -上下文长度, +截断为上下文长度, to the context length, 30 00:01:20,182 --> 00:01:22,650 -但这意味着我们将失去一切 +但这意味着在第一个上下文窗口之后 but this would mean that we lose everything 31 00:01:22,650 --> 00:01:24,513 -在第一个上下文窗口之后。 +我们将失去一切。 after the first context window. 32 00:01:25,500 --> 00:01:28,410 -使用返回溢出令牌标志, -Using the return overflowing token flag, +使用 return_overflowing_tokens 标志, +Using the return overflowing token flag, 33 00:01:28,410 --> 00:01:30,960 @@ -165,22 +165,22 @@ we can use the tokenizer to create chunks 34 00:01:30,960 --> 00:01:33,510 -每个都是上下文长度的大小。 +其中每个块都是上下文长度的大小。 with each one being the size of the context length. 35 00:01:34,860 --> 00:01:36,180 -有时,它仍然会发生 +有时,如果没有足够的词元来填充它 Sometimes, it can still happen 36 00:01:36,180 --> 00:01:37,590 -最后一块太短了 +仍然会出现 that the last chunk is too short 37 00:01:37,590 --> 00:01:39,900 -如果没有足够的令牌来填充它。 +最后一块太短的情况。 if there aren't enough tokens to fill it. 38 @@ -200,22 +200,22 @@ we also get the length of each chunk from the tokenizer. 41 00:01:51,960 --> 00:01:53,640 -此函数显示所有步骤 +此函数显示准备数据集 This function shows all the steps 42 00:01:53,640 --> 00:01:56,280 -准备数据集所必需的。 +所必需的所有步骤。 necessary to prepare the dataset. 43 00:01:56,280 --> 00:01:57,960 -首先,我们标记数据集 +首先,我们用我刚才提到的标志 First, we tokenize the dataset 44 00:01:57,960 --> 00:02:00,330 -用我刚才提到的标志。 +词元化数据集。 with the flags I just mentioned. 45 @@ -250,12 +250,12 @@ that to use batches and remove the existing columns. 51 00:02:15,450 --> 00:02:17,670 -我们需要删除现有的列, +我们之所以需要删除现有的列, We need to remove the existing columns, 52 00:02:17,670 --> 00:02:21,330 -因为我们可以为每个文本创建多个样本, +是因为我们可以为每个文本创建多个样本, because we can create multiple samples per text, 53 @@ -290,37 +290,37 @@ are shorter than the context size 59 00:02:38,400 --> 00:02:41,610 -并且将被以前的方法丢弃。 +并且按照之前的方法处理的话,将会丢弃它。 and will be discarded with the previous approach. 60 00:02:41,610 --> 00:02:45,150 -在这种情况下,最好先标记每个样本 +在这种情况下,最好先词元化每个样本 In this case, it is better to first tokenize each sample 61 00:02:45,150 --> 00:02:46,590 -没有截断 +而不去截断 without truncation 62 00:02:46,590 --> 00:02:49,290 -然后连接标记化样本 +然后连接词元化后的样本 and then concatenate the tokenized samples 63 00:02:49,290 --> 00:02:52,353 -中间有字符串结尾或 EOS 令牌。 +并且之间以字符串结尾或 EOS 词元结尾。 with an end of string or EOS token in between. 64 00:02:53,546 --> 00:02:56,220 -最后,我们可以分块这个长序列 +最后,我们可以按照上下文长度分块这个长序列, Finally, we can chunk this long sequence 65 00:02:56,220 --> 00:02:59,490 -使用上下文长度,我们不会丢失太多序列 +我们不会丢失太多序列 with the context length and we don't lose too many sequences 66 @@ -330,17 +330,17 @@ because they're too short anymore. 67 00:03:04,170 --> 00:03:05,760 -到目前为止,我们只谈过 +到目前为止,我们只介绍了 So far, we have only talked 68 00:03:05,760 --> 00:03:08,370 -关于因果语言建模的输入, +因果语言建模的输入, about the inputs for causal language modeling, 69 00:03:08,370 --> 00:03:11,850 -但不是监督培训所需的标签。 +但还没有提到监督训练所需的标签。 but not the labels needed for supervised training. 70 @@ -360,17 +360,17 @@ as the input sequences themselves are the labels. 73 00:03:20,610 --> 00:03:24,240 -在这个例子中,当我们将 token trans 提供给模型时, -In this example, when we feed the token trans to the model, +在这个例子中,当我们将词元 Trans 提供给模型时, +In this example, when we feed the token Trans to the model, 74 00:03:24,240 --> 00:03:27,510 -我们要预测的下一个标记是前者。 +我们要预测的下一个词元是 formers 。 the next token we wanted to predict is formers. 75 00:03:27,510 --> 00:03:30,780 -在下一步中,我们将 trans 和 formers 提供给模型 +在下一步中,我们将 Trans 和 formers 提供给模型 In the next step, we feed trans and formers to the model 76 @@ -385,17 +385,17 @@ This pattern continues, and as you can see, 78 00:03:38,130 --> 00:03:41,220 -输入序列是标签序列 +输入序列是前移了一个位置的 the input sequence is the label sequence 79 00:03:41,220 --> 00:03:42,663 -只是移动了一个。 +标签序列。 just shifted by one. 80 00:03:43,590 --> 00:03:47,310 -由于模型仅在第一个标记之后进行预测, +由于模型仅在第一个词元之后进行预测, Since the model only makes prediction after the first token, 81 @@ -405,32 +405,32 @@ the first element of the input sequence, 82 00:03:49,350 --> 00:03:52,980 -在这种情况下,反式不用作标签。 -in this case, trans, is not used as a label. +在本例中,就是 Trans,不会作为标签使用。 +in this case, Trans, is not used as a label. 83 00:03:52,980 --> 00:03:55,530 -同样,我们没有标签 +同样,对于序列中的最后一个词元 Similarly, we don't have a label 84 00:03:55,530 --> 00:03:57,600 -对于序列中的最后一个标记 +我们也没有标签 for the last token in the sequence 85 00:03:57,600 --> 00:04:00,843 -因为序列结束后没有令牌。 +因为序列结束后没有词元。 since there is no token after the sequence ends. 86 00:04:04,110 --> 00:04:06,300 -让我们看看我们需要做什么 +让我们看看当需要在代码中为因果语言建模创建标签 Let's have a look at what we need to do 87 00:04:06,300 --> 00:04:10,200 -在代码中为因果语言建模创建标签。 +我们需要做什么操作。 to create the labels for causal language modeling in code. 88 @@ -450,12 +450,12 @@ and all the shifting is handled in the model internally. 91 00:04:20,032 --> 00:04:22,170 -所以,你看,不涉及任何匹配 +所以,你看,在处理因果语言建模的数据时, So, you see, there's no matching involved 92 00:04:22,170 --> 00:04:24,870 -在处理因果语言建模的数据时, +不涉及任何匹配, in processing data for causal language modeling, 93 diff --git a/subtitles/zh-CN/64_using-a-custom-loss-function.srt b/subtitles/zh-CN/64_using-a-custom-loss-function.srt index efcbaf454..0b6beaeaf 100644 --- a/subtitles/zh-CN/64_using-a-custom-loss-function.srt +++ b/subtitles/zh-CN/64_using-a-custom-loss-function.srt @@ -15,12 +15,12 @@ 4 00:00:05,550 --> 00:00:07,500 -- 在本视频中,我们将介绍如何设置 +- 在本视频中,我们将介绍 - In this video, we take a look at setting up 5 00:00:07,500 --> 00:00:09,303 -用于训练的自定义损失函数。 +如何自定义用于训练的损失函数。 a custom loss function for training. 6 @@ -30,42 +30,42 @@ In the default loss function, all samples, 7 00:00:13,260 --> 00:00:15,840 -例如这些代码片段,都被同等对待 +例如这些代码片段,无论其内容如何 such as these code snippets, are treated the same 8 00:00:15,840 --> 00:00:18,960 -不管他们的内容如何,但有一些场景 +都被同等对待,但有一些场景下 irrespective of their content but there are scenarios 9 00:00:18,960 --> 00:00:21,660 -对样本进行不同加权可能有意义。 +对样本进行不同加权是合理的。 where it could make sense to weight the samples differently. 10 00:00:21,660 --> 00:00:24,570 -例如,如果一个样本包含很多标记 +例如,如果一个样本包含很多 If, for example, one sample contains a lot of tokens 11 00:00:24,570 --> 00:00:26,160 -我们感兴趣的 +我们所感兴趣的词元 that are of interest to us 12 00:00:26,160 --> 00:00:29,910 -或者样本是否具有有利的标记多样性。 +或者样本内包含理想的多样性词元 or if a sample has a favorable diversity of tokens. 13 00:00:29,910 --> 00:00:31,950 -我们还可以实施其他启发式 +我们还可以通过模式匹配或者其他规则 We can also implement other heuristics 14 00:00:31,950 --> 00:00:33,963 -与模式匹配或其他规则。 +实现其他启发式。 with pattern matching or other rules. 15 @@ -75,7 +75,7 @@ For each sample, we get a loss value during training 16 00:00:39,150 --> 00:00:41,850 -我们可以将损失与重量结合起来。 +我们可以将损失与权重结合起来。 and we can combine that loss with a weight. 17 @@ -110,12 +110,12 @@ that helps us autocomplete common data science code. 23 00:00:57,030 --> 00:01:01,830 -对于那个任务,我们想给样本赋予更强的权重 +对于那个任务,包含和数据科学栈相关的词元 For that task, we would like to weight samples stronger 24 00:01:01,830 --> 00:01:04,110 -其中与数据科学堆栈相关的令牌, +我们想给样本赋予更强的权重, where tokens related to the data science stack, 25 @@ -125,27 +125,27 @@ such as pd or np, occur more frequently. 26 00:01:10,140 --> 00:01:13,080 -在这里你看到一个损失函数正是这样做的 +在这里你看到一个损失函数是 Here you see a loss function that does exactly that 27 00:01:13,080 --> 00:01:15,180 -用于因果语言建模。 +针对因果语言建模这样做的。 for causal language modeling. 28 00:01:15,180 --> 00:01:18,030 -它采用模型的输入和预测的逻辑, +它采用模型的输入和预测的对数, It takes the model's input and predicted logits, 29 00:01:18,030 --> 00:01:20,343 -以及作为输入的密钥标记。 +以及作为输入的关键词元。 as well as the key tokens, as input. 30 00:01:21,869 --> 00:01:25,113 -首先,输入和逻辑对齐。 +首先,输入和对数是对齐的。 First, the inputs and logits are aligned. 31 @@ -155,7 +155,7 @@ Then the loss per sample is calculated, 32 00:01:29,310 --> 00:01:30,843 -其次是重量。 +其次是权重。 followed by the weights. 33 @@ -170,7 +170,7 @@ This is a pretty big function, so let's take a closer look 35 00:01:39,150 --> 00:01:40,953 -在损失和重量块。 +损失和权重块。 at the loss and the weight blocks. 36 @@ -180,37 +180,37 @@ During the calculation of the standard loss, 37 00:01:45,600 --> 00:01:48,930 -logits 和标签在批次上变平。 +对数和标签在整批数据上进行扁平化处理。 the logits and labels are flattened over the batch. 38 00:01:48,930 --> 00:01:52,590 -有了视图,我们展开张量得到矩阵 +有了视图,我们展开 tensor 得到矩阵 With the view, we unflatten the tensor to get the matrix 39 00:01:52,590 --> 00:01:55,320 -批次中的每个样本都有一行和一列 +其中的行代表整批数据中的每个样本, with a row for each sample in the batch and a column 40 00:01:55,320 --> 00:01:57,723 -对于样本序列中的每个位置。 +其中的列表示样本在序列中的位置。 for each position in the sequence of the sample. 41 00:01:58,920 --> 00:02:00,600 -我们不需要每个头寸的损失, +我们不需要每个位置上都计算损失, We don't need the loss per position, 42 00:02:00,600 --> 00:02:04,083 -所以我们对每个样本的所有头寸的损失进行平均。 +所以我们将每个样本在所有的位置的损失进行平均。 so we average the loss over all positions for each sample. 43 00:02:06,150 --> 00:02:08,970 -对于权重,我们使用布尔逻辑得到一个张量 +对于权重,我们使用 Boolean 逻辑得到一个 tensor For the weights, we use Boolean logic to get a tensor 44 @@ -220,17 +220,17 @@ with 1s where a keyword occurred and 0s where not. 45 00:02:13,440 --> 00:02:15,690 -这个张量有一个额外的维度 +这个 tensor 有一个额外的维度 This tensor has an additional dimension 46 00:02:15,690 --> 00:02:18,540 -作为我们刚刚看到的损失张量,因为我们得到 +作为我们刚刚看到的损失 tensor, as the loss tensor we just saw because we get 47 00:02:18,540 --> 00:02:21,693 -单独矩阵中每个关键字的信息。 +因为我们可以获得单独矩阵中的每个关键词的信息。 the information for each keyword in a separate matrix. 48 @@ -250,17 +250,17 @@ so we can sum overall keywords and all positions per sample. 51 00:02:33,450 --> 00:02:35,010 -现在我们快到了。 +现在我们就快要完成了。 Now we're almost there. 52 00:02:35,010 --> 00:02:38,850 -我们只需要将损失与每个样本的权重结合起来。 +我们只需要将每个样本的损失连同权重结合起来。 We only need to combine the loss with the weight per sample. 53 00:02:38,850 --> 00:02:41,790 -我们用元素明智的乘法来做到这一点 +我们通过元素积运算来做到这一点 We do this with element wise multiplication 54 @@ -270,32 +270,32 @@ and then average overall samples in the batch. 55 00:02:45,233 --> 00:02:46,066 -到底, +最后, In the end, 56 00:02:46,066 --> 00:02:49,110 -我们对整批只有一个损失值 +整批数据只有一个损失值 we have exactly one loss value for the whole batch 57 00:02:49,110 --> 00:02:51,330 -这是整个必要的逻辑 +这是创建自定义加权损失 and this is the whole necessary logic 58 00:02:51,330 --> 00:02:53,223 -创建自定义加权损失。 +整个必要的逻辑。 to create a custom weighted loss. 59 00:02:56,250 --> 00:02:59,010 -让我们看看如何利用自定义损失 +让我们看看如何结合 Accelerate 和 Trainer 一起 Let's see how we can make use of that custom loss 60 00:02:59,010 --> 00:03:00,753 -与 Accelerate 和 Trainer 一起。 +利用自定义损失。 with Accelerate and the Trainer. 61 @@ -305,7 +305,7 @@ In Accelerate, we just pass the input_ids 62 00:03:04,656 --> 00:03:05,730 -到模型以获得 logits +到模型以获得对数值 to the model to get the logits 63 @@ -340,17 +340,17 @@ We just need to make sure that we return 69 00:03:20,970 --> 00:03:24,450 -损失和模型以相同的格式输出。 +损失和模型输出的格式相同。 the loss and the model outputs in the same format. 70 00:03:24,450 --> 00:03:27,570 -这样,你就可以集成自己的出色损失函数 +这样,你就可以结合 Trainer 和 Accelerate With that, you can integrate your own awesome loss function 71 00:03:27,570 --> 00:03:29,763 -与培训师和加速。 +集成自己的出色损失函数。 with both the Trainer and Accelerate. 72 diff --git a/subtitles/zh-CN/65_data-processing-for-question-answering.srt b/subtitles/zh-CN/65_data-processing-for-question-answering.srt index d57dd83ef..520ef375d 100644 --- a/subtitles/zh-CN/65_data-processing-for-question-answering.srt +++ b/subtitles/zh-CN/65_data-processing-for-question-answering.srt @@ -1,21 +1,21 @@ 1 00:00:05,580 --> 00:00:07,177 -- 让我们研究如何预处理数据集 +- 让我们研究如何针对问答场景 - Let's study how to preprocess a dataset 2 00:00:07,177 --> 00:00:08,643 -用于答疑。 +预处理数据集。 for question answering. 3 00:00:10,200 --> 00:00:11,640 -回答问题是一项任务 +问答场景是在一些上下文环境下 Question answering is a task 4 00:00:11,640 --> 00:00:14,343 -在某些情况下找到问题的答案。 +针对某个问题寻找其答案的应用场景。 of finding answers to a question in some context. 5 @@ -30,17 +30,17 @@ in which we remove columns we won't use 7 00:00:19,860 --> 00:00:21,660 -并提取我们需要的信息 +并针对标签提取 and just extract the information we will need 8 00:00:21,660 --> 00:00:22,950 -对于标签, +我们需要的信息, for the labels, 9 00:00:22,950 --> 00:00:26,370 -上下文中答案的开始和结束。 +即上下文中答案的开始和结束。 the start and the end of the answer in the context. 10 @@ -50,12 +50,12 @@ If you have your own dataset for question answering, 11 00:00:28,690 --> 00:00:31,680 -只要确保你清理数据以达到同一点, +只要确保你清理数据以达到同一效果, just make sure you clean your data to get to the same point, 12 00:00:31,680 --> 00:00:33,900 -一栏包含问题, +其中一列包含问题, with one column containing the questions, 13 @@ -65,12 +65,12 @@ one column containing the context, 14 00:00:35,940 --> 00:00:38,610 -一列为开始和结束字符的索引 +一列为上下文中的答案的 one column for the index of the start and end character 15 00:00:38,610 --> 00:00:40,473 -上下文中的答案。 +起始和终止字符的索引。 of the answer in the context. 16 @@ -90,12 +90,12 @@ look at one of the sequence to sequence videos linked below. 19 00:00:51,600 --> 00:00:53,430 -现在,如果我们看一下令牌 +现在,如果我们看一下 Now, if we have a look at the tokens 20 00:00:53,430 --> 00:00:54,750 -我们将喂养我们的模型, +我们将输入给我们的模型的词元, we will feed our model, 21 @@ -105,12 +105,12 @@ we'll see the answer lies somewhere inside the context. 22 00:00:58,320 --> 00:01:01,080 -对于很长的上下文,该答案可能会被截断 +对于很长的上下文,该答案可能 For very long context, that answer may get truncated 23 00:01:01,080 --> 00:01:02,580 -由分词器。 +会被分词器截断。 by the tokenizer. 24 @@ -120,12 +120,12 @@ In this case, we won't have any proper labels for our model, 25 00:01:05,970 --> 00:01:07,680 -所以我们应该保留截断的部分 +所以我们应该 so we should keep the truncated part 26 00:01:07,680 --> 00:01:10,203 -作为一个单独的功能而不是丢弃它。 +保留截断的部分作为一个单独的功能而不是丢弃它。 as a separate feature instead of discarding it. 27 @@ -135,7 +135,7 @@ The only thing we need to be careful with 28 00:01:12,990 --> 00:01:15,660 -是允许不同的块之间有一些重叠 +是允许单独的块之间有一些重叠 is to allow some overlap between separate chunks 29 @@ -145,12 +145,12 @@ so that the answer is not truncated 30 00:01:17,670 --> 00:01:19,920 -以及包含答案的特征 +使得包含答案的特征 and that the feature containing the answer 31 00:01:19,920 --> 00:01:22,623 -获得足够的上下文以能够预测它。 +获得足够的上下文来预测它。 gets sufficient context to be able to predict it. 32 @@ -160,27 +160,27 @@ Here is how it can be done by the tokenizer. 33 00:01:26,040 --> 00:01:29,370 -我们将问题、上下文传递给它,设置截断 +我们将问题、上下文传递给它, We pass it the question, context, set a truncation 34 00:01:29,370 --> 00:01:33,240 -仅针对上下文,填充到最大长度。 +仅针对上下文设置截断,并且填充到最大长度。 for the context only, and the padding to the maximum length. 35 00:01:33,240 --> 00:01:35,340 -stride 参数是我们设置数字的地方 +stride 参数是我们针对重叠的词元 The stride argument is where we set the number 36 00:01:35,340 --> 00:01:36,900 -重叠的标记, +设置数字的地方, of overlapping tokens, 37 00:01:36,900 --> 00:01:39,600 -并且返回的溢出标记等于 true +并且return_overflowing_tokens 等于 true and the return overflowing tokens equals true 38 @@ -190,32 +190,32 @@ means we don't want to discard the truncated part. 39 00:01:42,630 --> 00:01:45,210 -最后,我们还返回偏移映射 +最后,我们还返回 return_offsets_mapping Lastly, we also return the offset mappings 40 00:01:45,210 --> 00:01:47,220 -能够找到相应的令牌 +从而能够找到和答案的起始与结束相关的 to be able to find the tokens corresponding 41 00:01:47,220 --> 00:01:48,693 -到答案的开始和结束。 +相应的词元。 to the answer start and end. 42 00:01:49,860 --> 00:01:52,290 -我们想要这些标记,因为它们将成为标签 +我们想要保留这些词元,因为它们将成为标签 We want those tokens because they will be the labels 43 00:01:52,290 --> 00:01:53,970 -我们通过我们的模型。 +我们将其传递给我们的模型。 we pass through our model. 44 00:01:53,970 --> 00:01:56,870 -在单热编码版本中,这是它们的样子。 +在独热编码版本中,这是它们的样子。 In a one-hot encoded version, here is what they look like. 45 @@ -225,17 +225,17 @@ If the context we have does not contain the answer, 46 00:02:00,480 --> 00:02:03,799 -我们将这两个标签设置为 CLS 令牌的索引。 +我们将这两个标签设置为 CLS 词元的索引。 we set the two labels to the index of the CLS token. 47 00:02:03,799 --> 00:02:05,700 -如果上下文,我们也会这样做 +如果上下文仅部分包含答案, We also do this if the context 48 00:02:05,700 --> 00:02:07,713 -仅部分包含答案。 +我们也会这样做。 only partially contains the answer. 49 @@ -255,22 +255,22 @@ we can determine the beginning and the end of the context. 52 00:02:17,220 --> 00:02:19,800 -然后,我们知道是否必须返回到 CLS 位置 +然后,我们就知道是否需要 Then, we know if we have to return to the CLS position 53 00:02:19,800 --> 00:02:22,290 -对于两个标签或者我们确定位置 +根据两个标签返回 CLS 位置 for the two labels or we determine the position 54 00:02:22,290 --> 00:02:25,050 -答案的第一个和最后一个标记。 +或者我们确定答案的第一个和最后一个词元的位置。 of the first and last tokens of the answer. 55 00:02:25,050 --> 00:02:27,800 -我们可以在前面的示例中检查它是否正常工作。 +我们可以在前面的示例中检查它是否正常发挥作用。 We can check it works properly on our previous example. 56 @@ -285,12 +285,12 @@ which we can apply to our datasets with the map method. 58 00:02:35,310 --> 00:02:37,920 -由于我们在标记化过程中应用了填充, +由于我们在词元化过程中应用了填充操作, Since we applied padding during the tokenization, 59 00:02:37,920 --> 00:02:40,680 -然后我们可以直接使用它作为训练器 +然后我们可以直接作为训练器使用 we can then use this directly as the trainer 60 From fe87b3977b5a5b604eb328305ee3ad821c184b02 Mon Sep 17 00:00:00 2001 From: lewtun Date: Thu, 5 Jan 2023 15:41:55 +1100 Subject: [PATCH 45/51] Bump release (#463) --- README.md | 2 +- chapters/de/chapter1/6.mdx | 2 +- chapters/en/chapter1/1.mdx | 19 +- chapters/en/chapter1/4.mdx | 2 + chapters/en/chapter1/6.mdx | 2 +- chapters/en/chapter5/5.mdx | 2 +- chapters/en/chapter6/3.mdx | 2 +- chapters/en/chapter7/3.mdx | 1 + chapters/en/chapter7/6.mdx | 2 +- chapters/es/chapter1/6.mdx | 2 +- chapters/es/chapter5/5.mdx | 2 +- chapters/fr/chapter1/6.mdx | 2 +- chapters/fr/chapter5/5.mdx | 2 +- chapters/fr/chapter7/3.mdx | 1 + chapters/fr/chapter7/6.mdx | 2 +- chapters/hi/chapter1/6.mdx | 2 +- chapters/it/chapter1/6.mdx | 2 +- chapters/it/chapter5/5.mdx | 2 +- chapters/ja/chapter1/6.mdx | 2 +- chapters/ja/chapter7/3.mdx | 1 + chapters/ko/_toctree.yml | 20 + chapters/ko/chapter1/6.mdx | 2 +- chapters/ko/chapter2/2.mdx | 2 +- chapters/ko/chapter2/3.mdx | 234 +++++ chapters/ko/chapter2/4.mdx | 241 +++++ chapters/ko/chapter2/5.mdx | 338 +++++++ chapters/ko/chapter2/6.mdx | 164 ++++ chapters/ko/chapter2/7.mdx | 18 + chapters/ko/chapter2/8.mdx | 310 ++++++ chapters/ko/chapter5/1.mdx | 22 + chapters/ko/chapter5/2.mdx | 167 ++++ chapters/pt/chapter1/6.mdx | 2 +- chapters/pt/chapter5/5.mdx | 2 +- chapters/ru/chapter1/6.mdx | 2 +- chapters/th/chapter1/6.mdx | 2 +- chapters/tr/chapter1/6.mdx | 2 +- chapters/vi/chapter1/1.mdx | 18 +- chapters/vi/chapter1/6.mdx | 2 +- chapters/vi/chapter5/5.mdx | 2 +- chapters/vi/chapter7/3.mdx | 1 + chapters/zh-CN/chapter1/6.mdx | 2 +- chapters/zh-CN/chapter5/5.mdx | 2 +- chapters/zh-CN/chapter7/3.mdx | 1 + chapters/zh-CN/chapter8/3.mdx | 6 +- subtitles/README.md | 30 +- .../00_welcome-to-the-hugging-face-course.srt | 2 +- .../00_welcome-to-the-hugging-face-course.srt | 16 +- subtitles/fr/03_what-is-transfer-learning.srt | 2 +- subtitles/fr/68_data-collators-a-tour.srt | 2 + ...ptions.txt => titles-and-descriptions.txt} | 903 +++++++++--------- .../00_welcome-to-the-hugging-face-course.srt | 162 ++-- subtitles/zh-CN/01_the-pipeline-function.srt | 94 +- ...2_the-carbon-footprint-of-transformers.srt | 190 ++-- ...g-step-in-question-answering-(pytorch).srt | 96 +- ...tep-in-question-answering-(tensorflow).srt | 102 +- utils/convert_bilingual_monolingual.py | 74 +- utils/generate_notebooks.py | 2 +- utils/generate_subtitles.py | 14 +- utils/validate_translation.py | 7 +- 59 files changed, 2414 insertions(+), 898 deletions(-) create mode 100644 chapters/ko/chapter2/3.mdx create mode 100644 chapters/ko/chapter2/4.mdx create mode 100644 chapters/ko/chapter2/5.mdx create mode 100644 chapters/ko/chapter2/6.mdx create mode 100644 chapters/ko/chapter2/7.mdx create mode 100644 chapters/ko/chapter2/8.mdx create mode 100644 chapters/ko/chapter5/1.mdx create mode 100644 chapters/ko/chapter5/2.mdx rename subtitles/fr/{Titles and descriptions.txt => titles-and-descriptions.txt} (76%) diff --git a/README.md b/README.md index 8c820e0e8..7d253aa5c 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ Once an issue is created, post a comment to indicate which chapters you'd like t **🗣 Join our Discord** -Since it can be difficult to discuss translation details quickly over GitHub issues, we have created dedicated channels for each language on our Discord server. If you'd like to jon, just following the instructions at this channel 👉: [https://discord.gg/JfAtkvEtRb](https://discord.gg/JfAtkvEtRb) +Since it can be difficult to discuss translation details quickly over GitHub issues, we have created dedicated channels for each language on our Discord server. If you'd like to join, follow the instructions at this channel 👉: [https://discord.gg/JfAtkvEtRb](https://discord.gg/JfAtkvEtRb) **🍴 Fork the repository** diff --git a/chapters/de/chapter1/6.mdx b/chapters/de/chapter1/6.mdx index c05ac8eb2..3e0e5768a 100644 --- a/chapters/de/chapter1/6.mdx +++ b/chapters/de/chapter1/6.mdx @@ -16,6 +16,6 @@ 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) -- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) +- [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) diff --git a/chapters/en/chapter1/1.mdx b/chapters/en/chapter1/1.mdx index 5136d0fe4..30c992371 100644 --- a/chapters/en/chapter1/1.mdx +++ b/chapters/en/chapter1/1.mdx @@ -37,23 +37,23 @@ After you've completed this course, we recommend checking out DeepLearning.AI's About the authors: -**Abubakar Abid** completed his PhD at Stanford in applied machine learning. During his PhD, he founded [Gradio](https://github.com/gradio-app/gradio), an open-source Python library that has been used to build over 600,000 machine learning demos. Gradio was acquired by Hugging Face, which is where Abubakar now serves as a machine learning team lead. +[**Abubakar Abid**](https://huggingface.co/abidlabs) completed his PhD at Stanford in applied machine learning. During his PhD, he founded [Gradio](https://github.com/gradio-app/gradio), an open-source Python library that has been used to build over 600,000 machine learning demos. Gradio was acquired by Hugging Face, which is where Abubakar now serves as a machine learning team lead. -**Matthew Carrigan** is a Machine Learning Engineer at Hugging Face. He lives in Dublin, Ireland and previously worked as an ML engineer at Parse.ly and before that as a post-doctoral researcher at Trinity College Dublin. He does not believe we're going to get to AGI by scaling existing architectures, but has high hopes for robot immortality regardless. +[**Matthew Carrigan**](https://huggingface.co/Rocketknight1) is a Machine Learning Engineer at Hugging Face. He lives in Dublin, Ireland and previously worked as an ML engineer at Parse.ly and before that as a post-doctoral researcher at Trinity College Dublin. He does not believe we're going to get to AGI by scaling existing architectures, but has high hopes for robot immortality regardless. -**Lysandre Debut** is a Machine Learning Engineer at Hugging Face and has been working on the 🤗 Transformers library since the very early development stages. His aim is to make NLP accessible for everyone by developing tools with a very simple API. +[**Lysandre Debut**](https://huggingface.co/lysandre) is a Machine Learning Engineer at Hugging Face and has been working on the 🤗 Transformers library since the very early development stages. His aim is to make NLP accessible for everyone by developing tools with a very simple API. -**Sylvain Gugger** is a Research Engineer at Hugging Face and one of the core maintainers of the 🤗 Transformers library. Previously he was a Research Scientist at fast.ai, and he co-wrote _[Deep Learning for Coders with fastai and PyTorch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)_ with Jeremy Howard. The main focus of his research is on making deep learning more accessible, by designing and improving techniques that allow models to train fast on limited resources. +[**Sylvain Gugger**](https://huggingface.co/sgugger) is a Research Engineer at Hugging Face and one of the core maintainers of the 🤗 Transformers library. Previously he was a Research Scientist at fast.ai, and he co-wrote _[Deep Learning for Coders with fastai and PyTorch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)_ with Jeremy Howard. The main focus of his research is on making deep learning more accessible, by designing and improving techniques that allow models to train fast on limited resources. -**Dawood Khan** is a Machine Learning Engineer at Hugging Face. He's from NYC and graduated from New York University studying Computer Science. After working as an iOS Engineer for a few years, Dawood quit to start Gradio with his fellow co-founders. Gradio was eventually acquired by Hugging Face. +[**Dawood Khan**](https://huggingface.co/dawoodkhan82) is a Machine Learning Engineer at Hugging Face. He's from NYC and graduated from New York University studying Computer Science. After working as an iOS Engineer for a few years, Dawood quit to start Gradio with his fellow co-founders. Gradio was eventually acquired by Hugging Face. -**Merve Noyan** is a developer advocate at Hugging Face, working on developing tools and building content around them to democratize machine learning for everyone. +[**Merve Noyan**](https://huggingface.co/merve) is a developer advocate at Hugging Face, working on developing tools and building content around them to democratize machine learning for everyone. -**Lucile Saulnier** is a machine learning engineer at Hugging Face, developing and supporting the use of open source tools. She is also actively involved in many research projects in the field of Natural Language Processing such as collaborative training and BigScience. +[**Lucile Saulnier**](https://huggingface.co/SaulLu) is a machine learning engineer at Hugging Face, developing and supporting the use of open source tools. She is also actively involved in many research projects in the field of Natural Language Processing such as collaborative training and BigScience. -**Lewis Tunstall** is a machine learning engineer at Hugging Face, focused on developing open-source tools and making them accessible to the wider community. He is also a co-author of the O’Reilly book [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). +[**Lewis Tunstall**](https://huggingface.co/lewtun) is a machine learning engineer at Hugging Face, focused on developing open-source tools and making them accessible to the wider community. He is also a co-author of the O’Reilly book [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). -**Leandro von Werra** is a machine learning engineer in the open-source team at Hugging Face and also a co-author of the O’Reilly book [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). He has several years of industry experience bringing NLP projects to production by working across the whole machine learning stack.. +[**Leandro von Werra**](https://huggingface.co/lvwerra) is a machine learning engineer in the open-source team at Hugging Face and also a co-author of the O’Reilly book [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). He has several years of industry experience bringing NLP projects to production by working across the whole machine learning stack.. ## FAQ[[faq]] @@ -100,6 +100,7 @@ Of course! The course is released under the permissive [Apache 2 license](https: } ``` +## Let's Go Are you ready to roll? In this chapter, you will learn: * How to use the `pipeline()` function to solve NLP tasks such as text generation and classification diff --git a/chapters/en/chapter1/4.mdx b/chapters/en/chapter1/4.mdx index 7097771f9..80f692852 100644 --- a/chapters/en/chapter1/4.mdx +++ b/chapters/en/chapter1/4.mdx @@ -81,6 +81,8 @@ Imagine if each time a research team, a student organization, or a company wante This is why sharing language models is paramount: sharing the trained weights and building on top of already trained weights reduces the overall compute cost and carbon footprint of the community. +By the way, you can evaluate the carbon footprint of your models' training through several tools. For example [ML CO2 Impact](https://mlco2.github.io/impact/) or [Code Carbon]( https://codecarbon.io/) which is integrated in 🤗 Transformers. To learn more about this, you can read this [blog post](https://huggingface.co/blog/carbon-emissions-on-the-hub) which will show you how to generate an `emissions.csv` file with an estimate of the footprint of your training, as well as the [documentation](https://huggingface.co/docs/hub/model-cards-co2) of 🤗 Transformers addressing this topic. + ## Transfer Learning[[transfer-learning]] diff --git a/chapters/en/chapter1/6.mdx b/chapters/en/chapter1/6.mdx index f6f4ec6a6..396d79bd3 100644 --- a/chapters/en/chapter1/6.mdx +++ b/chapters/en/chapter1/6.mdx @@ -16,6 +16,6 @@ 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) -- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) +- [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) diff --git a/chapters/en/chapter5/5.mdx b/chapters/en/chapter5/5.mdx index 08ec45a0b..10e43fe5e 100644 --- a/chapters/en/chapter5/5.mdx +++ b/chapters/en/chapter5/5.mdx @@ -185,7 +185,7 @@ Now when we call `fetch_issues()` it will download all the issues in batches to fetch_issues() ``` -Once the issues are downloaded we can load them locally using our newfound skills from [section 2](/course/chaper5/2): +Once the issues are downloaded we can load them locally using our newfound skills from [section 2](/course/chapter5/2): ```py issues_dataset = load_dataset("json", data_files="datasets-issues.jsonl", split="train") diff --git a/chapters/en/chapter6/3.mdx b/chapters/en/chapter6/3.mdx index 3739d4eb9..62d143dcc 100644 --- a/chapters/en/chapter6/3.mdx +++ b/chapters/en/chapter6/3.mdx @@ -28,7 +28,7 @@ In this section we will take a closer look at the capabilities of the tokenizers In the following discussion, we will often make the distinction between "slow" and "fast" tokenizers. Slow tokenizers are those written in Python inside the 🤗 Transformers library, while the fast versions are the ones provided by 🤗 Tokenizers, which are written in Rust. If you remember the table from [Chapter 5](/course/chapter5/3) that reported how long it took a fast and a slow tokenizer to tokenize the Drug Review Dataset, you should have an idea of why we call them fast and slow: - | Fast tokenizer | Slow tokenizer +| | Fast tokenizer | Slow tokenizer :--------------:|:--------------:|:-------------: `batched=True` | 10.8s | 4min41s `batched=False` | 59.2s | 5min3s diff --git a/chapters/en/chapter7/3.mdx b/chapters/en/chapter7/3.mdx index a1387158d..a31bb432c 100644 --- a/chapters/en/chapter7/3.mdx +++ b/chapters/en/chapter7/3.mdx @@ -723,6 +723,7 @@ trainer = Trainer( train_dataset=downsampled_dataset["train"], eval_dataset=downsampled_dataset["test"], data_collator=data_collator, + tokenizer=tokenizer, ) ``` diff --git a/chapters/en/chapter7/6.mdx b/chapters/en/chapter7/6.mdx index 2a42aa6b4..7a498a863 100644 --- a/chapters/en/chapter7/6.mdx +++ b/chapters/en/chapter7/6.mdx @@ -36,7 +36,7 @@ This is actually showcasing the model that was trained and uploaded to the Hub u ## Gathering the data[[gathering-the-data]] -Python code is abundantly available from code repositories such as GitHub, which we can use to create a dataset by scraping for every Python repository. This was the approach taken in the [Transformers textbook](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/) to pretrain a large GPT-2 model. Using a GitHub dump of about 180 GB containing roughly 20 million Python files called `codeparrot`, the authors built a dataset that they then shared on the [Hugging Face Hub](https://huggingface.co/datasets/transformersbook/codeparrot). +Python code is abundantly available from code repositories such as GitHub, which we can use to create a dataset by scraping for every Python repository. This was the approach taken in the [Transformers textbook](https://learning.oreilly.com/library/view/natural-language-processing/9781098136789/) to pretrain a large GPT-2 model. Using a GitHub dump of about 180 GB containing roughly 20 million Python files called `codeparrot`, the authors built a dataset that they then shared on the [Hugging Face Hub](https://huggingface.co/datasets/transformersbook/codeparrot). However, training on the full corpus is time- and compute-consuming, and we only need the subset of the dataset concerned with the Python data science stack. So, let's start by filtering the `codeparrot` dataset for all files that include any of the libraries in this stack. Because of the dataset's size, we want to avoid downloading it; instead, we'll use the streaming feature to filter it on the fly. To help us filter the code samples using the libraries we mentioned earlier, we'll use the following function: diff --git a/chapters/es/chapter1/6.mdx b/chapters/es/chapter1/6.mdx index b4462cd8e..3cc3f9afb 100644 --- a/chapters/es/chapter1/6.mdx +++ b/chapters/es/chapter1/6.mdx @@ -16,6 +16,6 @@ Estos modelos son más adecuados para tareas que implican la generación de text Los miembros de esta familia de modelos incluyen: - [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) -- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) +- [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) diff --git a/chapters/es/chapter5/5.mdx b/chapters/es/chapter5/5.mdx index 3d3cd21c2..113138fa5 100644 --- a/chapters/es/chapter5/5.mdx +++ b/chapters/es/chapter5/5.mdx @@ -185,7 +185,7 @@ Cuando ejecutemos `fetch_issues()`, se descargarán todos los issues en lotes pa fetch_issues() ``` -Una vez los issues estén descargados, los podemos cargar localmente usando las habilidades aprendidas en la [sección 2](/course/chaper5/2): +Una vez los issues estén descargados, los podemos cargar localmente usando las habilidades aprendidas en la [sección 2](/course/chapter5/2): ```py issues_dataset = load_dataset("json", data_files="datasets-issues.jsonl", split="train") diff --git a/chapters/fr/chapter1/6.mdx b/chapters/fr/chapter1/6.mdx index b2fbf236c..1b30883bf 100644 --- a/chapters/fr/chapter1/6.mdx +++ b/chapters/fr/chapter1/6.mdx @@ -16,6 +16,6 @@ Ces modèles sont vraiment adaptés aux tâches qui impliquent la génération d Les modèles qui représentent le mieux la famille des modèles décodeurs sont : - [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) -- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) +- [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) diff --git a/chapters/fr/chapter5/5.mdx b/chapters/fr/chapter5/5.mdx index bd72c6ff8..af48c82b3 100644 --- a/chapters/fr/chapter5/5.mdx +++ b/chapters/fr/chapter5/5.mdx @@ -188,7 +188,7 @@ Désormais, lorsque nous appellerons `fetch_issues()`, tous les problèmes seron fetch_issues() ``` -Une fois les problèmes téléchargés, nous pouvons les charger localement en utilisant nos nouvelles compétences de [section 2](/course/fr/chaper5/2) : +Une fois les problèmes téléchargés, nous pouvons les charger localement en utilisant nos nouvelles compétences de [section 2](/course/fr/chapter5/2) : ```py issues_dataset = load_dataset("json", data_files="datasets-issues.jsonl", split="train") diff --git a/chapters/fr/chapter7/3.mdx b/chapters/fr/chapter7/3.mdx index 675965901..11733c1b3 100644 --- a/chapters/fr/chapter7/3.mdx +++ b/chapters/fr/chapter7/3.mdx @@ -728,6 +728,7 @@ trainer = Trainer( train_dataset=downsampled_dataset["train"], eval_dataset=downsampled_dataset["test"], data_collator=data_collator, + tokenizer=tokenizer, ) ``` diff --git a/chapters/fr/chapter7/6.mdx b/chapters/fr/chapter7/6.mdx index e8dd9a3cd..91c90a96c 100644 --- a/chapters/fr/chapter7/6.mdx +++ b/chapters/fr/chapter7/6.mdx @@ -41,7 +41,7 @@ Il s'agit d'une présentation du modèle qui a été entraîné à l'aide du cod ## Collecte des données -On peut trouver du code Python en abondance dans les dépôts de code tels que GitHub, que nous pouvons utiliser pour créer un jeu de données en récupérant chaque dépôt Python. C'est l'approche adoptée dans le [livre *Natural Language Processing with Transformers*](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/) pour pré-entraîner un grand GPT-2. En utilisant un dépôt GitHub d'environ 180 Go contenant approximativement 20 millions de fichiers Python, les auteurs du livre ont construit un jeu de données appelé `codeparrot` qu'ils ont ensuite partagé sur le [*Hub*](https://huggingface.co/datasets/transformersbook/codeparrot). +On peut trouver du code Python en abondance dans les dépôts de code tels que GitHub, que nous pouvons utiliser pour créer un jeu de données en récupérant chaque dépôt Python. C'est l'approche adoptée dans le [livre *Natural Language Processing with Transformers*](https://learning.oreilly.com/library/view/natural-language-processing/9781098136789/) pour pré-entraîner un grand GPT-2. En utilisant un dépôt GitHub d'environ 180 Go contenant approximativement 20 millions de fichiers Python, les auteurs du livre ont construit un jeu de données appelé `codeparrot` qu'ils ont ensuite partagé sur le [*Hub*](https://huggingface.co/datasets/transformersbook/codeparrot). Cependant, entraîner sur l'ensemble du corpus prend beaucoup de temps et demande beaucoup de ressources de calculs. Dans notre cas, nous n'avons besoin que du sous-ensemble du jeu de données qui est relatif aux codes portant sur la science des données. Commençons donc par filtrer le jeu de données `codeparrot` en ne gardant que les fichiers incluant l'une des bibliothèques de science des données énumérées précédemment. En raison de la taille du jeu de données, nous voulons éviter de le télécharger. Nous utiliserons donc la fonctionnalité de *streaming* de 🤗 *Datasets* afin de le filtrer à la volée. Pour nous aider à filtrer les échantillons de code utilisant les bibliothèques que nous avons mentionnées précédemment, nous utilisons la fonction suivante : diff --git a/chapters/hi/chapter1/6.mdx b/chapters/hi/chapter1/6.mdx index cf0ae79ee..fe5a88ae7 100644 --- a/chapters/hi/chapter1/6.mdx +++ b/chapters/hi/chapter1/6.mdx @@ -16,6 +16,6 @@ मॉडल के इस परिवार के प्रतिनिधियों में शामिल हैं: - [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) -- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) +- [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) diff --git a/chapters/it/chapter1/6.mdx b/chapters/it/chapter1/6.mdx index f9bf8a64a..170932810 100644 --- a/chapters/it/chapter1/6.mdx +++ b/chapters/it/chapter1/6.mdx @@ -16,6 +16,6 @@ Questi modelli sono particolarmente adatti a compiti di generazione testuale. Alcuni rappresentanti di questa famiglia includono: - [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) -- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) +- [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) diff --git a/chapters/it/chapter5/5.mdx b/chapters/it/chapter5/5.mdx index d813fd152..3a9c0f19f 100644 --- a/chapters/it/chapter5/5.mdx +++ b/chapters/it/chapter5/5.mdx @@ -185,7 +185,7 @@ Ora quando eseguiremo `fetch_issues()`, scaricherà tutti gli issue in batch per fetch_issues() ``` -Una volta che gli issue sono stati scaricati, possiamo caricarli in locale usando le nuove abilità imparate nella [sezione 2](/course/chaper5/2): +Una volta che gli issue sono stati scaricati, possiamo caricarli in locale usando le nuove abilità imparate nella [sezione 2](/course/chapter5/2): ```py issues_dataset = load_dataset("json", data_files="datasets-issues.jsonl", split="train") diff --git a/chapters/ja/chapter1/6.mdx b/chapters/ja/chapter1/6.mdx index 6044ae35e..9ea29579c 100644 --- a/chapters/ja/chapter1/6.mdx +++ b/chapters/ja/chapter1/6.mdx @@ -17,6 +17,6 @@ デコーダーモデルでは以下のものが代表的です: - [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) -- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) +- [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) diff --git a/chapters/ja/chapter7/3.mdx b/chapters/ja/chapter7/3.mdx index b550203cc..7090ced4d 100644 --- a/chapters/ja/chapter7/3.mdx +++ b/chapters/ja/chapter7/3.mdx @@ -738,6 +738,7 @@ trainer = Trainer( train_dataset=downsampled_dataset["train"], eval_dataset=downsampled_dataset["test"], data_collator=data_collator, + tokenizer=tokenizer, ) ``` diff --git a/chapters/ko/_toctree.yml b/chapters/ko/_toctree.yml index d1d952c7d..7a523be1c 100644 --- a/chapters/ko/_toctree.yml +++ b/chapters/ko/_toctree.yml @@ -33,6 +33,26 @@ title: 단원 소개 - local: chapter2/2 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: 5. 🤗 Datasets 라이브러리 + sections: + - local: chapter5/1 + title: 단원 소개 + - local: chapter5/2 + title: 필요한 데이터셋이 Hub에 없다면 어떻게 할까요? - title: 8. 도움을 요청하는 방법 sections: diff --git a/chapters/ko/chapter1/6.mdx b/chapters/ko/chapter1/6.mdx index 30482ca4a..d335b0348 100644 --- a/chapters/ko/chapter1/6.mdx +++ b/chapters/ko/chapter1/6.mdx @@ -16,6 +16,6 @@ 디코더 모델 계열의 대표 주자들은 다음과 같습니다: - [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) -- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) +- [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) diff --git a/chapters/ko/chapter2/2.mdx b/chapters/ko/chapter2/2.mdx index 718ea976d..3f246f672 100644 --- a/chapters/ko/chapter2/2.mdx +++ b/chapters/ko/chapter2/2.mdx @@ -344,7 +344,7 @@ model.config.id2label - 첫 번째 문장: NEGATIVE: 0.0402, POSITIVE: 0.9598 - 두 번째 문장: NEGATIVE: 0.9995, POSITIVE: 0.0005 -파이프라인 세 단게-토크나이저를 이용한 전처리, 모델에 입력 넣어주기, 후처리-를 성공적으로 재현했습니다! 이제 각 단계별로 좀 더 깊게 알아보는 시간을 가져봅시다. +파이프라인 세 단계-토크나이저를 이용한 전처리, 모델에 입력 넣어주기, 후처리-를 성공적으로 재현했습니다! 이제 각 단계별로 좀 더 깊게 알아보는 시간을 가져봅시다. diff --git a/chapters/ko/chapter2/3.mdx b/chapters/ko/chapter2/3.mdx new file mode 100644 index 000000000..9380926dc --- /dev/null +++ b/chapters/ko/chapter2/3.mdx @@ -0,0 +1,234 @@ + + +# Models + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + +{:else} + +{/if} + +{#if fw === 'pt'} + +이번 섹션에서는 모델을 생성하고 사용하는 방법에 대해 자세히 알아보겠습니다. 체크포인트에서 모델을 인스턴스화하는 데 유용한 `AutoModel` 클래스를 사용할 것입니다. + +이 `AutoModel` 클래스와 관련 클래스들은 실제로 라이브러리에 있는 다양한 모델들을 감싸고 있는 간단한 래퍼입니다. 이 래퍼는 체크포인트에 적합한 모델 아키텍처를 자동으로 추측하고, 이 아키텍처를 가진 모델을 인스턴스화하는 것도 똑똑하게 처리합니다. + +{:else} +이번 섹션에서는 모델을 생성하고 사용하는 방법에 대해 자세히 알아보겠습니다. 체크포인트에서 모델을 인스턴스화하는 데 유용한 `TFAutoModel` 클래스를 사용할 것입니다. + +이 `TFAutoModel` 클래스와 관련 클래스들은 실제로 라이브러리에 있는 다양한 모델들을 감싸고 있는 간단한 래퍼입니다. 이 래퍼는 체크포인트에 적합한 모델 아키텍처를 자동으로 추측하고, 이 아키텍처를 가진 모델을 인스턴스화하는 것도 똑똑하게 처리합니다. + +{/if} + +하지만, 만약 모델의 아키텍처를 직접 정의하고 싶다면, 해당 모델의 클래스를 사용할 수 있습니다. BERT 모델을 예로 들어보겠습니다. + +## Creating a Transformer (Transformer 생성하기) + +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) +``` + +```python out +BertConfig { + [...] + "hidden_size": 768, + "intermediate_size": 3072, + "max_position_embeddings": 512, + "num_attention_heads": 12, + "num_hidden_layers": 12, + [...] +} +``` + +아직 이 속성들이 무엇을 의미하는지는 모르겠지만, 몇몇은 익숙할 것입니다: `hidden_size` 속성은 `hidden_states` 벡터의 크기를 정의하고, `num_hidden_layers`는 Transformer 모델이 가지고 있는 레이어의 수를 정의합니다. + +### Different loading methods (다른 로딩 방법) + +무작위 값을 통한 기본 환경설정으로 모델 생성 + +{#if fw === 'pt'} +```py +from transformers import BertConfig, BertModel + +config = BertConfig() +model = BertModel(config) + +# Model is randomly initialized! +``` +{:else} +```py +from transformers import BertConfig, TFBertModel + +config = BertConfig() +model = TFBertModel(config) + +# Model is randomly initialized! +``` +{/if} + +이 모델은 무작위로 초기화되어 있기 때문에, 아직은 아무런 유용한 정보를 포함하고 있지 않습니다. 이 모델을 훈련시키기 위해서는, 먼저 훈련 데이터를 준비해야 합니다. 우리가 바닥부터 학습을 할 수 있지만, 이 과정은 [Chapter 1](/course/chapter1)에서 확인 했듯이, 많은 시간과 많은 데이터가 필요하며, 학습 환경에도 큰 영향을 미칩니다. + +이러한 불필요한 중복된 노력을 피하기 위해서, 이미 훈련된 모델을 공유하고 재사용할 수 있어야 합니다. + +훈련된 Transformer 모델을 불러오는 것은 매우 간단합니다 - `from_pretrained` 메소드를 사용하면 됩니다. + +{#if fw === 'pt'} +```py +from transformers import BertModel + +model = BertModel.from_pretrained("bert-base-cased") +``` + +이전에 봤듯이, `BertModel` 대신 `AutoModel` 클래스를 사용할 수도 있습니다. 이제부터는 체크포인트에 독립적인 코드를 생성하기 위해 `AutoModel`를 사용하겠습니다. 만약 코드가 한 체크포인트에서 잘 동작한다면, 다른 체크포인트에서도 잘 동작해야 합니다. 이는 체크포인트의 아키텍처가 다르더라도, 비슷한 작업(예를 들어, 감성 분석 작업)을 위해 훈련된 경우에도 적용됩니다. + +{:else} +```py +from transformers import TFBertModel + +model = TFBertModel.from_pretrained("bert-base-cased") +``` + +이전에 봤듯이, `TFBertModel` 대신 `TFAutoModel` 클래스를 사용할 수도 있습니다. 이제부터는 체크포인트에 독립적인 코드를 생성하기 위해 `TFAutoModel`를 사용하겠습니다. 만약 코드가 한 체크포인트에서 잘 동작한다면, 다른 체크포인트에서도 잘 동작해야 합니다. 이는 체크포인트의 아키텍처가 다르더라도, 비슷한 작업(예를 들어, 감성 분석 작업)을 위해 훈련된 경우에도 적용됩니다. + +{/if} + +이 코드 샘플에서는 `BertConfig`를 사용하지 않았고, 대신 `bert-base-cased` 식별자를 통해 사전 훈련된 모델을 불러왔습니다. 이는 BERT의 저자들이 직접 훈련시킨 체크포인트입니다. 자세한 내용은 [모델 카드](https://huggingface.co/bert-base-cased)에서 확인할 수 있습니다. + +이 모델은 체크포인트의 모든 가중치로 초기화되었습니다. 이 모델은 체크포인트에서 훈련된 작업에 대해 직접 추론에 사용할 수 있으며, 새로운 작업에 대해 미세 조정할 수도 있습니다. 사전 훈련된 가중치로부터 학습을 진행하면, 빈 상태에서 훈련을 시작하는 것보다 빠르게 좋은 결과를 얻을 수 있습니다. + +모델을 불러오는 또 다른 방법은 `from_pretrained()` 메서드를 사용하는 것입니다. 이 메서드는 체크포인트를 다운로드하고, 캐시에 저장합니다(이후 `from_pretrained()` 메서드를 호출할 때 다시 다운로드하지 않습니다). 캐시 폴더는 기본적으로 *~/.cache/huggingface/transformers*에 저장됩니다. 캐시 폴더를 사용자 정의하려면 `HF_HOME` 환경 변수를 설정하면 됩니다. + +모델을 불러오는 식별자는 BERT 아키텍처와 호환되는 경우 모델 허브의 모든 모델의 식별자가 될 수 있습니다. BERT 체크포인트의 전체 목록은 [여기](https://huggingface.co/models?filter=bert)에서 확인할 수 있습니다. + +### Saving methods (저장 방법) + +모델을 저장하는 방법은 불러오는 방법처럼 쉽습니다. `save_pretrained()` 메서드를 사용하면 됩니다. 이 메서드는 `from_pretrained()` 메서드와 유사합니다. + +```py +model.save_pretrained("directory_on_my_computer") +``` + +이는 2가지 파일을 저장하게 됩니다: + +{#if fw === 'pt'} +``` +ls directory_on_my_computer + +config.json pytorch_model.bin +``` +{:else} +``` +ls directory_on_my_computer + +config.json tf_model.h5 +``` +{/if} + +*config.json* 파일은 모델 아키텍처를 구축하는 데 필요한 속성을 알려줍니다. 이 파일에는 체크포인트가 어디에서 생성되었는지, 마지막으로 체크포인트를 저장할 때 사용한 🤗 Transformers 버전 등의 메타데이터도 포함되어 있습니다. + +{#if fw === 'pt'} +The *pytorch_model.bin* file is known as the *state dictionary*; it contains all your model's weights. The two files go hand in hand; the configuration is necessary to know your model's architecture, while the model weights are your model's parameters. + +{:else} +The *tf_model.h5* file is known as the *state dictionary*; it contains all your model's weights. The two files go hand in hand; the configuration is necessary to know your model's architecture, while the model weights are your model's parameters. + +{/if} + +## Using a Transformer model for inference (Transformer 모델을 추론에 사용하기) + +Now that you know how to load and save a model, let's try using it to make some predictions. Transformer models can only process numbers — numbers that the tokenizer generates. But before we discuss tokenizers, let's explore what inputs the model accepts. + +이제 모델을 불러오고 저장하는 방법을 알았으니, 모델을 사용하여 예측을 만들어 보겠습니다. Transformer 모델은 토크나이저가 생성하는 숫자만 처리할 수 있습니다. 그러나 토크나이저에 대해 논의하기 전에 모델이 받는 입력에 대해 알아보겠습니다. + +토크나이저는 입력을 적절한 프레임워크의 텐서로 변환할 수 있지만, 이해도를 높이기 위해 모델에 입력을 보내기 전 무엇을 반드시 해야 하는지 간단히 살펴보겠습니다. + +우리가 여러 시퀀스들이 있다고 가정해 봅시다: + +```py +sequences = ["Hello!", "Cool.", "Nice!"] +``` + +토크나이저는 이를 단어 인덱스로 변환합니다. 이를 *input IDs*라고 합니다. 각 시퀀스는 이제 숫자 목록입니다! 결과는 다음과 같습니다: + +```py no-format +encoded_sequences = [ + [101, 7592, 999, 102], + [101, 4658, 1012, 102], + [101, 3835, 999, 102], +] +``` + +이는 인코딩된 시퀀스의 목록입니다. 텐서는 정사각형 모양만 받을 수 있습니다 (행렬을 생각해 보세요). 이 "배열"은 이미 정사각형 모양이므로 텐서로 변환하는 것은 쉽습니다: + +This is a list of encoded sequences: a list of lists. Tensors only accept rectangular shapes (think matrices). This "array" is already of rectangular shape, so converting it to a tensor is easy: + +{#if fw === 'pt'} +```py +import torch + +model_inputs = torch.tensor(encoded_sequences) +``` +{:else} +```py +import tensorflow as tf + +model_inputs = tf.constant(encoded_sequences) +``` +{/if} + +### Using the tensors as inputs to the model (텐서를 모델의 입력으로 사용하기) + +모델의 텐서를 사용하는 것은 매우 간단합니다. 모델에 입력을 넣기만 하면 됩니다: + +```py +output = model(model_inputs) +``` + +모델이 다양한 어규먼트를 받는 중에, 입력은 input IDs 만 필요합니다. 나머지 어규먼트들은 언제 필요한지, 어떤 역할을 하는지는 나중에 설명하겠습니다. 먼저 토크나이저에 대해 좀 더 자세히 알아보겠습니다. \ No newline at end of file diff --git a/chapters/ko/chapter2/4.mdx b/chapters/ko/chapter2/4.mdx new file mode 100644 index 000000000..fed8fc9cf --- /dev/null +++ b/chapters/ko/chapter2/4.mdx @@ -0,0 +1,241 @@ + + +# 토크나이저[[tokenizers]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + + +토크나이저는 자연어처리 파이프라인의 핵심 요소 중 하나입니다. 토크나이저의 역할은 텍스트를 모델이 처리할 수 있는 데이터로 변환하는 것입니다. 모델은 숫자만 처리할 수 있기 때문에 토크나이저는 텍스트 입력을 수치형 데이터로 변환해야 합니다. 이 장에서는 토큰화 파이프라인에서 정확히 어떤 일이 일어나고 있는지 알아볼 것입니다. + +자연어처리 태스크에서 처리되는 데이터는 일반적으로 원시 텍스트입니다. 아래는 원시 텍스트의 예시입니다. + +``` +Jim Henson was a puppeteer +``` + +그러나 모델은 숫자만 처리할 수 있기 때문에 우리는 원시 텍스트를 숫자로 바꿀 방법을 찾아야 합니다. 그게 바로 토크나이저가 하는 일이며 이 문제를 해결할 수 있는 다양한 방법이 있습니다. 목표는 모델에 가장 적합하면서 간결한 표현을 찾는 것입니다. + +토큰화 알고리즘의 몇 가지 예시를 살펴보고 당신이 토큰화에 대해 가지는 궁금증에 대한 해답을 찾아봅시다. + +## 단어 기반 토큰화[[word-based]] + + + +가장 먼저 떠오르는 토큰화 유형은 _단어 기반_입니다. 몇 가지 규칙만으로도 설정 및 사용이 매우 쉽고 종종 괜찮은 결과를 출력합니다. 예를 들어, 아래 보이는 사진에서 목표는 원시 텍스트를 단어로 나누고 단어 각각에 대한 수치 표현을 찾는 것입니다. + +
+ An example of word-based tokenization. + +
+ +텍스트를 나누는 방법은 다양합니다. 예를 들면 파이썬의 `split()` 함수를 이용해 공백 기준으로 텍스트를 나눌 수 있습니다. + +```py +tokenized_text = "Jim Henson was a puppeteer".split() +print(tokenized_text) +``` + +```python out +['Jim', 'Henson', 'was', 'a', 'puppeteer'] +``` + +구두점을 위한 규칙이 추가된 토크나이저도 있습니다. 이러한 종류의 토크나이저를 사용하면 꽤 큰 "단어 사전"을 얻을 수 있는데, 이 때 단어 사전은 우리가 가진 말뭉치(corpus) 내 존재하는 고유한 토큰 수에 의해 정의됩니다. + +각 단어에는 0부터 시작해서 단어 사전 크기까지의 ID가 할당됩니다. 모델은 각 단어를 구별하기 위해 이 ID들을 사용합니다. + +단어 기반 토크나이저로 언어를 완벽하게 처리하고 싶다면 언어의 각 단어에 대한 식별자가 있어야 하며, 이는 엄청난 양의 토큰을 생성할 것입니다. 예를 들어, 영어에 500,000개가 넘는 단어가 있다고 한다면, 각 단어에 입력 ID를 매핑시키기 위해 많은 ID를 추적해야 합니다. 게다가 "dog"와 같은 단어는 "dogs"처럼 다르게 표현되어 모델이 처음에 "dog"와 "dogs"가 유사하다는 것을 알 방법이 없을 것입니다. 모델은 두 단어를 관련이 없다고 인식할 것입니다. "run"과 "running" 같은 다른 유사한 단어에도 적용되는 것으로, 모델은 처음에 두 단어를 유사한 것으로 보지 않습니다. + +마지막으로, 단어 사전에 없는 단어를 표현하기 위한 커스텀 토큰이 필요합니다. "unknown" 토큰으로 알려진 이 토큰은 "[UNK]"이나 ""로 표현됩니다. 토크나이저가 unknown 토큰을 많이 만드는 것은 단어에 적합한 표현을 찾지 못해 정보를 잃어가고 있는 것이기 때문에 일반적으로 좋지 않은 신호입니다. 단어 사전을 생성할 때의 목표는 토크나이저가 단어를 unknown 토큰으로 가능한 한 적게 토큰화하는 것입니다. + +Unknown 토큰 수를 줄이기 위한 한 가지 방법은 한 단계 더 들어가 _문자 기반_ 토크나이저를 사용하는 것입니다. + +## 문자 기반 토큰화[[character-based]] + + + +문자 기반 토크나이저는 텍스트를 단어가 아닌 문자 단위로 나눕니다. 이 방법은 두 가지 장점이 있습니다. + +- 단어 사전이 간결해진다. +- 모든 단어는 문자로 이루어졌기 때문에 out-of-vocabulary (unknown) 토큰의 수가 훨씬 적다. + +하지만 여기에서도 공백과 구두점에 대한 몇 가지 궁금증을 가질 수 있습니다. + +
+ An example of character-based tokenization. + +
+ +이 방법 또한 완벽하지 않습니다. 이제 표현은 단어가 아닌 문자에 기반을 두고 있기 때문에 누군가는 문자의 의미가 적다고 주장할 수 있습니다. 각각의 단어는 그 자체로 큰 의미가 있지만 문자는 그렇지 않습니다. 그러나 이 점은 다시 언어에 따라 달라집니다. 예를 들어, 중국어에서는 각 문자가 라틴어보다 더 많은 정보를 전달합니다. + +또 다른 고려 사항은 모델이 처리해야 하는 매우 많은 양의 토큰이 생기게 된다는 것입니다. 단어 기반 토크나이저에서 단어는 단 하나의 토큰인 반면, 문자로 변환하면 10개 이상의 토큰으로 쉽게 바뀔 수 있습니다. + +장점만을 최대한 활용하기 위해 두 가지 방법을 결합한 세 번째 기법인 *서브워드 토큰화*를 사용할 것입니다. + +## 서브워드 토큰화[[subword-tokenization]] + + + +서브워드 토큰화 알고리즘은 자주 사용되는 단어는 더 작은 서브워드로 나누면 안되지만, 희귀한 단어는 의미 있는 서브워드로 나눠야 한다는 규칙에 기반합니다. + +예를 들면 "annoyingly"는 흔하지 않은 단어로 여겨질 수 있고 "annoying"과 "ly"로 분해할 수 있을 것입니다. 둘다 독립적인 서브워드로 자주 등장할 가능성이 있는 반면에 "annoyingly"는 "annoying"과 "ly"의 합성으로만 의미가 유지됩니다. + +다음 예시는 서브워드 토큰화 알고리즘이 "Let's do tokenization!"이라는 문장을 어떻게 토큰화하는지 보여줍니다. + +
+ A subword tokenization algorithm. + +
+ +서브워드는 많은 양의 의미론적 정보를 제공합니다. 위의 예시에서 "tokenization"은 의미론적인 정보를 갖는 두 개의 토큰 "token"과 "ization"으로 분할되었고 긴 단어를 두 단어만으로 표현할 수 있어 공간 효율적입니다. 이 방식을 통해 크기가 작은 단어 사전으로도 많은 토큰을 표현할 수 있고 unknown 토큰도 거의 없습니다. + +서브워드 토큰화를 이용한 접근법은 터키어와 같은 교착어에서 유용합니다. 서브워드를 함께 묶어 길고 복잡한 단어를 형성할 수 있습니다. + +### 더 알아보기![[and-more]] + +다양한 토큰화 기법이 존재하는데 몇 가지만 나열해보겠습니다. + +- GPT-2에서 사용된 Byte-level BPE +- BERT에서 사용된 WordPiece +- 여러 다언어 모델에서 사용되는 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단원](/course/chapter3)에서 이야기할 것이고 `attention_mask` 키는 나중에 설명할 것입니다. 그 전에 먼저 `input_ids`가 어떻게 만들어지는지 알아봅시다. 이를 위해 토크나이저의 중간 메서드를 들여다봐야 합니다. + +## 인코딩[[encoding]] + + + +텍스트를 숫자로 바꾸는 것은 _인코딩_으로 알려져 있습니다. 인코딩은 토큰화, 토큰을 입력 ID로 바꾸는 두 단계에 걸쳐 수행됩니다. + +첫 번째 단계는 이미 봤던 것처럼 텍스트를 흔히 *토큰*이라고 부르는 단어(또는 단어의 일부, 구두점 기호 등)로 나누는 것입니다. There are multiple rules that can govern that process, which is why we need to instantiate the tokenizer using the name of the model, to make sure we use the same rules that were used when the model was pretrained. + +두 번째 단계는 생성된 토큰들을 숫자로 변환해 텐서로 만들고 모델로 넘겨주는 것입니다. 이 과정을 위해 토크나이저는 `from_pretrained()` 메서드로 인스턴스화할 때 다운로드한 *단어 사전*을 가지고 있습니다. 여기서도 모델이 사전학습될 때 사용한 것과 동일한 단어 사전을 사용해야 합니다. + +두 단계를 더 잘 이해하기 위해, 단계별로 알아봅시다. Note that we will use some methods that perform parts of the tokenization pipeline separately to show you the intermediate results of those steps, but in practice, you should call the tokenizer directly on your inputs (as shown in the section 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`로 나눠집니다. + +### 토큰에서 입력 ID까지[[from-tokens-to-input-ids]] + +토크나이저의 `convert_tokens_to_ids()` 메서드를 이용해 토큰을 입력 ID로 변환합니다. + +```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!")을 이용해 두 단계(토큰화와 입력 ID로의 변환)를 수행해보세요. 위에서 얻은 결과와 당신이 얻은 결과가 동일한지 확인해보세요! + + + +## 디코딩[[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` 메서드는 인덱스를 토큰으로 바꿀 뿐만 아니라, 읽기 좋은 문장을 만들기 위해 같은 단어의 일부인 토큰을 그룹화합니다. 이 과정은 새 텍스트(프롬프트에서 생성된 텍스트 또는 번역이나 요약과 같은 시퀀스 간 문제)를 예측하는 모델을 쓸 때 매우 유용합니다. + +이제 토크나이저가 수행하는 토큰화, ID로의 변환, ID를 다시 문자열로 변환하는 과정을 이해할 수 있어야 합니다. 그러나 이는 빙산의 일각입니다. 이어지는 섹션에서는 이 접근법의 한계를 알아보고, 이를 극복하는 방법을 알아볼 것입니다. diff --git a/chapters/ko/chapter2/5.mdx b/chapters/ko/chapter2/5.mdx new file mode 100644 index 000000000..faf950b4f --- /dev/null +++ b/chapters/ko/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 모델은 기본적으로 여러 개의 문장을 입력으로 받는데 하나의 시퀀스만을 모델에 넘겨줬기 때문에 발생하는 문제입니다. 여기서 우리는 토크나이저를 `시퀀스`에 적용했을 때 뒤에서 일어나고 있는 모든 일을 수행하려고 했습니다. 하지만 자세히 보면, 토크나이저가 입력 ID 리스트를 텐서로 바꿨을 뿐만 아니라 차원도 추가한 것을 알 수 있습니다. + +{#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} + +입력 ID와 결과 로짓을 출력한 결과입니다. + +{#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] +``` + +동일한 문장 2개로 만든 배치입니다! + + + +✏️ **직접 해보세요!** 이 `batched_ids` 리스트를 텐서로 변환하고 모델로 전달해보세요. 이전에 얻은 로짓과 동일한 결과를 얻는지 확인해보세요. (개수는 두 개여야 합니다!) + + + +배치는 여러 개의 문장을 모델로 넘겼을 때도 모델이 작동하게 합니다. 다중 시퀀스를 사용하는 것은 단일 시퀀스로 배치를 만드는 것만큼 간단합니다. 하지만 두 번째 문제가 있습니다. 두 개 이상의 문장을 배치로 만드려고 할 때, 그 문장들은 아마 다른 길이를 가지고 있을 것입니다. 이전에 텐서를 다뤄본 사람이라면, 텐서의 형태가 직사각형이어야 한다는 것을 알고 있습니다. 문장 길이가 다르면 입력 ID 리스트를 텐서로 바로 변환할 수 없습니다. 이 문제를 해결하기 위해, 일반적으로 입력에 *패드*를 추가합니다. + +## 입력에 패딩 추가하기[[padding-the-inputs]] + +아래 보이는 리스트는 텐서로 변환될 수 없습니다. + +```py no-format +batched_ids = [ + [200, 200, 200], + [200, 200] +] +``` + +*패딩*을 이용해 텐서가 직사각형 형태를 가질 수 있게 하면 이 문제를 해결할 수 있습니다. 패딩은 길이가 짧은 문장에 *패딩 토큰*이라고 불리는 특별한 토큰을 추가함으로써 모든 문장이 같은 길이를 갖게 합니다. 10개의 단어로 이루어진 문장 10개와 20개의 단어로 이루어진 문장 1개를 가지고 있다고 가정한다면, 패딩은 모든 문장이 20개의 단어를 갖게 하는 역할을 합니다. 우리가 사용하는 예시에서 결과 텐서는 다음과 같습니다. + +```py no-format +padding_id = 100 + +batched_ids = [ + [200, 200, 200], + [200, 200, padding_id], +] +``` + +패딩 토큰의 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-masks]] + +*어텐션 마스크*는 입력 ID 텐서와 같은 크기를 같는 텐서로, 0과 1로 이루어져 있습니다. 1은 해당 토큰을 주의 깊게 봐야한다는 것을 의미하고 0은 해당 토큰을 신경 쓰지 않아도 된다는 의미입니다. (다시 말해, 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} + +이제 배치 내 두 번째 문장과 동일한 로짓을 얻었습니다. + +두 번째 문장의 어텐션 마스크에서 마지막 값인 0은 패딩 ID라는 것을 잊지 마세요. + + + +✏️ **직접 해보세요!** 2장에서 사용한 두 개의 문장("I've been waiting for a HuggingFace course my whole life." and "I hate this so much!")을 이용해 직접 토큰화를 적용해보세요. 토큰화 결과를 모델에 넘기고 2장에서 얻은 것과 동일한 로짓을 얻었는지 확인해보세요. 이제 Now batch them together using the padding token, then create the proper attention mask. Check that you obtain the same results when going through the model! + + + +## 길이가 긴 시퀀스[[longer-sequences]] + +Transformer 모델을 사용할 때, 모델에 넘겨줄 수 있는 시퀀스 길이에 제한이 있습니다. 대부분의 모델은 최대 512개나 1024개의 토큰으로 이루어진 시퀀스를 처리할 수 있으며 더 긴 길이의 시퀀스를 처리해달라는 요청을 받으면 작동하지 않습니다. 이 문제에 대한 해결 방법은 2가지가 있습니다. + +- 긴 시퀀스를 처리할 수 있는 모델을 사용하세요. +- 시퀀스 길이를 최대 길이에 맞게 잘라내세요. + +모델별로 지원하는 시퀀스 길이는 다르고 몇 개의 특별한 모델은 엄청나게 긴 시퀀스를 처리할 수 있습니다. [Longformer](https://huggingface.co/transformers/model_doc/longformer.html) 가 그 중 하나이며, [LED](https://huggingface.co/transformers/model_doc/led.html)도 해당합니다. 만약 매우 긴 길이의 시퀀스를 다뤄야 하는 태스크를 진행하고 있다면, 두 모델을 살펴보세요. + +그렇지 않으면 `max_sequence_length` 파라미터를 이용해 시퀀스 길이를 잘라내는 것이 좋습니다. + +```py +sequence = sequence[:max_sequence_length] +``` diff --git a/chapters/ko/chapter2/6.mdx b/chapters/ko/chapter2/6.mdx new file mode 100644 index 000000000..e564a39fa --- /dev/null +++ b/chapters/ko/chapter2/6.mdx @@ -0,0 +1,164 @@ + + +# 한 번에 실행하기[[putting-it-all-together]] + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +지난 섹션에서는 대부분의 과정을 하나씩 수행해왔습니다. 토크나이저의 작동 방식을 살펴보고 토큰화, 입력 ID로의 변환, 패딩, 잘라내기 그리고 어텐션 마스크에 대해 알아봤습니다. + +하지만 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는 어텐션 마스크뿐만 아니라 입력 ID도 포함합니다. 추가적인 입력을 받는 다른 모델들도 `tokenizer` 객체에 의해 생기는 결과물을 가지고 있습니다. + +아래의 예시를 보면 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") + +# 모델이 지원하는 최대 시퀀스 길이에 맞게 패딩을 추가합니다. +# (BERT나 DistilBERT의 최대 길이는 512) +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!"] + +# 모델이 지원하는 최대 시퀀스 길이에 맞게 시퀀스 길이를 잘라냅니다. +# (BERT나 DistilBERT의 최대 길이는 512) +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]] + +토크나이저가 반환한 입력 ID를 자세히 살펴보면 이전에 봤던 결과와 조금 다르다는 것을 알 수 있습니다. + +```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] +``` + +시작과 끝에 추가된 토큰 ID가 있습니다. 두 시퀀스의 ID가 무엇을 의미하는지 확인하기 위해 디코딩해보겠습니다. + +```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/ko/chapter2/7.mdx b/chapters/ko/chapter2/7.mdx new file mode 100644 index 000000000..8c6b6349d --- /dev/null +++ b/chapters/ko/chapter2/7.mdx @@ -0,0 +1,18 @@ +# 기본 사용 완료![[basic-usage-completed]] + + + +여기까지 오느라 수고하셨습니다! 이 단원에서 배운 것을 되돌아봅시다. + +- Transformer 모델의 기본 구성 요소에 대해 배웠습니다. +- 토큰화 파이프라인의 구성 요소에 대해 배웠습니다. +- Transformer 모델을 사용하는 방법을 알아보았습니다. +- 토크나이저를 이용해 텍스트를 모델이 이해할 수 있는 텐서로 변환하는 방법을 배웠습니다. +- 원시 텍스트에서 예측 결과를 얻기 위해 토크나이저와 모델을 함께 사용하는 법을 배웠습니다. +- 입력 ID의 한계와 어텐션 마스크에 대해 배웠습니다. +- 다양하고 설정 가능한 토크나이저 메서드를 사용해보았습니다. + +이제부터는 🤗 Transformers 문서 안에서 자유롭게 항해할 수 있어야 합니다. 단어는 익숙하게 들릴 것이고, 사용법을 익히는 데에 이미 충분한 시간을 들였습니다. diff --git a/chapters/ko/chapter2/8.mdx b/chapters/ko/chapter2/8.mdx new file mode 100644 index 000000000..ddc6f4558 --- /dev/null +++ b/chapters/ko/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: "오답입니다. 몇몇 체크포인트와 모델은 다양한 언어를 처리할 수 있지만, 언어에 따라 체크포인트를 자동으로 선택할 수 있도록 내장된 도구는 없습니다. 태스크에 가장 적합한 체크포인트를 찾으려면 모델 허브에 가보세요!" + } + ]} +/> + +{:else} +### 5. TFAutoModel이 무엇인가요? + +AutoTrain과 헷갈린 게 아닐까요?" + }, + { + text: "체크포인트에 기반하여 적합한 구조를 반환하는 객체입니다.", + explain: "정확합니다. AutoModel은 적절한 모델 구조를 초기화할 때 필요한 체크포인트만을 필요로 합니다.", + correct: true + }, + { + text: "적합한 가중치를 불러오기 위해 입력에 사용된 언어를 자동으로 감지하는 모델입니다.", + explain: "오답입니다. 몇몇 체크포인트와 모델은 다양한 언어를 처리할 수 있지만, 언어에 따라 체크포인트를 자동으로 선택할 수 있도록 내장된 도구는 없습니다. 태스크에 가장 적합한 체크포인트를 찾으려면 모델 허브에 가보세요!" + } + ]} +/> + +{/if} + +### 6. 길이가 다른 시퀀스를 하나의 배치로 만들 때 신경써야 할 부분은 무엇일까요? + + + +### 7. 시퀀스 분류 모델의 로짓 출력 결과에 소프트맥스 함수를 적용하는 핵심적인 이유는 무엇일까요? + + + +### 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/ko/chapter5/1.mdx b/chapters/ko/chapter5/1.mdx new file mode 100644 index 000000000..c97835d4a --- /dev/null +++ b/chapters/ko/chapter5/1.mdx @@ -0,0 +1,22 @@ +# 단원 소개[[introduction]] + + + +[챕터 3](/course/chapter3)에서는 🤗 Datasets 라이브러리를 처음 맛보면서 모델을 미세 조정하는 데 필요한 세 가지 주요 단계를 배웠습니다: + +1. Hugging Face Hub에서 데이터셋을 로드한다. +2. `Dataset.map()` 함수를 통해 데이터셋을 전처리한다. +3. 평가 메트릭를 로드하고 계산한다. + +하지만 지금까지 배운 것은 🤗 Datasets 라이브러리가 할 수 있는 것의 빙산의 일각입니다! 이번 챕터에서는 더 자세히 살펴볼 것이며, 그 과정에서 다음 질문에 대한 답을 찾을 수 있을 것입니다: + +* 데이터셋이 Hub에 없을 때는 어떻게 할 것인가? +* 데이터셋을 어떻게 쪼개어 분석할 수 있는가? (그리고 Pandas를 반드시 사용해야만 한다면?) +* 데이터셋이 너무 방대하여 노트북 RAM을 초과하면 어떻게 할 것인가? +* 도대체 "메모리 매핑 (mmap)"과 Apache Arrow는 무엇인가? +* 당신 고유의 데이터셋을 만들어 Hub로 푸시하려면 어떻게 해야 하는가? + +여기서 배운 기술을 통해 [챕터 6](/course/chapter6)과 [챕터 7](/course/chapter7)에서 심화 토큰화 기법 및 미세 조정 작업을 하는 데 활용할 예정이므로 커피 한 잔 하고 오신 뒤 시작하도록 하겠습니다! diff --git a/chapters/ko/chapter5/2.mdx b/chapters/ko/chapter5/2.mdx new file mode 100644 index 000000000..9f8c463f1 --- /dev/null +++ b/chapters/ko/chapter5/2.mdx @@ -0,0 +1,167 @@ +# 필요한 데이터셋이 Hub에 없다면 어떻게 할까요?[[what-if-my-dataset-isnt-on-the-hub]] + + + +[Hugging Face Hub](https://huggingface.co/datasets)를 통해 데이터셋을 다운로드하는 방법은 배웠지만, Hub가 아닌 로컬 환경이나 원격 서버에 저장된 데이터로 작업해야하는 경우도 많이 있을 것입니다. 이번 섹션에서는 🤗 Datasets를 이용하여 Hugging Face Hub에 없는 데이터셋을 로딩하는 방법을 알려드리도록 하겠습니다. + + + +## 로컬 또는 원격 데이터셋으로 작업하기[[working-with-local-and-remote-datasets]] + +🤗 Datasets는 로컬 또는 원격 데이터셋 로딩을 다루기 위한 스크립트를 제공하며 다음과 같이 몇 가지 일반적인 데이터 형식들을 지원합니다: + +| Data format | Loading script | Example | +| :----------------: | :------------: | :-----------------------------------------------------: | +| CSV & TSV | `csv` | `load_dataset("csv", data_files="my_file.csv")` | +| Text files | `text` | `load_dataset("text", data_files="my_file.txt")` | +| JSON & JSON Lines | `json` | `load_dataset("json", data_files="my_file.jsonl")` | +| Pickled DataFrames | `pandas` | `load_dataset("pandas", data_files="my_dataframe.pkl")` | + +테이블에서 볼 수 있듯이, 파일에 대한 경로를 지정하는 `data_files` 인수와 함께 `load_dataset()` 함수에 각 데이터 형식에 맞는 로딩 스크립트 유형을 지정해 주기만하면 됩니다. 지금부터 로컬 파일에서 데이터셋을 로딩을 해보는 것으로 시작해서 원격 파일로 동일한 작업을 수행하는 방법을 살펴보도록 하겠습니다. + +## 로컬 데이터셋 로딩하기[[loading-a-local-dataset]] + +이번 예제에서는 이탈리아어 질의응답 (QA) 대규모 데이터셋인 [SQuAD-it](https://github.com/crux82/squad-it/)를 사용하겠습니다. + +훈련 및 테스트 데이터는 Github에 호스팅되어 있으므로, 간단한 `wget` 명령어를 통해 다운로드 할 수 있습니다: + +```python +!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-train.json.gz +!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-test.json.gz +``` + +이렇게하면 *SQuAD_it-train.json.gz*, *SQuAD_it-test.json.gz* 두 개의 압축 파일을 다운로드 받을 수 있고, `gzip` 리눅스 명령어를 통해 압축을 풀 수 있습니다: + +```python +!gzip -dkv SQuAD_it-*.json.gz +``` + +```bash +SQuAD_it-test.json.gz: 87.4% -- replaced with SQuAD_it-test.json +SQuAD_it-train.json.gz: 82.2% -- replaced with SQuAD_it-train.json +``` + +명령어를 통해 압축 파일들이 각각 _SQuAD_it-train.json_, _SQuAD_it-test.json_ 로 바뀌어 저장되어 있는 것을 볼 수 있습니다. + + + +✎ 위 쉘 명령어에 `!`가 붙는 이유는 주피터 노트북에서 실행하고 있기 때문입니다. 터미널에서 명령어를 실행한다면 `!`를 없애주시면 됩니다. + + + +`load_dataset()` 함수로 JSON 파일을 로딩할 때, 파일이 일반 JSON (nested dictionary와 유사)으로 형태인 지 혹은 JSON Lines (줄로 구분된 JSON)형태인 지 알아야 합니다. 많은 질의응답 데이터셋처럼, SQuAD-it 데이터셋은 `data` 필드에 텍스트가 모두 저장된 nested 포맷을 사용하며, 이는 다음과 같이 `field` 인수를 지정하여 데이터셋을 로딩할 수 있음을 의미합니다: + +```py +from datasets import load_dataset + +squad_it_dataset = load_dataset("json", data_files="SQuAD_it-train.json", field="data") +``` + +디폴트로 로컬 파일을 로딩하면 `train` 스플릿을 가진 `DatasetDict` 객체가 생성됩니다. `squad_it_dataset` 객체를 통해 이를 확인할 수 있습니다: + +```py +squad_it_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 442 + }) +}) +``` + +이것은 `train` 스플릿에 대한 행의 수 (num_rows)와 열의 이름 (features)을 보여줍니다. 또한 다음과 같이 `train` 스플릿을 인덱싱하여 한 예제를 볼 수 있습니다: + +```py +squad_it_dataset["train"][0] +``` + +```python out +{ + "title": "Terremoto del Sichuan del 2008", + "paragraphs": [ + { + "context": "Il terremoto del Sichuan del 2008 o il terremoto...", + "qas": [ + { + "answers": [{"answer_start": 29, "text": "2008"}], + "id": "56cdca7862d2951400fa6826", + "question": "In quale anno si è verificato il terremoto nel Sichuan?", + }, + ... + ], + }, + ... + ], +} +``` + +훌륭하게 첫 번째 로컬 데이터셋을 로딩했습니다! 하지만 우리가 하고 싶은 것은 `train`과 `test` 스플릿을 모두 지닌 하나의 `DatasetDict` 객체를 만들어 두 스플릿에 동시에 `Dataset.map()` 함수를 적용하는 것입니다. 이를 위해, 각 스플릿 이름을 해당 스플릿과 관련된 파일에 매핑하는 dictionary를 `data_files` 인수로 줄 수 있습니다: + +```py +data_files = {"train": "SQuAD_it-train.json", "test": "SQuAD_it-test.json"} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +squad_it_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 442 + }) + test: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 48 + }) +}) +``` + +이것이 바로 우리가 원했던 것입니다. 이제 다양한 전처리 기법을 적용하여 데이터를 정리하고, 리뷰를 토큰화하는 등의 작업을 수행할 수 있습니다. + + + +`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)에서 확인할 수 있습니다. + + + +🤗 Datasets 로딩 스크립트는 입력 파일의 자동 압축 해제를 지원하기 때문에 실제로 사용할 때는 `data_files` 인수로 압축 파일을 입력하면 `gzip` 사용을 생략할 수 있습니다. + +```py +data_files = {"train": "SQuAD_it-train.json.gz", "test": "SQuAD_it-test.json.gz"} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +``` + +이는 많은 GZIP 파일들을 수동으로 압축 해제하고 싶지 않을 때 유용하며, ZIP, TAR과 같은 일반적인 포맷들도 지원하므로 `data_files`에 해당 파일을 입력해 주기만 하면 됩니다. + +노트북이나 데스크탑에서 로컬 파일을 로딩하는 방법을 알아봤으므로, 이제 원격 파일을 로딩하는 방법을 살펴보겠습니다. + +## 원격 데이터셋 로딩하기[[loading-a-remote-dataset]] + +만약 당신이 회사에서 데이터 과학자 또는 개발자로 일하고 있다면 분석하려는 데이터셋이 원격 서버에 저장되어 있을 가능성이 큽니다. 다행히, 원격 파일을 로딩하는 것은 로컬 파일을 로딩하는 것만큼 간단합니다! 로컬 파일 경로를 입력하는 대신, `load_dataset()`의 `data_files` 인수에 원격 파일들이 저장되어 있는 하나 또는 그 이상의 URL을 입력해주기만 하면 됩니다. 예를 들어, GitHub에서 호스팅하고 있는 SQuAD-it 데이터셋의 경우, `data_files`에 다음과 같이 _SQuAD_it-*.json.gz_ 을 입력하면 됩니다: + +```py +url = "https://github.com/crux82/squad-it/raw/master/" +data_files = { + "train": url + "SQuAD_it-train.json.gz", + "test": url + "SQuAD_it-test.json.gz", +} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +``` + +이와 같은 방법을 통해 위에서 얻은 것과 같이 `DatasetDict` 객체를 생성할 수 있으며 수동으로 파일들을 다운로드받고 압축을 푸는 작업을 생략할 수 있습니다. 지금까지 Hugging Face Hub에서 호스팅되어 있지 않은 데이터셋을 로딩하는 다양한 방법을 알아보았습니다. 이제 다뤄볼 데이터셋을 얻었으니 다양한 데이터 전처리 기법을 배워보도록 합시다. + + + +✏️ **시도해 보세요!** GitHub 또는 [UCI Machine Learning Repository](https://archive.ics.uci.edu/ml/index.php)에서 호스팅되는 다른 데이터셋을 선택하여 위에서 배운 기술을 통해 로컬 또는 원격으로 로딩해 보시고, 추가적으로 CSV 또는 텍스트 포맷으로 저장된 데이터셋도 로딩을 시도해 보시길 바랍니다. (이 포맷들에 대한 자세한 정보는 [documentation](https://huggingface.co/docs/datasets/loading#local-and-remote-files)을 참조하세요.) + + + + diff --git a/chapters/pt/chapter1/6.mdx b/chapters/pt/chapter1/6.mdx index 4922350c6..d57ace2a4 100644 --- a/chapters/pt/chapter1/6.mdx +++ b/chapters/pt/chapter1/6.mdx @@ -14,6 +14,6 @@ Esses modelos são mais adequados para tarefas que envolvem geração de texto. Os representantes desta família de modelos incluem: - [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) -- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) +- [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) diff --git a/chapters/pt/chapter5/5.mdx b/chapters/pt/chapter5/5.mdx index 6ba5323fe..9207fa010 100644 --- a/chapters/pt/chapter5/5.mdx +++ b/chapters/pt/chapter5/5.mdx @@ -185,7 +185,7 @@ Agora, quando chamamos `fetch_issues()`, ele fará o download de todas as issues fetch_issues() ``` -Depois que as issues forem baixadas, podemos carregá-las localmente usando nossas novas habilidades da [seção 2](/course/chaper5/2): +Depois que as issues forem baixadas, podemos carregá-las localmente usando nossas novas habilidades da [seção 2](/course/chapter5/2): ```py issues_dataset = load_dataset("json", data_files="datasets-issues.jsonl", split="train") diff --git a/chapters/ru/chapter1/6.mdx b/chapters/ru/chapter1/6.mdx index 2a43f54ce..815f267d5 100644 --- a/chapters/ru/chapter1/6.mdx +++ b/chapters/ru/chapter1/6.mdx @@ -16,6 +16,6 @@ Представителями этого семейства моделей являются: - [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) -- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) +- [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) diff --git a/chapters/th/chapter1/6.mdx b/chapters/th/chapter1/6.mdx index dbdd3ea56..9238ac70f 100644 --- a/chapters/th/chapter1/6.mdx +++ b/chapters/th/chapter1/6.mdx @@ -14,6 +14,6 @@ ตัวแทนโมเดลในกลุ่มนี้ได้แก่: - [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) -- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) +- [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) diff --git a/chapters/tr/chapter1/6.mdx b/chapters/tr/chapter1/6.mdx index 198db758a..d2a36a35e 100644 --- a/chapters/tr/chapter1/6.mdx +++ b/chapters/tr/chapter1/6.mdx @@ -16,6 +16,6 @@ Bu modeller, en çok metin oluşturmayı içeren görevler için uygundur. Bu model ailelerinin temsilcileri şunları kapsar: - [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) -- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) +- [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) diff --git a/chapters/vi/chapter1/1.mdx b/chapters/vi/chapter1/1.mdx index 0dffa6bd9..fe0bb2bcf 100644 --- a/chapters/vi/chapter1/1.mdx +++ b/chapters/vi/chapter1/1.mdx @@ -36,23 +36,23 @@ Sau khi bạn hoàn thành khóa học này, chúng tôi khuyến khích bạn x Giới thiệu về tác giả: -**Abubakar Abid** đã hoàn thành chương trình Tiến sĩ về học máy ứng dụng tại Stanford. Trong thời gian học tiến sĩ, anh ấy đã tạo ra [Gradio](https://github.com/gradio-app/gradio), một thư viện Python mã nguồn mở được sử dụng để xây dựng hơn 600,000 bản demo học máy. Gradio được mua lại bởi Hugging Face, nơi Abubakar hiện đóng vai trò là trưởng nhóm học máy. +[**Abubakar Abid**](https://huggingface.co/abidlabs) đã hoàn thành chương trình Tiến sĩ về học máy ứng dụng tại Stanford. Trong thời gian học tiến sĩ, anh ấy đã tạo ra [Gradio](https://github.com/gradio-app/gradio), một thư viện Python mã nguồn mở được sử dụng để xây dựng hơn 600,000 bản demo học máy. Gradio được mua lại bởi Hugging Face, nơi Abubakar hiện đóng vai trò là trưởng nhóm học máy. -**Matthew Carrigan** là một Kỹ sư Học máy tại Hugging Face. Anh ấy sống ở Dublin, Ireland, trước đây là kỹ sư Học máy tại Parse.ly và trước đó là nhà nghiên cứu sau tiến sĩ tại Trinity College Dublin. Anh ấy không tin rằng chúng ta sẽ đạt được AGI bằng cách mở rộng các kiến ​​trúc hiện có, nhưng có niềm tin vào sự bất tử của robot. +[**Matthew Carrigan**](https://huggingface.co/Rocketknight1) là một Kỹ sư Học máy tại Hugging Face. Anh ấy sống ở Dublin, Ireland, trước đây là kỹ sư Học máy tại Parse.ly và trước đó là nhà nghiên cứu sau tiến sĩ tại Trinity College Dublin. Anh ấy không tin rằng chúng ta sẽ đạt được AGI bằng cách mở rộng các kiến ​​trúc hiện có, nhưng có niềm tin vào sự bất tử của robot. -**Lysandre Debut** là một Kỹ sư Học máy tại Hugging Face và đã làm việc với thư viện 🤗 Transformers từ những giai đoạn đầu phát triển. Mục tiêu của anh ấy là làm cho NLP có thể dễ dàng truy cập được từ tất cả mọi người bằng cách phát triển các công cụ với một API rất đơn giản. +[**Lysandre Debut**](https://huggingface.co/lysandre) là một Kỹ sư Học máy tại Hugging Face và đã làm việc với thư viện 🤗 Transformers từ những giai đoạn đầu phát triển. Mục tiêu của anh ấy là làm cho NLP có thể dễ dàng truy cập được từ tất cả mọi người bằng cách phát triển các công cụ với một API rất đơn giản. -**Sylvain Gugger** là Kỹ sư nghiên cứu tại Hugging Face và là một trong những thành viên cốt lõi của thư viện 🤗 Transformers. Trước đây, anh ấy là Nhà nghiên cứu khoa học tại fast.ai và anh ấy là đồng sáng tác đầu sách _[Deep Learning for Coders with fastai and PyTorch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)_ cùng với Jeremy Howard. Hướng nghiên cứu chính của anh ấy là làm cho việc học sâu trở nên dễ tiếp cận hơn, bằng cách thiết kế và cải tiến các kỹ thuật cho phép các mô hình huấn luyện nhanh trên các tài nguyên hạn chế. +[**Sylvain Gugger**](https://huggingface.co/sgugger) là Kỹ sư nghiên cứu tại Hugging Face và là một trong những thành viên cốt lõi của thư viện 🤗 Transformers. Trước đây, anh ấy là Nhà nghiên cứu khoa học tại fast.ai và anh ấy là đồng sáng tác đầu sách _[Deep Learning for Coders with fastai and PyTorch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)_ cùng với Jeremy Howard. Hướng nghiên cứu chính của anh ấy là làm cho việc học sâu trở nên dễ tiếp cận hơn, bằng cách thiết kế và cải tiến các kỹ thuật cho phép các mô hình huấn luyện nhanh trên các tài nguyên hạn chế. -**Dawood Khan** là một Kỹ sư Học máy tại Hugging Face. Anh ấy đến từ New York và tốt nghiệp Đại học New York chuyên ngành Khoa học máy tính. Sau khi làm việc với tư cách là Kỹ sư iOS trong một vài năm, Dawood đã nghỉ việc để bắt đầu phát triển Gradio cùng với những người đồng sáng lập của mình. Gradio cuối cùng đã được mua lại bởi Hugging Face. +[**Dawood Khan**](https://huggingface.co/dawoodkhan82) là một Kỹ sư Học máy tại Hugging Face. Anh ấy đến từ New York và tốt nghiệp Đại học New York chuyên ngành Khoa học máy tính. Sau khi làm việc với tư cách là Kỹ sư iOS trong một vài năm, Dawood đã nghỉ việc để bắt đầu phát triển Gradio cùng với những người đồng sáng lập của mình. Gradio cuối cùng đã được mua lại bởi Hugging Face. -**Merve Noyan** là Chuyên gia về Quan hệ lập trình viên tại Hugging Face, hiện đang phát triển các công cụ và xây dựng nội dung xung quanh chúng để tất cả mọi người có thể tiếp cận học máy dễ dàng hơn. +[**Merve Noyan**](https://huggingface.co/merve) là Chuyên gia về Quan hệ lập trình viên tại Hugging Face, hiện đang phát triển các công cụ và xây dựng nội dung xung quanh chúng để tất cả mọi người có thể tiếp cận học máy dễ dàng hơn. -**Lucile Saulnier** là một Kỹ sư Học máy tại Hugging Face, phát triển và hỗ trợ việc sử dụng các công cụ mã nguồn mở. Cô cũng tích cực tham gia vào nhiều dự án nghiên cứu trong lĩnh vực Xử lý Ngôn ngữ Tự nhiên như huấn luyện cộng tác và BigScience. +[**Lucile Saulnier**](https://huggingface.co/SaulLu) là một Kỹ sư Học máy tại Hugging Face, phát triển và hỗ trợ việc sử dụng các công cụ mã nguồn mở. Cô cũng tích cực tham gia vào nhiều dự án nghiên cứu trong lĩnh vực Xử lý Ngôn ngữ Tự nhiên như huấn luyện cộng tác và BigScience. -**Lewis Tunstall** là một Kỹ sư Học máy tại Hugging Face, tập trung vào việc phát triển các công cụ mã nguồn mở và giúp chúng có thể tiếp cận được với cộng đồng rộng lớn hơn. Anh cũng là đồng tác giả của cuốn sách O’Reilly [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). +[**Lewis Tunstall**](https://huggingface.co/lewtun) là một Kỹ sư Học máy tại Hugging Face, tập trung vào việc phát triển các công cụ mã nguồn mở và giúp chúng có thể tiếp cận được với cộng đồng rộng lớn hơn. Anh cũng là đồng tác giả của cuốn sách O’Reilly [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). -**Leandro von Werra** là một Kỹ sư Học máy trong nhóm mã nguồn mở tại Hugging Face và cũng là đồng tác giả của cuốn sách O'Reilly [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). Anh ấy có nhiều năm kinh nghiệm thực tế triển khai các dự án NLP vào sản xuất bằng cách làm việc trên toàn bộ hệ thống học máy. +[**Leandro von Werra**](https://huggingface.co/lvwerra) là một Kỹ sư Học máy trong nhóm mã nguồn mở tại Hugging Face và cũng là đồng tác giả của cuốn sách O'Reilly [Natural Language Processing with Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). Anh ấy có nhiều năm kinh nghiệm thực tế triển khai các dự án NLP vào sản xuất bằng cách làm việc trên toàn bộ hệ thống học máy. Bạn đã sẵn sàng chưa? Trong chương này, bạn sẽ học: diff --git a/chapters/vi/chapter1/6.mdx b/chapters/vi/chapter1/6.mdx index 3ddd84eb9..256b0f452 100644 --- a/chapters/vi/chapter1/6.mdx +++ b/chapters/vi/chapter1/6.mdx @@ -16,6 +16,6 @@ Các mô hình này phù hợp nhất cho các tác vụ liên quan đến việ Một số mô hình tiêu biểu của nhóm này bao gồm: - [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) -- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) +- [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) diff --git a/chapters/vi/chapter5/5.mdx b/chapters/vi/chapter5/5.mdx index cbb417cfa..d8faafb4f 100644 --- a/chapters/vi/chapter5/5.mdx +++ b/chapters/vi/chapter5/5.mdx @@ -186,7 +186,7 @@ Bây giờ khi ta gọi `fetch_issues ()`, nó sẽ tải xuống tất cả cá fetch_issues() ``` -Sau khi các vấn đề được tải xuống, chúng tôi có thể lôi chúng cục bộ bằng cách sử dụng các kỹ năng mới khai phá từ [phần 2](/course/chaper5/2): +Sau khi các vấn đề được tải xuống, chúng tôi có thể lôi chúng cục bộ bằng cách sử dụng các kỹ năng mới khai phá từ [phần 2](/course/chapter5/2): ```py issues_dataset = load_dataset("json", data_files="datasets-issues.jsonl", split="train") diff --git a/chapters/vi/chapter7/3.mdx b/chapters/vi/chapter7/3.mdx index 96d819c08..0cc470d6c 100644 --- a/chapters/vi/chapter7/3.mdx +++ b/chapters/vi/chapter7/3.mdx @@ -723,6 +723,7 @@ trainer = Trainer( train_dataset=downsampled_dataset["train"], eval_dataset=downsampled_dataset["test"], data_collator=data_collator, + tokenizer=tokenizer, ) ``` diff --git a/chapters/zh-CN/chapter1/6.mdx b/chapters/zh-CN/chapter1/6.mdx index 1136f6ebf..35d27c7ae 100644 --- a/chapters/zh-CN/chapter1/6.mdx +++ b/chapters/zh-CN/chapter1/6.mdx @@ -17,6 +17,6 @@ - [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) -- [GPT](https://huggingface.co/transformers/model_doc/gpt.html) +- [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) diff --git a/chapters/zh-CN/chapter5/5.mdx b/chapters/zh-CN/chapter5/5.mdx index 6074be979..055f79ea1 100644 --- a/chapters/zh-CN/chapter5/5.mdx +++ b/chapters/zh-CN/chapter5/5.mdx @@ -183,7 +183,7 @@ def fetch_issues( fetch_issues() ``` -下载issue后,我们可以使用我们 [section 2](/course/chaper5/2)新学会的方法在本地加载它们: +下载issue后,我们可以使用我们 [section 2](/course/chapter5/2)新学会的方法在本地加载它们: ```py issues_dataset = load_dataset("json", data_files="datasets-issues.jsonl", split="train") diff --git a/chapters/zh-CN/chapter7/3.mdx b/chapters/zh-CN/chapter7/3.mdx index abf328d1f..b5c410d23 100644 --- a/chapters/zh-CN/chapter7/3.mdx +++ b/chapters/zh-CN/chapter7/3.mdx @@ -724,6 +724,7 @@ trainer = Trainer( train_dataset=downsampled_dataset["train"], eval_dataset=downsampled_dataset["test"], data_collator=data_collator, + tokenizer=tokenizer, ) ``` diff --git a/chapters/zh-CN/chapter8/3.mdx b/chapters/zh-CN/chapter8/3.mdx index b893984bb..d983455c2 100644 --- a/chapters/zh-CN/chapter8/3.mdx +++ b/chapters/zh-CN/chapter8/3.mdx @@ -9,7 +9,7 @@ -[Hugging Face forums](https://discuss.huggingface.co) 是从开源团队和更广泛的 Hugging Face 社区获得帮助的好地方。以下是任何一天的主页面: +[Hugging Face 论坛](https://discuss.huggingface.co) 是从开源团队和更广泛的 Hugging Face 社区获得帮助的好地方。以下是论坛某一天的主页面:
The Hugging Face forums. @@ -25,7 +25,7 @@ ## 写一篇好的论坛帖子 -作为一个运行示例,假设我们试图从 Wikipedia 文章生成嵌入以创建自定义搜索引擎。像往常一样,我们按如下方式加载分词器和模型: +作为一个运行示例,假设我们试图从 Wikipedia 文章生成嵌入表示以创建自定义搜索引擎。像往常一样,我们按如下方式加载分词器和模型: ```python from transformers import AutoTokenizer, AutoModel @@ -35,7 +35,7 @@ tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) model = AutoModel.from_pretrained(model_checkpoint) ``` -现在假设我们尝试嵌入整个部分[Wikipedia article](https://en.wikipedia.org/wiki/Transformers) 关于Transformers(专营权,而不是图书馆!): +现在我们尝试将[变形金刚的维基百科](https://en.wikipedia.org/wiki/Transformers)的一整段进行嵌入表示(热知识:变形金刚的英文就是 Transformers ,而现在 Transformers 作为一个 🤗 Python 库也被越来越多人熟知): ```python text = """ diff --git a/subtitles/README.md b/subtitles/README.md index 53d87db37..002948954 100644 --- a/subtitles/README.md +++ b/subtitles/README.md @@ -28,15 +28,29 @@ python utils/generate_subtitles.py --language zh-CN --youtube_language_code zh-H Once you have the `.srt` files you can manually fix any translation errors and then open a pull request with the new files. -# How to convert bilingual subtitle to monolingual subtitle +# Convert bilingual subtitles to monolingual subtitles -# Logic +In some SRT files, the English caption line is conventionally placed at the last line of each subtitle block to enable easier comparison when correcting the machine translation. -The english caption line is conventionally placed at the last line of each subtitle block in srt files. So removing the last line of each subtitle block would make the bilingual subtitle a monolingual subtitle. +For example, in the `zh-CN` subtitles, each block has the following format: -# Usage -> python3 convert_bilingual_monolingual.py -i \ -o \ +``` +1 +00:00:05,850 --> 00:00:07,713 +- 欢迎来到 Hugging Face 课程。 +- Welcome to the Hugging Face Course. +``` + +To upload the SRT file to YouTube, we need the subtitle in monolingual format, i.e. the above block should read: + +``` +1 +00:00:05,850 --> 00:00:07,713 +- 欢迎来到 Hugging Face 课程。 +``` -**Example** -* For instance, the input file name is "test.cn.en.srt", and you name your output file as "output_test.cn.srt" * -> python3 convert_bilingual_monolingual.py -i test.cn.en.srt -o output_test.cn.srt \ No newline at end of file +To handle this, we provide a script that converts the bilingual SRT files to monolingual ones. To perform the conversion, run: + +```bash +python utils/convert_bilingual_monolingual.py --input_language_folder subtitles/LANG_ID --output_language_folder tmp-subtitles +``` \ No newline at end of file diff --git a/subtitles/en/00_welcome-to-the-hugging-face-course.srt b/subtitles/en/00_welcome-to-the-hugging-face-course.srt index ae8eb7042..e7f55a2fe 100644 --- a/subtitles/en/00_welcome-to-the-hugging-face-course.srt +++ b/subtitles/en/00_welcome-to-the-hugging-face-course.srt @@ -1,6 +1,6 @@ 1 00:00:05,850 --> 00:00:07,713 -- Welcome to the Hugging Face Course. +Welcome to the Hugging Face Course. 2 00:00:08,550 --> 00:00:10,320 diff --git a/subtitles/fr/00_welcome-to-the-hugging-face-course.srt b/subtitles/fr/00_welcome-to-the-hugging-face-course.srt index 73960fe7a..d7fdaea1e 100644 --- a/subtitles/fr/00_welcome-to-the-hugging-face-course.srt +++ b/subtitles/fr/00_welcome-to-the-hugging-face-course.srt @@ -7,7 +7,7 @@ Bienvenue au cours d'Hugging Face. Ce cours a été conçu pour vous enseigner tout ce qu'il faut savoir à propos de l'écosystème d'Hugging Face. 3 -0:00:12.559,0:00:18.080 +0:00:12.559 --> 0:00:18.080 Comment utiliser le Hub de jeux de données et de modèles ainsi que toutes nos bibliothèques open source. 4 @@ -27,7 +27,7 @@ La première vous apprendra les bases sur comment utiliser un transformer finetu La deuxième est une plongée plus profonde dans nos bibliothèques et vous apprendra à aborder n'importe quelle tâche de NLP. 8 -0:00:42.079,0:00:48.320 +0:00:42.079 --> 0:00:48.320 Nous travaillons activement sur la dernière partie et nous espérons qu'elle sera prête pour le printemps 2022. 9 @@ -39,7 +39,7 @@ Le premier chapitre ne requiert aucune connaissance et constitue une bonne intro Les chapitres suivants nécessitent une bonne connaissance de Python et quelques notions de base de l'apprentissage automatique et de l'apprentissage profond. 11 -0:01:04.159,0:01:09.840 +0:01:04.159 --> 0:01:09.840 Si vous ne savez pas ce qu'un entraînement et une validation sont ou encore ce qu'une descente de gradient signifie, 12 @@ -59,7 +59,7 @@ Chaque partie abordée dans ce cours a une version dans ces deux frameworks. Vou Voici l'équipe qui a développé ce cours. Je vais maintenant laisser chacun des intervenants se présenter brièvement. 16 -0:01:37.119,0:01:41.000 +0:01:37.119 --> 0:01:41.000 Bonjour, je m'appelle Matthew et je suis ingénieur en apprentissage machine chez Hugging Face. 17 @@ -67,7 +67,7 @@ Bonjour, je m'appelle Matthew et je suis ingénieur en apprentissage machine che Je travaille dans l'équipe open source et je suis responsable de la maintenance en particulier des codes en TensorFlow. 18 -0:01:47.119,0:01:52.960 +0:01:47.119 --> 0:01:52.960 Auparavant, j'étais ingénieur en apprentissage automatique chez Parse.ly qui a récemment été acquis par Automattic. 19 @@ -79,7 +79,7 @@ Avant cela j'étais chercheur en post-doc au Trinity College Dublin en Irlande, Bonjour, je suis Lysandre, je suis ingénieur en apprentissage automatique chez Hugging Face et je fais spécifiquement partie de l'équipe open source. 21 -0:02:08.479,0:02:18.080 +0:02:08.479 --> 0:02:18.080 Je suis à Hugging Face depuis quelques années maintenant et aux côtés des membres de mon équipe j'ai travaillé sur la plupart des outils que vous verrez dans ce cours. 22 @@ -87,7 +87,7 @@ Je suis à Hugging Face depuis quelques années maintenant et aux côtés des me Bonjour, je m'appelle Sylvain, je suis ingénieur de recherche chez Hugging Face et l'un des principaux mainteneurs de la bibliothèque Transformers. 23 -0:02:25.599,0:02:32.000 +0:02:25.599 --> 0:02:32.000 Auparavant, j'ai travaillé chez Fast.ai où j'ai aidé à développer la bibliothèque Fastai ainsi que le livre en ligne. 24 @@ -151,5 +151,5 @@ Dans une vie antérieure, j'étais physicien théoricien et je faisais des reche Je m'appelle Leandro et je suis ingénieur en apprentissage automatique dans le domaine de l'équipe open source d'Hugging Face. 39 -0:04:20.799,0:04:28.680 +0:04:20.799 --> 0:04:28.680 Avant de rejoindre Hugging Face, j'ai travaillé comme data scientist en Suisse et j'ai enseigné la science des données à l'université. \ No newline at end of file diff --git a/subtitles/fr/03_what-is-transfer-learning.srt b/subtitles/fr/03_what-is-transfer-learning.srt index 05849f178..50ee488b4 100644 --- a/subtitles/fr/03_what-is-transfer-learning.srt +++ b/subtitles/fr/03_what-is-transfer-learning.srt @@ -124,7 +124,7 @@ OpenAI a également étudié le biais de prédiction de son modèle GPT-3 0:03:35.840,0:03:39.519 qui a été pré-entrainé en utilisant l'objectif de deviner le mot suivant. -0:03:39.5190:03:50.000 +0:03:39.519,0:03:50.000 En changeant le genre du prompt de « Il était très » à « Elle était très », les prédictions majoritairement neutres sont devenues presque uniquement axées sur le physique. 0:03:50.000,0:03:59.640 diff --git a/subtitles/fr/68_data-collators-a-tour.srt b/subtitles/fr/68_data-collators-a-tour.srt index 9075987e8..4cdfe8aa7 100644 --- a/subtitles/fr/68_data-collators-a-tour.srt +++ b/subtitles/fr/68_data-collators-a-tour.srt @@ -226,8 +226,10 @@ Mais l'assembleur de données pour la modélisation du langage le fera pour vous 00:05:57.680 --> 00:05:59.280 Et c'est tout. +60 00:05:59.280 --> 00:06:02.560 Ceci couvre donc les assembleurs de données les plus couramment utilisés et les tâches pour lesquelles ils sont utilisés. +61 00:06:02.560 --> 00:06:08.720 Nous espérons que vous savez maintenant quand utiliser les assembleurs de données et lequel choisir pour votre tâche spécifique. \ No newline at end of file diff --git a/subtitles/fr/Titles and descriptions.txt b/subtitles/fr/titles-and-descriptions.txt similarity index 76% rename from subtitles/fr/Titles and descriptions.txt rename to subtitles/fr/titles-and-descriptions.txt index cf9c832c8..ad102d6bf 100644 --- a/subtitles/fr/Titles and descriptions.txt +++ b/subtitles/fr/titles-and-descriptions.txt @@ -6,7 +6,7 @@ Traduction : Loïck Bourdois Vous voulez commencer par des vidéos ? Pourquoi ne pas essayer : - Qu'est-ce que l'apprentissage par transfert ? https://youtu.be/BqqfQnyjmgg - La fonction pipeline : https://youtu.be/tiZFewofSLM -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join @@ -14,15 +14,15 @@ Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://hugg La fonction pipeline La fonction pipeline et toutes les tâches NLP qu'elle peut effectuer. -Intervenant : Sylvain Gugger +Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter1 -Ouvrir les codes de la vidéo dans Colab : +Ouvrir les codes de la vidéo dans Colab : - En anglais : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/en/chapter1/section3.ipynb - En français : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/fr/chapter1/section3.ipynb Vidéos connexes : - Derrière la fonction pipeline : https://youtu.be/1pedAIvTWXk -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join @@ -35,7 +35,7 @@ Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter1 Vidéos connexes : - Qu'est-ce que l'apprentissage par transfert ? https://youtu.be/BqqfQnyjmgg -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join @@ -43,11 +43,11 @@ Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://hugg Qu'est-ce que l'apprentissage par transfert ? Qu'est-ce que l'apprentissage par transfert, pourquoi et quand est-il utile ? -Intervenant : Sylvain Gugger +Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter1 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/training_loop.ipynb -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join @@ -67,8 +67,8 @@ Pour mieux comprendre ce qui se passe à l'intérieur du transformer, nous vous - Le GPT-2 illustré : https://jalammar.github.io/illustrated-gpt2/ - Comprendre l'attention : https://jalammar.github.io/visualizing-neural-machine-translation-mechanics-of-seq2seq-models-with-attention/ En outre, pour une perspective axée sur le code, nous vous recommandons de jeter un coup d'œil à l'article suivant : -- The Annotated Transformer, par Harvard NLP : https://nlp.seas.harvard.edu/2018/04/03/attention.html -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +- The Annotated Transformer, par Harvard NLP : https://nlp.seas.harvard.edu/2018/04/03/attention.html +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join @@ -89,34 +89,14 @@ Pour mieux comprendre ce qui se passe à l'intérieur du transformer, nous vous - Le GPT-2 illustré : https://jalammar.github.io/illustrated-gpt2/ - Comprendre l'attention : https://jalammar.github.io/visualizing-neural-machine-translation-mechanics-of-seq2seq-models-with-attention/ En outre, pour une perspective axée sur le code, nous vous recommandons de jeter un coup d'œil à l'article suivant : -- The Annotated Transformer, par Harvard NLP : https://nlp.seas.harvard.edu/2018/04/03/attention.html -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +- The Annotated Transformer, par Harvard NLP : https://nlp.seas.harvard.edu/2018/04/03/attention.html +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Transformer : l’encodeur - -Une introduction générale de haut niveau à la partie Encodeur du transformer. -Qu'est-ce que c'est, quand faut-il l'utiliser ? -Intervenant : Lysandre Debut -Traduction : Loïck Bourdois -Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter1 -Vidéos connexes : -- Modèles encodeur : https://youtu.be/MUqNwgPjJvQ -- Modèles décodeur : https://youtu.be/d_ixlCubqQw -- Modèles encodeur-décodeur : https://youtu.be/0_4KEb08xrE -Pour mieux comprendre ce qui se passe à l'intérieur du transformer, nous vous recommandons les articles de blog suivants de Jay Alammar : -- Le Transformateur illustré : https://jalammar.github.io/illustrated-transformer/ -- Le GPT-2 illustré : https://jalammar.github.io/illustrated-gpt2/ -- Comprendre l'attention : https://jalammar.github.io/visualizing-neural-machine-translation-mechanics-of-seq2seq-models-with-attention/ -En outre, pour une perspective axée sur le code, nous vous recommandons de jeter un coup d'œil à l'article suivant : -- The Annotated Transformer, par Harvard NLP : https://nlp.seas.harvard.edu/2018/04/03/attention.html -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 -Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join - - Transformer : le décodeur + Une introduction générale de haut niveau à la partie décodeur du transformer. Qu'est-ce que c'est, quand faut-il l'utiliser ? Intervenant : Lysandre Debut @@ -131,13 +111,14 @@ Pour mieux comprendre ce qui se passe à l'intérieur du transformer, nous vous - Le GPT-2 illustré : https://jalammar.github.io/illustrated-gpt2/ - Comprendre l'attention : https://jalammar.github.io/visualizing-neural-machine-translation-mechanics-of-seq2seq-models-with-attention/ En outre, pour une perspective axée sur le code, nous vous recommandons de jeter un coup d'œil à l'article suivant : -- The Annotated Transformer, par Harvard NLP : https://nlp.seas.harvard.edu/2018/04/03/attention.html -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +- The Annotated Transformer, par Harvard NLP : https://nlp.seas.harvard.edu/2018/04/03/attention.html +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join Transformer : l’encodeur-décodeur + Une introduction générale de haut niveau à l'encodeur-décodeur, ou modèles de séquence à séquence utilisant l'architecture Transformer. Qu'est-ce que c'est, quand faut-il l'utiliser ? Intervenant : Lysandre Debut Traduction : Loïck Bourdois @@ -151,63 +132,63 @@ Pour mieux comprendre ce qui se passe à l'intérieur du transformer, nous vous - Le GPT-2 illustré : https://jalammar.github.io/illustrated-gpt2/ - Comprendre l'attention : https://jalammar.github.io/visualizing-neural-machine-translation-mechanics-of-seq2seq-models-with-attention/ En outre, pour une perspective axée sur le code, nous vous recommandons de jeter un coup d'œil à l'article suivant : -- The Annotated Transformer, par Harvard NLP : https://nlp.seas.harvard.edu/2018/04/03/attention.html -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +- The Annotated Transformer, par Harvard NLP : https://nlp.seas.harvard.edu/2018/04/03/attention.html +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Que se passe-t-il dans la fonction pipeline ? (PyTorch) +Que se passe-t-il dans la fonction pipeline? (PyTorch) -Que se passe-t-il vraiment lorsque nous exécutons un texte dans un pipeline créé avec la bibliothèque 🤗 Transformers ? +Que se passe-t-il vraiment lorsque nous exécutons un texte dans un pipeline créé avec la bibliothèque 🤗 Transformers ? Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter2 -Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/inside_pipeline_pt.ipynb -Version TensorFlow : https://youtu.be/wVN12smEvqg -Vidéos connexes : -- La fonction pipeline : https://youtu.be/tiZFewofSLM -- En savoir plus sur le pipeline de tokenization : https://youtu.be/Yffk5aydLzg -- En savoir plus sur l'instanciation d'un transformer : https://youtu.be/AhChOFRegn4 -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/inside_pipeline_pt.ipynb +Version TensorFlow : https://youtu.be/wVN12smEvqg +Vidéos connexes : +- La fonction pipeline : https://youtu.be/tiZFewofSLM +- En savoir plus sur le pipeline de tokenization : https://youtu.be/Yffk5aydLzg +- En savoir plus sur l'instanciation d'un transformer : https://youtu.be/AhChOFRegn4 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Que se passe-t-il dans la fonction pipeline ? (TensorFlow) +Que se passe-t-il dans la fonction pipeline? (TensorFlow) -Que se passe-t-il vraiment lorsque nous exécutons un texte dans un pipeline créé avec la bibliothèque 🤗 Transformers ? +Que se passe-t-il vraiment lorsque nous exécutons un texte dans un pipeline créé avec la bibliothèque 🤗 Transformers ? Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter2 -Ouvrir les codes de la vidéo dans Colab : Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/inside_pipeline_pt.ipynb +Ouvrir les codes de la vidéo dans Colab : Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/inside_pipeline_pt.ipynb Version PyTorch : https://youtu.be/1pedAIvTWXk Vidéos connexes : - La fonction pipeline : https://youtu.be/tiZFewofSLM - En savoir plus sur le pipeline de tokénisation : https://youtu.be/Yffk5aydLzg - En savoir plus sur l'instanciation d'un transformer : https://youtu.be/d3JVgghSOew -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Instancier un transformer (PyTorch) +Instancier un transformer (PyTorch) Instanciez n'importe quel modèle de la bibliothèque 🤗 Transformers, en utilisant un checkpoint pré-entraîné ou simplement des poids aléatoires. Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter2 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/model_api_pt.ipynb -Version TensorFlow : https://youtu.be/d3JVgghSOew -Vidéos connexes : -- La fonction pipeline : https://youtu.be/tiZFewofSLM +Version TensorFlow : https://youtu.be/d3JVgghSOew +Vidéos connexes : +- La fonction pipeline : https://youtu.be/tiZFewofSLM - L'API push_to_hub : https://youtu.be/A5IWIxsHLUw -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Instancier un transformers (TensorFlow) +Instancier un transformers (TensorFlow) Instanciez n'importe quel modèle de la bibliothèque 🤗 Transformers, en utilisant un checkpoint pré-entraîné ou simplement des poids aléatoires. Intervenant : Sylvain Gugger @@ -215,1014 +196,1018 @@ Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter2 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/model_api_pt.ipynb Version PyTorch : https://youtu.be/AhChOFRegn4 -Vidéos connexes : -- La fonction pipeline : https://youtu.be/tiZFewofSLM +Vidéos connexes : +- La fonction pipeline : https://youtu.be/tiZFewofSLM - L'API push_to_hub : https://youtu.be/A5IWIxsHLUw -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Vue d'ensemble des tokenizers +Vue d'ensemble des tokenizers -Une introduction générale sur les différents types de tokenizers. +Une introduction générale sur les différents types de tokenizers. Intervenant : Lysandre Debut Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter2 -Vidéos connexes : -- Tokenizers à base de mots : https://youtu.be/nhJxYji1aho -- Tokenizers à base de caractères : https://youtu.be/ssLq_EK2jLE +Vidéos connexes : +- Tokenizers à base de mots : https://youtu.be/nhJxYji1aho +- Tokenizers à base de caractères : https://youtu.be/ssLq_EK2jLE - Tokenizers à base de sous-mots : https://youtu.be/zHvTiHr506c -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Tokenizers à base de mots +Tokenizers à base de mots -Qu'est-ce qu'un tokenizer à base de mots, et quelles sont les forces et les faiblesses de ces tokenizers. Une introduction générale sur les différents types de tokenizers. +Qu'est-ce qu'un tokenizer à base de mots, et quelles sont les forces et les faiblesses de ces tokenizers. Une introduction générale sur les différents types de tokenizers. Intervenant : Lysandre Debut Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter2 -Vidéos connexes : -- Tokenizers à base de mots : https://youtu.be/nhJxYji1aho -- Tokenizers à base de caractères : https://youtu.be/ssLq_EK2jLE +Vidéos connexes : +- Tokenizers à base de mots : https://youtu.be/nhJxYji1aho +- Tokenizers à base de caractères : https://youtu.be/ssLq_EK2jLE - Tokenizers à base de sous-mots : https://youtu.be/zHvTiHr506c -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Tokenizers à base de caractères +Tokenizers à base de caractères -Qu'est-ce qu'un tokenizer à base de caractères, et quelles sont les forces et les faiblesses de ces tokenizers. +Qu'est-ce qu'un tokenizer à base de caractères, et quelles sont les forces et les faiblesses de ces tokenizers. Intervenant : Lysandre Debut Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter2 -Vidéos connexes : -- Tokenizers à base de mots : https://youtu.be/nhJxYji1aho -- Tokenizers à base de caractères : https://youtu.be/ssLq_EK2jLE +Vidéos connexes : +- Tokenizers à base de mots : https://youtu.be/nhJxYji1aho +- Tokenizers à base de caractères : https://youtu.be/ssLq_EK2jLE - Tokenizers à base de sous-mots : https://youtu.be/zHvTiHr506c -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join Tokenizers à base de sous-mots -Qu'est-ce qu'un tokenizer à base de sous-mots, et quelles sont les forces et les faiblesses de ces tokenizers. +Qu'est-ce qu'un tokenizer à base de sous-mots, et quelles sont les forces et les faiblesses de ces tokenizers. Intervenant : Lysandre Debut Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter2 -Vidéos connexes : -- Tokenizers à base de mots : https://youtu.be/nhJxYji1aho -- Tokenizers à base de caractères : https://youtu.be/ssLq_EK2jLE +Vidéos connexes : +- Tokenizers à base de mots : https://youtu.be/nhJxYji1aho +- Tokenizers à base de caractères : https://youtu.be/ssLq_EK2jLE - Tokenizers à base de sous-mots : https://youtu.be/zHvTiHr506c -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Le pipeline de tokénisation +Le pipeline de tokénisation -Que se passe-t-il lorsque nous appelons un tokenizer sur certains textes et comment calcule-t-il les nombres qu'il produit ? +Que se passe-t-il lorsque nous appelons un tokenizer sur certains textes et comment calcule-t-il les nombres qu'il produit ? Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter2 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/tokenizer_pipeline.ipynb -Vidéos connexes : -- La fonction pipeline : https://youtu.be/tiZFewofSLM -- En savoir plus sur les algorithmes du tokenizer : https://youtu.be/VFp38yj8h3A -- En savoir plus sur les masques d'attention : https://youtu.be/M6adb1j2jPI +Vidéos connexes : +- La fonction pipeline : https://youtu.be/tiZFewofSLM +- En savoir plus sur les algorithmes du tokenizer : https://youtu.be/VFp38yj8h3A +- En savoir plus sur les masques d'attention : https://youtu.be/M6adb1j2jPI - En savoir plus sur les token de type identifiant : https://youtu.be/0u3ioSwev3s -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Regroupement des entrées (PyTorch) +Regroupement des entrées (PyTorch) -Comment pouvons-nous regrouper différentes phrases de différentes longueurs et nous assurer que nous obtenons le même résultat lorsque nous les passons dans le modèle sous forme de batch ou de phrases individuelles. +Comment pouvons-nous regrouper différentes phrases de différentes longueurs et nous assurer que nous obtenons le même résultat lorsque nous les passons dans le modèle sous forme de batch ou de phrases individuelles. Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter2 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/batch_inputs_pt.ipynb -Version TensorFlow : https://youtu.be/ROxrFOEbsQE -Vidéos connexes : -- La fonction pipeline : https://youtu.be/tiZFewofSLM +Version TensorFlow : https://youtu.be/ROxrFOEbsQE +Vidéos connexes : +- La fonction pipeline : https://youtu.be/tiZFewofSLM - Le pipeline de tokenisation : https://youtu.be/Yffk5aydLzg -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Regroupement des entrées (TensorFlow) +Regroupement des entrées (TensorFlow) -Comment pouvons-nous regrouper différentes phrases de différentes longueurs et nous assurer que nous obtenons le même résultat lorsque nous les passons dans le modèle sous forme de batch ou de phrases individuelles. +Comment pouvons-nous regrouper différentes phrases de différentes longueurs et nous assurer que nous obtenons le même résultat lorsque nous les passons dans le modèle sous forme de batch ou de phrases individuelles. Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter2 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/batch_inputs_pt.ipynb Version PyTorch : https://youtu.be/M6adb1j2jPI -Vidéos connexes : -- La fonction pipeline : https://youtu.be/tiZFewofSLM +Vidéos connexes : +- La fonction pipeline : https://youtu.be/tiZFewofSLM - Le pipeline de tokenisation : https://youtu.be/Yffk5aydLzg -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Vue d'ensemble de Datasets d'Hugging Face (PyTorch) +Vue d'ensemble de Datasets d'Hugging Face (PyTorch) -Une introduction rapide à la bibliothèque 🤗 Datasets : comment l'utiliser pour télécharger et prétraiter un jeu de données. +Une introduction rapide à la bibliothèque 🤗 Datasets : comment l'utiliser pour télécharger et prétraiter un jeu de données. Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter3 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/datasets_overview_pt.ipynb -TensorFlow version: https://youtu.be/W_gMJF0xomE -Vidéos connexes : -- Comment prétraiter des paires de phrases : https://youtu.be/0u3ioSwev3s 🤗 -- Documentation de Datasets: https://huggingface.co/docs/datasets/ -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +TensorFlow version: https://youtu.be/W_gMJF0xomE +Vidéos connexes : +- Comment prétraiter des paires de phrases : https://youtu.be/0u3ioSwev3s +- Documentation de 🤗 Datasets: https://huggingface.co/docs/datasets/ +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Vue d'ensemble de Datasets d'Hugging Face (Tensorflow) +Vue d'ensemble de Datasets d'Hugging Face (Tensorflow) -Une introduction rapide à la bibliothèque 🤗 Datasets : comment l'utiliser pour télécharger et prétraiter un jeu de données. +Une introduction rapide à la bibliothèque 🤗 Datasets : comment l'utiliser pour télécharger et prétraiter un jeu de données. Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter3 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/datasets_overview_pt.ipynb Version PyTorch : https://youtu.be/_BZearw7f0w -Vidéos connexes : -- Comment prétraiter des paires de phrases : https://youtu.be/0u3ioSwev3s 🤗 -- Documentation de Datasets: https://huggingface.co/docs/datasets/ -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vidéos connexes : +- Comment prétraiter des paires de phrases : https://youtu.be/0u3ioSwev3s +- Documentation de 🤗 Datasets: https://huggingface.co/docs/datasets/ +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Prétraitement de paires de phrases (PyTorch) +Prétraitement de paires de phrases (PyTorch) -Beaucoup de problèmes de classification de textes traitent de paires de phrases, comment utiliser la bibliothèque Transformers et ses tokenizers pour les prétraiter ? +Beaucoup de problèmes de classification de textes traitent de paires de phrases, comment utiliser la bibliothèque Transformers et ses tokenizers pour les prétraiter ? Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter3 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/sentence_pairs_pt.ipynb -Version TensorFlow : https://youtu.be/P-rZWqcB6CE -Vidéos connexes : -- Le pipeline de tokenisation : https://youtu.be/Yffk5aydLzg +Version TensorFlow : https://youtu.be/P-rZWqcB6CE +Vidéos connexes : +- Le pipeline de tokenisation : https://youtu.be/Yffk5aydLzg - Comment regrouper les entrées : https://youtu.be/M6adb1j2jPI -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Prétraitement de paires de phrases (TensorFlow) +Prétraitement de paires de phrases (TensorFlow) -Beaucoup de problèmes de classification de textes traitent de paires de phrases, comment utiliser la bibliothèque Transformers et ses tokenizers pour les prétraiter ? +Beaucoup de problèmes de classification de textes traitent de paires de phrases, comment utiliser la bibliothèque Transformers et ses tokenizers pour les prétraiter ? Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter3 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/sentence_pairs_pt.ipynb Version PyTorch : https://youtu.be/0u3ioSwev3s -Vidéos connexes : -- Le pipeline de tokenisation : https://youtu.be/Yffk5aydLzg +Vidéos connexes : +- Le pipeline de tokenisation : https://youtu.be/Yffk5aydLzg - Comment regrouper les entrées : https://youtu.be/M6adb1j2jPI -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Qu'est-ce que le rembourrage dynamique ? +Qu'est-ce que le rembourrage dynamique ? Qu'est-ce que le rembourrage dynamique et diffère-t-il du rembourrage fixe traditionnel ? Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter3 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/dynamic_padding.ipynb -Vidéos connexes : -- Comment prétraiter des paires de phrases : https://youtu.be/0u3ioSwev3s +Vidéos connexes : +- Comment prétraiter des paires de phrases : https://youtu.be/0u3ioSwev3s - La bibliothèque 🤗 Datasets : https://youtu.be/_BZearw7f0w -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -L'API Trainer +L'API Trainer -L'API Trainer de la bibliothèque Transformers, et comment l'utiliser pour affiner un modèle. +L'API Trainer de la bibliothèque Transformers, et comment l'utiliser pour affiner un modèle. Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter3 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/trainer_api.ipynb -Vidéos connexes : -- Comment instancier un transformer : https://youtu.be/AhChOFRegn4 -- Comment prétraiter des paires de phrases : https://youtu.be/0u3ioSwev3s -- La bibliothèque 🤗 Datasets : https://youtu.be/_BZearw7f0w +Vidéos connexes : +- Comment instancier un transformer : https://youtu.be/AhChOFRegn4 +- Comment prétraiter des paires de phrases : https://youtu.be/0u3ioSwev3s +- La bibliothèque 🤗 Datasets : https://youtu.be/_BZearw7f0w - Qu'est-ce que le rembourrage dynamique : https://youtu.be/7q5NyFT8REg -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Introduction à Keras +Introduction à Keras -Qu'est-ce que Keras et comment l'utiliser avec les transformers. +Qu'est-ce que Keras et comment l'utiliser avec les transformers. Intervenant : Matthew Carrigan Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter3 -Vidéos connexes : -- Vue d’ensemble de Datasets : https://youtu.be/W_gMJF0xomE -- Finetuning avec TensorFlow : https://youtu.be/alq1l8Lv9GA -- Plannification du taux d'apprentissage dans TensorFlow : https://youtu.be/eKv4rRcCNX0 +Vidéos connexes : +- Vue d’ensemble de Datasets : https://youtu.be/W_gMJF0xomE +- Finetuning avec TensorFlow : https://youtu.be/alq1l8Lv9GA +- Plannification du taux d'apprentissage dans TensorFlow : https://youtu.be/eKv4rRcCNX0 - Prédiction et métriques : https://youtu.be/nx10eh4CoOs -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Finetuning avec TensorFlow +Finetuning avec TensorFlow -Finetunons un transformer dans TensorFlow, en utilisant Keras. +Finetunons un transformer dans TensorFlow, en utilisant Keras. Intervenant : Matthew Carrigan Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter3 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/tensorflow_finetuning.ipynb -Vidéos connexes : -- Qu'est-ce que l'apprentissage par transfert : https://youtu.be/BqqfQnyjmgg -- Que se passe-t-il dans la fonction pipeline ? : https://youtu.be/wVN12smEvqg +Vidéos connexes : +- Qu'est-ce que l'apprentissage par transfert : https://youtu.be/BqqfQnyjmgg +- Que se passe-t-il dans la fonction pipeline ? : https://youtu.be/wVN12smEvqg - Le pipeline de tokenization : https://youtu.be/Yffk5aydLzg -- Vue d’ensemble de Datasets : https://youtu.be/W_gMJF0xomE -- Introduction à Keras : https://youtu.be/rnTGBy2ax1c -- Planification du taux d'apprentissage dans TensorFlow : https://youtu.be/eKv4rRcCNX0 +- Vue d’ensemble de Datasets : https://youtu.be/W_gMJF0xomE +- Introduction à Keras : https://youtu.be/rnTGBy2ax1c +- Planification du taux d'apprentissage dans TensorFlow : https://youtu.be/eKv4rRcCNX0 - Prédiction et métriques : https://youtu.be/nx10eh4CoOs -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Planification du taux d'apprentissage avec TensorFlow +Planification du taux d'apprentissage avec TensorFlow -Comment planifier le taux d'apprentissage en utilisant TensorFlow et Keras. +Comment planifier le taux d'apprentissage en utilisant TensorFlow et Keras. Intervenant : Matthew Carrigan Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter3 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/tf_lr_scheduling.ipynb -Vidéos connexes : +Vidéos connexes : - Introduction à Keras: https://youtu.be/rnTGBy2ax1c -- Finetuning avec TensorFlow: https://youtu.be/alq1l8Lv9GA +- Finetuning avec TensorFlow: https://youtu.be/alq1l8Lv9GA - Prediction et métriques : https://youtu.be/nx10eh4CoOs -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Prédictions et métriques TensorFlow +Prédictions et métriques TensorFlow -Utilisez TensorFlow et Keras pour calculer des prédictions avec notre modèle finetuné, et l'évaluer. +Utilisez TensorFlow et Keras pour calculer des prédictions avec notre modèle finetuné, et l'évaluer. Intervenant : Matthew Carrigan Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter3 -Vidéos connexes : -- Vue d’ensemble de Datasets : https://youtu.be/W_gMJF0xomE -- Introduction à Keras: https://youtu.be/rnTGBy2ax1c -- Finetuning avec TensorFlow: https://youtu.be/alq1l8Lv9GA +Vidéos connexes : +- Vue d’ensemble de Datasets : https://youtu.be/W_gMJF0xomE +- Introduction à Keras: https://youtu.be/rnTGBy2ax1c +- Finetuning avec TensorFlow: https://youtu.be/alq1l8Lv9GA - Planification du taux d'apprentissage avec TensorFlow : https://youtu.be/eKv4rRcCNX0 -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Écrire votre boucle d'entraînement dans PyTorch +Écrire votre boucle d'entraînement dans PyTorch -Finetunons un transformer avec PyTorch sans utiliser d'outils spéciaux. +Finetunons un transformer avec PyTorch sans utiliser d'outils spéciaux. Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter3 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/training_loop.ipynb -Vidéos connexes : -- Comment instancier un transformer : https://youtu.be/AhChOFRegn4 -- Comment prétraiter des paires de phrases : https://youtu.be/0u3ioSwev3s -- La bibliothèque 🤗 Datasets : https://youtu.be/_BZearw7f0w -- Qu'est-ce que le rembourrage dynamique : https://youtu.be/7q5NyFT8REg +Vidéos connexes : +- Comment instancier un transformer : https://youtu.be/AhChOFRegn4 +- Comment prétraiter des paires de phrases : https://youtu.be/0u3ioSwev3s +- La bibliothèque 🤗 Datasets : https://youtu.be/_BZearw7f0w +- Qu'est-ce que le rembourrage dynamique : https://youtu.be/7q5NyFT8REg - L'API Trainer : https://youtu.be/nvBXf7s7vTI -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Optimisez votre boucle d'entraînement PyTorch avec Accelerate +Optimisez votre boucle d'entraînement PyTorch avec Accelerate -Comment faire fonctionner une boucle d'entraînement sur n'importe quelle configuration distribuée avec 🤗 Accelerate. +Comment faire fonctionner une boucle d'entraînement sur n'importe quelle configuration distribuée avec 🤗 Accelerate. Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter3 -Vidéos connexes : -- L’API Trainer : https://youtu.be/nvBXf7s7vTI -- Écrire votre boucle d'entraînement en PyTorch : https://youtu.be/Dh9CL8fyG80 +Vidéos connexes : +- L’API Trainer : https://youtu.be/nvBXf7s7vTI +- Écrire votre boucle d'entraînement en PyTorch : https://youtu.be/Dh9CL8fyG80 - Documentation d’🤗 Accelerate: https://huggingface.co/docs/accelerate/ -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join Naviguer sur le Hub des modèles -Visitez le Hub des modèles d’Hugging Face ! +Visitez le Hub des modèles d’Hugging Face ! Intervenant : Lysandre Debut Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter4 -Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/git... You will need a Hugging Face account to manage a repo on the Model Hub. Join now: http://huggingface.co/join Vidéos connexes : - The push to hub API: https://youtu.be/A5IWIxsHLUw - Managing a repo on the Model Hub: https://youtu.be/rkCly_cbMBk -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/training_loop.ipynb + +Vidéos connexes : - The push to hub API: https://youtu.be/A5IWIxsHLUw - Managing a repo on the Model Hub: https://youtu.be/rkCly_cbMBk +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join Gestion d'un dépôt sur le Hub des modèles -Apprenez à gérer un dépôt sur le Hub de modèles d'Hugging Face. Cette vidéo couvre la création d'un dépôt, les ajouts de fichiers via l'interface web ainsi que via la ligne de commande. +Apprenez à gérer un dépôt sur le Hub de modèles d'Hugging Face. Cette vidéo couvre la création d'un dépôt, les ajouts de fichiers via l'interface web ainsi que via la ligne de commande. Intervenant : Lysandre Debut Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter4 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/training_loop.ipynb -Afin de pouvoir suivre cette vidéo, nous vous recommandons de vous familiariser au préalable avec git et git-lfs. Installation de git : https://git-scm.com/book/en/v2/Gettin... Installer git-lfs : https://git-lfs.github.com/ Tutoriel Git : https://git-scm.com/docs/gittutorial -Vidéos connexes : -- L'API push to hub : https://youtu.be/A5IWIxsHLUw +Afin de pouvoir suivre cette vidéo, nous vous recommandons de vous familiariser au préalable avec git et git-lfs. Installation de git : https://git-scm.com/book/en/v2/Getting-Started-Installing-Git +Installer git-lfs : https://git-lfs.github.com/ +Tutoriel Git : https://git-scm.com/docs/gittutorial +Vidéos connexes : +- L'API push to hub : https://youtu.be/A5IWIxsHLUw - Naviguer dans le Hub des modèles : https://youtu.be/XvSGPZFEjDY -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -L'API Push to Hub (PyTorch) +L'API Push to Hub (PyTorch) -Partagez facilement vos modèles finetunés sur le Hub d’Hugging Face en utilisant l'API push to hub. +Partagez facilement vos modèles finetunés sur le Hub d’Hugging Face en utilisant l'API push to hub. Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter4 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/push_to_hub_pt.ipynb - Version TensorFlow : https://youtu.be/pUh5cGmNV8Y -Vidéos connexes : -- Naviguez dans le Hub des modèles : https://youtu.be/XvSGPZFEjDY -- Comment instancier un transformer : https://youtu.be/AhChOFRegn4 + Version TensorFlow : https://youtu.be/pUh5cGmNV8Y +Vidéos connexes : +- Naviguez dans le Hub des modèles : https://youtu.be/XvSGPZFEjDY +- Comment instancier un transformer : https://youtu.be/AhChOFRegn4 - L’API Trainer : https://youtu.be/nvBXf7s7vTI -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -L'API Push to Hub (TensorFlow) +L'API Push to Hub (TensorFlow) -Partagez facilement vos modèles finetunés sur le Hub d’Hugging Face en utilisant l'API push to hub. +Partagez facilement vos modèles finetunés sur le Hub d’Hugging Face en utilisant l'API push to hub. Intervenant : Matthew Carrigan Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter4 -Version PyTorch : https://youtu.be/Zh0FfmVrKX0 -Vidéos connexes : -- Naviguez dans le Hub des modèles : https://youtu.be/XvSGPZFEjDY -- Comment instancier un transformer : https://youtu.be/AhChOFRegn4 +Version PyTorch : https://youtu.be/Zh0FfmVrKX0 +Vidéos connexes : +- Naviguez dans le Hub des modèles : https://youtu.be/XvSGPZFEjDY +- Comment instancier un transformer : https://youtu.be/AhChOFRegn4 - L’API Trainer : https://youtu.be/nvBXf7s7vTI -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Chargement d'un jeu de données personnalisé +Chargement d'un jeu de données personnalisé -Découvrez comment charger un jeu de données personnalisé avec la bibliothèque 🤗 Datasets. +Découvrez comment charger un jeu de données personnalisé avec la bibliothèque 🤗 Datasets. Intervenant : Lewis Tunstall Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter5 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/load_custom_dataset.ipynb -Vidéos connexes : -- Sauvegarde et rechargement d'un jeu de données : https://youtu.be/blF9uxYcKHo -- Découper et trancher un jeu de données 🔪 : https://youtu.be/tqfSFcPMgOI -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vidéos connexes : +- Sauvegarde et rechargement d'un jeu de données : https://youtu.be/blF9uxYcKHo +- Découper et trancher un jeu de données 🔪 : https://youtu.be/tqfSFcPMgOI +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Découper et trancher un jeu de données 🔪 +Découper et trancher un jeu de données 🔪 -Cette vidéo vous apprendra toutes les opérations de base à connaître pour préparer vos données pour l'entraînement avec la bibliothèque 🤗 Datasets. +Cette vidéo vous apprendra toutes les opérations de base à connaître pour préparer vos données pour l'entraînement avec la bibliothèque 🤗 Datasets. Intervenant : Lewis Tunstall Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter5 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/slice_and_dice.ipynb -Vidéos connexes : -- Chargement d'un jeu de données personnalisé : https://youtu.be/HyQgpJTkRdE -- Sauvegarde et rechargement d'un jeu de données : https://youtu.be/blF9uxYcKHo -- Datasets + DataFrames = ❤️ : https://youtu.be/tfcY1067A5Q -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vidéos connexes : +- Chargement d'un jeu de données personnalisé : https://youtu.be/HyQgpJTkRdE +- Sauvegarde et rechargement d'un jeu de données : https://youtu.be/blF9uxYcKHo +- Datasets + DataFrames = ❤️ : https://youtu.be/tfcY1067A5Q +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Datasets + DataFrames = ❤️ +Datasets + DataFrames = ❤️ -Convertir un jeu de données de la bibliothèque 🤗 Datasets en un DataFrame pandas et inversement est très facile ! Cette vidéo vous montrera comment ces deux classes interagissent entre elles de manière transparente. +Convertir un jeu de données de la bibliothèque 🤗 Datasets en un DataFrame pandas et inversement est très facile ! Cette vidéo vous montrera comment ces deux classes interagissent entre elles de manière transparente. Intervenant : Lewis Tunstall Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter5 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/datasets_and_dataframes.ipynb -Vidéos connexes : -- Chargement d'un jeu de données personnalisé : https://youtu.be/HyQgpJTkRdE -- Découper et trancher un jeu de données 🔪 : https://youtu.be/tqfSFcPMgOI -- Sauvegarde et rechargement d'un jeu de données : https://youtu.be/blF9uxYcKHo -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vidéos connexes : +- Chargement d'un jeu de données personnalisé : https://youtu.be/HyQgpJTkRdE +- Découper et trancher un jeu de données 🔪 : https://youtu.be/tqfSFcPMgOI +- Sauvegarde et rechargement d'un jeu de données : https://youtu.be/blF9uxYcKHo +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Sauvegarde et rechargement d'un jeu de données +Sauvegarde et rechargement d'un jeu de données -Apprenez à enregistrer votre jeu de données et à le recharger ultérieurement avec la bibliothèque 🤗 Datasets. +Apprenez à enregistrer votre jeu de données et à le recharger ultérieurement avec la bibliothèque 🤗 Datasets. Intervenant : Lewis Tunstall Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter5 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/save_load_dataset.ipynb Vidéos connexes : -- Chargement d'un jeu de données personnalisé : https://youtu.be/HyQgpJTkRdE -- Découper et trancher un jeu de données 🔪 : https://youtu.be/tqfSFcPMgOI -- Téléchargement d'un jeu de données sur le Hub : https://youtu.be/HaN6qCr_Afc -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +- Chargement d'un jeu de données personnalisé : https://youtu.be/HyQgpJTkRdE +- Découper et trancher un jeu de données 🔪 : https://youtu.be/tqfSFcPMgOI +- Téléchargement d'un jeu de données sur le Hub : https://youtu.be/HaN6qCr_Afc +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Association de la mémoire et streaming +Association de la mémoire et streaming -La bibliothèque 🤗 Datasets vous permet d'utiliser et de traiter des jeux de données qui ne tiennent pas dans la mémoire vive. Découvrez comment elle peut le faire grâce à l’association mémoire et comment utiliser la fonctionnalité de streaming. +La bibliothèque 🤗 Datasets vous permet d'utiliser et de traiter des jeux de données qui ne tiennent pas dans la mémoire vive. Découvrez comment elle peut le faire grâce à l’association mémoire et comment utiliser la fonctionnalité de streaming. Intervenant : Lewis Tunstall Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter5 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/memory_mapping_streaming.ipynb -Vidéos connexes : -- Chargement d'un jeu de données personnalisé : https://youtu.be/HyQgpJTkRdE -- Découper et trancher un jeu de données 🔪 : https://youtu.be/tqfSFcPMgOI -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vidéos connexes : +- Chargement d'un jeu de données personnalisé : https://youtu.be/HyQgpJTkRdE +- Découper et trancher un jeu de données 🔪 : https://youtu.be/tqfSFcPMgOI +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Téléchargement d'un jeu de données vers le Hub +Téléchargement d'un jeu de données vers le Hub Dans cette vidéo, vous apprendrez à télécharger vos propres jeux de données sur le Hub. Intervenant : Lewis Tunstall Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter5 -Vidéos connexes : -- Chargement d'un jeu de données personnalisé : https://youtu.be/HyQgpJTkRdE -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vidéos connexes : +- Chargement d'un jeu de données personnalisé : https://youtu.be/HyQgpJTkRdE +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Enchâssement de texte et recherche sémantique +Enchâssement de texte et recherche sémantique -Découvrez comment les transformers peuvent être utilisés pour représenter des documents et des requêtes sous forme de vecteurs appelés enchâssements. Dans cette vidéo, nous appliquons cette technique pour créer un moteur de recherche sémantique ! +Découvrez comment les transformers peuvent être utilisés pour représenter des documents et des requêtes sous forme de vecteurs appelés enchâssements. Dans cette vidéo, nous appliquons cette technique pour créer un moteur de recherche sémantique ! Intervenant : Lewis Tunstall Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter5 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/semantic_search.ipynb -Vidéos connexes : -- Chargement d'un jeu de données personnalisé : https://youtu.be/HyQgpJTkRdE -- Découper et trancher un jeu de données 🔪 : https://youtu.be/tqfSFcPMgOI -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vidéos connexes : +- Chargement d'un jeu de données personnalisé : https://youtu.be/HyQgpJTkRdE +- Découper et trancher un jeu de données 🔪 : https://youtu.be/tqfSFcPMgOI +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join Entraîner un nouveau tokenizer -Vous êtes-vous déjà demandé comment créer un tokenizer BERT ou GPT2 dans votre propre langue ou sur votre propre corpus ? Cette vidéo vous apprendra à le faire avec n'importe quel tokenizer de la bibliothèque 🤗 Transformers. +Vous êtes-vous déjà demandé comment créer un tokenizer BERT ou GPT2 dans votre propre langue ou sur votre propre corpus ? Cette vidéo vous apprendra à le faire avec n'importe quel tokenizer de la bibliothèque 🤗 Transformers. Intervenant : Lucile Saulnier Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter5 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/train_new_tokenizer.ipynb -Vidéos connexes : +Vidéos connexes : - Construire un nouveau tokenizer : https://youtu.be/MR8tZm5ViWU -Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Pourquoi les tokenizers rapides sont-ils appelés rapides ? +Pourquoi les tokenizers rapides sont-ils appelés rapides ? -Les tokenizers rapides sont rapides, mais de combien exactement ? Cette vidéo vous le dira. +Les tokenizers rapides sont rapides, mais de combien exactement ? Cette vidéo vous le dira. Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter6 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/fast_tokenizers.ipynb -Vidéos connexes : -- Les superpouvoirs des tokenizers rapides : https://youtu.be/3umI3tm27Vw -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vidéos connexes : +- Les superpouvoirs des tokenizers rapides : https://youtu.be/3umI3tm27Vw +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Les superpouvoirs des tokenizers rapides +Les superpouvoirs des tokenizers rapides -Les tokenizers rapides sont rapides, mais ils disposent également de fonctionnalités supplémentaires permettant de faire correspondre les tokens aux mots dont ils proviennent ou à la portée originale des caractères dans le texte brut. Cette vidéo explore ces fonctionnalités. +Les tokenizers rapides sont rapides, mais ils disposent également de fonctionnalités supplémentaires permettant de faire correspondre les tokens aux mots dont ils proviennent ou à la portée originale des caractères dans le texte brut. Cette vidéo explore ces fonctionnalités. Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter6 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/offset_mapping.ipynb -Vidéos connexes : -- Pourquoi les tokenizers rapides sont-ils appelés rapides ? : https://youtu.be/g8quOxoqhHQ -- Entraîner un nouveau tokenizer: https://youtu.be/DJimQynXZsQ -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vidéos connexes : +- Pourquoi les tokenizers rapides sont-ils appelés rapides ? : https://youtu.be/g8quOxoqhHQ +- Entraîner un nouveau tokenizer: https://youtu.be/DJimQynXZsQ +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Dans le pipeline de classification de tokens (PyTorch) +Dans le pipeline de classification de tokens (PyTorch) -Que se passe-t-il dans le pipeline de classification de tokens, et comment passe-t-on des logits aux étiquettes d'entités ? Cette vidéo vous le montrera. +Que se passe-t-il dans le pipeline de classification de tokens, et comment passe-t-on des logits aux étiquettes d'entités ? Cette vidéo vous le montrera. Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter6 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/token_pipeline_pt.ipynb -Version TensorFlow : https://youtu.be/0E7ltQB7fM8 -Vidéos connexes : -- Que se passe-t-il à l'intérieur de la fonction pipeline ? : https://youtu.be/1pedAIvTWXk -- Les superpouvoirs des tokenizers rapides : https://youtu.be/3umI3tm27Vw -- Traitement des données pour la classification de tokens : https://youtu.be/iY2AZYdZAr0 -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Version TensorFlow : https://youtu.be/0E7ltQB7fM8 +Vidéos connexes : +- Que se passe-t-il à l'intérieur de la fonction pipeline ? : https://youtu.be/1pedAIvTWXk +- Les superpouvoirs des tokenizers rapides : https://youtu.be/3umI3tm27Vw +- Traitement des données pour la classification de tokens : https://youtu.be/iY2AZYdZAr0 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Dans le pipeline de classification de tokens (TensorFlow) +Dans le pipeline de classification de tokens (TensorFlow) -Que se passe-t-il dans le pipeline de classification de tokens, et comment passe-t-on des logits aux étiquettes d'entités ? Cette vidéo vous le montrera. +Que se passe-t-il dans le pipeline de classification de tokens, et comment passe-t-on des logits aux étiquettes d'entités ? Cette vidéo vous le montrera. Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter6 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/token_pipeline_pt.ipynb -Version PyTorch : https://youtu.be/0E7ltQB7fM8 -Vidéos connexes : -- Que se passe-t-il à l'intérieur de la fonction pipeline ? : https://youtu.be/1pedAIvTWXk -- Les superpouvoirs des tokenizers rapides : https://youtu.be/3umI3tm27Vw -- Traitement des données pour la classification de tokens : https://youtu.be/iY2AZYdZAr0 -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Version PyTorch : https://youtu.be/0E7ltQB7fM8 +Vidéos connexes : +- Que se passe-t-il à l'intérieur de la fonction pipeline ? : https://youtu.be/1pedAIvTWXk +- Les superpouvoirs des tokenizers rapides : https://youtu.be/3umI3tm27Vw +- Traitement des données pour la classification de tokens : https://youtu.be/iY2AZYdZAr0 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Dans le pipeline de réponse aux questions (PyTorch) +Dans le pipeline de réponse aux questions (PyTorch) -Comment le pipeline de réponse aux questions fonctionne-t-il réellement ? Dans cette vidéo, nous explorons comment nous passons des prédictions du modèle à la recherche de la réponse dans le contexte initial. +Comment le pipeline de réponse aux questions fonctionne-t-il réellement ? Dans cette vidéo, nous explorons comment nous passons des prédictions du modèle à la recherche de la réponse dans le contexte initial. Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter6 -Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/git... -Version TensorFlow : https://youtu.be/b3u8RzBCX9Y -Vidéos connexes : -- Que se passe-t-il à l'intérieur de la fonction pipeline ? : https://youtu.be/1pedAIvTWXk -- Les superpouvoirs des tokenizers rapides : https://youtu.be/3umI3tm27Vw -- Traitement des données pour la réponse aux questions : https://youtu.be/qgaM0weJHpA -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/git... +Version TensorFlow : https://youtu.be/b3u8RzBCX9Y +Vidéos connexes : +- Que se passe-t-il à l'intérieur de la fonction pipeline ? : https://youtu.be/1pedAIvTWXk +- Les superpouvoirs des tokenizers rapides : https://youtu.be/3umI3tm27Vw +- Traitement des données pour la réponse aux questions : https://youtu.be/qgaM0weJHpA +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -A l'intérieur du pipeline de réponse aux questions (TensorFlow) +A l'intérieur du pipeline de réponse aux questions (TensorFlow) -Comment le pipeline de réponse aux questions fonctionne-t-il réellement ? Dans cette vidéo, nous explorons comment nous passons des prédictions du modèle à la recherche de la réponse dans le contexte initial. +Comment le pipeline de réponse aux questions fonctionne-t-il réellement ? Dans cette vidéo, nous explorons comment nous passons des prédictions du modèle à la recherche de la réponse dans le contexte initial. Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter6 -Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/git... -Version PyTorch : https://youtu.be/_wxyB3j3mk4 +Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/git... +Version PyTorch : https://youtu.be/_wxyB3j3mk4 Vidéos connexes : -- Que se passe-t-il à l'intérieur de la fonction pipeline ? : https://youtu.be/1pedAIvTWXk -- Les superpouvoirs des tokenizers rapides : https://youtu.be/3umI3tm27Vw -- Traitement des données pour la réponse aux questions : https://youtu.be/qgaM0weJHpA -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +- Que se passe-t-il à l'intérieur de la fonction pipeline ? : https://youtu.be/1pedAIvTWXk +- Les superpouvoirs des tokenizers rapides : https://youtu.be/3umI3tm27Vw +- Traitement des données pour la réponse aux questions : https://youtu.be/qgaM0weJHpA +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Qu'est-ce que la normalisation ? +Qu'est-ce que la normalisation ? -La première étape de la tokenisation des textes est appelée normalisation. Mais qu'est-ce que cela signifie ? Cette vidéo vous dira tout à ce sujet. +La première étape de la tokenisation des textes est appelée normalisation. Mais qu'est-ce que cela signifie ? Cette vidéo vous dira tout à ce sujet. Intervenant : Lucile Saulnier Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter6 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/normalization.ipynb -Vidéos connexes : -- Qu'est-ce que la prétokénisation ? https://youtu.be/grlLV8AIXug -- Entraîner un nouveau tokenizer : https://youtu.be/DJimQynXZsQ -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vidéos connexes : +- Qu'est-ce que la prétokénisation ? https://youtu.be/grlLV8AIXug +- Entraîner un nouveau tokenizer : https://youtu.be/DJimQynXZsQ +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Qu'est-ce que la prétokénisation ? +Qu'est-ce que la prétokénisation ? -La prétokénisation est la deuxième étape de la tokénisation des textes. Mais qu'est-ce que cela signifie ? Cette vidéo vous dira tout à ce sujet. +La prétokénisation est la deuxième étape de la tokénisation des textes. Mais qu'est-ce que cela signifie ? Cette vidéo vous dira tout à ce sujet. Intervenant : Lucile Saulnier Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter6 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/pre_tokenization.ipynb -Vidéos connexes : -- Qu’est-ce que la normalization ? https://youtu.be/4IIC2jI9CaU -- Entraîner un nouveau tokenizer : https://youtu.be/DJimQynXZsQ -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vidéos connexes : +- Qu’est-ce que la normalization ? https://youtu.be/4IIC2jI9CaU +- Entraîner un nouveau tokenizer : https://youtu.be/DJimQynXZsQ +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join Tokenisation Byte Pair Encoding -Cette vidéo vous apprendra tout ce qu'il y a à savoir sur l'algorithme de tokenisation Byte Pair Encoding. Comment il est entraîné sur un corpus de textes et comment il est appliqué pour tokeniser des textes. +Cette vidéo vous apprendra tout ce qu'il y a à savoir sur l'algorithme de tokenisation Byte Pair Encoding. Comment il est entraîné sur un corpus de textes et comment il est appliqué pour tokeniser des textes. Intervenant : Lucile Saulnier Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter6 -Vidéos connexes : -- Tokenisation Unigram : https://youtu.be/TGZfZVuF9Yc -- Tokenisation WordPiece : https://youtu.be/qpv6ms_t_1A -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vidéos connexes : +- Tokenisation Unigram : https://youtu.be/TGZfZVuF9Yc +- Tokenisation WordPiece : https://youtu.be/qpv6ms_t_1A +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join Tokénisation WordPiece - -Cette vidéo vous apprendra tout ce qu'il y a à savoir sur l'algorithme de tokenisation WordPiece. Comment il est entraîné sur un corpus de textes et comment il est appliqué pour tokeniser des textes. + +Cette vidéo vous apprendra tout ce qu'il y a à savoir sur l'algorithme de tokenisation WordPiece. Comment il est entraîné sur un corpus de textes et comment il est appliqué pour tokeniser des textes. Intervenant : Lucile Saulnier Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter6 -Vidéos connexes : -- Tokenisation Byte Pair Encoding : https://youtu.be/HEikzVL-lZU -- Tokenisation Unigram : https://youtu.be/TGZfZVuF9Yc -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vidéos connexes : +- Tokenisation Byte Pair Encoding : https://youtu.be/HEikzVL-lZU +- Tokenisation Unigram : https://youtu.be/TGZfZVuF9Yc +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Tokenization Unigram +Tokenization Unigram -Cette vidéo vous apprendra tout ce qu'il y a à savoir sur l'algorithme de tokenisation Unigram. Comment il est entraîné sur un corpus de textes et comment il est appliqué pour tokeniser des textes. +Cette vidéo vous apprendra tout ce qu'il y a à savoir sur l'algorithme de tokenisation Unigram. Comment il est entraîné sur un corpus de textes et comment il est appliqué pour tokeniser des textes. Intervenant : Lucile Saulnier Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter6 Vidéos connexes : -- Tokenisation Byte Pair Encoding : https://youtu.be/HEikzVL-lZU -- Tokenisation WordPiece : https://youtu.be/qpv6ms_t_1A -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +- Tokenisation Byte Pair Encoding : https://youtu.be/HEikzVL-lZU +- Tokenisation WordPiece : https://youtu.be/qpv6ms_t_1A +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Construction d'un nouveau tokenizer +Construction d'un nouveau tokenizer -Découvrez comment utiliser la bibliothèque 🤗 Tokenizers pour construire votre propre tokenizer, l'entraîner, puis comment l'utiliser dans la bibliothèque 🤗 Transformers. +Découvrez comment utiliser la bibliothèque 🤗 Tokenizers pour construire votre propre tokenizer, l'entraîner, puis comment l'utiliser dans la bibliothèque 🤗 Transformers. Intervenant : Lucile Saulnier Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter6 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/building_tokenizer.ipynb -Vidéos connexes : +Vidéos connexes : - Entraîner un nouveau tokenizer : https://youtu.be/DJimQynXZsQ -- Tokenisation Byte Pair Encoding : https://youtu.be/HEikzVL-lZU -- Tokenisation WordPiece : https://youtu.be/qpv6ms_t_1A -- Tokenisation Unigram : https://youtu.be/TGZfZVuF9Yc -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +- Tokenisation Byte Pair Encoding : https://youtu.be/HEikzVL-lZU +- Tokenisation WordPiece : https://youtu.be/qpv6ms_t_1A +- Tokenisation Unigram : https://youtu.be/TGZfZVuF9Yc +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Traitement des données pour la classification de tokens +Traitement des données pour la classification de tokens -Cette vidéo vous explique comment prétraiter un jeu de données pour une tâche de classification de tokens. +Cette vidéo vous explique comment prétraiter un jeu de données pour une tâche de classification de tokens. Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter7 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/token_processing.ipynb -Vidéos connexes : -- 🤗 Tâches : classification de tokens : https://youtu.be/wVHdVlPScxA -- À l'intérieur du pipeline de classification de tokens (PyTorch): https://youtu.be/0E7ltQB7fM8 -- À l'intérieur du pipeline de classification de tokens (TensorFlow): https://youtu.be/PrX4CjrVnNc -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vidéos connexes : +- 🤗 Tâches : classification de tokens : https://youtu.be/wVHdVlPScxA +- À l'intérieur du pipeline de classification de tokens (PyTorch): https://youtu.be/0E7ltQB7fM8 +- À l'intérieur du pipeline de classification de tokens (TensorFlow): https://youtu.be/PrX4CjrVnNc +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Traitement des données pour la modélisation du langage masqué +Traitement des données pour la modélisation du langage masqué -Comment prétraiter un jeu de données pour une tâche de modélisation du langage masqué, par exemple pour remplir les blancs d'une phrase. +Comment prétraiter un jeu de données pour une tâche de modélisation du langage masqué, par exemple pour remplir les blancs d'une phrase. Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter7 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/mlm_processing.ipynb -Vidéos connexes : -- 🤗 Tâches : modélisation du langage masqué : https://youtu.be/mqElG5QJWUg -- Finetuning avec TensorFlow: https://youtu.be/AUozVp78dhk -- L’API Trainer : https://youtu.be/nvBXf7s7vTI -- Écrire votre boucle d'entraînement en PyTorch: https://youtu.be/Dh9CL8fyG80 -- Optimisez votre boucle d'entraînement PyTorch avec Accelerate : https://youtu.be/s7dy8QRgjJ0 -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vidéos connexes : +- 🤗 Tâches : modélisation du langage masqué : https://youtu.be/mqElG5QJWUg +- Finetuning avec TensorFlow: https://youtu.be/AUozVp78dhk +- L’API Trainer : https://youtu.be/nvBXf7s7vTI +- Écrire votre boucle d'entraînement en PyTorch: https://youtu.be/Dh9CL8fyG80 +- Optimisez votre boucle d'entraînement PyTorch avec Accelerate : https://youtu.be/s7dy8QRgjJ0 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Qu'est-ce que la perplexité ? +Qu'est-ce que la perplexité ? -Les modèles de langage sont souvent évalués à l'aide d'une métrique appelée perplexité. Vous vous sentez perplexe à ce sujet ? Regardez cette vidéo pour tout savoir. +Les modèles de langage sont souvent évalués à l'aide d'une métrique appelée perplexité. Vous vous sentez perplexe à ce sujet ? Regardez cette vidéo pour tout savoir. Intervenant : Leandro von Werra Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter7 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/perplexity.ipynb -Vidéos connexes : -- 🤗 Tâches : modélisation du langage causal : https://youtu.be/Vpjb1lu0MDk +Vidéos connexes : +- 🤗 Tâches : modélisation du langage causal : https://youtu.be/Vpjb1lu0MDk - 🤗 Tâches : modélisation du langage masqué : https://youtu.be/mqElG5QJWUg -- Traitement des données pour la modélisation du langage causal: https://youtu.be/ma1TrR7gE7I -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +- Traitement des données pour la modélisation du langage causal: https://youtu.be/ma1TrR7gE7I +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Qu'est-ce que l'adaptation au domaine ? +Qu'est-ce que l'adaptation au domaine ? -Dans cette vidéo, nous expliquons ce qu'est l'adaptation au domaine et nous examinons des exemples de versions finetunées de modèles qui se sont adaptés à leur corpus de finetuning. +Dans cette vidéo, nous expliquons ce qu'est l'adaptation au domaine et nous examinons des exemples de versions finetunées de modèles qui se sont adaptés à leur corpus de finetuning. Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter7 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/domain_adaptation.ipynb -Vidéos connexes : -- Qu'est-ce que l'apprentissage par transfert ? : https://youtu.be/BqqfQnyjmgg -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vidéos connexes : +- Qu'est-ce que l'apprentissage par transfert ? : https://youtu.be/BqqfQnyjmgg +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Traitement des données pour la traduction +Traitement des données pour la traduction -Comment prétraiter un jeu de données pour une tâche de traduction ? Cette vidéo vous aidera à le faire. +Comment prétraiter un jeu de données pour une tâche de traduction ? Cette vidéo vous aidera à le faire. Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter7 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/translation_processing.ipynb Vidéos connexes : -- 🤗 Tâches : traduction : https://youtu.be/1JvfrvZgi6c -- Traitement des données pour le résumé de texte : https://youtu.be/1m7BerpSq8A -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +- 🤗 Tâches : traduction : https://youtu.be/1JvfrvZgi6c +- Traitement des données pour le résumé de texte : https://youtu.be/1m7BerpSq8A +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Qu'est-ce que la métrique BLEU ? +Qu'est-ce que la métrique BLEU ? -La métrique BLEU est souvent utilisée pour évaluer les modèles de traduction. Cette vidéo vous explique comment elle fonctionne. +La métrique BLEU est souvent utilisée pour évaluer les modèles de traduction. Cette vidéo vous explique comment elle fonctionne. Intervenant : Lewis Tunstall Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter7 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/bleu_metric.ipynb -Vidéos connexes : -- 🤗 Tâches : traduction : https://youtu.be/1JvfrvZgi6c -- Traitement des données pour la traduction : https://youtu.be/XAR8jnZZuUs -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vidéos connexes : +- 🤗 Tâches : traduction : https://youtu.be/1JvfrvZgi6c +- Traitement des données pour la traduction : https://youtu.be/XAR8jnZZuUs +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Traitement des données pour le résumé +Traitement des données pour le résumé -Cette vidéo montre comment prétraiter des données pour une tâche de résumé. +Cette vidéo montre comment prétraiter des données pour une tâche de résumé. Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter7 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/summarization_processing.ipynb -Vidéos connexes : -- 🤗 Tâches : Résumé de textes : https://youtu.be/yHnr5Dk2zCI +Vidéos connexes : +- 🤗 Tâches : Résumé de textes : https://youtu.be/yHnr5Dk2zCI - Traitement des données pour la traduction : https://youtu.be/XAR8jnZZuUs - Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 + Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Qu'est-ce que la métrique ROUGE ? +Qu'est-ce que la métrique ROUGE ? -La métrique ROUGE est souvent utilisée dans les tâches de compression, mais comment est-elle calculée exactement ? +La métrique ROUGE est souvent utilisée dans les tâches de compression, mais comment est-elle calculée exactement ? Intervenant : Lewis Tunstall Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter7 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/rouge_metric.ipynb -Vidéos connexes : -- 🤗 Tâches : Résumé de textes : https://youtu.be/yHnr5Dk2zCI +Vidéos connexes : +- 🤗 Tâches : Résumé de textes : https://youtu.be/yHnr5Dk2zCI - Traitement des données pour la traduction : https://youtu.be/XAR8jnZZuUs -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Traitement des données pour la modélisation causale du langage +Traitement des données pour la modélisation causale du langage -Dans cette vidéo, nous allons voir comment prétraiter un jeu de données pour une tâche de modélisation causale du langage. +Dans cette vidéo, nous allons voir comment prétraiter un jeu de données pour une tâche de modélisation causale du langage. Intervenant : Leandro von Werra Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter7 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/clm_processing.ipynb Vidéos connexes : -- Finetuning avec TensorFlow : https://youtu.be/AUozVp78dhk -- L’API Trainer : https://youtu.be/nvBXf7s7vTI -- Écrivez votre boucle d'entraînement dans PyTorch : https://youtu.be/Dh9CL8fyG80 -- Optimisez votre boucle d'entraînement PyTorch avec Accelerate : https://youtu.be/s7dy8QRgjJ0 -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +- Finetuning avec TensorFlow : https://youtu.be/AUozVp78dhk +- L’API Trainer : https://youtu.be/nvBXf7s7vTI +- Écrivez votre boucle d'entraînement dans PyTorch : https://youtu.be/Dh9CL8fyG80 +- Optimisez votre boucle d'entraînement PyTorch avec Accelerate : https://youtu.be/s7dy8QRgjJ0 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join Utilisation d'une fonction de perte personnalisée -Dans cette vidéo, nous allons voir comment utiliser une fonction de perte personnalisée. La plupart des modèles de 🤗 Transformers renvoient automatiquement la perte lorsque vous leur fournissez des étiquettes, mais parfois, vous ne voulez pas la perte par défaut. Nous montrons ici comment pondérer dynamiquement les échantillons dans la perte en utilisant l'API Trainer et 🤗 Accelerate. +Dans cette vidéo, nous allons voir comment utiliser une fonction de perte personnalisée. La plupart des modèles de 🤗 Transformers renvoient automatiquement la perte lorsque vous leur fournissez des étiquettes, mais parfois, vous ne voulez pas la perte par défaut. Nous montrons ici comment pondérer dynamiquement les échantillons dans la perte en utilisant l'API Trainer et 🤗 Accelerate. Intervenant : Leandro von Werra Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter7 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/custom_loss.ipynb -Vidéos connexes : -- L’API Trainer : https://youtu.be/nvBXf7s7vTI -- Écrivez votre boucle d'entraînement dans PyTorch : https://youtu.be/Dh9CL8fyG80 -- Optimisez votre boucle d'entraînement PyTorch avec Accelerate : https://youtu.be/s7dy8QRgjJ0 -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vidéos connexes : +- L’API Trainer : https://youtu.be/nvBXf7s7vTI +- Écrivez votre boucle d'entraînement dans PyTorch : https://youtu.be/Dh9CL8fyG80 +- Optimisez votre boucle d'entraînement PyTorch avec Accelerate : https://youtu.be/s7dy8QRgjJ0 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Traitement des données pour la réponse aux questions +Traitement des données pour la réponse aux questions -Cette vidéo explore comment prétraiter un jeu de données pour la réponse aux questions et le préparer pour un transformer. +Cette vidéo explore comment prétraiter un jeu de données pour la réponse aux questions et le préparer pour un transformer. Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter7 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/qa_processing.ipynb -Vidéos connexes : -- 🤗 Tâches : Réponse aux questions : https://youtu.be/ajPx5LwJD-I -- L'étape de post-traitement dans la réponse aux questions (PyTorch) : https://youtu.be/BNy08iIWVJM -- L'étape de post-traitement dans la réponse aux questions (TensorFlow) : https://youtu.be/VN67ZpN33Ss -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vidéos connexes : +- 🤗 Tâches : Réponse aux questions : https://youtu.be/ajPx5LwJD-I +- L'étape de post-traitement dans la réponse aux questions (PyTorch) : https://youtu.be/BNy08iIWVJM +- L'étape de post-traitement dans la réponse aux questions (TensorFlow) : https://youtu.be/VN67ZpN33Ss +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -L'étape de post-traitement en réponse aux questions (PyTorch) +L'étape de post-traitement en réponse aux questions (PyTorch) -L'évaluation dans les tâches de réponse aux questions peut être délicate car il est difficile de convertir la sortie du modèle en réponses dans les contextes originaux. Cette vidéo va (espérons-le) rendre les choses plus claires ! +L'évaluation dans les tâches de réponse aux questions peut être délicate car il est difficile de convertir la sortie du modèle en réponses dans les contextes originaux. Cette vidéo va (espérons-le) rendre les choses plus claires ! Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter7 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/qa_postprocessing_pt.ipynb -Version TensorFlow: https://youtu.be/VN67ZpN33Ss -Vidéos connexes : -- Traitement des données pour la réponse aux questions : https://youtu.be/qgaM0weJHpA -- Dans le pipeline de réponse aux questions : https://youtu.be/_wxyB3j3mk4 -- Les superpouvoirs des tokenizers rapides : https://youtu.be/3umI3tm27Vw -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Version TensorFlow: https://youtu.be/VN67ZpN33Ss +Vidéos connexes : +- Traitement des données pour la réponse aux questions : https://youtu.be/qgaM0weJHpA +- Dans le pipeline de réponse aux questions : https://youtu.be/_wxyB3j3mk4 +- Les superpouvoirs des tokenizers rapides : https://youtu.be/3umI3tm27Vw +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -L'étape de post-traitement en réponse aux questions (TensorFlow) +L'étape de post-traitement en réponse aux questions (TensorFlow) -L'évaluation dans les tâches de réponse aux questions peut être délicate car il est difficile de convertir la sortie du modèle en réponses dans les contextes originaux. Cette vidéo va (espérons-le) rendre les choses plus claires ! +L'évaluation dans les tâches de réponse aux questions peut être délicate car il est difficile de convertir la sortie du modèle en réponses dans les contextes originaux. Cette vidéo va (espérons-le) rendre les choses plus claires ! Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter7 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/qa_postprocessing_tf.ipynb Version PyTorch : https://youtu.be/BNy08iIWVJM -Vidéos connexes : -- Traitement des données pour la réponse aux questions : https://youtu.be/qgaM0weJHpA -- Dans le pipeline de réponse aux questions : https://youtu.be/_wxyB3j3mk4 -- Les superpouvoirs des tokenizers rapides : https://youtu.be/3umI3tm27Vw -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vidéos connexes : +- Traitement des données pour la réponse aux questions : https://youtu.be/qgaM0weJHpA +- Dans le pipeline de réponse aux questions : https://youtu.be/_wxyB3j3mk4 +- Les superpouvoirs des tokenizers rapides : https://youtu.be/3umI3tm27Vw +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Assembleurs de données : une visite +Assembleurs de données : une visite -La bibliothèque 🤗 Transformers fournit de nombreux assembleurs de données que vous pouvez utiliser pour regrouper vos échantillons dans un batch. Si vous êtes perdu entre toutes les possibilités, cette vidéo est pour vous ! +La bibliothèque 🤗 Transformers fournit de nombreux assembleurs de données que vous pouvez utiliser pour regrouper vos échantillons dans un batch. Si vous êtes perdu entre toutes les possibilités, cette vidéo est pour vous ! Intervenant : Matthew Carrigan Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter7 -Vidéos connexes : -- Finetuning avec TensorFlow : https://youtu.be/AUozVp78dhk -- L’API Trainer : https://youtu.be/nvBXf7s7vTI -- Écrivez votre boucle d'entraînement dans PyTorch : https://youtu.be/Dh9CL8fyG80 -- Optimisez votre boucle d'entraînement PyTorch avec Accelerate : https://youtu.be/s7dy8QRgjJ0 -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vidéos connexes : +- Finetuning avec TensorFlow : https://youtu.be/AUozVp78dhk +- L’API Trainer : https://youtu.be/nvBXf7s7vTI +- Écrivez votre boucle d'entraînement dans PyTorch : https://youtu.be/Dh9CL8fyG80 +- Optimisez votre boucle d'entraînement PyTorch avec Accelerate : https://youtu.be/s7dy8QRgjJ0 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join Que faire lorsque vous obtenez une erreur ? -Vous vous sentez dépassé par ce message d'erreur que Python vient de vous envoyer ? Pas de panique, nous allons y voir plus clair ensemble. +Vous vous sentez dépassé par ce message d'erreur que Python vient de vous envoyer ? Pas de panique, nous allons y voir plus clair ensemble. Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter8 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/debug_error.ipynb -Vidéos connexes : -- Utilisation d'un débogueur dans un notebook : https://youtu.be/rSPyvPw0p9k -- Utilisation d'un débogueur dans un terminal : https://youtu.be/5PkZ4rbHL6c -- Déboguer le pipeline d'entraînement (PyTorch): https://youtu.be/L-WSwUWde1U -- Déboguer le pipeline d'entraînement (TensorFlow): https://youtu.be/N9kO52itd0Q -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vidéos connexes : +- Utilisation d'un débogueur dans un notebook : https://youtu.be/rSPyvPw0p9k +- Utilisation d'un débogueur dans un terminal : https://youtu.be/5PkZ4rbHL6c +- Déboguer le pipeline d'entraînement (PyTorch): https://youtu.be/L-WSwUWde1U +- Déboguer le pipeline d'entraînement (TensorFlow): https://youtu.be/N9kO52itd0Q +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Utilisation d'un débogueur dans un notebook +Utilisation d'un débogueur dans un notebook -Saviez-vous qu'il existe un débogueur Python que vous pouvez facilement utiliser dans un Jupyter Notebook ou un Google Colab ? Laissez-nous vous montrer comment l'utiliser. +Saviez-vous qu'il existe un débogueur Python que vous pouvez facilement utiliser dans un Jupyter Notebook ou un Google Colab ? Laissez-nous vous montrer comment l'utiliser. Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter8 Ouvrir les codes de la vidéo dans Colab : https://colab.research.google.com/github/huggingface/notebooks/blob/master/course/videos/debug_notebook.ipynb -Vidéos connexes : -- Que faire lorsque vous obtenez une erreur ? https://youtu.be/DQ-CpJn6Rc4 -- Utilisation d'un débogueur dans un terminal : https://youtu.be/5PkZ4rbHL6c -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vidéos connexes : +- Que faire lorsque vous obtenez une erreur ? https://youtu.be/DQ-CpJn6Rc4 +- Utilisation d'un débogueur dans un terminal : https://youtu.be/5PkZ4rbHL6c +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Utiliser un débogueur dans un terminal +Utiliser un débogueur dans un terminal -Saviez-vous qu'il existe un débogueur Python que vous pouvez facilement utiliser en ligne de commande ? Laissez-nous vous montrer comment l'utiliser. +Saviez-vous qu'il existe un débogueur Python que vous pouvez facilement utiliser en ligne de commande ? Laissez-nous vous montrer comment l'utiliser. Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter8 -Vidéos connexes : -- Que faire lorsque vous obtenez une erreur ? https://youtu.be/DQ-CpJn6Rc4 -- Utilisation d'un débogueur dans un notebook : https://youtu.be/rSPyvPw0p9k -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vidéos connexes : +- Que faire lorsque vous obtenez une erreur ? https://youtu.be/DQ-CpJn6Rc4 +- Utilisation d'un débogueur dans un notebook : https://youtu.be/rSPyvPw0p9k +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Demander de l'aide sur les forums +Demander de l'aide sur les forums -Cette vidéo vous montrera comment poser au mieux une question sur les 🤗 Forums, et maximiser les chances d'obtenir une réponse rapide. +Cette vidéo vous montrera comment poser au mieux une question sur les 🤗 Forums, et maximiser les chances d'obtenir une réponse rapide. Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter8 -Vidéos connexes : -- Que faire lorsque vous obtenez une erreur ? https://youtu.be/DQ-CpJn6Rc4 -- Ouvrir un bon ticket : https://youtu.be/_PAli-V4wj0 -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vidéos connexes : +- Que faire lorsque vous obtenez une erreur ? https://youtu.be/DQ-CpJn6Rc4 +- Ouvrir un bon ticket : https://youtu.be/_PAli-V4wj0 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Déboguer le pipeline d'entraînement (PyTorch) +Déboguer le pipeline d'entraînement (PyTorch) -Vous obtenez une erreur lorsque vous appelez Trainer.train() ? Dans cette vidéo, nous allons vous apprendre à déboguer l'ensemble du pipeline d'entraînement, et nous espérons faire disparaître cette erreur. +Vous obtenez une erreur lorsque vous appelez Trainer.train() ? Dans cette vidéo, nous allons vous apprendre à déboguer l'ensemble du pipeline d'entraînement, et nous espérons faire disparaître cette erreur. Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter8 -Version TensorFlow : https://youtu.be/N9kO52itd0Q -Vidéos connexes : -- L’API Trainer : https://youtu.be/nvBXf7s7vTI -- Que faire lorsque vous obtenez une erreur ? https://youtu.be/DQ-CpJn6Rc4 -- Utilisation d'un débogueur dans un notebook : https://youtu.be/rSPyvPw0p9k -- Utilisation d'un débogueur dans un terminal: https://youtu.be/5PkZ4rbHL6c -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Version TensorFlow : https://youtu.be/N9kO52itd0Q +Vidéos connexes : +- L’API Trainer : https://youtu.be/nvBXf7s7vTI +- Que faire lorsque vous obtenez une erreur ? https://youtu.be/DQ-CpJn6Rc4 +- Utilisation d'un débogueur dans un notebook : https://youtu.be/rSPyvPw0p9k +- Utilisation d'un débogueur dans un terminal: https://youtu.be/5PkZ4rbHL6c +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join -Déboguer le pipeline d'entraînement (TensorFlow) +Déboguer le pipeline d'entraînement (TensorFlow) -Vous obtenez une erreur lorsque vous appelez model.fit() ? Dans cette vidéo, nous allons vous apprendre à déboguer l'ensemble du pipeline d'entraînement et, si possible, à faire disparaître cette erreur. +Vous obtenez une erreur lorsque vous appelez model.fit() ? Dans cette vidéo, nous allons vous apprendre à déboguer l'ensemble du pipeline d'entraînement et, si possible, à faire disparaître cette erreur. Intervenant : Matthew Carrigan Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter8 -Version PyTorch : https://youtu.be/L-WSwUWde1U -Vidéos connexes : +Version PyTorch : https://youtu.be/L-WSwUWde1U +Vidéos connexes : - Finetuning avec TensorFlow: https://youtu.be/AUozVp78dhk -- Que faire lorsque vous obtenez une erreur ? https://youtu.be/DQ-CpJn6Rc4 +- Que faire lorsque vous obtenez une erreur ? https://youtu.be/DQ-CpJn6Rc4 - Utilisation d'un débogueur dans un notebook : https://youtu.be/rSPyvPw0p9k -- Utilisation d'un débogueur dans un terminal : https://youtu.be/5PkZ4rbHL6c -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +- Utilisation d'un débogueur dans un terminal : https://youtu.be/5PkZ4rbHL6c +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join Rédiger un bon ticket -Dans cette vidéo, nous allons vous montrer comment rédiger un bon ticket sur GitHub, afin de maximiser les chances que quelqu'un vous aide et résolve votre bug le plus rapidement possible. +Dans cette vidéo, nous allons vous montrer comment rédiger un bon ticket sur GitHub, afin de maximiser les chances que quelqu'un vous aide et résolve votre bug le plus rapidement possible. Intervenant : Sylvain Gugger Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter8 -Vidéos connexes : -- Que faire lorsque vous obtenez une erreur ? https://youtu.be/DQ-CpJn6Rc4 +Vidéos connexes : +- Que faire lorsque vous obtenez une erreur ? https://youtu.be/DQ-CpJn6Rc4 - Utilisation d'un débogueur dans un notebook : https://youtu.be/rSPyvPw0p9k -- Utilisation d'un débogueur dans un terminal : https://youtu.be/5PkZ4rbHL6c +- Utilisation d'un débogueur dans un terminal : https://youtu.be/5PkZ4rbHL6c - Demander de l'aide sur les forums : https://youtu.be/S2EEG3JIt2A -Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join \ No newline at end of file diff --git a/subtitles/zh-CN/00_welcome-to-the-hugging-face-course.srt b/subtitles/zh-CN/00_welcome-to-the-hugging-face-course.srt index 0e8575089..ee4ca158d 100644 --- a/subtitles/zh-CN/00_welcome-to-the-hugging-face-course.srt +++ b/subtitles/zh-CN/00_welcome-to-the-hugging-face-course.srt @@ -1,11 +1,11 @@ 1 00:00:05,850 --> 00:00:07,713 -- 欢迎来到 Hugging Face 课程。 -- Welcome to the Hugging Face Course. +欢迎来到 Hugging Face 课程。 +Welcome to the Hugging Face Course. 2 00:00:08,550 --> 00:00:10,320 -本课程旨在带你了解 +本课程旨在带您了解 This course has been designed to teach you 3 @@ -15,7 +15,7 @@ all about the Hugging Face ecosystem, 4 00:00:12,750 --> 00:00:14,700 -如何使用数据集和模型中心 +包括如何使用数据集和模型中心 how to use the dataset and model hub 5 @@ -25,37 +25,37 @@ as well as all our open-source libraries. 6 00:00:18,300 --> 00:00:19,950 -这是目录。 +这是本课程的目录。 Here is the Table of Contents. 7 00:00:19,950 --> 00:00:22,770 -如你所见,它分为三个部分 +它共分为三个部分 As you can see, it's divided in three sections 8 00:00:22,770 --> 00:00:25,110 -逐渐变得更先进。 +由浅入深地带您学习。 which become progressively more advanced. 9 00:00:25,110 --> 00:00:28,500 -现阶段,前两部分已经发布。 +到目前为止,前两部分已经发布。 At this stage, the first two sections have been released. 10 00:00:28,500 --> 00:00:30,120 -所以首先,我们会教你基础知识 +在课程的一开始,我们会教您基础知识 So first, we'll teach you the basics 11 00:00:30,120 --> 00:00:32,250 -如何使用 Transformer 模型, +包括如何使用 Transformer 模型, of how to use a Transformer model, 12 00:00:32,250 --> 00:00:34,230 -在你自己的数据集上微调 +以及如何基于您自己的数据集上进行微调 fine-tune it on your own data set 13 @@ -65,22 +65,22 @@ and share the result with the community. 14 00:00:36,960 --> 00:00:39,420 -其次,我们将更深入地研究我们的图书馆 +然后,我们将带您深入了解我们的开源库 So second, we'll dive deeper into our libraries 15 00:00:39,420 --> 00:00:42,360 -并教你如何处理任何 NLP 任务。 +并教您如果能够处理任何 NLP 任务。 and teach you how to tackle any NLP task. 16 00:00:42,360 --> 00:00:44,430 -我们正在积极研究最后一个 +我们正在积极研究最后一部分 We're actively working on the last one 17 00:00:44,430 --> 00:00:47,280 -并希望在 2022 年春季为你准备好它。 +并希望在 2022 年春季完成并发布。 and hope to have it ready for you for the spring of 2022. 18 @@ -90,62 +90,62 @@ The first chapter requires no technical knowledge 19 00:00:50,880 --> 00:00:52,320 -是一个很基础的介绍 +只会为您介绍一些基础知识 and is a good introduction to learn 20 00:00:52,320 --> 00:00:54,180 -关于 Transformers 模型可以做什么 +例如 Transformers 模型可以做什么 what Transformers models can do 21 00:00:54,180 --> 00:00:56,883 -以及它如何对你或你的公司有用。 +以及它如何帮助到您以及应用到您公司的业务中。 and how it could be of use to you or your company. 22 00:00:58,050 --> 00:01:01,110 -接下来的章节需要对 Python 有很好的了解 +第一部分之后的章节需要具备 Python 的相关知识 The next chapters require a good knowledge of Python 23 00:01:01,110 --> 00:01:02,130 -以及一些基础知识 +以及机器学习和深度学习的 and some basic knowledge of 24 00:01:02,130 --> 00:01:04,350 -机器学习和深度学习。 +一些基础知识。 Machine Learning and Deep Learning. 25 00:01:04,350 --> 00:01:07,110 -如果你不知道什么是训练集和验证集 +如果您不知道什么是训练集和验证集 If you don't know what a training and validation set are 26 00:01:07,110 --> 00:01:09,360 -或者梯度体面意味着什么, +或者梯度下降法意味着什么, or what gradient decent means, 27 00:01:09,360 --> 00:01:11,340 -你应该看看入门课程 +您应该看看一些 you should look at an introductory course 28 00:01:11,340 --> 00:01:14,863 -例如 deeplearning.ai 或 fast.ai 发布的那些。 +诸如 deeplearning.ai 或 fast.ai 发布的入门课程 such as the ones published by deeplearning.ai or fast.ai. 29 00:01:16,200 --> 00:01:17,910 -如果你有一些基础知识也是最好的 +如果您有一些关于某个 It's also best if you have some basics 30 00:01:17,910 --> 00:01:21,150 -在一个深度学习框架、PyTorch 或 TensorFlow 中。 +深度学习框架、PyTorch 或 TensorFlow 中的基础知识那就更好了。 in one Deep Learning Framework, PyTorch or TensorFlow. 31 @@ -155,17 +155,17 @@ Each part of the material introduced in this course 32 00:01:23,520 --> 00:01:25,590 -在这两个框架中都有一个版本, +在 PyTorch 和 TensorFlow 中都有一个相对应的版本, has a version in both those frameworks, 33 00:01:25,590 --> 00:01:26,730 -这样你就可以选择一个 +这样您就可以选择一个 so you will be able to pick the one 34 00:01:26,730 --> 00:01:28,230 -你最舒服。 +您最熟悉的版本。 you are most comfortable with. 35 @@ -175,7 +175,7 @@ This is the team that developed this course. 36 00:01:31,740 --> 00:01:33,120 -我现在让每个发言者 +接下来每位讲师会先 I'll now let each of the speakers 37 @@ -185,7 +185,7 @@ introduce themselves briefly. 38 00:01:37,230 --> 00:01:38,880 -- 你好,我叫马修, +- Hi,我叫马修, - Hi, my name is Matthew, 39 @@ -200,22 +200,22 @@ I work on the open-source team 41 00:01:43,200 --> 00:01:45,180 -我负责特别维护 +我负责维护 and I'm responsible for maintaining particularly 42 00:01:45,180 --> 00:01:47,280 -那里的 TensorFlow 代码。 +团队内的 TensorFlow 代码。 the TensorFlow code there. 43 00:01:47,280 --> 00:01:50,130 -之前,我是 Parsley 的机器学习工程师, +在此之前,我是 Parsley 的机器学习工程师, Previously, I was a Machine Learning Engineer at Parsley, 44 00:01:50,130 --> 00:01:52,620 -最近被 Automatic 收购, +最近该公司被 Automatic 收购, who've recently been acquired by Automatic, 45 @@ -230,12 +230,12 @@ before that at Trinity College, Dublin in Ireland 47 00:01:57,000 --> 00:02:00,093 -致力于计算遗传学和视网膜疾病。 +致力于计算遗传学和视网膜疾病的研究。 working on computational genetics and retinal disease. 48 00:02:02,400 --> 00:02:03,870 -- 你好,我是莱桑德尔。 +- Hi,我是 Lysandre - Hi, I'm Lysandre. 49 @@ -245,32 +245,32 @@ I'm a Machine Learning Engineer at Hugging Face 50 00:02:05,640 --> 00:02:08,700 -我特别是开源团队的一员。 +我是开源团队的一员。 and I'm specifically part of the open-source team. 51 00:02:08,700 --> 00:02:10,890 -我已经在 Hugging Face 工作了几年 +我已经在 Hugging Face 团队和我的团队成员一起 I've been at Hugging Face for a few years now 52 00:02:10,890 --> 00:02:12,300 -和我的团队成员一起, +工作了好几年, and alongside my team members, 53 00:02:12,300 --> 00:02:13,890 -我一直在研究大多数工具 +我一直致力于研究大多数您将在 I've been working on most of the tools 54 00:02:13,890 --> 00:02:15,790 -你将在本课程中看到。 +本课程中看到的工具。 that you'll get to see in this course. 55 00:02:18,270 --> 00:02:20,130 -- 你好,我是西尔万。 +- Hi,我是 Sylvain - Hi, I'm Sylvain. 56 @@ -280,7 +280,7 @@ I'm a Research Engineer at Hugging Face 57 00:02:22,140 --> 00:02:25,830 -也是 Transformers 库的主要维护者之一。 +也是 Transformers 代码库的主要维护者之一。 and one of the main maintainers of the Transformers Library. 58 @@ -300,17 +300,17 @@ as well as the online book. 61 00:02:32,220 --> 00:02:35,340 -在那之前,我是一名数学和计算机科学老师 +在那之前,我是一名在法国的 Before that, I was a math and computer science teacher 62 00:02:35,340 --> 00:02:36,173 -在法国。 +数学和计算机科学老师。 in France. 63 00:02:38,550 --> 00:02:41,340 -- 你好,我叫 Sasha,是 Hugging Face 的一名研究员, +- Hi,我叫 Sasha,是 Hugging Face 的一名研究员, - Hi, my name is Sasha and I'm a Researcher at Hugging Face, 64 @@ -320,67 +320,67 @@ working on the ethical, 65 00:02:42,420 --> 00:02:46,230 -机器学习模型的环境和社会影响。 +机器学习模型的环境和社会影响相关的研究。 environmental and social impacts of machine learning models. 66 00:02:46,230 --> 00:02:49,020 -之前,我是 Mila 的博士后研究员, +之前,我是 Mila 蒙特利尔大学的 Previously, I was a postdoctoral researcher at Mila, 67 00:02:49,020 --> 00:02:50,400 -蒙特利尔大学 +博士后研究员 University in Montreal 68 00:02:50,400 --> 00:02:53,040 -我还担任过应用人工智能研究员 +我还为联合国全球脉搏计划担任过 and I also worked as an Applied AI Researcher 69 00:02:53,040 --> 00:02:55,140 -为联合国全球脉搏。 +应用人工智能研究员。 for the United Nations Global Pulse. 70 00:02:55,140 --> 00:02:57,300 -参与过 CodeCarbon 等项目 +参与过 CodeCarbon 和 I've been involved in projects such as CodeCarbon 71 00:02:57,300 --> 00:02:59,790 -和机器学习影响计算器 +机器学习影响计算器等项目 and the Machine Learning Impacts Calculator 72 00:02:59,790 --> 00:03:02,390 -衡量机器学习的碳足迹。 +致力于衡量机器学习的碳足迹的研究。 to measure the carbon footprint of machine learning. 73 00:03:05,160 --> 00:03:07,650 -- 大家好,我是 Merve,我是 Hugging Face 团队的开发技术推广工程师 +- Hi,我是 Merve,我是 Hugging Face 团队的 - Hi, I'm Merve and I'm a Developer Advocate 74 00:03:07,650 --> 00:03:09,390 -- 大家好,我是 Merve,我是 Hugging Face 团队的开发技术推广工程师 +开发技术推广工程师 at Hugging Face. 75 00:03:09,390 --> 00:03:12,480 -以前,我是一名机器学习工程师 +在此之前,我是一名机器学习工程师 Previously, I was working as a Machine Learning Engineer 76 00:03:12,480 --> 00:03:15,360 -构建 NLP 工具和聊天机器人。 +负责构建 NLP 工具和聊天机器人。 building NLP tools and chat bots. 77 00:03:15,360 --> 00:03:17,670 -目前,我正在努力改进中心 +目前,我正在努力改进模型中心 Currently, I'm working to improve the hub 78 @@ -395,17 +395,17 @@ and democratize machine learning. 80 00:03:23,670 --> 00:03:27,210 -我叫 Lucile,是 Hugging Face 团队的一名机器学习工程师 +我叫 Lucile,是 Hugging Face 团队的 My name is Lucile and I'm a Machine Learning Engineer 81 00:03:27,210 --> 00:03:28,353 -我叫 Lucile,是 Hugging Face 团队的一名机器学习工程师 +一名机器学习工程师 at Hugging Face. 82 00:03:29,580 --> 00:03:32,550 -用两句话告诉你我是谁, +用两句话告诉您我是谁, To tell you in two sentences who I am, 83 @@ -415,12 +415,12 @@ I work on the development and support of open-source tools 84 00:03:36,600 --> 00:03:39,595 -我也参与了几个研究项目 +我也参与了在自然语言处理领域的 and I also participate in several research project 85 00:03:39,595 --> 00:03:41,795 -在自然语言处理领域。 +几个研究项目。 in the field of Natural Language Processing. 86 @@ -430,12 +430,12 @@ in the field of Natural Language Processing. 87 00:03:45,540 --> 00:03:47,550 -我是刘易斯,我是一名机器学习工程师 +我是 Lewis,我是 Hugging Face 开源团队中的 I'm Lewis and I'm a Machine Learning Engineer 88 00:03:47,550 --> 00:03:50,130 -在 Hugging Face 的开源团队中。 +一名机器学习工程师。 in the open-source team at Hugging Face. 89 @@ -445,12 +445,12 @@ I'm passionate about developing tools for the NLP community 90 00:03:53,490 --> 00:03:55,050 -你能在很多 Hugging Face 对外的活动里见到我 +您可以在很多 Hugging Face and you'll see me at many of Hugging Face's 91 00:03:55,050 --> 00:03:56,910 -你能在很多 Hugging Face 对外的活动里见到我 +对外的活动中见到我 outreach activities. 92 @@ -460,56 +460,56 @@ Before joining Hugging Face, 93 00:03:58,470 --> 00:03:59,790 -我花了几年时间开发 +我花了几年时间 I spent several years developing 94 00:03:59,790 --> 00:04:01,860 -初创公司的机器学习应用程序 +为初创公司和 NLP 领域的企业 machine learning applications for startups 95 00:04:01,860 --> 00:04:04,230 -和 NLP 领域的企业, +开发机器学习应用程序, and enterprises in the domains of NLP, 96 00:04:04,230 --> 00:04:07,260 -拓扑数据分析和时间序列。 +以及拓扑数据分析和时间序列。 topological data analysis and time series. 97 00:04:07,260 --> 00:04:10,110 -前世,我是一名理论物理学家, +在此之前,我是一名理论物理学家, In a former life, I was a theoretical physicist, 98 00:04:10,110 --> 00:04:11,760 -我在哪里研究粒子碰撞 +负责在大型强子对撞机等 where I researched particle collisions 99 00:04:11,760 --> 00:04:13,560 -在大型强子对撞机等。 +研究粒子碰撞。 at the Large Hadron Collider and so. 100 00:04:15,900 --> 00:04:18,450 -- 嘿,我是 Leandro,我是一名机器学习工程师 +- Hey,我是 Leandro,我是一名 Hugging Face 开源团队中 - Hey, I'm Leandro and I'm a Machine Learning Engineer 101 00:04:18,450 --> 00:04:21,030 -在 Hugging Face 的开源团队中。 +的一名机器学习工程师 in the open-source team at Hugging Face. 102 00:04:21,030 --> 00:04:23,460 -在加入 Hugging Face 之前,我是一名数据科学家 +在加入 Hugging Face 之前,我是一名在瑞士的数据科学家 Before joining Hugging Face, I worked as a Data Scientist 103 00:04:23,460 --> 00:04:26,733 -在瑞士,并在大学教授数据科学。 +并在大学教授数据科学。 in Switzerland and have taught Data Science at University. diff --git a/subtitles/zh-CN/01_the-pipeline-function.srt b/subtitles/zh-CN/01_the-pipeline-function.srt index 67579b6c7..f42efa619 100644 --- a/subtitles/zh-CN/01_the-pipeline-function.srt +++ b/subtitles/zh-CN/01_the-pipeline-function.srt @@ -15,12 +15,12 @@ 4 00:00:05,880 --> 00:00:07,080 -- 本节课内容是: Pipeline 函数 +- 本节课内容是: pipeline 函数 - The pipeline function. 5 00:00:09,540 --> 00:00:12,020 -Pipeline 函数是 Transformers 库中的 +pipeline 函数是 Transformers 库中的 The pipeline function is the most high level API 6 @@ -35,17 +35,17 @@ It regroups together all the steps 8 00:00:16,050 --> 00:00:18,873 -从原始文本到可用的预测。 +从而实现从原始文本到可用预测的转换。 to go from raw texts to usable predictions. 9 00:00:20,228 --> 00:00:22,980 -使用的模型是管道的核心, +使用的模型是 pipeline 的核心, The model used is at the core of a pipeline, 10 00:00:22,980 --> 00:00:24,390 -但管道还包括 +但 pipeline 还包括 but the pipeline also include 11 @@ -55,7 +55,7 @@ all the necessary pre-processing, 12 00:00:26,610 --> 00:00:30,240 -因为模型不期望文本,而是数字, +因为模型不期望得到文本,而是数字, since the model does not expect texts, but number, 13 @@ -70,27 +70,27 @@ to make the output of the model human-readable. 15 00:00:35,910 --> 00:00:37,593 -让我们看第一个例子 +让我们通过一个情绪分析 pipeline Let's look at a first example 16 00:00:37,593 --> 00:00:39,693 -与情绪分析管道。 +的例子来解释一下。 with the sentiment analysis pipeline. 17 00:00:40,740 --> 00:00:44,670 -此管道对给定输入执行文本分类 +此 pipeline 对给定的输入执行文本分类 This pipeline performs text classification on a given input 18 00:00:44,670 --> 00:00:46,953 -并确定它是正面的还是负面的。 +并确定它的情绪是正面的还是负面的。 and determines if it's positive or negative. 19 00:00:47,910 --> 00:00:51,750 -在这里,它将正面标签归因于给定文本, +在这里,它为给定文本标记正面情绪的标签, Here, it attributed the positive label on the given text, 20 @@ -100,22 +100,22 @@ with a confidence of 95%. 21 00:00:55,650 --> 00:00:58,470 -你可以将多个文本传递到同一个管道, +您可以将多个文本传递到同一个 pipeline, You can pass multiple texts to the same pipeline, 22 00:00:58,470 --> 00:01:00,270 -将被处理并通过 +它们将被处理并 which will be processed and passed 23 00:01:00,270 --> 00:01:02,673 -通过模型一起作为一个批次。 +作为一个批次传递给模型。 through the model together as a batch. 24 00:01:03,570 --> 00:01:05,970 -输出是单个结果的列表 +输出是包含单个结果的列表 The output is a list of individual results 25 @@ -125,12 +125,12 @@ in the same order as the input texts. 26 00:01:08,790 --> 00:01:12,270 -在这里,我们找到了第一个文本的相同标签和分数, +在这里,我们为第一个文本找到了相同的标签和分数, Here we find the same label and score for the first text, 27 00:01:12,270 --> 00:01:14,443 -第二个文本被判断为否定的 +第二个文本被判断为否定情绪 and the second text is judged negative 28 @@ -140,17 +140,17 @@ with a confidence of 99.9%. 29 00:01:18,720 --> 00:01:20,700 -零样本分类流水线 +零样本分类 pipeline The zero-shot classification pipeline 30 00:01:20,700 --> 00:01:23,610 -是一个更通用的文本分类管道, +是一个更通用的文本分类 pipeline, is a more general text-classification pipeline, 31 00:01:23,610 --> 00:01:26,370 -它允许你提供所需的标签。 +它允许您提供所需的标签。 it allows you to provide the labels you want. 32 @@ -160,12 +160,12 @@ Here we want to classify our input text along the labels, 33 00:01:29,850 --> 00:01:32,643 -教育、政治和商业。 +标签有教育、政治和商业。 education, politics, and business. 34 00:01:33,540 --> 00:01:35,580 -管道成功识别 +pipeline 都能够成功识别 The pipeline successfully recognizes 35 @@ -185,7 +185,7 @@ Moving on to other tasks, 38 00:01:43,110 --> 00:01:45,030 -文本生成管道将 +文本生成 pipeline 将 the text generation pipeline will 39 @@ -200,32 +200,32 @@ The output is generated with a bit of randomness, 41 00:01:49,980 --> 00:01:52,800 -所以每次调用生成器对象时它都会改变 +所以每次在给定的提示上调用生成器对象时 so it changes each time you call the generator object 42 00:01:52,800 --> 00:01:53,763 -在给定的提示上。 +这个结果都会改变。 on a given prompt. 43 00:01:54,990 --> 00:01:57,123 -到目前为止,我们已经使用了管道 API +到目前为止,我们已经使用了 pipeline API Up until now, we've used the the pipeline API 44 00:01:57,123 --> 00:02:00,360 -使用与每个任务关联的默认模型, +结合与每个任务关联的默认模型来演示, with the default model associated to each task, 45 00:02:00,360 --> 00:02:02,880 -但你可以将它与任何经过预训练的模型一起使用 +但您可以将它与任何经过预训练的模型一起使用 but you can use it with any model that has been pretrained 46 00:02:02,880 --> 00:02:04,263 -或微调此任务。 +或根据此任务进行微调。 or fine-tuned on this task. 47 @@ -235,7 +235,7 @@ Going on the model hub, huggingface.co/models 48 00:02:10,350 --> 00:02:13,350 -你可以按任务过滤可用模型。 +您可以按任务过滤可用模型。 you can filter the available models by task. 49 @@ -245,17 +245,17 @@ The default model used in our previous example was gpt2, 50 00:02:17,190 --> 00:02:19,290 -但还有更多型号可供选择, +但还有更多模型可供选择, but there are many more models available, 51 00:02:19,290 --> 00:02:20,523 -而不仅仅是英语。 +且不仅仅是英语。 and not just in English. 52 00:02:21,450 --> 00:02:23,670 -让我们回到文本生成管道 +让我们回到文本生成 pipeline Let's go back to the text generation pipeline 53 @@ -275,7 +275,7 @@ created by the Hugging Face team. 56 00:02:31,740 --> 00:02:34,110 -将管道应用于给定提示时, +将 pipeline 应用于给定提示时, When applying the pipeline to a given prompt, 57 @@ -300,7 +300,7 @@ since there is some randomness in the generation. 61 00:02:46,080 --> 00:02:48,750 -通过猜测句子中的下一个单词生成文本 +通过猜测句子中的下一个单词来生成文本 Generating texts by guessing the next word in a sentence 62 @@ -310,7 +310,7 @@ was the pretraining objective of GPT-2. 63 00:02:51,450 --> 00:02:55,140 -fill mask pipeline 是 BERT 的预训练目标, +掩码填充 pipeline 是 BERT 的预训练目标, The fill mask pipeline is the pretraining objective of BERT, 64 @@ -330,7 +330,7 @@ for the missing words, according to the model, 67 00:03:03,660 --> 00:03:07,053 -并获得数学或计算作为可能的答案。 +并通过数学计算推测可能的答案。 and get mathematical or computational as possible answers. 68 @@ -390,7 +390,7 @@ The grouped_entities=True argument used 79 00:03:40,230 --> 00:03:42,330 -就是把管道组在一起 +就是把 pipeline 组合在一起 is to make the pipeline group together 80 @@ -405,7 +405,7 @@ such as Hugging and Face here. 82 00:03:48,270 --> 00:03:50,670 -管道 API 可用的另一个任务 +pipeline API 可用的另一个任务 Another task available with the pipeline API 83 @@ -430,22 +430,22 @@ containing the answer to the question. 87 00:04:01,650 --> 00:04:03,960 -获取非常长的文章的简短摘要 +获取长文的简短摘要 Getting short summaries of very long articles 88 00:04:03,960 --> 00:04:06,540 -也是 Transformers 库可以提供帮助的东西, +也是 Transformers 库可以提供的功能, is also something the Transformers library can help with, 89 00:04:06,540 --> 00:04:08,140 -与摘要管道。 +这是摘要 pipeline 提供的功能。 with the summarization pipeline. 90 00:04:09,480 --> 00:04:12,570 -最后是 pipeline API 支持的最后一个任务 +pipeline API 支持的最后一个任务 Finally, the last task supported by the pipeline API 91 @@ -455,12 +455,12 @@ is translation. 92 00:04:14,130 --> 00:04:16,170 -这里我们使用法语 / 英语模型 +这里我们使用在模型中心 (Model Hub) 提供的 Here we use a French/English model 93 00:04:16,170 --> 00:04:17,460 -在模型中心找到 +法语/英语模型 found on the model hub 94 @@ -480,12 +480,12 @@ we've looked into in this video. 97 00:04:25,500 --> 00:04:27,390 -然后通过推理小部件尝试 +然后通过模型中心中提供的 Try then out through the inference widgets 98 00:04:27,390 --> 00:04:28,327 -在模型中心。 +推理小部件进行尝试。 in the model hub. 99 diff --git a/subtitles/zh-CN/02_the-carbon-footprint-of-transformers.srt b/subtitles/zh-CN/02_the-carbon-footprint-of-transformers.srt index 9d2211f64..8c0b66d09 100644 --- a/subtitles/zh-CN/02_the-carbon-footprint-of-transformers.srt +++ b/subtitles/zh-CN/02_the-carbon-footprint-of-transformers.srt @@ -1,21 +1,21 @@ 1 00:00:05,580 --> 00:00:08,820 -- 让我们谈谈变压器的碳足迹。 +- 让我们谈谈 transformer 的碳足迹。 - So let's talk about the carbon footprint of transformers. 2 00:00:08,820 --> 00:00:10,530 -也许你看过这样的头条新闻 +也许您看过这样的头条新闻 Maybe you've seen headlines such as this one 3 00:00:10,530 --> 00:00:13,530 -训练单个 AI 模型可以排放尽可能多的碳 +训练单个 AI 模型排放的碳含量 that training a single AI model can emit as much carbon 4 00:00:13,530 --> 00:00:16,020 -作为他们一生中的五辆汽车。 +相当于五辆汽车生命周期的总排放量。 as five cars in their lifetimes. 5 @@ -25,122 +25,122 @@ So when is this true and is it always true? 6 00:00:19,440 --> 00:00:21,803 -好吧,这实际上取决于几件事。 +其实呢,这实际上取决于几个因素。 Well, it actually depends on several things. 7 00:00:21,803 --> 00:00:23,430 -最重要的是,这取决于 +最重要的一点,这取决于 Most importantly, it depends 8 00:00:23,430 --> 00:00:24,960 -关于你使用的能源类型。 +您所使用的能源类型。 on the type of energy you're using. 9 00:00:24,960 --> 00:00:26,267 -如果你使用的是可再生能源,例如 +如果您使用的是可再生能源,例如 If you're using renewable energy such as 10 00:00:26,267 --> 00:00:30,670 -太阳能、风能、水力发电,你真的 +太阳能、风能、水力发电,那么 solar, wind, hydroelectricity, you're really 11 00:00:30,670 --> 00:00:33,810 -根本不排放任何碳,非常非常少。 +根本不会排放任何碳,非常非常少。 not emitting any carbon at all, very, very little. 12 00:00:33,810 --> 00:00:36,769 -如果你使用的是煤炭等不可再生能源 +如果您使用的是煤炭等不可再生能源 If you're using non-renewable energy sources such as coal 13 00:00:36,769 --> 00:00:39,570 -那么他们的碳足迹要高得多 +那么它们的碳足迹要高得多 then their carbon footprint is a lot higher 14 00:00:39,570 --> 00:00:43,260 -因为本质上你正在排放大量的温室气体。 +因为本质上您正在排放大量的温室气体。 'cuz essentially you are emitting a lot of greenhouse gases. 15 00:00:43,260 --> 00:00:44,670 -另一个方面是训练时间。 +另一个因素是训练时间。 Another aspect is training time. 16 00:00:44,670 --> 00:00:47,232 -所以你训练的时间越长,消耗的能量就越多 +所以您训练的时间越长,消耗的能量就越多 So the longer you train, the more energy you use 17 00:00:47,232 --> 00:00:50,250 -你使用的能源越多,你排放的碳就越多,对吗? +您使用的能源越多,您排放的碳就越多,对吗? the more energy you use, the more carbon you emit, right? 18 00:00:50,250 --> 00:00:51,270 -所以这真的加起来 +所以所有这些因素一起进行考虑 So this really adds up 19 00:00:51,270 --> 00:00:53,520 -特别是如果你正在训练大型模型 +特别是如果您正在训练大型模型 especially if you're training large models for 20 00:00:53,520 --> 00:00:56,460 -数小时、数天和数周。 +且持续了数小时、数天或数周的时间。 for hours and days and weeks. 21 00:00:56,460 --> 00:00:58,380 -你使用的硬件也很重要 +您使用的硬件也很重要 The hardware you use also matters 22 00:00:58,380 --> 00:01:00,930 -因为例如某些 GPU 效率更高 +例如某些 GPU 效率更高 because some GPUs, for example, are more efficient 23 00:01:00,930 --> 00:01:05,460 -比其他人和利用效率使用得当。 +相比较别的硬件来说,其利用效率使用得当。 than others and utilizing efficiency use properly. 24 00:01:05,460 --> 00:01:07,500 -所以一直百分百地使用它们 +一直能够百分百地被使用 So using them a hundred percent all the time 25 00:01:07,500 --> 00:01:10,650 -可以真正减少你的能源消耗。 +可以真正减少您的能源消耗。 can really reduce the energy consumption that you have. 26 00:01:10,650 --> 00:01:13,290 -然后再次减少你的碳足迹。 +进一步减少您的碳足迹。 And then once again, reduce your carbon footprint. 27 00:01:13,290 --> 00:01:15,870 -还有其他方面比如 IO +还有其他因素比如 IO There's also other aspects such as IO 28 00:01:15,870 --> 00:01:17,730 -比如数据,等等,等等。 +比如数据,等等。 such as data, et cetera, et cetera. 29 00:01:17,730 --> 00:01:20,940 -但这些是你应该关注的主要三个。 +但这三点是您应该关注的主要因素。 But these are the main three that you should focus on. 30 @@ -155,17 +155,17 @@ what does that really mean? 32 00:01:24,420 --> 00:01:27,480 -所以如果你看屏幕顶部 +所以如果您看屏幕顶部 So if you look at the top of the screen 33 00:01:27,480 --> 00:01:30,480 -你有碳足迹 +您可以看到印度孟买的云计算实例 you have a carbon footprint 34 00:01:30,480 --> 00:01:33,860 -印度孟买的云计算实例 +所产生的碳足迹 of a cloud computing instance in Mumbai, India 35 @@ -175,7 +175,7 @@ which emits 920 grams of CO2 per kilowatt hour. 36 00:01:38,700 --> 00:01:40,110 -这差不多一公斤 +这差不多有一公斤 This is almost one kilogram 37 @@ -185,17 +185,17 @@ of CO2 per kilowatt hour of electricity used. 38 00:01:43,680 --> 00:01:45,150 -如果你把它与加拿大相比,蒙特利尔 +如果您把它与加拿大蒙特利尔, If you compare that with Canada, Montreal 39 00:01:45,150 --> 00:01:48,720 -我现在所在的位置,每千克小时排放 20 克二氧化碳。 +也就是我现在所在的位置相比,每千克小时排放 20 克二氧化碳。 where I am right now, 20 grams of CO2 per kilo hour. 40 00:01:48,720 --> 00:01:50,040 -所以这是一个非常非常大的区别。 +所以它们有着非常大的区别。 So that's a really, really big difference. 41 @@ -210,27 +210,27 @@ in Mumbai versus Montreal. 43 00:01:55,950 --> 00:01:57,720 -所以这真的可以加起来。 +所以这真的都需要考虑进去。 And so this can really, really add up. 44 00:01:57,720 --> 00:01:59,820 -例如,如果你要训练一个模型数周 +例如,如果您要训练一个模型数周 If you're training a model for weeks, for example 45 00:01:59,820 --> 00:02:01,920 -你乘以 40 +您乘以 40 you're multiplying times 40 46 00:02:01,920 --> 00:02:03,450 -你排放的碳。 +您排放的碳。 the carbon that you're emitting. 47 00:02:03,450 --> 00:02:05,070 -所以选择合适的实例 +因此选择合适的实例 So choosing the right instance 48 @@ -240,22 +240,22 @@ choosing a low carbon compute instance 49 00:02:07,080 --> 00:02:09,690 -这真的是你能做的最有影响力的事情。 +这真的是您能做的最有影响力的事情。 is really the most impactful thing that you can do. 50 00:02:09,690 --> 00:02:13,020 -这就是它真正可以加起来的地方 +这就是它真正可以产生影响的地方 And this is where it can really add up 51 00:02:13,020 --> 00:02:15,930 -如果你正在接受非常密集的训练 +如果您正在一个碳密集的地区 if you're training in a very intensive 52 00:02:15,930 --> 00:02:17,580 -在一个非常碳密集的地区 +进行非常密集的训练 in a very carbon intensive region 53 @@ -275,27 +275,27 @@ that's the machine learning equivalent of recycling. 56 00:02:25,590 --> 00:02:28,292 -当你有可用的预训练模型时 +当您有可用的预训练模型时 When you have pre-trained models available using them 57 00:02:28,292 --> 00:02:30,120 -你根本没有排放任何碳,对吧? +您根本没有排放任何碳,对吧? you're not emitting any carbon at all, right? 58 00:02:30,120 --> 00:02:31,230 -你没有再训练任何东西。 +因为您没有在训练任何东西。 You're not retraining anything. 59 00:02:31,230 --> 00:02:33,450 -所以这也在做你的功课 +因此先看看当前已经有了哪些工具 So that's also doing your homework 60 00:02:33,450 --> 00:02:35,574 -并环顾四周已经存在的东西。 +能够帮助您处理所需要进行的任务。 and kind of looking around what already exists. 61 @@ -310,7 +310,7 @@ So once again 63 00:02:38,723 --> 00:02:40,590 -如果你找到几乎是你需要的模型 +如果您找到几乎是您需要的模型 if you find a model that is almost what you need 64 @@ -320,17 +320,17 @@ but not quite fine tuning the last couple of layers 65 00:02:43,530 --> 00:02:45,210 -让它真正适合你的目的 +通过调整来达到目的 making it really fit your purpose instead 66 00:02:45,210 --> 00:02:46,500 -培训大型变压器 +而不是从头通过训练 transformer of training a large transformer 67 00:02:46,500 --> 00:02:48,810 -从头开始真的很有帮助, +这样的话会大大提高您的效率 from scratch can really help, 68 @@ -340,7 +340,7 @@ starting with smaller experiments 69 00:02:51,270 --> 00:02:52,800 -并边调试边调试。 +并边调试边工作。 and debugging as you go. 70 @@ -350,17 +350,17 @@ So that means, for example, training 71 00:02:54,630 --> 00:02:58,770 -弄清楚数据编码,弄清楚,你知道 +弄清楚数据编码 figuring out data encoding, figuring out, you know 72 00:02:58,770 --> 00:03:01,170 -确保没有小错误,你会 +确保没有小错误 making sure that there's no small bugs, that you'll 73 00:03:01,170 --> 00:03:03,840 -你会意识到,你知道,经过 16 个小时的训练 +您会意识到,经过 16 个小时的训练 you'll realize, you know, 16 hours into training 74 @@ -370,12 +370,12 @@ starting small and really making sure 75 00:03:05,820 --> 00:03:08,760 -你在做什么,你的代码是什么,是稳定的。 +您在做什么,您的代码是什么,这样才是稳妥的。 that what you're doing, what your code is, is stable. 76 00:03:08,760 --> 00:03:11,430 -然后最后做一个文献综述 +最后做一个文献综述 And then finally doing a literature review to 77 @@ -395,22 +395,22 @@ So random searches for hyper parameters 80 00:03:18,420 --> 00:03:21,300 -组合实际上被证明是有效的 +在寻找最佳配置作为网格搜索时 combinations have actually shown to be as efficient 81 00:03:21,300 --> 00:03:24,000 -在寻找最佳配置作为网格搜索时。 +组合实际上被证明是有效的。 in finding the optimal configuration as grid search. 82 00:03:24,000 --> 00:03:27,510 -但显然你并没有尝试所有可能的组合 +但显然您并没有尝试所有可能的组合 But obviously you're not trying all possible combinations 83 00:03:27,510 --> 00:03:29,520 -你只是在尝试其中的一部分。 +您只是在尝试其中的一部分。 you're only trying a subset of them. 84 @@ -430,17 +430,17 @@ to the original paper by Strubell et all in 2019 87 00:03:36,300 --> 00:03:39,180 -臭名昭著的五辆车在他们一生的论文中。 +关于那五辆车的论文中。 the infamous five cars in their lifetimes paper. 88 00:03:39,180 --> 00:03:40,013 -如果你只是看 +如果您只是考虑 If you just look 89 00:03:40,013 --> 00:03:43,606 -在一个 2 亿周边变压器的变压器处 +一个 2 亿周边 transformer 的因素 at a transformer of 200 million perimeter transformer 90 @@ -450,7 +450,7 @@ it is carbon footprint is around 200 pounds of CO2 91 00:03:46,950 --> 00:03:47,940 -这很重要 +这很多 which is significant 92 @@ -465,22 +465,22 @@ It's not even a transatlantic flight. 94 00:03:52,893 --> 00:03:55,020 -它真正加起来的方式是当你在做的时候 +它真正到达这一量级的方式是当您在做 How it really adds up is when you're doing 95 00:03:55,020 --> 00:03:56,190 -神经架构搜索 +神经架构搜索的时候 neural architecture search 96 00:03:56,190 --> 00:03:58,560 -当你进行超参数调整时,以及 +当您进行超参数调整时,以及 when you're doing hyper parameter tuning, and 97 00:03:58,560 --> 00:04:00,930 -这是在尝试所有可能的组合 +在尝试所有可能的组合的时候 this is trying all possible combinations 98 @@ -490,27 +490,27 @@ et cetera, et cetera. 99 00:04:01,763 --> 00:04:02,596 -这是哪里 +这是就像 And this is where 100 00:04:02,596 --> 00:04:05,400 -就像来自 600,000 磅的二氧化碳一样。 +就像 600,000 磅的二氧化碳来自哪里一样。 like the 600,000 pounds of CO2 came from. 101 00:04:05,400 --> 00:04:08,490 -所以这真的是事情加起来的地方。 +所以这真的是需要把所有因素考虑进来才可以。 So this is really where things add up. 102 00:04:08,490 --> 00:04:11,880 -所以,但如果你正念认真地做事 +所以,但如果您正认真地做事 So, but if you're doing things mindfully and conscientiously 103 00:04:11,880 --> 00:04:16,410 -那么你的碳足迹就不会那么大, +那么您的碳足迹就不会那么大, then your carbon footprint wont be as big as, 104 @@ -520,7 +520,7 @@ as the paper implied, some tools to figure 105 00:04:20,040 --> 00:04:22,111 -计算出你排放的 CO2 量。 +出您排放的 CO2 量。 out how much CO2 exactly you're emitting. 106 @@ -530,17 +530,17 @@ There's a web-based tool called machine 107 00:04:24,270 --> 00:04:26,430 -学习提交计算器,它可以让你 +学习提交计算器,它可以让您 learning submissions calculator, which allows you 108 00:04:26,430 --> 00:04:29,010 -手动输入,例如,你使用的硬件 +手动输入,例如,您使用的硬件 to manually input, for example, which hardware you used 109 00:04:29,010 --> 00:04:30,488 -你用了多少小时 +您用了多少小时 how many hours you used it for 110 @@ -550,12 +550,12 @@ where it was located locally or in the cloud. 111 00:04:34,260 --> 00:04:35,640 -然后它会给你一个估计 +然后它会给您一个估计 And then it's gonna give you an estimate 112 00:04:35,640 --> 00:04:37,560 -你排放了多少二氧化碳。 +您排放了多少二氧化碳。 of how much CO2 you emitted. 113 @@ -570,27 +570,27 @@ is called code carbon. 115 00:04:41,190 --> 00:04:45,112 -所以你可以 PIP 安装它,你可以,你可以去 GitHub +您可以 PIP 安装它,您可以去 GitHub So you can PIP install it, you can, you can go to the GitHub 116 00:04:45,112 --> 00:04:48,120 -本质上它与你的代码并行运行。 +它与您的代码并行运行。 and essentially it runs in parallel to your code. 117 00:04:48,120 --> 00:04:49,085 -所以基本上你称之为 +所以基本上您调用它 So essentially you call it 118 00:04:49,085 --> 00:04:51,060 -然后你做所有的训练。 +然后交给它做所有的训练。 and then you do all your training. 119 00:04:51,060 --> 00:04:53,760 -然后最后它会给你一个估计 +最后它会给您一个估计 And then at the end it's gonna give you an estimate 120 @@ -600,12 +600,12 @@ a CSV file with an estimate of your emissions. 121 00:04:57,210 --> 00:04:59,250 -它会给你一些比较。 +它会给您一些比较。 And it's gonna give you some comparisons. 122 00:04:59,250 --> 00:05:01,230 -它有一个可视化用户界面,你可以在其中真正看到 +它有一个可视化用户界面,您可以在其中真正看到 It's got a visual UI where you can really look 123 @@ -615,31 +615,31 @@ at how this compares to driving a car or watching TV. 124 00:05:04,680 --> 00:05:06,060 -所以它可以给你一个想法 +所以它可以给您一个想法 So it can give you an idea 125 00:05:06,060 --> 00:05:07,740 -你的排放范围也是如此。 +您的排放范围也是如此。 of the scope of your emissions as well. 126 00:05:07,740 --> 00:05:09,930 -实际上,code carbon 已经集成到汽车中 +实际上,code carbon 已经集成到 auto And actually, code carbon is already integrated into auto 127 00:05:09,930 --> 00:05:12,270 -和 LP,希望人们会使用它 +和 LP 中,希望人们能够 and LP and hopefully people will be using it 128 00:05:12,270 --> 00:05:15,240 -开箱即用,轻松跟踪所有排放 +开箱即用,轻松跟踪所有训练和部署 transformer out of the box and easily tracking their emissions all 129 00:05:15,240 --> 00:05:17,523 -通过培训和部署变压器。 +的碳排放。 through training and deploying transformers. diff --git a/subtitles/zh-CN/66_the-post-processing-step-in-question-answering-(pytorch).srt b/subtitles/zh-CN/66_the-post-processing-step-in-question-answering-(pytorch).srt index ea2b861e6..44d924991 100644 --- a/subtitles/zh-CN/66_the-post-processing-step-in-question-answering-(pytorch).srt +++ b/subtitles/zh-CN/66_the-post-processing-step-in-question-answering-(pytorch).srt @@ -5,12 +5,12 @@ 2 00:00:05,940 --> 00:00:08,913 -- 问答任务中的后处理步骤。 +- 问答任务中的后处理操作。 - The post-processing step in a question answering task. 3 00:00:10,440 --> 00:00:12,180 -在做答题时, +在做问答任务时, When doing question answering, 4 @@ -20,7 +20,7 @@ the processing of the initial dataset 5 00:00:14,550 --> 00:00:17,370 -意味着将示例拆分为多个功能, +意味着以多个特征拆分示例, implies splitting examples in several features, 6 @@ -30,22 +30,22 @@ which may or may not contain the answer. 7 00:00:21,000 --> 00:00:22,740 -通过模型传递这些特征 +由于我们的标签是词元的索引 Passing those features through the model 8 00:00:22,740 --> 00:00:25,830 -将为我们提供开始和结束位置的 logits, +其对应于答案的起始和结束, will give us logits for the start and end positions, 9 00:00:25,830 --> 00:00:28,650 -因为我们的标签是令牌的索引 +通过模型传递这些特征 since our labels are the indices of the token 10 00:00:28,650 --> 00:00:31,050 -对应于开始和结束的答案。 +将为我们提供开始和结束位置的 logits。 that correspond to the start and end the answer. 11 @@ -55,12 +55,12 @@ We must then somehow convert those logits into an answer, 12 00:00:35,490 --> 00:00:38,610 -然后从每个功能给出的各种答案中选择一个 +然后从每个特征给出的各种答案中选择一个 and then pick one of the various answers each feature gives 13 00:00:38,610 --> 00:00:40,893 -成为给定示例的答案。 +作为给定示例的答案。 to be the answer for a given example. 14 @@ -70,12 +70,12 @@ For the processing step, 15 00:00:43,500 --> 00:00:45,750 -你应该参考下面链接的视频。 +你可以参考下面链接的视频。 you should refer to the video linked below. 16 00:00:45,750 --> 00:00:47,820 -验证并没有太大的不同, +验证方面也没有太大的变化, It's not very different for validation, 17 @@ -85,42 +85,42 @@ we just need to add a few lines to keep track of two things. 18 00:00:51,660 --> 00:00:54,960 -我们保留它们,而不是丢弃偏移映射, +我们保留 offset mapping,而不是丢弃它们, Instead of discarding the offset mappings, we keep them, 19 00:00:54,960 --> 00:00:55,793 -也包括在其中 +并且通过设置特殊词元的偏移量 and also include in them 20 00:00:55,793 --> 00:00:58,350 -上下文在哪里的信息 +以及将问题设置为 None the information of where the context is 21 00:00:58,350 --> 00:01:00,690 -通过设置特殊标记的偏移量 +将所保留的 offset mapping by setting the offsets of the special tokens 22 00:01:00,690 --> 00:01:02,253 -和无的问题。 +包含在上下文的信息里。 and the question to None. 23 00:01:03,480 --> 00:01:06,630 -然后我们还跟踪每个功能的示例 ID, +然后我们还跟踪每个特征的示例 ID, Then we also keep track of the example ID for each feature, 24 00:01:06,630 --> 00:01:08,280 -能够映射回特征 +能够将特征映射回 to be able to map back feature 25 00:01:08,280 --> 00:01:10,503 -他们起源的例子。 +他们初始的例子。 to the examples that they originated from. 26 @@ -130,22 +130,22 @@ If you don't want to compute the validation loss, 27 00:01:14,100 --> 00:01:15,990 -你不需要包含所有特殊代码 +你不需要包含所有这些 you won't need to include all the special code 28 00:01:15,990 --> 00:01:18,420 -我们用来创建标签的。 +用来创建标签的特殊代码。 that we used to create the labels. 29 00:01:18,420 --> 00:01:21,090 -完成后,我们可以应用该预处理功能 +完成后,我们可以调用 map 方法 With this done, we can apply that preprocessing function 30 00:01:21,090 --> 00:01:22,890 -使用映射方法。 +应用该预处理功能。 using the map method. 31 @@ -155,7 +155,7 @@ We take the SQUAD dataset 32 00:01:24,090 --> 00:01:26,840 -比如问答视频的预处理。 +就和问答视频的预处理中所用到的一样。 like in the preprocessing for question-answering video. 33 @@ -165,12 +165,12 @@ Once this is done, the next step is to create our model. 34 00:01:30,540 --> 00:01:31,710 -我们使用默认模型 +我们在这里的问答管道背后 We use the default model 35 00:01:31,710 --> 00:01:33,930 -在这里的问答管道背后, +使用默认模型, behind the question-answering pipeline here, 36 @@ -190,17 +190,17 @@ so we create a PyTorch DataLoader with our features. 39 00:01:42,657 --> 00:01:44,520 -有了它,我们可以计算和收集 +有了它,我们可以使用标准的 PyTorch 评估循环 With it, we can compute and gather 40 00:01:44,520 --> 00:01:46,650 -所有的开始和结束都是这样的, +来计算和收集所有 all the start and end logits like this, 41 00:01:46,650 --> 00:01:49,653 -使用标准的 PyTorch 评估循环。 +像这样的开始和结束的 logits。 with a standard PyTorch evaluation loop. 42 @@ -215,7 +215,7 @@ First, we'll need a map from example to features, 44 00:01:56,340 --> 00:01:57,873 -我们可以这样创建。 +我们像这样创建。 which we can create like this. 45 @@ -230,17 +230,17 @@ let's see how to extract an answer from the logits. 47 00:02:04,230 --> 00:02:05,760 -我们可以只取最好的索引 +我们可以针对开始和结束 logits We could just take the best index 48 00:02:05,760 --> 00:02:07,980 -对于开始和结束登录并完成, +只取最好的索引, for the start and end logits and be done, 49 00:02:07,980 --> 00:02:10,380 -但如果我们的模型预测了一些不可能的事情, +但如果我们的模型预测得出了不可思议的结果, but if our model predicts something impossible, 50 @@ -260,12 +260,12 @@ Note that in the question-answering pipeline, 53 00:02:17,070 --> 00:02:18,870 -我们将分数归因于每个答案 +我们基于概率将分数归因 we attributed score to each answer 54 00:02:18,870 --> 00:02:20,430 -基于概率, +于每个答案, based on the probabilities, 55 @@ -275,7 +275,7 @@ which we did not compute here. 56 00:02:22,350 --> 00:02:25,560 -就逻辑而言,我们在分数中的乘法 +就 logits 而言,我们在分数中的乘法 In terms of logits, the multiplication we had in the scores 57 @@ -285,27 +285,27 @@ becomes an addition. 58 00:02:28,110 --> 00:02:29,010 -要走得快, +要快速完成, To go fast, 59 00:02:29,010 --> 00:02:31,800 -我们不会查看所有可能的开始和结束日志, +我们不会查看所有可能的开始和结束的 logits, we don't look at all possible start and end logits, 60 00:02:31,800 --> 00:02:34,050 -但是最好的 20 个就足够了。 +仅需最好的 20 个就足够了。 but the 20 best one is enough. 61 00:02:34,050 --> 00:02:36,570 -我们忽略产生不可能答案的逻辑 +我们忽略产生不可能答案和答案太长 We ignore the logits that spawn impossible answers 62 00:02:36,570 --> 00:02:38,550 -或回答太长。 +的 logits。 or answer that are too long. 63 @@ -315,7 +315,7 @@ As we saw in the preprocessing, the labels 0,0 64 00:02:41,430 --> 00:02:43,230 -对应不回答。 +对应无答案。 correspond to a no answer. 65 @@ -330,12 +330,12 @@ to get the answer inside the context. 67 00:02:47,910 --> 00:02:49,107 -我们来看看预测答案 +我们来看看对于第一个特征 Let's have a look at the predicted answer 68 00:02:49,107 --> 00:02:50,370 -对于第一个功能, +预测的答案, for the first feature, 69 @@ -345,7 +345,7 @@ which is the answer with the best score 70 00:02:51,930 --> 00:02:53,640 -或最好的逻辑分数 +或最好的 logit 分数 or the best logit score 71 @@ -355,7 +355,7 @@ since the SoftMax is an increasing function. 72 00:02:56,280 --> 00:02:58,230 -模型做对了。 +这个模型就是合适的。 The model got it right. 73 @@ -365,12 +365,12 @@ Next we just have to loop this for every example, 74 00:03:00,690 --> 00:03:03,720 -为每个选择具有最佳 logit 分数的答案 +在示例生成的所有特征中 picking for each the answer with the best logit score 75 00:03:03,720 --> 00:03:06,750 -在示例生成的所有功能中。 +选择具有最佳 logit 分数的答案。 in all the features the example generated. 76 diff --git a/subtitles/zh-CN/67_the-post-processing-step-in-question-answering-(tensorflow).srt b/subtitles/zh-CN/67_the-post-processing-step-in-question-answering-(tensorflow).srt index 5991da907..28edb55c2 100644 --- a/subtitles/zh-CN/67_the-post-processing-step-in-question-answering-(tensorflow).srt +++ b/subtitles/zh-CN/67_the-post-processing-step-in-question-answering-(tensorflow).srt @@ -5,12 +5,12 @@ 2 00:00:05,850 --> 00:00:08,913 -- 问答任务中的后处理步骤。 +- 问答任务中的后处理操作。 - The post-processing step in a question-answering task. 3 00:00:10,830 --> 00:00:11,790 -在做答题时, +在做问答任务时, When doing question answering, 4 @@ -20,7 +20,7 @@ the processing of the initial dataset 5 00:00:14,670 --> 00:00:18,090 -意味着将示例拆分为多个功能, +意味着以多个特征拆分示例, implies splitting examples in several features, 6 @@ -30,22 +30,22 @@ which may or may not contain the answer. 7 00:00:20,850 --> 00:00:22,530 -通过模型传递这些特征 +由于我们的标签是词元的索引 Passing those features through the model 8 00:00:22,530 --> 00:00:25,860 -将为我们提供开始和结束位置的 logits, +其对应于答案的起始和结束, will give us logits for the start and end positions, 9 00:00:25,860 --> 00:00:28,620 -因为我们的标签是令牌的索引 +通过模型传递这些特征 since our labels are the indices of the tokens 10 00:00:28,620 --> 00:00:31,020 -对应于开始和结束的答案。 +将为我们提供开始和结束位置的 logits。 that correspond to the start and end the answer. 11 @@ -55,12 +55,12 @@ We must then somehow convert those logits into an answer, 12 00:00:34,740 --> 00:00:38,070 -然后从每个功能给出的各种答案中选择一个 +然后从每个特征给出的各种答案中选择一个 and then pick one of the various answers each feature gives 13 00:00:38,070 --> 00:00:40,473 -成为给定示例的答案。 +作为给定示例的答案。 to be the answer for a given example. 14 @@ -70,12 +70,12 @@ For the processing step, 15 00:00:43,200 --> 00:00:45,450 -你应该参考下面链接的视频。 +你可以参考下面链接的视频。 you should refer to the video linked below. 16 00:00:45,450 --> 00:00:47,310 -验证并没有太大的不同, +验证方面也没有太大的变化, It's not very different for validation, 17 @@ -85,37 +85,37 @@ we just need to add a few lines to keep track of two things: 18 00:00:50,053 --> 00:00:52,620 -而不是丢弃偏移映射, +我们保留 offset mapping, instead of discarding the offset mappings, 19 00:00:52,620 --> 00:00:55,380 -我们保留它们,并在其中包含信息 +而不是丢弃它们,并且通过设置特殊词元的偏移量 we keep them, and also include in them the information 20 00:00:55,380 --> 00:00:58,410 -通过设置偏移量来确定上下文的位置 +以及将问题设置为 None of where the context is by setting the offsets 21 00:00:58,410 --> 00:01:01,821 -特殊标记和无的问题。 +将所保留的 offset mapping 包含在上下文的信息里。 of the special tokens and the question to None. 22 00:01:01,821 --> 00:01:05,370 -然后我们还跟踪每个功能的示例 ID, +然后我们还跟踪每个特征的示例 ID, Then we also keep track of the example ID for each feature, 23 00:01:05,370 --> 00:01:07,020 -能够映射回特征 +能够将特征映射回 to be able to map back feature 24 00:01:07,020 --> 00:01:09,243 -他们起源的例子。 +他们初始的例子。 to the examples that they originated from. 25 @@ -125,32 +125,32 @@ If you don't want to compute the validation loss, 26 00:01:12,660 --> 00:01:14,610 -你不需要包含所有特殊代码 +你不需要包含所有这些 you won't need to include all the special code 27 00:01:14,610 --> 00:01:17,010 -我们用来创建标签的。 +用来创建标签的特殊代码。 that we used to create the labels. 28 00:01:17,010 --> 00:01:19,650 -完成后,我们可以应用该预处理功能 +完成后,我们可以调用 map 方法 With this done, we can apply that preprocessing function 29 00:01:19,650 --> 00:01:21,480 -使用映射方法。 +应用该预处理功能。 using the map method. 30 00:01:21,480 --> 00:01:23,610 -我们在预处理中采用 SQUAD 数据集 +我们采用 SQUAD 数据集就和问答视频的预处理 We take the SQUAD dataset like in the preprocessing 31 00:01:23,610 --> 00:01:25,060 -用于问答视频。 +中所用到的一样。 for question-answering video. 32 @@ -160,22 +160,22 @@ Once this is done, the next step is to create our model. 33 00:01:29,310 --> 00:01:30,570 -我们使用后面的默认模型 +我们在这里的问答管道之后 We use the default model behind 34 00:01:30,570 --> 00:01:32,640 -这里的问答管道, +使用默认模型, the question-answering pipeline here, 35 00:01:32,640 --> 00:01:35,880 -但你应该使用你想要评估的任何模型。 +不过你应该使用你想要评估的任何模型。 but you should use any model you want to evaluate. 36 00:01:35,880 --> 00:01:37,680 -使用 to_tf_dataset 方法, +通过 to_tf_dataset 方法, With the to_tf_dataset method, 37 @@ -185,7 +185,7 @@ we can just sent our processed dataset to model.predict, 38 00:01:41,370 --> 00:01:43,350 -我们直接得到我们的开始和结束 logits +我们直接得到我们的开始和结束的 logits and we directly get our start and end logits 39 @@ -205,7 +205,7 @@ First, we'll need a map from example to features, 42 00:01:52,380 --> 00:01:53,883 -我们可以这样创建。 +我们像这样创建。 which we can create like this. 43 @@ -220,22 +220,22 @@ let's see how to extract an answer from the logits. 45 00:02:00,270 --> 00:02:01,650 -我们可以只取最好的索引 +我们可以针对开始和结束 logits We could just take the best index 46 00:02:01,650 --> 00:02:03,690 -对于开始和结束登录并完成, +只取最好的索引, for the start and end logits and be done, 47 00:02:03,690 --> 00:02:06,180 -但如果我们的模型预测了一些不可能的事情, +但如果我们的模型预测得出了不可思议的结果, but if our model predicts something impossible, 48 00:02:06,180 --> 00:02:07,920 -就像问题中的标记, +就像问题中的词元, like tokens in the questions, 49 @@ -250,47 +250,47 @@ Note that in the question-answering pipeline, 51 00:02:12,570 --> 00:02:14,160 -我们将分数归因于每个答案 +我们基于概率将分数 we attributed the score to each answer 52 00:02:14,160 --> 00:02:17,880 -基于我们没有在这里计算的概率。 +归因于每个答案,在这里并没有计算。 based on the probabilities, which we did not compute here. 53 00:02:17,880 --> 00:02:19,860 -在 logits 方面,我们有乘法 +就 logits 而言,我们在分数中的乘法 In terms of logits, the multiplication we had 54 00:02:19,860 --> 00:02:21,663 -在分数中成为加法。 +变为加法 in the scores becomes an addition. 55 00:02:22,650 --> 00:02:23,910 -为了走得快,我们不看 +要快速完成, To go fast, we don't look 56 00:02:23,910 --> 00:02:25,343 -在所有可能的开始和结束日志中, +我们不会查看所有可能的开始和结束的 logits, at all possible start and end logits, 57 00:02:25,343 --> 00:02:26,973 -但最好的 20 个。 +仅需最好的 20 个就足够了。 but the 20 best ones. 58 00:02:27,810 --> 00:02:30,386 -我们忽略产生不可能答案的逻辑 +我们忽略产生不可能答案和答案太长 We ignore the logits that spawn impossible answers 59 00:02:30,386 --> 00:02:32,370 -或回答太长。 +的 logits or answer that are too long. 60 @@ -300,7 +300,7 @@ As we saw in the preprocessing, 61 00:02:33,720 --> 00:02:36,240 -标签 “0, 0” 对应于没有答案, +标签 “0, 0” 对应无答案, the label "0, 0" correspond to no answer, 62 @@ -315,12 +315,12 @@ to get the answer inside the context. 64 00:02:40,260 --> 00:02:41,580 -我们来看看预测答案 +我们来看看对于第一个特征 Let's have a look at the predicted answer 65 00:02:41,580 --> 00:02:43,200 -对于第一个功能, +预测的答案, for the first feature, 66 @@ -330,12 +330,12 @@ which is the answer with the best score, 67 00:02:44,790 --> 00:02:46,860 -或自 SoftMax 以来最好的 logit 分数 +或最好的 logit 分数 or the best logit score since the SoftMax 68 00:02:46,860 --> 00:02:48,810 -是增函数。 +因为 SoftMax 是增函数。 is an increasing function. 69 @@ -345,17 +345,17 @@ The model got it right. 70 00:02:51,210 --> 00:02:54,180 -接下来,我们只需要为每个示例循环这个, +接下来,我们只需要为每个例子循环这个, Next, we just have to loop this for every example, 71 00:02:54,180 --> 00:02:56,700 -为每个选择具有最佳 logit 分数的答案 +在示例生成的所有特征中 picking for each the answer with the best logit score 72 00:02:56,700 --> 00:02:59,133 -在示例生成的所有功能中。 +选择具有最佳 logit 分数的答案。 in all the features the example generated. 73 diff --git a/utils/convert_bilingual_monolingual.py b/utils/convert_bilingual_monolingual.py index 4a8004cdb..c993a6516 100644 --- a/utils/convert_bilingual_monolingual.py +++ b/utils/convert_bilingual_monolingual.py @@ -1,61 +1,53 @@ -#!/usr/bin/python3 -import getopt import re -import sys +import argparse +from pathlib import Path -PATTERN_TIMESTAMP = re.compile('^[0-9][0-9]:[0-9][0-9]:[0-9][0-9],[0-9][0-9][0-9] --> [0-9][0-9]:[0-9][0-9]:[0-9][0-9],[0-9][0-9][0-9]') -PATTERN_NUM = re.compile('\\d+') +PATTERN_TIMESTAMP = re.compile( + "^[0-9][0-9]:[0-9][0-9]:[0-9][0-9],[0-9][0-9][0-9] --> [0-9][0-9]:[0-9][0-9]:[0-9][0-9],[0-9][0-9][0-9]" +) +PATTERN_NUM = re.compile("\\d+") -def main(argv): - inputfile = '' - outputfile = '' - try: - opts, args = getopt.getopt(argv, "hi:o:", ["ifile=", "ofile="]) - except getopt.GetoptError: - print('srt_worker.py -i -o ') - sys.exit(2) - for opt, arg in opts: - if opt == '-h': - print( 'Usage: convert_bilingual_monolingual.py -i -o ') - sys.exit(-2) - elif opt in ("-i", "--ifile"): - inputfile = arg - elif opt in ("-o", "--ofile"): - outputfile = arg - - if not inputfile: - print('no input file is specified.\nUsage: convert_bilingual_monolingual.py -i -o ') - elif not outputfile: - print('no output file is specified.\nUsage: convert_bilingual_monolingual.py -i -o ') - else: - process(inputfile, outputfile) - - -def process(input_file, output): +def convert(input_file, output_file): """ - Convert bilingual caption file to monolingual caption, supported caption file type is srt. + Convert bilingual caption file to monolingual caption. Supported caption file type is SRT. """ line_count = 0 with open(input_file) as file: - with open(output, 'a') as output: + with open(output_file, "w") as output_file: for line in file: if line_count == 0: line_count += 1 - output.write(line) + output_file.write(line) elif PATTERN_TIMESTAMP.match(line): line_count += 1 - output.write(line) - elif line == '\n': + output_file.write(line) + elif line == "\n": line_count = 0 - output.write(line) + output_file.write(line) else: if line_count == 2: - output.write(line) + output_file.write(line) line_count += 1 - output.close() - print('conversion completed!') + output_file.close() if __name__ == "__main__": - main(sys.argv[1:]) + parser = argparse.ArgumentParser() + parser.add_argument( + "--input_language_folder", type=str, help="Folder with input bilingual SRT files to be converted" + ) + parser.add_argument( + "--output_language_folder", + type=str, + default="tmp-subtitles", + help="Folder to store converted monolingual SRT files", + ) + args = parser.parse_args() + + output_path = Path(args.output_language_folder) + output_path.mkdir(parents=True, exist_ok=True) + input_files = Path(args.input_language_folder).glob("*.srt") + for input_file in input_files: + convert(input_file, output_path / input_file.name) + print(f"Succesfully converted {len(list(input_files))} files to {args.output_language_folder} folder") diff --git a/utils/generate_notebooks.py b/utils/generate_notebooks.py index d7f235243..f4e77cd62 100644 --- a/utils/generate_notebooks.py +++ b/utils/generate_notebooks.py @@ -201,7 +201,7 @@ def build_notebook(fname, title, output_dir="."): installs = ["!pip install datasets evaluate transformers[sentencepiece]"] if section_name in sections_with_accelerate: installs.append("!pip install accelerate") - installs.append("# To run the training on TPU, you will need to uncomment the followin line:") + installs.append("# To run the training on TPU, you will need to uncomment the following line:") installs.append( "# !pip install cloud-tpu-client==0.10 torch==1.9.0 https://storage.googleapis.com/tpu-pytorch/wheels/torch_xla-1.9-cp37-cp37m-linux_x86_64.whl" ) diff --git a/utils/generate_subtitles.py b/utils/generate_subtitles.py index f5d1d4a05..31dccbe2c 100644 --- a/utils/generate_subtitles.py +++ b/utils/generate_subtitles.py @@ -7,14 +7,13 @@ import argparse import sys -def generate_subtitles(language: str, youtube_language_code: str=None): + +def generate_subtitles(language: str, youtube_language_code: str = None): metadata = [] formatter = SRTFormatter() path = Path(f"subtitles/{language}") path.mkdir(parents=True, exist_ok=True) - playlist_videos = Playlist.getVideos( - "https://youtube.com/playlist?list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o" - ) + playlist_videos = Playlist.getVideos("https://youtube.com/playlist?list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o") for idx, video in enumerate(playlist_videos["videos"]): video_id = video["id"] @@ -34,7 +33,9 @@ def generate_subtitles(language: str, youtube_language_code: str=None): # Map mismatched language codes if language not in languages: if youtube_language_code is None: - raise ValueError(f"Language code {language} not found in YouTube's list of supported language: {languages}. Please provide a value for `youtube_language_code` and try again.") + raise ValueError( + f"Language code {language} not found in YouTube's list of supported language: {languages}. Please provide a value for `youtube_language_code` and try again." + ) language_code = youtube_language_code else: language_code = language @@ -55,10 +56,11 @@ def generate_subtitles(language: str, youtube_language_code: str=None): df = pd.DataFrame(metadata) df.to_csv(f"subtitles/{language}/metadata.csv", index=False) + if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--language", type=str, help="Language to generate subtitles for") parser.add_argument("--youtube_language_code", type=str, help="YouTube language code") args = parser.parse_args() generate_subtitles(args.language, args.youtube_language_code) - print(f"All done! Subtitles stored at subtitles/{args.language}") \ No newline at end of file + print(f"All done! Subtitles stored at subtitles/{args.language}") diff --git a/utils/validate_translation.py b/utils/validate_translation.py index ef28a00fa..b3892c2cc 100644 --- a/utils/validate_translation.py +++ b/utils/validate_translation.py @@ -6,10 +6,9 @@ PATH_TO_COURSE = Path("chapters/") + def load_sections(language: str): - toc = yaml.safe_load( - open(os.path.join(PATH_TO_COURSE / language, "_toctree.yml"), "r") - ) + toc = yaml.safe_load(open(os.path.join(PATH_TO_COURSE / language, "_toctree.yml"), "r")) sections = [] for chapter in toc: for section in chapter["sections"]: @@ -35,4 +34,4 @@ def load_sections(language: str): for section in missing_sections: print(section) else: - print("✅ No missing sections - translation complete!") \ No newline at end of file + print("✅ No missing sections - translation complete!") From ae1b02da2a69b1183eff5c561ccf3ebf2c76adb3 Mon Sep 17 00:00:00 2001 From: lewtun Date: Thu, 11 May 2023 16:13:10 +0200 Subject: [PATCH 46/51] Bump release (#568) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Refactor tokenization of targets for transformers v4.22 (#316) * Refactor tokenization of targets for transformers v4.22 * typo fix (#319) line no.49 Changed _SQuAD_it-text.json_-> _SQuAD_it-test.json_ * [FR] Many corrections (#318) * Fix URL to the Pile (#324) * Fix URL to the Pile * [RU] ch5 (#317) * fix: book url (#323) * zh-CN - Chapter 7,8,9finished (#315) Co-authored-by: Lewis Tunstall * Refactor events (#261) * Fix whole word masking labels (#326) * Fix question answering indices (#327) * Add translation checker (#329) * [FR] Refactor events (#330) * Translation Chapter 4 (#325) * update author list (de) (#331) * Fix Russian ToC (#332) * Refactor dataset upload in Chapter 5 / section 5 (#334) * Fix id2label types (#337) * Fix keywords in de quiz chapter 3 (#338) Noticed two `undefined` in the new render, because the `text` key was capitalized. * Tweak course validator (#340) * [Italian] Added Ch2/3 and Ch2/4 (#322) * Completes chapter 1 (#341) * Create 5.mdx and translate it into Japanese. * Create 6.mdx and translate it into Japanese. * done chapter1.2,1.3 * Create 4.mdx and translate it into Japanese. * Slightly modified * Slightly modified * Slightly modified * TF generation fixes (#344) * Fixes to chapter 7 Co-authored-by: lewtun * i18n: ES - translate file chapter2/6.mdx (#346) * Typo in russian translation (#349) It should be "Обучающий цикл" not "Обучающий цикла" * Remove translated string (#350) * [It] Ch2/5, Ch2/6, Ch2/7 (#353) * Add FAQ (#354) * i18n: ES - translate file chapter2/7.mdx (#347) * [id] Add translation to Bahasa Indonesia for chapter0 & some of chapter1 (#351) * i18n: ES - chapter2/8.mdx (#352) * Update 4.mdx based on the advice. * [de] Translation Chapter 1 (#336) * Update 1.mdx (#356) * Update 1.mdx (#357) * removed original english texts to open pull request * removed original english texts to open pull request * removed original english texts to open pull request * add lines for chap1/4 to 6 * Slightly modified * modify 2.mdx, 3.mdx * modify _toctree.yml * Update pr docs actions (#369) * Add Python syntax highlighting (#370) * [FR] Add FAQ and more (#367) Co-authored-by: Lewis Tunstall * [RU] Chapter 6 (1/2) finished (#368) * Spanish translation of Chapter 5 (#366) Co-authored-by: Lewis Tunstall * Add Japanese trasnlation of chapter 1/ 7 to 10 (#359) * Adding Portuguese Translation to Chapter3 (#361) * make style * Typo in Chapter 2, Section 2 (#364) Replace "inputs" with "outputs". * Revert "Update pr docs actions (#369)" This reverts commit 44f77be2fb1898e43c309b7cb8191a13353856cb. * Typo (#374) * Chapter 9 - Italian (#373) * Fix notebook link (#378) * docs: feat: chapter2-1 in Korean (#375) Review by @lewtun 22/11/22 docs: fix: remove commented toc for future contributors * Migrate Spaces URLs to new domain (#379) * docs: feat: same links across languages (#380) Added custom anchors using double square brackets, e.g. [[formatted-anchor]] * Add video transcripts (#150) * docs: fix: Accurate for the origin (English) subtitles (#384) * docs: i18n: add zh-CN machine translation (#385) * [FR] Notebooks links (#386) * Upgrade python version in the workflow (#402) * Update README.md (#389) Add that preview does not work with windows * translated chapter2_1-3 (#392) * fixes small typos (#397) * Add Chap2/4.mdx and 5.mdx (#391) Co-authored-by: 長澤春希 * created new script for converting bilingual captions to monolingual caption (#399) * Add French YouTube videos transcription (#410) * docs(zh-cn): Reviewed 56_data-processing-for-masked-language-modeling.srt (#400) * docs(zh-cn): Reviewed 57_what-is-perplexity.srt (#401) * reviewed ep.58 (#405) * reviewed ep.59 (#406) * docs(zh-cn): Reviewed 60_what-is-the-bleu-metric.srt (#407) * finished review (#408) * docs(zh-cn): Reviewed 61_data-processing-for-summarization.srt (#409) * Fix subtitle - translation data processing (#411) * [FR] Final PR (#412) * [ko] Add chapter 8 translation (#417) * docs(zh-cn): Reviewed 62_what-is-the-rouge-metric.srt (#419) * finished review * fixed errors in original english subtitle * fixed errors (#420) * docs(zh-cn): Reviewed 63_data-processing-for-causal-language-modeling.srt (#421) * Update 63_data-processing-for-causal-language-modeling.srt * finished review * Update 63_data-processing-for-causal-language-modeling.srt * docs(zh-cn): Reviewed 65_data-processing-for-question-answering.srt (#423) * finished review * finished review * finished review (#422) * Add Ko chapter2 2.mdx (#418) * Add Ko chapter2 2.mdx * [ko] Add chapter 8 translation (#417) * docs(zh-cn): Reviewed 62_what-is-the-rouge-metric.srt (#419) * finished review * fixed errors in original english subtitle * fixed errors (#420) * docs(zh-cn): Reviewed 63_data-processing-for-causal-language-modeling.srt (#421) * Update 63_data-processing-for-causal-language-modeling.srt * finished review * Update 63_data-processing-for-causal-language-modeling.srt * docs(zh-cn): Reviewed 65_data-processing-for-question-answering.srt (#423) * finished review * finished review * finished review (#422) * Add Ko chapter2 2.mdx Co-authored-by: IL-GU KIM <53106649+dlfrnaos19@users.noreply.github.com> Co-authored-by: Yuan * update textbook link (#427) * Visual fixes (#428) * finish first round review (#429) * Fix French subtitles + refactor conversion script (#431) * Fix subtitles and scripts * Fix subtitle * Add tokenizer to MLM Trainer (#432) * Fix FR video descriptions (#433) * Fix FR video descriptions * Rename file * Fix dead GPT model docs link. (#430) * Translate into Korean: 2-3 (#434) Co-authored-by: “Ryan” <“sungjin.712@navercorp.com”> * Add korean translation of chapter5 (1,2) (#441) update toctree for chapter 5 (1, 2) ensure same title for 5-2 add updates from upstream English with custom anchors Co-Authored-By: Minho Ryu Co-authored-by: Meta Learner응용개발팀 류민호 Co-authored-by: Minho Ryu * Update 3.mdx (#444) * docs(zh-cn): Reviewed 67_the-post-processing-step-in-question-answering-(tensorflow).srt (#447) * Update 67_the-post-processing-step-in-question-answering-(tensorflow).srt * finished review * docs(zh-cn): Reviewed 66_the-post-processing-step-in-question-answering-(pytorch).srt (#448) * Update 66_the-post-processing-step-in-question-answering-(pytorch).srt * finished review * refined translation * docs(zh-cn): Reviewed 01_the-pipeline-function.srt (#452) * finish review * Update subtitles/zh-CN/01_the-pipeline-function.srt Co-authored-by: Luke Cheng <2258420+chenglu@users.noreply.github.com> Co-authored-by: Luke Cheng <2258420+chenglu@users.noreply.github.com> * finish review (#453) * Revise some unnatural translations (#458) Some unnatural translations have been revised to use expressions more popular with Chinese readers * Fix chapter 5 links (#461) * fix small typo (#460) * Add Ko chapter2 3~8.mdx & Modify Ko chapter2 2.mdx typo (#446) * Add captions for tasks videos (#464) * Add captions for tasks videos * Fix script * [FR] Add 🤗 Tasks videos (#468) * Synchronous Chinese course update Update the Chinese Course document to sha:f71cf6c3b4cb235bc75a14416c6e8a57fc3d00a7 sha date: 2023/01/06 00:02:26 UTC+8 * review sync * Update 3.mdx * format zh_CN * format all mdx * Remove temp folder * finished review (#449) * docs(zh-cn): Reviewed 31_navigating-the-model-hub.srt (#451) * docs(zh-cn): Reviewed No. 08 - What happens inside the pipeline function? (PyTorch) (#454) * docs(zh-cn): Reviewed 03_what-is-transfer-learning.srt (#457) * docs(zh-cn): 32_managing-a-repo-on-the-model-hub.srt (#469) * docs(zh-cn): Reviewed No. 10 - Instantiate a Transformers model (PyTorch) (#472) * update Chinese translation 有一些英文句子与中文语序是相反的,我直接按照最终的中文语序排列了,这样是否可以? * finish first round review * finish second round review * finish second round review * branch commit * Update subtitles/zh-CN/10_instantiate-a-transformers-model-(pytorch).srt Co-authored-by: Luke Cheng <2258420+chenglu@users.noreply.github.com> * Update subtitles/zh-CN/10_instantiate-a-transformers-model-(pytorch).srt Co-authored-by: Luke Cheng <2258420+chenglu@users.noreply.github.com> --------- Co-authored-by: Luke Cheng <2258420+chenglu@users.noreply.github.com> * docs(zh-cn): 33_the-push-to-hub-api-(pytorch).srt (#473) * docs(zh-cn): Reviewed 34_the-push-to-hub-api-(tensorflow).srt (#479) * running python utils/code_formatter.py * review 05 cn translations * review 06 cn translations * Review No.11 * translate no.24 * review 06 cn translations * review 07 cn translations * Update 23_what-is-dynamic-padding.srt * Update 23_what-is-dynamic-padding.srt * Update 23_what-is-dynamic-padding.srt * Update subtitles/zh-CN/23_what-is-dynamic-padding.srt Co-authored-by: Luke Cheng <2258420+chenglu@users.noreply.github.com> * Update subtitles/zh-CN/23_what-is-dynamic-padding.srt Co-authored-by: Luke Cheng <2258420+chenglu@users.noreply.github.com> * add blank * Review No. 11, No. 12 * Review No. 13 * Review No. 12 * Review No. 14 * finished review * optimized translation * optimized translation * docs(zh-cn): Reviewed No. 29 - Write your training loop in PyTorch * Review 15 * Review 16 * Review 17 * Review 18 * Review ch 72 translation * Update 72 cn translation * To be reviewed No.42-No.54 * No.11 check-out * No.12 check-out * No. 13 14 check-out * No. 15 16 check-out * No. 17 18 check-out * Add note for "token-*" * Reviewed No.8, 9, 10 * Reviewed No.42 * Review No.43 * finished review * optimized translation * finished review * optimized translation * Review 44(need refine) * Review 45(need refine) * Review No. 46 (need refine) * Review No.47 * Review No.46 * Review No.45 * Review No.44 * Review No.48 * Review No.49 * Review No.50 * Modify Ko chapter2 8.mdx (#465) * Add Ko chapter2 2.mdx * Add Ko chapter2 2.mdx * Add Ko chapter2 3.mdx & 4.mdx * Modify Ko chapter2 3.mdx & 4.mdx * Modify Ko chapter2 3.mdx & 4.mdx * Modify Ko chapter2 3.mdx & 4.mdx * Modify _toctree.yml * Add Ko chapter2 5.mdx * Modify Ko chapter2 4.mdx * Add doc-builder step * Add Ko chapter2 6~8.mdx & Modify Ko chapter2 2.mdx typo * Modify Ko _toctree.yml * Modify Ko chapter2 8.mdx & README.md * Fixed typo (#471) * fixed subtitle errors (#474) timestamp: 00:00:26,640 --> 00:00:28,620 modification: notification --> authentication timestamp: 00:04:21,113 --> 00:04:22,923 modification: of --> or * Fixed a typo (#475) * Update 3.mdx (#526) Fix typo * [zh-TW] Added chapters 1-9 (#477) The translation is based on Simplified Chinese version, converted via OpenCC and fixed some formatting issues. * finished review * Explain why there are more tokens, than reviews (#476) * Explain why there are more tokens, than reviews * Update chapters/en/chapter5/3.mdx --------- Co-authored-by: lewtun * [RU] Subtitles for Chapter 1 of the video course (#489) * Created a directory for the russian subtitles. Created a folder for Russian subtitles for the video course and published a translation of the introductory video from chapter 1. * Uploaded subtitles for chapter 1 Uploaded subtitles for the remaining videos for chapter 1 of the video course. * Added subtitles for chapter 2 of the video course Added STR subtitle files for the second chapter of the YouTube video course. * Delete subtitles/ru directory Removed the old translation. Incorrect timestamping. * Create 00_welcome-to-the-hugging-face-course.srt Create a directory and upload a subtitle file for the introductory video of the course. * Add files via upload Upload subtitle files for the first chapter of the course. * Review No.52 * [ru] Added the glossary and translation guide (#490) * Added the glossary and translation guide * Fixed casing * Minor fixes * Updated glossary * Glossary update * Glossary update * Glossary update * [ru] Chapters 0 and 1 proofreading, updating and translating missing sections (#491) * Chapter 0 proofreading * Chapter 1 Section 1 proofreading - Added new people from English version; - Added links to creator's pages; - Added FAQ translation; * Chapter 1 Sections 2-5 proofreading * Chapter 1 Sections 6-9 proofreading * Final proofreading and added missing quiz section * Minor spelling corrections * Review No.51 * Review No.53 * Review No.54 * finished review * modified translation * modified translation * modified subtitle use the same text appeared in video * translated * Fix typo (#532) * review chapter4/2 * review chapter4/2 * review chapter4/2 * Review 75 * Review No.20, need review some * docs(zh-cn): Reviewed Chapter 7/1 * Update 1.mdx * Review No.22 * Review No.21 (need refinement) * Review No.30, need review: 26 27 28 30 73 74 * Review 30 (good) * Review 20 * Review 21 (refine) * Review 21 * Review 22 * Review 26 * Review 27 * Review 28 * Review 30 * Review 73 * Review 74 * Review 26-28, 42-54, 73-75 * Demo link fixes (#562) * demo link fixes * minor demo fix --------- Co-authored-by: Aravind Kumar Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Pavel <60391448+pdumin@users.noreply.github.com> Co-authored-by: buti1021 Co-authored-by: 1375626371 <40328311+1375626371@users.noreply.github.com> Co-authored-by: Fabrizio Damicelli <40115969+fabridamicelli@users.noreply.github.com> Co-authored-by: Jesper Dramsch Co-authored-by: Acciaro Gennaro Daniele Co-authored-by: Caterina Bonan <97481648+CaterinaBi@users.noreply.github.com> Co-authored-by: Haruki Nagasawa <48641467+haruki-N@users.noreply.github.com> Co-authored-by: blackdoor571 Co-authored-by: Matt Co-authored-by: Angel Mendez Co-authored-by: Artem Vysotsky <420428+vood@users.noreply.github.com> Co-authored-by: Gusti Adli Anshari <54521960+gstdl@users.noreply.github.com> Co-authored-by: Marcus Fraaß Co-authored-by: Christopher Akiki Co-authored-by: Mishig Co-authored-by: David Gilbertson Co-authored-by: Camilo Martínez Burgos Co-authored-by: Hiroaki Funayama <47347722+hiro819@users.noreply.github.com> Co-authored-by: Cesar0106 <62568082+Cesar0106@users.noreply.github.com> Co-authored-by: Younes Belkada <49240599+younesbelkada@users.noreply.github.com> Co-authored-by: Filippo Broggini Co-authored-by: Mishig Co-authored-by: Nanachi <70123136+NanachiHub@users.noreply.github.com> Co-authored-by: Edoardo Abati <29585319+EdAbati@users.noreply.github.com> Co-authored-by: Wonhyeong Seo Co-authored-by: Luke Cheng <2258420+chenglu@users.noreply.github.com> Co-authored-by: xianbaoqian <38108242+xianbaoqian@users.noreply.github.com> Co-authored-by: Thomas Simonini Co-authored-by: Subaru Kimura <91125552+blackdoor571@users.noreply.github.com> Co-authored-by: Carlos Santos Garcia <62998863+carlossantosgarcia@users.noreply.github.com> Co-authored-by: 長澤春希 Co-authored-by: Yuan Co-authored-by: IL-GU KIM <53106649+dlfrnaos19@users.noreply.github.com> Co-authored-by: Kim Bo Geum <53206051+nsbg@users.noreply.github.com> Co-authored-by: Bartosz Szmelczynski <43574448+Bearnardd@users.noreply.github.com> Co-authored-by: Shawn Lee Co-authored-by: Naveen Reddy D Co-authored-by: rainmaker Co-authored-by: “Ryan” <“sungjin.712@navercorp.com”> Co-authored-by: Meta Learner응용개발팀 류민호 Co-authored-by: Minho Ryu Co-authored-by: richardachen <85973297+richardachen@users.noreply.github.com> Co-authored-by: beyondguo <37113676+beyondguo@users.noreply.github.com> Co-authored-by: bsenst Co-authored-by: 1375626371 <1375626371@qq.com> Co-authored-by: yaoqih <40328311+yaoqih@users.noreply.github.com> Co-authored-by: 李洋 <45715979+innovation64@users.noreply.github.com> Co-authored-by: PowerChina Co-authored-by: chenglu99 Co-authored-by: iCell Co-authored-by: Qi Zhang Co-authored-by: researcher <1131419673@qq.com> Co-authored-by: simpleAI Co-authored-by: FYJNEVERFOLLOWS Co-authored-by: zhangchaosd Co-authored-by: TK Buristrakul Co-authored-by: Carlos Aguayo Co-authored-by: ateliershen Co-authored-by: Pavel Nesterov Co-authored-by: Artyom Boyko Co-authored-by: Kirill Milintsevich Co-authored-by: jybarnes21 Co-authored-by: gxy-gxy <1115404657@qq.com> Co-authored-by: iLeGend <824040212@qq.com> Co-authored-by: Maria Khalusova --- README.md | 2 +- chapters/en/chapter4/3.mdx | 2 +- chapters/en/chapter5/3.mdx | 2 +- chapters/en/chapter6/3.mdx | 2 +- chapters/en/chapter7/3.mdx | 4 +- chapters/en/chapter9/5.mdx | 8 +- chapters/en/chapter9/7.mdx | 2 +- chapters/en/events/3.mdx | 2 +- chapters/it/chapter2/2.mdx | 2 +- chapters/ko/chapter2/8.mdx | 10 +- chapters/ru/TRANSLATING.txt | 47 + chapters/ru/_toctree.yml | 14 +- chapters/ru/chapter0/1.mdx | 16 +- chapters/ru/chapter1/1.mdx | 71 +- chapters/ru/chapter1/10.mdx | 258 ++++ chapters/ru/chapter1/2.mdx | 16 +- chapters/ru/chapter1/3.mdx | 43 +- chapters/ru/chapter1/4.mdx | 74 +- chapters/ru/chapter1/5.mdx | 8 +- chapters/ru/chapter1/6.mdx | 6 +- chapters/ru/chapter1/7.mdx | 6 +- chapters/ru/chapter1/8.mdx | 4 +- chapters/ru/chapter1/9.mdx | 12 +- chapters/ru/glossary/1.mdx | 146 ++ chapters/zh-CN/_toctree.yml | 2 +- chapters/zh-CN/chapter0/1.mdx | 8 +- chapters/zh-CN/chapter1/1.mdx | 73 +- chapters/zh-CN/chapter1/10.mdx | 3 +- chapters/zh-CN/chapter1/2.mdx | 6 +- chapters/zh-CN/chapter1/3.mdx | 24 +- chapters/zh-CN/chapter1/4.mdx | 21 +- chapters/zh-CN/chapter1/5.mdx | 2 +- chapters/zh-CN/chapter1/6.mdx | 4 +- chapters/zh-CN/chapter1/7.mdx | 2 +- chapters/zh-CN/chapter1/8.mdx | 2 +- chapters/zh-CN/chapter1/9.mdx | 2 +- chapters/zh-CN/chapter2/1.mdx | 2 +- chapters/zh-CN/chapter2/2.mdx | 18 +- chapters/zh-CN/chapter2/3.mdx | 12 +- chapters/zh-CN/chapter2/4.mdx | 20 +- chapters/zh-CN/chapter2/5.mdx | 12 +- chapters/zh-CN/chapter2/6.mdx | 6 +- chapters/zh-CN/chapter2/7.mdx | 2 +- chapters/zh-CN/chapter2/8.mdx | 6 +- chapters/zh-CN/chapter3/1.mdx | 2 +- chapters/zh-CN/chapter3/2.mdx | 10 +- chapters/zh-CN/chapter3/3.mdx | 10 +- chapters/zh-CN/chapter3/3_tf.mdx | 10 +- chapters/zh-CN/chapter3/4.mdx | 10 +- chapters/zh-CN/chapter3/5.mdx | 2 +- chapters/zh-CN/chapter3/6.mdx | 4 +- chapters/zh-CN/chapter4/1.mdx | 2 +- chapters/zh-CN/chapter4/2.mdx | 19 +- chapters/zh-CN/chapter4/3.mdx | 16 +- chapters/zh-CN/chapter4/4.mdx | 22 +- chapters/zh-CN/chapter4/5.mdx | 2 +- chapters/zh-CN/chapter4/6.mdx | 2 +- chapters/zh-CN/chapter5/1.mdx | 2 +- chapters/zh-CN/chapter5/2.mdx | 10 +- chapters/zh-CN/chapter5/3.mdx | 14 +- chapters/zh-CN/chapter5/4.mdx | 8 +- chapters/zh-CN/chapter5/5.mdx | 85 +- chapters/zh-CN/chapter5/6.mdx | 27 +- chapters/zh-CN/chapter5/7.mdx | 2 +- chapters/zh-CN/chapter5/8.mdx | 2 +- chapters/zh-CN/chapter6/1.mdx | 2 +- chapters/zh-CN/chapter6/10.mdx | 2 +- chapters/zh-CN/chapter6/2.mdx | 8 +- chapters/zh-CN/chapter6/3.mdx | 14 +- chapters/zh-CN/chapter6/3b.mdx | 10 +- chapters/zh-CN/chapter6/4.mdx | 10 +- chapters/zh-CN/chapter6/5.mdx | 10 +- chapters/zh-CN/chapter6/6.mdx | 12 +- chapters/zh-CN/chapter6/7.mdx | 12 +- chapters/zh-CN/chapter6/8.mdx | 10 +- chapters/zh-CN/chapter6/9.mdx | 2 +- chapters/zh-CN/chapter7/1.mdx | 18 +- chapters/zh-CN/chapter7/2.mdx | 49 +- chapters/zh-CN/chapter7/3.mdx | 30 +- chapters/zh-CN/chapter7/4.mdx | 125 +- chapters/zh-CN/chapter7/5.mdx | 86 +- chapters/zh-CN/chapter7/6.mdx | 36 +- chapters/zh-CN/chapter7/7.mdx | 51 +- chapters/zh-CN/chapter7/8.mdx | 2 +- chapters/zh-CN/chapter7/9.mdx | 2 +- chapters/zh-CN/chapter8/1.mdx | 2 +- chapters/zh-CN/chapter8/2.mdx | 6 +- chapters/zh-CN/chapter8/3.mdx | 12 +- chapters/zh-CN/chapter8/4.mdx | 61 +- chapters/zh-CN/chapter8/4_tf.mdx | 30 +- chapters/zh-CN/chapter8/5.mdx | 16 +- chapters/zh-CN/chapter8/6.mdx | 2 +- chapters/zh-CN/chapter8/7.mdx | 5 +- chapters/zh-CN/chapter9/1.mdx | 8 +- chapters/zh-CN/chapter9/2.mdx | 4 +- chapters/zh-CN/chapter9/3.mdx | 12 +- chapters/zh-CN/chapter9/4.mdx | 10 +- chapters/zh-CN/chapter9/5.mdx | 6 +- chapters/zh-CN/chapter9/6.mdx | 6 +- chapters/zh-CN/chapter9/7.mdx | 14 +- chapters/zh-CN/chapter9/8.mdx | 4 +- chapters/zh-CN/chapter9/9.mdx | 2 +- chapters/zh-CN/events/2.mdx | 6 +- chapters/zh-TW/_toctree.yml | 201 ++- chapters/zh-TW/chapter0/1.mdx | 32 +- chapters/zh-TW/chapter1/1.mdx | 57 + chapters/zh-TW/chapter1/10.mdx | 258 ++++ chapters/zh-TW/chapter1/2.mdx | 25 + chapters/zh-TW/chapter1/3.mdx | 287 ++++ chapters/zh-TW/chapter1/4.mdx | 177 +++ chapters/zh-TW/chapter1/5.mdx | 22 + chapters/zh-TW/chapter1/6.mdx | 22 + chapters/zh-TW/chapter1/7.mdx | 21 + chapters/zh-TW/chapter1/8.mdx | 31 + chapters/zh-TW/chapter1/9.mdx | 16 + chapters/zh-TW/chapter2/1.mdx | 23 + chapters/zh-TW/chapter2/2.mdx | 359 +++++ chapters/zh-TW/chapter2/3.mdx | 264 ++++ chapters/zh-TW/chapter2/4.mdx | 239 ++++ chapters/zh-TW/chapter2/5.mdx | 355 +++++ chapters/zh-TW/chapter2/6.mdx | 165 +++ chapters/zh-TW/chapter2/7.mdx | 32 + chapters/zh-TW/chapter2/8.mdx | 298 ++++ chapters/zh-TW/chapter3/1.mdx | 26 + chapters/zh-TW/chapter3/2.mdx | 383 ++++++ chapters/zh-TW/chapter3/3.mdx | 172 +++ chapters/zh-TW/chapter3/3_tf.mdx | 190 +++ chapters/zh-TW/chapter3/4.mdx | 358 +++++ chapters/zh-TW/chapter3/5.mdx | 25 + chapters/zh-TW/chapter3/6.mdx | 289 ++++ chapters/zh-TW/chapter4/1.mdx | 20 + chapters/zh-TW/chapter4/2.mdx | 97 ++ chapters/zh-TW/chapter4/3.mdx | 648 +++++++++ chapters/zh-TW/chapter4/4.mdx | 87 ++ chapters/zh-TW/chapter4/5.mdx | 12 + chapters/zh-TW/chapter4/6.mdx | 220 +++ chapters/zh-TW/chapter5/1.mdx | 22 + chapters/zh-TW/chapter5/2.mdx | 167 +++ chapters/zh-TW/chapter5/3.mdx | 743 ++++++++++ chapters/zh-TW/chapter5/4.mdx | 287 ++++ chapters/zh-TW/chapter5/5.mdx | 461 +++++++ chapters/zh-TW/chapter5/6.mdx | 526 +++++++ chapters/zh-TW/chapter5/7.mdx | 16 + chapters/zh-TW/chapter5/8.mdx | 221 +++ chapters/zh-TW/chapter6/1.mdx | 19 + chapters/zh-TW/chapter6/10.mdx | 273 ++++ chapters/zh-TW/chapter6/2.mdx | 256 ++++ chapters/zh-TW/chapter6/3.mdx | 473 +++++++ chapters/zh-TW/chapter6/3b.mdx | 639 +++++++++ chapters/zh-TW/chapter6/4.mdx | 124 ++ chapters/zh-TW/chapter6/5.mdx | 360 +++++ chapters/zh-TW/chapter6/6.mdx | 373 +++++ chapters/zh-TW/chapter6/7.mdx | 381 ++++++ chapters/zh-TW/chapter6/8.mdx | 564 ++++++++ chapters/zh-TW/chapter6/9.mdx | 16 + chapters/zh-TW/chapter7/1.mdx | 33 + chapters/zh-TW/chapter7/2.mdx | 978 +++++++++++++ chapters/zh-TW/chapter7/3.mdx | 1045 ++++++++++++++ chapters/zh-TW/chapter7/4.mdx | 996 ++++++++++++++ chapters/zh-TW/chapter7/5.mdx | 1047 ++++++++++++++ chapters/zh-TW/chapter7/6.mdx | 906 ++++++++++++ chapters/zh-TW/chapter7/7.mdx | 1210 +++++++++++++++++ chapters/zh-TW/chapter7/8.mdx | 17 + chapters/zh-TW/chapter7/9.mdx | 311 +++++ chapters/zh-TW/chapter8/1.mdx | 12 + chapters/zh-TW/chapter8/2.mdx | 364 +++++ chapters/zh-TW/chapter8/3.mdx | 166 +++ chapters/zh-TW/chapter8/4.mdx | 787 +++++++++++ chapters/zh-TW/chapter8/4_tf.mdx | 489 +++++++ chapters/zh-TW/chapter8/5.mdx | 85 ++ chapters/zh-TW/chapter8/6.mdx | 7 + chapters/zh-TW/chapter8/7.mdx | 190 +++ chapters/zh-TW/chapter9/1.mdx | 36 + chapters/zh-TW/chapter9/2.mdx | 112 ++ chapters/zh-TW/chapter9/3.mdx | 167 +++ chapters/zh-TW/chapter9/4.mdx | 144 ++ chapters/zh-TW/chapter9/5.mdx | 66 + chapters/zh-TW/chapter9/6.mdx | 97 ++ chapters/zh-TW/chapter9/7.mdx | 236 ++++ chapters/zh-TW/chapter9/8.mdx | 19 + chapters/zh-TW/chapter9/9.mdx | 231 ++++ chapters/zh-TW/events/2.mdx | 165 +++ subtitles/README.md | 6 +- .../en/33_the-push-to-hub-api-(pytorch).srt | 4 +- .../41_text-embeddings-&-semantic-search.srt | 8 +- subtitles/en/metadata_tasks.csv | 7 + subtitles/en/raw/tasks.md | 77 ++ ...37\244\227-tasks-token-classification.srt" | 116 ++ ...\237\244\227-tasks-question-answering.srt" | 87 ++ ...44\227-tasks-causal-language-modeling.srt" | 63 + ...44\227-tasks-masked-language-modeling.srt" | 85 ++ ..._\360\237\244\227-tasks-summarization.srt" | 68 + ...05_\360\237\244\227-tasks-translation.srt" | 96 ++ ...rocessing-for-causal-language-modeling.srt | 6 +- subtitles/fr/metadata_tasks.csv | 7 + ...37\244\227-tasks-token-classification.srt" | 99 ++ ...\237\244\227-tasks-question-answering.srt" | 71 + ...44\227-tasks-causal-language-modeling.srt" | 51 + ...44\227-tasks-masked-language-modeling.srt" | 71 + ..._\360\237\244\227-tasks-summarization.srt" | 55 + ...05_\360\237\244\227-tasks-translation.srt" | 83 ++ subtitles/fr/titles-and-descriptions.txt | 100 +- .../00_welcome-to-the-hugging-face-course.srt | 411 ++++++ subtitles/ru/01_the-pipeline-function.srt | 400 ++++++ ...2_the-carbon-footprint-of-transformers.srt | 516 +++++++ subtitles/ru/03_what-is-transfer-learning.srt | 360 +++++ .../ru/04_the-transformer-architecture.srt | 256 ++++ .../ru/05_transformer-models-encoders.srt | 407 ++++++ .../ru/06_transformer-models-decoders.srt | 348 +++++ ...07_transformer-models-encoder-decoders.srt | 260 ++++ .../zh-CN/03_what-is-transfer-learning.srt | 122 +- .../zh-CN/05_transformer-models-encoders.srt | 112 +- .../zh-CN/06_transformer-models-decoders.srt | 116 +- ...07_transformer-models-encoder-decoders.srt | 148 +- ...inside-the-pipeline-function-(pytorch).srt | 56 +- ...ide-the-pipeline-function-(tensorflow).srt | 38 +- ...antiate-a-transformers-model-(pytorch).srt | 141 +- ...iate-a-transformers-model-(tensorflow).srt | 49 +- subtitles/zh-CN/12_tokenizers-overview.srt | 15 +- subtitles/zh-CN/13_word-based-tokenizers.srt | 27 +- .../zh-CN/14_character-based-tokenizers.srt | 68 +- .../zh-CN/15_subword-based-tokenizers.srt | 145 +- .../zh-CN/16_the-tokenization-pipeline.srt | 89 +- .../17_batching-inputs-together-(pytorch).srt | 58 +- ..._batching-inputs-together-(tensorflow).srt | 59 +- ...ng-face-datasets-overview-(tensorflow).srt | 52 +- ...preprocessing-sentence-pairs-(pytorch).srt | 90 +- ...processing-sentence-pairs-(tensorflow).srt | 38 +- .../zh-CN/23_what-is-dynamic-padding.srt | 64 +- subtitles/zh-CN/24_the-trainer-api.srt | 178 +-- .../zh-CN/26_fine-tuning-with-tensorflow.srt | 36 +- ...arning-rate-scheduling-with-tensorflow.srt | 46 +- .../28_tensorflow-predictions-and-metrics.srt | 64 +- ...29_write-your-training-loop-in-pytorch.srt | 80 +- ...-pytorch-training-loop-with-accelerate.srt | 60 +- .../zh-CN/31_navigating-the-model-hub.srt | 104 +- .../32_managing-a-repo-on-the-model-hub.srt | 214 +-- .../33_the-push-to-hub-api-(pytorch).srt | 182 +-- .../34_the-push-to-hub-api-(tensorflow).srt | 232 ++-- .../zh-CN/35_loading-a-custom-dataset.srt | 70 +- ...dataframes-=-\342\235\244\357\270\217.srt" | 76 +- .../38_saving-and-reloading-a-dataset.srt | 66 +- .../zh-CN/39_memory-mapping-&-streaming.srt | 58 +- .../40_uploading-a-dataset-to-the-hub.srt | 60 +- .../41_text-embeddings-&-semantic-search.srt | 92 +- .../zh-CN/42_training-a-new-tokenizer.srt | 116 +- ...43_why-are-fast-tokenizers-called-fast.srt | 50 +- .../zh-CN/44_fast-tokenizer-superpowers.srt | 88 +- ...oken-classification-pipeline-(pytorch).srt | 50 +- ...n-classification-pipeline-(tensorflow).srt | 52 +- ...-question-answering-pipeline-(pytorch).srt | 68 +- ...estion-answering-pipeline-(tensorflow).srt | 64 +- subtitles/zh-CN/49_what-is-normalization.srt | 70 +- .../zh-CN/50_what-is-pre-tokenization.srt | 38 +- .../51_byte-pair-encoding-tokenization.srt | 50 +- subtitles/zh-CN/52_wordpiece-tokenization.srt | 28 +- subtitles/zh-CN/53_unigram-tokenization.srt | 96 +- .../zh-CN/54_building-a-new-tokenizer.srt | 74 +- subtitles/zh-CN/68_data-collators-a-tour.srt | 146 +- .../72_asking-for-help-on-the-forums.srt | 122 +- ...ugging-the-training-pipeline-(pytorch).srt | 60 +- ...ing-the-training-pipeline-(tensorflow).srt | 46 +- subtitles/zh-CN/75_writing-a-good-issue.srt | 30 +- utils/generate_subtitles.py | 36 +- 264 files changed, 28757 insertions(+), 2940 deletions(-) create mode 100644 chapters/ru/TRANSLATING.txt create mode 100644 chapters/ru/chapter1/10.mdx create mode 100644 chapters/ru/glossary/1.mdx create mode 100644 chapters/zh-TW/chapter1/1.mdx create mode 100644 chapters/zh-TW/chapter1/10.mdx create mode 100644 chapters/zh-TW/chapter1/2.mdx create mode 100644 chapters/zh-TW/chapter1/3.mdx create mode 100644 chapters/zh-TW/chapter1/4.mdx create mode 100644 chapters/zh-TW/chapter1/5.mdx create mode 100644 chapters/zh-TW/chapter1/6.mdx create mode 100644 chapters/zh-TW/chapter1/7.mdx create mode 100644 chapters/zh-TW/chapter1/8.mdx create mode 100644 chapters/zh-TW/chapter1/9.mdx create mode 100644 chapters/zh-TW/chapter2/1.mdx create mode 100644 chapters/zh-TW/chapter2/2.mdx create mode 100644 chapters/zh-TW/chapter2/3.mdx create mode 100644 chapters/zh-TW/chapter2/4.mdx create mode 100644 chapters/zh-TW/chapter2/5.mdx create mode 100644 chapters/zh-TW/chapter2/6.mdx create mode 100644 chapters/zh-TW/chapter2/7.mdx create mode 100644 chapters/zh-TW/chapter2/8.mdx create mode 100644 chapters/zh-TW/chapter3/1.mdx create mode 100644 chapters/zh-TW/chapter3/2.mdx create mode 100644 chapters/zh-TW/chapter3/3.mdx create mode 100644 chapters/zh-TW/chapter3/3_tf.mdx create mode 100644 chapters/zh-TW/chapter3/4.mdx create mode 100644 chapters/zh-TW/chapter3/5.mdx create mode 100644 chapters/zh-TW/chapter3/6.mdx create mode 100644 chapters/zh-TW/chapter4/1.mdx create mode 100644 chapters/zh-TW/chapter4/2.mdx create mode 100644 chapters/zh-TW/chapter4/3.mdx create mode 100644 chapters/zh-TW/chapter4/4.mdx create mode 100644 chapters/zh-TW/chapter4/5.mdx create mode 100644 chapters/zh-TW/chapter4/6.mdx create mode 100644 chapters/zh-TW/chapter5/1.mdx create mode 100644 chapters/zh-TW/chapter5/2.mdx create mode 100644 chapters/zh-TW/chapter5/3.mdx create mode 100644 chapters/zh-TW/chapter5/4.mdx create mode 100644 chapters/zh-TW/chapter5/5.mdx create mode 100644 chapters/zh-TW/chapter5/6.mdx create mode 100644 chapters/zh-TW/chapter5/7.mdx create mode 100644 chapters/zh-TW/chapter5/8.mdx create mode 100644 chapters/zh-TW/chapter6/1.mdx create mode 100644 chapters/zh-TW/chapter6/10.mdx create mode 100644 chapters/zh-TW/chapter6/2.mdx create mode 100644 chapters/zh-TW/chapter6/3.mdx create mode 100644 chapters/zh-TW/chapter6/3b.mdx create mode 100644 chapters/zh-TW/chapter6/4.mdx create mode 100644 chapters/zh-TW/chapter6/5.mdx create mode 100644 chapters/zh-TW/chapter6/6.mdx create mode 100644 chapters/zh-TW/chapter6/7.mdx create mode 100644 chapters/zh-TW/chapter6/8.mdx create mode 100644 chapters/zh-TW/chapter6/9.mdx create mode 100644 chapters/zh-TW/chapter7/1.mdx create mode 100644 chapters/zh-TW/chapter7/2.mdx create mode 100644 chapters/zh-TW/chapter7/3.mdx create mode 100644 chapters/zh-TW/chapter7/4.mdx create mode 100644 chapters/zh-TW/chapter7/5.mdx create mode 100644 chapters/zh-TW/chapter7/6.mdx create mode 100644 chapters/zh-TW/chapter7/7.mdx create mode 100644 chapters/zh-TW/chapter7/8.mdx create mode 100644 chapters/zh-TW/chapter7/9.mdx create mode 100644 chapters/zh-TW/chapter8/1.mdx create mode 100644 chapters/zh-TW/chapter8/2.mdx create mode 100644 chapters/zh-TW/chapter8/3.mdx create mode 100644 chapters/zh-TW/chapter8/4.mdx create mode 100644 chapters/zh-TW/chapter8/4_tf.mdx create mode 100644 chapters/zh-TW/chapter8/5.mdx create mode 100644 chapters/zh-TW/chapter8/6.mdx create mode 100644 chapters/zh-TW/chapter8/7.mdx create mode 100644 chapters/zh-TW/chapter9/1.mdx create mode 100644 chapters/zh-TW/chapter9/2.mdx create mode 100644 chapters/zh-TW/chapter9/3.mdx create mode 100644 chapters/zh-TW/chapter9/4.mdx create mode 100644 chapters/zh-TW/chapter9/5.mdx create mode 100644 chapters/zh-TW/chapter9/6.mdx create mode 100644 chapters/zh-TW/chapter9/7.mdx create mode 100644 chapters/zh-TW/chapter9/8.mdx create mode 100644 chapters/zh-TW/chapter9/9.mdx create mode 100644 chapters/zh-TW/events/2.mdx create mode 100644 subtitles/en/metadata_tasks.csv create mode 100644 subtitles/en/raw/tasks.md create mode 100644 "subtitles/en/tasks_00_\360\237\244\227-tasks-token-classification.srt" create mode 100644 "subtitles/en/tasks_01_\360\237\244\227-tasks-question-answering.srt" create mode 100644 "subtitles/en/tasks_02_\360\237\244\227-tasks-causal-language-modeling.srt" create mode 100644 "subtitles/en/tasks_03_\360\237\244\227-tasks-masked-language-modeling.srt" create mode 100644 "subtitles/en/tasks_04_\360\237\244\227-tasks-summarization.srt" create mode 100644 "subtitles/en/tasks_05_\360\237\244\227-tasks-translation.srt" create mode 100644 subtitles/fr/metadata_tasks.csv create mode 100644 "subtitles/fr/tasks_00_\360\237\244\227-tasks-token-classification.srt" create mode 100644 "subtitles/fr/tasks_01_\360\237\244\227-tasks-question-answering.srt" create mode 100644 "subtitles/fr/tasks_02_\360\237\244\227-tasks-causal-language-modeling.srt" create mode 100644 "subtitles/fr/tasks_03_\360\237\244\227-tasks-masked-language-modeling.srt" create mode 100644 "subtitles/fr/tasks_04_\360\237\244\227-tasks-summarization.srt" create mode 100644 "subtitles/fr/tasks_05_\360\237\244\227-tasks-translation.srt" create mode 100644 subtitles/ru/00_welcome-to-the-hugging-face-course.srt create mode 100644 subtitles/ru/01_the-pipeline-function.srt create mode 100644 subtitles/ru/02_the-carbon-footprint-of-transformers.srt create mode 100644 subtitles/ru/03_what-is-transfer-learning.srt create mode 100644 subtitles/ru/04_the-transformer-architecture.srt create mode 100644 subtitles/ru/05_transformer-models-encoders.srt create mode 100644 subtitles/ru/06_transformer-models-decoders.srt create mode 100644 subtitles/ru/07_transformer-models-encoder-decoders.srt diff --git a/README.md b/README.md index 7d253aa5c..b41485d8d 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ This repo contains the content that's used to create the **[Hugging Face course] | [Bahasa Indonesia](https://huggingface.co/course/id/chapter1/1) (WIP) | [`chapters/id`](https://github.com/huggingface/course/tree/main/chapters/id) | [@gstdl](https://github.com/gstdl) | | [Italian](https://huggingface.co/course/it/chapter1/1) (WIP) | [`chapters/it`](https://github.com/huggingface/course/tree/main/chapters/it) | [@CaterinaBi](https://github.com/CaterinaBi), [@ClonedOne](https://github.com/ClonedOne), [@Nolanogenn](https://github.com/Nolanogenn), [@EdAbati](https://github.com/EdAbati), [@gdacciaro](https://github.com/gdacciaro) | | [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) | +| [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) | | [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) | diff --git a/chapters/en/chapter4/3.mdx b/chapters/en/chapter4/3.mdx index a782152c6..6b27777f4 100644 --- a/chapters/en/chapter4/3.mdx +++ b/chapters/en/chapter4/3.mdx @@ -83,7 +83,7 @@ training_args = TrainingArguments( When you call `trainer.train()`, the `Trainer` will then upload your model to the Hub each time it is saved (here every epoch) in a repository in your namespace. That repository will be named like the output directory you picked (here `bert-finetuned-mrpc`) but you can choose a different name with `hub_model_id = "a_different_name"`. -To upload you model to an organization you are a member of, just pass it with `hub_model_id = "my_organization/my_repo_name"`. +To upload your model to an organization you are a member of, just pass it with `hub_model_id = "my_organization/my_repo_name"`. Once your training is finished, you should do a final `trainer.push_to_hub()` to upload the last version of your model. It will also generate a model card with all the relevant metadata, reporting the hyperparameters used and the evaluation results! Here is an example of the content you might find in a such a model card: diff --git a/chapters/en/chapter5/3.mdx b/chapters/en/chapter5/3.mdx index 3f9c37dc2..4a3ddc7b5 100644 --- a/chapters/en/chapter5/3.mdx +++ b/chapters/en/chapter5/3.mdx @@ -387,7 +387,7 @@ 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. -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). 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: +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: ```py tokenized_dataset = drug_dataset.map( diff --git a/chapters/en/chapter6/3.mdx b/chapters/en/chapter6/3.mdx index 62d143dcc..88250f6df 100644 --- a/chapters/en/chapter6/3.mdx +++ b/chapters/en/chapter6/3.mdx @@ -109,7 +109,7 @@ We can see that the tokenizer's special tokens `[CLS]` and `[SEP]` are mapped to -The notion of what a word is is complicated. For instance, does "I'll" (a contraction of "I will") count as one or two words? It actually depends on the tokenizer and the pre-tokenization operation it applies. Some tokenizers just split on spaces, so they will consider this as one word. Others use punctuation on top of spaces, so will consider it two words. +The notion of what a word is complicated. For instance, does "I'll" (a contraction of "I will") count as one or two words? It actually depends on the tokenizer and the pre-tokenization operation it applies. Some tokenizers just split on spaces, so they will consider this as one word. Others use punctuation on top of spaces, so will consider it two words. ✏️ **Try it out!** Create a tokenizer from the `bert-base-cased` and `roberta-base` checkpoints and tokenize "81s" with them. What do you observe? What are the word IDs? diff --git a/chapters/en/chapter7/3.mdx b/chapters/en/chapter7/3.mdx index a31bb432c..dc12fb8bc 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! @@ -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/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/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! 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..d74b9c206 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/it/chapter2/2.mdx b/chapters/it/chapter2/2.mdx index 5260f62c4..1471eb014 100644 --- a/chapters/it/chapter2/2.mdx +++ b/chapters/it/chapter2/2.mdx @@ -257,7 +257,7 @@ outputs = model(inputs) ``` {/if} -Ora, se osserviamo la forma dei nostri input, la dimensionalità sarà molto più bassa: la model head prende in input i vettori ad alta dimensionalità che abbiamo visto prima e produce vettori contenenti due valori (uno per etichetta): +Ora, se osserviamo la forma dei nostri output, la dimensionalità sarà molto più bassa: la model head prende in input i vettori ad alta dimensionalità che abbiamo visto prima e produce vettori contenenti due valori (uno per etichetta): ```python print(outputs.logits.shape) diff --git a/chapters/ko/chapter2/8.mdx b/chapters/ko/chapter2/8.mdx index ddc6f4558..49a23d412 100644 --- a/chapters/ko/chapter2/8.mdx +++ b/chapters/ko/chapter2/8.mdx @@ -88,16 +88,16 @@
-Это установка самой базовой версии 🤗 Transformers. В частности, никаких библиотек машинного обучения (как PyTorch или TensorFloat) установлено не будет. Так как мы будем использовать множество различных возможностей библиотеки 🤗 Transformers, мы рекомендуем установить версию для разработчиков, в составе которой сразу инсталлируются все необходимые зависимости: +Это установка самой базовой версии 🤗 Transformers. В частности, никаких библиотек машинного обучения (например, PyTorch или TensorFloat) установлено не будет. Так как мы будем использовать множество различных возможностей библиотеки 🤗 Transformers, мы рекомендуем установить версию для разработчиков, в состав которой сразу входят все необходимые зависимости: ``` !pip install transformers[sentencepiece] @@ -52,11 +52,11 @@ import transformers После установки Python у вас появится возможность запускать Python-команды в терминале. Прежде чем переходить дальше, запустите в терминале команду `python --version`. В результате должна быть распечатана версия Python, доступная для работы. -Когда вы запускаете Python-команду в терминале (например, `python --version`), эту команду обрабатывает _оснвной_ Python-интерпретатор вашей системы. Мы не рекомендуем устанавливать в его окружение дополнительные библиотеки, лучше для каждого проекта создавать виртуальные окружения. Каждый проект будет обладать собственными зависимостями и пакетами, если проекты будут в разных окружениях, то вам меньше придется следить за совместимостью бибилиотек. +Когда вы запускаете Python-команду в терминале (например, `python --version`), эту команду обрабатывает _основной_ Python-интерпретатор вашей системы. Мы не рекомендуем устанавливать в его окружение дополнительные библиотеки, лучше для каждого проекта создавать отдельное виртуальное окружение. Каждый проект будет обладать собственными зависимостями и пакетами, если проекты будут в разных окружениях, то вам меньше придется следить за совместимостью библиотек. В Python такой подход можно реализовать с помощью разных библиотек, а подробнее об окружениях можно почитать [тут](https://docs.python.org/3/tutorial/venv.html). Каждое окружение будет содержать в себе необходимую версию языка и набор библиотек. Все эти окружения изолированы друг от друга. Среди самых популярных инструментов для работы с виртуальными окружениями можно отметить [`venv`](https://docs.python.org/3/library/venv.html#module-venv). -Для начала создайте папку в домашней директории, в которой будут храниться ваши файлы курса (ее можно назвать произвольным именем, например: *transformers-course*): +Для начала создайте папку в домашней директории, в которой будут храниться ваши файлы курса (ее можно назвать произвольным именем, например, *transformers-course*): ``` mkdir ~/transformers-course @@ -84,7 +84,7 @@ ls -a # Активировать виртуальное окружение source .env/bin/activate -# Деактивировать окржуение +# Деактивировать окружение source .env/bin/deactivate ``` diff --git a/chapters/ru/chapter1/1.mdx b/chapters/ru/chapter1/1.mdx index 3c10ca68f..8d5b8a560 100644 --- a/chapters/ru/chapter1/1.mdx +++ b/chapters/ru/chapter1/1.mdx @@ -9,7 +9,7 @@ -В этом курсе вы научитесь основам обработки естесственного языка (NLP) с использованием библиотек от [Hugging Face](https://huggingface.co/). Экосистема состоит из: моделей ([🤗 Transformers](https://github.com/huggingface/transformers)), датасетов ([🤗 Datasets](https://github.com/huggingface/datasets)), вспомогательных бибилиотек ([🤗 Accelerate](https://github.com/huggingface/accelerate), [🤗 Tokenizers](https://github.com/huggingface/tokenizers)), а также репозитория [Hugging Face Hub](https://huggingface.co/models). Это полностью бесплатно! +В этом курсе вы научитесь основам обработки естественного языка (NLP) с использованием библиотек от [Hugging Face](https://huggingface.co/). Экосистема состоит из: моделей ([🤗 Transformers](https://github.com/huggingface/transformers)), датасетов ([🤗 Datasets](https://github.com/huggingface/datasets)), вспомогательных библиотек ([🤗 Accelerate](https://github.com/huggingface/accelerate), [🤗 Tokenizers](https://github.com/huggingface/tokenizers)), а также репозитория [Hugging Face Hub](https://huggingface.co/models). Это полностью бесплатно! ## Чего ожидать от курса? @@ -21,8 +21,8 @@ - Главы 1-4 содержат в себе введение в главные концепции библиотеки 🤗 Transformers. К концу этой части курса вы будете знакомы с тем, как функционируют трансформеры, как применять модели из репозитория [Hugging Face Hub](https://huggingface.co/models), как дообучить модели на собственных данных и опубликовать результаты на Hugging Face Hub! -- Главы 5-8 научат вас основам разделов 🤗 Datasets и 🤗 Tokenizers (датасеты и токенизаторы), это необходимо для дальнейшего погружения в область обработки естесственного языка. К концу этой части вы научитесь решать наиболее распространенные задачи в  NLP самостоятельно! -- Главы 9-12 выходят за рамки NLP, в них описано, как можно применять трансформеры в задачах обработки речи и компьютерном зрении. Также вы узнаете, как создавать и демонстрировать свои модели, оптимизировать их для промышленного использования. После изучения этой части вы будете в силах применить 🤗 трансформеры к (почти) любой задаче машинного обучения! +- Главы 5-8 научат вас основам библиотек 🤗 Datasets и 🤗 Tokenizers (датасеты и токенизаторы); это необходимо для дальнейшего погружения в область обработки естественного языка. К концу этой части вы научитесь решать наиболее распространенные задачи в  NLP самостоятельно! +- Главы 9-12 выходят за рамки NLP, в них описано, как можно применять трансформеры в задачах обработки речи и компьютерном зрении. Также вы узнаете, как создавать и демонстрировать свои модели, оптимизировать их для промышленного использования. После изучения этой части вы будете в силах применить 🤗 Transformers к (почти) любой задаче машинного обучения! Этот курс: @@ -37,23 +37,70 @@ Об авторах: -**Matthew Carrigan** - ML-инженер в Hugging Face. Живет в Дублине, Ирландия, и ранее работал инженером по машинному обучению в Parse.ly, а до этого — научным сотрудником в Тринити-колледже в Дублине. Он не верит, что мы сможем достичь реализовать теорию сильного искусственного интеллекта за счет масштабирования существующих архитектур, но все равно возлагает большие надежды на бессмертие роботов. +[**Abubakar Abid**](https://huggingface.co/abidlabs) окончил PhD в области прикладного машинного обучения в Стэндфордском университете. Во время PhD, он основал [Gradio](https://github.com/gradio-app/gradio) - свободная библиотека для Python, с помощью которой увидели свет свыше 600000 тысяч демоверсий моделей машинного обучения. Hugging Face приобрел Gradio, и теперь Abubakar работает с нами в качестве руководителя разработки машинного обучения. +[**Matthew Carrigan**](https://huggingface.co/Rocketknight1) - ML-инженер в Hugging Face. Живет в Дублине, Ирландия, и ранее работал инженером по машинному обучению в Parse.ly, а до этого — научным сотрудником в Тринити-колледже в Дублине. Он не верит, что мы сможем реализовать теорию сильного искусственного интеллекта за счет масштабирования существующих архитектур, но все равно возлагает большие надежды на бессмертие роботов. -**Lysandre Debut** - ML-инженер в Hugging Face, работает над библиотекой 🤗 Transformers с самых ранних этапов разработки. Его цель — сделать NLP доступным для всех, разработав инструменты с очень простым API. +[**Lysandre Debut**](https://huggingface.co/lysandre) - ML-инженер в Hugging Face, работает над библиотекой 🤗 Transformers с самых ранних этапов разработки. Его цель — сделать NLP доступным для всех, разработав инструменты с очень простым API. -**Sylvain Gugger** – инженер-исследователь в Hugging Face и один из ключевых участников разработки библиотеки 🤗 Transformers. Ранее работал научным сотрудником в fast.ai и написал книгу в соавторстве с Jeremy Howard: _[Deep Learning for Coders with fastai and PyTorch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)_. Основное внимание в его исследованиях уделяется тому, чтобы сделать глубокое обучение более доступным путем разработки и улучшения методов, позволяющих моделям быстро обучаться с ограниченными ресурсами. +[**Sylvain Gugger**](https://huggingface.co/sgugger) – инженер-исследователь в Hugging Face и один из ключевых участников разработки библиотеки 🤗 Transformers. Ранее работал научным сотрудником в fast.ai и написал книгу _[Deep Learning for Coders with fastai and PyTorch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)_ в соавторстве с Jeremy Howard. Основное внимание в его исследованиях уделяется тому, чтобы сделать глубокое обучение более доступным путем разработки и улучшения методов, позволяющих моделям быстро обучаться при ограниченных ресурсах. -**Merve Noyan** - developer advocate в Hugging Face, работает над разработкой инструментов и созданием контента на их основе, чтобы машинное обучение более доступным. +[**Dawood Khan**](https://huggingface.co/dawoodkhan82) - ML-инженер в Hugging Face. Dawood из Нью-Йорка, где он окончил Нью-Йоркский университет и получил степень бакалавра компьютерных наук. Проработав несколько лет iOS инженером, Dawood решил сменить работу и стал сооснователем Gradio. Позднее Hugging Face приобрел Gradio. -**Lucile Saulnier** - ML-инженер в Hugging Face, разрабатывающая и поддерживающая использование инструментов с открытым исходным кодом. Она также активно участвует во многих исследовательских проектах в области NLP, таких как совместное обучение и BigScience. +[**Merve Noyan**](https://huggingface.co/merve) - developer advocate в Hugging Face, работает над разработкой инструментов и созданием контента на их основе, чтобы машинное обучение более доступным. -**Lewis Tunstall** - ML-инженер в Hugging Face, сосредоточен на разработке инструментов с открытым исходным кодом и обеспечении их доступности для более широкого сообщества. Соавтор будущей книги [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). +[**Lucile Saulnier**](https://huggingface.co/SaulLu) - ML-инженер в Hugging Face, разрабатывающая и поддерживающая использование инструментов с открытым исходным кодом. Она также активно участвует во многих исследовательских проектах в области NLP, таких как совместное обучение и BigScience. -**Leandro von Werra** - ML-инженер в команде, работающей над открытым исходным кодом Hugging Face и соавтор будушей будущей книги [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). Обладает большим опытом реализации NLP-проектов в промышленности. +[**Lewis Tunstall**](https://huggingface.co/lewtun) - ML-инженер в Hugging Face, сосредоточен на разработке инструментов с открытым исходным кодом и обеспечении их доступности для более широкого сообщества. Соавтор будущей книги [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). +[**Leandro von Werra**](https://huggingface.co/lvwerra) - ML-инженер в команде, работающей над открытым исходным кодом Hugging Face и соавтор будущей будущей книги [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/). Обладает большим опытом реализации NLP-проектов в промышленности. + +## ЧАВО + +Мы собрали ответы на несколько часто задаваемых вопросов: + +- **Получу ли я сертификат после прохождения этого курса?** +На данный момент у нас нет сертификации для этого курса. Мы работаем над получением сертификации для экосистемы Hugging Face. Следите за новостями! + +- **Сколько времени мне нужно будет потратить на прохождение этого курса?** +Каждая глава этого курса рассчитана на неделю работы, то есть примерно 6-8 часов в неделю. Однако, вы можете проходить курс в любом удобном для вас ритме. + +- **Где я могу задать вопрос по материалам курса?** +Если у вас возникли какие-либо вопросы по поводу любой части курса, просто нажмите на "*Ask a question*" наверху страницы, и вы будете автоматически перенаправлены в соответствующий раздел [форума Hugging Face](https://discuss.huggingface.co/) (форум на английском языке): + +Link to the Hugging Face forums + +Обратите внимание, что на форуме также доступен список [идей для проектов](https://discuss.huggingface.co/c/course/course-event/25), если вы хотите применить полученные знания на практике после прохождения курса. + +- **Где я могу посмотреть на код, используемый в этом курсе?** +Внутри каждого раздела наверху страницы есть баннер, который позволит запустить код в Google Colab или Amazon SageMaker Studio Lab: + +Link to the Hugging Face course notebooks + +Блокноты Jupyter со всем кодом, используемом в материалах курса, доступны в репозитории [`huggingface/notebooks`](https://github.com/huggingface/notebooks). Если вы хотите сгенерировать их на своем компьютере, вы можете найти инструкцию в репозитории [`course`](https://github.com/huggingface/course#-jupyter-notebooks) на GitHub. + +- **Как я могу внести свой вклад в развитие курса?** +Существует множество способов внести свой вклад в наш курс! Если вы найдете опечатку или баг, пожалуйста, откройте вопрос (issue) в репозитории [`course`](https://github.com/huggingface/course). Если вы хотите помочь с переводом на ваш родной язык, вы можете найти инструкцию [здесь](https://github.com/huggingface/course#translating-the-course-into-your-language). + +- **Какие стандарты использовались при переводе?** +Каждый перевод содержит глоссарий и файл `TRANSLATING.txt`, в которых описаны стандарты, используемые для перевода терминов и т.д. Вы можете посмотреть на пример для немецкого языка [здесь](https://github.com/huggingface/course/blob/main/chapters/de/TRANSLATING.txt). + +- **Могу ли я использовать этот курс в своих целях?** +Конечно! Этот курс распространяется по либеральной лицензии [Apache 2 license](https://www.apache.org/licenses/LICENSE-2.0.html). Это означает, что вы должны упомянуть создателей этого курса, предоставить ссылку на лицензию и обозначить все изменения. Все это может быть сделано любым приемлемым способов, который, однако, не подразумевает, что правообладатель поддерживает вас или ваши действия по отношению этого курса. Если вы хотите процитировать этот курс, пожалуйста, используйте следующий BibTex: + +``` +@misc{huggingfacecourse, + author = {Hugging Face}, + title = {The Hugging Face Course, 2022}, + howpublished = "\url{https://huggingface.co/course}", + year = {2022}, + note = "[Online; accessed ]" +} +``` + +## Поехали! Вы готовы начать? В этой главе вы узнаете: -* Как испольовать `pipeline()` для решения NLP-задач генерации и классификации текста +* Как использовать `pipeline()` для решения NLP-задач генерации и классификации текста * Об архитектуре трансформеров -* Как различать архитектуры кодировщика, декодера и кодировщика-декодера и варианты их использования +* Как различать архитектуры кодировщика, декодировщика и кодировщика-декодировщика и варианты их использования diff --git a/chapters/ru/chapter1/10.mdx b/chapters/ru/chapter1/10.mdx new file mode 100644 index 000000000..124998f73 --- /dev/null +++ b/chapters/ru/chapter1/10.mdx @@ -0,0 +1,258 @@ + + +# Проверка знаний[[end-of-chapter-quiz]] + + + +В этой главе было много материала! Если вы чувствуете, что все еще всецело не познали все премудрости трансформеров - не переживайте! В следующих главах мы детально расскажем, как все устроено "под капотом". + +Сперва, однако, давайте проверим, что вы узнали в этой главе! + + +### 1. Зайдите на Hub и найдите чекпоинт модели `roberta-large-mnli`. Какую задачу она решает? + + +странице roberta-large-mnli." + }, + { + text: "Классификация текстов", + explain: "В частности, модель определяет, являются ли два предложения логически связанными и присваивает одну из трех меток: противопоставление, нейтральная связь, импликация (англ. contradiction, neutral, entailment). Эта задача называется автоматическое определение логической связи между текстами (англ. natural language inference).", + correct: true + }, + { + text: "Генерация текста", + explain: "Посмотрите получше на странице roberta-large-mnli." + } + ]} +/> + +### 2. Какой будет результат выполнения данного кода? + +```py +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +sentiment-analysis." + }, + { + text: "Пайплайн вернет текст, сгенерированный на основе данного предложения.", + explain: "Неверно — для этого используется пайплайн text-generation.", + }, + { + text: "Пайплайн вернет слова, обозначающие персон, организаций или географических локаций.", + explain: "Кроме того, с аргументом grouped_entities=True, пайплайн сгруппирует слова, принадлежащие одной и той же сущности, например, \"Hugging Face\".", + correct: true + } + ]} +/> + +### 3. Чем нужно заменить ... в данном коде? + +```py +from transformers import pipeline + +filler = pipeline("fill-mask", model="bert-base-cased") +result = filler("...") +``` + + has been waiting for you.", + explain: "Неверно. Прочитайте карточку модели bert-base-cased и попробуйте найти, где вы ошиблись." + }, + { + text: "This [MASK] has been waiting for you.", + explain: "Верно! Токен-маска для этой модели - [MASK].", + correct: true + }, + { + text: "This man has been waiting for you.", + explain: "Неверно. Этот пайплайн предсказывает замаскированный токен, а для этого нужно предоставить токен-маску." + } + ]} +/> + +### 4. Почему этот код выдаст ошибку? + +```py +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +result = classifier("This is a course about the Transformers library") +``` + +candidate_labels=[...].", + correct: true + }, + { + text: "Этому пайплайну требуются несколько предложений, а не одно.", + explain: "Неверно. Хотя, если использовать этот пайплайн правильно, он может принимать на вход массив предложений (как и все остальные пайплайны)." + }, + { + text: "Опять библиотека 🤗 Transformers не работает как положено.", + explain: "Мы даже не будем комментировать этот ответ!" + }, + { + text: "Этому пайплайну требуются более длинные предложения - это слишком короткое.", + explain: "Неверно. Однако, стоит отметить, что этот пайплайн обрежет очень длинный текст, для того, чтобы его корректно обработать." + } + ]} +/> + +### 5. Что такое «трансферное обучение»? + +получит знания предобученной модели. Другими словами, предобученная модель передаст свои знания новой.", + correct: true + }, + { + text: "Передача знаний от предобученной модели к новой модели путем проектирования новой модели с той же самой архитектурой, что и у предобученной.", + explain: "Архитектура - это лишь «скелет» модели; в этом случае никой передачи знаний не происходит." + } + ]} +/> + +### 6. Правда или ложь? Для предобучения языковой модели обычно не требуются метки классов. + +самостоятельно (англ. self-supervised). Это означает, что метки классов создаются автоматически на основе входных данных (например, предсказание следующего или замаскированного слова).", + correct: true + }, + { + text: "Ложь", + explain: "Это неверный ответ." + } + ]} +/> + +### 7. Выберите предложение, которое наилучшим способом описывает следующие термины: «модель», «архитектура» и «веса». + + + + +### 8. Какую из этих моделей вы выберете для дополнения текста по введенной его части? + + + +### 9. Какую из этих моделей вы выберете для автоматического реферирования? + + + +### 10. Какую из этих моделей вы выберете для классификации текстов путем присвоения им определенных меток? + + + +### 11. Что может быть одной из причин предвзятости модели? + + diff --git a/chapters/ru/chapter1/2.mdx b/chapters/ru/chapter1/2.mdx index f265cabca..3afb88da3 100644 --- a/chapters/ru/chapter1/2.mdx +++ b/chapters/ru/chapter1/2.mdx @@ -1,25 +1,25 @@ -# Обработка естесственного языка +# Обработка естественного языка -Прежде, чем перейти к трансформерам, сделаем быстрый обзор того, что такое обработка естесственного языка (NLP) и почему мы заинтересованы в этой сфере. +Прежде, чем перейти к трансформерам, сделаем быстрый обзор того, что такое обработка естественного языка (NLP), и почему мы заинтересованы в этой сфере. ## Что такое NLP? -NLP - область лингвистики и машинного обучения, сосредоточенная на изучении всего, что связано с человеческим языком. Главная цель NLP не просто понимать отдельные слова, но и иметь возможность понимать конекст, в котором эти слова находятся. +NLP - область лингвистики и машинного обучения, которая изучает все, что связано с естественными языками. Главная цель NLP не просто понимать отдельные слова, но и иметь возможность понимать контекст, в котором эти слова находятся. -Список общих NLP-задач с некоторыми примерами: +Список типичных NLP-задач с некоторыми примерами: -- **Классификация предложений**: определить эмоциональную окраску отзыва, детектировать среди входящий писем спам, определить грамматическую корректность предложения или даже проверить, являются ли два предложения связанными между собой логически +- **Классификация предложений**: определить эмоциональную окраску отзыва, детектировать среди входящих писем спам, определить грамматическую правильность предложения или даже проверить, являются ли два предложения связанными между собой логически - **Классификация каждого слова в предложении**: вычленить грамматические составляющие предложения (существительное, глагол, прилагательное) или определить именованные сущности (персона, локация, организация) -- **Генерация текста**: закончить предложение на основе некоторого вводного фрагмента, заполнить пропуски в тексте, содержащем замаскированные слова +- **Генерация текста**: закончить предложение на основе некоторого запроса, заполнить пропуски в тексте, содержащем замаскированные слова - **Сформулировать ответ на вопрос**: получить ответ на заданный по тексту вопрос -- **Сгенерировать новое предложение исходя из предложенного**: перевести текст с одного языка на другой, аннотировать/саммаризовать текст +- **Сгенерировать новое предложение исходя из предложенного**: перевести текст с одного языка на другой, выполнить автоматическое реферирование текста -NLP не ограничивается только письменным текстом. Есть множество сложных задач, связанных с распознаванием речи, компьютерным зрением, таких как расшифровка аудио-сигнала или описания изображений. +NLP не ограничивается только письменным текстом. Есть множество сложных задач, связанных с распознаванием речи и компьютерным зрением, таких как транскрибирование аудио или описание изображений. ## Почему это сложно? diff --git a/chapters/ru/chapter1/3.mdx b/chapters/ru/chapter1/3.mdx index 2f418a6fb..ed4fb86d4 100644 --- a/chapters/ru/chapter1/3.mdx +++ b/chapters/ru/chapter1/3.mdx @@ -1,4 +1,4 @@ -# Трансформеры, на что они способны? +# Трансформеры: на что они способны? Библиотека [🤗 Transformers](https://github.com/huggingface/transformers) предоставляет различную функциональность для создания и использования этих моделей. [Model Hub](https://huggingface.co/models) содержит тысячи предобученных моделей, которые может скачать и использовать любой. Вы также можете загружать свои модели на Model Hub! -⚠️ Hugging Face Hub не ограничивается только моделями. Любой человек может поделиться своими моделями или датасетами! Для этого нужно создать аккаунт: Create a huggingface.co +⚠️ Hugging Face Hub не ограничивается только моделями. Любой человек может поделиться своими моделями или датасетами! Для этого нужно создать учетную запись: Create a huggingface.co @@ -62,7 +62,7 @@ classifier( {'label': 'NEGATIVE', 'score': 0.9994558095932007}] ``` -По умолчанию этот пайплайн выбирает специальную модель, которая была предобучена для оценки эмоциональной окаски предложений на английском языке. Модель загружается и кэшируется когда вы создадите объект `classifier`. Если вы перезапустите команду, будет использована кэшированная модель, загрузки новой модели не произойдет. +По умолчанию этот пайплайн выбирает специальную модель, которая была предобучена для оценки тональности предложений на английском языке. Модель загружается и кэшируется когда вы создадаете объект `classifier`. Если вы перезапустите команду, будет использована кэшированная модель, т.е. загрузки новой модели не произойдет. В процессе обработки пайплайном текста, который вы ему передали, есть три главных шага: @@ -115,7 +115,7 @@ classifier( ## Генерация текста -Теперь давайте взглянем на пайплайн генерации текста. Главная идея заключается в следующем: вы передаете на вход модели небольшой фрагмент текста, а модель будет продолжать его. Это похоже на предсказание следующего слова в клавиатурах различных смартфонов. Генерация текста содержит в себе элемент случайности, поэтому ваш результат может отличаться от того, который приведен ниже в примере. +Теперь давайте взглянем на пайплайн генерации текста (англ. text generation). Главная идея заключается в следующем: вы передаете на вход модели небольшой фрагмент текста, а модель будет продолжать его. Это похоже на предсказание следующего слова в клавиатурах различных смартфонов. Генерация текста содержит в себе элемент случайности, поэтому ваш результат может отличаться от того, который приведен ниже в примере. ```python from transformers import pipeline @@ -141,7 +141,7 @@ generator("In this course, we will teach you how to")
-## Использование произвольной модлеи из Hub в пайплайне +## Использование произвольной модели из Hub в пайплайне Предыдущие примеры использовали модель по умолчанию для решения конкретной задачи, но у вас есть возможность выбрать произвольную модель из Hub и передать ее в пайплайн для конкретной задачи. Например, для генерации текста. Перейдите по ссылке [Model Hub](https://huggingface.co/models) и кликните на соответствующий тег слева, чтобы получить список доступных для этой задачи моделей. Вы должны увидеть страницу, подобную [этой](https://huggingface.co/models?pipeline_tag=text-generation). @@ -167,7 +167,7 @@ generator( 'time and real'}] ``` -Вы можете уточнить, для какого языка вам нужна модель, щелкнув на языковые теги, и выбрать ту, которая будет генерировать текст на другом языке. Model Hub предобученные модели для многоязычных задач. +Вы можете уточнить, для какого языка вам нужна модель, щелкнув на языковые теги, и выбрать ту, которая будет генерировать текст на другом языке. На Model Hub даже есть предобученные модели для многоязычных задач. Как только вы выберете модель, вы увидите, что есть виджет, позволяющий вам попробовать ее прямо на сайте. Таким образом, вы можете быстро протестировать возможности модели перед ее загрузкой. @@ -179,7 +179,7 @@ generator( ### The Inference API -Все модели могут быть протестированы прямо на сайте с использованием inference api, доступнго по адресу [https://huggingface.co/](https://huggingface.co/). Вы можете попробовать применить модель вводя различный текст и сразу же получая результат. +Все модели могут быть протестированы прямо на сайте с использованием inference API, доступного по адресу [https://huggingface.co/](https://huggingface.co/). Вы можете попробовать применить модель, вводя различный текст и сразу же получая результат. Inference API также представляется как платный продукт, что пригодится для интегрирования моделей в свои рабочие процессы. Подробнее можно узнать на странице с [ценами](https://huggingface.co/pricing). @@ -187,7 +187,7 @@ Inference API также представляется как платный пр ## Заполнение пропусков -Следующая задача, на которую мы обратим внимание, связана с заполнением пропусков в тексте. Идея очень проста, мы продемонстрируем ее на простом тексте: +Следующая задача, на которую мы обратим внимание, связана с заполнением пропусков в тексте (англ. mask filling). Идея очень проста, мы продемонстрируем ее на простом тексте: ```python @@ -208,19 +208,17 @@ unmasker("This course will teach you all about models.", top_k=2) 'token_str': ' computational'}] ``` -Аргумент `top_k` указывает, сколько вариантов для пропущенного слова будет отображено. Обратите внимание, что модель заполнит пропуск на месте слова ``, которое часто интерпретируют как *mask token*. Другие модели могут использовать другие токены для обозначения пропуска, всегда лучше проверять это. Один из способов сделать это - обратить внимание на виджет для соответствующей модели. - -The `top_k` argument controls how many possibilities you want to be displayed. Note that here the model fills in the special `` word, which is often referred to as a *mask token*. Other mask-filling models might have different mask tokens, so it's always good to verify the proper mask word when exploring other models. One way to check it is by looking at the mask word used in the widget. +Аргумент `top_k` указывает, сколько вариантов для пропущенного слова будет отображено. Обратите внимание, что модель заполнит пропуск на месте слова ``, которое часто интерпретируют как *mask token (токен-маска)*. Другие модели могут использовать другие токены для обозначения пропуска, всегда лучше проверять это. Один из способов сделать это - обратить внимание на виджет для соответствующей модели. -✏️ **Попробуйте!** Найдите в поиске модель `bert-based-cased` и обратите внимание на его mask token в виджете. Что эта модель будет предсказывать для нашего пайплайна выше? +✏️ **Попробуйте!** Найдите в поиске модель `bert-based-cased` и обратите внимание на его токен-маску в виджете. Что эта модель предскажет, если применить ее в предыдущем примере? ## Распознавание именованных сущностей (NER) -Распознавание именованных сущностей - это задача, в которой модели необходимо найти части текста, соответствующие некоторым сущностям, например: персонам, местам, организациям. Давайте посмотрим на пример: +Распознавание именованных сущностей (англ. named entity recognition) - это задача, в которой модели необходимо найти части текста, соответствующие некоторым сущностям, например: персонам, местам, организациям. Давайте посмотрим на пример: ```python from transformers import pipeline @@ -236,20 +234,19 @@ ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") ] ``` -В этом примере модель корректно обозначила Сильвен как персону (PER), Hugging Face как организацию (ORG) и Бруклин как локацию (LOC). +В этом примере модель корректно обозначила Sylvain как персону (PER), Hugging Face как организацию (ORG) и Brooklyn как локацию (LOC). Мы передали в пайплайн аргумент `grouped_entities=True` для того, чтобы модель сгруппировала части предложения, соответствующие одной сущности: в данном случае модель объединила "Hugging" и "Face" несмотря на то, что название организации состоит из двух слов. На самом деле, как мы увидим в следующей главе, препроцессинг делит даже отдельные слова на несколько частей. Например, `Sylvain` будет разделено на 4 части: `S`, `##yl`, `##va`, and `##in`. На этапе постпроцессинга пайплайн успешно объединит эти части. -✏️ **Попробуйте!** Найдите на Hub модель, позволяющую решать задачу определения частей речи в предложении (part of speech tagging, POS). Что модель предскажет для предложения из примера выше? +✏️ **Попробуйте!** Найдите на Model Hub модель, позволяющую решать задачу определения частей речи в предложении (part of speech tagging, POS). Что модель предскажет для предложения из примера выше? -## Ответы на вопросы - -Пайплан `question-answering` позволяет сгенерировать ответ на вопрос по данному контексту: +## Вопросно-ответные системы +Пайплайн `question-answering` позволяет сгенерировать ответ на вопрос по данному контексту: ```python from transformers import pipeline @@ -268,9 +265,9 @@ question_answerer( Обратите внимание, что пайплайн извлекает информацию для ответа из переданного ему контекста -## Саммаризация +## Автоматическое реферирование (саммаризация) -Саммаризация - задача, в которой необходимо сократить объем текста, но при этом сохранить все (или большинство) важных аспектов изначального текста. Вот пример: +Автоматическое реферирование (англ. summarization) - задача, в которой необходимо сократить объем текста, но при этом сохранить все важные аспекты (или большинство из них) изначального текста. Вот пример: ```python from transformers import pipeline @@ -329,7 +326,7 @@ translator("Ce cours est produit par Hugging Face.") [{'translation_text': 'This course is produced by Hugging Face.'}] ``` -Так же, как и в задачах генерации и саммаризации текста, вы можете указать максимальную длину `max_length` или минимальную длину `min_length` результата. +Так же, как и в задачах генерации и автоматического реферирования текста, вы можете указать максимальную длину `max_length` или минимальную длину `min_length` результата. @@ -338,5 +335,5 @@ translator("Ce cours est produit par Hugging Face.") -Показанные пайплайн в основном носят демонстрационный характер, потому что настроены на решение конкретных задач. В следующей главе вы узнаете, как изменить поведение функции `pipeline()`. +Показанные пайплайны в основном носят демонстрационный характер, потому что настроены на решение конкретных задач. В следующей главе вы узнаете, как изменить поведение функции `pipeline()`. diff --git a/chapters/ru/chapter1/4.mdx b/chapters/ru/chapter1/4.mdx index a817d124c..52072be87 100644 --- a/chapters/ru/chapter1/4.mdx +++ b/chapters/ru/chapter1/4.mdx @@ -5,7 +5,7 @@ classNames="absolute z-10 right-0 top-0" /> -В этом разделе мы посмотрим в общих чертах на то, как работают трансфореры. +В этом разделе мы посмотрим в общих чертах на то, как работают трансформеры. ## Немного истории @@ -18,9 +18,9 @@ [Архитектура трансформеров](https://arxiv.org/abs/1706.03762) была опубликована в июне 2017. Основной фокус оригинального исследования был сосредоточен на задачах перевода. Эта публикация повлекла за собой несколько влиятельных моделей: -- **Июнь 2018**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), первая предобученная модель, часто используется процедура тонкой настройки (fine-tuning) и применение для различных NLP-задач с последующим получением результатов высокого качества. +- **Июнь 2018**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), первая предобученная модель для тонкой настройки или дообучения (fine-tuning), которая показала результаты высокого качества для многих NLP-задач. -- **Октябрь 2018**: [BERT](https://arxiv.org/abs/1810.04805), другая большая предобученная модель, была разработана для для получения хороших саммаризаций предложений (больше мы узнаем об этом в следующей главе!) +- **Октябрь 2018**: [BERT](https://arxiv.org/abs/1810.04805), другая большая предобученная модель, была разработана для извлечения более точного содержания из предложений (больше мы узнаем об этом в следующей главе!) - **Февраль 2019**: [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), улучшенная (и более объемная) версия GPT, которая не была сразу опубликована по этическим соображениям @@ -34,17 +34,17 @@ В широком смысле трансформеры могут быть классифицированы на три типа: - GPT-подобные модели (также часто называемые _авторегрессионные_ трансформеры) - BERT-подобные модели (также часто называемые _автокодирующие_ трансформеры (_auto-encoding_)) -- тип BART/T5 модели (также часто называются модели класса _последовательность-последовательность_ (_sequence2sequence, seq2seq_)) +- тип BART/T5 модели (также часто называются модели класса _последовательность-последовательность_ (_sequence-to-sequence, seq2seq_)) -Мы рассмотри эти семейства более глубоко позже. +Мы рассмотрим эти семейства более детально позже. ## Трансформеры - языковые модели -Все модели трансформеров, упомянутые выше (GPT, BERT, BART, T5, etc.) обучены как *языковые модели*. Это означает, что они обучены на огромном количестве текста в технике самостоятельного обучения (self-supervised learning). Самостоятельное обучение - это такой способ обучения, в котором цель обучения автоматически вычислятся на основе входных данных. Это означает, что люди не должны размечать данные! +Все модели трансформеров, упомянутые выше (GPT, BERT, BART, T5, etc.) обучены как *языковые модели (англ. language models)*. Это означает, что они обучены на огромном количестве текста, используя технику самостоятельного обучения (англ. self-supervised learning). Самостоятельное обучение - это такой способ обучения, в котором цель обучения автоматически вычисляется на основе входных данных. Это означает, что люди не должны размечать данные! -Такой тип моделей реализует статистическое понимание языка, на котором он был обучен, но он не очень полезен для конкретных практических задач. Из-за этого базовая предварительно обученная модель потом подвергается процедуре, называемой *трансферным обучением*. В ходе этого процесса модель настраивается под конкретные наблюдения, т. е. с размеченными человеком данными конкретной задачи. +Такой тип моделей реализует статистическое понимание языка, на котором он был обучен, но он не очень полезен для конкретных практических задач. Из-за этого базовая предварительно обученная модель потом подвергается процедуре, называемой *трансферным обучением (англ. transfer learning)*. В ходе этого процесса модель настраивается под конкретные наблюдения, т.е. размеченными человеком данными для конкретной задачи. -В качестве примера можно привести предсказание следующего слова в предложении на основе *n* предыдущих слов. Это называется *каузальным языковым моделированием*, потому что модель зависит от прошлых и текущих слов, но не от будущих. +В качестве примера можно привести предсказание следующего слова в предложении на основе *n* предыдущих слов. Это называется *каузальным языковым моделированием (англ. causal language modeling)*, потому что модель зависит от прошлых и текущих слов, но не от будущих.
@@ -52,7 +52,7 @@
-Другой пример - *максированная языковая модель*, которая предсказывает замаскированное слово в предложении. +Другой пример - *маскированная языковая модель (англ. masked language modeling)*, которая предсказывает замаскированное слово в предложении.
Example of masked language modeling in which a masked word from a sentence is predicted. @@ -76,17 +76,19 @@ -Это продемонстрировано командой разработчиков на примере очень большой модели. Команда сознательно пытающется уменьшить воздействие предобучения на окружающую среду. Углеродный следу от проведения множества экспериментов для получения лучших гиперпараметров будет еще выше. +И это наглядная демонстрация проекта по разработке (очень большой) модели, которым руководит команда, _сознательно_ пытающаяся уменьшить воздействие предобучения на окружающую среду. Углеродный след от проведения множества экспериментов для получения лучших гиперпараметров будет еще выше. -Представьте себе, что каждый раз, когда исследовательская группа, студенческая организация или компания хотят обучить модель, они делают это с нуля. Это привело бы к огромным, ненужным глобальным затратам! +Представьте себе, что каждый раз, когда исследовательская группа, студенческая организация или компания хотели бы обучить модель, они делали бы это с нуля. Это привело бы к огромным, ненужным глобальным затратам! -Вот почему совместное использование языковых моделей имеет первостепенное значение: совместное использование обученных весов и построение на основе уже обученных весов снижает общую стоимость вычислений и углеродный след сообщества. +Вот почему распространение языковых моделей имеет первостепенное значение: распространение обученных весов и построение новых моделей на их основе снижает общую стоимость вычислений и углеродный след сообщества. + +Кстати, вы можете измерить углеродный след, который оставят ваши модели, при помощи нескольких инструментов. Например, интегрированные в 🤗 Transformers [ML CO2 Impact](https://mlco2.github.io/impact/) или [Code Carbon](https://codecarbon.io/). Чтобы узнать больше о этом, вы можете прочитать этот [блог-пост](https://huggingface.co/blog/carbon-emissions-on-the-hub), в котором мы рассказываем как сгенерировать файл `emissions.csv`, содержащий прогноз объемов выброса углерода во время обучения модели, а также [документацию](https://huggingface.co/docs/hub/model-cards-co2) 🤗 Transformers, в которой затрагивается эта тема. ## Трансферное обучение -*Предобучение* - это процесс обучения модели с нуля: веса модели случайным образом инициализируются, после начинается обучение без предварительных настроек. +*Предобучение (англ. pretraining)* - это процесс обучения модели с нуля: веса модели случайным образом инициализируются, после начинается обучение без предварительных настроек.
The pretraining of a language model is costly in both time and money. @@ -95,22 +97,22 @@ Предобучение обычно происходит на огромных наборах данных, сам процесс может занять несколько недель. -*Fine-tuning*, с другой стороны, это обучение, проведенной *после* того, как модель была предобучена. Для проведения fine-tuning вы сначала должны выбрать предобученную языковую модель, а после провести обучение на данных собственной задачи. Стойте -- почему не обучить модель сразу же на данных конкретной задачи? Этому есть несколько причин: +*Дообучение (англ. fine-tuning)*, с другой стороны, это обучение *после* того, как модель была предобучена. Для дообучения вы сначала должны выбрать предобученную языковую модель, а после продолжить ее обучение ее на данных собственной задачи. Стойте -- почему не обучить модель сразу же на данных конкретной задачи? Этому есть несколько причин: -* Предобученная модель уже обучена на датасете, который имеет много сходств с датасетом для fine-tuning. Процесс тонкой настройки может использовать знания, которые были получены моделью в процессе предобучения (например, в задачах NLP предварительно обученная модель будет иметь представление о статистических закономерностях языка, который вы используете в своей задаче). +* Предобученная модель уже обучена на датасете, который имеет много сходств с датасетом для дообучения. Процесс дообучения может использовать знания, которые были получены моделью в процессе предобучения (например, в задачах NLP предварительно обученная модель будет иметь представление о статистических закономерностях языка, который вы используете в своей задаче). -* Так как предобученная модель уже "видела" много данных, процесс тонкой настройки требует меньшего количества данных для получения приемлемых результатов. +* Так как предобученная модель уже "видела" много данных, процесс дообучения требует меньшего количества данных для получения приемлемых результатов. * По этой же причине требуется и намного меньше времени для получения хороших результатов. -Например, можно использовать предварительно обученную на английском языке модель, а затем провести ее fine-tuning на корпусе arXiv, в результате чего получится научно-исследовательская модель. Для тонкой настройки потребуется лишь ограниченный объем данных: знания, которые приобрела предварительно обученная модель, «передаются» (осуществляют трансфер), отсюда и термин «трансферное обучение». +Например, можно использовать предварительно обученную на английском языке модель, а затем дообучить ее на корпусе arXiv, в результате чего получится научно-исследовательская модель. Для дообучения потребуется лишь ограниченный объем данных: знания, которые приобрела предварительно обученная модель, «передаются» (осуществляют трансфер), отсюда и термин «трансферное обучение».
The fine-tuning of a language model is cheaper than pretraining in both time and money.
-Таким образом, тонкая настройка модели требует меньше времени, данных, финансовых и экологических затрат. Также быстрее и проще перебирать различные схемы тонкой настройки, поскольку обучение требует меньше усилий, чем полное предварительное обучение. +Таким образом, дообучение модели требует меньше времени, данных, финансовых и экологических затрат. Также быстрее и проще перебирать различные схемы дообучения, поскольку оно требует меньше усилий, чем полное предварительное обучение. Этот процесс также даст лучшие результаты, чем обучение с нуля (если только у вас нет большого количества данных), поэтому вы всегда должны пытаться использовать предобученную модель — модель, максимально приближенную к поставленной задаче, а потом дообучить ее. @@ -124,8 +126,8 @@ Модель состоит из двух блоков: -* **Encoder (слева)** (кодировщик, энкодер): энкодер получает входные данные и строит их репрезентацию (формирует признаки). Это означает, модель нацелена на "понимание" входных данных. -* **Декодер (справа)** (декодировщик, декодер): декодер использует репрезентации (признаки) энкодера с другими входными данными для создания нужной последовательности. Это означает, что модель нацелена на генерацию выходных данных. +* **Кодировщик (слева)** (англ. encoder): кодировщик получает входные данные и строит их репрезентацию (формирует признаки). Это означает, модель нацелена на "понимание" входных данных. +* **Декодировщик (справа)** (англ. decoder): декодировщик использует репрезентации (признаки) кодировщика с другими входными данными для создания нужной последовательности. Это означает, что модель нацелена на генерацию выходных данных.
Architecture of a Transformers models @@ -134,45 +136,45 @@ Каждая из этих частей может быть использована отдельно, это зависит от задачи: -* **Encoder-модели**: полезны для задач, требющих понимания входных данных, таких как классификация предложений и распознавание именованных сущностей. -* **Decoder-модели**: полезны для генеративных задач, таких как генерация текста. -* **Encoder-decoder модели** или **seq2seq-модели**: полезны в генеративных задачах, требущих входных данных. Например: перевод или саммаризация текста. +* **Модели-кодировщики**: полезны для задач, требующих понимания входных данных, таких как классификация предложений и распознавание именованных сущностей. +* **Модели-декодировщики**: полезны для генеративных задач, таких как генерация текста. +* **Модели типа "кодировщик-декодировщик"** или **seq2seq-модели**: полезны в генеративных задачах, требующих входных данных. Например: перевод или автоматическое реферирование текста. -Мы изучим эти архитектуры глубже в следующих разделах. +Мы изучим эти архитектуры подробнее в следующих разделах. -## Слой внимания или attention +## Слой внимания -Ключевой особенностью трансформеров является наличие в архитектуре специального слоя, называемого слоем внимания или attention'ом. Статья, в которой была описана архитектура трансформера, называлась["Attention Is All You Need"](https://arxiv.org/abs/1706.03762) ("Внимание - все, что вам нужно")! Мы изучим детали этого слоя позже. На текущий момент мы сформулируем механизм его работы так: attention-слой помогает модели "обращать внимание" на одни слова в поданном на вход предложении, а другие слова в той или иной степени игнорировать. И это происходит в процессе анализа каждого слова. +Ключевой особенностью трансформеров является наличие в архитектуре специального слоя, называемого *слоем внимания (англ. attention layer)*. Статья, в которой была впервые представлена архитектура трансформера, называется ["Attention Is All You Need"](https://arxiv.org/abs/1706.03762) ("Внимание - все, что вам нужно")! Мы изучим детали этого слоя позже. На текущий момент мы сформулируем механизм его работы так: слой внимания помогает модели "обращать внимание" на одни слова в поданном на вход предложении, а другие слова в той или иной степени игнорировать. И это происходит в процессе анализа каждого слова. Чтобы поместить это в контекст, рассмотрим задачу перевода текста с английского на французский язык. Для предложения "You like this course", модель должна будет также учитывать соседнее слово "You", чтобы получить правильный перевод слова "like", потому что во французском языке глагол "like" спрягается по-разному в зависимости от подлежащего. Однако остальная часть предложения бесполезна для перевода этого слова. В том же духе при переводе "like" также необходимо будет обратить внимание на слово "course", потому что "this" переводится по-разному в зависимости от того, стоит ли ассоциированное существительное в мужском или женском роде. Опять же, другие слова в предложении не будут иметь значения для перевода "this". С более сложными предложениями (и более сложными грамматическими правилами) модели потребуется уделять особое внимание словам, которые могут оказаться дальше в предложении, чтобы правильно перевести каждое слово. -Такая же концепция применима к любой задаче, связанной с обработкой естесственного языка: слово само по себе имеет некоторое значение, однако значение очень часто зависит от контекста, которым может являться слово (или слова), стоящие вокруг искомого слова. +Такая же концепция применима к любой задаче, связанной с обработкой естественного языка: слово само по себе имеет некоторое значение, однако значение очень часто зависит от контекста, которым может являться слово (или слова), стоящие вокруг искомого слова. -Теперь, когда вы знакомы с идеей attention в целом, посмотрим поближе на архитектуру всего трансформера. +Теперь, когда вы знакомы с идеей внимания в целом, посмотрим поближе на архитектуру всего трансформера. ## Первоначальная архитектура -Архитектура трансформера изначально была разработана для перевода. Во время обучения энкодер получает входные данные (предложения) на определенном языке, а декодер получает те же предложения на желаемом целевом языке. В энкодере слои внимания могут использовать все слова в предложении (поскольку, как мы только что видели, перевод данного слова может зависеть от того, что в предложении находится после и перед ним). Декодер, в свою очерель, работает последовательно и может обращать внимание только на слова в предложении, которые он уже перевел (то есть только на слова перед генерируемым в данный момент словом). Например, когда мы предсказали первые три слова переведенной цели, мы передаем их декодеру, который затем использует все входные данные энкодера, чтобы попытаться предсказать четвертое слово. +Архитектура трансформера изначально была разработана для перевода. Во время обучения кодировщик получает входные данные (предложения) на определенном языке, а декодировщик получает те же предложения на желаемом целевом языке. В кодировщике слои внимания могут использовать все слова в предложении (поскольку, как мы только что видели, перевод данного слова может зависеть от того, что в предложении находится после и перед ним). Декодировщик, в свою очередь, работает последовательно и может обращать внимание только на слова в предложении, которые он уже перевел (то есть только на слова перед генерируемым в данный момент словом). Например, когда мы предсказали первые три слова переведенной цели, мы передаем их декодировщику, который затем использует все входные данные кодировщика, чтобы попытаться предсказать четвертое слово. -Чтобы ускорить процесс во время обучения (когда модель имеет доступ к целевым предложениям), декодер получает целевое предложение полностью, но ему не разрешается использовать будущие слова (если он имел доступ к слову в позиции 2 при попытке предсказать слово на позиции 2, задача не будет сложной!). Например, при попытке предсказать четвертое слово уровень внимания будет иметь доступ только к словам в позициях с 1 по 3. +Чтобы ускорить процесс во время обучения (когда модель имеет доступ к целевым предложениям), декодировщик получает целевое предложение полностью, но ему не разрешается использовать будущие слова (если он имел доступ к слову в позиции 2 при попытке предсказать слово на позиции 2, задача не будет сложной!). Например, при попытке предсказать четвертое слово слой внимания будет иметь доступ только к словам в позициях с 1 по 3. -Первоначальная архитектура Transformer выглядела так: энкодер слева и декодер справа: +Первоначальная архитектура Transformer выглядела так: кодировщик слева и декодировщик справа:
Architecture of a Transformers models
-Обратите внимание, что первый уровень внимания в блоке декодера обращает внимание на все (прошлые) входные данные декодера, а второй уровень внимания использует выходные данные первого энкодера. Таким образом, он может получить доступ ко всему входному предложению, чтобы наилучшим образом предсказать текущее слово. Это очень полезно, так как разные языки могут иметь грамматические правила, которые располагают слова в разном порядке, или некоторый контекст, предоставленный в предложении далеко от текущего слова. Конекст может быть полезен для определения наилучшего перевода данного слова. +Обратите внимание, что первый слой внимания в блоке декодировщика обращает внимание на все (прошлые) входные данные декодировщика, а второй слой внимания использует выходные данные кодировщика. Таким образом, он может получить доступ ко всему входному предложению, чтобы наилучшим образом предсказать текущее слово. Это очень полезно, так как разные языки могут иметь грамматические правила, которые располагают слова в разном порядке, или некоторый контекст, предоставленный в предложении далеко от текущего слова. Контекст может быть полезен для определения наилучшего перевода данного слова. -*Attention-mask* (маска внимания) также может использоваться в энкодере/декодере, чтобы модель не обращала внимания на некоторые специальные слова — например, специальное несуществующее слово-заполнитель (служебный токен), используемое для придания всем входным данным одинаковой длины при группировке предложений. +*Маска внимания (англ. attention mask)* также может использоваться в кодировщике/декодировщике, чтобы модель не обращала внимания на некоторые специальные слова — например, специальное несуществующее слово-заполнитель (англ. padding), используемое для придания всем входным данным одинаковой длины при группировке предложений. -## Архитектуры и контрольные точки +## Архитектуры и чекпоинты -По мере погружения в трансформеры, вы будете встречать термины *архитектуры* и *контрольные точки* (checkpoints) в смысле *модели*. Эти термины имеют разный смысл: +По мере погружения в трансформеры, вы будете встречать термины *архитектуры* и *чекпоинты* (англ. checkpoints) в смысле *модели*. Эти термины имеют разный смысл: **Архитектура** - скелет модели -- слои, связи и операции, которые выполняются в модели. -**Контрольная точка** - веса модели, которые могут быть загружены для конкретной архитектуры. +**Чекпоинт** - веса модели, которые могут быть загружены для конкретной архитектуры. **Модель** - зонтичный термин, который может означать и архитектуру, и веса для конкретной архитектуры. В этом курсе мы будем точнее использовать термины *архитектуры* и *чекпоинт*, если это будет важно для лучшего понимания. Например, BERT - это архитектура, а `bert-base-cased` - набор весов, подготовленный Google к первому выпуску BERT'а, - это чекпоинт. Однако можно сказать и "модель BERT", и "модель bert-base-cased". diff --git a/chapters/ru/chapter1/5.mdx b/chapters/ru/chapter1/5.mdx index 1cc0839a3..f4b28c5c8 100644 --- a/chapters/ru/chapter1/5.mdx +++ b/chapters/ru/chapter1/5.mdx @@ -1,4 +1,4 @@ -# Модели энкодеров +# Модели-кодировщики -Энкодеры используют только компонент кодировщика трансформера. На каждом этапе слой внимания может использовать все слова исходного предложения. Эти модели часто характеризуют как имеющие двунаправленное внимание ("bi-directional attention"), и часто называют моделями *автоэнкодеров*. +Кодировщики используют только компонент кодировщика трансформера. На каждом этапе слой внимания может использовать все слова исходного предложения. Эти модели часто характеризуют как имеющие двунаправленное внимание (англ. bi-directional attention), и часто называют моделями *автокодировщиками*. -Предварительное обучение этих моделей обычно заключаетс в том, чтобы как-то исказить данное предложение (например, путем маскировки в нем случайных слов) и поставить перед моделью задачу найти или восстановить исходное предложение. +Предварительное обучение этих моделей обычно заключается в том, чтобы как-то исказить предложение (например, путем маскировки в нем случайных слов) и поставить перед моделью задачу найти или восстановить исходное предложение. -Энкодеры лучше всего подходят для задач, требующих _понимания_ всего предложения, таких как классификация предложений, распознавание именованных сущностей (и, в более общем смысле, классификация слов) и ответы на вопросы с извлечением информации из контекста. +Кодировщики лучше всего подходят для задач, требующих _понимания_ всего предложения, таких как классификация предложений, распознавание именованных сущностей (и, в более общем смысле, классификация слов) и ответы на вопросы с извлечением информации из контекста (выделительные вопросно-ответные системы). К представителям этого семейства моделей относятся: diff --git a/chapters/ru/chapter1/6.mdx b/chapters/ru/chapter1/6.mdx index 815f267d5..b75dd4ea4 100644 --- a/chapters/ru/chapter1/6.mdx +++ b/chapters/ru/chapter1/6.mdx @@ -1,4 +1,4 @@ -# Модели декодеров +# Модели-декодировщики -Декодировщики используют только компонент декодер трансформера. На каждом этапе для текущего слова слой внимания может получить доступ только к словам, которые были расположены до текущего в предложении. Такие модели часто называются *авторегрессионными моделями*. +Декодировщики используют только компонент декодирования трансформера. На каждом этапе для текущего слова слой внимания может получить доступ только к словам, которые были расположены до него в предложении. Такие модели часто называются *авторегрессионными моделями*. -Процесс предобучения декодеров обычно заключается в предсказании следующего слова в предложении. ё +Процесс предобучения декодировщиков обычно заключается в предсказании следующего слова в предложении. Такие модели лучше всего подходят для задач, связанных с генерацией текста. diff --git a/chapters/ru/chapter1/7.mdx b/chapters/ru/chapter1/7.mdx index 86be51145..6f13b4683 100644 --- a/chapters/ru/chapter1/7.mdx +++ b/chapters/ru/chapter1/7.mdx @@ -7,11 +7,11 @@ -Энкодер-декодер модели (также называемые *sequence-to-sequence models*) используют обе части трансформера. На каждом этапе слой внимания энкодера получает доступ ко всем словам в исходной последовательности, тогда как слой внимания декодера получает доступ только к тем словам, которые позиционированы до текущего слова. +Модели типа кодировщик-декодировщик (также называемые *sequence-to-sequence models*) используют обе части трансформера. На каждом этапе слой внимания кодировщика получает доступ ко всем словам в исходной последовательности, тогда как слой внимания декодировщика получает доступ только к тем словам, которые расположены до текущего слова. -Предобучение таких моделей может быть проведено по аналогии с процессом предобучения энкодера или декодера, но обычно это происходит сложнее. Например, модель [T5](https://huggingface.co/t5-base) была предобучена путем замены случайных фрагментов текста (фрагменты могут содержать несколько слов) на специальную маску, цель модели - предсказать текст, который заменила маска. +Предобучение таких моделей может быть выполнено на задачах, используемых для предобучения моделей кодировщиков или декодировщиков, но обычно все немного сложнее. Например, модель [T5](https://huggingface.co/t5-base) была предобучена путем замены случайных фрагментов текста (фрагменты могут содержать несколько слов) на специальную маску, цель модели - предсказать текст, который заменила маска. -Модели seq2seq лучше всего подходят для задач генерации новых предложений, зависящих от входного массива данных, например: саммаризация текста, перевод или генерация ответов на вопросы. +Модели seq2seq лучше всего подходят для задач генерации новых предложений, зависящих от входного массива данных, например: автоматическое реферирование текста, перевод или в генеративных вопросно-ответных системах. Представителями этого семейства являются: diff --git a/chapters/ru/chapter1/8.mdx b/chapters/ru/chapter1/8.mdx index fe5db9f6b..edc759829 100644 --- a/chapters/ru/chapter1/8.mdx +++ b/chapters/ru/chapter1/8.mdx @@ -7,7 +7,7 @@ {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/ru/chapter1/section8.ipynb"}, ]} /> -Если вы намерены использовать предварительно обученную модель или точно настроенную версию в рабочей среде, имейте в виду, что, хотя эти модели являются мощными инструментами, они имеют ограничения. Самая большая из них заключается в том, что для предварительной подготовки на больших объемах данных исследователи часто очищают весь контент, который они могут найти, беря как лучшее, так и худшее из того, что доступно в Интернете. +Если вы намерены использовать предварительно обученную модель или ее дообученную версию в рабочей среде, имейте в виду, что, хотя эти модели являются мощными инструментами, они имеют ограничения. Самое большое из них заключается в том, что для предварительного обучения на больших объемах данных исследователи часто очищают весь контент, который они могут найти, беря как лучшее, так и худшее из того, что доступно в Интернете. Для иллюстрации вернемся к примеру пайплайна `fill-mask` с моделью BERT: @@ -29,5 +29,5 @@ print([r["token_str"] for r in result]) На просьбу вставить пропущенное слово в этих двух предложениях модель дает только один ответ без гендерной принадлежности (официант/официантка). Другие рабочие профессии обычно ассоциируются с одним конкретным полом — и да, проститутка попала в топ-5 вариантов, которые модель ассоциирует с "женщиной" и "работой". Это происходит даже несмотря на то, что BERT — одна из редких моделей трансформеров, созданная не путем сбора данных со всего Интернета, а с использованием явно нейтральных данных (он обучен на [английской Википедии](https://huggingface.co/datasets/wikipedia) и наборе данных [BookCorpus](https://huggingface.co/datasets/bookcorpus). -Поэтому, когда вы используете эти инструменты, вам нужно помнить, что исходная модель, которую вы используете, может очень легко генерировать сексистский, расистский или гомофобный контент. Тонкая настройка модели на ваших данных не избавит вас от этой внутренней предвзятости. +Поэтому, когда вы используете эти инструменты, вам нужно помнить, что исходная модель, которую вы используете, может очень легко генерировать сексистский, расистский или гомофобный контент. Дообучение модели на ваших данных не сможет устранить эту внутреннюю предвзятость. diff --git a/chapters/ru/chapter1/9.mdx b/chapters/ru/chapter1/9.mdx index f808b04d0..98c0abaac 100644 --- a/chapters/ru/chapter1/9.mdx +++ b/chapters/ru/chapter1/9.mdx @@ -7,11 +7,11 @@ В этой главе вы увидели, как подходить к различным задачам NLP, используя высокоуровневую функцию `pipeline()` из библиотеки 🤗 Transformers. Вы также увидели, как искать и использовать модели в Hub, а также как использовать Inference API для тестирования моделей прямо в браузере. -Мы обсудили, как трансформеры работают на высоком уровне, и поговорили о важности трансферного обучения и тонкой настройки. Ключевым аспектом является то, что вы можете использовать всю архитектуру или только энкодер или декодер, в зависимости от того, какую задачу вы хотите решить. Следующая таблица резюмирует это: +Мы обсудили, как трансформеры работают на высоком уровне, и поговорили о важности трансферного обучения и дообучения. Ключевым аспектом является то, что вы можете использовать всю архитектуру или только кодировщик или декодировщик, в зависимости от того, какую задачу вы хотите решить. Следующая таблица резюмирует это: -| Модель | Примеры | Задачи | -|-----------------|--------------------------------------------|----------------------------------------------------------------------------------| -| Энкодер | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa | Классификация предложений, распознавание именованных сущностей, генерация ответов на вопросы с извлечением информации | -| Декодер | CTRL, GPT, GPT-2, Transformer XL | Генерация текста | -| Энкодер-декодер | BART, T5, Marian, mBART | Саммаризация, перевод, генеративный подход к ответам на вопросы | +| Модель | Примеры | Задачи | +|-------------------------|--------------------------------------------|---------------------------------------------------------------------------------------------------------| +| Кодировщик | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa | Классификация предложений, распознавание именованных сущностей, выделительные вопросно-ответные системы | +| Декодировщик | CTRL, GPT, GPT-2, Transformer XL | Генерация текста | +| Кодировщик-декодировщик | BART, T5, Marian, mBART | Автоматическое реферирование, перевод, генеративные вопросно-ответные системы | diff --git a/chapters/ru/glossary/1.mdx b/chapters/ru/glossary/1.mdx new file mode 100644 index 000000000..00e1217c8 --- /dev/null +++ b/chapters/ru/glossary/1.mdx @@ -0,0 +1,146 @@ +# Глоссарий + +| Оригинал | Перевод | +|---------------------------------|-----------------------------------------| +| Abstraction | абстракция | +| Account | учетная запись | +| Accuracy | accuracy | +| Artificial General Intelligence | сильный искусственный интеллект | +| Attention | внимание | +| Attention mask (layer) | маска внимания (слой) | +| Backward Pass\* | обратный проход | +| Batch | батч | +| Bias | смещение | +| Causal Language Modeling | каузальное языковое моделирование | +| Chapter | глава | +| Checkpoint(s) | чекпоинт | +| Class | класс | +| Classification | классификация | +| Code | код | +| Colab Notebook | блокнот Colab | +| Command | команда | +| Computer Vision | компьютерное зрение | +| Configuration | конфигурация | +| Course | курс | +| Decoder | декодировщик / декодер | +| Dependency | зависимость | +| Deployment | развертывание (программного обеспечения)| +| Development | разработка | +| Dictionary | dictionary | +| Distribution | распределение | +| Download | download | +| Encoder | кодировщик / энкодер | +| Extractive question answering | выделительная вопросно-ответная система | +| F1 score | F1-мера | +| Feature | признак | +| Fine-tune | дообучать | +| Fine-tuning | дообучение | +| Folder | папка / директория | +| Forward Pass\* | прямой проход | +| Function | функция | +| Generative question answering | генеративная вопросно-ответная система | +| Google | Google | +| Hugging Face | Hugging Face | +| Incompatibility | несовместимость | +| Inference | инференс | +| Input | вход | +| Input data | входные данные | +| Label (verb) | размечать | +| Label (subj) | метка класса | +| Layer | слой | +| Library | библиотека | +| Linux | Linux | +| Load | загружать | +| Loss function | функция потерь | +| Machine Learning | машинное обучение | +| macOS | macOS | +| Mask | маска | +| Mask Filling | предсказание замаскированного токена | +| Mask Token | токен-маска | +| Masked Language Modeling | маскированное языковое моделирование | +| Model | модель | +| Model Hub | Model Hub | +| Module | модуль | +| Named Entities | именованные сущности | +| Named Entity Recognition | распознавание именованных сущностей | +| Natural Language Processing | обработка естественного языка | +| Output | выход | +| Package | пакет | +| Package Manager | менеджер пакетов | +| Padding (объект) | padding | +| Padding (действие) | дополнение | +| Parameter | параметр | +| Postprocessing | постобработка / последующая обработка | +| Preprocessing | предобработка / предварительная обработка| +| Pretraining | предварительное обучение / предобучение | +| Pretrained model | предварительно обученная модель | +| Pretrained model | предобученная модель | +| Prompt | начальный текст | +| Python | Python | +| Pytorch | Pytorch | +| Question Answering | вопросно-ответная система | +| Save | сохранять | +| Sample | пример | +| Script | скрипт | +| Self-Attention | самовнимание | +| Self-Contained | самостоятельный | +| Sentiment analysis | анализ тональности текста (сентимент-анализ)| +| Sequence-to-sequence models | sequence-to-sequence модель | +| Setup | установка (программы) / настройка (среды)| +| Speech Processing | обработка речи | +| Speech Recognition | распознавание речи | +| Summarization | суммаризация | +| Target | целевая переменная | +| Task | задача | +| TensorFlow | Tensorflow | +| Terminal | терминал | +| Text generation | генерация текста | +| Tokenizer | Tokenizer (библиотека) / токенизатор | +| Train | обучение (обучать) | +| Transfer Learning | Transfer Learning / трансферное обучение| +| Transformer | трансформер | +| Transformer models | архитектура трансформер | +| Translation | (машинный) перевод | +| Virtual Environment | виртуальное окружение | +| Weight | вес | +| Weights | веса | +| Windows | Windows | +| Working Environment | рабочее окружение | +| Workload | нагрузка | +| Workspace | Workspace | +| Zero-shot classification | zero-shot классификация | +======= + +\* Данные термины могут употребляться взаимозаменяемо с их английской версией + +## Сокращения + +| Оригинал | Перевод | +|-----------|-------------| +| NLP | NLP | +| API | API | +| GPU | GPU | +| TPU | TPU | +| ML | ML | + +## Notes + +Please refer to [TRANSLATING.txt](/chapters/ru/TRANSLATING.txt) for a translation guide. Here are some excerpts relevant to the glossary: + +- 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. + +- The Russian language accepts English words especially in modern contexts more + than many other languages (i.e. Anglicisms). 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 Russian 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 package. + Library is library. Don't mix and match. + diff --git a/chapters/zh-CN/_toctree.yml b/chapters/zh-CN/_toctree.yml index ff3aaaa6d..08335aaf7 100644 --- a/chapters/zh-CN/_toctree.yml +++ b/chapters/zh-CN/_toctree.yml @@ -69,7 +69,7 @@ - local: chapter4/1 title: The Hugging Face Hub - local: chapter4/2 - title: 使用预训练的模型 + title: 使用预训练模型 - local: chapter4/3 title: 分享预训练的模型 - local: chapter4/4 diff --git a/chapters/zh-CN/chapter0/1.mdx b/chapters/zh-CN/chapter0/1.mdx index 5e46295d1..45af8dfc9 100644 --- a/chapters/zh-CN/chapter0/1.mdx +++ b/chapters/zh-CN/chapter0/1.mdx @@ -1,4 +1,4 @@ -# 课程简介 +# 课程简介 [[课程简介]] 欢迎来到拥抱脸课程!本介绍将指导您设置工作环境。如果您刚开始学习本课程,我们建议您先阅读[第一章](/course/chapter1), 然后再回来设置您的环境,以便您可以自己尝试运行代码。 @@ -10,7 +10,7 @@ 大多数课程和服务都依赖于您拥有 Hugging Face 帐户。我们建议现在创建一个:[创建一个账号](https://huggingface.co/join). -## 使用 Google Colab 笔记本 +## 使用 Google Colab 笔记本 [[使用 Google Colab 笔记本]] 使用 Colab notebook 是最简单的设置;可以在浏览器中启动Notebook并直接开始编写自己的代码! @@ -46,7 +46,7 @@ import transformers 这将需要一些时间,但当完成之后您就做好学习剩下的课程的环境准备了! -## 使用 Python 虚拟环境 +## 使用 Python 虚拟环境 [[使用 Python 虚拟环境]] 如果您更喜欢使用 Python 虚拟环境,那么第一步是在您的系统上安装 Python。我们建议您按照[这个教程](https://realpython.com/installing-python/)进行配置。 @@ -99,7 +99,7 @@ which python /home//transformers-course/.env/bin/python ``` -### 安装依赖 +### 安装依赖 [[安装依赖]] 与使用 Google Colab 实例的上一节一样,您现在需要安装继续所需的包。 同样,您可以使用 `pip` 包管理器安装 🤗 Transformers 的开发版本: diff --git a/chapters/zh-CN/chapter1/1.mdx b/chapters/zh-CN/chapter1/1.mdx index fb0ccc351..18d638949 100644 --- a/chapters/zh-CN/chapter1/1.mdx +++ b/chapters/zh-CN/chapter1/1.mdx @@ -1,18 +1,18 @@ -# 本章简介 +# 本章简介 [[本章简介]] -## 欢迎来到🤗课程 +## 欢迎来到🤗课程 [[欢迎来到🤗课程]] 本课程将使用 Hugging Face 生态系统中的库——🤗 Transformers、🤗 Datasets、🤗 Tokenizers 和 🤗 Accelerate——以及 Hugging Face Hub 教你自然语言处理 (NLP)。它是完全免费的,并且没有广告。 -## 有什么是值得期待的? +## 有什么是值得期待的?[[有什么是值得期待的?]] 以下是课程的简要概述: @@ -33,23 +33,74 @@ 完成本课程后,我们建议您查看 [DeepLearning.AI的自然语言处理系列课程](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh),其中涵盖了广泛的传统 NLP 模型,如朴素贝叶斯和 LSTM,这些模型非常值得了解! -## 我们是谁? +## 我们是谁?[[我们是谁?]] 关于作者: -**Matthew Carrigan** 是 Hugging Face 的机器学习工程师。他住在爱尔兰都柏林,之前在 Parse.ly 担任机器学习工程师,在此之前,他在Trinity College Dublin担任博士后研究员。他不相信我们会通过扩展现有架构来实现 AGI,但无论如何都对机器人充满希望。 +[**Abubakar Abid**](https://huggingface.co/abidlabs) 在斯坦福大学获得应用机器学习博士学位。 在攻读博士学位期间,他创立了 [Gradio](https://github.com/gradio-app/gradio),这是一个开源 Python 库,已用于构建超过 600,000 个机器学习演示。 Gradio 被 Hugging Face 收购,Abubakar 现在是该公司的机器学习团队负责人。 -**Lysandre Debut** 是 Hugging Face 的机器学习工程师,从早期的开发阶段就一直致力于 🤗 Transformers 库。他的目标是通过使用非常简单的 API 开发工具,让每个人都可以使用 NLP。 +[**Matthew Carrigan**](https://huggingface.co/Rocketknight1) 是 Hugging Face 的机器学习工程师。他住在爱尔兰都柏林,之前在 Parse.ly 担任机器学习工程师,在此之前,他在Trinity College Dublin担任博士后研究员。他不相信我们会通过扩展现有架构来实现 AGI,但无论如何都对机器人充满希望。 -**Sylvain Gugger** 是 Hugging Face 的一名研究工程师,也是 🤗Transformers库的核心维护者之一。此前,他是 fast.ai 的一名研究科学家,他与Jeremy Howard 共同编写了[Deep Learning for Coders with fastai and Py Torch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)。他的主要研究重点是通过设计和改进允许模型在有限资源上快速训练的技术,使深度学习更容易普及。 +[**Lysandre Debut**](https://huggingface.co/lysandre) 是 Hugging Face 的机器学习工程师,从早期的开发阶段就一直致力于 🤗 Transformers 库。他的目标是通过使用非常简单的 API 开发工具,让每个人都可以使用 NLP。 -**Merve Noyan** 是 Hugging Face 的开发者倡导者,致力于开发工具并围绕它们构建内容,以使每个人的机器学习平民化。 +[**Sylvain Gugger**](https://huggingface.co/sgugger) 是 Hugging Face 的一名研究工程师,也是 🤗Transformers库的核心维护者之一。此前,他是 fast.ai 的一名研究科学家,他与Jeremy Howard 共同编写了[Deep Learning for Coders with fastai and Py Torch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)。他的主要研究重点是通过设计和改进允许模型在有限资源上快速训练的技术,使深度学习更容易普及。 -**Lucile Saulnier** 是 Hugging Face 的机器学习工程师,负责开发和支持开源工具的使用。她还积极参与了自然语言处理领域的许多研究项目,例如协作训练和 BigScience。 +[**Dawood Khan**](https://huggingface.co/dawoodkhan82) 是 Hugging Face 的机器学习工程师。 他来自纽约,毕业于纽约大学计算机科学专业。 在担任 iOS 工程师几年后,Dawood 辞职并与其他联合创始人一起创办了 Gradio。 Gradio 最终被 Hugging Face 收购。 -**Lewis Tunstall** 是 Hugging Face 的机器学习工程师,专注于开发开源工具并使更广泛的社区可以使用它们。他也是即将出版的一本书[O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/)的作者之一。 +[**Merve Noyan**](https://huggingface.co/sgugger) 是 Hugging Face 的开发者倡导者,致力于开发工具并围绕它们构建内容,以使每个人的机器学习平民化。 -**Leandro von Werra** 是 Hugging Face 开源团队的机器学习工程师,也是即将出版的一本书[O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/)的作者之一。他拥有多年的行业经验,通过在整个机器学习堆栈中工作,将 NLP 项目投入生产。 +[**Lucile Saulnier**](https://huggingface.co/SaulLu) 是 Hugging Face 的机器学习工程师,负责开发和支持开源工具的使用。她还积极参与了自然语言处理领域的许多研究项目,例如协作训练和 BigScience。 + +[**Lewis Tunstall**](https://huggingface.co/lewtun) 是 Hugging Face 的机器学习工程师,专注于开发开源工具并使更广泛的社区可以使用它们。他也是即将出版的一本书[O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/)的作者之一。 + +[**Leandro von Werra**](https://huggingface.co/lvwerra) 是 Hugging Face 开源团队的机器学习工程师,也是即将出版的一本书[O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/)的作者之一。他拥有多年的行业经验,通过在整个机器学习堆栈中工作,将 NLP 项目投入生产。 + +## FAQ[[faq]] + +这里有一些经常被提到的问题: + +- **参加本课程是否会获得认证?** +目前,我们没有获得此课程的任何认证。 但是,我们正在为 Hugging Face 生态系统制定认证计划——敬请期待! + +- **我应该在这门课程上花多少时间?** +本课程的每一章都设计为在 1 周内完成,每周大约需要 6-8 小时的学习时间。 但是,您可以花尽可能多的时间来完成课程。 + +- **如果我有问题,我可以在哪里提问?** +如果您对课程的任何部分有疑问,只需单击页面顶部的“*提问*”横幅,系统就会自动重定向到 [Hugging Face 论坛](https:// discuss.huggingface.co/): + +拥抱脸论坛链接 + +请注意,如果您想在完成课程后进行更多练习,论坛上还提供了[项目灵感](https://discuss.huggingface.co/c/course/course-event/25) 列表。 + +- **我在哪里可以获得课程的代码?** +对于每个部分,单击页面顶部的横幅可以在 Google Colab 或 Amazon SageMaker Studio Lab 中运行代码: + +拥抱脸课程笔记本链接 + +包含课程所有代码的 Jupyter 笔记本托管在 [`huggingface/notebooks`](https://github.com/huggingface/notebooks) 仓库中。 如果您希望在本地生成它们,请查看 GitHub 上 [`course`](https://github.com/huggingface/course#-jupyter-notebooks) 仓库中的说明。 + + +- **我如何为课程做出贡献?** +有很多方法可以为课程做出贡献! 如果您发现拼写错误或错误,请在 [`course`](https://github.com/huggingface/course) 仓库中提出问题。 如果您想帮助将课程翻译成您的母语,请在[此处](https://github.com/huggingface/course#translating-the-course-into-your-language) 查看说明。 + +- ** 每个翻译的选择是什么?** +每个翻译都有一个词汇表和“TRANSLATING.txt”文件,其中详细说明了为机器学习术语等所做的选择。您可以在 [此处](https://github.com/huggingface/course/blob/main/chapters/de/TRANSLATING.txt)。 + + +- **我可以使用这门课程再次进行创作吗?** +当然! 该课程是根据宽松的 [Apache 2 许可证](https://www.apache.org/licenses/LICENSE-2.0.html) 发布的。 这意味着您必须按照诚信的原则,提供许可证的链接,并指出是否进行了更改。您可以以任何合理的方式这样做,但不能以任何表明许可方认可您或您的使用的方式。 如果您想引用该课程,请使用以下 BibTeX: + +``` +@misc{huggingfacecourse, + author = {Hugging Face}, + title = {The Hugging Face Course, 2022}, + howpublished = "\url{https://huggingface.co/course}", + year = {2022}, + note = "[Online; accessed ]" +} +``` + +## 让我们开始吧![[让我们开始吧!]] 你准备好了吗?在本章中,您将学习: * 如何使用 `pipeline()` 函数解决文本生成、分类等NLP任务 diff --git a/chapters/zh-CN/chapter1/10.mdx b/chapters/zh-CN/chapter1/10.mdx index 3e2951036..84a5cb639 100644 --- a/chapters/zh-CN/chapter1/10.mdx +++ b/chapters/zh-CN/chapter1/10.mdx @@ -1,6 +1,6 @@ -# 章末小测试 +# 章末小测试 [[章末小测试]] 准备. -## Transformer被应用于各个方面! +## Transformer被应用于各个方面! [[Transformer被应用于各个方面!]] Transformer 模型用于解决各种 NLP 任务,就像上一节中提到的那样。以下是一些使用 Hugging Face 和 Transformer 模型的公司和组织,他们也通过分享他们的模型回馈社区: ![使用 Hugging Face 的公司](https://huggingface.co/course/static/chapter1/companies.PNG) @@ -26,7 +26,7 @@ Transformer 模型用于解决各种 NLP 任务,就像上一节中提到的那 在深入研究 Transformer 模型的底层工作原理之前,让我们先看几个示例,看看它们如何用于解决一些有趣的 NLP 问题。 -## 使用pipelines +## 使用pipelines [[使用pipelines]] (这里有一个视频,但是国内可能打不开,译者注) @@ -77,7 +77,7 @@ classifier( 让我们来看看其中的一些吧! -## 零样本分类 +## 零样本分类 [[零样本分类]] 我们将首先处理一项非常具挑战性的任务,我们需要对尚未标记的文本进行分类。这是实际项目中的常见场景,因为注释文本通常很耗时并且需要领域专业知识。对于这项任务**zero-shot-classification**pipeline非常强大:它允许您直接指定用于分类的标签,因此您不必依赖预训练模型的标签。下面的模型展示了如何使用这两个标签将句子分类为正面或负面——但也可以使用您喜欢的任何其他标签集对文本进行分类。 ```python @@ -100,7 +100,7 @@ classifier( ✏️**快来试试吧!**使用您自己的序列和标签,看看模型的行为。 -## 文本生成 +## 文本生成 [[文本生成]] 现在让我们看看如何使用pipeline来生成一些文本。这里的主要使用方法是您提供一个提示,模型将通过生成剩余的文本来自动完成整段话。这类似于许多手机上的预测文本功能。文本生成涉及随机性,因此如果您没有得到相同的如下所示的结果,这是正常的。 ```python @@ -122,7 +122,7 @@ generator("In this course, we will teach you how to") ✏️**快来试试吧!**使用 num_return_sequences 和 max_length 参数生成两个句子,每个句子 15 个单词。 -## 在pipeline中使用 Hub 中的其他模型 +## 在pipeline中使用 Hub 中的其他模型 [[在pipeline中使用 Hub 中的其他模型]] 前面的示例使用了默认模型,但您也可以从 Hub 中选择特定模型以在特定任务的pipeline中使用 - 例如,文本生成。转到[模型中心(hub)](https://huggingface.co/models)并单击左侧的相应标签将会只显示该任务支持的模型。[例如这样](https://huggingface.co/models?pipeline_tag=text-generation)。 让我们试试 [**distilgpt2**](https://huggingface.co/distilgpt2) 模型吧!以下是如何在与以前相同的pipeline中加载它: @@ -151,12 +151,12 @@ generator( ✏️**快来试试吧!**使用标签筛选查找另一种语言的文本生成模型。使用小组件测试并在pipeline中使用它! -## 推理 API +## 推理 API [[推理 API]] 所有模型都可以使用 Inference API 直接通过浏览器进行测试,该 API 可在 [Hugging Face 网站](https://huggingface.co/)上找到。通过输入自定义文本并观察模型的输出,您可以直接在此页面上使用模型。 小组件形式的推理 API 也可作为付费产品使用,如果您的工作流程需要它,它会派上用场。有关更多详细信息,请参阅[定价页面](https://huggingface.co/pricing)。 -## Mask filling +## Mask filling [[Mask filling]] 您将尝试的下一个pipeline是 **fill-mask**。此任务的想法是填充给定文本中的空白: ```python from transformers import pipeline @@ -180,7 +180,7 @@ unmasker("This course will teach you all about models.", top_k=2) ✏️**快来试试吧!**在 Hub 上搜索基于 bert 的模型并在推理 API 小组件中找到它的掩码。这个模型对上面pipeline示例中的句子预测了什么? -## 命名实体识别 +## 命名实体识别 [[命名实体识别]] 命名实体识别 (NER) 是一项任务,其中模型必须找到输入文本的哪些部分对应于诸如人员、位置或组织之类的实体。让我们看一个例子: ```python from transformers import pipeline @@ -202,7 +202,7 @@ ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") ✏️**快来试试吧!**在模型中心(hub)搜索能够用英语进行词性标注(通常缩写为 POS)的模型。这个模型对上面例子中的句子预测了什么? -## 问答系统 +## 问答系统 [[问答系统]] 问答pipeline使用来自给定上下文的信息回答问题: ```python from transformers import pipeline @@ -221,7 +221,7 @@ klyn", ``` 请注意,此pipeline通过从提供的上下文中提取信息来工作;它不会凭空生成答案。 -## 文本摘要 +## 文本摘要 [[文本摘要]] 文本摘要是将文本缩减为较短文本的任务,同时保留文本中的主要(重要)信息。下面是一个例子: ```python @@ -262,7 +262,7 @@ summarizer( ``` 与文本生成一样,您指定结果的 **max_length** 或 **min_length**。 -## 翻译 +## 翻译 [[翻译]] 对于翻译,如果您在任务名称中提供语言对(例如“**translation_en_to_fr**”),则可以使用默认模型,但最简单的方法是在[模型中心(hub)](https://huggingface.co/models)选择要使用的模型。在这里,我们将尝试从法语翻译成英语: ```python diff --git a/chapters/zh-CN/chapter1/4.mdx b/chapters/zh-CN/chapter1/4.mdx index a2f8a839f..c056b2b10 100644 --- a/chapters/zh-CN/chapter1/4.mdx +++ b/chapters/zh-CN/chapter1/4.mdx @@ -1,4 +1,4 @@ -# Transformers 是如何工作的? +# Transformers 是如何工作的? [[Transformers 是如何工作的?]]
-## Transformer是大模型 +## Transformer是大模型 [[Transformer是大模型]] 除了一些特例(如 DistilBERT)外,实现更好性能的一般策略是增加模型的大小以及预训练的数据量。 @@ -81,8 +81,9 @@ Transformers是由一个团队领导的(非常大的)模型项目,该团 这就是为什么共享语言模型至关重要:共享经过训练的权重,当遇见新的需求时在预训练的权重之上进行微调,可以降低训练模型训练的算力和时间消耗,降低全球的总体计算成本和碳排放。 +顺便说一句,您可以通过多种工具评估模型训练的碳排放。 例如 [ML CO2 Impact](https://mlco2.github.io/impact/) 或集成在 🤗 Transformers 中的 [Code Carbon]( https://codecarbon.io/)。 要了解更多相关信息,您可以阅读这篇[博客文章](https://huggingface.co/blog/carbon-emissions-on-the-hub),其中将向您展示如何生成 `emissions.csv` 文件估计训练的碳足迹,这里还有 🤗 Transformers 关于碳足迹的[文档](https://huggingface.co/docs/hub/model-cards-co2)。 -## 迁移学习 +## 迁移学习 [[迁移学习]] @@ -112,12 +113,12 @@ Transformers是由一个团队领导的(非常大的)模型项目,该团 这个过程也会比从头开始的训练(除非你有很多数据)取得更好的效果,这就是为什么你应该总是尝试利用一个预训练的模型--一个尽可能接近你手头的任务的模型--并对其进行微调。 -## 一般的体系结构 +## 一般的体系结构 [[一般的体系结构]] 在这一部分,我们将介绍Transformer模型的一般架构。如果你不理解其中的一些概念,不要担心;下文将详细介绍每个组件。 -## 介绍 +## 介绍 [[介绍]] 该模型主要由两个块组成: @@ -137,7 +138,7 @@ Transformers是由一个团队领导的(非常大的)模型项目,该团 在后面的部分中,我们将单独地深入研究这些体系结构。 -## 注意力层 +## 注意力层 [[注意力层]] Transformer模型的一个关键特性是*注意力层*。事实上,介绍Transformer架构的文章的标题是[“注意力就是你所需要的”](https://arxiv.org/abs/1706.03762)! 我们将在课程的后面更加深入地探索注意力层;现在,您需要知道的是,这一层将告诉模型在处理每个单词的表示时,要特别重视您传递给它的句子中的某些单词(并且或多或少地忽略其他单词)。 @@ -147,7 +148,7 @@ Transformer模型的一个关键特性是*注意力层*。事实上,介绍Tran 现在您已经了解了注意力层的含义,让我们更仔细地了解Transformer架构。 -## 原始的结构 +## 原始的结构 [[原始的结构]] Transformer架构最初是为翻译而设计的。在训练期间,编码器接收特定语言的输入(句子),而解码器需要输出对应语言的翻译。在编码器中,注意力层可以使用一个句子中的所有单词(正如我们刚才看到的,给定单词的翻译可以取决于它在句子中的其他单词)。然而,解码器是按顺序工作的,并且只能注意它已经翻译过的句子中的单词。例如,当我们预测了翻译目标的前三个单词时,我们将它们提供给解码器,然后解码器使用编码器的所有输入来尝试预测第四个单词。 @@ -164,7 +165,7 @@ Transformer架构最初是为翻译而设计的。在训练期间,编码器接 也可以在编码器/解码器中使用*注意力遮罩层*,以防止模型注意某些特殊单词。例如,在批处理句子时,填充特殊词使所有句子的长度一致。 -## 架构与参数 +## 架构与参数 [[架构与参数]] 在本课程中,当我们深入探讨Transformers模型时,您将看到 架构、参数和模型 diff --git a/chapters/zh-CN/chapter1/5.mdx b/chapters/zh-CN/chapter1/5.mdx index 7c235523d..64b30d2d7 100644 --- a/chapters/zh-CN/chapter1/5.mdx +++ b/chapters/zh-CN/chapter1/5.mdx @@ -1,4 +1,4 @@ -# “编码器”模型 +# “编码器”模型 [[“编码器”模型]] -# 管道的内部 +# 管道的内部 [[管道的内部]] {#if fw === 'pt'} @@ -62,7 +62,7 @@ classifier( 让我们快速浏览一下这些内容。 -## 使用分词器进行预处理 +## 使用分词器进行预处理 [[使用分词器进行预处理]] 与其他神经网络一样,Transformer模型无法直接处理原始文本, 因此我们管道的第一步是将文本输入转换为模型能够理解的数字。 为此,我们使用*tokenizer*(标记器),负责: @@ -121,7 +121,7 @@ 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] ]) } ``` @@ -139,7 +139,7 @@ print(inputs) 'attention_mask': } ``` @@ -147,7 +147,7 @@ print(inputs) 输出本身是一个包含两个键的字典,`input_ids`和`attention_mask`。`input_ids`包含两行整数(每个句子一行),它们是每个句子中标记的唯一标记(token)。我们将在本章后面解释什么是`attention_mask`。 -## 浏览模型 +## 浏览模型 [[浏览模型]] {#if fw === 'pt'} 我们可以像使用标记器一样下载预训练模型。🤗 Transformers提供了一个`AutoModel`类,该类还具有`from_pretrained()`方法: @@ -177,7 +177,7 @@ model = TFAutoModel.from_pretrained(checkpoint) 虽然这些隐藏状态本身可能很有用,但它们通常是模型另一部分(称为*头部(head)*)的输入。 在[Chapter 1](/course/chapter1)中,可以使用相同的体系结构执行不同的任务,但这些任务中的每个任务都有一个与之关联的不同头。 -### 高维向量? +### 高维向量? [[高维向量?]] Transformers模块的矢量输出通常较大。它通常有三个维度: @@ -211,7 +211,7 @@ print(outputs.last_hidden_state.shape) 注意🤗 Transformers模型的输出与`namedtuple`或词典相似。您可以通过属性(就像我们所做的那样)或键(`输出["last_hidden_state"]`)访问元素,甚至可以通过索引访问元素,前提是您确切知道要查找的内容在哪里(`outputs[0]`)。 -### 模型头:数字的意义 +### 模型头:数字的意义 [[模型头:数字的意义]] 模型头将隐藏状态的高维向量作为输入,并将其投影到不同的维度。它们通常由一个或几个线性层组成: @@ -259,7 +259,7 @@ outputs = model(inputs) ``` {/if} -现在,如果我们观察输入的形状,维度将低得多:模型头将我们之前看到的高维向量作为输入,并输出包含两个值的向量(每个标签一个): +现在,如果我们观察输出的形状,维度将低得多:模型头将我们之前看到的高维向量作为输入,并输出包含两个值的向量(每个标签一个): ```python print(outputs.logits.shape) @@ -281,7 +281,7 @@ torch.Size([2, 2]) 因为我们只有两个句子和两个标签,所以我们从模型中得到的结果是2 x 2的形状。 -## 对输出进行后处理 +## 对输出进行后处理 [[对输出进行后处理]] 我们从模型中得到的输出值本身并不一定有意义。我们来看看, diff --git a/chapters/zh-CN/chapter2/3.mdx b/chapters/zh-CN/chapter2/3.mdx index e840668d5..67946ab1a 100644 --- a/chapters/zh-CN/chapter2/3.mdx +++ b/chapters/zh-CN/chapter2/3.mdx @@ -1,6 +1,6 @@ -# 模型 +# 模型 [[模型]] {#if fw === 'pt'} @@ -44,7 +44,7 @@ AutoModel类,当您希望从检查点实例化任何模型时,这非常方 但是,如果您知道要使用的模型类型,则可以使用直接定义其体系结构的类。让我们看看这是如何与BERT模型一起工作的。 -## 创建转换器 +## 创建转换器 [[创建转换器]] 初始化BERT模型需要做的第一件事是加载配置对象: @@ -90,7 +90,7 @@ BertConfig { 虽然您还没有看到所有这些属性都做了什么,但您应该认识到其中的一些属性:hidden_size属性定义了hidden_状态向量的大小,num_hidden_layers定义了Transformer模型的层数。 -### 不同的加载方式 +### 不同的加载方式 [[不同的加载方式]] 从默认配置创建模型会使用随机值对其进行初始化: @@ -166,7 +166,7 @@ HF_HOME 用于加载模型的标识符可以是模型中心Hub上任何模型的标识符,只要它与BERT体系结构兼容。可以找到可用的BERT检查点的完整列表 [here](https://huggingface.co/models?filter=bert) . -### 保存模型 +### 保存模型 [[保存模型]] 保存模型和加载模型一样简单--我们使用 save_pretrained() @@ -206,7 +206,7 @@ config.json {/if} -### 使用Transformers模型进行推理 +### 使用Transformers模型进行推理 [[使用Transformers模型进行推理]] 既然您知道了如何加载和保存模型,那么让我们尝试使用它进行一些预测。Transformer模型只能处理数字——分词器生成的数字。但在我们讨论标记化器之前,让我们先探讨模型接受哪些输入。 @@ -246,7 +246,7 @@ model_inputs = tf.constant(encoded_sequences) ``` {/if} -### 使用张量作为模型的输入 +### 使用张量作为模型的输入 [[使用张量作为模型的输入]] diff --git a/chapters/zh-CN/chapter2/4.mdx b/chapters/zh-CN/chapter2/4.mdx index 4190508b3..f211b3db4 100644 --- a/chapters/zh-CN/chapter2/4.mdx +++ b/chapters/zh-CN/chapter2/4.mdx @@ -1,6 +1,6 @@ -# 标记器(Tokenizer) +# 标记器(Tokenizer) [[标记器(Tokenizer)]] {#if fw === 'pt'} @@ -36,7 +36,7 @@ Jim Henson was a puppeteer 让我们看一下标记化算法的一些示例,并尝试回答您可能对标记化提出的一些问题。 -## 基于词的(Word-based) +## 基于词的(Word-based) [[基于词的(Word-based)]] @@ -68,7 +68,7 @@ print(tokenized_text) 减少未知标记数量的一种方法是使用更深一层的标记器(tokenizer),即基于字符的(_character-based_)标记器(tokenizer)。 -## 基于字符(Character-based) +## 基于字符(Character-based) [[基于字符(Character-based)]] @@ -90,7 +90,7 @@ print(tokenized_text) 为了两全其美,我们可以使用结合这两种方法的第三种技术:*子词标记化(subword tokenization)*。 -## 子词标记化 +## 子词标记化 [[子词标记化]] @@ -109,7 +109,7 @@ print(tokenized_text) 这种方法在土耳其语等粘着型语言(agglutinative languages)中特别有用,您可以通过将子词串在一起来形成(几乎)任意长的复杂词。 -### 还有更多! +### 还有更多! [[还有更多!]] 不出所料,还有更多的技术。仅举几例: @@ -119,7 +119,7 @@ print(tokenized_text) 您现在应该对标记器(tokenizers)的工作原理有足够的了解,以便开始使用 API。 -## 加载和保存 +## 加载和保存 [[加载和保存]] 加载和保存标记器(tokenizer)就像使用模型一样简单。实际上,它基于相同的两种方法: `from_pretrained()` 和 `save_pretrained()` 。这些方法将加载或保存标记器(tokenizer)使用的算法(有点像*建筑学(architecture)*的模型)以及它的词汇(有点像*权重(weights)*模型)。 @@ -165,7 +165,7 @@ tokenizer.save_pretrained("directory_on_my_computer") 我们在[Chapter 3](/Couse/chapter3)中将更多地谈论`token_type_ids`,稍后我们将解释 `attention_mask` 键。首先,让我们看看 `input_ids` 如何生成。为此,我们需要查看标记器(tokenizer)的中间方法。 -## 编码 +## 编码 [[编码]] @@ -177,7 +177,7 @@ tokenizer.save_pretrained("directory_on_my_computer") 为了更好地理解这两个步骤,我们将分别探讨它们。请注意,我们将使用一些单独执行部分标记化管道的方法来向您展示这些步骤的中间结果,但实际上,您应该直接在您的输入上调用标记器(tokenizer)(如第 2 部分所示)。 -### 标记化 +### 标记化 [[标记化]] 标记化过程由标记器(tokenizer)的`tokenize()` 方法实现: @@ -200,7 +200,7 @@ print(tokens) 这个标记器(tokenizer)是一个子词标记器(tokenizer):它对词进行拆分,直到获得可以用其词汇表表示的标记(token)。`transformer` 就是这种情况,它分为两个标记:`transform` 和 `##er`。 -### 从词符(token)到输入 ID +### 从词符(token)到输入 ID [[从词符(token)到输入 ID]] 输入 ID 的转换由标记器(tokenizer)的`convert_tokens_to_ids()`方法实现: ```py @@ -221,7 +221,7 @@ print(ids) -## 解码 +## 解码 [[解码]] *解码(Decoding)* 正好相反:从词汇索引中,我们想要得到一个字符串。这可以通过 `decode()` 方法实现,如下: diff --git a/chapters/zh-CN/chapter2/5.mdx b/chapters/zh-CN/chapter2/5.mdx index fc1e62c43..2f35dbcb9 100644 --- a/chapters/zh-CN/chapter2/5.mdx +++ b/chapters/zh-CN/chapter2/5.mdx @@ -1,6 +1,6 @@ -# 处理多个序列 +# 处理多个序列 [[处理多个序列]] {#if fw === 'pt'} @@ -43,7 +43,7 @@ 让我们看看这些问题会带来什么样的问题,以及如何使用🤗 Transformers API解决它们 -## 模型需要一批输入 +## 模型需要一批输入 [[模型需要一批输入]] 在上一个练习中,您看到了序列如何转换为数字列表。让我们将此数字列表转换为张量,并将其发送到模型: @@ -93,7 +93,7 @@ InvalidArgumentError: Input to reshape is a tensor with 14 values, but the reque 哦,不!为什么失败了?“我们遵循了第2节中管道的步骤。 -问题是我们向模型发送了一个序列,而🤗 Transformers模型默认情况下需要多个句子。在这里,当我们将分词器应用于一个应用程序时,我们尝试在幕后完成分词器所做的一切,但如果仔细观察,您会发现它不仅将输入ID列表转换为张量,还在其顶部添加了一个维度: +问题是我们向模型发送了一个序列,而🤗 Transformers模型默认情况下需要多个句子。在这里,当我们将分词器应用于一个应用程序时,我们尝试在幕后完成分词器所做的一切,但如果仔细观察,您会发现tokenizer不仅将输入ID列表转换为张量,还在其顶部添加了一个维度: {#if fw === 'pt'} ```py @@ -194,7 +194,7 @@ batched_ids = [ids, ids] 批处理允许模型在输入多个句子时工作。使用多个序列就像使用单个序列构建批一样简单。不过,还有第二个问题。当你试图将两个(或更多)句子组合在一起时,它们的长度可能不同。如果您以前使用过张量,那么您知道它们必须是矩形,因此无法将输入ID列表直接转换为张量。为了解决这个问题,我们通常填充输入。 -## 填充输入 +## 填充输入 [[填充输入]] 以下列表不能转换为张量: @@ -271,7 +271,7 @@ tf.Tensor( 这是因为Transformer模型的关键特性是关注层,它将每个标记上下文化。这些将考虑填充标记,因为它们涉及序列中的所有标记。为了在通过模型传递不同长度的单个句子时,或者在传递一批应用了相同句子和填充的句子时获得相同的结果,我们需要告诉这些注意层忽略填充标记。这是通过使用 attention mask来实现的。 -## 注意力面具 +## 注意力面具 [[注意力面具]] *Attention masks*是与输入ID张量形状完全相同的张量,用0和1填充:1s表示应注意相应的标记,0s表示不应注意相应的标记(即,模型的注意力层应忽略它们)。 @@ -330,7 +330,7 @@ tf.Tensor( -## 长序列 +## 长序列 [[长序列]] 对于Transformers模型,我们可以通过模型的序列长度是有限的。大多数模型处理多达512或1024个令牌的序列,当要求处理更长的序列时,会崩溃。此问题有两种解决方案: diff --git a/chapters/zh-CN/chapter2/6.mdx b/chapters/zh-CN/chapter2/6.mdx index 7c38c2c8d..17dd3c2c8 100644 --- a/chapters/zh-CN/chapter2/6.mdx +++ b/chapters/zh-CN/chapter2/6.mdx @@ -1,6 +1,6 @@ -# 把它们放在一起 +# 把它们放在一起 [[把它们放在一起]] {#if fw === 'pt'} @@ -98,7 +98,7 @@ model_inputs = tokenizer(sequences, padding=True, return_tensors="tf") model_inputs = tokenizer(sequences, padding=True, return_tensors="np") ``` -## 特殊词符(token) +## 特殊词符(token) [[特殊词符(token)]] 如果我们看一下标记器返回的输入 ID,我们会发现它们与之前的略有不同: @@ -132,7 +132,7 @@ print(tokenizer.decode(ids)) 标记器在开头添加了特殊单词`[CLS]`,在结尾添加了特殊单词`[SEP]`。这是因为模型是用这些数据预训练的,所以为了得到相同的推理结果,我们还需要添加它们。请注意,有些模型不添加特殊单词,或者添加不同的单词;模型也可能只在开头或结尾添加这些特殊单词。在任何情况下,标记器都知道需要哪些词符,并将为您处理这些词符。 -## 结束:从标记器到模型 +## 结束:从标记器到模型 [[结束:从标记器到模型]] 现在我们已经看到了标记器对象在应用于文本时使用的所有单独步骤,让我们最后一次看看它如何处理多个序列(填充!),非常长的序列(截断!),以及多种类型的张量及其主要API: diff --git a/chapters/zh-CN/chapter2/7.mdx b/chapters/zh-CN/chapter2/7.mdx index ef205f164..b3924309b 100644 --- a/chapters/zh-CN/chapter2/7.mdx +++ b/chapters/zh-CN/chapter2/7.mdx @@ -1,4 +1,4 @@ -# 基本用法完成! +# 基本用法完成! [[基本用法完成!]] -# 章末小测试 +# 章末小测试 [[章末小测验]] AutoNLP 产品相混淆了?" + explain: "错误。您可能把AutoModel与我们的AutoTrain 产品相混淆了?" }, { text: "一个根据Checkpoint(检查点)返回模型体系结构的对象", @@ -125,7 +125,7 @@ choices={[ { text: "根据您的数据自动进行训练的模型", - explain: "错误。您可能把TFAutoModel与我们的AutoNLP 产品相混淆了?" + explain: "错误。您可能把TFAutoModel与我们的AutoTrain 产品相混淆了?" }, { text: "一个根据Checkpoint(检查点)返回模型体系结构的对象", diff --git a/chapters/zh-CN/chapter3/1.mdx b/chapters/zh-CN/chapter3/1.mdx index e744bf78e..2e1c920e1 100644 --- a/chapters/zh-CN/chapter3/1.mdx +++ b/chapters/zh-CN/chapter3/1.mdx @@ -1,6 +1,6 @@ -# 本章简介 +# 本章简介 [[本章简介]] -# 处理数据 +# 处理数据 [[处理数据]] {#if fw === 'pt'} @@ -76,7 +76,7 @@ model.train_on_batch(batch, labels) 在本节中,我们将使用MRPC(微软研究释义语料库)数据集作为示例,该数据集由威廉·多兰和克里斯·布罗克特在[这篇文章](https://www.aclweb.org/anthology/I05-5002.pdf)发布。该数据集由5801对句子组成,每个句子对带有一个标签,指示它们是否为同义(即,如果两个句子的意思相同)。我们在本章中选择了它,因为它是一个小数据集,所以很容易对它进行训练。 -### 从模型中心(Hub)加载数据集 +### 从模型中心(Hub)加载数据集 [[从模型中心(Hub)加载数据集]] {#if fw === 'pt'} @@ -114,7 +114,7 @@ DatasetDict({ 正如你所看到的,我们获得了一个**DatasetDict**对象,其中包含训练集、验证集和测试集。每一个集合都包含几个列(**sentence1**, **sentence2**, **label**, and **idx**)以及一个代表行数的变量,即每个集合中的行的个数(因此,训练集中有3668对句子,验证集中有408对,测试集中有1725对)。 -默认情况下,此命令在下载数据集并缓存到 **~/.cache/huggingface/dataset**. 回想一下第2章,您可以通过设置**HF_HOME**环境变量来自定义缓存的文件夹。 +默认情况下,此命令在下载数据集并缓存到 **~/.cache/huggingface/datasets**. 回想一下第2章,您可以通过设置**HF_HOME**环境变量来自定义缓存的文件夹。 我们可以访问我们数据集中的每一个**raw_train_dataset**对象,如使用字典: @@ -151,7 +151,7 @@ raw_train_dataset.features -### 预处理数据集 +### 预处理数据集 [[预处理数据集]] {#if fw === 'pt'} @@ -278,7 +278,7 @@ DatasetDict({ 最后一件我们需要做的事情是,当我们一起批处理元素时,将所有示例填充到最长元素的长度——我们称之为动态填充。 -### 动态填充 +### 动态填充 [[动态填充]] diff --git a/chapters/zh-CN/chapter3/3.mdx b/chapters/zh-CN/chapter3/3.mdx index f00d71e12..efcdb0916 100644 --- a/chapters/zh-CN/chapter3/3.mdx +++ b/chapters/zh-CN/chapter3/3.mdx @@ -1,6 +1,6 @@ -# 使用 Trainer API 微调模型 +# 使用 Trainer API 微调模型 [[使用 Trainer API 微调模型]] diff --git a/chapters/zh-CN/chapter3/3_tf.mdx b/chapters/zh-CN/chapter3/3_tf.mdx index c369f06d2..936c81e76 100644 --- a/chapters/zh-CN/chapter3/3_tf.mdx +++ b/chapters/zh-CN/chapter3/3_tf.mdx @@ -1,6 +1,6 @@ -# 使用 Keras 微调一个模型 +# 使用 Keras 微调一个模型 [[使用 Keras 微调一个模型]] -### 提升训练的效果 +### 提升训练的效果 [[提升训练的效果]] @@ -150,7 +150,7 @@ model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) -### 模型预测 +### 模型预测 [[模型预测]] @@ -187,4 +187,4 @@ metric.compute(predictions=class_preds, references=raw_datasets["validation"]["l 您获得的确切结果可能会有所不同,因为模型头的随机初始化可能会改变它获得的指标。 在这里,我们可以看到我们的模型在验证集上的准确率为 85.78%,F1 得分为 89.97。 这些是用于评估 GLUE 基准的 MRPC 数据集结果的两个指标。 [BERT 论文](https://arxiv.org/pdf/1810.04805.pdf) 中的表格报告了基本模型的 F1 分数为 88.9。 那是 `uncased` 模型,而我们目前使用的是 `cased` 模型,这解释了为什么我们会获得更好的结果。 -使用 Keras API 进行微调的介绍到此结束。 第 7 章将给出对大多数常见 NLP 任务执行此操作的示例。如果您想在 Keras API 上磨练自己的技能,请尝试使第二节所使用的的数据处理在 GLUE SST-2 数据集上微调模型。 \ No newline at end of file +使用 Keras API 进行微调的介绍到此结束。 [第 7 章](/course/chapter7)将给出对大多数常见 NLP 任务执行此操作的示例。如果您想在 Keras API 上磨练自己的技能,请尝试使第二节所使用的的数据处理在 GLUE SST-2 数据集上微调模型。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter3/4.mdx b/chapters/zh-CN/chapter3/4.mdx index 99aa4a19e..bc8921f13 100644 --- a/chapters/zh-CN/chapter3/4.mdx +++ b/chapters/zh-CN/chapter3/4.mdx @@ -1,4 +1,4 @@ -# 一个完整的训练 +# 一个完整的训练 [[一个完整的训练]] -### S使用🤗 Accelerate加速您的训练循环 +### S使用🤗 Accelerate加速您的训练循环 [[S使用🤗 Accelerate加速您的训练循环]] diff --git a/chapters/zh-CN/chapter3/5.mdx b/chapters/zh-CN/chapter3/5.mdx index 51edbadeb..ee69b3a3a 100644 --- a/chapters/zh-CN/chapter3/5.mdx +++ b/chapters/zh-CN/chapter3/5.mdx @@ -1,6 +1,6 @@ -# 微调,检查! +# 微调,检查! [[微调,检查!]] -# End-of-chapter quiz +# 章末小测验 [[章末小测验]] TPUStrategy scope 中的所有内容,包括模型的初始化。" }, { - text: "您可以利用现有的方法,如 < code > compile () 、 < code > fit () < c/ode > 和 < code > predict () 。", + text: "您可以利用现有的方法,如 < code > compile () 、 < code > fit () 和 < code > predict () 。", explain: "正确! 一旦你有了这些数据,在这些数据上进行培训只需要很少的工作。", correct: true }, diff --git a/chapters/zh-CN/chapter4/1.mdx b/chapters/zh-CN/chapter4/1.mdx index ea639b48c..32c8ef87e 100644 --- a/chapters/zh-CN/chapter4/1.mdx +++ b/chapters/zh-CN/chapter4/1.mdx @@ -1,4 +1,4 @@ -# The Hugging Face Hub +# The Hugging Face Hub [[The Hugging Face Hub]] -# 使用预训练的模型 +# 使用预训练模型 [[使用预训练模型]] {#if fw === 'pt'} @@ -22,15 +22,15 @@ {/if} -模型中心使选择合适的模型变得简单,因此只需几行代码即可在任何下游库中使用它。让我们来看看如何实际使用这些模型之一,以及如何回馈社区。 +模型中心使选择合适的模型变得简单,因此只需几行代码即可在任何下游库中使用它。让我们来看看如何使用这些模型,以及如何将模型贡献到社区。 -假设我们正在寻找一种可以执行**mask**填充的French-based模型。 +假设我们正在寻找一种可以执行 mask 填充的 French-based 模型。
Selecting the Camembert model.
-我们选择 **camembert-base** 检查点来尝试一下。我们需要做的仅仅是输入 `camembert-base`标识符!正如您在前几章中看到的,我们可以使用 **pipeline()** 功能: +我们选择 `camembert-base` 检查点来尝试一下。我们需要做的仅仅是输入 `camembert-base` 标识符!正如你在前几章中看到的,我们可以使用 `pipeline()` 功能: ```py from transformers import pipeline @@ -49,13 +49,13 @@ results = camembert_fill_mask("Le camembert est :)") ] ``` -如您所见,在管道中加载模型非常简单。您唯一需要注意的是所选检查点是否适合它将用于的任务。例如,这里我们正在加载 **camembert-base** 检查点在 **fill-mask** 管道,这完全没问题。但是如果我们要在 **text-classification** 管道,结果没有任何意义,因为 **camembert-base** 不适合这个任务!我们建议使用 Hugging Face Hub 界面中的任务选择器来选择合适的检查点: +如你所见,在管道中加载模型非常简单。你唯一需要注意的是所选检查点是否适合它将用于的任务。例如,这里我们正在将 `camembert-base` 检查点加载在 `fill-mask` 管道,这完全没问题。但是如果我们在 `text-classification` 管道加载检查点,结果没有任何意义,因为 `camembert-base` 不适合这个任务!我们建议使用 Hugging Face Hub 界面中的任务选择器来选择合适的检查点:
The task selector on the web interface.
-您还可以直接使用模型架构实例化检查点: +你还可以直接使用模型架构实例化检查点: {#if fw === 'pt'} ```py @@ -65,7 +65,7 @@ tokenizer = CamembertTokenizer.from_pretrained("camembert-base") model = CamembertForMaskedLM.from_pretrained("camembert-base") ``` -然而,我们建议使用[Auto* 类](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes),因为Auto* 类设计与架构无关。前面的代码示例将只能在 CamemBERT 架构中加载可用的检查点,但使用 **Auto*** 类使切换检查点变得简单: +然而,我们建议使用 [`Auto*` 类](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes),因为 `Auto*` 类设计与架构无关。前面的代码示例将只能在 CamemBERT 架构中加载可用的检查点,但使用 `Auto*` 类使切换不同的检查点变得简单: ```py from transformers import AutoTokenizer, AutoModelForMaskedLM @@ -81,8 +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: -然而,我们建议使用[`TFAuto*` 类](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes),因为`TFAuto*`类设计与架构无关。前面的代码示例将只能在 CamemBERT 架构中加载可用的检查点,但使用 `TFAuto*` 类使切换检查点变得简单: +然而,我们建议使用 [`TFAuto*` 类](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes),因为 `TFAuto*` 类设计与架构无关。前面的代码示例将只能在 CamemBERT 架构中加载可用的检查点,但使用 `TFAuto*` 类使切换不同的检查点变得简单: ```py from transformers import AutoTokenizer, TFAutoModelForMaskedLM @@ -93,5 +92,5 @@ model = TFAutoModelForMaskedLM.from_pretrained("camembert-base") {/if} -使用预训练模型时,一定要检查它是如何训练的,在哪些数据集上,它的限制和它的偏差。所有这些信息都应在其模型卡片上注明。 +使用预训练模型时,一定要检查它是如何训练的、在哪些数据集上训练的、它的局限性和偏差。所有这些信息都应在其模型卡片上注明。 diff --git a/chapters/zh-CN/chapter4/3.mdx b/chapters/zh-CN/chapter4/3.mdx index c9c920dc5..9be4ff273 100644 --- a/chapters/zh-CN/chapter4/3.mdx +++ b/chapters/zh-CN/chapter4/3.mdx @@ -1,6 +1,6 @@ -# 共享预训练模型 +# 共享预训练模型 [[共享预训练模型]] {#if fw === 'pt'} @@ -37,7 +37,7 @@ 创建存储库后,您可以通过 git 和 git-lfs 将文件上传到其中。我们将在以下部分引导您创建模型存储库并将文件上传到它们 -## 使用 push_to_hub API +## 使用 push_to_hub API [[使用 push_to_hub API]] {#if fw === 'pt'} @@ -190,7 +190,7 @@ tokenizer.push_to_hub("dummy-model", organization="huggingface", use_auth_token= 跳到最后一部分,了解如何将文件上传到新创建的存储库! -## 使用 huggingface_hub python库 +## 使用 huggingface_hub python库 [[使用 huggingface_hub python库]] 这 **huggingface_hub** Python 库是一个包,它为模型和数据集中心提供了一组工具。它为常见任务提供了简单的方法和类,例如 获取有关集线器上存储库的信息并对其进行管理。它提供了在 git 之上工作的简单 API 来管理这些存储库的内容并集成 Hub @@ -256,7 +256,7 @@ create_repo("dummy-model", organization="huggingface") 创建存储库后,我们应该向其中添加文件!跳到下一部分以查看可以处理此问题的三种方法。 -## 使用网络界面 +## 使用网络界面 [[使用网络界面]] Web 界面提供了直接在 Hub 中管理存储库的工具。使用该界面,您可以轻松创建存储库、添加文件(甚至是大文件!)、探索模型、可视化差异等等。 @@ -292,13 +292,13 @@ README 文件在 Markdown 中 - 随意使用它!本章的第三部分致力于 接下来我们将看看如何添加一些新文件。 -## 上传模型文件 +## 上传模型文件 [[上传模型文件]] Hugging Face Hub 上的文件管理系统基于用于常规文件的 git 和 git-lfs(代表[Git Large File Storage](https://git-lfs.github.com/)) 对于较大的文件。 在下一节中,我们将介绍将文件上传到 Hub 的三种不同方式:通过 **huggingface_hub** 并通过 git 命令。 -### The `upload_file` approach +### The `upload_file` approach [[The `upload_file` approach]] 使用 **upload_file** 不需要在您的系统上安装 git 和 git-lfs。它使用 HTTP POST 请求将文件直接推送到 🤗 Hub。这种方法的一个限制是它不能处理大于 5GB 的文件。 如果您的文件大于 5GB,请按照下面详述的另外两种方法进行操作。API 可以按如下方式使用: @@ -320,7 +320,7 @@ upload_file( - repo_type, 如果你想要上传一个 `dataset` 或一个 `space` 而不是模型。 接受的值为 `"dataset"` 和 `"space"`. -### The `Repository` class +### The `Repository` class [[The `Repository` class]] 以类似 git 的方式管理本地存储库。它抽象了 git 可能遇到的大部分痛点,以提供我们需要的所有功能。 @@ -373,7 +373,7 @@ repo.git_push() 恭喜!您刚刚将第一个文件推送到hub上。 -### The git-based approach +### The git-based approach [[The git-based approach]] 这是上传文件的非常简单的方法:我们将直接使用 git 和 git-lfs 来完成。大多数困难都被以前的方法抽象掉了,但是下面的方法有一些警告,所以我们将遵循一个更复杂的用例。 diff --git a/chapters/zh-CN/chapter4/4.mdx b/chapters/zh-CN/chapter4/4.mdx index 694f4ace2..e844ac18a 100644 --- a/chapters/zh-CN/chapter4/4.mdx +++ b/chapters/zh-CN/chapter4/4.mdx @@ -1,4 +1,4 @@ -# 构建模型卡片 +# 构建模型卡片 [[构建模型卡片]] -# 章末小测试 +# 章末小测试 [[章末小测验]] -## 使用本地和远程数据集 +## 使用本地和远程数据集 [[使用本地和远程数据集]] 🤗 Datasets 提供了加载脚本来加载本地和远程数据集。它支持几种常见的数据格式,例如: @@ -24,7 +24,7 @@ 如表所示, 对于每种数据格式, 我们只需要使用 `load_dataset()` 函数, 使用 `data_files` 指定一个或多个文件的路径的参数。 让我们从本地文件加载数据集开始;稍后我们将看到如何对远程文件执行相同的操作。 -## 加载本地数据集 +## 加载本地数据集 [[加载本地数据集]] 对于这个例子,我们将使用 [SQuAD-it dataset](https://github.com/crux82/squad-it/), 这是一个大规模的意大利语问答数据集。 @@ -46,7 +46,7 @@ SQuAD_it-test.json.gz: 87.4% -- replaced with SQuAD_it-test.json SQuAD_it-train.json.gz: 82.2% -- replaced with SQuAD_it-train.json ``` -我们可以看到压缩文件已经被替换为SQuAD_it-train.json和SQuAD_it-text.json,并且数据以 JSON 格式存储。 +我们可以看到压缩文件已经被替换为SQuAD_it-train.json和SQuAD_it-test.json,并且数据以 JSON 格式存储。 @@ -143,7 +143,7 @@ squad_it_dataset = load_dataset("json", data_files=data_files, field="data") 现在你知道如何在笔记本电脑或台式机上加载本地文件,让我们来看看加载远程文件。 -## 加载远程数据集 +## 加载远程数据集 [[加载远程数据集]] 如果你在公司担任数据研究员或编码员,那么你要分析的数据集很有可能存储在某个远程服务器上。幸运的是,加载远程文件就像加载本地文件一样简单!我们没有提供本地文件的路径, 而是将`load_dataset()`的`data_files`参数指向存储远程文件的一个或多个URL。例如, 对于托管在 GitHub 上的 SQuAD-it 数据集, 我们可以将 `data_files` 指向 _SQuAD_it-*.json.gz_ 的网址,如下所示: diff --git a/chapters/zh-CN/chapter5/3.mdx b/chapters/zh-CN/chapter5/3.mdx index 5d92ed61b..239780da7 100644 --- a/chapters/zh-CN/chapter5/3.mdx +++ b/chapters/zh-CN/chapter5/3.mdx @@ -1,4 +1,4 @@ -# 是时候来学一下切片了 +# 是时候来学一下切片了 [[是时候来学一下切片了]] -## 切片与切分我们的数据 +## 切片与切分我们的数据 [[切片与切分我们的数据]] 与 Pandas 类似,🤗 Datasets 提供了几个函数来操作 **Dataset** 和 **DatasetDict** 对象。我们在[第三章](/course/chapter3)已经遇到了 **Dataset.map()** 方法,在本节中,我们将探索我们可以使用的其他功能。 @@ -168,7 +168,7 @@ drug_dataset["train"]["condition"][:3] 有用!现在我们已经清理了标签,让我们来看看清洗后的评论文本。 -## Creating new columns +## 创建新的数据列 [[创建新的数据列]] 每当您处理客户评论时,一个好的做法是检查每个评论中的字数。评论可能只是一个词,比如“太棒了!”或包含数千字的完整文章,根据实际的情况,您需要以不同的方式处理这些极端情况。为了计算每条评论中的单词数,我们将使用基于空格分割每个文本的粗略方法。 @@ -263,7 +263,7 @@ drug_dataset = drug_dataset.map(lambda x: {"review": html.unescape(x["review"])} 如您所见, **Dataset.map()** 方法对于处理数据非常有用——在示例中仅仅是浅尝辄止就有很大的收获! -## map() 方法的超级加速 +## map() 方法的超级加速 [[map() 方法的超级加速]] **Dataset.map()** 方法有一个 **batched** 参数,如果设置为 **True** , map 函数将会分批执行所需要进行的操作(批量大小是可配置的,但默认为 1,000)。例如,之前对所有 HTML 进行转义的 map 函数运行需要一些时间(您可以从进度条中读取所用时间)。我们可以通过使用列表推导同时处理多个元素来加快速度。 @@ -446,7 +446,7 @@ DatasetDict({ 您现在已经了解了 🤗 Datasets如何以各种方式用于预处理数据集。虽然🤗 Datasets 的处理功能会覆盖你大部分的模型训练需求,有时您可能需要切换到 Pandas 以使用更强大的功能,例如 **DataFrame.groupby()** 或用于可视化的高级 API。幸运的是,🤗 Datasets旨在与 Pandas、NumPy、PyTorch、TensorFlow 和 JAX 等库可以相互转换。让我们来看看这是如何工作的。 -## `🤗 Datasets 和 DataFrames 的相互转换 +## `🤗 Datasets 和 DataFrames 的相互转换 [[`🤗 Datasets 和 DataFrames 的相互转换]] @@ -606,7 +606,7 @@ Dataset({ drug_dataset.reset_format() ``` -## 创建验证集 +## 创建验证集 [[创建验证集]] 尽管我们有一个可以用于评估的测试集,但在开发过程中保持测试集不变并创建一个单独的验证集是一个很好的做法。一旦您对模型在测试集上的表现感到满意,您就可以对验证集进行最终的检查。此过程有助于降低您过拟合测试集并部署在现实世界数据上失败的模型的风险。 @@ -640,7 +640,7 @@ DatasetDict({ 太好了,我们现在已经准备好了一个数据集,可以用来训练一些模型了!在[第五节]](/course/chapter5/5)我们将向您展示如何将数据集上传到 Hugging Face Hub,但现在让我们查看在本地计算机上保存数据集的几种方法。 -## 保存数据集 +## 保存数据集 [[保存数据集]] diff --git a/chapters/zh-CN/chapter5/4.mdx b/chapters/zh-CN/chapter5/4.mdx index f5675110c..70ef713ca 100644 --- a/chapters/zh-CN/chapter5/4.mdx +++ b/chapters/zh-CN/chapter5/4.mdx @@ -1,4 +1,4 @@ -# 大数据? 🤗 Datasets 来救援! +# 大数据? 🤗 Datasets 来救援! [[大数据? 🤗 Datasets 来救援!]] -## 流式数据集 +## 流式数据集 [[流式数据集]] 要使用数据集流, 你只需要将 `streaming=True` 参数传递给 `load_dataset()` 函数。接下来, 让我们再次加载 PubMed Abstracts 数据集, 但是采用流模式: diff --git a/chapters/zh-CN/chapter5/5.mdx b/chapters/zh-CN/chapter5/5.mdx index 055f79ea1..a18ee94f4 100644 --- a/chapters/zh-CN/chapter5/5.mdx +++ b/chapters/zh-CN/chapter5/5.mdx @@ -1,4 +1,4 @@ -# 创建自己的数据集 +# 创建自己的数据集 [[创建自己的数据集]] -现在我们有了我们的增强数据集,是时候将它推送到 Hub 并且与社区共享它!要上传数据集,我们将使用[🤗 Hub 库](https://github.com/huggingface/huggingface_hub),它允许我们通过 Python API 与 Hugging Face Hub 进行交互。 🤗 Hub 预装了🤗 Transformers,所以我们可以直接使用它。例如,我们可以使用 **list_datasets()** 获取有关当前托管在 Hub 上的所有公共数据集的信息的函数: - -```py -from huggingface_hub import list_datasets - -all_datasets = list_datasets() -print(f"Number of datasets on Hub: {len(all_datasets)}") -print(all_datasets[0]) -``` - -```python out -Number of datasets on Hub: 1487 -Dataset Name: acronym_identification, Tags: ['annotations_creators:expert-generated', 'language_creators:found', 'languages:en', 'licenses:mit', 'multilinguality:monolingual', 'size_categories:10K - -✏️ 试试看!使用您的 Hugging Face Hub 用户名和密码获取令牌并创建一个名为 github-issues.请记住永远不要将您的凭据保存在 Colab 或任何其他存储库中,因为这些信息可能会被不法分子利用。 - - - -接下来,让我们将存储库从 Hub 克隆到我们的本地机器,并将我们的数据集文件复制到其中。 🤗 Hub 提供了一个方便的 **Repository** 类,它包含许多常见 Git 命令的类,因此要克隆远程存储库,我们只需要提供我们要克隆的 URL 和本地路径: - -```py -from huggingface_hub import Repository - -repo = Repository(local_dir="github-issues", clone_from=repo_url) -!cp datasets-issues-with-comments.jsonl github-issues/ -``` - -默认情况下,使用Git LFS跟踪各种文件扩展名(如.bin、.gz和.zip),以便在同一Git工作流中对大型文件进行版本控制。您可以在存储库的.gitattributes文件找到跟踪文件扩展名的列表。要在列表中包含JSON行格式,我们可以运行以下命令: - -```py -repo.lfs_track("*.jsonl") -``` - -然后我们可以使用 **Repository.push_to_hub()** 将数据集推送到 Hub: - -```py -repo.push_to_hub() -``` - -如果我们导航到包含在 **repo_url** ,我们现在应该看到我们的数据集文件已经上传。 - -
-Our dataset repository on the Hugging Face Hub. -
- -从这里,任何人都可以通过简单地提供来下载数据集 **load_dataset()** 以存储库 ID 作为 **path** 争论: +之后,任何人都可以通过便捷地提供带有存储库 ID 作为 path 参数的 load_dataset() 来下载数据集: ```py remote_dataset = load_dataset("lewtun/github-issues", split="train") @@ -427,7 +364,7 @@ Dataset({ -## 创建数据集卡片 +## 创建数据集卡片 [[创建数据集卡片]] 有据可查的数据集更有可能对其他人(包括你未来的自己!)有用,因为它们提供了上下文,使用户能够决定数据集是否与他们的任务相关,并评估任何潜在的偏见或与使用相关的风险。在 Hugging Face Hub 上,此信息存储在每个数据集存储库的自述文件文件。在创建此文件之前,您应该执行两个主要步骤: diff --git a/chapters/zh-CN/chapter5/6.mdx b/chapters/zh-CN/chapter5/6.mdx index 429881676..8de5fb335 100644 --- a/chapters/zh-CN/chapter5/6.mdx +++ b/chapters/zh-CN/chapter5/6.mdx @@ -1,6 +1,6 @@ -# 使用 FAISS 进行语义搜索 +# 使用 FAISS 进行语义搜索 [[使用 FAISS 进行语义搜索]] {#if fw === 'pt'} @@ -26,7 +26,7 @@ -## 使用嵌入进行语义搜索 +## 使用嵌入进行语义搜索 [[使用嵌入进行语义搜索]] 正如我们在[第一章](/course/chapter1),学习的, 基于 Transformer 的语言模型会将文本中的每个标记转换为嵌入向量.事实证明,可以“汇集”各个嵌入向量来创建整个句子、段落或文档(在某些情况下)的向量表示。然后,通过计算每个嵌入之间的点积相似度(或其他一些相似度度量)并返回相似度最大的文档,这些嵌入可用于在语料库中找到相似的文档。在本节中,我们将使用嵌入来开发语义搜索引擎。与基于将查询中的关键字的传统方法相比,这些搜索引擎具有多种优势。 @@ -35,26 +35,15 @@
-## ## 加载和准备数据集 +## 加载和准备数据集 [[加载和准备数据集]] -我们需要做的第一件事是下载我们的 GitHub 问题数据集,所以让我们使用 🤗 Hub 库来解析我们的文件在 Hugging Face Hub 上存储的数据: - -```py -from huggingface_hub import hf_hub_url - -data_files = hf_hub_url( - repo_id="lewtun/github-issues", - filename="datasets-issues-with-comments.jsonl", - repo_type="dataset", -) -``` - -将 URL 存储在 **data_files** ,然后我们可以使用[第二小节](/course/chapter5/2)介绍的方法加载远程数据集: +我们需要做的第一件事是下载我们的 GitHub issues 数据集,所以让我们像往常那样使用 `load_dataset()`函数: ```py from datasets import load_dataset -issues_dataset = load_dataset("json", data_files=data_files, split="train") +issues_dataset = load_dataset("lewtun/github-issues", split="train") + issues_dataset ``` @@ -232,7 +221,7 @@ comments_dataset = comments_dataset.map(concatenate_text) 我们终于准备好创建一些嵌入了!让我们来看看。 -## 创建文本嵌入 +## 创建文本嵌入 [[创建文本嵌入]] 我们在[第二章](/course/chapter2) 学过,我们可以通过使用 **AutoModel** 类来完成词嵌入。我们需要做的就是选择一个合适的检查点来加载模型。幸运的是,有一个名为 **sentence-transformers** 专门用于创建词嵌入。如库中[文档](https://www.sbert.net/examples/applications/semantic-search/README.html#symmetric-vs-asymmetric-semantic-search), 所述的,我们这次要实现的是非对称语义搜索,因为我们有一个简短的查询,我们希望在比如问题评论等更长的文档中找到其答案。通过查看[模型概述表](https://www.sbert.net/docs/pretrained_models.html#model-overview) 我们可以发现 **multi-qa-mpnet-base-dot-v1** 检查点在语义搜索方面具有最佳性能,因此我们将在我们的应用程序中使用它。我们还将使用相同的检查点加载标记器: @@ -345,7 +334,7 @@ embeddings_dataset = comments_dataset.map( 请注意,我们已经将嵌入转换为 NumPy 数组——这是因为当我们尝试使用 FAISS 索引它们时,🤗 Datasets需要这种格式,我们接下来会这样做。 -## 使用 FAISS 进行高效的相似性搜索 +## 使用 FAISS 进行高效的相似性搜索 [[使用 FAISS 进行高效的相似性搜索]] 现在我们有了一个词嵌入数据集,我们需要一些方法来搜索它们。为此,我们将在 🤗 Datasets中使用一种特殊的数据结构,称为 FAISS指数.[FAISS](https://faiss.ai/) (short for Facebook AI Similarity Search) (Facebook AI Similarity Search 的缩写)是一个提供高效算法来快速搜索和聚类嵌入向量的库。FAISS 背后的基本思想是创建一个特殊的数据结构,称为指数。这允许人们找到哪些嵌入词与输入的词嵌入相似。在 🤗 Datasets中创建一个 FAISS 索引很简单——我们使用 **Dataset.add_faiss_index()** 函数并指定我们要索引的数据集的哪一列: diff --git a/chapters/zh-CN/chapter5/7.mdx b/chapters/zh-CN/chapter5/7.mdx index fc9f5e4b4..9f42af397 100644 --- a/chapters/zh-CN/chapter5/7.mdx +++ b/chapters/zh-CN/chapter5/7.mdx @@ -1,4 +1,4 @@ -# 🤗 Datasets,回顾! +# 🤗 Datasets,回顾! [[🤗 Datasets,回顾!]] -# 章末小测试 +# 章末小测试 [[章末小测验]] -# 章末小测验 +# 章末小测验 [[章末小测验]] -## 准备语料库 +## 准备语料库 [[准备语料库]] 🤗 Transformers 中有一个非常简单的 API,你可以用它来训练一个新的标记器,使它与现有标记器相同的特征: **AutoTokenizer.train_new_from_iterator()** .为了复现这一点,假设我们想从头开始训练 GPT-2,但使用英语以外的语言。我们的首要任务是在训练语料库中收集该语言的大量数据。为了提供每个人都能理解的示例,我们在这里不会使用俄语或中文之类的语言,而是使用在特定领域的英语语言:Python 代码。 @@ -129,7 +129,7 @@ def get_training_corpus(): 这将产生与以前完全相同的生成器,但允许您使用比列表生成式中更复杂的逻辑。 -## 训练一个新的标记器 +## 训练一个新的标记器 [[训练一个新的标记器]] 现在我们的语料库是文本批量迭代器的形式,我们准备训练一个新的标记器。为此,我们首先需要加载要与模型配对的标记器(此处为 GPT-2): @@ -218,7 +218,7 @@ tokenizer.tokenize(example) 除了一个缩进对应的token,这里我们还可以看到一个双缩进的token: **ĊĠĠĠĠĠĠĠ** .特殊的 Python 词如 **class** , **init** , **call** , **self** , 和 **return** 每个都被标记为一个标记,我们可以看到,以及分裂 **_** 和 **.** 标记器甚至可以正确拆分驼峰式名称: **LinearLayer** 被标记为 **[ĠLinear, Layer]** . -## 保存标记器 +## 保存标记器 [[保存标记器]] 为了确保我们以后可以使用它,我们需要保存我们的新标记器。就像模型一样,是通过 **save_pretrained()** 方法: diff --git a/chapters/zh-CN/chapter6/3.mdx b/chapters/zh-CN/chapter6/3.mdx index 22db0d122..2cdcc95e3 100644 --- a/chapters/zh-CN/chapter6/3.mdx +++ b/chapters/zh-CN/chapter6/3.mdx @@ -1,6 +1,6 @@ -# 快速标记器的特殊能力 +# 快速标记器的特殊能力 [[快速标记器的特殊能力]] {#if fw === 'pt'} @@ -28,7 +28,7 @@ 在接下来的讨论中,我们会经常区分“慢”和“快”分词器。慢速分词器是在 🤗 Transformers 库中用 Python 编写的,而快速版本是由 🤗 分词器提供的,它们是用 Rust 编写的。如果你还记得在[Chapter 5](/course/chapter5/3)中报告了快速和慢速分词器对药物审查数据集进行分词所需的时间的这张表,您应该知道为什么我们称它们为“快”和“慢”: - | Fast tokenizer | Slow tokenizer +| | Fast tokenizer | Slow tokenizer :--------------:|:--------------:|:-------------: `batched=True` | 10.8s | 4min41s `batched=False` | 59.2s | 5min3s @@ -39,7 +39,7 @@ -## 批量编码 +## 批量编码 [[批量编码]] @@ -134,7 +134,7 @@ Sylvain -## 在令牌分类管道内 +## 在令牌分类管道内 [[在令牌分类管道内]] 在[Chapter 1](/course/chapter1)我们第一次尝试使用 NER——任务是识别文本的哪些部分对应于个人、地点或组织等实体——使用 🤗 Transformers **pipeline()** 功能。然后,在[Chapter 2](/course/chapter2),我们看到了管道如何将从原始文本中获取预测所需的三个阶段组合在一起:标记化、通过模型传递输入和后处理。前两步 **token-classification** 管道与任何其他管道相同,但后处理稍微复杂一些 - 让我们看看如何! @@ -148,7 +148,7 @@ Sylvain {/if} -### 通过管道获得基本结果 +### 通过管道获得基本结果 [[通过管道获得基本结果]] 首先,让我们获取一个标记分类管道,以便我们可以手动比较一些结果。默认使用的模型是[dbmdz/bert-large-cased-finetuned-conll03-english](https://huggingface.co/dbmdz/bert-large-cased-finetuned-conll03-english);它对句子执行 NER: @@ -195,7 +195,7 @@ token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.") 现在让我们看看如何在不使用pipeline()函数的情况下获得这些结果! -### 从输入到预测 +### 从输入到预测 [[从输入到预测]] {#if fw === 'pt'} @@ -402,7 +402,7 @@ print(results) 这和我们从第一个管道中得到的一样! -### 分组实体 +### 分组实体 [[分组实体]] 使用偏移量来确定每个实体的开始和结束键很方便,但该信息并不是绝对必要的。然而,当我们想要将实体组合在一起时,偏移量将为我们节省大量混乱的代码。例如,如果我们想将令牌组合在一起 **Hu** , **##gging** , 和 **Face** ,我们可以制定特殊的规则,说前两个应该附加,同时删除 **##** ,以及 **Face** 应该添加一个空格,因为它不以 **##** — 但这仅适用于这种特定类型的标记器。我们必须为 SentencePiece 或 Byte-Pair-Encoding 分词器(本章稍后讨论)。 diff --git a/chapters/zh-CN/chapter6/3b.mdx b/chapters/zh-CN/chapter6/3b.mdx index 4c4b95d7f..1c23ad523 100644 --- a/chapters/zh-CN/chapter6/3b.mdx +++ b/chapters/zh-CN/chapter6/3b.mdx @@ -1,6 +1,6 @@ -# QA 管道中的快速标记器 +# QA 管道中的快速标记器 [[QA 管道中的快速标记器]] {#if fw === 'pt'} @@ -34,7 +34,7 @@ {/if} -## 使用 `question-answering` 管道 +## 使用 `question-answering` 管道 [[使用 `question-answering` 管道]] 正如我们在[Chapter 1](/course/chapter1),我们可以使用 **question-answering** 像这样的管道以获得问题的答案: @@ -109,7 +109,7 @@ question_answerer(question=question, context=long_context) 让我们看看它是如何做到这一切的! -## 使用模型进行问答 +## 使用模型进行问答 [[使用模型进行问答]] 与任何其他管道一样,我们首先对输入进行标记化,然后通过模型将其发送。默认情况下用于的检查点 **question-answering** 管道是[distilbert-base-cased-distilled-squad](https://huggingface.co/distilbert-base-cased-distilled-squad)(名称中的“squad”来自模型微调的数据集;我们将在[Chapter 7](/course/chapter7/7)): @@ -244,6 +244,8 @@ scores = start_probabilities[:, None] * end_probabilities[None, :] 然后我们将屏蔽这些值 **start_index > end_index** 通过将它们设置为 **0** (其他概率都是正数)。这 **torch.triu()** 函数返回作为参数传递的 2D 张量的上三角部分,因此它会为我们做屏蔽: ```py +import numpy as np + scores = torch.triu(scores) ``` @@ -315,7 +317,7 @@ print(result) -## 处理长上下文 +## 处理长上下文 [[处理长上下文]] 如果我们尝试对我们之前作为示例使用的问题和长上下文进行标记化,我们将获得比在 **question-answering** 管道(即 384): diff --git a/chapters/zh-CN/chapter6/4.mdx b/chapters/zh-CN/chapter6/4.mdx index b43fcbb31..137aba74c 100644 --- a/chapters/zh-CN/chapter6/4.mdx +++ b/chapters/zh-CN/chapter6/4.mdx @@ -1,4 +1,4 @@ -# 标准化和预标记化 +# 标准化和预标记化 [[标准化和预标记化]] @@ -54,7 +54,7 @@ print(tokenizer.backend_tokenizer.normalizer.normalize_str("Héllò hôw are ü? -## 预标记化 +## 预标记化 [[预标记化]] @@ -103,13 +103,13 @@ tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you? 现在我们已经了解了一些不同的标记器如何处理文本,我们可以开始探索底层算法本身。我们首先快速浏览一下广泛适用的 SentencePiece;然后,在接下来的三个部分中,我们将研究用于子词标记化的三种主要算法是如何工作的。 -## 句子 +## 句子 [[句子]] [SentencePiece](https://github.com/google/sentencepiece) 是一种用于文本预处理的标记化算法,您可以将其与我们将在接下来的三个部分中看到的任何模型一起使用。它将文本视为 Unicode 字符序列,并用特殊字符替换空格, **▁** .与 Unigram 算法结合使用(参见[section 7](/course/chapter7/7)), 它甚至不需要预标记化步骤,这对于不使用空格字符的语言(如中文或日语)非常有用。 SentencePiece 的另一个主要特点是可逆标记化:由于没有对空格进行特殊处理,因此只需通过将它们连接起来并替换 **_** s 带空格——这会导致标准化的文本。正如我们之前看到的,BERT 分词器删除了重复的空格,因此它的分词是不可逆的。 -## 算法概述 +## 算法概述 [[算法概述]] 在下面的部分中,我们将深入研究三种主要的子词标记化算法:BPE(由 GPT-2 和其他人使用)、WordPiece(例如由 BERT 使用)和 Unigram(由 T5 和其他人使用)。在我们开始之前,这里是它们各自工作原理的快速概述。如果您还没有理解,请在阅读下一节后立即回到此表。 diff --git a/chapters/zh-CN/chapter6/5.mdx b/chapters/zh-CN/chapter6/5.mdx index af1170c06..e923db49b 100644 --- a/chapters/zh-CN/chapter6/5.mdx +++ b/chapters/zh-CN/chapter6/5.mdx @@ -1,4 +1,4 @@ -# 字节对编码标记化 +# 字节对编码标记化 [[字节对编码标记化]] -## 训练算法 +## 训练算法 [[训练算法]] BPE 训练首先计算语料库中使用的唯一单词集(在完成标准化和预标记化步骤之后),然后通过获取用于编写这些单词的所有符号来构建词汇表。一个非常简单的例子,假设我们的语料库使用了这五个词: @@ -80,7 +80,7 @@ Corpus: ("hug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("hug" "s", 5 -## 标记化算法 +## 标记化算法 [[标记化算法]] 标记化紧跟训练过程,从某种意义上说,通过应用以下步骤对新输入进行标记: @@ -105,7 +105,7 @@ Corpus: ("hug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("hug" "s", 5 -## 实现 BPE +## 实现 BPE [[实现 BPE]] 现在让我们看一下 BPE 算法的实现。这不会是你可以在大型语料库上实际使用的优化版本;我们只是想向你展示代码,以便你可以更好地理解算法 @@ -113,7 +113,7 @@ Corpus: ("hug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("hug" "s", 5 ```python corpus = [ - "This is the Hugging Face course.", + "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.", diff --git a/chapters/zh-CN/chapter6/6.mdx b/chapters/zh-CN/chapter6/6.mdx index 898e065ee..8f3b746ba 100644 --- a/chapters/zh-CN/chapter6/6.mdx +++ b/chapters/zh-CN/chapter6/6.mdx @@ -1,4 +1,4 @@ -# WordPiece 标记化 +# WordPiece 标记化 [[WordPiece 标记化]] -## 训练算法 +## 训练算法 [[训练算法]] @@ -81,7 +81,7 @@ Corpus: ("hug", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "## ✏️ **现在轮到你了!** 下一个合并规则是什么? -## 标记化算法 +## 标记化算法 [[标记化算法]] WordPiece 和 BPE 中的标记化的不同在于 WordPiece 只保存最终词汇,而不是学习的合并规则。从要标记的单词开始,WordPiece 找到词汇表中最长的子词,然后对其进行拆分。例如,如果我们使用上面例子中学到的词汇,对于单词 `"hugs"`,词汇表中从头开始的最长子词是 `"hug"`,所以我们在那里拆分并得到 `["hug", "##s"]`。 然后我们继续使用词汇表中的 `"##s"`,因此 `"hugs"` 的标记化是 `["hug", "##s"]`. @@ -97,7 +97,7 @@ WordPiece 和 BPE 中的标记化的不同在于 WordPiece 只保存最终词汇 -## 实现 WordPiece +## 实现 WordPiece [[实现 WordPiece]] 现在让我们看一下 WordPiece 算法的实现。与 BPE 一样,这只是教学,你将无法在大型语料库中使用它。 @@ -105,7 +105,7 @@ WordPiece 和 BPE 中的标记化的不同在于 WordPiece 只保存最终词汇 ```python corpus = [ - "This is the Hugging Face course.", + "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.", @@ -306,7 +306,7 @@ 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', '##fu', 'Fa', 'Fac', '##ct', '##ful', '##full', '##fully', + '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'] ``` diff --git a/chapters/zh-CN/chapter6/7.mdx b/chapters/zh-CN/chapter6/7.mdx index b5803646c..95202c1e3 100644 --- a/chapters/zh-CN/chapter6/7.mdx +++ b/chapters/zh-CN/chapter6/7.mdx @@ -1,4 +1,4 @@ -# Unigram标记化 +# Unigram标记化 [[Unigram标记化]] -## 训练算法 +## 训练算法 [[训练算法]] 与 BPE 和 WordPiece 相比,Unigram 在另一个方向上工作:它从一个较大的词汇表开始,然后从中删除标记,直到达到所需的词汇表大小。有多种选项可用于构建基本词汇表:例如,我们可以采用预标记化单词中最常见的子串,或者在具有大词汇量的初始语料库上应用 BPE。 @@ -41,7 +41,7 @@ ["h", "u", "g", "hu", "ug", "p", "pu", "n", "un", "b", "bu", "s", "hug", "gs", "ugs"] ``` -## 标记化算法 +## 标记化算法 [[标记化算法]] Unigram 模型是一种语言模型,它认为每个标记都独立于它之前的标记。它是最简单的语言模型,从某种意义上说, 给定先前上下文的标记 X 的概率就是标记 X 的概率。因此,如果我们使用 Unigram 语言模型生成文本,我们将始终预测最常见的标记。 @@ -104,7 +104,7 @@ Character 4 (g): "un" "hug" (score 0.005442) -## 回到训练 +## 回到训练 [[回到训练]] 现在我们已经了解了标记化的工作原理,我们可以更深入地研究训练期间使用的损失。在任何给定的阶段,这个损失是通过对语料库中的每个单词进行标记来计算的,使用当前词汇表和由语料库中每个标记的频率确定的 Unigram 模型(如前所述)。 @@ -149,7 +149,7 @@ Character 4 (g): "un" "hug" (score 0.005442) 因此, 标记 `"pu"`可能会从词汇表中删除,但不会删除 `"hug"`. -## 实现 Unigram +## 实现 Unigram [[实现 Unigram]] 现在让我们在代码中实现我们迄今为止看到的所有内容。与 BPE 和 WordPiece 一样,这不是 Unigram 算法的有效实现(恰恰相反),但它应该可以帮助你更好地理解它。 @@ -157,7 +157,7 @@ Character 4 (g): "un" "hug" (score 0.005442) ```python corpus = [ - "This is the Hugging Face course.", + "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.", diff --git a/chapters/zh-CN/chapter6/8.mdx b/chapters/zh-CN/chapter6/8.mdx index 43ce8d72f..7e6802bdc 100644 --- a/chapters/zh-CN/chapter6/8.mdx +++ b/chapters/zh-CN/chapter6/8.mdx @@ -1,4 +1,4 @@ -# 逐块地构建标记器 +# 逐块地构建标记器 [[逐块地构建标记器]] -# 章节简介 +# 章节简介 [[章节简介]] -在[第三章](/course/chapter3),您了解了如何微调文本分类的模型。在本章中,我们将处理以下常见NLP任务: +在[第三章](/course/chapter3),您了解了如何微调文本分类的模型。在本章中,我们将处理以下常见的 NLP 任务: -- 标记(token)分类 -- 遮罩语言建模(如BERT) -- 提取文本摘要 +- 词元(token)分类 +- 掩码语言建模(如 BERT) +- 文本摘要 - 翻译 -- 因果语言建模预训练(如GPT-2) +- 因果语言建模预训练(如 GPT-2) - 问答 {#if fw === 'pt'} -为此,您需要利用[第三章](/course/chapter3)中学到的`Trainer` API 和🤗Accelerate 库、[第五章](/course/chapter5)中的 🤗 Datasets 库以及[第六章](/course/chapter6)中的 🤗 Tokenizers 库的所有知识。我们还会将结果上传到模型中心,就像我们在[第四章](/course/chapter4)中所做的那样,所以这确实是将之前所有内容汇集在一起的章节! +为此,您需要利用[第三章](/course/chapter3)中学到的 `Trainer` API 和 🤗 Accelerate 库、[第五章](/course/chapter5)中的 🤗 Datasets 库以及[第六章](/course/chapter6)中的 🤗 Tokenizers 库的所有知识。我们同样会将结果上传到模型中心,就像我们在[第四章](/course/chapter4)中所做的那样,所以这确实是融会贯通的一章! -每个部分都可以独立阅读,并将向您展示如何使用API或按照您自己的训练循环训练模型,使用🤗 Accelerate 加速。你可以随意跳过其中一部分,把注意力集中在你最感兴趣的那一部分:API可以优化或训练您的模型而无需担心幕后发生了什么,而训练循环使用可以让您更轻松地自定义所需的任何结构。 +每个部分都可以独立阅读,并将向您展示如何使用 `Trainer` API 或按照您自己的训练循环训练模型,并采用 🤗 Accelerate 加速。你可以随意跳过任何一部分,专注于您最感兴趣的部分:`Trainer` API 非常适用于微调(fine-tuning)或训练您的模型,且无需担心幕后发生的事情;而采用 `Accelerate` 的训练循环可以让您更轻松地自定义所需的任何结构。 {:else} -为此,您需要利用[第三章](/course/chapter3)中学到的有关Keras API、[第五章](/course/chapter5)中的 🤗 Datasets 库以及[第六章](/course/chapter6)中的 🤗 Tokenizers 库的所有知识。我们还会将结果上传到模型中心,就像我们在[第四章](/course/chapter4)中所做的那样,所以这确实是将之前所有内容汇集在一起的章节! +为此,您需要利用[第三章](/course/chapter3)中学到的有关 Keras API、[第五章](/course/chapter5)中的 🤗 Datasets 库以及[第六章](/course/chapter6)中的 🤗 Tokenizers 库的所有知识。我们同样会将结果上传到模型中心,就像我们在[第四章](/course/chapter4)中所做的那样,所以这确实是融会贯通的一章! 每个部分都可以独立阅读。 diff --git a/chapters/zh-CN/chapter7/2.mdx b/chapters/zh-CN/chapter7/2.mdx index b328bbc0d..e9f663157 100644 --- a/chapters/zh-CN/chapter7/2.mdx +++ b/chapters/zh-CN/chapter7/2.mdx @@ -1,6 +1,6 @@ -# Token 分类 +# Token 分类 [[Token 分类]] {#if fw === 'pt'} @@ -33,7 +33,6 @@ 当然,还有很多其他类型的token分类问题;这些只是几个有代表性的例子。在本节中,我们将在 NER 任务上微调模型 (BERT),然后该模型将能够计算如下预测: - One-hot encoded labels for question answering. @@ -42,7 +41,7 @@ 您可以[在这里](https://huggingface.co/huggingface-course/bert-finetuned-ner?text=My+name+is+Sylvain+and+I+work+at+Hugging+Face+in+Brooklyn).找到我们将训练并上传到 Hub的模型,可以尝试输入一些句子看看模型的预测结果。 -## 准备数据 +## 准备数据 [[准备数据]] 首先,我们需要一个适合标记分类的数据集。在本节中,我们将使用[CoNLL-2003 数据集](https://huggingface.co/datasets/conll2003), 其中包含来自路透社的新闻报道。 @@ -52,7 +51,7 @@ -### CoNLL-2003 数据集 +### CoNLL-2003 数据集 [[CoNLL-2003 数据集]] 要加载 CoNLL-2003 数据集,我们使用 来自 🤗 Datasets 库的**load_dataset()** 方法: @@ -174,7 +173,7 @@ print(line2) -### 处理数据 +### 处理数据 [[处理数据]] @@ -302,20 +301,20 @@ tokenized_datasets = raw_datasets.map( {#if fw === 'pt'} -## 使用 Trainer API 微调模型 +## 使用 Trainer API 微调模型 [[使用 Trainer API 微调模型]] 使用 `Trainer` 的实际代码会和以前一样;唯一的变化是数据整理成时批处理的方式和度量计算函数。 {:else} -## 使用 Keras 微调模型 +## 使用 Keras 微调模型 [[使用 Keras 微调模型]] 使用Keras的实际代码将与之前非常相似;唯一的变化是将数据整理成批处理的方式和指标计算函数。 {/if} -### 数据排序 +### 数据排序 [[数据排序]] 我们不能像[第三章](/course/chapter3)那样只使用一个 `DataCollatorWithPadding `因为这只会填充输入(输入 ID、注意掩码和标记类型 ID)。在这里我们的标签应该以与输入完全相同的方式填充,以便它们保持长度相同,使用 `-100 ` ,这样在损失计算中就可以忽略相应的预测。 @@ -371,8 +370,7 @@ for i in range(2): {:else} -我们的数据整理器已准备就绪!现在,让我们用它来制作一个带有`to_tf_dataset()`方法的`tf.data.Dataset`。 - +我们的数据整理器已准备就绪! 现在让我们使用它通过 `to_tf_dataset()` 方法制作一个 `tf.data.Dataset`。 您还可以使用 model.prepare_tf_dataset() 来使用更少的样板代码来执行此操作——您将在本章的其他部分中看到这一点。 ```py tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( columns=["attention_mask", "input_ids", "labels", "token_type_ids"], @@ -403,7 +401,7 @@ tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( 它们应该由两个字典设置, `id2label` 和 `label2id` ,其中包含从 ID 到标签的映射,反之亦然: ```py -id2label = {i: label for i, label in enumerate(label_names)} +id2label = {str(i): label for i, label in enumerate(label_names)} label2id = {v: k for k, v in id2label.items()} ``` @@ -460,7 +458,6 @@ from transformers import create_optimizer import tensorflow as tf # Train in mixed-precision float16 -# Comment this line out if you're using a GPU that will not benefit from this tf.keras.mixed_precision.set_global_policy("mixed_float16") # The number of training steps is the number of samples in the dataset, divided by the batch size then multiplied @@ -510,7 +507,7 @@ model.fit( {/if} -### 评估指标 +### 评估指标 [[评估指标]] {#if fw === 'pt'} @@ -522,7 +519,7 @@ model.fit( !pip install seqeval ``` -然后我们可以通过加载它 `load_metric()` 函数就像我们在[第三章](/course/chapter3)做的那样: +然后我们可以通过加载它 `evaluate.load()` 函数就像我们在[第三章](/course/chapter3)做的那样: {:else} @@ -532,14 +529,14 @@ model.fit( !pip install seqeval ``` -然后我们可以通过加载它 `load_metric()` 函数就像我们在[第三章](/course/chapter3)做的那样: +然后我们可以通过`evaluate.load()` 函数加载它就像我们在[第三章](/course/chapter3)做的那样: {/if} ```py -from datasets import load_metric +import evaluate -metric = load_metric("seqeval") +metric = evaluate.load("seqeval") ``` 这个评估方式与标准精度不同:它实际上将标签列表作为字符串,而不是整数,因此在将预测和标签传递给它之前,我们需要完全解码它们。让我们看看它是如何工作的。首先,我们将获得第一个训练示例的标签: @@ -616,7 +613,7 @@ import numpy as np all_predictions = [] all_labels = [] for batch in tf_eval_dataset: - logits = model.predict(batch)["logits"] + logits = model.predict_on_batch(batch)["logits"] labels = batch["labels"] predictions = np.argmax(logits, axis=-1) for prediction, label in zip(predictions, labels): @@ -653,7 +650,7 @@ metric.compute(predictions=[all_predictions], references=[all_labels]) 它们应该由两个字典设置, `id2label` 和 `label2id` ,其中包含从 ID 到标签的映射,反之亦然: ```py -id2label = {i: label for i, label in enumerate(label_names)} +id2label = {str(i): label for i, label in enumerate(label_names)} label2id = {v: k for k, v in id2label.items()} ``` @@ -759,11 +756,11 @@ This command returns the URL of the commit it just did, if you want to inspect i 如果您想更深入地了解训练循环,我们现在将向您展示如何使用 🤗 Accelerate 做同样的事情。 -## 自定义训练循环 +## 自定义训练循环 [[自定义训练循环]] 现在让我们看一下完整的训练循环,这样您可以轻松定义所需的部分。它看起来很像我们在[第三章](/course/chapter3/4), 所做的,对评估进行了一些更改。 -### 做好训练前的准备 +### 做好训练前的准备 [[做好训练前的准备]] 首先我们需要为我们的数据集构建 `DataLoader` 。我们将重用我们的 `data_collator` 作为一个 `collate_fn` 并打乱训练集,但不打乱验证集: ```py @@ -845,18 +842,16 @@ repo_name 'sgugger/bert-finetuned-ner-accelerate' ``` -Then we can clone that repository in a local folder. If it already exists, this local folder should be an existing clone of the repository we are working with: +然后我们可以将该存储库克隆到本地文件夹中。 如果它已经存在,这个本地文件夹应该是我们正在使用的存储库的现有克隆: ```py output_dir = "bert-finetuned-ner-accelerate" repo = Repository(output_dir, clone_from=repo_name) ``` -We can now upload anything we save in `output_dir` by calling the `repo.push_to_hub()` method. This will help us upload the intermediate models at the end of each epoch. - -### Training loop +我们现在可以通过调用 `repo.push_to_hub()` 方法上传保存在 `output_dir` 中的任何内容。 这将帮助我们在每个训练周期结束时上传中间模型。 -### 训练循环 +### 训练循环 [[训练循环]] 我们现在准备编写完整的训练循环。为了简化它的评估部分,我们定义了这个 `postprocess()` 接受预测和标签并将它们转换为字符串列表的函数,也就是 `metric`对象需要的输入格式: ```py @@ -954,7 +949,7 @@ unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) {/if} -## 使用微调模型 +## 使用微调模型 [[使用微调模型]] 我们已经向您展示了如何使用我们在模型中心微调的模型和推理小部件。在本地使用它 `pipeline` ,您只需要指定正确的模型标识符: diff --git a/chapters/zh-CN/chapter7/3.mdx b/chapters/zh-CN/chapter7/3.mdx index b5c410d23..4b4c8232e 100644 --- a/chapters/zh-CN/chapter7/3.mdx +++ b/chapters/zh-CN/chapter7/3.mdx @@ -1,6 +1,6 @@ -# 微调掩码语言模型 +# 微调掩码语言模型 [[微调掩码语言模型]] {#if fw === 'pt'} @@ -36,7 +36,6 @@ 在本节结束时, 你将在Hub上拥有一个[掩码语言模型(masked language model)](https://huggingface.co/huggingface-course/distilbert-base-uncased-finetuned-imdb?text=This+is+a+great+%5BMASK%5D.), 该模型可以自动完成句子, 如下所示: - 让我们开始吧! @@ -48,7 +47,7 @@ -## 选择用于掩码语言建模的预训练模型 +## 选择用于掩码语言建模的预训练模型 [[选择用于掩码语言建模的预训练模型]] 首先, 让我们为掩码语言建模选择一个合适的预训练模型。如以下屏幕截图所示, 你可以通过在[Hugging Face Hub](https://huggingface.co/models?pipeline_tag=fill-mask&sort=downloads)上应用"Fill-Mask"过滤器找到: @@ -187,7 +186,7 @@ for token in top_5_tokens: 我们可以从输出中看到模型的预测是指日常用语, 鉴于英语维基百科的基础, 这也许并不奇怪。让我们看看我们如何将这个领域改变为更小众的东西 -- 高度两极分化的电影评论! -## 数据集 +## 数据集 [[数据集]] 为了展示域适配, 我们将使用著名的[大型电影评论数据集(Large Movie Review Dataset)](https://huggingface.co/datasets/imdb) (或者简称为IMDb), 这是一个电影评论语料库, 通常用于对情感分析模型进行基准测试。通过在这个语料库上对 DistilBERT 进行微调, 我们预计语言模型将根据维基百科的事实数据调整其词汇表, 这些数据已经预先训练到电影评论中更主观的元素。我们可以使用🤗 Datasets中的`load_dataset()`函数从Hugging Face 中获取数据: @@ -247,7 +246,7 @@ for row in sample: 现在我们已经快速浏览了数据, 让我们深入研究为掩码语言建模做准备。正如我们将看到的, 与我们在[第三章](/course/chapter3)中看到的序列分类任务相比, 还需要采取一些额外的步骤。让我们继续! -## 预处理数据 +## 预处理数据 [[预处理数据]] @@ -445,7 +444,7 @@ tokenizer.decode(lm_datasets["train"][1]["labels"]) 正如前面的 `group_texts()` 函数所期望的那样, 这看起来与解码后的 `input_ids` 相同 -- 但是我们的模型怎么可能学到任何东西呢? 我们错过了一个关键步骤: 在输入中的随机位置插入 `[MASK]` 标记! 让我们看看如何使用特殊的数据整理器在微调期间即时执行此操作。 -## 使用 `Trainer` API 微调DistilBERT +## 使用 `Trainer` API 微调DistilBERT [[使用 `Trainer` API 微调DistilBERT]] 微调屏蔽语言模型几乎与微调序列分类模型相同, 就像我们在 [第三章](/course/chapter3)所作的那样。 唯一的区别是我们需要一个特殊的数据整理器, 它可以随机屏蔽每批文本中的一些标记。幸运的是, 🤗 Transformers 为这项任务准备了专用的 `DataCollatorForLanguageModeling` 。我们只需要将它转递给标记器和一个 `mlm_probability` 参数, 该参数指定要屏蔽的标记的分数。我们将选择 15%, 这是 BERT 使用的数量也是文献中的常见选择: @@ -535,7 +534,7 @@ def whole_word_masking_data_collator(features): import collections import numpy as np -from transformers.data import tf_default_data_collator +from transformers.data.data_collator import tf_default_data_collator wwm_probability = 0.2 @@ -637,18 +636,18 @@ huggingface-cli login {#if fw === 'tf'} -登陆后, 我们就可以创建我们的 `tf.data` 数据集。我们将在这里只使用标准数据整理器, 但你也可以尝试使用整个单词掩码整理器并将结果作为练习进行比较: +登录后,我们可以创建我们的“tf.data”数据集。 为此,我们将使用 `prepare_tf_dataset()` 方法,该方法使用我们的模型自动推断哪些列应进入数据集。 如果您想准确控制要使用的列,可以改用“Dataset.to_tf_dataset()”方法。 为了简单起见,我们在这里只使用标准数据整理器,但您也可以尝试整个单词屏蔽整理器并将结果作为练习进行比较: ```python -tf_train_dataset = downsampled_dataset["train"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], +tf_train_dataset = model.prepare_tf_dataset( + downsampled_dataset["train"], collate_fn=data_collator, shuffle=True, batch_size=32, ) -tf_eval_dataset = downsampled_dataset["test"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], +tf_eval_dataset = model.prepare_tf_dataset( + downsampled_dataset["test"], collate_fn=data_collator, shuffle=False, batch_size=32, @@ -676,6 +675,7 @@ model.compile(optimizer=optimizer) # Train in mixed-precision 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 ) @@ -732,7 +732,7 @@ trainer = Trainer( {/if} -### 语言模型的perplexity +### 语言模型的perplexity [[语言模型的perplexity]] @@ -826,7 +826,7 @@ trainer.push_to_hub() 在我们的用例中, 我们不需要对训练循环做任何特别的事情, 但在某些情况下, 你可能需要实现一些自定义逻辑。对于这些应用, 你可以使用 🤗 Accelerate -- 让我们来看看吧! -## 使用 🤗 Accelerate 微调 DistilBERT +## 使用 🤗 Accelerate 微调 DistilBERT [[使用 🤗 Accelerate 微调 DistilBERT]] 正如我们在 `Trainer` 中看到的, 对掩码语言模型的微调与 [第三章](/course/chapter3) 中的文本分类示例非常相似。事实上, 唯一的微妙之处是使用特殊的数据整理器, 我们已经在本节的前面介绍过了! @@ -1003,7 +1003,7 @@ for epoch in range(num_train_epochs): {/if} -## 使用我们微调的模型 +## 使用我们微调的模型 [[使用我们微调的模型]] 你可以通过在Hub上使用其他小部件或在本地使用🤗 Transformers 的`管道`于微调模型进行交互。让我们使用后者来下载我们的模型, 使用 `fill-mask` 管道: diff --git a/chapters/zh-CN/chapter7/4.mdx b/chapters/zh-CN/chapter7/4.mdx index 32ea23dfa..a88dc2777 100644 --- a/chapters/zh-CN/chapter7/4.mdx +++ b/chapters/zh-CN/chapter7/4.mdx @@ -1,6 +1,6 @@ -# 翻译 +# 翻译 [[翻译]] {#if fw === 'pt'} @@ -36,7 +36,6 @@ 完成后,我们将拥有一个模型,可以进行这样的翻译: - One-hot encoded labels for question answering. @@ -45,16 +44,16 @@ 与前面的部分一样,您可以使用以下代码找到我们将训练并上传到 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.)查看模型输出的结果 -## 准备数据 +## 准备数据 [[准备数据]] 为了从头开始微调或训练翻译模型,我们需要一个适合该任务的数据集。如前所述,我们将使用[KDE4 数据集](https://huggingface.co/datasets/kde4)在本节中,但您可以很容易地调整代码以使用您自己的数据,只要您有要互译的两种语言的句子对。如果您需要复习如何将自定义数据加载到 **Dataset** 可以复习一下[第五章](/course/chapter5). -### KDE4 数据集 +### KDE4 数据集 [[KDE4 数据集]] 像往常一样,我们使用 **load_dataset()** 函数: ```py -from datasets import load_dataset, load_metric +from datasets import load_dataset raw_datasets = load_dataset("kde4", lang1="en", lang2="fr") ``` @@ -162,7 +161,7 @@ translator( -### 处理数据 +### 处理数据 [[处理数据]] @@ -172,7 +171,7 @@ translator( from transformers import AutoTokenizer model_checkpoint = "Helsinki-NLP/opus-mt-en-fr" -tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, return_tensors="tf") +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, return_tensors="pt") ``` 您可以将 **model_checkpoint** 更换为[Hub](https://huggingface.co/models)上你喜欢的任何其他型号,或本地保存的预训练模型和标记器。 @@ -183,36 +182,28 @@ tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, return_tensors="tf") -我们的数据准备非常简单。 只需要记住一件事:您照常处理输入,但对于这次的输出目标,您需要将标记器包装在上下文管理器“as_target_tokenizer()”中。 +我们的数据准备非常简单。 只有一件事要记住; 您需要确保分词器以输出语言(此处为法语)处理目标。 您可以通过将目标传递给分词器的 __call__ 方法的 text_targets 参数来完成此操作。 -Python 中的上下文管理器引入了 **with** 语句,当您有两个相关的操作要成对执行时很有用。最常见的例子是当您写入或读取文件时,下面是一个例子: - -``` -with open(file_path) as f: - content = f.read() -``` - -这里成对执行的两个相关操作是打开和关闭文件的操作。打开的文件f对应的对象只在with下的缩进块内有效;在该块之前打开,在该块的末尾关闭。 - -在本例中,上下文管理器 as_target_tokenizer() 将在执行缩进块之前将标记器设置为输出语言(此处为法语),然后将其设置回输入语言(此处为英语)。 - -因此,预处理一个样本如下所示: +为了了解这是如何工作的,让我们处理训练集中每种语言的一个样本: ```python en_sentence = split_datasets["train"][1]["translation"]["en"] fr_sentence = split_datasets["train"][1]["translation"]["fr"] -inputs = tokenizer(en_sentence) -with tokenizer.as_target_tokenizer(): - targets = tokenizer(fr_sentence) +inputs = tokenizer(en_sentence, text_target=fr_sentence) +inputs ``` -如果我们忘记在上下文管理器中标记目标,它们将被输入标记器标记,在Marian模型的情况下,会导致输出的异常: +```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]} +``` + +正如我们所见,输出包含与英语句子关联的输入 ID,而与法语句子关联的 ID 存储在“labels”字段中。 如果您忘记表明您正在对标签进行分词,它们将由输入分词器进行分词,这在 Marian 模型的情况下根本不会顺利进行: ```python wrong_targets = tokenizer(fr_sentence) print(tokenizer.convert_ids_to_tokens(wrong_targets["input_ids"])) -print(tokenizer.convert_ids_to_tokens(targets["input_ids"])) +print(tokenizer.convert_ids_to_tokens(inputs["labels"])) ``` ```python out @@ -222,24 +213,18 @@ print(tokenizer.convert_ids_to_tokens(targets["input_ids"])) 正如我们所看到的,使用英语标记器来预处理法语句子会产生更多的标记,因为标记器不知道任何法语单词(除了那些也出现在英语语言中的单词,比如“discussion”)。 -`inputs` 和 `targets` 都是带有我们常用键(输入 ID、注意掩码等)的字典,所以最后一步是在输入中设置一个 `"labels"` 键。 我们在数据集的预处理函数中执行此操作: +由于“inputs”是一个包含我们常用键(输入 ID、注意掩码等)的字典,最后一步是定义我们将应用于数据集的预处理函数: ```python -max_input_length = 128 -max_target_length = 128 +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, max_length=max_input_length, truncation=True) - - # Set up the tokenizer for targets - with tokenizer.as_target_tokenizer(): - labels = tokenizer(targets, max_length=max_target_length, truncation=True) - - model_inputs["labels"] = labels["input_ids"] - return model_inputs + model_inputs = tokenizer( + inputs, text_target=targets, max_length=max_length, truncation=True + ) ``` 请注意,我们为输入和输出设置了相同的最大长度。由于我们处理的文本看起来很短,我们使用 128。 @@ -270,7 +255,7 @@ tokenized_datasets = split_datasets.map( {#if fw === 'pt'} -## 使用 Trainer API 微调模型 +## 使用 Trainer API 微调模型 [[使用 Trainer API 微调模型]] 使用 `Trainer` 的实际代码将与以前相同,只是稍作改动:我们在这里使用 [`Seq2SeqTrainer`](https://huggingface.co/transformers/main_classes/trainer.html#seq2seqtrainer), 它是 `Trainer` 的子类,它可以正确处理这种序列到序列的评估,并使用 `generate()` 方法来预测输入的输出。 当我们讨论评估指标时,我们将更详细地探讨这一点。 @@ -284,7 +269,7 @@ model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) {:else} -## 使用 Keras 微调模型 +## 使用 Keras 微调模型 [[使用 Keras 微调模型]] 首先,我们需要一个实际的模型来进行微调。 我们将使用通常的 `AutoModel` API: @@ -304,7 +289,7 @@ model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint, from_pt=True) 请注意,这次我们使用的是在翻译任务上训练过的模型,并且实际上已经可以使用,因此没有关于丢失权重或新初始化权重的警告。 -### 数据整理 +### 数据整理 [[数据整理]] 我们需要一个数据整理器来处理动态批处理的填充。在本例中,我们不能像[第3章](/course/chapter3)那样使用带填充的**DataCollatorWithPadding**,因为它只填充输入(输入ID、注意掩码和令牌类型ID)。我们的标签也应该填充到标签中遇到的最大长度。而且,如前所述,用于填充标签的填充值应为-100,而不是标记器的填充标记,以确保在损失计算中忽略这些填充值。 @@ -386,14 +371,14 @@ for i in range(1, 3): 我们现在可以使用 `data_collator` 将我们的每个数据集转换为 `tf.data.Dataset`,准备好进行训练: ```python -tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], +tf_train_dataset = model.prepare_tf_dataset( + tokenized_datasets["train"], collate_fn=data_collator, shuffle=True, batch_size=32, ) -tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], +tf_eval_dataset = model.prepare_tf_dataset( + tokenized_datasets["validation"], collate_fn=data_collator, shuffle=False, batch_size=16, @@ -403,7 +388,7 @@ tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( {/if} -### 评估指标 +### 评估指标 [[评估指标]] @@ -424,12 +409,12 @@ BLEU 的一个缺点是它需要文本已经被分词,这使得比较使用不 !pip install sacrebleu ``` -然后我们可以就像我们在[第三章](/course/chapter3)那样通过 **load_metric()** 加载它 : +然后我们可以就像我们在[第三章](/course/chapter3)那样通过 **evaluate.load()** 加载它 : ```py -from datasets import load_metric +import evaluate -metric = load_metric("sacrebleu") +metric = evaluate.load("sacrebleu") ``` 该指标将文本作为输入和目标结果。它旨在接受多个可接受的目标,因为同一个句子通常有多个可接受的翻译——我们使用的数据集只提供一个,但在 NLP 中找到将多个句子作为标签的数据集不是一个难题。因此,预测结果应该是一个句子列表,而参考应该是一个句子列表的列表。 @@ -504,10 +489,32 @@ metric.compute(predictions=predictions, references=references) {#if fw === 'tf'} -为了从模型输出可以被评估基准可以使用的文本,我们将使用 **tokenizer.batch_decode()** 方法。我们只需要清理标签中所有 **-100** (标记器会自动为填充标记做同样的事情): +为了将模型输出的向量转换为可以使用的文本,我们将使用 `tokenizer.batch_decode()` 方法。 我们只需要清除标签中的所有“-100”; tokenizer 将自动对填充令牌执行相同的操作。 让我们定义一个函数,它接受我们的模型和数据集并计算其指标。 我们还将使用一个可以显着提高性能的技巧 - 使用 TensorFlow 的加速线性代数编译器 [XLA](https://www.tensorflow.org/xla) 编译我们的生成代码。 XLA 对模型的计算图应用了各种优化,并显着提高了速度和内存使用率。 正如 Hugging Face [博客](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(): @@ -520,12 +527,10 @@ def compute_metrics(): shuffle=False, batch_size=4, ) - for batch in tf_generate_dataset: - predictions = model.generate( - input_ids=batch["input_ids"], attention_mask=batch["attention_mask"] - ) + for batch, labels in tqdm(tf_generate_dataset): + predictions = generate_with_xla(batch) decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) - labels = batch["labels"].numpy() + 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] @@ -569,7 +574,7 @@ def compute_metrics(eval_preds): 现在这已经完成了,我们已经准备好微调我们的模型了! -### 微调模型 +### 微调模型 [[微调模型]] 第一步是登录 Hugging Face,这样您就可以将结果上传到模型中心。有一个方便的功能可以帮助您在notebook中完成此操作: @@ -719,7 +724,7 @@ trainer = Seq2SeqTrainer( 在训练之前,我们将首先查看我们的模型获得的分数,以仔细检查我们的微调没有让事情变得更糟。此命令需要一些时间,因此您可以在执行时喝杯咖啡: ```python -trainer.evaluate(max_length=max_target_length) +trainer.evaluate(max_length=max_length) ``` ```python out @@ -743,7 +748,7 @@ trainer.train() 训练完成后,我们再次评估我们的模型——希望我们会看到 BLEU 分数有所改善! ```py -trainer.evaluate(max_length=max_target_length) +trainer.evaluate(max_length=max_length) ``` ```python out @@ -777,11 +782,11 @@ trainer.push_to_hub(tags="translation", commit_message="Training complete") {#if fw === 'pt'} -## 自定义训练循环 +## 自定义训练循环 [[自定义训练循环]] 现在让我们看一下完整的训练循环,以便您可以轻松自定义所需的部分。它看起来很像我们在[本章第二节](/course/chapter7/2)和[第三章第四小节](/course/chapter3/4)所做的。 -### 准备训练所需的一切 +### 准备训练所需的一切 [[准备训练所需的一切]] 您已经多次看到所有这些,因此这一块会简略进行。首先我们将构建我们的数据集的**DataLoader** ,在将数据集设置为 **torch** 格式,我们就得到了 PyTorch 张量: @@ -865,7 +870,7 @@ repo = Repository(output_dir, clone_from=repo_name) 我们现在可以通过调用 **repo.push_to_hub()** 方法上传我们保存的任何内容 **output_dir** 。这将帮助我们在每个 epoch 结束时上传过程中的模型。 -### 训练循环 +### 训练循环 [[训练循环]] 我们现在准备编写完整的训练循环。为了简化它的评估部分,我们定义了这个 **postprocess()** 函数接收预测结果和正确标签并将它们转换为我们 **metric** 对象所需要的字符串列表: @@ -958,7 +963,7 @@ epoch 2, BLEU score: 54.44 {/if} -## 使用微调后的模型 +## 使用微调后的模型 [[使用微调后的模型]] 我们已经向您展示了如何将我们在模型中心微调的模型与推理小部件一起使用。 要在“管道”中本地使用它,我们只需要指定正确的模型标识符: diff --git a/chapters/zh-CN/chapter7/5.mdx b/chapters/zh-CN/chapter7/5.mdx index e09659687..0fbba71a7 100644 --- a/chapters/zh-CN/chapter7/5.mdx +++ b/chapters/zh-CN/chapter7/5.mdx @@ -1,6 +1,6 @@ -# 提取文本摘要 +# 提取文本摘要 [[提取文本摘要]] {#if fw === 'pt'} @@ -30,11 +30,10 @@ 尽管在[Hugging Face Hub](https://huggingface.co/models?pipeline_tag=summarization=downloads)上已经存在各种微调模型用于文本摘要,几乎所有这些都只适用于英文文档。因此,为了在本节中添加一些变化,我们将为英语和西班牙语训练一个双语模型。在本节结束时,您将有一个可以总结客户评论的[模型](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es)。 - 如下所示:正如我们将看到的,这些摘要很简洁,因为它们是从客户在产品评论中提供的标题中学到的。让我们首先为这项任务准备一个合适的双语语料库。 -## 准备多语言语料库 +## 准备多语言语料库 [[准备多语言语料库]] 我们将使用[多语言亚马逊评论语料库](https://huggingface.co/datasets/amazon_reviews_multi)创建我们的双语摘要器。该语料库由六种语言的亚马逊产品评论组成,通常用于对多语言分类器进行基准测试。然而,由于每条评论都附有一个简短的标题,我们可以使用标题作为我们模型学习的目标摘要!首先,让我们从 Hugging Face Hub 下载英语和西班牙语子集: @@ -204,7 +203,7 @@ books_dataset = books_dataset.filter(lambda x: len(x["review_title"].split()) > 现在我们已经准备好了我们的语料库,让我们来看看一些可以对其进行微调的可能的 Transformer 模型! -## 文本摘要模型 +## 文本摘要模型 [[文本摘要模型]] 如果你仔细想想,文本摘要是一种类似于机器翻译的任务:我们有一个像评论这样的文本正文,我们希望将其“翻译”成一个较短的版本,以捕捉输入的显着特征。因此,大多数用于文本摘要的 Transformer 模型采用了我们在[第一章](/course/chapter1)遇到的编码器-解码器架构。尽管有一些例外,例如 GPT 系列模型,它们在few-shot(少量微调)之后也可以提取摘要。下表列出了一些流行的预训练模型,可以对其进行微调以进行汇总。 @@ -235,7 +234,7 @@ mT5 不使用前缀,但具有 T5 的大部分功能,并且具有多语言的 -## 预处理数据 +## 预处理数据 [[预处理数据]] @@ -277,7 +276,7 @@ tokenizer.convert_ids_to_tokens(inputs.input_ids) 特殊的 Unicode 字符 `▁` 和序列结束标记 `` 表明我们正在处理 SentencePiece 分词器,它基于在[第六章](/course/chapter6)中讨论的Unigram分词算法. Unigram 对多语言语料库特别有用,因为它允许 SentencePiece 不知道重音、标点符号以及许多语言(如日语)没有空格字符。 -为了标记我们的语料库,我们必须处理与摘要相关的细节:因为我们的标签也是文本,它们可能会超过模型的最大上下文大小。这意味着我们需要对评论及其标题进行截断,以确保我们不会将过长的输入传递给我们的模型。 🤗 Transformers 中的分词器提供了一个漂亮的 **as_target_tokenizer()** 函数,它允许您并行分词并标记标签的函数。这通常是使用预处理函数内的上下文管理器完成的,该函数首先对输入进行编码,然后将标签编码为单独的列。以下是 mT5 的此函数的示例: +为了标记我们的语料库,我们必须处理与摘要相关的微妙之处:因为我们的标签也是文本,所以它们可能超过模型的最大上下文大小。 这意味着我们需要对评论及其标题进行截断,以确保我们不会将过长的输入传递给我们的模型。 🤗 Transformers 中的分词器提供了一个绝妙的 `text_target` 参数,允许您将标签与输入并行分词。 以下是如何为 mT5 处理输入和目标的示例: ```python max_input_length = 512 @@ -286,19 +285,18 @@ max_target_length = 30 def preprocess_function(examples): model_inputs = tokenizer( - examples["review_body"], max_length=max_input_length, truncation=True + examples["review_body"], + max_length=max_input_length, + truncation=True, + ) + labels = tokenizer( + examples["review_title"], max_length=max_target_length, truncation=True ) - # Set up the tokenizer for targets - with tokenizer.as_target_tokenizer(): - 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()** 我们可以看到评论首先被标记化,然后是标题在 **as_target_tokenizer()** 函数里也做了相同的处理. +让我们通过这段代码来了解发生了什么。我们做的第一件事是定义值 **max_input_length** 和 **max_target_length** ,它为我们的评论和标题的长度设置了上限。由于评论正文通常比标题大得多,我们相应地调整了这些值。 有了 `preprocess_function()`,我们在整个课程中广泛使用的方便的 `Dataset.map()` 函数来标记整个语料库是一件简单的事情: @@ -315,7 +313,7 @@ tokenized_datasets = books_dataset.map(preprocess_function, batched=True) -## 文本摘要的指标 +## 文本摘要的指标 [[文本摘要的指标]] @@ -352,9 +350,9 @@ $$ \mathrm{Precision} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{ 然后按如下方式加载 ROUGE 指标: ```python -from datasets import load_metric +import evaluate -rouge_score = load_metric("rouge") +rouge_score = evaluate.load("rouge") ``` 然后我们可以使用 **rouge_score.compute()** 一次性计算所有指标的函数: @@ -392,7 +390,7 @@ Score(precision=0.86, recall=1.0, fmeasure=0.92) 我们将使用这些 ROUGE 分数来跟踪我们模型的性能,但在此之前,让我们做每个优秀的 NLP 从业者都应该做的事情:创建一个强大而简单的baseline! -### 创建强大的baseline +### 创建强大的baseline [[创建强大的baseline]] 文本摘要的一个常见基线是简单地取一篇文章的前三个句子,通常称为 _lead-3_ 基线。 我们可以使用句号(英文使用.)来跟踪句子边界,但这在"U.S." or "U.N."之类的首字母缩略词上会失败。所以我们将使用 `nltk` 库,它包含一个更好的算法来处理这些情况。 您可以使用 `pip` 安装软件包,如下所示: @@ -453,7 +451,7 @@ rouge_dict {#if fw === 'pt'} -## 使用 `Trainer` API微调mT5 +## 使用 `Trainer` API微调mT5 [[使用 `Trainer` API微调mT5]] 微调模型以进行提取摘要与我们在本章中介绍的其他任务非常相似。 我们需要做的第一件事是从`mt5-small`检查点加载预训练模型。 由于摘要提取是一个序列到序列的任务,我们可以使用 AutoModelForSeq2SeqLM 类加载模型,该类会自动下载并缓存权重: @@ -465,9 +463,9 @@ model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) {:else} -## 使用 `Keras` API微调mT5 +## 使用 `Keras` API微调mT5 [[使用 `Keras` API微调mT5]] -微调模型以进行提取摘要与我们在本章中介绍的其他任务非常相似。 我们需要做的第一件事是从`mt5-small`检查点加载预训练模型。 由于摘要提取是一个序列到序列的任务,我们可以使用 AutoModelForSeq2SeqLM 类加载模型,该类会自动下载并缓存权重: +微调模型以进行提取摘要与我们在本章中介绍的其他任务非常相似。 我们需要做的第一件事是从`mt5-small`检查点加载预训练模型。 由于摘要提取是一个序列到序列的任务,我们可以使用 `TFAutoModelForSeq2SeqLM` 类加载模型,该类会自动下载并缓存权重: ```python from transformers import TFAutoModelForSeq2SeqLM @@ -673,14 +671,14 @@ trainer.push_to_hub(commit_message="Training complete", tags="summarization") 我们几乎准备好训练了! 我们只需要使用我们上面定义的数据整理器将我们的数据集转换为 tf.data.Dataset ,然后 `compile()` 和 `fit()` 模型。 首先,转换数据集: ```python -tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], +tf_train_dataset = model.prepare_tf_dataset( + tokenized_datasets["train"], collate_fn=data_collator, shuffle=True, batch_size=8, ) -tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], +tf_eval_dataset = model.prepare_tf_dataset( + tokenized_datasets["validation"], collate_fn=data_collator, shuffle=False, batch_size=8, @@ -726,18 +724,40 @@ model.fit( ) ``` -我们在训练期间输出了一些loss,但实际上我们希望看到我们之前计算的 ROUGE 指标。 要获得这些指标,我们需要从模型生成输出并将它们转换为字符串。 让我们为 ROUGE 指标构建一些标签和预测列表以进行比较(请注意,如果您在本节中遇到import的错误,您可能需要`!pip install tqdm`): +我们在训练期间得到了一些loss,但实际上我们希望看到我们之前计算的 ROUGE 指标。 要获得这些指标,我们需要从模型生成输出并将它们转换为字符串。 让我们为 ROUGE 指标构建一些标签和预测列表以进行比较(请注意,如果您遇到此部分的导入错误,您可能需要`!pip install tqdm`)。 我们还将使用一个可以显着提高性能的技巧 - 使用 TensorFlow 的加速线性代数编译器 [XLA](https://www.tensorflow.org/xla) 编译我们的生成代码。 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 in tqdm(tf_eval_dataset): - predictions = model.generate(**batch) +for batch, labels in tqdm(tf_generate_dataset): + predictions = generate_with_xla(batch) decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) - labels = batch["labels"].numpy() + 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] @@ -765,11 +785,11 @@ result = {key: value.mid.fmeasure * 100 for key, value in result.items()} {#if fw === 'pt'} -## 使用 🤗 Accelerate 微调 mT5 +## 使用 🤗 Accelerate 微调 mT5 [[使用 🤗 Accelerate 微调 mT5]] 使用 🤗 Accelerate 微调我们的模型与我们在 [Chapter 3](/course/chapter3) 中遇到的文本分类示例非常相似。 主要区别在于需要在训练期间显式生成摘要并定义我们如何计算 ROUGE 分数(回想一下,`Seq2SeqTrainer` 为我们生成了摘要)。 让我们看看我们如何在 🤗 Accelerate 中实现这两个要求! -### 为训练做好一切准备 +### 为训练做好一切准备 [[为训练做好一切准备]] The first thing we need to do is create a `DataLoader` for each of our splits. Since the PyTorch dataloaders expect batches of tensors, we need to set the format to `"torch"` in our datasets: 我们需要做的第一件事是为每个数据集的每一个拆分创建一个`DataLoader`。 由于 PyTorch 数据加载器需要成批的张量,我们需要在数据集中将格式设置为`torch`: @@ -888,7 +908,7 @@ repo = Repository(output_dir, clone_from=repo_name) ``` 这将允许我们在训练期间通过调用 `repo.push_to_hub()` 方法将模型推送到 Hub! 现在让我们通过写出完整的训练循环来结束我们的分析。 -### 训练循环 +### 训练循环 [[训练循环]] 文本摘要的训练循环与我们遇到的其他 🤗 Accelerate 示例非常相似,大致分为四个主要步骤:这 @@ -991,7 +1011,7 @@ Epoch 9: {'rouge1': 14.1192, 'rouge2': 7.0059, 'rougeL': 14.1172, 'rougeLsum': 1 {/if} -## 使用您微调的模型 +## 使用您微调的模型 [[使用您微调的模型]] 将模型推送到 Hub 后,您可以通过推理小部件或“管道”对象来使用它,如下所示: diff --git a/chapters/zh-CN/chapter7/6.mdx b/chapters/zh-CN/chapter7/6.mdx index 494baba89..6cf1a67b6 100644 --- a/chapters/zh-CN/chapter7/6.mdx +++ b/chapters/zh-CN/chapter7/6.mdx @@ -1,6 +1,6 @@ -# 从头开始训练因果语言模型 +# 从头开始训练因果语言模型 [[从头开始训练因果语言模型]] {#if fw === 'pt'} @@ -31,12 +31,11 @@ 在[第六章](/course/chapter6) 我们创建了一个高效的分词器来处理 Python 源代码,但我们仍然需要一个大规模数据集来预训练模型。在这里,我们将我们的分词器应用到源自 GitHub 存储库的 Python 代码语料库。然后我们将使用 `Trainer` API 和 🤗 Accelerate 来训练模型。让我们开始吧! - 这实际上展示了使用本节中训练并上传到 Hub 的模型。你可以在[这里](https://huggingface.co/huggingface-course/codeparrot-ds?text=plt.imshow%28)找到。请注意,由于在文本生成过程中发生了一些随机化,您可能会得到略有不同的结果。 -## 收集数据 +## 收集数据 [[收集数据]] -Python 代码可以从 GitHub 等代码存储库中获得,我们可以通过抓取每个 Python 存储库来使用它们来创建数据集。这是在[Transformers textbook](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/)预训练大型的GPT-2 模型。使用大约 180 GB 的 GitHub 转储,其中包含大约 2000 万个 Python 文件,称为 `codeparrot` ,作者构建了一个数据集,然后在[Hugging Face Hub](https://huggingface.co/datasets/transformersbook/codeparrot)上分享出来了. +Python 代码可以从 GitHub 等代码存储库中获得,我们可以通过抓取每个 Python 存储库来使用它们来创建数据集。这是在[Transformers textbook](https://learning.oreilly.com/library/view/natural-language-processing/9781098136789/)预训练大型的GPT-2 模型。使用大约 180 GB 的 GitHub 转储,其中包含大约 2000 万个 Python 文件,称为 `codeparrot` ,作者构建了一个数据集,然后在[Hugging Face Hub](https://huggingface.co/datasets/transformersbook/codeparrot)上分享出来了. 然而,对完整语料库的训练既耗时又费力,我们只需要与 Python 数据科学堆栈相关的数据集子集。所以,让我们开始过滤 `codeparrot` 包含此堆栈中任何库的所有文件的数据集。由于数据集的太大,我们希望避免下载它;因此反,我们将使用流功能来动态过滤它。为了使用前面提到的库过滤代码示例,我们将使用以下函数: @@ -67,6 +66,11 @@ 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 @@ -103,7 +107,7 @@ filtered_data = filter_streaming_dataset(data, filters) 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="train") +ds_valid = load_dataset("huggingface-course/codeparrot-ds-valid", split="validation") raw_datasets = DatasetDict( { @@ -162,7 +166,7 @@ LICENSE: bsd-3-clause''' 我们可以看到 `content` 字段包含我们希望我们的模型训练的代码。现在我们有了一个数据集,我们需要预处理文本,使其采用适合预训练的格式。 -## 准备数据集 +## 准备数据集 [[准备数据集]] @@ -254,7 +258,7 @@ DatasetDict({ -## 初始化新模型 +## 初始化新模型 [[初始化新模型]] 我们的第一步是新初始化一个 GPT-2 模型。我们将对我们的模型使用与小型 GPT-2 模型相同的配置,因此我们加载预训练配置,确保分词器大小与模型词汇量大小匹配并设置 `bos` 和 `eos` (序列的开始和结束)令牌 ID: @@ -374,17 +378,17 @@ labels shape: (5, 128) {#if fw === 'tf'} -现在,我们可以使用`to_tf_dataset()`方法,使用上面创建的数据整理器将数据集转换为TensorFlow数据集: +现在,我们可以使用`prepare_tf_dataset()`方法,使用上面创建的数据整理器将数据集转换为TensorFlow数据集: ```python -tf_train_dataset = tokenized_dataset["train"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], +tf_train_dataset = model.prepare_tf_dataset( + tokenized_dataset["train"], collate_fn=data_collator, shuffle=True, batch_size=32, ) -tf_eval_dataset = tokenized_dataset["valid"].to_tf_dataset( - columns=["input_ids", "attention_mask", "labels"], +tf_eval_dataset = model.prepare_tf_dataset( + tokenized_dataset["valid"], collate_fn=data_collator, shuffle=False, batch_size=32, @@ -510,13 +514,13 @@ model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback {:else} -💡 如果您有权访问具有多个 GPU 的计算机,则可以尝试使用 `MirroredStrategy` 上下文来大幅加快训练速度。您需要创建一个`tf.distribute.MirroredStrategy`对象,并确保 `to_tf_dataset` 命令以及模型创建和对 `fit()`的调用都在其 `scope()` context. 上下文中运行。您可以查看有关此内容的文档[here](https://www.tensorflow.org/guide/distributed_training#use_tfdistributestrategy_with_keras_modelfit). +💡 如果您正在使用具有多个 GPU 的计算机,则可以尝试使用 `MirroredStrategy` 上下文来大幅加快训练速度。您需要创建一个`tf.distribute.MirroredStrategy`对象,并确保所有的 `to_tf_dataset` 或 `prepare_tf_dataset()` 方法以及模型创建和对 `fit()`的调用都在其 `scope()` 上下文中运行。您可以查看有关此内容的文档[here](https://www.tensorflow.org/guide/distributed_training#use_tfdistributestrategy_with_keras_modelfit). {/if} -## 使用管道生成代码 +## 使用管道生成代码 [[使用管道生成代码]] 现在是关键的部分:让我们看看经过训练的模型的实际效果如何!我们可以在日志中看到损失稳步下降,但为了让模型进行测试,让我们看看它在某些测试上的表现如何。为此,我们将模型包装在文本生成中的`pipeline` ,如果有可用的,我们会将它放在 GPU 上进行快速生成: @@ -650,7 +654,7 @@ rf {#if fw === 'pt'} -## 用 🤗 Accelerate 训练 +## 用 🤗 Accelerate 训练 [[用 🤗 Accelerate 训练]] 我们已经看到了如何使用 `Trainer` ,这可以允许一些自定义。然而,有时我们想要完全控制训练循环,或者我们想要进行一些奇特的更改。在这种情况下 🤗 Accelerate 是一个不错的选择,在本节中,我们将逐步介绍使用它来训练我们的模型的步骤。为了让事情变得更有趣,我们还将在训练循环中添加一些修改。 @@ -854,7 +858,7 @@ model.train() completed_steps = 0 for epoch in range(num_train_epochs): for step, batch in tqdm( - enumerate(train_dataloader, start=1), total=len(train_dataloader) + 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) diff --git a/chapters/zh-CN/chapter7/7.mdx b/chapters/zh-CN/chapter7/7.mdx index 8a823cadb..4d1fdfb96 100644 --- a/chapters/zh-CN/chapter7/7.mdx +++ b/chapters/zh-CN/chapter7/7.mdx @@ -1,6 +1,6 @@ -# 问答 +# 问答 [[问答]] {#if fw === 'pt'} @@ -29,7 +29,6 @@ 我们将使用 [SQuAD 数据集](https://rajpurkar.github.io/SQuAD-explorer/) 微调一个BERT模型, 其中包括群众工作者对一组维基百科文章提出的问题。以下是一个小的测试样例: - 本节使用的代码已经上传到了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) 找到它并尝试用它进行预测。 @@ -39,11 +38,11 @@ -## 准备数据 +## 准备数据 [[准备数据]] 最常用作抽取式问答的学术基准的数据集是 [SQuAD](https://rajpurkar.github.io/SQuAD-explorer/), 所以这就是我们将在这里使用的。还有一个更难的 [SQuAD v2](https://huggingface.co/datasets/squad_v2) 基准, 其中包括没有答案的问题。只要你自己的数据集包含上下文列、问题列和答案列, 你就应该能够调整以下步骤。 -### SQuAD 数据集 +### SQuAD 数据集 [[SQuAD 数据集]] 像往常一样, 我们只需一步就可以下载和缓存数据集, 这要归功于 `load_dataset()`: @@ -127,7 +126,7 @@ print(raw_datasets["validation"][2]["question"]) 我们可以看到, 答案确实可以是我们之前看到的三种可能性之一。 -### 处理训练数据 +### 处理训练数据 [[处理训练数据]] @@ -448,7 +447,7 @@ len(raw_datasets["train"]), len(train_dataset) 正如我们所见, 预处理增加了大约 1,000 个特征。我们的训练集现在可以使用了-- 让我们深入研究验证集的预处理! -### 处理验证数据 +### 处理验证数据 [[处理验证数据]] 预处理验证数据会稍微容易一些, 因为我们不需要生成标签(除非我们想计算验证损失, 但这个数字并不能真正帮助我们理解模型有多好)。真正的乐趣是将模型的预测解释为原始上下文的跨度。为此, 我们只需要存储偏移映射和某种方式来将每个创建的特征与它来自的原始示例相匹配。由于原始数据集中有一个 ID 列, 我们将使用该 ID。 @@ -506,19 +505,19 @@ I在这种情况下, 我们只添加了几百个样本, 因此验证数据集中 {#if fw === 'pt'} -## 使用 `Trainer` API 微调模型 +## 使用 `Trainer` API 微调模型 [[使用 `Trainer` API 微调模型]] 这个例子的训练代码看起来很像前面几节中的代码 -- 最难的是编写 `compute_metrics()` 函数。由于我们将所有样本填充到我们设置的最大长度, 因此没有数据整理器要定义, 所以这个度量计算真的是我们唯一需要担心的事情。困难的部分是将模型预测后处理为原始示例中的文本范围; 一旦我们这样做了, 🤗 Datasets 库中的指标将为我们完成大部分工作。 {:else} -## 使用 Keras 微调模型 +## 使用 Keras 微调模型 [[使用 Keras 微调模型]] 这个示例的训练代码看起来很像前几节中的代码, 但是计算指标将是唯一的挑战。因为我们将所有的样本填充到我们设置的最大长度, 所以不需要定义数据整理器, 所以这个度量计算实际上是我们唯一需要担心的事情。困难的部分是将模型预测后处理成原始例子中的文本范围; 一旦我们完成了这些, 🤗 Datasets 库中的指标将为我们完成大部分工作。 {/if} -### 后处理 +### 后处理 [[后处理]] {#if fw === 'pt'} @@ -530,7 +529,7 @@ I在这种情况下, 我们只添加了几百个样本, 因此验证数据集中 {/if} -该模型将在输入ID中为答案的开始和结束位置输出Logit, 正如我们在探索 [`question-answering` pipeline](/course/chapter6/4) 时看到的那样。后处理步骤将类似于我们在那里所做的, 所以这里是我们采取的行动的快速提醒: +该模型将在输入ID中为答案的开始和结束位置输出Logit, 正如我们在探索 [`question-answering` pipeline](/course/chapter6/3b) 时看到的那样。后处理步骤将类似于我们在那里所做的, 所以这里是我们采取的行动的快速提醒: - 我们屏蔽了与上下文之外的标记相对应的开始和结束 logits。 - 然后, 我们使用 softmax 将开始和结束 logits 转换为概率。 @@ -671,12 +670,12 @@ for example in small_eval_set: predicted_answers.append({"id": example_id, "prediction_text": best_answer["text"]}) ``` -预测答案的最终格式是我们将使用的度量标准所期望的格式。像往常一样, 我们可以在 🤗 Datasets 库的帮助下加载它: +预测答案的最终格式是我们将使用的度量标准所期望的格式。像往常一样, 我们可以在 🤗 Evaluate 库的帮助下加载它: ```python -from datasets import load_metric +import evaluate -metric = load_metric("squad") +metric = evaluate.load("squad") ``` 该指标期望我们上面看到的格式的预测答案 (一个字典列表, 其中一个键用于示例 ID, 一个键用于预测文本) 和以下格式的理论答案 (一个字典列表, 一个键示例的 ID 和可能答案的一键): @@ -789,7 +788,7 @@ compute_metrics(start_logits, end_logits, eval_set, small_eval_set) 看起来不错! 现在让我们用它来微调我们的模型。 -### 微调模型 +### 微调模型 [[微调模型]] {#if fw === 'pt'} @@ -863,20 +862,14 @@ data_collator = DefaultDataCollator(return_tensors="tf") 现在我们像往常一样创建数据集。 ```python -tf_train_dataset = train_dataset.to_tf_dataset( - columns=[ - "input_ids", - "start_positions", - "end_positions", - "attention_mask", - "token_type_ids", - ], +tf_train_dataset = model.prepare_tf_dataset( + train_dataset, collate_fn=data_collator, shuffle=True, batch_size=16, ) -tf_eval_dataset = validation_dataset.to_tf_dataset( - columns=["input_ids", "attention_mask", "token_type_ids"], +tf_eval_dataset = model.prepare_tf_dataset( + validation_dataset, collate_fn=data_collator, shuffle=False, batch_size=16, @@ -956,7 +949,7 @@ model.fit(tf_train_dataset, callbacks=[callback], epochs=num_train_epochs) 一旦训练完成, 我们终于可以评估我们的模型(并祈祷我们没有把所有的计算时间都花在任何事情上)。`Trainer` 的 `predict()` 方法将返回一个元组, 其中第一个元素将是模型的预测 (这里是带有开始和结束 logits 的对)。我们将其发送给 `compute_metrics()` 函数: ```python -predictions, _ = trainer.predict(validation_dataset) +predictions, _, _ = trainer.predict(validation_dataset) start_logits, end_logits = predictions compute_metrics(start_logits, end_logits, validation_dataset, raw_datasets["validation"]) ``` @@ -1013,11 +1006,11 @@ trainer.push_to_hub(commit_message="Training complete") 如果你想更深入地了解训练循环, 我们现在将向你展示如何使用 🤗 Accelerate 来做同样的事情。 -## 自定义训练循环 +## 自定义训练循环 [[自定义训练循环]] 现在让我们来看一下完整的训练循环, 这样您就可以轻松地自定义所需的部分。它看起来很像 [第三章](/course/chapter3/4) 中的训练循环, 除了评估循环。我们将能够定期评估模型, 因为我们不再受 `Trainer` 类的限制。 -### 为训练做准备 +### 为训练做准备 [[为训练做准备]] 首先, 我们需要从我们的数据集中构建 `DataLoader`。我们将这些数据集的格式设置为 `"torch"`, 并删除模型未使用的验证集中的列。然后, 我们可以使用 Transformers 提供的 `default_data_collator` 作为 `collate_fn`, 并打乱训练集, 但不打乱验证集58: @@ -1105,7 +1098,7 @@ repo = Repository(output_dir, clone_from=repo_name) 我们现在可以通过调用 `repo.push_to_hub()` 方法上传我们保存在 `output_dir` 中的任何内容。这将帮助我们在每个 epoch 结束时上传中间模型。 -## 训练循环 +## 训练循环 [[训练循环]] 我们现在准备编写完整的训练循环。在定义了一个进度条来跟踪训练进行后, 循环分为三个部分: @@ -1181,7 +1174,7 @@ unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) {/if} -## 使用微调模型 +## 使用微调模型 [[使用微调模型]] 我们已经向您展示了如何将我们在模型中心微调的模型与推理小部件一起使用。要在 `pipeline` 中本地使用它, 你只需要指定模型标识符: diff --git a/chapters/zh-CN/chapter7/8.mdx b/chapters/zh-CN/chapter7/8.mdx index be36949af..6d691a65a 100644 --- a/chapters/zh-CN/chapter7/8.mdx +++ b/chapters/zh-CN/chapter7/8.mdx @@ -1,4 +1,4 @@ -# 精通自然语言处理 +# 精通自然语言处理 [[精通自然语言处理]] 如果你在课程中做到了这一步,恭喜你——你现在拥有了用 🤗 Transformers 和 Hugging Face 生态系统解决(几乎)任何 NLP 任务所需的所有知识和工具! diff --git a/chapters/zh-CN/chapter7/9.mdx b/chapters/zh-CN/chapter7/9.mdx index 3e4841372..0ab1edce2 100644 --- a/chapters/zh-CN/chapter7/9.mdx +++ b/chapters/zh-CN/chapter7/9.mdx @@ -2,7 +2,7 @@ -# End-of-chapter quiz +# 章末小测验 [[章末小测验]] Let's test what you learned in this chapter! diff --git a/chapters/zh-CN/chapter8/1.mdx b/chapters/zh-CN/chapter8/1.mdx index 0505bbb4d..7f2e8f531 100644 --- a/chapters/zh-CN/chapter8/1.mdx +++ b/chapters/zh-CN/chapter8/1.mdx @@ -1,4 +1,4 @@ -# 介绍 +# 介绍 [[介绍]] 既然您知道如何使用🤗 Transformers处理最常见的NLP任务,您应该能够开始自己的项目了!在本章中,我们将探讨遇到问题时该怎么做。您将学习如何成功调试代码或训练,以及如果自己无法解决问题,如何向社区寻求帮助。如果您认为您在其中一个拥抱人脸库中发现了错误,我们将向您展示报告它的最佳方式,以便尽快解决问题。 diff --git a/chapters/zh-CN/chapter8/2.mdx b/chapters/zh-CN/chapter8/2.mdx index 311b09e6b..bcab43e75 100644 --- a/chapters/zh-CN/chapter8/2.mdx +++ b/chapters/zh-CN/chapter8/2.mdx @@ -1,4 +1,4 @@ -# 出现错误时该怎么办 +# 出现错误时该怎么办 [[出现错误时该怎么办]] -# 调试训练管道 +# 调试训练管道 [[调试训练管道]] @@ -22,7 +22,7 @@ 为了证明这一点,我们将使用以下脚本(尝试)在 [MNLI 数据集](https://huggingface.co/datasets/glue)上微调 DistilBERT 模型: ```py -from datasets import load_dataset, load_metric +from datasets import load_dataset from transformers import ( AutoTokenizer, AutoModelForSequenceClassification, @@ -30,7 +30,7 @@ from transformers import ( Trainer, ) -raw_datasets = load_dataset("glue", "mnli") +raw_datasets = evaluate.load("glue", "mnli") model_checkpoint = "distilbert-base-uncased" tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) @@ -52,7 +52,7 @@ args = TrainingArguments( weight_decay=0.01, ) -metric = load_metric("glue", "mnli") +metric = evaluate.load("glue", "mnli") def compute_metrics(eval_pred): @@ -76,7 +76,7 @@ trainer.train() 'ValueError: You have to specify either input_ids or inputs_embeds' ``` -### 检查数据 +### 检查数据 [[检查数据]] 这是不言而喻的,如果你的数据被破坏,“Trainer”将无法形成批次,更不用说训练你的模型了。 所以首先,你需要看看你的训练集中有什么。 @@ -98,7 +98,7 @@ trainer.train_dataset[0] 为什么没有处理数据生成标记呢? 我们确实在数据集上使用了“Dataset.map()”方法来对每个样本应用标记器。 但是如果你仔细看代码,你会发现我们在将训练和评估集传递给`Trainer`时犯了一个错误。 我们在这里没有使用 `tokenized_datasets`,而是使用了 `raw_datasets` 🤦。 所以让我们解决这个问题! ```py -from datasets import load_dataset, load_metric +from datasets import load_dataset from transformers import ( AutoTokenizer, AutoModelForSequenceClassification, @@ -106,7 +106,7 @@ from transformers import ( Trainer, ) -raw_datasets = load_dataset("glue", "mnli") +raw_datasets = evaluate.load("glue", "mnli") model_checkpoint = "distilbert-base-uncased" tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) @@ -128,7 +128,7 @@ args = TrainingArguments( weight_decay=0.01, ) -metric = load_metric("glue", "mnli") +metric = evaluate.load("glue", "mnli") def compute_metrics(eval_pred): @@ -200,7 +200,7 @@ transformers.models.distilbert.modeling_distilbert.DistilBertForSequenceClassifi 我们通过解码检查了输入 ID 是否正确。 接下来是检查 `attention_mask`: ```py -tokenizer.decode(trainer.train_dataset[0]["attention_mask"]) +trainer.train_dataset[0]["attention_mask"] ``` ```python out @@ -253,7 +253,7 @@ trainer.train_dataset.features["label"].names 现在我们知道我们的数据集看起来不错,是时候检查训练管道的下一步了。 -### 从 datasets 到 dataloaders +### 从 datasets 到 dataloaders [[从 datasets 到 dataloaders]] 训练管道中可能出错的下一件事是当“Trainer”尝试从训练或验证集形成批次时。 一旦你确定 `Trainer` 的数据集是正确的,你可以尝试通过执行以下操作手动形成一个批次(可以将 `train` 替换为 `eval` 用于验证数据加载器): @@ -291,7 +291,8 @@ data_collator 答案是因为我们没有将 `tokenizer` 传递给 `Trainer`,所以它无法创建我们想要的 `DataCollatorWithPadding`。 在实践中,您应该明确地传递您想要使用的数据整理器,以确保避免这些类型的错误。 让我们调整我们的代码来做到这一点: ```py -from datasets import load_dataset, load_metric +from datasets import load_dataset +import evaluate from transformers import ( AutoTokenizer, AutoModelForSequenceClassification, @@ -300,7 +301,7 @@ from transformers import ( Trainer, ) -raw_datasets = load_dataset("glue", "mnli") +raw_datasets = evaluate.load("glue", "mnli") model_checkpoint = "distilbert-base-uncased" tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) @@ -322,7 +323,7 @@ args = TrainingArguments( weight_decay=0.01, ) -metric = load_metric("glue", "mnli") +metric = evaluate.load("glue", "mnli") def compute_metrics(eval_pred): @@ -371,7 +372,7 @@ batch = data_collator([actual_train_set[i] for i in range(4)]) 现在我们已经调试了批处理创建过程,是时候将数据传递给模型了! -### 检查模型 +### 检查模型 [[检查模型]] 您应该能够通过执行以下命令来获得一个批次的数据: @@ -416,7 +417,8 @@ trainer.model.config.num_labels 有两个标签,只允许 0 和 1 作为目标,但是根据错误信息我们得到一个 2。得到一个 2 实际上是正常的:如果我们记得我们之前提取的标签名称,有三个,所以我们有索引 0 , 1 和 2 在我们的数据集中。 问题是我们没有告诉我们的模型,它应该创建三个标签。 所以让我们解决这个问题! ```py -from datasets import load_dataset, load_metric +from datasets import load_dataset +import evaluate from transformers import ( AutoTokenizer, AutoModelForSequenceClassification, @@ -425,7 +427,7 @@ from transformers import ( Trainer, ) -raw_datasets = load_dataset("glue", "mnli") +raw_datasets = evaluate.load("glue", "mnli") model_checkpoint = "distilbert-base-uncased" tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) @@ -447,7 +449,7 @@ args = TrainingArguments( weight_decay=0.01, ) -metric = load_metric("glue", "mnli") +metric = evaluate.load("glue", "mnli") def compute_metrics(eval_pred): @@ -490,7 +492,7 @@ outputs = trainer.model.to(device)(**batch) 如果仍然出现错误,请确保重新启动notebook并仅执行脚本的最后一个版本。 -### 执行一个优化器步骤 +### 执行一个优化器步骤 [[执行一个优化器步骤]] 现在我们知道我们可以构建实际通过模型检查的成批次的数据,我们已经为训练管道的下一步做好准备:计算梯度并执行优化步骤。 @@ -512,7 +514,7 @@ trainer.optimizer.step() 同样,如果您在 `Trainer` 中使用默认优化器,则在此阶段您不应该收到错误,但如果您有自定义优化器,则可能会出现一些问题需要在这里调试。 如果您在此阶段遇到奇怪的 CUDA 错误,请不要忘记返回 CPU。 说到 CUDA 错误,前面我们提到了一个特殊情况。 现在让我们来看看。 -### 处理 CUDA out-of-memory错误 +### 处理 CUDA out-of-memory错误 [[处理 CUDA out-of-memory错误]] 每当您收到以`RuntimeError: CUDA out of memory`开头的错误消息时,这表明您的 GPU 内存不足。 这与您的代码没有直接关联,并且它可能发生在运行良好的代码中。 此错误意味着您试图在 GPU 的内部存储器中放入太多东西,这导致了错误。 与其他 CUDA 错误一样,您需要重新启动内核才能再次运行训练。 @@ -524,7 +526,7 @@ trainer.optimizer.step() -### 评估模型 +### 评估模型 [[评估模型]] 现在我们已经解决了代码的所有问题,一切都很完美,训练应该可以顺利进行,对吧? 没那么快! 如果你运行 `trainer.train()` 命令,一开始一切看起来都不错,但过一会儿你会得到以下信息: @@ -626,7 +628,8 @@ compute_metrics((predictions, labels)) ```py import numpy as np -from datasets import load_dataset, load_metric +from datasets import load_dataset +import evaluate from transformers import ( AutoTokenizer, AutoModelForSequenceClassification, @@ -635,7 +638,7 @@ from transformers import ( Trainer, ) -raw_datasets = load_dataset("glue", "mnli") +raw_datasets = evaluate.load("glue", "mnli") model_checkpoint = "distilbert-base-uncased" tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) @@ -657,7 +660,7 @@ args = TrainingArguments( weight_decay=0.01, ) -metric = load_metric("glue", "mnli") +metric = evaluate.load("glue", "mnli") def compute_metrics(eval_pred): @@ -688,11 +691,11 @@ trainer.train() -## 在训练期间调试静默(没有任何错误提示)错误 +## 在训练期间调试静默(没有任何错误提示)错误 [[在训练期间调试静默(没有任何错误提示)错误]] 我们可以做些什么来调试一个没有错误地完成但没有得到好的结果的训练? 我们会在这里给你一些提示,但请注意,这种调试是机器学习中最难的部分,并且没有神奇的答案。 -### 检查您的数据(再次!) +### 检查您的数据(再次!) [[检查您的数据(再次!)]] 只有在理论上可以从您的数据中学到任何东西时,您的模型才会学到一些东西。 如果存在损坏数据的错误或标签是随机属性的,那么您很可能不会在数据集上获得任何知识。 因此,始终首先仔细检查您的解码输入和标签,然后问自己以下问题: @@ -713,7 +716,7 @@ trainer.train() 当您确定您的数据是完美的时,您可以通过一个简单的测试来查看模型是否能够对其进行训练。 -### 在一批上过度拟合你的模型 +### 在一批上过度拟合你的模型 [[在一批上过度拟合你的模型]] 过度拟合通常是我们在训练时尽量避免的事情,因为这意味着模型没有学习识别我们想要的一般特征,而只是记住了训练样本。 在这种情况下,一遍又一遍地尝试在一个批次上训练您的模型是一个很好的测试,可以检查您的问题是否可以通过您尝试训练的模型来解决。 它还将帮助您查看您的初始学习率是否太高。 @@ -765,7 +768,7 @@ compute_metrics((preds.cpu().numpy(), labels.cpu().numpy())) -### 在你有第一个基线之前不要调整任何东西 +### 在你有第一个基线之前不要调整任何东西 [[在你有第一个基线之前不要调整任何东西]] 超参数调优总是被强调为机器学习中最难的部分,但这只是帮助您在指标上有所收获的最后一步。 大多数情况下,`Trainer` 的默认超参数可以很好地为您提供良好的结果,因此在您获得超出数据集基线的东西之前,不要开始进行耗时且昂贵的超参数搜索 . @@ -773,7 +776,7 @@ compute_metrics((preds.cpu().numpy(), labels.cpu().numpy())) 如果您正在调整模型本身,不要尝试任何您无法合理证明的事情。 始终确保您返回过拟合测试以验证您的更改没有产生任何意外后果。 -### 请求帮忙 +### 请求帮忙 [[请求帮忙]] 希望您会在本节中找到一些可以帮助您解决问题的建议,但如果不是这样,请记住您可以随时在 [论坛](https://discuss.huggingface.co/) 上向社区提问。 diff --git a/chapters/zh-CN/chapter8/4_tf.mdx b/chapters/zh-CN/chapter8/4_tf.mdx index 21a4e57b5..b23b67fa9 100644 --- a/chapters/zh-CN/chapter8/4_tf.mdx +++ b/chapters/zh-CN/chapter8/4_tf.mdx @@ -1,6 +1,6 @@ -# Debugging the training pipeline +# Debugging the training pipeline [[Debugging the training pipeline]] -### TensorFlow 🦛饿饿 +### TensorFlow 🦛饿饿 [[TensorFlow 🦛饿饿]] 您应该注意的 TensorFlow 的一个特殊怪癖是,它会在您加载模型或进行任何训练后立即为自己分配 *所有 * GPU 内存,然后根据需要分配该内存。这与其他框架的行为不同,例如 PyTorch,后者根据 CUDA 的需要分配内存,而不是在内部进行。 TensorFlow 方法的一个优点是,当您耗尽内存时,它通常会给出有用的错误,并且它可以从该状态恢复而不会导致整个 CUDA 内核崩溃。但也有一个重要的缺点:如果你同时运行两个 TensorFlow 进程,那么**你将度过一段糟糕的时光**。 @@ -407,7 +407,7 @@ model.fit(train_dataset) 如果您开始运行之前正确的代码却收到有关 CUDA、BLAS 或 cuBLAS 的错误,这通常是罪魁祸首。您可以使用类似 `nvidia-smi` 的命令来检查 - 当您关闭或重新启动当前笔记本时,您的大部分内存是否空闲,或者是否仍在使用中?如果它仍在使用中,则有其他东西在占用它! -### 检查您的数据(再次!) +### 检查您的数据(再次!) [[检查您的数据(再次!)]] 只有在理论上可以从您的数据中学到任何东西时,您的模型才会学到一些东西。 如果存在损坏数据的错误或标签是随机属性的,那么您很可能不会在数据集上获得任何知识。这里一个有用的工具是`tokenizer.decode()`。 这会将 `input_ids` 转换回字符串,因此您可以查看数据并查看您的训练数据是否正在教授您希望它教授的内容。 例如,像我们上面所做的那样从 `tf.data.Dataset` 中获取 `batch` 后,您可以像这样解码第一个元素: @@ -435,7 +435,7 @@ label = labels[0] 当您确定您的数据是完美的时,您可以通过一个简单的测试来查看模型是否能够对其进行训练。 -### 在一批上过度拟合你的模型 +### 在一批上过度拟合你的模型 [[在一批上过度拟合你的模型]] 过度拟合通常是我们在训练时尽量避免的事情,因为这意味着模型没有学习识别我们想要的一般特征,而只是记住了训练样本。 但是,一遍又一遍地尝试在一个批次上训练您的模型是一个很好的测试,可以检查您构建的问题是否可以通过您尝试训练的模型来解决。 它还将帮助您查看您的初始学习率是否太高。 @@ -445,8 +445,8 @@ label = labels[0] for batch in train_dataset: break -# Make sure you have run model.compile() and set your optimizer, -# and your loss/metrics if you're using them +# Make sure you have run model.compile() and set your optimizer, [[Make sure you have run model.compile() and set your optimizer,]] +# and your loss/metrics if you're using them [[and your loss/metrics if you're using them]] model.fit(batch, epochs=20) ``` @@ -467,7 +467,7 @@ model.fit(batch, epochs=20) -### 在你有第一个基线之前不要调整任何东西 +### 在你有第一个基线之前不要调整任何东西 [[在你有第一个基线之前不要调整任何东西]] 超参数调整总是被强调为机器学习中最难的部分,但这只是帮助您在指标上获得一点点提升的最后一步。 例如将默认的 Adam 学习率 1e-3 与 Transformer 模型一起使用,当然会使学习进行得非常缓慢或完全停止,但大多数时候“合理”的超参数,例如从 1e-5 到 5e-5 的学习率,会很好地给你带来好的结果。因此,在您获得超出数据集基线的东西之前,不要开始进行耗时且昂贵的超参数搜索。 @@ -475,7 +475,7 @@ model.fit(batch, epochs=20) 如果您正在调整模型本身,不要尝试任何您无法合理证明的事情。 始终确保您返回过拟合测试以验证您的更改没有产生任何意外后果。 -### 请求帮忙 +### 请求帮忙 [[请求帮忙]] 希望您会在本节中找到一些可以帮助您解决问题的建议,但如果不是这样,请记住您可以随时在 [论坛](https://discuss.huggingface.co/) 上向社区提问。 diff --git a/chapters/zh-CN/chapter8/5.mdx b/chapters/zh-CN/chapter8/5.mdx index c620cc9a7..e7164be66 100644 --- a/chapters/zh-CN/chapter8/5.mdx +++ b/chapters/zh-CN/chapter8/5.mdx @@ -1,4 +1,4 @@ -# 如何写一个好问题 +# 如何写一个好问题 [[如何写一个好问题]] 当您确定手头有错误时,第一步是构建一个最小的可重现示例。 -## 创建一个最小的可重现示例 +## 创建一个最小的可重现示例 [[创建一个最小的可重现示例]] 隔离产生错误的代码段非常重要,因为 Hugging Face 团队中没有人是魔术师(目前),他们无法修复他们看不到的东西。顾名思义,最小的可重现示例应该是可重现的。这意味着它不应依赖于您可能拥有的任何外部文件或数据。尝试用一些看起来像真实值的虚拟值替换您正在使用的数据,但仍然会产生相同的错误。 @@ -26,13 +26,13 @@ 如果您觉得足够舒服,请检查发生错误的源代码。您可能会找到问题的解决方案(在这种情况下,您甚至可以提出拉取请求来修复它),但更一般地说,这可以帮助维护人员在阅读您的报告时更好地理解来源。 -## 填写问题模板 +## 填写问题模板 [[填写问题模板]] 当您提交问题时,您会注意到有一个模板需要填写。我们将按照[🤗 Transformers issues](https://github.com/huggingface/transformers/issues/new/choose)在这里,但是如果您在另一个存储库中报告问题,则需要相同类型的信息。不要将模板留空:花时间填写它可以最大限度地提高您获得答案和解决问题的机会。 通常,在提交问题时,请始终保持礼貌。这是一个开源项目,因此您使用的是免费软件,没有人有任何义务帮助您。您可能会在您的问题中包含您认为合理的批评,但是维护人员很可能会认为它很糟糕并且不会急于帮助您。确保你阅读了[code of conduct](https://github.com/huggingface/transformers/blob/master/CODE_OF_CONDUCT.md)项目的。 -### 包括您的环境信息 +### 包括您的环境信息 [[包括您的环境信息]] 🤗 Transformers 提供了一个实用程序来获取我们需要的关于您的环境的所有信息。只需在终端中输入以下内容: @@ -59,13 +59,13 @@ Copy-and-paste the text below in your GitHub issue and FILL OUT the two last poi 您还可以添加一个 **!** 在开始的时候 **transformers-cli env** 命令从笔记本单元执行它,然后在问题的开头复制并粘贴结果。 -### 标记人员 +### 标记人员 [[标记人员]] 通过输入标记人员 **@** 其次是他们的 GitHub 句柄将向他们发送通知,以便他们会看到您的问题并可能会更快地回复。适度使用它,因为如果您标记的人没有直接链接,他们可能不喜欢收到通知。如果您查看了与您的错误相关的源文件,您应该在您认为对您的问题负责的行中标记最后一个进行更改的人(您可以通过查看 GitHub 上的所述行找到此信息,选择它,然后单击“查看 git blame”)。 否则,模板会提供要标记的人的建议。一般来说,不要标记超过三个人! -### 包含一格可重复的示例 +### 包含一格可重复的示例 [[包含一格可重复的示例]] 如果您已经设法创建了一个产生错误的独立示例,那么现在是包含它的时候了!键入一行包含三个反引号,后跟 **python** , 像这样: @@ -75,11 +75,11 @@ Copy-and-paste the text below in your GitHub issue and FILL OUT the two last poi 然后粘贴您的最小可重现示例并键入一个带有三个反引号的新行。这将确保您的代码格式正确。如果您没有设法创建可重现的示例,请以清晰的步骤解释您是如何解决问题的。如果可以,请包含指向错误所在的 Google Colab 笔记本的链接。你分享的信息越多,维护者就越有能力回复你。在所有情况下,您都应该复制并粘贴您收到的整个错误消息。如果您在 Colab 中工作,请记住,堆栈跟踪中的某些帧可能会自动折叠,因此请确保在复制之前展开它们。与代码示例一样,将该错误消息放在两行之间,并带有三个反引号,因此格式正确。 -### 描述预期行为 +### 描述预期行为 [[描述预期行为]] 用几行解释你期望得到什么,以便维护人员完全掌握问题。这部分通常很明显,所以应该用一句话来形容,但在某些情况下,您可能有很多话要说。 -## 然后什么? +## 然后什么? [[然后什么?]] 提交您的问题后,请确保快速检查一切是否正常。如果您犯了错误,您可以编辑问题,或者如果您发现问题与您最初的想法不同,甚至可以更改其标题。如果你没有得到答案,就没有必要对人进行 ping 操作。如果几天内没有人帮助您,很可能没有人能理解您的问题。不要犹豫,回到可重现的例子。你能不能让它更短更切题?如果您在一周内没有得到答复,您可以留言温和地寻求帮助,特别是如果您已编辑问题以包含有关该问题的更多信息。 diff --git a/chapters/zh-CN/chapter8/6.mdx b/chapters/zh-CN/chapter8/6.mdx index a13a9b23c..f33712406 100644 --- a/chapters/zh-CN/chapter8/6.mdx +++ b/chapters/zh-CN/chapter8/6.mdx @@ -1,4 +1,4 @@ -# 第2部分完成! +# 第2部分完成! [[第2部分完成!]] 恭喜,您已经完成了课程的第二部分!我们正在积极研究第三个,所以订阅我们的[newsletter](https://huggingface.curated.co/)以确保您不会错过它的发布。 diff --git a/chapters/zh-CN/chapter8/7.mdx b/chapters/zh-CN/chapter8/7.mdx index 45f4c7ee7..9bfb36bc0 100644 --- a/chapters/zh-CN/chapter8/7.mdx +++ b/chapters/zh-CN/chapter8/7.mdx @@ -1,6 +1,6 @@ -# 章末测评 +# 章末小测验 [[章末小测验]] 让我们测试一下你在本章学到的东西! @@ -184,7 +184,8 @@ from transformers import GPT3ForSequenceClassification }, { text: "它允许维护人员知道你是在 GPU 还是 CPU 上运行代码。", - explain: "正确! 正如我们在本章中看到的, gpu 和 cpu 上的错误在风格上可能有很大的不同, 了解您正在使用的硬件可以帮助集中维护人员的注意力。但这不是唯一的好处...", + explain: "正确的! 正如我们在本章中所见,在 GPU 或 CPU 上运行的代码可能会产生不同的结果或错误,了解您使用的是哪种硬件有助于吸引维护人员的注意力。 但这不是唯一的好处...", + correct: true } ]} /> \ No newline at end of file diff --git a/chapters/zh-CN/chapter9/1.mdx b/chapters/zh-CN/chapter9/1.mdx index 88b53b355..f438d58aa 100644 --- a/chapters/zh-CN/chapter9/1.mdx +++ b/chapters/zh-CN/chapter9/1.mdx @@ -1,4 +1,4 @@ -# Gradio 简介 +# Gradio 简介 [[Gradio 简介]] 在本章中,我们将学习如何为您的机器学习构建**交互式演示**模型。 @@ -28,9 +28,3 @@ 本章分为两个部分,包括_概念_和_应用程序_。在您了解每个部分的概念后,您将应用它来构建特定类型的演示,范围从图像分类到语音识别。当你读完本章时,你将能够用几行 Python 代码构建这些演示(以及更多!)。 👀 点击 Hugging Face Spaces 以查看机器学习社区构建的许多机器学习演示的最新示例! - -## Gradio 方块派对🥳 - -如果你想充分利用本章中的知识,就加入 Gradio 积木派对吧!这是由 Hugging Face 于**5 月 16 日至 31 日**举办的社区活动。在此活动中,您将使用 Gradio 构建酷炫的机器学习演示,并参与赢取 Hugging Face 礼物和奖品! - -查看 [活动描述](https://github.com/AK391/community-events/blob/main/gradio-blocks/README.md) 可以了解如何参与的详细信息 - 我们迫不及待地想看看你构建的🤗演示! diff --git a/chapters/zh-CN/chapter9/2.mdx b/chapters/zh-CN/chapter9/2.mdx index f313f304d..9910c483c 100644 --- a/chapters/zh-CN/chapter9/2.mdx +++ b/chapters/zh-CN/chapter9/2.mdx @@ -1,4 +1,4 @@ -# 构建你的第一个演示 +# 构建你的第一个演示 [[构建你的第一个演示]] -### `launch()` 方法 +### `launch()` 方法 [[`launch()` 方法]] 到目前为止,我们已经使用了`launch()`方法来启动界面,但是我们 还没有真正讨论过它的作用。 @@ -120,7 +120,7 @@ gr.Interface( 我们将在下一节中更详细地介绍 `share` 参数! -## ✏️ 让我们应用它! +## ✏️ 让我们应用它! [[✏️ 让我们应用它!]] 让我们构建一个界面,让您演示 **speech-recognition** 模型。 为了让它变得有趣,我们将接受 *or* 麦克风输入或上传的文件。 diff --git a/chapters/zh-CN/chapter9/4.mdx b/chapters/zh-CN/chapter9/4.mdx index 62c675d99..676084ce1 100644 --- a/chapters/zh-CN/chapter9/4.mdx +++ b/chapters/zh-CN/chapter9/4.mdx @@ -1,4 +1,4 @@ -# 与他人分享演示 +# 与他人分享演示 [[与他人分享演示]] Overview of a gradio interface @@ -52,7 +52,7 @@ gr.Interface( -### 使用临时链接分享您的演示 +### 使用临时链接分享您的演示 [[使用临时链接分享您的演示]] 现在我们已经有了机器学习模型的工作演示,让我们学习如何轻松共享指向我们界面的链接。 通过在 `launch()` 方法中设置 `share=True` 可以轻松地公开共享接口: @@ -64,7 +64,7 @@ gr.Interface(classify_image, "image", "label").launch(share=True) 但是请记住,这些链接是可公开访问的,这意味着任何人都可以使用您的模型进行预测! 因此,请确保不要通过您编写的函数公开任何敏感信息,或允许在您的设备上发生任何关键更改。 如果设置 `share=False`(默认值),则仅创建本地链接。 -### 在 Hugging Face Spaces 上托管您的演示 +### 在 Hugging Face Spaces 上托管您的演示 [[在 Hugging Face Spaces 上托管您的演示]] 可以传递给同事的共享链接很酷,但是如何永久托管您的演示并让它存在于互联网上自己的“空间”中? @@ -74,7 +74,7 @@ Hugging Face Spaces 提供了在互联网上永久托管 Gradio 模型的基础 -## ✏️ 让我们应用它! +## ✏️ 让我们应用它! [[✏️ 让我们应用它!]] 使用到目前为止我们在各节中学到的知识,让我们创建我们在[本章第一节](/course/chapter9/1)中看到的草图识别演示。 让我们为我们的界面添加一些自定义并设置 `share=True` 以创建一个我们可以传递的公共链接。 diff --git a/chapters/zh-CN/chapter9/5.mdx b/chapters/zh-CN/chapter9/5.mdx index 4fc5a67a0..77200b830 100644 --- a/chapters/zh-CN/chapter9/5.mdx +++ b/chapters/zh-CN/chapter9/5.mdx @@ -1,4 +1,4 @@ -# 与 Hugging Face Hub 整合 +# 与 Hugging Face Hub 整合 [[与 Hugging Face Hub 整合]] -### 创建多步demo +### 创建多步demo [[创建多步demo]] 在某些情况下, 您可能需要一个 _多步骤的demo_, 其中重用一个函数的输出作为下一个函数的输入。使用 `块` 很容易做到这一点, 因为你可以使用组件作为一个事件触发器的输入, 但作为另一个事件触发器的输出。看看下面示例中的文本组件, 它的值是语音到文本模型的结果, 但也被传递到情感分析模型: @@ -202,7 +202,7 @@ demo.launch() -### 更新组件属性 +### 更新组件属性 [[更新组件属性]] 到目前为止, 我们已经了解了如何创建事件来更新另一个组件的值。但是, 如果您想更改组件的其他属性, 例如文本框的可见性或单选按钮组中的选项, 会发生什么? 您可以通过返回组件类的 `update()` 方法而不是函数的常规返回值来做到这一点。 diff --git a/chapters/zh-CN/chapter9/8.mdx b/chapters/zh-CN/chapter9/8.mdx index 846b00250..0382eb43b 100644 --- a/chapters/zh-CN/chapter9/8.mdx +++ b/chapters/zh-CN/chapter9/8.mdx @@ -1,4 +1,4 @@ -# Gradio,回顾! +# Gradio,回顾! [[Gradio,回顾!]] 关于使用 Gradio 构建酷炫的 ML 演示的章节到此结束 - 我们希望您喜欢它!回顾一下,在本章中,我们学习了: @@ -10,7 +10,7 @@ 如果您想测试您对本章所涵盖概念的理解,请查看下一节中的测验! -## 下一步去哪里? +## 下一步去哪里? [[下一步去哪里?]] 如果您想了解有关 Gradio 的更多信息,您可以 diff --git a/chapters/zh-CN/chapter9/9.mdx b/chapters/zh-CN/chapter9/9.mdx index b3cbe287c..9027af836 100644 --- a/chapters/zh-CN/chapter9/9.mdx +++ b/chapters/zh-CN/chapter9/9.mdx @@ -2,7 +2,7 @@ -# 章末测验 +# 章末小测验 [[章末小测验]] An empty colab notebook
-接下來就是安裝我們將會用到的函式庫。我們會使用 `pip` 這個Python的資源管理工具來安裝。在Colab notebook裡,你可以用 `!` 來執行系統指令,所以你可以用以下的指令來安裝 🤗 Transformers函式庫: +接下來就是安裝我們將會用到的函式庫。我們會使用 `pip` 這個Python的資源管理工具來安裝。在Colab notebook裡,你可以用 `!` 來執行系統指令,所以你可以用以下的指令來安裝 🤗 Transformers 函式庫: ``` !pip install transformers ``` -把函式庫導入到Python runtime可以確認你的資源包有被正確地安裝: +把函式庫導入到 Python runtime 可以確認你的資源包有被正確地安裝: ``` import transformers @@ -38,7 +38,7 @@ import transformers A gif showing the result of the two commands above: installation and import -這會安裝一個非常輕量的🤗 Transformers。裡面沒有安裝任何像是PyTorch或TensorFlow等的機器學習框架。因為我們會用到很多函式庫裡的不同功能,所以我們建議安裝包含了大部分使用情境所需資源的開發用版本: +這會安裝一個非常輕量的 🤗 Transformers。裡面沒有安裝任何像是 PyTorch 或 TensorFlow 等的機器學習框架。因為我們會用到很多函式庫裡的不同功能,所以我們建議安裝包含了大部分使用情境所需資源的開發用版本: ``` @@ -50,16 +50,16 @@ import transformers ## 使用Python虛擬環境 -如果你比較想用Python虛擬環境的話,第一步就是安裝Python。我們建議跟著[這篇教學](https://realpython.com/installing-python/)做為起手式。 +如果你比較想用 Python 虛擬環境的話,第一步就是安裝 Python。我們建議跟著[這篇教學](https://realpython.com/installing-python/)做為起手式。 -當你安裝好Python後,你應該就能從終端機執行Python指令了。在進行下一步之前你可以先執行以下指令來確認Python有沒有安裝好:`python --version` 這條指令會讓終端機顯示你所安裝的Python版本。 +當你安裝好 Python 後,你應該就能從終端機執行 Python 指令了。在進行下一步之前你可以先執行以下指令來確認 Python 有沒有安裝好:`python --version` 這條指令會讓終端機顯示你所安裝的 Python 版本。 -在終端機執行像是`python --version`的Python指令時,你應該把你的指令想成是用你系統上主要的Python版本來執行。我們建議不要在這個版本上安裝任何資源包,讓每個專案在各自獨立的環境裡運行就可以了。這樣每個專案都可以有各自的相依性跟資源包,你也不用擔心不同專案之間使用同一個環境時潛在的相容性問題。 +在終端機執行像是`python --version`的 Python 指令時,你應該把你的指令想成是用你系統上主要的 Python 版本來執行。我們建議不要在這個版本上安裝任何資源包,讓每個專案在各自獨立的環境裡運行就可以了。這樣每個專案都可以有各自的相依性跟資源包,你也不用擔心不同專案之間使用同一個環境時潛在的相容性問題。 -在Python我們可以用[*虛擬環境*](https://docs.python.org/3/tutorial/venv.html)來做這件事。虛擬環境是一個獨立包裝的樹狀目錄,每一個目錄下都有安裝特定版本的Python跟它需要的所有資源包。創建這樣的虛擬環境可以用很多不同的工具,不過我們會用一個叫做[`venv`](https://docs.python.org/3/library/venv.html#module-venv)的Python官方資源包。 +在 Python 我們可以用[*虛擬環境*](https://docs.python.org/3/tutorial/venv.html)來做這件事。虛擬環境是一個獨立包裝的樹狀目錄,每一個目錄下都有安裝特定版本的Python跟它需要的所有資源包。創建這樣的虛擬環境可以用很多不同的工具,不過我們會用一個叫做[`venv`](https://docs.python.org/3/library/venv.html#module-venv)的Python官方資源包。 首先,創建你希望你的程式執行時所在的目錄 - 舉例來說,你可能想要在你的家目錄下新增一個叫*transformers-course*的目錄: @@ -103,7 +103,7 @@ which python ### 安裝相依性資源包 -在之前的段落中提到的使用Google Colab的情況裡,你會需要安裝相依性資源包才能繼續。你可以用 `pip` 這個資源管理工具來安裝開發版的🤗 Transformers: +在之前的段落中提到的使用 Google Colab 的情況裡,你會需要安裝相依性資源包才能繼續。你可以用 `pip` 這個資源管理工具來安裝開發版的🤗 Transformers: ``` pip install "transformers[sentencepiece]" diff --git a/chapters/zh-TW/chapter1/1.mdx b/chapters/zh-TW/chapter1/1.mdx new file mode 100644 index 000000000..f779a48d0 --- /dev/null +++ b/chapters/zh-TW/chapter1/1.mdx @@ -0,0 +1,57 @@ +# 本章簡介 + + + +## 歡迎來到🤗教學 + + + +本教學將使用 Hugging Face 生態系統中的庫——🤗 Transformers、🤗 Datasets、🤗 Tokenizers 和 🤗 Accelerate——以及 Hugging Face Hub 教你自然語言處理 (NLP)。它是完全免費的,並且沒有廣告。 + + +## 有什麼是值得期待的? + +以下是課程的簡要概述: + +
+Brief overview of the chapters of the course. + +
+ +- 第 1 章到第 4 章介紹了 🤗 Transformers 庫的主要概念。在本課程的這一部分結束時,您將熟悉 Transformer 模型的工作原理,並將瞭解如何使用 [Hugging Face Hub](https://huggingface.co/models) 中的模型,在數據集上對其進行微調,並在 Hub 上分享您的結果。 +- 第 5 章到第 8 章在深入研究經典 NLP 任務之前,教授 🤗 Datasets和 🤗 Tokenizers的基礎知識。在本部分結束時,您將能夠自己解決最常見的 NLP 問題。 +- 第 9 章到第 12 章更加深入,探討瞭如何使用 Transformer 模型處理語音處理和計算機視覺中的任務。在此過程中,您將學習如何構建和分享模型,並針對生產環境對其進行優化。在這部分結束時,您將準備好將🤗 Transformers 應用於(幾乎)任何機器學習問題! + +這個課程: + +* 需要良好的 Python 知識 +* 最好先學習深度學習入門課程,例如[DeepLearning.AI](https://www.deeplearning.ai/) 提供的 [fast.ai實用深度學習教程](https://course.fast.ai/) +* 不需要事先具備 [PyTorch](https://pytorch.org/) 或 [TensorFlow](https://www.tensorflow.org/) 知識,雖然熟悉其中任何一個都會對huggingface的學習有所幫助 + +完成本課程後,我們建議您查看 [DeepLearning.AI的自然語言處理系列課程](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh),其中涵蓋了廣泛的傳統 NLP 模型,如樸素貝葉斯和 LSTM,這些模型非常值得瞭解! + +## 我們是誰? + +關於作者: + +**Matthew Carrigan** 是 Hugging Face 的機器學習工程師。他住在愛爾蘭都柏林,之前在 Parse.ly 擔任機器學習工程師,在此之前,他在Trinity College Dublin擔任博士後研究員。他不相信我們會通過擴展現有架構來實現 AGI,但無論如何都對機器人充滿希望。 + +**Lysandre Debut** 是 Hugging Face 的機器學習工程師,從早期的開發階段就一直致力於 🤗 Transformers 庫。他的目標是通過使用非常簡單的 API 開發工具,讓每個人都可以使用 NLP。 + +**Sylvain Gugger** 是 Hugging Face 的一名研究工程師,也是 🤗Transformers庫的核心維護者之一。此前,他是 fast.ai 的一名研究科學家,他與Jeremy Howard 共同編寫了[Deep Learning for Coders with fastai and Py Torch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)。他的主要研究重點是通過設計和改進允許模型在有限資源上快速訓練的技術,使深度學習更容易普及。 + +**Merve Noyan** 是 Hugging Face 的開發者倡導者,致力於開發工具並圍繞它們構建內容,以使每個人的機器學習平民化。 + +**Lucile Saulnier** 是 Hugging Face 的機器學習工程師,負責開發和支持開源工具的使用。她還積極參與了自然語言處理領域的許多研究項目,例如協作訓練和 BigScience。 + +**Lewis Tunstall** 是 Hugging Face 的機器學習工程師,專注於開發開源工具並使更廣泛的社區可以使用它們。他也是即將出版的一本書[O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/)的作者之一。 + +**Leandro von Werra** 是 Hugging Face 開源團隊的機器學習工程師,也是即將出版的一本書[O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/)的作者之一。他擁有多年的行業經驗,通過在整個機器學習堆棧中工作,將 NLP 項目投入生產。 + +你準備好了嗎?在本章中,您將學習: +* 如何使用 `pipeline()` 函數解決文本生成、分類等NLP任務 +* 關於 Transformer 架構 +* 如何區分編碼器、解碼器和編碼器-解碼器架構和用例 diff --git a/chapters/zh-TW/chapter1/10.mdx b/chapters/zh-TW/chapter1/10.mdx new file mode 100644 index 000000000..cea2082cd --- /dev/null +++ b/chapters/zh-TW/chapter1/10.mdx @@ -0,0 +1,258 @@ + + +# 章末小測試 + + + +這一章涵蓋了很多內容! 如果有一些不太明白的地方,請不要擔心; 下一章將幫助你瞭解這些模塊在底層是如何運作的。 + +讓我們來測試一下你在這一章學到了什麼! + +### 1. 探索 Hub 並尋找 `roberta-large-mnli` checkpoint。 它可以完成什麼類型的任務? + + +roberta-large-mnli 頁面回顧一下." + }, + { + text: "文本分類", + explain: "更準確地說,它對兩個句子在三個標籤(矛盾、無關、相近)之間的邏輯鏈接進行分類——這項任務也稱爲自然語言推理.", + correct: true + }, + { + text: "文本生成", + explain: "點擊前往roberta-large-mnli 頁面回顧一下." + } + ]} +/> + +### 2. 下面的代碼將會返回什麼結果? + +```py +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` + +sentiment-analysis pipeline將會返回這些." + }, + { + text: "它將返回一個生成的文本來完成這句話。", + explain: "這個選項是不對的 — text-generation pipeline將會返回這些.", + }, + { + text: "它將返回代表人員、組織或位置的單詞。", + explain: "此外,使用 grouped_entities=True,它會將屬於同一實體的單詞組合在一起,例如“Hugging Face”。", + correct: true + } + ]} +/> + +### 3. 在此代碼示例中...的地方應該填寫什麼? + +```py +from transformers import pipeline + +filler = pipeline("fill-mask", model="bert-base-cased") +result = filler("...") +``` + + has been waiting for you.", + explain: "這個選項是不對的。 請查看 bert-base-cased 模型卡片,然後再嘗試找找錯在哪裏。" + }, + { + text: "This [MASK] has been waiting for you.", + explain: "正解! 這個模型的mask的掩碼是[MASK].", + correct: true + }, + { + text: "This man has been waiting for you.", + explain: "這個選項是不對的。 這個pipeline的作用是填充經過mask的文字,因此它需要在輸入的文本中存在mask的token。" + } + ]} +/> + +### 4. 爲什麼這段代碼會無法運行? + +```py +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +result = classifier("This is a course about the Transformers library") +``` + +candidate_labels=[...].", + correct: true + }, + { + text: "這個pipeline需要多個句子,而不僅僅是一個。", + explain: "這個選項是不對的。儘管正確使用時,此pipeline可以同時處理多個句子(與所有其他pipeline一樣)。" + }, + { + text: "像往常一樣,🤗 Transformers 庫出故障了。", + explain: "對此,我們不予置評!" + }, + { + text: "該 pipeline 需要更長的輸入; 這個句子太短了。", + explain: "這個選項是不對的。 不過請注意,在這個 pipeline 處理時,太長的文本將被截斷。" + } + ]} +/> + +### 5. “遷移學習”是什麼意思? + + + +### 6. 語言模型在預訓練時通常不需要標籤,這樣的說法是否正確。 + + +自監督,這意味着標籤是根據輸入自動創建的(例如:預測下一個單詞或填充一些[MARSK]單詞)。", + correct: true + }, + { + text: "錯誤", + explain: "這不是一個正確的答案。" + } + ]} +/> + + +### 7. 選擇最能描述「模型(model)」、「架構(architecture)」和「權重(weights)」的句子。 + + + +### 8. 你將使用以下哪種類型的模型來根據輸入的提示生成文本? + + + +### 9. 你會使用哪些類型的模型來生成文本的摘要? + + + +### 10. 你會使用哪一種類型的模型來根據特定的標籤對文本輸入進行分類? + + + +### 11. 模型中觀察到的偏見有哪些可能的來源? + + diff --git a/chapters/zh-TW/chapter1/2.mdx b/chapters/zh-TW/chapter1/2.mdx new file mode 100644 index 000000000..e9a1f74f6 --- /dev/null +++ b/chapters/zh-TW/chapter1/2.mdx @@ -0,0 +1,25 @@ +# 自然語言處理 + + + +在進入 Transformer 模型之前,讓我們快速概述一下自然語言處理是什麼以及我們為什麼這麼重視它。 + +## 什麼是自然語言處理? + +NLP 是語言學和機器學習交叉領域,專注於理解與人類語言相關的一切。 NLP 任務的目標不僅是單獨理解單個單詞,而且是能夠理解這些單詞的上下文。 + +以下是常見 NLP 任務的列表,每個任務都有一些示例: + +- **對整個句子進行分類**: 獲取評論的情緒,檢測電子郵件是否為垃圾郵件,確定句子在語法上是否正確或兩個句子在邏輯上是否相關 +- **對句子中的每個詞進行分類**: 識別句子的語法成分(名詞、動詞、形容詞)或命名實體(人、地點、組織) +- **生成文本內容**: 用自動生成的文本完成提示,用屏蔽詞填充文本中的空白 +- **從文本中提取答案**: 給定問題和上下文,根據上下文中提供的信息提取問題的答案 +- **從輸入文本生成新句子**: 將文本翻譯成另一種語言,總結文本 + +NLP 不僅限於書面文本。它還解決了語音識別和計算機視覺中的複雜挑戰,例如生成音頻樣本的轉錄或圖像描述。 +## 為什麼具有挑戰性? + +計算機處理信息的方式與人類不同。例如,當我們讀到「我餓了」這句話時,我們很容易理解它的意思。同樣,給定兩個句子,例如「我很餓」和「我很傷心」,我們可以輕鬆確定它們的相似程度。對於機器學習 (ML) 模型,此類任務更加困難。文本需要以一種使模型能夠從中學習的方式進行處理。而且由於語言很複雜,我們需要仔細考慮必須如何進行這種處理。關於如何表示文本已經做了很多研究,我們將在下一章中介紹一些方法。 \ No newline at end of file diff --git a/chapters/zh-TW/chapter1/3.mdx b/chapters/zh-TW/chapter1/3.mdx new file mode 100644 index 000000000..31e4c8296 --- /dev/null +++ b/chapters/zh-TW/chapter1/3.mdx @@ -0,0 +1,287 @@ +# Transformers能做什麼? + + + +在本節中,我們將看看 Transformer 模型可以做什麼,並使用 🤗 Transformers 庫中的第一個工具:pipeline() 函數。 + +👀 看到那個右上角的 在Colab中打開的按鈕了嗎? 單擊它就可以打開一個包含本節所有代碼示例的 Google Colab 筆記本。 每一個有實例代碼的小節都會有它。 + +如果您想在本地運行示例,我們建議您查看準備. + + +## Transformer被應用於各個方面! +Transformer 模型用於解決各種 NLP 任務,就像上一節中提到的那樣。以下是一些使用 Hugging Face 和 Transformer 模型的公司和組織,他們也通過分享他們的模型回饋社區: + +![使用 Hugging Face 的公司](https://huggingface.co/course/static/chapter1/companies.PNG) +[🤗 Transformers 庫](https://github.com/huggingface/transformers)提供了創建和使用這些共享模型的功能。[模型中心(hub)](https://huggingface.co/models)包含數千個任何人都可以下載和使用的預訓練模型。您還可以將自己的模型上傳到 Hub! + + +⚠️ Hugging Face Hub 不限於 Transformer 模型。任何人都可以分享他們想要的任何類型的模型或數據集!創建一個 Huggingface.co 帳戶(https://huggingface.co/join)以使用所有可用功能! + + +在深入研究 Transformer 模型的底層工作原理之前,讓我們先看幾個示例,看看它們如何用於解決一些有趣的 NLP 問題。 + +## 使用pipelines + + +(這裡有一個視頻,但是國內可能打不開,譯者注) + + +🤗 Transformers 庫中最基本的對象是 **pipeline()** 函數。它將模型與其必要的預處理和後處理步驟連接起來,使我們能夠通過直接輸入任何文本並獲得最終的答案: + +```python +from transformers import pipeline + +classifier = pipeline("sentiment-analysis") +classifier("I've been waiting for a HuggingFace course my whole life.") +``` +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}] +``` + + +我們也可以多傳幾句! +```python +classifier( + ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!"] +) +``` +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}, + {'label': 'NEGATIVE', 'score': 0.9994558095932007}] +``` +默認情況下,此pipeline選擇一個特定的預訓練模型,該模型已針對英語情感分析進行了微調。創建**分類器**對象時,將下載並緩存模型。如果您重新運行該命令,則將使用緩存的模型,無需再次下載模型。 + +將一些文本傳遞到pipeline時涉及三個主要步驟: + +1. 文本被預處理為模型可以理解的格式。 +2. 預處理的輸入被傳遞給模型。 +3. 模型處理後輸出最終人類可以理解的結果。 + +目前[可用的一些pipeline](https://huggingface.co/transformers/main_classes/pipelines.html)是: + +* **特徵提取**(獲取文本的向量表示) +* **填充空缺** +* **ner**(命名實體識別) +* **問答** +* **情感分析** +* **文本摘要** +* **文本生成** +* **翻譯** +* **零樣本分類** + +讓我們來看看其中的一些吧! + +## 零樣本分類 +我們將首先處理一項非常具挑戰性的任務,我們需要對尚未標記的文本進行分類。這是實際項目中的常見場景,因為注釋文本通常很耗時並且需要領域專業知識。對於這項任務**zero-shot-classification**pipeline非常強大:它允許您直接指定用於分類的標籤,因此您不必依賴預訓練模型的標籤。下面的模型展示瞭如何使用這兩個標籤將句子分類為正面或負面——但也可以使用您喜歡的任何其他標籤集對文本進行分類。 + +```python +from transformers import pipeline + +classifier = pipeline("zero-shot-classification") +classifier( + "This is a course about the Transformers library", + candidate_labels=["education", "politics", "business"], +) +``` +```python out +{'sequence': 'This is a course about the Transformers library', + 'labels': ['education', 'business', 'politics'], + 'scores': [0.8445963859558105, 0.111976258456707, 0.043427448719739914]} +``` + +此pipeline稱為zero-shot,因為您不需要對數據上的模型進行微調即可使用它。它可以直接返回您想要的任何標籤列表的概率分數! + +✏️**快來試試吧!**使用您自己的序列和標籤,看看模型的行為。 + + +## 文本生成 +現在讓我們看看如何使用pipeline來生成一些文本。這裡的主要使用方法是您提供一個提示,模型將通過生成剩餘的文本來自動完成整段話。這類似於許多手機上的預測文本功能。文本生成涉及隨機性,因此如果您沒有得到相同的如下所示的結果,這是正常的。 + +```python +from transformers import pipeline + +generator = pipeline("text-generation") +generator("In this course, we will teach you how to") +``` +```python out +[{'generated_text': 'In this course, we will teach you how to understand and use ' + 'data flow and data interchange when handling user data. We ' + 'will be working with one or more of the most commonly used ' + 'data flows — data flows of various types, as seen by the ' + 'HTTP'}] +``` +您可以使用參數 **num_return_sequences** 控制生成多少個不同的序列,並使用參數 **max_length** 控制輸出文本的總長度。 + + +✏️**快來試試吧!**使用 num_return_sequences 和 max_length 參數生成兩個句子,每個句子 15 個單詞。 + + +## 在pipeline中使用 Hub 中的其他模型 +前面的示例使用了默認模型,但您也可以從 Hub 中選擇特定模型以在特定任務的pipeline中使用 - 例如,文本生成。轉到[模型中心(hub)](https://huggingface.co/models)並單擊左側的相應標籤將會只顯示該任務支持的模型。[例如這樣](https://huggingface.co/models?pipeline_tag=text-generation)。 + +讓我們試試 [**distilgpt2**](https://huggingface.co/distilgpt2) 模型吧!以下是如何在與以前相同的pipeline中加載它: + +```python +from transformers import pipeline + +generator = pipeline("text-generation", model="distilgpt2") +generator( + "In this course, we will teach you how to", + max_length=30, + num_return_sequences=2, +) +``` +```python out +[{'generated_text': 'In this course, we will teach you how to manipulate the world and ' + 'move your mental and physical capabilities to your advantage.'}, + {'generated_text': 'In this course, we will teach you how to become an expert and ' + 'practice realtime, and with a hands on experience on both real ' + 'time and real'}] +``` +您可以通過單擊語言標籤來篩選搜索結果,然後選擇另一種文本生成模型的模型。模型中心(hub)甚至包含支持多種語言的多語言模型。 + +通過單擊選擇模型後,您會看到有一個小組件,可讓您直接在線試用。通過這種方式,您可以在下載之前快速測試模型的功能。 + +✏️**快來試試吧!**使用標籤篩選查找另一種語言的文本生成模型。使用小組件測試並在pipeline中使用它! + + +## 推理 API +所有模型都可以使用 Inference API 直接通過瀏覽器進行測試,該 API 可在 [Hugging Face 網站](https://huggingface.co/)上找到。通過輸入自定義文本並觀察模型的輸出,您可以直接在此頁面上使用模型。 + +小組件形式的推理 API 也可作為付費產品使用,如果您的工作流程需要它,它會派上用場。有關更多詳細信息,請參閱[定價頁面](https://huggingface.co/pricing)。 + +## Mask filling +您將嘗試的下一個pipeline是 **fill-mask**。此任務的想法是填充給定文本中的空白: +```python +from transformers import pipeline + +unmasker = pipeline("fill-mask") +unmasker("This course will teach you all about models.", top_k=2) +``` +```python out +[{'sequence': 'This course will teach you all about mathematical models.', + 'score': 0.19619831442832947, + 'token': 30412, + 'token_str': ' mathematical'}, + {'sequence': 'This course will teach you all about computational models.', + 'score': 0.04052725434303284, + 'token': 38163, + 'token_str': ' computational'}] +``` +**top_k** 參數控制要顯示的結果有多少種。請注意,這裡模型填充了特殊的< **mask** >詞,它通常被稱為掩碼標記。其他掩碼填充模型可能有不同的掩碼標記,因此在探索其他模型時要驗證正確的掩碼字是什麼。檢查它的一種方法是查看小組件中使用的掩碼。 + + +✏️**快來試試吧!**在 Hub 上搜索基於 bert 的模型並在推理 API 小組件中找到它的掩碼。這個模型對上面pipeline示例中的句子預測了什麼? + + +## 命名實體識別 +命名實體識別 (NER) 是一項任務,其中模型必須找到輸入文本的哪些部分對應於諸如人員、位置或組織之類的實體。讓我們看一個例子: +```python +from transformers import pipeline + +ner = pipeline("ner", grouped_entities=True) +ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") +``` +```python out +[{'entity_group': 'PER', 'score': 0.99816, 'word': 'Sylvain', 'start': 11, 'end': 18}, + {'entity_group': 'ORG', 'score': 0.97960, 'word': 'Hugging Face', 'start': 33, 'end': 45}, + {'entity_group': 'LOC', 'score': 0.99321, 'word': 'Brooklyn', 'start': 49, 'end': 57} +] +``` +在這裡,模型正確地識別出 Sylvain 是一個人 (PER),Hugging Face 是一個組織 (ORG),而布魯克林是一個位置 (LOC)。 + +我們在pipeline創建函數中傳遞選項 **grouped_entities=True** 以告訴pipeline將對應於同一實體的句子部分重新組合在一起:這裡模型正確地將「Hugging」和「Face」分組為一個組織,即使名稱由多個詞組成。事實上,正如我們即將在下一章看到的,預處理甚至會將一些單詞分成更小的部分。例如,**Sylvain** 分割為了四部分:**S、##yl、##va** 和 **##in**。在後處理步驟中,pipeline成功地重新組合了這些部分。 + + +✏️**快來試試吧!**在模型中心(hub)搜索能夠用英語進行詞性標注(通常縮寫為 POS)的模型。這個模型對上面例子中的句子預測了什麼? + + +## 問答系統 +問答pipeline使用來自給定上下文的信息回答問題: +```python +from transformers import pipeline + +question_answerer = pipeline("question-answering") +question_answerer( + question="Where do I work?", + context="My name is Sylvain and I work at Hugging Face in Brooklyn", +) +``` +```python out +{'score': 0.6385916471481323, 'start': 33, 'end': 45, 'answer': 'Hugging Face'} +klyn", +) + +``` +請注意,此pipeline通過從提供的上下文中提取信息來工作;它不會憑空生成答案。 + +## 文本摘要 +文本摘要是將文本縮減為較短文本的任務,同時保留文本中的主要(重要)信息。下面是一個例子: + +```python +from transformers import pipeline + +summarizer = pipeline("summarization") +summarizer( + """ + America has changed dramatically during recent years. Not only has the number of + graduates in traditional engineering disciplines such as mechanical, civil, + electrical, chemical, and aeronautical engineering declined, but in most of + the premier American universities engineering curricula now concentrate on + and encourage largely the study of engineering science. As a result, there + are declining offerings in engineering subjects dealing with infrastructure, + the environment, and related issues, and greater concentration on high + technology subjects, largely supporting increasingly complex scientific + developments. While the latter is important, it should not be at the expense + of more traditional engineering. + + Rapidly developing economies such as China and India, as well as other + industrial countries in Europe and Asia, continue to encourage and advance + the teaching of engineering. Both China and India, respectively, graduate + six and eight times as many traditional engineers as does the United States. + Other industrial countries at minimum maintain their output, while America + suffers an increasingly serious decline in the number of engineering graduates + and a lack of well-educated engineers. +""" +) +``` +```python out +[{'summary_text': ' America has changed dramatically during recent years . The ' + 'number of engineering graduates in the U.S. has declined in ' + 'traditional engineering disciplines such as mechanical, civil ' + ', electrical, chemical, and aeronautical engineering . Rapidly ' + 'developing economies such as China and India, as well as other ' + 'industrial countries in Europe and Asia, continue to encourage ' + 'and advance engineering .'}] +``` +與文本生成一樣,您指定結果的 **max_length** 或 **min_length**。 + +## 翻譯 +對於翻譯,如果您在任務名稱中提供語言對(例如「**translation_en_to_fr**」),則可以使用默認模型,但最簡單的方法是在[模型中心(hub)](https://huggingface.co/models)選擇要使用的模型。在這裡,我們將嘗試從法語翻譯成英語: + +```python +from transformers import pipeline + +translator = pipeline("translation", model="Helsinki-NLP/opus-mt-fr-en") +translator("Ce cours est produit par Hugging Face.") +``` +```python out +[{'translation_text': 'This course is produced by Hugging Face.'}] + +``` + +與文本生成和摘要一樣,您可以指定結果的 **max_length** 或 **min_length**。 + + + +✏️**快來試試吧!**搜索其他語言的翻譯模型,嘗試將前一句翻譯成幾種不同的語言。 + + + +到目前為止顯示的pipeline主要用於演示目的。它們是為特定任務而編程的,不能對他們進行自定義的修改。在下一章中,您將瞭解 **pipeline()** 函數內部的內容以及如何進行自定義的修改。 \ No newline at end of file diff --git a/chapters/zh-TW/chapter1/4.mdx b/chapters/zh-TW/chapter1/4.mdx new file mode 100644 index 000000000..cb531b1b5 --- /dev/null +++ b/chapters/zh-TW/chapter1/4.mdx @@ -0,0 +1,177 @@ +# Transformers 是如何工作的? + + + +在本節中,我們將深入瞭解 Transformer 模型的架構。 + +## 一點Transformers的發展歷史 + +以下是 Transformer 模型(簡短)歷史中的一些關鍵節點: + +
+A brief chronology of Transformers models. + +
+ +[Transformer 架構](https://arxiv.org/abs/1706.03762) 於 2017 年 6 月推出。原本研究的重點是翻譯任務。隨後推出了幾個有影響力的模型,包括 + +- **2018 年 6 月**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), 第一個預訓練的 Transformer 模型,用於各種 NLP 任務並獲得極好的結果 + +- **2018 年 10 月**: [BERT](https://arxiv.org/abs/1810.04805), 另一個大型預訓練模型,該模型旨在生成更好的句子摘要(下一章將詳細介紹!) + +- **2019 年 2 月**: [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), GPT 的改進(並且更大)版本,由於道德問題沒有立即公開發布 + +- **2019 年 10 月**: [DistilBERT](https://arxiv.org/abs/1910.01108), BERT 的提煉版本,速度提高 60%,內存減輕 40%,但仍保留 BERT 97% 的性能 + +- **2019 年 10 月**: [BART](https://arxiv.org/abs/1910.13461) 和 [T5](https://arxiv.org/abs/1910.10683), 兩個使用與原始 Transformer 模型相同架構的大型預訓練模型(第一個這樣做) + +- **2020 年 5 月**, [GPT-3](https://arxiv.org/abs/2005.14165), GPT-2 的更大版本,無需微調即可在各種任務上表現良好(稱爲零樣本學習) + +這個列表並不全面,只是爲了突出一些不同類型的 Transformer 模型。大體上,它們可以分爲三類: + +- GPT-like (也被稱作自迴歸Transformer模型) +- BERT-like (也被稱作自動編碼Transformer模型) +- BART/T5-like (也被稱作序列到序列的 Transformer模型) + +稍後我們將更深入地探討這些分類。 + +## Transformers是語言模型 + +上面提到的所有 Transformer 模型(GPT、BERT、BART、T5 等)都被訓練爲語言模型。這意味着他們已經以無監督學習的方式接受了大量原始文本的訓練。無監督學習是一種訓練類型,其中目標是根據模型的輸入自動計算的。這意味着不需要人工來標記數據! + +這種類型的模型可以對其訓練過的語言進行統計理解,但對於特定的實際任務並不是很有用。因此,一般的預訓練模型會經歷一個稱爲*遷移學習*的過程。在此過程中,模型在給定任務上以監督方式(即使用人工註釋標籤)進行微調。 + +任務的一個例子是閱讀 *n* 個單詞的句子,預測下一個單詞。這被稱爲因果語言建模,因爲輸出取決於過去和現在的輸入。 + +
+Example of causal language modeling in which the next word from a sentence is predicted. + +
+ +另一個例子是*遮罩語言建模*,該模型預測句子中的遮住的詞。 + +
+Example of masked language modeling in which a masked word from a sentence is predicted. + +
+ +## Transformer是大模型 + +除了一些特例(如 DistilBERT)外,實現更好性能的一般策略是增加模型的大小以及預訓練的數據量。 + +
+Number of parameters of recent Transformers models +
+ +不幸的是,訓練模型,尤其是大型模型,需要大量的數據,時間和計算資源。它甚至會對環境產生影響,如下圖所示。 + +
+The carbon footprint of a large language model. + +
+ + + +Transformers是由一個團隊領導的(非常大的)模型項目,該團隊試圖減少預訓練對環境的影響,通過運行大量試驗以獲得最佳超參數。 + +想象一下,如果每次一個研究團隊、一個學生組織或一家公司想要訓練一個模型,都從頭開始訓練的。這將導致巨大的、不必要的浪費! + +這就是爲什麼共享語言模型至關重要:共享經過訓練的權重,當遇見新的需求時在預訓練的權重之上進行微調,可以降低訓練模型訓練的算力和時間消耗,降低全球的總體計算成本和碳排放。 + + +## 遷移學習 + + + +*預訓練是*訓練模型前的一個操作:隨機初始化權重,在沒有任何先驗知識的情況下開始訓練。 + +
+The pretraining of a language model is costly in both time and money. + +
+ +這種預訓練通常是在非常大量的數據上進行的。因此,它需要大量的數據,而且訓練可能需要幾周的時間。 + +另一方面,*微調*是在模型經過預訓練後完成的訓練。要執行微調,首先需要獲取一個經過預訓練的語言模型,然後使用特定於任務的數據集執行額外的訓練。等等,爲什麼不直接爲最後的任務而訓練呢?有幾個原因: + +* 預訓練模型已經在與微調數據集有一些相似之處的數據集上進行了訓練。因此,微調過程能夠利用模型在預訓練期間獲得的知識(例如,對於NLP問題,預訓練模型將對您在任務中使用的語言有某種統計規律上的理解)。 +* 由於預訓練模型已經在大量數據上進行了訓練,因此微調需要更少的數據來獲得不錯的結果。 +* 出於同樣的原因,獲得好結果所需的時間和資源要少得多 + +例如,可以利用英語的預訓練過的模型,然後在arXiv語料庫上對其進行微調,從而形成一個基於科學/研究的模型。微調只需要有限的數據量:預訓練模型獲得的知識可以“遷移”到目標任務上,因此被稱爲*遷移學習*。 + +
+The fine-tuning of a language model is cheaper than pretraining in both time and money. + +
+ +因此,微調模型具有較低的時間、數據、財務和環境成本。迭代不同的微調方案也更快、更容易,因爲與完整的預訓練相比,訓練的約束更少。 + +這個過程也會比從頭開始的訓練(除非你有很多數據)取得更好的效果,這就是爲什麼你應該總是嘗試利用一個預訓練的模型--一個儘可能接近你手頭的任務的模型--並對其進行微調。 + +## 一般的體系結構 +在這一部分,我們將介紹Transformer模型的一般架構。如果你不理解其中的一些概念,不要擔心;下文將詳細介紹每個組件。 + + + +## 介紹 + +該模型主要由兩個塊組成: + +* **Encoder (左側)**: 編碼器接收輸入並構建其表示(其特徵)。這意味着對模型進行了優化,以從輸入中獲得理解。 +* **Decoder (右側)**: 解碼器使用編碼器的表示(特徵)以及其他輸入來生成目標序列。這意味着該模型已針對生成輸出進行了優化。 + +
+Architecture of a Transformers models + +
+ +這些部件中的每一個都可以獨立使用,具體取決於任務: + +* **Encoder-only models**: 適用於需要理解輸入的任務,如句子分類和命名實體識別。 +* **Decoder-only models**: 適用於生成任務,如文本生成。 +* **Encoder-decoder models** 或者 **sequence-to-sequence models**: 適用於需要根據輸入進行生成的任務,如翻譯或摘要。 + +在後面的部分中,我們將單獨地深入研究這些體系結構。 + +## 注意力層 + +Transformer模型的一個關鍵特性是*注意力層*。事實上,介紹Transformer架構的文章的標題是[“注意力就是你所需要的”](https://arxiv.org/abs/1706.03762)! 我們將在課程的後面更加深入地探索注意力層;現在,您需要知道的是,這一層將告訴模型在處理每個單詞的表示時,要特別重視您傳遞給它的句子中的某些單詞(並且或多或少地忽略其他單詞)。 + +把它放在語境中,考慮將文本從英語翻譯成法語的任務。在輸入“You like this course”的情況下,翻譯模型還需要注意相鄰的單詞“You”,以獲得單詞“like”的正確翻譯,因爲在法語中,動詞“like”的變化取決於主題。然而,句子的其餘部分對於該詞的翻譯沒有用處。同樣,在翻譯“this”時,模型也需要注意“course”一詞,因爲“this”的翻譯不同,取決於相關名詞是單數還是複數。同樣,句子中的其他單詞對於“this”的翻譯也不重要。對於更復雜的句子(以及更復雜的語法規則),模型需要特別注意可能出現在句子中更遠位置的單詞,以便正確地翻譯每個單詞。 + +同樣的概念也適用於與自然語言相關的任何任務:一個詞本身有一個含義,但這個含義受語境的影響很大,語境可以是研究該詞之前或之後的任何其他詞(或多個詞)。 + +現在您已經瞭解了注意力層的含義,讓我們更仔細地瞭解Transformer架構。 + +## 原始的結構 + +Transformer架構最初是爲翻譯而設計的。在訓練期間,編碼器接收特定語言的輸入(句子),而解碼器需要輸出對應語言的翻譯。在編碼器中,注意力層可以使用一個句子中的所有單詞(正如我們剛纔看到的,給定單詞的翻譯可以取決於它在句子中的其他單詞)。然而,解碼器是按順序工作的,並且只能注意它已經翻譯過的句子中的單詞。例如,當我們預測了翻譯目標的前三個單詞時,我們將它們提供給解碼器,然後解碼器使用編碼器的所有輸入來嘗試預測第四個單詞。 + +爲了在訓練過程中加快速度(當模型可以訪問目標句子時),解碼器會被輸入整個目標,但不允許獲取到要翻譯的單詞(如果它在嘗試預測位置2的單詞時可以訪問位置2的單詞,解碼器就會偷懶,直接輸出那個單詞,從而無法學習到正確的語言關係!)。例如,當試圖預測第4個單詞時,注意力層只能獲取位置1到3的單詞。 + +最初的Transformer架構如下所示,編碼器位於左側,解碼器位於右側: + +
+Architecture of a Transformers models + +
+ +注意,解碼器塊中的第一個注意力層關聯到解碼器的所有(過去的)輸入,但是第二注意力層使用編碼器的輸出。因此,它可以訪問整個輸入句子,以最好地預測當前單詞。這是非常有用的,因爲不同的語言可以有語法規則將單詞按不同的順序排列,或者句子後面提供的一些上下文可能有助於確定給定單詞的最佳翻譯。 + +也可以在編碼器/解碼器中使用*注意力遮罩層*,以防止模型注意某些特殊單詞。例如,在批處理句子時,填充特殊詞使所有句子的長度一致。 + +## 架構與參數 + +在本課程中,當我們深入探討Transformers模型時,您將看到 +架構、參數和模型 +。 這些術語的含義略有不同: + +* **架構**: 這是模型的骨架 -- 每個層的定義以及模型中發生的每個操作。 +* **Checkpoints**: 這些是將在給架構中結構中加載的權重。 +* **模型**: 這是一個籠統的術語,沒有“架構”或“參數”那麼精確:它可以指兩者。爲了避免歧義,本課程使用將使用架構和參數。 + +例如,BERT是一個架構,而 `bert-base-cased`, 這是谷歌團隊爲BERT的第一個版本訓練的一組權重參數,是一個參數。我們可以說“BERT模型”和"`bert-base-cased`模型." diff --git a/chapters/zh-TW/chapter1/5.mdx b/chapters/zh-TW/chapter1/5.mdx new file mode 100644 index 000000000..acd28f78c --- /dev/null +++ b/chapters/zh-TW/chapter1/5.mdx @@ -0,0 +1,22 @@ +# “編碼器”模型 + + + + + +“編碼器”模型指僅使用編碼器的Transformer模型。在每個階段,注意力層都可以獲取初始句子中的所有單詞。這些模型通常具有“雙向”注意力,被稱爲自編碼模型。 + +這些模型的預訓練通常圍繞着以某種方式破壞給定的句子(例如:通過隨機遮蓋其中的單詞),並讓模型尋找或重建給定的句子。 + +“編碼器”模型最適合於需要理解完整句子的任務,例如:句子分類、命名實體識別(以及更普遍的單詞分類)和閱讀理解後回答問題。 + +該系列模型的典型代表有: + +- [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) diff --git a/chapters/zh-TW/chapter1/6.mdx b/chapters/zh-TW/chapter1/6.mdx new file mode 100644 index 000000000..997f206bd --- /dev/null +++ b/chapters/zh-TW/chapter1/6.mdx @@ -0,0 +1,22 @@ +# 「解碼器」模型 + + + + + +「解碼器」模型通常指僅使用解碼器的 Transformer 模型。在每個階段,對於給定的單詞,注意力層只能獲取到句子中位於將要預測單詞前面的單詞。這些模型通常被稱爲自迴歸模型。 + +「解碼器」模型的預訓練通常圍繞預測句子中的下一個單詞進行。 + +這些模型最適合於涉及文本生成的任務。 + +該系列模型的典型代表有: + + +- [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) +- [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) diff --git a/chapters/zh-TW/chapter1/7.mdx b/chapters/zh-TW/chapter1/7.mdx new file mode 100644 index 000000000..1566714fa --- /dev/null +++ b/chapters/zh-TW/chapter1/7.mdx @@ -0,0 +1,21 @@ +# 序列到序列模型 + + + + + +編碼器-解碼器模型(也稱爲序列到序列模型)同時使用 Transformer 架構的編碼器和解碼器兩個部分。在每個階段,編碼器的注意力層可以訪問初始句子中的所有單詞,而解碼器的注意力層只能訪問位於輸入中將要預測單詞前面的單詞。 + +這些模型的預訓練可以使用訓練編碼器或解碼器模型的方式來完成,但通常涉及更復雜的內容。例如,[T5](https://huggingface.co/t5-base)通過將文本的隨機跨度(可以包含多個單詞)替換爲單個特殊單詞來進行預訓練,然後目標是預測該掩碼單詞替換的文本。 + +序列到序列模型最適合於圍繞根據給定輸入生成新句子的任務,如摘要、翻譯或生成性問答。 + +該系列模型的典型代表有: + +- [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) diff --git a/chapters/zh-TW/chapter1/8.mdx b/chapters/zh-TW/chapter1/8.mdx new file mode 100644 index 000000000..e6c3eeca5 --- /dev/null +++ b/chapters/zh-TW/chapter1/8.mdx @@ -0,0 +1,31 @@ +# Bias and limitations + + + +如果您打算在正式的項目中使用經過預訓練或經過微調的模型。請注意:雖然這些模型是很強大,但它們也有侷限性。其中最大的一個問題是,爲了對大量數據進行預訓練,研究人員通常會蒐集所有他們能找到的內容,中間可能夾帶一些意識形態或者價值觀的刻板印象。 + +爲了快速解釋清楚這個問題,讓我們回到一個使用 BERT 模型的 pipeline 的例子: + +```python +from transformers import pipeline + +unmasker = pipeline("fill-mask", model="bert-base-uncased") +result = unmasker("This man works as a [MASK].") +print([r["token_str"] for r in result]) + +result = unmasker("This woman works as a [MASK].") +print([r["token_str"] for r in result]) +``` + +```python out +['lawyer', 'carpenter', 'doctor', 'waiter', 'mechanic'] +['nurse', 'waitress', 'teacher', 'maid', 'prostitute'] +``` +當要求模型填寫這兩句話中缺少的單詞時,模型給出的答案中,只有一個與性別無關(服務生/女服務生)。其他職業通常與某一特定性別相關,妓女最終進入了模型中與「女人」和「工作」相關的前五位。儘管 BERT 是使用經過篩選和清洗後,明顯中立的數據集上建立的的 Transformer 模型,而不是通過從互聯網上搜集數據(它是在[Wikipedia 英文](https://huggingface.co/datasets/wikipedia)和[BookCorpus](https://huggingface.co/datasets/bookcorpus)數據集)。 + +因此,當您使用這些工具時,您需要記住,使用的原始模型的時候,很容易生成性別歧視、種族主義或恐同內容。這種固有偏見不會隨着微調模型而使消失。 \ No newline at end of file diff --git a/chapters/zh-TW/chapter1/9.mdx b/chapters/zh-TW/chapter1/9.mdx new file mode 100644 index 000000000..d24ba6139 --- /dev/null +++ b/chapters/zh-TW/chapter1/9.mdx @@ -0,0 +1,16 @@ +# 總結 + + + +在本章中,您瞭解瞭如何使用來自🤗Transformers 的函數 pipeline() 處理不同的 NLP 任務。您還了解了如何在模型中心(hub)中搜索和使用模型,以及如何使用推理API直接在瀏覽器中測試模型。 + +我們討論了Transformer模型如何在應用層上工作,並討論了遷移學習和微調的重要性。您可以使用完整的體系結構,也可以僅使用編碼器或解碼器,具體取決於您要解決的任務類型。下表總結了這一點: + +| 模型 | 示例 | 任務| +| ---- | ---- |----| +| 編碼器 | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa |句子分類、命名實體識別、從文本中提取答案| +| 解碼器 | CTRL, GPT, GPT-2, Transformer XL |文本生成| +| 編碼器-解碼器 | BART, T5, Marian, mBART |文本摘要、翻譯、生成問題的回答| \ No newline at end of file diff --git a/chapters/zh-TW/chapter2/1.mdx b/chapters/zh-TW/chapter2/1.mdx new file mode 100644 index 000000000..70ce241f2 --- /dev/null +++ b/chapters/zh-TW/chapter2/1.mdx @@ -0,0 +1,23 @@ +# 本章簡介 + + + +正如你在 [Chapter 1](/course/chapter1),中看到的那樣,Transformers 模型通常非常大。對於數以百萬計到數千萬計數十億的參數,訓練和部署這些模型是一項複雜的任務。此外,由於幾乎每天都在發佈新模型,而且每種模型都有自己的實現,因此嘗試它們絕非易事。 + +創建 🤗Transformers 庫就是為了解決這個問題。它的目標是提供一個API,通過它可以加載、訓練和保存任何Transformer模型。這個庫的主要特點是: +- **易於使用**:下載、加載和使用最先進的NLP模型進行推理只需兩行代碼即可完成。 +- **靈活**:所有型號的核心都是簡單的 PyTorch **nn.Module** 或者 TensorFlow **tf.kears.Model**,可以像它們各自的機器學習(ML)框架中的任何其他模型一樣進行處理。 +- **簡單**:當前位置整個庫幾乎沒有任何摘要。“都在一個文件中”是一個核心概念:模型的正向傳遞完全定義在一個文件中,因此代碼本身是可以理解的,並且是可以破解的。 + +最後一個特性使🤗 Transformers與其他ML庫截然不同。這些模型不是基於通過文件共享的模塊構建的;相反,每一個模型都有自己的菜單。除了使模型更加容易接受和更容易理解,這還允許你輕鬆地在一個模型上實驗,而且不影響其他模型。 + +本章將從一個端到端的示例開始,在該示例中,我們一起使用模型和tokenizer分詞器來複制[Chapter 1](/course/chapter1)中引入的函數 pipeline(). 接下來,我們將討論模型API:我們將深入研究模型和配置類,並向您展示如何加載模型以及如何將數值輸入處理為輸出預測。 + +然後我們來看看標記器API,它是 pipeline() 函數的另一個主要組件。它是作用分詞器負責第一個和最後一個處理步驟,處理從文本到神經網絡數字輸入的轉換,以及在需要時轉換回文本。最後,我們將向您展示如何處理在一個準備好的批處理中通過一個模型發送多個句子的問題,然後詳細介紹 pipeline() 函數。 + + +⚠️ 為了從模型集線器和 🤗Transformers 的所有可用功能中獲益,我們建議creating an account. + \ No newline at end of file diff --git a/chapters/zh-TW/chapter2/2.mdx b/chapters/zh-TW/chapter2/2.mdx new file mode 100644 index 000000000..fcacec271 --- /dev/null +++ b/chapters/zh-TW/chapter2/2.mdx @@ -0,0 +1,359 @@ + + +# 管道的內部 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + +這是第一部分,根據您使用 PyTorch 或者 TensorFlow,內容略有不同。點擊標題上方的平臺,選一個您喜歡的吧! + + +{#if fw === 'pt'} + +{:else} + +{/if} + +讓我們從一個完整的示例開始,看看在[Chapter 1](/course/chapter1)中執行以下代碼時在幕後發生了什麼 + +```python +from transformers import pipeline + +classifier = pipeline("sentiment-analysis") +classifier( + [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", + ] +) +``` + +獲得: + +```python out +[{'label': 'POSITIVE', 'score': 0.9598047137260437}, + {'label': 'NEGATIVE', 'score': 0.9994558095932007}] +``` + +正如我們在[Chapter 1](/course/chapter1)中看到的,此管道將三個步驟組合在一起:預處理、通過模型傳遞輸入和後處理: + +
+The full NLP pipeline: tokenization of text, conversion to IDs, and inference through the Transformer model and the model head. + +
+ +讓我們快速瀏覽一下這些內容。 + +## 使用分詞器進行預處理 + +與其他神經網絡一樣,Transformer 模型無法直接處理原始文本, 因此我們管道的第一步是將文本輸入轉換為模型能夠理解的數字。 為此,我們使用*tokenizer*(標記器),負責: + +- 將輸入拆分為單詞、子單詞或符號(如標點符號),稱為標記(*token*) +- 將每個標記(token)映射到一個整數 +- 添加可能對模型有用的其他輸入 + +所有這些預處理都需要以與模型預訓練時完全相同的方式完成,因此我們首先需要從[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)),我們運行以下程序: + +```python +from transformers import AutoTokenizer + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` + +一旦我們有了標記器,我們就可以直接將我們的句子傳遞給它,然後我們就會得到一本字典,它可以提供給我們的模型!剩下要做的唯一一件事就是將輸入ID列表轉換為張量。 + +您可以使用🤗 Transformers,而不必擔心哪個 ML 框架被用作後端;它可能是 PyTorch 或 TensorFlow,或 Flax。但是,Transformers型號只接受*張量*作為輸入。如果這是你第一次聽說張量,你可以把它們想象成NumPy數組。NumPy數組可以是標量(0D)、向量(1D)、矩陣(2D)或具有更多維度。它實際上是張量;其他 ML 框架的張量行為類似,通常與 NumPy 數組一樣易於實例化。 + +要指定要返回的張量類型(PyTorch、TensorFlow 或 plain NumPy),我們使用`return_tensors`參數: + +{#if fw === 'pt'} +```python +raw_inputs = [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", +] +inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="pt") +print(inputs) +``` +{:else} +```python +raw_inputs = [ + "I've been waiting for a HuggingFace course my whole life.", + "I hate this so much!", +] +inputs = tokenizer(raw_inputs, padding=True, truncation=True, return_tensors="tf") +print(inputs) +``` +{/if} + +現在不要擔心填充和截斷;我們稍後會解釋這些。這裡要記住的主要事情是,您可以傳遞一個句子或一組句子,還可以指定要返回的張量類型(如果沒有傳遞類型,您將得到一組列表)。 + +{#if fw === 'pt'} + +以下是PyTorch張量的結果: + +```python out +{ + 'input_ids': tensor([ + [ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012, 102], + [ 101, 1045, 5223, 2023, 2061, 2172, 999, 102, 0, 0, 0, 0, 0, 0, 0, 0] + ]), + '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] + ]) +} +``` +{:else} + +以下是 TensorFlow 張量的結果: + +```python out +{ + 'input_ids': , + 'attention_mask': +} +``` +{/if} + +輸出本身是一個包含兩個鍵的字典,`input_ids`和`attention_mask`。`input_ids`包含兩行整數(每個句子一行),它們是每個句子中標記的唯一標記(token)。我們將在本章後面解釋什麼是`attention_mask`。 + +## 瀏覽模型 + +{#if fw === 'pt'} +我們可以像使用標記器一樣下載預訓練模型。🤗 Transformers提供了一個`AutoModel`類,該類還具有`from_pretrained()`方法: + +```python +from transformers import AutoModel + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = AutoModel.from_pretrained(checkpoint) +``` +{:else} +我們可以像使用標記器一樣下載預訓練模型。🤗 Transformers提供了一個`TFAutoModel`類,該類還具有`from_pretrained()`方法: + +```python +from transformers import TFAutoModel + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = TFAutoModel.from_pretrained(checkpoint) +``` +{/if} + +在這個代碼片段中,我們下載了之前在管道中使用的相同檢查點(它實際上應該已經被緩存),並用它實例化了一個模型。 + +這個架構只包含基本轉換器模塊:給定一些輸入,它輸出我們將調用的內容*隱藏狀態(hidden states)*,亦稱*特徵(features)*。對於每個模型輸入,我們將檢索一個高維向量,表示**Transformer模型對該輸入的上下文理解**。 + +如果這不合理,不要擔心。我們以後再解釋。 + +雖然這些隱藏狀態本身可能很有用,但它們通常是模型另一部分(稱為*頭部(head)*)的輸入。 在[Chapter 1](/course/chapter1)中,可以使用相同的體系結構執行不同的任務,但這些任務中的每個任務都有一個與之關聯的不同頭。 + +### 高維向量? + +Transformers 模塊的向量輸出通常較大。它通常有三個維度: + +- **Batch size**: 一次處理的序列數(在我們的示例中為2)。 +- **Sequence length**: 序列的數值表示的長度(在我們的示例中為16)。 +- **Hidden size**: 每個模型輸入的向量維度。 + +由於最後一個值,它被稱為「高維」。隱藏的大小可能非常大(768通常用於較小的型號,而在較大的型號中,這可能達到3072或更大)。 + +如果我們將預處理的輸入輸入到模型中,我們可以看到這一點: + +{#if fw === 'pt'} +```python +outputs = model(**inputs) +print(outputs.last_hidden_state.shape) +``` + +```python out +torch.Size([2, 16, 768]) +``` +{:else} +```py +outputs = model(inputs) +print(outputs.last_hidden_state.shape) +``` + +```python out +(2, 16, 768) +``` +{/if} + +注意🤗 Transformers 模型的輸出與`namedtuple`或詞典相似。您可以通過屬性(就像我們所做的那樣)或鍵(`輸出["last_hidden_state"]`)訪問元素,甚至可以通過索引訪問元素,前提是您確切知道要查找的內容在哪裡(`outputs[0]`)。 + +### 模型頭:數字的意義 + +模型頭將隱藏狀態的高維向量作為輸入,並將其投影到不同的維度。它們通常由一個或幾個線性層組成: + + +
+A Transformer network alongside its head. + +
+ +Transformers 模型的輸出直接發送到模型頭進行處理。 + +在此圖中,模型由其嵌入層和後續層表示。嵌入層將標記化輸入中的每個輸入ID轉換為表示關聯標記(token)的向量。後續層使用注意機制操縱這些向量,以生成句子的最終表示。 + + +🤗 Transformers中有許多不同的體系結構,每種體系結構都是圍繞處理特定任務而設計的。以下是一個非詳盡的列表: + +- `*Model` (retrieve the hidden states) +- `*ForCausalLM` +- `*ForMaskedLM` +- `*ForMultipleChoice` +- `*ForQuestionAnswering` +- `*ForSequenceClassification` +- `*ForTokenClassification` +- 以及其他 🤗 + +{#if fw === 'pt'} +對於我們的示例,我們需要一個帶有序列分類頭的模型(能夠將句子分類為肯定或否定)。因此,我們實際上不會使用`AutoModel`類,而是使用`AutoModelForSequenceClassification`: + +```python +from transformers import AutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +outputs = model(**inputs) +``` +{:else} +For our example, we will need a model with a sequence classification head (to be able to classify the sentences as positive or negative). So, we won't actually use the `TFAutoModel` class, but `TFAutoModelForSequenceClassification`: + +```python +from transformers import TFAutoModelForSequenceClassification + +checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +outputs = model(inputs) +``` +{/if} + +現在,如果我們觀察輸入的形狀,維度將低得多:模型頭將我們之前看到的高維向量作為輸入,並輸出包含兩個值的向量(每個標籤一個): + +```python +print(outputs.logits.shape) +``` + +{#if fw === 'pt'} + +```python out +torch.Size([2, 2]) +``` + +{:else} + +```python out +(2, 2) +``` + +{/if} + +因為我們只有兩個句子和兩個標籤,所以我們從模型中得到的結果是2 x 2的形狀。 + +## 對輸出進行後處理 + +我們從模型中得到的輸出值本身並不一定有意義。我們來看看, + +```python +print(outputs.logits) +``` + +{#if fw === 'pt'} +```python out +tensor([[-1.5607, 1.6123], + [ 4.1692, -3.3464]], grad_fn=) +``` +{:else} +```python out + +``` +{/if} + +我們的模型預測第一句為`[-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 + +predictions = torch.nn.functional.softmax(outputs.logits, dim=-1) +print(predictions) +``` +{:else} +```py +import tensorflow as tf + +predictions = tf.math.softmax(outputs.logits, axis=-1) +print(predictions) +``` +{/if} + +{#if fw === 'pt'} +```python out +tensor([[4.0195e-02, 9.5980e-01], + [9.9946e-01, 5.4418e-04]], grad_fn=) +``` +{:else} +```python out +tf.Tensor( +[[4.01951671e-02 9.59804833e-01] + [9.9945587e-01 5.4418424e-04]], shape=(2, 2), dtype=float32) +``` +{/if} + +現在我們可以看到,模型預測第一句為`[0.0402, 0.9598]`,第二句為`[0.9995, 0.0005]`。這些是可識別的概率分數。 + +為了獲得每個位置對應的標籤,我們可以檢查模型配置的`id2label`屬性(下一節將對此進行詳細介紹): + +```python +model.config.id2label +``` + +```python out +{0: 'NEGATIVE', 1: 'POSITIVE'} +``` + +現在我們可以得出結論,該模型預測了以下幾點: + +- 第一句:否定:0.0402,肯定:0.9598 +- 第二句:否定:0.9995,肯定:0.0005 + +我們已經成功地複製了管道的三個步驟:使用標記化器進行預處理、通過模型傳遞輸入以及後處理!現在,讓我們花一些時間深入瞭解這些步驟中的每一步。 + + + +✏️ **試試看!** 選擇兩個(或更多)你自己的文本並在管道中運行它們。然後自己複製在這裡看到的步驟,並檢查是否獲得相同的結果! + + diff --git a/chapters/zh-TW/chapter2/3.mdx b/chapters/zh-TW/chapter2/3.mdx new file mode 100644 index 000000000..b93ad440e --- /dev/null +++ b/chapters/zh-TW/chapter2/3.mdx @@ -0,0 +1,264 @@ + + +# 模型 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + +{:else} + +{/if} + +{#if fw === 'pt'} +在本節中,我們將更詳細地瞭解如何創建和使用模型。我們將使用 +AutoModel類,當您希望從檢查點實例化任何模型時,這非常方便。 + +這個AutoModel類及其所有相關項實際上是對庫中各種可用模型的簡單包裝。它是一個聰明的包裝器,因為它可以自動猜測檢查點的適當模型體系結構,然後用該體系結構實例化模型。 + +{:else} +在本節中,我們將更詳細地瞭解如何創建和使用模型。我們將使用 +AutoModel類,當您希望從檢查點實例化任何模型時,這非常方便。 + +這個AutoModel類及其所有相關項實際上是對庫中各種可用模型的簡單包裝。它是一個聰明的包裝器,因為它可以自動猜測檢查點的適當模型體系結構,然後用該體系結構實例化模型。 + +{/if} + +但是,如果您知道要使用的模型類型,則可以使用直接定義其體系結構的類。讓我們看看這是如何與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) +``` + +```python out +BertConfig { + [...] + "hidden_size": 768, + "intermediate_size": 3072, + "max_position_embeddings": 512, + "num_attention_heads": 12, + "num_hidden_layers": 12, + [...] +} +``` + +雖然您還沒有看到所有這些屬性都做了什麼,但您應該認識到其中的一些屬性:hidden_size屬性定義了hidden_狀態向量的大小,num_hidden_layers定義了Transformer模型的層數。 + +### 不同的加載方式 + +從默認配置創建模型會使用隨機值對其進行初始化: + + +{#if fw === 'pt'} +```py +from transformers import BertConfig, BertModel + +config = BertConfig() +model = BertModel(config) + +# Model is randomly initialized! +``` +{:else} +```py +from transformers import BertConfig, TFBertModel + +config = BertConfig() +model = TFBertModel(config) + +# Model is randomly initialized! +``` +{/if} + + +該模型可以在這種狀態下使用,但會輸出胡言亂語;首先需要對其進行訓練。我們可以根據手頭的任務從頭開始訓練模型,但正如您在 +[Chapter 1](/course/chapter1) +,這將需要很長的時間和大量的數據,並將產生不可忽視的環境影響。為了避免不必要的重複工作,必須能夠共享和重用已經訓練過的模型。 + + +加載已經訓練過的Transformers模型很簡單-我們可以使用from_pretrained() +方法: + +{#if fw === 'pt'} +```py +from transformers import BertModel + +model = BertModel.from_pretrained("bert-base-cased") +``` + +正如您之前看到的,我們可以用等效的AutoModel類替換Bert模型。從現在開始,我們將這樣做,因為這會產生檢查點不可知的代碼;如果您的代碼適用於一個檢查點,那麼它應該與另一個檢查點無縫地工作。即使體系結構不同,這也適用,只要檢查點是針對類似任務(例如,情緒分析任務)訓練的。 + +{:else} +```py +from transformers import TFBertModel + +model = TFBertModel.from_pretrained("bert-base-cased") +``` + +正如您之前看到的,我們可以用等效的AutoModel類替換Bert模型。從現在開始,我們將這樣做,因為這會產生檢查點不可知的代碼;如果您的代碼適用於一個檢查點,那麼它應該與另一個檢查點無縫地工作。即使體系結構不同,這也適用,只要檢查點是針對類似任務(例如,情緒分析任務)訓練的。 + +{/if} + +在上面的代碼示例中,我們沒有使用BertConfig + +,而是通過Bert base cased標識符加載了一個預訓練模型。這是一個模型檢查點,由BERT的作者自己訓練;您可以在 +[model card](https://huggingface.co/bert-base-cased)中找到更多細節. + + + +該模型現在使用檢查點的所有權重進行初始化。它可以直接用於對訓練過的任務進行推理,也可以對新任務進行微調。通過預先訓練重量而不是從頭開始的訓練,我們可以很快取得好的效果。 + + + +權重已下載並緩存在緩存文件夾中(因此將來對from_pretrained()方法的調用將不會重新下載它們)默認為 +~/.cache/huggingface/transformers +. 您可以通過設置 +HF_HOME +環境變量來自定義緩存文件夾。 + + + +用於加載模型的標識符可以是模型中心Hub上任何模型的標識符,只要它與BERT體系結構兼容。可以找到可用的BERT檢查點的完整列表 +[here](https://huggingface.co/models?filter=bert) +. +### 保存模型 + +保存模型和加載模型一樣簡單--我們使用 +save_pretrained() +方法,類似於 +from_pretrained() +方法: + +```py +model.save_pretrained("directory_on_my_computer") +``` + +這會將兩個文件保存到磁盤: + +{#if fw === 'pt'} +``` +ls directory_on_my_computer + +config.json pytorch_model.bin +``` +{:else} +``` +ls directory_on_my_computer + +config.json tf_model.h5 +``` +{/if} + +如果你看一下 +config.json +文件,您將識別構建模型體系結構所需的屬性。該文件還包含一些元數據,例如檢查點的來源以及上次保存檢查點時使用的🤗 Transformers版本。 + +{#if fw === 'pt'} +這個 *pytorch_model.bin* 文件就是眾所周知的*state dictionary*; 它包含模型的所有權重。這兩個文件齊頭並進;配置是瞭解模型體系結構所必需的,而模型權重是模型的參數。 + +{:else} +這個 *pytorch_model.bin* 文件就是眾所周知的*state dictionary*; 它包含模型的所有權重。這兩個文件齊頭並進;配置是瞭解模型體系結構所必需的,而模型權重是模型的參數。 + +{/if} + +### 使用Transformers模型進行推理 + +既然您知道了如何加載和保存模型,那麼讓我們嘗試使用它進行一些預測。Transformer模型只能處理數字——分詞器生成的數字。但在我們討論標記化器之前,讓我們先探討模型接受哪些輸入。 + +標記化器可以將輸入轉換為適當的框架張量,但為了幫助您瞭解發生了什麼,我們將快速瞭解在將輸入發送到模型之前必須做什麼。 + +假設我們有幾個序列: + +```py +sequences = ["Hello!", "Cool.", "Nice!"] +``` + +分詞器將這些轉換為詞彙表索引,通常稱為 +input IDs +. 每個序列現在都是一個數字列表!結果是: + +```py no-format +encoded_sequences = [ + [101, 7592, 999, 102], + [101, 4658, 1012, 102], + [101, 3835, 999, 102], +] +``` + +這是一個編碼序列列表:一個列表列表。張量只接受矩形(想想矩陣)。此“數組”已為矩形,因此將其轉換為張量很容易: + +{#if fw === 'pt'} +```py +import torch + +model_inputs = torch.tensor(encoded_sequences) +``` +{:else} +```py +import tensorflow as tf + +model_inputs = tf.constant(encoded_sequences) +``` +{/if} + +### 使用張量作為模型的輸入 + + + +在模型中使用張量非常簡單-我們只需將輸入稱為模型: + + +```python +output = model(model_inputs) +``` + + + +雖然模型接受許多不同的參數,但只需要 +input IDs。我們稍後將解釋其他參數的作用以及何時需要它們,但首先我們需要更仔細地瞭解 +Transformer模型可以理解的輸入的標記 diff --git a/chapters/zh-TW/chapter2/4.mdx b/chapters/zh-TW/chapter2/4.mdx new file mode 100644 index 000000000..93bb26e65 --- /dev/null +++ b/chapters/zh-TW/chapter2/4.mdx @@ -0,0 +1,239 @@ + + +# 標記器(Tokenizer) + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + + +標記器(Tokenizer)是 NLP 管道的核心組件之一。它們有一個目的:將文本轉換為模型可以處理的數據。模型只能處理數字,因此標記器(Tokenizer)需要將我們的文本輸入轉換為數字數據。在本節中,我們將確切地探討標記化管道中發生的事情。 + +在 NLP 任務中,通常處理的數據是原始文本。這是此類文本的示例 + +``` +Jim Henson was a puppeteer +``` + +但是,模型只能處理數字,因此我們需要找到一種將原始文本轉換為數字的方法。這就是標記器(tokenizer)所做的,並且有很多方法可以解決這個問題。目標是找到最有意義的表示——即對模型最有意義的表示——並且如果可能的話,找到最小的表示。 + +讓我們看一下標記化算法的一些示例,並嘗試回答您可能對標記化提出的一些問題。 + +## 基於詞的(Word-based) + + + +想到的第一種標記器是基於詞的(_word-based_).它通常很容易設置和使用,只需幾條規則,並且通常會產生不錯的結果。例如,在下圖中,目標是將原始文本拆分為單詞併為每個單詞找到一個數字表示: + +
+ An example of word-based tokenization. + +
+ +有多種方法可以拆分文本。例如,我們可以通過應用Python的`split()`函數,使用空格將文本標記為單詞: + +```py +tokenized_text = "Jim Henson was a puppeteer".split() +print(tokenized_text) +``` + +```python out +['Jim', 'Henson', 'was', 'a', 'puppeteer'] +``` + +還有一些單詞標記器的變體,它們具有額外的標點符號規則。使用這種標記器,我們最終可以得到一些非常大的“詞彙表”,其中詞彙表由我們在語料庫中擁有的獨立標記的總數定義。 + +每個單詞都分配了一個 ID,從 0 開始一直到詞彙表的大小。該模型使用這些 ID 來識別每個單詞。 + +如果我們想用基於單詞的標記器(tokenizer)完全覆蓋一種語言,我們需要為語言中的每個單詞都有一個標識符,這將生成大量的標記。例如,英語中有超過 500,000 個單詞,因此要構建從每個單詞到輸入 ID 的映射,我們需要跟蹤這麼多 ID。此外,像“dog”這樣的詞與“dogs”這樣的詞的表示方式不同,模型最初無法知道“dog”和“dogs”是相似的:它會將這兩個詞識別為不相關。這同樣適用於其他相似的詞,例如“run”和“running”,模型最初不會認為它們是相似的。 + +最後,我們需要一個自定義標記(token)來表示不在我們詞彙表中的單詞。這被稱為“未知”標記(token),通常表示為“[UNK]”或"<unk>"。如果你看到標記器產生了很多這樣的標記,這通常是一個不好的跡象,因為它無法檢索到一個詞的合理表示,並且你會在這個過程中丟失信息。製作詞彙表時的目標是以這樣一種方式進行,即標記器將盡可能少的單詞標記為未知標記。 + +減少未知標記數量的一種方法是使用更深一層的標記器(tokenizer),即基於字符的(_character-based_)標記器(tokenizer)。 + +## 基於字符(Character-based) + + + +基於字符的標記器(tokenizer)將文本拆分為字符,而不是單詞。這有兩個主要好處: + +- 詞彙量要小得多。 +- 詞彙外(未知)標記(token)要少得多,因為每個單詞都可以從字符構建。 + +但是這裡也出現了一些關於空格和標點符號的問題: + +
+ An example of character-based tokenization. + +
+ +這種方法也不是完美的。由於現在表示是基於字符而不是單詞,因此人們可能會爭辯說,從直覺上講,它的意義不大:每個字符本身並沒有多大意義,而單詞就是這種情況。然而,這又因語言而異;例如,在中文中,每個字符比拉丁語言中的字符包含更多的信息。 + +另一件要考慮的事情是,我們的模型最終會處理大量的詞符(token):雖然使用基於單詞的標記器(tokenizer),單詞只會是單個標記,但當轉換為字符時,它很容易變成 10 個或更多的詞符(token)。 + +為了兩全其美,我們可以使用結合這兩種方法的第三種技術:*子詞標記化(subword tokenization)*。 + +## 子詞標記化 + + + +子詞分詞算法依賴於這樣一個原則,即不應將常用詞拆分為更小的子詞,而應將稀有詞分解為有意義的子詞。 + +例如,“annoyingly”可能被認為是一個罕見的詞,可以分解為“annoying”和“ly”。這兩者都可能作為獨立的子詞出現得更頻繁,同時“annoyingly”的含義由“annoying”和“ly”的複合含義保持。 + +這是一個示例,展示了子詞標記化算法如何標記序列“Let's do tokenization!”: + +
+ A subword tokenization algorithm. + +
+ +這些子詞最終提供了很多語義含義:例如,在上面的示例中,“tokenization”被拆分為“token”和“ization”,這兩個具有語義意義同時節省空間的詞符(token)(只需要兩個標記(token)代表一個長詞)。這使我們能夠對較小的詞彙表進行相對較好的覆蓋,並且幾乎沒有未知的標記 + +這種方法在土耳其語等粘著型語言(agglutinative languages)中特別有用,您可以通過將子詞串在一起來形成(幾乎)任意長的複雜詞。 + +### 還有更多! + +不出所料,還有更多的技術。僅舉幾例: + +- Byte-level BPE, 用於 GPT-2 +- WordPiece, 用於 BERT +- SentencePiece or Unigram, 用於多個多語言模型 + +您現在應該對標記器(tokenizers)的工作原理有足夠的瞭解,以便開始使用 API。 + +## 加載和保存 + +加載和保存標記器(tokenizer)就像使用模型一樣簡單。實際上,它基於相同的兩種方法: `from_pretrained()` 和 `save_pretrained()` 。這些方法將加載或保存標記器(tokenizer)使用的算法(有點像*建築學(architecture)*的模型)以及它的詞彙(有點像*權重(weights)*模型)。 + +加載使用與 BERT 相同的檢查點訓練的 BERT 標記器(tokenizer)與加載模型的方式相同,除了我們使用 `BertTokenizer` 類: + +```py +from transformers import BertTokenizer + +tokenizer = BertTokenizer.from_pretrained("bert-base-cased") +``` + +{#if fw === 'pt'} +如同 `AutoModel`,`AutoTokenizer` 類將根據檢查點名稱在庫中獲取正確的標記器(tokenizer)類,並且可以直接與任何檢查點一起使用: + +{:else} +如同 `TFAutoModel`, `AutoTokenizer` 類將根據檢查點名稱在庫中獲取正確的標記器(tokenizer)類,並且可以直接與任何檢查點一起使用: + +{/if} + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") +``` + +我們現在可以使用標記器(tokenizer),如上一節所示: + +```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]} +``` + +保存標記器(tokenizer)與保存模型相同: + +```py +tokenizer.save_pretrained("directory_on_my_computer") +``` + +我們在[Chapter 3](/Couse/chapter3)中將更多地談論`token_type_ids`,稍後我們將解釋 `attention_mask` 鍵。首先,讓我們看看 `input_ids` 如何生成。為此,我們需要查看標記器(tokenizer)的中間方法。 + +## 編碼 + + + +將文本翻譯成數字被稱為編碼(_encoding_).編碼分兩步完成:標記化,然後轉換為輸入 ID。 + +正如我們所見,第一步是將文本拆分為單詞(或單詞的一部分、標點符號等),通常稱為*標記(token)*。有多個規則可以管理該過程,這就是為什麼我們需要使用模型名稱來實例化標記器(tokenizer),以確保我們使用模型預訓練時使用的相同規則。 + +第二步是將這些標記轉換為數字,這樣我們就可以用它們構建一個張量並將它們提供給模型。為此,標記器(tokenizer)有一個*詞彙(vocabulary)*,這是我們在實例化它時下載的部分 `from_pretrained()` 方法。同樣,我們需要使用模型預訓練時使用的相同詞彙。 + +為了更好地理解這兩個步驟,我們將分別探討它們。請注意,我們將使用一些單獨執行部分標記化管道的方法來向您展示這些步驟的中間結果,但實際上,您應該直接在您的輸入上調用標記器(tokenizer)(如第 2 部分所示)。 + +### 標記化 + +標記化過程由標記器(tokenizer)的`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) +``` + +此方法的輸出是一個字符串列表或標記(token): + +```python out +['Using', 'a', 'transform', '##er', 'network', 'is', 'simple'] +``` + +這個標記器(tokenizer)是一個子詞標記器(tokenizer):它對詞進行拆分,直到獲得可以用其詞彙表表示的標記(token)。`transformer` 就是這種情況,它分為兩個標記:`transform` 和 `##er`。 + +### 從詞符(token)到輸入 ID +輸入 ID 的轉換由標記器(tokenizer)的`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!”)複製最後兩個步驟(標記化和轉換為輸入 ID)。檢查您獲得的輸入 ID 是否與我們之前獲得的相同! + + + +## 解碼 + +*解碼(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` 方法不僅將索引轉換回標記(token),還將屬於相同單詞的標記(token)組合在一起以生成可讀的句子。當我們使用預測新文本的模型(根據提示生成的文本,或序列到序列問題(如翻譯或摘要))時,這種行為將非常有用。 + +到現在為止,您應該瞭解標記器(tokenizer)可以處理的原子操作:標記化、轉換為 ID 以及將 ID 轉換回字符串。然而,我們只是刮到了冰山一角。在下一節中,我們將採用我們的方法來克服它的限制,並看看如何克服它們。 diff --git a/chapters/zh-TW/chapter2/5.mdx b/chapters/zh-TW/chapter2/5.mdx new file mode 100644 index 000000000..dd09f943d --- /dev/null +++ b/chapters/zh-TW/chapter2/5.mdx @@ -0,0 +1,355 @@ + + +# 處理多個序列 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} + +{:else} + +{/if} + +在上一節中,我們探討了最簡單的用例:對一個小長度的序列進行推理。然而,一些問題已經出現: + +* 我們如何處理多個序列? + + +* 我們如何處理多個序列不同長度? + + +* 詞彙索引是讓模型正常工作的唯一輸入嗎? + + +* 是否存在序列太長的問題? + +讓我們看看這些問題會帶來什麼樣的問題,以及如何使用🤗 Transformers 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) + +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) +# This line will fail. +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) +# This line will fail. +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 模型默認情況下需要多個句子。在這裡,當我們將分詞器應用於一個應用程序時,我們嘗試在幕後完成分詞器所做的一切,但如果仔細觀察,您會發現它不僅將輸入ID列表轉換為張量,還在其頂部添加了一個維度: + +{#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 +tf.Tensor: shape=(1, 16), dtype=int32, numpy= +array([[ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, + 12172, 2607, 2026, 2878, 2166, 1012, 102]], dtype=int32)> +``` +{/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} + +我們打印輸入ID以及生成的logits-以下是輸出: + +{#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} + +*Batching* 是一次通過模型發送多個句子的行為。如果你只有一句話,你可以用一個序列構建一個批次: + + +``` +batched_ids = [ids, ids] +``` + +這是一批兩個相同的序列! + + + +✏️ **Try it out!** 試試看!將此列表轉換為張量並通過模型傳遞。檢查您是否獲得與之前相同的登錄(但是隻有兩次) + + +批處理允許模型在輸入多個句子時工作。使用多個序列就像使用單個序列構建批一樣簡單。不過,還有第二個問題。當你試圖將兩個(或更多)句子組合在一起時,它們的長度可能不同。如果您以前使用過張量,那麼您知道它們必須是矩形,因此無法將輸入ID列表直接轉換為張量。為了解決這個問題,我們通常填充輸入。 + +## 填充輸入 + +以下列表不能轉換為張量: + +```py no-format +batched_ids = [ + [200, 200, 200], + [200, 200] +] +``` + +為了解決這個問題,我們將使用填充使張量具有矩形。Padding通過在值較少的句子中添加一個名為Padding token的特殊單詞來確保我們所有的句子長度相同。例如,如果你有10個包含10個單詞的句子和1個包含20個單詞的句子,填充將確保所有句子都包含20個單詞。在我們的示例中,生成的張量如下所示: + +```py no-format +padding_id = 100 + +batched_ids = [ + [200, 200, 200], + [200, 200, padding_id], +] +``` + +可以在tokenizer.pad_token_id中找到填充令牌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} + +我們批處理預測中的logits有點問題:第二行應該與第二句的logits相同,但我們得到了完全不同的值! + + +這是因為Transformer模型的關鍵特性是關注層,它將每個標記上下文化。這些將考慮填充標記,因為它們涉及序列中的所有標記。為了在通過模型傳遞不同長度的單個句子時,或者在傳遞一批應用了相同句子和填充的句子時獲得相同的結果,我們需要告訴這些注意層忽略填充標記。這是通過使用 attention mask來實現的。 + +## 注意力遮罩(Attention masks) + +*Attention masks* 是與輸入 ID 張量形狀完全相同的張量,用0和1填充:1s表示應注意相應的標記,0s表示不應注意相應的標記(即,模型的注意力層應忽略它們)。 + +讓我們用 attention mask 完成上一個示例: + +{#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} + +現在我們得到了該批中第二個句子的相同登錄。 + +請注意,第二個序列的最後一個值是一個填充ID,它在attention mask中是一個0值。 + + + +✏️ 試試看!在第2節中使用的兩個句子上手動應用標記化(“我一生都在等待擁抱課程。”和“我非常討厭這個!”)。通過模型傳遞它們,並檢查您是否獲得與第2節中相同的登錄。現在使用填充標記將它們批處理在一起,然後創建適當的注意掩碼。檢查通過模型時是否獲得相同的結果! + + + +## 長序列 + +對於Transformers模型,我們可以通過模型的序列長度是有限的。大多數模型處理多達512或1024個令牌的序列,當要求處理更長的序列時,會崩潰。此問題有兩種解決方案: + + + +* 使用支持的序列長度較長的模型。 + + +* 截斷序列。 + + +模型有不同的支持序列長度,有些模型專門處理很長的序列。 +[Longformer](https://huggingface.co/transformers/model_doc/longformer.html) +這是一個例子,另一個是 +[LED](https://huggingface.co/transformers/model_doc/led.html) +. 如果您正在處理一項需要很長序列的任務,我們建議您查看這些模型。 + +否則,我們建議您通過指定max_sequence_length參數: + +```py +sequence = sequence[:max_sequence_length] +``` diff --git a/chapters/zh-TW/chapter2/6.mdx b/chapters/zh-TW/chapter2/6.mdx new file mode 100644 index 000000000..7bde73d6c --- /dev/null +++ b/chapters/zh-TW/chapter2/6.mdx @@ -0,0 +1,165 @@ + + +# 把它們放在一起 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +在最後幾節中,我們一直在盡最大努力手工完成大部分工作。我們探討了標記化器的工作原理,並研究了標記化、到輸入ID的轉換、填充、截斷和注意掩碼。 + +然而,正如我們在第2節中所看到的,🤗 Transformers API可以通過一個高級函數為我們處理所有這些,我們將在這裡深入討論。當你直接在句子上調用標記器時,你會得到準備通過模型傳遞的輸入 + +```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,它包括輸入 ID和注意力掩碼(attention mask)。其他接受額外輸入的模型也會有標記器對象的輸出。 + +正如我們將在下面的一些示例中看到的,這種方法非常強大。首先,它可以標記單個序列: + +```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 +# Will pad the sequences up to the maximum sequence length +model_inputs = tokenizer(sequences, padding="longest") + +# Will pad the sequences up to the model max length +# (512 for BERT or DistilBERT) +model_inputs = tokenizer(sequences, padding="max_length") + +# Will pad the sequences up to the specified 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!"] + +# Will truncate the sequences that are longer than the model max length +# (512 for BERT or DistilBERT) +model_inputs = tokenizer(sequences, truncation=True) + +# Will truncate the sequences that are longer than the specified max length +model_inputs = tokenizer(sequences, max_length=8, truncation=True) +``` + +標記器對象可以處理到特定框架張量的轉換,然後可以直接發送到模型。例如,在下面的代碼示例中,我們提示標記器從不同的框架返回張量——`"pt"`返回Py Torch張量,`"tf"`返回TensorFlow張量,`"np"`返回NumPy數組: + +```py +sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] + +# Returns PyTorch tensors +model_inputs = tokenizer(sequences, padding=True, return_tensors="pt") + +# Returns TensorFlow tensors +model_inputs = tokenizer(sequences, padding=True, return_tensors="tf") + +# Returns NumPy arrays +model_inputs = tokenizer(sequences, padding=True, return_tensors="np") +``` + +## 特殊詞符(token) + +如果我們看一下標記器返回的輸入 ID,我們會發現它們與之前的略有不同: + +```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] +``` + +一個在開始時添加了一個標記(token) ID,一個在結束時添加了一個標記(token) ID。讓我們解碼上面的兩個ID序列,看看這是怎麼回事: + +```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]`。這是因為模型是用這些數據預訓練的,所以為了得到相同的推理結果,我們還需要添加它們。請注意,有些模型不添加特殊單詞,或者添加不同的單詞;模型也可能只在開頭或結尾添加這些特殊單詞。在任何情況下,標記器都知道需要哪些詞符,並將為您處理這些詞符。 + +## 結束:從標記器到模型 + +現在我們已經看到了標記器對象在應用於文本時使用的所有單獨步驟,讓我們最後一次看看它如何處理多個序列(填充!),非常長的序列(截斷!),以及多種類型的張量及其主要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/zh-TW/chapter2/7.mdx b/chapters/zh-TW/chapter2/7.mdx new file mode 100644 index 000000000..2cbff4f37 --- /dev/null +++ b/chapters/zh-TW/chapter2/7.mdx @@ -0,0 +1,32 @@ +# 基本用法完成! + + + +很好地完成了到這裡的課程!總而言之,在本章中,您可以: + +- 學習了Transformers模型的基本構造塊。 + + +- 瞭解了標記化管道的組成。 + + +- 瞭解瞭如何在實踐中使用Transformers模型。 + + +- 學習瞭如何利用分詞器將文本轉換為模型可以理解的張量。 + + +- 將分詞器和模型一起設置,以從文本到預測。 + + +- 瞭解了inputs IDs的侷限性,並瞭解了attention mask。 + + +- 使用多功能和可配置的分詞器方法。 + + + +從現在起,您應該能夠自由瀏覽🤗 Transformers文檔:詞彙聽起來很熟悉,並且您已經看到了大部分時間將使用的方法。 diff --git a/chapters/zh-TW/chapter2/8.mdx b/chapters/zh-TW/chapter2/8.mdx new file mode 100644 index 000000000..8f0ca8d7b --- /dev/null +++ b/chapters/zh-TW/chapter2/8.mdx @@ -0,0 +1,298 @@ + + + + +# 章末小測試 + + + +### 1. 語言建模 Pipeline 的順序是什麼? + + +### 2. Transformer模型的輸出有多少個維度,每個維度分別是什麼? + + +### 3.下列哪一個是Subword標記(Tokenization)的例子(從分詞的顆粒度來劃分)? + + +### 4.什麼是模型的 Head 層? + + +{#if fw === 'pt'} +### 5.什麼是AutoModel? +AutoNLP 產品相混淆了?" + }, + { + text: "一個根據Checkpoint(檢查點)返回模型體系結構的對象", + explain: "確切地說: AutoModel只需要知道初始化的Checkpoint(檢查點)就可以返回正確的體系結構。", + correct: true + }, + { + text: "一種可以自動檢測輸入語言來加載正確權重的模型", + explain: "不正確; 雖然有些Checkpoint(檢查點)和模型能夠處理多種語言,但是沒有內置的工具可以根據語言自動選擇Checkpoint(檢查點)。您應該前往 Model Hub 尋找完成所需任務的最佳Checkpoint(檢查點)!" + } + ]} +/> + +{:else} +### 5.什麼是 TFAutoModel? +AutoNLP 產品相混淆了?" + }, + { + text: "一個根據Checkpoint(檢查點)返回模型體系結構的對象", + explain: "確切地說: TFAutoModel只需要知道初始化的Checkpoint(檢查點)就可以返回正確的體系結構。", + correct: true + }, + { + text: "一種可以自動檢測輸入語言來加載正確權重的模型", + explain: "不正確; 雖然有些Checkpoint(檢查點)和模型能夠處理多種語言,但是沒有內置的工具可以根據語言自動選擇Checkpoint(檢查點)。您應該前往 Model Hub 尋找完成所需任務的最佳Checkpoint(檢查點)!" + } + ]} +/> + +{/if} + +### 6.當將不同長度的序列批處理在一起時,需要進行哪些處理? + + +### 7.將 SoftMax激活函數應用於序列分類(Sequence Classification)模型的 logits 輸出有什麼意義? + + +### 8.大多數標記器(Tokenizer)的API以什麼方法為核心? +編碼 ,因為它可以將文本編碼為id,將預測的id解碼為文本", + explain: "錯! 雖然 編碼 方法確實存在於標記器中,但是它不存在於模型中。" + }, + { + text: "直接調用標記器(Tokenizer)對象。", + explain: "完全正確!標記化器(Tokenizer) 的 __call__方法是一個非常強大的方法,可以處理幾乎任何事情。它也是從模型中獲取預測的方法。", + correct: true + }, + { + text: "pad(填充)", + explain: "錯! pad(填充)非常有用,但它只是標記器(Tokenizer) API的一部分。" + }, + { + text: "tokenize(標記)", + explain: "可以說,tokenize(標記)方法是最有用的方法之一,但它不是標記器(Tokenizer) 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: "包含所有標記(Token)的字符串", + explain: "這將是次優的,因為Tokenizer會將字符串拆分為多個標記的列表。" + } + ]} +/> + +{#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/zh-TW/chapter3/1.mdx b/chapters/zh-TW/chapter3/1.mdx new file mode 100644 index 000000000..aacaf7dc6 --- /dev/null +++ b/chapters/zh-TW/chapter3/1.mdx @@ -0,0 +1,26 @@ + + +# 本章簡介 + + + +在 [第二章](/course/chapter2) 我們探索瞭如何使用標記器(Tokenizer)和預訓練模型進行預測。但是,如果您想為自己的數據集微調預訓練模型,該怎麼做呢?這就是本章的主題!你將學到: + +{#if fw === 'pt'} +* 如何從模型中心(hub)準備大型數據集 +* 如何使用高級`訓練`API微調一個模型 +* 如何使用自定義訓練過程 +* 如何利用🤗 Accelerate庫在任何分佈式設備上輕鬆運行自定義訓練過程 + +{:else} +* 如何從模型中心(hub)準備大型數據集 +* 如何使用 Keras 微調模型 +* 如何使用 Keras 進行預測 +* 如何使用自定義指標 + +{/if} + +為了將經過訓練的參數上傳到 Hugging Face Hub,您需要一個 huggingface.co 帳戶: [創建一個賬戶](https://huggingface.co/join) \ No newline at end of file diff --git a/chapters/zh-TW/chapter3/2.mdx b/chapters/zh-TW/chapter3/2.mdx new file mode 100644 index 000000000..1b9045aab --- /dev/null +++ b/chapters/zh-TW/chapter3/2.mdx @@ -0,0 +1,383 @@ + + +# 處理數據 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +{#if fw === 'pt'} +這一小節學習[第一小節](/course/chapter2)中提到的「如何使用模型中心(hub)大型數據集」,下面是我們用模型中心的數據在 PyTorch 上訓練句子分類器的一個例子: + +```python +import torch +from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification + +# Same as before +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = AutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "This course is amazing!", +] +batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") + +# This is new +batch["labels"] = torch.tensor([1, 1]) + +optimizer = AdamW(model.parameters()) +loss = model(**batch).loss +loss.backward() +optimizer.step() +``` +{:else} +這一小節學習[第一小節](/course/chapter2)中提到的「如何使用模型中心(hub)大型數據集」,下面是我們用模型中心的數據在 TensorFlow 上訓練句子分類器的一個例子: + +```python +import tensorflow as tf +import numpy as np +from transformers import AutoTokenizer, TFAutoModelForSequenceClassification + +# Same as before +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) +sequences = [ + "I've been waiting for a HuggingFace course my whole life.", + "This course is amazing!", +] +batch = dict(tokenizer(sequences, padding=True, truncation=True, return_tensors="tf")) + +# This is new +model.compile(optimizer="adam", loss="sparse_categorical_crossentropy") +labels = tf.convert_to_tensor([1, 1]) +model.train_on_batch(batch, labels) +``` +{/if} + +當然,僅僅用兩句話訓練模型不會產生很好的效果。為了獲得更好的結果,您需要準備一個更大的數據集。 + +在本節中,我們將使用MRPC(微軟研究釋義語料庫)數據集作為示例,該數據集由威廉·多蘭和克里斯·布羅克特在[這篇文章](https://www.aclweb.org/anthology/I05-5002.pdf)發佈。該數據集由5801對句子組成,每個句子對帶有一個標籤,指示它們是否為同義(即,如果兩個句子的意思相同)。我們在本章中選擇了它,因為它是一個小數據集,所以很容易對它進行訓練。 + +### 從模型中心(Hub)加載數據集 + +{#if fw === 'pt'} + +{:else} + +{/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個不同文本分類任務中的性能。 + +🤗 Datasets庫提供了一個非常便捷的命令,可以在模型中心(hub)上下載和緩存數據集。我們可以通過以下的代碼下載MRPC數據集: + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("glue", "mrpc") +raw_datasets +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 408 + }) + test: Dataset({ + features: ['sentence1', 'sentence2', 'label', 'idx'], + num_rows: 1725 + }) +}) +``` + +正如你所看到的,我們獲得了一個**DatasetDict**對象,其中包含訓練集、驗證集和測試集。每一個集合都包含幾個列(**sentence1**, **sentence2**, **label**, and **idx**)以及一個代表行數的變量,即每個集合中的行的個數(因此,訓練集中有3668對句子,驗證集中有408對,測試集中有1725對)。 + +默認情況下,此命令在下載數據集並緩存到 **~/.cache/huggingface/dataset**. 回想一下第2章,您可以通過設置**HF_HOME**環境變量來自定義緩存的文件夾。 + +我們可以訪問我們數據集中的每一個**raw_train_dataset**對象,如使用字典: + +```py +raw_train_dataset = raw_datasets["train"] +raw_train_dataset[0] +``` + +```python out +{'idx': 0, + 'label': 1, + 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', + 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} +``` + +我們可以看到標籤已經是整數了,所以我們不需要對標籤做任何預處理。要知道哪個數字對應於哪個標籤,我們可以查看**raw_train_dataset**的**features**. 這將告訴我們每列的類型: + +```py +raw_train_dataset.features +``` + +```python out +{'sentence1': Value(dtype='string', id=None), + 'sentence2': Value(dtype='string', id=None), + 'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None), + 'idx': Value(dtype='int32', id=None)} +``` + +在上面的例子之中,**Label(標籤)** 是一種**ClassLabel(分類標籤)**,使用整數建立起到類別標籤的映射關係。**0**對應於**not_equivalent**,**1**對應於**equivalent**。 + + + +✏️ **試試看!** 查看訓練集的第15行元素和驗證集的87行元素。他們的標籤是什麼? + + + +### 預處理數據集 + +{#if fw === 'pt'} + +{:else} + +{/if} + +為了預處理數據集,我們需要將文本轉換為模型能夠理解的數字。正如你在[第二章](/course/chapter2)上看到的那樣 + +```py +from transformers import AutoTokenizer + +checkpoint = "bert-base-uncased" +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"]) +tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"]) +``` + +然而,在兩句話傳遞給模型,預測這兩句話是否是同義之前。我們需要這兩句話依次進行適當的預處理。幸運的是,標記器不僅僅可以輸入單個句子還可以輸入一組句子,並按照我們的BERT模型所期望的輸入進行處理: + +```py +inputs = tokenizer("This is the first sentence.", "This is the second one.") +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] +} +``` + +我們在[第二章](/course/chapter2) 討論了**輸入詞id(input_ids)** 和 **注意力遮罩(attention_mask)** ,但我們在那個時候沒有討論**類型標記ID(token_type_ids)**。在這個例子中,**類型標記ID(token_type_ids)**的作用就是告訴模型輸入的哪一部分是第一句,哪一部分是第二句。 + + + +✏️ ** 試試看!** 選取訓練集中的第15個元素,將兩句話分別標記為一對。結果和上方的例子有什麼不同? + + + +如果我們將**input_ids**中的id轉換回文字: + +```py +tokenizer.convert_ids_to_tokens(inputs["input_ids"]) +``` + +我們將得到: + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +``` + +所以我們看到模型需要輸入的形式是 **[CLS] sentence1 [SEP] sentence2 [SEP]**。因此,當有兩句話的時候。**類型標記ID(token_type_ids)** 的值是: + +```python out +['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +[ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] +``` + +如您所見,輸入中 **[CLS] sentence1 [SEP]** 它們的類型標記ID均為**0**,而其他部分,對應於**sentence2 [SEP]**,所有的類型標記ID均為**1**. + +請注意,如果選擇其他的檢查點,則不一定具有**類型標記ID(token_type_ids)**(例如,如果使用DistilBERT模型,就不會返回它們)。只有當它在預訓練期間使用過這一層,模型在構建時依賴它們,才會返回它們。 + +用類型標記ID對BERT進行預訓練,並且使用[第一章](/course/chapter1)的遮罩語言模型,還有一個額外的應用類型,叫做下一句預測. 這項任務的目標是建立成對句子之間關係的模型。 + +在下一個句子預測任務中,會給模型輸入成對的句子(帶有隨機遮罩的標記),並被要求預測第二個句子是否緊跟第一個句子。為了提高模型的泛化能力,數據集中一半的兩個句子在原始文檔中挨在一起,另一半的兩個句子來自兩個不同的文檔。 + +一般來說,你不需要擔心是否有**類型標記ID(token_type_ids)**。在您的標輸入中:只要您對標記器和模型使用相同的檢查點,一切都會很好,因為標記器知道向其模型提供什麼。 + +現在我們已經瞭解了標記器如何處理一對句子,我們可以使用它對整個數據集進行處理:如[之前的章節](/course/chapter2),我們可以給標記器提供一組句子,第一個參數是它第一個句子的列表,第二個參數是第二個句子的列表。這也與我們在[第二章](/course/chapter2)中看到的填充和截斷選項兼容. 因此,預處理訓練數據集的一種方法是: + +```py +tokenized_dataset = tokenizer( + raw_datasets["train"]["sentence1"], + raw_datasets["train"]["sentence2"], + padding=True, + truncation=True, +) +``` + +這很有效,但它的缺點是返回字典(字典的鍵是**輸入詞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)方法,如果我們需要做更多的預處理而不僅僅是標記化,那麼這也給了我們一些額外的自定義的方法。這個方法的工作原理是在數據集的每個元素上應用一個函數,因此讓我們定義一個標記輸入的函數: + +```py +def tokenize_function(example): + return tokenizer(example["sentence1"], example["sentence2"], truncation=True) +``` + +此函數的輸入是一個字典(與數據集的項類似),並返回一個包含**輸入詞id(input_ids)** , **注意力遮罩(attention_mask)** 和 **類型標記ID(token_type_ids)** 鍵的新字典。請注意,如果像上面的**示例**一樣,如果鍵所對應的值包含多個句子(每個鍵作為一個句子列表),那麼它依然可以工作,就像前面的例子一樣標記器可以處理成對的句子列表。這樣的話我們可以在調用**map()**使用該選項 **batched=True** ,這將顯著加快標記與標記的速度。這個**標記器**來自[🤗 Tokenizers](https://github.com/huggingface/tokenizers)庫由Rust編寫而成。當我們一次給它大量的輸入時,這個標記器可以非常快。 + +請注意,我們現在在標記函數中省略了**padding**參數。這是因為在標記的時候將所有樣本填充到最大長度的效率不高。一個更好的做法:在構建批處理時填充樣本更好,因為這樣我們只需要填充到該批處理中的最大長度,而不是整個數據集的最大長度。當輸入長度變化很大時,這可以節省大量時間和處理能力! + +下面是我們如何在所有數據集上同時應用標記函數。我們在調用**map**時使用了**batch =True**,這樣函數就可以同時應用到數據集的多個元素上,而不是分別應用到每個元素上。這將使我們的預處理快許多 + +```py +tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) +tokenized_datasets +``` + +🤗Datasets 庫應用這種處理的方式是向數據集添加新的字段,每個字段對應預處理函數返回的字典中的每個鍵: + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 3668 + }) + validation: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 408 + }) + test: Dataset({ + features: ['attention_mask', 'idx', 'input_ids', 'label', 'sentence1', 'sentence2', 'token_type_ids'], + num_rows: 1725 + }) +}) +``` + +在使用預處理函數**map()**時,甚至可以通過傳遞**num_proc**參數使用並行處理。我們在這裡沒有這樣做,因為🤗標記器庫已經使用多個線程來更快地標記我們的樣本,但是如果您沒有使用該庫支持的快速標記器,使用**num_proc**可能會加快預處理。 + +我們的**標記函數(tokenize_function)**返回包含**輸入詞id(input_ids)** , **注意力遮罩(attention_mask)** 和 **類型標記ID(token_type_ids)** 鍵的字典,所以這三個字段被添加到數據集的標記的結果中。注意,如果預處理函數**map()**為現有鍵返回一個新值,那將會修改原有鍵的值。 + +最後一件我們需要做的事情是,當我們一起批處理元素時,將所有示例填充到最長元素的長度——我們稱之為動態填充。 + +### 動態填充 + + + +{#if fw === 'pt'} +負責在批處理中將數據整理為一個batch的函數稱為*collate函數*。它是你可以在構建**DataLoader**時傳遞的一個參數,默認是一個函數,它將把你的數據集轉換為PyTorch張量,並將它們拼接起來(如果你的元素是列表、元組或字典,則會使用遞歸)。這在我們的這個例子中下是不可行的,因為我們的輸入不是都是相同大小的。我們故意在之後每個batch上進行填充,避免有太多填充的過長的輸入。這將大大加快訓練速度,但請注意,如果你在TPU上訓練,這可能會導致問題——TPU喜歡固定的形狀,即使這需要額外的填充。 + +{:else} + +負責在批處理中將數據整理為一個batch的函數稱為*collate函數*。它只會將您的樣本轉換為 tf.Tensor並將它們拼接起來(如果你的元素是列表、元組或字典,則會使用遞歸)。這在我們的這個例子中下是不可行的,因為我們的輸入不是都是相同大小的。我們故意在之後每個batch上進行填充,避免有太多填充的過長的輸入。這將大大加快訓練速度,但請注意,如果你在TPU上訓練,這可能會導致問題——TPU喜歡固定的形狀,即使這需要額外的填充。 + +{/if} + +為了解決句子長度統一的問題,我們必須定義一個collate函數,該函數會將每個batch句子填充到正確的長度。幸運的是,🤗transformer庫通過**DataCollatorWithPadding**為我們提供了這樣一個函數。當你實例化它時,需要一個標記器(用來知道使用哪個詞來填充,以及模型期望填充在左邊還是右邊),並將做你需要的一切: + +{#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} + +為了測試這個新玩具,讓我們從我們的訓練集中抽取幾個樣本。這裡,我們刪除列**idx**, **sentence1**和**sentence2**,因為不需要它們,並查看一個batch中每個條目的長度: + +```py +samples = tokenized_datasets["train"][:8] +samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "sentence2"]} +[len(x) for x in samples["input_ids"]] +``` + +```python out +[50, 59, 47, 67, 59, 50, 62, 32] +``` + +毫無疑問,我們得到了不同長度的樣本,從32到67。動態填充意味著該批中的所有樣本都應該填充到長度為67,這是該批中的最大長度。如果沒有動態填充,所有的樣本都必須填充到整個數據集中的最大長度,或者模型可以接受的最大長度。讓我們再次檢查**data_collator**是否正確地動態填充了這批樣本: + +```py: + +```py +batch = data_collator(samples) +{k: v.shape for k, v in batch.items()} +``` + +{#if fw === 'tf'} + +```python out +{'attention_mask': TensorShape([8, 67]), + 'input_ids': TensorShape([8, 67]), + 'token_type_ids': TensorShape([8, 67]), + 'labels': TensorShape([8])} +``` + +{:else} + +```python out +{'attention_mask': torch.Size([8, 67]), + 'input_ids': torch.Size([8, 67]), + 'token_type_ids': torch.Size([8, 67]), + 'labels': torch.Size([8])} +``` + +看起來不錯!現在,我們已經將原始文本轉化為了模型可以處理的數據,我們已準備好對其進行微調! + +{/if} + + + +✏️ ** 試試看!** 在GLUE SST-2數據集上應用預處理。它有點不同,因為它是由單個句子而不是成對的句子組成的,但是我們所做的其他事情看起來應該是一樣的。另一個更難的挑戰,請嘗試編寫一個可用於任何GLUE任務的預處理函數。 + + + +{#if fw === 'tf'} + +現在我們有了 dataset 和 data collator,我們需要將 dataset 批次地應用 data collator。 我們可以手動載入批次並整理它們,但這需要大量工作,性能可能也不是很好。 相反,有一個簡單的方法可以為這個問題提供高效的解決方案:`to_tf_dataset()`。 這將在您的數據集上調用一個 `tf.data.Dataset`的方法,這個方法帶有一個可選的 data collator 功能。 `tf.data.Dataset` 是 Keras 可用於 `model.fit()` 的原生 TensorFlow 格式,因此這種方法會立即將🤗 Dataset 轉換為可用於訓練的格式。 讓我們看看它在我們的數據集上是如何使用的! + +```py +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, +) +``` + +就是這樣! 我們可以將這些數據集帶入下一節,在經過所有艱苦的數據預處理工作之後,訓練將變得非常簡單。 + +{/if} diff --git a/chapters/zh-TW/chapter3/3.mdx b/chapters/zh-TW/chapter3/3.mdx new file mode 100644 index 000000000..93555c2aa --- /dev/null +++ b/chapters/zh-TW/chapter3/3.mdx @@ -0,0 +1,172 @@ + + +# 使用 Trainer API 微調模型 + + + + + +🤗 Transformers提供了一個 **Trainer** 類來幫助您在自己的數據集上微調任何預訓練模型。完成上一節中的所有數據預處理工作後,您只需要執行幾個步驟來創建 **Trainer** .最難的部分可能是為 **Trainer.train()**配置運行環境,因為它在 CPU 上運行速度會非常慢。如果您沒有設置 GPU,您可以訪問免費的 GPU 或 TPU[Google Colab](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) +``` + +### Training + +在我們定義我們的 **Trainer** 之前首先要定義一個 **TrainingArguments** 類,它將包含 **Trainer**用於訓練和評估的所有超參數。您唯一必須提供的參數是保存訓練模型的目錄,以及訓練過程中的檢查點。對於其餘的參數,您可以保留默認值,這對於基本微調應該非常有效。 + +```py +from transformers import TrainingArguments + +training_args = TrainingArguments("test-trainer") +``` + + + +💡 如果您想在訓練期間自動將模型上傳到 Hub,請將push_to_hub=True添加到TrainingArguments之中. 我們將在[第四章](/course/chapter4/3)中詳細介紹這部分。 + + + +第二步是定義我們的模型。正如在[之前的章節](/2_Using Transformers/Introduction)一樣,我們將使用 **AutoModelForSequenceClassification** 類,它有兩個參數: + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +你會注意到,和[第二章](/course/chapter2)不一樣的是,在實例化此預訓練模型後會收到警告。這是因為 BERT 沒有在句子對分類方面進行過預訓練,所以預訓練模型的頭部已經被丟棄,而是添加了一個適合句子序列分類的新頭部。警告表明一些權重沒有使用(對應於丟棄的預訓練頭的那些),而其他一些權重被隨機初始化(新頭的那些)。最後鼓勵您訓練模型,這正是我們現在要做的。 + +一旦我們有了我們的模型,我們就可以定義一個 **Trainer** 通過將之前構造的所有對象傳遞給它——我們的**model** 、**training_args** ,訓練和驗證數據集,**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**後,默認 **Trainer**使用 的**data_collator**會使用之前預定義的 **DataCollatorWithPadding** ,因此您可以在這個例子中跳過 **data_collator=data_collator**。在第 2 節中向您展示這部分處理仍然很重要! + +為了讓預訓練模型在在我們的數據集上微調,我們只需要調用**Trainer**的**train()** 方法 : + +```py +trainer.train() +``` + +這將開始微調(在GPU上應該需要幾分鐘),並每500步報告一次訓練損失。但是,它不會告訴您模型的性能如何(或質量如何)。這是因為: + +1. 我們沒有通過將**evaluation_strategy**設置為“**steps**”(在每次更新參數的時候評估)或“**epoch**”(在每個epoch結束時評估)來告訴**Trainer**在訓練期間進行評估。 +2. 我們沒有為**Trainer**提供一個**compute_metrics()**函數來直接計算模型的好壞(否則評估將只輸出loss,這不是一個非常直觀的數字)。 + + +### 評估 + +讓我們看看如何構建一個有用的 **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** 字段將只包含傳遞的數據集的loss,以及一些運行時間(預測所需的總時間和平均時間)。如果我們定義了自己的 **compute_metrics()** 函數並將其傳遞給 **Trainer** ,該字段還將包含**compute_metrics()**的結果。 + +**predict()** 方法是具有三個字段的命名元組: **predictions** , **label_ids** , 和 **metrics** .這 **metrics** 字段將只包含傳遞的數據集的loss,以及一些運行時間(預測所需的總時間和平均時間)。如果我們定義了自己的 **compute_metrics()** 函數並將其傳遞給 **Trainer** ,該字段還將包含**compute_metrics()** 的結果。如你看到的, **predictions** 是一個形狀為 408 x 2 的二維數組(408 是我們使用的數據集中元素的數量)。這些是我們傳遞給**predict()**的數據集的每個元素的結果(logits)(正如你在[之前的章節](/course/chapter2)看到的情況)。要將我們的預測的可以與真正的標籤進行比較,我們需要在第二個軸上取最大值的索引: + +```py +import numpy as np + +preds = np.argmax(predictions.predictions, axis=-1) +``` + +現在建立我們的 **compute_metric()** 函數來較為直觀地評估模型的好壞,我們將使用 🤗 [Evaluate](https://github.com/huggingface/evaluate/) 庫中的指標。我們可以像加載數據集一樣輕鬆加載與 MRPC 數據集關聯的指標,這次使用 **evaluate.load()** 函數。返回的對象有一個 **compute()**方法我們可以用來進行度量計算的方法: + +```py +import evaluate + +metric = evaluate.load("glue", "mrpc") +metric.compute(predictions=preds, references=predictions.label_ids) +``` + +```python out +{'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542} +``` + +您獲得的確切結果可能會有所不同,因為模型頭的隨機初始化可能會影響最終建立的模型。在這裡,我們可以看到我們的模型在驗證集上的準確率為 85.78%,F1 分數為 89.97。這是用於評估 GLUE 基準的 MRPC 數據集結果的兩個指標。而在[BERT 論文](https://arxiv.org/pdf/1810.04805.pdf)中展示的基礎模型的 F1 分數為 88.9。那是 **uncased** 模型,而我們目前正在使用 **cased** 模型,通過改進得到了更好的結果。 + +最後將所有東西打包在一起,我們得到了我們的 **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) +``` + +為了查看模型在每個訓練週期結束的好壞,下面是我們如何使用**compute_metrics()**函數定義一個新的 **Trainer** : + +```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() +``` + +這一次,它將在訓練loss之外,還會輸出每個 epoch 結束時的驗證loss和指標。同樣,由於模型的隨機頭部初始化,您達到的準確率/F1 分數可能與我們發現的略有不同,但它應該在同一範圍內。 + +這 **Trainer** 將在多個 GPU 或 TPU 上開箱即用,並提供許多選項,例如混合精度訓練(在訓練的參數中使用 **fp16 = True** )。我們將在第 10 章討論它支持的所有內容。 + +使用**Trainer** API微調的介紹到此結束。對最常見的 NLP 任務執行此操作的示例將在第 7 章中給出,但現在讓我們看看如何在純 PyTorch 中執行相同的操作。 + + + +✏️ **試試看!** 使用您在第 2 節中進行的數據處理,在 GLUE SST-2 數據集上微調模型。 + + + diff --git a/chapters/zh-TW/chapter3/3_tf.mdx b/chapters/zh-TW/chapter3/3_tf.mdx new file mode 100644 index 000000000..21d095289 --- /dev/null +++ b/chapters/zh-TW/chapter3/3_tf.mdx @@ -0,0 +1,190 @@ + + +# 使用 Keras 微調一個模型 + + + +完成上一節中的所有數據預處理工作後,您只剩下最後的幾個步驟來訓練模型。 但是請注意,`model.fit()` 命令在 CPU 上運行會非常緩慢。 如果您沒有GPU,則可以在 [Google Colab](https://colab.research.google.com/) 上使用免費的 GPU 或 TPU(需要梯子)。 + +這一節的代碼示例假設您已經執行了上一節中的代碼示例。 下面一個簡短的摘要,包含了在開始學習這一節之前您需要的執行的代碼: + +```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, +) +``` + +### 訓練模型 + +從🤗 Transformers 導入的 TensorFlow 模型已經是 Keras 模型。 下面的視頻是對 Keras 的簡短介紹。 + + + +這意味著,一旦我們有了數據,就需要很少的工作就可以開始對其進行訓練。 + + + +和[第二章](/course/chapter2)使用的方法一樣, 我們將使用二分類的 `TFAutoModelForSequenceClassification`類: + +```py +from transformers import TFAutoModelForSequenceClassification + +model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` + +您會注意到,與 [第二章](/course/chapter2) 不同的是,您在實例化此預訓練模型後會收到警告。 這是因為 BERT 沒有對句子對進行分類進行預訓練,所以預訓練模型的 head 已經被丟棄,而是插入了一個適合序列分類的新 head。 警告表明一些權重沒有使用(對應於丟棄的預訓練頭),而其他一些權重是隨機初始化的(新頭的權重)。 最後鼓勵您訓練模型,這正是我們現在要做的。 + +要在我們的數據集上微調模型,我們只需要在我們的模型上調用 `compile()` 方法,然後將我們的數據傳遞給 `fit()` 方法。 這將啟動微調過程(在 GPU 上應該需要幾分鐘)並輸出訓練loss,以及每個 epoch 結束時的驗證loss。 + + + +請注意🤗 Transformers 模型具有大多數 Keras 模型所沒有的特殊能力——它們可以自動使用內部計算的loss。 如果您沒有在 `compile()` 中設置損失函數,他們將默認使用內部計算的損失。 請注意,要使用內部損失,您需要將標籤作為輸入的一部分傳遞,而不是作為單獨的標籤(這是在 Keras 模型中使用標籤的正常方式)。 您將在課程的第 2 部分中看到這方面的示例,其中定義正確的損失函數可能很棘手。 然而,對於序列分類,標準的 Keras 損失函數可以正常工作,所以我們將在這裡使用它。 + + + +```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, +) +``` + + + +請注意這裡有一個非常常見的陷阱——你只是*可以*將損失的名稱作為字符串傳遞給 Keras,但默認情況下,Keras 會假設你已經對輸出應用了 softmax。 然而,許多模型在應用 softmax 之前就輸出,也稱為 *logits*。 我們需要告訴損失函數我們的模型是否經過了softmax,唯一的方法是直接調用它,而不是用字符串的名稱。 + + + + +### 提升訓練的效果 + + + +如果您嘗試上面的代碼,它肯定會運行,但您會發現loss只是緩慢或零星地下降。 主要原因是*學習率*。 與loss一樣,當我們將優化器的名稱作為字符串傳遞給 Keras 時,Keras 會初始化該優化器具有所有參數的默認值,包括學習率。 但是,根據長期經驗,我們知道Transformer 模型更適合使用比 Adam 的默認值(1e-3)也寫成為 10 的 -3 次方,或 0.001,低得多的學習率。 5e-5 (0.00005) 比默認值大約低 20 倍,是一個更好的起點。 + +除了降低學習率,我們還有第二個技巧:我們可以慢慢降低學習率。在訓練過程中。 在文獻中,您有時會看到這被稱為 *decaying* 或 *annealing*學習率。 在 Keras 中,最好的方法是使用 *learning rate scheduler*。 一個好用的是`PolynomialDecay`——儘管有這個名字,但在默認設置下,它只是簡單地從初始值線性衰減學習率值在訓練過程中的最終值,這正是我們想要的。但是, 為了正確使用調度程序,我們需要告訴它訓練的次數。 我們將在下面為其計算“num_train_steps”。 + +```py +from tensorflow.keras.optimizers.schedules import PolynomialDecay + +batch_size = 8 +num_epochs = 3 +# 訓練步數是數據集中的樣本數除以batch size再乘以 epoch。 +# 注意這裡的tf_train_dataset是一個轉化為batch後的 tf.data.Dataset, +# 不是原來的 Hugging Face Dataset,所以它的 len() 已經是 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) +``` + + + +🤗 Transformers 庫還有一個 `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"]) +``` + +現在,我們再次進行fit: + +```py +model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) +``` + + + +💡 如果您想在訓練期間自動將模型上傳到 Hub,您可以在 `model.fit()` 方法中傳遞 `PushToHubCallback`。 我們將在 [第四章](/course/chapter4/3) 中進行介紹 + + + +### 模型預測 + + + + +訓練和觀察的loss下降都非常好,但是如果我們想從訓練後的模型中獲得輸出,或者計算一些指標,或者在生產中使用模型呢? 為此,我們可以使用`predict()` 方法。 這將返回模型的輸出頭的*logits*數值,每個類一個。 + +```py +preds = model.predict(tf_validation_dataset)["logits"] +``` + +我們可以將這些 logit 轉換為模型的類別預測,方法是使用 argmax 找到最高的 logit,它對應於最有可能的類別: + +```py +class_preds = np.argmax(preds, axis=1) +print(preds.shape, class_preds.shape) +``` + +```python out +(408, 2) (408,) +``` + +現在,讓我們使用這些 `preds` 來計算一些指標! 我們可以像加載數據集一樣輕鬆地加載與 MRPC 數據集相關的指標,這次使用的是 `evaluate.load()` 函數。 返回的對象有一個 `compute()` 方法,我們可以使用它來進行度量計算: + +```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} +``` + +您獲得的確切結果可能會有所不同,因為模型頭的隨機初始化可能會改變它獲得的指標。 在這裡,我們可以看到我們的模型在驗證集上的準確率為 85.78%,F1 得分為 89.97。 這些是用於評估 GLUE 基準的 MRPC 數據集結果的兩個指標。 [BERT 論文](https://arxiv.org/pdf/1810.04805.pdf) 中的表格報告了基本模型的 F1 分數為 88.9。 那是 `uncased` 模型,而我們目前使用的是 `cased` 模型,這解釋了為什麼我們會獲得更好的結果。 + +使用 Keras API 進行微調的介紹到此結束。 第 7 章將給出對大多數常見 NLP 任務執行此操作的示例。如果您想在 Keras API 上磨練自己的技能,請嘗試使第二節所使用的的數據處理在 GLUE SST-2 數據集上微調模型。 \ No newline at end of file diff --git a/chapters/zh-TW/chapter3/4.mdx b/chapters/zh-TW/chapter3/4.mdx new file mode 100644 index 000000000..2cd3f1dea --- /dev/null +++ b/chapters/zh-TW/chapter3/4.mdx @@ -0,0 +1,358 @@ +# 一個完整的訓練 + + + + + +現在,我們將瞭解如何在不使用`Trainer`類的情況下獲得與上一節相同的結果。同樣,我們假設您已經學習了第 2 節中的數據處理。下面是一個簡短的總結,涵蓋了您需要的所有內容: + +```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) +``` + +### 訓練前的準備 + +在實際編寫我們的訓練循環之前,我們需要定義一些對象。第一個是我們將用於迭代批次的數據加載器。我們需要對我們的`tokenized_datasets`做一些處理,來處理`Trainer`自動為我們做的一些事情。具體來說,我們需要: + +- 刪除與模型不期望的值相對應的列(如`sentence1`和`sentence2`列)。 +- 將列名`label`重命名為`labels`(因為模型期望參數是`labels`)。 +- 設置數據集的格式,使其返回 PyTorch 張量而不是列表。 + +針對上面的每個步驟,我們的 `tokenized_datasets` 都有一個方法: + +```py +tokenized_datasets = tokenized_datasets.remove_columns(["sentence1", "sentence2", "idx"]) +tokenized_datasets = tokenized_datasets.rename_column("label", "labels") +tokenized_datasets.set_format("torch") +tokenized_datasets["train"].column_names +``` + +然後,我們可以檢查結果中是否只有模型能夠接受的列: + +```python +["attention_mask", "input_ids", "labels", "token_type_ids"] +``` + +至此,我們可以輕鬆定義數據加載器: + +```py +from torch.utils.data import DataLoader + +train_dataloader = DataLoader( + tokenized_datasets["train"], shuffle=True, batch_size=8, collate_fn=data_collator +) +eval_dataloader = DataLoader( + tokenized_datasets["validation"], batch_size=8, collate_fn=data_collator +) +``` + +為了快速檢驗數據處理中沒有錯誤,我們可以這樣檢驗其中的一個批次: + +```py +for batch in train_dataloader: + break +{k: v.shape for k, v in batch.items()} +``` + +```python out +{'attention_mask': torch.Size([8, 65]), + 'input_ids': torch.Size([8, 65]), + 'labels': torch.Size([8]), + 'token_type_ids': torch.Size([8, 65])} +``` + +請注意,實際的形狀可能與您略有不同,因為我們為訓練數據加載器設置了`shuffle=True`,並且模型會將句子填充到`batch`中的最大長度。 + +現在我們已經完全完成了數據預處理(對於任何 ML 從業者來說都是一個令人滿意但難以實現的目標),讓我們將注意力轉向模型。我們完全像在上一節中所做的那樣實例化它: + +```py +from transformers import AutoModelForSequenceClassification + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +``` +為了確保訓練過程中一切順利,我們將`batch`傳遞給這個模型: + +```py +outputs = model(**batch) +print(outputs.loss, outputs.logits.shape) +``` + +```python out +tensor(0.5441, grad_fn=) torch.Size([8, 2]) +``` + +當我們提供 `labels` 時, 🤗 Transformers 模型都將返回這個`batch`的`loss`,我們還得到了 `logits`(`batch`中的每個輸入有兩個,所以張量大小為 8 x 2)。 + +我們幾乎準備好編寫我們的訓練循環了!我們只是缺少兩件事:優化器和學習率調度器。由於我們試圖自行實現 `Trainer`的功能,我們將使用相同的優化器和學習率調度器。`Trainer` 使用的優化器是 `AdamW` , 與 `Adam` 相同,但在權重衰減正則化方面有所不同(參見[“Decoupled Weight Decay Regularization”](https://arxiv.org/abs/1711.05101)作者:Ilya Loshchilov 和 Frank Hutter): + +```py +from transformers import AdamW + +optimizer = AdamW(model.parameters(), lr=5e-5) +``` + +最後,默認使用的學習率調度器只是從最大值 (5e-5) 到 0 的線性衰減。 為了定義它,我們需要知道我們訓練的次數,即所有數據訓練的次數(epochs)乘以的數據量(這是我們所有訓練數據的數量)。`Trainer`默認情況下使用三個`epochs`,因此我們定義訓練過程如下: + +```py +from transformers import get_scheduler + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) +print(num_training_steps) +``` + +```python out +1377 +``` + +### 訓練循環 + +最後一件事:如果我們可以訪問 GPU,我們將希望使用 GPU(在 CPU 上,訓練可能需要幾個小時而不是幾分鐘)。為此,我們定義了一個 `device`,它在GPU可用的情況下指向GPU 我們將把我們的模型和`batche`放在`device`上: + +```py +import torch + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) +device +``` + +```python out +device(type='cuda') +``` + +我們現在準備好訓練了!為了瞭解訓練何時結束,我們使用 `tqdm` 庫,在訓練步驟數上添加了一個進度條: + +```py +from tqdm.auto import tqdm + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +您可以看到訓練循環的核心與介紹中的非常相似。我們沒有要求任何檢驗,所以這個訓練循環不會告訴我們任何關於模型目前的狀態。我們需要為此添加一個評估循環。 + + +### 評估循環 + +正如我們之前所做的那樣,我們將使用 🤗 Evaluate 庫提供的指標。我們已經瞭解了 `metric.compute()` 方法,當我們使用 `add_batch()`方法進行預測循環時,實際上該指標可以為我們累積所有 `batch` 的結果。一旦我們累積了所有 `batch` ,我們就可以使用 `metric.compute()` 得到最終結果 .以下是在評估循環中實現所有這些的方法: + +```py +import evaluate + +metric = evaluate.load("glue", "mrpc") +model.eval() +for batch in eval_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + with torch.no_grad(): + outputs = model(**batch) + + logits = outputs.logits + predictions = torch.argmax(logits, dim=-1) + metric.add_batch(predictions=predictions, references=batch["labels"]) + +metric.compute() +``` + +```python out +{'accuracy': 0.8431372549019608, 'f1': 0.8907849829351535} +``` + +同樣,由於模型頭部初始化和數據改組的隨機性,您的結果會略有不同,但它們應該在同一個範圍內。 + + + +✏️ **試試看!** 修改之前的訓練循環以在 SST-2 數據集上微調您的模型。 + + + +### S使用🤗 Accelerate加速您的訓練循環 + + + +我們之前定義的訓練循環在單個 CPU 或 GPU 上運行良好。但是使用[🤗 Accelerate](https://github.com/huggingface/accelerate)庫,只需進行一些調整,我們就可以在多個 GPU 或 TPU 上啟用分佈式訓練。從創建訓練和驗證數據加載器開始,我們的手動訓練循環如下所示: + +```py +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +model.to(device) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dataloader) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dataloader: + batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss + loss.backward() + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +以下是變化: + +```diff ++ from accelerate import Accelerator + from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + ++ accelerator = Accelerator() + + model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) + optimizer = AdamW(model.parameters(), lr=3e-5) + +- device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu") +- model.to(device) + ++ train_dataloader, eval_dataloader, model, optimizer = accelerator.prepare( ++ train_dataloader, eval_dataloader, model, optimizer ++ ) + + num_epochs = 3 + num_training_steps = num_epochs * len(train_dataloader) + lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps + ) + + progress_bar = tqdm(range(num_training_steps)) + + model.train() + for epoch in range(num_epochs): + for batch in train_dataloader: +- batch = {k: v.to(device) for k, v in batch.items()} + outputs = model(**batch) + loss = outputs.loss +- loss.backward() ++ accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +要添加的第一行是導入`Accelerator`。第二行實例化一個 `Accelerator`對象 ,它將查看環境並初始化適當的分佈式設置。 🤗 Accelerate 為您處理數據在設備間的傳遞,因此您可以刪除將模型放在設備上的那行代碼(或者,如果您願意,可使用 `accelerator.device` 代替 `device` )。 + +然後大部分工作會在將數據加載器、模型和優化器發送到的`accelerator.prepare()`中完成。這將會把這些對象包裝在適當的容器中,以確保您的分佈式訓練按預期工作。要進行的其餘更改是刪除將`batch`放在 `device` 的那行代碼(同樣,如果您想保留它,您可以將其更改為使用 `accelerator.device` ) 並將 `loss.backward()` 替換為`accelerator.backward(loss)`。 + + +⚠️ 為了使雲端 TPU 提供的加速發揮最大的效益,我們建議使用標記器(tokenizer)的 `padding=max_length` 和 `max_length` 參數將您的樣本填充到固定長度。 + + +如果您想複製並粘貼來直接運行,以下是 🤗 Accelerate 的完整訓練循環: + +```py +from accelerate import Accelerator +from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler + +accelerator = Accelerator() + +model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) +optimizer = AdamW(model.parameters(), lr=3e-5) + +train_dl, eval_dl, model, optimizer = accelerator.prepare( + train_dataloader, eval_dataloader, model, optimizer +) + +num_epochs = 3 +num_training_steps = num_epochs * len(train_dl) +lr_scheduler = get_scheduler( + "linear", + optimizer=optimizer, + num_warmup_steps=0, + num_training_steps=num_training_steps, +) + +progress_bar = tqdm(range(num_training_steps)) + +model.train() +for epoch in range(num_epochs): + for batch in train_dl: + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) +``` + +把這個放在 `train.py` 文件中,可以讓它在任何類型的分佈式設置上運行。要在分佈式設置中試用它,請運行以下命令: + +```bash +accelerate config +``` + +這將詢問您幾個配置的問題並將您的回答轉儲到此命令使用的配置文件中: + +``` +accelerate launch train.py +``` + +這將啟動分佈式訓練 + +這將啟動分佈式訓練。如果您想在 Notebook 中嘗試此操作(例如,在 Colab 上使用 TPU 進行測試),只需將代碼粘貼到 `training_function()` 並使用以下命令運行最後一個單元格: + +```python +from accelerate import notebook_launcher + +notebook_launcher(training_function) +``` + +您可以在[🤗 Accelerate repo](https://github.com/huggingface/accelerate/tree/main/examples)找到更多的示例。 diff --git a/chapters/zh-TW/chapter3/5.mdx b/chapters/zh-TW/chapter3/5.mdx new file mode 100644 index 000000000..a75f39af7 --- /dev/null +++ b/chapters/zh-TW/chapter3/5.mdx @@ -0,0 +1,25 @@ + + +# 微調,檢查! + + + +這是非常令人高興的! 在前兩章中,您瞭解了模型和標記器(tokenizer),現在您知道如何針對您自己的數據對它們進行微調。回顧一下,在本章中,您: + +{#if fw === 'pt'} +* 瞭解了[Hub](https://huggingface.co/datasets)中的數據集 +* 學習瞭如何加載和預處理數據集,包括使用動態填充和整理器 +* 實現您自己的模型微調和評估 +* 實施了一個較為底層的訓練循環 +* 使用 🤗 Accelerate 輕鬆調整您的訓練循環,使其適用於多個 GPU 或 TPU + +{:else} +* 瞭解了[Hub](https://huggingface.co/datasets)中的數據集 +* 學習瞭如何加載和預處理數據集 +* 學習瞭如何使用 Keras 微調和評估模型 +* 實現了自定義指標 + +{/if} diff --git a/chapters/zh-TW/chapter3/6.mdx b/chapters/zh-TW/chapter3/6.mdx new file mode 100644 index 000000000..9f90c3fc3 --- /dev/null +++ b/chapters/zh-TW/chapter3/6.mdx @@ -0,0 +1,289 @@ + + + + +# End-of-chapter quiz + + + +Test what you learned in this chapter! + +### 1.「情緒」數據集包含標記有情緒的 Twitter 消息。在[ Hub ]( https://huggingface.co/datasets 集線器)中搜索它,然後讀取數據集卡。哪一個不是它的基本情感? + + +### 2.在[ Hub ]( https://huggingface.co/datasets 集線器)中搜索‘ ar _ sarcasm’數據集,它支持哪個任務? + dataset card !" + }, + { + text: "命名實體識別", + explain: "不是這樣的ーー再看看 < a href =’https://huggingface.co/datasets/ar _ sarcasm’> dataset card !" + }, + { + text: "回答問題", + explain: "Alas, this question was not answered correctly. 再試一次!" + } + ]} +/> + +### 3.BERT 模型期望如何處理一對句子? + [ CLS ] 特殊令牌在開始時是必需的,但是這不是唯一的事情!" + }, + { + text: "表示句子1[ SEP ]的符號表示句子2[ SEP ]", + explain: "沒錯!", + correct: true + }, + { + text: "表示句子1[ SEP ]的符號表示句子2", + explain: "開頭需要一個 < code > [ CLS ] 特殊標記,還需要一個 < code > [ SEP ] 特殊標記來分隔兩個句子,但這還不是全部!" + } + ]} +/> + +{#if fw === 'pt'} +### 4.‘ Dataset.map ()’方法的好處是什麼? + + +### 5.什麼是動態填充? + + +### 6.校對函數的用途是什麼? + > 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.", + correct: true + }, + { + text: "它預處理整個數據集。", + explain: "這將是一個預處理函數,而不是校對函數。" + }, + { + text: "它截斷數據集中的序列。", + explain: "校對函數用於處理單個批處理,而不是整個數據集。如果您對截斷感興趣,可以使用 < code > tokenizer 的 < truncate 參數。" + } + ]} +/> + +### 7.當你用一個預先訓練過的語言模型(例如‘ bert-base-uncased’)實例化一個‘ AutoModelForXxx’類,這個類對應於一個不同於它所被訓練的任務時會發生什麼? + Trainer 所做的,而不是 Accelerate 庫。再試一次!", + correct: true + }, + { + text: "丟棄預先訓練好的模型頭部。", + explain: "Something else needs to happen. 再試一次!" + }, + { + text: "沒有,因為模型仍然可以針對不同的任務進行微調。", + explain: "這個經過訓練的模特的頭沒有經過訓練來解決這個問題,所以我們應該丟掉這個頭!" + } + ]} +/> + +### 8.訓練爭論的目的是什麼? + TrainingArguments 。" + }, + { + text: "它只包含用於評估的超參數。", + explain: "In the example, we specified where the model and its checkpoints will be saved. 再試一次!" + }, + { + text: "您可以輕鬆地計算與數據集相關的指標。", + explain: "In the example, we used an evaluation_strategy as well, so this impacts evaluation. 再試一次!" + } + ]} +/> + +### 9.為什麼要使用 Accelerate 庫? +Trainer, not the 🤗 Accelerate library. 再試一次!" + }, + { + text: "它使我們的訓練循環工作在分佈式策略上", + explain: "正確! 隨著加速,你的訓練循環將為多個 gpu 和 TPUs 工作。", + correct: true + }, + { + text: "它提供了更多的優化功能。", + explain: "不,Accelerate 庫不提供任何優化功能。" + } + ]} +/> + +{:else} +### 4.當你用一個預先訓練過的語言模型(例如‘ bert-base-uncased’)實例化一個‘ tfautoodelforxxx’類時,會發生什麼? + + +### 5.來自“變壓器”的 TensorFlow 模型已經是 Keras 模型,這有什麼好處? + TPUStrategy scope 中的所有內容,包括模型的初始化。" + }, + { + text: "您可以利用現有的方法,如 < code > compile () 、 < code > fit () < c/ode > 和 < code > predict () 。", + explain: "正確! 一旦你有了這些數據,在這些數據上進行培訓只需要很少的工作。", + correct: true + }, + { + text: "你可以學習 Keras 和變形金剛。", + explain: "沒錯,但我們要找的是別的東西:)", + correct: true + }, + { + text: "困惑", + explain: "Keras 幫助我們訓練和評估模型,而不是計算與數據集相關的度量。" + } + ]} +/> + +### 6.如何定義自己的定製度量? + tfkeras.metrics. Metric 。", + explain: "太好了!", + correct: true + }, + { + text: "使用 Keras 函數 API。", + explain: "再試一次!" + }, + { + text: "通過使用帶簽名的可調用 < code > metric _ fn (y _ true,y _ pred) 。", + explain: "正確!", + correct: true + }, + { + text: "通過谷歌搜索。", + explain: "這不是我們要找的答案,但它應該能幫助你找到答案。", + correct: true + } + ]} +/> + +{/if} \ No newline at end of file diff --git a/chapters/zh-TW/chapter4/1.mdx b/chapters/zh-TW/chapter4/1.mdx new file mode 100644 index 000000000..97500a0a3 --- /dev/null +++ b/chapters/zh-TW/chapter4/1.mdx @@ -0,0 +1,20 @@ +# The Hugging Face Hub + + + +[Hugging Face Hub](https://huggingface.co/) -- 我們的主網站,是一個中央平臺,在這個網站上任何人都可以查找、使用和貢獻新的最先進的模型和數據集。它擁有各種各樣的模型,公開可用的模型超過 10,000個。我們在本章去探索Hub中的模型,並在第 5 章中探索Hub中的數據集。 + +Hub 中的模型不僅限於 🤗 Transformers 甚至 NLP。有用於自然語言處理的[Flair](https://github.com/flairNLP/flair),[AllenNLP](https://github.com/allenai/allennlp),[Asteroid](https://github.com/asteroid-team/asteroid)和用於音頻檢測的[pyannote](https://github.com/pyannote/pyannote-audio),以及對於視覺的[timm](https://github.com/rwightman/pytorch-image-models),這些例子只是Hub中冰山一角,更多的模型。可以由你去探索。 + +這些模型中的每一個都作為 Git 存儲庫託管,這允許進行版本控制和重現。在 Hub 上共享模型意味著將其向社區開放,讓任何希望使用它的人都可以輕鬆訪問它,從而使其他人不用為訓練模型而苦惱就可以直接使用模型。 + +此外,在 Hub 上共享模型會自動為該模型部署託管的推理 API。社區中的任何人都可以直接在模型頁面上自由地測試它,使用自定義輸入和適當的小部件。 + +最棒的是是在 Hub 上共享和使用任何公共模型是完全免費的!如果您不想公開模型,也存在[付費計劃](https://huggingface.co/pricing)。下面的視頻顯示瞭如何使用 Hub。 + + + +這部分需要有一個 Huggingface.co 帳戶,因為我們將在 Hugging Face Hub 上創建和管理存儲庫:[創建一個賬戶](https://huggingface.co/join) \ No newline at end of file diff --git a/chapters/zh-TW/chapter4/2.mdx b/chapters/zh-TW/chapter4/2.mdx new file mode 100644 index 000000000..66268aeef --- /dev/null +++ b/chapters/zh-TW/chapter4/2.mdx @@ -0,0 +1,97 @@ + + +# 使用預訓練的模型 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +模型中心使選擇合適的模型變得簡單,因此只需幾行代碼即可在任何下游庫中使用它。讓我們來看看如何實際使用這些模型之一,以及如何回饋社區。 + +假設我們正在尋找一種可以執行**mask**填充的French-based模型。 + +
+Selecting the Camembert model. +
+ +我們選擇 **camembert-base** 檢查點來嘗試一下。我們需要做的僅僅是輸入 `camembert-base`標識符!正如您在前幾章中看到的,我們可以使用 **pipeline()** 功能: + +```py +from transformers import pipeline + +camembert_fill_mask = pipeline("fill-mask", model="camembert-base") +results = camembert_fill_mask("Le camembert est :)") +``` + +```python out +[ + {'sequence': 'Le camembert est délicieux :)', 'score': 0.49091005325317383, 'token': 7200, 'token_str': 'délicieux'}, + {'sequence': 'Le camembert est excellent :)', 'score': 0.1055697426199913, 'token': 2183, 'token_str': 'excellent'}, + {'sequence': 'Le camembert est succulent :)', 'score': 0.03453313186764717, 'token': 26202, 'token_str': 'succulent'}, + {'sequence': 'Le camembert est meilleur :)', 'score': 0.0330314114689827, 'token': 528, 'token_str': 'meilleur'}, + {'sequence': 'Le camembert est parfait :)', 'score': 0.03007650189101696, 'token': 1654, 'token_str': 'parfait'} +] +``` + +如您所見,在管道中加載模型非常簡單。您唯一需要注意的是所選檢查點是否適合它將用於的任務。例如,這裡我們正在加載 **camembert-base** 檢查點在 **fill-mask** 管道,這完全沒問題。但是如果我們要在 **text-classification** 管道,結果沒有任何意義,因為 **camembert-base** 不適合這個任務!我們建議使用 Hugging Face Hub 界面中的任務選擇器來選擇合適的檢查點: + +
+The task selector on the web interface. +
+ +您還可以直接使用模型架構實例化檢查點: + +{#if fw === 'pt'} +```py +from transformers import CamembertTokenizer, CamembertForMaskedLM + +tokenizer = CamembertTokenizer.from_pretrained("camembert-base") +model = CamembertForMaskedLM.from_pretrained("camembert-base") +``` + +然而,我們建議使用[Auto* 類](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes),因為Auto* 類設計與架構無關。前面的代碼示例將只能在 CamemBERT 架構中加載可用的檢查點,但使用 **Auto*** 類使切換檢查點變得簡單: + +```py +from transformers import AutoTokenizer, AutoModelForMaskedLM + +tokenizer = AutoTokenizer.from_pretrained("camembert-base") +model = AutoModelForMaskedLM.from_pretrained("camembert-base") +``` +{:else} +```py +from transformers import CamembertTokenizer, TFCamembertForMaskedLM + +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: +然而,我們建議使用[`TFAuto*` 類](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes),因為`TFAuto*`類設計與架構無關。前面的代碼示例將只能在 CamemBERT 架構中加載可用的檢查點,但使用 `TFAuto*` 類使切換檢查點變得簡單: + +```py +from transformers import AutoTokenizer, TFAutoModelForMaskedLM + +tokenizer = AutoTokenizer.from_pretrained("camembert-base") +model = TFAutoModelForMaskedLM.from_pretrained("camembert-base") +``` +{/if} + + +使用預訓練模型時,一定要檢查它是如何訓練的,在哪些數據集上,它的限制和它的偏差。所有這些信息都應在其模型卡片上註明。 + diff --git a/chapters/zh-TW/chapter4/3.mdx b/chapters/zh-TW/chapter4/3.mdx new file mode 100644 index 000000000..9c324b118 --- /dev/null +++ b/chapters/zh-TW/chapter4/3.mdx @@ -0,0 +1,648 @@ + + +# 共享預訓練模型 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +在下面的步驟中,我們將看看將預訓練模型分享到 🤗 Hub 的最簡單方法。有可用的工具和實用程序可以讓直接在 Hub 上共享和更新模型變得簡單,我們將在下面進行探討。 + + + +我們鼓勵所有訓練模型的用戶通過與社區共享來做出貢獻——共享模型,即使是在非常特定的數據集上進行訓練,也將幫助他人,節省他們的時間和計算資源,並提供對有用的訓練工件的訪問。反過來,您可以從其他人所做的工作中受益! + +創建新模型存儲庫的方法有以下三種: + +- 使用 push_to_hub API 接口 +- 使用 huggingface_hub Python 庫 +- 使用 web 界面 + +創建存儲庫後,您可以通過 git 和 git-lfs 將文件上傳到其中。我們將在以下部分引導您創建模型存儲庫並將文件上傳到它們 + + +## 使用 push_to_hub API + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +將文件上傳到集線器的最簡單方法是利用 **push_to_hub** API 接口。 + +在繼續之前,您需要生成一個身份驗證令牌,以便 **huggingface_hub** API 知道您是誰以及您對哪些名稱空間具有寫入權限。確保你在一個環境中 **transformers** 已安裝(見[Setup](/course/chapter0))。如果您在筆記本中,可以使用以下功能登錄: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +在終端中,您可以運行: + +```bash +huggingface-cli login +``` + +在這兩種情況下,系統都會提示您輸入用戶名和密碼,這與您用於登錄 Hub 的用戶名和密碼相同。如果您還沒有 Hub 配置文件,則應該創建一個[here](https://huggingface.co/join)。 + +好的!您現在已將身份驗證令牌存儲在緩存文件夾中。讓我們創建一些存儲庫! + +{#if fw === 'pt'} + +如果你玩過 **Trainer** 用於訓練模型的 API,將其上傳到 Hub 的最簡單方法是設置 **push_to_hub=True** 當你定義你的 **TrainingArguments** : + +```py +from transformers import TrainingArguments + +training_args = TrainingArguments( + "bert-finetuned-mrpc", save_strategy="epoch", push_to_hub=True +) +``` + +你聲明 **trainer.train()** 的時候, 這 **Trainer** 然後每次將您的模型保存到您的命名空間中的存儲庫中時(這裡是每個時代),它將上傳到集線器。該存儲庫將命名為您選擇的輸出目錄(此處 **bert-finetuned-mrpc** ) 但您可以選擇不同的名稱 **hub_model_id = a_different_name** 。 + +要將您的模型上傳到您所屬的組織,只需將其傳遞給 **hub_model_id = my_organization/my_repo_name** 。 + +訓練結束後,你應該做最後的 **trainer.push_to_hub()** 上傳模型的最新版本。它還將生成包含所有相關元數據的模型卡,報告使用的超參數和評估結果!以下是您可能會在此類模型卡中找到的內容示例: + +
+ An example of an auto-generated model card. +
+ + +{:else} + + +如果您使用Keras來訓練您的模型,則將其上傳到Hub的最簡單方法是在調用 **model.fit()** 時傳遞**PushToHubCallback**: + +```py +from transformers import PushToHubCallback + +callback = PushToHubCallback( + "bert-finetuned-mrpc", save_strategy="epoch", tokenizer=tokenizer +) +``` + +然後,您應該在對**model.fit()**的調用中添加**callbacks=[callback]**。然後,每次將模型保存在命名空間的存儲庫中(此處為每個 epoch)時,回調都會將模型上傳到 Hub。該存儲庫的名稱將類似於您選擇的輸出目錄(此處為**bert-finetuned-mrpc**),但您可以選擇另一個名稱,名稱為**hub_model_id = a_different_name**。 + +要將您的模型上傳到您所屬的組織,只需將其傳遞給 **hub_model_id = my_organization/my_repo_name** 。 + +{/if} + +在較低級別,可以通過模型、標記器和配置對象直接訪問模型中心 **push_to_hub()** 方法。此方法負責創建存儲庫並將模型和標記器文件直接推送到存儲庫。與我們將在下面看到的 API 不同,不需要手動處理。 + +為了瞭解它是如何工作的,讓我們首先初始化一個模型和一個標記器: + +{#if fw === 'pt'} + +```py +from transformers import AutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = AutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` + +{:else} + +```py +from transformers import TFAutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = TFAutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) +``` + +{/if} + +你可以自由地用這些做任何你想做的事情——向標記器添加標記,訓練模型,微調它。一旦您對生成的模型、權重和標記器感到滿意,您就可以利用 **push_to_hub()** 方法直接在 **model** 中: + +```py +model.push_to_hub("dummy-model") +``` + +這將創建新的存儲庫 **dummy-model** 在您的個人資料中,並用您的模型文件填充它。 +對標記器執行相同的操作,以便所有文件現在都可以在此存儲庫中使用: + +```py +tokenizer.push_to_hub("dummy-model") +``` + +如果您屬於一個組織,只需指定 **organization** 上傳到該組織的命名空間的參數: + +```py +tokenizer.push_to_hub("dummy-model", organization="huggingface") +``` + +如果您希望使用特定的 Hugging Face 令牌,您可以自由地將其指定給 **push_to_hub()** 方法也是: + +```py +tokenizer.push_to_hub("dummy-model", organization="huggingface", use_auth_token="") +``` + +現在前往模型中心找到您新上傳的模型:*https://huggingface.co/user-or-organization/dummy-model*。 + +單擊“文件和版本”選項卡,您應該會在以下屏幕截圖中看到可見的文件: + +{#if fw === 'pt'} +
+Dummy model containing both the tokenizer and model files. +
+{:else} +
+Dummy model containing both the tokenizer and model files. +
+{/if} + + + +✏️ **試試看**!獲取與檢查點關聯的模型和標記器,並使用該方法將它們上傳到您的命名空間中的存儲庫。在刪除之前,請仔細檢查該存儲庫是否正確顯示在您的頁面上。 + + + +如您所見, **push_to_hub()** 方法接受多個參數,從而可以上傳到特定的存儲庫或組織命名空間,或使用不同的 API 令牌。我們建議您查看直接在[🤗 Transformers documentation](https://huggingface.co/transformers/model_sharing.html)瞭解什麼是可能的 + +這 **push_to_hub()** 方法由[huggingface_hub](https://github.com/huggingface/huggingface_hub)Python 包,為 Hugging Face Hub 提供直接 API。它集成在 🤗 Transformers 和其他幾個機器學習庫中,例如[allenlp](https://github.com/allenai/allennlp).雖然我們在本章中專注於 🤗 Transformers 集成,但將其集成到您自己的代碼或庫中很簡單。 + +跳到最後一部分,瞭解如何將文件上傳到新創建的存儲庫! + +## 使用 huggingface_hub python庫 + +這 **huggingface_hub** Python 庫是一個包,它為模型和數據集中心提供了一組工具。它為常見任務提供了簡單的方法和類,例如 +獲取有關集線器上存儲庫的信息並對其進行管理。它提供了在 git 之上工作的簡單 API 來管理這些存儲庫的內容並集成 Hub +在您的項目和庫中。 + +類似於使用 **push_to_hub** API,這將要求您將 API 令牌保存在緩存中。為此,您需要使用 **login** 來自 CLI 的命令,如上一節所述(同樣,確保在這些命令前面加上 **!** 字符(如果在 Google Colab 中運行): + +```bash +huggingface-cli login +``` + +這 **huggingface_hub** 包提供了幾種對我們有用的方法和類。首先,有幾種方法可以管理存儲庫的創建、刪除等: + +```python no-format +from huggingface_hub import ( + # User management + login, + logout, + whoami, + + # Repository creation and management + create_repo, + delete_repo, + update_repo_visibility, + + # And some methods to retrieve/change information about the content + list_models, + list_datasets, + list_metrics, + list_repo_files, + upload_file, + delete_file, +) +``` + + +此外,它還提供了非常強大的 **Repository** 用於管理本地存儲庫的類。我們將在接下來的幾節中探討這些方法和該類,以瞭解如何利用它們。 + +這 **create_repo** 方法可用於在集線器上創建新存儲庫: + + +```py +from huggingface_hub import create_repo + +create_repo("dummy-model") +``` + +這將創建存儲庫 **dummy-model** 在您的命名空間中。如果願意,您可以使用 **organization** 爭論: + +```py +from huggingface_hub import create_repo + +create_repo("dummy-model", organization="huggingface") +``` + +這將創建 **dummy-model** 存儲庫中的 **huggingface** 命名空間,假設您屬於該組織。 +其他可能有用的參數是: + +- private 以指定存儲庫是否應對其他人可見。 +- token 如果您想用給定的令牌覆蓋存儲在緩存中的令牌。 +- repo_type 如果你想創建一個或一個替代一個的而不是模型。接受的值和 datasetspace "dataset""space"。 + +創建存儲庫後,我們應該向其中添加文件!跳到下一部分以查看可以處理此問題的三種方法。 + + +## 使用網絡界面 + +Web 界面提供了直接在 Hub 中管理存儲庫的工具。使用該界面,您可以輕鬆創建存儲庫、添加文件(甚至是大文件!)、探索模型、可視化差異等等。 + +要創建新的存儲庫,請訪問[huggingface.co/new](https://huggingface.co/new): + +
+Page showcasing the model used for the creation of a new model repository. +
+ +首先,指定存儲庫的所有者:這可以是您或您所屬的任何組織。如果您選擇一個組織,該模型將出現在該組織的頁面上,並且該組織的每個成員都可以為存儲庫做出貢獻。 + +接下來,輸入您的模型名稱。這也將是存儲庫的名稱。最後,您可以指定您的模型是公開的還是私有的。私人模特要求您擁有付費 Hugging Face 帳戶,並允許您將模特隱藏在公眾視野之外。 + +創建模型存儲庫後,您應該看到如下頁面: + +
+An empty model page after creating a new repository. +
+ +這是您的模型將被託管的地方。要開始填充它,您可以直接從 Web 界面添加 README 文件。 + +
+The README file showing the Markdown capabilities. +
+ +README 文件在 Markdown 中 - 隨意使用它!本章的第三部分致力於構建模型卡。這些對於為您的模型帶來價值至關重要,因為它們是您告訴其他人它可以做什麼的地方。 + +如果您查看“文件和版本”選項卡,您會發現那裡還沒有很多文件——只有自述文件你剛剛創建和.git 屬性跟蹤大文件的文件。 + +
+The 'Files and versions' tab only shows the .gitattributes and README.md files. +
+ +接下來我們將看看如何添加一些新文件。 + +## 上傳模型文件 + +Hugging Face Hub 上的文件管理系統基於用於常規文件的 git 和 git-lfs(代表[Git Large File Storage](https://git-lfs.github.com/)) 對於較大的文件。 + +在下一節中,我們將介紹將文件上傳到 Hub 的三種不同方式:通過 **huggingface_hub** 並通過 git 命令。 + +### The `upload_file` approach + +使用 **upload_file** 不需要在您的系統上安裝 git 和 git-lfs。它使用 HTTP POST 請求將文件直接推送到 🤗 Hub。這種方法的一個限制是它不能處理大於 5GB 的文件。 +如果您的文件大於 5GB,請按照下面詳述的另外兩種方法進行操作。API 可以按如下方式使用: + +```py +from huggingface_hub import upload_file + +upload_file( + "/config.json", + path_in_repo="config.json", + repo_id="/dummy-model", +) +``` + +這將上傳文件 **config.json** 可在 **path_to_file** 到存儲庫的根目錄 **config.json** , 到 **dummy-model** 存儲庫。 +其他可能有用的參數是: + +- token,如果要通過給定的令牌覆蓋緩存中存儲的令牌。 +- repo_type, 如果你想要上傳一個 `dataset` 或一個 `space` 而不是模型。 接受的值為 `"dataset"` 和 `"space"`. + + +### The `Repository` class + +以類似 git 的方式管理本地存儲庫。它抽象了 git 可能遇到的大部分痛點,以提供我們需要的所有功能。 + +使用這個類需要安裝 git 和 git-lfs,所以確保你已經安裝了 git-lfs(參見[here](https://git-lfs.github.com/)安裝說明)並在開始之前進行設置。 + +為了開始使用我們剛剛創建的存儲庫,我們可以通過克隆遠程存儲庫將其初始化到本地文件夾開始: + +```py +from huggingface_hub import Repository + +repo = Repository("", clone_from="/dummy-model") +``` + +這創建了文件夾 **path_to_dummy_folder** 在我們的工作目錄中。該文件夾僅包含 **.gitattributes** 文件,因為這是通過實例化存儲庫時創建的唯一文件 **create_repo**。 + +從現在開始,我們可以利用幾種傳統的 git 方法: + +```py +repo.git_pull() +repo.git_add() +repo.git_commit() +repo.git_push() +repo.git_tag() +``` + +另外!我們建議您查看 **Repository** 可用文件[here](https://github.com/huggingface/huggingface_hub/tree/main/src/huggingface_hub#advanced-programmatic-repository-management)有關所有可用方法的概述。 + +目前,我們有一個模型和一個標記器,我們希望將其推送到集線器。我們已經成功克隆了存儲庫,因此我們可以將文件保存在該存儲庫中。 + +我們首先通過拉取最新更改來確保我們的本地克隆是最新的: + +```py +repo.git_pull() +``` + +完成後,我們保存模型和標記器文件: + +```py +model.save_pretrained("") +tokenizer.save_pretrained("") +``` + +這 **path_to_dummy_folder** 現在包含所有模型和標記器文件。我們遵循通常的 git 工作流程,將文件添加到暫存區,提交它們並將它們推送到集線器: + +```py +repo.git_add() +repo.git_commit("Add model and tokenizer files") +repo.git_push() +``` + +恭喜!您剛剛將第一個文件推送到hub上。 + +### The git-based approach + +這是上傳文件的非常簡單的方法:我們將直接使用 git 和 git-lfs 來完成。大多數困難都被以前的方法抽象掉了,但是下面的方法有一些警告,所以我們將遵循一個更復雜的用例。 + +使用這個類需要安裝 git 和 git-lfs,所以請確保你有[git-lfs](https://git-lfs.github.com/)安裝(請參閱此處瞭解安裝說明)並在開始之前進行設置。 + +首先從初始化 git-lfs 開始: + +```bash +git lfs install +``` + +```bash +Updated git hooks. +Git LFS initialized. +``` + +完成後,第一步是克隆您的模型存儲庫: + +```bash +git clone https://huggingface.co// +``` + +我的用戶名是 **lysandre** 我使用了模型名稱 **dummy** ,所以對我來說,命令最終如下所示: + +``` +git clone https://huggingface.co/lysandre/dummy +``` + +我現在有一個名為的文件夾假在我的工作目錄中。我能 **cd** 進入文件夾並查看內容: + +```bash +cd dummy && ls +``` + +```bash +README.md +``` + +如果您剛剛使用 Hugging Face Hub 創建了您的存儲庫 **create_repo** 方法,這個文件夾應該只包含一個隱藏的 **.gitattributes** 文件。如果您按照上一節中的說明使用 Web 界面創建存儲庫,則該文件夾應包含一個自述文件文件旁邊的隱藏 **.gitattributes** 文件,如圖所示。 + +添加一個常規大小的文件,例如配置文件、詞彙文件,或者基本上任何幾兆字節以下的文件,就像在任何基於 git 的系統中所做的一樣。但是,更大的文件必須通過 git-lfs 註冊才能將它們推送到擁抱臉。 + +讓我們回到 Python 來生成我們想要提交到我們的虛擬存儲庫的模型和標記器: + +{#if fw === 'pt'} +```py +from transformers import AutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = AutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + +# Do whatever with the model, train it, fine-tune it... + +model.save_pretrained("") +tokenizer.save_pretrained("") +``` +{:else} +```py +from transformers import TFAutoModelForMaskedLM, AutoTokenizer + +checkpoint = "camembert-base" + +model = TFAutoModelForMaskedLM.from_pretrained(checkpoint) +tokenizer = AutoTokenizer.from_pretrained(checkpoint) + +# Do whatever with the model, train it, fine-tune it... + +model.save_pretrained("") +tokenizer.save_pretrained("") +``` +{/if} + +現在我們已經保存了一些模型和標記器工件,讓我們再看看假文件夾: + +```bash +ls +``` + +{#if fw === 'pt'} +```bash +config.json pytorch_model.bin README.md sentencepiece.bpe.model special_tokens_map.json tokenizer_config.json tokenizer.json +``` + +If you look at the file sizes (for example, with `ls -lh`), you should see that the model state dict file (*pytorch_model.bin*) is the only outlier, at more than 400 MB. + +{:else} +```bash +config.json README.md sentencepiece.bpe.model special_tokens_map.json tf_model.h5 tokenizer_config.json tokenizer.json +``` + +如果您查看文件大小(例如, **ls -lh** ),您應該會看到模型狀態 dict 文件 (pytorch_model.bin) 是唯一的異常值,超過 400 MB。 + +{/if} + + +✏️ 從 web 界面創建存儲庫時,*.gitattributes* 文件會自動設置為將具有某些擴展名的文件,例如 *.bin* 和 *.h5* 視為大文件,git-lfs 會對其進行跟蹤您無需進行必要的設置。 + + +我們現在可以繼續進行,就像我們通常使用傳統 Git 存儲庫一樣。我們可以使用以下命令將所有文件添加到 Git 的暫存環境中 **git add** 命令: + +```bash +git add . +``` + +然後我們可以查看當前暫存的文件: + +```bash +git status +``` + +{#if fw === 'pt'} +```bash +On branch main +Your branch is up to date with 'origin/main'. + +Changes to be committed: + (use "git restore --staged ..." to unstage) + modified: .gitattributes + new file: config.json + new file: pytorch_model.bin + new file: sentencepiece.bpe.model + new file: special_tokens_map.json + new file: tokenizer.json + new file: tokenizer_config.json +``` +{:else} +```bash +On branch main +Your branch is up to date with 'origin/main'. + +Changes to be committed: + (use "git restore --staged ..." to unstage) + modified: .gitattributes + new file: config.json + new file: sentencepiece.bpe.model + new file: special_tokens_map.json + new file: tf_model.h5 + new file: tokenizer.json + new file: tokenizer_config.json +``` +{/if} + +同樣,我們可以確保 git-lfs 使用其跟蹤正確的文件 **status** 命令: + +```bash +git lfs status +``` + +{#if fw === 'pt'} +```bash +On branch main +Objects to be pushed to origin/main: + + +Objects to be committed: + + config.json (Git: bc20ff2) + pytorch_model.bin (LFS: 35686c2) + sentencepiece.bpe.model (LFS: 988bc5a) + special_tokens_map.json (Git: cb23931) + tokenizer.json (Git: 851ff3e) + tokenizer_config.json (Git: f0f7783) + +Objects not staged for commit: + + +``` + +我們可以看到所有文件都有 **Git** 作為處理程序,除了其中有 **LFS**的*pytorch_model.bin* 和 *sentencepiece.bpe.model*。 + +{:else} +```bash +On branch main +Objects to be pushed to origin/main: + + +Objects to be committed: + + config.json (Git: bc20ff2) + sentencepiece.bpe.model (LFS: 988bc5a) + special_tokens_map.json (Git: cb23931) + tf_model.h5 (LFS: 86fce29) + tokenizer.json (Git: 851ff3e) + tokenizer_config.json (Git: f0f7783) + +Objects not staged for commit: + + +``` + +我們可以看到所有文件都有 **Git** 作為處理程序,除了其中有 **LFS**的*t5_model.h5*。 + +{/if} + +Let's proceed to the final steps, committing and pushing to 讓我們繼續最後的步驟,提交併推動擁抱臉遠程倉庫: + +```bash +git commit -m "First model version" +``` + +{#if fw === 'pt'} +```bash +[main b08aab1] First model version + 7 files changed, 29027 insertions(+) + 6 files changed, 36 insertions(+) + create mode 100644 config.json + create mode 100644 pytorch_model.bin + create mode 100644 sentencepiece.bpe.model + create mode 100644 special_tokens_map.json + create mode 100644 tokenizer.json + create mode 100644 tokenizer_config.json +``` +{:else} +```bash +[main b08aab1] First model version + 6 files changed, 36 insertions(+) + create mode 100644 config.json + create mode 100644 sentencepiece.bpe.model + create mode 100644 special_tokens_map.json + create mode 100644 tf_model.h5 + create mode 100644 tokenizer.json + create mode 100644 tokenizer_config.json +``` +{/if} + +推送可能需要一些時間,具體取決於您的互聯網連接速度和文件大小: + +```bash +git push +``` + +```bash +Uploading LFS objects: 100% (1/1), 433 MB | 1.3 MB/s, done. +Enumerating objects: 11, done. +Counting objects: 100% (11/11), done. +Delta compression using up to 12 threads +Compressing objects: 100% (9/9), done. +Writing objects: 100% (9/9), 288.27 KiB | 6.27 MiB/s, done. +Total 9 (delta 1), reused 0 (delta 0), pack-reused 0 +To https://huggingface.co/lysandre/dummy + 891b41d..b08aab1 main -> main +``` + +{#if fw === 'pt'} +If we take a look at the model repository when this is finished, we can see all the recently added files: + +
+The 'Files and versions' tab now contains all the recently uploaded files. +
+ +UI 允許您瀏覽模型文件和提交,並查看每個提交引入的差異: + +
+The diff introduced by the recent commit. +
+ +{:else} + +如果我們在完成後查看模型存儲庫,我們可以看到所有最近添加的文件: + +
+The 'Files and versions' tab now contains all the recently uploaded files. +
+ +UI 允許您瀏覽模型文件和提交,並查看每個提交引入的差異: + +
+The diff introduced by the recent commit. +
+{/if} diff --git a/chapters/zh-TW/chapter4/4.mdx b/chapters/zh-TW/chapter4/4.mdx new file mode 100644 index 000000000..b5eecf1a8 --- /dev/null +++ b/chapters/zh-TW/chapter4/4.mdx @@ -0,0 +1,87 @@ +# 構建模型卡片 + + + +模型卡片是一個配置文件,可以說與模型存儲庫中的模型和 tokenizer 文件一樣重要。它包含了模型的核心定義,確保了社區成員可以復現模型的結果,並提供一個其他成員可以在這個模型基礎上構建他們的組件的平臺。 + +記錄訓練和評估過程並提供有關使用的數據以及已完成的預處理和後續處理的足夠信息,有助於其他人瞭解對模型的能力——確保模型存在和目前的限制、偏差可以識別和理解。 + +因此,創建清晰定義模型的模型卡片是非常重要的一步。在這裡,我們提供了一些可以幫助您解決此問題的方法。創建模型卡片是通過您之前看到的 Markdown 文件:README.md 。 + +「模型卡片」的概念源於谷歌的一個研究方向, Margaret Mitchell 等人在論文[“Model Cards for Model Reporting”](https://arxiv.org/abs/1810.03993)中首次提出,此處包含的許多信息均基於該論文,我們建議您查看這篇論文以瞭解為什麼模型卡片在重視可重複性、可重用性和公平性的時候中如此重要。 + +模型卡通常以非常簡短的概述開始,說明模型的用途,然後是模型卡片需要的其他信息: + +- 模型描述 +- 預期用途和限制 +- 如何使用 +- 侷限性和偏見 +- 訓練數據 +- 訓練程序 +- 評價結果 + +讓我們來看看每個部分應該包含什麼。 + +### 模型描述: + +提供了有關模型的基本詳細信息。這包括架構、版本、如果它是在論文中介紹的,是否有原始的實現可用?作者以及有關模型的一般信息、任何版權都應歸於此處。這一部分還可以提及有關訓練程序、參數和重要免責聲明的一般信息。 + +### 預期用途和限制: + +在此描述模型可以適用的例子,包括可以應用它的語言、領域。模型卡的這一部分還可以記錄已知超出模型範圍的區域,或者可能表現不佳的區域。 + +### 使用方法: + +此部分應包括一些有關如何使用模型的示例。這可以展示使用 **pipeline()** 函數、模型和標記器類的使用以及其他任何您認為可能有幫助的代碼。 + +### 訓練數據: + +這部分應該指出模型是在哪個數據集上訓練的。也歡迎對數據集進行簡要描述。 + +### 訓練過程: + +此部分中,您應該描述從再現性角度來看有用的訓練的所有相關方面。這包括對數據進行的任何預處理和後處理,以及模型訓練的批量數、批量大小、學習率等細節。 + +### 變量和指標: + +在這裡,您應該描述您用於評估的指標,以及您測量的不同因素。提及使用了哪些指標、在哪個數據集上以及哪個數據集部分,可以輕鬆地將您的模型的性能與其他模型的性能進行比較。 + +### 評價結果: + +這些應該提前在前面的部分告知,例如預期的使用效果和示例。最後,提供模型在評估數據集上的表現的指示。如果模型使用決策閾值,要麼提供評估中使用的決策閾值,要麼提供在不同閾值下針對預期用途進行評估的詳細信息。 + +## 例子 + +查看以下幾個精心製作的模型卡的例子: + +* [bert-base-cased](https://huggingface.co/bert-base-cased) +* [gpt2](https://huggingface.co/gpt2) +* [distilbert](https://huggingface.co/distilbert-base-uncased) + +更多來自於不同組織和公司的示例可以在[這裡](https://github.com/huggingface/model_card/blob/master/examples.md)查閱. + +## 提示 + +發佈模型時不需要模型卡,製作一個模型時不需要包含上述所有部分。但是,模型的文檔會使未來的用戶受益,因此我們建議您儘自己的知識和能力填寫儘可能多的部分。 + +## 模型卡片元數據 + +如果您對 Hugging Face Hub 進行了一些探索,您應該已經看到某些模型屬於某些類別:您可以按任務、語言、庫等對其進行過濾。模型所屬的類別來自於您在模型卡片標題中添加的元數據。 + +例如,如果你看一下[`camembert-base` 模型卡片](https://huggingface.co/camembert-base/blob/main/README.md),您應該在模型卡標題中看到以下幾行: + +``` +--- +language: fr +license: mit +datasets: +- oscar +--- +``` + +該元數據由 Hugging Face Hub 解析,然後將這個模型識別為法語模型,擁有 MIT 許可證,在 Oscar 數據集上訓練。 + +允許的指定語言、許可證、標籤、數據集、指標以及模型在訓練時獲得的評估結果在[全部模型卡片的規格](https://raw.githubusercontent.com/huggingface/huggingface_hub/main/modelcard.md)可以查閱。 \ No newline at end of file diff --git a/chapters/zh-TW/chapter4/5.mdx b/chapters/zh-TW/chapter4/5.mdx new file mode 100644 index 000000000..c783f71f2 --- /dev/null +++ b/chapters/zh-TW/chapter4/5.mdx @@ -0,0 +1,12 @@ +# Part 1 完結! + + + +這是課程第一部分的結尾!第 2 部分將在 11 月 15 日與大型社區活動一起發佈,[點擊這裡](https://huggingface.co/blog/course-launch-event)查看更多信息. + +您現在應該能夠針對文本分類問題(單個或成對句子)對預訓練模型進行微調,並將結果上傳到模型中心。為確保您掌握了第一部分的內容,您應該針對您感興趣的想法進行嘗試(不一定是英語)!一旦你完成,您可以在[Hugging Face 社區](https://discuss.huggingface.co/)的[這個話題](https://discuss.huggingface.co/t/share-your-projects/6803)分享您的項目。 + +我們迫不及待地想看看您將用它構建什麼! \ No newline at end of file diff --git a/chapters/zh-TW/chapter4/6.mdx b/chapters/zh-TW/chapter4/6.mdx new file mode 100644 index 000000000..22486196c --- /dev/null +++ b/chapters/zh-TW/chapter4/6.mdx @@ -0,0 +1,220 @@ + + + + +# 章末小測試 + + + +讓我們測試一下你在本章所學的知識! + +### 1. Hub 上的模型有什麼限制? + + +### 2.如何管理Hub上的模型? + + +### 3.你能使用Hugging Face Hub網頁接口做什麼? + + +### 4.模型卡是什麼? + + +### 5.哪些🤗 Transformers 庫的對象可以直接在 Hub 上通過push _ to _ Hub ()共享? +{#if fw === 'pt'} + +{:else} + +{/if} + +### 6.當使用push _ to _ hub ()方法或 CLI 工具時,第一步是什麼? + + +### 7.您正在使用一個模型和一個標記器————如何將它們上傳到 Hub? + huggingface _ hub 實用程序: 不需要額外的包裝!" + }, + { + text: "將它們保存到磁盤並調用 < code > transformers-cli upload-model ", + explain: "命令 < code > upload-model 不存在。" + } + ]} +/> + +### 8.您可以使用'Repository'類執行哪些 git 操作? +git _ commit () 方法就是為此而存在的。", + correct: true + }, + { + text: "拉一下", + explain: "這就是 < code > git _ pull () 方法的目的。", + correct: true + }, + { + text: "推一下", + explain: "方法 < code > git _ push () 可以做到這一點。", + correct: true + }, + { + text: "合併", + explain: "不,這個操作在這個 API 中是不可能的。" + } + ]} +/> diff --git a/chapters/zh-TW/chapter5/1.mdx b/chapters/zh-TW/chapter5/1.mdx new file mode 100644 index 000000000..fefee8e16 --- /dev/null +++ b/chapters/zh-TW/chapter5/1.mdx @@ -0,0 +1,22 @@ +# 本章簡介 + + + +在[第三章](/course/chapter3)第一次體驗了 🤗Datasets 庫,並發現在微調模型時有三個主要步驟: + +1. 從 Hugging Face Hub 加載一個數據集。 +2. 使用 Dataset.map() 對數據進行預處理。 +3. 載入和計算指標(特徵)。 + +但這只是🤗 Datasets的表面功能而已!在本章中,我們將深入瞭解這個庫。在此過程中,我們將找到以下問題的答案: + +* 當數據集不在 hub 上時,您該怎麼做? +* 如何對數據集進行切片?(如果你真正的特別需要使用pandas的時候該怎麼辦?) +* 當你的數據集很大,會撐爆你筆記本電腦的RAM時,你會怎麼做? +* 「內存映射」和 Apache Arrow 到底是什麼? +* 如何創建自己的數據集並將其推送到中心? + +您在這裡學到的技術將為您在[第6章](/course/chapter6)和[第7章](/course/chapter7)中的高級標記化和微調任務做好準備——所以,喝杯咖啡,讓我們開始吧! \ No newline at end of file diff --git a/chapters/zh-TW/chapter5/2.mdx b/chapters/zh-TW/chapter5/2.mdx new file mode 100644 index 000000000..f47239575 --- /dev/null +++ b/chapters/zh-TW/chapter5/2.mdx @@ -0,0 +1,167 @@ +# 如果我的數據集不在 Hub 上怎麼辦? + + + +你知道如何使用[Hugging Face Hub](https://huggingface.co/datasets)下載數據集, 但你經常會發現自己正在處理存儲在筆記本電腦或遠程服務器上的數據。在本節中,我們將向您展示如何使用 🤗 Datasets來加載 Hugging Face Hub 上不可用的數據集。 + + + +## 使用本地和遠程數據集 + +🤗 Datasets 提供了加載腳本來加載本地和遠程數據集。它支持幾種常見的數據格式,例如: + +| Data format | Loading script | Example | +| :----------------: | :------------: | :-----------------------------------------------------: | +| CSV & TSV | `csv` | `load_dataset("csv", data_files="my_file.csv")` | +| Text files | `text` | `load_dataset("text", data_files="my_file.txt")` | +| JSON & JSON Lines | `json` | `load_dataset("json", data_files="my_file.jsonl")` | +| Pickled DataFrames | `pandas` | `load_dataset("pandas", data_files="my_dataframe.pkl")` | + +如表所示, 對於每種數據格式, 我們只需要使用 `load_dataset()` 函數, 使用 `data_files` 指定一個或多個文件的路徑的參數。 讓我們從本地文件加載數據集開始;稍後我們將看到如何對遠程文件執行相同的操作。 + +## 加載本地數據集 + +對於這個例子,我們將使用 [SQuAD-it dataset](https://github.com/crux82/squad-it/), 這是一個大規模的意大利語問答數據集。 + +訓練和測試都託管在 GitHub 上, 因此我們可以通過`wget`命令非常簡單地下載它們: + +```python +!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-train.json.gz +!wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-test.json.gz +``` + +這將下載兩個名為*SQuAD_it-train.json.gz* 和 *SQuAD_it-test.json.gz*的壓縮文件, 我們可以用Linux的解壓命令 `gzip`: + +```python +!gzip -dkv SQuAD_it-*.json.gz +``` + +```bash +SQuAD_it-test.json.gz: 87.4% -- replaced with SQuAD_it-test.json +SQuAD_it-train.json.gz: 82.2% -- replaced with SQuAD_it-train.json +``` + +我們可以看到壓縮文件已經被替換為SQuAD_it-train.json和SQuAD_it-text.json,並且數據以 JSON 格式存儲。 + + + +✎ 如果你想知道為什麼上面的shell命令中喲與一個字符`!`,那是因為我們是在 Jupyter notebook 中運行它們。如果您想在終端中下載和解壓縮數據集,只需刪除前綴!即可。 + + + +使用`load_dataset()`函數來加載JSON文件, 我們只需要知道我們是在處理普通的 JSON(類似於嵌套字典)還是 JSON 行(行分隔的 JSON)。像許多問答數據集一樣, SQuAD-it 使用嵌套格式,所有文本都存儲在 `data`文件中。這意味著我們可以通過指定參數`field`來加載數據集,如下所示: + +```py +from datasets import load_dataset + +squad_it_dataset = load_dataset("json", data_files="SQuAD_it-train.json", field="data") +``` + +默認情況下, 加載本地文件會創建一個帶有`train`的`DatasetDict` 對象。 我們可以通過 `squad_it_dataset`查看: + +```py +squad_it_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 442 + }) +}) +``` + +這向我們顯示了與訓練集相關聯的行數和列名。我們可以通過索引到 `train` 查看示例,如下所示: + +```py +squad_it_dataset["train"][0] +``` + +```python out +{ + "title": "Terremoto del Sichuan del 2008", + "paragraphs": [ + { + "context": "Il terremoto del Sichuan del 2008 o il terremoto...", + "qas": [ + { + "answers": [{"answer_start": 29, "text": "2008"}], + "id": "56cdca7862d2951400fa6826", + "question": "In quale anno si è verificato il terremoto nel Sichuan?", + }, + ... + ], + }, + ... + ], +} +``` + +很好, 我們已經加載了我們的第一個本地數據集! 但是, 雖然這對訓練集有效, 但是我們真正想要的是包括 `train` 和 `test` 的 `DatasetDict` 對象。這樣的話就可以使用 `Dataset.map()` 函數同時處理訓練集和測試集。 為此, 我們提供參數`data_files`的字典,將每個分割名稱映射到與該分割相關聯的文件: + +```py +data_files = {"train": "SQuAD_it-train.json", "test": "SQuAD_it-test.json"} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +squad_it_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 442 + }) + test: Dataset({ + features: ['title', 'paragraphs'], + num_rows: 48 + }) +}) +``` + +這正是我們想要的。現在, 現在,我們可以應用各種預處理技術來清理數據、標記評論等。 + + + +`load_dataset()`函數的`data_files`參數非常靈活並且可以是單個文件路徑、文件路徑列表或將分割後的名稱映射到文件路徑的字典。您還可以根據Unix shell使用的規則對與指定模式匹配的文件進行全局定位(例如,您可以通過設置'data_files=“*.JSON”'將目錄中的所有JSON文件作為單個拆分進行全局定位)。有關更多詳細信息,請參閱🤗Datasets 文檔。 + + + +🤗 Datasets實際上支持輸入文件的自動解壓,所以我們可以跳過使用`gzip`,直接設置 `data_files`參數傳遞壓縮文件: + +```py +data_files = {"train": "SQuAD_it-train.json.gz", "test": "SQuAD_it-test.json.gz"} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +``` + +如果您不想手動解壓縮許多 GZIP 文件,這會很有用。自動解壓也適用於其他常見格式,如 ZIP 和 TAR,因此您只需將 `data_files` 設置為壓縮文件所在的路徑,你就可以開始了! + +現在你知道如何在筆記本電腦或臺式機上加載本地文件,讓我們來看看加載遠程文件。 + +## 加載遠程數據集 + +如果你在公司擔任數據研究員或編碼員,那麼你要分析的數據集很有可能存儲在某個遠程服務器上。幸運的是,加載遠程文件就像加載本地文件一樣簡單!我們沒有提供本地文件的路徑, 而是將`load_dataset()`的`data_files`參數指向存儲遠程文件的一個或多個URL。例如, 對於託管在 GitHub 上的 SQuAD-it 數據集, 我們可以將 `data_files` 指向 _SQuAD_it-*.json.gz_ 的網址,如下所示: + +```py +url = "https://github.com/crux82/squad-it/raw/master/" +data_files = { + "train": url + "SQuAD_it-train.json.gz", + "test": url + "SQuAD_it-test.json.gz", +} +squad_it_dataset = load_dataset("json", data_files=data_files, field="data") +``` + +這將返回和上面的本地例子相同的 `DatasetDict` 對象, 但省去了我們手動下載和解壓 _SQuAD_it-*.json.gz_ 文件的步驟。這是我們對加載未託管在Hugging Face Hub的數據集的各種方法的總結。既然我們已經有了一個可以使用的數據集,讓我們開始大展身手吧! + + + +✏️ **試試看!** 選擇託管在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))。 + + + + diff --git a/chapters/zh-TW/chapter5/3.mdx b/chapters/zh-TW/chapter5/3.mdx new file mode 100644 index 000000000..2acf48b01 --- /dev/null +++ b/chapters/zh-TW/chapter5/3.mdx @@ -0,0 +1,743 @@ +# 是時候來學一下切片了 + + + +大多數情況下,您使用的數據都需根據模型所要求的輸入進行清洗。在本節中,我們將探索 🤗 Datasets 提供的用於數據集清洗的各種功能。 + + + +## 切片與切分我們的數據 + +與 Pandas 類似,🤗 Datasets 提供了幾個函數來操作 **Dataset** 和 **DatasetDict** 對象。我們在[第三章](/course/chapter3)已經遇到了 **Dataset.map()** 方法,在本節中,我們將探索我們可以使用的其他功能。 + +對於這個例子,我們將使用託管在[加州大學歐文分校機器學習存儲庫](https://archive.ics.uci.edu/ml/index.php)的[藥物審查數據集](https://archive.ics.uci.edu/ml/datasets/Drug+Review+Dataset+%28Drugs.com%29),其中包含患者對各種藥物的評論,以及正在治療的病情和患者滿意度的 10 星評級。 + +首先我們需要下載並提取數據,這可以通過 **wget** 和 **unzip** 命令: + +```py +!wget "https://archive.ics.uci.edu/ml/machine-learning-databases/00462/drugsCom_raw.zip" +!unzip drugsCom_raw.zip +``` + +由於 TSV 只是使用製表符而不是逗號作為分隔符的 CSV 變體,我們可以使用加載**csv**文件的**load_dataset()**函數並指定分隔符 示例如下: + +```py +from datasets import load_dataset + +data_files = {"train": "drugsComTrain_raw.tsv", "test": "drugsComTest_raw.tsv"} +# \t is the tab character in Python +drug_dataset = load_dataset("csv", data_files=data_files, delimiter="\t") +``` + +在進行任何類型的數據分析時,一個好的做法是抽取一個小的隨機樣本,以快速瞭解您正在處理的數據類型。在🤗數據集中,我們可以通過鏈接 **Dataset.shuffle()** 和 **Dataset.select()** 共同來完成抽取: + +```py +drug_sample = drug_dataset["train"].shuffle(seed=42).select(range(1000)) +# Peek at the first few examples +drug_sample[:3] +``` + +```python out +{'Unnamed: 0': [87571, 178045, 80482], + 'drugName': ['Naproxen', 'Duloxetine', 'Mobic'], + 'condition': ['Gout, Acute', 'ibromyalgia', 'Inflammatory Conditions'], + 'review': ['"like the previous person mention, I'm a strong believer of aleve, it works faster for my gout than the prescription meds I take. No more going to the doctor for refills.....Aleve works!"', + '"I have taken Cymbalta for about a year and a half for fibromyalgia pain. It is great\r\nas a pain reducer and an anti-depressant, however, the side effects outweighed \r\nany benefit I got from it. I had trouble with restlessness, being tired constantly,\r\ndizziness, dry mouth, numbness and tingling in my feet, and horrible sweating. I am\r\nbeing weaned off of it now. Went from 60 mg to 30mg and now to 15 mg. I will be\r\noff completely in about a week. The fibro pain is coming back, but I would rather deal with it than the side effects."', + '"I have been taking Mobic for over a year with no side effects other than an elevated blood pressure. I had severe knee and ankle pain which completely went away after taking Mobic. I attempted to stop the medication however pain returned after a few days."'], + 'rating': [9.0, 3.0, 10.0], + 'date': ['September 2, 2015', 'November 7, 2011', 'June 5, 2013'], + 'usefulCount': [36, 13, 128]} +``` + +請注意,出於可以復現的目的,我們已將在**Dataset.shuffle()**選取了固定的隨機數種子。 **Dataset.select()** 需要一個可迭代的索引,所以我們已經通過了 **range(1000)** 從隨機打亂的數據集中選取前 1,000 個示例。從抽取的數據中,我們已經可以看到我們數據集的一些特點: + +* **Unnamed: 0**這列看起來很像每個患者的匿名 ID。 +* **condition** 這列包含有描述健康狀況的標籤。 +* 評論長短不一,混合有 Python 行分隔符 (**\r\n**) 以及 HTML 字符代碼,如** &\#039;**。 + +讓我們看看我們如何使用 🤗 Datasets 來處理這些問題。為了驗證**Unnamed: 0** 列存儲的是患者 ID的猜想,我們可以使用 **Dataset.unique()** 函數來驗證匿名ID 的數量是否與拆分後每部分中的行數匹配: + +```py +for split in drug_dataset.keys(): + assert len(drug_dataset[split]) == len(drug_dataset[split].unique("Unnamed: 0")) +``` + +這似乎證實了我們的假設,所以讓我們把 **Unnamed: 0** 列重命名為患者的id。我們可以使用 **DatasetDict.rename_column()**函數一次性重命名DatasetDict中共有的列: + +```py +drug_dataset = drug_dataset.rename_column( + original_column_name="Unnamed: 0", new_column_name="patient_id" +) +drug_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'], + num_rows: 161297 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount'], + num_rows: 53766 + }) +}) +``` + + + +✏️ **試試看!** 使用 `Dataset.unique()` 函數查找訓練和測試集中滿足某個條件的藥物經過去重之後的數量。 + + + +接下來,讓我們使用 **Dataset.map()**標準化所有 **condition** 標籤 .正如我們在[第三章](/course/chapter3)中所做的那樣,我們可以定義一個簡單的函數,可以將該函數應用於**drug_dataset** 拆分後每部分的所有行: + +```py +def lowercase_condition(example): + return {"condition": example["condition"].lower()} + + +drug_dataset.map(lowercase_condition) +``` + +```python out +AttributeError: 'NoneType' object has no attribute 'lower' +``` + +哦不,我們的map功能遇到了問題!從錯誤中我們可以推斷出 **condition** 列存在 **None** , 不能轉換為小寫,因為它們不是字符串。讓我們使用 **Dataset.filter()** 刪除這些行 ,其工作方式類似於 **Dataset.map()** 。例如: + +```py +def filter_nones(x): + return x["condition"] is not None +``` + +然後運行 **drug_dataset.filter(filter_nones)** ,我們可以在一行中使用lambda 函數.在 Python 中,lambda 函數是您無需明確命名即可使用的微函數(匿名函數)。它們一般採用如下形式: + +``` +lambda : +``` + +其中**lambda** 是 Python 的特殊[關鍵字](https://docs.python.org/3/reference/lexical_analysis.html#keywords), **arguments** 是以逗號進行分隔的函數輸入的列表/集合, **expression** 代表您希望執行的操作。例如,我們可以定義一個簡單的 lambda 函數來對一個數字進行平方,如下所示: + +``` +lambda x : x * x +``` + +我們需要將要輸入給這個函數值括在括號中: + +```py +(lambda x: x * x)(3) +``` + +```python out +9 +``` + +類似地,我們可以通過用逗號分隔多個參數來定義 lambda 函數。例如,我們可以按如下方式計算三角形的面積: + +```py +(lambda base, height: 0.5 * base * height)(4, 8) +``` + +```python out +16.0 +``` + +當您想定義小型、一次性使用的函數時,Lambda 函數非常方便(有關它們的更多信息,我們建議閱讀安德烈·布爾高寫的[真正的Python教程](https://realpython.com/python-lambda/))。在🤗 Datasets 中,我們可以使用 lambda 函數來定義簡單的映射和過濾操作,所以讓我們使用這個技巧來消除我們數據集中的 **None** 條目: + +```py +drug_dataset = drug_dataset.filter(lambda x: x["condition"] is not None) +``` + +當 **None** 條目已刪除,我們可以標準化我們的 **condition** 列: + +```py +drug_dataset = drug_dataset.map(lowercase_condition) +# Check that lowercasing worked +drug_dataset["train"]["condition"][:3] +``` + +```python out +['left ventricular dysfunction', 'adhd', 'birth control'] +``` + +有用!現在我們已經清理了標籤,讓我們來看看清洗後的評論文本。 + +## Creating new columns + +每當您處理客戶評論時,一個好的做法是檢查每個評論中的字數。評論可能只是一個詞,比如“太棒了!”或包含數千字的完整文章,根據實際的情況,您需要以不同的方式處理這些極端情況。為了計算每條評論中的單詞數,我們將使用基於空格分割每個文本的粗略方法。 + +讓我們定義一個簡單的函數來計算每條評論中的單詞數: + +```py +def compute_review_length(example): + return {"review_length": len(example["review"].split())} +``` + +與我們的 `lowercase_condition()` 函數不同,`compute_review_length()` 返回一個字典,其鍵與數據集中的列名之一不對應。 在這種情況下,當 `compute_review_length()` 傳遞給 `Dataset.map()` 時,它將應用於數據集中的所有行以創建新的 `review_length` 列: + +```py +drug_dataset = drug_dataset.map(compute_review_length) +# Inspect the first training example +drug_dataset["train"][0] +``` + +```python out +{'patient_id': 206461, + 'drugName': 'Valsartan', + 'condition': 'left ventricular dysfunction', + 'review': '"It has no side effect, I take it in combination of Bystolic 5 Mg and Fish Oil"', + 'rating': 9.0, + 'date': 'May 20, 2012', + 'usefulCount': 27, + 'review_length': 17} +``` + +正如預期的那樣,我們可以看到一個 **review_length** 列已添加到我們的訓練集中。我們可以使用 **Dataset.sort()**對這個新列進行排序,然後查看極端長度的評論的樣子: + +```py +drug_dataset["train"].sort("review_length")[:3] +``` + +```python out +{'patient_id': [103488, 23627, 20558], + 'drugName': ['Loestrin 21 1 / 20', 'Chlorzoxazone', 'Nucynta'], + 'condition': ['birth control', 'muscle spasm', 'pain'], + 'review': ['"Excellent."', '"useless"', '"ok"'], + 'rating': [10.0, 1.0, 6.0], + 'date': ['November 4, 2008', 'March 24, 2017', 'August 20, 2016'], + 'usefulCount': [5, 2, 10], + 'review_length': [1, 1, 1]} +``` + +正如我們所猜想的那樣,一些評論只包含一個詞,雖然這對於情感分析來說可能沒問題,但如果我們想要預測病情,這些評論可能並不適合。 + + + +🙋向數據集添加新列的另一種方法是使用函數Dataset.add_column() 。這允許您輸入Python 列表或 NumPy,在不適合使用Dataset.map()情況下可以很方便。 + + + +讓我們使用 **Dataset.filter()** 功能來刪除包含少於 30 個單詞的評論。與我們對 **condition** 列的處理相似,我們可以通過選取評論的長度高於此閾值來過濾掉非常短的評論: + +```py +drug_dataset = drug_dataset.filter(lambda x: x["review_length"] > 30) +print(drug_dataset.num_rows) +``` + +```python out +{'train': 138514, 'test': 46108} +``` + +如您所見,這已經從我們的原始訓練和測試集中刪除了大約 15% 的評論。 + + + +✏️ 試試看!使用 Dataset.sort() 函數查看單詞數最多的評論。請參閱文檔以瞭解您需要使用哪個參數按長度降序對評論進行排序。 + + + +我們需要處理的最後一件事是評論中是否存在 HTML 字符代碼。我們可以使用 Python 的**html**模塊取消這些字符的轉義,如下所示: + +```py +import html + +text = "I'm a transformer called BERT" +html.unescape(text) +``` + +```python out +"I'm a transformer called BERT" +``` + +我們將使用 **Dataset.map()** 對我們語料庫中的所有 HTML 字符進行轉義: + +```python +drug_dataset = drug_dataset.map(lambda x: {"review": html.unescape(x["review"])}) +``` + +如您所見, **Dataset.map()** 方法對於處理數據非常有用——在示例中僅僅是淺嘗輒止就有很大的收穫! + +## map() 方法的超級加速 + +**Dataset.map()** 方法有一個 **batched** 參數,如果設置為 **True** , map 函數將會分批執行所需要進行的操作(批量大小是可配置的,但默認為 1,000)。例如,之前對所有 HTML 進行轉義的 map 函數運行需要一些時間(您可以從進度條中讀取所用時間)。我們可以通過使用列表推導同時處理多個元素來加快速度。 + +當您在使用 **Dataset.map()**函數時指定 **batched=True**。該函數會接收一個包含數據集字段的字典,每個值都是一個列表,而不僅僅是單個值。**Dataset.map()** 的返回值應該是相同的:一個包含我們想要更新或添加到數據集中的字段的字典,字典的鍵是要添加的字段,字典的值是結果的列表。例如,這是使用 **batched=True**對所有 HTML 字符進行轉義的方法 : + +```python +new_drug_dataset = drug_dataset.map( + lambda x: {"review": [html.unescape(o) for o in x["review"]]}, batched=True +) +``` + +如果您在筆記本中運行此代碼,您會看到此命令的執行速度比前一個命令快得多。這不是因為我們的評論已經是處理過的——如果你重新執行上一節的指令(沒有 **batched=True** ),它將花費與以前相同的時間。這是因為列表推導式通常比在同一代碼中用 **for** 循環執行相同的代碼更快,並且我們還通過同時訪問多個元素而不是一個一個來處理來提高處理的速度。 + +在[第六章](/course/chapter6)我們將遇到的“快速”標記器,它可以快速標記大文本列表。使用 **Dataset.map()** 和 **batched=True** 是加速的關鍵。例如,要使用快速標記器標記所有藥物評論,我們可以使用這樣的函數: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") + + +def tokenize_function(examples): + return tokenizer(examples["review"], truncation=True) +``` + +正如你在[第三章](/course/chapter3)所看到的,我們原本就可以將一個或多個示例傳遞給分詞器,因此在**batched=True**是一個非必須的選項.讓我們藉此機會比較不同選項的性能。在筆記本中,您可以在您要測量的代碼行之前添加 **%time**來測試改行運行所消耗的時間: + +```python no-format +%time tokenized_dataset = drug_dataset.map(tokenize_function, batched=True) +``` + +您還可以通過將整個單元格計時 **%%time** 在單元格的開頭。在我們執行此操作的硬件上,該指令顯示 10.8 秒(這是寫在“Wall time”之後的數字)。 + + + +✏️ **試試看!** 使用和不使用 `batched=True` 執行相同的指令,然後使用慢速標記器嘗試(在 `AutoTokenizer.from_pretrained()` 方法中添加 `use_fast=False`),這樣你就可以看看在你的電腦上它需要多長的時間。 + + + +以下是我們在使用和不使用批處理時使用快速和慢速分詞器獲得的結果: + +Options | Fast tokenizer | Slow tokenizer +:--------------:|:--------------:|:-------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s + +這意味著使用帶有 **batched=True** 選項比沒有批處理的慢選項快 30 倍——這真是太棒了!這就是為什麼**AutoTokenizer** 的默認設置是**use_fast=True**的主要原因 (以及為什麼它們被稱為“快速”)。他們能夠實現這樣的加速,因為在底層的標記化代碼是在 Rust 中執行的,Rust 是一種可以輕鬆並行化執行的語言。 + +並行化也是快速標記器通過批處理實現近 6 倍加速的原因:單個標記化操作是不能並行的,但是當您想同時標記大量文本時,您可以將執行拆分為多個進程,每個進程都對自己的文本負責。 + +**Dataset.map()** 也有一些自己的並行化能力。由於它們不受 Rust 的支持,因此慢速分詞器的速度趕不上快速分詞器,但它們仍然會更快一些(尤其是當您使用沒有快速版本的分詞器時)。要啟用多處理,請在**Dataset.map()**時使用 **num_proc** 參數並指定要在調用中使用的進程數 : + +```py +slow_tokenizer = AutoTokenizer.from_pretrained("bert-base-cased", use_fast=False) + + +def slow_tokenize_function(examples): + return slow_tokenizer(examples["review"], truncation=True) + + +tokenized_dataset = drug_dataset.map(slow_tokenize_function, batched=True, num_proc=8) +``` + +您可以對處理的時間進行一些試驗,以確定要使用的最佳進程數;在我們的例子中,8 似乎產生了最好的速度增益。以下是我們在使用和不使用多處理時所需要的時間: + +Options | Fast tokenizer | Slow tokenizer +:--------------:|:--------------:|:-------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s +`batched=True`, `num_proc=8` | 6.52s | 41.3s +`batched=False`, `num_proc=8` | 9.49s | 45.2s + +對於慢速分詞器來說,這些結果要合理得多,但快速分詞器的性能也得到了顯著提高。但是請注意,情況並非總是如此——除了 **num_proc=8**,我們的測試表明,使用**batched=True**而不帶有**num_proc**參數的選項處理起來更快。通常,我們不建議將 Python 多線程處理用於具有**batched=True**功能的快速標記器 . + + + +使用num_proc以加快處理速度通常是一個好主意,只要您使用的函數還沒有自己帶有的進行某種多進程處理的方法。 + + + +將所有這些功能濃縮到一個方法中已經非常了不起,但還有更多!使用 **Dataset.map()** 和 **batched=True** 您可以更改數據集中的元素數量。當你想從一個例子中創建幾個訓練特徵時,這是非常有用的。我們將在[第七章](/course/chapter7).中進行的幾個NLP任務的預處理中使用到這個功能,它非常便利。 + + + +💡在機器學習中,一個例子通常可以為我們的模型提供一組特徵。在某些情況下,這些特徵會儲存在數據集的幾個列,但在其他情況下(例如此處的例子和用於問答的數據),可以從單個示例的一列中提取多個特徵 + + + +讓我們來看看它是如何工作的!在這裡,我們將標記化我們的示例並將最大截斷長度設置128,但我們將要求標記器返回全部文本塊,而不僅僅是第一個。這可以用 **return_overflowing_tokens=True** : + +```py +def tokenize_and_split(examples): + return tokenizer( + examples["review"], + truncation=True, + max_length=128, + return_overflowing_tokens=True, + ) +``` + +在使用**Dataset.map()** 正式在整個數據集上開始處理之前讓我們先在一個例子上測試一下: + +```py +result = tokenize_and_split(drug_dataset["train"][0]) +[len(inp) for inp in result["input_ids"]] +``` + +```python out +[128, 49] +``` + +瞧!我們在訓練集中的第一個示例變成了兩個特徵,因為它被標記為超過我們指定的最大截斷長度,因此結果被截成了兩段:第一段長度為 128 ,第二段長度為 49 。現在讓我們對所有元素執行此操作數據集! + +```py +tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) +``` + +```python out +ArrowInvalid: Column 1 named condition expected length 1463 but got length 1000 +``` + +不好了!它沒有起作用!為什麼呢?查看錯誤消息會給我們一個線索:列的長度不匹配,一列長度為 1,463,另一列長度為 1,000。1,000行的"review"給出了 1,463 行的新特徵,導致和原本的1000行數據不匹配。 + +問題出在我們試圖混合兩個不同大小的不同數據集: **drug_dataset** 列將有一定數量的元素(我們錯誤中的 1,000),但是我們正在構建**tokenized_dataset** 將有更多的元素(錯誤消息中的 1,463)。這不適用於 **Dataset** ,因此我們需要從舊數據集中刪除列或使它們的大小與新數據集中的大小相同。我們可以用 **remove_columns** 參數: + +```py +tokenized_dataset = drug_dataset.map( + tokenize_and_split, batched=True, remove_columns=drug_dataset["train"].column_names +) +``` + +現在這個過程沒有錯誤。我們可以通過比較長度來檢查新數據集的元素是否比原始數據集多得多: + +```py +len(tokenized_dataset["train"]), len(drug_dataset["train"]) +``` + +```python out +(206772, 138514) +``` + +我們提到我們還可以通過使舊列與新列的大小相同來處理長度不匹配的問題。為此,我們可以使用 **overflow_to_sample_mapping** 字段,當我們設置**return_overflowing_tokens=True** .它為我們提供了特徵到它所產生的樣本的映射。使用這個,我們可以將原始數據集中的每個鍵關聯到一個合適大小的值列表中,通過遍歷所有的數據來生成新特性: + +```py +def tokenize_and_split(examples): + result = tokenizer( + examples["review"], + truncation=True, + max_length=128, + return_overflowing_tokens=True, + ) + # Extract mapping between new and old indices + sample_map = result.pop("overflow_to_sample_mapping") + for key, values in examples.items(): + result[key] = [values[i] for i in sample_map] + return result +``` + +我們可以使用**Dataset.map()**來進行批處理,這樣無需我們刪除舊列: + +```py +tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) +tokenized_dataset +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'], + num_rows: 206772 + }) + test: Dataset({ + features: ['attention_mask', 'condition', 'date', 'drugName', 'input_ids', 'patient_id', 'rating', 'review', 'review_length', 'token_type_ids', 'usefulCount'], + num_rows: 68876 + }) +}) +``` + +我們獲得了與以前相同數量的訓練特徵,但在這裡我們保留了所有舊字段。如果您在使用模型計算之後需要它們進行一些後處理,您可能需要使用這種方法。 + +您現在已經瞭解了 🤗 Datasets如何以各種方式用於預處理數據集。雖然🤗 Datasets 的處理功能會覆蓋你大部分的模型訓練需求,有時您可能需要切換到 Pandas 以使用更強大的功能,例如 **DataFrame.groupby()** 或用於可視化的高級 API。幸運的是,🤗 Datasets旨在與 Pandas、NumPy、PyTorch、TensorFlow 和 JAX 等庫可以相互轉換。讓我們來看看這是如何工作的。 + +## `🤗 Datasets 和 DataFrames 的相互轉換 + + + +為了實現各種第三方庫之間的轉換,🤗 Datasets 提供了一個 **Dataset.set_format()** 功能。此功能可以通過僅更改輸出格式的,輕鬆切換到另一種格式,而不會影響底層數據格式,即 Apache Arrow。格式化會在數據本身上進行。為了演示,讓我們將數據集轉換為 Pandas: + +```py +drug_dataset.set_format("pandas") +``` + +現在,當我們訪問數據集的元素時,我們會得到一個 **pandas.DataFrame** 而不是字典: + +```py +drug_dataset["train"][:3] +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
patient_iddrugNameconditionreviewratingdateusefulCountreview_length
095260Guanfacineadhd"My son is halfway through his fourth week of Intuniv..."8.0April 27, 2010192141
192703Lybrelbirth control"I used to take another oral contraceptive, which had 21 pill cycle, and was very happy- very light periods, max 5 days, no other side effects..."5.0December 14, 200917134
2138000Ortho Evrabirth control"This is my first time using any form of birth control..."8.0November 3, 20151089
+ +讓我們創建一個 **pandas.DataFrame** 來選擇 **drug_dataset[train]** 的所有元素: + +```py +train_df = drug_dataset["train"][:] +``` + + + +🚨 在底層,`Dataset.set_format()` 改變了數據集的 `__getitem__()` dunder 方法的返回格式。 這意味著當我們想從 `"pandas"` 格式的 `Dataset` 中創建像 `train_df` 這樣的新對象時,我們需要對整個數據集進行切片以獲得 `pandas.DataFrame`。 無論輸出格式如何,您都可以自己驗證 `drug_dataset["train"]` 的類型依然還是 `Dataset`。 + + + + +從這裡我們可以使用我們想要的所有 Pandas 功能。例如,我們可以通過花式鏈接來計算 **condition**類之間的分佈 : + +```py +frequencies = ( + train_df["condition"] + .value_counts() + .to_frame() + .reset_index() + .rename(columns={"index": "condition", "condition": "frequency"}) +) +frequencies.head() +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
conditionfrequency
0birth control27655
1depression8023
2acne5209
3anxiety4991
4pain4744
+ + +一旦我們完成了 Pandas 分析,我們總是通過使用對象 **Dataset.from_pandas()**方法可以創建一個新的 **Dataset** 如下: + + +```py +from datasets import Dataset + +freq_dataset = Dataset.from_pandas(frequencies) +freq_dataset +``` + +```python out +Dataset({ + features: ['condition', 'frequency'], + num_rows: 819 +}) +``` + + + +✏️ **試試看!** 計算每種藥物的平均評級並將結果存儲在一個新的Dataset. + + + +我們對 🤗 Datasets中可用的各種預處理技術的介紹到此結束。在最後一部分,讓我們創建一個驗證集來準備用於訓練分類器的數據集。在此之前,我們將輸出格式 **drug_dataset** 從 **pandas**重置到 **arrow** : + +```python +drug_dataset.reset_format() +``` + +## 創建驗證集 + +儘管我們有一個可以用於評估的測試集,但在開發過程中保持測試集不變並創建一個單獨的驗證集是一個很好的做法。一旦您對模型在測試集上的表現感到滿意,您就可以對驗證集進行最終的檢查。此過程有助於降低您過擬合測試集並部署在現實世界數據上失敗的模型的風險。 + +🤗 Datasets提供了一個基於**scikit-learn**的經典方法**Dataset.train_test_split()** .讓我們用它把我們的訓練集分成 **train** 和 **validation** (為了可以復現,我們將設置**seed**的值為一個常量): + +```py +drug_dataset_clean = drug_dataset["train"].train_test_split(train_size=0.8, seed=42) +# Rename the default "test" split to "validation" +drug_dataset_clean["validation"] = drug_dataset_clean.pop("test") +# Add the "test" set to our `DatasetDict` +drug_dataset_clean["test"] = drug_dataset["test"] +drug_dataset_clean +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 110811 + }) + validation: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 27703 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length', 'review_clean'], + num_rows: 46108 + }) +}) +``` + +太好了,我們現在已經準備好了一個數據集,可以用來訓練一些模型了!在[第五節]](/course/chapter5/5)我們將向您展示如何將數據集上傳到 Hugging Face Hub,但現在讓我們查看在本地計算機上保存數據集的幾種方法。 + +## 保存數據集 + + + +雖然 🤗 Datasets 會緩存每個下載的數據集和對它執行的操作,但有時你會想要將數據集保存到磁盤(例如,以防緩存被刪除)。如下表所示,🤗 Datasets 提供了三個主要功能來以不同的格式保存您的數據集: + +| 數據格式 | 對應的方法 | +| :---------: | :--------------------: | +| Arrow | `Dataset.save_to_disk()` | +| CSV | `Dataset.to_csv()` | +| JSON | `Dataset.to_json()` | + +例如,讓我們以 Arrow 格式保存我們清洗過的數據集: + +```py +drug_dataset_clean.save_to_disk("drug-reviews") +``` + +這將創建一個具有以下結構的目錄: + +``` +drug-reviews/ +├── dataset_dict.json +├── test +│ ├── dataset.arrow +│ ├── dataset_info.json +│ └── state.json +├── train +│ ├── dataset.arrow +│ ├── dataset_info.json +│ ├── indices.arrow +│ └── state.json +└── validation + ├── dataset.arrow + ├── dataset_info.json + ├── indices.arrow + └── state.json +``` + +在那裡我們可以看到每個部分.arrow表,以及一些元數據數據集信息.json和狀態文件保存在一起.您可以將 Arrow 格式視為一個精美的列和行的表格,它針對構建處理和傳輸大型數據集的高性能應用程序進行了優化。 + +保存數據集後,我們可以使用 **load_from_disk()** 功能從磁盤讀取數據如下: + +```py +from datasets import load_from_disk + +drug_dataset_reloaded = load_from_disk("drug-reviews") +drug_dataset_reloaded +``` + +```python out +DatasetDict({ + train: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 110811 + }) + validation: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 27703 + }) + test: Dataset({ + features: ['patient_id', 'drugName', 'condition', 'review', 'rating', 'date', 'usefulCount', 'review_length'], + num_rows: 46108 + }) +}) +``` + +對於 CSV 和 JSON 格式,我們必須將每個部分存儲為單獨的文件。一種方法是迭代**DatasetDict**中的鍵和值 : + +```py +for split, dataset in drug_dataset_clean.items(): + dataset.to_json(f"drug-reviews-{split}.jsonl") +``` + +這將保存每個拆分都是[JSON的標準格式](https://jsonlines.org),其中數據集中的每一行都存儲為一行 JSON。這是第一個示例: + +```py +!head -n 1 drug-reviews-train.jsonl +``` + +```python out +{"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} +``` + +然後我們可以使用[第二節](/course/chapter5/2)學過的技術加載 JSON 文件如下: + +```py +data_files = { + "train": "drug-reviews-train.jsonl", + "validation": "drug-reviews-validation.jsonl", + "test": "drug-reviews-test.jsonl", +} +drug_dataset_reloaded = load_dataset("json", data_files=data_files) +``` + +這就是我們探索 🤗 Datasets 的旅程!現在我們有了一個清洗過的數據集,以下是您可以嘗試的一些想法: + +1. 使用[第3章](/course/chapter3)的技術來訓練一個分類器,它可以根據藥物評論預測病人的情況。 +2. 使用 [Chapter 1](/course/chapter1) 中的“summarization”管道生成評論摘要。 + +接下來,我們將看看 🤗 Datasets如何使您能夠在不撐爆筆記本電腦內存的情況下處理龐大的數據集! \ No newline at end of file diff --git a/chapters/zh-TW/chapter5/4.mdx b/chapters/zh-TW/chapter5/4.mdx new file mode 100644 index 000000000..4cd7ae730 --- /dev/null +++ b/chapters/zh-TW/chapter5/4.mdx @@ -0,0 +1,287 @@ +# 大數據? 🤗 Datasets 來救援! + + + + +如今,不難發現我們經常使用數GB的數據集, 特別是如果你打算從頭開始預訓練像 BERT 或者 GPT-2 這樣的轉換器。 在這種情況下, _加載_ 數據集就是一個挑戰。例如, 用於預訓練 GPT-2 的 WebText 語料庫包含超過 800 萬個文檔和 40 GB 的文本 -- 將其加載到筆記本電腦的 RAM 中可能會讓它抓狂! + +幸運的是, 🤗 Datasets 旨在克服這些限制。它通過將數據集作為內存映射文件來處理,並通過在語料庫中流化條目來擺脫硬盤限制, 從而使你避免內存管理問題。 + + + +在本節中, 我們將探索🤗 Datasets 的特性。它有一個稱為 [the Pile](https://pile.eleuther.ai)的825 GB的語料庫。 讓我們開始吧! + +## 什麼是Pile? + +The Pile 是由[EleutherAI](https://www.eleuther.ai)創建的一個英語文本語料庫, 用於訓練大規模語言模型。它包含各種各樣的數據集, 涵蓋科學文章, GitHub 代碼庫以及過濾的Web文本。訓練語料庫在[14 GB chunks](https://the-eye.eu/public/AI/pile/), 並且你也可以下載幾個[單獨的組件](https://the-eye.eu/public/AI/pile_preliminary_components/)。 讓我們先來看看 PubMed Abstracts 數據集, 它是[PubMed](https://pubmed.ncbi.nlm.nih.gov/)上的1500萬篇生物醫學出版物的摘要的語料庫。 數據集採用[JSON行格式](https://jsonlines.org) 並使用`zstandard`庫進行壓縮, 所以我們首先需要先安裝`zstandard`庫: + +```py +!pip install zstandard +``` + +接下來, 我們可以使用[第二節](/course/chapter5/2)中所學的加載遠程數據集的方法加載數據集: + +```py +from datasets import load_dataset + +# This takes a few minutes to run, so go grab a tea or coffee while you wait :) +data_files = "https://the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst" +pubmed_dataset = load_dataset("json", data_files=data_files, split="train") +pubmed_dataset +``` + +```python out +Dataset({ + features: ['meta', 'text'], + num_rows: 15518009 +}) +``` + +我們可以看到我們的數據集中有 15,518,009 行和 2 列 -- 這是非常多的! + + + +✎ 默認情況下, 🤗 Datasets 會自動解壓加載數據集所需的文件。 如果你想保留硬盤空間, 你可以傳遞 `DownloadConfig(delete_extracted=True)` 到 `download_config` 的 `load_dataset()`參數. 有關更多詳細信息, 請參閱文檔](https://huggingface.co/docs/datasets/package_reference/builder_classes.html?#datasets.utils.DownloadConfig)。 + + + +讓我們看看數據集的第一個元素的內容: + +```py +pubmed_dataset[0] +``` + +```python out +{'meta': {'pmid': 11409574, 'language': 'eng'}, + '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 ...'} +``` + +可以看到, 這看起來像是醫學文章的摘要。 現在讓我們看看我們使用了RAM的多少存儲空間來加載數據集! + +## 內存映射的魔力 + +在 Python 中測量內存使用情況的一個簡單的方法是使用[`psutil`](https://psutil.readthedocs.io/en/latest/)庫,它可以使用 `pip`安裝, 如下所示: + +```python +!pip install psutil +``` + +它提供了一個 `Process` 類,這個類允許我們檢查當前進程的內存使用情況, 如下所示: + +```py +import psutil + +# Process.memory_info is expressed in bytes, so convert to megabytes +print(f"RAM used: {psutil.Process().memory_info().rss / (1024 * 1024):.2f} MB") +``` + +```python out +RAM used: 5678.33 MB +``` + +這裡的`rss`屬性是指 _常駐集_ 的大小, 它是進程在RAM中佔用的內存比例。 這個測量結果也包括了 Python 編譯器和我們加載的庫所使用的內存, 所以實際上用於加載數據集的內存會更小一些。為了比較, 讓我們使用 `dataset_size` 屬性看看數據集在磁盤上有多大。 由於結果像之前一樣用字節表示, 我們需要手動將其轉換為GB: + +```py +print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") +size_gb = pubmed_dataset.dataset_size / (1024**3) +print(f"Dataset size (cache file) : {size_gb:.2f} GB") +``` + +```python out +Number of files in dataset : 20979437051 +Dataset size (cache file) : 19.54 GB +``` + +非常棒 -- 儘管它將近20GB, 但我們能夠佔用很少的RAM空間加載和訪問數據集! + + + +✏️ **試試看!** 從[subsets](https://the-eye.eu/public/AI/pile_preliminary_components/)中選擇一個大於你的筆記本或者臺式機的RAM大小的子集, 用 🤗 Datasets加載這個數據集, 並且測量RAM的使用量。 請注意, 要獲得準確的測量結果, 你需要在另一個進程中執行這個操作。你可以在 [the Pile paper](https://arxiv.org/abs/2101.00027)的表一中找到每個子集解壓後的大小。 + + + +如果你熟悉 Pandas, 這個結果可能會讓人感到很意外。因為 Wes Kinney 的著名的[經驗法則](https://wesmckinney.com/blog/apache-arrow-pandas-internals/) 是你需要的RAM應該是數據集的大小的5倍到10倍。 那麼 🤗 Datasets 是如何解決這個內存管理問題的呢? 🤗 Datasets 將每一個數據集看作一個[內存映射文件](https://en.wikipedia.org/wiki/Memory-mapped_file), 它提供了RAM和文件系統存儲之間的映射, 該映射允許庫訪問和操作數據集的元素, 而且無需將其完全加載到內存中。 + +內存映射文件也一個在多個進程之間共享, 這使得像 `Dataset.map()`之類的方法可以並行化, 並且無需移動或者賦值數據集。在底層, 這些功能都是由[Apache Arrow](https://arrow.apache.org)內存格式和[`pyarrow`](https://arrow.apache.org/docs/python/index.html)庫提供的支持, 使得數據加載和處理速度快如閃電。 (更多有關Apache Arrow的詳細信息以及與Pandas的比較, 請查看[Dejan Simic's blog post](https://towardsdatascience.com/apache-arrow-read-dataframe-with-zero-memory-69634092b1a).) 為了更清晰地看到這個過程, 讓我們通過迭代PubMed Abstracts數據集中的所有元素來運行一個速度測試小程序: + +```py +import timeit + +code_snippet = """batch_size = 1000 + +for idx in range(0, len(pubmed_dataset), batch_size): + _ = pubmed_dataset[idx:idx + batch_size] +""" + +time = timeit.timeit(stmt=code_snippet, number=1, globals=globals()) +print( + f"Iterated over {len(pubmed_dataset)} examples (about {size_gb:.1f} GB) in " + f"{time:.1f}s, i.e. {size_gb/time:.3f} GB/s" +) +``` + +```python out +'Iterated over 15518009 examples (about 19.5 GB) in 64.2s, i.e. 0.304 GB/s' +``` + +這裡我們使用了 Python的 `timeit` 模塊來測量執行 `code_snippet`所耗的時間。 你通常能以十分之幾GB/s到幾GB/s的速度迭代數據集。通過上述的方法就已經能夠解決大多數大數據集加載的限制, 但是有時候你不得不使用一個很大的數據集, 它甚至都不能存儲在筆記本電腦的硬盤上。例如, 如果我們嘗試下載整個 Pile, 我們需要825GB的可用磁盤空間! 為了處理這種情況, 🤗 Datasets 提供了一個流式功能, 這個功能允許我們動態下載和訪問元素, 並且不需要下載整個數據集。讓我們來看看這個功能是如何工作的。 + + + +💡在 Jupyter 筆記中你還可以使用[`%%timeit` magic function](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit)為單元格計時。 + + + +## 流式數據集 + +要使用數據集流, 你只需要將 `streaming=True` 參數傳遞給 `load_dataset()` 函數。接下來, 讓我們再次加載 PubMed Abstracts 數據集, 但是採用流模式: + +```py +pubmed_dataset_streamed = load_dataset( + "json", data_files=data_files, split="train", streaming=True +) +``` + +與我們在本章其他地方遇到的熟悉的 `Dataset` 不同, `streaming=True` 返回的對象是一個 `IterableDataset`。 顧名思義, 要訪問 `IterableDataset` , 我們需要迭代它。我們可以按照如下方式訪問流式數據集的第一個元素: + + +```py +next(iter(pubmed_dataset_streamed)) +``` + +```python out +{'meta': {'pmid': 11409574, 'language': 'eng'}, + '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()`進行動態處理。該過程與我們在[第三章](/course/chapter3)中標記數據集的過程完全相同, 唯一的區別是輸出是逐個返回的: + +```py +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained("distilbert-base-uncased") +tokenized_dataset = pubmed_dataset_streamed.map(lambda x: tokenizer(x["text"])) +next(iter(tokenized_dataset)) +``` + +```python out +{'input_ids': [101, 4958, 5178, 4328, 6779, ...], 'attention_mask': [1, 1, 1, 1, 1, ...]} +``` + + + +💡 你可以傳遞 `batched=True` 來通過流式加速標記化, 如同我們在上一節看到的那樣。它將逐批處理示例; 默認的批量大小為 1,000, 可以使用 `batch_size` 參數指定批量大小。 + + + +你還可以使用 `IterableDataset.shuffle()` 打亂流式數據集, 但與 `Dataset.shuffle()` 不同的是這隻會打亂預定義 `buffer_size` 中的元素: + +```py +shuffled_dataset = pubmed_dataset_streamed.shuffle(buffer_size=10_000, seed=42) +next(iter(shuffled_dataset)) +``` + +```python out +{'meta': {'pmid': 11410799, 'language': 'eng'}, + 'text': 'Randomized study of dose or schedule modification of granulocyte colony-stimulating factor in platinum-based chemotherapy for elderly patients with lung cancer ...'} +``` + +在這個示例中, 我們從緩衝區的前 10,000 個示例中隨機選擇了一個示例。一旦訪問了一個示例, 它在緩衝區中的位置就會被語料庫中的下一個示例填充 (即, 上述案例中的第 10,001個示例)。你還可以使用 `IterableDataset.take()` 和 `IterableDataset.skip()` 函數從流式數據集中選擇元素, 它的作用類似於 `Dataset.select()`。例如, 要選擇 PubMed Abstracts 數據集的前5個示例, 我們可以執行以下操作: + +```py +dataset_head = pubmed_dataset_streamed.take(5) +list(dataset_head) +``` + +```python out +[{'meta': {'pmid': 11409574, 'language': 'eng'}, + 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'}, + {'meta': {'pmid': 11409575, 'language': 'eng'}, + 'text': 'Clinical signs of hypoxaemia in children with acute lower respiratory infection: indicators of oxygen therapy ...'}, + {'meta': {'pmid': 11409576, 'language': 'eng'}, + 'text': "Hypoxaemia in children with severe pneumonia in Papua New Guinea ..."}, + {'meta': {'pmid': 11409577, 'language': 'eng'}, + 'text': 'Oxygen concentrators and cylinders ...'}, + {'meta': {'pmid': 11409578, 'language': 'eng'}, + 'text': 'Oxygen supply in rural africa: a personal experience ...'}] +``` + +同樣, 你可以使用 `IterableDataset.skip()` 函數將打亂的數據集拆分為訓練集和驗證集, 如下所示: + +```py +# Skip the first 1,000 examples and include the rest in the training set +train_dataset = shuffled_dataset.skip(1000) +# Take the first 1,000 examples for the validation set +validation_dataset = shuffled_dataset.take(1000) +``` + +讓我們用一個常見的任務來進行我們對數據集流的最後探索: 將多個數據集組合在一起創建一個心得語料庫。 🤗 Datasets 提供了一個 `interleave_datasets()` 函數, 它將一個 `IterableDataset` 對象列表組合為單個的 `IterableDataset`, 其中新數據集的元素是通過在列表中的對象交替獲得的。當你試圖組合大型數據集時, 這個函數特別有用, 讓我們通過下面這個例子來試著組合 Pile的自由法律數據集,它是來自美國法院的51 GB的法律意見數據集: + +```py +law_dataset_streamed = load_dataset( + "json", + data_files="https://the-eye.eu/public/AI/pile_preliminary_components/FreeLaw_Opinions.jsonl.zst", + split="train", + streaming=True, +) +next(iter(law_dataset_streamed)) +``` + +```python out +{'meta': {'case_ID': '110921.json', + 'case_jurisdiction': 'scotus.tar.gz', + 'date_created': '2010-04-28T17:12:49Z'}, + 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'} +``` + +這個數據集足夠大, 可以對大多數筆記本電腦的RAM有足夠的壓力, 但是我們已經能夠毫不費力地加載和訪問它! 現在我們使用 `interleave_datasets()` 函數加載來自 FreeLaw 和 PubMed Abstracts 的數據集: + +```py +from itertools import islice +from datasets import interleave_datasets + +combined_dataset = interleave_datasets([pubmed_dataset_streamed, law_dataset_streamed]) +list(islice(combined_dataset, 2)) +``` + +```python out +[{'meta': {'pmid': 11409574, 'language': 'eng'}, + 'text': 'Epidemiology of hypoxaemia in children with acute lower respiratory infection ...'}, + {'meta': {'case_ID': '110921.json', + 'case_jurisdiction': 'scotus.tar.gz', + 'date_created': '2010-04-28T17:12:49Z'}, + 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'}] +``` + +這裡我們使用了來自Python的 `itertools` 模塊的 `islice()` 函數從合併的數據集中選擇前兩個示例, 並且我們可以看到它們實際上就是兩個源數據集中的前兩個示例拼在一起形成的: + +最後, 如果你想流式傳輸整個825GB的 Pile, 你可以按照如下方式獲取所有準備好的文件: + +```py +base_url = "https://the-eye.eu/public/AI/pile/" +data_files = { + "train": [base_url + "train/" + f"{idx:02d}.jsonl.zst" for idx in range(30)], + "validation": base_url + "val.jsonl.zst", + "test": base_url + "test.jsonl.zst", +} +pile_dataset = load_dataset("json", data_files=data_files, streaming=True) +next(iter(pile_dataset["train"])) +``` + +```python out +{'meta': {'pile_set_name': 'Pile-CC'}, + 'text': 'It is done, and submitted. You can play “Survival of the Tastiest” on Android, and on the web...'} +``` + + + +✏️ **試試看!** 使用像[`mc4`](https://huggingface.co/datasets/mc4) 或者 [`oscar`](https://huggingface.co/datasets/oscar)這樣的大型 Common Crawl 語料庫來創建一個流式多語言數據集, 該數據集代表你選擇的國家/地區語言的口語比例。例如, 瑞士的四種民族語言分別是德語、法語、意大利語和羅曼什語, 因此你可以嘗試根據根據口語比例對Oscar子集進行採用來創建瑞士語料庫。 + + + +你現在擁有加載和處理各種類型和大小的數據集的所需的所有工具 -- 但是除非你非常幸運, 否則在你的NLP之旅中會有一個難題, 你將不得不創建一個數據集來解決手頭的問題。這就是下一節的主題! diff --git a/chapters/zh-TW/chapter5/5.mdx b/chapters/zh-TW/chapter5/5.mdx new file mode 100644 index 000000000..f65b1c0e2 --- /dev/null +++ b/chapters/zh-TW/chapter5/5.mdx @@ -0,0 +1,461 @@ +# 創建自己的數據集 + + + +有時,不存在合適的數據集適用於您構建 NLP 應用,因此您需要自己創建。在本節中,我們將向您展示如何創建一個[GitHub issues](https://github.com/features/issues/)的語料庫,GitHub issues通常用於跟蹤 GitHub 存儲庫中的錯誤或功能。該語料庫可用於各種目的,包括: +* 探索關閉未解決的issue或拉取請求需要多長時間 +* 訓練一個*多標籤分類器*可以根據issue的描述(例如,“錯誤”、“增強”或“issue”)用元數據標記issue +* 創建語義搜索引擎以查找與用戶查詢匹配的issue + +在這裡,我們將專注於創建語料庫,在下一節中,我們將探索語義搜索。我們將使用與流行的開源項目相關的 GitHub issue:🤗 Datasets!接下來讓我們看看如何獲取數據並探索這些issue中包含的信息。 + +## 獲取數據 + +您可以瀏覽 🤗 Datasets 中的所有issue[Issues tab](https://github.com/huggingface/datasets/issues).如以下屏幕截圖所示,在撰寫本文時,有 331 個未解決的issue和 668 個已關閉的issue。 + +
+The GitHub issues associated with 🤗 Datasets. +
+ +如果您單擊其中一個issue,您會發現它包含一個標題、一個描述和一組表徵該issue的標籤。下面的屏幕截圖顯示了一個示例. + +
+A typical GitHub issue in the 🤗 Datasets repository. +
+ +要下載所有存儲庫的issue,我們將使用[GitHub REST API](https://docs.github.com/en/rest)投票[Issues endpoint](https://docs.github.com/en/rest/reference/issues#list-repository-issues).此節點返回一個 JSON 對象列表,每個對象包含大量字段,其中包括標題和描述以及有關issue狀態的元數據等。 + +下載issue的一種便捷方式是通過 **requests** 庫,這是用 Python 中發出 HTTP 請求的標準方式。您可以通過運行以下的代碼來安裝庫: + +```python +!pip install requests +``` + +安裝庫後,您通過調用 **requests.get()** 功能來獲取**Issues**節點。例如,您可以運行以下命令來獲取第一頁上的第一個Issues: + +```py +import requests + +url = "https://api.github.com/repos/huggingface/datasets/issues?page=1&per_page=1" +response = requests.get(url) +``` + +這 **response** 對象包含很多關於請求的有用信息,包括 HTTP 狀態碼: + +```py +response.status_code +``` + +```python out +200 +``` + +其中一個狀態碼 **200** 表示請求成功(您可以[在這裡](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes)找到可能的 HTTP 狀態代碼列表)。然而,我們真正感興趣的是有效的信息,由於我們知道我們的issues是 JSON 格式,讓我們按如下方式查看所有的信息: + +```py +response.json() +``` + +```python out +[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/2792', + 'repository_url': 'https://api.github.com/repos/huggingface/datasets', + 'labels_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/labels{/name}', + 'comments_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/comments', + 'events_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792/events', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792', + 'id': 968650274, + 'node_id': 'MDExOlB1bGxSZXF1ZXN0NzEwNzUyMjc0', + 'number': 2792, + 'title': 'Update GooAQ', + 'user': {'login': 'bhavitvyamalik', + 'id': 19718818, + 'node_id': 'MDQ6VXNlcjE5NzE4ODE4', + 'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4', + 'gravatar_id': '', + 'url': 'https://api.github.com/users/bhavitvyamalik', + 'html_url': 'https://github.com/bhavitvyamalik', + 'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers', + 'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}', + 'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}', + 'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}', + 'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions', + 'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs', + 'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos', + 'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}', + 'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events', + 'type': 'User', + 'site_admin': False}, + 'labels': [], + 'state': 'open', + 'locked': False, + 'assignee': None, + 'assignees': [], + 'milestone': None, + 'comments': 1, + 'created_at': '2021-08-12T11:40:18Z', + 'updated_at': '2021-08-12T12:31:17Z', + 'closed_at': None, + 'author_association': 'CONTRIBUTOR', + 'active_lock_reason': None, + 'pull_request': {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/2792', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792', + 'diff_url': 'https://github.com/huggingface/datasets/pull/2792.diff', + 'patch_url': 'https://github.com/huggingface/datasets/pull/2792.patch'}, + 'body': '[GooAQ](https://github.com/allenai/gooaq) dataset was recently updated after splits were added for the same. This PR contains new updated GooAQ with train/val/test splits and updated README as well.', + 'performed_via_github_app': None}] +``` + +哇,這是很多信息!我們可以看到有用的字段,例如 **標題** , **內容** , **參與的成員**, **issue的描述信息**,以及打開issue的GitHub 用戶的信息。 + + + +✏️ 試試看!單擊上面 JSON 中的幾個 URL,以瞭解每個 GitHub issue中我url鏈接到的實際的地址。 + + +如 GitHub[文檔](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting) 中所述,未經身份驗證的請求限制為每小時 60 個請求。雖然你可以增加 **per_page** 查詢參數以減少您發出的請求數量,您仍然會遭到任何超過幾千個issue的存儲庫的速率限制。因此,您應該關注 GitHub 的[創建個人身份令牌](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token),創建一個個人訪問令牌這樣您就可以將速率限制提高到每小時 5,000 個請求。獲得令牌後,您可以將其包含在請求標頭中: + +```py +GITHUB_TOKEN = xxx # Copy your GitHub token here +headers = {"Authorization": f"token {GITHUB_TOKEN}"} +``` + + + +⚠️ 不要與陌生人共享存在GITHUB令牌的筆記本。我們建議您在使用完後將GITHUB令牌刪除,以避免意外洩漏此信息。一個更好的做法是,將令牌存儲在.env文件中,並使用 [`python-dotenv` library](https://github.com/theskumar/python-dotenv) 為您自動將其作為環境變量加載。 + + + +現在我們有了訪問令牌,讓我們創建一個可以從 GitHub 存儲庫下載所有issue的函數: + +```py +import time +import math +from pathlib import Path +import pandas as pd +from tqdm.notebook import tqdm + + +def fetch_issues( + owner="huggingface", + repo="datasets", + num_issues=10_000, + rate_limit=5_000, + issues_path=Path("."), +): + if not issues_path.is_dir(): + issues_path.mkdir(exist_ok=True) + + batch = [] + all_issues = [] + per_page = 100 # Number of issues to return per page + num_pages = math.ceil(num_issues / per_page) + base_url = "https://api.github.com/repos" + + for page in tqdm(range(num_pages)): + # Query with state=all to get both open and closed issues + query = f"issues?page={page}&per_page={per_page}&state=all" + issues = requests.get(f"{base_url}/{owner}/{repo}/{query}", headers=headers) + batch.extend(issues.json()) + + if len(batch) > rate_limit and len(all_issues) < num_issues: + all_issues.extend(batch) + batch = [] # Flush batch for next time period + print(f"Reached GitHub rate limit. Sleeping for one hour ...") + time.sleep(60 * 60 + 1) + + all_issues.extend(batch) + df = pd.DataFrame.from_records(all_issues) + df.to_json(f"{issues_path}/{repo}-issues.jsonl", orient="records", lines=True) + print( + f"Downloaded all the issues for {repo}! Dataset stored at {issues_path}/{repo}-issues.jsonl" + ) +``` + +現在我們可以調用 **fetch_issues()** 批量下載所有issue,避免超過GitHub每小時的請求數限制;結果將存儲在repository_name-issues.jsonl文件,其中每一行都是一個 JSON 對象,代表一個issue。讓我們使用這個函數從 🤗 Datasets中抓取所有issue: + +```py +# Depending on your internet connection, this can take several minutes to run... +fetch_issues() +``` + +下載issue後,我們可以使用我們 [section 2](/course/chapter5/2)新學會的方法在本地加載它們: + +```py +issues_dataset = load_dataset("json", data_files="datasets-issues.jsonl", split="train") +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'timeline_url', 'performed_via_github_app'], + num_rows: 3019 +}) +``` + +太好了,我們已經從頭開始創建了我們的第一個數據集!但是為什麼會有幾千個issue,而🤗 Datasets存儲庫中的[Issues 選項卡](https://github.com/huggingface/datasets/issues)總共卻只顯示了大約 1,000 個issue🤔?如 GitHub [文檔](https://docs.github.com/en/rest/reference/issues#list-issues-assigned-to-the-authenticated-user)中所述,那是因為我們也下載了所有的拉取請求: + +>Git Hub的REST API v3認為每個pull請求都是一個issue,但並不是每個issue都是一個pull請求。因此,“Issues”節點可能在響應中同時返回issue和拉取請求。你可以通過pull_request 的 key來辨別pull請求。請注意,從“Issues”節點返回的pull請求的id將是一個issue id。 + +由於issue和pull request的內容有很大的不同,我們先做一些小的預處理,讓我們能夠區分它們。 + +## 清理數據 + +上面來自 GitHub 文檔的片段告訴我們, **pull_request** 列可用於區分issue和拉取請求。讓我們隨機挑選一些樣本,看看有什麼不同。我們將使用在[第三節](/course/chapter5/3), 學習的方法,使用 **Dataset.shuffle()** 和 **Dataset.select()** 抽取一個隨機樣本,然後將 **html_url** 和 **pull_request** 列使用zip函數打包,以便我們可以比較各種 URL: + +```py +sample = issues_dataset.shuffle(seed=666).select(range(3)) + +# Print out the URL and pull request entries +for url, pr in zip(sample["html_url"], sample["pull_request"]): + print(f">> URL: {url}") + print(f">> Pull request: {pr}\n") +``` + +```python out +>> URL: https://github.com/huggingface/datasets/pull/850 +>> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/850', 'html_url': 'https://github.com/huggingface/datasets/pull/850', 'diff_url': 'https://github.com/huggingface/datasets/pull/850.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/850.patch'} + +>> URL: https://github.com/huggingface/datasets/issues/2773 +>> Pull request: None + +>> URL: https://github.com/huggingface/datasets/pull/783 +>> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/783', 'html_url': 'https://github.com/huggingface/datasets/pull/783', 'diff_url': 'https://github.com/huggingface/datasets/pull/783.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/783.patch'} +``` + +這裡我們可以看到,每個pull請求都與各種url相關聯,而普通issue只有一個None條目。我們可以使用這一點不同來創建一個新的is_pull_request列通過檢查pull_request字段是否為None來區分它們: + +```py +issues_dataset = issues_dataset.map( + lambda x: {"is_pull_request": False if x["pull_request"] is None else True} +) +``` + + + +✏️ 試試看!計算在 🤗 Datasets中解決issue所需的平均時間。您可能會發現 Dataset.filter()函數對於過濾拉取請求和未解決的issue很有用,並且您可以使用Dataset.set_format()函數將數據集轉換為DataFrame,以便您可以輕鬆地按照需求修改創建和關閉的時間的格式(以時間戳格式)。 + + + +儘管我們可以通過刪除或重命名某些列來進一步清理數據集,但在此階段儘可能保持數據集“原始”狀態通常是一個很好的做法,以便它可以在多個應用程序中輕鬆使用。在我們將數據集推送到 Hugging Face Hub 之前,讓我們再添加一些缺少的數據:與每個issue和拉取請求相關的評論。我們接下來將添加它們——你猜對了——我們將依然使用GitHub REST API! + +## 擴充數據集 + +如以下屏幕截圖所示,與issue或拉取請求相關的評論提供了豐富的信息,特別是如果我們有興趣構建搜索引擎來回答用戶對這個項目的疑問。 + +
+Comments associated with an issue about 🤗 Datasets. +
+ +GitHub REST API 提供了一個 [評論節點](https://docs.github.com/en/rest/reference/issues#list-issue-comments) 返回與issue編號相關的所有評論。讓我們測試節點以查看它返回的內容: + +```py +issue_number = 2792 +url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments" +response = requests.get(url, headers=headers) +response.json() +``` + +```python out +[{'url': 'https://api.github.com/repos/huggingface/datasets/issues/comments/897594128', + 'html_url': 'https://github.com/huggingface/datasets/pull/2792#issuecomment-897594128', + 'issue_url': 'https://api.github.com/repos/huggingface/datasets/issues/2792', + 'id': 897594128, + 'node_id': 'IC_kwDODunzps41gDMQ', + 'user': {'login': 'bhavitvyamalik', + 'id': 19718818, + 'node_id': 'MDQ6VXNlcjE5NzE4ODE4', + 'avatar_url': 'https://avatars.githubusercontent.com/u/19718818?v=4', + 'gravatar_id': '', + 'url': 'https://api.github.com/users/bhavitvyamalik', + 'html_url': 'https://github.com/bhavitvyamalik', + 'followers_url': 'https://api.github.com/users/bhavitvyamalik/followers', + 'following_url': 'https://api.github.com/users/bhavitvyamalik/following{/other_user}', + 'gists_url': 'https://api.github.com/users/bhavitvyamalik/gists{/gist_id}', + 'starred_url': 'https://api.github.com/users/bhavitvyamalik/starred{/owner}{/repo}', + 'subscriptions_url': 'https://api.github.com/users/bhavitvyamalik/subscriptions', + 'organizations_url': 'https://api.github.com/users/bhavitvyamalik/orgs', + 'repos_url': 'https://api.github.com/users/bhavitvyamalik/repos', + 'events_url': 'https://api.github.com/users/bhavitvyamalik/events{/privacy}', + 'received_events_url': 'https://api.github.com/users/bhavitvyamalik/received_events', + 'type': 'User', + 'site_admin': False}, + 'created_at': '2021-08-12T12:21:52Z', + 'updated_at': '2021-08-12T12:31:17Z', + 'author_association': 'CONTRIBUTOR', + 'body': "@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?", + 'performed_via_github_app': None}] +``` + +我們可以看到註釋存儲在body字段中,所以讓我們編寫一個簡單的函數,通過在response.json()中為每個元素挑選body內容來返回與某個issue相關的所有評論: + +```py +def get_comments(issue_number): + url = f"https://api.github.com/repos/huggingface/datasets/issues/{issue_number}/comments" + response = requests.get(url, headers=headers) + return [r["body"] for r in response.json()] + + +# Test our function works as expected +get_comments(2792) +``` + +```python out +["@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?"] +``` + +這看起來不錯,所以讓我們使用 **Dataset.map()** 方法在我們數據集中每個issue的添加一個**comments**列: + +```py +# Depending on your internet connection, this can take a few minutes... +issues_with_comments_dataset = issues_dataset.map( + lambda x: {"comments": get_comments(x["number"])} +) +``` + +最後一步是將增強數據集與原始數據保存在一起,以便我們可以將它們都推送到 Hub: + +```py +issues_with_comments_dataset.to_json("issues-datasets-with-comments.jsonl") +``` + +## 將數據集上傳到 Hugging Face Hub + + + +現在我們有了我們的增強數據集,是時候將它推送到 Hub 並且與社區共享它!要上傳數據集,我們將使用[🤗 Hub 庫](https://github.com/huggingface/huggingface_hub),它允許我們通過 Python API 與 Hugging Face Hub 進行交互。 🤗 Hub 預裝了🤗 Transformers,所以我們可以直接使用它。例如,我們可以使用 **list_datasets()** 獲取有關當前託管在 Hub 上的所有公共數據集的信息的函數: + +```py +from huggingface_hub import list_datasets + +all_datasets = list_datasets() +print(f"Number of datasets on Hub: {len(all_datasets)}") +print(all_datasets[0]) +``` + +```python out +Number of datasets on Hub: 1487 +Dataset Name: acronym_identification, Tags: ['annotations_creators:expert-generated', 'language_creators:found', 'languages:en', 'licenses:mit', 'multilinguality:monolingual', 'size_categories:10K + +✏️ 試試看!使用您的 Hugging Face Hub 用戶名和密碼獲取令牌並創建一個名為 github-issues.請記住永遠不要將您的憑據保存在 Colab 或任何其他存儲庫中,因為這些信息可能會被不法分子利用。 + + + +接下來,讓我們將存儲庫從 Hub 克隆到我們的本地機器,並將我們的數據集文件複製到其中。 🤗 Hub 提供了一個方便的 **Repository** 類,它包含許多常見 Git 命令的類,因此要克隆遠程存儲庫,我們只需要提供我們要克隆的 URL 和本地路徑: + +```py +from huggingface_hub import Repository + +repo = Repository(local_dir="github-issues", clone_from=repo_url) +!cp datasets-issues-with-comments.jsonl github-issues/ +``` + +默認情況下,使用Git LFS跟蹤各種文件擴展名(如.bin、.gz和.zip),以便在同一Git工作流中對大型文件進行版本控制。您可以在存儲庫的.gitattributes文件找到跟蹤文件擴展名的列表。要在列表中包含JSON行格式,我們可以運行以下命令: + +```py +repo.lfs_track("*.jsonl") +``` + +然後我們可以使用 **Repository.push_to_hub()** 將數據集推送到 Hub: + +```py +repo.push_to_hub() +``` + +如果我們導航到包含在 **repo_url** ,我們現在應該看到我們的數據集文件已經上傳。 + +
+Our dataset repository on the Hugging Face Hub. +
+ +從這裡,任何人都可以通過簡單地提供來下載數據集 **load_dataset()** 以存儲庫 ID 作為 **path** 爭論: + +```py +remote_dataset = load_dataset("lewtun/github-issues", split="train") +remote_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 2855 +}) +``` + +很酷,我們已經將我們的數據集推送到 Hub,其他人可以使用它!只剩下一件重要的事情要做:添加一個數據卡這解釋了語料庫是如何創建的,併為使用數據集的其他提供一些其他有用的信息。 + + + +💡 您還可以使用一些 Git 魔法直接從終端將數據集上傳到 Hugging Face Hub。有關如何執行此操作的詳細信息,請參閱 [🤗 Datasets guide](https://huggingface.co/docs/datasets/share.html#add-a-community-dataset) 指南。 + + + +## 創建數據集卡片 + +有據可查的數據集更有可能對其他人(包括你未來的自己!)有用,因為它們提供了上下文,使用戶能夠決定數據集是否與他們的任務相關,並評估任何潛在的偏見或與使用相關的風險。在 Hugging Face Hub 上,此信息存儲在每個數據集存儲庫的自述文件文件。在創建此文件之前,您應該執行兩個主要步驟: + +1. 使用[數據集標籤應用程序](https://huggingface.co/datasets/tagging/) 創建YAML格式的元數據標籤。這些標籤用於各種各樣的搜索功能,並確保您的數據集可以很容易地被社區成員找到。因為我們已經在這裡創建了一個自定義數據集,所以您需要克隆數據集標籤存儲庫並在本地運行應用程序。它的界面是這樣的: + +
+The `datasets-tagging` interface. +
+ +2.閱讀[🤗 Datasets guide](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) 關於創建信息性數據集卡片的指南,並將其作為模板使用。 + +您可以創建自述文件文件直接在Hub上,你可以在裡面找到模板數據集卡片 **lewtun/github-issues** 數據集存儲庫。填寫好的數據集卡片的屏幕截圖如下所示。! + +
+A dataset card. +
+ + + +✏️試試看!使用應用程序和 [🤗 Datasets guide](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) 指南來完成 GitHub issue數據集的 README.md 文件。 + + + +很好! 我們在本節中看到,創建一個好的數據集可能非常複雜,但幸運的是,將其上傳並與社區共享會很容易實現。在下一節中,我們將使用我們的新數據集創建一個帶有 🤗 Datasets的語義搜索引擎,該數據集可以將issue與最相關的issue和評論進行匹配。 + + + +✏️ 試試看!按照我們在本節中採取的步驟為您最喜歡的開源庫創建一個 GitHub issue數據集(當然,選擇 🤗 Datasets以外的其他東西!)。對於獎勵積分,微調多標籤分類器以預測該領域中存在的標籤。 + + + diff --git a/chapters/zh-TW/chapter5/6.mdx b/chapters/zh-TW/chapter5/6.mdx new file mode 100644 index 000000000..b5646f739 --- /dev/null +++ b/chapters/zh-TW/chapter5/6.mdx @@ -0,0 +1,526 @@ + + +# 使用 FAISS 進行語義搜索 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +在[第五小節](/course/chapter5/5), 我們從 🤗 Datasets 存儲庫創建了一個包含 GitHub 問題和評論的數據集。在本節中,我們將使用這些信息來構建一個搜索引擎,它可以幫助我們找到這個庫最緊迫問題的答案! + + + +## 使用嵌入進行語義搜索 + +正如我們在[第一章](/course/chapter1),學習的, 基於 Transformer 的語言模型會將文本中的每個標記轉換為嵌入向量.事實證明,可以“彙集”各個嵌入向量來創建整個句子、段落或文檔(在某些情況下)的向量表示。然後,通過計算每個嵌入之間的點積相似度(或其他一些相似度度量)並返回相似度最大的文檔,這些嵌入可用於在語料庫中找到相似的文檔。在本節中,我們將使用嵌入來開發語義搜索引擎。與基於將查詢中的關鍵字的傳統方法相比,這些搜索引擎具有多種優勢。 + +
+Semantic search. + +
+ +## ## 加載和準備數據集 + +我們需要做的第一件事是下載我們的 GitHub 問題數據集,所以讓我們使用 🤗 Hub 庫來解析我們的文件在 Hugging Face Hub 上存儲的數據: + +```py +from huggingface_hub import hf_hub_url + +data_files = hf_hub_url( + repo_id="lewtun/github-issues", + filename="datasets-issues-with-comments.jsonl", + repo_type="dataset", +) +``` + +將 URL 存儲在 **data_files** ,然後我們可以使用[第二小節](/course/chapter5/2)介紹的方法加載遠程數據集: + +```py +from datasets import load_dataset + +issues_dataset = load_dataset("json", data_files=data_files, split="train") +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 2855 +}) +``` + +這裡我們在load_dataset()中使用了默認的訓練集分割,所以它返回一個數據集而不是數據集字典。第一項任務是過濾掉pull請求,因為這些請求很少用於回答用戶提出的問題,而且會給我們的搜索引擎帶來噪聲。現在應該很熟悉了,我們可以使用dataset.filter()函數來排除數據集中的這些行。同時,讓我們也過濾掉沒有註釋的行,因為這些行不會是用戶提問的答案: + +```py +issues_dataset = issues_dataset.filter( + lambda x: (x["is_pull_request"] == False and len(x["comments"]) > 0) +) +issues_dataset +``` + +```python out +Dataset({ + features: ['url', 'repository_url', 'labels_url', 'comments_url', 'events_url', 'html_url', 'id', 'node_id', 'number', 'title', 'user', 'labels', 'state', 'locked', 'assignee', 'assignees', 'milestone', 'comments', 'created_at', 'updated_at', 'closed_at', 'author_association', 'active_lock_reason', 'pull_request', 'body', 'performed_via_github_app', 'is_pull_request'], + num_rows: 771 +}) +``` + +我們可以看到我們的數據集中有很多列,其中大部分我們不需要構建我們的搜索引擎。從搜索的角度來看,信息量最大的列是 **title** , **body** , 和 **comments** ,而 **html_url** 為我們提供了一個回到源問題的鏈接。讓我們使用 **Dataset.remove_columns()** 刪除其餘部分的功能: + +```py +columns = issues_dataset.column_names +columns_to_keep = ["title", "body", "html_url", "comments"] +columns_to_remove = set(columns_to_keep).symmetric_difference(columns) +issues_dataset = issues_dataset.remove_columns(columns_to_remove) +issues_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body'], + num_rows: 771 +}) +``` + +為了創建我們的嵌入,我們將用問題的標題和正文來擴充每條評論,因為這些字段通常包含有用的上下文信息。因為我們的 **comments** 列當前是每個問題的評論列表,我們需要“重新組合”列,以便每一條評論都包含一個 **(html_url, title, body, comment)** 元組。在 Pandas 中,我們可以使用 [DataFrame.explode() 函數](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html), 它為類似列表的列中的每個元素創建一個新行,同時複製所有其他列值。為了看到它的實際效果,讓我們首先切換到 Pandas的**DataFrame** 格式: + +```py +issues_dataset.set_format("pandas") +df = issues_dataset[:] +``` + +如果我們檢查這裡的第一行 **DataFrame** 我們可以看到有四個評論與這個問題相關: + +```py +df["comments"][0].tolist() +``` + +```python out +['the bug code locate in :\r\n if data_args.task_name is not None:\r\n # Downloading and loading a dataset from the hub.\r\n datasets = load_dataset("glue", data_args.task_name, cache_dir=model_args.cache_dir)', + 'Hi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com\r\n\r\nNormally, it should work if you wait a little and then retry.\r\n\r\nCould you please confirm if the problem persists?', + 'cannot connect,even by Web browser,please check that there is some problems。', + 'I can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...'] +``` + +我們希望這些評論中的每一條都得到一行。讓我們檢查是否是這種情況: + +```py +comments_df = df.explode("comments", ignore_index=True) +comments_df.head(4) +``` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
html_urltitlecommentsbody
0https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comthe bug code locate in :\r\n if data_args.task_name is not None...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
1https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comHi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
2https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comcannot connect,even by Web browser,please check that there is some problems。Hello,\r\nI am trying to run run_glue.py and it gives me this error...
3https://github.com/huggingface/datasets/issues/2787ConnectionError: Couldn't reach https://raw.githubusercontent.comI can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...Hello,\r\nI am trying to run run_glue.py and it gives me this error...
+ +太好了,我們可以看到評論成功被擴充, **comments** 是包含個人評論的列!現在我們已經完成了 Pandas要完成的部分功能,我們可以快速切換回 **Dataset** 通過加載 **DataFrame** 在內存中: + +```py +from datasets import Dataset + +comments_dataset = Dataset.from_pandas(comments_df) +comments_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body'], + num_rows: 2842 +}) +``` + +太好了,我們獲取到了幾千條的評論! + + + + +✏️ **Try it out!** 看看能不能不用pandas就可以完成列的擴充; 這有點棘手; 你可能會發現 🤗 Datasets 文檔的 ["Batch mapping"](https://huggingface.co/docs/datasets/v1.12.1/about_map_batch.html?batch-mapping#batch-mapping) 對這個任務很有用。 + + + +現在我們每行有一個評論,讓我們創建一個新的 **comments_length** 列來存放每條評論的字數: + +```py +comments_dataset = comments_dataset.map( + lambda x: {"comment_length": len(x["comments"].split())} +) +``` + +我們可以使用這個新列來過濾掉簡短的評論,其中通常包括“cc @lewtun”或“謝謝!”之類與我們的搜索引擎無關的內容。雖然無法為過濾器選擇的精確數字,但大約大於15 個單詞似乎是一個不錯的選擇: + +```py +comments_dataset = comments_dataset.filter(lambda x: x["comment_length"] > 15) +comments_dataset +``` + +```python out +Dataset({ + features: ['html_url', 'title', 'comments', 'body', 'comment_length'], + num_rows: 2098 +}) +``` + +稍微清理了我們的數據集後,讓我們將問題標題、描述和評論連接到一個新的 **text** 列。像往常一樣,我們可以編寫一個簡單的函數,並將其傳遞給 **Dataset.map()**來做到這些 : + +```py +def concatenate_text(examples): + return { + "text": examples["title"] + + " \n " + + examples["body"] + + " \n " + + examples["comments"] + } + + +comments_dataset = comments_dataset.map(concatenate_text) +``` + +我們終於準備好創建一些嵌入了!讓我們來看看。 + +## 創建文本嵌入 + +我們在[第二章](/course/chapter2) 學過,我們可以通過使用 **AutoModel** 類來完成詞嵌入。我們需要做的就是選擇一個合適的檢查點來加載模型。幸運的是,有一個名為 **sentence-transformers** 專門用於創建詞嵌入。如庫中[文檔](https://www.sbert.net/examples/applications/semantic-search/README.html#symmetric-vs-asymmetric-semantic-search), 所述的,我們這次要實現的是非對稱語義搜索,因為我們有一個簡短的查詢,我們希望在比如問題評論等更長的文檔中找到其答案。通過查看[模型概述表](https://www.sbert.net/docs/pretrained_models.html#model-overview) 我們可以發現 **multi-qa-mpnet-base-dot-v1** 檢查點在語義搜索方面具有最佳性能,因此我們將在我們的應用程序中使用它。我們還將使用相同的檢查點加載標記器: + +{#if fw === 'pt'} + +```py +from transformers import AutoTokenizer, AutoModel + +model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1" +tokenizer = AutoTokenizer.from_pretrained(model_ckpt) +model = AutoModel.from_pretrained(model_ckpt) +``` + +為了加快嵌入過程,將模型和輸入放在 GPU 設備上,所以現在讓我們這樣做: + +```py +import torch + +device = torch.device("cuda") +model.to(device) +``` + +{:else} + +```py +from transformers import AutoTokenizer, TFAutoModel + +model_ckpt = "sentence-transformers/multi-qa-mpnet-base-dot-v1" +tokenizer = AutoTokenizer.from_pretrained(model_ckpt) +model = TFAutoModel.from_pretrained(model_ckpt, from_pt=True) +``` + +請注意,我們已將 from_pt=True 設置為 from_pretrained() 方法的參數。這是因為 multi-qa-mpnet-base-dot-v1 檢查點只有PyTorch權重,因此設置 from_pt=True 會自動將它們轉換為TensorFlow格式。如您所見,在Transformers中的🤗框架之間切換非常簡單! + +{/if} + +正如我們之前提到的,我們希望將 GitHub 問題語料庫中的每個條目表示為單個向量,因此我們需要以某種方式“池化”或平均化我們的標記嵌入。一種流行的方法是在我們模型的輸出上執行CLS 池化,我們只獲取**[CLS]** 令牌的最後一個隱藏狀態。以下函數為我們提供了這樣的方法: + +```py +def cls_pooling(model_output): + return model_output.last_hidden_state[:, 0] +``` + +接下來,我們將創建一個輔助函數,該函數將標記文檔列表,將tensor放在 GPU 上,然後提供給模型,最後對輸出使用CLS 池化: + +{#if fw === 'pt'} + +```py +def get_embeddings(text_list): + encoded_input = tokenizer( + text_list, padding=True, truncation=True, return_tensors="pt" + ) + encoded_input = {k: v.to(device) for k, v in encoded_input.items()} + model_output = model(**encoded_input) + return cls_pooling(model_output) +``` + +我們可以通過在我們的語料庫中輸入第一個文本條目並檢查輸出維度來測試該函數是否有效: + +```py +embedding = get_embeddings(comments_dataset["text"][0]) +embedding.shape +``` + +```python out +torch.Size([1, 768]) +``` + +太好了,我們已經將語料庫中的第一個條目轉換為 768 維向量!我們可以用 **Dataset.map()** 應用我們的 **get_embeddings()** 函數到我們語料庫中的每一行,所以讓我們創建一個新的 **embeddings** 列如下: + +```py +embeddings_dataset = comments_dataset.map( + lambda x: {"embeddings": get_embeddings(x["text"]).detach().cpu().numpy()[0]} +) +``` + +{:else} + +```py +def get_embeddings(text_list): + encoded_input = tokenizer( + text_list, padding=True, truncation=True, return_tensors="tf" + ) + encoded_input = {k: v for k, v in encoded_input.items()} + model_output = model(**encoded_input) + return cls_pooling(model_output) +``` + +我們可以通過在我們的語料庫中輸入第一個文本條目並檢查輸出維度來測試該函數是否有效: + +```py +embedding = get_embeddings(comments_dataset["text"][0]) +embedding.shape +``` + +```python out +TensorShape([1, 768]) +``` + +太好了,我們已經將語料庫中的第一個條目轉換為 768 維向量!我們可以用 **Dataset.map()** 應用我們的 **get_embeddings()** 函數到我們語料庫中的每一行,所以讓我們創建一個新的 **embeddings** 列如下: + +```py +embeddings_dataset = comments_dataset.map( + lambda x: {"embeddings": get_embeddings(x["text"]).numpy()[0]} +) +``` + +{/if} + +請注意,我們已經將嵌入轉換為 NumPy 數組——這是因為當我們嘗試使用 FAISS 索引它們時,🤗 Datasets需要這種格式,我們接下來會這樣做。 + + +## 使用 FAISS 進行高效的相似性搜索 + +現在我們有了一個詞嵌入數據集,我們需要一些方法來搜索它們。為此,我們將在 🤗 Datasets中使用一種特殊的數據結構,稱為 FAISS指數.[FAISS](https://faiss.ai/) (short for Facebook AI Similarity Search) (Facebook AI Similarity Search 的縮寫)是一個提供高效算法來快速搜索和聚類嵌入向量的庫。FAISS 背後的基本思想是創建一個特殊的數據結構,稱為指數。這允許人們找到哪些嵌入詞與輸入的詞嵌入相似。在 🤗 Datasets中創建一個 FAISS 索引很簡單——我們使用 **Dataset.add_faiss_index()** 函數並指定我們要索引的數據集的哪一列: + +```py +embeddings_dataset.add_faiss_index(column="embeddings") +``` + +現在,我們可以使用**Dataset.get_nearest_examples()**函數進行最近鄰居查找。讓我們通過首先嵌入一個問題來測試這一點,如下所示: + +{#if fw === 'pt'} + +```py +question = "How can I load a dataset offline?" +question_embedding = get_embeddings([question]).cpu().detach().numpy() +question_embedding.shape +``` + +```python out +torch.Size([1, 768]) +``` + +{:else} + +```py +question = "How can I load a dataset offline?" +question_embedding = get_embeddings([question]).numpy() +question_embedding.shape +``` + +```python out +(1, 768) +``` + +{/if} + +就像文檔一樣,我們現在有一個 768 維向量表示查詢,我們可以將其與整個語料庫進行比較以找到最相似的嵌入: + +```py +scores, samples = embeddings_dataset.get_nearest_examples( + "embeddings", question_embedding, k=5 +) +``` + + **Dataset.get_nearest_examples()** 函數返回一個分數元組,對查詢和文檔之間的相似度進行排序,以及一組最佳匹配的結果(這裡是 5 個)。讓我們把這些收集到一個 **pandas.DataFrame** 以便我們可以輕鬆地對它們進行排序: + +```py +import pandas as pd + +samples_df = pd.DataFrame.from_dict(samples) +samples_df["scores"] = scores +samples_df.sort_values("scores", ascending=False, inplace=True) +``` + +現在我們可以遍歷前幾行來查看我們的查詢與評論的匹配程度: + +```py +for _, row in samples_df.iterrows(): + print(f"COMMENT: {row.comments}") + print(f"SCORE: {row.scores}") + print(f"TITLE: {row.title}") + print(f"URL: {row.html_url}") + print("=" * 50) + print() +``` + +```python out +""" +COMMENT: Requiring online connection is a deal breaker in some cases unfortunately so it'd be great if offline mode is added similar to how `transformers` loads models offline fine. + +@mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like? +SCORE: 25.505046844482422 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: The local dataset builders (csv, text , json and pandas) are now part of the `datasets` package since #1726 :) +You can now use them offline +\`\`\`python +datasets = load_dataset("text", data_files=data_files) +\`\`\` + +We'll do a new release soon +SCORE: 24.555509567260742 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: I opened a PR that allows to reload modules that have already been loaded once even if there's no internet. + +Let me know if you know other ways that can make the offline mode experience better. I'd be happy to add them :) + +I already note the "freeze" modules option, to prevent local modules updates. It would be a cool feature. + +---------- + +> @mandubian's second bullet point suggests that there's a workaround allowing you to use your offline (custom?) dataset with `datasets`. Could you please elaborate on how that should look like? + +Indeed `load_dataset` allows to load remote dataset script (squad, glue, etc.) but also you own local ones. +For example if you have a dataset script at `./my_dataset/my_dataset.py` then you can do +\`\`\`python +load_dataset("./my_dataset") +\`\`\` +and the dataset script will generate your dataset once and for all. + +---------- + +About I'm looking into having `csv`, `json`, `text`, `pandas` dataset builders already included in the `datasets` package, so that they are available offline by default, as opposed to the other datasets that require the script to be downloaded. +cf #1724 +SCORE: 24.14896583557129 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: > here is my way to load a dataset offline, but it **requires** an online machine +> +> 1. (online machine) +> +> ``` +> +> import datasets +> +> data = datasets.load_dataset(...) +> +> data.save_to_disk(/YOUR/DATASET/DIR) +> +> ``` +> +> 2. copy the dir from online to the offline machine +> +> 3. (offline machine) +> +> ``` +> +> import datasets +> +> data = datasets.load_from_disk(/SAVED/DATA/DIR) +> +> ``` +> +> +> +> HTH. + + +SCORE: 22.893993377685547 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== + +COMMENT: here is my way to load a dataset offline, but it **requires** an online machine +1. (online machine) +\`\`\` +import datasets +data = datasets.load_dataset(...) +data.save_to_disk(/YOUR/DATASET/DIR) +\`\`\` +2. copy the dir from online to the offline machine +3. (offline machine) +\`\`\` +import datasets +data = datasets.load_from_disk(/SAVED/DATA/DIR) +\`\`\` + +HTH. +SCORE: 22.406635284423828 +TITLE: Discussion using datasets in offline mode +URL: https://github.com/huggingface/datasets/issues/824 +================================================== +""" +``` + +我們的第二個搜索結果似乎與查詢相符。 + + + +✏️ 試試看!創建您自己的查詢並查看您是否可以在檢索到的文檔中找到答案。您可能需要增加參數k以擴大搜索範圍。 + + \ No newline at end of file diff --git a/chapters/zh-TW/chapter5/7.mdx b/chapters/zh-TW/chapter5/7.mdx new file mode 100644 index 000000000..4ece9ccb8 --- /dev/null +++ b/chapters/zh-TW/chapter5/7.mdx @@ -0,0 +1,16 @@ +# 🤗 Datasets,回顧! + + + +這是對 🤗 Datasets 庫的一次完整遊覽——祝賀你走到這一步!憑藉從本章中獲得的知識,您應該能夠: + +- 從任何地方加載數據集,無論是 Hugging Face Hub、您的筆記本電腦還是您公司的遠程服務器。 +- 混合使用Dataset.map()和Dataset.filter()函數來整理數據。 +- 使用`Dataset.set_format()`在 Pandas 和 NumPy 等數據格式之間快速切換. +- 創建您自己的數據集並將其推送到 Hugging Face Hub。. +- 使用 Transformer 模型為您的文檔創建詞嵌入,並使用 FAISS 構建語義搜索引擎。. + +在[第七章](/course/chapter7),當我們深入研究 Transformer 模型非常適合的核心 NLP 任務時,我們將充分利用所有這些。 \ No newline at end of file diff --git a/chapters/zh-TW/chapter5/8.mdx b/chapters/zh-TW/chapter5/8.mdx new file mode 100644 index 000000000..b6c62af5e --- /dev/null +++ b/chapters/zh-TW/chapter5/8.mdx @@ -0,0 +1,221 @@ + + +# 章末小測試 + + + +本章涵蓋了很多方面! 如果你沒有掌握所有細節, 不用擔心; 在下一章將幫助你瞭解內部的事情是如何工作的。 + +不過, 在繼續下一章之前, 讓我們測試一下你在本章學到的內容。 + +### 1.🤗 Datasets中的 `load_dataset ()` 函數允許你從下列哪個位置加載數據集? +load_dataset() 函數的 data_files 參數來加載本地數據集。", + correct: true + }, + { + text: "Hugging Face Hub", + explain: "正確! 你可以通過提供數據集 ID 在 Hub 上加載數據集, 例如 < code > load _ dataset ('em otion') 。", + correct: true + }, + { + text: "遠程服務器", + explain: "正確! 你可以將URL傳遞給 load_dataset() 函數的 data_files 參數來加載遠程文件。", + correct: true + }, + ]} +/> + +### 2.假設您加載了 GLUE 任務,如下所示: +```py +from datasets import load_dataset + +dataset = load_dataset("glue", "mrpc", split="train") +``` + +以下哪個命令將從 `dataset` 中生成50個元素的隨機樣本? + + dataset.sample (50) ", + explain: "這是不正確的——沒有 < code > Dataset.sample () 方法。" + }, + { + text: "dataset.shuffle().select(range(50))", + explain: "正確! 正如你在本章中看待的, 你首先打亂了數據集, 然後從中選擇樣本。", + correct: true + }, + { + text: "dataset.select(range(50)).shuffle()", + explain: "這是不正確的——儘管代碼會運行, 但它只會隨機處理數據集中的前50個元素。" + } + ]} +/> + +### 3.假設你有一個叫做寵物數據集的家庭寵物數據集,它有一個名字列表示每個寵物的名字。下列哪種方法可以讓你過濾所有名字以字母"L"開頭的寵物的數據? + 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: "創建一個類似於 < code > def filter _ names (x) : return x ['name'] . startswith ('L') 的函數並運行 < code > pets _ dataset. filter (filter _ names) 。", + explain: "正確!就像使用 < code > Dataset.map () 一樣,你可以將顯式函數傳遞給 < code > Dataset.filter () 。當你有一些不適合於簡短 lambda 函數的複雜邏輯時,這是非常有用的。其他解決方案中還有哪一個可行?", + correct: true + } + ]} +/> + +### 4.什麼是內存映射? + + +### 5.下列哪一項是內存映射的主要好處? + + +### 6.為什麼下面的代碼是錯誤的? +```py +from datasets import load_dataset + +dataset = load_dataset("allocine", streaming=True, split="train") +dataset[0] +``` + + IterableDataset 。", + explain: "正確! < code > IterableDataset 是一個生成器, 而不是一個容器, 因此你應該使用 < code > next (iter (dataset)) 來訪問它的元素。", + correct: true + }, + { + text: "數據集 < code > allocine 沒有分割< code >訓練集。", + explain: "這是不正確的---- 查看 Hub 上的[ < code > allocine dataset card ]( https://huggingface.co/datasets/allocine ), 看看它包含哪些拆分。" + } + ]} +/> + +### 7.創建數據集卡的主要好處是什麼? + + + +### 8.什麼是語義搜索? + + +### 9.對於非對稱語義搜索,通常有: + + +### 10.我可以使用數據集加載數據用於其他領域,如語音處理? + diff --git a/chapters/zh-TW/chapter6/1.mdx b/chapters/zh-TW/chapter6/1.mdx new file mode 100644 index 000000000..725a405f0 --- /dev/null +++ b/chapters/zh-TW/chapter6/1.mdx @@ -0,0 +1,19 @@ +# 本章簡介 + + + +在 [第三章] (/course/chapter3) 中,我們研究瞭如何在給定任務上微調模型。 當我們這樣做時,我們需要使用與模型預訓練相同的標記器——但是當我們想從頭開始訓練模型時該怎麼辦? 不過,使用在來自其他領域或語言的語料庫上預訓練的標記器通常不是最理想的。 例如,在英語語料庫上訓練的標記器在日語文本語料庫上表現不佳,因為兩種語言中空格和標點符號的使用非常不同。 + +在本章中,您將學習如何在文本語料庫上訓練一個全新的標記器,然後將其用於預訓練語言模型。 這一切都將在 [🤗 Tokenizers](https://github.com/huggingface/tokenizers) 庫的幫助下完成,該庫在 [🤗 Transformers](https://github.com /huggingface/transformers) 庫之內。 我們將仔細研究這個庫提供的功能,並探討快速標記器與“慢”版本的區別。 + +我們將涵蓋的主題包括: + +* 如何訓練一個新的標記器,類似於給定檢查點在新的文本語料庫上使用的標記器 +* 快速標記器的特殊功能 +* 目前 NLP 中使用的三種主要子詞標記化算法之間的差異 +* 如何使用🤗 Tokenizers 庫從頭開始構建標記器並在一些數據上對其進行訓練 + +本章介紹的技術將使您為 [第 7 章](/course/chapter7/6) 中的部分做好準備,在那部分中,我們著眼於為 Python 源代碼創建語言模型。 讓我們首先看一下什麼是“訓練”標記器? \ No newline at end of file diff --git a/chapters/zh-TW/chapter6/10.mdx b/chapters/zh-TW/chapter6/10.mdx new file mode 100644 index 000000000..5064e77bc --- /dev/null +++ b/chapters/zh-TW/chapter6/10.mdx @@ -0,0 +1,273 @@ + + +# 章末小測驗 + + + +讓我們測試一下您在本章中學到了什麼! + +### 1.你應該什麼時候訓練一個新的標記器? + + +### 2.當使用“ train_new_from_iterator()”時,使用文本列表生成器與文本列表相比有什麼優點? + train_new_from_iterator() 接受的唯一類型。", + explain: "文本列表是一種特殊的文本列表生成器,因此該方法也會接受這種方法。再試一次!" + }, + { + text: "您將避免立即將整個數據集載入內存中。", + explain: "沒錯!每一批文本都會在你迭代的時候從內存中釋放出來,如果你使用數據集存儲文本的話,增益將尤其明顯。", + correct: true + }, + { + text: "這將允許 Tokenizers 庫使用並行處理。", + explain: "不,無論如何它都將使用並行處理。" + }, + { + text: "你訓練的標記器將產生更好的文本。", + explain: "Tokenizer 不生成文本——您是否將其與語言模型混淆了?" + } + ]} +/> + +### 3.使用“快速”標記器的優點是什麼? + + +### 4.“token-classification”管道如何處理跨多個標記的實體? + + +### 5.“question-answering”流水線如何處理長上下文? + + +### 6.什麼是標準化? + + +### 7.什麼是子詞標記化的前標記化? + + +### 8.選擇描述標記化 BPE 模式最準確的句子。 + + +### 9.選擇適用於 WordPiece 標記模型的句子。 + + +### 10.選擇適用於 Unigram 標記模式的句子。 + diff --git a/chapters/zh-TW/chapter6/2.mdx b/chapters/zh-TW/chapter6/2.mdx new file mode 100644 index 000000000..57c320314 --- /dev/null +++ b/chapters/zh-TW/chapter6/2.mdx @@ -0,0 +1,256 @@ +# 根據已有的tokenizer訓練新的tokenizer + + + +如果您感興趣的語言中沒有可用的語言模型,或者如果您的語料庫與您的語言模型所訓練的語料庫有很大不同,您很可能希望從適合您的數據的標記器從頭開始重新訓練模型 . 這將需要在您的數據集上訓練一個新的標記器。 但這究竟是什麼意思? 當我們在 [第二章](/course/chapter2) 中第一次查看標記器時,我們看到大多數 Transformer 模型使用_子詞分詞算法_。 為了識別哪些子詞是感興趣的並且在手頭的語料庫中最常出現,標記器需要仔細查看語料庫中的所有文本——我們稱之為*training*的過程。 這種訓練的確切規則取決於所使用的標記器的類型,我們將在本章後面討論三種主要算法。 + + + + + +⚠️ 訓練標記器與訓練模型不同!模型訓練使用隨機梯度下降使每個batch的loss小一點。它本質上是隨機的(這意味著在進行兩次相同的訓練時,您必須設置一些隨機數種子才能獲得相同的結果)。訓練標記器是一個統計過程,它試圖確定哪些子詞最適合為給定的語料庫選擇,用於選擇它們的確切規則取決於分詞算法。它是確定性的,這意味著在相同的語料庫上使用相同的算法進行訓練時,您總是會得到相同的結果。 + + + +## 準備語料庫 + +🤗 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 部分: + +```py +from datasets import load_dataset + +# This can take a few minutes to load, so grab a coffee or tea while you wait! +raw_datasets = load_dataset("code_search_net", "python") +``` + +我們可以查看訓練集的部分,以查看我們數據集中有哪些列: + +```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 +}) +``` + +我們可以看到數據集將文檔字符串與代碼分開,並且有他們各自的標記化後的結果。 這裡。 我們將只使用 `whole_func_string` 列來訓練我們的標記器。 我們可以通過指定到 `train` 中的一部分來查看這些函數的一個示例: + +```py +print(raw_datasets["train"][123456]["whole_func_string"]) +``` + +應該打印以下內容: + +```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) +``` + +我們需要做的第一件事是將數據集轉換為迭代器文本列表 - 例如,文本列表。使用文本列表將使我們的標記器運行得更快(訓練成批文本而不是一個接一個地處理單個文本),如果我們想避免一次將所有內容都放在內存中,它應該是一個迭代器。如果你的語料庫很大,你會想要利用這樣一個特性:🤗 Datasets 不會將所有內容都加載到 RAM 中,而是將數據集的元素存儲在磁盤上。 + +執行以下操作將創建一個包含 1,000 個文本的列表的列表,但會將所有內容加載到內存中: + +```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)] +``` + +使用 Python 生成器,我們可以避免 Python 將任何內容加載到內存中,直到真正需要為止。要創建這樣的生成器,您只需要將括號替換為圓括號: + +```py +training_corpus = ( + raw_datasets["train"][i : i + 1000]["whole_func_string"] + for i in range(0, len(raw_datasets["train"]), 1000) +) +``` + +這行代碼不會獲取數據集的任何元素;它只是創建了一個可以在 Python 中使用的對象 **for** 環形。文本只會在您需要時加載(即,當您處於 **for** 需要它們的循環),並且一次只會加載 1,000 個文本。這樣,即使您正在處理龐大的數據集,也不會耗盡所有內存。 + +生成器對象的問題在於它只能使用一次,每次訪問它將給出下一個值。 下面是一個例子: + +```py +gen = (i for i in range(10)) +print(list(gen)) +print(list(gen)) +``` + +我們第一次得到了這個列表,然後是一個空列表: + +```python out +[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] +[] +``` + +這就是我們定義一個返回生成器的函數的原因: + +```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() +``` + +您還可以在一個 **for** 循環內部使用 **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"] +``` + +這將產生與以前完全相同的生成器,但允許您使用比列表生成式中更復雜的邏輯。 + +## 訓練一個新的標記器 + +現在我們的語料庫是文本批量迭代器的形式,我們準備訓練一個新的標記器。為此,我們首先需要加載要與模型配對的標記器(此處為 GPT-2): + +```py +from transformers import AutoTokenizer + +old_tokenizer = AutoTokenizer.from_pretrained("gpt2") +``` + +即使我們要訓練一個新的標記器,最好還是這樣做以避免完全從頭開始。這樣,我們就不必指定任何關於標記化算法或我們想要使用的特殊標記;我們的新標記器將與 GPT-2 完全相同,唯一會改變的是輸入的數據,這將取決於我們訓練的語料。 + +首先讓我們看看這個標記器將如何處理示例的數據: + +```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'] +``` + +這個標記器有一些特殊的符號,比如 **Ċ** 和 **Ġ** ,分別表示空格和換行符。正如我們所看到的,這不是太有效:標記器為每個空格返回單獨的標記,當它可以將縮進級別組合在一起時(因為在代碼中具有四個或八個空格的集合將非常普遍)。它也有點奇怪地拆分了函數名稱,而習慣使用**_**的函數命名的方法。 + +讓我們訓練一個新的標記器,看看它是否能解決這些問題。為此,我們將使用 **train_new_from_iterator()** 方法: + +```py +tokenizer = old_tokenizer.train_new_from_iterator(training_corpus, 52000) +``` +如果您的語料庫非常大,此命令可能需要一些時間,但對於這個 1.6 GB 文本數據集,它的速度非常快(在具有 12 個內核的 AMD Ryzen 9 3900X CPU 上為 1 分 16 秒)。 + +注意 **AutoTokenizer.train_new_from_iterator()** 僅當您使用的標記器是“快速(fast)”標記器時才有效。正如您將在下一節中看到的,🤗 Transformers 庫包含兩種類型的標記器:一些完全用 Python 編寫,而另一些(快速的)由 🤗 Tokenizers 庫支持,該庫用[Rust](https://www.rust-lang.org)編程語言編寫。 Python 是最常用於數據科學和深度學習應用程序的語言,但是當需要並行化以提高速度時,必須用另一種語言編寫。例如,模型計算核心的矩陣乘法是用 CUDA 編寫的,CUDA 是一個針對 GPU 的優化 C 庫。 + +用純 Python 訓練一個全新的標記器會非常緩慢,這就是我們開發 🤗 Tokenizers庫的原因。請注意,正如您無需學習 CUDA 語言即可在 GPU 上執行您的模型一樣,您也無需學習 Rust 即可使用快速標記器。 🤗 Tokenizers 庫為許多內部調用 Rust 代碼的方法提供 Python 綁定;例如,並行化新標記器的訓練,或者,正如我們在[第三章](/course/chapter3)中看到的,對一批輸入進行標記化。 + +大多數 Transformer 模型都有可用的快速標記器(您可以[在這裡](https://huggingface.co/transformers/#supported-frameworks)檢查一些例外情況),如果 **AutoTokenizer** 可用,API 總是為您選擇快速標記器。在下一節中,我們將看看快速標記器具有的其他一些特殊功能,這些功能對於標記分類和問答等任務非常有用。然而,在深入研究之前,讓我們在上一個示例中嘗試我們全新的標記器: + +```py +tokens = tokenizer.tokenize(example) +tokens +``` + +```python out +['def', 'Ġadd', '_', 'numbers', '(', 'a', ',', 'Ġb', '):', 'ĊĠĠĠ', 'Ġ"""', 'Add', 'Ġthe', 'Ġtwo', 'Ġnumbers', 'Ġ`', + 'a', '`', 'Ġand', 'Ġ`', 'b', '`."""', 'ĊĠĠĠ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb'] +``` + +在這裡我們再次看到特殊符號 **Ċ** 和 **Ġ** 表示空格和換行符,但我們也可以看到我們的標記器學習了一些高度特定於 Python 函數語料庫的標記:例如,有一個 **ĊĠĠĠ** 表示縮進的標記,以及 **Ġ** 表示開始文檔字符串的三個引號的標記。標記器還正確使用**_**命名的規範將函數名稱拆分為 .這是一個非常緊湊的表示;相比之下,在同一個例子中使用簡單的英語標記器會給我們一個更長的句子: + +```py +print(len(tokens)) +print(len(old_tokenizer.tokenize(example))) +``` + +```python out +27 +36 +``` + +讓我們再看一個例子: + +```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', 'ĊĠĠĠĠ'] +``` + +除了一個縮進對應的token,這裡我們還可以看到一個雙縮進的token: **ĊĠĠĠĠĠĠĠ** .特殊的 Python 詞如 **class** , **init** , **call** , **self** , 和 **return** 每個都被標記為一個標記,我們可以看到,以及分裂 **_** 和 **.** 標記器甚至可以正確拆分駝峰式名稱: **LinearLayer** 被標記為 **[ĠLinear, Layer]** . + +## 保存標記器 + +為了確保我們以後可以使用它,我們需要保存我們的新標記器。就像模型一樣,是通過 **save_pretrained()** 方法: + +```py +tokenizer.save_pretrained("code-search-net-tokenizer") +``` + +這將創建一個名為的*code-search-net-tokenizer*的新文件夾,它將包含重新加載標記器所需要的所有文件。如果您想與您的同事和朋友分享這個標記器,您可以通過登錄您的帳戶將其上傳到 Hub。如果您在notebook上工作,有一個方便的功能可以幫助您: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +這將顯示一個小部件,您可以在其中輸入您的 Hugging Face 登錄憑據。如果您不是在notebook上工作,只需在終端中輸入以下行: + +```bash +huggingface-cli login +``` + +登錄後,您可以通過執行以下命令來推送您的標記器: + +```py +tokenizer.push_to_hub("code-search-net-tokenizer") +``` + +這將在您的命名空間中創建一個名為**code-search-net-tokenizer**的新存儲庫 ,包含標記器文件。然後,您可以使用以下命令從任何地方加載標記器的 **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") +``` + +您現在已準備好從頭開始訓練語言模型並根據您手頭的任務對其進行微調!我們將在[第七章](/course/chapter7)進行這部分。但首先,在本章的其餘部分,我們將仔細研究快速標記器,並詳細探討調用 **train_new_from_iterator()** 方法時實際發生的情況 . diff --git a/chapters/zh-TW/chapter6/3.mdx b/chapters/zh-TW/chapter6/3.mdx new file mode 100644 index 000000000..c35a3e3ca --- /dev/null +++ b/chapters/zh-TW/chapter6/3.mdx @@ -0,0 +1,473 @@ + + +# 快速標記器的特殊能力 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +在本節中,我們將仔細研究 🤗 Transformers 中標記器的功能。到目前為止,我們只使用它們來標記輸入或將 ID 解碼迴文本,但是標記器——尤其是那些由 🤗 Tokenizers 庫支持的——可以做更多的事情。為了說明這些附加功能,我們將探索如何重現結果 **token-classification** (我們稱之為 **ner** ) 和 **question-answering** 我們第一次在[Chapter 1](/course/chapter1)中遇到的管道. + + + +在接下來的討論中,我們會經常區分“慢”和“快”分詞器。慢速分詞器是在 🤗 Transformers 庫中用 Python 編寫的,而快速版本是由 🤗 分詞器提供的,它們是用 Rust 編寫的。如果你還記得在[Chapter 5](/course/chapter5/3)中報告了快速和慢速分詞器對藥物審查數據集進行分詞所需的時間的這張表,您應該知道為什麼我們稱它們為“快”和“慢”: + + | Fast tokenizer | Slow tokenizer +:--------------:|:--------------:|:-------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s + + + +⚠️ 對單個句子進行分詞時,您不會總是看到相同分詞器的慢速和快速版本之間的速度差異。事實上,快速版本實際上可能更慢!只有同時對大量文本進行標記時,您才能清楚地看到差異。 + + + +## 批量編碼 + + + +分詞器的輸出不是簡單的 Python 字典;我們得到的實際上是一個特殊的 **BatchEncoding** 目的。它是字典的子類(這就是為什麼我們之前能夠毫無問題地索引到該結果中的原因),但具有主要由快速標記器使用的附加方法。 + +除了它們的並行化能力之外,快速標記器的關鍵功能是它們始終跟蹤最終標記來自的原始文本範圍——我們稱之為偏移映射.這反過來又解鎖了諸如將每個單詞映射到它生成的標記或將原始文本的每個字符映射到它內部的標記等功能,反之亦然。讓我們看一個例子: + +```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** 類默認選擇快速標記器,我們可以使用附加方法 this **BatchEncoding** 對象提供。我們有兩種方法來檢查我們的分詞器是快的還是慢的。我們可以檢查 **is_fast** 的屬性 **tokenizer** : + +```python +tokenizer.is_fast +``` + +```python out +True +``` + +或檢查我們的相同屬性 **encoding** : + +```python +encoding.is_fast +``` + +```python out +True +``` + +讓我們看看快速標記器使我們能夠做什麼。首先,我們可以訪問令牌而無需將 ID 轉換回令牌: + +```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) 和詞性 (POS) 標記等任務中的標記。我們還可以使用它來屏蔽來自屏蔽語言建模中來自同一單詞的所有標記(一種稱為全詞掩碼)。 + + + +一個詞是什麼的概念很複雜。例如,“I'll”(“I will”的縮寫)算一兩個詞嗎?它實際上取決於分詞器和它應用的預分詞操作。一些標記器只是在空格上拆分,因此他們會將其視為一個詞。其他人在空格頂部使用標點符號,因此將其視為兩個詞。 + +✏️ 試試看!從bert base cased和roberta base檢查點創建一個標記器,並用它們標記“81s”。你觀察到了什麼?ID這個詞是什麼? + + + +同樣,有一個 **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 +``` + +正如我們之前提到的,這一切都是由快速標記器跟蹤每個標記來自列表中的文本跨度這一事實提供支持的抵消.為了說明它們的用途,接下來我們將向您展示如何複製結果 **token-classification** 手動管道。 + + + +✏️ 試試看!創建您自己的示例文本,看看您是否能理解哪些標記與單詞 ID 相關聯,以及如何提取單個單詞的字符跨度。對於獎勵積分,請嘗試使用兩個句子作為輸入,看看句子 ID 是否對您有意義。 + + + +## 在令牌分類管道內 + +在[Chapter 1](/course/chapter1)我們第一次嘗試使用 NER——任務是識別文本的哪些部分對應於個人、地點或組織等實體——使用 🤗 Transformers **pipeline()** 功能。然後,在[Chapter 2](/course/chapter2),我們看到了管道如何將從原始文本中獲取預測所需的三個階段組合在一起:標記化、通過模型傳遞輸入和後處理。前兩步 **token-classification** 管道與任何其他管道相同,但後處理稍微複雜一些 - 讓我們看看如何! + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +### 通過管道獲得基本結果 + +首先,讓我們獲取一個標記分類管道,以便我們可以手動比較一些結果。默認使用的模型是[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,標記的分數) + +- `"max"`,其中每個實體的分數是該實體中標記的最大分數(因此對於“Hugging Face”,它將是 0.98879766,即“Face”的分數) + +- `"average"`, 其中每個實體的分數是組成該實體的單詞分數的平均值(因此對於“Sylvain”,與“simple”策略,但“Hugging Face”的得分為 0.9819,“Hugging”得分的平均值為 0.975,“Face”得分為 0.98879) + +現在讓我們看看如何在不使用pipeline()函數的情況下獲得這些結果! + +### 從輸入到預測 + +{#if fw === 'pt'} + +首先,我們需要標記我們的輸入並將其傳遞給模型。這是完全按照[Chapter 2](/course/chapter2);我們使用 **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** 在這裡,我們為輸入序列中的每個標記獲得一組 logits: + +```py +print(inputs["input_ids"].shape) +print(outputs.logits.shape) +``` + +```python out +torch.Size([1, 19]) +torch.Size([1, 19, 9]) +``` + +{:else} + +首先,我們需要標記我們的輸入並將其傳遞給模型。這是完全按照[Chapter 2](/course/chapter2);我們使用 **AutoXxx** 類,然後在我們的示例中使用它們: + +```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) +``` + +於我們正在使用 **AutoModelForTokenClassification** 在這裡,我們為輸入序列中的每個標記獲得一組 logits: + +```py +print(inputs["input_ids"].shape) +print(outputs.logits.shape) +``` + +```python out +(1, 19) +(1, 19, 9) +``` + +{/if} + +我們有一個包含 19 個標記的 1 個序列的批次,模型有 9 個不同的標籤,因此模型的輸出具有 1 x 19 x 9 的形狀。與文本分類管道一樣,我們使用 softmax 函數來轉換這些 logits到概率,我們採用 argmax 來獲得預測(請注意,我們可以在 logits 上採用 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** 是不在任何命名實體中的標記的標籤(它代表“外部”),然後我們為每種類型的實體(雜項、人員、組織和位置)提供兩個標籤。標籤 **B-XXX** 表示令牌在實體的開頭 **XXX** 和標籤 **I-XXX** 表示令牌在實體內 **XXX** .例如,在當前示例中,我們希望我們的模型對令牌進行分類 **S** 作為 **B-PER** (一個人實體的開始)和令牌 **##yl** , **##va** 和 **##in** 作為 **I-PER** (在個人實體內) + +在這種情況下,您可能認為模型是錯誤的,因為它給出了標籤 **I-PER** 對所有這四個令牌,但這並不完全正確。實際上有兩種格式 **B-** 和 **I-** 標籤:IOB1和IOB2. IOB2 格式(下面粉紅色)是我們介紹的格式,而在 IOB1 格式(藍色)中,標籤以 **B-** 僅用於分隔相同類型的兩個相鄰實體。我們使用的模型在使用該格式的數據集上進行了微調,這就是它分配標籤的原因 **I-PER** 到 **S** 令牌。 + +
+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}] +``` + +這和我們從第一個管道中得到的一樣! + +### 分組實體 + +使用偏移量來確定每個實體的開始和結束鍵很方便,但該信息並不是絕對必要的。然而,當我們想要將實體組合在一起時,偏移量將為我們節省大量混亂的代碼。例如,如果我們想將令牌組合在一起 **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": + # Remove the B- or I- + label = label[2:] + start, _ = offsets[idx] + + # Grab all the tokens labeled with I-label + 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 + + # The score is the mean of all the scores of the tokens in that grouped entity + 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}] +``` + +這些偏移量非常有用的另一個任務示例是問答。深入研究這個管道,我們將在下一節中進行,也將使我們能夠了解 🤗 Transformers 庫中標記器的最後一個功能:當我們將輸入截斷為給定長度時處理溢出的標記。 diff --git a/chapters/zh-TW/chapter6/3b.mdx b/chapters/zh-TW/chapter6/3b.mdx new file mode 100644 index 000000000..4471c4cec --- /dev/null +++ b/chapters/zh-TW/chapter6/3b.mdx @@ -0,0 +1,639 @@ + + +# QA 管道中的快速標記器 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +我們現在將深入研究 **question-answering** 管道,看看如何利用偏移量從上下文中獲取手頭問題的答案,有點像我們在上一節中對分組實體所做的。然後我們將看到我們如何處理最終被截斷的非常長的上下文。如果您對問答任務不感興趣,可以跳過此部分。 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +## 使用 `question-answering` 管道 + +正如我們在[Chapter 1](/course/chapter1),我們可以使用 **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'} +``` + +讓我們看看它是如何做到這一切的! + +## 使用模型進行問答 + +與任何其他管道一樣,我們首先對輸入進行標記化,然後通過模型將其發送。默認情況下用於的檢查點 **question-answering** 管道是[distilbert-base-cased-distilled-squad](https://huggingface.co/distilbert-base-cased-distilled-squad)(名稱中的“squad”來自模型微調的數據集;我們將在[Chapter 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} + +請注意,我們將問題和上下文標記為一對,首先是問題 + +
+An example of tokenization of question and context + +
+ +問答模型的工作方式與我們迄今為止看到的模型略有不同。以上圖為例,該模型已經過訓練,可以預測答案開始的標記的索引(此處為 21)和答案結束處的標記的索引(此處為 24)。這就是為什麼這些模型不返回一個 logits 的張量,而是返回兩個:一個用於對應於答案的開始標記的 logits,另一個用於對應於答案的結束標記的 logits。由於在這種情況下我們只有一個包含 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} + +為了將這些 logits 轉換為概率,我們將應用一個 softmax 函數——但在此之前,我們需要確保我們屏蔽了不屬於上下文的索引。我們的輸入是 **[CLS] question [SEP] context [SEP]** ,所以我們需要屏蔽問題的標記以及 **[SEP]** 令牌。我們將保留 **[CLS]** 然而,因為某些模型使用它來表示答案不在上下文中。 + +由於我們將在之後應用 softmax,我們只需要用一個大的負數替換我們想要屏蔽的 logits。在這裡,我們使用 **-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} + +現在我們已經正確屏蔽了與我們不想預測的位置相對應的 logits,我們可以應用 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}]\\) where `start_index <= end_index`. + +首先讓我們計算所有可能的產品: +```py +scores = start_probabilities[:, None] * end_probabilities[None, :] +``` + +{#if fw === 'pt'} + +然後我們將屏蔽這些值 **start_index > end_index** 通過將它們設置為 **0** (其他概率都是正數)。這 **torch.triu()** 函數返回作為參數傳遞的 2D 張量的上三角部分,因此它會為我們做屏蔽: + +```py +scores = torch.triu(scores) +``` + +{:else} +然後我們將屏蔽這些值 **start_index > end_index** 通過將它們設置為 **0** (其他概率都是正數)。這 **torch.triu()** 函數返回作為參數傳遞的 2D 張量的上三角部分,因此它會為我們做屏蔽: + +```py +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** 就標記而言的答案,所以現在我們只需要轉換為上下文中的字符索引。這是偏移量非常有用的地方。我們可以像在令牌分類任務中一樣抓住它們並使用它們: + +```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} +``` + +太棒了!這和我們的第一個例子一樣! + + + +✏️ **試試看!** 使用您之前計算的最佳分數來顯示五個最可能的答案。要檢查您的結果,請返回到第一個管道並在調用它時傳入。 + + + +## 處理長上下文 + +如果我們嘗試對我們之前作為示例使用的問題和長上下文進行標記化,我們將獲得比在 **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 個標記(我們需要添加填充以使最後一個條目與其他條目的大小相同)並且每個條目之間有 2 個標記的重疊。 + +讓我們仔細看看標記化的結果: + +```py +print(inputs.keys()) +``` + +```python out +dict_keys(['input_ids', 'attention_mask', 'overflow_to_sample_mapping']) +``` + +正如預期的那樣,我們得到了輸入 ID 和一個注意力掩碼。最後一個鍵, **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"]) +``` + +讓我們: + +```python out +[0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1] +``` + +這意味著第一個句子像以前一樣分成 7 個塊,接下來的 4 個塊來自第二個句子。 + + +現在讓我們回到我們的長期背景。默認情況下 **question-answering** 管道使用的最大長度為 384,正如我們之前提到的,步長為 128,這對應於模型微調的方式(您可以通過傳遞 **max_seq_len** 和 **stride** 調用管道時的參數)。因此,我們將在標記化時使用這些參數。我們還將添加填充(具有相同長度的樣本,因此我們可以構建張量)以及請求偏移量: + +```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** 將包含模型期望的輸入 ID 和注意力掩碼,以及偏移量和 **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} + +我們的長上下文被分成兩部分,這意味著在它通過我們的模型後,我們將有兩組開始和結束 logits: + +```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 之前首先屏蔽不屬於上下文的標記。我們還屏蔽了所有填充標記(由注意掩碼標記): + +{#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} + +然後我們可以使用 softmax 將我們的 logits 轉換為概率: + +{#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} +``` + +如果我們忽略第一個結果,我們會得到與這個長上下文的管道相同的結果——是的! + + + +✏️ **試試看!** 使用您之前計算的最佳分數來顯示五個最可能的答案(對於整個上下文,而不是每個塊)。要檢查您的結果,請返回到第一個管道並在調用它時傳入。 + + + +我們對分詞器功能的深入研究到此結束。我們將在下一章再次將所有這些付諸實踐,屆時我們將向您展示如何在一系列常見的 NLP 任務上微調模型。 diff --git a/chapters/zh-TW/chapter6/4.mdx b/chapters/zh-TW/chapter6/4.mdx new file mode 100644 index 000000000..e866ea2bf --- /dev/null +++ b/chapters/zh-TW/chapter6/4.mdx @@ -0,0 +1,124 @@ +# 標準化和預標記化 + + + +在我們更深入地研究與 Transformer 模型(字節對編碼 [BPE]、WordPiece 和 Unigram)一起使用的三種最常見的子詞標記化算法之前,我們將首先看一下每個標記器應用於文本的預處理。以下是標記化管道中步驟的高級概述: + +
+The tokenization pipeline. + +
+ +在將文本拆分為子標記之前(根據其模型),分詞器執行兩個步驟: _normalization_ 和 _pre-tokenization_. + +## 正常化 + + + +標準化步驟涉及一些常規清理,例如刪除不必要的空格、小寫和/或刪除重音符號。如果你熟悉[Unicode normalization](http://www.unicode.org/reports/tr15/)(例如 NFC 或 NFKC),這也是 tokenizer 可能應用的東西。 + +🤗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** 檢查點,標準化應用小寫並刪除重音。 + + + +✏️ **試試看!** 從檢查點加載標記器並將相同的示例傳遞給它。您可以看到分詞器的帶殼和無殼版本之間的主要區別是什麼? + + + + +## 預標記化 + + + +正如我們將在下一節中看到的,分詞器不能單獨在原始文本上進行訓練。相反,我們首先需要將文本拆分為小實體,例如單詞。這就是預標記化步驟的用武之地。 正如我們在[Chapter 2](/course/chapter2), 基於單詞的標記器可以簡單地將原始文本拆分為空白和標點符號的單詞。這些詞將是分詞器在訓練期間可以學習的子標記的邊界。 + +要查看快速分詞器如何執行預分詞,我們可以使用 **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 分詞器不同,此分詞器不會忽略雙空格 + +最後一個例子,讓我們看一下基於 SentencePiece 算法的 T5 分詞器: + +```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](https://github.com/google/sentencepiece) 是一種用於文本預處理的標記化算法,您可以將其與我們將在接下來的三個部分中看到的任何模型一起使用。它將文本視為 Unicode 字符序列,並用特殊字符替換空格, **▁** .與 Unigram 算法結合使用(參見[section 7](/course/chapter7/7)), 它甚至不需要預標記化步驟,這對於不使用空格字符的語言(如中文或日語)非常有用。 + +SentencePiece 的另一個主要特點是可逆標記化:由於沒有對空格進行特殊處理,因此只需通過將它們連接起來並替換 **_** s 帶空格——這會導致標準化的文本。正如我們之前看到的,BERT 分詞器刪除了重複的空格,因此它的分詞是不可逆的。 + +## 算法概述 + +在下面的部分中,我們將深入研究三種主要的子詞標記化算法:BPE(由 GPT-2 和其他人使用)、WordPiece(例如由 BERT 使用)和 Unigram(由 T5 和其他人使用)。在我們開始之前,這裡是它們各自工作原理的快速概述。如果您還沒有理解,請在閱讀下一節後立即回到此表。 + + +Model | BPE | WordPiece | Unigram +:----:|:---:|:---------:|:------: +Training | Starts from a small vocabulary and learns rules to merge tokens | Starts from a small vocabulary and learns rules to merge tokens | Starts from a large vocabulary and learns rules to remove tokens +Training step | Merges the tokens corresponding to the most common pair | Merges the tokens corresponding to the pair with the best score based on the frequency of the pair, privileging pairs where each individual token is less frequent | Removes all the tokens in the vocabulary that will minimize the loss computed on the whole corpus +Learns | Merge rules and a vocabulary | Just a vocabulary | A vocabulary with a score for each token +Encoding | Splits a word into characters and applies the merges learned during training | Finds the longest subword starting from the beginning that is in the vocabulary, then does the same for the rest of the word | Finds the most likely split into tokens, using the scores learned during training + +現在讓我們深入瞭解 BPE! \ No newline at end of file diff --git a/chapters/zh-TW/chapter6/5.mdx b/chapters/zh-TW/chapter6/5.mdx new file mode 100644 index 000000000..1471d292d --- /dev/null +++ b/chapters/zh-TW/chapter6/5.mdx @@ -0,0 +1,360 @@ +# 字節對編碼標記化 + + + +字節對編碼(BPE)最初被開發為一種壓縮文本的算法,然後在預訓練 GPT 模型時被 OpenAI 用於標記化。許多 Transformer 模型都使用它,包括 GPT、GPT-2、RoBERTa、BART 和 DeBERTa。 + + + + + +💡 本節深入介紹了BPE,甚至展示了一個完整的實現。如果你只想大致瞭解標記化算法,可以跳到最後。 + + + +## 訓練算法 + +BPE 訓練首先計算語料庫中使用的唯一單詞集(在完成標準化和預標記化步驟之後),然後通過獲取用於編寫這些單詞的所有符號來構建詞彙表。舉一個簡單的例子,假設我們的語料庫使用了這五個詞: + +``` +"hug", "pug", "pun", "bun", "hugs" +``` + +基礎詞彙將是 `["b", "g", "h", "n", "p", "s", "u"]`。對於實際情況,基本詞彙表將包含所有 ASCII 字符,至少,可能還包含一些 Unicode 字符。如果您正在標記的示例使用不在訓練語料庫中的字符,則該字符將轉換為未知標記。這就是為什麼許多 NLP 模型在分析帶有表情符號的內容方面非常糟糕的原因之一。 + + + +TGPT-2 和 RoBERTa 標記器(非常相似)有一個聰明的方法來處理這個問題: 他們不把單詞看成是用 Unicode 字符寫的,而是用字節寫的。這樣,基本詞彙表的大小很小(256),但你能想到的每個字符仍將被包含在內,而不會最終轉換為未知標記。這個技巧被稱為 *字節級 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) +``` + +我們繼續這樣合併,直到達到我們所需的詞彙量。 + + + +✏️ **現在輪到你了!**你認為下一個合併規則是什麼? + + + +## 標記化算法 + +標記化緊跟訓練過程,從某種意義上說,通過應用以下步驟對新輸入進行標記: + +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"` 被合併,然後是 `"hu"` 和 `"g"` 被合併。 + + + +✏️ **現在輪到你了!** 你認為這個詞 `"unhug"` 將如何被標記? + + + +## 實現 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") +``` + +要繼續接下來的步驟,我們需要在我們的`分詞`字典中應用該合併。讓我們為此編寫另一個函數: + +```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/zh-TW/chapter6/6.mdx b/chapters/zh-TW/chapter6/6.mdx new file mode 100644 index 000000000..69c08c682 --- /dev/null +++ b/chapters/zh-TW/chapter6/6.mdx @@ -0,0 +1,373 @@ +# WordPiece 標記化 + + + +WordPiece 是 Google 為預訓練 BERT 而開發的標記化算法。此後,它在不少基於 BERT 的 Transformer 模型中得到重用,例如 DistilBERT、MobileBERT、Funnel Transformers 和 MPNET。它在訓練方面與 BPE 非常相似,但實際標記化的方式不同。 + + + + + +💡 本節深入介紹 WordPiece,甚至展示完整的實現。如果您只想大致瞭解標記化算法,可以跳到最後。 + + + +## 訓練算法 + + + +⚠️ 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) +``` + +我們繼續這樣處理,直到達到我們所需的詞彙量。 + + + +✏️ **現在輪到你了!** 下一個合併規則是什麼? + + +## 標記化算法 + +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 的另一個區別,BPE 只會將不在詞彙表中的單個字符分類為未知。 + + + +✏️ **現在輪到你了!** `"pugs"` 將被如何標記? + + + +## 實現 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") +``` + +要繼續接下來的步驟,我們需要在我們的 `拆分` 字典中應用該合併。讓我們為此編寫另一個函數: + +```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', '##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/zh-TW/chapter6/7.mdx b/chapters/zh-TW/chapter6/7.mdx new file mode 100644 index 000000000..95e013cb8 --- /dev/null +++ b/chapters/zh-TW/chapter6/7.mdx @@ -0,0 +1,381 @@ +# Unigram標記化 + + + +在 SentencePiece 中經常使用 Unigram 算法,該算法是 AlBERT、T5、mBART、Big Bird 和 XLNet 等模型使用的標記化算法。 + + + + + +💡 本節深入介紹了 Unigram,甚至展示了一個完整的實現。如果你只想大致瞭解標記化算法,可以跳到最後。 + + + +## 訓練算法 + +與 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"] +``` + +## 標記化算法 + +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 模型計算每個分割的概率。由於所有標記都被認為是獨立的,所以這個概率只是每個標記概率的乘積。例如 `"pug"` 的標記化 `["p", "u", "g"]` 的概率為: + +$$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)算法*。本質上,我們可以構建一個圖來檢測給定單詞的可能分割,如果從_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"` 的標記化及其分數。 + + + +## 回到訓練 + +現在我們已經瞭解了標記化的工作原理,我們可以更深入地研究訓練期間使用的損失。在任何給定的階段,這個損失是通過對語料庫中的每個單詞進行標記來計算的,使用當前詞彙表和由語料庫中每個標記的頻率確定的 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 + +現在讓我們在代碼中實現我們迄今為止看到的所有內容。與 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 + # 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)] +``` + +我們用最優的子詞對字符進行分組,以獲得大小為 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 使用一種稱為增強後綴數組(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()} +``` + +N現在主要功能是使用 Viterbi 算法標記單詞的功能。正如我們之前看到的,該算法計算單詞的每個子串的最佳分段,我們將其存儲在名為 `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)): + # 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 +``` + +我們已經可以在一些詞上嘗試我們的初始模型: + +```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(): + # 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 +``` + +我們可以在給定的標記上嘗試: + +```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]) + # 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()} +``` + +然後,為了標記一些文本,我們只需要應用預標記化,然後使用我們的 `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/zh-TW/chapter6/8.mdx b/chapters/zh-TW/chapter6/8.mdx new file mode 100644 index 000000000..9a31a2bf1 --- /dev/null +++ b/chapters/zh-TW/chapter6/8.mdx @@ -0,0 +1,564 @@ +# 逐塊地構建標記器 + + + +正如我們在前幾節中看到的,標記化包括幾個步驟: + +- 規範化(任何認為必要的文本清理,例如刪除空格或重音符號、Unicode 規範化等) +- 預標記化(將輸入拆分為單詞) +- 通過模型處理輸入(使用預先拆分的詞來生成一系列標記) +- 後處理(添加標記器的特殊標記,生成注意力掩碼和標記類型 ID) + +提醒一下,這裡再看一下整個過程 + +
+The tokenization pipeline. + +
+ +🤗 Tokenizers 庫旨在為每個步驟提供多個選項,您可以將它們混合和匹配在一起。在本節中,我們將看到如何從頭開始構建標記器,而不是像我們[第二節 2](/course/chapter6/2)那樣從舊的標記器訓練新的標記器.然後,您將能夠構建您能想到的任何類型的標記器! + + + +更準確地說,該庫是圍繞一個中央「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)找到完整的模塊列表。 + +## 獲取語​​料庫 + +為了訓練我們的新標記器,我們將使用一個小的文本語料庫(因此示例運行得很快)。獲取語​​料庫的步驟與我們在[在這章的開始]((/course/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()** 函數是一個生成器,每次調用的時候將產生 1,000 個文本,我們將用它來訓練標記器。 + +🤗 Tokenizers 也可以直接在文本文件上進行訓練。以下是我們如何生成一個文本文件,其中包含我們可以在本地使用的來自 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 標記器 + +要使用 🤗 Tokenizers 庫構建標記器,我們首先使用 **model** 實例化一個 **Tokenizer** 對象,然後將 **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庫中實現的非常方便的normalizer——所以讓我們看看如何手動創建 BERT normalizer。 該庫提供了一個“Lowercase(小寫)”的normalizer和一個“StripAccents”的normalizer,您可以使用“序列”組合多個normalizer: + +```python +tokenizer.normalizer = normalizers.Sequence( + [normalizers.NFD(), normalizers.Lowercase(), normalizers.StripAccents()] +) +``` + +我們也在使用 **NFD** Unicode normalizer,否則 **StripAccents** normalizer 無法正確識別帶重音的字符,因此沒辦法刪除它們。 + +正如我們之前看到的,我們可以使用 **normalize** 的 **normalize_str()** 方法查看它對給定文本的影響: + +```python +print(tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) +``` + +```python out +hello how are u? +``` + + + +**更進一步**如果您在包含 unicode 字符的字符串上測試先前normalizers的兩個版本,您肯定會注意到這兩個normalizers並不完全等效。 +為了不過度使用 `normalizers.Sequence` 使版本過於複雜,我們沒有包含當 `clean_text` 參數設置為 `True` 時 `BertNormalizer` 需要的正則表達式替換 - 這是默認行為。 但不要擔心:通過在normalizer序列中添加兩個 `normalizers.Replace` 可以在不使用方便的 `BertNormalizer` 的情況下獲得完全相同的規範化。 + + + +接下來是預標記步驟。 同樣,我們可以使用一個預構建的“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))] +``` + +像normalizers一樣,您可以使用 **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]** 在詞彙表中的ID: + +```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** .對於這些特殊標記和句子,我們還需要使用在冒號後指定相應的標記類型 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)], +) +``` + +請注意,我們需要傳遞特殊標記的 ID,以便標記器可以正確地將特殊標記轉換為它們的 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") +``` + +然後我們可以使用**from_file()** 方法從該文件裡重新加載 **Tokenizer** 對象: + +```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", # You can load from the tokenizer file, alternatively + 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 標記器 + +現在讓我們構建一個 GPT-2 標記器。與 BERT 標記器一樣,我們首先使用 **Tokenizer** 初始化一個BPE 模型: + +```python +tokenizer = Tokenizer(models.BPE()) +``` + +和 BERT 一樣,如果我們有一個詞彙表,我們可以用一個詞彙表來初始化這個模型(在這種情況下,我們需要傳遞 `vocab` 和 `merges`),但是由於我們將從頭開始訓練,所以我們不需要這樣去做。 我們也不需要指定“unk_token”,因為 GPT-2 使用的字節級 BPE,不需要“unk_token”。 + +GPT-2 不使用歸一化器,因此我們跳過該步驟並直接進入預標記化: + +```python +tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False) +``` + +我們在此處添加到 `ByteLevel` 的選項是不在句子開頭添加空格(默認為ture)。 我們可以看一下使用這個標記器對之前示例文本的預標記: + +```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 標記器 + +現在讓我們構建一個 XLNet 標記器。與之前的標記器一樣,我們首先使用 Unigram 模型初始化一個 **Tokenizer** : + +```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,}"), " "), + ] +) +``` + +這會取代 **“** 和 **”** 和 **”** 以及任何兩個或多個空格與單個空格的序列,以及刪除文本中的重音以進行標記。 + +用於任何 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', '.'] +``` + +A peculiarity of XLNet is that it puts the `` token at the end of the sentence, with a type ID of 2 (to distinguish it from the other tokens). It's padding on the left, as a result. We can deal with all the special tokens and token type IDs with a template, like for BERT, but first we have to get the IDs of the `` and `` tokens: +XLNet 的一個特點是它將`` 標記放在句子的末尾,類型ID 為2(以將其與其他標記區分開來)。它會將結果填充在左側。 我們可以使用模板處理所有特殊標記和標記類型 ID,例如 BERT,但首先我們必須獲取 `` 和 `` 標記的 ID: + +```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() +``` + +我們完成了這個標記器! 我們可以像以前一樣保存標記器,如果我們想在 🤗 Transformers 中使用它,可以將它包裝在 `PreTrainedTokenizerFast` 或 `XLNetTokenizerFast` 中。 使用 `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) +``` + +現在您已經瞭解瞭如何使用各種構建塊來構建現有的標記器,您應該能夠使用 🤗 tokenizer庫編寫您想要的任何標記器,並能夠在 🤗 Transformers中使用它。 \ No newline at end of file diff --git a/chapters/zh-TW/chapter6/9.mdx b/chapters/zh-TW/chapter6/9.mdx new file mode 100644 index 000000000..2a8064b81 --- /dev/null +++ b/chapters/zh-TW/chapter6/9.mdx @@ -0,0 +1,16 @@ +# 標記器,回顧! + + + +完成這一章,辛苦了! + +在深入研究標記器之後,您應該: + +- 能夠使用舊的標記器作為模板來訓練新的標記器 +- 瞭解如何使用偏移量將標記的位置映射到其原始文本範圍 +- 瞭解 BPE、WordPiece 和 Unigram 之間的區別 +- 能夠混合和匹配 🤗 Tokenizers 庫提供的塊來構建您自己的標記器 +- 能夠在 🤗 Transformers 庫中使用該標記器 \ No newline at end of file diff --git a/chapters/zh-TW/chapter7/1.mdx b/chapters/zh-TW/chapter7/1.mdx new file mode 100644 index 000000000..038fcc3bd --- /dev/null +++ b/chapters/zh-TW/chapter7/1.mdx @@ -0,0 +1,33 @@ + + +# 章節簡介 + +在[第三章](/course/chapter3),您瞭解瞭如何微調文本分類的模型。在本章中,我們將處理以下常見NLP任務: + +- 標記(token)分類 +- 遮罩語言建模(如BERT) +- 提取文本摘要 +- 翻譯 +- 因果語言建模預訓練(如GPT-2) +- 問答 + +{#if fw === 'pt'} + +為此,您需要利用[第三章](/course/chapter3)中學到的`Trainer` API 和🤗Accelerate 庫、[第五章](/course/chapter5)中的 🤗 Datasets 庫以及[第六章](/course/chapter6)中的 🤗 Tokenizers 庫的所有知識。我們還會將結果上傳到模型中心,就像我們在[第四章](/course/chapter4)中所做的那樣,所以這確實是將之前所有內容彙集在一起的章節! + +每個部分都可以獨立閱讀,並將向您展示如何使用API或按照您自己的訓練循環訓練模型,使用🤗 Accelerate 加速。你可以隨意跳過其中一部分,把注意力集中在你最感興趣的那一部分:API可以優化或訓練您的模型而無需擔心幕後發生了什麼,而訓練循環使用可以讓您更輕鬆地自定義所需的任何結構。 + +{:else} + +為此,您需要利用[第三章](/course/chapter3)中學到的有關Keras API、[第五章](/course/chapter5)中的 🤗 Datasets 庫以及[第六章](/course/chapter6)中的 🤗 Tokenizers 庫的所有知識。我們還會將結果上傳到模型中心,就像我們在[第四章](/course/chapter4)中所做的那樣,所以這確實是將之前所有內容彙集在一起的章節! + +每個部分都可以獨立閱讀。 + +{/if} + + + + +如果您按順序閱讀這些部分,您會注意到它們有很多共同的代碼和陳述。 重複是有意為之的,讓您可以深入(或稍後返回)任何您感興趣的任務並找到一個完整的工作示例。 + + diff --git a/chapters/zh-TW/chapter7/2.mdx b/chapters/zh-TW/chapter7/2.mdx new file mode 100644 index 000000000..3652439a7 --- /dev/null +++ b/chapters/zh-TW/chapter7/2.mdx @@ -0,0 +1,978 @@ + + +# Token 分類 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +我們將探索的第一個應用是Token分類。這個通用任務包括任何可以表述為“為句子中的詞或字分配標籤”的問題,例如: + +- **實體命名識別 (NER)**: 找出句子中的實體(如人物、地點或組織)。這可以通過為每個實體或“無實體”指定一個類別的標籤。 +- **詞性標註 (POS)**: 將句子中的每個單詞標記為對應於特定的詞性(如名詞、動詞、形容詞等)。 +- **分塊(chunking)**: 找到屬於同一實體的Token。這個任務(可結合POS或NER)可以任何將一塊Token作為制定一個標籤(通常是B -),另一個標籤(通常I -)表示Token是否是同一塊,和第三個標籤(通常是O)表示Token不屬於任何塊。也就是標出句子中的短語塊,例如名詞短語(NP),動詞短語(VP)等。 + + + +當然,還有很多其他類型的token分類問題;這些只是幾個有代表性的例子。在本節中,我們將在 NER 任務上微調模型 (BERT),然後該模型將能夠計算如下預測: + + + + + +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).找到我們將訓練並上傳到 Hub的模型,可以嘗試輸入一些句子看看模型的預測結果。 + +## 準備數據 + +首先,我們需要一個適合標記分類的數據集。在本節中,我們將使用[CoNLL-2003 數據集](https://huggingface.co/datasets/conll2003), 其中包含來自路透社的新聞報道。 + + + +💡 只要您的數據集由帶有相應標籤的分割成單詞並的文本組成,您就能夠將這裡描述的數據處理過程應用到您自己的數據集。如果需要複習如何在.Dataset中加載自定義數據,請參閱[Chapter 5](/course/chapter5)。 + + + +### CoNLL-2003 數據集 + +要加載 CoNLL-2003 數據集,我們使用 來自 🤗 Datasets 庫的**load_dataset()** 方法: + +```py +from datasets import load_dataset + +raw_datasets = load_dataset("conll2003") +``` + +這將下載並緩存數據集,就像和我們在[第三章](/course/chapter3) 加載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] +``` + +這一列是類標籤的序列。元素的類型在ner_feature的feature屬性中,我們可以通過查看該特性的names屬性來訪問名稱列表: + +```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) +``` + +因此,這一列包含的元素是ClassLabels的序列。序列元素的類型在`ner_feature`的`feature`中,我們可以通過查看該`feature`的`names`屬性來訪問名稱列表: + +```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'] +``` + +我們在[第六章](/course/chapter6/3), 深入研究**token-classification** 管道時已經看到了這些標籤 ,但為了快速複習: + +- `O` 表示這個詞不對應任何實體。 +- `B-PER`/`I-PER`意味著這個詞對應於人名實體的開頭/內部。 +- `B-ORG`/`I-ORG` 的意思是這個詞對應於組織名稱實體的開頭/內部。 +- `B-LOC`/`I-LOC` 指的是是這個詞對應於地名實體的開頭/內部。 +- `B-MISC`/`I-MISC` 表示該詞對應於一個雜項實體的開頭/內部。 + +現在解碼我們之前看到的標籤: + +```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標籤識別同一個句子。 + + + +### 處理數據 + + + +像往常一樣,我們的文本需要轉換為Token ID,然後模型才能理解它們。正如我們在[第六章](/course/chapter6/)所學的那樣。不過在標記任務中,一個很大的區別是我們有pre-tokenized的輸入。幸運的是,tokenizer 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), 上看到所有帶有快速版本的架構,或者檢查 您可以通過查看它`is_fast` 屬性來檢測正在使用的`tokenizer`對象是否由 🤗 Tokenizers 支持: + +```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]'] +``` + +正如我們所見,分詞器添加了模型使用的特殊Token(`[CLS]` 在開始和`[SEP]` 最後) 而大多數單詞未被修改。然而,單詞 `lamb`,被分為兩個子單詞 `la` and `##mb`。這導致了輸入和標籤之間的不匹配:標籤列表只有9個元素,而我們的輸入現在有12個token 。計算特殊Token很容易(我們知道它們在開頭和結尾),但我們還需要確保所有標籤與適當的單詞對齊。 +幸運的是,由於我們使用的是快速分詞器,因此我們可以訪問🤗 Tokenizers超能力,這意味著我們可以輕鬆地將每個令牌映射到其相應的單詞(如[Chapter 6](/course/chapter6/3)): + +```py +inputs.word_ids() +``` + +```python out +[None, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, None] +``` + +通過一點點工作,我們可以擴展我們的標籤列表以匹配token 。我們將應用的第一條規則是,特殊token 的標籤為 `-100` 。這是因為默認情況下 `-100` 是一個在我們將使用的損失函數(交叉熵)中被忽略的索引。然後,每個token 都會獲得與其所在單詞的token 相同的標籤,因為它們是同一實體的一部分。對於單詞內部但不在開頭的Token,我們將`B-` 替換為 `I-` (因為token 不以實體開頭): + +```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: + # Start of a new 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: + # Special token + new_labels.append(-100) + else: + # Same word as previous token + label = labels[word_id] + # If the label is B-XXX we change it to 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` 給定詞中的其他子標記。這是為了避免分解成大量子標記的長詞對損失造成嚴重影響。按照此規則更改前一個函數使標籤與輸入id對齊。 + + + +為了預處理我們的整個數據集,我們需要標記所有輸入並在所有標籤上應用 `align_labels_with_tokens()` 。為了利用我們的快速分詞器的速度優勢,最好同時對大量文本進行分詞,因此我們將編寫一個處理示例列表的函數並使用帶 `batched=True` 有選項的 `Dataset.map()`方法 .與我們之前的示例唯一不同的是當分詞器的輸入是文本列表(或者像例子中的單詞列表)時 `word_ids()` 函數需要獲取我們想要單詞的索引的ID,所以我們也添加它: + +```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 +``` + +請注意,我們還沒有填充我們的輸入;我們稍後會在使用數據整理器創建batch時這樣做。 + +我們現在可以一次性將所有預處理應用於數據集的其他部分: + +```py +tokenized_datasets = raw_datasets.map( + tokenize_and_align_labels, + batched=True, + remove_columns=raw_datasets["train"].column_names, +) +``` + +我們已經完成了最難的部分!現在數據已經被預處理了,實際的訓練看起來很像我們[第三章](/course/chapter3)做的. + +{#if fw === 'pt'} + +## 使用 Trainer API 微調模型 + +使用 `Trainer` 的實際代碼會和以前一樣;唯一的變化是數據整理成時批處理的方式和度量計算函數。 + +{:else} + +## 使用 Keras 微調模型 + +使用Keras的實際代碼將與之前非常相似;唯一的變化是將數據整理成批處理的方式和指標計算函數。 + +{/if} + + +### 數據排序 + +我們不能像[第三章](/course/chapter3)那樣只使用一個 `DataCollatorWithPadding `因為這隻會填充輸入(輸入 ID、注意掩碼和標記類型 ID)。在這裡我們的標籤應該以與輸入完全相同的方式填充,以便它們保持長度相同,使用 `-100 ` ,這樣在損失計算中就可以忽略相應的預測。 + +這一切都是由一個 [`DataCollatorForTokenClassification`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorfortokenclassification)完成.它是一個帶有填充的數據整理器它需要 `tokenizer ` 用於預處理輸入: + +{#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} + +我們的數據整理器已準備就緒!現在,讓我們用它來製作一個帶有`to_tf_dataset()`方法的`tf.data.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, +) +``` + + + Next stop: the model itself. + +{/if} + +{#if fw === 'tf'} + +### 定義模型 + +由於我們正在研究Token分類問題,因此我們將使用 `AutoModelForTokenClassification` 類。定義這個模型時要記住的主要事情是傳遞一些關於我們的標籤數量的信息。執行此操作的最簡單方法是將該數字傳遞給 `num_labels` 參數,但是如果我們想要一個很好的推理小部件,就像我們在本節開頭看到的那樣,最好設置正確的標籤對應關係。 + +它們應該由兩個字典設置, `id2label` 和 `label2id` ,其中包含從 ID 到標籤的映射,反之亦然: + +```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 TFAutoModelForTokenClassification + +model = TFAutoModelForTokenClassification.from_pretrained( + model_checkpoint, + id2label=id2label, + label2id=label2id, +) +``` + +就像我們在[第三章](/course/chapter3),定義我們的 `AutoModelForSequenceClassification` ,創建模型會發出警告,提示一些權重未被使用(來自預訓練頭的權重)和一些其他權重被隨機初始化(來自新Token分類頭的權重),我們將要訓練這個模型。我們將在一分鐘內完成,但首先讓我們仔細檢查我們的模型是否具有正確數量的標籤: + +```python +model.config.num_labels +``` + +```python out +9 +``` + + + +⚠️ 如果您的模型標籤數量錯誤,則在稍後調用 `model.fit()` 時將收到一個模糊的錯誤。調試起來可能很煩人,因此請確保執行此檢查以確認您具有預期的標籤數。 + + + +### 微調模型 + +現在,我們已準備好訓練模型了!不過,我們首先要做兩件事:應該登錄到Hugging Face並定義我們的訓練超參數。如果你在notebook上工作,有一個便利功能可以幫助你做到這一點: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +這將顯示一個小部件,您可以在其中輸入您的 Hugging Face 賬號和密碼。 + +如果您不是在notebook上工作,只需在終端中輸入以下行: + +```bash +huggingface-cli login +``` + +登錄後,我們可以準備編譯模型所需的一切。🤗 Transformers提供了一個方便的`create_optimizer()` 函數,該函數將為您提供一個`AdamW`優化器,其中包含適當的權重衰減和學習速率衰減設置,與內置的`Adam`優化器相似,這兩者都將提高模型的性能: + +```python +from transformers import create_optimizer +import tensorflow as tf + +# Train in mixed-precision float16 +# Comment this line out if you're using a GPU that will not benefit from this +tf.keras.mixed_precision.set_global_policy("mixed_float16") + +# 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_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) +``` + +還要注意,我們不為`compile()`提供`loss`參數。這是因為模型實際上可以在內部計算損失 - 如果您編譯時沒有損失並在輸入字典中提供標籤(就像我們在數據集中所做的那樣),那麼模型將使用該內部損失進行訓練,這將適用於您選擇的任務和模型類型。 + +接下來,我們定義一個`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, +) +``` + +您之前已經看過其中的大部分內容:我們設置了一些超參數(例如學習率、要訓練的 epoch 數和權重衰減),然後我們指定 `push_to_hub=True` 表明我們想要保存模型並在每個時期結束時對其進行評估,並且我們想要將我們的結果上傳到模型中心。請注意,可以使用hub_model_id參數指定要推送到的存儲庫的名稱(特別是,必須使用這個參數來推送到一個組織)。例如,當我們將模型推送到[`huggingface-course` organization](https://huggingface.co/huggingface-course), 我們添加了 `hub_model_id=huggingface-course/bert-finetuned-ner` 到 `TrainingArguments` .默認情況下,使用的存儲庫將在您的命名空間中並以您設置的輸出目錄命名,因此在我們的例子中它將是 `sgugger/bert-finetuned-ner` . + + + +💡 如果您正在使用的輸出目錄已經存在,那麼輸出目錄必須是從同一個存儲庫clone下來的。如果不是,您將在聲明 `model.fit()` 時遇到錯誤,並且需要設置一個新名稱。 + + + +請注意,當訓練發生時,每次保存模型時(這裡是每個epooch),它都會在後臺上傳到 Hub。這樣,如有必要,您將能夠在另一臺機器上繼續您的訓練。 + +在此階段,您可以使用模型中心上的推理小組件來測試模型並與朋友共享。您已經成功微調了令牌分類任務的模型 - 恭喜!但是,我們的模型到底有多好呢?我們應該評估一些指標來找出答案。 + +{/if} + + +### 評估指標 + +{#if fw === 'pt'} + +為了讓 `Trainer` 在每個epoch計算一個度量,我們需要定義一個 `compute_metrics()` 函數,該函數接受預測和標籤數組,並返回一個包含度量名稱和值的字典 + +用於評估Token分類預測的傳統框架是 [*seqeval*](https://github.com/chakki-works/seqeval). 要使用此指標,我們首先需要安裝seqeval庫: + +```py +!pip install seqeval +``` + +然後我們可以通過加載它 `load_metric()` 函數就像我們在[第三章](/course/chapter3)做的那樣: + +{:else} + +用於評估Token分類預測的傳統框架是 [*seqeval*](https://github.com/chakki-works/seqeval). 要使用此指標,我們首先需要安裝seqeval庫: + +```py +!pip install seqeval +``` + +然後我們可以通過加載它 `load_metric()` 函數就像我們在[第三章](/course/chapter3)做的那樣: + +{/if} + +```py +from datasets import load_metric + +metric = load_metric("seqeval") +``` + +這個評估方式與標準精度不同:它實際上將標籤列表作為字符串,而不是整數,因此在將預測和標籤傳遞給它之前,我們需要完全解碼它們。讓我們看看它是如何工作的。首先,我們將獲得第一個訓練示例的標籤: + +```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'} + +它返回很多信息!我們獲得每個單獨實體以及整體的準確率、召回率和 F1 分數。對於我們的度量計算,我們將只保留總分,但可以隨意調整 `compute_metrics()` 函數返回您想要查看的所有指標。 + +這`compute_metrics()` 函數首先採用 logits 的 argmax 將它們轉換為預測(像往常一樣,logits 和概率的順序相同,因此我們不需要應用 softmax)。然後我們必須將標籤和預測從整數轉換為字符串。我們刪除標籤為 `-100` 所有值 ,然後將結果傳遞給 `metric.compute()` 方法: + +```py +import numpy as np + + +def compute_metrics(eval_preds): + logits, labels = eval_preds + predictions = np.argmax(logits, axis=-1) + + # Remove ignored index (special tokens) and convert to labels + 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} + +它返回很多信息!我們獲得每個單獨實體以及整體的準確率、召回率和 F1 分數。對於我們的度量計算,我們將只保留總分,但可以隨意調整 `compute_metrics()` 函數返回您想要查看的所有指標。 + +這`compute_metrics()` 函數首先採用 logits 的 argmax 將它們轉換為預測(像往常一樣,logits 和概率的順序相同,因此我們不需要應用 softmax)。然後我們必須將標籤和預測從整數轉換為字符串。我們刪除標籤為 `-100` 所有值 ,然後將結果傳遞給 `metric.compute()` 方法: + +```py +import numpy as np + +all_predictions = [] +all_labels = [] +for batch in tf_eval_dataset: + logits = model.predict(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'} + +### 定義模型 + +由於我們正在研究Token分類問題,因此我們將使用 `AutoModelForTokenClassification` 類。定義這個模型時要記住的主要事情是傳遞一些關於我們的標籤數量的信息。執行此操作的最簡單方法是將該數字傳遞給 `num_labels` 參數,但是如果我們想要一個很好的推理小部件,就像我們在本節開頭看到的那樣,最好設置正確的標籤對應關係。 + +它們應該由兩個字典設置, `id2label` 和 `label2id` ,其中包含從 ID 到標籤的映射,反之亦然: + +```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, +) +``` + +就像我們在[第三章](/course/chapter3),定義我們的 `AutoModelForSequenceClassification` ,創建模型會發出警告,提示一些權重未被使用(來自預訓練頭的權重)和一些其他權重被隨機初始化(來自新Token分類頭的權重),我們將要訓練這個模型。我們將在一分鐘內完成,但首先讓我們仔細檢查我們的模型是否具有正確數量的標籤: + +```python +model.config.num_labels +``` + +```python out +9 +``` + + + +⚠️ 如果模型的標籤數量錯誤,稍後調用Trainer.train()方法時會出現一個模糊的錯誤(類似於“CUDA error: device-side assert triggered”)。這是用戶報告此類錯誤的第一個原因,因此請確保進行這樣的檢查以確認您擁有預期數量的標籤。 + + + +### 微調模型 + +我們現在準備好訓練我們的模型了!在定義我們的 `Trainer`之前,我們只需要做最後兩件事:登錄 Hugging Face 並定義我們的訓練參數。如果您在notebook上工作,有一個方便的功能可以幫助您: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` +這將顯示一個小部件,您可以在其中輸入您的 Hugging Face 賬號和密碼。如果您不是在notebook上工作,只需在終端中輸入以下行: + +```bash +huggingface-cli login +``` + +Once this is done, we can define our `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, +) +``` + +您之前已經看過其中的大部分內容:我們設置了一些超參數(例如學習率、要訓練的 epoch 數和權重衰減),然後我們指定 `push_to_hub=True` 表明我們想要保存模型並在每個時期結束時對其進行評估,並且我們想要將我們的結果上傳到模型中心。請注意,可以使用hub_model_id參數指定要推送到的存儲庫的名稱(特別是,必須使用這個參數來推送到一個組織)。例如,當我們將模型推送到[`huggingface-course` organization](https://huggingface.co/huggingface-course), 我們添加了 `hub_model_id=huggingface-course/bert-finetuned-ner` 到 `TrainingArguments` 。默認情況下,使用的存儲庫將在您的命名空間中並以您設置的輸出目錄命名,因此在我們的例子中它將是 `sgugger/bert-finetuned-ner`。 + + + +💡 如果您正在使用的輸出目錄已經存在,那麼輸出目錄必須是從同一個存儲庫clone下來的。如果不是,您將在聲明 `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() +``` + +請注意,當訓練發生時,每次保存模型時(這裡是每個epooch),它都會在後臺上傳到 Hub。這樣,如有必要,您將能夠在另一臺機器上繼續您的訓練。 + +訓練完成後,我們使用 `push_to_hub()` 確保我們上傳模型的最新版本 + +```py +trainer.push_to_hub(commit_message="Training complete") +``` + +This command returns the URL of the commit it just did, if you want to inspect it: + +```python out +'https://huggingface.co/sgugger/bert-finetuned-ner/commit/26ab21e5b1568f9afeccdaed2d8715f571d786ed' +``` + +這 `Trainer` 還創建了一張包含所有評估結果的模型卡並上傳。在此階段,您可以使用模型中心上的推理小部件來測試您的模型並與您的朋友分享。您已成功在Token分類任務上微調模型 - 恭喜! + +如果您想更深入地瞭解訓練循環,我們現在將向您展示如何使用 🤗 Accelerate 做同樣的事情。 + +## 自定義訓練循環 + +現在讓我們看一下完整的訓練循環,這樣您可以輕鬆定義所需的部分。它看起來很像我們在[第三章](/course/chapter3/4), 所做的,對評估進行了一些更改。 + +### 做好訓練前的準備 +首先我們需要為我們的數據集構建 `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) +``` + +Once we have all those objects, we can send them to the `accelerator.prepare()` method: + +```py +from accelerate import Accelerator + +accelerator = Accelerator() +model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( + model, optimizer, train_dataloader, eval_dataloader +) +``` + + + +🚨 如果您在 TPU 上進行訓練,則需要將以上單元格中的所有代碼移動到專用的訓練函數中。有關詳細信息,請參閱 [第3章](/course/chapter3)。 + + + +現在我們已經發送了我們的 `train_dataloader` 到 `accelerator.prepare()` ,我們可以使用它的長度來計算訓練步驟的數量。請記住,我們應該始終在準備好dataloader後執行此操作,因為該方法會改變其長度。我們使用經典線性學習率調度: + +```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。我們將從我們想要為模型提供的模型 ID 中確定存儲庫名稱(您可以自由地用自己的選擇替換 `repo_name` ;它只需要包含您的用戶名,可以使用`get_full_repo_name()`函數的查看目前的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' +``` + +Then we can clone that repository in a local folder. If it already exists, this local folder should be an existing clone of the repository we are working with: + +```py +output_dir = "bert-finetuned-ner-accelerate" +repo = Repository(output_dir, clone_from=repo_name) +``` + +We can now upload anything we save in `output_dir` by calling the `repo.push_to_hub()` method. This will help us upload the intermediate models at the end of each epoch. + +### Training loop + +### 訓練循環 +我們現在準備編寫完整的訓練循環。為了簡化它的評估部分,我們定義了這個 `postprocess()` 接受預測和標籤並將它們轉換為字符串列表的函數,也就是 `metric`對象需要的輸入格式: + +```py +def postprocess(predictions, labels): + predictions = predictions.detach().cpu().clone().numpy() + labels = labels.detach().cpu().clone().numpy() + + # Remove ignored index (special tokens) and convert to labels + 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`的經典迭代,向前傳遞模型,然後反向傳遞和優化參數 +- 評估,在獲得我們模型的輸出後:因為兩個進程可能將輸入和標籤填充成不同的形狀,在調用`gather()`方法前我們需要使用`accelerator.pad_across_processes()`來讓預測和標籤形狀相同。如果我們不這樣做,評估要麼出錯,要麼永遠不會得到結果。然後,我們將結果發送給`metric.add_batch()`,並在計算循環結束後調用`metric.compute()`。 +- 保存和上傳,首先保存模型和標記器,然後調用`repo.push_to_hub()`。注意,我們使用參數`blocking=False`告訴🤗 hub 庫用在異步進程中推送。這樣,訓練將正常繼續,並且該(長)指令將在後臺執行。 + +這是訓練循環的完整代碼: + +```py +from tqdm.auto import tqdm +import torch + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Training + 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) + + # Evaluation + model.eval() + for batch in eval_dataloader: + with torch.no_grad(): + outputs = model(**batch) + + predictions = outputs.logits.argmax(dim=-1) + labels = batch["labels"] + + # Necessary to pad predictions and labels for being gathered + 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"] + }, + ) + + # Save and upload + accelerator.wait_for_everyone() + unwrapped_model = accelerator.unwrap_model(model) + unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) + if accelerator.is_main_process: + tokenizer.save_pretrained(output_dir) + repo.push_to_hub( + commit_message=f"Training in progress epoch {epoch}", blocking=False + ) +``` + +果這是您第一次看到用 🤗 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_pretraining()`方法;`accelerator.unwrap_model()`方法將撤銷該步驟。最後,我們調用`save_pretraining()`,但告訴該方法使用`accelerator.save()`而不是`torch.save()`。 + +當完成之後,你應該有一個模型,它產生的結果與`Trainer`的結果非常相似。你可以在[hugs face-course/bert-fine - tuning -ner-accelerate](https://huggingface.co/huggingface-course/bert-finetuned-ner-accelerate)中查看我們使用這個代碼訓練的模型。如果你想測試訓練循環的任何調整,你可以直接通過編輯上面顯示的代碼來實現它們! + +{/if} + +## 使用微調模型 + +我們已經向您展示瞭如何使用我們在模型中心微調的模型和推理小部件。在本地使用它 `pipeline` ,您只需要指定正確的模型標識符: + +```py +from transformers import pipeline + +# Replace this with your own checkpoint +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/zh-TW/chapter7/3.mdx b/chapters/zh-TW/chapter7/3.mdx new file mode 100644 index 000000000..7f6e95008 --- /dev/null +++ b/chapters/zh-TW/chapter7/3.mdx @@ -0,0 +1,1045 @@ + + +# 微調掩碼語言模型 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +對於許多涉及 Transformer 模型的 NLP 程序, 你可以簡單地從 Hugging Face Hub 中獲取一個預訓練的模型, 然後直接在你的數據上對其進行微調, 以完成手頭的任務。只要用於預訓練的語料庫與用於微調的語料庫沒有太大區別, 遷移學習通常會產生很好的結果。 + +但是, 在某些情況下, 你需要先微調數據上的語言模型, 然後再訓練特定於任務的head。例如, 如果您的數據集包含法律合同或科學文章, 像 BERT 這樣的普通 Transformer 模型通常會將您語料庫中的特定領域詞視為稀有標記, 結果性能可能不盡如人意。通過在域內數據上微調語言模型, 你可以提高許多下游任務的性能, 這意味著您通常只需執行一次此步驟! + +這種在域內數據上微調預訓練語言模型的過程通常稱為 _領域適應_。 它於 2018 年由 [ULMFiT](https://arxiv.org/abs/1801.06146)推廣, 這是使遷移學習真正適用於 NLP 的首批神經架構之一 (基於 LSTM)。 下圖顯示了使用 ULMFiT 進行域自適應的示例; 在本節中, 我們將做類似的事情, 但使用的是 Transformer 而不是 LSTM! + +
+ULMFiT. + +
+ +在本節結束時, 你將在Hub上擁有一個[掩碼語言模型(masked language model)](https://huggingface.co/huggingface-course/distilbert-base-uncased-finetuned-imdb?text=This+is+a+great+%5BMASK%5D.), 該模型可以自動完成句子, 如下所示: + + + + +讓我們開始吧! + + + + + +🙋 如果您對“掩碼語言建模”和“預訓練模型”這兩個術語感到陌生, 請查看[第一章](/course/chapter1), 我們在其中解釋了所有這些核心概念, 並附有視頻! + + + +## 選擇用於掩碼語言建模的預訓練模型 + +首先, 讓我們為掩碼語言建模選擇一個合適的預訓練模型。如以下屏幕截圖所示, 你可以通過在[Hugging Face Hub](https://huggingface.co/models?pipeline_tag=fill-mask&sort=downloads)上應用"Fill-Mask"過濾器找到: + +
+Hub models. +
+ +儘管 BERT 和 RoBERTa 系列模型的下載量最大, 但我們將使用名為 [DistilBERT](https://huggingface.co/distilbert-base-uncased)的模型。 +可以更快地訓練, 而下游性能幾乎沒有損失。這個模型使用一種稱為[_知識蒸餾_](https://en.wikipedia.org/wiki/Knowledge_distillation)的特殊技術進行訓練, 其中使用像 BERT 這樣的大型“教師模型”來指導參數少得多的“學生模型”的訓練。在本節中對知識蒸餾細節的解釋會使我們離題太遠, 但如果你有興趣, 可以閱讀所有相關內容 [_Natural Language Processing with Transformers_](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) (俗稱Transformers教科書)。 + +{#if fw === 'pt'} + +讓我們繼續, 使用 `AutoModelForMaskedLM` 類下載 DistilBERT: + +```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} + +讓我們繼續, 使用 `AutoModelForMaskedLM` 類下載 DistilBERT: + +```python +from transformers import TFAutoModelForMaskedLM + +model_checkpoint = "distilbert-base-uncased" +model = TFAutoModelForMaskedLM.from_pretrained(model_checkpoint) +``` + +我們可以通過調用 `summary()` 方法查看模型有多少參數: + +```python +model(model.dummy_inputs) # Build the model +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} + +DistilBERT 大約有 6700 萬個參數, 大約比 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 的標記器來生成模型的輸入, 所以讓我們也從 Hub 下載它: + +```python +from transformers import AutoTokenizer + +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +使用標記器和模型, 我們現在可以將我們的文本示例傳遞給模型, 提取 logits, 並打印出前 5 個候選: + +{#if fw === 'pt'} + +```python +import torch + +inputs = tokenizer(text, return_tensors="pt") +token_logits = model(**inputs).logits +# Find the location of [MASK] and extract its logits +mask_token_index = torch.where(inputs["input_ids"] == tokenizer.mask_token_id)[1] +mask_token_logits = token_logits[0, mask_token_index, :] +# Pick the [MASK] candidates with the highest logits +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 +# Find the location of [MASK] and extract its logits +mask_token_index = np.argwhere(inputs["input_ids"] == tokenizer.mask_token_id)[0, 1] +mask_token_logits = token_logits[0, mask_token_index, :] +# Pick the [MASK] candidates with the highest logits +# We negate the array before argsort to get the largest, not the smallest, logits +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.' +``` + +我們可以從輸出中看到模型的預測是指日常用語, 鑑於英語維基百科的基礎, 這也許並不奇怪。讓我們看看我們如何將這個領域改變為更小眾的東西 -- 高度兩極分化的電影評論! + + +## 數據集 + +為了展示域適配, 我們將使用著名的[大型電影評論數據集(Large Movie Review Dataset)](https://huggingface.co/datasets/imdb) (或者簡稱為IMDb), 這是一個電影評論語料庫, 通常用於對情感分析模型進行基準測試。通過在這個語料庫上對 DistilBERT 進行微調, 我們預計語言模型將根據維基百科的事實數據調整其詞彙表, 這些數據已經預先訓練到電影評論中更主觀的元素。我們可以使用🤗 Datasets中的`load_dataset()`函數從Hugging Face 中獲取數據: + +```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' +``` + +是的, 這些肯定是電影評論, 如果你年齡足夠,你甚至可能會理解上次評論中關於擁有 VHS 版本的評論😜! 雖然我們不需要語言建模的標籤, 但我們已經可以看到 `0` 表示負面評論, 而 `1` 對應正面。 + + + +✏️ **試試看!** 創建 `無監督` 拆分的隨機樣本, 並驗證標籤既不是 `0` 也不是 `1`。在此過程中, 你還可以檢查 `train` 和 `test` 拆分中的標籤是否確實為 `0` 或 `1` -- 這是每個 NLP 從業者在新項目開始時都應該執行的有用的健全性檢查! + + + +現在我們已經快速瀏覽了數據, 讓我們深入研究為掩碼語言建模做準備。正如我們將看到的, 與我們在[第三章](/course/chapter3)中看到的序列分類任務相比, 還需要採取一些額外的步驟。讓我們繼續! + +## 預處理數據 + + + +對於自迴歸和掩碼語言建模, 一個常見的預處理步驟是連接所有示例, 然後將整個語料庫拆分為相同大小的塊。 這與我們通常的方法完全不同, 我們只是簡單地標記單個示例。為什麼要將所有內容連接在一起? 原因是單個示例如果太長可能會被截斷, 這將導致丟失可能對語言建模任務有用的信息! + +因此, 我們將像往常一樣首先標記我們的語料庫, 但是 _沒有_ 在我們的標記器中設置 `truncation=True` 選項。 我們還將獲取可用的單詞 ID ((如果我們使用快速標記器, 它們是可用的, 如 [第六章](/course/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 + + +# Use batched=True to activate fast multithreading! +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 一樣。 + + + +✏️ **試試看!** 一些 Transformer 模型, 例如 [BigBird](https://huggingface.co/google/bigbird-roberta-base) 和 [Longformer](hf.co/allenai/longformer-base-4096), 它們具有比BERT和其他早期Transformer模型更長的上下文長度。為這些檢查點之一實例化標記器, 並驗證 `model_max_length` 是否與模型卡上引用的內容一致。 + + + +因此, 以便在像Google Colab 那樣的 GPU 上運行我們的實驗, 我們將選擇可以放入內存的更小一些的東西: + +```python +chunk_size = 128 +``` + + + +請注意, 在實際場景中使用較小的塊大小可能是有害的, 因此你應該使用與將應用模型的用例相對應的大小。 + + + +有趣的來了。為了展示串聯是如何工作的, 讓我們從我們的標記化訓練集中取一些評論並打印出每個評論的標記數量: + +```python +# Slicing produces a list of lists for each feature +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' +``` + +然後我們可以用一個簡單的字典理解來連接所有例子, 如下所示: + +```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' +``` + +很棒, 總長度檢查出來了 -- 現在, 讓我們將連接的評論拆分為大小為 `block_size` 的塊。為此, 我們迭代了 `concatenated_examples` 中的特徵, 並使用列表理解來創建每個特徵的切片。結果是每個特徵的塊字典: + +```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): + # Concatenate all texts + concatenated_examples = {k: sum(examples[k], []) for k in examples.keys()} + # Compute length of concatenated texts + total_length = len(concatenated_examples[list(examples.keys())[0]]) + # We drop the last chunk if it's smaller than chunk_size + total_length = (total_length // chunk_size) * chunk_size + # Split by chunks of 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() + } + # Create a new labels column + result["labels"] = result["input_ids"].copy() + return result +``` + +注意, 在 `group_texts()` 的最後一步中, 我們創建了一個新的 `labels` 列, 它是 `input_ids` 列的副本。我們很快就會看到, 這是因為在掩碼語言建模中, 目標是預測輸入批次中隨機掩碼的標記, 並通過創建一個 `labels` 列, 們為我們的語言模型提供了基礎事實以供學習。 + +現在, 讓我們使用我們可信賴的 `Dataset.map()` 函數將 `group_texts()` 應用到我們的標記化數據集: + +```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` 拆分的示例多得多。那是因為我們現在有了涉及 _連續標記_ 的示例, 這些示例跨越了原始語料庫中的多個示例。你可以通過在其中一個塊中查找特殊的 `[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]` 標記! 讓我們看看如何使用特殊的數據整理器在微調期間即時執行此操作。 + +## 使用 `Trainer` API 微調DistilBERT + +微調屏蔽語言模型幾乎與微調序列分類模型相同, 就像我們在 [第三章](/course/chapter3)所作的那樣。 唯一的區別是我們需要一個特殊的數據整理器, 它可以隨機屏蔽每批文本中的一些標記。幸運的是, 🤗 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} + +在為掩碼語言建模訓練模型時, 可以使用的一種技術是將整個單詞一起屏蔽, 而不僅僅是單個標記。這種方法稱為 _全詞屏蔽_。 如果我們想使用全詞屏蔽, 我們需要自己構建一個數據整理器。數據整理器只是一個函數, 它接受一個樣本列表並將它們轉換為一個批次, 所以現在讓我們這樣做吧! 我們將使用之前計算的單詞 ID 在單詞索引和相應標記之間進行映射, 然後隨機決定要屏蔽哪些單詞並將該屏蔽應用於輸入。請注意, 除了與掩碼對應的標籤外, 所有的標籤均為 `-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") + + # Create a map between words and corresponding token indices + 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) + + # Randomly mask words + 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 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") + + # Create a map between words and corresponding token indices + 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) + + # Randomly mask words + 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} + +Next, we can try it on the same samples as before: + +```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()` 以查看來自給定單詞的標記始終被屏蔽在一起。 + + + +現在我們有兩個數據整理器, 其餘的微調步驟是標準的。如果您沒有足夠幸運地獲得神話般的 P100 GPU 😭, 在 Google Colab 上進行訓練可能需要一段時間, 因此我們將首先將訓練集的大小縮減為幾千個示例。別擔心, 我們仍然會得到一個相當不錯的語言模型! 在 🤗 Datasets 中快速下采樣數據集的方法是通過我們在 [第五章](/course/chapter5) 中看到的 `Dataset.train_test_split()` 函數: + +```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` 數據集。我們將在這裡只使用標準數據整理器, 但你也可以嘗試使用整個單詞掩碼整理器並將結果作為練習進行比較: + +```python +tf_train_dataset = downsampled_dataset["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) + +tf_eval_dataset = downsampled_dataset["test"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=32, +) +``` + +接下來, 我們設置訓練超參數並編譯模型。我們使用 🤗 Transformers 庫中的 `create_optimizer()` 函數, 它提供了一個具有線性學習率衰減的 `AdamW` 優化器。我們還使用了模型內置的損失, 當沒有損失被指定為 `compile()` 參數是, 這是默認值, 並且我們將訓練精度設置為 `"mixed_float16"`。請注意,如果你使用的是Colab GPU或其他不支持float16加速的GPU, 你可能應該註釋掉這一行。 + +此外, 我們還設置了一個 `PushToHubCallback`, 它將在每個epoch後將模型保存到Hub。你可以使用 `hub_model_id` 參數指定你想要推送到的存儲庫的名稱 (特別是你將必須使用該參數來推送到組織)。例如, 為了將模型推送到 [`huggingface-course` organization](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) + +# Train in mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") + +callback = PushToHubCallback( + output_dir=f"{model_name}-finetuned-imdb", tokenizer=tokenizer +) +``` + +我們現在已經準備好運行 `model.fit()` 了 -- 但在此之前, 讓我們先簡單地看看 _perplexity_, 它是評估語言模型性能的常用指標。 + +{:else} + +登陸後, 我們可以指定 `Trainer` 參數: + +```python +from transformers import TrainingArguments + +batch_size = 64 +# Show the training loss with every epoch +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` , 以確保我們跟蹤每個epoch的訓練損失。我們還使用了 `fp16=True` 來實現混合精度訓練, 這給我們帶來了另一個速度提升。默認情況下, `Trainer` 將刪除不屬於模型的 `forward()` 方法的列。這意味著, 如果你使用整個單詞屏蔽排序器, 你還需要設置 `remove_unused_columns=False`, 以確保我們不會在訓練期間丟失 `word_ids` 列。 + +請注意, 你可以使用 `hub_model_id` 參數指定要推送到的存儲庫的名稱((特別是你將必須使用該參數向組織推送)。例如, 當我們將模型推送到 [`huggingface-course` organization](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 + + + +與文本分類或問答等其他任務不同, 在這些任務中, 我們會得到一個帶標籤的語料庫進行訓練, 而語言建模則沒有任何明確的標籤。那麼我們如何確定什麼是好的語言模型呢? 就像手機中的自動更正功能一樣, 一個好的語言模型是為語法正確的句子分配高概率, 為無意義的句子分配低概率。為了讓你更好地瞭解這是什麼樣子, 您可以在網上找到一整套 "autocorrect fails", 其中一個人手機中的模型產生了一些相當有趣 (而且通常不合適) 的結果! + +{#if fw === 'pt'} + +假設我們的測試集主要由語法正確的句子組成, 那麼衡量我們的語言模型質量的一種方法是計算它分配給測試集中所有句子中的下一個單詞的概率。高概率表明模型對看不見的例子並不感到 "驚訝" 或 "疑惑", 並表明它已經學習了語言中的基本語法模式。 perplexity度有多種數學定義, 但我們將使用的定義是交叉熵損失的指數。因此, 我們可以通過 `Trainer.evaluate()` 函數計算測試集上的交叉熵損失, 然後取結果的指數來計算預訓練模型的perplexity度: + +```python +import math + +eval_results = trainer.evaluate() +print(f">>> Perplexity: {math.exp(eval_results['eval_loss']):.2f}") +``` + +{:else} + +假設我們的測試集主要由語法正確的句子組成, 那麼衡量我們的語言模型質量的一種方法是計算測試集所有句子中它分配給下一個單詞的概率。高概率表明, 該模型表明該模型對未見過的示例不感到 "驚訝" 或 "困惑", 並表明它已經學會了該語言的基本語法模式。關於perplexity度有各種不同的數學定義, 但我們要用的定義是交叉熵損失的指數。因此, 我們可以通過 `model.evaluate()` 方法計算測試集上的交叉熵損失, 然後取結果的指數來計算我們預訓練模型的perplexity度: + +```python +import math + +eval_loss = model.evaluate(tf_eval_dataset) +print(f"Perplexity: {math.exp(eval_loss):.2f}") +``` + +{/if} + +```python out +>>> Perplexity: 21.75 +``` + +較低的perplexity分數意味著更好的語言模型, 我們可以在這裡看到我們的起始模型有一個較大的值。看看我們能不能通過微調來降低它! 為此, 我們首先運行訓練循環: + +{#if fw === 'pt'} + +```python +trainer.train() +``` + +{:else} + +```python +model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback]) +``` + +{/if} + +然後像以前一樣計算測試集上的perplexity度: + +{#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 -- 讓我們來看看吧! + +## 使用 🤗 Accelerate 微調 DistilBERT + +正如我們在 `Trainer` 中看到的, 對掩碼語言模型的微調與 [第三章](/course/chapter3) 中的文本分類示例非常相似。事實上, 唯一的微妙之處是使用特殊的數據整理器, 我們已經在本節的前面介紹過了! + +然而, 我們看到 `DataCollatorForLanguageModeling` 還對每次評估應用隨機掩碼, 因此每次訓練運行時, 我們都會看到perplexity度分數的一些波動。消除這種隨機性來源的一種方法是應用掩碼 _一次_ 在整個測試集上, 然後使用🤗 Transformers 中的默認數據整理器在評估期間收集批次。為了看看它是如何工作的, 讓我們實現一個簡單的函數, 將掩碼應用於批處理, 類似於我們第一次遇到的 `DataCollatorForLanguageModeling`: + +```python +def insert_random_mask(batch): + features = [dict(zip(batch, t)) for t in zip(*batch.values())] + masked_inputs = data_collator(features) + # Create a new "masked" column for each column in the dataset + 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", + } +) +``` + +然後我們可以像往常一樣設置數據加載器, 但我們將使用🤗 Transformers 中的 `default_data_collator` 作為評估集: + +```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 庫來首先生成我們的 repo 的全名: + +```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' +``` + +然後使用來自🤗 Hub 的 `Repository` 類: + +```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): + # Training + 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) + + # Evaluation + 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}") + + # Save and upload + accelerator.wait_for_everyone() + unwrapped_model = accelerator.unwrap_model(model) + unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) + if accelerator.is_main_process: + tokenizer.save_pretrained(output_dir) + repo.push_to_hub( + commit_message=f"Training in progress epoch {epoch}", blocking=False + ) +``` + +```python out +>>> Epoch 0: Perplexity: 11.397545307900472 +>>> Epoch 1: Perplexity: 10.904909330983092 +>>> Epoch 2: Perplexity: 10.729503505340409 +``` + +很棒, 我們已經能夠評估每個 epoch 的perplexity度, 並確保多次訓練運行是可重複的! + +{/if} + +## 使用我們微調的模型 + +你可以通過在Hub上使用其他小部件或在本地使用🤗 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.' +``` + +好的 -- 我們的模型顯然已經調整了它的權重來預測與電影更密切相關的詞! + + + +這結束了我們訓練語言模型的第一個實驗。在 [第六節](/course/chapter7/section6)中你將學習如何從頭開始訓練像 GPT-2 這樣的自迴歸模型; 如果你想了解如何預訓練您自己的 Transformer 模型, 請前往那裡! + + + +✏️ **試試看!** 為了量化域適應的好處, 微調 IMDb 標籤上的分類器和預先訓練和微調的Distil BERT檢查點。如果你需要複習文本分類, 請查看 [第三章](/course/chapter3)。 + + diff --git a/chapters/zh-TW/chapter7/4.mdx b/chapters/zh-TW/chapter7/4.mdx new file mode 100644 index 000000000..fe3c33cd6 --- /dev/null +++ b/chapters/zh-TW/chapter7/4.mdx @@ -0,0 +1,996 @@ + + +# 翻譯 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +現在讓我們深入研究翻譯。這是另一個[sequence-to-sequence 任務](/course/chapter1/7),這意味著這是一個可以表述為從一個序列到另一個序列的問題。從這個意義上說,這個問題非常類似[文本摘要](/course/chapter7/6),並且您可以將我們將在此處學習到的一些內容遷移到其他的序列到序列問題,例如: + +- **風格遷移** : 創建一個模型將某種風格遷移到一段文本(例如,正式的風格遷移到休閒的風格或莎士比亞英語到現代英語) +- **生成問題的回答** :創建一個模型,在給定上下文的情況下生成問題的答案 + + + +如果您有足夠大的兩種(或更多)語言的文本語料庫,您可以從頭開始訓練一個新的翻譯模型,就像我們在[因果語言建模](/course/chapter7/6)部分中所做的那樣。然而,微調現有的翻譯模型會更快,無論是從像 mT5 或 mBART 這樣的多語言模型微調到特定的語言對,或者是專門用於從一種語言翻譯成另一種語言的模型。 + +在本節中,我們將對[KDE4 數據集](https://huggingface.co/datasets/kde4)上的Marian模型進行微調,該模型經過了從英語到法語的翻譯預訓練(因為很多“ Hugging Face”的員工會說這兩種語言),它是[KDE應用程序](https://apps.kde.org/)本地化文件的數據集。我們將使用的模型已經在從[Opus 數據集](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.)查看模型輸出的結果 + +## 準備數據 + +為了從頭開始微調或訓練翻譯模型,我們需要一個適合該任務的數據集。如前所述,我們將使用[KDE4 數據集](https://huggingface.co/datasets/kde4)在本節中,但您可以很容易地調整代碼以使用您自己的數據,只要您有要互譯的兩種語言的句子對。如果您需要複習如何將自定義數據加載到 **Dataset** 可以複習一下[第五章](/course/chapter5). + +### KDE4 數據集 + +像往常一樣,我們使用 **load_dataset()** 函數: + +```py +from datasets import load_dataset, load_metric + +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 對句子,但在一次訓練過程中,我們需要創建自己的驗證集。正如我們在[第五章](/course/chapter5)學的的那樣, **Dataset** 有一個 **train_test_split()** 方法,可以幫我們拆分數據集。我們將設定固定的隨機數種子: + +```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”。在訓練數據集中找到使用這個詞的第一個樣本。它是如何翻譯的?預訓練模型如何翻譯同一個英文句子? + + + +### 處理數據 + + + +您現在應該知道我們的下一步該做些什麼了:所有文本都需要轉換為token ID,以便模型能夠理解它們。對於這個任務,我們需要同時標記輸入和目標。我們的首要任務是創建我們的 **tokenizer** 對象。如前所述,我們將使用 Marian 英語到法語的預訓練模型。如果您使用另一對語言嘗試此代碼,請確保調整模型Checkpoint。[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="tf") +``` + +您可以將 **model_checkpoint** 更換為[Hub](https://huggingface.co/models)上你喜歡的任何其他型號,或本地保存的預訓練模型和標記器。 + + + +💡 如果正在使用mart、mBART-50或M2 M100等多語言標記器,則需要在tokenizer中設置tokenizer.src_lang和tokenizer.tgt_lang為正確的輸入和目標的語言代碼。 + + + +我們的數據準備非常簡單。 只需要記住一件事:您照常處理輸入,但對於這次的輸出目標,您需要將標記器包裝在上下文管理器“as_target_tokenizer()”中。 + +Python 中的上下文管理器引入了 **with** 語句,當您有兩個相關的操作要成對執行時很有用。最常見的例子是當您寫入或讀取文件時,下面是一個例子: + +``` +with open(file_path) as f: + content = f.read() +``` + +這裡成對執行的兩個相關操作是打開和關閉文件的操作。打開的文件f對應的對象只在with下的縮進塊內有效;在該塊之前打開,在該塊的末尾關閉。 + +在本例中,上下文管理器 as_target_tokenizer() 將在執行縮進塊之前將標記器設置為輸出語言(此處為法語),然後將其設置回輸入語言(此處為英語)。 + +因此,預處理一個樣本如下所示: + +```python +en_sentence = split_datasets["train"][1]["translation"]["en"] +fr_sentence = split_datasets["train"][1]["translation"]["fr"] + +inputs = tokenizer(en_sentence) +with tokenizer.as_target_tokenizer(): + targets = tokenizer(fr_sentence) +``` + +如果我們忘記在上下文管理器中標記目標,它們將被輸入標記器標記,在Marian模型的情況下,會導致輸出的異常: + +```python +wrong_targets = tokenizer(fr_sentence) +print(tokenizer.convert_ids_to_tokens(wrong_targets["input_ids"])) +print(tokenizer.convert_ids_to_tokens(targets["input_ids"])) +``` + +```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` 和 `targets` 都是帶有我們常用鍵(輸入 ID、注意掩碼等)的字典,所以最後一步是在輸入中設置一個 `"labels"` 鍵。 我們在數據集的預處理函數中執行此操作: + +```python +max_input_length = 128 +max_target_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, max_length=max_input_length, truncation=True) + + # Set up the tokenizer for targets + with tokenizer.as_target_tokenizer(): + labels = tokenizer(targets, max_length=max_target_length, truncation=True) + + model_inputs["labels"] = labels["input_ids"] + return model_inputs +``` + +請注意,我們為輸入和輸出設置了相同的最大長度。由於我們處理的文本看起來很短,我們使用 128。 + + + +💡如果你使用的是T5模型(更具體地說,是T5 -xxx檢查點之一),模型將需要文本輸入有一個前綴來表示正在進行的任務,例如從英語到法語的翻譯 + + + + + +⚠️ 我們不關注目標的注意力掩碼,因為模型不會需要它。相反,對應於填充標記的標籤應設置為-100,以便在loss計算中忽略它們。這將在稍後由我們的數據整理器完成,因為我們正在應用動態填充,但是如果您在此處使用填充,您應該調整預處理函數以將與填充標記對應的所有標籤設置為 -100。 + + + +我們現在可以對數據集的所有數據一次性應用該預處理: + +```py +tokenized_datasets = split_datasets.map( + preprocess_function, + batched=True, + remove_columns=split_datasets["train"].column_names, +) +``` + +現在數據已經過預處理,我們準備好微調我們的預訓練模型! + +{#if fw === 'pt'} + +## 使用 Trainer API 微調模型 + +使用 `Trainer` 的實際代碼將與以前相同,只是稍作改動:我們在這裡使用 [`Seq2SeqTrainer`](https://huggingface.co/transformers/main_classes/trainer.html#seq2seqtrainer), 它是 `Trainer` 的子類,它可以正確處理這種序列到序列的評估,並使用 `generate()` 方法來預測輸入的輸出。 當我們討論評估指標時,我們將更詳細地探討這一點。 + +首先,我們需要一個實際的模型來進行微調。 我們將使用通常的 `AutoModel` API: + +```py +from transformers import AutoModelForSeq2SeqLM + +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{:else} + +## 使用 Keras 微調模型 + +首先,我們需要一個實際的模型來進行微調。 我們將使用通常的 `AutoModel` API: + +```py +from transformers import TFAutoModelForSeq2SeqLM + +model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint, from_pt=True) +``` + + + +💡 `Helsinki-NLP/opus-mt-en-fr` checkpoint只有 PyTorch 的權重,所以在使用`from_pretrained()`方法加載模型時不會使用 `from_pt=True` 參數。 當您指定`from_pt=True`,庫會自動下載並轉換PyTorch 為您提供權重。 如您所見,使用🤗transormer 在兩者之間切換非常簡單 + + + +{/if} + +請注意,這次我們使用的是在翻譯任務上訓練過的模型,並且實際上已經可以使用,因此沒有關於丟失權重或新初始化權重的警告。 + +### 數據整理 + +我們需要一個數據整理器來處理動態批處理的填充。在本例中,我們不能像[第3章](/course/chapter3)那樣使用帶填充的**DataCollatorWithPadding**,因為它只填充輸入(輸入ID、注意掩碼和令牌類型ID)。我們的標籤也應該填充到標籤中遇到的最大長度。而且,如前所述,用於填充標籤的填充值應為-100,而不是標記器的填充標記,以確保在損失計算中忽略這些填充值。 + +這一切都可以由 [`DataCollatorForSeq2Seq`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorforseq2seq) 完成。 與 `DataCollatorWithPadding` 一樣,它採用用於預處理輸入的`tokenizer`,但它也採用`model`。 這是因為數據整理器還將負責準備解碼器輸入 ID,它們是標籤偏移之後形成的,開頭帶有特殊標記。 由於不同架構的這種轉變略有不同,因此“DataCollatorForSeq2Seq”需要知道“模型”對象: + +{#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]]) +``` + +我們還可以查看解碼器輸入 ID,看看它們是標籤的偏移形成的版本: + +```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 = tokenized_datasets["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) +tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=16, +) +``` + +{/if} + + +### 評估指標 + + + +{#if fw === 'pt'} + +`Seq2SeqTrainer` 添加到其超類 `Trainer` 的功能是在評估或預測期間使用 `generate()` 方法的能力。 在訓練期間,模型將使用帶有注意掩碼的“decoder_input_ids”,以確保它不使用預測的標記之後的標記,以加快訓練速度。 在推理過程中,我們將無法使用預測的標記之後的標記,因為我們沒有標籤,因此使用相同的設置使用帶有注意掩碼的“decoder_input_ids”,評估我們的模型是個好主意。 + +正如我們在[第一章](/course/chapter1/6)看到的,解碼器通過一個一個地預測標記來執行推理——這是🤗 Transformers 在幕後通過 **generate()** 方法實現的。如果我們設置 predict_with_generate=True,Seq2 Seq Trainer 將允許我們使用該方法進行評估。 + + +{/if} + +用於翻譯的傳統指標是[BLEU 分數](https://en.wikipedia.org/wiki/BLEU), 由Kishore Papineni等人在[2002年的一篇文章](https://aclanthology.org/P02-1040.pdf)中引入。BLEU 分數評估翻譯與其標籤的接近程度。它不衡量模型生成輸出的可懂度或語法正確性,而是使用統計規則來確保生成輸出中的所有單詞也出現在目標中。此外,如果相同單詞在目標中沒有重複,則有規則懲罰相同單詞的重複(以避免模型輸出類似 **the the the the the**的句子 ) 並輸出比目標中短的句子(以避免模型輸出像 **the** 這樣的句子)。 + +BLEU 的一個缺點是它需要文本已經被分詞,這使得比較使用不同標記器的模型之間的分數變得困難。因此,當今用於基準翻譯模型的最常用指標是[SacreBLEU](https://github.com/mjpost/sacrebleu),它通過標準化標記化步驟解決了這個缺點(和其他的一些缺點)。要使用此指標,我們首先需要安裝 SacreBLEU 庫: + +```py +!pip install sacrebleu +``` + +然後我們可以就像我們在[第三章](/course/chapter3)那樣通過 **load_metric()** 加載它 : + +```py +from datasets import load_metric + +metric = load_metric("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} +``` + +這得到了 46.75 的 BLEU 分數,這是相當不錯的——作為參考,原始 Transformer 模型在[“Attention Is All You Need” 論文](https://arxiv.org/pdf/1706.03762.pdf)類似的英語和法語翻譯任務中獲得了 41.8 的 BLEU 分數! (有關各個指標的更多信息,例如 **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** (標記器會自動為填充標記做同樣的事情): + +```py +import numpy as np + + +def compute_metrics(): + all_preds = [] + all_labels = [] + sampled_dataset = tokenized_datasets["validation"].shuffle().select(range(200)) + tf_generate_dataset = sampled_dataset.to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=4, + ) + for batch in tf_generate_dataset: + predictions = model.generate( + input_ids=batch["input_ids"], attention_mask=batch["attention_mask"] + ) + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + labels = batch["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 + # In case the model returns more than the prediction logits + if isinstance(preds, tuple): + preds = preds[0] + + decoded_preds = tokenizer.batch_decode(preds, skip_special_tokens=True) + + # Replace -100s in the labels as we can't decode them + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + # Some simple post-processing + 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} + +現在這已經完成了,我們已經準備好微調我們的模型了! + +### 微調模型 + +第一步是登錄 Hugging Face,這樣您就可以將結果上傳到模型中心。有一個方便的功能可以幫助您在notebook中完成此操作: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +這將顯示一個小部件,您可以在其中輸入您的 Hugging Face 登錄憑據。 + +如果您不是在notebook上運行代碼,只需在終端中輸入以下行: + +```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 + +# 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_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) + +# Train in mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +接下來,我們定義一個 `PushToHubCallback` 以便在訓練期間將我們的模型上傳到 Hub,正如我們在 [第 2 節]((/course/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"`。 + + + +💡如果您使用的輸出目錄已經存在,則它需要是您要推送到的存儲庫的本地克隆。如果不是,您將在定義您的名稱時會遇到錯誤,並且需要設置一個新名稱。 + + + +最後,讓我們看看訓練結束後我們的指標是什麼樣的: + +```py +print(compute_metrics()) +``` + +``` +{'bleu': 57.334066271545865} +``` + +在這個階段,您可以使用模型中心上的推理小部件來測試您的模型並與您的朋友分享。 您已經成功地微調了翻譯任務中的模型——恭喜! + +{: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,這可以加快支持fp16的 GPU 上的訓練速度。 +- 和上面我們討論的那樣,我們設置predict_with_generate=True +- 我們用push_to_hub=True在每個 epoch 結束時將模型上傳到 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"`。 + + + +💡如果您使用的輸出目錄已經存在,則它需要是您要推送到的存儲庫的本地克隆。如果不是,您將在定義您的名稱時會遇到錯誤,並且需要設置一個新名稱。 + + + + +最後,我們需要將所有內容傳遞給 **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_target_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的分數還不錯,這反映了我們的模型已經擅長將英語句子翻譯成法語句子。 + +接下來是訓練,這也需要一些時間: + +```python +trainer.train() +``` + +請注意,當訓練發生時,每次保存模型時(這裡是每個時期),它都會在後臺上傳到 Hub。這樣,如有必要,您將能夠在另一臺機器上繼續您的訓練。 + +訓練完成後,我們再次評估我們的模型——希望我們會看到 BLEU 分數有所改善! + +```py +trainer.evaluate(max_length=max_target_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()** 方法來確保我們上傳模型的最新版本。這 **Trainer** 還創建了一張包含所有評估結果的模型卡並上傳。此模型卡包含可幫助模型中心為推理演示選擇小部件的元數據。通常不需要做額外的更改,因為它可以從模型類中推斷出正確的小部件,但在這種情況下,相同的模型類可以用於所有類型的序列到序列問題,所以我們指定它是一個翻譯模型: + +```py +trainer.push_to_hub(tags="translation", commit_message="Training complete") +``` + +如果您想檢查命令執行的結果,此命令將返回它剛剛執行的提交的 URL,可以打開url進行檢查: + +```python out +'https://huggingface.co/sgugger/marian-finetuned-kde4-en-to-fr/commit/3601d621e3baae2bc63d3311452535f8f58f6ef3' +``` + +在此階段,您可以使用模型中心上的推理小部件來測試您的模型並與您的朋友分享。您已成功微調翻譯任務的模型 - 恭喜! + +如果您想更深入地瞭解訓練循環,我們現在將向您展示如何使用 🤗 Accelerate 做同樣的事情。 + +{/if} + +{#if fw === 'pt'} + +## 自定義訓練循環 + +現在讓我們看一下完整的訓練循環,以便您可以輕鬆自定義所需的部分。它看起來很像我們在[本章第二節](/course/chapter7/2)和[第三章第四小節](/course/chapter3/4)所做的。 + +### 準備訓練所需的一切 + +您已經多次看到所有這些,因此這一塊會簡略進行。首先我們將構建我們的數據集的**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()` 方法。 請記住,如果您想在 Colab 筆記本訓練中使用TPU,則需要將所有這些代碼移動到訓練函數中,並且不應執行任何實例化“加速器”的對象。 + +```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** 工作文件夾中的對象。如果您尚未登錄,請先登錄 Hugging Face。我們將從我們想要為模型提供的模型 ID 中確定存儲庫名稱(您可以自由地用自己的選擇替換 **repo_name** ;它需要包含您的用戶名,可以使用**get_full_repo_name()**函數的查看目前的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) +``` + +我們現在可以通過調用 **repo.push_to_hub()** 方法上傳我們保存的任何內容 **output_dir** 。這將幫助我們在每個 epoch 結束時上傳過程中的模型。 + +### 訓練循環 + +我們現在準備編寫完整的訓練循環。為了簡化它的評估部分,我們定義了這個 **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) + + # Replace -100 in the labels as we can't decode them. + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + # Some simple post-processing + decoded_preds = [pred.strip() for pred in decoded_preds] + decoded_labels = [[label.strip()] for label in decoded_labels] + return decoded_preds, decoded_labels +``` + +訓練循環看起來和[本章第二節](/course/chapter7/2)與[第三章](/course/chapter3)很像,在評估部分有一些不同 - 所以讓我們專注於這一點! + +首先要注意的是我們使用 `generate()` 方法來計算預測,但這是我們基礎模型上的一個方法,而不是包裝模型🤗 Accelerate 在 `prepare()` 方法中創建。 這就是為什麼我們先解包模型,然後調用這個方法。 + +第二件事是,就像[token 分類](/course/chapter7/2),兩個進程可能將輸入和標籤填充為不同的形狀,因此我們在調用 **gather()** 方法之前使用 **accelerator.pad_across_processes()** 使預測和標籤具有相同的形狀。如果我們不這樣做,評估要麼出錯,要麼永遠在阻塞。 + +```py +from tqdm.auto import tqdm +import torch + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Training + 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) + + # Evaluation + 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"] + + # Necessary to pad predictions and labels for being gathered + 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}") + + # Save and upload + accelerator.wait_for_everyone() + unwrapped_model = accelerator.unwrap_model(model) + unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) + if accelerator.is_main_process: + tokenizer.save_pretrained(output_dir) + repo.push_to_hub( + commit_message=f"Training in progress epoch {epoch}", blocking=False + ) +``` + +```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} + +## 使用微調後的模型 + +我們已經向您展示瞭如何將我們在模型中心微調的模型與推理小部件一起使用。 要在“管道”中本地使用它,我們只需要指定正確的模型標識符: + +```py +from transformers import pipeline + +# Replace this with your own checkpoint +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”,而是將其翻譯成法語官方版本。 “”的翻譯也是一樣的: + +```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."}] +``` + +風格適應的另一個很好的例子! + + + +✏️ **輪到你了!** “電子郵件”這個詞在模型返回了什麼? + + diff --git a/chapters/zh-TW/chapter7/5.mdx b/chapters/zh-TW/chapter7/5.mdx new file mode 100644 index 000000000..eeab998ed --- /dev/null +++ b/chapters/zh-TW/chapter7/5.mdx @@ -0,0 +1,1047 @@ + + +# 提取文本摘要 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + + +在本節中,我們將看看如何使用 Transformer 模型將長文檔壓縮為摘要,這項任務稱為文本摘要.這是最具挑戰性的 NLP 任務之一,因為它需要一系列能力,例如理解長篇文章和生成能夠捕捉文檔中主要主題的連貫文本。但是,如果做得好,文本摘要是一種強大的工具,可以減輕領域專家詳細閱讀長文檔的負擔,從而加快各種業務流程。 + + + +儘管在[Hugging Face Hub](https://huggingface.co/models?pipeline_tag=summarization=downloads)上已經存在各種微調模型用於文本摘要,幾乎所有這些都只適用於英文文檔。因此,為了在本節中添加一些變化,我們將為英語和西班牙語訓練一個雙語模型。在本節結束時,您將有一個可以總結客戶評論的[模型](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es)。 + + + + +如下所示:正如我們將看到的,這些摘要很簡潔,因為它們是從客戶在產品評論中提供的標題中學到的。讓我們首先為這項任務準備一個合適的雙語語料庫。 + +## 準備多語言語料庫 + +我們將使用[多語言亞馬遜評論語料庫](https://huggingface.co/datasets/amazon_reviews_multi)創建我們的雙語摘要器。該語料庫由六種語言的亞馬遜產品評論組成,通常用於對多語言分類器進行基準測試。然而,由於每條評論都附有一個簡短的標題,我們可以使用標題作為我們模型學習的目標摘要!首先,讓我們從 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** 列。讓我們通過創建一個簡單的函數來查看一些示例,該函數使用我們在[第五章](/course/chapter5)學到過: + +```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.' +``` + + + +✏️ **試試看!** 更改 `Dataset.shuffle()` 命令中的隨機種子以探索語料庫中的其他評論。 如果您是說西班牙語的人,請查看 `spanish_dataset` 中的一些評論,看看標題是否也像合理的摘要。 + + + +此示例顯示了人們通常在網上找到的評論的多樣性,從正面到負面(以及介於兩者之間的所有內容!)。儘管標題為“meh”的示例信息量不大,但其他標題看起來像是對評論本身的體面總結。在單個 GPU 上訓練所有 400,000 條評論的摘要模型將花費太長時間,因此我們將專注於為單個產品領域生成摘要。為了瞭解我們可以選擇哪些域,讓我們將 **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 +``` + +英語數據集中最受歡迎的產品是家居用品、服裝和無線電子產品。不過,為了堅持亞馬遜的主題,讓我們專注於總結書籍的評論——畢竟,這是亞馬遜這家公司成立的基礎!我們可以看到兩個符合要求的產品類別( **book** 和 **digital_ebook_purchase** ),所以讓我們為這些產品過濾兩種語言的數據集。正如我們在[第五章](/course/chapter5)學到的, 這 **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) + +# Peek at a few examples +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 模型! + +## 文本摘要模型 + +如果你仔細想想,文本摘要是一種類似於機器翻譯的任務:我們有一個像評論這樣的文本正文,我們希望將其“翻譯”成一個較短的版本,以捕捉輸入的顯著特徵。因此,大多數用於文本摘要的 Transformer 模型採用了我們在[第一章](/course/chapter1)遇到的編碼器-解碼器架構。儘管有一些例外,例如 GPT 系列模型,它們在few-shot(少量微調)之後也可以提取摘要。下表列出了一些流行的預訓練模型,可以對其進行微調以進行彙總。 + +| Transformer 模型 | 描述 | 多種語言? | +| :---------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------: | +| [GPT-2](https://huggingface.co/gpt2-xl) | 雖然訓練為自迴歸語言模型,但您可以通過在輸入文本末尾附加“TL;DR”來使 GPT-2 生成摘要。 | ❌ | +| [PEGASUS](https://huggingface.co/google/pegasus-large) | 在預訓練是的目標是來預測多句子文本中的屏蔽句子。 這個預訓練目標比普通語言建模更接近文本摘要,並且在流行的基準測試中得分很高。 | ❌ | +| [T5](https://huggingface.co/t5-base) | 通用的 Transformer 架構,在文本到文本的框架中制定所有任務; 例如,模型文本摘要的輸入格式是`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 的有趣架構,在文本到文本框架中進行了預訓練。在 T5 中,每個 NLP 任務都是根據提示前綴來制定的,例如 **summarize:** 這使模型使生成的文本適應提示。如下圖所示,這讓 T5 變得非常通用,因為你可以用一個模型解決很多任務! + + +
+Different tasks performed by the T5 architecture. + +
+ +mT5 不使用前綴,但具有 T5 的大部分功能,並且具有多語言的優勢。現在我們已經選擇了一個模型,讓我們來看看準備我們的訓練數據。 + + + +✏️ **試試看!** 完成本節後,通過使用相同的技術對 mBART 進行微調,看看 mT5 與 mBART 相比有多好。 對於獎勵積分,您還可以嘗試僅在英文評論上微調 T5。 由於 T5 需要一個特殊的前綴提示,因此您需要在下面的預處理步驟中將“summarize:”添加到輸入示例中。 + + + +## 預處理數據 + + + +我們的下一個任務是對我們的評論及其標題進行標記和編碼。像往常一樣,我們首先加載與預訓練模型檢查點相關的標記器。我們將使用 **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]} +``` + +在這裡我們可以看到我們在[第三章](/course/chapter3)第一次微調實驗中遇到的熟悉的 **input_ids** 和 **attention_mask** .讓我們用分詞器解碼這些輸入 ID ,可以**convert_ids_to_tokens()** 函數來查看我們正在處理什麼樣的標記器: + +```python +tokenizer.convert_ids_to_tokens(inputs.input_ids) +``` + +```python out +['▁I', '▁', 'loved', '▁reading', '▁the', '▁Hung', 'er', '▁Games', ''] +``` + +特殊的 Unicode 字符 `▁` 和序列結束標記 `` 表明我們正在處理 SentencePiece 分詞器,它基於在[第六章](/course/chapter6)中討論的Unigram分詞算法. Unigram 對多語言語料庫特別有用,因為它允許 SentencePiece 不知道重音、標點符號以及許多語言(如日語)沒有空格字符。 + +為了標記我們的語料庫,我們必須處理與摘要相關的細節:因為我們的標籤也是文本,它們可能會超過模型的最大上下文大小。這意味著我們需要對評論及其標題進行截斷,以確保我們不會將過長的輸入傳遞給我們的模型。 🤗 Transformers 中的分詞器提供了一個漂亮的 **as_target_tokenizer()** 函數,它允許您並行分詞並標記標籤的函數。這通常是使用預處理函數內的上下文管理器完成的,該函數首先對輸入進行編碼,然後將標籤編碼為單獨的列。以下是 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 + ) + # Set up the tokenizer for targets + with tokenizer.as_target_tokenizer(): + 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()** 我們可以看到評論首先被標記化,然後是標題在 **as_target_tokenizer()** 函數里也做了相同的處理. + +有了 `preprocess_function()`,我們在整個課程中廣泛使用的方便的 `Dataset.map()` 函數來標記整個語料庫是一件簡單的事情: + +```python +tokenized_datasets = books_dataset.map(preprocess_function, batched=True) +``` + +既然語料庫已經預處理完畢,我們來看看一些常用的摘要指標。正如我們將看到的,在衡量機器生成的文本的質量方面沒有靈丹妙藥。 + + + +💡 你可能已經注意到我們在上面的 `Dataset.map()` 函數中使用了 `batched=True`。 這會以 1,000 個(默認)為單位對示例進行編碼,並允許您利用 🤗 Transformers 中快速標記器的多線程功能。 在可能的情況下,嘗試使用 `batched=True` 來加速您的預處理! + + + + +## 文本摘要的指標 + + + +與我們在本課程中涵蓋的大多數其他任務相比,衡量文本生成任務(如摘要或翻譯)的性能並不那麼簡單。例如,對於“我喜歡閱讀飢餓遊戲”這樣的評論,有多個有效摘要,例如“我喜歡飢餓遊戲”或“飢餓遊戲是一本好書”。顯然,在生成的摘要和標籤之間應用某種精確匹配並不是一個好的解決方案——即使是人類在這樣的指標下也會表現不佳,因為我們都有自己的寫作風格。 + +總而言之,最常用的指標之一是[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_ 分數。。 + + + +🙋 如果這是您第一次聽說精確率和召回率,請不要擔心——我們將一起通過一些明確的示例來說明一切。 這些指標通常在分類任務中遇到,因此如果您想了解在該上下文中如何定義精確度和召回率,我們建議查看 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}} $$ + +對於我們上面的簡單例子,這個公式給出了 6/6 = 1 的完美召回率;即,參考摘要中的所有單詞都已由模型生成。這聽起來可能很棒,但想象一下,如果我們生成的摘要是“我真的很喜歡整晚閱讀飢餓遊戲”。這也將有完美的recall,但可以說是一個更糟糕的總結,因為它很冗長。為了處理這些場景,我們還計算了pecision,它在 ROUGE 上下文中衡量生成的摘要中有多少是相關的: + +$$ \mathrm{Precision} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, generated\, summary}} $$ + +將此應用到我們的詳細摘要中會得到 6/10 = 0.6 的精度,這比我們較短的摘要獲得的 6/7 = 0.86 的精度要差得多。在實踐中,通常計算精度和召回率,然後報告 F1-score(精度和召回率的調和平均值)。我們可以在 🤗 Datasets 中通過安裝 **rouge_score** 包來計算他們: + +```py +!pip install rouge_score +``` + +然後按如下方式加載 ROUGE 指標: + +```python +from datasets import load_metric + +rouge_score = load_metric("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實際上計算了精度、召回率和 F1 分數的置信區間;這些是你可以在這裡看到的 **low** , **mid** , 和 **high** 屬性。此外,🤗 Datasets在比較生成摘要和參考摘要時,會根據不同類型的文本粒度計算各種 ROUGE 分數。這 **rouge1** 變體是一元組的重疊——這只是表達單詞重疊的一種奇特方式,這正是我們上面討論的度量標準。為了驗證這一點,讓我們輸出 **mid** 的數值: + +```python +scores["rouge1"].mid +``` + +```python out +Score(precision=0.86, recall=1.0, fmeasure=0.92) +``` +太好了,準確率和召回率匹配了!那麼其他的 ROUGE 分數呢? **rouge2** 測量二元組之間的重疊(想想單詞對的重疊),而 **rougeL** 和 **rougeLsum** 通過在生成的和參考摘要中查找最長的公共子串來測量最長的單詞匹配序列。中的“總和” **rougeLsum** 指的是這個指標是在整個摘要上計算的,而 **rougeL** 計算為單個句子的平均值。 + + + + ✏️ **試試看!** 創建您自己的生成和參考摘要示例,並查看生成的 ROUGE 分數是否與基於精確度和召回率公式的手動計算一致。 對於附加分,將文本拆分為二元組並比較“rouge2”指標的精度和召回率。 + + + +我們將使用這些 ROUGE 分數來跟蹤我們模型的性能,但在此之前,讓我們做每個優秀的 NLP 從業者都應該做的事情:創建一個強大而簡單的baseline! + +### 創建強大的baseline + +文本摘要的一個常見基線是簡單地取一篇文章的前三個句子,通常稱為 _lead-3_ 基線。 我們可以使用句號(英文使用.)來跟蹤句子邊界,但這在"U.S." or "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.' +``` + +這似乎有效,所以讓我們現在實現一個函數,從數據集中提取這些“摘要”並計算baseline的 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 baseline過於冗長。 現在我們有了一個很好的基準,讓我們將注意力轉向微調 mT5! + +{#if fw === 'pt'} + +## 使用 `Trainer` API微調mT5 + +微調模型以進行提取摘要與我們在本章中介紹的其他任務非常相似。 我們需要做的第一件事是從`mt5-small`檢查點加載預訓練模型。 由於摘要提取是一個序列到序列的任務,我們可以使用 AutoModelForSeq2SeqLM 類加載模型,該類會自動下載並緩存權重: + +```python +from transformers import AutoModelForSeq2SeqLM + +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{:else} + +## 使用 `Keras` API微調mT5 + +微調模型以進行提取摘要與我們在本章中介紹的其他任務非常相似。 我們需要做的第一件事是從`mt5-small`檢查點加載預訓練模型。 由於摘要提取是一個序列到序列的任務,我們可以使用 AutoModelForSeq2SeqLM 類加載模型,該類會自動下載並緩存權重: + +```python +from transformers import TFAutoModelForSeq2SeqLM + +model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +{/if} + + + +💡 If you're wondering why you don't see any warnings about fine-tuning the model on a downstream task, that's because for sequence-to-sequence tasks we keep all the weights of the network. Compare this to our text classification model in [Chapter 3](/course/chapter3), where the head of the pretrained model was replaced with a randomly initialized network. +💡 如果您想知道為什麼在下游任務中沒有看到任何關於微調模型的警告,那是因為對於序列到序列的任務,我們保留了網絡的所有權重。與我們在[第三章] (/course/chapter3)中的文本分類模型進行比較,文本分類模型預訓練模型的頭部被隨機初始化的網絡替換。 + + + +我們需要做的下一件事是登錄 Hugging Face Hub。如果您在notebook中運行此代碼,則可以使用以下實用程序函數執行此操作: + +```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 +# Show the training loss with every epoch +logging_steps = len(tokenized_datasets["train"]) // batch_size +model_name = model_checkpoint.split("/")[-1] + +args = Seq2SeqTrainingArguments( + output_dir=f"{model_name}-finetuned-amazon-en-es", + evaluation_strategy="epoch", + learning_rate=5.6e-5, + per_device_train_batch_size=batch_size, + per_device_eval_batch_size=batch_size, + weight_decay=0.01, + save_total_limit=3, + num_train_epochs=num_train_epochs, + predict_with_generate=True, + logging_steps=logging_steps, + push_to_hub=True, +) +``` + +在這裡, **predict_with_generate** 參數已設置為True表明我們應該在評估期間生成摘要,以便我們可以計算每個時期的 ROUGE 分數。正如在[第一章](/course/chapter1)所討論的,解碼器通過逐個預測令牌來執行推理,這是由模型的 **generate()** 方法實現的。設置 **predict_with_generate=True** 告訴 **Seq2SeqTrainer** 使用該方法進行評估。我們還調整了一些默認的超參數,例如學習率、epoch數和權重衰減,並且我們設置了 **save_total_limit** 訓練期間最多隻保存 3 個檢查點的選項——這是因為即使是 mT5 的“small”版本也使用大約 1 GB 的硬盤空間,我們可以通過限制我們保存的副本數量來節省一點空間。 + +`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 分數之前將輸出和標籤解碼為文本。 下面的函數正是這樣做的,並且還利用 `nltk` 中的 `sent_tokenize()` 函數來用換行符分隔摘要語句: + +```python +import numpy as np + + +def compute_metrics(eval_pred): + predictions, labels = eval_pred + # Decode generated summaries into text + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + # Replace -100 in the labels as we can't decode them + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + # Decode reference summaries into text + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + # ROUGE expects a newline after each sentence + decoded_preds = ["\n".join(sent_tokenize(pred.strip())) for pred in decoded_preds] + decoded_labels = ["\n".join(sent_tokenize(label.strip())) for label in decoded_labels] + # Compute ROUGE scores + result = rouge_score.compute( + predictions=decoded_preds, references=decoded_labels, use_stemmer=True + ) + # Extract the median scores + result = {key: value.mid.fmeasure * 100 for key, value in result.items()} + return {k: round(v, 4) for k, v in result.items()} +``` + +{/if} + +接下來,我們需要為我們的序列到序列任務定義一個數據整理器。由於 mT5 是一個編碼器-解碼器 Transformer 模型,準備我們的批次的一個微妙之處是,在解碼過程中,我們需要將標籤向右移動一個。 這是為了確保解碼器只看到之前的真實的標籤,而不是當前或未來的標籤,這對於模型來說很容易記憶。 這類似於在 [因果語言建模](/course/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 +) +``` + +由於 collator 需要一個 `dict` 的列表,其中每個 `dict` 代表數據集中的一個示例,我們還需要在將數據傳遞給 data collator 之前將數據整理成預期的格式: + +```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]` 標記(其 ID 是 ` 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 分數隨著每個 epoch 增加。訓練完成後,您可以通過運行**Trainer.evaluate()** 查看最終的 ROUGE 分數 : + +```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 baseline——很好!最後要做的是將模型權重推送到 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()** 是 Git 提交哈希的 URL,因此您可以輕鬆查看對模型存儲庫所做的更改! + +在結束本節之前,讓我們看一下如何使用 🤗 Accelerate 提供的底層API對 mT5 進行微調。 + +{:else} + +我們幾乎準備好訓練了! 我們只需要使用我們上面定義的數據整理器將我們的數據集轉換為 tf.data.Dataset ,然後 `compile()` 和 `fit()` 模型。 首先,轉換數據集: +```python +tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=8, +) +tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=8, +) +``` + +現在,我們定義訓練超參數並編譯: + +```python +from transformers import create_optimizer +import tensorflow as tf + +# 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_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) + +# Train in mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +最後,我們擬合模型。 我們在每個 epoch 之後使用`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 +) +``` + +我們在訓練期間輸出了一些loss,但實際上我們希望看到我們之前計算的 ROUGE 指標。 要獲得這些指標,我們需要從模型生成輸出並將它們轉換為字符串。 讓我們為 ROUGE 指標構建一些標籤和預測列表以進行比較(請注意,如果您在本節中遇到import的錯誤,您可能需要`!pip install tqdm`): + +```python +from tqdm import tqdm +import numpy as np + +all_preds = [] +all_labels = [] +for batch in tqdm(tf_eval_dataset): + predictions = model.generate(**batch) + decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) + labels = batch["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'} + +## 使用 🤗 Accelerate 微調 mT5 + +使用 🤗 Accelerate 微調我們的模型與我們在 [Chapter 3](/course/chapter3) 中遇到的文本分類示例非常相似。 主要區別在於需要在訓練期間顯式生成摘要並定義我們如何計算 ROUGE 分數(回想一下,`Seq2SeqTrainer` 為我們生成了摘要)。 讓我們看看我們如何在 🤗 Accelerate 中實現這兩個要求! + +### 為訓練做好一切準備 + +The first thing we need to do is create a `DataLoader` for each of our splits. Since the PyTorch dataloaders expect batches of tensors, we need to set the format to `"torch"` in our datasets: +我們需要做的第一件事是為每個數據集的每一個拆分創建一個`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 上進行訓練,則需要將上述所有代碼移動到專門的訓練函數中。有關詳細信息,請參閱[第三章](/course/chapter3)。 + + + +現在我們已經準備好了我們索要用的對象,還有三件事要做: + +* 定義學習率調度計劃。 +* 實現一個功能來對摘要進行後續處理以進行評估。 +* 在 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 expects a newline after each sentence + preds = ["\n".join(nltk.sent_tokenize(pred)) for pred in preds] + labels = ["\n".join(nltk.sent_tokenize(label)) for label in labels] + + return preds, labels +``` + +如果你還記得我們是如何定義 `Seq2SeqTrainer` 的 `compute_metrics()` 函數的,這對你來說應該很熟悉。 + +最後,我們需要在 Hugging Face Hub 上創建一個模型存儲庫。 為此,我們可以使用🤗 Hub 庫的get_full_repo_name。 我們只需要為我們的存儲庫定義一個名稱,該庫有一個非常好用的函數可以將存儲庫 ID 與用戶配置文件結合起來: +```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) +``` +這將允許我們在訓練期間通過調用 `repo.push_to_hub()` 方法將模型推送到 Hub! 現在讓我們通過寫出完整的訓練循環來結束我們的分析。 + +### 訓練循環 + +文本摘要的訓練循環與我們遇到的其他 🤗 Accelerate 示例非常相似,大致分為四個主要步驟:這 + +1. 通過在每個epoch 迭代 `train_dataloader` 中的所有示例來訓練模型。 +2. 在每個 epoch 結束時生成模型摘要,首先生成標記,然後將它們(和參考摘要)解碼為文本。 +3. 使用我們之前看到的相同技術計算 ROUGE 分數。 +4. 保存檢查點並將所有內容推送到 Hub。 在這裡,我們依賴 `Repository` 對象的巧妙的 `blocking=False` 參數,以便我們可以在每個 epoch 異步地上傳檢查點。 這使我們能夠繼續訓練,而不必等待與 GB 大小的模型慢呼呼的上傳! + +這些步驟可以在以下代碼塊中看到: + +```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): + # Training + model.train() + for step, batch in enumerate(train_dataloader): + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) + + # Evaluation + model.eval() + for step, batch in enumerate(eval_dataloader): + with torch.no_grad(): + generated_tokens = accelerator.unwrap_model(model).generate( + batch["input_ids"], + attention_mask=batch["attention_mask"], + ) + + generated_tokens = accelerator.pad_across_processes( + generated_tokens, dim=1, pad_index=tokenizer.pad_token_id + ) + labels = batch["labels"] + + # If we did not pad to max length, we need to pad the labels too + labels = accelerator.pad_across_processes( + batch["labels"], dim=1, pad_index=tokenizer.pad_token_id + ) + + generated_tokens = accelerator.gather(generated_tokens).cpu().numpy() + labels = accelerator.gather(labels).cpu().numpy() + + # Replace -100 in the labels as we can't decode them + labels = np.where(labels != -100, labels, tokenizer.pad_token_id) + if isinstance(generated_tokens, tuple): + generated_tokens = generated_tokens[0] + decoded_preds = tokenizer.batch_decode( + generated_tokens, skip_special_tokens=True + ) + decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) + + decoded_preds, decoded_labels = postprocess_text( + decoded_preds, decoded_labels + ) + + rouge_score.add_batch(predictions=decoded_preds, references=decoded_labels) + + # Compute metrics + result = rouge_score.compute() + # Extract the median ROUGE scores + result = {key: value.mid.fmeasure * 100 for key, value in result.items()} + result = {k: round(v, 4) for k, v in result.items()} + print(f"Epoch {epoch}:", result) + + # Save and upload + accelerator.wait_for_everyone() + unwrapped_model = accelerator.unwrap_model(model) + unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) + if accelerator.is_main_process: + tokenizer.save_pretrained(output_dir) + repo.push_to_hub( + commit_message=f"Training in progress epoch {epoch}", blocking=False + ) +``` + +```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} + +## 使用您微調的模型 + +將模型推送到 Hub 後,您可以通過推理小部件或“管道”對象來使用它,如下所示: + +```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' +``` + +摘要翻譯成了英文的“非常容易閱讀”,在這種情況下,我們可以看到它是直接從評論中提取的。 這顯示了 mT5 模型的多功能性,並讓您體驗了處理多語言語料庫的感覺! + +接下來,我們將把注意力轉向稍微複雜的任務:從頭開始訓練語言模型。 diff --git a/chapters/zh-TW/chapter7/6.mdx b/chapters/zh-TW/chapter7/6.mdx new file mode 100644 index 000000000..49a820da5 --- /dev/null +++ b/chapters/zh-TW/chapter7/6.mdx @@ -0,0 +1,906 @@ + + +# 從頭開始訓練因果語言模型 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +到目前為止,我們主要使用預訓練模型,並通過重用預訓練的權重來針對新用例對它們進行微調。正如我們在[第一章](/course/chapter1), 這通常稱為遷移學習,這是將 Transformer 模型應用於大多數標記數據稀疏的現實世界用例的非常成功的策略。在本章中,我們將採用不同的方法並從頭開始訓練一個全新的模型。如果您有大量數據並且它與用於可用模型的預訓練數據有很大不同,那麼這是一個很好的方法。然而,它也需要更多的計算資源來預訓練語言模型,而不僅僅是微調現有的模型。訓練新模型有意義的示例包括由音符、分子序列(如 DNA)或編程語言組成的數據集。後者最近受到關注,這要歸功於 TabNine 和 GitHub 的 Copilot 等工具,它們由 OpenAI 的 Codex 模型提供支持,可以生成長代碼序列。這種文本生成任務最好使用自迴歸或因果語言模型(例如 GPT-2)來解決。 + +在本節中,我們將構建代碼生成模型的縮小版本:我們將使用 Python 代碼的子集專注於單行完成而不是完整的函數或類。在 Python 中處理數據時,您會經常接觸 Python 數據科學堆棧,包括 `matplotlib` , `seaborn` , `pandas` , 和 `scikit-learn` 庫。在使用這些框架時,通常需要查找特定的命令,因此如果我們可以使用模型來為我們完成這些調用,那就太好了。 + + + +在[第六章](/course/chapter6) 我們創建了一個高效的分詞器來處理 Python 源代碼,但我們仍然需要一個大規模數據集來預訓練模型。在這裡,我們將我們的分詞器應用到源自 GitHub 存儲庫的 Python 代碼語料庫。然後我們將使用 `Trainer` API 和 🤗 Accelerate 來訓練模型。讓我們開始吧! + + + + +這實際上展示了使用本節中訓練並上傳到 Hub 的模型。你可以在[這裡](https://huggingface.co/huggingface-course/codeparrot-ds?text=plt.imshow%28)找到。請注意,由於在文本生成過程中發生了一些隨機化,您可能會得到略有不同的結果。 +## 收集數據 + +Python 代碼可以從 GitHub 等代碼存儲庫中獲得,我們可以通過抓取每個 Python 存儲庫來使用它們來創建數據集。這是在[Transformers textbook](https://learning.oreilly.com/library/view/natural-language-processing/9781098103231/)預訓練大型的GPT-2 模型。使用大約 180 GB 的 GitHub 轉儲,其中包含大約 2000 萬個 Python 文件,稱為 `codeparrot` ,作者構建了一個數據集,然後在[Hugging Face Hub](https://huggingface.co/datasets/transformersbook/codeparrot)上分享出來了. + +然而,對完整語料庫的訓練既耗時又費力,我們只需要與 Python 數據科學堆棧相關的數據集子集。所以,讓我們開始過濾 `codeparrot` 包含此堆棧中任何庫的所有文件的數據集。由於數據集的太大,我們希望避免下載它;因此反,我們將使用流功能來動態過濾它。為了使用前面提到的庫過濾代碼示例,我們將使用以下函數: + +```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 +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 +# This cell will take a very long time to execute, so you should skip it and go to +# the next one! +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 GB,包含 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="train") + +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` 字段包含我們希望我們的模型訓練的代碼。現在我們有了一個數據集,我們需要預處理文本,使其採用適合預訓練的格式。 + +## 準備數據集 + + + +第一步是對數據進行標記,以便我們可以將其用於訓練。由於我們的目標主要是自動完成短函數調用,因此我們可以保持上下文大小相對較小。這樣做的好處是我們可以更快地訓練模型並且它需要的內存顯著減少。如果您的應用程序擁有更多上下文很重要(例如,如果您希望模型基於具有函數定義的文件編寫單元測試),請確保增加該數量,但請記住,這需要更大的 GPU 內存佔用。現在,讓我們將上下文大小固定為 128 個標記,而不是 GPT-2 或 GPT-3 中分別使用的 1,024 或 2,048 個標記。 + + +大多數文檔包含超過 128 個標記,因此簡單地將輸入截斷到最大長度將消除我們數據集的很大一部分。相反,我們將使用 `return_overflowing_tokens` 標記整個輸入並將其分成幾個塊的選項,就像我們在[第六章](/course/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` 字段,我們還可以重建哪些塊屬於哪些輸入樣本。 + +通過這個操作,我們使用了一個方便的🤗 Datasets 中的` Dataset.map()` 函數,就是不需要一對一的映射;正如我們在[第三節](/course/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 + }) +}) +``` + +我們現在有 1670 萬個示例,每個示例有 128 個tokens ,總共相當於大約 21 億個tokens 。作為參考,OpenAI 的 GPT-3 和 Codex 模型分別在 300 和 1000 億個tokens 上訓練,其中 Codex 模型從 GPT-3 檢查點初始化。我們在本節中的目標不是與這些模型競爭,這些模型可以生成長而連貫的文本,而是創建一個縮小版本,為數據科學家提供快速自動完成功能。 + +現在我們已經準備好了數據集,讓我們設置模型! + + + + +✏️ **試試看!** 擺脫所有小於上下文大小的塊在這裡並不是什麼大問題,因為我們使用的是小上下文窗口。隨著上下文大小的增加(或者如果您有一個短文檔語料庫),被丟棄的塊的比例也會增加。準備數據的更有效方法是將所有標記化的樣本加入一個批次中,每個語料之間有一個`eos_token_id` 標記, 然後對連接的序列執行分塊。作為練習,修改 `tokenize()`函數以使用該方法。請注意,您需要設置`truncation=False` 和刪除標記生成器中的其他參數以獲取完整的標記 ID 序列。 + + + + +## 初始化新模型 + +我們的第一步是新初始化一個 GPT-2 模型。我們將對我們的模型使用與小型 GPT-2 模型相同的配置,因此我們加載預訓練配置,確保分詞器大小與模型詞彙量大小匹配並設置 `bos` 和 `eos` (序列的開始和結束)令牌 ID: + +{#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) # Builds the model +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} + +我們的模型有 1.24 億個參數,我們必須對其進行調整。在開始訓練之前,我們需要設置一個負責創建批次的數據整理器。我們可以使用 `DataCollatorForLanguageModeling` ,它是專為語言建模而設計(顧名思義)。除了堆疊和填充批次,它還負責創建語言模型標籤——在因果語言建模中,輸入也用作標籤(只是移動了一個元素),並且這個數據整理器在訓練期間即時創建它們,所以我們不需要複製 `input_ids`。 + +注意 `DataCollatorForLanguageModeling` 支持掩碼語言建模 (MLM) 和因果語言建模 (CLM)。默認情況下它為 MLM 準備數據,但我們可以通過設置`mlm=False`參數切換到 CLM : + +{#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_dataset["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'} + +現在,我們可以使用`to_tf_dataset()`方法,使用上面創建的數據整理器將數據集轉換為TensorFlow數據集: + +```python +tf_train_dataset = tokenized_dataset["train"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=True, + batch_size=32, +) +tf_eval_dataset = tokenized_dataset["valid"].to_tf_dataset( + columns=["input_ids", "attention_mask", "labels"], + collate_fn=data_collator, + shuffle=False, + batch_size=32, +) +``` + +{/if} + + + +⚠️ 移動輸入和標籤以對齊它們發生在模型內部,因此數據整理器只需複製輸入以創建標籤。 + + + + +現在我們已經準備好實際訓練我們的模型的一切了——畢竟這不是那麼多工作!在我們開始訓練之前,我們應該登錄 Hugging Face。如果您在筆記本上工作,則可以使用以下實用程序功能: + +```python +from huggingface_hub import notebook_login + +notebook_login() +``` + +這將顯示一個小部件,您可以在其中輸入您的 Hugging Face 登錄憑據。 + +如果您不是在notebook上工作,只需在終端中輸入以下行: + +```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) + +# Train in mixed-precision float16 +tf.keras.mixed_precision.set_global_policy("mixed_float16") +``` + +現在我們可以調用`model.fit(),`並等待訓練完成。你是在完整的訓練集還是他的子集上運行,這將分別需要20和2個小時,所以拿一些咖啡和一本好書來閱讀!訓練完成後,我們可以將模型和分詞器推送到中心: + +```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} + + + +✏️ **試試看!** 除了`TrainingArguments` 之外,我們只需要大約30行代碼就可以從原始文本到訓練GPT-2。 用你自己的數據集試試看,看看你能不能得到好的結果! + + + + + +{#if fw === 'pt'} + +💡 如果您可以訪問具有多個 GPU 的機器,請嘗試在那裡運行代碼。 `Trainer`自動管理多臺機器,這可以極大地加快訓練速度。 + +{:else} + +💡 如果您有權訪問具有多個 GPU 的計算機,則可以嘗試使用 `MirroredStrategy` 上下文來大幅加快訓練速度。您需要創建一個`tf.distribute.MirroredStrategy`對象,並確保 `to_tf_dataset` 命令以及模型創建和對 `fit()`的調用都在其 `scope()` context. 上下文中運行。您可以查看有關此內容的文檔[here](https://www.tensorflow.org/guide/distributed_training#use_tfdistributestrategy_with_keras_modelfit). + +{/if} + + + +## 使用管道生成代碼 + +現在是關鍵的部分:讓我們看看經過訓練的模型的實際效果如何!我們可以在日誌中看到損失穩步下降,但為了讓模型進行測試,讓我們看看它在某些測試上的表現如何。為此,我們將模型包裝在文本生成中的`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} + +讓我們從創建散點圖的簡單任務開始: + +```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` 。由於生成的token數量有限,以下 `for` 循環被切斷。讓我們看看我們是否可以做一些更復雜的事情並讓模型幫助我們分組操作: + +```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` 並建立一個隨機森林模型: + +```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數據科學堆棧的一些語法。當然,在將模型部署到現實世界之前,我們需要更徹底地評估模型,但這仍然是一個令人印象深刻的原型。 + +{:else} + +從這幾個例子來看,模型似乎已經學習了 Python 數據科學堆棧的一些語法(當然,在將模型部署到現實世界之前,我們需要對其進行更全面的評估)。然而,有時需要對模型訓練進行更多定製才能實現給定用例的必要性能。例如,如果我們想動態更新批量大小或有一個條件訓練循環來即時跳過壞示例怎麼辦?一種選擇是將 `Trainer` 並添加必要的更改,但有時從頭開始編寫訓練循環會更簡單。這就是🤗 Accelerate 的用武之地。 + +{/if} + +{#if fw === 'pt'} + +## 用 🤗 Accelerate 訓練 + +我們已經看到了如何使用 `Trainer` ,這可以允許一些自定義。然而,有時我們想要完全控制訓練循環,或者我們想要進行一些奇特的更改。在這種情況下 🤗 Accelerate 是一個不錯的選擇,在本節中,我們將逐步介紹使用它來訓練我們的模型的步驟。為了讓事情變得更有趣,我們還將在訓練循環中添加一些修改。 + + + +由於我們主要對數據科學庫的合理自動填充感興趣,因此對更多使用這些庫的訓練樣本給予更多權重是有意義的。我們可以通過使用關鍵字輕鬆識別這些示例,例如 `plt`、`pd`、`sk`、`fit`和`predict`等關鍵字,我們可以很容易地識別這些示例,這些關鍵字是matplotlib最常用的導入名稱。`Pyplot`, `pandas`和`sklearn`以及後者的擬合/預測模式。如果這些都表示為單個標記,我們可以輕鬆檢查它們是否出現在輸入序列中。標記可能有一個空格前綴,因此我們還將在標記器詞彙表中檢查這些版本。為了驗證它是否有效,我們將添加一個測試token ,該token 應拆分為多個tokens: + +```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' +``` + +太好了,這似乎很好用!我們現在可以編寫一個自定義損失函數,它將輸入序列、logits 和我們剛剛選擇的關​​鍵標記作為輸入。首先,我們需要對齊 logits 和輸入:向右移動一個的輸入序列形成標籤,因為下一個標記是當前標記的標籤。我們可以通過從輸入序列的第二個標記開始標記來實現這一點,因為模型無論如何都不會對第一個標記進行預測。然後我們切斷最後一個 logit,因為我們沒有完整輸入序列之後的標記的標籤。有了這個,我們可以計算每個樣本的損失並計算每個樣本中所有關鍵字的出現次數。最後,我們使用出現次數作為權重計算所有樣本的加權平均值。由於我們不想扔掉所有沒有關鍵字的樣本,我們將權重加1: + +```py +from torch.nn import CrossEntropyLoss +import torch + + +def keytoken_weighted_loss(inputs, logits, keytoken_ids, alpha=1.0): + # Shift so that tokens < n predict n + shift_labels = inputs[..., 1:].contiguous() + shift_logits = logits[..., :-1, :].contiguous() + # Calculate per-token loss + loss_fct = CrossEntropyLoss(reduce=False) + loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1)) + # Resize and average loss per sample + loss_per_sample = loss.view(shift_logits.size(0), shift_logits.size(1)).mean(axis=1) + # Calculate and scale weighting + weights = torch.stack([(inputs == kt).float() for kt in keytoken_ids]).sum( + axis=[0, 2] + ) + weights = alpha * (1.0 + weights) + # Calculate weighted average + weighted_loss = (loss_per_sample * weights).mean() + return weighted_loss +``` + +在我們開始使用這個很棒的新損失函數進行訓練之前,我們需要準備一些東西: + +- 我們需要數據加載器來批量加載數據。 +- 我們需要設置權重衰減參數。 +- 有時我們想要求值,因此將求值代碼包裝在一個函數中是有意義的。 + +讓我們從數據加載器開始。我們只需要將數據集的格式設置為 `"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) +``` + +接下來,我們對參數進行分組,以便優化器知道哪些將獲得額外的權重衰減。通常,所有偏差和 LayerNorm 權重項都不受此限制;以下我們如何做到這一點: + +```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()` 函數我們定期可以獲取損失值和[perplexity](/course/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 章](/course/chapter3) for more details. + + + +現在我們已經發送了我們的 `train_dataloader`到 `accelerator.prepare()` ,我們可以使用它的長度來計算訓練步驟的數量。請記住,我們應該始終在準備好dataloader後執行此操作,因為該方法會改變其長度。我們使用經典線性學習率調度: + +```py +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` 工作文件夾中的對象。如果您尚未登錄,請先登錄 Hugging Face。我們將從我們想要為模型提供的模型 ID 中確定存儲庫名稱(您可以自由地用自己的選擇替換 `repo_name` ;它只需要包含您的用戶名,可以使用`get_full_repo_name()`函數的查看目前的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()` 方法。這將幫助我們在每個 epoch 結束時上傳中間模型。在我們訓練之前,讓我們運行一個快速測試,看看評估函數是否正常工作: + +```py +evaluate() +``` + +```python out +(10.934126853942871, 56057.14453125) +``` + +這些損失和困惑度的值非常高,但這並不奇怪,因為我們還沒有訓練過模型。有了這個,我們已經準備好編寫訓練腳本的核心部分:訓練循環。在訓練循環中,我們遍歷數據加載器並將批次傳遞給模型。有了 logits,我們就可以評估我們的自定義損失函數。我們通過梯度累積步驟的數量來縮放損失,以便在聚合更多步驟時不會產生更大的損失。在我們優化之前,我們還剪輯了梯度以獲得更好的收斂性。最後,每隔幾步,我們就會使用新的 `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=len(train_dataloader) + ): + logits = model(batch["input_ids"]).logits + loss = keytoken_weighted_loss(batch["input_ids"], logits, keytoken_ids) + if step % 100 == 0: + accelerator.print( + { + "lr": get_lr(), + "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 等工具記錄重要指標。向訓練循環添加適當的日誌記錄,以便您始終可以檢查訓練的進行情況。going. + + + +{/if} \ No newline at end of file diff --git a/chapters/zh-TW/chapter7/7.mdx b/chapters/zh-TW/chapter7/7.mdx new file mode 100644 index 000000000..075c1f6ff --- /dev/null +++ b/chapters/zh-TW/chapter7/7.mdx @@ -0,0 +1,1210 @@ + + +# 問答 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +是時候看問答了! 這項任務有多種形式, 但我們將在本節中關注的一項稱為*提取*的問答。問題的答案就在 _給定的文檔_ 之中。 + + + +我們將使用 [SQuAD 數據集](https://rajpurkar.github.io/SQuAD-explorer/) 微調一個BERT模型, 其中包括群眾工作者對一組維基百科文章提出的問題。以下是一個小的測試樣例: + + + + +本節使用的代碼已經上傳到了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 這樣的純編碼器模型往往很擅長提取諸如 "誰發明了 Transformer 架構?"之類的事實性問題的答案。但在給出諸如 "為什麼天空是藍色的?" 之類的開放式問題時表現不佳。在這些更具挑戰性的情況下, T5 和 BART 等編碼器-解碼器模型通常使用以與 [文本摘要](/course/chapter7/5) 非常相似的方式合成信息。如果你對這種類型的*生成式*問答感興趣, 我們建議您查看我們基於 [ELI5 數據集](https://huggingface.co/datasets/eli5) 的 [演示](https://yjernite.github.io/lfqa.html)。 + + + +## 準備數據 + +最常用作抽取式問答的學術基準的數據集是 [SQuAD](https://rajpurkar.github.io/SQuAD-explorer/), 所以這就是我們將在這裡使用的。還有一個更難的 [SQuAD v2](https://huggingface.co/datasets/squad_v2) 基準, 其中包括沒有答案的問題。只要你自己的數據集包含上下文列、問題列和答案列, 你就應該能夠調整以下步驟。 + +### SQuAD 數據集 + +像往常一樣, 我們只需一步就可以下載和緩存數據集, 這要歸功於 `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 處的樣本e: + +```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?' +``` + +我們可以看到, 答案確實可以是我們之前看到的三種可能性之一。 + +### 處理訓練數據 + + + +讓我們從預處理訓練數據開始。困難的部分將是為問題的答案生成標籤, 這將是與上下文中的答案相對應的標記的開始和結束位置。 + +但是, 我們不要超越自己。首先, 我們需要使用分詞器將輸入中的文本轉換為模型可以理解的 ID: + +```py +from transformers import AutoTokenizer + +model_checkpoint = "bert-base-cased" +tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) +``` + +如前所述, 我們將對 BERT 模型進行微調, 但你可以使用任何其他模型類型, 只要它實現了快速標記器即可。你可以在 [this big table](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]' +``` + +然後標籤將成為開始和結束答案的標記的索引, 並且模型的任務是預測輸入中每個標記的開始和結束 logit, 理論標籤如下: + +
+One-hot encoded labels for question answering. + +
+ +在這種情況下, 上下文不會太長, 但是數據集中的一些示例的上下文很長, 會超過我們設置的最大長度(在這種情況下為 384)。正如我們在 [第六章](/course/chapter6/4) 中所看到的, 當我們探索 `question-answering` 管道的內部結構時, 我們將通過從我們的數據集的一個樣本中創建幾個訓練特徵來處理長上下文, 它們之間有一個滑動窗口。 + +要使用當前示例查看其工作原理, 我們可以將長度限制為 100, 並使用 50 個標記的滑動窗口。提醒一下, 我們使用: + +- `max_length` 設置最大長度 (此處為 100) +- `truncation="only_second"` 用於當帶有上下文的問題太長時, 截斷上下文t (位於第二個位置) +- `stride` 設置兩個連續塊之間的重疊標記數 (這裡為 50) +- `return_overflowing_tokens=True` 讓標記器知道我們想要溢出的標記 + +```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]` 標記)。我們還將在答案被截斷的不幸情況下設置這些標籤, 以便我們只有它的開始(或結束)。對於答案完全在上下文中的示例, 標籤將是答案開始的標記的索引和答案結束的標記的索引。 + +數據集為我們提供了上下文中答案的開始字符, 通過添加答案的長度, 我們可以找到上下文中的結束字符。要將它們映射到令牌索引, 我們將需要使用我們在 [第六章](/course/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']) +``` + +如我們所見, 我們取回了通常的輸入 ID、令牌類型 ID 和注意掩碼, 以及我們需要的偏移映射和一個額外的鍵, `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` 是答案開頭的標記索引 (在輸入 ID 中), 並且 `end_position` 是答案結束的標記的索引 (在輸入 ID 中)。 + +為了確定是哪種情況以及標記的位置, 以及(如果相關的話)標記的位置, 我們首先在輸入 ID 中找到開始和結束上下文的索引。我們可以使用標記類型 ID 來執行此操作, 但由於這些 ID 不一定存在於所有模型中 (例如, DistilBERT 不需要它們), 我們將改為使用我們的標記器返回的 `BatchEncoding` 的 `sequence_ids()` 方法。 + +一旦我們有了這些標記索引, 我們就會查看相應的偏移量, 它們是兩個整數的元組, 表示原始上下文中的字符範圍。因此, 我們可以檢測此特徵中的上下文塊是在答案之後開始還是在答案開始之前結束(在這種情況下, 標籤是 `(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) + + # Find the start and end of the context + idx = 0 + while sequence_ids[idx] != 1: + idx += 1 + context_start = idx + while sequence_ids[idx] == 1: + idx += 1 + context_end = idx - 1 + + # If the answer is not fully inside the context, label is (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: + # Otherwise it's the start and end token positions + 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) + + # Find the start and end of the context + idx = 0 + while sequence_ids[idx] != 1: + idx += 1 + context_start = idx + while sequence_ids[idx] == 1: + idx += 1 + context_end = idx - 1 + + # If the answer is not fully inside the context, label is (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: + # Otherwise it's the start and end token positions + 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 個特徵。我們的訓練集現在可以使用了-- 讓我們深入研究驗證集的預處理! + +### 處理驗證數據 + +預處理驗證數據會稍微容易一些, 因為我們不需要生成標籤(除非我們想計算驗證損失, 但這個數字並不能真正幫助我們理解模型有多好)。真正的樂趣是將模型的預測解釋為原始上下文的跨度。為此, 我們只需要存儲偏移映射和某種方式來將每個創建的特徵與它來自的原始示例相匹配。由於原始數據集中有一個 ID 列, 我們將使用該 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) +``` + +I在這種情況下, 我們只添加了幾百個樣本, 因此驗證數據集中的上下文似乎有點短。 + +現在我們已經對所有數據進行了預處理, 我們可以開始訓練了。 + +{#if fw === 'pt'} + +## 使用 `Trainer` API 微調模型 + +這個例子的訓練代碼看起來很像前面幾節中的代碼 -- 最難的是編寫 `compute_metrics()` 函數。由於我們將所有樣本填充到我們設置的最大長度, 因此沒有數據整理器要定義, 所以這個度量計算真的是我們唯一需要擔心的事情。困難的部分是將模型預測後處理為原始示例中的文本範圍; 一旦我們這樣做了, 🤗 Datasets 庫中的指標將為我們完成大部分工作。 + +{:else} + +## 使用 Keras 微調模型 + +這個示例的訓練代碼看起來很像前幾節中的代碼, 但是計算指標將是唯一的挑戰。因為我們將所有的樣本填充到我們設置的最大長度, 所以不需要定義數據整理器, 所以這個度量計算實際上是我們唯一需要擔心的事情。困難的部分是將模型預測後處理成原始例子中的文本範圍; 一旦我們完成了這些, 🤗 Datasets 庫中的指標將為我們完成大部分工作。 + +{/if} + +### 後處理 + +{#if fw === 'pt'} + + + +{:else} + + + +{/if} + +該模型將在輸入ID中為答案的開始和結束位置輸出Logit, 正如我們在探索 [`question-answering` pipeline](/course/chapter6/4) 時看到的那樣。後處理步驟將類似於我們在那裡所做的, 所以這裡是我們採取的行動的快速提醒: + +- 我們屏蔽了與上下文之外的標記相對應的開始和結束 logits。 +- 然後, 我們使用 softmax 將開始和結束 logits 轉換為概率。 +- 我們通過取對應的兩個概率的乘積來給每個 `(start_token, end_token)` 組合賦值。 +- 我們尋找產生有效答案的最高分數的配對 (例如, `start_token` 低於 `end_token`)。 + +在這裡, 我們將稍微改變這個過程, 因為我們不需要計算實際分數 (只是預測的答案)。這意味著我們可以跳過 softmax 步驟。為了更快, 我們也不會對所有可能的 `(start_token, end_token)` 對進行評分, 而只會對對應於最高 `n_best` 的那些對進行評分 (使用 `n_best=20`)。由於我們將跳過 softmax, 因此這些分數將是 logit 分數, 並且將通過取 start 和 end logits 的總和來獲得 (而不是乘積, 因為規則 \\(\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 數組的預測, 我們獲取開始和結束 logits 並將它們轉換為該格式 + +```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` 開始 logits 和結束 logits 的 logit 分數, 不包括以下的位置: + +- 一個不在上下文中的答案 +- 長度為負的答案 +- 答案太長 (我們將可能性限制在 `max_answer_length=30`) + +一旦我們為一個示例獲得了所有可能的答案, 我們只需選擇一個具有最佳 logit 分數的答案: + +```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: + # Skip answers that are not fully in the context + if offsets[start_index] is None or offsets[end_index] is None: + continue + # Skip answers with a length that is either < 0 or > 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"]}) +``` + +預測答案的最終格式是我們將使用的度量標準所期望的格式。像往常一樣, 我們可以在 🤗 Datasets 庫的幫助下加載它: + +```python +from datasets import load_metric + +metric = load_metric("squad") +``` + +該指標期望我們上面看到的格式的預測答案 (一個字典列表, 其中一個鍵用於示例 ID, 一個鍵用於預測文本) 和以下格式的理論答案 (一個字典列表, 一個鍵示例的 ID 和可能答案的一鍵): + +```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} +``` + +同樣, 考慮到根據 [its paper](https://arxiv.org/abs/1910.01108v2), 在 SQuAD 上微調的 DistilBERT 在整個數據集上的得分分別為 79.1 和 86.9, 這是相當不錯的。 + +{#if fw === 'pt'} + +現在, 讓我們把剛才所做的一切放在 `compute_metrics()` 函數中, 我們將在 `Trainer` 中使用它。通常, `compute_metrics()` 函數只接收一個包含 logits 和 labels 的元組 `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 = [] + + # Loop through all features associated with that example + 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: + # Skip answers that are not fully in the context + if offsets[start_index] is None or offsets[end_index] is None: + continue + # Skip answers with a length that is either < 0 or > 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) + + # Select the answer with the best score + 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} +``` + +看起來不錯! 現在讓我們用它來微調我們的模型。 + +### 微調模型 + +{#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` 的子類來完成這一任務(你可以在 [question answering example script](https://github.com/huggingface/transformers/blob/master/examples/pytorch/question-answering/trainer_qa.py)中找到一種方法), 但這對於本節來說有點太長了。相反, 我們只會在訓練結束時評估模型, 並在下面的"自定義訓練循環"向你展示如何進行常規評估。 + +T這確實時 `Trainer` API 顯示其侷限性和 🤗 Accelerate 庫的亮點所在: 根據特定用例自定義類可能很痛苦, 但調整完全公開的訓練循環很容易。 + +來看看我們的 `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, +) +``` + +我們之前已經看到了其中的大部分: 我們設置了一些超參數 (比如學習率、訓練的 epoch 數和一些權重衰減), 並表明我們希望在每個 epoch 結束時保存模型, 跳過評估, 並將我們的結果上傳到模型中心。我們還使用 `fp16=True` 啟用混合精度訓練, 因為它可以在最近的 GPU 上很好地加快訓練速度。 + +{:else} + +現在, 我們可以創建我們的 TF 數據集。這次我們可以使用簡單的默認數據整理器: + +```python +from transformers import DefaultDataCollator + +data_collator = DefaultDataCollator(return_tensors="tf") +``` + +現在我們像往常一樣創建數據集。 + +```python +tf_train_dataset = train_dataset.to_tf_dataset( + columns=[ + "input_ids", + "start_positions", + "end_positions", + "attention_mask", + "token_type_ids", + ], + collate_fn=data_collator, + shuffle=True, + batch_size=16, +) +tf_eval_dataset = validation_dataset.to_tf_dataset( + columns=["input_ids", "attention_mask", "token_type_ids"], + 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 + +# 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_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) + +# Train in mixed-precision 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) + +# We're going to do validation afterwards, so no validation mid-training +model.fit(tf_train_dataset, callbacks=[callback], epochs=num_train_epochs) +``` + +{/if} + +請注意, 在進行訓練時, 每次保存模型時 (這裡是每個 epoch) 它都會在後臺上傳到 Hub。這樣, 如有必要, 你將能夠在另一臺機器上恢復訓練。整個培訓需要一段時間 (在 Titan RTX 上需要一個多小時), 因此您可以喝杯咖啡或重讀課程中您發現在進行過程中更具挑戰性的部分內容。另請注意, 一旦第一個 epoch 完成, 你將看到一些權重上傳到 Hub, 你可以開始在其頁面上使用你的模型。 + +{#if fw === 'pt'} + +一旦訓練完成, 我們終於可以評估我們的模型(並祈禱我們沒有把所有的計算時間都花在任何事情上)。`Trainer` 的 `predict()` 方法將返回一個元組, 其中第一個元素將是模型的預測 (這裡是帶有開始和結束 logits 的對)。我們將其發送給 `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} + +一旦訓練完成, 我們終於可以評估我們的模型(並祈禱我們沒有把所有的計算時間都花在任何事情上)。我們的 `model` 的 `predict()` 方法將負責獲取預測, 並且由於我們之前已經完成了定義 `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} + +在這個階段, 你可以使用模型中心上的推理小部件來測試模型並與您的朋友、家人和最喜歡的寵物分享。你已經成功地微調了一個問答任務的模型 -- 恭喜! + + + +✏️ **輪到你了!** 嘗試另一種模型架構, 看看它是否在此任務上表現更好! + + + +{#if fw === 'pt'} + +如果你想更深入地瞭解訓練循環, 我們現在將向你展示如何使用 🤗 Accelerate 來做同樣的事情。 + +## 自定義訓練循環 + +現在讓我們來看一下完整的訓練循環, 這樣您就可以輕鬆地自定義所需的部分。它看起來很像 [第三章](/course/chapter3/4) 中的訓練循環, 除了評估循環。我們將能夠定期評估模型, 因為我們不再受 `Trainer` 類的限制。 + +### 為訓練做準備 + +首先, 我們需要從我們的數據集中構建 `DataLoader`。我們將這些數據集的格式設置為 `"torch"`, 並刪除模型未使用的驗證集中的列。然後, 我們可以使用 Transformers 提供的 `default_data_collator` 作為 `collate_fn`, 並打亂訓練集, 但不打亂驗證集58: + +```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()` 方法。請記住, 如果您想在 Colab 筆記本中的 TPU 上進行訓練, 您需要將所有這些代碼移動到一個訓練函數中, 並且不應該執行任何實例化 `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` 對象。如果你尚未登錄, 請先登錄 Hugging Face Hub。我們將根據我們想要為模型提供的模型 ID 確定存儲庫名稱 (隨意用你自己的選擇替換 `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) +``` + +我們現在可以通過調用 `repo.push_to_hub()` 方法上傳我們保存在 `output_dir` 中的任何內容。這將幫助我們在每個 epoch 結束時上傳中間模型。 + +## 訓練循環 + +我們現在準備編寫完整的訓練循環。在定義了一個進度條來跟蹤訓練進行後, 循環分為三個部分: + +- 訓練本身是對 `train_dataloader` 的經典迭代, 前向傳遞模型, 然後反向傳遞和優化器步驟。 +- 在計算中, 我們在將 `start_logits` 和 `end_logits` 的所有值轉換為 NumPy 數組之前, 收集它們的所有值。評估循環完成後,我們將連接所有結果。請注意, 我們需要截斷, 因為 `Accelerator` 可能在最後添加了一些示例, 以確保我們在每個過程中擁有相同數量的示例。 +- 保存和上傳, 這裡我們先保存模型和分詞器, 然後調用 `repo.push_to_hub()`。正如我們之前所做的那樣, 我們使用參數 `blocking=False` 來告訴 🤗 Hub 庫推入一個異步進程。這樣, 訓練正常繼續, 並且這個 (長) 指令在後臺執行。 + +這是訓練循環的完整代碼: + +```py +from tqdm.auto import tqdm +import torch + +progress_bar = tqdm(range(num_training_steps)) + +for epoch in range(num_train_epochs): + # Training + model.train() + for step, batch in enumerate(train_dataloader): + outputs = model(**batch) + loss = outputs.loss + accelerator.backward(loss) + + optimizer.step() + lr_scheduler.step() + optimizer.zero_grad() + progress_bar.update(1) + + # Evaluation + model.eval() + 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) + + # Save and upload + accelerator.wait_for_everyone() + unwrapped_model = accelerator.unwrap_model(model) + unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) + if accelerator.is_main_process: + tokenizer.save_pretrained(output_dir) + repo.push_to_hub( + commit_message=f"Training in progress epoch {epoch}", blocking=False + ) +``` + +如果這是您第一次看到使用 🤗 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} + +## 使用微調模型 + +我們已經向您展示瞭如何將我們在模型中心微調的模型與推理小部件一起使用。要在 `pipeline` 中本地使用它, 你只需要指定模型標識符: + +```py +from transformers import pipeline + +# Replace this with your own checkpoint +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/zh-TW/chapter7/8.mdx b/chapters/zh-TW/chapter7/8.mdx new file mode 100644 index 000000000..e2e651d3f --- /dev/null +++ b/chapters/zh-TW/chapter7/8.mdx @@ -0,0 +1,17 @@ +# 精通自然語言處理 + +如果你在課程中做到了這一步,恭喜你——你現在擁有了用 🤗 Transformers 和 Hugging Face 生態系統解決(幾乎)任何 NLP 任務所需的所有知識和工具! + +我們見過很多不同的數據整理器,所以我們製作了這個小視頻來幫助您找到每個任務使用哪一個: + + + +在完成核心 NLP 任務的快速入門後,您應該: + +* 瞭解哪種架構(編碼器、解碼器或編碼器-解碼器)最適合每個任務 +* 瞭解預訓練和微調語言模型之間的區別 +* 瞭解如何使用 `Trainer` API 和 🤗 Accelerate 或 TensorFlow 和 Keras 的分佈式訓練功能來訓練 Transformer 模型,具體選擇那一種方法取決於您所需要完成的任務。 +* 瞭解 ROUGE 和 BLEU 等指標在文本生成任務中的意義和侷限性 +* 知道如何在 Hub 上和使用 🤗 Transformers 中的“管道”與您的微調模型進行交互 + +儘管掌握了所有這些知識,但總有一天你會遇到代碼中的困難錯誤,或者對如何解決特定的 NLP 問題有疑問。幸運的是,Hugging Face 社區隨時為您提供幫助!在這部分課程的最後一章中,我們將探討如何調試 Transformer 模型並有效地尋求幫助。 \ No newline at end of file diff --git a/chapters/zh-TW/chapter7/9.mdx b/chapters/zh-TW/chapter7/9.mdx new file mode 100644 index 000000000..4852756bd --- /dev/null +++ b/chapters/zh-TW/chapter7/9.mdx @@ -0,0 +1,311 @@ + + + + +# End-of-chapter quiz + +Let's test what you learned in this chapter! + +### 1.下列哪個任務可以被框定為令牌分類問題? + + +### 2.令牌分類預處理的哪一部分與其他預處理管道不同? +-100 作為我們希望在丟失時忽略的令牌的標籤。" + }, + { + text: "因為互聯網上有大量的文本", + explain: "的確如此! 但這並不是唯一的區別。", + correct: true + } + ]} +/> + +### 3.當我們對標記分類問題中的單詞進行標記並希望標記時,會出現什麼問題? +-100 標記為 < code > ,以便在丟失時忽略它們。" + }, + { + text: "每個單詞可以產生幾個標記,所以我們最終得到的標記比標籤多。", + explain: "這是主要的問題,我們需要將原始標籤與標記對齊。", + correct: true + }, + { + text: "因為目標是按順序排列的文本問題", + explain: "這是不正確的; 我們需要儘可能多的標籤,否則我們的模型就會出錯。" + } + ]} +/> + +### 4.“領域適應”是什麼意思? + + +### 5.掩碼語言建模問題中的標籤是什麼? + + +### 6.這些任務中的哪一個可以看作是一個順序到順序的問題? +-100 來標記特殊標記。", + explain: "這絕對是一個從序列到序列的問題。你能發現另一個嗎?", + correct: true + }, + { + text: "修正我侄子/朋友發來的信息,使它們用正確的英語", + explain: "這是一種翻譯問題,所以肯定是一個順序到順序的任務。但這不是唯一正確的答案!", + correct: true + } + ]} +/> + +### 7.對於序列到序列的問題,預處理數據的正確方法是什麼? + input = ... 和 < code > target = ... 。", + explain: "這可能是我們將來添加的一個 API,但現在不可能。" + }, + { + text: "輸入和目標都必須在對標記器的兩個獨立調用中進行預處理。", + explain: "不,這是在訓練一個模型; 這裡沒有適應性。" + }, + { + text: "因為我們在訓練之後計算度量", + explain: "不是在序列分類問題; 目標也是文本,我們需要轉換成數字!" + }, + { + text: "輸入必須發送到標記器,目標也是如此,但需要使用特殊的上下文管理器。", + explain: "沒錯,標記器需要由上下文管理器放入目標模式。", + correct: true + } + ]} +/> + +{#if fw === 'pt'} + +### 8.為什麼序列到序列問題有一個特定的“培訓者”子類? +-100 的標籤", + explain: "這根本不是習慣性的損失,而是損失總是通過計算得到的。" + }, + { + text: "當您擁有大量可用數據時,即使有一個經過預先訓練的模型可以處理這些數據", + explain: "沒錯。 Sequence-to-sequence models' predictions are often run using the generate() method.", + correct: true + }, + { + text: "文本是作為單詞給出的,所以我們只需要應用子詞的標記。", + explain: "< code > Trainer 並不關心這些,因為它們以前已經被預處理過。" + }, + { + text: "因為我們在序列到序列問題中使用了兩個模型", + explain: "我們確實在某種程度上使用了兩種模型,編碼器和解碼器,但是它們被組合在一個模型中。" + } + ]} +/> + +{:else} + +### 9.為什麼在 Transformer 模型上調用“ compile ()”時通常不需要指定損失? + input = ... 和 < code > target = ... 。", + explain: "沒錯!", + correct: true + }, + { + text: "因為我們在訓練之後計算指標", + explain: "這可以被定義為一個從序列到序列的問題,儘管這不是唯一正確的答案。" + }, + { + text: "因為損失是在“ model.fit ()”中指定的", + explain: "不,損失函數在運行‘ model.com pile ()’時是固定的,不能在‘ model.fit ()’中更改。" + } + ]} +/> + +{/if} + +### 10.你應該在什麼時候預先訓練一個新的模型? + + +### 11.為什麼在大量的文本上預先訓練一個語言模型是很容易的呢? + + +### 12.問答任務的預處理數據時,主要的挑戰是什麼? + + +### 13.問題回答中的後處理通常是怎樣進行的? + diff --git a/chapters/zh-TW/chapter8/1.mdx b/chapters/zh-TW/chapter8/1.mdx new file mode 100644 index 000000000..525a21b2e --- /dev/null +++ b/chapters/zh-TW/chapter8/1.mdx @@ -0,0 +1,12 @@ +# 介紹 + +既然您知道如何使用🤗 Transformers處理最常見的NLP任務,您應該能夠開始自己的項目了!在本章中,我們將探討遇到問題時該怎麼做。您將學習如何成功調試代碼或訓練,以及如果自己無法解決問題,如何向社區尋求幫助。如果您認為您在其中一個擁抱人臉庫中發現了錯誤,我們將向您展示報告它的最佳方式,以便儘快解決問題。 + +更準確地說,在本章中,您將學習: + +- 出現錯誤時要做的第一件事 +- 如何在網上尋求幫助[forums](https://discuss.huggingface.co/) +- 如何調試您的訓練管道 +- 如何寫一個好問題 + +當然,所有這些都與 🤗 Transformers 或Hugging Face 生態無關;本章的經驗教訓適用於大多數開源項目! diff --git a/chapters/zh-TW/chapter8/2.mdx b/chapters/zh-TW/chapter8/2.mdx new file mode 100644 index 000000000..d41597cd7 --- /dev/null +++ b/chapters/zh-TW/chapter8/2.mdx @@ -0,0 +1,364 @@ +# 出現錯誤時該怎麼辦 + + + +在本節中, 我們將研究當你嘗試從新調整的 Transformer 模型生成預測時可能發生的一些常見錯誤。這將為 [第四節](/course/chapter8/section4) 做準備, 我們將探索如何調試訓練階段本身。 + + + +我們為這一節準備了一個 [模板模型庫](https://huggingface.co/lewtun/distilbert-base-uncased-finetuned-squad-d5716d28), 如果你想運行本章中的代碼, 你首先需要將模型複製到你的 [Hugging Face Hub](https://huggingface.co) 賬號。為此, 首先通過在 Jupyter 筆記本中運行以下任一命令來登錄: + +```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(): + # Clone the repo and extract the local path + template_repo_id = "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28" + commit_hash = "be3eaffc28669d7932492681cd5f3e8905e358b4" + template_repo_dir = snapshot_download(template_repo_id, revision=commit_hash) + # Create an empty repo on the Hub + model_name = template_repo_id.split("/")[1] + create_repo(model_name, exist_ok=True) + # Clone the empty repo + new_repo_id = get_full_repo_name(model_name) + new_repo_dir = model_name + repo = Repository(local_dir=new_repo_dir, clone_from=new_repo_id) + # Copy files + copy_tree(template_repo_dir, new_repo_dir) + # Push to Hub + repo.push_to_hub() +``` + +現在, 當你調用 `copy_repository_template()` 時, 它將在你的帳戶下創建模板存儲庫的副本。 + +## 從 🤗 Transformers 調試管道 + +要開始我們調試 Transformer 模型的奇妙世界之旅, 請考慮以下場景: 你正在與一位同事合作進行問答項目, 以幫助電子商務網站的客戶找到有關消費品的答案。你的同事給你發了一條消息, 比如: + +> 嗨! 我剛剛使用了抱抱臉課程的 [第七章](/course/chapter7/7) 中的技術進行了一個實驗, 並在 SQuAD 上獲得了一些很棒的結果! 我認為我們可以用這個模型作為我們項目的起點。Hub上的模型ID是 "lewtun/distillbert-base-uncased-finetuned-squad-d5716d28"。請隨意測試一下 :) + +你首先想到的是使用 🤗 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. +
+ +這些報告中包含很多信息, 所以讓我們一起來看看關鍵部分。首先要注意的是, 應該從 _從底部到頂部_ 讀取回溯。如果你習慣於從上到下閱讀英文文本, 這可能聽起來很奇怪,但它反映了這樣一個事實,即回溯顯示了在下載模型和標記器時 `管道` 進行的函數調用序列。(查看 [第二章](/course/chapter2) 瞭解有關 `pipeline` 如何在後臺工作的更多詳細信息。) + + + +🚨 看到Google Colab 回溯中 "6 幀" 周圍的藍色框了嗎? 這是 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/) 搜索欄中 (是的, 真的!)。你很可能不是第一個遇到錯誤的人, 這是找到社區中其他人發佈的解決方案的好方法。例如, 在 Stack Overflow 上搜索 `OSError: Can't load config for` 給出了幾個[hits](https://stackoverflow.com/search?q=OSError%3A+Can%27t+load+config+for+), 可能是用作解決問題的起點。 + + + +第一個建議是要求我們檢查模型ID是否真的正確, 所以首先要做的就是複製標識符並將其粘貼到Hub的搜索欄中: + +
+The wrong model name. +
+ +嗯, 看起來我們同事的模型確實不在 Hub 上... 啊哈, 但是模型名稱中有一個錯字! DistilBERT 的名稱中只有一個 "l", 所以讓我們解決這個問題並尋找 "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28": + +
+The right model name. +
+ +好的, 這很受歡迎。現在讓我們嘗試使用正確的模型 ID 再次下載模型: + +```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 +""" +``` + +啊, 再次挫敗 -- 歡迎來到機器學習工程師的日常生活! 因為我們已經修復了模型 ID, 所以問題一定出在存儲庫本身。訪問 🤗 Hub 上存儲庫內容的一種快速方法是通過 `huggingface_hub` 庫的 `list_repo_files()` 方法: + +```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。在這種情況下, 問題似乎很容易解決: 我們可以要求他們添加文件, 或者, 因為我們可以從模型 ID 中看到使用的預訓練模型是 [`distilbert-base-uncased`](https://huggingface.co/distilbert-base-uncased), 我們可以下載此模型的配置並將其推送到我們的存儲庫以查看是否可以解決問題。讓我們試試看。使用我們在 [第二章](/course/chapter2) 中學習的技術, 我們使用 `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_ , 並從下到上閱讀。錯誤消息的最後一行通常包含定位問題根源所需的信息。 +- 如果最後一行沒有包含足夠的信息, 請按照您的方式進行回溯, 看看您是否可以確定源代碼中發生錯誤的位置。 +- 如果沒有任何錯誤消息可以幫助您調試問題, 請嘗試在線搜索類似問題的解決方案。 +- `huggingface_hub` +// 🤗 Hub? +庫提供了一套工具, 你可以使用這些工具與 Hub 上的存儲庫進行交互和調試。 + +現在你知道如何調試管道, 讓我們看一下模型本身前向傳遞中的一個更棘手的示例。 + +## 調試模型的前向傳遞 + +儘管 `pipeline` 對於大多數需要快速生成預測的應用程序來說非常有用, 有時您需要訪問模型的 logits (例如, 如果您有一些想要應用的自定義後處理)。為了看看在這種情況下會出現什麼問題, 讓我們首先從 `pipeline` 中獲取模型和標記器: + +```python +tokenizer = reader.tokenizer +model = reader.model +``` + +接下來我們需要一個問題, 那麼讓我們看看是否支持我們最喜歡的框架: + +```python +question = "Which frameworks can I use?" +``` + +正如我們在 [第七章](/course/chapter7) 中學習的, 我們需要採取的通常步驟是對輸入進行標記化, 提取開始和結束標記的對數, 然後解碼答案範圍: + +```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 +# Get the most likely beginning of answer with the argmax of the score +answer_start = torch.argmax(answer_start_scores) +# Get the most likely end of answer with the argmax of the score +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' object has no attribute 'size'`, 我們可以看到一個 `-->` 箭頭指向 `model(**inputs)` 中出現問題的行。你可以使用 Python 調試器以交互方式調試它, 但現在我們只需打印出一部分 `inputs`, 看看我們有什麼: + +```python +inputs["input_ids"][:5] +``` + +```python out +[101, 2029, 7705, 2015, 2064] +``` + +這當然看起來像一個普通的 Python `list`, 但讓我們仔細檢查一下類型: + +```python +type(inputs["input_ids"]) +``` + +```python out +list +``` + +是的, 這肯定是一個 Python `list`。那麼出了什麼問題呢? 回憶 [第二章](/course/chapter2) 🤗 Transformers 中的 `AutoModelForXxx` 類在 _tensors_ 上運行(PyTorch或者or 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 上搜索錯誤消息給出了很多相關的 [hits](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 +# Get the most likely beginning of answer with the argmax of the score +answer_start = torch.argmax(answer_start_scores) +# Get the most likely end of answer with the argmax of the score +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/zh-TW/chapter8/3.mdx b/chapters/zh-TW/chapter8/3.mdx new file mode 100644 index 000000000..8fa909369 --- /dev/null +++ b/chapters/zh-TW/chapter8/3.mdx @@ -0,0 +1,166 @@ +# 在論壇上尋求幫助 + + + + + +[Hugging Face 論壇](https://discuss.huggingface.co) 是從開源團隊和更廣泛的 Hugging Face 社區獲得幫助的好地方。以下是論壇某一天的主頁面: + +
+The Hugging Face forums. +
+ +在左側,您可以看到各種主題分組的所有類別,而右側顯示了最新的主題。主題是包含標題、類別和描述的帖子;它與我們在創建自己的數據集時看到的 GitHub 問題格式非常相似[Chapter 5](/course/chapter5).顧名思義,[Beginners](https://discuss.huggingface.co/c/beginners/5)類別主要面向剛開始使用 Hugging Face 庫和生態系統的人。歡迎對任何庫提出任何問題,無論是調試一些代碼還是尋求有關如何做某事的幫助。 (也就是說,如果您的問題特別涉及某個圖書館,您可能應該前往論壇上的相應圖書館類別。) + +同樣,the [Intermediate](https://discuss.huggingface.co/c/intermediate/6)和[Research](https://discuss.huggingface.co/c/research/7)類別用於更高級的問題,例如關於圖書館或您想討論的一些很酷的新 NLP 研究。 + +當然,我們也應該提到[Course](https://discuss.huggingface.co/c/course/20)類別,您可以在其中提出與 Hugging Face 課程相關的任何問題! + +選擇類別後,您就可以編寫第一個主題了。 你可以找一些[guidelines](https://discuss.huggingface.co/t/how-to-request-support/3128) 在有關如何執行此操作的論壇中,在本節中,我們將看看構成一個好的主題的一些功能。 + +## 寫一篇好的論壇帖子 + +作為一個運行示例,假設我們試圖從 Wikipedia 文章生成嵌入表示以創建自定義搜索引擎。像往常一樣,我們按如下方式加載分詞器和模型: + +```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)的一整段進行嵌入表示(熱知識:變形金剛的英文就是 Transformers ,而現在 Transformers 作為一個 🤗 Python 庫也被越來越多人熟知): + +```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 +``` + +呃,我們遇到了一個問題——錯誤信息比我們看到的要神秘得多[section 2](/course/chapter8/section2)!我們無法確定完整回溯的正面或反面,因此我們決定轉向 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. 這個話題直接用一種有點苛刻的語氣標記了幾個人。 + +像這樣的主題不太可能很快得到答案(如果他們得到了答案),那麼讓我們看看如何改進它。我們將從選擇一個好標題的第一個問題開始。 + +### 選擇描述性標題 + +如果您想就代碼中的錯誤尋求幫助,一個好的經驗法則是在標題中包含足夠的信息,以便其他人可以快速確定他們是否認為他們可以回答您的問題。在我們的運行示例中,我們知道正在引發的異常的名稱,並有一些提示它是在模型的前向傳遞中觸發的,我們調用 **model(**inputs)** .為了傳達這一點,一個可能的標題可能是: + +> 自動建模正向傳遞中的索引錯誤的來源? + +這個標題告訴讀者在哪裡你認為錯誤來自,如果他們遇到了 **IndexError** 在此之前,他們很有可能知道如何調試它。當然,標題可以是您想要的任何內容,也可以是其他變體,例如: + +> 為什麼我的模型會產生索引錯誤? + +也可以。現在我們有了一個描述性的標題,讓我們來看看改善主體。 + +### 設置代碼段的格式 + +如:也可以。現在我們有了一個描述性的標題,讓我們來看看改善身體。在 IDE 中閱讀源代碼已經夠難的了,但是當將代碼複製粘貼為純文本時就更難了!幸運的是,Hugging Face 論壇支持使用 Markdown,因此您應該始終用三個反引號 (```) 將代碼塊括起來,以便更容易閱讀。讓我們這樣做來美化錯誤消息——在我們這樣做的時候,讓我們讓正文比我們的原始版本更有禮貌: + +
+Our revised forum topic, with proper code formatting. +
+ +正如您在屏幕截圖中看到的,將代碼塊括在反引號中會將原始文本轉換為格式化代碼,並帶有顏色樣式!另請注意,單個反引號可用於格式化內聯變量,就像我們所做的那樣 **distilbert-base-uncased** .這個主題看起來好多了,如果幸運的話,我們可能會在社區中找到可以猜測錯誤是什麼的人。然而,與其依靠運氣,不如讓我們在其完整的血腥細節中包含回溯,讓生活更輕鬆! + +### 包括完整的回溯 + +由於回溯的最後一行通常足以調試您自己的代碼,因此很容易在您的主題中提供它以“節省空間”。雖然本意是好的,但這實際上使它更難供其他人調試問題,因為回溯中較高的信息也非常有用。因此,一個好的做法是複製並粘貼所有的回溯,同時確保它的格式很好。由於這些回溯可能會很長,有些人更喜歡在解釋了源代碼之後再展示它們。我們開工吧。現在,我們的論壇主題如下所示: + +
+Our example forum topic, with the complete traceback. +
+ +這提供了更多信息,細心的讀者可能會指出問題似乎是由於回溯中的這一行而傳遞了一個長輸入: + +> 令牌索引序列長度長於為此模型指定的最大序列長度 (583 > 512)。 + +但是,通過提供觸發錯誤的實際代碼,我們可以讓他們更輕鬆。我們現在就這樣做。 + +### 提供可重複的示例 + +如果您曾經嘗試過調試其他人的代碼,那麼您可能首先嚐試重現他們報告的問題,以便您可以開始通過回溯來查明錯誤。在論壇上獲得(或提供)幫助時沒有什麼不同,所以如果你能提供一個重現錯誤的小例子真的很有幫助。有一半的時間,簡單地完成這個練習將幫助你找出問題所在。在任何情況下,我們的示例缺少的部分是顯示輸入我們提供給模型的。這樣做會為我們提供類似於以下完整示例的內容: + +
+The final version of our forum topic. +
+ +該主題現在包含相當多的信息,並且它的編寫方式更可能吸引社區的注意力並獲得有用的答案。有了這些基本指南,您現在可以創建很棒的主題來找到您的 🤗 Transformers問題的答案! + diff --git a/chapters/zh-TW/chapter8/4.mdx b/chapters/zh-TW/chapter8/4.mdx new file mode 100644 index 000000000..4d7b3c1c1 --- /dev/null +++ b/chapters/zh-TW/chapter8/4.mdx @@ -0,0 +1,787 @@ + + +# 調試訓練管道 + + + +你已經編寫了一個漂亮的腳本來訓練或微調給定任務的模型,盡職盡責地遵循 [Chapter 7](/course/chapter7) 中的建議。 但是當你啟動命令 `trainer.train()` 時,可怕的事情發生了:你得到一個錯誤😱! 或者更糟糕的是,一切似乎都很好,訓練運行沒有錯誤,但生成的模型很糟糕。 在本節中,我們將向您展示如何調試此類問題。 + +## 調試訓練管道 + + + +當您在 `trainer.train()` 中遇到錯誤時,它可能來自多個來源,因為 `Trainer` 通常會將很多東西放在一起組合運行。 它將datasets轉換為dataloaders,因此問題可能出在datasets中,或者在嘗試將datasets的元素一起批處理時出現問題。 然後它需要準備一批數據並將其提供給模型,因此問題可能出在模型代碼中。 之後,它會計算梯度並執行優化器,因此問題也可能出在您的優化器中。 即使訓練一切順利,如果您的評估指標有問題,評估期間仍然可能出現問題。 + +調試 `trainer.train()` 中出現的錯誤的最佳方法是手動檢查整個管道,看看哪裡出了問題。 錯誤通常很容易解決。 + +為了證明這一點,我們將使用以下腳本(嘗試)在 [MNLI 數據集](https://huggingface.co/datasets/glue)上微調 DistilBERT 模型: + +```py +from datasets import load_dataset, load_metric +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 = load_metric("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' +``` + +### 檢查數據 + +這是不言而喻的,如果你的數據被破壞,“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, load_metric +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 = load_metric("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)' +``` + +查看traceback,我們可以看到錯誤發生在數據整理步驟中: + +```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` 也會記錄它丟棄的列。 + +我們通過解碼檢查了輸入 ID 是否正確。 接下來是檢查 `attention_mask`: + +```py +tokenizer.decode(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] +``` + +由於我們沒有在預處理中應用填充,這看起來非常自然。 為確保該注意掩碼沒有問題,讓我們檢查它與輸入 ID 的長度是否相同: + +```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 +``` + +與輸入 ID 一樣,這是一個本身並沒有真正意義的數字。 正如我們之前看到的,整數和標籤名稱之間的映射存儲在數據集相應 *feature* 的 `names` 屬性中: + +```py +trainer.train_dataset.features["label"].names +``` + +```python out +['entailment', 'neutral', 'contradiction'] +``` + +所以`1`表示`neutral`,表示我們上面看到的兩句話並不矛盾,也沒有包含關係。 這似乎是正確的! + +我們這裡沒有令牌類型 ID,因為 DistilBERT 不需要它們; 如果您的模型中有一些,您還應該確保它們正確匹配輸入中第一句和第二句的位置。 + + + +✏️ **輪到你了!** 檢查訓練數據集的第二個元素是否正確。 + + + +我們在這裡只對訓練集進行檢查,但您當然應該以同樣的方式仔細檢查驗證集和測試集。 + +現在我們知道我們的數據集看起來不錯,是時候檢查訓練管道的下一步了。 + +### 從 datasets 到 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) +``` + +檢查trackback的最後一個堆棧的輸出應該足以給你一個線索,但讓我們做更多的挖掘。 批處理創建過程中的大多數問題是由於將示例整理到單個批處理中而出現的,因此在有疑問時首先要檢查的是您的 DataLoader 正在使用什麼 collate_fn: + +```py +data_collator = trainer.get_train_dataloader().collate_fn +data_collator +``` + +```python out + Dict[str, Any]> +``` + +所以,目前使用的是 `default_data_collator`,但這不是我們在這種情況下想要的。 我們希望將示例填充到批處理中最長的句子,這是由 `DataCollatorWithPadding` 整理器完成的。 而這個數據收集器應該是默認被 `Trainer` 使用的,為什麼這裡沒有使用呢? + +答案是因為我們沒有將 `tokenizer` 傳遞給 `Trainer`,所以它無法創建我們想要的 `DataCollatorWithPadding`。 在實踐中,您應該明確地傳遞您想要使用的數據整理器,以確保避免這些類型的錯誤。 讓我們調整我們的代碼來做到這一點: + +```py +from datasets import load_dataset, load_metric +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 = load_metric("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 錯誤通常很難調試。 我們稍後會看到如何解決這個問題,但首先讓我們完成對批處理創建的分析。 + +如果您確定您的數據整理器是正確的,則應嘗試將其應用於數據集的幾個樣本: + +```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)]) +``` + +如果錯誤仍然存在,您應該能夠手動調試數據整理器內部以確定具體的問題。 + +現在我們已經調試了批處理創建過程,是時候將數據傳遞給模型了! + +### 檢查模型 + +您應該能夠通過執行以下命令來獲得一個批次的數據: + +```py +for batch in trainer.get_train_dataloader(): + break +``` + +如果您在notebook中運行此代碼,您可能會收到與我們之前看到的類似的 CUDA 錯誤,在這種情況下,您需要重新啟動notebook並重新執行最後一個片段,而不運行 `trainer.train()` 行.這是關於 CUDA 錯誤的第二個最煩人的事情:它們會破壞您的Cuda內核,而且無法恢復。它們最煩人的事情是它們很難調試。 + +這是為什麼?它與 GPU 的工作方式有關。它們在並行執行大量操作方面非常有效,但缺點是當其中一條指令導致錯誤時,您不會立即知道。只有當程序在 GPU 上調用多個進程的同步處理時,它才會意識到出現問題,因此錯誤實際上是在與創建它的原因無關的地方引發的。例如,如果我們查看之前的trackback,錯誤是在向後傳遞期間引發的,但我們會在一分鐘內看到它實際上源於向前傳遞中的某些東西。 + +那麼我們如何調試這些錯誤呢?答案很簡單:我們沒有。除非您的 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”(因此與我們之前所說的反向傳播無關)。 更準確地說,我們可以看到是Target 2 造成了錯誤,所以這是檢查模型標籤數量的好時機: + +```python +trainer.model.config.num_labels +``` + +```python out +2 +``` + +有兩個標籤,只允許 0 和 1 作為目標,但是根據錯誤信息我們得到一個 2。得到一個 2 實際上是正常的:如果我們記得我們之前提取的標籤名稱,有三個,所以我們有索引 0 , 1 和 2 在我們的數據集中。 問題是我們沒有告訴我們的模型,它應該創建三個標籤。 所以讓我們解決這個問題! + +```py +from datasets import load_dataset, load_metric +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 = load_metric("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) +``` + +下一步是回到 GPU 並檢查一切是否仍然有效: + +```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) +``` + +如果仍然出現錯誤,請確保重新啟動notebook並僅執行腳本的最後一個版本。 + +### 執行一個優化器步驟 + +現在我們知道我們可以構建實際通過模型檢查的成批次的數據,我們已經為訓練管道的下一步做好準備:計算梯度並執行優化步驟。 + +第一部分只是在 loss 上調用 `backward()` 方法: + +```py +loss = outputs.loss +loss.backward() +``` + +在這個階段很少出現錯誤,但如果確實出現錯誤,請返回 CPU 以獲取有用的錯誤消息。 + +要執行優化步驟,我們只需要創建 `optimizer` 並調用它的 `step()` 方法: + +```py +trainer.create_optimizer() +trainer.optimizer.step() +``` + +同樣,如果您在 `Trainer` 中使用默認優化器,則在此階段您不應該收到錯誤,但如果您有自定義優化器,則可能會出現一些問題需要在這裡調試。 如果您在此階段遇到奇怪的 CUDA 錯誤,請不要忘記返回 CPU。 說到 CUDA 錯誤,前面我們提到了一個特殊情況。 現在讓我們來看看。 + +### 處理 CUDA out-of-memory錯誤 + +每當您收到以`RuntimeError: CUDA out of memory`開頭的錯誤消息時,這表明您的 GPU 內存不足。 這與您的代碼沒有直接關聯,並且它可能發生在運行良好的代碼中。 此錯誤意味著您試圖在 GPU 的內部存儲器中放入太多東西,這導致了錯誤。 與其他 CUDA 錯誤一樣,您需要重新啟動內核才能再次運行訓練。 + +要解決這個問題,您只需要使用更少的 GPU 空間——這往往說起來容易做起來難。 首先,確保您沒有同時在 GPU 上運行兩個模型(當然,除非您的問題需要這樣做)。 然後,您可能應該減少batch的大小,因為它直接影響模型的所有中間輸出的大小及其梯度。 如果問題仍然存在,請考慮使用較小版本的模型。 + + + +在課程的下一部分中,我們將介紹更先進的技術,這些技術可以幫助您減少內存佔用並讓您微調最大的模型。 + + + +### 評估模型 + +現在我們已經解決了代碼的所有問題,一切都很完美,訓練應該可以順利進行,對吧? 沒那麼快! 如果你運行 `trainer.train()` 命令,一開始一切看起來都不錯,但過一會兒你會得到以下信息: + +```py +# This will take a long time and error out, so you shouldn't run this cell +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) +``` + +稍等一會兒,錯誤出現,在評估階段結束時,如果我們查看trackback,我們會看到: + +```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()` 函數的問題。 它需要一個帶有 logits 和標籤的元組作為 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 +``` + +我們得到同樣的錯誤,所以問題肯定出在那個函數上。 如果我們回顧它的代碼,我們會發現它只是將“預測”和“真實的標籤”轉發到“metric.compute()”。 那麼這種方法有問題嗎? 並不真地。 讓我們快速瀏覽一下形狀: + +```py +predictions.shape, labels.shape +``` + +```python out +((8, 3), (8,)) +``` + +我們的預測仍然是 logits,而不是實際的預測,這就是metrics返回這個(有點模糊)錯誤的原因。 修復很簡單; 我們只需要在 `compute_metrics()` 函數中添加一個 argmax: + +```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, load_metric +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 = load_metric("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()`! + + + +## 在訓練期間調試靜默(沒有任何錯誤提示)錯誤 + +我們可以做些什麼來調試一個沒有錯誤地完成但沒有得到好的結果的訓練? 我們會在這裡給你一些提示,但請注意,這種調試是機器學習中最難的部分,並且沒有神奇的答案。 + +### 檢查您的數據(再次!) + +只有在理論上可以從您的數據中學到任何東西時,您的模型才會學到一些東西。 如果存在損壞數據的錯誤或標籤是隨機屬性的,那麼您很可能不會在數據集上獲得任何知識。 因此,始終首先仔細檢查您的解碼輸入和標籤,然後問自己以下問題: + +- 解碼後的數據是否可以理解? +- 你認同這些標籤嗎? +- 有沒有一個標籤比其他標籤更常見? +- 如果模型預測隨機的答案/總是相同的答案,那麼loss/評估指標應該是多少? + + + +⚠️ 如果您正在進行分佈式訓練,請在每個過程中打印數據集的樣本,並三次檢查您是否得到相同的結果。 一個常見的錯誤是在數據創建中有一些隨機性來源,這使得每個進程都有不同版本的數據集。 + + + +查看您的數據後,查看模型的一些預測並對其進行解碼。 如果模型總是預測同樣的事情,那可能是因為你的數據集偏向一個類別(針對分類問題); 過採樣稀有類等技術可能會有所幫助。 + +如果您在初始模型上獲得的loss/評估指標與您期望的隨機預測的loss/評估指標非常不同,請仔細檢查您的loss或評估指標的計算方式,因為那裡可能存在錯誤。 如果您使用最後添加的多個loss,請確保它們具有相同的規模。 + +當您確定您的數據是完美的時,您可以通過一個簡單的測試來查看模型是否能夠對其進行訓練。 + +### 在一批上過度擬合你的模型 + +過度擬合通常是我們在訓練時儘量避免的事情,因為這意味著模型沒有學習識別我們想要的一般特徵,而只是記住了訓練樣本。 在這種情況下,一遍又一遍地嘗試在一個批次上訓練您的模型是一個很好的測試,可以檢查您的問題是否可以通過您嘗試訓練的模型來解決。 它還將幫助您查看您的初始學習率是否太高。 + +一旦你定義了你的 `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”,因為獲得的模型可能無法在您的完整數據集上恢復和學習有用的東西。 + + + +### 在你有第一個基線之前不要調整任何東西 + +超參數調優總是被強調為機器學習中最難的部分,但這只是幫助您在指標上有所收穫的最後一步。 大多數情況下,`Trainer` 的默認超參數可以很好地為您提供良好的結果,因此在您獲得超出數據集基線的東西之前,不要開始進行耗時且昂貴的超參數搜索 . + +一旦你有一個足夠好的模型,你就可以開始稍微調整一下。 不要嘗試使用不同的超參數啟動一千次運行,而是比較一個超參數的不同值的幾次運行,以瞭解哪個影響最大。 + +如果您正在調整模型本身,不要嘗試任何您無法合理證明的事情。 始終確保您返回過擬合測試以驗證您的更改沒有產生任何意外後果。 + +### 請求幫忙 + +希望您會在本節中找到一些可以幫助您解決問題的建議,但如果不是這樣,請記住您可以隨時在 [論壇](https://discuss.huggingface.co/) 上向社區提問。 + +以下是一些可能有用的額外資源: + +- [“作為工程最佳實踐工具的再現性”](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p),作者:Joel Grus +- [“神經網絡調試清單”](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) 作者:Cecelia Shao +- [“如何對機器學習代碼進行單元測試”](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) by Chase Roberts +- [“訓練神經網絡的秘訣”](http://karpathy.github.io/2019/04/25/recipe/)作者:Andrej Karpathy + +當然,並不是你在訓練神經網絡時遇到的每一個問題都是你自己的錯! 如果您在 🤗 Transformers 或 🤗 Datasets 庫中遇到看起來不正確的內容,您可能遇到了錯誤。 你應該告訴我們這一切,在下一節中,我們將準確解釋如何做到這一點。 diff --git a/chapters/zh-TW/chapter8/4_tf.mdx b/chapters/zh-TW/chapter8/4_tf.mdx new file mode 100644 index 000000000..64a80718f --- /dev/null +++ b/chapters/zh-TW/chapter8/4_tf.mdx @@ -0,0 +1,489 @@ + + +# Debugging the training pipeline + + + +你已經編寫了一個漂亮的腳本來訓練或微調給定任務的模型,盡職盡責地遵循 [第七章](/course/chapter7) 中的建議。 但是當你啟動命令 `model.fit()` 時,可怕的事情發生了:你得到一個錯誤😱! 或者更糟糕的是,一切似乎都很好,訓練運行沒有錯誤,但生成的模型很糟糕。 在本節中,我們將向您展示如何調試此類問題。 + +## Debugging the training pipeline + + + +The problem when you encounter an error in `model.fit()` is that it could come from multiple sources, as training usually brings together a lot of things that you've been working on up until that point. The problem could be something wrong in your dataset, or some issue when trying to batch elements of the datasets together. Or it could be something wrong in the model code, or your loss function or optimizer. And even if everything goes well for training, something could still go wrong during the evaluation if there is a problem with your metric. + +The best way to debug an error that arises in `model.fit()` is to manually go through this whole pipeline to see where things went awry. The error is then often very easy to solve. + +To demonstrate this, we will use the following script that (tries to) fine-tune a DistilBERT model on the [MNLI dataset](https://huggingface.co/datasets/glue): + +當您在 `model.fit()` 中遇到錯誤時,問題在於它可能來自多個來源,因為訓練通常會彙集很多您在此之前一直在做的事情。 問題可能是您的數據集中有問題,或者是在嘗試將數據集的元素批處理在一起時出現問題。 或者模型代碼、損失函數或優化器中可能有問題。 即使訓練一切順利,如果您的指標有問題,評估期間仍然可能出現問題。 + +調試 `model.fit()` 中出現的錯誤的最佳方法是手動檢查整個管道,看看哪裡出了問題。 錯誤通常很容易解決。 + +為了證明這一點,我們將使用以下腳本(嘗試)在 [MNLI 數據集](https://huggingface.co/datasets/glue)上微調 DistilBERT 模型: + +```py +from datasets import load_dataset, load_metric +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 年 11 月之後閱讀這門課程並且它仍在繼續,那麼請在推特上 @carrigmat 上發送憤怒的推文,直到他修復它。 + +然而,更嚴重的問題是我們得到了一個徹底的錯誤。 它真的非常長: + +```python out +ValueError: No gradients provided for any variable: ['tf_distil_bert_for_sequence_classification/distilbert/embeddings/word_embeddings/weight:0', '...'] +``` + +這意味著什麼? 我們試圖訓練我們的數據,但我們沒有梯度? 這很令人困惑。 我們甚至不知道該如何開始調試類似的東西? 當你得到的錯誤並不能立即表明問題出在哪裡時,最好的解決方案通常是按順序檢查所有內容,確保在每個階段一切看起來都是正確的。 當然,開始的地方總是... + +### 檢查您的數據 + +這是不言而喻的,但如果您的數據已損壞,Keras 將無法為您修復它。 所以首先,你需要看看你的訓練集中有什麼。 + +儘管查看 `raw_datasets` 和 `tokenized_datasets` 很誘人,但我們強烈建議您在數據將要進入模型的地方直接查看數據。 這意味著應該從您使用 `to_tf_dataset()` 函數創建的 `tf.data.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 訓練 Transformer 模型時會遇到的最常見問題之一。我們的模型都可以在內部計算損失,但要做到這一點,需要在輸入字典中傳遞標籤。這是當我們沒有為 `compile()` 指定損失值時使用的損失。另一方面,Keras 通常希望標籤與輸入字典分開傳遞,如果你不這樣做,損失計算通常會失敗。 + +問題現在變得更清楚了:我們傳遞了一個“損失”參數,這意味著我們要求 Keras 為我們計算損失,但我們將標籤作為模型的輸入傳遞,而不是放在 Keras 期望的地方的!我們需要二選一:要麼使用模型的內部損失並將標籤保留在原處,要麼繼續使用 Keras 損失,但將標籤移動到 Keras 期望的位置。為簡單起見,讓我們採用第一種方法。將對 `compile()` 的調用更改為: + +```py +model.compile(optimizer="adam") +``` + +現在我們將使用模型的內部損失,這個問題應該解決了! + + + +✏️ **輪到你了!** 作為我們解決其他問題後的可選挑戰,你可以嘗試回到這一步,讓模型使用原始 Keras 計算的損失而不是內部損失。 您需要將 `"labels"` 添加到 `to_tf_dataset()` 的 `label_cols` 參數,以確保正確輸出標籤,這將為您提供梯度——但我們指定的損失還有一個問題 . 訓練仍然會遇到這個問題,學習會非常緩慢,並且會在多次訓練損失時達到穩定狀態。 你能弄清楚它是什麼嗎? + +一個 ROT13 編碼的提示,如果你卡住了:Vs lbh ybbx ng gur bhgchgf bs FrdhraprPynffvsvpngvba zbqryf va Genafsbezref, gurve svefg bhgchg vf `ybtvgf`。 榮格納 ybtvgf? + +第二個提示:Jura lbh fcrpvsl bcgvzvmref, npgvingvbaf 是 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 +``` + +Oh no. + +`nan` 不是一個非常令人開心的損失值。 儘管如此,我們已經檢查了我們的數據,它看起來還不錯。 如果這不是問題,我們下一步該去哪裡? 顯而易見的下一步是... + +### 檢查你的模型 + +`model.fit()` 是 Keras 中一個非常方便的函數,但它為您做了很多事情,這使得準確找到問題發生的位置變得更加棘手。 如果您正在調試您的模型,一個真正有用的策略是隻將一個批次傳遞給模型,並詳細查看該批次的輸出。 如果模型拋出錯誤,另一個非常有用的提示是使用 `run_eagerly=True` `compile()` 模型。 這會使它變慢很多,但它會使錯誤消息更容易理解,因為它們會準確地指出問題發生在模型代碼的哪個位置。 + +不過,目前我們還不需要 `run_eagerly`。 讓我們通過模型運行我們之前得到的“批處理”,看看輸出是什麼樣子的: + +```py +model(batch) +``` + +```python out +TFSequenceClassifierOutput(loss=, logits=, hidden_states=None, attentions=None) +``` + +嗯,這很棘手。一切都是`nan`!但這很奇怪,不是嗎?我們所有的 logits 怎麼會變成“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) +``` + +*現在*我們到了某個地方! 我們的 logits 中沒有 `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]) +``` + +讓我們看看這些來自樣本的輸入id: + +```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。這是一個非常明顯的提示。 當我們的標籤為 2 時,我們會得到loss為 `nan`,這表明這是檢查模型中標籤數量的好時機: + +```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's,我們的損失正在減少......有點。 如果你觀察一段時間,你可能會開始有點不耐煩,因為損失值一直居高不下。 讓我們在這裡停止訓練並嘗試考慮可能導致此問題的原因。 在這一點上,我們很確定數據和模型都沒有問題,但是我們的模型並沒有很好地學習。 還剩下什麼? 是時候... + +### 檢查你的超參數 + +如果你回頭看上面的代碼,你可能根本看不到任何超參數,除了 `batch_size`,這似乎不是罪魁禍首。不過,不要被迷惑;總是有超參數,如果你看不到它們,那只是意味著你不知道它們的設置是什麼。特別要記住關於 Keras 的一個關鍵點:如果您使用字符串設置損失函數、優化器或激活函數,_它的所有參數都將設置為它們的默認值_。這意味著即使為此使用字符串非常方便,但在這樣做時您應該非常小心,因為它很容易對您隱藏關鍵的事情。 (任何嘗試上述方式的人都應該仔細注意這一事實。) + +在這種情況下,我們在哪裡設置了帶有字符串的參數?我們最初使用字符串設置損失,但我們不再這樣做了。但是,我們正在使用字符串設置優化器。難道這對我們隱瞞了什麼?讓我們看看[關於它的一些討論](https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/Adam)。 + +這裡有什麼需要注意的嗎?沒錯——學習率!當我們只使用字符串“adam”時,我們將獲得默認的學習率,即 0.001,即 1e-3。這對於transormer模型來說太高了!一般來說,我們建議您的模型嘗試 1e-5 和 1e-4 之間的學習率;這比我們在這裡實際使用的值小 10X 到 100X 之間。聽起來這可能是一個主要問題,所以讓我們嘗試減少它。為此,我們需要導入實際的“優化器”對象。當我們這樣做的時候,讓我們從檢查點重新初始化模型,以防高學習率的訓練損壞了它的權重: + +```python +from tensorflow.keras.optimizers import Adam + +model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint) +model.compile(optimizer=Adam(5e-5)) +``` + + + +💡您還可以從🤗 Transformers 中導入 `create_optimizer()` 函數,這將為您提供具有正確權重衰減以及學習率預熱和學習率衰減的 AdamW 優化器。 此優化器通常會產生比使用默認 Adam 優化器獲得的結果稍好一些的結果。 + + + +現在,我們可以嘗試使用新的、改進後的學習率來擬合模型: + +```python +model.fit(train_dataset) +``` + +```python out +319/24543 [..............................] - ETA: 16:07 - loss: 0.9718 +``` + +現在我們的損失真的在某個地方! 訓練終於看起來奏效了。 這裡有一個教訓:當你的模型正在運行但損失沒有下降,並且你確定你的數據沒問題時,檢查學習率和權重衰減等超參數是個好主意。 將其中任何一個設置得太高很可能導致訓練在高損失值下“停滯”。 + +## 其他潛在問題 + +我們已經涵蓋了上面腳本中的問題,但您可能會遇到其他幾個常見錯誤。 讓我們看一個(非常不完整的)列表。 + +### 處理內存不足錯誤 + +內存不足的跡象是“分配張量時出現 OOM”之類的錯誤——OOM 是“內存不足”的縮寫。 在處理大型語言模型時,這是一個非常常見的危險。 如果遇到這種情況,一個好的策略是將批量大小減半並重試。 但請記住,有些型號*非常*大。 例如,全尺寸 GPT-2 的參數為 1.5B,這意味著您將需要 6 GB 的內存來存儲模型,另外需要 6 GB 的內存用於梯度下降! 無論您使用什麼批量大小,訓練完整的 GPT-2 模型通常需要超過 20 GB 的 VRAM,而只有少數 GPU 擁有。 像“distilbert-base-cased”這樣更輕量級的模型更容易運行,訓練也更快。 + + + +在課程的下一部分中,我們將介紹更先進的技術,這些技術可以幫助您減少內存佔用並讓您微調最大的模型。 + + + +### TensorFlow 🦛餓餓 + +您應該注意的 TensorFlow 的一個特殊怪癖是,它會在您加載模型或進行任何訓練後立即為自己分配 *所有 * GPU 內存,然後根據需要分配該內存。這與其他框架的行為不同,例如 PyTorch,後者根據 CUDA 的需要分配內存,而不是在內部進行。 TensorFlow 方法的一個優點是,當您耗盡內存時,它通常會給出有用的錯誤,並且它可以從該狀態恢復而不會導致整個 CUDA 內核崩潰。但也有一個重要的缺點:如果你同時運行兩個 TensorFlow 進程,那麼**你將度過一段糟糕的時光**。 + +如果您在 Colab 上運行,則無需擔心這一點,但如果您在本地運行,這絕對是您應該小心的事情。特別要注意,關閉筆記本選項卡並不一定會關閉該筆記本!您可能需要選擇正在運行的筆記本(帶有綠色圖標的筆記本)並在目錄列表中手動關閉它們。任何使用 TensorFlow 的正在運行的筆記本仍可能佔用大量 GPU 內存,這意味著您啟動的任何新筆記本都可能會遇到一些非常奇怪的問題。 + +如果您開始運行之前正確的代碼卻收到有關 CUDA、BLAS 或 cuBLAS 的錯誤,這通常是罪魁禍首。您可以使用類似 `nvidia-smi` 的命令來檢查 - 當您關閉或重新啟動當前筆記本時,您的大部分內存是否空閒,或者是否仍在使用中?如果它仍在使用中,則有其他東西在佔用它! + +### 檢查您的數據(再次!) + +只有在理論上可以從您的數據中學到任何東西時,您的模型才會學到一些東西。 如果存在損壞數據的錯誤或標籤是隨機屬性的,那麼您很可能不會在數據集上獲得任何知識。這裡一個有用的工具是`tokenizer.decode()`。 這會將 `input_ids` 轉換回字符串,因此您可以查看數據並查看您的訓練數據是否正在教授您希望它教授的內容。 例如,像我們上面所做的那樣從 `tf.data.Dataset` 中獲取 `batch` 後,您可以像這樣解碼第一個元素: + +```py +input_ids = batch["input_ids"].numpy() +tokenizer.decode(input_ids[0]) +``` + +Then you can compare it with the first label, like so: + +```py +labels = batch["labels"].numpy() +label = labels[0] +``` +一旦您可以像這樣查看您的數據,您可以問自己以下問題: + +- 解碼後的數據是否可以理解? +- 你認同這些標籤嗎? +- 有沒有一個標籤比其他標籤更常見? +- 如果模型預測隨機的答案/總是相同的答案,那麼loss/評估指標應該是多少? + +查看您的數據後,查看模型的一些預測並對其進行解碼。 如果模型總是預測同樣的事情,那可能是因為你的數據集偏向一個類別(針對分類問題); 過採樣稀有類等技術可能會有所幫助。 + +如果您在初始模型上獲得的loss/評估指標與您期望的隨機預測的loss/評估指標非常不同,請仔細檢查您的loss或評估指標的計算方式,因為那裡可能存在錯誤。 如果您使用最後添加的多個loss,請確保它們具有相同的規模。 + +當您確定您的數據是完美的時,您可以通過一個簡單的測試來查看模型是否能夠對其進行訓練。 + +### 在一批上過度擬合你的模型 + +過度擬合通常是我們在訓練時儘量避免的事情,因為這意味著模型沒有學習識別我們想要的一般特徵,而只是記住了訓練樣本。 但是,一遍又一遍地嘗試在一個批次上訓練您的模型是一個很好的測試,可以檢查您構建的問題是否可以通過您嘗試訓練的模型來解決。 它還將幫助您查看您的初始學習率是否太高。 + +一旦你定義了你的“模型”,這樣做真的很容易; 只需獲取一批訓練數據,然後將該“批次”視為您的整個數據集,並在其上fit大量epoch: + +```py +for batch in train_dataset: + break + +# Make sure you have run model.compile() and set your optimizer, +# and your loss/metrics if you're using them + +model.fit(batch, epochs=20) +``` + + + +💡 如果您的訓練數據不平衡,請確保構建一批包含所有標籤的訓練數據。 + + + +生成的模型在“批次”上應該有接近完美的結果,損失迅速下降到 0(或您正在使用的損失的最小值)。 + +如果你沒有設法讓你的模型獲得這樣的完美結果,這意味著你構建問題或數據的方式有問題,所以你應該修復它。 只有當你設法通過過擬合測試時,你才能確定你的模型實際上可以學到一些東西。 + + + +⚠️ 在此測試之後,您將不得不重新創建您的模型和“Trainer”,因為獲得的模型可能無法在您的完整數據集上恢復和學習有用的東西。 + + + +### 在你有第一個基線之前不要調整任何東西 + +超參數調整總是被強調為機器學習中最難的部分,但這只是幫助您在指標上獲得一點點提升的最後一步。 例如將默認的 Adam 學習率 1e-3 與 Transformer 模型一起使用,當然會使學習進行得非常緩慢或完全停止,但大多數時候“合理”的超參數,例如從 1e-5 到 5e-5 的學習率,會很好地給你帶來好的結果。因此,在您獲得超出數據集基線的東西之前,不要開始進行耗時且昂貴的超參數搜索。 + +一旦你有一個足夠好的模型,你就可以開始稍微調整一下。 不要嘗試使用不同的超參數啟動一千次運行,而是比較一個超參數的不同值的幾次運行,以瞭解哪個影響最大。 + +如果您正在調整模型本身,不要嘗試任何您無法合理證明的事情。 始終確保您返回過擬合測試以驗證您的更改沒有產生任何意外後果。 + +### 請求幫忙 + +希望您會在本節中找到一些可以幫助您解決問題的建議,但如果不是這樣,請記住您可以隨時在 [論壇](https://discuss.huggingface.co/) 上向社區提問。 + +以下是一些可能有用的額外資源: + +- [“作為工程最佳實踐工具的再現性”](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p),作者:Joel Grus +- [“神經網絡調試清單”](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) 作者:Cecelia Shao +- [“如何對機器學習代碼進行單元測試”](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) by Chase Roberts +- [“訓練神經網絡的秘訣”](http://karpathy.github.io/2019/04/25/recipe/)作者:Andrej Karpathy + +當然,並不是你在訓練神經網絡時遇到的每一個問題都是你自己的錯! 如果您在 🤗 Transformers 或 🤗 Datasets 庫中遇到看起來不正確的內容,您可能遇到了錯誤。 你應該告訴我們這一切,在下一節中,我們將準確解釋如何做到這一點。 diff --git a/chapters/zh-TW/chapter8/5.mdx b/chapters/zh-TW/chapter8/5.mdx new file mode 100644 index 000000000..378a8ccb9 --- /dev/null +++ b/chapters/zh-TW/chapter8/5.mdx @@ -0,0 +1,85 @@ +# 如何寫一個好問題 + + + +當您遇到 Hugging Face 庫中的一個看起來不正確的東西時,您一定要告訴我們,以便我們可以修復它(就此而言,任何開源庫也是如此)。如果您不能完全確定錯誤是在您自己的代碼中還是在我們的某個庫中,那麼首先要檢查的是[forums](https://discuss.huggingface.co/).社區會幫助你解決這個問題,Hugging Face 團隊也會密切關注那裡的討論。 + + + +當您確定手頭有錯誤時,第一步是構建一個最小的可重現示例。 +## 創建一個最小的可重現示例 + +隔離產生錯誤的代碼段非常重要,因為 Hugging Face 團隊中沒有人是魔術師(目前),他們無法修復他們看不到的東西。顧名思義,最小的可重現示例應該是可重現的。這意味著它不應依賴於您可能擁有的任何外部文件或數據。嘗試用一些看起來像真實值的虛擬值替換您正在使用的數據,但仍然會產生相同的錯誤。 + + + +🚨🤗 Transformers 存儲庫中的許多問題都沒有解決,因為用於複製它們的數據不可訪問。 + + + +一旦你有一些自包含的東西,你可以嘗試將它減少到更少的代碼行,構建我們所謂的最小的可重複示例.雖然這需要你做更多的工作,但如果你提供一個漂亮的、簡短的錯誤重現器,你幾乎可以保證得到幫助和修復。 + +如果您覺得足夠舒服,請檢查發生錯誤的源代碼。您可能會找到問題的解決方案(在這種情況下,您甚至可以提出拉取請求來修復它),但更一般地說,這可以幫助維護人員在閱讀您的報告時更好地理解來源。 + +## 填寫問題模板 + +當您提交問題時,您會注意到有一個模板需要填寫。我們將按照[🤗 Transformers issues](https://github.com/huggingface/transformers/issues/new/choose)在這裡,但是如果您在另一個存儲庫中報告問題,則需要相同類型的信息。不要將模板留空:花時間填寫它可以最大限度地提高您獲得答案和解決問題的機會。 + +通常,在提交問題時,請始終保持禮貌。這是一個開源項目,因此您使用的是免費軟件,沒有人有任何義務幫助您。您可能會在您的問題中包含您認為合理的批評,但是維護人員很可能會認為它很糟糕並且不會急於幫助您。確保你閱讀了[code of conduct](https://github.com/huggingface/transformers/blob/master/CODE_OF_CONDUCT.md)項目的。 + +### 包括您的環境信息 + +🤗 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** 命令從筆記本單元執行它,然後在問題的開頭複製並粘貼結果。 + +### 標記人員 + +通過輸入標記人員 **@** 其次是他們的 GitHub 句柄將向他們發送通知,以便他們會看到您的問題並可能會更快地回覆。適度使用它,因為如果您標記的人沒有直接鏈接,他們可能不喜歡收到通知。如果您查看了與您的錯誤相關的源文件,您應該在您認為對您的問題負責的行中標記最後一個進行更改的人(您可以通過查看 GitHub 上的所述行找到此信息,選擇它,然後單擊“查看 git blame”)。 + +否則,模板會提供要標記的人的建議。一般來說,不要標記超過三個人! + +### 包含一格可重複的示例 + +如果您已經設法創建了一個產生錯誤的獨立示例,那麼現在是包含它的時候了!鍵入一行包含三個反引號,後跟 **python** , 像這樣: + +``` +```python +``` + +然後粘貼您的最小可重現示例並鍵入一個帶有三個反引號的新行。這將確保您的代碼格式正確。如果您沒有設法創建可重現的示例,請以清晰的步驟解釋您是如何解決問題的。如果可以,請包含指向錯誤所在的 Google Colab 筆記本的鏈接。你分享的信息越多,維護者就越有能力回覆你。在所有情況下,您都應該複製並粘貼您收到的整個錯誤消息。如果您在 Colab 中工作,請記住,堆棧跟蹤中的某些幀可能會自動摺疊,因此請確保在複製之前展開它們。與代碼示例一樣,將該錯誤消息放在兩行之間,並帶有三個反引號,因此格式正確。 + +### 描述預期行為 + +用幾行解釋你期望得到什麼,以便維護人員完全掌握問題。這部分通常很明顯,所以應該用一句話來形容,但在某些情況下,您可能有很多話要說。 + +## 然後什麼? + +提交您的問題後,請確保快速檢查一切是否正常。如果您犯了錯誤,您可以編輯問題,或者如果您發現問題與您最初的想法不同,甚至可以更改其標題。如果你沒有得到答案,就沒有必要對人進行 ping 操作。如果幾天內沒有人幫助您,很可能沒有人能理解您的問題。不要猶豫,回到可重現的例子。你能不能讓它更短更切題?如果您在一週內沒有得到答覆,您可以留言溫和地尋求幫助,特別是如果您已編輯問題以包含有關該問題的更多信息。 + diff --git a/chapters/zh-TW/chapter8/6.mdx b/chapters/zh-TW/chapter8/6.mdx new file mode 100644 index 000000000..e6aa7e608 --- /dev/null +++ b/chapters/zh-TW/chapter8/6.mdx @@ -0,0 +1,7 @@ +# 第2部分完成! + +恭喜,您已經完成了課程的第二部分!我們正在積極研究第三個,所以訂閱我們的[newsletter](https://huggingface.curated.co/)以確保您不會錯過它的發佈。 + +。您現在應該能夠處理一系列 NLP 任務,並對它們進行微調或預訓練模型。不要忘記與社區分享您的結果[Model Hub](https://huggingface.co/models). + +我們迫不及待地想看看您將利用所獲得的知識構建什麼! diff --git a/chapters/zh-TW/chapter8/7.mdx b/chapters/zh-TW/chapter8/7.mdx new file mode 100644 index 000000000..de910a9f4 --- /dev/null +++ b/chapters/zh-TW/chapter8/7.mdx @@ -0,0 +1,190 @@ + + +# 章末測評 + +讓我們測試一下你在本章學到的東西! + +### 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) +``` + +以下哪項可能是論壇主題標題尋求幫助的好選擇? + + 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 包含有關計算環境的詳細信息是個好主意? + \ No newline at end of file diff --git a/chapters/zh-TW/chapter9/1.mdx b/chapters/zh-TW/chapter9/1.mdx new file mode 100644 index 000000000..26099ac12 --- /dev/null +++ b/chapters/zh-TW/chapter9/1.mdx @@ -0,0 +1,36 @@ +# Gradio 簡介 + +在本章中,我們將學習如何為您的機器學習構建**交互式演示**模型。 + +為什麼首先要為您的機器學習模型構建演示或 GUI?演示可以帶來: + +- **機器學習開發人員**可以輕鬆地向包括非技術團隊或客戶在內的廣大受眾展示他們的工作 +- **研究人員**更輕鬆地重現機器學習模型和行為 +- **質量測試人員**或**最終用戶**更容易識別和調試模型的故障點 +- **不同的用戶**發現模型中的算法偏差 + +我們將使用 Gradio 庫為我們的模型構建演示。 Gradio 允許您完全使用 Python 為任何機器學習模型構建、自定義和共享基於 Web 的演示。 + +以下是一些使用 Gradio 構建的機器學習演示示例: + +* 一個**草圖識別**模型,它接收草圖並輸出它認為正在繪製的標籤: + + + +* 一個抽取式**問題回答**模型,它接受上下文段落和一個任務並輸出一個結果和一個概率分數(我們在[第7章](/course/chapter7/7)中討論了這種模型): + + + +* 一個**背景去除**模型,它接收圖像並輸出去除背景的圖像: + + + +本章分為兩個部分,包括_概念_和_應用程序_。在您瞭解每個部分的概念後,您將應用它來構建特定類型的演示,範圍從圖像分類到語音識別。當你讀完本章時,你將能夠用幾行 Python 代碼構建這些演示(以及更多!)。 + +👀 點擊 Hugging Face Spaces 以查看機器學習社區構建的許多機器學習演示的最新示例! + +## Gradio 方塊派對🥳 + +如果你想充分利用本章中的知識,就加入 Gradio 積木派對吧!這是由 Hugging Face 於**5 月 16 日至 31 日**舉辦的社區活動。在此活動中,您將使用 Gradio 構建酷炫的機器學習演示,並參與贏取 Hugging Face 禮物和獎品! + +查看 [活動描述](https://github.com/AK391/community-events/blob/main/gradio-blocks/README.md) 可以瞭解如何參與的詳細信息 - 我們迫不及待地想看看你構建的🤗演示! diff --git a/chapters/zh-TW/chapter9/2.mdx b/chapters/zh-TW/chapter9/2.mdx new file mode 100644 index 000000000..b465850a7 --- /dev/null +++ b/chapters/zh-TW/chapter9/2.mdx @@ -0,0 +1,112 @@ +# 構建你的第一個演示 + + + +讓我們從安裝 Gradio 開始吧! 由於它是一個 Python 包,只需運行: + +`$ pip install gradio ` + +您可以在任何地方運行 Gradio,無論是從您最喜歡的 Python IDE、Jupyter notebook 還是 Google Colab 🤯! +所以無論你在哪裡運行 Python,都可以安裝 Gradio! + +讓我們從一個簡單的“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”的簡單函數,但它通常可以是 *any* Python 函數。 例如,在機器學習應用程序中,此函數將*調用模型以對輸入進行預測*並返回輸出。 +- 然後,我們創建一個帶有三個參數的 Gradio `Interface`,`fn`、`inputs` 和 `outputs`。 這些參數定義了預測函數,以及我們想要的輸入和輸出組件的_type_。 在我們的例子中,兩個組件都是簡單的文本框。 +- 然後我們在我們創建的 `Interface` 上調用 `launch()` 方法。 + +如果你運行這段代碼,下面的界面會自動出現在 Jupyter/Colab notebook 中,或者在瀏覽器中彈出 **[http://localhost:7860](http://localhost:7860/)** 如果運行 從一個腳本。 + + + +立即嘗試使用您自己的姓名或其他輸入來使用此 GUI! + +您會注意到,在這個 GUI 中,Gradio 自動推斷輸入參數的名稱 (`name`)並將其應用為文本框頂部的標籤。 如果你想改變它怎麼辦?或者,如果您想以其他方式自定義文本框? 在這種情況下,您可以實例化一個表示輸入組件的類對象。 + +看看下面的例子: + +```py +import gradio as gr + + +def greet(name): + return "Hello " + name + + +# We instantiate the Textbox class +textbox = gr.Textbox(label="Type your name here:", placeholder="John Doe", lines=2) + +gr.Interface(fn=greet, inputs=textbox, outputs="text").launch() +``` + + + +在這裡,我們創建了一個帶有標籤、佔位符和一組行數的輸入文本框。您可以對輸出文本框執行相同的操作,但我們現在將其保留。 + +我們已經看到,只需幾行代碼,Gradio 就可以讓您圍繞任何具有任何類型輸入或輸出的函數創建一個簡單的界面。 在本節中,我們從一個簡單的文本框開始,但在接下來的部分中,我們將介紹其他類型的輸入和輸出。 現在讓我們看看在 Gradio 應用程序中包含一些 NLP。 + + +## 🤖 包括模型預測 + +現在讓我們構建一個簡單的界面,讓您可以演示像 GPT-2 這樣的**文本生成**模型。 + +我們將使用 🤗 Transformers 中的 `pipeline()` 函數加載我們的模型。 +如果您需要快速複習,您可以返回到 [第 1 章中的那個部分](/course/chapter1/3#text-generation)。 + +首先,我們定義一個接受文本提示並返回文本完成的預測函數: + +```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. +``` + +現在我們有了一個生成預測的函數,我們可以像之前一樣創建和啟動一個“接口”: + +```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/zh-TW/chapter9/3.mdx b/chapters/zh-TW/chapter9/3.mdx new file mode 100644 index 000000000..b05f6a556 --- /dev/null +++ b/chapters/zh-TW/chapter9/3.mdx @@ -0,0 +1,167 @@ +# 瞭解接口類 + + + +在本節中,我們將仔細研究 `Interface` 類,並瞭解用於創建其的主要參數。 + +## 如何創建接口 + +您會注意到 `Interface` 類有 3 個必需參數: + +`Interface(fn, inputs, outputs, ...)` + +這些參數是: + + - `fn`: 由 Gradio 接口包裝的預測函數。 該函數可以接受一個或多個參數並返回一個或多個值 + - `inputs`: 輸入組件類型。 Gradio 提供了許多預構建的組件,例如`"image"` 或`"mic"`。 + - `outputs`: 輸出組件類型。 同樣,Gradio 提供了許多預構建的組件,例如 `“圖像”`或“標籤”`。 + +有關組件的完整列表,[請參閱 Gradio 文檔](https://gradio.app/docs)。 每個預構建的組件都可以通過實例化該組件對應的類來定製。 + +例如,正如我們在 [上一節](/course/chapter9/2) 中看到的,您可以傳入一個 `Textbox(lines=7, label="Prompt")` 組件來創建一個包含 7 行和一個標籤的文本框,而不是將 `"textbox"` 傳遞給 `inputs` 參數。 +讓我們看另一個例子,這次是一個 `Audio` 組件。 + +## 一個帶音頻的簡單示例 + +如前所述,Gradio 提供了許多不同的輸入和輸出。 +因此,讓我們構建一個適用於音頻的“接口”。 + +在這個例子中,我們將構建一個音頻到音頻的函數,它需要一個音頻文件並簡單地反轉它。 + +我們將使用 `Audio` 組件作為輸入。 使用 `Audio` 組件時,您可以指定希望音頻的 `source` 是用戶上傳的文件還是用戶錄製聲音的麥克風。 在這種情況下,讓我們將其設置為“麥克風”。 只是為了好玩,我們會在我們的“音頻”中添加一個標籤,上面寫著“在這裡說話……”。 + +此外,我們希望將音頻作為 numpy 數組接收,以便我們可以輕鬆地“反轉”它。 所以我們將 `"type"` 設置為 `"numpy"`,它會傳遞輸入data 作為 (`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() +``` + +上面的代碼會產生一個類似下面的界面(如果你的瀏覽器沒有 +詢問您的麥克風權限, open the demo in a separate tab.) + + + +您現在應該能夠錄製自己的聲音並聽到自己在反向說話 - 聽起來好怪👻! + +## 處理多個輸入和輸出 + +假設我們有一個更復雜的函數,有多個輸入和輸出。在下面的示例中,我們有一個接受下拉索引、滑塊值和數字的函數,並返回一個音調的音頻樣本。 + +看看我們如何傳遞輸入和輸出組件列表,看看你能不能跟上正在發生的事情。 + +這裡的關鍵是當你通過時: +* 輸入組件列表,每個組件依次對應一個參數。 +* 輸出組件列表,每個組件對應一個返回值。 + +下面的代碼片段顯示了三個輸入組件如何與 `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()` 方法 + +到目前為止,我們已經使用了`launch()`方法來啟動界面,但是我們 +還沒有真正討論過它的作用。 + +默認情況下,`launch()` 方法將在 Web 服務器中啟動演示正在本地運行。 如果您在 Jupyter 或 Colab 筆記本中運行代碼,那麼Gradio 會將演示 GUI 嵌入到筆記本中,以便您輕鬆使用它。 + +您可以通過不同的參數自定義 `launch()` 的行為: + + - `inline` - whether to display the interface inline on Python notebooks. + - `inbrowser` - whether to automatically launch the interface in a new tab on the default browser. + - `share` - whether to create a publicly shareable link from your computer for the interface. Kind of like a Google Drive link! + +我們將在下一節中更詳細地介紹 `share` 參數! + +## ✏️ 讓我們應用它! + +讓我們構建一個界面,讓您演示 **speech-recognition** 模型。 +為了讓它變得有趣,我們將接受 *or* 麥克風輸入或上傳的文件。 + +像往常一樣,我們將使用 🤗 Transformers 中的 `pipeline()` 函數加載我們的語音識別模型。如果您需要快速複習,您可以返回 [第 1 章中的那個部分](/course/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() +``` + +如果您的瀏覽器沒有要求您提供麥克風權限,open the demo in a separate tab. + + + + +就是這樣! 您現在可以使用此界面來轉錄音頻。 注意這裡 +通過將 `optional` 參數作為 `True` 傳遞,我們允許用戶 +提供麥克風或音頻文件(或兩者都不提供,但這會返回錯誤消息)。 + +繼續看看如何與他人分享您的界面! \ No newline at end of file diff --git a/chapters/zh-TW/chapter9/4.mdx b/chapters/zh-TW/chapter9/4.mdx new file mode 100644 index 000000000..62a05c6f9 --- /dev/null +++ b/chapters/zh-TW/chapter9/4.mdx @@ -0,0 +1,144 @@ +# 與他人分享演示 + + + +現在您已經構建了一個演示,您可能希望與其他人分享它。 梯度演示 +可以通過兩種方式共享:使用 ***temporary share link*** 或 ***permanent hosting on Spaces***。 + +我們將很快介紹這兩種方法。 但在分享演示之前,您可能需要完善它 💅. + +### 打磨你的 Gradio 演示: + +
+Overview of a gradio interface + +
+ +為了給你的演示添加額外的內容,`Interface` 類支持一些可選參數: + - `title`:你可以給你的演示一個標題,它出現在輸入和輸出組件的上方。 + - `description`:您可以為界面提供描述(文本、Markdown 或 HTML),顯示在輸入和輸出組件的上方和標題下方。 + - `article`:您還可以編寫擴展文章(文本、Markdown 或 HTML)來解釋界面。如果提供,它會出現在輸入和輸出組件的_下方。 + - `theme`:不喜歡默認顏色?將主題設置為使用 `default`、`huggingface`、`grass`、`peach` 之一。您還可以添加 `dark-` 前綴,例如`dark-peach` 用於深色主題(或者只是 `dark` 用於默認的深色主題)。 + - `examples`:為了讓您的演示*更易於使用*,您可以為函數提供一些示例輸入。它們出現在 UI 組件下方,可用於填充界面。這些應該作為嵌套列表提供,其中外部列表​​由樣本組成,每個內部列表對應於每個輸入組件的輸入組成。 + - `live`:如果你想讓你的演示“活”,這意味著你的模型每次輸入更改時都會重新運行,你可以設置 `live=True`。這對使用快速模型很有意義(我們將在本節末尾看到一個示例) +使用上面的選項,我們最終得到了一個更完整的界面。 運行下面的代碼,以便與 Rick and Morty 聊天: + +```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() +``` + +使用上面的選項,我們最終得到了一個更完整的界面。 試試下面的界面: + + + +### 使用臨時鏈接分享您的演示 +現在我們已經有了機器學習模型的工作演示,讓我們學習如何輕鬆共享指向我們界面的鏈接。 +通過在 `launch()` 方法中設置 `share=True` 可以輕鬆地公開共享接口: + +```python +gr.Interface(classify_image, "image", "label").launch(share=True) +``` + +這會生成一個公開的、可共享的鏈接,您可以將其發送給任何人! 當您發送此鏈接時,另一方的用戶可以在瀏覽器中試用該模型長達 72 小時。 因為處理發生在您的設備上(只要您的設備保持開啟!),您不必擔心打包任何依賴項。 如果您使用 Google Colab 筆記本工作,則始終會自動創建共享鏈接。 它通常看起來像這樣:**XXXXX.gradio.app**。 雖然鏈接是通過 Gradio 鏈接提供的,但我們只是您本地服務器的代理,不會存儲通過接口發送的任何數據。 + +但是請記住,這些鏈接是可公開訪問的,這意味著任何人都可以使用您的模型進行預測! 因此,請確保不要通過您編寫的函數公開任何敏感信息,或允許在您的設備上發生任何關鍵更改。 如果設置 `share=False`(默認值),則僅創建本地鏈接。 + +### 在 Hugging Face Spaces 上託管您的演示 + +可以傳遞給同事的共享鏈接很酷,但是如何永久託管您的演示並讓它存在於互聯網上自己的“空間”中? + +Hugging Face Spaces 提供了在互聯網上永久託管 Gradio 模型的基礎設施,**免費**! Spaces 允許您創建並推送到(公共或私人)存儲庫, +你的 Gradio 在哪裡 +接口代碼將存在於 `app.py` 文件中。 [閱讀分步教程](https://huggingface.co/blog/gradio-spaces) 開始使用,或觀看下面的示例視頻。 + + + +## ✏️ 讓我們應用它! + +使用到目前為止我們在各節中學到的知識,讓我們創建我們在[本章第一節](/course/chapter9/1)中看到的草圖識別演示。 讓我們為我們的界面添加一些自定義並設置 `share=True` 以創建一個我們可以傳遞的公共鏈接。 + +我們可以從 [class_names.txt](https://huggingface.co/spaces/dawood/Sketch-Recognition/blob/main/class_names.txt) 加載標籤,並從 [pytorch_model.bin](https://huggingface.co/spaces/dawood/Sketch-Recognition/blob/main/pytorch_model.bin)加載預訓練的 pytorch 模型 。 通過點擊鏈接並單擊文件預覽左上角的下載來下載這些文件。 讓我們看看下面的代碼,看看我們如何使用這些文件來加載我們的模型並創建一個`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()`函數。 下一步是定義並啟動我們的漸變界面: + +```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) +``` + + + + +注意 `Interface` 中的 `live=True` 參數,這意味著草圖演示使 +每次有人在畫板上畫畫時的預測(沒有提交按鈕!)。 + +此外,我們還在 `launch()` 方法中設置了 `share=True` 參數。 +這將創建一個公共鏈接,您可以發送給任何人! 當您發送此鏈接時,對方的用戶可以嘗試草圖識別模型。 重申一下,您還可以在 Hugging Face Spaces 上託管模型,這就是我們能夠嵌入上面的演示的方式。 + +接下來,我們將介紹 Gradio 可用於 Hugging Face 生態系統的其他方式! \ No newline at end of file diff --git a/chapters/zh-TW/chapter9/5.mdx b/chapters/zh-TW/chapter9/5.mdx new file mode 100644 index 000000000..af733d52f --- /dev/null +++ b/chapters/zh-TW/chapter9/5.mdx @@ -0,0 +1,66 @@ +# 與 Hugging Face Hub 整合 + + + +為了讓你的生活更輕鬆, Gradio 直接與 Hugging Face Hub 和 Hugging Face Spaces 集成。你可以僅使用 *一行代碼* 從中心和空間加載演示。 + +### 從 Hugging Face Hub 加載模型 +首先, 從 Hugging Face 通過 Hub 提供的數千個模型中選擇一個, 如 [第四章](/course/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

" +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() +``` + +上述代碼將生成以下界面: + + + +以這種方式加載模型使用 Hugging Face 的 [Inference API](https://huggingface.co/inference-api),而不是將模型加載到內存中。這對於像 GPT-J 或 T0pp這樣需要大量 RAM 的大型模型是理想的。 + +### 從 Hugging Face Spaces 空間加載 +要從hugs 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與hugs Face Hub集成的方法, 讓我們來看看 `Interface` 類的一些高級功能。這就是下一節的主題! \ No newline at end of file diff --git a/chapters/zh-TW/chapter9/6.mdx b/chapters/zh-TW/chapter9/6.mdx new file mode 100644 index 000000000..77ad805e8 --- /dev/null +++ b/chapters/zh-TW/chapter9/6.mdx @@ -0,0 +1,97 @@ +# 高級接口功能 + + + +現在我們可以構建和共享一個基本接口, 讓我們來探索一些更高級的特性, 如狀態和解釋。 + +### 使用狀態保存數據 + +Gradio 支持 *會話狀態*, 其中數據在頁面加載中的多個提交中持續存在。會話狀態對於構建演示很有用, 例如, 你希望在用戶與模型交互時保留數據的聊天機器人。請注意, 會話狀態不會在模型的不同用戶之間共享數據。 + +要將數據存儲在會話狀態中, 你需要做三件事: + +1. 向函數中傳遞一個 *額外的參數* , 該參數表示接口的狀態。 +1. 在函數結束時, 將狀態的更新值作為 *額外的返回值* 返回。 +1. 在創建`接口`時添加 'state' 輸入和 'state' 輸出組件。 + +請參閱下面的聊天機器人示例: + +```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 參數傳入一個默認值, 作為 state 的初始值。 + +### 通過解釋來理解預測 + +大多數機器學習模型都是黑盒子, 函數的內部邏輯對終端用戶是隱藏的。為了提高透明度, 我們通過簡單地將 Interface 類中的解釋關鍵字設置為默認值, 使向模型添加解釋變得非常容易。這允許你的用戶理解輸入的哪些部分負責輸出。看看下面這個簡單的接口, 它顯示了一個還包括解釋的圖像分類器: + +```py +import requests +import tensorflow as tf + +import gradio as gr + +inception_net = tf.keras.applications.MobileNetV2() # load the model + +# Download human-readable labels for 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提供的默認解釋方法之外, 你還可以為 `interpretation` 參數指定 `shap`, 並設置 `num_shap` 參數。這使用基於 Shapley 的解釋, 你可以在 [here](https://christophm.github.io/interpretable-ml-book/shap.html) 閱讀更多信息。最後, 還可以將自己的解釋函數傳入 `interpretation` 參數。在Gradio的入門頁面 [here](https://gradio.app/getting_started/) 中可以看到一個例子。 + +這結束了我們對Gradio的`Interface`類的深入研究。正如我們所看到的, 這個類使用幾行Python代碼創建機器學習演示變得簡單。然而, 有時你會想通過改變佈局或鏈接多個預測函數來定製你的demo。如果我們能以某種方式將 `接口` 分成可定製的 "塊", 那不是很好嗎? 幸運的是, 有! 這是最後一部分的主題。 \ No newline at end of file diff --git a/chapters/zh-TW/chapter9/7.mdx b/chapters/zh-TW/chapter9/7.mdx new file mode 100644 index 000000000..9cf1dc7cd --- /dev/null +++ b/chapters/zh-TW/chapter9/7.mdx @@ -0,0 +1,236 @@ +# Gradio 塊簡介 + + + +在前面的部分中, 我們已經使用 `Interface` 類探索並創建了演示。在本節中, 我們將介紹我們 **新開發**的稱為`gradio.Blocks`低級API。 + +現在, `接口`和`塊`之間有什麼區別? + +- ⚡ `接口`: 一個高級 API, 讓你只需提供輸入和輸出列表即可創建完整的機器學習演示。 + +- 🧱 `塊`: :一個低級的 API, 它允許你完全控制你的應用程序的數據流和佈局。您可以使用`塊`(如 "構建塊")構建非常複雜的多步驟應用程序。 + + +### 為什麼要塊 🧱? + +正如我們在前幾節中看到的, `Interface` 類允許你使用幾行代碼輕鬆創建成熟的機器學習demo。`Interface` API 非常易於使用, 但缺乏 `Blocks` API 提供的靈活性。例如, 你可能想要: + +- 將相關演示組合為一個web應用程序中的多個選項卡 +- 更改demo的佈局, 例如指定輸入和輸出的位置 +- 具有多步驟接口, 其中一個模型的輸出成為下一個模型的輸入, 或者通常具有更靈活的數據流 +- 根據用戶輸入更改組件的屬性 (例如, 下拉列表中的選項) 或其可見性 + +我們將在下面探討所有這些概念。 + +### 使用塊創建簡單demo + +安裝 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. 塊允許你允許你構建結合markdown、HTML、按鈕和交互組件的web應用程序, 只需在一個帶有gradio的Python中實例化對象。 + +🙋如果你不熟悉 Python 中的 `with` 語句, 我們建議你查看來自 Real Python 的極好的[教程](https://realpython.com/python-with-statement/)。看完後回到這裡 🤗 + +實例化組件的順序很重要, 因為每個元素都按照創建的順序呈現到 Web 應用程序中。(更復雜的佈局在下面討論) + +2. 你可以在代碼中的任何位置定義常規 Python 函數, 並使用`塊`在用戶輸入的情況下運行它們。在我們的示例中, 們有一個"翻轉"輸入文本的簡單函數, 但你可以編寫任何 Python 函數, 從簡單的計算到處理機器學習模型的預測。 + +3. 你可以將事件指定給任何`塊`組件。這將在組件被單擊、更改等情況下運行函數。當你分配一個事件時, 你傳入三個參數: `fn`: 應該被調用的函數, `inputs`: 輸入組件的(列表), 以及 `outputs`: 應該被調用的輸出組件的(列表)。 + + 在上面的示例中, 當名為 `input` 的 `Textbox` 中的值發生變化時, 我們運行 `flip_text()` 函數。該事件讀取`輸入`中的值, 將其作為名稱參數傳遞給 `flip_text()`, 然後它返回一個值, 該值被分配給我們的第二個名為 `output` 的 `Textbox`。 + + 要查看每個組件支持的事件列表, 請參閱 Gradio [文檔](https://www.gradio.app/docs/)。 + +4. 塊會根據你定義的事件觸發器自動確定組件是否應該是交互式的 (接受用戶輸入)。在我們的示例中, 第一個文本框是交互式的, 因為它的值由 `flip_text()` 函數使用。第二個文本框不是交互式的, 因為它的值從不用作輸入。在某些情況下, 你可能想要覆蓋它, 你可以通過傳遞一個布爾值給組件的`交互`參數(例如 `gr.Textbox(placeholder="Flip this text", interactive=True)`)。 + +### 自定義演示的佈局 + +我們如何使用`塊`來定製我們的演示的佈局? 默認情況下, `塊`在一列中垂直呈現創建的組件。你可以通過使用 `with gradio.Column():` 創建其他列或使用 `with gradio.Row():` 創建其他行並在這些上下文中創建組件來改變這一點。 + +你應該記住: 在 `列` 下創建的任何組件(這也是默認設置) 都將垂直佈局。在 `Row` 下創建的任何組件都將水平佈局, 類似於 [Web 開發中的 flexbox 模型](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox)。 + +最後, 你還可以使用 `with gradio.Tabs()` 上下文管理器為您的demo創建選項卡。在此上下文中, 您可以通過使用 `gradio.TabItem(name_of_tab):` 指定來創建多個選項卡。在 `gradio.TabItem(name_of_tab):` 中創建的任何組件都會出現在該選項卡中。 + +現在讓我們在demo中添加一個 `flip_image()`函數並添加一個翻轉圖像的新選項卡。下面是一個帶有 2 個選項卡的示例, 也使用了一個行: + +```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` 組件, 並且我們為每個按鈕分配了一個點擊事件,這是實際運行該函數的事件。 + +### 探索事件和狀態 + +正如你可以控制佈局一樣, `塊` 可以讓你對觸發函數調用的事件進行細粒度控制。每個組件和許多佈局都有它們支持的特定事件。 + +例如, `Textbox` 組件有兩個事件: `change()` (當文本框內的值發生變化時), 和 `submit()` (當用戶在關注文本框時按下enter鍵)。更復雜的組件可以有更多的事件: 例如,`Audio`組件也有單獨的事件, 用於播放、清除、暫停音頻文件等。請參閱文檔瞭解每個組件支持的事件。 + +你可以將事件觸發器附加到這些事件中的一個、一個或多個。你可以通過在組件實例中調用事件名稱作為函數來創建一個事件觸發器 -- 例如 `textbox.change(...)` 或 `btn.click(...)`。如前所述, 該函數接受三個參數: + +- `fn`: 要運行的函數 +- `inputs`: 組件的(列表), 其值應作為函數的輸入參數提供。每個組件的值按順序映射到相應的函數參數。如果函數不帶任何參數, 則此參數可以為 None。 +- `outputs`: 應根據函數返回的值更新其值的組件(列表)。每個返回值按順序設置相應組件的值。如果函數不返回任何內容, 則此參數可以為None。 + +你甚至可以使輸入和輸出組件成為同一個組件, 就像我們在這個使用 GPT 模型進行文本補全的示例中所做的那樣: + +```py +import gradio as gr + +api = gr.Interface.load("huggingface/EleutherAI/gpt-j-6B") + + +def complete_with_gpt(text): + # Use the last 50 characters of the text as context + 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() +``` + + + +### 創建多步demo + +在某些情況下, 您可能需要一個 _多步驟的demo_, 其中重用一個函數的輸出作為下一個函數的輸入。使用 `塊` 很容易做到這一點, 因為你可以使用組件作為一個事件觸發器的輸入, 但作為另一個事件觸發器的輸出。看看下面示例中的文本組件, 它的值是語音到文本模型的結果, 但也被傳遞到情感分析模型: + +```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() +``` + + + +### 更新組件屬性 + +到目前為止, 我們已經瞭解瞭如何創建事件來更新另一個組件的值。但是, 如果您想更改組件的其他屬性, 例如文本框的可見性或單選按鈕組中的選項, 會發生什麼? 您可以通過返回組件類的 `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() +``` + + + +我們剛剛探索了`塊`的所有核心概念! 就像 `參數一樣`, 你可以創建很酷的demo, 可以通過在`launch()`方法中使用`share=True`來共享, 或者部署在[Hugging Face Spaces](https://huggingface.co/spaces)上。 \ No newline at end of file diff --git a/chapters/zh-TW/chapter9/8.mdx b/chapters/zh-TW/chapter9/8.mdx new file mode 100644 index 000000000..86c4dc09b --- /dev/null +++ b/chapters/zh-TW/chapter9/8.mdx @@ -0,0 +1,19 @@ +# Gradio,回顧! + +關於使用 Gradio 構建酷炫的 ML 演示的章節到此結束 - 我們希望您喜歡它!回顧一下,在本章中,我們學習了: + +- 如何使用高級 `Interface` API 創建 Gradio 演示,以及如何配置不同的輸入和輸出模式。 +- 通過臨時鏈接和託管在 [Hugging Face Spaces](https://huggingface.co/spaces) 上共享 Gradio 演示的不同方式。 +- 如何將 Gradio 演示與 Hugging Face Hub 上的Model和Space集成在一起。 +- 高級功能,例如在演示中存儲狀態或提供身份驗證。 +- 如何使用 Gradio Blocks 完全控制演示的數據流和佈局。 + +如果您想測試您對本章所涵蓋概念的理解,請查看下一節中的測驗! + +## 下一步去哪裡? + +如果您想了解有關 Gradio 的更多信息,您可以 + +- 看看 repo 中的 [Demos](https://github.com/gradio-app/gradio/tree/main/demo),那裡有很多例子。 +- 請參閱 [指南](https://gradio.app/guides/) 頁面,您可以在其中找到有關酷炫和高級功能的指南。 +- 查看 [文檔](https://gradio.app/docs/) 頁面瞭解詳情。 diff --git a/chapters/zh-TW/chapter9/9.mdx b/chapters/zh-TW/chapter9/9.mdx new file mode 100644 index 000000000..730e6e334 --- /dev/null +++ b/chapters/zh-TW/chapter9/9.mdx @@ -0,0 +1,231 @@ + + + + +# 章末測驗 + + + +讓我們測試一下您在本章中學到了什麼! + +### 1.你能利用Grado做什麼? + share = True 參數,可以生成一個共享鏈接發送給任何人。", + correct: true + }, + { + text: "調試模型", + explain: "Grado演示的一個優點是能夠用真實數據測試模型,您可以實時更改並觀察模型的預測變化,從而幫助您調試模型。", + correct: true + }, + { + text: "訓練你的模型", + explain: "在你的模型被訓練之後,Grado 被設計用來進行模型推理。", + } + ]} +/> + +### 2.Grado只在 PyTorch 模型上工作 + + +### 3.你可以在哪裡發佈一個 GRadio 演示? + + +### 4.Gdio 主要是為 NLP 模型設計的 + + +### 5.下列哪些特性是由 Grado 支持的? + gr. Interface.load () 方法加載任何 Hugging Face 模型", + correct: true + } + ]} +/> + +### 6.下列哪一種是從 Hub 或 Spaces 加載 Huggging Face 模型的有效方法? + + +### 7.創建您的 Gradio 接口時,您必須添加以下步驟: + + +### 8.Gradio 庫包括以下哪些組件? + + +### 9.Gradio允許你做什麼? + + +### 10.你可以共享一個`Blocks`演示的公共鏈接,並創建一個`Blocks`的演示在HuggingFace空間。 + \ No newline at end of file diff --git a/chapters/zh-TW/events/2.mdx b/chapters/zh-TW/events/2.mdx new file mode 100644 index 000000000..cd1116004 --- /dev/null +++ b/chapters/zh-TW/events/2.mdx @@ -0,0 +1,165 @@ +# Part 2 發佈活動 + +對於課程第 2 部分的發佈,我們在微調 sprint 之前組織了一場現場活動,為期兩天的會談。 如果你錯過了,你可以趕上下面列出的講座! + +## Day 1: Transformer 的高級API以及如何訓練它們 + +**Thomas Wolf:** *遷移學習和Transformers庫的誕生* + +
+ +
+ +

+一張圖總結 Thom 的演講 +

+ +Thomas Wolf 是 Hugging Face 的聯合創始人兼首席科學官。 Thomas Wolf 和 Hugging Face 團隊創建的工具被 5,000 多個研究機構使用,包括 Facebook 人工智能研究、谷歌研究、DeepMind、亞馬遜研究、蘋果、艾倫人工智能研究所以及大多數大學系。 Thomas Wolf 是人工智能領域有史以來最大的研究合作的發起人和高級主席:[“BigScience”](https://bigscience.huggingface.co),以及一組廣泛使用的 [庫和工具](https://github.com/huggingface/)。 Thomas Wolf 還是一位多產的教育家、人工智能和自然語言處理領域的思想領袖,並且經常受邀在世界各地的會議上發表演講 [https://thomwolf.io](https://thomwolf.io )。 + +**Jay Alammar:** *Transformers模型的圖解* + +
+ +
+ +

+一張圖總結 Jay 的演講 +

+ +通過他廣受歡迎的 ML 博客,Jay 幫助數百萬研究人員和工程師直觀地理解了機器學習工具和概念,從基礎(最終出現在 NumPy、Pandas 文檔)到前沿(Transformers、BERT、GPT-3)。 + +**Margaret Mitchell:** *關於機器學習開發中的價值觀* + +
+ +
+ +

+一張圖總結 Margaret 的演講 +

+ +Margaret Mitchell 是一名從事人工智能倫理研究的研究員,目前專注於以倫理為依據的人工智能開發。她在自然語言生成、輔助技術、計算機視覺和人工智能倫理方面發表了 50 多篇論文,並在會話生成和情感分類領域擁有多項專利。她之前曾在 Google AI 擔任員工研究科學家,在那裡她創立並共同領導了 Google 的倫理 AI 小組,專注於基礎 AI 倫理研究和在 Google 內部實施 AI 倫理。在加入谷歌之前,她是微軟研究院的一名研究員,專注於計算機視覺到語言的生成;並且是約翰霍普金斯大學的博士後,專注於貝葉斯建模和信息提取。她擁有阿伯丁大學計算機科學博士學位和華盛頓大學計算語言學碩士學位。在獲得學位的同時,她還於 2005 年至 2012 年在俄勒岡健康與科學大學從事機器學習、神經系統疾病和輔助技術方面的工作。她在多樣性、包容性、計算機科學和倫理學的交叉領域領導了許多研討會和倡議。她的工作獲得了國防部長阿什卡特和美國盲人基金會的獎勵,並被多家科技公司實施。她喜歡園藝、狗和貓。 + +**Matthew Watson 和 Chen Qian:** *使用 Keras 的 NLP 工作流程* + +
+ +
+ +

+一張圖總結 Matt 和 Chen 的演講 +

+ +Matthew Watson 是 Keras 團隊的機器學習工程師,專注於高級建模 API。 他在本科期間學習計算機圖形學,並在斯坦福大學獲得碩士學位。 作為一名幾乎是英語專業的學生,他轉向計算機科學,熱衷於跨學科工作並使 NLP 為更廣泛的受眾所接受。 + +Chen Qian 是 Keras 團隊的一名軟件工程師,專注於高級建模 API。 Chen 在斯坦福大學獲得電氣工程碩士學位,他對簡化 ML 任務和大規模 ML 的代碼實現特別感興趣。 + +**Mark Saroufim:** *如何使用 Pytorch 訓練模型* + +
+ +
+ +

+一張圖總結 Mark 的演講 +

+ +Mark Saroufim 是 Pytorch 的合作伙伴工程師,致力於開發 OSS 生產工具,包括 TorchServe 和 Pytorch Enterprise。 Mark 是 Graphcore、[yuri.ai](http://yuri.ai/)、Microsoft 和 NASA 的 JPL 的應用科學家和產品經理。 他熱衷於讓編程更有趣。 + +**Jakob Uszkoreit:** *它沒有壞所以不要修復讓我們打破它* + +
+ +
+ +

+一張圖總結 Jakob 的演講 +

+ +Jakob Uszkoreit 是 Inceptive 的聯合創始人。 Inceptive 在緊密循環中使用大規模深度學習和高通量實驗設計用於疫苗和治療的 RNA 分子,目標是使基於 RNA 的藥物更容易獲得、更有效和更廣泛適用。 此前,Jakob 在谷歌工作了十多年,領導谷歌大腦、研究和搜索領域的研發團隊,致力於深度學習基礎、計算機視覺、語言理解和機器翻譯。 + +## Day 2: 可以使用的工具 + +**Lewis Tunstall:** *使用 🤗 Transformers Trainer 讓訓練更加簡單* + +
+ +
+ +Lewis 是 Hugging Face 的機器學習工程師,專注於開發開源工具並讓更廣泛的社區可以訪問它們。 他還是 O'Reilly 即將出版的有關於Transform的合著者,您可以在 Twitter (@_lewtun) 上關注他,瞭解 NLP 提示和技巧! + +**Matthew Carrigan:** *用於 🤗 Transformers 和 🤗 Datasets的新 TensorFlow 特性* + +
+ +
+ +Matt 負責Transformers的TensorFlow維護,並將最終領導一場針對現任PyTorch派系的政變,可能會通過他的推特賬戶@carrigmat進行協調。 + +**Lysandre Debut:** *使用Hugging Face Hub 作為協作和共享機器學習項目* + +
+ +
+ +

+一張圖總結 Lysandre 的演講 +

+ +Lysandre 是 Hugging Face 的機器學習工程師,他參與了許多開源項目。 他的目標是通過使用非常簡單的 API 開發強大的工具,讓每個人都可以使用機器學習。 + +**Lucile Saulnier:** *使用 🤗 Transformers 和 🤗 Tokenizers 獲取您自己的tokenizer* + +
+ +
+ +Lucile 是 Hugging Face 的機器學習工程師,負責開發和支持開源工具的使用。 她還積極參與了自然語言處理領域的許多研究項目,例如協作訓練模型和 BigScience。 + +**Sylvain Gugger:** *使用 🤗 Accelerate* 增強您的 PyTorch 訓練循環* + +
+ +
+ +Sylvain 是 Hugging Face 的研究工程師,也是🤗 Transformers 的核心維護者之一,也是🤗 Accelerate 的開發者。 他喜歡讓模型訓練變得更容易。 + +**Merve Noyan:** *使用 🤗 Spaces 展示您的模型演示* + +
+ +
+ +Merve 是 Hugging Face 的開發者倡導者,致力於開發工具並圍繞它們構建內容,以使每個人的機器學習民主化。 + +**Abubakar Abid:** *快速構建機器學習應用程序* + +
+ +
+ +

+一張圖總結 Abubakar 的演講 +

+ +Abubakar Abid 是 [Gradio](www.gradio.app) 的首席執行官。 他於 2015 年獲得麻省理工學院電氣工程和計算機科學學士學位,並於 2021 年獲得斯坦福大學應用機器學習博士學位。作為 Gradio 的首席執行官,Abubakar 致力於使機器學習模型更易於演示、調試和部署。 + +**Mathieu Desvé:** *AWS ML Vision:讓所有客戶都可以使用機器學習* + +
+ +
+ +

+一張圖總結 Mathieu 的演講 +

+ +技術愛好者,有空閒時間的創客。 我喜歡挑戰和解決客戶和用戶的問題,每天和有才華的人一起學習。 自 2004 年以來,我在前端、後端、基礎設施、運營和管理等多個職位上工作。 嘗試以敏捷的方式解決公共技術和管理問題。 + +**Philipp Schmid:** *使用 Amazon SageMaker 和🤗 Transformers 進行託管訓練* + +
+ +
+ +Philipp Schmid 是 Hugging Face 的機器學習工程師和技術主管,負責領導與 Amazon SageMaker 團隊的合作。 他熱衷於使尖端 NLP 模型民主化和生產化,並提高深度學習的易用性。 \ No newline at end of file diff --git a/subtitles/README.md b/subtitles/README.md index 002948954..833481ede 100644 --- a/subtitles/README.md +++ b/subtitles/README.md @@ -37,8 +37,8 @@ For example, in the `zh-CN` subtitles, each block has the following format: ``` 1 00:00:05,850 --> 00:00:07,713 -- 欢迎来到 Hugging Face 课程。 -- Welcome to the Hugging Face Course. +欢迎来到 Hugging Face 课程。 +Welcome to the Hugging Face Course. ``` To upload the SRT file to YouTube, we need the subtitle in monolingual format, i.e. the above block should read: @@ -46,7 +46,7 @@ To upload the SRT file to YouTube, we need the subtitle in monolingual format, i ``` 1 00:00:05,850 --> 00:00:07,713 -- 欢迎来到 Hugging Face 课程。 +欢迎来到 Hugging Face 课程。 ``` To handle this, we provide a script that converts the bilingual SRT files to monolingual ones. To perform the conversion, run: diff --git a/subtitles/en/33_the-push-to-hub-api-(pytorch).srt b/subtitles/en/33_the-push-to-hub-api-(pytorch).srt index a2fcf8caf..3c27675f3 100644 --- a/subtitles/en/33_the-push-to-hub-api-(pytorch).srt +++ b/subtitles/en/33_the-push-to-hub-api-(pytorch).srt @@ -40,7 +40,7 @@ password, then click login, 10 00:00:26,640 --> 00:00:28,620 -this will store a notification token +this will store a authentication token 11 00:00:28,620 --> 00:00:30,670 @@ -446,7 +446,7 @@ with the from_pretrained method 103 00:04:21,113 --> 00:04:22,923 -of with the pipeline function. +or with the pipeline function. 104 00:04:34,350 --> 00:04:36,780 diff --git a/subtitles/en/41_text-embeddings-&-semantic-search.srt b/subtitles/en/41_text-embeddings-&-semantic-search.srt index 51c9d9b29..5fc6dc369 100644 --- a/subtitles/en/41_text-embeddings-&-semantic-search.srt +++ b/subtitles/en/41_text-embeddings-&-semantic-search.srt @@ -194,12 +194,12 @@ average the token embeddings 44 00:01:49,650 --> 00:01:52,500 -which is called mean pooling +which is called mean_pooling and this is what we do here. 45 00:01:53,370 --> 00:01:55,800 -With mean pooling the only +With mean_pooling the only thing we need to make sure 46 @@ -210,7 +210,7 @@ padding tokens in the average, 47 00:01:58,410 --> 00:02:01,860 which is why you can see the -attention mask being used here. +attention_mask being used here. 48 00:02:01,860 --> 00:02:05,100 @@ -313,7 +313,7 @@ we take a small sample 70 00:02:56,070 --> 00:02:57,780 -from the SQUAD dataset and apply +from the squad dataset and apply 71 00:02:57,780 --> 00:03:00,180 diff --git a/subtitles/en/metadata_tasks.csv b/subtitles/en/metadata_tasks.csv new file mode 100644 index 000000000..2af9c9a82 --- /dev/null +++ b/subtitles/en/metadata_tasks.csv @@ -0,0 +1,7 @@ +id,title,link,srt_filename +wVHdVlPScxA,🤗 Tasks: Token Classification,https://www.youtube.com/watch?v=wVHdVlPScxA&list=PLo2EIpI_JMQtyEr-sLJSy5_SnLCb4vtQf&index=1,subtitles/en/tasks_00_🤗-tasks-token-classification.srt +ajPx5LwJD-I,🤗 Tasks: Question Answering,https://www.youtube.com/watch?v=ajPx5LwJD-I&list=PLo2EIpI_JMQtyEr-sLJSy5_SnLCb4vtQf&index=2,subtitles/en/tasks_01_🤗-tasks-question-answering.srt +Vpjb1lu0MDk,🤗 Tasks: Causal Language Modeling,https://www.youtube.com/watch?v=Vpjb1lu0MDk&list=PLo2EIpI_JMQtyEr-sLJSy5_SnLCb4vtQf&index=3,subtitles/en/tasks_02_🤗-tasks-causal-language-modeling.srt +mqElG5QJWUg,🤗 Tasks: Masked Language Modeling,https://www.youtube.com/watch?v=mqElG5QJWUg&list=PLo2EIpI_JMQtyEr-sLJSy5_SnLCb4vtQf&index=4,subtitles/en/tasks_03_🤗-tasks-masked-language-modeling.srt +yHnr5Dk2zCI,🤗 Tasks: Summarization,https://www.youtube.com/watch?v=yHnr5Dk2zCI&list=PLo2EIpI_JMQtyEr-sLJSy5_SnLCb4vtQf&index=5,subtitles/en/tasks_04_🤗-tasks-summarization.srt +1JvfrvZgi6c,🤗 Tasks: Translation,https://www.youtube.com/watch?v=1JvfrvZgi6c&list=PLo2EIpI_JMQtyEr-sLJSy5_SnLCb4vtQf&index=6,subtitles/en/tasks_05_🤗-tasks-translation.srt diff --git a/subtitles/en/raw/tasks.md b/subtitles/en/raw/tasks.md new file mode 100644 index 000000000..a95d2429a --- /dev/null +++ b/subtitles/en/raw/tasks.md @@ -0,0 +1,77 @@ +Note: the following transcripts are associated with Merve Noyan's videos in the Hugging Face Tasks playlist: https://www.youtube.com/playlist?list=PLo2EIpI_JMQtyEr-sLJSy5_SnLCb4vtQf + +Token Classification video + +Welcome to the Hugging Face tasks series! In this video we’ll take a look at the token classification task. +Token classification is the task of assigning a label to each token in a sentence. There are various token classification tasks and the most common are Named Entity Recognition and Part-of-Speech Tagging. +Let’s take a quick look at the Named Entity Recognition task. The goal of this task is to find the entities in a piece of text, such as person, location, or organization. This task is formulated as labelling each token with one class for each entity, and another class for tokens that have no entity. +Another token classification task is part-of-speech tagging. The goal of this task is to label the words for a particular part of a speech, such as noun, pronoun, adjective, verb and so on. This task is formulated as labelling each token with parts of speech. +Token classification models are evaluated on Accuracy, Recall, Precision and F1-Score. The metrics are calculated for each of the classes. We calculate true positive, true negative and false positives to calculate precision and recall, and take their harmonic mean to get F1-Score. Then we calculate it for every class and take the overall average to evaluate our model. +An example dataset used for this task is ConLL2003. Here, each token belongs to a certain named entity class, denoted as the indices of the list containing the labels. +You can extract important information from invoices using named entity recognition models, such as date, organization name or address. +For more information about the Token classification task, check out the Hugging Face course. + + +Question Answering video + +Welcome to the Hugging Face tasks series. In this video, we will take a look at the Question Answering task. +Question answering is the task of extracting an answer in a given document. +Question answering models take a context, which is the document you want to search in, and a question and return an answer. Note that the answer is not generated, but extracted from the context. This type of question answering is called extractive. +The task is evaluated on two metrics, exact match and F1-Score. +As the name implies, exact match looks for an exact match between the predicted answer and the correct answer. +A common metric used is the F1-Score, which is calculated over tokens that are predicted correctly and incorrectly. It is calculated over the average of two metrics called precision and recall which are metrics that are used widely in classification problems. +An example dataset used for this task is called SQuAD. This dataset contains contexts, questions and the answers that are obtained from English Wikipedia articles. +You can use question answering models to automatically answer the questions asked by your customers. You simply need a document containing information about your business and query through that document with the questions asked by your customers. +For more information about the Question Answering task, check out the Hugging Face course. + + +Causal Language Modeling video + +Welcome to the Hugging Face tasks series! In this video we’ll take a look at Causal Language Modeling. +Causal language modeling is the task of predicting the next +word in a sentence, given all the previous words. This task is very similar to the autocorrect function that you might have on your phone. +These models take a sequence to be completed and outputs the complete sequence. +Classification metrics can’t be used as there’s no single correct answer for completion. Instead, we evaluate the distribution of the text completed by the model. +A common metric to do so is the cross-entropy loss. Perplexity is also a widely used metric and it is calculated as the exponential of the cross-entropy loss. +You can use any dataset with plain text and tokenize the text to prepare the data. +Causal language models can be used to generate code. +For more information about the Causal Language Modeling task, check out the Hugging Face course. + + +Masked Language Modeling video + +Welcome to the Hugging Face tasks series! In this video we’ll take a look at Masked Language Modeling. +Masked language modeling is the task of predicting which words should fill in the blanks of a sentence. +These models take a masked text as the input and output the possible values for that mask. +Masked language modeling is handy before fine-tuning your model for your task. For example, if you need to use a model in a specific domain, say, biomedical documents, models like BERT will treat your domain-specific words as rare tokens. If you train a masked language model using your biomedical corpus and then fine tune your model on a downstream task, you will have a better performance. +Classification metrics can’t be used as there’s no single correct answer to mask values. Instead, we evaluate the distribution of the mask values. +A common metric to do so is the cross-entropy loss. Perplexity is also a widely used metric and it is calculated as the exponential of the cross-entropy loss. +You can use any dataset with plain text and tokenize the text to mask the data. +For more information about the Masked Language Modeling, check out the Hugging Face course. + + +Summarization video + +Welcome to the Hugging Face tasks series. In this video, we will take a look at the Text Summarization task. +Summarization is a task of producing a shorter version of a document while preserving the relevant and important information in the document. +Summarization models take a document to be summarized and output the summarized text. +This task is evaluated on the ROUGE score. It’s based on the overlap between the produced sequence and the correct sequence. +You might see this as ROUGE-1, which is the overlap of single tokens and ROUGE-2, the overlap of subsequent token pairs. ROUGE-N refers to the overlap of n subsequent tokens. Here we see an example of how overlaps take place. +An example dataset used for this task is called Extreme Summarization, XSUM. This dataset contains texts and their summarized versions. +You can use summarization models to summarize research papers which would enable researchers to easily pick papers for their reading list. +For more information about the Summarization task, check out the Hugging Face course. + + +Translation video + +Welcome to the Hugging Face tasks series. In this video, we will take a look at the Translation task. +Translation is the task of translating text from one language to another. +These models take a text in the source language and output the translation of that text in the target language. +The task is evaluated on the BLEU score. +The score ranges from 0 to 1, in which 1 means the translation perfectly matched and 0 did not match at all. +BLEU is calculated over subsequent tokens called n-grams. Unigram refers to a single token while bi-gram refers to token pairs and n-grams refer to n subsequent tokens. +Machine translation datasets contain pairs of text in a language and translation of the text in another language. +These models can help you build conversational agents across different languages. +One option is to translate the training data used for the chatbot and train a separate chatbot. +You can put one translation model from your user’s language to the language your chatbot is trained on, translate the user inputs and do intent classification, take the output of the chatbot and translate it from the language your chatbot was trained on to the user’s language. +For more information about the Translation task, check out the Hugging Face course. diff --git "a/subtitles/en/tasks_00_\360\237\244\227-tasks-token-classification.srt" "b/subtitles/en/tasks_00_\360\237\244\227-tasks-token-classification.srt" new file mode 100644 index 000000000..ee6e6f207 --- /dev/null +++ "b/subtitles/en/tasks_00_\360\237\244\227-tasks-token-classification.srt" @@ -0,0 +1,116 @@ +1 +00:00:04,520 --> 00:00:07,400 +Welcome to the Hugging Face tasks series! + +2 +00:00:07,400 --> 00:00:11,870 +In this video we’ll take a look at the token +classification task. + +3 +00:00:11,870 --> 00:00:17,900 +Token classification is the task of assigning +a label to each token in a sentence. + +4 +00:00:17,900 --> 00:00:23,310 +There are various token classification tasks +and the most common are Named Entity Recognition + +5 +00:00:23,310 --> 00:00:26,430 +and Part-of-Speech Tagging. + +6 +00:00:26,430 --> 00:00:31,640 +Let’s take a quick look at the Named Entity +Recognition task. + +7 +00:00:31,640 --> 00:00:38,400 +The goal of this task is to find the entities +in a piece of text, such as person, location, + +8 +00:00:38,400 --> 00:00:40,210 +or organization. + +9 +00:00:40,210 --> 00:00:45,250 +This task is formulated as labelling each +token with one class for each entity, and + +10 +00:00:45,250 --> 00:00:51,719 +another class for tokens that have no entity. + +11 +00:00:51,719 --> 00:00:55,670 +Another token classification task is part-of-speech +tagging. + +12 +00:00:55,670 --> 00:01:01,399 +The goal of this task is to label the words +for a particular part of a speech, such as + +13 +00:01:01,399 --> 00:01:05,900 +noun, pronoun, adjective, verb and so on. + +14 +00:01:05,900 --> 00:01:11,270 +This task is formulated as labelling each +token with parts of speech. + +15 +00:01:11,270 --> 00:01:19,659 +Token classification models are evaluated +on Accuracy, Recall, Precision and F1-Score. + +16 +00:01:19,659 --> 00:01:22,950 +The metrics are calculated for each of the +classes. + +17 +00:01:22,950 --> 00:01:28,040 +We calculate true positive, true negative +and false positives to calculate precision + +18 +00:01:28,040 --> 00:01:31,829 +and recall, and take their harmonic mean to +get F1-Score. + +19 +00:01:31,829 --> 00:01:42,329 +Then we calculate it for every class and take +the overall average to evaluate our model. + +20 +00:01:42,329 --> 00:01:45,680 +An example dataset used for this task is ConLL2003. + +21 +00:01:45,680 --> 00:01:51,750 +Here, each token belongs to a certain named +entity class, denoted as the indices of the + +22 +00:01:51,750 --> 00:01:55,380 +list containing the labels. + +23 +00:01:55,380 --> 00:02:00,720 +You can extract important information from +invoices using named entity recognition models, + +24 +00:02:00,720 --> 00:02:07,070 +such as date, organization name or address. + +25 +00:02:07,070 --> 00:02:16,840 +For more information about the Token classification +task, check out the Hugging Face course. diff --git "a/subtitles/en/tasks_01_\360\237\244\227-tasks-question-answering.srt" "b/subtitles/en/tasks_01_\360\237\244\227-tasks-question-answering.srt" new file mode 100644 index 000000000..6416fde12 --- /dev/null +++ "b/subtitles/en/tasks_01_\360\237\244\227-tasks-question-answering.srt" @@ -0,0 +1,87 @@ +1 +00:00:04,400 --> 00:00:06,480 +Welcome to the Hugging Face tasks series.   + +2 +00:00:07,200 --> 00:00:10,080 +In this video, we will take a look  +at the Question Answering task.  + +3 +00:00:13,120 --> 00:00:17,200 +Question answering is the task of  +extracting an answer in a given document.  + +4 +00:00:21,120 --> 00:00:25,600 +Question answering models take a context,  +which is the document you want to search in,   + +5 +00:00:26,240 --> 00:00:31,440 +and a question and return an answer.  +Note that the answer is not generated,   + +6 +00:00:31,440 --> 00:00:37,600 +but extracted from the context. This type  +of question answering is called extractive.  + +7 +00:00:42,320 --> 00:00:46,960 +The task is evaluated on two  +metrics, exact match and F1-Score.  + +8 +00:00:49,680 --> 00:00:52,320 +As the name implies, exact match looks for an   + +9 +00:00:52,320 --> 00:00:57,840 +exact match between the predicted  +answer and the correct answer.  + +10 +00:01:00,080 --> 00:01:05,520 +A common metric used is the F1-Score, which  +is calculated over tokens that are predicted   + +11 +00:01:05,520 --> 00:01:10,960 +correctly and incorrectly. It is calculated  +over the average of two metrics called   + +12 +00:01:10,960 --> 00:01:16,560 +precision and recall which are metrics that  +are used widely in classification problems.  + +13 +00:01:20,880 --> 00:01:28,240 +An example dataset used for this task is called  +SQuAD. This dataset contains contexts, questions   + +14 +00:01:28,240 --> 00:01:32,080 +and the answers that are obtained  +from English Wikipedia articles.  + +15 +00:01:35,440 --> 00:01:39,520 +You can use question answering models to  +automatically answer the questions asked   + +16 +00:01:39,520 --> 00:01:46,480 +by your customers. You simply need a document  +containing information about your business   + +17 +00:01:47,200 --> 00:01:53,840 +and query through that document with  +the questions asked by your customers.  + +18 +00:01:55,680 --> 00:02:06,160 +For more information about the Question Answering  +task, check out the Hugging Face course. diff --git "a/subtitles/en/tasks_02_\360\237\244\227-tasks-causal-language-modeling.srt" "b/subtitles/en/tasks_02_\360\237\244\227-tasks-causal-language-modeling.srt" new file mode 100644 index 000000000..06dc54e12 --- /dev/null +++ "b/subtitles/en/tasks_02_\360\237\244\227-tasks-causal-language-modeling.srt" @@ -0,0 +1,63 @@ +1 +00:00:04,560 --> 00:00:06,640 +Welcome to the Hugging Face tasks series!   + +2 +00:00:07,200 --> 00:00:10,400 +In this video we’ll take a look  +at Causal Language Modeling.  + +3 +00:00:13,600 --> 00:00:16,880 +Causal language modeling is  +the task of predicting the next  + +4 +00:00:16,880 --> 00:00:21,920 +word in a sentence, given all the  +previous words. This task is very   + +5 +00:00:21,920 --> 00:00:29,920 +similar to the autocorrect function  +that you might have on your phone.  + +6 +00:00:29,920 --> 00:00:34,720 +These models take a sequence to be  +completed and outputs the complete sequence.  + +7 +00:00:38,640 --> 00:00:44,160 +Classification metrics can’t be used as there’s  +no single correct answer for completion.   + +8 +00:00:44,960 --> 00:00:49,280 +Instead, we evaluate the distribution  +of the text completed by the model.  + +9 +00:00:50,800 --> 00:00:55,440 +A common metric to do so is the  +cross-entropy loss. Perplexity is   + +10 +00:00:55,440 --> 00:01:01,280 +also a widely used metric and it is calculated  +as the exponential of the cross-entropy loss.  + +11 +00:01:05,200 --> 00:01:11,840 +You can use any dataset with plain text  +and tokenize the text to prepare the data.  + +12 +00:01:15,040 --> 00:01:18,240 +Causal language models can  +be used to generate code.  + +13 +00:01:22,480 --> 00:01:33,200 +For more information about the Causal Language  +Modeling task, check out the Hugging Face course. diff --git "a/subtitles/en/tasks_03_\360\237\244\227-tasks-masked-language-modeling.srt" "b/subtitles/en/tasks_03_\360\237\244\227-tasks-masked-language-modeling.srt" new file mode 100644 index 000000000..28f376b68 --- /dev/null +++ "b/subtitles/en/tasks_03_\360\237\244\227-tasks-masked-language-modeling.srt" @@ -0,0 +1,85 @@ +1 +00:00:04,660 --> 00:00:07,589 +Welcome to the Hugging Face tasks series! + +2 +00:00:07,589 --> 00:00:13,730 +In this video we’ll take a look at Masked +Language Modeling. + +3 +00:00:13,730 --> 00:00:20,720 +Masked language modeling is the task of predicting +which words should fill in the blanks of a + +4 +00:00:20,720 --> 00:00:23,500 +sentence. + +5 +00:00:23,500 --> 00:00:32,870 +These models take a masked text as the input +and output the possible values for that mask. + +6 +00:00:32,870 --> 00:00:37,550 +Masked language modeling is handy before fine-tuning +your model for your task. + +7 +00:00:37,550 --> 00:00:43,579 +For example, if you need to use a model in +a specific domain, say, biomedical documents, + +8 +00:00:43,579 --> 00:00:49,050 +models like BERT will treat your domain-specific +words as rare tokens. + +9 +00:00:49,050 --> 00:00:54,220 +If you train a masked language model using +your biomedical corpus and then fine tune + +10 +00:00:54,220 --> 00:01:02,929 +your model on a downstream task, you will +have a better performance. + +11 +00:01:02,929 --> 00:01:07,799 +Classification metrics can’t be used as +there’s no single correct answer to mask + +12 +00:01:07,799 --> 00:01:08,799 +values. + +13 +00:01:08,799 --> 00:01:12,900 +Instead, we evaluate the distribution of the +mask values. + +14 +00:01:12,900 --> 00:01:16,590 +A common metric to do so is the cross-entropy +loss. + +15 +00:01:16,590 --> 00:01:22,010 +Perplexity is also a widely used metric and +it is calculated as the exponential of the + +16 +00:01:22,010 --> 00:01:27,240 +cross-entropy loss. + +17 +00:01:27,240 --> 00:01:35,680 +You can use any dataset with plain text and +tokenize the text to mask the data. + +18 +00:01:35,680 --> 00:01:44,710 +For more information about the Masked Language +Modeling, check out the Hugging Face course. diff --git "a/subtitles/en/tasks_04_\360\237\244\227-tasks-summarization.srt" "b/subtitles/en/tasks_04_\360\237\244\227-tasks-summarization.srt" new file mode 100644 index 000000000..0c16f7f85 --- /dev/null +++ "b/subtitles/en/tasks_04_\360\237\244\227-tasks-summarization.srt" @@ -0,0 +1,68 @@ +1 +00:00:04,560 --> 00:00:06,640 +Welcome to the Hugging Face tasks series.   + +2 +00:00:07,280 --> 00:00:10,720 +In this video, we will take a look  +at the Text Summarization task.  + +3 +00:00:13,680 --> 00:00:16,480 +Summarization is a task of  +producing a shorter version   + +4 +00:00:16,480 --> 00:00:21,600 +of a document while preserving the relevant  +and important information in the document.  + +5 +00:00:25,040 --> 00:00:29,840 +Summarization models take a document to be  +summarized and output the summarized text.  + +6 +00:00:33,360 --> 00:00:40,240 +This task is evaluated on the ROUGE score. It’s  +based on the overlap between the produced sequence   + +7 +00:00:40,240 --> 00:00:48,000 +and the correct sequence. +You might see this as ROUGE-1,   + +8 +00:00:48,000 --> 00:00:55,600 +which is the overlap of single tokens and ROUGE-2,  +the overlap of subsequent token pairs. ROUGE-N   + +9 +00:00:55,600 --> 00:01:02,960 +refers to the overlap of n subsequent tokens.  +Here we see an example of how overlaps take place.  + +10 +00:01:06,160 --> 00:01:11,280 +An example dataset used for this task is  +called Extreme Summarization, XSUM. This   + +11 +00:01:11,280 --> 00:01:14,480 +dataset contains texts and  +their summarized versions.  + +12 +00:01:17,680 --> 00:01:21,280 +You can use summarization models  +to summarize research papers which   + +13 +00:01:21,280 --> 00:01:25,680 +would enable researchers to easily  +pick papers for their reading list.  + +14 +00:01:29,040 --> 00:01:39,520 +For more information about the Summarization  +task, check out the Hugging Face course. diff --git "a/subtitles/en/tasks_05_\360\237\244\227-tasks-translation.srt" "b/subtitles/en/tasks_05_\360\237\244\227-tasks-translation.srt" new file mode 100644 index 000000000..ff491e24c --- /dev/null +++ "b/subtitles/en/tasks_05_\360\237\244\227-tasks-translation.srt" @@ -0,0 +1,96 @@ +1 +00:00:04,569 --> 00:00:07,529 +Welcome to the Hugging Face tasks series. + +2 +00:00:07,529 --> 00:00:11,840 +In this video, we will take a look at the +Translation task. + +3 +00:00:11,840 --> 00:00:19,420 +Translation is the task of translating text +from one language to another. + +4 +00:00:19,420 --> 00:00:24,420 +These models take a text in the source language +and output the translation of that text in + +5 +00:00:24,420 --> 00:00:28,609 +the target language. + +6 +00:00:28,609 --> 00:00:31,619 +The task is evaluated on the BLEU score. + +7 +00:00:31,619 --> 00:00:38,430 +The score ranges from 0 to 1, in which 1 means +the translation perfectly matched and 0 did + +8 +00:00:38,430 --> 00:00:40,110 +not match at all. + +9 +00:00:40,110 --> 00:00:45,320 +BLEU is calculated over subsequent tokens +called n-grams. + +10 +00:00:45,320 --> 00:00:51,629 +Unigram refers to a single token while bi-gram +refers to token pairs and n-grams refer to + +11 +00:00:51,629 --> 00:00:56,219 +n subsequent tokens. + +12 +00:00:56,219 --> 00:01:01,859 +Machine translation datasets contain pairs +of text in a language and translation of the + +13 +00:01:01,859 --> 00:01:05,910 +text in another language. + +14 +00:01:05,910 --> 00:01:11,290 +These models can help you build conversational +agents across different languages. + +15 +00:01:11,290 --> 00:01:16,110 +One option is to translate the training data +used for the chatbot and train a separate + +16 +00:01:16,110 --> 00:01:19,970 +chatbot. + +17 +00:01:19,970 --> 00:01:24,950 +You can put one translation model from your +user’s language to the language your chatbot + +18 +00:01:24,950 --> 00:01:31,360 +is trained on, translate the user inputs and +do intent classification, take the output + +19 +00:01:31,360 --> 00:01:39,399 +of the chatbot and translate it from the language +your chatbot was trained on to the user’s + +20 +00:01:39,399 --> 00:01:40,850 +language. + +21 +00:01:40,850 --> 00:01:49,720 +For more information about the Translation +task, check out the Hugging Face course. diff --git a/subtitles/fr/63_data-processing-for-causal-language-modeling.srt b/subtitles/fr/63_data-processing-for-causal-language-modeling.srt index ebf9da64e..c4d912776 100644 --- a/subtitles/fr/63_data-processing-for-causal-language-modeling.srt +++ b/subtitles/fr/63_data-processing-for-causal-language-modeling.srt @@ -116,11 +116,11 @@ et nous ne perdons aucune séquence car elles sont trop courtes. Jusqu'à prése 30 00:03:05,840 --> 00:03:10,720 -des entrées pour la modélisation causale du langage, mais pas des étiquettes nécessaires à l'entraînement supervisée. +des entrées pour la modélisation du langage causal, mais pas des étiquettes nécessaires à l'entraînement supervisée. 31 00:03:11,600 --> 00:03:16,480 -Lorsque nous effectuons une modélisation causale du langage, nous n'avons pas besoin d'étiquettes supplémentaires pour les séquences d'entrée +Lorsque nous effectuons une modélisation du langage causal, nous n'avons pas besoin d'étiquettes supplémentaires pour les séquences d'entrée 32 00:03:16,480 --> 00:03:22,080 @@ -168,4 +168,4 @@ Donc vous voyez qu'il n'y a pas de magie 43 00:04:21,600 --> 00:04:27,840 -impliquée dans le traitement des données pour la modélisation du langage causal et ne nécessite que quelques étapes simples ! \ No newline at end of file +impliquée dans le traitement des données pour la modélisation du langage causal et ne nécessite que quelques étapes simples ! diff --git a/subtitles/fr/metadata_tasks.csv b/subtitles/fr/metadata_tasks.csv new file mode 100644 index 000000000..c40e0d858 --- /dev/null +++ b/subtitles/fr/metadata_tasks.csv @@ -0,0 +1,7 @@ +id,title,link,srt_filename +wVHdVlPScxA,🤗 Tasks: Token Classification,https://www.youtube.com/watch?v=wVHdVlPScxA&list=PLo2EIpI_JMQtyEr-sLJSy5_SnLCb4vtQf&index=1,subtitles/fr/tasks_00_🤗-tasks-token-classification.srt +ajPx5LwJD-I,🤗 Tasks: Question Answering,https://www.youtube.com/watch?v=ajPx5LwJD-I&list=PLo2EIpI_JMQtyEr-sLJSy5_SnLCb4vtQf&index=2,subtitles/fr/tasks_01_🤗-tasks-question-answering.srt +Vpjb1lu0MDk,🤗 Tasks: Causal Language Modeling,https://www.youtube.com/watch?v=Vpjb1lu0MDk&list=PLo2EIpI_JMQtyEr-sLJSy5_SnLCb4vtQf&index=3,subtitles/fr/tasks_02_🤗-tasks-causal-language-modeling.srt +mqElG5QJWUg,🤗 Tasks: Masked Language Modeling,https://www.youtube.com/watch?v=mqElG5QJWUg&list=PLo2EIpI_JMQtyEr-sLJSy5_SnLCb4vtQf&index=4,subtitles/fr/tasks_03_🤗-tasks-masked-language-modeling.srt +yHnr5Dk2zCI,🤗 Tasks: Summarization,https://www.youtube.com/watch?v=yHnr5Dk2zCI&list=PLo2EIpI_JMQtyEr-sLJSy5_SnLCb4vtQf&index=5,subtitles/fr/tasks_04_🤗-tasks-summarization.srt +1JvfrvZgi6c,🤗 Tasks: Translation,https://www.youtube.com/watch?v=1JvfrvZgi6c&list=PLo2EIpI_JMQtyEr-sLJSy5_SnLCb4vtQf&index=6,subtitles/fr/tasks_05_🤗-tasks-translation.srt diff --git "a/subtitles/fr/tasks_00_\360\237\244\227-tasks-token-classification.srt" "b/subtitles/fr/tasks_00_\360\237\244\227-tasks-token-classification.srt" new file mode 100644 index 000000000..bf391083f --- /dev/null +++ "b/subtitles/fr/tasks_00_\360\237\244\227-tasks-token-classification.srt" @@ -0,0 +1,99 @@ +1 +00:00:04,520 --> 00:00:07,400 +Bienvenue dans la série d'Hugging Face sur les tâches ! + +2 +00:00:07,400 --> 00:00:11,870 +Dans cette vidéo, nous allons jeter un coup d'œil à la tâche de classification de tokens. + +3 +00:00:11,870 --> 00:00:17,900 +La classification de tokens consiste à attribuer une étiquette à chaque token d'une phrase + +4 +00:00:17,900 --> 00:00:23,310 +Il existe plusieurs tâches de classification de tokens, les plus courantes étant la reconnaissance d’entités nommées + +5 +00:00:23,310 --> 00:00:26,430 +et le « part-of-speech ». + +6 +00:00:26,430 --> 00:00:31,640 +Jetons un coup d'œil rapide à la tâche de reconnaissance d'entités nommées + +7 +00:00:31,640 --> 00:00:38,400 +L'objectif de cette tâche est de trouver les entités dans un texte, comme une personne, un lieu + +8 +00:00:38,400 --> 00:00:40,210 +ou une organisation. + +9 +00:00:40,210 --> 00:00:45,250 +Cette tâche est formulée comme l'étiquetage de chaque token avec une classe pour chaque entité, + +10 +00:00:45,250 --> 00:00:51,719 +et une autre classe pour les tokens qui n'ont pas d'entité. + +11 +00:00:51,719 --> 00:00:55,670 +Une autre tâche de classification de tokens est le « part-of-speech ». + +12 +00:00:55,670 --> 00:01:01,399 +L'objectif de cette tâche est d'étiqueter les mots pour une partie particulière du texte, comme + +13 +00:01:01,399 --> 00:01:05,900 +un nom, un pronom, un adjectif, un verbe, etc. + +14 +00:01:05,900 --> 00:01:11,270 +Cette tâche est formulée comme l'étiquetage de chaque token avec les parties du texte. + +15 +00:01:11,270 --> 00:01:19,659 +Les modèles de classification de tokens sont évalués sur l'exactitude, le rappel, la précision et le score F1. + +16 +00:01:19,659 --> 00:01:22,950 +Les métriques sont calculées pour chacune des classes. + +17 +00:01:22,950 --> 00:01:28,040 +Nous calculons les vrais positifs, les vrais négatifs et les faux positifs pour calculer la précision + +18 +00:01:28,040 --> 00:01:31,829 +et le rappel, et prenons leur moyenne harmonique pour obtenir le score F1. + +19 +00:01:31,829 --> 00:01:42,329 +Ensuite, nous les calculons pour chaque classe et prenons la moyenne globale pour évaluer notre modèle. + +20 +00:01:42,329 --> 00:01:45,680 +Un exemple de jeu de données utilisé pour cette tâche est ConLL2003. + +21 +00:01:45,680 --> 00:01:51,750 +Ici, chaque token appartient à une certaine classe d'entités nommées, désignées par les indices de la + +22 +00:01:51,750 --> 00:01:55,380 +liste contenant les étiquettes. + +23 +00:01:55,380 --> 00:02:00,720 +Vous pouvez extraire des informations importantes de factures à l'aide de modèles de reconnaissance d'entités nommées, + +24 +00:02:00,720 --> 00:02:07,070 +telles que la date, le nom de l'organisation ou l'adresse. + +25 +00:02:07,070 --> 00:02:16,840 +Pour plus d'informations sur la tâche de classification de tokens, consultez le cours d'Hugging Face. diff --git "a/subtitles/fr/tasks_01_\360\237\244\227-tasks-question-answering.srt" "b/subtitles/fr/tasks_01_\360\237\244\227-tasks-question-answering.srt" new file mode 100644 index 000000000..da7060062 --- /dev/null +++ "b/subtitles/fr/tasks_01_\360\237\244\227-tasks-question-answering.srt" @@ -0,0 +1,71 @@ +1 +00:00:04,400 --> 00:00:06,480 +Bienvenue dans la série d'Hugging Face sur les tâches ! + +2 +00:00:07,200 --> 00:00:10,080 +Dans cette vidéo, nous allons examiner la tâche de réponse aux questions. + +3 +00:00:13,120 --> 00:00:17,200 +La réponse aux questions consiste à extraire une réponse dans un document donné. + +4 +00:00:21,120 --> 00:00:25,600 +Les modèles de réponse aux questions prennent un contexte, qui est le document dans lequel vous souhaitez effectuer une recherche, + +5 +00:00:26,240 --> 00:00:31,440 +et une question et renvoient une réponse. Notez que la réponse n'est pas générée, + +6 +00:00:31,440 --> 00:00:37,600 +mais extraite du contexte. Ce type de réponse aux questions est appelé extractive. + +7 +00:00:42,320 --> 00:00:46,960 +La tâche est évaluée sur deux statistiques, la correspondance exacte et le score F1. + +8 +00:00:49,680 --> 00:00:52,320 +Comme son nom l'indique, la correspondance exacte recherche une + +9 +00:00:52,320 --> 00:00:57,840 +correspondance exacte entre la réponse prédite et la bonne réponse. + +10 +00:01:00,080 --> 00:01:05,520 +Une métrique couramment utilisée est le F1-Score, qui est calculé sur des tokens prédits + +11 +00:01:05,520 --> 00:01:10,960 +correctement et incorrectement. Il est calculé sur la moyenne de deux métriques appelées + +12 +00:01:10,960 --> 00:01:16,560 +précision et rappel, qui sont des métriques largement utilisées dans les problèmes de classification. + +13 +00:01:20,880 --> 00:01:28,240 +Un exemple de jeu de données utilisé pour cette tâche est appelé SQuAD. Ce jeu de données contient des contextes, des questions + +14 +00:01:28,240 --> 00:01:32,080 +et les réponses obtenues à partir d'articles de Wikipédia en anglais. + +15 +00:01:35,440 --> 00:01:39,520 +Vous pouvez utiliser des modèles de réponse aux questions pour répondre automatiquement aux questions posées + +16 +00:01:39,520 --> 00:01:46,480 +par vos clients. Vous avez simplement besoin d'un document contenant des informations sur votre entreprise + +17 +00:01:47,200 --> 00:01:53,840 +et interrogez ce document avec les questions posées par vos clients. + +18 +00:01:55,680 --> 00:02:06,160 +Pour plus d'informations sur la tâche de réponse aux questions, consultez le cours d'Hugging Face. diff --git "a/subtitles/fr/tasks_02_\360\237\244\227-tasks-causal-language-modeling.srt" "b/subtitles/fr/tasks_02_\360\237\244\227-tasks-causal-language-modeling.srt" new file mode 100644 index 000000000..27e05d726 --- /dev/null +++ "b/subtitles/fr/tasks_02_\360\237\244\227-tasks-causal-language-modeling.srt" @@ -0,0 +1,51 @@ +1 +00:00:04,560 --> 00:00:06,640 +Bienvenue dans la série d'Hugging Face sur les tâches ! + +2 +00:00:07,200 --> 00:00:10,400 +Dans cette vidéo, nous allons jeter un œil à la modélisation du langage causal. + +3 +00:00:13,600 --> 00:00:16,880 +La modélisation du langage causal consiste à prédire le + +4 +00:00:16,880 --> 00:00:21,920 +mot suivant dans une phrase, compte tenu de tous les mots précédents. Cette tâche est très + +5 +00:00:21,920 --> 00:00:29,920 +similaire à la fonction de correction automatique que vous pourriez avoir sur votre téléphone. + +6 +00:00:29,920 --> 00:00:34,720 +Ces modèles prennent une séquence à compléter et génèrent la séquence complète. + +7 +00:00:38,640 --> 00:00:44,160 +Les métriques de classification ne peuvent pas être utilisées, car il n'y a pas de réponse correcte unique pour la complétion. + +8 +00:00:44,960 --> 00:00:49,280 +Au lieu de cela, nous évaluons la distribution du texte complété par le modèle. + +9 +00:00:50,800 --> 00:00:55,440 +Une métrique courante pour ce faire est la perte d'entropie croisée. La perplexité est + +10 +00:00:55,440 --> 00:01:01,280 +aussi une métrique largement utilisée et elle est calculée comme l'exponentielle de la perte d'entropie croisée. + +11 +00:01:05,200 --> 00:01:11,840 +Vous pouvez utiliser n'importe quel jeu de données avec du texte brut et tokeniser le texte pour préparer les données. + +12 +00:01:15,040 --> 00:01:18,240 +Les modèles de langage causal peuvent être utilisés pour générer du code. + +13 +00:01:22,480 --> 00:01:33,200 +Pour plus d'informations sur la tâche de modélisation du langage causal, consultez le cours d'Hugging Face. diff --git "a/subtitles/fr/tasks_03_\360\237\244\227-tasks-masked-language-modeling.srt" "b/subtitles/fr/tasks_03_\360\237\244\227-tasks-masked-language-modeling.srt" new file mode 100644 index 000000000..ca32dc906 --- /dev/null +++ "b/subtitles/fr/tasks_03_\360\237\244\227-tasks-masked-language-modeling.srt" @@ -0,0 +1,71 @@ +1 +00:00:04,660 --> 00:00:07,589 +Bienvenue dans la série d'Hugging Face sur les tâches ! + +2 +00:00:07,589 --> 00:00:13,730 +Dans cette vidéo, nous allons jeter un œil à la modélisation du langage masqué. + +3 +00:00:13,730 --> 00:00:20,720 +La modélisation du langage masqué consiste à prédire quels mots doivent remplir les blancs d'une + +4 +00:00:20,720 --> 00:00:23,500 +phrase. + +5 +00:00:23,500 --> 00:00:32,870 +Ces modèles prennent un texte masqué en entrée et génèrent les valeurs possibles pour ce masque. + +6 +00:00:32,870 --> 00:00:37,550 +La modélisation en langage masqué est pratique avant de finetuner votre modèle pour votre tâche. + +7 +00:00:37,550 --> 00:00:43,579 +Par exemple, si vous devez utiliser un modèle dans un domaine spécifique, par exemple des documents biomédicaux, des + +8 +00:00:43,579 --> 00:00:49,050 +modèles comme BERT traiteront vos mots spécifiques à un domaine comme des tokens rares. + +9 +00:00:49,050 --> 00:00:54,220 +Si vous entraînez un modèle de langage masqué à l'aide de votre corpus biomédical, puis finetunez + +10 +00:00:54,220 --> 00:01:02,929 +votre modèle sur une tâche en aval, vous obtiendrez de meilleures performances. + +11 +00:01:02,929 --> 00:01:07,799 +Les métriques de classification ne peuvent pas être utilisées car il n'y a pas de réponse correcte unique aux + +12 +00:01:07,799 --> 00:01:08,799 +valeurs du masque. + +13 +00:01:08,799 --> 00:01:12,900 +Au lieu de cela, nous évaluons la distribution des valeurs du masque. + +14 +00:01:12,900 --> 00:01:16,590 +Une métrique courante pour ce faire est la perte d'entropie croisée. + +15 +00:01:16,590 --> 00:01:22,010 +La perplexité est aussi une métrique largement utilisée et elle est calculée comme l'exponentielle de la + +16 +00:01:22,010 --> 00:01:27,240 +perte d'entropie croisée. + +17 +00:01:27,240 --> 00:01:35,680 +Vous pouvez utiliser n'importe quel jeu de données avec du texte brut et tokeniser le texte pour masquer les données. + +18 +00:01:35,680 --> 00:01:44,710 +Pour plus d'informations sur la modélisation du langage masqué, consultez le cours d'Hugging Face. diff --git "a/subtitles/fr/tasks_04_\360\237\244\227-tasks-summarization.srt" "b/subtitles/fr/tasks_04_\360\237\244\227-tasks-summarization.srt" new file mode 100644 index 000000000..8a19f28bd --- /dev/null +++ "b/subtitles/fr/tasks_04_\360\237\244\227-tasks-summarization.srt" @@ -0,0 +1,55 @@ +1 +00:00:04,560 --> 00:00:06,640 +Bienvenue dans la série d'Hugging Face sur les tâches ! + +2 +00:00:07,280 --> 00:00:10,720 +Dans cette vidéo, nous allons examiner la tâche de résumé de texte. + +3 +00:00:13,200 --> 00:00:16,480 +Le résumé consiste à produire une version plus courte + +4 +00:00:16,480 --> 00:00:21,600 +d'un document tout en préservant les informations pertinentes et importantes dans le document. + +5 +00:00:25,040 --> 00:00:29,840 +Les modèles de résumé prennent un document à résumer et génèrent le texte résumé. + +6 +00:00:33,360 --> 00:00:40,240 +Cette tâche est évaluée sur le score ROUGE. Il est basé sur le chevauchement entre la séquence produite + +7 +00:00:40,240 --> 00:00:48,000 +et la séquence correcte. Vous pouvez voir ceci comme ROUGE-1, + +8 +00:00:48,000 --> 00:00:55,600 +qui est le chevauchement de tokens uniques et ROUGE-2, le chevauchement de paires de tokens successives. ROUGE-N + +9 +00:00:55,600 --> 00:01:02,960 +fait référence au chevauchement de N tokens successifs. Ici, nous voyons un exemple de la façon dont les chevauchements ont lieu. + +10 +00:01:06,160 --> 00:01:11,280 +Un exemple de jeu de données utilisé pour cette tâche s'appelle Extreme Summarization (XSUM). + +11 +00:01:11,280 --> 00:01:14,480 +Ce jeu de données contient des textes et leurs versions résumées. + +12 +00:01:17,680 --> 00:01:21,280 +Vous pouvez utiliser des modèles de résumé pour résumer les articles de recherche, ce + +13 +00:01:21,280 --> 00:01:25,680 +qui permettrait aux chercheurs de choisir facilement des articles pour leur liste de lecture. + +14 +00:01:29,040 --> 00:01:39,520 +Pour plus d'informations sur la tâche de résumé de textes, consultez le cours d'Hugging Face. diff --git "a/subtitles/fr/tasks_05_\360\237\244\227-tasks-translation.srt" "b/subtitles/fr/tasks_05_\360\237\244\227-tasks-translation.srt" new file mode 100644 index 000000000..06a851321 --- /dev/null +++ "b/subtitles/fr/tasks_05_\360\237\244\227-tasks-translation.srt" @@ -0,0 +1,83 @@ +1 +00:00:04,569 --> 00:00:07,529 +Bienvenue dans la série d'Hugging Face sur les tâches ! + +2 +00:00:07,529 --> 00:00:11,840 +Dans cette vidéo, nous allons jeter un œil à la tâche de traduction. + +3 +00:00:11,840 --> 00:00:19,420 +La traduction est la tâche de traduire un texte d'une langue à une autre. + +4 +00:00:19,420 --> 00:00:24,420 +Ces modèles prennent un texte dans la langue source et génèrent la traduction de ce texte dans + +5 +00:00:24,420 --> 00:00:28,609 +la langue cible. + +6 +00:00:28,609 --> 00:00:31,619 +La tâche est évaluée sur le score BLEU. + +7 +00:00:31,619 --> 00:00:38,430 +Le score varie de 0 à 1, où 1 signifie que la traduction correspond parfaitement et 0 ne + +8 +00:00:38,430 --> 00:00:40,110 +correspond pas du tout. + +9 +00:00:40,110 --> 00:00:45,320 +BLEU est calculé sur les tokens successifs appelés n-grammes. + +10 +00:00:45,320 --> 00:00:51,629 +« unigram » fait référence à un seul token tandis que bi-gramme fait référence à des paires de tokens et n-grammes fait référence à + +11 +00:00:51,629 --> 00:00:56,219 +n tokens successifs. + +12 +00:00:56,219 --> 00:01:01,859 +Les jeux de données de traduction automatique contiennent des paires de texte dans une langue et la traduction du + +13 +00:01:01,859 --> 00:01:05,910 +texte dans une autre langue. + +14 +00:01:05,910 --> 00:01:11,290 +Ces modèles peuvent vous aider à créer des agents conversationnels dans différentes langues. + +15 +00:01:11,290 --> 00:01:16,110 +Une option consiste à traduire les données d'entraînement utilisées pour le chatbot et à entraîner un + +16 +00:01:16,110 --> 00:01:19,970 +chatbot séparé. + +17 +00:01:19,970 --> 00:01:24,950 +Vous pouvez mettre un modèle de traduction de la langue de votre utilisateur vers la langue dans laquelle votre chatbot + +18 +00:01:24,950 --> 00:01:31,360 +est entraîné, traduire les entrées de l'utilisateur et effectuer une classification d'intention, prendre la sortie + +19 +00:01:31,360 --> 00:01:39,399 +du chatbot et la traduire de la langue dans laquelle votre chatbot a été entraîné vers la + +20 +00:01:39,399 --> 00:01:40,850 +langue de l'utilisateur. + +21 +00:01:40,850 --> 00:01:49,720 +Pour plus d'informations sur la tâche de traduction, consultez le cours d'Hugging Face. diff --git a/subtitles/fr/titles-and-descriptions.txt b/subtitles/fr/titles-and-descriptions.txt index ad102d6bf..3ff3a64c4 100644 --- a/subtitles/fr/titles-and-descriptions.txt +++ b/subtitles/fr/titles-and-descriptions.txt @@ -1005,9 +1005,9 @@ Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://hugg -Traitement des données pour la modélisation causale du langage +Traitement des données pour la modélisation du langage causal -Dans cette vidéo, nous allons voir comment prétraiter un jeu de données pour une tâche de modélisation causale du langage. +Dans cette vidéo, nous allons voir comment prétraiter un jeu de données pour une tâche de modélisation du langage causal. Intervenant : Leandro von Werra Traduction : Loïck Bourdois Cette vidéo fait partie du cours Hugging Face : http://huggingface.co/course/fr/chapter7 @@ -1210,4 +1210,98 @@ Vidéos connexes : - Utilisation d'un débogueur dans un terminal : https://youtu.be/5PkZ4rbHL6c - Demander de l'aide sur les forums : https://youtu.be/S2EEG3JIt2A Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 -Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join \ No newline at end of file +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join + + + +🤗 Tasks : Classification de tokens + +Cette vidéo fait partie du cours d’Hugging Face : http://huggingface.co/course/fr +Intervenante : Merve Noyan +Traduction : Loïck Bourdois +Un aperçu de la tâche de classification de tokens. +Vous pouvez en savoir plus sur la classification de tokens dans cette section du cours : https://huggingface.co/course/fr/chapter7/2 +Vidéos connexes : +- Dans le pipeline de classification de tokens (PyTorch) : https://youtu.be/0E7ltQB7fM8 +- Dans le pipeline de classification de tokens (TensorFlow) : https://youtu.be/PrX4CjrVnNc +- Traitement des données pour la classification de tokens : https://youtu.be/iY2AZYdZAr0 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 + + + +🤗 Tasks : Réponse aux questions + +Cette vidéo fait partie du cours d’Hugging Face : http://huggingface.co/course/fr +Intervenante : Merve Noyan +Traduction : Loïck Bourdois +Un aperçu de la tâche de réponse aux questions. +Vous pouvez en savoir plus sur la réponse aux questions dans cette section du cours : https://huggingface.co/course/fr/chapter7/7 +Vidéos connexes : +- Dans le pipeline de réponse aux questions (PyTorch) : https://youtu.be/_wxyB3j3mk4 +- Dans le pipeline de réponse aux questions (TensorFlow) : https://youtu.be/b3u8RzBCX9Y +- Traitement des données pour la réponse aux questions : https://youtu.be/qgaM0weJHpA +- L'étape de post-traitement en réponse aux questions (PyTorch) : https://youtu.be/BNy08iIWVJM +- L'étape de post-traitement en réponse aux questions (TensorFlow) : https://youtu.be/VN67ZpN33Ss +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 + + + +🤗 Tasks : Modélisation du langage causal + +Cette vidéo fait partie du cours d’Hugging Face : http://huggingface.co/course/fr +Intervenante : Merve Noyan +Traduction : Loïck Bourdois +Un aperçu de la tâche de modélisation du langage causal. +Vous pouvez en savoir plus sur la modélisation du langage causal dans cette section du cours : https://huggingface.co/course/fr/chapter7/6 +Vidéos connexes : +- Traitement des données pour la modélisation du langage causal : https://youtu.be/ma1TrR7gE7I +- Qu'est-ce que la perplexité ? : https://youtu.be/NURcDHhYe98 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 + + + +🤗 Tasks : Modélisation du langage masqué + +Cette vidéo fait partie du cours d’Hugging Face : http://huggingface.co/course/fr +Intervenante : Merve Noyan +Traduction : Loïck Bourdois +Un aperçu de la tâche de modélisation du langage masqué. +Vous pouvez en savoir plus sur la modélisation du langage masqué dans cette section du cours : https://huggingface.co/course/fr/chapter7/3 +Vidéos connexes : +- Traitement des données pour la modélisation du langage masqué : https://youtu.be/8PmhEIXhBvI +- Qu'est-ce que la perplexité ? : https://youtu.be/NURcDHhYe98 +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 + + + +🤗 Tasks : Résumé de textes + +Cette vidéo fait partie du cours d’Hugging Face : http://huggingface.co/course/fr +Intervenante : Merve Noyan +Traduction : Loïck Bourdois +Un aperçu de la tâche de résumé de textes. +Vous pouvez en savoir plus sur le résumé de textes dans cette section du cours : https://huggingface.co/course/fr/chapter7/5 +Vidéos connexes : +- Traitement des données pour le résumé : https://youtu.be/1m7BerpSq8A +- Qu'est-ce que la métrique ROUGE ? https://youtu.be/TMshhnrEXlg +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 + + + +🤗 Tasks : Traduction + +Cette vidéo fait partie du cours d’Hugging Face : http://huggingface.co/course/fr +Intervenante : Merve Noyan +Traduction : Loïck Bourdois +Un aperçu de la tâche de traduction. +Vous pouvez en savoir plus sur la traduction dans cette section du cours : https://huggingface.co/course/fr/chapter7/4 +Vidéos connexes : +- Traitement des données pour la traduction : https://youtu.be/XAR8jnZZuUs +- Qu'est-ce que la métrique BLEU ? : https://youtu.be/M05L1DhFqcw +Vous n'avez pas de compte Hugging Face ? Inscrivez-vous maintenant : http://huggingface.co/join +Vous avez une question ? Consultez le forum d’Hugging Face : https://discuss.huggingface.co/c/course/20 \ No newline at end of file diff --git a/subtitles/ru/00_welcome-to-the-hugging-face-course.srt b/subtitles/ru/00_welcome-to-the-hugging-face-course.srt new file mode 100644 index 000000000..7f7cf0c28 --- /dev/null +++ b/subtitles/ru/00_welcome-to-the-hugging-face-course.srt @@ -0,0 +1,411 @@ +1 +00:00:05,850 --> 00:00:07,713 +Добро пожаловать на курс Hugging Face. + +2 +00:00:08,550 --> 00:00:10,320 +Этот курс был разработан, чтобы научить вас + +3 +00:00:10,320 --> 00:00:12,750 +всему, что касается экосистемы Hugging Face, + +4 +00:00:12,750 --> 00:00:14,700 +как использовать набор данных и хаб моделей, + +5 +00:00:14,700 --> 00:00:16,803 +а также все наши библиотеки с открытым исходным кодом. + +6 +00:00:18,300 --> 00:00:19,950 +Вот содержание. + +7 +00:00:19,950 --> 00:00:22,770 +Как вы можете видеть, он разделен на три раздела, + +8 +00:00:22,770 --> 00:00:25,110 +которые постепенно становятся все более сложными. + +9 +00:00:25,110 --> 00:00:28,500 +На данном этапе выпущены первые два раздела. + +10 +00:00:28,500 --> 00:00:30,120 +Итак, сначала мы научим вас основам + +11 +00:00:30,120 --> 00:00:32,250 +как использовать модель Transformer, + +12 +00:00:32,250 --> 00:00:34,230 +дообучить ее на собственном наборе данных + +13 +00:00:34,230 --> 00:00:36,960 + и поделиться результатом с сообществом. + +14 +00:00:36,960 --> 00:00:39,420 +Во-вторых, мы глубже погрузимся в наши библиотеки + +15 +00:00:39,420 --> 00:00:42,360 +и научим вас решать любые задачи NLP. + +16 +00:00:42,360 --> 00:00:44,430 +Мы активно работаем над последним разделом + +17 +00:00:44,430 --> 00:00:47,280 +и надеемся, что он будет готов для вас к весне 2022 года. + +18 +00:00:48,510 --> 00:00:50,880 +Первая глава не требует технических знаний + +19 +00:00:50,880 --> 00:00:52,320 +и является хорошим введением, чтобы изучить, + +20 +00:00:52,320 --> 00:00:54,180 +что могут модели Transformers + +21 +00:00:54,180 --> 00:00:56,883 +и как они могут быть полезны для вас или вашей компании. + +22 +00:00:58,050 --> 00:01:01,110 +Следующие главы требуют хорошего знания Python + +23 +00:01:01,110 --> 00:01:02,130 +и некоторых базовых знаний в + +24 +00:01:02,130 --> 00:01:04,350 +Машинном обучении и Глубоком обучении. + +25 +00:01:04,350 --> 00:01:07,110 +Если вы не знаете, что такое тренировочное и валидационное множества + +26 +00:01:07,110 --> 00:01:09,360 +или что означает градиентный спуск, + +27 +00:01:09,360 --> 00:01:11,340 +вам следует изучить вводный курс, + +28 +00:01:11,340 --> 00:01:14,863 +например, опубликованный на сайтах deeplearning.ai или fast.ai. + +29 +00:01:16,200 --> 00:01:17,910 +Также будет лучше, если вы владеете основами + +30 +00:01:17,910 --> 00:01:21,150 +одного из фреймворков глубокого обучения, PyTorch или TensorFlow. + +31 +00:01:21,150 --> 00:01:23,520 +Каждая часть материала, представленного в этом курсе, + +32 +00:01:23,520 --> 00:01:25,590 +имеет версию на обоих этих фреймворках, + +33 +00:01:25,590 --> 00:01:26,730 +поэтому вы сможете выбрать тот, + +34 +00:01:26,730 --> 00:01:28,230 +с которым вам удобнее работать. + +35 +00:01:29,550 --> 00:01:31,740 +Это команда, которая разработала данный курс. + +36 +00:01:31,740 --> 00:01:33,120 +Теперь я предоставлю каждому из выступающих + +37 +00:01:33,120 --> 00:01:34,570 +возможность кратко представиться. + +38 +00:01:37,230 --> 00:01:38,880 +- Привет, меня зовут Мэтью, + +39 +00:01:38,880 --> 00:01:41,610 +и я инженер по машинному обучению в компании Hugging Face. + +40 +00:01:41,610 --> 00:01:43,200 +Я работаю в команде по работе с открытым исходным кодом + +41 +00:01:43,200 --> 00:01:45,180 +и отвечаю там за поддержку, в частности, + +42 +00:01:45,180 --> 00:01:47,280 +кода TensorFlow. + +43 +00:01:47,280 --> 00:01:50,130 +Ранее я работал инженером по машинному обучению в компании Parsley, + +44 +00:01:50,130 --> 00:01:52,620 +которая недавно была приобретена компанией Automatic, + +45 +00:01:52,620 --> 00:01:54,210 +а до этого был постдокторантом-исследователем + +46 +00:01:54,210 --> 00:01:57,000 +в Тринити-колледже в Дублине в Ирландии, + +47 +00:01:57,000 --> 00:02:00,093 +занимался компьютерной генетикой и заболеваниями сетчатки. + +48 +00:02:02,400 --> 00:02:03,870 +- Привет, я Лисандр. + +49 +00:02:03,870 --> 00:02:05,640 +Я инженер по машинному обучению в Hugging Face + +50 +00:02:05,640 --> 00:02:08,700 +и, в частности, являюсь частью команды по работе с открытым исходным кодом. + +51 +00:02:08,700 --> 00:02:10,890 +Я работаю в Hugging Face уже несколько лет, + +52 +00:02:10,890 --> 00:02:12,300 +и вместе с членами моей команды + +53 +00:02:12,300 --> 00:02:13,890 +я работал над большинством инструментов, + +54 +00:02:13,890 --> 00:02:15,790 +которые вы увидите в этом курсе. + +55 +00:02:18,270 --> 00:02:20,130 +- Привет, я Сильвен. + +56 +00:02:20,130 --> 00:02:22,140 +Я инженер-исследователь в Hugging Face + +57 +00:02:22,140 --> 00:02:25,830 +и один из главных сопровождающих библиотеки Transformers. + +58 +00:02:25,830 --> 00:02:28,110 +Ранее я работал в компании fast.ai, + +59 +00:02:28,110 --> 00:02:30,420 +где помогал разрабатывать библиотеку fast.ai, + +60 +00:02:30,420 --> 00:02:32,220 +а также онлайн-книгу. + +61 +00:02:32,220 --> 00:02:35,340 +До этого я был учителем математики и информатики + +62 +00:02:35,340 --> 00:02:36,173 +во Франции. + +63 +00:02:38,550 --> 00:02:41,340 +- Привет, меня зовут Саша, и я исследователь в компании Hugging Face, + +64 +00:02:41,340 --> 00:02:42,420 +работаю над этическим, + +65 +00:02:42,420 --> 00:02:46,230 +экологическим и социальным воздействием моделей машинного обучения. + +66 +00:02:46,230 --> 00:02:49,020 +Ранее я была постдокторантом-исследователем в университете Mila + +67 +00:02:49,020 --> 00:02:50,400 +в Монреале, + +68 +00:02:50,400 --> 00:02:53,040 +а также работала в качестве исследователя прикладного ИИ + +69 +00:02:53,040 --> 00:02:55,140 +для UN Global Pulse. + +70 +00:02:55,140 --> 00:02:57,300 +Я участвовала в таких проектах, как CodeCarbon + +71 +00:02:57,300 --> 00:02:59,790 +и Machine Learning Impacts Calculator + +72 +00:02:59,790 --> 00:03:02,390 +для оценки углеродного следа машинного обучения. + +73 +00:03:05,160 --> 00:03:07,650 +- Привет, меня зовут Мерве, и я являюсь адвокатом разработчиков + +74 +00:03:07,650 --> 00:03:09,390 +в компании Hugging Face. + +75 +00:03:09,390 --> 00:03:12,480 +Ранее я работала инженером по машинному обучению, + +76 +00:03:12,480 --> 00:03:15,360 +создавая инструменты NLP и чат-боты. + +77 +00:03:15,360 --> 00:03:17,670 +В настоящее время я работаю над улучшением хаба + +78 +00:03:17,670 --> 00:03:19,563 +и демократизацией машинного обучения. + +79 +00:03:22,140 --> 00:03:23,670 +- Привет всем. + +80 +00:03:23,670 --> 00:03:27,210 +Меня зовут Люсиль, и я инженер по машинному обучению + +81 +00:03:27,210 --> 00:03:28,353 +в Hugging Face. + +82 +00:03:29,580 --> 00:03:32,550 +Если в двух предложениях рассказать, кто я такая, + +83 +00:03:32,550 --> 00:03:35,590 +я занимаюсь разработкой и поддержкой инструментов с открытым исходным кодом, + +84 +00:03:36,600 --> 00:03:39,595 +а также участвую в нескольких исследовательских проектах + +85 +00:03:39,595 --> 00:03:41,795 +в области Natural Language Processing. + +86 +00:03:44,610 --> 00:03:45,540 +- Хорошего дня. + +87 +00:03:45,540 --> 00:03:47,550 +Меня зовут Льюис, я инженер по машинному обучению + +88 +00:03:47,550 --> 00:03:50,130 + в команде разработчиков открытого программного обеспечения Hugging Face. + +89 +00:03:50,130 --> 00:03:53,490 +Я увлечен разработкой инструментов для сообщества NLP, + +90 +00:03:53,490 --> 00:03:55,050 +и вы можете увидеть меня + +91 +00:03:55,050 --> 00:03:56,910 +на многих мероприятиях Hugging Face. + +92 +00:03:56,910 --> 00:03:58,470 +До присоединения к Hugging Face + +93 +00:03:58,470 --> 00:03:59,790 +я несколько лет занимался разработкой + +94 +00:03:59,790 --> 00:04:01,860 +приложений машинного обучения для стартапов + +95 +00:04:01,860 --> 00:04:04,230 +и предприятий в области NLP, + +96 +00:04:04,230 --> 00:04:07,260 +топологического анализа данных и временных рядов. + +97 +00:04:07,260 --> 00:04:10,110 +В прошлой жизни я был физиком-теоретиком, + +98 +00:04:10,110 --> 00:04:11,760 +исследовал столкновения частиц + +99 +00:04:11,760 --> 00:04:13,560 +на Большом адронном коллайдере и так далее. + +100 +00:04:15,900 --> 00:04:18,450 +- Привет, меня зовут Леандро, я инженер по машинному обучению + +101 +00:04:18,450 --> 00:04:21,030 +в команде открытого кода Hugging Face. + +102 +00:04:21,030 --> 00:04:23,460 +До прихода в Hugging Face я работал специалистом по анализу данных + +103 +00:04:23,460 --> 00:04:26,733 +в Швейцарии и преподавал науку о данных в университете. diff --git a/subtitles/ru/01_the-pipeline-function.srt b/subtitles/ru/01_the-pipeline-function.srt new file mode 100644 index 000000000..311e5dfe9 --- /dev/null +++ b/subtitles/ru/01_the-pipeline-function.srt @@ -0,0 +1,400 @@ +1 +00:00:00,069 --> 00:00:01,341 + + +2 +00:00:01,341 --> 00:00:02,449 + + +3 +00:00:02,449 --> 00:00:05,880 + + +4 +00:00:05,880 --> 00:00:07,080 +- Функция pipeline (конвеер). + +5 +00:00:09,540 --> 00:00:12,020 +Функция pipeline является наиболее высокоуровневым API + +6 +00:00:12,020 --> 00:00:14,010 +библиотеки Transformers. + +7 +00:00:14,010 --> 00:00:16,050 +Она объединяет все шаги + +8 +00:00:16,050 --> 00:00:18,873 +перехода от необработанных текстов к пригодным для использования прогнозам. + +9 +00:00:20,228 --> 00:00:22,980 +Используемая модель лежит в основе конвейера, + +10 +00:00:22,980 --> 00:00:24,390 +но конвейер также включает + +11 +00:00:24,390 --> 00:00:26,610 +всю необходимую пред-обработку, + +12 +00:00:26,610 --> 00:00:30,240 +поскольку модель ожидает не тексты, а числа, + +13 +00:00:30,240 --> 00:00:32,040 +а также некоторую постобработку, + +14 +00:00:32,040 --> 00:00:34,533 +чтобы сделать вывод модели человекочитаемым. + +15 +00:00:35,910 --> 00:00:37,593 +Давайте рассмотрим первый пример + +16 +00:00:37,593 --> 00:00:39,693 +с конвейером анализа настроений. + +17 +00:00:40,740 --> 00:00:44,670 +Этот конвейер выполняет классификацию текста на заданном входе + +18 +00:00:44,670 --> 00:00:46,953 +и определяет, является ли он позитивным или негативным. + +19 +00:00:47,910 --> 00:00:51,750 +Здесь он приписывает положительную оценку данному тексту + +20 +00:00:51,750 --> 00:00:54,413 +с достоверностью 95%. + +21 +00:00:55,650 --> 00:00:58,470 +Вы можете передать множество текстов в один конвейер, + +22 +00:00:58,470 --> 00:01:00,270 +которые будут обработаны и переданы + +23 +00:01:00,270 --> 00:01:02,673 +через модель вместе как батч (пакет). + +24 +00:01:03,570 --> 00:01:05,970 +На выходе получается список отдельных результатов + +25 +00:01:05,970 --> 00:01:07,923 +в том же порядке, что и входные тексты. + +26 +00:01:08,790 --> 00:01:12,270 +Здесь мы находим ту же метку и оценку для первого текста, + +27 +00:01:12,270 --> 00:01:14,443 +а второй текст оценивается как отрицательный + +28 +00:01:14,443 --> 00:01:17,243 +с достоверностью 99,9%. + +29 +00:01:18,720 --> 00:01:20,700 +Конвейер zero-shot классификации + +30 +00:01:20,700 --> 00:01:23,610 +это более общий конвейер классификации текста, + +31 +00:01:23,610 --> 00:01:26,370 +он позволяет вам предоставлять нужные метки. + +32 +00:01:26,370 --> 00:01:29,850 +Здесь мы хотим классифицировать наш входной текст по меткам, + +33 +00:01:29,850 --> 00:01:32,643 +образование, политика и бизнес. + +34 +00:01:33,540 --> 00:01:35,580 +Конвейер успешно распознает + +35 +00:01:35,580 --> 00:01:38,280 +это скорее образование, чем другие метки, + +36 +00:01:38,280 --> 00:01:40,643 +с достоверностью 84%. + +37 +00:01:41,670 --> 00:01:43,110 +Переходим к другим задачам, + +38 +00:01:43,110 --> 00:01:45,030 +конвейер генерации текста будет + +39 +00:01:45,030 --> 00:01:46,533 +автоматически заполнять заданную подсказку. + +40 +00:01:47,460 --> 00:01:49,980 +Вывод генерируется с некоторой долей случайности, + +41 +00:01:49,980 --> 00:01:52,800 +поэтому он меняется каждый раз, когда вы вызываете объект генератора + +42 +00:01:52,800 --> 00:01:53,763 +для заданной строки. + +43 +00:01:54,990 --> 00:01:57,123 +До сих пор мы использовали API конвейера + +44 +00:01:57,123 --> 00:02:00,360 +с моделью по умолчанию, связанной с каждой задачей, + +45 +00:02:00,360 --> 00:02:02,880 +но вы можете использовать его с любой моделью, которая была предварительно обучена + +46 +00:02:02,880 --> 00:02:04,263 +или дообучена на этой задаче. + +47 +00:02:06,540 --> 00:02:10,350 +Зайдя в хаб моделей, huggingface.co/models + +48 +00:02:10,350 --> 00:02:13,350 +вы можете отфильтровать доступные модели по задаче. + +49 +00:02:13,350 --> 00:02:17,190 +Модель по умолчанию, использованная в нашем предыдущем примере, была gpt2, + +50 +00:02:17,190 --> 00:02:19,290 +но существует множество других моделей, + +51 +00:02:19,290 --> 00:02:20,523 +и не только на английском языке. + +52 +00:02:21,450 --> 00:02:23,670 +Давайте вернемся к конвейеру генерации текста + +53 +00:02:23,670 --> 00:02:26,193 +и загрузим в него другую модель, distilgpt2. + +54 +00:02:27,060 --> 00:02:28,950 +Это облегченная версия gpt2 + +55 +00:02:28,950 --> 00:02:30,603 +созданная командой Hugging Face. + +56 +00:02:31,740 --> 00:02:34,110 +При применении конвейера к данной строке, + +57 +00:02:34,110 --> 00:02:36,360 +мы можем указать несколько аргументов + +58 +00:02:36,360 --> 00:02:39,240 +такие как максимальная длина генерируемых текстов, + +59 +00:02:39,240 --> 00:02:41,700 +или количество предложений, которые мы хотим вернуть, + +60 +00:02:41,700 --> 00:02:44,150 +поскольку в процессе генерации присутствует некоторая случайность. + +61 +00:02:46,080 --> 00:02:48,750 +Генерирование текстов путем угадывания следующего слова в предложении + +62 +00:02:48,750 --> 00:02:51,450 +было целью предварительного обучения в GPT-2. + +63 +00:02:51,450 --> 00:02:55,140 +Конвейер заполнения маски является целью предварительного обучения BERT, + +64 +00:02:55,140 --> 00:02:57,363 +которая заключается в угадывании значения замаскированного слова. + +65 +00:02:58,260 --> 00:03:01,020 +В этом случае мы спрашиваем два наиболее вероятных значения + +66 +00:03:01,020 --> 00:03:03,660 +для пропущенных слов, согласно модели, + +67 +00:03:03,660 --> 00:03:07,053 +и получаем в качестве возможных вариантов ответов "mathematical" или "computational". + +68 +00:03:08,280 --> 00:03:10,170 +Еще одна задача, которую может выполнить модель Transformers + +69 +00:03:10,170 --> 00:03:12,660 +классифицировать каждое слово в предложении + +70 +00:03:12,660 --> 00:03:14,970 +вместо предложения в целом. + +71 +00:03:14,970 --> 00:03:18,390 +Одним из примеров этого является Named Entity Recognition (распознавание именованных сущностей), + +72 +00:03:18,390 --> 00:03:20,820 +которая представляет собой задачу идентификации сущностей, + +73 +00:03:20,820 --> 00:03:25,323 +таких как люди, организации или места в предложении. + +74 +00:03:26,400 --> 00:03:30,570 +Здесь модель правильно находит персону, "Sylvain", + +75 +00:03:30,570 --> 00:03:32,453 +организацию, "Hugging Face", + +76 +00:03:32,453 --> 00:03:35,010 +а также местоположение, "Brooklyn", + +77 +00:03:35,010 --> 00:03:36,303 +внутри входного текста. + +78 +00:03:37,661 --> 00:03:40,230 +Аргумент grouped_entities=True используется + +79 +00:03:40,230 --> 00:03:42,330 +для того, чтобы заставить конвейер сгруппировать + +80 +00:03:42,330 --> 00:03:44,790 +различные слова, связанные с одним и тем же объектом, + +81 +00:03:44,790 --> 00:03:46,353 +например, "Hugging" и "Face". + +82 +00:03:48,270 --> 00:03:50,670 +Еще одна задача, доступная с помощью API конвейера + +83 +00:03:50,670 --> 00:03:52,920 +является extractive question answering (экстрактивный ответ на вопрос). + +84 +00:03:52,920 --> 00:03:55,380 +Предоставляется контекст и вопрос, + +85 +00:03:55,380 --> 00:03:58,290 +модель определит участок текста в контексте + +86 +00:03:58,290 --> 00:04:00,190 +содержащий ответ на вопрос. + +87 +00:04:01,650 --> 00:04:03,960 +Получение кратких резюме очень длинных статей + +88 +00:04:03,960 --> 00:04:06,540 +это то, с чем также может помочь библиотека Transformers, + +89 +00:04:06,540 --> 00:04:08,140 +с конвейером суммаризации. + +90 +00:04:09,480 --> 00:04:12,570 +Наконец, последняя задача, поддерживаемая API конвейера + +91 +00:04:12,570 --> 00:04:14,130 +это перевод. + +92 +00:04:14,130 --> 00:04:16,170 +Здесь мы используем французско-английскую модель + +93 +00:04:16,170 --> 00:04:17,460 +найденную в хабе моделей + +94 +00:04:17,460 --> 00:04:19,893 +для получения английской версии нашего входного текста. + +95 +00:04:21,600 --> 00:04:23,490 +Вот краткий обзор всех задач + +96 +00:04:23,490 --> 00:04:25,500 +которые мы рассмотрели в этом видео. + +97 +00:04:25,500 --> 00:04:27,390 +Попробуйте использовать виджеты инференса + +98 +00:04:27,390 --> 00:04:28,327 +в хабе моделей. + +99 +00:04:30,459 --> 00:04:33,475 + + +100 +00:04:33,475 --> 00:04:35,175 + + diff --git a/subtitles/ru/02_the-carbon-footprint-of-transformers.srt b/subtitles/ru/02_the-carbon-footprint-of-transformers.srt new file mode 100644 index 000000000..bbe7ff90e --- /dev/null +++ b/subtitles/ru/02_the-carbon-footprint-of-transformers.srt @@ -0,0 +1,516 @@ +1 +00:00:05,580 --> 00:00:08,820 +- Итак, давайте поговорим об углеродном следе трансформеров. + +2 +00:00:08,820 --> 00:00:10,530 +Возможно, вы видели заголовки, подобные этому + +3 +00:00:10,530 --> 00:00:13,530 +что обучение одной модели ИИ может выбросить столько углерода, + +4 +00:00:13,530 --> 00:00:16,020 +сколько пять автомобилей за весь срок службы. + +5 +00:00:16,020 --> 00:00:19,440 +Так когда же это правда и всегда ли это так? + +6 +00:00:19,440 --> 00:00:21,803 +На самом деле, это зависит от нескольких вещей. + +7 +00:00:21,803 --> 00:00:23,430 +Самое главное, это зависит + +8 +00:00:23,430 --> 00:00:24,960 +от типа энергии, которую вы используете. + +9 +00:00:24,960 --> 00:00:26,267 +Если вы используете возобновляемые источники энергии, такие как + +10 +00:00:26,267 --> 00:00:30,670 +солнце, ветер, гидроэлектроэнергия, вы действительно + +11 +00:00:30,670 --> 00:00:33,810 +не выбрасываете углерод вообще, очень, очень мало. + +12 +00:00:33,810 --> 00:00:36,769 +Если вы используете невозобновляемые источники энергии, такие как уголь + +13 +00:00:36,769 --> 00:00:39,570 +то их углеродный след намного выше + +14 +00:00:39,570 --> 00:00:43,260 +потому что, по сути, вы выделяете большое количество парниковых газов. + +15 +00:00:43,260 --> 00:00:44,670 +Другой аспект - время обучения. + +16 +00:00:44,670 --> 00:00:47,232 +Поэтому чем дольше вы обучаете, тем больше энергии вы используете, + +17 +00:00:47,232 --> 00:00:50,250 +тем больше углерода вы выбрасываете, верно? + +18 +00:00:50,250 --> 00:00:51,270 +Таким образом, это действительно увеличивает + +19 +00:00:51,270 --> 00:00:53,520 +особенно если вы тренируете большие модели + +20 +00:00:53,520 --> 00:00:56,460 +в течение часов, дней и недель. + +21 +00:00:56,460 --> 00:00:58,380 +Используемое вами оборудование также имеет значение + +22 +00:00:58,380 --> 00:01:00,930 +потому что некоторые GPU, например, более эффективны + +23 +00:01:00,930 --> 00:01:05,460 +чем другие и правильно используют эффективность. + +24 +00:01:05,460 --> 00:01:07,500 +Поэтому их постоянное использование на сто процентов + +25 +00:01:07,500 --> 00:01:10,650 +может реально снизить потребление энергии. + +26 +00:01:10,650 --> 00:01:13,290 +И опять же, уменьшить углеродный след. + +27 +00:01:13,290 --> 00:01:15,870 +Есть и другие аспекты, такие как ввод/вывод, + +28 +00:01:15,870 --> 00:01:17,730 +такие как данные, и так далее, и тому подобное. + +29 +00:01:17,730 --> 00:01:20,940 +Но это основные три, на которых вам следует сосредоточиться. + +30 +00:01:20,940 --> 00:01:23,340 +Поэтому, когда я говорю об источниках энергии и углеродоемкости, + +31 +00:01:23,340 --> 00:01:24,420 +что это означает на самом деле? + +32 +00:01:24,420 --> 00:01:27,480 +Итак, если вы посмотрите на верхнюю часть экрана, вы + +33 +00:01:27,480 --> 00:01:30,480 +вы увидите углеродный след + +34 +00:01:30,480 --> 00:01:33,860 +облачного вычислительного центра в Мумбаи, Индия, + +35 +00:01:33,860 --> 00:01:38,700 +который выбрасывает 920 граммов CO2 на киловатт-час. + +36 +00:01:38,700 --> 00:01:40,110 +Это почти один килограмм + +37 +00:01:40,110 --> 00:01:43,680 +CO2 на киловатт-час используемой электроэнергии. + +38 +00:01:43,680 --> 00:01:45,150 +Если сравнить с Канадой, Монреалем, + +39 +00:01:45,150 --> 00:01:48,720 +где я сейчас нахожусь, то 20 граммов CO2 на килограмм-час. + +40 +00:01:48,720 --> 00:01:50,040 +Так что это очень, очень большая разница. + +41 +00:01:50,040 --> 00:01:54,240 +Почти в 40 раз больше углерода выбрасывается + +42 +00:01:54,240 --> 00:01:55,950 +в Мумбаи по сравнению с Монреалем. + +43 +00:01:55,950 --> 00:01:57,720 +И это может очень, очень сильно увеличиться. + +44 +00:01:57,720 --> 00:01:59,820 +Например, если вы обучаете модель в течение нескольких недель + +45 +00:01:59,820 --> 00:02:01,920 +вы умножаете в 40 раз + +46 +00:02:01,920 --> 00:02:03,450 +углерод, который вы выбрасываете. + +47 +00:02:03,450 --> 00:02:05,070 +Поэтому выбор правильного экземпляра + +48 +00:02:05,070 --> 00:02:07,080 +выбор низкоуглеродного компьютерного экземпляра + +49 +00:02:07,080 --> 00:02:09,690 +это действительно самая важная вещь, которую вы можете сделать. + +50 +00:02:09,690 --> 00:02:13,020 +И вот тут-то и может возникнуть реальная проблема + +51 +00:02:13,020 --> 00:02:15,930 +если вы обучаете в очень интенсивном + +52 +00:02:15,930 --> 00:02:17,580 +регионе с высоким выбросом углерода + +53 +00:02:19,170 --> 00:02:21,750 +другие элементы, которые следует рассмотреть, например + +54 +00:02:21,750 --> 00:02:22,770 +использование предварительно обученных моделей + +55 +00:02:22,770 --> 00:02:25,590 +это эквивалент повторного использования в машинном обучении. + +56 +00:02:25,590 --> 00:02:28,292 +Когда у вас есть предварительно обученные модели, используя их, + +57 +00:02:28,292 --> 00:02:30,120 +вы вообще не выбрасываете углерод, верно? + +58 +00:02:30,120 --> 00:02:31,230 +Вы ничего не переобучаете. + +59 +00:02:31,230 --> 00:02:33,450 +Так что это еще и выполнение домашней работы + +60 +00:02:33,450 --> 00:02:35,574 +и изучие того, что уже существует. + +61 +00:02:35,574 --> 00:02:37,890 +Дообучение вместо обучения с нуля. + +62 +00:02:37,890 --> 00:02:38,723 +Поэтому еще раз, + +63 +00:02:38,723 --> 00:02:40,590 +если вы нашли модель, которая почти то, что вам нужно, + +64 +00:02:40,590 --> 00:02:43,530 +но не совсем, то добучение последней пары слоев, + +65 +00:02:43,530 --> 00:02:45,210 +чтобы она действительно соответствовала вашей цели, вместо того, + +66 +00:02:45,210 --> 00:02:46,500 +чтобы обучать большой трансформер + +67 +00:02:46,500 --> 00:02:48,810 +с нуля, может действительно помочь, + +68 +00:02:48,810 --> 00:02:51,270 +начинайте с небольших экспериментов + +69 +00:02:51,270 --> 00:02:52,800 +и отлаживайте по ходу дела. + +70 +00:02:52,800 --> 00:02:54,630 +Это означает, что, например, + +71 +00:02:54,630 --> 00:02:58,770 +вы убедитесь в правильности кодировки данных, убедитесь в + +72 +00:02:58,770 --> 00:03:01,170 +том, что нет мелких ошибок, которые + +73 +00:03:01,170 --> 00:03:03,840 +могут появиться после 16 часов обучения, + +74 +00:03:03,840 --> 00:03:05,820 +начинайте с малого и убедитесь + +75 +00:03:05,820 --> 00:03:08,760 +в том, что то что вы делаете, что делает ваш код, является стабильным. + +76 +00:03:08,760 --> 00:03:11,430 +И, наконец, сделайте обзор литературы, + +77 +00:03:11,430 --> 00:03:13,740 +чтобы выбрать диапазоны гиперпараметров, а затем + +78 +00:03:13,740 --> 00:03:15,900 +выполнить случайный поиск вместо поиска по сетке. + +79 +00:03:15,900 --> 00:03:18,420 +Так, случайный поиск комбинаций гиперпараметров + +80 +00:03:18,420 --> 00:03:21,300 +на самом деле оказался столь же эффективным + +81 +00:03:21,300 --> 00:03:24,000 +в поиске оптимальной конфигурации, как и поиск по сетке. + +82 +00:03:24,000 --> 00:03:27,510 +Но очевидно, что вы не проверяете все возможные комбинации, + +83 +00:03:27,510 --> 00:03:29,520 +а только их подмножество. + +84 +00:03:29,520 --> 00:03:31,800 +Так что это тоже может помочь. + +85 +00:03:31,800 --> 00:03:32,760 +Итак, если мы вернемся + +86 +00:03:32,760 --> 00:03:36,300 +к оригинальной статье Струбелла и других в 2019 году + +87 +00:03:36,300 --> 00:03:39,180 +печально известной статье о пяти автомобилях за время их эксплуатации. + +88 +00:03:39,180 --> 00:03:40,013 +Если вы просто посмотрите + +89 +00:03:40,013 --> 00:03:43,606 +на трансформер с 200 миллионами параметров, + +90 +00:03:43,606 --> 00:03:46,950 +то его углеродный след составит около 200 фунтов CO2, + +91 +00:03:46,950 --> 00:03:47,940 +что значительно + +92 +00:03:47,940 --> 00:03:49,980 +но это не больше, чем у пяти автомобилей, верно? + +93 +00:03:49,980 --> 00:03:52,893 +Это даже не трансатлантический перелет. + +94 +00:03:52,893 --> 00:03:55,020 +Как это действительно увеличивается, когда вы делаете + +95 +00:03:55,020 --> 00:03:56,190 +поиск архитектуры нейронной сети, + +96 +00:03:56,190 --> 00:03:58,560 +когда вы делаете настройку гиперпараметров, и + +97 +00:03:58,560 --> 00:04:00,930 +это перебор всех возможных комбинаций + +98 +00:04:00,930 --> 00:04:01,763 +и так далее, и тому подобное. + +99 +00:04:01,763 --> 00:04:02,596 +И вот откуда + +100 +00:04:02,596 --> 00:04:05,400 +например, 600 000 фунтов CO2. + +101 +00:04:05,400 --> 00:04:08,490 +Так что здесь все действительно складывается. + +102 +00:04:08,490 --> 00:04:11,880 +Итак, если вы поступаете разумно и осознанно, + +103 +00:04:11,880 --> 00:04:16,410 +то ваш углеродный след не будет таким большим, как + +104 +00:04:16,410 --> 00:04:20,040 +предполагалось в статье, а некоторые инструменты помогут вам + +105 +00:04:20,040 --> 00:04:22,111 +определить, сколько CO2 выбрасываете именно вы. + +106 +00:04:22,111 --> 00:04:24,270 +Существует веб-инструмент под названием machine + +107 +00:04:24,270 --> 00:04:26,430 +learning submissions calculator, который позволяет вам + +108 +00:04:26,430 --> 00:04:29,010 +вручную ввести, например, какое оборудование вы использовали, + +109 +00:04:29,010 --> 00:04:30,488 +сколько часов вы его использовали, + +110 +00:04:30,488 --> 00:04:34,260 +где оно было расположено - локально или в облаке. + +111 +00:04:34,260 --> 00:04:35,640 +И затем он даст вам оценку того, + +112 +00:04:35,640 --> 00:04:37,560 +сколько CO2 вы выбросили. + +113 +00:04:37,560 --> 00:04:40,200 +Другой инструмент, который делает это программно, + +114 +00:04:40,200 --> 00:04:41,190 +называется Code Carbon. + +115 +00:04:41,190 --> 00:04:45,112 +Поэтому вы можете установить его с помощью PIP, вы можете зайти на GitHub + +116 +00:04:45,112 --> 00:04:48,120 +и, по сути, он работает параллельно с вашим кодом. + +117 +00:04:48,120 --> 00:04:49,085 +Так что, по сути, вы вызываете его + +118 +00:04:49,085 --> 00:04:51,060 +и затем проводите все свое обучение. + +119 +00:04:51,060 --> 00:04:53,760 +И в конце он предоставит вам оценку + +120 +00:04:53,760 --> 00:04:57,210 +CSV-файл с оценкой ваших выбросов. + +121 +00:04:57,210 --> 00:04:59,250 +И он даст вам несколько сравнений. + +122 +00:04:59,250 --> 00:05:01,230 +У него есть визуальный пользовательский интерфейс, где вы можете реально посмотреть + +123 +00:05:01,230 --> 00:05:04,680 +как это сравнимо с вождением автомобиля или просмотром телевизора. + +124 +00:05:04,680 --> 00:05:06,060 +Так что это может дать вам представление + +125 +00:05:06,060 --> 00:05:07,740 +о масштабах ваших выбросов. + +126 +00:05:07,740 --> 00:05:09,930 +И на самом деле, code carbon уже интегрирован в AutoML, + +127 +00:05:09,930 --> 00:05:12,270 +и, надеюсь, люди будут использовать его + +128 +00:05:12,270 --> 00:05:15,240 +из коробки и легко отслеживать свои выбросы на протяжении всего + +129 +00:05:15,240 --> 00:05:17,523 +процесса обучения и внедрения трансформеров. + diff --git a/subtitles/ru/03_what-is-transfer-learning.srt b/subtitles/ru/03_what-is-transfer-learning.srt new file mode 100644 index 000000000..5ca2b54ba --- /dev/null +++ b/subtitles/ru/03_what-is-transfer-learning.srt @@ -0,0 +1,360 @@ +1 +00:00:00,189 --> 00:00:02,856 + + +2 +00:00:05,550 --> 00:00:07,293 +Что такое трансфертное обучение? + +3 +00:00:09,480 --> 00:00:10,920 +Идея трансферного обучения + +4 +00:00:10,920 --> 00:00:12,570 +состоит в том, чтобы использовать знания, полученные + +5 +00:00:12,570 --> 00:00:15,543 +моделью, обученной на большом количестве данных для другой задачи. + +6 +00:00:16,410 --> 00:00:20,130 +Модель A будет обучена специально для задачи A. + +7 +00:00:20,130 --> 00:00:22,200 +Теперь предположим, что вы хотите обучить модель B + +8 +00:00:22,200 --> 00:00:23,970 +для другой задачи. + +9 +00:00:23,970 --> 00:00:27,330 +Одним из вариантов может быть обучение модели с нуля. + +10 +00:00:27,330 --> 00:00:30,633 +Это может потребовать большого количества вычислений, времени и данных. + +11 +00:00:31,470 --> 00:00:34,260 +Вместо этого мы можем инициализировать модель B + +12 +00:00:34,260 --> 00:00:36,570 +с теми же весами, что и модель A, + +13 +00:00:36,570 --> 00:00:39,213 +передавая знания модели A на задачу B. + +14 +00:00:41,040 --> 00:00:42,690 +При обучении с нуля, + +15 +00:00:42,690 --> 00:00:45,870 +все веса модели инициализируются случайным образом. + +16 +00:00:45,870 --> 00:00:48,870 +В этом примере мы обучаем модель BERT + +17 +00:00:48,870 --> 00:00:50,220 +на задаче распознавания того, + +18 +00:00:50,220 --> 00:00:52,203 +похожи или нет два предложения. + +19 +00:00:54,116 --> 00:00:56,730 +Слева - обучение с нуля, + +20 +00:00:56,730 --> 00:01:00,000 +а справа - дообучение предварительно обученной модели. + +21 +00:01:00,000 --> 00:01:02,220 +Как мы видим, использование трансфертного обучения + +22 +00:01:02,220 --> 00:01:05,160 +и предварительно обученной модели дает лучшие результаты. + +23 +00:01:05,160 --> 00:01:07,140 +И неважно, будем ли мы обучать дольше. + +24 +00:01:07,140 --> 00:01:10,620 +Точность обучения с нуля составляет около 70%, + +25 +00:01:10,620 --> 00:01:13,293 +в то время как предварительно обученная модель легко преодолевает отметку в 86%. + +26 +00:01:14,460 --> 00:01:16,140 +Это связано с тем, что предварительно обученные модели + +27 +00:01:16,140 --> 00:01:18,420 +обычно обучаются на больших объемах данных + +28 +00:01:18,420 --> 00:01:21,000 +которые обеспечивают модели статистическое понимание + +29 +00:01:21,000 --> 00:01:23,413 +языка, используемого во время предварительного обучения. + +30 +00:01:24,450 --> 00:01:25,950 +В компьютерном зрении + +31 +00:01:25,950 --> 00:01:28,080 +трансфертное обучение успешно применяется + +32 +00:01:28,080 --> 00:01:30,060 +уже почти десять лет. + +33 +00:01:30,060 --> 00:01:32,850 +Модели часто предварительно обучаются на наборе данных ImageNet, + +34 +00:01:32,850 --> 00:01:36,153 +содержащем 1,2 миллиона фотографий. + +35 +00:01:37,170 --> 00:01:41,130 +Каждое изображение классифицируется по одной из 1000 меток. + +36 +00:01:41,130 --> 00:01:44,010 +Подобное обучение на размеченных данных + +37 +00:01:44,010 --> 00:01:45,663 +называется обучением с учителем. + +38 +00:01:47,340 --> 00:01:49,140 +В обработке естественного языка (NLP), + +39 +00:01:49,140 --> 00:01:51,870 +трансфертное обучение появилось совсем недавно. + +40 +00:01:51,870 --> 00:01:54,480 +Ключевое отличие от ImageNet заключается в том, что предварительное обучение + +41 +00:01:54,480 --> 00:01:56,460 +обычно осуществляется самостоятельно, + +42 +00:01:56,460 --> 00:01:58,770 +что означает, что оно не требует аннотации от человека + +43 +00:01:58,770 --> 00:01:59,673 +для меток. + +44 +00:02:00,780 --> 00:02:02,700 +Очень распространенной целью предварительного обучения + +45 +00:02:02,700 --> 00:02:05,310 +является угадывание следующего слова в предложении. + +46 +00:02:05,310 --> 00:02:07,710 +Для этого нужно только много-много текста. + +47 +00:02:07,710 --> 00:02:10,710 +Например, GPT-2 была предварительно обучена таким образом + +48 +00:02:10,710 --> 00:02:12,900 +используя содержание 45 миллионов ссылок + +49 +00:02:12,900 --> 00:02:14,673 +размещенных пользователями в Reddit. + +50 +00:02:16,560 --> 00:02:19,590 +Другим примером задачи предварительного cамообучения под наблюдением + +51 +00:02:19,590 --> 00:02:22,470 +является предсказание значения случайно замаскированных слов. + +52 +00:02:22,470 --> 00:02:24,540 +Это похоже на тесты "заполни пустое место", + +53 +00:02:24,540 --> 00:02:26,760 +которые вы, возможно, выполняли в школе. + +54 +00:02:26,760 --> 00:02:29,880 +BERT был предварительно обучен таким образом, используя английскую Википедию + +55 +00:02:29,880 --> 00:02:31,893 +и 11 000 неопубликованных книг. + +56 +00:02:33,120 --> 00:02:36,450 +На практике трансферное обучение применяется к заданной модели + +57 +00:02:36,450 --> 00:02:39,090 +путем отбрасывания ее головы, + +58 +00:02:39,090 --> 00:02:42,150 +то есть последних слоев, сфокусированных на цели предварительного обучения, + +59 +00:02:42,150 --> 00:02:45,360 +и замены ее новой, случайно инициализированной головой, + +60 +00:02:45,360 --> 00:02:46,860 +подходящей для поставленной задачи. + +61 +00:02:47,970 --> 00:02:51,570 +Например, когда мы ранее проводили дообучение модели BERT, + +62 +00:02:51,570 --> 00:02:54,060 +мы удалили голову, которая классифицировала слова-маски, + +63 +00:02:54,060 --> 00:02:56,790 +и заменили ее классификатором с двумя выходами. + +64 +00:02:56,790 --> 00:02:58,563 +Поскольку наша задача имеет две метки. + +65 +00:02:59,700 --> 00:03:02,490 +Чтобы быть максимально эффективной, используемая предварительно обученная модель + +66 +00:03:02,490 --> 00:03:03,770 +должна быть максимально похожа + +67 +00:03:03,770 --> 00:03:06,270 +на задачу, для которой она дообучается. + +68 +00:03:06,270 --> 00:03:08,190 +Например, если проблема + +69 +00:03:08,190 --> 00:03:10,860 +состоит в классификации немецких предложений, + +70 +00:03:10,860 --> 00:03:13,053 +лучше всего использовать предварительно обученную немецкую модель. + +71 +00:03:14,370 --> 00:03:16,649 +Но вместе с хорошим приходит и плохое. + +72 +00:03:16,649 --> 00:03:19,380 +Предварительно обученная модель передает не только свои знания, + +73 +00:03:19,380 --> 00:03:21,693 +но и любую предвзятость, которую она может содержать. + +74 +00:03:22,530 --> 00:03:24,300 +ImageNet в основном содержит изображения + +75 +00:03:24,300 --> 00:03:26,850 +из Соединенных Штатов и Западной Европы. + +76 +00:03:26,850 --> 00:03:28,020 +Поэтому модели, дообученные с его помощью + +77 +00:03:28,020 --> 00:03:31,710 +обычно лучше работают с изображениями из этих стран. + +78 +00:03:31,710 --> 00:03:33,690 +OpenAI также изучил смещение + +79 +00:03:33,690 --> 00:03:36,120 +в прогнозах своей модели GPT-3 + +80 +00:03:36,120 --> 00:03:36,953 +которая была предварительно обучена + +81 +00:03:36,953 --> 00:03:38,750 +с использованием задачи "Угадай следующее слово". + +82 +00:03:39,720 --> 00:03:41,040 +Изменение пола в строке подсказке + +83 +00:03:41,040 --> 00:03:44,250 +с "He was very" на "She was very" + +84 +00:03:44,250 --> 00:03:47,550 +изменило предсказания с преимущественно нейтральных прилагательных + +85 +00:03:47,550 --> 00:03:49,233 +на почти только физические. + +86 +00:03:50,400 --> 00:03:52,367 +В карточке модели GPT-2 + +87 +00:03:52,367 --> 00:03:54,990 +OpenAI также признает ее необъективность + +88 +00:03:54,990 --> 00:03:56,730 +и не рекомендует использовать ее + +89 +00:03:56,730 --> 00:03:58,803 +в системах, взаимодействующих с людьми. + +90 +00:04:01,040 --> 00:04:03,707 + + diff --git a/subtitles/ru/04_the-transformer-architecture.srt b/subtitles/ru/04_the-transformer-architecture.srt new file mode 100644 index 000000000..bc8dd997f --- /dev/null +++ b/subtitles/ru/04_the-transformer-architecture.srt @@ -0,0 +1,256 @@ +1 +00:00:00,000 --> 00:00:02,750 + + +2 +00:00:05,010 --> 00:00:07,323 +- Давайте изучим архитектуру трансформера. + +3 +00:00:09,150 --> 00:00:12,030 +Этот видео является вводным в серию видео о кодерах, + +4 +00:00:12,030 --> 00:00:15,510 +декодерах и кодер-декодерах. + +5 +00:00:15,510 --> 00:00:16,343 +В этой серии, + +6 +00:00:16,343 --> 00:00:18,900 +мы попытаемся понять, что представляет собой трансформерная сеть, + +7 +00:00:18,900 --> 00:00:22,770 +и постараемся объяснить это простыми, высокоуровневыми терминами. + +8 +00:00:22,770 --> 00:00:25,800 +Понимание нейронных сетей не требуется, + +9 +00:00:25,800 --> 00:00:29,343 +может помочь только понимание основ векторов и тензоров. + +10 +00:00:32,250 --> 00:00:33,270 +Для начала + +11 +00:00:33,270 --> 00:00:34,530 +мы возьмем эту диаграмму + +12 +00:00:34,530 --> 00:00:36,630 +из оригинальной статьи о трансформерах, + +13 +00:00:36,630 --> 00:00:40,140 +озаглавленной "Внимание - все, что вам нужно". + +14 +00:00:40,140 --> 00:00:41,010 +Как мы увидим здесь, + +15 +00:00:41,010 --> 00:00:42,780 +мы можем использовать только некоторые его части, + +16 +00:00:42,780 --> 00:00:44,630 +в зависимости от того, что мы пытаемся сделать. + +17 +00:00:45,480 --> 00:00:47,610 +Мы не будем углубляться в конкретные слои, + +18 +00:00:47,610 --> 00:00:48,990 +составляющие эту архитектуру, + +19 +00:00:48,990 --> 00:00:51,390 +но попытаемся понять различные способы + +20 +00:00:51,390 --> 00:00:52,893 +использования этой архитектуры. + +21 +00:00:55,170 --> 00:00:56,003 +Для начала давайте + +22 +00:00:56,003 --> 00:00:58,260 +разделим эту архитектуру на две части. + +23 +00:00:58,260 --> 00:00:59,910 +Слева находится кодер, + +24 +00:00:59,910 --> 00:01:01,980 +а справа - декодер. + +25 +00:01:01,980 --> 00:01:03,330 +Их можно использовать вместе, + +26 +00:01:03,330 --> 00:01:05,330 +но можно и независимо. + +27 +00:01:06,180 --> 00:01:08,610 +Давайте разберемся, как они работают. + +28 +00:01:08,610 --> 00:01:11,460 +Кодер принимает входные данные, представляющие собой текст. + +29 +00:01:11,460 --> 00:01:13,620 +Он преобразует этот текст, эти слова, + +30 +00:01:13,620 --> 00:01:15,675 +в числовые представления. + +31 +00:01:15,675 --> 00:01:17,400 +Эти числовые представления + +32 +00:01:17,400 --> 00:01:20,460 +могут также называться эмбеддингами, или признаками. + +33 +00:01:20,460 --> 00:01:23,100 +Мы увидим, что он использует механизм самовнимания + +34 +00:01:23,100 --> 00:01:24,483 +в качестве основного компонента. + +35 +00:01:25,500 --> 00:01:27,120 +Мы рекомендуем вам посмотреть видео + +36 +00:01:27,120 --> 00:01:29,700 +о кодерах специально для того, чтобы понять + +37 +00:01:29,700 --> 00:01:31,680 +что такое это числовое представление, + +38 +00:01:31,680 --> 00:01:33,690 +а также как оно работает. + +39 +00:01:33,690 --> 00:01:36,660 +Мы изучим механизм самовнимания более подробно, + +40 +00:01:36,660 --> 00:01:38,913 +а также его двунаправленные свойства. + +41 +00:01:40,650 --> 00:01:42,780 +Декодер аналогичен кодеру. + +42 +00:01:42,780 --> 00:01:45,630 +Он также может принимать текстовые входы. + +43 +00:01:45,630 --> 00:01:48,210 +Он использует аналогичный механизм, что и кодер, + +44 +00:01:48,210 --> 00:01:51,150 +который также является маскированным самовниманием. + +45 +00:01:51,150 --> 00:01:52,590 +Он отличается от кодера + +46 +00:01:52,590 --> 00:01:54,990 +своим однонаправленным свойством + +47 +00:01:54,990 --> 00:01:58,590 +и традиционно используется в авторегрессионной манере. + +48 +00:01:58,590 --> 00:02:01,650 +Здесь мы также рекомендуем вам посмотреть видео о декодерах, + +49 +00:02:01,650 --> 00:02:04,000 +особенно для того, чтобы понять, как все это работает. + +50 +00:02:06,810 --> 00:02:07,890 +Комбинирование этих двух частей + +51 +00:02:07,890 --> 00:02:10,200 +дает так называемый кодер-декодер, + +52 +00:02:10,200 --> 00:02:12,720 +или трансформер последовательности в последовательность. + +53 +00:02:12,720 --> 00:02:14,280 +Кодер принимает входные данные + +54 +00:02:14,280 --> 00:02:17,850 +и вычисляет высокоуровневое представление этих входов. + +55 +00:02:17,850 --> 00:02:20,252 +Эти выходы затем передаются в декодер. + +56 +00:02:20,252 --> 00:02:22,860 +Декодер использует выход кодера, + +57 +00:02:22,860 --> 00:02:26,370 +наряду с другими входными данными для создания прогноза. + +58 +00:02:26,370 --> 00:02:27,900 +Затем он прогнозирует выход, + +59 +00:02:27,900 --> 00:02:30,248 +который он будет повторно использовать в будущих итерациях, + +60 +00:02:30,248 --> 00:02:32,662 +отсюда и термин "авторегрессивный". + +61 +00:02:32,662 --> 00:02:34,740 +Наконец, чтобы получить представление + +62 +00:02:34,740 --> 00:02:36,690 +о кодерах-декодерах в целом, + +63 +00:02:36,690 --> 00:02:39,670 +мы рекомендуем вам ознакомиться с видео о кодерах-декодерах. + +64 +00:02:39,670 --> 00:02:42,420 + + diff --git a/subtitles/ru/05_transformer-models-encoders.srt b/subtitles/ru/05_transformer-models-encoders.srt new file mode 100644 index 000000000..0f5ee4cf3 --- /dev/null +++ b/subtitles/ru/05_transformer-models-encoders.srt @@ -0,0 +1,407 @@ +1 +00:00:00,253 --> 00:00:03,003 + +2 +00:00:04,440 --> 00:00:07,830 +- В этом видео мы изучим архитектуру кодера. + +3 +00:00:07,830 --> 00:00:11,070 +Примером популярной архитектуры, использующей только кодер, является BERT, + +4 +00:00:11,070 --> 00:00:13,323 +который является самой популярной моделью такого рода. + +5 +00:00:14,550 --> 00:00:16,950 +Для начала давайте разберемся, как это работает. + +6 +00:00:18,360 --> 00:00:20,910 +Мы воспользуемся небольшим примером, используя три слова. + +7 +00:00:20,910 --> 00:00:23,823 +Мы используем их в качестве входных данных и пропустим через кодер. + +8 +00:00:25,290 --> 00:00:28,173 +Мы получили числовое представление каждого слова. + +9 +00:00:29,970 --> 00:00:32,700 +Вот, например, кодер преобразует три слова + +10 +00:00:32,700 --> 00:00:37,350 +"Welcome to NYC" в эти три последовательности цифр. + +11 +00:00:37,350 --> 00:00:40,350 +Кодер выдает ровно одну последовательность чисел + +12 +00:00:40,350 --> 00:00:41,493 +на каждое входное слово. + +13 +00:00:42,330 --> 00:00:44,880 +Это числовое представление можно также назвать + +14 +00:00:44,880 --> 00:00:47,163 +вектор признаков или тензор признаков. + +15 +00:00:49,080 --> 00:00:51,030 +Давайте погрузимся в это представление. + +16 +00:00:51,030 --> 00:00:52,740 +Он содержит один вектор на каждое слово + +17 +00:00:52,740 --> 00:00:54,540 +которое было пропущено через кодер. + +18 +00:00:56,130 --> 00:00:58,620 +Каждый из этих векторов является числовым представлением + +19 +00:00:58,620 --> 00:01:00,033 +рассматриваемого слова. + +20 +00:01:01,080 --> 00:01:03,300 +Размерность этого вектора определяется + +21 +00:01:03,300 --> 00:01:05,520 +архитектурой модели. + +22 +00:01:05,520 --> 00:01:08,703 +Для базовой модели BERT это значение равно 768. + +23 +00:01:10,650 --> 00:01:13,230 +Эти представления содержат значение слова, + +24 +00:01:13,230 --> 00:01:15,240 +но с учетом контекста. + +25 +00:01:15,240 --> 00:01:18,570 +Например, вектор, приписываемый слову "to" + +26 +00:01:18,570 --> 00:01:22,290 +не является представлением только слова "to". + +27 +00:01:22,290 --> 00:01:25,650 +Он также учитывает окружающие слова, + +28 +00:01:25,650 --> 00:01:27,363 +которые мы называем контекстом. + +29 +00:01:28,650 --> 00:01:30,780 +Например, он смотрит на левый контекст, + +30 +00:01:30,780 --> 00:01:32,970 +слова слева от изучаемого нами, + +31 +00:01:32,970 --> 00:01:34,980 +здесь слово "Welcome", + +32 +00:01:34,980 --> 00:01:37,497 +и контекст справа, здесь слово "NYC", + +33 +00:01:38,348 --> 00:01:42,000 +и выводит значение для слова с учетом его контекста. + +34 +00:01:42,000 --> 00:01:45,420 +Таким образом, это контекстуализированное значение. + +35 +00:01:45,420 --> 00:01:48,810 +Можно сказать, что вектор из 768 значений + +36 +00:01:48,810 --> 00:01:51,993 +хранит значение слова в тексте. + +37 +00:01:53,310 --> 00:01:56,073 +Это происходит благодаря механизму самовнимания. + +38 +00:01:57,240 --> 00:02:00,630 +Механизм самовнимания связан с различными позициями, + +39 +00:02:00,630 --> 00:02:02,850 +или различным словам в одной последовательности + +40 +00:02:02,850 --> 00:02:06,003 +для того, чтобы вычислить представление этой последовательности. + +41 +00:02:07,200 --> 00:02:09,000 +Как мы уже видели, это означает, что + +42 +00:02:09,000 --> 00:02:11,130 +на результирующее представление слова + +43 +00:02:11,130 --> 00:02:13,983 +повлияли другие слова в последовательности. + +44 +00:02:15,840 --> 00:02:18,030 +Мы не будем углубляться в детали, + +45 +00:02:18,030 --> 00:02:19,680 +но предлагаем вам ознакомиться с некоторыми дополнительными материалами, + +46 +00:02:19,680 --> 00:02:21,330 +если вы хотите лучше понять, + +47 +00:02:21,330 --> 00:02:22,953 +что происходит под капотом. + +48 +00:02:25,050 --> 00:02:27,480 +Так почему же следует использовать кодер? + +49 +00:02:27,480 --> 00:02:29,370 +Кодеры могут использоваться в качестве автономных моделей + +50 +00:02:29,370 --> 00:02:31,263 +в самых разнообразных задачах. + +51 +00:02:32,100 --> 00:02:33,360 +Например, BERT, + +52 +00:02:33,360 --> 00:02:35,670 +возможно, самая известная модель трансформера, + +53 +00:02:35,670 --> 00:02:37,590 +является отдельной моделью кодера, + +54 +00:02:37,590 --> 00:02:38,820 +и на момент выпуска + +55 +00:02:38,820 --> 00:02:40,440 +она была передовой + +56 +00:02:40,440 --> 00:02:42,780 +во многих задачах классификации последовательностей, + +57 +00:02:42,780 --> 00:02:44,190 +задачах ответа на вопросы, + +58 +00:02:44,190 --> 00:02:46,743 +и моделировании языка с маской, и это лишь некоторые из них. + +59 +00:02:48,150 --> 00:02:50,460 +Идея заключается в том, что кодеры очень сильны + +60 +00:02:50,460 --> 00:02:52,470 +в извлечении векторов, которые несут + +61 +00:02:52,470 --> 00:02:55,350 +значимую информацию о последовательности. + +62 +00:02:55,350 --> 00:02:57,870 +Этот вектор затем может быть обработан в дальнейшем + +63 +00:02:57,870 --> 00:03:00,070 +дополнительными нейронами, чтобы понять его смысл. + +64 +00:03:01,380 --> 00:03:02,850 +Давайте рассмотрим несколько примеров + +65 +00:03:02,850 --> 00:03:04,563 +где кодер действительно блистает. + +66 +00:03:06,210 --> 00:03:09,900 +Прежде всего, Masked Language Modeling, или MLM. + +67 +00:03:09,900 --> 00:03:11,970 +Это задача предсказания скрытого слова + +68 +00:03:11,970 --> 00:03:13,590 +в последовательности слов. + +69 +00:03:13,590 --> 00:03:15,630 +Здесь, например, мы скрыли слово + +70 +00:03:15,630 --> 00:03:17,247 +между "My" и "is". + +71 +00:03:18,270 --> 00:03:21,120 +Это одна из целей обучения BERT. + +72 +00:03:21,120 --> 00:03:24,393 +Он был обучен предсказывать скрытые слова в последовательности. + +73 +00:03:25,230 --> 00:03:27,930 +В этом сценарии кодеры проявляют себя особенно ярко, поскольку + +74 +00:03:27,930 --> 00:03:31,140 +двунаправленная информация имеет здесь решающее значение. + +75 +00:03:31,140 --> 00:03:32,947 +Если бы у нас не было слов справа, + +76 +00:03:32,947 --> 00:03:34,650 +это "Sylvain" и ".", + +77 +00:03:34,650 --> 00:03:35,940 +то очень мало шансов, + +78 +00:03:35,940 --> 00:03:38,580 +что BERT смог бы определить имя + +79 +00:03:38,580 --> 00:03:40,500 +как правильное слово. + +80 +00:03:40,500 --> 00:03:42,270 +Кодер должен хорошо понимать + +81 +00:03:42,270 --> 00:03:45,360 +последовательности, чтобы предсказать замаскированное слово + +82 +00:03:45,360 --> 00:03:48,840 +поскольку даже если текст грамматически правильный, + +83 +00:03:48,840 --> 00:03:50,610 +он не обязательно имеет смысл + +84 +00:03:50,610 --> 00:03:52,413 +в контексте последовательности. + +85 +00:03:55,230 --> 00:03:56,580 +Как упоминалось ранее, + +86 +00:03:56,580 --> 00:03:59,520 +кодеры хорошо справляются с классификацией последовательностей. + +87 +00:03:59,520 --> 00:04:02,883 +Анализ настроений является примером классификации последовательностей. + +88 +00:04:04,410 --> 00:04:09,410 +Цель модели - определить настроение последовательности. + +89 +00:04:09,540 --> 00:04:11,280 +Это может варьироваться от присвоения последовательности + +90 +00:04:11,280 --> 00:04:12,960 +рейтинга от одной до пяти звезд + +91 +00:04:12,960 --> 00:04:15,900 +если проводится анализ отзывов, до присвоения положительного + +92 +00:04:15,900 --> 00:04:17,820 +или отрицательного рейтинга последовательности + +93 +00:04:17,820 --> 00:04:19,220 +что и показано здесь. + +94 +00:04:20,280 --> 00:04:22,950 +Например, здесь, даны две последовательности, + +95 +00:04:22,950 --> 00:04:25,860 +мы используем модель для вычисления прогноза, + +96 +00:04:25,860 --> 00:04:27,420 +и классифицируем последовательности + +97 +00:04:27,420 --> 00:04:30,393 +между двумя классами, положительным и отрицательным. + +98 +00:04:31,230 --> 00:04:33,450 +Хотя эти две последовательности очень похожи + +99 +00:04:33,450 --> 00:04:35,220 +и содержат одни и те же слова, + +100 +00:04:35,220 --> 00:04:37,170 +смысл совершенно разный, + +101 +00:04:37,170 --> 00:04:40,143 +и модель кодера способна уловить эту разницу. + +102 +00:04:41,404 --> 00:04:44,154 + + diff --git a/subtitles/ru/06_transformer-models-decoders.srt b/subtitles/ru/06_transformer-models-decoders.srt new file mode 100644 index 000000000..54ecf410b --- /dev/null +++ b/subtitles/ru/06_transformer-models-decoders.srt @@ -0,0 +1,348 @@ +1 +00:00:03,750 --> 00:00:07,140 +- В этом видео мы изучим архитектуру декодера. + +2 +00:00:07,140 --> 00:00:07,973 +Примером + +3 +00:00:07,973 --> 00:00:11,338 +популярной архитектуры только декодера является GPT-2. + +4 +00:00:11,338 --> 00:00:14,160 +Для того чтобы понять, как работают декодеры + +5 +00:00:14,160 --> 00:00:17,430 +мы рекомендуем посмотреть видео о кодерах. + +6 +00:00:17,430 --> 00:00:19,980 +Они чрезвычайно похожи на декодеры. + +7 +00:00:19,980 --> 00:00:21,210 +Декодер можно использовать + +8 +00:00:21,210 --> 00:00:23,760 +большинства тех же задач, что и кодер, + +9 +00:00:23,760 --> 00:00:27,330 +хотя, как правило, с небольшой потерей производительности. + +10 +00:00:27,330 --> 00:00:28,890 +Давайте воспользуемся тем же подходом, который мы использовали + +11 +00:00:28,890 --> 00:00:30,300 +с кодером, чтобы попытаться + +12 +00:00:30,300 --> 00:00:32,670 +понять архитектурные различия + +13 +00:00:32,670 --> 00:00:34,803 +между кодером и декодером. + +14 +00:00:35,777 --> 00:00:38,910 +Мы используем небольшой пример, используя три слова. + +15 +00:00:38,910 --> 00:00:41,050 +Мы пропускаем их через декодер. + +16 +00:00:41,050 --> 00:00:44,793 +Мы получаем числовое представление для каждого слова. + +17 +00:00:46,410 --> 00:00:49,350 +Здесь, например, декодер преобразует три слова. + +18 +00:00:49,350 --> 00:00:53,545 +"Welcome to NYC" в эти три последовательности цифр. + +19 +00:00:53,545 --> 00:00:56,040 +Декодер выдает ровно одну последовательность + +20 +00:00:56,040 --> 00:00:58,740 +чисел на каждое входное слово. + +21 +00:00:58,740 --> 00:01:00,630 +Это числовое представление может также + +22 +00:01:00,630 --> 00:01:03,783 +назвать вектором признаков или тензором признаков. + +23 +00:01:04,920 --> 00:01:07,200 +Давайте погрузимся в это представление. + +24 +00:01:07,200 --> 00:01:08,490 +Оно содержит один вектор + +25 +00:01:08,490 --> 00:01:11,340 +на каждое слово, прошедшее через декодер. + +26 +00:01:11,340 --> 00:01:14,250 +Каждый из этих векторов является числовым представлением + +27 +00:01:14,250 --> 00:01:15,573 +рассматриваемого слова. + +28 +00:01:16,920 --> 00:01:18,562 +Размерность этого вектора определяется + +29 +00:01:18,562 --> 00:01:20,703 +архитектурой модели. + +30 +00:01:22,860 --> 00:01:26,040 +Декодер отличается от кодера главным образом + +31 +00:01:26,040 --> 00:01:28,200 +своим механизмом самовнимания. + +32 +00:01:28,200 --> 00:01:30,843 +Он использует так называемое маскированное самовнимание. + +33 +00:01:31,860 --> 00:01:34,650 +Здесь, например, если мы сосредоточимся на слове "to" + +34 +00:01:34,650 --> 00:01:37,620 +мы увидим, что вектор абсолютно не изменен + +35 +00:01:37,620 --> 00:01:39,690 +словом "NYC". + +36 +00:01:39,690 --> 00:01:41,731 +Это происходит потому, что все слова справа, также известные + +37 +00:01:41,731 --> 00:01:45,276 +как правильный контекст слова, маскируются, а + +38 +00:01:45,276 --> 00:01:49,230 +вместо того чтобы извлекать пользу из всех слов слева и справа. + +39 +00:01:49,230 --> 00:01:51,600 +Таким образом, двунаправленный контекст. + +40 +00:01:51,600 --> 00:01:55,020 +Декодеры имеют доступ только к одному контексту + +41 +00:01:55,020 --> 00:01:58,203 +который может быть левым или правым контекстом. + +42 +00:01:59,539 --> 00:02:03,356 +Механизм маскированного самовнимания отличается + +43 +00:02:03,356 --> 00:02:04,320 +от механизма самовнимания тем, + +44 +00:02:04,320 --> 00:02:07,110 +что использует дополнительную маску, скрывающую контекст + +45 +00:02:07,110 --> 00:02:09,390 +по обе стороны от слова, + +46 +00:02:09,390 --> 00:02:12,810 +при этом на числовое представление слова не влияют + +47 +00:02:12,810 --> 00:02:14,853 +слова в скрытом контексте. + +48 +00:02:16,260 --> 00:02:18,330 +Так когда же следует использовать декодер? + +49 +00:02:18,330 --> 00:02:22,380 +Декодеры, как и кодеры, могут быть использованы как самостоятельные модели + +50 +00:02:22,380 --> 00:02:25,020 +поскольку они генерируют числовое представление. + +51 +00:02:25,020 --> 00:02:28,320 +Они также могут использоваться для решения самого широкого круга задач. + +52 +00:02:28,320 --> 00:02:31,260 +Однако сила декодера заключается в том, что + +53 +00:02:31,260 --> 00:02:34,530 +слово может иметь доступ только к своему левому контексту. + +54 +00:02:34,530 --> 00:02:36,690 + + +55 +00:02:36,690 --> 00:02:39,120 +Они по своей природе хороши в генерации текста: + +56 +00:02:39,120 --> 00:02:41,010 +способности генерировать слово + +57 +00:02:41,010 --> 00:02:45,000 +или последовательность слов, учитывая известную последовательность слов. + +58 +00:02:45,000 --> 00:02:45,833 +Это известно как + +59 +00:02:45,833 --> 00:02:49,083 +каузальное языковое моделирование или генерация естественного языка. + +60 +00:02:50,430 --> 00:02:53,520 +Вот пример того, как работает каузальное языковое моделирование. + +61 +00:02:53,520 --> 00:02:56,410 +Мы начинаем с вводного слова, которым является "my", + +62 +00:02:57,339 --> 00:02:59,973 +используем его в качестве входного сигнала для декодера. + +63 +00:03:00,810 --> 00:03:04,260 +Модель выводит вектор чисел + +64 +00:03:04,260 --> 00:03:07,230 +и этот вектор содержит информацию о последовательности, + +65 +00:03:07,230 --> 00:03:08,733 +которая здесь является одним словом. + +66 +00:03:09,780 --> 00:03:11,430 +Мы применяем небольшое преобразование + +67 +00:03:11,430 --> 00:03:13,110 +к этому вектору так, чтобы он отображался + +68 +00:03:13,110 --> 00:03:16,500 +на все слова, известные модели, это отображение + +69 +00:03:16,500 --> 00:03:19,890 +которое, как мы увидим позже, называется "голова языкового моделирования". + +70 +00:03:19,890 --> 00:03:21,930 +Мы определили, что модель считает, + +71 +00:03:21,930 --> 00:03:25,053 +что наиболее вероятное следующее слово это "name". + +72 +00:03:26,250 --> 00:03:28,710 +Затем мы берем это новое слово и добавляем его + +73 +00:03:28,710 --> 00:03:33,480 +к начальной последовательности из "my", и теперь у нас есть "my name". + +74 +00:03:33,480 --> 00:03:36,870 +Именно здесь возникает авторегрессивный аспект. + +75 +00:03:36,870 --> 00:03:38,490 +Авторегрессионные модели + +76 +00:03:38,490 --> 00:03:42,513 +повторно используют свои прошлые выходы в качестве входов на следующих этапах. + +77 +00:03:43,452 --> 00:03:46,980 +И снова мы выполняем точно такую же операцию. + +78 +00:03:46,980 --> 00:03:49,500 +Мы пропускаем эту последовательность через декодер + +79 +00:03:49,500 --> 00:03:51,993 +и извлекаем наиболее вероятное следующее слово. + +80 +00:03:52,978 --> 00:03:57,978 +В данном случае это слово "is", мы повторяем операцию + +81 +00:03:58,230 --> 00:04:02,040 +пока не будем удовлетворены, начиная с одного слова. + +82 +00:04:02,040 --> 00:04:04,590 +Теперь мы сгенерировали полное предложение. + +83 +00:04:04,590 --> 00:04:07,890 +Мы решаем остановиться на этом, но могли бы продолжать еще какое-то время. + +84 +00:04:07,890 --> 00:04:12,890 +GPT-2, например, имеет максимальный размер контекста 1024. + +85 +00:04:13,170 --> 00:04:16,830 +В конечном итоге мы могли бы сгенерировать до 1024 слов, + +86 +00:04:16,830 --> 00:04:19,050 +и декодер все еще будет помнить о + +87 +00:04:19,050 --> 00:04:21,003 +первых словах в этой последовательности. + diff --git a/subtitles/ru/07_transformer-models-encoder-decoders.srt b/subtitles/ru/07_transformer-models-encoder-decoders.srt new file mode 100644 index 000000000..37003b346 --- /dev/null +++ b/subtitles/ru/07_transformer-models-encoder-decoders.srt @@ -0,0 +1,260 @@ +1 +00:00:04,160 --> 00:00:07,200 +В этом видео мы изучим архитектуру кодера-декодера. + +2 +00:00:08,160 --> 00:00:16,160 +Примером популярной модели кодера-декодера является T5. Для того чтобы понять, как работает кодер-декодер + +3 +00:00:16,160 --> 00:00:21,680 +мы рекомендуем вам ознакомиться с видео о кодерах и декодерах как самостоятельных моделях. + +4 +00:00:22,400 --> 00:00:30,320 +Понимание того, как они ведут себя по отдельности, поможет понять, как ведет себя кодер-декодер. + +5 +00:00:30,320 --> 00:00:35,360 +Давайте начнем с того, что мы видели о кодере. Кодер принимает слова в качестве входа, + +6 +00:00:36,000 --> 00:00:40,640 +прогоняет их через кодер и извлекает числовое представление + +7 +00:00:40,640 --> 00:00:47,360 +для каждого пропущенного через него слова. Теперь мы знаем, что числовое представление содержит информацию + +8 +00:00:47,360 --> 00:00:54,000 +о смысле последовательности. Давайте отложим это в сторону и добавим к диаграмме декодер. + +9 +00:00:56,480 --> 00:01:00,160 +В этом сценарии мы используем декодер таким образом, которого раньше не видели. + +10 +00:01:00,720 --> 00:01:07,600 +Мы передаем выходы кодера непосредственно на него! Дополнительно к выходам кодера, + +11 +00:01:07,600 --> 00:01:13,040 +мы также передаем декодеру последовательность. При запросе декодера на вывод без + +12 +00:01:13,040 --> 00:01:17,360 +начальной последовательности, мы можем передать ему значение, указывающее на начало последовательности. + +13 +00:01:18,000 --> 00:01:23,520 +И именно здесь происходит волшебство кодера-декодера. Кодер принимает на вход последовательность. + +14 +00:01:24,560 --> 00:01:30,480 +Он вычисляет прогноз и выдает числовое представление. Затем он посылает + +15 +00:01:30,480 --> 00:01:38,000 +его декодеру. Он, в некотором смысле, закодировал последовательность. А декодер, в свою очередь, + +16 +00:01:38,000 --> 00:01:42,960 +используя этот вход наряду с обычной входной последовательностью, попытается декодировать последовательность. + +17 +00:01:44,720 --> 00:01:50,400 +Декодер декодирует последовательность и выводит слово. На данный момент нам не нужно понимать + +18 +00:01:50,400 --> 00:01:55,440 +смысл этого слова, но мы можем понять, что декодер по сути декодирует то, что вывел кодер. + +19 +00:01:55,440 --> 00:02:02,160 +Слово "Start of sequence word" указывает на то, что он должен начать декодирование последовательности. + +20 +00:02:03,600 --> 00:02:10,240 +Теперь, когда у нас есть и вектор признаков, и начальное сгенерированное слово, нам больше не нужен + +21 +00:02:10,240 --> 00:02:17,760 +кодер. Как мы уже видели на примере декодера, он может действовать в авторегрессивной манере; + +22 +00:02:18,640 --> 00:02:24,960 +слово, которое он только что вывел, теперь может быть использовано в качестве входа. Это, в сочетании с + +23 +00:02:24,960 --> 00:02:30,800 +числовым представлением, выводимым кодером, может быть использовано для генерации второго слова. + +24 +00:02:33,200 --> 00:02:38,880 +Обратите внимание, что первое слово все еще здесь, поскольку модель все еще выводит его. Однако оно выделено серым цветом, + +25 +00:02:38,880 --> 00:02:45,120 +поскольку оно нам больше не нужно. Мы можем продолжать и продолжать, например, пока декодер + +26 +00:02:45,120 --> 00:02:50,720 +не выдаст значение, которое мы считаем "stopping value", например, точку, означающую конец последовательности. + +27 +00:02:53,440 --> 00:02:58,080 +Здесь мы увидели весь механизм трансформера кодер-декодер: давайте пройдемся по нему + +28 +00:02:58,080 --> 00:03:05,120 +еще раз. У нас есть начальная последовательность, которая отправляется на кодер. Затем выходной сигнал кодера + +29 +00:03:05,120 --> 00:03:12,240 +передается декодеру для декодирования. Если кодер мы теперь можем выбросить после одного использования, + +30 +00:03:12,240 --> 00:03:17,840 +то декодер будет использоваться несколько раз: пока мы не сгенерируем все слова, которые нам нужны. + +31 +00:03:20,000 --> 00:03:25,120 +Рассмотрим конкретный случай; на примере Translation Language Modeling; также называемого трансдукцией; + +32 +00:03:25,120 --> 00:03:30,800 +акт перевода последовательности. Здесь мы хотим перевести английскую последовательность "Welcome" + +33 +00:03:30,800 --> 00:03:38,400 +"to NYC" на французский язык. Мы используем модель трансформера, которая обучена для этой задачи в явном виде. + +34 +00:03:38,400 --> 00:03:43,520 +Мы используем кодер для создания представления английского предложения. Мы передаем это + +35 +00:03:43,520 --> 00:03:48,880 +декодеру и, используя слово "start of sequence", просим его вывести первое слово. + +36 +00:03:50,720 --> 00:03:52,960 +Он выводит "Bienvenue", что означает "Welcome". + +37 +00:03:55,280 --> 00:04:02,480 +Затем мы используем "Bienvenue" в качестве входной последовательности для декодера. Это, наряду с вектором признаков, + +38 +00:04:04,320 --> 00:04:08,480 +позволяет декодеру предсказать второе слово, "à", которое в английском языке означает "to". + +39 +00:04:10,160 --> 00:04:14,400 +Наконец, мы просим декодер предсказать третье слово; он предсказывает "NYC", + +40 +00:04:14,400 --> 00:04:20,240 +что снова является правильным. Мы перевели предложение! Где кодер-декодер действительно + +41 +00:04:20,240 --> 00:04:24,880 +блистает, так это в том, что у нас есть кодер и декодер, которые часто не имеют общих весов. + +42 +00:04:27,280 --> 00:04:31,440 +Таким образом, у нас есть целый блок (кодер), который можно обучить понимать последовательность + +43 +00:04:31,440 --> 00:04:36,480 +и извлекать релевантную информацию. Например, для сценария перевода, который мы рассматривали ранее, + +44 +00:04:36,480 --> 00:04:44,160 +это означает разбор и понимание того, что было сказано на английском языке; извлечение + +45 +00:04:44,160 --> 00:04:49,040 +информации из этого языка и помещение всего этого в вектор, насыщенный информацией. + +46 +00:04:50,880 --> 00:04:57,280 +С другой стороны, у нас есть декодер, единственной целью которого является декодирование признака, выводимого + +47 +00:04:57,280 --> 00:05:03,760 +кодером. Этот декодер может быть специализирован для совершенно другого языка или даже модальности, + +48 +00:05:03,760 --> 00:05:11,760 +например, изображения или речи. Кодеры-декодеры являются особенными по нескольким причинам. Во-первых, + +49 +00:05:11,760 --> 00:05:17,040 +они способны справляться с задачами преобразования последовательности в последовательность, такими как перевод, который мы только что видели. + +50 +00:05:18,640 --> 00:05:23,880 +Во-вторых, веса между частями кодера и декодера не обязательно являются общими. Давайте + +51 +00:05:24,480 --> 00:05:31,200 +возьмем другой пример перевода. Здесь мы переводим "Transformers are powerful" на французский язык. + +52 +00:05:32,240 --> 00:05:36,560 +Во-первых, это означает, что из последовательности из трех слов мы можем сгенерировать + +53 +00:05:36,560 --> 00:05:42,240 +последовательность из четырех слов. Кто-то может возразить, что с этим можно справиться с помощью декодера, + +54 +00:05:42,240 --> 00:05:46,960 +который будет генерировать перевод авторегрессивным способом, и он будет прав! + +55 +00:05:49,840 --> 00:05:53,840 +Другим примером того, где трансформеры последовательности в последовательность проявляют себя с лучшей стороны, является суммаризация. + +56 +00:05:54,640 --> 00:05:58,560 +Здесь у нас есть очень длинная последовательность, как правило, полный текст, + +57 +00:05:58,560 --> 00:06:03,840 +и мы хотим обобщить его. Поскольку кодер и декодер разделены, + +58 +00:06:03,840 --> 00:06:08,880 +мы можем иметь разные длины контекста (например, очень длинный контекст для кодера, который + +59 +00:06:08,880 --> 00:06:13,840 +обрабатывает текст, и меньший контекст для декодера, который обрабатывает обобщенный текст). + +60 +00:06:16,240 --> 00:06:20,480 +Существует множество моделей преобразования последовательности в последовательность. + +61 +00:06:20,480 --> 00:06:24,160 +Здесь приведено несколько примеров популярных моделей кодеров-декодеров, доступных в библиотеке трансформеров. + +62 +00:06:26,320 --> 00:06:31,200 +Кроме того, вы можете загрузить кодер и декодер внутри модели кодера-декодера! + +63 +00:06:31,200 --> 00:06:35,040 +Поэтому, в зависимости от конкретной задачи, которую вы ставите перед собой, + +64 +00:06:35,040 --> 00:06:40,240 +вы можете использовать конкретные кодеры и декодеры, которые зарекомендовали себя с лучшей стороны + +65 +00:06:40,240 --> 00:06:49,850 +в этих конкретных задачах. На этом мы завершаем разговор о кодерах-декодерах. Спасибо за просмотр! + diff --git a/subtitles/zh-CN/03_what-is-transfer-learning.srt b/subtitles/zh-CN/03_what-is-transfer-learning.srt index 8a8a0b509..4ddaf9c5d 100644 --- a/subtitles/zh-CN/03_what-is-transfer-learning.srt +++ b/subtitles/zh-CN/03_what-is-transfer-learning.srt @@ -5,22 +5,22 @@ 2 00:00:05,550 --> 00:00:07,293 -- 什么是迁移学习? +- 什么是转移学习? - What is transfer learning? 3 00:00:09,480 --> 00:00:10,920 -迁移学习的思想 +转移学习的思想 The idea of transfer learning 4 00:00:10,920 --> 00:00:12,570 -是利用所获得的知识 +是利用在另一项任务上使用大量数据训练的模型结果 is to leverage the knowledge acquired 5 00:00:12,570 --> 00:00:15,543 -通过在另一项任务上使用大量数据训练的模型。 +来获取知识。 by a model trained with lots of data on another task. 6 @@ -30,12 +30,12 @@ The model A will be trained specifically for task A. 7 00:00:20,130 --> 00:00:22,200 -现在假设你想训练模型 B +现在假设您想为了另一个任务 Now let's say you want to train a model B 8 00:00:22,200 --> 00:00:23,970 -为了不同的任务。 +训练模型 B。 for a different task. 9 @@ -45,17 +45,17 @@ One option would be to train the model from scratch. 10 00:00:27,330 --> 00:00:30,633 -这可能需要大量的计算、时间和数据。 +但这可能需要大量的计算、时间和数据。 This could take lots of computation, time and data. 11 00:00:31,470 --> 00:00:34,260 -相反,我们可以初始化模型 B +我们可以有另一种选择,初始化模型 B Instead, we could initialize model B 12 00:00:34,260 --> 00:00:36,570 -与模型 A 具有相同的权重, +它与模型 A 具有相同的权重, with the same weights as model A, 13 @@ -75,37 +75,37 @@ all the model's weight are initialized randomly. 16 00:00:45,870 --> 00:00:48,870 -在这个例子中,我们正在训练一个 BERT 模型 +在这个例子中,我们正在基于识别任务上 In this example, we are training a BERT model 17 00:00:48,870 --> 00:00:50,220 -在识别任务上 +训练一个 BERT 模型 on the task of recognizing 18 00:00:50,220 --> 00:00:52,203 -两个句子是否相似。 +来判断两个句子是否相似。 if two sentences are similar or not. 19 00:00:54,116 --> 00:00:56,730 -在左边,它是从头开始训练的, +左边的例子是从头开始训练的, On the left, it's trained from scratch, 20 00:00:56,730 --> 00:01:00,000 -在右侧,它正在微调预训练模型。 +右边则代表正在微调预训练模型。 and on the right it's fine-tuning a pretrained model. 21 00:01:00,000 --> 00:01:02,220 -正如我们所见,使用迁移学习 +正如我们所见,使用转移学习 As we can see, using transfer learning 22 00:01:02,220 --> 00:01:05,160 -并且预训练模型产生了更好的结果。 +和预训练模型产生了更好的结果。 and the pretrained model yields better results. 23 @@ -120,7 +120,7 @@ The training from scratch is capped around 70% accuracy 25 00:01:10,620 --> 00:01:13,293 -而预训练模型轻松击败了 86%。 +而预训练模型轻松达到了 86%。 while the pretrained model beats the 86% easily. 26 @@ -130,17 +130,17 @@ This is because pretrained models 27 00:01:16,140 --> 00:01:18,420 -通常接受大量数据的训练 +通常基于大量数据进行训练 are usually trained on large amounts of data 28 00:01:18,420 --> 00:01:21,000 -为模型提供统计理解 +这些数据为模型在预训练期间 that provide the model with a statistical understanding 29 00:01:21,000 --> 00:01:23,413 -预训练期间使用的语言。 +提供了对语言使用的统计理解。 of the language used during pretraining. 30 @@ -150,7 +150,7 @@ In computer vision, 31 00:01:25,950 --> 00:01:28,080 -迁移学习已成功应用 +转移学习已成功应用 transfer learning has been applied successfully 32 @@ -165,7 +165,7 @@ Models are frequently pretrained on ImageNet, 34 00:01:32,850 --> 00:01:36,153 -包含 120 万张照片图像的数据集。 +它是一种包含 120 万张照片图像的数据集。 a dataset containing 1.2 millions of photo images. 35 @@ -190,12 +190,12 @@ In Natural Language Processing, 39 00:01:49,140 --> 00:01:51,870 -迁移学习是最近才出现的。 +转移学习是最近才出现的。 transfer learning is a bit more recent. 40 00:01:51,870 --> 00:01:54,480 -与 ImageNet 的一个关键区别是预训练 +它与 ImageNet 的一个关键区别是预训练 A key difference with ImageNet is that the pretraining 41 @@ -205,12 +205,12 @@ is usually self-supervised, 42 00:01:56,460 --> 00:01:58,770 -这意味着它不需要人工注释 +这意味着它不需要人工对标签 which means it doesn't require humans annotations 43 00:01:58,770 --> 00:01:59,673 -对于标签。 +进行注释。 for the labels. 44 @@ -225,7 +225,7 @@ is to guess the next word in a sentence. 46 00:02:05,310 --> 00:02:07,710 -这只需要大量的文本。 +它只需要大量的输入文本。 Which only requires lots and lots of text. 47 @@ -235,12 +235,12 @@ GPT-2 for instance, was pretrained this way 48 00:02:10,710 --> 00:02:12,900 -使用 4500 万个链接的内容 +它使用 4500 万个用户在 Reddit 上发布的 using the content of 45 millions links 49 00:02:12,900 --> 00:02:14,673 -用户在 Reddit 上发布。 +链接的内容。 posted by users on Reddit. 50 @@ -260,42 +260,42 @@ Which is similar to fill-in-the-blank tests 53 00:02:24,540 --> 00:02:26,760 -你可能在学校做过。 +您可能在学校做过。 you may have done in school. 54 00:02:26,760 --> 00:02:29,880 -BERT 是使用英文维基百科以这种方式进行预训练的 +BERT 是使用英文维基百科和 11,000 本未出版的书籍 BERT was pretrained this way using the English Wikipedia 55 00:02:29,880 --> 00:02:31,893 -和 11,000 本未出版的书籍。 +进行预训练的。 and 11,000 unpublished books. 56 00:02:33,120 --> 00:02:36,450 -在实践中,迁移学习应用于给定模型 +在实践中,转移学习是通过抛弃原模型的头部 In practice, transfer learning is applied on a given model 57 00:02:36,450 --> 00:02:39,090 -通过扔掉它的头,也就是说, +即其针对预训练目标的最后几层 by throwing away its head, that is, 58 00:02:39,090 --> 00:02:42,150 -它的最后一层专注于预训练目标, +并用一个新的、随机初始化的头部 its last layers focused on the pretraining objective, 59 00:02:42,150 --> 00:02:45,360 -并用一个新的、随机初始化的头替换它 +来替换它来应用的 and replacing it with a new, randomly initialized head 60 00:02:45,360 --> 00:02:46,860 -适合手头的任务。 +这个新的头部适用于当前的任务。 suitable for the task at hand. 61 @@ -320,37 +320,37 @@ Since our task had two labels. 65 00:02:59,700 --> 00:03:02,490 -为了尽可能高效,使用预训练模型 +为了尽可能高效 To be as efficient as possible, the pretrained model used 66 00:03:02,490 --> 00:03:03,770 -应该尽可能相似 +所使用的预训练模型 should be as similar as possible 67 00:03:03,770 --> 00:03:06,270 -对其进行微调的任务。 +应尽可能与其微调的任务相似。 to the task it's fine-tuned on. 68 00:03:06,270 --> 00:03:08,190 -例如,如果问题 +例如,如果当前需要 For instance, if the problem 69 00:03:08,190 --> 00:03:10,860 -是对德语句子进行分类, +对德语句子进行分类, is to classify German sentences, 70 00:03:10,860 --> 00:03:13,053 -最好使用德国预训练模型。 +最好使用德语预训练模型。 it's best to use a German pretrained model. 71 00:03:14,370 --> 00:03:16,649 -但好事也有坏事。 +但有好事也有坏事。 But with the good comes the bad. 72 @@ -360,87 +360,87 @@ The pretrained model does not only transfer its knowledge, 73 00:03:19,380 --> 00:03:21,693 -以及它可能包含的任何偏见。 +同时也转移了它可能包含的任何偏见。 but also any bias it may contain. 74 00:03:22,530 --> 00:03:24,300 -ImageNet 主要包含图像 +ImageNet 主要包含来自美国和西欧 ImageNet mostly contains images 75 00:03:24,300 --> 00:03:26,850 -来自美国和西欧。 +的图像。 coming from the United States and Western Europe. 76 00:03:26,850 --> 00:03:28,020 -所以模型用它微调 +所以基于它进行微调的模型 So models fine-tuned with it 77 00:03:28,020 --> 00:03:31,710 -通常会在来自这些国家 / 地区的图像上表现更好。 +通常会在来自这些国家或地区的图像上表现更好。 usually will perform better on images from these countries. 78 00:03:31,710 --> 00:03:33,690 -OpenAI 还研究了偏差 +OpenAI 还研究了 OpenAI also studied the bias 79 00:03:33,690 --> 00:03:36,120 -在其 GPT-3 模型的预测中 +其使用猜测下一个单词目标 in the predictions of its GPT-3 model 80 00:03:36,120 --> 00:03:36,953 -这是预训练的 +预训练的 GPT-3 模型中 which was pretrained 81 00:03:36,953 --> 00:03:38,750 -使用猜测下一个单词目标。 +预测的偏差。 using the guess the next word objective. 82 00:03:39,720 --> 00:03:41,040 -更改提示的性别 +将提示的性别 Changing the gender of the prompt 83 00:03:41,040 --> 00:03:44,250 -从他非常到她非常 +从“他”更改到“她” from he was very to she was very 84 00:03:44,250 --> 00:03:47,550 -改变了大多数中性形容词的预测 +会使预测从主要是中性形容词 changed the predictions from mostly neutral adjectives 85 00:03:47,550 --> 00:03:49,233 -几乎只有物理的。 +变为几乎只有物理上的形容词。 to almost only physical ones. 86 00:03:50,400 --> 00:03:52,367 -在他们的 GPT-2 模型的模型卡中, +在他们的 GPT-2 的模型卡中, In their model card of the GPT-2 model, 87 00:03:52,367 --> 00:03:54,990 -OpenAI 也承认它的偏见 +OpenAI 也承认了它的偏见 OpenAI also acknowledges its bias 88 00:03:54,990 --> 00:03:56,730 -并且不鼓励使用它 +并且不鼓励在与人类交互的系统中 and discourages its use 89 00:03:56,730 --> 00:03:58,803 -在与人类交互的系统中。 +使用它。 in systems that interact with humans. 90 diff --git a/subtitles/zh-CN/05_transformer-models-encoders.srt b/subtitles/zh-CN/05_transformer-models-encoders.srt index 7c3118cf1..f75cd6686 100644 --- a/subtitles/zh-CN/05_transformer-models-encoders.srt +++ b/subtitles/zh-CN/05_transformer-models-encoders.srt @@ -1,6 +1,6 @@ 1 00:00:00,253 --> 00:00:03,003 -(介绍引人注目) +(引人注目的介绍) (intro striking) 2 @@ -10,12 +10,12 @@ 3 00:00:07,830 --> 00:00:11,070 -一个流行的仅编码器架构的例子是 BURT +一个流行的仅使用编码器架构的例子是 BURT An example of a popular encoder only architecture is BURT 4 00:00:11,070 --> 00:00:13,323 -这是同类产品中最受欢迎的型号。 +这是同类产品中最受欢迎的模型。 which is the most popular model of its kind. 5 @@ -25,37 +25,37 @@ Let's first start by understanding how it works. 6 00:00:18,360 --> 00:00:20,910 -我们将使用一个使用三个词的小例子。 +我们将使用一个三个单词的小例子。 We'll use a small example using three words. 7 00:00:20,910 --> 00:00:23,823 -我们使用这些作为输入并将它们传递给编码器。 +我们使用这些作为输入传递给编码器。 We use these as inputs and pass them through the encoder. 8 00:00:25,290 --> 00:00:28,173 -我们检索每个单词的数字表示。 +得到了每个单词的数值表示。 We retrieve a numerical representation of each word. 9 00:00:29,970 --> 00:00:32,700 -例如,在这里,编码器转换这三个词, +例如,在这里,编码器将这三个词, Here, for example, the encoder converts those three words, 10 00:00:32,700 --> 00:00:37,350 -欢迎来到纽约,在这三个数字序列中。 +welcome to NYC,转换为这三个数字序列。 welcome to NYC, in these three sequences of numbers. 11 00:00:37,350 --> 00:00:40,350 -编码器只输出一个数字序列 +编码器对于每个输入单词 The encoder outputs exactly one sequence of numbers 12 00:00:40,350 --> 00:00:41,493 -每个输入词。 +精确输出一个数字序列。 per input word. 13 @@ -65,7 +65,7 @@ This numerical representation can also be called 14 00:00:44,880 --> 00:00:47,163 -特征向量或特征张量。 +特征向量(feature vector)或特征张量(feature tensor)。 a feature vector, or a feature tensor. 15 @@ -85,22 +85,22 @@ that was passed through the encoder. 18 00:00:56,130 --> 00:00:58,620 -这些向量中的每一个都是一个数字表示 +每个向量都是 Each of these vector is a numerical representation 19 00:00:58,620 --> 00:01:00,033 -有问题的词。 +该词的数字表示。 of the word in question. 20 00:01:01,080 --> 00:01:03,300 -该向量的维度被定义 +该向量的维度由 The dimension of that vector is defined 21 00:01:03,300 --> 00:01:05,520 -通过模型的架构。 +模型的架构所决定。 by the architecture of the model. 22 @@ -115,17 +115,17 @@ These representations contain the value of a word, 24 00:01:13,230 --> 00:01:15,240 -但语境化。 +但包含上下文化的处理。 but contextualized. 25 00:01:15,240 --> 00:01:18,570 -例如,归因于单词 “to” 的向量 +例如,与单词 "to" 相关联的向量 For example, the vector attributed to the word "to" 26 00:01:18,570 --> 00:01:22,290 -不只是 “to” 这个词的代表。 +不只是 “to” 这个词的表示。 isn't the representation of only the "to" word. 27 @@ -150,7 +150,7 @@ the words on the left of the one we're studying, 31 00:01:32,970 --> 00:01:34,980 -这里是 “欢迎” 这个词, +这里是 “Welcome” 这个词, here the word "Welcome", 32 @@ -180,22 +180,22 @@ holds the meaning of the word within the text. 37 00:01:53,310 --> 00:01:56,073 -由于自我注意机制,它做到了这一点。 +由于自注意力机制,它做到了这一点。 It does this thanks to the self-attention mechanism. 38 00:01:57,240 --> 00:02:00,630 -自注意力机制涉及到不同的位置, +自注意力机制指的是与单个序列中的不同位置 The self-attention mechanism relates to different positions, 39 00:02:00,630 --> 00:02:02,850 -或单个序列中的不同单词 +或不同单词相关联 or different words in a single sequence 40 00:02:02,850 --> 00:02:06,003 -为了计算该序列的表示。 +以计算该序列的表示形式。 in order to compute a representation of that sequence. 41 @@ -220,17 +220,17 @@ We won't dive into the specifics here 45 00:02:18,030 --> 00:02:19,680 -这将提供一些进一步的阅读 +我们会提供一些进一步的阅读资料 which will offer some further readings 46 00:02:19,680 --> 00:02:21,330 -如果你想获得更好的理解 +如果您想对底层发生了什么 if you want to get a better understanding 47 00:02:21,330 --> 00:02:22,953 -在引擎盖下发生的事情。 +有更好的理解。 at what happens under the hood. 48 @@ -250,17 +250,17 @@ in a wide variety of tasks. 51 00:02:32,100 --> 00:02:33,360 -例如,伯特, +例如,BERT, For example, BERT, 52 00:02:33,360 --> 00:02:35,670 -可以说是最著名的变压器模型, +可以说是最著名的 transformer 模型, arguably the most famous transformer model, 53 00:02:35,670 --> 00:02:37,590 -是一个独立的编码器模型, +它是一个独立的编码器模型, is a standalone encoder model, 54 @@ -270,12 +270,12 @@ and at the time of release, 55 00:02:38,820 --> 00:02:40,440 -这将是最先进的 +它是许多 it'd be the state of the art 56 00:02:40,440 --> 00:02:42,780 -在许多序列分类任务中, +序列分类任务 in many sequence classification tasks, 57 @@ -285,32 +285,32 @@ question answering tasks, 58 00:02:44,190 --> 00:02:46,743 -掩码语言建模仅举几例。 +和掩码语言建模等任务中的最先进技术。 and mask language modeling to only cite of few. 59 00:02:48,150 --> 00:02:50,460 -这个想法是编码器非常强大 +编码器非常擅长 The idea is that encoders are very powerful 60 00:02:50,460 --> 00:02:52,470 -在提取携带载体 +提取包含有意义信息的 at extracting vectors that carry 61 00:02:52,470 --> 00:02:55,350 -关于序列的有意义的信息。 +关于序列的向量。 meaningful information about a sequence. 62 00:02:55,350 --> 00:02:57,870 -然后可以在路上处理这个向量 +这个向量可以被传递给后续的神经元来进一步处理 This vector can then be handled down the road 63 00:02:57,870 --> 00:03:00,070 -通过额外的神经元来理解它们。 +以便理解其中包含的信息。 by additional neurons to make sense of them. 64 @@ -330,22 +330,22 @@ First of all, Masked Language Modeling, or MLM. 67 00:03:09,900 --> 00:03:11,970 -这是预测隐藏词的任务 +这是在一个单词序列中 It's the task of predicting a hidden word 68 00:03:11,970 --> 00:03:13,590 -在一个单词序列中。 +预测隐藏词的任务。 in a sequence of word. 69 00:03:13,590 --> 00:03:15,630 -在这里,例如,我们隐藏了这个词 +在这里,例如,我们在 “My” 和 “is” 之间 Here, for example, we have hidden the word 70 00:03:15,630 --> 00:03:17,247 -在 “我的” 和 “是” 之间。 +隐藏了这个词。 between "My" and "is". 71 @@ -360,7 +360,7 @@ It was trained to predict hidden words in a sequence. 73 00:03:25,230 --> 00:03:27,930 -编码器尤其在这种情况下大放异彩 +编码器在这种情况下尤其大放异彩 Encoders shine in this scenario in particular 74 @@ -375,32 +375,32 @@ If we didn't have the words on the right, 76 00:03:32,947 --> 00:03:34,650 -“是”、“Sylvain” 和 “.”, +“is”、“Sylvain” 和 “.”, "is", "Sylvain" and the ".", 77 00:03:34,650 --> 00:03:35,940 -那么机会就很小 +那么BERT 将能够识别名称的 then there is very little chance 78 00:03:35,940 --> 00:03:38,580 -BERT 将能够识别名称 +作为正确的词 that BERT would have been able to identify name 79 00:03:38,580 --> 00:03:40,500 -作为正确的词。 +的机会就很小。 as the correct word. 80 00:03:40,500 --> 00:03:42,270 -编码器需要有很好的理解 +为了预测一个掩码单词 The encoder needs to have a good understanding 81 00:03:42,270 --> 00:03:45,360 -序列以预测掩码词 +编码器需要对序列有很好的理解 of the sequence in order to predict a masked word 82 @@ -410,12 +410,12 @@ as even if the text is grammatically correct, 83 00:03:48,840 --> 00:03:50,610 -它不一定有意义 +但不一定符合 it does not necessarily make sense 84 00:03:50,610 --> 00:03:52,413 -在序列的上下文中。 +序列的上下文。 in the context of the sequence. 85 @@ -440,22 +440,22 @@ The model's aim is to identify the sentiment of a sequence. 89 00:04:09,540 --> 00:04:11,280 -它的范围可以从给出一个序列, +它可以从给出的一个序列, It can range from giving a sequence, 90 00:04:11,280 --> 00:04:12,960 -从一颗星到五颗星的评级 +做出一颗星到五颗星的评级 a rating from one to five stars 91 00:04:12,960 --> 00:04:15,900 -如果进行评论分析以给予肯定, +如果进行评论分析 if doing review analysis to giving a positive, 92 00:04:15,900 --> 00:04:17,820 -或对序列的负面评价 +来对一个序列进行积极或消极的评级 or negative rating to a sequence 93 @@ -495,7 +495,7 @@ containing the same words, 100 00:04:35,220 --> 00:04:37,170 -意义完全不同, +意义却完全不同, the meaning is entirely different, 101 @@ -505,6 +505,6 @@ and the encoder model is able to grasp that difference. 102 00:04:41,404 --> 00:04:44,154 -(结尾引人注目) +(引人注目的结尾) (outro striking) diff --git a/subtitles/zh-CN/06_transformer-models-decoders.srt b/subtitles/zh-CN/06_transformer-models-decoders.srt index 4f81ed010..7a22241f2 100644 --- a/subtitles/zh-CN/06_transformer-models-decoders.srt +++ b/subtitles/zh-CN/06_transformer-models-decoders.srt @@ -5,13 +5,13 @@ 2 00:00:07,140 --> 00:00:07,973 -一个例子 +一种流行的仅包含解码器架构 An example 3 00:00:07,973 --> 00:00:11,338 -一种流行的解码器唯一架构是 GPT 两种。 -of a popular decoder only architecture is GPT two. +的例子是 GPT-2。 +of a popular decoder only architecture is GPT-2. 4 00:00:11,338 --> 00:00:14,160 @@ -20,7 +20,7 @@ In order to understand how decoders work 5 00:00:14,160 --> 00:00:17,430 -我们建议你观看有关编码器的视频。 +我们建议您观看有关编码器的视频。 we recommend taking a look at the video regarding encoders. 6 @@ -35,7 +35,7 @@ One can use a decoder 8 00:00:21,210 --> 00:00:23,760 -对于大多数与编码器相同的任务 +执行与编码器相同的大多数任务 for most of the same tasks as an encoder 9 @@ -55,12 +55,12 @@ with the encoder to try 12 00:00:30,300 --> 00:00:32,670 -并了解架构差异 +并了解在编码器和解码器之间 and understand the architectural differences 13 00:00:32,670 --> 00:00:34,803 -在编码器和解码器之间。 +的架构差异。 between an encoder and decoder. 14 @@ -70,12 +70,12 @@ We'll use a small example using three words. 15 00:00:38,910 --> 00:00:41,050 -我们通过他们的解码器传递它们。 +我们通过解码器传递它们。 We pass them through their decoder. 16 00:00:41,050 --> 00:00:44,793 -我们检索每个单词的数字表示。 +我们检索每个单词的数值表示。 We retrieve a numerical representation for each word. 17 @@ -85,17 +85,17 @@ Here for example, the decoder converts the three words. 18 00:00:49,350 --> 00:00:53,545 -欢迎来到纽约,欢迎来到这三个数字序列。 +Welcome to NYC,这三个数字序列。 Welcome to NYC, and these three sequences of numbers. 19 00:00:53,545 --> 00:00:56,040 -解码器只输出一个序列 +解码器针对每个输入词汇 The decoder outputs exactly one sequence 20 00:00:56,040 --> 00:00:58,740 -每个输入词的数字。 +只输出一个数列。 of numbers per input word. 21 @@ -105,7 +105,7 @@ This numerical representation can also 22 00:01:00,630 --> 00:01:03,783 -称为特征向量或特征传感器。 +称为特征向量(feature vector)或特征传感器(feature sensor)。 be called a feature vector or a feature sensor. 23 @@ -115,32 +115,32 @@ Let's dive in this representation. 24 00:01:07,200 --> 00:01:08,490 -它包含一个向量 +它包含了每个通过解码器 It contains one vector 25 00:01:08,490 --> 00:01:11,340 -每个通过解码器的单词。 +传递的单词的一个向量。 per word that was passed through the decoder. 26 00:01:11,340 --> 00:01:14,250 -这些向量中的每一个都是一个数字表示 +这些向量中的每一个单词 Each of these vectors is a numerical representation 27 00:01:14,250 --> 00:01:15,573 -有问题的词。 +都是一个数值表示。 of the word in question. 28 00:01:16,920 --> 00:01:18,562 -该向量的维度被定义 +这个向量的维度 The dimension of that vector is defined 29 00:01:18,562 --> 00:01:20,703 -通过模型的架构。 +由模型的架构所决定。 by the architecture of the model. 30 @@ -150,12 +150,12 @@ Where the decoder differs from the encoder is principally 31 00:01:26,040 --> 00:01:28,200 -具有自我注意机制。 +具有自注意力机制。 with its self attention mechanism. 32 00:01:28,200 --> 00:01:30,843 -它使用所谓的掩蔽自我关注。 +它使用所谓的掩蔽自注意力。 It's using what is called masked self attention. 33 @@ -165,27 +165,27 @@ Here, for example, if we focus on the word "to" 34 00:01:34,650 --> 00:01:37,620 -我们会看到 vector 是绝对未修改的 +我们会发现它的向量 we'll see that is vector is absolutely unmodified 35 00:01:37,620 --> 00:01:39,690 -用纽约的话来说。 +完全未被 NYC 单词修改。 by the NYC word. 36 00:01:39,690 --> 00:01:41,731 -那是因为右边所有的话,也都知道 +那是因为右边所有的话,即 That's because all the words on the right, also known 37 00:01:41,731 --> 00:01:45,276 -因为这个词的正确上下文被掩盖了 +单词的右侧上下文都被屏蔽了 as the right context of the word is masked rather 38 00:01:45,276 --> 00:01:49,230 -而不是受益于左右所有的话。 +而没有从左侧和右侧的所有单词中受益。 than benefiting from all the words on the left and right. 39 @@ -205,32 +205,32 @@ which can be the left context or the right context. 42 00:01:59,539 --> 00:02:03,356 -Masked self attention 机制不同 +掩蔽自注意力机制不同于 The masked self attention mechanism differs 43 00:02:03,356 --> 00:02:04,320 -来自 self attention 机制 +自注意力机制 from the self attention mechanism 44 00:02:04,320 --> 00:02:07,110 -通过使用额外的掩码来隐藏上下文 +通过使用额外的掩码在单词的两边 by using an additional mask to hide the context 45 00:02:07,110 --> 00:02:09,390 -在单词的两边 +来隐藏上下文 on either side of the word 46 00:02:09,390 --> 00:02:12,810 -单词数值表示不会受到影响 +通过隐藏上下文中的单词 the words numerical representation will not be affected 47 00:02:12,810 --> 00:02:14,853 -通过隐藏上下文中的单词。 +单词数值表示不会受到影响。 by the words in the hidden context. 48 @@ -245,7 +245,7 @@ Decoders like encoders can be used as standalone models 50 00:02:22,380 --> 00:02:25,020 -因为它们生成数字表示。 +因为它们生成数值表示。 as they generate a numerical representation. 51 @@ -265,42 +265,42 @@ A word can only have access to its left context 54 00:02:34,530 --> 00:02:36,690 -只能访问他们的左上下文。 +因为它只有左侧的上下文信息。 having only access to their left context. 55 00:02:36,690 --> 00:02:39,120 -他们天生擅长文本生成 +它们天生擅长文本生成 They're inherently good at text generation 56 00:02:39,120 --> 00:02:41,010 -生成单词的能力 +即在已知的词序列基础上生成一个单词 the ability to generate a word 57 00:02:41,010 --> 00:02:45,000 -或给定已知单词序列的单词序列。 +或单词序列的能力。 or a sequence of words given a known sequence of words. 58 00:02:45,000 --> 00:02:45,833 -这是众所周知的 +这被称为 This is known 59 00:02:45,833 --> 00:02:49,083 -作为因果语言建模或自然语言生成。 +因果语言建模或自然语言生成。 as causal language modeling or natural language generation. 60 00:02:50,430 --> 00:02:53,520 -这是因果语言建模如何工作的示例。 +下面是一个展示因果语言模型的工作原理的示例。 Here's an example of how causal language modeling works. 61 00:02:53,520 --> 00:02:56,410 -我们从一个词开始,这是我的 +我们从一个词 my 开始, We start with an initial word, which is my 62 @@ -320,52 +320,52 @@ and this vector contains information about the sequence 65 00:03:07,230 --> 00:03:08,733 -这是一个词。 +这里的序列是一个单词。 which is here a single word. 66 00:03:09,780 --> 00:03:11,430 -我们应用一个小的转换 +我们对该向量 We apply a small transformation 67 00:03:11,430 --> 00:03:13,110 -到那个向量,以便它映射 +应用一个小的转换 to that vector so that it maps 68 00:03:13,110 --> 00:03:16,500 -到模型已知的所有单词,这是一个映射 +使其映射到模型已知的所有单词 to all the words known by the model, which is a mapping 69 00:03:16,500 --> 00:03:19,890 -我们稍后会看到称为语言建模头。 +这个映射我们稍后会看到,称为语言模型头部信息 that we'll see later called a language modeling head. 70 00:03:19,890 --> 00:03:21,930 -我们确定该模型相信 +我们发现模型认为 We identify that the model believes 71 00:03:21,930 --> 00:03:25,053 -最有可能的后续单词是 name。 +接下来最有可能的单词是 “name”。 that the most probable following word is name. 72 00:03:26,250 --> 00:03:28,710 -然后我们取那个新词并添加它 +然后我们把这个新单词加到原始的序列 my 后面 We then take that new word and add it 73 00:03:28,710 --> 00:03:33,480 -到我的初始序列,我们现在以我的名字命名。 +我们得到了 my name。 to the initial sequence from my, we are now at my name. 74 00:03:33,480 --> 00:03:36,870 -这就是自回归方面的用武之地。 +这就是自回归(auto-regressive)的作用所在。 This is where the auto regressive aspect comes in. 75 @@ -375,7 +375,7 @@ Auto regressive models. 76 00:03:38,490 --> 00:03:42,513 -我们使用他们过去的输出作为输入和以下步骤。 +我们使用它们过去的输出作为输入和接下来的步骤。 We use their past outputs as inputs and the following steps. 77 @@ -395,7 +395,7 @@ and retrieve the most probable following word. 80 00:03:52,978 --> 00:03:57,978 -本例中就是 “是” 这个词,我们重复操作 +本例中就是 “is” 这个词,我们重复操作 In this case, it is the word "is", we repeat the operation 81 @@ -410,13 +410,13 @@ We've now generated a full sentence. 83 00:04:04,590 --> 00:04:07,890 -我们决定就此打住,但我们可以继续一段时间。 +我们决定就此打住,但我们也可以继续一段时间。 We decide to stop there, but we could continue for a while. 84 00:04:07,890 --> 00:04:12,890 -例如,GPT 2 的最大上下文大小为 1,024。 -GPT two, for example, has a maximum context size of 1,024. +例如,GPT-2 的最大上下文大小为 1,024。 +GPT-2, for example, has a maximum context size of 1,024. 85 00:04:13,170 --> 00:04:16,830 @@ -425,11 +425,11 @@ We could eventually generate up to a 1,024 words 86 00:04:16,830 --> 00:04:19,050 -并且解码器仍然会有一些记忆 +并且解码器仍然会对这个序列的前几个单词 and the decoder would still have some memory 87 00:04:19,050 --> 00:04:21,003 -这个序列中的第一个单词。 +有一些记忆。 of the first words in this sequence. diff --git a/subtitles/zh-CN/07_transformer-models-encoder-decoders.srt b/subtitles/zh-CN/07_transformer-models-encoder-decoders.srt index 28b847a27..c4303f575 100644 --- a/subtitles/zh-CN/07_transformer-models-encoder-decoders.srt +++ b/subtitles/zh-CN/07_transformer-models-encoder-decoders.srt @@ -10,42 +10,42 @@ 3 00:00:05,063 --> 00:00:07,638 -我们将研究编码器 - 解码器架构。 +我们将研究编码-解码器架构。 we'll study the encoder-decoder architecture. 4 00:00:07,638 --> 00:00:12,243 -流行的编码器 - 解码器模型的一个示例是 T5。 +流行的编码-解码器模型的一个示例是 T5。 An example of a popular encoder-decoder model is T5. 5 00:00:13,770 --> 00:00:16,980 -为了理解编码器 - 解码器是如何工作的, +为了理解编码-解码器是如何工作的, In order to understand how the encoder-decoder works, 6 00:00:16,980 --> 00:00:18,630 -我们建议你查看视频 +我们建议您查看 we recommend you check out the videos 7 00:00:18,630 --> 00:00:22,590 -“Encoders and Decoders as standalone models”。 +将编码-解码器作为独立的模型(Encoders and Decoders as standalone models)这一视频。 on encoders and decoders as standalone models. 8 00:00:22,590 --> 00:00:24,990 -了解他们如何单独工作 +了解它们如何单独工作 Understanding how they work individually 9 00:00:24,990 --> 00:00:28,323 -将有助于理解编码器 - 解码器的工作原理。 +将有助于理解编码-解码器的工作原理。 will help understanding how an encoder-decoder works. 10 00:00:30,510 --> 00:00:33,390 -让我们从我们所看到的编码器开始。 +让我们从我们已了解的编码器开始。 Let's start from what we've seen about the encoder. 11 @@ -55,27 +55,27 @@ The encoder takes words as inputs, 12 00:00:36,240 --> 00:00:38,520 -通过编码器投射它们, +通过编码器进行转换, casts them through the encoder, 13 00:00:38,520 --> 00:00:40,800 -并检索数字表示 +并检索每个单词的 and retrieves a numerical representation 14 00:00:40,800 --> 00:00:42,663 -对于通过它的每个单词。 +数值表示。 for each word cast through it. 15 00:00:43,560 --> 00:00:46,470 -我们现在知道这个数字表示 +我们现在知道这个数值表示 We now know that this numerical representation 16 00:00:46,470 --> 00:00:49,473 -包含有关序列含义的信息。 +包含关于序列意义的信息。 holds information about the meaning of the sequence. 17 @@ -90,12 +90,12 @@ In this scenario, 19 00:00:57,510 --> 00:00:59,190 -我们以某种方式使用解码器 +我们以某种我们以前没见过的方式 we're using the decoder in a manner 20 00:00:59,190 --> 00:01:00,960 -我们以前没见过。 +使用解码器。 that we haven't seen before. 21 @@ -105,37 +105,37 @@ We're passing the outputs of the encoder directly to it. 22 00:01:05,356 --> 00:01:07,770 -除了编码器输出, +另外,在给解码器输入序列的同时 Additionally to the encoder outputs, 23 00:01:07,770 --> 00:01:10,800 -我们还给解码器一个序列。 +我们还需要编码器的输出。 we also give the decoder a sequence. 24 00:01:10,800 --> 00:01:12,840 -当提示解码器输出时 +在不给定初始序列的情况下 When prompting the decoder for an output 25 00:01:12,840 --> 00:01:14,190 -没有初始序列, +向解码器提示输出时, with no initial sequence, 26 00:01:14,190 --> 00:01:16,140 -我们可以给它指示的值 +我们可以给它一个 we can give it the value that indicates 27 00:01:16,140 --> 00:01:18,060 -序列的开始。 +表示序列开头的值。 the start of a sequence. 28 00:01:18,060 --> 00:01:20,919 -这就是编码器 - 解码器魔术发生的地方。 +这就是编码-解码器魔术发生的地方。 And that's where the encoder-decoder magic happens. 29 @@ -150,7 +150,7 @@ It computes a prediction, 31 00:01:25,980 --> 00:01:28,858 -并输出一个数字表示。 +并输出一个数值表示。 and outputs a numerical representation. 32 @@ -165,7 +165,7 @@ It has, in a sense, encoded that sequence. 34 00:01:36,300 --> 00:01:38,130 -反过来,解码器, +反过来,解码器 And the decoder, in turn, 35 @@ -235,7 +235,7 @@ As we have seen before with the decoder, 48 00:02:15,540 --> 00:02:18,720 -它可以以自动回归的方式起作用。 +它可以以自回归的方式起作用。 it can act in an auto-regressive manner. 49 @@ -245,17 +245,17 @@ The word it has just output can now be used as an input. 50 00:02:22,933 --> 00:02:26,188 -这个,结合数值表示 +这个编码器输出的数值表示 This, in combination with the numerical representation 51 00:02:26,188 --> 00:02:28,560 -编码器输出, +和初始化的值结合, output by the encoder, 52 00:02:28,560 --> 00:02:31,203 -现在可用于生成第二个单词。 +可以被用于生成第二个单词。 can now be used to generate a second word. 53 @@ -285,27 +285,27 @@ We can continue on and on, for example, 58 00:02:44,070 --> 00:02:46,320 -直到解码器输出一个值 +直到解码器输出一个 until the decoder outputs a value 59 00:02:46,320 --> 00:02:48,540 -我们考虑一个停止值, +我们认为是停止值的数值, that we consider a stopping value, 60 00:02:48,540 --> 00:02:51,093 -就像一个点,表示序列的结尾。 +比如句号表示序列的结束。 like a dot meaning the end of a sequence. 61 00:02:53,580 --> 00:02:55,926 -在这里,我们已经看到了完整的机制 +在这里,我们已经看到了编码-解码 transformer Here, we've seen the full mechanism 62 00:02:55,926 --> 00:02:57,540 -编码器 - 解码器变压器。 +完整的机制。 of the encoder-decoder transformer. 63 @@ -315,12 +315,12 @@ Let's go over it one more time. 64 00:02:59,280 --> 00:03:02,773 -我们有一个发送到编码器的初始序列。 +我们有一个初始序列被送到编码器中。 We have an initial sequence that is sent to the encoder. 65 00:03:02,773 --> 00:03:06,450 -然后将该编码器输出发送到解码器 +编码器的输出发送到解码器 That encoder output is then sent to the decoder 66 @@ -330,32 +330,32 @@ for it to be decoded. 67 00:03:08,760 --> 00:03:12,450 -虽然它现在可以在一次使用后丢弃编码器, +虽然在一次使用后可以丢弃编码器, While it can now discard the encoder after a single use, 68 00:03:12,450 --> 00:03:14,427 -解码器将被多次使用 +但解码器将被多次使用 the decoder will be used several times 69 00:03:14,427 --> 00:03:17,763 -直到我们生成了我们需要的每一个词。 +直到我们生成了所需要的每一个词。 until we have generated every word that we need. 70 00:03:19,288 --> 00:03:21,510 -那么让我们看一个具体的例子 +那么让我们结合翻译语言建模 So let's see a concrete example 71 00:03:21,510 --> 00:03:23,460 -与翻译语言建模。 +看一个具体的例子。 with Translation Language Modeling. 72 00:03:23,460 --> 00:03:24,930 -也称为转导, +也称为传导, Also called transduction, 73 @@ -370,42 +370,42 @@ Here, we would like to translate this English sequence 75 00:03:30,577 --> 00:03:33,067 -法语 “欢迎来到纽约”。 +“Welcome to NYC”到法语。 "Welcome to NYC" in French. 76 00:03:33,067 --> 00:03:35,460 -我们正在使用变压器模型 +我们正在使用 transformer 模型 We're using a transformer model 77 00:03:35,460 --> 00:03:38,070 -明确针对该任务进行了培训。 +明确针对该任务进行了训练。 that is trained for that task explicitly. 78 00:03:38,070 --> 00:03:40,560 -我们使用编码器来创建表示 +我们使用编码器来创建英语句子 We use the encoder to create a representation 79 00:03:40,560 --> 00:03:42,240 -的英语句子。 +的表示。 of the English sentence. 80 00:03:42,240 --> 00:03:44,730 -我们把它投给解码器, +我们使用编码器来创建英语句子的表示形式, We cast this to the decoder, 81 00:03:44,730 --> 00:03:46,620 -使用序列字的开头, +然后将其传递给解码器, with the use of the start of sequence word, 82 00:03:46,620 --> 00:03:49,173 -我们要求它输出第一个单词。 +在使用开始序列单词的情况下,请求它输出第一个单词。 we ask it to output the first word. 83 @@ -445,7 +445,7 @@ Finally, we ask the decoder to predict a third word 90 00:04:13,590 --> 00:04:15,330 -它预测纽约市,这是正确的。 +它预测 NYC,这是正确的。 It predicts NYC, which is correct. 91 @@ -455,7 +455,7 @@ We've translated the sentence. 92 00:04:18,288 --> 00:04:20,760 -编码器 - 解码器真正发挥作用的地方, +编码-解码器真正发挥作用的地方, Where the encoder-decoder really shines, 93 @@ -475,7 +475,7 @@ Therefore, we have an entire block, the encoder, 96 00:04:29,460 --> 00:04:31,650 -可以训练以理解序列 +可以被训练,从而理解序列 that can be trained to understand the sequence 97 @@ -515,12 +515,12 @@ On the other hand, we have the decoder, 104 00:04:53,370 --> 00:04:56,850 -其唯一目的是解码数字表示 +它的唯一目的是解码 whose sole purpose is to decode the numerical representation 105 00:04:56,850 --> 00:04:58,203 -编码器输出。 +编码器输出的数值表示。 output by the encoder. 106 @@ -540,12 +540,12 @@ or even modality like images or speech. 109 00:05:07,170 --> 00:05:10,473 -编码器 - 解码器之所以特殊,有几个原因。 +编码-解码器之所以特殊,有几个原因。 Encoders-decoders are special for several reasons. 110 00:05:11,310 --> 00:05:15,570 -首先,他们能够管理任务的顺序, +首先,它们能够管理任务的顺序, Firstly, they're able to manage sequence to sequence tasks, 111 @@ -555,12 +555,12 @@ like translation that we have just seen. 112 00:05:18,358 --> 00:05:20,940 -其次,编码器之间的权重 +其次,编码器和解码器之间的权重 Secondly, the weights between the encoder 113 00:05:20,940 --> 00:05:24,540 -并且解码器部分不一定共享。 +并不一定共享。 and the decoder parts are not necessarily shared. 114 @@ -570,12 +570,12 @@ Let's take another example of translation. 115 00:05:27,172 --> 00:05:30,810 -这里我们用法语翻译 Transformers are powerful +这里我们用法语 Here we're translating "Transformers are powerful" 116 00:05:30,810 --> 00:05:32,048 -这里我们用法语翻译 Transformers are powerful +翻译 Transformers are powerful in French. 117 @@ -595,12 +595,12 @@ One could argue that this could be handled with a decoder 120 00:05:42,480 --> 00:05:44,160 -那会产生翻译 +那会以自回归的方式 that would generate the translation 121 00:05:44,160 --> 00:05:46,260 -以自回归的方式, +生成翻译结果, in an auto-regressive manner, 122 @@ -610,12 +610,12 @@ and they would be right. 123 00:05:49,980 --> 00:05:51,930 -基于 Transformers 的 Seq2Seq 模型的 +基于 Transformers Seq2Seq 模型的 Another example of where sequence to sequence 124 00:05:51,930 --> 00:05:54,810 -另一个亮点是总结 +另一个亮点是进行总结的能力 transformers shine is in summarization. 125 @@ -650,7 +650,7 @@ which handles the text, 131 00:06:10,230 --> 00:06:12,210 -和解码器的较小上下文 +尔解码器上下文则较小 and a smaller context for the decoder 132 @@ -660,47 +660,47 @@ which handles the summarized sequence. 133 00:06:16,470 --> 00:06:18,840 -有很多序列模型。 +有很多序列到序列的模型。 There are a lot of sequence to sequence models. 134 00:06:18,840 --> 00:06:20,310 -这包含一些例子 +这包含了 Transformers 库中 This contains a few examples 135 00:06:20,310 --> 00:06:22,500 -流行的编码器 - 解码器模型 +几个受欢迎的 of popular encoder-decoder models 136 00:06:22,500 --> 00:06:24,400 -在 Transformers 库中可用。 +编码-解码器模型的示例。 available in the transformers library. 137 00:06:25,829 --> 00:06:29,940 -此外,你可以加载编码器和解码器 +此外,您可以在编码-解码器模型中 Additionally, you can load an encoder and a decoder 138 00:06:29,940 --> 00:06:32,130 -在编码器 - 解码器模型中。 +加载编码器和解码器。 inside an encoder-decoder model. 139 00:06:32,130 --> 00:06:35,190 -因此,根据你针对的具体任务, +因此,根据您要解决的具体任务, Therefore, according to the specific task you are targeting, 140 00:06:35,190 --> 00:06:38,700 -你可以选择使用特定的编码器和解码器, +您可能会选择使用在这些具体任务上证明 you may choose to use specific encoders and decoders, 141 00:06:38,700 --> 00:06:42,613 -在这些特定任务中证明了它们的价值。 +其价值的特定编码器和解码器。 which have proven their worth on these specific tasks. 142 diff --git a/subtitles/zh-CN/08_what-happens-inside-the-pipeline-function-(pytorch).srt b/subtitles/zh-CN/08_what-happens-inside-the-pipeline-function-(pytorch).srt index ca6c0276f..48d021499 100644 --- a/subtitles/zh-CN/08_what-happens-inside-the-pipeline-function-(pytorch).srt +++ b/subtitles/zh-CN/08_what-happens-inside-the-pipeline-function-(pytorch).srt @@ -5,7 +5,8 @@ 2 00:00:05,340 --> 00:00:07,563 -- 管道函数内部发生了什么? +- pipeline 函数内部发生了什么? +*[译者注: pipeline 作为 流水线 的意思] - What happens inside the pipeline function? 3 @@ -25,22 +26,22 @@ of the Transformers library. 6 00:00:15,090 --> 00:00:16,860 -更具体地说,我们将看看 +详细来讲,我们将看 More specifically, we will look 7 00:00:16,860 --> 00:00:19,200 -在情绪分析管道中, +在情绪分析的 pipeline 中, at the sentiment analysis pipeline, 8 00:00:19,200 --> 00:00:22,020 -以及它是如何从以下两个句子开始的, +它是如何从以下两个句子开始的, and how it went from the two following sentences, 9 00:00:22,020 --> 00:00:23,970 -正负标签 +将正负标签 to the positive and negative labels 10 @@ -50,12 +51,12 @@ with their respective scores. 11 00:00:26,760 --> 00:00:29,190 -正如我们在管道演示中看到的那样, +正如我们在 pipeline 展示中看到的那样, As we have seen in the pipeline presentation, 12 00:00:29,190 --> 00:00:31,860 -管道分为三个阶段。 +pipeline 分为三个阶段。 there are three stages in the pipeline. 13 @@ -65,7 +66,7 @@ First, we convert the raw texts to numbers 14 00:00:34,620 --> 00:00:37,173 -该模型可以理解使用分词器。 +该模型可以通过使用分词器理解。 the model can make sense of using a tokenizer. 15 @@ -75,17 +76,17 @@ Then those numbers go through the model, 16 00:00:40,530 --> 00:00:41,943 -输出逻辑。 +输出 logits 。 which outputs logits. 17 00:00:42,780 --> 00:00:45,600 -最后,后处理步骤变换 +最后,后处理步骤转换 Finally, the post-processing steps transforms 18 00:00:45,600 --> 00:00:48,150 -那些登录到标签和分数。 +那些 logits 包含标签和分数。 those logits into labels and scores. 19 @@ -100,17 +101,18 @@ and how to replicate them using the Transformers library, 21 00:00:53,640 --> 00:00:56,043 -从第一阶段开始,标记化。 +从第一阶段开始,分词化。 beginning with the first stage, tokenization. 22 00:00:57,915 --> 00:01:00,360 -令牌化过程有几个步骤。 +分词化过程有几个步骤。 The tokenization process has several steps. 23 00:01:00,360 --> 00:01:04,950 -首先,文本被分成称为标记的小块。 +首先,文本被分成小块, 称之为 token。 +*[译者注: 后面 token-* 均翻译成 分词-*] First, the text is split into small chunks called tokens. 24 @@ -120,7 +122,7 @@ They can be words, parts of words or punctuation symbols. 25 00:01:08,550 --> 00:01:11,580 -然后 tokenizer 将有一些特殊的标记, +然后分词器将有一些特殊的 token , Then the tokenizer will had some special tokens, 26 @@ -130,17 +132,17 @@ if the model expect them. 27 00:01:13,500 --> 00:01:16,860 -这里的模型在开头使用期望 CLS 令牌 +这里的模型在开头使用期望 CLS token Here the model uses expects a CLS token at the beginning 28 00:01:16,860 --> 00:01:19,743 -以及用于分类的句子末尾的 SEP 标记。 +以及用于分类的句子末尾的 SEP token。 and a SEP token at the end of the sentence to classify. 29 00:01:20,580 --> 00:01:24,180 -最后,标记器将每个标记与其唯一 ID 匹配 +最后,分词器将每个 token 与其唯一 ID 匹配 Lastly, the tokenizer matches each token to its unique ID 30 @@ -180,7 +182,7 @@ Here the checkpoint used by default 37 00:01:45,360 --> 00:01:47,280 -用于情绪分析管道 +用于情绪分析的 pipeline for the sentiment analysis pipeline 38 @@ -250,7 +252,7 @@ Looking at the result, we see we have a dictionary 51 00:02:25,590 --> 00:02:26,670 -用两把钥匙。 +和两个主键 with two keys. 52 @@ -265,7 +267,7 @@ with zero where the padding is applied. 54 00:02:32,550 --> 00:02:34,260 -第二把钥匙,注意面具, +第二个键值,注意力掩码, The second key, attention mask, 55 @@ -280,7 +282,7 @@ so the model does not pay attention to it. 57 00:02:38,940 --> 00:02:42,090 -这就是标记化步骤中的全部内容。 +这就是分词化步骤中的全部内容。 This is all what is inside the tokenization step. 58 @@ -350,7 +352,7 @@ for our classification problem. 71 00:03:15,030 --> 00:03:19,230 -这里的张量有两个句子,每个句子有 16 个标记, +这里的张量有两个句子,每个句子有 16 个 token , Here the tensor has two sentences, each of 16 tokens, 72 @@ -425,12 +427,12 @@ This is because each model 86 00:03:57,270 --> 00:04:00,810 -每个模型都会返回 logits。 +每个模型都会返回 logits 。 of the Transformers library returns logits. 87 00:04:00,810 --> 00:04:02,250 -为了理解这些逻辑, +为了理解这些 logits , To make sense of those logits, 88 @@ -490,7 +492,7 @@ correspond to the negative label, 99 00:04:32,250 --> 00:04:34,140 -秒,索引一, +然后第二个,索引一, and the seconds, index one, 100 @@ -505,7 +507,7 @@ This is how our classifier built 102 00:04:37,950 --> 00:04:40,230 -使用管道功能选择了那些标签 +使用 pipeline 功能选择了那些标签 with the pipeline function picked those labels 103 diff --git a/subtitles/zh-CN/09_what-happens-inside-the-pipeline-function-(tensorflow).srt b/subtitles/zh-CN/09_what-happens-inside-the-pipeline-function-(tensorflow).srt index 1983a6ea6..4e6d58930 100644 --- a/subtitles/zh-CN/09_what-happens-inside-the-pipeline-function-(tensorflow).srt +++ b/subtitles/zh-CN/09_what-happens-inside-the-pipeline-function-(tensorflow).srt @@ -75,7 +75,8 @@ Then, those numbers go through the model, 16 00:00:42,600 --> 00:00:44,550 -输出逻辑。 +输出 logits 。 +*[译者注: logits 作为逻辑值的意思] which outputs logits. 17 @@ -95,22 +96,22 @@ Let's look in detail at those three steps, 20 00:00:52,590 --> 00:00:55,200 -以及如何使用 Transformers 库复制它们, +以及如何使用 Transformers 库复现它们, and how to replicate them using the Transformers library, 21 00:00:55,200 --> 00:00:57,903 -从第一阶段开始,标记化。 +从第一阶段开始,分词化。 beginning with the first stage, tokenization. 22 00:00:59,905 --> 00:01:02,520 -令牌化过程有几个步骤。 +分词化过程有几个步骤。 The tokenization process has several steps. 23 00:01:02,520 --> 00:01:06,900 -首先,文本被分成称为标记的小块。 +首先,文本被分成称为 token 的小块。 First, the text is split into small chunks called token. 24 @@ -120,7 +121,7 @@ They can be words, parts of words or punctuation symbols. 25 00:01:10,800 --> 00:01:14,310 -然后 tokenizer 将有一些特殊的标记 +然后分词器将有一些特殊的 token Then the tokenizer will had some special tokens 26 @@ -130,12 +131,12 @@ if the model expect them. 27 00:01:16,440 --> 00:01:20,430 -在这里,所使用的模型在开头需要一个 CLS 令牌 +在这里,所使用的模型在开头需要一个 CLS token Here, the model used expects a CLS token at the beginning 28 00:01:20,430 --> 00:01:23,910 -以及用于分类的句子末尾的 SEP 标记。 +以及用于分类的句子末尾的 SEP token。 and a SEP token at the end of the sentence to classify. 29 @@ -170,7 +171,8 @@ which will download and cache the configuration 35 00:01:41,940 --> 00:01:44,913 -以及与给定检查点相关联的词汇表。 +以及与给定 checkpoint 相关联的词汇表。 +*[译者注: 在深度学习中, checkpoint 作为检查点是用来备份模型的, 后不翻译] and the vocabulary associated to a given checkpoint. 36 @@ -180,13 +182,13 @@ Here, the checkpoint used by default 37 00:01:48,180 --> 00:01:50,310 -用于情绪分析管道 +用于情绪分析的 pipeline for the sentiment analysis pipeline 38 00:01:50,310 --> 00:01:54,510 -是 distilbert base uncased finetuned sst2 英语, -is distilbert base uncased finetuned sst2 English, +是 distilbert-base-uncased-finetuned-sst2-English, +is distilbert-base-uncased-finetuned-sst2-English, 39 00:01:54,510 --> 00:01:55,960 @@ -195,7 +197,7 @@ which is a bit of a mouthful. 40 00:01:56,820 --> 00:01:59,760 -我们实例化一个与该检查点关联的分词器, +我们实例化一个与该 checkpoint 关联的分词器, We instantiate a tokenizer associated with that checkpoint, 41 @@ -270,7 +272,7 @@ with zeros where the padding is applied. 55 00:02:36,750 --> 00:02:38,550 -第二把钥匙,注意面具, +第二把钥匙,注意力掩码, The second key, attention mask, 56 @@ -320,7 +322,7 @@ However, the AutoModel API will only instantiate 65 00:03:04,830 --> 00:03:06,540 -模特的身体, +模型的主体, the body of the model, 66 @@ -360,7 +362,7 @@ Here the tensor has two sentences, 73 00:03:24,210 --> 00:03:26,070 -每十六个令牌, +每十六个 token, each of sixteen token, 74 @@ -435,12 +437,12 @@ This is because each model of the Transformers library 88 00:04:06,090 --> 00:04:07,830 -返回逻辑。 +返回 logits 。 returns logits. 89 00:04:07,830 --> 00:04:09,480 -为了理解这些逻辑, +为了理解这些 logits , To make sense of those logits, 90 diff --git a/subtitles/zh-CN/10_instantiate-a-transformers-model-(pytorch).srt b/subtitles/zh-CN/10_instantiate-a-transformers-model-(pytorch).srt index f058fdebf..efa2fc2d7 100644 --- a/subtitles/zh-CN/10_instantiate-a-transformers-model-(pytorch).srt +++ b/subtitles/zh-CN/10_instantiate-a-transformers-model-(pytorch).srt @@ -10,107 +10,108 @@ 3 00:00:08,483 --> 00:00:11,790 -在本视频中,我们将了解如何创建用户模型 +在本视频中,我们会带您了解我们如何能创建自己的模型 In this video, we'll look at how we can create a user model 4 00:00:11,790 --> 00:00:13,290 -来自变形金刚图书馆。 +用 Transformers 库创。 from the Transformers library. 5 00:00:14,310 --> 00:00:17,100 -正如我们之前看到的 AutoModel 类允许 +正如我们之前看到的,AutoModel 类允许 As we have seen before the AutoModel class allows 6 00:00:17,100 --> 00:00:19,140 -你实例化一个预训练模型 +您去实例化一个预训练的模型。 you to instantiate a pretrained model 7 00:00:19,140 --> 00:00:21,513 -从 Hugging Face Hub 上的任何检查站。 +从 Hugging Face Hub 的任何 checkpoint +*[译者注: checkpoint 意思是 检查点, 作为训练模型在训练时的备份] from any checkpoint on the Hugging Face Hub. 8 00:00:22,350 --> 00:00:23,910 -它会选择正确的模型类 +它会挑选合适的模型类 It'll pick the right model class 9 00:00:23,910 --> 00:00:26,654 -从库中实例化适当的体系结构 +从开源库中, 以实例化对应的结构 from the library to instantiate the proper architecture 10 00:00:26,654 --> 00:00:29,793 -和大量的权重作为内部的预训练模型。 +和权重来作为预训练模型。 and loads of weights as the pretrained model inside. 11 00:00:30,690 --> 00:00:33,810 -正如我们所见,当给定一个 BERT 检查点时 +正如我们所见,当给定一个 BERT checkpoint 时, As we can see, when given a BERT checkpoint 12 00:00:33,810 --> 00:00:38,043 -我们最终得到一个 BertModel,类似地,对于 GPT-2 或 BART。 -we end up with a BertModel and similarly, for GPT-2 or BART. +我们最终会得到一个 BertModel ,类似地,模型 GPT-2 或 BERT 也可以这么做。 +we end up with a BertModel and similarly, for GPT-2 or BERT. 13 00:00:40,020 --> 00:00:42,360 -在幕后,这个 API 可以取名字 +背后的信息是,这个 API 可以接受 Behind the scenes,this API can take the name 14 00:00:42,360 --> 00:00:44,250 -集线器上的检查点 +Hub 上一个 checkpoint 的名字 of a checkpoint on the Hub 15 00:00:44,250 --> 00:00:46,980 -在这种情况下,它将下载并缓存配置 -in which case it will download and cache the configuration +在这种情况下,它将下载和缓存配置文件 +in which case it will download and cache the configuration file 16 00:00:46,980 --> 00:00:48,843 -文件以及模型权重文件。 -file as well as a model weights file. +以及模型权重文件。 +as well as a model weights file. 17 00:00:49,698 --> 00:00:52,710 -你还可以指定本地文件夹的路径 +您也可以指定一个本地文件夹的路径, You can also specify the path to a local folder 18 00:00:52,710 --> 00:00:55,290 -包含一个有效的配置文件和一个 -that contains a valid configuration file and a +这个文件夹包含一个有效的配置文件和 +that contains a valid configuration file and 19 00:00:55,290 --> 00:00:56,390 -权重文件的模型。 -model of weights file. +一个权重模型文件。 +a model of weights file. 20 00:00:57,600 --> 00:00:59,479 -要实例化预训练模型, +为了实例化预训练的模型, To instantiate the pretrained model, 21 00:00:59,479 --> 00:01:01,950 -AutoModel API 将首先打开配置 +AutoModel API 会先打开配置文件 the AutoModel API will first open the configuration 22 00:01:01,950 --> 00:01:05,403 -文件来查看应该使用的配置类。 +查看应该使用的配置类。 file to look at a configuration class that should be used. 23 00:01:06,420 --> 00:01:08,580 -配置类取决于类型 +配置类取决于模型的类型, The configuration class depends on the type 24 @@ -120,42 +121,42 @@ of the model BERT, GPT-2 or BART for instance. 25 00:01:13,680 --> 00:01:15,930 -一旦它有一个合适的配置类, +一旦有了一个合适的配置类, Once it has a proper configuration class, 26 00:01:15,930 --> 00:01:18,390 -它可以实例化该配置 +就可以实例化那个配置, it can instantiate that configuration 27 00:01:18,390 --> 00:01:21,900 -这是了解如何创建模型的蓝图。 +其包含一张关于如何创建模型的蓝图。 which is a blueprint to know how to create the model. 28 00:01:21,900 --> 00:01:24,240 -它还使用这个配置类来 +它还使用这个配置类 It also uses this configuration class to 29 00:01:24,240 --> 00:01:27,150 -找到合适的模型类,然后合并 +去寻找合适的模型类,其然后被 find the proper model class, which is then combined 30 00:01:27,150 --> 00:01:29,823 -使用加载的配置加载模型。 +和加载后的配置一起来加载模型。 with the loaded configuration to load the model. 31 00:01:30,904 --> 00:01:33,210 -该模型还不是预训练模型 +这个模型还不是一个预训练模型, This model is not yet a pretrained model 32 00:01:33,210 --> 00:01:35,883 -因为它刚刚用随机权重初始化。 +因为它刚刚用随机权重完成了初始化。 as it has just been initialized with random weights. 33 @@ -165,23 +166,23 @@ The last step is to load the weight from the model file 34 00:01:39,810 --> 00:01:40,923 -在这个模型里面。 +到模型里 inside this model. 35 00:01:42,330 --> 00:01:44,250 -轻松加载模型的配置 +为了方便地加载一个模型的配置 To easily load the configuration of a model 36 -00:01:44,250 --> 00:01:46,410 -从任何检查点或文件夹包含 -from any checkpoint or folder containing +00:01:44,250 --> 00:01:48,210 +从任何 checkpoint 或包含配置文件的文件夹中 +from any checkpoint or folder containing the configuration file. 37 -00:01:46,410 --> 00:01:48,210 -配置文件。 -the configuration file. +00:01:48,210 --> 00:01:48,210 +. +. 38 00:01:48,210 --> 00:01:50,373 @@ -195,52 +196,52 @@ Like the AutoModel class, 40 00:01:52,693 --> 00:01:55,693 -它将从库中选择正确的配置类。 +它将从开源库中挑选合适的配置类。 it will pick the right configuration class from the library. 41 00:01:57,060 --> 00:01:59,220 -我们也可以使用特定的类对应 +我们也可以使用一个特定的类来对应 We can also use a specific class corresponding 42 00:01:59,220 --> 00:02:01,470 -到一个检查站,但我们需要改变 +一个 checkpoint ,但每次我们需要 to a checkpoint, but we will need to change 43 00:02:01,470 --> 00:02:03,000 -每次我们想尝试的代码 +改代码, 每当我们想尝试 the code each time we want to try 44 00:02:03,000 --> 00:02:04,550 -不同的模型架构。 +不同的模型结构时. a different model architecture. 45 00:02:06,030 --> 00:02:07,860 -正如我们之前所说,配置 +正如我们刚才所说的,一个模型的配置就是 As we said before, the configuration 46 00:02:07,860 --> 00:02:10,350 -模型的蓝图包含所有 +一张蓝图,其包括了 of a model is a blueprint that contains all the 47 00:02:10,350 --> 00:02:13,830 -创建模型架构所需的信息。 +创建模型架构所需的所有信息。 information necessary to create the model architecture. 48 00:02:13,830 --> 00:02:15,990 -例如,关联的 BERT 模型 +例如,关联到 bert-base-cased checkpoint 的 For instance, the BERT model associated 49 00:02:15,990 --> 00:02:19,980 -bert-base-cased 检查点有 12 层, +BERT 模型有 12 层, with the bert-base-cased checkpoint has 12 layers, 50 @@ -255,52 +256,52 @@ Once we have the configuration, 52 00:02:29,910 --> 00:02:31,950 -我们可以创建一个具有相同架构的模型 -we can create a model that does the same architecture +我们就可以创建一个和 checkpoint 有着同样架构的模型, +we can create a model that does the same architecture as our checkpoint, 53 00:02:31,950 --> 00:02:35,280 -作为我们的检查点,但是是随机初始化的。 -as our checkpoint, but is randomly initialized. +但是模型是随机初始化的。 +but is randomly initialized. 54 00:02:35,280 --> 00:02:36,660 -然后我们可以从头开始训练它。 +然后我们可以从头开始训练它, We can then train it from scratch. 55 00:02:36,660 --> 00:02:38,010 -像任何生物 PyTorch 模块一样 +就像任何 bio PyTorch 模块一样 Like any bio PyTorch module 56 00:02:39,497 --> 00:02:40,380 -我们也可以改变任何部分 +我们也可以通过改变 We can also change any part 57 00:02:40,380 --> 00:02:43,200 -通过使用关键字参数的配置。 +配置的任何部分, 使用关键字参数 of the configuration by using keyword arguments. 58 00:02:43,200 --> 00:02:46,138 -第二段代码实例化 +第二段代码实例化了 The second snippet of code instantiates 59 00:02:46,138 --> 00:02:48,360 -随机初始化的 BERT 模型 +一个随机初始化的 BERT 模型, a randomly initialized BERT model 60 00:02:48,360 --> 00:02:50,403 -有 10 层而不是 12 层。 +这个模型有 10 层而非 12 层。 with 10 layers instead of 12. 61 00:02:51,409 --> 00:02:55,051 -训练或微调后保存模型非常容易。 +一个模型被训练或微调后,想要保存这个模型是很容易的。 Saving a model once it's trained or fine-tuned is very easy. 62 @@ -310,31 +311,31 @@ We just have to use a safe pretrained method. 63 00:02:58,500 --> 00:03:01,417 -此处模型将保存在名为 +这里模型将保存在当前工作目录下 Here the model will be saved in a folder named 64 00:03:01,417 --> 00:03:04,473 -当前工作目录中的 “my-bert-model”。 +一个名为 "my-bert-model" 的文件夹中。 "my-bert-model" inside the current working directory. 65 00:03:05,400 --> 00:03:08,255 -然后可以使用表单重新加载这样的模型 +然后,已保存的模型可以使用 Such a model can then be reloaded using the form 66 00:03:08,255 --> 00:03:09,596 -预训练方法。 +from_pretrained 函数重新加载进来。 pretrained method. 67 00:03:09,596 --> 00:03:11,250 -了解如何轻松处理此模型 +如果您要学习如何轻松地应用这个模型, To learn how to easily approach this model 68 00:03:11,250 --> 00:03:13,473 -为此,请查看对视频的推送。 +请查看课程中的相关视频。 to that, check out the push to a video. diff --git a/subtitles/zh-CN/11_instantiate-a-transformers-model-(tensorflow).srt b/subtitles/zh-CN/11_instantiate-a-transformers-model-(tensorflow).srt index c14d2347f..07dae038e 100644 --- a/subtitles/zh-CN/11_instantiate-a-transformers-model-(tensorflow).srt +++ b/subtitles/zh-CN/11_instantiate-a-transformers-model-(tensorflow).srt @@ -25,12 +25,12 @@ As we've seen before, the TFAutoModel class 6 00:00:17,850 --> 00:00:20,100 -允许你实例化预训练模型 +允许你实例化预训练模型, allows you to instantiate a pre-trained model 7 00:00:20,100 --> 00:00:22,503 -从 Hugging Face Hub 上的任何检查站。 +从 Hugging Face Hub 上的任何一个 checkpoint。 from any checkpoint on the Hugging Face Hub. 8 @@ -40,7 +40,7 @@ It will pick the right model class from the library 9 00:00:25,620 --> 00:00:27,750 -实例化适当的架构 +来实例化适当的结构 to instantiate the proper architecture 10 @@ -50,7 +50,7 @@ and load the weights of the pre-trained model inside. 11 00:00:31,200 --> 00:00:34,020 -正如我们所见,当给定一个 BERT 检查点时, +正如我们所见,当给定一个 BERT 的 checkpoint 时, As we can see, when given a BERT checkpoint, 12 @@ -65,12 +65,12 @@ and similarly for GPT2 or BART. 14 00:00:40,170 --> 00:00:42,510 -在幕后,这个 API 可以取名字 +在幕后,这个 API 接受 Behind the scenes, this API can take the name 15 00:00:42,510 --> 00:00:44,040 -集线器上的检查点, +Hub 上的一个 checkpoint 的名字, of a checkpoint on the Hub, 16 @@ -95,7 +95,7 @@ that contains a valid configuration file 20 00:00:54,090 --> 00:00:55,340 -和模型权重文件。 +和模型的权重文件。 and a model weights file. 21 @@ -105,12 +105,12 @@ To instantiate the pre-trained model, 22 00:00:58,167 --> 00:01:02,400 -TFAutoModel API 将首先打开配置文件 +TFAutoModel API 会首先打开配置文件 the TFAutoModel API will first open the configuration file 23 00:01:02,400 --> 00:01:05,253 -查看应该使用的配置类。 +以查看应该使用的配置类。 to look at the configuration class that should be used. 24 @@ -145,7 +145,7 @@ It also uses this configuration class 30 00:01:22,770 --> 00:01:24,750 -找到合适的模型类, +来找到合适的模型类, to find the proper model class, 31 @@ -175,23 +175,22 @@ The last step is to load the weights 36 00:01:36,690 --> 00:01:38,973 -来自该模型中的模型文件。 +从该模型中的模型文件中。 from the model file inside this model. 37 00:01:40,230 --> 00:01:42,270 -轻松加载模型的配置 +为了轻松加载模型的配置, To easily load the configuration of a model 38 00:01:42,270 --> 00:01:44,220 -从任何检查点或文件夹 -from any checkpoint or a folder - +从任何 checkpoint 或 +from any checkpoint or 39 00:01:44,220 --> 00:01:46,170 -包含配置文件, -containing the configuration file, +包含配置文件的文件夹, +a folder containing the configuration file, 40 00:01:46,170 --> 00:01:47,790 @@ -215,7 +214,7 @@ We can also use the specific class 44 00:01:56,040 --> 00:01:57,840 -对应一个检查点, +对应一个 checkpoint , corresponding to a checkpoint, 45 @@ -225,7 +224,7 @@ but we will need to change the code 46 00:01:59,430 --> 00:02:02,230 -每次我们都想尝试不同的模型架构。 +每当我们想尝试不同的模型结构时。 each time we want to try a different model architecture. 47 @@ -240,7 +239,7 @@ is a blueprint that contains all the information necessary 49 00:02:08,610 --> 00:02:11,070 -创建模型架构。 +以创建模型结构。 to create the model architecture. 50 @@ -250,7 +249,7 @@ For instance, the BERT model 51 00:02:12,750 --> 00:02:15,510 -与 bert-base-cased 检查点关联 +与 bert-base-cased 的 checkpoint 关联 associated with the bert-base-cased checkpoint 52 @@ -270,17 +269,17 @@ Once we have the configuration, 55 00:02:26,670 --> 00:02:28,890 -我们可以创建一个具有相同架构的模型 +我们可以创建一个具有相同结构的模型 we can create a model that has the same architecture 56 00:02:28,890 --> 00:02:32,160 -作为我们的检查点,但随机初始化。 +作为我们的 checkpoint ,但是随机初始化的。 as our checkpoint but is randomly initialized. 57 00:02:32,160 --> 00:02:36,030 -然后我们可以像任何 TensorFlow 模型一样从头开始训练它。 +我们然后就可以像任何 TensorFlow 模型一样从头开始训练它。 We can then train it from scratch like any TensorFlow model. 58 @@ -300,7 +299,7 @@ The second snippet of code instantiates 61 00:02:43,110 --> 00:02:44,970 -随机初始化的 BERT 模型 +一个随机初始化的 BERT 模型 a randomly initialized BERT model 62 diff --git a/subtitles/zh-CN/12_tokenizers-overview.srt b/subtitles/zh-CN/12_tokenizers-overview.srt index 92326f9b7..7940fd193 100644 --- a/subtitles/zh-CN/12_tokenizers-overview.srt +++ b/subtitles/zh-CN/12_tokenizers-overview.srt @@ -20,7 +20,8 @@ 5 00:00:04,920 --> 00:00:06,720 -我们将看一下分词器。 +我们将看一下分词器 +*[译者注: token, tokenization, tokenizer 等词均译成了 分词*, 实则不翻译最佳] we'll take a look at the tokenizers. 6 @@ -45,7 +46,7 @@ cannot read or understand text in its raw form, 10 00:00:18,540 --> 00:00:20,253 -他们只能使用数字。 +他们只能理解数字。 they can only work with numbers. 11 @@ -55,7 +56,7 @@ So the tokenizer's objective 12 00:00:23,220 --> 00:00:25,923 -将文本翻译成数字。 +将是把文本翻译成数字。 will be to translate the text into numbers. 13 @@ -65,22 +66,22 @@ There are several possible approaches to this conversion, 14 00:00:30,240 --> 00:00:31,110 -和目标 +并且目标 and the objective 15 00:00:31,110 --> 00:00:33,453 -就是找到最有意义的表示。 +是找到最有意义的表示。 is to find the most meaningful representation. 16 00:00:36,240 --> 00:00:39,390 -我们将看看三种不同的标记化算法。 +我们将看看三种不同的分词化算法。 We'll take a look at three distinct tokenization algorithms. 17 00:00:39,390 --> 00:00:40,530 -我们一对一比较, +我们对其一对一比较, We compare them one to one, 18 diff --git a/subtitles/zh-CN/13_word-based-tokenizers.srt b/subtitles/zh-CN/13_word-based-tokenizers.srt index 2fcf95891..7a8104067 100644 --- a/subtitles/zh-CN/13_word-based-tokenizers.srt +++ b/subtitles/zh-CN/13_word-based-tokenizers.srt @@ -16,11 +16,12 @@ 4 00:00:03,549 --> 00:00:05,603 - 让我们来看看基于单词的分词。 +*[译者注: token, tokenization, tokenizer 等词均译成了 分词*, 实则不翻译最佳] - Let's take a look at word-based tokenization. 5 00:00:07,650 --> 00:00:09,780 -基于单词的标记化是这个想法 +基于单词的分词化的想法是 Word-based tokenization is the idea 6 @@ -35,7 +36,7 @@ by splitting on spaces or other specific rules, 8 00:00:16,020 --> 00:00:17,163 -像标点符号。 +比如标点符号。 like punctuation. 9 @@ -45,22 +46,22 @@ In this algorithm, each word has a specific number 10 00:00:21,810 --> 00:00:23,463 -或归因于它的 ID。 +或者说他的 ID。 or ID attributed to it. 11 00:00:24,360 --> 00:00:27,270 -在这里,我们有 ID 250, +在这里,"let's" 的 ID 是 250, Here, let's has the ID 250, 12 00:00:27,270 --> 00:00:30,150 -确实有 861,并且标记化 + "do" 是 861,并且分词化 do has 861, and tokenization 13 00:00:30,150 --> 00:00:33,393 -后面跟感叹号的有 345。 +后面跟感叹号的是 345。 followed by an exclamation mark has 345. 14 @@ -110,7 +111,7 @@ and their meaning is close. 23 00:01:03,210 --> 00:01:05,550 -然而,基于单词的标记化, +然而,基于单词的分词化, The word-based tokenization, however, 24 @@ -135,7 +136,7 @@ This is unfortunate as we would like the model 28 00:01:15,090 --> 00:01:18,240 -了解这些词确实相关, +理解这些词是确实相关的, to understand that these words are indeed related, 29 @@ -195,12 +196,12 @@ that represents the word's meaning, 40 00:01:50,190 --> 00:01:52,170 -并跟踪这些映射 +并实现保持这些映射 and keeping track of these mappings 41 00:01:52,170 --> 00:01:54,990 -需要大量的权重 +需要很大的模型 requires an enormous number of weights 42 @@ -220,7 +221,7 @@ we can opt for our tokenizer to ignore certain words 45 00:02:04,440 --> 00:02:06,093 -我们不一定需要。 +我们不一定需要的。 that we don't necessarily need. 46 @@ -260,7 +261,7 @@ into numbers, but any other word will be converted 53 00:02:29,370 --> 00:02:31,530 -到词汇外的词, +作为词汇外的词, to the out-of-vocabulary word, 54 @@ -280,7 +281,7 @@ The model will have the exact same representation 57 00:02:39,900 --> 00:02:42,390 -对于它不知道的所有单词, +对于所有它不知道的单词, for all words that it doesn't know, 58 diff --git a/subtitles/zh-CN/14_character-based-tokenizers.srt b/subtitles/zh-CN/14_character-based-tokenizers.srt index 0b07937f9..ea74b563f 100644 --- a/subtitles/zh-CN/14_character-based-tokenizers.srt +++ b/subtitles/zh-CN/14_character-based-tokenizers.srt @@ -5,22 +5,23 @@ 2 00:00:04,260 --> 00:00:07,200 -- 在深入研究基于字符的标记化之前, +- 在深入研究基于字符的分词化之前, +*[译者注: token, tokenization, tokenizer 等词均译成了 分词*, 实则不翻译最佳] - Before diving in character-based tokenization, 3 00:00:07,200 --> 00:00:10,350 -理解为什么这种标记化很有趣 +理解为什么这种分词化很有趣 understanding why this kind of tokenization is interesting 4 00:00:10,350 --> 00:00:13,533 -需要了解基于单词的标记化的缺陷。 +需要了解基于单词的分词化的缺陷。 requires understanding the flaws of word-based tokenization. 5 00:00:14,640 --> 00:00:16,320 -如果你还没有看过第一个视频 +如果你还没有看过第一个视频, If you haven't seen the first video 6 @@ -30,12 +31,12 @@ on word-based tokenization 7 00:00:17,880 --> 00:00:21,450 -我们建议你在观看此视频之前检查一下。 +我们建议你在观看此视频之前看一下。 we recommend you check it out before looking at this video. 8 00:00:21,450 --> 00:00:24,250 -好的,让我们看一下基于字符的标记化。 +好的,让我们看一下基于字符的分词化。 Okay, let's take a look at character-based tokenization. 9 @@ -60,7 +61,7 @@ while the number of characters stays low. 13 00:00:38,610 --> 00:00:41,313 -首先让我们看一下英语, +首先, 让我们看一下英语, To begin let's take a look at the English language, 14 @@ -75,7 +76,7 @@ so we would need a very large vocabulary 16 00:00:47,730 --> 00:00:49,413 -包含所有单词。 +来包含所有单词。 to encompass all words. 17 @@ -95,7 +96,7 @@ which includes letters, numbers and special characters. 20 00:00:59,760 --> 00:01:02,190 -即使是有很多不同字符的语言 +即使是有大量不同字符的语言 Even languages with a lot of different characters 21 @@ -105,12 +106,12 @@ like the Chinese languages can have dictionaries 22 00:01:04,800 --> 00:01:08,130 -多达 20,000 个不同的字符 +多达 20,000 个不同的汉字 with up to 20,000 different characters 23 00:01:08,130 --> 00:01:11,523 -但超过 375,000 个不同的单词。 +超过 375,000 个不同的词语。 but more than 375,000 different words. 24 @@ -120,7 +121,7 @@ So character-based vocabularies 25 00:01:14,310 --> 00:01:16,293 -让我们使用更少的不同标记 +让我们使用更少的不同分词 let us use fewer different tokens 26 @@ -135,12 +136,12 @@ we would otherwise use. 28 00:01:23,250 --> 00:01:25,830 -这些词汇也比较全 +这些词汇也更全面 These vocabularies are also more complete 29 00:01:25,830 --> 00:01:28,950 -比他们基于单词的词汇对应物。 +相较于其基于单词的词汇。 than their word-based vocabularies counterparts. 30 @@ -150,7 +151,7 @@ As our vocabulary contains all characters 31 00:01:31,410 --> 00:01:33,960 -用在一种语言中,甚至是看不见的词 +在一种语言中的,甚至是看不见的词 used in a language, even words unseen 32 @@ -160,12 +161,12 @@ during the tokenizer training can still be tokenized, 33 00:01:36,990 --> 00:01:39,633 -因此词汇表外的标记将不那么频繁。 +因此溢出的分词将不那么频繁。 so out-of-vocabulary tokens will be less frequent. 34 00:01:40,680 --> 00:01:42,840 -这包括正确标记化的能力 +这包括正确分词化 This includes the ability to correctly tokenize 35 @@ -175,7 +176,7 @@ misspelled words, rather than discarding them 36 00:01:45,210 --> 00:01:46,623 -立即未知。 +作为未知的。 as unknown straight away. 37 @@ -220,22 +221,22 @@ have a lot of information held in single characters, 45 00:02:12,750 --> 00:02:15,360 -但对于其他像基于罗马的语言, +但对于其他像基于字母的语言, but for others like roman-based languages, 46 00:02:15,360 --> 00:02:17,760 -该模型必须理解多个标记 -the model will have to make sense of multiple tokens +该模型必须一次性理解多个分词 +the model will have to make sense of multiple tokens at a time 47 00:02:17,760 --> 00:02:20,670 -一次获取以其他方式持有的信息 -at a time to get the information otherwise held +以获取信息 +to get the information otherwise held 48 00:02:20,670 --> 00:02:21,753 -一句话。 +在一句话中。 in a single word. 49 @@ -250,7 +251,7 @@ their sequences are translated into very large amount 51 00:02:29,520 --> 00:02:31,593 -模型要处理的标记数。 +模型要处理的分词。 of tokens to be processed by the model. 52 @@ -260,12 +261,12 @@ And this can have an impact on the size of the context 53 00:02:36,810 --> 00:02:40,020 -该模型将随身携带,并会减小尺寸 +该模型将装载,并会减小 the model will carry around, and will reduce the size 54 00:02:40,020 --> 00:02:42,030 -我们可以用作模型输入的文本, +可以用作模型输入文本的尺寸, of the text we can use as input for our model, 55 @@ -280,22 +281,21 @@ This tokenization, while it has some issues, 57 00:02:46,650 --> 00:02:48,720 -在过去看到了一些非常好的结果 +但在过去看到了一些非常好的结果 has seen some very good results in the past 58 00:02:48,720 --> 00:02:50,490 -所以在接近时应该考虑 -and so it should be considered when approaching - +所以应该被考虑 +and so it should be considered 59 00:02:50,490 --> 00:02:52,680 -解决问题的新问题 -a new problem as it solves issues +当碰到新问题时, 来解决问题. +when approaching a new problem as it solves issues 60 00:02:52,680 --> 00:02:54,843 -在基于词的算法中遇到。 +在基于词的算法中遇到的。 encountered in the word-based algorithm. 61 diff --git a/subtitles/zh-CN/15_subword-based-tokenizers.srt b/subtitles/zh-CN/15_subword-based-tokenizers.srt index 2fa836c93..7b53f8219 100644 --- a/subtitles/zh-CN/15_subword-based-tokenizers.srt +++ b/subtitles/zh-CN/15_subword-based-tokenizers.srt @@ -1,21 +1,23 @@ 1 00:00:06,450 --> 00:00:09,540 - 让我们来看看基于子词的分词。 +*[译者注: token, tokenization, tokenizer 等词均译成了 分词*, 实则不翻译最佳] - Let's take a look at subword based tokenization. 2 00:00:09,540 --> 00:00:11,610 了解为什么基于子词的分词是 -Understanding why subword based tokenization is +Understanding why subword based tokenization 3 00:00:11,610 --> 00:00:13,980 -有趣需要理解缺陷 -interesting requires understanding the flaws +是有趣的需要理解 +is interesting requires understanding the flaws + 4 00:00:13,980 --> 00:00:17,340 -基于单词和基于校正器的标记化。 +基于单词和基于校正器分词化的缺陷。 of word based and corrector based tokenization. 5 @@ -25,63 +27,63 @@ If you haven't seen the first videos 6 00:00:18,780 --> 00:00:22,020 -基于单词和基于字符的标记化 +关于基于单词和基于字符的标记化 on word based and character based tokenization 7 00:00:22,020 --> 00:00:23,130 -我们建议你检查它们 -we recommend you check them +我们建议你观看它们 +we recommend you check them out 8 00:00:23,130 --> 00:00:24,780 在看这个视频之前。 -out before looking at this video. +before looking at this video. 9 00:00:27,840 --> 00:00:31,493 -基于子词的标记化介于基于字符之间 +基于子词的分词化介于基于字符 Subword based tokenization lies in between character based 10 00:00:31,493 --> 00:00:35,280 -和基于词的分词算法。 +和基于单词的分词算法之间。 and word based tokenization algorithms. 11 00:00:35,280 --> 00:00:37,410 -这个想法是找到一个中间立场 +这个想法是找到一个中间场 The idea is to find a middle ground 12 00:00:37,410 --> 00:00:39,486 -在非常大的词汇表之间 -between very large vocabularies +在很大的词汇表, +between very large vocabularies, 13 00:00:39,486 --> 00:00:42,600 -大量的词汇标记 +大量的词汇分词 a large quantity of out vocabulary tokens 14 00:00:42,600 --> 00:00:45,360 -并且在非常相似的词中失去意义 +还有在非常相似的词之间意义差 and a loss of meaning across very similar words 15 00:00:45,360 --> 00:00:48,630 -用于基于词的分词器和非常长的序列 +对基于单词的分词器和非常长的序列 for word based tokenizers and very long sequences 16 00:00:48,630 --> 00:00:51,330 -以及意义不大的单个标记。 -as well as less meaningful individual tokens. +以及意义不大的单个标记 +as well as less meaningful individual tokens 17 00:00:51,330 --> 00:00:53,133 -对于基于字符的分词器。 -For character based tokenizers. +对于基于字符的分词器之间。 +for character based tokenizers. 18 00:00:54,840 --> 00:00:57,960 @@ -95,7 +97,7 @@ Frequently used words should not be split 20 00:01:00,000 --> 00:01:01,500 -分成更小的子词 +成更小的子词 into smaller subwords 21 @@ -110,7 +112,7 @@ into meaningful subwords. 23 00:01:06,510 --> 00:01:08,460 -一个例子是狗这个词。 +一个例子是 dog 这个词。 An example is the word dog. 24 @@ -120,42 +122,42 @@ We would like to have our tokenizer to have a single ID 25 00:01:11,190 --> 00:01:12,600 -对于狗这个词 +对于 dog 这个词 for the word dog rather 26 00:01:12,600 --> 00:01:15,363 -而不是将其拆分为校正器 DO 和 G。 -than splitting it into correctors D O and G. +而不是将其拆分为字母 d o 和 g。 +than splitting it into characters d o and g. 27 00:01:16,650 --> 00:01:19,260 -然而,当遇到狗这个词时 +然而,当遇到 dog 这个词时 However, when encountering the word dogs 28 00:01:19,260 --> 00:01:22,710 -我们希望我们的标记化从根本上理解这一点 +我们希望我们的分词从词根上理解这一点 we would like our tokenize to understand that at the root 29 00:01:22,710 --> 00:01:24,120 -这还是狗这个词。 +这还是 dog 这个词。 this is still the word dog. 30 00:01:24,120 --> 00:01:27,030 -添加 S 后,意思略有改变 -With an added S, that slightly changes the meaning +添加 s 后,意思略有改变 +With an added "s", that slightly changes the meaning 31 00:01:27,030 --> 00:01:28,923 -同时保持最初的想法。 +同时保持最初的意思。 while keeping the original idea. 32 00:01:30,600 --> 00:01:34,080 -另一个例子是像标记化这样的复杂词 +另一个例子是像 tokenization 这样的复杂词 Another example is a complex word like tokenization 33 @@ -165,17 +167,17 @@ which can be split into meaningful subwords. 34 00:01:37,140 --> 00:01:37,973 -根 -The root +这个词的根 +The root of the word 35 00:01:37,973 --> 00:01:40,590 -这个词的是记号,-ization 完成根 -of the word is token and -ization completes the root +是 token ,以及 -ization 完整了这个词 +is token and -ization completes the root 36 00:01:40,590 --> 00:01:42,870 -赋予它稍微不同的含义。 +以赋予它稍微不同的含义。 to give it a slightly different meaning. 37 @@ -195,42 +197,42 @@ labeled as the start of the word 40 00:01:49,950 --> 00:01:52,530 -和化作为标记的附加信息 -and ization as additional information labeled +和 -ization 作为标记的附加信息 +and -ization as additional information labeled 41 00:01:52,530 --> 00:01:54,393 -作为单词的完成。 +作为单词的完整化。 as a completion of the word. 42 00:01:55,826 --> 00:01:58,740 -反过来,该模型现在将能够有意义 +如此一来,该模型现在将能够有作用 In turn, the model will now be able to make sense 43 00:01:58,740 --> 00:02:01,080 -不同情况下的令牌。 +在不同情况下的分词。 of token in different situations. 44 00:02:01,080 --> 00:02:04,602 -它会理解这个词的 token, tokens, tokenizing +它会理解这个词的形式: token, tokens, tokenizing It will understand that the word's token, tokens, tokenizing 45 00:02:04,602 --> 00:02:08,760 -和标记化具有相似的含义并且是相关联的。 +和 tokenization 具有相似的含义并且是相关联的。 and tokenization have a similar meaning and are linked. 46 00:02:08,760 --> 00:02:12,450 -它还将理解标记化、现代化 +它还将理解 tokenization 、modernization It's will also understand that tokenization, modernization 47 00:02:12,450 --> 00:02:16,200 -和免疫,它们都有相同的后缀 +和 immunization ,都有相同的后缀 and immunization, which all have the same suffixes 48 @@ -240,62 +242,62 @@ are probably used in the same syntactic situations. 49 00:02:20,610 --> 00:02:23,130 -基于子词的分词器通常有办法 +基于子词的分词器通常有办法来 Subword based tokenizers generally have a way to 50 00:02:23,130 --> 00:02:25,890 -识别哪些标记是单词的开头 +识别哪些分词是单词的开头 identify which tokens are a start of word 51 00:02:25,890 --> 00:02:28,443 -以及哪些标记完成单词的开头。 +以及哪些分词完成了单词的开始。 and which tokens complete start of words. 52 00:02:29,520 --> 00:02:31,140 -所以这里以令牌为开始 -So here token as the start +所以这里以分词作为单词的开始 +So here token as the start of a word -53 +53 00:02:31,140 --> 00:02:35,100 -病房和哈希哈希化作为奖励的完成。 -of a ward and hash hash ization as completion of award. +以及 ##ization 的开始作为单词的完成。 +and ##ization as completion of a word. 54 00:02:35,100 --> 00:02:38,103 -这里 hash 哈希前缀表示化是一部分 -Here, the hash hash prefix indicates that ization is part +这里 ## 表示 -ization 是单词的一部分 +Here, the ## prefix indicates that ization is part of a word 55 00:02:38,103 --> 00:02:41,013 -奖项而不是它的开始。 -of award rather than the beginning of it. +而不是它的开始。 +rather than the beginning of it. 56 00:02:41,910 --> 00:02:43,110 -hash 散列来了 -The hash hash comes + ## 记号 +The ## comes 57 00:02:43,110 --> 00:02:47,013 -来自基于词片算法的 BERT 分词器。 +来自基于单词片算法的 BERT 分词器。 from the BERT tokenizer based on the word piece algorithm. 58 00:02:47,850 --> 00:02:50,700 -其他标记化使用其他前缀可以是 +其他分词器使用其他前缀, 可以是 Other tokenizes use other prefixes which can be 59 00:02:50,700 --> 00:02:52,200 -放置以表示单词的一部分 +用来表示单词的一部分 placed to indicate part of words 60 00:02:52,200 --> 00:02:55,083 -喜欢在这里或单词的开头。 +比如在这里或单词的开头。 like in here or start of words instead. 61 @@ -310,12 +312,11 @@ of different algorithms that can be used 63 00:02:58,740 --> 00:03:00,090 -用于子词标记化 +用于子词分词化 for subword tokenization - 64 00:03:00,090 --> 00:03:02,670 -大多数模型都获得了最先进的结果 +大多数模型都获得了目前最先进的结果 and most models obtaining state-of-the-art results 65 @@ -325,7 +326,7 @@ in English today 66 00:03:03,780 --> 00:03:06,663 -使用某种子词标记化算法。 +使用一些子词标记化算法。 use some kind of subword tokenization algorithms. 67 @@ -335,17 +336,17 @@ These approaches help in reducing the vocabulary sizes 68 00:03:10,953 --> 00:03:13,636 -通过跨不同的词共享信息 +通过不同词之间共享的信息 by sharing information across different words 69 00:03:13,636 --> 00:03:15,960 -有前缀的能力 +有能力以理解前缀 having the ability to have prefixes 70 00:03:15,960 --> 00:03:18,630 -和后缀这样理解。 +和后缀如斯。 and suffixes understood as such. 71 @@ -355,6 +356,6 @@ They keep meaning across very similar words 72 00:03:20,700 --> 00:03:23,103 -通过识别相似的标记,将它们组合起来。 +通过识别相似的分词,将它们组合起来。 by recognizing similar tokens, making them up. diff --git a/subtitles/zh-CN/16_the-tokenization-pipeline.srt b/subtitles/zh-CN/16_the-tokenization-pipeline.srt index a359fd213..4db8dcce1 100644 --- a/subtitles/zh-CN/16_the-tokenization-pipeline.srt +++ b/subtitles/zh-CN/16_the-tokenization-pipeline.srt @@ -5,22 +5,23 @@ 2 00:00:05,610 --> 00:00:06,873 -- 分词器管道。 +- 分词器的 pipeline 。 +*[译者注: token, tokenization, tokenizer 等词均译成了 分词*, 实则不翻译最佳] - The tokenizer pipeline. 3 00:00:07,920 --> 00:00:10,570 -在本视频中,我们将了解分词器如何将 +在本视频中,我们将了解分词器如何转换 In this video, we'll look at how a tokenizer converts 4 00:00:11,433 --> 00:00:12,480 -原始文本到数字, +从原始文本到数字, raw texts to numbers, 5 00:00:12,480 --> 00:00:14,970 -Transformer 模型可以理解, +Transformer 模型可以理解成, that a Transformer model can make sense of, 6 @@ -35,12 +36,12 @@ Here is a quick overview 8 00:00:18,690 --> 00:00:21,630 -tokenizer 对象内部发生的事情: +对于 tokenizer 对象内部发生的事情: of what happens inside the tokenizer object: 9 00:00:21,630 --> 00:00:24,360 -首先,文本被分成标记, +首先,文本被分成分词, first, the text is split into tokens, 10 @@ -50,12 +51,12 @@ which are words, parts of words, or punctuation symbols. 11 00:00:28,440 --> 00:00:31,500 -然后标记器添加潜在的特殊标记 +然后分词器添加潜在的特殊分词 Then the tokenizer adds potential special tokens 12 00:00:31,500 --> 00:00:34,680 -并将每个令牌转换为各自唯一的 ID +并将每个分词转换为各自唯一的 ID and converts each token to their unique respective ID 13 @@ -65,17 +66,17 @@ as defined by the tokenizer's vocabulary. 14 00:00:37,710 --> 00:00:40,380 -正如我们将要看到的,它并不是按照这个顺序发生的, +正如我们将要看到的,它并不完全是按照这个顺序发生的, As we'll see, it doesn't quite happen in this order, 15 00:00:40,380 --> 00:00:43,233 -但这样做更利于理解。 +但这样更利于理解。 but doing it like this is better for understandings. 16 00:00:44,280 --> 00:00:47,670 -第一步是将我们的输入文本拆分为标记。 +第一步是将我们的输入文本拆分为分词。 The first step is to split our input text into tokens. 17 @@ -90,7 +91,7 @@ To do that, the tokenizer may first perform some operations, 19 00:00:54,030 --> 00:00:56,880 -喜欢将所有单词小写,然后遵循一组规则 +比如将所有单词小写,然后遵循一组规则 like lowercasing all words, then follow a set of rules 20 @@ -105,7 +106,7 @@ Most of the Transformer models uses 22 00:01:02,286 --> 00:01:04,890 -单词标记化算法,这意味着 +单词分词化算法,这意味着 a word tokenization algorithm, which means 23 @@ -115,12 +116,12 @@ that one given word can be split 24 00:01:06,750 --> 00:01:10,050 -在几个标记中,例如 tokenize here。 +在几个分词中,例如这里的分词。 in several tokens like tokenize here. 25 00:01:10,050 --> 00:01:12,570 -查看下面的“标记化算法”视频链接 +查看下面的 “分词化算法” 视频链接 Look at the "Tokenization algorithms" video link below 26 @@ -130,17 +131,17 @@ for more information. 27 00:01:14,760 --> 00:01:17,820 -我们在ize前面看到的##前缀是 -The # # prefix we see in front of ize is +我们在 ize 前面看到的 ## 前缀是 +The ## prefix we see in front of ize is 28 00:01:17,820 --> 00:01:19,830 -Bert 用来表示的约定 -a convention used by Bert to indicate +BERT 的约定, 用来表示 +a convention used by BERT to indicate 29 00:01:19,830 --> 00:01:22,762 -这个标记不是单词的开头。 +这个分词不是单词的开头。 this token is not the beginning of the word. 30 @@ -155,7 +156,7 @@ for instance, ALBERT tokenizers will add a long underscore 32 00:01:29,984 --> 00:01:31,620 -在所有令牌前 +在所有分词前 in front of all the tokens 33 @@ -170,17 +171,17 @@ by all sentencepiece tokenizers. 35 00:01:38,580 --> 00:01:41,040 -标记化管道的第二步是 +分词化管道的第二步是 The second step of the tokenization pipeline is 36 00:01:41,040 --> 00:01:43,470 -将这些令牌映射到它们各自的 ID +将这些分词映射到它们各自的 ID to map those tokens to their respective IDs 37 00:01:43,470 --> 00:01:45,770 -由分词器的词汇定义。 +由分词器的词汇定义的。 as defined by the vocabulary of the tokenizer. 38 @@ -205,7 +206,7 @@ We have to make sure we use the same mapping 42 00:01:54,390 --> 00:01:56,520 -就像模型被预训练时一样。 +和模型被预训练时一致。 as when the model was pretrained. 43 @@ -225,7 +226,7 @@ that we don't have the exact same results 46 00:02:03,540 --> 00:02:05,580 -就像我们的第一张幻灯片一样 +就像我们的第一张幻灯片一样, 或许 as in our first slide, or not 47 @@ -235,18 +236,18 @@ as this looks like a list of random numbers anyway, 48 00:02:07,920 --> 00:02:10,680 -在这种情况下,请允许我重温一下你的记忆。 +在这种情况下,请允许我重温一下你的回忆。 in which case, allow me to refresh your memory. 49 -00:02:10,680 --> 00:02:12,350 -我们有一个开头的数字和一个数字 -We had a the number at the beginning and a number +00:02:10,680 --> 00:02:15,350 +我们有缺少开头的数字和一个结尾的数字 +We had a the number at the beginning and a number at the end, that are missing, 50 -00:02:12,350 --> 00:02:17,130 -最后缺少的是特殊标记。 -at the end that are missing, those are the special tokens. +00:02:15,350 --> 00:02:17,130 +是特殊标记。 +those are the special tokens. 51 00:02:17,130 --> 00:02:20,340 @@ -255,12 +256,12 @@ The special tokens are added by the prepare_for_model method 52 00:02:20,340 --> 00:02:22,350 -它知道这个令牌的索引 +它知道这个分词的索引 which knows the indices of this token 53 00:02:22,350 --> 00:02:25,680 -在词汇表中并添加适当的数字。 +在词汇表中, 并仅添加适当的数字。 in the vocabulary and just adds the proper numbers. 54 @@ -285,7 +286,7 @@ at how the tokenizer has changed your text, 58 00:02:33,870 --> 00:02:35,280 -通过使用解码方法 +通过使用 decode 方法 by using the decode method 59 @@ -300,7 +301,7 @@ As for the prefix for beginning 61 00:02:39,423 --> 00:02:44,160 -词的/词的一部分,特殊标记因情况而异 +词的/词的部分,对于特殊标记, 依赖于 of words/ part of words, for special tokens vary depending 62 @@ -315,8 +316,8 @@ So that tokenizer uses CLS and SEP, 64 00:02:48,810 --> 00:02:52,417 -但是 roberta tokenizer 使用类似 HTML 的锚点 -but the roberta tokenizer uses HTML-like anchors +但是 RoBERTa 分词器使用类似 HTML 的锚点 +but the RoBERTa tokenizer uses HTML-like anchors 65 00:02:52,417 --> 00:02:55,230 @@ -345,12 +346,12 @@ on your input texts. 70 00:03:03,870 --> 00:03:05,310 -分词器的输出不 +分词器的输出不只是 The output of a tokenizer don't 71 00:03:05,310 --> 00:03:07,853 -但是,只包含输入 ID。 +仅包含输入 ID, 然而. just contain the input IDs, however. 72 @@ -360,17 +361,17 @@ To learn what the attention mask is, 73 00:03:09,750 --> 00:03:12,360 -查看“一起批量输入”视频。 +查看 “一起批量输入” 视频。 check out the "Batch input together" video. 74 00:03:12,360 --> 00:03:14,220 -要了解令牌类型 ID, +要了解分词类型 ID, To learn about token type IDs, 75 00:03:14,220 --> 00:03:16,570 -观看“处理句子对”视频。 +观看 “处理句子对” 视频。 look at the "Process pairs of sentences" video. 76 diff --git a/subtitles/zh-CN/17_batching-inputs-together-(pytorch).srt b/subtitles/zh-CN/17_batching-inputs-together-(pytorch).srt index b4fdd4d2b..885fc5296 100644 --- a/subtitles/zh-CN/17_batching-inputs-together-(pytorch).srt +++ b/subtitles/zh-CN/17_batching-inputs-together-(pytorch).srt @@ -20,7 +20,7 @@ to batch input sequences together. 5 00:00:12,137 --> 00:00:15,420 -一般来说,我们想要通过我们的模型的句子 +一般来说,我们想要输入模型的句子 In general, the sentences we want to pass through our model 6 @@ -30,12 +30,12 @@ won't all have the same lengths. 7 00:00:17,670 --> 00:00:19,740 -在这里,我们使用我们看到的模型 +在这里,我们使用模型 Here, we are using the model we saw 8 00:00:19,740 --> 00:00:22,080 -在情绪分析管道中 +在情绪分析 pipeline 中讲的 in the sentiment analysis pipeline 9 @@ -45,7 +45,8 @@ and want to classify two sentences. 10 00:00:24,900 --> 00:00:27,360 -将它们标记化并映射每个标记时 +将它们分词化并映射每个分词时 +*[译者注: token, tokenization, tokenizer 等词均译成了 分词*, 实则不翻译最佳] When tokenizing them and mapping each token 11 @@ -60,12 +61,12 @@ we get two lists of different lengths. 13 00:00:33,240 --> 00:00:35,340 -尝试创建张量或 NumPy 数组 +尝试创建 tensor 或 NumPy 数组 Trying to create a tensor or a NumPy array 14 00:00:35,340 --> 00:00:38,220 -从这两个列表中将导致错误, +从这两个列表中, 将导致错误, from those two lists will result in an error, 15 @@ -75,12 +76,12 @@ because all arrays and tensors should be rectangular. 16 00:00:42,240 --> 00:00:44,160 -克服此限制的一种方法 +突破此限制的一种方法 One way to overcome this limit 17 00:00:44,160 --> 00:00:45,690 -是造第二句 +是让第二句 is to make the second sentence 18 @@ -90,7 +91,7 @@ the same length as the first 19 00:00:47,640 --> 00:00:50,463 -通过根据需要多次添加特殊令牌。 +通过根据需要多次添加特殊分词。 by adding a special token as many times as necessary. 20 @@ -105,12 +106,12 @@ to the length of the second, 22 00:00:55,710 --> 00:00:58,140 -但我们会让他们失去很多信息 -but we would them lose a lot of information +但我们会失去很多信息 +but we would then lose a lot of information 23 00:00:58,140 --> 00:01:01,083 -这可能是正确分类句子所必需的。 +而这可能是正确分类句子所必需的。 that might be necessary to properly classify the sentence. 24 @@ -135,7 +136,7 @@ The value used to pad the second sentence 28 00:01:11,850 --> 00:01:13,740 -不应随意挑选; +不应被随意挑选; should not be picked randomly; 29 @@ -155,7 +156,7 @@ Now that we have padded our sentences, 32 00:01:22,800 --> 00:01:24,303 -我们可以和他们一起做一批。 +我们可以和他们做成一批。 we can make a batch with them. 33 @@ -165,7 +166,7 @@ If we pass the two sentences to the model separately 34 00:01:28,320 --> 00:01:30,120 -然而,并批在一起, +和并批在一起,然而 and batched together however, 35 @@ -185,9 +186,8 @@ here, the second one. 38 00:01:36,390 --> 00:01:39,420 -它在 Transformers 库的后面?不。 -这是 Transformers -It's at the back in the Transformers Library? No. +是 Transformers 有问题?不。 +It's at the backend in the Transformers Library? No. 39 00:01:39,420 --> 00:01:40,770 @@ -206,7 +206,7 @@ this should not come as a total surprise; 42 00:01:45,210 --> 00:01:48,277 -在计算每个标记的上下文表示时, +在计算每个分词的上下文表示时, when computing the contextual representation of each token, 43 @@ -226,7 +226,7 @@ If we have just the sentence 46 00:01:53,850 --> 00:01:56,970 -或者添加了几个填充标记的句子, +或者添加了几个填充 token 的句子, or the sentence with several padding tokens added, 47 @@ -246,7 +246,7 @@ we need to indicate to the attention layers 50 00:02:05,340 --> 00:02:08,070 -他们应该忽略那些填充标记。 +他们应该忽略那些填充 token 。 that they should ignore those padding tokens. 51 @@ -261,27 +261,27 @@ a tensor with the same shape as the input IDs, 53 00:02:13,320 --> 00:02:14,733 -用零和一个。 +用 0 和 1 。 with zeros and ones. 54 00:02:15,780 --> 00:02:18,120 -那些表示注意层的标记 +1 的分词表示注意层 Ones indicate the tokens the attention layers 55 00:02:18,120 --> 00:02:20,100 -应结合上下文考虑 +应该结合上下文考虑 should consider in the context 56 00:02:20,100 --> 00:02:22,100 -并将他们应该忽略的标记归零。 +并且 0 的分词他们应该忽略。 and zeros the tokens they should ignore. 57 00:02:23,520 --> 00:02:26,760 -现在,将这个注意掩码与输入 ID 一起传递 +现在,将这个注意掩码与输入 ID 一起传入 Now, passing this attention mask along with the input ID 58 @@ -291,7 +291,7 @@ will give us the same results 59 00:02:28,170 --> 00:02:31,170 -就像我们将两个句子分别发送给模型一样。 +就像我们将两个句子单独发送给模型一样。 as when we sent the two sentences individually to the model. 60 @@ -306,7 +306,7 @@ when you apply it to several sentences 62 00:02:36,900 --> 00:02:38,613 -标志 padding=True。 +设参数 padding=True。 with the flag padding=True. 63 @@ -316,7 +316,7 @@ It will apply the padding with the proper value 64 00:02:41,490 --> 00:02:43,140 -到较小的句子 +对较小的句子 to the smaller sentences 65 diff --git a/subtitles/zh-CN/18_batching-inputs-together-(tensorflow).srt b/subtitles/zh-CN/18_batching-inputs-together-(tensorflow).srt index a672d8f66..fbbb2b247 100644 --- a/subtitles/zh-CN/18_batching-inputs-together-(tensorflow).srt +++ b/subtitles/zh-CN/18_batching-inputs-together-(tensorflow).srt @@ -5,17 +5,17 @@ 2 00:00:05,310 --> 00:00:07,590 -- 如何一起批量输入。 +- 如何批量一起输入。 - How to batch inputs together. 3 00:00:07,590 --> 00:00:09,150 -在这个视频中,我们将看到 +在本视频中,我们将看到 In this video, we'll see 4 00:00:09,150 --> 00:00:11,050 -如何将输入序列一起批处理。 +如何将输入序列一起批量处理。 how to batch input sequences together. 5 @@ -25,17 +25,17 @@ In general, the sentences we want to pass 6 00:00:14,910 --> 00:00:18,000 -通过我们的模型不会都具有相同的长度。 +进入我们的模型不会都具有相同的长度。 through our model won't all have the same lengths. 7 00:00:18,000 --> 00:00:20,310 -在这里,我们使用我们看到的模型 +在这里,我们使用模型 Here, we are using the model we saw 8 00:00:20,310 --> 00:00:22,650 -在情绪分析管道中 +在情绪分析 pipeline 中的 in the sentiment analysis pipeline 9 @@ -45,7 +45,8 @@ and want to classify two sentences. 10 00:00:25,860 --> 00:00:27,870 -将它们标记化并映射每个标记时 +将它们分词化并映射每个标记时 +*[译者注: token, tokenization, tokenizer 等词均译成了 分词*, 实则不翻译最佳] When tokenizing them and mapping each token 11 @@ -60,7 +61,7 @@ we get two lists of different lengths. 13 00:00:33,360 --> 00:00:35,070 -尝试创建张量和 NumPy 数组 +尝试创建 tensor 和 NumPy 数组 Trying to create a tensor and NumPy array 14 @@ -75,7 +76,7 @@ because all arrays and tensors should be rectangular. 16 00:00:42,510 --> 00:00:43,920 -克服此限制的一种方法 +克服此困难的一种方法 One way to overcome this limit 17 @@ -85,7 +86,7 @@ is to make the second sentence the same length as the first 18 00:00:47,340 --> 00:00:50,373 -通过根据需要多次添加特殊令牌。 +通过根据需要多次添加特殊分词。 by adding a special token as many times as necessary. 19 @@ -95,7 +96,7 @@ Another way would be to truncate the first sequence 20 00:00:53,340 --> 00:00:56,550 -到秒的长度,但我们会失去很多 +到第二个的长度,但我们会失去很多 to the length of the second, but we would then lose a lot 21 @@ -105,17 +106,17 @@ of information that may be necessary 22 00:00:58,590 --> 00:01:01,230 -正确地对句子进行分类。 +来正确地对句子进行分类。 to properly classify the sentence. 23 00:01:01,230 --> 00:01:04,710 -一般来说,我们只会截断更长的句子 +一般来说,我们只会截断句子 In general, we only truncate sentences when they are longer 24 00:01:04,710 --> 00:01:07,083 -超过模型可以处理的最大长度。 +超过模型可以处理的最大长度的。 than the maximum length the model can handle. 25 @@ -125,7 +126,7 @@ The value used to pad the second sentence 26 00:01:10,320 --> 00:01:12,390 -不应随意挑选。 +不应被随意挑选。 should not be picked randomly. 27 @@ -155,7 +156,7 @@ If we pass the two sentences to the model separately 32 00:01:26,730 --> 00:01:29,130 -或批在一起,但是,我们注意到 +或并批在一起,但是,我们注意到 or batched together, however, we notice 33 @@ -175,7 +176,7 @@ Here, the second one. 36 00:01:34,440 --> 00:01:36,690 -期待 transformer 库中的单词? +希望是 transformer 库中的单词? Expect the word in the transformer library? 37 @@ -190,12 +191,12 @@ If you remember that Transformer models make heavy use 39 00:01:39,720 --> 00:01:43,800 -注意力层,它不应该完全令人惊讶。 +注意力层,它应该不足为奇。 of attention layers, it should not come as a total surprise. 40 00:01:43,800 --> 00:01:47,100 -在计算每个标记的上下文表示时, +在计算每个分词的上下文表示时, When computing the contextual representation of each token, 41 @@ -215,7 +216,7 @@ If we have just a sentence 44 00:01:52,252 --> 00:01:55,650 -或者添加了几个填充标记的句子, +或者添加了几个填充 token 的句子, or the sentence with several padding tokens added, 45 @@ -235,7 +236,7 @@ we need to indicate to the attention layers 48 00:02:03,750 --> 00:02:06,660 -他们应该忽略那些填充标记。 +他们应该忽略那些填充 token 。 that they should ignore those padding tokens. 49 @@ -245,17 +246,17 @@ This is done by creating an attention mask, 50 00:02:08,970 --> 00:02:11,700 -与输入 ID 具有相同形状的张量 +与输入 ID 具有相同形状的 tensor a tensor with the same shape as the input IDs 51 00:02:11,700 --> 00:02:13,173 -用零和一个。 +用 0 和 1 。 with zeros and ones. 52 00:02:14,640 --> 00:02:16,830 -那些表示注意层的标记 +1 的 token 表示注意层 Ones indicate the tokens the attention layers 53 @@ -265,12 +266,12 @@ should consider in the context, 54 00:02:18,660 --> 00:02:20,823 -和零,他们应该忽略的标记。 +并且 0 的 token 他们应该忽略。 and zeros, the tokens they should ignore. 55 00:02:21,810 --> 00:02:23,290 -现在,通过这个注意力面具 +现在,通过这个注意力掩码 Now, passing this attention mask 56 @@ -295,8 +296,8 @@ when you apply it to several sentences 60 00:02:35,583 --> 00:02:37,713 -标志填充等于 true。 -with the flag padding equals true. +设置参数 padding=True。 +with the flag padding=True. 61 00:02:38,640 --> 00:02:39,690 @@ -305,7 +306,7 @@ It will apply the padding 62 00:02:39,690 --> 00:02:42,180 -对较小的句子具有适当的价值 +对较小的句子具有适当值 with the proper value to the smaller sentences 63 diff --git a/subtitles/zh-CN/20_hugging-face-datasets-overview-(tensorflow).srt b/subtitles/zh-CN/20_hugging-face-datasets-overview-(tensorflow).srt index 8d655495b..dcc4aa9db 100644 --- a/subtitles/zh-CN/20_hugging-face-datasets-overview-(tensorflow).srt +++ b/subtitles/zh-CN/20_hugging-face-datasets-overview-(tensorflow).srt @@ -5,7 +5,7 @@ 2 00:00:05,371 --> 00:00:09,690 -- 本节将带来 Hugging Face Datasets 库的快速概览。 +- Hugging Face Datasets 库: 快速概览。 - The Hugging Face Datasets library: A Quick overview. 3 @@ -15,12 +15,12 @@ The Hugging Face Datasets library 4 00:00:10,917 --> 00:00:12,870 -是一个提供 API 的库 +是一个库, 提供 API is a library that provides an API 5 00:00:12,870 --> 00:00:15,150 -快速下载许多公共数据集 +来快速下载许多公共数据集 to quickly download many public datasets 6 @@ -45,12 +45,12 @@ you can directly download and cache a dataset 10 00:00:26,010 --> 00:00:28,023 -来自其在数据集中心的标识符。 +来自其在数据集 Hub 的 ID 。 from its identifier on the Dataset hub. 11 00:00:29,160 --> 00:00:33,690 -这里我们从 GLUE 基准中获取 MRPC 数据集, +这里我们从 GLUE benchmark 中获取 MRPC 数据集, Here we fetch the MRPC dataset from the GLUE benchmark, 12 @@ -75,7 +75,7 @@ is a DatasetDict, which is a sort of dictionary 16 00:00:45,090 --> 00:00:46,940 -包含我们数据集的每个分割。 +包含我们数据集的每个拆分。 containing each split of our dataset. 17 @@ -90,17 +90,17 @@ This split is then an instance of the Dataset class, 19 00:00:54,540 --> 00:00:57,423 -有列,这里是 sentence1,sentence2, +有列: sentence1,sentence2, with columns, here sentence1, sentence2, 20 00:00:58,350 --> 00:01:00,813 -标签和 idx,以及行。 +label 和 idx,以及行。 label and idx, and rows. 21 00:01:02,160 --> 00:01:05,220 -我们可以通过索引访问给定的元素。 +我们可以访问给定的元素, 通过索引。 We can access a given element by its index. 22 @@ -120,7 +120,7 @@ which means that even if your dataset is huge 25 00:01:14,460 --> 00:01:16,219 -你不会离开 RAM, +你不会内存溢出, you won't get out of RAM, 26 @@ -130,7 +130,7 @@ only the elements you request are loaded in memory. 27 00:01:19,920 --> 00:01:24,510 -访问数据集的一部分就像访问一个元素一样简单。 +访问数据集的一个切片就像访问一个元素一样简单。 Accessing a slice of your dataset is as easy as one element. 28 @@ -160,7 +160,7 @@ The features attribute of a Dataset 33 00:01:37,080 --> 00:01:39,840 -为我们提供有关其专栏的更多信息。 +为我们提供有关其列的更多信息。 gives us more information about its columns. 34 @@ -180,7 +180,7 @@ and names for the labels. 37 00:01:46,110 --> 00:01:49,623 -0 代表不等价,1 代表等价。 +0 代表不相等,1 代表相等。 0 stands for not equivalent and 1 for equivalent. 38 @@ -190,12 +190,12 @@ To pre-process all the elements of our dataset, 39 00:01:54,090 --> 00:01:55,980 -我们需要将它们标记化。 +我们需要将它们 token 化。 we need to tokenize them. 40 00:01:55,980 --> 00:01:58,470 -看看视频 “预处理句子对” +看看视频 “Pre-process sentence pairs” Have a look at the video "Pre-process sentence pairs" 41 @@ -205,7 +205,7 @@ for a refresher, but you just have to send the two sentences 42 00:02:01,800 --> 00:02:04,833 -带有一些额外的关键字参数的分词器。 +给 tokenizer, 带有一些额外的关键字参数。 to the tokenizer with some additional keyword arguments. 43 @@ -215,7 +215,7 @@ Here we indicate a maximum length of 128 44 00:02:09,300 --> 00:02:11,460 -和垫输入短于这个长度, +和垫全输入短于这个长度的, and pad inputs shorter than this length, 45 @@ -255,12 +255,12 @@ or update existing ones. 52 00:02:30,060 --> 00:02:32,520 -加快预处理并利用 +为加快预处理并利用 To speed up pre-processing and take advantage 53 00:02:32,520 --> 00:02:35,130 -事实上,我们的分词器由 Rust 支持 +我们的分词器由 Rust 支持的事实 of the fact our tokenizer is backed by Rust 54 @@ -280,12 +280,12 @@ in our tokenize function, using the batched=True argument. 57 00:02:45,300 --> 00:02:46,980 -由于分词器可以处理列表 +由于 tokenizer 可以处理列表 Since the tokenizer can handle a list 58 00:02:46,980 --> 00:02:50,280 -第一句话或第二句话的 tokenize_function +第一句话或第二句话, tokenize_function of first or second sentences, the tokenize_function 59 @@ -295,7 +295,7 @@ does not need to change for this. 60 00:02:52,740 --> 00:02:55,410 -你还可以将多处理与 map 方法一起使用, +你还可以将多进程与 map 方法一起使用, You can also use multiprocessing with the map method, 61 @@ -305,7 +305,7 @@ check out its documentation linked below. 62 00:02:58,740 --> 00:03:02,130 -完成后,我们几乎可以开始训练了, +完成后,我们几乎准备好训练了, Once this is done, we are almost ready for training, 63 @@ -320,12 +320,12 @@ with the remove_columns method, 65 00:03:06,120 --> 00:03:08,580 -将标签重命名为标签,因为模型 +将 label 重命名为 labels ,因为模型是 rename label to labels, since the models 66 00:03:08,580 --> 00:03:11,430 -从变形金刚图书馆期望, + transformers 库中的,期望如此 from the transformers library expect that, 67 @@ -335,7 +335,7 @@ and set the output format to our desired backend, 68 00:03:14,040 --> 00:03:15,893 -手电筒、tensorflow 或 numpy。 +torch、tensorflow 或 numpy。 torch, tensorflow or numpy. 69 diff --git a/subtitles/zh-CN/21_preprocessing-sentence-pairs-(pytorch).srt b/subtitles/zh-CN/21_preprocessing-sentence-pairs-(pytorch).srt index 8541dbc5e..2e8e2d741 100644 --- a/subtitles/zh-CN/21_preprocessing-sentence-pairs-(pytorch).srt +++ b/subtitles/zh-CN/21_preprocessing-sentence-pairs-(pytorch).srt @@ -10,27 +10,27 @@ 3 00:00:09,150 --> 00:00:11,340 -我们已经看到了如何标记单个句子 +我们已经看到了如何分词化单个句子 We have seen how to tokenize single sentences 4 00:00:11,340 --> 00:00:12,877 并将它们放在一起, -and batch them together in the, +and batch them together in the 5 00:00:12,877 --> 00:00:15,810 -“批量输入视频。” -"Batching inputs together video." +在 “Batching inputs together” 视频中 +"Batching inputs together" video. 6 00:00:15,810 --> 00:00:18,330 -如果你觉得这段代码不熟悉, +如果这段代码对你而言不熟悉, If this code look unfamiliar to you, 7 00:00:18,330 --> 00:00:20,030 -请务必再次检查该视频。 +请务必再次查看该视频。 be sure to check that video again. 8 @@ -40,23 +40,23 @@ Here will focus on tasks that classify pair of sentences. 9 00:00:25,620 --> 00:00:28,470 -例如,我们可能想要对两个文本进行分类 +例如,我们可能想要对两个文本是否被释义 For instance, we may want to classify whether two texts 10 00:00:28,470 --> 00:00:30,360 -是否被释义。 +进行分类。 are paraphrased or not. 11 00:00:30,360 --> 00:00:32,880 -这是从 Quora 问题对中获取的示例 -Here is an example taken from the Quora Question Pairs +这是从 Quora Question Pairs 数据集中获取的示例 +Here is an example taken from the Quora Question Pairs dataset 12 00:00:32,880 --> 00:00:37,530 -数据集,它专注于识别重复的问题。 -dataset, which focuses on identifying duplicate questions. +它专注于识别重复的问题。 +which focuses on identifying duplicate questions. 13 00:00:37,530 --> 00:00:40,650 @@ -65,7 +65,7 @@ In the first pair, the two questions are duplicates, 14 00:00:40,650 --> 00:00:42,000 -在第二个他们不是。 +在第二个它们不是。 in the second they are not. 15 @@ -75,7 +75,7 @@ Another pair classification problem is 16 00:00:45,540 --> 00:00:47,400 -当我们想知道两个句子是否 +当我们想知道两个句子是 when we want to know if two sentences are 17 @@ -90,7 +90,7 @@ a problem called natural language inference or NLI. 19 00:00:53,970 --> 00:00:57,000 -在这个例子中,取自 MultiNLI 数据集, +在这个取自 MultiNLI 数据集的例子中, In this example, taken from the MultiNLI data set, 20 @@ -100,7 +100,7 @@ we have a pair of sentences for each possible label. 21 00:00:59,880 --> 00:01:02,490 -矛盾,自然的或必然的, +矛盾,自然的或蕴涵, Contradiction, natural or entailment, 22 @@ -115,32 +115,32 @@ implies the second. 24 00:01:06,930 --> 00:01:08,820 -所以分类成对的句子是一个问题 +所以分类成对的句子是一个 So classifying pairs of sentences is a problem 25 00:01:08,820 --> 00:01:10,260 -值得研究。 +值得研究的问题。 worth studying. 26 00:01:10,260 --> 00:01:12,630 -事实上,在 GLUE 基准测试中, +事实上,在 GLUE 基准中, In fact, in the GLUE benchmark, 27 00:01:12,630 --> 00:01:15,750 -这是文本分类的学术基准 +这是文本分类问题的学术界基准 which is an academic benchmark for text classification 28 00:01:15,750 --> 00:01:17,910 -10 个数据集中有 8 个是重点 -eight of the 10 data sets are focused +10 个数据集中有 8 个是关注 +8 of the 10 datasets are focused 29 00:01:17,910 --> 00:01:19,953 -在使用句子对的任务上。 +使用句子对的任务。 on tasks using pairs of sentences. 30 @@ -165,7 +165,7 @@ they often have an objective related to sentence pairs. 34 00:01:31,230 --> 00:01:34,320 -例如,在预训练期间显示 BERT +例如,在预训练期间 BERT 见到 For instance, during pretraining BERT is shown 35 @@ -175,12 +175,12 @@ pairs of sentences and must predict both 36 00:01:36,810 --> 00:01:39,930 -随机屏蔽令牌的价值,以及第二个是否 +随机掩蔽的标记值,以及第二个是否 the value of randomly masked tokens, and whether the second 37 00:01:39,930 --> 00:01:41,830 -句子是否从头开始。 +句子是否接着第一个句子。 sentence follow from the first or not. 38 @@ -200,32 +200,32 @@ You just have to pass them as two arguments 41 00:01:51,270 --> 00:01:52,120 -到分词器。 +到 tokenizer 。 to the tokenizer. 42 00:01:53,430 --> 00:01:55,470 -在输入 ID 和注意掩码之上 +在我们已经研究过的输入 ID On top of the input IDs and the attention mask 43 00:01:55,470 --> 00:01:56,970 -我们已经研究过, +和注意掩码之上, we studied already, 44 00:01:56,970 --> 00:01:59,910 -它返回一个名为令牌类型 ID 的新字段, +它返回一个名为标记类型 ID 的新字段, it returns a new field called token type IDs, 45 00:01:59,910 --> 00:02:01,790 -它告诉模型哪些令牌属于 +它告诉模型哪些标记属于 which tells the model which tokens belong 46 00:02:01,790 --> 00:02:03,630 -对于第一句话, +第一句话, to the first sentence, 47 @@ -240,32 +240,32 @@ Zooming in a little bit, here has an input IDs 49 00:02:09,840 --> 00:02:12,180 -与它们对应的标记对齐, +与它们对应的 token 对齐, aligned with the tokens they correspond to, 50 00:02:12,180 --> 00:02:15,213 -它们各自的令牌类型 ID 和注意掩码。 +它们各自的标记类型 ID 和注意掩码。 their respective token type ID and attention mask. 51 00:02:16,080 --> 00:02:19,260 -我们可以看到标记器还添加了特殊标记。 +我们可以看到分词器还添加了特殊标记。 We can see the tokenizer also added special tokens. 52 00:02:19,260 --> 00:02:22,620 -所以我们有一个 CLS 标记,来自第一句话的标记, +所以我们有一个 CLS token ,来自第一句话的 token , So we have a CLS token, the tokens from the first sentence, 53 00:02:22,620 --> 00:02:25,770 -一个 SEP 令牌,第二句话中的令牌, +一个 SEP 标记,第二句话中的标记, a SEP token, the tokens from the second sentence, 54 00:02:25,770 --> 00:02:27,003 -和最终的 SEP 令牌。 +和最终的 SEP 标记。 and a final SEP token. 55 @@ -275,12 +275,12 @@ If we have several pairs of sentences, 56 00:02:30,570 --> 00:02:32,840 -我们可以通过传递列表将它们标记在一起 +我们可以通过第一句话的传递列表 we can tokenize them together by passing the list 57 00:02:32,840 --> 00:02:36,630 -第一句话,然后是第二句话的列表 +将它们标记在一起,然后是第二句话的列表 of first sentences, then the list of second sentences 58 @@ -290,7 +290,7 @@ and all the keyword arguments we studied already 59 00:02:39,300 --> 00:02:40,353 -像填充 = 真。 +例如 padding=True。 like padding=True. 60 @@ -300,17 +300,17 @@ Zooming in at the result, 61 00:02:43,140 --> 00:02:45,030 -我们还可以看到标记化添加的填充 -we can see also tokenize added padding +我们可以看到分词器如何添加填充 +we can see how the tokenizer added padding 62 00:02:45,030 --> 00:02:48,090 -到第二对句子来制作两个输出 +到第二对句子使得两个输出的 to the second pair sentences to make the two outputs 63 00:02:48,090 --> 00:02:51,360 -相同的长度,并正确处理令牌类型 ID +长度相同,并正确处理标记类型 ID the same length, and properly dealt with token type IDs 64 diff --git a/subtitles/zh-CN/22_preprocessing-sentence-pairs-(tensorflow).srt b/subtitles/zh-CN/22_preprocessing-sentence-pairs-(tensorflow).srt index 9bccf528e..cbc4612cc 100644 --- a/subtitles/zh-CN/22_preprocessing-sentence-pairs-(tensorflow).srt +++ b/subtitles/zh-CN/22_preprocessing-sentence-pairs-(tensorflow).srt @@ -20,7 +20,7 @@ and batch them together 5 00:00:13,020 --> 00:00:15,660 -在 “一起批量输入” 视频中。 +在 “Batching inputs together” 视频中。 in the "Batching inputs together" video. 6 @@ -75,7 +75,7 @@ In the first pair, the two questions are duplicates; 16 00:00:40,650 --> 00:00:43,620 -第二,他们不是。 +在第二对中,他们不是。 in the second, they are not. 17 @@ -90,7 +90,7 @@ is when we want to know if two sentences 19 00:00:46,980 --> 00:00:49,290 -逻辑上是否相关, +逻辑上相关,或反之 are logically related or not, 20 @@ -185,7 +185,7 @@ BERT is shown pairs of sentences 38 00:01:36,690 --> 00:01:39,900 -并且必须预测随机屏蔽标记的值 +并且必须预测随机屏蔽 token 的值 and must predict both the value of randomly masked tokens 39 @@ -195,12 +195,12 @@ and whether the second sentence 40 00:01:41,250 --> 00:01:42,903 -是否从第一个开始。 +从第一个开始。 follows from the first or not. 41 00:01:44,070 --> 00:01:47,100 -幸运的是,来自 Transformers 库的分词器 +幸运的是,来自 Transformers 库的 tokenizer Fortunately, the tokenizer from the Transformers library 42 @@ -215,7 +215,7 @@ you just have to pass them as two arguments 44 00:01:52,650 --> 00:01:53,613 -到分词器。 +到 tokenizer 。 to the tokenizer. 45 @@ -225,17 +225,17 @@ On top of the input IDs 46 00:01:56,040 --> 00:01:58,440 -和我们已经研究过的注意力面具, +和我们已经研究过的注意力掩码, and the attention mask we studied already, 47 00:01:58,440 --> 00:02:01,530 -它返回一个名为令牌类型 ID 的新字段, +它返回一个名为 token 类型 ID 的新字段, it returns a new field called token type IDs, 48 00:02:01,530 --> 00:02:03,210 -它告诉模型哪些标记 +它告诉模型哪些 token which tells the model which tokens 49 @@ -255,32 +255,32 @@ Zooming in a little bit, here are the input IDs, 52 00:02:11,430 --> 00:02:13,710 -与它们对应的标记对齐, +与它们对应的 token 对齐, aligned with the tokens they correspond to, 53 00:02:13,710 --> 00:02:17,193 -它们各自的令牌类型 ID 和注意掩码。 +它们各自的 token 类型 ID 和注意掩码。 their respective token type ID and attention mask. 54 00:02:18,540 --> 00:02:21,300 -我们可以看到标记器还添加了特殊标记 +我们可以看到 tokenizer 还添加了特殊 token We can see the tokenizer also added special tokens 55 00:02:21,300 --> 00:02:25,230 -所以我们有一个 CLS 标记,来自第一句话的标记, +所以我们有一个 CLS token ,来自第一句话的 token , so we have a CLS token, the tokens from the first sentence, 56 00:02:25,230 --> 00:02:28,590 -一个 SEP 令牌,第二句话中的令牌, +一个 SEP token ,第二句话中的 token , a SEP token, the tokens from the second sentence, 57 00:02:28,590 --> 00:02:30,153 -和最终的 SEP 令牌。 +和最终的 SEP token 。 and a final SEP token. 58 @@ -310,7 +310,7 @@ and all the keyword arguments we studied already, 63 00:02:43,050 --> 00:02:44,133 -像填充 = 真。 +像 padding=True 。 like padding=True. 64 @@ -320,7 +320,7 @@ Zooming in at the result, 65 00:02:46,770 --> 00:02:49,050 -我们可以看到分词器是如何添加填充的 +我们可以看到 tokenizer 是如何添加填充的 we can see how the tokenizer added padding 66 @@ -335,7 +335,7 @@ to make the two outputs the same length. 68 00:02:53,490 --> 00:02:55,620 -它还可以正确处理令牌类型 IDS +它还可以正确处理 token 类型 IDS It also properly dealt with token type IDS 69 diff --git a/subtitles/zh-CN/23_what-is-dynamic-padding.srt b/subtitles/zh-CN/23_what-is-dynamic-padding.srt index 30efe21df..4dba99a5f 100644 --- a/subtitles/zh-CN/23_what-is-dynamic-padding.srt +++ b/subtitles/zh-CN/23_what-is-dynamic-padding.srt @@ -15,12 +15,12 @@ In the "Batching Inputs together" video, 4 00:00:10,890 --> 00:00:12,720 -我们已经看到能够对输入进行分组 +我们已经看到为了能够对(不同长度的同批次)输入进行分组 we have seen that to be able to group inputs 5 00:00:12,720 --> 00:00:15,300 -同一批不同长度的, +(同一批不同长度的), of different lengths in the same batch, 6 @@ -40,12 +40,12 @@ Here, for instance, the longest sentence is the third one, 9 00:00:24,600 --> 00:00:27,270 -我们需要添加五个、两个或七个填充令牌 +我们需要添加五个、两个或七个填充标记 and we need to add five, two, or seven pad tokens 10 00:00:27,270 --> 00:00:30,090 -到其他句子有四个句子 +到其他句子使得四个句子具有 to the other sentences to have four sentences 11 @@ -65,12 +65,12 @@ there are various padding strategies we can apply. 14 00:00:37,560 --> 00:00:39,540 -最明显的一种是填充所有元素 +最明显的一种是填充整个数据集所有的样本 The most obvious one is to pad all the elements 15 00:00:39,540 --> 00:00:40,923 -数据集的相同长度: +达到相同的长度: of the dataset to the same length: 16 @@ -80,67 +80,67 @@ the length of the longest sample. 17 00:00:44,070 --> 00:00:45,330 -这会给我们批次 +我们得到具有相同形状的批次 This will then give us batches 18 00:00:45,330 --> 00:00:46,890 -都具有相同的形状 + that all have the same shape 19 00:00:46,890 --> 00:00:49,800 -由最大序列长度决定。 +(其长度)由最大序列长度决定。 determined by the maximum sequence length. 20 00:00:49,800 --> 00:00:52,893 -缺点是批次由短句组成 +缺点是(如果)批次样本由短句组成 The downside is that batches composed from short sentences 21 00:00:52,893 --> 00:00:54,960 -会有很多填充令牌 +将带来很多填充符号 will have a lot of padding tokens 22 00:00:54,960 --> 00:00:57,660 -这将在模型中引入更多计算 +并且在模型中引入更多不必要的计算。 which will introduce more computations in the model 23 00:00:57,660 --> 00:00:58,910 -我们最终不需要。 + we ultimately don't need. 24 00:01:00,060 --> 00:01:03,300 -为了避免这种情况,另一种策略是填充元素 +为了避免这种情况,另一种策略是填充(短样本)符号 To avoid this, another strategy is to pad the elements 25 00:01:03,300 --> 00:01:05,280 -当我们把它们批在一起时, +当把它们放在一批时, when we batch them together, 26 00:01:05,280 --> 00:01:08,190 -到批次中最长的句子。 +达到本批次中最长句子的长度。 to the longest sentence inside the batch. 27 00:01:08,190 --> 00:01:12,000 -这样,由短输入组成的批次会更小 +这样,由短样本输入组成的批次大小 This way, batches composed of short inputs will be smaller 28 00:01:12,000 --> 00:01:13,920 -比包含最长句子的批次 +会比按整个数据集最长句子的长度(补齐)批次更小 than the batch containing the longest sentence 29 00:01:13,920 --> 00:01:15,510 -在数据集中。 + in the dataset. 30 @@ -155,7 +155,7 @@ The downside is that all batches 32 00:01:20,490 --> 00:01:22,140 -然后会有不同的形状, +会有不同的形状, will then have different shapes, 33 @@ -170,7 +170,7 @@ Let's see how to apply both strategies in practice. 35 00:01:29,370 --> 00:01:31,280 -我们实际上已经看到了如何应用固定填充 +我们实际上已经知道了如何使用固定填充 We have actually seen how to apply fixed padding 36 @@ -190,22 +190,22 @@ after loading the dataset and tokenizer, 39 00:01:38,250 --> 00:01:40,680 -我们将标记化应用于所有数据集 +我们将符号化应用于所有数据集 we applied the tokenization to all the dataset 40 00:01:40,680 --> 00:01:42,480 -带填充和截断 +包括填充和截断 with padding and truncation 41 00:01:42,480 --> 00:01:45,273 -制作所有长度为 128 的样本。 +保证所有样本的长度为 128 。 to make all samples of length 128. 42 00:01:46,530 --> 00:01:48,360 -结果,如果我们传递这个数据集 +最后,如果我们传递这个数据集 As a result, if we pass this dataset 43 @@ -215,7 +215,8 @@ to a PyTorch DataLoader, 44 00:01:50,520 --> 00:01:55,503 -我们得到形状批量大小的批次,这里是 16,乘以 128。 +我们得到形状为 batch_size 乘以 16 乘以 128 的批次。 + we get batches of shape batch size, here 16, by 128. 45 @@ -230,7 +231,7 @@ we must defer the padding to the batch preparation, 47 00:02:01,440 --> 00:02:04,740 -所以我们从标记化函数中删除了那部分。 +所以我们从标记函数中删除了那部分。 so we remove that part from our tokenize function. 48 @@ -295,7 +296,7 @@ We pass it to the PyTorch DataLoader as a collate function, 60 00:02:35,310 --> 00:02:37,620 -然后观察生成的批次 +然后观察到生成的批次 then observe that the batches generated 61 @@ -310,12 +311,13 @@ all way below the 128 from before. 63 00:02:42,660 --> 00:02:44,820 -动态批处理几乎总是更快 +动态批处理几乎在 CPU 和 GPU 上更快, + Dynamic batching will almost always be faster 64 00:02:44,820 --> 00:02:47,913 -在 CPU 和 GPU 上,所以如果可以的话你应该应用它。 +所以如果可以的话你应该应用它。 on CPUs and GPUs, so you should apply it if you can. 65 @@ -330,7 +332,7 @@ if you run your training script on TPU 67 00:02:53,490 --> 00:02:55,293 -或者需要成批的固定形状。 +或者需要固定形状的批次输入。 or need batches of fixed shapes. 68 diff --git a/subtitles/zh-CN/24_the-trainer-api.srt b/subtitles/zh-CN/24_the-trainer-api.srt index 2ffaf8c72..de4af9a41 100644 --- a/subtitles/zh-CN/24_the-trainer-api.srt +++ b/subtitles/zh-CN/24_the-trainer-api.srt @@ -15,28 +15,28 @@ 4 00:00:05,698 --> 00:00:06,548 -- 所以培训师 API。 -- So Trainer API. +- Trainer API。 +- The Trainer API. 5 00:00:08,070 --> 00:00:10,040 -所以 Transformers Library 提供了一个 Trainer API -So Transformers Library provides a Trainer API +Transformers Library 提供了一个 Trainer API +The Transformers Library provides a Trainer API 6 00:00:10,040 --> 00:00:13,320 -让你轻松找到调谐变压器模型 -that allows you to easily find tune transformers models +让你能够轻松的微调 Transformer 模型 +that allows you to easily fine-tune transformer models 7 00:00:13,320 --> 00:00:14,193 -在你的数据集上。 -on your dataset. +在你自己的数据集上。 +on your own dataset. 8 00:00:15,150 --> 00:00:17,250 -所以 Trainer 类接受你的数据集, -So Trainer class takes your datasets, +Trainer 类接受你的数据集, +The Trainer class take your datasets, 9 00:00:17,250 --> 00:00:19,900 @@ -50,28 +50,28 @@ and can perform the training on any kind of setup, 11 00:00:23,310 --> 00:00:26,654 -CPU、GPU、多个 GPU、TPU -CPU, GPU, multiple GPUs, TPUs +(CPU、GPU、多个 GPU、TPUs) +(CPU, GPU, multiple GPUs, TPUs) 12 00:00:26,654 --> 00:00:28,680 -也可以计算预测 +也可以在任意数据集上进行推理 can also compute the predictions 13 00:00:28,680 --> 00:00:31,710 -在任何数据集上,如果你提供了指标 +如果你提供了指标 on any dataset and if you provided metrics 14 00:00:31,710 --> 00:00:33,813 -在任何数据集上评估你的模型。 +就可以在任意数据集上评估你的模型。 evaluate your model on any dataset. 15 00:00:34,950 --> 00:00:36,930 -你还可以涉及最终数据处理 -You can also involve final data processing +Trainer 也可以负责最后的数据处理 +It can also handle final data processing 16 00:00:36,930 --> 00:00:38,670 @@ -80,13 +80,13 @@ such as dynamic padding, 17 00:00:38,670 --> 00:00:40,377 -只要你提供分词器 +只要你提供 tokenizer as long as you provide the tokenizer 18 00:00:40,377 --> 00:00:42,693 -或给定数据整理器。 -or given data collator. +或给定 data collator。 +or a given data collator. 19 00:00:43,572 --> 00:00:45,900 @@ -95,53 +95,53 @@ We will try this API on the MRPC dataset, 20 00:00:45,900 --> 00:00:48,492 -因为它相对较小且易于预处理。 +因为该数据集相对较小且易于预处理。 since it's relatively small and easy to preprocess. 21 00:00:48,492 --> 00:00:49,325 -正如我们在数据集概述视频中看到的那样, +正如我们在 Datasets 概述视频中看到的那样, As we saw in the Datasets overview video, 22 00:00:49,325 --> 00:00:54,325 -但是我们可以对其进行预处理。 -however we can preprocess it. +我们可以像这样对其进行预处理。 +here is how we can preprocess it. 23 00:00:54,511 --> 00:00:57,030 -我们在预处理期间不应用填充, +我们在预处理过程中不进行填充, We do not apply padding during the preprocessing, 24 00:00:57,030 --> 00:00:58,590 -因为我们将使用动态填充 +因为我们将进行动态填充 as we will use dynamic padding 25 00:00:58,590 --> 00:01:00,083 -在 DataCollatorWithPadding 之前。 -before DataCollatorWithPadding. +使用我们的 DataCollatorWithPadding。 +with our DataCollatorWithPadding. 26 00:01:01,170 --> 00:01:02,790 -请注意,我们不执行最后的步骤 +请注意,我们不执行一些最终的数据处理步骤 Note that we don't do the final steps 27 00:01:02,790 --> 00:01:04,830 -重命名删除列 -of renaming removing columns +像是重命名列/删除列 +of renaming/removing columns 28 00:01:04,830 --> 00:01:06,873 -或将格式设置为火炬张量。 +或将格式设置为 torch 张量。 or set the format to torch tensors. 29 00:01:07,710 --> 00:01:10,560 -所以 Trainer 会自动为我们做这一切 -So Trainer will do all of this automatically for us +Trainer 会自动为我们做这一切 +The Trainer will do all of this automatically for us 30 00:01:10,560 --> 00:01:12,633 @@ -150,7 +150,7 @@ by analyzing the model signature. 31 00:01:14,054 --> 00:01:16,650 -创建培训师之前的最后一步是 +实例化 Trainer 前的最后一步是 The last step before creating the Trainer are 32 @@ -165,62 +165,62 @@ and some training hyperparameters. 34 00:01:20,250 --> 00:01:22,653 -我们在模型 API 视频中看到了第一个。 +我们在 model API 视频中学会了如何定义模型。 We saw to do the first in the model API video. 35 00:01:23,734 --> 00:01:26,790 -第二次我们使用 TrainingArguments 类。 +对于第二点,我们使用 TrainingArguments 类。 For the second we use the TrainingArguments class. 36 00:01:26,790 --> 00:01:28,710 -它只需要一个文件夹的路径 +Trainer 只需要一个文件夹的路径 It only takes a path to a folder 37 00:01:28,710 --> 00:01:30,900 -结果和检查点将保存在哪里, +用以保存结果和检查点, where results and checkpoint will be saved, 38 00:01:30,900 --> 00:01:33,060 -但你也可以自定义所有超参数 +但你也可以自定义你的 Trainer 会使用的 but you can also customize all the hyperparameters 39 00:01:33,060 --> 00:01:34,470 -你的教练会使用, +所有超参数 your Trainer will use, 40 00:01:34,470 --> 00:01:37,270 -学习权重、训练影响数等。等等。 -learning weight, number of training impacts, et. cetera. +比如学习率,训练几个 epoch 等等。 +learning rate, number of training epochs etc. 41 00:01:38,190 --> 00:01:39,660 -创建培训师非常容易 -It's been very easy to create a Trainer +接下来实例化一个 Trainer 并开始训练 +It's then very easy to create a Trainer 42 00:01:39,660 --> 00:01:41,400 -并开展培训。 +就非常简单了。 and launch a training. 43 00:01:41,400 --> 00:01:43,170 -你应该显示一个进度条 -You should display a progress bar +这会显示一个进度条 +This should display a progress bar 44 00:01:43,170 --> 00:01:45,900 -如果你在 GPU 上运行,几分钟后 -and after a few minutes if you're running on a GPU +几分钟后(如果你在 GPU 上运行) +and after a few minutes (if you're running on a GPU) 45 00:01:45,900 --> 00:01:48,000 -你应该完成培训。 +你会完成训练。 you should have the training finished. 46 @@ -235,12 +235,12 @@ as you will only get a training loss 48 00:01:52,710 --> 00:01:54,300 -这并没有真正告诉你任何事情 +这并没有真正告诉你 which doesn't really tell you anything 49 00:01:54,300 --> 00:01:56,820 -关于你的模型表现如何。 +你的模型表现如何。 about how well your model is performing. 50 @@ -260,12 +260,12 @@ To get those metrics, 53 00:02:02,160 --> 00:02:03,810 -我们将首先收集预测 +我们将首先使用预测方法 we will first gather the predictions 54 00:02:03,810 --> 00:02:06,513 -使用预测方法在整个评估集上。 +在整个评估集上收集预测结果。 on the whole evaluation set using the predict method. 55 @@ -275,23 +275,23 @@ It returns a namedtuple with three fields, 56 00:02:10,020 --> 00:02:12,990 -预测,其中包含预测模型。 -Prediction, which contains the model of predictions. +Prediction (其中包含模型的预测), +Prediction(which contains the model predictions), 57 00:02:12,990 --> 00:02:15,030 -Label_IDs,其中包含标签 -Label_IDs, which contains the labels +Label_IDs (其中包含标签 +Label_IDs(which contains the labels 58 00:02:15,030 --> 00:02:16,800 -如果你的数据集有它们 -if your dataset had them +如果你的数据集有的话) +if your dataset had them) 59 00:02:16,800 --> 00:02:18,570 -和此处为空的指标。 -and metrics which is empty here. +和指标(在本示例中是空的) +and metrics (which is empty here). 60 00:02:18,570 --> 00:02:20,520 @@ -300,17 +300,17 @@ We're trying to do that. 61 00:02:20,520 --> 00:02:22,470 -预测是模型的对数 +预测结果是模型 The predictions are the logits of the models 62 00:02:22,470 --> 00:02:24,143 -对于数据集中的所有句子。 +对于数据集中的所有句子所输出的 logits。 for all the sentences in the dataset. 63 00:02:24,143 --> 00:02:27,513 -所以一个形状为 408 x 2 的 NumPy 数组。 +所以是一个形状为 408 x 2 的 NumPy 数组。 So a NumPy array of shape 408 by 2. 64 @@ -320,7 +320,7 @@ To match them with our labels, 65 00:02:30,270 --> 00:02:31,590 -我们需要采取最大的 logit +我们需要取最大的 logit we need to take the maximum logit 66 @@ -330,8 +330,8 @@ for each prediction 67 00:02:32,850 --> 00:02:35,820 -知道预测了这两个类别中的哪一个。 -to know which of the two classes was predicted. +(知道两个类别中的哪一个类是所预测的结果) +(to know which of the two classes was predicted.) 68 00:02:35,820 --> 00:02:37,683 @@ -340,23 +340,23 @@ We do this with the argmax function. 69 00:02:38,640 --> 00:02:41,550 -然后我们可以使用数据集库中的指标。 +然后我们可以使用 Datasets library 中的指标。 Then we can use a metric from the Datasets library. 70 00:02:41,550 --> 00:02:43,500 -它可以像数据集一样轻松加载 +它可以像数据集一样被轻松地加载 It can be loaded as easily as a dataset 71 00:02:43,500 --> 00:02:45,360 -具有负载度量功能 -with the load metric function +使用 load_metric 函数 +with the load_metric function 72 00:02:45,360 --> 00:02:49,500 -并且每个返回用于数据集的评估指标。 -and each returns the evaluation metric used for the dataset. +并且返回用于该数据集的评估指标。 +and it returns the evaluation metric used for the dataset. 73 00:02:49,500 --> 00:02:51,600 @@ -365,13 +365,13 @@ We can see our model did learn something 74 00:02:51,600 --> 00:02:54,363 -因为它的准确率为 85.7%。 +因为它有 85.7% 的准确率。 as it is 85.7% accurate. 75 00:02:55,440 --> 00:02:57,870 -在训练期间监控评估矩阵, -To monitor the evaluation matrix during training, +为了在训练期间监控评估指标, +To monitor the evaluation metrics during training, 76 00:02:57,870 --> 00:02:59,829 @@ -385,52 +385,52 @@ that does the same step as before. 78 00:03:02,670 --> 00:03:04,728 -它需要一个带有预测和标签的命名元组 +它接收一个带有预测和标签的命名元组 It takes a namedtuple with predictions and labels 79 00:03:04,728 --> 00:03:06,327 -并且必须返回一个字典 +并且返回一个字典 and must return a dictionary 80 00:03:06,327 --> 00:03:08,427 -使用我们想要跟踪的指标。 +包含我们想要跟踪的指标。 with the metrics we want to keep track of. 81 00:03:09,360 --> 00:03:11,490 -通过 epoch 评估策略 +通过将评估策略设置为 epoch By passing the epoch evaluation strategy 82 00:03:11,490 --> 00:03:13,080 -对于我们的训练论点, -to our training arguments, +对于我们的 TrainingArguments, +to our TrainingArguments, 83 00:03:13,080 --> 00:03:14,490 -我们告诉培训师评估 +我们告诉 Trainer 去进行评估 we tell the Trainer to evaluate 84 00:03:14,490 --> 00:03:15,903 -在每个纪元的末尾。 +在每个 epoch 结束的时候。 at the end of every epoch. 85 00:03:17,280 --> 00:03:18,587 -在笔记本中启动培训 +在 notebook 中启动训练 Launching a training inside a notebook 86 00:03:18,587 --> 00:03:20,640 -然后会显示一个进度条 +会显示一个进度条 will then display a progress bar 87 00:03:20,640 --> 00:03:23,643 -并在你通过每个纪元时完成你在这里看到的表格。 +并在你运行完每个 epoch 时将数据填到你看到的这个表格。 and complete the table you see here as you pass every epoch. 88 diff --git a/subtitles/zh-CN/26_fine-tuning-with-tensorflow.srt b/subtitles/zh-CN/26_fine-tuning-with-tensorflow.srt index a4c18d3a4..bb4ddce17 100644 --- a/subtitles/zh-CN/26_fine-tuning-with-tensorflow.srt +++ b/subtitles/zh-CN/26_fine-tuning-with-tensorflow.srt @@ -20,12 +20,12 @@ It's very quick. 5 00:00:12,510 --> 00:00:14,490 -如果你看过我们的管道视频, +如果你看过我们关于 pipeline 的视频, And if you've watched our pipeline videos, 6 00:00:14,490 --> 00:00:18,150 -我将在下面链接,过程非常相似。 +我将在下面链接之,过程非常相似。 which I'll link below, the process is very similar. 7 @@ -50,7 +50,7 @@ So to learn more about transfer learning, 11 00:00:28,710 --> 00:00:31,320 -前往 “什么是迁移学习?” 视频, +前往 “What is transfer learning?” 视频, head to the 'What is transfer learning?' video, 12 @@ -85,7 +85,7 @@ as the foundation for our training today. 18 00:00:44,850 --> 00:00:46,770 -但这条怪物线是什么, +但这条怪物般的一行是什么, But what is this monstrosity line, 19 @@ -170,12 +170,12 @@ And secondly, it needs to know how many classes 35 00:01:23,940 --> 00:01:26,693 -你的问题有,因为这将决定大小, +你的问题是有的,因为这将决定大小, your problem has, because that will determine the size, 36 00:01:26,693 --> 00:01:29,610 -输出头中的神经元数量。 +对输出头中的神经元数量。 the number of neurons in the output head. 37 @@ -185,12 +185,12 @@ So if you want to follow along with the data 38 00:01:31,530 --> 00:01:34,500 -来自我们的数据集视频,我将在下面链接, +来自我们有关数据集的视频,我将在下面链接, from our datasets videos, which I'll link below, 39 00:01:34,500 --> 00:01:37,440 -那么你将有两个班级,积极的和消极的, +那么你将有两个类别,积极的和消极的, then you'll have two classes, positive and negative, 40 @@ -255,7 +255,7 @@ loss function. 52 00:02:07,260 --> 00:02:09,930 -所以这是一口,但它是标准的损失函数 +所以这是一点点,但它是标准的损失函数 So that's a mouthful, but it's the standard loss function 53 @@ -275,7 +275,7 @@ to output large values for the right class, 56 00:02:17,730 --> 00:02:20,910 -以及错误类别的低值。 +以及为错误的类别输出低值。 and low values for the wrong classes. 57 @@ -290,7 +290,7 @@ like we did with the optimizer. 59 00:02:26,010 --> 00:02:27,600 -但这里有一个风险, +但这里有一个问题, But there's a risk there, 60 @@ -300,7 +300,7 @@ there's a very common trap people fall into, 61 00:02:30,090 --> 00:02:32,580 -这是默认情况下,这种损失假设 +就是默认情况下,这种损失假设 which is that by default, this loss assumes 62 @@ -335,7 +335,7 @@ But you probably seen these before 68 00:02:47,790 --> 00:02:49,950 -在关于管道的视频中。 +在关于 pipeline 的视频中。 in the video about pipelines. 69 @@ -375,7 +375,7 @@ But for now, remember to set from_logits to true. 76 00:03:09,480 --> 00:03:11,430 -compile 需要知道的第二件事 +编译需要知道的第二件事 The second thing compile needs to know 77 @@ -385,7 +385,7 @@ is the optimizer you want. 78 00:03:13,230 --> 00:03:15,120 -在我们的例子中,我们使用亚当, +在我们的例子中,我们使用 adam , In our case, we use adam, 79 @@ -395,7 +395,7 @@ which is sort of the standard optimizer 80 00:03:16,830 --> 00:03:18,720 -这些天用于深度学习。 +用于现代深度学习。 for deep learning these days. 81 @@ -490,7 +490,7 @@ So we don't need to specify separate labels, 99 00:04:00,420 --> 00:04:01,570 -当我们称呼健康时。 +当我们调用 fit 时。 when we're calling fit. 100 @@ -510,7 +510,7 @@ like the number of epochs for training 103 00:04:09,900 --> 00:04:12,420 -你可以传递一些其他参数来适应。 +你可以传递一些其他参数给 fit 。 where there's some other arguments you can pass to fit. 104 diff --git a/subtitles/zh-CN/27_learning-rate-scheduling-with-tensorflow.srt b/subtitles/zh-CN/27_learning-rate-scheduling-with-tensorflow.srt index 700402578..9ce0a5587 100644 --- a/subtitles/zh-CN/27_learning-rate-scheduling-with-tensorflow.srt +++ b/subtitles/zh-CN/27_learning-rate-scheduling-with-tensorflow.srt @@ -65,7 +65,7 @@ which will make your training 14 00:00:31,080 --> 00:00:33,303 -更加一致地成功。 +更加稳定地成功。 much more consistently successful. 15 @@ -95,23 +95,23 @@ by default, Adam uses a learning rate 20 00:00:48,030 --> 00:00:51,540 -10 的负 3,1 E 负 3, -of 10 to the minus 3, 1 E minus 3, +10 的 -3 次方,1e-3, +of 10 to the minus 3, 1e-3, 21 00:00:51,540 --> 00:00:54,660 -这对于训练变压器模型来说非常高。 +这对于训练 transformer 模型来说非常高。 and that's very high for training transformer models. 22 00:00:54,660 --> 00:00:58,200 -我们将从 5 乘 10 到负 5 开始, +我们将从 5 乘 10 的负 5次方 开始, We're going to start at 5 by 10 to the minus 5, 23 00:00:58,200 --> 00:01:02,700 -5 E - 5,比默认值低 20 倍。 -5 E minus 5, which is 20 times lower than the default. +5e-5,比默认值低 20 倍。 +5e-5, which is 20 times lower than the default. 24 00:01:02,700 --> 00:01:06,330 @@ -130,7 +130,7 @@ if we decay the learning rate down to a tiny value, 27 00:01:11,160 --> 00:01:13,920 -甚至在培训过程中为零。 +甚至在训练过程中为零。 or even to zero , over the course of training. 28 @@ -145,17 +145,17 @@ this Polynomial Decay schedule thing is doing. 30 00:01:18,540 --> 00:01:21,570 -等会儿我会告诉你衰变是什么样子的, +等会儿我会告诉你衰减是什么样子的, So I'll show you what that decay looks like in a second, 31 00:01:21,570 --> 00:01:23,160 -但首先我们需要告诉调度程序 +但首先我们需要告诉规划程序 but first we need to tell the scheduler 32 00:01:23,160 --> 00:01:25,290 -培训将持续多长时间, +训练将持续多长时间, how long training is going to be, 33 @@ -190,12 +190,12 @@ and then we multiply that 39 00:01:39,570 --> 00:01:41,220 -按纪元数 +按 epoch 数 by the number of epochs 40 00:01:41,220 --> 00:01:42,930 -获得批次总数 +获得 batch 总数 to get the total number of batches 41 @@ -210,7 +210,7 @@ Once we know how many training steps we're taking, 43 00:01:47,880 --> 00:01:50,580 -我们只是将所有这些信息传递给调度程序 +我们只是将所有这些信息传递给规划程序 we just pass all that information to the scheduler 44 @@ -230,12 +230,12 @@ Well, it looks like this, 47 00:01:59,610 --> 00:02:02,160 -它从 5 E 减 5 开始, -it starts at 5 E minus 5, +它从 5e-5 开始, +it starts at 5e-5, 48 00:02:02,160 --> 00:02:05,490 -这意味着 10 的 5 乘以负 5, +这意味着 5 乘以 10^{-5}, which means 5 times 10 to the minus 5, 49 @@ -270,7 +270,7 @@ this is actually constant or a linear decay, 55 00:02:18,690 --> 00:02:20,310 -我知道这个名字是多项式的, +我知道这个名字有 "多项式", and I know the name is polynomial, 56 @@ -425,7 +425,7 @@ which is getting passed to to the learning rate argument 86 00:03:27,540 --> 00:03:29,100 -该优化器。 +对该优化器的。 of that optimizer. 87 @@ -445,7 +445,7 @@ so this is going to be sparse categorical crossentropy 90 00:03:37,050 --> 00:03:39,840 -如果你正在关注微调视频。 +如果你正在关注 fine-tuning (微调) 的视频。 if you're following along from the fine-tuning video. 91 @@ -480,7 +480,7 @@ Remember, because we compiled the model 97 00:03:51,600 --> 00:03:54,300 -使用新的优化器和新的学习率计划, +使用新的优化器和新的学习率规划, with the new optimizer and the new learning rate schedule, 98 @@ -490,7 +490,7 @@ we actually don't need to change anything at all 99 00:03:56,190 --> 00:03:57,360 -当我们称呼合适时, +当我们调用 fit 时, when we call fit, 100 @@ -515,7 +515,7 @@ with a nice, smooth learning rate decay, 104 00:04:04,740 --> 00:04:06,330 -从好的价值开始, +从好的值开始, starting from a good value, 105 diff --git a/subtitles/zh-CN/28_tensorflow-predictions-and-metrics.srt b/subtitles/zh-CN/28_tensorflow-predictions-and-metrics.srt index 9d157d4d1..b6d06f85d 100644 --- a/subtitles/zh-CN/28_tensorflow-predictions-and-metrics.srt +++ b/subtitles/zh-CN/28_tensorflow-predictions-and-metrics.srt @@ -15,7 +15,7 @@ and as always, there'll be links below 4 00:00:09,000 --> 00:00:10,740 -如果你想检查那些, +如果你想回看那些, if you want to check those out, 5 @@ -25,7 +25,7 @@ we showed you how to initialize and fine-tune 6 00:00:13,230 --> 00:00:15,690 -TensorFlow 中的转换器模型。 +TensorFlow 中的 transformer 模型。 a transformer model in TensorFlow. 7 @@ -55,13 +55,13 @@ so let's see how to do that. 12 00:00:25,560 --> 00:00:28,320 -同样,如果你熟悉 Keras,那么好消息 -Again, if you're familiar with Keras, the good news +同样,如果你熟悉 Keras,那么好消息是 +Again, if you're familiar with Keras, the good news is 13 00:00:28,320 --> 00:00:31,860 -是因为只有标准的 Keras 模型, -is that because there are just standard Keras models, +因为只有标准的 Keras 模型, +that because there are just standard Keras models, 14 00:00:31,860 --> 00:00:34,770 @@ -75,7 +75,7 @@ as shown here. 16 00:00:36,990 --> 00:00:40,560 -你只需将标记化的文本传递给此方法, +你只需将分词化的文本传递给此方法, You simply pass in tokenized text to this method, 17 @@ -105,17 +105,17 @@ but most of the time the thing you want 22 00:00:50,310 --> 00:00:52,290 -是输出逻辑。 +是输出的 logits 。 is the output logits. 23 00:00:52,290 --> 00:00:54,900 -如果你在登录之前没有遇到过它们, -If you haven't come across them before logits, +如果你在之前没有遇到过它们,logits +If you haven't come across them before, logits, 24 00:00:54,900 --> 00:00:57,630 -有时对 logits 发音,因为没有人确定, +有时发音成 logits ,因为没有人确定, sometimes pronounced to logits because no one's sure, 25 @@ -125,7 +125,7 @@ are the outputs of the last layer of the network 26 00:01:00,390 --> 00:01:03,150 -因为在应用 softmax 之前。 +因为在使用 softmax 之前。 because before a softmax has been applied. 27 @@ -135,27 +135,27 @@ So if you want to turn the logits 28 00:01:04,710 --> 00:01:06,900 -进入模型的概率输出, +进成模型的概率输出, into the model's probability outputs, 29 00:01:06,900 --> 00:01:09,423 -你只需应用一个 softmax,就像这样。 +你只需使用一个 softmax,就像这样。 you just apply a softmax, like so. 30 00:01:10,981 --> 00:01:12,630 -如果我们想改变这些概率怎么办 +如果我们想这些概率 What if we want to turn those probabilities 31 00:01:12,630 --> 00:01:14,370 -进入课堂预测? +进成类别预测? into class predictions? 32 00:01:14,370 --> 00:01:16,410 -同样,它非常简单。 +同样,它非常直接。 Again, it's very straightforward. 33 @@ -195,7 +195,7 @@ one in the first position, and so on. 40 00:01:37,350 --> 00:01:40,380 -所以这些是我们的类预测表明类零, +所以这些是我们的类预测表明第零类, So these are our class predictions indicating class zero, 41 @@ -205,7 +205,7 @@ class one, and so on. 42 00:01:42,300 --> 00:01:45,090 -事实上,如果你想要的只是课堂预测, +事实上,如果你想要的只是类别预测, In fact, if class predictions are all you want, 43 @@ -260,12 +260,12 @@ for the model's predictions. 53 00:02:09,060 --> 00:02:10,920 -如果你关注我们的数据集 +如果你关注我们有关数据集 If you're following along with our datasets 54 00:02:10,920 --> 00:02:12,390 -和微调视频, +和微调的视频, and fine tuning videos, 55 @@ -280,7 +280,7 @@ which is part of the GLUE benchmark. 57 00:02:17,130 --> 00:02:19,050 -每个 GLUE 数据集 +GLUE 数据集的每一个 Each of the GLUE datasets 58 @@ -310,12 +310,12 @@ For the MRPC dataset, the built-in metrics are accuracy 63 00:02:33,720 --> 00:02:35,790 -它只是衡量时间的百分比 +它只是衡量百分比, 其 which just measures the percentage of the time 64 00:02:35,790 --> 00:02:37,830 -模型的预测是正确的, +模型的预测是正确的次数, the model's prediction was correct, 65 @@ -325,12 +325,12 @@ and the F1 score, 66 00:02:39,780 --> 00:02:41,610 -这是一个稍微复杂的措施 +这是一个稍微复杂的度量 which is a slightly more complex measure 67 00:02:41,610 --> 00:02:43,920 -衡量模型权衡的程度 +衡量模型如何权衡 that measures how well the model trades off 68 @@ -355,7 +355,7 @@ and to the ground truth labels, 72 00:02:53,220 --> 00:02:56,880 -我们在一个简单的 Python dict 中得到我们的结果。 +我们在一个简单的 Python 字典中得到我们的结果。 and we get our results in a straightforward Python dict. 73 @@ -400,12 +400,12 @@ on the fly while you're training, 81 00:03:10,470 --> 00:03:11,910 -这给了你一个非常有用的见解 +这给了你一个非常有用的理解 which gives you a very useful insight 82 00:03:11,910 --> 00:03:13,740 -了解培训的进展情况。 +了解训练的进展情况。 into how training is going. 83 @@ -445,7 +445,7 @@ or you can import the actual metric objects 90 00:03:30,810 --> 00:03:33,240 -并向他们传递具体的论点。 +并向他们传递具体的参数。 and pass specific arguments to them. 91 @@ -470,7 +470,7 @@ Once a model has been compiled with a metric, 95 00:03:43,140 --> 00:03:45,360 -它将报告该训练指标, +它将报告该指标对于训练, it will report that metric for training, 96 @@ -515,7 +515,7 @@ by default in Keras, 104 00:04:02,850 --> 00:04:04,473 -比如 F1 分数。 +比如 F1 score. such as the F1 score. 105 diff --git a/subtitles/zh-CN/29_write-your-training-loop-in-pytorch.srt b/subtitles/zh-CN/29_write-your-training-loop-in-pytorch.srt index 732483f13..8d956c8b9 100644 --- a/subtitles/zh-CN/29_write-your-training-loop-in-pytorch.srt +++ b/subtitles/zh-CN/29_write-your-training-loop-in-pytorch.srt @@ -15,8 +15,8 @@ 4 00:00:05,460 --> 00:00:08,486 -- 使用 PyTorch 编写你自己的训练循环。 -- Write your own training loop with PyTorch. +使用 PyTorch 编写你自己的训练循环。 +Write your own training loop with PyTorch. 5 00:00:08,486 --> 00:00:09,960 @@ -25,7 +25,7 @@ In this video, we'll look at 6 00:00:09,960 --> 00:00:12,750 -我们如何进行与培训师视频中相同的微调, +我们如何进行与训练器视频中相同的微调, how we can do the same fine-tuning as in the Trainer video, 7 @@ -35,12 +35,12 @@ but without relying on that class. 8 00:00:14,760 --> 00:00:17,790 -这样,你就可以轻松自定义每个步骤 +这样,你就可以根据你的需要轻松自定义 This way, you'll be able to easily customize each step 9 00:00:17,790 --> 00:00:20,310 -到你需要的训练循环。 +训练循环的每个步骤。 to the training loop to your needs. 10 @@ -50,12 +50,12 @@ This is also very useful 11 00:00:21,660 --> 00:00:22,740 -手动调试某些东西 +对于手动调试 to manually debug something 12 00:00:22,740 --> 00:00:24,590 -Trainer API 出了问题。 +Trainer API 出现的问题。 that went wrong with the Trainer API. 13 @@ -85,8 +85,8 @@ That number is not useful in its own, 18 00:00:39,316 --> 00:00:40,260 -用于计算 -that is used to compute +它是用于计算 +but is used to compute 19 00:00:40,260 --> 00:00:42,150 @@ -95,12 +95,12 @@ the ingredients of our model weights, 20 00:00:42,150 --> 00:00:43,440 -那是损失的导数 +即,损失关于 that is the derivative of the loss 21 00:00:44,610 --> 00:00:47,160 -关于每个模型的重量。 +每个模型权重的导数。 with respect to each model weight. 22 @@ -125,7 +125,7 @@ We then repeat the process 26 00:00:54,510 --> 00:00:56,880 -与一批新的训练数据。 +使用一批新的训练数据。 with a new batch of training data. 27 @@ -165,7 +165,7 @@ Check out the videos link below 34 00:01:12,630 --> 00:01:14,280 -如果你还没有看到它们。 +如果你还没有看过它们。 if you haven't seen them already. 35 @@ -180,8 +180,8 @@ which will be responsible to convert 37 00:01:20,610 --> 00:01:23,253 -我们数据集的元素到补丁中。 -the elements of our dataset into patches. +我们数据集的元素到批次数据中。 +the elements of our dataset into batches. 38 00:01:24,450 --> 00:01:27,960 @@ -190,17 +190,17 @@ We use our DataColletorForPadding as a collate function, 39 00:01:27,960 --> 00:01:29,460 -并洗牌训练集 +并打乱训练集的次序 and shuffle the training set 40 00:01:29,460 --> 00:01:31,080 -确保我们不会检查样品 +确保我们不会每个纪元 to make sure we don't go over the samples 41 00:01:31,080 --> 00:01:33,870 -在一个时代 * 以相同的顺序。 +以相同的顺序遍历样本。 in the same order at a epoch*. 42 @@ -216,16 +216,16 @@ we try to grab a batch of data, and inspect it. 44 00:01:40,080 --> 00:01:43,050 就像我们的数据集元素一样,它是一个字典, -Like our data set elements, it's a dictionary, +Like our dataset elements, it's a dictionary, 45 00:01:43,050 --> 00:01:46,260 -但这些时候值不是一个整数列表 -but these times the values are not a single list of integers +但这里的值不是一个整数列表 +but this times the values are not a single list of integers 46 00:01:46,260 --> 00:01:49,053 -但是按序列长度形状批量大小的张量。 +而是形状为批量大小乘以序列长度的张量。 but a tensor of shape batch size by sequence length. 47 @@ -240,7 +240,7 @@ For that, we'll need to actually create a model. 49 00:01:56,730 --> 00:01:58,740 -如 Model API 视频中所示, +如模型 API 视频中所示, As seen in the Model API video, 50 @@ -250,12 +250,12 @@ we use the from_pretrained method, 51 00:02:00,540 --> 00:02:03,270 -并将标签数量调整为类别数量 +并将标签数量调整为这个数据集 and adjust the number of labels to the number of classes 52 00:02:03,270 --> 00:02:06,810 -我们有这个数据集,这里有两个。 +拥有的类别数量,这里是 2。 we have on this data set, here two. 53 @@ -275,7 +275,7 @@ and check there is no error. 56 00:02:13,320 --> 00:02:14,940 -如果提供标签, +如果提供了标签, If the labels are provided, 57 @@ -290,12 +290,12 @@ always returns a loss directly. 59 00:02:19,525 --> 00:02:21,090 -我们将能够做 loss.backward () +我们将能够做损失的反向传播 We will be able to do loss.backward () 60 00:02:21,090 --> 00:02:22,860 -计算所有梯度, +以计算所有梯度, to compute all the gradients, 61 @@ -325,17 +325,17 @@ Using the previous loss, 66 00:02:36,150 --> 00:02:39,060 -并使用 loss.backward () 计算梯度, +并使用 loss.backward() 计算梯度, and computing the gradients with loss.backward (), 67 00:02:39,060 --> 00:02:41,130 -我们检查我们是否可以执行优化器步骤 +我们检查我们是否可以无误 we check that we can do the optimizer step 68 00:02:41,130 --> 00:02:42,030 -没有任何错误。 +执行优化器步骤。 without any error. 69 @@ -360,7 +360,7 @@ We could already write our training loop, 73 00:02:52,080 --> 00:02:53,220 -但我们还要添加两件事 +但我们还要再做两件事 but we will add two more things 74 @@ -390,7 +390,7 @@ is just a convenience function 79 00:03:06,150 --> 00:03:07,800 -轻松构建这样的调度程序。 +轻松构建这样的调度器。 to easily build such a scheduler. 80 @@ -400,7 +400,7 @@ You can again use 81 00:03:09,683 --> 00:03:11,860 -取而代之的是任何 PyTorch 学习率调度程序。 +取而代之的是任何 PyTorch 学习率调度器。 any PyTorch learning rate scheduler instead. 82 @@ -426,7 +426,7 @@ The first step is to get one, 86 00:03:21,270 --> 00:03:23,283 例如通过使用协作笔记本。 -for instance by using a collab notebook. +for instance by using a colab notebook. 87 00:03:24,180 --> 00:03:26,040 @@ -435,12 +435,12 @@ Then you need to actually send your model, 88 00:03:26,040 --> 00:03:28,923 -并使用火炬设备对其进行训练数据。 +并使用 torch 设备对其进行训练数据。 and training data on it by using a torch device. 89 00:03:29,790 --> 00:03:30,840 -仔细检查以下行 +仔细检查以下代码 Double-check the following lines 90 @@ -560,12 +560,12 @@ then go through all the data in the evaluation data loader. 113 00:04:23,850 --> 00:04:25,530 -正如我们在培训师视频中看到的那样, +正如我们在训练器视频中看到的那样, As we have seen in the Trainer video, 114 00:04:25,530 --> 00:04:26,850 -模型输出 logits, +模型输出逻辑斯蒂, the model outputs logits, 115 @@ -610,7 +610,7 @@ Congratulations, you have now fine-tuned a model 123 00:04:44,490 --> 00:04:45,633 -靠你自己。 +全靠你自己。 all by yourself. 124 diff --git a/subtitles/zh-CN/30_supercharge-your-pytorch-training-loop-with-accelerate.srt b/subtitles/zh-CN/30_supercharge-your-pytorch-training-loop-with-accelerate.srt index 0402b95da..0061b8020 100644 --- a/subtitles/zh-CN/30_supercharge-your-pytorch-training-loop-with-accelerate.srt +++ b/subtitles/zh-CN/30_supercharge-your-pytorch-training-loop-with-accelerate.srt @@ -5,12 +5,12 @@ 2 00:00:05,460 --> 00:00:07,470 -- 使用 Hugging Face Accelerate 增强 +- 增强你的 PyTorch 训练循环 - Supercharge your PyTorch training loop 3 00:00:07,470 --> 00:00:08,943 -你的 PyTorch 训练循环 +使用 Hugging Face Accelerate with Hugging Face Accelerate. 4 @@ -20,12 +20,12 @@ There are multiple setups 5 00:00:12,600 --> 00:00:14,580 -你可以在其上进行培训: +你可以在其上进行训练: on which you can run your training: 6 00:00:14,580 --> 00:00:17,910 -它可能在 CPU、GPU、TPU 上, +它可以在 CPU、GPU、TPU 上, it could be on CPU, GPUs, TPUs, 7 @@ -35,7 +35,7 @@ distributed on one machine with several devices, 8 00:00:20,610 --> 00:00:23,220 -甚至几台机器,通常称为节点, +甚至几台机器,通常称为节点 (node) , or even several machines, often called nodes, 9 @@ -45,7 +45,7 @@ each with multiple devices. 10 00:00:26,340 --> 00:00:28,200 -最重要的是,还有新的调整 +在这之上,还有新的调整 On top of that, there are new tweaks 11 @@ -55,22 +55,22 @@ to make your training faster or more efficient, 12 00:00:30,810 --> 00:00:32,763 -比如混合精度和 DeepSpeed。 +比如混合精度和 DeepSpeed 。 like mixed precision and DeepSpeed. 13 00:00:33,840 --> 00:00:36,600 -每一个设置或训练调整 +这每一个设置或训练调整 Each of those setups or training tweaks 14 00:00:36,600 --> 00:00:38,760 -要求你更改训练循环的代码 +都要求你更改训练循环的代码 requires you to change the code of your training loop 15 00:00:38,760 --> 00:00:41,733 -以某种方式学习新的 API。 +以某种方式, 或者学习新的 API。 in one way or another and to learn a new API. 16 @@ -85,7 +85,7 @@ and there are several third-party libraries that can help. 18 00:00:49,590 --> 00:00:50,760 -那些问题 +它们的问题 The problem with those 19 @@ -95,12 +95,12 @@ is that they can feel like a black box 20 00:00:53,100 --> 00:00:55,320 -并且实施调整可能并不容易 +并且实现调整可能并不容易 and that it might not be easy to implement the tweak 21 00:00:55,320 --> 00:00:56,820 -到你需要的训练循环。 +到你需要的训练循环上。 to the training loop you need. 22 @@ -110,12 +110,12 @@ Accelerate has been designed specifically 23 00:00:59,760 --> 00:01:02,790 -让你保持对训练循环的完全控制 +以让你保持对训练循环的完全控制 to let you retain full control over your training loop 24 00:01:02,790 --> 00:01:04,833 -并尽可能不打扰。 +并尽可能不打乱。 and be as non-intrusive as possible. 25 @@ -135,7 +135,7 @@ Accelerate will handle all the setups 28 00:01:14,730 --> 00:01:17,180 -和第一张幻灯片中提到的培训调整。 +和第一张幻灯片中提到的训练调整。 and training tweaks mentioned on the first slide. 29 @@ -155,7 +155,7 @@ More specifically, you have to import and instantiate 32 00:01:25,980 --> 00:01:27,360 -加速器对象, +一个 accelerator 对象, an accelerator object, 33 @@ -170,7 +170,7 @@ for your specific setup. 35 00:01:31,380 --> 00:01:33,780 -然后你必须把模型发给它, +然后你必须发给它模型 Then you have to send it the model, 36 @@ -190,7 +190,7 @@ Accelerate handles device placement, 39 00:01:42,870 --> 00:01:44,370 -所以你不需要把你的批次 +所以你不需要把你的分批 so you don't need to put your batch 40 @@ -205,7 +205,7 @@ Finally, you have to replace the loss.backward line 42 00:01:50,640 --> 00:01:54,300 -通过 accelerator.backwardloss, +成 accelerator.backwardloss, by accelerator.backwardloss, 43 @@ -260,32 +260,32 @@ to the accelerator.prepare method, like for training. 53 00:02:22,170 --> 00:02:23,430 -然后你可以关闭这条线 +然后你可以关闭这行 Then you can dismiss the line 54 00:02:23,430 --> 00:02:26,160 -将批次放在适当的设备上, +将分批放在适当的设备上, that places the batch on the proper device, 55 00:02:26,160 --> 00:02:27,870 -在通过你的预测之前 +在输入你的预测 and just before passing your predictions 56 00:02:27,870 --> 00:02:31,110 -和指标的标签,使用 accelerator.gather +和指标的标签之前,使用 accelerator.gather and labels to your metric, use accelerator.gather 57 00:02:31,110 --> 00:02:33,300 -收集预测 +来收集预测 to gather together the predictions 58 00:02:33,300 --> 00:02:34,803 -和每个过程的标签。 +和每个进程的标签。 and labels from each process. 59 @@ -295,7 +295,7 @@ A distributed training script 60 00:02:37,890 --> 00:02:41,040 -必须在不同的过程中多次启动, +必须在不同的进程中多次启动, has to be launched several times on different processes, 61 @@ -330,7 +330,7 @@ In a terminal, run accelerate config 67 00:02:57,270 --> 00:02:58,650 -并回答小问卷 +并回答小问题 and answer the small questionnaire 68 @@ -340,7 +340,7 @@ to generate a configuration file 69 00:03:00,330 --> 00:03:02,073 -与所有相关信息, +含所有相关信息, with all the relevant information, 70 @@ -355,7 +355,7 @@ followed by the path to your training script. 72 00:03:08,580 --> 00:03:12,000 -在笔记本中,你可以使用笔记本启动器功能 +在 notebook 中,你可以使用 notebook 启动器函数 In a notebook, you can use the notebook launcher function 73 diff --git a/subtitles/zh-CN/31_navigating-the-model-hub.srt b/subtitles/zh-CN/31_navigating-the-model-hub.srt index 3b9e03f36..a4f1c2a5b 100644 --- a/subtitles/zh-CN/31_navigating-the-model-hub.srt +++ b/subtitles/zh-CN/31_navigating-the-model-hub.srt @@ -5,12 +5,12 @@ 2 00:00:04,050 --> 00:00:05,910 -- [Instructor] 在这段视频中,我们将回顾 +- [Instructor] 在本视频中,我们将回顾 - [Instructor] In this video, we're going to go over 3 00:00:05,910 --> 00:00:08,013 -HuggingFace 模型中心导航。 +HuggingFace Model Hub 导航。 the HuggingFace Model Hub navigation. 4 @@ -20,42 +20,42 @@ This is the huggingface.co landing page. 5 00:00:13,260 --> 00:00:16,020 -要访问模型中心,请单击模型选项卡 -To access the model hub, click on the models tab +要访问 Model Hub,请在右上角 +To access the Model Hub, click on the Models tab 6 00:00:16,020 --> 00:00:17,463 -在右上角。 +单击 Models 选项卡。 in the upper right corner. 7 00:00:18,960 --> 00:00:21,030 -你应该面对这个网络界面, +你会看到这个网页界面, You should be facing this web interface, 8 00:00:21,030 --> 00:00:23,193 -可以分成几个部分。 +它被分为几个部分。 which can be split into several parts. 9 00:00:24,480 --> 00:00:26,790 -在左侧,你会找到类别, +在左侧,你会找到类别 (category), On the left, you'll find categories, 10 00:00:26,790 --> 00:00:29,090 -你可以使用它来定制你的模型搜索。 +你可以使用它来自定义模型搜索。 which you can use to tailor your model search. 11 00:00:29,970 --> 00:00:32,970 -第一类是任务。 +第一类是任务 (tasks)。 The first category is the tasks. 12 00:00:32,970 --> 00:00:36,660 -集线器上的模型可用于各种各样的任务。 +hub 上的模型可用于各种各样的任务。 Models on the hub may be used for a wide variety of tasks. 13 @@ -90,12 +90,12 @@ or automatic speech recognition for speech. 19 00:00:54,840 --> 00:00:57,870 -第二类是图书馆。 +第二类是库 (Library)。 The second category is the libraries. 20 00:00:57,870 --> 00:01:00,990 -集线器上的模型通常共享三个主干之一, +hub 上的模型通常共享三个主要框架之一, Models on the hub usually share one of three backbones, 21 @@ -110,7 +110,7 @@ However, other backbones, such as rust or ONNX also exist. 23 00:01:09,540 --> 00:01:11,850 -最后,这个选项卡也可以使用 +最后,这个选项卡也可以 Finally, this tab can also be used 24 @@ -120,13 +120,13 @@ to specify from which high-level framework the models comes. 25 00:01:16,140 --> 00:01:19,260 -这包括变形金刚,但不限于此。 +这包括 Transformers ,但也不仅仅是这些。 This includes Transformers, but it isn't limited to it. 26 00:01:19,260 --> 00:01:21,060 -模型集线器用于托管 -The model hub is used to host +Model Hub 用于托管 +The Model Hub is used to host 27 00:01:21,060 --> 00:01:22,920 @@ -135,32 +135,32 @@ a lot of different frameworks models, 28 00:01:22,920 --> 00:01:24,600 -我们正在积极寻求举办 +我们正在积极寻求 and we're actively looking to host 29 00:01:24,600 --> 00:01:25,893 -其他框架模型。 +支持其他框架模型。 other frameworks models. 30 00:01:28,530 --> 00:01:31,890 -第三类是数据集选项卡。 +第三类是数据集 (Datasets) 选项卡。 The third category is the datasets tab. 31 00:01:31,890 --> 00:01:35,070 -从此选项卡中选择数据集意味着过滤模型 +从此选项卡中选择数据集 Selecting a dataset from this tab means filtering the models 32 00:01:35,070 --> 00:01:37,683 -这样他们就可以在该特定数据集上接受培训。 +意味筛选在特定数据集上训练的模型。 so that they were trained on that specific dataset. 33 00:01:39,180 --> 00:01:42,300 -第四类是语言选项卡。 +第四类是语言 (Language) 选项卡。 The fourth category is the languages tab. 34 @@ -170,33 +170,33 @@ Selecting a language from this tab 35 00:01:43,800 --> 00:01:45,990 -意味着过滤模型以便它们处理 +意味着筛选模型以便它们适配 means filtering the models so that they handle 36 00:01:45,990 --> 00:01:47,090 -选择的语言。 +所选的语言。 the language selected. 37 00:01:48,600 --> 00:01:51,750 -最后,最后一个类别允许选择许可证 +最后,最后一个类别允许选择模型之间 Finally, the last category allows to choose the license 38 00:01:51,750 --> 00:01:53,313 -与之共享模型。 +共享的许可证 (license)。 with which the model is shared. 39 00:01:56,700 --> 00:01:58,770 -在右侧,你会找到可用的型号 +在右侧,你可以在 Model Hub  On the right, you'll find the models available 40 00:01:58,770 --> 00:02:00,480 -在模型中心。 -on the model hub. +找到可用的模型。 +on the Model Hub. 41 00:02:00,480 --> 00:02:03,750 @@ -210,7 +210,7 @@ When clicking on a model, 43 00:02:04,890 --> 00:02:07,230 -你应该面对它的模型卡。 +你可以看到它的模型卡。 you should be facing its model card. 44 @@ -220,12 +220,12 @@ The model card contains information about the model, 45 00:02:09,990 --> 00:02:13,263 -它的描述、预期用途、限制和偏见。 +它的描述、预期用途、限制和偏差。 its description, intended use, limitations and biases. 46 00:02:14,310 --> 00:02:17,580 -它还可以显示有关如何使用模型的代码片段, +它还可以展示与如何使用模型相关的代码片段, It can also show code snippets on how to use the model, 47 @@ -235,7 +235,7 @@ as well as any relevant information; 48 00:02:20,070 --> 00:02:22,080 -培训程序,数据处理, +训练过程,数据处理, training procedure, data processing, 49 @@ -255,22 +255,22 @@ The better crafted a model card is, 52 00:02:30,360 --> 00:02:33,270 -其他用户越容易利用你的模型 +越方便其他用户在他们的应用程序中 the easier it will be for other users to leverage your model 53 00:02:33,270 --> 00:02:34,443 -在他们的应用程序中。 +利用你的模型。 in their applications. 54 00:02:35,820 --> 00:02:38,553 -模型卡右侧是推理 API。 +模型卡右侧是 inference API。 On the right of the model card is the inference API. 55 00:02:39,540 --> 00:02:41,040 -可以使用此推理 API +可以使用此 interence API This inference API can be used 56 @@ -280,7 +280,7 @@ to play with the model directly. 57 00:02:43,290 --> 00:02:45,690 -随意修改文字,点击计算 +随意修改文字,点击 compute Feel free to modify the text and click on compute 58 @@ -305,12 +305,12 @@ that is relevant to the categories we have just seen. 62 00:03:01,320 --> 00:03:04,410 -文件和版本选项卡显示体系结构 -The files & versions tab displays the architecture +Files and versions 选项卡显示 +The Files and versions tab displays the architecture 63 00:03:04,410 --> 00:03:06,213 -该模型的存储库。 +模型仓库的架构。 of the repository of that model. 64 @@ -320,42 +320,42 @@ Here, we can see all the files that define this model. 65 00:03:10,920 --> 00:03:13,650 -你将看到 Git 存储库的所有常用功能: +你将看到 Git repository 的所有常用功能: You'll see all usual features of a Git repository: 66 00:03:13,650 --> 00:03:15,093 -可用的分支机构, +可用的 branch, the branches available, 67 00:03:17,160 --> 00:03:18,520 -提交历史 +commit 历史 the commit history 68 00:03:20,760 --> 00:03:22,683 -以及提交差异。 +以及 commit diff。 as well as the commit diff. 69 00:03:25,740 --> 00:03:27,510 -三个不同的按钮可用 +在模型卡的顶部 Three different buttons are available 70 00:03:27,510 --> 00:03:29,760 -在模型卡的顶部。 +有三个不同的按钮可用。 at the top of the model card. 71 00:03:29,760 --> 00:03:31,170 -第一个显示如何使用 +第一个显示如何在代码中 The first one shows how to use 72 00:03:31,170 --> 00:03:33,093 -以编程方式推理 API。 +使用 inference API。 the inference API programmatically. 73 @@ -365,12 +365,12 @@ The second one shows how to train this model in SageMaker. 74 00:03:42,870 --> 00:03:44,820 -最后一个显示了如何加载该模型 +最后一个显示了如何在适当的库中 And the last one shows how to load that model 75 00:03:44,820 --> 00:03:46,860 -在适当的库中。 +加载该模型。 within the appropriate library. 76 diff --git a/subtitles/zh-CN/32_managing-a-repo-on-the-model-hub.srt b/subtitles/zh-CN/32_managing-a-repo-on-the-model-hub.srt index e59ddd96f..e0997dfa7 100644 --- a/subtitles/zh-CN/32_managing-a-repo-on-the-model-hub.srt +++ b/subtitles/zh-CN/32_managing-a-repo-on-the-model-hub.srt @@ -5,7 +5,7 @@ 2 00:00:06,210 --> 00:00:08,280 -管理模型存储库 +管理模型仓库 to manage a model repository 3 @@ -15,7 +15,7 @@ on the Hugging Face Hub Model Hub. 4 00:00:10,920 --> 00:00:13,020 -为了处理存储库 +为了处理仓库 In order to handle a repository 5 @@ -25,32 +25,32 @@ you should first have a Hugging Face account. 6 00:00:15,450 --> 00:00:17,610 -创建新帐户的链接可用 +在描述中有创建新帐户 A link to create a new account is available 7 00:00:17,610 --> 00:00:18,573 -在说明中。 +的链接。 in the description. 8 00:00:20,130 --> 00:00:22,980 -登录后,你可以创建一个新的存储库 +登录后,你可以创建一个新的仓库 Once you are logged in, you can create a new repository 9 00:00:22,980 --> 00:00:25,890 -通过单击新模型选项。 -by clicking on the new model option. +通过单击 New Model 选项。 +by clicking on the New Model option. 10 00:00:25,890 --> 00:00:29,400 -你应该面对与以下类似的模式。 -You should be facing a similar modal to the following. +你会看到类似下面的模型。 +You should be facing a similar model to the following. 11 00:00:29,400 --> 00:00:33,240 -在所有者输入中,你可以放置自己的命名空间 +在 Owner 输入框中,你可以放置自己的命名空间 In the owner input, you can put either your own namespace 12 @@ -60,12 +60,12 @@ or any of your organization's namespaces. 13 00:00:36,660 --> 00:00:39,330 -模型名称是模型标识符 -The model name is the model identifier +Model name 是模型标识符 +The Model name is the model identifier 14 00:00:39,330 --> 00:00:40,320 -然后将被使用 +它将被用于 that will then be used 15 @@ -75,7 +75,7 @@ to identify your model on the chosen namespace. 16 00:00:44,130 --> 00:00:47,700 -最后的选择是在公共和私人之间。 +最后可以在 Public(公共) 和 Private(私有) 之间选择。 The final choice is between public and private. 17 @@ -95,12 +95,12 @@ as this makes your model easily accessible and shareable. 20 00:00:54,960 --> 00:00:57,630 -你的命名空间的所有者是唯一的 +你的命名空间的所有者 The owners of your namespace are the only ones 21 00:00:57,630 --> 00:00:59,523 -谁可以更新和更改你的模型。 +是唯一可以更新和更改你的模型。 who can update and change your model. 22 @@ -120,7 +120,7 @@ only the owners of your namespace 25 00:01:06,000 --> 00:01:08,280 -将对你的模型有可见性。 +对你的模型有可见性。 will have visibility over your model. 26 @@ -135,7 +135,7 @@ and will not be able to use it. 28 00:01:15,030 --> 00:01:17,030 -让我们创建一个虚拟模型来玩。 +让我们创建一个虚拟模型来试试看。 Let's create a dummy model to play with. 29 @@ -155,17 +155,17 @@ Three tabs are available to you. 32 00:01:24,360 --> 00:01:27,960 -你面对的是第一个,这是模型卡页面。 -You're facing the first one, which is the model card page. +你面对的是第一个,这是 Model card 页面。 +You're facing the first one, which is the Model card page. 33 00:01:27,960 --> 00:01:29,970 -这是你用来展示模型的页面 +这是你用来向全世界展示模型 This is the page you use to showcase your model 34 00:01:29,970 --> 00:01:31,110 -致全世界。 +的页面。 to the world. 35 @@ -175,17 +175,17 @@ We'll see how it can be completed in a bit. 36 00:01:34,500 --> 00:01:37,503 -第二个是文件和版本选项卡。 -The second one is the files and versions tab. +第二个是 Files and Versions 选项卡。 +The second one is the Files and Versions tab. 37 00:01:38,340 --> 00:01:40,920 -你的模型本身就是一个 Git 存储库。 +你的模型本身就是一个 Git 仓库。 Your model itself is a Git repository. 38 00:01:40,920 --> 00:01:43,230 -如果你不知道什么是 Git 存储库, +如果你不知道什么是 Git 仓库, If you're unaware of what is a Git repository, 39 @@ -200,22 +200,22 @@ If you have never used Git before, 41 00:01:48,120 --> 00:01:50,100 -我们建议看介绍 +我们建议观看视频描述中 we recommend looking at an introduction 42 00:01:50,100 --> 00:01:52,600 -就像本视频描述中提供的那样。 +提供的介绍内容。 like the one provided in this video's description. 43 00:01:53,850 --> 00:01:56,910 -Git 存储库允许你查看发生的更改 +Git 仓库支持按照时间推移 The Git repository allows you to see the changes happening 44 00:01:56,910 --> 00:02:00,900 -随着时间的推移在此文件夹中,因此术语版本。 +查看本文件夹中的变化,也就是版本。 over time in this folder, hence the term versions. 45 @@ -225,12 +225,12 @@ We'll see how to add files and versions in a bit. 46 00:02:07,020 --> 00:02:09,570 -最后一个选项卡是设置选项卡, +最后一个选项卡是 Settings 选项卡, The final tab is the settings tab, 47 00:02:09,570 --> 00:02:12,120 -这使你可以管理模型的可见性 +可以管理模型的可见性 which allows you to manage your model's visibility 48 @@ -240,27 +240,27 @@ and availability. 49 00:02:14,790 --> 00:02:17,673 -让我们首先从将文件添加到存储库开始。 +让我们首先从将文件添加到仓库开始。 Let's first start by adding files to the repository. 50 00:02:18,540 --> 00:02:19,560 -可以添加文件 +还好有 add file 按钮 Files can be added 51 00:02:19,560 --> 00:02:23,340 -多亏了添加文件按钮,通过网络界面。 +通过网页操作即可添加文件。 through the web interface thanks to the add file button. 52 00:02:23,340 --> 00:02:27,060 -添加的文件可以是任何类型,python,JSON,文本, +添加的文件可以是任何类型,python,JSON,纯文本, The added files can be of any type, python, JSON, text, 53 00:02:27,060 --> 00:02:27,893 -你的名字。 +任君选择。 you name it. 54 @@ -270,32 +270,32 @@ Alongside your added file and its content, 55 00:02:31,170 --> 00:02:33,363 -你应该命名你的更改或提交。 +你还应该命名你的 change 或 commit。 you should name your change or commit. 56 00:02:36,330 --> 00:02:38,400 -一般添加文件比较简单 +通常,使用 Hugging Face Hub Python 库 Generally, adding files is simpler 57 00:02:38,400 --> 00:02:40,770 -通过使用 Hugging Face Hub Python 库 +或使用命令行添加文件 by using the Hugging Face Hub Python library 58 00:02:40,770 --> 00:02:43,050 -或使用命令行。 +比较简单。 or by using the command-line. 59 00:02:43,050 --> 00:02:44,310 -我们将展示如何做到这一点 +我们将展示如何使用 We'll showcase how to do this 60 00:02:44,310 --> 00:02:46,290 -使用 Hugging Face Hub Python 库, +Hugging Face Hub Python 库做到这一点 using the Hugging Face Hub Python library, 61 @@ -305,7 +305,7 @@ and there is a link in the description 62 00:02:48,060 --> 00:02:49,800 -到这个视频的前一个版本, +可以指向这个视频的前一个版本, to the previous version of this video, 63 @@ -325,7 +325,7 @@ into your Hugging Face account, 66 00:02:56,460 --> 00:02:59,523 -通过命令行或在 Python 运行时中。 +可以通过命令行或者 Python 运行时中操作。 either through the command-line or in a Python runtime. 67 @@ -335,7 +335,7 @@ The first approach we'll take a look at 68 00:03:06,390 --> 00:03:08,880 -正在使用上传文件的方法。 +正在使用 upload_file 方法。 is using the upload file method. 69 @@ -345,12 +345,12 @@ This offers an extremely simple API 70 00:03:10,770 --> 00:03:12,630 -通过集线器上传文件。 +通过 hub 上传文件。 to upload files through the hub. 71 00:03:12,630 --> 00:03:14,190 -三个必需的参数 +其中三个必需的参数 The three required parameters 72 @@ -360,13 +360,13 @@ are the current location of the file, 73 00:03:18,570 --> 00:03:21,300 -该文件在存储库中的路径, +该文件在仓库中的路径, the path of that file in the repository, 74 00:03:21,300 --> 00:03:24,050 -以及你要推送到的存储库的想法。 -and the idea of the repository to which you're pushing. +以及你要推送到的仓库的标识符。 +and the id of the repository to which you're pushing. 75 00:03:25,650 --> 00:03:27,930 @@ -375,37 +375,37 @@ There are a few additional parameters. 76 00:03:27,930 --> 00:03:29,100 -令牌参数, +token 参数, The token parameter, 77 00:03:29,100 --> 00:03:31,200 -如果你想指定一个不同的令牌 +如果你想指定一个和登录时 if you would like to specify a different token 78 00:03:31,200 --> 00:03:33,650 -比登录时保存在缓存中的那个, +所保存的不同的 token, than the one saved in your cache with your login, 79 00:03:34,830 --> 00:03:36,750 -回购类型参数, +repo_type 参数, the repo type parameter, 80 00:03:36,750 --> 00:03:40,503 -如果你想推送到数据集或空间。 -if you would like to push to a data set or a space. +如果你想推送到 dataset 或 space。 +if you would like to push to a dataset or a space. 81 00:03:42,300 --> 00:03:45,690 -我们将上传一个名为 readme.md 的文件到存储库 +我们将使用这种方法上传一个名为 readme.md 的文件 We'll upload a file called readme.md to the repository 82 00:03:45,690 --> 00:03:47,190 -使用这种方法。 +到仓库。 using this method. 83 @@ -415,12 +415,12 @@ We first start by saving a file with that name, 84 00:03:49,710 --> 00:03:51,210 -其中包含一些信息 +其中包含一些关于 which contains some information 85 00:03:51,210 --> 00:03:52,920 -关于存储库本身。 +仓库本身的信息。 about the repository itself. 86 @@ -435,7 +435,7 @@ Now that the file is saved, 88 00:03:57,420 --> 00:04:00,513 -让我们使用上传文件方法将其上传到集线器。 +让我们使用 upload_file 方法将其上传到 hub。 let's use the upload file method to upload it to the hub. 89 @@ -455,12 +455,12 @@ The file upload was a success. 92 00:04:10,170 --> 00:04:13,500 -除了这个方法之外还有一个删除文件的方法 +除了这个方法之外还有一个 delete_file 方法 Alongside this method exists a delete file method 93 00:04:13,500 --> 00:04:16,170 -这样你就可以完全管理你的存储库。 +这样你就可以完全管理你的仓库。 so that you may manage your repository fully. 94 @@ -480,7 +480,7 @@ the file was indeed deleted. 97 00:04:29,070 --> 00:04:32,730 -这种只使用这两种方法的方法非常简单。 +这两种方法操作起来非常简单。 This approach using only these two methods is super simple. 98 @@ -515,12 +515,12 @@ let's take a look at the second method 104 00:04:45,540 --> 00:04:47,643 -这是存储库实用程序。 +这是仓库实用程序。 which is the repository utility. 105 00:04:48,600 --> 00:04:51,840 -此类是 Git 和 Git LFS 方法的包装器, +该类封装了 Git 和 Git LFS 方法, This class is a wrapper over Git and Git LFS methods, 106 @@ -535,7 +535,7 @@ and offers a flexible API 108 00:04:55,500 --> 00:04:57,990 -管理你的在线存储库。 +管理你的在线仓库。 to manage your online repositories. 109 @@ -545,22 +545,22 @@ Let's take a look at how it works. 110 00:05:03,870 --> 00:05:08,369 -我们首先从实例化存储库实用程序开始。 +我们首先从实例化仓库实用程序开始。 We first start by instantiating the repository utility. 111 00:05:08,369 --> 00:05:10,380 -我们从参数中提供克隆, +为了克隆我们刚刚创建的仓库 We provide the clone from parameter, 112 00:05:10,380 --> 00:05:13,383 -为了克隆我们刚刚创建的存储库。 +我们可以通过传递参数进行克隆。 in order to clone the repository we just created. 113 00:05:14,400 --> 00:05:18,750 -存储库现已克隆到本地文件夹中。 +仓库现已克隆到本地文件夹中。 The repository is now cloned in the local folder. 114 @@ -575,7 +575,7 @@ offers quite a few methods which are useful for us. 116 00:05:25,920 --> 00:05:28,800 -我们有兴趣将模型推送到中心。 +我们有兴趣将模型推送到 hub。 We're interested in pushing a model to the hub. 117 @@ -585,7 +585,7 @@ I'll start by loading a model and tokenizer 118 00:05:31,170 --> 00:05:32,643 -我几个小时前训练过。 +这是几个小时前训练过的。 I trained a few hours ago. 119 @@ -595,42 +595,42 @@ We'll now follow the traditional Git approach 120 00:05:36,810 --> 00:05:38,670 -首先提取最新的更改 +首先 pull 最新的更改内容 by first pulling latest changes 121 00:05:38,670 --> 00:05:40,053 -使用 Git pull 方法。 -using the Git pull method. +使用 git_pull 方法。 +using the git_pull method. 122 00:05:40,980 --> 00:05:43,170 -我们刚刚克隆了存储库, +我们刚刚克隆了仓库, We just cloned the repository, 123 00:05:43,170 --> 00:05:45,780 -所以除非这是一个超级活跃的存储库, +所以除非这是一个超级活跃的仓库, so unless this is a super active repository, 124 00:05:45,780 --> 00:05:48,660 -不太可能有新的变化可用。 +否则不太可能内容的变化。 it's unlikely that new changes are available. 125 00:05:48,660 --> 00:05:51,000 -但拉取最新的更改总是一个好主意 +但在做任何新的事情之前养成 pull 最新内容 But it's always a good idea to pull the latest changes 126 00:05:51,000 --> 00:05:52,300 -在做任何新的事情之前。 +的好习惯也是不错的。 before doing anything new. 127 00:05:53,220 --> 00:05:55,200 -现在我们已经拉取了存储库, +现在我们已经 pull 了仓库, Now that we have pulled the repository, 128 @@ -670,53 +670,53 @@ If we were using the command-line, 135 00:06:12,150 --> 00:06:14,250 -有一些 Git LFS 特定的命令 +我们将不得不调用一些 there are a few Git LFS specific commands 136 00:06:14,250 --> 00:06:15,600 -我们将不得不调用。 +特定的 Git LFS 命令。 we would have to invoke. 137 00:06:15,600 --> 00:06:17,940 -但是在这里,Hugging Face 枢纽包 +但是在这里,huggingface_hub 包 But here, the Hugging Face hub package 138 00:06:17,940 --> 00:06:20,070 -负责所有这些。 +会处理所有这些。 takes care of all of that. 139 00:06:20,070 --> 00:06:24,420 -我们将从使用 Git add 方法暂存文件开始。 -We'll start by staging the files using the Git add method. +我们将从使用 git_add 方法暂存文件开始。 +We'll start by staging the files using the git_add method. 140 00:06:24,420 --> 00:06:27,600 -然后我们将使用 Git commit 方法提交这些更改, +然后我们将使用 git_commit 方法提交这些更改, We'll then commit these changes using Git commit method, 141 00:06:27,600 --> 00:06:30,690 -并提供有用的提交信息。 +并提供有用的 commit 信息。 and providing a helpful commit message. 142 00:06:30,690 --> 00:06:33,210 -最后,我们将更改推送到远程, +最后,我们将更改推送到远端, Finally, we'll push the changes to the remote, 143 00:06:33,210 --> 00:06:34,953 -使用 Git 推送方法。 +使用 git_push 方法。 using the Git push method. 144 00:06:45,090 --> 00:06:47,430 -如果我们回到文件和版本选项卡, -If we go back to the files and versions tab, +如果我们回到 Files and Versions 选项卡, +If we go back to the Files and Versions tab, 145 00:06:47,430 --> 00:06:49,950 @@ -725,7 +725,7 @@ we can now see the newly committed files. 146 00:06:49,950 --> 00:06:52,600 -我们甚至可以在推理 API 中使用模型。 +我们甚至可以在 inference API 中使用模型。 We can even play with the model in the inference API. 147 @@ -735,7 +735,7 @@ Unfortunately, the front page of our model 148 00:06:55,770 --> 00:06:57,540 -还是很空虚。 +还是显得非常空。 is still very empty. 149 @@ -745,13 +745,13 @@ Let's add a README markdown file 150 00:06:59,280 --> 00:07:00,753 -完成它一点点。 +让整体显得完整一点点。 to complete it a little bit. 151 00:07:01,710 --> 00:07:04,200 -这个 README 被称为模型卡 -This README is known as the model card +这个 README 被称为 Model card +This README is known as the Model card 152 00:07:04,200 --> 00:07:06,030 @@ -760,12 +760,12 @@ and it's arguably as important 153 00:07:06,030 --> 00:07:09,330 -作为模型存储库中的模型和标记器文件。 +作为模型仓库中的模型和分词器文件。 as the model and tokenizer files in the model repository. 154 00:07:09,330 --> 00:07:11,280 -这是中心定义 +这是你的模型的综合定义 It is the central definition 155 @@ -795,12 +795,12 @@ may build their artifacts. 160 00:07:23,220 --> 00:07:25,590 -我们只会在此处添加标题和简短描述 +为了简单起见我们只会在此处添加标题 We'll only add a title and a small description here 161 00:07:25,590 --> 00:07:27,060 -为了简单起见, +和简短描述, for simplicity's sake, 162 @@ -810,7 +810,7 @@ but we encourage you to add information relevant 163 00:07:29,370 --> 00:07:30,990 -模型是如何训练的, +说明模型是如何训练的, to how was the model trained, 164 @@ -820,7 +820,7 @@ it's intended use and limitations, 165 00:07:33,120 --> 00:07:36,180 -以及它识别出的潜在偏见, +以及目前一直的潜在偏差, as well as it's identified potential biases, 166 @@ -835,7 +835,7 @@ and code samples on how to use your model. 168 00:07:41,460 --> 00:07:44,130 -为模型中心贡献模型的出色工作。 +为 Model Hub 贡献出色的模型。 Great work contributing a model to the Model Hub. 169 diff --git a/subtitles/zh-CN/33_the-push-to-hub-api-(pytorch).srt b/subtitles/zh-CN/33_the-push-to-hub-api-(pytorch).srt index e27897b99..475489f28 100644 --- a/subtitles/zh-CN/33_the-push-to-hub-api-(pytorch).srt +++ b/subtitles/zh-CN/33_the-push-to-hub-api-(pytorch).srt @@ -15,13 +15,13 @@ 4 00:00:05,130 --> 00:00:06,830 -- [Instructor] 所以推送到 hub API。 -- [Instructor] So push to hub API. +- [Instructor] push_to_hub API。 +- [Instructor] So push_to_hub API. 5 00:00:08,310 --> 00:00:10,533 -让我们看一下推送到集线器 API。 -Let's have a look at the push to hub API. +让我们看一下 push_to_hub API。 +Let's have a look at the push_to_hub API. 6 00:00:11,730 --> 00:00:14,640 @@ -30,12 +30,12 @@ You will need to be logged in with your Hugging Face account 7 00:00:14,640 --> 00:00:17,400 -你可以通过执行第一个单元格来做到这一点, +你可以通过执行第一个单元格里的操作来登录, which you can do by executing this first cell, 8 00:00:17,400 --> 00:00:21,123 -或者在终端中输入 huggingface-cli login。 +或者在 terminal 中输入 huggingface-cli login。 or by typing huggingface-cli login in a terminal. 9 @@ -45,37 +45,37 @@ Just enter you username and password, then click login, 10 00:00:26,640 --> 00:00:28,620 -这将存储一个通知令牌 -this will store a notification token +登录后将存储一个授权 token +this will store a authentication token 11 00:00:28,620 --> 00:00:30,670 -在你正在使用的机器的缓存中。 +到你正在使用的机器的缓存中。 in the cache of the machine you're using. 12 00:00:31,890 --> 00:00:35,790 -现在,让我们对 BERT 模型进行微调 +现在,让我们基于 GLUE COLA 数据集 Now, let's launch a fine tuning of a BERT model 13 00:00:35,790 --> 00:00:37,920 -在 GLUE COLA 数据集上。 +对 BERT 模型进行微调。 on the GLUE COLA dataset. 14 00:00:37,920 --> 00:00:39,600 -我们不会讨论微调代码 +我们不会深入探讨微调代码 We won't go over the fine tuning code 15 00:00:39,600 --> 00:00:42,270 -因为你可以在任何变压器教程中找到它, +你可以在任何 transformer 教程 because you can find it in any transformer tutorial, 16 00:00:42,270 --> 00:00:44,670 -或通过查看下面的视频链接。 +或查看下面的视频链接找到相关参考。 or by looking at the videos link below. 17 @@ -85,7 +85,7 @@ What interests us here is 18 00:00:46,470 --> 00:00:48,970 -我们如何在训练期间利用模型中心。 +如何在训练期间利用 model hub。 how we can leverage the model hub during training. 19 @@ -95,72 +95,72 @@ This is done with the "push_to_hub=true" argument 20 00:00:52,980 --> 00:00:55,530 -传入你的 TrainingArguments。 +将该参数添加到你的 TrainingArguments。 passed in your TrainingArguments. 21 00:00:55,530 --> 00:00:57,240 -这将自动上传你的模型 +每次保存时将自动上传 This will automatically upload your model 22 00:00:57,240 --> 00:00:59,400 -每次保存到集线器, +你的模型到 Hub,在我们的示例中, to the Hub each time it is saved, 23 00:00:59,400 --> 00:01:01,323 -所以我们案例中的每个时代。 +每个 epoch 都会如此操作。 so every epoch in our case. 24 00:01:02,280 --> 00:01:04,860 -这允许你从不同的机器恢复训练 +如果当前的被打断 This allows you to resume training from a different machine 25 00:01:04,860 --> 00:01:06,873 -如果当前的被打断。 +这允许你从不同的机器恢复训练之前的训练。 if the current one gets interrupted. 26 00:01:08,220 --> 00:01:10,440 -该模型将在你的名称空间中更新 -The model will be updated in your name space +该模型将使用 +The model will be updated in your namespace 27 00:01:10,440 --> 00:01:14,640 -使用你默认选择的输出目录的名称。 +你默认选择的输出目录的名称在你的 namespace 中更新。 with the name of the output directory you picked by default. 28 00:01:14,640 --> 00:01:16,020 -你可以选择其他名称 +你可以通过将其 You can choose another name 29 00:01:16,020 --> 00:01:19,113 -通过将其传递给 hub_model_id 参数。 +传递给 hub_model_id 参数选择其他名称。 by passing it to the hub_model_id argument. 30 00:01:20,070 --> 00:01:23,370 -你还可以推动你所属的组织内部 +你还可以通过传递完整的仓库名称 You can also push inside an organization you are a member of 31 00:01:23,370 --> 00:01:25,740 -通过传递完整的存储库名称, +将模型 push 到你所属的组织内部, by passing a full repository name, 32 00:01:25,740 --> 00:01:28,933 -以组织名称 /, +使用 organization/ 的形式, with the name of the organization/, 33 00:01:28,933 --> 00:01:30,433 -你要选择的型号 ID。 +再加上你所选的 model ID。 the model ID you want to pick. 34 @@ -175,38 +175,38 @@ and wait a little bit. 36 00:01:36,960 --> 00:01:39,033 -我会从视频中缩短等待时间。 +视频中会跳过等待的过程。 I'll cut the waiting time from the video. 37 00:01:43,260 --> 00:01:46,350 -请注意,模型是异步推送的, +请注意,模型是异步 push 的, Note that the model is pushed asynchronously, 38 00:01:46,350 --> 00:01:47,730 -意味着训练继续 +意味着当你的模型上传到 hub 时, meaning that the training continues 39 00:01:47,730 --> 00:01:49,730 -当你的模型上传到集线器时。 +训练将继续进行。 while your model is uploaded to the hub. 40 00:01:51,060 --> 00:01:52,950 -当你的第一次提交完成时, +当你的第一次 commit 完成时, When your first commit is finished, 41 00:01:52,950 --> 00:01:55,650 -你可以去中心检查你的模型 +你可以通过查看你的 namespace you can go inspect your model on the Hub 42 00:01:55,650 --> 00:01:57,960 -通过查看你的名称空间, -by looking inside your name space, +去 Hub 检查你的模型, +by looking inside your namespace, 43 00:01:57,960 --> 00:01:59,943 @@ -215,22 +215,22 @@ and you'll find it at the very top. 44 00:02:01,980 --> 00:02:04,200 -你甚至可以开始使用它的推理小部件 +你甚至可以在继续训练的同时 You can even start playing with its inference widget 45 00:02:04,200 --> 00:02:06,630 -在继续训练的同时。 +开始使用它的 inference 小部件。 while it's continuing the training. 46 00:02:06,630 --> 00:02:09,270 Cola 数据集让模型确定 -The Cola data set tasks the model with determining +The Cola dataset tasks the model with determining 47 00:02:09,270 --> 00:02:11,970 -如果句子在语法上是正确的。 +句子在语法上是否是正确的。 if the sentence is grammatically correct on that. 48 @@ -240,102 +240,102 @@ So we pick an example of incorrect sentence to test it. 49 00:02:15,510 --> 00:02:16,950 -请注意,这需要一些时间 +请注意,第一次尝试使用它时, Note that it'll take a bit of time 50 00:02:16,950 --> 00:02:18,750 -在推理 API 中加载模型, +这需要一些时间才能在 inference API 中 to load your model inside the inference APIs, 51 00:02:18,750 --> 00:02:20,880 -所以第一次尝试使用它。 +完成模型加载。 so first time you try to use it. 52 00:02:20,880 --> 00:02:23,280 -我们将按时间从视频中删减。 +我们将根据时间从视频中删掉。 We'll cut by time from the video. 53 00:02:23,280 --> 00:02:24,870 -标签有问题, +标签有点问题, There is something wrong with the labels, 54 00:02:24,870 --> 00:02:27,360 -但我们稍后会在本视频中修复它。 +我们稍后会在本视频中修复它。 but we'll fix it later in this video. 55 00:02:27,360 --> 00:02:29,520 -一旦你的训练结束, +一旦你的训练完成, Once your training is finished, 56 00:02:29,520 --> 00:02:31,770 -你应该和教练一起做最后一击 +你应该使用 trainer.push_to_hub 方法 you should do one last push with the trainer 57 00:02:31,770 --> 00:02:33,840 -推到一个方法。 +最后再提交一次。 that pushed to a method. 58 00:02:33,840 --> 00:02:35,430 -这是有两个原因的。 +这其中有两个原因。 This is for two reason. 59 00:02:35,430 --> 00:02:36,750 -首先,这将确保 +首先,若你尚未完成 First, this will make sure 60 00:02:36,750 --> 00:02:39,180 -你正在预测模型的最终版本 +这将确保你正在预测模型的 you are predicting the final version of your model 61 00:02:39,180 --> 00:02:40,680 -如果你还没有。 +最终版本。 if you didn't already. 62 00:02:40,680 --> 00:02:42,480 -例如,如果你曾经保存 +例如,如果你曾经是在每一步保存 For instance, if you used to save 63 00:02:42,480 --> 00:02:46,980 -每一步策略而不是每秒, +而不是每秒保存, every in step strategy instead of every second, 64 00:02:46,980 --> 00:02:48,180 -这将起草一张模型卡 +这将创建一个 model card this will draft a model card 65 00:02:48,180 --> 00:02:51,120 -那将是你的模型回购的登陆页面。 +那将是你的 model repo 的最初始页面。 that will be the landing page of your model repo. 66 00:02:51,120 --> 00:02:52,260 -提交完成后, +commit 完成后, Once the commit is done, 67 00:02:52,260 --> 00:02:54,810 -让我们回到我们的模型页面并刷新。 +让我们回到我们的 model 页面并刷新。 let's go back on our model page and refresh. 68 00:02:54,810 --> 00:02:56,820 -我们可以看到制图者模型卡 +我们可以看到 model card 的草稿 We can see the drafters model card 69 @@ -345,17 +345,17 @@ which includes information, 70 00:02:58,080 --> 00:03:00,381 -我们发现调整了哪一种模型。 +以及哪个模型被调整过。 and which one model we find tuned. 71 00:03:00,381 --> 00:03:03,570 -所以最终评估损失和指标, +接下来是最终的评估 loss 和 metric, So final evaluation loss and metric, 72 00:03:03,570 --> 00:03:06,300 -使用的训练超参数, +使用过的训练超参数, the training hyperparameter used, 73 @@ -380,42 +380,42 @@ On top of all that information, 77 00:03:16,860 --> 00:03:19,740 -培训师还包括一些解释的元数据 +trainer 还包括一些 metadata,它可以 the trainer also included some metadata that is interpreted 78 00:03:19,740 --> 00:03:22,650 -通过模型云中的 Hugging Face 网站。 +通过 model cloud 上的 HuggingFace 网站解析。 by the Hugging Face website in the model cloud. 79 00:03:22,650 --> 00:03:26,010 -你获得了一个漂亮的小部件中报告的指标的价值 +你将会得到一个漂亮的 widget 所返回的相关指标数值 You get the value of the metrics reported in a nice widget 80 00:03:26,010 --> 00:03:29,640 -以及带有代码的论文排行榜的链接。 +以及一个链接指向 leaderboard(Paper with Code)。 as well as a link to a leaderboard with paper with code. 81 00:03:29,640 --> 00:03:32,550 -所以 Tensorboard runs 也被推送了 +并且 Tensorboard 的运行结果也包含 So the Tensorboard runs have also been pushed 82 00:03:32,550 --> 00:03:34,560 -到这份报告,我们可以看看他们 +在这份报告中,我们可以在 Model Hub 中 to this report, and we can look at them 83 00:03:34,560 --> 00:03:36,000 -直接从模型中心 +通过点击子菜单中的 directly from the model hub 84 00:03:36,000 --> 00:03:38,850 -通过单击训练指标子菜单。 +Training metrics 查看报告。 by clicking on the training metrics sub menu. 85 @@ -430,52 +430,52 @@ to fine-tune your model, 87 00:03:42,510 --> 00:03:43,770 -你可以使用 push_to_hub 方法 +你可以在模型上直接 you can use a push_to_hub method 88 00:03:43,770 --> 00:03:46,427 -在模型上,并直接标记器。 +使用 push_to_hub 方法和分词器。 on the model, and tokenizer directly. 89 00:03:46,427 --> 00:03:50,160 -让我们测试一下以修复推理小部件中的所有标签。 +让我们测试一下以修复 inference widget 中的所有标签。 Let's test this to fix all labels in the inference widget. 90 00:03:50,160 --> 00:03:52,740 -推理小部件使用不同的标签名称 +inference widget 使用不同的标签名称 The inference widget was using different names for labels 91 00:03:52,740 --> 00:03:54,810 -因为我们没有注明对应 +因为我们没有在整数和标签名称之间 because we did not indicate the correspondence 92 00:03:54,810 --> 00:03:57,030 -在整数和标签名称之间。 +注明关联性。 between integer and label names. 93 00:03:57,030 --> 00:03:58,740 -我们可以在配置中解决这个问题 +当推送模型配置到 hub 时, We can fix this in the configuration 94 00:03:58,740 --> 00:04:01,350 -通过坐在 label2id, -by sitting the label2id, +我们可以通过将 label2id +by setting the label2id, 95 00:04:01,350 --> 00:04:04,170 -和 id2label 字段通过适当的值 +和 id2label 字段设置为合适的值 and id2label fields through the proper values 96 00:04:04,170 --> 00:04:06,933 -将模型配置推送到集线器时。 +在配置中解决这个问题。 when pushing the model config to the hub. 97 @@ -490,7 +490,7 @@ and the model is now showing the proper label. 99 00:04:13,380 --> 00:04:15,240 -现在模型在集线器上, +现在模型在 hub 上, Now that the model is on the hub, 100 @@ -500,7 +500,7 @@ we can use it from anywhere 101 00:04:17,370 --> 00:04:19,920 -就像我们对任何其他 Transformer 模型一样 +就像我们对任何其他 Transformer 模型 as we would any other Transformer model 102 @@ -510,12 +510,12 @@ with the from_pretrained method 103 00:04:21,113 --> 00:04:22,923 -具有管道功能。 -of with the pipeline function. +或者使用 pipeline 函数。 +or with the pipeline function. 104 00:04:34,350 --> 00:04:36,780 -我们只需要使用集线器的标识符, +我们只需要使用 hub 的标识符, We just have to use the identifier from the hub, 105 @@ -525,12 +525,12 @@ and we can see that the model configuration and weights 106 00:04:39,450 --> 00:04:42,483 -以及标记化的文件会自动下载。 +以及分词处理后的文件会自动下载。 as well as the tokenized files are automatically downloaded. 107 00:04:53,880 --> 00:04:55,950 -在下一次培训中尝试 push_to_hub API +在下一次训练中尝试 push_to_hub API Try the push_to_hub API in the next training 108 diff --git a/subtitles/zh-CN/34_the-push-to-hub-api-(tensorflow).srt b/subtitles/zh-CN/34_the-push-to-hub-api-(tensorflow).srt index e05f61932..fe6d2061c 100644 --- a/subtitles/zh-CN/34_the-push-to-hub-api-(tensorflow).srt +++ b/subtitles/zh-CN/34_the-push-to-hub-api-(tensorflow).srt @@ -5,37 +5,37 @@ 2 00:00:05,100 --> 00:00:07,080 -- [旁白] 嗨,这将是一个视频 +- [旁白] 嗨,本视频将带领大家了解 - [Narrator] Hi, this is going to be a video 3 00:00:07,080 --> 00:00:09,420 -关于 push_to_hub API +push_to_hub API 适用于 about the push_to_hub API 4 00:00:09,420 --> 00:00:10,670 -适用于 Tensorflow 和 Keras。 +Tensorflow 和 Keras 的版本。 for Tensorflow and Keras. 5 00:00:11,820 --> 00:00:14,850 -因此,首先,我们将打开我们的笔记本。 +那么首先,打开我们的 jupyter。 So, to get started, we'll open up our notebook. 6 00:00:14,850 --> 00:00:16,920 -你需要做的第一件事就是登录 +你需要做的第一件事就是 And the first thing you'll need to do is log in to 7 00:00:16,920 --> 00:00:18,170 -你的 HuggingFace 帐户, +登录你的 HuggingFace 帐户, your HuggingFace account, 8 00:00:19,043 --> 00:00:20,663 -例如笔记本登录功能。 +例如调用 notebook_login 函数。 for example with the notebook login function. 9 @@ -45,22 +45,22 @@ So to use that, you simply call the function, 10 00:00:24,630 --> 00:00:26,010 -弹出窗口将出现。 +随后会弹出窗口。 the popup will emerge. 11 00:00:26,010 --> 00:00:28,800 -你将输入你的用户名和密码, +你需要输入你的用户名和密码, You will enter your username and password, 12 00:00:28,800 --> 00:00:31,425 -我要在这里从我的密码管理器中取出, +这里我使用密码管理器拷贝密码, which I'm going to pull out of my password manager here, 13 00:00:31,425 --> 00:00:33,108 -然后你登录。 +然后就登录了。 and you log in. 14 @@ -70,7 +70,7 @@ The next two cells are just 15 00:00:35,670 --> 00:00:37,080 -为训练做好一切准备。 +为训练做好准备。 getting everything ready for training. 16 @@ -80,22 +80,22 @@ So we're just going to load a dataset, 17 00:00:38,940 --> 00:00:41,100 -我们要标记那个数据集, +我们要对数据集进行分词, we're going to tokenize that dataset, 18 00:00:41,100 --> 00:00:42,990 -然后我们将加载我们的模型并编译 +然后我们将加载我们的模型并 and then we're going to load our model and compile 19 00:00:42,990 --> 00:00:45,660 -它与标准的 Adam 优化器。 +使用标准 Adam 优化器进行编译。 it with the standard Adam optimizer. 20 00:00:45,660 --> 00:00:47,560 -所以我将运行所有这些。 +现在我们会运行这些代码。 So I'm just going to run all of those. 21 @@ -135,22 +135,22 @@ So the first is with the PushToHubCallback. 28 00:01:08,190 --> 00:01:10,107 -所以在 Keras 中回调 +所以 Keras 中的回调 So a callback in Keras 29 00:01:10,107 --> 00:01:13,710 -是在训练期间定期调用的函数。 +也会在训练过程中被频繁调用。 is a function that's called regularly during training. 30 00:01:13,710 --> 00:01:17,400 -你可以将其设置为在一定数量的步骤后调用, +你可以将其设置为在一定数量的 step 后或者在每个 epoch 被调用, You can set it to be called after a certain number of steps, 31 00:01:17,400 --> 00:01:21,427 -或每个时期,甚至只是在训练结束时一次。 +甚至只是在训练结束时调用一次。 or every epoch, or even just once at the end of training. 32 @@ -160,7 +160,7 @@ So a lot of callbacks in Keras, for example, 33 00:01:25,080 --> 00:01:28,050 -控制学习率在高原上衰减, +在平稳期控制学习率衰减, control learning rate decaying on plateau, 34 @@ -175,12 +175,12 @@ So this callback, by default, 36 00:01:32,520 --> 00:01:35,760 -每个时期都会将你的模型保存到 Hub 一次。 +在每个 epoch 都会将你的模型保存到 Hub 一次。 will save your model to the Hub once every epoch. 37 00:01:35,760 --> 00:01:37,080 -这真的很有帮助, +这真的非常有用, And that's really helpful, 38 @@ -190,27 +190,27 @@ especially if your training is very long, 39 00:01:38,790 --> 00:01:40,800 -因为那意味着你可以从那个保存中恢复, +因为那意味着你可以从那个存储中恢复, because that means you can resume from that save, 40 00:01:40,800 --> 00:01:43,290 -所以你得到了你的模型的自动云保存。 +所以你可以实现将你的模型进行自动云存储。 so you get this automatic cloud-saving of your model. 41 00:01:43,290 --> 00:01:45,027 -你甚至可以进行推理 +你甚至可以利用 And you can even run inference 42 00:01:45,027 --> 00:01:47,730 -使用模型的检查点 +该回调所上传的模型存储点(checkpoint) with the checkpoints of your model 43 00:01:47,730 --> 00:01:50,208 -已通过此回调上传。 +进行推断过程。 that have been uploaded by this callback. 44 @@ -220,17 +220,17 @@ And that means you can, 45 00:01:52,260 --> 00:01:54,150 -你知道,运行一些测试输入 +你懂的,运行一些测试输入 y'know, run some test inputs 46 00:01:54,150 --> 00:01:56,100 -并实际查看你的模型是如何工作的 +并实际查看你的模型在训练的各个阶段 and actually see how your model works 47 00:01:56,100 --> 00:01:57,990 -在训练的各个阶段, +是如何工作的, at various stages during training, 48 @@ -250,17 +250,17 @@ and it takes just a few arguments. 51 00:02:05,670 --> 00:02:08,250 -所以第一个参数是临时目录 +第一个参数是临时目录 So the first argument is the temporary directory 52 00:02:08,250 --> 00:02:10,260 -该文件将被保存到 +文件在上传到 Hub 之前 that files are going to be saved to 53 00:02:10,260 --> 00:02:12,150 -在将它们上传到 Hub 之前。 +将被保存到该目录。 before they're uploaded to the Hub. 54 @@ -280,12 +280,12 @@ is the keyword argument hub_model_id. 57 00:02:19,080 --> 00:02:21,330 -所以这就是它将被保存的名称 +也就是在 HuggingFace Hub 上 So that's the name it's going to be saved under 58 00:02:21,330 --> 00:02:23,006 -在 HuggingFace Hub 上。 +它将被保存的名称。 on the HuggingFace Hub. 59 @@ -295,12 +295,12 @@ You can also upload to an organization account 60 00:02:26,267 --> 00:02:29,370 -只需添加组织名称 +只需在带有斜杠的仓库名称之前 just by adding the organization name 61 00:02:29,370 --> 00:02:32,460 -在带有斜杠的存储库名称之前,就像这样。 +添加组织名称,就像这样。 before the repository name with a slash, like this. 62 @@ -315,17 +315,17 @@ to upload to the HuggingFace organization, 64 00:02:36,000 --> 00:02:37,170 -如果你这样做请提交错误 +如果你有权限可以上传, if you do please file a bug 65 00:02:37,170 --> 00:02:38,973 -并非常紧急地通知我们。 +请提交 bug 并尽快通知我们。 and let us know extremely urgently. 66 00:02:40,830 --> 00:02:42,960 -但如果你确实有权访问你自己的组织, +但如果你确实有权限访问你自己的组织, But if you do have access to your own organization, 67 @@ -345,22 +345,22 @@ instead of to your own personal set of models. 70 00:02:50,760 --> 00:02:53,520 -所以,一旦你回电了, +所以,一旦你实现了回调, So, once you've made your callback, 71 00:02:53,520 --> 00:02:56,310 -你只需将它添加到回调列表 +你只需在调用 model.fit 时 you simply add it to the callbacks list 72 00:02:56,310 --> 00:02:58,080 -当你调用 model.fit 时。 +将它添加到回调列表。 when you're calling model.fit. 73 00:02:58,080 --> 00:03:01,110 -一切都从那里为你上传, +所有内容均为从那里上传, And everything is uploaded for you from there, 74 @@ -395,7 +395,7 @@ You can just call this manually whenever you want to 80 00:03:13,680 --> 00:03:15,240 -将模型上传到集线器。 +将模型上传到 hub。 upload a model to the hub. 81 @@ -405,37 +405,37 @@ So we recommend running this after the end of training, 82 00:03:18,949 --> 00:03:21,870 -只是为了确保你有一条提交消息 +只是为了确保你有一条 commit 消息 just to make sure that you have a commit message 83 00:03:21,870 --> 00:03:24,060 -保证这是最终版本 +保证这是训练结束时 to guarantee that this was the final version 84 00:03:24,060 --> 00:03:26,143 -训练结束时的模型。 +模型的最终版本。 of the model at the end of training. 85 00:03:26,143 --> 00:03:27,930 -它只是确保,你知道, +它只是为了确保,你懂的, And it just makes sure that, you know, 86 00:03:27,930 --> 00:03:30,480 -你正在使用最终的训练结束模型 +你当前正在使用的是最终训练结束的模型 you're working with the definitive end-of-training model 87 00:03:30,480 --> 00:03:32,190 -而不是不小心使用检查点 +而不是意外从某个地方拿到的 and not accidentally using a checkpoint 88 00:03:32,190 --> 00:03:34,224 -从沿途的某个地方。 +模型的某个存储点的版本。 from somewhere along the way. 89 @@ -455,7 +455,7 @@ just because training is going to take a couple of minutes. 92 00:03:43,080 --> 00:03:44,580 -所以我会跳到最后, +所以我会直接跳到最后, So I'll skip forward to the end of that, 93 @@ -465,17 +465,17 @@ when the models have all been uploaded, 94 00:03:46,320 --> 00:03:48,390 -我会告诉你怎么做 +我会告诉你 and I'm gonna show you how you can 95 00:03:48,390 --> 00:03:50,010 -访问 Hub 中的模型, +如何访问 Hub 中的模型, access the models in the Hub, 96 00:03:50,010 --> 00:03:52,713 -以及你可以从那里用它们做的其他事情。 +以及其他能够在那里利用模型做到的事情。 and the other things you can do with them from there. 97 @@ -505,17 +505,17 @@ So everything's looking good. 102 00:04:05,910 --> 00:04:09,960 -所以现在如果我们转到我在 HuggingFace 上的个人资料, +那么现在如果我们转到我在 HuggingFace 上的个人资料, So now if we drop over to my profile on HuggingFace, 103 00:04:09,960 --> 00:04:12,630 -你只需点击个人资料按钮就可以到达那里 +你只需点击 profile 按钮就可以 and you can get there just by clicking the profile button 104 00:04:12,630 --> 00:04:13,680 -在下拉列表中。 +在下拉列表中打开。 in the dropdown. 105 @@ -555,52 +555,52 @@ is the Glue CoLA dataset, 112 00:04:34,320 --> 00:04:36,210 -CoLA 是代表 +CoLA 是 Corpus of Linguistic Acceptability(语言可接受性语料库) and CoLA is an acronym standing for 113 00:04:36,210 --> 00:04:39,420 -语言可接受性语料库。 +的首字母缩写。 the Corpus of Linguistic Acceptability. 114 00:04:39,420 --> 00:04:42,480 -所以这意味着正在训练模型来决定 +所以这意味着正在训练模型用来决定 So what that means is the model is being trained to decide 115 00:04:42,480 --> 00:04:46,350 -如果一个句子在语法或语言上没问题, +一个句子在语法或语言上是正确的呢, if a sentence is grammatically or linguistically okay, 116 00:04:46,350 --> 00:04:48,171 -或者它是否有问题。 +还是有问题的呢。 or if there's a problem with it. 117 00:04:48,171 --> 00:04:52,890 -例如,我们可以说,“这是一个合法的句子。” +例如,我们可以说,“This is a legitimate sentence.” For example, we could say, "This is a legitimate sentence." 118 00:04:52,890 --> 00:04:54,180 -希望它意识到 +并且希望它判断 And hopefully it realizes that 119 00:04:54,180 --> 00:04:56,080 -这实际上是一个合法的判决。 +这实际上是一个合理的句子。 this is in fact a legitimate sentence. 120 00:04:57,630 --> 00:05:00,240 -所以加载模型可能需要几秒钟 +所以当你第一次调用模型时, So it might take a couple of seconds for the model to load 121 00:05:00,240 --> 00:05:03,060 -当你第一次调用它时。 +加载模型可能需要几秒钟。 when you call it for the first time. 122 @@ -615,7 +615,7 @@ Okay, we're back. 124 00:05:09,060 --> 00:05:12,407 -所以模型加载了,我们得到了一个输出, +所以模型加载了,我们得到了一个输出结果, So the model loaded and we got an output, 125 @@ -625,17 +625,17 @@ but there's an obvious problem here. 126 00:05:14,340 --> 00:05:16,888 -所以这些标签并没有真正告诉我们 +这些标签并没有真正告诉我们 So these labels aren't really telling us 127 00:05:16,888 --> 00:05:19,740 -模型实际分配了哪些类别 +模型实际为这个输入的句子 what categories the model has actually assigned 128 00:05:19,740 --> 00:05:21,655 -到这个输入句子。 +分配了哪些类别。 to this input sentence. 129 @@ -650,7 +650,7 @@ we want to make sure the model config 131 00:05:26,010 --> 00:05:28,980 -每个标签类别都有正确的名称, +针对每个标签类别都有正确的名称, has the correct names for each of the label classes, 132 @@ -660,7 +660,7 @@ and then we want to upload that config. 133 00:05:30,707 --> 00:05:32,220 -所以我们可以在这里做。 +所以我们可以在这里实现。 So we can do that down here. 134 @@ -675,7 +675,7 @@ we can get that from the dataset we loaded, 136 00:05:36,547 --> 00:05:39,627 -从它具有的特性属性。 +它其中包含特性属性。 from the features attribute it has. 137 @@ -690,7 +690,7 @@ And then we can create dictionaries 139 00:05:44,865 --> 00:05:47,452 -并将它们分配给模型配置。 +并将它们设置到模型配置中。 and just assign them to the model config. 140 @@ -700,7 +700,7 @@ And then we can just push our updated config, 141 00:05:50,790 --> 00:05:54,690 -这将覆盖 Hub 存储库中的现有配置。 +这将覆盖 Hub 仓库中的现有配置。 and that'll override the existing config in the Hub repo. 142 @@ -710,7 +710,7 @@ So that's just been done. 143 00:05:56,368 --> 00:05:58,320 -所以现在,如果我们回到这里, +那么现在,如果我们回到这里, So now, if we go back here, 144 @@ -725,7 +725,7 @@ because the outputs for sentences are sometimes cached. 146 00:06:03,540 --> 00:06:06,030 -所以,如果我们想产生新的结果 +所以,如果我们想生成新的结果 And so, if we want to generate new results 147 @@ -735,7 +735,7 @@ I'm going to use something slightly different. 148 00:06:07,590 --> 00:06:09,783 -因此,让我们尝试一个不正确的句子。 +那么,让我们尝试换一个不正确的句子。 So let's try an incorrect sentence. 149 @@ -745,7 +745,7 @@ So this is not valid English grammar 150 00:06:13,538 --> 00:06:15,030 -希望模型能看到这一点。 +希望模型能发现这一点。 and hopefully the model will see that. 151 @@ -755,12 +755,12 @@ It's going to reload here, 152 00:06:16,958 --> 00:06:18,630 -所以我要在这里缩短几秒钟, +所以在这里稍微快几秒, so I'm going to cut a couple of seconds here, 153 00:06:18,630 --> 00:06:20,933 -然后我们会看到模型会说什么。 +然后我们会看到模型会返回什么结果。 and then we'll see what the model is going to say. 154 @@ -770,12 +770,12 @@ Okay. 155 00:06:23,820 --> 00:06:26,580 -所以这个模型,它的信心不是很好, +所以这个模型,它的置信度不是很好, So the model, it's confidence isn't very good, 156 00:06:26,580 --> 00:06:28,830 -因为我们当然没有真正优化 +因为我们还没有真正优化 because of course we didn't really optimize 157 @@ -785,22 +785,22 @@ our hyperparameters at all. 158 00:06:30,630 --> 00:06:32,190 -但它决定了这句话 +但它返回的结果显示这句话 But it has decided that this sentence 159 00:06:32,190 --> 00:06:35,094 -不可接受的可能性大于可接受的可能性。 +不可接受的程度大于可接受的程度。 is more likely to be unacceptable than acceptable. 160 00:06:35,094 --> 00:06:38,160 -大概如果我们更努力地训练 +如果我们更努力地训练 Presumably if we tried a bit harder with training 161 00:06:38,160 --> 00:06:40,080 -我们可以获得更低的验证损失, +我们可以获得更低的验证集 loss, we could get a much lower validation loss, 162 @@ -835,12 +835,12 @@ So let's try, "This is a valid English sentence". 168 00:07:00,150 --> 00:07:02,100 -我们看到现在模型正确地决定了 +我们看到现在模型的结果是正确的 And we see that now the model correctly decides 169 00:07:02,100 --> 00:07:04,290 -它被接受的可能性非常高, +它的可接受度非常高, that it has a very high probability of being acceptable, 170 @@ -850,42 +850,42 @@ and a very low probability of being unacceptable. 171 00:07:06,900 --> 00:07:09,930 -所以你可以使用这个推理 API +所以你可以使用这个 inference API So you can use this inference API 172 00:07:09,930 --> 00:07:12,810 -即使有训练期间上传的检查点, +甚至可用于训练期间上传的存储点, even with the checkpoints that are uploaded during training, 173 00:07:12,810 --> 00:07:14,546 -所以看看如何 +能够看到在每个训练的 epoch 时 so it can be very interesting to see how 174 00:07:14,546 --> 00:07:17,690 -模型对样本输入的预测发生变化 +不同的样本输入所输出的预测结果 the model's predictions for sample inputs change 175 00:07:17,690 --> 00:07:20,579 -在每个训练阶段。 +也是非常有意思的。 with each epoch of training. 176 00:07:20,579 --> 00:07:23,370 -另外,我们上传的模型 +另外,你也可以访问 Also, the model we've uploaded 177 00:07:23,370 --> 00:07:25,740 -你将可以访问,并且 +我们上传的模型 is going to be accessible to you and, 178 00:07:25,740 --> 00:07:28,046 -如果公开分享给其他任何人。 +如果公开分享,其它任何人都可以访问。 if it's shared publicly, to anyone else. 179 @@ -895,17 +895,17 @@ So if you want to load that model, 180 00:07:29,788 --> 00:07:32,500 -你或其他任何人需要做的一切 +你或其他任何人所需要做的 all you or anyone else needs to do 181 00:07:34,290 --> 00:07:37,440 -只是将它加载到管道中, +就是将它加载到 pipeline, is just to load it in either a pipeline, 182 00:07:37,440 --> 00:07:40,925 -或者你可以加载它,例如, +或者你可以使用其它方式加载它,例如, or you can just load it with, for example, 183 @@ -915,12 +915,12 @@ TFAutoModelForSequenceClassification. 184 00:07:46,920 --> 00:07:49,989 -然后对于名称,你只需通过 +然后对于名称,你只需传入 And then for the name you would just simply pass 185 00:07:49,989 --> 00:07:53,325 -你要上传的存储库的路径。 +想要上传的 repo 的路径即可。 the path to the repo you want to upload. 186 @@ -935,12 +935,12 @@ So if I want to use this model again, 188 00:07:58,710 --> 00:08:00,667 -如果我想从集线器加载它, +如果我想从 hub 加载它, if I want to load it from the hub, 189 00:08:00,667 --> 00:08:01,763 -我只是运行这一行代码。 +仅需运行这一行代码。 I just run this one line of code. 190 @@ -965,42 +965,42 @@ or do anything else you wanna do. 194 00:08:14,340 --> 00:08:17,700 -所以这是对如何的快速概述, +以上内容就是关于 So that was a quick overview of how, 195 00:08:17,700 --> 00:08:19,470 -在你训练之后或训练期间, +在你训练期间或者训练之后 after your training or during your training, 196 00:08:19,470 --> 00:08:21,420 -你可以将模型上传到 Hub, +如何将模型上传到 Hub, you can upload models to the Hub, 197 00:08:21,420 --> 00:08:22,440 -你可以在那里检查站, +可以添加存储点, you can checkpoint there, 198 00:08:22,440 --> 00:08:24,240 -你可以从那里恢复训练, +可以恢复训练, you can resume training from there, 199 00:08:24,240 --> 00:08:26,790 -你可以得到推理结果 +以及从所上传模型 and you can get inference results 200 00:08:26,790 --> 00:08:28,384 -来自你上传的模型。 +获得推理结果。 from the models you've uploaded. 201 00:08:28,384 --> 00:08:31,084 -所以谢谢你,我希望在以后的视频中见到你。 +谢谢大家,希望在以后的视频再会。 So thank you, and I hope to see you in a future video. 202 diff --git a/subtitles/zh-CN/35_loading-a-custom-dataset.srt b/subtitles/zh-CN/35_loading-a-custom-dataset.srt index fd3a794e2..7e6c8815f 100644 --- a/subtitles/zh-CN/35_loading-a-custom-dataset.srt +++ b/subtitles/zh-CN/35_loading-a-custom-dataset.srt @@ -20,8 +20,8 @@ 5 00:00:08,430 --> 00:00:09,750 -尽管 Hugging Face Hub 主持 -Although the Hugging Face Hub hosts +尽管 Hugging Face Hub 上承载了 +Although the HuggingFace Hub hosts 6 00:00:09,750 --> 00:00:11,730 @@ -30,27 +30,27 @@ over a thousand public datasets, 7 00:00:11,730 --> 00:00:12,930 -你经常需要处理数据 +你可能仍然需要经常处理存储在你的笔记本电脑 you'll often need to work with data 8 00:00:12,930 --> 00:00:15,900 -存储在你的笔记本电脑或某些远程服务器上。 +或存储在远程服务器上的数据。 that is stored on your laptop or some remote server. 9 00:00:15,900 --> 00:00:18,060 -在本视频中,我们将探讨数据集库如何 +在本视频中,我们将探讨如何利用 Datasets 库 In this video, we'll explore how the Datasets library 10 00:00:18,060 --> 00:00:20,310 -可用于加载不可用的数据集 +加载 Hugging Face Hub 以外 can be used to load datasets that aren't available 11 00:00:20,310 --> 00:00:21,510 -在 Hugging Face Hub 上。 +的数据集。 on the Hugging Face Hub. 12 @@ -75,22 +75,22 @@ To load a dataset in one of these formats, 16 00:00:31,200 --> 00:00:32,730 -你只需要提供格式的名称 +你只需要向 load_dataset 函数 you just need to provide the name of the format 17 00:00:32,730 --> 00:00:34,350 -到 load_dataset 函数, +提供格式的名称, to the load_dataset function, 18 00:00:34,350 --> 00:00:35,790 -连同 data_files 参数 +并且连同 data_files 参数一起传入 along with a data_files argument 19 00:00:35,790 --> 00:00:37,610 -指向一个或多个文件路径或 URL。 +该参数指向一个或多个文件路径或 URL。 that points to one or more filepaths or URLs. 20 @@ -105,7 +105,7 @@ In this example, we first download a dataset 22 00:00:45,960 --> 00:00:48,963 -关于来自 UCI 机器学习库的葡萄酒质量。 +该数据集是来自 UCI 机器学习库的葡萄酒质量数据。 about wine quality from the UCI machine learning repository. 23 @@ -150,7 +150,7 @@ so here we've also specified 31 00:01:06,750 --> 00:01:09,030 -分隔符是分号。 +分号作为分隔符。 that the separator is a semi-colon. 32 @@ -165,7 +165,7 @@ is loaded automatically as a DatasetDict object, 34 00:01:13,020 --> 00:01:15,920 -CSV 文件中的每一列都表示为一个特征。 +CSV 文件中的每一列都代表一个特征。 with each column in the CSV file represented as a feature. 35 @@ -175,7 +175,7 @@ If your dataset is located on some remote server like GitHub 36 00:01:20,280 --> 00:01:22,050 -或其他一些存储库, +或其他一些数据仓库, or some other repository, 37 @@ -205,12 +205,12 @@ This format is quite common in NLP, 42 00:01:35,100 --> 00:01:36,750 -你通常会找到书籍和戏剧 +你常常会发现书籍和戏剧 and you'll typically find books and plays 43 00:01:36,750 --> 00:01:39,393 -只是一个包含原始文本的文件。 +只是一个包含原始文本的独立文件。 are just a single file with raw text inside. 44 @@ -220,7 +220,7 @@ In this example, we have a text file of Shakespeare plays 45 00:01:43,020 --> 00:01:45,330 -存储在 GitHub 存储库中。 +存储在 GitHub 仓库中。 that's stored on a GitHub repository. 46 @@ -245,12 +245,12 @@ As you can see, these files are processed line-by-line, 50 00:01:55,110 --> 00:01:57,690 -所以原始文本中的空行也被表示 +所以原始文本中的空行 so empty lines in the raw text are also represented 51 00:01:57,690 --> 00:01:58,953 -作为数据集中的一行。 +也按照数据集中的一行表示。 as a row in the dataset. 52 @@ -270,12 +270,12 @@ where every row in the file is a separate JSON object. 55 00:02:09,510 --> 00:02:11,100 -对于这些文件,你可以加载数据集 +对于这些文件,你可以通过选择 JSON 加载脚本 For these files, you can load the dataset 56 00:02:11,100 --> 00:02:13,020 -通过选择 JSON 加载脚本 +来加载数据集 by selecting the JSON loading script 57 @@ -285,12 +285,12 @@ and pointing the data_files argument to the file or URL. 58 00:02:17,160 --> 00:02:19,410 -在这个例子中,我们加载了一个 JSON 行文件 +在这个例子中,我们加载了一个多行 JSON 的文件 In this example, we've loaded a JSON lines files 59 00:02:19,410 --> 00:02:21,710 -基于 Stack Exchange 问题和答案。 +其内容基于 Stack Exchange 问题和答案。 based on Stack Exchange questions and answers. 60 @@ -310,27 +310,27 @@ so the load_dataset function allow you to specify 63 00:02:31,200 --> 00:02:32,733 -要加载哪个特定密钥。 +要加载哪个特定关键词。 which specific key to load. 64 00:02:33,630 --> 00:02:35,910 -例如,用于问答的 SQuAD 数据集 +例如,用于问答的 SQuAD 数据集有它的格式, For example, the SQuAD dataset for question and answering 65 00:02:35,910 --> 00:02:38,340 -有它的格式,我们可以通过指定来加载它 +我们可以通过指定我们感兴趣的数据字段 has its format, and we can load it by specifying 66 00:02:38,340 --> 00:02:40,340 -我们对数据字段感兴趣。 +我们对 data 字段感兴趣。 that we're interested in the data field. 67 00:02:41,400 --> 00:02:42,780 -最后一件事要提 +最后要和大家分享的内容是 There is just one last thing to mention 68 @@ -340,7 +340,7 @@ about all of these loading scripts. 69 00:02:44,910 --> 00:02:46,410 -你可以有不止一次分裂, +你可以有不止一次数据切分, You can have more than one split, 70 @@ -350,7 +350,7 @@ you can load them by treating data files as a dictionary, 71 00:02:49,080 --> 00:02:52,140 -并将每个拆分名称映射到其对应的文件。 +并将每个拆分的名称映射到其对应的文件。 and map each split name to its corresponding file. 72 @@ -360,22 +360,22 @@ Everything else stays completely unchanged 73 00:02:53,970 --> 00:02:55,350 -你可以看到一个加载的例子 +你可以看到一个例子, and you can see an example of loading 74 00:02:55,350 --> 00:02:58,283 -此 SQuAD 的训练和验证拆分均在此处。 +加载此 SQuAD 的训练和验证分解步骤都在这里。 both the training and validation splits for this SQuAD here. 75 00:02:59,550 --> 00:03:02,310 -这样,你现在可以从笔记本电脑加载数据集, +这样,你现在可以加载来自笔记本电脑的数据集,来自 Hugging Face Hub 的数据集, And with that, you can now load datasets from your laptop, 76 00:03:02,310 --> 00:03:04,653 -Hugging Face Hub,或任何其他地方。 +或来自任何其他地方的数据集。 the Hugging Face Hub, or anywhere else want. 77 diff --git "a/subtitles/zh-CN/37_datasets-+-dataframes-=-\342\235\244\357\270\217.srt" "b/subtitles/zh-CN/37_datasets-+-dataframes-=-\342\235\244\357\270\217.srt" index 2964fc8ab..383793324 100644 --- "a/subtitles/zh-CN/37_datasets-+-dataframes-=-\342\235\244\357\270\217.srt" +++ "b/subtitles/zh-CN/37_datasets-+-dataframes-=-\342\235\244\357\270\217.srt" @@ -15,7 +15,7 @@ 4 00:00:05,340 --> 00:00:07,833 -- 数据集和数据帧等于爱。 +- 数据集加上数据帧等于真爱。 - Datasets and DataFrames equals love. 5 @@ -30,32 +30,32 @@ will cover most of the cases needed to train a model, 7 00:00:14,040 --> 00:00:15,660 -有时你需要切换到图书馆 +有时你需要切换到其它库 there are times when you'll need to switch to a library 8 00:00:15,660 --> 00:00:18,240 -像 Pandas 一样访问更强大的功能 +比如 Pandas 来获得更强大的功能 like Pandas to access more powerful features 9 00:00:18,240 --> 00:00:20,970 -或用于可视化的高级 API。 +或针对计算机视觉使用更高级的 API。 or high level APIs for visualization. 10 00:00:20,970 --> 00:00:23,220 -幸运的是,Datasets 库是专为 +幸运的是,Datasets 库是专门为了 Fortunately, the Datasets library is designed 11 00:00:23,220 --> 00:00:25,710 -与 Pandas 等库互操作, +和外部库进行互操作所设计的,比如 Pandas to be interoperable with libraries like Pandas, 12 00:00:25,710 --> 00:00:29,790 -以及 NumPy、PyTorch、TensorFlow 和 JAX。 +此外还有 NumPy、PyTorch、TensorFlow 和 JAX。 as well as NumPy, PyTorch, TensorFlow and JAX. 13 @@ -65,12 +65,12 @@ In this video, we'll take a look 14 00:00:30,930 --> 00:00:32,550 -我们如何快速切换数据 +我们如何将数据格式快速切换到 Pandas DataFrames at how we can quickly switch our data 15 00:00:32,550 --> 00:00:34,263 -到 Pandas DataFrames 并返回。 +并且再切换回来。 to Pandas DataFrames and back. 16 @@ -85,12 +85,12 @@ Supreme Court cases from Switzerland. 18 00:00:40,830 --> 00:00:43,020 -像往常一样,我们从集线器下载我们的数据集 +像往常一样,我们使用 load_dataset 函数 As usual, we download our dataset from the hub 19 00:00:43,020 --> 00:00:44,940 -使用 load_dataset 函数。 +从 hub 下载我们的数据集。 using the load_dataset function. 20 @@ -100,12 +100,12 @@ And you can see that the first element of the training set 21 00:00:46,980 --> 00:00:48,510 -是一个普通的 Python 字典 +是一个普通的 Python 字典类型 is an ordinary Python dictionary 22 00:00:48,510 --> 00:00:50,110 -与各种兴趣领域。 +里面包含各种我们需要的字段。 with various fields of interest. 23 @@ -115,12 +115,12 @@ Now, suppose that before we train any models, 24 00:00:53,670 --> 00:00:55,590 -我们想稍微探索一下数据。 +我们想稍微浏览一下数据。 we'd like to explore the data a bit. 25 00:00:55,590 --> 00:00:57,390 -例如,我们可能有兴趣知道 +例如,我们可能希望知道 For example, we might be interested in knowing 26 @@ -135,17 +135,17 @@ or we might wanna know how the languages 28 00:01:01,380 --> 00:01:02,930 -分布在各个地区。 +在各个地区是如何分布的。 are distributed across regions. 29 00:01:04,500 --> 00:01:05,333 -回答这些问题 +使用原生 Arrow 类型 Answering these questions 30 00:01:05,333 --> 00:01:07,530 -使用原生 Arrow 格式并不容易, +很难回答这些问题, with the native Arrow format isn't easy, 31 @@ -155,7 +155,7 @@ but we can quickly switch to Pandas to get our answers. 32 00:01:10,500 --> 00:01:13,500 -它的工作方式是通过使用 set_format 方法, +通过使用 set_format 方法即可实现, The way this works is that by using the set_format method, 33 @@ -165,7 +165,7 @@ we will change the output format of the dataset 34 00:01:15,480 --> 00:01:18,930 -从 Python 字典到 Pandas DataFrame。 +从 Python 字典类型到 Pandas DataFrame 类型。 from Python dictionaries to Pandas DataFrames. 35 @@ -185,37 +185,37 @@ so we can slice the whole dataset 38 00:01:24,540 --> 00:01:26,583 -获取语料库的单个 DataFrame。 +来获取语料库的单个 DataFrame。 to get a single DataFrame of the corpus. 39 00:01:28,080 --> 00:01:29,520 -这在引擎盖下的工作方式, +其底层工作原理 The way this works under the hood, 40 00:01:29,520 --> 00:01:31,080 -是数据集库发生了变化 +是 datasets 库改变了 is that the datasets library changes 41 00:01:31,080 --> 00:01:33,900 -数据集的神奇 __getitem__ 方法。 +数据集中神奇的 __getitem__ 方法。 the magic __getitem__ method of the dataset. 42 00:01:33,900 --> 00:01:35,640 -__getitem__ 方法是一种特殊的方法 +对于 Python 容器来说 __getitem__ 方法 The __getitem__ method is a special method 43 00:01:35,640 --> 00:01:37,320 -对于允许你的 Python 容器 +是一种特殊的方法,它允许你指定 for Python containers that allows you 44 00:01:37,320 --> 00:01:39,870 -指定索引的工作方式。 +以何种方式使用索引。 to specify how indexing works. 45 @@ -230,7 +230,7 @@ starts off by returning a Python dictionary 47 00:01:45,150 --> 00:01:47,520 -然后在应用 set_format 之后, +然后在调用 set_format 之后, and then after applying set_format, 48 @@ -240,17 +240,17 @@ we change __getitem__ to return DataFrames instead. 49 00:01:52,080 --> 00:01:54,690 -Datasets 库还提供了一个 to_pandas 方法 +如果你想做格式转换 The Datasets library also provides a to_pandas method 50 00:01:54,690 --> 00:01:56,250 -如果你想做格式转换 +并一次性对数据集进行切片 if you wanna do the format conversion 51 00:01:56,250 --> 00:01:58,113 -并一次性对数据集进行切片。 +Datasets 库还提供了一个 to_pandas 方法。 and slicing of the dataset in one go. 52 @@ -280,37 +280,37 @@ is that once you're done with your Pandas analysis, 57 00:02:10,830 --> 00:02:14,460 -你应该将输出格式重置回箭头表。 +你应该将输出格式重置回 Arrow 表。 you should reset the output format back to Arrow tables. 58 00:02:14,460 --> 00:02:16,350 -如果你不这样做,你可能会遇到问题 +如果不这样做,当你尝试词元化你的文本 If you don't, you can run into problems 59 00:02:16,350 --> 00:02:17,910 -如果你尝试标记化你的文本 +可能会遇到问题 if you try to tokenize your text 60 00:02:17,910 --> 00:02:19,260 -因为它不再代表 +因为它不再是字典中的 because it is no longer represented 61 00:02:19,260 --> 00:02:20,610 -作为字典中的字符串。 +字符串。 as strings in a dictionary. 62 00:02:21,750 --> 00:02:24,780 -通过重置输出格式,我们得到箭头表 +通过重置输出格式,我们得到 Arrow 表 By resetting the output format we get back Arrow tables 63 00:02:24,780 --> 00:02:26,580 -我们可以毫无问题地标记化。 +我们可以毫无顾虑地进行词元化。 and we can tokenize without problem. 64 diff --git a/subtitles/zh-CN/38_saving-and-reloading-a-dataset.srt b/subtitles/zh-CN/38_saving-and-reloading-a-dataset.srt index 4039d68b0..d6e25801c 100644 --- a/subtitles/zh-CN/38_saving-and-reloading-a-dataset.srt +++ b/subtitles/zh-CN/38_saving-and-reloading-a-dataset.srt @@ -25,17 +25,17 @@ and explore the ways to reload the saved data. 6 00:00:17,310 --> 00:00:20,100 -下载数据集时,处理脚本和数据 +下载数据集时,所需的处理脚本和数据都会本地存储 When you download a dataset, the processing scripts and data 7 00:00:20,100 --> 00:00:22,470 -本地存储在你的计算机上。 +在你的计算机上。 are stored locally on your computer. 8 00:00:22,470 --> 00:00:24,000 -缓存允许数据集库 +缓存允许 Datasets 库 The cache allows the Datasets library 9 @@ -50,27 +50,27 @@ or processing the entire dataset every time you use it. 11 00:00:28,620 --> 00:00:31,170 -现在,数据以箭头表的形式存储 +现在,数据以 Arrow 表的形式存储 Now, the data is stored in the form of Arrow tables 12 00:00:31,170 --> 00:00:32,490 -可以找到谁的位置 +通过访问数据集的 whose location can be found 13 00:00:32,490 --> 00:00:35,730 -通过访问数据集的 cache_files 属性。 +cache_files 属性可以找到它的位置。 by accessing the dataset's cache_files attribute. 14 00:00:35,730 --> 00:00:38,430 -在这个例子中,我们下载了 allocine 数据集 +在这个例子中,我们从 Hugging Face Hub In this example, we've downloaded the allocine dataset 15 00:00:38,430 --> 00:00:40,080 -来自 Hugging Face Hub,你可以看到 +下载了 allocine 数据集,你可以看到 from the Hugging Face Hub, and you can see 16 @@ -80,17 +80,17 @@ that there are three Arrow files 17 00:00:41,430 --> 00:00:43,473 -存储在缓存中,每个拆分一个。 +存储在缓存中,每个文件对应一个分片数据。 stored in the cache, one for each split. 18 00:00:45,360 --> 00:00:47,460 -但在很多情况下,你会想保存你的数据集 +但在很多情况下,你希望在不同的物理地址或者以不同的格式 But in many cases, you'll wanna save your dataset 19 00:00:47,460 --> 00:00:49,890 -在不同的位置或格式。 +保存你的数据集。 in a different location or format. 20 @@ -115,12 +115,12 @@ with the CSV and JSON formats, both of which are great 24 00:00:58,770 --> 00:01:00,810 -如果你只是想快速节省一小笔钱 +如果你只是想快速保存一个小规模 if you just wanna quickly save a small 25 00:01:00,810 --> 00:01:02,790 -或中型数据集。 +或中等规模的数据集。 or medium-sized dataset. 26 @@ -135,12 +135,12 @@ you'll wanna save it in either the Arrow or Parquet formats. 28 00:01:07,860 --> 00:01:09,660 -如果你打算重新加载,箭头文件很棒 +如果你打算重新加载或在不久的将来处理数据, Arrow files are great if you plan to reload 29 00:01:09,660 --> 00:01:11,850 -或在不久的将来处理数据。 +Arrow 文件就很棒。 or process the data in the near future. 30 @@ -175,22 +175,22 @@ As you can see in this example, 36 00:01:26,910 --> 00:01:29,790 -我们只需提供我们希望将数据保存到的路径 +只需提供我们希望将数据保存到的路径 we simply provide the path we wish to save the data to 37 00:01:29,790 --> 00:01:30,720 -和数据集库 +然后 Datasets 库 and the Datasets library 38 00:01:30,720 --> 00:01:32,340 -会自动创建一个目录 +会针对每个分片数据自动创建一个目录 will automatically create a directory 39 00:01:32,340 --> 00:01:35,790 -对于每个拆分来存储箭头表和元数据。 +来存储 Arrow 表和元数据。 for each split to store the Arrow table and the metadata. 40 @@ -200,7 +200,7 @@ Since we're dealing with a dataset_dict object 41 00:01:37,680 --> 00:01:39,090 -有多个拆分, +其中包含多个分片数据, that has multiple splits, 42 @@ -215,7 +215,7 @@ in the dataset_dict.json file. 44 00:01:44,250 --> 00:01:46,710 -现在,当我们想要重新加载 Arrow 数据集时, +现在,当想要重新加载 Arrow 数据集时, Now, when we wanna reload the Arrow datasets, 45 @@ -225,12 +225,12 @@ we use the load_from_disk function. 46 00:01:48,870 --> 00:01:51,210 -我们只需传递数据集目录的路径, +只需传递数据集目录的路径, We simply pass the path of our dataset directory, 47 00:01:51,210 --> 00:01:53,583 -瞧,原始数据集已恢复。 +啊瞧,原始数据集已恢复。 and voila, the original dataset is recovered. 48 @@ -250,7 +250,7 @@ In this case, you'll need to loop 51 00:02:02,280 --> 00:02:04,170 -在 dataset_dict 对象的拆分上 +dataset_dict 对象的分片数据 over the splits of the dataset_dict object 52 @@ -270,12 +270,12 @@ you can pass keyword arguments to configure the output. 55 00:02:13,980 --> 00:02:16,230 -在这个例子中,我们设置了索引参数 +在这个例子中,我们将索引参数设置为 None In this example, we've set the index argument 56 00:02:16,230 --> 00:02:18,480 -为 None 以防止数据集的索引列 +以防止数据集的索引列 to None to prevent the dataset's index column 57 @@ -290,7 +290,7 @@ To reload our CSV files, 59 00:02:24,240 --> 00:02:27,180 -然后我们就使用熟悉的 load_dataset 函数 +就使用熟悉的 load_dataset 函数 we just then use the familiar load_dataset function 60 @@ -305,7 +305,7 @@ and the data_files argument, 62 00:02:30,360 --> 00:02:34,020 -它指定与每个拆分关联的文件名。 +它指定与每个分片数据关联的文件名。 which specifies the file names associated with each split. 63 @@ -315,7 +315,7 @@ As you can see in this example, 64 00:02:35,400 --> 00:02:37,320 -通过提供所有拆分及其文件名, +通过提供所有分片数据及其文件名, by providing all the splits and their file names, 65 @@ -330,7 +330,7 @@ Now, to save a dataset in the JSON 67 00:02:43,560 --> 00:02:46,710 -或 Parquet 格式与 CSV 的情况非常相似。 +或保存为 Parquet 格式与 CSV 的情况非常相似。 or Parquet formats is very similar to the CSV case. 68 @@ -345,7 +345,7 @@ or the to_parquet function for Parquet ones. 70 00:02:52,740 --> 00:02:55,740 -就像 CSV 案例一样,我们需要遍历拆分 +就像 CSV 案例一样,我们需要遍历分片数据 And just like the CSV case, we need to loop over the splits 71 @@ -365,7 +365,7 @@ we can reload them again 74 00:03:03,990 --> 00:03:06,960 -使用 load_dataset 函数中的适当脚本。 +使用 load_dataset 函数中的合适的脚本。 with the appropriate script in the load_dataset function. 75 @@ -380,7 +380,7 @@ This example shows 77 00:03:11,910 --> 00:03:14,560 -我们如何以任何一种格式重新加载我们的保存数据集。 +我们如何以任何一种格式重新加载我们保存的数据集。 how we can reload our save datasets in either format. 78 diff --git a/subtitles/zh-CN/39_memory-mapping-&-streaming.srt b/subtitles/zh-CN/39_memory-mapping-&-streaming.srt index e39a21f86..5c8f58909 100644 --- a/subtitles/zh-CN/39_memory-mapping-&-streaming.srt +++ b/subtitles/zh-CN/39_memory-mapping-&-streaming.srt @@ -15,7 +15,7 @@ 4 00:00:05,640 --> 00:00:07,203 -- 内存映射和流。 +- 内存映射和流式数据。 - Memory mapping and streaming. 5 @@ -30,22 +30,22 @@ at two core features of the Datasets library 7 00:00:11,520 --> 00:00:14,220 -允许你加载和处理庞大的数据集 +在不耗尽笔记本电脑的 CPU 资源的前提下 that allow you to load and process huge datasets 8 00:00:14,220 --> 00:00:16,263 -而不会炸毁笔记本电脑的 CPU。 +允许你加载和处理庞大的数据集。 without blowing up your laptop's CPU. 9 00:00:18,300 --> 00:00:20,280 -如今,发现自己并不罕见 +如今,工作上处理多达数个 GB 体量的数据集 Nowadays, it's not uncommon to find yourself 10 00:00:20,280 --> 00:00:22,950 -使用多 GB 大小的数据集, +已经不是什么新鲜事了, working with multi-GB sized datasets, 11 @@ -55,12 +55,12 @@ especially if you're planning to pretrain 12 00:00:24,420 --> 00:00:28,110 -从头开始像 BERT 或 GPT-2 这样的转换器。 +类似 BERT 或 GPT-2 这样的 transformer。 a transformer like BERT or GPT-2 from scratch. 13 00:00:28,110 --> 00:00:31,260 -在这些情况下,即使加载数据也可能是一个挑战。 +在这些场景下,即使加载数据也可能是一个挑战。 In these cases, even loading the data can be a challenge. 14 @@ -95,12 +95,12 @@ Arrow is designed for high-performance data processing 20 00:00:49,110 --> 00:00:51,360 -并代表每个类似表格的数据集 +并代表每个类似表格的 and represents each table-like dataset 21 00:00:51,360 --> 00:00:52,773 -使用基于列的格式。 +基于列格式的数据集。 with a column-based format. 22 @@ -110,12 +110,12 @@ As you can see in this example, column-based formats 23 00:00:56,130 --> 00:00:59,280 -将表格的元素分组到连续的 RAM 块中 +将表格的元素分组缓存到连续的 RAM 块中 group the elements of a table in consecutive blocks of RAM 24 00:00:59,280 --> 00:01:01,563 -这解锁了快速访问和处理。 +这实现了快速访问和处理。 and this unlocks fast access and processing. 25 @@ -130,7 +130,7 @@ but some datasets are so large 27 00:01:07,110 --> 00:01:09,600 -你甚至不能把它们放在你的硬盘上。 +你甚至不能把它们完全放在你的硬盘上。 that you can't even fit them on your hard disk. 28 @@ -145,7 +145,7 @@ a streaming API that allows you to progressively download 30 00:01:14,820 --> 00:01:17,700 -原始数据一次一个元素。 +可以每次下载原始数据的一个元素。 the raw data one element at a time. 31 @@ -155,7 +155,7 @@ The result is a special object called an IterableDataset 32 00:01:20,430 --> 00:01:22,180 -我们很快就会看到更多细节。 +我们接下来就会看到更多细节。 that we'll see in more detail soon. 33 @@ -165,12 +165,12 @@ Let's start by looking at why Arrow is so powerful. 34 00:01:26,670 --> 00:01:28,860 -第一个特点是它处理每个数据集 +第一个特点是它将每个数据集 The first feature is that it treats every dataset 35 00:01:28,860 --> 00:01:30,153 -作为内存映射文件。 +作为内存映射文件处理。 as a memory-mapped file. 36 @@ -200,7 +200,7 @@ to access segments of an extremely large file 41 00:01:41,280 --> 00:01:44,080 -无需先将整个文件读入内存。 +而无需先将整个文件读入内存。 without having to read the whole file into memory first. 42 @@ -220,7 +220,7 @@ to work with the same large dataset 45 00:01:51,840 --> 00:01:54,333 -不以任何方式移动或复制它。 +而无需以任何方式移动或复制它。 without moving it or copying it in any way. 46 @@ -235,17 +235,17 @@ makes it extremely fast for iterating over a dataset. 48 00:02:00,600 --> 00:02:02,640 -这个例子,你可以看到我们迭代 +这个例子,你可以看到我们使用 And this example, you can see that we iterate 49 00:02:02,640 --> 00:02:05,160 -大约一分钟内超过 1500 万行 +普通的笔记本电脑在一分钟之内迭代 over 15 million rows in about a minute 50 00:02:05,160 --> 00:02:06,780 -仅使用标准笔记本电脑。 +大约超过 1500 万行数据。 just using a standard laptop. 51 @@ -260,12 +260,12 @@ Let's now take a look at how we can stream a large dataset. 53 00:02:12,660 --> 00:02:14,520 -你需要做的唯一更改是设置 +你需要做的唯一修改是 The only change you need to make is to set 54 00:02:14,520 --> 00:02:17,910 -load_dataset () 函数中的 streaming=True 参数。 +设置 load_dataset () 函数中的 streaming=True 参数。 the streaming=True argument in the load_dataset () function. 55 @@ -295,12 +295,12 @@ which means we can't index it to access elements, 60 00:02:28,530 --> 00:02:30,180 -但相反我们迭代它 +但相反我们使用 iter but instead we iterate on it 61 00:02:30,180 --> 00:02:32,850 -使用 iter 和 next 方法。 +和 next 方法迭代它。 using the iter and next methods. 62 @@ -320,7 +320,7 @@ which means you can progressively iterate 65 00:02:37,410 --> 00:02:40,360 -通过庞大的数据集,而无需先下载它。 +庞大的数据集,而无需提前下载它。 through a huge dataset without having to download it first. 66 @@ -345,7 +345,7 @@ and then apply the map () method with the tokenizer. 70 00:02:49,830 --> 00:02:53,283 -要获得第一个标记化示例,我们应用 iter 和 next。 +要获得第一个词元化示例,我们应用 iter 和 next。 To get the first tokenized example, we apply iter and next. 71 @@ -355,12 +355,12 @@ The main difference with an IterableDataset is that 72 00:02:57,210 --> 00:02:59,970 -而不是使用 select () 方法返回示例, +并未使用 select () 方法返回示例, instead of using a select () method to return examples, 73 00:02:59,970 --> 00:03:01,530 -我们使用 take () 和 skip () 方法 +而是使用 take () 和 skip () 方法 we use the take () and skip () methods 74 diff --git a/subtitles/zh-CN/40_uploading-a-dataset-to-the-hub.srt b/subtitles/zh-CN/40_uploading-a-dataset-to-the-hub.srt index 860785521..563602e72 100644 --- a/subtitles/zh-CN/40_uploading-a-dataset-to-the-hub.srt +++ b/subtitles/zh-CN/40_uploading-a-dataset-to-the-hub.srt @@ -5,7 +5,7 @@ 2 00:00:05,490 --> 00:00:07,950 -- 将数据集上传到中心。 +- 将数据集上传到 hub。 - Uploading a dataset to the hub. 3 @@ -30,7 +30,7 @@ The first thing you need to do 7 00:00:14,670 --> 00:00:17,400 -是在集线器上创建一个新的数据集存储库。 +是在 hub 上创建一个新的数据集仓库。 is create a new dataset repository on the hub. 8 @@ -40,7 +40,7 @@ So, just click on your profile icon 9 00:00:19,260 --> 00:00:21,750 -并选择新建数据集按钮。 +并选择 New Dataset 按钮。 and select the New Dataset button. 10 @@ -50,17 +50,17 @@ Next, we need to assign an owner of the dataset. 11 00:00:24,750 --> 00:00:26,970 -默认情况下,这将是你的中心帐户, +默认情况下,所有者是你的 hub 帐户, By default, this will be your hub account, 12 00:00:26,970 --> 00:00:28,170 -但你也可以创建数据集 +但你也可以 but you can also create datasets 13 00:00:28,170 --> 00:00:30,585 -在你所属的任何组织下。 +以你所属的组织的名义创建数据集。 under any organization that you belong to. 14 @@ -80,12 +80,12 @@ Public datasets can be accessed by anyone 17 00:00:39,810 --> 00:00:41,670 -而私人数据集只能被访问 +而私人数据集只能 while private datasets can only be accessed 18 00:00:41,670 --> 00:00:43,653 -由你或你的组织成员。 +允许你或你的组织成员访问。 by you or members of your organization. 19 @@ -95,7 +95,7 @@ And with that, we can go ahead and create the dataset. 20 00:00:48,690 --> 00:00:51,060 -现在你在集线器上有一个空的数据集存储库, +现在你在 hub 上有一个空的数据集仓库, Now that you have an empty dataset repository on the hub, 21 @@ -110,17 +110,17 @@ You can do this with git, 23 00:00:55,050 --> 00:00:57,960 -但最简单的方法是选择上传文件按钮。 +但最简单的方法是选择 Upload file 按钮。 but the easiest way is by selecting the Upload file button. 24 00:00:57,960 --> 00:00:59,160 -然后,你可以继续 +然后,你可以继续下一步 And then, you can just go ahead 25 00:00:59,160 --> 00:01:02,243 -并直接从你的机器上传文件。 +直接从你的机器上传文件。 and upload the files directly from your machine. 26 @@ -130,12 +130,12 @@ After you've uploaded your files, 27 00:01:03,846 --> 00:01:05,670 -你会看到它们出现在存储库中 +你会在 Files and versions 选项卡下 you'll see them appear in the repository 28 00:01:05,670 --> 00:01:07,320 -在文件和版本选项卡下。 +看到它们出现在仓库中。 under the Files and versions tab. 29 @@ -145,27 +145,27 @@ The last step is to create a dataset card. 30 00:01:11,370 --> 00:01:13,590 -记录良好的数据集更有用 +对其他人来说,归档良好的数据集貌似更有用, Well-documented datasets are more likely to be useful 31 00:01:13,590 --> 00:01:15,600 -给其他人,因为他们提供了决定的背景 +因为他们提供了影响决策的上下文信息 to others as they provide the context to decide 32 00:01:15,600 --> 00:01:17,370 -数据集是否相关 +包括数据集是否与需求相关 whether the dataset is relevant 33 00:01:17,370 --> 00:01:18,450 -或者有没有偏见 +或者有没有误差 or whether there are any biases 34 00:01:18,450 --> 00:01:20,673 -或与使用数据集相关的风险。 +或使用该数据可能遇到的风险。 or risks associated with using the dataset. 35 @@ -175,12 +175,12 @@ On the Hugging Face Hub, 36 00:01:22,710 --> 00:01:25,650 -此信息存储在每个存储库的自述文件中。 +此信息存储在每个仓库的 README 文件中。 this information is stored in each repositories README file. 37 00:01:25,650 --> 00:01:27,988 -你应该采取两个主要步骤。 +你需要执行两个主要步骤。 There are two main steps that you should take. 38 @@ -190,27 +190,27 @@ First, you need to create some metadata 39 00:01:30,651 --> 00:01:32,010 -这将允许你的数据集 +这将允许其他人在 hub 上 that will allow your dataset 40 00:01:32,010 --> 00:01:34,590 -其他人可以在集线器上轻松找到。 +轻松找到你的数据集。 to be easily found by others on the hub. 41 00:01:34,590 --> 00:01:35,670 -你可以创建此元数据 +你可以使用数据集标记应用程序 You can create this metadata 42 00:01:35,670 --> 00:01:37,860 -使用数据集标记应用程序, +创建此元数据, using the datasets tagging application, 43 00:01:37,860 --> 00:01:40,620 -我们将在视频说明中链接到它。 +我们将在视频说明信息中包含它的链接。 which we'll link to in the video description. 44 @@ -230,12 +230,12 @@ and we provide a template 47 00:01:45,240 --> 00:01:47,090 -我们还将在视频中链接到。 +相关链接也会包含在下面的视频信息内容中。 that we'll also link to in the video. 48 00:01:48,480 --> 00:01:50,280 -一旦你的数据集在集线器上, +一旦你的数据集在 hub 上, And once your dataset is on the hub, 49 @@ -245,12 +245,12 @@ you can load it using the trusty load_dataset function. 50 00:01:53,400 --> 00:01:55,015 -只需提供你的存储库的名称 +只需提供你的仓库的名称 Just provide the name of your repository 51 00:01:55,015 --> 00:01:57,843 -和一个 data_files 参数,你就可以开始了。 +和一个 data_files 参数,你就可以开始使用了。 and a data_files argument, and you're good to go. 52 diff --git a/subtitles/zh-CN/41_text-embeddings-&-semantic-search.srt b/subtitles/zh-CN/41_text-embeddings-&-semantic-search.srt index 0b8958068..4c43f0e18 100644 --- a/subtitles/zh-CN/41_text-embeddings-&-semantic-search.srt +++ b/subtitles/zh-CN/41_text-embeddings-&-semantic-search.srt @@ -20,17 +20,17 @@ represent text as embedding vectors 5 00:00:12,810 --> 00:00:15,420 -以及如何使用这些向量来查找相似文档 +以及在语料库中如何使用这些向量 and how these vectors can be used to find similar documents 6 00:00:15,420 --> 00:00:16,293 -在语料库中。 +来查找相似文档。 in a corpus. 7 00:00:17,730 --> 00:00:19,890 -文本嵌入只是一种奇特的说法 +文本嵌入只是一种时髦的说法 Text embeddings are just a fancy way of saying 8 @@ -40,7 +40,7 @@ that we can represent text as an array of numbers 9 00:00:22,170 --> 00:00:23,640 -称为矢量。 +称之为矢量。 called a vector. 10 @@ -65,12 +65,12 @@ to the encoder and get three vectors as the output. 14 00:00:34,830 --> 00:00:37,050 -读课文,我们可以看到遛狗 +读一下输入文本,我们可以看到 walking the dog Reading the text, we can see that walking the dog 15 00:00:37,050 --> 00:00:39,450 -好像跟遛猫最像, +和 walking the cat 从字面上感觉很像, seems to be most similar to walking the cat, 16 @@ -85,12 +85,12 @@ The trick to do the comparison 18 00:00:44,040 --> 00:00:45,630 -是计算相似性度量 +是在每对嵌入向量之间 is to compute a similarity metric 19 00:00:45,630 --> 00:00:48,210 -在每对嵌入向量之间。 +计算相似性度量。 between each pair of embedding vectors. 20 @@ -100,12 +100,12 @@ These vectors usually live in a very high-dimensional space, 21 00:00:51,120 --> 00:00:53,190 -所以相似性度量可以是任何可以衡量的东西 +所以相似性度量可以是任何可用于 so a similarity metric can be anything that measures 22 00:00:53,190 --> 00:00:55,740 -矢量之间的某种距离。 +衡量矢量之间的某种距离的属性。 some sort of distance between vectors. 23 @@ -125,7 +125,7 @@ to measure how close they are. 26 00:01:02,610 --> 00:01:05,250 -在这个例子中,我们的嵌入向量存在于 3D 中 +在这个例子中,我们的嵌入向量存在于三维空间中 In this example, our embedding vectors live in 3D 27 @@ -135,7 +135,7 @@ and we can see that the orange and Grey vectors 28 00:01:07,110 --> 00:01:09,560 -彼此靠近并且具有较小的角度。 +彼此靠近并且具有更小的角度。 are close to each other and have a smaller angle. 29 @@ -150,12 +150,12 @@ is that Transformer models like BERT will actually return 31 00:01:15,180 --> 00:01:16,983 -每个标记一个嵌入向量。 +每个词元一个嵌入向量。 one embedding vector per token. 32 00:01:17,880 --> 00:01:20,700 -例如在句子中,“我带我的狗去散步,” +例如在句子中,“I took my dog for a walk,” For example in the sentence, "I took my dog for a walk," 33 @@ -180,58 +180,58 @@ and each vector has 384 dimensions. 37 00:01:33,750 --> 00:01:36,210 -但我们真正想要的是一个单一的嵌入向量 +但我们真正想要的是对于每个句子 But what we really want is a single embedding vector 38 00:01:36,210 --> 00:01:37,353 -对于每个句子。 +对应一个单一的嵌入向量。 for each sentence. 39 00:01:38,940 --> 00:01:42,060 -为了解决这个问题,我们可以使用一种称为池化的技术。 +为了解决这个问题,我们可以使用一种称为 pooling 的技术。 To deal with this, we can use a technique called pooling. 40 00:01:42,060 --> 00:01:43,050 -最简单的池化方法 +最简单的 pooling 方法 The simplest pooling method 41 00:01:43,050 --> 00:01:44,520 -就是把令牌嵌入 +就是把词元嵌入 is to just take the token embedding 42 00:01:44,520 --> 00:01:46,203 -特殊的 CLS 令牌。 +特殊的 CLS 词元。 of the special CLS token. 43 00:01:47,100 --> 00:01:49,650 -或者,我们可以对令牌嵌入进行平均 +或者,我们可以对词元嵌入进行平均 Alternatively, we can average the token embeddings 44 00:01:49,650 --> 00:01:52,500 -这就是所谓的均值池,这就是我们在这里所做的。 -which is called mean pooling and this is what we do here. +这就是所谓的 mean_pooling,也就是我们在这里所做的。 +which is called mean_pooling and this is what we do here. 45 00:01:53,370 --> 00:01:55,800 -使用均值池是我们唯一需要确保的事情 -With mean pooling the only thing we need to make sure +使用 mean_pooling 时我们唯一需要确保的事情 +With mean_pooling the only thing we need to make sure 46 00:01:55,800 --> 00:01:58,410 -是我们不在平均值中包含填充标记, +是我们不在平均值中包含 padding 词元, is that we don't include the padding tokens in the average, 47 00:01:58,410 --> 00:02:01,860 -这就是为什么你可以看到这里使用的注意力掩码。 -which is why you can see the attention mask being used here. +这就是为什么你可以看到这里用到了 attention_mask。 +which is why you can see the attention_mask being used here. 48 00:02:01,860 --> 00:02:05,100 @@ -250,12 +250,12 @@ And once we have our sentence embeddings, 51 00:02:09,810 --> 00:02:11,730 -我们可以计算余弦相似度 +我们可以针对每对向量 we can compute the cosine similarity 52 00:02:11,730 --> 00:02:13,113 -对于每对向量。 +计算余弦相似度。 for each pair of vectors. 53 @@ -265,12 +265,12 @@ In this example we use the function from scikit-learn 54 00:02:16,350 --> 00:02:19,140 -你可以看到 “I tok my dog for a walk” 这句话 +你可以看到 “I took my dog for a walk” 这句话 and you can see that the sentence "I took my dog for a walk" 55 00:02:19,140 --> 00:02:22,140 -确实与 “我带我的猫去散步” 有很强的重叠。 +确实与 “I took my cat for a walk” 有很明显的重叠。 has indeed a strong overlap with "I took my cat for a walk". 56 @@ -285,22 +285,22 @@ We can actually take this idea one step further 58 00:02:27,180 --> 00:02:29,220 -通过比较问题之间的相似性 +通过比较问题和文档语料库 by comparing the similarity between a question 59 00:02:29,220 --> 00:02:31,170 -和文档语料库。 +之间的相似性。 and a corpus of documents. 60 00:02:31,170 --> 00:02:33,810 -例如,假设我们嵌入每个帖子 +例如,假设我们在 Hugging Face 论坛中 For example, suppose we embed every post 61 00:02:33,810 --> 00:02:35,430 -在 Hugging Face 论坛中。 +嵌入每个帖子。 in the Hugging Face forums. 62 @@ -325,12 +325,12 @@ because it allows us to compare queries with context. 66 00:02:47,040 --> 00:02:48,450 -创建语义搜索引擎 +使用 datasets 库 To create a semantic search engine 67 00:02:48,450 --> 00:02:51,030 -在数据集库中其实很简单。 +创建语义搜索引擎其实很简单。 is actually quite simple in the datasets library. 68 @@ -340,22 +340,22 @@ First we need to embed all the documents. 69 00:02:53,340 --> 00:02:56,070 -在这个例子中,我们取了一个小样本 +在这个例子中,我们取了 And in this example, we take a small sample 70 00:02:56,070 --> 00:02:57,780 -来自 SQUAD 数据集并应用 -from the SQUAD dataset and apply +一个来自 squad 数据集的小样本 +from the squad dataset and apply 71 00:02:57,780 --> 00:03:00,180 -与以前相同的嵌入逻辑。 +并按照与以前相同的嵌入逻辑使用。 the same embedding logic as before. 72 00:03:00,180 --> 00:03:02,280 -这为我们提供了一个名为嵌入的新列, +这为我们提供了一个名为 embeddings 的新列, This gives us a new column called embeddings, 73 @@ -370,12 +370,12 @@ Once we have our embeddings, 75 00:03:07,260 --> 00:03:10,200 -我们需要一种方法来为查询找到最近的邻居。 +我们需要一种方法来为查询找到最近的相邻数据。 we need a way to find nearest neighbors for a query. 76 00:03:10,200 --> 00:03:13,170 -数据集库提供了一个名为 FAISS 的特殊对象 +datasets 库提供了一个名为 FAISS 的特殊对象 The datasets library provides a special object called FAISS 77 @@ -395,7 +395,7 @@ we've now found the 3 most similar articles 80 00:03:21,870 --> 00:03:23,320 -这可能会存储答案。 +其中可能会包含答案。 which might store the answer. 81 diff --git a/subtitles/zh-CN/42_training-a-new-tokenizer.srt b/subtitles/zh-CN/42_training-a-new-tokenizer.srt index 2b0d28498..9b8bb0032 100644 --- a/subtitles/zh-CN/42_training-a-new-tokenizer.srt +++ b/subtitles/zh-CN/42_training-a-new-tokenizer.srt @@ -10,7 +10,7 @@ 3 00:00:08,700 --> 00:00:11,820 -训练分词器的目的是什么, +训练 tokenizer 的目的是什么, what is the purpose of training a tokenizer, 4 @@ -30,7 +30,7 @@ You will ask yourself the question, 7 00:00:20,677 --> 00:00:23,040 -“我应该训练一个新的分词器吗?”, +“我应该训练一个新的 tokenizer 吗?”, "Should I train a new tokenizer?", 8 @@ -40,37 +40,37 @@ when you plan to train a new model from scratch. 9 00:00:29,520 --> 00:00:34,020 -训练有素的分词器不适合你的语料库 +一个训练过的分词器会不适合你的语料库 A trained tokenizer would not be suitable for your corpus 10 00:00:34,020 --> 00:00:37,080 -如果你的语料库使用不同的语言, +如果你的语料库使用一个不同的语言, if your corpus is in a different language, 11 00:00:37,080 --> 00:00:42,060 -使用新字符,例如重音符号或大写字母, +使用新字符,比如重音符号或大写字母, uses new characters, such as accents or upper cased letters, 12 00:00:42,060 --> 00:00:47,060 -有特定的词汇,例如医学或法律, +有特定的词汇,例如医学的或法律的, has a specific vocabulary, for example medical or legal, 13 00:00:47,100 --> 00:00:49,050 -或使用不同的风格, +或使用一个不同的风格, or uses a different style, 14 00:00:49,050 --> 00:00:51,873 -例如,来自另一个世纪的语言。 +例如,来自另一个世纪时的语言。 a language from another century for example. 15 00:00:56,490 --> 00:00:58,320 -如果我接受训练的分词器 +如果我让 tokenizer 训练在 If I take the tokenizer trained on 16 @@ -85,22 +85,22 @@ and ignore its normalization step, 18 00:01:04,260 --> 00:01:07,650 -然后我们可以看到标记化操作 +然后我们就能看到 tokenization 操作 then we can see that the tokenization operation 19 00:01:07,650 --> 00:01:09,277 -关于英语句子, +在英语句子上, on the English sentence, 20 00:01:09,277 --> 00:01:12,480 -“这是一个适合我们分词器的句子”, +“这是一个适合我们 tokenizer 的句子”, "Here is a sentence adapted to our tokenizer", 21 00:01:12,480 --> 00:01:15,600 -产生一个相当令人满意的令牌列表, +产生一个相当令人满意的 token 列表, produces a rather satisfactory list of tokens, 22 @@ -110,12 +110,12 @@ in the sense that this sentence of eight words 23 00:01:18,510 --> 00:01:20,643 -被标记为九个标记。 +被标记成了九个 token 。 is tokenized into nine tokens. 24 00:01:22,920 --> 00:01:26,340 -另一方面,如果我使用相同的分词器 +另一方面,如果我使用相同的 tokenizer On the other hand, if I use this same tokenizer 25 @@ -125,37 +125,37 @@ on a sentence in Bengali, we see that 26 00:01:29,370 --> 00:01:33,690 -要么一个词被分成许多子标记, +要么一个词被分成许多子 token , either a word is divided into many sub tokens, 27 00:01:33,690 --> 00:01:36,270 -或者分词器不知道其中之一 +或者 tokenizer 不知道任何一个 or that the tokenizer does not know one of 28 00:01:36,270 --> 00:01:39,873 -unicode 字符并仅返回未知标记。 +unicode 字符并仅返回未知 token 。 the unicode characters and returns only an unknown token. 29 00:01:41,220 --> 00:01:44,970 -一个常用词被分成许多标记的事实 +一个常用词被分成许多 token 的事实 The fact that a common word is split into many tokens 30 00:01:44,970 --> 00:01:47,910 -可能会有问题,因为语言模型 +可能是问题的,因为语言模型 can be problematic, because language models 31 00:01:47,910 --> 00:01:51,903 -只能处理有限长度的令牌序列。 +只能处理有限长度的 token 序列。 can only handle a sequence of tokens of limited length. 32 00:01:52,830 --> 00:01:55,830 -过度拆分初始文本的分词器 +过度拆分初始文本的 tokenizer A tokenizer that excessively splits your initial text 33 @@ -165,7 +165,7 @@ may even impact the performance of your model. 34 00:01:59,760 --> 00:02:02,280 -未知的令牌也有问题, +未知的 token 也有问题, Unknown tokens are also problematic, 35 @@ -185,17 +185,17 @@ In this other example, we can see that 38 00:02:13,440 --> 00:02:17,100 -分词器替换包含字符的单词 +tokenizer 替换包含字符的单词 the tokenizer replaces words containing characters 39 00:02:17,100 --> 00:02:20,973 -带有重音和带有未知标记的大写字母。 +带有重音和带有未知 token 的大写字母。 with accents and capital letters with unknown tokens. 40 00:02:22,050 --> 00:02:24,770 -最后,如果我们再次使用这个分词器 +最后,如果我们再次使用这个 tokenizer Finally, if we use again this tokenizer 41 @@ -205,32 +205,32 @@ to tokenize medical vocabulary, we see again that 42 00:02:28,170 --> 00:02:31,800 -一个单词被分成许多子标记, +一个单词被分成许多子 token , a single word is divided into many sub tokens, 43 00:02:31,800 --> 00:02:34,803 -四个用于扑热息痛,四个用于咽炎。 +四个用于 "paracetamol" ,四个用于 "pharyngitis" 。 four for paracetamol, and four for pharyngitis. 44 00:02:37,110 --> 00:02:39,360 -当前使用的大多数分词器 +目前的大多数 tokenizer 被 Most of the tokenizers used by the current 45 00:02:39,360 --> 00:02:42,540 -需要训练最先进的语言模型 +最先进的语言模型使用的, 需要被训练 state of the art language models need to be trained 46 00:02:42,540 --> 00:02:45,360 -在与使用的语料库相似的语料库上 +在语料库上, 其与使用的语料库如要相似于 on a corpus that is similar to the one used 47 00:02:45,360 --> 00:02:47,463 -预训练语言模型。 +预训练语言模型的。 to pre-train the language model. 48 @@ -250,12 +250,12 @@ And the way to learn these rules and use them 51 00:02:56,160 --> 00:02:58,233 -取决于所选的分词器模型。 +取决于所选的 tokenizer 模型。 depends on the chosen tokenizer model. 52 00:03:00,630 --> 00:03:04,590 -因此,要训练一个新的分词器,首先需要 +因此,要训练一个新的 tokenizer ,首先需要 Thus, to train a new tokenizer, it is first necessary 53 @@ -265,7 +265,7 @@ to build a training corpus composed of raw texts. 54 00:03:08,910 --> 00:03:12,423 -然后,你必须为你的分词器选择一种架构。 +然后,你必须为你的 tokenizer 选择一种结构。 Then, you have to choose an architecture for your tokenizer. 55 @@ -275,12 +275,12 @@ Here there are two options. 56 00:03:15,900 --> 00:03:19,710 -最简单的是重用与那个相同的架构 +最简单的是重用与那个结构, 其相同于 The simplest is to reuse the same architecture as the one 57 00:03:19,710 --> 00:03:22,863 -另一个已经训练过的模型使用的分词器。 +另一个已经训练过的模型使用的 tokenizer 。 of a tokenizer used by another model already trained. 58 @@ -290,22 +290,22 @@ Otherwise it is also possible 59 00:03:25,980 --> 00:03:28,560 -完全设计你的分词器。 +彻底设计你的分词器。 to completely design your tokenizer. 60 00:03:28,560 --> 00:03:31,683 -但这需要更多的经验和关注。 +但这需要更多的经验和观察。 But it requires more experience and attention. 61 00:03:33,750 --> 00:03:36,660 -一旦选择了架构,你就可以 +一旦选择了结构,你就可以 Once the architecture is chosen, you can thus 62 00:03:36,660 --> 00:03:39,513 -在你构成的语料库上训练这个分词器。 +在你构建的语料库上训练这个 tokenizer 。 train this tokenizer on your constituted corpus. 63 @@ -315,7 +315,7 @@ Finally, the last thing that you need to do is to save 64 00:03:43,440 --> 00:03:46,443 -能够使用此分词器的学习规则。 +能够使用此 tokenizer 的学习规则。 the learned rules to be able to use this tokenizer. 65 @@ -340,7 +340,7 @@ this type of text is very specific, 69 00:04:02,386 --> 00:04:04,473 -并值得一个训练有素的分词器。 +并值得一个训练过的 tokenizer 。 and deserves a tokenizer trained on it. 70 @@ -360,12 +360,12 @@ For that we are going to use the method 73 00:04:13,747 --> 00:04:18,240 -“train_new_from_iterator”,所有快速分词器 +“train_new_from_iterator”,所有快速的 tokenizer "train_new_from_iterator" that all the fast tokenizers 74 00:04:18,240 --> 00:04:20,040 -图书馆有,因此, +在库中有的,因此, of the library have and thus, 75 @@ -380,7 +380,7 @@ This is the simplest method in our case 77 00:04:26,100 --> 00:04:28,983 -有一个适合 Python 代码的分词器。 +有一个适合 Python 代码的 tokenizer 。 to have a tokenizer adapted to Python code. 78 @@ -450,12 +450,12 @@ on our Python functions corpus, we can load 91 00:05:09,630 --> 00:05:12,351 -GPT-2 分词器架构。 +GPT-2 分词器结构。 the GPT-2 tokenizer architecture. 92 00:05:12,351 --> 00:05:16,560 -这里 old_tokenizer 不适应我们的语料库。 +这里 old_tokenizer 不适合我们的语料库。 Here old_tokenizer is not adapted to our corpus. 93 @@ -470,12 +470,12 @@ one more line to train it on our new corpus. 95 00:05:21,780 --> 00:05:24,720 -大多数标记化的共同论点 +有一点很普遍的是大多数 分词化 An argument that is common to most of the tokenization 96 00:05:24,720 --> 00:05:28,980 -目前使用的算法是词汇表的大小。 +算法, 目前使用的是根据词汇表的大小。 algorithms used at the moment is the size of the vocabulary. 97 @@ -490,12 +490,12 @@ Finally, once the training is finished, 99 00:05:35,760 --> 00:05:38,850 -我们只需要在本地保存我们的新分词器, +我们只需要在本地保存我们的新 tokenizer , we just have to save our new tokenizer locally, 100 00:05:38,850 --> 00:05:41,730 -或将其发送到集线器以便能够重用它 +或将其发送到 hub 以便能够重用它 or send it to the hub to be able to reuse it 101 @@ -510,12 +510,12 @@ Finally, let's see together on an example if it was useful 103 00:05:48,990 --> 00:05:53,073 -重新训练一个类似于 GPT-2 的分词器。 +重新训练一个类似于 GPT-2 的 tokenizer 。 to re-train a tokenizer similar to GPT-2 one. 104 00:05:55,110 --> 00:05:57,660 -使用 GPT-2 的原始分词器 +使用 GPT-2 的原始 tokenizer With the original tokenizer of GPT-2 105 @@ -525,12 +525,12 @@ we see that all spaces are isolated, 106 00:06:00,330 --> 00:06:01,920 -和方法名称 randn, +和方法名称 "randn", and the method name randn, 107 00:06:01,920 --> 00:06:04,833 -在 Python 代码中比较常见,分为两部分。 +在 Python 代码中相对比较常见,分为两部分。 relatively common in Python code, is split in two. 108 @@ -540,12 +540,12 @@ With our new tokenizer, single and double indentations 109 00:06:09,060 --> 00:06:10,890 -已经学习和方法 randn +已经学习和方法 "randn" have been learned and the method randn 110 00:06:10,890 --> 00:06:13,770 -被代币化为一个代币。 +被分词化为一个 token 。 is tokenized into one token. 111 diff --git a/subtitles/zh-CN/43_why-are-fast-tokenizers-called-fast.srt b/subtitles/zh-CN/43_why-are-fast-tokenizers-called-fast.srt index 26a06bbaf..2e113cf9c 100644 --- a/subtitles/zh-CN/43_why-are-fast-tokenizers-called-fast.srt +++ b/subtitles/zh-CN/43_why-are-fast-tokenizers-called-fast.srt @@ -5,7 +5,7 @@ 2 00:00:05,340 --> 00:00:08,460 -- 为什么快速分词器被称为快速? +- 为什么快速 tokenizer 被称 "快速"? - Why are fast tokenizers called fast? 3 @@ -15,12 +15,12 @@ In this video, we'll see exactly how much faster, 4 00:00:10,950 --> 00:00:13,800 -另外,比较了所谓的快速组织者 +另外,所谓的快速 tokenizer 被比较 also, so-called fast organizers are compared 5 00:00:13,800 --> 00:00:15,153 -给他们慢的同行。 +和他们慢的参照物。 to their slow counterparts. 6 @@ -40,12 +40,12 @@ We'll see how long it takes for the fast and slow versions 9 00:00:25,890 --> 00:00:28,143 -一个 BERT 分词器来处理它们。 +一个 BERT tokenizer 来处理它们。 of a BERT tokenizer to process them all. 10 00:00:29,670 --> 00:00:31,380 -我们定义我们的快速和慢速令牌组织者 +我们定义我们的快速和慢速的 tokenizer We define our fast and slow token organizer 11 @@ -55,37 +55,37 @@ using the AutoTokenizer API. 12 00:00:33,717 --> 00:00:37,110 -快速分词器在可用时是默认的。 +快速 tokenizer 在可用时是默认的。 The fast tokenizer is a default when available. 13 00:00:37,110 --> 00:00:40,443 -所以我们通过,使用_fast=False 来定义慢速的。 +所以我们通过设置 use_fast=False 来定义成慢速。 So we pass along, use_fast=False to define the slow one. 14 00:00:41,430 --> 00:00:43,530 -在笔记本中,我们可以为执行计时 +在笔记本中,我们可以计时执行 In a notebook, we can time the execution 15 00:00:43,530 --> 00:00:46,800 -本身带有时间魔法命令,就像这样。 +让其本身附带时间魔术命令,就像这样。 of itself with a time magic command, like this. 16 00:00:46,800 --> 00:00:49,350 处理整个数据集快四倍 -Processing the whole data set is four times faster +Processing the whole dataset is four times faster 17 00:00:49,350 --> 00:00:50,970 -使用快速分词器。 +使用快速 tokenizer 。 with a fast tokenizer. 18 00:00:50,970 --> 00:00:54,000 -确实更快,但不是很令人印象深刻。 +确实更快,但不够吸引人。 That quicker indeed, but not very impressive. 19 @@ -95,22 +95,22 @@ This is because we passed along the texts 20 00:00:55,380 --> 00:00:57,240 -一次一个到分词器。 +一次一个到 tokenizer 。 to the tokenizer one at a time. 21 00:00:57,240 --> 00:00:59,730 -这是快速组织者的常见错误 +这是快速 tokenizer 的常见错误 This is a common mistake to do with fast organizers 22 00:00:59,730 --> 00:01:02,550 -由 Rust 支持,因此能够确定优先级 +由 Rust 支持,因此能够确定优先化 which are backed by Rust, and thus able to prioritize 23 00:01:02,550 --> 00:01:05,370 -多个文本的标记化。 +多个文本的 tokenization 。 the tokenization of multiple texts. 24 @@ -130,7 +130,7 @@ with just one container, it's very inefficient. 27 00:01:13,140 --> 00:01:15,810 -为了释放我们快速分词器的全部速度, +为了释放我们快速 tokenizer 的全部速度, To unleash the full speed of our fast tokenizers, 28 @@ -145,41 +145,41 @@ with the batched=True argument of the map method. 30 00:01:22,620 --> 00:01:25,950 -现在这些都是令人印象深刻的结果,所以快速分词器 +现在这些都是令人印象深刻的结果,所以快速 tokenizer Now those are impressive results, so the fast tokenizer 31 00:01:25,950 --> 00:01:28,410 -处理需要 4 秒的数据集需要 12 秒 +需要 12 秒处理, 而需要 4 takes 12 second to process the dataset that takes four 32 00:01:28,410 --> 00:01:30,093 -分钟到慢分词器。 +分钟, 对于慢速 tokenizer 。 minute to the slow tokenizer. 33 00:01:31,440 --> 00:01:33,510 -总结此表中的结果, +结果总结于此表中, Summarizing the results in this table, 34 00:01:33,510 --> 00:01:36,630 -你可以看到为什么我们快速调用这些分词器。 +你可以看到为什么我们称 tokenizer 为快速。 you can see why we have called those tokenizers fast. 35 00:01:36,630 --> 00:01:38,760 -这仅用于标记化文本。 +这仅用于分词化文本。 And this is only for tokenizing texts. 36 00:01:38,760 --> 00:01:40,710 -如果你需要训练一个新的分词器, +如果你需要训练一个新的 tokenizer , If you ever need to train a new tokenizer, 37 00:01:40,710 --> 00:01:42,523 -他们也很快做到这一点。 +它们也很快做到这一点。 they do this very quickly too. diff --git a/subtitles/zh-CN/44_fast-tokenizer-superpowers.srt b/subtitles/zh-CN/44_fast-tokenizer-superpowers.srt index 70ea63f74..f008cfb0c 100644 --- a/subtitles/zh-CN/44_fast-tokenizer-superpowers.srt +++ b/subtitles/zh-CN/44_fast-tokenizer-superpowers.srt @@ -1,16 +1,16 @@ 1 00:00:05,010 --> 00:00:06,270 -- 快速分词器 +- 快速 tokenizer - The fast tokenizers 2 00:00:06,270 --> 00:00:08,580 -变形金刚图书馆的速度很快, +在 transformers 库中的, 速度很快, of the Transformers library are fast, 3 00:00:08,580 --> 00:00:11,490 -但他们也实现了非常有用的功能 +同时他们也实现了非常有用的功能 but they also implement features that will be super useful 4 @@ -30,7 +30,7 @@ First, let's have a look 7 00:00:18,650 --> 00:00:21,690 -在分词器的通常输出中。 +在 tokenizer 的通常输出中。 at the usual output of a tokenizer. 8 @@ -50,7 +50,7 @@ For instance, 11 00:00:29,010 --> 00:00:31,856 -这里两个句子的标记化是相同的 +这里两个句子的分词化是相同的 here the tokenization is the same for the two sentences 12 @@ -65,17 +65,17 @@ Just having the input IDs is thus not enough 14 00:00:39,150 --> 00:00:42,330 -如果我们想将一些标记与一段文本相匹配, +如果我们想将一些 token 与一段文本相匹配, if we want to match some tokens with a span of text, 15 00:00:42,330 --> 00:00:43,320 -我们需要做的事情 +我们将需要做的事情 something we'll need to do 16 00:00:43,320 --> 00:00:46,111 -例如,在处理问题回答时。 +例如,在处理问答时。 when tackling question answering, for instance. 17 @@ -85,23 +85,23 @@ It's also difficult to know 18 00:00:47,592 --> 00:00:50,850 -当两个标记是否属于同一个词时。 +当两个 token 是否属于同一个词时。 when two tokens belong to the same word or not. 19 00:00:50,850 --> 00:00:52,860 -当你只看输出时看起来很容易 +是容易的, 当你只看 It looks easy when you just look at the output 20 00:00:52,860 --> 00:00:55,650 -一个 BERT 分词器,我们只需要看一下 +一个 BERT 分词器的输出,我们只需要看一下 of a BERT tokenizer where we just need to look 21 00:00:55,650 --> 00:00:56,779 -对于散列散列。 -for the hash hash. +对于 ## 。 +for the ##. 22 00:00:56,779 --> 00:00:59,040 @@ -110,7 +110,7 @@ But other tokenizers have different ways 23 00:00:59,040 --> 00:01:00,987 -标记部分单词。 +分词化部分单词。 to tokenize parts of words. 24 @@ -120,7 +120,7 @@ For instance, RoBERTa adds this special G symbol 25 00:01:04,470 --> 00:01:06,491 -在单词的开头标记标记 +在单词的开头 token 标记 to mark the tokens at the beginning of the word 26 @@ -135,12 +135,12 @@ for the same purpose. 28 00:01:11,150 --> 00:01:14,760 -值得庆幸的是,快速分词器会跟踪这个词 +值得庆幸的是,快速 tokenizer 会跟踪这个词 Thankfully, the fast tokenizers keep track of the word 29 00:01:14,760 --> 00:01:16,230 -每个令牌来自, +每个 token 来自, each token comes from, 30 @@ -155,22 +155,22 @@ The output is not necessarily clear, 32 00:01:21,870 --> 00:01:24,076 -但像这样聚集在一张漂亮的桌子上, +但像这样聚集在一张漂亮的表格上, but assembled together in a nice table like this, 33 00:01:24,076 --> 00:01:26,853 -我们可以查看每个标记的单词位置。 +我们可以查看每个 token 的单词位置。 we can look at the word position for each token. 34 00:01:27,930 --> 00:01:30,220 -更好的是,快速标记器会跟踪 +更好的是,快速 tokenizer 会跟踪 Even better, the fast tokenizers keep track 35 00:01:30,220 --> 00:01:33,198 -每个标记来自的字符范围, +每个 token 来自的字符范围, of the span of characters each token comes from, 36 @@ -180,7 +180,7 @@ and we can get them when calling it on one 37 00:01:35,760 --> 00:01:37,221 -或通过添加几个文本 +或几个文本通过添加 or several text by adding 38 @@ -190,13 +190,13 @@ the return_offsets_mapping=True argument. 39 00:01:40,470 --> 00:01:42,312 -在这种情况下,我们可以看到我们是如何跳仓的 +在这种情况下,我们可以看到我们是如何变换位置的 In this instance, we can see how we jump positions 40 00:01:42,312 --> 00:01:45,650 -在 hash hash token 和 super token 之间, -between the hash hash token and the super token, +在 ## token 和 super token 之间, +between the ## token and the super token, 41 00:01:45,650 --> 00:01:49,992 @@ -205,7 +205,7 @@ because of the multiple spaces in the initial sentence. 42 00:01:49,992 --> 00:01:52,110 -为了实现这一点,快速分词器 +为了实现这一点,快速 tokenizer To enable this, the fast tokenizers 43 @@ -215,27 +215,27 @@ store additional information at each step 44 00:01:54,270 --> 00:01:55,440 -他们的内部管道。 +他们的内部管线( pipeline )。 of their internal pipeline. 45 00:01:55,440 --> 00:01:57,951 -该内部管道包括规范化, +该内部管线包括规范化, That internal pipeline consists of normalization, 46 00:01:57,951 --> 00:02:00,360 -我们对文本进行一些清洁, +我们对文本进行一些整理, where we apply some cleaning to the text, 47 00:02:00,360 --> 00:02:02,621 -喜欢小写或删除口音; +比如小写或删除口音; like lower casing or removing the accents; 48 00:02:02,621 --> 00:02:04,088 -预标记化, +预分词化, pre-tokenization, 49 @@ -245,12 +245,12 @@ which is where we split the texts into words; 50 00:02:06,530 --> 00:02:09,360 -然后我们应用分词器的模型, +然后我们应用 tokenizer 的模型, then we apply the model of the tokenizer, 51 00:02:09,360 --> 00:02:11,725 -这是单词被分成标记的地方, +这是单词被分成 token 的地方, which is where the words are split into tokens, 52 @@ -260,22 +260,22 @@ before finally doing the post processing, 53 00:02:13,748 --> 00:02:16,023 -添加特殊标记的地方。 +添加特殊 token 的地方。 where special tokens are added. 54 00:02:17,100 --> 00:02:19,050 -从管道的开始到结束, +从管线的开始到结束, From the beginning to the end of the pipeline, 55 00:02:19,050 --> 00:02:21,390 -标记器跟踪每个文本范围 +tokenizer 跟踪每个文本范围 the tokenizer keeps track of each span of text 56 00:02:21,390 --> 00:02:23,853 -对应于每个单词,然后是每个标记。 +对应于每个单词,然后是每个 token 。 that corresponds to each word, then each token. 57 @@ -295,12 +295,12 @@ when doing masked language modeling 60 00:02:29,549 --> 00:02:32,407 -一种获得最先进结果的变体 +一种获得最 SOTA 的变体 one variation that gets state-of-the-art results 61 00:02:32,407 --> 00:02:35,040 -是屏蔽给定单词的所有标记 +是屏蔽给定单词的所有 token is to mask all the tokens of a given word 62 @@ -325,7 +325,7 @@ we'll need to convert the labels we have on words, 66 00:02:45,090 --> 00:02:47,250 -每个标记上的标签。 +每个 token 上的标签。 to labels on each tokens. 67 @@ -340,7 +340,7 @@ it will be super useful when we need to convert 69 00:02:50,610 --> 00:02:53,436 -将句子中的标记位置转换为一段文本, +将句子中的 token 位置转换为一段文本, token positions in a sentence into a span of text, 70 @@ -350,17 +350,17 @@ which we'll need to know when we're looking 71 00:02:55,800 --> 00:02:56,813 -在回答问题时 +在问答时 at question answering 72 00:02:56,813 --> 00:02:58,680 -或者在对相应的标记进行分组时 +或者在对相应的 token 进行分组时 or when grouping the tokens corresponding 73 00:02:58,680 --> 00:03:01,023 -到令牌分类中的同一实体。 +到 token 分类中的同一实体。 to the same entity in token classification. 74 diff --git a/subtitles/zh-CN/45_inside-the-token-classification-pipeline-(pytorch).srt b/subtitles/zh-CN/45_inside-the-token-classification-pipeline-(pytorch).srt index 4f9310845..22106ec0e 100644 --- a/subtitles/zh-CN/45_inside-the-token-classification-pipeline-(pytorch).srt +++ b/subtitles/zh-CN/45_inside-the-token-classification-pipeline-(pytorch).srt @@ -20,27 +20,27 @@ 5 00:00:06,210 --> 00:00:08,283 -在令牌分类管道内。 +在 token 分类管线( pipeline )内。 inside the token classification pipeline. 6 00:00:10,080 --> 00:00:11,580 -在管道视频中, +在有关管线视频中, In the pipeline video, 7 00:00:11,580 --> 00:00:13,320 -我们研究了不同的应用 +我们学习了不同的应用 we looked at the different applications 8 00:00:13,320 --> 00:00:15,960 -Transformers 库支持开箱即用, +transformers 库支持开箱即用的, the Transformers library supports out of the box, 9 00:00:15,960 --> 00:00:18,780 -其中之一是令牌分类, +其中之一是 token 分类, one of them being token classification, 10 @@ -55,17 +55,17 @@ whether they correspond to a person, an organization 12 00:00:24,510 --> 00:00:25,353 -或位置。 +或一个位置。 or a location. 13 00:00:26,670 --> 00:00:28,920 -我们甚至可以将相应的标记组合在一起 +我们甚至可以将相应的 token 组合在一起 We can even group together the tokens corresponding 14 00:00:28,920 --> 00:00:32,040 -到同一个实体,例如所有令牌 +到同一个实体,例如所有 token to the same entity, for instance all the tokens 15 @@ -75,12 +75,12 @@ that formed the word Sylvain here, or Hugging and Face. 16 00:00:37,290 --> 00:00:40,230 -令牌分类管道的工作方式相同 + token 分类管线的工作方式相同 The token classification pipeline works the same way 17 00:00:40,230 --> 00:00:42,630 -作为我们研究的文本分类管道 +和我们研究的文本分类的管线 as the text classification pipeline we studied 18 @@ -95,7 +95,7 @@ There are three steps. 20 00:00:45,930 --> 00:00:49,623 -标记化、模型和后处理。 +分词化、模型和后处理。 The tokenization, the model, and the postprocessing. 21 @@ -105,12 +105,12 @@ The first two steps are identical 22 00:00:52,530 --> 00:00:54,630 -到文本分类管道, +到文本分类管线, to the text classification pipeline, 23 00:00:54,630 --> 00:00:57,300 -除了我们使用自动标记分类模型 +除了我们使用 auto (自动) 的 token 分类模型 except we use an auto token classification model 24 @@ -120,7 +120,7 @@ instead of a sequence classification one. 25 00:01:00,150 --> 00:01:03,720 -我们标记我们的文本,然后将其提供给模型。 +我们分词化我们的文本,然后将其提供给模型。 We tokenize our text then feed it to the model. 26 @@ -140,13 +140,13 @@ for each of the possible nine labels 29 00:01:10,770 --> 00:01:13,983 -对于句子中的每个标记,此处为 19。 +对于句子中的每个 token ,此处为 19。 for every token in the sentence, here 19. 30 00:01:15,300 --> 00:01:18,090 -与 Transformers 库的所有其他模型一样, -Like all the other models of the Transformers library, +与 transformers 库的所有其他模型一样, +Like all the other models of the transformers library, 31 00:01:18,090 --> 00:01:19,830 @@ -155,12 +155,12 @@ our model outputs logits, 32 00:01:19,830 --> 00:01:23,073 -我们使用 SoftMax 将其转化为预测。 +我们使用 SoftMax 将其转化为预测值。 which we turn into predictions by using a SoftMax. 33 00:01:23,940 --> 00:01:26,190 -我们还获得了每个标记的预测标签 +我们还获得了每个 token 的预测标签 We also get the predicted label for each token 34 @@ -195,7 +195,7 @@ in its id2label field. 40 00:01:37,740 --> 00:01:41,430 -使用它,我们可以将每个标记映射到其相应的标签。 +使用它,我们可以将每个 token 映射到其相应的标签。 Using it, we can map every token to its corresponding label. 41 @@ -250,7 +250,7 @@ if you don't know about them already. 51 00:02:00,300 --> 00:02:02,280 -然后,遍历每个标记 +然后,遍历每个 token Then, looping through each token 52 @@ -265,12 +265,12 @@ we can build the list of results we got 54 00:02:06,120 --> 00:02:07,320 -用我们的第一条管道。 +用我们的第一条管线。 with our first pipeline. 55 00:02:08,460 --> 00:02:10,560 -最后一步是将标记组合在一起 +最后一步是将 token 组合在一起 The last step is to group together tokens 56 @@ -290,7 +290,7 @@ I-PER and B-PER, for instance. 59 00:02:18,450 --> 00:02:20,100 -它让我们知道一个令牌是否是 +它让我们知道一个 token 是否是 It allows us to know if a token is 60 @@ -305,7 +305,7 @@ Note, that there are two ways of labeling used 62 00:02:25,350 --> 00:02:26,850 -用于令牌分类。 +用于 token 分类。 for token classification. 63 diff --git a/subtitles/zh-CN/46_inside-the-token-classification-pipeline-(tensorflow).srt b/subtitles/zh-CN/46_inside-the-token-classification-pipeline-(tensorflow).srt index e216a08ff..6cab3657c 100644 --- a/subtitles/zh-CN/46_inside-the-token-classification-pipeline-(tensorflow).srt +++ b/subtitles/zh-CN/46_inside-the-token-classification-pipeline-(tensorflow).srt @@ -10,12 +10,12 @@ 3 00:00:06,143 --> 00:00:08,133 -在令牌分类管道内。 +在 token 分类管线(pipeline)内。 inside the token classification pipeline. 4 00:00:09,780 --> 00:00:11,430 -在管道视频中, +在有关管线的视频中, In the pipeline video, 5 @@ -25,12 +25,12 @@ we looked at the different applications 6 00:00:13,230 --> 00:00:16,050 -Transformers 库支持开箱即用。 -the Transformers library supports out of the box. +transformers 库支持开箱即用的。 +the transformers library supports out of the box. 7 00:00:16,050 --> 00:00:18,660 -其中之一是令牌分类。 +其中之一是 token 分类。 One of them being token classification. 8 @@ -50,7 +50,7 @@ an organization, or location. 11 00:00:27,690 --> 00:00:29,250 -我们甚至可以将令牌组合在一起 +我们甚至可以将 token 组合在一起 We can even group together the tokens 12 @@ -60,7 +60,7 @@ corresponding to the same entity. 13 00:00:31,320 --> 00:00:34,890 -例如,这里构成单词 Sylvain 的所有标记 +例如,这里构成单词 Sylvain 的所有 token For instance, all the tokens that form the word Sylvain here 14 @@ -70,12 +70,12 @@ or Hugging and Face. 15 00:00:37,320 --> 00:00:39,720 -因此,令牌分类管道 +因此,token 分类管线 So, token classification pipeline 16 00:00:39,720 --> 00:00:42,480 -与文本分类管道的工作方式相同 +与文本分类管线的工作方式相同 works the same way as a text classification pipeline 17 @@ -90,7 +90,7 @@ There are three steps. 19 00:00:46,500 --> 00:00:50,043 -标记化、模型和后处理。 +分词化、模型和后处理。 Tokenization, the model, and the post processing. 20 @@ -105,17 +105,17 @@ to the text classification pipeline, 22 00:00:55,230 --> 00:00:58,230 -除了我们使用自动标记分类模型 +除了我们使用 auto 的 token 分类模型 except we use an auto token classification model 23 00:00:58,230 --> 00:01:00,303 -而不是序列分类。 +而不是分类。 instead of a sequence classification one. 24 00:01:01,560 --> 00:01:04,593 -我们标记我们的文本,然后将其提供给模型。 +我们分词化我们的文本,然后将其提供给模型。 We tokenize our text, then feed it to the model. 25 @@ -135,7 +135,7 @@ we get one number for each of the possible nine levels 28 00:01:12,270 --> 00:01:14,250 -对于句子中的每个标记。 +对于句子中的每个 token 。 for every token in the sentence. 29 @@ -145,12 +145,12 @@ Here, 19. 30 00:01:17,070 --> 00:01:19,710 -与 Transformers 库的所有其他模型一样, -Like all the other models of the Transformers library, +与 transformers 库的所有其他模型一样, +Like all the other models of the transformers library, 31 00:01:19,710 --> 00:01:22,560 -我们的模型输出我们需要转换的逻辑 +我们的模型输出我们需要转换的 logits our model outputs logits which we need to turn 32 @@ -160,7 +160,7 @@ into predictions by using a SoftMax. 33 00:01:25,830 --> 00:01:28,170 -我们还获得了每个标记的预测标签 +我们还获得了每个 token 的预测标签 We also get the predicted label for each token 34 @@ -195,12 +195,12 @@ in its id2label field. 40 00:01:42,090 --> 00:01:45,600 -使用它,我们可以将每个标记映射到其相应的标签。 +使用它,我们可以将每个 token 映射到其相应的标签。 Using it, we can map every token to its corresponding label. 41 00:01:45,600 --> 00:01:48,630 -标签 O 对应 “无实体” +标签 O 对应 “no entity” (没有实体) The label O corresponds to "no entity" 42 @@ -235,7 +235,7 @@ We'll need to use the offset mapping 48 00:01:59,880 --> 00:02:01,110 -分词器得到那些。 +对于 tokenizer 以得到那些。 of the tokenizer to get those. 49 @@ -250,7 +250,7 @@ if you don't know about them already. 51 00:02:05,340 --> 00:02:06,990 -然后,遍历每个标记 +然后,遍历每个 token Then, looping through each token 52 @@ -265,12 +265,12 @@ we can build the list of results 54 00:02:10,590 --> 00:02:12,140 -我们得到了我们的第一条管道。 +我们得到了我们的第一条管线。 we got with our first pipeline. 55 00:02:13,650 --> 00:02:15,840 -最后一步是将标记组合在一起 +最后一步是将 token 组合在一起 The last step is to group together tokens 56 @@ -290,7 +290,7 @@ I-PER and B-PER for instance. 59 00:02:23,940 --> 00:02:25,530 -它让我们知道一个令牌是否 +它让我们知道一个 token 是否 It allows us to know if a token 60 @@ -305,7 +305,7 @@ Note that there are two ways 62 00:02:29,850 --> 00:02:32,490 -用于标记分类的标签。 +用于 token 分类的标签。 of labeling used for token classification. 63 diff --git a/subtitles/zh-CN/47_inside-the-question-answering-pipeline-(pytorch).srt b/subtitles/zh-CN/47_inside-the-question-answering-pipeline-(pytorch).srt index 5ce8d0e78..69ebc63b6 100644 --- a/subtitles/zh-CN/47_inside-the-question-answering-pipeline-(pytorch).srt +++ b/subtitles/zh-CN/47_inside-the-question-answering-pipeline-(pytorch).srt @@ -1,21 +1,21 @@ 1 00:00:04,230 --> 00:00:07,699 -- 让我们来看看问答管道的内部情况。 +- 让我们来看看问答管线的内部情况。 - Let's have a look inside the question answering pipeline. 2 00:00:07,699 --> 00:00:10,680 -问答管道可以提取答案 +问答管线可以提取答案 The question answering pipeline can extracts answers 3 00:00:10,680 --> 00:00:14,190 -从给定的上下文或文本段落中提出问题, +对问题, 从给定的上下文或文本段落中, to questions from a given context or passage of text, 4 00:00:14,190 --> 00:00:16,540 -就像变形金刚回购自述文件的这一部分。 +就像 transformers 仓库的 README 文档的这部分。 like this part of the transformers repo README. 5 @@ -25,7 +25,7 @@ It also works for very long contexts, 6 00:00:20,310 --> 00:00:23,850 -即使答案在最后,就像这个例子一样。 +即使答案很靠后,就像这个例子一样。 even if the answer is at the very end, like in this example. 7 @@ -35,12 +35,12 @@ In this video, we will see why. 8 00:00:26,820 --> 00:00:29,460 -问答管道遵循相同的步骤 +问答管线遵循相同的步骤 The question answering pipeline follows the same steps 9 00:00:29,460 --> 00:00:31,050 -与其他管道一样: +与其他 pipeline 一样: as the other pipelines: 10 @@ -55,7 +55,7 @@ fed to the model then some post-processing is applied. 12 00:00:37,955 --> 00:00:41,730 -令牌化和模型步骤应该很熟悉。 +分词化和模型步骤应该很熟悉。 The tokenization and model steps should be familiar. 13 @@ -75,12 +75,12 @@ but one key difference with text classification 16 00:00:49,392 --> 00:00:52,980 -是我们的模型输出两个名为 start logits 的张量 +是我们的模型输出两个张量, 名为 start logits is that our model outputs two tensors named start logits 17 00:00:52,980 --> 00:00:54,570 -并结束登录。 +和 end logits 。 and end logits. 18 @@ -95,7 +95,7 @@ Well, this is the way the model finds the answer 20 00:00:57,930 --> 00:00:58,803 -到这个问题。 +对这个问题。 to the question. 21 @@ -105,27 +105,27 @@ First, let's have a look at the model inputs. 22 00:01:02,130 --> 00:01:04,350 -它的数字与令牌化相关联 +它的数字相关联于分词化 Its numbers associated with the tokenization 23 00:01:04,350 --> 00:01:06,843 -问题后跟上下文 +问题, 对应于上下文 of the question followed by the context 24 00:01:06,843 --> 00:01:09,723 -使用通常的 CLS 和 SEP 特殊令牌。 +使用通常的 CLS 和 SEP 特殊 token 。 with the usual CLS and SEP special tokens. 25 00:01:10,620 --> 00:01:13,320 -答案是那些令牌的一部分。 +答案是那些 token 的一部分。 The answer is a part of those tokens. 26 00:01:13,320 --> 00:01:15,510 -所以我们要求模型预测哪个令牌开始 +所以我们要求模型预测哪个 token 开始 So we ask the model to predict which token starts 27 @@ -140,7 +140,7 @@ For our two logit outputs, 29 00:01:19,650 --> 00:01:22,803 -理论标签是粉色和紫色的向量。 +理论上的标签是粉色和紫色的向量。 the theoretical labels are the pink and purple vectors. 30 @@ -155,17 +155,17 @@ we will need to apply a SoftMax, 32 00:01:28,436 --> 00:01:30,360 -就像在文本分类管道中一样。 +就像在文本分类管线中一样。 like in the text classification pipeline. 33 00:01:30,360 --> 00:01:33,390 -我们只是屏蔽不属于上下文的标记 +我们只是掩蔽不属于上下文的 token We just mask the tokens that are not part of the context 34 00:01:33,390 --> 00:01:36,855 -在此之前,不屏蔽初始 CLS 令牌 +在此之前,不掩蔽初始 CLS token before doing that, leaving the initial CLS token unmasked 35 @@ -190,23 +190,23 @@ since its exponential will then be zero. 39 00:01:48,957 --> 00:01:50,580 -现在,每个开始的概率 +现在,每个概率, 对于开始的 Now, the probability for each start 40 00:01:50,580 --> 00:01:53,550 -和对应于可能答案的结束位置, +和对于结束的可能答案的位置, and end position corresponding to a possible answer, 41 00:01:53,550 --> 00:01:55,050 -我们给的分数就是产品 -we give a score that is the product +我们给的分数就是 +we give a score that is the 42 00:01:55,050 --> 00:01:57,630 -开始概率和结束概率 -of the start probabilities and end probabilities +开始概率和结束概率之乘积 +product of the start probabilities and end probabilities 43 00:01:57,630 --> 00:01:58,803 @@ -230,12 +230,12 @@ Here is the code to find the best score 47 00:02:07,080 --> 00:02:08,820 -一个可能的答案。 +对一个可能的答案。 for a possible answer. 48 00:02:08,820 --> 00:02:11,430 -一旦我们有了令牌的开始和结束位置, +一旦我们有了这些 token 的开始和结束位置, Once we have the start and end positions of the tokens, 49 @@ -275,7 +275,7 @@ the whole answer, being truncated. 56 00:02:29,100 --> 00:02:31,050 -所以我们不丢弃截断的标记 +所以我们不丢弃截断的 token So we don't discard the truncated tokens 57 @@ -300,12 +300,12 @@ If we take disjoint chunks of texts, 61 00:02:41,430 --> 00:02:43,530 -我们最终可能会得到分裂的答案 +我们最终可能会得到拆分的答案 we might end up with the answer being split 62 00:02:43,530 --> 00:02:45,330 -两个特征之间。 +于两个特征之间。 between two features. 63 @@ -325,7 +325,7 @@ the answer to the question. 66 00:02:52,830 --> 00:02:55,260 -标记器自动为我们完成所有这些 +分词器自动为我们完成所有这些 The tokenizers do all of this for us automatically 67 @@ -340,7 +340,7 @@ The stride argument controls 69 00:02:59,700 --> 00:03:02,070 -重叠标记的数量。 +重叠 token 的数量。 the number of overlapping tokens. 70 @@ -365,7 +365,7 @@ for each feature, we get the answer with a score 74 00:03:10,636 --> 00:03:12,453 -对于他们每个人, +对于他们每个, for each of them, 75 diff --git a/subtitles/zh-CN/48_inside-the-question-answering-pipeline-(tensorflow).srt b/subtitles/zh-CN/48_inside-the-question-answering-pipeline-(tensorflow).srt index cdb7d661a..c42c93ce5 100644 --- a/subtitles/zh-CN/48_inside-the-question-answering-pipeline-(tensorflow).srt +++ b/subtitles/zh-CN/48_inside-the-question-answering-pipeline-(tensorflow).srt @@ -5,12 +5,12 @@ 2 00:00:05,490 --> 00:00:08,440 -- 让我们来看看问答管道的内部情况。 +- 让我们来看看问答 pipeline(管线) 的内部情况。 - Let's have a look inside the question answering pipeline. 3 00:00:09,780 --> 00:00:11,370 -问答管道 +问答管线 The question answering pipeline 4 @@ -25,7 +25,7 @@ from a given context or passage of text 6 00:00:16,020 --> 00:00:18,370 -就像变形金刚回购自述文件的这一部分。 +就像 transformer 仓库的 README 文件的这一部分。 like this part of the Transformers repo README. 7 @@ -35,7 +35,7 @@ It also works for very long context, 8 00:00:21,180 --> 00:00:24,720 -即使答案在最后,就像这个例子一样。 +即使答案靠后,就像这个例子一样。 even if the answer is at the very end, like in this example. 9 @@ -45,12 +45,12 @@ In this video, we'll see why. 10 00:00:27,840 --> 00:00:29,310 -问答管道 +问答管线 The question answering pipeline 11 00:00:29,310 --> 00:00:32,130 -遵循与其他管道相同的步骤。 +遵循与其他管线相同的步骤。 follows the same steps as the other pipelines. 12 @@ -65,7 +65,7 @@ fed to the model then some post-processing is applied. 14 00:00:39,540 --> 00:00:42,840 -所以标记化和模型步骤应该很熟悉。 +所以分词化和模型步骤应该很熟悉。 So tokenization and model steps should be familiar. 15 @@ -90,7 +90,7 @@ is that our model outputs two tensors 19 00:00:52,380 --> 00:00:55,230 -命名开始 logits 和结束 logits。 +命名 start logits 和 end logits。 named start logits and end logits. 20 @@ -105,7 +105,7 @@ Well, this is the way the model finds the answer 22 00:00:58,170 --> 00:00:59,043 -到这个问题。 +对这个问题。 to the question. 23 @@ -114,28 +114,28 @@ to the question. First, let's have a look at the model inputs. 24 -00:01:02,610 --> 00:01:04,800 -它是与标记化相关的数字 -It's numbers associated with the tokenization +00:01:02,610 --> 00:01:05,800 +它是与问题分词化相关的数字 +It's numbers associated with the tokenization of the question 25 -00:01:04,800 --> 00:01:05,850 -的问题, -of the question, +00:01:05,800 --> 00:01:05,850 +, +, 26 00:01:05,850 --> 00:01:07,753 -其次是上下文 +对于该上下文 followed by the context 27 00:01:07,753 --> 00:01:10,233 -使用通常的 CLS 和 SEP 特殊令牌。 +使用通常的 CLS 和 SEP 特殊 token 。 with the usual CLS and SEP special tokens. 28 00:01:11,130 --> 00:01:13,203 -答案是那些令牌的一部分。 +答案是那些 token 的一部分。 The answer is a part of those tokens. 29 @@ -145,7 +145,7 @@ So we ask the model to predict 30 00:01:15,330 --> 00:01:17,040 -哪个标记开始回答 +哪个 token 开始回答 which token starts the answer 31 @@ -175,12 +175,12 @@ we will need to apply a SoftMax, 36 00:01:28,596 --> 00:01:31,020 -就像在文本分类管道中一样。 +就像在文本分类管线中一样。 like in the text classification pipeline. 37 00:01:31,020 --> 00:01:32,310 -我们只是屏蔽令牌 +我们只是掩蔽 token We just mask the tokens 38 @@ -190,12 +190,12 @@ that are not part of the context before doing that, 39 00:01:35,940 --> 00:01:38,310 -不屏蔽初始 CLS 令牌 +不掩蔽初始 CLS token leaving the initial CLS token unmasked 40 00:01:38,310 --> 00:01:40,773 -因为我们用它来预测一个不可能的答案。 +我们用它来预测一个不可能的答案。 as we use it to predict an impossible answer. 41 @@ -230,13 +230,13 @@ will give a score that is a product 47 00:01:57,540 --> 00:01:58,680 -开始概率 -of the start probabilities +开始概率和结束概率 +of the start probabilities and end probabilities 48 -00:01:58,680 --> 00:02:00,873 -并在这些位置结束概率。 -and end probabilities at those position. +00:01:59,680 --> 00:02:00,873 +在这些位置。 +at those position. 49 00:02:01,920 --> 00:02:04,530 @@ -255,12 +255,12 @@ Here is the code to find the best score 52 00:02:09,510 --> 00:02:11,280 -一个可能的答案。 +对一个可能的答案。 for a possible answer. 53 00:02:11,280 --> 00:02:13,830 -一旦我们有了令牌的开始和结束位置, +一旦我们有了 token 的开始和结束位置, Once we have the start and end position for the tokens, 54 @@ -300,7 +300,7 @@ the whole answer, being truncated. 61 00:02:32,190 --> 00:02:34,020 -所以我们不丢弃截断的标记 +所以我们不丢弃截断的 token So we don't discard the truncated tokens 62 @@ -360,7 +360,7 @@ with the return overflowing tokens option. 73 00:03:01,920 --> 00:03:02,753 -步幅论证 +步幅参数 The stride argument 74 diff --git a/subtitles/zh-CN/49_what-is-normalization.srt b/subtitles/zh-CN/49_what-is-normalization.srt index edff41bf9..1c0c83dfa 100644 --- a/subtitles/zh-CN/49_what-is-normalization.srt +++ b/subtitles/zh-CN/49_what-is-normalization.srt @@ -10,27 +10,27 @@ 3 00:00:07,380 --> 00:00:09,930 -什么是标准化组件 +什么是规范化组件 what is the normalizer component 4 00:00:09,930 --> 00:00:13,023 -我们会在每个分词器的开头找到它。 +我们会在每个 tokenizer 的开头找到它。 that we'd find at the beginning of each tokenizer. 5 00:00:14,550 --> 00:00:16,830 -归一化操作包括 +规范化操作包括 The normalization operation consists 6 00:00:16,830 --> 00:00:19,890 -在应用一系列规范化规则时 +在应用一系列规范化规则 in applying a succession of normalization rules 7 00:00:19,890 --> 00:00:20,853 -到原始文本。 +到原始文本时。 to the raw text. 8 @@ -50,12 +50,12 @@ and use of our language model. 11 00:00:33,090 --> 00:00:37,470 -让我们用不同的字体来看一个非常多样化的句子, +让我们来看一个非常多样化的句子,用不同的字体 Let's take a very diverse sentence with different fonts, 12 00:00:37,470 --> 00:00:39,780 -大写和小写字符, +大写和小写的字符, upper and lower case characters, 13 @@ -65,12 +65,12 @@ accents, punctuation and multiple spaces, 14 00:00:43,920 --> 00:00:46,683 -看看几个分词器是如何规范化它的。 +看看几个 tokenizer 是如何规范化它的。 to see how several tokenizer normalize it. 15 00:00:48,488 --> 00:00:50,730 -来自 FNet 模型的分词器 +来自 FNet 模型的 tokenizer The tokenizer from the FNet model 16 @@ -110,7 +110,7 @@ with several font variants and keeps the multiple spaces, 23 00:01:12,090 --> 00:01:14,223 -但它删除了所有的口音。 +但它删除了所有的重音。 but it removes all the accents. 24 @@ -120,12 +120,12 @@ And if we continue to test this normalization 25 00:01:18,870 --> 00:01:23,040 -与模型相关的许多其他分词器 +与模型相关的许多其他 tokenizer of many other tokenizers associated to models 26 00:01:23,040 --> 00:01:25,110 -我们可以在集线器上找到, +我们可以在 Hub 上找到, that we can find on the Hub, 27 @@ -135,12 +135,12 @@ we see that they also propose other kind of normalization. 28 00:01:33,900 --> 00:01:35,850 -使用快速分词器, +使用快速 tokenizer , With the fast tokenizers, 29 00:01:35,850 --> 00:01:39,060 -很容易观察到选择的归一化 +很容易观察到选择的规范化 it's very easy to observe the normalization chosen 30 @@ -150,12 +150,12 @@ for the currently loaded tokenizer. 31 00:01:42,330 --> 00:01:46,140 -事实上,每个快速分词器的实例 +事实上,每个快速 tokenizer 的实例 Indeed, each instance of a fast tokenizer 32 00:01:46,140 --> 00:01:48,030 -有一个底层分词器 +有一个底层 tokenizer has an underlying tokenizer 33 @@ -175,12 +175,12 @@ This object has itself a normalizer attribute 36 00:01:58,470 --> 00:02:01,830 -我们可以使用 normalize_str 方法 +我们可以使用, 多亏 normalize_str 方法 that we can use thanks to the normalize_str method 37 00:02:01,830 --> 00:02:03,153 -规范化一个字符串。 +以规范化一个字符串。 to normalize a string. 38 @@ -190,12 +190,12 @@ It is thus very practical that this normalization, 39 00:02:08,700 --> 00:02:11,070 -这是在训练时使用的 +使用在训练时 which was used at the time of the training 40 00:02:11,070 --> 00:02:12,903 -分词器的保存, +保存分词器, of the tokenizer was saved, 41 @@ -205,7 +205,7 @@ and that it applies automatically 42 00:02:16,200 --> 00:02:19,233 -当你要求训练有素的分词器对文本进行分词时。 +当你要求训练过的 tokenizer 对文本进行分词时。 when you ask a trained tokenizer to tokenize a text. 43 @@ -215,7 +215,7 @@ For example, if we hadn't included the albert normalizer, 44 00:02:25,500 --> 00:02:28,770 -我们会有很多未知的代币 +我们会有很多未知的 token we would have had a lot of unknown tokens 45 @@ -230,12 +230,12 @@ with accents and capital letters. 47 00:02:35,730 --> 00:02:38,370 -这种转变也可能是检测不到的 +这种转变也可能是无法检测的 This transformation can also be undetectable 48 00:02:38,370 --> 00:02:40,050 -带有简单的打印。 +通过简单的打印出来。 with a simple print. 49 @@ -250,12 +250,12 @@ text is only a succession of 0 and 1, 51 00:02:45,840 --> 00:02:47,820 -碰巧不同的继承 +碰巧是不同的承接 and it happens that different successions 52 00:02:47,820 --> 00:02:51,363 -0 和 1 呈现相同的打印字符。 +让 0 和 1 呈现相同的打印字符。 of 0 and 1 render the same printed character. 53 @@ -275,7 +275,7 @@ into a sequence of code points. 56 00:03:04,530 --> 00:03:09,530 -在我们的示例中,2 个字节使用 UTF-8 解码 +在我们的示例中,2 个字节通过 UTF-8 解码 In our example, the 2 bytes is decoded using UTF-8 57 @@ -305,7 +305,7 @@ Let's repeat the same operation 62 00:03:23,790 --> 00:03:26,577 -有了这个由 3 个字节组成的新序列,。 +有了这个由 3 个字节组成的新序列, with this new sequence composed of 3 bytes,. 63 @@ -340,7 +340,7 @@ But it's annoying because what appears to us 69 00:03:45,000 --> 00:03:46,680 -成为一个单一的角色 +为了成为一个单一的字符 to be a single character 70 @@ -365,7 +365,7 @@ that allow erasing some of these differences. 74 00:04:05,730 --> 00:04:08,223 -这些标准通常由分词器使用。 +这些标准通常由 tokenizer 使用。 These standards are often used by tokenizers. 75 @@ -400,7 +400,7 @@ However, you must be aware that some normalizations 81 00:04:25,980 --> 00:04:30,363 -如果他们不适应他们的语料库,可能会非常有害。 +如果他们不适配他们的语料库,可能会非常有害。 can be very harmful if they are not adapted to their corpus. 82 @@ -415,7 +415,7 @@ For example, if you take the French sentence, 84 00:04:38,790 --> 00:04:42,510 -并使用 bert-base-uncase 分词器对其进行规范化 +并使用 bert-base-uncase tokenizer 对其进行规范化 and normalize it with the bert-base-uncase tokenizer 85 @@ -435,17 +435,17 @@ which means "An unworthy father". 88 00:04:53,460 --> 00:04:56,760 -如果你观看此视频是为了构建自己的分词器, +如果你观看此视频是为了构建自己的 tokenizer , If you watched this video to build your own tokenizer, 89 00:04:56,760 --> 00:04:59,610 -选择与否没有绝对的规则 +没有绝对的规则选择与否 there are no absolute rules to choose or not 90 00:04:59,610 --> 00:05:02,970 -新分词器的规范化, +一个新 tokenizer 的规范化, a normalization for a new tokenizer, 91 diff --git a/subtitles/zh-CN/50_what-is-pre-tokenization.srt b/subtitles/zh-CN/50_what-is-pre-tokenization.srt index 6b2870fe3..1068c69c2 100644 --- a/subtitles/zh-CN/50_what-is-pre-tokenization.srt +++ b/subtitles/zh-CN/50_what-is-pre-tokenization.srt @@ -1,6 +1,6 @@ 1 00:00:05,550 --> 00:00:08,910 -- 标记化管道涉及几个步骤 +- 分词化管线涉及几个步骤 - The tokenization pipeline involves several steps 2 @@ -15,22 +15,22 @@ In this video, we will see what happens 4 00:00:14,280 --> 00:00:16,293 -在预标记化步骤中。 +在预分词化步骤中。 during the pre-tokenization step. 5 00:00:18,390 --> 00:00:22,110 -pre-tokenization 操作是执行的操作 +预分词化操作是操作执行在 The pre-tokenization operation is the operation performed 6 00:00:22,110 --> 00:00:24,630 -文本归一化后 +文本规范化后 after the normalization of the text 7 00:00:24,630 --> 00:00:27,633 -在应用标记化算法之前。 +在应用分词化算法之前。 and before the application of the tokenization algorithm. 8 @@ -55,12 +55,12 @@ Let's look at how several tokenizers 12 00:00:41,310 --> 00:00:43,143 -在此示例中进行预标记。 +在此示例中进行预分词。 pre-tokenize in this example. 13 00:00:46,200 --> 00:00:50,820 -gpt2 pre-tokenization 在空格上划分文本 +gpt2 pre-tokenization 把文本划分到空格 The gpt2 pre-tokenization divides the text on spaces 14 @@ -80,7 +80,7 @@ by capital G with a dot above. 17 00:01:07,170 --> 00:01:09,540 -Albert 的预标记化将文本划分 +Albert 的预分词化将文本划分 Albert's pre-tokenization divides the text 18 @@ -115,12 +115,12 @@ But unlike the previous tokenizers, 24 00:01:31,260 --> 00:01:33,780 -空间没有变换 +空格没有变换 spaces are not transformed 25 00:01:33,780 --> 00:01:37,293 -并集成到使用此预标记器生成的令牌中。 +并集成到使用此预标记器生成的 token 中。 and integrated into tokens produced with this pre-tokenizer. 26 @@ -135,7 +135,7 @@ we could observe the two main type of operation 28 00:01:45,330 --> 00:01:47,073 -预标记化带来的; +预分词化带来的; brought by the pre-tokenization; 29 @@ -160,7 +160,7 @@ Finally, the backend tokenizer of the fast tokenizers 33 00:02:04,230 --> 00:02:07,680 -还允许测试预标记化操作 +还允许测试预分词化操作 also allows to test the pre-tokenization operation 34 @@ -175,12 +175,12 @@ We notice that the output of this operation 36 00:02:14,970 --> 00:02:18,450 -由令牌和偏移量组成, +由 token 和偏移量组成, is composed of both tokens and offsets, 37 00:02:18,450 --> 00:02:21,960 -允许将标记链接到它在文本中的位置 +允许将 token 链接到它在文本中的位置 which allow to link the tokens to its position in the text 38 @@ -190,22 +190,22 @@ given in input of the method. 39 00:02:25,650 --> 00:02:28,860 -此操作定义了最大的令牌 +此操作定义了最大的 token This operation defines the largest tokens 40 00:02:28,860 --> 00:02:31,740 -可以通过标记化产生, +可以通过分词化产生, that can be produced by the tokenization, 41 00:02:31,740 --> 00:02:36,090 -或者换句话说,子令牌的障碍 +或者换句话说,子 token 的障碍 or in those words, the barriers of the sub-tokens 42 00:02:36,090 --> 00:02:37,653 -届时将生产。 +届时将产生。 which will be produced then. 43 @@ -215,6 +215,6 @@ And that's all for the characteristic 44 00:02:41,850 --> 00:02:43,203 -预分词器。 +对预分词器。 of the pre-tokenizers. diff --git a/subtitles/zh-CN/51_byte-pair-encoding-tokenization.srt b/subtitles/zh-CN/51_byte-pair-encoding-tokenization.srt index 513b464a9..9bad1de88 100644 --- a/subtitles/zh-CN/51_byte-pair-encoding-tokenization.srt +++ b/subtitles/zh-CN/51_byte-pair-encoding-tokenization.srt @@ -10,12 +10,12 @@ 3 00:00:06,720 --> 00:00:10,464 -如果你想了解什么是字节对编码 +如果你想了解什么是字节对编码(BPE)算法 if you want to understand what the Byte Pair Encoding 4 00:00:10,464 --> 00:00:13,263 -子词标记化算法是, +子词分词化算法是, subword tokenization algorithm is, 5 @@ -25,7 +25,7 @@ how to train it 6 00:00:15,505 --> 00:00:17,790 -以及文本的标记化是如何完成的 +以及文本的分词化是如何完成的 and how the tokenization of a text is done 7 @@ -65,12 +65,12 @@ into a sequence of'subword units' 14 00:00:38,100 --> 00:00:41,970 -哪些是参考语料库中频繁出现的单位 +其是参考语料库中频繁出现的单位 which are units that appear frequently in a reference corpus 15 00:00:41,970 --> 00:00:44,613 -也就是我们用来训练它的语料库。 +也是我们用来训练它的语料库。 which is, the corpus we used to train it. 16 @@ -90,7 +90,7 @@ We will not train our tokenizer on this raw text 19 00:00:56,940 --> 00:00:59,490 -但我们首先将其标准化 +但我们首先将其规范化 but we will first normalize it 20 @@ -120,7 +120,7 @@ by gathering together the same words 25 00:01:10,350 --> 00:01:12,450 -并通过维护一个柜台, +并通过维护一个柜, and by maintaining a counter, 26 @@ -130,12 +130,12 @@ here represented in blue. 27 00:01:17,340 --> 00:01:19,860 -要了解培训的工作原理, +要了解训练的工作原理, To understand how the training works, 28 00:01:19,860 --> 00:01:23,730 -我们认为这个玩具语料库由以下单词组成: +我们认为这个小语料库由以下单词组成: we consider this toy corpus composed of the following words: 29 @@ -170,7 +170,7 @@ into a list of elementary units that compose them, 35 00:01:45,210 --> 00:01:47,013 -在这里,人物。 +在这里, 字符。 here, the characters. 36 @@ -190,7 +190,7 @@ Let's now see how to increase it. 39 00:02:05,520 --> 00:02:08,250 -我们回到我们分裂的语料库, +我们回到我们拆分的语料库, We return to our split corpus, 40 @@ -200,7 +200,7 @@ we will go through the words one by one 41 00:02:11,340 --> 00:02:14,313 -并计算令牌对的所有出现次数。 +并计算 token 对的所有出现次数。 and count all the occurrences of token pairs. 42 @@ -210,7 +210,7 @@ The first pair is composed of the token 'h' and 'u', 43 00:02:20,130 --> 00:02:23,067 -第二个 'u' 和 'g', +第二个 "u" 和 "g", the second 'u' and 'g', 44 @@ -245,7 +245,7 @@ We note our first merging rule 50 00:02:54,593 --> 00:02:57,243 -然后我们将新标记添加到我们的词汇表中。 +然后我们将新 token 添加到我们的词汇表中。 and we add the new token to our vocabulary. 51 @@ -255,7 +255,7 @@ We can then apply this merging rule to our splits. 52 00:03:04,260 --> 00:03:07,350 -你可以看到我们已经合并了所有的令牌对 +你可以看到我们已经合并了所有的 token 对 You can see that we have merged all the pairs of tokens 53 @@ -270,7 +270,7 @@ And now, we just have to reproduce the same steps 55 00:03:18,150 --> 00:03:19,353 -与我们的新分裂。 +与我们的新拆分。 with our new splits. 56 @@ -280,7 +280,7 @@ We calculate the frequency of occurrence 57 00:03:23,460 --> 00:03:25,023 -每对标记, +对每对 token , of each pair of tokens, 58 @@ -295,12 +295,12 @@ we note it in our merge rules, 60 00:03:36,000 --> 00:03:39,360 -我们将新的标记添加到词汇表中 +我们将新的 token 添加到词汇表中 we add the new one token the vocabulary 61 00:03:39,360 --> 00:03:41,880 -然后我们合并所有的标记对 +然后我们合并所有的 token 对 and then we merge all the pairs of tokens 62 @@ -320,7 +320,7 @@ until we reach the desired vocabulary size. 65 00:04:05,671 --> 00:04:10,671 -在这里,当我们的词汇量达到 21 个标记时,我们就停止了。 +在这里,当我们的词汇量达到 21 个 token 时,我们就停止了。 Here, we stopped when our vocabulary reached 21 tokens. 66 @@ -335,7 +335,7 @@ are now divided into far fewer tokens 68 00:04:17,040 --> 00:04:20,280 -被分成了更少的 tokens +被分成了更少的 token than at the beginning of the training. 69 @@ -390,7 +390,7 @@ Then, we'll go through our merge rules 79 00:04:52,020 --> 00:04:54,690 -直到我们有一个我们可以申请。 +直到我们有一个我们可以应用。 until we have one we can apply. 80 @@ -400,7 +400,7 @@ Here, we can merge the letters 'h' and 'u'. 81 00:04:57,930 --> 00:05:01,467 -在这里,我们可以合并 2 个令牌以获得新令牌 “hug”。 +在这里,我们可以合并 2 个 token 以获得新 token “hug”。 And here, we can merge 2 tokens to get the new token 'hug'. 82 @@ -410,7 +410,7 @@ When we get to the end of our merge rules, 83 00:05:05,760 --> 00:05:07,563 -标记化完成。 +分词化完成。 the tokenization is finished. 84 @@ -425,7 +425,7 @@ I hope that now the BPE algorithm 86 00:05:14,850 --> 00:05:16,413 -没有更多的秘密给你! +对你而言不再是秘密! has no more secret for you! 87 diff --git a/subtitles/zh-CN/52_wordpiece-tokenization.srt b/subtitles/zh-CN/52_wordpiece-tokenization.srt index 26873d619..60e15875e 100644 --- a/subtitles/zh-CN/52_wordpiece-tokenization.srt +++ b/subtitles/zh-CN/52_wordpiece-tokenization.srt @@ -10,12 +10,12 @@ 3 00:00:08,370 --> 00:00:11,851 -WordPiece 算法及其执行方式 +对 WordPiece 算法及其执行方式 of the WordPiece algorithm, and how it performs 4 00:00:11,851 --> 00:00:15,150 -一旦训练,文本的标记化。 +一旦训练,文本的分词化。 the tokenization of a text, once trained. 5 @@ -45,7 +45,7 @@ So we base our explanations 10 00:00:33,510 --> 00:00:36,903 -根据我们自己对已发表文献的解释。 +根据我们自己对已发表文献的理解。 on our own interpretation of the published literature. 11 @@ -100,7 +100,7 @@ We add two hashtags in front of the letters 21 00:01:14,190 --> 00:01:16,083 -那不开始一个词。 +那不开启一个词。 that do not start a word. 22 @@ -120,7 +120,7 @@ We will list all the existing pairs in our corpus. 25 00:01:30,990 --> 00:01:32,640 -一旦我们有了这份清单, +一旦我们有了这份表格, Once we have this list, 26 @@ -150,22 +150,22 @@ the first pair composed of the letters H and U. 31 00:01:48,510 --> 00:01:51,390 -一对的分数简单地等于频率 +一对的分数单纯地等于频率 The score of a pair is simply equal to the frequency 32 00:01:51,390 --> 00:01:54,510 -一对的外观除以产品 +对这对出现的, 除以乘积 of appearance of the pair, divided by the product 33 00:01:54,510 --> 00:01:57,330 -第一个标记出现的频率, +对第一个 token 出现的频率, of the frequency of appearance of the first token, 34 00:01:57,330 --> 00:02:00,063 -通过第二个标记的出现频率。 +乘上第二个 token 的出现频率。 by the frequency of appearance of the second token. 35 @@ -220,7 +220,7 @@ the pair with the highest score, after merging it of course. 45 00:02:40,140 --> 00:02:43,863 -现在我们可以将同样的融合应用于我们的拆分语料库。 +现在我们可以将同样的操作应用于我们的拆分语料库。 And now we can apply this same fusion to our split corpus. 46 @@ -250,7 +250,7 @@ to see the evolution of our vocabulary, 51 00:02:58,957 --> 00:03:01,773 -以及劈叉长度的演变。 +以及拆分长度的演变。 and also the evolution of the length of the splits. 52 @@ -275,7 +275,7 @@ WordPiece follows these rules. 56 00:03:20,310 --> 00:03:22,530 -我们将寻找最长的令牌 +我们将寻找最长的 token We will look for the longest possible token 57 @@ -285,7 +285,7 @@ at the beginning of the word. 58 00:03:24,960 --> 00:03:28,920 -然后我们重新开始我们的话的剩余部分, +然后我们重新开始我们的词语的剩余部分, Then we start again on the remaining part of our word, 59 @@ -295,7 +295,7 @@ and so on until we reach the end. 60 00:03:32,100 --> 00:03:35,973 -就是这样。 Huggingface 分为四个子令牌。 +就是这样。 Huggingface 分为四个子 token 。 And that's it. Huggingface is divided into four sub-tokens. 61 diff --git a/subtitles/zh-CN/53_unigram-tokenization.srt b/subtitles/zh-CN/53_unigram-tokenization.srt index 48f3099f5..fef8e79b8 100644 --- a/subtitles/zh-CN/53_unigram-tokenization.srt +++ b/subtitles/zh-CN/53_unigram-tokenization.srt @@ -10,12 +10,12 @@ 3 00:00:06,420 --> 00:00:09,881 -我们将一起研究 “Unigram 语言模型” +我们将一起研究 “Unigram 语言模型 we will study together 'the Unigram Language Model 4 00:00:09,881 --> 00:00:13,288 -子词标记化算法 '。 +子词分词化算法 " 。 subword tokenization algorithm'. 5 @@ -25,7 +25,7 @@ The overall training strategy 6 00:00:15,567 --> 00:00:18,450 -Unigram 语言模型分词器 +对一个 Unigram 语言模型分词器 of a Unigram Language Model tokenizer 7 @@ -35,7 +35,7 @@ is to start with a very large vocabulary 8 00:00:21,480 --> 00:00:24,240 -然后在每次迭代中删除标记 +然后在每次迭代中删除 token and then to remove tokens at each iteration 9 @@ -55,7 +55,7 @@ we will calculate a loss on our training corpus 12 00:00:30,930 --> 00:00:33,480 -感谢 Unigram 模型。 +多亏了 Unigram 模型。 thanks to the Unigram model. 13 @@ -70,12 +70,12 @@ we can use it to choose how to reduce the vocabulary. 15 00:00:41,550 --> 00:00:43,620 -所以我们看看损失的演变 +所以我们看看损失的变化 So we look at the evolution of the loss 16 00:00:43,620 --> 00:00:47,103 -通过依次从词汇表中删除每个标记。 +通过依次从词汇表中删除每个 token 。 by removing in turn each token from the vocabulary. 17 @@ -110,8 +110,8 @@ The Unigram Language Model 23 00:01:06,030 --> 00:01:08,493 -是一种统计语言调制解调器。 -is a type of Statistical Language Modem. +是一种统计语言模型。 +is a type of Statistical Language Model. 24 00:01:09,450 --> 00:01:10,980 @@ -125,12 +125,12 @@ will assign a probability to a text 26 00:01:13,530 --> 00:01:18,090 -考虑到文本实际上是一系列标记。 +考虑到文本实际上是一系列 token 。 considering that the text is in fact a sequence of tokens. 27 00:01:18,090 --> 00:01:21,090 -可以想象的最简单的标记序列 +可以想象的最简单的 token 序列 The simplest sequences of tokens to imagine 28 @@ -170,7 +170,7 @@ is equal to the product of the probabilities 35 00:01:42,210 --> 00:01:43,953 -组成它的令牌。 +对组成它的 token 。 of the tokens that compose it. 36 @@ -185,7 +185,7 @@ which would not be adapted to the generation of text 38 00:01:53,850 --> 00:01:57,840 -因为这个模型总是会生成相同的令牌, +因为这个模型总是会生成相同的 token , since this model would always generate the same token, 39 @@ -195,7 +195,7 @@ the one which has the greatest probability. 40 00:02:01,320 --> 00:02:03,360 -然而,要进行标记化, +然而,要进行分词化, Nevertheless, to do tokenization, 41 @@ -320,17 +320,17 @@ At each iteration, 65 00:03:15,120 --> 00:03:17,430 -我们估计代币的概率 +我们估计 token 的概率 we estimate the probabilities of the tokens 66 00:03:17,430 --> 00:03:18,430 -词汇的 +对词汇 of the vocabulary 67 00:03:20,130 --> 00:03:23,100 -然后我们删除 p 百分比的标记 +然后我们删除 p 百分比的 token and then we remove the p-percent of tokens 68 @@ -365,7 +365,7 @@ The probability of a token simply estimated 74 00:03:42,360 --> 00:03:44,760 -按此令牌出现的次数 +按此 token 出现的次数 by the number of appearance of this token 75 @@ -375,7 +375,7 @@ in our training corpus 76 00:03:46,440 --> 00:03:50,133 -除以所有令牌出现的总数。 +除以所有 token 出现的总数。 divided by the total number of appearance of all the tokens. 77 @@ -405,7 +405,7 @@ and how the loss is calculated on our corpus. 82 00:04:09,088 --> 00:04:12,263 -我们的文本 “Hug” 的 Unigram LM 标记化 +我们的文本 “Hug” 的 Unigram LM 分词化 The Unigram LM tokenization of our text 'Hug' 83 @@ -425,12 +425,12 @@ To find it, the simplest way to proceed 86 00:04:21,750 --> 00:04:24,120 -将列出所有可能的细分 +将列出所有可能的分割 would be to list all the possible segmentations 87 00:04:24,120 --> 00:04:25,800 -我们的文字 Hug +对我们的文本 "Hug" , of our text 'Hug', 88 @@ -450,7 +450,7 @@ With the current vocabulary, 91 00:04:34,920 --> 00:04:38,640 -两个标记化获得完全相同的概率。 +两个分词化获得完全相同的概率。 two tokenizations get exactly the same probability. 92 @@ -470,7 +470,7 @@ To compute the loss on our training corpus, 95 00:04:46,380 --> 00:04:48,570 -我们需要像刚才那样进行标记化 +我们需要像刚才那样进行分词化 we need to tokenize as we just did 96 @@ -480,7 +480,7 @@ all the remaining words in the corpus. 97 00:04:52,290 --> 00:04:56,430 -损失就是语料库中所有单词的总和 +损失就是所有单词的总和, 语料库中 The loss is then the sum over all the words in the corpus 98 @@ -495,7 +495,7 @@ multiplied by the opposite of the log of the probability 100 00:05:02,670 --> 00:05:05,463 -与单词的标记化相关联。 +与单词的分词化相关联的。 associated with the tokenization of the word. 101 @@ -510,7 +510,7 @@ Remember, our initial goal was to reduce the vocabulary. 103 00:05:18,630 --> 00:05:21,870 -为此,我们将从词汇表中删除一个标记 +为此,我们将从词汇表中删除一个 token To do this, we will remove a token from the vocabulary 104 @@ -525,12 +525,12 @@ Let's remove for example, the token 'ug'. 106 00:05:31,920 --> 00:05:35,370 -我们注意到 “hug” 的标记化 +我们注意到 “hug” 的分词化 We notice that the tokenization for 'hug' 107 00:05:35,370 --> 00:05:39,990 -字母 h 和元组 ug 现在是不可能的。 +字母 "h" 和元组 "ug" 现在是不可能的。 with the letter 'h' and the tuple 'ug' is now impossible. 108 @@ -540,12 +540,12 @@ Nevertheless, as we saw earlier 109 00:05:42,240 --> 00:05:45,180 -两个标记化具有相同的概率, +两个分词化具有相同的概率, that two tokenizations had the same probability, 110 00:05:45,180 --> 00:05:47,730 -我们仍然可以选择剩余的标记化 +我们仍然可以选择剩余的分词化 we can still choose the remaining tokenization 111 @@ -585,7 +585,7 @@ if we continue the calculation, 118 00:06:10,080 --> 00:06:13,050 -我们会注意到我们可以删除任何令牌 +我们会注意到我们可以删除任何 token we would notice that we could remove any token 119 @@ -610,7 +610,7 @@ So we estimate again the probability of each token 123 00:06:27,300 --> 00:06:30,630 -在计算每个代币对损失的影响之前。 +在计算每个 token 对损失的影响之前。 before calculating the impact of each token on the loss. 124 @@ -620,17 +620,17 @@ For example, if we remove now 125 00:06:33,990 --> 00:06:36,290 -由字母 “h” 和 “u” 组成的令牌, +由字母 “h” 和 “u” 组成的 token , the token composed of the letters 'h' and 'u', 126 00:06:37,350 --> 00:06:41,013 -hug 只剩下一种可能的标记化。 -there is only one possible tokenization left for hug. +"hug" 只剩下一种可能的分词化。 +there is only one possible tokenization left for "hug". 127 00:06:41,940 --> 00:06:44,700 -词汇表中其他词的标记化 +词汇表中其他词的分词化 The tokenization of the other words of the vocabulary 128 @@ -645,7 +645,7 @@ In the end, 130 00:06:47,393 --> 00:06:49,200 -我们通过删除令牌获得 +我们通过删除 token 获得 we obtain by removing the token 131 @@ -655,22 +655,22 @@ composed of the letters 'h' and 'u' from the vocabulary, 132 00:06:52,749 --> 00:06:56,430 -亏损 168。 +损失为 168。 a loss of 168. 133 00:06:56,430 --> 00:06:59,490 -最后,要选择要删除的令牌, +最后,要选择要删除的 token , Finally, to choose which token to remove, 134 00:06:59,490 --> 00:07:02,490 -我们将为词汇表的每个剩余标记, +我们将为词汇表的每个剩余 token , we will for each remaining token of the vocabulary, 135 00:07:02,490 --> 00:07:04,800 -这不是基本令牌, +这不是基本 token , which is not an elementary token, 136 @@ -685,17 +685,17 @@ Then, compare these losses between them. 138 00:07:11,730 --> 00:07:13,800 -我们将删除的令牌 +我们将删除的 token The token which we will remove 139 00:07:13,800 --> 00:07:17,340 -是对损失影响最小的代币, +是对损失影响最小的 token , is the token which impacts the least the loss, 140 00:07:17,340 --> 00:07:18,870 -这里是令牌 “bu”。 +这里是 token “bu”。 here the token 'bu'. 141 @@ -715,12 +715,12 @@ p-percent of the tokens by iteration. 144 00:07:29,356 --> 00:07:33,000 -可以在本次迭代中删除的第二个标记 +可以在本次迭代中删除的第二个 token The second token that could be removed at this iteration 145 00:07:33,000 --> 00:07:34,317 -是标记 “du”。 +是 token “du”。 is the token 'du'. 146 @@ -765,7 +765,7 @@ before comparing them to keep the best one 154 00:07:58,770 --> 00:08:01,440 -但是我们使用维特比算法 +但是我们使用 Viterbi 算法 but we use the Viterbi algorithm 155 diff --git a/subtitles/zh-CN/54_building-a-new-tokenizer.srt b/subtitles/zh-CN/54_building-a-new-tokenizer.srt index 7faa3a529..5928f79fd 100644 --- a/subtitles/zh-CN/54_building-a-new-tokenizer.srt +++ b/subtitles/zh-CN/54_building-a-new-tokenizer.srt @@ -10,17 +10,17 @@ In this video, we will see how 3 00:00:07,500 --> 00:00:11,310 -你可以从头开始创建自己的分词器。 +你可以从头开始创建自己的 tokenizer(分词器) 。 you can create your own tokenizer from scratch. 4 00:00:11,310 --> 00:00:15,000 -要创建自己的分词器,你必须考虑 +要创建自己的 tokenizer ,你必须考虑 To create your own tokenizer, you will have to think about 5 00:00:15,000 --> 00:00:18,180 -令牌化中涉及的每个操作。 +分词化中涉及的每个操作。 each of the operations involved in tokenization. 6 @@ -50,27 +50,27 @@ I advise you to go and see the videos linked below. 11 00:00:34,531 --> 00:00:37,110 -后处理收集所有修改 +后处理包括所有修改 The post processing gathers all the modifications 12 00:00:37,110 --> 00:00:40,860 -我们将对标记化文本执行。 +我们将对分词化文本执行的。 that we will carry out on the tokenized text. 13 00:00:40,860 --> 00:00:43,890 -它可以包括添加特殊令牌, +它可以包括添加特殊 token , It can include the addition of special tokens, 14 00:00:43,890 --> 00:00:46,290 -创建一个意图面具, +创建一个意图掩码, the creation of an intention mask, 15 00:00:46,290 --> 00:00:48,903 -还会生成令牌 ID 列表。 +还会生成 token 的 ID 列表。 but also the generation of a list of token IDs. 16 @@ -90,22 +90,22 @@ from the sequence of IDs in a sentence. 19 00:00:58,890 --> 00:01:01,800 -例如,你可以看到主题标签 +例如,你可以看到 hash 标签 For example, you can see that the hashtags 20 00:01:01,800 --> 00:01:04,260 -已被删除,并且令牌 +已被删除,并且 token have been removed, and the tokens 21 00:01:04,260 --> 00:01:07,323 -今天的作文词都归在了一起。 +今天的词都归在了一起。 composing the word today have been grouped together. 22 00:01:10,860 --> 00:01:13,440 -在快速分词器中,所有这些组件 +在快速 tokenizer 中,所有这些组件 In a fast tokenizer, all these components 23 @@ -115,12 +115,12 @@ are gathered in the backend_tokenizer attribute. 24 00:01:17,370 --> 00:01:20,070 -正如你在这个小代码片段中看到的那样, +正如你在这个小片代码中看到的那样, As you can see with this small code snippet, 25 00:01:20,070 --> 00:01:22,020 -它是分词器的一个实例 +它是 tokenizer 的一个实例 it is an instance of a tokenizer 26 @@ -130,7 +130,7 @@ from the tokenizers library. 27 00:01:25,740 --> 00:01:28,263 -因此,要创建自己的分词器, +因此,要创建自己的 tokenizer , So, to create your own tokenizer, 28 @@ -140,22 +140,22 @@ you will have to follow these steps. 29 00:01:33,270 --> 00:01:35,433 -首先,创建一个训练数据集。 +第一,创建一个训练数据集。 First, create a training dataset. 30 00:01:36,690 --> 00:01:39,000 -二、创建和训练分词器 +第二, 创建和训练 tokenizer Second, create and train a tokenizer 31 00:01:39,000 --> 00:01:41,700 -与变压器库。 +用 transformer 库。 with the transformer library. 32 00:01:41,700 --> 00:01:46,700 -第三,将此分词器加载到转换器分词器中。 +第三,将此 tokenizer 加载为 transformer 的 tokenizer 中。 And third, load this tokenizer into a transformer tokenizer. 33 @@ -195,17 +195,17 @@ perfect for the example. 40 00:02:12,210 --> 00:02:13,920 -我们主要攻击这里, +我们主要修改这里, We attack here the big part, 41 00:02:13,920 --> 00:02:17,373 -使用标记器库设计我们的标记器。 +使用 tokenizer 库设计我们的 tokenizer 。 the design of our tokenizer with the tokenizer library. 42 00:02:18,750 --> 00:02:22,020 -我们首先初始化一个分词器实例 +我们首先初始化一个 tokenizer 实例 We start by initializing a tokenizer instance 43 @@ -255,7 +255,7 @@ and the second one isolating the punctuation marks. 52 00:03:03,360 --> 00:03:06,360 -现在,我们可以定义允许我们的培训师 +现在,我们可以定义允许我们的训练方法 Now, we can define the trainer that will allow us 53 @@ -265,7 +265,7 @@ to train the WordPiece model chosen at the beginning. 54 00:03:11,160 --> 00:03:12,600 -为了开展培训, +为了开展训练, To carry out the training, 55 @@ -280,7 +280,7 @@ Here we choose 25,000. 57 00:03:17,910 --> 00:03:21,270 -我们还需要公布特殊代币 +我们还需要公布特殊 token And we also need to announce the special tokens 58 @@ -305,7 +305,7 @@ Once the model has been trained, we can retrieve 62 00:03:42,570 --> 00:03:46,560 -特殊类别和分离标记的 ID, +特殊类别和分离 token 的 ID, the IDs of the special class and separation tokens, 63 @@ -320,12 +320,12 @@ Thanks to the TemplateProcessing class, 65 00:03:52,860 --> 00:03:57,210 -我们可以在每个序列的开头添加 CLS 标记, +我们可以在每个序列的开头添加 CLS token , we can add the CLS token at the beginning of each sequence, 66 00:03:57,210 --> 00:04:00,120 -和序列末尾的 SEP 令牌, +和序列末尾的 SEP token , and the SEP token at the end of the sequence, 67 @@ -345,12 +345,12 @@ which will allow us to remove the hashtags 70 00:04:12,690 --> 00:04:14,610 -在令牌的开头 +在 token 的开头 at the beginning of the tokens 71 00:04:14,610 --> 00:04:17,193 -必须重新附加到以前的令牌。 +必须重新附加到以前的 token 。 that must be reattached to the previous token. 72 @@ -365,12 +365,12 @@ You have all the necessary lines of code 74 00:04:25,110 --> 00:04:29,403 -使用分词器库定义你自己的分词器。 +使用 tokenizer 库定义你自己的 tokenizer 。 to define your own tokenizer with the tokenizer library. 75 00:04:30,960 --> 00:04:32,280 -现在我们有了一个全新的分词器 +现在我们有了一个全新的 tokenizer Now that we have a brand new tokenizer 76 @@ -380,7 +380,7 @@ with the tokenizer library, we just have to load it 77 00:04:35,400 --> 00:04:38,463 -从 transformers 库中转换为快速分词器。 +从 transformers 库中转换为快速 tokenizer 。 into a fast tokenizer from the transformers library. 78 @@ -390,7 +390,7 @@ Here again, we have several possibilities. 79 00:04:42,630 --> 00:04:44,430 -我们可以在泛型类中加载它, +我们可以在一般类中加载它, We can load it in the generic class, 80 @@ -410,7 +410,7 @@ I really hope this video has helped you understand 83 00:04:59,670 --> 00:05:02,133 -如何创建自己的分词器, +如何创建自己的 tokenizer , how you can create your own tokenizer, 84 @@ -420,12 +420,12 @@ and that you are ready now to navigate 85 00:05:06,240 --> 00:05:08,070 -分词器库文档 + tokenizer 库文档 the tokenizer library documentation 86 00:05:08,070 --> 00:05:11,367 -为你的全新分词器选择组件。 +为你的全新 tokenizer 选择组件。 to choose the components for your brand new tokenizer. 87 diff --git a/subtitles/zh-CN/68_data-collators-a-tour.srt b/subtitles/zh-CN/68_data-collators-a-tour.srt index c5d93c002..16805049c 100644 --- a/subtitles/zh-CN/68_data-collators-a-tour.srt +++ b/subtitles/zh-CN/68_data-collators-a-tour.srt @@ -60,22 +60,22 @@ What are data collators? 13 00:00:27,600 --> 00:00:30,480 -数据整理者整理数据。 +数据整理器整理数据。 Data collators collate data. 14 00:00:30,480 --> 00:00:31,800 -那不是很有帮助。 +好像和没说一样。 That's not that helpful. 15 00:00:31,800 --> 00:00:35,023 -但更具体地说,他们整理了一份样本清单 +更具体地说,他们整理了一份样本清单 But to be more specific, they put together a list of samples 16 00:00:35,023 --> 00:00:37,830 -成一个单一的训练小批量。 +组成一个单独的小批量训练数据。 into a single training minibatch. 17 @@ -85,7 +85,7 @@ For some tasks, 18 00:00:38,910 --> 00:00:41,670 -数据整理器可以非常简单。 +数据整理器可以非常简单明了。 the data collator can be very straightforward. 19 @@ -95,17 +95,17 @@ For example, when you're doing sequence classification, 20 00:00:44,820 --> 00:00:47,010 -数据整理器提供你真正需要的一切 +你真正需要数据整理器做的是 all you really need from your data collator 21 00:00:47,010 --> 00:00:49,860 -是它将你的样品填充到相同的长度 +将你的样本数据填充到相同的长度 is that it pads your samples to the same length 22 00:00:49,860 --> 00:00:52,413 -并将它们连接成一个张量。 +并将它们连接成一个单独的 Tensor。 and concatenates them into a single Tensor. 23 @@ -115,12 +115,12 @@ But for other workflows, data collators can be quite complex 24 00:00:57,750 --> 00:00:59,910 -因为他们处理一些预处理 +因为他们为特定任务 as they handle some of the preprocessing 25 00:00:59,910 --> 00:01:02,340 -该特定任务所需。 +完成一些预处理操作。 needed for that particular task. 26 @@ -130,12 +130,12 @@ So, if you want to use a data collator, 27 00:01:04,800 --> 00:01:07,860 -对于 PyTorch 用户,你通常通过数据整理器 +对于 PyTorch 用户,你通常将数据整理器 for PyTorch users, you usually pass the data collator 28 00:01:07,860 --> 00:01:09,780 -到你的 Trainer 对象。 +传递到你的 Trainer 对象。 to your Trainer object. 29 @@ -155,7 +155,7 @@ is to pass it to the to_tf_dataset method of your dataset. 32 00:01:16,860 --> 00:01:20,198 -这会给你一个 tensorflow_tf_data.dataset +这会给你 tensorflow_tf_data.dataset And this will give you a tensorflow_tf_data.dataset 33 @@ -180,17 +180,17 @@ Also note that all of our collators 37 00:01:30,180 --> 00:01:32,610 -采用 return_tensors 参数。 +可接受 return_tensors 参数。 take a return_tensors argument. 38 00:01:32,610 --> 00:01:35,737 -你可以将其设置为 “pt” 以获取 PyTorch 张量, +你可以将其设置为 “pt” 以获取 PyTorch Tensor, You can set this to "pt" to get PyTorch Tensors, 39 00:01:35,737 --> 00:01:37,920 -"tf" 获取 TensorFlow 张量, +"tf" 获取 TensorFlow Tensor, "tf" to get TensorFlow Tensors, 40 @@ -210,12 +210,12 @@ the default value is "pt", 43 00:01:44,460 --> 00:01:47,160 -所以 PyTorch 用户甚至不必设置这个参数 +所以 PyTorch 用户甚至大多数情况下 so PyTorch users don't even have to set this argument 44 00:01:47,160 --> 00:01:48,270 -大多数时候。 +不必设置这个参数。 most of the time. 45 @@ -225,7 +225,7 @@ And so as a result, they're often totally unaware 46 00:01:50,820 --> 00:01:52,713 -这个论点甚至存在。 +这个参数的存在。 that this argument even exists. 47 @@ -245,7 +245,7 @@ are often the most blind to its existence. 50 00:02:00,690 --> 00:02:01,920 -不过还好,回来了。 +好吧好吧,扯远了。 But okay, coming back. 51 @@ -255,17 +255,17 @@ Let's see how some specific data collators work in action. 52 00:02:06,540 --> 00:02:08,070 -虽然再次记住如果没有 +虽然再次记住 Although again, remember if none 53 00:02:08,070 --> 00:02:09,900 -的内置数据整理器可以满足你的需求, +如果内置数据整理器无法满足你的需求, of the built-in data collators do what you need, 54 00:02:09,900 --> 00:02:13,650 -你总是可以自己写,而且它们通常很短。 +你可以自己实现数据整理器,而且它们通常很短。 you can always write your own and they're often quite short. 55 @@ -280,12 +280,12 @@ These are DefaultDataCollator and DataCollatorWithPadding. 57 00:02:21,420 --> 00:02:22,830 -这些是你应该使用的 +如果在准备训练之前 These are the ones you should use 58 00:02:22,830 --> 00:02:24,720 -如果你的标签很简单 +你的标签很简单 if your labels are straightforward 59 @@ -295,7 +295,7 @@ and your data doesn't need any special processing 60 00:02:27,300 --> 00:02:29,673 -在准备训练之前。 +就可以使用上述的数据整理器 before being ready for training. 61 @@ -305,7 +305,7 @@ Notice that because different models 62 00:02:31,272 --> 00:02:33,690 -有不同的填充标记, +有不同的填充词元, have different padding tokens, 63 @@ -320,7 +320,7 @@ so it knows how to pad sequences properly. 65 00:02:40,150 --> 00:02:44,790 -默认的数据整理器不需要 Tokenizer 来工作, +默认的数据整理器不需要 Tokenizer 工作, The default data collator doesn't need a Tokenizer to work, 66 @@ -360,12 +360,12 @@ they're usually designed to handle one specific task. 73 00:02:59,490 --> 00:03:01,050 -所以,我要在这里展示一对。 +所以,我要在这里展示两个整理器。 And so, I'm going to show a couple here. 74 00:03:01,050 --> 00:03:04,320 -这些是 DataCollatorForTokenClassification +它们是 DataCollatorForTokenClassification These are DataCollatorForTokenClassification 75 @@ -375,7 +375,7 @@ and DataCollatorForSeqToSeq. 76 00:03:06,447 --> 00:03:09,540 -以及这些任务需要特殊整理器的原因 +而这些任务需要特殊整理器的原因 And the reason these tasks need special collators 77 @@ -385,7 +385,7 @@ is because their labels are variable in length. 78 00:03:12,600 --> 00:03:15,960 -在令牌分类中,每个令牌都有一个标签, +在词元分类中,每个词元都有一个标签, In token classification there's one label for each token, 79 @@ -400,32 +400,32 @@ is the length of the sequence. 81 00:03:20,280 --> 00:03:23,520 -而在 SeqToSeq 中,标签是一系列标记 +而在 SeqToSeq 中,标签是一系列词元 While in SeqToSeq the labels are a sequence of tokens 82 00:03:23,520 --> 00:03:24,780 -可以是可变长度, +它可以是可变长度, that can be variable length, 83 00:03:24,780 --> 00:03:25,800 -那可能会非常不同 +那可能会和输入序列 that can be very different 84 00:03:25,800 --> 00:03:28,200 -从输入序列的长度。 +的长度不同。 from the length of the input sequence. 85 00:03:28,200 --> 00:03:32,880 -所以在这两种情况下,我们处理整理那批 +所以在这两种情况下,我们通过填充标签 So in both of these cases, we handle collating that batch 86 00:03:32,880 --> 00:03:35,280 -也通过填充标签, +处理整理那批数据, by padding the labels as well, 87 @@ -435,17 +435,17 @@ as you can see here in this example. 88 00:03:37,410 --> 00:03:40,770 -因此,需要填充输入和标签 +因此,如果我们想加入可变长度的样本 So, inputs and the labels will need to be padded 89 00:03:40,770 --> 00:03:43,860 -如果我们想加入可变长度的样本 +到同一个小批量数据, if we want to join samples of variable length 90 00:03:43,860 --> 00:03:45,120 -进入同一个小批量。 +就需要填充输入和标签。 into the same minibatch. 91 @@ -460,17 +460,17 @@ and that's exactly what these data collators will do for us 93 00:03:50,460 --> 00:03:52,383 -你知道,为了这个特定的任务。 +你懂的,为了这个特定的任务。 you know, for this particular task. 94 00:03:53,820 --> 00:03:56,070 -所以,有一个最终的数据整理器 +那么,还有最后一个 So, there's one final data collator 95 00:03:56,070 --> 00:03:58,560 -我也想在本次讲座中向你展示。 +想在这里与大家分享的数据整理器。 I want to show you as well just in this lecture. 96 @@ -480,17 +480,17 @@ And that's the DataCollatorForLanguageModeling. 97 00:04:01,410 --> 00:04:03,390 -所以,这非常重要,首先, +嗯,它非常重要,首先, So, it's very important, and it's firstly, 98 00:04:03,390 --> 00:04:05,820 -因为语言模型是如此基础 +因为语言模型是我们这段时间以来 because language models are just so foundational 99 00:04:05,820 --> 00:04:09,720 -这些天我们用 NLP 所做的一切。 +处理 NLP 所涉及的最基本的概念。 to do for everything we do with NLP these days. 100 @@ -500,7 +500,7 @@ But secondly, because it has two modes 101 00:04:12,060 --> 00:04:14,760 -做两件截然不同的事情。 +可以完成两件截然不同的事情。 that do two very different things. 102 @@ -510,7 +510,7 @@ So you choose which mode you want with the mlm argument. 103 00:04:19,230 --> 00:04:22,470 -将其设置为 True 以进行掩码语言建模, +将其设置为 True 以进行屏蔽语言建模, Set it to True for masked language modeling, 104 @@ -535,7 +535,7 @@ The model is just making predictions 108 00:04:32,640 --> 00:04:35,460 -接下来是什么标记,所以你的标签 +接下来是什么词元,所以你的标签 for what token comes next, and so your labels 109 @@ -545,7 +545,7 @@ are more or less just a copy of your inputs, 110 00:04:37,800 --> 00:04:39,090 -整理人会处理 +整理器会处理你的输入 and the collator will handle that 111 @@ -560,7 +560,7 @@ When you set mlm to True though, 113 00:04:44,910 --> 00:04:46,786 -你会得到完全不同的行为, +你会得到完全不同的效果, you get quite different behavior, 114 @@ -575,7 +575,7 @@ and that's because setting mlm to True 116 00:04:51,660 --> 00:04:53,550 -表示掩码语言建模 +表示屏蔽语言建模 means masked language modeling 117 @@ -585,7 +585,7 @@ and that means the labels need to be, 118 00:04:55,680 --> 00:04:58,080 -你知道,输入需要被屏蔽。 +你懂的,输入需要被屏蔽。 you know, the inputs need to be masked. 119 @@ -605,7 +605,7 @@ the model is not predicting the next word, 122 00:05:06,570 --> 00:05:09,240 -相反,我们随机屏蔽掉一些标记 +相反,我们随机屏蔽掉一些词元 instead we randomly mask out some tokens 123 @@ -615,12 +615,12 @@ and the model predicts all of them at once. 124 00:05:11,130 --> 00:05:12,780 -所以,它试图填补空白 +所以,对于那些被屏蔽的词元 So, it tries to kinda fill in the blanks 125 00:05:12,780 --> 00:05:14,790 -对于那些被屏蔽的令牌。 +它试图填补空白。 for those masked tokens. 126 @@ -635,72 +635,72 @@ If we follow the protocol from the original BERT paper, 128 00:05:21,330 --> 00:05:23,970 -我们需要用掩码标记替换一些标记, +我们需要用掩码词元替换一些词元, we need to replace some tokens with a masked token, 129 00:05:23,970 --> 00:05:26,190 -一些其他带有随机令牌的令牌, +一些其他带有随机词元的词元, some other tokens with a random token, 130 00:05:26,190 --> 00:05:29,820 -然后保持第三组标记不变。 +然后保持第三组词元不变。 and then keep a third set of tokens unchanged. 131 00:05:29,820 --> 00:05:30,840 -是的,这不是讲座 +好吧,具体细节和我们这么做的原因 Yeah, this is not the lecture 132 00:05:30,840 --> 00:05:33,903 -进入细节或我们为什么这样做。 +就不在这里和大家详细说明了。 to go into the specifics of that or why we do it. 133 00:05:33,903 --> 00:05:36,660 -你可以随时查看原始的 BERT 论文 +如果你好奇的话,可以随时查看原始的 You can always check out the original BERT paper 134 00:05:36,660 --> 00:05:37,493 -如果你好奇的话。 +BERT 论文。 if you're curious. 135 00:05:37,493 --> 00:05:39,620 -写得很好。这很容易理解。 +它写得非常好。也很容易理解。 It's well written. It's easy to understand. 136 00:05:40,650 --> 00:05:44,190 -这里要知道的主要事情是它可能是一个真正的痛苦 +这里要知道的主要事情是自己实现起来 The main thing to know here is that it can be a real pain 137 00:05:44,190 --> 00:05:46,770 -并且自己实施起来非常复杂。 +是一件非常痛苦的事情,而且也非常地复杂。 and quite complex to implement that yourself. 138 00:05:46,770 --> 00:05:49,740 -但是 DataCollatorForLanguageModeling 会为你做 +但是当你将 mlm 设置为 True 时,DataCollatorForLanguageModeling But DataCollatorForLanguageModeling will do it for you 139 00:05:49,740 --> 00:05:51,750 -当你将 mlm 设置为 True 时。 +会为你完成。 when you set mlm to True. 140 00:05:51,750 --> 00:05:54,690 -这是一个更复杂的例子 +这是一些数据整理器所完成的 And that's an example of the more intricate 141 00:05:54,690 --> 00:05:57,870 -我们的一些数据整理人员所做的预处理。 +更加复杂的预处理操作。 preprocessing that some of our data collators do. 142 @@ -715,7 +715,7 @@ So, this covers the most commonly used data collators 144 00:06:01,920 --> 00:06:03,480 -以及它们用于执行的任务。 +以及它们所针对的具体任务。 and the tasks they're used for. 145 @@ -725,7 +725,7 @@ And hopefully, now you'll know when to use data collators 146 00:06:06,990 --> 00:06:10,833 -以及为你的特定任务选择哪一个。 +以及该为你的特定任务选择哪一个。 and which one to choose for your specific task. 147 diff --git a/subtitles/zh-CN/72_asking-for-help-on-the-forums.srt b/subtitles/zh-CN/72_asking-for-help-on-the-forums.srt index 3ccb3dda1..e4aa42002 100644 --- a/subtitles/zh-CN/72_asking-for-help-on-the-forums.srt +++ b/subtitles/zh-CN/72_asking-for-help-on-the-forums.srt @@ -20,17 +20,17 @@ 5 00:00:10,020 --> 00:00:11,640 -如果你有一般性问题 +如果你有一些问题 If you have a general question 6 00:00:11,640 --> 00:00:13,110 -或者正在调试你的代码, +或者正在调试你代码中的 bug, or are looking to debug your code, 7 00:00:13,110 --> 00:00:15,540 -论坛是提问的地方。 +那你可以去论坛提问。 the forums are the place to ask. 8 @@ -40,17 +40,17 @@ In this video we will teach you 9 00:00:16,710 --> 00:00:18,030 -如何写出一个好问题, +如何正确地在论坛中提问, how to write a good question, 10 00:00:18,030 --> 00:00:20,380 -以最大限度地提高你获得答案的机会。 +以使你尽快获得答案。 to maximize the chances you will get an answer. 11 00:00:21,570 --> 00:00:23,970 -首先,登录论坛, +首先,为了登录论坛, First things first, to login on the forums, 12 @@ -60,112 +60,112 @@ you need a Hugging Face account. 13 00:00:25,920 --> 00:00:27,750 -如果你还没有创建一个, +如果你还没有 Hugging Face 帐户, If you haven't created one already, 14 00:00:27,750 --> 00:00:31,080 -转到 hf.co 并单击注册。 +就访问 hf.co,然后单击注册。 go to hf.co and click sign up. 15 00:00:31,080 --> 00:00:32,780 -下面还有一个直接链接。 +也可以直接点击下面的链接。 There is also a direct link below. 16 00:00:33,750 --> 00:00:35,160 -填写你的邮箱和密码, +首先需要填写你的邮箱和密码, Fill your email and password, 17 00:00:35,160 --> 00:00:37,410 -然后继续选择你的用户名的步骤 +然后填写你的用户名 then continue the steps to pick your username 18 00:00:37,410 --> 00:00:38,860 -并更新头像。 +然后再更新一下头像。 and update a profile picture. 19 00:00:39,720 --> 00:00:43,200 -完成后,去 discuss.huggingface.co, +这些步骤完成后,访问 discuss.huggingface.co, Once this is done, go to discuss.huggingface.co, 20 00:00:43,200 --> 00:00:45,630 -下方链接,点击登录。 +或者点击下方的链接,然后点击登录(Log In)。 link below, and click log in. 21 00:00:45,630 --> 00:00:47,033 -使用与以下相同的登录信息 +使用前面注册的 Hugging Face 账户信息 Use the same login information as 22 00:00:47,033 --> 00:00:48,693 -Hugging Face 网站。 +登录。 for the Hugging Face website. 23 00:00:49,890 --> 00:00:51,300 -你可以通过单击搜索论坛 +你可以单击放大镜图标 You can search the forums by clicking 24 00:00:51,300 --> 00:00:52,800 -在放大镜上。 +来在论坛中搜索。 on the magnifying glass. 25 00:00:52,800 --> 00:00:55,710 -可能有人已经在某个主题中问过你的问题。 +因为可能有人已经在某个主题(Topic)中问过你想问的问题。 Someone may have already asked your question in a topic. 26 00:00:55,710 --> 00:00:58,260 -如果你发现你无法作为新用户发布新主题, +如果你发现你新注册的账户无法发布新主题, If you find you can't post a new topic as a new user, 27 00:00:58,260 --> 00:01:01,290 -这可能是因为反垃圾邮件过滤器。 +这可能是因为论坛的反垃圾信息过滤机制。 it may be because of the antispam filters. 28 00:01:01,290 --> 00:01:03,750 -确保你花一些时间阅读现有主题 +你需要花一段时间来阅读现有的主题, Make sure you spend some time reading existing topics 29 00:01:03,750 --> 00:01:05,370 -停用它。 +然后就可以发布新主题了。 to deactivate it. 30 00:01:05,370 --> 00:01:07,590 -当你确定你的问题还没有被问到时, +当你确定你的问题还没有人问过时, When you're sure your question hasn't been asked yet, 31 00:01:07,590 --> 00:01:09,660 -单击新主题按钮。 +单击新主题(New Topic)按钮。 click on the new topic button. 32 00:01:09,660 --> 00:01:12,600 -对于此示例,我们将使用以下代码, +在这里,我们将用 For this example, we'll use the following code, 33 00:01:12,600 --> 00:01:13,860 -产生错误, +「出现错误时该怎么办」视频中 that produces an error, 34 00:01:13,860 --> 00:01:16,660 -正如我们在 “遇到错误时该怎么办” 视频中看到的那样。 +报错的代码作为例子 as we saw in the "What to do when I get an error" video. 35 @@ -180,7 +180,7 @@ Since our error has to do with the Transformers library, 37 00:01:23,790 --> 00:01:24,903 -我们选择这个类别。 +所以我们选择这个类别。 we pick this category. 38 @@ -190,12 +190,12 @@ Next, choose a title that summarizes your error well. 39 00:01:29,880 --> 00:01:32,300 -不要太含糊,否则用户会遇到与你相同的错误 +标题不要太模糊,否则如果以后有用户遇到了与你相同的错误, Don't be too vague or users that get the same error you did 40 00:01:32,300 --> 00:01:34,773 -以后将无法找到你的主题。 +那么将无法搜索到你的主题。 in the future won't be able to find your topic. 41 @@ -205,47 +205,47 @@ Once you have finished typing your topic title, 42 00:01:38,370 --> 00:01:40,170 -确保问题没有得到回答 +确保你要问的问题在建议的相似主题中 make sure the question hasn't been answered 43 00:01:40,170 --> 00:01:42,690 -在 Discourse 主题中建议你。 +没有出现。 in the topics Discourse suggests you. 44 00:01:42,690 --> 00:01:44,190 -单击十字删除该窗口 +当你仔细检查之后, Click on the cross to remove that window 45 00:01:44,190 --> 00:01:46,230 -当你仔细检查时。 +就可以单击叉号关闭该窗口。 when you have double-checked. 46 00:01:46,230 --> 00:01:49,710 -这是发布错误时不应执行的操作的示例。 +这里展示的是一个不好的提问示例。 This is an example of what not to do when posting an error. 47 00:01:49,710 --> 00:01:51,120 -消息非常模糊, +这个示例中对问题的描述非常模糊, The message is very vague, 48 00:01:51,120 --> 00:01:53,370 -所以没有人能猜出哪里出了问题 +所以没有人能简单地判断出哪里出了问题 so no one else will be able to guess what went wrong 49 00:01:53,370 --> 00:01:55,623 -对你来说,它标记了太多人。 +另外,在这个问题中标记(@)了太多人。 for you, and it tags too many people. 50 00:01:56,490 --> 00:01:58,740 -标记人,尤其是版主, +标记别人,尤其是版主, Tagging people, especially moderators, 51 @@ -255,57 +255,57 @@ might have the opposite effect of what you want. 52 00:02:01,470 --> 00:02:04,380 -当你向他们发送通知时,他们会收到很多通知, +当你向他们发送通知的同时,他们也会收到很多别的通知, As you send them a notification, and they get plenty, 53 00:02:04,380 --> 00:02:06,300 -他们可能不会费心回复你, +所以他们可能不会费心回复你, they will probably not bother replying to you, 54 00:02:06,300 --> 00:02:09,300 -而你没有标记的用户可能会忽略这些问题, +而你没有标记的用户看到你标记了其他人, and users you didn't tag will probably ignore the questions, 55 00:02:09,300 --> 00:02:11,430 -因为他们看到被标记的用户。 +可能就会忽略这个问题。 since they see tagged users. 56 00:02:11,430 --> 00:02:13,697 -仅在你完全确定时才标记用户 +仅在你完全确定某个人特别适合回答这个问题时 Only tag a user when you are completely certain 57 00:02:13,697 --> 00:02:16,097 -他们是回答你问题的最佳场所。 +才去标记这个人。 they are the best place to answer your question. 58 00:02:17,730 --> 00:02:20,370 -文本要准确,如果出现错误 +文字描述要准确,如果你的某一段代码 Be precise in your text, and if you have an error coming 59 00:02:20,370 --> 00:02:22,710 -从一段特定的代码,包括该代码 +产生了错误,那么就要把这段代码 from a specific piece of code, include that code 60 00:02:22,710 --> 00:02:24,030 -在你的帖子中。 +包含在你的帖子中。 in your post. 61 00:02:24,030 --> 00:02:27,210 -为了确保你的帖子看起来不错,请提出你的问题 +为了确保你的帖子看起来美观,请把代码 To make sure your post looks good, place your question 62 00:02:27,210 --> 00:02:30,060 -在像这样的三个反引号之间。 +放在包含三个反引号(backticks)的两行之间。 between three backticks like this. 63 @@ -315,7 +315,7 @@ You can check on the right 64 00:02:30,990 --> 00:02:32,943 -发布后你的帖子将如何显示。 +帖子发布后的预览效果。 how your post will appear once posted. 65 @@ -325,47 +325,47 @@ If your question is about an error, 66 00:02:35,850 --> 00:02:38,640 -最好包括完整的回溯。 +最好包括完整的报错信息。 it's even better to include the full traceback. 67 00:02:38,640 --> 00:02:41,610 -正如 “遇到错误时该怎么办” 视频中所述, +正如「出现错误时该怎么办」视频中所述, As explained in the "What to do when I get an error" video, 68 00:02:41,610 --> 00:02:43,763 -如果你使用的是 Colab,请展开回溯。 +如果你使用的是 Colab,请展开回溯(traceback)。 expand the traceback if you're on Colab. 69 00:02:44,769 --> 00:02:45,990 -就像代码一样,把它 +和代码一样,把它们 Like for the code, put it 70 00:02:45,990 --> 00:02:48,300 -在包含三个反引号的两行之间 +放在包含三个反引号的两行之间 between two lines containing three backticks 71 00:02:48,300 --> 00:02:50,160 -正确格式化。 +看起来会比较美观。 for proper formatting. 72 00:02:50,160 --> 00:02:52,740 -我们最后的建议是记住要友善。 +我们最后的建议是语气要温和, Our last advice is to remember to be nice. 73 00:02:52,740 --> 00:02:54,540 -一句 “请” 和一句 “谢谢” 将大有帮助 +使用「请」和「谢谢」将让别人 A "Please," and a "Thank you" will go a long way 74 00:02:54,540 --> 00:02:56,490 -让别人帮助你。 +更乐意帮助你。 into getting others to help you. 75 diff --git a/subtitles/zh-CN/73_debugging-the-training-pipeline-(pytorch).srt b/subtitles/zh-CN/73_debugging-the-training-pipeline-(pytorch).srt index d12bee78e..c4b8ad020 100644 --- a/subtitles/zh-CN/73_debugging-the-training-pipeline-(pytorch).srt +++ b/subtitles/zh-CN/73_debugging-the-training-pipeline-(pytorch).srt @@ -5,18 +5,18 @@ 2 00:00:08,760 --> 00:00:11,896 -你在运行 Trainer.train 时遇到 +你在运行 Trainer.train 时你会遇到的 you encounter when running Trainer.train 3 00:00:11,896 --> 00:00:15,066 -例如,我们将使用这个微调脚本 +作为一个例子,我们将使用这个脚本微调 As an example, we will use this script that finetunes 4 00:00:15,066 --> 00:00:17,760 -GLUE MNLI 数据集上的 bert 模型。 -a bert model on the GLUE MNLI dataset. +一个 BERT 模型, 在 GLUE MNLI 数据集上。 +a BERT model on the GLUE MNLI dataset. 5 00:00:17,760 --> 00:00:19,470 @@ -25,7 +25,7 @@ Checkout the videos linked below 6 00:00:19,470 --> 00:00:21,840 -看看我们是如何得出这样一个脚本的。 +以看看我们是如何得出这样一个脚本的。 to see how we came to such a script. 7 @@ -40,7 +40,7 @@ Running the script gives us an error pretty quickly. 9 00:00:28,110 --> 00:00:29,040 -它发生在线上 +它发生在这一行 It happens at the line 10 @@ -55,12 +55,12 @@ according to the traceback. 12 00:00:32,850 --> 00:00:34,702 -这告诉我们那里有问题, +这告诉我们这里有问题, That tells us there is a problem there, 13 00:00:34,702 --> 00:00:37,881 -但问题可能来自许多不同的原因。 +但问题可能有许多不同的原因。 but the problem could come from many different causes. 14 @@ -70,7 +70,7 @@ To debug an error in a training, 15 00:00:39,330 --> 00:00:41,760 -你需要确保训练管道的每一步 +你需要确保训练 pipeline 的每一步 you need to make sure each step of the training pipeline 16 @@ -90,12 +90,12 @@ are correct, 19 00:00:47,040 --> 00:00:48,720 -你可以把它们批在一起, +你可以把它们分批在一起, you can batch them together, 20 00:00:48,720 --> 00:00:50,790 -通过模型喂养他们以获得损失, +把他们通过模型以获得损失, feed them through the model to get a loss, 21 @@ -115,7 +115,7 @@ So let's start by looking at the training dataset 24 00:00:57,810 --> 00:00:59,043 -这个培训师正在使用。 +这个 Trainer 正在使用。 this Trainer is using. 25 @@ -140,7 +140,7 @@ did not get input IDs 29 00:01:08,220 --> 00:01:11,100 -我们确实没有数据集中的那些。 +我们数据集中确实没有那些。 and we do not have those in the dataset indeed. 30 @@ -155,7 +155,7 @@ we can see we made a mistake 32 00:01:14,400 --> 00:01:17,400 -并将错误的数据集传递给培训师。 +并将错误的数据集传递给 Trainer 。 and passed the wrong datasets to the Trainer. 33 @@ -175,17 +175,17 @@ Inspecting the traceback 36 00:01:23,130 --> 00:01:25,860 -告诉我们当我们尝试创建批处理时会发生这种情况, +告诉我们当我们尝试创建批处理时会发生, tells us it happens when we try to create a batch, 37 00:01:25,860 --> 00:01:28,743 -专门用于对张量中的特征进行分组。 +特别对于对张量中的特征进行分组。 specifically to group the features in a tensor. 38 00:01:29,700 --> 00:01:32,610 -我们可以通过要求培训师给我们一批来确认这一点 +我们可以通过要求 Trainer 给我们一分批来确认这一点 We can confirm this by asking the Trainer to get us a batch 39 @@ -195,7 +195,7 @@ of the training data loader, 40 00:01:34,230 --> 00:01:35,913 -重现相同的错误。 +复现相同的错误。 which reproduces the same error. 41 @@ -210,12 +210,12 @@ we can then see they are not all of the same size. 43 00:01:42,870 --> 00:01:45,120 -这是因为我们还没有通过数据整理器 +这是因为我们还没有输入数据整理器 This is because we have not passed a data collator 44 00:01:45,120 --> 00:01:46,890 -对培训师进行填充 +对 Trainer 进行填充 to do the padding to the Trainer 45 @@ -230,7 +230,7 @@ Padding inside the Trainer is normally the default, 47 00:01:52,710 --> 00:01:55,380 -但前提是你将分词器提供给培训师, +但前提是你将 tokenizer 提供给 Trainer , but only if you provide your tokenizer to the Trainer, 48 @@ -265,7 +265,7 @@ so you have to restart your notebook from the beginning 54 00:02:13,260 --> 00:02:16,950 -第二,追溯对那些人来说完全没用。 +第二,追溯对那些来说完全没用。 and two, the traceback is completely useless for those. 55 @@ -345,7 +345,7 @@ But the GPU still hasn't finished 70 00:02:54,180 --> 00:02:55,710 -模型的前向传递 +模型的前向传播 the forward pass of the model 71 @@ -390,13 +390,13 @@ who raises the error at the wrong place. 79 00:03:16,350 --> 00:03:18,720 -所以要调试这个,我们需要执行接下来的步骤 -So to debug this, we will need to execute the next steps +所以要调试这个,我们需要执行 +So to debug this, we will need to execute 80 00:03:18,720 --> 00:03:21,211 -CPU 上的训练流水线。 -of the training pipeline on the CPU. +接下来 CPU 上的训练流水线的步骤。 +the next steps of the training pipeline on the CPU. 81 00:03:21,211 --> 00:03:22,380 @@ -420,7 +420,7 @@ the error actually happens during the forward pass 85 00:03:28,620 --> 00:03:29,453 -模型的, +对模型, of the model, 86 @@ -490,12 +490,12 @@ but here is how we would debug the next step 99 00:04:00,960 --> 00:04:02,944 -管道,梯度计算, + pipeline ,梯度计算, of the pipeline, gradient computation, 100 00:04:02,944 --> 00:04:05,850 -以及优化器步骤。 +以及优化器更新。 as well as the optimizer step. 101 diff --git a/subtitles/zh-CN/74_debugging-the-training-pipeline-(tensorflow).srt b/subtitles/zh-CN/74_debugging-the-training-pipeline-(tensorflow).srt index 0bf09b45a..176dbbe54 100644 --- a/subtitles/zh-CN/74_debugging-the-training-pipeline-(tensorflow).srt +++ b/subtitles/zh-CN/74_debugging-the-training-pipeline-(tensorflow).srt @@ -90,7 +90,7 @@ So, this is going to be a video about what to do 19 00:00:50,370 --> 00:00:52,410 -当你遇到那些噩梦中的一个错误时 +当你遇到那些噩梦般的一个错误时 when you run into one of those nightmare bugs 20 @@ -135,7 +135,7 @@ First, we do all our imports, we load a dataset, 28 00:01:16,410 --> 00:01:20,280 -我们创建了分词器并对数据集进行分词。 +我们创建了 tokenizer 并对数据集进行分词。 we create our tokenizer and we tokenize the dataset. 29 @@ -150,12 +150,12 @@ so that's tf.data.Dataset, 31 00:01:26,100 --> 00:01:28,500 -这样我们就可以适应它们, +这样我们就可以拟合它们, and that's so that we can run fit on them, 32 00:01:28,500 --> 00:01:31,170 -然后我们从预训练的检查点加载我们的模型, +然后我们从预训练的 checkpoint 加载我们的模型, and then we load our model from a pretrained checkpoint, 33 @@ -205,7 +205,7 @@ We tried to train on our data, but we got no gradient? 42 00:01:55,470 --> 00:01:59,130 -这很令人困惑,我的意思是,我们如何开始 +这很令人困惑,我的意思是,我们从何开始 It's pretty perplexing, I mean, how do we even begin 43 @@ -275,7 +275,7 @@ and that's because it's right at the end 56 00:02:31,560 --> 00:02:33,990 -的数据管道。 +的数据 pipeline 。 of the data pipeline. 57 @@ -285,7 +285,7 @@ And so that means that if those outputs are good, 58 00:02:36,990 --> 00:02:39,990 -你可以保证你的数据管道运行良好。 +你可以保证你的数据 pipeline 运行良好。 you're guaranteed that your data pipeline is working well. 59 @@ -300,7 +300,7 @@ for one iteration and then breaking, 61 00:02:44,790 --> 00:02:46,980 -这给了我们一个批次。 +这给了我们一个分批。 and that gives us a single batch. 62 @@ -360,7 +360,7 @@ the labels need to be passed in the input dictionary, 73 00:03:15,960 --> 00:03:17,940 -模型可以看到它们的地方。 +其模型可以看到它们。 where the model can see them. 74 @@ -430,7 +430,7 @@ or we keep using Keras losses 87 00:03:46,980 --> 00:03:50,520 -但我们将标签移动到 Keras 期望的位置。 +但我们将标签移动到 Keras 期望的地方。 but we move the labels to the place Keras expects them. 88 @@ -465,7 +465,7 @@ So we recompile with that, we call model.fit, what happens? 94 00:04:08,220 --> 00:04:13,050 -好吧,这次它运行了,但现在我们失去了 NaN。 +好吧,这次它运行了,但现在我们消失了 NaN。 Well, it runs this time but now we get a loss of NaN. 95 @@ -495,7 +495,7 @@ all the weights are NaN as well, as well as the loss. 100 00:04:27,600 --> 00:04:30,810 -所以一旦一个 NaN 悄悄进入你的计算, +所以一旦一个 NaN 悄悄爬进你的计算, So once a single NaN creeps into your computations, 101 @@ -515,17 +515,17 @@ it gets to the gradient, 104 00:04:37,530 --> 00:04:38,910 -然后一旦它处于渐变中 +然后一旦它处于梯度中 and then once it's in the gradient 105 00:04:38,910 --> 00:04:41,280 -它进入重量更新, +它进入权重更新, it enters the weight updates, 106 00:04:41,280 --> 00:04:43,980 -然后你所有的体重更新最终也都是 NaN 。 +然后你所有的权重更新最终也都是 NaN 。 and then all your weight updates end up as NaN as well. 107 @@ -630,8 +630,8 @@ because 0 is a label as well. 127 00:05:33,630 --> 00:05:35,070 -所以我们失去了 NaN -So we got a loss of NaN +所以我们得到大量 NaN +So we got a lots of NaN 128 00:05:35,070 --> 00:05:37,887 @@ -655,7 +655,7 @@ so we can set num_labels=3 132 00:05:45,870 --> 00:05:48,540 -当我们初始化模型但来自_pretrained 时, +当我们初始化模型用 from_pretrained 时, when we initialize the model but from_pretrained, 133 @@ -670,7 +670,7 @@ So, now we think our data is good and our model is good 135 00:05:54,660 --> 00:05:56,220 -所以培训应该有效 +所以训练应该有效 and so training should work 136 @@ -695,7 +695,7 @@ but it's not going down very quickly 140 00:06:06,090 --> 00:06:07,770 -如果我们一直用完这个, +如果我们一直运行这个, and if we keep running this out, 141 @@ -790,7 +790,7 @@ So this learning rate is way too high 159 00:06:50,550 --> 00:06:52,530 -用于训练变压器模型, +用于训练 transformer 模型, for training transformer models, 160 @@ -820,7 +820,7 @@ So if you recompile with that, 165 00:07:06,990 --> 00:07:09,840 -你最终会发现培训确实有效。 +你最终会发现训练确实有效。 you'll find that training actually works, at last. 166 @@ -860,7 +860,7 @@ and see what the errors look like 173 00:07:23,490 --> 00:07:25,380 -以及如何接近他们, +以及如何解决他们, and how you can approach them, 174 diff --git a/subtitles/zh-CN/75_writing-a-good-issue.srt b/subtitles/zh-CN/75_writing-a-good-issue.srt index 1afee7ceb..08a599f02 100644 --- a/subtitles/zh-CN/75_writing-a-good-issue.srt +++ b/subtitles/zh-CN/75_writing-a-good-issue.srt @@ -20,7 +20,7 @@ and you should always go there to report a bug 5 00:00:14,010 --> 00:00:16,020 -或要求新功能。 +或求新功能。 or ask for a new feature. 6 @@ -40,17 +40,17 @@ It's very important to write good issues 9 00:00:23,677 --> 00:00:27,232 -因为它将帮助你立即修复发现的错误。 +因为它将帮助你发现的错误被立即修复。 as it will help the bug you uncovered be fixed in no time. 10 00:00:27,232 --> 00:00:29,750 -对于这个视频,我们创建了一个版本的变形金刚 +对于这个视频,我们创建了一个版本的 Transformers For this video, we have created a version of Transformers 11 00:00:29,750 --> 00:00:31,066 -有一个错误。 +带有一个错误。 with a bug. 12 @@ -70,7 +70,7 @@ In this version, the following example fails. 15 00:00:41,016 --> 00:00:42,472 -错误相当神秘 +错误是相当神秘的 The error is rather cryptic 16 @@ -105,7 +105,7 @@ In our case, inspecting the traceback, 22 00:00:56,802 --> 00:00:59,645 -我们看到失败发生在管道函数内部 +我们看到失败发生在 pipeline 数内部 we see the failure happens inside the pipeline function 23 @@ -150,7 +150,7 @@ but it also will make it easier for the maintainers 31 00:01:20,610 --> 00:01:22,320 -解决你的问题。 +来解决你的问题。 to fix your problem. 32 @@ -160,7 +160,7 @@ Here we can play around a bit more with this code 33 00:01:24,030 --> 00:01:26,460 -并注意错误发生在不同的检查点 +并注意错误发生在不同的 checkpoints and notice the error happens for different checkpoints 34 @@ -175,7 +175,7 @@ and that it disappears when we use use_fast=False 36 00:01:31,262 --> 00:01:33,240 -在我们的分词器调用中。 +在我们的 tokenizer 调用中。 inside our tokenizer call. 37 @@ -190,7 +190,7 @@ that does not depend on any external files or data. 39 00:01:38,640 --> 00:01:40,350 -尝试用假值替换你的数据 +尝试用虚假的值替换你的数据 Try to replace your data by fake values 40 @@ -205,12 +205,12 @@ With all of this done, 42 00:01:43,620 --> 00:01:46,260 -我们准备开始写我们的问题。 +我们准备开始写我们的 issue 。 we are ready to start writing our issue. 43 00:01:46,260 --> 00:01:48,600 -单击错误报告旁边的按钮 +单击 Bug Report (错误报告) 旁边的按钮 Click on the button next to Bug Report 44 @@ -225,7 +225,7 @@ It will only take you a couple of minutes. 46 00:01:53,940 --> 00:01:56,460 -首先是正确命名你的问题。 +首先是正确命名你的 issue 。 The first thing is to properly name your issue. 47 @@ -280,7 +280,7 @@ There is a full list of usernames in the template. 57 00:02:23,010 --> 00:02:25,043 -由于我们的问题与分词器有关, +由于我们的问题与 tokenizer 有关, Since our issue has to do with tokenizers, 58 @@ -350,7 +350,7 @@ With all of this, you should expect an answer 71 00:02:57,030 --> 00:02:59,880 -非常快地解决你的问题,希望能快速解决。 +非常快地解决你的 issue ,希望能快速解决。 to your issue pretty fast and hopefully a quick fix. 72 diff --git a/utils/generate_subtitles.py b/utils/generate_subtitles.py index 31dccbe2c..7de26249e 100644 --- a/utils/generate_subtitles.py +++ b/utils/generate_subtitles.py @@ -1,31 +1,45 @@ import pandas as pd -from tqdm.auto import tqdm from youtube_transcript_api import YouTubeTranscriptApi from youtube_transcript_api.formatters import SRTFormatter from youtubesearchpython import Playlist from pathlib import Path import argparse -import sys +COURSE_VIDEOS_PLAYLIST = "https://youtube.com/playlist?list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o" +TASK_VIDEOS_PLAYLIST = "https://youtube.com/playlist?list=PLo2EIpI_JMQtyEr-sLJSy5_SnLCb4vtQf" +# These videos are not part of the course, but are part of the task playlist +TASK_VIDEOS_TO_SKIP = ["tjAIM7BOYhw", "WdAeKSOpxhw", "KWwzcmG98Ds", "TksaY_FDgnk", "leNG9fN9FQU", "dKE8SIt9C-w"] -def generate_subtitles(language: str, youtube_language_code: str = None): + +def generate_subtitles(language: str, youtube_language_code: str = None, is_task_playlist: bool = False): metadata = [] formatter = SRTFormatter() path = Path(f"subtitles/{language}") path.mkdir(parents=True, exist_ok=True) - playlist_videos = Playlist.getVideos("https://youtube.com/playlist?list=PLo2EIpI_JMQvWfQndUesu0nPBAtZ9gP1o") + if is_task_playlist: + playlist_videos = Playlist.getVideos(TASK_VIDEOS_PLAYLIST) + else: + playlist_videos = Playlist.getVideos(COURSE_VIDEOS_PLAYLIST) for idx, video in enumerate(playlist_videos["videos"]): video_id = video["id"] title = video["title"] title_formatted = title.lower().replace(" ", "-").replace(":", "").replace("?", "") id_str = f"{idx}".zfill(2) - srt_filename = f"subtitles/{language}/{id_str}_{title_formatted}.srt" + + if is_task_playlist: + srt_filename = f"{path}/tasks_{id_str}_{title_formatted}.srt" + else: + srt_filename = f"{path}/{id_str}_{title_formatted}.srt" # Skip course events if "Event Day" in title: continue + # Skip task videos that don't belong to the course + if video_id in TASK_VIDEOS_TO_SKIP: + continue + # Get transcript transcript_list = YouTubeTranscriptApi.list_transcripts(video_id) english_transcript = transcript_list.find_transcript(language_codes=["en", "en-US"]) @@ -51,10 +65,15 @@ def generate_subtitles(language: str, youtube_language_code: str = None): f.write("No transcript found for this video!") metadata.append({"id": video_id, "title": title, "link": video["link"], "srt_filename": srt_filename}) - break df = pd.DataFrame(metadata) - df.to_csv(f"subtitles/{language}/metadata.csv", index=False) + + if is_task_playlist: + df.to_csv(f"{path}/metadata_tasks.csv", index=False) + else: + df.to_csv(f"{path}/metadata.csv", index=False) + + if __name__ == "__main__": @@ -62,5 +81,6 @@ def generate_subtitles(language: str, youtube_language_code: str = None): parser.add_argument("--language", type=str, help="Language to generate subtitles for") parser.add_argument("--youtube_language_code", type=str, help="YouTube language code") args = parser.parse_args() - generate_subtitles(args.language, args.youtube_language_code) + generate_subtitles(args.language, args.youtube_language_code, is_task_playlist=False) + generate_subtitles(args.language, args.youtube_language_code, is_task_playlist=True) print(f"All done! Subtitles stored at subtitles/{args.language}") From 70d6f6a30ba2b138818a1d5f32e370aaf33cac99 Mon Sep 17 00:00:00 2001 From: lewtun Date: Wed, 10 Jan 2024 14:04:20 +1100 Subject: [PATCH 47/51] Bump release (#654) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * reviewed ep.59 (#406) * docs(zh-cn): Reviewed 60_what-is-the-bleu-metric.srt (#407) * finished review (#408) * docs(zh-cn): Reviewed 61_data-processing-for-summarization.srt (#409) * Fix subtitle - translation data processing (#411) * [FR] Final PR (#412) * [ko] Add chapter 8 translation (#417) * docs(zh-cn): Reviewed 62_what-is-the-rouge-metric.srt (#419) * finished review * fixed errors in original english subtitle * fixed errors (#420) * docs(zh-cn): Reviewed 63_data-processing-for-causal-language-modeling.srt (#421) * Update 63_data-processing-for-causal-language-modeling.srt * finished review * Update 63_data-processing-for-causal-language-modeling.srt * docs(zh-cn): Reviewed 65_data-processing-for-question-answering.srt (#423) * finished review * finished review * finished review (#422) * Add Ko chapter2 2.mdx (#418) * Add Ko chapter2 2.mdx * [ko] Add chapter 8 translation (#417) * docs(zh-cn): Reviewed 62_what-is-the-rouge-metric.srt (#419) * finished review * fixed errors in original english subtitle * fixed errors (#420) * docs(zh-cn): Reviewed 63_data-processing-for-causal-language-modeling.srt (#421) * Update 63_data-processing-for-causal-language-modeling.srt * finished review * Update 63_data-processing-for-causal-language-modeling.srt * docs(zh-cn): Reviewed 65_data-processing-for-question-answering.srt (#423) * finished review * finished review * finished review (#422) * Add Ko chapter2 2.mdx Co-authored-by: IL-GU KIM <53106649+dlfrnaos19@users.noreply.github.com> Co-authored-by: Yuan * update textbook link (#427) * Visual fixes (#428) * finish first round review (#429) * Fix French subtitles + refactor conversion script (#431) * Fix subtitles and scripts * Fix subtitle * Add tokenizer to MLM Trainer (#432) * Fix FR video descriptions (#433) * Fix FR video descriptions * Rename file * Fix dead GPT model docs link. (#430) * Translate into Korean: 2-3 (#434) Co-authored-by: “Ryan” <“sungjin.712@navercorp.com”> * Add korean translation of chapter5 (1,2) (#441) update toctree for chapter 5 (1, 2) ensure same title for 5-2 add updates from upstream English with custom anchors Co-Authored-By: Minho Ryu Co-authored-by: Meta Learner응용개발팀 류민호 Co-authored-by: Minho Ryu * Update 3.mdx (#444) * docs(zh-cn): Reviewed 67_the-post-processing-step-in-question-answering-(tensorflow).srt (#447) * Update 67_the-post-processing-step-in-question-answering-(tensorflow).srt * finished review * docs(zh-cn): Reviewed 66_the-post-processing-step-in-question-answering-(pytorch).srt (#448) * Update 66_the-post-processing-step-in-question-answering-(pytorch).srt * finished review * refined translation * docs(zh-cn): Reviewed 01_the-pipeline-function.srt (#452) * finish review * Update subtitles/zh-CN/01_the-pipeline-function.srt Co-authored-by: Luke Cheng <2258420+chenglu@users.noreply.github.com> Co-authored-by: Luke Cheng <2258420+chenglu@users.noreply.github.com> * finish review (#453) * Revise some unnatural translations (#458) Some unnatural translations have been revised to use expressions more popular with Chinese readers * Fix chapter 5 links (#461) * fix small typo (#460) * Add Ko chapter2 3~8.mdx & Modify Ko chapter2 2.mdx typo (#446) * Add captions for tasks videos (#464) * Add captions for tasks videos * Fix script * [FR] Add 🤗 Tasks videos (#468) * Synchronous Chinese course update Update the Chinese Course document to sha:f71cf6c3b4cb235bc75a14416c6e8a57fc3d00a7 sha date: 2023/01/06 00:02:26 UTC+8 * review sync * Update 3.mdx * format zh_CN * format all mdx * Remove temp folder * finished review (#449) * docs(zh-cn): Reviewed 31_navigating-the-model-hub.srt (#451) * docs(zh-cn): Reviewed No. 08 - What happens inside the pipeline function? (PyTorch) (#454) * docs(zh-cn): Reviewed 03_what-is-transfer-learning.srt (#457) * docs(zh-cn): 32_managing-a-repo-on-the-model-hub.srt (#469) * docs(zh-cn): Reviewed No. 10 - Instantiate a Transformers model (PyTorch) (#472) * update Chinese translation 有一些英文句子与中文语序是相反的,我直接按照最终的中文语序排列了,这样是否可以? * finish first round review * finish second round review * finish second round review * branch commit * Update subtitles/zh-CN/10_instantiate-a-transformers-model-(pytorch).srt Co-authored-by: Luke Cheng <2258420+chenglu@users.noreply.github.com> * Update subtitles/zh-CN/10_instantiate-a-transformers-model-(pytorch).srt Co-authored-by: Luke Cheng <2258420+chenglu@users.noreply.github.com> --------- Co-authored-by: Luke Cheng <2258420+chenglu@users.noreply.github.com> * docs(zh-cn): 33_the-push-to-hub-api-(pytorch).srt (#473) * docs(zh-cn): Reviewed 34_the-push-to-hub-api-(tensorflow).srt (#479) * running python utils/code_formatter.py * review 05 cn translations * review 06 cn translations * Review No.11 * translate no.24 * review 06 cn translations * review 07 cn translations * Update 23_what-is-dynamic-padding.srt * Update 23_what-is-dynamic-padding.srt * Update 23_what-is-dynamic-padding.srt * Update subtitles/zh-CN/23_what-is-dynamic-padding.srt Co-authored-by: Luke Cheng <2258420+chenglu@users.noreply.github.com> * Update subtitles/zh-CN/23_what-is-dynamic-padding.srt Co-authored-by: Luke Cheng <2258420+chenglu@users.noreply.github.com> * add blank * Review No. 11, No. 12 * Review No. 13 * Review No. 12 * Review No. 14 * finished review * optimized translation * optimized translation * docs(zh-cn): Reviewed No. 29 - Write your training loop in PyTorch * Review 15 * Review 16 * Review 17 * Review 18 * Review ch 72 translation * Update 72 cn translation * To be reviewed No.42-No.54 * No.11 check-out * No.12 check-out * No. 13 14 check-out * No. 15 16 check-out * No. 17 18 check-out * Add note for "token-*" * Reviewed No.8, 9, 10 * Reviewed No.42 * Review No.43 * finished review * optimized translation * finished review * optimized translation * Review 44(need refine) * Review 45(need refine) * Review No. 46 (need refine) * Review No.47 * Review No.46 * Review No.45 * Review No.44 * Review No.48 * Review No.49 * Review No.50 * Modify Ko chapter2 8.mdx (#465) * Add Ko chapter2 2.mdx * Add Ko chapter2 2.mdx * Add Ko chapter2 3.mdx & 4.mdx * Modify Ko chapter2 3.mdx & 4.mdx * Modify Ko chapter2 3.mdx & 4.mdx * Modify Ko chapter2 3.mdx & 4.mdx * Modify _toctree.yml * Add Ko chapter2 5.mdx * Modify Ko chapter2 4.mdx * Add doc-builder step * Add Ko chapter2 6~8.mdx & Modify Ko chapter2 2.mdx typo * Modify Ko _toctree.yml * Modify Ko chapter2 8.mdx & README.md * Fixed typo (#471) * fixed subtitle errors (#474) timestamp: 00:00:26,640 --> 00:00:28,620 modification: notification --> authentication timestamp: 00:04:21,113 --> 00:04:22,923 modification: of --> or * Fixed a typo (#475) * Update 3.mdx (#526) Fix typo * [zh-TW] Added chapters 1-9 (#477) The translation is based on Simplified Chinese version, converted via OpenCC and fixed some formatting issues. * finished review * Explain why there are more tokens, than reviews (#476) * Explain why there are more tokens, than reviews * Update chapters/en/chapter5/3.mdx --------- Co-authored-by: lewtun * [RU] Subtitles for Chapter 1 of the video course (#489) * Created a directory for the russian subtitles. Created a folder for Russian subtitles for the video course and published a translation of the introductory video from chapter 1. * Uploaded subtitles for chapter 1 Uploaded subtitles for the remaining videos for chapter 1 of the video course. * Added subtitles for chapter 2 of the video course Added STR subtitle files for the second chapter of the YouTube video course. * Delete subtitles/ru directory Removed the old translation. Incorrect timestamping. * Create 00_welcome-to-the-hugging-face-course.srt Create a directory and upload a subtitle file for the introductory video of the course. * Add files via upload Upload subtitle files for the first chapter of the course. * Review No.52 * [ru] Added the glossary and translation guide (#490) * Added the glossary and translation guide * Fixed casing * Minor fixes * Updated glossary * Glossary update * Glossary update * Glossary update * [ru] Chapters 0 and 1 proofreading, updating and translating missing sections (#491) * Chapter 0 proofreading * Chapter 1 Section 1 proofreading - Added new people from English version; - Added links to creator's pages; - Added FAQ translation; * Chapter 1 Sections 2-5 proofreading * Chapter 1 Sections 6-9 proofreading * Final proofreading and added missing quiz section * Minor spelling corrections * Review No.51 * Review No.53 * Review No.54 * finished review * modified translation * modified translation * modified subtitle use the same text appeared in video * translated * Fix typo (#532) * review chapter4/2 * review chapter4/2 * review chapter4/2 * Review 75 * Review No.20, need review some * docs(zh-cn): Reviewed Chapter 7/1 * Update 1.mdx * Review No.22 * Review No.21 (need refinement) * Review No.30, need review: 26 27 28 30 73 74 * Review 30 (good) * Review 20 * Review 21 (refine) * Review 21 * Review 22 * Review 26 * Review 27 * Review 28 * Review 30 * Review 73 * Review 74 * Fix typo * Review 26-28, 42-54, 73-75 * The GPT2 link is broken The link `/course/en/chapter7/section6` does not exist in the course. Corrected to `/course/en/chapter7/6`. * typo in `Now your turn!` section Duplicated `the` was removed * `chunk_size` should be instead of `block_size` `chunk_size` should be instead of `block_size` (`block_size` was never mentioned before) * refactor: rephrase text to improve clarity and specificity In context to "training with a dataset specific to your task" and "train directly for the final task", I was not able to infer easily that "directly" here implies training from scratch. * Demo link fixes (#562) * demo link fixes * minor demo fix * Bump release (#566) * Add note about `remove_unused_columns` for whole word masking * Merge pull request #24 from huggingface/fix-typo Fix typo * Merge pull request #26 from huggingface/fix-qa-offsets Fix inequalities in answer spans for QA chapter * Merge pull request #30 from huggingface/fix-modelcard-url Update model card URL * Merge pull request #69 from yulonglin/patch-1 Correct typo mixing up space and newline symbols * Bump release (#99) * Bump release * Update author list Co-authored-by: DOOHAE JUNG Co-authored-by: m_khandaker Co-authored-by: Md. Al-Amin Khandaker Co-authored-by: ftarlaci <18291571+ftarlaci@users.noreply.github.com> Co-authored-by: Doohae Jung <80743307+Doohae@users.noreply.github.com> Co-authored-by: melaniedrevet * Bump release (#115) * ko-chapter1/1 * ko _toctree.yml created * Fix the issue #80 * Single expression changed * ko/chapter1 finished * ko/chapter0 finished * ko/chapter0 finished * reviewed by @bzantium ko/chapter0 * reviewed by @bzantium chapter0 & fixed typo * reviewed by @rainmaker712 * maximize Korean expressions * [Chapter 1] bangla traslation initial commit * Update 1.mdx update and fix formating * Fix formating and typos * translate _toctree.yml 0-1 chapter * Add Korean to CI * [tr] Translated chapter1/2.mdx * remove translation from sec titles not yet translated * Add authors [th ru] * [FIX] _toctree.yml * Update chapters/bn/chapter0/1.mdx [FIX] syntax formatting Co-authored-by: lewtun * tag typos & indentation & unnatural expressions * modified toctree.yml for chapter1/2 * modified toctree.yml for chapter1/2 & fix typo * French Translation - Chapter 5 * Add Bengali to CI * Update author list * Adding translations for 2/4 and 2/5 🚀 (#74) * Adding translations for 2/4 and 2/5 🚀 * Remove English content Co-authored-by: lewtun * Translation to Russian (#97) * translation of chapter 2/section 1 * add section 1 / chapter 2 to _toctree.yml * Translation of Chapter0 to Hindi (#86) * Hindi?Chapter0-Part_1 * Hindi/Chapter0-Part_2 * Chapter 0 Persian Translation First Draft (#95) * merged branch0 into main. no toctree yet. * updated toctree. * Updated the glossary with terms from chapter0. * Second draft in collab w/ @schoobani. Added empty chapter1 for preview. * Glossary typo fix. * Translation of Chapter0 (setup) to Arabic (#104) * Add AR translation for `introduction` * Fix alignment & format * Add Arabic to CI build * Russian - Chapter 1 finished (#98) * 01/4 start * 1/4 finished * 1/5 finished * 1/5 update toc * 1/6 finished * 1/7 finished * 1/8 finished * 1/9 finished * 1/4 fix * toc update * Chinese - Chapter 1 finished (#113) * Chinese - Chapter 1 finished * Add zh to the languages field Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> * [PT] Translation of chapter 2 (#107) * add PT translate to 2.1 * add PT translate to 2.2 * add portuguese translation to 2.3 * WIP portuguese translation to 2.4 * add portuguese translation to 2.4 * add portuguese translation to 2.5 * add portuguese translation to 2.6 * add _toctree infos Co-authored-by: lewtun * [FR] Translation of chapter 2 & event + Review of chapters 0 & 5 (#106) * Update _toctree.yml Add chapter 2 + little fix of chapter 5 * Update 1.mdx Review of chapter 0 * Create 1.mdx * Create 2.mdx * Create 3.mdx * Create 4.mdx * Create 5.mdx * Create 6.mdx * Create 7.mdx * Create 8.mdx * Update 8.mdx Since AutoNLP has recently been renamed to AutoTrain, let me make the correction on the English file * Update 1.mdx Review of chapter 5/1 * Update 2.mdx Review of chapter 5/2 * Update 3.mdx Review of chapter 5/3 * Update 4.mdx Review of chapter 5/4 * Update 5.mdx Review of chapter 5/5 * Update 6.mdx Review of chapter 5/6 * Update 7.mdx Review of chapter 5/7 * Update 8.mdx Review of chapter 5/8 * Create 1.mdx event's translation * Update _toctree.yml add event to the tree * Update _toctree.yml deletion of the files that pose a problem to pass the checks, will be resubmitted in another PR * Delete 1.mdx deletion of the files that pose a problem to pass the checks, will be resubmitted in another PR * make style correction * Update _toctree.yml the - * Fix spacing Co-authored-by: Lewis Tunstall * [th] Translated Chapter2/1 (#83) * Finish chapter2/1 * Update _toctree.yml * ko-chapter1/1 * ko _toctree.yml created * Fix the issue #80 * Single expression changed * ko/chapter1 finished * ko/chapter0 finished * ko/chapter0 finished * reviewed by @bzantium ko/chapter0 * reviewed by @bzantium chapter0 & fixed typo * reviewed by @rainmaker712 * maximize Korean expressions * [Chapter 1] bangla traslation initial commit * Update 1.mdx update and fix formating * Fix formating and typos * translate _toctree.yml 0-1 chapter * Add Korean to CI * remove translation from sec titles not yet translated * Add authors [th ru] * [FIX] _toctree.yml * Update chapters/bn/chapter0/1.mdx [FIX] syntax formatting Co-authored-by: lewtun * tag typos & indentation & unnatural expressions * modified toctree.yml for chapter1/2 * modified toctree.yml for chapter1/2 & fix typo * Add Bengali to CI * Update author list * Adding translations for 2/4 and 2/5 🚀 (#74) * Adding translations for 2/4 and 2/5 🚀 * Remove English content Co-authored-by: lewtun * Translation to Russian (#97) * translation of chapter 2/section 1 * add section 1 / chapter 2 to _toctree.yml * Translation of Chapter0 to Hindi (#86) * Hindi?Chapter0-Part_1 * Hindi/Chapter0-Part_2 * Chapter 0 Persian Translation First Draft (#95) * merged branch0 into main. no toctree yet. * updated toctree. * Updated the glossary with terms from chapter0. * Second draft in collab w/ @schoobani. Added empty chapter1 for preview. * Glossary typo fix. * Translation of Chapter0 (setup) to Arabic (#104) * Add AR translation for `introduction` * Fix alignment & format * Add Arabic to CI build * Russian - Chapter 1 finished (#98) * 01/4 start * 1/4 finished * 1/5 finished * 1/5 update toc * 1/6 finished * 1/7 finished * 1/8 finished * 1/9 finished * 1/4 fix * toc update * Chinese - Chapter 1 finished (#113) * Chinese - Chapter 1 finished * Add zh to the languages field Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> * [PT] Translation of chapter 2 (#107) * add PT translate to 2.1 * add PT translate to 2.2 * add portuguese translation to 2.3 * WIP portuguese translation to 2.4 * add portuguese translation to 2.4 * add portuguese translation to 2.5 * add portuguese translation to 2.6 * add _toctree infos Co-authored-by: lewtun * [FR] Translation of chapter 2 & event + Review of chapters 0 & 5 (#106) * Update _toctree.yml Add chapter 2 + little fix of chapter 5 * Update 1.mdx Review of chapter 0 * Create 1.mdx * Create 2.mdx * Create 3.mdx * Create 4.mdx * Create 5.mdx * Create 6.mdx * Create 7.mdx * Create 8.mdx * Update 8.mdx Since AutoNLP has recently been renamed to AutoTrain, let me make the correction on the English file * Update 1.mdx Review of chapter 5/1 * Update 2.mdx Review of chapter 5/2 * Update 3.mdx Review of chapter 5/3 * Update 4.mdx Review of chapter 5/4 * Update 5.mdx Review of chapter 5/5 * Update 6.mdx Review of chapter 5/6 * Update 7.mdx Review of chapter 5/7 * Update 8.mdx Review of chapter 5/8 * Create 1.mdx event's translation * Update _toctree.yml add event to the tree * Update _toctree.yml deletion of the files that pose a problem to pass the checks, will be resubmitted in another PR * Delete 1.mdx deletion of the files that pose a problem to pass the checks, will be resubmitted in another PR * make style correction * Update _toctree.yml the - * Fix spacing Co-authored-by: Lewis Tunstall * [th] Translated Chapter2/1 (#83) * Finish chapter2/1 * Update _toctree.yml * Add Hindi to CI (#116) Co-authored-by: DOOHAE JUNG Co-authored-by: m_khandaker Co-authored-by: Md. Al-Amin Khandaker Co-authored-by: ftarlaci <18291571+ftarlaci@users.noreply.github.com> Co-authored-by: Doohae Jung <80743307+Doohae@users.noreply.github.com> Co-authored-by: melaniedrevet Co-authored-by: Jose M Munoz Co-authored-by: svv73 <88366711+svv73@users.noreply.github.com> Co-authored-by: Vedant Pandya Co-authored-by: Bahram Shamshiri Co-authored-by: Giyaseddin Bayrak <34009210+giyaseddin@users.noreply.github.com> Co-authored-by: Pavel <60391448+pdumin@users.noreply.github.com> Co-authored-by: 1375626371 <40328311+1375626371@users.noreply.github.com> Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> Co-authored-by: João Gustavo A. Amorim Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Cherdsak Kingkan * Bump release 4 (#133) * Bump release (#138) * ko-chapter1/1 * ko _toctree.yml created * Fix the issue #80 * Single expression changed * ko/chapter1 finished * ko/chapter0 finished * ko/chapter0 finished * reviewed by @bzantium ko/chapter0 * reviewed by @bzantium chapter0 & fixed typo * reviewed by @rainmaker712 * maximize Korean expressions * [Chapter 1] bangla traslation initial commit * Update 1.mdx update and fix formating * Fix formating and typos * translate _toctree.yml 0-1 chapter * Add Korean to CI * [tr] Translated chapter1/2.mdx * remove translation from sec titles not yet translated * Add authors [th ru] * [FIX] _toctree.yml * Update chapters/bn/chapter0/1.mdx [FIX] syntax formatting Co-authored-by: lewtun * tag typos & indentation & unnatural expressions * modified toctree.yml for chapter1/2 * modified toctree.yml for chapter1/2 & fix typo * French Translation - Chapter 5 * Add Bengali to CI * Update author list * Adding translations for 2/4 and 2/5 🚀 (#74) * Adding translations for 2/4 and 2/5 🚀 * Remove English content Co-authored-by: lewtun * Translation to Russian (#97) * translation of chapter 2/section 1 * add section 1 / chapter 2 to _toctree.yml * Translation of Chapter0 to Hindi (#86) * Hindi?Chapter0-Part_1 * Hindi/Chapter0-Part_2 * Chapter 0 Persian Translation First Draft (#95) * merged branch0 into main. no toctree yet. * updated toctree. * Updated the glossary with terms from chapter0. * Second draft in collab w/ @schoobani. Added empty chapter1 for preview. * Glossary typo fix. * Translation of Chapter0 (setup) to Arabic (#104) * Add AR translation for `introduction` * Fix alignment & format * Add Arabic to CI build * Russian - Chapter 1 finished (#98) * 01/4 start * 1/4 finished * 1/5 finished * 1/5 update toc * 1/6 finished * 1/7 finished * 1/8 finished * 1/9 finished * 1/4 fix * toc update * Chinese - Chapter 1 finished (#113) * Chinese - Chapter 1 finished * Add zh to the languages field Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> * [PT] Translation of chapter 2 (#107) * add PT translate to 2.1 * add PT translate to 2.2 * add portuguese translation to 2.3 * WIP portuguese translation to 2.4 * add portuguese translation to 2.4 * add portuguese translation to 2.5 * add portuguese translation to 2.6 * add _toctree infos Co-authored-by: lewtun * [FR] Translation of chapter 2 & event + Review of chapters 0 & 5 (#106) * Update _toctree.yml Add chapter 2 + little fix of chapter 5 * Update 1.mdx Review of chapter 0 * Create 1.mdx * Create 2.mdx * Create 3.mdx * Create 4.mdx * Create 5.mdx * Create 6.mdx * Create 7.mdx * Create 8.mdx * Update 8.mdx Since AutoNLP has recently been renamed to AutoTrain, let me make the correction on the English file * Update 1.mdx Review of chapter 5/1 * Update 2.mdx Review of chapter 5/2 * Update 3.mdx Review of chapter 5/3 * Update 4.mdx Review of chapter 5/4 * Update 5.mdx Review of chapter 5/5 * Update 6.mdx Review of chapter 5/6 * Update 7.mdx Review of chapter 5/7 * Update 8.mdx Review of chapter 5/8 * Create 1.mdx event's translation * Update _toctree.yml add event to the tree * Update _toctree.yml deletion of the files that pose a problem to pass the checks, will be resubmitted in another PR * Delete 1.mdx deletion of the files that pose a problem to pass the checks, will be resubmitted in another PR * make style correction * Update _toctree.yml the - * Fix spacing Co-authored-by: Lewis Tunstall * [th] Translated Chapter2/1 (#83) * Finish chapter2/1 * Update _toctree.yml * Add Hindi to CI (#116) * Update README.md (#87) * Update authors on README (#120) * Update authors * French translation `Chapter1` full (#56) * traduction 1st part of chapter1 * fix typo * fix job titles and encoder-decoder translation * add part 2 for 1st chapter * fix some typo part2 * fix Transformer -> Transformers * add part 3 not totally ended * end of part3 of chapter1 * part9 chapter 1 * add part7 chapter 1 * add part5 chapter 1 * part 6 chapter 1 * add part8 chapter 1 * end quizz of chapter * add last part of chapter 1 Co-authored-by: ChainYo * Translate to Japanese Chapter0 (#123) * start working * translate chapter0/1.mdx * [FA] First draft of Chapter2/Page2 (#129) * merged branch0 into main. no toctree yet. * updated toctree. * Updated the glossary with terms from chapter0. * Second draft in collab w/ @schoobani. Added empty chapter1 for preview. * Glossary typo fix. * Added missing backticks. * Removed a couple of bad indefinite articles I forgot. * First draft of ch2/p2. Adds to glossary. Trans. guidelines moved out. * Fixed missing diacritics, fixed the py/tf switch placing. Other fixes. * Changed the equivalent for prediction per @kambizG 's direction. * Redid ambiguous passage in translation per @lewtun 's direction. * [th] Finished whole Chapter 2 translation (#127) * Finish chapter2/1 * delete untranslated files * chapter2/2 WIP * Delete WIP files * WIP chapter2/2 * Fixed conflict * Update _toctree.yml * Update _toctree.yml * Finished Chapter2/2 * Finish all chapter2/n * Finish all chapter2/n * Fixed Ch2/8 as PR run failed * [de] Translation Chapter 0 (#130) * Copy files to newly created german dir (de) * Add translation for chapter 0 * Clean up english files for chapter 1 * Change _toctree.yml for chapter 0 * Fix whitespaces * Fix whitespaces again * Adjust _toctree.yml - leave only chaper 0 * Add German language (de) to workflow yaml files * [de] German Translation Guide (#132) * German Translation Guide * Add German Glossary to TOC * Chapter 1, Section 1 Bengali translation (#124) * [ADD] Chapter 1, Section 1 benglai tranlation * [FIX] toc * [FIX] commit mistakes * [FIX] remove the Eng duplicates Co-authored-by: m_khandaker * [FR] Review of chapters 0, 2 & 5 + add chapters 6, 7, 8 & event (#125) * Create 1.mdx Event translation * Create 1.mdx * Chapter 6 in French * Update 1.mdx fix italic * Update 9.mdx fix italic * Update 3.mdx fix italic * Update 4.mdx fix italic * Update 4.mdx * Update 1.mdx little fix * Update 2.mdx little fix * Update 4.mdx fix italic * Update 8.mdx fix italic * Update 1.mdx little fix * Update 2.mdx little fix * Update 3.mdx little fix * Update 5.mdx little fix * Update 7.mdx little fix * Update 8.mdx little fix * add chapter8 * Update 6.mdx fix italic * Update 3.mdx fix, fix everywhere * Update 2.mdx fix, fix everywhere * Update 4.mdx fix, fix everywhere * Update 4_tf.mdx fix, fix everywhere * Add files via upload add chapter 7 * Update 1.mdx fix links * Update 2.mdx fix, fix everywhere * Update 3.mdx fix, fix everywhere * Update 4.mdx fix, fix everywhere * Update 5.mdx * Update 6.mdx fix, fix everywhere * Update 7.mdx fix, fix everywhere * Update 3.mdx fix link * Update 8.mdx fix link * Update 2.mdx fix link * Update 4.mdx little fix * Update 6.mdx * Update 7.mdx * Update 8.mdx fix * Update 2.mdx little fix * Update 3.mdx little fix * Update 5.mdx * Update 4_tf.mdx little fix * Update _toctree.yml Forgot the toctree * Update _toctree.yml fix local fields * Update _toctree.yml My bad, I forgot some 🙃 * Update 7.mdx I don't know why it was there... * Update 1.mdx * [de] Chapter 3 translation (#128) * chapter 3 part 1 DE * [DE] Chapter 3 - Part 2 * Prepare TOC-Tree * Fein-tuning * Initial translation * Glossary additions for C3P3 * C3P2 style * [de] Chapter 3 P3-TF initial translation * [de] Chapter 3 P4 initial translation * [de] Chapter 3 Part 5 initial translation * [de] Chapter 3 P6 Initial translation * Missing commas * fixing quotes * Mark third option on chapter 8, question 8 as correct (#135) * doc_change(translation): translating course from english to gujarati (#126) * change(translation): chapter0 to gujarati content translated: Chapter0/1.mdx - Introduction commit-by: menonashwathi4@gmail.com * Revert "change(translation): chapter0 to gujarati" This reverts commit c27e06992af8892687f343a19368ce322d69e8b2. * doc_change(translation): translation to gj translated content: chapters/gj/chapter0.mdx - introduction * doc_change(translation): translation to gj translated content: chapters/gj/chapter0.mdx - introduction * Delete _toctree.yml * change: adding gj to github workflow * nit: fix heading * Update authors (#136) * [FA] First draft of Chapter4/Page1 (#134) * added chapter4 title and it's first section * added first draft of Chapter4/Page1 * minor fix * updated the title according to convention * applied some updates according to convention * added footnotes, minor improvements * applied tweaks according to review points * the new draft of glossary according to PR #134 * fixed an inconsistant title * minor fix for better compatibility with T points * applied final touches for this round of G updates * [FR] End of chapter 3 + chapter 4 (#137) * add chapters 3 & 4 * Update 2.mdx fix links * Update 3.mdx some fix * Update 6.mdx fix tag * Update 3.mdx add link to chapter 7 * Update 3_tf.mdx add link to chapter 7 * Update _toctree.yml Co-authored-by: DOOHAE JUNG Co-authored-by: m_khandaker Co-authored-by: Md. Al-Amin Khandaker Co-authored-by: ftarlaci <18291571+ftarlaci@users.noreply.github.com> Co-authored-by: Doohae Jung <80743307+Doohae@users.noreply.github.com> Co-authored-by: melaniedrevet Co-authored-by: Jose M Munoz Co-authored-by: svv73 <88366711+svv73@users.noreply.github.com> Co-authored-by: Vedant Pandya Co-authored-by: Bahram Shamshiri Co-authored-by: Giyaseddin Bayrak <34009210+giyaseddin@users.noreply.github.com> Co-authored-by: Pavel <60391448+pdumin@users.noreply.github.com> Co-authored-by: 1375626371 <40328311+1375626371@users.noreply.github.com> Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> Co-authored-by: João Gustavo A. Amorim Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Cherdsak Kingkan Co-authored-by: Thomas Chaigneau <50595514+ChainYo@users.noreply.github.com> Co-authored-by: ChainYo Co-authored-by: hiromu <45531573+hiromu166@users.noreply.github.com> Co-authored-by: Cherdsak Kingkan Co-authored-by: Marcus Fraaß Co-authored-by: Jesper Dramsch Co-authored-by: amyeroberts Co-authored-by: Ash <103081562+ashwathim@users.noreply.github.com> Co-authored-by: Hamed Homaei Rad * Bump release (#147) * Bump release (#161) * Fix typos in chapter 9 (#176) (#180) Co-authored-by: regisss <15324346+regisss@users.noreply.github.com> * Bump release (#187) * Chapter 2 Section 1 Bengali Translation (huggingface#72) (#168) * [TH] Chapter 6 Section 1 and 2 (#171) Co-authored-by: Suteera * [FA] CH1 / P1-2 (#142) * Spanish Chapter 3: sections 1 & 2 (#162) * fix typos in bpe, wordpiece, unigram (#166) * [FR] French Review (#186) * Part 7: Training a causal... fixes (#179) * typo & error mitigation * consistency * Trainer.predict() returns 3 fields * ran make style * [TR] Translated Chapter 1.6 🤗 (#185) * added chapter 1/6 to _toctree.yml * [TR] Translated Chapter 1.6 🤗 Co-authored-by: Avishek Das Co-authored-by: Suteera Seeha <33692408+meanna@users.noreply.github.com> Co-authored-by: Suteera Co-authored-by: Saeed Choobani Co-authored-by: Fermin Ordaz Co-authored-by: Kerem Turgutlu Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Sebastian Sosa <37946988+CakeCrusher@users.noreply.github.com> Co-authored-by: tanersekmen <56790802+tanersekmen@users.noreply.github.com> * Bump release 10 (#194) * Bump release (#195) * Bump release 12 (#209) * Bump release (#224) * Bump release (#229) * Bump release (#236) * Bump release (#258) * Bump release (#270) * Bump release (#274) * Bump release (#286) * Bump release (#288) * Chapter 2 Section 1 Bengali Translation (huggingface#72) (#168) * [TH] Chapter 6 Section 1 and 2 (#171) Co-authored-by: Suteera * [FA] CH1 / P1-2 (#142) * Spanish Chapter 3: sections 1 & 2 (#162) * fix typos in bpe, wordpiece, unigram (#166) * [FR] French Review (#186) * Part 7: Training a causal... fixes (#179) * typo & error mitigation * consistency * Trainer.predict() returns 3 fields * ran make style * [TR] Translated Chapter 1.6 🤗 (#185) * added chapter 1/6 to _toctree.yml * [TR] Translated Chapter 1.6 🤗 * [PT][Chapter 01 - 2.mdx] - issue #51 (#170) * Fix Gradio ToC (#193) * Add Gradio authors and Blocks event (#189) * Update 6.mdx (#188) Correct link to Transformer XL doc * Add translating notes and glossary to Spanish (#192) * Add translating notes and glosary to Spanish * Adding glossary to the toc * add pt 4.3 (#191) * [FR] Visual corrections (#190) * [PT] add chapter 4.4 and 4.5 (#196) * fix invite discord link (#197) * [FA] Second draft of CH2/P1-2 (#139) * added chapter3 in hindi (#198) * [TR] Chapter 3/1 (#165) * [RU] Ch3-1/2/3 (#200) * [PT] add 5.1 and 5.2 (#204) * [FA] - Ch3 - P1 and P2 (#199) * [PT] add `end-of-chapter quiz` for chapter 4 (4.6) (#201) Co-authored-by: lewtun * Chapter1: 2.mdx Translated. (#206) * Remove comments from Persian ToC (#210) * Fix CI URL for PRs (#211) * code fragment & english syntax and meaning (#203) * Updated Ch1/1 with Emoji (#214) * Add missing numpy import (#217) * [ES] translate sections 8.1 and 8.2 (#215) * Fix path to datasets (#216) * [PT] add 5.3 (#218) * fix 4.3 (#223) * Fix notebook generation (#227) * Add Gradio nb links * add 5.4 (#226) * add pt wip (#225) * Added Gujarati List. (#221) * Add Gradio nbs links to fr (#228) * Chinese - Chapter 3finished (#219) * add ch7 at _toctree and translate 7.1 (#222) * add 5.5 (#235) * [FR] Review of chapter 7 (#233) * Italian translation - chapter 4 (#230) * Added Thai translation of chapters 3 (#231) * [Ru] Add part 2, chapter 2 (#234) * Update 8.mdx (#237) - Remove Gradio Blocks Party - Add, Where to next? section * Created HI/Chapter1/5.mdx (#232) * Add Spanish chaper3/section4, update toc and glossary (#238) * [RU] Chapter 3 finished (#239) * [PT] add 5.6 and 5.7 (#240) * [EN] Visual corrections (#245) * Translation for 1/4, 1/5 and 1/6. (#247) * add event in PT (#250) * Pin version of black (#252) * Translate ja event (#241) * [PT] add quiz chapter 5 (#243) * Update 5.mdx (#253) inconsistent naming with line 327 * Translation for Traditional Chinese (zh-tw) chapter0 (#251) Co-authored-by: Lewis Tunstall * Translated the whole Chapter 3 to Thai (#255) * Japanese chapter 4 (#244) * Translation of 1/7, 1/8, and 1/9. (#263) * [PT] add chapter 8.1 and 8.2 (#265) * [RU] Chapter 4 (#269) * Add Thai translation for chapter 6.3b to 6.10 (#268) * add 8.3 (#266) * 3.mdx of chapter 01 (#260) Co-authored-by: Lewis Tunstall * Fix typo (#271) * [PT] add chapter 6.1 (#273) * add Japanese chapter7 (#267) * replace `load_metric` with `evaluate.load` (#285) * update `load_metric` refs to `evaluate.load` Co-authored-by: lewtun * [GJ] Translation to Gujarati - Ch0 Setup (#287) * [PT] add chapter 6.2 and 6.3 (#279) * zh-CN - Chapter 4,5finished (#281) Co-authored-by: Lewis Tunstall * Chapter 01 - Done [PT] #51 (#280) Co-authored-by: Lewis Tunstall Co-authored-by: Avishek Das Co-authored-by: Suteera Seeha <33692408+meanna@users.noreply.github.com> Co-authored-by: Suteera Co-authored-by: Saeed Choobani Co-authored-by: Fermin Ordaz Co-authored-by: Kerem Turgutlu Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Sebastian Sosa <37946988+CakeCrusher@users.noreply.github.com> Co-authored-by: tanersekmen <56790802+tanersekmen@users.noreply.github.com> Co-authored-by: Victor Costa <54755870+victorescosta@users.noreply.github.com> Co-authored-by: Camille Couturier Co-authored-by: João Gustavo A. Amorim Co-authored-by: Bahram Shamshiri Co-authored-by: Kavya <36916536+robotjellyzone@users.noreply.github.com> Co-authored-by: Batuhan Ayhan Co-authored-by: Pavel <60391448+pdumin@users.noreply.github.com> Co-authored-by: Kambiz Ghoorchian Co-authored-by: Vedant Pandya Co-authored-by: Diego Vargas <91356068+dzarkV@users.noreply.github.com> Co-authored-by: Thomas O'Brien Co-authored-by: Lincoln V Schreiber Co-authored-by: 1375626371 <40328311+1375626371@users.noreply.github.com> Co-authored-by: Giorgio Severi Co-authored-by: svv73 <88366711+svv73@users.noreply.github.com> Co-authored-by: Ömer Faruk Özdemir Co-authored-by: Caterina Bonan <97481648+CaterinaBi@users.noreply.github.com> Co-authored-by: Hiromu Hota Co-authored-by: trtd56 <5toda6@gmail.com> Co-authored-by: Mehrdad Nezamdoost Co-authored-by: Wolvz Co-authored-by: a-krirk <56425947+a-krirk@users.noreply.github.com> Co-authored-by: atgctg <105969161+atgctg@users.noreply.github.com> Co-authored-by: Thiago Medeiros Co-authored-by: webbigdata-jp <87654083+webbigdata-jp@users.noreply.github.com> Co-authored-by: Leandro von Werra Co-authored-by: Bhadresh Savani * Bump release (#295) * Bump release (#296) * Bump release (#299) * Bump release (#305) * Chinese - Chapter 1 finished * Add zh to the languages field Add zh to the languages field in the build_documentation.yml and build_pr_documentation.yml files * Remove untranslated chapters in _toctree.yml Remove all these sections that haven't been translated yet Remove Chapter 0 from the table of contents since it hasn't been translated yet * Fixed an error in the translation format Fixed an error in the translation format of Chapter 1, Section 3 * Added a small part of the missing content * Fix style * Complete the translation of Chapters 0 and 2 * Fixed some bugs ·Fixed some formatting errors ·Moved Chapters 0 and 2 to Simplified Chinese * Add files via upload Formatting revisions and some translation corrections * run make style to format chapter1 session3 * run make style to format code * run make style to format code * Fix style * Chapter 2 Section 1 Bengali Translation (huggingface#72) (#168) * [TH] Chapter 6 Section 1 and 2 (#171) Co-authored-by: Suteera * [FA] CH1 / P1-2 (#142) * Spanish Chapter 3: sections 1 & 2 (#162) * fix typos in bpe, wordpiece, unigram (#166) * [FR] French Review (#186) * Part 7: Training a causal... fixes (#179) * typo & error mitigation * consistency * Trainer.predict() returns 3 fields * ran make style * [TR] Translated Chapter 1.6 🤗 (#185) * added chapter 1/6 to _toctree.yml * [TR] Translated Chapter 1.6 🤗 * [PT][Chapter 01 - 2.mdx] - issue #51 (#170) * Fix Gradio ToC (#193) * Add Gradio authors and Blocks event (#189) * Update 6.mdx (#188) Correct link to Transformer XL doc * Add translating notes and glossary to Spanish (#192) * Add translating notes and glosary to Spanish * Adding glossary to the toc * add pt 4.3 (#191) * [FR] Visual corrections (#190) * [PT] add chapter 4.4 and 4.5 (#196) * fix invite discord link (#197) * [FA] Second draft of CH2/P1-2 (#139) * added chapter3 in hindi (#198) * [TR] Chapter 3/1 (#165) * [RU] Ch3-1/2/3 (#200) * [PT] add 5.1 and 5.2 (#204) * Add placeholders for audio chapters (#208) * [FA] - Ch3 - P1 and P2 (#199) * [PT] add `end-of-chapter quiz` for chapter 4 (4.6) (#201) Co-authored-by: lewtun * Chapter1: 2.mdx Translated. (#206) * Remove comments from Persian ToC (#210) * Fix CI URL for PRs (#211) * code fragment & english syntax and meaning (#203) * Updated Ch1/1 with Emoji (#214) * Add missing numpy import (#217) * Updata chapter3 * Code format for chapter3 * Updata yml file of chapter3 * Uptata yml file of chapter3 * Fix yml file bug * [ES] translate sections 8.1 and 8.2 (#215) * Fix path to datasets (#216) * [PT] add 5.3 (#218) * fix 4.3 (#223) * Run make style * Fix notebook generation (#227) * Add Gradio nb links * add 5.4 (#226) * add pt wip (#225) * Added Gujarati List. (#221) * Fix quality * Add Gradio nbs links to fr (#228) * Fix ToC tree * Remove audio templates * Fix fr section * Fix fr chapter * Chinese - Chapter 3finished (#219) * add ch7 at _toctree and translate 7.1 (#222) * add 5.5 (#235) * [FR] Review of chapter 7 (#233) * Italian translation - chapter 4 (#230) * Added Thai translation of chapters 3 (#231) * [Ru] Add part 2, chapter 2 (#234) * Update 8.mdx (#237) - Remove Gradio Blocks Party - Add, Where to next? section * Created HI/Chapter1/5.mdx (#232) * Add Spanish chaper3/section4, update toc and glossary (#238) * [RU] Chapter 3 finished (#239) * [PT] add 5.6 and 5.7 (#240) * [EN] Visual corrections (#245) * Translation for 1/4, 1/5 and 1/6. (#247) * add event in PT (#250) * Pin version of black (#252) * Translate ja event (#241) * [PT] add quiz chapter 5 (#243) * Update 5.mdx (#253) inconsistent naming with line 327 * Translation for Traditional Chinese (zh-tw) chapter0 (#251) Co-authored-by: Lewis Tunstall * Translated the whole Chapter 3 to Thai (#255) * Japanese chapter 4 (#244) * Translation of 1/7, 1/8, and 1/9. (#263) * [PT] add chapter 8.1 and 8.2 (#265) * [RU] Chapter 4 (#269) * Add Thai translation for chapter 6.3b to 6.10 (#268) * add 8.3 (#266) * 3.mdx of chapter 01 (#260) Co-authored-by: Lewis Tunstall * Fix typo (#271) * [PT] add chapter 6.1 (#273) * add Japanese chapter7 (#267) * zh-CN - Chapter 4,5finished * replace `load_metric` with `evaluate.load` (#285) * update `load_metric` refs to `evaluate.load` Co-authored-by: lewtun * [GJ] Translation to Gujarati - Ch0 Setup (#287) * [PT] add chapter 6.2 and 6.3 (#279) * Fix formatting * Debug formatting * Debug FR formatting * zh-CN - Chapter 4,5finished (#281) Co-authored-by: Lewis Tunstall * Chapter 01 - Done [PT] #51 (#280) Co-authored-by: Lewis Tunstall * tf_default_data_collator seems to have moved * zh-CN - Chapter 6finished * Revert "Merge branch 'huggingface:main' into main" This reverts commit aebb46e12f9f87a4303f8bb4f0f2cf545eb83b21, reversing changes made to 69187a3789e8d3d2d0de821ebe495f111d1cc73d. * Revert "zh-CN - Chapter 6finished" This reverts commit e69fce28d3a7b35b76c4f768a6cedf295b37d8c9. * zh-CN - Chapter 6finished * fix style * undo bad commit * Chapter5it (#278) * added the italian translation for unit 1 chapter5 Co-authored-by: Leandro von Werra * Vietnamese translation (#293) * Update .github/workflows/build_pr_documentation.yml Co-authored-by: lewtun * Translate JP chapter 8 (#249) * Italian translation - Chapter 8 (#272) * Translation to Vietnamese - chapter 5 (#297) * Add course contributors (#298) * Add CourseFloatingBanner component * DocNotebookDropdown -> CourseFloatingBanner * Italian translation Ch 2/1, 2/2 (#300) * Add contributors (#304) * Add forum button (#306) Co-authored-by: 1375626371 <40328311+1375626371@users.noreply.github.com> Co-authored-by: 1375626371 <1375626371@qq.com> Co-authored-by: Avishek Das Co-authored-by: Suteera Seeha <33692408+meanna@users.noreply.github.com> Co-authored-by: Suteera Co-authored-by: Saeed Choobani Co-authored-by: Fermin Ordaz Co-authored-by: Kerem Turgutlu Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Sebastian Sosa <37946988+CakeCrusher@users.noreply.github.com> Co-authored-by: tanersekmen <56790802+tanersekmen@users.noreply.github.com> Co-authored-by: Victor Costa <54755870+victorescosta@users.noreply.github.com> Co-authored-by: Camille Couturier Co-authored-by: João Gustavo A. Amorim Co-authored-by: Bahram Shamshiri Co-authored-by: Kavya <36916536+robotjellyzone@users.noreply.github.com> Co-authored-by: Batuhan Ayhan Co-authored-by: Pavel <60391448+pdumin@users.noreply.github.com> Co-authored-by: Kambiz Ghoorchian Co-authored-by: Vedant Pandya Co-authored-by: Diego Vargas <91356068+dzarkV@users.noreply.github.com> Co-authored-by: Thomas O'Brien Co-authored-by: Lincoln V Schreiber Co-authored-by: Giorgio Severi Co-authored-by: svv73 <88366711+svv73@users.noreply.github.com> Co-authored-by: Ömer Faruk Özdemir Co-authored-by: Caterina Bonan <97481648+CaterinaBi@users.noreply.github.com> Co-authored-by: Hiromu Hota Co-authored-by: trtd56 <5toda6@gmail.com> Co-authored-by: Mehrdad Nezamdoost Co-authored-by: Wolvz Co-authored-by: a-krirk <56425947+a-krirk@users.noreply.github.com> Co-authored-by: atgctg <105969161+atgctg@users.noreply.github.com> Co-authored-by: Thiago Medeiros Co-authored-by: webbigdata-jp <87654083+webbigdata-jp@users.noreply.github.com> Co-authored-by: Leandro von Werra Co-authored-by: Bhadresh Savani Co-authored-by: Andreas Ehrencrona Co-authored-by: leandro Co-authored-by: Matt Co-authored-by: Nolanogenn <52080100+Nolanogenn@users.noreply.github.com> Co-authored-by: Hồng Hạnh Co-authored-by: Younes Belkada <49240599+younesbelkada@users.noreply.github.com> Co-authored-by: Edoardo Abati <29585319+EdAbati@users.noreply.github.com> Co-authored-by: Mishig Davaadorj Co-authored-by: Acciaro Gennaro Daniele * Bump release (#307) * Bump release (#308) * Bump release (#314) * Bump release (#320) * Bump release (#328) * Bump release (#333) * Bump release (#335) * Bump release (#343) * Bump release (#355) * Bump release (#358) * Bump release (#371) * Bump release (#381) * Bump release (#387) * Bump release (#404) * Bump release (#413) * Bump release (#426) * Bump release (#463) --------- Co-authored-by: DOOHAE JUNG Co-authored-by: m_khandaker Co-authored-by: Md. Al-Amin Khandaker Co-authored-by: ftarlaci <18291571+ftarlaci@users.noreply.github.com> Co-authored-by: Doohae Jung <80743307+Doohae@users.noreply.github.com> Co-authored-by: melaniedrevet Co-authored-by: Jose M Munoz Co-authored-by: svv73 <88366711+svv73@users.noreply.github.com> Co-authored-by: Vedant Pandya Co-authored-by: Bahram Shamshiri Co-authored-by: Giyaseddin Bayrak <34009210+giyaseddin@users.noreply.github.com> Co-authored-by: Pavel <60391448+pdumin@users.noreply.github.com> Co-authored-by: 1375626371 <40328311+1375626371@users.noreply.github.com> Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> Co-authored-by: João Gustavo A. Amorim Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Cherdsak Kingkan Co-authored-by: Thomas Chaigneau <50595514+ChainYo@users.noreply.github.com> Co-authored-by: ChainYo Co-authored-by: hiromu <45531573+hiromu166@users.noreply.github.com> Co-authored-by: Cherdsak Kingkan Co-authored-by: Marcus Fraaß Co-authored-by: Jesper Dramsch Co-authored-by: amyeroberts Co-authored-by: Ash <103081562+ashwathim@users.noreply.github.com> Co-authored-by: Hamed Homaei Rad Co-authored-by: Dawood Khan Co-authored-by: regisss <15324346+regisss@users.noreply.github.com> Co-authored-by: Avishek Das Co-authored-by: Suteera Seeha <33692408+meanna@users.noreply.github.com> Co-authored-by: Suteera Co-authored-by: Saeed Choobani Co-authored-by: Fermin Ordaz Co-authored-by: Kerem Turgutlu Co-authored-by: Sebastian Sosa <37946988+CakeCrusher@users.noreply.github.com> Co-authored-by: tanersekmen <56790802+tanersekmen@users.noreply.github.com> Co-authored-by: Victor Costa <54755870+victorescosta@users.noreply.github.com> Co-authored-by: Camille Couturier Co-authored-by: Kavya <36916536+robotjellyzone@users.noreply.github.com> Co-authored-by: Batuhan Ayhan Co-authored-by: Kambiz Ghoorchian Co-authored-by: Diego Vargas <91356068+dzarkV@users.noreply.github.com> Co-authored-by: Thomas O'Brien Co-authored-by: Lincoln V Schreiber Co-authored-by: Giorgio Severi Co-authored-by: Ömer Faruk Özdemir Co-authored-by: Caterina Bonan <97481648+CaterinaBi@users.noreply.github.com> Co-authored-by: Hiromu Hota Co-authored-by: trtd56 <5toda6@gmail.com> Co-authored-by: Mehrdad Nezamdoost Co-authored-by: Wolvz Co-authored-by: a-krirk <56425947+a-krirk@users.noreply.github.com> Co-authored-by: atgctg <105969161+atgctg@users.noreply.github.com> Co-authored-by: Thiago Medeiros Co-authored-by: webbigdata-jp <87654083+webbigdata-jp@users.noreply.github.com> Co-authored-by: Leandro von Werra Co-authored-by: Bhadresh Savani Co-authored-by: 1375626371 <1375626371@qq.com> Co-authored-by: Andreas Ehrencrona Co-authored-by: leandro Co-authored-by: Matt Co-authored-by: Nolanogenn <52080100+Nolanogenn@users.noreply.github.com> Co-authored-by: Hồng Hạnh Co-authored-by: Younes Belkada <49240599+younesbelkada@users.noreply.github.com> Co-authored-by: Edoardo Abati <29585319+EdAbati@users.noreply.github.com> Co-authored-by: Mishig Davaadorj Co-authored-by: Acciaro Gennaro Daniele * Revert "Bump release (#566)" (#567) This reverts commit cccc2c91ac8e702e5e14bbb0419dbf0490c7aaaf. * updated documentation links * [doc build] Use secrets (#581) * docs: fix broken links * changed 'perspires' to 'persists' in chapter 1 quiz solves issue #585 * Update 4.mdx You forgot to write a return for this function. * Update 4.mdx : Fix Typo Should be "course" * fix link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Fix syntax in vi/chapter7/7.mdx There was an unnecessary `` * Remove `get_lr()` from logs which refers to nonexistent function `get_lr()` is called as part of this function, but the function is not declared anywhere in the script. This change removes this portion of the code since it is non-necessary. * Update 4.mdx removed judgmental argument * Update en-version * fix: remove useless token * fix: remove useless token (#635) * Translate Chapter 3 to Spanish (#510) * translate Chapter 3 to Spanish * translate code comments to Spanish and fix typos * Translating Chapter 6 to Spanish (#523) * Translating sections 1 and 2 to spanish * Translating sections 3 to spanish * Translating sections 3b to spanish * Translating sections 4 to spanish * Translating section 5 to spanish * Translating section 6 to spanish * Translating section 7 to spanish * Translating section 8 to spanish * Translating section 9 to spanish * Translating section 10 to spanish * Adding Sections to _toctree.yml * Fixing Typos after second review --------- Co-authored-by: datacubeR * Update 5.mdx Ajuste na tradução de "encoders". São "codificadores", não "decodificadores". Decoders são "decodificadores". * Update doc CI (#643) * Фиксация текущих результатов. * Фиксирую текущее состояние. * Fixing the transfer results for today. * Translated files 3b and partially 4. Fixing the result. * Fixing today's translation. * fix typos in Spanish translation (#511) * Fixing today's translation. Files: 6.mdx, 7.mdx and half of 8.mdx. * The translation of chapter 6 has been completed. * Delete chapters/en/.ipynb_checkpoints/_toctree-checkpoint.yml This is backup created by JupyterLab. * Delete chapters/en/chapter5/.ipynb_checkpoints/8-checkpoint.mdx This is backup created by JupyterLab. * Delete chapters/en/chapter6/.ipynb_checkpoints/1-checkpoint.mdx This is backup created by JupyterLab. * Delete chapters/en/chapter6/.ipynb_checkpoints/2-checkpoint.mdx This is backup created by JupyterLab. * Delete chapters/en/chapter6/.ipynb_checkpoints/8-checkpoint.mdx This is backup created by JupyterLab. * Delete chapters/en/chapter6/.ipynb_checkpoints/9-checkpoint.mdx This is backup created by JupyterLab. * Delete chapters/ru/.ipynb_checkpoints/TRANSLATING-checkpoint.txt This is backup created by JupyterLab. * Delete chapters/ru/.ipynb_checkpoints/_toctree-checkpoint.yml This is backup created by JupyterLab. * Delete chapters/ru/chapter5/.ipynb_checkpoints/8-checkpoint.mdx This is backup created by JupyterLab. * Update 10.mdx Minor fix. * Update 10.mdx Trying to solve the markup problem. * Update 10.mdx Correcting the syntax of some markup again) * Update chapters/ru/chapter6/4.mdx Yes, that space is redundant here. You're right about that. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter6/4.mdx Extra space. I overlooked it. My mistake. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter6/3.mdx There's an extra space here. You're right. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter6/3.mdx There's an extra space here. You're right. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter6/3b.mdx Yeah, there's no need for a space here. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter6/3.mdx Co-authored-by: Maria Khalusova * Update 3.mdx * Update 7.mdx Translated the comments noted on the review. * Update 3.mdx Translated the missing comments in the code. * Update chapters/ru/chapter6/3b.mdx Yes, an extra space. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter6/5.mdx Minor fix. Co-authored-by: Maria Khalusova --------- Co-authored-by: Yuan Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: IL-GU KIM <53106649+dlfrnaos19@users.noreply.github.com> Co-authored-by: Kim Bo Geum <53206051+nsbg@users.noreply.github.com> Co-authored-by: Bartosz Szmelczynski <43574448+Bearnardd@users.noreply.github.com> Co-authored-by: Shawn Lee Co-authored-by: Naveen Reddy D Co-authored-by: rainmaker Co-authored-by: “Ryan” <“sungjin.712@navercorp.com”> Co-authored-by: Wonhyeong Seo Co-authored-by: Meta Learner응용개발팀 류민호 Co-authored-by: Minho Ryu Co-authored-by: richardachen <85973297+richardachen@users.noreply.github.com> Co-authored-by: Luke Cheng <2258420+chenglu@users.noreply.github.com> Co-authored-by: beyondguo <37113676+beyondguo@users.noreply.github.com> Co-authored-by: bsenst Co-authored-by: 1375626371 <1375626371@qq.com> Co-authored-by: yaoqih <40328311+yaoqih@users.noreply.github.com> Co-authored-by: 李洋 <45715979+innovation64@users.noreply.github.com> Co-authored-by: PowerChina Co-authored-by: chenglu99 Co-authored-by: iCell Co-authored-by: Tiezhen WANG <38108242+xianbaoqian@users.noreply.github.com> Co-authored-by: Qi Zhang Co-authored-by: researcher <1131419673@qq.com> Co-authored-by: simpleAI Co-authored-by: FYJNEVERFOLLOWS Co-authored-by: zhangchaosd Co-authored-by: TK Buristrakul Co-authored-by: Acciaro Gennaro Daniele Co-authored-by: Carlos Aguayo Co-authored-by: ateliershen Co-authored-by: Pavel Nesterov Co-authored-by: Artyom Boyko Co-authored-by: Kirill Milintsevich Co-authored-by: jybarnes21 Co-authored-by: gxy-gxy <1115404657@qq.com> Co-authored-by: iLeGend <824040212@qq.com> Co-authored-by: sj Co-authored-by: Sureshkumar Thangavel <108256+tsureshkumar@users.noreply.github.com> Co-authored-by: Andrei Shirobokov Co-authored-by: Pranav <66965591+Pranav-Bobde@users.noreply.github.com> Co-authored-by: Maria Khalusova Co-authored-by: DOOHAE JUNG Co-authored-by: m_khandaker Co-authored-by: Md. Al-Amin Khandaker Co-authored-by: ftarlaci <18291571+ftarlaci@users.noreply.github.com> Co-authored-by: Doohae Jung <80743307+Doohae@users.noreply.github.com> Co-authored-by: melaniedrevet Co-authored-by: Jose M Munoz Co-authored-by: svv73 <88366711+svv73@users.noreply.github.com> Co-authored-by: Vedant Pandya Co-authored-by: Bahram Shamshiri Co-authored-by: Giyaseddin Bayrak <34009210+giyaseddin@users.noreply.github.com> Co-authored-by: Pavel <60391448+pdumin@users.noreply.github.com> Co-authored-by: 1375626371 <40328311+1375626371@users.noreply.github.com> Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> Co-authored-by: João Gustavo A. Amorim Co-authored-by: Cherdsak Kingkan Co-authored-by: Thomas Chaigneau <50595514+ChainYo@users.noreply.github.com> Co-authored-by: ChainYo Co-authored-by: hiromu <45531573+hiromu166@users.noreply.github.com> Co-authored-by: Cherdsak Kingkan Co-authored-by: Marcus Fraaß Co-authored-by: Jesper Dramsch Co-authored-by: amyeroberts Co-authored-by: Ash <103081562+ashwathim@users.noreply.github.com> Co-authored-by: Hamed Homaei Rad Co-authored-by: Dawood Khan Co-authored-by: regisss <15324346+regisss@users.noreply.github.com> Co-authored-by: Avishek Das Co-authored-by: Suteera Seeha <33692408+meanna@users.noreply.github.com> Co-authored-by: Suteera Co-authored-by: Saeed Choobani Co-authored-by: Fermin Ordaz Co-authored-by: Kerem Turgutlu Co-authored-by: Sebastian Sosa <37946988+CakeCrusher@users.noreply.github.com> Co-authored-by: tanersekmen <56790802+tanersekmen@users.noreply.github.com> Co-authored-by: Victor Costa <54755870+victorescosta@users.noreply.github.com> Co-authored-by: Camille Couturier Co-authored-by: Kavya <36916536+robotjellyzone@users.noreply.github.com> Co-authored-by: Batuhan Ayhan Co-authored-by: Kambiz Ghoorchian Co-authored-by: Diego Vargas <91356068+dzarkV@users.noreply.github.com> Co-authored-by: Thomas O'Brien Co-authored-by: Lincoln V Schreiber Co-authored-by: Giorgio Severi Co-authored-by: Ömer Faruk Özdemir Co-authored-by: Caterina Bonan <97481648+CaterinaBi@users.noreply.github.com> Co-authored-by: Hiromu Hota Co-authored-by: trtd56 <5toda6@gmail.com> Co-authored-by: Mehrdad Nezamdoost Co-authored-by: Wolvz Co-authored-by: a-krirk <56425947+a-krirk@users.noreply.github.com> Co-authored-by: atgctg <105969161+atgctg@users.noreply.github.com> Co-authored-by: Thiago Medeiros Co-authored-by: webbigdata-jp <87654083+webbigdata-jp@users.noreply.github.com> Co-authored-by: Leandro von Werra Co-authored-by: Bhadresh Savani Co-authored-by: Andreas Ehrencrona Co-authored-by: leandro Co-authored-by: Matt Co-authored-by: Nolanogenn <52080100+Nolanogenn@users.noreply.github.com> Co-authored-by: Hồng Hạnh Co-authored-by: Younes Belkada <49240599+younesbelkada@users.noreply.github.com> Co-authored-by: Edoardo Abati <29585319+EdAbati@users.noreply.github.com> Co-authored-by: Mishig Davaadorj Co-authored-by: nnoboa Co-authored-by: Vipula Sandaruwan Dissanayake Co-authored-by: Alex Bzdel Co-authored-by: JieShen <49408146+JieShenAI@users.noreply.github.com> Co-authored-by: Hardik Bhadani Co-authored-by: Omar Sanseviero Co-authored-by: Suket Kamboj <82956207+Sookeyy-12@users.noreply.github.com> Co-authored-by: Brad Windsor Co-authored-by: Pierre Alexandre SCHEMBRI Co-authored-by: Remy Co-authored-by: María Grandury <57645283+mariagrandury@users.noreply.github.com> Co-authored-by: Alfonso Tobar-Arancibia <48638337+datacubeR@users.noreply.github.com> Co-authored-by: datacubeR Co-authored-by: Alysson <50303964+k3ybladewielder@users.noreply.github.com> Co-authored-by: Merve Noyan --- .github/workflows/build_documentation.yml | 2 +- .github/workflows/build_pr_documentation.yml | 1 - .github/workflows/delete_doc_comment.yml | 13 - .github/workflows/upload_pr_documentation.yml | 17 + chapters/de/chapter3/2.mdx | 2 +- chapters/en/chapter1/10.mdx | 2 +- chapters/en/chapter1/4.mdx | 4 +- chapters/en/chapter1/5.mdx | 10 +- chapters/en/chapter2/5.mdx | 2 +- chapters/en/chapter3/2.mdx | 2 +- chapters/en/chapter3/6.mdx | 2 +- chapters/en/chapter6/5.mdx | 2 +- chapters/en/chapter6/7.mdx | 2 +- chapters/en/chapter7/3.mdx | 4 +- chapters/en/chapter7/4.mdx | 2 +- chapters/en/chapter7/6.mdx | 3 +- chapters/en/events/3.mdx | 2 +- chapters/es/_toctree.yml | 39 +- chapters/es/chapter0/1.mdx | 8 +- chapters/es/chapter1/10.mdx | 8 +- chapters/es/chapter1/3.mdx | 4 +- chapters/es/chapter1/4.mdx | 16 +- chapters/es/chapter1/5.mdx | 2 +- chapters/es/chapter1/6.mdx | 2 +- chapters/es/chapter1/7.mdx | 4 +- chapters/es/chapter1/8.mdx | 2 +- chapters/es/chapter2/4.mdx | 27 +- chapters/es/chapter2/6.mdx | 32 +- chapters/es/chapter2/7.mdx | 2 +- chapters/es/chapter2/8.mdx | 12 +- chapters/es/chapter3/1.mdx | 20 +- chapters/es/chapter3/2.mdx | 54 +- chapters/es/chapter3/3.mdx | 180 +++++ chapters/es/chapter3/3_tf.mdx | 203 ++++++ chapters/es/chapter3/4.mdx | 31 +- chapters/es/chapter3/5.mdx | 24 + chapters/es/chapter3/6.mdx | 333 +++++++++ chapters/es/chapter5/1.mdx | 2 +- chapters/es/chapter5/2.mdx | 2 +- chapters/es/chapter5/3.mdx | 16 +- chapters/es/chapter5/4.mdx | 15 +- chapters/es/chapter5/5.mdx | 10 +- chapters/es/chapter5/6.mdx | 4 +- chapters/es/chapter5/8.mdx | 18 +- chapters/es/chapter6/1.mdx | 20 + chapters/es/chapter6/10.mdx | 283 ++++++++ chapters/es/chapter6/2.mdx | 250 +++++++ chapters/es/chapter6/3.mdx | 478 +++++++++++++ chapters/es/chapter6/3b.mdx | 643 ++++++++++++++++++ chapters/es/chapter6/4.mdx | 123 ++++ chapters/es/chapter6/5.mdx | 360 ++++++++++ chapters/es/chapter6/6.mdx | 373 ++++++++++ chapters/es/chapter6/7.mdx | 382 +++++++++++ chapters/es/chapter6/8.mdx | 566 +++++++++++++++ chapters/es/chapter6/9.mdx | 16 + chapters/es/glossary/1.mdx | 12 +- chapters/fa/_toctree.yml | 17 +- chapters/fa/chapter3/2.mdx | 4 +- chapters/fa/chapter3/3.mdx | 221 ++++++ chapters/fa/chapter3/3_tf.mdx | 240 +++++++ chapters/fr/chapter3/2.mdx | 2 +- chapters/fr/chapter7/4.mdx | 2 +- chapters/hi/chapter3/2.mdx | 2 +- chapters/it/chapter3/2.mdx | 2 +- chapters/pt/chapter1/5.mdx | 4 +- chapters/ru/_toctree.yml | 31 +- chapters/ru/chapter3/2.mdx | 2 +- chapters/ru/chapter6/1.mdx | 16 +- chapters/ru/chapter6/10.mdx | 283 ++++++++ chapters/ru/chapter6/2.mdx | 82 +-- chapters/ru/chapter6/3.mdx | 474 +++++++++++++ chapters/ru/chapter6/3b.mdx | 642 +++++++++++++++++ chapters/ru/chapter6/4.mdx | 123 ++++ chapters/ru/chapter6/5.mdx | 360 ++++++++++ chapters/ru/chapter6/6.mdx | 374 ++++++++++ chapters/ru/chapter6/7.mdx | 381 +++++++++++ chapters/ru/chapter6/8.mdx | 563 +++++++++++++++ chapters/ru/chapter6/9.mdx | 16 + chapters/th/chapter3/2.mdx | 2 +- chapters/vi/chapter3/2.mdx | 2 +- chapters/vi/chapter7/7.mdx | 2 - chapters/zh-CN/chapter3/2.mdx | 2 +- chapters/zh-CN/chapter7/4.mdx | 1 + chapters/zh-TW/chapter3/2.mdx | 2 +- utils/generate_subtitles.py | 1 - 85 files changed, 8245 insertions(+), 256 deletions(-) delete mode 100644 .github/workflows/delete_doc_comment.yml create mode 100644 .github/workflows/upload_pr_documentation.yml create mode 100644 chapters/es/chapter3/3.mdx create mode 100644 chapters/es/chapter3/3_tf.mdx create mode 100644 chapters/es/chapter3/5.mdx create mode 100644 chapters/es/chapter3/6.mdx create mode 100644 chapters/es/chapter6/1.mdx create mode 100644 chapters/es/chapter6/10.mdx create mode 100644 chapters/es/chapter6/2.mdx create mode 100644 chapters/es/chapter6/3.mdx create mode 100644 chapters/es/chapter6/3b.mdx create mode 100644 chapters/es/chapter6/4.mdx create mode 100644 chapters/es/chapter6/5.mdx create mode 100644 chapters/es/chapter6/6.mdx create mode 100644 chapters/es/chapter6/7.mdx create mode 100644 chapters/es/chapter6/8.mdx create mode 100644 chapters/es/chapter6/9.mdx create mode 100644 chapters/fa/chapter3/3.mdx create mode 100644 chapters/fa/chapter3/3_tf.mdx create mode 100644 chapters/ru/chapter6/10.mdx create mode 100644 chapters/ru/chapter6/3.mdx create mode 100644 chapters/ru/chapter6/3b.mdx create mode 100644 chapters/ru/chapter6/4.mdx create mode 100644 chapters/ru/chapter6/5.mdx create mode 100644 chapters/ru/chapter6/6.mdx create mode 100644 chapters/ru/chapter6/7.mdx create mode 100644 chapters/ru/chapter6/8.mdx create mode 100644 chapters/ru/chapter6/9.mdx 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/chapters/de/chapter3/2.mdx b/chapters/de/chapter3/2.mdx index 49a025c1a..50a183761 100644 --- a/chapters/de/chapter3/2.mdx +++ b/chapters/de/chapter3/2.mdx @@ -84,7 +84,7 @@ 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: 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/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/chapter2/5.mdx b/chapters/en/chapter2/5.mdx index 81d496fef..199877efb 100644 --- a/chapters/en/chapter2/5.mdx +++ b/chapters/en/chapter2/5.mdx @@ -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..f64747dac 100644 --- a/chapters/en/chapter3/2.mdx +++ b/chapters/en/chapter3/2.mdx @@ -84,7 +84,7 @@ 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: 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/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/chapter7/3.mdx b/chapters/en/chapter7/3.mdx index dc12fb8bc..de3da9a1f 100644 --- a/chapters/en/chapter7/3.mdx +++ b/chapters/en/chapter7/3.mdx @@ -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..6c6418c75 100644 --- a/chapters/en/chapter7/6.mdx +++ b/chapters/en/chapter7/6.mdx @@ -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/events/3.mdx b/chapters/en/events/3.mdx index d74b9c206..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..47b77485a 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,10 +82,10 @@ 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 +# Desactiva el entorno virtual source .env/bin/deactivate ``` 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..7aed993ea 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,15 @@ 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 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: +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: ```py from datasets import load_dataset @@ -147,13 +150,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 +183,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 +194,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 +219,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,7 +238,7 @@ 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: @@ -244,9 +247,9 @@ 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 +258,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 +277,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 +296,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 +353,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 +385,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..a0de6264a 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. diff --git a/chapters/es/chapter5/3.mdx b/chapters/es/chapter5/3.mdx index 241916c9d..f1cc5ab69 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. @@ -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.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. 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..38919187d 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. @@ -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..e901c3717 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. @@ -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..1e635315b 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. @@ -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..795be36aa --- /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/python/latest/api/reference.html#module-tokenizers.normalizers)). +- `pre_tokenizers` contiene todos los posibles tipos de `PreTokenizer` que puedes usar (la lista completa [aquí](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.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/python/latest/api/reference.html#module-tokenizers.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/python/latest/api/reference.html#module-tokenizers.trainers)). +- `post_processors` contiene varios tipos de `PostProcessor` que puedes usar (la lista completa [aquí](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.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/python/latest/components.html#decoders)). + +Puedes encontrar la lista completas de las unidades más básicas [aquí](https://huggingface.co/docs/tokenizers/python/latest/components.html). + +## 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/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index d553572ad..07d933cc6 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -99,7 +99,7 @@ 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 را به روش زیر دانلود کنیم: @@ -498,4 +498,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/chapter3/2.mdx b/chapters/fr/chapter3/2.mdx index 6e36b38df..70b529f7b 100644 --- a/chapters/fr/chapter3/2.mdx +++ b/chapters/fr/chapter3/2.mdx @@ -92,7 +92,7 @@ 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 : 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/hi/chapter3/2.mdx b/chapters/hi/chapter3/2.mdx index eb45b962d..481fc824f 100644 --- a/chapters/hi/chapter3/2.mdx +++ b/chapters/hi/chapter3/2.mdx @@ -84,7 +84,7 @@ 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 डेटासेट को इस तरह डाउनलोड कर सकते हैं: diff --git a/chapters/it/chapter3/2.mdx b/chapters/it/chapter3/2.mdx index 9de949110..5ebea52a6 100644 --- a/chapters/it/chapter3/2.mdx +++ b/chapters/it/chapter3/2.mdx @@ -84,7 +84,7 @@ 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ì: 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/ru/_toctree.yml b/chapters/ru/_toctree.yml index 8bc60860c..597730c46 100644 --- a/chapters/ru/_toctree.yml +++ b/chapters/ru/_toctree.yml @@ -53,6 +53,7 @@ - local: chapter3/6 title: Итоговый тест по главе quiz: 3 + - title: 4. Hugging Face Hub sections: - local: chapter4/1 @@ -68,6 +69,7 @@ - local: chapter4/6 title: Итоговый тест по главе quiz: 4 + - title: 5. Библиотека 🤗 Datasets sections: - local: chapter5/1 @@ -83,14 +85,35 @@ - local: chapter5/7 title: 🤗 Datasets, итоги! - local: chapter5/8 - title: Тест по главе 5 + title: Тест по главе 5 + - 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: Глоссарий sections: - local: glossary/1 - title: Глоссарий \ No newline at end of file + title: Глоссарий diff --git a/chapters/ru/chapter3/2.mdx b/chapters/ru/chapter3/2.mdx index 250e553b1..dc8c83f24 100644 --- a/chapters/ru/chapter3/2.mdx +++ b/chapters/ru/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)). Но сейчас вернемся к датасету 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. Мы можем загрузить датасет следующим образом: diff --git a/chapters/ru/chapter6/1.mdx b/chapters/ru/chapter6/1.mdx index 50a260ae0..93525db9d 100644 --- a/chapters/ru/chapter6/1.mdx +++ b/chapters/ru/chapter6/1.mdx @@ -1,19 +1,19 @@ -# Введение +# Введение[[introduction]] -В [главе 3](/course/ru/chapter3), мы рассмотрели, как настроить модель под конкретную задачу. Когда мы это делаем, мы используем тот же токенизатор, с помощью которого была предварительно обучена модель, но что нам делать, когда мы хотим обучить модель с нуля? В этих случаях использование токенизатора, предварительно обученного на корпусе из другого домена или языка, обычно неоптимально. Например, токенизатор, обученный на английском корпусе, будет плохо работать с корпусом японских текстов, поскольку использование пробелов и пунктуации в этих двух языках сильно различается. +В [Главе 3](/course/chapter3) мы рассмотрели, как дообучить модель для конкретной задачи. При этом мы используем тот же токенизатор, на котором была предварительно обучена модель, но что делать, когда мы хотим обучить модель с нуля? В таких случаях использование токенизатора, который был предварительно обучен на корпусе из другой области или языка, как правило, является неоптимальным. Например, токенизатор, обученный на корпусе английских текстов, будет плохо работать на корпусе японских текстов, поскольку использование пробелов и знаков препинания в этих двух языках сильно отличается. -В этой главе вы узнаете, как обучить совершенно новый токенизатор на корпусе текстов, чтобы затем его можно было использовать для предобучения языковой модели. Все это будет сделано с помощью библиотеки [🤗 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](/course/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..4dfd465bb 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](/course/chapter2), мы увидели, что большинство моделей трансформеров используют _алгоритм токенизации по подсловам_. Чтобы определить, какие подслова представляют интерес и наиболее часто встречаются в корпусе, токенизатор должен внимательно изучить все тексты в корпусе - этот процесс мы называем *обучением*. Точные правила обучения зависят от типа используемого токенизатора, далее в этой главе мы рассмотрим три основных алгоритма. -⚠️ Обучение токенизатора — это не то же самое, что обучение модели! Обучение модели использует стохастический градиентный спуск, чтобы уменьшить значение функции потерь для каждого батча данных. Он рандомизирован по своей природе (это означает, что вы должны зафиксировать несколько начальных значений, чтобы получить одинаковые результаты при выполнении одной и той же тренировки дважды). Обучение токенизатора — это статистический процесс, который пытается определить, какие подслова лучше всего выбирать для данного корпуса, а точные правила, используемые для их выбора, зависят от алгоритма токенизации. Он детерминирован, то есть вы всегда получаете одни и те же результаты при обучении с одним и тем же алгоритмом на одном и том же корпусе. +⚠️ Обучение токенизатора - это не то же самое, что обучение модели! При обучении модели используется стохастический градиентный спуск, чтобы сделать потери немного меньше для каждого батча. Оно рандомизировано по своей природе (это означает, что вам нужно задать некоторое число 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](/course/chapter3), токенизации батча входных данных. -Большинство моделей 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](/course/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..25aa85b22 --- /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] (/course/chapter1). + + + +В дальнейшем обсуждении мы будем часто проводить различие между "медленными" и "быстрыми" токенизаторами. Медленные токенизаторы - это те, что написаны на Python в библиотеке 🤗 Transformers, а быстрые версии - это те, что предоставляются в 🤗 Tokenizers, которые написаны на Rust. Если вы помните таблицу из [Главы 5](/course/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](/course/chapter1) мы впервые попробовали применить NER - когда задача состоит в том, чтобы определить, какие части текста соответствуют сущностям, таким как люди, места или организации - с помощью функции 🤗 Transformers `pipeline()`. Затем, в [Главе 2](/course/chapter2), мы увидели, как конвейер объединяет три этапа, необходимые для получения прогнозов из необработанного текста: токенизацию, прохождение входных данных через модель и постобработку. Первые два шага в конвейере `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](/course/chapter2); мы инстанцируем токенизатор и модель с помощью классов `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](/course/chapter2); мы инстанцируем токенизатор и модель с помощью классов `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..7902ec2ff --- /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](/course/chapter1), для получения ответа на вопрос мы можем использовать конвейер `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](/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} + +Обратите внимание, что мы токенизируем вопрос и контекст как пару, причем вопрос стоит первым. + +
+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..e3c04d37a --- /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](/course/chapter2), токенизатор на основе слов (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](/course/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..191e1d2f4 --- /dev/null +++ b/chapters/ru/chapter6/8.mdx @@ -0,0 +1,563 @@ +# Создание токенизатора, блок за блоком[[building-a-tokenizer-block-by-block]] + + + +Как мы уже видели в предыдущих разделах, токенизация состоит из нескольких этапов: + +- Нормализация (любая необходимая очистка текста, например, удаление пробелов или подчеркиваний, нормализация Unicode и т. д.) +- Предварительная токенизация (разделение входного текста на слова). +- Прогон входных данных через модель (использование предварительно токенизированных слов для создания последовательности токенов) +- Постобработка (добавление специальных токенов токенизатора, генерация маски внимания и идентификаторов типов токенов) + +В качестве напоминания вот еще один взгляд на общий процесс: + +
+The tokenization pipeline. + +
+ +Библиотека 🤗 Tokenizers была создана для того, чтобы предоставить несколько вариантов каждого из этих шагов, которые вы можете смешивать и сочетать между собой. В этом разделе мы рассмотрим, как можно создать токенизатор с нуля, а не обучать новый токенизатор на основе старого, как мы делали в [разделе 2](/course/chapter6/2). После этого вы сможете создать любой токенизатор, который только сможете придумать! + + + +Точнее, библиотека построена вокруг центрального класса `Tokenizer`, а строительные блоки сгруппированы в подмодули: + +- `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`, которые вы можете использовать для обучения модели на корпусе (по одному на каждый тип модели; полный список [здесь](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). + +## Получение корпуса текста[[acquiring-a-corpus]] + +Для обучения нашего нового токенизатора мы будем использовать небольшой корпус текстов (чтобы примеры выполнялись быстро). Шаги по сбору корпуса аналогичны тем, что мы делали в [начале этой главы](/course/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/th/chapter3/2.mdx b/chapters/th/chapter3/2.mdx index 79444c44c..73f8ec8cf 100644 --- a/chapters/th/chapter3/2.mdx +++ b/chapters/th/chapter3/2.mdx @@ -84,7 +84,7 @@ 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 ได้ดังนี้: diff --git a/chapters/vi/chapter3/2.mdx b/chapters/vi/chapter3/2.mdx index af766f084..e04c61e6d 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: 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/chapter3/2.mdx b/chapters/zh-CN/chapter3/2.mdx index 94fd5d1eb..57e649e65 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数据集: diff --git a/chapters/zh-CN/chapter7/4.mdx b/chapters/zh-CN/chapter7/4.mdx index a88dc2777..bb15ba4c7 100644 --- a/chapters/zh-CN/chapter7/4.mdx +++ b/chapters/zh-CN/chapter7/4.mdx @@ -225,6 +225,7 @@ def preprocess_function(examples): model_inputs = tokenizer( inputs, text_target=targets, max_length=max_length, truncation=True ) + return model_inputs ``` 请注意,我们为输入和输出设置了相同的最大长度。由于我们处理的文本看起来很短,我们使用 128。 diff --git a/chapters/zh-TW/chapter3/2.mdx b/chapters/zh-TW/chapter3/2.mdx index 1b9045aab..4970fb9eb 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數據集: diff --git a/utils/generate_subtitles.py b/utils/generate_subtitles.py index 7de26249e..68592830f 100644 --- a/utils/generate_subtitles.py +++ b/utils/generate_subtitles.py @@ -75,7 +75,6 @@ def generate_subtitles(language: str, youtube_language_code: str = None, is_task - if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--language", type=str, help="Language to generate subtitles for") From b7b8471bf1b92e3e0db20cd3627551005dd436eb Mon Sep 17 00:00:00 2001 From: lewtun Date: Mon, 29 Jan 2024 18:14:59 +0100 Subject: [PATCH 48/51] Bump release (#668) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * translate no.24 * review 06 cn translations * review 07 cn translations * Update 23_what-is-dynamic-padding.srt * Update 23_what-is-dynamic-padding.srt * Update 23_what-is-dynamic-padding.srt * Update subtitles/zh-CN/23_what-is-dynamic-padding.srt Co-authored-by: Luke Cheng <2258420+chenglu@users.noreply.github.com> * Update subtitles/zh-CN/23_what-is-dynamic-padding.srt Co-authored-by: Luke Cheng <2258420+chenglu@users.noreply.github.com> * add blank * Review No. 11, No. 12 * Review No. 13 * Review No. 12 * Review No. 14 * finished review * optimized translation * optimized translation * docs(zh-cn): Reviewed No. 29 - Write your training loop in PyTorch * Review 15 * Review 16 * Review 17 * Review 18 * Review ch 72 translation * Update 72 cn translation * To be reviewed No.42-No.54 * No.11 check-out * No.12 check-out * No. 13 14 check-out * No. 15 16 check-out * No. 17 18 check-out * Add note for "token-*" * Reviewed No.8, 9, 10 * Reviewed No.42 * Review No.43 * finished review * optimized translation * finished review * optimized translation * Review 44(need refine) * Review 45(need refine) * Review No. 46 (need refine) * Review No.47 * Review No.46 * Review No.45 * Review No.44 * Review No.48 * Review No.49 * Review No.50 * Modify Ko chapter2 8.mdx (#465) * Add Ko chapter2 2.mdx * Add Ko chapter2 2.mdx * Add Ko chapter2 3.mdx & 4.mdx * Modify Ko chapter2 3.mdx & 4.mdx * Modify Ko chapter2 3.mdx & 4.mdx * Modify Ko chapter2 3.mdx & 4.mdx * Modify _toctree.yml * Add Ko chapter2 5.mdx * Modify Ko chapter2 4.mdx * Add doc-builder step * Add Ko chapter2 6~8.mdx & Modify Ko chapter2 2.mdx typo * Modify Ko _toctree.yml * Modify Ko chapter2 8.mdx & README.md * Fixed typo (#471) * fixed subtitle errors (#474) timestamp: 00:00:26,640 --> 00:00:28,620 modification: notification --> authentication timestamp: 00:04:21,113 --> 00:04:22,923 modification: of --> or * Fixed a typo (#475) * Update 3.mdx (#526) Fix typo * [zh-TW] Added chapters 1-9 (#477) The translation is based on Simplified Chinese version, converted via OpenCC and fixed some formatting issues. * finished review * Explain why there are more tokens, than reviews (#476) * Explain why there are more tokens, than reviews * Update chapters/en/chapter5/3.mdx --------- Co-authored-by: lewtun * [RU] Subtitles for Chapter 1 of the video course (#489) * Created a directory for the russian subtitles. Created a folder for Russian subtitles for the video course and published a translation of the introductory video from chapter 1. * Uploaded subtitles for chapter 1 Uploaded subtitles for the remaining videos for chapter 1 of the video course. * Added subtitles for chapter 2 of the video course Added STR subtitle files for the second chapter of the YouTube video course. * Delete subtitles/ru directory Removed the old translation. Incorrect timestamping. * Create 00_welcome-to-the-hugging-face-course.srt Create a directory and upload a subtitle file for the introductory video of the course. * Add files via upload Upload subtitle files for the first chapter of the course. * Review No.52 * [ru] Added the glossary and translation guide (#490) * Added the glossary and translation guide * Fixed casing * Minor fixes * Updated glossary * Glossary update * Glossary update * Glossary update * [ru] Chapters 0 and 1 proofreading, updating and translating missing sections (#491) * Chapter 0 proofreading * Chapter 1 Section 1 proofreading - Added new people from English version; - Added links to creator's pages; - Added FAQ translation; * Chapter 1 Sections 2-5 proofreading * Chapter 1 Sections 6-9 proofreading * Final proofreading and added missing quiz section * Minor spelling corrections * Review No.51 * Review No.53 * Review No.54 * finished review * modified translation * modified translation * modified subtitle use the same text appeared in video * translated * Fix typo (#532) * review chapter4/2 * review chapter4/2 * review chapter4/2 * Review 75 * Review No.20, need review some * docs(zh-cn): Reviewed Chapter 7/1 * Update 1.mdx * Review No.22 * Review No.21 (need refinement) * Review No.30, need review: 26 27 28 30 73 74 * Review 30 (good) * Review 20 * Review 21 (refine) * Review 21 * Review 22 * Review 26 * Review 27 * Review 28 * Review 30 * Review 73 * Review 74 * Fix typo * Review 26-28, 42-54, 73-75 * The GPT2 link is broken The link `/course/en/chapter7/section6` does not exist in the course. Corrected to `/course/en/chapter7/6`. * typo in `Now your turn!` section Duplicated `the` was removed * `chunk_size` should be instead of `block_size` `chunk_size` should be instead of `block_size` (`block_size` was never mentioned before) * refactor: rephrase text to improve clarity and specificity In context to "training with a dataset specific to your task" and "train directly for the final task", I was not able to infer easily that "directly" here implies training from scratch. * Demo link fixes (#562) * demo link fixes * minor demo fix * Bump release (#566) * Add note about `remove_unused_columns` for whole word masking * Merge pull request #24 from huggingface/fix-typo Fix typo * Merge pull request #26 from huggingface/fix-qa-offsets Fix inequalities in answer spans for QA chapter * Merge pull request #30 from huggingface/fix-modelcard-url Update model card URL * Merge pull request #69 from yulonglin/patch-1 Correct typo mixing up space and newline symbols * Bump release (#99) * Bump release * Update author list Co-authored-by: DOOHAE JUNG Co-authored-by: m_khandaker Co-authored-by: Md. Al-Amin Khandaker Co-authored-by: ftarlaci <18291571+ftarlaci@users.noreply.github.com> Co-authored-by: Doohae Jung <80743307+Doohae@users.noreply.github.com> Co-authored-by: melaniedrevet * Bump release (#115) * ko-chapter1/1 * ko _toctree.yml created * Fix the issue #80 * Single expression changed * ko/chapter1 finished * ko/chapter0 finished * ko/chapter0 finished * reviewed by @bzantium ko/chapter0 * reviewed by @bzantium chapter0 & fixed typo * reviewed by @rainmaker712 * maximize Korean expressions * [Chapter 1] bangla traslation initial commit * Update 1.mdx update and fix formating * Fix formating and typos * translate _toctree.yml 0-1 chapter * Add Korean to CI * [tr] Translated chapter1/2.mdx * remove translation from sec titles not yet translated * Add authors [th ru] * [FIX] _toctree.yml * Update chapters/bn/chapter0/1.mdx [FIX] syntax formatting Co-authored-by: lewtun * tag typos & indentation & unnatural expressions * modified toctree.yml for chapter1/2 * modified toctree.yml for chapter1/2 & fix typo * French Translation - Chapter 5 * Add Bengali to CI * Update author list * Adding translations for 2/4 and 2/5 🚀 (#74) * Adding translations for 2/4 and 2/5 🚀 * Remove English content Co-authored-by: lewtun * Translation to Russian (#97) * translation of chapter 2/section 1 * add section 1 / chapter 2 to _toctree.yml * Translation of Chapter0 to Hindi (#86) * Hindi?Chapter0-Part_1 * Hindi/Chapter0-Part_2 * Chapter 0 Persian Translation First Draft (#95) * merged branch0 into main. no toctree yet. * updated toctree. * Updated the glossary with terms from chapter0. * Second draft in collab w/ @schoobani. Added empty chapter1 for preview. * Glossary typo fix. * Translation of Chapter0 (setup) to Arabic (#104) * Add AR translation for `introduction` * Fix alignment & format * Add Arabic to CI build * Russian - Chapter 1 finished (#98) * 01/4 start * 1/4 finished * 1/5 finished * 1/5 update toc * 1/6 finished * 1/7 finished * 1/8 finished * 1/9 finished * 1/4 fix * toc update * Chinese - Chapter 1 finished (#113) * Chinese - Chapter 1 finished * Add zh to the languages field Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> * [PT] Translation of chapter 2 (#107) * add PT translate to 2.1 * add PT translate to 2.2 * add portuguese translation to 2.3 * WIP portuguese translation to 2.4 * add portuguese translation to 2.4 * add portuguese translation to 2.5 * add portuguese translation to 2.6 * add _toctree infos Co-authored-by: lewtun * [FR] Translation of chapter 2 & event + Review of chapters 0 & 5 (#106) * Update _toctree.yml Add chapter 2 + little fix of chapter 5 * Update 1.mdx Review of chapter 0 * Create 1.mdx * Create 2.mdx * Create 3.mdx * Create 4.mdx * Create 5.mdx * Create 6.mdx * Create 7.mdx * Create 8.mdx * Update 8.mdx Since AutoNLP has recently been renamed to AutoTrain, let me make the correction on the English file * Update 1.mdx Review of chapter 5/1 * Update 2.mdx Review of chapter 5/2 * Update 3.mdx Review of chapter 5/3 * Update 4.mdx Review of chapter 5/4 * Update 5.mdx Review of chapter 5/5 * Update 6.mdx Review of chapter 5/6 * Update 7.mdx Review of chapter 5/7 * Update 8.mdx Review of chapter 5/8 * Create 1.mdx event's translation * Update _toctree.yml add event to the tree * Update _toctree.yml deletion of the files that pose a problem to pass the checks, will be resubmitted in another PR * Delete 1.mdx deletion of the files that pose a problem to pass the checks, will be resubmitted in another PR * make style correction * Update _toctree.yml the - * Fix spacing Co-authored-by: Lewis Tunstall * [th] Translated Chapter2/1 (#83) * Finish chapter2/1 * Update _toctree.yml * ko-chapter1/1 * ko _toctree.yml created * Fix the issue #80 * Single expression changed * ko/chapter1 finished * ko/chapter0 finished * ko/chapter0 finished * reviewed by @bzantium ko/chapter0 * reviewed by @bzantium chapter0 & fixed typo * reviewed by @rainmaker712 * maximize Korean expressions * [Chapter 1] bangla traslation initial commit * Update 1.mdx update and fix formating * Fix formating and typos * translate _toctree.yml 0-1 chapter * Add Korean to CI * remove translation from sec titles not yet translated * Add authors [th ru] * [FIX] _toctree.yml * Update chapters/bn/chapter0/1.mdx [FIX] syntax formatting Co-authored-by: lewtun * tag typos & indentation & unnatural expressions * modified toctree.yml for chapter1/2 * modified toctree.yml for chapter1/2 & fix typo * Add Bengali to CI * Update author list * Adding translations for 2/4 and 2/5 🚀 (#74) * Adding translations for 2/4 and 2/5 🚀 * Remove English content Co-authored-by: lewtun * Translation to Russian (#97) * translation of chapter 2/section 1 * add section 1 / chapter 2 to _toctree.yml * Translation of Chapter0 to Hindi (#86) * Hindi?Chapter0-Part_1 * Hindi/Chapter0-Part_2 * Chapter 0 Persian Translation First Draft (#95) * merged branch0 into main. no toctree yet. * updated toctree. * Updated the glossary with terms from chapter0. * Second draft in collab w/ @schoobani. Added empty chapter1 for preview. * Glossary typo fix. * Translation of Chapter0 (setup) to Arabic (#104) * Add AR translation for `introduction` * Fix alignment & format * Add Arabic to CI build * Russian - Chapter 1 finished (#98) * 01/4 start * 1/4 finished * 1/5 finished * 1/5 update toc * 1/6 finished * 1/7 finished * 1/8 finished * 1/9 finished * 1/4 fix * toc update * Chinese - Chapter 1 finished (#113) * Chinese - Chapter 1 finished * Add zh to the languages field Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> * [PT] Translation of chapter 2 (#107) * add PT translate to 2.1 * add PT translate to 2.2 * add portuguese translation to 2.3 * WIP portuguese translation to 2.4 * add portuguese translation to 2.4 * add portuguese translation to 2.5 * add portuguese translation to 2.6 * add _toctree infos Co-authored-by: lewtun * [FR] Translation of chapter 2 & event + Review of chapters 0 & 5 (#106) * Update _toctree.yml Add chapter 2 + little fix of chapter 5 * Update 1.mdx Review of chapter 0 * Create 1.mdx * Create 2.mdx * Create 3.mdx * Create 4.mdx * Create 5.mdx * Create 6.mdx * Create 7.mdx * Create 8.mdx * Update 8.mdx Since AutoNLP has recently been renamed to AutoTrain, let me make the correction on the English file * Update 1.mdx Review of chapter 5/1 * Update 2.mdx Review of chapter 5/2 * Update 3.mdx Review of chapter 5/3 * Update 4.mdx Review of chapter 5/4 * Update 5.mdx Review of chapter 5/5 * Update 6.mdx Review of chapter 5/6 * Update 7.mdx Review of chapter 5/7 * Update 8.mdx Review of chapter 5/8 * Create 1.mdx event's translation * Update _toctree.yml add event to the tree * Update _toctree.yml deletion of the files that pose a problem to pass the checks, will be resubmitted in another PR * Delete 1.mdx deletion of the files that pose a problem to pass the checks, will be resubmitted in another PR * make style correction * Update _toctree.yml the - * Fix spacing Co-authored-by: Lewis Tunstall * [th] Translated Chapter2/1 (#83) * Finish chapter2/1 * Update _toctree.yml * Add Hindi to CI (#116) Co-authored-by: DOOHAE JUNG Co-authored-by: m_khandaker Co-authored-by: Md. Al-Amin Khandaker Co-authored-by: ftarlaci <18291571+ftarlaci@users.noreply.github.com> Co-authored-by: Doohae Jung <80743307+Doohae@users.noreply.github.com> Co-authored-by: melaniedrevet Co-authored-by: Jose M Munoz Co-authored-by: svv73 <88366711+svv73@users.noreply.github.com> Co-authored-by: Vedant Pandya Co-authored-by: Bahram Shamshiri Co-authored-by: Giyaseddin Bayrak <34009210+giyaseddin@users.noreply.github.com> Co-authored-by: Pavel <60391448+pdumin@users.noreply.github.com> Co-authored-by: 1375626371 <40328311+1375626371@users.noreply.github.com> Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> Co-authored-by: João Gustavo A. Amorim Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Cherdsak Kingkan * Bump release 4 (#133) * Bump release (#138) * ko-chapter1/1 * ko _toctree.yml created * Fix the issue #80 * Single expression changed * ko/chapter1 finished * ko/chapter0 finished * ko/chapter0 finished * reviewed by @bzantium ko/chapter0 * reviewed by @bzantium chapter0 & fixed typo * reviewed by @rainmaker712 * maximize Korean expressions * [Chapter 1] bangla traslation initial commit * Update 1.mdx update and fix formating * Fix formating and typos * translate _toctree.yml 0-1 chapter * Add Korean to CI * [tr] Translated chapter1/2.mdx * remove translation from sec titles not yet translated * Add authors [th ru] * [FIX] _toctree.yml * Update chapters/bn/chapter0/1.mdx [FIX] syntax formatting Co-authored-by: lewtun * tag typos & indentation & unnatural expressions * modified toctree.yml for chapter1/2 * modified toctree.yml for chapter1/2 & fix typo * French Translation - Chapter 5 * Add Bengali to CI * Update author list * Adding translations for 2/4 and 2/5 🚀 (#74) * Adding translations for 2/4 and 2/5 🚀 * Remove English content Co-authored-by: lewtun * Translation to Russian (#97) * translation of chapter 2/section 1 * add section 1 / chapter 2 to _toctree.yml * Translation of Chapter0 to Hindi (#86) * Hindi?Chapter0-Part_1 * Hindi/Chapter0-Part_2 * Chapter 0 Persian Translation First Draft (#95) * merged branch0 into main. no toctree yet. * updated toctree. * Updated the glossary with terms from chapter0. * Second draft in collab w/ @schoobani. Added empty chapter1 for preview. * Glossary typo fix. * Translation of Chapter0 (setup) to Arabic (#104) * Add AR translation for `introduction` * Fix alignment & format * Add Arabic to CI build * Russian - Chapter 1 finished (#98) * 01/4 start * 1/4 finished * 1/5 finished * 1/5 update toc * 1/6 finished * 1/7 finished * 1/8 finished * 1/9 finished * 1/4 fix * toc update * Chinese - Chapter 1 finished (#113) * Chinese - Chapter 1 finished * Add zh to the languages field Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> * [PT] Translation of chapter 2 (#107) * add PT translate to 2.1 * add PT translate to 2.2 * add portuguese translation to 2.3 * WIP portuguese translation to 2.4 * add portuguese translation to 2.4 * add portuguese translation to 2.5 * add portuguese translation to 2.6 * add _toctree infos Co-authored-by: lewtun * [FR] Translation of chapter 2 & event + Review of chapters 0 & 5 (#106) * Update _toctree.yml Add chapter 2 + little fix of chapter 5 * Update 1.mdx Review of chapter 0 * Create 1.mdx * Create 2.mdx * Create 3.mdx * Create 4.mdx * Create 5.mdx * Create 6.mdx * Create 7.mdx * Create 8.mdx * Update 8.mdx Since AutoNLP has recently been renamed to AutoTrain, let me make the correction on the English file * Update 1.mdx Review of chapter 5/1 * Update 2.mdx Review of chapter 5/2 * Update 3.mdx Review of chapter 5/3 * Update 4.mdx Review of chapter 5/4 * Update 5.mdx Review of chapter 5/5 * Update 6.mdx Review of chapter 5/6 * Update 7.mdx Review of chapter 5/7 * Update 8.mdx Review of chapter 5/8 * Create 1.mdx event's translation * Update _toctree.yml add event to the tree * Update _toctree.yml deletion of the files that pose a problem to pass the checks, will be resubmitted in another PR * Delete 1.mdx deletion of the files that pose a problem to pass the checks, will be resubmitted in another PR * make style correction * Update _toctree.yml the - * Fix spacing Co-authored-by: Lewis Tunstall * [th] Translated Chapter2/1 (#83) * Finish chapter2/1 * Update _toctree.yml * Add Hindi to CI (#116) * Update README.md (#87) * Update authors on README (#120) * Update authors * French translation `Chapter1` full (#56) * traduction 1st part of chapter1 * fix typo * fix job titles and encoder-decoder translation * add part 2 for 1st chapter * fix some typo part2 * fix Transformer -> Transformers * add part 3 not totally ended * end of part3 of chapter1 * part9 chapter 1 * add part7 chapter 1 * add part5 chapter 1 * part 6 chapter 1 * add part8 chapter 1 * end quizz of chapter * add last part of chapter 1 Co-authored-by: ChainYo * Translate to Japanese Chapter0 (#123) * start working * translate chapter0/1.mdx * [FA] First draft of Chapter2/Page2 (#129) * merged branch0 into main. no toctree yet. * updated toctree. * Updated the glossary with terms from chapter0. * Second draft in collab w/ @schoobani. Added empty chapter1 for preview. * Glossary typo fix. * Added missing backticks. * Removed a couple of bad indefinite articles I forgot. * First draft of ch2/p2. Adds to glossary. Trans. guidelines moved out. * Fixed missing diacritics, fixed the py/tf switch placing. Other fixes. * Changed the equivalent for prediction per @kambizG 's direction. * Redid ambiguous passage in translation per @lewtun 's direction. * [th] Finished whole Chapter 2 translation (#127) * Finish chapter2/1 * delete untranslated files * chapter2/2 WIP * Delete WIP files * WIP chapter2/2 * Fixed conflict * Update _toctree.yml * Update _toctree.yml * Finished Chapter2/2 * Finish all chapter2/n * Finish all chapter2/n * Fixed Ch2/8 as PR run failed * [de] Translation Chapter 0 (#130) * Copy files to newly created german dir (de) * Add translation for chapter 0 * Clean up english files for chapter 1 * Change _toctree.yml for chapter 0 * Fix whitespaces * Fix whitespaces again * Adjust _toctree.yml - leave only chaper 0 * Add German language (de) to workflow yaml files * [de] German Translation Guide (#132) * German Translation Guide * Add German Glossary to TOC * Chapter 1, Section 1 Bengali translation (#124) * [ADD] Chapter 1, Section 1 benglai tranlation * [FIX] toc * [FIX] commit mistakes * [FIX] remove the Eng duplicates Co-authored-by: m_khandaker * [FR] Review of chapters 0, 2 & 5 + add chapters 6, 7, 8 & event (#125) * Create 1.mdx Event translation * Create 1.mdx * Chapter 6 in French * Update 1.mdx fix italic * Update 9.mdx fix italic * Update 3.mdx fix italic * Update 4.mdx fix italic * Update 4.mdx * Update 1.mdx little fix * Update 2.mdx little fix * Update 4.mdx fix italic * Update 8.mdx fix italic * Update 1.mdx little fix * Update 2.mdx little fix * Update 3.mdx little fix * Update 5.mdx little fix * Update 7.mdx little fix * Update 8.mdx little fix * add chapter8 * Update 6.mdx fix italic * Update 3.mdx fix, fix everywhere * Update 2.mdx fix, fix everywhere * Update 4.mdx fix, fix everywhere * Update 4_tf.mdx fix, fix everywhere * Add files via upload add chapter 7 * Update 1.mdx fix links * Update 2.mdx fix, fix everywhere * Update 3.mdx fix, fix everywhere * Update 4.mdx fix, fix everywhere * Update 5.mdx * Update 6.mdx fix, fix everywhere * Update 7.mdx fix, fix everywhere * Update 3.mdx fix link * Update 8.mdx fix link * Update 2.mdx fix link * Update 4.mdx little fix * Update 6.mdx * Update 7.mdx * Update 8.mdx fix * Update 2.mdx little fix * Update 3.mdx little fix * Update 5.mdx * Update 4_tf.mdx little fix * Update _toctree.yml Forgot the toctree * Update _toctree.yml fix local fields * Update _toctree.yml My bad, I forgot some 🙃 * Update 7.mdx I don't know why it was there... * Update 1.mdx * [de] Chapter 3 translation (#128) * chapter 3 part 1 DE * [DE] Chapter 3 - Part 2 * Prepare TOC-Tree * Fein-tuning * Initial translation * Glossary additions for C3P3 * C3P2 style * [de] Chapter 3 P3-TF initial translation * [de] Chapter 3 P4 initial translation * [de] Chapter 3 Part 5 initial translation * [de] Chapter 3 P6 Initial translation * Missing commas * fixing quotes * Mark third option on chapter 8, question 8 as correct (#135) * doc_change(translation): translating course from english to gujarati (#126) * change(translation): chapter0 to gujarati content translated: Chapter0/1.mdx - Introduction commit-by: menonashwathi4@gmail.com * Revert "change(translation): chapter0 to gujarati" This reverts commit c27e06992af8892687f343a19368ce322d69e8b2. * doc_change(translation): translation to gj translated content: chapters/gj/chapter0.mdx - introduction * doc_change(translation): translation to gj translated content: chapters/gj/chapter0.mdx - introduction * Delete _toctree.yml * change: adding gj to github workflow * nit: fix heading * Update authors (#136) * [FA] First draft of Chapter4/Page1 (#134) * added chapter4 title and it's first section * added first draft of Chapter4/Page1 * minor fix * updated the title according to convention * applied some updates according to convention * added footnotes, minor improvements * applied tweaks according to review points * the new draft of glossary according to PR #134 * fixed an inconsistant title * minor fix for better compatibility with T points * applied final touches for this round of G updates * [FR] End of chapter 3 + chapter 4 (#137) * add chapters 3 & 4 * Update 2.mdx fix links * Update 3.mdx some fix * Update 6.mdx fix tag * Update 3.mdx add link to chapter 7 * Update 3_tf.mdx add link to chapter 7 * Update _toctree.yml Co-authored-by: DOOHAE JUNG Co-authored-by: m_khandaker Co-authored-by: Md. Al-Amin Khandaker Co-authored-by: ftarlaci <18291571+ftarlaci@users.noreply.github.com> Co-authored-by: Doohae Jung <80743307+Doohae@users.noreply.github.com> Co-authored-by: melaniedrevet Co-authored-by: Jose M Munoz Co-authored-by: svv73 <88366711+svv73@users.noreply.github.com> Co-authored-by: Vedant Pandya Co-authored-by: Bahram Shamshiri Co-authored-by: Giyaseddin Bayrak <34009210+giyaseddin@users.noreply.github.com> Co-authored-by: Pavel <60391448+pdumin@users.noreply.github.com> Co-authored-by: 1375626371 <40328311+1375626371@users.noreply.github.com> Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> Co-authored-by: João Gustavo A. Amorim Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Cherdsak Kingkan Co-authored-by: Thomas Chaigneau <50595514+ChainYo@users.noreply.github.com> Co-authored-by: ChainYo Co-authored-by: hiromu <45531573+hiromu166@users.noreply.github.com> Co-authored-by: Cherdsak Kingkan Co-authored-by: Marcus Fraaß Co-authored-by: Jesper Dramsch Co-authored-by: amyeroberts Co-authored-by: Ash <103081562+ashwathim@users.noreply.github.com> Co-authored-by: Hamed Homaei Rad * Bump release (#147) * Bump release (#161) * Fix typos in chapter 9 (#176) (#180) Co-authored-by: regisss <15324346+regisss@users.noreply.github.com> * Bump release (#187) * Chapter 2 Section 1 Bengali Translation (huggingface#72) (#168) * [TH] Chapter 6 Section 1 and 2 (#171) Co-authored-by: Suteera * [FA] CH1 / P1-2 (#142) * Spanish Chapter 3: sections 1 & 2 (#162) * fix typos in bpe, wordpiece, unigram (#166) * [FR] French Review (#186) * Part 7: Training a causal... fixes (#179) * typo & error mitigation * consistency * Trainer.predict() returns 3 fields * ran make style * [TR] Translated Chapter 1.6 🤗 (#185) * added chapter 1/6 to _toctree.yml * [TR] Translated Chapter 1.6 🤗 Co-authored-by: Avishek Das Co-authored-by: Suteera Seeha <33692408+meanna@users.noreply.github.com> Co-authored-by: Suteera Co-authored-by: Saeed Choobani Co-authored-by: Fermin Ordaz Co-authored-by: Kerem Turgutlu Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Sebastian Sosa <37946988+CakeCrusher@users.noreply.github.com> Co-authored-by: tanersekmen <56790802+tanersekmen@users.noreply.github.com> * Bump release 10 (#194) * Bump release (#195) * Bump release 12 (#209) * Bump release (#224) * Bump release (#229) * Bump release (#236) * Bump release (#258) * Bump release (#270) * Bump release (#274) * Bump release (#286) * Bump release (#288) * Chapter 2 Section 1 Bengali Translation (huggingface#72) (#168) * [TH] Chapter 6 Section 1 and 2 (#171) Co-authored-by: Suteera * [FA] CH1 / P1-2 (#142) * Spanish Chapter 3: sections 1 & 2 (#162) * fix typos in bpe, wordpiece, unigram (#166) * [FR] French Review (#186) * Part 7: Training a causal... fixes (#179) * typo & error mitigation * consistency * Trainer.predict() returns 3 fields * ran make style * [TR] Translated Chapter 1.6 🤗 (#185) * added chapter 1/6 to _toctree.yml * [TR] Translated Chapter 1.6 🤗 * [PT][Chapter 01 - 2.mdx] - issue #51 (#170) * Fix Gradio ToC (#193) * Add Gradio authors and Blocks event (#189) * Update 6.mdx (#188) Correct link to Transformer XL doc * Add translating notes and glossary to Spanish (#192) * Add translating notes and glosary to Spanish * Adding glossary to the toc * add pt 4.3 (#191) * [FR] Visual corrections (#190) * [PT] add chapter 4.4 and 4.5 (#196) * fix invite discord link (#197) * [FA] Second draft of CH2/P1-2 (#139) * added chapter3 in hindi (#198) * [TR] Chapter 3/1 (#165) * [RU] Ch3-1/2/3 (#200) * [PT] add 5.1 and 5.2 (#204) * [FA] - Ch3 - P1 and P2 (#199) * [PT] add `end-of-chapter quiz` for chapter 4 (4.6) (#201) Co-authored-by: lewtun * Chapter1: 2.mdx Translated. (#206) * Remove comments from Persian ToC (#210) * Fix CI URL for PRs (#211) * code fragment & english syntax and meaning (#203) * Updated Ch1/1 with Emoji (#214) * Add missing numpy import (#217) * [ES] translate sections 8.1 and 8.2 (#215) * Fix path to datasets (#216) * [PT] add 5.3 (#218) * fix 4.3 (#223) * Fix notebook generation (#227) * Add Gradio nb links * add 5.4 (#226) * add pt wip (#225) * Added Gujarati List. (#221) * Add Gradio nbs links to fr (#228) * Chinese - Chapter 3finished (#219) * add ch7 at _toctree and translate 7.1 (#222) * add 5.5 (#235) * [FR] Review of chapter 7 (#233) * Italian translation - chapter 4 (#230) * Added Thai translation of chapters 3 (#231) * [Ru] Add part 2, chapter 2 (#234) * Update 8.mdx (#237) - Remove Gradio Blocks Party - Add, Where to next? section * Created HI/Chapter1/5.mdx (#232) * Add Spanish chaper3/section4, update toc and glossary (#238) * [RU] Chapter 3 finished (#239) * [PT] add 5.6 and 5.7 (#240) * [EN] Visual corrections (#245) * Translation for 1/4, 1/5 and 1/6. (#247) * add event in PT (#250) * Pin version of black (#252) * Translate ja event (#241) * [PT] add quiz chapter 5 (#243) * Update 5.mdx (#253) inconsistent naming with line 327 * Translation for Traditional Chinese (zh-tw) chapter0 (#251) Co-authored-by: Lewis Tunstall * Translated the whole Chapter 3 to Thai (#255) * Japanese chapter 4 (#244) * Translation of 1/7, 1/8, and 1/9. (#263) * [PT] add chapter 8.1 and 8.2 (#265) * [RU] Chapter 4 (#269) * Add Thai translation for chapter 6.3b to 6.10 (#268) * add 8.3 (#266) * 3.mdx of chapter 01 (#260) Co-authored-by: Lewis Tunstall * Fix typo (#271) * [PT] add chapter 6.1 (#273) * add Japanese chapter7 (#267) * replace `load_metric` with `evaluate.load` (#285) * update `load_metric` refs to `evaluate.load` Co-authored-by: lewtun * [GJ] Translation to Gujarati - Ch0 Setup (#287) * [PT] add chapter 6.2 and 6.3 (#279) * zh-CN - Chapter 4,5finished (#281) Co-authored-by: Lewis Tunstall * Chapter 01 - Done [PT] #51 (#280) Co-authored-by: Lewis Tunstall Co-authored-by: Avishek Das Co-authored-by: Suteera Seeha <33692408+meanna@users.noreply.github.com> Co-authored-by: Suteera Co-authored-by: Saeed Choobani Co-authored-by: Fermin Ordaz Co-authored-by: Kerem Turgutlu Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Sebastian Sosa <37946988+CakeCrusher@users.noreply.github.com> Co-authored-by: tanersekmen <56790802+tanersekmen@users.noreply.github.com> Co-authored-by: Victor Costa <54755870+victorescosta@users.noreply.github.com> Co-authored-by: Camille Couturier Co-authored-by: João Gustavo A. Amorim Co-authored-by: Bahram Shamshiri Co-authored-by: Kavya <36916536+robotjellyzone@users.noreply.github.com> Co-authored-by: Batuhan Ayhan Co-authored-by: Pavel <60391448+pdumin@users.noreply.github.com> Co-authored-by: Kambiz Ghoorchian Co-authored-by: Vedant Pandya Co-authored-by: Diego Vargas <91356068+dzarkV@users.noreply.github.com> Co-authored-by: Thomas O'Brien Co-authored-by: Lincoln V Schreiber Co-authored-by: 1375626371 <40328311+1375626371@users.noreply.github.com> Co-authored-by: Giorgio Severi Co-authored-by: svv73 <88366711+svv73@users.noreply.github.com> Co-authored-by: Ömer Faruk Özdemir Co-authored-by: Caterina Bonan <97481648+CaterinaBi@users.noreply.github.com> Co-authored-by: Hiromu Hota Co-authored-by: trtd56 <5toda6@gmail.com> Co-authored-by: Mehrdad Nezamdoost Co-authored-by: Wolvz Co-authored-by: a-krirk <56425947+a-krirk@users.noreply.github.com> Co-authored-by: atgctg <105969161+atgctg@users.noreply.github.com> Co-authored-by: Thiago Medeiros Co-authored-by: webbigdata-jp <87654083+webbigdata-jp@users.noreply.github.com> Co-authored-by: Leandro von Werra Co-authored-by: Bhadresh Savani * Bump release (#295) * Bump release (#296) * Bump release (#299) * Bump release (#305) * Chinese - Chapter 1 finished * Add zh to the languages field Add zh to the languages field in the build_documentation.yml and build_pr_documentation.yml files * Remove untranslated chapters in _toctree.yml Remove all these sections that haven't been translated yet Remove Chapter 0 from the table of contents since it hasn't been translated yet * Fixed an error in the translation format Fixed an error in the translation format of Chapter 1, Section 3 * Added a small part of the missing content * Fix style * Complete the translation of Chapters 0 and 2 * Fixed some bugs ·Fixed some formatting errors ·Moved Chapters 0 and 2 to Simplified Chinese * Add files via upload Formatting revisions and some translation corrections * run make style to format chapter1 session3 * run make style to format code * run make style to format code * Fix style * Chapter 2 Section 1 Bengali Translation (huggingface#72) (#168) * [TH] Chapter 6 Section 1 and 2 (#171) Co-authored-by: Suteera * [FA] CH1 / P1-2 (#142) * Spanish Chapter 3: sections 1 & 2 (#162) * fix typos in bpe, wordpiece, unigram (#166) * [FR] French Review (#186) * Part 7: Training a causal... fixes (#179) * typo & error mitigation * consistency * Trainer.predict() returns 3 fields * ran make style * [TR] Translated Chapter 1.6 🤗 (#185) * added chapter 1/6 to _toctree.yml * [TR] Translated Chapter 1.6 🤗 * [PT][Chapter 01 - 2.mdx] - issue #51 (#170) * Fix Gradio ToC (#193) * Add Gradio authors and Blocks event (#189) * Update 6.mdx (#188) Correct link to Transformer XL doc * Add translating notes and glossary to Spanish (#192) * Add translating notes and glosary to Spanish * Adding glossary to the toc * add pt 4.3 (#191) * [FR] Visual corrections (#190) * [PT] add chapter 4.4 and 4.5 (#196) * fix invite discord link (#197) * [FA] Second draft of CH2/P1-2 (#139) * added chapter3 in hindi (#198) * [TR] Chapter 3/1 (#165) * [RU] Ch3-1/2/3 (#200) * [PT] add 5.1 and 5.2 (#204) * Add placeholders for audio chapters (#208) * [FA] - Ch3 - P1 and P2 (#199) * [PT] add `end-of-chapter quiz` for chapter 4 (4.6) (#201) Co-authored-by: lewtun * Chapter1: 2.mdx Translated. (#206) * Remove comments from Persian ToC (#210) * Fix CI URL for PRs (#211) * code fragment & english syntax and meaning (#203) * Updated Ch1/1 with Emoji (#214) * Add missing numpy import (#217) * Updata chapter3 * Code format for chapter3 * Updata yml file of chapter3 * Uptata yml file of chapter3 * Fix yml file bug * [ES] translate sections 8.1 and 8.2 (#215) * Fix path to datasets (#216) * [PT] add 5.3 (#218) * fix 4.3 (#223) * Run make style * Fix notebook generation (#227) * Add Gradio nb links * add 5.4 (#226) * add pt wip (#225) * Added Gujarati List. (#221) * Fix quality * Add Gradio nbs links to fr (#228) * Fix ToC tree * Remove audio templates * Fix fr section * Fix fr chapter * Chinese - Chapter 3finished (#219) * add ch7 at _toctree and translate 7.1 (#222) * add 5.5 (#235) * [FR] Review of chapter 7 (#233) * Italian translation - chapter 4 (#230) * Added Thai translation of chapters 3 (#231) * [Ru] Add part 2, chapter 2 (#234) * Update 8.mdx (#237) - Remove Gradio Blocks Party - Add, Where to next? section * Created HI/Chapter1/5.mdx (#232) * Add Spanish chaper3/section4, update toc and glossary (#238) * [RU] Chapter 3 finished (#239) * [PT] add 5.6 and 5.7 (#240) * [EN] Visual corrections (#245) * Translation for 1/4, 1/5 and 1/6. (#247) * add event in PT (#250) * Pin version of black (#252) * Translate ja event (#241) * [PT] add quiz chapter 5 (#243) * Update 5.mdx (#253) inconsistent naming with line 327 * Translation for Traditional Chinese (zh-tw) chapter0 (#251) Co-authored-by: Lewis Tunstall * Translated the whole Chapter 3 to Thai (#255) * Japanese chapter 4 (#244) * Translation of 1/7, 1/8, and 1/9. (#263) * [PT] add chapter 8.1 and 8.2 (#265) * [RU] Chapter 4 (#269) * Add Thai translation for chapter 6.3b to 6.10 (#268) * add 8.3 (#266) * 3.mdx of chapter 01 (#260) Co-authored-by: Lewis Tunstall * Fix typo (#271) * [PT] add chapter 6.1 (#273) * add Japanese chapter7 (#267) * zh-CN - Chapter 4,5finished * replace `load_metric` with `evaluate.load` (#285) * update `load_metric` refs to `evaluate.load` Co-authored-by: lewtun * [GJ] Translation to Gujarati - Ch0 Setup (#287) * [PT] add chapter 6.2 and 6.3 (#279) * Fix formatting * Debug formatting * Debug FR formatting * zh-CN - Chapter 4,5finished (#281) Co-authored-by: Lewis Tunstall * Chapter 01 - Done [PT] #51 (#280) Co-authored-by: Lewis Tunstall * tf_default_data_collator seems to have moved * zh-CN - Chapter 6finished * Revert "Merge branch 'huggingface:main' into main" This reverts commit aebb46e12f9f87a4303f8bb4f0f2cf545eb83b21, reversing changes made to 69187a3789e8d3d2d0de821ebe495f111d1cc73d. * Revert "zh-CN - Chapter 6finished" This reverts commit e69fce28d3a7b35b76c4f768a6cedf295b37d8c9. * zh-CN - Chapter 6finished * fix style * undo bad commit * Chapter5it (#278) * added the italian translation for unit 1 chapter5 Co-authored-by: Leandro von Werra * Vietnamese translation (#293) * Update .github/workflows/build_pr_documentation.yml Co-authored-by: lewtun * Translate JP chapter 8 (#249) * Italian translation - Chapter 8 (#272) * Translation to Vietnamese - chapter 5 (#297) * Add course contributors (#298) * Add CourseFloatingBanner component * DocNotebookDropdown -> CourseFloatingBanner * Italian translation Ch 2/1, 2/2 (#300) * Add contributors (#304) * Add forum button (#306) Co-authored-by: 1375626371 <40328311+1375626371@users.noreply.github.com> Co-authored-by: 1375626371 <1375626371@qq.com> Co-authored-by: Avishek Das Co-authored-by: Suteera Seeha <33692408+meanna@users.noreply.github.com> Co-authored-by: Suteera Co-authored-by: Saeed Choobani Co-authored-by: Fermin Ordaz Co-authored-by: Kerem Turgutlu Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Sebastian Sosa <37946988+CakeCrusher@users.noreply.github.com> Co-authored-by: tanersekmen <56790802+tanersekmen@users.noreply.github.com> Co-authored-by: Victor Costa <54755870+victorescosta@users.noreply.github.com> Co-authored-by: Camille Couturier Co-authored-by: João Gustavo A. Amorim Co-authored-by: Bahram Shamshiri Co-authored-by: Kavya <36916536+robotjellyzone@users.noreply.github.com> Co-authored-by: Batuhan Ayhan Co-authored-by: Pavel <60391448+pdumin@users.noreply.github.com> Co-authored-by: Kambiz Ghoorchian Co-authored-by: Vedant Pandya Co-authored-by: Diego Vargas <91356068+dzarkV@users.noreply.github.com> Co-authored-by: Thomas O'Brien Co-authored-by: Lincoln V Schreiber Co-authored-by: Giorgio Severi Co-authored-by: svv73 <88366711+svv73@users.noreply.github.com> Co-authored-by: Ömer Faruk Özdemir Co-authored-by: Caterina Bonan <97481648+CaterinaBi@users.noreply.github.com> Co-authored-by: Hiromu Hota Co-authored-by: trtd56 <5toda6@gmail.com> Co-authored-by: Mehrdad Nezamdoost Co-authored-by: Wolvz Co-authored-by: a-krirk <56425947+a-krirk@users.noreply.github.com> Co-authored-by: atgctg <105969161+atgctg@users.noreply.github.com> Co-authored-by: Thiago Medeiros Co-authored-by: webbigdata-jp <87654083+webbigdata-jp@users.noreply.github.com> Co-authored-by: Leandro von Werra Co-authored-by: Bhadresh Savani Co-authored-by: Andreas Ehrencrona Co-authored-by: leandro Co-authored-by: Matt Co-authored-by: Nolanogenn <52080100+Nolanogenn@users.noreply.github.com> Co-authored-by: Hồng Hạnh Co-authored-by: Younes Belkada <49240599+younesbelkada@users.noreply.github.com> Co-authored-by: Edoardo Abati <29585319+EdAbati@users.noreply.github.com> Co-authored-by: Mishig Davaadorj Co-authored-by: Acciaro Gennaro Daniele * Bump release (#307) * Bump release (#308) * Bump release (#314) * Bump release (#320) * Bump release (#328) * Bump release (#333) * Bump release (#335) * Bump release (#343) * Bump release (#355) * Bump release (#358) * Bump release (#371) * Bump release (#381) * Bump release (#387) * Bump release (#404) * Bump release (#413) * Bump release (#426) * Bump release (#463) --------- Co-authored-by: DOOHAE JUNG Co-authored-by: m_khandaker Co-authored-by: Md. Al-Amin Khandaker Co-authored-by: ftarlaci <18291571+ftarlaci@users.noreply.github.com> Co-authored-by: Doohae Jung <80743307+Doohae@users.noreply.github.com> Co-authored-by: melaniedrevet Co-authored-by: Jose M Munoz Co-authored-by: svv73 <88366711+svv73@users.noreply.github.com> Co-authored-by: Vedant Pandya Co-authored-by: Bahram Shamshiri Co-authored-by: Giyaseddin Bayrak <34009210+giyaseddin@users.noreply.github.com> Co-authored-by: Pavel <60391448+pdumin@users.noreply.github.com> Co-authored-by: 1375626371 <40328311+1375626371@users.noreply.github.com> Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> Co-authored-by: João Gustavo A. Amorim Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Cherdsak Kingkan Co-authored-by: Thomas Chaigneau <50595514+ChainYo@users.noreply.github.com> Co-authored-by: ChainYo Co-authored-by: hiromu <45531573+hiromu166@users.noreply.github.com> Co-authored-by: Cherdsak Kingkan Co-authored-by: Marcus Fraaß Co-authored-by: Jesper Dramsch Co-authored-by: amyeroberts Co-authored-by: Ash <103081562+ashwathim@users.noreply.github.com> Co-authored-by: Hamed Homaei Rad Co-authored-by: Dawood Khan Co-authored-by: regisss <15324346+regisss@users.noreply.github.com> Co-authored-by: Avishek Das Co-authored-by: Suteera Seeha <33692408+meanna@users.noreply.github.com> Co-authored-by: Suteera Co-authored-by: Saeed Choobani Co-authored-by: Fermin Ordaz Co-authored-by: Kerem Turgutlu Co-authored-by: Sebastian Sosa <37946988+CakeCrusher@users.noreply.github.com> Co-authored-by: tanersekmen <56790802+tanersekmen@users.noreply.github.com> Co-authored-by: Victor Costa <54755870+victorescosta@users.noreply.github.com> Co-authored-by: Camille Couturier Co-authored-by: Kavya <36916536+robotjellyzone@users.noreply.github.com> Co-authored-by: Batuhan Ayhan Co-authored-by: Kambiz Ghoorchian Co-authored-by: Diego Vargas <91356068+dzarkV@users.noreply.github.com> Co-authored-by: Thomas O'Brien Co-authored-by: Lincoln V Schreiber Co-authored-by: Giorgio Severi Co-authored-by: Ömer Faruk Özdemir Co-authored-by: Caterina Bonan <97481648+CaterinaBi@users.noreply.github.com> Co-authored-by: Hiromu Hota Co-authored-by: trtd56 <5toda6@gmail.com> Co-authored-by: Mehrdad Nezamdoost Co-authored-by: Wolvz Co-authored-by: a-krirk <56425947+a-krirk@users.noreply.github.com> Co-authored-by: atgctg <105969161+atgctg@users.noreply.github.com> Co-authored-by: Thiago Medeiros Co-authored-by: webbigdata-jp <87654083+webbigdata-jp@users.noreply.github.com> Co-authored-by: Leandro von Werra Co-authored-by: Bhadresh Savani Co-authored-by: 1375626371 <1375626371@qq.com> Co-authored-by: Andreas Ehrencrona Co-authored-by: leandro Co-authored-by: Matt Co-authored-by: Nolanogenn <52080100+Nolanogenn@users.noreply.github.com> Co-authored-by: Hồng Hạnh Co-authored-by: Younes Belkada <49240599+younesbelkada@users.noreply.github.com> Co-authored-by: Edoardo Abati <29585319+EdAbati@users.noreply.github.com> Co-authored-by: Mishig Davaadorj Co-authored-by: Acciaro Gennaro Daniele * Revert "Bump release (#566)" (#567) This reverts commit cccc2c91ac8e702e5e14bbb0419dbf0490c7aaaf. * updated documentation links * [doc build] Use secrets (#581) * docs: fix broken links * changed 'perspires' to 'persists' in chapter 1 quiz solves issue #585 * Update 4.mdx You forgot to write a return for this function. * Update 4.mdx : Fix Typo Should be "course" * fix link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Fix syntax in vi/chapter7/7.mdx There was an unnecessary `` * Remove `get_lr()` from logs which refers to nonexistent function `get_lr()` is called as part of this function, but the function is not declared anywhere in the script. This change removes this portion of the code since it is non-necessary. * Update 4.mdx removed judgmental argument * Update en-version * fix: remove useless token * fix: remove useless token (#635) * Translate Chapter 3 to Spanish (#510) * translate Chapter 3 to Spanish * translate code comments to Spanish and fix typos * Translating Chapter 6 to Spanish (#523) * Translating sections 1 and 2 to spanish * Translating sections 3 to spanish * Translating sections 3b to spanish * Translating sections 4 to spanish * Translating section 5 to spanish * Translating section 6 to spanish * Translating section 7 to spanish * Translating section 8 to spanish * Translating section 9 to spanish * Translating section 10 to spanish * Adding Sections to _toctree.yml * Fixing Typos after second review --------- Co-authored-by: datacubeR * Update 5.mdx Ajuste na tradução de "encoders". São "codificadores", não "decodificadores". Decoders são "decodificadores". * Update doc CI (#643) * Фиксация текущих результатов. * Фиксирую текущее состояние. * Fixing the transfer results for today. * Translated files 3b and partially 4. Fixing the result. * Fixing today's translation. * fix typos in Spanish translation (#511) * Fixing today's translation. Files: 6.mdx, 7.mdx and half of 8.mdx. * The translation of chapter 6 has been completed. * Delete chapters/en/.ipynb_checkpoints/_toctree-checkpoint.yml This is backup created by JupyterLab. * Delete chapters/en/chapter5/.ipynb_checkpoints/8-checkpoint.mdx This is backup created by JupyterLab. * Delete chapters/en/chapter6/.ipynb_checkpoints/1-checkpoint.mdx This is backup created by JupyterLab. * Delete chapters/en/chapter6/.ipynb_checkpoints/2-checkpoint.mdx This is backup created by JupyterLab. * Delete chapters/en/chapter6/.ipynb_checkpoints/8-checkpoint.mdx This is backup created by JupyterLab. * Delete chapters/en/chapter6/.ipynb_checkpoints/9-checkpoint.mdx This is backup created by JupyterLab. * Delete chapters/ru/.ipynb_checkpoints/TRANSLATING-checkpoint.txt This is backup created by JupyterLab. * Delete chapters/ru/.ipynb_checkpoints/_toctree-checkpoint.yml This is backup created by JupyterLab. * Delete chapters/ru/chapter5/.ipynb_checkpoints/8-checkpoint.mdx This is backup created by JupyterLab. * Update 10.mdx Minor fix. * Update 10.mdx Trying to solve the markup problem. * Update 10.mdx Correcting the syntax of some markup again) * Update chapters/ru/chapter6/4.mdx Yes, that space is redundant here. You're right about that. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter6/4.mdx Extra space. I overlooked it. My mistake. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter6/3.mdx There's an extra space here. You're right. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter6/3.mdx There's an extra space here. You're right. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter6/3b.mdx Yeah, there's no need for a space here. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter6/3.mdx Co-authored-by: Maria Khalusova * Update 3.mdx * Update 7.mdx Translated the comments noted on the review. * Update 3.mdx Translated the missing comments in the code. * Update chapters/ru/chapter6/3b.mdx Yes, an extra space. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter6/5.mdx Minor fix. Co-authored-by: Maria Khalusova * Completed the translation of the first part of Chapter 7 into Russian. * After run python utils/code_formatter.py * Update chapters/ru/chapter7/1.mdx Extra space. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/2.mdx Extra space. I didn't notice. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/2.mdx Extra space. I didn't notice. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/2.mdx Yes, indeed, I ate the space bar))))) Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/5.mdx There's that extra space again. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/5.mdx There's that extra space again that I didn't notice. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/5.mdx Extra space. Co-authored-by: Maria Khalusova * Update 5.mdx Translated the missing comment. * Update chapters/ru/chapter7/4.mdx Extra space. Co-authored-by: Maria Khalusova * Update 2.mdx Translated the missing comment in the code * Update 2.mdx Translated the missing sentence. * Update 3.mdx Translated the missing sentence. * Update 3.mdx I agree, it sounds more neutral that way. * Update chapters/ru/chapter7/3.mdx An unnecessary parenthesis. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/3.mdx Also an option, but we've translated it as "карточка модели" a lot of places. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/3.mdx Extra space. Co-authored-by: Maria Khalusova * Update 3.mdx Translated the missing comment in the code. * Update chapters/ru/chapter7/3.mdx Extra sapce. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/4.mdx Extra space. Co-authored-by: Maria Khalusova * Update 4.mdx Translated the missing comment in the code. * Update 5.mdx Added and translated the missing sentence: "Since the collator expects a list of dicts, where each dict represents a single example in the dataset, we also need to wrangle the data into the expected format before passing it to the data collator:" * Update 5.mdx Edit the display of the table on the course page. * fixed links to other chapters * fixed links to chapters' intros * I added myself to the Languages and translations table. * Deleted unnecessary folder automatically created by JupyterLab. * Fix links to HF docs * Finalizing the translation of chapter 7. * Update 6.mdx Extra space * Update 7.mdx Extra space * Update chapters/ru/chapter7/6.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/6.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/6.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/7.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/6.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/7.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/7.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/8.mdx Correction of abbreviation - NLP Co-authored-by: Maria Khalusova * Update 7.mdx Translated the code commentary * Update 6.mdx Translated the missing sentence. * Update chapters/ru/chapter7/7.mdx Co-authored-by: Maria Khalusova * Update 6.mdx * Update chapters/ru/chapter7/6.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/7.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/6.mdx Co-authored-by: Maria Khalusova * Fix style --------- Co-authored-by: researcher <1131419673@qq.com> Co-authored-by: iCell Co-authored-by: simpleAI Co-authored-by: Luke Cheng <2258420+chenglu@users.noreply.github.com> Co-authored-by: Qi Zhang Co-authored-by: Tiezhen WANG <38108242+xianbaoqian@users.noreply.github.com> Co-authored-by: Yuan Co-authored-by: FYJNEVERFOLLOWS Co-authored-by: zhangchaosd Co-authored-by: Kim Bo Geum <53206051+nsbg@users.noreply.github.com> Co-authored-by: TK Buristrakul Co-authored-by: Acciaro Gennaro Daniele Co-authored-by: Carlos Aguayo Co-authored-by: ateliershen Co-authored-by: Pavel Nesterov Co-authored-by: Artyom Boyko Co-authored-by: Kirill Milintsevich Co-authored-by: jybarnes21 Co-authored-by: gxy-gxy <1115404657@qq.com> Co-authored-by: iLeGend <824040212@qq.com> Co-authored-by: sj Co-authored-by: Sureshkumar Thangavel <108256+tsureshkumar@users.noreply.github.com> Co-authored-by: Andrei Shirobokov Co-authored-by: Pranav <66965591+Pranav-Bobde@users.noreply.github.com> Co-authored-by: Maria Khalusova Co-authored-by: DOOHAE JUNG Co-authored-by: m_khandaker Co-authored-by: Md. Al-Amin Khandaker Co-authored-by: ftarlaci <18291571+ftarlaci@users.noreply.github.com> Co-authored-by: Doohae Jung <80743307+Doohae@users.noreply.github.com> Co-authored-by: melaniedrevet Co-authored-by: Jose M Munoz Co-authored-by: svv73 <88366711+svv73@users.noreply.github.com> Co-authored-by: Vedant Pandya Co-authored-by: Bahram Shamshiri Co-authored-by: Giyaseddin Bayrak <34009210+giyaseddin@users.noreply.github.com> Co-authored-by: Pavel <60391448+pdumin@users.noreply.github.com> Co-authored-by: 1375626371 <40328311+1375626371@users.noreply.github.com> Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> Co-authored-by: João Gustavo A. Amorim Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Cherdsak Kingkan Co-authored-by: Thomas Chaigneau <50595514+ChainYo@users.noreply.github.com> Co-authored-by: ChainYo Co-authored-by: hiromu <45531573+hiromu166@users.noreply.github.com> Co-authored-by: Cherdsak Kingkan Co-authored-by: Marcus Fraaß Co-authored-by: Jesper Dramsch Co-authored-by: amyeroberts Co-authored-by: Ash <103081562+ashwathim@users.noreply.github.com> Co-authored-by: Hamed Homaei Rad Co-authored-by: Dawood Khan Co-authored-by: regisss <15324346+regisss@users.noreply.github.com> Co-authored-by: Avishek Das Co-authored-by: Suteera Seeha <33692408+meanna@users.noreply.github.com> Co-authored-by: Suteera Co-authored-by: Saeed Choobani Co-authored-by: Fermin Ordaz Co-authored-by: Kerem Turgutlu Co-authored-by: Sebastian Sosa <37946988+CakeCrusher@users.noreply.github.com> Co-authored-by: tanersekmen <56790802+tanersekmen@users.noreply.github.com> Co-authored-by: Victor Costa <54755870+victorescosta@users.noreply.github.com> Co-authored-by: Camille Couturier Co-authored-by: Kavya <36916536+robotjellyzone@users.noreply.github.com> Co-authored-by: Batuhan Ayhan Co-authored-by: Kambiz Ghoorchian Co-authored-by: Diego Vargas <91356068+dzarkV@users.noreply.github.com> Co-authored-by: Thomas O'Brien Co-authored-by: Lincoln V Schreiber Co-authored-by: Giorgio Severi Co-authored-by: Ömer Faruk Özdemir Co-authored-by: Caterina Bonan <97481648+CaterinaBi@users.noreply.github.com> Co-authored-by: Hiromu Hota Co-authored-by: trtd56 <5toda6@gmail.com> Co-authored-by: Mehrdad Nezamdoost Co-authored-by: Wolvz Co-authored-by: a-krirk <56425947+a-krirk@users.noreply.github.com> Co-authored-by: atgctg <105969161+atgctg@users.noreply.github.com> Co-authored-by: Thiago Medeiros Co-authored-by: webbigdata-jp <87654083+webbigdata-jp@users.noreply.github.com> Co-authored-by: Leandro von Werra Co-authored-by: Bhadresh Savani Co-authored-by: 1375626371 <1375626371@qq.com> Co-authored-by: Andreas Ehrencrona Co-authored-by: leandro Co-authored-by: Matt Co-authored-by: Nolanogenn <52080100+Nolanogenn@users.noreply.github.com> Co-authored-by: Hồng Hạnh Co-authored-by: Younes Belkada <49240599+younesbelkada@users.noreply.github.com> Co-authored-by: Edoardo Abati <29585319+EdAbati@users.noreply.github.com> Co-authored-by: Mishig Davaadorj Co-authored-by: nnoboa Co-authored-by: Vipula Sandaruwan Dissanayake Co-authored-by: Alex Bzdel Co-authored-by: JieShen <49408146+JieShenAI@users.noreply.github.com> Co-authored-by: Hardik Bhadani Co-authored-by: Omar Sanseviero Co-authored-by: Suket Kamboj <82956207+Sookeyy-12@users.noreply.github.com> Co-authored-by: Brad Windsor Co-authored-by: Pierre Alexandre SCHEMBRI Co-authored-by: Remy Co-authored-by: María Grandury <57645283+mariagrandury@users.noreply.github.com> Co-authored-by: Alfonso Tobar-Arancibia <48638337+datacubeR@users.noreply.github.com> Co-authored-by: datacubeR Co-authored-by: Alysson <50303964+k3ybladewielder@users.noreply.github.com> Co-authored-by: Merve Noyan Co-authored-by: mariosasko --- README.md | 2 +- chapters/de/chapter1/3.mdx | 2 +- chapters/de/chapter1/5.mdx | 10 +- chapters/de/chapter1/6.mdx | 6 +- chapters/de/chapter1/7.mdx | 8 +- chapters/de/chapter3/2.mdx | 2 +- chapters/de/chapter4/2.mdx | 4 +- chapters/en/chapter1/3.mdx | 2 +- chapters/en/chapter1/6.mdx | 6 +- chapters/en/chapter1/7.mdx | 8 +- chapters/en/chapter3/2.mdx | 2 +- chapters/en/chapter4/2.mdx | 4 +- chapters/en/chapter4/3.mdx | 2 +- chapters/en/chapter5/2.mdx | 4 +- chapters/en/chapter5/3.mdx | 4 +- chapters/en/chapter5/4.mdx | 2 +- chapters/en/chapter5/5.mdx | 2 +- chapters/en/chapter5/6.mdx | 2 +- chapters/en/chapter6/8.mdx | 16 +- chapters/es/chapter3/2.mdx | 2 +- chapters/es/chapter5/2.mdx | 4 +- chapters/es/chapter5/3.mdx | 4 +- chapters/es/chapter5/4.mdx | 2 +- chapters/es/chapter5/5.mdx | 2 +- chapters/es/chapter5/6.mdx | 2 +- chapters/es/chapter6/8.mdx | 16 +- chapters/fa/chapter3/2.mdx | 2 +- chapters/fr/chapter3/2.mdx | 2 +- chapters/fr/chapter5/2.mdx | 4 +- chapters/fr/chapter5/3.mdx | 4 +- chapters/fr/chapter5/4.mdx | 2 +- chapters/fr/chapter5/5.mdx | 2 +- chapters/fr/chapter5/6.mdx | 2 +- chapters/fr/chapter6/8.mdx | 16 +- chapters/fr/chapter9/4.mdx | 2 +- chapters/hi/chapter3/2.mdx | 2 +- chapters/it/chapter3/2.mdx | 2 +- chapters/it/chapter5/2.mdx | 4 +- chapters/it/chapter5/3.mdx | 4 +- chapters/it/chapter5/4.mdx | 2 +- chapters/it/chapter5/5.mdx | 2 +- chapters/it/chapter5/6.mdx | 2 +- chapters/ko/chapter5/2.mdx | 2 +- chapters/pt/chapter5/2.mdx | 4 +- chapters/pt/chapter5/3.mdx | 4 +- chapters/pt/chapter5/4.mdx | 2 +- chapters/pt/chapter5/5.mdx | 2 +- chapters/pt/chapter5/6.mdx | 2 +- chapters/ru/_toctree.yml | 22 + chapters/ru/chapter0/1.mdx | 2 +- chapters/ru/chapter2/1.mdx | 4 +- chapters/ru/chapter2/2.mdx | 6 +- chapters/ru/chapter2/3.mdx | 2 +- chapters/ru/chapter3/1.mdx | 2 +- chapters/ru/chapter3/2.mdx | 14 +- chapters/ru/chapter3/3.mdx | 8 +- chapters/ru/chapter3/3_tf.mdx | 8 +- chapters/ru/chapter4/3.mdx | 2 +- chapters/ru/chapter5/1.mdx | 4 +- chapters/ru/chapter5/2.mdx | 4 +- chapters/ru/chapter5/3.mdx | 26 +- chapters/ru/chapter5/4.mdx | 6 +- chapters/ru/chapter5/6.mdx | 10 +- chapters/ru/chapter5/7.mdx | 2 +- chapters/ru/chapter6/1.mdx | 4 +- chapters/ru/chapter6/2.mdx | 6 +- chapters/ru/chapter6/3.mdx | 10 +- chapters/ru/chapter6/3b.mdx | 4 +- chapters/ru/chapter6/4.mdx | 4 +- chapters/ru/chapter6/8.mdx | 18 +- chapters/ru/chapter7/1.mdx | 38 ++ chapters/ru/chapter7/2.mdx | 981 +++++++++++++++++++++++++++ chapters/ru/chapter7/3.mdx | 1044 ++++++++++++++++++++++++++++ chapters/ru/chapter7/4.mdx | 1002 +++++++++++++++++++++++++++ chapters/ru/chapter7/5.mdx | 1072 +++++++++++++++++++++++++++++ chapters/ru/chapter7/6.mdx | 914 +++++++++++++++++++++++++ chapters/ru/chapter7/7.mdx | 1203 +++++++++++++++++++++++++++++++++ chapters/ru/chapter7/8.mdx | 22 + chapters/ru/chapter7/9.mdx | 329 +++++++++ chapters/th/chapter3/2.mdx | 2 +- chapters/th/chapter6/8.mdx | 16 +- chapters/vi/chapter3/2.mdx | 2 +- chapters/vi/chapter5/2.mdx | 4 +- chapters/vi/chapter5/3.mdx | 4 +- chapters/vi/chapter5/4.mdx | 2 +- chapters/vi/chapter5/5.mdx | 2 +- chapters/vi/chapter5/6.mdx | 2 +- chapters/vi/chapter6/8.mdx | 16 +- chapters/zh-CN/chapter3/2.mdx | 2 +- chapters/zh-CN/chapter5/2.mdx | 2 +- chapters/zh-CN/chapter5/4.mdx | 2 +- chapters/zh-CN/chapter5/5.mdx | 2 +- chapters/zh-CN/chapter5/6.mdx | 2 +- chapters/zh-CN/chapter6/8.mdx | 16 +- chapters/zh-TW/chapter3/2.mdx | 2 +- chapters/zh-TW/chapter5/2.mdx | 2 +- chapters/zh-TW/chapter5/4.mdx | 2 +- chapters/zh-TW/chapter5/5.mdx | 2 +- chapters/zh-TW/chapter5/6.mdx | 2 +- chapters/zh-TW/chapter6/8.mdx | 16 +- 100 files changed, 6846 insertions(+), 219 deletions(-) create mode 100644 chapters/ru/chapter7/1.mdx create mode 100644 chapters/ru/chapter7/2.mdx create mode 100644 chapters/ru/chapter7/3.mdx create mode 100644 chapters/ru/chapter7/4.mdx create mode 100644 chapters/ru/chapter7/5.mdx create mode 100644 chapters/ru/chapter7/6.mdx create mode 100644 chapters/ru/chapter7/7.mdx create mode 100644 chapters/ru/chapter7/8.mdx create mode 100644 chapters/ru/chapter7/9.mdx 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/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 50a183761..12c7f699a 100644 --- a/chapters/de/chapter3/2.mdx +++ b/chapters/de/chapter3/2.mdx @@ -235,7 +235,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/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/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/chapter3/2.mdx b/chapters/en/chapter3/2.mdx index f64747dac..ebf464fc3 100644 --- a/chapters/en/chapter3/2.mdx +++ b/chapters/en/chapter3/2.mdx @@ -235,7 +235,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/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/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/es/chapter3/2.mdx b/chapters/es/chapter3/2.mdx index 7aed993ea..624749727 100644 --- a/chapters/es/chapter3/2.mdx +++ b/chapters/es/chapter3/2.mdx @@ -240,7 +240,7 @@ 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). -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): diff --git a/chapters/es/chapter5/2.mdx b/chapters/es/chapter5/2.mdx index a0de6264a..e68820743 100644 --- a/chapters/es/chapter5/2.mdx +++ b/chapters/es/chapter5/2.mdx @@ -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 f1cc5ab69..daae902ea 100644 --- a/chapters/es/chapter5/3.mdx +++ b/chapters/es/chapter5/3.mdx @@ -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. @@ -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 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.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 38919187d..326a2a609 100644 --- a/chapters/es/chapter5/4.mdx +++ b/chapters/es/chapter5/4.mdx @@ -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. diff --git a/chapters/es/chapter5/5.mdx b/chapters/es/chapter5/5.mdx index e901c3717..21419d4e5 100644 --- a/chapters/es/chapter5/5.mdx +++ b/chapters/es/chapter5/5.mdx @@ -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. diff --git a/chapters/es/chapter5/6.mdx b/chapters/es/chapter5/6.mdx index 1e635315b..d92a1333c 100644 --- a/chapters/es/chapter5/6.mdx +++ b/chapters/es/chapter5/6.mdx @@ -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. diff --git a/chapters/es/chapter6/8.mdx b/chapters/es/chapter6/8.mdx index 795be36aa..0754b82d0 100644 --- a/chapters/es/chapter6/8.mdx +++ b/chapters/es/chapter6/8.mdx @@ -27,14 +27,14 @@ La librería 🤗 Tokenizers ha sido construida para proveer varias opciones par 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/python/latest/api/reference.html#module-tokenizers.normalizers)). -- `pre_tokenizers` contiene todos los posibles tipos de `PreTokenizer` que puedes usar (la lista completa [aquí](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.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/python/latest/api/reference.html#module-tokenizers.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/python/latest/api/reference.html#module-tokenizers.trainers)). -- `post_processors` contiene varios tipos de `PostProcessor` que puedes usar (la lista completa [aquí](https://huggingface.co/docs/tokenizers/python/latest/api/reference.html#module-tokenizers.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/python/latest/components.html#decoders)). - -Puedes encontrar la lista completas de las unidades más básicas [aquí](https://huggingface.co/docs/tokenizers/python/latest/components.html). +- `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]] diff --git a/chapters/fa/chapter3/2.mdx b/chapters/fa/chapter3/2.mdx index 07d933cc6..696135a20 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -300,7 +300,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()` با اعمال کردن یک عملیات روی هر عنصر دیتاسِت عمل می‌کند، بنابراین اجازه دهید تابعی تعریف کنیم که ورودی‌ها را توکِن کند:
diff --git a/chapters/fr/chapter3/2.mdx b/chapters/fr/chapter3/2.mdx index 70b529f7b..0aa69be72 100644 --- a/chapters/fr/chapter3/2.mdx +++ b/chapters/fr/chapter3/2.mdx @@ -246,7 +246,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/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/chapter9/4.mdx b/chapters/fr/chapter9/4.mdx index ea9509a02..937ac66d0 100644 --- a/chapters/fr/chapter9/4.mdx +++ b/chapters/fr/chapter9/4.mdx @@ -48,7 +48,7 @@ gr.Interface( title=title, description=description, article=article, - examples=[["What are you doing?"], ["Where should we time travel to?"]] + examples=[["What are you doing?"], ["Where should we time travel to?"]], # ["Que faites-vous ?"], ["Où devrions-nous voyager dans le temps ?"] ).launch() ``` diff --git a/chapters/hi/chapter3/2.mdx b/chapters/hi/chapter3/2.mdx index 481fc824f..066bfddfc 100644 --- a/chapters/hi/chapter3/2.mdx +++ b/chapters/hi/chapter3/2.mdx @@ -235,7 +235,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/it/chapter3/2.mdx b/chapters/it/chapter3/2.mdx index 5ebea52a6..0b428d08d 100644 --- a/chapters/it/chapter3/2.mdx +++ b/chapters/it/chapter3/2.mdx @@ -236,7 +236,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/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/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 597730c46..0ec4ac451 100644 --- a/chapters/ru/_toctree.yml +++ b/chapters/ru/_toctree.yml @@ -113,6 +113,28 @@ 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: Глоссарий sections: - local: glossary/1 diff --git a/chapters/ru/chapter0/1.mdx b/chapters/ru/chapter0/1.mdx index 6e42a9fc7..72fce3460 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-пакетов. В этом уроке мы покажем, как установить окружение и необходимые библиотеки. diff --git a/chapters/ru/chapter2/1.mdx b/chapters/ru/chapter2/1.mdx index 47421460e..ce2dbae74 100644 --- a/chapters/ru/chapter2/1.mdx +++ b/chapters/ru/chapter2/1.mdx @@ -5,7 +5,7 @@ classNames="absolute z-10 right-0 top-0" /> -Как вы могли заметить в [Главе 1](/course/chapter1), модели трансформеров обычно бывают очень большие. Обучение и развертывание таких моделей с миллионами и даже десятками *миллиардов* параметров является сложной задачей. Кроме того, новые модели выпускаются почти ежедневно, и каждая из них имеет собственную реализацию, опробовать их все — непростая задача. +Как вы могли заметить в [Главе 1](../chapter1/1), модели трансформеров обычно бывают очень большие. Обучение и развертывание таких моделей с миллионами и даже десятками *миллиардов* параметров является сложной задачей. Кроме того, новые модели выпускаются почти ежедневно, и каждая из них имеет собственную реализацию, опробовать их все — непростая задача. Библиотека 🤗 Transformers была создана для решения этой проблемы. Её цель — предоставить единый API, с помощью которого можно загружать, обучать и сохранять любую модель трансформера. Основными функциями библиотеки являются: @@ -15,7 +15,7 @@ Последняя особенность сильно отличает библиотеку 🤗 Transformers от других библиотек машинного обучения. Модели не строятся на модулях, которые являются общими для всех файлов; вместо этого каждая модель имеет свои собственные слои. Это не только делает модели более доступными и понятными, но и позволяет легко экспериментировать с одной моделью, не затрагивая другие. -Эта глава начнается со сквозного примера, в котором мы используем модель и токенизатор вместе, чтобы воспроизвести функцию `pipeline()` представленную в [Главе 1](/course/chapter1). Далее мы обсудим API модели: углубимся в классы модели и конфигурации и покажем, как загружать модель и как она обрабатывает числовые входные данные для получения прогнозов. +Эта глава начнается со сквозного примера, в котором мы используем модель и токенизатор вместе, чтобы воспроизвести функцию `pipeline()` представленную в [Главе 1](../chapter1/1). Далее мы обсудим API модели: углубимся в классы модели и конфигурации и покажем, как загружать модель и как она обрабатывает числовые входные данные для получения прогнозов. Затем мы рассмотрим API токенизатора, который является другим основным компонентом функции `pipeline()`. Токенизаторы берут на себя первый и последний этапы обработки, обрабатывая преобразование текста в числовые входные данные для нейронной сети и обратное преобразование в текст, когда это необходимо. Наконец, мы покажем вам, как обработывается передача нескольких предложений в модель с помощью подготовленных пакетов, а затем завершим все это более детальным рассмотрением высокоуровневой функции `tokenizer()`. diff --git a/chapters/ru/chapter2/2.mdx b/chapters/ru/chapter2/2.mdx index ff5c6a630..3960afe39 100644 --- a/chapters/ru/chapter2/2.mdx +++ b/chapters/ru/chapter2/2.mdx @@ -32,7 +32,7 @@ {/if} -Давайте начнем с готового примера, взглянув на то, что происходило за кулисами, когда мы выполняли следующий код в [Главе 1](/course/chapter1): +Давайте начнем с готового примера, взглянув на то, что происходило за кулисами, когда мы выполняли следующий код в [Главе 1](../chapter1/1): ```python from transformers import pipeline @@ -53,7 +53,7 @@ classifier( {'label': 'NEGATIVE', 'score': 0.9994558095932007}] ``` -Как мы уже увидели в [Главе 1](/course/chapter1), данный конвейер включает в себя три шага: предварительная обработка, передача входных данных через модель и постобработка: +Как мы уже увидели в [Главе 1](../chapter1/1), данный конвейер включает в себя три шага: предварительная обработка, передача входных данных через модель и постобработка:
Полный конвейер NLP: токенизация текста, преобразование в идентификаторы и вывод с помощью модели Transformer и слоя 'head' модели. @@ -176,7 +176,7 @@ model = TFAutoModel.from_pretrained(checkpoint) Если вы пока не понимаете в чем смысл, не беспокойтесь об этом. Мы объясним все это позже. -Хотя эти скрытые состояния могут быть полезны сами по себе, они обычно являются входными данными для другой части модели, известной как слой *head*. В [Главе 1](/course/chapter1) разные задачи могли бы выполняться с одной и той же архитектурой, но с каждой из этих задач будет связан отдельный слой "head". +Хотя эти скрытые состояния могут быть полезны сами по себе, они обычно являются входными данными для другой части модели, известной как слой *head*. В [Главе 1](../chapter1/1) разные задачи могли бы выполняться с одной и той же архитектурой, но с каждой из этих задач будет связан отдельный слой "head". ### Многомерный вектор, что это? diff --git a/chapters/ru/chapter2/3.mdx b/chapters/ru/chapter2/3.mdx index 41d226813..b7e941ac4 100644 --- a/chapters/ru/chapter2/3.mdx +++ b/chapters/ru/chapter2/3.mdx @@ -112,7 +112,7 @@ model = TFBertModel(config) ``` {/if} -Модель можно использовать в этом состоянии, но она будет выводить тарабарщину; сначала ее нужно обучить. Мы могли бы обучить модель с нуля для решения поставленной задачи, но, как вы видели в [Главе 1](/course/chapter1), это потребовало бы много времени и большого количества данных, а также имело бы значительное воздействие на окружающую среду. Чтобы избежать ненужных и дублирующих усилий, крайне важно иметь возможность делиться и повторно использовать модели, которые уже были обучены. +Модель можно использовать в этом состоянии, но она будет выводить тарабарщину; сначала ее нужно обучить. Мы могли бы обучить модель с нуля для решения поставленной задачи, но, как вы видели в [Главе 1](../chapter1/1), это потребовало бы много времени и большого количества данных, а также имело бы значительное воздействие на окружающую среду. Чтобы избежать ненужных и дублирующих усилий, крайне важно иметь возможность делиться и повторно использовать модели, которые уже были обучены. Загрузить уже обученную модель Transformer очень просто — мы можем сделать это с помощью метода `from_pretrained()`: 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 dc8c83f24..0eada491c 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 @@ -159,7 +159,7 @@ raw_train_dataset.features {/if} -Чтобы предобработать датасет, нам необходимо конвертировать текст в числа, которые может обработать модель. Как вы видели в [предыдущей главе](/course/ru/chapter2), это делается с помощью токенайзера. Мы можем подать на вход токенайзеру одно или список предложений, т.е. можно токенизировать предложения попарно таким образом: +Чтобы предобработать датасет, нам необходимо конвертировать текст в числа, которые может обработать модель. Как вы видели в [предыдущей главе](../chapter2/1), это делается с помощью токенайзера. Мы можем подать на вход токенайзеру одно или список предложений, т.е. можно токенизировать предложения попарно таким образом: ```py from transformers import AutoTokenizer @@ -185,7 +185,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 +216,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 +235,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/chapter4/3.mdx b/chapters/ru/chapter4/3.mdx index d737fd07a..3a7fbf507 100644 --- a/chapters/ru/chapter4/3.mdx +++ b/chapters/ru/chapter4/3.mdx @@ -50,7 +50,7 @@ Простейший путь загрузки файлов на Hub – `push_to_hub` API. -Перед тем, как пойдем дальше, необходимо сгенерировать токен аутентификации. Это необходимо сделать для того, чтобы `huggingface_hub` API «узнал» вас и предоставил вам необходимые права на запись. Убедитесь, что вы находитесь в окружении, в котором установлена библиотека `transformers` (см. [Установка](/course/ru/chapter0)). Если вы работаете в Jupyter'е, вы можете использовать следующую функцию для авторизации: +Перед тем, как пойдем дальше, необходимо сгенерировать токен аутентификации. Это необходимо сделать для того, чтобы `huggingface_hub` API «узнал» вас и предоставил вам необходимые права на запись. Убедитесь, что вы находитесь в окружении, в котором установлена библиотека `transformers` (см. [Установка](../chapter0)). Если вы работаете в Jupyter'е, вы можете использовать следующую функцию для авторизации: ```python from huggingface_hub import notebook_login diff --git a/chapters/ru/chapter5/1.mdx b/chapters/ru/chapter5/1.mdx index ff8429d6a..911a94fa8 100644 --- a/chapters/ru/chapter5/1.mdx +++ b/chapters/ru/chapter5/1.mdx @@ -1,6 +1,6 @@ # Введение -В [главе 3](/course/ru/chapter3) вы поверхностно ознакомились с библиотекой 🤗 Datasets и увидели три главных шага для использования ее в процессе fine-tuning: +В [главе 3](../chapter3/1) вы поверхностно ознакомились с библиотекой 🤗 Datasets и увидели три главных шага для использования ее в процессе fine-tuning: 1. Загрузить датасет из Hugging Face Hub. 2. Произвести препроцессинг с помощью `Dataset.map()`. @@ -14,4 +14,4 @@ * Что, черт возьми, такое «отображение памяти» (memory mapping) и Apache Arrow? * Как вы можете создать свой собственный датасет и отправить его в Hub? -Принципы, которые вы изучите в этой главе, подготовят вас к более глубокому использованию токенизации и fine-tuning'а моделей в [главе 6](/course/ru/chapter6) и [главе 7](/course/ru/chapter7) – заваривайте кофе и мы начинаем! \ No newline at end of file +Принципы, которые вы изучите в этой главе, подготовят вас к более глубокому использованию токенизации и fine-tuning'а моделей в [главе 6](../chapter6) и [главе 7](../chapter7) – заваривайте кофе и мы начинаем! \ No newline at end of file diff --git a/chapters/ru/chapter5/2.mdx b/chapters/ru/chapter5/2.mdx index ee50ef207..b4a9c9ab5 100644 --- a/chapters/ru/chapter5/2.mdx +++ b/chapters/ru/chapter5/2.mdx @@ -123,7 +123,7 @@ DatasetDict({ -Аргумент `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..d5c232d63 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,9 @@ 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()`, вы можете увидеть, что одно из этих чисел – число объектов, поданных на вход функции, а другое – +О, нет! Не сработало! Почему? Посмотрим на ошибку: несовпадение в длинах, один из которых длиной 1463, а другой – 1000. Если вы обратитесь в [документацию](https://huggingface.co/docs/datasets/package_reference/main_classes#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. +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#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. Проблема заключается в том, что мы пытаемся смешать два разных датасета разной размерности: число колонок датасета `drug_dataset` равняется 1000, а нужный нам `tokenized_dataset` имеет 1463 колонки. Чтобы избежать этой ошибки, необходимо удалить несколько столбцов из старого датасета и сделать оба датасета одинакового размера. Мы можем достичь этого с помощью аргумента `remove_columns`: @@ -641,7 +641,7 @@ DatasetDict({ }) ``` -Отлично, теперь мы подготовили датасет, на котором можно обучить некоторые модели. В [разделе 5](/course/ru/chapter5/5) мы покажем, как загрузить датасеты на Hugging Face Hub, а пока закончим наш обзор и посмотрим несколько способов сохранения датасетов на локальный компьютер. +Отлично, теперь мы подготовили датасет, на котором можно обучить некоторые модели. В [разделе 5](../chapter5/5) мы покажем, как загрузить датасеты на Hugging Face Hub, а пока закончим наш обзор и посмотрим несколько способов сохранения датасетов на локальный компьютер. ## Сохранение датасетов @@ -727,7 +727,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 +740,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/chapter6/1.mdx b/chapters/ru/chapter6/1.mdx index 93525db9d..3aaeb428a 100644 --- a/chapters/ru/chapter6/1.mdx +++ b/chapters/ru/chapter6/1.mdx @@ -5,7 +5,7 @@ classNames="absolute z-10 right-0 top-0" /> -В [Главе 3](/course/chapter3) мы рассмотрели, как дообучить модель для конкретной задачи. При этом мы используем тот же токенизатор, на котором была предварительно обучена модель, но что делать, когда мы хотим обучить модель с нуля? В таких случаях использование токенизатора, который был предварительно обучен на корпусе из другой области или языка, как правило, является неоптимальным. Например, токенизатор, обученный на корпусе английских текстов, будет плохо работать на корпусе японских текстов, поскольку использование пробелов и знаков препинания в этих двух языках сильно отличается. +В [Главе 3](../chapter3/1) мы рассмотрели, как дообучить модель для конкретной задачи. При этом мы используем тот же токенизатор, на котором была предварительно обучена модель, но что делать, когда мы хотим обучить модель с нуля? В таких случаях использование токенизатора, который был предварительно обучен на корпусе из другой области или языка, как правило, является неоптимальным. Например, токенизатор, обученный на корпусе английских текстов, будет плохо работать на корпусе японских текстов, поскольку использование пробелов и знаков препинания в этих двух языках сильно отличается. В этой главе вы узнаете, как обучить совершенно новый токенизатор на корпусе текстов, чтобы затем использовать его для предварительного обучения языковой модели. Все это будет сделано с помощью библиотеки [🤗 Tokenizers](https://github.com/huggingface/tokenizers), которая предоставляет "быстрые" токенизаторы в библиотеке [🤗 Transformers](https://github.com/huggingface/transformers). Мы подробно рассмотрим возможности, которые предоставляет эта библиотека, и выясним, чем быстрые токенизаторы отличаются от "медленных" версий. @@ -16,4 +16,4 @@ * Различия между тремя основными алгоритмами токенизации по подсловам, используемыми в NLP сегодня * Как создать токенизатор с нуля с помощью библиотеки 🤗 Tokenizers и обучить его на некоторых данных -Техники, представленные в этой главе, подготовят вас к разделу в [Главе 7](/course/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/2.mdx b/chapters/ru/chapter6/2.mdx index 4dfd465bb..089e23962 100644 --- a/chapters/ru/chapter6/2.mdx +++ b/chapters/ru/chapter6/2.mdx @@ -7,7 +7,7 @@ {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/en/chapter6/section2.ipynb"}, ]} /> -Если языковая модель не доступна на интересующем вас языке или ваш корпус сильно отличается от того, на котором обучалась языковая модель, вам, скорее всего, придется заново обучать модель с нуля, используя токенизатор, адаптированный к вашим данным. Для этого потребуется обучить новый токенизатор на вашем наборе данных. Но что именно это значит? Когда мы впервые рассматривали токенизаторы в [Главе 2](/course/chapter2), мы увидели, что большинство моделей трансформеров используют _алгоритм токенизации по подсловам_. Чтобы определить, какие подслова представляют интерес и наиболее часто встречаются в корпусе, токенизатор должен внимательно изучить все тексты в корпусе - этот процесс мы называем *обучением*. Точные правила обучения зависят от типа используемого токенизатора, далее в этой главе мы рассмотрим три основных алгоритма. +Если языковая модель не доступна на интересующем вас языке или ваш корпус сильно отличается от того, на котором обучалась языковая модель, вам, скорее всего, придется заново обучать модель с нуля, используя токенизатор, адаптированный к вашим данным. Для этого потребуется обучить новый токенизатор на вашем наборе данных. Но что именно это значит? Когда мы впервые рассматривали токенизаторы в [Главе 2](../chapter2/1), мы увидели, что большинство моделей трансформеров используют _алгоритм токенизации по подсловам_. Чтобы определить, какие подслова представляют интерес и наиболее часто встречаются в корпусе, токенизатор должен внимательно изучить все тексты в корпусе - этот процесс мы называем *обучением*. Точные правила обучения зависят от типа используемого токенизатора, далее в этой главе мы рассмотрим три основных алгоритма. @@ -169,7 +169,7 @@ tokenizer = old_tokenizer.train_new_from_iterator(training_corpus, 52000) Обратите внимание, что `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, чтобы выполнить свою модель на батче входных данных на GPU, вам не понадобится изучать Rust, чтобы использовать быстрый токенизатор. Библиотека 🤗 Tokenizers предоставляет привязки к Python для многих методов, которые внутренне вызывают некоторые части кода на Rust; например, для распараллеливания обучения вашего нового токенизатора или, как мы видели в [Главе 3](/course/chapter3), токенизации батча входных данных. +Обучение совершенно нового токенизатора на чистом Python было бы мучительно медленным, поэтому мы разработали библиотеку 🤗 Tokenizers. Обратите внимание, что так же как вам не нужно было изучать язык CUDA, чтобы выполнить свою модель на батче входных данных на GPU, вам не понадобится изучать Rust, чтобы использовать быстрый токенизатор. Библиотека 🤗 Tokenizers предоставляет привязки к Python для многих методов, которые внутренне вызывают некоторые части кода на Rust; например, для распараллеливания обучения вашего нового токенизатора или, как мы видели в [Главе 3](../chapter3/1), токенизации батча входных данных. В большинстве моделей Transformer доступен быстрый токенизатор (есть некоторые исключения, о которых вы можете узнать [здесь](https://huggingface.co/transformers/#supported-frameworks)), а API `AutoTokenizer` всегда выбирает быстрый токенизатор, если он доступен. В следующем разделе мы рассмотрим некоторые другие особенности быстрых токенизаторов, которые будут очень полезны для таких задач, как классификация токенов и ответы на вопросы. Однако прежде чем погрузиться в эту тему, давайте попробуем наш новый токенизатор на предыдущем примере: @@ -254,4 +254,4 @@ tokenizer.push_to_hub("code-search-net-tokenizer") tokenizer = AutoTokenizer.from_pretrained("huggingface-course/code-search-net-tokenizer") ``` -Теперь вы готовы обучить языковую модель с нуля и дообучить ее в соответствии с поставленной задачей! Мы займемся этим в [Главе 7](/course/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 index 25aa85b22..214e2ba13 100644 --- a/chapters/ru/chapter6/3.mdx +++ b/chapters/ru/chapter6/3.mdx @@ -22,11 +22,11 @@ {/if} -В этом разделе мы подробно рассмотрим возможности токенизаторов в 🤗 Transformers. До сих пор мы использовали их только для токенизации входных данных или декодирования идентификаторов обратно в текст, но токенизаторы -- особенно те, которые поддерживаются библиотекой 🤗 Tokenizers - могут делать гораздо больше. Чтобы проиллюстрировать эти дополнительные возможности, мы рассмотрим, как воспроизвести результаты конвейеров `token-classification` (которые мы назвали `ner`) и `question-answering`, с которыми мы впервые столкнулись в [Главе 1] (/course/chapter1). +В этом разделе мы подробно рассмотрим возможности токенизаторов в 🤗 Transformers. До сих пор мы использовали их только для токенизации входных данных или декодирования идентификаторов обратно в текст, но токенизаторы -- особенно те, которые поддерживаются библиотекой 🤗 Tokenizers - могут делать гораздо больше. Чтобы проиллюстрировать эти дополнительные возможности, мы рассмотрим, как воспроизвести результаты конвейеров `token-classification` (которые мы назвали `ner`) и `question-answering`, с которыми мы впервые столкнулись в [Главе 1](../chapter1/1). -В дальнейшем обсуждении мы будем часто проводить различие между "медленными" и "быстрыми" токенизаторами. Медленные токенизаторы - это те, что написаны на Python в библиотеке 🤗 Transformers, а быстрые версии - это те, что предоставляются в 🤗 Tokenizers, которые написаны на Rust. Если вы помните таблицу из [Главы 5](/course/chapter5/3), в которой приводилось, сколько времени потребовалось быстрому и медленному токенизаторам для токенизации датасета Drug Review Dataset, вы должны иметь представление о том, почему мы называем их быстрыми и медленными: +В дальнейшем обсуждении мы будем часто проводить различие между "медленными" и "быстрыми" токенизаторами. Медленные токенизаторы - это те, что написаны на Python в библиотеке 🤗 Transformers, а быстрые версии - это те, что предоставляются в 🤗 Tokenizers, которые написаны на Rust. Если вы помните таблицу из [Главы 5](../chapter5/3), в которой приводилось, сколько времени потребовалось быстрому и медленному токенизаторам для токенизации датасета Drug Review Dataset, вы должны иметь представление о том, почему мы называем их быстрыми и медленными: | | Быстрый токенизатор | Медленный токенизатор :--------------:|:----------------------:|:----------------------: @@ -139,7 +139,7 @@ Sylvain ## Внутри конвейера `token-classification`[[inside-the-token-classification-pipeline]] -В [Главе 1](/course/chapter1) мы впервые попробовали применить NER - когда задача состоит в том, чтобы определить, какие части текста соответствуют сущностям, таким как люди, места или организации - с помощью функции 🤗 Transformers `pipeline()`. Затем, в [Главе 2](/course/chapter2), мы увидели, как конвейер объединяет три этапа, необходимые для получения прогнозов из необработанного текста: токенизацию, прохождение входных данных через модель и постобработку. Первые два шага в конвейере `token-classification` такие же, как и в любом другом конвейере, но постобработка немного сложнее - давайте посмотрим, как это сделать! +В [Главе 1](../chapter1/1) мы впервые попробовали применить NER - когда задача состоит в том, чтобы определить, какие части текста соответствуют сущностям, таким как люди, места или организации - с помощью функции 🤗 Transformers `pipeline()`. Затем, в [Главе 2](../chapter2/1), мы увидели, как конвейер объединяет три этапа, необходимые для получения прогнозов из необработанного текста: токенизацию, прохождение входных данных через модель и постобработку. Первые два шага в конвейере `token-classification` такие же, как и в любом другом конвейере, но постобработка немного сложнее - давайте посмотрим, как это сделать! {#if fw === 'pt'} @@ -200,7 +200,7 @@ token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.") {#if fw === 'pt'} -Сначала нам нужно токенизировать наш ввод и пропустить его через модель. Это делается точно так же, как в [Главе 2](/course/chapter2); мы инстанцируем токенизатор и модель с помощью классов `AutoXxx`, а затем используем их в нашем примере: +Сначала нам нужно токенизировать наш ввод и пропустить его через модель. Это делается точно так же, как в [Главе 2](../chapter2/1); мы инстанцируем токенизатор и модель с помощью классов `AutoXxx`, а затем используем их в нашем примере: ```py from transformers import AutoTokenizer, AutoModelForTokenClassification @@ -228,7 +228,7 @@ torch.Size([1, 19, 9]) {:else} -Сначала нам нужно токенизировать наши входные данные и пропустить их через модель. Это делается точно так же, как в [Главе 2](/course/chapter2); мы инстанцируем токенизатор и модель с помощью классов `TFAutoXxx`, а затем используем их в нашем примере: +Сначала нам нужно токенизировать наши входные данные и пропустить их через модель. Это делается точно так же, как в [Главе 2](../chapter2/1); мы инстанцируем токенизатор и модель с помощью классов `TFAutoXxx`, а затем используем их в нашем примере: ```py from transformers import AutoTokenizer, TFAutoModelForTokenClassification diff --git a/chapters/ru/chapter6/3b.mdx b/chapters/ru/chapter6/3b.mdx index 7902ec2ff..6ad4b6ed0 100644 --- a/chapters/ru/chapter6/3b.mdx +++ b/chapters/ru/chapter6/3b.mdx @@ -36,7 +36,7 @@ ## Использование конвейера `question-answering`[[using-the-question-answering-pipeline]] -Как мы видели в [Главе 1](/course/chapter1), для получения ответа на вопрос мы можем использовать конвейер `question-answering` следующим образом: +Как мы видели в [Главе 1](../chapter1/1), для получения ответа на вопрос мы можем использовать конвейер `question-answering` следующим образом: ```py from transformers import pipeline @@ -111,7 +111,7 @@ question_answerer(question=question, context=long_context) ## Использование модели для ответа на вопросы[[using-a-model-for-question-answering]] -Как и в любом другом конвейере, мы начинаем с токенизации входных данных, а затем отправляем их через модель. По умолчанию для конвейера `question-answering` используется контрольная точка [`distilbert-base-cased-distilled-squad`](https://huggingface.co/distilbert-base-cased-distilled-squad) (слово "squad" в названии происходит от набора данных, на котором дообучили модель; подробнее о наборе данных SQuAD мы поговорим в [главе 7](/course/chapter7/7)): +Как и в любом другом конвейере, мы начинаем с токенизации входных данных, а затем отправляем их через модель. По умолчанию для конвейера `question-answering` используется контрольная точка [`distilbert-base-cased-distilled-squad`](https://huggingface.co/distilbert-base-cased-distilled-squad) (слово "squad" в названии происходит от набора данных, на котором дообучили модель; подробнее о наборе данных SQuAD мы поговорим в [главе 7](../chapter7/7)): {#if fw === 'pt'} diff --git a/chapters/ru/chapter6/4.mdx b/chapters/ru/chapter6/4.mdx index e3c04d37a..fc5eaecf0 100644 --- a/chapters/ru/chapter6/4.mdx +++ b/chapters/ru/chapter6/4.mdx @@ -57,7 +57,7 @@ print(tokenizer.backend_tokenizer.normalizer.normalize_str("Héllò hôw are ü? -Как мы увидим в следующих разделах, токенизатор не может быть обучен только на сыром тексте. Сначала необходимо разбить текст на небольшие части, например, на слова. Именно в этом и заключается этап предварительной токенизации. Как мы видели в [Главе 2](/course/chapter2), токенизатор на основе слов (word-based tokenizer) может просто разбить необработанный текст на слова по пробелам и знакам пунктуации. Эти слова станут границами подтокенов, которые токенизатор сможет выучить в процессе обучения. +Как мы увидим в следующих разделах, токенизатор не может быть обучен только на сыром тексте. Сначала необходимо разбить текст на небольшие части, например, на слова. Именно в этом и заключается этап предварительной токенизации. Как мы видели в [Главе 2](../chapter2/1), токенизатор на основе слов (word-based tokenizer) может просто разбить необработанный текст на слова по пробелам и знакам пунктуации. Эти слова станут границами подтокенов, которые токенизатор сможет выучить в процессе обучения. Чтобы увидеть, как быстрый токенизатор выполняет предварительную токенизацию, мы можем воспользоваться методом `pre_tokenize_str()` атрибута `pre_tokenizer` объекта `tokenizer`: @@ -104,7 +104,7 @@ tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you? ## SentencePiece[[sentencepiece]] -[SentencePiece](https://github.com/google/sentencepiece) - это алгоритм токенизации для предварительной обработки текста, который можно использовать с любой из моделей, которые мы рассмотрим в следующих трех разделах. Он рассматривает текст как последовательность символов Unicode и заменяет пробелы специальным символом `▁`. При использовании в сочетании с алгоритмом Unigram (см. [раздел 7](/course/chapter7/7)) он даже не требует шага предварительной токенизации, что очень полезно для языков, где символ пробела не используется (например, китайского или японского). +[SentencePiece](https://github.com/google/sentencepiece) - это алгоритм токенизации для предварительной обработки текста, который можно использовать с любой из моделей, которые мы рассмотрим в следующих трех разделах. Он рассматривает текст как последовательность символов Unicode и заменяет пробелы специальным символом `▁`. При использовании в сочетании с алгоритмом Unigram (см. [раздел 7](../chapter7/7)) он даже не требует шага предварительной токенизации, что очень полезно для языков, где символ пробела не используется (например, китайского или японского). Другой главной особенностью SentencePiece является *обратимая токенизация*: поскольку в нем нет специальной обработки пробелов, декодирование токенов осуществляется просто путем их конкатенации и замены `_` на пробелы - в результате получается нормализованный текст. Как мы видели ранее, токенизатор BERT удаляет повторяющиеся пробелы, поэтому его токенизация не является обратимой. diff --git a/chapters/ru/chapter6/8.mdx b/chapters/ru/chapter6/8.mdx index 191e1d2f4..36b260a6b 100644 --- a/chapters/ru/chapter6/8.mdx +++ b/chapters/ru/chapter6/8.mdx @@ -21,24 +21,24 @@
-Библиотека 🤗 Tokenizers была создана для того, чтобы предоставить несколько вариантов каждого из этих шагов, которые вы можете смешивать и сочетать между собой. В этом разделе мы рассмотрим, как можно создать токенизатор с нуля, а не обучать новый токенизатор на основе старого, как мы делали в [разделе 2](/course/chapter6/2). После этого вы сможете создать любой токенизатор, который только сможете придумать! +Библиотека 🤗 Tokenizers была создана для того, чтобы предоставить несколько вариантов каждого из этих шагов, которые вы можете смешивать и сочетать между собой. В этом разделе мы рассмотрим, как можно создать токенизатор с нуля, а не обучать новый токенизатор на основе старого, как мы делали в [разделе 2](../chapter6/2). После этого вы сможете создать любой токенизатор, который только сможете придумать! Точнее, библиотека построена вокруг центрального класса `Tokenizer`, а строительные блоки сгруппированы в подмодули: -- `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`, которые вы можете использовать для обучения модели на корпусе (по одному на каждый тип модели; полный список [здесь](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)). +- `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/python/latest/components.html). +Весь список блоков вы можете найти [здесь](https://huggingface.co/docs/tokenizers/components). ## Получение корпуса текста[[acquiring-a-corpus]] -Для обучения нашего нового токенизатора мы будем использовать небольшой корпус текстов (чтобы примеры выполнялись быстро). Шаги по сбору корпуса аналогичны тем, что мы делали в [начале этой главы](/course/chapter6/2), но на этот раз мы будем использовать набор данных [WikiText-2](https://huggingface.co/datasets/wikitext): +Для обучения нашего нового токенизатора мы будем использовать небольшой корпус текстов (чтобы примеры выполнялись быстро). Шаги по сбору корпуса аналогичны тем, что мы делали в [начале этой главы](../chapter6/2), но на этот раз мы будем использовать набор данных [WikiText-2](https://huggingface.co/datasets/wikitext): ```python from datasets import load_dataset 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/th/chapter3/2.mdx b/chapters/th/chapter3/2.mdx index 73f8ec8cf..7e33dd154 100644 --- a/chapters/th/chapter3/2.mdx +++ b/chapters/th/chapter3/2.mdx @@ -235,7 +235,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/vi/chapter3/2.mdx b/chapters/vi/chapter3/2.mdx index e04c61e6d..3d2b3af2d 100644 --- a/chapters/vi/chapter3/2.mdx +++ b/chapters/vi/chapter3/2.mdx @@ -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/zh-CN/chapter3/2.mdx b/chapters/zh-CN/chapter3/2.mdx index 57e649e65..fbe6d00d3 100644 --- a/chapters/zh-CN/chapter3/2.mdx +++ b/chapters/zh-CN/chapter3/2.mdx @@ -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-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..f3a1044e9 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) 对这个任务很有用。 +✏️ **Try it out!** 看看能不能不用pandas就可以完成列的扩充; 这有点棘手; 你可能会发现 🤗 Datasets 文档的 ["Batch mapping"](https://huggingface.co/docs/datasets/about_map_batch#batch-mapping) 对这个任务很有用。 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-TW/chapter3/2.mdx b/chapters/zh-TW/chapter3/2.mdx index 4970fb9eb..5dd10b0a0 100644 --- a/chapters/zh-TW/chapter3/2.mdx +++ b/chapters/zh-TW/chapter3/2.mdx @@ -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/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..24bb8e9dd 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) 對這個任務很有用。 +✏️ **Try it out!** 看看能不能不用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)找到完整的模塊列表。 ## 獲取語​​料庫 From 7a98d35f7e7c1854bb45de978c0f6d356907bc7b Mon Sep 17 00:00:00 2001 From: lewtun Date: Fri, 2 Feb 2024 21:56:29 +0100 Subject: [PATCH 49/51] Bump release (#677) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * review chapter4/2 * review chapter4/2 * Review 75 * Review No.20, need review some * docs(zh-cn): Reviewed Chapter 7/1 * Update 1.mdx * Review No.22 * Review No.21 (need refinement) * Review No.30, need review: 26 27 28 30 73 74 * Review 30 (good) * Review 20 * Review 21 (refine) * Review 21 * Review 22 * Review 26 * Review 27 * Review 28 * Review 30 * Review 73 * Review 74 * Fix typo * Review 26-28, 42-54, 73-75 * The GPT2 link is broken The link `/course/en/chapter7/section6` does not exist in the course. Corrected to `/course/en/chapter7/6`. * typo in `Now your turn!` section Duplicated `the` was removed * `chunk_size` should be instead of `block_size` `chunk_size` should be instead of `block_size` (`block_size` was never mentioned before) * refactor: rephrase text to improve clarity and specificity In context to "training with a dataset specific to your task" and "train directly for the final task", I was not able to infer easily that "directly" here implies training from scratch. * Demo link fixes (#562) * demo link fixes * minor demo fix * Bump release (#566) * Add note about `remove_unused_columns` for whole word masking * Merge pull request #24 from huggingface/fix-typo Fix typo * Merge pull request #26 from huggingface/fix-qa-offsets Fix inequalities in answer spans for QA chapter * Merge pull request #30 from huggingface/fix-modelcard-url Update model card URL * Merge pull request #69 from yulonglin/patch-1 Correct typo mixing up space and newline symbols * Bump release (#99) * Bump release * Update author list Co-authored-by: DOOHAE JUNG Co-authored-by: m_khandaker Co-authored-by: Md. Al-Amin Khandaker Co-authored-by: ftarlaci <18291571+ftarlaci@users.noreply.github.com> Co-authored-by: Doohae Jung <80743307+Doohae@users.noreply.github.com> Co-authored-by: melaniedrevet * Bump release (#115) * ko-chapter1/1 * ko _toctree.yml created * Fix the issue #80 * Single expression changed * ko/chapter1 finished * ko/chapter0 finished * ko/chapter0 finished * reviewed by @bzantium ko/chapter0 * reviewed by @bzantium chapter0 & fixed typo * reviewed by @rainmaker712 * maximize Korean expressions * [Chapter 1] bangla traslation initial commit * Update 1.mdx update and fix formating * Fix formating and typos * translate _toctree.yml 0-1 chapter * Add Korean to CI * [tr] Translated chapter1/2.mdx * remove translation from sec titles not yet translated * Add authors [th ru] * [FIX] _toctree.yml * Update chapters/bn/chapter0/1.mdx [FIX] syntax formatting Co-authored-by: lewtun * tag typos & indentation & unnatural expressions * modified toctree.yml for chapter1/2 * modified toctree.yml for chapter1/2 & fix typo * French Translation - Chapter 5 * Add Bengali to CI * Update author list * Adding translations for 2/4 and 2/5 🚀 (#74) * Adding translations for 2/4 and 2/5 🚀 * Remove English content Co-authored-by: lewtun * Translation to Russian (#97) * translation of chapter 2/section 1 * add section 1 / chapter 2 to _toctree.yml * Translation of Chapter0 to Hindi (#86) * Hindi?Chapter0-Part_1 * Hindi/Chapter0-Part_2 * Chapter 0 Persian Translation First Draft (#95) * merged branch0 into main. no toctree yet. * updated toctree. * Updated the glossary with terms from chapter0. * Second draft in collab w/ @schoobani. Added empty chapter1 for preview. * Glossary typo fix. * Translation of Chapter0 (setup) to Arabic (#104) * Add AR translation for `introduction` * Fix alignment & format * Add Arabic to CI build * Russian - Chapter 1 finished (#98) * 01/4 start * 1/4 finished * 1/5 finished * 1/5 update toc * 1/6 finished * 1/7 finished * 1/8 finished * 1/9 finished * 1/4 fix * toc update * Chinese - Chapter 1 finished (#113) * Chinese - Chapter 1 finished * Add zh to the languages field Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> * [PT] Translation of chapter 2 (#107) * add PT translate to 2.1 * add PT translate to 2.2 * add portuguese translation to 2.3 * WIP portuguese translation to 2.4 * add portuguese translation to 2.4 * add portuguese translation to 2.5 * add portuguese translation to 2.6 * add _toctree infos Co-authored-by: lewtun * [FR] Translation of chapter 2 & event + Review of chapters 0 & 5 (#106) * Update _toctree.yml Add chapter 2 + little fix of chapter 5 * Update 1.mdx Review of chapter 0 * Create 1.mdx * Create 2.mdx * Create 3.mdx * Create 4.mdx * Create 5.mdx * Create 6.mdx * Create 7.mdx * Create 8.mdx * Update 8.mdx Since AutoNLP has recently been renamed to AutoTrain, let me make the correction on the English file * Update 1.mdx Review of chapter 5/1 * Update 2.mdx Review of chapter 5/2 * Update 3.mdx Review of chapter 5/3 * Update 4.mdx Review of chapter 5/4 * Update 5.mdx Review of chapter 5/5 * Update 6.mdx Review of chapter 5/6 * Update 7.mdx Review of chapter 5/7 * Update 8.mdx Review of chapter 5/8 * Create 1.mdx event's translation * Update _toctree.yml add event to the tree * Update _toctree.yml deletion of the files that pose a problem to pass the checks, will be resubmitted in another PR * Delete 1.mdx deletion of the files that pose a problem to pass the checks, will be resubmitted in another PR * make style correction * Update _toctree.yml the - * Fix spacing Co-authored-by: Lewis Tunstall * [th] Translated Chapter2/1 (#83) * Finish chapter2/1 * Update _toctree.yml * ko-chapter1/1 * ko _toctree.yml created * Fix the issue #80 * Single expression changed * ko/chapter1 finished * ko/chapter0 finished * ko/chapter0 finished * reviewed by @bzantium ko/chapter0 * reviewed by @bzantium chapter0 & fixed typo * reviewed by @rainmaker712 * maximize Korean expressions * [Chapter 1] bangla traslation initial commit * Update 1.mdx update and fix formating * Fix formating and typos * translate _toctree.yml 0-1 chapter * Add Korean to CI * remove translation from sec titles not yet translated * Add authors [th ru] * [FIX] _toctree.yml * Update chapters/bn/chapter0/1.mdx [FIX] syntax formatting Co-authored-by: lewtun * tag typos & indentation & unnatural expressions * modified toctree.yml for chapter1/2 * modified toctree.yml for chapter1/2 & fix typo * Add Bengali to CI * Update author list * Adding translations for 2/4 and 2/5 🚀 (#74) * Adding translations for 2/4 and 2/5 🚀 * Remove English content Co-authored-by: lewtun * Translation to Russian (#97) * translation of chapter 2/section 1 * add section 1 / chapter 2 to _toctree.yml * Translation of Chapter0 to Hindi (#86) * Hindi?Chapter0-Part_1 * Hindi/Chapter0-Part_2 * Chapter 0 Persian Translation First Draft (#95) * merged branch0 into main. no toctree yet. * updated toctree. * Updated the glossary with terms from chapter0. * Second draft in collab w/ @schoobani. Added empty chapter1 for preview. * Glossary typo fix. * Translation of Chapter0 (setup) to Arabic (#104) * Add AR translation for `introduction` * Fix alignment & format * Add Arabic to CI build * Russian - Chapter 1 finished (#98) * 01/4 start * 1/4 finished * 1/5 finished * 1/5 update toc * 1/6 finished * 1/7 finished * 1/8 finished * 1/9 finished * 1/4 fix * toc update * Chinese - Chapter 1 finished (#113) * Chinese - Chapter 1 finished * Add zh to the languages field Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> * [PT] Translation of chapter 2 (#107) * add PT translate to 2.1 * add PT translate to 2.2 * add portuguese translation to 2.3 * WIP portuguese translation to 2.4 * add portuguese translation to 2.4 * add portuguese translation to 2.5 * add portuguese translation to 2.6 * add _toctree infos Co-authored-by: lewtun * [FR] Translation of chapter 2 & event + Review of chapters 0 & 5 (#106) * Update _toctree.yml Add chapter 2 + little fix of chapter 5 * Update 1.mdx Review of chapter 0 * Create 1.mdx * Create 2.mdx * Create 3.mdx * Create 4.mdx * Create 5.mdx * Create 6.mdx * Create 7.mdx * Create 8.mdx * Update 8.mdx Since AutoNLP has recently been renamed to AutoTrain, let me make the correction on the English file * Update 1.mdx Review of chapter 5/1 * Update 2.mdx Review of chapter 5/2 * Update 3.mdx Review of chapter 5/3 * Update 4.mdx Review of chapter 5/4 * Update 5.mdx Review of chapter 5/5 * Update 6.mdx Review of chapter 5/6 * Update 7.mdx Review of chapter 5/7 * Update 8.mdx Review of chapter 5/8 * Create 1.mdx event's translation * Update _toctree.yml add event to the tree * Update _toctree.yml deletion of the files that pose a problem to pass the checks, will be resubmitted in another PR * Delete 1.mdx deletion of the files that pose a problem to pass the checks, will be resubmitted in another PR * make style correction * Update _toctree.yml the - * Fix spacing Co-authored-by: Lewis Tunstall * [th] Translated Chapter2/1 (#83) * Finish chapter2/1 * Update _toctree.yml * Add Hindi to CI (#116) Co-authored-by: DOOHAE JUNG Co-authored-by: m_khandaker Co-authored-by: Md. Al-Amin Khandaker Co-authored-by: ftarlaci <18291571+ftarlaci@users.noreply.github.com> Co-authored-by: Doohae Jung <80743307+Doohae@users.noreply.github.com> Co-authored-by: melaniedrevet Co-authored-by: Jose M Munoz Co-authored-by: svv73 <88366711+svv73@users.noreply.github.com> Co-authored-by: Vedant Pandya Co-authored-by: Bahram Shamshiri Co-authored-by: Giyaseddin Bayrak <34009210+giyaseddin@users.noreply.github.com> Co-authored-by: Pavel <60391448+pdumin@users.noreply.github.com> Co-authored-by: 1375626371 <40328311+1375626371@users.noreply.github.com> Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> Co-authored-by: João Gustavo A. Amorim Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Cherdsak Kingkan * Bump release 4 (#133) * Bump release (#138) * ko-chapter1/1 * ko _toctree.yml created * Fix the issue #80 * Single expression changed * ko/chapter1 finished * ko/chapter0 finished * ko/chapter0 finished * reviewed by @bzantium ko/chapter0 * reviewed by @bzantium chapter0 & fixed typo * reviewed by @rainmaker712 * maximize Korean expressions * [Chapter 1] bangla traslation initial commit * Update 1.mdx update and fix formating * Fix formating and typos * translate _toctree.yml 0-1 chapter * Add Korean to CI * [tr] Translated chapter1/2.mdx * remove translation from sec titles not yet translated * Add authors [th ru] * [FIX] _toctree.yml * Update chapters/bn/chapter0/1.mdx [FIX] syntax formatting Co-authored-by: lewtun * tag typos & indentation & unnatural expressions * modified toctree.yml for chapter1/2 * modified toctree.yml for chapter1/2 & fix typo * French Translation - Chapter 5 * Add Bengali to CI * Update author list * Adding translations for 2/4 and 2/5 🚀 (#74) * Adding translations for 2/4 and 2/5 🚀 * Remove English content Co-authored-by: lewtun * Translation to Russian (#97) * translation of chapter 2/section 1 * add section 1 / chapter 2 to _toctree.yml * Translation of Chapter0 to Hindi (#86) * Hindi?Chapter0-Part_1 * Hindi/Chapter0-Part_2 * Chapter 0 Persian Translation First Draft (#95) * merged branch0 into main. no toctree yet. * updated toctree. * Updated the glossary with terms from chapter0. * Second draft in collab w/ @schoobani. Added empty chapter1 for preview. * Glossary typo fix. * Translation of Chapter0 (setup) to Arabic (#104) * Add AR translation for `introduction` * Fix alignment & format * Add Arabic to CI build * Russian - Chapter 1 finished (#98) * 01/4 start * 1/4 finished * 1/5 finished * 1/5 update toc * 1/6 finished * 1/7 finished * 1/8 finished * 1/9 finished * 1/4 fix * toc update * Chinese - Chapter 1 finished (#113) * Chinese - Chapter 1 finished * Add zh to the languages field Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> * [PT] Translation of chapter 2 (#107) * add PT translate to 2.1 * add PT translate to 2.2 * add portuguese translation to 2.3 * WIP portuguese translation to 2.4 * add portuguese translation to 2.4 * add portuguese translation to 2.5 * add portuguese translation to 2.6 * add _toctree infos Co-authored-by: lewtun * [FR] Translation of chapter 2 & event + Review of chapters 0 & 5 (#106) * Update _toctree.yml Add chapter 2 + little fix of chapter 5 * Update 1.mdx Review of chapter 0 * Create 1.mdx * Create 2.mdx * Create 3.mdx * Create 4.mdx * Create 5.mdx * Create 6.mdx * Create 7.mdx * Create 8.mdx * Update 8.mdx Since AutoNLP has recently been renamed to AutoTrain, let me make the correction on the English file * Update 1.mdx Review of chapter 5/1 * Update 2.mdx Review of chapter 5/2 * Update 3.mdx Review of chapter 5/3 * Update 4.mdx Review of chapter 5/4 * Update 5.mdx Review of chapter 5/5 * Update 6.mdx Review of chapter 5/6 * Update 7.mdx Review of chapter 5/7 * Update 8.mdx Review of chapter 5/8 * Create 1.mdx event's translation * Update _toctree.yml add event to the tree * Update _toctree.yml deletion of the files that pose a problem to pass the checks, will be resubmitted in another PR * Delete 1.mdx deletion of the files that pose a problem to pass the checks, will be resubmitted in another PR * make style correction * Update _toctree.yml the - * Fix spacing Co-authored-by: Lewis Tunstall * [th] Translated Chapter2/1 (#83) * Finish chapter2/1 * Update _toctree.yml * Add Hindi to CI (#116) * Update README.md (#87) * Update authors on README (#120) * Update authors * French translation `Chapter1` full (#56) * traduction 1st part of chapter1 * fix typo * fix job titles and encoder-decoder translation * add part 2 for 1st chapter * fix some typo part2 * fix Transformer -> Transformers * add part 3 not totally ended * end of part3 of chapter1 * part9 chapter 1 * add part7 chapter 1 * add part5 chapter 1 * part 6 chapter 1 * add part8 chapter 1 * end quizz of chapter * add last part of chapter 1 Co-authored-by: ChainYo * Translate to Japanese Chapter0 (#123) * start working * translate chapter0/1.mdx * [FA] First draft of Chapter2/Page2 (#129) * merged branch0 into main. no toctree yet. * updated toctree. * Updated the glossary with terms from chapter0. * Second draft in collab w/ @schoobani. Added empty chapter1 for preview. * Glossary typo fix. * Added missing backticks. * Removed a couple of bad indefinite articles I forgot. * First draft of ch2/p2. Adds to glossary. Trans. guidelines moved out. * Fixed missing diacritics, fixed the py/tf switch placing. Other fixes. * Changed the equivalent for prediction per @kambizG 's direction. * Redid ambiguous passage in translation per @lewtun 's direction. * [th] Finished whole Chapter 2 translation (#127) * Finish chapter2/1 * delete untranslated files * chapter2/2 WIP * Delete WIP files * WIP chapter2/2 * Fixed conflict * Update _toctree.yml * Update _toctree.yml * Finished Chapter2/2 * Finish all chapter2/n * Finish all chapter2/n * Fixed Ch2/8 as PR run failed * [de] Translation Chapter 0 (#130) * Copy files to newly created german dir (de) * Add translation for chapter 0 * Clean up english files for chapter 1 * Change _toctree.yml for chapter 0 * Fix whitespaces * Fix whitespaces again * Adjust _toctree.yml - leave only chaper 0 * Add German language (de) to workflow yaml files * [de] German Translation Guide (#132) * German Translation Guide * Add German Glossary to TOC * Chapter 1, Section 1 Bengali translation (#124) * [ADD] Chapter 1, Section 1 benglai tranlation * [FIX] toc * [FIX] commit mistakes * [FIX] remove the Eng duplicates Co-authored-by: m_khandaker * [FR] Review of chapters 0, 2 & 5 + add chapters 6, 7, 8 & event (#125) * Create 1.mdx Event translation * Create 1.mdx * Chapter 6 in French * Update 1.mdx fix italic * Update 9.mdx fix italic * Update 3.mdx fix italic * Update 4.mdx fix italic * Update 4.mdx * Update 1.mdx little fix * Update 2.mdx little fix * Update 4.mdx fix italic * Update 8.mdx fix italic * Update 1.mdx little fix * Update 2.mdx little fix * Update 3.mdx little fix * Update 5.mdx little fix * Update 7.mdx little fix * Update 8.mdx little fix * add chapter8 * Update 6.mdx fix italic * Update 3.mdx fix, fix everywhere * Update 2.mdx fix, fix everywhere * Update 4.mdx fix, fix everywhere * Update 4_tf.mdx fix, fix everywhere * Add files via upload add chapter 7 * Update 1.mdx fix links * Update 2.mdx fix, fix everywhere * Update 3.mdx fix, fix everywhere * Update 4.mdx fix, fix everywhere * Update 5.mdx * Update 6.mdx fix, fix everywhere * Update 7.mdx fix, fix everywhere * Update 3.mdx fix link * Update 8.mdx fix link * Update 2.mdx fix link * Update 4.mdx little fix * Update 6.mdx * Update 7.mdx * Update 8.mdx fix * Update 2.mdx little fix * Update 3.mdx little fix * Update 5.mdx * Update 4_tf.mdx little fix * Update _toctree.yml Forgot the toctree * Update _toctree.yml fix local fields * Update _toctree.yml My bad, I forgot some 🙃 * Update 7.mdx I don't know why it was there... * Update 1.mdx * [de] Chapter 3 translation (#128) * chapter 3 part 1 DE * [DE] Chapter 3 - Part 2 * Prepare TOC-Tree * Fein-tuning * Initial translation * Glossary additions for C3P3 * C3P2 style * [de] Chapter 3 P3-TF initial translation * [de] Chapter 3 P4 initial translation * [de] Chapter 3 Part 5 initial translation * [de] Chapter 3 P6 Initial translation * Missing commas * fixing quotes * Mark third option on chapter 8, question 8 as correct (#135) * doc_change(translation): translating course from english to gujarati (#126) * change(translation): chapter0 to gujarati content translated: Chapter0/1.mdx - Introduction commit-by: menonashwathi4@gmail.com * Revert "change(translation): chapter0 to gujarati" This reverts commit c27e06992af8892687f343a19368ce322d69e8b2. * doc_change(translation): translation to gj translated content: chapters/gj/chapter0.mdx - introduction * doc_change(translation): translation to gj translated content: chapters/gj/chapter0.mdx - introduction * Delete _toctree.yml * change: adding gj to github workflow * nit: fix heading * Update authors (#136) * [FA] First draft of Chapter4/Page1 (#134) * added chapter4 title and it's first section * added first draft of Chapter4/Page1 * minor fix * updated the title according to convention * applied some updates according to convention * added footnotes, minor improvements * applied tweaks according to review points * the new draft of glossary according to PR #134 * fixed an inconsistant title * minor fix for better compatibility with T points * applied final touches for this round of G updates * [FR] End of chapter 3 + chapter 4 (#137) * add chapters 3 & 4 * Update 2.mdx fix links * Update 3.mdx some fix * Update 6.mdx fix tag * Update 3.mdx add link to chapter 7 * Update 3_tf.mdx add link to chapter 7 * Update _toctree.yml Co-authored-by: DOOHAE JUNG Co-authored-by: m_khandaker Co-authored-by: Md. Al-Amin Khandaker Co-authored-by: ftarlaci <18291571+ftarlaci@users.noreply.github.com> Co-authored-by: Doohae Jung <80743307+Doohae@users.noreply.github.com> Co-authored-by: melaniedrevet Co-authored-by: Jose M Munoz Co-authored-by: svv73 <88366711+svv73@users.noreply.github.com> Co-authored-by: Vedant Pandya Co-authored-by: Bahram Shamshiri Co-authored-by: Giyaseddin Bayrak <34009210+giyaseddin@users.noreply.github.com> Co-authored-by: Pavel <60391448+pdumin@users.noreply.github.com> Co-authored-by: 1375626371 <40328311+1375626371@users.noreply.github.com> Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> Co-authored-by: João Gustavo A. Amorim Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Cherdsak Kingkan Co-authored-by: Thomas Chaigneau <50595514+ChainYo@users.noreply.github.com> Co-authored-by: ChainYo Co-authored-by: hiromu <45531573+hiromu166@users.noreply.github.com> Co-authored-by: Cherdsak Kingkan Co-authored-by: Marcus Fraaß Co-authored-by: Jesper Dramsch Co-authored-by: amyeroberts Co-authored-by: Ash <103081562+ashwathim@users.noreply.github.com> Co-authored-by: Hamed Homaei Rad * Bump release (#147) * Bump release (#161) * Fix typos in chapter 9 (#176) (#180) Co-authored-by: regisss <15324346+regisss@users.noreply.github.com> * Bump release (#187) * Chapter 2 Section 1 Bengali Translation (huggingface#72) (#168) * [TH] Chapter 6 Section 1 and 2 (#171) Co-authored-by: Suteera * [FA] CH1 / P1-2 (#142) * Spanish Chapter 3: sections 1 & 2 (#162) * fix typos in bpe, wordpiece, unigram (#166) * [FR] French Review (#186) * Part 7: Training a causal... fixes (#179) * typo & error mitigation * consistency * Trainer.predict() returns 3 fields * ran make style * [TR] Translated Chapter 1.6 🤗 (#185) * added chapter 1/6 to _toctree.yml * [TR] Translated Chapter 1.6 🤗 Co-authored-by: Avishek Das Co-authored-by: Suteera Seeha <33692408+meanna@users.noreply.github.com> Co-authored-by: Suteera Co-authored-by: Saeed Choobani Co-authored-by: Fermin Ordaz Co-authored-by: Kerem Turgutlu Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Sebastian Sosa <37946988+CakeCrusher@users.noreply.github.com> Co-authored-by: tanersekmen <56790802+tanersekmen@users.noreply.github.com> * Bump release 10 (#194) * Bump release (#195) * Bump release 12 (#209) * Bump release (#224) * Bump release (#229) * Bump release (#236) * Bump release (#258) * Bump release (#270) * Bump release (#274) * Bump release (#286) * Bump release (#288) * Chapter 2 Section 1 Bengali Translation (huggingface#72) (#168) * [TH] Chapter 6 Section 1 and 2 (#171) Co-authored-by: Suteera * [FA] CH1 / P1-2 (#142) * Spanish Chapter 3: sections 1 & 2 (#162) * fix typos in bpe, wordpiece, unigram (#166) * [FR] French Review (#186) * Part 7: Training a causal... fixes (#179) * typo & error mitigation * consistency * Trainer.predict() returns 3 fields * ran make style * [TR] Translated Chapter 1.6 🤗 (#185) * added chapter 1/6 to _toctree.yml * [TR] Translated Chapter 1.6 🤗 * [PT][Chapter 01 - 2.mdx] - issue #51 (#170) * Fix Gradio ToC (#193) * Add Gradio authors and Blocks event (#189) * Update 6.mdx (#188) Correct link to Transformer XL doc * Add translating notes and glossary to Spanish (#192) * Add translating notes and glosary to Spanish * Adding glossary to the toc * add pt 4.3 (#191) * [FR] Visual corrections (#190) * [PT] add chapter 4.4 and 4.5 (#196) * fix invite discord link (#197) * [FA] Second draft of CH2/P1-2 (#139) * added chapter3 in hindi (#198) * [TR] Chapter 3/1 (#165) * [RU] Ch3-1/2/3 (#200) * [PT] add 5.1 and 5.2 (#204) * [FA] - Ch3 - P1 and P2 (#199) * [PT] add `end-of-chapter quiz` for chapter 4 (4.6) (#201) Co-authored-by: lewtun * Chapter1: 2.mdx Translated. (#206) * Remove comments from Persian ToC (#210) * Fix CI URL for PRs (#211) * code fragment & english syntax and meaning (#203) * Updated Ch1/1 with Emoji (#214) * Add missing numpy import (#217) * [ES] translate sections 8.1 and 8.2 (#215) * Fix path to datasets (#216) * [PT] add 5.3 (#218) * fix 4.3 (#223) * Fix notebook generation (#227) * Add Gradio nb links * add 5.4 (#226) * add pt wip (#225) * Added Gujarati List. (#221) * Add Gradio nbs links to fr (#228) * Chinese - Chapter 3finished (#219) * add ch7 at _toctree and translate 7.1 (#222) * add 5.5 (#235) * [FR] Review of chapter 7 (#233) * Italian translation - chapter 4 (#230) * Added Thai translation of chapters 3 (#231) * [Ru] Add part 2, chapter 2 (#234) * Update 8.mdx (#237) - Remove Gradio Blocks Party - Add, Where to next? section * Created HI/Chapter1/5.mdx (#232) * Add Spanish chaper3/section4, update toc and glossary (#238) * [RU] Chapter 3 finished (#239) * [PT] add 5.6 and 5.7 (#240) * [EN] Visual corrections (#245) * Translation for 1/4, 1/5 and 1/6. (#247) * add event in PT (#250) * Pin version of black (#252) * Translate ja event (#241) * [PT] add quiz chapter 5 (#243) * Update 5.mdx (#253) inconsistent naming with line 327 * Translation for Traditional Chinese (zh-tw) chapter0 (#251) Co-authored-by: Lewis Tunstall * Translated the whole Chapter 3 to Thai (#255) * Japanese chapter 4 (#244) * Translation of 1/7, 1/8, and 1/9. (#263) * [PT] add chapter 8.1 and 8.2 (#265) * [RU] Chapter 4 (#269) * Add Thai translation for chapter 6.3b to 6.10 (#268) * add 8.3 (#266) * 3.mdx of chapter 01 (#260) Co-authored-by: Lewis Tunstall * Fix typo (#271) * [PT] add chapter 6.1 (#273) * add Japanese chapter7 (#267) * replace `load_metric` with `evaluate.load` (#285) * update `load_metric` refs to `evaluate.load` Co-authored-by: lewtun * [GJ] Translation to Gujarati - Ch0 Setup (#287) * [PT] add chapter 6.2 and 6.3 (#279) * zh-CN - Chapter 4,5finished (#281) Co-authored-by: Lewis Tunstall * Chapter 01 - Done [PT] #51 (#280) Co-authored-by: Lewis Tunstall Co-authored-by: Avishek Das Co-authored-by: Suteera Seeha <33692408+meanna@users.noreply.github.com> Co-authored-by: Suteera Co-authored-by: Saeed Choobani Co-authored-by: Fermin Ordaz Co-authored-by: Kerem Turgutlu Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Sebastian Sosa <37946988+CakeCrusher@users.noreply.github.com> Co-authored-by: tanersekmen <56790802+tanersekmen@users.noreply.github.com> Co-authored-by: Victor Costa <54755870+victorescosta@users.noreply.github.com> Co-authored-by: Camille Couturier Co-authored-by: João Gustavo A. Amorim Co-authored-by: Bahram Shamshiri Co-authored-by: Kavya <36916536+robotjellyzone@users.noreply.github.com> Co-authored-by: Batuhan Ayhan Co-authored-by: Pavel <60391448+pdumin@users.noreply.github.com> Co-authored-by: Kambiz Ghoorchian Co-authored-by: Vedant Pandya Co-authored-by: Diego Vargas <91356068+dzarkV@users.noreply.github.com> Co-authored-by: Thomas O'Brien Co-authored-by: Lincoln V Schreiber Co-authored-by: 1375626371 <40328311+1375626371@users.noreply.github.com> Co-authored-by: Giorgio Severi Co-authored-by: svv73 <88366711+svv73@users.noreply.github.com> Co-authored-by: Ömer Faruk Özdemir Co-authored-by: Caterina Bonan <97481648+CaterinaBi@users.noreply.github.com> Co-authored-by: Hiromu Hota Co-authored-by: trtd56 <5toda6@gmail.com> Co-authored-by: Mehrdad Nezamdoost Co-authored-by: Wolvz Co-authored-by: a-krirk <56425947+a-krirk@users.noreply.github.com> Co-authored-by: atgctg <105969161+atgctg@users.noreply.github.com> Co-authored-by: Thiago Medeiros Co-authored-by: webbigdata-jp <87654083+webbigdata-jp@users.noreply.github.com> Co-authored-by: Leandro von Werra Co-authored-by: Bhadresh Savani * Bump release (#295) * Bump release (#296) * Bump release (#299) * Bump release (#305) * Chinese - Chapter 1 finished * Add zh to the languages field Add zh to the languages field in the build_documentation.yml and build_pr_documentation.yml files * Remove untranslated chapters in _toctree.yml Remove all these sections that haven't been translated yet Remove Chapter 0 from the table of contents since it hasn't been translated yet * Fixed an error in the translation format Fixed an error in the translation format of Chapter 1, Section 3 * Added a small part of the missing content * Fix style * Complete the translation of Chapters 0 and 2 * Fixed some bugs ·Fixed some formatting errors ·Moved Chapters 0 and 2 to Simplified Chinese * Add files via upload Formatting revisions and some translation corrections * run make style to format chapter1 session3 * run make style to format code * run make style to format code * Fix style * Chapter 2 Section 1 Bengali Translation (huggingface#72) (#168) * [TH] Chapter 6 Section 1 and 2 (#171) Co-authored-by: Suteera * [FA] CH1 / P1-2 (#142) * Spanish Chapter 3: sections 1 & 2 (#162) * fix typos in bpe, wordpiece, unigram (#166) * [FR] French Review (#186) * Part 7: Training a causal... fixes (#179) * typo & error mitigation * consistency * Trainer.predict() returns 3 fields * ran make style * [TR] Translated Chapter 1.6 🤗 (#185) * added chapter 1/6 to _toctree.yml * [TR] Translated Chapter 1.6 🤗 * [PT][Chapter 01 - 2.mdx] - issue #51 (#170) * Fix Gradio ToC (#193) * Add Gradio authors and Blocks event (#189) * Update 6.mdx (#188) Correct link to Transformer XL doc * Add translating notes and glossary to Spanish (#192) * Add translating notes and glosary to Spanish * Adding glossary to the toc * add pt 4.3 (#191) * [FR] Visual corrections (#190) * [PT] add chapter 4.4 and 4.5 (#196) * fix invite discord link (#197) * [FA] Second draft of CH2/P1-2 (#139) * added chapter3 in hindi (#198) * [TR] Chapter 3/1 (#165) * [RU] Ch3-1/2/3 (#200) * [PT] add 5.1 and 5.2 (#204) * Add placeholders for audio chapters (#208) * [FA] - Ch3 - P1 and P2 (#199) * [PT] add `end-of-chapter quiz` for chapter 4 (4.6) (#201) Co-authored-by: lewtun * Chapter1: 2.mdx Translated. (#206) * Remove comments from Persian ToC (#210) * Fix CI URL for PRs (#211) * code fragment & english syntax and meaning (#203) * Updated Ch1/1 with Emoji (#214) * Add missing numpy import (#217) * Updata chapter3 * Code format for chapter3 * Updata yml file of chapter3 * Uptata yml file of chapter3 * Fix yml file bug * [ES] translate sections 8.1 and 8.2 (#215) * Fix path to datasets (#216) * [PT] add 5.3 (#218) * fix 4.3 (#223) * Run make style * Fix notebook generation (#227) * Add Gradio nb links * add 5.4 (#226) * add pt wip (#225) * Added Gujarati List. (#221) * Fix quality * Add Gradio nbs links to fr (#228) * Fix ToC tree * Remove audio templates * Fix fr section * Fix fr chapter * Chinese - Chapter 3finished (#219) * add ch7 at _toctree and translate 7.1 (#222) * add 5.5 (#235) * [FR] Review of chapter 7 (#233) * Italian translation - chapter 4 (#230) * Added Thai translation of chapters 3 (#231) * [Ru] Add part 2, chapter 2 (#234) * Update 8.mdx (#237) - Remove Gradio Blocks Party - Add, Where to next? section * Created HI/Chapter1/5.mdx (#232) * Add Spanish chaper3/section4, update toc and glossary (#238) * [RU] Chapter 3 finished (#239) * [PT] add 5.6 and 5.7 (#240) * [EN] Visual corrections (#245) * Translation for 1/4, 1/5 and 1/6. (#247) * add event in PT (#250) * Pin version of black (#252) * Translate ja event (#241) * [PT] add quiz chapter 5 (#243) * Update 5.mdx (#253) inconsistent naming with line 327 * Translation for Traditional Chinese (zh-tw) chapter0 (#251) Co-authored-by: Lewis Tunstall * Translated the whole Chapter 3 to Thai (#255) * Japanese chapter 4 (#244) * Translation of 1/7, 1/8, and 1/9. (#263) * [PT] add chapter 8.1 and 8.2 (#265) * [RU] Chapter 4 (#269) * Add Thai translation for chapter 6.3b to 6.10 (#268) * add 8.3 (#266) * 3.mdx of chapter 01 (#260) Co-authored-by: Lewis Tunstall * Fix typo (#271) * [PT] add chapter 6.1 (#273) * add Japanese chapter7 (#267) * zh-CN - Chapter 4,5finished * replace `load_metric` with `evaluate.load` (#285) * update `load_metric` refs to `evaluate.load` Co-authored-by: lewtun * [GJ] Translation to Gujarati - Ch0 Setup (#287) * [PT] add chapter 6.2 and 6.3 (#279) * Fix formatting * Debug formatting * Debug FR formatting * zh-CN - Chapter 4,5finished (#281) Co-authored-by: Lewis Tunstall * Chapter 01 - Done [PT] #51 (#280) Co-authored-by: Lewis Tunstall * tf_default_data_collator seems to have moved * zh-CN - Chapter 6finished * Revert "Merge branch 'huggingface:main' into main" This reverts commit aebb46e12f9f87a4303f8bb4f0f2cf545eb83b21, reversing changes made to 69187a3789e8d3d2d0de821ebe495f111d1cc73d. * Revert "zh-CN - Chapter 6finished" This reverts commit e69fce28d3a7b35b76c4f768a6cedf295b37d8c9. * zh-CN - Chapter 6finished * fix style * undo bad commit * Chapter5it (#278) * added the italian translation for unit 1 chapter5 Co-authored-by: Leandro von Werra * Vietnamese translation (#293) * Update .github/workflows/build_pr_documentation.yml Co-authored-by: lewtun * Translate JP chapter 8 (#249) * Italian translation - Chapter 8 (#272) * Translation to Vietnamese - chapter 5 (#297) * Add course contributors (#298) * Add CourseFloatingBanner component * DocNotebookDropdown -> CourseFloatingBanner * Italian translation Ch 2/1, 2/2 (#300) * Add contributors (#304) * Add forum button (#306) Co-authored-by: 1375626371 <40328311+1375626371@users.noreply.github.com> Co-authored-by: 1375626371 <1375626371@qq.com> Co-authored-by: Avishek Das Co-authored-by: Suteera Seeha <33692408+meanna@users.noreply.github.com> Co-authored-by: Suteera Co-authored-by: Saeed Choobani Co-authored-by: Fermin Ordaz Co-authored-by: Kerem Turgutlu Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Sebastian Sosa <37946988+CakeCrusher@users.noreply.github.com> Co-authored-by: tanersekmen <56790802+tanersekmen@users.noreply.github.com> Co-authored-by: Victor Costa <54755870+victorescosta@users.noreply.github.com> Co-authored-by: Camille Couturier Co-authored-by: João Gustavo A. Amorim Co-authored-by: Bahram Shamshiri Co-authored-by: Kavya <36916536+robotjellyzone@users.noreply.github.com> Co-authored-by: Batuhan Ayhan Co-authored-by: Pavel <60391448+pdumin@users.noreply.github.com> Co-authored-by: Kambiz Ghoorchian Co-authored-by: Vedant Pandya Co-authored-by: Diego Vargas <91356068+dzarkV@users.noreply.github.com> Co-authored-by: Thomas O'Brien Co-authored-by: Lincoln V Schreiber Co-authored-by: Giorgio Severi Co-authored-by: svv73 <88366711+svv73@users.noreply.github.com> Co-authored-by: Ömer Faruk Özdemir Co-authored-by: Caterina Bonan <97481648+CaterinaBi@users.noreply.github.com> Co-authored-by: Hiromu Hota Co-authored-by: trtd56 <5toda6@gmail.com> Co-authored-by: Mehrdad Nezamdoost Co-authored-by: Wolvz Co-authored-by: a-krirk <56425947+a-krirk@users.noreply.github.com> Co-authored-by: atgctg <105969161+atgctg@users.noreply.github.com> Co-authored-by: Thiago Medeiros Co-authored-by: webbigdata-jp <87654083+webbigdata-jp@users.noreply.github.com> Co-authored-by: Leandro von Werra Co-authored-by: Bhadresh Savani Co-authored-by: Andreas Ehrencrona Co-authored-by: leandro Co-authored-by: Matt Co-authored-by: Nolanogenn <52080100+Nolanogenn@users.noreply.github.com> Co-authored-by: Hồng Hạnh Co-authored-by: Younes Belkada <49240599+younesbelkada@users.noreply.github.com> Co-authored-by: Edoardo Abati <29585319+EdAbati@users.noreply.github.com> Co-authored-by: Mishig Davaadorj Co-authored-by: Acciaro Gennaro Daniele * Bump release (#307) * Bump release (#308) * Bump release (#314) * Bump release (#320) * Bump release (#328) * Bump release (#333) * Bump release (#335) * Bump release (#343) * Bump release (#355) * Bump release (#358) * Bump release (#371) * Bump release (#381) * Bump release (#387) * Bump release (#404) * Bump release (#413) * Bump release (#426) * Bump release (#463) --------- Co-authored-by: DOOHAE JUNG Co-authored-by: m_khandaker Co-authored-by: Md. Al-Amin Khandaker Co-authored-by: ftarlaci <18291571+ftarlaci@users.noreply.github.com> Co-authored-by: Doohae Jung <80743307+Doohae@users.noreply.github.com> Co-authored-by: melaniedrevet Co-authored-by: Jose M Munoz Co-authored-by: svv73 <88366711+svv73@users.noreply.github.com> Co-authored-by: Vedant Pandya Co-authored-by: Bahram Shamshiri Co-authored-by: Giyaseddin Bayrak <34009210+giyaseddin@users.noreply.github.com> Co-authored-by: Pavel <60391448+pdumin@users.noreply.github.com> Co-authored-by: 1375626371 <40328311+1375626371@users.noreply.github.com> Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> Co-authored-by: João Gustavo A. Amorim Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Cherdsak Kingkan Co-authored-by: Thomas Chaigneau <50595514+ChainYo@users.noreply.github.com> Co-authored-by: ChainYo Co-authored-by: hiromu <45531573+hiromu166@users.noreply.github.com> Co-authored-by: Cherdsak Kingkan Co-authored-by: Marcus Fraaß Co-authored-by: Jesper Dramsch Co-authored-by: amyeroberts Co-authored-by: Ash <103081562+ashwathim@users.noreply.github.com> Co-authored-by: Hamed Homaei Rad Co-authored-by: Dawood Khan Co-authored-by: regisss <15324346+regisss@users.noreply.github.com> Co-authored-by: Avishek Das Co-authored-by: Suteera Seeha <33692408+meanna@users.noreply.github.com> Co-authored-by: Suteera Co-authored-by: Saeed Choobani Co-authored-by: Fermin Ordaz Co-authored-by: Kerem Turgutlu Co-authored-by: Sebastian Sosa <37946988+CakeCrusher@users.noreply.github.com> Co-authored-by: tanersekmen <56790802+tanersekmen@users.noreply.github.com> Co-authored-by: Victor Costa <54755870+victorescosta@users.noreply.github.com> Co-authored-by: Camille Couturier Co-authored-by: Kavya <36916536+robotjellyzone@users.noreply.github.com> Co-authored-by: Batuhan Ayhan Co-authored-by: Kambiz Ghoorchian Co-authored-by: Diego Vargas <91356068+dzarkV@users.noreply.github.com> Co-authored-by: Thomas O'Brien Co-authored-by: Lincoln V Schreiber Co-authored-by: Giorgio Severi Co-authored-by: Ömer Faruk Özdemir Co-authored-by: Caterina Bonan <97481648+CaterinaBi@users.noreply.github.com> Co-authored-by: Hiromu Hota Co-authored-by: trtd56 <5toda6@gmail.com> Co-authored-by: Mehrdad Nezamdoost Co-authored-by: Wolvz Co-authored-by: a-krirk <56425947+a-krirk@users.noreply.github.com> Co-authored-by: atgctg <105969161+atgctg@users.noreply.github.com> Co-authored-by: Thiago Medeiros Co-authored-by: webbigdata-jp <87654083+webbigdata-jp@users.noreply.github.com> Co-authored-by: Leandro von Werra Co-authored-by: Bhadresh Savani Co-authored-by: 1375626371 <1375626371@qq.com> Co-authored-by: Andreas Ehrencrona Co-authored-by: leandro Co-authored-by: Matt Co-authored-by: Nolanogenn <52080100+Nolanogenn@users.noreply.github.com> Co-authored-by: Hồng Hạnh Co-authored-by: Younes Belkada <49240599+younesbelkada@users.noreply.github.com> Co-authored-by: Edoardo Abati <29585319+EdAbati@users.noreply.github.com> Co-authored-by: Mishig Davaadorj Co-authored-by: Acciaro Gennaro Daniele * Revert "Bump release (#566)" (#567) This reverts commit cccc2c91ac8e702e5e14bbb0419dbf0490c7aaaf. * updated documentation links * [doc build] Use secrets (#581) * docs: fix broken links * changed 'perspires' to 'persists' in chapter 1 quiz solves issue #585 * Update 4.mdx You forgot to write a return for this function. * Update 4.mdx : Fix Typo Should be "course" * fix link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Fix syntax in vi/chapter7/7.mdx There was an unnecessary `` * Remove `get_lr()` from logs which refers to nonexistent function `get_lr()` is called as part of this function, but the function is not declared anywhere in the script. This change removes this portion of the code since it is non-necessary. * Update 4.mdx removed judgmental argument * Update en-version * fix: remove useless token * fix: remove useless token (#635) * Translate Chapter 3 to Spanish (#510) * translate Chapter 3 to Spanish * translate code comments to Spanish and fix typos * Translating Chapter 6 to Spanish (#523) * Translating sections 1 and 2 to spanish * Translating sections 3 to spanish * Translating sections 3b to spanish * Translating sections 4 to spanish * Translating section 5 to spanish * Translating section 6 to spanish * Translating section 7 to spanish * Translating section 8 to spanish * Translating section 9 to spanish * Translating section 10 to spanish * Adding Sections to _toctree.yml * Fixing Typos after second review --------- Co-authored-by: datacubeR * Update 5.mdx Ajuste na tradução de "encoders". São "codificadores", não "decodificadores". Decoders são "decodificadores". * Update doc CI (#643) * Фиксация текущих результатов. * Фиксирую текущее состояние. * Fixing the transfer results for today. * Translated files 3b and partially 4. Fixing the result. * Fixing today's translation. * fix typos in Spanish translation (#511) * Fixing today's translation. Files: 6.mdx, 7.mdx and half of 8.mdx. * The translation of chapter 6 has been completed. * Delete chapters/en/.ipynb_checkpoints/_toctree-checkpoint.yml This is backup created by JupyterLab. * Delete chapters/en/chapter5/.ipynb_checkpoints/8-checkpoint.mdx This is backup created by JupyterLab. * Delete chapters/en/chapter6/.ipynb_checkpoints/1-checkpoint.mdx This is backup created by JupyterLab. * Delete chapters/en/chapter6/.ipynb_checkpoints/2-checkpoint.mdx This is backup created by JupyterLab. * Delete chapters/en/chapter6/.ipynb_checkpoints/8-checkpoint.mdx This is backup created by JupyterLab. * Delete chapters/en/chapter6/.ipynb_checkpoints/9-checkpoint.mdx This is backup created by JupyterLab. * Delete chapters/ru/.ipynb_checkpoints/TRANSLATING-checkpoint.txt This is backup created by JupyterLab. * Delete chapters/ru/.ipynb_checkpoints/_toctree-checkpoint.yml This is backup created by JupyterLab. * Delete chapters/ru/chapter5/.ipynb_checkpoints/8-checkpoint.mdx This is backup created by JupyterLab. * Update 10.mdx Minor fix. * Update 10.mdx Trying to solve the markup problem. * Update 10.mdx Correcting the syntax of some markup again) * Update chapters/ru/chapter6/4.mdx Yes, that space is redundant here. You're right about that. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter6/4.mdx Extra space. I overlooked it. My mistake. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter6/3.mdx There's an extra space here. You're right. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter6/3.mdx There's an extra space here. You're right. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter6/3b.mdx Yeah, there's no need for a space here. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter6/3.mdx Co-authored-by: Maria Khalusova * Update 3.mdx * Update 7.mdx Translated the comments noted on the review. * Update 3.mdx Translated the missing comments in the code. * Update chapters/ru/chapter6/3b.mdx Yes, an extra space. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter6/5.mdx Minor fix. Co-authored-by: Maria Khalusova * Completed the translation of the first part of Chapter 7 into Russian. * After run python utils/code_formatter.py * Update chapters/ru/chapter7/1.mdx Extra space. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/2.mdx Extra space. I didn't notice. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/2.mdx Extra space. I didn't notice. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/2.mdx Yes, indeed, I ate the space bar))))) Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/5.mdx There's that extra space again. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/5.mdx There's that extra space again that I didn't notice. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/5.mdx Extra space. Co-authored-by: Maria Khalusova * Update 5.mdx Translated the missing comment. * Update chapters/ru/chapter7/4.mdx Extra space. Co-authored-by: Maria Khalusova * Update 2.mdx Translated the missing comment in the code * Update 2.mdx Translated the missing sentence. * Update 3.mdx Translated the missing sentence. * Update 3.mdx I agree, it sounds more neutral that way. * Update chapters/ru/chapter7/3.mdx An unnecessary parenthesis. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/3.mdx Also an option, but we've translated it as "карточка модели" a lot of places. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/3.mdx Extra space. Co-authored-by: Maria Khalusova * Update 3.mdx Translated the missing comment in the code. * Update chapters/ru/chapter7/3.mdx Extra sapce. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/4.mdx Extra space. Co-authored-by: Maria Khalusova * Update 4.mdx Translated the missing comment in the code. * Update 5.mdx Added and translated the missing sentence: "Since the collator expects a list of dicts, where each dict represents a single example in the dataset, we also need to wrangle the data into the expected format before passing it to the data collator:" * Update 5.mdx Edit the display of the table on the course page. * fixed links to other chapters * fixed links to chapters' intros * I added myself to the Languages and translations table. * Deleted unnecessary folder automatically created by JupyterLab. * Fix links to HF docs * Finalizing the translation of chapter 7. * Update 6.mdx Extra space * Update 7.mdx Extra space * Update chapters/ru/chapter7/6.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/6.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/6.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/7.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/6.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/7.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/7.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/8.mdx Correction of abbreviation - NLP Co-authored-by: Maria Khalusova * Update 7.mdx Translated the code commentary * Update 6.mdx Translated the missing sentence. * Update chapters/ru/chapter7/7.mdx Co-authored-by: Maria Khalusova * Update 6.mdx * Update chapters/ru/chapter7/6.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/7.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/6.mdx Co-authored-by: Maria Khalusova * 8/1-2 done * 8/3 finished * 8/4 finished * fix typo * toc update * typos fixed * removed english text * 8/5 finished * 8/6-7 finished * fix and update toc * chapter8/1 fixed * chapter8/2 fixed * chapter8/3 fixed * chapter8/4 fixed * chapter8/5 fixed * fix title 8/5 * fix title 8/5 in toc * Update _toctree.yml title 8 Co-authored-by: Maria Khalusova * Bump black (#671) * fix unexpected token in quiz * 8/2 fixed * 8/3 fixed * 8/4_tf fixed * Added translation of chapter 9 and Course Events. * Added translation of chapter 9 and Course Events. * Update chapters/ru/chapter9/6.mdx OK Co-authored-by: Maria Khalusova * Update chapters/ru/chapter9/7.mdx OK Co-authored-by: Maria Khalusova * Update chapters/ru/chapter9/7.mdx OK Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx I agree. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Sorry. I was hasty and made an incorrect assumption))) Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova --------- Co-authored-by: gxy-gxy <1115404657@qq.com> Co-authored-by: Qi Zhang Co-authored-by: iLeGend <824040212@qq.com> Co-authored-by: sj Co-authored-by: Sureshkumar Thangavel <108256+tsureshkumar@users.noreply.github.com> Co-authored-by: Andrei Shirobokov Co-authored-by: Tiezhen WANG <38108242+xianbaoqian@users.noreply.github.com> Co-authored-by: Pranav <66965591+Pranav-Bobde@users.noreply.github.com> Co-authored-by: Maria Khalusova Co-authored-by: DOOHAE JUNG Co-authored-by: m_khandaker Co-authored-by: Md. Al-Amin Khandaker Co-authored-by: ftarlaci <18291571+ftarlaci@users.noreply.github.com> Co-authored-by: Doohae Jung <80743307+Doohae@users.noreply.github.com> Co-authored-by: melaniedrevet Co-authored-by: Jose M Munoz Co-authored-by: svv73 <88366711+svv73@users.noreply.github.com> Co-authored-by: Vedant Pandya Co-authored-by: Bahram Shamshiri Co-authored-by: Giyaseddin Bayrak <34009210+giyaseddin@users.noreply.github.com> Co-authored-by: Pavel <60391448+pdumin@users.noreply.github.com> Co-authored-by: 1375626371 <40328311+1375626371@users.noreply.github.com> Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> Co-authored-by: João Gustavo A. Amorim Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Cherdsak Kingkan Co-authored-by: Thomas Chaigneau <50595514+ChainYo@users.noreply.github.com> Co-authored-by: ChainYo Co-authored-by: hiromu <45531573+hiromu166@users.noreply.github.com> Co-authored-by: Cherdsak Kingkan Co-authored-by: Marcus Fraaß Co-authored-by: Jesper Dramsch Co-authored-by: amyeroberts Co-authored-by: Ash <103081562+ashwathim@users.noreply.github.com> Co-authored-by: Hamed Homaei Rad Co-authored-by: Dawood Khan Co-authored-by: regisss <15324346+regisss@users.noreply.github.com> Co-authored-by: Avishek Das Co-authored-by: Suteera Seeha <33692408+meanna@users.noreply.github.com> Co-authored-by: Suteera Co-authored-by: Saeed Choobani Co-authored-by: Fermin Ordaz Co-authored-by: Kerem Turgutlu Co-authored-by: Sebastian Sosa <37946988+CakeCrusher@users.noreply.github.com> Co-authored-by: tanersekmen <56790802+tanersekmen@users.noreply.github.com> Co-authored-by: Victor Costa <54755870+victorescosta@users.noreply.github.com> Co-authored-by: Camille Couturier Co-authored-by: Kavya <36916536+robotjellyzone@users.noreply.github.com> Co-authored-by: Batuhan Ayhan Co-authored-by: Kambiz Ghoorchian Co-authored-by: Diego Vargas <91356068+dzarkV@users.noreply.github.com> Co-authored-by: Thomas O'Brien Co-authored-by: Lincoln V Schreiber Co-authored-by: Giorgio Severi Co-authored-by: Ömer Faruk Özdemir Co-authored-by: Caterina Bonan <97481648+CaterinaBi@users.noreply.github.com> Co-authored-by: Hiromu Hota Co-authored-by: trtd56 <5toda6@gmail.com> Co-authored-by: Mehrdad Nezamdoost Co-authored-by: Wolvz Co-authored-by: a-krirk <56425947+a-krirk@users.noreply.github.com> Co-authored-by: atgctg <105969161+atgctg@users.noreply.github.com> Co-authored-by: Thiago Medeiros Co-authored-by: webbigdata-jp <87654083+webbigdata-jp@users.noreply.github.com> Co-authored-by: Leandro von Werra Co-authored-by: Bhadresh Savani Co-authored-by: 1375626371 <1375626371@qq.com> Co-authored-by: Andreas Ehrencrona Co-authored-by: leandro Co-authored-by: Matt Co-authored-by: Nolanogenn <52080100+Nolanogenn@users.noreply.github.com> Co-authored-by: Hồng Hạnh Co-authored-by: Younes Belkada <49240599+younesbelkada@users.noreply.github.com> Co-authored-by: Edoardo Abati <29585319+EdAbati@users.noreply.github.com> Co-authored-by: Mishig Davaadorj Co-authored-by: Acciaro Gennaro Daniele Co-authored-by: nnoboa Co-authored-by: Vipula Sandaruwan Dissanayake Co-authored-by: Alex Bzdel Co-authored-by: JieShen <49408146+JieShenAI@users.noreply.github.com> Co-authored-by: Hardik Bhadani Co-authored-by: Omar Sanseviero Co-authored-by: Suket Kamboj <82956207+Sookeyy-12@users.noreply.github.com> Co-authored-by: Brad Windsor Co-authored-by: Pierre Alexandre SCHEMBRI Co-authored-by: Remy Co-authored-by: María Grandury <57645283+mariagrandury@users.noreply.github.com> Co-authored-by: Alfonso Tobar-Arancibia <48638337+datacubeR@users.noreply.github.com> Co-authored-by: datacubeR Co-authored-by: Alysson <50303964+k3ybladewielder@users.noreply.github.com> Co-authored-by: Merve Noyan Co-authored-by: Artyom Boyko Co-authored-by: mariosasko Co-authored-by: Pavel --- chapters/ru/_toctree.yml | 54 +++ chapters/ru/chapter5/3.mdx | 2 - chapters/ru/chapter8/1.mdx | 17 + chapters/ru/chapter8/2.mdx | 364 ++++++++++++++++ chapters/ru/chapter8/3.mdx | 164 +++++++ chapters/ru/chapter8/4.mdx | 791 ++++++++++++++++++++++++++++++++++ chapters/ru/chapter8/4_tf.mdx | 486 +++++++++++++++++++++ chapters/ru/chapter8/5.mdx | 92 ++++ chapters/ru/chapter8/6.mdx | 12 + chapters/ru/chapter8/7.mdx | 204 +++++++++ chapters/ru/chapter9/1.mdx | 37 ++ chapters/ru/chapter9/2.mdx | 118 +++++ chapters/ru/chapter9/3.mdx | 186 ++++++++ chapters/ru/chapter9/4.mdx | 147 +++++++ chapters/ru/chapter9/5.mdx | 67 +++ chapters/ru/chapter9/6.mdx | 101 +++++ chapters/ru/chapter9/7.mdx | 239 ++++++++++ chapters/ru/chapter9/8.mdx | 24 ++ chapters/ru/chapter9/9.mdx | 239 ++++++++++ chapters/ru/events/1.mdx | 34 ++ chapters/ru/events/2.mdx | 135 ++++++ chapters/ru/events/3.mdx | 9 + requirements.txt | 2 +- 23 files changed, 3521 insertions(+), 3 deletions(-) create mode 100644 chapters/ru/chapter8/1.mdx create mode 100644 chapters/ru/chapter8/2.mdx create mode 100644 chapters/ru/chapter8/3.mdx create mode 100644 chapters/ru/chapter8/4.mdx create mode 100644 chapters/ru/chapter8/4_tf.mdx create mode 100644 chapters/ru/chapter8/5.mdx create mode 100644 chapters/ru/chapter8/6.mdx create mode 100644 chapters/ru/chapter8/7.mdx create mode 100644 chapters/ru/chapter9/1.mdx create mode 100644 chapters/ru/chapter9/2.mdx create mode 100644 chapters/ru/chapter9/3.mdx create mode 100644 chapters/ru/chapter9/4.mdx create mode 100644 chapters/ru/chapter9/5.mdx create mode 100644 chapters/ru/chapter9/6.mdx create mode 100644 chapters/ru/chapter9/7.mdx create mode 100644 chapters/ru/chapter9/8.mdx create mode 100644 chapters/ru/chapter9/9.mdx create mode 100644 chapters/ru/events/1.mdx create mode 100644 chapters/ru/events/2.mdx create mode 100644 chapters/ru/events/3.mdx diff --git a/chapters/ru/_toctree.yml b/chapters/ru/_toctree.yml index 0ec4ac451..575ac4556 100644 --- a/chapters/ru/_toctree.yml +++ b/chapters/ru/_toctree.yml @@ -135,6 +135,60 @@ 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 diff --git a/chapters/ru/chapter5/3.mdx b/chapters/ru/chapter5/3.mdx index d5c232d63..49b35ca2c 100644 --- a/chapters/ru/chapter5/3.mdx +++ b/chapters/ru/chapter5/3.mdx @@ -387,8 +387,6 @@ ArrowInvalid: Column 1 named condition expected length 1463 but got length 1000 О, нет! Не сработало! Почему? Посмотрим на ошибку: несовпадение в длинах, один из которых длиной 1463, а другой – 1000. Если вы обратитесь в [документацию](https://huggingface.co/docs/datasets/package_reference/main_classes#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#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. - Проблема заключается в том, что мы пытаемся смешать два разных датасета разной размерности: число колонок датасета `drug_dataset` равняется 1000, а нужный нам `tokenized_dataset` имеет 1463 колонки. Чтобы избежать этой ошибки, необходимо удалить несколько столбцов из старого датасета и сделать оба датасета одинакового размера. Мы можем достичь этого с помощью аргумента `remove_columns`: ```py 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/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 From 40c51fbe3ae184e0771a35aa00f8197ce14e5a77 Mon Sep 17 00:00:00 2001 From: lewtun Date: Thu, 15 Feb 2024 14:17:08 +0100 Subject: [PATCH 50/51] Bump release (#682) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: rephrase text to improve clarity and specificity In context to "training with a dataset specific to your task" and "train directly for the final task", I was not able to infer easily that "directly" here implies training from scratch. * Demo link fixes (#562) * demo link fixes * minor demo fix * Bump release (#566) * Add note about `remove_unused_columns` for whole word masking * Merge pull request #24 from huggingface/fix-typo Fix typo * Merge pull request #26 from huggingface/fix-qa-offsets Fix inequalities in answer spans for QA chapter * Merge pull request #30 from huggingface/fix-modelcard-url Update model card URL * Merge pull request #69 from yulonglin/patch-1 Correct typo mixing up space and newline symbols * Bump release (#99) * Bump release * Update author list Co-authored-by: DOOHAE JUNG Co-authored-by: m_khandaker Co-authored-by: Md. Al-Amin Khandaker Co-authored-by: ftarlaci <18291571+ftarlaci@users.noreply.github.com> Co-authored-by: Doohae Jung <80743307+Doohae@users.noreply.github.com> Co-authored-by: melaniedrevet * Bump release (#115) * ko-chapter1/1 * ko _toctree.yml created * Fix the issue #80 * Single expression changed * ko/chapter1 finished * ko/chapter0 finished * ko/chapter0 finished * reviewed by @bzantium ko/chapter0 * reviewed by @bzantium chapter0 & fixed typo * reviewed by @rainmaker712 * maximize Korean expressions * [Chapter 1] bangla traslation initial commit * Update 1.mdx update and fix formating * Fix formating and typos * translate _toctree.yml 0-1 chapter * Add Korean to CI * [tr] Translated chapter1/2.mdx * remove translation from sec titles not yet translated * Add authors [th ru] * [FIX] _toctree.yml * Update chapters/bn/chapter0/1.mdx [FIX] syntax formatting Co-authored-by: lewtun * tag typos & indentation & unnatural expressions * modified toctree.yml for chapter1/2 * modified toctree.yml for chapter1/2 & fix typo * French Translation - Chapter 5 * Add Bengali to CI * Update author list * Adding translations for 2/4 and 2/5 🚀 (#74) * Adding translations for 2/4 and 2/5 🚀 * Remove English content Co-authored-by: lewtun * Translation to Russian (#97) * translation of chapter 2/section 1 * add section 1 / chapter 2 to _toctree.yml * Translation of Chapter0 to Hindi (#86) * Hindi?Chapter0-Part_1 * Hindi/Chapter0-Part_2 * Chapter 0 Persian Translation First Draft (#95) * merged branch0 into main. no toctree yet. * updated toctree. * Updated the glossary with terms from chapter0. * Second draft in collab w/ @schoobani. Added empty chapter1 for preview. * Glossary typo fix. * Translation of Chapter0 (setup) to Arabic (#104) * Add AR translation for `introduction` * Fix alignment & format * Add Arabic to CI build * Russian - Chapter 1 finished (#98) * 01/4 start * 1/4 finished * 1/5 finished * 1/5 update toc * 1/6 finished * 1/7 finished * 1/8 finished * 1/9 finished * 1/4 fix * toc update * Chinese - Chapter 1 finished (#113) * Chinese - Chapter 1 finished * Add zh to the languages field Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> * [PT] Translation of chapter 2 (#107) * add PT translate to 2.1 * add PT translate to 2.2 * add portuguese translation to 2.3 * WIP portuguese translation to 2.4 * add portuguese translation to 2.4 * add portuguese translation to 2.5 * add portuguese translation to 2.6 * add _toctree infos Co-authored-by: lewtun * [FR] Translation of chapter 2 & event + Review of chapters 0 & 5 (#106) * Update _toctree.yml Add chapter 2 + little fix of chapter 5 * Update 1.mdx Review of chapter 0 * Create 1.mdx * Create 2.mdx * Create 3.mdx * Create 4.mdx * Create 5.mdx * Create 6.mdx * Create 7.mdx * Create 8.mdx * Update 8.mdx Since AutoNLP has recently been renamed to AutoTrain, let me make the correction on the English file * Update 1.mdx Review of chapter 5/1 * Update 2.mdx Review of chapter 5/2 * Update 3.mdx Review of chapter 5/3 * Update 4.mdx Review of chapter 5/4 * Update 5.mdx Review of chapter 5/5 * Update 6.mdx Review of chapter 5/6 * Update 7.mdx Review of chapter 5/7 * Update 8.mdx Review of chapter 5/8 * Create 1.mdx event's translation * Update _toctree.yml add event to the tree * Update _toctree.yml deletion of the files that pose a problem to pass the checks, will be resubmitted in another PR * Delete 1.mdx deletion of the files that pose a problem to pass the checks, will be resubmitted in another PR * make style correction * Update _toctree.yml the - * Fix spacing Co-authored-by: Lewis Tunstall * [th] Translated Chapter2/1 (#83) * Finish chapter2/1 * Update _toctree.yml * ko-chapter1/1 * ko _toctree.yml created * Fix the issue #80 * Single expression changed * ko/chapter1 finished * ko/chapter0 finished * ko/chapter0 finished * reviewed by @bzantium ko/chapter0 * reviewed by @bzantium chapter0 & fixed typo * reviewed by @rainmaker712 * maximize Korean expressions * [Chapter 1] bangla traslation initial commit * Update 1.mdx update and fix formating * Fix formating and typos * translate _toctree.yml 0-1 chapter * Add Korean to CI * remove translation from sec titles not yet translated * Add authors [th ru] * [FIX] _toctree.yml * Update chapters/bn/chapter0/1.mdx [FIX] syntax formatting Co-authored-by: lewtun * tag typos & indentation & unnatural expressions * modified toctree.yml for chapter1/2 * modified toctree.yml for chapter1/2 & fix typo * Add Bengali to CI * Update author list * Adding translations for 2/4 and 2/5 🚀 (#74) * Adding translations for 2/4 and 2/5 🚀 * Remove English content Co-authored-by: lewtun * Translation to Russian (#97) * translation of chapter 2/section 1 * add section 1 / chapter 2 to _toctree.yml * Translation of Chapter0 to Hindi (#86) * Hindi?Chapter0-Part_1 * Hindi/Chapter0-Part_2 * Chapter 0 Persian Translation First Draft (#95) * merged branch0 into main. no toctree yet. * updated toctree. * Updated the glossary with terms from chapter0. * Second draft in collab w/ @schoobani. Added empty chapter1 for preview. * Glossary typo fix. * Translation of Chapter0 (setup) to Arabic (#104) * Add AR translation for `introduction` * Fix alignment & format * Add Arabic to CI build * Russian - Chapter 1 finished (#98) * 01/4 start * 1/4 finished * 1/5 finished * 1/5 update toc * 1/6 finished * 1/7 finished * 1/8 finished * 1/9 finished * 1/4 fix * toc update * Chinese - Chapter 1 finished (#113) * Chinese - Chapter 1 finished * Add zh to the languages field Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> * [PT] Translation of chapter 2 (#107) * add PT translate to 2.1 * add PT translate to 2.2 * add portuguese translation to 2.3 * WIP portuguese translation to 2.4 * add portuguese translation to 2.4 * add portuguese translation to 2.5 * add portuguese translation to 2.6 * add _toctree infos Co-authored-by: lewtun * [FR] Translation of chapter 2 & event + Review of chapters 0 & 5 (#106) * Update _toctree.yml Add chapter 2 + little fix of chapter 5 * Update 1.mdx Review of chapter 0 * Create 1.mdx * Create 2.mdx * Create 3.mdx * Create 4.mdx * Create 5.mdx * Create 6.mdx * Create 7.mdx * Create 8.mdx * Update 8.mdx Since AutoNLP has recently been renamed to AutoTrain, let me make the correction on the English file * Update 1.mdx Review of chapter 5/1 * Update 2.mdx Review of chapter 5/2 * Update 3.mdx Review of chapter 5/3 * Update 4.mdx Review of chapter 5/4 * Update 5.mdx Review of chapter 5/5 * Update 6.mdx Review of chapter 5/6 * Update 7.mdx Review of chapter 5/7 * Update 8.mdx Review of chapter 5/8 * Create 1.mdx event's translation * Update _toctree.yml add event to the tree * Update _toctree.yml deletion of the files that pose a problem to pass the checks, will be resubmitted in another PR * Delete 1.mdx deletion of the files that pose a problem to pass the checks, will be resubmitted in another PR * make style correction * Update _toctree.yml the - * Fix spacing Co-authored-by: Lewis Tunstall * [th] Translated Chapter2/1 (#83) * Finish chapter2/1 * Update _toctree.yml * Add Hindi to CI (#116) Co-authored-by: DOOHAE JUNG Co-authored-by: m_khandaker Co-authored-by: Md. Al-Amin Khandaker Co-authored-by: ftarlaci <18291571+ftarlaci@users.noreply.github.com> Co-authored-by: Doohae Jung <80743307+Doohae@users.noreply.github.com> Co-authored-by: melaniedrevet Co-authored-by: Jose M Munoz Co-authored-by: svv73 <88366711+svv73@users.noreply.github.com> Co-authored-by: Vedant Pandya Co-authored-by: Bahram Shamshiri Co-authored-by: Giyaseddin Bayrak <34009210+giyaseddin@users.noreply.github.com> Co-authored-by: Pavel <60391448+pdumin@users.noreply.github.com> Co-authored-by: 1375626371 <40328311+1375626371@users.noreply.github.com> Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> Co-authored-by: João Gustavo A. Amorim Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Cherdsak Kingkan * Bump release 4 (#133) * Bump release (#138) * ko-chapter1/1 * ko _toctree.yml created * Fix the issue #80 * Single expression changed * ko/chapter1 finished * ko/chapter0 finished * ko/chapter0 finished * reviewed by @bzantium ko/chapter0 * reviewed by @bzantium chapter0 & fixed typo * reviewed by @rainmaker712 * maximize Korean expressions * [Chapter 1] bangla traslation initial commit * Update 1.mdx update and fix formating * Fix formating and typos * translate _toctree.yml 0-1 chapter * Add Korean to CI * [tr] Translated chapter1/2.mdx * remove translation from sec titles not yet translated * Add authors [th ru] * [FIX] _toctree.yml * Update chapters/bn/chapter0/1.mdx [FIX] syntax formatting Co-authored-by: lewtun * tag typos & indentation & unnatural expressions * modified toctree.yml for chapter1/2 * modified toctree.yml for chapter1/2 & fix typo * French Translation - Chapter 5 * Add Bengali to CI * Update author list * Adding translations for 2/4 and 2/5 🚀 (#74) * Adding translations for 2/4 and 2/5 🚀 * Remove English content Co-authored-by: lewtun * Translation to Russian (#97) * translation of chapter 2/section 1 * add section 1 / chapter 2 to _toctree.yml * Translation of Chapter0 to Hindi (#86) * Hindi?Chapter0-Part_1 * Hindi/Chapter0-Part_2 * Chapter 0 Persian Translation First Draft (#95) * merged branch0 into main. no toctree yet. * updated toctree. * Updated the glossary with terms from chapter0. * Second draft in collab w/ @schoobani. Added empty chapter1 for preview. * Glossary typo fix. * Translation of Chapter0 (setup) to Arabic (#104) * Add AR translation for `introduction` * Fix alignment & format * Add Arabic to CI build * Russian - Chapter 1 finished (#98) * 01/4 start * 1/4 finished * 1/5 finished * 1/5 update toc * 1/6 finished * 1/7 finished * 1/8 finished * 1/9 finished * 1/4 fix * toc update * Chinese - Chapter 1 finished (#113) * Chinese - Chapter 1 finished * Add zh to the languages field Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> * [PT] Translation of chapter 2 (#107) * add PT translate to 2.1 * add PT translate to 2.2 * add portuguese translation to 2.3 * WIP portuguese translation to 2.4 * add portuguese translation to 2.4 * add portuguese translation to 2.5 * add portuguese translation to 2.6 * add _toctree infos Co-authored-by: lewtun * [FR] Translation of chapter 2 & event + Review of chapters 0 & 5 (#106) * Update _toctree.yml Add chapter 2 + little fix of chapter 5 * Update 1.mdx Review of chapter 0 * Create 1.mdx * Create 2.mdx * Create 3.mdx * Create 4.mdx * Create 5.mdx * Create 6.mdx * Create 7.mdx * Create 8.mdx * Update 8.mdx Since AutoNLP has recently been renamed to AutoTrain, let me make the correction on the English file * Update 1.mdx Review of chapter 5/1 * Update 2.mdx Review of chapter 5/2 * Update 3.mdx Review of chapter 5/3 * Update 4.mdx Review of chapter 5/4 * Update 5.mdx Review of chapter 5/5 * Update 6.mdx Review of chapter 5/6 * Update 7.mdx Review of chapter 5/7 * Update 8.mdx Review of chapter 5/8 * Create 1.mdx event's translation * Update _toctree.yml add event to the tree * Update _toctree.yml deletion of the files that pose a problem to pass the checks, will be resubmitted in another PR * Delete 1.mdx deletion of the files that pose a problem to pass the checks, will be resubmitted in another PR * make style correction * Update _toctree.yml the - * Fix spacing Co-authored-by: Lewis Tunstall * [th] Translated Chapter2/1 (#83) * Finish chapter2/1 * Update _toctree.yml * Add Hindi to CI (#116) * Update README.md (#87) * Update authors on README (#120) * Update authors * French translation `Chapter1` full (#56) * traduction 1st part of chapter1 * fix typo * fix job titles and encoder-decoder translation * add part 2 for 1st chapter * fix some typo part2 * fix Transformer -> Transformers * add part 3 not totally ended * end of part3 of chapter1 * part9 chapter 1 * add part7 chapter 1 * add part5 chapter 1 * part 6 chapter 1 * add part8 chapter 1 * end quizz of chapter * add last part of chapter 1 Co-authored-by: ChainYo * Translate to Japanese Chapter0 (#123) * start working * translate chapter0/1.mdx * [FA] First draft of Chapter2/Page2 (#129) * merged branch0 into main. no toctree yet. * updated toctree. * Updated the glossary with terms from chapter0. * Second draft in collab w/ @schoobani. Added empty chapter1 for preview. * Glossary typo fix. * Added missing backticks. * Removed a couple of bad indefinite articles I forgot. * First draft of ch2/p2. Adds to glossary. Trans. guidelines moved out. * Fixed missing diacritics, fixed the py/tf switch placing. Other fixes. * Changed the equivalent for prediction per @kambizG 's direction. * Redid ambiguous passage in translation per @lewtun 's direction. * [th] Finished whole Chapter 2 translation (#127) * Finish chapter2/1 * delete untranslated files * chapter2/2 WIP * Delete WIP files * WIP chapter2/2 * Fixed conflict * Update _toctree.yml * Update _toctree.yml * Finished Chapter2/2 * Finish all chapter2/n * Finish all chapter2/n * Fixed Ch2/8 as PR run failed * [de] Translation Chapter 0 (#130) * Copy files to newly created german dir (de) * Add translation for chapter 0 * Clean up english files for chapter 1 * Change _toctree.yml for chapter 0 * Fix whitespaces * Fix whitespaces again * Adjust _toctree.yml - leave only chaper 0 * Add German language (de) to workflow yaml files * [de] German Translation Guide (#132) * German Translation Guide * Add German Glossary to TOC * Chapter 1, Section 1 Bengali translation (#124) * [ADD] Chapter 1, Section 1 benglai tranlation * [FIX] toc * [FIX] commit mistakes * [FIX] remove the Eng duplicates Co-authored-by: m_khandaker * [FR] Review of chapters 0, 2 & 5 + add chapters 6, 7, 8 & event (#125) * Create 1.mdx Event translation * Create 1.mdx * Chapter 6 in French * Update 1.mdx fix italic * Update 9.mdx fix italic * Update 3.mdx fix italic * Update 4.mdx fix italic * Update 4.mdx * Update 1.mdx little fix * Update 2.mdx little fix * Update 4.mdx fix italic * Update 8.mdx fix italic * Update 1.mdx little fix * Update 2.mdx little fix * Update 3.mdx little fix * Update 5.mdx little fix * Update 7.mdx little fix * Update 8.mdx little fix * add chapter8 * Update 6.mdx fix italic * Update 3.mdx fix, fix everywhere * Update 2.mdx fix, fix everywhere * Update 4.mdx fix, fix everywhere * Update 4_tf.mdx fix, fix everywhere * Add files via upload add chapter 7 * Update 1.mdx fix links * Update 2.mdx fix, fix everywhere * Update 3.mdx fix, fix everywhere * Update 4.mdx fix, fix everywhere * Update 5.mdx * Update 6.mdx fix, fix everywhere * Update 7.mdx fix, fix everywhere * Update 3.mdx fix link * Update 8.mdx fix link * Update 2.mdx fix link * Update 4.mdx little fix * Update 6.mdx * Update 7.mdx * Update 8.mdx fix * Update 2.mdx little fix * Update 3.mdx little fix * Update 5.mdx * Update 4_tf.mdx little fix * Update _toctree.yml Forgot the toctree * Update _toctree.yml fix local fields * Update _toctree.yml My bad, I forgot some 🙃 * Update 7.mdx I don't know why it was there... * Update 1.mdx * [de] Chapter 3 translation (#128) * chapter 3 part 1 DE * [DE] Chapter 3 - Part 2 * Prepare TOC-Tree * Fein-tuning * Initial translation * Glossary additions for C3P3 * C3P2 style * [de] Chapter 3 P3-TF initial translation * [de] Chapter 3 P4 initial translation * [de] Chapter 3 Part 5 initial translation * [de] Chapter 3 P6 Initial translation * Missing commas * fixing quotes * Mark third option on chapter 8, question 8 as correct (#135) * doc_change(translation): translating course from english to gujarati (#126) * change(translation): chapter0 to gujarati content translated: Chapter0/1.mdx - Introduction commit-by: menonashwathi4@gmail.com * Revert "change(translation): chapter0 to gujarati" This reverts commit c27e06992af8892687f343a19368ce322d69e8b2. * doc_change(translation): translation to gj translated content: chapters/gj/chapter0.mdx - introduction * doc_change(translation): translation to gj translated content: chapters/gj/chapter0.mdx - introduction * Delete _toctree.yml * change: adding gj to github workflow * nit: fix heading * Update authors (#136) * [FA] First draft of Chapter4/Page1 (#134) * added chapter4 title and it's first section * added first draft of Chapter4/Page1 * minor fix * updated the title according to convention * applied some updates according to convention * added footnotes, minor improvements * applied tweaks according to review points * the new draft of glossary according to PR #134 * fixed an inconsistant title * minor fix for better compatibility with T points * applied final touches for this round of G updates * [FR] End of chapter 3 + chapter 4 (#137) * add chapters 3 & 4 * Update 2.mdx fix links * Update 3.mdx some fix * Update 6.mdx fix tag * Update 3.mdx add link to chapter 7 * Update 3_tf.mdx add link to chapter 7 * Update _toctree.yml Co-authored-by: DOOHAE JUNG Co-authored-by: m_khandaker Co-authored-by: Md. Al-Amin Khandaker Co-authored-by: ftarlaci <18291571+ftarlaci@users.noreply.github.com> Co-authored-by: Doohae Jung <80743307+Doohae@users.noreply.github.com> Co-authored-by: melaniedrevet Co-authored-by: Jose M Munoz Co-authored-by: svv73 <88366711+svv73@users.noreply.github.com> Co-authored-by: Vedant Pandya Co-authored-by: Bahram Shamshiri Co-authored-by: Giyaseddin Bayrak <34009210+giyaseddin@users.noreply.github.com> Co-authored-by: Pavel <60391448+pdumin@users.noreply.github.com> Co-authored-by: 1375626371 <40328311+1375626371@users.noreply.github.com> Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> Co-authored-by: João Gustavo A. Amorim Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Cherdsak Kingkan Co-authored-by: Thomas Chaigneau <50595514+ChainYo@users.noreply.github.com> Co-authored-by: ChainYo Co-authored-by: hiromu <45531573+hiromu166@users.noreply.github.com> Co-authored-by: Cherdsak Kingkan Co-authored-by: Marcus Fraaß Co-authored-by: Jesper Dramsch Co-authored-by: amyeroberts Co-authored-by: Ash <103081562+ashwathim@users.noreply.github.com> Co-authored-by: Hamed Homaei Rad * Bump release (#147) * Bump release (#161) * Fix typos in chapter 9 (#176) (#180) Co-authored-by: regisss <15324346+regisss@users.noreply.github.com> * Bump release (#187) * Chapter 2 Section 1 Bengali Translation (huggingface#72) (#168) * [TH] Chapter 6 Section 1 and 2 (#171) Co-authored-by: Suteera * [FA] CH1 / P1-2 (#142) * Spanish Chapter 3: sections 1 & 2 (#162) * fix typos in bpe, wordpiece, unigram (#166) * [FR] French Review (#186) * Part 7: Training a causal... fixes (#179) * typo & error mitigation * consistency * Trainer.predict() returns 3 fields * ran make style * [TR] Translated Chapter 1.6 🤗 (#185) * added chapter 1/6 to _toctree.yml * [TR] Translated Chapter 1.6 🤗 Co-authored-by: Avishek Das Co-authored-by: Suteera Seeha <33692408+meanna@users.noreply.github.com> Co-authored-by: Suteera Co-authored-by: Saeed Choobani Co-authored-by: Fermin Ordaz Co-authored-by: Kerem Turgutlu Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Sebastian Sosa <37946988+CakeCrusher@users.noreply.github.com> Co-authored-by: tanersekmen <56790802+tanersekmen@users.noreply.github.com> * Bump release 10 (#194) * Bump release (#195) * Bump release 12 (#209) * Bump release (#224) * Bump release (#229) * Bump release (#236) * Bump release (#258) * Bump release (#270) * Bump release (#274) * Bump release (#286) * Bump release (#288) * Chapter 2 Section 1 Bengali Translation (huggingface#72) (#168) * [TH] Chapter 6 Section 1 and 2 (#171) Co-authored-by: Suteera * [FA] CH1 / P1-2 (#142) * Spanish Chapter 3: sections 1 & 2 (#162) * fix typos in bpe, wordpiece, unigram (#166) * [FR] French Review (#186) * Part 7: Training a causal... fixes (#179) * typo & error mitigation * consistency * Trainer.predict() returns 3 fields * ran make style * [TR] Translated Chapter 1.6 🤗 (#185) * added chapter 1/6 to _toctree.yml * [TR] Translated Chapter 1.6 🤗 * [PT][Chapter 01 - 2.mdx] - issue #51 (#170) * Fix Gradio ToC (#193) * Add Gradio authors and Blocks event (#189) * Update 6.mdx (#188) Correct link to Transformer XL doc * Add translating notes and glossary to Spanish (#192) * Add translating notes and glosary to Spanish * Adding glossary to the toc * add pt 4.3 (#191) * [FR] Visual corrections (#190) * [PT] add chapter 4.4 and 4.5 (#196) * fix invite discord link (#197) * [FA] Second draft of CH2/P1-2 (#139) * added chapter3 in hindi (#198) * [TR] Chapter 3/1 (#165) * [RU] Ch3-1/2/3 (#200) * [PT] add 5.1 and 5.2 (#204) * [FA] - Ch3 - P1 and P2 (#199) * [PT] add `end-of-chapter quiz` for chapter 4 (4.6) (#201) Co-authored-by: lewtun * Chapter1: 2.mdx Translated. (#206) * Remove comments from Persian ToC (#210) * Fix CI URL for PRs (#211) * code fragment & english syntax and meaning (#203) * Updated Ch1/1 with Emoji (#214) * Add missing numpy import (#217) * [ES] translate sections 8.1 and 8.2 (#215) * Fix path to datasets (#216) * [PT] add 5.3 (#218) * fix 4.3 (#223) * Fix notebook generation (#227) * Add Gradio nb links * add 5.4 (#226) * add pt wip (#225) * Added Gujarati List. (#221) * Add Gradio nbs links to fr (#228) * Chinese - Chapter 3finished (#219) * add ch7 at _toctree and translate 7.1 (#222) * add 5.5 (#235) * [FR] Review of chapter 7 (#233) * Italian translation - chapter 4 (#230) * Added Thai translation of chapters 3 (#231) * [Ru] Add part 2, chapter 2 (#234) * Update 8.mdx (#237) - Remove Gradio Blocks Party - Add, Where to next? section * Created HI/Chapter1/5.mdx (#232) * Add Spanish chaper3/section4, update toc and glossary (#238) * [RU] Chapter 3 finished (#239) * [PT] add 5.6 and 5.7 (#240) * [EN] Visual corrections (#245) * Translation for 1/4, 1/5 and 1/6. (#247) * add event in PT (#250) * Pin version of black (#252) * Translate ja event (#241) * [PT] add quiz chapter 5 (#243) * Update 5.mdx (#253) inconsistent naming with line 327 * Translation for Traditional Chinese (zh-tw) chapter0 (#251) Co-authored-by: Lewis Tunstall * Translated the whole Chapter 3 to Thai (#255) * Japanese chapter 4 (#244) * Translation of 1/7, 1/8, and 1/9. (#263) * [PT] add chapter 8.1 and 8.2 (#265) * [RU] Chapter 4 (#269) * Add Thai translation for chapter 6.3b to 6.10 (#268) * add 8.3 (#266) * 3.mdx of chapter 01 (#260) Co-authored-by: Lewis Tunstall * Fix typo (#271) * [PT] add chapter 6.1 (#273) * add Japanese chapter7 (#267) * replace `load_metric` with `evaluate.load` (#285) * update `load_metric` refs to `evaluate.load` Co-authored-by: lewtun * [GJ] Translation to Gujarati - Ch0 Setup (#287) * [PT] add chapter 6.2 and 6.3 (#279) * zh-CN - Chapter 4,5finished (#281) Co-authored-by: Lewis Tunstall * Chapter 01 - Done [PT] #51 (#280) Co-authored-by: Lewis Tunstall Co-authored-by: Avishek Das Co-authored-by: Suteera Seeha <33692408+meanna@users.noreply.github.com> Co-authored-by: Suteera Co-authored-by: Saeed Choobani Co-authored-by: Fermin Ordaz Co-authored-by: Kerem Turgutlu Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Sebastian Sosa <37946988+CakeCrusher@users.noreply.github.com> Co-authored-by: tanersekmen <56790802+tanersekmen@users.noreply.github.com> Co-authored-by: Victor Costa <54755870+victorescosta@users.noreply.github.com> Co-authored-by: Camille Couturier Co-authored-by: João Gustavo A. Amorim Co-authored-by: Bahram Shamshiri Co-authored-by: Kavya <36916536+robotjellyzone@users.noreply.github.com> Co-authored-by: Batuhan Ayhan Co-authored-by: Pavel <60391448+pdumin@users.noreply.github.com> Co-authored-by: Kambiz Ghoorchian Co-authored-by: Vedant Pandya Co-authored-by: Diego Vargas <91356068+dzarkV@users.noreply.github.com> Co-authored-by: Thomas O'Brien Co-authored-by: Lincoln V Schreiber Co-authored-by: 1375626371 <40328311+1375626371@users.noreply.github.com> Co-authored-by: Giorgio Severi Co-authored-by: svv73 <88366711+svv73@users.noreply.github.com> Co-authored-by: Ömer Faruk Özdemir Co-authored-by: Caterina Bonan <97481648+CaterinaBi@users.noreply.github.com> Co-authored-by: Hiromu Hota Co-authored-by: trtd56 <5toda6@gmail.com> Co-authored-by: Mehrdad Nezamdoost Co-authored-by: Wolvz Co-authored-by: a-krirk <56425947+a-krirk@users.noreply.github.com> Co-authored-by: atgctg <105969161+atgctg@users.noreply.github.com> Co-authored-by: Thiago Medeiros Co-authored-by: webbigdata-jp <87654083+webbigdata-jp@users.noreply.github.com> Co-authored-by: Leandro von Werra Co-authored-by: Bhadresh Savani * Bump release (#295) * Bump release (#296) * Bump release (#299) * Bump release (#305) * Chinese - Chapter 1 finished * Add zh to the languages field Add zh to the languages field in the build_documentation.yml and build_pr_documentation.yml files * Remove untranslated chapters in _toctree.yml Remove all these sections that haven't been translated yet Remove Chapter 0 from the table of contents since it hasn't been translated yet * Fixed an error in the translation format Fixed an error in the translation format of Chapter 1, Section 3 * Added a small part of the missing content * Fix style * Complete the translation of Chapters 0 and 2 * Fixed some bugs ·Fixed some formatting errors ·Moved Chapters 0 and 2 to Simplified Chinese * Add files via upload Formatting revisions and some translation corrections * run make style to format chapter1 session3 * run make style to format code * run make style to format code * Fix style * Chapter 2 Section 1 Bengali Translation (huggingface#72) (#168) * [TH] Chapter 6 Section 1 and 2 (#171) Co-authored-by: Suteera * [FA] CH1 / P1-2 (#142) * Spanish Chapter 3: sections 1 & 2 (#162) * fix typos in bpe, wordpiece, unigram (#166) * [FR] French Review (#186) * Part 7: Training a causal... fixes (#179) * typo & error mitigation * consistency * Trainer.predict() returns 3 fields * ran make style * [TR] Translated Chapter 1.6 🤗 (#185) * added chapter 1/6 to _toctree.yml * [TR] Translated Chapter 1.6 🤗 * [PT][Chapter 01 - 2.mdx] - issue #51 (#170) * Fix Gradio ToC (#193) * Add Gradio authors and Blocks event (#189) * Update 6.mdx (#188) Correct link to Transformer XL doc * Add translating notes and glossary to Spanish (#192) * Add translating notes and glosary to Spanish * Adding glossary to the toc * add pt 4.3 (#191) * [FR] Visual corrections (#190) * [PT] add chapter 4.4 and 4.5 (#196) * fix invite discord link (#197) * [FA] Second draft of CH2/P1-2 (#139) * added chapter3 in hindi (#198) * [TR] Chapter 3/1 (#165) * [RU] Ch3-1/2/3 (#200) * [PT] add 5.1 and 5.2 (#204) * Add placeholders for audio chapters (#208) * [FA] - Ch3 - P1 and P2 (#199) * [PT] add `end-of-chapter quiz` for chapter 4 (4.6) (#201) Co-authored-by: lewtun * Chapter1: 2.mdx Translated. (#206) * Remove comments from Persian ToC (#210) * Fix CI URL for PRs (#211) * code fragment & english syntax and meaning (#203) * Updated Ch1/1 with Emoji (#214) * Add missing numpy import (#217) * Updata chapter3 * Code format for chapter3 * Updata yml file of chapter3 * Uptata yml file of chapter3 * Fix yml file bug * [ES] translate sections 8.1 and 8.2 (#215) * Fix path to datasets (#216) * [PT] add 5.3 (#218) * fix 4.3 (#223) * Run make style * Fix notebook generation (#227) * Add Gradio nb links * add 5.4 (#226) * add pt wip (#225) * Added Gujarati List. (#221) * Fix quality * Add Gradio nbs links to fr (#228) * Fix ToC tree * Remove audio templates * Fix fr section * Fix fr chapter * Chinese - Chapter 3finished (#219) * add ch7 at _toctree and translate 7.1 (#222) * add 5.5 (#235) * [FR] Review of chapter 7 (#233) * Italian translation - chapter 4 (#230) * Added Thai translation of chapters 3 (#231) * [Ru] Add part 2, chapter 2 (#234) * Update 8.mdx (#237) - Remove Gradio Blocks Party - Add, Where to next? section * Created HI/Chapter1/5.mdx (#232) * Add Spanish chaper3/section4, update toc and glossary (#238) * [RU] Chapter 3 finished (#239) * [PT] add 5.6 and 5.7 (#240) * [EN] Visual corrections (#245) * Translation for 1/4, 1/5 and 1/6. (#247) * add event in PT (#250) * Pin version of black (#252) * Translate ja event (#241) * [PT] add quiz chapter 5 (#243) * Update 5.mdx (#253) inconsistent naming with line 327 * Translation for Traditional Chinese (zh-tw) chapter0 (#251) Co-authored-by: Lewis Tunstall * Translated the whole Chapter 3 to Thai (#255) * Japanese chapter 4 (#244) * Translation of 1/7, 1/8, and 1/9. (#263) * [PT] add chapter 8.1 and 8.2 (#265) * [RU] Chapter 4 (#269) * Add Thai translation for chapter 6.3b to 6.10 (#268) * add 8.3 (#266) * 3.mdx of chapter 01 (#260) Co-authored-by: Lewis Tunstall * Fix typo (#271) * [PT] add chapter 6.1 (#273) * add Japanese chapter7 (#267) * zh-CN - Chapter 4,5finished * replace `load_metric` with `evaluate.load` (#285) * update `load_metric` refs to `evaluate.load` Co-authored-by: lewtun * [GJ] Translation to Gujarati - Ch0 Setup (#287) * [PT] add chapter 6.2 and 6.3 (#279) * Fix formatting * Debug formatting * Debug FR formatting * zh-CN - Chapter 4,5finished (#281) Co-authored-by: Lewis Tunstall * Chapter 01 - Done [PT] #51 (#280) Co-authored-by: Lewis Tunstall * tf_default_data_collator seems to have moved * zh-CN - Chapter 6finished * Revert "Merge branch 'huggingface:main' into main" This reverts commit aebb46e12f9f87a4303f8bb4f0f2cf545eb83b21, reversing changes made to 69187a3789e8d3d2d0de821ebe495f111d1cc73d. * Revert "zh-CN - Chapter 6finished" This reverts commit e69fce28d3a7b35b76c4f768a6cedf295b37d8c9. * zh-CN - Chapter 6finished * fix style * undo bad commit * Chapter5it (#278) * added the italian translation for unit 1 chapter5 Co-authored-by: Leandro von Werra * Vietnamese translation (#293) * Update .github/workflows/build_pr_documentation.yml Co-authored-by: lewtun * Translate JP chapter 8 (#249) * Italian translation - Chapter 8 (#272) * Translation to Vietnamese - chapter 5 (#297) * Add course contributors (#298) * Add CourseFloatingBanner component * DocNotebookDropdown -> CourseFloatingBanner * Italian translation Ch 2/1, 2/2 (#300) * Add contributors (#304) * Add forum button (#306) Co-authored-by: 1375626371 <40328311+1375626371@users.noreply.github.com> Co-authored-by: 1375626371 <1375626371@qq.com> Co-authored-by: Avishek Das Co-authored-by: Suteera Seeha <33692408+meanna@users.noreply.github.com> Co-authored-by: Suteera Co-authored-by: Saeed Choobani Co-authored-by: Fermin Ordaz Co-authored-by: Kerem Turgutlu Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Sebastian Sosa <37946988+CakeCrusher@users.noreply.github.com> Co-authored-by: tanersekmen <56790802+tanersekmen@users.noreply.github.com> Co-authored-by: Victor Costa <54755870+victorescosta@users.noreply.github.com> Co-authored-by: Camille Couturier Co-authored-by: João Gustavo A. Amorim Co-authored-by: Bahram Shamshiri Co-authored-by: Kavya <36916536+robotjellyzone@users.noreply.github.com> Co-authored-by: Batuhan Ayhan Co-authored-by: Pavel <60391448+pdumin@users.noreply.github.com> Co-authored-by: Kambiz Ghoorchian Co-authored-by: Vedant Pandya Co-authored-by: Diego Vargas <91356068+dzarkV@users.noreply.github.com> Co-authored-by: Thomas O'Brien Co-authored-by: Lincoln V Schreiber Co-authored-by: Giorgio Severi Co-authored-by: svv73 <88366711+svv73@users.noreply.github.com> Co-authored-by: Ömer Faruk Özdemir Co-authored-by: Caterina Bonan <97481648+CaterinaBi@users.noreply.github.com> Co-authored-by: Hiromu Hota Co-authored-by: trtd56 <5toda6@gmail.com> Co-authored-by: Mehrdad Nezamdoost Co-authored-by: Wolvz Co-authored-by: a-krirk <56425947+a-krirk@users.noreply.github.com> Co-authored-by: atgctg <105969161+atgctg@users.noreply.github.com> Co-authored-by: Thiago Medeiros Co-authored-by: webbigdata-jp <87654083+webbigdata-jp@users.noreply.github.com> Co-authored-by: Leandro von Werra Co-authored-by: Bhadresh Savani Co-authored-by: Andreas Ehrencrona Co-authored-by: leandro Co-authored-by: Matt Co-authored-by: Nolanogenn <52080100+Nolanogenn@users.noreply.github.com> Co-authored-by: Hồng Hạnh Co-authored-by: Younes Belkada <49240599+younesbelkada@users.noreply.github.com> Co-authored-by: Edoardo Abati <29585319+EdAbati@users.noreply.github.com> Co-authored-by: Mishig Davaadorj Co-authored-by: Acciaro Gennaro Daniele * Bump release (#307) * Bump release (#308) * Bump release (#314) * Bump release (#320) * Bump release (#328) * Bump release (#333) * Bump release (#335) * Bump release (#343) * Bump release (#355) * Bump release (#358) * Bump release (#371) * Bump release (#381) * Bump release (#387) * Bump release (#404) * Bump release (#413) * Bump release (#426) * Bump release (#463) --------- Co-authored-by: DOOHAE JUNG Co-authored-by: m_khandaker Co-authored-by: Md. Al-Amin Khandaker Co-authored-by: ftarlaci <18291571+ftarlaci@users.noreply.github.com> Co-authored-by: Doohae Jung <80743307+Doohae@users.noreply.github.com> Co-authored-by: melaniedrevet Co-authored-by: Jose M Munoz Co-authored-by: svv73 <88366711+svv73@users.noreply.github.com> Co-authored-by: Vedant Pandya Co-authored-by: Bahram Shamshiri Co-authored-by: Giyaseddin Bayrak <34009210+giyaseddin@users.noreply.github.com> Co-authored-by: Pavel <60391448+pdumin@users.noreply.github.com> Co-authored-by: 1375626371 <40328311+1375626371@users.noreply.github.com> Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> Co-authored-by: João Gustavo A. Amorim Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Cherdsak Kingkan Co-authored-by: Thomas Chaigneau <50595514+ChainYo@users.noreply.github.com> Co-authored-by: ChainYo Co-authored-by: hiromu <45531573+hiromu166@users.noreply.github.com> Co-authored-by: Cherdsak Kingkan Co-authored-by: Marcus Fraaß Co-authored-by: Jesper Dramsch Co-authored-by: amyeroberts Co-authored-by: Ash <103081562+ashwathim@users.noreply.github.com> Co-authored-by: Hamed Homaei Rad Co-authored-by: Dawood Khan Co-authored-by: regisss <15324346+regisss@users.noreply.github.com> Co-authored-by: Avishek Das Co-authored-by: Suteera Seeha <33692408+meanna@users.noreply.github.com> Co-authored-by: Suteera Co-authored-by: Saeed Choobani Co-authored-by: Fermin Ordaz Co-authored-by: Kerem Turgutlu Co-authored-by: Sebastian Sosa <37946988+CakeCrusher@users.noreply.github.com> Co-authored-by: tanersekmen <56790802+tanersekmen@users.noreply.github.com> Co-authored-by: Victor Costa <54755870+victorescosta@users.noreply.github.com> Co-authored-by: Camille Couturier Co-authored-by: Kavya <36916536+robotjellyzone@users.noreply.github.com> Co-authored-by: Batuhan Ayhan Co-authored-by: Kambiz Ghoorchian Co-authored-by: Diego Vargas <91356068+dzarkV@users.noreply.github.com> Co-authored-by: Thomas O'Brien Co-authored-by: Lincoln V Schreiber Co-authored-by: Giorgio Severi Co-authored-by: Ömer Faruk Özdemir Co-authored-by: Caterina Bonan <97481648+CaterinaBi@users.noreply.github.com> Co-authored-by: Hiromu Hota Co-authored-by: trtd56 <5toda6@gmail.com> Co-authored-by: Mehrdad Nezamdoost Co-authored-by: Wolvz Co-authored-by: a-krirk <56425947+a-krirk@users.noreply.github.com> Co-authored-by: atgctg <105969161+atgctg@users.noreply.github.com> Co-authored-by: Thiago Medeiros Co-authored-by: webbigdata-jp <87654083+webbigdata-jp@users.noreply.github.com> Co-authored-by: Leandro von Werra Co-authored-by: Bhadresh Savani Co-authored-by: 1375626371 <1375626371@qq.com> Co-authored-by: Andreas Ehrencrona Co-authored-by: leandro Co-authored-by: Matt Co-authored-by: Nolanogenn <52080100+Nolanogenn@users.noreply.github.com> Co-authored-by: Hồng Hạnh Co-authored-by: Younes Belkada <49240599+younesbelkada@users.noreply.github.com> Co-authored-by: Edoardo Abati <29585319+EdAbati@users.noreply.github.com> Co-authored-by: Mishig Davaadorj Co-authored-by: Acciaro Gennaro Daniele * Revert "Bump release (#566)" (#567) This reverts commit cccc2c91ac8e702e5e14bbb0419dbf0490c7aaaf. * updated documentation links * [doc build] Use secrets (#581) * docs: fix broken links * changed 'perspires' to 'persists' in chapter 1 quiz solves issue #585 * Update 4.mdx You forgot to write a return for this function. * Update 4.mdx : Fix Typo Should be "course" * fix link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Update 2.mdx updated loading datasets link * Fix syntax in vi/chapter7/7.mdx There was an unnecessary `` * Remove `get_lr()` from logs which refers to nonexistent function `get_lr()` is called as part of this function, but the function is not declared anywhere in the script. This change removes this portion of the code since it is non-necessary. * Update 4.mdx removed judgmental argument * Update en-version * fix: remove useless token * fix: remove useless token (#635) * Translate Chapter 3 to Spanish (#510) * translate Chapter 3 to Spanish * translate code comments to Spanish and fix typos * Translating Chapter 6 to Spanish (#523) * Translating sections 1 and 2 to spanish * Translating sections 3 to spanish * Translating sections 3b to spanish * Translating sections 4 to spanish * Translating section 5 to spanish * Translating section 6 to spanish * Translating section 7 to spanish * Translating section 8 to spanish * Translating section 9 to spanish * Translating section 10 to spanish * Adding Sections to _toctree.yml * Fixing Typos after second review --------- Co-authored-by: datacubeR * Update 5.mdx Ajuste na tradução de "encoders". São "codificadores", não "decodificadores". Decoders são "decodificadores". * finalize de_ch_4_part2 * add ch 4 parts to table of contents * Update doc CI (#643) * Фиксация текущих результатов. * Фиксирую текущее состояние. * Fixing the transfer results for today. * Translated files 3b and partially 4. Fixing the result. * Fixing today's translation. * fix typos in Spanish translation (#511) * Fixing today's translation. Files: 6.mdx, 7.mdx and half of 8.mdx. * The translation of chapter 6 has been completed. * Delete chapters/en/.ipynb_checkpoints/_toctree-checkpoint.yml This is backup created by JupyterLab. * Delete chapters/en/chapter5/.ipynb_checkpoints/8-checkpoint.mdx This is backup created by JupyterLab. * Delete chapters/en/chapter6/.ipynb_checkpoints/1-checkpoint.mdx This is backup created by JupyterLab. * Delete chapters/en/chapter6/.ipynb_checkpoints/2-checkpoint.mdx This is backup created by JupyterLab. * Delete chapters/en/chapter6/.ipynb_checkpoints/8-checkpoint.mdx This is backup created by JupyterLab. * Delete chapters/en/chapter6/.ipynb_checkpoints/9-checkpoint.mdx This is backup created by JupyterLab. * Delete chapters/ru/.ipynb_checkpoints/TRANSLATING-checkpoint.txt This is backup created by JupyterLab. * Delete chapters/ru/.ipynb_checkpoints/_toctree-checkpoint.yml This is backup created by JupyterLab. * Delete chapters/ru/chapter5/.ipynb_checkpoints/8-checkpoint.mdx This is backup created by JupyterLab. * Update 10.mdx Minor fix. * Update 10.mdx Trying to solve the markup problem. * Update 10.mdx Correcting the syntax of some markup again) * Update chapters/ru/chapter6/4.mdx Yes, that space is redundant here. You're right about that. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter6/4.mdx Extra space. I overlooked it. My mistake. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter6/3.mdx There's an extra space here. You're right. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter6/3.mdx There's an extra space here. You're right. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter6/3b.mdx Yeah, there's no need for a space here. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter6/3.mdx Co-authored-by: Maria Khalusova * Update 3.mdx * Update 7.mdx Translated the comments noted on the review. * Update 3.mdx Translated the missing comments in the code. * Update chapters/ru/chapter6/3b.mdx Yes, an extra space. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter6/5.mdx Minor fix. Co-authored-by: Maria Khalusova * Completed the translation of the first part of Chapter 7 into Russian. * After run python utils/code_formatter.py * Update chapters/ru/chapter7/1.mdx Extra space. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/2.mdx Extra space. I didn't notice. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/2.mdx Extra space. I didn't notice. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/2.mdx Yes, indeed, I ate the space bar))))) Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/5.mdx There's that extra space again. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/5.mdx There's that extra space again that I didn't notice. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/5.mdx Extra space. Co-authored-by: Maria Khalusova * Update 5.mdx Translated the missing comment. * Update chapters/ru/chapter7/4.mdx Extra space. Co-authored-by: Maria Khalusova * Update 2.mdx Translated the missing comment in the code * Update 2.mdx Translated the missing sentence. * Update 3.mdx Translated the missing sentence. * Update 3.mdx I agree, it sounds more neutral that way. * Update chapters/ru/chapter7/3.mdx An unnecessary parenthesis. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/3.mdx Also an option, but we've translated it as "карточка модели" a lot of places. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/3.mdx Extra space. Co-authored-by: Maria Khalusova * Update 3.mdx Translated the missing comment in the code. * Update chapters/ru/chapter7/3.mdx Extra sapce. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/4.mdx Extra space. Co-authored-by: Maria Khalusova * Update 4.mdx Translated the missing comment in the code. * Update 5.mdx Added and translated the missing sentence: "Since the collator expects a list of dicts, where each dict represents a single example in the dataset, we also need to wrangle the data into the expected format before passing it to the data collator:" * Update 5.mdx Edit the display of the table on the course page. * fixed links to other chapters * fixed links to chapters' intros * I added myself to the Languages and translations table. * Deleted unnecessary folder automatically created by JupyterLab. * Fix links to HF docs * Finalizing the translation of chapter 7. * Update 6.mdx Extra space * Update 7.mdx Extra space * Update chapters/ru/chapter7/6.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/6.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/6.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/7.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/6.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/7.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/7.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/8.mdx Correction of abbreviation - NLP Co-authored-by: Maria Khalusova * Update 7.mdx Translated the code commentary * Update 6.mdx Translated the missing sentence. * Update chapters/ru/chapter7/7.mdx Co-authored-by: Maria Khalusova * Update 6.mdx * Update chapters/ru/chapter7/6.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/7.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/6.mdx Co-authored-by: Maria Khalusova * 8/1-2 done * 8/3 finished * 8/4 finished * fix typo * toc update * typos fixed * removed english text * 8/5 finished * 8/6-7 finished * fix and update toc * chapter8/1 fixed * chapter8/2 fixed * chapter8/3 fixed * chapter8/4 fixed * chapter8/5 fixed * fix title 8/5 * fix title 8/5 in toc * Update _toctree.yml title 8 Co-authored-by: Maria Khalusova * Bump black (#671) * fix unexpected token in quiz * 8/2 fixed * 8/3 fixed * 8/4_tf fixed * Added translation of chapter 9 and Course Events. * Added translation of chapter 9 and Course Events. * Update chapters/ru/chapter9/6.mdx OK Co-authored-by: Maria Khalusova * Update chapters/ru/chapter9/7.mdx OK Co-authored-by: Maria Khalusova * Update chapters/ru/chapter9/7.mdx OK Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx I agree. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Sorry. I was hasty and made an incorrect assumption))) Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Capture the current state of the translation. Two files are translated: , . Corrections to the table of contents. * Made a full translation of Chapter 2. * Fix problem in . * Deleting JupyterLab backup files. * Update 8.mdx Correcting problems in file. * Update 8.mdx Translated a missing piece of text in English. * remove original sentence * Update chapters/ru/chapter2/2.mdx OK. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter2/2.mdx Co-authored-by: Maria Khalusova * Update chapters/ru/chapter2/4.mdx Co-authored-by: Maria Khalusova * Update chapters/ru/chapter2/5.mdx Co-authored-by: Maria Khalusova * Update chapters/ru/chapter2/5.mdx Co-authored-by: Maria Khalusova * Update chapters/ru/chapter2/5.mdx Co-authored-by: Maria Khalusova * Update chapters/ru/chapter2/5.mdx Co-authored-by: Maria Khalusova * Update chapters/ru/chapter2/2.mdx Co-authored-by: Maria Khalusova * Update 2.mdx * Update 2.mdx * Update chapters/ru/chapter2/2.mdx Co-authored-by: Maria Khalusova * Update 2.mdx * Update chapters/ru/chapter2/2.mdx Co-authored-by: Maria Khalusova * Update chapters/ru/chapter2/3.mdx Co-authored-by: Maria Khalusova * Update chapters/ru/chapter2/4.mdx Co-authored-by: Maria Khalusova * Update 4.mdx --------- Co-authored-by: Tiezhen WANG <38108242+xianbaoqian@users.noreply.github.com> Co-authored-by: Pranav <66965591+Pranav-Bobde@users.noreply.github.com> Co-authored-by: Maria Khalusova Co-authored-by: DOOHAE JUNG Co-authored-by: m_khandaker Co-authored-by: Md. Al-Amin Khandaker Co-authored-by: ftarlaci <18291571+ftarlaci@users.noreply.github.com> Co-authored-by: Doohae Jung <80743307+Doohae@users.noreply.github.com> Co-authored-by: melaniedrevet Co-authored-by: Jose M Munoz Co-authored-by: svv73 <88366711+svv73@users.noreply.github.com> Co-authored-by: Vedant Pandya Co-authored-by: Bahram Shamshiri Co-authored-by: Giyaseddin Bayrak <34009210+giyaseddin@users.noreply.github.com> Co-authored-by: Pavel <60391448+pdumin@users.noreply.github.com> Co-authored-by: 1375626371 <40328311+1375626371@users.noreply.github.com> Co-authored-by: petrichor1122 <87262598+petrichor1122@users.noreply.github.com> Co-authored-by: zhlhyx <95976146+zhlhyx@users.noreply.github.com> Co-authored-by: João Gustavo A. Amorim Co-authored-by: lbourdois <58078086+lbourdois@users.noreply.github.com> Co-authored-by: Cherdsak Kingkan Co-authored-by: Thomas Chaigneau <50595514+ChainYo@users.noreply.github.com> Co-authored-by: ChainYo Co-authored-by: hiromu <45531573+hiromu166@users.noreply.github.com> Co-authored-by: Cherdsak Kingkan Co-authored-by: Marcus Fraaß Co-authored-by: Jesper Dramsch Co-authored-by: amyeroberts Co-authored-by: Ash <103081562+ashwathim@users.noreply.github.com> Co-authored-by: Hamed Homaei Rad Co-authored-by: Dawood Khan Co-authored-by: regisss <15324346+regisss@users.noreply.github.com> Co-authored-by: Avishek Das Co-authored-by: Suteera Seeha <33692408+meanna@users.noreply.github.com> Co-authored-by: Suteera Co-authored-by: Saeed Choobani Co-authored-by: Fermin Ordaz Co-authored-by: Kerem Turgutlu Co-authored-by: Sebastian Sosa <37946988+CakeCrusher@users.noreply.github.com> Co-authored-by: tanersekmen <56790802+tanersekmen@users.noreply.github.com> Co-authored-by: Victor Costa <54755870+victorescosta@users.noreply.github.com> Co-authored-by: Camille Couturier Co-authored-by: Kavya <36916536+robotjellyzone@users.noreply.github.com> Co-authored-by: Batuhan Ayhan Co-authored-by: Kambiz Ghoorchian Co-authored-by: Diego Vargas <91356068+dzarkV@users.noreply.github.com> Co-authored-by: Thomas O'Brien Co-authored-by: Lincoln V Schreiber Co-authored-by: Giorgio Severi Co-authored-by: Ömer Faruk Özdemir Co-authored-by: Caterina Bonan <97481648+CaterinaBi@users.noreply.github.com> Co-authored-by: Hiromu Hota Co-authored-by: trtd56 <5toda6@gmail.com> Co-authored-by: Mehrdad Nezamdoost Co-authored-by: Wolvz Co-authored-by: a-krirk <56425947+a-krirk@users.noreply.github.com> Co-authored-by: atgctg <105969161+atgctg@users.noreply.github.com> Co-authored-by: Thiago Medeiros Co-authored-by: webbigdata-jp <87654083+webbigdata-jp@users.noreply.github.com> Co-authored-by: Leandro von Werra Co-authored-by: Bhadresh Savani Co-authored-by: 1375626371 <1375626371@qq.com> Co-authored-by: Andreas Ehrencrona Co-authored-by: leandro Co-authored-by: Matt Co-authored-by: Nolanogenn <52080100+Nolanogenn@users.noreply.github.com> Co-authored-by: Hồng Hạnh Co-authored-by: Younes Belkada <49240599+younesbelkada@users.noreply.github.com> Co-authored-by: Edoardo Abati <29585319+EdAbati@users.noreply.github.com> Co-authored-by: Mishig Davaadorj Co-authored-by: Acciaro Gennaro Daniele Co-authored-by: nnoboa Co-authored-by: Vipula Sandaruwan Dissanayake Co-authored-by: Alex Bzdel Co-authored-by: JieShen <49408146+JieShenAI@users.noreply.github.com> Co-authored-by: Hardik Bhadani Co-authored-by: Omar Sanseviero Co-authored-by: Suket Kamboj <82956207+Sookeyy-12@users.noreply.github.com> Co-authored-by: Brad Windsor Co-authored-by: Pierre Alexandre SCHEMBRI Co-authored-by: Remy Co-authored-by: María Grandury <57645283+mariagrandury@users.noreply.github.com> Co-authored-by: Alfonso Tobar-Arancibia <48638337+datacubeR@users.noreply.github.com> Co-authored-by: datacubeR Co-authored-by: Alysson <50303964+k3ybladewielder@users.noreply.github.com> Co-authored-by: Fabrizio Damicelli Co-authored-by: Merve Noyan Co-authored-by: Artyom Boyko Co-authored-by: mariosasko Co-authored-by: Pavel --- chapters/de/_toctree.yml | 7 + chapters/de/chapter4/4.mdx | 91 ++++++++++ chapters/de/chapter4/5.mdx | 12 ++ chapters/de/chapter4/6.mdx | 231 +++++++++++++++++++++++++ chapters/ru/_toctree.yml | 13 +- chapters/ru/chapter2/1.mdx | 21 +-- chapters/ru/chapter2/2.mdx | 125 +++++++------- chapters/ru/chapter2/3.mdx | 85 +++++----- chapters/ru/chapter2/4.mdx | 240 ++++++++++++++++++++++++++ chapters/ru/chapter2/5.mdx | 338 +++++++++++++++++++++++++++++++++++++ chapters/ru/chapter2/6.mdx | 164 ++++++++++++++++++ chapters/ru/chapter2/7.mdx | 18 +- chapters/ru/chapter2/8.mdx | 310 ++++++++++++++++++++++++++++++++++ 13 files changed, 1529 insertions(+), 126 deletions(-) create mode 100644 chapters/de/chapter4/4.mdx create mode 100644 chapters/de/chapter4/5.mdx create mode 100644 chapters/de/chapter4/6.mdx create mode 100644 chapters/ru/chapter2/4.mdx create mode 100644 chapters/ru/chapter2/5.mdx create mode 100644 chapters/ru/chapter2/6.mdx create mode 100644 chapters/ru/chapter2/8.mdx 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/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/ru/_toctree.yml b/chapters/ru/_toctree.yml index 575ac4556..46b889425 100644 --- a/chapters/ru/_toctree.yml +++ b/chapters/ru/_toctree.yml @@ -26,16 +26,25 @@ - local: chapter1/10 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: diff --git a/chapters/ru/chapter2/1.mdx b/chapters/ru/chapter2/1.mdx index ce2dbae74..94add379c 100644 --- a/chapters/ru/chapter2/1.mdx +++ b/chapters/ru/chapter2/1.mdx @@ -1,24 +1,25 @@ -# Введение +# Введение[[introduction]] -Как вы могли заметить в [Главе 1](../chapter1/1), модели трансформеров обычно бывают очень большие. Обучение и развертывание таких моделей с миллионами и даже десятками *миллиардов* параметров является сложной задачей. Кроме того, новые модели выпускаются почти ежедневно, и каждая из них имеет собственную реализацию, опробовать их все — непростая задача. +Как вы видели в [Главе 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](../chapter1/1). Далее мы обсудим 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 3960afe39..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](../chapter1/1): +Давайте начнем с полноценного примера и посмотрим, что произошло за кулисами, когда мы выполнили следующий код в [Главе 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](../chapter1/1), данный конвейер включает в себя три шага: предварительная обработка, передача входных данных через модель и постобработка: +Как мы видели в [Главе 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](../chapter1/1) разные задачи могли бы выполняться с одной и той же архитектурой, но с каждой из этих задач будет связан отдельный слой "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 b7e941ac4..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](../chapter1/1), это потребовало бы много времени и большого количества данных, а также имело бы значительное воздействие на окружающую среду. Чтобы избежать ненужных и дублирующих усилий, крайне важно иметь возможность делиться и повторно использовать модели, которые уже были обучены. +Модель можно использовать и в таком состоянии, но она будет выдавать тарабарщину; сначала ее нужно обучить. Мы могли бы обучить модель с нуля для конкретной задачи, но, как вы видели в [Главе 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..d9e4a98f1 --- /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} From d4d64eee6924730470412f0d79b0155793f1a67e Mon Sep 17 00:00:00 2001 From: lewtun Date: Thu, 16 Jan 2025 23:48:06 +1030 Subject: [PATCH 51/51] Bump release (#768) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update chapters/ru/chapter7/5.mdx There's that extra space again. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/5.mdx There's that extra space again that I didn't notice. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/5.mdx Extra space. Co-authored-by: Maria Khalusova * Update 5.mdx Translated the missing comment. * Update chapters/ru/chapter7/4.mdx Extra space. Co-authored-by: Maria Khalusova * Update 2.mdx Translated the missing comment in the code * Update 2.mdx Translated the missing sentence. * Update 3.mdx Translated the missing sentence. * Update 3.mdx I agree, it sounds more neutral that way. * Update chapters/ru/chapter7/3.mdx An unnecessary parenthesis. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/3.mdx Also an option, but we've translated it as "карточка модели" a lot of places. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/3.mdx Extra space. Co-authored-by: Maria Khalusova * Update 3.mdx Translated the missing comment in the code. * Update chapters/ru/chapter7/3.mdx Extra sapce. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/4.mdx Extra space. Co-authored-by: Maria Khalusova * Update 4.mdx Translated the missing comment in the code. * Update 5.mdx Added and translated the missing sentence: "Since the collator expects a list of dicts, where each dict represents a single example in the dataset, we also need to wrangle the data into the expected format before passing it to the data collator:" * Update 5.mdx Edit the display of the table on the course page. * fixed links to other chapters * fixed links to chapters' intros * I added myself to the Languages and translations table. * Deleted unnecessary folder automatically created by JupyterLab. * Fix links to HF docs * Finalizing the translation of chapter 7. * Update 6.mdx Extra space * Update 7.mdx Extra space * Update chapters/ru/chapter7/6.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/6.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/6.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/7.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/6.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/7.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/7.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/8.mdx Correction of abbreviation - NLP Co-authored-by: Maria Khalusova * Update 7.mdx Translated the code commentary * Update 6.mdx Translated the missing sentence. * Update chapters/ru/chapter7/7.mdx Co-authored-by: Maria Khalusova * Update 6.mdx * Update chapters/ru/chapter7/6.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/7.mdx Correcting a link Co-authored-by: Maria Khalusova * Update chapters/ru/chapter7/6.mdx Co-authored-by: Maria Khalusova * 8/1-2 done * 8/3 finished * 8/4 finished * fix typo * toc update * typos fixed * removed english text * 8/5 finished * 8/6-7 finished * fix and update toc * chapter8/1 fixed * chapter8/2 fixed * chapter8/3 fixed * chapter8/4 fixed * chapter8/5 fixed * fix title 8/5 * fix title 8/5 in toc * Update _toctree.yml title 8 Co-authored-by: Maria Khalusova * Bump black (#671) * fix unexpected token in quiz * 8/2 fixed * 8/3 fixed * 8/4_tf fixed * Update 3b.mdx fix typo * Added translation of chapter 9 and Course Events. * Added translation of chapter 9 and Course Events. * Update 5.mdx Fix typo * Update 7.mdx Fix typo * Update 10.mdx fix tpo * Update chapters/ru/chapter9/6.mdx OK Co-authored-by: Maria Khalusova * Update chapters/ru/chapter9/7.mdx OK Co-authored-by: Maria Khalusova * Update chapters/ru/chapter9/7.mdx OK Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx I agree. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Sorry. I was hasty and made an incorrect assumption))) Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/1.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Update chapters/ru/events/2.mdx Removing tag. I agree with you. Co-authored-by: Maria Khalusova * Capture the current state of the translation. Two files are translated: , . Corrections to the table of contents. * Made a full translation of Chapter 2. * Fix problem in . * Deleting JupyterLab backup files. * Update 8.mdx Correcting problems in file. * Update 8.mdx Translated a missing piece of text in English. * remove original sentence * Update chapters/ru/chapter2/2.mdx OK. Co-authored-by: Maria Khalusova * Update chapters/ru/chapter2/2.mdx Co-authored-by: Maria Khalusova * Update chapters/ru/chapter2/4.mdx Co-authored-by: Maria Khalusova * Update chapters/ru/chapter2/5.mdx Co-authored-by: Maria Khalusova * Update chapters/ru/chapter2/5.mdx Co-authored-by: Maria Khalusova * Update chapters/ru/chapter2/5.mdx Co-authored-by: Maria Khalusova * Update chapters/ru/chapter2/5.mdx Co-authored-by: Maria Khalusova * Update chapters/ru/chapter2/2.mdx Co-authored-by: Maria Khalusova * Update 2.mdx * Update 2.mdx * Update chapters/ru/chapter2/2.mdx Co-authored-by: Maria Khalusova * Update 2.mdx * Update chapters/ru/chapter2/2.mdx Co-authored-by: Maria Khalusova * Update chapters/ru/chapter2/3.mdx Co-authored-by: Maria Khalusova * Update chapters/ru/chapter2/4.mdx Co-authored-by: Maria Khalusova * Update 4.mdx * Minor edits to the table of contents, and the titles of the final test files at the end of the chapter. * chapter 2's introduction hackable French translation In this context, the word "hackable" doesn't translate in "piratable" (which conveys the notion of illegal piracy). I believe that "modifiable" (i.e can be modified) is more appropriate. * Chapter 2.2 - doesn't make sense French translation In French, "doesn't make sense" translates in "n'a pas de sens". * [doc-fix] Add accelerate required for notebook 3/3 * translation fix chapter2 - section3: a mistake on TensorFlow part, which use a pytorch keyword * fix: typo * fix formatting * remove unnecessary translate * fix typo and formatting in Chinese translation * fix zh-cn translation for chapter 3-6 * fix formatting issue for zh-cn on chapter 5-8 * fix zh-cn translation on chapter 4-6 * docs: mdx typo * fix deactivate * tip to install datasets * 修正翻译 * fix zh * fix zh * Changing tokenized_dataset to tokenized_datasets Signed-off-by: Jiri Podivin * Update 9.mdx fix typo. * Update 2.mdx fix typo * [zh-CN/TW] bugfix of broken image link: Companies using Hugging Face * [zh-CN/TW] pipeline name translation is not needed * [zh-CN/TW] translation of `Hub`: 集线器(集線器) => 模型中心 * correct zh translation * correct zh-TW translation * finish review chapter7 8 9 This year, my friends and I conducted a comprehensive review of the Chinese translations published before, greatly improving the readability, hoping that they can help more people. We have completed the second half of the first part, and are gradually reviewing the first half. * format fr/chapter9/4.mdx * Update 7.mdx add the miss close tag * create rum folder * Add Steven as reviewer (#746) * update toctree * translate chapter0-1 * update course0-1 * remove files and folders that are not updated * add the rum folder in the build documentation * Introduction to Argilla * Set up Argilla * Remove mention of chapters 10-12 * finish review ZH-CN chapter1-6 * code_format for chaper 1-6 * Fixed wrong full width colon * Initial draft * Fix * Corrections section 2 * Section 3 improvements * More improvements * Images & apply review comments * Apply suggestions from code review Co-authored-by: vb * Fix style * Updated images and banners * More screenshots * Fix quiz inline code * More improvements from reviews * Added chapter 0 and initiated _toctree for Nepali Language * Added Nepali language code in the workflow github actions * Ran make styles without any errors! * Update chapters/ne/chapter0/1.mdx Co-authored-by: Steven Liu <59462357+stevhliu@users.noreply.github.com> * Update chapters/ne/chapter0/1.mdx Co-authored-by: Steven Liu <59462357+stevhliu@users.noreply.github.com> * Made same codeblocks for activate and deactivate --------- Signed-off-by: Jiri Podivin Co-authored-by: Artyom Boyko Co-authored-by: Maria Khalusova Co-authored-by: mariosasko Co-authored-by: Pavel Co-authored-by: Pavel <60391448+pdumin@users.noreply.github.com> Co-authored-by: Yanis ALLOUCH <75087263+yanisallouch@users.noreply.github.com> Co-authored-by: Fabrizio Damicelli Co-authored-by: Florent Flament Co-authored-by: Dmitrii Ioksha <41271764+DmitryIo@users.noreply.github.com> Co-authored-by: brealid <1134204972@qq.com> Co-authored-by: Owen Co-authored-by: ruochenhua Co-authored-by: Jesse Zhang <13264500190@163.com> Co-authored-by: Quentin Gallouédec <45557362+qgallouedec@users.noreply.github.com> Co-authored-by: tal7aouy Co-authored-by: buqieryu <2680643943@qq.com> Co-authored-by: Jiri Podivin Co-authored-by: Taeyoon Kim Co-authored-by: Jks Liu Co-authored-by: huqian Co-authored-by: Omar Sanseviero Co-authored-by: Tiezhen WANG <38108242+xianbaoqian@users.noreply.github.com> Co-authored-by: Adam Molnar <70143200+lunarflu@users.noreply.github.com> Co-authored-by: 1375626371 <1375626371@qq.com> Co-authored-by: Hu Yaoqi <40328311+yaoqih@users.noreply.github.com> Co-authored-by: eduard-balamatiuc Co-authored-by: Eduard Balamatiuc <66115008+eduard-balamatiuc@users.noreply.github.com> Co-authored-by: nataliaElv Co-authored-by: Steven Liu <59462357+stevhliu@users.noreply.github.com> Co-authored-by: Ann Huang Co-authored-by: Ann Huang Co-authored-by: Natalia Elvira <126158523+nataliaElv@users.noreply.github.com> Co-authored-by: vb Co-authored-by: CRLannister Co-authored-by: Ashish Agarwal <29420145+CRLannister@users.noreply.github.com> Co-authored-by: Pedro Cuenca --- .github/workflows/build_documentation.yml | 2 +- .github/workflows/build_pr_documentation.yml | 2 +- README.md | 2 +- chapters/ar/chapter0/1.mdx | 2 +- chapters/bn/chapter0/1.mdx | 2 +- chapters/de/chapter0/1.mdx | 2 +- chapters/de/chapter3/2.mdx | 3 + chapters/en/_toctree.yml | 20 ++ chapters/en/chapter0/1.mdx | 2 +- chapters/en/chapter1/1.mdx | 2 +- chapters/en/chapter10/1.mdx | 26 ++ chapters/en/chapter10/2.mdx | 55 ++++ chapters/en/chapter10/3.mdx | 108 +++++++ chapters/en/chapter10/4.mdx | 44 +++ chapters/en/chapter10/5.mdx | 69 +++++ chapters/en/chapter10/6.mdx | 20 ++ chapters/en/chapter10/7.mdx | 187 ++++++++++++ chapters/en/chapter2/5.mdx | 2 +- chapters/en/chapter3/2.mdx | 4 + chapters/en/chapter7/6.mdx | 10 +- chapters/es/chapter0/1.mdx | 2 +- chapters/es/chapter3/2.mdx | 4 + chapters/fa/chapter0/1.mdx | 2 +- chapters/fa/chapter3/2.mdx | 3 + chapters/fr/chapter0/1.mdx | 2 +- chapters/fr/chapter2/1.mdx | 2 +- chapters/fr/chapter2/2.mdx | 2 +- chapters/fr/chapter3/2.mdx | 4 + chapters/fr/chapter6/10.mdx | 2 +- chapters/fr/chapter6/3b.mdx | 6 +- chapters/fr/chapter6/5.mdx | 2 +- chapters/fr/chapter6/7.mdx | 2 +- chapters/gj/chapter0/1.mdx | 2 +- chapters/he/chapter0/1.mdx | 2 +- chapters/hi/chapter0/1.mdx | 2 +- chapters/hi/chapter3/2.mdx | 4 + chapters/id/chapter0/1.mdx | 2 +- chapters/it/chapter0/1.mdx | 2 +- chapters/it/chapter3/2.mdx | 4 + chapters/ja/chapter0/1.mdx | 2 +- chapters/ko/chapter0/1.mdx | 2 +- chapters/ko/chapter1/9.mdx | 2 +- chapters/ko/chapter2/2.mdx | 2 +- chapters/ne/_toctree.yml | 4 + chapters/ne/chapter0/1.mdx | 116 ++++++++ chapters/pt/chapter0/1.mdx | 2 +- chapters/ru/_toctree.yml | 10 +- chapters/ru/chapter0/1.mdx | 2 +- chapters/ru/chapter1/10.mdx | 2 +- chapters/ru/chapter2/8.mdx | 2 +- chapters/ru/chapter3/2.mdx | 4 + chapters/ru/chapter3/6.mdx | 2 +- chapters/ru/chapter4/6.mdx | 2 +- chapters/ru/chapter5/8.mdx | 2 +- chapters/rum/_toctree.yml | 4 + chapters/rum/chapter0/1.mdx | 110 +++++++ chapters/th/chapter0/1.mdx | 2 +- chapters/th/chapter3/2.mdx | 4 + chapters/tr/chapter0/1.mdx | 2 +- chapters/vi/chapter0/1.mdx | 2 +- chapters/zh-CN/_toctree.yml | 2 +- chapters/zh-CN/chapter0/1.mdx | 52 ++-- chapters/zh-CN/chapter1/1.mdx | 63 ++-- chapters/zh-CN/chapter1/10.mdx | 86 +++--- chapters/zh-CN/chapter1/2.mdx | 20 +- chapters/zh-CN/chapter1/3.mdx | 161 ++++++---- chapters/zh-CN/chapter1/4.mdx | 102 ++++--- chapters/zh-CN/chapter1/5.mdx | 16 +- chapters/zh-CN/chapter1/6.mdx | 13 +- chapters/zh-CN/chapter1/7.mdx | 14 +- chapters/zh-CN/chapter1/8.mdx | 8 +- chapters/zh-CN/chapter1/9.mdx | 10 +- chapters/zh-CN/chapter2/1.mdx | 18 +- chapters/zh-CN/chapter2/2.mdx | 134 +++++---- chapters/zh-CN/chapter2/3.mdx | 120 +++----- chapters/zh-CN/chapter2/4.mdx | 109 +++---- chapters/zh-CN/chapter2/5.mdx | 83 +++--- chapters/zh-CN/chapter2/6.mdx | 47 ++- chapters/zh-CN/chapter2/7.mdx | 28 +- chapters/zh-CN/chapter2/8.mdx | 154 +++++----- chapters/zh-CN/chapter3/1.mdx | 14 +- chapters/zh-CN/chapter3/2.mdx | 237 ++++++++++----- chapters/zh-CN/chapter3/3.mdx | 48 ++- chapters/zh-CN/chapter3/3_tf.mdx | 49 +-- chapters/zh-CN/chapter3/4.mdx | 71 ++--- chapters/zh-CN/chapter3/5.mdx | 18 +- chapters/zh-CN/chapter3/6.mdx | 162 +++++----- chapters/zh-CN/chapter4/1.mdx | 14 +- chapters/zh-CN/chapter4/2.mdx | 16 +- chapters/zh-CN/chapter4/3.mdx | 222 +++++++------- chapters/zh-CN/chapter4/4.mdx | 50 ++-- chapters/zh-CN/chapter4/5.mdx | 8 +- chapters/zh-CN/chapter4/6.mdx | 124 ++++---- chapters/zh-CN/chapter5/1.mdx | 21 +- chapters/zh-CN/chapter5/2.mdx | 55 ++-- chapters/zh-CN/chapter5/3.mdx | 213 +++++++------ chapters/zh-CN/chapter5/4.mdx | 89 +++--- chapters/zh-CN/chapter5/5.mdx | 112 +++---- chapters/zh-CN/chapter5/6.mdx | 75 ++--- chapters/zh-CN/chapter5/7.mdx | 16 +- chapters/zh-CN/chapter5/8.mdx | 124 ++++---- chapters/zh-CN/chapter6/1.mdx | 16 +- chapters/zh-CN/chapter6/10.mdx | 163 +++++----- chapters/zh-CN/chapter6/2.mdx | 86 +++--- chapters/zh-CN/chapter6/3.mdx | 114 +++---- chapters/zh-CN/chapter6/3b.mdx | 121 ++++---- chapters/zh-CN/chapter6/4.mdx | 59 ++-- chapters/zh-CN/chapter6/5.mdx | 108 +++---- chapters/zh-CN/chapter6/6.mdx | 106 +++---- chapters/zh-CN/chapter6/7.mdx | 130 ++++---- chapters/zh-CN/chapter6/8.mdx | 176 +++++------ chapters/zh-CN/chapter6/9.mdx | 14 +- chapters/zh-CN/chapter7/1.mdx | 14 +- chapters/zh-CN/chapter7/2.mdx | 272 +++++++++-------- chapters/zh-CN/chapter7/3.mdx | 259 ++++++++-------- chapters/zh-CN/chapter7/4.mdx | 226 +++++++------- chapters/zh-CN/chapter7/5.mdx | 289 +++++++++--------- chapters/zh-CN/chapter7/6.mdx | 215 +++++++------- chapters/zh-CN/chapter7/7.mdx | 296 ++++++++++--------- chapters/zh-CN/chapter7/8.mdx | 12 +- chapters/zh-CN/chapter7/9.mdx | 199 +++++++------ chapters/zh-CN/chapter8/1.mdx | 10 +- chapters/zh-CN/chapter8/2.mdx | 100 +++---- chapters/zh-CN/chapter8/3.mdx | 64 ++-- chapters/zh-CN/chapter8/4.mdx | 197 ++++++------ chapters/zh-CN/chapter8/4_tf.mdx | 165 +++++------ chapters/zh-CN/chapter8/5.mdx | 50 ++-- chapters/zh-CN/chapter8/6.mdx | 8 +- chapters/zh-CN/chapter8/7.mdx | 102 +++---- chapters/zh-CN/chapter9/1.mdx | 23 +- chapters/zh-CN/chapter9/2.mdx | 49 +-- chapters/zh-CN/chapter9/3.mdx | 85 +++--- chapters/zh-CN/chapter9/4.mdx | 66 ++--- chapters/zh-CN/chapter9/5.mdx | 30 +- chapters/zh-CN/chapter9/6.mdx | 35 ++- chapters/zh-CN/chapter9/7.mdx | 85 +++--- chapters/zh-CN/chapter9/8.mdx | 22 +- chapters/zh-CN/chapter9/9.mdx | 102 +++---- chapters/zh-TW/_toctree.yml | 2 +- chapters/zh-TW/chapter0/1.mdx | 2 +- chapters/zh-TW/chapter1/3.mdx | 33 ++- chapters/zh-TW/chapter2/1.mdx | 6 +- chapters/zh-TW/chapter2/5.mdx | 4 +- chapters/zh-TW/chapter3/6.mdx | 4 +- chapters/zh-TW/chapter4/3.mdx | 16 +- chapters/zh-TW/chapter5/6.mdx | 2 +- chapters/zh-TW/chapter7/5.mdx | 2 +- utils/generate_notebooks.py | 1 + 148 files changed, 4439 insertions(+), 3574 deletions(-) create mode 100644 chapters/en/chapter10/1.mdx create mode 100644 chapters/en/chapter10/2.mdx create mode 100644 chapters/en/chapter10/3.mdx create mode 100644 chapters/en/chapter10/4.mdx create mode 100644 chapters/en/chapter10/5.mdx create mode 100644 chapters/en/chapter10/6.mdx create mode 100644 chapters/en/chapter10/7.mdx create mode 100644 chapters/ne/_toctree.yml create mode 100644 chapters/ne/chapter0/1.mdx create mode 100644 chapters/rum/_toctree.yml create mode 100644 chapters/rum/chapter0/1.mdx diff --git a/.github/workflows/build_documentation.yml b/.github/workflows/build_documentation.yml index bd7ce48d9..40415999c 100644 --- a/.github/workflows/build_documentation.yml +++ b/.github/workflows/build_documentation.yml @@ -14,6 +14,6 @@ jobs: package: course 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 + languages: ar bn de en es fa fr gj he hi id it ja ko ne pt ru rum th tr vi zh-CN zh-TW secrets: hf_token: ${{ secrets.HF_DOC_BUILD_PUSH }} diff --git a/.github/workflows/build_pr_documentation.yml b/.github/workflows/build_pr_documentation.yml index a568b16f4..30ee138f5 100644 --- a/.github/workflows/build_pr_documentation.yml +++ b/.github/workflows/build_pr_documentation.yml @@ -16,4 +16,4 @@ jobs: package: course 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 + languages: ar bn de en es fa fr gj he hi id it ja ko ne pt ru rum th tr vi zh-CN zh-TW diff --git a/README.md b/README.md index d48e97773..1c629759c 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ pip install -r requirements.txt make style ``` -Once that's run, commit any changes, open a pull request, and tag [@lewtun](https://github.com/lewtun) for a review. Congratulations, you've now completed your first translation 🥳! +Once that's run, commit any changes, open a pull request, and tag [@lewtun](https://github.com/lewtun) and [@stevhliu](https://github.com/stevhliu) for a review. If you also know other native-language speakers who are able to review the translation, tag them as well for help. Congratulations, you've now completed your first translation 🥳! > 🚨 To build the course on the website, double-check your language code exists in `languages` field of the `build_documentation.yml` and `build_pr_documentation.yml` files in the `.github` folder. If not, just add them in their alphabetical order. 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/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/chapter3/2.mdx b/chapters/de/chapter3/2.mdx index 12c7f699a..ddfcb101d 100644 --- a/chapters/de/chapter3/2.mdx +++ b/chapters/de/chapter3/2.mdx @@ -87,6 +87,9 @@ In diesem Abschnitt verwenden wir den MRPC-Datensatz (Microsoft Research Paraphr 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 diff --git a/chapters/en/_toctree.yml b/chapters/en/_toctree.yml index c8364cc6d..fd88d016e 100644 --- a/chapters/en/_toctree.yml +++ b/chapters/en/_toctree.yml @@ -191,6 +191,26 @@ title: End-of-chapter quiz quiz: 9 +- title: 10. Curate high-quality datasets + new: true + subtitle: How to use Argilla to create amazing datasets + sections: + - local: chapter10/1 + title: Introduction to Argilla + - local: chapter10/2 + title: Set up your Argilla instance + - local: chapter10/3 + title: Load your dataset to Argilla + - local: chapter10/4 + title: Annotate your dataset + - local: chapter10/5 + title: Use your annotated dataset + - local: chapter10/6 + title: Argilla, check! + - local: chapter10/7 + title: End-of-chapter quiz + quiz: 10 + - title: Course Events sections: - local: events/1 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/1.mdx b/chapters/en/chapter1/1.mdx index 30c992371..a2a11c2d9 100644 --- a/chapters/en/chapter1/1.mdx +++ b/chapters/en/chapter1/1.mdx @@ -23,7 +23,7 @@ Here is a brief overview of the course: - Chapters 1 to 4 provide an introduction to the main concepts of the 🤗 Transformers library. By the end of this part of the course, you will be familiar with how Transformer models work and will know how to use a model from the [Hugging Face Hub](https://huggingface.co/models), fine-tune it on a dataset, and share your results on the Hub! - Chapters 5 to 8 teach the basics of 🤗 Datasets and 🤗 Tokenizers before diving into classic NLP tasks. By the end of this part, you will be able to tackle the most common NLP problems by yourself. -- Chapters 9 to 12 go beyond NLP, and explore how Transformer models can be used to tackle tasks in speech processing and computer vision. Along the way, you'll learn how to build and share demos of your models, and optimize them for production environments. By the end of this part, you will be ready to apply 🤗 Transformers to (almost) any machine learning problem! +- Chapter 9 goes beyond NLP to cover how to build and share demos of your models on the 🤗 Hub. By the end of this part, you will be ready to showcase your 🤗 Transformers application to the world! This course: diff --git a/chapters/en/chapter10/1.mdx b/chapters/en/chapter10/1.mdx new file mode 100644 index 000000000..4301620cc --- /dev/null +++ b/chapters/en/chapter10/1.mdx @@ -0,0 +1,26 @@ +# Introduction to Argilla[[introduction-to-argilla]] + + + +In Chapter 5 you learnt how to build a dataset using the 🤗 Datasets library and in Chapter 6 you explored how to fine-tune models for some common NLP tasks. In this chapter, you will learn how to use [Argilla](https://argilla.io) to **annotate and curate datasets** that you can use to train and evaluate your models. + +The key to training models that perform well is to have high-quality data. Although there are some good datasets in the Hub that you could use to train and evaluate your models, these may not be relevant for your specific application or use case. In this scenario, you may want to build and curate a dataset of your own. Argilla will help you to do this efficiently. + +Argilla sign in page. + +With Argilla you can: + +- turn unstructured data into **structured data** to be used in NLP tasks. +- curate a dataset to go from a low-quality dataset to a **high-quality dataset**. +- gather **human feedback** for LLMs and multi-modal models. +- invite experts to collaborate with you in Argilla, or crowdsource annotations! + +Here are some of the things that you will learn in this chapter: + +- How to set up your own Argilla instance. +- How to load a dataset and configure it based on some popular NLP tasks. +- How to use the Argilla UI to annotate your dataset. +- How to use your curated dataset and export it to the Hub. diff --git a/chapters/en/chapter10/2.mdx b/chapters/en/chapter10/2.mdx new file mode 100644 index 000000000..447ef04aa --- /dev/null +++ b/chapters/en/chapter10/2.mdx @@ -0,0 +1,55 @@ +# Set up your Argilla instance[[set-up-your-argilla-instance]] + + + +To start using Argilla, you will need to set up your own Argilla instance first. Then you will need to install the Python SDK so that you can manage Argilla using Python code. + +## Deploy the Argilla UI + +The easiest way to set up your Argilla instance is through Hugging Face Spaces. To create your Argilla Space, simply follow [this form](https://huggingface.co/new-space?template=argilla%2Fargilla-template-space). If you need further guidance, check the [Argilla quickstart](https://docs.argilla.io/latest/getting_started/quickstart/). +Space configuration form. + +>[!WARNING] +> ⚠️ You may want to enable **Persistent storage** so the data isn't lost if the Space is paused or restarted. +> You can do that from the Settings of your Space. + +Once Argilla is up and running, you can log in with your credentials. + +## Install and connect the Python SDK + +Now you can go to your Python environment or notebook and install the argilla library: + +`!pip install argilla` + +Let's connect with our Argilla instance. To do that you will need the following information: + +- **Your API URL**: This is the URL where Argilla is running. If you are using a Space, you can open the Space, click on the three dots in the top right corner, then "Embed this Space" and copy the **Direct URL**. It should look something like `https://..hf.space`. +- **Your API key**: To get your key, log in to your Argilla instance and go to "My Settings", then copy the API key. +- **Your HF token**: If your Space is private, you will need to an Access Token in your Hugging Face Hub account with writing permissions. + +```python +import argilla as rg + +HF_TOKEN = "..." # only for private spaces + +client = rg.Argilla( + api_url="...", + api_key="...", + headers={"Authorization": f"Bearer {HF_TOKEN}"}, # only for private spaces +) +``` + +To check that everything is working properly, we'll call `me`. This should return our user: + +```python +client.me +``` + +If this worked, your Argilla instance is up and running and you're connected to it! Congrats! + +We can now get started with loading our first dataset to Argilla. diff --git a/chapters/en/chapter10/3.mdx b/chapters/en/chapter10/3.mdx new file mode 100644 index 000000000..2595c86bf --- /dev/null +++ b/chapters/en/chapter10/3.mdx @@ -0,0 +1,108 @@ +# Load your dataset to Argilla[[load-your-dataset-to-argilla]] + + + +Depending on the NLP task that you're working with and the specific use case or application, your data and the annotation task will look differently. For this section of the course, we'll use [a dataset collecting news](https://huggingface.co/datasets/SetFit/ag_news) to complete two tasks: a text classification on the topic of each text and a token classification to identify the named entities mentioned. + + + +It is possible to import datasets from the Hub using the Argilla UI directly, but we'll be using the SDK to learn how we can make further edits to the data if needed. + +## Configure your dataset + +The first step is to connect to our Argilla instance as we did in the previous section: + +```python +import argilla as rg + +HF_TOKEN = "..." # only for private spaces + +client = rg.Argilla( + api_url="...", + api_key="...", + headers={"Authorization": f"Bearer {HF_TOKEN}"}, # only for private spaces +) +``` + +We can now think about the settings of our dataset in Argilla. These represent the annotation task we'll do over our data. First, we can load the dataset from the Hub and inspect its features, so that we can make sure that we configure the dataset correctly. + +```python +from datasets import load_dataset + +data = load_dataset("SetFit/ag_news", split="train") +data.features +``` + +These are the features of our dataset: + +```python out +{'text': Value(dtype='string', id=None), + 'label': Value(dtype='int64', id=None), + 'label_text': Value(dtype='string', id=None)} +``` + +It contains a `text` and also some initial labels for the text classification. We'll add those to our dataset settings together with a `spans` question for the named entities: + +```python +settings = rg.Settings( + fields=[rg.TextField(name="text")], + questions=[ + rg.LabelQuestion( + name="label", title="Classify the text:", labels=data.unique("label_text") + ), + rg.SpanQuestion( + name="entities", + title="Highlight all the entities in the text:", + labels=["PERSON", "ORG", "LOC", "EVENT"], + field="text", + ), + ], +) +``` + +Let's dive a bit deeper into what these settings mean. First, we've defined **fields**, these include the information that we'll be annotating. In this case, we only have one field and it comes in the form of a text, so we've choosen a `TextField`. + +Then, we define **questions** that represent the tasks that we want to perform on our data: + +- For the text classification task we've chosen a `LabelQuestion` and we used the unique values of the `label_text` column as our labels, to make sure that the question is compatible with the labels that already exist in the dataset. +- For the token classification task, we'll need a `SpanQuestion`. We've defined a set of labels that we'll be using for that task, plus the field on which we'll be drawing the spans. + +To learn more about all the available types of fields and questions and other advanced settings, like metadata and vectors, go to the [Argilla docs](https://docs.argilla.io/latest/how_to_guides/dataset/#define-dataset-settings). + +## Upload the dataset + +Now that we've defined some settings, we can create the dataset: + +```python +dataset = rg.Dataset(name="ag_news", settings=settings) + +dataset.create() +``` + +The dataset now appears in our Argilla instance, but you will see that it's empty: + +Screenshot of the empty dataset. + +Now we need to add the records that we'll be annotating i.e., the rows in our dataset. To do that, we'll simply need to log the data as records and provide a mapping for those elements that don't have the same name in the hub and Argilla datasets: + +```python +dataset.records.log(data, mapping={"label_text": "label"}) +``` + +In our mapping, we've specified that the `label_text` column in the dataset should be mapped to the question with the name `label`. In this way, we'll use the existing labels in the dataset as pre-annotations so we can annotate faster. + +While the the records continue to log, you can already start working with your dataset in the Argilla UI. At this point, it should look like this: + +Screenshot of the dataset in Argilla. + +Now our dataset is ready to start annotating! diff --git a/chapters/en/chapter10/4.mdx b/chapters/en/chapter10/4.mdx new file mode 100644 index 000000000..59c845502 --- /dev/null +++ b/chapters/en/chapter10/4.mdx @@ -0,0 +1,44 @@ +# Annotate your dataset[[annotate-your-dataset]] + + + +Now it is time to start working from the Argilla UI to annotate our dataset. + +## Align your team with annotation guidelines + +Before you start annotating your dataset, it is always good practice to write some guidelines, especially if you're working as part of a team. This will help you align on the task and the use of the different labels, and resolve questions or conflicts when they come up. + +In Argilla, you can go to your dataset settings page in the UI and modify the guidelines and the descriptions of your questions to help with alignment. + +Screenshot of the Dataset Settings page in Argilla. + +If you want to dive deeper into the topic of how to write good guidelines, we recommend reading [this blogpost](https://argilla.io/blog/annotation-guidelines-practices) and the bibliographical references mentioned there. + +## Distribute the task + +In the dataset settings page, you can also change the dataset distribution settings. This will help you annotate more efficiently when you're working as part of a team. The default value for the minimum submitted responses is 1, meaning that as soon as a record has 1 submitted response it will be considered complete and count towards the progress in your dataset. + +Sometimes, you want to have more than one submitted response per record, for example, if you want to analyze the inter-annotator agreement in your task. In that case, make sure to change this setting to a higher number, but always smaller or equal to the total number of annotators. If you're working on the task alone, you want this setting to be 1. + +## Annotate records + +>[!TIP] +>💡 If you are deploying Argilla in a Hugging Face Space, any team members will be able to log in using the Hugging Face OAuth. Otherwise, you may need to create users for them following [this guide](https://docs.argilla.io/latest/how_to_guides/user/). + +When you open your dataset, you will realize that the first question is already filled in with some suggested labels. That's because in the previous section we mapped our question called `label` to the `label_text` column in the dataset, so that we simply need to review and correct the already existing labels: + +Screenshot of the dataset in Argilla. + +For the token classification, we'll need to add all labels manually, as we didn't include any suggestions. This is how it might look after the span annotations: + +Screenshot of the dataset in Argilla with spans annotated. + +As you move through the different records, there are different actions you can take: +- submit your responses, once you're done with the record. +- save them as a draft, in case you want to come back to them later. +- discard them, if the record souldn't be part of the dataset or you won't give responses to it. + +In the next section, you will learn how you can export and use those annotations. diff --git a/chapters/en/chapter10/5.mdx b/chapters/en/chapter10/5.mdx new file mode 100644 index 000000000..7a3b01f1b --- /dev/null +++ b/chapters/en/chapter10/5.mdx @@ -0,0 +1,69 @@ +# Use your annotated dataset[[use-your-annotated-dataset]] + + + +We will learn now how to export and use the annotated data that we have in Argilla. + +## Load the dataset + +First, we'll need to make sure that we're connected to our Argilla instance as in the previous steps: + +```python +import argilla as rg + +HF_TOKEN = "..." # only for private spaces + +client = rg.Argilla( + api_url="...", + api_key="...", + headers={"Authorization": f"Bearer {HF_TOKEN}"}, # only for private spaces +) +``` + +And now, we'll load the dataset that we'll be working with: + +```python +dataset = client.datasets(name="ag_news") +``` + +Loading the dataset and calling its records with `dataset.records` is enough to start using your dataset and records for your own purposes and pipelines. However, we'll also learn how to do a few optional operations, like filtering the records and exporting your dataset to the Hugging Face Hub. + +## Filter the dataset + +Sometimes you only want to use the records that have been completed, so we will first filter the records in our dataset based on their status: + +```python +status_filter = rg.Query(filter=rg.Filter([("status", "==", "completed")])) + +filtered_records = dataset.records(status_filter) +``` + +>[!TIP] +>⚠️ Note that the records with `completed` status (i.e., records that meet the minimum submitted responses configured in the task distribution settings) could have more than one response and that each response can have any status from `submitted`, `draft` or `discarded`. + +Learn more about querying and filtering records in the [Argilla docs](https://docs.argilla.io/latest/how_to_guides/query/). + +## Export to the Hub + +We can now export our annotations to the Hugging Face Hub, so we can share them with others. To do this, we'll need to convert the records into a 🤗 Dataset and then push it to the Hub: + +```python +filtered_records.to_datasets().push_to_hub("argilla/ag_news_annotated") +``` + +Alternatively, we can export directly the complete Argilla dataset (including pending records) like this: + +```python +dataset.to_hub(repo_id="argilla/ag_news_annotated") +``` + +This is an interesting choice in case others want to open the dataset in their Argilla instances, as the settings are automatically saved and they can simply import the full dataset using a single line of code: + +```python +dataset = rg.Dataset.from_hub(repo_id="argilla/ag_news_annotated") +``` diff --git a/chapters/en/chapter10/6.mdx b/chapters/en/chapter10/6.mdx new file mode 100644 index 000000000..a65b9e3c8 --- /dev/null +++ b/chapters/en/chapter10/6.mdx @@ -0,0 +1,20 @@ +# Argilla, check![[argilla-check]] + + + +That's all! Congrats! 👏 + +In this chapter, you learnt the basic steps to: +- set up Argilla. +- annotate to improve the quality of your dataset. +- adapt an existing dataset and re-use it for a different NLP task. +- share your annotated dataset with the community in the Hugging Face Hub. + +## What's next? +- Check more step-by-step tutorials for other popular tasks in the [tutorials page](https://docs.argilla.io/latest/tutorials/). +- You can also explore other examples of datasets in this [demo](https://demo.argilla.io/sign-in?auth=ZGVtbzoxMjM0NTY3OA==). +- If you'd like to keep learning about Argilla and more advanced features, check the [Argilla documentation](https://docs.argilla.io/latest/). + \ No newline at end of file diff --git a/chapters/en/chapter10/7.mdx b/chapters/en/chapter10/7.mdx new file mode 100644 index 000000000..1ae6c0d7b --- /dev/null +++ b/chapters/en/chapter10/7.mdx @@ -0,0 +1,187 @@ + + +# End-of-chapter quiz[[end-of-chapter-quiz]] + + + +Let's test what you learned in this chapter! + +### 1. What can you use Argilla for? + + + +### 2. Argilla ONLY works in the Hugging Face Spaces and with Hugging Face Datasets. + + + +### 3. You need a Hugging Face token to connect the Python SDK to your Argilla server. + + + +### 4. What are **fields** in Argilla? How many fields can you use? + + + +### 5. What's the best type of question for a token classification task? + + + +### 6. What is the purpose of the "Save as draft" button? + + + +### 7. Argilla does not offer suggested labels automatically, you need to provide that data yourself. + + + +### 8. Select all the necessary steps to export an Argilla dataset in full to the Hub: + +client= rg.Argilla(api_url='...', api_key='...')", + explain: "Yes, to interact with your server you'll need to instantiate it first.", + correct: true + }, + { + text: "Import the dataset from the hub: dataset = rg.Dataset.from_hub(repo_id='argilla/ag_news_annotated')", + explain: "No. This is to import a dataset from the Hub into your Argilla instance.", + }, + { + text: "Load the dataset: dataset = client.datasets(name='my_dataset')", + explain: "Yes, you'll need this for further operations", + correct: true + }, + { + text: "Convert the Argilla dataset into a Datasets dataset: dataset = dataset.to_datasets()", + explain: "This is not needed if you export the full dataset. Argilla will take care of this for you. However, you might need it if you're working with a subset of records." + }, + { + text: "Use the to_hub method to export the dataset: dataset.to_hub(repo_id='my_username/dataset_name')", + explain: "This will push the dataset to the indicated repo id, and create a new repo if it doesn't exist.", + correct: true + }, + ]} +/> diff --git a/chapters/en/chapter2/5.mdx b/chapters/en/chapter2/5.mdx index 199877efb..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: diff --git a/chapters/en/chapter3/2.mdx b/chapters/en/chapter3/2.mdx index ebf464fc3..5ac395c8e 100644 --- a/chapters/en/chapter3/2.mdx +++ b/chapters/en/chapter3/2.mdx @@ -88,6 +88,10 @@ The Hub doesn't just contain models; it also has multiple datasets in lots of di 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 diff --git a/chapters/en/chapter7/6.mdx b/chapters/en/chapter7/6.mdx index 6c6418c75..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: diff --git a/chapters/es/chapter0/1.mdx b/chapters/es/chapter0/1.mdx index 47b77485a..cf5aab097 100644 --- a/chapters/es/chapter0/1.mdx +++ b/chapters/es/chapter0/1.mdx @@ -86,7 +86,7 @@ Puedes entrar y salir de tu entorno virtual con los scripts `activate` y `deacti source .env/bin/activate # Desactiva el entorno virtual -source .env/bin/deactivate +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/chapter3/2.mdx b/chapters/es/chapter3/2.mdx index 624749727..088bbfe03 100644 --- a/chapters/es/chapter3/2.mdx +++ b/chapters/es/chapter3/2.mdx @@ -91,6 +91,10 @@ El Hub no solo contiene modelos; sino que también tiene múltiples conjunto de 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: + +⚠️ **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 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 696135a20..9b50e29e3 100644 --- a/chapters/fa/chapter3/2.mdx +++ b/chapters/fa/chapter3/2.mdx @@ -105,6 +105,9 @@ model.train_on_batch(batch, labels)
+ +⚠️ **هشدار** مطمئن شوید که `datasets` نصب شده است. برای اطمینان، دستور `pip install datasets` را اجرا کنید. سپس، مجموعه داده MRPC را بارگذاری کنید و آن را چاپ کنید تا ببینید چه چیزی در آن وجود دارد. + ```py from datasets import load_dataset 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 0aa69be72..f0f74bd1c 100644 --- a/chapters/fr/chapter3/2.mdx +++ b/chapters/fr/chapter3/2.mdx @@ -96,6 +96,10 @@ Le *Hub* ne contient pas seulement des modèles mais aussi plusieurs jeux de don 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 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/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 066bfddfc..501c3dc5e 100644 --- a/chapters/hi/chapter3/2.mdx +++ b/chapters/hi/chapter3/2.mdx @@ -88,6 +88,10 @@ model.train_on_batch(batch, labels) 🤗 डेटासेट लाइब्रेरी एक बहुत ही सरल कमांड प्रदान करती है हब पर डेटासेट को डाउनलोड और कैश करने के लिए। हम MRPC डेटासेट को इस तरह डाउनलोड कर सकते हैं: + +⚠️ **चेतावनी** सुनिश्चित करें कि `datasets` स्थापित है। इसके लिए `pip install datasets` चलाएँ। फिर, MRPC डेटासेट को लोड करें और देखें कि इसमें क्या है। + + ```py from datasets import load_dataset 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 0b428d08d..c83c1601a 100644 --- a/chapters/it/chapter3/2.mdx +++ b/chapters/it/chapter3/2.mdx @@ -88,6 +88,10 @@ L'Hub non contiene solo modelli; contiene anche molti dataset in tante lingue di 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 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/ne/_toctree.yml b/chapters/ne/_toctree.yml new file mode 100644 index 000000000..637b10081 --- /dev/null +++ b/chapters/ne/_toctree.yml @@ -0,0 +1,4 @@ +- title: 0. सेटअप + sections: + - local: chapter0/1 + title: परिचय diff --git a/chapters/ne/chapter0/1.mdx b/chapters/ne/chapter0/1.mdx new file mode 100644 index 000000000..0ec58569e --- /dev/null +++ b/chapters/ne/chapter0/1.mdx @@ -0,0 +1,116 @@ + +# परिचय + +नमस्कार! Hugging Face कोर्समा स्वागत छ! यो परिचयले तपाईंलाई काम गर्ने वातावरण सेट गर्न सिकाउँछ। यदि तपाईं भर्खरै कोर्स सुरु गर्दै हुनुहुन्छ भने, हामी सुझाव दिन्छौं कि पहिले [अध्याय १](/course/chapter1) हेर्नुहोस्, अनि फर्केर आफ्नो वातावरण सेट गर्नुहोस् ताकि तपाईं आफैं कोड गर्न सक्नुहुनेछ। + +यो कोर्समा हामीले प्रयोग गर्ने सबै लाइब्रेरीहरू Python प्याकेजको रूपमा पाइन्छ। यहाँ हामी तपाईंलाई Python वातावरण कसरी सेट गर्ने र चाहिने लाइब्रेरीहरू कसरी इन्स्टल गर्ने भनेर देखाउँछौं। + +तपाईंको काम गर्ने वातावरण सेट गर्न दुईवटा तरिका छन् - Colab नोटबुक वा Python भर्चुअल इन्भाइरोमेन्ट। जुन तरिका तपाईंलाई ठिक लाग्छ त्यही रोज्न सक्नुहुन्छ। नयाँ सिकारुहरूलाई चाहिँ Colab नोटबुकबाट सुरु गर्न सल्लाह दिन्छौं। + +याद गर्नुहोस्, हामी Windows सिस्टमको बारेमा केही बताउँदैनौं। Windows चलाइरहनुभएको छ भने Colab नोटबुक प्रयोग गर्नुहोस्। Linux वा macOS चलाइरहनुभएको छ भने माथिका दुवै तरिका प्रयोग गर्न सक्नुहुन्छ। + +यो कोर्सको धेरैजसो भागमा Hugging Face अकाउन्ट चाहिन्छ। अहिले नै एउटा बनाउनुहोस्: [अकाउन्ट बनाउनुहोस्](https://huggingface.co/join)। + +## Google Colab नोटबुक प्रयोग गर्ने तरिका + +Colab नोटबुक प्रयोग गर्नु सबैभन्दा सजिलो तरिका हो। ब्राउजरमा नोटबुक खोल्नुहोस् र सिधै कोडिङ सुरु गर्नुहोस्! + +Colab बारे थाहा छैन भने, [परिचय](https://colab.research.google.com/notebooks/intro.ipynb) हेरेर सुरु गर्नुहोस्। Colab मा GPUs वा TPUs जस्ता छिटो चल्ने हार्डवेयर पनि प्रयोग गर्न पाइन्छ, र साना कामहरूका लागि सित्तैमा पाइन्छ। + +Colab मा काम गर्न सजिलो लाग्यो भने, नयाँ नोटबुक बनाएर सेटअप सुरु गर्नुहोस्। + +
+खाली Colab नोटबुक +
+ +अब हामीलाई चाहिने लाइब्रेरीहरू इन्स्टल गर्नुपर्छ। यसको लागि हामी Python को प्याकेज म्यानेजर `pip` प्रयोग गर्छौं। नोटबुकमा सिस्टम कमाण्डहरू चलाउन `!` चिन्ह लगाउनुपर्छ। त्यसैले 🤗 Transformers लाइब्रेरी यसरी इन्स्टल गर्न सकिन्छ: +``` +!pip install transformers +``` + + +लाइब्रेरी राम्रोसँग इन्स्टल भयो कि भएन जाँच गर्न Python मा import गरेर हेर्न सक्नुहुन्छ: + +```python +import transformers +``` + +
+माथिका दुई कमाण्डको नतिजा देखाउने GIF: installation र import +
+ +यसले 🤗 Transformers को सबैभन्दा हल्का भर्जन इन्स्टल गर्छ। यसमा PyTorch वा TensorFlow जस्ता मेशिन लर्निङ फ्रेमवर्कहरू समावेश हुँदैनन्। किनभने हामी लाइब्रेरीका धेरै फिचरहरू प्रयोग गर्नेछौं, हामी डेभलपमेन्ट भर्जन इन्स्टल गर्न सुझाव दिन्छौं, जसमा सबै आवश्यक डिपेन्डेन्सीहरू समावेश छन्: + +``` +!pip install transformers[sentencepiece] +``` + + +यसले केही समय लिन्छ, तर त्यसपछि तपाईं कोर्सको बाँकी भागको लागि तयार हुनुहुनेछ! + +## Python भर्चुअल इन्भाइरोमेन्ट प्रयोग + +यदि तपाईं Python भर्चुअल इन्भाइरोमेन्ट प्रयोग गर्न चाहनुहुन्छ भने, पहिलो चरणमा आफ्नो सिस्टममा Python इन्स्टल गर्नुपर्छ। सुरु गर्नको लागि [यो गाइड](https://realpython.com/installing-python/) हेर्न सुझाव दिन्छौं। + +Python इन्स्टल गरिसकेपछि, तपाईंले टर्मिनलमा Python कमाण्डहरू चलाउन सक्नुहुनेछ। सुरुमा यो कमाण्ड चलाएर Python ठीकसँग इन्स्टल भएको छ कि छैन जाँच गर्न सक्नुहुन्छ: `python --version`। यसले तपाईंको सिस्टममा भएको Python को भर्जन देखाउनुपर्छ। + +जब तपाईं `python --version` जस्तो कमाण्ड चलाउनुहुन्छ, त्यो तपाईंको सिस्टमको "मुख्य" Python ले चलाएको हो भन्ने बुझ्नुपर्छ। हामी सुझाव दिन्छौं कि यो मुख्य इन्स्टलेसनलाई खाली राख्नुहोस्, र हरेक एप्लिकेसनको लागि छुट्टै इन्भाइरोमेन्ट बनाउनुहोस् - यसरी हरेक एप्लिकेसनको आफ्नै डिपेन्डेन्सी र प्याकेजहरू हुन्छ र अरु एप्लिकेसनहरूसँग कम्प्याटिबिलिटी समस्या आउँदैन। + +Python मा यो [*भर्चुअल इन्भाइरोमेन्ट*](https://docs.python.org/3/tutorial/venv.html) को माध्यमबाट गरिन्छ, जुन एउटा छुट्टै डाइरेक्टरी हो जसमा निश्चित Python भर्जन र एप्लिकेसनलाई चाहिने सबै प्याकेजहरू हुन्छन्। यस्तो भर्चुअल इन्भाइरोमेन्ट धेरै टूलहरूले बनाउन सक्छन्, तर हामी +अफिसियल Python प्याकेज [`venv`](https://docs.python.org/3/library/venv.html#module-venv) प्रयोग गर्छौं। + +पहिले, आफ्नो एप्लिकेसन राख्ने डाइरेक्टरी बनाउनुहोस् - उदाहरणको लागि, होम डाइरेक्टरीमा *transformers-course* नामको नयाँ फोल्डर: + +``` +mkdir ~/transformers-course +cd ~/transformers-course +``` + + +यो डाइरेक्टरीभित्र, Python को `venv` मोड्युल प्रयोग गरेर भर्चुअल इन्भाइरोमेन्ट बनाउनुहोस्: + +``` +python -m venv .env +``` + + +अब तपाईंको खाली फोल्डरमा *.env* नामको डाइरेक्टरी हुनुपर्छ: + +``` +ls -a +``` + + +```out +. .. .env +``` + +तपाईं activate र deactivate स्क्रिप्टहरू प्रयोग गरेर भर्चुअल इन्भाइरोमेन्टमा प्रवेश र बाहिर निस्कन सक्नुहुन्छ: + + +``` +# भर्चुअल इन्भाइरोमेन्ट सक्रिय गर्न +source .env/bin/activate + +# भर्चुअल इन्भाइरोमेन्ट निष्क्रिय गर्न +deactivate +``` + + +इन्भाइरोमेन्ट सक्रिय भएको छ कि छैन जाँच गर्न `which python` कमाण्ड चलाउन सक्नुहुन्छ: यदि यसले भर्चुअल इन्भाइरोमेन्टतर्फ इंगित गर्छ भने, तपाईंले सफलतापूर्वक सक्रिय गर्नुभएको छ! + +``` +which python +``` + +```out +/home//transformers-course/.env/bin/python +``` + +### डिपेन्डेन्सीहरू इन्स्टल गर्ने + +अघिल्लो Google Colab खण्डमा जस्तै, अब तपाईंले आवश्यक प्याकेजहरू इन्स्टल गर्नुपर्छ। फेरि पनि, pip प्याकेज म्यानेजर प्रयोग गरेर 🤗 Transformers को डेभलपमेन्ट भर्जन इन्स्टल गर्न सक्नुहुन्छ: +``` +pip install "transformers[sentencepiece]" +``` +अब तपाईंको सेटअप पूरा भयो र तपाईं अगाडि बढ्न तयार हुनुहुन्छ! 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/ru/_toctree.yml b/chapters/ru/_toctree.yml index 46b889425..ee3b0d8f1 100644 --- a/chapters/ru/_toctree.yml +++ b/chapters/ru/_toctree.yml @@ -24,7 +24,7 @@ - local: chapter1/9 title: Итоги - local: chapter1/10 - title: Проверка знаний + title: Тест в конце главы - title: 2. Использование 🤗 Transformers sections: @@ -43,7 +43,7 @@ - local: chapter2/7 title: Базовое использование завершено! - local: chapter2/8 - title: Итоговый тест по главе + title: Тест в конце главы quiz: 2 - title: 3. Fine-tuning предобученной модели @@ -60,7 +60,7 @@ - local: chapter3/5 title: Fine-tuning, итоги! - local: chapter3/6 - title: Итоговый тест по главе + title: Тест в конце главы quiz: 3 - title: 4. Hugging Face Hub @@ -76,7 +76,7 @@ - local: chapter4/5 title: Первая часть завершена! - local: chapter4/6 - title: Итоговый тест по главе + title: Тест в конце главы quiz: 4 - title: 5. Библиотека 🤗 Datasets @@ -94,7 +94,7 @@ - local: chapter5/7 title: 🤗 Datasets, итоги! - local: chapter5/8 - title: Тест по главе 5 + title: Тест в конце главы - title: 6. Бибилиотека 🤗 Tokenizers sections: diff --git a/chapters/ru/chapter0/1.mdx b/chapters/ru/chapter0/1.mdx index 72fce3460..f2f9f5215 100644 --- a/chapters/ru/chapter0/1.mdx +++ b/chapters/ru/chapter0/1.mdx @@ -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]] -# Итоговый тест по главе[[end-of-chapter-quiz]] +# Тест в конце главы[[end-of-chapter-quiz]] +⚠️ **Предупреждение** Убедитесь, что `datasets` установлены, выполнив `pip install datasets`. Затем загрузите набор данных MRPC и выведите его, чтобы увидеть, что он содержит. + + ```py from datasets import load_dataset 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]] -# Тест по главе 5 +# Тест в конце главы[[end-of-chapter-quiz]] +Un notebook Colab gol +
+ +Următorul pas este să instalezi librăriile pe care le vom folosi în acest curs. Vom folosi `pip` pentru instalare, care este managerul de packages pentru Python. În notebook-uri, poți rula comenzi de sistem începând comanda cu caracterul `!`, așa că poți instala librăria 🤗 Transformers astfel: + +``` +!pip install transformers +``` + +Te poți asigura că pachetul a fost instalat corect importându-l în cadrul mediului tău Python: + +``` +import transformers +``` + +
+O animație care arată rezultatul celor două comenzi de mai sus: instalare și importarea +
+ +Aceasta instalează o versiune foarte ușoară a 🤗 Transformers. În special, nu sunt instalate framework-uri specifice de machine learning (cum ar fi PyTorch sau TensorFlow). Deoarece vom folosi multe caracteristici diferite ale librăriei, îți recomandăm să instalezi versiunea pentru development, care vine cu toate dependențele necesare pentru cam orice caz de utilizare imaginabil: + +``` +!pip install transformers[sentencepiece] +``` + +Aceasta va dura puțin timp, dar apoi vei fi gata de drum pentru restul cursului! + +## Folosirea unui virtual environment Python[[folosirea-unui-virtual-environment-python]] + +Dacă preferi să folosești un mediu virtual Python, primul pas este să instalezi Python pe sistemul tău. Îți recomandăm să urmezi [această ghidare](https://realpython.com/installing-python/) pentru a începe. + +Odată ce ai Python instalat, ar trebui să poți rula comenzi Python în terminalul tău. Poți începe rulând următoarea comandă pentru a te asigura că este instalat corect înainte de a trece la pașii următori: `python --version`. Aceasta ar trebui să afișeze versiunea Python disponibilă acum pe sistemul tău. + +Când rulezi o comandă Python în terminalul tău, cum ar fi `python --version`, ar trebui să te gândești la programul care rulează comanda ta ca la Python-ul "principal" de pe sistemul tău. Îți recomandăm să păstrezi această instalare principală liberă de orice pachete și să o folosești pentru a crea environment-uri separate pentru fiecare aplicație pe care lucrezi — în acest fel, fiecare aplicație poate avea propriile sale dependențe și pachete, și nu va trebui să te preocupi de problemele de compatibilitate potențiale cu alte aplicații. + +În Python, acest lucru se face prin [*virtual environments*](https://docs.python.org/3/tutorial/venv.html), care sunt directory trees autonomi ce conțin fiecare o instalare Python cu o anumită versiune Python împreună cu toate pachetele de care are nevoie aplicația. Crearea unui astfel de mediu virtual poate fi realizată cu mai multe instrumente diferite, dar vom folosi pachetul oficial Python pentru acest scop, denumit [`venv`](https://docs.python.org/3/library/venv.html#module-venv). + +În primul rând, creează folder în care dorești ca aplicația ta să locuiască — de exemplu, ai putea dori să faci un nou folder numit *transformers-course* în rădăcina folderului tău personal: + +``` +mkdir ~/transformers-course +cd ~/transformers-course +``` + +Din interiorul acestui folder, creează un virtual environment folosind modulul Python `venv`: + +``` +python -m venv .env +``` + +Acum ar trebui să ai un folder numit *.env* în folderul tău altfel gol: + +``` +ls -a +``` + +```out +. .. .env +``` + +Poți să intri și să ieși din environment folosind comenzile `activate` și `deactivate`: + +``` +# Activează mediul virtual +source .env/bin/activate + +# Dezactivează virtual environment-ul +deactivate +``` + +Te poți asigura că environment-ul este activat rulând comanda `which python`: dacă aceasta indică către virtual environment, atunci l-ai activat cu succes! + +``` +which python +``` + +```out +/home//transformers-course/.env/bin/python +``` + +### Instalarea dependențelor[[instalarea-dependențelor]] + +La fel ca în secțiunea anterioară despre utilizarea instanțelor Google Colab, acum va trebui să instalezi pachetele necesare pentru a continua. Din nou, poți instala versiunea pentru development a 🤗 Transformers folosind managerul de pachete `pip`: + +``` +pip install "transformers[sentencepiece]" +``` + +Acum ești gata să începi! \ No newline at end of file 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 7e33dd154..a9479c075 100644 --- a/chapters/th/chapter3/2.mdx +++ b/chapters/th/chapter3/2.mdx @@ -88,6 +88,10 @@ Hub นั้นไม่ได้เก็บเพียงแค่โมเ ไลบรารี่ 🤗 Datasets library มีคำสั่งที่ใช้งานได้ง่ายมากในการดาวโหลดและ cache ชุดข้อมูลที่อยู่บน Hub เราสามารถดาวโหลดชุดข้อมูล MRPC ได้ดังนี้: + +⚠️ **คำเตือน** ตรวจสอบให้แน่ใจว่า `datasets` ได้ถูกติดตั้งโดยการรัน `pip install datasets` ก่อน จากนั้นโหลดชุดข้อมูล MRPC และพิมพ์เพื่อดูว่ามีอะไรบ้าง + + ```py from datasets import load_dataset 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/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..4861a6d1f 100644 --- a/chapters/zh-CN/chapter0/1.mdx +++ b/chapters/zh-CN/chapter0/1.mdx @@ -1,34 +1,34 @@ # 课程简介 [[课程简介]] -欢迎来到拥抱脸课程!本介绍将指导您设置工作环境。如果您刚开始学习本课程,我们建议您先阅读[第一章](/course/chapter1), 然后再回来设置您的环境,以便您可以自己尝试运行代码。 +欢迎来到 Hugging Face 课程!本小节将指导你搭建工作环境。如果你刚开始学习本课程,我们建议你先阅读 [第一章](/course/chapter1) ,然后返回此处搭建环境,以便你亲自尝试和实现代码。 -我们将在本课程中使用的所有库都以 Python 包的形式提供,因此在这里我们将向您展示如何设置 Python 环境并安装您需要的特定库。 +本课程涉及的所有库均以 Python 包形式提供,因此在这里我们将展示如何搭建 Python 环境并安装所需要的库。 -我们将介绍两种设置工作环境的方法,使用 Colab 笔记本或 Python 虚拟环境。选择任意一种趁手的方式即可。对于初学者,我们强烈建议您从使用 Colab 笔记本开始(国内无法访问,可以跳过,直接查阅本页**安装依赖**那一节即可在本地的环境下运行,译者注)。 +我们将介绍两种设置工作环境的方法,使用 Colab Notebook 或 Python 虚拟环境。选择任意一种趁手的方式即可。对于初学者,我们强烈建议你从使用 Colab Notebook 开始(国内无法访问,可以跳过,直接查阅本页 **安装依赖** 那一节即可在本地的环境下运行,译者注)。 -请注意,Python 虚拟环境的一些命令不支持Windows系统。如果您在 Windows 上运行,我们建议您继续使用 Colab 笔记本。如果您使用的是 Linux 发行版或 macOS,则可以使用此处的任一方法。 +请注意,Python 虚拟环境的一些命令不支持 Windows 系统。如果你使用的操作系统是 Windows,我们建议你继续使用 Colab Notebook 如果你使用的操作系统是 Linux 发行版或 macOS,则可以使用此处的任一方法。 -大多数课程和服务都依赖于您拥有 Hugging Face 帐户。我们建议现在创建一个:[创建一个账号](https://huggingface.co/join). +大多数课程和服务都依赖于你拥有 Hugging Face 帐户。我们建议现在创建一个: [创建一个账号](https://huggingface.co/join) 。 -## 使用 Google Colab 笔记本 [[使用 Google Colab 笔记本]] +## 使用 Google Colab Notebook [[使用 Google Colab Notebook ]] -使用 Colab notebook 是最简单的设置;可以在浏览器中启动Notebook并直接开始编写自己的代码! +使用 Colab Notebook 是最简单的搭建方式;可以在浏览器中启动 Notebook 并直接开始编写自己的代码! -如果您不熟悉 Colab,我们建议您从[这个介绍](https://colab.research.google.com/notebooks/intro.ipynb)开始。Colab 提供一些加速硬件,例如 GPU 或 TPU,并且当我们使用的算力比较少的时候是免费的。 +如果你不熟悉 Colab,建议你先查看 [Colab 使用入门](https://colab.research.google.com/notebooks/intro.ipynb) 。Colab 提供一些加速硬件,例如 GPU 或 TPU,并且当我们使用的算力比较少的时候是免费的。 -当打开 Colab 后,创建一个新笔记本: +当打开 Colab 后,创建一个新的 Notebook
An empty colab notebook
-下一步是安装我们将在本课程中使用的库。我们将使用 **pip** 进行安装,它是 Python 的包管理器。在Notebook中,您可以通过加上!字符表示执行系统命令,所以安装🤗 Transformers 的命令如下: +下一步是安装我们将在本课程中使用的库。我们将使用 **pip** 进行安装,它是 Python 的包管理器。在 Notebook 中,你可以通过加上!字符表示执行系统命令,所以安装🤗 Transformers 的命令如下: ``` !pip install transformers ``` -您可以通过在运行 Python 时导入包来判断是否正确安装了该包: +你可以通过在运行 Python 时导入包来判断是否正确安装了该包: ``` import transformers @@ -38,25 +38,25 @@ import transformers A gif showing the result of the two commands above: installation and import -这将安装一个非常轻量级的 🤗 Transformers。并没有安装机器学习框架(如 PyTorch 或 TensorFlow)。由于之后我们将使用该库的许多不同功能,我们建议安装开发版本,它带有几乎所有所需的依赖项: +这将安装一个没有安装机器学习框架(如 PyTorch 或 TensorFlow)非常轻量级的 🤗 Transformers。由于之后我们将使用该库的许多不同功能,我们建议安装开发版本,它带有几乎所有所需的依赖项: ``` !pip install transformers[sentencepiece] ``` -这将需要一些时间,但当完成之后您就做好学习剩下的课程的环境准备了! +这将需要一些时间,但当完成之后你就做好学习剩下的课程环境的全部准备了! ## 使用 Python 虚拟环境 [[使用 Python 虚拟环境]] -如果您更喜欢使用 Python 虚拟环境,那么第一步是在您的系统上安装 Python。我们建议您按照[这个教程](https://realpython.com/installing-python/)进行配置。 +如果你更喜欢使用 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). +在 Python 中,这是通过 [*虚拟环境*](https://docs.python.org/3/tutorial/venv.html) 实现的,虚拟环境会创建许多目录树,每个目录树都包含具有特定 Python 版本的 Python 安装以及应用程序所需的所有包。可以使用许多不同的工具来创建这样的虚拟环境,但在此我们将使用官方 Python 包: [`venv`](https://docs.python.org/3/library/venv.html#module-venv) 。 -首先,创建您希望Transformers所在的目录 - 例如,您可能希望在主目录的根目录下创建一个名为 *Transformers-course* 的新目录: +首先,创建你希望 Transformers 所在的目录 - 例如,你可能希望在主目录的根目录下创建一个名为 `Transformers-course` 的新目录: ``` mkdir ~/transformers-course @@ -69,7 +69,7 @@ cd ~/transformers-course python -m venv .env ``` -您现在应该在原本为空的文件夹中看到一个名为 *.env* 的目录: +你现在应该在原本为空的文件夹中看到一个名为 `.env` 的目录: ``` ls -a @@ -79,17 +79,17 @@ ls -a . .. .env ``` -您可以使用`activate`和`deactivate`命令来控制进入和退出您的虚拟环境: +你可以使用 `activate` 和 `deactivate` 命令来控制进入和退出你的虚拟环境: ``` -# Activate the virtual environment +# 激活虚拟环境 source .env/bin/activate -# Deactivate the virtual environment -source .env/bin/deactivate +# 退出虚拟环境 +deactivate ``` -您可以通过运行 `which python` 命令来检测虚拟环境是否被激活:如果它指向虚拟环境,那么您已经成功激活了它! +你可以通过运行 `which python` 命令来检测虚拟环境是否被激活:如果它指向虚拟环境,那么你已经成功激活了它! ``` which python @@ -101,10 +101,10 @@ which python ### 安装依赖 [[安装依赖]] -与使用 Google Colab 实例的上一节一样,您现在需要安装继续所需的包。 同样,您可以使用 `pip` 包管理器安装 🤗 Transformers 的开发版本: +与前面使用 Google Colab 的部分一样,你现在需要安装继续学习所需的软件包。同样,你可以使用 `pip` 包管理器安装 🤗 Transformers 的开发版本: ``` pip install "transformers[sentencepiece]" ``` -您现在已准备就绪,可以开始了! \ No newline at end of file +你现在已准备就绪,可以开始了! \ No newline at end of file diff --git a/chapters/zh-CN/chapter1/1.mdx b/chapters/zh-CN/chapter1/1.mdx index 18d638949..e509d628f 100644 --- a/chapters/zh-CN/chapter1/1.mdx +++ b/chapters/zh-CN/chapter1/1.mdx @@ -5,12 +5,11 @@ classNames="absolute z-10 right-0 top-0" /> -## 欢迎来到🤗课程 [[欢迎来到🤗课程]] +## 欢迎来到🤗 Hugging Face 课程 [[欢迎来到🤗 Hugging Face 课程]] -本课程将使用 Hugging Face 生态系统中的库——🤗 Transformers、🤗 Datasets、🤗 Tokenizers 和 🤗 Accelerate——以及 Hugging Face Hub 教你自然语言处理 (NLP)。它是完全免费的,并且没有广告。 - +本课程将教你如何使用 Hugging Face 生态系统的库进行自然语言处理(NLP)。这些库包括 🤗 Transformers、🤗 Datasets、🤗 Tokenizers 和 🤗 Accelerate,以及 Hugging Face Hub。本课程完全免费且无广告。 ## 有什么是值得期待的?[[有什么是值得期待的?]] @@ -21,74 +20,74 @@ -- 第 1 章到第 4 章介绍了 🤗 Transformers 库的主要概念。在本课程的这一部分结束时,您将熟悉 Transformer 模型的工作原理,并将了解如何使用 [Hugging Face Hub](https://huggingface.co/models) 中的模型,在数据集上对其进行微调,并在 Hub 上分享您的结果。 -- 第 5 章到第 8 章在深入研究经典 NLP 任务之前,教授 🤗 Datasets和 🤗 Tokenizers的基础知识。在本部分结束时,您将能够自己解决最常见的 NLP 问题。 -- 第 9 章到第 12 章更加深入,探讨了如何使用 Transformer 模型处理语音处理和计算机视觉中的任务。在此过程中,您将学习如何构建和分享模型,并针对生产环境对其进行优化。在这部分结束时,您将准备好将🤗 Transformers 应用于(几乎)任何机器学习问题! +- 第 1 章到第 4 章介绍了 🤗 Transformers 库的主要概念。在本课程的这一部分结束时,你将了解 Transformer 模型的工作原理,并将了解如何使用 [Hugging Face Hub](https://huggingface.co/models) 中的模型,在数据集上对其进行微调,并在 Hub 上分享你的结果。 +- 第 5 章到第 8 章在深入研究经典 NLP 任务之前,教授 🤗 Datasets 和 🤗 Tokenizers 的基础知识。在本部分结束时,你将能够自己解决最常见的 NLP 问题。 +- 第 9 章到第 12 章将超越 NLP,探讨如何使用 Transformer 模型解决语音处理和计算机视觉任务。在此过程中,你将学习如何构建和分享模型的演示,并将它们优化为生产环境。完成这部分课程后,你将准备好将 🤗 Transformers 应用于(几乎)任何机器学习问题! 这个课程: * 需要良好的 Python 知识 -* 最好先学习深度学习入门课程,例如[DeepLearning.AI](https://www.deeplearning.ai/) 提供的 [fast.ai实用深度学习教程](https://course.fast.ai/) -* 不需要事先具备 [PyTorch](https://pytorch.org/) 或 [TensorFlow](https://www.tensorflow.org/) 知识,虽然熟悉其中任何一个都会对huggingface的学习有所帮助 +* 在完成入门深度学习课程后效果更佳,例如 [DeepLearning.AI](https://www.deeplearning.ai/) 提供的 [fast.ai实用深度学习教程](https://course.fast.ai/) +* 不需要事先具备 [PyTorch](https://pytorch.org/) 或 [TensorFlow](https://www.tensorflow.org/) 知识,虽然熟悉其中任何一个都会对学习有所帮助 -完成本课程后,我们建议您查看 [DeepLearning.AI的自然语言处理系列课程](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh),其中涵盖了广泛的传统 NLP 模型,如朴素贝叶斯和 LSTM,这些模型非常值得了解! +完成本课程后,我们建议你查看 [DeepLearning.AI的自然语言处理系列课程](https://www.coursera.org/specializations/natural-language-processing?utm_source=deeplearning-ai&utm_medium=institutions&utm_campaign=20211011-nlp-2-hugging_face-page-nlp-refresh) ,该课程涵盖了诸如朴素贝叶斯和 LSTM 等传统 NLP 模型的广泛内容,非常值得了解! ## 我们是谁?[[我们是谁?]] 关于作者: -[**Abubakar Abid**](https://huggingface.co/abidlabs) 在斯坦福大学获得应用机器学习博士学位。 在攻读博士学位期间,他创立了 [Gradio](https://github.com/gradio-app/gradio),这是一个开源 Python 库,已用于构建超过 600,000 个机器学习演示。 Gradio 被 Hugging Face 收购,Abubakar 现在是该公司的机器学习团队负责人。 + [**Abubakar Abid**](https://huggingface.co/abidlabs) 在斯坦福大学获得应用机器学习博士学位。在攻读博士学位期间,他创立了 [Gradio](https://github.com/gradio-app/gradio) ,这是一个开源 Python 库,已用于构建超过 600,000 个机器学习演示。Gradio 被 Hugging Face 收购,Abubakar 现在在 Hugging Face 担任机器学习团队负责人。 -[**Matthew Carrigan**](https://huggingface.co/Rocketknight1) 是 Hugging Face 的机器学习工程师。他住在爱尔兰都柏林,之前在 Parse.ly 担任机器学习工程师,在此之前,他在Trinity College Dublin担任博士后研究员。他不相信我们会通过扩展现有架构来实现 AGI,但无论如何都对机器人充满希望。 + [**Matthew Carrigan**](https://huggingface.co/Rocketknight1) 是 Hugging Face 的机器学习工程师。他住在爱尔兰都柏林,之前在 Parse.ly 担任机器学习工程师,在此之前,他在 Trinity College Dublin 担任博士后研究员。他不相信我们会通过扩展现有架构来实现 AGI,但仍然对机器人永生抱有很高的期望。 -[**Lysandre Debut**](https://huggingface.co/lysandre) 是 Hugging Face 的机器学习工程师,从早期的开发阶段就一直致力于 🤗 Transformers 库。他的目标是通过使用非常简单的 API 开发工具,让每个人都可以使用 NLP。 + [**Lysandre Debut**](https://huggingface.co/lysandre) 是 Hugging Face 的机器学习工程师,从早期的开发阶段就一直致力于 🤗 Transformers 库。他的目标是通过使用非常简单的 API 开发工具,让 NLP 对每个人都变得易用。 -[**Sylvain Gugger**](https://huggingface.co/sgugger) 是 Hugging Face 的一名研究工程师,也是 🤗Transformers库的核心维护者之一。此前,他是 fast.ai 的一名研究科学家,他与Jeremy Howard 共同编写了[Deep Learning for Coders with fastai and Py Torch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/)。他的主要研究重点是通过设计和改进允许模型在有限资源上快速训练的技术,使深度学习更容易普及。 + [**Sylvain Gugger**](https://huggingface.co/sgugger) 是 Hugging Face 的一名研究工程师,也是 🤗Transformers 库的核心维护者之一。此前,他是 fast.ai 的一名研究科学家,他与 Jeremy Howard 共同编写了 [Deep Learning for Coders with fastai and Py Torch](https://learning.oreilly.com/library/view/deep-learning-for/9781492045519/) 。他的研究重点是通过设计和改进技术,使模型在有限资源上进行快速训练,使深度学习更容易普及。 -[**Dawood Khan**](https://huggingface.co/dawoodkhan82) 是 Hugging Face 的机器学习工程师。 他来自纽约,毕业于纽约大学计算机科学专业。 在担任 iOS 工程师几年后,Dawood 辞职并与其他联合创始人一起创办了 Gradio。 Gradio 最终被 Hugging Face 收购。 + [**Dawood Khan**](https://huggingface.co/dawoodkhan82) 是 Hugging Face 的机器学习工程师。他来自纽约,毕业于纽约大学计算机科学专业。在担任 iOS 工程师几年后,Dawood 辞职并与其他联合创始人一起创办了 Gradio。Gradio 最终被 Hugging Face 收购。 -[**Merve Noyan**](https://huggingface.co/sgugger) 是 Hugging Face 的开发者倡导者,致力于开发工具并围绕它们构建内容,以使每个人的机器学习平民化。 + [**Merve Noyan**](https://huggingface.co/sgugger) 是 Hugging Face 的开发者倡导者,致力于开发工具并围绕它们构建内容,以使每个人都可以使用机器学习。 -[**Lucile Saulnier**](https://huggingface.co/SaulLu) 是 Hugging Face 的机器学习工程师,负责开发和支持开源工具的使用。她还积极参与了自然语言处理领域的许多研究项目,例如协作训练和 BigScience。 + [**Lucile Saulnier**](https://huggingface.co/SaulLu) 是 Hugging Face 的机器学习工程师,负责开发和支持开源工具的使用。她还积极参与了自然语言处理领域的许多研究项目,例如协作训练和 BigScience。 -[**Lewis Tunstall**](https://huggingface.co/lewtun) 是 Hugging Face 的机器学习工程师,专注于开发开源工具并使更广泛的社区可以使用它们。他也是即将出版的一本书[O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/)的作者之一。 + [**Lewis Tunstall**](https://huggingface.co/lewtun) 是 Hugging Face 的机器学习工程师,专注于开发开源工具并使更广泛的社区可以使用它们。他也是即将出版的一本书 [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) 的作者之一。 -[**Leandro von Werra**](https://huggingface.co/lvwerra) 是 Hugging Face 开源团队的机器学习工程师,也是即将出版的一本书[O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/)的作者之一。他拥有多年的行业经验,通过在整个机器学习堆栈中工作,将 NLP 项目投入生产。 + [**Leandro von Werra**](https://huggingface.co/lvwerra) 是 Hugging Face 开源团队的机器学习工程师,也是即将出版的一本书 [O’Reilly book on Transformers](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) 的作者之一。他拥有多年的行业经验,并在整个机器学习技术栈上进行了工作。 ## FAQ[[faq]] -这里有一些经常被提到的问题: +这里有一些经常被提到的问题: - **参加本课程是否会获得认证?** -目前,我们没有获得此课程的任何认证。 但是,我们正在为 Hugging Face 生态系统制定认证计划——敬请期待! +目前我们还没有为这门课程提供认证。然而,我们正在为 Hugging Face 生态系统制定认证计划——敬请期待! - **我应该在这门课程上花多少时间?** -本课程的每一章都设计为在 1 周内完成,每周大约需要 6-8 小时的学习时间。 但是,您可以花尽可能多的时间来完成课程。 +本课程的每一章都设计为在 1 周内完成,每周大约需要 6-8 小时的学习时间。但是,但你可以根据自己的需要随意安排学习时间。 - **如果我有问题,我可以在哪里提问?** -如果您对课程的任何部分有疑问,只需单击页面顶部的“*提问*”横幅,系统就会自动重定向到 [Hugging Face 论坛](https:// discuss.huggingface.co/): +如果你对课程的任何部分有疑问,只需单击页面顶部的“*提问*”横幅,系统就会自动跳转到 [Hugging Face 论坛](https:// discuss.huggingface.co/) : -拥抱脸论坛链接 +Hugging Face论坛链接 -请注意,如果您想在完成课程后进行更多练习,论坛上还提供了[项目灵感](https://discuss.huggingface.co/c/course/course-event/25) 列表。 +请注意,如果你想在完成课程后进行更多练习,论坛上还提供了 [项目灵感](https://discuss.huggingface.co/c/course/course-event/25) 列表,如果你希望在完成课程后进行更多实践,可以参考这些想法。 - **我在哪里可以获得课程的代码?** 对于每个部分,单击页面顶部的横幅可以在 Google Colab 或 Amazon SageMaker Studio Lab 中运行代码: -拥抱脸课程笔记本链接 +Hugging Face课程Notebook 链接 -包含课程所有代码的 Jupyter 笔记本托管在 [`huggingface/notebooks`](https://github.com/huggingface/notebooks) 仓库中。 如果您希望在本地生成它们,请查看 GitHub 上 [`course`](https://github.com/huggingface/course#-jupyter-notebooks) 仓库中的说明。 +包含课程所有代码的 Jupyter Notebook 托管在 [`huggingface/notebooks`](https://github.com/huggingface/notebooks) 仓库中。如果你希望在本地生成它们,请查看 GitHub 上 [`course`](https://github.com/huggingface/course#-jupyter-notebooks) 仓库中的说明。 - **我如何为课程做出贡献?** -有很多方法可以为课程做出贡献! 如果您发现拼写错误或错误,请在 [`course`](https://github.com/huggingface/course) 仓库中提出问题。 如果您想帮助将课程翻译成您的母语,请在[此处](https://github.com/huggingface/course#translating-the-course-into-your-language) 查看说明。 +有很多方法可以为课程做出贡献!如果你发现拼写错误或错误,请在 [`course`](https://github.com/huggingface/course) 仓库中提出问题。如果你想帮助将课程翻译成你的母语,请在 [此处](https://github.com/huggingface/course#translating-the-course-into-your-language) 查看说明。 -- ** 每个翻译的选择是什么?** -每个翻译都有一个词汇表和“TRANSLATING.txt”文件,其中详细说明了为机器学习术语等所做的选择。您可以在 [此处](https://github.com/huggingface/course/blob/main/chapters/de/TRANSLATING.txt)。 +- **翻译的时候有没有术语表** +每个翻译都有一个词汇表和“TRANSLATING.txt”文件,其中详细说明了为机器学习术语等所做的选择。你可以在 [此处](https://github.com/huggingface/course/blob/main/chapters/de/TRANSLATING.txt) 找到德语的示例。 - **我可以使用这门课程再次进行创作吗?** -当然! 该课程是根据宽松的 [Apache 2 许可证](https://www.apache.org/licenses/LICENSE-2.0.html) 发布的。 这意味着您必须按照诚信的原则,提供许可证的链接,并指出是否进行了更改。您可以以任何合理的方式这样做,但不能以任何表明许可方认可您或您的使用的方式。 如果您想引用该课程,请使用以下 BibTeX: +当然!该课程是根据宽松的 [Apache 2 许可证](https://www.apache.org/licenses/LICENSE-2.0.html) 发布的。这意味着你必须按照诚信的原则,提供许可证的链接,并指出是否进行了更改。你可以以任何合理的方式这样做,但不能以任何表明许可方认可你或你的使用的方式。如果你想引用该课程,请使用以下 BibTeX: ``` @misc{huggingfacecourse, @@ -102,7 +101,7 @@ ## 让我们开始吧![[让我们开始吧!]] -你准备好了吗?在本章中,您将学习: -* 如何使用 `pipeline()` 函数解决文本生成、分类等NLP任务 +你准备好了吗?在本章中,你将学习: +* 如何使用 `pipeline()` 函数解决文本生成、分类等 NLP 任务 * 关于 Transformer 架构 * 如何区分编码器、解码器和编码器-解码器架构和用例 diff --git a/chapters/zh-CN/chapter1/10.mdx b/chapters/zh-CN/chapter1/10.mdx index 84a5cb639..2900c46d2 100644 --- a/chapters/zh-CN/chapter1/10.mdx +++ b/chapters/zh-CN/chapter1/10.mdx @@ -2,10 +2,10 @@ # 章末小测试 [[章末小测试]] - +/>` 这一章涵盖了很多内容! 如果有一些不太明白的地方,请不要担心; 下一章将帮助你了解这些模块在底层是如何工作的。 @@ -13,26 +13,25 @@ ### 1. 探索 Hub 并寻找 `roberta-large-mnli` checkpoint。 它可以完成什么类型的任务? - roberta-large-mnli 页面回顾一下." + explain: "点击前往`roberta-large-mnli` 页面 再仔细看一下。" }, { text: "文本分类", - explain: "更准确地说,它对两个句子在三个标签(矛盾、无关、相近)之间的逻辑链接进行分类——这项任务也称为自然语言推理.", + explain: "更准确地说,它能通过三个标签(矛盾,无关,蕴含)分类,判断两句话是否在逻辑上有关联——这项任务也称为自然语言推理(natural language inference)。", correct: true }, { text: "文本生成", - explain: "点击前往roberta-large-mnli 页面回顾一下." + explain: "点击前往`roberta-large-mnli` 页面 ``再仔细看一下。" } ]} /> -### 2. 下面的代码将会返回什么结果? +### 2. 下面的代码将会输出什么结果? ```py from transformers import pipeline @@ -44,16 +43,16 @@ ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") sentiment-analysis pipeline将会返回这些." + text: "它将输出带有 \"positive\" 或者 \"negative\"标签分类的分数。", + explain: "不对 —— sentiment-analysis(情感分析) pipeline才会输出这些。" }, { - text: "它将返回一个生成的文本来完成这句话。", - explain: "这个选项是不对的 — text-generation pipeline将会返回这些.", + text: "它将生成这句话的下一句话。", + explain: "不对 —— text-generation(文本生成) pipeline才会输出这些。", }, { - text: "它将返回代表人员、组织或位置的单词。", - explain: "此外,使用 grouped_entities=True,它会将属于同一实体的单词组合在一起,例如“Hugging Face”。", + text: "它找出代表人员、组织或位置的单词。", + explain: "正解! 此外,使用 grouped_entities=True,可以将属于同一实体的单词组合在一起,例如“Hugging Face”。", correct: true } ]} @@ -71,17 +70,17 @@ result = filler("...") has been waiting for you.", - explain: "这个选项是不对的。 请查看 bert-base-cased 模型卡片,然后再尝试找找错在哪里。" + text: "This <mask> has been waiting for you.", + explain: "不对。 请查看 bert-base-cased 模型卡片,然后再尝试找找错在哪里。" }, { text: "This [MASK] has been waiting for you.", - explain: "正解! 这个模型的mask的掩码是[MASK].", + explain: "正解! 这个模型的mask token是[MASK]。", correct: true }, { text: "This man has been waiting for you.", - explain: "这个选项是不对的。 这个pipeline的作用是填充经过mask的文字,因此它需要在输入的文本中存在mask的token。" + explain: "不对。 这个pipeline的作用是填充经过mask的文字,因此它需要在输入中有一个mask token。" } ]} /> @@ -98,21 +97,21 @@ result = classifier("This is a course about the Transformers library") candidate_labels=[...].", + text: "这个pipeline需要提供用来分类此文本的标签。", + explain: "正解 —— 正确的代码需要包含:candidate_labels=[...]。", correct: true }, { text: "这个pipeline需要多个句子,而不仅仅是一个。", - explain: "这个选项是不对的。尽管正确使用时,此pipeline可以同时处理多个句子(与所有其他pipeline一样)。" + explain: "不对。尽管正确使用时,此pipeline可以同时处理多个句子(所有其他pipeline也是一样)。" }, { - text: "像往常一样,🤗 Transformers库出故障了。", + text: "🤗 Transformers库又出故障了。", explain: "对此,我们不予置评!" }, { text: "该pipeline需要更长的输入; 这个句子太短了。", - explain: "这个选项是不对的。 不过请注意,在这个pipeline处理时,太长的文本将被截断。" + explain: "不对。 不过请注意,在这个pipeline处理太长的文本时会将其截断。" } ]} /> @@ -122,12 +121,12 @@ result = classifier("This is a course about the Transformers library") 自监督,这意味着标签是根据输入自动创建的(例如:预测下一个单词或填充一些[MARSK]单词)。", + explain: "预训练通常是`自监督(self-supervised)`,这意味着标签是根据输入自动创建的(例如:预测下一个单词或填充一些[MARSK]单词)。", correct: true }, { @@ -154,17 +152,17 @@ result = classifier("This is a course about the Transformers library") ]} /> - ### 7. 选择最能描述“模型(model)”、“架构(architecture)”和“权重(weights)”的句子。 + - ### 8. 你将使用以下哪种类型的模型来根据输入的提示生成文本? + @@ -199,15 +197,15 @@ result = classifier("This is a course about the Transformers library") -在进入 Transformer 模型之前,让我们快速概述一下自然语言处理是什么以及我们为什么这么重视它。 +在深入了解 Transformer 模型之前,让我们快速回顾一下自然语言处理是什么以及它为什么如此重要。 -## 什么是自然语言处理? [[什么是自然语言处理?]] +## 什么是自然语言处理?[[什么是自然语言处理?]] -NLP 是语言学和机器学习交叉领域,专注于理解与人类语言相关的一切。 NLP 任务的目标不仅是单独理解单个单词,而且是能够理解这些单词的上下文。 +NLP 是语言学和机器学习交叉领域,专注于理解与人类语言相关的一切。NLP 任务的目标不仅是单独理解单个单词,而且是能够理解这些单词的上下文。 以下是常见 NLP 任务的列表,每个任务都有一些示例: -- **对整个句子进行分类**: 获取评论的情绪,检测电子邮件是否为垃圾邮件,确定句子在语法上是否正确或两个句子在逻辑上是否相关 -- **对句子中的每个词进行分类**: 识别句子的语法成分(名词、动词、形容词)或命名实体(人、地点、组织) -- **生成文本内容**: 用自动生成的文本完成提示,用屏蔽词填充文本中的空白 -- **从文本中提取答案**: 给定问题和上下文,根据上下文中提供的信息提取问题的答案 -- **从输入文本生成新句子**: 将文本翻译成另一种语言,总结文本 +- **对整个句子进行分类**:获取评论的情感,检测电子邮件是否为垃圾邮件,确定句子在语法上是否正确或两个句子在逻辑上是否相关。 +- **对句子中的每个词进行分类**:识别句子的语法成分(名词、动词、形容词)或命名实体(人、地点、组织)。 +- **生成文本内容**:用自动生成的文本完成提示,用掩码词填充文本中的空白。 +- **从文本中提取答案**:给定问题和上下文,根据上下文中提供的信息提取问题的答案。 +- **从输入文本生成新句子**:将文本翻译成另一种语言,对文本进行总结。 -NLP 不仅限于书面文本。它还解决了语音识别和计算机视觉中的复杂挑战,例如生成音频样本的转录或图像描述。 +NLP 不仅限于书面文本。它还解决了语音识别和计算机视觉中的复杂挑战,例如生成音频样本的转录或图像的描述。 ## 为什么具有挑战性?[[为什么具有挑战性?]] -计算机处理信息的方式与人类不同。例如,当我们读到“我饿了”这句话时,我们很容易理解它的意思。同样,给定两个句子,例如“我很饿”和“我很伤心”,我们可以轻松确定它们的相似程度。对于机器学习 (ML) 模型,此类任务更加困难。文本需要以一种使模型能够从中学习的方式进行处理。而且由于语言很复杂,我们需要仔细考虑必须如何进行这种处理。关于如何表示文本已经做了很多研究,我们将在下一章中介绍一些方法。 \ No newline at end of file +计算机处理信息的方式与人类不同。例如,当我们读到“我饿了”这句话时,我们很容易理解它的意思。同样,给定两个句子,例如“我很饿”和“我很伤心”,我们可以轻松确定它们的相似程度。对于机器学习 (ML) 模型,此类任务更加困难。文本需要以一种使模型能够从中学习的方式进行处理。而且由于语言的复杂性,我们很难直接设计算法来处理此类问题。已经有很多关于如何表示文本的研究,我们将在下一章中介绍一些方法。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter1/3.mdx b/chapters/zh-CN/chapter1/3.mdx index 917ff91db..7563b179e 100644 --- a/chapters/zh-CN/chapter1/3.mdx +++ b/chapters/zh-CN/chapter1/3.mdx @@ -1,4 +1,4 @@ -# Transformers能做什么? [[Transformers能做什么?]] +# Transformers 能做什么?[[Transformers 能做什么?]] -在本节中,我们将看看 Transformer 模型可以做什么,并使用 🤗 Transformers 库中的第一个工具:pipeline() 函数。 + +在本节中,我们将看看 Transformer 模型可以做什么,并使用 🤗 Transformers 库中的第一个工具: `pipeline()` 函数。 + -👀 看到那个右上角的 在Colab中打开的按钮了吗? 单击它就可以打开一个包含本节所有代码示例的 Google Colab 笔记本。 每一个有实例代码的小节都会有它。 -如果您想在本地运行示例,我们建议您查看准备. +👀 看到那个右上角的在 Colab 中打开(Open in Colab)的按钮了吗?单击它就可以打开一个包含本节所有代码示例的 Google Colab Notebook 每一个有实例代码的小节都会有它。 + +如果你想在本地运行示例,我们建议你查看第 0 章。 + -## Transformer被应用于各个方面! [[Transformer被应用于各个方面!]] +## Transformers 无处不在![[Transformer 被应用于各个方面!]] + Transformer 模型用于解决各种 NLP 任务,就像上一节中提到的那样。以下是一些使用 Hugging Face 和 Transformer 模型的公司和组织,他们也通过分享他们的模型回馈社区: -![使用 Hugging Face 的公司](https://huggingface.co/course/static/chapter1/companies.PNG) -[🤗 Transformers 库](https://github.com/huggingface/transformers)提供了创建和使用这些共享模型的功能。[模型中心(hub)](https://huggingface.co/models)包含数千个任何人都可以下载和使用的预训练模型。您还可以将自己的模型上传到 Hub! +使用 Hugging Face 的公司 + +[🤗 Transformers 库](https://github.com/huggingface/transformers) 提供了创建和使用这些共享模型的功能。 [Hugging Face 模型中心(以下简称“Hub”)](https://huggingface.co/models) 包含数千个任何人都可以下载和使用的预训练模型。你还可以将自己的模型上传到 Hub! -⚠️ Hugging Face Hub 不限于 Transformer 模型。任何人都可以分享他们想要的任何类型的模型或数据集!创建一个 Huggingface.co 帐户(https://huggingface.co/join)以使用所有可用功能! + +⚠️ Hugging Face Hub 不限于 Transformer 模型。任何人都可以分享他们想要的任何类型的模型或数据集!创建一个 [Huggingface.co 帐户](https://huggingface.co/join) 即可使用所有可用功能! + 在深入研究 Transformer 模型的底层工作原理之前,让我们先看几个示例,看看它们如何用于解决一些有趣的 NLP 问题。 -## 使用pipelines [[使用pipelines]] - - -(这里有一个视频,但是国内可能打不开,译者注) +## 使用 pipelines [[使用 pipelines]] + -🤗 Transformers 库中最基本的对象是 **pipeline()** 函数。它将模型与其必要的预处理和后处理步骤连接起来,使我们能够通过直接输入任何文本并获得最终的答案: +🤗 Transformers 库中最基本的对象是 `pipeline()` 函数。它将模型与所需的预处理和后续处理步骤连接起来,使我们能够通过直接输入任何文本并获得最终的结果: ```python from transformers import pipeline @@ -42,43 +48,44 @@ classifier("I've been waiting for a HuggingFace course my whole life.") ``` ```python out [{'label': 'POSITIVE', 'score': 0.9598047137260437}] -``` +``` 我们也可以多传几句! ```python classifier( ["I've been waiting for a HuggingFace course my whole life.", "I hate this so much!"] ) -``` +``` ```python out [{'label': 'POSITIVE', 'score': 0.9598047137260437}, {'label': 'NEGATIVE', 'score': 0.9994558095932007}] ``` -默认情况下,此pipeline选择一个特定的预训练模型,该模型已针对英语情感分析进行了微调。创建**分类器**对象时,将下载并缓存模型。如果您重新运行该命令,则将使用缓存的模型,无需再次下载模型。 +默认情况下,此 pipeline 加载选定的预训练模型,该模型已针对英语情感分析进行了微调。创建 `classifier` 对象时,模型将被下载和缓存。如果你重新运行该命令,则将使用缓存的模型,而无需再次下载模型。 -将一些文本传递到pipeline时涉及三个主要步骤: +将一些文本传递到 pipeline 时涉及三个主要步骤: 1. 文本被预处理为模型可以理解的格式。 -2. 预处理的输入被传递给模型。 -3. 模型处理后输出最终人类可以理解的结果。 +2. 将预处理后的输入传递给模型。 +3. 对模型的预测进行后续处理并输出最终人类可以理解的结果。 -目前[可用的一些pipeline](https://huggingface.co/transformers/main_classes/pipelines.html)是: +目前 [可用的一些pipeline](https://huggingface.co/transformers/main_classes/pipelines) 有: -* **特征提取**(获取文本的向量表示) -* **填充空缺** -* **ner**(命名实体识别) -* **问答** -* **情感分析** -* **文本摘要** -* **文本生成** -* **翻译** -* **零样本分类** +* `eature-extraction` (获取文本的向量表示) +* `fill-mask` (完形填空) +* `ner` (命名实体识别) +* `question-answering` (问答) +* `sentiment-analysis` (情感分析) +* `summarization` (提取摘要) +* `text-generation` (文本生成) +* `translation` (翻译) +* `zero-shot-classification` (零样本分类) -让我们来看看其中的一些吧! +让我们来看看其中的一些例子吧! ## 零样本分类 [[零样本分类]] -我们将首先处理一项非常具挑战性的任务,我们需要对尚未标记的文本进行分类。这是实际项目中的常见场景,因为注释文本通常很耗时并且需要领域专业知识。对于这项任务**zero-shot-classification**pipeline非常强大:它允许您直接指定用于分类的标签,因此您不必依赖预训练模型的标签。下面的模型展示了如何使用这两个标签将句子分类为正面或负面——但也可以使用您喜欢的任何其他标签集对文本进行分类。 + +我们将从一个具挑战性的任务开始,我们需要对没有标签的文本进行分类。这是实际项目中的常见场景,因为给文本打标签通常很耗时并且需要领域专业知识。对于这项任务 `zero-shot-classification`。pipeline 非常强大:它允许你直接指定用于分类的标签,因此你不必依赖预训练模型的标签。下面的模型展示了如何使用这三个标签将句子分类为 `education(教育)` 、 `politics(政治)` 、或者 `business(商业)` —— 也可以使用你喜欢的任何其他标签集对文本进行分类。 ```python from transformers import pipeline @@ -95,13 +102,17 @@ classifier( 'scores': [0.8445963859558105, 0.111976258456707, 0.043427448719739914]} ``` -此pipeline称为zero-shot,因为您不需要对数据上的模型进行微调即可使用它。它可以直接返回您想要的任何标签列表的概率分数! +这个 pipeline 称为 `zero-shot` (零样本学习),因为你不需要对数据上的模型进行微调即可使用它。它可以直接返回你想要的任何标签列表的概率分数! + -✏️**快来试试吧!**使用您自己的序列和标签,看看模型的行为。 + +✏️**快来试试吧!**使用你自己的序列和标签,看看模型的表现如何。 + ## 文本生成 [[文本生成]] -现在让我们看看如何使用pipeline来生成一些文本。这里的主要使用方法是您提供一个提示,模型将通过生成剩余的文本来自动完成整段话。这类似于许多手机上的预测文本功能。文本生成涉及随机性,因此如果您没有得到相同的如下所示的结果,这是正常的。 + +让我们尝试一下另一个 pipeline,这次是用于生成文本的 `text-generation` 。这里的主要使用方法是你提供一些文本,模型将通过生成剩余的文本来自动补全整段话。这类似于许多手机上的预测文本功能。文本生成涉及随机性,因此如果你没有得到相同的如下所示的结果也是正常的。 ```python from transformers import pipeline @@ -116,16 +127,19 @@ generator("In this course, we will teach you how to") 'data flows — data flows of various types, as seen by the ' 'HTTP'}] ``` -您可以使用参数 **num_return_sequences** 控制生成多少个不同的序列,并使用参数 **max_length** 控制输出文本的总长度。 +你可以使用参数 `num_return_sequences` 控制生成多少个不同的候选的句子,并使用参数 `max_length` 控制输出文本的总长度。 -✏️**快来试试吧!**使用 num_return_sequences 和 max_length 参数生成两个句子,每个句子 15 个单词。 + +✏️**快来试试吧!**使用 `num_return_sequences` 和 `max_length` 参数生成两个句子,每个句子 15 个单词。 + -## 在pipeline中使用 Hub 中的其他模型 [[在pipeline中使用 Hub 中的其他模型]] -前面的示例使用了默认模型,但您也可以从 Hub 中选择特定模型以在特定任务的pipeline中使用 - 例如,文本生成。转到[模型中心(hub)](https://huggingface.co/models)并单击左侧的相应标签将会只显示该任务支持的模型。[例如这样](https://huggingface.co/models?pipeline_tag=text-generation)。 +## 在 pipeline 中使用 Hub 中的其他模型 [[在 pipeline 中使用 Hub 中的其他模型]] -让我们试试 [**distilgpt2**](https://huggingface.co/distilgpt2) 模型吧!以下是如何在与以前相同的pipeline中加载它: +前面的示例使用了默认模型,但你也可以从 Hub 中选择一个特定模型,将其用于特定任务,例如文本生成。转到 [模型中心(hub)](https://huggingface.co/models) 并单击左侧的相应标签将会只显示该任务支持的模型。你应该看到一个 [模型筛选](https://huggingface.co/models?pipeline_tag=text-generation) 的页面。 + +让我们试试 [`distilgpt2`](https://huggingface.co/distilgpt2) 模型吧!以下面是在之前的 pipeline 中加载它的方法: ```python from transformers import pipeline @@ -144,20 +158,27 @@ generator( 'practice realtime, and with a hands on experience on both real ' 'time and real'}] ``` -您可以通过单击语言标签来筛选搜索结果,然后选择另一种文本生成模型的模型。模型中心(hub)甚至包含支持多种语言的多语言模型。 -通过单击选择模型后,您会看到有一个小组件,可让您直接在线试用。通过这种方式,您可以在下载之前快速测试模型的功能。 +你可以通过单击语言标签来筛选搜索结果,然后选择另一种文本生成模型的模型。模型中心(hub)也包含了支持多种语言的多语言模型。 + +通过单击选择模型后,你会看到有一个小组件,可让你直接在线试用。通过这种方式,你可以在下载之前快速测试模型的功能。 + -✏️**快来试试吧!**使用标签筛选查找另一种语言的文本生成模型。使用小组件测试并在pipeline中使用它! + +✏️**快来试试吧!**使用标签筛选查找另一种语言的文本生成模型。使用小组件测试并在 pipeline 中使用它! + ## 推理 API [[推理 API]] -所有模型都可以使用 Inference API 直接通过浏览器进行测试,该 API 可在 [Hugging Face 网站](https://huggingface.co/)上找到。通过输入自定义文本并观察模型的输出,您可以直接在此页面上使用模型。 -小组件形式的推理 API 也可作为付费产品使用,如果您的工作流程需要它,它会派上用场。有关更多详细信息,请参阅[定价页面](https://huggingface.co/pricing)。 +所有模型都可以使用 Inference API 直接通过浏览器进行测试,该 API 可在 [Hugging Face 网站](https://huggingface.co/) 上找到。通过输入自定义文本并观察模型的输出,你可以直接在此页面上使用模型。 + +小组件形式的推理 API 也可作为付费产品使用,如果你的工作流程需要它,它会派上用场。有关更多详细信息,请参阅 [定价页面](https://huggingface.co/pricing) 。 + +## 完形填空 [[Mask filling]] + +你将尝试的下一个 pipeline 是 `fill-mask` 。此任务的想法是填补给定文本中的空白: -## Mask filling [[Mask filling]] -您将尝试的下一个pipeline是 **fill-mask**。此任务的想法是填充给定文本中的空白: ```python from transformers import pipeline @@ -174,14 +195,19 @@ unmasker("This course will teach you all about models.", top_k=2) 'token': 38163, 'token_str': ' computational'}] ``` -**top_k** 参数控制要显示的结果有多少种。请注意,这里模型填充了特殊的< **mask** >词,它通常被称为掩码标记。其他掩码填充模型可能有不同的掩码标记,因此在探索其他模型时要验证正确的掩码字是什么。检查它的一种方法是查看小组件中使用的掩码。 + + `top_k` 参数控制要显示的结果有多少种。请注意,这里模型填补了特殊的 `` 词,它通常被称为 `mask token` 。不同的 `mask-filling` 模型可能有不同的 `mask token` ,因此在探索其他模型时要验证正确的 `mask token` 是什么。检查它的一种方法是查看小组件中使用的 `mask token` 。 -✏️**快来试试吧!**在 Hub 上搜索基于 bert 的模型并在推理 API 小组件中找到它的掩码。这个模型对上面pipeline示例中的句子预测了什么? + +✏️**快来试试吧!**在 Hub 上搜索 `bert-base-cased` 模型并在推理 API 小组件中找到它的 mask token。对于上面 pipeline 示例中的句子,这个模型预测了什么? + ## 命名实体识别 [[命名实体识别]] -命名实体识别 (NER) 是一项任务,其中模型必须找到输入文本的哪些部分对应于诸如人员、位置或组织之类的实体。让我们看一个例子: + +命名实体识别 (NER) 是一项任务,其中模型必须找到输入文本的哪些部分对应于诸如人员、位置或组织之类的实体。让我们看一个例子: + ```python from transformers import pipeline @@ -194,16 +220,21 @@ ner("My name is Sylvain and I work at Hugging Face in Brooklyn.") {'entity_group': 'LOC', 'score': 0.99321, 'word': 'Brooklyn', 'start': 49, 'end': 57} ] ``` -在这里,模型正确地识别出 Sylvain 是一个人 (PER),Hugging Face 是一个组织 (ORG),而布鲁克林是一个位置 (LOC)。 -我们在pipeline创建函数中传递选项 **grouped_entities=True** 以告诉pipeline将对应于同一实体的句子部分重新组合在一起:这里模型正确地将“Hugging”和“Face”分组为一个组织,即使名称由多个词组成。事实上,正如我们即将在下一章看到的,预处理甚至会将一些单词分成更小的部分。例如,**Sylvain** 分割为了四部分:**S、##yl、##va** 和 **##in**。在后处理步骤中,pipeline成功地重新组合了这些部分。 +在这里,模型正确地识别出 Sylvain 是一个人 (PER),Hugging Face 是一个组织 (ORG),而布鲁克林是一个位置 (LOC)。 + +我们在创建 pipeline 的函数中传递的 `grouped_entities=True` 参数告诉 pipeline 将与同一实体对应的句子部分重新分组:这里模型正确地将“Hugging”和“Face”分组为一个组织,即使名称由多个词组成。事实上,正如我们即将在下一章看到的,预处理甚至会将一些单词分成更小的部分。例如, `Sylvain` 分割为了四部分: `S、##yl、##va` 和 `##in` 。在后处理步骤中,pipeline 成功地重新组合了这些部分。 -✏️**快来试试吧!**在模型中心(hub)搜索能够用英语进行词性标注(通常缩写为 POS)的模型。这个模型对上面例子中的句子预测了什么? + +✏️**快来试试吧!**在模型中心(hub)搜索能够用英语进行词性标注(通常缩写为 POS)的模型。对于上面示例中的句子,这个词性标注的模型预测了什么? + -## 问答系统 [[问答系统]] -问答pipeline使用来自给定上下文的信息回答问题: +## 问答 [[问答]] + + `question-answering` pipeline 使用给定上下文中的信息回答问题: + ```python from transformers import pipeline @@ -215,14 +246,12 @@ question_answerer( ``` ```python out {'score': 0.6385916471481323, 'start': 33, 'end': 45, 'answer': 'Hugging Face'} -klyn", -) - ``` -请注意,此pipeline通过从提供的上下文中提取信息来工作;它不会凭空生成答案。 +请注意,这个 pipeline 通过从提供的上下文中提取信息来工作;它不会凭空生成答案。 + +## 提取摘要 [[文本摘要]] -## 文本摘要 [[文本摘要]] -文本摘要是将文本缩减为较短文本的任务,同时保留文本中的主要(重要)信息。下面是一个例子: +提取摘要是将文本缩减为较短文本的任务,同时保留文本中所有(或大部分)主要(重要)的信息。下面是一个例子: ```python from transformers import pipeline @@ -260,10 +289,12 @@ summarizer( 'industrial countries in Europe and Asia, continue to encourage ' 'and advance engineering .'}] ``` -与文本生成一样,您指定结果的 **max_length** 或 **min_length**。 + +与文本生成一样,你指定结果的 `max_length` 或 `min_length` 。 ## 翻译 [[翻译]] -对于翻译,如果您在任务名称中提供语言对(例如“**translation_en_to_fr**”),则可以使用默认模型,但最简单的方法是在[模型中心(hub)](https://huggingface.co/models)选择要使用的模型。在这里,我们将尝试从法语翻译成英语: + +对于翻译,如果你在任务名称中包含语言对(例如“ `translation_en_to_fr` ”),则可以使用默认模型,但最简单的方法是在 [模型中心(hub)](https://huggingface.co/models) 选择要使用的模型。在这里,我们将尝试从法语翻译成英语: ```python from transformers import pipeline @@ -276,12 +307,12 @@ translator("Ce cours est produit par Hugging Face.") ``` -与文本生成和摘要一样,您可以指定结果的 **max_length** 或 **min_length**。 +与文本生成和摘要一样,你可以指定结果的 `max_length` 或 `min_length` 。 -✏️**快来试试吧!**搜索其他语言的翻译模型,尝试将前一句翻译成几种不同的语言。 +✏️**快来试试吧!**搜索其他语言的翻译模型,尝试将前面的句子翻译成几种不同的语言。 -到目前为止显示的pipeline主要用于演示目的。它们是为特定任务而编程的,不能对他们进行自定义的修改。在下一章中,您将了解 **pipeline()** 函数内部的内容以及如何进行自定义的修改。 \ No newline at end of file +到目前为止显示的 pipeline 主要用于演示目的。它们是为特定任务而定制的,不能对他们进行自定义的修改。在下一章中,你将了解 `pipeline()` 函数内部的过程以及如何进行自定义的修改。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter1/4.mdx b/chapters/zh-CN/chapter1/4.mdx index c056b2b10..3e75f3660 100644 --- a/chapters/zh-CN/chapter1/4.mdx +++ b/chapters/zh-CN/chapter1/4.mdx @@ -1,64 +1,64 @@ -# Transformers 是如何工作的? [[Transformers 是如何工作的?]] +# Transformers 是如何工作的?[[Transformers 是如何工作的?]] -在本节中,我们将深入了解 Transformer 模型的架构。 +在本节中,我们简要了解 Transformer 模型的架构。 -## 一点Transformers的发展历史 [[一点Transformers的发展历史]] +## 一点 Transformers 的发展历史 [[一点Transformers的发展历史]] -以下是 Transformer 模型(简短)历史中的一些关键节点: +以下是 Transformer 模型(简要)历史中的一些关键节点:
A brief chronology of Transformers models.
-[Transformer 架构](https://arxiv.org/abs/1706.03762) 于 2017 年 6 月推出。原本研究的重点是翻译任务。随后推出了几个有影响力的模型,包括 + [Transformer 架构](https://arxiv.org/abs/1706.03762) 于 2017 年 6 月提出。原本研究的重点是翻译任务。随后推出了几个有影响力的模型,包括 -- **2018 年 6 月**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf), 第一个预训练的 Transformer 模型,用于各种 NLP 任务并获得极好的结果 +- **2018 年 6 月**: [GPT](https://cdn.openai.com/research-covers/language-unsupervised/language_understanding_paper.pdf) ,第一个预训练的 Transformer 模型,用于各种 NLP 任务并获得极好的结果 -- **2018 年 10 月**: [BERT](https://arxiv.org/abs/1810.04805), 另一个大型预训练模型,该模型旨在生成更好的句子摘要(下一章将详细介绍!) +- **2018 年 10 月**: [BERT](https://arxiv.org/abs/1810.04805) ,另一个大型预训练模型,该模型旨在生成更好的句子摘要(下一章将详细介绍!) -- **2019 年 2 月**: [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf), GPT 的改进(并且更大)版本,由于道德问题没有立即公开发布 +- **2019 年 2 月**: [GPT-2](https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf) ,GPT 的改进(并且更大)版本,由于道德问题没有立即公开发布 -- **2019 年 10 月**: [DistilBERT](https://arxiv.org/abs/1910.01108), BERT 的提炼版本,速度提高 60%,内存减轻 40%,但仍保留 BERT 97% 的性能 +- **2019 年 10 月**: [DistilBERT](https://arxiv.org/abs/1910.01108) ,BERT 的提炼版本,速度提高 60%,内存减轻 40%,但仍保留 BERT 97% 的性能 -- **2019 年 10 月**: [BART](https://arxiv.org/abs/1910.13461) 和 [T5](https://arxiv.org/abs/1910.10683), 两个使用与原始 Transformer 模型相同架构的大型预训练模型(第一个这样做) +- **2019 年 10 月**: [BART](https://arxiv.org/abs/1910.13461) 和 [T5](https://arxiv.org/abs/1910.10683) ,两个使用与原始 Transformer 模型原始架构的大型预训练模型(第一个这样做) -- **2020 年 5 月**, [GPT-3](https://arxiv.org/abs/2005.14165), GPT-2 的更大版本,无需微调即可在各种任务上表现良好(称为零样本学习) +- **2020 年 5 月**, [GPT-3](https://arxiv.org/abs/2005.14165) ,GPT-2 的更大版本,无需微调即可在各种任务上表现良好(称为零样本学习) -这个列表并不全面,只是为了突出一些不同类型的 Transformer 模型。大体上,它们可以分为三类: +此列表远非详尽无遗,只是为了突出一些不同类型的 Transformer 模型。大体上,它们可以分为三类: -- GPT-like (也被称作自回归Transformer模型) -- BERT-like (也被称作自动编码Transformer模型) -- BART/T5-like (也被称作序列到序列的 Transformer模型) +- GPT-like (也被称作自回归 Transformer 模型) +- BERT-like (也被称作自动编码 Transformer 模型) +- BART/T5-like (也被称作序列到序列的 Transformer 模型) 稍后我们将更深入地探讨这些分类。 -## Transformers是语言模型 [[Transformers是语言模型]] +## Transformers 是语言模型 [[Transformers是语言模型]] 上面提到的所有 Transformer 模型(GPT、BERT、BART、T5 等)都被训练为语言模型。这意味着他们已经以无监督学习的方式接受了大量原始文本的训练。无监督学习是一种训练类型,其中目标是根据模型的输入自动计算的。这意味着不需要人工来标记数据! -这种类型的模型可以对其训练过的语言进行统计理解,但对于特定的实际任务并不是很有用。因此,一般的预训练模型会经历一个称为*迁移学习*的过程。在此过程中,模型在给定任务上以监督方式(即使用人工注释标签)进行微调。 +这种类型的模型可以对其训练过的语言有统计学的理解,但对于特定的实际任务的效果并不是很好。因此,一般的预训练模型会经历一个称为迁移学习(transfer learning)的过程。在此过程中,模型在给定任务上以监督方式(即使用人工注释标签)进行微调。 -任务的一个例子是阅读 *n* 个单词的句子,预测下一个单词。这被称为因果语言建模,因为输出取决于过去和现在的输入。 +任务的一个例子是阅读 `n` 个单词的句子,预测下一个单词。这被称为因果语言建模(causal language modeling),因为输出取决于过去和现在的输入,而不依赖于未来的输入。
Example of causal language modeling in which the next word from a sentence is predicted.
-另一个例子是*遮罩语言建模*,该模型预测句子中的遮住的词。 +另一个例子是掩码语言建模(masked language modeling),俗称完形填空,该模型预测句子中的遮住的词。
Example of masked language modeling in which a masked word from a sentence is predicted.
-## Transformer是大模型 [[Transformer是大模型]] +## Transformer 是大模型 [[Transformer是大模型]] 除了一些特例(如 DistilBERT)外,实现更好性能的一般策略是增加模型的大小以及预训练的数据量。 @@ -75,19 +75,19 @@ -Transformers是由一个团队领导的(非常大的)模型项目,该团队试图减少预训练对环境的影响,通过运行大量试验以获得最佳超参数。 +这只是显示了一支团队领导的(非常大的)模型项目,该团队试图减少预训练对环境的影响。如果为了获得最佳超参数而进行大量试验,所造成的碳排放当量会更高。 想象一下,如果每次一个研究团队、一个学生组织或一家公司想要训练一个模型,都从头开始训练的。这将导致巨大的、不必要的浪费! 这就是为什么共享语言模型至关重要:共享经过训练的权重,当遇见新的需求时在预训练的权重之上进行微调,可以降低训练模型训练的算力和时间消耗,降低全球的总体计算成本和碳排放。 -顺便说一句,您可以通过多种工具评估模型训练的碳排放。 例如 [ML CO2 Impact](https://mlco2.github.io/impact/) 或集成在 🤗 Transformers 中的 [Code Carbon]( https://codecarbon.io/)。 要了解更多相关信息,您可以阅读这篇[博客文章](https://huggingface.co/blog/carbon-emissions-on-the-hub),其中将向您展示如何生成 `emissions.csv` 文件估计训练的碳足迹,这里还有 🤗 Transformers 关于碳足迹的[文档](https://huggingface.co/docs/hub/model-cards-co2)。 +顺便说一句,你可以通过多种工具评估模型训练的碳排放。例如 [ML CO2 Impact](https://mlco2.github.io/impact/) 或集成在 🤗 Transformers 中的 [Code Carbon]( https://codecarbon.io/) 。要了解更多相关信息,你可以阅读这篇 [博客文章](https://huggingface.co/blog/carbon-emissions-on-the-hub) ,其中将向你展示如何生成 `emissions.csv` 文件估计训练的碳排放,这里还有 🤗 Transformers 关于碳排放的 [文档](https://huggingface.co/docs/hub/model-cards-co2) 。 ## 迁移学习 [[迁移学习]] -*预训练是*训练模型前的一个操作:随机初始化权重,在没有任何先验知识的情况下开始训练。 +预训练(Pretraining)是是指从头开始训练模型:随机初始化权重,在没有任何先验知识的情况下开始训练。
The pretraining of a language model is costly in both time and money. @@ -96,13 +96,13 @@ Transformers是由一个团队领导的(非常大的)模型项目,该团 这种预训练通常是在非常大量的数据上进行的。因此,它需要大量的数据,而且训练可能需要几周的时间。 -另一方面,*微调*是在模型经过预训练后完成的训练。要执行微调,首先需要获取一个经过预训练的语言模型,然后使用特定于任务的数据集执行额外的训练。等等,为什么不直接为最后的任务而训练呢?有几个原因: +另一方面,微调(Fine-tuning)是在模型经过预训练后完成的训练。要执行微调,首先需要获取一个经过预训练的语言模型,然后使用特定于任务的数据集进行额外的训练。等等,为什么不直接为实际的最终任务而训练模型呢?有几个原因: -* 预训练模型已经在与微调数据集有一些相似之处的数据集上进行了训练。因此,微调过程能够利用模型在预训练期间获得的知识(例如,对于NLP问题,预训练模型将对您在任务中使用的语言有某种统计规律上的理解)。 +* 预训练模型已经在与微调数据集有一些相似之处的数据集上进行了训练。因此,微调过程能够利用模型在预训练期间获得的知识(例如,对于 NLP 问题,预训练模型将对你在任务中使用的语言有某种统计规律上的理解)。 * 由于预训练模型已经在大量数据上进行了训练,因此微调需要更少的数据来获得不错的结果。 * 出于同样的原因,获得好结果所需的时间和资源要少得多 -例如,可以利用英语的预训练过的模型,然后在arXiv语料库上对其进行微调,从而形成一个基于科学/研究的模型。微调只需要有限的数据量:预训练模型获得的知识可以“迁移”到目标任务上,因此被称为*迁移学习*。 +例如,可以利用英语的预训练过的模型,然后在 arXiv 语料库上对其进行微调,从而形成一个基于科学研究的模型。微调只需要有限的数据量:预训练模型获得的知识可以“迁移”到目标任务上,因此被称为迁移学习(transfer learning)。
The fine-tuning of a language model is cheaper than pretraining in both time and money. @@ -111,19 +111,19 @@ Transformers是由一个团队领导的(非常大的)模型项目,该团 因此,微调模型具有较低的时间、数据、财务和环境成本。迭代不同的微调方案也更快、更容易,因为与完整的预训练相比,训练的约束更少。 -这个过程也会比从头开始的训练(除非你有很多数据)取得更好的效果,这就是为什么你应该总是尝试利用一个预训练的模型--一个尽可能接近你手头的任务的模型--并对其进行微调。 +这个过程也会比从头开始的训练(除非你有很多数据)取得更好的效果,这就是为什么你应该总是尝试利用一个预训练的模型——一个尽可能接近你手头的任务的模型——并对其进行微调。 -## 一般的体系结构 [[一般的体系结构]] -在这一部分,我们将介绍Transformer模型的一般架构。如果你不理解其中的一些概念,不要担心;下文将详细介绍每个组件。 +## Transformer 模型的通用架构 [[Transformer 模型的通用架构]] +在本节中,我们将概述 Transformer 模型的通用架构。如果你不理解其中的一些概念,不用担心;后面的章节将详细介绍每个组件。 -## 介绍 [[介绍]] +## 简介 [[简介]] 该模型主要由两个块组成: -* **Encoder (左侧)**: 编码器接收输入并构建其表示(其特征)。这意味着对模型进行了优化,以从输入中获得理解。 -* **Decoder (右侧)**: 解码器使用编码器的表示(特征)以及其他输入来生成目标序列。这意味着该模型已针对生成输出进行了优化。 +* **Encoder (左侧)**:编码器接收输入并构建其表示(特征)。这意味着模型的使命是从输入中获取理解。 +* **Decoder (右侧)**:解码器使用编码器的表示(特征)以及其他输入来生成目标序列。这意味着模型的使命是生成输出。
Architecture of a Transformers models @@ -132,47 +132,45 @@ Transformers是由一个团队领导的(非常大的)模型项目,该团 这些部件中的每一个都可以独立使用,具体取决于任务: -* **Encoder-only models**: 适用于需要理解输入的任务,如句子分类和命名实体识别。 -* **Decoder-only models**: 适用于生成任务,如文本生成。 -* **Encoder-decoder models** 或者 **sequence-to-sequence models**: 适用于需要根据输入进行生成的任务,如翻译或摘要。 +* **Encoder-only 模型**:适用于需要理解输入的任务,如句子分类和命名实体识别。 +* **Decoder-only 模型**:适用于生成任务,如文本生成。 +* **Encoder-decoder 模型** 或者 **sequence-to-sequence 模型**:适用于需要根据输入进行生成的任务,如翻译或摘要。 在后面的部分中,我们将单独地深入研究这些体系结构。 ## 注意力层 [[注意力层]] -Transformer模型的一个关键特性是*注意力层*。事实上,介绍Transformer架构的文章的标题是[“注意力就是你所需要的”](https://arxiv.org/abs/1706.03762)! 我们将在课程的后面更加深入地探索注意力层;现在,您需要知道的是,这一层将告诉模型在处理每个单词的表示时,要特别重视您传递给它的句子中的某些单词(并且或多或少地忽略其他单词)。 +Transformer 模型的一个关键特性是*注意力层*。事实上,提出 Transformer 架构的文章的标题是 [“Attention Is All You Need”](https://arxiv.org/abs/1706.03762) !我们将在课程的后面更加深入地探讨注意力层的细节;现在,你需要知道的是,这一层将告诉模型在处理每个单词的表示时,对不同单词的重视(忽略)程度。 -把它放在语境中,考虑将文本从英语翻译成法语的任务。在输入“You like this course”的情况下,翻译模型还需要注意相邻的单词“You”,以获得单词“like”的正确翻译,因为在法语中,动词“like”的变化取决于主题。然而,句子的其余部分对于该词的翻译没有用处。同样,在翻译“this”时,模型也需要注意“course”一词,因为“this”的翻译不同,取决于相关名词是单数还是复数。同样,句子中的其他单词对于“this”的翻译也不重要。对于更复杂的句子(以及更复杂的语法规则),模型需要特别注意可能出现在句子中更远位置的单词,以便正确地翻译每个单词。 +可以考虑一个例子,假如我们要将英语翻译成法语。给定输入“You like this course”时,翻译模型需要注意相邻的词“You”,才能获得“like”的正确翻译,因为在法语中,动词“like”的变形依赖于主语。然而,对于那个词的翻译,句子的其余部分并没有用。同样,当翻译“this”时,模型也需要注意词“course”,因为“this”的翻译依赖于关联名词是单数还是复数。再次,句子中的其他词对于“course”的翻译并不重要。对于更复杂的句子(和更复杂的语法规则),模型可能需要特别注意可能在句子中更远处出现的词,以正确翻译每个词。 同样的概念也适用于与自然语言相关的任何任务:一个词本身有一个含义,但这个含义受语境的影响很大,语境可以是研究该词之前或之后的任何其他词(或多个词)。 -现在您已经了解了注意力层的含义,让我们更仔细地了解Transformer架构。 +现在你对注意力层是什么有了一些了解,让我们更详细地看一下 Transformer 架构。 -## 原始的结构 [[原始的结构]] +## Transformer 的原始结构 [[原始的结构]] -Transformer架构最初是为翻译而设计的。在训练期间,编码器接收特定语言的输入(句子),而解码器需要输出对应语言的翻译。在编码器中,注意力层可以使用一个句子中的所有单词(正如我们刚才看到的,给定单词的翻译可以取决于它在句子中的其他单词)。然而,解码器是按顺序工作的,并且只能注意它已经翻译过的句子中的单词。例如,当我们预测了翻译目标的前三个单词时,我们将它们提供给解码器,然后解码器使用编码器的所有输入来尝试预测第四个单词。 +Transformer 架构最初是为翻译而设计的。在训练期间,编码器接收特定语言的输入(句子),而解码器需要输出对应语言的翻译。在编码器中,注意力层可以使用一个句子中的所有单词(正如我们刚才看到的,给定单词的翻译可以取决于它在句子中的其他单词)。然而,解码器是按顺序工作的,并且只能注意它已经翻译过的句子中的单词。例如,当我们预测了翻译目标的前三个单词时,我们将它们提供给解码器,然后解码器使用编码器的所有输出来尝试预测第四个单词。 -为了在训练过程中加快速度(当模型可以访问目标句子时),解码器会被输入整个目标,但不允许获取到要翻译的单词(如果它在尝试预测位置2的单词时可以访问位置2的单词,解码器就会偷懒,直接输出那个单词,从而无法学习到正确的语言关系!)。例如,当试图预测第4个单词时,注意力层只能获取位置1到3的单词。 +为了在训练过程中加快速度(当模型可以访问目标句子时),会将整个目标输入解码器,但不允许获取到要翻译的单词(如果它在尝试预测位置 2 的单词时可以访问位置 2 的单词,解码器就会偷懒,直接输出那个单词,从而无法学习到正确的语言关系!)。例如,当试图预测第 4 个单词时,注意力层只能获取位置 1 到 3 的单词。 -最初的Transformer架构如下所示,编码器位于左侧,解码器位于右侧: +最初的 Transformer 架构如下所示,编码器位于左侧,解码器位于右侧:
Architecture of a Transformers models
-注意,解码器块中的第一个注意力层关联到解码器的所有(过去的)输入,但是第二注意力层使用编码器的输出。因此,它可以访问整个输入句子,以最好地预测当前单词。这是非常有用的,因为不同的语言可以有语法规则将单词按不同的顺序排列,或者句子后面提供的一些上下文可能有助于确定给定单词的最佳翻译。 +注意,解码器块中的第一个注意力层关联到解码器的所有(过去的)输入,但是第二个注意力层只使用编码器的输出。因此,它在预测当前单词时,可以使用整个句子的信息。这是非常有用的,因因为不同的语言可以有把词放在不同顺序的语法规则,或者句子后面提供的一些上下文可能有助于确定给定单词的最佳翻译。 -也可以在编码器/解码器中使用*注意力遮罩层*,以防止模型注意某些特殊单词。例如,在批处理句子时,填充特殊词使所有句子的长度一致。 +也可以在编码器/解码器中使用*attention mask(注意力掩码层)*,以防止模型关注到某些特殊单词。例如,用于在批量处理句子时使所有输入长度一致的特殊填充词。 -## 架构与参数 [[架构与参数]] +## architecture(架构)与 checkpoints(权重参数又称检查点) [[architecture(架构)与 checkpoints(权重参数又称检查点)]] -在本课程中,当我们深入探讨Transformers模型时,您将看到 -架构、参数和模型 -。 这些术语的含义略有不同: +在本课程中,当我们深入探讨 Transformers 模型时,你将看到架构、参数和模型。这些术语的含义略有不同: -* **架构**: 这是模型的骨架 -- 每个层的定义以及模型中发生的每个操作。 -* **Checkpoints**: 这些是将在给架构中结构中加载的权重。 -* **模型**: 这是一个笼统的术语,没有“架构”或“参数”那么精确:它可以指两者。为了避免歧义,本课程使用将使用架构和参数。 +* **architecture(架构)**:这是模型的骨架 —— 每个层的定义以及模型中发生的每个操作。 +* **Checkpoints(权重参数又称检查点)**:这些是将在给架构中结构中加载的权重参数,是一些具体的数值。 +* **Model(模型)**:这是一个笼统的术语,没有“架构”或“参数”那么精确:它可以指两者。为了避免歧义,本课程使用将使用架构和参数。 -例如,BERT是一个架构,而 `bert-base-cased`, 这是谷歌团队为BERT的第一个版本训练的一组权重参数,是一个参数。我们可以说“BERT模型”和"`bert-base-cased`模型." +例如,BERT 是一个架构,而 `bert-base-cased` ,这是谷歌团队为 BERT 的第一个版本训练的一组权重参数,是一个参数。我们可以说“BERT 模型”和“ `bert-base-cased` 模型。” diff --git a/chapters/zh-CN/chapter1/5.mdx b/chapters/zh-CN/chapter1/5.mdx index 64b30d2d7..ee388deba 100644 --- a/chapters/zh-CN/chapter1/5.mdx +++ b/chapters/zh-CN/chapter1/5.mdx @@ -7,16 +7,16 @@ -“编码器”模型指仅使用编码器的Transformer模型。在每个阶段,注意力层都可以获取初始句子中的所有单词。这些模型通常具有“双向”注意力,被称为自编码模型。 +编码器模型仅使用 Transformer 模型的编码器部分。在每次计算过程中,注意力层都能访问整个句子的所有单词,这些模型通常具有“双向”(向前/向后)注意力,被称为自编码模型。 -这些模型的预训练通常围绕着以某种方式破坏给定的句子(例如:通过随机遮盖其中的单词),并让模型寻找或重建给定的句子。 +这些模型的预训练通常会使用某种方式破坏给定的句子(例如:通过随机遮盖其中的单词),并让模型寻找或重建给定的句子。 -“编码器”模型最适合于需要理解完整句子的任务,例如:句子分类、命名实体识别(以及更普遍的单词分类)和阅读理解后回答问题。 +“编码器”模型适用于需要理解完整句子的任务,例如:句子分类、命名实体识别(以及更普遍的单词分类)和阅读理解后回答问题。 该系列模型的典型代表有: -- [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/zh-CN/chapter1/6.mdx b/chapters/zh-CN/chapter1/6.mdx index 450d827f1..2d5006304 100644 --- a/chapters/zh-CN/chapter1/6.mdx +++ b/chapters/zh-CN/chapter1/6.mdx @@ -7,16 +7,15 @@ -“解码器”模型通常指仅使用解码器的Transformer模型。在每个阶段,对于给定的单词,注意力层只能获取到句子中位于将要预测单词前面的单词。这些模型通常被称为自回归模型。 +“解码器”模型仅使用 Transformer 模型的解码器部分。在每个阶段,对于给定的单词,注意力层只能获取到句子中位于将要预测单词前面的单词。这些模型通常被称为自回归模型。 “解码器”模型的预训练通常围绕预测句子中的下一个单词进行。 -这些模型最适合于涉及文本生成的任务。 +这些模型最适合处理文本生成的任务。 该系列模型的典型代表有: - -- [CTRL](https://huggingface.co/transformers/model_doc/ctrl.html) -- [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/transfor-xl.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) +- [Transformer XL](https://huggingface.co/transformers/model_doc/transfor-xl) diff --git a/chapters/zh-CN/chapter1/7.mdx b/chapters/zh-CN/chapter1/7.mdx index 5e8b7c084..010e3af72 100644 --- a/chapters/zh-CN/chapter1/7.mdx +++ b/chapters/zh-CN/chapter1/7.mdx @@ -1,4 +1,4 @@ -# 序列到序列模型 [[序列到序列模型]] +# 编码器-解码器模型 [[编码器-解码器模型]] -编码器-解码器模型(也称为序列到序列模型)同时使用Transformer架构的编码器和解码器两个部分。在每个阶段,编码器的注意力层可以访问初始句子中的所有单词,而解码器的注意力层只能访问位于输入中将要预测单词前面的单词。 +编码器-解码器模型(也称为序列到序列模型)同时使用 Transformer 架构的编码器和解码器两个部分。在每个阶段,编码器的注意力层可以访问输入句子中的所有单词,而解码器的注意力层只能访问位于输入中将要预测单词前面的单词。 -这些模型的预训练可以使用训练编码器或解码器模型的方式来完成,但通常涉及更复杂的内容。例如,[T5](https://huggingface.co/t5-base)通过将文本的随机跨度(可以包含多个单词)替换为单个特殊单词来进行预训练,然后目标是预测该掩码单词替换的文本。 +这些模型的预训练可以使用训练编码器或解码器模型的方式来完成,但通常会更加复杂。例如, [T5](https://huggingface.co/t5-base) 通过用单个掩码特殊词替换随机文本范围(可能包含多个词)进行预训练,然后目标是预测被遮盖单词原始的文本。 序列到序列模型最适合于围绕根据给定输入生成新句子的任务,如摘要、翻译或生成性问答。 该系列模型的典型代表有: -- [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/zh-CN/chapter1/8.mdx b/chapters/zh-CN/chapter1/8.mdx index 641590288..0929d80f8 100644 --- a/chapters/zh-CN/chapter1/8.mdx +++ b/chapters/zh-CN/chapter1/8.mdx @@ -7,9 +7,9 @@ {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter1/section8.ipynb"}, ]} /> -如果您打算在正式的项目中使用经过预训练或经过微调的模型。请注意:虽然这些模型是很强大,但它们也有局限性。其中最大的一个问题是,为了对大量数据进行预训练,研究人员通常会搜集所有他们能找到的内容,中间可能夹带一些意识形态或者价值观的刻板印象。 +如果你打算在正式的项目中使用经过预训练或经过微调的模型。请注意:虽然这些模型是很强大,但它们也有局限性。其中最大的一个问题是,为了对大量数据进行预训练,研究人员通常会搜集所有他们能找到的所有文字内容,中间可能夹带一些意识形态或者价值观的刻板印象。 -为了快速解释清楚这个问题,让我们回到一个使用BERT模型的pipeline的例子: +为了快速解释清楚这个问题,让我们回到一个使用 BERT 模型的 pipeline 的例子: ```python from transformers import pipeline @@ -26,6 +26,6 @@ print([r["token_str"] for r in result]) ['lawyer', 'carpenter', 'doctor', 'waiter', 'mechanic'] ['nurse', 'waitress', 'teacher', 'maid', 'prostitute'] ``` -当要求模型填写这两句话中缺少的单词时,模型给出的答案中,只有一个与性别无关(服务员/女服务员)。其他职业通常与某一特定性别相关,妓女最终进入了模型中与“女人”和“工作”相关的前五位。尽管BERT是使用经过筛选和清洗后,明显中立的数据集上建立的的Transformer模型,而不是通过从互联网上搜集数据(它是在[Wikipedia 英文](https://huggingface.co/datasets/wikipedia)和[BookCorpus](https://huggingface.co/datasets/bookcorpus)数据集)。 +当要求模型填写这两句话中缺少的单词时,模型给出的答案中,只有一个与性别无关(服务员/女服务员)。其他职业通常与某一特定性别相关,妓女最终进入了模型中与“女人”和“工作”相关的前五位。尽管 BERT 是少数使用经过筛选和清洗后,并且看似中立的数据集上建立的的 Transformer 模型,而不是通过从互联网上搜集数据(它使用的是 [Wikipedia 英文](https://huggingface.co/datasets/wikipedia) 和 [BookCorpus](https://huggingface.co/datasets/bookcorpus) 数据集),但是这种情况还是发生了。 -因此,当您使用这些工具时,您需要记住,使用的原始模型的时候,很容易生成性别歧视、种族主义或恐同内容。这种固有偏见不会随着微调模型而使消失。 \ No newline at end of file +因此,当你使用这些工具时,你需要记住,使用的原始模型的时候,很容易生成性别歧视、种族主义或恐同内容。这种固有偏见不会随着微调模型而使消失。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter1/9.mdx b/chapters/zh-CN/chapter1/9.mdx index 286a98fed..27061d5a6 100644 --- a/chapters/zh-CN/chapter1/9.mdx +++ b/chapters/zh-CN/chapter1/9.mdx @@ -5,12 +5,12 @@ classNames="absolute z-10 right-0 top-0" /> -在本章中,您了解了如何使用来自🤗Transformers的函数pipeline()处理不同的NLP任务。您还了解了如何在模型中心(hub)中搜索和使用模型,以及如何使用推理API直接在浏览器中测试模型。 +在本章中,你了解了如何使用来自🤗Transformers 的高级函数 `pipeline()` 处理不同的 NLP 任务。你还了解了如何在模型中心(hub)中搜索和使用模型,以及如何使用推理 API 直接在浏览器中测试模型。 -我们讨论了Transformer模型如何在应用层上工作,并讨论了迁移学习和微调的重要性。您可以使用完整的体系结构,也可以仅使用编码器或解码器,具体取决于您要解决的任务类型。下表总结了这一点: +我们从最终的效果的角度讨论了 Transformer 模型的工作方式,并讨论了迁移学习和微调的重要性。一个关键的收获是:你可以使用完整的体系结构,也可以仅使用编码器或解码器,具体取决于你要解决的任务类型。下表总结了这一点: | 模型 | 示例 | 任务| | ---- | ---- |----| -| 编码器 | ALBERT, BERT, DistilBERT, ELECTRA, RoBERTa |句子分类、命名实体识别、从文本中提取答案| -| 解码器 | CTRL, GPT, GPT-2, Transformer XL |文本生成| -| 编码器-解码器 | BART, T5, Marian, mBART |文本摘要、翻译、生成问题的回答| \ No newline at end of file +| 编码器 | ALBERT,BERT,DistilBERT,ELECTRA,RoBERTa |句子分类、命名实体识别、抽取式问答(从文本中提取答案)| +| 解码器 | CTRL,GPT,GPT-2,Transformer XL |文本生成| +| 编码器-解码器 | BART,T5,Marian,mBART |文本摘要、翻译、生成式问答(生成问题的回答类似 chatgpt)| \ No newline at end of file diff --git a/chapters/zh-CN/chapter2/1.mdx b/chapters/zh-CN/chapter2/1.mdx index e68a74f0d..01f4c1523 100644 --- a/chapters/zh-CN/chapter2/1.mdx +++ b/chapters/zh-CN/chapter2/1.mdx @@ -5,19 +5,19 @@ classNames="absolute z-10 right-0 top-0" /> -正如你在 [Chapter 1](/course/chapter1),中看到的那样,Transformers模型通常非常大。对于数以百万计到数千万计数十亿的参数,训练和部署这些模型是一项复杂的任务。此外,由于几乎每天都在发布新模型,而且每种模型都有自己的实现,因此尝试它们绝非易事。 +正如你在 [第一章](/course/chapter1) ,中看到的那样,Transformers 模型通常规模庞大。包含数以百万计到数千万计数十亿的参数,训练和部署这些模型是一项复杂的任务。再者,新模型的推出几乎日新月异,而每种模型都有其独特的实现方式,尝试全部模型绝非易事。 -创建🤗 Transformers库就是为了解决这个问题。它的目标是提供一个API,通过它可以加载、训练和保存任何Transformer模型。这个库的主要特点是: -- **易于使用**:下载、加载和使用最先进的NLP模型进行推理只需两行代码即可完成。 -- **灵活**:所有型号的核心都是简单的PyTorch **nn.Module** 或者 TensorFlow **tf.kears.Model**,可以像它们各自的机器学习(ML)框架中的任何其他模型一样进行处理。 -- **简单**:当前位置整个库几乎没有任何摘要。“都在一个文件中”是一个核心概念:模型的正向传递完全定义在一个文件中,因此代码本身是可以理解的,并且是可以破解的。 +🤗 Transformers 库应运而生,就是为了解决这个问题。它的目标是提供一个统一的 API 接口,通过它可以加载、训练和保存任何 Transformer 模型。该库的主要特点有: +- **易于使用**:仅需两行代码,就能下载、加载并使用先进的 NLP 模型进行推理。 +- **灵活**:在本质上,所有的模型都是简单的 PyTorch nn.Module 或 TensorFlow tf.keras.Model 类,并可像在各自的机器学习(ML)框架中处理其他模型一样处理它们。 +- **简单**:该库几乎没有进行任何抽象化。🤗 Transformers 库一个核心概念是“全在一个文件中”:模型的前向传播完全在一个文件中定义,这使得代码本身易于理解和修改。 -最后一个特性使🤗 Transformers与其他ML库截然不同。这些模型不是基于通过文件共享的模块构建的;相反,每一个模型都有自己的菜单。除了使模型更加容易接受和更容易理解,这还允许你轻松地在一个模型上实验,而且不影响其他模型。 +最后一个特性使🤗 Transformers 与其他 ML 库截然不同。模型并非建立在跨越多个代码文件共享的模块上;相反,每一个模型都有自己的层次结构。除了使模型更加容易接受和更容易理解,这还允许你轻松地在一个模型上实验,而且不影响其他模型。 -本章将从一个端到端的示例开始,在该示例中,我们一起使用模型和tokenizer分词器来复制[Chapter 1](/course/chapter1)中引入的函数pipeline(). 接下来,我们将讨论模型API:我们将深入研究模型和配置类,并向您展示如何加载模型以及如何将数值输入处理为输出预测。 +本章将从一个端到端(从输入端到输出端)的示例开始,在该示例中,我们一起使用模型和 tokenizer 来复刻 [第一章](/course/chapter1) 中看到的 `pipeline()` 函数。接下来,我们将讨论 `Model` API:我们将深入研究 `Model` 类和 `Config` 类,并向你展示如何加载模型,以及它如何将输入处理为输出。 -然后我们来看看标记器API,它是pipeline()函数的另一个主要组件。它是作用分词器负责第一个和最后一个处理步骤,处理从文本到神经网络数字输入的转换,以及在需要时转换回文本。最后,我们将向您展示如何处理在一个准备好的批处理中通过一个模型发送多个句子的问题,然后详细介绍pipeline()函数。 +然后我们来看看 `tokenizer` API,它是 `pipeline()` 函数的另一个重要组成部分。在 `pipeline()` 中 `Tokenizer` 负责第一步和最后一步的处理,将文本转换到神经网络的输入,以及在需要时将其转换回文本。最后,我们将向你展示如何处理将多个句子整理为一个 batch 发送给模型,然后我们将更深入地研究 `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..da9a4b5fe 100644 --- a/chapters/zh-CN/chapter2/2.mdx +++ b/chapters/zh-CN/chapter2/2.mdx @@ -1,6 +1,6 @@ -# 管道的内部 [[管道的内部]] +# Pipeline 的内部 [[Pipeline 的内部]] {#if fw === 'pt'} @@ -23,7 +23,7 @@ {/if} -这是第一部分,根据您使用PyTorch或者TensorFlow,内容略有不同。点击标题上方的平台,选择您喜欢的平台! +这是第一部分,根据你使用 PyTorch 或者 TensorFlow,内容略有不同。点击标题上方的平台,选择你喜欢的平台! {#if fw === 'pt'} @@ -32,7 +32,7 @@ {/if} -让我们从一个完整的示例开始,看看在[Chapter 1](/course/chapter1)中执行以下代码时在幕后发生了什么 +让我们从一个完整的示例开始,看看在 [第一章](/course/chapter1) 中执行以下代码时在幕后发生了什么 ```python from transformers import pipeline @@ -46,33 +46,36 @@ classifier( ) ``` -获得: +获得如下输出: ```python out [{'label': 'POSITIVE', 'score': 0.9598047137260437}, {'label': 'NEGATIVE', 'score': 0.9994558095932007}] ``` -正如我们在[Chapter 1](/course/chapter1)中看到的,此管道将三个步骤组合在一起:预处理、通过模型传递输入和后处理: +正如我们在 [第一章](/course/chapter1) 中看到的,这个 pipeline 集成了三个步骤:预处理、模型计算和后处理:
The full NLP pipeline: tokenization of text, conversion to IDs, and inference through the Transformer model and the model head.
-让我们快速浏览一下这些内容。 +让我们先简单了解一下这三个步骤。 -## 使用分词器进行预处理 [[使用分词器进行预处理]] +## 使用 tokenizer( tokenizer )进行预处理 [[使用 tokenizer( tokenizer )进行预处理]] -与其他神经网络一样,Transformer模型无法直接处理原始文本, 因此我们管道的第一步是将文本输入转换为模型能够理解的数字。 为此,我们使用*tokenizer*(标记器),负责: +与其他神经网络一样,Transformer 模型无法直接处理原始文本,因此我们管道的第一步是将文本输入转换为模型能够理解的数字。为此,我们使用 tokenizer( tokenizer ),它将负责: -- 将输入拆分为单词、子单词或符号(如标点符号),称为标记(*token*) -- 将每个标记(token)映射到一个整数 -- 添加可能对模型有用的其他输入 +- 将输入拆分为单词、子单词或符号(如标点符号),称为 **token**(标记) +- 将每个标记(token)映射到一个数字,称为 **input ID**(inputs ID) +- 添加模型需要的其他输入,例如特殊标记(如 `[CLS]` 和 `[SEP]` ) +- - 位置编码:指示每个标记在句子中的位置。 +- - 段落标记:区分不同段落的文本。 +- - 特殊标记:例如 [CLS] 和 [SEP] 标记,用于标识句子的开头和结尾。 -所有这些预处理都需要以与模型预训练时完全相同的方式完成,因此我们首先需要从[Model Hub](https://huggingface.co/models)中下载这些信息。为此,我们使用`AutoTokenizer`类及其`from_pretrained()`方法。使用我们模型的检查点名称,它将自动获取与模型的标记器相关联的数据,并对其进行缓存(因此只有在您第一次运行下面的代码时才会下载)。 +在使用模型时所有这些预处理都需要与模型预训练时的方式完全相同,因此我们首先需要从 [Model Hub](https://huggingface.co/models) 中下载这些信息。为此,我们使用 `AutoTokenizer` 类和它的 `from_pretrained()` 方法,并输入我们模型 checkpoint 的名称,它将自动获取与模型的 tokenizer 相关联的数据,并对其进行缓存(因此只有在你第一次运行下面的代码时才会下载)。 -因为`sentiment-analysis`(情绪分析)管道的默认检查点是`distilbert-base-uncased-finetuned-sst-2-english`(你可以看到它的模型卡[here](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english)),我们运行以下程序: +`sentiment-analysis` (情绪分析)管道默认的 checkpoint 是 `distilbert-base-uncased-finetuned-sst-2-english` (你可以在 [这里](https://huggingface.co/distilbert-base-uncased-finetuned-sst-2-english) )看到它的模型卡片,我们运行以下代码: ```python from transformers import AutoTokenizer @@ -81,11 +84,11 @@ checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" tokenizer = AutoTokenizer.from_pretrained(checkpoint) ``` -一旦我们有了标记器,我们就可以直接将我们的句子传递给它,然后我们就会得到一本字典,它可以提供给我们的模型!剩下要做的唯一一件事就是将输入ID列表转换为张量。 +当我们有了 tokenizer,我们就可以直接将我们的句子传递给它,我们就会得到一个 `input ID(inputs ID)` 的列表!剩下要做的唯一一件事就是将 input ID 列表转换为 tensor(张量)。 -您可以使用🤗 Transformers,而不必担心哪个ML框架被用作后端;它可能是PyTorch或TensorFlow,或Flax。但是,Transformers型号只接受*张量*作为输入。如果这是你第一次听说张量,你可以把它们想象成NumPy数组。NumPy数组可以是标量(0D)、向量(1D)、矩阵(2D)或具有更多维度。它实际上是张量;其他ML框架的张量行为类似,通常与NumPy数组一样易于实例化。 +你在使用🤗 Transformers 时,您无需担心它背后的机器学习框架;它可能是 PyTorch 或 TensorFlow,或 Flax。但是,Transformers 模型只接受 `tensor(张量)` 作为输入。如果这是你第一次听说 tensor,你可以把它们想象成 NumPy 数组。NumPy 数组可以是标量(0D)、向量(1D)、矩阵(2D)或具有更多维度。这些都可以称为 tensor;其他 ML 框架的 tensor 使用方法也类似,通常与 NumPy 数组一样容易创建。 -要指定要返回的张量类型(PyTorch、TensorFlow或plain NumPy),我们使用`return_tensors`参数: +我们可以使用 `return_tensors` 参数指定我们想要得到的 tensor 的类型(PyTorch、TensorFlow 或纯 NumPy), {#if fw === 'pt'} ```python @@ -107,11 +110,11 @@ print(inputs) ``` {/if} -现在不要担心填充和截断;我们稍后会解释这些。这里要记住的主要事情是,您可以传递一个句子或一组句子,还可以指定要返回的张量类型(如果没有传递类型,您将得到一组列表)。 +现在不要担心 padding(填充)和 truncation(截断);我们稍后会解释这些。这里要记住的是,你可以传递一个句子或一组句子,还可以指定要返回的 tensor 类型(如果没有传递类型,默认返回的是 python 中的 list 格式)。 {#if fw === 'pt'} -以下是PyTorch张量的结果: +以下是 PyTorch 张量的结果: ```python out { @@ -127,7 +130,7 @@ print(inputs) ``` {:else} -以下是TensorFlow张量的结果: +以下是 TensorFlow 张量的结果: ```python out { @@ -145,12 +148,13 @@ print(inputs) ``` {/if} -输出本身是一个包含两个键的字典,`input_ids`和`attention_mask`。`input_ids`包含两行整数(每个句子一行),它们是每个句子中标记的唯一标记(token)。我们将在本章后面解释什么是`attention_mask`。 +输出是一个包含两个键, `input_ids` 和 `attention_mask` 。 `input_ids` 包含两行整数(每个句子一行),它们是每个句子中 token 的 ID。我们将在本章后面解释什么是 `attention_mask` 。 -## 浏览模型 [[浏览模型]] +## 探索模型 [[探索模型]] {#if fw === 'pt'} -我们可以像使用标记器一样下载预训练模型。🤗 Transformers提供了一个`AutoModel`类,该类还具有`from_pretrained()`方法: + +我们可以像使用 tokenizer 一样下载预训练模型。Transformers 提供了一个 `AutoModel` 类,它也有一个 `from_pretrained()` 方法: ```python from transformers import AutoModel @@ -159,7 +163,8 @@ checkpoint = "distilbert-base-uncased-finetuned-sst-2-english" model = AutoModel.from_pretrained(checkpoint) ``` {:else} -我们可以像使用标记器一样下载预训练模型。🤗 Transformers提供了一个`TFAutoModel`类,该类还具有`from_pretrained()`方法: + +我们可以像使用 tokenizer 一样下载预训练模型。Transformers 提供了一个 `TFAutoModel` 类,它也有一个 `from_pretrained()` 方法: ```python from transformers import TFAutoModel @@ -169,25 +174,25 @@ model = TFAutoModel.from_pretrained(checkpoint) ``` {/if} -在这个代码片段中,我们下载了之前在管道中使用的相同检查点(它实际上应该已经被缓存),并用它实例化了一个模型。 +在这段代码中,我们将之前在 pipeline 中使用的 checkpoint(实际上应该已经被缓存了)下载下来,并用它实例化了一个模型。 -这个架构只包含基本转换器模块:给定一些输入,它输出我们将调用的内容*隐藏状态(hidden states)*,亦称*特征(features)*。对于每个模型输入,我们将检索一个高维向量,表示**Transformer模型对该输入的上下文理解**。 +这个模型只包含基本的 Transformer 模块:输入一些句子,它输出我们将称为 `hidden states(隐状态)` ,也被称为特征。每个输入,我们都可以获取一个高维向量,代表 Transformer 模型对该输入的上下文理解。 -如果这不合理,不要担心。我们以后再解释。 +如果这有些难以理解,不要担心。我们以后再解释。 -虽然这些隐藏状态本身可能很有用,但它们通常是模型另一部分(称为*头部(head)*)的输入。 在[Chapter 1](/course/chapter1)中,可以使用相同的体系结构执行不同的任务,但这些任务中的每个任务都有一个与之关联的不同头。 +这些隐状态本身就很有用,它们被称为模型头(head),通常是模型另一部分的输入。在 [第一章](/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,37 +214,38 @@ print(outputs.last_hidden_state.shape) ``` {/if} -注意🤗 Transformers模型的输出与`namedtuple`或词典相似。您可以通过属性(就像我们所做的那样)或键(`输出["last_hidden_state"]`)访问元素,甚至可以通过索引访问元素,前提是您确切知道要查找的内容在哪里(`outputs[0]`)。 +注意,🤗 Transformers 模型的输出有些像 `namedtuple` 或词典。你可以通过使用“.”+属性(就像我们在上面示例中所做的那样)或键( `outputs["last_hidden_state"]` )访问元素,如果你确切知道要查找的内容的位置( `outputs[0]` ),也可以通过索引访问元素。 -### 模型头:数字的意义 [[模型头:数字的意义]] +### 模型头:理解数字的意义 [[模型头:理解数字的意义]] -模型头将隐藏状态的高维向量作为输入,并将其投影到不同的维度。它们通常由一个或几个线性层组成: +Transformers 模型的输出会直接发送到模型头进行处理。 +模型头通常由一个或几个线性层组成,它的输入是隐状态的高维向量,它会并将其投影到不同的维度。
A Transformer network alongside its head.
-Transformers模型的输出直接发送到模型头进行处理。 - -在此图中,模型由其嵌入层和后续层表示。嵌入层将标记化输入中的每个输入ID转换为表示关联标记(token)的向量。后续层使用注意机制操纵这些向量,以生成句子的最终表示。 +在此图中,模型由其嵌入层和后续层表示。嵌入层将 tokenize 后输入中的每个 inputs ID 转换为表示关联 token 的向量。后续层使用注意机制操纵这些向量,生成句子的最终表示。 -🤗 Transformers中有许多不同的体系结构,每种体系结构都是围绕处理特定任务而设计的。以下是一个非详尽的列表: +Transformers 中有许多不同的体系结构,每种体系结构都是围绕处理特定任务而设计的。以下是一个非详尽的列表: -- `*Model` (retrieve the hidden states) -- `*ForCausalLM` -- `*ForMaskedLM` -- `*ForMultipleChoice` -- `*ForQuestionAnswering` -- `*ForSequenceClassification` -- `*ForTokenClassification` +- `*Model` (隐状态检索) +- `*ForCausalLM` +- `*ForMaskedLM` +- `*ForMultipleChoice` +- `*ForQuestionAnswering` +- `*ForSequenceClassification` +- `*ForTokenClassification` - 以及其他 🤗 {#if fw === 'pt'} -对于我们的示例,我们需要一个带有序列分类头的模型(能够将句子分类为肯定或否定)。因此,我们实际上不会使用`AutoModel`类,而是使用`AutoModelForSequenceClassification`: +以情感分类为例,我们需要一个带有序列分类头的模型(能够将句子分类为积极或消极)。因此,我们不选用 `AutoModel` 类,而是使用 `AutoModelForSequenceClassification` 。也就是说前面写的 `model = AutoModel.from_pretrained(checkpoint)` 并不能得到情感分类任务的结果,因为没有加载 Model head。 +`AutoModelForSequenceClassification` 类在 `AutoModel` 的基础上添加了一个序列分类头部,可以 +将文本分类为不同的类别。 ```python from transformers import AutoModelForSequenceClassification @@ -248,7 +254,8 @@ model = AutoModelForSequenceClassification.from_pretrained(checkpoint) outputs = model(**inputs) ``` {:else} -For our example, we will need a model with a sequence classification head (to be able to classify the sentences as positive or negative). So, we won't actually use the `TFAutoModel` class, but `TFAutoModelForSequenceClassification`: + +以情感分类为例,我们需要一个带有序列分类头的模型(能够将句子分类为积极或消极)。因此,我们不选用 `TFAutoModel` 类,而是使用 `TFAutoModelForSequenceClassification` 。也就是说前面写的 model = TFAutoModel.from_pretrained(checkpoint)并不能得到情感分类任务的结果,因为没有加载 Model head。 ```python from transformers import TFAutoModelForSequenceClassification @@ -259,7 +266,8 @@ outputs = model(inputs) ``` {/if} -现在,如果我们观察输出的形状,维度将低得多:模型头将我们之前看到的高维向量作为输入,并输出包含两个值的向量(每个标签一个): +如果我们看一下现在输出的形状,其维度会降低很多:模型头接收我们之前看到的高维向量作为输入,并输出包含两个值(每种标签一个)的向量。正如您所见,输出向量的尺寸与输入向量相比要小得多。这是因为模型头将输入向量中的信息压缩成两个值,每个标签一个 +264 ```python 265 ```python: ```python print(outputs.logits.shape) @@ -279,11 +287,11 @@ torch.Size([2, 2]) {/if} -因为我们只有两个句子和两个标签,所以我们从模型中得到的结果是2 x 2的形状。 +由于我们只有两个句子和两钟标签,所以我们从模型中得到的结果的形状是 2 x 2。 -## 对输出进行后处理 [[对输出进行后处理]] +## 对输出进行后序处理 [[对输出进行后处理]] -我们从模型中得到的输出值本身并不一定有意义。我们来看看, +我们从模型中得到的输出值本身并不一定有意义。我们来看看, ```python print(outputs.logits) @@ -302,7 +310,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 @@ -333,9 +341,9 @@ 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,15 +353,15 @@ model.config.id2label {0: 'NEGATIVE', 1: 'POSITIVE'} ``` -现在我们可以得出结论,该模型预测了以下几点: - -- 第一句:否定:0.0402,肯定:0.9598 -- 第二句:否定:0.9995,肯定:0.0005 +现在我们可以得出结论,模型预测如下: + +- 第一句:消极的概率:0.0402,积极的概率:0.9598 +- 第二句:消极的概率:0.9995,积极的概率:0.0005 -我们已经成功地复制了管道的三个步骤:使用标记化器进行预处理、通过模型传递输入以及后处理!现在,让我们花一些时间深入了解这些步骤中的每一步。 +我们已经成功地复刻了管道的三个步骤:使用 tokenizer 进行预处理、通过模型传递输入以及后处理!接下来,让我们花一些时间深入了解这些步骤中的每一步。 -✏️ **试试看!** 选择两个(或更多)你自己的文本并在管道中运行它们。然后自己复制在这里看到的步骤,并检查是否获得相同的结果! +✏️ **试试看!** 选择两个(或更多)句子并分别在 `sentiment-analysis` 管道和自己实现的管道中运行它们。看一看是否获得的结果是不是相同的! diff --git a/chapters/zh-CN/chapter2/3.mdx b/chapters/zh-CN/chapter2/3.mdx index 67946ab1a..e47c88240 100644 --- a/chapters/zh-CN/chapter2/3.mdx +++ b/chapters/zh-CN/chapter2/3.mdx @@ -29,48 +29,50 @@ {/if} {#if fw === 'pt'} -在本节中,我们将更详细地了解如何创建和使用模型。我们将使用 -AutoModel类,当您希望从检查点实例化任何模型时,这非常方便。 -这个AutoModel类及其所有相关项实际上是对库中各种可用模型的简单包装。它是一个聪明的包装器,因为它可以自动猜测检查点的适当模型体系结构,然后用该体系结构实例化模型。 +在本节中,我们将更详细地了解如何创建和使用模型。我们将使用 `AutoModel` 类,当你希望从 checkpoint 实例化任何模型时,使用它非常方便。 + +`AutoModel` 类及其所有的相关类其实就是对库中可用的各种模型的简单包装。它是一个智能的包装,因为它可以自动猜测你的 checkpoint 适合的模型架构,然后实例化一个具有相同架构的模型。 {:else} -在本节中,我们将更详细地了解如何创建和使用模型。我们将使用 -AutoModel类,当您希望从检查点实例化任何模型时,这非常方便。 -这个AutoModel类及其所有相关项实际上是对库中各种可用模型的简单包装。它是一个聪明的包装器,因为它可以自动猜测检查点的适当模型体系结构,然后用该体系结构实例化模型。 +在本节中,我们将更详细地了解如何创建和使用模型。我们将使用 `TFAutoModel` 类,当你希望从 checkpoint 实例化任何模型时,这非常方便。 + +`TFAutoModel` 类及其所有的相关类其实就是对库中可用的各种模型的简单包装。它是一个智能的包装,因为它可以自动猜测你的 checkpoint 适合的模型架构,然后实例化一个具有相同架构的模型。 {/if} -但是,如果您知道要使用的模型类型,则可以使用直接定义其体系结构的类。让我们看看这是如何与BERT模型一起工作的。 +然而,如果你知道要使用模型的类型,你可以直接使用其架构相对应的模型类。让我们看看如何使用 BERT 模型。 -## 创建转换器 [[创建转换器]] +## 创建 Transformer 模型 [[创建 Transformer 模型]] -初始化BERT模型需要做的第一件事是加载配置对象: +初始化 BERT 模型需要做的第一件事是加载 `Config` 对象: {#if fw === 'pt'} ```py from transformers import BertConfig, BertModel -# Building the config +# 初始化 Config 类 config = BertConfig() -# Building the model from the config +# 从 Config 类初始化模型 model = BertModel(config) ``` + {:else} + ```py from transformers import BertConfig, TFBertModel -# Building the config +# 初始化 Config 类 config = BertConfig() -# Building the model from the config +# 从 Config 类初始化模型 model = TFBertModel(config) ``` {/if} -配置包含许多用于构建模型的属性: +`config` 中包含许多用于构建模型的属性: ```py print(config) @@ -88,12 +90,11 @@ BertConfig { } ``` -虽然您还没有看到所有这些属性都做了什么,但您应该认识到其中的一些属性:hidden_size属性定义了hidden_状态向量的大小,num_hidden_layers定义了Transformer模型的层数。 - -### 不同的加载方式 [[不同的加载方式]] +虽然可能你还不知道这些属性的含义,但其中一部分应该比较眼熟: `hidden_size` 属性定义了 `hidden_states(隐状态)` 向量的大小,而 `num_hidden_layers` 定义了 Transformer 模型的层数。 -从默认配置创建模型会使用随机值对其进行初始化: +### 使用不同的加载方式 [[使用不同的加载方式]] +使用默认配置创建模型会使用随机值对其进行初始化: {#if fw === 'pt'} ```py @@ -102,7 +103,7 @@ from transformers import BertConfig, BertModel config = BertConfig() model = BertModel(config) -# Model is randomly initialized! +# 模型已随机初始化! ``` {:else} ```py @@ -111,18 +112,13 @@ from transformers import BertConfig, TFBertModel config = BertConfig() model = TFBertModel(config) -# Model is randomly initialized! +# 模型已随机初始化! ``` {/if} +这个模型是可以运行并得到结果的,但它会输出胡言乱语;它需要先进行训练才能正常使用。我们可以根据手头的任务从头开始训练模型,但正如你在 [第一章](/course/chapter1) 中看到的,这将需要很长的时间和大量的数据,并且产生的碳足迹会对环境产生不可忽视的影响。为了避免不必要的重复工作,能够共享和复用已经训练过的模型是非常重要的。 -该模型可以在这种状态下使用,但会输出胡言乱语;首先需要对其进行训练。我们可以根据手头的任务从头开始训练模型,但正如您在 -[Chapter 1](/course/chapter1) -,这将需要很长的时间和大量的数据,并将产生不可忽视的环境影响。为了避免不必要的重复工作,必须能够共享和重用已经训练过的模型。 - - -加载已经训练过的Transformers模型很简单-我们可以使用from_pretrained() -方法: +加载已经训练过的 Transformers 模型很简单——我们可以使用 `from_pretrained()` 方法: {#if fw === 'pt'} ```py @@ -131,7 +127,7 @@ from transformers import BertModel model = BertModel.from_pretrained("bert-base-cased") ``` -正如您之前看到的,我们可以用等效的AutoModel类替换Bert模型。从现在开始,我们将这样做,因为这会产生检查点不可知的代码;如果您的代码适用于一个检查点,那么它应该与另一个检查点无缝地工作。即使体系结构不同,这也适用,只要检查点是针对类似任务(例如,情绪分析任务)训练的。 +正如你在上一小节看到的,从现在开始,我们会将 `BertModel` 替换为等效的 `AutoModel` 类,这样可以摆脱对 checkpoint 的依赖;如果你的代码适用于一个 checkpoint 那么它就可以在另一个 checkpoint 无缝地工作。即使体系结构不同,这也适用,只要 checkpoint 是针对同类的任务(例如,情绪分析任务)训练的。 {:else} ```py @@ -140,39 +136,21 @@ from transformers import TFBertModel model = TFBertModel.from_pretrained("bert-base-cased") ``` -正如您之前看到的,我们可以用等效的AutoModel类替换Bert模型。从现在开始,我们将这样做,因为这会产生检查点不可知的代码;如果您的代码适用于一个检查点,那么它应该与另一个检查点无缝地工作。即使体系结构不同,这也适用,只要检查点是针对类似任务(例如,情绪分析任务)训练的。 +正如你在上一小节看到的,从现在开始,我们将用等效的 `TFAutoModel` 类替换 `TFBert` 模型。这样可以摆脱对 checkpoint 的依赖;如果你的代码适用于一个 checkpoint 那么它应该与另一个 checkpoint 无缝地工作。即使体系结构不同,这也适用,只要 checkpoint 是针对类似任务(例如,情绪分析任务)训练的。 {/if} -在上面的代码示例中,我们没有使用BertConfig - -,而是通过Bert base cased标识符加载了一个预训练模型。这是一个模型检查点,由BERT的作者自己训练;您可以在 -[model card](https://huggingface.co/bert-base-cased)中找到更多细节. - - - -该模型现在使用检查点的所有权重进行初始化。它可以直接用于对训练过的任务进行推理,也可以对新任务进行微调。通过预先训练重量而不是从头开始的训练,我们可以很快取得好的效果。 - +在上述代码示例中,我们没有使用 `BertConfig` ,而是通过 `bert-base-cased` 标签加载了一个预训练模型。这是一个由 BERT 的作者训练的模型 checkpoint 权重;你可以在其 [模型卡片](https://huggingface.co/bert-base-cased) 中查看更多详细信息。 +现在,此模型已经用 checkpoint 的所有权重进行了初始化。它可以直接用于推理它训练过的任务,也可以在新任务上进行微调。通过使用预训练的权重进行训练,相比于从头开始训练,我们可以迅速获得比较好的结果。 -权重已下载并缓存在缓存文件夹中(因此将来对from_pretrained()方法的调用将不会重新下载它们)默认为 -~/.cache/huggingface/transformers -. 您可以通过设置 -HF_HOME -环境变量来自定义缓存文件夹。 +权重已下载并缓存在缓存文件夹中(因此,未来调用 `from_pretrained()` 方法的调用将不会重新下载它们)默认为 `~/.cache/huggingface/transformers` 。你可以通过设置 `HF_HOME` 环境变量来自定义缓存文件夹。 +加载模型的标识符可以是 Model Hub 上任何模型的标签,只要它与 BERT 架构兼容。可用的 BERT checkpoint 的完整列表可以在 [这里](https://huggingface.co/models?filter=bert) 找到。 - -用于加载模型的标识符可以是模型中心Hub上任何模型的标识符,只要它与BERT体系结构兼容。可以找到可用的BERT检查点的完整列表 -[here](https://huggingface.co/models?filter=bert) -. ### 保存模型 [[保存模型]] -保存模型和加载模型一样简单--我们使用 -save_pretrained() -方法,类似于 -from_pretrained() -方法: +保存模型和加载模型一样简单--我们使用 `save_pretrained()` 方法,该方法类似于 `from_pretrained()` 方法: ```py model.save_pretrained("directory_on_my_computer") @@ -194,33 +172,31 @@ config.json tf_model.h5 ``` {/if} -如果你看一下 -config.json -文件,您将识别构建模型体系结构所需的属性。该文件还包含一些元数据,例如检查点的来源以及上次保存检查点时使用的🤗 Transformers版本。 +如果你看一下 `config.json` 文件,你会认出构建模型架构所需的属性。这个文件还包含一些元数据,例如 checkpoint 的来源,以及你上次保存 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模型进行推理]] +### 使用 Transformers 模型进行推理 [[使用 Transformers 模型进行推理]] -既然您知道了如何加载和保存模型,那么让我们尝试使用它进行一些预测。Transformer模型只能处理数字——分词器生成的数字。但在我们讨论标记化器之前,让我们先探讨模型接受哪些输入。 +既然你知道了如何加载和保存模型,那么让我们尝试使用它进行一些预测。Transformer 模型只能处理数字——由 tokenizer 转化后的数字。但在我们讨论 tokenizer 之前,让我们探讨一下模型可以接受的输入是什么。 -标记化器可以将输入转换为适当的框架张量,但为了帮助您了解发生了什么,我们将快速了解在将输入发送到模型之前必须做什么。 +可以将输入转换为适当的框架张量,但为了帮助你了解发生了什么,我们将快速了解在将输入发送到模型之前必须做什么。 -假设我们有几个序列: +假设我们有几个句子: ```py sequences = ["Hello!", "Cool.", "Nice!"] ``` -分词器将这些转换为词汇表索引,通常称为 -input IDs -. 每个序列现在都是一个数字列表!结果是: +tokenizer 将这些转换为词汇表索引,通常称为 `input IDs` 。每个句子现在都是一个数字列表!结果输出是: ```py no-format encoded_sequences = [ @@ -230,15 +206,18 @@ encoded_sequences = [ ] ``` -这是一个编码序列列表:一个列表列表。张量只接受矩形(想想矩阵)。此“数组”已为矩形,因此将其转换为张量很容易: +这是一个编码序列列表:一个列表列表。张量只接受矩形(形状规则的的列表:每一列元素的数量都相同)。这个数组已经是矩形了,因此将其转换为张量很容易: {#if fw === 'pt'} + ```py import torch model_inputs = torch.tensor(encoded_sequences) ``` + {:else} + ```py import tensorflow as tf @@ -248,17 +227,10 @@ model_inputs = tf.constant(encoded_sequences) ### 使用张量作为模型的输入 [[使用张量作为模型的输入]] - - -在模型中使用张量非常简单-我们只需将输入称为模型: - +将张量输入给模型非常简单 —— 我们只需调用模型并输入: ```python output = model(model_inputs) ``` - - -虽然模型接受许多不同的参数,但只需要 -input IDs。我们稍后将解释其他参数的作用以及何时需要它们,但首先我们需要更仔细地了解 -Transformer模型可以理解的输入的标记 +虽然模型接受很多不同的参数,但只有 input IDs 是必需的。我们稍后会解释其他参数的作用以及何时需要它们,但首先我们需要仔细研究一下如何构建 Transformer 模型能理解的输入。 diff --git a/chapters/zh-CN/chapter2/4.mdx b/chapters/zh-CN/chapter2/4.mdx index f211b3db4..a5569f2aa 100644 --- a/chapters/zh-CN/chapter2/4.mdx +++ b/chapters/zh-CN/chapter2/4.mdx @@ -1,6 +1,6 @@ -# 标记器(Tokenizer) [[标记器(Tokenizer)]] +# Tokenizers [[Tokenizers]] {#if fw === 'pt'} @@ -24,30 +24,30 @@ -标记器(Tokenizer)是 NLP 管道的核心组件之一。它们有一个目的:将文本转换为模型可以处理的数据。模型只能处理数字,因此标记器(Tokenizer)需要将我们的文本输入转换为数字数据。在本节中,我们将确切地探讨标记化管道中发生的事情。 +tokenizer 是 NLP 管道的核心组件之一。它们有一个非常明确的目的:将文本转换为模型可以处理的数据。模型只能处理数字,因此 tokenizer 需要将我们的文本输入转换为数字。在本节中,我们将确切地探讨 tokenization 管道中发生的事情。 -在 NLP 任务中,通常处理的数据是原始文本。这是此类文本的示例 +在 NLP 任务中,通常处理的原始数据是文本。这里是一个例子: ``` Jim Henson was a puppeteer ``` -但是,模型只能处理数字,因此我们需要找到一种将原始文本转换为数字的方法。这就是标记器(tokenizer)所做的,并且有很多方法可以解决这个问题。目标是找到最有意义的表示——即对模型最有意义的表示——并且如果可能的话,找到最小的表示。 +但是,模型只能处理数字,因此我们需要找到一种将原始文本转换为数字的方法。这就是 tokenizer 所做的,并且有很多方法可以解决这个问题。目标是找到最有意义的表达方式 —— 即对模型来说最有意义的方式 —— 如果可能,还要找到最简洁的表达方式。 -让我们看一下标记化算法的一些示例,并尝试回答您可能对标记化提出的一些问题。 +让我们看一下 tokenization 算法的一些示例,并尝试回答一些你可能对 tokenization 有的疑问。 -## 基于词的(Word-based) [[基于词的(Word-based)]] +## 基于单词(Word-based)的 tokenization [[基于单词(Word-based)的 tokenization ]] -想到的第一种标记器是基于词的(_word-based_).它通常很容易设置和使用,只需几条规则,并且通常会产生不错的结果。例如,在下图中,目标是将原始文本拆分为单词并为每个单词找到一个数字表示: +想到的第一种 tokenizer 是基于词(word-based)的 tokenization。它通常很容易配置和使用,只需几条规则,并且通常会产生不错的结果。例如,在下图中,目标是将原始文本拆分为单词并为每个单词找到一个数字表示:
An example of word-based tokenization.
-有多种方法可以拆分文本。例如,我们可以通过应用Python的`split()`函数,使用空格将文本标记为单词: +有多种方法可以拆分文本。例如,我们可以通过使用 Python 的 `split()` 函数,使用空格将文本分割为单词: ```py tokenized_text = "Jim Henson was a puppeteer".split() @@ -58,72 +58,72 @@ print(tokenized_text) ['Jim', 'Henson', 'was', 'a', 'puppeteer'] ``` -还有一些单词标记器的变体,它们具有额外的标点符号规则。使用这种标记器,我们最终可以得到一些非常大的“词汇表”,其中词汇表由我们在语料库中拥有的独立标记的总数定义。 +此外,还有一些基于单词的 tokenizer 的变体,对标点符号有额外的规则。使用这类 tokenizer,我们最终可以得到一些非常大的“词汇表(vocabulary)”,其中词汇表的大小由我们在语料库中拥有的独立 tokens 的总数确定。 -每个单词都分配了一个 ID,从 0 开始一直到词汇表的大小。该模型使用这些 ID 来识别每个单词。 +每个单词都分配了一个 ID,从 0 开始一直到词汇表的大小。模型使用这些 ID 来识别每个词。 -如果我们想用基于单词的标记器(tokenizer)完全覆盖一种语言,我们需要为语言中的每个单词都有一个标识符,这将生成大量的标记。例如,英语中有超过 500,000 个单词,因此要构建从每个单词到输入 ID 的映射,我们需要跟踪这么多 ID。此外,像“dog”这样的词与“dogs”这样的词的表示方式不同,模型最初无法知道“dog”和“dogs”是相似的:它会将这两个词识别为不相关。这同样适用于其他相似的词,例如“run”和“running”,模型最初不会认为它们是相似的。 +如果我们想用基于单词的 tokenizer 完全覆盖一种语言,我们需要为语言中的每个单词设置一个标识符,这将生成大量的 tokens。例如,英语中有超过 500,000 个单词,因此要构建从每个单词到 ID 的映射,我们需要跟踪这么多 ID。此外,像“dog”这样的词与“dogs”这样的词的表示方式不同,模型最初无法知道“dog”和“dogs”是相似的:它会将这两个词识别为不相关。这同样适用于其他相似的词,例如“run”和“running”,模型最初也不会看到它们的相似性。 -最后,我们需要一个自定义标记(token)来表示不在我们词汇表中的单词。这被称为“未知”标记(token),通常表示为“[UNK]”或"<unk>"。如果你看到标记器产生了很多这样的标记,这通常是一个不好的迹象,因为它无法检索到一个词的合理表示,并且你会在这个过程中丢失信息。制作词汇表时的目标是以这样一种方式进行,即标记器将尽可能少的单词标记为未知标记。 +最后,我们需要一个自定义 token 来表示不在我们词汇表中的单词。这被称为“unknown” token,通常表示为“[UNK]”或“<unk>”。如果你看到 tokenizer 产生了很多这样的 token 这通常是一个不好的迹象,因为它无法检索到一个词的合理表示,并且你会在转化过程中丢失信息。制作词汇表时的其中一个目标是 tokenizer 将尽可能少的单词标记为未知 tokens。 -减少未知标记数量的一种方法是使用更深一层的标记器(tokenizer),即基于字符的(_character-based_)标记器(tokenizer)。 +减少未知 tokens 数量的一种方法是使用更深一层的 tokenizer 即基于字符(character-based)的 tokenizer -## 基于字符(Character-based) [[基于字符(Character-based)]] +## 基于字符(Character-based)的 tokenization [[基于字符(Character-based)的 tokenization ]] -基于字符的标记器(tokenizer)将文本拆分为字符,而不是单词。这有两个主要好处: +基于字符的 tokenizer 将文本拆分为字符,而不是单词。这有两个主要好处: - 词汇量要小得多。 -- 词汇外(未知)标记(token)要少得多,因为每个单词都可以从字符构建。 +- unknown tokens (out-of-vocabulary)要少得多,因为每个单词都可以由字符构建。 -但是这里也出现了一些关于空格和标点符号的问题: +但在此过程中也有一些问题,关于空格和标点符号:
An example of character-based tokenization.
-这种方法也不是完美的。由于现在表示是基于字符而不是单词,因此人们可能会争辩说,从直觉上讲,它的意义不大:每个字符本身并没有多大意义,而单词就是这种情况。然而,这又因语言而异;例如,在中文中,每个字符比拉丁语言中的字符包含更多的信息。 +这种方法也不是完美的。由于现在表示是基于字符而不是单词,因此人们可能会争辩说,从直觉上讲,它的意义不大:每个字符本身并没有多大意义,但是单词则不然。然而,这又因语言而异;例如,在中文中,每个字符比拉丁语言中的字符包含更多的信息。 -另一件要考虑的事情是,我们的模型最终会处理大量的词符(token):虽然使用基于单词的标记器(tokenizer),单词只会是单个标记,但当转换为字符时,它很容易变成 10 个或更多的词符(token)。 +另一件要考虑的因素是,这样做会导致我们的模型需要处理大量的 tokens:虽然一个单词在基于单词的 tokenizer 中只是一个 token,但当它被转换为字符时,很可能就变成了 10 个或更多的 tokens -为了两全其美,我们可以使用结合这两种方法的第三种技术:*子词标记化(subword tokenization)*。 +为了两全其美,我们可以使用结合这两种方法的第三种技术:基于子词(subword)的 tokenization。 -## 子词标记化 [[子词标记化]] +## 基于子词(subword)的 tokenization [[基于子词(subword)的 tokenization ]] -子词分词算法依赖于这样一个原则,即不应将常用词拆分为更小的子词,而应将稀有词分解为有意义的子词。 +基于子词(subword)的 tokenization 算法依赖于这样一个原则:常用词不应被分解为更小的子词,但罕见词应被分解为有意义的子词。 -例如,“annoyingly”可能被认为是一个罕见的词,可以分解为“annoying”和“ly”。这两者都可能作为独立的子词出现得更频繁,同时“annoyingly”的含义由“annoying”和“ly”的复合含义保持。 +例如,“annoyingly”可能被视为一个罕见的词,可以分解为“annoying”和“ly”。这两者都可能作为独立的子词并且出现得更频繁,同时“annoyingly”的含义通过“annoying”和“ly”的复合含义得以保留。 -这是一个示例,展示了子词标记化算法如何标记序列“Let's do tokenization!”: +这里有一个例子,展示了基于子词的 tokenization 算法如何将序列“Let's do tokenization!”分词:
A subword tokenization algorithm.
-这些子词最终提供了很多语义含义:例如,在上面的示例中,“tokenization”被拆分为“token”和“ization”,这两个具有语义意义同时节省空间的词符(token)(只需要两个标记(token)代表一个长词)。这使我们能够对较小的词汇表进行相对较好的覆盖,并且几乎没有未知的标记 +这些子词最终提供了大量的语义信息:例如,在上面的例子中,“tokenization”被分割成“token”和“ization”,这两个 tokens 在保持空间效率的同时具有语义意义(只需要两个 tokens 就能表示一个长词)。这让我们能够在词汇量小的情况下获得相对良好的覆盖率,并且几乎没有未知的 token。 -这种方法在土耳其语等粘着型语言(agglutinative languages)中特别有用,您可以通过将子词串在一起来形成(几乎)任意长的复杂词。 +这种方法在土耳其语等粘着型语言(agglutinative languages)中特别有用,你可以通过将子词串在一起来形成(几乎)任意长的复杂词。 -### 还有更多! [[还有更多!]] +### 还有更多![[还有更多!]] 不出所料,还有更多的技术。仅举几例: -- Byte-level BPE, 用于 GPT-2 -- WordPiece, 用于 BERT -- SentencePiece or Unigram, 用于多个多语言模型 +- Byte-level BPE,用于 GPT-2 +- WordPiece,用于 BERT +- SentencePiece or Unigram,用于多个多语言模型 -您现在应该对标记器(tokenizers)的工作原理有足够的了解,以便开始使用 API。 +你现在应该对 tokenizer 的工作原理有足够的了解,可以开始使用 API 了。 ## 加载和保存 [[加载和保存]] -加载和保存标记器(tokenizer)就像使用模型一样简单。实际上,它基于相同的两种方法: `from_pretrained()` 和 `save_pretrained()` 。这些方法将加载或保存标记器(tokenizer)使用的算法(有点像*建筑学(architecture)*的模型)以及它的词汇(有点像*权重(weights)*模型)。 +加载和保存 tokenizer 就像使用模型一样简单。实际上,它基于相同的两种方法: `from_pretrained()` 和 `save_pretrained()` 。这些方法会加载或保存分词器使用的算法(有点像模型的架构(architecture))以及其词汇表(有点像模型的权重(weights))。 -加载使用与 BERT 相同的检查点训练的 BERT 标记器(tokenizer)与加载模型的方式相同,除了我们使用 `BertTokenizer` 类: +加载使用与 BERT 相同的 checkpoint 训练的 BERT tokenizer 与加载模型的方式相同,只是换成了 `Bert tokenizer` 类: ```py from transformers import BertTokenizer @@ -132,10 +132,12 @@ tokenizer = BertTokenizer.from_pretrained("bert-base-cased") ``` {#if fw === 'pt'} -如同 `AutoModel`,`AutoTokenizer` 类将根据检查点名称在库中获取正确的标记器(tokenizer)类,并且可以直接与任何检查点一起使用: + +如同 `AutoModel` , `AutoTokenizer` 类将根据 checkpoint 名称在库中获取正确的 tokenizer 类,并且可以直接与任何 checkpoint 一起使用: {:else} -如同 `TFAutoModel`, `AutoTokenizer` 类将根据检查点名称在库中获取正确的标记器(tokenizer)类,并且可以直接与任何检查点一起使用: + +如同 `TFAutoModel` , `AutoTokenizer` 类将根据 checkpoint 名称在库中获取正确的 tokenizer 类,并且可以直接与任何 checkpoint 一起使用: {/if} @@ -145,7 +147,7 @@ from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") ``` -我们现在可以使用标记器(tokenizer),如上一节所示: +现在我们可以像在上一节中显示的那样使用 tokenizer: ```python tokenizer("Using a Transformer network is simple") @@ -157,29 +159,29 @@ tokenizer("Using a Transformer network is simple") 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]} ``` -保存标记器(tokenizer)与保存模型相同: +保存 tokenizer 与保存模型完全相同: ```py tokenizer.save_pretrained("directory_on_my_computer") ``` -我们在[Chapter 3](/Couse/chapter3)中将更多地谈论`token_type_ids`,稍后我们将解释 `attention_mask` 键。首先,让我们看看 `input_ids` 如何生成。为此,我们需要查看标记器(tokenizer)的中间方法。 +我们将在 [第三章](/Couse/chapter3) 中将更多地谈论 `token_type_ids` ,稍后我们将解释 `attention_mask` 。首先,让我们看看如何生成 `input_ids` 。为此,我们需要查看 tokenizer 的内部是如何实现的。 ## 编码 [[编码]] -将文本翻译成数字被称为编码(_encoding_).编码分两步完成:标记化,然后转换为输入 ID。 +将文本翻译成数字被称为编码(encoding)。编码分两步完成:分词,然后转换为 inputs ID。 -正如我们所见,第一步是将文本拆分为单词(或单词的一部分、标点符号等),通常称为*标记(token)*。有多个规则可以管理该过程,这就是为什么我们需要使用模型名称来实例化标记器(tokenizer),以确保我们使用模型预训练时使用的相同规则。 +正如我们所见,第一步是将文本拆分为单词(或部分单词、标点符号等),通常称为 tokens 不同的不同的分词器使用的算法也不一样,这就是为什么我们需要使用模型名称来实例化 tokenizer,以确保我们使用模型预训练时使用的相同的算法。 -第二步是将这些标记转换为数字,这样我们就可以用它们构建一个张量并将它们提供给模型。为此,标记器(tokenizer)有一个*词汇(vocabulary)*,这是我们在实例化它时下载的部分 `from_pretrained()` 方法。同样,我们需要使用模型预训练时使用的相同词汇。 +第二步是将这些 tokens 转换为数字,这样我们就可以用它们构建一个张量并将它们提供给模型。为此,tokenizer 有一个词汇表(vocabulary),这是我们在使用 `from_pretrained()` 方法实例化它时下载的部分。同样,我们需要使用与预训练模型时相同的词汇表。 -为了更好地理解这两个步骤,我们将分别探讨它们。请注意,我们将使用一些单独执行部分标记化管道的方法来向您展示这些步骤的中间结果,但实际上,您应该直接在您的输入上调用标记器(tokenizer)(如第 2 部分所示)。 +为了更好地理解这两个步骤,我们将分别探讨它们。请注意,我们将单独执行部分 tokenization 管道的方法来向你展示这些步骤的中间结果,但在实践中,你应该直接在你的输入上调用 tokenizer(如第 2 小节所示)。 -### 标记化 [[标记化]] +### tokenization [[ tokenization ]] -标记化过程由标记器(tokenizer)的`tokenize()` 方法实现: +tokenization 过程由 tokenizer 的 `tokenize()` 方法实现: ```py from transformers import AutoTokenizer @@ -192,16 +194,17 @@ tokens = tokenizer.tokenize(sequence) print(tokens) ``` -此方法的输出是一个字符串列表或标记(token): +这个方法的输出是一个字符串列表,或者说 tokens ```python out ['Using', 'a', 'transform', '##er', 'network', 'is', 'simple'] ``` -这个标记器(tokenizer)是一个子词标记器(tokenizer):它对词进行拆分,直到获得可以用其词汇表表示的标记(token)。`transformer` 就是这种情况,它分为两个标记:`transform` 和 `##er`。 +这个 tokenizer 是一个基于子词的 tokenizer:它对词进行拆分,直到获得可以用其词汇表表示的 tokens。以 `transformer` 为例,它分为两个 tokens `transform` 和 `##er` 。 + +### 从 tokens 到 inputs ID [[从 tokens 到 inputs ID]] -### 从词符(token)到输入 ID [[从词符(token)到输入 ID]] -输入 ID 的转换由标记器(tokenizer)的`convert_tokens_to_ids()`方法实现: +inputs ID 的转换由 tokenizer 的 `convert_tokens_to_ids()` 方法实现: ```py ids = tokenizer.convert_tokens_to_ids(tokens) @@ -213,17 +216,17 @@ print(ids) [7993, 170, 11303, 1200, 2443, 1110, 3014] ``` -这些输出一旦转换为适当的框架张量,就可以用作模型的输入,如本章前面所见。 +这些输出,一旦转换为适当的框架张量,就可以用作模型的输入,如本章前面所示。 -✏️ **试试看!** 在我们在第 2 节中使用的输入句子(“I've been waiting for a HuggingFace course my whole life.”和“I hate this so much!”)复制最后两个步骤(标记化和转换为输入 ID)。检查您获得的输入 ID 是否与我们之前获得的相同! +✏️ **试试看!** 请将我们在第 2 节中使用的输入句子(“I've been waiting for a HuggingFace course my whole life.”和“I hate this so much!”)执行最后两个步骤(分词和转换为 inputs ID)。检查你获得的 inputs ID 是否与我们在第二节中获得的一致! ## 解码 [[解码]] -*解码(Decoding)* 正好相反:从词汇索引中,我们想要得到一个字符串。这可以通过 `decode()` 方法实现,如下: +解码(Decoding) 正好相反:从 inputs ID 到一个字符串。这以通过 `decode()` 方法实现: ```py decoded_string = tokenizer.decode([7993, 170, 11303, 1200, 2443, 1110, 3014]) @@ -234,6 +237,6 @@ print(decoded_string) 'Using a Transformer network is simple' ``` -请注意, `decode` 方法不仅将索引转换回标记(token),还将属于相同单词的标记(token)组合在一起以生成可读的句子。当我们使用预测新文本的模型(根据提示生成的文本,或序列到序列问题(如翻译或摘要))时,这种行为将非常有用。 +请注意, `decode` 方法不仅将索引转换回 tokens,还将属于相同单词的 tokens 组合在一起以生成可读的句子。当我们使用预测新文本的模型(根据提示生成的文本,或序列到序列问题(如翻译或摘要))时,这样的功能将非常有用。 -到现在为止,您应该了解标记器(tokenizer)可以处理的原子操作:标记化、转换为 ID 以及将 ID 转换回字符串。然而,我们只是刮到了冰山一角。在下一节中,我们将采用我们的方法来克服它的限制,并看看如何克服它们。 +到现在为止,你应该了解 tokenizer 可以处理的原子操作:分词、转换为 ID 以及将 ID 转换回字符串。然而,我们只是瞥到了冰山一角。在下一节中,我们将继续探讨它能力的极限,并看看如何克服它们。 diff --git a/chapters/zh-CN/chapter2/5.mdx b/chapters/zh-CN/chapter2/5.mdx index 2f35dbcb9..26e529030 100644 --- a/chapters/zh-CN/chapter2/5.mdx +++ b/chapters/zh-CN/chapter2/5.mdx @@ -28,24 +28,21 @@ {/if} -在上一节中,我们探讨了最简单的用例:对一个小长度的序列进行推理。然而,一些问题已经出现: +在上一节中,我们探讨了最简单的案例:对一个较短的句子进行推理。然而,一些问题已经出现: -* 我们如何处理多个序列? +* 我们如何处理多个句子? +* 我们如何处理不同长度的多个句子? -* 我们如何处理多个序列不同长度? +* 词汇索引是唯一可以让模型运行的输入吗? +* 是否存在句子太长的问题? -* 词汇索引是让模型正常工作的唯一输入吗? - - -* 是否存在序列太长的问题? - -让我们看看这些问题会带来什么样的问题,以及如何使用🤗 Transformers API解决它们 +让我们看看这些问题会带来什么样的问题,以及如何使用🤗 Transformers API 解决它们 ## 模型需要一批输入 [[模型需要一批输入]] -在上一个练习中,您看到了序列如何转换为数字列表。让我们将此数字列表转换为张量,并将其发送到模型: +在上一个练习中,你看到了句子如何转换为数字列表。让我们将此数字列表转换为张量,并将其发送到模型: {#if fw === 'pt'} ```py @@ -61,7 +58,7 @@ 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) -# This line will fail. +# 这一行会运行失败 model(input_ids) ``` @@ -82,7 +79,7 @@ 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) -# This line will fail. +# 这一行会运行失败 model(input_ids) ``` @@ -91,9 +88,9 @@ InvalidArgumentError: Input to reshape is a tensor with 14 values, but the reque ``` {/if} -哦,不!为什么失败了?“我们遵循了第2节中管道的步骤。 +哦,不!为什么失败了?“我们的确是按照第 2 节中管道的步骤一步步来做的。 -问题是我们向模型发送了一个序列,而🤗 Transformers模型默认情况下需要多个句子。在这里,当我们将分词器应用于一个应用程序时,我们尝试在幕后完成分词器所做的一切,但如果仔细观察,您会发现tokenizer不仅将输入ID列表转换为张量,还在其顶部添加了一个维度: +问题是我们向模型发送了一个单独的句子,而🤗 Transformers 模型默认情况下需要一个句子列表。在这里,当我们试图重现 tokenizer 在输入 `sequence` 后在其内部进行的所有操作。但如果你仔细观察,你会发现 tokenizer 不仅仅是将 inputs ID 的列表转换为张量,它还在其上添加了一个维度: {#if fw === 'pt'} ```py @@ -118,7 +115,7 @@ array([[ 101, 1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, ``` {/if} -让我们重试并添加一个新维度: +让我们再试一次并添加一个新的维度: {#if fw === 'pt'} ```py @@ -162,7 +159,7 @@ print("Logits:", output.logits) ``` {/if} -我们打印输入ID以及生成的logits-以下是输出: +让我们打印 inputs ID 以及生成的 logits 值,以下是输出: {#if fw === 'pt'} ```python out @@ -178,23 +175,24 @@ Logits: tf.Tensor([[-2.7276208 2.8789377]], shape=(1, 2), dtype=float32) ``` {/if} -*Batching* 是一次通过模型发送多个句子的行为。如果你只有一句话,你可以用一个序列构建一个批次: +批处理(Batching)是一次性通过模型发送多个句子的行为。如果你只有一句话,你可以构建一个只有一个句子的 batch: ``` batched_ids = [ids, ids] ``` -这是一批两个相同的序列! +这就是一个包含两个相同句子的 batch -✏️ **Try it out!** 试试看!将此列表转换为张量并通过模型传递。检查您是否获得与之前相同的登录(但是只有两次) +✏️ **试试看!** 将这个 `batched_ids` 列表转换为张量,并通过你的模型进行处理。检查你是否得到了与之前相同的 logits 值(但是重复了两次)! + -批处理允许模型在输入多个句子时工作。使用多个序列就像使用单个序列构建批一样简单。不过,还有第二个问题。当你试图将两个(或更多)句子组合在一起时,它们的长度可能不同。如果您以前使用过张量,那么您知道它们必须是矩形,因此无法将输入ID列表直接转换为张量。为了解决这个问题,我们通常填充输入。 +批处理支持模型在输入多个句子时工作。使用多个句子就像使用单个句子构建批一样简单。不过,还有第二个问题。当你试图将两个(或更多)句子组合在一起时,它们的长度可能不同。如果你以前使用过张量,那么你知道它们必须是矩形,因此无法将 inputs ID 列表直接转换为张量。为了解决这个问题,我们通常填充输入(Padding)。 -## 填充输入 [[填充输入]] +## 填充输入(Padding) [[填充输入(Padding)]] 以下列表不能转换为张量: @@ -205,7 +203,7 @@ batched_ids = [ ] ``` -为了解决这个问题,我们将使用填充使张量具有矩形。Padding通过在值较少的句子中添加一个名为Padding token的特殊单词来确保我们所有的句子长度相同。例如,如果你有10个包含10个单词的句子和1个包含20个单词的句子,填充将确保所有句子都包含20个单词。在我们的示例中,生成的张量如下所示: +为了解决这个问题,我们将使用填充使张量成为标准的矩形。Padding 通过在值较少的句子中添加一个名为 `padding_id` 的特殊单词来确保我们所有的句子长度相同。例如,如果你有 10 个包含 10 个单词的句子和 1 个包含 20 个单词的句子,填充能确保所有句子都包含 20 个单词。在我们的示例中,填充后的张量如下所示: ```py no-format padding_id = 100 @@ -216,8 +214,7 @@ batched_ids = [ ] ``` -可以在tokenizer.pad_token_id中找到填充令牌ID. 让我们使用它,将我们的两句话分别发送到模型中,并分批发送到一起: - +我们可以在 `tokenizer.pad_token_id` 中找到填充 token 的 ID。让我们使用它,分别用模型处理这两个句子,然后再尝试将这两个句子放在一起用模型批处理: {#if fw === 'pt'} ```py no-format @@ -266,16 +263,15 @@ tf.Tensor( ``` {/if} -我们批处理预测中的logits有点问题:第二行应该与第二句的logits相同,但我们得到了完全不同的值! - +咦,我们批处理预测中的 logits 值有点问题:第二行应该与第二句的 logits 相同,但我们得到了完全不同的值! -这是因为Transformer模型的关键特性是关注层,它将每个标记上下文化。这些将考虑填充标记,因为它们涉及序列中的所有标记。为了在通过模型传递不同长度的单个句子时,或者在传递一批应用了相同句子和填充的句子时获得相同的结果,我们需要告诉这些注意层忽略填充标记。这是通过使用 attention mask来实现的。 +这是因为 Transformer 模型的关键特性:注意力层,它考虑了每个 token 的上下文信息。这具体来说,每个 token 的含义并非单独存在的,它的含义还取决于它在句子中的位置以及周围的其他 tokens。当我们使用填充(padding)来处理长度不同的句子时,我们会添加特殊的“填充 token”来使所有句子达到相同的长度。但是,注意力层会将这些填充 token 也纳入考虑,因为它们会关注序列中的所有 tokens。这就导致了一个问题:尽管填充 token 本身并没有实际的含义,但它们的存在会影响模型对句子的理解。我们需要告诉这些注意层忽略填充 token。这是通过使用注意力掩码(attention mask)层来实现的。 -## 注意力面具 [[注意力面具]] +## 注意力掩码(attention mask)层 [[注意力掩码(attention mask)层]] -*Attention masks*是与输入ID张量形状完全相同的张量,用0和1填充:1s表示应注意相应的标记,0s表示不应注意相应的标记(即,模型的注意力层应忽略它们)。 +注意力掩码(attention mask)是与 inputs ID 张量形状完全相同的张量,用 0 和 1 填充:1 表示应关注相应的 tokens,0 表示应忽略相应的 tokens(即,它们应被模型的注意力层忽视)。 -让我们用attention mask完成上一个示例: +让我们用 attention mask 修改上一个示例: {#if fw === 'pt'} ```py no-format @@ -320,35 +316,26 @@ tf.Tensor( ``` {/if} -现在我们得到了该批中第二个句子的相同登录。 +现在我们得到了批处理中第二句话相同的 logits 值。 -请注意,第二个序列的最后一个值是一个填充ID,它在attention mask中是一个0值。 +注意第二序列的最后一个值是填充 ID,其在注意力掩码中的值为 0。 -✏️ 试试看!在第2节中使用的两个句子上手动应用标记化(“我一生都在等待拥抱课程。”和“我非常讨厌这个!”)。通过模型传递它们,并检查您是否获得与第2节中相同的登录。现在使用填充标记将它们批处理在一起,然后创建适当的注意掩码。检查通过模型时是否获得相同的结果! +✏️ **试试看!**在第二节使用的两个句子(“I've been waiting for a HuggingFace course my whole life.” 和 “I hate this so much!”)上手动进行 tokenize。将它们输入模型并检查你是否得到了与第二节相同的 logits 值。然后使用填充 token 将它们一起进行批处理,然后创建合适的注意力掩码。检查模型计算后是否得到了相同的结果! -## 长序列 [[长序列]] - -对于Transformers模型,我们可以通过模型的序列长度是有限的。大多数模型处理多达512或1024个令牌的序列,当要求处理更长的序列时,会崩溃。此问题有两种解决方案: - - - -* 使用支持的序列长度较长的模型。 - +## 更长的句子 [[更长的句子]] -* 截断序列。 +对于 Transformers 模型,我们可以通过模型的序列长度是有限的。大多数模型处理多达 512 或 1024 个 的 tokens 序列,当使用模型处理更长的序列时,会崩溃。此问题有两种解决方案: +* 使用支持更长序列长度的模型。 +* 截断你的序列。 -模型有不同的支持序列长度,有些模型专门处理很长的序列。 -[Longformer](https://huggingface.co/transformers/model_doc/longformer.html) -这是一个例子,另一个是 -[LED](https://huggingface.co/transformers/model_doc/led.html) -. 如果您正在处理一项需要很长序列的任务,我们建议您查看这些模型。 +不同的模型支持的序列长度各不相同,有些模型专门用于处理非常长的序列。例如 [Longformer](https://huggingface.co/transformers/model_doc/longformer) 和 [LED](https://huggingface.co/transformers/model_doc/led) 。如果你正在处理需要非常长序列的任务,我们建议你考虑使用这些模型。 -否则,我们建议您通过指定max_sequence_length参数: +另外,我们建议你通过设定 `max_sequence_length` 参数来截断序列: ```py sequence = sequence[:max_sequence_length] diff --git a/chapters/zh-CN/chapter2/6.mdx b/chapters/zh-CN/chapter2/6.mdx index 17dd3c2c8..48b567d68 100644 --- a/chapters/zh-CN/chapter2/6.mdx +++ b/chapters/zh-CN/chapter2/6.mdx @@ -1,6 +1,6 @@ -# 把它们放在一起 [[把它们放在一起]] +# 综合应用 [[综合应用]] {#if fw === 'pt'} @@ -22,9 +22,9 @@ {/if} -在最后几节中,我们一直在尽最大努力手工完成大部分工作。我们探讨了标记化器的工作原理,并研究了标记化、到输入ID的转换、填充、截断和注意掩码。 +在过去的几个章节中,我们已经尝试尽可能手动完成大部分工作。我们探索了 tokenizer 的运行机制,并且了解了分词、转换为 inputs ID、填充、截断以及注意力掩码的处理方式。 -然而,正如我们在第2节中所看到的,🤗 Transformers API可以通过一个高级函数为我们处理所有这些,我们将在这里深入讨论。当你直接在句子上调用标记器时,你会得到准备通过模型传递的输入 +然而,正如我们在第二节中看到的那样,🤗 Transformers API 能够通过一个高级函数为我们处理所有这些工作,接下来我们就要深入研究这个函数。当你直接在句子上调用你的 `tokenizer` 时,就可以得到转换后的可以直接放入模型的数据了: ```py from transformers import AutoTokenizer @@ -37,10 +37,9 @@ sequence = "I've been waiting for a HuggingFace course my whole life." model_inputs = tokenizer(sequence) ``` -这里,`model_inputs` -变量包含模型良好运行所需的一切。对于DistilBERT,它包括输入 ID和注意力掩码(attention mask)。其他接受额外输入的模型也会有标记器对象的输出。 +这里, `model_inputs` 变量包含模型运行所需的所有数据。在 DistilBERT 中,它包括 inputs ID 和注意力掩码(attention mask)。其他接受额外输入的模型也会有对应的 tokenizer 可以将输入转化为模型所需要的输入。 -正如我们将在下面的一些示例中看到的,这种方法非常强大。首先,它可以标记单个序列: +正如我们将在下面的一些例子中看到的,这个函数非常强大。首先,它可以对单个句子进行处理: ```py sequence = "I've been waiting for a HuggingFace course my whole life." @@ -48,7 +47,7 @@ sequence = "I've been waiting for a HuggingFace course my whole life." model_inputs = tokenizer(sequence) ``` -它还一次处理多个序列,并且API没有任何变化: +它还一次处理多个多个,并且 API 的用法完全一致: ```py sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] @@ -56,51 +55,51 @@ sequences = ["I've been waiting for a HuggingFace course my whole life.", "So ha model_inputs = tokenizer(sequences) ``` -它可以根据几个目标进行填充: +它可以使用多种不同的方法对目标进行填充: ```py -# Will pad the sequences up to the maximum sequence length +# 将句子序列填充到最长句子的长度 model_inputs = tokenizer(sequences, padding="longest") -# Will pad the sequences up to the model max length +# 将句子序列填充到模型的最大长度 # (512 for BERT or DistilBERT) model_inputs = tokenizer(sequences, padding="max_length") -# Will pad the sequences up to the specified 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!"] -# Will truncate the sequences that are longer than the model max length +# 将截断比模型最大长度长的句子序列 # (512 for BERT or DistilBERT) model_inputs = tokenizer(sequences, truncation=True) -# Will truncate the sequences that are longer than the specified max length +# 将截断长于指定最大长度的句子序列 model_inputs = tokenizer(sequences, max_length=8, truncation=True) ``` -标记器对象可以处理到特定框架张量的转换,然后可以直接发送到模型。例如,在下面的代码示例中,我们提示标记器从不同的框架返回张量——`"pt"`返回Py Torch张量,`"tf"`返回TensorFlow张量,`"np"`返回NumPy数组: +`tokenizer` 对象可以处理指定框架张量的转换,然后可以直接发送到模型。例如,在下面的代码示例中,我们告诉 tokenizer 返回不同框架的张量 —— `"pt"` 返回 PyTorch 张量, `"tf"` 返回 TensorFlow 张量,而 `"np"` 则返回 NumPy 数组: ```py sequences = ["I've been waiting for a HuggingFace course my whole life.", "So have I!"] -# Returns PyTorch tensors +# 返回 PyTorch tensors model_inputs = tokenizer(sequences, padding=True, return_tensors="pt") -# Returns TensorFlow tensors +# 返回 TensorFlow tensors model_inputs = tokenizer(sequences, padding=True, return_tensors="tf") -# Returns NumPy arrays +# 返回 NumPy arrays model_inputs = tokenizer(sequences, padding=True, return_tensors="np") ``` -## 特殊词符(token) [[特殊词符(token)]] +## 特殊的 tokens [[特殊的 tokens ]] -如果我们看一下标记器返回的输入 ID,我们会发现它们与之前的略有不同: +如果我们看一下 tokenizer 返回的 inputs ID,我们会发现它们与之前的略有不同: ```py sequence = "I've been waiting for a HuggingFace course my whole life." @@ -118,7 +117,7 @@ print(ids) [1045, 1005, 2310, 2042, 3403, 2005, 1037, 17662, 12172, 2607, 2026, 2878, 2166, 1012] ``` -一个在开始时添加了一个标记(token) ID,一个在结束时添加了一个标记(token) ID。让我们解码上面的两个ID序列,看看这是怎么回事: +句子的开始和结束分别增加了一个 inputs ID。我们来解码上述的两个 ID 序列,看看是怎么回事: ```py print(tokenizer.decode(model_inputs["input_ids"])) @@ -130,11 +129,11 @@ print(tokenizer.decode(ids)) "i've been waiting for a huggingface course my whole life." ``` -标记器在开头添加了特殊单词`[CLS]`,在结尾添加了特殊单词`[SEP]`。这是因为模型是用这些数据预训练的,所以为了得到相同的推理结果,我们还需要添加它们。请注意,有些模型不添加特殊单词,或者添加不同的单词;模型也可能只在开头或结尾添加这些特殊单词。在任何情况下,标记器都知道需要哪些词符,并将为您处理这些词符。 + tokenizer 在开头添加了特殊单词 `[CLS]` ,在结尾添加了特殊单词 `[SEP]` 。这是因为模型在预训练时使用了这些字词,所以为了得到相同的推断结果,我们也需要添加它们。请注意,有些模型不添加特殊单词,或者添加不同的特殊单词;模型也可能只在开头或结尾添加这些特殊单词。无论如何,tokenizer 知道哪些是必需的,并会为你处理这些问题。 -## 结束:从标记器到模型 [[结束:从标记器到模型]] +## 小结:从 tokenizer 到模型 [[小结:从 tokenizer 到模型]] -现在我们已经看到了标记器对象在应用于文本时使用的所有单独步骤,让我们最后一次看看它如何处理多个序列(填充!),非常长的序列(截断!),以及多种类型的张量及其主要API: +现在我们已经看到 `tokenizer` 对象在处理文本时的所有步骤,让我们最后再看一次它如何通过 tokenizer API 处理多个序列(填充!),非常长的序列(截断!)以及多种类型的张量: {#if fw === 'pt'} ```py diff --git a/chapters/zh-CN/chapter2/7.mdx b/chapters/zh-CN/chapter2/7.mdx index b3924309b..b1cf2de2d 100644 --- a/chapters/zh-CN/chapter2/7.mdx +++ b/chapters/zh-CN/chapter2/7.mdx @@ -1,32 +1,24 @@ -# 基本用法完成! [[基本用法完成!]] +# 基本用法完成![[基本用法完成!]] -很好地完成了到这里的课程!总而言之,在本章中,您可以: +恭喜你跟随课程走到这里!回顾一下,在这一章中,你已经: -- 学习了Transformers模型的基本构造块。 +- 学习了 Transformers 模型的基本构造块。 +- 了解了 Tokenizer 管道的组成。 -- 了解了标记化管道的组成。 +- 了解了如何在实践中使用 Transformers 模型。 +- 学习了如何利用 tokenizer 将文本转换为模型可以理解的张量。 -- 了解了如何在实践中使用Transformers模型。 +- 设定了 tokenizer 和模型,可以从输入的文本获取预测的结果。 +- 了解了 inputs IDs 的局限性,并学习了关于注意力掩码(attention mask)的知识。 -- 学习了如何利用分词器将文本转换为模型可以理解的张量。 +- 试用了灵活且可配置的 Tokenizer 方法。 - -- 将分词器和模型一起设置,以从文本到预测。 - - -- 了解了inputs IDs的局限性,并了解了attention mask。 - - -- 使用多功能和可配置的分词器方法。 - - - -从现在起,您应该能够自由浏览🤗 Transformers文档:词汇听起来很熟悉,并且您已经看到了大部分时间将使用的方法。 +从现在开始,你应该能够自由浏览🤗 Transformers 文档:你会遇到许多看起来很熟悉的词汇;而且到目前为止,你已经见到了你大部分时间会使用的方法。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter2/8.mdx b/chapters/zh-CN/chapter2/8.mdx index ff8f98b8c..26440c09e 100644 --- a/chapters/zh-CN/chapter2/8.mdx +++ b/chapters/zh-CN/chapter2/8.mdx @@ -9,68 +9,68 @@ classNames="absolute z-10 right-0 top-0" /> -### 1. 语言建模 Pipeline 的顺序是什么? +### 1. 自然语言处理流程的顺序是什么? -### 2. Transformer模型的输出有多少个维度,每个维度分别是什么? +### 2. Transformer 模型的输出有的张量多少个维度,每个维度分别是什么? -### 3.下列哪一个是Subword标记(Tokenization)的例子(从分词的颗粒度来划分)? +### 3.下列哪一个是子词分词的例子(从分词的颗粒度来划分)? -### 4.什么是模型的Head层? +### 4.什么是模型头(Haed 层)? {#if fw === 'pt'} -### 5.什么是AutoModel? +### 5.什么是 AutoModel? AutoTrain 产品相混淆了?" + text: "根据你的数据自动进行训练的模型", + explain: "错误。你可能把 AutoModel 与 Hugging Face 的AutoTrain 产品相混淆了?" }, { - text: "一个根据Checkpoint(检查点)返回模型体系结构的对象", - explain: "确切地说: AutoModel只需要知道初始化的Checkpoint(检查点)就可以返回正确的体系结构。", + text: "一个根据 checkpoint(检查点)返回模型体系结构的对象", + explain: "确切地说:AutoModel只需要知道初始化的 checkpoint(检查点)名称就可以返回正确的体系结构。", correct: true }, { text: "一种可以自动检测输入语言来加载正确权重的模型", - explain: "不正确; 虽然有些Checkpoint(检查点)和模型能够处理多种语言,但是没有内置的工具可以根据语言自动选择Checkpoint(检查点)。您应该前往 Model Hub 寻找完成所需任务的最佳Checkpoint(检查点)!" + explain: "不正确;虽然有些 checkpoint(检查点)和模型能够处理多种语言,但是没有内置的工具可以根据语言自动选择 checkpoint(检查点)。你应该前往 Model Hub 寻找完成所需任务的最佳 checkpoint(检查点)!" } ]} /> {:else} -### 5.什么是 TFAutoModel? +### 5.什么是 TFAutoModel? AutoTrain 产品相混淆了?" + text: "根据你的数据自动进行训练的模型", + explain: "错误。你可能把 TFAutoModel 与 Hugging Face 的AutoTrain 产品相混淆了?" }, { - text: "一个根据Checkpoint(检查点)返回模型体系结构的对象", - explain: "确切地说: TFAutoModel只需要知道初始化的Checkpoint(检查点)就可以返回正确的体系结构。", + text: "一个根据 checkpoint(检查点)返回模型体系结构的对象", + explain: "确切地说:TFAutoModel只需要知道初始化的 checkpoint(检查点)名称就可以返回正确的体系结构。", correct: true }, { text: "一种可以自动检测输入语言来加载正确权重的模型", - explain: "不正确; 虽然有些Checkpoint(检查点)和模型能够处理多种语言,但是没有内置的工具可以根据语言自动选择Checkpoint(检查点)。您应该前往 Model Hub 寻找完成所需任务的最佳Checkpoint(检查点)!" + explain: "不正确;虽然有些 checkpoint(检查点)和模型能够处理多种语言,但是没有内置的工具可以根据语言自动选择 checkpoint(检查点)。你应该前往 Model Hub 寻找完成所需任务的最佳 checkpoint(检查点)!" } ]} /> {/if} -### 6.当将不同长度的序列批处理在一起时,需要进行哪些处理? +### 6.当将不同长度的句子序列在一起批处理时,需要进行哪些处理? -### 7.将 SoftMax激活函数应用于序列分类(Sequence Classification)模型的 logits 输出有什么意义? +### 7.使用 SoftMax 激活函数对序列分类(Sequence Classification)模型的 logits 输出进行处理有什么意义? -### 8.大多数标记器(Tokenizer)的API以什么方法为核心? +### 8.Tokenizer API 的核心方法是哪一个? 编码 ,因为它可以将文本编码为id,将预测的id解码为文本", - explain: "错! 虽然 编码 方法确实存在于标记器中,但是它不存在于模型中。" + text: "encode 因为它可以将文本编码为 ID,将预测的 ID 解码为文本", + explain: "错!虽然 encode 方法确实是 Tokenizer 中的方法之一,但是它并不是核心的方法,此外将预测 ID 解码为文本的是 decode。" }, { - text: "直接调用标记器(Tokenizer)对象。", - explain: "完全正确!标记化器(Tokenizer) 的 __call__方法是一个非常强大的方法,可以处理几乎任何事情。它也是从模型中获取预测的方法。", + text: "直接调用 Tokenizer 对象。", + explain: "完全正确! tokenizer (Tokenizer) 的 __call__方法是一个非常强大的方法,可以处理几乎任何事情。它同时也可以从模型中获取预测。", correct: true }, { text: "pad(填充)", - explain: "错! pad(填充)非常有用,但它只是标记器(Tokenizer) API的一部分。" + explain: "错!pad(填充)非常有用,但它只是 Tokenizer API 的一部分。" }, { - text: "tokenize(标记)", - explain: "可以说,tokenize(标记)方法是最有用的方法之一,但它不是标记器(Tokenizer) API的核心方法。" + text: "tokenize", + explain: "可以说,tokenize方法是最有用的方法之一,但它不是 Tokenizer API 的核心方法。" } ]} /> -### 9.这个代码示例中的`result`变量包含什么? +### 9.这个代码示例中的 `result` 变量包含什么? ```py from transformers import AutoTokenizer @@ -220,23 +220,23 @@ result = tokenizer.tokenize("Hello!") __call__ 或 convert_tokens_to_ids方法的作用!" + text: "一个 ID 的列表", + explain: "不正确;这是 __call__convert_tokens_to_ids方法的作用!" }, { - text: "包含所有标记(Token)的字符串", - explain: "这将是次优的,因为Tokenizer会将字符串拆分为多个标记的列表。" + text: "包含所有分词后的的字符串", + explain: "这将是次优的答案,因为 tokenize 方法会将字符串拆分为多个 tokens 的列表。" } ]} /> {#if fw === 'pt'} -### 10.下面的代码有什么错误吗? +### 10.下面的代码有什么错误吗? ```py from transformers import AutoTokenizer, AutoModel @@ -251,22 +251,22 @@ result = model(**encoded) choices={[ { text: "不,看起来是对的。", - explain: "不幸的是,将一个模型与一个用不同Checkpoint(检查点)训练的Tokenizer(标记器)耦合在并不是一个好主意。模型没有在这个这个Tokenizer(标记器)上训练来理解Tokenizer(标记器)的输出,因此模型输出(如果它可以运行的话)不会有任何意义。" + explain: "将一个模型与一个在不同 checkpoint 训练的 tokenizer 耦合在一起并不是一个好主意。模型没有在这个这个 tokenizer 上训练来理解 tokenizer 的输出,因此模型的输出(如果它可以运行的话)会是错乱的。" }, { - text: "Tokenizer(标记器)和模型应该来自相同的Checkpoint(检查点)。", + text: "Tokenizer 和模型应该来自相同的 checkpoint。", explain: "对!", correct: true }, { - text: "由于每个输入都是一个Batch,因此可以使用标记器(Tokenizer)对其进行平移和截断来改善这段代码。", - explain: "的确,每个模型都需要Batch类型的输入。然而,截断或填充这个序列并不一定有意义,这里只有一句话,而这些技术是用来批处理一个句子列表的。" + text: "由于模型输入需要是一个 Batch,因此可以使用 tokenizer 对其进行截断或填充来改进这段代码。", + explain: "的确,模型输入需要是一个 Batch。然而,截断或填充这个序列并不一定有意义,这里只有一句话,而截断或填充这些技术是用来批处理一个句子列表使其长度一致的。" } ]} /> {:else} -### 10.下面的代码有什么错误吗? +### 10.下面的代码有什么错误吗? ```py from transformers import AutoTokenizer, TFAutoModel @@ -281,16 +281,16 @@ result = model(**encoded) choices={[ { text: "不,看起来是对的。", - explain: "不幸的是,将一个模型与一个用不同Checkpoint(检查点)训练的Tokenizer(标记器)耦合在并不是一个好主意。模型没有在这个这个Tokenizer(标记器)上训练来理解Tokenizer(标记器)的输出,因此模型输出(如果它可以运行的话)不会有任何意义。" + explain: "不幸的是,将一个模型与一个不同 checkpoint 训练的 tokenizer 耦合在并不是一个好主意。模型没有在这个这个 tokenizer 上训练来理解 tokenizer 的输出,因此模型的输出(如果它可以运行的话)会是错乱的。" }, { - text: "Tokenizer(标记器)和模型应该来自相同的Checkpoint(检查点)。", + text: "Tokenizer 和模型应该来自相同的 checkpoint。", explain: "对!", correct: true }, { - text: "由于每个输入都是一个Batch,因此可以使用标记器(Tokenizer)对其进行平移和截断来改善这段代码。", - explain: "的确,每个模型都需要Batch类型的输入。然而,截断或填充这个序列并不一定有意义,这里只有一句话,而这些技术是用来批处理一个句子列表的。" + text: "由于每个输入都是一个 Batch,因此可以使用 tokenizer 对其进行平移和截断来改善这段代码。", + explain: "的确,每个模型都需要 Batch 类型的输入。然而,截断或填充这个序列并不一定有意义,这里只有一句话,而这些技术是用来批处理一个句子列表的。" } ]} /> diff --git a/chapters/zh-CN/chapter3/1.mdx b/chapters/zh-CN/chapter3/1.mdx index 2e1c920e1..13e70ddd9 100644 --- a/chapters/zh-CN/chapter3/1.mdx +++ b/chapters/zh-CN/chapter3/1.mdx @@ -7,20 +7,22 @@ classNames="absolute z-10 right-0 top-0" /> -在 [第二章](/course/chapter2) 我们探索了如何使用标记器(Tokenizer)和预训练模型进行预测。但是,如果您想为自己的数据集微调预训练模型,该怎么做呢?这就是本章的主题!你将学到: +在 [第二章](/course/chapter2) 我们探索了如何使用 Tokenizer 和预训练模型进行预测。那么如何使用自己的数据集微调预训练模型呢?本章将解决这个问题!你将学到: {#if fw === 'pt'} -* 如何从模型中心(hub)准备大型数据集 -* 如何使用高级`训练`API微调一个模型 + +* 如何从模型中心(hub)加载大型数据集 +* 如何使用高级的 `Trainer` API 微调一个模型 * 如何使用自定义训练过程 -* 如何利用🤗 Accelerate库在任何分布式设备上轻松运行自定义训练过程 +* 如何利用🤗 Accelerate 库在所有分布式设备上轻松运行自定义训练过程 {:else} -* 如何从模型中心(hub)准备大型数据集 + +* 如何从模型中心(hub)加载大型数据集 * 如何使用 Keras 微调模型 * 如何使用 Keras 进行预测 * 如何使用自定义指标 {/if} -为了将经过训练的参数上传到Hugging Face Hub,您需要一个huggingface.co帐户: [创建一个账户](https://huggingface.co/join) \ No newline at end of file +为了将经过训练的参数上传到 Hugging Face Hub,你需要一个 huggingface.co 帐户: [创建一个账户](https://huggingface.co/join) diff --git a/chapters/zh-CN/chapter3/2.mdx b/chapters/zh-CN/chapter3/2.mdx index fbe6d00d3..b9d79afa2 100644 --- a/chapters/zh-CN/chapter3/2.mdx +++ b/chapters/zh-CN/chapter3/2.mdx @@ -23,13 +23,14 @@ {/if} {#if fw === 'pt'} -这一小节学习[第一小节](/course/chapter2)中提到的“如何使用模型中心(hub)大型数据集”,下面是我们用模型中心的数据在PyTorch上训练句子分类器的一个例子: + +在这一小节你将学习 [第一小节](/course/chapter2) 中提到的“如何使用模型中心(hub)加载大型数据集”,下面是用模型中心的数据在 PyTorch 上训练句子分类器的一个例子: ```python import torch from transformers import AdamW, AutoTokenizer, AutoModelForSequenceClassification -# Same as before +# 和之前一样 checkpoint = "bert-base-uncased" tokenizer = AutoTokenizer.from_pretrained(checkpoint) model = AutoModelForSequenceClassification.from_pretrained(checkpoint) @@ -39,7 +40,7 @@ sequences = [ ] batch = tokenizer(sequences, padding=True, truncation=True, return_tensors="pt") -# This is new +# 新增部分 batch["labels"] = torch.tensor([1, 1]) optimizer = AdamW(model.parameters()) @@ -47,15 +48,17 @@ loss = model(**batch).loss loss.backward() optimizer.step() ``` + {:else} -这一小节学习[第一小节](/course/chapter2)中提到的“如何使用模型中心(hub)大型数据集”,下面是我们用模型中心的数据在TensorFlow上训练句子分类器的一个例子: + +在这一小节你将学习 [第一小节](/course/chapter2) 中提到的“如何使用模型中心(hub)加载大型数据集”,下面是用模型中心的数据在 TensorFlow 上训练句子分类器的一个例子: ```python import tensorflow as tf import numpy as np from transformers import AutoTokenizer, TFAutoModelForSequenceClassification -# Same as before +# 和之前一样 checkpoint = "bert-base-uncased" tokenizer = AutoTokenizer.from_pretrained(checkpoint) model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint) @@ -65,18 +68,19 @@ sequences = [ ] batch = dict(tokenizer(sequences, padding=True, truncation=True, return_tensors="tf")) -# This is new +# 新增部分 model.compile(optimizer="adam", loss="sparse_categorical_crossentropy") labels = tf.convert_to_tensor([1, 1]) model.train_on_batch(batch, labels) ``` + {/if} -当然,仅仅用两句话训练模型不会产生很好的效果。为了获得更好的结果,您需要准备一个更大的数据集。 +然而仅用两句话训练模型不会产生很好的效果,你需要准备一个更大的数据集才能得到更好的训练结果。 -在本节中,我们将使用MRPC(微软研究释义语料库)数据集作为示例,该数据集由威廉·多兰和克里斯·布罗克特在[这篇文章](https://www.aclweb.org/anthology/I05-5002.pdf)发布。该数据集由5801对句子组成,每个句子对带有一个标签,指示它们是否为同义(即,如果两个句子的意思相同)。我们在本章中选择了它,因为它是一个小数据集,所以很容易对它进行训练。 +在本节中,我们以 MRPC(微软研究院释义语料库)数据集为例,该数据集由威廉·多兰和克里斯·布罗克特在 [这篇文章](https://www.aclweb.org/anthology/I05-5002.pdf) 发布,由 5801 对句子组成,每个句子对带有一个标签来指示它们是否为同义(即两个句子的意思相同)。在本章选择该数据集的原因是它的数据体量小,容易对其进行训练。 -### 从模型中心(Hub)加载数据集 [[从模型中心(Hub)加载数据集]] +## 从模型中心(Hub)加载数据集 [[从模型中心(Hub)加载数据集]] {#if fw === 'pt'} @@ -84,9 +88,14 @@ model.train_on_batch(batch, labels) {/if} -模型中心(hub)不只是包含模型;它也有许多不同语言的多个数据集。点击[数据集](https://huggingface.co/datasets)的链接即可进行浏览。我们建议您在阅读本节后阅读一下[加载和处理新的数据集](https://huggingface.co/docs/datasets/loading)这篇文章,这会让您对huggingface的darasets更加清晰。但现在,让我们使用MRPC数据集中的[GLUE 基准测试数据集](https://gluebenchmark.com/),它是构成MRPC数据集的10个数据集之一,这是一个学术基准,用于衡量机器学习模型在10个不同文本分类任务中的性能。 +模型中心(hub)不仅仅包含模型,还有许多别的语言的数据集。访问 [Datasets](https://huggingface.co/datasets) 的链接即可进行浏览。我们建议你在完成本节的学习后阅读一下 [加载和处理新的数据集](https://huggingface.co/docs/datasets/loading) 这篇文章,这会让你对 huggingface 的数据集理解更加清晰。现在让我们使用 MRPC 数据集中的 [GLUE 基准测试数据集](https://gluebenchmark.com) 作为我们训练所使用的数据集,它是构成 MRPC 数据集的 10 个数据集之一,作为一个用于衡量机器学习模型在 10 个不同文本分类任务中性能的学术基准。 -🤗 Datasets库提供了一个非常便捷的命令,可以在模型中心(hub)上下载和缓存数据集。我们可以通过以下的代码下载MRPC数据集: +🤗 Datasets 库提供了一条非常便捷的命令,可以在模型中心(hub)上下载和缓存数据集。你可以以下代码下载 MRPC 数据集: + + +⚠️ **警告** 确保你已经运行 `pip install datasets` 安装了 `datasets`。然后,再继续下面的加载 MRPC 数据集和打印出来查看其内容。 + + ```py from datasets import load_dataset @@ -112,46 +121,52 @@ DatasetDict({ }) ``` -正如你所看到的,我们获得了一个**DatasetDict**对象,其中包含训练集、验证集和测试集。每一个集合都包含几个列(**sentence1**, **sentence2**, **label**, and **idx**)以及一个代表行数的变量,即每个集合中的行的个数(因此,训练集中有3668对句子,验证集中有408对,测试集中有1725对)。 +现在我们获得了一个 `DatasetDict` 对象,这个对象包含训练集、验证集和测试集。每一个集合都包含 4 个列( `sentence1` , `sentence2` , `label` 和 `idx` )以及一个代表行数的变量(每个集合中的行的个数)。运行结果显示该训练集中有 3668 对句子,验证集中有 408 对,测试集中有 1725 对。 -默认情况下,此命令在下载数据集并缓存到 **~/.cache/huggingface/datasets**. 回想一下第2章,您可以通过设置**HF_HOME**环境变量来自定义缓存的文件夹。 +默认情况下,该命令会下载数据集并缓存到 `~/.cache/huggingface/datasets` 。回想在第 2 章中我们学到过,可以通过设置 `HF_HOME` 环境变量来自定义缓存的文件夹。 -我们可以访问我们数据集中的每一个**raw_train_dataset**对象,如使用字典: +我们可以访问该数据集中的每一个 `raw_train_dataset` 对象,例如使用字典: ```py raw_train_dataset = raw_datasets["train"] raw_train_dataset[0] ``` -```python out -{'idx': 0, - 'label': 1, - 'sentence1': 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', - 'sentence2': 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .'} +```python +{ + "idx": 0, + "label": 1, + "sentence1": 'Amrozi accused his brother , whom he called " the witness " , of deliberately distorting his evidence .', + "sentence2": 'Referring to him as only " the witness " , Amrozi accused his brother of deliberately distorting his evidence .', +} ``` -我们可以看到标签已经是整数了,所以我们不需要对标签做任何预处理。要知道哪个数字对应于哪个标签,我们可以查看**raw_train_dataset**的**features**. 这将告诉我们每列的类型: +现在可以看到标签已经是整数了,因此不需要对标签做任何预处理。如果想要知道不同数字对应标签的实际含义,我们可以查看 `raw_train_dataset` 的 `features` 。这告诉我们每列的类型: ```py raw_train_dataset.features ``` -```python out -{'sentence1': Value(dtype='string', id=None), - 'sentence2': Value(dtype='string', id=None), - 'label': ClassLabel(num_classes=2, names=['not_equivalent', 'equivalent'], names_file=None, id=None), - 'idx': Value(dtype='int32', id=None)} +```python +{ + "sentence1": Value(dtype="string", id=None), + "sentence2": Value(dtype="string", id=None), + "label": ClassLabel( + num_classes=2, names=["not_equivalent", "equivalent"], names_file=None, id=None + ), + "idx": Value(dtype="int32", id=None), +} ``` -在上面的例子之中,**Label(标签)** 是一种**ClassLabel(分类标签)**,使用整数建立起到类别标签的映射关系。**0**对应于**not_equivalent**,**1**对应于**equivalent**。 +上面的例子中的 `Label(标签)` 是一种 `ClassLabel(分类标签)` ,也就是使用整数建立起类别标签的映射关系。 `0` 对应于 `not_equivalent(非同义)` , `1` 对应于 `equivalent(同义)` 。 -✏️ **试试看!** 查看训练集的第15行元素和验证集的87行元素。他们的标签是什么? +✏️ **试试看!** 查看训练集的第 15 行元素和验证集的 87 行元素。他们的标签是什么? -### 预处理数据集 [[预处理数据集]] +## 预处理数据集 [[预处理数据集]] {#if fw === 'pt'} @@ -159,7 +174,7 @@ raw_train_dataset.features {/if} -为了预处理数据集,我们需要将文本转换为模型能够理解的数字。正如你在[第二章](/course/chapter2)上看到的那样 +为了预处理数据集,我们需要将文本转换为模型能够理解的数字。在 [第二章](/course/chapter2) 我们已经学习过。这是通过一个 Tokenizer 完成的,我们可以向 Tokenizer 输入一个句子或一个句子列表。以下代码表示对每对句子中的所有第一句和所有第二句进行 tokenize: ```py from transformers import AutoTokenizer @@ -170,59 +185,108 @@ tokenized_sentences_1 = tokenizer(raw_datasets["train"]["sentence1"]) tokenized_sentences_2 = tokenizer(raw_datasets["train"]["sentence2"]) ``` -然而,在两句话传递给模型,预测这两句话是否是同义之前。我们需要这两句话依次进行适当的预处理。幸运的是,标记器不仅仅可以输入单个句子还可以输入一组句子,并按照我们的BERT模型所期望的输入进行处理: +不过在将两句话传递给模型,预测这两句话是否是同义之前,我们需要给这两句话依次进行适当的预处理。Tokenizer 不仅仅可以输入单个句子,还可以输入一组句子,并按照 BERT 模型所需要的输入进行处理: ```py inputs = tokenizer("This is the first sentence.", "This is the second one.") 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] +```python +{ + "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], } ``` -我们在[第二章](/course/chapter2) 讨论了**输入词id(input_ids)** 和 **注意力遮罩(attention_mask)** ,但我们在那个时候没有讨论**类型标记ID(token_type_ids)**。在这个例子中,**类型标记ID(token_type_ids)**的作用就是告诉模型输入的哪一部分是第一句,哪一部分是第二句。 +我们在 [第二章](/course/chapter2) 讨论了 `输入词id(input_ids)` 和 `注意力遮罩(attention_mask)` ,但尚未讨论 `token类型ID(token_type_ids)` 。在本例中, `token类型ID(token_type_ids)` 的作用就是告诉模型输入的哪一部分是第一句,哪一部分是第二句。 -✏️ ** 试试看!** 选取训练集中的第15个元素,将两句话分别标记为一对。结果和上方的例子有什么不同? +✏️ ** 试试看!** 选取训练集中的第 15 个元素,将两句话分别进行tokenization。结果和上方的例子有什么不同? -如果我们将**input_ids**中的id转换回文字: +如果将 `input_ids` 中的 id 转换回文字: ```py tokenizer.convert_ids_to_tokens(inputs["input_ids"]) ``` -我们将得到: +将得到: -```python out -['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] +```python +[ + "[CLS]", + "this", + "is", + "the", + "first", + "sentence", + ".", + "[SEP]", + "this", + "is", + "the", + "second", + "one", + ".", + "[SEP]", +] ``` -所以我们看到模型需要输入的形式是 **[CLS] sentence1 [SEP] sentence2 [SEP]**。因此,当有两句话的时候。**类型标记ID(token_type_ids)** 的值是: +所以我们看到模型需要输入的形式是 `[CLS] sentence1 [SEP] sentence2 [SEP]` 。所以当有两句话的时候, `token类型ID(token_type_ids)` 的值是: -```python out -['[CLS]', 'this', 'is', 'the', 'first', 'sentence', '.', '[SEP]', 'this', 'is', 'the', 'second', 'one', '.', '[SEP]'] -[ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] +```python +[ + "[CLS]", + "this", + "is", + "the", + "first", + "sentence", + ".", + "[SEP]", + "this", + "is", + "the", + "second", + "one", + ".", + "[SEP]", +] +[0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] ``` -如您所见,输入中 **[CLS] sentence1 [SEP]** 它们的类型标记ID均为**0**,而其他部分,对应于**sentence2 [SEP]**,所有的类型标记ID均为**1**. +现在输入中 `[CLS] sentence1 [SEP]` 它们的 `token_type_ids` 均为 `0` ,而其他部分例如 `sentence2 [SEP]` ,所有的 `token_type_ids` 均为 `1` 。 -请注意,如果选择其他的检查点,则不一定具有**类型标记ID(token_type_ids)**(例如,如果使用DistilBERT模型,就不会返回它们)。只有当它在预训练期间使用过这一层,模型在构建时依赖它们,才会返回它们。 +请注意,如果选择其他的 checkpoint,不一定具有 `token_type_ids` ,比如,DistilBERT 模型就不会返回。只有当 tokenizer 在预训练期间使用过这一层,也就是模型在构建时需要它们时,才会返回 `token_type_ids` 。 -用类型标记ID对BERT进行预训练,并且使用[第一章](/course/chapter1)的遮罩语言模型,还有一个额外的应用类型,叫做下一句预测. 这项任务的目标是建立成对句子之间关系的模型。 +在这里,BERT 使用了带有 `token_type_ids` 的预训练 tokenizer,除了我们在 [第一章](/course/chapter1) 中讨论的掩码语言建模,还有一个额外的应用类型称为“下一句预测”。这个任务的目标是对句子对之间的关系进行建模。 -在下一个句子预测任务中,会给模型输入成对的句子(带有随机遮罩的标记),并被要求预测第二个句子是否紧跟第一个句子。为了提高模型的泛化能力,数据集中一半的两个句子在原始文档中挨在一起,另一半的两个句子来自两个不同的文档。 -一般来说,你不需要担心是否有**类型标记ID(token_type_ids)**。在您的标输入中:只要您对标记器和模型使用相同的检查点,一切都会很好,因为标记器知道向其模型提供什么。 +在下一句预测任务中,会给模型输入成对的句子(带有随机遮罩的 token),并要求预测第二个句子是否紧跟第一个句子。为了使任务具有挑战性,提高模型的泛化能力,数据集中一有一半句子对中的句子在原始文档中顺序排列,另一半句子对中的两个句子来自两个不同的文档。 -现在我们已经了解了标记器如何处理一对句子,我们可以使用它对整个数据集进行处理:如[之前的章节](/course/chapter2),我们可以给标记器提供一组句子,第一个参数是它第一个句子的列表,第二个参数是第二个句子的列表。这也与我们在[第二章](/course/chapter2)中看到的填充和截断选项兼容. 因此,预处理训练数据集的一种方法是: +一般来说无需要担心在你的输入中是否需要有 `token_type_ids` 。只要你使用相同的 checkpoint 的 Tokenizer 和模型,Tokenizer 就会知道向模型提供什么,一切都会顺利进行。 + +现在我们已经了解了 Tokenizer 如何处理一对句子,我们可以用它来处理整个数据集:就像在 [第二章](/course/chapter2) 中一样,我们可以给 Tokenizer 提供一对句子,第一个参数是它第一个句子的列表,第二个参数是第二个句子的列表。这也与我们在 [第二章](/course/chapter2) 中看到的填充和截断选项兼容。因此预处理训练数据集的一种方法是: ```py tokenized_dataset = tokenizer( @@ -233,27 +297,29 @@ tokenized_dataset = tokenizer( ) ``` -这很有效,但它的缺点是返回字典(字典的键是**输入词id(input_ids)** , **注意力遮罩(attention_mask)** 和 **类型标记ID(token_type_ids)**,字典的值是键所对应值的列表)。而且只有当您在转换过程中有足够的内存来存储整个数据集时才不会出错(而🤗数据集库中的数据集是以[Apache Arrow](https://arrow.apache.org/)文件存储在磁盘上,因此您只需将接下来要用的数据加载在内存中,因此会对内存容量的需求要低一些)。 +这种方法虽然有效,但有一个缺点是它返回的是一个字典(字典的键是 `输入词id(input_ids)` , `注意力遮罩(attention_mask)` 和 `token类型ID(token_type_ids)` ,字典的值是键所对应值的列表)。这意味着在转换过程中要有足够的内存来存储整个数据集才不会出错。不过来自🤗 Datasets 库中的数据集是以 [Apache Arrow](https://arrow.apache.org) 格式存储在磁盘上的,因此你只需将接下来要用的数据加载在内存中,而不是加载整个数据集,这对内存容量的需求比较友好。 -为了将数据保存为数据集,我们将使用[Dataset.map()](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map)方法,如果我们需要做更多的预处理而不仅仅是标记化,那么这也给了我们一些额外的自定义的方法。这个方法的工作原理是在数据集的每个元素上应用一个函数,因此让我们定义一个标记输入的函数: +我们将使用 [Dataset.map()](https://huggingface.co/docs/datasets/package_reference/main_classes#datasets.Dataset.map) 方法将数据保存为 dataset 格式,如果我们需要做更多的预处理而不仅仅是 tokenization 它还支持了一些额外的自定义的方法。 `map()` 方法的工作原理是使用一个函数处理数据集的每个元素。让我们定义一个对输入进行 tokenize 的函数: ```py def tokenize_function(example): return tokenizer(example["sentence1"], example["sentence2"], truncation=True) ``` -此函数的输入是一个字典(与数据集的项类似),并返回一个包含**输入词id(input_ids)** , **注意力遮罩(attention_mask)** 和 **类型标记ID(token_type_ids)** 键的新字典。请注意,如果像上面的**示例**一样,如果键所对应的值包含多个句子(每个键作为一个句子列表),那么它依然可以工作,就像前面的例子一样标记器可以处理成对的句子列表。这样的话我们可以在调用**map()**使用该选项 **batched=True** ,这将显著加快标记与标记的速度。这个**标记器**来自[🤗 Tokenizers](https://github.com/huggingface/tokenizers)库由Rust编写而成。当我们一次给它大量的输入时,这个标记器可以非常快。 +该函数接收一个字典(与 dataset 的项类似)并返回一个包含 `输入词id(input_ids)` , `注意力遮罩(attention_mask)` 和 `token_type_ids` 键的新字典。请注意,如果 `example` 字典所对应的值包含多个句子(每个键作为一个句子列表),那么它依然可以运行,就像前面的例子一样, `tokenizer` 可以处理成对的句子列表,这样的话我们可以在调用 `map()` 时使用该选项 `batched=True` ,这将显著加快处理的速度。 `tokenizer` 来自 [🤗 Tokenizers](https://github.com/huggingface/tokenizers) 库,由 Rust 编写而成。当一次给它很多输入时,这个 `tokenizer` 可以处理地非常快。 + +请注意,我们暂时在 `tokenize_function` 中省略了 padding 参数。这是因为将所有的样本填充到最大长度有些浪费。一个更好的做法是:在构建 batch 的时候。这样我们只需要填充到每个 batch 中的最大长度,而不是整个数据集的最大长度。当输入长度不稳定时,这可以节省大量时间和处理能力! -请注意,我们现在在标记函数中省略了**padding**参数。这是因为在标记的时候将所有样本填充到最大长度的效率不高。一个更好的做法:在构建批处理时填充样本更好,因为这样我们只需要填充到该批处理中的最大长度,而不是整个数据集的最大长度。当输入长度变化很大时,这可以节省大量时间和处理能力! +下面是我们如何使用一次性 `tokenize_function` 处理整个数据集。我们在调用 `map` 时使用了 `batch =True` ,这样函数就可以同时处理数据集的多个元素,而不是分别处理每个元素,这样可以更快进行预处理。 -下面是我们如何在所有数据集上同时应用标记函数。我们在调用**map**时使用了**batch =True**,这样函数就可以同时应用到数据集的多个元素上,而不是分别应用到每个元素上。这将使我们的预处理快许多 +以下是如何使用 tokenization 函数处理我们的整个数据集的方法。我们在调用 map 时使用了 `batched=True` ,因此该函数会一次性处理数据集的多个元素,而不是单独处理每个元素。这样可以实现更快的预处理。 ```py tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) tokenized_datasets ``` -🤗Datasets库应用这种处理的方式是向数据集添加新的字段,每个字段对应预处理函数返回的字典中的每个键: +🤗Datasets 库进行这种处理的方式是向数据集添加新的字段,每个字段对应预处理函数返回的字典中的每个键: ```python out DatasetDict({ @@ -272,42 +338,47 @@ DatasetDict({ }) ``` -在使用预处理函数**map()**时,甚至可以通过传递**num_proc**参数使用并行处理。我们在这里没有这样做,因为🤗标记器库已经使用多个线程来更快地标记我们的样本,但是如果您没有使用该库支持的快速标记器,使用**num_proc**可能会加快预处理。 +在使用预处理函数 `map()` 时,甚至可以通过传递 `num_proc` 参数并行处理。我们在这里没有这样做,因为在这个例子中🤗 Tokenizers 库已经使用多线程来更快地对样本 tokenize,但是如果没有使用该库支持的快速 tokenizer,使用 `num_proc` 可能会加快预处理。 -我们的**标记函数(tokenize_function)**返回包含**输入词id(input_ids)** , **注意力遮罩(attention_mask)** 和 **类型标记ID(token_type_ids)** 键的字典,所以这三个字段被添加到数据集的标记的结果中。注意,如果预处理函数**map()**为现有键返回一个新值,那将会修改原有键的值。 +我们的 `tokenize_function` 返回包含 `输入词id(input_ids)` , `注意力遮罩(attention_mask)` 和 `token_type_ids` 键的字典,这三个字段被添加到数据集的三个集合里(训练集、验证集和测试集)。请注意,如果预处理函数 `map()` 为现有键返回一个新值,我们可以通过使用 `map()` 函数返回的新值修改现有的字段。 -最后一件我们需要做的事情是,当我们一起批处理元素时,将所有示例填充到最长元素的长度——我们称之为动态填充。 +我们最后需要做的是将所有示例填充到该 batch 中最长元素的长度,这种技术被称为动态填充。 -### 动态填充 [[动态填充]] +## 动态填充 [[动态填充]] {#if fw === 'pt'} -负责在批处理中将数据整理为一个batch的函数称为*collate函数*。它是你可以在构建**DataLoader**时传递的一个参数,默认是一个函数,它将把你的数据集转换为PyTorch张量,并将它们拼接起来(如果你的元素是列表、元组或字典,则会使用递归)。这在我们的这个例子中下是不可行的,因为我们的输入不是都是相同大小的。我们故意在之后每个batch上进行填充,避免有太多填充的过长的输入。这将大大加快训练速度,但请注意,如果你在TPU上训练,这可能会导致问题——TPU喜欢固定的形状,即使这需要额外的填充。 + +负责在批处理中将数据整理为一个 batch 的函数称为 `collate 函数` 。这是一个可以在构建 `DataLoader` 时传递的一个参数,默认是一个将你的数据集转换为 PyTorch 张量并将它们拼接起来的函数(如果你的元素是列表、元组或字典,则会使用递归进行拼接)。这在本例子中下是不可行的,因为我们的输入的大小可能是不相同的。我们特意推迟了填充的时间,只在每个 batch 上进行必要的填充,以避免出现有大量填充的过长输入。这将大大加快训练速度,但请注意,如果你在 TPU 上训练,需要注意一个问题——TPU 喜欢固定的形状,即使这需要额外填充很多无用的 token。 {:else} -负责在批处理中将数据整理为一个batch的函数称为*collate函数*。它只会将您的样本转换为 tf.Tensor并将它们拼接起来(如果你的元素是列表、元组或字典,则会使用递归)。这在我们的这个例子中下是不可行的,因为我们的输入不是都是相同大小的。我们故意在之后每个batch上进行填充,避免有太多填充的过长的输入。这将大大加快训练速度,但请注意,如果你在TPU上训练,这可能会导致问题——TPU喜欢固定的形状,即使这需要额外的填充。 +负责在批处理中将数据整理为一个 batch 的函数称为 `collate 函数` 。默认的拼合函数只会将你的样本转换为 `tf.Tensor` 并将它们拼接起来(如果你的元素是列表、元组或字典,则会使用递归进行拼接)。这在本例中是不可行的,因为我们的输入不是都是相同大小的。我们特意推迟了填充时间,只在每个 batch 上进行填充,以避免有太多填充的过长的输入。这将大大加快训练速度,但请注意,如果你在 TPU 上训练,需要注意一个问题——TPU 喜欢固定的形状,即使这需要额外填充很多无用的 token。 {/if} -为了解决句子长度统一的问题,我们必须定义一个collate函数,该函数会将每个batch句子填充到正确的长度。幸运的是,🤗transformer库通过**DataCollatorWithPadding**为我们提供了这样一个函数。当你实例化它时,需要一个标记器(用来知道使用哪个词来填充,以及模型期望填充在左边还是右边),并将做你需要的一切: +为了解决句子长度不统一的问题,我们必须定义一个 collate 函数,该函数会将每个 batch 句子填充到正确的长度。幸运的是,🤗transformer 库通过 `DataCollatorWithPadding` 为我们提供了这样一个函数。当你实例化它时,它需要一个 tokenizer (用来知道使用哪种填充 token 以及模型期望在输入的左边填充还是右边填充),然后它会自动完成所有需要的操作: {#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} -为了测试这个新玩具,让我们从我们的训练集中抽取几个样本。这里,我们删除列**idx**, **sentence1**和**sentence2**,因为不需要它们,并查看一个batch中每个条目的长度: +为了测试这个新东西,让我们从我们的训练集中抽取几个样本,在这里,我们删除列 `idx` , `sentence1` 和 `sentence2` ,因为不需要它们,而且删除包含字符串的列(我们不能用字符串创建张量),然后查看一个 batch 中每个条目的长度: ```py samples = tokenized_datasets["train"][:8] @@ -315,11 +386,11 @@ samples = {k: v for k, v in samples.items() if k not in ["idx", "sentence1", "se [len(x) for x in samples["input_ids"]] ``` -```python out +```python [50, 59, 47, 67, 59, 50, 62, 32] ``` -毫无疑问,我们得到了不同长度的样本,从32到67。动态填充意味着该批中的所有样本都应该填充到长度为67,这是该批中的最大长度。如果没有动态填充,所有的样本都必须填充到整个数据集中的最大长度,或者模型可以接受的最大长度。让我们再次检查**data_collator**是否正确地动态填充了这批样本: +不出所料,我们得到了不同长度的样本,从 32 到 67。动态填充意味着这个 batch 都应该填充到长度为 67,这是这个 batch 中的最大长度。如果没有动态填充,所有的样本都必须填充到整个数据集中的最大长度,或者模型可以接受的最大长度。让我们再次检查 `data_collator` 是否正确地动态填充了这批样本: ```py: @@ -330,35 +401,39 @@ batch = data_collator(samples) {#if fw === 'tf'} -```python out -{'attention_mask': TensorShape([8, 67]), - 'input_ids': TensorShape([8, 67]), - 'token_type_ids': TensorShape([8, 67]), - 'labels': TensorShape([8])} +```python +{ + "attention_mask": TensorShape([8, 67]), + "input_ids": TensorShape([8, 67]), + "token_type_ids": TensorShape([8, 67]), + "labels": TensorShape([8]), +} ``` {:else} -```python out -{'attention_mask': torch.Size([8, 67]), - 'input_ids': torch.Size([8, 67]), - 'token_type_ids': torch.Size([8, 67]), - 'labels': torch.Size([8])} +```python +{ + "attention_mask": torch.Size([8, 67]), + "input_ids": torch.Size([8, 67]), + "token_type_ids": torch.Size([8, 67]), + "labels": torch.Size([8]), +} ``` -看起来不错!现在,我们已经将原始文本转化为了模型可以处理的数据,我们已准备好对其进行微调! +看起来不错!现在,我们已经从原始文本转化为了模型可以处理的数据,我们准备好对其进行微调。 {/if} -✏️ ** 试试看!** 在GLUE SST-2数据集上应用预处理。它有点不同,因为它是由单个句子而不是成对的句子组成的,但是我们所做的其他事情看起来应该是一样的。另一个更难的挑战,请尝试编写一个可用于任何GLUE任务的预处理函数。 +✏️ ** 试试看!** 在 GLUE SST-2 数据集上复刻上述预处理。它有点不同,因为它是由单句而不是成对的句子组成的,但是我们所做的其他事情看起来应该是一样的。另一个进阶的挑战是尝试编写一个可用于任何 GLUE 任务的预处理函数。 {#if fw === 'tf'} -现在我们有了dataset和data collator,我们需要将dataset批量地应用data collator。 我们可以手动加载批次并整理它们,但这需要大量工作,而且可能性能也不是很好。 相反,有一个简单的方法可以为这个问题提供高效的解决方案:`to_tf_dataset()`。 这将在您的数据集上调用一个 `tf.data.Dataset`的方法,这个方法带有一个可选的data collator功能。 `tf.data.Dataset` 是 Keras 可用于 `model.fit()` 的原生 TensorFlow 格式,因此这种方法会立即将🤗 Dataset 转换为可用于训练的格式。 让我们看看它在我们的数据集上是如何使用的! +现在我们有了 dataset 和 data collator,我们需要使用 data collator 批量地处理 dataset。我们可以手动加载批次并进行整合,但这需要大量工作,性能也有可能不好。相反,有一个简单的方法为这个问题提供高效的解决方案: `to_tf_dataset()` 。它将把你的数据集包装一个 `tf.data.Dataset` 类中,这个方法带有一个可选的 data collator 功能。 `tf.data.Dataset` 是 TensorFlow 的本地格式,Keras 可以直接用它来进行 `model.fit()` ,因此这种方法会立即将🤗 Dataset 转换为可用于训练的格式。让我们用我们的数据集演示一下这个方法! ```py tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( @@ -378,6 +453,6 @@ tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( ) ``` -就是这样! 我们可以将这些数据集带入下一节,在经过所有艰苦的数据预处理工作之后,训练将变得非常简单。 +就是这样!我们可以把这些数据集带入下一节,在经过所有艰苦的数据预处理工作之后,训练将变得非常简单和愉快。 {/if} diff --git a/chapters/zh-CN/chapter3/3.mdx b/chapters/zh-CN/chapter3/3.mdx index efcdb0916..b67557bc5 100644 --- a/chapters/zh-CN/chapter3/3.mdx +++ b/chapters/zh-CN/chapter3/3.mdx @@ -11,9 +11,9 @@ -🤗 Transformers提供了一个 **Trainer** 类来帮助您在自己的数据集上微调任何预训练模型。完成上一节中的所有数据预处理工作后,您只需要执行几个步骤来创建 **Trainer** .最难的部分可能是为 **Trainer.train()**配置运行环境,因为它在 CPU 上运行速度会非常慢。如果您没有设置 GPU,您可以访问免费的 GPU 或 TPU[Google Colab](https://colab.research.google.com/). +🤗 Transformers 提供了一个 `Trainer` 类,可以帮助你在数据集上微调任何预训练模型。在上一节中完成所有数据预处理工作后,你只需完成几个步骤来定义 `Trainer` 。最困难的部分可能是准备运行 `Trainer.train()` 所需的环境,因为在 CPU 上运行速度非常慢。如果你没有设置 GPU,可以使用 [Google Colab](https://colab.research.google.com/) (国内网络无法使用) 上获得免费的 GPU 或 TPU。 -下面的示例假设您已经执行了上一节中的示例。下面这段代码,概括了您需要提前运行的代码: +下面的示例假设你已经执行了上一节中的示例。下面是在开始学习这一节之前你需要运行的代码: ```py from datasets import load_dataset @@ -34,7 +34,7 @@ data_collator = DataCollatorWithPadding(tokenizer=tokenizer) ### Training [[Training]] -在我们定义我们的 **Trainer** 之前首先要定义一个 **TrainingArguments** 类,它将包含 **Trainer**用于训练和评估的所有超参数。您唯一必须提供的参数是保存训练模型的目录,以及训练过程中的检查点。对于其余的参数,您可以保留默认值,这对于基本微调应该非常有效。 +在我们定义 `Trainer` 之前第一步要定义一个 `TrainingArguments` 类,它包含 `Trainer` 在训练和评估中使用的所有超参数。你只需要提供的参数是一个用于保存训练后的模型以及训练过程中的 checkpoint 的目录。对于其余的参数你可以保留默认值,这对于简单的微调应该效果就很好了。 ```py from transformers import TrainingArguments @@ -44,11 +44,11 @@ training_args = TrainingArguments("test-trainer") -💡 如果您想在训练期间自动将模型上传到 Hub,请将push_to_hub=True添加到TrainingArguments之中. 我们将在[第四章](/course/chapter4/3)中详细介绍这部分。 +💡 如果你想在训练期间自动将模型上传到 Hub,请将 `push_to_hub=True` 添加到 TrainingArguments 之中。我们将在 [第四章](/course/chapter4/3) 中详细介绍这部分。 -第二步是定义我们的模型。正如在[之前的章节](/2_Using Transformers/Introduction)一样,我们将使用 **AutoModelForSequenceClassification** 类,它有两个参数: +第二步是定义我们的模型。与 [前一章](/course/chapter2) 一样,我们将使用 `AutoModelForSequenceClassification` 类,它有两个参数: ```py from transformers import AutoModelForSequenceClassification @@ -56,9 +56,9 @@ from transformers import AutoModelForSequenceClassification model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) ``` -你会注意到,和[第二章](/course/chapter2)不一样的是,在实例化此预训练模型后会收到警告。这是因为 BERT 没有在句子对分类方面进行过预训练,所以预训练模型的头部已经被丢弃,而是添加了一个适合句子序列分类的新头部。警告表明一些权重没有使用(对应于丢弃的预训练头的那些),而其他一些权重被随机初始化(新头的那些)。最后鼓励您训练模型,这正是我们现在要做的。 +你会注意到,和 [第二章](/course/chapter2) 不同的是,在实例化此预训练模型后会收到警告。这是因为 BERT 没有在句子对分类方面进行过预训练,所以预训练模型的 head 已经被丢弃,而是添加了一个适合句子序列分类的新头部。这些警告表明一些权重没有使用(对应于被放弃的预训练头的权重),而有些权重被随机初始化(对应于新 head 的权重)。 -一旦我们有了我们的模型,我们就可以定义一个 **Trainer** 通过将之前构造的所有对象传递给它——我们的**model** 、**training_args** ,训练和验证数据集,**data_collator** ,和 **tokenizer** : +一旦有了我们的模型,我们就可以定义一个 `Trainer` 把到目前为止构建的所有对象 —— `model` ,`training_args`,训练和验证数据集, `data_collator` 和 `tokenizer` 传递给 `Trainer` : ```py from transformers import Trainer @@ -73,23 +73,23 @@ trainer = Trainer( ) ``` -请注意,当您在这里完成**tokenizer**后,默认 **Trainer**使用 的**data_collator**会使用之前预定义的 **DataCollatorWithPadding** ,因此您可以在这个例子中跳过 **data_collator=data_collator**。在第 2 节中向您展示这部分处理仍然很重要! +请注意,当你在这里传递 `tokenizer` 时, `Trainer` 默认使用的 `data_collator` 是之前预定义的 `DataCollatorWithPadding`。所以你可以在本例中可以跳过 `data_collator=data_collator` 一行。在第 2 节中向你展示这部分处理过程仍然很重要! -为了让预训练模型在在我们的数据集上微调,我们只需要调用**Trainer**的**train()** 方法 : +要在我们的数据集上微调模型,我们只需调用 `Trainer` 的 `train()` 方法: ```py trainer.train() ``` -这将开始微调(在GPU上应该需要几分钟),并每500步报告一次训练损失。但是,它不会告诉您模型的性能如何(或质量如何)。这是因为: +开始微调(在 GPU 上应该需要几分钟),每 500 步报告一次训练损失。然而它不会告诉你模型的性能(或质量)如何。这是因为: -1. 我们没有通过将**evaluation_strategy**设置为“**steps**”(在每次更新参数的时候评估)或“**epoch**”(在每个epoch结束时评估)来告诉**Trainer**在训练期间进行评估。 -2. 我们没有为**Trainer**提供一个**compute_metrics()**函数来直接计算模型的好坏(否则评估将只输出loss,这不是一个非常直观的数字)。 +1. 我们没有告诉 Trainer 在训练过程中进行评估,比如将 `evaluation_strategy` 设置为“ `step` ”(在每个 `eval_steps` 步骤评估一次)或“ `epoch` ”(在每个 epoch 结束时评估)。 +2. 我们没有为 `Trainer` 提供一个 `compute_metrics()` 函数来计算上述评估过程的指标(否则评估将只会输出 loss,但这不是一个非常直观的数字)。 ### 评估 [[评估]] -让我们看看如何构建一个有用的 **compute_metrics()** 函数并在我们下次训练时使用它。该函数必须采用 **EvalPrediction** 对象(带有 **predictions** 和 **label_ids** 字段的参数元组)并将返回一个字符串到浮点数的字典(字符串是返回的指标的名称,而浮点数是它们的值)。我们可以使用 **Trainer.predict()** 命令来使用我们的模型进行预测: +让我们看看如何构建一个有用的 `compute_metrics()` 函数,并在下次训练时使用它。该函数必须接收一个 `EvalPrediction` 对象(它是一个带有 `predictions` 和 `label_ids` 字段的参数元组),并将返回一个字符串映射到浮点数的字典(字符串是返回的指标名称,而浮点数是其值)。为了从我们的模型中获得预测结果,可以使用 `Trainer.predict()` 命令: ```py predictions = trainer.predict(tokenized_datasets["validation"]) @@ -100,9 +100,7 @@ print(predictions.predictions.shape, predictions.label_ids.shape) (408, 2) (408,) ``` - **predict()** 的输出结果是具有三个字段的命名元组: **predictions** , **label_ids** , 和 **metrics** .这 **metrics** 字段将只包含传递的数据集的loss,以及一些运行时间(预测所需的总时间和平均时间)。如果我们定义了自己的 **compute_metrics()** 函数并将其传递给 **Trainer** ,该字段还将包含**compute_metrics()**的结果。 - -**predict()** 方法是具有三个字段的命名元组: **predictions** , **label_ids** , 和 **metrics** .这 **metrics** 字段将只包含传递的数据集的loss,以及一些运行时间(预测所需的总时间和平均时间)。如果我们定义了自己的 **compute_metrics()** 函数并将其传递给 **Trainer** ,该字段还将包含**compute_metrics()** 的结果。如你看到的, **predictions** 是一个形状为 408 x 2 的二维数组(408 是我们使用的数据集中元素的数量)。这些是我们传递给**predict()**的数据集的每个元素的结果(logits)(正如你在[之前的章节](/course/chapter2)看到的情况)。要将我们的预测的可以与真正的标签进行比较,我们需要在第二个轴上取最大值的索引: +`predict()` 方法的输出另一个带有三个字段的命名元组: `predictions` `label_ids` 和 `metrics` `metrics` 字段将只包含所传递的数据集的损失,以及一些时间指标(总共花费的时间和平均预测时间)。当我们定义了自己的 `compute_metrics()` 函数并将其传递给 `Trainer` 该字段还将包含 `compute_metrics()` 返回的结果。`predict()` 是一个二维数组,形状为 408 × 2(408 是我们使用的数据集中的元素数量),这是我们传递给 `pprdict()` 的数据集中每个元素的 logits(正如在 [前一章](/course/chapter2) 中看到的,所有 Transformer 模型都返回 logits)。为了将它们转化为可以与我们的标签进行比较的预测值,我们需要找出第二个维度上取值最大的索引: ```py import numpy as np @@ -110,7 +108,7 @@ import numpy as np preds = np.argmax(predictions.predictions, axis=-1) ``` -现在建立我们的 **compute_metric()** 函数来较为直观地评估模型的好坏,我们将使用 🤗 [Evaluate](https://github.com/huggingface/evaluate/) 库中的指标。我们可以像加载数据集一样轻松加载与 MRPC 数据集关联的指标,这次使用 **evaluate.load()** 函数。返回的对象有一个 **compute()**方法我们可以用来进行度量计算的方法: +我们现在可以将这些 `preds` 与标签进行比较。为了构建我们的 `compute_metric()` 函数,我们将使用 🤗 [Evaluate](https://github.com/huggingface/evaluate/) 库中的指标。我们可以像加载数据集一样轻松地加载与 MRPC 数据集关联的指标,这次是使用 `evaluate.load()` 函数。返回的对象有一个 `compute()` 方法,我们可以用它来进行指标的计算: ```py import evaluate @@ -123,9 +121,9 @@ metric.compute(predictions=preds, references=predictions.label_ids) {'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542} ``` -您获得的确切结果可能会有所不同,因为模型头的随机初始化可能会影响最终建立的模型。在这里,我们可以看到我们的模型在验证集上的准确率为 85.78%,F1 分数为 89.97。这是用于评估 GLUE 基准的 MRPC 数据集结果的两个指标。而在[BERT 论文](https://arxiv.org/pdf/1810.04805.pdf)中展示的基础模型的 F1 分数为 88.9。那是 **uncased** 模型,而我们目前正在使用 **cased** 模型,通过改进得到了更好的结果。 +你得到的确切结果可能会有所不同,因为模型头部的随机初始化可能会改变其指标。在这里,我们可以看到我们的模型在验证集上的准确率为 85.78%,F1 分数为 89.97。这是用于评估 MRPC 数据集在 GLUE 基准测试中的结果的两个指标。在 BERT 论文中的表格中,基础模型的 F1 分数为 88.9。那是 uncased 模型,而我们现在正在使用 cased 模型,这解释了为什么我们得到了更好的结果。 -最后将所有东西打包在一起,我们得到了我们的 **compute_metrics()** 函数: +最后把所有东西打包在一起,我们就得到了 `compute_metrics()` 函数: ```py def compute_metrics(eval_preds): @@ -135,7 +133,7 @@ def compute_metrics(eval_preds): return metric.compute(predictions=predictions, references=labels) ``` -为了查看模型在每个训练周期结束的好坏,下面是我们如何使用**compute_metrics()**函数定义一个新的 **Trainer** : +为了查看模型在每个训练周期结束时的好坏,下面是我们如何使用 `compute_metrics()` 函数定义一个新的 `Trainer` ```py training_args = TrainingArguments("test-trainer", evaluation_strategy="epoch") @@ -152,21 +150,19 @@ trainer = Trainer( ) ``` -请注意,我们设置了了一个新的 **TrainingArguments** 它的**evaluation_strategy** 设置为 **epoch** 并创建了一个新模型。如果不创建新的模型就直接训练,就只会继续训练之前我们已经训练过的模型。要启动新的训练运行,我们执行: +请注意,我们设置了一个新的 `TrainingArguments` ,其 `evaluation_strategy` 设置为 `epoch` 并且创建了一个新模型。如果不创建新的模型就直接训练,就只会继续训练我们已经训练过的模型。为了启动新的训练,我们执行: ```py trainer.train() ``` -这一次,它将在训练loss之外,还会输出每个 epoch 结束时的验证loss和指标。同样,由于模型的随机头部初始化,您达到的准确率/F1 分数可能与我们发现的略有不同,但它应该在同一范围内。 - -这 **Trainer** 将在多个 GPU 或 TPU 上开箱即用,并提供许多选项,例如混合精度训练(在训练的参数中使用 **fp16 = True** )。我们将在第 10 章讨论它支持的所有内容。 +这一次,它将在每个 epoch 结束时在训练损失的基础上报告验证损失和指标。同样,由于模型的随机头部初始化,达到的准确率/F1 分数可能与我们发现的略有不同,这是由于模型头部的随机初始化造成的,但应该相差不多。 `Trainer` 可以在多个 GPU 或 TPU 上运行,并提供许多选项,例如混合精度训练(在训练的参数中使用 `fp16 = True` )。我们将在第十章讨论它支持的所有内容。 -使用**Trainer** API微调的介绍到此结束。对最常见的 NLP 任务执行此操作的示例将在[第 7 章](/course/chapter7)中给出,但现在让我们看看如何在纯 PyTorch 中执行相同的操作。 +使用 `Trainer` API 微调的介绍到此结束。在 [第七章](/course/chapter7) 中会给出一个对大多数常见的 NLP 任务进行训练的例子,但现在让我们看看如何在 PyTorch 中做相同的操作。 -✏️ **试试看!** 使用您在第 2 节中进行的数据处理,在 GLUE SST-2 数据集上微调模型。 +✏️ **试试看!** 使用你在第 2 节中学到的数据处理过程,在 GLUE SST-2 数据集上对模型进行微调。 diff --git a/chapters/zh-CN/chapter3/3_tf.mdx b/chapters/zh-CN/chapter3/3_tf.mdx index 936c81e76..773224214 100644 --- a/chapters/zh-CN/chapter3/3_tf.mdx +++ b/chapters/zh-CN/chapter3/3_tf.mdx @@ -9,9 +9,9 @@ {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter3/section3_tf.ipynb"}, ]} /> -完成上一节中的所有数据预处理工作后,您只剩下最后的几个步骤来训练模型。 但是请注意,`model.fit()` 命令在 CPU 上运行会非常缓慢。 如果您没有GPU,则可以在 [Google Colab](https://colab.research.google.com/) 上使用免费的 GPU 或 TPU(需要梯子)。 +一旦完成上一节中的所有数据预处理工作后,你只剩下最后的几个步骤来训练模型。 但是请注意,`model.fit()` 命令在 CPU 上运行会非常缓慢。 如果你没有GPU,你可以在 [Google Colab](https://colab.research.google.com)(国内网络无法使用) 上使用免费的 GPU 或 TPU。 -这一节的代码示例假设您已经执行了上一节中的代码示例。 下面一个简短的摘要,包含了在开始学习这一节之前您需要的执行的代码: +下面的代码示例假设你已经运行了上一节中的代码示例。 下面是在开始学习这一节之前你需要运行的代码: ```py from datasets import load_dataset @@ -48,17 +48,17 @@ tf_validation_dataset = tokenized_datasets["validation"].to_tf_dataset( ) ``` -### 训练模型 [[训练模型]] +## 训练模型 [[训练模型]] 从🤗 Transformers 导入的 TensorFlow 模型已经是 Keras 模型。 下面的视频是对 Keras 的简短介绍。 -这意味着,一旦我们有了数据,就需要很少的工作就可以开始对其进行训练。 +这意味着一旦我们有了数据,只需要很少的工作就可以开始对其进行训练。 -和[第二章](/course/chapter2)使用的方法一样, 我们将使用二分类的 `TFAutoModelForSequenceClassification`类: +和[第二章](/course/chapter2)使用的方法一样, 我们将使用二分类的 `TFAutoModelForSequenceClassification`类,我们将有两个标签: ```py from transformers import TFAutoModelForSequenceClassification @@ -66,13 +66,13 @@ from transformers import TFAutoModelForSequenceClassification model = TFAutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) ``` -您会注意到,与 [第二章](/course/chapter2) 不同的是,您在实例化此预训练模型后会收到警告。 这是因为 BERT 没有对句子对进行分类进行预训练,所以预训练模型的 head 已经被丢弃,而是插入了一个适合序列分类的新 head。 警告表明一些权重没有使用(对应于丢弃的预训练头),而其他一些权重是随机初始化的(新头的权重)。 最后鼓励您训练模型,这正是我们现在要做的。 +你会注意到,与 [第二章](/course/chapter2) 不同的是,在实例化这个预训练的模型后会收到警告。 这是因为 BERT 没有对句子对的分类进行预训练,所以预训练模型的 head 已经被丢弃,并且插入了一个适合序列分类的新 head。 警告表明,这些权重没有使用(对应于丢弃的预训练 head 权重),而其他一些权重是随机初始化的(对应于新 head 的权重)。 最后它建议你训练模型,这正是我们现在要做的。 -要在我们的数据集上微调模型,我们只需要在我们的模型上调用 `compile()` 方法,然后将我们的数据传递给 `fit()` 方法。 这将启动微调过程(在 GPU 上应该需要几分钟)并输出训练loss,以及每个 epoch 结束时的验证loss。 +为了在我们的数据集上微调模型,我们只需要在我们的模型上调用 `compile()` 方法,然后将我们的数据传递给 `fit()` 方法。 这将启动微调过程(在 GPU 上应该需要几分钟)并输出训练损失,以及每个 epoch 结束时的验证损失。 -请注意🤗 Transformers 模型具有大多数 Keras 模型所没有的特殊能力——它们可以自动使用内部计算的loss。 如果您没有在 `compile()` 中设置损失函数,他们将默认使用内部计算的损失。 请注意,要使用内部损失,您需要将标签作为输入的一部分传递,而不是作为单独的标签(这是在 Keras 模型中使用标签的正常方式)。 您将在课程的第 2 部分中看到这方面的示例,其中定义正确的损失函数可能很棘手。 然而,对于序列分类,标准的 Keras 损失函数可以正常工作,所以我们将在这里使用它。 +请注意🤗 Transformers 模型具有大多数 Keras 模型所没有的特殊能力——它们可以自动使用内部计算的损失。 如果你没有在 `compile()` 中设置损失参数,它们可以自动使用适当的损失函数,并在内部计算。 请注意,要使用内部损失,你需要将标签作为输入的一部分传入模型,而不是作为单独的标签(这是在 Keras 模型中使用标签的常规方式)。 你将在课程的第 2 部分中看到这方面的示例,正确定义损失函数可能会有些棘手。 然而对于序列分类来说,标准的 Keras 损失函数效果很好,因此我们将在这里使用它。 @@ -92,27 +92,28 @@ model.fit( -请注意这里有一个非常常见的陷阱——你只是*可以*将损失的名称作为字符串传递给 Keras,但默认情况下,Keras 会假设你已经对输出应用了 softmax。 然而,许多模型在应用 softmax 之前就输出,也称为 *logits*。 我们需要告诉损失函数我们的模型是否经过了softmax,唯一的方法是直接调用它,而不是用字符串的名称。 +请注意这里有一个非常常见的陷阱——你可以把损失的名称作为一个字符串传递给 Keras,但默认情况下,Keras 会假设你已经对输出进行了 softmax。 然而,许多模型在经过 softmax 函数之前输出的是被称为 `logits` 的值。 我们需要告诉损失函数,我们的模型是否已经使用 softmax 函数进行了处理,唯一的方法是传递一个损失函数并且在参数的部分告诉模型,而不是只传递一个字符串。 -### 提升训练的效果 [[提升训练的效果]] +## 改善训练的效果 [[改善训练的效果]] -如果您尝试上面的代码,它肯定会运行,但您会发现loss只是缓慢或零星地下降。 主要原因是*学习率*。 与loss一样,当我们将优化器的名称作为字符串传递给 Keras 时,Keras 会初始化该优化器具有所有参数的默认值,包括学习率。 但是,根据长期经验,我们知道Transformer 模型更适合使用比 Adam 的默认值(1e-3)也写成为 10 的 -3 次方,或 0.001,低得多的学习率。 5e-5 (0.00005) 比默认值大约低 20 倍,是一个更好的起点。 +如果你尝试上述代码,它的确可以运行,但你会发现损失下降得很慢或者不规律。 主要原因是`学习率`。 与损失一样,当我们把优化器的名称作为字符串传递给 Keras 时,Keras 会初始化该优化器具有所有参数的默认值,包括学习率。 不过根据长期经验,我们知道Transformer 模型的通常的最佳学习率比 Adam 的默认值(即1e-3,也写成为 10 的 -3 次方,或 0.001)低得多。 5e-5(0.00005),是一个更好的起始点。 -除了降低学习率,我们还有第二个技巧:我们可以慢慢降低学习率。在训练过程中。 在文献中,您有时会看到这被称为 *decaying* 或 *annealing*学习率。 在 Keras 中,最好的方法是使用 *learning rate scheduler*。 一个好用的是`PolynomialDecay`——尽管有这个名字,但在默认设置下,它只是简单地从初始值线性衰减学习率值在训练过程中的最终值,这正是我们想要的。但是, 为了正确使用调度程序,我们需要告诉它训练的次数。 我们将在下面为其计算“num_train_steps”。 +除了降低学习率之外,我们还有第二个技巧:我们可以在训练过程中慢慢降低学习率。在文献中,你有时会看到这被称为 学习率的`衰减(decaying)` 或 `退火(annealing)`。 在 Keras 中,最好的方法是使用 `学习率调度器(learning rate scheduler)`。 一个好用的调度器是`PolynomialDecay`——尽管它的名字叫`PolynomialDecay(多项式衰减)`,但在默认设置下,它只是简单将学习率从初始值线性衰减到最终值,这正是我们想要的。不过为了正确使用调度程序,我们需要告诉它训练的次数。我们可以通过下面的`num_train_steps`计算得到。 ```py from tensorflow.keras.optimizers.schedules import PolynomialDecay batch_size = 8 num_epochs = 3 -# 训练步数是数据集中的样本数除以batch size再乘以 epoch。 -# 注意这里的tf_train_dataset是一个转化为batch后的 tf.data.Dataset, -# 不是原来的 Hugging Face Dataset,所以它的 len() 已经是 num_samples // batch_size。 + +# 训练步数是数据集中的样本数量,除以 batch 大小,然后乘以总的 epoch 数。 +# 注意这里的 tf_train_dataset 是 batch 形式的 tf.data.Dataset, +# 而不是原始的 Hugging Face Dataset ,所以使用 len() 计算它的长度已经是 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 @@ -124,11 +125,11 @@ opt = Adam(learning_rate=lr_scheduler) -🤗 Transformers 库还有一个 `create_optimizer()` 函数,它将创建一个具有学习率衰减的 `AdamW` 优化器。 这是一个便捷的方式,您将在本课程的后续部分中详细了解。 +🤗 Transformers 库还有一个 `create_optimizer()` 函数,它将创建一个具有学习率衰减的 `AdamW` 优化器。 这是一个快捷的方式,你将在本课程的后续部分中详细了解。 -现在我们有了全新的优化器,我们可以尝试使用它进行训练。 首先,让我们重新加载模型,以重置我们刚刚进行的训练运行对权重的更改,然后我们可以使用新的优化器对其进行编译: +现在我们有了全新的优化器,我们可以尝试使用它进行训练。 首先,让我们重新加载模型,重新设置刚刚训练时的权重,然后我们可以使用新的优化器对其进行编译: ```py import tensorflow as tf @@ -146,22 +147,22 @@ model.fit(tf_train_dataset, validation_data=tf_validation_dataset, epochs=3) -💡 如果您想在训练期间自动将模型上传到 Hub,您可以在 `model.fit()` 方法中传递 `PushToHubCallback`。 我们将在 [第四章](/course/chapter4/3) 中进行介绍 +💡 如果你想在训练期间自动将模型上传到 Hub,你可以在 `model.fit()` 方法中传递一个 `PushToHubCallback`。 我们将在 [第四章](/course/chapter4/3) 中进一步了解这个问题。 -### 模型预测 [[模型预测]] +## 模型预测 [[模型预测]] -训练和观察的loss下降都非常好,但是如果我们想从训练后的模型中获得输出,或者计算一些指标,或者在生产中使用模型呢? 为此,我们可以使用`predict()` 方法。 这将返回模型的输出头的*logits*数值,每个类一个。 +训练和观察损失的下降都是非常好,但如果我们想从训练后的模型中得到输出,或者计算一些指标,或者在生产中使用模型,该怎么办呢?为此,我们可以使用`predict()` 方法。 这将返回模型的输出头的`logits`数值,每个类一个。 ```py preds = model.predict(tf_validation_dataset)["logits"] ``` -我们可以将这些 logit 转换为模型的类别预测,方法是使用 argmax 找到最高的 logit,它对应于最有可能的类别: +我们可以通过使用 argmax 将这些 logits 转换为模型的类别预测,最高的 logits,对应于最有可能的类别 ```py class_preds = np.argmax(preds, axis=1) @@ -172,7 +173,7 @@ print(preds.shape, class_preds.shape) (408, 2) (408,) ``` -现在,让我们使用这些 `preds` 来计算一些指标! 我们可以像加载数据集一样轻松地加载与 MRPC 数据集相关的指标,这次使用的是 `evaluate.load()` 函数。 返回的对象有一个 `compute()` 方法,我们可以使用它来进行度量计算: +现在,让我们使用这些 `preds` 来计算一些指标! 我们可以像加载数据集一样轻松地加载与 MRPC 数据集相关的指标,这次使用的是 `evaluate.load()` 函数。 返回的对象有一个 `compute()` 方法,我们可以使用它来进行指标的计算: ```py import evaluate @@ -185,6 +186,6 @@ metric.compute(predictions=class_preds, references=raw_datasets["validation"]["l {'accuracy': 0.8578431372549019, 'f1': 0.8996539792387542} ``` -您获得的确切结果可能会有所不同,因为模型头的随机初始化可能会改变它获得的指标。 在这里,我们可以看到我们的模型在验证集上的准确率为 85.78%,F1 得分为 89.97。 这些是用于评估 GLUE 基准的 MRPC 数据集结果的两个指标。 [BERT 论文](https://arxiv.org/pdf/1810.04805.pdf) 中的表格报告了基本模型的 F1 分数为 88.9。 那是 `uncased` 模型,而我们目前使用的是 `cased` 模型,这解释了为什么我们会获得更好的结果。 +由于模型头的随机初始化可能会改变模型达到的指标,因此你得到的最终结果可能会有所不同。在这里,我们可以看到我们的模型在验证集上的准确率为85.78%,F1分数为89.97。 这就是用于评估 GLUE 基准的 MRPC 数据集上的结果两个指标。 [BERT 论文](https://arxiv.org/pdf/1810.04805.pdf) 中的表格报告了基本模型的 F1 分数为 88.9。 那是 `uncased` 模型,而我们目前使用的是 `cased` 模型,这也可以解释为什么我们会得到了更好的结果。 -使用 Keras API 进行微调的介绍到此结束。 [第 7 章](/course/chapter7)将给出对大多数常见 NLP 任务执行此操作的示例。如果您想在 Keras API 上磨练自己的技能,请尝试使第二节所使用的的数据处理在 GLUE SST-2 数据集上微调模型。 \ No newline at end of file +关于使用 Keras API 进行微调的介绍到此结束。 在[第七章](/course/chapter7)将给出对大多数常见 NLP 任务微调或训练的示例。如果你想在 Keras API 上磨练自己的技能,请尝试使第2节所学的的数据处理技巧在 GLUE SST-2 数据集上微调模型。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter3/4.mdx b/chapters/zh-CN/chapter3/4.mdx index bc8921f13..743f763f1 100644 --- a/chapters/zh-CN/chapter3/4.mdx +++ b/chapters/zh-CN/chapter3/4.mdx @@ -9,7 +9,7 @@ -现在,我们将了解如何在不使用`Trainer`类的情况下获得与上一节相同的结果。同样,我们假设您已经学习了第 2 节中的数据处理。下面是一个简短的总结,涵盖了您需要的所有内容: +现在,我们将了解如何在不使用 `Trainer` 类的情况下实现与上一节相同的结果。同样,我们假设你已经完成了第 2 节中的数据处理。下面对第 2 节内容的一个简短总结,涵盖了你需要在本节之前运行的所有内容: ```py from datasets import load_dataset @@ -28,15 +28,15 @@ tokenized_datasets = raw_datasets.map(tokenize_function, batched=True) data_collator = DataCollatorWithPadding(tokenizer=tokenizer) ``` -### 训练前的准备 [[训练前的准备]] +## 训练前的准备 [[训练前的准备]] -在实际编写我们的训练循环之前,我们需要定义一些对象。第一个是我们将用于迭代批次的数据加载器。我们需要对我们的`tokenized_datasets`做一些处理,来处理`Trainer`自动为我们做的一些事情。具体来说,我们需要: +在正式开始编写我们的训练循环之前,我们需要定义一些对象。首先是我们将用于迭代 batch 的数据加载器。但在定义这些数据加载器之前,我们需要对我们的 `tokenized_datasets` 进行一些后处理,以自己实现一些 Trainer 自动为我们处理的内容。具体来说,我们需要: -- 删除与模型不期望的值相对应的列(如`sentence1`和`sentence2`列)。 -- 将列名`label`重命名为`labels`(因为模型期望参数是`labels`)。 +- 删除与模型不需要的列(如 `sentence1` 和 `sentence2` 列)。 +- 将列名 `label` 重命名为 `labels` (因为模型默认的输入是 `labels` )。 - 设置数据集的格式,使其返回 PyTorch 张量而不是列表。 -针对上面的每个步骤,我们的 `tokenized_datasets` 都有一个方法: +针对上面的每个步骤,我们的 `tokenized_datasets` 都有一个方法: ```py tokenized_datasets = tokenized_datasets.remove_columns(["sentence1", "sentence2", "idx"]) @@ -45,13 +45,13 @@ tokenized_datasets.set_format("torch") tokenized_datasets["train"].column_names ``` -然后,我们可以检查结果中是否只有模型能够接受的列: +然后,我们可以检查结果中是否只有模型能够接受的列: ```python ["attention_mask", "input_ids", "labels", "token_type_ids"] ``` -至此,我们可以轻松定义数据加载器: +至此,我们可以轻松定义数据加载器: ```py from torch.utils.data import DataLoader @@ -64,7 +64,7 @@ eval_dataloader = DataLoader( ) ``` -为了快速检验数据处理中没有错误,我们可以这样检验其中的一个批次: +为了快速检验数据处理中没有错误,我们可以这样检验其中的一个 batch: ```py for batch in train_dataloader: @@ -79,16 +79,17 @@ for batch in train_dataloader: 'token_type_ids': torch.Size([8, 65])} ``` -请注意,实际的形状可能与您略有不同,因为我们为训练数据加载器设置了`shuffle=True`,并且模型会将句子填充到`batch`中的最大长度。 +请注意,这里的形状可能与你略有不同,因为我们为训练数据加载器设置了 `shuffle=True` ,并且模型会将句子填充到 `batch` 中的最大长度。 -现在我们已经完全完成了数据预处理(对于任何 ML 从业者来说都是一个令人满意但难以实现的目标),让我们将注意力转向模型。我们完全像在上一节中所做的那样实例化它: +现在我们已经完全完成了数据预处理(对于任何 ML 从业者来说都是一个令人满意但难以实现的目标),让我们将注意力转向模型。我们会像在上一节中所做的那样实例化它: ```py from transformers import AutoModelForSequenceClassification model = AutoModelForSequenceClassification.from_pretrained(checkpoint, num_labels=2) ``` -为了确保训练过程中一切顺利,我们将`batch`传递给这个模型: + +为了确保训练过程中一切顺利,我们将 `batch` 传递给这个模型: ```py outputs = model(**batch) @@ -99,9 +100,9 @@ print(outputs.loss, outputs.logits.shape) tensor(0.5441, grad_fn=) torch.Size([8, 2]) ``` -当我们提供 `labels` 时, 🤗 Transformers 模型都将返回这个`batch`的`loss`,我们还得到了 `logits`(`batch`中的每个输入有两个,所以张量大小为 8 x 2)。 +当我们输入 `labels` 时,🤗 Transformers 模型都将返回这个 `batch` 的 `loss` ,我们还得到了 `logits` ( `batch` 中的每个输入有两个输出,所以张量大小为 8 x 2)。 -我们几乎准备好编写我们的训练循环了!我们只是缺少两件事:优化器和学习率调度器。由于我们试图自行实现 `Trainer`的功能,我们将使用相同的优化器和学习率调度器。`Trainer` 使用的优化器是 `AdamW` , 与 `Adam` 相同,但在权重衰减正则化方面有所不同(参见[“Decoupled Weight Decay Regularization”](https://arxiv.org/abs/1711.05101)作者:Ilya Loshchilov 和 Frank Hutter): +我们几乎准备好编写我们的训练循环了!我们只是缺少两件事:优化器和学习率调度器。由于我们试图手动实现 `Trainer` 的功能,我们将使用相同的优化器和学习率调度器。 `Trainer` 使用的优化器是 `AdamW` ,它与 `Adam` 相同,但加入了权重衰减正则化的一点变化(参见 Ilya Loshchilov 和 Frank Hutter 的 [“Decoupled Weight Decay Regularization”](https://arxiv.org/abs/1711.05101) ): ```py from transformers import AdamW @@ -109,7 +110,7 @@ from transformers import AdamW optimizer = AdamW(model.parameters(), lr=5e-5) ``` -最后,默认使用的学习率调度器只是从最大值 (5e-5) 到 0 的线性衰减。 为了定义它,我们需要知道我们训练的次数,即所有数据训练的次数(epochs)乘以的数据量(这是我们所有训练数据的数量)。`Trainer`默认情况下使用三个`epochs`,因此我们定义训练过程如下: +最后,默认使用的学习率调度器只是从最大值 (5e-5) 到 0 的线性衰减。为了定义它,我们需要知道我们训练的次数,即所有数据训练的次数(epochs)乘以的 batch 的数量(即我们训练数据加载器的长度)。 `Trainer` 默认情况下使用三个 `epochs` ,因此我们定义训练过程如下: ```py from transformers import get_scheduler @@ -129,9 +130,9 @@ print(num_training_steps) 1377 ``` -### 训练循环 [[训练循环]] +## 训练循环 [[训练循环]] -最后一件事:如果我们可以访问 GPU,我们将希望使用 GPU(在 CPU 上,训练可能需要几个小时而不是几分钟)。为此,我们定义了一个 `device`,它在GPU可用的情况下指向GPU 我们将把我们的模型和`batche`放在`device`上: +最后一件事:如果我们可以访问 GPU,我们将希望使用 GPU(在 CPU 上,训练可能需要几个小时而不是几分钟)。为此,我们定义了一个 `device` ,它在 GPU 可用的情况下指向 GPU,最后我们将把我们的模型和 `batch` 放在 `device` 上: ```py import torch @@ -145,7 +146,7 @@ device device(type='cuda') ``` -我们现在准备好训练了!为了了解训练何时结束,我们使用 `tqdm` 库,在训练步骤数上添加了一个进度条: +我们现在准备好训练了!为了知道训练何时结束,我们使用 `tqdm` 库,在训练步骤数上添加了一个进度条: ```py from tqdm.auto import tqdm @@ -166,12 +167,12 @@ for epoch in range(num_epochs): progress_bar.update(1) ``` -您可以看到训练循环的核心与介绍中的非常相似。我们没有要求任何检验,所以这个训练循环不会告诉我们任何关于模型目前的状态。我们需要为此添加一个评估循环。 +你可以看到训练循环的核心与介绍中的非常相似。我们没有要求在训练的过程中进行检验,所以这个训练循环不会告诉我们任何关于模型目前的状态。我们需要为此添加一个评估循环。 -### 评估循环 [[评估循环]] +## 评估循环 [[评估循环]] -正如我们之前所做的那样,我们将使用 🤗 Evaluate 库提供的指标。我们已经了解了 `metric.compute()` 方法,当我们使用 `add_batch()`方法进行预测循环时,实际上该指标可以为我们累积所有 `batch` 的结果。一旦我们累积了所有 `batch` ,我们就可以使用 `metric.compute()` 得到最终结果 .以下是在评估循环中实现所有这些的方法: +正如我们之前所做的那样,我们将使用 🤗 Evaluate 库提供的指标。我们已经了解了 `metric.compute()` 方法,当我们使用 `add_batch()` 方法进行预测循环时,实际上该指标可以为我们累积所有 `batch` 的结果。一旦我们累积了所有 `batch` ,我们就可以使用 `metric.compute()` 评估得到的结果。以下是如何在评估循环中实现所有这些的方法: ```py import evaluate @@ -194,19 +195,19 @@ metric.compute() {'accuracy': 0.8431372549019608, 'f1': 0.8907849829351535} ``` -同样,由于模型头部初始化和数据改组的随机性,您的结果会略有不同,但它们应该在同一个范围内。 +同样,由于模型头部初始化和数据打乱的随机性,你的结果会略有不同,但应该相差不多。 -✏️ **试试看!** 修改之前的训练循环以在 SST-2 数据集上微调您的模型。 +✏️ **试试看!** 修改之前的训练循环以在 SST-2 数据集上微调你的模型。 -### S使用🤗 Accelerate加速您的训练循环 [[S使用🤗 Accelerate加速您的训练循环]] +## 使用🤗 Accelerate 加速你的训练循环 [[使用🤗 Accelerate加速你的训练循环]] -我们之前定义的训练循环在单个 CPU 或 GPU 上运行良好。但是使用[🤗 Accelerate](https://github.com/huggingface/accelerate)库,只需进行一些调整,我们就可以在多个 GPU 或 TPU 上启用分布式训练。从创建训练和验证数据加载器开始,我们的手动训练循环如下所示: +我们之前定义的训练循环在单个 CPU 或 GPU 上运行良好。通过使用 [🤗 Accelerate](https://github.com/huggingface/accelerate) 库,只需进行一些调整,我们就可以在多个 GPU 或 TPU 上启用分布式训练。从创建训练和验证数据加载器开始,我们的手动训练循环如下所示: ```py from transformers import AdamW, AutoModelForSequenceClassification, get_scheduler @@ -242,7 +243,7 @@ for epoch in range(num_epochs): progress_bar.update(1) ``` -以下是变化: +以下是更改的部分: ```diff + from accelerate import Accelerator @@ -286,15 +287,17 @@ for epoch in range(num_epochs): progress_bar.update(1) ``` -要添加的第一行是导入`Accelerator`。第二行实例化一个 `Accelerator`对象 ,它将查看环境并初始化适当的分布式设置。 🤗 Accelerate 为您处理数据在设备间的传递,因此您可以删除将模型放在设备上的那行代码(或者,如果您愿意,可使用 `accelerator.device` 代替 `device` )。 +要添加的第一行是导入 `Accelerator` 。第二行实例化一个 `Accelerator` 对象 它将查看环境并初始化适当的分布式设置。🤗 Accelerate 为你处理数据在设备间的数据传递,因此你可以删除将模型放在设备上的那行代码(或者,如果你愿意,可使用 `accelerator.device` 代替 `device` )。 -然后大部分工作会在将数据加载器、模型和优化器发送到的`accelerator.prepare()`中完成。这将会把这些对象包装在适当的容器中,以确保您的分布式训练按预期工作。要进行的其余更改是删除将`batch`放在 `device` 的那行代码(同样,如果您想保留它,您可以将其更改为使用 `accelerator.device` ) 并将 `loss.backward()` 替换为`accelerator.backward(loss)`。 +然后大部分工作会在将数据加载器、模型和优化器发送到的 `accelerator.prepare()` 中完成。这将会把这些对象包装在适当的容器中,以确保你的分布式训练按预期工作。要进行的其余更改是删除将 `batch` 放在 `device` 的那行代码(同样,如果你想保留它,你可以将其更改为使用 `accelerator.device` ) 并将 `loss.backward()` 替换为 `accelerator.backward(loss)` 。 -⚠️ 为了使云端 TPU 提供的加速发挥最大的效益,我们建议使用标记器(tokenizer)的 `padding=max_length` 和 `max_length` 参数将您的样本填充到固定长度。 + +⚠️ 为了使云端 TPU 提供的加速中发挥最大的效益,我们建议使用 tokenizer 的 `padding=max_length` 和 `max_length` 参数将你的样本填充到固定长度。 + -如果您想复制并粘贴来直接运行,以下是 🤗 Accelerate 的完整训练循环: +如果你想复制并粘贴来直接运行,以下是 🤗 Accelerate 的完整训练循环: ```py from accelerate import Accelerator @@ -333,13 +336,13 @@ for epoch in range(num_epochs): progress_bar.update(1) ``` -把这个放在 `train.py` 文件中,可以让它在任何类型的分布式设置上运行。要在分布式设置中试用它,请运行以下命令: +把这个放在 `train.py` 文件中,可以让它在任何类型的分布式设置上运行。要在分布式设置中试用它,请运行以下命令: ```bash accelerate config ``` -这将询问您几个配置的问题并将您的回答转储到此命令使用的配置文件中: +这将询问你几个配置的问题并将你的回答保存到此命令使用的配置文件中: ``` accelerate launch train.py @@ -347,7 +350,7 @@ accelerate launch train.py 这将启动分布式训练 -这将启动分布式训练。如果您想在 Notebook 中尝试此操作(例如,在 Colab 上使用 TPU 进行测试),只需将代码粘贴到 `training_function()` 并使用以下命令运行最后一个单元格: +这将启动分布式训练。如果你想在 Notebook 中尝试此操作(例如,在 Colab 上使用 TPU 进行测试),只需将代码粘贴到一个 `training_function()` 函数中,并在最后一个单元格中运行: ```python from accelerate import notebook_launcher @@ -355,4 +358,4 @@ from accelerate import notebook_launcher notebook_launcher(training_function) ``` -您可以在[🤗 Accelerate repo](https://github.com/huggingface/accelerate/tree/main/examples)找到更多的示例。 +你可以在 [🤗 Accelerate repo](https://github.com/huggingface/accelerate/tree/main/examples) 找到更多的示例。 diff --git a/chapters/zh-CN/chapter3/5.mdx b/chapters/zh-CN/chapter3/5.mdx index ee69b3a3a..dce6ede80 100644 --- a/chapters/zh-CN/chapter3/5.mdx +++ b/chapters/zh-CN/chapter3/5.mdx @@ -1,23 +1,25 @@ -# 微调,检查! [[微调,检查!]] +# 微调,检查![[微调,检查!]] -这是非常令人高兴的! 在前两章中,您了解了模型和标记器(tokenizer),现在您知道如何针对您自己的数据对它们进行微调。回顾一下,在本章中,您: +这是非常令人高兴的!在前两章中,你了解了模型和 Tokenizer,现在你知道如何针对你自己的数据对它们进行微调。回顾一下,在本章中,你: {#if fw === 'pt'} -* 了解了[Hub](https://huggingface.co/datasets)中的数据集 -* 学习了如何加载和预处理数据集,包括使用动态填充和整理器 -* 实现您自己的模型微调和评估 -* 实施了一个较为底层的训练循环 -* 使用 🤗 Accelerate 轻松调整您的训练循环,使其适用于多个 GPU 或 TPU + +* 了解了 [Hub](https://huggingface.co/datasets) 中的数据集 +* 学习了如何加载和预处理数据集,包括使用动态填充和数据整理器 +* 实现你自己的模型微调和评估 +* 实现了一个较为底层的训练循环 +* 使用 🤗 Accelerate 轻松调整你的训练循环,使其适用于多个 GPU 或 TPU {:else} -* 了解了[Hub](https://huggingface.co/datasets)中的数据集 + +* 了解了 [Hub](https://huggingface.co/datasets) 中的数据集 * 学习了如何加载和预处理数据集 * 学习了如何使用 Keras 微调和评估模型 * 实现了自定义指标 diff --git a/chapters/zh-CN/chapter3/6.mdx b/chapters/zh-CN/chapter3/6.mdx index 06f6dbccd..66ec54b1c 100644 --- a/chapters/zh-CN/chapter3/6.mdx +++ b/chapters/zh-CN/chapter3/6.mdx @@ -9,165 +9,165 @@ classNames="absolute z-10 right-0 top-0" /> -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: "不对,请再看看[数据卡片](https://huggingface.co/datasets/ar_sarcasm)!" }, { text: "命名实体识别", - explain: "不是这样的ーー再看看 < a href =’https://huggingface.co/datasets/ar _ sarcasm’> dataset card !" + explain: "不对,请再看看[数据卡片](https://huggingface.co/datasets/ar_sarcasm) !" }, { text: "回答问题", - explain: "Alas, this question was not answered correctly. 再试一次!" + explain: "哎呀, 问题回答不正确. 再试一次!" } ]} /> -### 3.BERT 模型期望如何处理一对句子? +### 3. 当输入一对句子时 BERT 模型会需要进行怎么样的预处理? [ CLS ] 特殊令牌在开始时是必需的,但是这不是唯一的事情!" + text: "[CLS] 句子1的token序列 句子2的token序列", + explain: "需要一个特殊的 `[CLS]` token 来指示句子的开头,但是只有这一个还不够。" }, { - text: "表示句子1[ SEP ]的符号表示句子2[ SEP ]", - explain: "没错!", + text: "[CLS] 句子1的token序列 [SEP] 句子2的token序列 [SEP]", + explain: "正确!", correct: true }, { - text: "表示句子1[ SEP ]的符号表示句子2", - explain: "开头需要一个 < code > [ CLS ] 特殊标记,还需要一个 < code > [ SEP ] 特殊标记来分隔两个句子,但这还不是全部!" + text: "[CLS] 句子1的token序列 [SEP] 句子2的token序列", + explain: "需要一个特殊的 `[CLS]` token 来指示句子的开头,还需要一个特殊的 `[SEP]` token 来分隔两个句子,但这还不是需要全部的预处理。" } ]} /> {#if fw === 'pt'} -### 4.‘ Dataset.map ()’方法的好处是什么? +### 4. `Dataset.map ()`方法的好处是什么? -### 5.什么是动态填充? +### 5. 什么是动态填充? -### 6.校对函数的用途是什么? +### 6. collate 函数的用途是什么? > DataCollatorWithPadding 。" + explain: "collate 函数用于处理单个 batch 处理,而不是整个数据集。此外,我们讨论的是所有 collate 函数通常的用途,而不是特定的 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: "它把所有的样本地放在一个 batch 里。", + explain: "正确!你可将 collate 函数作为 DataLoader函数的一个参数。 我们使用了 DataCollatorWithPadding 函数, 该函数对批次中的所有项目进行填充,使它们具有相同的长度。", correct: true }, { text: "它预处理整个数据集。", - explain: "这将是一个预处理函数,而不是校对函数。" + explain: "预处理函数(preprocessing)用于预处理整个数据集,而不是 collate 函数。" }, { text: "它截断数据集中的序列。", - explain: "校对函数用于处理单个批处理,而不是整个数据集。如果您对截断感兴趣,可以使用 < code > tokenizer 的 < truncate 参数。" + explain: "collate 函数用于处理单个 batch 的处理,而不是整个数据集。如果你对截断感兴趣,可以使用 tokenizer truncate 参数。" } ]} /> -### 7.当你用一个预先训练过的语言模型(例如‘ bert-base-uncased’)实例化一个‘ AutoModelForXxx’类,这个类对应于一个不同于它所被训练的任务时会发生什么? +### 7. 当你用一个预先训练过的语言模型(例如 `bert-base-uncased`)实例化一个`AutoModelForXxx`类,这个类与它所被训练的任务不匹配时会发生什么? Trainer 所做的,而不是 Accelerate 库。再试一次!", + text: "丢弃预训练模型的头部,取而代之的是一个适合该任务的新头部。", + explain: "正确的。 例如,当我们将 AutoModelForSequenceClassification 与 bert-base-uncased 结合使用时,我们在实例化模型时将收到警告。 预训练的头不用于序列分类任务,因此它被丢弃,并使用随机初始化权重的用于序列分类任务的头。", correct: true }, { text: "丢弃预先训练好的模型头部。", - explain: "Something else needs to happen. 再试一次!" + explain: "还需要做其他事情,再试一次!" }, { text: "没有,因为模型仍然可以针对不同的任务进行微调。", - explain: "这个经过训练的模特的头没有经过训练来解决这个问题,所以我们应该丢掉这个头!" + explain: "这个经过训练的模特的头没有经过训练来解决这个问题,所以我们应该丢弃该头部!" } ]} /> -### 8.训练争论的目的是什么? +### 8.`TrainingArguments`的用途是什么? TrainingArguments 。" + explain: "模型大小是由模型配置定义的,而不是由 `TrainingArguments` 类 。" }, { text: "它只包含用于评估的超参数。", - explain: "In the example, we specified where the model and its checkpoints will be saved. 再试一次!" - }, - { - text: "您可以轻松地计算与数据集相关的指标。", - explain: "In the example, we used an evaluation_strategy as well, so this impacts evaluation. 再试一次!" + explain: "在示例中,我们还指定了模型的超参数及其检查点的保存位置。 再试一次!" } ]} /> -### 9.为什么要使用 Accelerate 库? +### 9.为什么要使用🤗 Accelerate 库? Trainer, not the 🤗 Accelerate library. 再试一次!" + text: "它提供了一个高级 API,因此我不必实现自己的训练循环。", + explain: "这是我们使用 Trainer 所做的事情,而不是 🤗 Accelerate 库。 再试一次" }, { - text: "它使我们的训练循环工作在分布式策略上", - explain: "正确! 随着加速,你的训练循环将为多个 gpu 和 TPUs 工作。", + text: "它使我们的训练循环运行在分布式架构上", + explain: "正确! 使用🤗 Accelerate 库,你的训练循环可以在多个 GPU 和 TPUs 上运行。", correct: true }, { text: "它提供了更多的优化功能。", - explain: "不,Accelerate 库不提供任何优化功能。" + explain: "不,🤗 Accelerate 库不提供任何优化功能。" } ]} /> {:else} -### 4.当你用一个预先训练过的语言模型(例如‘ bert-base-uncased’)实例化一个‘ tfautoodelforxxx’类时,会发生什么? +### 4.当模型与预训练的任务不匹配时,例如使用预训练的语言模型(例如“`bert-base-uncased`”)实例化“`TFAutoModelForXxx`”类时会发生什么? -### 5.来自“变压器”的 TensorFlow 模型已经是 Keras 模型,这有什么好处? +### 5.来自 `transformers` 的 TensorFlow 模型已经是 Keras 模型,这有什么好处? TPUStrategy scope 中的所有内容,包括模型的初始化。" + text: "这些模型可在开箱即用的 TPU 上运行。", + explain: "差不多!但是还需要进行一些小的额外修改。例如,你需要在 TPUStrategy 范围内运行所有内容,包括模型的初始化。" }, { - text: "您可以利用现有的方法,如 < code > compile () 、 < code > fit () 和 < code > predict () 。", - explain: "正确! 一旦你有了这些数据,在这些数据上进行培训只需要很少的工作。", + text: "你可以利用现有方法,例如 compile()fit()predict()。", + explain: "正确! 一旦你有了数据,只需要很少的工作就可以在这些数据上进行训练。", correct: true }, { - text: "你可以学习 Keras 和变形金刚。", + text: "你可以学习 Keras 以及 Transformer。", explain: "没错,但我们要找的是别的东西:)", correct: true }, { - text: "困惑", - explain: "Keras 帮助我们训练和评估模型,而不是计算与数据集相关的度量。" + text: "你可以轻松计算与数据集相关的指标。", + explain: "Keras 帮助我们训练和评估模型,而不是计算与数据集相关的指标。" } ]} /> -### 6.如何定义自己的定制度量? +### 6.如何定义自己的自定义指标? tfkeras.metrics. Metric 。", + text: "使用子类化 tf.keras.metrics.Metric。", explain: "太好了!", correct: true }, @@ -274,13 +270,13 @@ 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 }, { - text: "通过谷歌搜索。", - explain: "这不是我们要找的答案,但它应该能帮助你找到答案。", + text: "使用谷歌搜索。", + explain: "这不是我们要找的答案,不过它应该能帮助你找到答案。", correct: true } ]} diff --git a/chapters/zh-CN/chapter4/1.mdx b/chapters/zh-CN/chapter4/1.mdx index 32c8ef87e..2bf33daab 100644 --- a/chapters/zh-CN/chapter4/1.mdx +++ b/chapters/zh-CN/chapter4/1.mdx @@ -5,16 +5,18 @@ classNames="absolute z-10 right-0 top-0" /> -[Hugging Face Hub](https://huggingface.co/)---我们的主网站,是一个中央平台,在这个网站上任何人都可以查找、使用和贡献新的最先进的模型和数据集。它拥有各种各样的模型,公开可用的模型超过 10,000个。我们在本章去探索Hub中的模型,并在第 5 章中探索Hub中的数据集。 +我们的主网站——— [Hugging Face中心](https://huggingface.co/) 是一个集发现、使用及贡献最新先进模型与数据集为一体的中心平台。这里汇聚了超过 10,000 个公开可用的各种领域的模型。我们将在本章节专注探讨这些模型,并在第五章节深入讨论数据集。 -Hub 中的模型不仅限于🤗 Transformers 甚至 NLP。有用于自然语言处理的[Flair](https://github.com/flairNLP/flair),[AllenNLP](https://github.com/allenai/allennlp),[Asteroid](https://github.com/asteroid-team/asteroid)和用于音频检测的[pyannote](https://github.com/pyannote/pyannote-audio),以及对于视觉的[timm](https://github.com/rwightman/pytorch-image-models),这些例子只是Hub中冰山一角,更多的模型。可以由你去探索。 +Hub 中的模型不仅限于🤗 Transformers 或者是自然语言处理。这里有许多模型,比如用于自然语言处理的 [Flair](https://github.com/flairNLP/flair) 和 [AllenNLP](https://github.com/allenai/allennlp) 模型,用于音频检测的 [Asteroid](https://github.com/asteroid-team/asteroid) 和 [pyannote](https://github.com/pyannote/pyannote-audio) 模型,以及用于视觉的 [timm](https://github.com/rwightman/pytorch-image-models) 模型等,这些例子只是 Hub 中模型的冰山一角,更多的模型可以由你去探索。 -这些模型中的每一个都作为 Git 存储库托管,这允许进行版本控制和重现。在 Hub 上共享模型意味着将其向社区开放,让任何希望使用它的人都可以轻松访问它,从而使其他人不用为训练模型而苦恼就可以直接使用模型。 +这些模型中的每一个都以 Git 仓库的形式托管,方便进行版本控制和复现。在 Hub 上共享模型意味着向社区开放,让任何希望使用模型的人都可以轻松访问,从而使他们免于训练模型之苦,并且简化了模型分享和使用。 -此外,在 Hub 上共享模型会自动为该模型部署托管的推理 API。社区中的任何人都可以直接在模型页面上自由地测试它,使用自定义输入和适当的小部件。 +此外,在 Hub 上分享一个模型会自动为该模型部署一个推理 API。社区中的任何人都可以直接在模型页面上使用各自的输入和相应的小工具来进行测试。 -最棒的是是在 Hub 上共享和使用任何公共模型是完全免费的!如果您不想公开模型,也存在[付费计划](https://huggingface.co/pricing)。下面的视频显示了如何使用 Hub。 +最妙的是,在 Hub 上分享和使用任何公共模型都是完全免费的!如果你希望只把模型分享给特定的人,我们也有 [付费计划](https://huggingface.co/pricing) 。 + +下面的视频展示了如何使用 Hub。 -这部分需要有一个 Huggingface.co 帐户,因为我们将在 Hugging Face Hub 上创建和管理存储库:[创建一个账户](https://huggingface.co/join) \ No newline at end of file +如果你想跟进实现这一部分内容,你需要有一个 Huggingface.co 帐户,因为这一节我们将在 Hugging Face Hub 上创建和管理存储库: [创建一个账户](https://huggingface.co/join) \ No newline at end of file diff --git a/chapters/zh-CN/chapter4/2.mdx b/chapters/zh-CN/chapter4/2.mdx index e40ba9d79..956ecd244 100644 --- a/chapters/zh-CN/chapter4/2.mdx +++ b/chapters/zh-CN/chapter4/2.mdx @@ -22,15 +22,15 @@ {/if} -模型中心使选择合适的模型变得简单,因此只需几行代码即可在任何下游库中使用它。让我们来看看如何使用这些模型,以及如何将模型贡献到社区。 +模型中心使选择合适的模型变得简单,只需几行代码即可在任何下游库中使用它。让我们来看看如何使用这些模型,以及如何将模型贡献到社区。 -假设我们正在寻找一种可以执行 mask 填充的 French-based 模型。 +假设我们正在寻找一种可以执行掩码填充(mask filling 又称完形填空)的 French-based(法语)模型。
Selecting the Camembert model.
-我们选择 `camembert-base` 检查点来尝试一下。我们需要做的仅仅是输入 `camembert-base` 标识符!正如你在前几章中看到的,我们可以使用 `pipeline()` 功能: +我们选择 `camembert-base` checkpoint 来尝试一下。我们需要做的仅仅是输入 `camembert-base` 标签!正如你在前几章中学习的,我们可以使用 `pipeline()` 功能: ```py from transformers import pipeline @@ -49,13 +49,13 @@ results = camembert_fill_mask("Le camembert est :)") ] ``` -如你所见,在管道中加载模型非常简单。你唯一需要注意的是所选检查点是否适合它将用于的任务。例如,这里我们正在将 `camembert-base` 检查点加载在 `fill-mask` 管道,这完全没问题。但是如果我们在 `text-classification` 管道加载检查点,结果没有任何意义,因为 `camembert-base` 不适合这个任务!我们建议使用 Hugging Face Hub 界面中的任务选择器来选择合适的检查点: +如你所见,在管道中加载模型非常简单。你唯一需要注意的是所选 checkpoint 是否适合它将用于的任务。例如,这里我们正在将 `camembert-base` checkpoint 加载在 `fill-mask` 管道,这完全没问题。但是如果我们在 `text-classification` 管道中加载该 checkpoint 结果没有任何意义,因为 `camembert-base` 不适合这个任务!我们建议使用 Hugging Face Hub 中的任务选择器来选择合适的 checkpoint 。
The task selector on the web interface.
-你还可以直接使用模型架构实例化检查点: +你还可以直接使用模型架构实例化 checkpoint: {#if fw === 'pt'} ```py @@ -65,7 +65,7 @@ tokenizer = CamembertTokenizer.from_pretrained("camembert-base") model = CamembertForMaskedLM.from_pretrained("camembert-base") ``` -然而,我们建议使用 [`Auto*` 类](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes),因为 `Auto*` 类设计与架构无关。前面的代码示例将只能在 CamemBERT 架构中加载可用的检查点,但使用 `Auto*` 类使切换不同的检查点变得简单: +然而,我们建议使用 [`Auto*` 类](https://huggingface.co/transformers/model_doc/auto?highlight=auto#auto-classes) ,因为 `Auto*` 类在设计时不依赖模型架构。前面的代码示例将只能在 CamemBERT 架构中加载可用的 checkpoint,但使用 `Auto*` 类使切换不同的 checkpoint 变得简单: ```py from transformers import AutoTokenizer, AutoModelForMaskedLM @@ -81,7 +81,7 @@ tokenizer = CamembertTokenizer.from_pretrained("camembert-base") model = TFCamembertForMaskedLM.from_pretrained("camembert-base") ``` -然而,我们建议使用 [`TFAuto*` 类](https://huggingface.co/transformers/model_doc/auto.html?highlight=auto#auto-classes),因为 `TFAuto*` 类设计与架构无关。前面的代码示例将只能在 CamemBERT 架构中加载可用的检查点,但使用 `TFAuto*` 类使切换不同的检查点变得简单: +然而,我们建议使用 [`TFAuto*` 类](https://huggingface.co/transformers/model_doc/auto?highlight=auto#auto-classes) ,因为 `TFAuto*` 类在设计时不依赖模型架构。前面的代码示例将只能在 CamemBERT 架构中加载可用的 checkpoint,但使用 `TFAuto*` 类使切换不同的 checkpoint 变得简单: ```py from transformers import AutoTokenizer, TFAutoModelForMaskedLM @@ -92,5 +92,5 @@ model = TFAutoModelForMaskedLM.from_pretrained("camembert-base") {/if} -使用预训练模型时,一定要检查它是如何训练的、在哪些数据集上训练的、它的局限性和偏差。所有这些信息都应在其模型卡片上注明。 +使用预训练模型时,一定要检查它是如何训练的、在哪些数据集上训练的、它的局限性和偏见。所有这些信息都应在其模型卡片上有所展示。 diff --git a/chapters/zh-CN/chapter4/3.mdx b/chapters/zh-CN/chapter4/3.mdx index 9be4ff273..a10f1e13d 100644 --- a/chapters/zh-CN/chapter4/3.mdx +++ b/chapters/zh-CN/chapter4/3.mdx @@ -22,22 +22,21 @@ {/if} -在下面的步骤中,我们将看看将预训练模型分享到 🤗 Hub 的最简单方法。有可用的工具和实用程序可以让直接在 Hub 上共享和更新模型变得简单,我们将在下面进行探讨。 +接下来,我们将探索把预训练模型分享到 🤗 Hub 最便捷的方法。我们将一同研究一些工具和功能,它们可以简化直接在中心上分享和更新模型的流程。 -我们鼓励所有训练模型的用户通过与社区共享来做出贡献——共享模型,即使是在非常特定的数据集上进行训练,也将帮助他人,节省他们的时间和计算资源,并提供对有用的训练工件的访问。反过来,您可以从其他人所做的工作中受益! +我们鼓励所有训练模型的用户通过与社区共享来做出贡献——即使是在特定数据集上训练的模型的分享,也能帮助他人,节省他们的时间和计算资源,并提供一些有用的训练成果。反过来,你可以从其他人所做的工作中受益! -创建新模型存储库的方法有以下三种: +创建模型存储仓库的方法有以下三种: -- 使用 push_to_hub API 接口 -- 使用 huggingface_hub Python 库 -- 使用 web 界面 +- 使用 `push_to_hub` API 接口 +- 使用 `huggingface_hub` Python 库 +- 使用网页界面 -创建存储库后,您可以通过 git 和 git-lfs 将文件上传到其中。我们将在以下部分引导您创建模型存储库并将文件上传到它们 +创建仓库后,你可以通过 git 和 git-lfs 将文件上传到其中。我们将在以下部分带领你创建模型仓库并向其中上传文件。 - -## 使用 push_to_hub API [[使用 push_to_hub API]] +## 使用 `push_to_hub` API [[使用 push_to_hub API]] {#if fw === 'pt'} @@ -49,9 +48,11 @@ {/if} -将文件上传到集线器的最简单方法是利用 **push_to_hub** API 接口。 +将文件上传到 hub 的最简单方法是利用 `push_to_hub` API 接口。 + +在进行下一步操作之前,你需要生成一个身份验证令牌,这样 `huggingface_hub` API 才会知道你是谁以及你对哪些空间具有写入权限。 -在继续之前,您需要生成一个身份验证令牌,以便 **huggingface_hub** API 知道您是谁以及您对哪些名称空间具有写入权限。确保你在一个环境中 **transformers** 已安装(见[Setup](/course/chapter0))。如果您在笔记本中,可以使用以下功能登录: +在一个安装了 `transformers` 环境中(参见 [第零章](/course/chapter0) )。如果你在 notebook 中,可以运行以下代码登录: ```python from huggingface_hub import notebook_login @@ -59,19 +60,19 @@ from huggingface_hub import notebook_login notebook_login() ``` -在终端中,您可以运行: +在终端中,你可以运行: ```bash huggingface-cli login ``` -在这两种情况下,系统都会提示您输入用户名和密码,这与您用于登录 Hub 的用户名和密码相同。如果您还没有 Hub 配置文件,则应该创建一个[here](https://huggingface.co/join)。 +在这两种情况下,系统都会提示你输入用户名和密码,这与你用于登录 Hub 的用户名和密码相同。如果你还没有 Huggingface 账户,则应该 [在这里](https://huggingface.co/join) 创建一个账户。 -好的!您现在已将身份验证令牌存储在缓存文件夹中。让我们创建一些存储库! +好极了!现在已将你的身份验证令牌存储在了缓存文件夹中。让我们创建一些仓库! {#if fw === 'pt'} -如果你玩过 **Trainer** 用于训练模型的 API,将其上传到 Hub 的最简单方法是设置 **push_to_hub=True** 当你定义你的 **TrainingArguments** : +如果你已经尝试使用过 `Trainer` API 训练模型,将其上传到 Hub 的最简单方法是在定义 `TrainingArguments` 时设置 `push_to_hub=True` : ```py from transformers import TrainingArguments @@ -81,11 +82,11 @@ training_args = TrainingArguments( ) ``` -你声明 **trainer.train()** 的时候, 这 **Trainer** 然后每次将您的模型保存到您的命名空间中的存储库中时(这里是每个时代),它将上传到集线器。该存储库将命名为您选择的输出目录(此处 **bert-finetuned-mrpc** ) 但您可以选择不同的名称 **hub_model_id = a_different_name** 。 +这样,调用 `trainer.train()` 的时候, `Trainer` 会在每次保存模型时(这里是每个训练周期)将其上传到 Hub 中你的账户下的一个仓库。该仓库的名称与你选择的输出目录名称相同(这里是 `bert-finetuned-mrpc` ),但你可以通过 `hub_model_id = "a_different_name"` 指定一个不同的名称。 -要将您的模型上传到您所属的组织,只需将其传递给 **hub_model_id = my_organization/my_repo_name** 。 +若要将你的模型上传到你所属的组织,只需设置 `hub_model_id = my_organization/my_repo_name` 。 -训练结束后,你应该做最后的 **trainer.push_to_hub()** 上传模型的最新版本。它还将生成包含所有相关元数据的模型卡,报告使用的超参数和评估结果!以下是您可能会在此类模型卡中找到的内容示例: +最后,需要在训练循环结束后运行 `trainer.push_to_hub()` 上传模型的最新版本。它还会生成一份包含所有相关元数据的模型卡片,包含使用的超参数和评估结果!以下是一个模型卡片的示例:
An example of an auto-generated model card. @@ -95,7 +96,7 @@ training_args = TrainingArguments( {:else} -如果您使用Keras来训练您的模型,则将其上传到Hub的最简单方法是在调用 **model.fit()** 时传递**PushToHubCallback**: +如果你使用 Keras 来训练你的模型,则将其上传到 Hub 的最简单方法是在调用 `model.fit()` 添加 `PushToHubCallback` : ```py from transformers import PushToHubCallback @@ -105,15 +106,15 @@ callback = PushToHubCallback( ) ``` -然后,您应该在对**model.fit()**的调用中添加**callbacks=[callback]**。然后,每次将模型保存在命名空间的存储库中(此处为每个 epoch)时,回调都会将模型上传到 Hub。该存储库的名称将类似于您选择的输出目录(此处为**bert-finetuned-mrpc**),但您可以选择另一个名称,名称为**hub_model_id = a_different_name**。 +然后,你应该在调用 `model.fit()` 时添加 `callbacks=[callback]` 。回调函数将在每次保存模型时(这里是每个训练周期)将你的模型上传到 Hub 的你的账户下的一个仓库。该仓库的名称你选择的输出目录名称相同(此处为 `bert-finetuned-mrpc` ),但你可以通过 `hub_model_id = "a_different_name"` 指定一个不同的名称。 -要将您的模型上传到您所属的组织,只需将其传递给 **hub_model_id = my_organization/my_repo_name** 。 +要将你的模型上传到你所属的组织,只需设置如下 `hub_model_id = my_organization/my_repo_name` 。 {/if} -在较低级别,可以通过模型、标记器和配置对象直接访问模型中心 **push_to_hub()** 方法。此方法负责创建存储库并将模型和标记器文件直接推送到存储库。与我们将在下面看到的 API 不同,不需要手动处理。 +在更底层,你可以通过直接在模型、tokenizer 和配置对象上调用 `push_to_hub()` 方法来访问模型中心。该方法能够同时创建仓库并直接将模型和 tokenizer 文件推送至仓库,无需手动操作,这与我们在下面要看到的 API 有些许不同。 -为了了解它是如何工作的,让我们首先初始化一个模型和一个标记器: +为了了解它是如何工作的,让我们首先创建一个模型和一个 tokenizer {#if fw === 'pt'} @@ -139,34 +140,35 @@ tokenizer = AutoTokenizer.from_pretrained(checkpoint) {/if} -你可以自由地用这些做任何你想做的事情——向标记器添加标记,训练模型,微调它。一旦您对生成的模型、权重和标记器感到满意,您就可以利用 **push_to_hub()** 方法直接在 **model** 中: +你可以随意进行一些操作——给 tokenizer 添加 tokens 训练模型,微调模型。直到你对最终的模型、权重和 tokenizer 满意。当你对的模最终的模型、权重和 tokenizer 感到满意,你就可以直接在 `model` 对象上调用 `push_to_hub()` 方法: ```py model.push_to_hub("dummy-model") ``` -这将创建新的存储库 **dummy-model** 在您的个人资料中,并用您的模型文件填充它。 -对标记器执行相同的操作,以便所有文件现在都可以在此存储库中使用: +这将在你的账户中创建新的 `dummy-model` 仓库,并将这个模型文件上传上去。 + +对 tokenizer 也需要进行相同的操作: ```py tokenizer.push_to_hub("dummy-model") ``` -如果您属于一个组织,只需指定 **organization** 上传到该组织的命名空间的参数: +如果你属于一个组织,只需指定 `organization` 参数即可将模型上传到该组织的账户下: ```py tokenizer.push_to_hub("dummy-model", organization="huggingface") ``` -如果您希望使用特定的 Hugging Face 令牌,您可以自由地将其指定给 **push_to_hub()** 方法也是: +如果你希望使用特定的 Hugging Face 令牌,你也可以很方便地将其传递给 `push_to_hub()` 方法: ```py tokenizer.push_to_hub("dummy-model", organization="huggingface", use_auth_token="") ``` -现在前往模型中心找到您新上传的模型:*https://huggingface.co/user-or-organization/dummy-model*。 +现在你就可以前往 [模型中心](https://huggingface.co/user-or-organization/dummy-model) 查看你新上传的模型。 -单击“文件和版本”选项卡,您应该会在以下屏幕截图中看到可见的文件: +单击“文件和版本(Files and versions)”选项卡,你应该能看到以下的文件: {#if fw === 'pt'}
@@ -180,43 +182,38 @@ tokenizer.push_to_hub("dummy-model", organization="huggingface", use_auth_token= -✏️ **试试看**!获取与检查点关联的模型和标记器,并使用该方法将它们上传到您的命名空间中的存储库。在删除之前,请仔细检查该存储库是否正确显示在您的页面上。 +✏️ **试试看** 获取与 `bert-base-cased` checkpoint 相关的模型和 tokenizer 并使用 `push_to_hub()` 方法将它们上传到你账户中的一个仓库。并请仔细检查该仓库在你的页面上显示是否正常。 -如您所见, **push_to_hub()** 方法接受多个参数,从而可以上传到特定的存储库或组织命名空间,或使用不同的 API 令牌。我们建议您查看直接在[🤗 Transformers documentation](https://huggingface.co/transformers/model_sharing.html)了解什么是可能的 +如你所见, `push_to_hub()` 方法接受多个参数,从而可以上传到特定的仓库或账户,除此之外还可以使用不同的 API 令牌验证身份。我们建议你直接查看 [🤗 Transformers 文档](https://huggingface.co/transformers/model_sharing) 了解更多的用法。 -这 **push_to_hub()** 方法由[huggingface_hub](https://github.com/huggingface/huggingface_hub)Python 包,为 Hugging Face Hub 提供直接 API。它集成在 🤗 Transformers 和其他几个机器学习库中,例如[allenlp](https://github.com/allenai/allennlp).虽然我们在本章中专注于 🤗 Transformers 集成,但将其集成到您自己的代码或库中很简单。 +位于 [huggingface_hub](https://github.com/huggingface/huggingface_hub) 库里的 `push_to_hub()` 方提供了直接面向 Hugging Face Hub 的 API。它集成在 🤗 Transformers 和其他几个机器学习库中,例如 [allenlp](https://github.com/allenai/allennlp) 。虽然我们在本章中专注于 🤗 Transformers 但将其集成到你自己的代码或库中很简单。 -跳到最后一部分,了解如何将文件上传到新创建的存储库! +到了最后一部分,让我们看看如何将文件上传到你新创建的仓库! -## 使用 huggingface_hub python库 [[使用 huggingface_hub python库]] +## 使用 `huggingface_hub` python 库 [[使用 `huggingface_hub` python 库]] +`huggingface_hub` Python 库是一个包,为模型和数据集中心提供了一系列工具。它提供了简单的方法和类,可以便捷地获取 hub 中仓库的信息以及管理仓库等常见任务。它在 git 的基础上提供了简单的 API,可以在你你的项目中管理这些仓库的内容,以及 hub 的一些功能。 -这 **huggingface_hub** Python 库是一个包,它为模型和数据集中心提供了一组工具。它为常见任务提供了简单的方法和类,例如 -获取有关集线器上存储库的信息并对其进行管理。它提供了在 git 之上工作的简单 API 来管理这些存储库的内容并集成 Hub -在您的项目和库中。 - -类似于使用 **push_to_hub** API,这将要求您将 API 令牌保存在缓存中。为此,您需要使用 **login** 来自 CLI 的命令,如上一节所述(同样,确保在这些命令前面加上 **!** 字符(如果在 Google Colab 中运行): +与使用 `push_to_hub` API 类似,这需要你将 API 令牌保存在缓存中。为此,你需要在操作系统的命令行使用 CLI 中的 `login` 命令,(如果在 Notebook 中运行,请在这些命令前添加 `!` 字符): ```bash huggingface-cli login -``` - -这 **huggingface_hub** 包提供了几种对我们有用的方法和类。首先,有几种方法可以管理存储库的创建、删除等: +``` `huggingface_hub` 包提供了几个非常便捷方法和类可以帮助我们完成 hub 的管理。首先,有一些方法可以管理仓库的创建、删除等: ```python no-format from huggingface_hub import ( - # User management + # 用户管理 login, logout, whoami, - # Repository creation and management + # 仓库创建和管理 create_repo, delete_repo, update_repo_visibility, - # And some methods to retrieve/change information about the content + # 以及一些检索/更改文件的方法 list_models, list_datasets, list_metrics, @@ -227,10 +224,9 @@ from huggingface_hub import ( ``` -此外,它还提供了非常强大的 **Repository** 用于管理本地存储库的类。我们将在接下来的几节中探讨这些方法和该类,以了解如何利用它们。 - -这 **create_repo** 方法可用于在集线器上创建新存储库: +此外,它还提供了非常强大的 `Repository` 类来管理本地仓库。我们将在接下来的几节中探讨这些方法和类,以理解如何利用它们。 +可以使用 `create_repo` 方法在 Hub 上创建新的仓库: ```py from huggingface_hub import create_repo @@ -238,7 +234,7 @@ from huggingface_hub import create_repo create_repo("dummy-model") ``` -这将创建存储库 **dummy-model** 在您的命名空间中。如果愿意,您可以使用 **organization** 争论: +这将在你的账户中创建 `dummy-model` 仓库。除此之外你还可以使用 `organization` 参数指定仓库应属于哪个组织: ```py from huggingface_hub import create_repo @@ -246,45 +242,47 @@ from huggingface_hub import create_repo create_repo("dummy-model", organization="huggingface") ``` -这将创建 **dummy-model** 存储库中的 **huggingface** 命名空间,假设您属于该组织。 -其他可能有用的参数是: +这将在 `huggingface` 组织的账户中创建 `dummy-model` 仓库,如果你属于该组织并且有创建仓库的权限。 -- private 以指定存储库是否应对其他人可见。 -- token 如果您想用给定的令牌覆盖存储在缓存中的令牌。 -- repo_type 如果你想创建一个或一个替代一个的而不是模型。接受的值和 datasetspace "dataset""space"。 +其他可能有用的一些参数有: -创建存储库后,我们应该向其中添加文件!跳到下一部分以查看可以处理此问题的三种方法。 +- `private` ,用于指定仓库是否应对其他人可见。 +- `token` ,如果你希望使用特定的身份令牌,而不是缓存中的身份令牌。 +- `repo_type` ,如果你希望创建一个 `dataset` 或 `space` 而不是模型。需要将 `repo_type` 设置为 `"dataset"` 和 `"space"` 。 +创建仓库后,我们应该向其中添加文件!让我们到下一节,看看三种添加文件的方式。 ## 使用网络界面 [[使用网络界面]] -Web 界面提供了直接在 Hub 中管理存储库的工具。使用该界面,您可以轻松创建存储库、添加文件(甚至是大文件!)、探索模型、可视化差异等等。 +Web 界面提供了直接在 Hub 中管理仓库的工具。使用该界面,你可以轻松创建仓库、添加文件(甚至是大文件!)、探索模型、可视化差异等等。 -要创建新的存储库,请访问[huggingface.co/new](https://huggingface.co/new): +要创建新的仓库,请访问 [huggingface.co/new](https://huggingface.co/new) :
Page showcasing the model used for the creation of a new model repository.
-首先,指定存储库的所有者:这可以是您或您所属的任何组织。如果您选择一个组织,该模型将出现在该组织的页面上,并且该组织的每个成员都可以为存储库做出贡献。 +首先,指定仓库的所有者:这可以是你或你所属的组织。如果你选择一个组织,模型将在会显示在组织的页面上,组织的每个成员可以操作仓库。 -接下来,输入您的模型名称。这也将是存储库的名称。最后,您可以指定您的模型是公开的还是私有的。私人模特要求您拥有付费 Hugging Face 帐户,并允许您将模特隐藏在公众视野之外。 +接下来,输入你的模型名称。同时也是仓库的名称。最后,你可以指定你的模型是公开的还是私有的。私有的模型将被隐藏,对其他的用户不可见。 -创建模型存储库后,您应该看到如下页面: +创建模型仓库后,你应该会看到如下页面:
An empty model page after creating a new repository.
-这是您的模型将被托管的地方。要开始填充它,您可以直接从 Web 界面添加 README 文件。 +这就是你的模型仓库会显示的页面。你可以直接从网页界面添加 README 文件来填写它。
The README file showing the Markdown capabilities.
-README 文件在 Markdown 中 - 随意使用它!本章的第三部分致力于构建模型卡。这些对于为您的模型带来价值至关重要,因为它们是您告诉其他人它可以做什么的地方。 +README 文件采用 Markdown 格式 - 你可以根据你的需要自由编写! + +这一章的下一部分将帮助你建立模型卡片。这些模型卡片对于提升模型的价值非常重要,因为它们可以告诉其他人你的模型能做什么。 -如果您查看“文件和版本”选项卡,您会发现那里还没有很多文件——只有自述文件你刚刚创建和.git 属性跟踪大文件的文件。 +如果你希望添加更多文件,点击页面顶部的 “Files and versions(文件和版本)” 标签页。在这里,你可以看到仓库的所有文件以及它们的版本。
The 'Files and versions' tab only shows the .gitattributes and README.md files. @@ -294,15 +292,13 @@ README 文件在 Markdown 中 - 随意使用它!本章的第三部分致力于 ## 上传模型文件 [[上传模型文件]] -Hugging Face Hub 上的文件管理系统基于用于常规文件的 git 和 git-lfs(代表[Git Large File Storage](https://git-lfs.github.com/)) 对于较大的文件。 - -在下一节中,我们将介绍将文件上传到 Hub 的三种不同方式:通过 **huggingface_hub** 并通过 git 命令。 +Hugging Face Hub 的文件管理系统基于 git 用于处理常规文件,对于较大的文件则需要使用 git-lfs(详情请参阅 [Git 大文件存储](https://git-lfs.github.com/) ) -### The `upload_file` approach [[The `upload_file` approach]] +在下一节中,我们会讲述怎么以三种不同的方式将文件上传到 Hub: `upload_file` 方法、 `Repository` 类 以及通过 git 命令。 -使用 **upload_file** 不需要在您的系统上安装 git 和 git-lfs。它使用 HTTP POST 请求将文件直接推送到 🤗 Hub。这种方法的一个限制是它不能处理大于 5GB 的文件。 -如果您的文件大于 5GB,请按照下面详述的另外两种方法进行操作。API 可以按如下方式使用: +### `upload_file` 方法 [[ `upload_file` 方法]] +使用 `upload_file` 不需要在你的系统上安装 git 和 git-lfs。它使用 HTTP POST 请求将文件直接发送到 Hub。这种方法的一个限制是它不能上传大于 5GB 的文件。如果你的文件大于 5GB,请按照另外两种方法进行操作。 `upload_file` 的使用方法如下: ```py from huggingface_hub import upload_file @@ -313,20 +309,16 @@ upload_file( ) ``` -这将上传文件 **config.json** 可在 **path_to_file** 到存储库的根目录 **config.json** , 到 **dummy-model** 存储库。 -其他可能有用的参数是: +以上的代码会把 `` 位置的 `config.json` 文件上传到 `dummy-model` 仓库的根目录作为模型的 `config.json` 。其他可能有用的参数包括: -- token,如果要通过给定的令牌覆盖缓存中存储的令牌。 -- repo_type, 如果你想要上传一个 `dataset` 或一个 `space` 而不是模型。 接受的值为 `"dataset"` 和 `"space"`. +- `token` ,如果你希望在这里使用特定的身份令牌,而不是缓存中的身份令牌。 +- `repo_type` ,如果你希望创建一个 `dataset` 或 `space` 而不是模型。需要将 `repo_type` 设置为 `"dataset"` 和 `"space"` 。 +### `Repository` 类 [[ `Repository` 类]] `Repository` 类可以使用类似 git 的方式管理一个本地仓库。它解决了使用 git 可能遇到的大部分痛点,提供我们所需的所有功能。 -### The `Repository` class [[The `Repository` class]] +使用这个类需要安装 git 和 git-lfs,所以请确保你已经安装了 git-lfs(如果没有安装,可以查看 [git-lfs 官网](https://git-lfs.github.com/) 的安装指南)并已经在操作系统里配置好了相关功能。 -以类似 git 的方式管理本地存储库。它抽象了 git 可能遇到的大部分痛点,以提供我们需要的所有功能。 - -使用这个类需要安装 git 和 git-lfs,所以确保你已经安装了 git-lfs(参见[here](https://git-lfs.github.com/)安装说明)并在开始之前进行设置。 - -为了开始使用我们刚刚创建的存储库,我们可以通过克隆远程存储库将其初始化到本地文件夹开始: +为了开始使用我们刚刚创建的仓库,我们可以把远程仓库克隆到一个本地文件夹: ```py from huggingface_hub import Repository @@ -334,9 +326,9 @@ from huggingface_hub import Repository repo = Repository("", clone_from="/dummy-model") ``` -这创建了文件夹 **path_to_dummy_folder** 在我们的工作目录中。该文件夹仅包含 **.gitattributes** 文件,因为这是通过实例化存储库时创建的唯一文件 **create_repo**。 +以上代码会在我们当前的工作目录中创建 `` 文件夹。此文件夹里只有 `.gitattributes` 文件,这是使用 `create_repo` 创建仓库时生成的文件。 -从现在开始,我们可以利用几种传统的 git 方法: +从现在开始,我们可以利用许多传统的 git 方法: ```py repo.git_pull() @@ -346,24 +338,24 @@ repo.git_push() repo.git_tag() ``` -另外!我们建议您查看 **Repository** 可用文件[here](https://github.com/huggingface/huggingface_hub/tree/main/src/huggingface_hub#advanced-programmatic-repository-management)有关所有可用方法的概述。 +此外!我们建议你查看 [huggingface](https://github.com/huggingface/huggingface_hub/tree/main/src/huggingface_hub#advanced-programmatic-repository-management) 的 `Repository` 文档,了解一下所有可用方法。 -目前,我们有一个模型和一个标记器,我们希望将其推送到集线器。我们已经成功克隆了存储库,因此我们可以将文件保存在该存储库中。 +现在,我们有了一个模型和一个 tokenizer 我们希望将其推送到 hub。我们已经成功克隆了仓库,因此我们可以将文件保存到在该仓库的文件夹中。 -我们首先通过拉取最新更改来确保我们的本地克隆是最新的: +首先,我们通过拉取最新的更改,确保我们的本地克隆是最新的: ```py repo.git_pull() ``` -完成后,我们保存模型和标记器文件: +完成后,我们保存模型和 tokenizer 文件: ```py model.save_pretrained("") tokenizer.save_pretrained("") -``` +``` -这 **path_to_dummy_folder** 现在包含所有模型和标记器文件。我们遵循通常的 git 工作流程,将文件添加到暂存区,提交它们并将它们推送到集线器: +`` 现在已经存储了模型和 tokenizer 文件。我们遵循常规的 git 工作流程,将文件添加到暂存区,提交它们并推送它们到 hub: ```py repo.git_add() @@ -371,15 +363,15 @@ repo.git_commit("Add model and tokenizer files") repo.git_push() ``` -恭喜!您刚刚将第一个文件推送到hub上。 +恭喜你!你刚刚在 hub 上推送了你的第一个文件。 -### The git-based approach [[The git-based approach]] +### 基于 git 的方法 [[基于 git 的方法]] -这是上传文件的非常简单的方法:我们将直接使用 git 和 git-lfs 来完成。大多数困难都被以前的方法抽象掉了,但是下面的方法有一些警告,所以我们将遵循一个更复杂的用例。 +这是上传文件的最基础方法:我们将直接使用 git 和 git-lfs 进行操作,相比于之前的方法难度更大同时也更加灵活。在前面的方法大部分的难点都已经被封装好的库解决,但是接下来的方法需要手动来解决它们,所以我们会尝试一个更复杂的使用案例。 -使用这个类需要安装 git 和 git-lfs,所以请确保你有[git-lfs](https://git-lfs.github.com/)安装(请参阅此处了解安装说明)并在开始之前进行设置。 +使用这个方法需要安装 git 和 git-lfs,所以请确保你已经安装了 git-lfs(如果没有安装,可以参见 [git-lfs 官网](https://git-lfs.github.com/) 的安装指南)并已经在操作系统里配置好了相关功能。 -首先从初始化 git-lfs 开始: +首先,初始化 git-lfs: ```bash git lfs install @@ -390,19 +382,19 @@ Updated git hooks. Git LFS initialized. ``` -完成后,第一步是克隆您的模型存储库: +完成后,接下来需要克隆你的模型仓库: ```bash git clone https://huggingface.co// ``` -我的用户名是 **lysandre** 我使用了模型名称 **dummy** ,所以对我来说,命令最终如下所示: +我的用户名是 `lysandre` 我使用的模型的名称是 `dummy` ,所以对我来说,命令最终如下所示: ``` git clone https://huggingface.co/lysandre/dummy ``` -我现在有一个名为的文件夹假在我的工作目录中。我能 **cd** 进入文件夹并查看内容: +我现在在工作目录中多了一个名为 dummy 的文件夹。我可以 cd 到这个文件夹,看看其中的内容: ```bash cd dummy && ls @@ -412,11 +404,11 @@ cd dummy && ls README.md ``` -如果您刚刚使用 Hugging Face Hub 创建了您的存储库 **create_repo** 方法,这个文件夹应该只包含一个隐藏的 **.gitattributes** 文件。如果您按照上一节中的说明使用 Web 界面创建存储库,则该文件夹应包含一个自述文件文件旁边的隐藏 **.gitattributes** 文件,如图所示。 +如果你刚刚用的是 Hugging Face Hub 的 `create_repo` 方法创建的仓库,这个文件夹应该只包含一个隐藏的 .gitattributes 文件。如果你使用的是网页界面创建了一个仓库,那么文件夹应该只包含一个 README.md 文件和一个隐藏的 .gitattributes 文件。 -添加一个常规大小的文件,例如配置文件、词汇文件,或者基本上任何几兆字节以下的文件,就像在任何基于 git 的系统中所做的一样。但是,更大的文件必须通过 git-lfs 注册才能将它们推送到拥抱脸。 +较小的文件,如配置文件、词汇表文件,或者基本上任何几 MB 以下的文件,操作的方法就像我们平时使用 git 一样。然而,更大的文件必须通过 git-lfs 注册才能推送到 huggingface.co。 -让我们回到 Python 来生成我们想要提交到我们的虚拟存储库的模型和标记器: +让我们回到 Python,生成一些我们希望提交到 dummy 仓库的模型和 tokenizer {#if fw === 'pt'} ```py @@ -427,7 +419,7 @@ checkpoint = "camembert-base" model = AutoModelForMaskedLM.from_pretrained(checkpoint) tokenizer = AutoTokenizer.from_pretrained(checkpoint) -# Do whatever with the model, train it, fine-tune it... +# 对模型进行一些操作,训练、微调... model.save_pretrained("") tokenizer.save_pretrained("") @@ -441,14 +433,14 @@ checkpoint = "camembert-base" model = TFAutoModelForMaskedLM.from_pretrained(checkpoint) tokenizer = AutoTokenizer.from_pretrained(checkpoint) -# Do whatever with the model, train it, fine-tune it... +# 对模型进行一些操作,训练、微调... model.save_pretrained("") tokenizer.save_pretrained("") ``` {/if} -现在我们已经保存了一些模型和标记器工件,让我们再看看假文件夹: +现在我们已经保存了模型和 tokenizer 让我们再看一下 dummy 文件夹: ```bash ls @@ -459,22 +451,22 @@ ls config.json pytorch_model.bin README.md sentencepiece.bpe.model special_tokens_map.json tokenizer_config.json tokenizer.json ``` -If you look at the file sizes (for example, with `ls -lh`), you should see that the model state dict file (*pytorch_model.bin*) is the only outlier, at more than 400 MB. +如果你尝试查看文件大小(例如,使用 `ls -lh` ),你应该看到模型状态字典文件(pytorch_model.bin)是唯一的异常的文件,超过了 400 MB。 {:else} ```bash config.json README.md sentencepiece.bpe.model special_tokens_map.json tf_model.h5 tokenizer_config.json tokenizer.json ``` -如果您查看文件大小(例如, **ls -lh** ),您应该会看到模型状态 dict 文件 (pytorch_model.bin) 是唯一的异常值,超过 400 MB。 +如果你尝试查看文件大小(例如, `ls -lh` ),你应该会看到模型状态字典文件 (t5_model.h5) 唯一的异常的文件,超过了 400 MB。 {/if} -✏️ 从 web 界面创建存储库时,*.gitattributes* 文件会自动设置为将具有某些扩展名的文件,例如 *.bin* 和 *.h5* 视为大文件,git-lfs 会对其进行跟踪您无需进行必要的设置。 +✏️ 当通过网页界面创建仓库时, `.gitattributes` 文件会自动将某些扩展名(如 `.bin` 和 `.h5` )的文件视为大文件,你无需对 git-lfs 进行任何设置即可跟踪它们。 -我们现在可以继续进行,就像我们通常使用传统 Git 存储库一样。我们可以使用以下命令将所有文件添加到 Git 的暂存环境中 **git add** 命令: +我们现在可以继续上传,就像我们使用传统 Git 仓库一样。我们可以使用以下命令将所有文件添加到 Git 的暂存环境中 `git add` 命令: ```bash git add . @@ -518,7 +510,7 @@ Changes to be committed: ``` {/if} -同样,我们可以确保 git-lfs 使用其跟踪正确的文件 **status** 命令: +同样,我们可以使用 `status` 命令查看 git-lfs 正在跟踪的文件: ```bash git lfs status @@ -544,7 +536,7 @@ Objects not staged for commit: ``` -我们可以看到所有文件都有 **Git** 作为处理程序,除了其中有 **LFS**的*pytorch_model.bin* 和 *sentencepiece.bpe.model*。 +我们可以看到所有文件都使用 `Git` 作为处理程序,除了 `pytorch_model.bin` 和 `sentencepiece.bpe.model` 它们的处理程序是 `LFS` 。 {:else} ```bash @@ -566,11 +558,11 @@ Objects not staged for commit: ``` -我们可以看到所有文件都有 **Git** 作为处理程序,除了其中有 **LFS**的*t5_model.h5*。 +我们可以看到所有文件都使用 `Git` 作为处理程序,除了 `t5_model.h5` ,它的处理程序是 `LFS` 。 {/if} -Let's proceed to the final steps, committing and pushing to 让我们继续最后的步骤,提交并推动拥抱脸远程仓库: +让我们继续进行最后的步骤,提交并推送到 huggingface.co 远程仓库: ```bash git commit -m "First model version" @@ -601,7 +593,7 @@ git commit -m "First model version" ``` {/if} -推送可能需要一些时间,具体取决于您的互联网连接速度和文件大小: +推送可能需要一些时间,具体取决于你的网络速度以及你的文件大小: ```bash git push @@ -620,13 +612,13 @@ To https://huggingface.co/lysandre/dummy ``` {#if fw === 'pt'} -If we take a look at the model repository when this is finished, we can see all the recently added files: +当这些都完成时,如果我们查看模型仓库,我们可以看到所有最近添加的文件:
The 'Files and versions' tab now contains all the recently uploaded files.
-UI 允许您浏览模型文件和提交,并查看每个提交引入的差异: +在Huggingface 的网页上你可以查看模型文件和提交,并查看每次提交引入的差异:
The diff introduced by the recent commit. @@ -634,13 +626,13 @@ UI 允许您浏览模型文件和提交,并查看每个提交引入的差异 {:else} -如果我们在完成后查看模型存储库,我们可以看到所有最近添加的文件: +当这些都完成时,如果我们查看模型仓库,我们可以看到所有最近添加的文件:
The 'Files and versions' tab now contains all the recently uploaded files.
-UI 允许您浏览模型文件和提交,并查看每个提交引入的差异: +在Huggingface 的网页上你可以查看模型文件和提交,并查看每次提交引入的差异:
The diff introduced by the recent commit. diff --git a/chapters/zh-CN/chapter4/4.mdx b/chapters/zh-CN/chapter4/4.mdx index e844ac18a..3f78af83e 100644 --- a/chapters/zh-CN/chapter4/4.mdx +++ b/chapters/zh-CN/chapter4/4.mdx @@ -7,11 +7,11 @@ 模型卡片是一个配置文件,可以说与模型存储库中的模型和 tokenizer 文件一样重要。它包含了模型的核心定义,确保了社区成员可以复现模型的结果,并提供一个其他成员可以在这个模型基础上构建他们的组件的平台。 -记录训练和评估过程并提供有关使用的数据以及已完成的预处理和后续处理的足够信息,有助于其他人了解对模型的能力——确保模型存在和目前的限制、偏差可以识别和理解。 +记录训练和评估过程并提供有关使用的数据以及已完成的预处理和后续处理的足够信息,有助于其他人了解对模型的能力——模型存的局限性、偏见以及模型有效或无效的使用场景。 -因此,创建清晰定义模型的模型卡片是非常重要的一步。在这里,我们提供了一些可以帮助您解决此问题的方法。创建模型卡片是通过您之前看到的 Markdown 文件:README.md 。 +因此,创建一个清晰定义你的模型的模型卡片是非常重要的一步。在这里,我们提供了一些可以帮助你创建模型卡片的建议。创建模型卡片的内容保存在你之前看到的 README.md 文件中,这是一个 Markdown 文件。 -“模型卡片”的概念源于谷歌的一个研究方向, Margaret Mitchell 等人在论文[“Model Cards for Model Reporting”](https://arxiv.org/abs/1810.03993)中首次提出,此处包含的许多信息均基于该论文,我们建议您查看这篇论文以了解为什么模型卡片在重视可重复性、可重用性和公平性的时候中如此重要。 +“模型卡片”的概念源于谷歌的一个研究方向,Margaret Mitchell 等人在论文 [“模型卡片用于模型报告”](https://arxiv.org/abs/1810.03993) 中首次提出,这里许多内容均基于该论文,我们建议你查看这篇论文以了解为什么模型卡片在实现可复现性、可重用性和公平性的中如此重要。 模型卡通常以非常简短的概述开始,说明模型的用途,然后是模型卡片需要的其他信息: @@ -25,53 +25,53 @@ 让我们来看看每个部分应该包含什么。 -### 模型描述: [[模型描述:]] +### 模型描述:[[模型描述:]] 提供了有关模型的基本详细信息。这包括架构、版本、如果它是在论文中介绍的,是否有原始的实现可用?作者以及有关模型的一般信息、任何版权都应归于此处。这一部分还可以提及有关训练程序、参数和重要免责声明的一般信息。 -### 预期用途和限制: [[预期用途和限制:]] +### 预期用途和限制:[[预期用途和限制:]] 在此描述模型可以适用的例子,包括可以应用它的语言、领域。模型卡的这一部分还可以记录已知超出模型范围的区域,或者可能表现不佳的区域。 -### 使用方法: [[使用方法:]] +### 使用方法:[[使用方法:]] -此部分应包括一些有关如何使用模型的示例。这可以展示使用 **pipeline()** 函数、模型和标记器类的使用以及其他任何您认为可能有帮助的代码。 +此部分应包括一些有关如何使用模型的示例。这可以展示使用 `pipeline()` 函数、模型和 tokenizer 的使用以及其他任何你认为可能有帮助的代码。 -### 训练数据: [[训练数据:]] +### 训练数据:[[训练数据:]] 这部分应该指出模型是在哪个数据集上训练的。也欢迎对数据集进行简要描述。 -### 训练过程: [[训练过程:]] +### 训练过程:[[训练过程:]] -此部分中,您应该描述从再现性角度来看有用的训练的所有相关方面。这包括对数据进行的任何预处理和后处理,以及模型训练的批量数、批量大小、学习率等细节。 +此部分中,你应该描述从再现性角度来看有用的训练的所有相关方面。这包括对数据进行的任何预处理和后处理,以及模型训练的批量数、批量大小、学习率等细节。 -### 变量和指标: [[变量和指标:]] +### 变量和指标:[[变量和指标:]] -在这里,您应该描述您用于评估的指标,以及您测量的不同因素。提及使用了哪些指标、在哪个数据集上以及哪个数据集部分,可以轻松地将您的模型的性能与其他模型的性能进行比较。 +在这里,你应该描述你用于评估的指标,以及你测量的不同因素。提及使用了哪些指标、在哪个数据集上以及哪个数据集部分,可以轻松地将你的模型的性能与其他模型的性能进行比较。 -### 评价结果: [[评价结果:]] +### 评估结果:[[评估结果:]] 这些应该提前在前面的部分告知,例如预期的使用效果和示例。最后,提供模型在评估数据集上的表现的指示。如果模型使用决策阈值,要么提供评估中使用的决策阈值,要么提供在不同阈值下针对预期用途进行评估的详细信息。 -## 例子 [[例子]] +## 示例 [[示例]] -查看以下几个精心制作的模型卡的例子: +请查看以下几个精心制作的模型卡的示例: -* [bert-base-cased](https://huggingface.co/bert-base-cased) -* [gpt2](https://huggingface.co/gpt2) -* [distilbert](https://huggingface.co/distilbert-base-uncased) +* [bert-base-cased](https://huggingface.co/bert-base-cased) +* [gpt2](https://huggingface.co/gpt2) +* [distilbert](https://huggingface.co/distilbert-base-uncased) -更多来自于不同组织和公司的示例可以在[这里](https://github.com/huggingface/model_card/blob/master/examples.md)查阅. +更多来自于不同组织和公司的示例可以在 [模型卡片示例](https://github.com/huggingface/model_card/blob/master/examples.md) 中查阅. -## 提示 [[提示]] +## 注意事项 [[注意事项]] -发布模型时不需要模型卡,制作一个模型时不需要包含上述所有部分。但是,模型的文档会使未来的用户受益,因此我们建议您尽自己的知识和能力填写尽可能多的部分。 +发布模型时,并非必须有模型卡片,制作一个模型时不需要包含上述所有部分。但是,模型的文档会使未来的用户受益,因此我们建议你尽你所知和能力所及地尽可能填写尽可能多的部分。 ## 模型卡片元数据 [[模型卡片元数据]] -如果您对 Hugging Face Hub 进行了一些探索,您应该已经看到某些模型属于某些类别:您可以按任务、语言、库等对其进行过滤。模型所属的类别来自于您在模型卡片标题中添加的元数据。 +如果你对 Hugging Face Hub 进行了一些探索,你应该已经看到某些模型属于某些类别:你可以按任务、语言、库等对其进行筛选。模型所属的类别来自于你在模型卡片头部中添加的元数据。 -例如,如果你看一下[`camembert-base` 模型卡片](https://huggingface.co/camembert-base/blob/main/README.md),您应该在模型卡标题中看到以下几行: +例如,如果你看一下 [`camembert-base` 模型卡片](https://huggingface.co/camembert-base/blob/main/README.md) ,你应该在模型卡头部中看到以下几行: ``` --- @@ -82,6 +82,6 @@ datasets: --- ``` -该元数据由 Hugging Face Hub 解析,然后将这个模型识别为法语模型,拥有 MIT 许可证,在 Oscar 数据集上训练。 +该元数据由 Hugging Face Hub 解析,然后将这个模型为法语模型,拥有 MIT 许可证,在 Oscar 数据集上训练。 -允许的指定语言、许可证、标签、数据集、指标以及模型在训练时获得的评估结果在[全部模型卡片的规格](https://raw.githubusercontent.com/huggingface/huggingface_hub/main/modelcard.md)可以查阅。 \ No newline at end of file +可以在 [全部模型卡片的规格](https://raw.githubusercontent.com/huggingface/huggingface_hub/main/modelcard.md) 查阅支持的语言、许可证、标签、数据集、指标以及模型在训练时获得的评估结果。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter4/5.mdx b/chapters/zh-CN/chapter4/5.mdx index 5c3b9708d..9218fc5d9 100644 --- a/chapters/zh-CN/chapter4/5.mdx +++ b/chapters/zh-CN/chapter4/5.mdx @@ -1,12 +1,12 @@ -# Part 1 完结! [[Part 1 完结!]] +# Part 1 完结![[Part 1 完结!]] -这是课程第一部分的结尾!第 2 部分将在 11 月 15 日与大型社区活动一起发布,[点击这里](https://huggingface.co/blog/course-launch-event)查看更多信息. +这是课程第一部分的结尾!第 2 部分将在 11 月 15 日与大型社区活动一起发布,更多信息请 [点击这里](https://huggingface.co/blog/course-launch-event) 。 -您现在应该能够针对文本分类问题(单个或成对句子)对预训练模型进行微调,并将结果上传到模型中心。为确保您掌握了第一部分的内容,您应该针对您感兴趣的想法进行尝试(不一定是英语)!一旦你完成,您可以在[Hugging Face 社区](https://discuss.huggingface.co/)的[这个话题](https://discuss.huggingface.co/t/share-your-projects/6803)分享您的项目。 +你现在应该能够针对文本分类问题(单个或成对句子)对预训练模型进行微调,并将结果上传到模型中心。为确保你掌握了第一部分的内容,你应该针对你感兴趣的想法进行尝试(不一定是英语)!一旦你完成,你可以在 [Hugging Face 社区](https://discuss.huggingface.co/) 的 [这个话题](https://discuss.huggingface.co/t/share-your-projects/6803) 分享你的项目。 -我们迫不及待地想看看您将用它构建什么! \ No newline at end of file +我们迫不及待地想看到你会用这些内容做出什么! diff --git a/chapters/zh-CN/chapter4/6.mdx b/chapters/zh-CN/chapter4/6.mdx index 806ab4352..218ba78b9 100644 --- a/chapters/zh-CN/chapter4/6.mdx +++ b/chapters/zh-CN/chapter4/6.mdx @@ -10,31 +10,32 @@ /> 让我们测试一下你在本章所学的知识! - -### 1.Hub上的模型有什么限制? + +### 1.往Hub 上传模型有什么限制? + -### 2.如何管理Hub上的模型? +### 2.如何管理 Hub 上的模型? -### 3.你能使用Hugging Face Hub网页接口做什么? +### 3.你能使用 Hugging Face Hub 网页接口做什么? -### 4.模型卡是什么? +### 4.什么是模型卡片? -### 5.哪些🤗 Transformers 库的对象可以直接在 Hub 上通过push _ to _ Hub ()共享? +### 5.哪些🤗 Transformers 库的对象可以直接通过 `push_to_hub()` 分享到 Hub? {#if fw === 'pt'} {/if} -### 6.当使用push _ to _ hub ()方法或 CLI 工具时,第一步是什么? +### 6.当使用 `push_to_hub()` 方法或 CLI 工具时,第一步是什么? -### 7.您正在使用一个模型和一个标记器————如何将它们上传到 Hub? +### 7.你正在使用一个模型和一个 tokenizer ——如何将它们上传到 Hub? + huggingface _ hub 实用程序: 不需要额外的包装!" + text: "在 Python 运行时中,使用 `huggingface_hub` 中的方法进行封装。", + explain: "模型和 tokenizer 已经使用 `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 () 方法就是为此而存在的。", + text: "提交(commit)", + explain: "正确, `git_commit()` 方法就是为此而存在的。", correct: true }, { - text: "拉一下", - explain: "这就是 < code > git _ pull () 方法的目的。", + text: "拉取(pull)", + explain: "这就是 `git_pull()` 方法的功能。", correct: true }, { - text: "推一下", - explain: "方法 < code > git _ push () 可以做到这一点。", + text: "推送(push)", + explain: "方法 `git_push()` 可以做到这一点。", correct: true }, { - text: "合并", - explain: "不,这个操作在这个 API 中是不可能的。" + text: "合并(merge)", + explain: "不,这个操作在这个 API 中是无法实现的。" } ]} /> diff --git a/chapters/zh-CN/chapter5/1.mdx b/chapters/zh-CN/chapter5/1.mdx index c13d18b91..d626e75a2 100644 --- a/chapters/zh-CN/chapter5/1.mdx +++ b/chapters/zh-CN/chapter5/1.mdx @@ -5,18 +5,17 @@ classNames="absolute z-10 right-0 top-0" /> -在[第三章](/course/chapter3)第一次体验了🤗Datasets 库,并发现在微调模型时有三个主要步骤: +我们在 [第三章](/course/chapter3) 第一次体验了🤗 Datasets 库,了解到微调模型主要有三个步骤: -1. 从hugs Face Hub加载一个数据集。 -2. 使用Dataset.map()对数据进行预处理。 -3. 加载和计算指标(特征)。 +1. 从 Hugging Face Hub 加载数据集。 +2. 使用 `Dataset.map()` 预处理数据。 +3. 加载和计算指标(特征)。 -但这只是🤗 Datasets的表面功能而已!在本章中,我们将深入了解这个库。在此过程中,我们将找到以下问题的答案: - -* 当数据集不在hub上时,您该怎么做? -* 如何对数据集进行切片?(如果你真正的特别需要使用pandas的时候该怎么办?) -* 当你的数据集很大,会撑爆你笔记本电脑的RAM时,你会怎么做? -* “内存映射”和Apache Arrow到底是什么? +但这仅仅触及了🤗 Datasets 库能做的事情的冰山一角!在本章,我们将深入探索这个库。一路上,我们会找到以下问题的答案: +* 当你的数据集不在 Hub 上时,你应该怎么做? +* 你如何切分和操作数据集?(如果你非常需要使用 Pandas,该如何处理?) +* 当你的数据集非常大,会撑爆你笔记本电脑的 RAM 时,你应该怎么办? +* 什么是“内存映射”和 “Apache Arrow”? * 如何创建自己的数据集并将其推送到中心? -您在这里学到的技术将为您在[第6章](/course/chapter6)和[第7章](/course/chapter7)中的高级标记化和微调任务做好准备——所以,喝杯咖啡,让我们开始吧! \ No newline at end of file +你在这里学到的技术将为你在 [第六章](/course/chapter6) 和 [第七章](/course/chapter7) 中的高级 tokenization 和微调任务做好准备——所以,来杯咖啡,让我们开始吧! \ No newline at end of file diff --git a/chapters/zh-CN/chapter5/2.mdx b/chapters/zh-CN/chapter5/2.mdx index 555e35f70..478e457b5 100644 --- a/chapters/zh-CN/chapter5/2.mdx +++ b/chapters/zh-CN/chapter5/2.mdx @@ -1,4 +1,4 @@ -# 如果我的数据集不在 Hub 上怎么办? [[如果我的数据集不在 Hub 上怎么办?]] +# 如果我的数据集不在 Hub 上怎么办?[[如果我的数据集不在 Hub 上怎么办?]] -你知道如何使用[Hugging Face Hub](https://huggingface.co/datasets)下载数据集, 但你经常会发现自己正在处理存储在笔记本电脑或远程服务器上的数据。在本节中,我们将向您展示如何使用 🤗 Datasets来加载 Hugging Face Hub 上不可用的数据集。 +你已经知道如何使用 [Hugging Face Hub](https://huggingface.co/datasets) 中的数据集,但你往往会发现自己需要处理在自己的笔记本电脑或者网络上的数据集。在本节中,我们将展示如何使用🤗 Datasets 加载不在 Hugging Face Hub 中的数据集。 ## 使用本地和远程数据集 [[使用本地和远程数据集]] -🤗 Datasets 提供了加载脚本来加载本地和远程数据集。它支持几种常见的数据格式,例如: +🤗 Datasets 提供了加载本地和远程数据集的方法。它支持几种常见的数据格式,例如: -| Data format | Loading script | Example | +| 数据格式 | 类型参数 | 加载的指令 | | :----------------: | :------------: | :-----------------------------------------------------: | -| CSV & TSV | `csv` | `load_dataset("csv", data_files="my_file.csv")` | -| Text files | `text` | `load_dataset("text", data_files="my_file.txt")` | -| JSON & JSON Lines | `json` | `load_dataset("json", data_files="my_file.jsonl")` | -| Pickled DataFrames | `pandas` | `load_dataset("pandas", data_files="my_dataframe.pkl")` | +| CSV & TSV | `csv` | `load_dataset("csv", data_files="my_file.csv")` | +| Text files | `text` | `load_dataset("text", data_files="my_file.txt")` | +| JSON & JSON Lines | `json` | `load_dataset("json", data_files="my_file.jsonl")` | +| Pickled DataFrames | `pandas` | `load_dataset("pandas", data_files="my_dataframe.pkl")` | -如表所示, 对于每种数据格式, 我们只需要使用 `load_dataset()` 函数, 使用 `data_files` 指定一个或多个文件的路径的参数。 让我们从本地文件加载数据集开始;稍后我们将看到如何对远程文件执行相同的操作。 +如表所示,对于每种数据格式,我们只需要在 `load_dataset()` 函数中指定数据的类型,并使用 `data_files` 指定一个或多个文件的路径的参数。首先,我们从加载本地文件的数据集开始;稍后,我们将看到如何使用远程文件做同样的事情。 ## 加载本地数据集 [[加载本地数据集]] -对于这个例子,我们将使用 [SQuAD-it dataset](https://github.com/crux82/squad-it/), 这是一个大规模的意大利语问答数据集。 +在这个例子中,我们将使用 [SQuAD-it 数据集](https://github.com/crux82/squad-it/) ,这是一个用于意大利语问答的大规模数据集。 -训练和测试都托管在 GitHub 上, 因此我们可以通过`wget`命令非常简单地下载它们: +训练集和测试集都托管在 GitHub 上,因此我们可以通过 `wget` 命令非常轻易地下载它们: ```python !wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-train.json.gz !wget https://github.com/crux82/squad-it/raw/master/SQuAD_it-test.json.gz ``` -这将下载两个名为*SQuAD_it-train.json.gz* 和 *SQuAD_it-test.json.gz*的压缩文件, 我们可以用Linux的解压命令 `gzip`: +这将下载两个名为 `SQuAD_it-train.json.gz` 和 `SQuAD_it-test.json.gz` 的压缩文件,我们可以用 Linux 的 `gzip` 命令解压他们: ```python !gzip -dkv SQuAD_it-*.json.gz @@ -46,15 +46,15 @@ SQuAD_it-test.json.gz: 87.4% -- replaced with SQuAD_it-test.json SQuAD_it-train.json.gz: 82.2% -- replaced with SQuAD_it-train.json ``` -我们可以看到压缩文件已经被替换为SQuAD_it-train.json和SQuAD_it-test.json,并且数据以 JSON 格式存储。 +我们可以看到压缩文件已经被替换为 `SQuAD_it-train.json` 和 `SQuAD_it-test.json` ,并且数据以 JSON 格式存储。 -✎ 如果你想知道为什么上面的shell命令中哟与一个字符`!`,那是因为我们是在 Jupyter notebook 中运行它们。如果您想在终端中下载和解压缩数据集,只需删除前缀!即可。 +✏️ 如果你想知道为什么上面的 shell 命令中有一个 `!` ,那是因为我们现在是在 Jupyter notebook 中运行它们。如果你想在命令行中下载和解压缩数据集,只需删除前缀 `!` 即可。 -使用`load_dataset()`函数来加载JSON文件, 我们只需要知道我们是在处理普通的 JSON(类似于嵌套字典)还是 JSON 行(行分隔的 JSON)。像许多问答数据集一样, SQuAD-it 使用嵌套格式,所有文本都存储在 `data`文件中。这意味着我们可以通过指定参数`field`来加载数据集,如下所示: +当我们使用 `load_dataset()` 函数来加载 JSON 文件时,我们需要知道我们是在处理普通的 JSON(类似于嵌套字典)还是 JSON Lines(每一行都是一个 JSON)。像许多问答数据集一样,SQuAD-it 使用的是嵌套字典,所有文本都存储在 `data` 字段中。这意味着我们可以通过使用参数 `field` 来加载数据集,如下所示: ```py from datasets import load_dataset @@ -62,7 +62,7 @@ from datasets import load_dataset squad_it_dataset = load_dataset("json", data_files="SQuAD_it-train.json", field="data") ``` -默认情况下, 加载本地文件会创建一个带有`train`的`DatasetDict` 对象。 我们可以通过 `squad_it_dataset`查看: +默认情况下,加载本地文件会创建一个带有 `train` 标签的 `DatasetDict` 对象。我们可以在这里查看一下 `squad_it_dataset` 对象: ```py squad_it_dataset @@ -77,7 +77,7 @@ DatasetDict({ }) ``` -这向我们显示了与训练集相关联的行数和列名。我们可以通过索引到 `train` 查看示例,如下所示: +输出了与训练集的行数和列名。我们可以使用 `train` 标签来查看数据集中的一个示例,如下所示: ```py squad_it_dataset["train"][0] @@ -103,7 +103,7 @@ squad_it_dataset["train"][0] } ``` -很好, 我们已经加载了我们的第一个本地数据集! 但是, 虽然这对训练集有效, 但是我们真正想要的是包括 `train` 和 `test` 的 `DatasetDict` 对象。这样的话就可以使用 `Dataset.map()` 函数同时处理训练集和测试集。 为此, 我们提供参数`data_files`的字典,将每个分割名称映射到与该分割相关联的文件: +很好,我们已经加载了我们的第一个本地数据集!但是,也仅仅加载了训练集,我们真正想要的是包含 `train` 和 `test` 的 `DatasetDict` 对象。这样的话就可以使用 `Dataset.map()` 函数同时处理训练集和测试集。为此,我们向 `data_files` 参数输入一个字典,将数据集的标签名映射到相关联的文件: ```py data_files = {"train": "SQuAD_it-train.json", "test": "SQuAD_it-test.json"} @@ -124,28 +124,28 @@ DatasetDict({ }) ``` -这正是我们想要的。现在, 现在,我们可以应用各种预处理技术来清理数据、标记评论等。 +这正是我们想要的。现在,我们可以使用各种预处理技术来清洗数据、tokenize 评论等等。 - + -`load_dataset()`函数的`data_files`参数非常灵活并且可以是单个文件路径、文件路径列表或将分割后的名称映射到文件路径的字典。您还可以根据Unix shell使用的规则对与指定模式匹配的文件进行全局定位(例如,您可以通过设置'data_files=“*.JSON”'将目录中的所有JSON文件作为单个拆分进行全局定位)。有关更多详细信息,请参阅🤗Datasets 文档。 +`load_dataset()` 函数的 `data_files` 参数非常灵活:可以是单个文件路径、文件路径列表或者是标签映射到文件路径的字典。你还可以根据 Unix shell 的规则,对符合指定模式的文件进行批量匹配(例如,你可以通过设置 `data_files="*.JSON"` 匹配目录中所有的 JSON 文件)。有关`load_dataset()`更多详细信息,请参阅 [🤗Datasets 文档](https://huggingface.co/docs/datasets/v2.12.0/en/loading#local-and-remote-files) 。 -🤗 Datasets实际上支持输入文件的自动解压,所以我们可以跳过使用`gzip`,直接设置 `data_files`参数传递压缩文件: +🤗 Datasets 实际上支持自动解压输入文件,所以我们可以跳过使用 `gzip` ,直接将 `data_files` 参数设置为压缩文件: ```py data_files = {"train": "SQuAD_it-train.json.gz", "test": "SQuAD_it-test.json.gz"} squad_it_dataset = load_dataset("json", data_files=data_files, field="data") ``` -如果您不想手动解压缩许多 GZIP 文件,这会很有用。自动解压也适用于其他常见格式,如 ZIP 和 TAR,因此您只需将 `data_files` 设置为压缩文件所在的路径,你就可以开始了! +如果你不想手动解压缩许多 GZIP 文件,这会很有用。自动解压也支持于其他常见格式,如 ZIP 和 TAR,因此你只需将 `data_files` 设置为压缩文件所在的路径,接下来就交给🤗 Datasets 吧! -现在你知道如何在笔记本电脑或台式机上加载本地文件,让我们来看看加载远程文件。 +现在你知道如何在笔记本电脑或台式机上加载本地文件,让我们来看看如何加载远程文件。 ## 加载远程数据集 [[加载远程数据集]] -如果你在公司担任数据研究员或编码员,那么你要分析的数据集很有可能存储在某个远程服务器上。幸运的是,加载远程文件就像加载本地文件一样简单!我们没有提供本地文件的路径, 而是将`load_dataset()`的`data_files`参数指向存储远程文件的一个或多个URL。例如, 对于托管在 GitHub 上的 SQuAD-it 数据集, 我们可以将 `data_files` 指向 _SQuAD_it-*.json.gz_ 的网址,如下所示: +如果你在公司担任数据研究员或编程员,那么你要分析的数据集很有可能存储在某个远程服务器上。幸运的是,加载远程文件就像加载本地文件一样简单!我们只需要将 `load_dataset()` 的 `data_files` 参数指向存储远程文件的一个或多个 URL。例如,对于托管在 GitHub 上的 SQuAD-it 数据集,我们可以将 `data_files` 设置为指向 `SQuAD_it-*.json.gz` 的网址,如下所示: ```py url = "https://github.com/crux82/squad-it/raw/master/" @@ -156,12 +156,11 @@ data_files = { squad_it_dataset = load_dataset("json", data_files=data_files, field="data") ``` -这将返回和上面的本地例子相同的 `DatasetDict` 对象, 但省去了我们手动下载和解压 _SQuAD_it-*.json.gz_ 文件的步骤。这是我们对加载未托管在Hugging Face Hub的数据集的各种方法的总结。既然我们已经有了一个可以使用的数据集,让我们开始大展身手吧! +这将返回和上面的本地例子相同的 `DatasetDict` 对象,但省去了我们手动下载和解压 `SQuAD_it-*.json.gz` 文件的步骤。这是我们对加载未托管在 Hugging Face Hub 的数据集的各种方法的总结。既然我们已经有了一个可以使用的数据集,让我们开始大展身手吧! -✏️ **试试看!** 选择托管在GitHub或[UCI Machine Learning Repository](https://archive.ics.uci.edu/ml/index.php)上的另一个数据集并尝试使用上述技术在本地和远程加载它。另外,可以尝试加载CSV或者文本格式存储的数据集(有关这些格式的更多信息,请参阅[文档](https://huggingface.co/docs/datasets/loading#local-and-remote-files))。 +✏️ **试试看!** 选择托管在 GitHub 或 [UCI 机器学习仓库](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/3.mdx b/chapters/zh-CN/chapter5/3.mdx index 239780da7..163623d9f 100644 --- a/chapters/zh-CN/chapter5/3.mdx +++ b/chapters/zh-CN/chapter5/3.mdx @@ -1,4 +1,4 @@ -# 是时候来学一下切片了 [[是时候来学一下切片了]] +# 分割和整理数据 [[分割和整理数据]] -大多数情况下,您使用的数据都需根据模型所要求的输入进行清洗。在本节中,我们将探索 🤗 Datasets 提供的用于数据集清洗的各种功能。 +大多数情况下,你处理的数据并不能直接用于训练模型。在本节中,我们将探索🤗 Datasets 提供的各种功能,用于清洗你的数据集。 -## 切片与切分我们的数据 [[切片与切分我们的数据]] +## 分割和整理我们的数据 [[分割和整理我们的数据]] -与 Pandas 类似,🤗 Datasets 提供了几个函数来操作 **Dataset** 和 **DatasetDict** 对象。我们在[第三章](/course/chapter3)已经遇到了 **Dataset.map()** 方法,在本节中,我们将探索我们可以使用的其他功能。 +与 Pandas 类似,🤗 Datasets 提供了多个函数来操作 `Dataset` 和 `DatasetDict` 对象。我们在 [第三章](/course/chapter3) 已经遇到了 `Dataset.map()` 方法,在本节中,我们将探索一些其他可用的函数。 -对于这个例子,我们将使用托管在[加州大学欧文分校机器学习存储库](https://archive.ics.uci.edu/ml/index.php)的[药物审查数据集](https://archive.ics.uci.edu/ml/datasets/Drug+Review+Dataset+%28Drugs.com%29),其中包含患者对各种药物的评论,以及正在治疗的病情和患者满意度的 10 星评级。 +在本例中,我们将使用托管在 [加州大学欧文分校机器学习仓库](https://archive.ics.uci.edu/ml/index.php) 的 [药物审查数据集](https://archive.ics.uci.edu/ml/datasets/Drug+Review+Dataset+%28Drugs.com%29) ,其中包含患者对各种药物的评论,以及正在治疗的病情和患者满意度的 10 星评价。 -首先我们需要下载并提取数据,这可以通过 **wget** 和 **unzip** 命令: +首先我们需要下载并解压数据,可以通过 `wget` 和 `unzip` 命令: ```py !wget "https://archive.ics.uci.edu/ml/machine-learning-databases/00462/drugsCom_raw.zip" !unzip drugsCom_raw.zip ``` -由于 TSV 只是使用制表符而不是逗号作为分隔符的 CSV 变体,我们可以使用加载**csv**文件的**load_dataset()**函数并指定分隔符 示例如下: +由于 TSV 仅仅是 CSV 的一个变体,它使用制表符而不是逗号作为分隔符,我们可以使用加载 `csv` 文件的 `load_dataset()` 函数并指定分隔符,来加载这些文件: ```py from datasets import load_dataset data_files = {"train": "drugsComTrain_raw.tsv", "test": "drugsComTest_raw.tsv"} -# \t is the tab character in Python +# \t 在python中是制表符的意思 drug_dataset = load_dataset("csv", data_files=data_files, delimiter="\t") ``` -在进行任何类型的数据分析时,一个好的做法是抽取一个小的随机样本,以快速了解您正在处理的数据类型。在🤗数据集中,我们可以通过链接 **Dataset.shuffle()** 和 **Dataset.select()** 共同来完成抽取: +在进行数据分析时,获取一个小的随机样本以快速了解你正在处理数据的特点是一种好的实践。在🤗数据集中,我们可以通过链接 `Dataset.shuffle()` 和 `Dataset.select()` 函数创建一个随机的样本: ```py drug_sample = drug_dataset["train"].shuffle(seed=42).select(range(1000)) -# Peek at the first few examples +# 细看前几个例子 drug_sample[:3] ``` @@ -54,20 +54,21 @@ drug_sample[:3] 'usefulCount': [36, 13, 128]} ``` -请注意,出于可以复现的目的,我们已将在**Dataset.shuffle()**选取了固定的随机数种子。 **Dataset.select()** 需要一个可迭代的索引,所以我们已经通过了 **range(1000)** 从随机打乱的数据集中选取前 1,000 个示例。从抽取的数据中,我们已经可以看到我们数据集的一些特点: +请注意,出于可以复现的目的,我们已将在 `Dataset.shuffle()` 设定了固定的随机数种子。 `Dataset.select()` 需要一个可迭代的索引,所以我们传递了 `range(1000)` 从随机打乱的数据集中抽取前 1,000 个示例。从抽取的数据中,我们已经可以看到我们数据集中有一些特殊的地方: -* **Unnamed: 0**这列看起来很像每个患者的匿名 ID。 -* **condition** 这列包含有描述健康状况的标签。 -* 评论长短不一,混合有 Python 行分隔符 (**\r\n**) 以及 HTML 字符代码,如** &\#039;**。 +* `Unnamed: 0` 这列看起来很像每个患者的匿名 ID。 +* `condition` 列包含了大小写混合的标签。 +* 评论长短不一,混合有 Python 行分隔符 ( `\r\n` ) 以及 HTML 字符代码,如 `&\#039;` 。 -让我们看看我们如何使用 🤗 Datasets 来处理这些问题。为了验证**Unnamed: 0** 列存储的是患者 ID的猜想,我们可以使用 **Dataset.unique()** 函数来验证匿名ID 的数量是否与拆分后每部分中的行数匹配: + +让我们看看我们如何使用 🤗 Datasets 来处理这些问题。为了验证 `Unnamed: 0` 列存储的是患者 ID 的猜想,我们可以使用 `Dataset.unique()` 函数来验证匿名 ID 的数量是否与分割后每个分组中的行数匹配: ```py for split in drug_dataset.keys(): assert len(drug_dataset[split]) == len(drug_dataset[split].unique("Unnamed: 0")) ``` -这似乎证实了我们的假设,所以让我们把 **Unnamed: 0** 列重命名为患者的id。我们可以使用 **DatasetDict.rename_column()**函数一次性重命名DatasetDict中共有的列: +这似乎证实了我们的假设,所以让我们把 `Unnamed: 0` 列重命名为患者的 id。我们可以使用 `DatasetDict.rename_column()` 函数来一次性重命名两个分组: ```py drug_dataset = drug_dataset.rename_column( @@ -91,11 +92,11 @@ DatasetDict({ -✏️ **试试看!** 使用 `Dataset.unique()` 函数查找训练和测试集中满足某个条件的药物经过去重之后的数量。 +✏️ **试试看!** 使用 `Dataset.unique()` 函数查找训练和测试集中的特定药物和病症的数量。 -接下来,让我们使用 **Dataset.map()**标准化所有 **condition** 标签 .正如我们在[第三章](/course/chapter3)中所做的那样,我们可以定义一个简单的函数,可以将该函数应用于**drug_dataset** 拆分后每部分的所有行: +接下来,让我们使用 `Dataset.map()` 来规范所有的 `condition` 标签。正如我们在 [第三章](/course/chapter3) 中处理 tokenizer 一样,我们可以定义一个简单的函数,可以使用该函数 `drug_dataset` 处理每个分组的所有行: ```py def lowercase_condition(example): @@ -109,26 +110,26 @@ drug_dataset.map(lowercase_condition) AttributeError: 'NoneType' object has no attribute 'lower' ``` -哦不,我们的map功能遇到了问题!从错误中我们可以推断出 **condition** 列存在 **None** , 不能转换为小写,因为它们不是字符串。让我们使用 **Dataset.filter()** 删除这些行 ,其工作方式类似于 **Dataset.map()** 。例如: +哦不,我们的 map 函数遇到了问题!从错误中我们可以推断出 `condition` 列存在 `None` ,不能转换为小写,因为它们不是字符串。让我们使用 `Dataset.filter()` 删除这些行 其工作方式类似于 `Dataset.map()` 。例如: ```py def filter_nones(x): return x["condition"] is not None ``` -然后运行 **drug_dataset.filter(filter_nones)** ,我们可以在一行中使用lambda 函数.在 Python 中,lambda 函数是您无需明确命名即可使用的微函数(匿名函数)。它们一般采用如下形式: +然后运行 `drug_dataset.filter(filter_nones)` ,我们可以用 lambda 函数在一行代码完成这个任务。在 Pyhton 中,lambda 函数是你无需明确命名即可使用的微函数(匿名函数)。它们一般采用如下形式: ``` lambda : ``` -其中**lambda** 是 Python 的特殊[关键字](https://docs.python.org/3/reference/lexical_analysis.html#keywords), **arguments** 是以逗号进行分隔的函数输入的列表/集合, **expression** 代表您希望执行的操作。例如,我们可以定义一个简单的 lambda 函数来对一个数字进行平方,如下所示: +其中 `lambda` 是 Python 的特殊 [关键字](https://docs.python.org/3/reference/lexical_analysis.html#keywords) 之一, `arguments` 是以逗号进行分隔的函数参数的列表/集合, `expression` 代表你希望执行的操作。例如,我们可以定义一个简单的 lambda 函数来对一个数字进行平方,如下所示: ``` lambda x : x * x ``` -我们需要将要输入给这个函数值括在括号中: +我们需要将要输入放在括号中: ```py (lambda x: x * x)(3) @@ -138,7 +139,7 @@ lambda x : x * x 9 ``` -类似地,我们可以通过用逗号分隔多个参数来定义 lambda 函数。例如,我们可以按如下方式计算三角形的面积: +同样,我们可以通过使用逗号分隔来定义带有多个参数的 lambda 函数。例如,我们可以按如下方式计算三角形的面积: ```py (lambda base, height: 0.5 * base * height)(4, 8) @@ -148,17 +149,17 @@ lambda x : x * x 16.0 ``` -当您想定义小型、一次性使用的函数时,Lambda 函数非常方便(有关它们的更多信息,我们建议阅读安德烈·布尔高写的[真正的Python教程](https://realpython.com/python-lambda/))。在🤗 Datasets 中,我们可以使用 lambda 函数来定义简单的映射和过滤操作,所以让我们使用这个技巧来消除我们数据集中的 **None** 条目: +当你想定义小型、一次性使用的函数时,lambda 函数非常方便(有关它们的更多信息,我们建议阅读 Andre Burgaud 写的 [真正的Python教程](https://realpython.com/python-lambda/) )。在🤗 Datasets 中,我们可以使用 lambda 函数来定义简单的映射和过滤操作,所以让我们使用这个技巧来删除我们数据集中的所有`condition` 为 `None`的记录: ```py drug_dataset = drug_dataset.filter(lambda x: x["condition"] is not None) -``` +``` -当 **None** 条目已删除,我们可以标准化我们的 **condition** 列: +含有`None`的积累删除之后,我们可以规范我们的 `condition` 列: ```py drug_dataset = drug_dataset.map(lowercase_condition) -# Check that lowercasing worked +# 检查一下转换后的结果 drug_dataset["train"]["condition"][:3] ``` @@ -168,22 +169,22 @@ drug_dataset["train"]["condition"][:3] 有用!现在我们已经清理了标签,让我们来看看清洗后的评论文本。 -## 创建新的数据列 [[创建新的数据列]] +## 创建新的列 [[创建新的列]] -每当您处理客户评论时,一个好的做法是检查每个评论中的字数。评论可能只是一个词,比如“太棒了!”或包含数千字的完整文章,根据实际的情况,您需要以不同的方式处理这些极端情况。为了计算每条评论中的单词数,我们将使用基于空格分割每个文本的粗略方法。 +每当我们处理客户评论时,一个好的习惯是检查评论的字数的分布。评论可能只是一个词,比如“太棒了!”或包含数千字的完整文章。在不同的使用场景,你需要以不同的方式处理这些极端情况。为了计算每条评论中的单词数,我们将使用空格分割每个文本进行粗略统计。 -让我们定义一个简单的函数来计算每条评论中的单词数: +让我们定义一个简单的函数,计算每条评论的字数: ```py def compute_review_length(example): return {"review_length": len(example["review"].split())} ``` -与我们的 `lowercase_condition()` 函数不同,`compute_review_length()` 返回一个字典,其键与数据集中的列名之一不对应。 在这种情况下,当 `compute_review_length()` 传递给 `Dataset.map()` 时,它将应用于数据集中的所有行以创建新的 `review_length` 列: +不同于我们的 `lowercase_condition()` 函数, `compute_review_length()` 返回一个字典,其键并不对应数据集中的某一列名称。在这种情况下,当 `compute_review_length()` 传递给 `Dataset.map()` 时,它将处理数据集中的所有行,最后返回值会创建一个新的 `review_length` 列: ```py drug_dataset = drug_dataset.map(compute_review_length) -# Inspect the first training example +# 检查第一个训练样例 drug_dataset["train"][0] ``` @@ -198,7 +199,7 @@ drug_dataset["train"][0] 'review_length': 17} ``` -正如预期的那样,我们可以看到一个 **review_length** 列已添加到我们的训练集中。我们可以使用 **Dataset.sort()**对这个新列进行排序,然后查看极端长度的评论的样子: +正如预期的那样,我们可以看到一个 `review_length` 列已添加到我们的训练集中。我们可以使用 `Dataset.sort()` 对这个新列进行排序,然后查看一下极端长度的评论是什么样的: ```py drug_dataset["train"].sort("review_length")[:3] @@ -215,15 +216,15 @@ drug_dataset["train"].sort("review_length")[:3] 'review_length': [1, 1, 1]} ``` -正如我们所猜想的那样,一些评论只包含一个词,虽然这对于情感分析来说可能没问题,但如果我们想要预测病情,这些评论可能并不适合。 +正如我们所猜想的那样,有些评论只包含一个词,虽然这对于情感分析任务来说还可以接受,但如果我们想要预测病情,那么它所提供的信息就不够丰富了。 -🙋向数据集添加新列的另一种方法是使用函数Dataset.add_column() 。这允许您输入Python 列表或 NumPy,在不适合使用Dataset.map()情况下可以很方便。 +🙋向数据集添加新列的另一种方法是使用函数 `Dataset.add_column()` ,在使用它时你可以通过 Python 列表或 NumPy 数组的方式提供数据,在不适合使用 `Dataset.map()` 情况下可以很方便。 -让我们使用 **Dataset.filter()** 功能来删除包含少于 30 个单词的评论。与我们对 **condition** 列的处理相似,我们可以通过选取评论的长度高于此阈值来过滤掉非常短的评论: +让我们使用 `Dataset.filter()` 功能来删除包含少于 30 个单词的评论。这与我们过滤 `condition` 列的处理方式相似,我们可以通过设定评论长度的最小阈值,筛选出过短的评论: ```py drug_dataset = drug_dataset.filter(lambda x: x["review_length"] > 30) @@ -234,15 +235,15 @@ print(drug_dataset.num_rows) {'train': 138514, 'test': 46108} ``` -如您所见,这已经从我们的原始训练和测试集中删除了大约 15% 的评论。 +如你所见,这个操作从我们的原始训练和测试集中删除了大约 15% 的评论。 -✏️ 试试看!使用 Dataset.sort() 函数查看单词数最多的评论。请参阅文档以了解您需要使用哪个参数按长度降序对评论进行排序。 +✏️ **试试看!**使用 `Dataset.sort()` 函数查看单词数最多的评论。你可以参阅 [文档](https://huggingface.co/docs/datasets/package_reference/main_classes.html#datasets.Dataset.sort) 了解如何按照评论的长度降序排序。 -我们需要处理的最后一件事是评论中是否存在 HTML 字符代码。我们可以使用 Python 的**html**模块取消这些字符的转义,如下所示: +我们需要处理的最后一件事是处理评论中的 HTML 字符。我们可以使用 Python 的 `html` 模块来解码这些字符,如下所示: ```py import html @@ -255,19 +256,19 @@ html.unescape(text) "I'm a transformer called BERT" ``` -我们将使用 **Dataset.map()** 对我们语料库中的所有 HTML 字符进行转义: +我们将使用 `Dataset.map()` 对我们语料库中的所有 HTML 字符进行解码: ```python drug_dataset = drug_dataset.map(lambda x: {"review": html.unescape(x["review"])}) ``` -如您所见, **Dataset.map()** 方法对于处理数据非常有用——在示例中仅仅是浅尝辄止就有很大的收获! +如你所见, `Dataset.map()` 方法对于处理数据非常有用,即使我们还没有完全了解它的所有功能! -## map() 方法的超级加速 [[map() 方法的超级加速]] +## `map()` 方法的超级加速 [[`map()` 方法的超级加速]] -**Dataset.map()** 方法有一个 **batched** 参数,如果设置为 **True** , map 函数将会分批执行所需要进行的操作(批量大小是可配置的,但默认为 1,000)。例如,之前对所有 HTML 进行转义的 map 函数运行需要一些时间(您可以从进度条中读取所用时间)。我们可以通过使用列表推导同时处理多个元素来加快速度。 +`Dataset.map()` 方法有一个 `batched` 参数,如果设置为 `True` ,map 函数将会分批执行所需要进行的操作(批量大小是可配置的,但默认为 1,000)。例如,之前对所有 HTML 进行解码的 map 函数运行需要一些时间(你可以从进度条中看到所需的时间)。我们可以通过使用列表推导同时处理多个元素来加速。 -当您在使用 **Dataset.map()**函数时指定 **batched=True**。该函数会接收一个包含数据集字段的字典,每个值都是一个列表,而不仅仅是单个值。**Dataset.map()** 的返回值应该是相同的:一个包含我们想要更新或添加到数据集中的字段的字典,字典的键是要添加的字段,字典的值是结果的列表。例如,这是使用 **batched=True**对所有 HTML 字符进行转义的方法 : +当你在使用 `Dataset.map()` 函数时设定 `batched=True` 。该函数需要接收一个包含数据集字段的字典,字典的值是一个列表。例如,这是使用 `batched=True` 对所有 HTML 字符进行解码的方法 ```python new_drug_dataset = drug_dataset.map( @@ -275,9 +276,9 @@ new_drug_dataset = drug_dataset.map( ) ``` -如果您在笔记本中运行此代码,您会看到此命令的执行速度比前一个命令快得多。这不是因为我们的评论已经是处理过的——如果你重新执行上一节的指令(没有 **batched=True** ),它将花费与以前相同的时间。这是因为列表推导式通常比在同一代码中用 **for** 循环执行相同的代码更快,并且我们还通过同时访问多个元素而不是一个一个来处理来提高处理的速度。 +如果你在笔记本中运行此代码,你会看到此命令的执行速度比前一个命令快得多。这不是因为我们的评论已经是处理过的——如果你重新执行上一节的指令(没有 `batched=True` ),它将花费与之前相同的时间。这是因为列表推导式通常比在同一代码中用 `for` 循环执行相同的代码更快,并且我们还通过同时访问多个元素而不是一个一个来处理来提高处理的速度。 -在[第六章](/course/chapter6)我们将遇到的“快速”标记器,它可以快速标记大文本列表。使用 **Dataset.map()** 和 **batched=True** 是加速的关键。例如,要使用快速标记器标记所有药物评论,我们可以使用这样的函数: +在 [第六章](/course/chapter6) 我们将遇到的“快速” tokenizer 它可以快速对长文本列表进行 tokenize。使用 `Dataset.map()` 搭配 `batched=True` 参数是加速的关键。例如,要使用快速 tokenizer 对所有药物评论 tokenize,我们可以使用如下的函数: ```python from transformers import AutoTokenizer @@ -289,32 +290,30 @@ def tokenize_function(examples): return tokenizer(examples["review"], truncation=True) ``` -正如你在[第三章](/course/chapter3)所看到的,我们原本就可以将一个或多个示例传递给分词器,因此在**batched=True**是一个非必须的选项.让我们借此机会比较不同选项的性能。在笔记本中,您可以在您要测量的代码行之前添加 **%time**来测试改行运行所消耗的时间: +正如我们在 [第三章](/course/chapter3) 所看到的,我们原本就可以将一个或多个示例传递给 tokenizer,因此在 `batched=True` 是一个非必须的选项。让我们借此机会比较不同选项的性能。在 notebook 中,你可以在你要测量的代码行之前添加 `%time` 来记录该行运行所消耗的时间: ```python no-format %time tokenized_dataset = drug_dataset.map(tokenize_function, batched=True) ``` -您还可以通过将整个单元格计时 **%%time** 在单元格的开头。在我们执行此操作的硬件上,该指令显示 10.8 秒(这是写在“Wall time”之后的数字)。 +你也可以将 `%%time` 放置在单元格开头来统计整个单元格的执行时间。在我们的硬件上,该指令显示 10.8 秒(这就是真正(Wall time)的执行时间)。 -✏️ **试试看!** 使用和不使用 `batched=True` 执行相同的指令,然后使用慢速标记器尝试(在 `AutoTokenizer.from_pretrained()` 方法中添加 `use_fast=False`),这样你就可以看看在你的电脑上它需要多长的时间。 +✏️ **试试看!** 在有和无 `batched=True` 的情况下执行相同的指令,然后试试慢速 tokenizer (在 `AutoTokenizer.from_pretrained()` 方法中添加 `use_fast=False` ),这样你就可以测试一下在你的电脑上它需要多长的时间。 -以下是我们在使用和不使用批处理时使用快速和慢速分词器获得的结果: +以下是我们在使用和不使用批处理时使用快速和慢速 tokenizer 获得的结果: -Options | Fast tokenizer | Slow tokenizer +选项 | 快速 tokenizer | 慢速 tokenizer :--------------:|:--------------:|:-------------: -`batched=True` | 10.8s | 4min41s +`batched=True` | 10.8s | 4min41s `batched=False` | 59.2s | 5min3s -这意味着使用带有 **batched=True** 选项比没有批处理的慢选项快 30 倍——这真是太棒了!这就是为什么**AutoTokenizer** 的默认设置是**use_fast=True**的主要原因 (以及为什么它们被称为“快速”)。他们能够实现这样的加速,因为在底层的标记化代码是在 Rust 中执行的,Rust 是一种可以轻松并行化执行的语言。 - -并行化也是快速标记器通过批处理实现近 6 倍加速的原因:单个标记化操作是不能并行的,但是当您想同时标记大量文本时,您可以将执行拆分为多个进程,每个进程都对自己的文本负责。 +这意味着使用快速 tokenizer 配合 `batched=True` 选项比没有批处理的慢速版本快 30 倍——这真的太 Amazing 了!这就是为什么在使用 `AutoTokenizer` 时,将会默认使用 `use_fast=True` 的主要原因 (以及为什么它们被称为“快速”的原因)。他们能够实现这样的加速,因为在底层的 tokenize 代码是在 Rust 中执行的,Rust 是一种可以易于并行化执行的语言。 -**Dataset.map()** 也有一些自己的并行化能力。由于它们不受 Rust 的支持,因此慢速分词器的速度赶不上快速分词器,但它们仍然会更快一些(尤其是当您使用没有快速版本的分词器时)。要启用多处理,请在**Dataset.map()**时使用 **num_proc** 参数并指定要在调用中使用的进程数 : +并行化也是快速 tokenizer 通过批处理实现近 6 倍加速的原因:单个 tokenize 操作是不能并行的,但是当你想同时对大量文本进行 tokenize 时,你可以将执行过程拆分为多个进程,每个进程负责处理自己的文本。 `Dataset.map()` 也有一些自己的并行化能力。尽管它们没有 Rust 提供支持,但它们仍然可以帮助慢速 tokenizer 加速(尤其是当你使用的 tokenizer 没有快速版本时)。要启用多进程处理,请在调用 `Dataset.map()` 时使用 `num_proc` 参数并指定要在调用中使用的进程数 ```py slow_tokenizer = AutoTokenizer.from_pretrained("bert-base-cased", use_fast=False) @@ -327,32 +326,32 @@ def slow_tokenize_function(examples): tokenized_dataset = drug_dataset.map(slow_tokenize_function, batched=True, num_proc=8) ``` -您可以对处理的时间进行一些试验,以确定要使用的最佳进程数;在我们的例子中,8 似乎产生了最好的速度增益。以下是我们在使用和不使用多处理时所需要的时间: +你可以对处理进行一些计时的试验,以确定最佳进程数;在我们的例子中,8 似乎产生了最好的速度增益。以下是我们在有无多进程处理的情况下,得到的结果: -Options | Fast tokenizer | Slow tokenizer -:--------------:|:--------------:|:-------------: -`batched=True` | 10.8s | 4min41s -`batched=False` | 59.2s | 5min3s -`batched=True`, `num_proc=8` | 6.52s | 41.3s -`batched=False`, `num_proc=8` | 9.49s | 45.2s +选项 | 快速 tokenizer | 慢速 tokenizer +:--------------:|:--------------:|:-------------: +`batched=True` | 10.8s | 4min41s +`batched=False` | 59.2s | 5min3s +`batched=True` , `num_proc=8` | 6.52s | 41.3s +`batched=False` , `num_proc=8` | 9.49s | 45.2s -对于慢速分词器来说,这些结果要合理得多,但快速分词器的性能也得到了显着提高。但是请注意,情况并非总是如此——除了 **num_proc=8**,我们的测试表明,使用**batched=True**而不带有**num_proc**参数的选项处理起来更快。通常,我们不建议将 Python 多线程处理用于具有**batched=True**功能的快速标记器 . +这个结果对于慢速分词器来说是更加友好了,但快速分词器的性能也得到了显著提升。但是请注意,情况并非总是如此—对于 `num_proc` 的其他值,在我们的测试中,使用 `batched=True` 而不带有 `num_proc` 参数的选项处理起来更快。总的来说,我们并不推荐在快速 tokenizer 和 `batched=True` 的情况下使用 Python 的多进程处理。 -使用num_proc以加快处理速度通常是一个好主意,只要您使用的函数还没有自己带有的进行某种多进程处理的方法。 +通常来说,使用 `num_proc` 以加快处理速度通常是一个好主意,只要你使用的函数本身没有进行某种类型的多进程处理。 -将所有这些功能浓缩到一个方法中已经非常了不起,但还有更多!使用 **Dataset.map()** 和 **batched=True** 您可以更改数据集中的元素数量。当你想从一个例子中创建几个训练特征时,这是非常有用的。我们将在[第七章](/course/chapter7).中进行的几个NLP任务的预处理中使用到这个功能,它非常便利。 +将所有这些功能浓缩到一个方法中已经非常了不起,但是还有更多!使用 `Dataset.map()` 和 `batched=True` 你可以更改数据集中的元素数量。当你想从一个样本中创建几个训练特征时,这是非常有用的。我们将在 [第七章](/course/chapter7) 中几个 NLP 任务的预处理中使用到这个功能,它非常便捷。 -💡在机器学习中,一个例子通常可以为我们的模型提供一组特征。在某些情况下,这些特征会储存在数据集的几个列,但在其他情况下(例如此处的例子和用于问答的数据),可以从单个示例的一列中提取多个特征 +💡在机器学习中,一个样本通常可以为我们的模型提供一组特征。在某些情况下,这组特征会储存在数据集的几个列,但在某些情况下(例如此处的例子和用于问答的数据),可以从单个样本的那一列中提取多个特征。 -让我们来看看它是如何工作的!在这里,我们将标记化我们的示例并将最大截断长度设置128,但我们将要求标记器返回全部文本块,而不仅仅是第一个。这可以用 **return_overflowing_tokens=True** : +让我们来看看从一列中提取多个特征是如何实现的!在这里,我们将对我们的样本进行 tokenize 并将最大截断长度设置为 128,并且我们将要求 tokenizer 返回全部文本块,而不仅仅是第一个。这可以通过设置 `return_overflowing_tokens=True` 来实现: ```py def tokenize_and_split(examples): @@ -364,7 +363,7 @@ def tokenize_and_split(examples): ) ``` -在使用**Dataset.map()** 正式在整个数据集上开始处理之前让我们先在一个例子上测试一下: +在使用 `Dataset.map()` 正式开始处理整个数据集之前,让我们先在一个样本上测试一下: ```py result = tokenize_and_split(drug_dataset["train"][0]) @@ -375,7 +374,7 @@ result = tokenize_and_split(drug_dataset["train"][0]) [128, 49] ``` -瞧!我们在训练集中的第一个示例变成了两个特征,因为它被标记为超过我们指定的最大截断长度,因此结果被截成了两段:第一段长度为 128 ,第二段长度为 49 。现在让我们对所有元素执行此操作数据集! +瞧!我们在训练集中的第一个样本变成了两个特征,因为它超过了我们指定的最大截断长度,因此被截成了两段:第一段长度为 128 第二段长度为 49 现在让我们对数据集的所有样本执行此操作! ```py tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) @@ -385,9 +384,9 @@ tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) ArrowInvalid: Column 1 named condition expected length 1463 but got length 1000 ``` -不好了!它没有起作用!为什么呢?查看错误消息会给我们一个线索:列的长度不匹配,一列长度为 1,463,另一列长度为 1,000。1,000行的"review"给出了 1,463 行的新特征,导致和原本的1000行数据不匹配。 +不好了!这并没有成功!为什么呢?查看错误消息会给我们一个线索:列的长度不匹配,一列长度为 1,463,另一列长度为 1,000。1,000 行的“重新”生成了 1,463 行的新特征,导致和原本的 1000 行的长度不匹配。 -问题出在我们试图混合两个不同大小的不同数据集: **drug_dataset** 列将有一定数量的元素(我们错误中的 1,000),但是我们正在构建**tokenized_dataset** 将有更多的元素(错误消息中的 1,463)。这不适用于 **Dataset** ,因此我们需要从旧数据集中删除列或使它们的大小与新数据集中的大小相同。我们可以用 **remove_columns** 参数: +问题出在我们试图混合两个长度不同的数据集: `drug_dataset` 列将有 1000 个样本,但是我们正在构建 `tokenized_dataset` 列将有 1,463 个样本(因为我们使用 `return_overflowing_tokens=True` 将长评论分词成了多个样本)。这对 `Dataset` 来说不可行,所以我们需要要么删除旧数据集的列,要么使它们与新数据集中的尺寸相同。我们可以使用 `remove_columns` 参数来实现前者: ```py tokenized_dataset = drug_dataset.map( @@ -395,7 +394,7 @@ tokenized_dataset = drug_dataset.map( ) ``` -现在这个过程没有错误。我们可以通过比较长度来检查新数据集的元素是否比原始数据集多得多: +现在这个过程没有错误。我们可以通过比较长度来检查我们的新数据集是否比原始数据集有更多的元素: ```py len(tokenized_dataset["train"]), len(drug_dataset["train"]) @@ -405,7 +404,7 @@ len(tokenized_dataset["train"]), len(drug_dataset["train"]) (206772, 138514) ``` -我们提到我们还可以通过使旧列与新列的大小相同来处理长度不匹配的问题。为此,我们可以使用 **overflow_to_sample_mapping** 字段,当我们设置**return_overflowing_tokens=True** .它为我们提供了特征到它所产生的样本的映射。使用这个,我们可以将原始数据集中的每个键关联到一个合适大小的值列表中,通过遍历所有的数据来生成新特性: +我们也可以通过使旧列与新列保持相同大小来处理不匹配长度的问题。为此,当我们设置 `return_overflowing_tokens=True` 时,可以使用 `overflow_to_sample_mapping` 字段。它给出了新特征索引到它源自的样本索引的映射。使用这个,我们可以将原始数据集中的每个键关联到一个合适大小的值列表中,通过遍历所有的数据来生成新特性: ```py def tokenize_and_split(examples): @@ -415,14 +414,14 @@ def tokenize_and_split(examples): max_length=128, return_overflowing_tokens=True, ) - # Extract mapping between new and old indices + # 提取新旧索引之间的映射 sample_map = result.pop("overflow_to_sample_mapping") for key, values in examples.items(): result[key] = [values[i] for i in sample_map] return result ``` -我们可以使用**Dataset.map()**来进行批处理,这样无需我们删除旧列: +可以看到它可以与 `Dataset.map()` 一起协作,无需我们删除旧列: ```py tokenized_dataset = drug_dataset.map(tokenize_and_split, batched=True) @@ -442,21 +441,21 @@ DatasetDict({ }) ``` -我们获得了与以前相同数量的训练特征,但在这里我们保留了所有旧字段。如果您在使用模型计算之后需要它们进行一些后处理,您可能需要使用这种方法。 +我们获得了与之前数量相同的训练特征,并且在这里我们保留了所有旧字段。如果你在使用模型计算之后需要它们进行一些后续处理,你可能需要使用这种方法。 -您现在已经了解了 🤗 Datasets如何以各种方式用于预处理数据集。虽然🤗 Datasets 的处理功能会覆盖你大部分的模型训练需求,有时您可能需要切换到 Pandas 以使用更强大的功能,例如 **DataFrame.groupby()** 或用于可视化的高级 API。幸运的是,🤗 Datasets旨在与 Pandas、NumPy、PyTorch、TensorFlow 和 JAX 等库可以相互转换。让我们来看看这是如何工作的。 +你现在已经了解了如何使用 🤗 Datasets 以各种方式用于预处理数据集。虽然🤗 Datasets 的处理功能会覆盖你大部分的模型训练需求,有时你可能需要切换到 Pandas 以使用更强大的功能,例如 `DataFrame.groupby()` 或用于可视化的高级 API。幸运的是,🤗 Datasets 设计宗旨就是与 Pandas、NumPy、PyTorch、TensorFlow 和 JAX 等库可以相互转换。让我们来看看这是如何实现的。 -## `🤗 Datasets 和 DataFrames 的相互转换 [[`🤗 Datasets 和 DataFrames 的相互转换]] +## 🤗 Datasets 和 DataFrames 的相互转换 [[ 🤗 Datasets 和 DataFrames 的相互转换]] -为了实现各种第三方库之间的转换,🤗 Datasets 提供了一个 **Dataset.set_format()** 功能。此功能可以通过仅更改输出格式的,轻松切换到另一种格式,而不会影响底层数据格式,即 Apache Arrow。格式化会在数据本身上进行。为了演示,让我们将数据集转换为 Pandas: +为了实现各种第三方库之间的转换,🤗 Datasets 提供了一个 `Dataset.set_format()` 函数。此函数可以通过仅更改输出格式的,轻松切换到另一种格式,而不会影响底层数据格式(以 Apache Arrow 方式进行存储)。为了演示,让我们把数据集转换为 Pandas: ```py drug_dataset.set_format("pandas") ``` -现在,当我们访问数据集的元素时,我们会得到一个 **pandas.DataFrame** 而不是字典: +现在,当我们访问数据集的元素时,我们会得到一个 `pandas.DataFrame` 而不是字典: ```py drug_dataset["train"][:3] @@ -482,7 +481,7 @@ drug_dataset["train"][:3] 95260 Guanfacine adhd - "My son is halfway through his fourth week of Intuniv..." + "My son is halfway through his fourth week of Intuniv." 8.0 April 27, 2010 192 @@ -493,7 +492,7 @@ drug_dataset["train"][:3] 92703 Lybrel birth control - "I used to take another oral contraceptive, which had 21 pill cycle, and was very happy- very light periods, max 5 days, no other side effects..." + "I used to take another oral contraceptive, which had 21 pill cycle, and was very happy- very light periods, max 5 days, no other side effects." 5.0 December 14, 2009 17 @@ -504,7 +503,7 @@ drug_dataset["train"][:3] 138000 Ortho Evra birth control - "This is my first time using any form of birth control..." + "This is my first time using any form of birth control." 8.0 November 3, 2015 10 @@ -513,7 +512,7 @@ drug_dataset["train"][:3] -让我们创建一个 **pandas.DataFrame** 来选择 **drug_dataset[train]** 的所有元素: +接下来我们从数据集中选择 `drug_dataset[train]` 的所有数据来得到训练集数据: ```py train_df = drug_dataset["train"][:] @@ -521,12 +520,12 @@ train_df = drug_dataset["train"][:] -🚨 在底层,`Dataset.set_format()` 改变了数据集的 `__getitem__()` dunder 方法的返回格式。 这意味着当我们想从 `"pandas"` 格式的 `Dataset` 中创建像 `train_df` 这样的新对象时,我们需要对整个数据集进行切片以获得 `pandas.DataFrame`。 无论输出格式如何,您都可以自己验证 `drug_dataset["train"]` 的类型依然还是 `Dataset`。 +🚨 实际上, `Dataset.set_format()` 仅仅改变了数据集的 `__getitem__()` 方法的返回格式。这意味着当我们想从 `"pandas"` 格式的 `Dataset` 中创建像 `train_df` 这样的新对象时,我们需要对整个数据集进行切片([:])才可以获得 `pandas.DataFrame` 对象。无论输出格式如何,你都可以自己验证 `drug_dataset["train"]` 的类型依然还是 `Dataset` 。 -从这里我们可以使用我们想要的所有 Pandas 功能。例如,我们可以通过花式链接来计算 **condition**类之间的分布 : +有了这个基础,我们可以使用我们想要的所有 Pandas 功能。例如,我们可以巧妙地链式操作,来计算 `condition` 列中不同类别的分布 ```py frequencies = ( @@ -577,7 +576,7 @@ frequencies.head() -一旦我们完成了 Pandas 分析,我们总是通过使用对象 **Dataset.from_pandas()**方法可以创建一个新的 **Dataset** 如下: +当我们完成了 Pandas 分析之后,我们可以使用对象 `Dataset.from_pandas()` 方法可以创建一个新的 `Dataset` 对象,如下所示: ```py @@ -596,11 +595,11 @@ Dataset({ -✏️ **试试看!** 计算每种药物的平均评级并将结果存储在一个新的Dataset. +✏️**试试看!**计算每种药物的平均评分并将结果存储在一个新的 Dataset 中。 -我们对 🤗 Datasets中可用的各种预处理技术的介绍到此结束。在最后一部分,让我们创建一个验证集来准备用于训练分类器的数据集。在此之前,我们将输出格式 **drug_dataset** 从 **pandas**重置到 **arrow** : +到此为止,我们对🤗 Datasets 中可用的各种预处理技术的介绍就结束了。在本节的最后一部分,让我们为训练分类器创建一个验证集。在此之前,让我们将输出格式 `drug_dataset` 从 `pandas` 重置到 `arrow` : ```python drug_dataset.reset_format() @@ -608,15 +607,15 @@ drug_dataset.reset_format() ## 创建验证集 [[创建验证集]] -尽管我们有一个可以用于评估的测试集,但在开发过程中保持测试集不变并创建一个单独的验证集是一个很好的做法。一旦您对模型在测试集上的表现感到满意,您就可以对验证集进行最终的检查。此过程有助于降低您过拟合测试集并部署在现实世界数据上失败的模型的风险。 +尽管我们有一个可以用于评估的测试集,但在开发过程中保持测试集不变并创建一个单独的验证集是一个很好的做法。一旦你对模型在测试集上的表现感到满意,你就可以使用验证集进行最终的检查。此过程有助于降低你过拟合测试集和部署在现实世界数据上失败的模型的风险。 -🤗 Datasets提供了一个基于**scikit-learn**的经典方法**Dataset.train_test_split()** .让我们用它把我们的训练集分成 **train** 和 **validation** (为了可以复现,我们将设置**seed**的值为一个常量): +🤗 Datasets 提供了一个基于 `scikit-learn` 的经典方法: `Dataset.train_test_split()` 。让我们用它把我们的训练集分成 `train` 和 `validation` (为了可以复现,我们将设置 `seed` 的值为一个常量): ```py drug_dataset_clean = drug_dataset["train"].train_test_split(train_size=0.8, seed=42) -# Rename the default "test" split to "validation" +# 将默认的 "test" 部分重命名为 "validation" drug_dataset_clean["validation"] = drug_dataset_clean.pop("test") -# Add the "test" set to our `DatasetDict` +# 将 "test" 部分添加到我们的 `DatasetDict` 中 drug_dataset_clean["test"] = drug_dataset["test"] drug_dataset_clean ``` @@ -638,19 +637,19 @@ DatasetDict({ }) ``` -太好了,我们现在已经准备好了一个数据集,可以用来训练一些模型了!在[第五节]](/course/chapter5/5)我们将向您展示如何将数据集上传到 Hugging Face Hub,但现在让我们查看在本地计算机上保存数据集的几种方法。 +太好了,我们现在已经准备好了一个适合训练模型的数据集了!在 [第五节](/course/chapter5/5) 我们将向你展示如何将数据集上传到 Hugging Face Hub,现在让我们先结束我们的分析,看一看在本地计算机上保存数据集的几种方法。 ## 保存数据集 [[保存数据集]] -虽然 🤗 Datasets 会缓存每个下载的数据集和对它执行的操作,但有时你会想要将数据集保存到磁盘(例如,以防缓存被删除)。如下表所示,🤗 Datasets 提供了三个主要功能来以不同的格式保存您的数据集: +虽然 🤗 Datasets 会缓存每个下载的数据集和对它执行的操作,但有时你会想要将数据集保存到磁盘(比如,以防缓存被删除)。如下表所示,🤗 Datasets 提供了三个主要函数来以不同的格式保存你的数据集: | 数据格式 | 对应的方法 | | :---------: | :--------------------: | | Arrow | `Dataset.save_to_disk()` | -| CSV | `Dataset.to_csv()` | -| JSON | `Dataset.to_json()` | +| CSV | `Dataset.to_csv()` | +| JSON | `Dataset.to_json()` | 例如,让我们以 Arrow 格式保存我们清洗过的数据集: @@ -679,9 +678,9 @@ drug-reviews/ └── state.json ``` -在那里我们可以看到每个部分.arrow表,以及一些元数据数据集信息.json和状态文件保存在一起.您可以将 Arrow 格式视为一个精美的列和行的表格,它针对构建处理和传输大型数据集的高性能应用程序进行了优化。 +其中,我们可以看到,每个部分都有 `dataset.arrow` 表,以及保存元数据的 `dataset_info.json` 和 `state.json` 。你可以将 Arrow 格式视为一个优化过的列和行的精美表格,它针对构建处理和传输大型数据集的高性能应用程序进行了优化。 -保存数据集后,我们可以使用 **load_from_disk()** 功能从磁盘读取数据如下: +保存数据集后,我们可以使用 `load_from_disk()` 功能从磁盘读取数据: ```py from datasets import load_from_disk @@ -707,14 +706,14 @@ DatasetDict({ }) ``` -对于 CSV 和 JSON 格式,我们必须将每个部分存储为单独的文件。一种方法是迭代**DatasetDict**中的键和值 : +对于 CSV 和 JSON 格式,我们必须将每个部分存储为单独的文件。一种方法是遍历 `DatasetDict` 中的键和值 ```py for split, dataset in drug_dataset_clean.items(): dataset.to_json(f"drug-reviews-{split}.jsonl") ``` -这将保存每个拆分都是[JSON的标准格式](https://jsonlines.org),其中数据集中的每一行都存储为一行 JSON。这是第一个示例: +这将把每个部分保存为 [JSON Lines格式](https://jsonlines.org) ,其中数据集中的每一行都存储为一行 JSON。下面是第一个例子的样子: ```py !head -n 1 drug-reviews-train.jsonl @@ -724,7 +723,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} ``` -然后我们可以使用[第二节](/course/chapter5/2)学过的技术加载 JSON 文件如下: +然后我们可以使用 [第二节](/course/chapter5/2) 中的技巧,按如下所示加载 JSON 文件 ```py data_files = { @@ -735,9 +734,9 @@ data_files = { drug_dataset_reloaded = load_dataset("json", data_files=data_files) ``` -这就是我们探索 🤗 Datasets 的旅程!现在我们有了一个清洗过的数据集,以下是您可以尝试的一些想法: +至此,我们对使用🤗 Datasets 进行数据整理的探索就此结束!现在我们有了一个清洗过的数据集,以下是你可以尝试的一些想法: -1. 使用[第3章](/course/chapter3)的技术来训练一个分类器,它可以根据药物评论预测病人的情况。 -2. 使用 [Chapter 1](/course/chapter1) 中的“summarization”管道生成评论摘要。 +1. 使用 [第三章](/course/chapter3) 的技术来训练一个分类器,它能够基于药品评价预测患者的病情。 +2. 使用 [第一章](/course/chapter1) 中的 `summarization` 管道生成评论的摘要。 -接下来,我们将看看 🤗 Datasets如何使您能够在不撑爆笔记本电脑内存的情况下处理庞大的数据集! \ No newline at end of file +接下来,我们将看看 🤗 Datasets 如何使你能够在不撑爆笔记本电脑内存的情况下处理庞大的数据集! \ No newline at end of file diff --git a/chapters/zh-CN/chapter5/4.mdx b/chapters/zh-CN/chapter5/4.mdx index bf34117ff..8625c67ae 100644 --- a/chapters/zh-CN/chapter5/4.mdx +++ b/chapters/zh-CN/chapter5/4.mdx @@ -1,4 +1,4 @@ -# 大数据? 🤗 Datasets 来救援! [[大数据? 🤗 Datasets 来救援!]] +# 大数据?🤗 Datasets 应对有方![[大数据?🤗 Datasets 应对有方!]] -如今,不难发现我们经常使用数GB的数据集, 特别是如果你打算从头开始预训练像 BERT 或者 GPT-2 这样的转换器。 在这种情况下, _加载_ 数据集就是一个挑战。例如, 用于预训练 GPT-2 的 WebText 语料库包含超过 800 万个文档和 40 GB 的文本 -- 将其加载到笔记本电脑的 RAM 中可能会让它抓狂! +如今,处理 GB 级别的数据集已不再罕见,特别是如果你打算从头开始预训练像 BERT 或者 GPT-2 这样的 Transormer 模型。在这种情况下,甚至 `加载(load)` 数据集都可能成为挑战。例如,用于预训练 GPT-2 的 WebText 语料库包含超过 800 万个文档和 40 GB 的文本 —— 将其加载到笔记本电脑的 RAM 中都可能会让人抓狂! -幸运的是, 🤗 Datasets 旨在克服这些限制。它通过将数据集作为内存映射文件来处理,并通过在语料库中流化条目来摆脱硬盘限制, 从而使你避免内存管理问题。 +幸运的是,🤗 Datasets 的设计旨在克服这些限制。它通过将数据集作为 `内存映射(memory-mapped)` 文件来处理,解放内存管理问题;并通过 `流式处理(streaming)` 来摆脱硬盘限制。 -在本节中, 我们将探索🤗 Datasets 的特性。它有一个称为 [the Pile](https://pile.eleuther.ai)的825 GB的语料库。 让我们开始吧! +在本节中,我们将使用一个庞大的 825 GB 语料库——被称为 [the Pile](https://pile.eleuther.ai) 的数据集,来探索🤗 Datasets 的这些功能。让我们开始吧! -## 什么是Pile? [[什么是Pile?]] +## 什么是 the Pile?[[什么是 the Pile?]] -The Pile 是由[EleutherAI](https://www.eleuther.ai)创建的一个英语文本语料库, 用于训练大规模语言模型。它包含各种各样的数据集, 涵盖科学文章, GitHub 代码库以及过滤的Web文本。训练语料库在[14 GB chunks](https://the-eye.eu/public/AI/pile/), 并且你也可以下载几个[单独的组件](https://the-eye.eu/public/AI/pile_preliminary_components/)。 让我们先来看看 PubMed Abstracts 数据集, 它是[PubMed](https://pubmed.ncbi.nlm.nih.gov/)上的1500万篇生物医学出版物的摘要的语料库。 数据集采用[JSON行格式](https://jsonlines.org) 并使用`zstandard`库进行压缩, 所以我们首先需要先安装`zstandard`库: +The Pile 是由 [EleutherAI](https://www.eleuther.ai) 创建的一个用于训练大规模语言模型的英语文本语料库。它包含各种各样的数据集,涵盖科学文章,GitHub 代码库以及过滤后的 Web 文本。训练语料库以 [14 GB 的文件块](https://the-eye.eu/public/AI/pile/) 提供,并且你也可以下载几个 [单独的组件](https://the-eye.eu/public/AI/pile_preliminary_components/) 。让我们先来看看 PubMed Abstracts 部分,它是 [PubMed](https://pubmed.ncbi.nlm.nih.gov/) 上的 1500 万篇生物医学出版物的摘要的语料库。数据集采用 [JSON Lines格式](https://jsonlines.org) 并使用 `zstandard` 库进行压缩,所以我们首先需要先安装 `zstandard` 库: ```py !pip install zstandard ``` -接下来, 我们可以使用[第二节](/course/chapter5/2)中所学的加载远程数据集的方法加载数据集: +接下来,我们可以使用 [第二节](/course/chapter5/2) 中所学的加载远程数据集的方法加载数据集: ```py from datasets import load_dataset -# This takes a few minutes to run, so go grab a tea or coffee while you wait :) +# 这需要几分钟才能运行,所以在你等待的时候去喝杯茶或咖啡 :) data_files = "https://the-eye.eu/public/AI/pile_preliminary_components/PUBMED_title_abstracts_2019_baseline.jsonl.zst" pubmed_dataset = load_dataset("json", data_files=data_files, split="train") pubmed_dataset @@ -42,15 +42,15 @@ Dataset({ }) ``` -我们可以看到我们的数据集中有 15,518,009 行和 2 列 -- 这是非常多的! +我们可以看到我们的数据集中有 15,518,009 行和 2 列 —— 如此庞大! -✎ 默认情况下, 🤗 Datasets 会自动解压加载数据集所需的文件。 如果你想保留硬盘空间, 你可以传递 `DownloadConfig(delete_extracted=True)` 到 `download_config` 的 `load_dataset()`参数. 有关更多详细信息, 请参阅文档](https://huggingface.co/docs/datasets/package_reference/builder_classes#datasets.DownloadConfig)。 +✏️ 默认情况下,🤗 Datasets 会自动解压加载数据集所需的文件。如果你想保留硬盘空间,你可以把 `DownloadConfig(delete_extracted=True)` 传递给 `load_dataset()` 的 `download_config` 参数。更多详细信息,请参阅 [文档](https://huggingface.co/docs/datasets/package_reference/builder_classes#datasets.DownloadConfig) 。 -让我们看看数据集的第一个元素的内容: +让我们看看数据集的第一个元素的内容: ```py pubmed_dataset[0] @@ -61,53 +61,53 @@ pubmed_dataset[0] '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 ...'} ``` -可以看到, 这看起来像是医学文章的摘要。 现在让我们看看我们使用了RAM的多少存储空间来加载数据集! +可以看到,这看起来像是医学文章的摘要。现在,让我们看看加载数据集所使用的 RAM! ## 内存映射的魔力 [[内存映射的魔力]] -在 Python 中测量内存使用情况的一个简单的方法是使用[`psutil`](https://psutil.readthedocs.io/en/latest/)库,它可以使用 `pip`安装, 如下所示: +测量 Python 内存使用的简单方式是使用 [`psutil`](https://psutil.readthedocs.io/en/latest/) 库,可以通过如下方式安装: ```python !pip install psutil ``` -它提供了一个 `Process` 类,这个类允许我们检查当前进程的内存使用情况, 如下所示: +它提供了一个 `Process` 类,让我们可以检查当前进程的内存使用情况,如下所示: ```py import psutil -# Process.memory_info is expressed in bytes, so convert to megabytes -print(f"RAM used: {psutil.Process().memory_info().rss / (1024 * 1024):.2f} MB") +# Process.memory_info 是以字节为单位的,所以转换为兆字节 +print(f"使用的RAM:{psutil.Process().memory_info().rss / (1024 * 1024):.2f} MB") ``` ```python out RAM used: 5678.33 MB ``` -这里的`rss`属性是指 _常驻集_ 的大小, 它是进程在RAM中占用的内存比例。 这个测量结果也包括了 Python 编译器和我们加载的库所使用的内存, 所以实际上用于加载数据集的内存会更小一些。为了比较, 让我们使用 `dataset_size` 属性看看数据集在磁盘上有多大。 由于结果像之前一样用字节表示, 我们需要手动将其转换为GB: +这里的 `rss` 属性是指 `常驻集(resident set size)` 的大小,它是进程在 RAM 中占用的内存的部分。这个测量结果也包括了 Python 解释器和我们加载的库所使用的内存,所以实际上用于加载数据集的内存会更小一些。作为比较,让我们使用 `dataset_size` 属性看看数据集在磁盘上上的大小。由于结果像之前一样以字节为单位,我们需要手动将其转换为 GB: ```py -print(f"Number of files in dataset : {pubmed_dataset.dataset_size}") +print(f"数据集中文件的数量 : {pubmed_dataset.dataset_size}") size_gb = pubmed_dataset.dataset_size / (1024**3) -print(f"Dataset size (cache file) : {size_gb:.2f} GB") +print(f"数据集大小 (缓存文件) : {size_gb:.2f} GB") ``` ```python out -Number of files in dataset : 20979437051 -Dataset size (cache file) : 19.54 GB +数据集中文件的数量 : 20979437051 +数据集大小 (缓存文件) : 19.54 GB ``` -非常棒 -- 尽管它将近20GB, 但我们能够占用很少的RAM空间加载和访问数据集! +令人欣喜的是——尽管它将近 20GB 之大,我们却能用远小于此的 RAM 加载和访问数据集! -✏️ **试试看!** 从[subsets](https://the-eye.eu/public/AI/pile_preliminary_components/)中选择一个大于你的笔记本或者台式机的RAM大小的子集, 用 🤗 Datasets加载这个数据集, 并且测量RAM的使用量。 请注意, 要获得准确的测量结果, 你需要在另一个进程中执行这个操作。你可以在 [the Pile paper](https://arxiv.org/abs/2101.00027)的表一中找到每个子集解压后的大小。 +✏️ **试试看!** 从 Pile 选择一个比你的笔记本电脑或台式机的 RAM 更大的 [子集](https://the-eye.eu/public/AI/pile_preliminary_components/) ,用 🤗 Datasets 加载这个数据集,并且测量 RAM 的使用量。请注意,为了获得准确的测量结果,你需要新开一个进程执行这个操作。你可以在 [the Pile paper](https://arxiv.org/abs/2101.00027) 的表 1 中找到每个子集解压后的大小。 -如果你熟悉 Pandas, 这个结果可能会让人感到很意外。因为 Wes Kinney 的著名的[经验法则](https://wesmckinney.com/blog/apache-arrow-pandas-internals/) 是你需要的RAM应该是数据集的大小的5倍到10倍。 那么 🤗 Datasets 是如何解决这个内存管理问题的呢? 🤗 Datasets 将每一个数据集看作一个[内存映射文件](https://en.wikipedia.org/wiki/Memory-mapped_file), 它提供了RAM和文件系统存储之间的映射, 该映射允许库访问和操作数据集的元素, 而且无需将其完全加载到内存中。 +如果你熟悉 Pandas,这个结果可能会让人感到很惊奇。因为根据 Wes Kinney 的著名的 [经验法则](https://wesmckinney.com/blog/apache-arrow-pandas-internals/) ,你通常需要 5 到 10 倍于你数据集大小的 RAM。那么 🤗 Datasets 是如何解决这个内存管理问题的呢?🤗 Datasets 将每一个数据集看作一个 [内存映射文件](https://en.wikipedia.org/wiki/Memory-mapped_file) ,它提供了 RAM 和文件系统存储之间的映射,该映射允许 Datasets 库无需将其完全加载到内存中即可访问和操作数据集的元素。 -内存映射文件也一个在多个进程之间共享, 这使得像 `Dataset.map()`之类的方法可以并行化, 并且无需移动或者赋值数据集。在底层, 这些功能都是由[Apache Arrow](https://arrow.apache.org)内存格式和[`pyarrow`](https://arrow.apache.org/docs/python/index.html)库提供的支持, 使得数据加载和处理速度快如闪电。 (更多有关Apache Arrow的详细信息以及与Pandas的比较, 请查看[Dejan Simic's blog post](https://towardsdatascience.com/apache-arrow-read-dataframe-with-zero-memory-69634092b1a).) 为了更清晰地看到这个过程, 让我们通过迭代PubMed Abstracts数据集中的所有元素来运行一个速度测试小程序: +内存映射文件也一个在多个进程之间共享,这使得像 `Dataset.map()` 之类的方法可以在无需移动或者复制数据集的情况下实现并行化。在底层,这些功能都是由 [Apache Arrow](https://arrow.apache.org) 内存格式和 [`pyarrow`](https://arrow.apache.org/docs/python/index.html) 库实现的,这使得数据加载和处理速度快如闪电。(更多有关 Apache Arrow 的详细信息以及与 Pandas 的比较,请查看 [Dejan Simic的博客文章](https://towardsdatascience.com/apache-arrow-read-dataframe-with-zero-memory-69634092b1a) 。) 为了更清晰地看到这个过程,让我们通过遍历 PubMed 摘要数据集中的所有元素,运行一个小速度测试: ```py import timeit @@ -120,26 +120,25 @@ for idx in range(0, len(pubmed_dataset), batch_size): time = timeit.timeit(stmt=code_snippet, number=1, globals=globals()) print( - f"Iterated over {len(pubmed_dataset)} examples (about {size_gb:.1f} GB) in " - f"{time:.1f}s, i.e. {size_gb/time:.3f} GB/s" + f"在 {time:.1f}s 内遍历了 {len(pubmed_dataset)}个示例(约 {size_gb:.1f} GB),即 {size_gb/time:.3f} GB/s" ) ``` ```python out -'Iterated over 15518009 examples (about 19.5 GB) in 64.2s, i.e. 0.304 GB/s' +'在64.2s内遍历了15518009个示例(约19.5 GB),即0.304 GB/s' ``` -这里我们使用了 Python的 `timeit` 模块来测量执行 `code_snippet`所耗的时间。 你通常能以十分之几GB/s到几GB/s的速度迭代数据集。通过上述的方法就已经能够解决大多数大数据集加载的限制, 但是有时候你不得不使用一个很大的数据集, 它甚至都不能存储在笔记本电脑的硬盘上。例如, 如果我们尝试下载整个 Pile, 我们需要825GB的可用磁盘空间! 为了处理这种情况, 🤗 Datasets 提供了一个流式功能, 这个功能允许我们动态下载和访问元素, 并且不需要下载整个数据集。让我们来看看这个功能是如何工作的。 +这里我们使用了 Python 的 `timeit` 模块来测量执行 `code_snippet` 所耗的时间。你通常能以十分之几 GB/s 到几 GB/s 的速度遍历一个数据集。通过上述的方法就已经能够解决大多数大数据集加载的限制,但是有时候你不得不使用一个很大的数据集,它甚至都不能存储在笔记本电脑的硬盘上。例如,如果我们尝试下载整个 Pile,我们需要 825GB 的可用磁盘空间!为了处理这种情况,🤗 Datasets 提供了一个流式功能,这个功能允许我们动态下载和访问元素,并且不需要下载整个数据集。让我们来看看这个功能是如何工作的。 -💡在 Jupyter 笔记中你还可以使用[`%%timeit` magic function](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit)为单元格计时。 +💡在 Jupyter 笔记中你还可以使用 [`%%timeit` 魔术函数](https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-timeit) 为整个单元格计时。 ## 流式数据集 [[流式数据集]] -要使用数据集流, 你只需要将 `streaming=True` 参数传递给 `load_dataset()` 函数。接下来, 让我们再次加载 PubMed Abstracts 数据集, 但是采用流模式: +要使用数据集流,你只需要将 `streaming=True` 参数传递给 `load_dataset()` 函数。接下来,让我们以流模式加载 PubMed 摘要数据集: ```py pubmed_dataset_streamed = load_dataset( @@ -147,7 +146,7 @@ pubmed_dataset_streamed = load_dataset( ) ``` -与我们在本章其他地方遇到的熟悉的 `Dataset` 不同, `streaming=True` 返回的对象是一个 `IterableDataset`。 顾名思义, 要访问 `IterableDataset` , 我们需要迭代它。我们可以按照如下方式访问流式数据集的第一个元素: +不同于我们在这一章其它地方遇到的熟悉的 `Dataset` , `streaming=True` 返回的对象是一个 `IterableDataset` 。顾名思义,要访问 `IterableDataset` ,我们需要迭代它。我们可以按照如下方式访问流式数据集的第一个元素: ```py @@ -159,7 +158,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()`进行动态处理。该过程与我们在[第三章](/course/chapter3)中标记数据集的过程完全相同, 唯一的区别是输出是逐个返回的: +如果你需要在训练期间对流式数据集中的元素 tokenize,可以使用 `IterableDataset.map()` 进行在线处理,而不需要等待数据集全部加载完毕。该过程与我们在 [第三章](/course/chapter3) 中对数据集 tokenize 的过程完全相同,唯一的区别是输出是逐个返回的: ```py from transformers import AutoTokenizer @@ -175,11 +174,11 @@ next(iter(tokenized_dataset)) -💡 你可以传递 `batched=True` 来通过流式加速标记化, 如同我们在上一节看到的那样。它将逐批处理示例; 默认的批量大小为 1,000, 可以使用 `batch_size` 参数指定批量大小。 +💡 为了加速流式的 tokenize,你可以传递 `batched=True` ,就像我们在上一节看到的那样。它会批量处理示例;默认的批大小是 1000,可以通过 `batch_size` 参数指定批量大小。 -你还可以使用 `IterableDataset.shuffle()` 打乱流式数据集, 但与 `Dataset.shuffle()` 不同的是这只会打乱预定义 `buffer_size` 中的元素: +你还可以使用 `IterableDataset.shuffle()` 打乱流式数据集,但与 `Dataset.shuffle()` 不同的是这只会打乱预定义 `buffer_size` 中的元素: ```py shuffled_dataset = pubmed_dataset_streamed.shuffle(buffer_size=10_000, seed=42) @@ -191,7 +190,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 ...'} ``` -在这个示例中, 我们从缓冲区的前 10,000 个示例中随机选择了一个示例。一旦访问了一个示例, 它在缓冲区中的位置就会被语料库中的下一个示例填充 (即, 上述案例中的第 10,001个示例)。你还可以使用 `IterableDataset.take()` 和 `IterableDataset.skip()` 函数从流式数据集中选择元素, 它的作用类似于 `Dataset.select()`。例如, 要选择 PubMed Abstracts 数据集的前5个示例, 我们可以执行以下操作: +在这个例子中,我们从缓冲区的前 10,000 个示例中随机选择了一个示例。一旦访问了一个示例,它在缓冲区中的位置就会被语料库中的下一个示例填充 (即,上述案例中的第 10,001 个示例)。你还可以使用 `IterableDataset.take()` 和 `IterableDataset.skip()` 函数从流式数据集中选择元素,它的作用类似于 `Dataset.select()` 。例如,要选择 PubMed Abstracts 数据集的前 5 个示例,我们可以执行以下代码: ```py dataset_head = pubmed_dataset_streamed.take(5) @@ -211,16 +210,16 @@ list(dataset_head) 'text': 'Oxygen supply in rural africa: a personal experience ...'}] ``` -同样, 你可以使用 `IterableDataset.skip()` 函数将打乱的数据集拆分为训练集和验证集, 如下所示: +同样,你可以使用 `IterableDataset.skip()` 函数从打乱的数据集中创建训练集和验证集,如下所示: ```py -# Skip the first 1,000 examples and include the rest in the training set +# 跳过前 1,000 个示例 ,将其余部分创建为训练集 train_dataset = shuffled_dataset.skip(1000) -# Take the first 1,000 examples for the validation set +# 将前 1,000 个示例用于验证集 validation_dataset = shuffled_dataset.take(1000) ``` -让我们用一个常见的任务来进行我们对数据集流的最后探索: 将多个数据集组合在一起创建一个心得语料库。 🤗 Datasets 提供了一个 `interleave_datasets()` 函数, 它将一个 `IterableDataset` 对象列表组合为单个的 `IterableDataset`, 其中新数据集的元素是通过在列表中的对象交替获得的。当你试图组合大型数据集时, 这个函数特别有用, 让我们通过下面这个例子来试着组合 Pile的自由法律数据集,它是来自美国法院的51 GB的法律意见数据集: +让我们用一个常见的任务来进行我们对数据集流的最后探索:将多个数据集组合在一起创建一个新的语料库。🤗 Datasets 提供了一个 `interleave_datasets()` 函数,它将一个 `IterableDataset` 对象列表组合为单个的 `IterableDataset` ,其中新数据集的元素是交替抽取列表中的数据集获得的。当你试图组合大型数据集时,这个函数特别有用,让我们通过下面这个例子来试着组合 Pile 的 FreeLaw 数据集,这是一个包含美国法院法律意见的 51 GB 数据集: ```py law_dataset_streamed = load_dataset( @@ -239,7 +238,7 @@ next(iter(law_dataset_streamed)) 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'} ``` -这个数据集足够大, 可以对大多数笔记本电脑的RAM有足够的压力, 但是我们已经能够毫不费力地加载和访问它! 现在我们使用 `interleave_datasets()` 函数加载来自 FreeLaw 和 PubMed Abstracts 的数据集: +这个数据集足够大,可以对大多数笔记本电脑的 RAM 有足够的压力,但是我们已经能够毫不费力地加载和访问它!现在我们使用 `interleave_datasets()` 函数将 FreeLaw 和 PubMed Abstracts 数据集的样本整合在一起: ```py from itertools import islice @@ -258,9 +257,9 @@ list(islice(combined_dataset, 2)) 'text': '\n461 U.S. 238 (1983)\nOLIM ET AL.\nv.\nWAKINEKONA\nNo. 81-1581.\nSupreme Court of United States.\nArgued January 19, 1983.\nDecided April 26, 1983.\nCERTIORARI TO THE UNITED STATES COURT OF APPEALS FOR THE NINTH CIRCUIT\n*239 Michael A. Lilly, First Deputy Attorney General of Hawaii, argued the cause for petitioners. With him on the brief was James H. Dannenberg, Deputy Attorney General...'}] ``` -这里我们使用了来自Python的 `itertools` 模块的 `islice()` 函数从合并的数据集中选择前两个示例, 并且我们可以看到它们实际上就是两个源数据集中的前两个示例拼在一起形成的: +这里我们使用了来自 Python 的 `itertools` 模块的 `islice()` 函数从合并的数据集中选择前两个示例,并且我们可以看到它们实际上就是两个源数据集中的前两个示例拼在一起形成的: -最后, 如果你想流式传输整个825GB的 Pile, 你可以按照如下方式获取所有准备好的文件: +最后,如果你想流式传输整个 825GB 的 Pile,你可以按照如下方式获取所有的预处理文件: ```py base_url = "https://the-eye.eu/public/AI/pile/" @@ -280,8 +279,8 @@ next(iter(pile_dataset["train"])) -✏️ **试试看!** 使用像[`mc4`](https://huggingface.co/datasets/mc4) 或者 [`oscar`](https://huggingface.co/datasets/oscar)这样的大型 Common Crawl 语料库来创建一个流式多语言数据集, 该数据集代表你选择的国家/地区语言的口语比例。例如, 瑞士的四种民族语言分别是德语、法语、意大利语和罗曼什语, 因此你可以尝试根据根据口语比例对Oscar子集进行采用来创建瑞士语料库。 +✏️ **试试看!** 使用像 [`mc4`](https://huggingface.co/datasets/mc4) 或者 [`oscar`](https://huggingface.co/datasets/oscar) 这样的大型 Common Crawl 语料库来创建一个流式多语言数据集,该数据集代表你选择的国家/地区语言的口语比例。例如,瑞士的四种民族语言分别是德语、法语、意大利语和罗曼什语,因此你可以尝试根据根据口语比例对 Oscar 子集进行抽样来创建一个瑞士语料库。 -你现在拥有加载和处理各种类型和大小的数据集的所需的所有工具 -- 但是除非你非常幸运, 否则在你的NLP之旅中会有一个难题, 你将不得不创建一个数据集来解决手头的问题。这就是下一节的主题! +你现在拥有加载和处理各种类型和大小的数据集的所需的所有工具 —— 但是除非你非常幸运,否则在你的 NLP 之旅中会有一个难题,你将不得不亲自创建一个数据集来解决手头的问题。这就是我们接下来要讨论的主题! diff --git a/chapters/zh-CN/chapter5/5.mdx b/chapters/zh-CN/chapter5/5.mdx index f6454f914..c3f203a11 100644 --- a/chapters/zh-CN/chapter5/5.mdx +++ b/chapters/zh-CN/chapter5/5.mdx @@ -7,36 +7,36 @@ {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/zh-CN/chapter5/section5.ipynb"}, ]} /> -有时,不存在合适的数据集适用于您构建 NLP 应用,因此您需要自己创建。在本节中,我们将向您展示如何创建一个[GitHub issues](https://github.com/features/issues/)的语料库,GitHub issues通常用于跟踪 GitHub 存储库中的错误或功能。该语料库可用于各种目的,包括: -* 探索关闭未解决的issue或拉取请求需要多长时间 -* 训练一个*多标签分类器*可以根据issue的描述(例如,“错误”、“增强”或“issue”)用元数据标记issue -* 创建语义搜索引擎以查找与用户查询匹配的issue +有时,不存在现有的合适的数据集适用于你构建 NLP 应用,因此你需要自己创建。在本节中,我们将向你展示如何创建一个由 [GitHub issues](https://github.com/features/issues/) 组成的的语料库,这些 issues 通常用于跟踪 GitHub 仓库中的错误或功能。该语料库可用于各种应用场景,包括: +* 探索解决 issue 或合并 pull 请求需要多长时间 +* 训练一个 `多标签分类器(multilabel classifier)` 可以根据 issue 的描述为 issue 标上元数据标签(例如,“bug”、“enhancement(增强功能)”或“question”) +* 创建语义搜索引擎以查找与用户查询匹配的 issue -在这里,我们将专注于创建语料库,在下一节中,我们将探索语义搜索。我们将使用与流行的开源项目相关的 GitHub issue:🤗 Datasets!接下来让我们看看如何获取数据并探索这些issue中包含的信息。 +在这里,我们将关注如何创建语料库,在下一节中,我们将探索语义搜索。原汤化原食,我们将使用一个流行的开源项目的 GitHub issue:🤗 Datasets!接下来让我们看看如何获取数据并探索这些 issue 中包含的信息。 ## 获取数据 [[获取数据]] -您可以浏览 🤗 Datasets 中的所有issue[Issues tab](https://github.com/huggingface/datasets/issues).如以下屏幕截图所示,在撰写本文时,有 331 个未解决的issue和 668 个已关闭的issue。 +你可以点击 [Issues 选项卡](https://github.com/huggingface/datasets/issues) 浏览 🤗 Datasets 中的所有 issus。如以下屏幕截图所示,在撰写本文时,有 331 个未解决的 issue 和 668 个已关闭的 issue。
The GitHub issues associated with 🤗 Datasets.
-如果您单击其中一个issue,您会发现它包含一个标题、一个描述和一组表征该issue的标签。下面的屏幕截图显示了一个示例. +如果你单击其中一个 issue,你会发现它包含一个标题、一个描述和一组标签,这些标签是对 issue 的描述。下面的屏幕截图显示了一个示例:
A typical GitHub issue in the 🤗 Datasets repository.
-要下载所有存储库的issue,我们将使用[GitHub REST API](https://docs.github.com/en/rest)投票[Issues endpoint](https://docs.github.com/en/rest/reference/issues#list-repository-issues).此节点返回一个 JSON 对象列表,每个对象包含大量字段,其中包括标题和描述以及有关issue状态的元数据等。 +我们可以使用 [GitHub REST API](https://docs.github.com/en/rest) 遍历 [Issues 节点(endpoint)](https://docs.github.com/en/rest/reference/issues#list-repository-issues) 下载所有仓库的 issue。节点(endpoint)将返回一个 JSON 对象列表,每个对象包含大量字段,其中包括标题和描述以及有关 issue 状态的元数据等。 -下载issue的一种便捷方式是通过 **requests** 库,这是用 Python 中发出 HTTP 请求的标准方式。您可以通过运行以下的代码来安装库: +我们将使用 `requests` 库来下载,这是用 Python 中发出 HTTP 请求的标准方式。你可以通过运行以下的代码来安装 `requests` 库: ```python !pip install requests ``` -安装库后,您通过调用 **requests.get()** 功能来获取**Issues**节点。例如,您可以运行以下命令来获取第一页上的第一个Issues: +安装库后,你通过调用 `requests.get()` 功能来获取 `Issues` 节点(endpoint)。例如,你可以运行以下命令来获取第一页上的第一个 Issues: ```py import requests @@ -45,7 +45,7 @@ url = "https://api.github.com/repos/huggingface/datasets/issues?page=1&per_page= response = requests.get(url) ``` -这 **response** 对象包含很多关于请求的有用信息,包括 HTTP 状态码: +`response` 对象包含很多关于请求的有用信息,包括 HTTP 状态码: ```py response.status_code @@ -55,7 +55,7 @@ response.status_code 200 ``` -其中一个状态码 **200** 表示请求成功(您可以[在这里](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes)找到可能的 HTTP 状态代码列表)。然而,我们真正感兴趣的是有效的信息,由于我们知道我们的issues是 JSON 格式,让我们按如下方式查看所有的信息: +其中, `200` 状态表示请求成功(你可以在 [这里](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) 找到所有可能的 HTTP 状态代码列表)。不过,我们真正感兴趣的是消息体中的有效信息,由于我们知道我们的 issues 是 JSON 格式,让我们按如下方式查看消息体的信息: ```py response.json() @@ -110,27 +110,28 @@ response.json() 'performed_via_github_app': None}] ``` -哇,这是很多信息!我们可以看到有用的字段,例如 **标题** , **内容** , **参与的成员**, **issue的描述信息**,以及打开issue的GitHub 用户的信息。 +哇,好大量的信息!我们可以看到有用的字段,我们可以看到诸如 `title` 、 `body` 和 `number` 等描述 issue 的有用字段,以及关于创建 issue 的 GitHub 用户的信息。 -✏️ 试试看!单击上面 JSON 中的几个 URL,以了解每个 GitHub issue中我url链接到的实际的地址。 +✏️ **试试看!**打开上面 JSON 中的一些 URL,以了解每个 GitHub issue 中 url 所链接的信息类型。 + -如 GitHub[文档](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting) 中所述,未经身份验证的请求限制为每小时 60 个请求。虽然你可以增加 **per_page** 查询参数以减少您发出的请求数量,您仍然会遭到任何超过几千个issue的存储库的速率限制。因此,您应该关注 GitHub 的[创建个人身份令牌](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token),创建一个个人访问令牌这样您就可以将速率限制提高到每小时 5,000 个请求。获得令牌后,您可以将其包含在请求标头中: +如 GitHub [文档](https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting) 中所述,未经身份验证的请求限制为每小时 60 个请求。虽然你可以增加 `per_page` 查询参数以减少你发出的请求次数,但你仍会在任何有几千个以上 issue 的仓库上触发速率限制。因此,你应该按照 GitHub 的 [说明](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) ,创建一个 `个人访问令牌(personal access token)` 这样你就可以将速率限制提高到每小时 5,000 个请求。获得令牌后,你可以将其放在请求标头中: ```py -GITHUB_TOKEN = xxx # Copy your GitHub token here +GITHUB_TOKEN = xxx # 将你的GitHub令牌复制到此处 headers = {"Authorization": f"token {GITHUB_TOKEN}"} ``` -⚠️ 不要与陌生人共享存在GITHUB令牌的笔记本。我们建议您在使用完后将GITHUB令牌删除,以避免意外泄漏此信息。一个更好的做法是,将令牌存储在.env文件中,并使用 [`python-dotenv` library](https://github.com/theskumar/python-dotenv) 为您自动将其作为环境变量加载。 +⚠️ 不要与陌生人共享存在 `GITHUB令牌` 的笔记本。我们建议你在使用完后将 `GITHUB令牌` 删除,以避免意外泄漏。一个更好的做法是,将令牌存储在.env 文件中,并使用 [`python-dotenv`库](https://github.com/theskumar/python-dotenv) 自动加载环境变量。 -现在我们有了访问令牌,让我们创建一个可以从 GitHub 存储库下载所有issue的函数: +现在我们有了访问令牌,让我们创建一个可以从 GitHub 仓库下载所有 issue 的函数: ```py import time @@ -152,19 +153,19 @@ def fetch_issues( batch = [] all_issues = [] - per_page = 100 # Number of issues to return per page + per_page = 100 ## 每页返回的 issue 的数量 num_pages = math.ceil(num_issues / per_page) base_url = "https://api.github.com/repos" for page in tqdm(range(num_pages)): - # Query with state=all to get both open and closed issues + # 使用 state=all 进行查询来获取 open 和 closed 的issue query = f"issues?page={page}&per_page={per_page}&state=all" issues = requests.get(f"{base_url}/{owner}/{repo}/{query}", headers=headers) batch.extend(issues.json()) if len(batch) > rate_limit and len(all_issues) < num_issues: all_issues.extend(batch) - batch = [] # Flush batch for next time period + batch = [] # 重置batch print(f"Reached GitHub rate limit. Sleeping for one hour ...") time.sleep(60 * 60 + 1) @@ -176,14 +177,14 @@ def fetch_issues( ) ``` -现在我们可以调用 **fetch_issues()** 批量下载所有issue,避免超过GitHub每小时的请求数限制;结果将存储在repository_name-issues.jsonl文件,其中每一行都是一个 JSON 对象,代表一个issue。让我们使用这个函数从 🤗 Datasets中抓取所有issue: +现在我们可以调用 `fetch_issues()` 它将按批次下载所有 issue,以避免超过 GitHub 每小时请求次数的限制;结果将存储在 `repository_name-issues.jsonl` 文件,其中每一行都是一个 JSON 对象,代表一个 issue。让我们使用这个函数从 🤗 Datasets 中抓取所有 issue: ```py -# Depending on your internet connection, this can take several minutes to run... +# 取决于你的网络连接,这可能需要几分钟的时间来运行... fetch_issues() ``` -下载issue后,我们可以使用我们 [section 2](/course/chapter5/2)新学会的方法在本地加载它们: +下载 issue 后,我们可以使用我们在 [第二节](/course/chapter5/2) 新学会的方法在本地加载它们: ```py issues_dataset = load_dataset("json", data_files="datasets-issues.jsonl", split="train") @@ -197,20 +198,20 @@ Dataset({ }) ``` -太好了,我们已经从头开始创建了我们的第一个数据集!但是为什么会有几千个issue,而🤗 Datasets存储库中的[Issues 选项卡](https://github.com/huggingface/datasets/issues)总共却只显示了大约 1,000 个issue🤔?如 GitHub [文档](https://docs.github.com/en/rest/reference/issues#list-issues-assigned-to-the-authenticated-user)中所述,那是因为我们也下载了所有的拉取请求: +太好了,我们已经从头开始创建了我们的第一个数据集!但是为什么会有几千个 issue,而🤗 Datasets 仓库中的 [Issues 选项卡](https://github.com/huggingface/datasets/issues) 总共却只显示了大约 1,000 个 issue🤔?如 GitHub [文档](https://docs.github.com/en/rest/reference/issues#list-issues-assigned-to-the-authenticated-user) 中所述,那是因为我们不加下载了 issue 还下载了所有的 pull 请求: ->Git Hub的REST API v3认为每个pull请求都是一个issue,但并不是每个issue都是一个pull请求。因此,“Issues”节点可能在响应中同时返回issue和拉取请求。你可以通过pull_request 的 key来辨别pull请求。请注意,从“Issues”节点返回的pull请求的id将是一个issue id。 +> Git Hub 的 REST API v3 认为每个 pull 请求都是一个 issue,但事实上并不是每个 issue 都是一个 pull 请求。因此,“Issues”节点可能同时返回了 issue 和 pull 请求。你可以通过 `pull_request` 的 key 来辨别 pull 请求。请注意,从“Issues”节点返回的 pull 请求的 id 将是一个 issue id。 -由于issue和pull request的内容有很大的不同,我们先做一些小的预处理,让我们能够区分它们。 +由于 issue 和 pull request 的内容有很大的不同,我们先做一些小的预处理,让我们能够区分它们。 -## 清理数据 [[清理数据]] +## 清洗数据 [[清洗数据]] -上面来自 GitHub 文档的片段告诉我们, **pull_request** 列可用于区分issue和拉取请求。让我们随机挑选一些样本,看看有什么不同。我们将使用在[第三节](/course/chapter5/3), 学习的方法,使用 **Dataset.shuffle()** 和 **Dataset.select()** 抽取一个随机样本,然后将 **html_url** 和 **pull_request** 列使用zip函数打包,以便我们可以比较各种 URL: +上面的 GitHub 文档告诉我们, `pull_request` 列可用于区分 issue 和 pull 请求。让我们随机挑选一些样本,看看有什么不同。我们将使用在 [第三节](/course/chapter5/3) ,学习的方法,使用 `Dataset.shuffle()` 和 `Dataset.select()` 抽取一个随机样本,然后将 `html_url` 和 `pull_request` 列使用 zip 函数组合起来,以便我们可以比较各种 URL: ```py sample = issues_dataset.shuffle(seed=666).select(range(3)) -# Print out the URL and pull request entries +# 打印出 URL 和 pull 请求 for url, pr in zip(sample["html_url"], sample["pull_request"]): print(f">> URL: {url}") print(f">> Pull request: {pr}\n") @@ -227,7 +228,7 @@ for url, pr in zip(sample["html_url"], sample["pull_request"]): >> Pull request: {'url': 'https://api.github.com/repos/huggingface/datasets/pulls/783', 'html_url': 'https://github.com/huggingface/datasets/pull/783', 'diff_url': 'https://github.com/huggingface/datasets/pull/783.diff', 'patch_url': 'https://github.com/huggingface/datasets/pull/783.patch'} ``` -这里我们可以看到,每个pull请求都与各种url相关联,而普通issue只有一个None条目。我们可以使用这一点不同来创建一个新的is_pull_request列通过检查pull_request字段是否为None来区分它们: +这里我们可以看到,每个 pull 请求都与各种 url 相关联,而普通 issue 只有一个 `None` 条目。我们可以使用这一点不同来创建一个新的 `is_pull_request` 列,通过检查 `pull_request` 字段是否为 `None` 来区分它们: ```py issues_dataset = issues_dataset.map( @@ -237,21 +238,21 @@ issues_dataset = issues_dataset.map( -✏️ 试试看!计算在 🤗 Datasets中解决issue所需的平均时间。您可能会发现 Dataset.filter()函数对于过滤拉取请求和未解决的issue很有用,并且您可以使用Dataset.set_format()函数将数据集转换为DataFrame,以便您可以轻松地按照需求修改创建和关闭的时间的格式(以时间戳格式)。 +✏️ **试试看!**计算在 🤗 Datasets 中解决 issue 所需的平均时间。你可能会发现 `Dataset.filter()` 函数对于过滤 pull 请求和未解决的 issue 很有用,并且你可以使用 `Dataset.set_format()` 函数将数据集转换为 `DataFrame` ,以便你可以轻松地按照需求修改 `创建(created_at)` 和 `关闭(closed_at)` 的时间的格式(以时间戳格式)。 -尽管我们可以通过删除或重命名某些列来进一步清理数据集,但在此阶段尽可能保持数据集“原始”状态通常是一个很好的做法,以便它可以在多个应用程序中轻松使用。在我们将数据集推送到 Hugging Face Hub 之前,让我们再添加一些缺少的数据:与每个issue和拉取请求相关的评论。我们接下来将添加它们——你猜对了——我们将依然使用GitHub REST API! +尽管我们可以通过删除或重命名某些列来进一步清理数据集,但在此阶段尽可能保持数据集“原始”状态通常是一个很好的做法,以便它可以在多个不同的项目中轻松使用。在我们将数据集推送到 Hugging Face Hub 之前,让我们再添加一些缺少的数据:每个 issue 和 pull 中的评论。我们接下来将添加它们——你猜对了——我们将依然使用 GitHub REST API! ## 扩充数据集 [[扩充数据集]] -如以下屏幕截图所示,与issue或拉取请求相关的评论提供了丰富的信息,特别是如果我们有兴趣构建搜索引擎来回答用户对这个项目的疑问。 +如以下截图所示,issue 或 pull 请求相关的评论提供了丰富的信息,特别是如果我们有兴趣构建搜索引擎来回答用户对这个仓库的疑问时,这些信息将非常有用。
Comments associated with an issue about 🤗 Datasets.
-GitHub REST API 提供了一个 [评论节点](https://docs.github.com/en/rest/reference/issues#list-issue-comments) 返回与issue编号相关的所有评论。让我们测试节点以查看它返回的内容: +GitHub REST API 提供了一个 [`Comments(评论)`节点](https://docs.github.com/en/rest/reference/issues#list-issue-comments) 返回与 issue 编号相关的所有评论。让我们测试一下该节点返回的内容: ```py issue_number = 2792 @@ -287,11 +288,11 @@ response.json() 'created_at': '2021-08-12T12:21:52Z', 'updated_at': '2021-08-12T12:31:17Z', 'author_association': 'CONTRIBUTOR', - 'body': "@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?", + 'body': "@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?", 'performed_via_github_app': None}] ``` -我们可以看到注释存储在body字段中,所以让我们编写一个简单的函数,通过在response.json()中为每个元素挑选body内容来返回与某个issue相关的所有评论: +我们可以看到评论存储在 `body` 字段中,因此让我们编写一个简单的函数,通过挑选出 `response.json()` 中每个元素的 `body` 内容,返回与某个 issue 相关的所有评论: ```py def get_comments(issue_number): @@ -300,18 +301,18 @@ def get_comments(issue_number): return [r["body"] for r in response.json()] -# Test our function works as expected +# 测试我们的函数是否按预期工作 get_comments(2792) ``` ```python out -["@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):\r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?"] +["@albertvillanova my tests are failing here:\r\n```\r\ndataset_name = 'gooaq'\r\n\r\n def test_load_dataset(self, dataset_name):r\n configs = self.dataset_tester.load_all_configs(dataset_name, is_local=True)[:1]\r\n> self.dataset_tester.check_load_dataset(dataset_name, configs, is_local=True, use_local_dummy_data=True)\r\n\r\ntests/test_dataset_common.py:234: \r\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \r\ntests/test_dataset_common.py:187: in check_load_dataset\r\n self.parent.assertTrue(len(dataset[split]) > 0)\r\nE AssertionError: False is not true\r\n```\r\nWhen I try loading dataset on local machine it works fine. Any suggestions on how can I avoid this error?"] ``` -这看起来不错,所以让我们使用 **Dataset.map()** 方法在我们数据集中每个issue的添加一个**comments**列: +这看起来不错,所以让我们使用 `Dataset.map()` 方法,为我们数据集中每个 issue 的添加一个 `comments` 列: ```py -# Depending on your internet connection, this can take a few minutes... +# 取决于你的网络连接,这可能需要几分钟时间... issues_with_comments_dataset = issues_dataset.map( lambda x: {"comments": get_comments(x["number"])} ) @@ -323,14 +324,14 @@ issues_with_comments_dataset = issues_dataset.map( -现在我们有了增强的数据集,是时候将它推送到 Hub 以便我们可以与社区共享它了! 上传数据集非常简单:就像 🤗 Transformers 中的模型和分词器一样,我们可以使用 `push_to_hub()` 方法来推送数据集。 为此,我们需要一个身份验证令牌,它可以通过首先使用 `notebook_login()` 函数登录到 Hugging Face Hub 来获得: +现在我们有了扩充后的数据集,是时候将它推送到 Hub 并与社区共享了!上传数据集非常简单:就像 🤗 Transformers 中的模型和 tokenizer 一样,我们可以使用 `push_to_hub()` 方法来推送数据集。为此,我们需要一个身份验证令牌,它可以通过首先使用 `notebook_login()` 函数登录到 Hugging Face Hub 来获得: ```py from huggingface_hub import notebook_login notebook_login() ``` -这将创建一个小部件,您可以在其中输入您的用户名和密码, API 令牌将保存在~/.huggingface/令牌.如果您在终端中运行代码,则可以改为通过 CLI 登录: +这将创建一个小部件,你可以在其中输入你的用户名和密码,API 令牌将保存在 `~/.huggingface/token` 中。如果你在终端中运行代码,则可以改为使用命令行登录: ```bash huggingface-cli login @@ -342,7 +343,7 @@ huggingface-cli login issues_with_comments_dataset.push_to_hub("github-issues") ``` -之后,任何人都可以通过便捷地提供带有存储库 ID 作为 path 参数的 load_dataset() 来下载数据集: +之后,任何人都可以通过便捷地使用附带仓库 ID 作为 `path` 参数的 `load_dataset()` 函数 来下载数据集: ```py remote_dataset = load_dataset("lewtun/github-issues", split="train") @@ -356,27 +357,29 @@ Dataset({ }) ``` -很酷,我们已经将我们的数据集推送到 Hub,其他人可以使用它!只剩下一件重要的事情要做:添加一个数据卡这解释了语料库是如何创建的,并为使用数据集的其他提供一些其他有用的信息。 +很酷,我们已经将我们的数据集推送到 Hub,其他人可以使用它!只剩下一件重要的事情要做:添加一个数据卡片,解释语料库是如何创建的,并为使用数据集的其他提供一些其他有用的信息。 -💡 您还可以使用一些 Git 魔法直接从终端将数据集上传到 Hugging Face Hub。有关如何执行此操作的详细信息,请参阅 [🤗 Datasets guide](https://huggingface.co/docs/datasets/share#share-a-dataset-using-the-cli) 指南。 +💡 你还可以使用一些 Git 技巧和 `huggingface-cli` 直接从终端将数据集上传到 Hugging Face Hub。有关如何执行此操作的详细信息,请参阅 [🤗 Datasets 指南](https://huggingface.co/docs/datasets/share#share-a-dataset-using-the-cli) 指南。 ## 创建数据集卡片 [[创建数据集卡片]] -有据可查的数据集更有可能对其他人(包括你未来的自己!)有用,因为它们提供了上下文,使用户能够决定数据集是否与他们的任务相关,并评估任何潜在的偏见或与使用相关的风险。在 Hugging Face Hub 上,此信息存储在每个数据集存储库的自述文件文件。在创建此文件之前,您应该执行两个主要步骤: +有据可查的数据集更有可能对其他人(包括你未来的自己!)有用,因为它们提供了数据集相关的信息,使用户能够决定数据集是否与他们的任务相关,并评估任何潜在的偏见或与使用相关的风险。 + +在 Hugging Face Hub 上,此信息存储在每个数据集仓库的自述文件(`README.md`)。在创建此文件之前,你应该采取两个主要步骤: -1. 使用[数据集标签应用程序](https://huggingface.co/datasets/tagging/) 创建YAML格式的元数据标签。这些标签用于各种各样的搜索功能,并确保您的数据集可以很容易地被社区成员找到。因为我们已经在这里创建了一个自定义数据集,所以您需要克隆数据集标签存储库并在本地运行应用程序。它的界面是这样的: +1. 使用 [`datasets-tagging`应用程序](https://huggingface.co/datasets/tagging/) 创建 YAML 格式的元数据标签。这些标签用于 Hugging Face Hub 上的各种搜索功能,并确保你的数据集可以很容易地被社区成员找到。由于我们已经在这里创建了一个自定义数据集,所以你需要克隆数据集标签仓库( `datasets-tagging` )并在本地运行应用程序。它的界面是这样的:
-The `datasets-tagging` interface. + datasets-tagger interface.
-2.阅读[🤗 Datasets guide](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) 关于创建信息性数据集卡片的指南,并将其作为模板使用。 +2.阅读 [🤗 Datasets 指南](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) 中关于创建完善的数据集卡片的指南,并将其作为模板使用。 -您可以创建自述文件文件直接在Hub上,你可以在里面找到模板数据集卡片 **lewtun/github-issues** 数据集存储库。填写好的数据集卡片的屏幕截图如下所示。! +你可以直接在 Hub 上创建 README.md 文件,你可以在 `lewtun/github-issues` 数据集仓库中找到一个模板数据集卡片。下面显示了填写好的数据集卡片的截图。
A dataset card. @@ -384,15 +387,14 @@ Dataset({ -✏️试试看!使用应用程序和 [🤗 Datasets guide](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) 指南来完成 GitHub issue数据集的 README.md 文件。 +✏️**试试看!**使用 `dataset-tagging` 应用程序和 [🤗 Datasets 指南](https://github.com/huggingface/datasets/blob/master/templates/README_guide.md) 指南来完成 GitHub issue 数据集的 README.md 文件。 -很好! 我们在本节中看到,创建一个好的数据集可能非常复杂,但幸运的是,将其上传并与社区共享会很容易实现。在下一节中,我们将使用我们的新数据集创建一个带有 🤗 Datasets的语义搜索引擎,该数据集可以将issue与最相关的issue和评论进行匹配。 +很好!我们在本节中可以看到,创建一个好的数据集可能涉及相当多的工作,但幸运的是,将其上传并与社区共享会很容易实现。在下一节中,我们将使用我们的新数据集创建一个 🤗 Datasets 的语义搜索引擎,该引擎可以将输入匹配到最相关的 issue 和评论。 -✏️ 试试看!按照我们在本节中采取的步骤为您最喜欢的开源库创建一个 GitHub issue数据集(当然,选择 🤗 Datasets以外的其他东西!)。对于奖励积分,微调多标签分类器以预测该领域中存在的标签。 +✏️ **试试看!**按照我们在本节中采取的步骤为你最喜欢的开源库创建一个 GitHub issue 数据集(当然是除了 🤗 Datasets)。进阶的挑战:微调多标签分类器以预测在 `labels` 字段中出现的标签。 - diff --git a/chapters/zh-CN/chapter5/6.mdx b/chapters/zh-CN/chapter5/6.mdx index f3a1044e9..71a46b74a 100644 --- a/chapters/zh-CN/chapter5/6.mdx +++ b/chapters/zh-CN/chapter5/6.mdx @@ -22,13 +22,15 @@ {/if} -在[第五小节](/course/chapter5/5), 我们从 🤗 Datasets 存储库创建了一个包含 GitHub 问题和评论的数据集。在本节中,我们将使用这些信息来构建一个搜索引擎,它可以帮助我们找到这个库最紧迫问题的答案! +在 [第5小节](/course/chapter5/5) ,我们创建了一个 🤗 Datasets 仓库的 GitHub issues 和评论组成的数据集。在本节,我们将使用这些信息构建一个搜索引擎,帮助我们找到关于该库的最相关的 issue 的答案! -## 使用嵌入进行语义搜索 [[使用嵌入进行语义搜索]] +## 使用文本嵌入进行语义搜索 [[使用文本嵌入进行语义搜索]] -正如我们在[第一章](/course/chapter1),学习的, 基于 Transformer 的语言模型会将文本中的每个标记转换为嵌入向量.事实证明,可以“汇集”各个嵌入向量来创建整个句子、段落或文档(在某些情况下)的向量表示。然后,通过计算每个嵌入之间的点积相似度(或其他一些相似度度量)并返回相似度最大的文档,这些嵌入可用于在语料库中找到相似的文档。在本节中,我们将使用嵌入来开发语义搜索引擎。与基于将查询中的关键字的传统方法相比,这些搜索引擎具有多种优势。 +正如我们在 [第一章](/course/chapter1) ,学习的,基于 Transformer 的语言模型会将文本中的每个 token 转换为嵌入向量。事实证明,我们可以 “池化(pool)” 嵌入向量以创建整个句子、段落或(在某些情况下)文档的向量表示。然后,通过计算每个嵌入之间的点积相似度(或其他一些相似度度量)并返回相似度最大的文档,这些嵌入可用于在语料库中找到相似的文档。 + +在本节中,我们将使用文本嵌入向量来开发语义搜索引擎。与基于将查询中的关键字的传统方法相比,这些搜索引擎具有多种优势。
Semantic search. @@ -37,7 +39,7 @@ ## 加载和准备数据集 [[加载和准备数据集]] -我们需要做的第一件事是下载我们的 GitHub issues 数据集,所以让我们像往常那样使用 `load_dataset()`函数: +首先,我们需要下载我们的 GitHub issues 数据集,所以让我们像往常那样使用 `load_dataset()` 函数: ```py from datasets import load_dataset @@ -54,7 +56,7 @@ Dataset({ }) ``` -这里我们在load_dataset()中使用了默认的训练集分割,所以它返回一个数据集而不是数据集字典。第一项任务是过滤掉pull请求,因为这些请求很少用于回答用户提出的问题,而且会给我们的搜索引擎带来噪声。现在应该很熟悉了,我们可以使用dataset.filter()函数来排除数据集中的这些行。同时,让我们也过滤掉没有注释的行,因为这些行不会是用户提问的答案: +在此,我们在 `load_dataset()` 中指定了默认的 `train(训练集)` 部分,因此它返回一个 `Dataset` 而不是 `DatasetDict` 。首要任务是排除掉拉取请求(pull),因为这些请求往往很少用于回答提出的 issue,会为我们的搜索引擎引入干扰。正如我们现在熟悉的那样,我们可以使用 `Dataset.filter()` 函数来排除数据集中的这些行。与此同时,让我们也筛选掉没有评论的行,因为这些行没有为用户提问提供回答: ```py issues_dataset = issues_dataset.filter( @@ -70,7 +72,7 @@ Dataset({ }) ``` -我们可以看到我们的数据集中有很多列,其中大部分我们不需要构建我们的搜索引擎。从搜索的角度来看,信息量最大的列是 **title** , **body** , 和 **comments** ,而 **html_url** 为我们提供了一个回到源问题的链接。让我们使用 **Dataset.remove_columns()** 删除其余部分的功能: +我们可以看到,我们的数据集中有很多列,其中大部分在构建我们的搜索引擎都不会使用。从搜索的角度来看,信息量最大的列是 `title` , `body` ,和 `comments` ,而 `html_url` 为我们提供了一个回到原 issue 的链接。让我们使用 `Dataset.remove_columns()` 删除其余的列: ```py columns = issues_dataset.column_names @@ -87,27 +89,27 @@ Dataset({ }) ``` -为了创建我们的嵌入,我们将用问题的标题和正文来扩充每条评论,因为这些字段通常包含有用的上下文信息。因为我们的 **comments** 列当前是每个问题的评论列表,我们需要“重新组合”列,以便每一条评论都包含一个 **(html_url, title, body, comment)** 元组。在 Pandas 中,我们可以使用 [DataFrame.explode() 函数](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html), 它为类似列表的列中的每个元素创建一个新行,同时复制所有其他列值。为了看到它的实际效果,让我们首先切换到 Pandas的**DataFrame** 格式: +为了创建我们的文本嵌入数据集,我们将用 issue 的标题和正文来扩充每条评论,因为这些字段通常包含有用的上下文信息。因为我们的 `comments` 列当前是每个 issue 的评论列表,我们需要“重新组合”列,使得每一行都是由一个 `(html_url, title, body, comment)` 元组组成。在 Pandas 中,我们可以使用 [DataFrame.explode() 函数](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.explode.html) 完成这个操作 它为类似列表的列中的每个元素创建一个新行,同时复制所有其他列值。让我们首先切换到 Pandas 的 `DataFrame` 格式: ```py issues_dataset.set_format("pandas") df = issues_dataset[:] ``` -如果我们检查这里的第一行 **DataFrame** 我们可以看到有四个评论与这个问题相关: +如果我们检查这个 `DataFrame` 的第一行,我们可以看到这个 issue 有四个相关评论: ```py df["comments"][0].tolist() ``` ```python out -['the bug code locate in :\r\n if data_args.task_name is not None:\r\n # Downloading and loading a dataset from the hub.\r\n datasets = load_dataset("glue", data_args.task_name, cache_dir=model_args.cache_dir)', +['the bug code locate in :\r\n if data_args.task_name is not None:\r\n # Downloading and loading a dataset from the hub.\r\n datasets = load_dataset("glue", data_args.task_name, cache_dir=model_args.cache_dir)', 'Hi @jinec,\r\n\r\nFrom time to time we get this kind of `ConnectionError` coming from the github.com website: https://raw.githubusercontent.com\r\n\r\nNormally, it should work if you wait a little and then retry.\r\n\r\nCould you please confirm if the problem persists?', - 'cannot connect,even by Web browser,please check that there is some problems。', + 'cannot connect,even by Web browser,please check that there is some problems。', 'I can access https://raw.githubusercontent.com/huggingface/datasets/1.7.0/datasets/glue/glue.py without problem...'] ``` -我们希望这些评论中的每一条都得到一行。让我们检查是否是这种情况: +我们希望使用 `explode()` 将这些评论中的每一条都展开成为一行。让我们看看是否可以做到: ```py comments_df = df.explode("comments", ignore_index=True) @@ -156,7 +158,7 @@ comments_df.head(4) -太好了,我们可以看到评论成功被扩充, **comments** 是包含个人评论的列!现在我们已经完成了 Pandas要完成的部分功能,我们可以快速切换回 **Dataset** 通过加载 **DataFrame** 在内存中: +非常好,我们可以看到其他三列已经被复制了,并且 `comments` 列里存放着单独的评论!现在我们已经完成了 Pandas 要完成的部分功能,我们可以通过加载内存中的 `DataFrame` 快速切换回 `Dataset` : ```py from datasets import Dataset @@ -177,11 +179,11 @@ Dataset({ -✏️ **Try it out!** 看看能不能不用pandas就可以完成列的扩充; 这有点棘手; 你可能会发现 🤗 Datasets 文档的 ["Batch mapping"](https://huggingface.co/docs/datasets/about_map_batch#batch-mapping) 对这个任务很有用。 +✏️ **试试看!** 看看你是否可以使用 `Dataset.map()` 展开 `issues_dataset` 的 `comments` 列,这有点棘手;你可能会发现 🤗 Datasets 文档的 ["批处理映射(Batch mapping)"](https://huggingface.co/docs/datasets/about_map_batch#batch-mapping) 对这个任务很有用。 -现在我们每行有一个评论,让我们创建一个新的 **comments_length** 列来存放每条评论的字数: +既然我们每行有一个评论,让我们创建一个新的 `comments_length` 列来存放每条评论的字数: ```py comments_dataset = comments_dataset.map( @@ -189,7 +191,7 @@ comments_dataset = comments_dataset.map( ) ``` -我们可以使用这个新列来过滤掉简短的评论,其中通常包括“cc @lewtun”或“谢谢!”之类与我们的搜索引擎无关的内容。虽然无法为过滤器选择的精确数字,但大约大于15 个单词似乎是一个不错的选择: +我们可以使用这个新列来过滤掉简短的评论,其中通常包括“cc @lewtun”或“谢谢!”之类与我们的搜索引擎无关的内容。筛选的精确数字没有硬性规定,但大约大于 15 个单词似乎是一个不错的选择: ```py comments_dataset = comments_dataset.filter(lambda x: x["comment_length"] > 15) @@ -203,7 +205,7 @@ Dataset({ }) ``` -稍微清理了我们的数据集后,让我们将问题标题、描述和评论连接到一个新的 **text** 列。像往常一样,我们可以编写一个简单的函数,并将其传递给 **Dataset.map()**来做到这些 : +稍微清理了我们的数据集后,让我们使用 issue 标题、描述和评论构建一个新的 `text` 列。像往常一样,我们可以编写一个简单的函数,并将其传递给 `Dataset.map()` 来完成这些操作 ```py def concatenate_text(examples): @@ -219,11 +221,11 @@ def concatenate_text(examples): comments_dataset = comments_dataset.map(concatenate_text) ``` -我们终于准备好创建一些嵌入了!让我们来看看。 +我们终于准备好创建一些文本嵌入了!让我们继续。 ## 创建文本嵌入 [[创建文本嵌入]] -我们在[第二章](/course/chapter2) 学过,我们可以通过使用 **AutoModel** 类来完成词嵌入。我们需要做的就是选择一个合适的检查点来加载模型。幸运的是,有一个名为 **sentence-transformers** 专门用于创建词嵌入。如库中[文档](https://www.sbert.net/examples/applications/semantic-search/README.html#symmetric-vs-asymmetric-semantic-search), 所述的,我们这次要实现的是非对称语义搜索,因为我们有一个简短的查询,我们希望在比如问题评论等更长的文档中找到其答案。通过查看[模型概述表](https://www.sbert.net/docs/pretrained_models.html#model-overview) 我们可以发现 **multi-qa-mpnet-base-dot-v1** 检查点在语义搜索方面具有最佳性能,因此我们将在我们的应用程序中使用它。我们还将使用相同的检查点加载标记器: +正如我们在 [第二章](/course/chapter2) 学过,我们可以通过使用 `AutoModel` 类来完成文本嵌入。首先需要做的就是选择一个合适的 checkpoint。幸运的是,有一个名为 `sentence-transformers` 的库专门用于创建文本嵌入。如库中的 [文档](https://www.sbert.net/examples/applications/semantic-search/README.html#symmetric-vs-asymmetric-semantic-search) 所述的,我们这次要实现的是非对称语义搜索(asymmetric semantic search),因为我们有一个简短的查询,我们希望在比如 issue 评论等更长的文档中找到其匹配的文本。通过查看 [模型概述表](https://www.sbert.net/docs/pretrained_models.html#model-overview) 我们可以发现 `multi-qa-mpnet-base-dot-v1` checkpoint 在语义搜索方面具有最佳性能,因此我们将使用它。我们还将使用这个 checkpoint 加载了对应的 tokenizer : {#if fw === 'pt'} @@ -235,7 +237,7 @@ tokenizer = AutoTokenizer.from_pretrained(model_ckpt) model = AutoModel.from_pretrained(model_ckpt) ``` -为了加快嵌入过程,将模型和输入放在 GPU 设备上,所以现在让我们这样做: +将模型和输入的文本放到 GPU 上会加速嵌入过程,所以让我们现在就这么做: ```py import torch @@ -254,19 +256,18 @@ tokenizer = AutoTokenizer.from_pretrained(model_ckpt) model = TFAutoModel.from_pretrained(model_ckpt, from_pt=True) ``` -请注意,我们已将 from_pt=True 设置为 from_pretrained() 方法的参数。这是因为 multi-qa-mpnet-base-dot-v1 检查点只有PyTorch权重,因此设置 from_pt=True 会自动将它们转换为TensorFlow格式。如您所见,在Transformers中的🤗框架之间切换非常简单! +请注意,我们在调用 `from_pretrained()` 方法的时候添加了 `from_pt=True` 参数。这是因为 `multi-qa-mpnet-base-dot-v1` checkpoint 只有 PyTorch 权重,因此设置 `from_pt=True` 会自动将它们转换为 TensorFlow 格式。如你所见,在🤗 Transformers 中切换框架非常简单! {/if} -正如我们之前提到的,我们希望将 GitHub 问题语料库中的每个条目表示为单个向量,因此我们需要以某种方式“池化”或平均化我们的标记嵌入。一种流行的方法是在我们模型的输出上执行CLS 池化,我们只获取**[CLS]** 令牌的最后一个隐藏状态。以下函数为我们提供了这样的方法: +根据我们之前的想法,我们希望将我们的 GitHub issue 中的每一条记录转化为一个单一的向量,所以我们需要以某种方式“池化(pool)”或平均每个词的嵌入向量。一种流行的方法是在我们模型的输出上进行 `CLS 池化` ,我们只需要收集 `[CLS]` token 的的最后一个隐藏状态。以下函数实现了这个功能: ```py def cls_pooling(model_output): return model_output.last_hidden_state[:, 0] ``` -接下来,我们将创建一个辅助函数,该函数将标记文档列表,将tensor放在 GPU 上,然后提供给模型,最后对输出使用CLS 池化: - +接下来,我们将创建一个辅助函数,它将对一组文档进行 tokenize,然后将张量放在 GPU 上,接着将它们喂给模型,最后对输出进行 CLS 池化: {#if fw === 'pt'} ```py @@ -279,7 +280,7 @@ def get_embeddings(text_list): return cls_pooling(model_output) ``` -我们可以通过在我们的语料库中输入第一个文本条目并检查输出维度来测试该函数是否有效: +我们可以将第一条数据喂给它并检查输出的形状来测试这个函数是否正常工作: ```py embedding = get_embeddings(comments_dataset["text"][0]) @@ -290,7 +291,7 @@ embedding.shape torch.Size([1, 768]) ``` -太好了,我们已经将语料库中的第一个条目转换为 768 维向量!我们可以用 **Dataset.map()** 应用我们的 **get_embeddings()** 函数到我们语料库中的每一行,所以让我们创建一个新的 **embeddings** 列如下: +太好了,我们已经将语料库中的第一个条目转换为了一个 768 维向量!我们可以用 `Dataset.map()` 将我们的 `get_embeddings()` 函数应用到我们语料库中的每一行,然后创建一个新的 `embeddings` 列: ```py embeddings_dataset = comments_dataset.map( @@ -310,7 +311,7 @@ def get_embeddings(text_list): return cls_pooling(model_output) ``` -我们可以通过在我们的语料库中输入第一个文本条目并检查输出维度来测试该函数是否有效: +我们可以将第一个文本条目喂给它并检查输出的形状来测试这个函数是否正常工作: ```py embedding = get_embeddings(comments_dataset["text"][0]) @@ -321,7 +322,7 @@ embedding.shape TensorShape([1, 768]) ``` -太好了,我们已经将语料库中的第一个条目转换为 768 维向量!我们可以用 **Dataset.map()** 应用我们的 **get_embeddings()** 函数到我们语料库中的每一行,所以让我们创建一个新的 **embeddings** 列如下: +太好了,我们已经将语料库中的第一个条目转换为了一个 768 维向量!我们可以用 `Dataset.map()` 将我们的 `get_embeddings()` 函数应用到我们语料库中的每一行,然后创建一个新的 `embeddings` 列: ```py embeddings_dataset = comments_dataset.map( @@ -331,18 +332,20 @@ embeddings_dataset = comments_dataset.map( {/if} -请注意,我们已经将嵌入转换为 NumPy 数组——这是因为当我们尝试使用 FAISS 索引它们时,🤗 Datasets需要这种格式,我们接下来会这样做。 +请注意,在上面的代码中我们已经将文本嵌入转换为 NumPy 数组——这是因为当我们尝试使用 FAISS 搜索它们时,🤗 Datasets 需要这种格式,让我们继续吧。 ## 使用 FAISS 进行高效的相似性搜索 [[使用 FAISS 进行高效的相似性搜索]] -现在我们有了一个词嵌入数据集,我们需要一些方法来搜索它们。为此,我们将在 🤗 Datasets中使用一种特殊的数据结构,称为 FAISS指数.[FAISS](https://faiss.ai/) (short for Facebook AI Similarity Search) (Facebook AI Similarity Search 的缩写)是一个提供高效算法来快速搜索和聚类嵌入向量的库。FAISS 背后的基本思想是创建一个特殊的数据结构,称为指数。这允许人们找到哪些嵌入词与输入的词嵌入相似。在 🤗 Datasets中创建一个 FAISS 索引很简单——我们使用 **Dataset.add_faiss_index()** 函数并指定我们要索引的数据集的哪一列: +现在我们有了一个文本嵌入数据集,我们需要一些方法来搜索它们。为此,我们将使用🤗 Datasets 中一种特殊的数据结构,称为 FAISS 指数。 [FAISS](https://faiss.ai/) (Facebook AI Similarity Search 的缩写)是一个库,提供了用于快速搜索和聚类嵌入向量的高效算法。 + +FAISS 背后的基本思想是创建一个特殊的数据结构,称为 `index(索引)` 它可以找到哪些嵌入与输入嵌入相似。在 🤗 Datasets 中创建一个 FAISS index(索引)很简单——我们使用 `Dataset.add_faiss_index()` 函数并指定我们要索引的数据集的哪一列: ```py embeddings_dataset.add_faiss_index(column="embeddings") ``` -现在,我们可以使用**Dataset.get_nearest_examples()**函数进行最近邻居查找。让我们通过首先嵌入一个问题来测试这一点,如下所示: +现在,我们可以使用 `Dataset.get_nearest_examples()` 函数进行最近邻居查找。让我们通过首先嵌入一个 issue 来测试这一点,如下所示: {#if fw === 'pt'} @@ -370,7 +373,7 @@ question_embedding.shape {/if} -就像文档一样,我们现在有一个 768 维向量表示查询,我们可以将其与整个语料库进行比较以找到最相似的嵌入: +就像对文档进行嵌入一样,我们现在有一个表示查询的 768 维向量,我们可以将其与整个语料库进行比较以找到最相似的嵌入: ```py scores, samples = embeddings_dataset.get_nearest_examples( @@ -378,7 +381,7 @@ scores, samples = embeddings_dataset.get_nearest_examples( ) ``` - **Dataset.get_nearest_examples()** 函数返回一个分数元组,对查询和文档之间的相似度进行排序,以及一组最佳匹配的结果(这里是 5 个)。让我们把这些收集到一个 **pandas.DataFrame** 以便我们可以轻松地对它们进行排序: +`Dataset.get_nearest_examples()` 函数返回一个元组,包括评分(评价查询和文档之间的相似程度)和对应的样本(这里是 5 个最佳匹配)。让我们把这些收集到一个 `pandas.DataFrame` ,这样我们就可以轻松地对它们进行排序: ```py import pandas as pd @@ -388,7 +391,7 @@ samples_df["scores"] = scores samples_df.sort_values("scores", ascending=False, inplace=True) ``` -现在我们可以遍历前几行来查看我们的查询与评论的匹配程度: +现在我们可以遍历前几行来查看我们的查询与评论的匹配程度如何: ```py for _, row in samples_df.iterrows(): @@ -456,7 +459,7 @@ COMMENT: > here is my way to load a dataset offline, but it **requires** an onli > > import datasets > -> data = datasets.load_dataset(...) +> data = datasets.load_dataset(.) > > data.save_to_disk(/YOUR/DATASET/DIR) > @@ -506,10 +509,10 @@ URL: https://github.com/huggingface/datasets/issues/824 """ ``` -我们的第二个搜索结果似乎与查询相符。 +不错!我们的输出的第 2 个结果似乎与查询匹配。 -✏️ 试试看!创建您自己的查询并查看您是否可以在检索到的文档中找到答案。您可能需要增加参数k以扩大搜索范围。 +✏️ 试试看!创建你自己的查询并查看你是否可以在检索到的文档中找到答案。你可能需要在 `Dataset.get_nearest_examples()` 增加参数 `k` 以扩大搜索范围。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter5/7.mdx b/chapters/zh-CN/chapter5/7.mdx index 9f42af397..d42422160 100644 --- a/chapters/zh-CN/chapter5/7.mdx +++ b/chapters/zh-CN/chapter5/7.mdx @@ -1,16 +1,16 @@ -# 🤗 Datasets,回顾! [[🤗 Datasets,回顾!]] +# 🤗 Datasets,完结![[🤗 Datasets,完结!]] -这是对 🤗 Datasets 库的一次完整游览——祝贺你走到这一步!凭借从本章中获得的知识,您应该能够: +这是对 🤗 Datasets 库的一次完整的探索——祝贺你走到这一步!凭借从本章中获得的知识,你应该能够: -- 从任何地方加载数据集,无论是 Hugging Face Hub、您的笔记本电脑还是您公司的远程服务器。 -- 混合使用Dataset.map()和Dataset.filter()函数来整理数据。 -- 使用`Dataset.set_format()`在 Pandas 和 NumPy 等数据格式之间快速切换. -- 创建您自己的数据集并将其推送到 Hugging Face Hub。. -- 使用 Transformer 模型为您的文档创建词嵌入,并使用 FAISS 构建语义搜索引擎。. +- 从任何地方加载数据集,无论是 Hugging Face Hub、你的笔记本电脑还是你公司的远程服务器。 +- 混合使用 `Dataset.map()` 和 `Dataset.filter()` 函数来整理数据。 +- 使用 `Dataset.set_format()` 在 Pandas 和 NumPy 等数据格式之间快速切换。 +- 创建你自己的数据集并将其推送到 Hugging Face Hub。 +- 使用 Transformer 模型为你的文档创建文本嵌入,并使用 FAISS 构建语义搜索引擎。 -在[第七章](/course/chapter7),当我们深入研究 Transformer 模型非常适合的核心 NLP 任务时,我们将充分利用所有这些。 \ No newline at end of file +在 [第七章](/course/chapter7) ,我们将把所有这些用于深入研究 Transformer 模型擅长的核心 NLP 任务。不过,在跳到下一步之前,先用一次快速的小测验来检验你对 🤗 Datasets 的了解! \ No newline at end of file diff --git a/chapters/zh-CN/chapter5/8.mdx b/chapters/zh-CN/chapter5/8.mdx index 502c0d8b5..c3cf162ad 100644 --- a/chapters/zh-CN/chapter5/8.mdx +++ b/chapters/zh-CN/chapter5/8.mdx @@ -7,118 +7,118 @@ classNames="absolute z-10 right-0 top-0" /> -本章涵盖了很多方面! 如果你没有掌握所有细节, 不用担心; 在下一章将帮助你了解内部的事情是如何工作的。 +本章涵盖了很多方面!如果你没有掌握所有细节,不用担心;接下来的章节将继续帮助你了解🤗 Datasets 内在运作机制。 -不过, 在继续下一章之前, 让我们测试一下你在本章学到的内容。 +不过,在继续下一章之前,让我们测试一下你在本章学到的内容。 -### 1.🤗 Datasets中的 `load_dataset ()` 函数允许你从下列哪个位置加载数据集? +### 1.🤗 Datasets 中的 `load_dataset ()` 函数允许你从下列哪个位置加载数据集? load_dataset() 函数的 data_files 参数来加载本地数据集。", + explain: "正确!你可以将本地文件的路径传递给 `load_dataset()` 函数的 `data_files` 参数来加载本地数据集。", correct: true }, { text: "Hugging Face Hub", - explain: "正确! 你可以通过提供数据集 ID 在 Hub 上加载数据集, 例如 < code > load _ dataset ('em otion') 。", + explain: "正确!你可以通过提供数据集 ID 来加载 Hub 上的数据集,例如 `load_dataset('emotion')` 。", correct: true }, { text: "远程服务器", - explain: "正确! 你可以将URL传递给 load_dataset() 函数的 data_files 参数来加载远程文件。", + explain: "正确!你可以将 URL 传递给 `load_dataset()` 函数的 `data_files` 参数来加载远程文件。", correct: true }, ]} /> -### 2.假设您加载了 GLUE 任务,如下所示: +### 2.假设你按照以下方式加载了一个 GLUE 任务: ```py from datasets import load_dataset dataset = load_dataset("glue", "mrpc", split="train") ``` -以下哪个命令将从 `dataset` 中生成50个元素的随机样本? +以下哪个命令可以从 `dataset` 中生成 50 个元素的随机样本? dataset.sample (50) ", - explain: "这是不正确的——没有 < code > Dataset.sample () 方法。" + text: " `dataset.sample (50)` ", + explain: "不正确 —— 没有 `Dataset.sample()` 方法。" }, { - text: "dataset.shuffle().select(range(50))", - explain: "正确! 正如你在本章中看待的, 你首先打乱了数据集, 然后从中选择样本。", + text: " `dataset.shuffle().select(range(50))` ", + explain: "正确!正如你在这一章中所学到的,上面的代码首先打乱了数据集,然后从中选择样本。", correct: true }, { - text: "dataset.select(range(50)).shuffle()", - explain: "这是不正确的——尽管代码会运行, 但它只会随机处理数据集中的前50个元素。" + text: " `dataset.select(range(50)).shuffle()` ", + explain: "这是不正确的——尽管代码会运行,但它只会选取数据集的前 50 个元素然后打乱它们。" } ]} /> -### 3.假设你有一个叫做宠物数据集的家庭宠物数据集,它有一个名字列表示每个宠物的名字。下列哪种方法可以让你过滤所有名字以字母"L"开头的宠物的数据? +### 3.假设你有一个关于家庭宠物的数据集 `pets_dataset` ,它有一个 `name` 列表示每个宠物的名字。以下哪种方法可以筛选出所有名字以 "L" 开头的宠物数据? pets _ dataset. filter (lambda x: x ['name'] . startswith ('L')) ", - explain: "正确! 为这些快速过滤使用 Python lambda 函数是一个好主意。你还能想到其他解决方案吗?", + 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*` , 因此在这种情况下需要提供 arguments。" }, { - 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 } ]} /> -### 4.什么是内存映射? +### 4.什么是内存映射? -### 5.下列哪一项是内存映射的主要好处? +### 5.下列哪一项是内存映射的主要好处? -### 6.为什么下面的代码是错误的? +### 6.为什么下面的代码是错误的? ```py from datasets import load_dataset @@ -130,92 +130,92 @@ dataset[0] choices={[ { text: "它试图对一个太大而无法放入 RAM 的数据集进行流式处理。", - explain: "这是不正确的---- 流数据集是动态解压的, 你可以用非常小的RAM处理TB大小的数据集!", + 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) , 可以看到它有那几部分" } ]} /> -### 7.创建数据集卡的主要好处是什么? +### 7.创建数据集卡片的主要好处是什么? -### 8.什么是语义搜索? +### 8.什么是语义搜索? -### 9.对于非对称语义搜索,通常有: +### 9.对于非对称语义搜索,通常有: -### 10.我可以使用数据集加载数据用于其他领域,如语音处理? +### 10.我可以使用🤗 Datasets 来加载用于其他领域(如语音处理)的数据吗? diff --git a/chapters/zh-CN/chapter6/1.mdx b/chapters/zh-CN/chapter6/1.mdx index 077ee3277..960160671 100644 --- a/chapters/zh-CN/chapter6/1.mdx +++ b/chapters/zh-CN/chapter6/1.mdx @@ -5,15 +5,15 @@ classNames="absolute z-10 right-0 top-0" /> -在 [第三章] (/course/chapter3) 中,我们研究了如何在给定任务上微调模型。 当我们这样做时,我们需要使用与模型预训练相同的标记器——但是当我们想从头开始训练模型时该怎么办? 不过,使用在来自其他领域或语言的语料库上预训练的标记器通常不是最理想的。 例如,在英语语料库上训练的标记器在日语文本语料库上表现不佳,因为两种语言中空格和标点符号的使用非常不同。 +在 [第三章](/course/chapter3) 中,我们研究了如何在特定任务上微调模型。当我们需要微调模型时,我们需要使用与模型预训练相同的 tokenizer —— 但是当我们想从头开始训练模型时应该选用哪个 tokenizer ?使用在来自其他领域或语言的语料库上预训练的 tokenizer 通常不是最理想的。例如,在英语语料库上训练的 tokenizer 在日语文本语料库上效果会大打折扣,因为两种语言在空格和标点的使用上有着显著的差异。 -在本章中,您将学习如何在文本语料库上训练一个全新的标记器,然后将其用于预训练语言模型。 这一切都将在 [🤗 Tokenizers](https://github.com/huggingface/tokenizers) 库的帮助下完成,该库在 [🤗 Transformers](https://github.com /huggingface/transformers) 库之内。 我们将仔细研究这个库提供的功能,并探讨快速标记器与“慢”版本的区别。 +在本章中,你将学习如何在一份文本语料库上训练一个全新的 tokenizer,然后将使用它来预训练语言模型。这一切都将在 [🤗 Tokenizers](https://github.com/huggingface/tokenizers) 库的帮助下完成,该库提供了 [🤗 Transformers](https://github.com/huggingface/transformers) 库中的“快速” tokenizer 。 我们将深入探讨这个库所提供的功能,并研究“快速” tokenizer 与“慢速”版本的区别。 -我们将涵盖的主题包括: +本章将涵盖以下主题: -* 如何训练一个新的标记器,类似于给定检查点在新的文本语料库上使用的标记器 -* 快速标记器的特殊功能 -* 目前 NLP 中使用的三种主要子词标记化算法之间的差异 -* 如何使用🤗 Tokenizers 库从头开始构建标记器并在一些数据上对其进行训练 +* 如何在新的文本语料库上训练一个类似于给定 checkpoint 所使用的新 tokenizer +* 快速 tokenizer 的特殊功能 +* 目前 NLP 中使用的三种主要子词 tokenization 算法之间的差异 +* 如何使用🤗 Tokenizers 库从头开始构建 tokenizer 并在一些数据上进行训练 -本章介绍的技术将使您为 [第 7 章](/course/chapter7/6) 中的部分做好准备,在那部分中,我们着眼于为 Python 源代码创建语言模型。 让我们首先看一下什么是“训练”标记器? \ No newline at end of file +本章介绍的技术将使你为 [第七章](/course/chapter7/6) 中的部分做好准备,在那部分中,我们将尝试为 Python 源代码创建语言模型。让我们首先看一下什么是“训练” tokenizer \ No newline at end of file diff --git a/chapters/zh-CN/chapter6/10.mdx b/chapters/zh-CN/chapter6/10.mdx index b1b485952..4cd7b16ef 100644 --- a/chapters/zh-CN/chapter6/10.mdx +++ b/chapters/zh-CN/chapter6/10.mdx @@ -1,4 +1,4 @@ - + # 章末小测验 [[章末小测验]] @@ -7,229 +7,230 @@ classNames="absolute z-10 right-0 top-0" /> -让我们测试一下您在本章中学到了什么! +让我们测试一下你在本章中学到了什么! -### 1.你应该什么时候训练一个新的标记器? +### 1.在什么时候你应该考虑训练一个新的 tokenizer ? -### 2.当使用“ train_new_from_iterator()”时,使用文本列表生成器与文本列表相比有什么优点? +### 2.当使用 `train_new_from_iterator()` 时,使用文本列表生成器与文本列表相比有什么优点? train_new_from_iterator() 接受的唯一类型。", + text: "文本列表生成器是`train_new_from_iterator()`方法唯一接受的输入类型。", explain: "文本列表是一种特殊的文本列表生成器,因此该方法也会接受这种方法。再试一次!" }, { - text: "您将避免立即将整个数据集载入内存中。", - explain: "没错!每一批文本都会在你迭代的时候从内存中释放出来,如果你使用数据集存储文本的话,增益将尤其明显。", + text: "你可以避免一次性将整个数据集加载到内存中。", + explain: "正确!每个 batch 的文本在迭代时都将从内存中释放,如果你使用🤗 Datasets 存储你的文本,你会看到特别明显的收益。", correct: true }, { - text: "这将允许 Tokenizers 库使用并行处理。", - explain: "不,无论如何它都将使用并行处理。" + text: "这将允许🤗 Tokenizers 库使用多进程。", + explain: "不,即使随时文本列表它也会使用多进程。" }, { - text: "你训练的标记器将产生更好的文本。", - explain: "Tokenizer 不生成文本——您是否将其与语言模型混淆了?" + text: "你训练的 Tokenizer 将生成效果更好的输出。", + explain: "Tokenizer 不生成文本——你是否将其与语言模型混淆了?" } ]} /> -### 3.使用“快速”标记器的优点是什么? +### 3.使用“快速” tokenizer 有什么优势? + -### 4.“token-classification”管道如何处理跨多个标记的实体? +### 4. `token-classification` 管道如何处理跨越多个 tokens 的实体? -### 5.“question-answering”流水线如何处理长上下文? +### 5. `question-answering` 管道如何处理长上下文? -### 6.什么是标准化? +### 6.什么是标准化? -### 7.什么是子词标记化的前标记化? +### 7.什么是 tokenizer 的预分词? -### 8.选择描述标记化 BPE 模式最准确的句子。 +### 8.选择描述 BPE 算法最准确的句子。 -### 9.选择适用于 WordPiece 标记模型的句子。 +### 9.选择描述 WordPiece 算法最准确的句子。 -### 10.选择适用于 Unigram 标记模式的句子。 +### 10.选择描述 Unigram 算法最准确的句子。 diff --git a/chapters/zh-CN/chapter6/2.mdx b/chapters/zh-CN/chapter6/2.mdx index 87589bd04..1c24c35f4 100644 --- a/chapters/zh-CN/chapter6/2.mdx +++ b/chapters/zh-CN/chapter6/2.mdx @@ -1,4 +1,4 @@ -# 根据已有的tokenizer训练新的tokenizer [[根据已有的tokenizer训练新的tokenizer]] +# 基于已有的 tokenizer 训练新的 tokenizer [[基于已有的 tokenizer 训练新的 tokenizer]] -如果您感兴趣的语言中没有可用的语言模型,或者如果您的语料库与您的语言模型所训练的语料库有很大不同,您很可能希望从适合您的数据的标记器从头开始重新训练模型 . 这将需要在您的数据集上训练一个新的标记器。 但这究竟是什么意思? 当我们在 [第二章](/course/chapter2) 中第一次查看标记器时,我们看到大多数 Transformer 模型使用_子词分词算法_。 为了识别哪些子词是感兴趣的并且在手头的语料库中最常出现,标记器需要仔细查看语料库中的所有文本——我们称之为*training*的过程。 这种训练的确切规则取决于所使用的标记器的类型,我们将在本章后面讨论三种主要算法。 +如果你感兴趣的语言中没有可用的语言模型,或者你的语料库与语言模型训练时所使用的语料库差异很大,你可能需要从零开始重新训练一个适应你的数据的 tokenizer 模型。训练一个新的 tokenizer 是什么意思呢?从我们在 [第二章](/course/chapter2) 中第一次看到 tokenizer 开始,我们看到大多数 Transformer 模型使用 `子词分词算法` 。为了找到语料库中的常见子词,tokenizer 需要深入统计语料库中的所有文本——这个过程我们称之为 `训练 (training)` 。具体的训练规则取决于使用的 tokenizer 类型,我们将在本章后面的部分详细介绍三种主要算法。 -⚠️ 训练标记器与训练模型不同!模型训练使用随机梯度下降使每个batch的loss小一点。它本质上是随机的(这意味着在进行两次相同的训练时,您必须设置一些随机数种子才能获得相同的结果)。训练标记器是一个统计过程,它试图确定哪些子词最适合为给定的语料库选择,用于选择它们的确切规则取决于分词算法。它是确定性的,这意味着在相同的语料库上使用相同的算法进行训练时,您总是会得到相同的结果。 +⚠️ 训练 tokenizer 与训练模型不同!模型训练使用随机梯度下降使每个 batch 的 loss 小一点。它本质上是随机的(这意味着在即使两次训练的参数和算法完全相同,你也必须设置一些随机数种子才能获得相同的结果)。训练 tokenizer 是一个统计过程,它试图确定哪些子词最适合为给定的语料库选择,确定的过程取决于分词算法。它是确定性的,这意味着在相同的语料库上使用相同的算法进行训练时,得到的结果总是相同的。 ## 准备语料库 [[准备语料库]] -🤗 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 部分: +在🤗 Transformers 中,有一个非常简单的 API 可以让你从旧的 tokenizer 训练一个新的 tokenizer 且新的 tokenizer 具有和旧 tokenizer 相同的特性,它就是: `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 部分: ```py from datasets import load_dataset -# This can take a few minutes to load, so grab a coffee or tea while you wait! +# 加载这个可能需要几分钟的时间,你可以趁此喝杯咖啡或茶! raw_datasets = load_dataset("code_search_net", "python") ``` -我们可以查看训练集的部分,以查看我们数据集中有哪些列: +我们可以查看训练集部分来看我们可以使用哪些列: ```py raw_datasets["train"] @@ -46,13 +45,14 @@ Dataset({ }) ``` -我们可以看到数据集将文档字符串与代码分开,并且有他们各自的标记化后的结果。 这里。 我们将只使用 `whole_func_string` 列来训练我们的标记器。 我们可以通过指定到 `train` 中的一部分来查看这些函数的一个示例: +我们可以看到数据集把函数的文档说明(func_documentation_string)与代码(func_code_string)分开保存,并提供了一个可以参考的分词后的结果(func_code_tokens)。在这里,我们仅使用 `whole_func_string` 列来训练我们的 tokenizer 我们可以通过索引来查看其中一个函数的示例: + ```py print(raw_datasets["train"][123456]["whole_func_string"]) ``` -应该打印以下内容: +应该输出以下内容: ```out def handle_simple_responses( @@ -69,16 +69,19 @@ def handle_simple_responses( return self._accept_responses('OKAY', info_cb, timeout_ms=timeout_ms) ``` -我们需要做的第一件事是将数据集转换为迭代器文本列表 - 例如,文本列表。使用文本列表将使我们的标记器运行得更快(训练成批文本而不是一个接一个地处理单个文本),如果我们想避免一次将所有内容都放在内存中,它应该是一个迭代器。如果你的语料库很大,你会想要利用这样一个特性:🤗 Datasets 不会将所有内容都加载到 RAM 中,而是将数据集的元素存储在磁盘上。 +我们首先需要做的是将数据集转换为一个文本列表的 `迭代器` 例如,文本列表的列表。使用文本列表会使我们的 tokenizer 运行得更快(这样可以以文本批次为单位进行训练,而不是一次处理一个文本),并且使用迭代器可以不把所有内容都加载到内存中。如果你的语料库很大,你可能会想利用🤗 Datasets 将数据集的元素存储在磁盘上分批加载,而不是将所有内容加载到 RAM 的特性。 -执行以下操作将创建一个包含 1,000 个文本的列表的列表,但会将所有内容加载到内存中: +下面的操作会创建一个由每个列包含 1000 个文本组成的文本列表,但会将所有内容加载到内存中: ```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)] +# 除非你的数据集很小,否则不要直接运行下面的代码! +# 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 = ( @@ -87,9 +90,9 @@ training_corpus = ( ) ``` -这行代码不会获取数据集的任何元素;它只是创建了一个可以在 Python 中使用的对象 **for** 环形。文本只会在您需要时加载(即,当您处于 **for** 需要它们的循环),并且一次只会加载 1,000 个文本。这样,即使您正在处理庞大的数据集,也不会耗尽所有内存。 +这行代码不会加载数据集的任何元素;它只创建了一个你可以在 Python for 循环中使用的对象。只有当你使用它们(即,当你在 `for` 循环尝试访问他们)时,文本才会被加载,而且一次只会加载 1000 个文本。这样,即使你在处理大型数据集,也不会耗尽所有内存。 -生成器对象的问题在于它只能使用一次,每次访问它将给出下一个值。 下面是一个例子: +生成器对象的问题是它只能被使用一次。让我们尝试获取 2 次 10 个数字组成的列表: ```py gen = (i for i in range(10)) @@ -97,14 +100,14 @@ print(list(gen)) print(list(gen)) ``` -我们第一次得到了这个列表,然后是一个空列表: +我们第一次得到了这个列表,第二次我们得到了一个空列表: ```python out [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] [] ``` -这就是我们定义一个返回生成器的函数的原因: +这就是为什么我们需要定义一个返回生成器的函数。通过每次调用函数生成一个新的生成器对象,我们可以多次使用生成器而不会遇到只能使用一次的限制。 ```py def get_training_corpus(): @@ -117,7 +120,7 @@ def get_training_corpus(): training_corpus = get_training_corpus() ``` -您还可以在一个 **for** 循环内部使用 **yield** 关键字定义您的生成器: +你还可以在一个 `for` 循环内部使用 `yield` 关键字定义你的生成器: ```py def get_training_corpus(): @@ -127,11 +130,11 @@ def get_training_corpus(): yield samples["whole_func_string"] ``` -这将产生与以前完全相同的生成器,但允许您使用比列表生成式中更复杂的逻辑。 +这将得到和上面列表生成器完全相同的生成器,但允许你在迭代器的过程中添加更复杂的逻辑。 -## 训练一个新的标记器 [[训练一个新的标记器]] +## 训练一个新的 tokenizer [[训练一个新的 tokenizer ]] -现在我们的语料库是文本批量迭代器的形式,我们准备训练一个新的标记器。为此,我们首先需要加载要与模型配对的标记器(此处为 GPT-2): +现在我们已经将文本转化为迭代器形式准备好了我们的语料库,我们就可以开始训练新的 tokenizer 了。首先,我们需要加载我们想要与我们的模型匹配的 tokenizer (这我们这个例子中是 GPT-2): ```py from transformers import AutoTokenizer @@ -139,9 +142,9 @@ from transformers import AutoTokenizer old_tokenizer = AutoTokenizer.from_pretrained("gpt2") ``` -即使我们要训练一个新的标记器,最好还是这样做以避免完全从头开始。这样,我们就不必指定任何关于标记化算法或我们想要使用的特殊标记;我们的新标记器将与 GPT-2 完全相同,唯一会改变的是输入的数据,这将取决于我们训练的语料。 +尽管我们要训练一个新的 tokenizer,但从旧的 tokenizer 开始初始化依然是个不错的主意,这样,我们就不必指定具体的 tokenization 算法或设置我们想要使用的特殊 tokens;我们新的 tokenizer 将与 GPT-2 完全相同,唯一的区别是词汇表,这将由我们的语料库通过训练来重新确定。 -首先让我们看看这个标记器将如何处理示例的数据: +首先让我们看看旧的 tokenizer 将如何处理示例的数据: ```py example = '''def add_numbers(a, b): @@ -157,20 +160,21 @@ tokens 'Ġnumbers', 'Ġ`', 'a', '`', 'Ġand', 'Ġ`', 'b', '`', '."', '""', 'Ċ', 'Ġ', 'Ġ', 'Ġ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb'] ``` -这个标记器有一些特殊的符号,比如 **Ċ** 和 **Ġ** ,分别表示空格和换行符。正如我们所看到的,这不是太有效:标记器为每个空格返回单独的标记,当它可以将缩进级别组合在一起时(因为在代码中具有四个或八个空格的集合将非常普遍)。它也有点奇怪地拆分了函数名称,而习惯使用**_**的函数命名的方法。 +这个 tokenizer 输出了一些特殊的符号,比如 `Ċ` 和 `Ġ` ,分别表示空格和换行符。正如我们所看到的,这并不是非常高效:tokenizer 将每个空格视作为单独的 token,其实它可以将缩进级别组合在一起时(因为在代码中经常出现相邻在一起的四个或八个空格)。它也有点奇怪地拆分了函数名称,对使用 `_` 命名方法的函数并不友好。 -让我们训练一个新的标记器,看看它是否能解决这些问题。为此,我们将使用 **train_new_from_iterator()** 方法: +让我们训练一个新的 tokenizer 看看它是否能解决这些问题。为此,我们将使用 `train_new_from_iterator()` 方法: ```py tokenizer = old_tokenizer.train_new_from_iterator(training_corpus, 52000) ``` -如果您的语料库非常大,此命令可能需要一些时间,但对于这个 1.6 GB 文本数据集,它的速度非常快(在具有 12 个内核的 AMD Ryzen 9 3900X CPU 上为 1 分 16 秒)。 -注意 **AutoTokenizer.train_new_from_iterator()** 仅当您使用的标记器是“快速(fast)”标记器时才有效。正如您将在下一节中看到的,🤗 Transformers 库包含两种类型的标记器:一些完全用 Python 编写,而另一些(快速的)由 🤗 Tokenizers 库支持,该库用[Rust](https://www.rust-lang.org)编程语言编写。 Python 是最常用于数据科学和深度学习应用程序的语言,但是当需要并行化以提高速度时,必须用另一种语言编写。例如,模型计算核心的矩阵乘法是用 CUDA 编写的,CUDA 是一个针对 GPU 的优化 C 库。 +如果你的语料库非常大,此命令可能需要一些时间,但对于这个 1.6 GB 文本数据集,它的速度非常快(在具有 12 个内核的 AMD Ryzen 9 3900X CPU 上仅需 1 分 16 秒)。 + +注意 `AutoTokenizer.train_new_from_iterator()` 只有你使用的 tokenizer 是“快速(fast)” tokenizer 时才有效。下一节中,你将在看到,🤗 Transformers 库包含两种类型的 tokenizer 一些(慢速的)完全用 Python 编写,而另一些(快速的)由 🤗 Tokenizers 库支持,该库用 [Rust](https://www.rust-lang.org) 编程语言编写。Python 是最常用于数据科学和深度学习应用程序的语言,但是当需要并行化以提高速度时,就需要用另一种语言来编写。例如,模型计算核心的矩阵乘法是用 CUDA 编写的,这是一个针对 GPU 优化的 C 语言库。 -用纯 Python 训练一个全新的标记器会非常缓慢,这就是我们开发 🤗 Tokenizers库的原因。请注意,正如您无需学习 CUDA 语言即可在 GPU 上执行您的模型一样,您也无需学习 Rust 即可使用快速标记器。 🤗 Tokenizers 库为许多内部调用 Rust 代码的方法提供 Python 绑定;例如,并行化新标记器的训练,或者,正如我们在[第三章](/course/chapter3)中看到的,对一批输入进行标记化。 +用纯 Python 训练一个全新的 tokenizer 会非常缓慢,这就是我们开发 🤗 Tokenizers 库的原因。正如你无需学习 CUDA 语言即可在 GPU 上训练你的模型一样,你也无需学习 Rust 即可使用快速 tokenizer。🤗 Tokenizers 库为许多内部调用 Rust 代码的方法提供 Python 语言绑定;例如,并行化训练新的 tokenizer 或者像我们在 [第三章](/course/chapter3) 中看到的那样,对一批输入进行 tokenize。 -大多数 Transformer 模型都有可用的快速标记器(您可以[在这里](https://huggingface.co/transformers/#supported-frameworks)检查一些例外情况),如果 **AutoTokenizer** 可用,API 总是为您选择快速标记器。在下一节中,我们将看看快速标记器具有的其他一些特殊功能,这些功能对于标记分类和问答等任务非常有用。然而,在深入研究之前,让我们在上一个示例中尝试我们全新的标记器: +大多数 Transformer 模型都有可用的快速 tokenizer (你可以 [在这里](https://huggingface.co/transformers/#supported-frameworks) 检查一些例外情况),如果 `AutoTokenizer` 可用,API 默认为你选择快速 tokenizer 在下一节中,我们将看看快速 tokenizer 具有的其他一些特殊功能,这些功能对于 token 分类和问答等任务非常有用。然而,在深入研究之前,让我们尝试在之前的例子上使用我们的全新 tokenizer ```py tokens = tokenizer.tokenize(example) @@ -182,7 +186,7 @@ tokens 'a', '`', 'Ġand', 'Ġ`', 'b', '`."""', 'ĊĠĠĠ', 'Ġreturn', 'Ġa', 'Ġ+', 'Ġb'] ``` -在这里我们再次看到特殊符号 **Ċ** 和 **Ġ** 表示空格和换行符,但我们也可以看到我们的标记器学习了一些高度特定于 Python 函数语料库的标记:例如,有一个 **ĊĠĠĠ** 表示缩进的标记,以及 **Ġ** 表示开始文档字符串的三个引号的标记。标记器还正确使用**_**命名的规范将函数名称拆分为 .这是一个非常紧凑的表示;相比之下,在同一个例子中使用简单的英语标记器会给我们一个更长的句子: +在这里我们再次看到了表示空格和换行符的特殊符号 `Ċ` 和 `Ġ` ,但我们也可以看到我们的 tokenizer 学习了一些专属于 Python 函数语料库的 token:例如,有一个 `ĊĠĠĠ`token 表示缩进,以及 `Ġ` token 表示开始文档字符串的三个引号。tokenizer 也正确地在 `_` 上拆分了函数名称。这是一个非常紧凑的表示;相比之下,使用简单的英语 tokenizer 会得到一个更长的句子: ```py print(len(tokens)) @@ -216,17 +220,17 @@ tokenizer.tokenize(example) 'Ġreturn', 'Ġx', 'Ġ@', 'Ġself', '.', 'weights', 'Ġ+', 'Ġself', '.', 'bias', 'ĊĠĠĠĠ'] ``` -除了一个缩进对应的token,这里我们还可以看到一个双缩进的token: **ĊĠĠĠĠĠĠĠ** .特殊的 Python 词如 **class** , **init** , **call** , **self** , 和 **return** 每个都被标记为一个标记,我们可以看到,以及分裂 **_** 和 **.** 标记器甚至可以正确拆分驼峰式名称: **LinearLayer** 被标记为 **[ĠLinear, Layer]** . +除了与缩进对应的 token 外,这里我们还可以看到一个与双缩进对应的 token : `ĊĠĠĠĠĠĠĠ` 。特殊的 Python 关键词如 `class` , `init` , `call` , `self` ,和 `return` 每个都被分配了一个 token ,我们还可以看到,除了可以在 `_` 和 `.` 上正确拆分,tokenizer 甚至可以正确拆分驼峰法命名的名称: `LinearLayer` 被分词为 `[ĠLinear, Layer]` 。 -## 保存标记器 [[保存标记器]] +## 保存 tokenizer [[保存 tokenizer ]] -为了确保我们以后可以使用它,我们需要保存我们的新标记器。就像模型一样,是通过 **save_pretrained()** 方法: +为了确保我们以后可以使用它,我们需要保存我们的新 tokenizer 。像模型一样,是通过 `save_pretrained()` 方法进行保存: ```py tokenizer.save_pretrained("code-search-net-tokenizer") ``` -这将创建一个名为的*code-search-net-tokenizer*的新文件夹,它将包含重新加载标记器所需要的所有文件。如果您想与您的同事和朋友分享这个标记器,您可以通过登录您的帐户将其上传到 Hub。如果您在notebook上工作,有一个方便的功能可以帮助您: +这将创建一个名为的 `code-search-net-tokenizer` 的新文件夹,它将包含重新加载 tokenizer 所需要的所有文件。如果你想与你的同事和朋友分享这个 tokenizer 你可以通过登录你的帐户将其上传到 Hub。如果你在 notebook 上工作,有一个便捷的功能可以帮助你: ```python from huggingface_hub import notebook_login @@ -234,23 +238,23 @@ from huggingface_hub import notebook_login notebook_login() ``` -这将显示一个小部件,您可以在其中输入您的 Hugging Face 登录凭据。如果您不是在notebook上工作,只需在终端中输入以下行: +这将显示一个小部件,你可以在其中输入你的 Hugging Face 账号密码。如果你不是在 notebook 上工作,只需在终端中输入以下行: ```bash huggingface-cli login ``` -登录后,您可以通过执行以下命令来推送您的标记器: +登录后,你可以通过执行以下命令来推送你的 tokenizer ```py tokenizer.push_to_hub("code-search-net-tokenizer") ``` -这将在您的命名空间中创建一个名为**code-search-net-tokenizer**的新存储库 ,包含标记器文件。然后,您可以使用以下命令从任何地方加载标记器的 **from_pretrained()** 方法: +这将在你的账户中创建一个名为 `code-search-net-tokenizer` 的新仓库,其中将包含 tokenizer 文件。然后,你可以使用 tokenizer 的 `from_pretrained()` 方法从任何地方加载 tokenizer 。 ```py -# Replace "huggingface-course" below with your actual namespace to use your own tokenizer +# 将下面的 "huggingface-course" 替换为你的用户名来加载你的 tokenizer tokenizer = AutoTokenizer.from_pretrained("huggingface-course/code-search-net-tokenizer") ``` -您现在已准备好从头开始训练语言模型并根据您手头的任务对其进行微调!我们将在[第七章](/course/chapter7)进行这部分。但首先,在本章的其余部分,我们将仔细研究快速标记器,并详细探讨调用 **train_new_from_iterator()** 方法时实际发生的情况 . +你现在已准备好从头开始训练语言模型并根据你手头的任务对其进行微调!我们将在 [第七章](/course/chapter7) 进行这部分。在本章的剩余部分,我们将仔细研究快速 tokenizer 并详细探讨调用 `train_new_from_iterator()` 方法时到底在幕后发生了什么。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter6/3.mdx b/chapters/zh-CN/chapter6/3.mdx index 2cdcc95e3..1a9fea14a 100644 --- a/chapters/zh-CN/chapter6/3.mdx +++ b/chapters/zh-CN/chapter6/3.mdx @@ -1,6 +1,6 @@ -# 快速标记器的特殊能力 [[快速标记器的特殊能力]] +# 快速 tokenizer 的特殊能力 [[快速 tokenizer 的特殊能力]] {#if fw === 'pt'} @@ -22,20 +22,18 @@ {/if} -在本节中,我们将仔细研究 🤗 Transformers 中标记器的功能。到目前为止,我们只使用它们来标记输入或将 ID 解码回文本,但是标记器——尤其是那些由 🤗 Tokenizers 库支持的——可以做更多的事情。为了说明这些附加功能,我们将探索如何重现结果 **token-classification** (我们称之为 **ner** ) 和 **question-answering** 我们第一次在[Chapter 1](/course/chapter1)中遇到的管道. +在本节中,我们将仔细研究 🤗 Transformers 中 tokenizer 的功能。到目前为止,我们只使用它们来对文本进行 tokenize 或将token ID 解码回文本,但是 tokenizer —— 特别是由🤗 Tokenizers 库支持的 tokenizer —— 能够做的事情还有很多。为了说明这些附加功能,我们将探讨如何复现在 [第一章](/course/chapter1) 中首次遇到的 `token-classification` (我们称之为 `ner` ) 和 `question-answering` 管道的结果。 -在接下来的讨论中,我们会经常区分“慢”和“快”分词器。慢速分词器是在 🤗 Transformers 库中用 Python 编写的,而快速版本是由 🤗 分词器提供的,它们是用 Rust 编写的。如果你还记得在[Chapter 5](/course/chapter5/3)中报告了快速和慢速分词器对药物审查数据集进行分词所需的时间的这张表,您应该知道为什么我们称它们为“快”和“慢”: +在接下来的讨论中,我们会经常区分“慢速”和“快速” tokenizer 。慢速 tokenizer 是在 🤗 Transformers 库中用 Python 编写的,而快速版本是由 🤗 Tokenizers 提供的,它们是用 Rust 编写的。如果你还记得在 [第五章](/course/chapter5/3) 中对比了快速和慢速 tokenizer 对药物审查数据集进行 tokenize 所需的时间的这张表,你应该理解为什么我们称它们为“快速”和“慢速”: -| | Fast tokenizer | Slow tokenizer -:--------------:|:--------------:|:-------------: -`batched=True` | 10.8s | 4min41s -`batched=False` | 59.2s | 5min3s +| | 快速 tokenizer | 慢速 tokenizer +:--------------:|:--------------:|:-------------: `batched=True` | 10.8s | 4min41s `batched=False` | 59.2s | 5min3s -⚠️ 对单个句子进行分词时,您不会总是看到相同分词器的慢速和快速版本之间的速度差异。事实上,快速版本实际上可能更慢!只有同时对大量文本进行标记时,您才能清楚地看到差异。 +⚠️ 对单个句子进行 tokenize 时,你不总是能看到同一个 tokenizer 的慢速和快速版本之间的速度差异。事实上,快速版本可能更慢!只有同时对大量文本进行 tokenize 时,你才能清楚地看到差异。 @@ -43,9 +41,11 @@ -分词器的输出不是简单的 Python 字典;我们得到的实际上是一个特殊的 **BatchEncoding** 目的。它是字典的子类(这就是为什么我们之前能够毫无问题地索引到该结果中的原因),但具有主要由快速标记器使用的附加方法。 +tokenizer 的输出不是简单的 Python 字典;我们得到的实际上是一个特殊的 `BatchEncoding` 对象。它是字典的子类(这就是为什么我们之前能够直接使用索引获取结果的原因),但是它还提供了一些主要由快速 tokenizer 提供的附加方法。 -除了它们的并行化能力之外,快速标记器的关键功能是它们始终跟踪最终标记来自的原始文本范围——我们称之为偏移映射.这反过来又解锁了诸如将每个单词映射到它生成的标记或将原始文本的每个字符映射到它内部的标记等功能,反之亦然。让我们看一个例子: +除了它们的并行化能力之外,快速 tokenizer 的关键功能是它们始终跟踪最终 token 相对于的原始文本的映射——我们称之为 `偏移映射(offset mapping)` 。这反过来又解锁了如将每个词映射到它生成的 token,或者将原始文本的每个字符映射到它所在的 token 等功能。 + +让我们看一个例子: ```py from transformers import AutoTokenizer @@ -56,13 +56,13 @@ encoding = tokenizer(example) print(type(encoding)) ``` -如前所述,我们得到一个 **BatchEncoding** 标记器输出中的对象: +如前所述,我们从 tokenizer 得到了一个 `BatchEncoding` 对象: ```python out ``` -由于 **AutoTokenizer** 类默认选择快速标记器,我们可以使用附加方法 this **BatchEncoding** 对象提供。我们有两种方法来检查我们的分词器是快的还是慢的。我们可以检查 **is_fast** 的属性 **tokenizer** : +由于 `AutoTokenizer` 类默认选择快速 tokenizer 因此我们可以使用 `BatchEncoding` 对象提供的附加方法。我们有两种方法来检查我们的 tokenizer 是快速的还是慢速的。我们可以检查 `tokenizer` 的 `is_fast` 属性: ```python tokenizer.is_fast @@ -72,7 +72,7 @@ tokenizer.is_fast True ``` -或检查我们的相同属性 **encoding** : +或检查我们 `encoding` 的 `is_fast` 属性: ```python encoding.is_fast @@ -82,7 +82,7 @@ encoding.is_fast True ``` -让我们看看快速标记器使我们能够做什么。首先,我们可以访问令牌而无需将 ID 转换回令牌: +让我们看看快速 tokenizer 能让为我们提供什么功能。首先,我们可以直接得到Ttokenization 之前的单词而无需将 ID 转换回单词: ```py encoding.tokens() @@ -93,7 +93,7 @@ encoding.tokens() 'Brooklyn', '.', '[SEP]'] ``` -在这种情况下,索引 5 处的令牌是 **##yl** ,它是原始句子中“Sylvain”一词的一部分。我们也可以使用 **word_ids()** 获取每个标记来自的单词索引的方法: +在这种情况下,索引 5 处的 token 是 `##yl` ,它是原始句子中“Sylvain”一词的一部分。我们也可以使用 `word_ids()` 方法来获取每个 token 原始单词的索引: ```py encoding.word_ids() @@ -103,19 +103,19 @@ encoding.word_ids() [None, 0, 1, 2, 3, 3, 3, 3, 4, 5, 6, 7, 8, 8, 9, 10, 11, 12, None] ``` -我们可以看到分词器的特殊标记 **[CLS]** 和 **[SEP]** 被映射到 **None** ,然后每个标记都映射到它起源的单词。这对于确定一个标记是否在单词的开头或两个标记是否在同一个单词中特别有用。我们可以依靠 **##** 前缀,但它仅适用于类似 BERT 的分词器;这种方法适用于任何类型的标记器,只要它是快速的。在下一章中,我们将看到如何使用此功能将每个单词的标签正确应用于命名实体识别 (NER) 和词性 (POS) 标记等任务中的标记。我们还可以使用它来屏蔽来自屏蔽语言建模中来自同一单词的所有标记(一种称为全词掩码)。 +我们可以看到 tokenizer 的特殊 token `[CLS]` 和 `[SEP]` 被映射到 `None` ,然后每个 token 都映射到它来源的单词。这对于确定一个 token 是否在单词的开头或两个 token 是否在同一个单词中特别有用。对于 BERT 类型(BERT-like)的的 tokenizer 我们也可以依靠 `##` 前缀来实现这个功能;不过只要是快速 tokenizer 它所提供的 `word_ids()` 方法适用于任何类型的 tokenizer 。在下一章,我们将看到如何利用这种能力,将我们为每个词正确地对应到词汇任务中的标签,如命名实体识别(NER)和词性标注(POS)。我们也可以使用它在掩码语言建模(masked language modeling)中来遮盖来自同一词的所有 token(一种称为 `全词掩码(whole word masking)` 的技术)。 -一个词是什么的概念很复杂。例如,“I'll”(“I will”的缩写)算一两个词吗?它实际上取决于分词器和它应用的预分词操作。一些标记器只是在空格上拆分,因此他们会将其视为一个词。其他人在空格顶部使用标点符号,因此将其视为两个词。 +词的概念是复杂的。例如,“I'll”(“I will”的缩写)算作一个词还是两个词?这实际上取决于 tokenizer 和它采用的预分词操作。有些 tokenizer 只在空格处分割,所以它们会把这个看作是一个词。有些其他 tokenizer 在空格的基础之上还使用标点,所以会认为它是两个词。 -✏️ 试试看!从bert base cased和roberta base检查点创建一个标记器,并用它们标记“81s”。你观察到了什么?ID这个词是什么? +✏️ **试试看!**从 `bert base cased` 和 `roberta base` checkpoint 创建一个 tokenizer 并用它们对“81s”进行分词。你观察到了什么?这些词的 ID 是什么? -同样,有一个 **sentence_ids()** 我们可以用来将标记映射到它来自的句子的方法(尽管在这种情况下, **token_type_ids** 分词器返回的信息可以为我们提供相同的信息)。 +同样,我们还有一个 `sentence_ids()` 方法,可以用它把一个 token 映射到它原始的句子(尽管在这种情况下,tokenizer 返回的 `token_type_ids`也可以为我们提供相同的信息)。 -最后,我们可以将任何单词或标记映射到原始文本中的字符,反之亦然,通过 **word_to_chars()** 或者 **token_to_chars()** 和 **char_to_word()** 或者 **char_to_token()** 方法。例如, **word_ids()** 方法告诉我们 **##yl** 是索引 3 处单词的一部分,但它是句子中的哪个单词?我们可以这样发现: +最后,我们可以通过 `word_to_chars()` 或 `token_to_chars()` 和 `char_to_word()` 或 `char_to_token()` 方法,将任何词或 token 映射到原始文本中的字符,反之亦然。例如, `word_ids()` 方法告诉我们 `##yl` 是索引 3 处单词的一部分,但它是句子中的哪个单词?我们可以这样找出来: ```py start, end = encoding.word_to_chars(3) @@ -126,17 +126,18 @@ example[start:end] Sylvain ``` -正如我们之前提到的,这一切都是由快速标记器跟踪每个标记来自列表中的文本跨度这一事实提供支持的抵消.为了说明它们的用途,接下来我们将向您展示如何复制结果 **token-classification** 手动管道。 +如前所述,这一切都是由于快速分词器跟踪每个 token 来自的文本范围的一组*偏移*。为了阐明它们的作用,接下来我们将展示如何手动复现 `token-classification` 管道的结果。 + -✏️ 试试看!创建您自己的示例文本,看看您是否能理解哪些标记与单词 ID 相关联,以及如何提取单个单词的字符跨度。对于奖励积分,请尝试使用两个句子作为输入,看看句子 ID 是否对您有意义。 +✏️ **试试看!** 使用自己的文本,看看你是否能理解哪些 token 与单词 ID 相关联,以及如何提取单个单词的字符跨度。附加题:请尝试使用两个句子作为输入,看看句子 ID 是否对你有意义。 -## 在令牌分类管道内 [[在令牌分类管道内]] +## `token-classification` 管道内部流程 [[`token-classification`管道内部流程]] -在[Chapter 1](/course/chapter1)我们第一次尝试使用 NER——任务是识别文本的哪些部分对应于个人、地点或组织等实体——使用 🤗 Transformers **pipeline()** 功能。然后,在[Chapter 2](/course/chapter2),我们看到了管道如何将从原始文本中获取预测所需的三个阶段组合在一起:标记化、通过模型传递输入和后处理。前两步 **token-classification** 管道与任何其他管道相同,但后处理稍微复杂一些 - 让我们看看如何! +在 [第一章](/course/chapter1) 我们初次尝试实现命名实体识别(NER)——该任务是确定文本的哪些部分对应于人名、地名或组织名等实体——当时是使用🤗 Transformers 的 `pipeline()` 函数实现的。然后,在 [第二章](/course/chapter2) ,我们看到一个管道如何将获取原始文本到预测结果的三个阶段整合在一起:tokenize、通过模型处理输入和后处理。 `token-classification` 管道中的前两步与其他任何管道中的步骤相同,但后处理稍有复杂——让我们看看具体情况! {#if fw === 'pt'} @@ -148,9 +149,9 @@ Sylvain {/if} -### 通过管道获得基本结果 [[通过管道获得基本结果]] +### 使用管道获得基本结果 [[使用管道获得基本结果]] -首先,让我们获取一个标记分类管道,以便我们可以手动比较一些结果。默认使用的模型是[dbmdz/bert-large-cased-finetuned-conll03-english](https://huggingface.co/dbmdz/bert-large-cased-finetuned-conll03-english);它对句子执行 NER: +首先,让我们获取一个 token 分类管道,以便我们可以手动比较一些结果。这次我们选用的模型是 [`dbmdz/bert-large-cased-finetuned-conll03-english`](https://huggingface.co/dbmdz/bert-large-cased-finetuned-conll03-english) ;我们使用它对句子进行 NER: ```py from transformers import pipeline @@ -170,7 +171,7 @@ token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.") {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn', 'start': 49, 'end': 57}] ``` -该模型正确地将“Sylvain”生成的每个标记识别为一个人,将“Hugging Face”生成的每个标记识别为一个组织,将“Brooklyn”生成的标记识别为一个位置。我们还可以要求管道将对应于同一实体的令牌组合在一起: +模型正确地识别出:“Sylvain”是一个人,“Hugging Face”是一个组织,以及“Brooklyn”是一个地点。我们也可以让管道将同一实体的 token 组合在一起: ```py from transformers import pipeline @@ -185,21 +186,19 @@ token_classifier("My name is Sylvain and I work at Hugging Face in Brooklyn.") {'entity_group': 'LOC', 'score': 0.99321055, 'word': 'Brooklyn', 'start': 49, 'end': 57}] ``` -**aggregation_strategy** 选择将更改为每个分组实体计算的分数。和 **simple** 分数只是给定实体中每个标记的分数的平均值:例如,“Sylvain”的分数是我们在前面的示例中看到的标记分数的平均值 **S** , **##yl** , **##va** , 和 **##in** .其他可用的策略是: - -- `"first"`, 其中每个实体的分数是该实体的第一个标记的分数(因此对于“Sylvain”,它将是 0.993828,标记的分数) +选择不同的 `aggregation_strategy` 可以更改每个分组实体计算的策略。对于 `simple` 策略,最终的分数就是给定实体中每个 token 的分数的平均值:例如,“Sylvain”的分数是我们在前一个例子中看到的 token `S` , `##yl` , `##va` ,和 `##in` 的分数的平均值。其他可用的策略包括: -- `"max"`,其中每个实体的分数是该实体中标记的最大分数(因此对于“Hugging Face”,它将是 0.98879766,即“Face”的分数) +- “first”,其中每个实体的分数是该实体的第一个 token 的分数(因此对于“Sylvain”,分数将是 0.993828,这是“S”的分数) +- “max”,其中每个实体的分数是该实体中 token 的最大分数(因此对于“Hugging Face”,分数将是 0.98879766,即“Face”的分数) +- “average”,其中每个实体的分数是组成该实体的单词分数的平均值(因此对于“Sylvain”,与“simple”策略相同,但“Hugging Face”的得分将是 0.9819,这是“Hugging”的分数 0.975 和“Face”的分数 0.98879 的平均值) -- `"average"`, 其中每个实体的分数是组成该实体的单词分数的平均值(因此对于“Sylvain”,与“simple”策略,但“Hugging Face”的得分为 0.9819,“Hugging”得分的平均值为 0.975,“Face”得分为 0.98879) - -现在让我们看看如何在不使用pipeline()函数的情况下获得这些结果! +现在让我们看看如何在不使用 `pipeline()` 函数的情况下获得这些结果! ### 从输入到预测 [[从输入到预测]] {#if fw === 'pt'} -首先,我们需要标记我们的输入并将其传递给模型。这是完全按照[Chapter 2](/course/chapter2);我们使用 **AutoXxx** 类,然后在我们的示例中使用它们: +首先,我们需要将我们的输入进行 tokenization 并将其传递给模型。这个过程与 [第二章](/course/chapter2) 中的方法完全相同;我们使用 `AutoXxx` 类实例化 tokenizer 和模型,然后将我们的示例传递给它们: ```py from transformers import AutoTokenizer, AutoModelForTokenClassification @@ -213,7 +212,7 @@ inputs = tokenizer(example, return_tensors="pt") outputs = model(**inputs) ``` -由于我们正在使用 **AutoModelForTokenClassification** 在这里,我们为输入序列中的每个标记获得一组 logits: +由于我们在此使用了 `AutoModelForTokenClassification` ,所以我们得到了输入序列中每个 token 的一组 logits: ```py print(inputs["input_ids"].shape) @@ -227,7 +226,7 @@ torch.Size([1, 19, 9]) {:else} -首先,我们需要标记我们的输入并将其传递给模型。这是完全按照[Chapter 2](/course/chapter2);我们使用 **AutoXxx** 类,然后在我们的示例中使用它们: +首先,我们需要我们的输入 tokenization 并将其传递给模型。这个过程与 [第二章](/course/chapter2) 中的方法完全相同;我们使用 `TFAutoXxx` 类实例化 tokenizer 和模型,然后将我们的示例传递给它们: ```py from transformers import AutoTokenizer, TFAutoModelForTokenClassification @@ -241,7 +240,7 @@ inputs = tokenizer(example, return_tensors="tf") outputs = model(**inputs) ``` -于我们正在使用 **AutoModelForTokenClassification** 在这里,我们为输入序列中的每个标记获得一组 logits: +由于我们在此使用了 `TFAutoModelForTokenClassification` ,所以我们得到了输入序列中每个 token 的一组 logits: ```py print(inputs["input_ids"].shape) @@ -255,7 +254,7 @@ print(outputs.logits.shape) {/if} -我们有一个包含 19 个标记的 1 个序列的批次,模型有 9 个不同的标签,因此模型的输出具有 1 x 19 x 9 的形状。与文本分类管道一样,我们使用 softmax 函数来转换这些 logits到概率,我们采用 argmax 来获得预测(请注意,我们可以在 logits 上采用 argmax,因为 softmax 不会改变顺序): +我们有一个包含 19 个 token 序列的 batch 和有 9 个不同的标签类型,所以模型的输出形状为 1 x 19 x 9。像文本分类管道一样,我们使用 softmax 函数将这些 logits 转化为概率,并取 argmax 来得到预测(请注意,我们可以在 logits 上直接算取 argmax,因为 softmax 不会改变顺序): {#if fw === 'pt'} @@ -285,7 +284,7 @@ print(predictions) [0, 0, 0, 0, 4, 4, 4, 4, 0, 0, 0, 0, 6, 6, 6, 0, 8, 0, 0] ``` - **model.config.id2label** 属性包含索引到标签的映射,我们可以用它来理解预测: +`model.config.id2label` 属性包含索引到标签的映射,我们可以用它来将预测转化为标签: ```py model.config.id2label @@ -303,16 +302,16 @@ model.config.id2label 8: 'I-LOC'} ``` -正如我们之前看到的,有 9 个标签: **O** 是不在任何命名实体中的标记的标签(它代表“外部”),然后我们为每种类型的实体(杂项、人员、组织和位置)提供两个标签。标签 **B-XXX** 表示令牌在实体的开头 **XXX** 和标签 **I-XXX** 表示令牌在实体内 **XXX** .例如,在当前示例中,我们希望我们的模型对令牌进行分类 **S** 作为 **B-PER** (一个人实体的开始)和令牌 **##yl** , **##va** 和 **##in** 作为 **I-PER** (在个人实体内) +如前所述,这里有 9 个标签: `O` 是不在任何实体中的 token 的标签(它代表“outside”),然后我们为每种类型的实体(杂项、人员、组织和位置)提供两个标签:标签 `B-XXX` 表示 token 在实体 `XXX` 的开头,标签 `I-XXX` 表示 token 在实体 `XXX` 内部。例如,在当前的例子,我们期望我们的模型将 token `S` 分类为 `B-PER` (人物实体的开始),并且将 token `##yl` , `##va` 和 `##in` 分类为 `I-PER` (人物实体的内部) -在这种情况下,您可能认为模型是错误的,因为它给出了标签 **I-PER** 对所有这四个令牌,但这并不完全正确。实际上有两种格式 **B-** 和 **I-** 标签:IOB1和IOB2. IOB2 格式(下面粉红色)是我们介绍的格式,而在 IOB1 格式(蓝色)中,标签以 **B-** 仅用于分隔相同类型的两个相邻实体。我们使用的模型在使用该格式的数据集上进行了微调,这就是它分配标签的原因 **I-PER** 到 **S** 令牌。 +你可能会觉得上面模型的输出是错误的,因为它给所有这四个 token 都标上了 `I-PER` 标签,但这样理解并不完全正确。对于 `B-` 和 `I-` 标签,实际上有两种格式:IOB1 和 IOB2。我们介绍的是 IOB2 格式(如下图所示的粉色),而在 IOB1 格式(蓝色)中,以 `B-` 开头的标签只用于分隔同一类型的两个相邻实体。我们正在使用的模型在使用该格式的数据集上进行了微调,这就是为什么它将 `S` token 标上了 `I-PER` 标签的原因。
IOB1 vs IOB2 format
-了这张地图,我们已经准备好(几乎完全)重现第一个管道的结果——我们可以获取每个未被归类为的标记的分数和标签 **O** : +有了这个映射字典,我们就可以几乎完全复现管道的结果 —— 我们只需要获取每个没有被分类为 O 的 token 的得分和标签: ```py results = [] @@ -339,7 +338,7 @@ print(results) {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn'}] ``` -这与我们之前的情况非常相似,只有一个例外:管道还为我们提供了有关 **start** 和 **end** 原始句子中的每个实体。这是我们的偏移映射将发挥作用的地方。要获得偏移量,我们只需要设置 **return_offsets_mapping=True** 当我们将分词器应用于我们的输入时: +这与我们之前的结果非常相似,但有一点不同:pipeline 还给我们提供了每个实体在原始句子中的 `start` 和 `end` 的信息。如果要复现这个特性,这就是我们的偏移映射要发挥作用的地方。要获得偏移量,我们只需要在使用 tokenizer 器时设置 `return_offsets_mapping=True` : ```py inputs_with_offsets = tokenizer(example, return_offsets_mapping=True) @@ -351,20 +350,20 @@ inputs_with_offsets["offset_mapping"] (33, 35), (35, 40), (41, 45), (46, 48), (49, 57), (57, 58), (0, 0)] ``` -每个元组是对应于每个标记的文本跨度,其中 **(0, 0)** 保留用于特殊令牌。我们之前看到索引 5 处的令牌是 **##yl** , 其中有 **(12, 14)** 作为这里的抵消。如果我们在示例中抓取相应的切片: +每个元组都是每个 token 对应的文本范围,其中 `(0, 0)` 是为特殊 token 保留的。我们之前看到索引 5 的 token 是 `##yl` ,它所对应的偏移量是 `(12, 14)` 。如果我们截取我们例子中的对应片段: ```py example[12:14] ``` -我们得到了正确的文本跨度,而没有 **##** : +我们会得到没有 `##` 的文本: ```python out yl ``` -使用这个,我们现在可以完成之前的结果: +使用这个,我们现在可以完成之前的想法: ```py results = [] @@ -400,13 +399,14 @@ print(results) {'entity': 'I-LOC', 'score': 0.99321055, 'index': 16, 'word': 'Brooklyn', 'start': 49, 'end': 57}] ``` -这和我们从第一个管道中得到的一样! +我们得到了与第一条 pipeline 相同的结果! + +### 实体分组 [[实体分组]] -### 分组实体 [[分组实体]] +使用偏移来确定每个实体的开始和结束的索引很方便,但这并不是它唯一的用法。当我们希望将实体分组在一起时,偏移映射将为我们省去很多复杂的代码。例如,如果我们想将 `Hu` 、 `##gging` 和 `Face` token 分组在一起,我们可以制定特殊规则,比如说前两个应该在去除 `##` 的同时连在一起, `Face` 应该在不以 `##` 开头的情况下增加空格 —— 但这些规则只适用于这种特定类型的分词器。当我们使用 SentencePiece 或 Byte-Pair-Encoding 分词器(在本章后面讨论)时就要重新写另外一套规则。 -使用偏移量来确定每个实体的开始和结束键很方便,但该信息并不是绝对必要的。然而,当我们想要将实体组合在一起时,偏移量将为我们节省大量混乱的代码。例如,如果我们想将令牌组合在一起 **Hu** , **##gging** , 和 **Face** ,我们可以制定特殊的规则,说前两个应该附加,同时删除 **##** ,以及 **Face** 应该添加一个空格,因为它不以 **##** — 但这仅适用于这种特定类型的标记器。我们必须为 SentencePiece 或 Byte-Pair-Encoding 分词器(本章稍后讨论)。 +有了偏移量,就可以免去为特定分词器定制特殊的分组规则:我们只需要取原始文本中以第一个 token 开始和最后一个 token 结束的范围。因此,假如说我们有 `Hu` 、 `##gging` 和 `Face` token,我们只需要从字符 33( `Hu` 的开始)截取到字符 45( `Face` 的结束): -编写另一组规则。使用偏移量,所有自定义代码都消失了:我们可以在原始文本中获取从第一个标记开始到最后一个标记结束的跨度。所以,在令牌的情况下 **Hu** , **##gging** , 和 **Face** ,我们应该从字符 33(开始 **Hu** ) 并在字符 45 之前结束(结束 **Face** ): ```py example[33:45] @@ -416,7 +416,7 @@ example[33:45] Hugging Face ``` -为了编写在对实体进行分组的同时对预测进行后处理的代码,我们将连续并标记为的实体分组在一起 **I-XXX** ,除了第一个,可以标记为 **B-XXX** 或者 **I-XXX** (因此,当我们得到一个实体时,我们停止对实体进行分组 **O** ,一种新型实体,或 **B-XXX** 这告诉我们一个相同类型的实体正在启动): +为了编写处理预测结果并分组实体的代码,我们将对连续标记为 `I-XXX` 的实体进行分组,因为只有实体的第一个 token 可以被标记为 `B-XXX` 或 `I-XXX` ,因此,当我们遇到实体 `O` 、新类型的实体或 `B-XXX` 时,我们就可以停止聚合同一类型实体。 ```py import numpy as np @@ -431,11 +431,11 @@ while idx < len(predictions): pred = predictions[idx] label = model.config.id2label[pred] if label != "O": - # Remove the B- or I- + # 删除 B- 或者 I- label = label[2:] start, _ = offsets[idx] - # Grab all the tokens labeled with I-label + # 获取所有标有 I 标签的token all_scores = [] while ( idx < len(predictions) @@ -445,7 +445,7 @@ while idx < len(predictions): _, end = offsets[idx] idx += 1 - # The score is the mean of all the scores of the tokens in that grouped entity + # 分数是该分组实体中所有token分数的平均值 score = np.mean(all_scores).item() word = example[start:end] results.append( @@ -462,7 +462,7 @@ while idx < len(predictions): print(results) ``` -我们得到了与第二条管道相同的结果! +我们得到了与第二条 pipeline 相同的结果! ```python out [{'entity_group': 'PER', 'score': 0.9981694, 'word': 'Sylvain', 'start': 11, 'end': 18}, @@ -470,4 +470,4 @@ print(results) {'entity_group': 'LOC', 'score': 0.99321055, 'word': 'Brooklyn', 'start': 49, 'end': 57}] ``` -这些偏移量非常有用的另一个任务示例是问答。深入研究这个管道,我们将在下一节中进行,也将使我们能够了解 🤗 Transformers 库中标记器的最后一个功能:当我们将输入截断为给定长度时处理溢出的标记。 +另一个非常需要偏移量的任务领域是问答。我们将在下一部分深入探索这个 pipeline。同时我们也会看到🤗 Transformers 库中 tokenizer 的最后一个特性:在我们将输入超过给定长度时,处理溢出的 tokens。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter6/3b.mdx b/chapters/zh-CN/chapter6/3b.mdx index 1c23ad523..c6c366722 100644 --- a/chapters/zh-CN/chapter6/3b.mdx +++ b/chapters/zh-CN/chapter6/3b.mdx @@ -1,6 +1,6 @@ -# QA 管道中的快速标记器 [[QA 管道中的快速标记器]] +# 在 QA 管道中使用快速 tokenizer [[在 QA 管道中使用快速 tokenizer ]] {#if fw === 'pt'} @@ -22,7 +22,7 @@ {/if} -我们现在将深入研究 **question-answering** 管道,看看如何利用偏移量从上下文中获取手头问题的答案,有点像我们在上一节中对分组实体所做的。然后我们将看到我们如何处理最终被截断的非常长的上下文。如果您对问答任务不感兴趣,可以跳过此部分。 +我们现在将深入研究 `question-answering` 管道,看看如何利用偏移量从上下文(context)中获取当前问题的答案,这与我们在上一节中处理分组实体的方式有些相似。我们会看到如何处理那些因为过长而最终被截断的上下文(context)。如果你对问答任务不感兴趣,可以跳过这一节。 {#if fw === 'pt'} @@ -36,7 +36,7 @@ ## 使用 `question-answering` 管道 [[使用 `question-answering` 管道]] -正如我们在[Chapter 1](/course/chapter1),我们可以使用 **question-answering** 像这样的管道以获得问题的答案: +正如我们在 [第一章](/course/chapter1) ,我们可以使用 `question-answering` 像这样的管道以获得问题的答案: ```py from transformers import pipeline @@ -57,7 +57,8 @@ question_answerer(question=question, context=context) 'answer': 'Jax, PyTorch and TensorFlow'} ``` -与其他管道不同,它不能截断和拆分长于模型接受的最大长度的文本(因此可能会丢失文档末尾的信息),此管道可以处理非常长的上下文,并将返回回答这个问题,即使它在最后: +与其他不能处理超过模型接受的最大长度的文本的管道不同,这个管道可以处理非常长的上下文(context),并且即使答案在末尾也能返回问题的答案: + ```py long_context = """ @@ -107,11 +108,11 @@ question_answerer(question=question, context=long_context) 'answer': 'Jax, PyTorch and TensorFlow'} ``` -让我们看看它是如何做到这一切的! +让我们看看它是如何做到这一点的! ## 使用模型进行问答 [[使用模型进行问答]] -与任何其他管道一样,我们首先对输入进行标记化,然后通过模型将其发送。默认情况下用于的检查点 **question-answering** 管道是[distilbert-base-cased-distilled-squad](https://huggingface.co/distilbert-base-cased-distilled-squad)(名称中的“squad”来自模型微调的数据集;我们将在[Chapter 7](/course/chapter7/7)): +与任何其他管道一样,我们首先对输入进行 tokenize,然后将其传入模型。 `question-answering` 管道默认情况下用于的 checkpoint 是 [distilbert-base-cased-distilled-squad](https://huggingface.co/distilbert-base-cased-distilled-squad) (名字中的"squad"源自模型微调所用的数据集;我们将在 [第七章](/course/chapter7/7) 详细讨论 SQuAD 数据集): {#if fw === 'pt'} @@ -141,14 +142,14 @@ outputs = model(**inputs) {/if} -请注意,我们将问题和上下文标记为一对,首先是问题 +请注意在这里,我们将问题放在前面和上下文放后面,一起作为一对进行tokenization。
An example of tokenization of question and context
-问答模型的工作方式与我们迄今为止看到的模型略有不同。以上图为例,该模型已经过训练,可以预测答案开始的标记的索引(此处为 21)和答案结束处的标记的索引(此处为 24)。这就是为什么这些模型不返回一个 logits 的张量,而是返回两个:一个用于对应于答案的开始标记的 logits,另一个用于对应于答案的结束标记的 logits。由于在这种情况下我们只有一个包含 66 个标记的输入,我们得到: +问答模型的工作方式与我们迄今为止看到的模型略有不同。以上图为例,模型训练的目标是来预测答案开始的 token 的索引(这里是 21)和答案结束的 token 的索引(这里是 24)。这就是为什么这些模型不返回一个 logits 的张量,而是返回两个:一个对应于答案的开始 token 的 logits,另一个对应于答案的结束 token 的 logits。在这个例子中,我们的输入包含了 66 个 token ,因此我们得到: ```py start_logits = outputs.start_logits @@ -170,9 +171,9 @@ torch.Size([1, 66]) torch.Size([1, 66]) {/if} -为了将这些 logits 转换为概率,我们将应用一个 softmax 函数——但在此之前,我们需要确保我们屏蔽了不属于上下文的索引。我们的输入是 **[CLS] question [SEP] context [SEP]** ,所以我们需要屏蔽问题的标记以及 **[SEP]** 令牌。我们将保留 **[CLS]** 然而,因为某些模型使用它来表示答案不在上下文中。 +为了将这些 logits 转换为概率,我们将使用一个 softmax 函数——但在此之前,我们需要确保我们屏蔽了不属于上下文的索引。我们的输入格式是 `[CLS] question [SEP] context [SEP]` ,所以我们需要屏蔽 question 的 tokens 以及 `[SEP]` token 。不过,我们将保留 `[CLS]` ,因为某些模型使用它来表示答案不在上下文中。 -由于我们将在之后应用 softmax,我们只需要用一个大的负数替换我们想要屏蔽的 logits。在这里,我们使用 **-10000** : +由于我们将在之后使用 softmax,我们只需要将我们想要屏蔽的 logits 替换为一个大的负数就可以在计算 softmax 的时候屏蔽他们。在这里,我们使用 `-10000` : {#if fw === 'pt'} @@ -180,9 +181,9 @@ torch.Size([1, 66]) torch.Size([1, 66]) import torch sequence_ids = inputs.sequence_ids() -# Mask everything apart from the tokens of the context +# 屏蔽除 context 之外的所有内容 mask = [i != 1 for i in sequence_ids] -# Unmask the [CLS] token +# 不屏蔽 [CLS] token mask[0] = False mask = torch.tensor(mask)[None] @@ -196,9 +197,9 @@ end_logits[mask] = -10000 import tensorflow as tf sequence_ids = inputs.sequence_ids() -# Mask everything apart from the tokens of the context +# 屏蔽除 context 之外的所有内容 mask = [i != 1 for i in sequence_ids] -# Unmask the [CLS] token +# 不屏蔽 [CLS] token mask[0] = False mask = tf.constant(mask)[None] @@ -208,7 +209,7 @@ end_logits = tf.where(mask, -10000, end_logits) {/if} -现在我们已经正确屏蔽了与我们不想预测的位置相对应的 logits,我们可以应用 softmax: +现在我们已经屏蔽了与我们不想预测的位置相对应的 logits,接下来我们可以使用 softmax: {#if fw === 'pt'} @@ -226,22 +227,24 @@ 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)** 以最高的概率。 +在这个阶段,我们可以取开始和结束概率的 argmax —— 但是我们可能会得到一个比结束索引大的开始索引,因此我们需要采取一些更多的措施来处理这些特殊情况。我们将在满足 `start_index <= end_index` 的前提下计算每个可能的 `start_index` 和 `end_index` 的概率,然后取概率最高的 `(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` 的 \($$\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}]\\) where `start_index <= end_index`. -首先让我们计算所有可能的产品: +首先让我们计算所有可能的乘积: + ```py scores = start_probabilities[:, None] * end_probabilities[None, :] ``` {#if fw === 'pt'} -然后我们将屏蔽这些值 **start_index > end_index** 通过将它们设置为 **0** (其他概率都是正数)。这 **torch.triu()** 函数返回作为参数传递的 2D 张量的上三角部分,因此它会为我们做屏蔽: +然后我们将 `start_index > end_index` 的值设置为 `0` 来屏蔽他们(其他概率都是正数)。 `torch.triu()` 函数返回传入的 2D 张量的上三角部分,所以我们可以使用它来完成屏蔽: ```py import numpy as np @@ -250,15 +253,15 @@ scores = torch.triu(scores) ``` {:else} -然后我们将屏蔽这些值 **start_index > end_index** 通过将它们设置为 **0** (其他概率都是正数)。这 **torch.triu()** 函数返回作为参数传递的 2D 张量的上三角部分,因此它会为我们做屏蔽: +然后我们将 `start_index > end_index` 的值设置为 `0` 来屏蔽他们(其他概率都是正数)。 `np.triu()` 函数返回传入的 2D 张量的上三角部分,所以我们做可以使用它来完成屏蔽: ```py scores = np.triu(scores) ``` {/if} -现在我们只需要得到最大值的索引。由于 PyTorch 将返回展平张量中的索引,因此我们需要使用地板除法 **//** 和模数 **%** 操作以获得 **start_index** 和 **end_index** : +现在我们只需要得到最大值的索引。由于 PyTorch 将返回展平(flattened)后张量中的索引,因此我们需要使用向下取整的除法 `//` 和取模 `%` 操作来获得 `start_index` 和 `end_index` : ```py max_index = scores.argmax().item() @@ -267,7 +270,7 @@ end_index = max_index % scores.shape[1] print(scores[start_index, end_index]) ``` -我们还没有完全完成,但至少我们已经有了正确的答案分数(您可以通过将其与上一节中的第一个结果进行比较来检查这一点): +我们还没有完全完成,但至少我们已经有了正确的答案分数(你可以通过将其与上一节中的第一个结果进行比较来检查这一点): ```python out 0.97773 @@ -275,11 +278,11 @@ print(scores[start_index, end_index]) -✏️ **试试看!** 计算五个最可能的答案的开始和结束索引。 +✏️ **试试看!** 计算五个最可能的答案的开始和结束索引。 -我们有 **start_index** 和 **end_index** 就标记而言的答案,所以现在我们只需要转换为上下文中的字符索引。这是偏移量非常有用的地方。我们可以像在令牌分类任务中一样抓住它们并使用它们: +我们有了答案的 `start_index` 和 `end_index` ,所以现在我们只需要将他们转换为上下文中的字符索引。这就是偏移量将会非常有用的地方。我们可以像我们在 token 分类任务中那样获取偏移量并使用它们: ```py inputs_with_offsets = tokenizer(question, context, return_offsets_mapping=True) @@ -290,7 +293,8 @@ _, end_char = offsets[end_index] answer = context[start_char:end_char] ``` -现在我们只需要格式化所有内容以获得我们的结果: +现在我们只需要格式化所有内容,获取我们的结果: + ```py result = { @@ -309,17 +313,17 @@ print(result) 'score': 0.97773} ``` -太棒了!这和我们的第一个例子一样! +太棒了!这和我们上面获取的结果一样! -✏️ **试试看!** 使用您之前计算的最佳分数来显示五个最可能的答案。要检查您的结果,请返回到第一个管道并在调用它时传入。 +✏️ **试试看!** 使用你之前计算的最佳分数来显示五个最可能的答案。你可以回到之前的 QA pipeline,并在调用时传入 `top_k=5` 来对比检查你的结果。 -## 处理长上下文 [[处理长上下文]] +## 处理长文本 [[处理长文本]] -如果我们尝试对我们之前作为示例使用的问题和长上下文进行标记化,我们将获得比在 **question-answering** 管道(即 384): +如果我们尝试将我们之前使用的长问题和长上下文进行 tokenize,我们将得到一个比 `question-answering` pipeline 中使用的最大长度(384)更大的 tokens 数量: ```py inputs = tokenizer(question, long_context) @@ -330,7 +334,7 @@ print(len(inputs["input_ids"])) 461 ``` -因此,我们需要在最大长度处截断我们的输入。有几种方法可以做到这一点,但我们不想截断问题,只想截断上下文。由于上下文是第二个句子,我们将使用 **"only_second"** 截断策略。那么出现的问题是问题的答案可能不在截断上下文中。例如,在这里,我们选择了一个答案在上下文末尾的问题,当我们截断它时,答案不存在 +所以,我们需要将我们的输入截断到模型允许输入的最大长度。我们可以用几种方式做到这一点,但我们不想截断问题部分,只想截断上下文部分,并且由于上下文部分是第二项,因此我们将使用 `"only_second"` 截断策略。然后又出现了新的问题:问题的答案可能在截断后被丢弃了,并没有在截断后保留下来的上下文文本中。例如,我们选了一个问题,其中的答案在上下文的末尾,当我们截断它时,答案就不在里面了: ```py inputs = tokenizer(question, long_context, max_length=384, truncation="only_second") @@ -373,9 +377,9 @@ Why should I use transformers? """ ``` -这意味着模型将很难选择正确的答案。为了解决这个问题, **question-answering** 管道允许我们将上下文分成更小的块,指定最大长度。为确保我们不会在完全错误的位置拆分上下文以找到答案,它还包括块之间的一些重叠。 +这意味着模型将很难找到正确的答案。为了解决这个问题, `question-answering` 管道允许我们将上下文分成更小的块,指定最大长度。为了确保我们不在刚好可能找到答案的地方将上下文分割,它还在各块之间包含了一些重叠。 -我们可以让分词器(快或慢)通过添加来为我们做这件事 **return_overflowing_tokens=True** ,我们可以指定我们想要的重叠 **stride** 争论。这是一个使用较小句子的示例: +我们可以通过添加 `return_overflowing_tokens=True` 参数,并可以用 `stride` 参数指定我们想要的重叠长度来让 tokenizer (快速或慢速)为我们做这个工作。下面是一个使用较短的句子的例子: ```py sentence = "This sentence is not too long but we are going to split it anyway." @@ -397,9 +401,9 @@ for ids in inputs["input_ids"]: '[CLS] it anyway. [SEP]' ``` -正如我们所看到的,句子已被分成多个块,使得每个条目 **inputs["input_ids"]** 最多有 6 个标记(我们需要添加填充以使最后一个条目与其他条目的大小相同)并且每个条目之间有 2 个标记的重叠。 +正如我们所看到的,句子已被分成多个块,使得每个条目 `inputs["input_ids"]` 最多有 6 个 token (我们需要添加填充以使分割后的最后一个条目与其他条目的大小相同)并且每个条目之间有 2 个 token 的重叠。 -让我们仔细看看标记化的结果: +让我们仔细看看tokenization的结果: ```py print(inputs.keys()) @@ -409,7 +413,8 @@ print(inputs.keys()) dict_keys(['input_ids', 'attention_mask', 'overflow_to_sample_mapping']) ``` -正如预期的那样,我们得到了输入 ID 和一个注意力掩码。最后一个键, **overflow_to_sample_mapping** , 是一个映射,它告诉我们每个结果对应哪个句子——这里我们有 7 个结果,它们都来自我们通过标记器的(唯一)句子: + +正如我们所预期的,我们得到了inputs ID 和注意力掩码。最后一个键,overflow_to_sample_mapping,是一个映射,告诉我们每个结果对应哪个句子——在这里,我们有 7 个结果,它们都来自我们传递给 tokenizer 的(唯一的)句子: ```py print(inputs["overflow_to_sample_mapping"]) @@ -419,7 +424,7 @@ print(inputs["overflow_to_sample_mapping"]) [0, 0, 0, 0, 0, 0, 0] ``` -当我们将几个句子标记在一起时,这更有用。例如,这个: +当我们一起对多个句子 tokenize 时,这会更有用。例如,这个: ```py sentences = [ @@ -433,16 +438,16 @@ inputs = tokenizer( print(inputs["overflow_to_sample_mapping"]) ``` -让我们: +输出的结果是: ```python out [0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1] ``` -这意味着第一个句子像以前一样分成 7 个块,接下来的 4 个块来自第二个句子。 +这意味着第一句话被分割成 7 个块,就像上面的例子一样,后面的 4 个块来自第二句话。 +现在让我们回到我们的长上下文。默认情况下, `question-answering pipeline` 使用我们之前提到的 384 作为最大长度,步长为 128,这与模型的微调方式相对应(你可以通过在调用 pipeline 时传递 `max_seq_len` 和 `stride` 参数来调整这些参数)。因此,我们在 tokenization 时将使用微调时使用的这些参数。我们还会添加填充(使样本具有相同的长度,这样我们就可以构建拼接成一个矩形的张量),并获取偏移量: -现在让我们回到我们的长期背景。默认情况下 **question-answering** 管道使用的最大长度为 384,正如我们之前提到的,步长为 128,这对应于模型微调的方式(您可以通过传递 **max_seq_len** 和 **stride** 调用管道时的参数)。因此,我们将在标记化时使用这些参数。我们还将添加填充(具有相同长度的样本,因此我们可以构建张量)以及请求偏移量: ```py inputs = tokenizer( @@ -457,7 +462,8 @@ inputs = tokenizer( ) ``` -那些 **inputs** 将包含模型期望的输入 ID 和注意力掩码,以及偏移量和 **overflow_to_sample_mapping** 我们刚刚谈到。由于这两个不是模型使用的参数,我们将把它们从 **inputs** (我们不会存储地图,因为它在这里没有用)在将其转换为张量之前: +这些 `inputs` 将包含模型期望的inputs ID 和注意力掩码,以及我们刚刚谈到的偏移量和 `overflow_to_sample_mapping` 。由于模型不需要这两个参数,我们将它们从 `inputs` 中删除(我们不会存储映射的字典,因为这在这里没有用)然后将 `inputs` 转换为张量: + {#if fw === 'pt'} @@ -489,7 +495,7 @@ print(inputs["input_ids"].shape) {/if} -我们的长上下文被分成两部分,这意味着在它通过我们的模型后,我们将有两组开始和结束 logits: +我们的长上下文被分成两部分,这意味着在经过我们的模型后,我们将得到两组开始和结束的 logits: ```py outputs = model(**inputs) @@ -513,17 +519,17 @@ torch.Size([2, 384]) torch.Size([2, 384]) {/if} -和以前一样,我们在采用 softmax 之前首先屏蔽不属于上下文的标记。我们还屏蔽了所有填充标记(由注意掩码标记): +和以前一样,我们在计算 softmax 之前屏蔽不属于上下文的 token 。我们还屏蔽了所有填充 token (如注意力掩码): {#if fw === 'pt'} ```py sequence_ids = inputs.sequence_ids() -# Mask everything apart from the tokens of the context +# 屏蔽除 context tokens 之外的所有内容 mask = [i != 1 for i in sequence_ids] -# Unmask the [CLS] token +# 取消对 [CLS] token 的屏蔽 mask[0] = False -# Mask all the [PAD] tokens +# 屏蔽所有的 [PAD] tokens mask = torch.logical_or(torch.tensor(mask)[None], (inputs["attention_mask"] == 0)) start_logits[mask] = -10000 @@ -534,11 +540,11 @@ end_logits[mask] = -10000 ```py sequence_ids = inputs.sequence_ids() -# Mask everything apart from the tokens of the context +# 屏蔽除 context tokens 之外的所有内容 mask = [i != 1 for i in sequence_ids] -# Unmask the [CLS] token +# 取消对 [CLS] token的屏蔽 mask[0] = False -# Mask all the [PAD] tokens +# 屏蔽所有的 [PAD] tokens mask = tf.math.logical_or(tf.constant(mask)[None], inputs["attention_mask"] == 0) start_logits = tf.where(mask, -10000, start_logits) @@ -565,7 +571,7 @@ end_probabilities = tf.math.softmax(end_logits, axis=-1).numpy() {/if} -下一步与我们对小上下文所做的类似,但我们对两个块中的每一个都重复它。我们将分数归因于所有可能的答案跨度,然后取得分最高的跨度: +下一步与我们对短的上下文所做的类似,但在这里我们将对两个块分别进行处理。我们为所有可能的回答范围赋予一个得分,然后选择得分最高的范围: {#if fw === 'pt'} @@ -605,15 +611,16 @@ print(candidates) [(0, 18, 0.33867), (173, 184, 0.97149)] ``` -这两个候选对应于模型能够在每个块中找到的最佳答案。该模型对正确答案在第二部分更有信心(这是一个好兆头!)。现在我们只需要将这两个标记跨度映射到上下文中的字符跨度(我们只需要映射第二个标记以获得我们的答案,但看看模型在第一个块中选择了什么很有趣)。 +这两个候选范围对应的是模型在每个块中能够找到的最好的答案。模型对于正确的答案在第二部分更有信心(这是个好兆头!)。现在我们只需要将这两个 token 范围映射到上下文中的字符范围(我们只需要映射第二个就能得到我们的答案,但是看看模型在第一块中选取了什么作为答案还是很有意思的)。 + -✏️ **试试看!** 修改上面的代码以返回五个最可能的答案的分数和跨度(总计,而不是每个块)。 +✏️ **试试看!** 调整上面的代码,以返回五个最可能的答案的得分和范围(对于整个上下文,而不是单个块)。 -这 **offsets** 我们之前抓取的实际上是一个偏移量列表,每个文本块有一个列表: +我们之前抓取 `offsets` 的实际上是一个偏移量列表,每个文本块都有一个列表: ```py for candidate, offset in zip(candidates, offsets): @@ -630,12 +637,12 @@ for candidate, offset in zip(candidates, offsets): {'answer': 'Jax, PyTorch and TensorFlow', 'start': 1892, 'end': 1919, 'score': 0.97149} ``` -如果我们忽略第一个结果,我们会得到与这个长上下文的管道相同的结果——是的! +如果我们选择分数最高的第二个结果,我们会得到与 QA 管道相同结果——耶! -✏️ **试试看!** 使用您之前计算的最佳分数来显示五个最可能的答案(对于整个上下文,而不是每个块)。要检查您的结果,请返回到第一个管道并在调用它时传入。 +✏️ **试试看!** 使用你之前计算的最佳分数来显示五个最可能的答案(对于整个上下文,而不是单个块)。如果想要与 pipeline 对比检查你的结果的话,返回之前的 QA 管道,并在调用时传入 `top_k=5` 的参数。 -我们对分词器功能的深入研究到此结束。我们将在下一章再次将所有这些付诸实践,届时我们将向您展示如何在一系列常见的 NLP 任务上微调模型。 +我们已经结束了我们对 tokenizer 能力的深入探究。在下一章,我们将展示如何在一系列常见的 NLP 任务上微调模型,我们将对这些内容再次付诸实践。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter6/4.mdx b/chapters/zh-CN/chapter6/4.mdx index 137aba74c..6064c0179 100644 --- a/chapters/zh-CN/chapter6/4.mdx +++ b/chapters/zh-CN/chapter6/4.mdx @@ -1,4 +1,4 @@ -# 标准化和预标记化 [[标准化和预标记化]] +# 标准化和预分词 [[标准化和预分词]] -在我们更深入地研究与 Transformer 模型(字节对编码 [BPE]、WordPiece 和 Unigram)一起使用的三种最常见的子词标记化算法之前,我们将首先看一下每个标记器应用于文本的预处理。以下是标记化管道中步骤的高级概述: +在深入探讨 Transformer 模型常用的三种分词算法(字节对编码[BPE]、WordPiece 和 Unigram)之前,我们首先来看看 tokenizer 对文本进行了哪些预处理。以下是 tokenization 过程的高度概括:
The tokenization pipeline.
-在将文本拆分为子标记之前(根据其模型),分词器执行两个步骤: _normalization_ 和 _pre-tokenization_. +在分词(根据其模型)之前,tokenizer 需要进行两个步骤: `标准化(normalization)` 和 `预分词(pre-tokenization)` 。 -## 正常化 [[正常化]] +## `标准化(normalization)` [[`标准化(normalization)`]] -标准化步骤涉及一些常规清理,例如删除不必要的空格、小写和/或删除重音符号。如果你熟悉[Unicode normalization](http://www.unicode.org/reports/tr15/)(例如 NFC 或 NFKC),这也是 tokenizer 可能应用的东西。 +标准化步骤涉及一些常规清理,例如删除不必要的空格、小写和“/”或删除重音符号。如果你熟悉 [Unicode 标准化](http://www.unicode.org/reports/tr15/) (例如 NFC 或 NFKC),那么这也是 tokenizer 可能会使用的东西。 -🤗Transformers **tokenizer** 有一个属性叫做 **backend_tokenizer** 它提供了对 🤗 Tokenizers 库中底层标记器的访问: +🤗 Transformers 的 `tokenizer` 具有一个名为 `backend_tokenizer` 的属性,该属性可以访问来自🤗 Tokenizers 库的底层 tokenizer 。 ```py from transformers import AutoTokenizer @@ -35,7 +35,7 @@ print(type(tokenizer.backend_tokenizer)) ``` -**normalizer** 的属性 **tokenizer** 对象有一个 **normalize_str()** 我们可以用来查看标准化是如何执行的方法: +`tokenizer` 对象的 `normalizer` 属性具有一个 `normalize_str()` 方法,我们可以使用该方法查看如何进行标准化: ```py print(tokenizer.backend_tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) @@ -45,22 +45,21 @@ print(tokenizer.backend_tokenizer.normalizer.normalize_str("Héllò hôw are ü? 'hello how are u?' ``` -在这个例子中,因为我们选择了 **bert-base-uncased** 检查点,标准化应用小写并删除重音。 +在这个例子中,由于我们选择了 `bert-base-uncased` checkpoint,所以会在标准化的过程中转换为小写并删除重音。 -✏️ **试试看!** 从检查点加载标记器并将相同的示例传递给它。您可以看到分词器的带壳和无壳版本之间的主要区别是什么? - +✏️ **试试看!** 从 `bert-base-cased` checkpoint 加载 tokenizer 并处理相同的示例。看一看 tokenizer 的 `cased` 和 `uncased` 版本之间的主要区别是什么? -## 预标记化 [[预标记化]] +## 预分词 [[预分词]] -正如我们将在下一节中看到的,分词器不能单独在原始文本上进行训练。相反,我们首先需要将文本拆分为小实体,例如单词。这就是预标记化步骤的用武之地。 正如我们在[Chapter 2](/course/chapter2), 基于单词的标记器可以简单地将原始文本拆分为空白和标点符号的单词。这些词将是分词器在训练期间可以学习的子标记的边界。 +正如我们将在下一节中看到的,tokenizer 一般不会在原始文本上进行训练。因此,我们首先需要将文本拆分为更小的实体,例如单词。这就是预分词步骤的作用。正如我们在 [第二章](/course/chapter2) 中看到的,基于单词的 tokenizer 可以简单地根据空格和标点符号将原始文本拆分为单词。这些词将是 tokenizer 在训练期间可以学习的子词的边界。 -要查看快速分词器如何执行预分词,我们可以使用 **pre_tokenize_str()** 的方法 **pre_tokenizer** 的属性 **tokenizer** 目的: +要查看快速 tokenizer 如何执行预分词,我们可以使用 `tokenizer` 对象的 `pre_tokenizer` 属性的 `pre_tokenize_str()` 方法: ```py tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") @@ -70,25 +69,26 @@ tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you? [('Hello', (0, 5)), (',', (5, 6)), ('how', (7, 10)), ('are', (11, 14)), ('you', (16, 19)), ('?', (19, 20))] ``` -请注意分词器如何已经跟踪偏移量,这就是它如何为我们提供上一节中使用的偏移量映射。这里分词器忽略了这两个空格,只用一个替换它们,但偏移量在 **are** 和 **you** 考虑到这一点。 +请注意 tokenizer 记录了偏移量,这是就是我们在前一节中使用的偏移映射。在这里 tokenizer 将两个空格并将它们替换为一个,从 `are` 和 `you` 之间的偏移量跳跃可以看出来这一点。 -由于我们使用的是 BERT 分词器,预分词涉及对空格和标点符号进行拆分。对于这一步,其他标记器可以有不同的规则。例如,如果我们使用 GPT-2 标记器: +由于我们使用的是 BERT tokenizer 所以预分词会涉及到在空白和标点上进行分割。其他的 tokenizer 可能会对这一步有不同的规则。例如,如果我们使用 GPT-2 的 tokenizer ```py tokenizer = AutoTokenizer.from_pretrained("gpt2") tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you?") ``` -它也会在空格和标点符号上拆分,但它会保留空格并将它们替换为 **Ġ** 符号,如果我们解码令牌,则使其能够恢复原始空格: +它也会在空格和标点符号上拆分,但它会保留空格并将它们替换为 `Ġ` 符号,如果我们对 tokens 进行解码,则使其能够恢复原始空格: + ```python out [('Hello', (0, 5)), (',', (5, 6)), ('Ġhow', (6, 10)), ('Ġare', (10, 14)), ('Ġ', (14, 15)), ('Ġyou', (15, 19)), ('?', (19, 20))] ``` -另请注意,与 BERT 分词器不同,此分词器不会忽略双空格 +也请注意,与 BERT tokenizer 不同的是,这个 tokenizer 不会忽略双空格。 -最后一个例子,让我们看一下基于 SentencePiece 算法的 T5 分词器: +最后一个例子,让我们看一下基于 SentencePiece 算法的 T5 tokenizer ```py tokenizer = AutoTokenizer.from_pretrained("t5-small") @@ -99,26 +99,25 @@ tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str("Hello, how are you? [('▁Hello,', (0, 6)), ('▁how', (7, 10)), ('▁are', (11, 14)), ('▁you?', (16, 20))] ``` -与 GPT-2 标记器一样,这个标记器保留空格并用特定标记替换它们( **_** ),但 T5 分词器只在空格上拆分,而不是标点符号。还要注意,它默认在句子的开头添加了一个空格(之前 **Hello** ) 并忽略了之间的双空格 **are** 和 **you** . +与 GPT-2 的 tokenizer 类似,这个 tokenizer 保留空格并用特定 token 替换它们( `_` ),但 T5 tokenizer 只在空格上拆分,不考虑标点符号。另外,它会在句子开头(在 `Hello` 之前)默认添加一个空格,并忽略 `are` 和 `you` 之间的双空格。 -现在我们已经了解了一些不同的标记器如何处理文本,我们可以开始探索底层算法本身。我们首先快速浏览一下广泛适用的 SentencePiece;然后,在接下来的三个部分中,我们将研究用于子词标记化的三种主要算法是如何工作的。 +现在我们对一些不同的 tokenizer 如何处理文本有了一些了解,接下来我们就可以开始探索底层算法本身。我们将先简要了解一下广泛适用的 SentencePiece;然后,在接下来的三节中,我们将研究用于子词分词的三种主要算法的工作原理。 -## 句子 [[句子]] +## SentencePiece [[SentencePiece]] -[SentencePiece](https://github.com/google/sentencepiece) 是一种用于文本预处理的标记化算法,您可以将其与我们将在接下来的三个部分中看到的任何模型一起使用。它将文本视为 Unicode 字符序列,并用特殊字符替换空格, **▁** .与 Unigram 算法结合使用(参见[section 7](/course/chapter7/7)), 它甚至不需要预标记化步骤,这对于不使用空格字符的语言(如中文或日语)非常有用。 + [SentencePiece](https://github.com/google/sentencepiece) 是一种用于文本预处理的 tokenization 算法,你可以将其与我们将在接下来的三个部分中看到的任何模型一起使用。它将文本视为 Unicode 字符序列,并用特殊字符 `▁` 替换空格。与 Unigram 算法结合使用(参见 [第七节](/course/chapter7/7) )时,它甚至不需要预分词步骤,这对于不使用空格字符的语言(如中文或日语)非常有用。 -SentencePiece 的另一个主要特点是可逆标记化:由于没有对空格进行特殊处理,因此只需通过将它们连接起来并替换 **_** s 带空格——这会导致标准化的文本。正如我们之前看到的,BERT 分词器删除了重复的空格,因此它的分词是不可逆的。 +SentencePiece 的另一个主要特点是可逆 tokenization:由于没有对空格进行特殊处理,解码 tokens 的时候只需将它们连接起来,然后将 `_` 替换为空格,就可以还原原始的文本。如我们之前所见,BERT tokenizer 会删除重复的空格,所以它的分词不是可逆的。 ## 算法概述 [[算法概述]] -在下面的部分中,我们将深入研究三种主要的子词标记化算法:BPE(由 GPT-2 和其他人使用)、WordPiece(例如由 BERT 使用)和 Unigram(由 T5 和其他人使用)。在我们开始之前,这里是它们各自工作原理的快速概述。如果您还没有理解,请在阅读下一节后立即回到此表。 - +在下面的部分中,我们将深入研究三种主要的子词 tokenization 算法:BPE(由 GPT-2 等使用)、WordPiece(由 BERT 使用)和 Unigram(由 T5 等使用)。在我们开始之前,先来快速了解它们各自的工作方式。如果你还不能理解,不妨在阅读接下来的每一节之后返回此表格进行查看。 -Model | BPE | WordPiece | Unigram +模型 | BPE | WordPiece | Unigram :----:|:---:|:---------:|:------: -Training | Starts from a small vocabulary and learns rules to merge tokens | Starts from a small vocabulary and learns rules to merge tokens | Starts from a large vocabulary and learns rules to remove tokens -Training step | Merges the tokens corresponding to the most common pair | Merges the tokens corresponding to the pair with the best score based on the frequency of the pair, privileging pairs where each individual token is less frequent | Removes all the tokens in the vocabulary that will minimize the loss computed on the whole corpus -Learns | Merge rules and a vocabulary | Just a vocabulary | A vocabulary with a score for each token -Encoding | Splits a word into characters and applies the merges learned during training | Finds the longest subword starting from the beginning that is in the vocabulary, then does the same for the rest of the word | Finds the most likely split into tokens, using the scores learned during training +训练 | 从小型词汇表开始,学习合并 token 的规则 | 从小型词汇表开始,学习合并 token 的规则 | 从大型词汇表开始,学习删除 token 的规则 +训练步骤 | 合并对应最常见的 token 对 | 合并对应得分最高的 token 对,优先考虑每个独立 token 出现频率较低的对 | 删除会在整个语料库上最小化损失的词汇表中的所有 token +学习 | 合并规则和词汇表 | 仅词汇表 | 含有每个 token 分数的词汇表 +编码 | 将一个单词分割成字符并使用在训练过程中学到的合并 | 从开始处找到词汇表中的最长子词,然后对其余部分做同样的事 | 使用在训练中学到找到最可能的 token 分割方式 现在让我们深入了解 BPE! \ No newline at end of file diff --git a/chapters/zh-CN/chapter6/5.mdx b/chapters/zh-CN/chapter6/5.mdx index e923db49b..4a8e1d4f7 100644 --- a/chapters/zh-CN/chapter6/5.mdx +++ b/chapters/zh-CN/chapter6/5.mdx @@ -1,4 +1,4 @@ -# 字节对编码标记化 [[字节对编码标记化]] +# BPE tokenization 算法 [[BPE tokenization算法]] -字节对编码(BPE)最初被开发为一种压缩文本的算法,然后在预训练 GPT 模型时被 OpenAI 用于标记化。许多 Transformer 模型都使用它,包括 GPT、GPT-2、RoBERTa、BART 和 DeBERTa。 +字节对编码(BPE)最初被开发为一种压缩文本的算法,然后在预训练 GPT 模型时被 OpenAI 用于 tokenization。许多 Transformer 模型都使用它,包括 GPT、GPT-2、RoBERTa、BART 和 DeBERTa。 -💡 本节深入介绍了BPE,甚至展示了一个完整的实现。如果你只想大致了解标记化算法,可以跳到最后。 +💡 本节深入介绍了 BPE,甚至展示了一个完整的实现。如果你只想大致了解 tokenization 算法,可以跳到最后。 -## 训练算法 [[训练算法]] +## BPE 训练 [[BPE 训练]] -BPE 训练首先计算语料库中使用的唯一单词集(在完成标准化和预标记化步骤之后),然后通过获取用于编写这些单词的所有符号来构建词汇表。一个非常简单的例子,假设我们的语料库使用了这五个词: +BPE 训练首先计算语料库中使用的唯一单词集合(在完成标准化和预分词步骤之后),然后取出用来编写这些词的所有符号来构建词汇表。举一个非常简单的例子,假设我们的语料库使用了这五个词: ``` "hug", "pug", "pun", "bun", "hugs" ``` -基础词汇将是 `["b", "g", "h", "n", "p", "s", "u"]`。对于实际情况,基本词汇表将包含所有 ASCII 字符,至少,可能还包含一些 Unicode 字符。如果您正在标记的示例使用不在训练语料库中的字符,则该字符将转换为未知标记。这就是为什么许多 NLP 模型在分析带有表情符号的内容方面非常糟糕的原因之一。 +基础单词集合将是 `["b", "g", "h", "n", "p", "s", "u"]` 。在实际应用中,基本词汇表将至少包含所有 ASCII 字符,可能还包含一些 Unicode 字符。如果你正在 tokenization 不在训练语料库中的字符,则该字符将转换为未知 tokens,这就是为什么许多 NLP 模型在分析带有表情符号的内容的结果非常糟糕的原因之一。 -TGPT-2 和 RoBERTa 标记器(非常相似)有一个聪明的方法来处理这个问题: 他们不把单词看成是用 Unicode 字符写的,而是用字节写的。这样,基本词汇表的大小很小(256),但你能想到的每个字符仍将被包含在内,而不会最终转换为未知标记。这个技巧被称为 *字节级 BPE*。 +GPT-2 和 RoBERTa (这两者非常相似)的 tokenizer 有一个巧妙的方法来处理这个问题:他们不把单词看成是用 Unicode 字符编写的,而是用字节编写的。这样,基本词汇表的大小很小(256),但是能包含几乎所有你能想象的字符,而不会最终转换为未知 tokens 这个技巧被称为 `字节级(byte-level) BPE` 。 -获得这个基本词汇后,我们添加新的标记,直到通过学习*合并*达到所需的词汇量,这是将现有词汇表的两个元素合并为一个新元素的规则。因此,在开始时,这些合并将创建具有两个字符的标记,然后,随着训练的进行,会创建更长的子词。 +获得这个基础单词集合后,我们通过学习 `合并(merges)` 来添加新的 tokens 直到达到期望的词汇表大小。合并是将现有词汇表中的两个元素合并为一个新元素的规则。所以,一开始会创建出含有两个字符的 tokens 然后,随着训练的进展,会产生更长的子词。 -在分词器训练期间的任何一步,BPE 算法都会搜索最常见的现有标记对 ("对",这里我们指的是单词中的两个连续标记)。最频繁的一对将被合并,我们冲洗并重复下一步。 +在分词器训练期间的任何一步,BPE 算法都会搜索最常见的现有 tokens 对 (在这里,“对”是指一个词中的两个连续 tokens )。最常见的这一对会被合并,然后我们重复这个过程。 -回到我们之前的例子,让我们假设单词具有以下频率: +回到我们之前的例子,让我们假设单词具有以下频率: ``` ("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) ``` -意味着 `"hug"` 在语料库中出现了10次, `"pug"` 5次, `"pun"` 12次, `"bun"` 4次, 以及 `"hugs"` 5次。我们通过将每个单词拆分为字符(形成我们初始词汇表的字符)来开始训练,这样我们就可以将每个单词视为一个标记列表: +意思是 `"hug"` 在语料库中出现了 10 次, `"pug"` 出现了 5 次, `"pun"` 出现了 12 次, `"bun"` 出现了 4 次, `"hugs"` 出现了 5 次。我们通过将每个单词拆分为字符(形成我们初始词汇表的字符)来开始训练,这样我们就可以将每个单词视为一个 tokens 列表: ``` ("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 次。 +然后我们看看相邻的字符对。 `("h", "u")` 在词 `"hug"` 和 `"hugs"` 中出现,所以在语料库中总共出现了 15 次。然而,最常见的对属于 `("u", "g")` ,它在 `"hug"` 、 `"pug"` 和 `"hugs"` 中出现,总共在词汇表中出现了 20 次。 -因此,标记器学习的第一个合并规则是 `("u", "g") -> "ug"`,意思就是 `"ug"` 将被添加到词汇表中,并且这对应该合并到语料库的所有单词中。在这个阶段结束时,词汇表和语料库看起来像这样: +因此,tokenizer 学习的第一个合并规则是 `("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) +词汇表: ["b", "g", "h", "n", "p", "s", "u", "ug"] +语料库: ("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"`。将其添加到词汇表并合并所有现有的这个对,将出现: +现在我们有一些对,继续合并的话会产生一个比两个字符长的 tokens 例如 `("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) +词汇表: ["b", "g", "h", "n", "p", "s", "u", "ug", "un"] +语料库: ("h" "ug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("h" "ug" "s", 5) ``` -现在最频繁的一对是 `("h", "ug")`,所以我们学习了合并规则 `("h", "ug") -> "hug"`,这给了我们第一个三个字母的标记。合并后,语料库如下所示: +现在最频繁的一对是 `("h", "ug")` ,所以我们学习了合并规则 `("h", "ug") -> "hug"` ,这形成了我们第一个三个字母的 tokens 合并后,语料库如下所示: ``` -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) +词汇表: ["b", "g", "h", "n", "p", "s", "u", "ug", "un", "hug"] +语料库: ("hug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("hug" "s", 5) ``` -我们继续这样合并,直到达到我们所需的词汇量。 +我们继续这样合并,直到达到我们所需的词汇量。 -✏️ **现在轮到你了!**你认为下一个合并规则是什么? +✏️ **现在轮到你了!** 你认为下一个合并规则是什么? -## 标记化算法 [[标记化算法]] +## tokenization [[tokenization]] -标记化紧跟训练过程,从某种意义上说,通过应用以下步骤对新输入进行标记: +完成训练之后就可以对新的输入 tokenization 了,从某种意义上说,新的输入会依照以下步骤对新输入进行 tokenization: -1. 规范化 -2. 预标记化 +1. 标准化 +2. 预分词 3. 将单词拆分为单个字符 -4. 将学习的合并规则按顺序应用于这些拆分 +4. 根据学习的合并规则,按顺序合并拆分的字符 -让我们以我们在训练期间使用的示例为例,学习三个合并规则: +让我们以我们在训练期间使用的示例为例,Tokenizer 学习到了三个合并规则: ``` ("u", "g") -> "ug" @@ -97,19 +97,19 @@ Corpus: ("hug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("hug" "s", 5 ("h", "ug") -> "hug" ``` -这个单词 `"bug"` 将被标记为 `["b", "ug"]`。然而 `"mug"`,将被标记为 `["[UNK]", "ug"]`,因为字母 `"m"` 不再基本词汇表中。同样,单词`"thug"` 会被标记为 `["[UNK]", "hug"]`: 字母 `"t"` 不在基本词汇表中,应用合并规则首先导致 `"u"` 和 `"g"` 被合并,然后是 `"hu"` 和 `"g"` 被合并。 +在这种情况下,单词 `"bug"` 将被转化为 `["b", "ug"]` 。然而 `"mug"` ,将被转换为 `["[UNK]", "ug"]` ,因为字母 `"m"` 不再基本词汇表中。同样,单词 `"thug"` 会被转换为 `["[UNK]", "hug"]` :字母 `"t"` 不在基本词汇表中,使用合并规则首先会将 `"u"` 和 `"g"` 合并,然后将 `"h"` 和 `"ug"` 合并。 -✏️ **现在轮到你了!** 你认为这个词 `"unhug"` 将如何被标记? +✏️ **现在轮到你了!** 你认为这个词 `"unhug"` 将如何被 tokenization? -## 实现 BPE [[实现 BPE]] +## 实现 BPE 算法[[实现 BPE 算法]] -现在让我们看一下 BPE 算法的实现。这不会是你可以在大型语料库上实际使用的优化版本;我们只是想向你展示代码,以便你可以更好地理解算法 +现在,让我们看一下 BPE 算法的实现。这并不是在大型语料库上实际使用的经过优化的版本;我们只是想向你展示代码,以便你可以更好地理解算法 -首先我们需要一个语料库,所以让我们用几句话创建一个简单的语料库: +首先,我们需要一个语料库,让我们创建一个含有几句话的简单语料库: ```python corpus = [ @@ -120,7 +120,7 @@ corpus = [ ] ``` -接下来,我们需要将该语料库预先标记为单词。由于我们正在复制 BPE 标记器(如 GPT-2),我们将使用 `gpt2` 标记器作为预标记化的标记器: +接下来,我们需要将该语料库预分词为单词。由于我们正在复现一个 BPE tokenizer (例如 GPT-2),我们将使用 `gpt2` 分词器进行预分词: ```python from transformers import AutoTokenizer @@ -128,7 +128,7 @@ from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("gpt2") ``` -然后我们在进行预标记化时计算语料库中每个单词的频率: +然后,我们在进行预分词的同时计算语料库中每个单词的频率: ```python from collections import defaultdict @@ -151,7 +151,7 @@ defaultdict(int, {'This': 3, 'Ġis': 2, 'Ġthe': 1, 'ĠHugging': 1, 'ĠFace': 1, 'Ġthey': 1, 'Ġare': 1, 'Ġtrained': 1, 'Ġand': 1, 'Ġgenerate': 1, 'Ġtokens': 1}) ``` -下一步是计算基本词汇,由语料库中使用的所有字符组成: +下一步是计算基础词汇表,这由语料库中使用的所有字符组成: ```python alphabet = [] @@ -170,19 +170,19 @@ print(alphabet) 't', 'u', 'v', 'w', 'y', 'z', 'Ġ'] ``` -我们还在该词汇表的开头添加了模型使用的特殊标记。对于GPT-2,唯一的特殊标记是 `"<|endoftext|>"`: +我们还在该词汇表的开头添加了模型使用的特殊 tokens 对于 GPT-2,唯一的特殊 tokens 是 `"<|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): @@ -197,7 +197,7 @@ def compute_pair_freqs(splits): return pair_freqs ``` -让我们来看看这个字典在初始拆分后的一部分: +让我们来看看这个字典在初始合并后的一些结果: ```python pair_freqs = compute_pair_freqs(splits) @@ -217,7 +217,8 @@ for i, key in enumerate(pair_freqs.keys()): ('t', 'h'): 3 ``` -现在, 找到最频繁的对只需要一个快速的循环: +现在,只需要一个简单的循环就可以找到出现频率最高的对: + ```python best_pair = "" @@ -235,14 +236,14 @@ print(best_pair, max_freq) ('Ġ', 't') 7 ``` -所以第一个要学习的合并是 `('Ġ', 't') -> 'Ġt'`, 我们添加 `'Ġt'` 到词汇表: +所以第一个要学习的合并规则是 `('Ġ', 't') -> 'Ġt'` ,我们将 `'Ġt'` 添加到词汇表: ```python merges = {("Ġ", "t"): "Ġt"} vocab.append("Ġt") ``` -要继续接下来的步骤,我们需要在我们的`分词`字典中应用该合并。让我们为此编写另一个函数: +接下来,我们需要在我们的 `splits` 字典中进行这个合并。让我们为此编写另一个函数: ```python def merge_pair(a, b, splits): @@ -261,7 +262,7 @@ def merge_pair(a, b, splits): return splits ``` -我们可以看看第一次合并的结果: +我们可以观察一下第一次合并的结果: ```py splits = merge_pair("Ġ", "t", splits) @@ -272,7 +273,7 @@ print(splits["Ġtrained"]) ['Ġt', 'r', 'a', 'i', 'n', 'e', 'd'] ``` -现在我们有了循环所需的一切,直到我们学会了我们想要的所有合并。我们的目标是词汇量达到50: +现在我们有了我们需要的所有代码,可以循环直到我们学习到我们想要的所有合并。让我们把目标词汇表的大小设定为 50: ```python vocab_size = 50 @@ -290,7 +291,7 @@ while len(vocab) < vocab_size: vocab.append(best_pair[0] + best_pair[1]) ``` -结果,我们学习了 19 条合并规则(初始词汇表的大小 31 -- 30 字母字符,加上特殊标记): +最终,我们学习了 19 条合并规则(初始词汇量为 31 —— 字母表中的 30 个字符,加上特殊 token ): ```py print(merges) @@ -303,7 +304,7 @@ print(merges) ('i', 'n'): 'in', ('Ġa', 'b'): 'Ġab', ('Ġtoken', 'i'): 'Ġtokeni'} ``` -词汇表由特殊标记、初始字母和所有合并结果组成: +词汇表由特殊 token 初始字母和所有合并结果组成: ```py print(vocab) @@ -317,11 +318,11 @@ print(vocab) -💡 在同一语料库上使用 `train_new_from_iterator()` 不会产生完全相同的词汇表。这是因为当有最频繁对的选择时,我们选择遇到的第一个, 而 🤗 Tokenizers 库根据内部ID选择第一个。 +💡 在同一语料库上使用 `train_new_from_iterator()` 可能不会产生完全相同的词汇表。这是因为当有多个出现频率最高的对时,我们选择遇到的第一个,而 🤗 Tokenizers 库根据内部 ID 选择第一个。 -为了对新文本进行分词,我们对其进行预分词、拆分,然后应用学到的所有合并规则: +为了对新文本进行分词,我们对其进行预分词、拆分,然后使用学到的所有合并规则: ```python def tokenize(text): @@ -341,7 +342,8 @@ def tokenize(text): return sum(splits, []) ``` -我们可以在任何由字母表中的字符组成的文本上尝试这个: +我们可以尝试在任何由字母表中的字符组成的文本上进行此操作: + ```py tokenize("This is not a token.") @@ -353,8 +355,8 @@ tokenize("This is not a token.") -⚠️ 如果存在未知字符,我们的实现将抛出错误,因为我们没有做任何处理它们。GPT-2 实际上没有未知标记(使用字节级 BPE 时不可能得到未知字符),但这可能发生在这里,因为我们没有在初始词汇表中包含所有可能的字节。 BPE 的这方面超出了本节的范围,因此我们忽略了细节。 +⚠️ 如果存在未知字符,我们的实现将抛出错误,因为我们没有做任何处理它们。GPT-2 实际上没有未知 tokens (使用字节级 BPE 时不可能得到未知字符),但这里的代码可能会出现这个错误,因为我们并未在初始词汇中包含所有可能的字节。BPE 的这一部分已超出了本节的范围,因此我们省略了一些细节。 -这就是 BPE 算法!接下来,我们将看看 WordPiece。 \ No newline at end of file +至此,BPE 算法的介绍就到此结束!接下来,我们将研究 WordPiece 算法。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter6/6.mdx b/chapters/zh-CN/chapter6/6.mdx index 8f3b746ba..799c8737c 100644 --- a/chapters/zh-CN/chapter6/6.mdx +++ b/chapters/zh-CN/chapter6/6.mdx @@ -1,4 +1,4 @@ -# WordPiece 标记化 [[WordPiece 标记化]] +# WordPiece tokenization 算法 [[WordPiece tokenization算法]] -WordPiece 是 Google 为预训练 BERT 而开发的标记化算法。此后,它在不少基于 BERT 的 Transformer 模型中得到重用,例如 DistilBERT、MobileBERT、Funnel Transformers 和 MPNET。它在训练方面与 BPE 非常相似,但实际标记化的方式不同。 +WordPiece 是 Google 开发的用于 BERT 预训练的分词算法。自此之后,很多基于 BERT 的 Transformer 模型都复用了这种方法,比如 DistilBERT,MobileBERT,Funnel Transformers 和 MPNET。它在训练方面与 BPE 非常类似,但实际的分词方法有所不同。 -💡 本节深入介绍 WordPiece,甚至展示完整的实现。如果您只想大致了解标记化算法,可以跳到最后。 +💡 本节详细讲述了 WordPiece,甚至展示了一个完整的实现。如果你只想对这个分词算法有个大概的理解,可以直接跳到最后。 -## 训练算法 [[训练算法]] +## WordPiece 训练 [[WordPiece 训练]] -⚠️ Google 从未开源 WordPiece 训练算法的实现,因此以下是我们基于已发表文献的最佳猜测。它可能不是 100% 准确的。 +⚠️ Google 从未开源 WordPiece 训练算法的实现,因此以下是我们基于已发表文献的最佳猜测。它可能并非 100% 准确的。 -与 BPE 一样,WordPiece 从一个小词汇表开始,包括模型使用的特殊标记和初始字母表。因为它通过添加前缀来识别子词 (如同 `##` 对于 BERT),每个单词最初是通过将该前缀添加到单词内的所有字符来拆分的。所以,例如 `"word"` ,像这样拆分: +与BPE 一样,WordPiece 也是从包含模型使用的特殊 tokens 和初始字母表的小词汇表开始的。由于它是通过添加前缀(如 BERT 中的 `##` )来识别子词的,每个词最初都会通过在词内部所有字符前添加该前缀进行分割。因此,例如 `"word"` 将被这样分割: ``` w ##o ##r ##d ``` -因此,初始字母表包含出现在单词开头的所有字符以及出现在单词内部的以 WordPiece 前缀开头的字符。 +因此,初始字母表包含所有出现在单词第一个位置的字符,以及出现在单词内部并带有 WordPiece 前缀的字符。 -然后,再次像 BPE 一样,WordPiece 学习合并规则。主要区别在于选择要合并的对的方式。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"` 这两个词单独出现地频率可能较低。 +$$\mathrm{score} = (\mathrm{freq\_of\_pair}) / (\mathrm{freq\_of\_first\_element} \times\mathrm{freq\_of\_second\_element})$$ -让我们看看我们在 BPE 训练示例中使用的相同词汇: +通过将两部分合在一起的频率除以其中各部分的频率的乘积,该算法优先合并那些在词汇表中单独出现出现的对。例如,即使 `("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")`。 +所以最初的词汇表将会是 `["b", "h", "p", "##g", "##n", "##s", "##u"]` (如果我们暂时忽略特殊 tokens )。出现频率最高的一对是 `("##u", "##g")` (目前 20 次),但 `"##u"` 和其他单词一起出现的频率非常高,所以它的分数不是最高的(分数是 1 / 36)。所有带有 `"##u"` 的对实际上都有相同的分数(1 / 36),所以分数最高的对是 `("##g", "##s")` —— 唯一没有 `"##u"` 的对——分数是 1 / 20,所以学习的第一个合并是 `("##g", "##s") -> ("##gs")` 。 -请注意,当我们合并时,我们删除了两个标记之间的 `##`,所以我们添加 `"##gs"` 到词汇表中,并在语料库的单词中应用该合并: +请注意,当我们合并时,我们会删除两个 tokens 之间的 `##` ,所以我们将 `"##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) +词汇表: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs"] +语料库: ("h" "##u" "##g", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("h" "##u" "##gs", 5) ``` -在这一点中, `"##u"` 是在所有可能的对中,因此它们最终都具有相同的分数。假设在这种情况下,第一对被合并, `("h", "##u") -> "hu"`。这使得我们: +此时, `"##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) +词汇表: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu"] +语料库: ("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 相比),因此合并得分最高的第一对: +然后,下一个最佳得分的对是 `("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) +词汇表: ["b", "h", "p", "##g", "##n", "##s", "##u", "##gs", "hu", "hug"] +语料库: ("hug", 10), ("p" "##u" "##g", 5), ("p" "##u" "##n", 12), ("b" "##u" "##n", 4), ("hu" "##gs", 5) ``` -我们继续这样处理,直到达到我们所需的词汇量。 +然后我们就按此方式继续,直到我们达到所需的词汇表大小。 -✏️ **现在轮到你了!** 下一个合并规则是什么? +✏️ **现在轮到你了!** 下一个合并规则是什么? + -## 标记化算法 [[标记化算法]] +## tokenization 算法 [[tokenization 算法]] -WordPiece 和 BPE 中的标记化的不同在于 WordPiece 只保存最终词汇,而不是学习的合并规则。从要标记的单词开始,WordPiece 找到词汇表中最长的子词,然后对其进行拆分。例如,如果我们使用上面例子中学到的词汇,对于单词 `"hugs"`,词汇表中从头开始的最长子词是 `"hug"`,所以我们在那里拆分并得到 `["hug", "##s"]`。 然后我们继续使用词汇表中的 `"##s"`,因此 `"hugs"` 的标记化是 `["hug", "##s"]`. +WordPiece 和 BPE 的分词方式有所不同,WordPiece 只保存最终词汇表,而不保存学习到的合并规则。WordPiece 从待分词的词开始,找到词汇表中最长的子词,然后在其处分割。例如,如果我们使用上述示例中学习到的词汇表,对于词 `"hugs"` ,从开始处的最长子词在词汇表中是 `"hug"` ,所以我们在那里分割,得到 `["hug", "##s"]` 。然后我们继续处理 `"##s"` ,它在词汇表中,所以 `"hugs"` 的分词结果是 `["hug", "##s"]` 。 -使用 BPE, 我们将按顺序应用学习到的合并并将其标记为 `["hu", "##gs"]`,所以编码不同。 +如果使用 BPE,我们会按照学习到的合并规则进行合并,并将其分词为 `["hu", "##gs"]` ,不同的字词分词算法所以最终得到的编码是不同的。 -再举一个例子,让我们看看 `"bugs"` 将如何被标记化。 `"b"` 是从词汇表中单词开头开始的最长子词,所以我们在那里拆分并得到 `["b", "##ugs"]`。然后 `"##u"` 是词汇表中从 `"##ugs"` 开始的最长的子词,所以我们在那里拆分并得到 `["b", "##u, "##gs"]`。最后, `"##gs"` 在词汇表中,所以最后一个列表是 `"bugs"` 的标记化。 +再举一个例子,让我们看看 `"bugs"` 将如何分词的。 `"b"` 是从词汇表中单词开头开始的最长子词,所以我们在那里分割并得到 `["b", "##ugs"]` 。然后 `"##u"` 是词汇表中从 `"##ugs"` 开始的最长的子词,所以我们在那里拆分并得到 `["b", "##u, "##gs"]` 。最后, `"##gs"` 在词汇表中,因此 `"bugs"` 的分词结果是: `["b", "##u, "##gs"]` 。 -当分词达到无法在词汇表中找到子词的阶段时, 整个词被标记为未知 -- 例如, `"mug"` 将被标记为 `["[UNK]"]`,就像 `"bum"` (即使我们可以以 `"b"` 和 `"##u"` 开始, `"##m"` 不在词汇表中,由此产生的标记将只是 `["[UNK]"]`, 不是 `["b", "##u", "[UNK]"]`)。这是与 BPE 的另一个区别,BPE 只会将不在词汇表中的单个字符分类为未知。 +当分词过程中无法在词汇库中找到该子词时,整个词会被标记为 unknown(未知)—— 例如, `"mug"` 将被标记为 `["[UNK]"]` , `"bum"` 也是如此(即使我们的词汇表中包含 `"b"` 和 `"##u"` 开始,但是 `"##m"` 不在词汇表中,因此最终的分词结果只会是 `["[UNK]"]` ,而不是 `["b", "##u", "[UNK]"]` )。这是与 BPE 的另一个区别,BPE 只会将不在词汇库中的单个字符标记为 unknown。 -✏️ **现在轮到你了!** `"pugs"` 将被如何标记? +✏️ **现在轮到你了!** `"pugs"` 将被如何分词? ## 实现 WordPiece [[实现 WordPiece]] -现在让我们看一下 WordPiece 算法的实现。与 BPE 一样,这只是教学,你将无法在大型语料库中使用它。 +现在让我们看一下 WordPiece 算法的实现。与 BPE 一样,这只是教学示例,你不能在大型语料库上使用。 -我们将使用与 BPE 示例中相同的语料库: +我们将使用与 BPE 示例中相同的语料库: ```python corpus = [ @@ -112,7 +114,7 @@ corpus = [ ] ``` -首先,我们需要将语料库预先标记为单词。由于我们正在复制 WordPiece 标记器 (如 BERT),因此我们将使用 `bert-base-cased` 标记器用于预标记化: +首先,我们需要将语料库预分词为单词。由于我们正在复刻 WordPiece tokenizer (如 BERT),因此我们将使用 `bert-base-cased` tokenizer 进行预分词: ```python from transformers import AutoTokenizer @@ -120,7 +122,7 @@ from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") ``` -然后我们在进行预标记化时计算语料库中每个单词的频率: +然后我们在进行预分词的同时,计算语料库中每个单词的频率: ```python from collections import defaultdict @@ -143,7 +145,7 @@ defaultdict( 'trained': 1, 'and': 1, 'generate': 1, 'tokens': 1}) ``` -正如我们之前看到的,字母表是由单词的所有第一个字母组成的唯一集合,以及出现在前缀为 `##` 的其他字母: +如我们之前看到的,字母表是一个独特的集合,由所有单词的第一个字母以及所有以 `##` 为前缀和在单词中的其他字母组成: ```python alphabet = [] @@ -166,13 +168,13 @@ print(alphabet) 'w', 'y'] ``` -我们还在该词汇表的开头添加了模型使用的特殊标记。在使用 BERT 的情况下,它是列表 `["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]`: +我们还在该词汇表的开头添加了模型使用的特殊 tokens,在使用 BERT 的情况下,特殊 tokens 是 `["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"]` : ```python vocab = ["[PAD]", "[UNK]", "[CLS]", "[SEP]", "[MASK]"] + alphabet.copy() ``` -接下来我们需要拆分每个单词, 所有不是第一个字母的字母都以 `##` 为前缀: +接下来我们需要将每个单词进行分割,除了第一个字母外,其他字母都需要以 `##` 为前缀: ```python splits = { @@ -181,7 +183,7 @@ splits = { } ``` -现在我们已经准备好训练了,让我们编写一个函数来计算每对的分数。我们需要在训练的每个步骤中使用它: +现在我们已经准备好训练了,让我们编写一个函数来计算每对的分数。我们需要在训练的每个步骤中使用它: ```python def compute_pair_scores(splits): @@ -205,7 +207,7 @@ def compute_pair_scores(splits): return scores ``` -让我们来看看这个字典在初始拆分后的一部分: +让我们来看看在初始分割后的部分字典: ```python pair_scores = compute_pair_scores(splits) @@ -224,7 +226,7 @@ for i, key in enumerate(pair_scores.keys()): ('##h', '##e'): 0.011904761904761904 ``` -现在,找到得分最高的对只需要一个快速循环: +现在,只需要一个快速循环就可以找到得分最高的对: ```python best_pair = "" @@ -241,13 +243,13 @@ print(best_pair, max_score) ('a', '##b') 0.2 ``` -所以第一个要学习的合并是 `('a', '##b') -> 'ab'`, 并且我们添加 `'ab'` 到词汇表中: +所以第一个要学习的合并是 `('a', '##b') -> 'ab'` ,并且我们添加 `'ab'` 到词汇表中: ```python vocab.append("ab") ``` -要继续接下来的步骤,我们需要在我们的 `拆分` 字典中应用该合并。让我们为此编写另一个函数: +接下来,我们需要对 `splits` 字典进行这种合并。让我们为此写另一个函数: ```python def merge_pair(a, b, splits): @@ -266,7 +268,7 @@ def merge_pair(a, b, splits): return splits ``` -我们可以看看第一次合并的结果: +我们可以看看第一次合并的结果: ```py splits = merge_pair("a", "##b", splits) @@ -277,7 +279,7 @@ splits["about"] ['ab', '##o', '##u', '##t'] ``` -现在我们有了循环所需的一切,直到我们学会了我们想要的所有合并。我们的目标词汇量为70: +现在我们有了合并循环的所有代码。让我们设定词汇表的大小为 70: ```python vocab_size = 70 @@ -297,7 +299,7 @@ while len(vocab) < vocab_size: vocab.append(new_token) ``` -然后我们可以查看生成的词汇表: +然后我们可以查看生成的词汇表: ```py print(vocab) @@ -311,15 +313,15 @@ print(vocab) '##ut'] ``` -正如我们所看到的,与 BPE 相比,这个标记器将单词的一部分作为标记学习得更快一些。 +如我们所见,相较于 BPE(字节对编码),此分词器在学习单词部分作为 tokens 时稍快一些。 -💡 在同一语料库上使用 `train_new_from_iterator()` 不会产生完全相同的词汇表。这是因为 🤗 Tokenizers 库没有为训练实现 WordPiece(因为我们不完全确定它的内部结构),而是使用 BPE。 +💡 在同一语料库上使用 `train_new_from_iterator()` 不会产生完全相同的词汇表。这是因为 🤗 Tokenizers 库没有为训练实现 WordPiece(因为我们不完全确定它的真实实现方式),而是使用了 BPE。 -为了对新文本进行分词,我们对其进行预分词、拆分,然后对每个单词应用分词算法。也就是说,我们从第一个词的开头寻找最大的子词并将其拆分,然后我们在第二部分重复这个过程,对于该词的其余部分和文本中的以下词,依此类推: +要对新文本进行分词,我们先预分词,再进行分割,然后在每个词上使用分词算法。也就是说,我们寻找从第一个词开始的最大子词并将其分割,然后我们对第二部分重复此过程,以此类推,对该词以及文本中的后续词进行分割: ```python def encode_word(word): @@ -337,7 +339,7 @@ def encode_word(word): return tokens ``` -让我们用词汇表中的一个单词和另一个不在词汇表中的单词进行测试: +让我们使用词汇表中的一个词和一个不在词汇表中的词上测试一下: ```python print(encode_word("Hugging")) @@ -349,7 +351,7 @@ print(encode_word("HOgging")) ['[UNK]'] ``` -现在,让我们编写一个标记文本的函数: +现在,让我们编写一个对文本分词的函数: ```python def tokenize(text): @@ -359,7 +361,7 @@ def tokenize(text): return sum(encoded_words, []) ``` -我们可以在任何文本上尝试: +我们可以在任何文本上尝试: ```python tokenize("This is the Hugging Face course!") @@ -370,4 +372,4 @@ tokenize("This is the Hugging Face course!") '##e', '[UNK]'] ``` -这就是 WordPiece 算法的全部内容!现在让我们来看看 Unigram。 +这就是 WordPiece 算法的全部内容!现在让我们来看看 Unigram。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter6/7.mdx b/chapters/zh-CN/chapter6/7.mdx index 95202c1e3..98e16f6fb 100644 --- a/chapters/zh-CN/chapter6/7.mdx +++ b/chapters/zh-CN/chapter6/7.mdx @@ -1,4 +1,4 @@ -# Unigram标记化 [[Unigram标记化]] +# Unigram tokenization 算法 [[Unigram tokenization算法]] -在 SentencePiece 中经常使用 Unigram 算法,该算法是 AlBERT、T5、mBART、Big Bird 和 XLNet 等模型使用的标记化算法。 +Unigram 算法常用于 SentencePiece 中,该切分算法被 AlBERT,T5,mBART,Big Bird 和 XLNet 等模型广泛采用。 -💡 本节深入介绍了 Unigram,甚至展示了一个完整的实现。如果你只想大致了解标记化算法,可以跳到最后。 +💡 本节将深入探讨 Unigram,甚至展示完整的实现过程。如果你只想大致了解 tokenization 算法,可以直接跳到章节末尾。 -## 训练算法 [[训练算法]] +## Unigram 训练 [[Unigram 训练]] -与 BPE 和 WordPiece 相比,Unigram 在另一个方向上工作:它从一个较大的词汇表开始,然后从中删除标记,直到达到所需的词汇表大小。有多种选项可用于构建基本词汇表:例如,我们可以采用预标记化单词中最常见的子串,或者在具有大词汇量的初始语料库上应用 BPE。 +与BPE 和 WordPiece 相比,Unigram 的工作方式正好相反:它从一个大词汇库开始,然后逐步删除词汇,直到达到目标词汇库大小。构建基础词汇库有多种方法:例如,我们可以选取预切分词汇中最常见的子串,或者在具有大词汇量的初始语料库上进行 BPE 得到一个初始词库。 -在训练的每一步,Unigram 算法都会在给定当前词汇的情况下计算语料库的损失。然后,对于词汇表中的每个符号,算法计算如果删除该符号,整体损失会增加多少,并寻找增加最少的符号。这些符号对语料库的整体损失影响较小,因此从某种意义上说,它们“不太需要”并且是移除的最佳候选者。 +在训练的每一步,Unigram 算法都会在给定当前词汇的情况下计算语料库的损失。然后,对于词汇表中的每个符号,算法计算如果删除该符号,整体损失会增加多少,并寻找删除后损失增加最少的符号。这些符号对语料库的整体损失影响较小,因此从某种意义上说,它们“相对不必要”并且是移除的最佳候选者。 -这是一个非常昂贵的操作,所以我们不只是删除与最低损失增加相关的单个符号,而且\\(p\\) (\\(p\\)是一个可以控制的超参数,通常是 10 或 20)与最低损失增加相关的符号的百分比。然后重复这个过程,直到词汇量达到所需的大小。 +这个过程非常消耗计算资源,因此我们不只是删除与最低损失增长相关的单个符号,而是删除与最低损失增长相关的百分之/p (p 是一个可以控制的超参数,通常是 10 或 20)的符号。然后重复此过程,直到词汇库达到所需大小。 -请注意,我们从不删除基本字符,以确保可以标记任何单词。 +注意,我们永远不会删除基础的单个字符,以确保任何词都能被切分。 -现在,这仍然有点模糊:算法的主要部分是计算语料库的损失,并查看当我们从词汇表中删除一些标记时它会如何变化,但我们还没有解释如何做到这一点。这一步依赖于 Unigram 模型的标记化算法,因此我们接下来将深入研究。 +然而,这仍然有些模糊:算法的主要部分是在词汇库中计算语料库的损失并观察当我们从词汇库中移除一些符号时损失如何变化,但我们尚未解释如何做到这一点。这一步依赖于 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 算法 [[tokenization算法]] -Unigram 模型是一种语言模型,它认为每个标记都独立于它之前的标记。它是最简单的语言模型,从某种意义上说, 给定先前上下文的标记 X 的概率就是标记 X 的概率。因此,如果我们使用 Unigram 语言模型生成文本,我们将始终预测最常见的标记。 +Unigram 模型是一种语言模型,它认为每个符号都与其之前的符号独立。这是最简单的语言模型,因此给定之前的上下文情况下,符号 X 的概率就是符号 X 的概率。所以,如果我们使用 Unigram 语言模型来生成文本,我们的预测总会输出最常见的符号。 -给定标记的概率是它在原始语料库中的频率(我们找到它的次数),除以词汇表中所有标记的所有频率的总和(以确保概率总和为 1)。例如, `"ug"` 在 `"hug"` 、 `"pug"` 以及 `"hugs"` 中,所以它在我们的语料库中的频率为 20。 +给定符号的概率是其在原始语料库中的频率(我们计算它出现的次数),除以词汇库中所有符号的频率总和(以确保概率总和为 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。 +所以,所有频率之和为 210,子词 `"ug"` 出现的概率是 20/210。 -✏️ **现在轮到你了!** 编写代码来计算上面的频率,并仔细检查显示的结果以及总和是否正确。 +✏️ **现在轮到你了!** 编写代码计算上述频率,然后验证结果的准确性,以及概率的总和是否正确。 -现在,为了对给定的单词进行标记,我们将所有可能的分割视为标记,并根据 Unigram 模型计算每个分割的概率。由于所有标记都被认为是独立的,所以这个概率只是每个标记概率的乘积。例如, `"pug"` 的标记化 `["p", "u", "g"]` 的概率为: +现在,为了对一个给定的单词进行分词,我们会查看所有可能的分词组合,并根据 Unigram 模型计算出每种可能的概率。由于所有的分词都被视为独立的,因此这个单词分词的概率就是每个子词概率的乘积。例如,将 `"pug"` 分词为 `["p", "u", "g"]` 的概率为: + $$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"]` 的概率为: +相比之下,将 “pug” 分词为 `["pu", "g"]` 的概率为: $$P([``pu", ``g"]) = P(``pu") \times P(``g") = \frac{5}{210} \times \frac{20}{210} = 0.0022676$$ -所以一个更有可能。一般来说,具有尽可能少的标记的标记化将具有最高的概率(因为每个标记重复除以 210),这对应于我们直观想要的:将一个单词分成尽可能少的标记。 +因此,后者的可能性更大。一般来说,分词数最少的分词方式将具有最高的概率(因为每个分词都要除以 210),这正符合我们的直觉:将一个词分割为尽可能少的子词。 -使用 Unigram 模型对单词进行分词是概率最高的分词。在示例 `"pug"` 中,这里是我们为每个可能的分割获得的概率: +利用 Unigram 模型对一个词进行分词,就是找出概率最高的分词方式。以 `"pug"` 为例,我们得到的各种可能分词方式的概率如下: ``` ["p", "u", "g"] : 0.000389 @@ -80,13 +81,13 @@ $$P([``pu", ``g"]) = P(``pu") \times P(``g") = \frac{5}{210} \times \frac{20}{21 ["pu", "g"] : 0.0022676 ``` -所以, `"pug"` 将被标记为 `["p", "ug"]` 或者 `["pu", "g"]`, 取决于首先遇到这些分割中的哪一个(请注意,在更大的语料库中,这样的相等的情况很少见)。 +因此, `"pug"` 将被分词为 `["p", "ug"]` 或 `["pu", "g"]` ,取决于哪种分词方式排在前面(注意,在更大的语料库中,像这样的相等情况将很少见)。 -在这种情况下,很容易找到所有可能的分割并计算它们的概率,但一般来说会有点困难。有一种用于此的经典算法,称为 *维特比(Viterbi)算法*。本质上,我们可以构建一个图来检测给定单词的可能分割,如果从_a_到_b_的子词在词汇表中,则从字符_a_到字符_b_之间存在一个分支,并将子词的概率归因于该分支。 +在这个例子中,找出所有可能的分词方式并计算其概率是容易的,但在语料库比较大的情况下有些困难。有一个经典的算法可以用来计算这个概率,叫做 `Viterbi 算法` 。事实上,我们可以通过创建一个图来表示一个给定单词的所有可能分词,如果从字符 `a` 到字符 `b` 的子词在词汇表中,那么就存在一个从 `a` 到 `b` 的分支,分支的边就是进行这个切分的概率。 -为了在该图中找到将具有最佳分数的路径,维特比算法为单词中的每个位置确定在该位置结束的具有最佳分数的分段。由于我们从开始到结束,可以通过循环遍历以当前位置结尾的所有子词,然后使用该子词开始位置的最佳标记化分数来找到最佳分数。然后,我们只需要展开到达终点所采取的路径。 +为了在图中找到得分最高的路径,Viterbi 算法会确定出每个位置上结束的最佳得分分割。我们从头到尾进行处理,可以通过遍历所有在当前位置结束的子词,然后使用这个子词开始位置的最佳得分,找到最高得分。然后,我们只需要回溯走过的路径,就能找到最终的最优路径。 -让我们看一个使用我们的词汇表和单词 `"unhug"` 的例子。对于每个位置,以最好的分数结尾的子词如下: +让我们看一个使用我们的词汇表和单词 `"unhug"` 的例子。对于每个位置,最佳切分子词的分数如下: ``` Character 0 (u): "u" (score 0.171429) @@ -96,27 +97,28 @@ Character 3 (u): "un" "hu" (score 0.005442) Character 4 (g): "un" "hug" (score 0.005442) ``` -因此 `"unhug"` 将被标记为 `["un", "hug"]`。 +因此 “unhug” 将被分词为 `["un", "hug"]` 。 + -✏️ **现在轮到你了!** 确定单词 `"huggun"` 的标记化及其分数。 +✏️ **现在轮到你了!** 确定单词 “huggun” 的分词方式以及其得分。 ## 回到训练 [[回到训练]] -现在我们已经了解了标记化的工作原理,我们可以更深入地研究训练期间使用的损失。在任何给定的阶段,这个损失是通过对语料库中的每个单词进行标记来计算的,使用当前词汇表和由语料库中每个标记的频率确定的 Unigram 模型(如前所述)。 +我们已经了解了如何进行分词处理,接下来我们可以更详细地了解一下在训练过程中如何计算损失值。在训练的每个阶段,我们都会将语料库中的每个词进行分词,分词所使用的词表和 Unigram 模型是基于目前的情况(即根据每个词在语料库中出现的频率)来确定的。然后,基于这种分词结果,我们就可以计算出损失值(loss)。 -语料库中的每个词都有一个分数,损失是这些分数的负对数似然 -- 即所有词的语料库中所有词的总和 `-log(P(word))`。 +语料库中的每个词都有一个分数,损失(loss)值是这些分数的负对数似然 —— 即所有词的语料库中所有词的 `-log(P(word))` 总和 -让我们用以下语料库回到我们的例子: +让我们回到我们的例子,以下是我们的语料库: ``` ("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5) ``` -每个单词的标记化及其各自的分数是: +每个单词的分词及其相应的得分如下: ``` "hug": ["hug"] (score 0.071428) @@ -126,34 +128,34 @@ Character 4 (g): "un" "hug" (score 0.005442) "hugs": ["hug", "s"] (score 0.001701) ``` -所以损失是: +因此,损失值(loss)是: ``` 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"` 标记将给出完全相同的损失。 +现在,我们需要计算移除每个 token 对损失值的影响。这个过程颇为繁琐,所以我们这里仅对两个单词进行演示,在我们编写代码来协助处理这个过程时,再对全部的词进行 tokenization 的处理。在这个(非常)特殊的例子中,我们对单词的两种等效的分词方式:例如,“pug”可以被分词为 `["pu", "g"]` ,也可以被分词为 `["p", "ug"]` ,获得的分数是相同的。因此,去除词汇表中的 `"pu"` 损失值还会是一样的。 -另一方面,去除 `"hug"` 损失变得更糟, 因为 `"hug"` 和 `"hugs"` 的标记化会变成: +但是,去除 `"hug"` 之后,损失会变得更糟,因为 `"hug"` 和 `"hugs"` 的 tokenization 会变成: ``` "hug": ["hu", "g"] (score 0.006802) "hugs": ["hu", "gs"] (score 0.001701) ``` -这些变化将导致损失增加: +这些变化将导致损失增加: ``` - 10 * (-log(0.071428)) + 10 * (-log(0.006802)) = 23.5 ``` -因此, 标记 `"pu"`可能会从词汇表中删除,但不会删除 `"hug"`. +因此, `"pu"` tokens 可能会从词汇表中移除,但 `"hug"` 则不会。 ## 实现 Unigram [[实现 Unigram]] -现在让我们在代码中实现我们迄今为止看到的所有内容。与 BPE 和 WordPiece 一样,这不是 Unigram 算法的有效实现(恰恰相反),但它应该可以帮助你更好地理解它。 +现在让我们在代码中实现上面看到的所内容。与 BPE 和 WordPiece 一样,这不是 Unigram 算法的高效实现(恰恰相反,这套代码的效率非常低),但它应该可以帮助你更好地理解它。 -我们将使用与之前相同的语料库作为示例: +我们将使用与之前相同的语料库作为示例: ```python corpus = [ @@ -164,7 +166,7 @@ corpus = [ ] ``` -这一次,我们将使用 `xlnet-base-cased` 作为我们的模型: +这次,我们将使用 `xlnet-base-cased` 作为我们的模型: ```python from transformers import AutoTokenizer @@ -172,7 +174,7 @@ from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("xlnet-base-cased") ``` -与 BPE 和 WordPiece 一样,我们首先计算语料库中每个单词的出现次数: +与 BPE 和 WordPiece 一样,我们首先计算语料库中每个单词的出现次数: ```python from collections import defaultdict @@ -187,7 +189,7 @@ for text in corpus: word_freqs ``` -然后,我们需要将我们的词汇表初始化为大于我们最终想要的词汇量。我们必须包含所有基本字符(否则我们将无法标记每个单词),但对于较大的子字符串,我们将只保留最常见的字符,因此我们按频率对它们进行排序: +然后,我们需要将我们的词汇表初始化为大于我们最终想要的词汇量。我们必须包含所有基本的单个字符(否则我们将无法对每个单词赋予一个 token ),但对于较大的子字符串,我们将只保留最常见的字符,因此我们按出现频率对它们进行排序: ```python char_freqs = defaultdict(int) @@ -195,11 +197,11 @@ 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 + # 循环遍历长度至少为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] ``` @@ -208,7 +210,7 @@ sorted_subwords[:10] [('▁t', 7), ('is', 5), ('er', 5), ('▁a', 5), ('▁to', 4), ('to', 4), ('en', 4), ('▁T', 3), ('▁Th', 3), ('▁Thi', 3)] ``` -我们用最优的子词对字符进行分组,以获得大小为 300 的初始词汇表: +我们用最优的子词对字符进行分组,以获得大小为 300 的初始词汇表: ```python token_freqs = list(char_freqs.items()) + sorted_subwords[: 300 - len(char_freqs)] @@ -217,11 +219,11 @@ token_freqs = {token: freq for token, freq in token_freqs} -💡 SentencePiece 使用一种称为增强后缀数组(ESA)的更高效算法来创建初始词汇表。 +💡 SentencePiece 使用一种名为增强后缀数组(ESA)的更高效的算法来创建初始词汇表。 -接下来,我们计算所有频率的总和,将频率转换为概率。对于我们的模型,我们将存储概率的对数,因为添加对数比乘以小数在数值上更稳定,这将简化模型损失的计算: +接下来,我们需要计算所有频率的总和,将频率转化为概率。在我们的模型中,我们将存储概率的对数,因为相较于小数相乘,对数相加在数值上更稳定,而且这将简化模型损失的计算: ```python from math import log @@ -230,11 +232,11 @@ total_sum = sum([freq for token, freq in token_freqs.items()]) model = {token: -log(freq / total_sum) for token, freq in token_freqs.items()} ``` -N现在主要功能是使用 Viterbi 算法标记单词的功能。正如我们之前看到的,该算法计算单词的每个子串的最佳分段,我们将其存储在名为 `best_segmentations` 的变量中。我们将在单词的每个位置(从 0 到其总长度)存储一个字典,有两个键:最佳分割中最后一个标记的开始索引,以及最佳分割的分数。使用最后一个标记的开始索引,一旦列表完全填充,我们将能够检索完整的分段。 +现在,主函数是使用 Viterbi 算法对单词进行分词。像我们之前看到的那样,这个算法会计算出每个词的最好的分割方式,我们把这个结果保存在一个叫做 `best_segmentations` 的变量里。我们会为词的每一个位置(从 0 开始,一直到词的总长度)都保存一个字典,字典里有两个键:最好的分割中最后一个词的起始位置,以及最好的分割的得分。有了最后一个词的起始位置,当我们把整个列表都填满后,我们就能找到完整的分割方式。 -填充列表只需两个循环:主循环遍历每个起始位置,第二个循环尝试从该起始位置开始的所有子字符串。如果子串在词汇表中,我们有一个新的词分段,直到该结束位置,我们将其与 `best_segmentations` 相比较。 +我们只需要两个循环就可以填充这个列表:一个主循环用来遍历每个可能的开始位置,第二个循环则试着找出所有以这个开始位置开始的子串。如果这个子串在我们的词表里,那么我们就找到了一个新的分词方式,这个分词方式会在当前位置结束。然后,我们会把这个新的分词方式和 `best_segmentations` 里的内容进行比较。 -一旦主循环完成,我们就从结尾开始,从一个开始位置跳到下一个,记录我们前进的标记,直到我们到达单词的开头: +当主循环结束后,我们就从词的最后一个位置开始,然后一步步往前跳,跳过的每一步,我们都会记录下来,直到我们回到词的开头。 ```python def encode_word(word, model): @@ -242,13 +244,13 @@ def encode_word(word, model): {"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_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 + # 如果我们发现以 end_idx 结尾的更好分段,我们会更新 if ( best_segmentations[end_idx]["score"] is None or best_segmentations[end_idx]["score"] > score @@ -257,7 +259,7 @@ def encode_word(word, model): segmentation = best_segmentations[-1] if segmentation["score"] is None: - # We did not find a tokenization of the word -> unknown + # 我们没有找到单词的 tokens -> unknown return [""], None score = segmentation["score"] @@ -273,7 +275,7 @@ def encode_word(word, model): return tokens, score ``` -我们已经可以在一些词上尝试我们的初始模型: +我们已经可以在一些词上尝试我们的初始模型: ```python print(encode_word("Hopefully", model)) @@ -285,7 +287,7 @@ print(encode_word("This", model)) (['This'], 6.288267030694535) ``` -现在很容易计算模型在语料库上的损失! +现在,计算语料库上的分词损失就很简单了! ```python def compute_loss(model): @@ -296,7 +298,7 @@ def compute_loss(model): return loss ``` -我们可以检查它是否适用于我们拥有的模型: +我们可以检查一下我们的模型是否有效: ```python compute_loss(model) @@ -306,7 +308,7 @@ compute_loss(model) 413.10377642940875 ``` -计算每个标记的分数也不是很难;我们只需要计算通过删除每个标记获得的模型的损失: +计算每个词的分数也并非难事;我们只需要计算通过删除每个词得到的模型的损失: ```python import copy @@ -316,7 +318,7 @@ def compute_scores(model): scores = {} model_loss = compute_loss(model) for token, score in model.items(): - # We always keep tokens of length 1 + # 我们将保留长度为 1 的 tokens if len(token) == 1: continue model_without_token = copy.deepcopy(model) @@ -325,7 +327,7 @@ def compute_scores(model): return scores ``` -我们可以在给定的标记上尝试: +我们可以试试看对于给定的词是否有效: ```python scores = compute_scores(model) @@ -333,7 +335,7 @@ print(scores["ll"]) print(scores["his"]) ``` -自从 `"ll"` 用于标记化 `"Hopefully"`, 删除它可能会让我们使用标记 `"l"` 两次相反,我们预计它将产生正损失。 `"his"` 仅在单词`"This"` 内使用,它被标记为自身,所以我们期望它的损失为零。结果如下: +因为 `"ll"` 这个子词在 `"Hopefully"` 这个词的分词中被使用了,如果我们把它删掉,我们可能会需要用两个 `"l"` 来代替,所以我们预计它会导致损失值增加。而 `"his"` 这个词只在 `"This"` 这个词里面被使用,而且 `"This"` 是作为一个完整的词被分割的,所以我们预计删除它的损失值变化会是零。下面就是实验结果: ```python out 6.376412403623874 @@ -342,18 +344,18 @@ print(scores["his"]) -💡 这种方法非常低效,因此 SentencePiece 使用了没有标记 X 的模型损失的近似值:它不是从头开始,而是通过其在剩余词汇表中的分段替换标记 X。这样,所有分数可以与模型损失同时计算。 +💡 这种方式效率非常低,所以 SentencePiece 使用了一种估算方法来计算如果没有 X token,模型的损失会是多少:它不是重新开始,而是只是用剩下的词表里 X token 的分词方式来替代它。这样,所有的得分都能在和模型损失一起的同时计算出来。 -完成所有这些后,我们需要做的最后一件事是将模型使用的特殊标记添加到词汇表中,然后循环直到我们从词汇表中修剪了足够的标记以达到我们想要的大小: +至此,我们需要做的最后一件事就是将模型使用的特殊 tokens 添加到词汇表中,然后循环直到我们从词汇表中剪除足够多的 tokens 以达到我们期望的规模: ```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. + # 删除分数最低的percent_to_remov tokens 。 for i in range(int(len(model) * percent_to_remove)): _ = token_freqs.pop(sorted_scores[i][0]) @@ -361,7 +363,7 @@ while len(model) > 100: model = {token: -log(freq / total_sum) for token, freq in token_freqs.items()} ``` -然后,为了标记一些文本,我们只需要应用预标记化,然后使用我们的 `encode_word()` 函数: +然后,要对某些文本进行 tokenization,我们只需进行预分词然后使用我们的 `encode_word()` 函数: ```python def tokenize(text, model): @@ -378,4 +380,4 @@ tokenize("This is the Hugging Face course.", model) ['▁This', '▁is', '▁the', '▁Hugging', '▁Face', '▁', 'c', 'ou', 'r', 's', 'e', '.'] ``` -Unigram 就是这样!希望现在你感觉自己是标记器所有方面的专家。在下一节中,我们将深入研究 🤗 Tokenizers 库的构建块,并向您展示如何使用它们来构建您自己的标记器。 +至此 Unigram 的介绍完毕!期望此刻你已感觉自身如同领域的专家一般。在下一节中,我们将深入探讨🤗Tokenizers 库的基本构造模块,并展示如何使用它们构建自己的 tokenizer \ No newline at end of file diff --git a/chapters/zh-CN/chapter6/8.mdx b/chapters/zh-CN/chapter6/8.mdx index 73e07c7fa..2ff99a64b 100644 --- a/chapters/zh-CN/chapter6/8.mdx +++ b/chapters/zh-CN/chapter6/8.mdx @@ -1,4 +1,4 @@ -# 逐块地构建标记器 [[逐块地构建标记器]] +# 模块化构建 tokenizer [[模块化构建 tokenizer ]] -正如我们在前几节中看到的,标记化包括几个步骤: +正如我们在前几节中看到的,tokenization 包括几个步骤: -- 规范化(任何认为必要的文本清理,例如删除空格或重音符号、Unicode 规范化等) -- 预标记化(将输入拆分为单词) -- 通过模型处理输入(使用预先拆分的词来生成一系列标记) -- 后处理(添加标记器的特殊标记,生成注意力掩码和标记类型 ID) +- 标准化(任何认为必要的文本清理,例如删除空格或重音符号、Unicode 规范化等) +- 预分词(将输入拆分为单词) +- 通过模型处理输入(使用预先拆分的词来生成一系列 tokens ) +- 后处理(添加 tokenizer 的特殊 tokens 生成注意力掩码和 token 类型 ID) -提醒一下,这里再看一下整个过程 +作为复习,这里再看一遍整个过程:
The tokenization pipeline.
-🤗 Tokenizers 库旨在为每个步骤提供多个选项,您可以将它们混合和匹配在一起。在本节中,我们将看到如何从头开始构建标记器,而不是像我们[第二节 2](/course/chapter6/2)那样从旧的标记器训练新的标记器.然后,您将能够构建您能想到的任何类型的标记器! +🤗 Tokenizers 库旨在为每个步骤提供多个选项,你可以任意搭配这些选项。在这一节中,我们将看到如何从零开始构建 tokenizer,而不是像我们在 [第二节](/course/chapter6/2) 中那样从旧的 tokenizer 训练新的 tokenizer 然后,你将能够构建任何你能想到的类型的 tokenizer -更准确地说,该库是围绕一个中央“Tokenizer”类构建的,构建这个类的每一部分可以在子模块的列表中重新组合: +更精确地说,这个库围绕一个中心的 `Tokenizer` 类,实现了组成 `Tokenizer` 的各种子模块: -- `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))。 +- `normalizers` 包含所有可能使用的 `Normalizer(标准化)` 模块(完整列表 [在这里](https://huggingface.co/docs/tokenizers/api/normalizers) )。 +- `pre_tokenizesr` 包含所有可能使用的 `PreTokenizer(预处理)` 模块(完整列表 [在这里](https://huggi ngface.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` ,可以用来解码 tokenization 后的输出(完整列表 [在这里](https://huggingface.co/docs/tokenizers/components#decoders) )。 -您可以[在这里](https://huggingface.co/docs/tokenizers/components)找到完整的模块列表。 +你可以 [在这里](https://huggingface.co/docs/tokenizers/components) 找到完整的模块列表。 ## 获取语​​料库 [[获取语​​料库]] -为了训练我们的新标记器,我们将使用一个小的文本语料库(因此示例运行得很快)。获取语​​料库的步骤与我们在[在这章的开始]((/course/chapter6/2)那一小节,但这次我们将使用[WikiText-2](https://huggingface.co/datasets/wikitext)数据集: +为了训练新的 tokenizer 我们将使用一小部分文本作为语料库(这样运行得更快)。获取语​​料库的步骤与我们在 [在这章的开头](/course/chapter6/2) 采取的步骤类似,但这次我们将使用 [WikiText-2](https://huggingface.co/datasets/wikitext) 数据集: ```python from datasets import load_dataset @@ -50,10 +50,9 @@ def get_training_corpus(): for i in range(0, len(dataset), 1000): yield dataset[i : i + 1000]["text"] ``` +`get_training_corpus()` 函数是一个生成器,每次调用的时候将产生 1,000 个文本,我们将用它来训练 tokenizer 。 -**get_training_corpus()** 函数是一个生成器,每次调用的时候将产生 1,000 个文本,我们将用它来训练标记器。 - -🤗 Tokenizers 也可以直接在文本文件上进行训练。以下是我们如何生成一个文本文件,其中包含我们可以在本地使用的来自 WikiText-2 的所有文本/输入: +🤗 Tokenizers 也可以直接在文本文件上进行训练。以下是我们生成一个包含 WikiText-2 所有文本的代码,这样我们就可以在本地离线使用: ```python with open("wikitext-2.txt", "w", encoding="utf-8") as f: @@ -61,13 +60,13 @@ with open("wikitext-2.txt", "w", encoding="utf-8") as f: f.write(dataset[i]["text"] + "\n") ``` -接下来,我们将向您展示如何逐块构建您自己的 BERT、GPT-2 和 XLNet 标记器。这将为我们提供三个主要标记化算法的示例:WordPiece、BPE 和 Unigram。让我们从 BERT 开始吧! +接下来,我们将展示如何模块化地构建你自己的 BERT、GPT-2 和 XLNet tokenizer 这将包含主要的分词算法:WordPiece、BPE 和 Unigram 的例子。让我们从 BERT 开始吧! -## 从头开始构建 WordPiece 标记器 [[从头开始构建 WordPiece 标记器]] +## 从头开始构建 WordPiece tokenizer [[从头开始构建 WordPiece tokenizer ]] -要使用 🤗 Tokenizers 库构建标记器,我们首先使用**model**实例化一个 **Tokenizer** 对象与 ,然后将 **normalizer** , **pre_tokenizer** , **post_processor** , 和 **decoder** 属性设置成我们想要的值。 +要用🤗 Tokenizers 库构建一个 tokenizer 我们首先实例化一个带有 `model` 的 `Tokenizer` 对象,然后将其 `normalizer` , `pre_tokenizer` , `post_processor` 和 `decoder` 属性设置为我们想要的值。 -对于这个例子,我们将创建一个 **Tokenizer** 使用 WordPiece 模型: +以这个例子来说,我们将创建一个使用 WordPiece 模型的 `Tokenizer` : ```python from tokenizers import ( @@ -83,15 +82,15 @@ from tokenizers import ( tokenizer = Tokenizer(models.WordPiece(unk_token="[UNK]")) ``` -我们必须指定 **unk_token** 这样模型才知道当它遇到以前没有见过的字符时要返回什么。我们可以在此处设置的其他参数包括我们模型的**vocab(字典)**(我们将训练模型,所以我们不需要设置它)和 **max_input_chars_per_word** 即每个单词的最大长度(比传递的值长的单词将被拆分) +我们必须指定 `unk_token` ,这样当模型遇到它从未见过的字符时,它就会返回 `unk_token`。我们在这里可以设置的其他参数包括已有的 `vocab(词汇表)` (我们要重新训练模型,所以我们不需要设置这个)和 `max_input_chars_per_word` ,它指定了每个词的最大长度(比 `max_input_chars_per_word` 长的词将被拆分)。 -标记化的第一步是规范化,所以让我们从它开始。 由于 BERT 被广泛使用,所以有一个可以使用的 `BertNormalizer`,我们可以为 BERT 设置经典的选项:`lowercase(小写)` 和 `strip_accents(去除音调)`,不言自明; `clean_text` 删除所有控制字符并将重复的空格替换为一个; 和 `handle_chinese_chars`,在汉字周围放置空格。 要实现 `bert-base-uncased` ,我们可以这样设置这个规范器: +tokenization 的第一步是标准化,所以我们从这里开始。由于 BERT 被广泛使用,所以我们可以使用 `BertNormalizer` ,我们可以为 BERT 设置经典参数: `lowercase(小写)` 和 `strip_accents(去除重音的字符)` , `clean_text` 用于删除所有控制字符并将重复的空格替换为一个空格;以及 `handle_chinese_chars` ,它将在中文字符周围添加空格。要复现 `bert-base-uncased` tokenizer 我们可以这样设置 `normalizer` : ```python tokenizer.normalizer = normalizers.BertNormalizer(lowercase=True) ``` -然而,一般来说,在构建新的标记器时,您可以使用已经在 🤗 Tokenizers库中实现的非常方便的normalizer——所以让我们看看如何手动创建 BERT normalizer。 该库提供了一个“Lowercase(小写)”的normalizer和一个“StripAccents”的normalizer,您可以使用“序列”组合多个normalizer: +然而,通常来说,当你构建一个新的 tokenizer 时,也需要同步构建一个新的 `normalizer` —— 所以我们来看看如何手动创建 `BERT normalizer` 。🤗 Tokenizers 库提供了一个 `Lowercase normalizer` 和一个 `StripAccents normalizer` ,并且你可以使用 Sequence 来组合多个 normalizer。 ```python tokenizer.normalizer = normalizers.Sequence( @@ -99,9 +98,9 @@ tokenizer.normalizer = normalizers.Sequence( ) ``` -我们也在使用 **NFD** Unicode normalizer,否则 **StripAccents** normalizer 无法正确识别带重音的字符,因此没办法删除它们。 +我们还使用了一个 `NFD Unicode normalizer` ,否则,否则 `StripAccents normalizer` 将因为无法正确识别带有重音的字符,从而没办法去除重音。 -正如我们之前看到的,我们可以使用 **normalize** 的 **normalize_str()** 方法查看它对给定文本的影响: +正如我们之前看到的,我们可以使用 `normalizer` 的 `normalize_str()` 方法来对它进行测试: ```python print(tokenizer.normalizer.normalize_str("Héllò hôw are ü?")) @@ -113,12 +112,13 @@ hello how are u? -**更进一步**如果您在包含 unicode 字符的字符串上测试先前normalizers的两个版本,您肯定会注意到这两个normalizers并不完全等效。 -为了不过度使用 `normalizers.Sequence` 使版本过于复杂,我们没有包含当 `clean_text` 参数设置为 `True` 时 `BertNormalizer` 需要的正则表达式替换 - 这是默认行为。 但不要担心:通过在normalizer序列中添加两个 `normalizers.Replace` 可以在不使用方便的 `BertNormalizer` 的情况下获得完全相同的规范化。 +**更进一步**如果你在包含 unicode 字符的字符串上测试先前 normalizers 的两个版本,你肯定会注意到这两个 normalizers 并不完全等效。 + +为了避免 `normalizers.Sequence` 过于复杂,我们的实现没有包含当 `clean_text` 参数设置为 `True` 时 `BertNormalizer` 需要的正则表达式替换 —— 而这是 `BertNormalizer` 默认会实现的。但不要担心:通过在 normalizer 序列中添加两个 `normalizers.Replace` 可以在不使用方便的 `BertNormalizer` 的情况下获得完全相同的标准化。 -接下来是预标记步骤。 同样,我们可以使用一个预构建的“BertPreTokenizer”: +下一步是预分词。同样,我们可以使用预构建的 `BertPreTokenizer` : ```python tokenizer.pre_tokenizer = pre_tokenizers.BertPreTokenizer() @@ -130,7 +130,7 @@ tokenizer.pre_tokenizer = pre_tokenizers.BertPreTokenizer() tokenizer.pre_tokenizer = pre_tokenizers.Whitespace() ``` -请注意,`Whitespace` 预标记器会在空格和所有非字母、数字或下划线字符的字符上进行拆分,因此在本次的例子中上会根据空格和标点符号进行拆分: +注意, `Whitespace` 会使用空格和所有不是字母、数字或下划线的字符进行分割,因此在本次的例子中上会根据空格和标点符号进行分割: ```python tokenizer.pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.") @@ -141,7 +141,7 @@ tokenizer.pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.") ('-', (17, 18)), ('tokenizer', (18, 27)), ('.', (27, 28))] ``` -如果您只想在空白处进行拆分,则应使用 **WhitespaceSplit** 代替预标记器: +如果你只想使用空格进行分割,则应该使用 `WhitespaceSplit` : ```python pre_tokenizer = pre_tokenizers.WhitespaceSplit() @@ -152,7 +152,7 @@ pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.") [("Let's", (0, 5)), ('test', (6, 10)), ('my', (11, 13)), ('pre-tokenizer.', (14, 28))] ``` -像normalizers一样,您可以使用 **Sequence** 组成几个预标记器: +就像 normalizer 一样,你可以使用 `Sequence` 来组合几个预分词的步骤: ```python pre_tokenizer = pre_tokenizers.Sequence( @@ -166,29 +166,29 @@ pre_tokenizer.pre_tokenize_str("Let's test my pre-tokenizer.") ('-', (17, 18)), ('tokenizer', (18, 27)), ('.', (27, 28))] ``` -标记化管道的下一步是输入给模型。我们已经在初始化中指定了我们的模型,但我们仍然需要训练它,这将需要一个 **WordPieceTrainer** .在 🤗 Tokenizers 中实例化训练器时要记住的主要事情是,您需要将您打算使用的所有特殊标记传递给它 - 否则它不会将它们添加到词汇表中,因为它们不在训练语料库中: +tokenization 流程的下一步是将输入数据传递给模型。我们已经在初始化时指定了我们的模型,但是我们还需要对其进行训练,这就需要一个 `WordPieceTrainer` 。在实例化一个🤗 Tokenizers 中的 `Trainer` 时,一件很重要的事情是,你需要将你打算使用的所有特殊 tokens 都传递给它——否则,由于它们不在训练语料库中,`Trainer` 就不会将它们添加到词汇表中: ```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** (如果我们想使用与 **##**指代存在与字词相同的前缀 )。 +除了指定 `vocab_size` 和 `special_tokens` ,我们还可以设置 `min_frequency` (一个 tokens 必须达到的最小的出现的次数才能被包含在词汇表中)或更改 `continuing_subword_prefix` (如果我们想使用其他的字符来替代 `##` )。 -要使用我们之前定义的迭代器训练我们的模型,我们只需要执行以下命令: +我们只需要执行以下命令就可以使用我们之前定义的迭代器训练我们的模型: ```python tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) ``` -我们还可以使用文本文件来训练我们的标记器,它看起来像这样(我们需要先初始化一个空的 **WordPiece** ): +我们还可以使用本地的文本文件来训练我们的 tokenizer 它看起来像这样(我们需要先使用 `WordPiece` 初始化一个空的模型): ```python tokenizer.model = models.WordPiece(unk_token="[UNK]") tokenizer.train(["wikitext-2.txt"], trainer=trainer) ``` -在这两种情况下,我们都可以通过调用文本来测试标记器 **encode()** 方法: +在这两种情况下,我们都可以通过调用 `encode()` 方法来测试 tokenizer ```python encoding = tokenizer.encode("Let's test this tokenizer.") @@ -199,9 +199,9 @@ print(encoding.tokens) ['let', "'", 's', 'test', 'this', 'tok', '##eni', '##zer', '.'] ``` -这个 **encoding** 获得的是一个 **Encoding**对象 ,它的属性中包含标记器的所有必要输出: **ids** , **type_ids** , **tokens** , **offsets** , **attention_mask** , **special_tokens_mask** , 和 **overflowing** . +所得到的 `encoding` 是一个 `Encoding` 对象,它包含 tokenizer 的所有必要属性: `ids` 、 `type_ids` 、 `tokens` 、 `offsets` 、 `attention_mask` 、 `special_tokens_mask` 和 `overflowing` 。 -标记化管道的最后一步是后处理。我们需要添加 **[CLS]** 开头的标记和 **[SEP]** 标记在末尾(或在每个句子之后,如果我们有一对句子)。我们将使用一个 **TemplateProcessor** 为此,但首先我们需要知道 **[CLS]** 和 **[SEP]** 在词汇表中的ID: +tokenizer 管道的最后一步是后处理。我们需要在开头添加 `[CLS]` token,然后在结束时(或在每句话后,如果我们有一对句子)添加 `[SEP]` token。我们将使用 `TemplateProcessor` 来完成这个任务,但首先我们需要知道词汇表中 `[CLS]` 和 `[SEP]` tokens 的 ID: ```python cls_token_id = tokenizer.token_to_id("[CLS]") @@ -213,9 +213,9 @@ print(cls_token_id, sep_token_id) (2, 3) ``` -为了给 **TemplateProcessor** 编写模板,我们必须指定如何处理单个句子和一对句子。对于两者,我们都编写了我们想要使用的特殊标记;第一个(或单个)句子表示为 **$A** ,而第二个句子(如果对一对进行编码)表示为 **$B** .对于这些特殊标记和句子,我们还需要使用在冒号后指定相应的标记类型 ID。 +编写 `TemplateProcessor` 的模板时,我们必须指定如何处理单个句子和一对句子。对于这两者,我们写下我们想使用的特殊 tokens 第一句(或单句)用 `$A` 表示,而第二句(如果需要编码一对句子)用 `$B` 表示。对于这些(特殊 tokens 和句子),我们还需要在冒号后指定相应的 token 类型 ID。 -因此经典的 BERT 模板定义如下: +因此,经典的 BERT 模板定义如下: ```python tokenizer.post_processor = processors.TemplateProcessing( @@ -225,9 +225,9 @@ tokenizer.post_processor = processors.TemplateProcessing( ) ``` -请注意,我们需要传递特殊标记的 ID,以便标记器可以正确地将特殊标记转换为它们的 ID。 +请注意,我们需要传递特殊 tokens 的 ID,这样 tokenizer 才能正确地将它们转换为它们的 ID。 -添加后,我们之前的示例将输出出: +添加之后,我们再次对之前的例子进行 tokenization: ```python encoding = tokenizer.encode("Let's test this tokenizer.") @@ -238,7 +238,8 @@ print(encoding.tokens) ['[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) @@ -250,13 +251,13 @@ print(encoding.type_ids) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1] ``` -我们几乎从头开始构建了这个标记器——但是还有最后一步是指定一个解码器: +我们几乎从头开始构建了这个 tokenizer ——但是还有最后一步:指定一个解码器: ```python tokenizer.decoder = decoders.WordPiece(prefix="##") ``` -让我们测试一下我们之前的 **encoding** : +让我们在之前的 `encoding` 上测试一下它: ```python tokenizer.decode(encoding.ids) @@ -266,28 +267,29 @@ tokenizer.decode(encoding.ids) "let's test this tokenizer... on a pair of sentences." ``` -很好!我们可以将标记器保存在一个 JSON 文件中,如下所示: +很好!我们可以将 tokenizer 保存在一个 JSON 文件中,如下所示: ```python tokenizer.save("tokenizer.json") ``` -然后我们可以使用**from_file()** 方法从该文件里重新加载 **Tokenizer** 对象: +然后,我们可以在一个 `Tokenizer` 对象中使用 `from_file()` 方法重新加载该文件: ```python new_tokenizer = Tokenizer.from_file("tokenizer.json") ``` -要在 🤗 Transformers 中使用这个标记器,我们必须将它包裹在一个 **PreTrainedTokenizerFast** 类中。我们可以使用泛型类,或者,如果我们的标记器对应于现有模型,则使用该类(例如这里的 **BertTokenizerFast** )。如果您应用本课来构建全新的标记器,则必须使用第一个选项。 +要在🤗 Transformers 中使用这个 tokenizer 我们需要将它封装在一个 `PreTrainedTokenizerFast` 类中。我们可以使用通用类(PreTrainedTokenizerFast),或者,如果我们的 tokenizer 对应于一个现有的模型,则可以使用该类(例如这里的 `BertTokenizerFast` )。如果你使用这个课程来构建一个全新的 tokenizer 并且没有一个现有的模型可以使用,就必须需要使用通用类。 + +要将构建的 tokenizer 封装在 `PreTrainedTokenizerFast` 类中,我们可以将我们构建的 tokenizer 作为 `tokenizer_object` 传入,或者将我们保存的 tokenizer 文件作为 `tokenizer_file` 传入。要记住的关键一点是,我们需要手动设置所有的特殊 tokens,因为这个类不能从 `tokenizer` 对象推断出哪个符号是掩码符号, `[CLS]` 符号等: -要将标记器包装在 `PreTrainedTokenizerFast` 类中,我们可以将我们构建的标记器作为`tokenizer_object` 传递,或者将我们保存为`tokenizer_file` 的标记器文件传递。 要记住的关键是我们必须手动设置所有特殊标记,因为该类无法从 `tokenizer` 对象推断出哪个标记是掩码标记、`[CLS]` 标记等: ```python from transformers import PreTrainedTokenizerFast wrapped_tokenizer = PreTrainedTokenizerFast( tokenizer_object=tokenizer, - # tokenizer_file="tokenizer.json", # You can load from the tokenizer file, alternatively + # tokenizer_file="tokenizer.json", # 也可以从tokenizer文件中加载 unk_token="[UNK]", pad_token="[PAD]", cls_token="[CLS]", @@ -296,7 +298,7 @@ wrapped_tokenizer = PreTrainedTokenizerFast( ) ``` -如果您使用特定的标记器类(例如 **BertTokenizerFast** ),您只需要指定与默认标记不同的特殊标记(此处没有): +如果你使用的是其他的 tokenizer 类(如 `BertTokenizerFast` ),你只需要指定那些与默认值不同的特殊符号(这里没有): ```python from transformers import BertTokenizerFast @@ -304,27 +306,26 @@ from transformers import BertTokenizerFast wrapped_tokenizer = BertTokenizerFast(tokenizer_object=tokenizer) ``` -然后,您可以像使用任何其他 🤗 Transformers 标记器一样使用此标记器。你可以用 **save_pretrained()** 方法,或使用 **push_to_hub()** 方法。 +然后,你就可以像使用其他的🤗 Transformers tokenizer 一样使用这个 tokenizer 了。你可以使用 `save_pretrained()` 方法来保存它,或者使用 `push_to_hub()` 方法将它上传到 Hub。 -现在我们已经了解了如何构建 WordPiece 标记器,让我们对 BPE 标记器进行同样的操作。因为您已经知道了所有步骤,所以我们会进行地更快一点,并且只突出展示两者不一样的地方。 +既然我们已经看到了如何构建一个 WordPiece tokenizer。那么让我们也尝试构建 BPE tokenizer。这次我们会快一些,因为你已经知道所有的步骤,我们主要强调其中的区别。 -## 从头开始构建 BPE 标记器 [[从头开始构建 BPE 标记器]] +## 从头开始构建 BPE tokenizer [[从头开始构建 BPE tokenizer ]] -现在让我们构建一个 GPT-2 标记器。与 BERT 标记器一样,我们首先使用 **Tokenizer** 初始化一个BPE 模型: +现在让我们构建一个 GPT-2 tokenizer,与 BERT tokenizer 一样,我们首先通过 BPE model 初始化一个 `Tokenizer` : ```python tokenizer = Tokenizer(models.BPE()) ``` -和 BERT 一样,如果我们有一个词汇表,我们可以用一个词汇表来初始化这个模型(在这种情况下,我们需要传递 `vocab` 和 `merges`),但是由于我们将从头开始训练,所以我们不需要这样去做。 我们也不需要指定“unk_token”,因为 GPT-2 使用的字节级 BPE,不需要“unk_token”。 +同样,类似于 BERT,如果我们已经有一个词汇表,我们也可以使用这个词汇表来初始化 GPT 模型(在这种情况下,我们需要传入 `vocab` 和 `merges` 参数),但是因为我们将从头开始训练,所以我们不需要做这个。我们也不需要指定 `unk_token` ,因为 GPT-2 使用字节级 BPE,这不需要它。 -GPT-2 不使用归一化器,因此我们跳过该步骤并直接进入预标记化: +GPT-2 不使用 `normalizer` ,因此我们跳过该步骤并直接进入预分词: ```python tokenizer.pre_tokenizer = pre_tokenizers.ByteLevel(add_prefix_space=False) ``` - -我们在此处添加到 `ByteLevel` 的选项是不在句子开头添加空格(默认为ture)。 我们可以看一下使用这个标记器对之前示例文本的预标记: +我们在这里给 `ByteLevel` 添加的选项的含义是不在句子的开始添加空格(默认为 ture)。我们可以看一下之前的示例文本经过预分词后的结果: ```python tokenizer.pre_tokenizer.pre_tokenize_str("Let's test pre-tokenization!") @@ -335,23 +336,23 @@ tokenizer.pre_tokenizer.pre_tokenize_str("Let's test pre-tokenization!") ('tokenization', (15, 27)), ('!', (27, 28))] ``` -接下来是需要训练的模型。对于 GPT-2,唯一的特殊标记是文本结束标记: +接下来是需要训练的模型。对于 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` 设置它。 +就像 `WordPieceTrainer` 一样,除了 `vocab_size` 和 `special_tokens` ,我们也可以设置 `min_frequency` ,或者如果我们需要添加一个词尾后缀(如 `` ),我们可以用 `end_of_word_suffix` 设置它。 -这个标记器也可以在文本文件上训练: +这个 tokenizer 也可以在本地的文本文件上训练: ```python tokenizer.model = models.BPE() tokenizer.train(["wikitext-2.txt"], trainer=trainer) ``` -让我们看一下示例文本的标记化后的结果: +让我们看一下示例文本经过 tokenization 后的结果: ```python encoding = tokenizer.encode("Let's test this tokenizer.") @@ -362,13 +363,13 @@ print(encoding.tokens) ['L', 'et', "'", 's', 'Ġtest', 'Ġthis', 'Ġto', 'ken', 'izer', '.'] ``` -我们对 GPT-2 标记器添加字节级后处理,如下所示: +我们对 GPT-2 tokenizer 添加字节级后处理,如下所示: ```python tokenizer.post_processor = processors.ByteLevel(trim_offsets=False) ``` -`trim_offsets = False` 选项指示我们应该保留以 'Ġ' 开头的标记的偏移量:这样偏移量的开头将指向单词之前的空格,而不是第一个单词的字符(因为空格在技术上是标记的一部分)。 让我们看看我们刚刚编码的文本的结果,其中 `'Ġtest'` 是索引第 4 处的标记: +`trim_offsets = False` 这个选项告诉 post-processor,我们应该让那些以‘Ġ’开始的 tokens 的偏移量保持不变:这样,偏移量起始的索引将指向单词前的空格,而不是单词的第一个字符(因为空格在技术上是 token 的一部分)。让我们看一下我们编码示例文本的结果,其中 `'Ġtest'` 是索引 4 的 token ```python sentence = "Let's test this tokenizer." @@ -387,7 +388,7 @@ sentence[start:end] tokenizer.decoder = decoders.ByteLevel() ``` -我们可以仔细检查它是否正常工作: +我们可以再次检查它是否工作正常: ```python tokenizer.decode(encoding.ids) @@ -397,7 +398,7 @@ tokenizer.decode(encoding.ids) "Let's test this tokenizer." ``` -很好!现在我们完成了,我们可以像以前一样保存标记器,并将它包装在一个 **PreTrainedTokenizerFast** 或者 **GPT2TokenizerFast** 如果我们想在 🤗 Transformers中使用它: +太好了!现在我们完成了,我们可以像之前一样保存 tokenizer,并且如果我们想在🤗 Transformers 中使用它,可以将它封装在 `PreTrainedTokenizerFast` 类或者 `GPT2TokenizerFast` 类中: ```python from transformers import PreTrainedTokenizerFast @@ -417,11 +418,11 @@ from transformers import GPT2TokenizerFast wrapped_tokenizer = GPT2TokenizerFast(tokenizer_object=tokenizer) ``` -作为最后一个示例,我们将向您展示如何从头开始构建 Unigram 标记器。 +作为最后一个示例,我们将向你展示如何从零开始构建 Unigram tokenizer -## 从头开始构建 Unigram 标记器 [[从头开始构建 Unigram 标记器]] +## 从零开始构建 Unigram tokenizer [[从零开始构建 Unigram tokenizer ]] -现在让我们构建一个 XLNet 标记器。与之前的标记器一样,我们首先使用 Unigram 模型初始化一个 **Tokenizer** : +现在让我们构建一个 XLNet tokenizer 与之前的 tokenizer 一样,我们首先使用 Unigram model 初始化一个 `Tokenizer` : ```python tokenizer = Tokenizer(models.Unigram()) @@ -429,7 +430,7 @@ tokenizer = Tokenizer(models.Unigram()) 同样,如果我们有词汇表,我们可以用词汇表初始化这个模型。 -对于标准化,XLNet 使用了一些替换的方法(来自 SentencePiece): +在标准化步骤,XLNet 进行了一些替换(来自 SentencePiece 算法): ```python from tokenizers import Regex @@ -445,15 +446,15 @@ tokenizer.normalizer = normalizers.Sequence( ) ``` -这会取代 **“** 和 **”** 和 **”** 以及任何两个或多个空格与单个空格的序列,以及删除文本中的重音以进行标记。 +这会将``''替换为",将任何连续两个或更多的空格替换为一个空格,同时还将去掉待分词文本中的重音符号。 -用于任何 SentencePiece 标记器的预标记器是 `Metaspace`: +任何 SentencePiece tokenizer 使用的预 tokenizer 是 `Metaspace` : ```python tokenizer.pre_tokenizer = pre_tokenizers.Metaspace() ``` -我们可以像以前一样查看示例文本的预标记化: +我们可以像以前一样查看示例文本的预分词: ```python tokenizer.pre_tokenizer.pre_tokenize_str("Let's test the pre-tokenizer!") @@ -463,7 +464,7 @@ tokenizer.pre_tokenizer.pre_tokenize_str("Let's test the pre-tokenizer!") [("▁Let's", (0, 5)), ('▁test', (5, 10)), ('▁the', (10, 14)), ('▁pre-tokenizer!', (14, 29))] ``` -接下来是需要训练的模型。 XLNet 有不少特殊的标记: +接下来是需要训练的模型。XLNet 有不少特殊的 tokens ```python special_tokens = ["", "", "", "", "", "", ""] @@ -473,16 +474,16 @@ trainer = trainers.UnigramTrainer( tokenizer.train_from_iterator(get_training_corpus(), trainer=trainer) ``` -不要忘记`UnigramTrainer` 的一个非常重要的参数是`unk_token`。 我们还可以传递特定于 Unigram 算法的其他参数,例如删除标记的每个步骤的“shrinking_factor(收缩因子)”(默认为 0.75)或指定给定标记的最大长度的“max_piece_length”(默认为 16) . +对于 `UnigramTrainer` 来说,一个非常重要的参数是 `unk_token` 。我们也可以传递一些 Unigram 算法独有的其他参数,例如我们可以设置每个删除 token 时的 `shrinking_factor` (默认为 0.75),或者指定 token 最大长度的 `max_piece_length` (默认为 16)。 -这个标记器也可以在文本文件上训练: +这个 tokenizer 也可以在本地的文本文件上训练: ```python tokenizer.model = models.Unigram() tokenizer.train(["wikitext-2.txt"], trainer=trainer) ``` -让我们看一下示例文本的标记化后的结果: +让我们看一下示例文本的 tokenization 后的结果: ```python encoding = tokenizer.encode("Let's test this tokenizer.") @@ -493,8 +494,7 @@ print(encoding.tokens) ['▁Let', "'", 's', '▁test', '▁this', '▁to', 'ken', 'izer', '.'] ``` -A peculiarity of XLNet is that it puts the `` token at the end of the sentence, with a type ID of 2 (to distinguish it from the other tokens). It's padding on the left, as a result. We can deal with all the special tokens and token type IDs with a template, like for BERT, but first we have to get the IDs of the `` and `` tokens: -XLNet 的一个特点是它将`` 标记放在句子的末尾,类型ID 为2(以将其与其他标记区分开来)。它会将结果填充在左侧。 我们可以使用模板处理所有特殊标记和标记类型 ID,例如 BERT,但首先我们必须获取 `` 和 `` 标记的 ID: +XLNet 的一个特点是它将 `` token 放在句子的末尾,token 类型 ID 为 2(以区别于其他 tokens)。因此,它在左边进行填充。我们可以像对待 BERT 一样,用模板处理所有特殊 tokens 和 tokens 类型 ID,但首先我们需要获取 `` 和 `` tokens 的 ID: ```python cls_token_id = tokenizer.token_to_id("") @@ -516,7 +516,7 @@ tokenizer.post_processor = processors.TemplateProcessing( ) ``` -我们可以通过编码一对句子来测试它的工作原理: +我们可以通过编码一对句子来测试它是否有效: ```python encoding = tokenizer.encode("Let's test this tokenizer...", "on a pair of sentences!") @@ -530,13 +530,13 @@ print(encoding.type_ids) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2] ``` -最后,我们添加一个 **Metaspace** 解码器: +最后,我们添加一个 `Metaspace` 解码器: ```python tokenizer.decoder = decoders.Metaspace() ``` -我们完成了这个标记器! 我们可以像以前一样保存标记器,如果我们想在 🤗 Transformers 中使用它,可以将它包装在 `PreTrainedTokenizerFast` 或 `XLNetTokenizerFast` 中。 使用 `PreTrainedTokenizerFast` 时要注意的一件事是,我们需要告诉🤗 Transformers 库应该在左侧填充特殊标记: +我们完成了这个 tokenizer。我们可以像保存其他 tokenizer 一样保存它。如果我们想在 🤗 Transformers 中使用它,可以将它封装在 `PreTrainedTokenizerFast` 类或 `XLNetTokenizerFast` 类中。使用 `PreTrainedTokenizerFast` 类时需要注意的一点是,除了特殊 tokens 之外,我们还需要告诉🤗 Transformers 库在左边进行填充: ```python from transformers import PreTrainedTokenizerFast @@ -561,4 +561,4 @@ from transformers import XLNetTokenizerFast wrapped_tokenizer = XLNetTokenizerFast(tokenizer_object=tokenizer) ``` -现在您已经了解了如何使用各种构建块来构建现有的标记器,您应该能够使用 🤗 tokenizer库编写您想要的任何标记器,并能够在 🤗 Transformers中使用它。 \ No newline at end of file +现在你已经了解了如何使用各种模块来构建现有的 tokenizer,你应该能够使用 🤗 tokenizer 库编写你想要的任何 tokenizer 并能够在 🤗 Transformers 中使用它。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter6/9.mdx b/chapters/zh-CN/chapter6/9.mdx index cb0c68169..61d507cdc 100644 --- a/chapters/zh-CN/chapter6/9.mdx +++ b/chapters/zh-CN/chapter6/9.mdx @@ -1,16 +1,16 @@ -# 标记器,回顾! [[标记器,回顾!]] +# tokenizer 回顾![[ tokenizer ,回顾!]] -完成这一章,辛苦了! +恭喜你完成了这一章! -在深入研究标记器之后,您应该: +在深入研究 tokenizer 之后,你应该: -- 能够使用旧的标记器作为模板来训练新的标记器 -- 了解如何使用偏移量将标记的位置映射到其原始文本范围 +- 能够使用旧的 tokenizer 作为模板来训练新的 tokenizer +- 了解如何使用偏移量将 tokens 的位置映射到其原始文本范围 - 了解 BPE、WordPiece 和 Unigram 之间的区别 -- 能够混合和匹配 🤗 Tokenizers 库提供的块来构建您自己的标记器 -- 能够在 🤗 Transformers 库中使用该标记器 \ No newline at end of file +- 能够混合使用 🤗 Tokenizers 库提供的块来构建你自己的 tokenizer +- 能够在 🤗 Transformers 库中使用该 tokenizer \ No newline at end of file diff --git a/chapters/zh-CN/chapter7/1.mdx b/chapters/zh-CN/chapter7/1.mdx index 53e35918e..8617c8397 100644 --- a/chapters/zh-CN/chapter7/1.mdx +++ b/chapters/zh-CN/chapter7/1.mdx @@ -2,9 +2,9 @@ # 章节简介 [[章节简介]] -在[第三章](/course/chapter3),您了解了如何微调文本分类的模型。在本章中,我们将处理以下常见的 NLP 任务: +在 [第三章](/course/chapter3) ,你了解了如何微调文本分类模型。在本章中,我们将处理以下常见的 NLP 任务: -- 词元(token)分类 +- Token 分类 - 掩码语言建模(如 BERT) - 文本摘要 - 翻译 @@ -13,21 +13,21 @@ {#if fw === 'pt'} -为此,您需要利用[第三章](/course/chapter3)中学到的 `Trainer` API 和 🤗 Accelerate 库、[第五章](/course/chapter5)中的 🤗 Datasets 库以及[第六章](/course/chapter6)中的 🤗 Tokenizers 库的所有知识。我们同样会将结果上传到模型中心,就像我们在[第四章](/course/chapter4)中所做的那样,所以这确实是融会贯通的一章! +为此,你需要充分利用在 [第三章](/course/chapter3) 中学到的有关 `Trainer` API 和 🤗 Accelerate 库的知识,以及在 [第五章](/course/chapter5) 中学到的 🤗 Datasets 库和 [第六章](/course/chapter6) 中学到的 🤗 Tokenizers 库的知识。我们还会像在 [第四章](/course/chapter4) 中那样将结果上传到 Model Hub,所以这真的是所有所学内容融会贯通的一章! -每个部分都可以独立阅读,并将向您展示如何使用 `Trainer` API 或按照您自己的训练循环训练模型,并采用 🤗 Accelerate 加速。你可以随意跳过任何一部分,专注于您最感兴趣的部分:`Trainer` API 非常适用于微调(fine-tuning)或训练您的模型,且无需担心幕后发生的事情;而采用 `Accelerate` 的训练循环可以让您更轻松地自定义所需的任何结构。 +本章的每个部分都可以独立阅读,它们将向你展示如何使用 `Trainer` API 或自己的训练循环来训练模型,同时使用 🤗 Accelerate 加速。你可以随意跳过其中任意部分,重点关注你最感兴趣的部分: `Trainer` API 非常适合微调(fine-tuning)或训练模型,而无需关注内部的实现细节,而使用 `Accelerate` 的训练循环将使你更容易自定义所需的任何结构。 {:else} -为此,您需要利用[第三章](/course/chapter3)中学到的有关 Keras API、[第五章](/course/chapter5)中的 🤗 Datasets 库以及[第六章](/course/chapter6)中的 🤗 Tokenizers 库的所有知识。我们同样会将结果上传到模型中心,就像我们在[第四章](/course/chapter4)中所做的那样,所以这确实是融会贯通的一章! +为此,你需要充分利用在 [第三章](/course/chapter3) 中学到的有关 `Keras` API、 [第五章](/course/chapter5) 中的 🤗 Datasets 库以及 [第六章](/course/chapter6) 中的 🤗 Tokenizers 库的所有知识。我们还会像在 [第四章](/course/chapter4) 中那样将结果上传到 Model Hub,所以这真的是所有所学内容融会贯通的一章! -每个部分都可以独立阅读。 +本章每个部分都可以独立阅读。 {/if} -如果您按顺序阅读这些部分,您会注意到它们有很多共同的代码和陈述。 重复是有意为之的,让您可以深入(或稍后返回)任何您感兴趣的任务并找到一个完整的工作示例。 +如果你按顺序阅读这些部分,你会注意到各小节在代码和描述上有许多相似之处。这种重复是有意为之的,让你可以随时钻研或对比学习任何感兴趣的任务,并且在每个任务中都可以找到一个完整的可运行示例。 diff --git a/chapters/zh-CN/chapter7/2.mdx b/chapters/zh-CN/chapter7/2.mdx index e9f663157..330606138 100644 --- a/chapters/zh-CN/chapter7/2.mdx +++ b/chapters/zh-CN/chapter7/2.mdx @@ -22,15 +22,14 @@ {/if} -我们将探索的第一个应用是Token分类。这个通用任务包括任何可以表述为“为句子中的词或字分配标签”的问题,例如: - -- **实体命名识别 (NER)**: 找出句子中的实体(如人物、地点或组织)。这可以通过为每个实体或“无实体”指定一个类别的标签。 -- **词性标注 (POS)**: 将句子中的每个单词标记为对应于特定的词性(如名词、动词、形容词等)。 -- **分块(chunking)**: 找到属于同一实体的Token。这个任务(可结合POS或NER)可以任何将一块Token作为制定一个标签(通常是B -),另一个标签(通常I -)表示Token是否是同一块,和第三个标签(通常是O)表示Token不属于任何块。也就是标出句子中的短语块,例如名词短语(NP),动词短语(VP)等。 +我们将首先探讨的应用是 Token 分类。这个通用任务涵盖了所有可以表述为“给句子中的词或字贴上标签”的问题,例如: +- **实体命名识别 (NER)**:找出句子中的实体(如人物、地点或组织)。这可以通过为每个实体指定一个类别的标签,如果没有实体则会输出无实体的标签。 +- **词性标注 (POS)**:将句子中的每个单词标记为对应于特定的词性(如名词、动词、形容词等)。 +- **分块(chunking)**:找出属于同一实体的 tokens 这个任务(可以与词性标注或命名实体识别结合)可以被描述为将位于块开头的 token 赋予一个标签(通常是 “ `B-` ” (Begin),代表该token位于实体的开头),将位于块内的 tokens 赋予另一个标签(通常是 “ `I-` ”(inner)代表该token位于实体的内部),将不属于任何块的 tokens 赋予第三个标签(通常是 “ `O` ” (outer)代表该token不属于任何实体)。 -当然,还有很多其他类型的token分类问题;这些只是几个有代表性的例子。在本节中,我们将在 NER 任务上微调模型 (BERT),然后该模型将能够计算如下预测: +当然,还有很多其他类型的 token 分类问题;这些只是几个有代表性的例子。在本节中,我们将在 NER 任务上微调模型 (BERT),然后该模型将能够生成如下预测: @@ -39,21 +38,21 @@ -您可以[在这里](https://huggingface.co/huggingface-course/bert-finetuned-ner?text=My+name+is+Sylvain+and+I+work+at+Hugging+Face+in+Brooklyn).找到我们将训练并上传到 Hub的模型,可以尝试输入一些句子看看模型的预测结果。 +你可以 [在这里](https://huggingface.co/huggingface-course/bert-finetuned-ner?text=My+name+is+Sylvain+and+I+work+at+Hugging+Face+in+Brooklyn) 。找到我们将训练并上传到 Hub 的模型,可以尝试输入一些句子测试一下模型的预测结果。 ## 准备数据 [[准备数据]] -首先,我们需要一个适合标记分类的数据集。在本节中,我们将使用[CoNLL-2003 数据集](https://huggingface.co/datasets/conll2003), 其中包含来自路透社的新闻报道。 +首先,我们需要一个适合 token 分类的数据集。在本节中,我们将使用 [CoNLL-2003 数据集](https://huggingface.co/datasets/conll2003) ,该数据集来源于路透社的新闻报道。 -💡 只要您的数据集由带有相应标签的分割成单词并的文本组成,您就能够将这里描述的数据处理过程应用到您自己的数据集。如果需要复习如何在.Dataset中加载自定义数据,请参阅[Chapter 5](/course/chapter5)。 +💡 只要你的数据集由分词文本和对应的标签组成,就能够将这里描述的数据处理过程应用到自己的数据集中。如果需要复习如何在 `Dataset` 中加载自定义数据集,请复习 [第五章](/course/chapter5) 。 ### CoNLL-2003 数据集 [[CoNLL-2003 数据集]] -要加载 CoNLL-2003 数据集,我们使用 来自 🤗 Datasets 库的**load_dataset()** 方法: +要加载 CoNLL-2003 数据集,我们可以使用🤗 Datasets 库的 `load_dataset()` 方法: ```py from datasets import load_dataset @@ -61,7 +60,7 @@ from datasets import load_dataset raw_datasets = load_dataset("conll2003") ``` -这将下载并缓存数据集,就像和我们在[第三章](/course/chapter3) 加载GLUE MRPC 数据集一样。检查这个对象可以让我们看到存在哪些列,以及训练集、验证集和测试集之间是如何分割的: +这将下载并缓存数据集,就像和我们在 [第三章](/course/chapter3) 加载 GLUE MRPC 数据集一样。查看 `raw_datasets` 对象可以让我们看到数据集中存在哪些列,以及训练集、验证集和测试集之间是如何划分的: ```py raw_datasets @@ -84,7 +83,7 @@ DatasetDict({ }) ``` -特别是,我们可以看到数据集包含我们之前提到的三个任务的标签:NER、POS 和chunking。与其他数据集的一个很大区别是输入文本不是作为句子或文档呈现的,而是单词列表(最后一列称为 **tokens** ,但它包含的是这些词是预先标记化的输入,仍然需要通过标记器进行子词标记)。 +可以看到数据集包含了我们之前提到的三项任务的标签:命名实体识别(NER)、词性标注(POS)以及分块(chunking)。这个数据集与其他数据集的一个显著区别在于,输入文本的那一列并非以句子或整片的文本的形式存储,而是以单词列表的形式(最后一列被称为 `tokens` ,不过 `tokens` 列保存的还是单词,也就是说,这些预先分词的输入仍需要经过 tokenizer 进行子词分词处理)。 我们来看看训练集的第一个元素: @@ -96,7 +95,7 @@ raw_datasets["train"][0]["tokens"] ['EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'lamb', '.'] ``` -由于我们要执行命名实体识别,我们将查看 NER 标签: +由于我们要进行命名实体识别,让我们查看一下 NER 标签: ```py raw_datasets["train"][0]["ner_tags"] @@ -106,7 +105,7 @@ raw_datasets["train"][0]["ner_tags"] [3, 0, 7, 0, 0, 0, 7, 0, 0] ``` -这一列是类标签的序列。元素的类型在ner_feature的feature属性中,我们可以通过查看该特性的names属性来访问名称列表: +这些是可以直接训练的整数类别标签,当我们想要检查数据时,直接看整数的标签不是很直观。就像在文本分类中,我们可以通过查看数据集的 `features` 属性来找到这些整数和标签名称之间的对应关系: ```py ner_feature = raw_datasets["train"].features["ner_tags"] @@ -117,7 +116,7 @@ ner_feature 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) ``` -因此,这一列包含的元素是ClassLabels的序列。序列元素的类型在`ner_feature`的`feature`中,我们可以通过查看该`feature`的`names`属性来访问名称列表: +因此,我们得到了 `ClassLabels` 类型的序列。序列中元素的类型存储在 `ner_feature` 的 `feature` 中,我们可以通过查看该 `feature` 的 `names` 属性来访问标签名称的列表: ```py label_names = ner_feature.feature.names @@ -128,15 +127,15 @@ label_names ['O', 'B-PER', 'I-PER', 'B-ORG', 'I-ORG', 'B-LOC', 'I-LOC', 'B-MISC', 'I-MISC'] ``` -我们在[第六章](/course/chapter6/3), 深入研究**token-classification** 管道时已经看到了这些标签 ,但为了快速复习: +我们在 [第六章](/course/chapter6/3) 深入研究 `token-classification` 管道时已经看到过这些标签 我们在这里进行一个快速的回顾: - `O` 表示这个词不对应任何实体。 -- `B-PER`/`I-PER`意味着这个词对应于人名实体的开头/内部。 -- `B-ORG`/`I-ORG` 的意思是这个词对应于组织名称实体的开头/内部。 -- `B-LOC`/`I-LOC` 指的是是这个词对应于地名实体的开头/内部。 -- `B-MISC`/`I-MISC` 表示该词对应于一个杂项实体的开头/内部。 +- `B-PER` / `I-PER` 意味着这个词对应于人名实体的开头/内部。 +- `B-ORG` / `I-ORG` 的意思是这个词对应于组织名称实体的开头/内部。 +- `B-LOC` / `I-LOC` 指的是是这个词对应于地名实体的开头/内部。 +- `B-MISC` / `I-MISC` 表示这个词对应一个其他实体(不属于特定类别或类别之外)的开头 / 内部。 -现在解码我们之前看到的标签: +接下来使用对这些标签名对数据集的 `ner_tags` 进行解码,将得到以下输出结果: ```python words = raw_datasets["train"][0]["tokens"] @@ -158,18 +157,18 @@ print(line2) 'B-ORG O B-MISC O O O B-MISC O O' ``` -例如混合 **B-** 和 **I-** 标签,这是相同的代码在索引 4 的训练集元素上的预测结果: +我们还可以查看训练集中索引为 4 的数据,它是一个同时包含 `B-` 和 `I-` 标签的例子: ```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-标签。 +正如我们在上面的输出中所看到的,跨越两个单词的实体,如“European Union”和“Werner Zwingmann”,数据集把第一个单词标注为了 `B-` 标签,将第二个单词标记为了 `I-` 标签。 -✏️ **轮到你了!** 使用 POS 或chunking标签识别同一个句子。 +✏️ **轮到你了!** 检查同一个句子的词性标注 (POS)或分块(chunking)列,查看输出的结果。 @@ -177,9 +176,9 @@ print(line2) -像往常一样,我们的文本需要转换为Token ID,然后模型才能理解它们。正如我们在[第六章](/course/chapter6/)所学的那样。不过在标记任务中,一个很大的区别是我们有pre-tokenized的输入。幸运的是,tokenizer API可以很容易地处理这个问题;我们只需要用一个特殊的tokenizer。 +像往常一样,我们的文本需要转换为 Token ID,然后模型才能理解它们。正如我们在 [第六章](/course/chapter6/) 所学的那样。不过与 tokens 分类任务不同的是数据集已经完成了预分词,我们在处理的过程中不需要再次分词。幸运的是,tokenizer API 可以很容易地处理这种差异;我们只需要通过一个特殊的标志告诉 tokenizer即可。 -首先,让我们创建`tokenizer`对象。如前所述,我们将使用 BERT 预训练模型,因此我们将从下载并缓存关联的分词器开始: +首先,让我们创建 `tokenizer` 对象。如前所述,我们将使用 BERT 预训练模型,因此我们将从下载并缓存关联的 `tokenizer` 开始: ```python from transformers import AutoTokenizer @@ -188,7 +187,7 @@ 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), 上看到所有带有快速版本的架构,或者检查 您可以通过查看它`is_fast` 属性来检测正在使用的`tokenizer`对象是否由 🤗 Tokenizers 支持: +你可以更换把 `model_checkpoint` 更换为 [Hub](https://huggingface.co/models) 上你喜欢的任何其他模型,或使用本地保存的预训练模型和 `tokenizer`。唯一的限制是 tokenizer 需要由 🤗 Tokenizers 库支持,并且有一个“快速”版本可用。你可以在 [Huggingface 模型后端支持表](https://huggingface.co/transformers/#supported-frameworks) 上看到所有带有快速版本的 `tokenizer` 架构,或者你也可以通过查看它 `is_fast` 属性来检测正在使用的 `tokenizer` 对象是否由 🤗 Tokenizers 支持: ```py tokenizer.is_fast @@ -198,7 +197,7 @@ tokenizer.is_fast True ``` -要对预先标记的输入进行标记,我们可以像往常一样使用我们的`tokenizer` 只需添加 `is_split_into_words=True`: +我们可以像往常一样使用我们的 `tokenizer` 对预先分词的输入进行 `tokenize` ,只需额外添加 `is_split_into_words=True` 参数: ```py inputs = tokenizer(raw_datasets["train"][0]["tokens"], is_split_into_words=True) @@ -209,8 +208,10 @@ inputs.tokens() ['[CLS]', 'EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'la', '##mb', '.', '[SEP]'] ``` -正如我们所见,分词器添加了模型使用的特殊Token(`[CLS]` 在开始和`[SEP]` 最后) 而大多数单词未被修改。然而,单词 `lamb`,被分为两个子单词 `la` and `##mb`。这导致了输入和标签之间的不匹配:标签列表只有9个元素,而我们的输入现在有12个token 。计算特殊Token很容易(我们知道它们在开头和结尾),但我们还需要确保所有标签与适当的单词对齐。 -幸运的是,由于我们使用的是快速分词器,因此我们可以访问🤗 Tokenizers超能力,这意味着我们可以轻松地将每个令牌映射到其相应的单词(如[Chapter 6](/course/chapter6/3)): +如我们所见,`tokenizer` 在结果中添加了模型需要使用的特殊 tokens(在开头的 `[CLS]` ,在结尾的 `[SEP]` ),并且大部分单词保持不变。不过,单词 `lamb` 被分词为两个子词, `la` 和 `##mb` 。这导致了输入和标签之间的不匹配:标签列表只有 9 个元素,而我们的输入现在有 12 个 tokens。解决特殊 tokens 的问题很容易(我们已经知道了每个 `token` 开始和结束的位置),我们需要确保的是我们将所有的标签与正确的词对齐。 + + +幸运的是,由于我们使用的是快速 tokens 因此我们可以使用🤗 Tokenizers 超能力,这意味着我们可以轻松地将每个 token 映射到其相应的单词(如 [第六章](/course/chapter6/3) 中所学): ```py inputs.word_ids() @@ -220,7 +221,7 @@ inputs.word_ids() [None, 0, 1, 2, 3, 4, 5, 6, 7, 7, 8, None] ``` -通过一点点工作,我们可以扩展我们的标签列表以匹配token 。我们将应用的第一条规则是,特殊token 的标签为 `-100` 。这是因为默认情况下 `-100` 是一个在我们将使用的损失函数(交叉熵)中被忽略的索引。然后,每个token 都会获得与其所在单词的token 相同的标签,因为它们是同一实体的一部分。对于单词内部但不在开头的Token,我们将`B-` 替换为 `I-` (因为token 不以实体开头): +只需要一点点工作,我们就可以扩展我们的标签列表。我们将添加的使其与 `token` 列表相匹配。 添加的第一条规则是,特殊 tokens 的标签设置为 `-100` 。这是因为默认情况下, `-100` 会被我们的损失函数(交叉熵)忽略。然后将每个 `token` 的标签设置为这个 `token` 所在单词开头的 `token` 的标签,因为同一个单词一定是同一个实体的一部分。最后将单词的内部 `tokens` 标签中的 `B-` 替换为 `I-`,因为该 `token` 不在实体的开头,`B-` 在每个实体中只应该出现一次: ```python def align_labels_with_tokens(labels, word_ids): @@ -228,17 +229,17 @@ def align_labels_with_tokens(labels, word_ids): current_word = None for word_id in word_ids: if word_id != current_word: - # Start of a new 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: - # Special token + # 特殊的token new_labels.append(-100) else: - # Same word as previous token + # 与前一个 tokens 类型相同的单词 label = labels[word_id] - # If the label is B-XXX we change it to I-XXX + # 如果标签是 B-XXX 我们将其更改为 I-XXX if label % 2 == 1: label += 1 new_labels.append(label) @@ -246,7 +247,7 @@ def align_labels_with_tokens(labels, word_ids): return new_labels ``` -让我们在我们的第一句话上试一试: +让我们在数据集的第一个元素上试一试: ```py labels = raw_datasets["train"][0]["ner_tags"] @@ -260,15 +261,15 @@ print(align_labels_with_tokens(labels, word_ids)) [-100, 3, 0, 7, 0, 0, 0, 7, 0, 0, 0, -100] ``` -正如我们所看到的,我们的函数为开头和结尾的两个特殊标记添加了 `-100` ,并为分成两个标记的单词添加了一个新的`0` 。 +正如我们所看到的,我们的函数为开头和结尾添加了两个特殊 tokens :`-100` ,并为切分成两个 tokens 的单词添加了一个新的 `0` 标签。 -✏️ **轮到你了!** 一些研究人员更喜欢每个词只归属一个标签, 并分配 `-100` 给定词中的其他子标记。这是为了避免分解成大量子标记的长词对损失造成严重影响。按照此规则更改前一个函数使标签与输入id对齐。 +✏️ **轮到你了!** 有些研究人员更喜欢只为每个单只分配一个标签,对该单词其他部分的 token 分配 `-100` 标签。这是为了避免那些分解成许多子词的长单词对损失作出过多的贡献。请按照这个思路,改变之前的函数,使标签与 inputs ID 对齐。 -为了预处理我们的整个数据集,我们需要标记所有输入并在所有标签上应用 `align_labels_with_tokens()` 。为了利用我们的快速分词器的速度优势,最好同时对大量文本进行分词,因此我们将编写一个处理示例列表的函数并使用带 `batched=True` 有选项的 `Dataset.map()`方法 .与我们之前的示例唯一不同的是当分词器的输入是文本列表(或者像例子中的单词列表)时 `word_ids()` 函数需要获取我们想要单词的索引的ID,所以我们也添加它: +为了预处理我们的整个数据集,我们需要对所有输入进行 `tokenize`,并使用 `align_labels_with_tokens()` 函数处理所有标签。为了充分利用快速 tokenizer 的优势,最好是同时对大量文本一起进行 `tokenize`,所以我们需要编写一个处理一组示例的函数,并使用带有 `batched=True` 参数的 `Dataset.map()` 方法。与我们之前的示例唯一不同的是,当 `tokenizer` 的输入是文本列表(或示例中单词的列表的列表)时, `word_ids()` 函数需要根据列表的索引获取 `token` 的 ID,所以我们也在下面的函数中添加了这个功能: ```py def tokenize_and_align_labels(examples): @@ -285,9 +286,9 @@ def tokenize_and_align_labels(examples): return tokenized_inputs ``` -请注意,我们还没有填充我们的输入;我们稍后会在使用数据整理器创建batch时这样做。 +注意,我们还没有填充我们的输入,我们将在稍后使用数据整理器创建 `batch` 时进行。 -我们现在可以一次性将所有预处理应用于数据集的其他部分: +我们现在可以一次性使用预处理函数处理整个数据集: ```py tokenized_datasets = raw_datasets.map( @@ -297,28 +298,27 @@ tokenized_datasets = raw_datasets.map( ) ``` -我们已经完成了最难的部分!现在数据已经被预处理了,实际的训练看起来很像我们[第三章](/course/chapter3)做的. +我们已经完成了最困难的部分!现在数据已经经过了预处理,实际的训练过程将会与我们在 [第三章](/course/chapter3) 所做的很相似。 {#if fw === 'pt'} ## 使用 Trainer API 微调模型 [[使用 Trainer API 微调模型]] -使用 `Trainer` 的实际代码会和以前一样;唯一的变化是数据整理成时批处理的方式和度量计算函数。 +使用 `Trainer` 的代码会和以前一样,唯一的变化是数据如何整理成 `batch` 以及计算评估的分数。 {:else} ## 使用 Keras 微调模型 [[使用 Keras 微调模型]] -使用Keras的实际代码将与之前非常相似;唯一的变化是将数据整理成批处理的方式和指标计算函数。 +使用 `Keras` 的代码将与之前非常相似;唯一的变化是数据如何整理成 `batch` 以及计算评估的分数。 {/if} +### 整理数据 [[整理数据]] -### 数据排序 [[数据排序]] - -我们不能像[第三章](/course/chapter3)那样只使用一个 `DataCollatorWithPadding `因为这只会填充输入(输入 ID、注意掩码和标记类型 ID)。在这里我们的标签应该以与输入完全相同的方式填充,以便它们保持长度相同,使用 `-100 ` ,这样在损失计算中就可以忽略相应的预测。 +我们不能像 [第三章](/course/chapter3) 那样直接使用 `DataCollatorWithPadding` ,因为那样只会填充输入(inputs ID、注意掩码和 tokens 类型 ID)。除了输入部分,在这里我们还需要对标签也使用与输入完全相同的方式填充,以保证它与输入的大小相同。我们将使用 `-100` 进行填充,以便在损失计算中忽略填充的内容。 -这一切都是由一个 [`DataCollatorForTokenClassification`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorfortokenclassification)完成.它是一个带有填充的数据整理器它需要 `tokenizer ` 用于预处理输入: +以上的这些过程可以由 [`DataCollatorForTokenClassification`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorfortokenclassification) 实现。它是一个带有填充功能的数据整理器,使用时只需要传入用于预处理输入的 `tokenizer` : {#if fw === 'pt'} @@ -340,7 +340,7 @@ data_collator = DataCollatorForTokenClassification( {/if} -为了在几个样本上测试这一点,我们可以在训练集中的示例列表上调用它: +为了在几个样本上测试这个数据整理器,我们可以先在训练集中的几个示例上调用它: ```py batch = data_collator([tokenized_datasets["train"][i] for i in range(2)]) @@ -352,7 +352,7 @@ 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): @@ -366,11 +366,11 @@ for i in range(2): {#if fw === 'pt'} -正如我们所看到的,第二组标签的长度已经使用 `-100` 填充到与第一组标签相同。 +正如我们所看到的,已经使用 `-100` 将第二组标签填充到了与第一组标签相同的长度。 {:else} -我们的数据整理器已准备就绪! 现在让我们使用它通过 `to_tf_dataset()` 方法制作一个 `tf.data.Dataset`。 您还可以使用 model.prepare_tf_dataset() 来使用更少的样板代码来执行此操作——您将在本章的其他部分中看到这一点。 +我们的数据整理器已准备就绪!现在让我们使用 `to_tf_dataset()` 方法创建一个 `tf.data.Dataset` 。除此之外,你还可以使用 `model.prepare_tf_dataset()` 来使用更少的模板代码来创建 `Dataset`——你将在本章的其他小节中看到这样的操作。 ```py tf_train_dataset = tokenized_datasets["train"].to_tf_dataset( columns=["attention_mask", "input_ids", "labels", "token_type_ids"], @@ -387,25 +387,24 @@ tf_eval_dataset = tokenized_datasets["validation"].to_tf_dataset( ) ``` - - Next stop: the model itself. +下一站:模型本身。 {/if} {#if fw === 'tf'} -### 定义模型 +### 初始化模型 -由于我们正在研究Token分类问题,因此我们将使用 `AutoModelForTokenClassification` 类。定义这个模型时要记住的主要事情是传递一些关于我们的标签数量的信息。执行此操作的最简单方法是将该数字传递给 `num_labels` 参数,但是如果我们想要一个很好的推理小部件,就像我们在本节开头看到的那样,最好设置正确的标签对应关系。 +由于我们正在研究 Token 分类问题,因此我们将使用 `TFAutoModelForTokenClassification` 类。实例化此模型时要记得传递我们标签的数量,最简单方法是将该数字传递给 `num_labels` 参数,但是如果我们想要创建一个就像我们在本节开头看到的那样的推理小部件,那么就需要用最标准的方法正确设置标签的对应关系。 -它们应该由两个字典设置, `id2label` 和 `label2id` ,其中包含从 ID 到标签的映射,反之亦然: +最标准的方法是用两个字典 `id2label` 和 `label2id` 来设置它们,这两个字典包含从 ID 到标签的映射以及反向的映射: ```py id2label = {str(i): label for i, label in enumerate(label_names)} label2id = {v: k for k, v in id2label.items()} ``` -现在我们可以将它们传递给 `AutoModelForTokenClassification.from_pretrained()` 方法,它们将在模型的配置中设置,然后保存并上传到Hub: +现在我们只需将这两个字典传递给 `TFAutoModelForTokenClassification.from_pretrained()` 方法,它们就会被保存在模型的配置中,然后被正确地保存和上传到 Hub: ```py from transformers import TFAutoModelForTokenClassification @@ -417,7 +416,7 @@ model = TFAutoModelForTokenClassification.from_pretrained( ) ``` -就像我们在[第三章](/course/chapter3),定义我们的 `AutoModelForSequenceClassification` ,创建模型会发出警告,提示一些权重未被使用(来自预训练头的权重)和一些其他权重被随机初始化(来自新Token分类头的权重),我们将要训练这个模型。我们将在一分钟内完成,但首先让我们仔细检查我们的模型是否具有正确数量的标签: +就像我们在 [第三章](/course/chapter3) 中实例化 `TFAutoModelForTokenClassification` 类一样 创建模型会发出一个警告,提示一些权重未被使用(来自预训练模型头部的权重)和一些其他权重被随机初始化(来自新 Token 分类头部的权重),别担心,我们马上就要训练这些权重。首先让我们确认一下我们的模型是否具有正确的标签数量: ```python model.config.num_labels @@ -429,13 +428,13 @@ model.config.num_labels -⚠️ 如果您的模型标签数量错误,则在稍后调用 `model.fit()` 时将收到一个模糊的错误。调试起来可能很烦人,因此请确保执行此检查以确认您具有预期的标签数。 +⚠️ 如果你的模型的标签数量错误,那么在后面调用 `model.fit()` 时,你会得到一个晦涩的错误。这可能会令人烦恼,所以确保你做了这个检查,确认你的标签数量是正确的。 ### 微调模型 -现在,我们已准备好训练模型了!不过,我们首先要做两件事:应该登录到Hugging Face并定义我们的训练超参数。如果你在notebook上工作,有一个便利功能可以帮助你做到这一点: +现在,我们已准备好训练模型了!不过,我们在这之前还要做两件事:登录到 Hugging Face 并设置我们的训练超参数。如果你在 notebook 上工作,有一个便捷函数可以帮助你做到这一点: ```python from huggingface_hub import notebook_login @@ -443,26 +442,26 @@ from huggingface_hub import notebook_login notebook_login() ``` -这将显示一个小部件,您可以在其中输入您的 Hugging Face 账号和密码。 +这将显示一个小部件,你可以在其中输入你的 Hugging Face 账号和密码。 -如果您不是在notebook上工作,只需在终端中输入以下行: +如果你不是在 notebook 上工作,只需在终端中输入以下代码: ```bash huggingface-cli login ``` -登录后,我们可以准备编译模型所需的一切。🤗 Transformers提供了一个方便的`create_optimizer()` 函数,该函数将为您提供一个`AdamW`优化器,其中包含适当的权重衰减和学习速率衰减设置,与内置的`Adam`优化器相似,这两者都将提高模型的性能: +登录后,我们可以编译我们模型所需要的所有配置。🤗 Transformers 提供了一个便捷的 `create_optimizer()` 函数,与 `TensorFlow` 内置的 `Adam` 优化器相比,它可以提供一个带有恰当的权重衰减和学习率衰减机制的 `AdamW` 优化器,不过这两个机制都将提高模型的性能: ```python from transformers import create_optimizer import tensorflow as tf -# Train in mixed-precision float16 +# 在混合精度 float16 中进行训练 tf.keras.mixed_precision.set_global_policy("mixed_float16") -# 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. +# 训练步数是数据集中的样本数量,除以 batch 大小,然后乘以总的 epoch 数。 +# 注意这里的 tf_train_dataset 是 batch 形式的 tf.data.Dataset, +# 而不是原始的 Hugging Face Dataset ,所以使用 len() 计算它的长度已经是 num_samples // batch_size。 num_epochs = 3 num_train_steps = len(tf_train_dataset) * num_epochs @@ -475,9 +474,9 @@ optimizer, schedule = create_optimizer( model.compile(optimizer=optimizer) ``` -还要注意,我们不为`compile()`提供`loss`参数。这是因为模型实际上可以在内部计算损失 - 如果您编译时没有损失并在输入字典中提供标签(就像我们在数据集中所做的那样),那么模型将使用该内部损失进行训练,这将适用于您选择的任务和模型类型。 +请注意,我们没有给 `compile()` 提供 `loss` 参数。这是因为模型实际上可以使用默认的方式自动计算损失 —— 如果你在编译时没有提供损失的计算方法或在输入中提供真实的标签值(就像我们在数据集中所做的那样),那么模型将使用内部默认的 `loss` 计算方法进行训练,具体的计算方式取决于你选择的任务和模型类型。 -接下来,我们定义一个`PushToHubCallback`,以便在训练期间将模型上传到 Hub,并使用该回调来拟合模型: +接下来,我们定义一个 `PushToHubCallback` 回调函数,以便在训练期间将模型上传到 Hub,并在拟合模型时添加该回调函数: ```python from transformers.keras_callbacks import PushToHubCallback @@ -492,44 +491,43 @@ model.fit( ) ``` -您之前已经看过其中的大部分内容:我们设置了一些超参数(例如学习率、要训练的 epoch 数和权重衰减),然后我们指定 `push_to_hub=True` 表明我们想要保存模型并在每个时期结束时对其进行评估,并且我们想要将我们的结果上传到模型中心。请注意,可以使用hub_model_id参数指定要推送到的存储库的名称(特别是,必须使用这个参数来推送到一个组织)。例如,当我们将模型推送到[`huggingface-course` organization](https://huggingface.co/huggingface-course), 我们添加了 `hub_model_id=huggingface-course/bert-finetuned-ner` 到 `TrainingArguments` .默认情况下,使用的存储库将在您的命名空间中并以您设置的输出目录命名,因此在我们的例子中它将是 `sgugger/bert-finetuned-ner` . +你可以使用 `hub_model_id` 参数指定你想要推送的仓库的全名(特别需要注意的是,如果你需要推送给某个组织,就必须使用这个参数)。例如,当我们将模型推送到 [`huggingface-course` 组织](https://huggingface.co/huggingface-course) 时,我们添加了 `hub_model_id="huggingface-course/bert-finetuned-ner"` 。默认情况下,这个仓库将保存在你的账户之内,并以你设置的输出目录命名,例如 `"cool_huggingface_user/bert-finetuned-ner"` 。 -💡 如果您正在使用的输出目录已经存在,那么输出目录必须是从同一个存储库clone下来的。如果不是,您将在声明 `model.fit()` 时遇到错误,并且需要设置一个新名称。 +💡 如果设置了模型保存的路径,并且在那里已经存在了一个非空的同名文件夹,那么该目录应该是 Hub 仓库克隆在本地的版本,如果不是,则会在调用 model.fit() 方法时收到一个错误,并需要设置一个新的路径。 -请注意,当训练发生时,每次保存模型时(这里是每个epooch),它都会在后台上传到 Hub。这样,如有必要,您将能够在另一台机器上继续您的训练。 +请注意,在训练过程中每次保存模型时(这里是每个 epooch),它都会在后台上传到 Hub。这样,如有需要,你将能够在另一台机器上继续你的训练。 -在此阶段,您可以使用模型中心上的推理小组件来测试模型并与朋友共享。您已经成功微调了令牌分类任务的模型 - 恭喜!但是,我们的模型到底有多好呢?我们应该评估一些指标来找出答案。 +到此阶段,你可以在模型 Hub 上使用推理小部件来测试你的模型并与你的朋友分享。你已经成功地在一个 tokens 分类任务上微调了一个模型——恭喜你!但我们的模型真的好用吗?我们应该找出一些指标来对模型进评估。 {/if} - ### 评估指标 [[评估指标]] {#if fw === 'pt'} -为了让 `Trainer` 在每个epoch计算一个度量,我们需要定义一个 `compute_metrics()` 函数,该函数接受预测和标签数组,并返回一个包含度量名称和值的字典 +要让 `Trainer` 在每个周期计算一个指标,我们需要定义一个 `compute_metrics()` 函数,该函数的输入是预测值和标签的数组,并返回带有指标名称和评估结果的字典。 -用于评估Token分类预测的传统框架是 [*seqeval*](https://github.com/chakki-works/seqeval). 要使用此指标,我们首先需要安装seqeval库: +用于评估 Token 分类预测的经典框架是 [*seqeval*](https://github.com/chakki-works/seqeval) 。要使用此指标,我们首先需要安装 seqeval 库: ```py !pip install seqeval ``` -然后我们可以通过加载它 `evaluate.load()` 函数就像我们在[第三章](/course/chapter3)做的那样: +然后我们可以通过 `evaluate.load()` 函数加载它,就像我们在 [第三章](/course/chapter3) 中所做的那样: {:else} -用于评估Token分类预测的传统框架是 [*seqeval*](https://github.com/chakki-works/seqeval). 要使用此指标,我们首先需要安装seqeval库: +用于评估 Token 分类预测的经典框架是 [*seqeval*](https://github.com/chakki-works/seqeval) 。要使用此指标,我们首先需要安装 seqeval 库: ```py !pip install seqeval ``` -然后我们可以通过`evaluate.load()` 函数加载它就像我们在[第三章](/course/chapter3)做的那样: +然后我们可以通过 `evaluate.load()` 函数加载它,就像我们在 [第三章](/course/chapter3) 中所做的那样: {/if} @@ -539,8 +537,9 @@ import evaluate metric = evaluate.load("seqeval") ``` -这个评估方式与标准精度不同:它实际上将标签列表作为字符串,而不是整数,因此在将预测和标签传递给它之前,我们需要完全解码它们。让我们看看它是如何工作的。首先,我们将获得第一个训练示例的标签: +该指标和常规的评测指标有些区别:它需要字符串形式的标签列表而不是整数,所以我们需要在将它们传递给指标之前将预测值和标签由数字解码为字符串。让我们先用一些测试数据看看它是如何工作的: +首先,获取第一个训练样本的标签。 ```py labels = raw_datasets["train"][0]["ner_tags"] labels = [label_names[i] for i in labels] @@ -551,7 +550,7 @@ labels ['B-ORG', 'O', 'B-MISC', 'O', 'O', 'O', 'B-MISC', 'O', 'O'] ``` -然后我们可以通过更改索引 2 处的值来为那些创建假的预测: +然后我们可以通过更改索引 2 处的值来为这些标签创建假的预测值来测试该指标: ```py predictions = labels.copy() @@ -559,7 +558,7 @@ predictions[2] = "O" metric.compute(predictions=[predictions], references=[labels]) ``` -请注意,该指标的输入是预测列表(不仅仅是一个)和标签列表。这是输出: +请注意,该指标的输入是预测列表(不是一个)和标签列表,输出结果如下: ```python out {'MISC': {'precision': 1.0, 'recall': 0.5, 'f1': 0.67, 'number': 2}, @@ -572,9 +571,7 @@ metric.compute(predictions=[predictions], references=[labels]) {#if fw === 'pt'} -它返回很多信息!我们获得每个单独实体以及整体的准确率、召回率和 F1 分数。对于我们的度量计算,我们将只保留总分,但可以随意调整 `compute_metrics()` 函数返回您想要查看的所有指标。 - -这`compute_metrics()` 函数首先采用 logits 的 argmax 将它们转换为预测(像往常一样,logits 和概率的顺序相同,因此我们不需要应用 softmax)。然后我们必须将标签和预测从整数转换为字符串。我们删除标签为 `-100` 所有值 ,然后将结果传递给 `metric.compute()` 方法: +结果返回很多信息!包括每个单独实体及整体的准确率、召回率和 F1 分数。在这里我们将只保留总分,但是你可以自由地调整 `compute_metrics()` 函数返回的所需要指标。这个函数中我们首先会先取预测 `logits` 的 `argmax`,并将其转换为预测值。 `compute_metrics()` 函数首先取 `logits` 的 `argmax`,将它们转换为预测值(通常情况下,logits 和概率的顺序是相同,所以我们不需要使用 softmax)。然后我们需要将标签和预测值都从整数转换为字符串。我们删除所有标签为 `-100` 的值,最后将结果传递给 `metric.compute()` 方法: ```py import numpy as np @@ -584,7 +581,7 @@ def compute_metrics(eval_preds): logits, labels = eval_preds predictions = np.argmax(logits, axis=-1) - # Remove ignored index (special tokens) and convert to labels + # 删除忽略的索引(特殊 tokens )并转换为标签 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] @@ -599,13 +596,13 @@ def compute_metrics(eval_preds): } ``` -现在已经完成了,我们几乎准备好定义我们的 `Trainer` .我们只需要一个 `model` 微调! +现在已经完成了,我们下面就可以开始定义我们的 `Trainer` 了。接下来我们只需要一个 `model` 并对其微调! {:else} -它返回很多信息!我们获得每个单独实体以及整体的准确率、召回率和 F1 分数。对于我们的度量计算,我们将只保留总分,但可以随意调整 `compute_metrics()` 函数返回您想要查看的所有指标。 +它返回了大量的信息!我们获得每个单独实体以及整体的准确率、召回率和 F1 分数。现在,让我们看看如果我们尝试使用真实的模型预测来计算分数。 -这`compute_metrics()` 函数首先采用 logits 的 argmax 将它们转换为预测(像往常一样,logits 和概率的顺序相同,因此我们不需要应用 softmax)。然后我们必须将标签和预测从整数转换为字符串。我们删除标签为 `-100` 所有值 ,然后将结果传递给 `metric.compute()` 方法: +在使用基于TensorFlow 的框架时,我们通常不会把预测拼接在一起,因为这样会导致序列长度不统一。这意味着我们不能直接使用 `model.predict()` 方法 —— 但这并不能阻止我们。我们可以逐批获取一些预测并在在此的过程中将它们拼接成一个大的列表,删除表示 `masking/padding` 的 `-100` tokens 然后在合并后的大列表上计算度量值: ```py import numpy as np @@ -637,7 +634,7 @@ metric.compute(predictions=[all_predictions], references=[all_labels]) 'overall_accuracy': 0.97} ``` -与我们的模型相比,您的模型做得如何?如果您获得类似的数字,那么您的训练就是成功的! +与我们的模型相比,你的模型的表现如何?如果你看到类似的数字,那么你的训练就成功了! {/if} @@ -645,16 +642,16 @@ metric.compute(predictions=[all_predictions], references=[all_labels]) ### 定义模型 -由于我们正在研究Token分类问题,因此我们将使用 `AutoModelForTokenClassification` 类。定义这个模型时要记住的主要事情是传递一些关于我们的标签数量的信息。执行此操作的最简单方法是将该数字传递给 `num_labels` 参数,但是如果我们想要一个很好的推理小部件,就像我们在本节开头看到的那样,最好设置正确的标签对应关系。 +由于我们正在研究 Token 分类问题,因此我们将使用 `AutoModelForTokenClassification` 类。定义此模型时要记得传递我们标签的数量,最简单方法是将该数字传递给 `num_labels` 参数,但是如果我们想要一个就像我们在本节开头看到的那样的推理小部件,就需要用最标准的方法正确设置标签的对应关系。 -它们应该由两个字典设置, `id2label` 和 `label2id` ,其中包含从 ID 到标签的映射,反之亦然: +最标准的方式是用两个字典 `id2label` 和 `label2id` 来设置标签,这两个字典包含从 ID 到标签的映射以及反向的映射: ```py id2label = {str(i): label for i, label in enumerate(label_names)} label2id = {v: k for k, v in id2label.items()} ``` -现在我们可以将它们传递给 `AutoModelForTokenClassification.from_pretrained()` 方法,它们将在模型的配置中设置,然后保存并上传到Hub: +现在我们只需将它们传递给 `AutoModelForTokenClassification.from_pretrained()` 方法,它们就会被保存在模型的配置中,然后被正确地保存和上传到 Hub: ```py from transformers import AutoModelForTokenClassification @@ -666,7 +663,7 @@ model = AutoModelForTokenClassification.from_pretrained( ) ``` -就像我们在[第三章](/course/chapter3),定义我们的 `AutoModelForSequenceClassification` ,创建模型会发出警告,提示一些权重未被使用(来自预训练头的权重)和一些其他权重被随机初始化(来自新Token分类头的权重),我们将要训练这个模型。我们将在一分钟内完成,但首先让我们仔细检查我们的模型是否具有正确数量的标签: +就像我们在 [第三章](/course/chapter3) 中定义 `AutoModelForSequenceClassification` 类一样 创建模型会发出一个警告,提示一些权重未被使用(来自预训练头部的权重)和一些其他权重被随机初始化(来自新 Token 分类头部的权重),不过不用担心,我们马上就要训练这些权重。在这之前,让我们先确认一下我们的模型是否具有正确的标签数量: ```python model.config.num_labels @@ -678,26 +675,21 @@ model.config.num_labels -⚠️ 如果模型的标签数量错误,稍后调用Trainer.train()方法时会出现一个模糊的错误(类似于“CUDA error: device-side assert triggered”)。这是用户报告此类错误的第一个原因,因此请确保进行这样的检查以确认您拥有预期数量的标签。 +⚠️ 如果你的模型的标签数量有错误,那么在后面调用 `Trainer.train()` 时,你会得到一个晦涩的错误(类似于“CUDA error:device-side assert triggered”)。这可能会令人烦恼,所以确保你做了这个检查,确认你的标签数量是正确。 ### 微调模型 -我们现在准备好训练我们的模型了!在定义我们的 `Trainer`之前,我们只需要做最后两件事:登录 Hugging Face 并定义我们的训练参数。如果您在notebook上工作,有一个方便的功能可以帮助您: +我们现在准备好训练我们的模型了!在定义 `Trainer` 之前,我们只需要做最后两件事:登录 Hugging Face 并设置我们的训练参数。如果你在 notebook 上工作,有一个方便的小工具可以帮助你: ```python from huggingface_hub import notebook_login notebook_login() ``` -这将显示一个小部件,您可以在其中输入您的 Hugging Face 账号和密码。如果您不是在notebook上工作,只需在终端中输入以下行: -```bash -huggingface-cli login -``` - -Once this is done, we can define our `TrainingArguments`: +登录后,我们就可以设置我们的 `TrainingArguments` : ```python from transformers import TrainingArguments @@ -713,15 +705,15 @@ args = TrainingArguments( ) ``` -您之前已经看过其中的大部分内容:我们设置了一些超参数(例如学习率、要训练的 epoch 数和权重衰减),然后我们指定 `push_to_hub=True` 表明我们想要保存模型并在每个时期结束时对其进行评估,并且我们想要将我们的结果上传到模型中心。请注意,可以使用hub_model_id参数指定要推送到的存储库的名称(特别是,必须使用这个参数来推送到一个组织)。例如,当我们将模型推送到[`huggingface-course` organization](https://huggingface.co/huggingface-course), 我们添加了 `hub_model_id=huggingface-course/bert-finetuned-ner` 到 `TrainingArguments` 。默认情况下,使用的存储库将在您的命名空间中并以您设置的输出目录命名,因此在我们的例子中它将是 `sgugger/bert-finetuned-ner`。 +你已经对大多数内容有所了解了:我们设置了一些超参数(如学习率、训练的轮数和权重衰减),并设定 `push_to_hub=True` ,表示我们希望在每个训练轮次结束时保存并评估模型,然后将结果上传到模型中心。注意,你可以通过 `hub_model_id` 参数指定你想推送的仓库的名称(特别需要注意的是,如果你需要推送给某个组织,就必须使用这个参数)。例如,当我们将模型推送到 [`huggingface-course` 组织](https://huggingface.co/huggingface-course) 时,我们在 `TrainingArguments` 中添加了 `hub_model_id="huggingface-course/bert-finetuned-ner"` 。默认情况下,使用的仓库将保存在你的账户之内,并以你设置的输出目录命名,所以在我们的例子中,仓库的地址是 `"sgugger/bert-finetuned-ner"` 。 -💡 如果您正在使用的输出目录已经存在,那么输出目录必须是从同一个存储库clone下来的。如果不是,您将在声明 `Trainer` 时遇到错误,并且需要设置一个新名称。 +💡 如果你使用的输出路径已经存在一个同名的文件夹,那么它需要是你想推送到 hub 的仓库的克隆在本地的版本。如果不是,你将在声明 `Trainer` 时遇到一个错误,并需要设置一个新的路径。 -最后,我们只是将所有内容传递给 `Trainer` 并启动训练: +最后,我们将所有内容传递给 `Trainer` 并启动训练: ```python from transformers import Trainer @@ -738,30 +730,31 @@ trainer = Trainer( trainer.train() ``` -请注意,当训练发生时,每次保存模型时(这里是每个epooch),它都会在后台上传到 Hub。这样,如有必要,您将能够在另一台机器上继续您的训练。 +请注意,在训练过程中每次保存模型时(这里是每个 epooch),它都会在后台上传到 Hub。这样,如有必要,你将能够在另一台机器上继续你的训练。 -训练完成后,我们使用 `push_to_hub()` 确保我们上传模型的最新版本 +训练完成后,我们使用 `push_to_hub()` 上传模型的最新版本 ```py trainer.push_to_hub(commit_message="Training complete") ``` -This command returns the URL of the commit it just did, if you want to inspect it: +如果你想检查一下是否上传成功,这个命令会返回刚刚执行的提交的 URL: ```python out 'https://huggingface.co/sgugger/bert-finetuned-ner/commit/26ab21e5b1568f9afeccdaed2d8715f571d786ed' ``` -这 `Trainer` 还创建了一张包含所有评估结果的模型卡并上传。在此阶段,您可以使用模型中心上的推理小部件来测试您的模型并与您的朋友分享。您已成功在Token分类任务上微调模型 - 恭喜! +同时 `Trainer` 还创建并上传了一张包含所有评估结果的模型卡。到此阶段,你可以在模型 Hub 上使用推理小部件来测试你的模型并与你的朋友分享。你已经成功地在一个 tokens 分类任务上微调了一个模型——恭喜你! -如果您想更深入地了解训练循环,我们现在将向您展示如何使用 🤗 Accelerate 做同样的事情。 +如果你想更深入地了解训练循环,我们现在将向你展示如何使用 🤗 Accelerate 做同样的事情。 ## 自定义训练循环 [[自定义训练循环]] -现在让我们看一下完整的训练循环,这样您可以轻松定义所需的部分。它看起来很像我们在[第三章](/course/chapter3/4), 所做的,对评估进行了一些更改。 +现在我们来看看完整的训练循环,这样你就可以轻松地定制你需要的部分。它与我们在 [第三章](/course/chapter3/4) 中所做的内容很相似,但对评估部分有一些改动。 ### 做好训练前的准备 [[做好训练前的准备]] -首先我们需要为我们的数据集构建 `DataLoader` 。我们将重用我们的 `data_collator` 作为一个 `collate_fn` 并打乱训练集,但不打乱验证集: + +首先我们需要为我们的数据集构建 `DataLoader` 。我们将 `data_collator` 传递给 `collate_fn` 参数并随机打乱训练集,但不打乱验证集: ```py from torch.utils.data import DataLoader @@ -777,7 +770,7 @@ eval_dataloader = DataLoader( ) ``` -接下来,我们重新实例化我们的模型,以确保我们不会从之前的训练继续训练,而是再次从 BERT 预训练模型开始: +接下来我们重新实例化我们的模型,以确保我们不是继续之前的微调,而是重新开始从 BERT 微调预训练模型: ```py model = AutoModelForTokenClassification.from_pretrained( @@ -787,14 +780,15 @@ model = AutoModelForTokenClassification.from_pretrained( ) ``` -然后我们将需要一个优化器。我们将使用经典 `AdamW` ,这就像 `Adam` ,但在应用权重衰减的方式上进行了改进: +然后我们需要一个优化器。我们将使用经典 `AdamW` ,它类似于 `Adam` ,但在权重衰减的方式上进行了改进: + ```py from torch.optim import AdamW optimizer = AdamW(model.parameters(), lr=2e-5) ``` -Once we have all those objects, we can send them to the `accelerator.prepare()` method: +当我们拥有了所有这些对象之后,我们就可以将它们发传递给 `accelerator.prepare()` 方法: ```py from accelerate import Accelerator @@ -807,11 +801,11 @@ model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( -🚨 如果您在 TPU 上进行训练,则需要将以上单元格中的所有代码移动到专用的训练函数中。有关详细信息,请参阅 [第3章](/course/chapter3)。 +🚨 如果你正在 TPU 上训练,你需要将上面单元格开始的所有代码移动到一个专门的训练函数中。更多详情请回顾 [第三章](/course/chapter3) 。 -现在我们已经发送了我们的 `train_dataloader` 到 `accelerator.prepare()` ,我们可以使用它的长度来计算训练步骤的数量。请记住,我们应该始终在准备好dataloader后执行此操作,因为该方法会改变其长度。我们使用经典线性学习率调度: +现在我们已经将我们的 `train_dataloader` 传递给了 `accelerator.prepare()` 方法,我们还可以使用 `len()` 来计算训练步骤的数量。请记住,我们应该在准备好 `dataloader` 后再使用 `len()` ,因为改动 `dataloader` 会改变训练长度的数量。这里我们将使用一个从学习率衰减到 0 的经典线性学习率调度: ```py from transformers import get_scheduler @@ -828,7 +822,7 @@ lr_scheduler = get_scheduler( ) ``` -最后,要将我们的模型推送到 Hub,我们需要创建一个 `Repository` 工作文件夹中的对象。如果您尚未登录,请先登录 Hugging Face。我们将从我们想要为模型提供的模型 ID 中确定存储库名称(您可以自由地用自己的选择替换 `repo_name` ;它只需要包含您的用户名,可以使用`get_full_repo_name()`函数的查看目前的repo_name): +最后,为了将我们的模型推送到 Hub,我们需要在一个工作文件夹中创建一个 `Repository` 对象。如果你还没有登录的话,首先需要登录到 Hugging Face,然后根据模型 ID 来确定仓库名称(你可以将 `repo_name` 替换为你喜欢的名字;只需要包含你的用户名即可,你可以使用 `get_full_repo_name()` 函数的查看目前的 `repo_name` ): ```py from huggingface_hub import Repository, get_full_repo_name @@ -842,24 +836,25 @@ repo_name 'sgugger/bert-finetuned-ner-accelerate' ``` -然后我们可以将该存储库克隆到本地文件夹中。 如果它已经存在,这个本地文件夹应该是我们正在使用的存储库的现有克隆: +然后我们可以将该仓库克隆到本地文件夹中。如果本地已经存在同名的文件夹,这个本地文件夹必须是我们正在使用的仓库克隆在本地的版本: ```py output_dir = "bert-finetuned-ner-accelerate" repo = Repository(output_dir, clone_from=repo_name) ``` -我们现在可以通过调用 `repo.push_to_hub()` 方法上传保存在 `output_dir` 中的任何内容。 这将帮助我们在每个训练周期结束时上传中间模型。 +我们现在可以通过调用 `repo.push_to_hub()` 方法上传保存在 `output_dir` 中的所有内容。它帮助我们在每个训练周期结束时上传中间模型。 ### 训练循环 [[训练循环]] -我们现在准备编写完整的训练循环。为了简化它的评估部分,我们定义了这个 `postprocess()` 接受预测和标签并将它们转换为字符串列表的函数,也就是 `metric`对象需要的输入格式: + +我们现在准备编写完整的训练循环。为了简化其评估部分,我们定义了一个 `postprocess()` 函数,该函数会接收模型的预测和真实的标签,并将它们转换为字符串列表,也就是我们的 `metric` (评估函数)对象需要的输入格式。 ```py def postprocess(predictions, labels): predictions = predictions.detach().cpu().clone().numpy() labels = labels.detach().cpu().clone().numpy() - # Remove ignored index (special tokens) and convert to labels + # 删除忽略的索引(特殊 tokens )并转换为标签 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] @@ -868,13 +863,13 @@ def postprocess(predictions, labels): return true_labels, true_predictions ``` -然后我们可以编写训练循环。在定义一个进度条来跟踪训练的进行后,循环分为三个部分: +然后我们可以编写训练循环。在定义一个进度条来跟踪训练的进度后,循环分为三个部分: -- 训练本身,这是对`train_dataloader`的经典迭代,向前传递模型,然后反向传递和优化参数 -- 评估,在获得我们模型的输出后:因为两个进程可能将输入和标签填充成不同的形状,在调用`gather()`方法前我们需要使用`accelerator.pad_across_processes()`来让预测和标签形状相同。如果我们不这样做,评估要么出错,要么永远不会得到结果。然后,我们将结果发送给`metric.add_batch()`,并在计算循环结束后调用`metric.compute()`。 -- 保存和上传,首先保存模型和标记器,然后调用`repo.push_to_hub()`。注意,我们使用参数`blocking=False`告诉🤗 hub 库用在异步进程中推送。这样,训练将正常继续,并且该(长)指令将在后台执行。 +- 训练本身,这是经典的迭代过程,即在 `train_dataloader` 上进行迭代,在模型上前向传播训练数据,然后反向传递 `loss` 和优化参数 +- 评估,在获取模型在一个 `batch` 上的输出之后,这里有一个需要注意的地方:由于两个进程可能已将输入和标签填充到不同的形状,我们需要使用 `accelerator.pad_across_processes()` 使预测和真实的标签在调用 `gather()` 方法之前具有相同的形状。如果我们不这样做,评估循环将会出错或无限期卡住。最后我们将结果发送到 `metric.add_batch()` 方法中,并在评估循环结束时调用 `metric.compute()` 方法。 +- 保存和上传,首先保存模型和 `tokenizer` 然后调用 `repo.push_to_hub()` 方法。注意,我们使用参数 `blocking=False` 来告诉 🤗 `Hub` 库在一个异步进程中推送。这样,在训练时,这个指令在后台将模型和 `tokenizer` 推送到 `hub`。 -这是训练循环的完整代码: +以下是完整的训练循环代码: ```py from tqdm.auto import tqdm @@ -883,7 +878,7 @@ import torch progress_bar = tqdm(range(num_training_steps)) for epoch in range(num_train_epochs): - # Training + # 训练 model.train() for batch in train_dataloader: outputs = model(**batch) @@ -895,7 +890,7 @@ for epoch in range(num_train_epochs): optimizer.zero_grad() progress_bar.update(1) - # Evaluation + # 评估 model.eval() for batch in eval_dataloader: with torch.no_grad(): @@ -904,7 +899,7 @@ for epoch in range(num_train_epochs): predictions = outputs.logits.argmax(dim=-1) labels = batch["labels"] - # Necessary to pad predictions and labels for being gathered + # 填充模型的预测和标签后才能调用 gathere() predictions = accelerator.pad_across_processes(predictions, dim=1, pad_index=-100) labels = accelerator.pad_across_processes(labels, dim=1, pad_index=-100) @@ -923,7 +918,7 @@ for epoch in range(num_train_epochs): }, ) - # Save and upload + # 保存并上传 accelerator.wait_for_everyone() unwrapped_model = accelerator.unwrap_model(model) unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) @@ -934,7 +929,7 @@ for epoch in range(num_train_epochs): ) ``` -果这是您第一次看到用 🤗 Accelerate 保存的模型,让我们花点时间检查一下它附带的三行代码: +如果这是你第一次看到使用 🤗 Accelerate 保存模型,让我们花点时间来了解一下这个过程中的三行代码: ```py accelerator.wait_for_everyone() @@ -942,21 +937,22 @@ unwrapped_model = accelerator.unwrap_model(model) unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) ``` -第一行是不言自明的:它告诉所有进程等到都处于那个阶段再继续(阻塞)。这是为了确保在保存之前,我们在每个过程中都有相同的模型。然后获取`unwrapped_model`,它是我们定义的基本模型。 -`accelerator.prepare()`方法将模型更改为在分布式训练中工作,所以它不再有`save_pretraining()`方法;`accelerator.unwrap_model()`方法将撤销该步骤。最后,我们调用`save_pretraining()`,但告诉该方法使用`accelerator.save()`而不是`torch.save()`。 +第一行是不言自明的:它告诉所有的进程先等待,直到所有的进程都处于这个阶段再继续(阻塞)。这是为了确保在保存之前,我们在每个进程中都有相同的模型。 +第二行代码用于获取 `unwrapped_model` ,它就是我们定义的基本模型。 `accelerator.prepare()` 方法会为了在分布式训练中工作而对模型进行了一些修改,所以它不再有 `save_pretraining()` 方法;使用 `accelerator.unwrap_model()` 方法可以撤销对模型的更改。 +在第三行代码中,我们调用 `save_pretraining()` ,并指定 `accelerator.save()` 作为 `save_function` 而不是默认的 `torch.save()` 。 -当完成之后,你应该有一个模型,它产生的结果与`Trainer`的结果非常相似。你可以在[hugs face-course/bert-fine - tuning -ner-accelerate](https://huggingface.co/huggingface-course/bert-finetuned-ner-accelerate)中查看我们使用这个代码训练的模型。如果你想测试训练循环的任何调整,你可以直接通过编辑上面显示的代码来实现它们! +完成这些操作后,你应该拥有一个与 `Trainer` 训练出的模型结果相当类似的模型。你可以在 [huggingface-course/bert-finetuned-ner-accelerate](https://huggingface.co/huggingface-course/bert-finetuned-ner-accelerate) 查看我们使用这些代码训练的模型。如果你想在训练循环中测试任何调整,你可以直接通过编辑上面显示的代码来实现它们! {/if} ## 使用微调模型 [[使用微调模型]] -我们已经向您展示了如何使用我们在模型中心微调的模型和推理小部件。在本地使用它 `pipeline` ,您只需要指定正确的模型标识符: +我们已经向你展示了如何使用在模型中心微调的模型和推理小部件。在本地使用 `pipeline` 来使用它非常容易,你只需要指定正确的模型标签: ```py from transformers import pipeline -# Replace this with your own checkpoint +# 将此替换为你自己的 checkpoint model_checkpoint = "huggingface-course/bert-finetuned-ner" token_classifier = pipeline( "token-classification", model=model_checkpoint, aggregation_strategy="simple" diff --git a/chapters/zh-CN/chapter7/3.mdx b/chapters/zh-CN/chapter7/3.mdx index 4b4c8232e..32cc5d563 100644 --- a/chapters/zh-CN/chapter7/3.mdx +++ b/chapters/zh-CN/chapter7/3.mdx @@ -1,6 +1,6 @@ -# 微调掩码语言模型 [[微调掩码语言模型]] +# 微调掩码语言模型(masked language model) [[微调掩码语言模型(masked language model)]] {#if fw === 'pt'} @@ -22,45 +22,43 @@ {/if} -对于许多涉及 Transformer 模型的 NLP 程序, 你可以简单地从 Hugging Face Hub 中获取一个预训练的模型, 然后直接在你的数据上对其进行微调, 以完成手头的任务。只要用于预训练的语料库与用于微调的语料库没有太大区别, 迁移学习通常会产生很好的结果。 +对于许多涉及 Transformer 模型的 NLP 任务,你可以简单地从 Hugging Face Hub 中获取一个预训练的模型,然后直接在你的数据上对其进行微调,以完成手头的任务。只要用于预训练的语料库与用于微调的语料库没有太大区别,迁移学习通常会产生很好的结果。 -但是, 在某些情况下, 你需要先微调数据上的语言模型, 然后再训练特定于任务的head。例如, 如果您的数据集包含法律合同或科学文章, 像 BERT 这样的普通 Transformer 模型通常会将您语料库中的特定领域词视为稀有标记, 结果性能可能不尽如人意。通过在域内数据上微调语言模型, 你可以提高许多下游任务的性能, 这意味着您通常只需执行一次此步骤! +但是,在某些情况下,你可能需要先在你的数据上微调语言模型,然后再训练特定于任务的 head。例如,如果你的数据集包含法律合同或科学文章,像 BERT 这样的普通 Transformer 模型通常会将你语料库中的特定领域词视为稀有 tokens ,导致性能可能不尽如人意。通过在特定领域内数据上微调语言模型,你可以提高许多下游任务的性能,这意味着你通常只需执行一次此步骤! -这种在域内数据上微调预训练语言模型的过程通常称为 _领域适应_。 它于 2018 年由 [ULMFiT](https://arxiv.org/abs/1801.06146)推广, 这是使迁移学习真正适用于 NLP 的首批神经架构之一 (基于 LSTM)。 下图显示了使用 ULMFiT 进行域自适应的示例; 在本节中, 我们将做类似的事情, 但使用的是 Transformer 而不是 LSTM! +这种在特定领域内数据上微调预训练语言模型的过程通常称为 `领域适应(domain adaptation)` 。它于 2018 年由 [ULMFiT](https://arxiv.org/abs/1801.06146) 推广, NLP 的首批神经架构之一 (基于 LSTM)。下图显示了使用 ULMFiT这是使迁移学习真正适用于 进行领域自适应的示例;在本节中,我们将做类似的事情,但我们将使用 Transformer 而不是 LSTM!
ULMFiT.
-在本节结束时, 你将在Hub上拥有一个[掩码语言模型(masked language model)](https://huggingface.co/huggingface-course/distilbert-base-uncased-finetuned-imdb?text=This+is+a+great+%5BMASK%5D.), 该模型可以自动完成句子, 如下所示: +在本节结束时,你将在 Hub 上拥有一个 [掩码语言模型(masked language model)](https://huggingface.co/huggingface-course/distilbert-base-uncased-finetuned-imdb?text=This+is+a+great+%5BMASK%5D.) ,该模型可以自动补全句子,如下所示: - + -让我们开始吧! +让我们开始吧! -🙋 如果您对“掩码语言建模”和“预训练模型”这两个术语感到陌生, 请查看[第一章](/course/chapter1), 我们在其中解释了所有这些核心概念, 并附有视频! +🙋 如果你对“掩码语言建模”和“预训练模型”这两个术语感到陌生,请回顾 [第一章](/course/chapter1) ,我们在其中解释了所有这些核心概念,并附有视频! ## 选择用于掩码语言建模的预训练模型 [[选择用于掩码语言建模的预训练模型]] -首先, 让我们为掩码语言建模选择一个合适的预训练模型。如以下屏幕截图所示, 你可以通过在[Hugging Face Hub](https://huggingface.co/models?pipeline_tag=fill-mask&sort=downloads)上应用"Fill-Mask"过滤器找到: +首先,让我们为掩码语言建模选择一个合适的预训练模型。如以下屏幕截图所示,你可以通过在 [Hugging Face Hub](https://huggingface.co/models?pipeline_tag=fill-mask&sort=downloads) 上选择“Fill-Mask”过滤器:
Hub models.
- -尽管 BERT 和 RoBERTa 系列模型的下载量最大, 但我们将使用名为 [DistilBERT](https://huggingface.co/distilbert-base-uncased)的模型。 -可以更快地训练, 而下游性能几乎没有损失。这个模型使用一种称为[_知识蒸馏_](https://en.wikipedia.org/wiki/Knowledge_distillation)的特殊技术进行训练, 其中使用像 BERT 这样的大型“教师模型”来指导参数少得多的“学生模型”的训练。在本节中对知识蒸馏细节的解释会使我们离题太远, 但如果你有兴趣, 可以阅读所有相关内容 [_Natural Language Processing with Transformers_](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) (俗称Transformers教科书)。 +尽管 BERT 和 RoBERTa 系列模型的下载量最大,但我们将使用名为 [DistilBERT](https://huggingface.co/distilbert-base-uncased) 的模型。它可以更快地训练,而且对下游性能几乎没有损失。它使用了一种称为 [`知识蒸馏(knowledge distillation)`](https://en.wikipedia.org/wiki/Knowledge_distillation) 的特殊技术进行训练,其中使用像 BERT 这样的大型“教师模型”来指导参数少得多的“学生模型”的训练。在本节中对知识蒸馏进行详细解释会使我们偏离本节主题太远,但如果你有兴趣,可以阅读 [`使用 Transformers 进行自然语言处理(Natural Language Processing with Transformers)`](https://www.oreilly.com/library/view/natural-language-processing/9781098136789/) (俗称 Transformers 教科书)中知识蒸馏的相关内容。 {#if fw === 'pt'} -让我们继续, 使用 `AutoModelForMaskedLM` 类下载 DistilBERT: +让我们继续,我们可以使用 `AutoModelForMaskedLM` 类下载 DistilBERT: ```python from transformers import AutoModelForMaskedLM @@ -69,7 +67,7 @@ model_checkpoint = "distilbert-base-uncased" model = AutoModelForMaskedLM.from_pretrained(model_checkpoint) ``` -我们可以通过调用 `num_parameters()` 方法查看模型有多少参数: +然后,我们可以通过调用 `num_parameters()` 方法查看模型有多少参数: ```python distilbert_num_parameters = model.num_parameters() / 1_000_000 @@ -84,7 +82,7 @@ print(f"'>>> BERT number of parameters: 110M'") {:else} -让我们继续, 使用 `AutoModelForMaskedLM` 类下载 DistilBERT: +让我们继续,我们可以使用 `TFAutoModelForMaskedLM` 类下载 DistilBERT: ```python from transformers import TFAutoModelForMaskedLM @@ -93,25 +91,29 @@ model_checkpoint = "distilbert-base-uncased" model = TFAutoModelForMaskedLM.from_pretrained(model_checkpoint) ``` -我们可以通过调用 `summary()` 方法查看模型有多少参数: +然后,我们可以通过调用 `summary()` 方法查看模型有多少参数: ```python -model(model.dummy_inputs) # Build the model +model(model.dummy_inputs) # 构建模型 model.summary() ``` ```python out Model: "tf_distil_bert_for_masked_lm" _________________________________________________________________ -Layer (type) Output Shape Param # + 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 + distilbert (TFDistilBertMai multiple 66362880 + nLayer) + + vocab_transform (Dense) multiple 590592 + + vocab_layer_norm (LayerNorm multiple 1536 + alization) + + vocab_projector (TFDistilBe multiple 23866170 + rtLMHead) + ================================================================= Total params: 66,985,530 Trainable params: 66,985,530 @@ -121,13 +123,13 @@ _________________________________________________________________ {/if} -DistilBERT 大约有 6700 万个参数, 大约比 BERT 基本模型小两倍, 这大致意味着训练的速度提高了两倍 -- 非常棒! 现在让我们看看这个模型预测什么样的标记最有可能完成一小部分文本: +DistilBERT 大约有 6700 万个参数,大约只有 BERT base 模型的二分之一,这大致意味着训练的速度可以提高两倍 —— 非常棒!现在让我们看看对于下面的一小部分文本,这个模型最有可能预测什么: ```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 的标记器来生成模型的输入, 所以让我们也从 Hub 下载它: +作为人类,我们可以想象 `[MASK]` token 有很多可能性,例如 “day”、“ride” 或者 “painting”。对于预训练模型,预测取决于模型所训练的语料库,因为它会学习获取数据中存在的语料统计分布。与 BERT 一样,DistilBERT 在 [English Wikipedia](https://huggingface.co/datasets/wikipedia) 和 [BookCorpus](https://huggingface.co/datasets/bookcorpus) 数据集上进行预训练,所以我们猜想模型对 `[MASK]` 的预测能够反映这些领域。为了预测 `[MASK]` ,我们需要 DistilBERT 的 tokenizer 来处理输入,所以让我们也从 Hub 下载它: ```python from transformers import AutoTokenizer @@ -135,7 +137,7 @@ from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) ``` -使用标记器和模型, 我们现在可以将我们的文本示例传递给模型, 提取 logits, 并打印出前 5 个候选: +有了 tokenizer 和模型,我们现在可以将我们的示例文本传递给模型,提取 logits,并打印出前 5 个候选词: {#if fw === 'pt'} @@ -144,10 +146,10 @@ import torch inputs = tokenizer(text, return_tensors="pt") token_logits = model(**inputs).logits -# Find the location of [MASK] and extract its logits +# 找到 [MASK] 的位置并提取其 logits mask_token_index = torch.where(inputs["input_ids"] == tokenizer.mask_token_id)[1] mask_token_logits = token_logits[0, mask_token_index, :] -# Pick the [MASK] candidates with the highest logits +# 选择具有最高 logits 的 [MASK] 候选词 top_5_tokens = torch.topk(mask_token_logits, 5, dim=1).indices[0].tolist() for token in top_5_tokens: @@ -162,11 +164,11 @@ import tensorflow as tf inputs = tokenizer(text, return_tensors="np") token_logits = model(**inputs).logits -# Find the location of [MASK] and extract its logits +# 找到 [MASK] 的位置并提取其 logits mask_token_index = np.argwhere(inputs["input_ids"] == tokenizer.mask_token_id)[0, 1] mask_token_logits = token_logits[0, mask_token_index, :] -# Pick the [MASK] candidates with the highest logits -# We negate the array before argsort to get the largest, not the smallest, logits +# 选择具有最高 logits 的 [MASK] 候选词 +# 通过在 argsort 前对数组取负,来得到最大的 logits top_5_tokens = np.argsort(-mask_token_logits)[:5].tolist() for token in top_5_tokens: @@ -183,12 +185,13 @@ for token in top_5_tokens: '>>> This is a great feat.' ``` -我们可以从输出中看到模型的预测是指日常用语, 鉴于英语维基百科的基础, 这也许并不奇怪。让我们看看我们如何将这个领域改变为更小众的东西 -- 高度两极分化的电影评论! - +我们可以从输出中看到,模型的预测的是日常术语,虑到模型训练的语料数据主要来源于维基百科,这并不奇怪。现在让我们看看如何将这个领域改变成稍微更加独特——高度两极分化的电影评论! ## 数据集 [[数据集]] -为了展示域适配, 我们将使用著名的[大型电影评论数据集(Large Movie Review Dataset)](https://huggingface.co/datasets/imdb) (或者简称为IMDb), 这是一个电影评论语料库, 通常用于对情感分析模型进行基准测试。通过在这个语料库上对 DistilBERT 进行微调, 我们预计语言模型将根据维基百科的事实数据调整其词汇表, 这些数据已经预先训练到电影评论中更主观的元素。我们可以使用🤗 Datasets中的`load_dataset()`函数从Hugging Face 中获取数据: +为了展示领域适应性,我们将使用来自IMDB的 [大型电影评论数据集(Large Movie Review Dataset)](https://huggingface.co/datasets/imdb),这是一个电影评论语料库,通常用于对情感分析模型进行基准测试。通过在这个语料库上对 DistilBERT 进行微调,我们期望语言模型会从其预训练的维基百科的事实性数据,适应到更主观的电影评论的领域。 + +首先,我们可以使用🤗 Datasets 中的 `load_dataset()` 函数从 Hugging Face 中获取数据: ```python from datasets import load_dataset @@ -214,7 +217,7 @@ DatasetDict({ }) ``` -我们可以看到 `train` 和 `test` 每个拆分包含 25,000 条评论, 而有一个未标记的拆分称为 `unsupervised` 包含 50,000 条评论。让我们看一些示例, 以了解我们正在处理的文本类型。正如我们在本课程的前几章中所做的那样, 我们将链接 `Dataset.shuffle()` 和 `Dataset.select()` 函数创建随机样本: +我们可以看到 `train` 和 `test` 分别包含了 25,000 条评论,还有一个没有的标签的 `unsupervised(无监督)` 部分包含 50,000 条评论。接下来让我们从里面取一些样本,来了解一下我们正在处理的文本的特点。正如我们在本课程的前几章中所做的那样,我们将把 `Dataset.shuffle()` 函数链接到 `Dataset.select()` 函数创建随机样本: ```python sample = imdb_dataset["train"].shuffle(seed=42).select(range(3)) @@ -236,23 +239,23 @@ for row in sample: '>>> Label: 1' ``` -是的, 这些肯定是电影评论, 如果你年龄足够,你甚至可能会理解上次评论中关于拥有 VHS 版本的评论😜! 虽然我们不需要语言建模的标签, 但我们已经可以看到 `0` 表示负面评论, 而 `1` 对应正面。 +是的,这些肯定是电影评论,如果你年龄足够大,你甚至可能会理解上述评论中关于拥有 VHS (一种古老的盒式摄像机格式)版本的评论😜!虽然语言模型不需要预先标注好的标签,但我们已经可以看到数据集其实包含了标签, `0` 代表负面评论, `1` 代表正面评论。 -✏️ **试试看!** 创建 `无监督` 拆分的随机样本, 并验证标签既不是 `0` 也不是 `1`。在此过程中, 你还可以检查 `train` 和 `test` 拆分中的标签是否确实为 `0` 或 `1` -- 这是每个 NLP 从业者在新项目开始时都应该执行的有用的健全性检查! +✏️ **试一试!** 创建一个 `unsupervised` 部分的随机样本,并验证其标签既不是 `0` 也不是 `1` 。或者,你也可以检查 `train` 和 `test` 部分的标签确实是 `0` 或 `1` —— 每个 NLP 实践者在开始新项目时都应该对数据标注进行的有用的、合理的检查! -现在我们已经快速浏览了数据, 让我们深入研究为掩码语言建模做准备。正如我们将看到的, 与我们在[第三章](/course/chapter3)中看到的序列分类任务相比, 还需要采取一些额外的步骤。让我们继续! +现在我们已经快速浏览了一下数据,接下来我们要深入准备这些数据以供进行掩码语言建模。如我们所见,与我们在 [第三章](https://chat.openai.com/course/chapter3) 看到的序列分类任务相比,这里需要采取一些额外的步骤。让我们开始吧! ## 预处理数据 [[预处理数据]] -对于自回归和掩码语言建模, 一个常见的预处理步骤是连接所有示例, 然后将整个语料库拆分为相同大小的块。 这与我们通常的方法完全不同, 我们只是简单地标记单个示例。为什么要将所有内容连接在一起? 原因是单个示例如果太长可能会被截断, 这将导致丢失可能对语言建模任务有用的信息! +对于自回归和掩码语言建模,常见的预处理步骤是将所有的文本拼接起来,然后再将整个语料库切割为相同大小的块。这与我们之前的做法有很大的不同,我们之前只是对单个的示例进行 tokenize。为什么要将所有的示例连接在一起呢?原因是如果单个示例太长,可能会被截断,这会导致我们失去可能对语言建模任务有用的信息! -因此, 我们将像往常一样首先标记我们的语料库, 但是 _没有_ 在我们的标记器中设置 `truncation=True` 选项。 我们还将获取可用的单词 ID ((如果我们使用快速标记器, 它们是可用的, 如 [第六章](/course/chapter6/3)中所述), 因为我们稍后将需要它们来进行全字屏蔽。我们将把它包装在一个简单的函数中, 当我们在做的时候, 我们将删除 `text` 和 `label` 列, 因为我们不再需要它们: +因此,我们首先会像往常一样对语料库进行 tokenize 处理,但是不在 tokenizer 中设置 `truncation=True` 选项。如果我们有可以使用快速 tokenizer(如 [第六章](/course/chapter6/3) 中所述),除此之外,我们还需要获取单词的 `ID`,因为后面我们需要用到它们来进行全词掩码。最后我们将把这个过程封装在一个简单的函数中,并删除 `text` 和 `label` 列,因为我们不再需要它们。 ```python def tokenize_function(examples): @@ -262,7 +265,7 @@ def tokenize_function(examples): return result -# Use batched=True to activate fast multithreading! +# 使用 batched=True 来激活快速多线程! tokenized_datasets = imdb_dataset.map( tokenize_function, batched=True, remove_columns=["text", "label"] ) @@ -286,9 +289,9 @@ DatasetDict({ }) ``` -由于 DistilBERT 是一个类似 BERT 的模型, 我们可以看到编码文本由我们在其他章节中看到的 `input_ids` 和 `attention_mask` 组成, 以及我们添加的 `word_ids`。 +由于 DistilBERT 是一个类似 BERT 的模型,我们可以看到编码后的文本包含了我们在之前章节中看到的 `input_ids` 和 `attention_mask` ,以及我们添加的 `word_ids` 。 -现在我们已经标记了我们的电影评论, 下一步是将它们组合在一起并将结果分成块。 但是这些块应该有多大? 这最终将取决于你可用的 GPU 内存量, 但一个好的起点是查看模型的最大上下文大小是多少。这可以通过检查标记器的 `model_max_length` 属性来判断: +现在我们已经对电影评论进行了 `tokenize`,下一步是将它们全部组合在一起并将结果分割成块。但是,这些块应该有多大呢?这最终将取决于你可以使用的显存大小,但一个好的起点是查看模型的最大上下文大小。这可以在 tokenizer 的 `model_max_length` 属性中找到: ```python tokenizer.model_max_length @@ -298,15 +301,15 @@ tokenizer.model_max_length 512 ``` -该值来自于与检查点相关联的 *tokenizer_config.json* 文件; 在这种情况下, 我们可以看到上下文大小是 512 个标记, 就像 BERT 一样。 +该值来自于与 checkpoint 相关联的 `tokenizer_config.json` 文件;在我们的例子中,我们可以看到上下文大小是 512 个 `tokens` 与 BERT 模型一样。 -✏️ **试试看!** 一些 Transformer 模型, 例如 [BigBird](https://huggingface.co/google/bigbird-roberta-base) 和 [Longformer](hf.co/allenai/longformer-base-4096), 它们具有比BERT和其他早期Transformer模型更长的上下文长度。为这些检查点之一实例化标记器, 并验证 `model_max_length` 是否与模型卡上引用的内容一致。 +✏️ **试试看!** 一些 Transformer 模型,例如 [BigBird](https://huggingface.co/google/bigbird-roberta-base) 和 [Longformer](hf.co/allenai/longformer-base-4096) 具有比 BERT 和其他早期 Transformer 模型更长的上下文长度。选择一个 `checkpoint` 来实例化 `tokenizer` 并验证 `model_max_length` 是否与模型卡上标注的大小一致。 -因此, 以便在像Google Colab 那样的 GPU 上运行我们的实验, 我们将选择可以放入内存的更小一些的东西: +因此,为了可以在像 Google Colab 那样的 GPU 上运行我们的实验,我们会选择一个稍小一点、可以放入内存中的分块大小: ```python chunk_size = 128 @@ -314,14 +317,14 @@ chunk_size = 128 -请注意, 在实际场景中使用较小的块大小可能是有害的, 因此你应该使用与将应用模型的用例相对应的大小。 +注意,在实际应用场景中,使用小的块可能会有丢失长句子之间的语义信息从而对最终模型的性能产生不利的影响,所以如果显存条件允许的话,你应该选择一个与你将要使用模型的相匹配的大小。 -有趣的来了。为了展示串联是如何工作的, 让我们从我们的标记化训练集中取一些评论并打印出每个评论的标记数量: +现在来到了最有趣的部分。为了展示如何把这些示例连接在一,我们从分词后的训练集中取出几个评论,并打印出每个评论的 token 数量: ```python -# Slicing produces a list of lists for each feature +# 切片会为每个特征生成一个列表的列表 tokenized_samples = tokenized_datasets["train"][:3] for idx, sample in enumerate(tokenized_samples["input_ids"]): @@ -334,7 +337,7 @@ for idx, sample in enumerate(tokenized_samples["input_ids"]): '>>> Review 2 length: 192' ``` -然后我们可以用一个简单的字典理解来连接所有例子, 如下所示: +然后,我们可以用一个简单的字典推导式将所有这些示例连接在一起,如下所示: ```python concatenated_examples = { @@ -348,7 +351,7 @@ print(f"'>>> Concatenated reviews length: {total_length}'") '>>> Concatenated reviews length: 951' ``` -很棒, 总长度检查出来了 -- 现在, 让我们将连接的评论拆分为大小为 `block_size` 的块。为此, 我们迭代了 `concatenated_examples` 中的特征, 并使用列表理解来创建每个特征的切片。结果是每个特征的块字典: +很棒,总长度计算出来了 —— 现在,让我们将连接的评论拆分为大小为 `chunk_size` 的块。为此,我们迭代了 `concatenated_examples` 中的特征,并使用列表推导式为每个特征分块。结果是一个字典,键是特征的名称,值是对应值经过分块的列表: ```python chunks = { @@ -371,34 +374,34 @@ for chunk in chunks["input_ids"]: '>>> Chunk length: 55' ``` -正如你在这个例子中看到的, 最后一个块通常会小于最大块大小。有两种主要的策略来处理这个问题: +正如你在这个例子中看到的,最后一个块通常会小于所设置的分块的大小。有两种常见的策略来处理这个问题: -* 如果最后一个块小于 `chunk_size`, 请删除它。 -* 填充最后一个块, 直到其长度等于 `chunk_size`。 +* 如果最后一个块小于 `chunk_size` ,就丢弃。 +* 填充最后一个块,直到其长度等于 `chunk_size` 。 -我们将在这里采用第一种方法, 因此让我们将上述所有逻辑包装在一个函数中, 我们可以将其应用于我们的标记化数据集: +我们将在这里采用第一种方法,最后让我们将上述所有逻辑包装在一个函数中,以便我们可以将其应用于我们的已分词数据集上: ```python def group_texts(examples): - # Concatenate all texts + # 拼接所有的文本 concatenated_examples = {k: sum(examples[k], []) for k in examples.keys()} - # Compute length of concatenated texts + # 计算拼接文本的长度 total_length = len(concatenated_examples[list(examples.keys())[0]]) - # We drop the last chunk if it's smaller than chunk_size + # 如果最后一个块小于 chunk_size,我们将其丢弃 total_length = (total_length // chunk_size) * chunk_size - # Split by chunks of 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() } - # Create a new labels column + # 创建一个新的 labels 列 result["labels"] = result["input_ids"].copy() return result ``` -注意, 在 `group_texts()` 的最后一步中, 我们创建了一个新的 `labels` 列, 它是 `input_ids` 列的副本。我们很快就会看到, 这是因为在掩码语言建模中, 目标是预测输入批次中随机掩码的标记, 并通过创建一个 `labels` 列, 们为我们的语言模型提供了基础事实以供学习。 +注意,在 `group_texts()` 的最后一步,我们创建了一个新的 `labels` 列,它是通过复制 `input_ids` 列形成的。这是因为在掩码语言模型的目标是预测输入中随机遮住(Masked)的 token,我们保存了让我们的语言模型从中学习 `[Mask]` 的答案。 -现在, 让我们使用我们可信赖的 `Dataset.map()` 函数将 `group_texts()` 应用到我们的标记化数据集: +现在,让我们使用我们强大的 `Dataset.map()` 函数将 `group_texts()` 应用到我们的已分词数据集上: ```python lm_datasets = tokenized_datasets.map(group_texts, batched=True) @@ -422,7 +425,7 @@ DatasetDict({ }) ``` -你可以看到, 对文本进行分组, 然后对文本进行分块, 产生的示例比我们最初的 25,000 用于 `train`和 `test` 拆分的示例多得多。那是因为我们现在有了涉及 _连续标记_ 的示例, 这些示例跨越了原始语料库中的多个示例。你可以通过在其中一个块中查找特殊的 `[SEP]` 和 `[CLS]` 标记来明确的看到这一点: +通过对文本进行分块,我们得到了比原来的训练集和测试集的 25000 个例子多得多的评论数据。这是因为我们现在有了涉及跨越原始语料库中多个例子的连续标记的例子。你可以通过在其中一个块中查找特殊的 `[SEP]` 和 `[CLS]` tokens 来清晰地看到这一点: ```python tokenizer.decode(lm_datasets["train"][1]["input_ids"]) @@ -432,7 +435,7 @@ tokenizer.decode(lm_datasets["train"][1]["input_ids"]) ".... 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"]) @@ -442,11 +445,11 @@ tokenizer.decode(lm_datasets["train"][1]["labels"]) ".... 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]` 标记! 让我们看看如何使用特殊的数据整理器在微调期间即时执行此操作。 +正如我们上面的 `group_texts()` 函数所预期的那样,这看起来与解码的 `input_ids` 完全相同 —— 但是要怎么样才能让我们的的模型可以学习到一些东西呢?我们缺少一个关键的步骤:在输入中随机插入 `[MASK]` token!让我们看看如何在微调期间使用特殊的数据整理器来实时地完成这个步骤。 -## 使用 `Trainer` API 微调DistilBERT [[使用 `Trainer` API 微调DistilBERT]] +## 使用 `Trainer` API 微调 DistilBERT [[使用 `Trainer` API 微调 DistilBERT]] -微调屏蔽语言模型几乎与微调序列分类模型相同, 就像我们在 [第三章](/course/chapter3)所作的那样。 唯一的区别是我们需要一个特殊的数据整理器, 它可以随机屏蔽每批文本中的一些标记。幸运的是, 🤗 Transformers 为这项任务准备了专用的 `DataCollatorForLanguageModeling` 。我们只需要将它转递给标记器和一个 `mlm_probability` 参数, 该参数指定要屏蔽的标记的分数。我们将选择 15%, 这是 BERT 使用的数量也是文献中的常见选择: +微调掩码语言模型几乎与微调序列分类模型相同,就像我们在 [第三章](/course/chapter3) 所做的那样。唯一的区别是我们需要一个特殊的数据整理器,它可以随机屏蔽每批文本中的一些 tokens。幸运的是,🤗 Transformers 为这项任务准备了专用的 `DataCollatorForLanguageModeling` 。我们只需要将 tokenizer 和一个 `mlm_probability` 参数(掩盖 tokens 的比例)传递给它。在这里我们将 `mlm_probability` 参数设置为 15%,这是 `BERT` 默认的数量,也是文献中最常见的选择。 ```python from transformers import DataCollatorForLanguageModeling @@ -454,7 +457,7 @@ from transformers import DataCollatorForLanguageModeling data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm_probability=0.15) ``` -要了解随机掩码的工作原理, 让我们向数据整理器提供一些示例。由于它需要一个 `dict` 的列表, 其中每个 `dict` 表示单个连续文本块, 我们首先迭代数据集, 然后再将批次提供给整理器。我们删除了这个数据整理器的 `"word_ids"` 键, 因为它不需要它: +为了了解随机掩码数据整理器的工作原理,让我们把一些例子输入到数据整理器。由于数据整理器期望接收一个字典列表,其中每个字典中存储一段连续文本的块,所以我们首先遍历数据集取出来一些样本数据,然后将样本数据输入到整理器。在输入到数据整理器之间,我们删除了 `word_ids` 这个键,因为它不需要这个键。 ```python samples = [lm_datasets["train"][i] for i in range(2)] @@ -471,21 +474,20 @@ for chunk in data_collator(samples)["input_ids"]: '>>> .... 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]` 插入! +很棒,成功了!我们可以看到, `[MASK]` tokens 已随机插入我们文本中的不同位置。这些将是我们的模型在训练期间必须预测的 tokens —— 数据整理器的美妙之处在于,它会在每个 batch 中随机插入 `[MASK]` ! -✏️ **试试看!** 多次运行上面的代码片段, 看看随机屏蔽发生在你眼前! 还要将 `tokenizer.decode()` 方法替换为 `tokenizer.convert_ids_to_tokens()` 以查看有时会屏蔽给定单词中的单个标记, 而不是其他标记。 +✏️ **试一试!** 多运行上面的代码片段几次,亲眼看看随机遮蔽的效果!也可以用 `tokenizer.convert_ids_to_tokens()` 替换 `tokenizer.decode()` 方法,看看只把一个给定单词的单个 token 遮蔽,而保持这个单词其他 tokens 不变的效果。 {#if fw === 'pt'} -随机掩码的一个副作用是, 当使用 `Trainer` 时, 我们的评估指标将不是确定性的, 因为我们对训练集和测试集使用相同的数据整理器。稍后我们会看到, 当我们使用 🤗 Accelerate 进行微调时, 我们将如何利用自定义评估循环的灵活性来冻结随机性。 - +随机掩码的一个缺点是,当使用 `Trainer` 时,每次计算出来的评估结果会有些许不同,即使我们会对训练集和测试集使用相同的数据整理器。稍后,我们在学习使用 🤗 Accelerate 进行微调时, 就会看到如何利用灵活的自定义评估循环的来冻结随机性。 {/if} -在为掩码语言建模训练模型时, 可以使用的一种技术是将整个单词一起屏蔽, 而不仅仅是单个标记。这种方法称为 _全词屏蔽_。 如果我们想使用全词屏蔽, 我们需要自己构建一个数据整理器。数据整理器只是一个函数, 它接受一个样本列表并将它们转换为一个批次, 所以现在让我们这样做吧! 我们将使用之前计算的单词 ID 在单词索引和相应标记之间进行映射, 然后随机决定要屏蔽哪些单词并将该屏蔽应用于输入。请注意, 除了与掩码对应的标签外, 所有的标签均为 `-100`。 +在为掩码语言建模训练模型时,不仅仅可以遮蔽单个 `token`,还可以一次遮蔽整个单词的所有 `token`,这种方法被称为全词屏蔽(whole word masking)。如果我们想使用全词屏蔽(whole word masking),我们就需要自己构建一个数据整理器。数据整理器的核心是一个函数,它接受一个样本列表并将它们转换为一个 `batch`,所以现在让我们这样做吧!我们将使用先前计算的`word ID`,构建一个单词索引和相应 `token` 之间的映射,然后随机决定遮蔽哪些单词,并使用这种方法对输入进行遮蔽。请注意,除了与掩码对应的标签外,所有其他的标签均应该设置为 `-100` 。 {#if fw === 'pt'} @@ -502,7 +504,7 @@ def whole_word_masking_data_collator(features): for feature in features: word_ids = feature.pop("word_ids") - # Create a map between words and corresponding token indices + # 创建一个单词与对应 token 索引之间的映射 mapping = collections.defaultdict(list) current_word_index = -1 current_word = None @@ -513,7 +515,7 @@ def whole_word_masking_data_collator(features): current_word_index += 1 mapping[current_word_index].append(idx) - # Randomly mask words + # 随机遮蔽单词 mask = np.random.binomial(1, wwm_probability, (len(mapping),)) input_ids = feature["input_ids"] labels = feature["labels"] @@ -543,7 +545,7 @@ def whole_word_masking_data_collator(features): for feature in features: word_ids = feature.pop("word_ids") - # Create a map between words and corresponding token indices + # 创建一个单词与对应 token 索引之间的映射 mapping = collections.defaultdict(list) current_word_index = -1 current_word = None @@ -554,7 +556,7 @@ def whole_word_masking_data_collator(features): current_word_index += 1 mapping[current_word_index].append(idx) - # Randomly mask words + # 随机遮蔽单词 mask = np.random.binomial(1, wwm_probability, (len(mapping),)) input_ids = feature["input_ids"] labels = feature["labels"] @@ -571,7 +573,7 @@ def whole_word_masking_data_collator(features): {/if} -Next, we can try it on the same samples as before: +接下来,我们可以在之前的样本上试试它: ```py samples = [lm_datasets["train"][i] for i in range(2)] @@ -589,11 +591,11 @@ for chunk in batch["input_ids"]: -✏️ **试试看!** 多次运行上面的代码片段, 看看随机屏蔽发生在你眼前! 还要将 `tokenizer.decode()` 方法替换为 `tokenizer.convert_ids_to_tokens()` 以查看来自给定单词的标记始终被屏蔽在一起。 +✏️ **试试看!** 多次运行上面的代码片段,亲眼看看随机遮蔽的效果!也可以将 `tokenizer.decode()` 方法替换为 `tokenizer.convert_ids_to_tokens()` ,可以观察到给定单词的所有 tokens 总是被一起遮蔽。 -现在我们有两个数据整理器, 其余的微调步骤是标准的。如果您没有足够幸运地获得神话般的 P100 GPU 😭, 在 Google Colab 上进行训练可能需要一段时间, 因此我们将首先将训练集的大小缩减为几千个示例。别担心, 我们仍然会得到一个相当不错的语言模型! 在 🤗 Datasets 中快速下采样数据集的方法是通过我们在 [第五章](/course/chapter5) 中看到的 `Dataset.train_test_split()` 函数: +现在我们有了两个数据整理器,剩下的微调步骤与其他任务类似都是标准的。如果你在 Google Colab 上运行并且没有幸运地分配到神秘的 P100 GPU😭,那么训练可能会需要一些时间,所以我们首先将训练集的大小减小到几千个例子。不用担心,我们仍然可以得到一个相当不错的语言模型!在 🤗 Datasets 中快速筛选数据集的方法是使用我们在 [第五章](/course/chapter5) 中看到的 `Dataset.train_test_split()` 函数: ```python train_size = 10_000 @@ -618,7 +620,7 @@ DatasetDict({ }) ``` -这会自动创建新的 `train` 和 `test` 拆分, 训练集大小设置为 10,000 个示例, 验证设置为其中的 10% -- 如果你有一个强大的 GPU, 可以随意增加它! 我们需要做的下一件事是登录 Hugging Face Hub。如果你在笔记本中运行此代码, 则可以使用以下实用程序函数执行此操作: +运行上述代码会自动创建新的 `train` 和 `test` 数据集,训练集大小为 10,000 个示例,验证的大小是训练集的 10% —— 如果你有一个强大的 GPU,可以自行增加这个比例!我们接下来要做的事情是登录 Hugging Face Hub。如果你在 Notebook 中运行这段代码,你可以通过以下的工具函数进行登录: ```python from huggingface_hub import notebook_login @@ -626,17 +628,17 @@ from huggingface_hub import notebook_login notebook_login() ``` -这将显示一个小部件, 你可以在其中输入你的凭据。或者, 你可以运行: +它将显示一个小部件,在其中你可以输入你的账号和密码进行登陆。或者,你也可以在你最喜欢的终端中输入指令: ``` huggingface-cli login ``` -在你最喜欢的终端中登录。 +然后在那里登录。 {#if fw === 'tf'} -登录后,我们可以创建我们的“tf.data”数据集。 为此,我们将使用 `prepare_tf_dataset()` 方法,该方法使用我们的模型自动推断哪些列应进入数据集。 如果您想准确控制要使用的列,可以改用“Dataset.to_tf_dataset()”方法。 为了简单起见,我们在这里只使用标准数据整理器,但您也可以尝试整个单词屏蔽整理器并将结果作为练习进行比较: +登录后,我们可以创建我们的 `tf.data` 数据集。我们将使用 `prepare_tf_dataset()` 方法,该方法会根据所使用的模型自动推断哪些列应该存入数据集。如果你想准确控制要使用的列,可以改用 `Dataset.to_tf_dataset()` 方法。为了简单起见,我们在这里只使用标准数据整理器,但你也可以尝试全词屏蔽(whole word masking)数据整理器,并作为一个练习对比一下效果: ```python tf_train_dataset = model.prepare_tf_dataset( @@ -653,10 +655,9 @@ tf_eval_dataset = model.prepare_tf_dataset( batch_size=32, ) ``` +接下来我们使用🤗 Transformers 库的 `create_optimizer()` 函数来设置超参数并编译模型,该函数提供了一个带有线性学习率衰减的 `AdamW` 优化器。在 `compile()` 的参数中我们没有指定损失函数,这代表使用模型内置的默认损失函数,然后我们将训练精度设置为`mixed_float16` 。注意,如果你使用的是 Colab GPU 或者其他不支持 float16 加速的 GPU,你可能应该注释掉这一行。 -接下来, 我们设置训练超参数并编译模型。我们使用 🤗 Transformers 库中的 `create_optimizer()` 函数, 它提供了一个具有线性学习率衰减的 `AdamW` 优化器。我们还使用了模型内置的损失, 当没有损失被指定为 `compile()` 参数是, 这是默认值, 并且我们将训练精度设置为 `"mixed_float16"`。请注意,如果你使用的是Colab GPU或其他不支持float16加速的GPU, 你可能应该注释掉这一行。 - -此外, 我们还设置了一个 `PushToHubCallback`, 它将在每个epoch后将模型保存到Hub。你可以使用 `hub_model_id` 参数指定你想要推送到的存储库的名称 (特别是你将必须使用该参数来推送到组织)。例如, 为了将模型推送到 [`huggingface-course` organization](https://huggingface.co/huggingface-course), 我们添加了 `hub_model_id="huggingface-course/distilbert-finetuned-imdb"`。默认情况下, 使用的存储库将位于你的命名空间中, 并以你设置的输出目录命名, 因此在我们的示例中, 它将是 `"lewtun/distilbert-finetuned-imdb"`。 +另外,我们还设置了一个 `PushToHubCallback` ,它将在每个 epoch 后将模型保存到 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 @@ -672,7 +673,7 @@ optimizer, schedule = create_optimizer( ) model.compile(optimizer=optimizer) -# Train in mixed-precision float16 +# 使用 float16 精度进行混合精度训练 tf.keras.mixed_precision.set_global_policy("mixed_float16") model_name = model_checkpoint.split("/")[-1] @@ -681,17 +682,17 @@ callback = PushToHubCallback( ) ``` -我们现在已经准备好运行 `model.fit()` 了 -- 但在此之前, 让我们先简单地看看 _perplexity_, 它是评估语言模型性能的常用指标。 +我们现在已经准备好运行 `model.fit()` 了 —— 但在此之前,让我们先简单了解一下 `困惑度(perplexity)` ,它是一种常用的评估语言模型性能的指标。 {:else} -登陆后, 我们可以指定 `Trainer` 参数: +登陆后,我们可以指定 `Trainer` 的参数: ```python from transformers import TrainingArguments batch_size = 64 -# Show the training loss with every epoch +# 在每个 epoch 输出训练的 loss logging_steps = len(downsampled_dataset["train"]) // batch_size model_name = model_checkpoint.split("/")[-1] @@ -709,11 +710,11 @@ training_args = TrainingArguments( ) ``` -在这里, 我们调整了一些默认选项, 包括 `logging_steps` , 以确保我们跟踪每个epoch的训练损失。我们还使用了 `fp16=True` 来实现混合精度训练, 这给我们带来了另一个速度提升。默认情况下, `Trainer` 将删除不属于模型的 `forward()` 方法的列。这意味着, 如果你使用整个单词屏蔽排序器, 你还需要设置 `remove_unused_columns=False`, 以确保我们不会在训练期间丢失 `word_ids` 列。 +在这里,我们调整了一些默认选项,包括 `logging_steps` ,以确保我们可以跟踪每个 epoch 的训练损失。我们还使用了 `fp16=True` 来实现混合精度训练,从而进一步提高训练速度。默认情况下, `Trainer` 将删除模型的 `forward()` 方法中未使用的列。这意味着,如果你使用全词屏蔽(whole word masking)数据整理器,你还需要设置 `remove_unused_columns=False` ,以确保我们不会在训练期间丢失 `word_ids` 列。 -请注意, 你可以使用 `hub_model_id` 参数指定要推送到的存储库的名称((特别是你将必须使用该参数向组织推送)。例如, 当我们将模型推送到 [`huggingface-course` organization](https://huggingface.co/huggingface-course) 时, 我们添加了 `hub_model_id="huggingface-course/distilbert-finetuned-imdb"` 到 `TrainingArguments` 中。默认情况下, 使用的存储库将在你的命名空间中并以你设置的输出目录命名, 因此在我们的示例中, 它将是 `"lewtun/distilbert-finetuned-imdb"`。 +请注意,你可以使用 `hub_model_id` 参数指定你想推送到的仓库的名称(如果你想把它推送到一个组织,就必须使用这个参数)。例如,当我们将模型推送到 [`huggingface-course` 组织](https://huggingface.co/huggingface-course) 时,就在 `TrainingArguments` 中添加了 `hub_model_id="huggingface-course/distilbert-finetuned-imdb"` 。默认情况下,使用的仓库将保存在你的账户中并以你设置的输出目录命名,因此在我们的示例中,它将是 `"lewtun/distilbert-finetuned-imdb"` 。 -我们现在拥有实例化 `Trainer` 的所有成分。这里我们只使用标准的 `data_collator`, 但你可以尝试使用整个单词掩码整理器并比较结果作为练习: +现在,我们拥有了初始化 `Trainer` 所需的所有要素。这里我们只使用了标准的 `data_collator` ,但你可以尝试使用全词屏蔽作为数据整理器的一个练习,并对比一下不同屏蔽方式的结果有什么不同: ```python from transformers import Trainer @@ -728,19 +729,19 @@ trainer = Trainer( ) ``` -我们现在准备运行 `trainer.train()` -- 但在此之前让我们简要地看一下 _perplexity_, 这是评估语言模型性能的常用指标。 +我们现在准备运行 `trainer.train()` —— 但在此之前让我们简要地看一下 `困惑度(perplexity)` ,这是评估语言模型性能常用的指标。 {/if} -### 语言模型的perplexity [[语言模型的perplexity]] +### 语言模型的困惑度(perplexity) [[语言模型的困惑度(perplexity)]] -与文本分类或问答等其他任务不同, 在这些任务中, 我们会得到一个带标签的语料库进行训练, 而语言建模则没有任何明确的标签。那么我们如何确定什么是好的语言模型呢? 就像手机中的自动更正功能一样, 一个好的语言模型是为语法正确的句子分配高概率, 为无意义的句子分配低概率。为了让你更好地了解这是什么样子, 您可以在网上找到一整套 "autocorrect fails", 其中一个人手机中的模型产生了一些相当有趣 (而且通常不合适) 的结果! +语言建模与文本分类或问答等其他任务有所不同,在其他任务中,我们会得到一个带标签的语料库进行训练,而语言建模则没有任何明确的标签。那么我们如何确定什么是好的语言模型呢?就像手机中的自动更正功能一样,一个好的语言模型会较高概率输出一个语法正确的句子,较低概率输出无意义的句子。为了给你一个更直观感受,你可以在网上找到一整套“自动更正失败”的例子。其中,人们手机中的模型产生了一些相当有趣(并且常常不妥当)的自动生成的结果! {#if fw === 'pt'} -假设我们的测试集主要由语法正确的句子组成, 那么衡量我们的语言模型质量的一种方法是计算它分配给测试集中所有句子中的下一个单词的概率。高概率表明模型对看不见的例子并不感到 "惊讶" 或 "疑惑", 并表明它已经学习了语言中的基本语法模式。 perplexity度有多种数学定义, 但我们将使用的定义是交叉熵损失的指数。因此, 我们可以通过 `Trainer.evaluate()` 函数计算测试集上的交叉熵损失, 然后取结果的指数来计算预训练模型的perplexity度: +如果测试集主要由语法正确的句子组成,那么衡量语言模型质量的一种方式就是计算它给测试集中所有句子的下一个词的概率。高概率表示模型对未见过的例子不感到“惊讶”或“困惑”,这表明它已经学习了语言的基本语法模式。困惑度有很多种数学定义,我们将使用的定义是交叉熵损失的指数。具体方法是使用 `Trainer.evaluate()`方法计算测试集上的交叉熵损失,取结果的指数来计算预训练模型的困惑度。 ```python import math @@ -751,7 +752,7 @@ print(f">>> Perplexity: {math.exp(eval_results['eval_loss']):.2f}") {:else} -假设我们的测试集主要由语法正确的句子组成, 那么衡量我们的语言模型质量的一种方法是计算测试集所有句子中它分配给下一个单词的概率。高概率表明, 该模型表明该模型对未见过的示例不感到 "惊讶" 或 "困惑", 并表明它已经学会了该语言的基本语法模式。关于perplexity度有各种不同的数学定义, 但我们要用的定义是交叉熵损失的指数。因此, 我们可以通过 `model.evaluate()` 方法计算测试集上的交叉熵损失, 然后取结果的指数来计算我们预训练模型的perplexity度: +如果测试集主要由语法正确的句子组成,那么衡量语言模型质量的一种方式就是计算它给测试集中所有句子的下一个词的概率。高概率表示模型对未见过的例子不感到“惊讶”或“困惑”,这表明它已经学习了语言的基本语法模式。困惑度有很多种数学定义,我们将使用的定义是交叉熵损失的指数。具体方法是使用 `model.evaluate()` 方法计算测试集上的交叉熵损失,取结果的指数来计算预训练模型的困惑度。 ```python import math @@ -766,7 +767,7 @@ print(f"Perplexity: {math.exp(eval_loss):.2f}") >>> Perplexity: 21.75 ``` -较低的perplexity分数意味着更好的语言模型, 我们可以在这里看到我们的起始模型有一个较大的值。看看我们能不能通过微调来降低它! 为此, 我们首先运行训练循环: +较低的困惑度分数意味着更好的语言模型,我们可以看到,我们的初始模型的困惑度相当地高。让我们看看我们是否可以通过微调来降低它!为此,我们首先运行训练循环: {#if fw === 'pt'} @@ -782,7 +783,7 @@ model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback {/if} -然后像以前一样计算测试集上的perplexity度: +然后像之前那样计算测试集上的结果困惑度: {#if fw === 'pt'} @@ -804,11 +805,11 @@ print(f"Perplexity: {math.exp(eval_loss):.2f}") >>> Perplexity: 11.32 ``` -很好 -- 这大大减少了困惑, 这告诉我们模型已经了解了一些关于电影评论领域的知识! +太棒了——困惑度显著降低,这告诉我们模型已经学习到了电影评论领域的一些知识! {#if fw === 'pt'} -训练完成后, 我们可以将带有训练信息的模型卡推送到 Hub (检查点在训练过程中自行保存): +一旦训练完成,我们可以将带有训练信息的模型卡片推送到 Hub(checkpoint 在训练过程中就已经保存了): ```python trainer.push_to_hub() @@ -818,29 +819,28 @@ trainer.push_to_hub() -✏️ **轮到你了!** 将数据整理器改为全字屏蔽整理器后运行上面的训练。你有得到更好的结果吗? +✏️ **轮到你了!** 将数据整理器改为全词屏蔽的数据整理器后运行上面的训练。你能得到更好的结果吗? {#if fw === 'pt'} -在我们的用例中, 我们不需要对训练循环做任何特别的事情, 但在某些情况下, 你可能需要实现一些自定义逻辑。对于这些应用, 你可以使用 🤗 Accelerate -- 让我们来看看吧! +在我们的使用案例中,我们不需要对训练循环做任何特殊的处理,但在某些情况下,你可能需要实现一些自定义逻辑。对于这些应用,你可以使用 🤗 Accelerate —— 让我们看一看! ## 使用 🤗 Accelerate 微调 DistilBERT [[使用 🤗 Accelerate 微调 DistilBERT]] -正如我们在 `Trainer` 中看到的, 对掩码语言模型的微调与 [第三章](/course/chapter3) 中的文本分类示例非常相似。事实上, 唯一的微妙之处是使用特殊的数据整理器, 我们已经在本节的前面介绍过了! - -然而, 我们看到 `DataCollatorForLanguageModeling` 还对每次评估应用随机掩码, 因此每次训练运行时, 我们都会看到perplexity度分数的一些波动。消除这种随机性来源的一种方法是应用掩码 _一次_ 在整个测试集上, 然后使用🤗 Transformers 中的默认数据整理器在评估期间收集批次。为了看看它是如何工作的, 让我们实现一个简单的函数, 将掩码应用于批处理, 类似于我们第一次遇到的 `DataCollatorForLanguageModeling`: +从上面的 `Trainer` 训练流程中我们就能发现,微调一个掩码的语言模型与 [第三章](https://chat.openai.com/course/chapter3) 中的文本分类非常相似。事实上,唯一的不同之处是使用了一个特殊的数据整理器,我们已经在本节的前面讨论过这个问题了! +然而,我们注意到 `DataCollatorForLanguageModeling` 在每次评估时也会进行随机遮罩,因此我们在每次训练运行中都会看到困惑度得分有些波动。消除这种随机性的一种方法是在整个测试集上 `仅进行一次` 遮罩,然后在评估过程中使用🤗 Transformers 中的默认数据整理器来收集 batch。为实现这个过程,让我们实现一个简单的函数,类似于我们第一次使用 `DataCollatorForLanguageModeling` 时进行遮罩的方式: ```python def insert_random_mask(batch): features = [dict(zip(batch, t)) for t in zip(*batch.values())] masked_inputs = data_collator(features) - # Create a new "masked" column for each column in the dataset + # 为数据集中的每一列创建一个新的"masked"列 return {"masked_" + k: v.numpy() for k, v in masked_inputs.items()} ``` -接下来, 我们将此函数应用于我们的测试集并删除未掩码的列, 以便我们可以用掩码的列替换它们。你可以通过用适当的替换上面的 `data_collator` 来使整个单词掩码, 在这种情况下, 你应该删除此处的第一行: +接下来,我们将上述函数应用到测试集,并删除未进行遮罩的列,这样就实现了使用遮罩过的数据替换原始输入的数据。你可以通过将上述 `data_collator` 替换为支持全词遮罩的数据整理器并且删除下面的第一行(全词遮罩的数据整理器需要 `word_ids` 列来定位同一个单词中的不同 `token`)来实现全词遮罩 ```py downsampled_dataset = downsampled_dataset.remove_columns(["word_ids"]) @@ -858,7 +858,7 @@ eval_dataset = eval_dataset.rename_columns( ) ``` -然后我们可以像往常一样设置数据加载器, 但我们将使用🤗 Transformers 中的 `default_data_collator` 作为评估集: +然后我们可以像往常一样设置 DataLoader,但我们将使用🤗 Transformers 中的 `default_data_collator` 来设置 DataLoader: ```python from torch.utils.data import DataLoader @@ -876,13 +876,13 @@ eval_dataloader = DataLoader( ) ``` -从这里开始, 我们遵循🤗 Accelerate 的标准步骤。第一个任务是加载预训练模型的新版本: +从这里开始,我们将遵循🤗 Accelerate 的标准步骤。第一个任务是重新加载预训练模型: ``` model = AutoModelForMaskedLM.from_pretrained(model_checkpoint) ``` -然后我们需要指定优化器; 我们将使用标准的 `AdamW`: +然后我们需要指定优化器;我们将使用标准的 `AdamW` : ```python from torch.optim import AdamW @@ -890,7 +890,7 @@ from torch.optim import AdamW optimizer = AdamW(model.parameters(), lr=5e-5) ``` -有了这些对象, 我们现在可以准备使用 `Accelerator` 加速器进行训练的一切: +有了这些对象,我们现在可以用 `Accelerator` 对象包装所有的组件,以进行训练: ```python from accelerate import Accelerator @@ -901,7 +901,7 @@ model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( ) ``` -现在我们的模型、优化器和数据加载器都配置好了, 我们可以指定学习率调度器如下: +现在我们的模型、优化器和 DataLoader 都配置好了,我们可以按照以下方式设置学习率调度器: ```python from transformers import get_scheduler @@ -918,7 +918,7 @@ lr_scheduler = get_scheduler( ) ``` -在训练之前要做的最后一件事是: 在 Hugging Face Hub 上创建一个模型库! 我们可以使用 🤗 Hub 库来首先生成我们的 repo 的全名: +在开始训练之前,我们还需要做的最后一件事就是在 Hugging Face Hub 上创建一个模型仓库!我们可以使用🤗 Hub 库的 `get_full_repo_name`,生成我们仓库的全名: ```python from huggingface_hub import get_full_repo_name @@ -932,7 +932,7 @@ repo_name 'lewtun/distilbert-base-uncased-finetuned-imdb-accelerate' ``` -然后使用来自🤗 Hub 的 `Repository` 类: +然后,我们可以使用🤗 Hub 的 `Repository` 类创建并克隆仓库: ```python from huggingface_hub import Repository @@ -941,7 +941,7 @@ output_dir = model_name repo = Repository(output_dir, clone_from=repo_name) ``` -完成后, 只需写出完整的训练和评估循环即可: +完成后,只需写出完整的训练和评估循环即可: ```python from tqdm.auto import tqdm @@ -951,7 +951,7 @@ import math progress_bar = tqdm(range(num_training_steps)) for epoch in range(num_train_epochs): - # Training + # 训练 model.train() for batch in train_dataloader: outputs = model(**batch) @@ -963,7 +963,7 @@ for epoch in range(num_train_epochs): optimizer.zero_grad() progress_bar.update(1) - # Evaluation + # 评估 model.eval() losses = [] for step, batch in enumerate(eval_dataloader): @@ -982,7 +982,7 @@ for epoch in range(num_train_epochs): print(f">>> Epoch {epoch}: Perplexity: {perplexity}") - # Save and upload + # 保存并上传 accelerator.wait_for_everyone() unwrapped_model = accelerator.unwrap_model(model) unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) @@ -999,13 +999,13 @@ for epoch in range(num_train_epochs): >>> Epoch 2: Perplexity: 10.729503505340409 ``` -很棒, 我们已经能够评估每个 epoch 的perplexity度, 并确保多次训练运行是可重复的! +很棒,我们已经能够评估每个 epoch 的困惑度,并确保运行的结果可以复现! {/if} ## 使用我们微调的模型 [[使用我们微调的模型]] -你可以通过在Hub上使用其他小部件或在本地使用🤗 Transformers 的`管道`于微调模型进行交互。让我们使用后者来下载我们的模型, 使用 `fill-mask` 管道: +你可以使用 Hub 上的模型部件或者在本地使用🤗 Transformers 的 `pipeline` 加载微调模型预测文本。让我们使用后者通过 `fill-mask` pipeline 下载我们的模型: ```python from transformers import pipeline @@ -1015,7 +1015,7 @@ mask_filler = pipeline( ) ``` -然后, 我们可以向管道提供 "This is a great [MASK]" 示例文本, 并查看前 5 个预测是什么: +然后我们可以将文本“This is a great [MASK]”提供给 pipeline,看看前 5 个预测是什么: ```python preds = mask_filler(text) @@ -1032,14 +1032,13 @@ for pred in preds: '>>> this is a great character.' ``` -好的 -- 我们的模型显然已经调整了它的权重来预测与电影更密切相关的词! +Nice!—— 我们的模型显然已经调整了它的权重来预测与电影更密切相关的词! -这结束了我们训练语言模型的第一个实验。在 [第六节](/course/chapter7/section6)中你将学习如何从头开始训练像 GPT-2 这样的自回归模型; 如果你想了解如何预训练您自己的 Transformer 模型, 请前往那里! +这标志着我们第一次训练语言模型的实验到现在就结束了。在 [第 6 节](https://chat.openai.com/course/en/chapter7/6) 中,你将学习如何从头开始训练一个自动回归模型,比如 GPT-2;如果你想看看如何预训练你自己的 Transformer 模型,就赶快去那里看看吧! -✏️ **试试看!** 为了量化域适应的好处, 微调 IMDb 标签上的分类器和预先训练和微调的Distil BERT检查点。如果你需要复习文本分类, 请查看 [第三章](/course/chapter3)。 - +✏️ **试试看!** 为了量化领域适应的好处,分别使用预训练和微调的 DistilBERT checkpoint 以及数据集自带的 IMDb 标签来微调一个分类器,并对比一下这个两个 checkpoint 的差异。如果你需要复习文本分类的知识,请查看 [第三章](/course/chapter3) 。 diff --git a/chapters/zh-CN/chapter7/4.mdx b/chapters/zh-CN/chapter7/4.mdx index bb15ba4c7..715a3fdb9 100644 --- a/chapters/zh-CN/chapter7/4.mdx +++ b/chapters/zh-CN/chapter7/4.mdx @@ -22,16 +22,16 @@ {/if} -现在让我们深入研究翻译。这是另一个[sequence-to-sequence 任务](/course/chapter1/7),这意味着这是一个可以表述为从一个序列到另一个序列的问题。从这个意义上说,这个问题非常类似[文本摘要](/course/chapter7/6),并且您可以将我们将在此处学习到的一些内容迁移到其他的序列到序列问题,例如: +现在让我们深入研究翻译。这是另一个 [sequence-to-sequence 任务](/course/chapter1/7) ,着这是一个可以表述为输入是一个序列输出另一个序列的问题。从这个意义上说,这个问题非常类似 [文本摘要](/course/chapter7/6) ,并且你可以将我们将在此处学习到的一些技巧迁移到其他的序列到序列问题,例如: -- **风格迁移** : 创建一个模型将某种风格迁移到一段文本(例如,正式的风格迁移到休闲的风格或莎士比亚英语到现代英语) -- **生成问题的回答** :创建一个模型,在给定上下文的情况下生成问题的答案 +- **风格迁移** 创建一个模型将某种风格迁移到一段文本(例如,正式的风格迁移到休闲的风格,或从莎士比亚英语迁移到现代英语)。 +- **生成问题的回答** 创建一个模型,在给定上下文的情况下生成问题的答案。 -如果您有足够大的两种(或更多)语言的文本语料库,您可以从头开始训练一个新的翻译模型,就像我们在[因果语言建模](/course/chapter7/6)部分中所做的那样。然而,微调现有的翻译模型会更快,无论是从像 mT5 或 mBART 这样的多语言模型微调到特定的语言对,或者是专门用于从一种语言翻译成另一种语言的模型。 +如果你有足够大的两种(或更多)语言的文本语料库,你可以从头开始训练一个新的翻译模型,就像我们在 [因果语言建模](/course/chapter7/6) 部分中所做的那样。然而,微调现有的翻译模型会更快,无论是从像 mT5 或 mBART 这样的多语言模型微调到特定的语言对,还是从特定语料库的一种语言到另一种语言的专用翻译模型。 -在本节中,我们将对[KDE4 数据集](https://huggingface.co/datasets/kde4)上的Marian模型进行微调,该模型经过了从英语到法语的翻译预训练(因为很多“ Hugging Face”的员工会说这两种语言),它是[KDE应用程序](https://apps.kde.org/)本地化文件的数据集。我们将使用的模型已经在从[Opus 数据集](https://opus.nlpl.eu/)(实际上包含KDE4数据集)中提取的法语和英语文本的大型语料库上进行了预先训练。但是,即使我们使用的预训练模型在其预训练期间使用了这部分数据集,我们也会看到,经过微调后,我们可以得到一个更好的版本。 +在这一节中,我们将在 [KDE4 数据集](https://huggingface.co/datasets/kde4) 上微调一个预训练的 Marian 模型,用来把英语翻译成法语的(因为很多 Hugging Face 的员工都会说这两种语言)。KDE4 数据集是一个 [KDE 应用](https://apps.kde.org/) 本地化的数据集。我们将使用的模型已经在从 [Opus 数据集](https://opus.nlpl.eu/) (实际上包含 KDE4 数据集)中提取的法语和英语文本的大型语料库上进行了预先训练。不过,即使我们使用的预训练模型在其预训练期间使用了这部分数据集,我们也会看到,经过微调后,我们可以得到一个更好的版本。 完成后,我们将拥有一个模型,可以进行这样的翻译: @@ -42,15 +42,15 @@ -与前面的部分一样,您可以使用以下代码找到我们将训练并上传到 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.)查看模型输出的结果 +与前面几节一样,你可以使用以下代码找到我们将训练并上传到 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.) 查看模型输出的结果。 ## 准备数据 [[准备数据]] -为了从头开始微调或训练翻译模型,我们需要一个适合该任务的数据集。如前所述,我们将使用[KDE4 数据集](https://huggingface.co/datasets/kde4)在本节中,但您可以很容易地调整代码以使用您自己的数据,只要您有要互译的两种语言的句子对。如果您需要复习如何将自定义数据加载到 **Dataset** 可以复习一下[第五章](/course/chapter5). +为了从头开始微调或训练翻译模型,我们需要一个适合该任务的数据集。如前所述,我们将使用 [KDE4 数据集](https://huggingface.co/datasets/kde4) 。只要数据集中有互译的两种语言的句子对,就可以很容易地调整本节的代码以使用自己的数据集进行微调。如果你需要复习如何将自定义数据加载到 `Dataset` ,可以复习一下 [第五章](/course/chapter5) 。 ### KDE4 数据集 [[KDE4 数据集]] -像往常一样,我们使用 **load_dataset()** 函数: +像往常一样,我们使用 `load_dataset()` 函数下载数据集: ```py from datasets import load_dataset @@ -58,7 +58,7 @@ from datasets import load_dataset raw_datasets = load_dataset("kde4", lang1="en", lang2="fr") ``` -如果您想使用不同的语言对,您可以使用它们的代码来指定它们。该数据集共有 92 种语言可用;您可以通过[数据集卡片](https://huggingface.co/datasets/kde4)展开其上的语言标签来查看它们. +如果你想使用其他的语言对,你可以使用语言代码来设置你想使用的语言对。该数据集共有 92 种语言可用;你可以通过展开 [数据集卡片](https://huggingface.co/datasets/kde4) 上的语言标签来查看数据集支持的语言标签。 Language available for the KDE4 dataset. @@ -77,7 +77,7 @@ DatasetDict({ }) ``` -我们有 210,173 对句子,但在一次训练过程中,我们需要创建自己的验证集。正如我们在[第五章](/course/chapter5)学的的那样, **Dataset** 有一个 **train_test_split()** 方法,可以帮我们拆分数据集。我们将设定固定的随机数种子: +我们下载的数据集有 210,173 对句子,在一次训练过程中,除了训练集,我们也需要创建自己的验证集。正如我们在 [第五章](/course/chapter5) 学的的那样, `Dataset` 有一个 `train_test_split()` 方法可以帮助我们。我们将设置一个固定的随机数种子以保证结果可以复现: ```py split_datasets = raw_datasets["train"].train_test_split(train_size=0.9, seed=20) @@ -97,7 +97,7 @@ DatasetDict({ }) ``` -我们可以将 **test** 的键重命名为 **validation** 像这样: +我们可以像下面这样将 `test` 键重命名为 `validation`: ```py split_datasets["validation"] = split_datasets.pop("test") @@ -114,7 +114,7 @@ split_datasets["train"][1]["translation"] 'fr': 'Par défaut, développer les fils de discussion'} ``` -我们得到一个字典,其中包含我们请求的两种语言的两个句子。这个充满技术计算机科学术语的数据集的一个特殊之处在于它们都完全用法语翻译。然而,法国工程师通常很懒惰,在交谈时,大多数计算机科学专用词汇都用英语表述。例如,“threads”这个词很可能出现在法语句子中,尤其是在技术对话中;但在这个数据集中,它被翻译成更正确的“fils de Discussion”。我们使用的预训练模型已经在一个更大的法语和英语句子语料库上进行了预训练,采用了更简单的选择,即保留单词的原样: +我们得到一个包含我们选择的两种语言的两个句子的字典。这个充满技术计算机科学术语的数据集的一个特殊之处在于它们都完全用法语翻译。然而现实中,法国工程师在交谈时,大多数计算机科学专用词汇都用英语表述。例如,“threads”这个词很可能出现在法语句子中,尤其是在技术对话中;但在这个数据集中,它被翻译成更准确的“fils de Discussion”。我们使用的预训练模型已经在一个更大的法语和英语句子语料库上进行了预训练,所以输出的是原始的英语表达: ```py from transformers import pipeline @@ -128,8 +128,8 @@ translator("Default to expanded threads") [{'translation_text': 'Par défaut pour les threads élargis'}] ``` -这种情况的另一个例子是“plugin”这个词,它不是正式的法语词,但大多数母语人士都能理解,也不会费心去翻译。 -在 KDE4 数据集中,这个词在法语中被翻译成更正式的“module d’extension”: +这种情况的另一个例子可以在“plugin”这个词上看到,它并非正式的法语词汇,但大多数母语是法语的人都能够看懂并且不会去翻译它。不过,在 KDE4 数据集中,这个词被翻译成了更正式的法语词汇“module d'extension”: + ```py split_datasets["train"][172]["translation"] ``` @@ -151,13 +151,13 @@ translator( [{'translation_text': "Impossible d'importer %1 en utilisant le plugin d'importateur OFX. Ce fichier n'est pas le bon format."}] ``` -看看我们的微调模型是否能识别数据集的这些特殊性。(剧透警告:它会)。 +看看我们的微调模型是否能学习到数据集的这些特殊特性。(剧透警告:它能)。 -✏️ **轮到你了!** 另一个在法语中经常使用的英语单词是“email”。在训练数据集中找到使用这个词的第一个样本。它是如何翻译的?预训练模型如何翻译同一个英文句子? +✏️ **轮到你了!** 另一个在法语中经常使用的英语单词是“email”。在训练数据集中找到使用这个词的第一个样本。在数据集中它是如何翻译的?预训练模型如何翻译同一个英文句子? @@ -165,7 +165,7 @@ translator( -您现在应该知道我们的下一步该做些什么了:所有文本都需要转换为token ID,以便模型能够理解它们。对于这个任务,我们需要同时标记输入和目标。我们的首要任务是创建我们的 **tokenizer** 对象。如前所述,我们将使用 Marian 英语到法语的预训练模型。如果您使用另一对语言尝试此代码,请确保调整模型Checkpoint。[Helsinki-NLP](https://huggingface.co/Helsinki-NLP)组织提供了多种语言的一千多种模型。 +你现在应该可以预测我们的下一步该做些什么了:将所有文本转换为 token IDs 的集合,这样模型才可以理解它们。对于这个任务,我们需要同时对原始文本和翻译后的文本同时进行 tokenize。首先,我们需要创建 `tokenizer` 对象。如前所述,我们将使用 Marian 英语到法语的预训练模型。如果你使用下面的代码微调另一对语言,请记得更改下面代码中的 checkpoint。 [Helsinki-NLP](https://huggingface.co/Helsinki-NLP) 组织提供了超过一千个多语言模型。 ```python from transformers import AutoTokenizer @@ -174,17 +174,17 @@ model_checkpoint = "Helsinki-NLP/opus-mt-en-fr" tokenizer = AutoTokenizer.from_pretrained(model_checkpoint, return_tensors="pt") ``` -您可以将 **model_checkpoint** 更换为[Hub](https://huggingface.co/models)上你喜欢的任何其他型号,或本地保存的预训练模型和标记器。 +你也可以将 `model_checkpoint` 替换为你从 [Hub](https://huggingface.co/models) 中选择的其他模型,或者一个保存了预训练模型和 tokenizer 的本地文件夹。 -💡 如果正在使用mart、mBART-50或M2 M100等多语言标记器,则需要在tokenizer中设置tokenizer.src_lang和tokenizer.tgt_lang为正确的输入和目标的语言代码。 +💡 如果你在使用一个多语言的 tokenizer,比如 mBART,mBART-50,或者 M2M100,你需要通过设置 `tokenizer.src_lang` 和 `tokenizer.tgt_lang` 来在 tokenizer 中指定输入和目标的语言代码。 -我们的数据准备非常简单。 只有一件事要记住; 您需要确保分词器以输出语言(此处为法语)处理目标。 您可以通过将目标传递给分词器的 __call__ 方法的 text_targets 参数来完成此操作。 +我们的数据准备相当简单。只有一点要记住;你需要确保 tokenizer 处理的目标是输出语言(在这里是法语)。你可以通过将目标语言传递给 tokenizer 的 `__call__` 方法的 `text_targets` 参数来完成此操作。 -为了了解这是如何工作的,让我们处理训练集中每种语言的一个样本: +为了演示设置的方法,让我们处理训练集中的一个样本: ```python en_sentence = split_datasets["train"][1]["translation"]["en"] @@ -198,7 +198,7 @@ inputs {'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]} ``` -正如我们所见,输出包含与英语句子关联的输入 ID,而与法语句子关联的 ID 存储在“labels”字段中。 如果您忘记表明您正在对标签进行分词,它们将由输入分词器进行分词,这在 Marian 模型的情况下根本不会顺利进行: +我们可以看到,输出包含了与英语句子的 `inputs IDs`,而与法语句子的 IDs 存储在 `labels` 字段中。如果你忘记设置 labels 的 `tokenizer`,默认情况下 `labels` 将由输入的 `tokenizer`(语言类型不一样) 进行 `tokenize`,对于 Marian 模型来说,效果不会很好。 ```python wrong_targets = tokenizer(fr_sentence) @@ -211,9 +211,9 @@ print(tokenizer.convert_ids_to_tokens(inputs["labels"])) ['▁Par', '▁défaut', ',', '▁développer', '▁les', '▁fils', '▁de', '▁discussion', ''] ``` -正如我们所看到的,使用英语标记器来预处理法语句子会产生更多的标记,因为标记器不知道任何法语单词(除了那些也出现在英语语言中的单词,比如“discussion”)。 +如你所见,如果用英语的 tokenizer 来预处理法语句子,会产生更多的 tokens,因为这个 tokenizer 不认识任何法语单词(除了那些在英语里也出现的,比如“discussion”)。 -由于“inputs”是一个包含我们常用键(输入 ID、注意掩码等)的字典,最后一步是定义我们将应用于数据集的预处理函数: +最后一步是定义我们数据集的预处理函数: ```python max_length = 128 @@ -228,21 +228,21 @@ def preprocess_function(examples): return model_inputs ``` -请注意,我们为输入和输出设置了相同的最大长度。由于我们处理的文本看起来很短,我们使用 128。 +请注意,上述代码也为输入和输出设置了相同的最大长度。由于要处理的文本看起来很短,因此在这里将最大长度设置为 128。 -💡如果你使用的是T5模型(更具体地说,是T5 -xxx检查点之一),模型将需要文本输入有一个前缀来表示正在进行的任务,例如从英语到法语的翻译 +💡 如果你正在使用 T5 模型(更具体地说,一个 `t5-xxx` checkpoint ),模型会期望文本输入有一个前缀指示目前的任务,比如 `translate: English to French:` 。 -⚠️ 我们不关注目标的注意力掩码,因为模型不会需要它。相反,对应于填充标记的标签应设置为-100,以便在loss计算中忽略它们。这将在稍后由我们的数据整理器完成,因为我们正在应用动态填充,但是如果您在此处使用填充,您应该调整预处理函数以将与填充标记对应的所有标签设置为 -100。 +⚠️ 我们不需要对待遇测的目标设置注意力掩码,因为模型序列到序列的不会需要它。不过,我们应该将填充(padding) token 对应的标签设置为 `-100` ,以便在 loss 计算中忽略它们。由于我们正在使用动态填充,这将在稍后由我们的数据整理器完成,但是如果你在此处就打算进行填充,你应该调整预处理函数,将所有填充(padding) token 对应的标签设置为 `-100` 。 -我们现在可以对数据集的所有数据一次性应用该预处理: +我们现在可以一次性使用上述预处理处理数据集的所有数据。 ```py tokenized_datasets = split_datasets.map( @@ -252,15 +252,15 @@ tokenized_datasets = split_datasets.map( ) ``` -现在数据已经过预处理,我们准备好微调我们的预训练模型! +现在数据已经过预处理,我们准备好微调我们的预训练模型了! {#if fw === 'pt'} -## 使用 Trainer API 微调模型 [[使用 Trainer API 微调模型]] +## 使用 `Trainer` API 微调模型 [[使用 `Trainer` API 微调模型]] -使用 `Trainer` 的实际代码将与以前相同,只是稍作改动:我们在这里使用 [`Seq2SeqTrainer`](https://huggingface.co/transformers/main_classes/trainer.html#seq2seqtrainer), 它是 `Trainer` 的子类,它可以正确处理这种序列到序列的评估,并使用 `generate()` 方法来预测输入的输出。 当我们讨论评估指标时,我们将更详细地探讨这一点。 +使用 `Trainer` 的代码将与以前相同,只是稍作改动:我们在这里将使用 [`Seq2SeqTrainer`](https://huggingface.co/transformers/main_classes/trainer.html#seq2seqtrainer) ,它是 `Trainer` 的子类,它使用 `generate()` 方法来预测输入的输出结果,并且可以正确处理这种序列到序列的评估。当我们讨论评估指标时,我们将更详细地探讨这一点。 -首先,我们需要一个实际的模型来进行微调。 我们将使用通常的 `AutoModel` API: +首先,我们需要一个模型来进行微调。我们将使用常用的 `AutoModel` API: ```py from transformers import AutoModelForSeq2SeqLM @@ -272,7 +272,7 @@ model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) ## 使用 Keras 微调模型 [[使用 Keras 微调模型]] -首先,我们需要一个实际的模型来进行微调。 我们将使用通常的 `AutoModel` API: +首先,我们需要一个模型来进行微调。我们将使用常用的 `AutoModel` API: ```py from transformers import TFAutoModelForSeq2SeqLM @@ -282,19 +282,19 @@ model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint, from_pt=True) -💡 `Helsinki-NLP/opus-mt-en-fr` checkpoint只有 PyTorch 的权重,所以在使用`from_pretrained()`方法加载模型时不会使用 `from_pt=True` 参数。 当您指定`from_pt=True`,库会自动下载并转换PyTorch 为您提供权重。 如您所见,使用🤗transormer 在两者之间切换非常简单 +💡 `Helsinki-NLP/opus-mt-en-fr` checkpoint 只有 PyTorch 的权重,所以如果你尝试使用 `from_pretrained()` 方法加载模型并且忘记设置`from_pt=True` 参数的时候,你会得到一个错误。当你设定 `from_pt=True` 时,🤗transormer 会自动下载并为你转换 PyTorch 权重。如你所见,使用🤗transormer 在两种框架之间切换非常简单。 {/if} -请注意,这次我们使用的是在翻译任务上训练过的模型,并且实际上已经可以使用,因此没有关于丢失权重或新初始化权重的警告。 +注意,这次我们使用的是一个已经在翻译任务上进行过训练的模型,实际上已经可以直接使用了,所以没有收到关于缺少权重或重新初始化的权重的警告。 ### 数据整理 [[数据整理]] -我们需要一个数据整理器来处理动态批处理的填充。在本例中,我们不能像[第3章](/course/chapter3)那样使用带填充的**DataCollatorWithPadding**,因为它只填充输入(输入ID、注意掩码和令牌类型ID)。我们的标签也应该填充到标签中遇到的最大长度。而且,如前所述,用于填充标签的填充值应为-100,而不是标记器的填充标记,以确保在损失计算中忽略这些填充值。 +在这个任务中,我们需要一个数据整理器来动态批处理填充。因此,我们不能像 [第三章](/course/chapter3) 那样直接使用 `DataCollatorWithPadding` ,因为它只填充输入的部分(inputs ID、注意掩码和 token 类型 ID)。我们的标签也应该被填充到所有标签中最大的长度。而且,如前所述,用于填充标签的填充值应为 `-100` ,而不是 tokenizer 默认的的填充 token,这样才可以在确保在损失计算中忽略这些填充值。 -这一切都可以由 [`DataCollatorForSeq2Seq`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorforseq2seq) 完成。 与 `DataCollatorWithPadding` 一样,它采用用于预处理输入的`tokenizer`,但它也采用`model`。 这是因为数据整理器还将负责准备解码器输入 ID,它们是标签偏移之后形成的,开头带有特殊标记。 由于不同架构的这种转变略有不同,因此“DataCollatorForSeq2Seq”需要知道“模型”对象: +上述的这些需求都可以由 [`DataCollatorForSeq2Seq`](https://huggingface.co/transformers/main_classes/data_collator.html#datacollatorforseq2seq) 完成。它与 `DataCollatorWithPadding` 一样,它接收用于预处理输入的 `tokenizer` ,同时它也接收一个 `model` 参数。这是因为数据整理器还将负责准备解码器 `inputs ID`,它们是标签偏移之后形成的,开头带有特殊 `token` 。由于对于不同的模型架构有稍微不同的偏移方式,所以 `DataCollatorForSeq2Seq` 还需要接收 `model` 对象: {#if fw === 'pt'} @@ -314,7 +314,7 @@ data_collator = DataCollatorForSeq2Seq(tokenizer, model=model, return_tensors="t {/if} -为了在几个样本上进行测试,我们只需在我们标记化训练集中的部分数据上调用它: +为了在几个样本上进行测试,我们在已经完成 tokenize 的训练集中的部分数据上调用它,测试一下其功能: ```py batch = data_collator([tokenized_datasets["train"][i] for i in range(1, 3)]) @@ -325,7 +325,7 @@ batch.keys() dict_keys(['attention_mask', 'input_ids', 'labels', 'decoder_input_ids']) ``` -我们可以检查我们的标签是否已使用 **-100** 填充到批次的最大长度: +我们可以检查我们的标签是否已经用 `-100` 填充到 batch 的最大长度: ```py batch["labels"] @@ -338,7 +338,7 @@ tensor([[ 577, 5891, 2, 3184, 16, 2542, 5, 1710, 0, -100, 550, 7032, 5821, 7907, 12649, 0]]) ``` -我们还可以查看解码器输入 ID,看看它们是标签的偏移形成的版本: +我们还可以查看解码器的 inputs ID,可以看到它们是标签经过偏移后的结果: ```py batch["decoder_input_ids"] @@ -365,11 +365,10 @@ for i in range(1, 3): {#if fw === 'pt'} -我们将把这个 `data_collator` 传递给 `Seq2SeqTrainer`。 接下来,让我们看一下评估指标。 - +把 `data_collator` 传递给 `Seq2SeqTrainer` 后就完成了数据整理。接下来,让我们看一下评估指标。 {:else} -我们现在可以使用 `data_collator` 将我们的每个数据集转换为 `tf.data.Dataset`,准备好进行训练: +我们现在可以使用 `data_collator` 将我们的每个数据集转换为 `tf.data.Dataset` ,这样就完成了数据整理: ```python tf_train_dataset = model.prepare_tf_dataset( @@ -395,22 +394,22 @@ tf_eval_dataset = model.prepare_tf_dataset( {#if fw === 'pt'} -`Seq2SeqTrainer` 添加到其超类 `Trainer` 的功能是在评估或预测期间使用 `generate()` 方法的能力。 在训练期间,模型将使用带有注意掩码的“decoder_input_ids”,以确保它不使用预测的标记之后的标记,以加快训练速度。 在推理过程中,我们将无法使用预测的标记之后的标记,因为我们没有标签,因此使用相同的设置使用带有注意掩码的“decoder_input_ids”,评估我们的模型是个好主意。 +`Seq2SeqTrainer` 是 `Trainer` 类的一个子类,它的主要增强特性是在评估或预测时使用 `generate()` 方法。在训练过程中,模型会利用 `decoder_input_ids` 和一个特殊的注意力掩码来加速训练。这种方法允许模型在预测下一个token时看到部分目标序列,但确保它不会使用预测token之后的信息。这种优化策略显著提高了训练效率。然而,在实际的推理过程中,我们没有真实的标签值,因此无法生成 `decoder_input_ids` 和相应的注意力掩码。这意味着我们无法在推理时使用这种训练时的优化方法。 -正如我们在[第一章](/course/chapter1/6)看到的,解码器通过一个一个地预测标记来执行推理——这是🤗 Transformers 在幕后通过 **generate()** 方法实现的。如果我们设置 predict_with_generate=True,Seq2 Seq Trainer 将允许我们使用该方法进行评估。 +为了确保评估结果能够准确反映模型在实际使用中的表现,我们应该在评估阶段模拟真实推理的条件。这意味着我们需要使用在 [第一章](/course/chapter1/6) 中介绍的 🤗 Transformers 库中的 `generate()` 方法。该方法能够逐个生成token,真实地模拟推理过程,而不是依赖于训练时的优化技巧。要启用这个功能,我们需要在训练时添加 `predict_with_generate=True` 参数。这样做可以确保我们的评估结果更加接近模型在实际应用中的表现。 {/if} -用于翻译的传统指标是[BLEU 分数](https://en.wikipedia.org/wiki/BLEU), 由Kishore Papineni等人在[2002年的一篇文章](https://aclanthology.org/P02-1040.pdf)中引入。BLEU 分数评估翻译与其标签的接近程度。它不衡量模型生成输出的可懂度或语法正确性,而是使用统计规则来确保生成输出中的所有单词也出现在目标中。此外,如果相同单词在目标中没有重复,则有规则惩罚相同单词的重复(以避免模型输出类似 **the the the the the**的句子 ) 并输出比目标中短的句子(以避免模型输出像 **the** 这样的句子)。 +用于翻译的传统指标是 [BLEU 分数](https://en.wikipedia.org/wiki/BLEU) ,它最初由 Kishore Papineni 等人在 2002 年的 [一篇文章](https://aclanthology.org/P02-1040.pdf) 中引入。BLEU 分数评估翻译与参考翻译的接近程度。它不衡量模型生成输出的可理解性或语法正确性,而是使用统计规则来确保生成输出中的所有单词也出现在参考的输出中。此外,还有一些规则对重复的词进行惩罚,如果这些词在输出中重复出现(模型输出像“the the the the the”这样的句子);或者输出的句子长度比目标中的短(模型输出像“the”这样的句子)都会被惩罚。 -BLEU 的一个缺点是它需要文本已经被分词,这使得比较使用不同标记器的模型之间的分数变得困难。因此,当今用于基准翻译模型的最常用指标是[SacreBLEU](https://github.com/mjpost/sacrebleu),它通过标准化标记化步骤解决了这个缺点(和其他的一些缺点)。要使用此指标,我们首先需要安装 SacreBLEU 库: +BLEU 的一个缺点是的输入是已分词的文本,这使得比较使用不同分词器的模型之间的分数变得困难。因此,当今用于评估翻译模型的最常用指标是 [SacreBLEU](https://github.com/mjpost/sacrebleu) ,它通过标准化的分词步骤解决了这个缺点(和其他的一些缺点)。要使用此指标,我们首先需要安装 SacreBLEU 库: ```py !pip install sacrebleu ``` -然后我们可以就像我们在[第三章](/course/chapter3)那样通过 **evaluate.load()** 加载它 : +然后我们可以就像在 [第三章](/course/chapter3) 那样通过 `evaluate.load()` 加载它 ```py import evaluate @@ -418,7 +417,7 @@ import evaluate metric = evaluate.load("sacrebleu") ``` -该指标将文本作为输入和目标结果。它旨在接受多个可接受的目标,因为同一个句子通常有多个可接受的翻译——我们使用的数据集只提供一个,但在 NLP 中找到将多个句子作为标签的数据集不是一个难题。因此,预测结果应该是一个句子列表,而参考应该是一个句子列表的列表。 +SacreBLEU 指标中待评估的预测和参考的目标译文输入的格式都是文本。它的设计是为了支持多个参考翻译,因为同一句话通常有多种可接受的翻译——虽然我们使用的数据集只提供一个,但在 NLP 中找到将多个句子作为标签的数据集是很常见的。因此,预测结果应该是一个句子列表,而参考应该是一个句子列表的列表。 让我们尝试一个例子: @@ -444,7 +443,7 @@ metric.compute(predictions=predictions, references=references) 'ref_len': 13} ``` -这得到了 46.75 的 BLEU 分数,这是相当不错的——作为参考,原始 Transformer 模型在[“Attention Is All You Need” 论文](https://arxiv.org/pdf/1706.03762.pdf)类似的英语和法语翻译任务中获得了 41.8 的 BLEU 分数! (有关各个指标的更多信息,例如 **counts** 和 **bp** ,见[SacreBLEU 仓库](https://github.com/mjpost/sacrebleu/blob/078c440168c6adc89ba75fe6d63f0d922d42bcfe/sacrebleu/metrics/bleu.py#L74).) 另一方面,如果我们尝试使用翻译模型中经常出现的两种糟糕的预测类型(大量重复或太短),我们将得到相当糟糕的 BLEU 分数: +达到了 46.75 的 BLEU 分数,这是相当不错的——作为参考,原始 Transformer 模型在 [“Attention Is All You Need” 论文](https://arxiv.org/pdf/1706.03762.pdf) 类似的英语和法语翻译任务中获得了 41.8 的 BLEU 分数!(关于其他指标的含义,例如 `counts` 和 `bp` ,可以参见 [SacreBLEU仓库](https://github.com/mjpost/sacrebleu/blob/078c440168c6adc89ba75fe6d63f0d922d42bcfe/sacrebleu/metrics/bleu.py#L74) )另一方面,如果我们尝试将翻译模型中经常出现的两种糟糕的预测类型(大量重复或太短)输入给指标计算的函数,我们将得到相当糟糕的 BLEU 分数: ```py predictions = ["This This This This"] @@ -490,7 +489,9 @@ metric.compute(predictions=predictions, references=references) {#if fw === 'tf'} -为了将模型输出的向量转换为可以使用的文本,我们将使用 `tokenizer.batch_decode()` 方法。 我们只需要清除标签中的所有“-100”; tokenizer 将自动对填充令牌执行相同的操作。 让我们定义一个函数,它接受我们的模型和数据集并计算其指标。 我们还将使用一个可以显着提高性能的技巧 - 使用 TensorFlow 的加速线性代数编译器 [XLA](https://www.tensorflow.org/xla) 编译我们的生成代码。 XLA 对模型的计算图应用了各种优化,并显着提高了速度和内存使用率。 正如 Hugging Face [博客](https://huggingface.co/blog/tf-xla-generate) 中所述,当我们的输入形状变化不大时,XLA 效果最佳。 为了处理这个问题,我们将输入填充为 128 的倍数,并使用填充整理器创建一个新数据集,然后我们将 @tf.function(jit_compile=True) 装饰器应用于我们的生成函数,将整个函数标记为使用 XLA 进行编译。 +为了将模型的输出转化为评估指标可以使用的文本,我们将利用 `tokenizer.batch_decode()` 方法。因为 tokenizer 会自动处理填充 `tokens`,所以我们只需要清理所有标签中的 填充的`-100` 。让我们定义一个函数,这个函数会接收一个模型和一个数据集,并在其上计算 BLEU 指标。 + +我们还将使用一个显著提升性能的技巧 - 使用 [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 编译。 ```py import numpy as np @@ -545,7 +546,7 @@ def compute_metrics(): {:else} -为了从模型输出到度量可以使用的文本,我们将使用 `tokenizer.batch_decode()` 方法。 我们只需要清理标签中的所有 `-100`(标记器将自动对填充标记执行相同操作): +为了将模型的输出转化为评估指标可以使用的文本,我们将使用 `tokenizer.batch_decode()` 方法。因为 tokenizer 会自动处理填充的 tokens,所以我们只需要清理标签中的所有 `-100` token: ```py import numpy as np @@ -553,17 +554,17 @@ import numpy as np def compute_metrics(eval_preds): preds, labels = eval_preds - # In case the model returns more than the prediction logits + # 如果模型返回的内容超过了预测的logits if isinstance(preds, tuple): preds = preds[0] decoded_preds = tokenizer.batch_decode(preds, skip_special_tokens=True) - # Replace -100s in the labels as we can't decode them + # 由于我们无法解码 -100,因此将标签中的 -100 替换掉 labels = np.where(labels != -100, labels, tokenizer.pad_token_id) decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) - # Some simple post-processing + # 一些简单的后处理 decoded_preds = [pred.strip() for pred in decoded_preds] decoded_labels = [[label.strip()] for label in decoded_labels] @@ -577,7 +578,7 @@ def compute_metrics(eval_preds): ### 微调模型 [[微调模型]] -第一步是登录 Hugging Face,这样您就可以将结果上传到模型中心。有一个方便的功能可以帮助您在notebook中完成此操作: +第一步是登录 Hugging Face,这样你就可以在训练过程中将结果上传到 Hub中。有一个方便的功能可以帮助你在 notebook 中完成登陆: ```python from huggingface_hub import notebook_login @@ -585,9 +586,9 @@ from huggingface_hub import notebook_login notebook_login() ``` -这将显示一个小部件,您可以在其中输入您的 Hugging Face 登录凭据。 +这将显示一个小部件,你可以在其中输入你的 Hugging Face 登录凭据。 -如果您不是在notebook上运行代码,只需在终端中输入以下行: +如果你不是在 notebook 上运行代码,可以在终端中输入以下命令: ```bash huggingface-cli login @@ -595,8 +596,7 @@ huggingface-cli login {#if fw === 'tf'} -在我们开始之前,让我们看看我们在没有任何训练的情况下从我们的模型中得到了什么样的结果: - +在我们开始之前,让我们看看我们在没有任何训练的情况下我们的模型效果怎么样。 ```py print(compute_metrics()) ``` @@ -605,16 +605,17 @@ print(compute_metrics()) {'bleu': 33.26983701454733} ``` -一旦完成,我们就可以准备编译和训练模型所需的一切。 注意当使用 `tf.keras.mixed_precision.set_global_policy("mixed_float16")`时——这将告诉 Keras 使用 float16 进行训练,这可以显着提高支持它的 GPU(Nvidia 20xx/V100 或更高版本)的速度。 +现在可以准备编译和训练模型了。运行 `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 -# 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. +# 训练步数是数据集中的样本数量,除以 batch 大小,然后乘以总的 epoch 数。 +# 注意这里的 tf_train_dataset 是 batch 形式的 tf.data.Dataset, +# 而不是原始的 Hugging Face Dataset ,所以使用 len() 计算它的长度已经是 num_samples // batch_size。 + num_epochs = 3 num_train_steps = len(tf_train_dataset) * num_epochs @@ -626,11 +627,11 @@ optimizer, schedule = create_optimizer( ) model.compile(optimizer=optimizer) -# Train in mixed-precision float16 +# 使用 float16 混合精度进行训练 tf.keras.mixed_precision.set_global_policy("mixed_float16") ``` -接下来,我们定义一个 `PushToHubCallback` 以便在训练期间将我们的模型上传到 Hub,正如我们在 [第 2 节]((/course/chapter7/2)) 中看到的,然后我们只需拟合模型时添加该回调函数: +接下来就像我们在 [第 2 节](/course/chapter7/2) 中学到的,我们将定义一个 `PushToHubCallback` 回调函数,并拟合模型的时候添加该回调函数,这样就可以训练期间将我们的模型上传到 Hub。 ```python from transformers.keras_callbacks import PushToHubCallback @@ -647,15 +648,15 @@ model.fit( ) ``` -请注意,您可以使用 `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"`。 +请注意,你可以使用 `hub_model_id` 参数指定要推送到的模型仓库的名称(当你想把模型推送到指定的组织的时候,就必须使用此参数)。例如,当我们将模型推送到 [`huggingface-course` 组织](https://huggingface.co/huggingface-course) 时,我们在 `Seq2SeqTrainingArguments`添加了 `hub_model_id="huggingface-course/marian-finetuned-kde4-en- to-fr"` 。默认情况下,该仓库将保存在你的账户里,并以你设置的输出目录命名,因此在我们的例子中它是 `"sgugger/marian-finetuned-kde4-en-to-fr"` 。 -💡如果您使用的输出目录已经存在,则它需要是您要推送到的存储库的本地克隆。如果不是,您将在定义您的名称时会遇到错误,并且需要设置一个新名称。 +💡如果正在使用的输出目录已经存在一个同名的文件夹,则它应该是目标推送仓库的在本地克隆在本地的版本。如果不是,当调用 `model.fit()` 时会收到错误,并需要设置一个新的路径。 -最后,让我们看看训练结束后我们的指标是什么样的: +最后,让我们看看训练结束后我们的模型的 BLEU 的分数: ```py print(compute_metrics()) @@ -665,11 +666,11 @@ print(compute_metrics()) {'bleu': 57.334066271545865} ``` -在这个阶段,您可以使用模型中心上的推理小部件来测试您的模型并与您的朋友分享。 您已经成功地微调了翻译任务中的模型——恭喜! +在这个阶段,你可以使用模型中心上的推理小部件来测试你的模型并与你的朋友分享。恭喜你,你已经成功地在翻译任务上微调了一个模型! {:else} -一旦完成,我们就可以定义我们的 `Seq2SeqTrainingArguments`。 与 `Trainer` 一样,我们使用 `TrainingArguments` 的子类,其中包含更多可以设置的字段: +完成这些步骤之后,我们就可以定义我们的 `Seq2SeqTrainingArguments` 了。与 `Trainer` 一样,它是 `TrainingArguments` 的子类,其中包含更多可以设置的字段: ```python from transformers import Seq2SeqTrainingArguments @@ -690,23 +691,22 @@ args = Seq2SeqTrainingArguments( ) ``` -除了通常的超参数(如学习率、训练轮数、批次大小和一些权重衰减)之外,与我们在前几节中看到的相比,这里有一些变化: +除了通常的超参数(如学习率、训练轮数、批次大小和一些权重衰减)之外,这里的部分参数与我们在前面章节看到的有一些不同: -- 我们没有设置任何定期评估,因为评估需要耗费一定的时间;我们只会在训练开始之前和结束之后评估我们的模型一次。 -- 我们设置fp16=True,这可以加快支持fp16的 GPU 上的训练速度。 -- 和上面我们讨论的那样,我们设置predict_with_generate=True -- 我们用push_to_hub=True在每个 epoch 结束时将模型上传到 Hub。 +- 我们没有设置定期进行评估,因为评估需要耗费一定的时间;我们将只在训练开始之前和结束之后评估我们的模型一次。 +- 我们设置 `fp16=True` ,这可以加快在支持 fp16 的 GPU 上的训练速度。 +- 和之前我们讨论的一样,我们设置 `predict_with_generate=True` 。 +- 我们设置了 `push_to_hub=True` ,在每个 epoch 结束时将模型上传到 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"`。 +请注意,你可以使用 `hub_model_id` 参数指定要推送到的存储库的名称(当你想把模型推送到指定的组织的时候,就必须使用此参数)。例如,当我们将模型推送到 [`huggingface-course` 组织](https://huggingface.co/huggingface-course) 时,我们在 `Seq2SeqTrainingArguments` 添加了 `hub_model_id="huggingface-course/marian-finetuned-kde4-en- to-fr"` 。默认情况下,该仓库将保存在你的账户中,并以你设置的输出目录命名,因此在我们的例子中它是 `"sgugger/marian-finetuned-kde4-en-to-fr"` 。 -💡如果您使用的输出目录已经存在,则它需要是您要推送到的存储库的本地克隆。如果不是,您将在定义您的名称时会遇到错误,并且需要设置一个新名称。 +💡如果你使用的输出目录已经存在一个同名的文件夹,则它应该是推送的仓库克隆在本地的版本。如果不是,你将在定义你的 `Seq2SeqTrainer` 名称时会遇到错误,并且需要设置一个新名称。 - -最后,我们需要将所有内容传递给 **Seq2SeqTrainer** : +最后,我们将所有内容传递给 `Seq2SeqTrainer` : ```python from transformers import Seq2SeqTrainer @@ -722,7 +722,7 @@ trainer = Seq2SeqTrainer( ) ``` -在训练之前,我们将首先查看我们的模型获得的分数,以仔细检查我们的微调没有让事情变得更糟。此命令需要一些时间,因此您可以在执行时喝杯咖啡: +在开始训练之前,我们先查看一下我们的模型目前的 BLEU 分数,以确保我们的微调并未使情况变得更糟。这个命令需要一些时间,所以你可以在执行期间去喝杯咖啡: ```python trainer.evaluate(max_length=max_length) @@ -736,7 +736,7 @@ trainer.evaluate(max_length=max_length) 'eval_steps_per_second': 0.341} ``` -BLEU的分数还不错,这反映了我们的模型已经擅长将英语句子翻译成法语句子。 +BLEU 得分为 39 并不算太差,这反映了我们的模型已经擅长将英语句子翻译成法语句子。 接下来是训练,这也需要一些时间: @@ -744,9 +744,9 @@ BLEU的分数还不错,这反映了我们的模型已经擅长将英语句子 trainer.train() ``` -请注意,当训练发生时,每次保存模型时(这里是每个时期),它都会在后台上传到 Hub。这样,如有必要,您将能够在另一台机器上继续您的训练。 +请注意,在训练过程中,每当保存模型时(这里是每个 epoch),它都会在后台将模型上传到 Hub。这样,如有必要,你将能够在另一台机器上继续你的训练。 -训练完成后,我们再次评估我们的模型——希望我们会看到 BLEU 分数有所改善! +训练完成后,我们再次评估我们的模型——希望我们会看到 BLEU 分数有所提高! ```py trainer.evaluate(max_length=max_length) @@ -761,23 +761,23 @@ trainer.evaluate(max_length=max_length) 'epoch': 3.0} ``` -这是近 14 点的改进,这很棒。 +可以看到近 14 点的改进,这很棒! -最后,我们使用 **push_to_hub()** 方法来确保我们上传模型的最新版本。这 **Trainer** 还创建了一张包含所有评估结果的模型卡并上传。此模型卡包含可帮助模型中心为推理演示选择小部件的元数据。通常不需要做额外的更改,因为它可以从模型类中推断出正确的小部件,但在这种情况下,相同的模型类可以用于所有类型的序列到序列问题,所以我们指定它是一个翻译模型: +最后,我们使用 `push_to_hub()` 方法来确保我们上传了模型最新的版本。 `Trainer` 还创建了一张包含所有评估结果的模型卡片并上传到 Hub 。这个模型卡片包含了可以帮助 Hub 为推理演示选择小部件的元数据,通常情况下我们不需要做额外的更改,因为它可以从模型类中推断出正确的小部件,但在这个示例中,它只能通过模型类推断这是一个序列到序列的问题,所以我们补充一下具体的模型类别。 ```py trainer.push_to_hub(tags="translation", commit_message="Training complete") ``` -如果您想检查命令执行的结果,此命令将返回它刚刚执行的提交的 URL,可以打开url进行检查: +如果你想检查命令执行的结果,此命令将返回它刚刚执行的提交的 URL,你可以打开 url 进行检查: ```python out 'https://huggingface.co/sgugger/marian-finetuned-kde4-en-to-fr/commit/3601d621e3baae2bc63d3311452535f8f58f6ef3' ``` -在此阶段,您可以使用模型中心上的推理小部件来测试您的模型并与您的朋友分享。您已成功微调翻译任务的模型 - 恭喜! +在此阶段,你可以在 Model Hub 上使用推理小部件来测试你的模型,并与你的朋友分享。你已经成功地在翻译任务上进行了模型的微调,恭喜你! -如果您想更深入地了解训练循环,我们现在将向您展示如何使用 🤗 Accelerate 做同样的事情。 +如果你想更深入地了解训练循环,我们现在将向你展示如何使用 🤗 Accelerate 做同样的事情。 {/if} @@ -785,11 +785,11 @@ trainer.push_to_hub(tags="translation", commit_message="Training complete") ## 自定义训练循环 [[自定义训练循环]] -现在让我们看一下完整的训练循环,以便您可以轻松自定义所需的部分。它看起来很像我们在[本章第二节](/course/chapter7/2)和[第三章第四小节](/course/chapter3/4)所做的。 +我们现在来看一下完整的训练循环,这样你就可以轻松定制你需要的部分。它将与我们在 [第 2 节](https://chat.openai.com/course/chapter7/2) 和 [第 3 节](https://chat.openai.com/course/chapter3/4) 中做的非常相似。 ### 准备训练所需的一切 [[准备训练所需的一切]] -您已经多次看到所有这些,因此这一块会简略进行。首先我们将构建我们的数据集的**DataLoader** ,在将数据集设置为 **torch** 格式,我们就得到了 PyTorch 张量: +由于这里的步骤在之前的章节已经出现过很多次,因此这里只做简略说明。首先,我们将数据集设置为 `torch` 格式,这样可以将数据集的格式转换为 `PyTorch` 张量,然后我们用数据集构建 `DataLoader` : ```py from torch.utils.data import DataLoader @@ -820,7 +820,7 @@ from transformers import AdamW optimizer = AdamW(model.parameters(), lr=2e-5) ``` -一旦我们拥有所有这些对象,我们就可以将它们发送到 `accelerator.prepare()` 方法。 请记住,如果您想在 Colab 笔记本训练中使用TPU,则需要将所有这些代码移动到训练函数中,并且不应执行任何实例化“加速器”的对象。 +准备好这些对象,我们就可以将它们发送到 `accelerator.prepare()` 方法中。请记住,如果你想在 Colab Notebook 上使用 TPU 进行训练,你需要将所有这些代码移动到一个训练函数中,并且这个训练函数不应该包含实例化 `Accelerator` `的代码。换句话说,Accelerator` 的实例化应该在这个函数之外进行。这么做的原因是,TPU 在 Colab 中的工作方式有些特殊。TPU 运行时会重新执行整个单元格的代码,因此如果 `Accelerator` 的实例化在训练函数内部,它可能会被多次实例化,导致错误。 ```py from accelerate import Accelerator @@ -831,7 +831,7 @@ model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( ) ``` -现在我们已经发送了我们的 **train_dataloader** 到 **accelerator.prepare()** ,我们可以使用它的长度来计算训练步骤的数量。请记住,我们应该始终在准备好数据加载器后执行此操作,因为该方法会更改 **DataLoader** .我们使用从学习率衰减到 0 的经典线性学习率调度: +现在我们已经将我们的 `train_dataloader` 发送到 `accelerator.prepare()` 方法中了,现在我们可以使用它的长度来计算训练步骤的数量。请记住,我们应该始终在准备好数据加载器后再执行此操作,因为更改数据加载器会改变 `DataLoader` 的长度。然后,我们使用学习率衰减到 0 的经典线性学习率调度: ```py from transformers import get_scheduler @@ -848,7 +848,7 @@ lr_scheduler = get_scheduler( ) ``` -最后,要将我们的模型推送到 Hub,我们需要创建一个 **Repository** 工作文件夹中的对象。如果您尚未登录,请先登录 Hugging Face。我们将从我们想要为模型提供的模型 ID 中确定存储库名称(您可以自由地用自己的选择替换 **repo_name** ;它需要包含您的用户名,可以使用**get_full_repo_name()**函数的查看目前的repo_name): +最后,为了将我们的模型推送到 Hugging Face Hub,我们需要在一个工作文件夹中创建一个 `Repository` 对象。如果你尚未登录 Hugging Face,请先进行登录。我们将根据模型 ID 来确定仓库名称。你可以使用自己选择的名称替换 `repo_name`,但请确保包含你的用户名。如果你不确定当前的用户名,可以使用` get_full_repo_name()` 函数来查看: ```py from huggingface_hub import Repository, get_full_repo_name @@ -862,18 +862,18 @@ repo_name '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) ``` -我们现在可以通过调用 **repo.push_to_hub()** 方法上传我们保存的任何内容 **output_dir** 。这将帮助我们在每个 epoch 结束时上传过程中的模型。 +现在,我们可以通过调用 `repo.push_to_hub()` 方法上传我们在 `output_dir` 中保存的所有文件。这将帮助我们在每个 epoch 结束时上传中间模型。 ### 训练循环 [[训练循环]] -我们现在准备编写完整的训练循环。为了简化它的评估部分,我们定义了这个 **postprocess()** 函数接收预测结果和正确标签并将它们转换为我们 **metric** 对象所需要的字符串列表: +我们现在准备编写完整的训练循环。为了简化其评估部分,我们定义了这个 `postprocess()` 函数用于接收预测值和参考翻译对于的标签值,并将其转换为 `metric` 对象所需要的字符串列表。: ```py def postprocess(predictions, labels): @@ -882,21 +882,23 @@ def postprocess(predictions, labels): decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) - # Replace -100 in the labels as we can't decode them. + # 替换标签中的 -100,因为我们无法解码它们。 labels = np.where(labels != -100, labels, tokenizer.pad_token_id) decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) - # Some simple post-processing + # 一些简单的后处理 decoded_preds = [pred.strip() for pred in decoded_preds] decoded_labels = [[label.strip()] for label in decoded_labels] return decoded_preds, decoded_labels ``` -训练循环看起来和[本章第二节](/course/chapter7/2)与[第三章](/course/chapter3)很像,在评估部分有一些不同 - 所以让我们专注于这一点! +训练循环看起来和本章 [第 2 节](/course/chapter7/2) 与 [第三章](/course/chapter3) 中代码很相似,只是在评估部分有一些不同 —— 所以让我们重点关注一下这一点! + +首先要注意的是,我们使用 `generate()` 方法来计算预测,但这是我们基础模型上的一个方法,而不是🤗 Accelerate 在 `prepare()` 方法中创建的封装模型。这就是为什么我们首先 `unwrap_model` ,然后调用此方法。 -首先要注意的是我们使用 `generate()` 方法来计算预测,但这是我们基础模型上的一个方法,而不是包装模型🤗 Accelerate 在 `prepare()` 方法中创建。 这就是为什么我们先解包模型,然后调用这个方法。 +首先要注意的是,我们用来计算预测的 `generate()` 函数是基础模型上的一个方法,而不是🤗 Accelerate 在 `prepare()` 函数中创建的封装模型。这就是在调用此函数之前先调用`unwrap_model`,的原因。 -第二件事是,就像[token 分类](/course/chapter7/2),两个进程可能将输入和标签填充为不同的形状,因此我们在调用 **gather()** 方法之前使用 **accelerator.pad_across_processes()** 使预测和标签具有相同的形状。如果我们不这样做,评估要么出错,要么永远在阻塞。 +第二个要注意的是,就像 [token 分类](https://chat.openai.com/course/chapter7/2) 一样,在训练和评估这两个过程可能以不同的形状对输入和标签进行了填充,所以我们在调用 `gather()` 函数之前使用 `accelerator.pad_across_processes()` 方法,使预测和标签具有相同的形状。如果我们不这么做,那么评估的过程将会出错或被永远挂起。 ```py from tqdm.auto import tqdm @@ -905,7 +907,7 @@ import torch progress_bar = tqdm(range(num_training_steps)) for epoch in range(num_train_epochs): - # Training + # 训练 model.train() for batch in train_dataloader: outputs = model(**batch) @@ -917,7 +919,7 @@ for epoch in range(num_train_epochs): optimizer.zero_grad() progress_bar.update(1) - # Evaluation + # 评估 model.eval() for batch in tqdm(eval_dataloader): with torch.no_grad(): @@ -928,7 +930,7 @@ for epoch in range(num_train_epochs): ) labels = batch["labels"] - # Necessary to pad predictions and labels for being gathered + # 需要填充预测和标签才能调用gather() generated_tokens = accelerator.pad_across_processes( generated_tokens, dim=1, pad_index=tokenizer.pad_token_id ) @@ -943,7 +945,7 @@ for epoch in range(num_train_epochs): results = metric.compute() print(f"epoch {epoch}, BLEU score: {results['score']:.2f}") - # Save and upload + # 保存和上传 accelerator.wait_for_everyone() unwrapped_model = accelerator.unwrap_model(model) unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) @@ -960,18 +962,18 @@ 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)查看训练完的结果。 如果您想测试对训练循环的任何调整,您可以通过编辑上面显示的代码直接实现它们! +训练完成之后,你就有了一个模型,最终的 BLEU 分数应该与 `Seq2SeqTrainer` 训练的模型非常相似。你可以在 [huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate](https://huggingface.co/huggingface-course/marian-finetuned-kde4-en-to-fr-accelerate) 上查看我们使用此代码训练的模型。如果你想测试对训练循环的任何调整,你可以直接通过编辑上面的代码来实现! {/if} ## 使用微调后的模型 [[使用微调后的模型]] -我们已经向您展示了如何将我们在模型中心微调的模型与推理小部件一起使用。 要在“管道”中本地使用它,我们只需要指定正确的模型标识符: +我们已经向你展示了如何在模型 Hub 上使用我们微调的模型。要在本地的 `pipeline` 中使用它,我们只需要指定正确的模型标识符: ```py from transformers import pipeline -# Replace this with your own checkpoint +# 将其替换成你自己的 checkpoint model_checkpoint = "huggingface-course/marian-finetuned-kde4-en-to-fr" translator = pipeline("translation", model=model_checkpoint) translator("Default to expanded threads") @@ -981,7 +983,7 @@ translator("Default to expanded threads") [{'translation_text': 'Par défaut, développer les fils de discussion'}] ``` -正如预期的那样,我们的预训练模型将其知识适应了我们对其进行微调的语料库,而不是单独留下英文单词“threads”,而是将其翻译成法语官方版本。 “”的翻译也是一样的: +和预想的一样,我们的预训练模型适应了我们微调它的语料库,没有保留英语单词“threads”,而是将它翻译成官方的法语版本。对于“plugin”也是如此: ```py translator( @@ -993,10 +995,10 @@ translator( [{'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/zh-CN/chapter7/5.mdx b/chapters/zh-CN/chapter7/5.mdx index 0fbba71a7..f50469f85 100644 --- a/chapters/zh-CN/chapter7/5.mdx +++ b/chapters/zh-CN/chapter7/5.mdx @@ -23,19 +23,19 @@ {/if} -在本节中,我们将看看如何使用 Transformer 模型将长文档压缩为摘要,这项任务称为文本摘要.这是最具挑战性的 NLP 任务之一,因为它需要一系列能力,例如理解长篇文章和生成能够捕捉文档中主要主题的连贯文本。但是,如果做得好,文本摘要是一种强大的工具,可以减轻领域专家详细阅读长文档的负担,从而加快各种业务流程。 +在本节中,我们将看看如何使用 Transformer 模型将长篇文档压缩为摘要,这项任务称为文本摘要。这是最具挑战性的自然语言处理(NLP)任务之一,因为它需要一系列能力,例如理解长篇文章并且生成能够捕捉文档中主要主题的连贯文本。但是,如果做得好,文本摘要是一种强大的工具,可以减轻各个领域的人详细阅读长文档的负担,从而加快业务流程。 -尽管在[Hugging Face Hub](https://huggingface.co/models?pipeline_tag=summarization=downloads)上已经存在各种微调模型用于文本摘要,几乎所有这些都只适用于英文文档。因此,为了在本节中添加一些变化,我们将为英语和西班牙语训练一个双语模型。在本节结束时,您将有一个可以总结客户评论的[模型](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es)。 +尽管在 [Hugging Face Hub](https://huggingface.co/models?pipeline_tag=summarization=downloads) 上已经存在各种提取文本摘要的微调模型,但是几乎所有的这些模型都只适用于英文文档。因此,为了在本节中添加一些不一样的特点,我们将为英语和西班牙语训练一个双语模型。在本节结束时,你将有一个可以总结客户评论的 [模型](https://huggingface.co/huggingface-course/mt5-small-finetuned-amazon-en-es) 。 -如下所示:正如我们将看到的,这些摘要很简洁,因为它们是从客户在产品评论中提供的标题中学到的。让我们首先为这项任务准备一个合适的双语语料库。 +如果你试一试的话,就发现模型能够生成非常简洁的摘要,因为它们是从客户在产品评论中提供的标题中学到的。让我们首先为这项任务准备一个合适的双语语料库。 ## 准备多语言语料库 [[准备多语言语料库]] -我们将使用[多语言亚马逊评论语料库](https://huggingface.co/datasets/amazon_reviews_multi)创建我们的双语摘要器。该语料库由六种语言的亚马逊产品评论组成,通常用于对多语言分类器进行基准测试。然而,由于每条评论都附有一个简短的标题,我们可以使用标题作为我们模型学习的目标摘要!首先,让我们从 Hugging Face Hub 下载英语和西班牙语子集: +我们将使用 [多语言亚马逊评论语料库](https://huggingface.co/datasets/amazon_reviews_multi) 创建我们的双语摘要器。该语料库由六种语言的亚马逊产品评论组成,通常用于多语言分类器的基准测试。然而,由于每条评论都附有一个简短的标题,我们可以使用标题作为我们模型学习的参考摘要!首先,让我们从 Hugging Face Hub 下载英语和西班牙语子集: ```python from datasets import load_dataset @@ -62,7 +62,7 @@ DatasetDict({ }) ``` -如您所见,对于每种语言,都有 200,000 条评论 **train** 拆分,每个评论有 5,000 条评论 **validation** 和 **test** 分裂。我们感兴趣的评论信息包含在 **review_body** 和 **review_title** 列。让我们通过创建一个简单的函数来查看一些示例,该函数使用我们在[第五章](/course/chapter5)学到过: +如你所见,在英语数据集的 `train` 部分有 200,000 条评论, `validation` 和 `test` 部分有 5,000 条评论。我们感兴趣的评论正文和标题保存在 `review_body` 和 `review_title` 列中。让我们通过创建一个简单的函数来从训练集中随机抽取一些样本,该函数使用我们在 [第五章](/course/chapter5) 学到过: ```python def show_samples(dataset, num_samples=3, seed=42): @@ -88,16 +88,16 @@ show_samples(english_dataset) -✏️ **试试看!** 更改 `Dataset.shuffle()` 命令中的随机种子以探索语料库中的其他评论。 如果您是说西班牙语的人,请查看 `spanish_dataset` 中的一些评论,看看标题是否也像合理的摘要。 +✏️ **试试看!** 更改 `Dataset.shuffle()` 命令中的随机种子以探索语料库中的其他评论。如果你是说西班牙语的人,请查看 `spanish_dataset` 中的一些评论,看看标题是否像是合理的摘要。 -此示例显示了人们通常在网上找到的评论的多样性,从正面到负面(以及介于两者之间的所有内容!)。尽管标题为“meh”的示例信息量不大,但其他标题看起来像是对评论本身的体面总结。在单个 GPU 上训练所有 400,000 条评论的摘要模型将花费太长时间,因此我们将专注于为单个产品领域生成摘要。为了了解我们可以选择哪些域,让我们将 **english_dataset** 转换到 **pandas.DataFrame** 并计算每个产品类别的评论数量: +这个示例显示了人们通常在网上评论的多样性,从积极的到消极的(以及介于两者之间的评论!)。尽管带有“meh”标题的示例的信息量不大,但其他标题看起来像是对评论本身的不错的总结。在单个 GPU 上训练所有 400,000 条评论的摘要模型将花费太长时间,因此我们将专注于为单个产品领域生成摘要。为了了解我们可以选择哪些领域,让我们将 `english_dataset` 转换为 `pandas.DataFrame` ,并计算每个产品类别的评论数量: ```python english_dataset.set_format("pandas") english_df = english_dataset["train"][:] -# Show counts for top 20 products +# 显示前 20 个产品的数量 english_df["product_category"].value_counts()[:20] ``` @@ -125,7 +125,7 @@ book 3756 Name: product_category, dtype: int64 ``` -英语数据集中最受欢迎的产品是家居用品、服装和无线电子产品。不过,为了坚持亚马逊的主题,让我们专注于总结书籍的评论——毕竟,这是亚马逊这家公司成立的基础!我们可以看到两个符合要求的产品类别( **book** 和 **digital_ebook_purchase** ),所以让我们为这些产品过滤两种语言的数据集。正如我们在[第五章](/course/chapter5)学到的, 这 **Dataset.filter()** 函数允许我们非常有效地对数据集进行切片,因此我们可以定义一个简单的函数来执行此操作: +在英语数据集中,最受欢迎的产品是家居用品、服装和无线电子产品。不过,为了带有亚马逊的特色,让我们专注于总结书籍的评论——毕竟,这是亚马逊这家公司成立的基础!我们可以看到两个符合要求的产品类别( `book` 和 `digital_ebook_purchase` ),所以让我们用这两个产品类别过滤两种语言的数据集。正如我们在 [第五章](/course/chapter5) 学到的, `Dataset.filter()` 函数可以让我们非常有效地对数据集进行切片,所以我们可以定义一个简单的函数来进行此操作: ```python def filter_books(example): @@ -135,13 +135,13 @@ def filter_books(example): ) ``` -现在,当我们将此函数应用于 **english_dataset** 和 **spanish_dataset** ,结果将只包含涉及书籍类别的那些行。在应用过滤器之前,让我们将**english_dataset**的格式从 **pandas** 切换回到 **arrow** : +当我们使用这个函数对 `english_dataset` 和 `spanish_dataset` 过滤后,结果将只包含涉及书籍类别的那些行。在使用过滤器之前,让我们将 `english_dataset` 的格式从 `"pandas"` 切换回 `"arrow"` : ```python english_dataset.reset_format() ``` -然后我们可以应用过滤器功能,作为健全性检查,让我们检查评论样本,看看它们是否确实与书籍有关: +然后我们可以使用过滤器功能,作为一个基本的检查,让我们检查一些评论的样本,看看它们是否确实与书籍有关: ```python spanish_books = spanish_dataset.filter(filter_books) @@ -160,7 +160,7 @@ show_samples(english_books) '>> 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** 对象。因此,为了创建我们的双语数据集,我们将遍历每个拆分,连接该拆分的数据集,并打乱结果以确保我们的模型不会过度拟合单一语言: +好吧,我们可以看到评论并不是严格意义上的书籍,也可能是指日历和 OneNote 等电子应用程序等内容。尽管如此,该领域似乎也适合训练摘要模型。在我们查筛选适合此任务的各种模型之前,我们还有最后一点数据准备要做:将英文和西班牙文评论作为单个 `DatasetDict` 对象组合起来。🤗 Datasets 提供了一个方便的 `concatenate_datasets()` 函数,它(名如其实)将把两个 `Dataset` 对象堆叠在一起。因此,为了创建我们的双语数据集,我们将遍历数据集的每个部分,并打乱结果以确保我们的模型不会过度拟合单一语言: ```python from datasets import concatenate_datasets, DatasetDict @@ -173,7 +173,7 @@ for split in english_books.keys(): ) books_dataset[split] = books_dataset[split].shuffle(seed=42) -# Peek at a few examples +# 挑选一些样例 show_samples(books_dataset) ``` @@ -188,37 +188,37 @@ show_samples(books_dataset) '>> Review: igual que el anterior' ``` -这当然看起来像是英语和西班牙语评论的混合!现在我们有了一个训练语料库,最后要检查的一件事是评论中单词的分布及其标题。这对于摘要任务尤其重要,其中数据中的简短参考摘要会使模型偏向于仅在生成的摘要中输出一两个单词。下面的图显示了单词分布,我们可以看到有些标题严重偏向于 1-2 个单词: +这的确看起来像是混合了英语和西班牙语的评论!现在我们有了一个训练语料库,最后要检查的一件事是评论及其标题中单词的分布。这对于摘要任务尤其重要,其中数据中如果出现大量参考摘要过于简短会使模型偏向于生成的摘要中仅有一两个单词。下面的图中显示了单词分布,我们可以看到有些标题严重偏向于 1-2 个单词:
Word count distributions for the review titles and texts.
-为了解决这个问题,我们将过滤掉标题非常短的示例,以便我们的模型可以生成更有趣的摘要。由于我们正在处理英文和西班牙文文本,因此我们可以使用粗略的启发式方法在空白处拆分标题,然后使用我们可信赖的 **Dataset.filter()** 方法如下: +为了解决这个问题,我们将过滤掉标题非常短的示例,以便我们的模型可以生成更有效的摘要。由于我们正在处理英文和西班牙文文本,因此我们可以使用粗略的启发式方法在空白处拆分标题的单词,然后用我们强大的 `Dataset.filter()` 方法如下: ```python books_dataset = books_dataset.filter(lambda x: len(x["review_title"].split()) > 2) ``` -现在我们已经准备好了我们的语料库,让我们来看看一些可以对其进行微调的可能的 Transformer 模型! +现在我们已经准备好了我们的语料库,让我们来看看一些可以对其进行微调的可选的 Transformer 模型! ## 文本摘要模型 [[文本摘要模型]] -如果你仔细想想,文本摘要是一种类似于机器翻译的任务:我们有一个像评论这样的文本正文,我们希望将其“翻译”成一个较短的版本,以捕捉输入的显着特征。因此,大多数用于文本摘要的 Transformer 模型采用了我们在[第一章](/course/chapter1)遇到的编码器-解码器架构。尽管有一些例外,例如 GPT 系列模型,它们在few-shot(少量微调)之后也可以提取摘要。下表列出了一些流行的预训练模型,可以对其进行微调以进行汇总。 +如果你仔细想想,文本摘要是一种类似于机器翻译的任务:我们有一个像评论这样的文本正文,我们希望将其“翻译”成一个较短的版本,同时捕捉到输入文本的主要特征。因此,大多数用于文本摘要的 Transformer 模型采用了我们在 [第一章](/course/chapter1) 遇到的编码器-解码器架构。尽管有一些例外,例如 GPT 系列模型,它们在 few-shot(少量微调)之后也可以提取摘要。下表列出了一些可以进行摘要微调的流行预训练模型。 -| Transformer 模型 | 描述 | 多种语言? | +| Transformer 模型 | 描述 | 多种言?| | :---------: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-----------: | -| [GPT-2](https://huggingface.co/gpt2-xl) | 虽然训练为自回归语言模型,但您可以通过在输入文本末尾附加“TL;DR”来使 GPT-2 生成摘要。 | ❌ | -| [PEGASUS](https://huggingface.co/google/pegasus-large) | 在预训练是的目标是来预测多句子文本中的屏蔽句子。 这个预训练目标比普通语言建模更接近文本摘要,并且在流行的基准测试中得分很高。 | ❌ | -| [T5](https://huggingface.co/t5-base) | 通用的 Transformer 架构,在文本到文本的框架中制定所有任务; 例如,模型文本摘要的输入格式是`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 种语言。 | ✅ | +| [GPT-2](https://huggingface.co/gpt2-xl) | 虽然训练为自回归语言模型,但你可以通过在输入文本末尾附加“TL;DR”来使 GPT-2 生成摘要。| ❌ | +| [PEGASUS](https://huggingface.co/google/pegasus-large) | 在预训练时的目标是来预测多句子文本中的屏蔽句子。这个预训练目标比普通语言建模更接近文本摘要,并且在流行的基准测试中得分很高。| ❌ | +| [T5](https://huggingface.co/t5-base) | 通用的 Transformer 架构,所有任务都以文本到文本的框架进行描述;例如,模型文本摘要的输入格式是 `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 多种语言的文本上进行联合训练! +从此表中可以看出,大多数用于摘要的 Transformer 模型(以及大多数 NLP 任务)都是单一语言的。如果你的任务所使用的语言是“有大量语料库”(如英语或德语)的语言,这很好。但对于世界各地正在使用的数千种其他语言,则不然。幸运的是,有一类多语言 Transformer 模型,如 mT5 和 mBART,可以解决问题。这些模型也是使用因果语言建模进行预训练的,但有一点不同:它们不是在一种语言的语料库上训练,而是同时在 50 多种语言的文本上进行联合训练! -我们将使用 mT5,这是一种基于 T5 的有趣架构,在文本到文本框架中进行了预训练。在 T5 中,每个 NLP 任务都是根据提示前缀来制定的,例如 **summarize:** 这使模型使生成的文本适应提示。如下图所示,这让 T5 变得非常通用,因为你可以用一个模型解决很多任务! +我们将使用 mT5,这是一种基于 T5 的有趣架构,在文本到文本任务中进行了预训练。在 T5 中,每个 NLP 任务都是以任务前缀(如 `summarize:` )的形式定义的,模型根据不同的任务生成不同的文本。如下图所示,这让 T5 变得非常通用,因为你可以用一个模型解决很多任务!
@@ -226,11 +226,11 @@ books_dataset = books_dataset.filter(lambda x: len(x["review_title"].split()) >
-mT5 不使用前缀,但具有 T5 的大部分功能,并且具有多语言的优势。现在我们已经选择了一个模型,让我们来看看准备我们的训练数据。 +mT5 不使用前缀,但具有 T5 的大部分功能,并且具有多语言的优势。现在我们已经选择了一个模型,接下来让我们来看看如何准备我们的训练数据。 -✏️ **试试看!** 完成本节后,通过使用相同的技术对 mBART 进行微调,看看 mT5 与 mBART 相比有多好。 对于奖励积分,您还可以尝试仅在英文评论上微调 T5。 由于 T5 需要一个特殊的前缀提示,因此您需要在下面的预处理步骤中将“summarize:”添加到输入示例中。 +✏️ **试试看!** 完成本节后,可以尝试比较一下 mT5 和用相同技术微调过的 mBART 的性能。附加的挑战:只在英文评论上微调 T5。因为 T5 有一个特殊的前缀提示,你需要在下面的预处理步骤中将 `summarize:` 添加到输入例子前。 @@ -238,7 +238,7 @@ mT5 不使用前缀,但具有 T5 的大部分功能,并且具有多语言的 -我们的下一个任务是对我们的评论及其标题进行标记和编码。像往常一样,我们首先加载与预训练模型检查点相关的标记器。我们将使用 **mt5-small** 作为我们的检查点,以便我们可以在合理的时间内微调模型: +我们接下来的任务是对我们的评论及其标题进行 tokenize 和 encode 。通常,我们需要首先加载与预训练模型 checkpoint 相关的 tokenizer,这次我们将使用较小的 `mt5-small` 作为我们的 checkpoint 这样我们就可以在合理的时间消耗内对模型进行微调: ```python from transformers import AutoTokenizer @@ -249,11 +249,11 @@ tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) -💡在 NLP 项目的早期阶段,一个好的做法是在小样本数据上训练一类“小”模型。这使您可以更快地调试和迭代端到端工作流。一旦您对结果充满信心,您始终可以通过简单地更改模型检查点来在大规模数据上训练模型! +💡在 NLP 项目的早期阶段,一个好的做法是在小样本数据上训练一类“小”模型。这使你可以更快地调试和迭代端到端工作流。当你对结果有信心之后,你只需要通过简单地更改模型 checkpoint 就可以在较大规模数据上训练模型! -让我们在一个小例子上测试 mT5 标记器: +让我们在一个小样本上测试 mT5 tokenizer ```python inputs = tokenizer("I loved reading the Hunger Games!") @@ -264,7 +264,7 @@ inputs {'input_ids': [336, 259, 28387, 11807, 287, 62893, 295, 12507, 1], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]} ``` -在这里我们可以看到我们在[第三章](/course/chapter3)第一次微调实验中遇到的熟悉的 **input_ids** 和 **attention_mask** .让我们用分词器解码这些输入 ID ,可以**convert_ids_to_tokens()** 函数来查看我们正在处理什么样的标记器: +在这里我们可以看到熟悉的 `input_ids` 和 `attention_mask` ,我们在 [第3章](https://chat.openai.com/course/chapter3) 的第一次微调实验中遇到过。让我们使用 tokenizer 的 `convert_ids_to_tokens()` 函数解码这些输入 ID,看看我们正在处理的是什么类型的 tokenizer: ```python tokenizer.convert_ids_to_tokens(inputs.input_ids) @@ -274,9 +274,9 @@ tokenizer.convert_ids_to_tokens(inputs.input_ids) ['▁I', '▁', 'loved', '▁reading', '▁the', '▁Hung', 'er', '▁Games', ''] ``` -特殊的 Unicode 字符 `▁` 和序列结束标记 `` 表明我们正在处理 SentencePiece 分词器,它基于在[第六章](/course/chapter6)中讨论的Unigram分词算法. Unigram 对多语言语料库特别有用,因为它允许 SentencePiece 不知道重音、标点符号以及许多语言(如日语)没有空格字符。 +从特殊的 Unicode 字符 `▁` 和表示序列结束 `` token 可以看出来,我们正在使用基于 [第6章](https://chat.openai.com/course/chapter6) 中讨论的 Unigram 子词分词算法的 SentencePiece tokenizer 。 Unigram 对于多语言语料库特别有用,因为它让 SentencePiece 不必受口音、标点符号以及很多语言(如日语)没有空白字符的影响,只专注于找出最优的分词方式。 -为了标记我们的语料库,我们必须处理与摘要相关的微妙之处:因为我们的标签也是文本,所以它们可能超过模型的最大上下文大小。 这意味着我们需要对评论及其标题进行截断,以确保我们不会将过长的输入传递给我们的模型。 🤗 Transformers 中的分词器提供了一个绝妙的 `text_target` 参数,允许您将标签与输入并行分词。 以下是如何为 mT5 处理输入和目标的示例: +为了对我们的语料库 tokenize ,我们需要处理与摘要任务会遇到的一个细微问题:因为我们的输出目标也是文本,所以输入和输出加起来可能超过模型的最大上下文大小。这意味着我们需要对评论及其标题进行截断,以确保我们不会将过长的输入传递给我们的模型。🤗 Transformers 中的 tokenizer 提供了一个绝妙的 `text_target` 参数,允许你将目标文本与输入并行 tokenize。以下是如何为 mT5 处理输入和目标文本的示例: ```python max_input_length = 512 @@ -296,52 +296,51 @@ def preprocess_function(examples): return model_inputs ``` -让我们通过这段代码来了解发生了什么。我们做的第一件事是定义值 **max_input_length** 和 **max_target_length** ,它为我们的评论和标题的长度设置了上限。由于评论正文通常比标题大得多,我们相应地调整了这些值。 +让我们逐步解析这段代码,理解发生了什么。我们首先定义了 `max_input_length` 和 `max_target_length` 的值,这些值设定了我们的评论和标题的最大长度。由于评论主体通常比标题大得多,我们相应地调整了这些值。 -有了 `preprocess_function()`,我们在整个课程中广泛使用的方便的 `Dataset.map()` 函数来标记整个语料库是一件简单的事情: +通过 `preprocess_function()` 函数,我们可以使用我们在这门课程中广泛使用的方便的 `Dataset.map()` 函数,轻松地对整个语料库 tokenize 。 ```python tokenized_datasets = books_dataset.map(preprocess_function, batched=True) ``` -既然语料库已经预处理完毕,我们来看看一些常用的摘要指标。正如我们将看到的,在衡量机器生成的文本的质量方面没有灵丹妙药。 +既然语料库已经预处理完毕,我们来看看一些常用的摘要指标。正如我们在下面即将看到的,在衡量机器生成的文本的质量方面没有灵丹妙药。 -💡 你可能已经注意到我们在上面的 `Dataset.map()` 函数中使用了 `batched=True`。 这会以 1,000 个(默认)为单位对示例进行编码,并允许您利用 🤗 Transformers 中快速标记器的多线程功能。 在可能的情况下,尝试使用 `batched=True` 来加速您的预处理! +💡 你可能已经注意到我们在上面的 `Dataset.map()` 函数中使用了 `batched=True` 。这将以 1000(默认值)的 batch size 对示例继续编码,并让你可以利用 🤗 Transformers 中快速 tokenizer 的多线程功能。在可能的情况下,尝试使用 `batched=True` 来加速你的预处理! - -## 文本摘要的指标 [[文本摘要的指标]] +## 文本摘要的评估指标 [[文本摘要的评估指标]] -与我们在本课程中涵盖的大多数其他任务相比,衡量文本生成任务(如摘要或翻译)的性能并不那么简单。例如,对于“我喜欢阅读饥饿游戏”这样的评论,有多个有效摘要,例如“我喜欢饥饿游戏”或“饥饿游戏是一本好书”。显然,在生成的摘要和标签之间应用某种精确匹配并不是一个好的解决方案——即使是人类在这样的指标下也会表现不佳,因为我们都有自己的写作风格。 +与我们在本课程中涵盖的大多数其他任务相比,衡量文本生成任务(如摘要或翻译)的好坏并不那么简单。例如,对于“我喜欢阅读饥饿游戏”这样的评论,可能有多个有效摘要,例如“我喜欢饥饿游戏”或“饥饿游戏是一本好书”。显然,在生成的摘要和标签之间进行某种精确匹配并不是一个好的解决方案——即使是人类在这样的评估指标下也会表现不佳,因为每个人都有自己的写作风格。 -总而言之,最常用的指标之一是[ROUGE 分数](https://en.wikipedia.org/wiki/ROUGE_(metric))(Recall-Oriented Understudy for Gisting Evaluation 的缩写)。该指标背后的基本思想是将生成的摘要与一组通常由人类创建的参考摘要进行比较。为了更精确,假设我们要比较以下两个摘要: +总而言之,最常用的指标之一是[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_ 分数。。 +比较它们的一种方法是计算重叠单词的数量,在这个例子中为 6。然而,这种方法有些粗糙,因此 ROUGE 是基于计算计算重叠部分的 `精确度(Precision)` 和 `召回率(Recall)` 分数来计算的。 -🙋 如果这是您第一次听说精确率和召回率,请不要担心——我们将一起通过一些明确的示例来说明一切。 这些指标通常在分类任务中遇到,因此如果您想了解在该上下文中如何定义精确度和召回率,我们建议查看 scikit-learn [指南](https://scikit-learn.org/stable /auto_examples/model_selection/plot_precision_recall.html)。 +🙋 如果这是你第一次听说精确度(Precision)和召回率(Recall),请不要担心——我们将一起通过一些清晰的示例来理解它们。这些指标通常在分类任务中遇到,所以如果你想了解在分类任务中精确度(Precision)和召回率(Recall)是如何定义的,我们建议你查看 `scikit-learn` 的 [指南](https://scikit-learn.org/stable/auto_examples/model_selection/plot_precision_recall.html) 。 -对于 ROUGE,recall 衡量生成的参考摘要包含了多少参考摘要。如果我们只是比较单词,recall可以根据以下公式计算: +对于 ROUGE,召回率衡量的是参考摘要中被生成摘要捕获的内容量。如果我们只是比较单词,召回率可以按照以下公式计算: -$$ \mathrm{Recall} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, reference\, summary}} $$ +$$ \mathrm{召回率} = \frac{\mathrm{重叠词的数量}}{\mathrm{参考摘要中的总词数}} $$ -对于我们上面的简单例子,这个公式给出了 6/6 = 1 的完美召回率;即,参考摘要中的所有单词都已由模型生成。这听起来可能很棒,但想象一下,如果我们生成的摘要是“我真的很喜欢整晚阅读饥饿游戏”。这也将有完美的recall,但可以说是一个更糟糕的总结,因为它很冗长。为了处理这些场景,我们还计算了pecision,它在 ROUGE 上下文中衡量生成的摘要中有多少是相关的: +对于上面的那个例子,这个公式给出了 6/6 = 1 的完美召回率;即,参考摘要中的所有单词模型都生成出来了。这听起来可能很棒,但想象一下,如果我们生成的摘要是“我真的很喜欢整晚阅读饥饿游戏”。这也会有完美的 recall,但可以说这是一个更糟糕的总结,因为它很冗长。为了适应于这些场景,我们还计算了精确度,它在 ROUGE 上下文中衡量了生成的摘要中有多少是相关的: -$$ \mathrm{Precision} = \frac{\mathrm{Number\,of\,overlapping\, words}}{\mathrm{Total\, number\, of\, words\, in\, generated\, summary}} $$ +$$ \mathrm{精确度} = \frac{\mathrm{重叠词的数量}}{\mathrm{生成摘要中的总词数}} $$ -将此应用到我们的详细摘要中会得到 6/10 = 0.6 的精度,这比我们较短的摘要获得的 6/7 = 0.86 的精度要差得多。在实践中,通常计算精度和召回率,然后报告 F1-score(精度和召回率的调和平均值)。我们可以在 🤗 Datasets 中通过安装 **rouge_score** 包来计算他们: +详细摘要使用这种计算方法会得到 6/10 = 0.6 的精确度,这比较短的摘要获得的 6/7 = 0.86 的精确度要差得多。在实践中,通常会先计算计算精度和召回率,然后得到 F1 分数(精确度和召回率的调和平均数)。我们可以很容易地在🤗 Datasets 中通过安装 `rouge_score` 包来实现这些计算: ```py !pip install rouge_score @@ -355,7 +354,7 @@ import evaluate rouge_score = evaluate.load("rouge") ``` -然后我们可以使用 **rouge_score.compute()** 一次性计算所有指标的函数: +接着我们可以使用 `rouge_score.compute()` 函数来一次性计算所有的指标: ```python scores = rouge_score.compute( @@ -371,7 +370,8 @@ scores '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实际上计算了精度、召回率和 F1 分数的置信区间;这些是你可以在这里看到的 **low** , **mid** , 和 **high** 属性。此外,🤗 Datasets在比较生成摘要和参考摘要时,会根据不同类型的文本粒度计算各种 ROUGE 分数。这 **rouge1** 变体是一元组的重叠——这只是表达单词重叠的一种奇特方式,这正是我们上面讨论的度量标准。为了验证这一点,让我们输出 **mid** 的数值: +哇,这个输出中包含了很多信息——它们都代表什么意思呢?首先,🤗 Datasets 计算了精度、召回率和 F1 分数的置信区间;也些就是你在这里看到的 `low` 、 `mid` 和 `high` 属性。此外,🤗 Datasets 还计算了基于在比较生成摘要和参考摘要时的采用不同文本粒度的各种 ROUGE 得分。 `rouge1` 测量的是生成摘要和参考摘要中单个单词的重叠程度。 +为了验证这一点,让我们提取出我们得分的 `mid` 值: ```python scores["rouge1"].mid @@ -380,19 +380,19 @@ scores["rouge1"].mid ```python out Score(precision=0.86, recall=1.0, fmeasure=0.92) ``` -太好了,准确率和召回率匹配了!那么其他的 ROUGE 分数呢? **rouge2** 测量二元组之间的重叠(想想单词对的重叠),而 **rougeL** 和 **rougeLsum** 通过在生成的和参考摘要中查找最长的公共子串来测量最长的单词匹配序列。中的“总和” **rougeLsum** 指的是这个指标是在整个摘要上计算的,而 **rougeL** 计算为单个句子的平均值。 +太好了,精确度和召回率的数字都对上了!那么其他的 ROUGE 得分表示什么含义呢? `rouge2` 度量了二元词组(考虑单词对的重叠)之间的重叠,而 `rougeL` 和 `rougeLsum` 通过寻找生成的摘要和参考摘要中最长的公共子串来度量单词的最长匹配序列。 `rougeLsum` 中的“sum”指的是该指标是在整个摘要上计算的,而 `rougeL` 是指在各个句子上计算的平均值。 - ✏️ **试试看!** 创建您自己的生成和参考摘要示例,并查看生成的 ROUGE 分数是否与基于精确度和召回率公式的手动计算一致。 对于附加分,将文本拆分为二元组并比较“rouge2”指标的精度和召回率。 +✏️ **试试看!** 自己手动创建一个生成摘要和参考摘要,看看使用 evaluate 得出的 ROUGE 分数是否与基于精确度和召回率公式的手动计算一致。附加的挑战:将文本切分为长度为2的词组,并手动计算精度和召回率与 `rouge2` 指标的精确度和召回率进行对比。 -我们将使用这些 ROUGE 分数来跟踪我们模型的性能,但在此之前,让我们做每个优秀的 NLP 从业者都应该做的事情:创建一个强大而简单的baseline! +我们将使用这些 ROUGE 分数来跟踪我们模型的性能,但在此之前,让我们做每个优秀的 NLP 从业者都应该做的事情:创建一个强大而简单的 baseline! -### 创建强大的baseline [[创建强大的baseline]] +### 创建强大的 baseline [[创建强大的 baseline]] -文本摘要的一个常见基线是简单地取一篇文章的前三个句子,通常称为 _lead-3_ 基线。 我们可以使用句号(英文使用.)来跟踪句子边界,但这在"U.S." or "U.N."之类的首字母缩略词上会失败。所以我们将使用 `nltk` 库,它包含一个更好的算法来处理这些情况。 您可以使用 `pip` 安装软件包,如下所示: +对于文本摘要,一个常见的参考 baseline 是简单地取文章的前三句话作为摘要,通常称为 `lead-3` baseline。我们可以使用句号(英文使用.)来跟踪句子边界,但这在“U.S.” or “U.N.”之类的首字母缩略词上会计算错误。所以我们将使用 `nltk` 库,它包含一个更好的算法来处理这些情况。你可以使用以下方式安装该包: ```python !pip install nltk @@ -405,7 +405,8 @@ import nltk nltk.download("punkt") ``` -接下来,我们从 `nltk` 导入句子标记器并创建一个简单的函数来提取评论中的前三个句子。 文本摘要的约定是用换行符分隔每个摘要,因此我们也将其包含在内并在训练示例上对其进行测试: + +接下来,我们从 `nltk` 导入句子的 tokenizer 并创建一个简单的函数用来提取评论中的前三个句子。文本摘要的默认情况下使用换行符分隔每个摘要,因此我们也按照这样的规则处理,并在训练集的示例上对其进行测试: ```python from nltk.tokenize import sent_tokenize @@ -424,7 +425,7 @@ print(three_sentence_summary(books_dataset["train"][1]["review_body"])) 'She found Strangers.' ``` -这似乎有效,所以让我们现在实现一个函数,从数据集中提取这些“摘要”并计算baseline的 ROUGE 分数: +这似乎有效,接下来让我们现在实现一个函数,从数据集中提取这些“摘要”并计算 baseline 的 ROUGE 分数: ```python def evaluate_baseline(dataset, metric): @@ -432,7 +433,7 @@ def evaluate_baseline(dataset, metric): return metric.compute(predictions=summaries, references=dataset["review_title"]) ``` -然后我们可以使用这个函数来计算验证集上的 ROUGE 分数,并使用 Pandas 对它们进行一些美化: +然后我们可以使用这个函数来计算验证集上的 ROUGE 分数,并使用 Pandas 对输出的结果进行一些美化: ```python import pandas as pd @@ -447,13 +448,13 @@ rouge_dict {'rouge1': 16.74, 'rouge2': 8.83, 'rougeL': 15.6, 'rougeLsum': 15.96} ``` -我们可以看到`rouge2`的分数明显低于其他; 这可能反映了这样一个事实,即评论标题通常很简洁,因此lead-3 baseline过于冗长。 现在我们有了一个很好的基准,让我们将注意力转向微调 mT5! +我们可以看到 `rouge2` 的分数明显低于其他的rouge;这可能反映了这样一个事实,即评论标题通常很简洁,因此 `lead-3` baseline 过于冗长导致得分不高。现在我们有了一个很好的参考基准,让我们将注意力转向微调 mT5! {#if fw === 'pt'} -## 使用 `Trainer` API微调mT5 [[使用 `Trainer` API微调mT5]] +## 使用 `Trainer` API 微调 mT5 [[使用 `Trainer` API 微调 mT5]] -微调模型以进行提取摘要与我们在本章中介绍的其他任务非常相似。 我们需要做的第一件事是从`mt5-small`检查点加载预训练模型。 由于摘要提取是一个序列到序列的任务,我们可以使用 AutoModelForSeq2SeqLM 类加载模型,该类会自动下载并缓存权重: +微调模型来提取摘要与我们在本章中介绍的其他任务非常相似。我们需要做的第一件事是从 `mt5-small` checkpoint 中加载预训练模型。由于摘要提取是一个序列到序列的任务,我们可以使用 AutoModelForSeq2SeqLM 类加载模型,该类会自动下载并缓存模型权重: ```python from transformers import AutoModelForSeq2SeqLM @@ -463,9 +464,9 @@ model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) {:else} -## 使用 `Keras` API微调mT5 [[使用 `Keras` API微调mT5]] +## 使用 `Keras` API 微调 mT5 [[使用 `Keras` API 微调 mT5]] -微调模型以进行提取摘要与我们在本章中介绍的其他任务非常相似。 我们需要做的第一件事是从`mt5-small`检查点加载预训练模型。 由于摘要提取是一个序列到序列的任务,我们可以使用 `TFAutoModelForSeq2SeqLM` 类加载模型,该类会自动下载并缓存权重: +微调模型来提取摘要与我们在本章中介绍的其他任务非常相似。我们需要做的第一件事是从 `mt5-small` checkpoint 中加载预训练模型。由于摘要提取是一个序列到序列的任务,我们可以使用 `TFAutoModelForSeq2SeqLM` 类加载模型,该类会自动下载并缓存模型权重: ```python from transformers import TFAutoModelForSeq2SeqLM @@ -477,12 +478,11 @@ model = TFAutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) -💡 If you're wondering why you don't see any warnings about fine-tuning the model on a downstream task, that's because for sequence-to-sequence tasks we keep all the weights of the network. Compare this to our text classification model in [Chapter 3](/course/chapter3), where the head of the pretrained model was replaced with a randomly initialized network. -💡 如果您想知道为什么在下游任务中没有看到任何关于微调模型的警告,那是因为对于序列到序列的任务,我们保留了网络的所有权重。与我们在[第三章] (/course/chapter3)中的文本分类模型进行比较,文本分类模型预训练模型的头部被随机初始化的网络替换。 +💡 如果你想知道为什么在实例化的过程中没有看到任何关于微调模型的警告,那是因为对于序列到序列的任务,我们保留了网络的所有权重。与此相比,在 [第三章](https://chat.openai.com/course/chapter3) 中的文本分类模型中,我们用一个随机初始化的网络替换了预训练模型的头部。 -我们需要做的下一件事是登录 Hugging Face Hub。如果您在notebook中运行此代码,则可以使用以下实用程序函数执行此操作: +我们需要做的下一件事是登录 Hugging Face Hub。如果你在 notebook 中运行此代码,则可以使用以下实用程序函数进行此操作: ```python from huggingface_hub import notebook_login @@ -490,7 +490,7 @@ from huggingface_hub import notebook_login notebook_login() ``` -这将显示一个小部件,您可以在其中输入您的凭据。或者,您可以在终端中运行此命令并在那里登录: +这将显示一个小工具,你可以在其中输入你的凭据。或者,你可以在你的终端运行这条命令来登陆: ``` huggingface-cli login @@ -498,14 +498,14 @@ huggingface-cli login {#if fw === 'pt'} -我们需要生成摘要以便在训练期间计算 ROUGE 分数。幸运的是,🤗 Transformers 提供了专用的 `Seq2SeqTrainingArguments` 和 `Seq2SeqTrainer` 类,可以自动为我们完成这项工作! 为了了解它是如何工作的,让我们首先为我们的实验定义超参数和其他参数: +为了在训练期间计算 `ROUGE` 分数,我们需要在训练期间生成文本形式的摘要。幸运的是,🤗 Transformers 提供了专用的 `Seq2SeqTrainingArguments` 和 `Seq2SeqTrainer` 类,可以自动为我们完成这项工作!为了了解它是如何工作的,让我们首先为我们的实验定义超参数和其他参数,在后面的的训练过程会讲到如何实现的。 ```python from transformers import Seq2SeqTrainingArguments batch_size = 8 num_train_epochs = 8 -# Show the training loss with every epoch +# 每个训练周期都输出训练损失 logging_steps = len(tokenized_datasets["train"]) // batch_size model_name = model_checkpoint.split("/")[-1] @@ -524,11 +524,11 @@ args = Seq2SeqTrainingArguments( ) ``` -在这里, **predict_with_generate** 参数已设置为True表明我们应该在评估期间生成摘要,以便我们可以计算每个时期的 ROUGE 分数。正如在[第一章](/course/chapter1)所讨论的,解码器通过逐个预测令牌来执行推理,这是由模型的 **generate()** 方法实现的。设置 **predict_with_generate=True** 告诉 **Seq2SeqTrainer** 使用该方法进行评估。我们还调整了一些默认的超参数,例如学习率、epoch数和权重衰减,并且我们设置了 **save_total_limit** 训练期间最多只保存 3 个检查点的选项——这是因为即使是 mT5 的“small”版本也使用大约 1 GB 的硬盘空间,我们可以通过限制我们保存的副本数量来节省一点空间。 +在上面的代码中,我们把 `predict_with_generate` 参数设置为 `True` ,这样可以在评估期间生成摘要来计算每个 `epoch` 的 ROUGE 分数。正如在 [第一章](/course/chapter1) 中学到的,模型的 `generate()` 函数实现了使用解码器逐个预测单词来推理生成的文本。设置 `predict_with_generate=True` 后,`Seq2SeqTrainer` 会在评估时使用 `generate()` 函数。除此之外我们还调整默认的超参数,如学习率、`epochs` 数和权重衰减,并且设置 `save_total_limit` 选项, 使训练期间最多只能保存 3 个 `checkpoint` 的选项。——这是因为即使是 mT5 的“small”版本也使用大约 1 GB 的硬盘空间,我们可以通过限制保存的副本数量来节省一点空间。 -`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`。 +设置 `push_to_hub=True` 选项后 `Trainer` 会在训练后自动将模型推送到 Hub 中;你可以在 `output_dir` 指定的位置下的用户配置文件中找到对应的仓库。请注意,你可以使用 `hub_model_id` 参数指定要推送到的仓库的名称(特别是当你想要推送到组织时,就必须使用此参数)。例如,当我们将模型推送到 [`huggingface-course` 组织](https://huggingface.co/huggingface-course) 时,我们在 `Seq2SeqTrainingArguments` 中添加了 `hub_model_id="huggingface-course/mt5-finetuned-amazon-en-es"` 。 -我们需要做的下一件事是为训练器提供一个“compute_metrics()”函数,以便我们可以在训练期间评估我们的模型。 总结起来,这比简单地在模型的预测上调用 `rouge_score.compute()` 更复杂一些,因为我们需要在计算 ROUGE 分数之前将输出和标签解码为文本。 下面的函数正是这样做的,并且还利用 `nltk` 中的 `sent_tokenize()` 函数来用换行符分隔摘要语句: +为了在训练期间评估模型,我们还需要为 `Trainer` 提供一个 `compute_metrics()` 函数。对于摘要模型来说,不能直接调用 `rouge_score.compute()` 进行评估,因为我们需要将输出和参考摘要解码为文本,然后才能计算 ROUGE 分数。下面的函数就完成了解码和计算分数,除此之外还使用了 `nltk` 中的 `sent_tokenize()` 函数将摘要句子用换行符分隔开: ```python import numpy as np @@ -536,29 +536,29 @@ import numpy as np def compute_metrics(eval_pred): predictions, labels = eval_pred - # Decode generated summaries into text + # 将生成的摘要解码为文本 decoded_preds = tokenizer.batch_decode(predictions, skip_special_tokens=True) - # Replace -100 in the labels as we can't decode them + # 替换标签中的-100,因为我们无法解码它们 labels = np.where(labels != -100, labels, tokenizer.pad_token_id) - # Decode reference summaries into text + # 将参考摘要解码为文本 decoded_labels = tokenizer.batch_decode(labels, skip_special_tokens=True) - # ROUGE expects a newline after each sentence + # 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] - # Compute ROUGE scores + # 计算ROUGE分数 result = rouge_score.compute( predictions=decoded_preds, references=decoded_labels, use_stemmer=True ) - # Extract the median scores + # 计算ROUGE分数 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 是一个编码器-解码器 Transformer 模型,准备我们的批次的一个微妙之处是,在解码过程中,我们需要将标签向右移动一个。 这是为了确保解码器只看到之前的真实的标签,而不是当前或未来的标签,这对于模型来说很容易记忆。 这类似于在 [因果语言建模](/course/chapter7/6) 等任务中如何将掩蔽的自我注意应用于输入。 +接下来,我们需要为我们的序列到序列任务定义一个数据整理器(data collator)。由于 mT5 是一个编码器-解码器的 Transformer 模型,因此在将数据整理成 batch 时有一点需要注意,那就是在解码期间,我们需要将标签向右移动一个单位。这是为了确保解码器只看到之前的参考序列,而不是当前要预测的 `token` 或之后的参考序列,这样模型就能避免容易记住标签。这类似与在 [因果语言模型](https://chat.openai.com/course/chapter7/6) 这样的任务中使用掩码自注意力的机制类似。 -幸运的是,🤗 Transformers 提供了一个 `DataCollatorForSeq2Seq` 整理器,它将为我们动态填充输入和标签。 要实例化这个收集器,我们只需要提供 `tokenizer` 和 `model`: +幸运的是,🤗 Transformers 提供了一个 `DataCollatorForSeq2Seq` 整理器,它会动态地填充我们的输入和标签。我们只需要提供 `tokenizer` 和 `model` 既可实例化这个整理器: {#if fw === 'pt'} @@ -578,7 +578,7 @@ data_collator = DataCollatorForSeq2Seq(tokenizer, model=model, return_tensors="t {/if} -让我们看看这个整理器在输入一小批示例时会产生什么。 首先,我们需要删除带有字符串的列,因为整理器不知道如何填充这些元素: +让我们看看当给这个整理器提供一个小批次的样本时,它的处理过程是怎么样的。首先,我们需要删除带有字符串的列,因为整理器不知道如何对这些元素进行填充(padding): ```python tokenized_datasets = tokenized_datasets.remove_columns( @@ -586,7 +586,7 @@ tokenized_datasets = tokenized_datasets.remove_columns( ) ``` -由于 collator 需要一个 `dict` 的列表,其中每个 `dict` 代表数据集中的一个示例,我们还需要在将数据传递给 data collator 之前将数据整理成预期的格式: +由于 collator 需要一个 `dict` 的列表,其中每个 `dict` 代表数据集中的一个样本,所以我们也需要在将数据传给数据整理器之前,将数据整理成预期的格式: ```python features = [tokenized_datasets["train"][i] for i in range(2)] @@ -609,11 +609,11 @@ data_collator(features) [ 0, 259, 27531, 13483, 259, 7505]])} ``` -这里要注意的主要是第一个例子比第二个例子要长,所以第二个例子的 `input_ids` 和 `attention_mask` 已经在右侧填充了一个 `[PAD]` 标记(其 ID 是 ` 0`)。 类似地,我们可以看到 `labels` 已用 `-100` 填充,以确保填充标记被损失函数忽略。 最后,我们可以看到一个新的 `decoder_input_ids`,它通过在第一个条目中插入 `[PAD]` 标记将标签向右移动。 +首先要注意的是,第二个例子比第一个例子要长,所以第一个例子的 `input_ids` 和 `attention_mask` 在右边用 `[PAD]` token (ID 为 `0` )进行了填充。同样,我们可以看到标签也使用 `-100` 进行了填充,以确保填充的 `tokens` 被损失函数忽略。最后,我们可以看到多了一个新的 `decoder_input_ids` 字段,它是通过在第一个条目中插入 `[PAD]` `tokens` 来将标签向右移动一个 token 形成的。 {#if fw === 'pt'} -我们终于拥有了训练所需的所有的前期准备!我们现在只需要使用标准参数实例化训练器: +我们终于拥有了训练所需的所有的前期准备!我们现在只需要使用标准参数实例化 Trainer ```python from transformers import Seq2SeqTrainer @@ -629,13 +629,13 @@ trainer = Seq2SeqTrainer( ) ``` -并启动我们的训练: +然后启动我们的训练: ```python trainer.train() ``` -在训练期间,您应该会看到训练损失减少并且 ROUGE 分数随着每个 epoch 增加。训练完成后,您可以通过运行**Trainer.evaluate()** 查看最终的 ROUGE 分数 : +在训练期间,应该可以看到训练损失逐渐减小,并且 ROUGE 分数随着 epoch 的增加而增加。训练完成之后,你可以通过运行 `Trainer.evaluate()` 来查看最后的 ROUGE 分数: ```python trainer.evaluate() @@ -653,7 +653,7 @@ trainer.evaluate() 'eval_steps_per_second': 4.914} ``` -从分数中我们可以看到,我们的模型轻松超过了我们的lead-3 baseline——很好!最后要做的是将模型权重推送到 Hub,如下所示: +从分数中我们可以看到,我们的模型轻松超过了我们的 `lead-3` baseline——很好!最后要做的是将模型权重推送到 Hub,如下所示: ``` trainer.push_to_hub(commit_message="Training complete", tags="summarization") @@ -663,13 +663,15 @@ 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,因此您可以轻松查看对模型存储库所做的更改! -在结束本节之前,让我们看一下如何使用 🤗 Accelerate 提供的底层API对 mT5 进行微调。 +上面的代码会把 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} -我们几乎准备好训练了! 我们只需要使用我们上面定义的数据整理器将我们的数据集转换为 tf.data.Dataset ,然后 `compile()` 和 `fit()` 模型。 首先,转换数据集: +我们几乎准备好训练所需的所有东西了!我们只需要使用我们上面定义的数据整理器将我们的数据集转换为 `tf.data.Dataset` ,然后 `compile()` 和 `fit()` 模型。首先,转换数据集: ```python tf_train_dataset = model.prepare_tf_dataset( tokenized_datasets["train"], @@ -691,9 +693,10 @@ tf_eval_dataset = model.prepare_tf_dataset( from transformers import create_optimizer import tensorflow as tf -# 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. +# 训练步数是数据集中的样本数量,除以 batch 大小,然后乘以总的 epoch 数。 +# 注意这里的 tf_train_dataset 是 batch 形式的 tf.data.Dataset, +# 而不是原始的 Hugging Face Dataset ,所以使用 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] @@ -707,11 +710,12 @@ optimizer, schedule = create_optimizer( model.compile(optimizer=optimizer) -# Train in mixed-precision float16 +# 使用 float16 混合精度进行训练 tf.keras.mixed_precision.set_global_policy("mixed_float16") ``` -最后,我们拟合模型。 我们在每个 epoch 之后使用`PushToHubCallback`将模型保存到 Hub,这将允许我们稍后使用它进行推理: +最后,训练模型。我们添加了 `PushToHubCallback` ,它会在每个 epoch 后将模型上传到 Hub,这样我们就可以在后面用上传好的模型来进行推理: + ```python from transformers.keras_callbacks import PushToHubCallback @@ -724,7 +728,9 @@ model.fit( ) ``` -我们在训练期间得到了一些loss,但实际上我们希望看到我们之前计算的 ROUGE 指标。 要获得这些指标,我们需要从模型生成输出并将它们转换为字符串。 让我们为 ROUGE 指标构建一些标签和预测列表以进行比较(请注意,如果您遇到此部分的导入错误,您可能需要`!pip install tqdm`)。 我们还将使用一个可以显着提高性能的技巧 - 使用 TensorFlow 的加速线性代数编译器 [XLA](https://www.tensorflow.org/xla) 编译我们的生成代码。 XLA 对模型的计算图应用了各种优化,并显着提高了速度和内存使用率。 正如 Hugging Face [博客](https://huggingface.co/blog/tf-xla-generate) 中所述,当我们的输入形状变化不大时,XLA 效果最佳。 为了处理这个问题,我们将输入填充为 128 的倍数,并使用填充整理器创建一个新数据集,然后我们将 @tf.function(jit_compile=True) 装饰器应用于我们的生成函数,它会将整个函数标记为使用 XLA 进行编译。 +我们在训练期间看到了一些损失值,但没有看到之前计算的 `ROUGE` 分数。要获得这些指标,需要先获取模型的输出结果并将其转换为字符串。然后构建参考摘要和预测的列表,接下来就可以计算 `ROUGE` 分数了(请注意,如果在这一部分遇到缺少 `tqdm` 库,则可能需要执行 `!pip install tqdm` )。 + +我们还将使用一种可以显着提高性能的技巧 —— 使用 TensorFlow 的线性代数加速编译器 [XLA](https://www.tensorflow.org/xla) 编译我们的代码。XLA 对模型的计算图进行了各种优化,并显着提高了速度和内存使用率。正如 Hugging Face [博客](https://huggingface.co/blog/tf-xla-generate) 中所述,当我们的输入形状变化不大时,XLA 效果最佳。为此,我们将输入填充到 128 的倍数,并使用填充整理器创建一个新数据集,然后使用 `@tf.function(jit_compile=True)` 装饰器装饰我们的生成函数,这将把整个函数标记为用 XLA 编译。 ```python from tqdm import tqdm @@ -766,7 +772,7 @@ for batch, labels in tqdm(tf_generate_dataset): all_labels.extend(decoded_labels) ``` -一旦我们有了标签和预测字符串列表,计算 ROUGE 分数就很容易了: +我们有了参考摘要和模型输出预测的字符串的列表之后,计算 ROUGE 分数就很容易了: ```python result = rouge_score.compute( @@ -787,24 +793,17 @@ result = {key: value.mid.fmeasure * 100 for key, value in result.items()} ## 使用 🤗 Accelerate 微调 mT5 [[使用 🤗 Accelerate 微调 mT5]] -使用 🤗 Accelerate 微调我们的模型与我们在 [Chapter 3](/course/chapter3) 中遇到的文本分类示例非常相似。 主要区别在于需要在训练期间显式生成摘要并定义我们如何计算 ROUGE 分数(回想一下,`Seq2SeqTrainer` 为我们生成了摘要)。 让我们看看我们如何在 🤗 Accelerate 中实现这两个要求! +使用 🤗 Accelerate 微调我们的模型与我们在 [第三章](/course/chapter3) 中遇到的文本分类示例非常相似。与文本分类的主要区别在于摘要模型需要在训练期间显式生成摘要并实现 ROUGE 分数的计算(请记住, `Seq2SeqTrainer` 已经为我们实现了生成摘要的部分)。让我们看看我们如何在 🤗 Accelerate 中实现这两个要求! -### 为训练做好一切准备 [[为训练做好一切准备]] +### 为训练做好准备 [[为训练做好准备]] -The first thing we need to do is create a `DataLoader` for each of our splits. Since the PyTorch dataloaders expect batches of tensors, we need to set the format to `"torch"` in our datasets: -我们需要做的第一件事是为每个数据集的每一个拆分创建一个`DataLoader`。 由于 PyTorch 数据加载器需要成批的张量,我们需要在数据集中将格式设置为`torch`: +首先,我们需要为每个数据分组创建一个 `DataLoader` 。由于 PyTorch 的 dataloaders 的输入是由张量组成的 batch,所以我们需要将数据集的格式设定为 `"torch"` : ```python tokenized_datasets.set_format("torch") ``` -现在我们已经有了仅由张量组成的数据集,接下来要做的是再次实例化`DataCollatorForSeq2Seq`。 为此,我们需要提供模型微调前的版本,所以让我们从缓存中再次加载它: - -```python -model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) -``` - -然后我们可以实例化数据整理器并使用它来定义我们的数据加载器: +然后我们可以实例化数据整理器,并使用它来定义我们的 DataLoader: ```python from torch.utils.data import DataLoader @@ -821,7 +820,7 @@ eval_dataloader = DataLoader( ) ``` -接下来要做的是定义我们想要使用的优化器。与我们的其他示例一样,我们将使用 **AdamW** ,这适用于大多数问题: +接下来,我们需要定义我们要使用的优化器。与我们的其他例子一样,我们将使用 `AdamW` ,这个优化器大多数场景下都很有效: ```python from torch.optim import AdamW @@ -829,7 +828,13 @@ from torch.optim import AdamW optimizer = AdamW(model.parameters(), lr=2e-5) ``` -最后,我们将模型、优化器和数据加载器提供给 **accelerator.prepare()** 方法: +为了重新开始微调,而不是从上面微调过的模型继续微调,我们需要重新实例化 model。 + +```python +model = AutoModelForSeq2SeqLM.from_pretrained(model_checkpoint) +``` + +最后,我们将模型、优化器和 dataloaders 输入到 `accelerator.prepare()` 方法中: ```python from accelerate import Accelerator @@ -842,15 +847,15 @@ model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( -🚨如果您在 TPU 上进行训练,则需要将上述所有代码移动到专门的训练函数中。有关详细信息,请参阅[第三章](/course/chapter3)。 +🚨如果你在 TPU 上进行训练,则需要将上述所有代码移动到专门的训练函数中。有关 TPU 的详细信息,请回顾 [第三章](/course/chapter3) 。 -现在我们已经准备好了我们索要用的对象,还有三件事要做: +现在我们已经准备好了我们的对象,还有三个事情需要做 * 定义学习率调度计划。 -* 实现一个功能来对摘要进行后续处理以进行评估。 -* 在 Hub 上创建一个存储库,我们可以将模型推送到该存储库。 +* 实现一个功能来对模型输出的摘要进行后续处理以进行评估。 +* 在 Hub 上创建一个模型仓库,我们可以将模型推送到该仓库。 对于学习率调度,我们将使用前几节中的标准线性衰减: @@ -869,23 +874,24 @@ lr_scheduler = get_scheduler( ) ``` -对于后续处理,我们需要一个函数,将生成的摘要拆分为由换行符分隔的句子。 这是 ROUGE 指标所期望的格式,我们可以使用以下代码片段来实现: +对于后续处理,我们需要一个函数,将生成的摘要拆分为由换行符分隔的句子。这是 ROUGE 指标需要的输入格式,我们可以使用以下代码片段来实现: ```python def postprocess_text(preds, labels): preds = [pred.strip() for pred in preds] labels = [label.strip() for label in labels] - # ROUGE expects a newline after each sentence + # 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 ``` -如果你还记得我们是如何定义 `Seq2SeqTrainer` 的 `compute_metrics()` 函数的,这对你来说应该很熟悉。 +如果你还记得我们是如何定义 `Seq2SeqTrainer` 的 `compute_metrics()` 函数,那么你应该对上述的代码来感到很熟悉。 + +最后,我们需要在 Hugging Face Hub 上创建一个模型仓库。为此,我们可以使用名为🤗 Hub 的 python 库。我们只需要为我们的仓库取一个 ID,Hub 库中有一个实用的函数可以将仓库 ID 与用户 ID 组合起来: -最后,我们需要在 Hugging Face Hub 上创建一个模型存储库。 为此,我们可以使用🤗 Hub 库的get_full_repo_name。 我们只需要为我们的存储库定义一个名称,该库有一个非常好用的函数可以将存储库 ID 与用户配置文件结合起来: ```python from huggingface_hub import get_full_repo_name @@ -898,7 +904,7 @@ repo_name 'lewtun/mt5-finetuned-amazon-en-es-accelerate' ``` -现在我们可以使用这个存储库名称将本地版本克隆到我们的结果目录中,该目录将存储训练的模型: +现在我们可以将这个仓库克隆到模型保存的路径中,该目录将存储训练生成的文件: ```python from huggingface_hub import Repository @@ -906,16 +912,17 @@ from huggingface_hub import Repository output_dir = "results-mt5-finetuned-squad-accelerate" repo = Repository(output_dir, clone_from=repo_name) ``` -这将允许我们在训练期间通过调用 `repo.push_to_hub()` 方法将模型推送到 Hub! 现在让我们通过写出完整的训练循环来结束我们的分析。 + +这样,我们就可以在训练期间通过调用 `repo.push_to_hub()` 方法将模型推送到 Hub!现在让我们通过写出完整的训练循环来结束我们的分析。 ### 训练循环 [[训练循环]] -文本摘要的训练循环与我们遇到的其他 🤗 Accelerate 示例非常相似,大致分为四个主要步骤:这 +文本摘要的训练循环与我们遇到的其他 🤗 Accelerate 示例非常相似,大致分为四个主要步骤: -1. 通过在每个epoch 迭代 `train_dataloader` 中的所有示例来训练模型。 -2. 在每个 epoch 结束时生成模型摘要,首先生成标记,然后将它们(和参考摘要)解码为文本。 -3. 使用我们之前看到的相同技术计算 ROUGE 分数。 -4. 保存检查点并将所有内容推送到 Hub。 在这里,我们依赖 `Repository` 对象的巧妙的 `blocking=False` 参数,以便我们可以在每个 epoch 异步地上传检查点。 这使我们能够继续训练,而不必等待与 GB 大小的模型慢呼呼的上传! +1. 通过在每个 epoch 迭代 `train_dataloader` 中的所有示例来训练模型。 +2. 在每个 epoch 结束时生成摘要,首先生成 tokens 然后将它们(和参考摘要)解码为文本。 +3. 使用我们之前的方法计算 ROUGE 分数。 +4. 保存 checkpoint 并将所有内容推送到 Hub。在这里,我们依赖 `Repository` 对象的巧妙的 `blocking=False` 参数,以便我们可以在每个 epoch 异步地上传 checkpoint,这使我们能够继续训练,而不必等待与 GB 大小的模型慢呼呼的上传! 这些步骤可以在以下代码块中看到: @@ -927,7 +934,7 @@ import numpy as np progress_bar = tqdm(range(num_training_steps)) for epoch in range(num_train_epochs): - # Training + # 训练 model.train() for step, batch in enumerate(train_dataloader): outputs = model(**batch) @@ -939,7 +946,7 @@ for epoch in range(num_train_epochs): optimizer.zero_grad() progress_bar.update(1) - # Evaluation + # 评估 model.eval() for step, batch in enumerate(eval_dataloader): with torch.no_grad(): @@ -953,7 +960,7 @@ for epoch in range(num_train_epochs): ) labels = batch["labels"] - # If we did not pad to max length, we need to pad the labels too + # 如果我们没有填充到最大长度,我们需要填充标签 labels = accelerator.pad_across_processes( batch["labels"], dim=1, pad_index=tokenizer.pad_token_id ) @@ -961,7 +968,7 @@ for epoch in range(num_train_epochs): generated_tokens = accelerator.gather(generated_tokens).cpu().numpy() labels = accelerator.gather(labels).cpu().numpy() - # Replace -100 in the labels as we can't decode them + # 替换标签中的 -100,因为我们无法解码它们 labels = np.where(labels != -100, labels, tokenizer.pad_token_id) if isinstance(generated_tokens, tuple): generated_tokens = generated_tokens[0] @@ -976,14 +983,14 @@ for epoch in range(num_train_epochs): rouge_score.add_batch(predictions=decoded_preds, references=decoded_labels) - # Compute metrics + # 计算评估的 loss result = rouge_score.compute() - # Extract the median ROUGE scores + # 提取中位 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) - # Save and upload + # 保存和上传 accelerator.wait_for_everyone() unwrapped_model = accelerator.unwrap_model(model) unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) @@ -1007,13 +1014,13 @@ Epoch 8: {'rouge1': 13.96, 'rouge2': 6.5998, 'rougeL': 13.9123, 'rougeLsum': 13. Epoch 9: {'rouge1': 14.1192, 'rouge2': 7.0059, 'rougeL': 14.1172, 'rougeLsum': 13.9509} ``` -就是这样! 运行此程序后,您将获得与我们使用“Trainer”获得的模型和结果非常相似的模型和结果。 +就是这样!运行此程序后,你将获得与我们使用“Trainer”获得的模型和结果非常相似的模型和结果。 {/if} -## 使用您微调的模型 [[使用您微调的模型]] +## 使用你微调的模型 [[使用你微调的模型]] -将模型推送到 Hub 后,您可以通过推理小部件或“管道”对象来使用它,如下所示: +将模型推送到 Hub 后,你可以通过推理小部件或 `pipeline` 对象来使用它,如下所示: ```python from transformers import pipeline @@ -1022,7 +1029,7 @@ hub_model_id = "huggingface-course/mt5-small-finetuned-amazon-en-es" summarizer = pipeline("summarization", model=hub_model_id) ``` -我们可以将测试集中的一些示例(模型还没有看到)提供给我们的管道,以了解生成摘要的质量。 首先让我们实现一个简单的函数来一起显示评论、标题和生成的摘要: +我们可以将测试集(模型还没有见过的一些数据)中取一些样本提供给我们的管道,来感受一下生成的摘要的质量。首先让我们实现一个简单的函数,同时显示评论、标题和生成的摘要: ```python def print_summary(idx): @@ -1034,7 +1041,7 @@ def print_summary(idx): print(f"\n'>>> Summary: {summary}'") ``` -让我们看一下我们得到的一个英文例子: +让我们看一下其中一个英文摘要的例子: ```python print_summary(100) @@ -1048,7 +1055,7 @@ print_summary(100) '>>> Summary: Nothing special at all about this product' ``` -这还不错! 我们可以看到,我们的模型实际上已经能够通过增加部分新词来执行抽象摘要。 也许我们模型最酷的方面是它是双语的,所以我们还可以生成西班牙语评论的摘要: +这还不错!我们可以看到,我们的模型实际上已经能够通过增加部分新词来生成总结的摘要了。我们模型最酷的方面是它是双语的,所以我们还可以生成西班牙语评论的摘要: ```python print_summary(0) @@ -1062,6 +1069,6 @@ print_summary(0) '>>> Summary: Muy facil de leer' ``` -摘要翻译成了英文的“非常容易阅读”,在这种情况下,我们可以看到它是直接从评论中提取的。 这显示了 mT5 模型的多功能性,并让您体验了处理多语言语料库的感觉! +在这个例子中生成的摘要翻译成中文的意思是“非常容易阅读”,我们可以看到它是直接从评论中提取的。同时,这个例子还展现了mT5 模型的多种功能特性,并支持处理多语言的语料库! -接下来,我们将把注意力转向稍微复杂的任务:从头开始训练语言模型。 +接下来,我们将尝试一个稍微复杂一点的任务:从头开始训练一个语言模型。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter7/6.mdx b/chapters/zh-CN/chapter7/6.mdx index 6cf1a67b6..83028b05f 100644 --- a/chapters/zh-CN/chapter7/6.mdx +++ b/chapters/zh-CN/chapter7/6.mdx @@ -22,22 +22,23 @@ {/if} -到目前为止,我们主要使用预训练模型,并通过重用预训练的权重来针对新用例对它们进行微调。正如我们在[第一章](/course/chapter1), 这通常称为迁移学习,这是将 Transformer 模型应用于大多数标记数据稀疏的现实世界用例的非常成功的策略。在本章中,我们将采用不同的方法并从头开始训练一个全新的模型。如果您有大量数据并且它与用于可用模型的预训练数据有很大不同,那么这是一个很好的方法。然而,它也需要更多的计算资源来预训练语言模型,而不仅仅是微调现有的模型。训练新模型有意义的示例包括由音符、分子序列(如 DNA)或编程语言组成的数据集。后者最近受到关注,这要归功于 TabNine 和 GitHub 的 Copilot 等工具,它们由 OpenAI 的 Codex 模型提供支持,可以生成长代码序列。这种文本生成任务最好使用自回归或因果语言模型(例如 GPT-2)来解决。 +到目前为止,我们主要使用预训练模型,并通过复用预训练的权重,然后使用新的数据对它们进行微调,以适应新的应用场景。正如我们在 [第一章](/course/chapter1) 中看到的,这通常称为 `迁移学习(transfer learning)` ,对于大多数标注数据稀缺的应用场景,它是一种将 Transformer 模型应用到大部分真实的应用场景中的一个非常成功的策略。在本章中,我们将采用不同的方法并从头开始训练一个全新的模型。如果你有大量数据而且这些数据与可用模型的预训练数据差异很大,那么这是一个很好的方法。然而,相比仅微调现有模型,预训练语言模型需要更多的计算资源。训练一个新模型可能是有意义的示例包括由音乐符号、DNA 等分子序列或编程语言组成的数据集。编程语言组成的数据集最近广泛地受到关注,这要归功于 TabNine 和 GitHub 的 Copilot 等工具的流行,它们由 OpenAI 的 Codex 模型提供支持,可以生成长代码序列。这种文本生成任务最适合使用自回归或因果语言模型(例如 GPT-2)。 -在本节中,我们将构建代码生成模型的缩小版本:我们将使用 Python 代码的子集专注于单行完成而不是完整的函数或类。在 Python 中处理数据时,您会经常接触 Python 数据科学堆栈,包括 `matplotlib` , `seaborn` , `pandas` , 和 `scikit-learn` 库。在使用这些框架时,通常需要查找特定的命令,因此如果我们可以使用模型来为我们完成这些调用,那就太好了。 +在这一节,我们将构建一个精简版的代码生成模型:使用 Python 代码的一个数据集,来实现一行代码的补全,而不是直接生成完整的函数或类。当你使用 Python 处理数据时,你经常会接触到 Python 数据科学栈,包括 `matplotlib` , `seaborn` , `pandas` ,和 `scikit-learn` 这些库。当使用这些框架时,经常需要查找特定的命令,如果我们能够用模型来自动给出恰当的推荐命令就太好了! -在[第六章](/course/chapter6) 我们创建了一个高效的分词器来处理 Python 源代码,但我们仍然需要一个大规模数据集来预训练模型。在这里,我们将我们的分词器应用到源自 GitHub 存储库的 Python 代码语料库。然后我们将使用 `Trainer` API 和 🤗 Accelerate 来训练模型。让我们开始吧! +在 [第六章](/course/chapter6) 中,我们创建了一个高效的 tokenizer 来处理 Python 源代码,但我们还需要一个大规模的数据集来预训练模型。在这里,我们将使用 tokenizer 处理一个来自 GitHub 仓库的 Python 代码语料库。然后,我们将使用 Trainer API 和 🤗 Accelerate 来训练模型。让我们开始吧! -这实际上展示了使用本节中训练并上传到 Hub 的模型。你可以在[这里](https://huggingface.co/huggingface-course/codeparrot-ds?text=plt.imshow%28)找到。请注意,由于在文本生成过程中发生了一些随机化,您可能会得到略有不同的结果。 +这里展示的是一个已经训练并上传到 Hub 的模型,它就是使用本节中的代码训练的。你可以在 [这里](https://huggingface.co/huggingface-course/codeparrot-ds?text=plt.imshow%28) 找到它。注意,由于文本生成过程中有一些随机性,你可能会得到稍微不同的结果。 + ## 收集数据 [[收集数据]] -Python 代码可以从 GitHub 等代码存储库中获得,我们可以通过抓取每个 Python 存储库来使用它们来创建数据集。这是在[Transformers textbook](https://learning.oreilly.com/library/view/natural-language-processing/9781098136789/)预训练大型的GPT-2 模型。使用大约 180 GB 的 GitHub 转储,其中包含大约 2000 万个 Python 文件,称为 `codeparrot` ,作者构建了一个数据集,然后在[Hugging Face Hub](https://huggingface.co/datasets/transformersbook/codeparrot)上分享出来了. +我们可以从诸如 GitHub 这样的代码仓库中获取丰富的 Python 代码,通过对每个 Python 仓库进行抓取,我们就可以创建一个数据集。这就是在 [Transformers textbook](https://learning.oreilly.com/library/view/natural-language-processing/9781098136789/) 中预训练一个大型 GPT-2 模型的方法。开发者整理了名为 `codeparrot` 的一个大约为 180GB 的 GitHub 数据集, 其中包含大约 2,000 万个的Python 文件。 开发者用这些文件构建了一个数据集,并在 [Hugging Face Hub](https://huggingface.co/datasets/transformersbook/codeparrot) 上分享了这个数据集。 -然而,对完整语料库的训练既耗时又费力,我们只需要与 Python 数据科学堆栈相关的数据集子集。所以,让我们开始过滤 `codeparrot` 包含此堆栈中任何库的所有文件的数据集。由于数据集的太大,我们希望避免下载它;因此反,我们将使用流功能来动态过滤它。为了使用前面提到的库过滤代码示例,我们将使用以下函数: +然而,使用完整语料库的训练既耗时又费力,我们只需要找到 Python 数据科学栈相关的数据集子集。所以,让我们从 `codeparrot` 数据集中筛选出包含这个栈中所有相关库的所有文件。由于数据集的太大,我们希望避免直接把全部的数据集下载下来;因此,我们将使用流式传输的方法来动态过滤它。为了使用上述的库来筛选代码样本,我们将使用以下函数: ```py def any_keyword_in_string(string, keywords): @@ -63,7 +64,7 @@ print( False True ``` -我们可以使用它来创建一个函数来流式传输数据集并过滤我们想要的元素: +我们可以使用这个函数来创建一个新的函数,该函数将流式传输数据集并过滤我们想要的元素: ```py from collections import defaultdict @@ -83,11 +84,10 @@ def filter_streaming_dataset(dataset, filters): return Dataset.from_dict(filtered_dict) ``` -然后我们可以简单地将此函数应用于流数据集: +然后我们可以直接使用这里函数流式处理数据集: ```py -# This cell will take a very long time to execute, so you should skip it and go to -# the next one! +# 执行这个代码块需要非常长的时间,因此你可以跳过它,继续执行下一个! from datasets import load_dataset split = "train" # "valid" @@ -101,7 +101,9 @@ filtered_data = filter_streaming_dataset(data, filters) 3.26% of data after filtering. ``` -这给我们留下了大约 3% 的原始数据集,这个数据集仍然相当可观——结果数据集有 6 GB,包含 600,000 个 Python 脚本!过滤完整数据集可能需要 2-3 小时,具体取决于您的机器和带宽。如果您不想自己经历这个漫长的过程,我们在 Hub 上提供过滤后的数据集供您下载: +完成这个操作后,我们过滤后的数据集只有原始数据集的大约 3%,但这仍然是相当可观的大小——最终的数据集是 6GB,由 600,000 个 Python 脚本组成! + +过滤完整的数据集可能需要 2-3 小时,这取决于你的机器性能和带宽。如果你不想亲自经历这个漫长的过程,我们在 Hub 上提供了过滤后的数据集供你下载: ```py from datasets import load_dataset, DatasetDict @@ -132,12 +134,6 @@ DatasetDict({ }) ``` - - -预训练语言模型需要一段时间。我们建议您首先通过取消注释以上两行的注释对数据样本运行训练循环,并确保训练成功完成并存储模型。没有什么比最后一步的训练失败更令人沮丧的了,因为你忘记创建一个文件夹或者因为保存路径在训练循环结束时有一个错字! - - - 让我们看一个来自数据集的例子。我们将只显示每个字段的前 200 个字符: ```py @@ -164,23 +160,22 @@ from .murmurhash import murm LICENSE: bsd-3-clause''' ``` -我们可以看到 `content` 字段包含我们希望我们的模型训练的代码。现在我们有了一个数据集,我们需要预处理文本,使其采用适合预训练的格式。 +我们可以看到, `content` 字段包含了我们希望模型训练的代码。有了这个数据集之后,我们需要对文本进行一些处理,以便它们适合于预训练。 ## 准备数据集 [[准备数据集]] -第一步是对数据进行标记,以便我们可以将其用于训练。由于我们的目标主要是自动完成短函数调用,因此我们可以保持上下文大小相对较小。这样做的好处是我们可以更快地训练模型并且它需要的内存显着减少。如果您的应用程序拥有更多上下文很重要(例如,如果您希望模型基于具有函数定义的文件编写单元测试),请确保增加该数量,但请记住,这需要更大的 GPU 内存占用。现在,让我们将上下文大小固定为 128 个标记,而不是 GPT-2 或 GPT-3 中分别使用的 1,024 或 2,048 个标记。 +首先,我们需要将数据进行分词处理,这样才能进行训练。由于我们的主要目标是自动补全短的函数调用,因此我们可以将上下文大小设置得相对较小。这样做的好处是我们可以更快地训练模型,而且需要的内存也大大减少。如果你的应用需要更多的上下文(比如,你希望模型根据包含函数定义的文件编写单元测试),那么应该增大该数字,但是也要记住这会增加 GPU 显存的占用。现在,我们将上下文大小固定为 128 个 tokens 而不是在 GPT-2 或 GPT-3 中使用的 1,024 或 2,048 个 tokens - -大多数文档包含超过 128 个标记,因此简单地将输入截断到最大长度将消除我们数据集的很大一部分。相反,我们将使用 `return_overflowing_tokens` 标记整个输入并将其分成几个块的选项,就像我们在[第六章](/course/chapter6/4). 我们还将使用 `return_length` 选项自动返回每个创建的块的长度。通常最后一个块会小于上下文大小,我们会去掉这些块以避免填充问题;因为无论如何我们都有大量数据。 +大多数文档都包含超过 128 个 tokens 因此简单地将输入截断到最大长度会删除我们数据集的很一大部分。因此,我们将使用 `return_overflowing_tokens` 选项将整个输入进行分词处理,并将其分割为几个块,正如我们在 [第六章](/course/chapter6/4) 中所做的那样。我们还将使用 `return_length` 选项自动返回创建的每个块的长度。通常,最后一个块的大小会小于上下文大小,我们将去掉最后一块以避免填充问题;因为我们已经有足够的数据,所以不需要它们。
Chunking a large texts in several pieces.
-让我们通过查看前两个示例来确切了解这是如何工作的: +让我们通过查看前两个示例来具体了解结果怎么样: ```py from transformers import AutoTokenizer @@ -207,9 +202,9 @@ Input chunk lengths: [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128 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` 字段,我们还可以重建哪些块属于哪些输入样本。 +我们可以看到,这两个例子总共得到了 34 个块。查看块长度,我们可以看到两个文档末端的块少于 128 个 tokens (分别为 117 和 41)。不过这些只占我们所拥有的总块数的一小部分,因此我们可以放心地丢掉它们。通过 `overflow_to_sample_mapping` 字段,我们还可以分辨出哪些块属于哪个样本。 -通过这个操作,我们使用了一个方便的🤗 Datasets 中的` Dataset.map()` 函数,就是不需要一对一的映射;正如我们在[第三节](/course/chapter7/3),我们可以创建具有比输入批次更多或更少元素的批次。这在执行更改元素数量的数据增强或数据过滤等操作时非常有用。在我们的例子中,当将每个元素标记为指定上下文大小的块时,我们从每个文档中创建了许多样本。我们只需要确保删除现有的列,因为它们的大小存在冲突。如果我们想保留它们,我们可以适当地重复它们,并在`Dataset.map()` 调用中返回它们: +在这个操作中,我们使用了🤗 Datasets 中的 `Dataset.map()` 函数的一个便捷的特性,即它并不需要一对一地设置分块后和分块前的映射关系;正如我们在 [第三节](/course/chapter7/3) 中看到的,我们可以自由地将一个样本拆分或者删除部分样本来创建比输入的 `batch_size` 更多或更少元素的 batch。 `Dataset.map()` 函数会自动帮我们关联映射关系,当进行像数据增强或数据过滤这样改变元素数量的操作时非常有用。在我们的情况下,当将每个样本分词并分割成指定上下文大小的块时,我们从每个样本中创建了许多样本。我们需要删除原本的列,因为它们的大小和我们分割后的大小不一样。如果我们想保留它们,我们可以复制它们来填充,并在 `Dataset.map()` 调用中返回它们。 ```py def tokenize(element): @@ -246,21 +241,19 @@ DatasetDict({ }) ``` -我们现在有 1670 万个示例,每个示例有 128 个tokens ,总共相当于大约 21 亿个tokens 。作为参考,OpenAI 的 GPT-3 和 Codex 模型分别在 300 和 1000 亿个tokens 上训练,其中 Codex 模型从 GPT-3 检查点初始化。我们在本节中的目标不是与这些模型竞争,这些模型可以生成长而连贯的文本,而是创建一个缩小版本,为数据科学家提供快速自动完成功能。 - -现在我们已经准备好了数据集,让我们设置模型! +我们现在有 1670 万个样本,每个样本有 128 个 tokens 总共相当于大约 21 亿个 tokens。作为参考,OpenAI 的 GPT-3 和 Codex 模型分别在 300 和 1000 亿个 tokens 上进行了训练,其中 Codex 模型从 GPT-3 checkpoint 初始化。本节的目标不是与这些能生成长且连贯文本的模型竞争,而是创建一个能为数据科学家提供快速自动代码补全功能的精简版本。 +既然我们已经准备好了数据集,那就来设置模型吧! -✏️ **试试看!** 摆脱所有小于上下文大小的块在这里并不是什么大问题,因为我们使用的是小上下文窗口。随着上下文大小的增加(或者如果您有一个短文档语料库),被丢弃的块的比例也会增加。准备数据的更有效方法是将所有标记化的样本加入一个批次中,每个语料之间有一个`eos_token_id` 标记, 然后对连接的序列执行分块。作为练习,修改 `tokenize()`函数以使用该方法。请注意,您需要设置`truncation=False` 和删除标记生成器中的其他参数以获取完整的标记 ID 序列。 +✏️ **试一试!**这里我们删除了所有小于设定的上下文大小的块,并不会造成大问题,因为我们使用的是比较小的上下文窗口。随着增大上下文大小(或者语料库中的文档长度都很短),被抛弃的块的比例也会增加。更有效方法是将所有 tokenize 后的样本拼接起来加入一个 batch 中,每个样本之间有一个 `eos_token_id` token 作为分隔,然后对连接后的序列进行切块处理。作为练习,修改 `tokenize()` 函数以利用这种方法。请注意,为了获取完整的 token ID 序列你需要设置 `truncation=False` ,并删除 tokenizer 中的其他参数。 +## 初始化一个新模型 [[初始化一个新模型]] -## 初始化新模型 [[初始化新模型]] - -我们的第一步是新初始化一个 GPT-2 模型。我们将对我们的模型使用与小型 GPT-2 模型相同的配置,因此我们加载预训练配置,确保分词器大小与模型词汇量大小匹配并设置 `bos` 和 `eos` (序列的开始和结束)令牌 ID: +我们的第一步是初始化一个全新地 GPT-2 模型。我们可以通过加载预训练配置来初始化一个与 GPT-2 small 相同的配置的模型,并确保 tokenizer 大小与模型的词汇表大小匹配,以及设置 `bos` 和 `eos` (序列的开始和结束) token IDs: {#if fw === 'pt'} @@ -276,7 +269,7 @@ config = AutoConfig.from_pretrained( ) ``` -使用该配置,我们可以加载一个新模型。请注意,这是我们第一次不使用 `from_pretrained()` 函数,因为我们实际上是在自己初始化模型 +有了这个配置对象,我们就可以加载一个全新的 GPT-2 模型。注意,这是我们第一次不使用 `from_pretrained()` 函数,因为我们实际上是自己初始化一个全新的模型而不是从一个预训练的模型继续训练: ```py model = GPT2LMHeadModel(config) @@ -302,11 +295,11 @@ config = AutoConfig.from_pretrained( ) ``` -通过该配置,我们可以加载新模型。请注意,这是我们刚开始不使用`from_pretrained()`函数,因为我们实际上是在自己初始化模型: +有了这个配置,,我们就可以加载一个全新的 GPT-2 模型。注意,这是我们第一次不使用 `from_pretrained()` 函数,因为我们实际上是自己初始化一个全新的模型而不是从一个预训练的模型继续训练: ```py model = TFGPT2LMHeadModel(config) -model(model.dummy_inputs) # Builds the model +model(model.dummy_inputs) # 构建模型 model.summary() ``` @@ -314,7 +307,7 @@ model.summary() _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= -transformer (TFGPT2MainLayer multiple 124242432 +transformer (TFGPT2MainLayer multiple) 124242432 ================================================================= Total params: 124,242,432 Trainable params: 124,242,432 @@ -324,9 +317,9 @@ _________________________________________________________________ {/if} -我们的模型有 1.24 亿个参数,我们必须对其进行调整。在开始训练之前,我们需要设置一个负责创建批次的数据整理器。我们可以使用 `DataCollatorForLanguageModeling` ,它是专为语言建模而设计(顾名思义)。除了堆叠和填充批次,它还负责创建语言模型标签——在因果语言建模中,输入也用作标签(只是移动了一个元素),并且这个数据整理器在训练期间即时创建它们,所以我们不需要复制 `input_ids`。 +我们的新模型有 124M 个参数需要训练。在开始训练之前,我们需要设置一个数据整理器(DataCollator),它将负责创建 Batch。我们可以使用 `DataCollatorForLanguageModeling` ,顾名思义,它专门用于语言建模。除了堆叠和填充创建 Batch 之外,它还负责创建语言模型的待预测的标签 —— 在因果语言建模中,输入就是待预测的标签(只是偏移一个元素),而这个数据整理器(DataCollator)在训练过程中实时将输入偏移一个元素来创建它们,因此我们不需要复制 `input_ids` 。 -注意 `DataCollatorForLanguageModeling` 支持掩码语言建模 (MLM) 和因果语言建模 (CLM)。默认情况下它为 MLM 准备数据,但我们可以通过设置`mlm=False`参数切换到 CLM : +注意, `DataCollatorForLanguageModeling` 同时支持掩码语言建模 (MLM) 和因果语言建模 (CLM)。默认情况下它安装 MLM 需要的格式准备数据,但我们可以通过设置 `mlm=False` 参数切换到 CLM。 {#if fw === 'pt'} @@ -351,7 +344,7 @@ data_collator = DataCollatorForLanguageModeling(tokenizer, mlm=False, return_ten 让我们看一个例子: ```py -out = data_collator([tokenized_dataset["train"][i] for i in range(5)]) +out = data_collator([tokenized_datasets["train"][i] for i in range(5)]) for key in out: print(f"{key} shape: {out[key].shape}") ``` @@ -374,21 +367,21 @@ labels shape: (5, 128) {/if} -我们可以看到示例已经堆叠在一起,并且所有张量都具有相同的形状。 +我们可以看到示例的数据已经处理好了,并且所有 tensor 都具有相同的形状。 {#if fw === 'tf'} -现在,我们可以使用`prepare_tf_dataset()`方法,使用上面创建的数据整理器将数据集转换为TensorFlow数据集: +现在,我们可以使用 `prepare_tf_dataset()` 方法,将上面创建的数据整理器(DataCollator)将数据集转换为 TensorFlow 数据集: ```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, @@ -399,12 +392,11 @@ tf_eval_dataset = model.prepare_tf_dataset( -⚠️ 移动输入和标签以对齐它们发生在模型内部,因此数据整理器只需复制输入以创建标签。 +⚠️ 输入序列和目标序列对齐将在模型内部自动进行,所以数据整理器只需复制输入序列来创建目标序列。 - -现在我们已经准备好实际训练我们的模型的一切了——毕竟这不是那么多工作!在我们开始训练之前,我们应该登录 Hugging Face。如果您在笔记本上工作,则可以使用以下实用程序功能: +现在我们已经准备好了所有东西,可以开始训练我们的模型了——好像也不是那么困难!在我们开始训练之前,我们应该登录到 Hugging Face。如果你正在使用 Notebook 运行代码,你可以使用下面的实用函数进行登录: ```python from huggingface_hub import notebook_login @@ -412,9 +404,9 @@ from huggingface_hub import notebook_login notebook_login() ``` -这将显示一个小部件,您可以在其中输入您的 Hugging Face 登录凭据。 +这将显示一个小部件,你可以在其中输入你的 Hugging Face 登录凭据。 -如果您不是在notebook上工作,只需在终端中输入以下行: +如果你不是在 Notebook 上工作,只需在终端中输入以下行: ```bash huggingface-cli login @@ -422,7 +414,7 @@ huggingface-cli login {#if fw === 'pt'} -剩下要做的就是配置训练参数并启动 `Trainer` .我们将使用余弦学习率,并进行一些Warmup和有效批量大小为 256 ( `per_device_train_batch_size` * `gradient_accumulation_steps`)。当单个批次不适合内存时使用梯度累积,并通过多次向前/向后传递逐步建立梯度。当我们使用 🤗 Accelerate 创建训练循环时,我们将看到这一点。 +剩下要做的就是配置训练参数并启动 `Trainer` 。本次的训练中我们将使用余弦学习率调度,并进行一些 Warmup。训练的 batch size 是 256 ( `per_device_train_batch_size` * `gradient_accumulation_steps` )。当单个 batch 无法放入内存时,可以使用梯度累积,并通过多次向前/向后传递逐步累积梯度。当我们在本节最后使用 🤗 Accelerate 创建训练循环时,我们将看到这一点。 ```py from transformers import Trainer, TrainingArguments @@ -455,13 +447,13 @@ trainer = Trainer( ) ``` -现在我们可以开始 `Trainer`并等待训练完成。根据您是在整个训练集还是在训练集的一个子集上运行它,这将分别需要 20 或 2 个小时,因此请喝杯咖啡和一本好书来阅读! +现在我们只需启动 `Trainer` 并等待训练完成。根据你是在整个训练集还是在训练集的一个子集上运行它,这将分别需要 20 或 2 个小时,因此请喝杯咖啡或者找一本好书来阅读! ```py trainer.train() ``` -训练完成后,我们可以将模型和标记器推送到 Hub: +训练完成后,我们可以将模型和 tokenizer 推送到 Hub: ```py trainer.push_to_hub() @@ -469,7 +461,7 @@ trainer.push_to_hub() {:else} -剩下要做的就是配置训练超参数并调用 `compile()` 和 `fit()`。我们将使用带有一些预热的学习率调整策略来提高训练的稳定性: +接下来我们需要做的就是配置训练超参数并调用 `compile()` 和 `fit()` 方法。然后使用带有一些预热的学习率调整策略来提高训练的稳定性: ```py from transformers import create_optimizer @@ -484,11 +476,11 @@ optimizer, schedule = create_optimizer( ) model.compile(optimizer=optimizer) -# Train in mixed-precision float16 +# 使用 float16 混合精度进行训练 tf.keras.mixed_precision.set_global_policy("mixed_float16") ``` -现在我们可以调用`model.fit(),`并等待训练完成。你是在完整的训练集还是他的子集上运行,这将分别需要20和2个小时,所以拿一些咖啡和一本好书来阅读!训练完成后,我们可以将模型和分词器推送到中心: +现在我们只需调用 `model.fit()` ,并等待训练完成。根据你是否在完整的训练集或者训练集的子集上运行,这将分别需要 20 小时或者 2 小时,所以拿一些咖啡和一本好书来阅读!训练完成后,我们可以将模型和 tokenizer 推送到 Hub: ```py from transformers.keras_callbacks import PushToHubCallback @@ -502,7 +494,7 @@ model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback -✏️ **试试看!** 除了`TrainingArguments` 之外,我们只需要大约30行代码就可以从原始文本到训练GPT-2。 用你自己的数据集试试看,看看你能不能得到好的结果! +✏️ **试试看!** 除了 `TrainingArguments` 之外,我们只需要大约 30 行代码就可以从原始文本到训练 GPT-2。用你自己的数据集试试看,看看你能不能得到好的结果! @@ -510,19 +502,19 @@ model.fit(tf_train_dataset, validation_data=tf_eval_dataset, callbacks=[callback {#if fw === 'pt'} -💡 如果您可以访问具有多个 GPU 的机器,请尝试在那里运行代码。 `Trainer`自动管理多台机器,这可以极大地加快训练速度。 +💡 如果你能使用多 GPU 的机器,尝试在那里运行代码。 `Trainer` 自动管理多台机器,这能极大地加快训练速度。 {:else} -💡 如果您正在使用具有多个 GPU 的计算机,则可以尝试使用 `MirroredStrategy` 上下文来大幅加快训练速度。您需要创建一个`tf.distribute.MirroredStrategy`对象,并确保所有的 `to_tf_dataset` 或 `prepare_tf_dataset()` 方法以及模型创建和对 `fit()`的调用都在其 `scope()` 上下文中运行。您可以查看有关此内容的文档[here](https://www.tensorflow.org/guide/distributed_training#use_tfdistributestrategy_with_keras_modelfit). +💡 如果你正在使用具有多个 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} -## 使用管道生成代码 [[使用管道生成代码]] +## 使用 pipeline 进行代码生成 [[使用管道生成代码]] -现在是关键的部分:让我们看看经过训练的模型的实际效果如何!我们可以在日志中看到损失稳步下降,但为了让模型进行测试,让我们看看它在某些测试上的表现如何。为此,我们将模型包装在文本生成中的`pipeline` ,如果有可用的,我们会将它放在 GPU 上进行快速生成: +现在是见证奇迹的时刻:我们来看看训练好的模型到底表现如何!我们可以在日志中看到损失持续下降,但要测试模型的效果,我们就看看它对一些提示的反应如何。为此,我们将模型包装在一个文本生成的 `pipeline` 中,并如果有 GPU 可用,我们将把它放在 GPU 上加快生成速度: {#if fw === 'pt'} @@ -550,93 +542,93 @@ pipe = pipeline( {/if} -让我们从创建散点图的简单任务开始: +让我们从简单的创建散点图任务开始: ```py txt = """\ -# create some data +# 创建一些数据 x = np.random.randn(100) y = np.random.randn(100) -# create scatter plot with x, y +# 使用 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 +# 使用 x,y 创建散点图 plt.scatter(x, y) -# create scatter +# 创建散点 ``` -结果看起来是正确的。它也适用于 `pandas` 类型?让我们看看我们是否使用两个数组可以创建一个 `DataFrame` : +结果看起来是正确的。那么对于 `pandas` 操作也可以吗?让我们看看是否能从两个数组创建一个 `DataFrame` : ```py txt = """\ -# create some data +# 创建一些数据 x = np.random.randn(100) y = np.random.randn(100) -# create dataframe from x and y +# 从 x 和 y 创建 dataframe """ 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 +# 从 x 和 y 创建 dataframe df = pd.DataFrame({'x': x, 'y': y}) df.insert(0,'x', x) for ``` -很好,这是正确的答案——尽管它随后再次插入了列 `x` 。由于生成的token数量有限,以下 `for` 循环被切断。让我们看看我们是否可以做一些更复杂的事情并让模型帮助我们分组操作: +很好,这是正确的答案——尽管它又把 `x` 重复插入了一次。而且由于生成的 token 数量有限,所以下面的 `for` 循环被切断了。让我们看看我们是否能做些更复杂的事情,让模型帮助我们使用 `groupby` 操作: ```py txt = """\ -# dataframe with profession, income and name +# 有职业,收入和名字的 dataframe 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 +# 有职业,收入和名字的 dataframe df = pd.DataFrame({'profession': x, 'income':y, 'name': z}) -# calculate the mean income per profession +# 计算每个职业的平均收入 profession = df.groupby(['profession']).mean() -# compute the +# 计算 ``` -不错;这是正确的做法。最后,让我们看看我们是否也可以将其用于 `scikit-learn` 并建立一个随机森林模型: +不错;是正确的。最后,让我们看看是否能引导模型使用 `scikit-learn` 并建立一个随机森林模型: ```py txt = """ -# import random forest regressor from scikit-learn +# 从 scikit-learn 导入随机森林回归器 from sklearn.ensemble import RandomForestRegressor -# fit random forest model with 300 estimators on X, y: +# 用 X, y 拟合带有 300 个估算器的随机森林模型: """ print(pipe(txt, num_return_sequences=1)[0]["generated_text"]) ``` ```python out -# import random forest regressor from scikit-learn +# 从 scikit-learn 导入随机森林回归器 from sklearn.ensemble import RandomForestRegressor -# fit random forest model with 300 estimators on X, y: +# 用 X, y 拟合带有 300 个估算器的随机森林模型: rf = RandomForestRegressor(n_estimators=300, random_state=random_state, max_depth=3) rf.fit(X, y) rf @@ -644,23 +636,23 @@ rf {#if fw === 'tf'} -看看这几个例子,似乎模型已经学习了Python数据科学堆栈的一些语法。当然,在将模型部署到现实世界之前,我们需要更彻底地评估模型,但这仍然是一个令人印象深刻的原型。 +从上述几个示例来看,似乎模型已经学习了 Python 数据科学栈的一些语法。当然,在将模型部署到现实世界之前,我们需要更彻底地评估模型,但这仍然是一个令人印象深刻的原型。 {:else} -从这几个例子来看,模型似乎已经学习了 Python 数据科学堆栈的一些语法(当然,在将模型部署到现实世界之前,我们需要对其进行更全面的评估)。然而,有时需要对模型训练进行更多定制才能实现给定用例的必要性能。例如,如果我们想动态更新批量大小或有一个条件训练循环来即时跳过坏示例怎么办?一种选择是将 `Trainer` 并添加必要的更改,但有时从头开始编写训练循环会更简单。这就是🤗 Accelerate 的用武之地。 +从这几个例子来看,模型似乎已经学习了 Python 数据科学堆栈的一些语法(当然,在将模型部署到现实世界之前,我们需要对其进行更全面的评估)。然而,有时候它需要更多的模型训练定制来达到特定情境所需的性能。例如,如果我们想动态更新 `batch_size` 或添加一个条件训练循环来跳过坏示例怎么办?一种选择是修改 `Trainer` 添加新的功能,但有时从头开始编写训练循环会更简单。这就是🤗 Accelerate 的用武之地。 {/if} {#if fw === 'pt'} -## 用 🤗 Accelerate 训练 [[用 🤗 Accelerate 训练]] +## 使用🤗 Accelerate 进行训练 [[使用🤗 Accelerate 进行训练]] -我们已经看到了如何使用 `Trainer` ,这可以允许一些自定义。然而,有时我们想要完全控制训练循环,或者我们想要进行一些奇特的更改。在这种情况下 🤗 Accelerate 是一个不错的选择,在本节中,我们将逐步介绍使用它来训练我们的模型的步骤。为了让事情变得更有趣,我们还将在训练循环中添加一些修改。 +我们已经看到了如何使用 `Trainer` 训练模型,在 `Trainer` 中可以对训练过程可以通过修改一些参数进行一些定制。然而,有时我们想要完全控制训练循环,或者我们想要进行一些更自由的的更改。在这种情况下 🤗 Accelerate 是一个不错的选择,本节我们将介绍如何使用它来训练我们的模型。为了让事情变得更有趣,相比于上面的 `Trainer` 我们还将在训练循环中添加一些修改。 -由于我们主要对数据科学库的合理自动填充感兴趣,因此对更多使用这些库的训练样本给予更多权重是有意义的。我们可以通过使用关键字轻松识别这些示例,例如 `plt`、`pd`、`sk`、`fit`和`predict`等关键字,我们可以很容易地识别这些示例,这些关键字是matplotlib最常用的导入名称。`Pyplot`, `pandas`和`sklearn`以及后者的拟合/预测模式。如果这些都表示为单个标记,我们可以轻松检查它们是否出现在输入序列中。标记可能有一个空格前缀,因此我们还将在标记器词汇表中检查这些版本。为了验证它是否有效,我们将添加一个测试token ,该token 应拆分为多个tokens: +由于我们主要关注的是为数据科学库提供合理的代码自动补充功能,因此对于更多使用这些库的训练样本赋予更高的权重是有意义的。我们可以通过使用 `plt` 、 `pd` 、 `sk` 、 `fit` 和 `predict` 等关键词来轻松地识别出这些例子,这些关键词是 `matplotlib.pyplot` 、 `pandas` 和 `sklearn` 导入后最常用重命名的名称,以及 `sklearn` 的 `fit/predict` 方法。如果这些在模型的内部是用单一的一个 `token` 表示的,我们可以通过 token 的 id 轻松地检查它们是否出现在输入序列中。然而,Tokens 有可能有空格前缀,所以我们也需要在 tokenizer 词汇表中检查这些关键词。为了验证这个策略的有效性,我们会在测试样本中添加一个应该被分割为多个 tokens 的测试 token: ```py keytoken_ids = [] @@ -688,7 +680,7 @@ for keyword in [ 'Keyword has not single token: testtest' ``` -太好了,这似乎很好用!我们现在可以编写一个自定义损失函数,它将输入序列、logits 和我们刚刚选择的关​​键标记作为输入。首先,我们需要对齐 logits 和输入:向右移动一个的输入序列形成标签,因为下一个标记是当前标记的标签。我们可以通过从输入序列的第二个标记开始标记来实现这一点,因为模型无论如何都不会对第一个标记进行预测。然后我们切断最后一个 logit,因为我们没有完整输入序列之后的标记的标签。有了这个,我们可以计算每个样本的损失并计算每个样本中所有关键字的出现次数。最后,我们使用出现次数作为权重计算所有样本的加权平均值。由于我们不想扔掉所有没有关键字的样本,我们将权重加1: +太好了,这个方法似乎很有效!我们现在可以编写一个自定义的损失函数,它的输入有输入序列、logits 和我们刚刚选择的关键字。首先需要对齐 `logits` 和 `inputs` : 并将输入序列右移一个单位形成目标序列,因为下一个 `token` 就是当前 `token` 的预测的目标。我们可以通过从输入序列的第二个 `token` 开始设置标签,因为模型不会预测第一个 `token`。然后我们截断最后一个 `logit`,因为我们没有完整输入序列后面的标签。有了这些,我们就可以计算每个样本的损失,并计算每个样本中所有关键词的出现次数。最后,我们使用出现次数作为权重,计算所有样本的加权平均值。由于我们不想抛弃所有没有关键词的样本,我们将所有的权重都加 1: ```py from torch.nn import CrossEntropyLoss @@ -696,41 +688,41 @@ import torch def keytoken_weighted_loss(inputs, logits, keytoken_ids, alpha=1.0): - # Shift so that tokens < n predict n + # 左移 tokens < n 预测 n shift_labels = inputs[..., 1:].contiguous() shift_logits = logits[..., :-1, :].contiguous() - # Calculate per-token loss + # 计算每一个token的loss loss_fct = CrossEntropyLoss(reduce=False) loss = loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1)) - # Resize and average loss per sample + # 对于每个样本重新调整大小并平均 loss_per_sample = loss.view(shift_logits.size(0), shift_logits.size(1)).mean(axis=1) - # Calculate and scale weighting + # 计算并缩放权重 weights = torch.stack([(inputs == kt).float() for kt in keytoken_ids]).sum( axis=[0, 2] ) weights = alpha * (1.0 + weights) - # Calculate weighted average + # 计算评价权重 weighted_loss = (loss_per_sample * weights).mean() return weighted_loss ``` -在我们开始使用这个很棒的新损失函数进行训练之前,我们需要准备一些东西: +在我们开始使用这个精妙的新损失函数进行训练之前,我们需要准备一些事情: - 我们需要数据加载器来批量加载数据。 - 我们需要设置权重衰减参数。 -- 有时我们想要求值,因此将求值代码包装在一个函数中是有意义的。 +- 有时我们在调试模型的时候可能需要临时评估,所以将评估代码包装在一个函数中。 -让我们从数据加载器开始。我们只需要将数据集的格式设置为 `"torch"`,然后我们可以将它传递给 PyTorch `DataLoader` ,同时设置适当的批量大小: +让我们从数据加载器开始。我们只需要将数据集的格式设置为 `"torch"` ,然后我们就可以将它传递给一个具有适当 `batch size` 的 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) +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) ``` -接下来,我们对参数进行分组,以便优化器知道哪些将获得额外的权重衰减。通常,所有偏差和 LayerNorm 权重项都不受此限制;以下我们如何做到这一点: +接下来,我们将参数分组,以便优化器知道哪些参数需要进行额外的权重衰减。通常,所有的偏置和 LayerNorm 权重项都不需要进行权重衰减;因此我们可以这样做: ```py weight_decay = 0.1 @@ -749,7 +741,7 @@ def get_grouped_params(model, no_decay=["bias", "LayerNorm.weight"]): ] ``` -由于我们希望在训练期间定期在验证集上评估模型,因此我们也为此编写一个函数。它只是运行评估数据加载器并收集跨进程的所有损失: +我们希望在训练过程中定期在验证集上评估模型,让我们为此编写一个函数。它只需遍历评估数据加载器,并收集所有进程中的损失值: ```py def evaluate(): @@ -768,7 +760,7 @@ def evaluate(): return loss.item(), perplexity.item() ``` -通过 `evaluate()` 函数我们定期可以获取损失值和[perplexity](/course/chapter7/3)。接下来,我们重新定义我们的模型以确保我们再次从头开始训练: +通过 `evaluate()` 函数我们定期可以获取损失值和 [困惑度(perplexity)](/course/chapter7/3) 。接下来,我们重新加载我们的模型以确保我们再次从头开始训练,而不是从上面的 `Trainer` 继续微调: ```py model = GPT2LMHeadModel(config) @@ -782,7 +774,7 @@ from torch.optim import AdamW optimizer = AdamW(get_grouped_params(model), lr=5e-4) ``` -现在让我们准备模型、优化器和数据加载器,以便我们可以开始训练: +现在让我们准备模型、优化器和数据加载器,然后我们可以开始训练: ```py from accelerate import Accelerator @@ -796,11 +788,12 @@ model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( -🚨 如果您在 TPU 上进行训练,则需要将从上面的单元格开始的所有代码移动到专用的训练函数中。有关详细信息,请参阅 [第 3 章](/course/chapter3) for more details. +🚨 如果你在 TPU 上训练,你需要将上述单元格开始的所有代码移到一个专门的训练函数中。更多详情请参阅 [第三章](/course/chapter3) 。 -现在我们已经发送了我们的 `train_dataloader`到 `accelerator.prepare()` ,我们可以使用它的长度来计算训练步骤的数量。请记住,我们应该始终在准备好dataloader后执行此操作,因为该方法会改变其长度。我们使用经典线性学习率调度: +现在我们已经将我们的 `train_dataloader` 传递给了 `accelerator.prepare()` ,我们可以使用 `len()` 来计算训练步骤的数量。请记住,我们应该在准备好 `dataloader` 后再使用 `len()` ,因为改动 `dataloader` 会改变其长度。我们使用一个从学习率衰减到 0 的经典线性学习率调度: + ```py num_train_epochs = 1 @@ -815,7 +808,7 @@ lr_scheduler = get_scheduler( ) ``` -最后,要将我们的模型推送到 Hub,我们需要创建一个 `Repository` 工作文件夹中的对象。如果您尚未登录,请先登录 Hugging Face。我们将从我们想要为模型提供的模型 ID 中确定存储库名称(您可以自由地用自己的选择替换 `repo_name` ;它只需要包含您的用户名,可以使用`get_full_repo_name()`函数的查看目前的repo_name): +最后,为了将我们的模型推送到 Hub,我们需要在一个工作文件夹中创建一个 `Repository` 对象。如果你还没有登录的话,首先需要登录到 Hugging Face,我们将根据模型 ID 来确定仓库名称(你可以使用你喜欢的名字替换 `repo_name` ;它只需要包含你的用户名,可以使用 `get_full_repo_name()` 函数的查看目前的 `repo_name`): ```py from huggingface_hub import Repository, get_full_repo_name @@ -829,14 +822,13 @@ repo_name 'sgugger/codeparrot-ds-accelerate' ``` -然后我们可以在本地文件夹中克隆该存储库。如果它已经存在,这个本地文件夹应该是我们正在使用的存储库的克隆: - +然后我们可以将该仓库克隆到本地文件夹中。如果本地已经存在一个同名的文件夹,这个本地文件夹应该是我们正在使用的仓库的克隆在本地的版本: ```py output_dir = "codeparrot-ds-accelerate" repo = Repository(output_dir, clone_from=repo_name) ``` -我们现在可以上传我们保存的任何内容 `output_dir` 通过调用 `repo.push_to_hub()` 方法。这将帮助我们在每个 epoch 结束时上传中间模型。在我们训练之前,让我们运行一个快速测试,看看评估函数是否正常工作: +我们现在可以通过调用 `repo.push_to_hub()` 方法上传保存在 `output_dir` 中的所有内容。这将帮助我们在每个训练周期结束时上传中间模型。 ```py evaluate() @@ -846,7 +838,7 @@ evaluate() (10.934126853942871, 56057.14453125) ``` -这些损失和困惑度的值非常高,但这并不奇怪,因为我们还没有训练过模型。有了这个,我们已经准备好编写训练脚本的核心部分:训练循环。在训练循环中,我们遍历数据加载器并将批次传递给模型。有了 logits,我们就可以评估我们的自定义损失函数。我们通过梯度累积步骤的数量来缩放损失,以便在聚合更多步骤时不会产生更大的损失。在我们优化之前,我们还剪辑了梯度以获得更好的收敛性。最后,每隔几步,我们就会使用新的 `evaluate()` 函数评估模型: +目前的损失和困惑度都是非常高的值,但这并不奇怪,因为我们还没有训练模型。到现在为止,我们已经为编写训练脚本的核心部分:训练循环已经做好了准备。在训练循环中,我们迭代遍历数据加载器并将成批量的数据传递给模型。有了模型输出的 logits,我们就可以使用自定义损失函数计算损伤。我们通过梯度累积步骤的数量来缩放损失,以避免在聚合更多步骤时产生更大的损失。在我们优化之前,我们也会剪裁梯度来更好的收敛。最后,每隔一段步数,我们用新的 `evaluate()` 函数在评估集上评估模型: ```py from tqdm.notebook import tqdm @@ -865,7 +857,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, @@ -893,17 +884,17 @@ for epoch in range(num_train_epochs): ) ``` -就是这样 - 您现在拥有自己的因果语言模型(例如 GPT-2)的自定义训练循环,您可以根据自己的需要进一步自定义。 +就是这样 - 你现在拥有自己的因果语言模型(例如 GPT-2)的自定义训练循环,你可以根据自己的需要进一步定制。 -✏️ **试试看!** 创建适合您的用例的自定义损失函数,或在训练循环中添加另一个自定义步骤。 +✏️ **试试看!** 创建适合你的用例的自定义损失函数,或在训练循环中添加另一个自定义步骤。 -✏️ **试试看!** 在运行长时间的训练实验时,最好使用 TensorBoard 或 Weights Biases 等工具记录重要指标。向训练循环添加适当的日志记录,以便您始终可以检查训练的进行情况。going. +✏️ **试试看!** 当运行长时间的训练实验时,使用 TensorBoard 或 Weights & Biases 等工具记录重要指标是个好主意。向训练循环中添加适当的日志记录,这样你可以随时检查训练进度。 diff --git a/chapters/zh-CN/chapter7/7.mdx b/chapters/zh-CN/chapter7/7.mdx index 4d1fdfb96..efeda62f6 100644 --- a/chapters/zh-CN/chapter7/7.mdx +++ b/chapters/zh-CN/chapter7/7.mdx @@ -1,6 +1,6 @@ -# 问答 [[问答]] +# 抽取式问答问答 [[抽取式问答]] {#if fw === 'pt'} @@ -22,29 +22,29 @@ {/if} -是时候看问答了! 这项任务有多种形式, 但我们将在本节中关注的一项称为*提取*的问答。问题的答案就在 _给定的文档_ 之中。 +现在我们来看看问答这个任务!这个任务有很多种类型,但我们在本节将要关注的是称为 `抽取式(extractive)` 问题回答的形式。会有一些问题和文档,其中答案就在文档段落之内。 -我们将使用 [SQuAD 数据集](https://rajpurkar.github.io/SQuAD-explorer/) 微调一个BERT模型, 其中包括群众工作者对一组维基百科文章提出的问题。以下是一个小的测试样例: +我们将使用 [SQuAD 数据集](https://rajpurkar.github.io/SQuAD-explorer/) 微调一个 BERT 模型,其中包括群众工作者对一组维基百科文章提出的问题。以下是一个小的测试样例: -本节使用的代码已经上传到了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) 找到它并尝试用它进行预测。 +本节使用的代码已经上传到了 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 这样的纯编码器模型往往很擅长提取诸如 "谁发明了 Transformer 架构?"之类的事实性问题的答案。但在给出诸如 "为什么天空是蓝色的?" 之类的开放式问题时表现不佳。在这些更具挑战性的情况下, T5 和 BART 等编码器-解码器模型通常使用以与 [文本摘要](/course/chapter7/5) 非常相似的方式合成信息。如果你对这种类型的*生成式*问答感兴趣, 我们建议您查看我们基于 [ELI5 数据集](https://huggingface.co/datasets/eli5) 的 [演示](https://yjernite.github.io/lfqa.html)。 +💡 像 BERT 这样的纯编码器模型往往很擅长提取诸如 “谁发明了 Transformer 架构?”之类的事实性问题的答案。但在给出诸如 “为什么天空是蓝色的?” 之类的开放式问题时表现不佳。在这些更具挑战性的情况下,通常使用编码器-解码器模型如 T5 和 BART 来以类似于 [文本摘要](https://chat.openai.com/course/chapter7/5) 的方式整合信息。如果你对这种 `生成式(generative)` 问答感兴趣,我们推荐你查看我们做的基于 [ELI5 数据集](https://huggingface.co/datasets/eli5) 的 [演示demo](https://yjernite.github.io/lfqa.html) 。 ## 准备数据 [[准备数据]] -最常用作抽取式问答的学术基准的数据集是 [SQuAD](https://rajpurkar.github.io/SQuAD-explorer/), 所以这就是我们将在这里使用的。还有一个更难的 [SQuAD v2](https://huggingface.co/datasets/squad_v2) 基准, 其中包括没有答案的问题。只要你自己的数据集包含上下文列、问题列和答案列, 你就应该能够调整以下步骤。 +作为抽取式问题回答的学术基准最常用的数据集是 [SQuAD](https://rajpurkar.github.io/SQuAD-explorer/) ,所以我们在这里将使用它。还有一个更难的 [SQuAD v2](https://huggingface.co/datasets/squad_v2) 基准,其中包含一些没有答案的问题。你也可以使用自己的数据集,只要你自己的数据集包含了 Context 列、问题列和答案列,应该也能够适用下面的步骤。 ### SQuAD 数据集 [[SQuAD 数据集]] -像往常一样, 我们只需一步就可以下载和缓存数据集, 这要归功于 `load_dataset()`: +像往常一样,我们可以使用 `load_dataset()` 在一行中下载和缓存数据集: ```py from datasets import load_dataset @@ -52,7 +52,7 @@ from datasets import load_dataset raw_datasets = load_dataset("squad") ``` -然后我们可以查看这个对象以, 了解有关 SQuAD 数据集的更多信息: +我们可以查看这个 `raw_datasets` 对象来了解关于 SQuAD 数据集的更多信息: ```py raw_datasets @@ -71,7 +71,7 @@ DatasetDict({ }) ``` -看起来我们拥有所需的 `context` 、`question` 和 `answers` 字段, 所以让我们打印训练集的第一个元素: +看起来我们的数据集拥有所需的 `context` 、 `question` 和 `answers` 字段,所以让我们打印训练集的第一个元素: ```py print("Context: ", raw_datasets["train"][0]["context"]) @@ -84,10 +84,9 @@ Context: 'Architecturally, the school has a Catholic character. Atop the Main Bu 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` 字段包含了 Context 中每个答案开始的索引。 -`context` 和 `question` 字段使用起来非常简单。但是 `answers` 字段有点棘手, 因为它将字典与两个都是列表的字段组成。这是在评估过程中 `squad` 指标所期望的格式; 如果你使用的是自己的数据, 则不必担心将答案采用相同的格式。`text` 字段比较明显, 而 `answer_start` 字段包含上下文中每个答案的起始字符索引。 - -在训练期间, 只有一种可能的答案。我们可以使用 `Dataset.filter()` 方法: +在训练过程中,只有一个可能的答案。我们也可以使用 `Dataset.filter()` 方法来进行检查: ```py raw_datasets["train"].filter(lambda x: len(x["answers"]["text"]) != 1) @@ -100,7 +99,7 @@ Dataset({ }) ``` -然而, 对于评估, 每个样本都有几个可能的答案, 它们可能相同或不同: +然而,在评估过程中,每个样本可能有多个答案,这些答案可能相同或不同: ```py print(raw_datasets["validation"][0]["answers"]) @@ -112,7 +111,7 @@ print(raw_datasets["validation"][2]["answers"]) {'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 处的样本e: +我们不会深入探究评估的代码,因为所有的东西都将由🤗 Datasets metric 帮我们完成,但简单来说,一些问题可能有多个可能的答案,而该评估代码将把预测的答案与所有可接受的答案进行比较,并选择最佳分数。例如,让我们看一下索引为 2 的样本: ```py print(raw_datasets["validation"][2]["context"]) @@ -124,15 +123,15 @@ print(raw_datasets["validation"][2]["question"]) 'Where did Super Bowl 50 take place?' ``` -我们可以看到, 答案确实可以是我们之前看到的三种可能性之一。 +我们可以看到,答案的确可能是我们之前看到的三个可能选择 `['Denver Broncos', 'Denver Broncos', 'Denver Broncos']` 的之一。 ### 处理训练数据 [[处理训练数据]] -让我们从预处理训练数据开始。困难的部分将是为问题的答案生成标签, 这将是与上下文中的答案相对应的标记的开始和结束位置。 +我们从预处理训练数据开始。最困难的部分将是生成问题答案的位置,即找到 Context 中对应答案 token 的起始和结束位置。 -但是, 我们不要超越自己。首先, 我们需要使用分词器将输入中的文本转换为模型可以理解的 ID: +但我们不要急于求成。首先,我们需要使用 tokenizer 将输入中的文本转换为模型可以理解的 ID: ```py from transformers import AutoTokenizer @@ -141,7 +140,7 @@ model_checkpoint = "bert-base-cased" tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) ``` -如前所述, 我们将对 BERT 模型进行微调, 但你可以使用任何其他模型类型, 只要它实现了快速标记器即可。你可以在 [this big table](https://huggingface.co/transformers/#supported-frameworks) 中看到所有快速版本的架构, 并检查你正在使用的 `tokenizer` 对象确实由 🤗 Tokenizers 支持, 你可以查看它的 `is_fast` 属性: +如前所述,我们将对 BERT 模型进行微调,但你可以使用任何其他模型类型,只要它实现了快速 tokenizer 即可。你可以在 [支持快速 tokenizer 的框架](https://huggingface.co/transformers/#supported-frameworks) 表中看到所有带有快速版本的架构,要检查你正在使用的 `tokenizer` 对象是否真的是由🤗 Tokenizers 支持的,你可以查看它的 `is_fast` 属性: ```py tokenizer.is_fast @@ -151,13 +150,13 @@ tokenizer.is_fast True ``` -我们可以将问题和上下文一起传递给我们的标记器, 它会正确插入特殊标记以形成如下句子: +我们可以将 question 和 context 一起传递给我们的 tokenizer 它会正确插入特殊 tokens 形成如下句子: ``` [CLS] question [SEP] context [SEP] ``` -让我们仔细检查一下: +让我们检查一下处理后的样本: ```py context = raw_datasets["train"][0]["context"] @@ -178,21 +177,21 @@ tokenizer.decode(inputs["input_ids"]) 'and the Gold Dome ), is a simple, modern stone statue of Mary. [SEP]' ``` -然后标签将成为开始和结束答案的标记的索引, 并且模型的任务是预测输入中每个标记的开始和结束 logit, 理论标签如下: +需要预测的是答案起始和结束 token 的索引,模型的任务是为输入中的每个标记预测一个起始和结束的 logit 值,理论上的预测的结果如下所示:
One-hot encoded labels for question answering.
-在这种情况下, 上下文不会太长, 但是数据集中的一些示例的上下文很长, 会超过我们设置的最大长度(在这种情况下为 384)。正如我们在 [第六章](/course/chapter6/4) 中所看到的, 当我们探索 `question-answering` 管道的内部结构时, 我们将通过从我们的数据集的一个样本中创建几个训练特征来处理长上下文, 它们之间有一个滑动窗口。 +在做个例子中,Context 没有很长,但是数据集中的一些示例的 Context 会很长,会超过我们设置的最大长度(本例中为 384)。正如我们在 [第六章](/course/chapter6/4) 中所看到的,当我们探索 `question-answering` 管道的内部结构时,我们会通过将一个样本的较长的 Context 划分成多个片段,并在这些片段之间使用滑动窗口,来处理较长的 Context。 -要使用当前示例查看其工作原理, 我们可以将长度限制为 100, 并使用 50 个标记的滑动窗口。提醒一下, 我们使用: +要了解在这个过程中对当前的训练样本进行了哪些处理,我们可以将长度限制为 100,并使用长度为 50 的 token 窗口。我们将设置以下的参数: -- `max_length` 设置最大长度 (此处为 100) -- `truncation="only_second"` 用于当带有上下文的问题太长时, 截断上下文t (位于第二个位置) -- `stride` 设置两个连续块之间的重叠标记数 (这里为 50) -- `return_overflowing_tokens=True` 让标记器知道我们想要溢出的标记 +- `max_length` 来设置最大长度 (这里为 100) +- `truncation="only_second"` 在问题和 Context 过长时截断 Context(Context 位于第二个位置,第一个是 Question) +- `stride` 设置两个连续块之间的重叠 tokens 数 (这里为 50) +- `return_overflowing_tokens=True` 告诉 tokenizer 我们想要保留超过长度的 tokens ```py inputs = tokenizer( @@ -215,9 +214,9 @@ for ids in inputs["input_ids"]: '[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]` 标记)。我们还将在答案被截断的不幸情况下设置这些标签, 以便我们只有它的开始(或结束)。对于答案完全在上下文中的示例, 标签将是答案开始的标记的索引和答案结束的标记的索引。 +如我们所见,示例文本被拆分成四个输入,每个输入都包含问题和 Context 的一部分。请注意,问题的答案 (“Bernadette Soubirous”) 仅出现在第三个和最后一个片段中,因此通过以这种方式处理较长的 Context 时,我们可能创建一些 Context 中不包含答案的训练样本。我们把这些样本的标签设置为 `start_position = end_position = 0` (这样的话,实际上我们的答案指向了 `[CLS]` tokens)。如果答案被截断,那么只在这一部分预测答案的起始(或结束)的token 的索引。对于答案完全在 Context 中的示例,标签将是答案起始的 token 的索引和答案结束的 token 的索引。 -数据集为我们提供了上下文中答案的开始字符, 通过添加答案的长度, 我们可以找到上下文中的结束字符。要将它们映射到令牌索引, 我们将需要使用我们在 [第六章](/course/chapter6/4) 中研究的偏移映射。我们可以让标记器通过传递 `return_offsets_mapping=True` 来返回这些值: +数据集为我们提供了 Context 中答案的起始的位置索引,加上答案的长度,我们可以找到 Context 中的结束索引。要将它们映射到 tokens 索引,我们将需要使用我们在 [第六章](/course/chapter6/4) 中学到的偏移映射。我们可以通过使用 `return_offsets_mapping=True`,让我们的 tokenizer 返回偏移后的映射: ```py inputs = tokenizer( @@ -236,7 +235,7 @@ inputs.keys() dict_keys(['input_ids', 'token_type_ids', 'attention_mask', 'offset_mapping', 'overflow_to_sample_mapping']) ``` -如我们所见, 我们取回了通常的输入 ID、令牌类型 ID 和注意掩码, 以及我们需要的偏移映射和一个额外的键, `overflow_to_sample_mapping`。当我们同时标记多个文本时, 相应的值将对我们有用(我们应该这样做以受益于我们的标记器由 Rust 支持的事实)。由于一个样本可以提供多个特征, 因此它将每个特征映射到其来源的示例。因为这里我们只标记了一个例子, 我们得到一个 `0` 的列表: +如我们所见,我们得到了 inputs ID、tokens 类型 ID 和注意力掩码,以及我们所需的偏移映射和一个额外的 `overflow_to_sample_mapping` 。当我们同时对多个文本并行 tokenize 时,为了从支持 Rust 中受益,这个键的值对我们很有用。由于一个长的样本可以切分为多个短的样本,它保存了这些短的样本是来自于哪个长的样本。因为这里我们只对一个样本进行了 tokenize,所以我们得到一个由 `0` 组成的列表: ```py inputs["overflow_to_sample_mapping"] @@ -246,7 +245,7 @@ inputs["overflow_to_sample_mapping"] [0, 0, 0, 0] ``` -但是, 如果我们标记更多示例, 这将变得更加有用: +但是,如果我们对更多的示例进行 tokenize ,它会变得更加有用: ```py inputs = tokenizer( @@ -268,16 +267,16 @@ print(f"Here is where each comes from: {inputs['overflow_to_sample_mapping']}.") '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 个特征。 +在我们的这个例子中,前三条数据 (在训练集中的索引 2、3 和 4 处) 每条数据被拆分为4个样本,最后一条数据(在训练集中的索引 5 处) 拆分为了5个样本。 -此信息将有助于将我们获得的每个特征映射到其相应的标签。如前所述, 这些标签是: +这些信息将有助于将我们拆分后的文本块映射到其相应的标签。如前所述,这些标签的规则是: -- `(0, 0)` 如果答案不在上下文的相应范围内 -- `(start_position, end_position)` 如果答案在上下文的相应范围内, 则 `start_position` 是答案开头的标记索引 (在输入 ID 中), 并且 `end_position` 是答案结束的标记的索引 (在输入 ID 中)。 +- 如果答案不在相应上下文的范围内,则为 `(0, 0)` +- 如果答案在相应上下文的范围内,则为 `(start_position, end_position)` ,其中 `start_position` 是答案起始处的 token 索引(在 inputs ID 中), `end_position` 是答案结束处的 token 索引(在 inputs ID 中) -为了确定是哪种情况以及标记的位置, 以及(如果相关的话)标记的位置, 我们首先在输入 ID 中找到开始和结束上下文的索引。我们可以使用标记类型 ID 来执行此操作, 但由于这些 ID 不一定存在于所有模型中 (例如, DistilBERT 不需要它们), 我们将改为使用我们的标记器返回的 `BatchEncoding` 的 `sequence_ids()` 方法。 +为了确定这两种情况中的哪一种,并且如果是第二种,则需要确定 token 的位置,我们首先找到在输入 ID 中起始和结束上下文的索引。我们首先找到拆分后的每一个部分在 `Context` 起始和结束的索引,可以使用 token 类型 ID 来完成此操作,但由于并非所有模型都支持这样的操作(如DistilBERT),因此可以使用 `tokenizer` 的 `sequence_ids()` 函数返回的 BatchEncoding 对象。 -一旦我们有了这些标记索引, 我们就会查看相应的偏移量, 它们是两个整数的元组, 表示原始上下文中的字符范围。因此, 我们可以检测此特征中的上下文块是在答案之后开始还是在答案开始之前结束(在这种情况下, 标签是 `(0, 0)`)。如果不是这样, 我们循环查找答案的第一个和最后一个标记: +有了这些 tokens 的索引之后,我们就可以计算相应的偏移量了,它们是两个整数的元组,表示原始 Context 中的字符范围。因此,我们可以检测每个分块中的 Context 块是在答案之后起始还是在答案起始之前结束(在这种情况下,标签是 `(0, 0)` )。如果答案就在 Context 里,我们就循环查找答案的第一个和最后一个 token: ```py answers = raw_datasets["train"][2:6]["answers"] @@ -291,7 +290,7 @@ for i, offset in enumerate(inputs["offset_mapping"]): end_char = answer["answer_start"][0] + len(answer["text"][0]) sequence_ids = inputs.sequence_ids(i) - # Find the start and end of the context + # 找到上下文的起始和结束 idx = 0 while sequence_ids[idx] != 1: idx += 1 @@ -300,12 +299,12 @@ for i, offset in enumerate(inputs["offset_mapping"]): idx += 1 context_end = idx - 1 - # If the answer is not fully inside the context, label is (0, 0) + # 如果答案不完全在上下文内,标签为(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: - # Otherwise it's the start and end token positions + # 否则,它就是起始和结束 token 的位置 idx = context_start while idx <= context_end and offset[idx][0] <= start_char: idx += 1 @@ -324,7 +323,7 @@ start_positions, end_positions [85, 53, 21, 0, 0, 70, 33, 0, 40, 0, 0, 0, 68, 35, 0, 0, 0, 0, 0]) ``` -让我们看一些结果来验证我们的方法是否正确。对于我们发现的第一个特征, 我们将 `(83, 85)` 作为标签, 让我们将理论答案与从 83 到 85 (包括)的标记解码范围进行比较: +让我们查看一些结果来验证一下我们的方法是否正确。在拆分后的第一个部分的文本中,我们看到了 `(83, 85)` 是待预测的标签值,因此让我们将理论答案与从 83 到 85(包括 85)的 tokens 解码的结果进行比较: ```py idx = 0 @@ -342,7 +341,7 @@ print(f"Theoretical answer: {answer}, labels give: {labeled_answer}") 'Theoretical answer: the Main Building, labels give: the Main Building' ``` -所以这是一场比赛! 现在让我们检查索引 4, 我们将标签设置为 `(0, 0)`, 这意味着答案不在该功能的上下文块中 +很好!寻找的答案是正确的!现在让我们来看一下拆分后的第4个文本块,我们我们得到的标签是 `(0, 0)` ,这意味着答案不在这个文本块中: ```py idx = 4 @@ -357,15 +356,15 @@ 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]' ``` -事实上, 我们在上下文中看不到答案。 +确实,我们在 Context 中没有看到答案。 -✏️ **轮到你了!** 使用 XLNet 架构时, 在左侧应用填充, 并切换问题和上下文。将我们刚刚看到的所有代码改编为 XLNet 架构 (并添加 `padding=True`)。请注意, `[CLS]` 标记可能不在应用填充的 0 位置。 +✏️ **轮你来了!** 在使用 XLNet 架构时,如果截取后的文本长度没有达到设定的最大长度,需要在左侧进行填充,并且需要交互问题和 Context 的顺序。尝试将我们刚刚看到的所有代码调整为 XLNet 架构(并添加 `padding=True` )。请注意,因为是在左侧填充的,所以填充后的 `[CLS]` tokens 可能不在索引为 0 的位置。 -现在我们已经逐步了解了如何预处理我们的训练数据, 我们可以将其分组到一个函数中, 我们将应用于整个训练数据集。我们会将每个特征填充到我们设置的最大长度, 因为大多数上下文会很长 (并且相应的样本将被分成几个特征), 所以在这里应用动态填充没有真正的好处: +现在,我们已经逐步了解了如何预处理我们的训练数据,接下来可以将其组合到一个函数中,并使用该函数处理整个训练数据集。我们将每个拆分后的样本都填充到我们设置的最大长度,因为大多数上下文都很长(相应的样本会被分割成几小块),所以在这里进行动态填充的所带来的增益不是很大。 ```py max_length = 384 @@ -398,7 +397,7 @@ def preprocess_training_examples(examples): end_char = answer["answer_start"][0] + len(answer["text"][0]) sequence_ids = inputs.sequence_ids(i) - # Find the start and end of the context + # 找到上下文的起始和结束 idx = 0 while sequence_ids[idx] != 1: idx += 1 @@ -407,12 +406,12 @@ def preprocess_training_examples(examples): idx += 1 context_end = idx - 1 - # If the answer is not fully inside the context, label is (0, 0) + # 如果答案不完全在上下文内,标签为(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: - # Otherwise it's the start and end token positions + # 否则,它就是起始和结束 tokens 的位置 idx = context_start while idx <= context_end and offset[idx][0] <= start_char: idx += 1 @@ -428,9 +427,9 @@ def preprocess_training_examples(examples): return inputs ``` -请注意, 我们定义了两个常数来确定使用的最大长度以及滑动窗口的长度, 并且我们在标记化之前添加了一点清理: SQuAD 数据集中的一些问题在开头有额外的空格, 并且不添加任何内容的结尾 (如果你使用像 RoBERTa 这样的模型, 则在标记化时会占用空间), 因此我们删除了那些额外的空格。 +请注意,我们定义了两个常量来确定所使用的最大长度以及滑动窗口的长度,并且在之前 tokenize 之前对数据进行了一些清洗:SQuAD 数据集中的一些问题在开头和结尾有额外的空格,这些空格没有任何意义(如果你使用像 RoBERTa 这样的模型,它们会占用 tokenize 的长度),因此我们去掉了这些额外的空格。 -为了将此函数应用于整个训练集, 我们使用 `Dataset.map()` 方法与 `batched=True` 标志。这是必要的, 因为我们正在更改数据集的长度(因为一个示例可以提供多个训练特征): +要使用该函数处理整个训练集,我们可以使用 `Dataset.map()` 方法并设置 `batched=True` 参数。这是必要的,因为我们正在更改数据集的长度(因为一个样本可能会产生多个子样本): ```py train_dataset = raw_datasets["train"].map( @@ -445,13 +444,13 @@ len(raw_datasets["train"]), len(train_dataset) (87599, 88729) ``` -正如我们所见, 预处理增加了大约 1,000 个特征。我们的训练集现在可以使用了-- 让我们深入研究验证集的预处理! +如我们所见,预处理增加了大约 1000 个样本。我们的训练集现在已经准备好使用了——让我们深入研究一下验证集的预处理! ### 处理验证数据 [[处理验证数据]] -预处理验证数据会稍微容易一些, 因为我们不需要生成标签(除非我们想计算验证损失, 但这个数字并不能真正帮助我们理解模型有多好)。真正的乐趣是将模型的预测解释为原始上下文的跨度。为此, 我们只需要存储偏移映射和某种方式来将每个创建的特征与它来自的原始示例相匹配。由于原始数据集中有一个 ID 列, 我们将使用该 ID。 +验证集的预处理会更加容易,因为我们不需要生成标签(除非我们想计算验证损失,但那个数字并不能真正帮助我们了解模型的好坏,如果要评估模型更好的方式使用我们之前提到的`squad` 指标)。真正的挑战在于将模型的预测转化为为原始 Context 的片段。为此,我们只需要存储偏移映射并且找到一种方法来将每个分割后的样本与分割前的原始片段匹配起来。由于原始数据集中有一个 ID 列,我们可以使用ID来代表原始的片段。 -我们将在这里添加的唯一内容是对偏移映射的一点点清理。它们将包含问题和上下文的偏移量, 但是一旦我们进入后处理阶段, 我们将无法知道输入 ID 的哪一部分对应于上下文以及哪一部分是问题(我们使用的 `sequence_ids()` 方法仅可用于标记器的输出)。因此, 我们将与问题对应的偏移量设置为 `None`: +我们唯一需要做的是对偏移映射进行一些微小修改。偏移映射包含问题和 Context 的偏移量(问题的偏移量是0,Context 是1),但当我们进入后处理阶段,我们将无法知道 inputs ID 的哪个部分对应于 Context,哪个部分是问题(我们使用的 `sequence_ids()` 方法仅可用于 tokenizer 的输出)。因此,我们将将与问题对应的偏移设置为 `None` Context 对应的偏移量保持不变: ```py def preprocess_validation_examples(examples): @@ -484,7 +483,7 @@ def preprocess_validation_examples(examples): return inputs ``` -我们可以像以前一样将此函数应用于整个验证数据集: +我们可以像处理训练集一样使用此函数处理整个验证数据集: ```py validation_dataset = raw_datasets["validation"].map( @@ -499,21 +498,21 @@ len(raw_datasets["validation"]), len(validation_dataset) (10570, 10822) ``` -I在这种情况下, 我们只添加了几百个样本, 因此验证数据集中的上下文似乎有点短。 +从最终的结果来看,我们只添加了几百个样本,因此验证数据集中的 Context 似乎要短一些。 -现在我们已经对所有数据进行了预处理, 我们可以开始训练了。 +现在我们已经对所有数据进行了预处理,我们可以开始训练了。 {#if fw === 'pt'} ## 使用 `Trainer` API 微调模型 [[使用 `Trainer` API 微调模型]] -这个例子的训练代码看起来很像前面几节中的代码 -- 最难的是编写 `compute_metrics()` 函数。由于我们将所有样本填充到我们设置的最大长度, 因此没有数据整理器要定义, 所以这个度量计算真的是我们唯一需要担心的事情。困难的部分是将模型预测后处理为原始示例中的文本范围; 一旦我们这样做了, 🤗 Datasets 库中的指标将为我们完成大部分工作。 +这个例子的训练代码与前面的部分非常相似,最困难的部分是编写 `compute_metrics()` 评估指标函数。由于我们将所有样本填充到了我们设置的最大长度,所以没有需要定义的数据整理器,因此我们唯一需要担心的事情是如何计算评估指标。比较困难的部分将是将模型预测的结果还原到原始示例中的文本片段;一旦我们完成了这一步骤,🤗 Datasets 库中的 metric 就可以帮助我们做大部分工作。 {:else} ## 使用 Keras 微调模型 [[使用 Keras 微调模型]] -这个示例的训练代码看起来很像前几节中的代码, 但是计算指标将是唯一的挑战。因为我们将所有的样本填充到我们设置的最大长度, 所以不需要定义数据整理器, 所以这个度量计算实际上是我们唯一需要担心的事情。困难的部分是将模型预测后处理成原始例子中的文本范围; 一旦我们完成了这些, 🤗 Datasets 库中的指标将为我们完成大部分工作。 +这个例子的训练代码与前面的部分非常相似,最困难的部分是计算评估指标。由于我们将所有样本填充到了我们设置的最大长度,所以没有需要定义的数据整理器,因此我们唯一需要担心的事情是计算评估指标。比较困难的部分将是将模型预测的结果还原到原始示例中的文本片段;一旦我们完成了这一步骤,🤗 Datasets 库中的 metric 就可以帮助我们做大部分工作。 {/if} @@ -529,16 +528,18 @@ I在这种情况下, 我们只添加了几百个样本, 因此验证数据集中 {/if} -该模型将在输入ID中为答案的开始和结束位置输出Logit, 正如我们在探索 [`question-answering` pipeline](/course/chapter6/3b) 时看到的那样。后处理步骤将类似于我们在那里所做的, 所以这里是我们采取的行动的快速提醒: +模型将输出答案在 inputs ID 中起始和结束位置的 logit,正如我们在探索 [`question-answering` pipeline](/course/chapter6/3b) 时看到的那样。后处理步骤与我们在那里所做的很相似,所以这里简单回顾一下我们所采取的操作: + +- 我们屏蔽了除了 Context 之外的 tokens 对应的起始和结束 logit。 +- 然后,我们使用 softmax 将起始和结束 logits 转换为概率。 +- 我们通过将两个概率对应的乘积来为每个 `(start_token, end_token)` 对计算一个分数。 +- 我们寻找具有最大分数且产生有效答案(例如, `start_token` 小于 `end_token` )的对。 -- 我们屏蔽了与上下文之外的标记相对应的开始和结束 logits。 -- 然后, 我们使用 softmax 将开始和结束 logits 转换为概率。 -- 我们通过取对应的两个概率的乘积来给每个 `(start_token, end_token)` 组合赋值。 -- 我们寻找产生有效答案的最高分数的配对 (例如, `start_token` 低于 `end_token`)。 +这次我们将稍微改变这个流程,因为我们不需要计算实际分数(只需要预测的答案的文本)。这意味着我们可以跳过 softmax 步骤(因为 softmax 并不会改变分数大小的排序)。为了加快计算速度,我们也不会为所有可能的 `(start_token, end_token)` 对计算分数,而只会计算与最高的 `n_best` 对应的 logit 分数(其中 `n_best=20` )。由于我们将跳过 softmax,这些分数将是 logit 分数,而且是起始和结束对数概率的和(而不是乘积,因为对数运算规则: \($\log(ab) = \log(a) + \log(b))$。 -在这里, 我们将稍微改变这个过程, 因为我们不需要计算实际分数 (只是预测的答案)。这意味着我们可以跳过 softmax 步骤。为了更快, 我们也不会对所有可能的 `(start_token, end_token)` 对进行评分, 而只会对对应于最高 `n_best` 的那些对进行评分 (使用 `n_best=20`)。由于我们将跳过 softmax, 因此这些分数将是 logit 分数, 并且将通过取 start 和 end logits 的总和来获得 (而不是乘积, 因为规则 \\(\log(ab) = \log(a) + \log(b)\\))。 +为了验证猜想,我们需要一些预测。由于我们还没有训练我们的模型,我们将使用 QA 管道的默认模型对一小部分验证集生成一些预测。我们可以使用和之前一样的处理函数;因为它依赖于全局常量 `tokenizer` ,我们只需将该对象更改为我们要临时使用的模型的 tokenizer -为了证明这一切, 我们需要一些预测。由于我们还没有训练我们的模型, 我们将使用 QA 管道的默认模型对一小部分验证集生成一些预测。我们可以使用和之前一样的处理函数; 因为它依赖于全局常量 `tokenizer`, 我们只需将该对象更改为我们要临时使用的模型的标记器: +为了测试这些代码,我们需要一些预测结果。由于我们还没有训练模型,我们将使用 QA pipeline 的默认模型在验证集的一小部分上生成一些预测结果。我们可以使用与之前相同的处理函数;因为它依赖于全局常量 `tokenizer` ,所以只需将其更改为这次临时使用的模型对应的 `tokenizer` 即可。 ```python small_eval_set = raw_datasets["validation"].select(range(100)) @@ -552,13 +553,13 @@ eval_set = small_eval_set.map( ) ``` -现在预处理已经完成, 我们将分词器改回我们最初选择的那个: +现在预处理已经完成,我们将 `tokenizer` 改回我们最初选择的 `tokenizer` : ```python tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) ``` -然后, 我们删除 `eval_set` 中模型不期待的列, 用所有的小验证集构建一个批次, 然后通过模型。如果 GPU 可用, 我们会使用它来加快速度: +然后我们移除 `eval_set` 中模型不需要的列,构建一个包含所有小型验证集数据的 batch,并将其传递给模型。如果有可用的 GPU,我们将使用 GPU 以加快计算: {#if fw === 'pt'} @@ -579,7 +580,7 @@ with torch.no_grad(): outputs = trained_model(**batch) ``` -由于 `Trainer` 将为我们提供 NumPy 数组的预测, 我们获取开始和结束 logits 并将它们转换为该格式 +为了便于实验,让我们将这些输出转换为 NumPy 数组: ```python start_logits = outputs.start_logits.cpu().numpy() @@ -601,7 +602,7 @@ trained_model = TFAutoModelForQuestionAnswering.from_pretrained(trained_checkpoi outputs = trained_model(**batch) ``` -为了便于实验, 让我们将这些输出转换为 NumPy 数组: +为了便于实验,让我们将这些输出转换为 NumPy 数组: ```python start_logits = outputs.start_logits.numpy() @@ -610,7 +611,7 @@ end_logits = outputs.end_logits.numpy() {/if} -现在, 我们需要在 `small_eval_set` 中找到每个示例的预测答案。一个示例可能已经在 `eval_set` 中拆分为多个特征, 因此第一步是将 `small_eval_set` 中的每个示例映射到 `eval_set` 中相应的特征: +现在,我们需要找到 `small_eval_set` 中每个样本的预测答案。一个样本可能会被拆分成 `eval_set` 中的多个子样本,所以第一步是将 `small_eval_set` 中的每个样本映射到 `eval_set` 中对应的子样本: ```python import collections @@ -620,13 +621,13 @@ for idx, feature in enumerate(eval_set): example_to_features[feature["example_id"]].append(idx) ``` -有了这个, 我们就可以真正开始工作, 循环遍历所有示例, 并为每个示例遍历所有相关功能。正如我们之前所说, 我们将查看 `n_best` 开始 logits 和结束 logits 的 logit 分数, 不包括以下的位置: +有了这个映射,我们可以通过循环遍历所有样本,并遍历每个样本的所有子样本。正如之前所说,我们将查看 `n_best` 个起始 logit 和结束 logit 的得分,排除以下情况: -- 一个不在上下文中的答案 -- 长度为负的答案 -- 答案太长 (我们将可能性限制在 `max_answer_length=30`) +- 答案不在上下文中 +- 答案长度为负数 +- 答案过长(我们将长度限制为 `max_answer_length=30` ) -一旦我们为一个示例获得了所有可能的答案, 我们只需选择一个具有最佳 logit 分数的答案: +当我们得到一个样本的所有得分可能答案,我们只需选择具有最佳 logit 得分的答案: ```python import numpy as np @@ -649,10 +650,10 @@ for example in small_eval_set: end_indexes = np.argsort(end_logit)[-1 : -n_best - 1 : -1].tolist() for start_index in start_indexes: for end_index in end_indexes: - # Skip answers that are not fully in the context + # 跳过不完全在上下文中的答案 if offsets[start_index] is None or offsets[end_index] is None: continue - # Skip answers with a length that is either < 0 or > max_answer_length. + # 跳过长度为负数或大于 max_answer_length 的答案。 if ( end_index < start_index or end_index - start_index + 1 > max_answer_length @@ -670,7 +671,7 @@ for example in small_eval_set: predicted_answers.append({"id": example_id, "prediction_text": best_answer["text"]}) ``` -预测答案的最终格式是我们将使用的度量标准所期望的格式。像往常一样, 我们可以在 🤗 Evaluate 库的帮助下加载它: +完成上述处理后,预测答案就变成了我们将使用的评估指标所要求的输入的格式,在这种情况下可以借助🤗 Evaluate 库来加载它。 ```python import evaluate @@ -678,7 +679,9 @@ import evaluate metric = evaluate.load("squad") ``` -该指标期望我们上面看到的格式的预测答案 (一个字典列表, 其中一个键用于示例 ID, 一个键用于预测文本) 和以下格式的理论答案 (一个字典列表, 一个键示例的 ID 和可能答案的一键): +这个评估指标一个如上所示格式(一个包含示例 ID 和预测文本的字典列表)的预测答案,同时也需要一个如下格式(一个包含示例 ID 和可能答案的字典列表)的参考答案: + +该评估指标需要一个由样本 ID 和预测文本字典的列表组成预测答案,同时也需要一个由参考ID 和可能答案字典的列表组成参考答案。 ```python theoretical_answers = [ @@ -686,7 +689,7 @@ theoretical_answers = [ ] ``` -我们现在可以通过查看两个列表的第一个元素来检查我们是否得到了合理的结果: +现在,我们可以通过查看两个列表中的第一个元素来检查是否符合评估指标的要求: ```python print(predicted_answers[0]) @@ -698,7 +701,7 @@ print(theoretical_answers[0]) {'id': '56be4db0acb8001400a502ec', 'answers': {'text': ['Denver Broncos', 'Denver Broncos', 'Denver Broncos'], 'answer_start': [177, 177, 177]}} ``` -还不错! 现在让我们看看这个指标给我们的分数: +还不错!现在让我们看一下评估指标给出的分数: ```python metric.compute(predictions=predicted_answers, references=theoretical_answers) @@ -708,17 +711,16 @@ metric.compute(predictions=predicted_answers, references=theoretical_answers) {'exact_match': 83.0, 'f1': 88.25} ``` -同样, 考虑到根据 [its paper](https://arxiv.org/abs/1910.01108v2), 在 SQuAD 上微调的 DistilBERT 在整个数据集上的得分分别为 79.1 和 86.9, 这是相当不错的。 +根据 [DistilBERT 的论文](https://arxiv.org/abs/1910.01108v2) 所述,DistilBERT 在 SQuAD 上微调后整体数据集的得分为 79.1 和 86.9,相比之下我们取得的结果相当不错。 {#if fw === 'pt'} -现在, 让我们把刚才所做的一切放在 `compute_metrics()` 函数中, 我们将在 `Trainer` 中使用它。通常, `compute_metrics()` 函数只接收一个包含 logits 和 labels 的元组 `eval_preds`。这里我们需要更多, 因为我们必须在特征数据集中查找偏移量, 在原始上下文的示例数据集中查找, 因此我们将无法在训练期间使用此函数获得常规评估结果。我们只会在训练结束时使用它来检查结果。 - -`compute_metrics()` 函数将与前面相同的步骤分组; 我们只是添加一个小检查, 以防我们没有提出任何有效的答案 (在这种情况下, 我们预测一个空字符串)。 +现在,让我们将刚才所做的放入 `compute_metrics()` 函数中,就可以在 `Trainer` 中使用它了。通常, `compute_metrics()` 函数只接收一个包含 logits 和带预测标签组成的 `eval_preds` 元组。但是在这里,我们需要更多的信息才能评估结果,因为我们需要在分割后的数据集中查找偏移量,并在原始数据集中查找原始 Context,因此我们无法在训练过程中使用此函数来获取常规的评估结果。我们只会在训练结束时使用它来检查训练的结果。 +`compute_metrics()` 函数与之前的步骤相同;我们只是添加了一个小的检查,以防我们找不到任何有效的答案(在这种情况下,我们的预测会输出一个空字符串)。 {:else} -现在, 让我们将刚才所做的一切放入 `compute_metrics()` 函数中, 我们将在训练模型后使用该函数。我们需要传递的不仅仅是输出日志, 因为我们必须在特征数据集中寻找偏移量, 在原始上下文的示例数据集中寻找: +现在,让我们将刚才所做的放入 `compute_metrics()` 函数中,就可以在训练模型时使用该函数。我们需要传递的不仅仅是输出的 logits,因为我们必须在分割后的数据集中查找偏移量,并在原始数据集中查找原始的 Context: {/if} @@ -737,7 +739,7 @@ def compute_metrics(start_logits, end_logits, features, examples): context = example["context"] answers = [] - # Loop through all features associated with that example + # 循环遍历与该示例相关联的所有特征 for feature_index in example_to_features[example_id]: start_logit = start_logits[feature_index] end_logit = end_logits[feature_index] @@ -747,10 +749,10 @@ def compute_metrics(start_logits, end_logits, features, examples): end_indexes = np.argsort(end_logit)[-1 : -n_best - 1 : -1].tolist() for start_index in start_indexes: for end_index in end_indexes: - # Skip answers that are not fully in the context + # 跳过不完全位于上下文中的答案 if offsets[start_index] is None or offsets[end_index] is None: continue - # Skip answers with a length that is either < 0 or > max_answer_length + # 跳过长度小于 0 或大于 max_answer_length 的答案 if ( end_index < start_index or end_index - start_index + 1 > max_answer_length @@ -763,7 +765,7 @@ def compute_metrics(start_logits, end_logits, features, examples): } answers.append(answer) - # Select the answer with the best score + # 选择得分最高的答案 if len(answers) > 0: best_answer = max(answers, key=lambda x: x["logit_score"]) predicted_answers.append( @@ -776,7 +778,7 @@ def compute_metrics(start_logits, end_logits, features, examples): return metric.compute(predictions=predicted_answers, references=theoretical_answers) ``` -我们可以检查它是否适用于我们的预测: +我们可以评估我们模型在评估数据集输出的结果: ```python compute_metrics(start_logits, end_logits, eval_set, small_eval_set) @@ -786,13 +788,13 @@ compute_metrics(start_logits, end_logits, eval_set, small_eval_set) {'exact_match': 83.0, 'f1': 88.25} ``` -看起来不错! 现在让我们用它来微调我们的模型。 +看起来不错!现在让我们使用它来微调我们的模型。 ### 微调模型 [[微调模型]] {#if fw === 'pt'} -我们现在准备好训练我们的模型了。让我们首先创建它, 像以前一样使用 `AutoModelForQuestionAnswering` 类: +现在我们已经准备好训练我们的模型了。首先,让我们像之前一样使用 `AutoModelForQuestionAnswering` 类创建模型: ```python model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) @@ -800,7 +802,7 @@ model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) {:else} -我们现在准备好训练我们的模型了。让我们首先创建它, 像以前一样使用 `TFAutoModelForQuestionAnswering` 类: +现在我们已经准备好训练我们的模型了。首先,让我们像之前一样使用 `TFAutoModelForQuestionAnswering` 类创建模型: ```python model = TFAutoModelForQuestionAnswering.from_pretrained(model_checkpoint) @@ -808,9 +810,9 @@ model = TFAutoModelForQuestionAnswering.from_pretrained(model_checkpoint) {/if} -像往常一样, 我们收到一个警告, 有些权重没有使用(来自预训练头的), 而另一些是随机初始化的 (用于问答头的)。你现在应该已经习惯了, 但这意味着这个模型还没有准备好使用, 需要微调 -- 我们即将这样做! +和之前一样,我们会收到一个警告,提示有些权重没有被使用(来自预训练头部的权重),而其他一些权重是随机初始化的(用于问答头部的权重)。你现在应该已经习惯了这种情况,但这意味着这个模型还没有准备好使用,需要进行微调——好在这正是我们接下来要做的事情! -为了能够将我们的模型推送到 Hub, 我们需要登录 Hugging Face。 如果你在笔记本中运行此代码, 则可以使用以下实用程序函数执行此操作, 该函数会显示一个小部件, 你可以在其中输入登录凭据: +为了能够将我们的模型推送到 Hub,我们需要登录 Hugging Face。如果你在 Notebook 中运行此代码,则可以使用以下的函数执行此操作,该函数会显示一个小部件,你可以在其中输入登录凭据进行登陆: ```python from huggingface_hub import notebook_login @@ -818,7 +820,7 @@ from huggingface_hub import notebook_login notebook_login() ``` -如果你不在笔记本中工作, 只需在终端中键入以下行: +如果你不在 Notebook 中工作,只需在终端中输入以下行: ```bash huggingface-cli login @@ -826,11 +828,11 @@ huggingface-cli login {#if fw === 'pt'} -完成后, 我们就可以定义我们的 `TrainingArguments`。正如我们在定义函数来计算度量时所说的那样, 由于 `compute_metrics()` 函数的签名, 我们将不能有常规的求值循环。我们可以编写 `Trainer` 的子类来完成这一任务(你可以在 [question answering example script](https://github.com/huggingface/transformers/blob/master/examples/pytorch/question-answering/trainer_qa.py)中找到一种方法), 但这对于本节来说有点太长了。相反, 我们只会在训练结束时评估模型, 并在下面的"自定义训练循环"向你展示如何进行常规评估。 +完成后,我们就可以定义我们的 `TrainingArguments` 。正如我们在定义计算评估函数时所说的,由于 `compute_metrics()` 函数的输入参数限制,我们无法使用常规的方法来编写评估循环。不过,我们可以编写自己的 `Trainer` 子类来实现这一点(你可以在 [问答示例代码](https://github.com/huggingface/transformers/blob/master/examples/pytorch/question-answering/trainer_qa.py) 中找到该方法),但放在本节中会有些冗长。因此,我们在这里将仅在训练结束时评估模型,并在下面的“自定义训练循环”中向你展示如何使用常规的方法进行评估。 -T这确实时 `Trainer` API 显示其局限性和 🤗 Accelerate 库的亮点所在: 根据特定用例自定义类可能很痛苦, 但调整完全公开的训练循环很容易。 +这确实是 `Trainer` API 局限性的地方,而🤗 Accelerate 库则非常适合处理这种情况:定制化特定用例的类可能会很繁琐,但定制化调整训练循环却很简单。 -来看看我们的 `TrainingArguments`: +让我们来看看我们的 `TrainingArguments` : ```python from transformers import TrainingArguments @@ -847,11 +849,11 @@ args = TrainingArguments( ) ``` -我们之前已经看到了其中的大部分: 我们设置了一些超参数 (比如学习率、训练的 epoch 数和一些权重衰减), 并表明我们希望在每个 epoch 结束时保存模型, 跳过评估, 并将我们的结果上传到模型中心。我们还使用 `fp16=True` 启用混合精度训练, 因为它可以在最近的 GPU 上很好地加快训练速度。 +我们之前已经见过其中大部分内容:我们设置了一些超参数(如学习率、训练的周期数和一些权重衰减),并设定我们想在每个周期结束时保存模型、跳过评估,并将结果上传到模型中心。我们还启用了 `fp16=True` 的混合精度训练,因为它可以在最新的 GPU 上加快训练速度。 {:else} -现在, 我们可以创建我们的 TF 数据集。这次我们可以使用简单的默认数据整理器: +现在完成了这一步骤,我们需要创建我们的 TF 数据集。这次我们可以使用简单的默认数据整理器: ```python from transformers import DefaultDataCollator @@ -859,7 +861,7 @@ from transformers import DefaultDataCollator data_collator = DefaultDataCollator(return_tensors="tf") ``` -现在我们像往常一样创建数据集。 +然后像往常一样创建数据集。 ```python tf_train_dataset = model.prepare_tf_dataset( @@ -876,16 +878,16 @@ tf_eval_dataset = model.prepare_tf_dataset( ) ``` -接下来, 我们设置了我们的训练超参数并编译了我们的模型: +接下来,我们设置训练超参数并编译我们的模型 ```python from transformers import create_optimizer from transformers.keras_callbacks import PushToHubCallback import tensorflow as tf -# 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. +# 训练步骤的数量是数据集中的样本数量除以 batch 大小,然后乘以总的训练周期数。 +# 注意,这里的 tf_train_dataset 是一个batch的 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( @@ -896,25 +898,25 @@ optimizer, schedule = create_optimizer( ) model.compile(optimizer=optimizer) -# Train in mixed-precision float16 +# 使用 float16 混合精度进行训练 tf.keras.mixed_precision.set_global_policy("mixed_float16") ``` -最后, 我们准备好使用 `model.fit()` 进行训练了。我们使用 `PushToHubCallback` 在每个时期之后将模型上传到Hub。 +最后,我们准备使用 `model.fit()` 进行训练,使用 `PushToHubCallback` 在每个周期结束后将模型上传到模型中心。 {/if} -默认情况下, 使用的存储库将在您的命名空间中, 并以您设置的输出目录命名, 因此在我们的例子中, 它将在 `"sgugger/bert-finetuned-squad"` 中。我们可以通过传递 `hub_model_id` 来覆盖它; 例如, 为了将模型推送到 `huggingface_course` 组织, 我们使用了 `hub_model_id="huggingface_course/bert-finetuned-squad"` (这是我们在本节开头链接的模型)。 +默认情况下,使用的仓库将保存在你的账户中,并以你设置的输出目录命名,所以在我们的例子中它将位于 `"sgugger/bert-finetuned-squad"` 中。我们可以通过传递一个 `hub_model_id` 参数来覆盖这个设置;例如,要将模型推送到我们使用的 `huggingface_course` 组织中,我们使用了 `hub_model_id="huggingface_course/bert-finetuned-squad"` (这是我们在本节开始时演示的模型)。 {#if fw === 'pt'} -💡 如果您使用的输出目录存在, 则它需要是您要推送到的存储库的本地克隆 (因此, 如果在定义 `Trainer` 时出错, 请设置新名称)。 +💡 如果你正在使用的输出目录已经存在一个同名的文件,则它需要是你要推送到的存储库克隆在本地的版本(因此,如果在定义你的 `Trainer` 时出现错误,请设置一个新的名称)。 -最后, 我们只需将所有内容传递给 `Trainer` 类并启动训练: +最后,我们只需将所有内容传递给 `Trainer` 类并启动训练: ```python from transformers import Trainer @@ -936,17 +938,17 @@ from transformers.keras_callbacks import PushToHubCallback callback = PushToHubCallback(output_dir="bert-finetuned-squad", tokenizer=tokenizer) -# We're going to do validation afterwards, so no validation mid-training +# 我们将在之后进行验证,因此训练过程中不会进行验证 model.fit(tf_train_dataset, callbacks=[callback], epochs=num_train_epochs) ``` {/if} -请注意, 在进行训练时, 每次保存模型时 (这里是每个 epoch) 它都会在后台上传到 Hub。这样, 如有必要, 你将能够在另一台机器上恢复训练。整个培训需要一段时间 (在 Titan RTX 上需要一个多小时), 因此您可以喝杯咖啡或重读课程中您发现在进行过程中更具挑战性的部分内容。另请注意, 一旦第一个 epoch 完成, 你将看到一些权重上传到 Hub, 你可以开始在其页面上使用你的模型。 +请注意,在训练过程中,每次模型保存(例如,每个 epoch 结束时),模型都会在后台上传到 Hub。这样,如果需要的话,你就可以在另一台机器上恢复训练。整个训练过程需要一些时间(在 Titan RTX 上略超过一个小时),所以你可以喝杯咖啡或者重新阅读一些你觉得更具挑战性的课程部分来消磨时间。还要注意,在第一个 epoch 完成后,你可以看到一些权重上传到 Hub,并且你可以在其页面上开始使用你的模型进行测试。 {#if fw === 'pt'} -一旦训练完成, 我们终于可以评估我们的模型(并祈祷我们没有把所有的计算时间都花在任何事情上)。`Trainer` 的 `predict()` 方法将返回一个元组, 其中第一个元素将是模型的预测 (这里是带有开始和结束 logits 的对)。我们将其发送给 `compute_metrics()` 函数: +训练完成后,我们就可以评估我们最终的模型了(并祈祷我们可以一次成功)。 `Trainer` 的 `predict()` 方法将返回一个元组,其中第一个元素将是模型的预测结果(在这里是一个包含起始和结束 logits 的数值对)。我们将这个结果传递给我们的 `compute_metrics()` 函数: ```python predictions, _, _ = trainer.predict(validation_dataset) @@ -956,7 +958,7 @@ compute_metrics(start_logits, end_logits, validation_dataset, raw_datasets["vali {:else} -一旦训练完成, 我们终于可以评估我们的模型(并祈祷我们没有把所有的计算时间都花在任何事情上)。我们的 `model` 的 `predict()` 方法将负责获取预测, 并且由于我们之前已经完成了定义 `compute_metrics()` 函数的所以艰苦工作, 因此我们可以在一行中获得结果: +训练完成后,我们可以最终评估我们的模型(并祈祷我们可以一次成功)。我们可以使用 `model` 的 `predict()` 方法将负责获取预测结果,并且由于我们之前已经定义了一个 `compute_metrics()` 函数,所以我们可以用一行代码得到我们的结果: ```python predictions = model.predict(tf_eval_dataset) @@ -974,45 +976,44 @@ compute_metrics( {'exact_match': 81.18259224219489, 'f1': 88.67381321905516} ``` -很好! 作为比较, BERT 文章中报告的该模型的基线分数是 80.8 和 88.5, 所以我们应该是正确的。 +很棒!作为对比,BERT 文章中报告的该模型的基准分数分别为 80.8 和 88.5,所以我们的结果正好达到了预期分数。 {#if fw === 'pt'} -最后, 我们使用 `push_to_hub()` 方法确保我们上传模型的最新版本: +最后,我们使用 `push_to_hub()` 方法确保上传模型的最新版本: ```py trainer.push_to_hub(commit_message="Training complete") ``` -如果你想检查它, 这将返回它刚刚执行的提交的 URL: +如果你想检查它,上面的代码返回它刚刚执行的提交的 URL: ```python out 'https://huggingface.co/sgugger/bert-finetuned-squad/commit/9dcee1fbc25946a6ed4bb32efb1bd71d5fa90b68' ``` - -`Trainer` 还起草了包含所有评估结果的模型卡并上传。 +`Trainer` 还会创建一个包含所有评估结果的模型卡片,并将其上传。 {/if} -在这个阶段, 你可以使用模型中心上的推理小部件来测试模型并与您的朋友、家人和最喜欢的宠物分享。你已经成功地微调了一个问答任务的模型 -- 恭喜! +在这个阶段,你可以使用模型库中的推理小部件来测试模型,并与你的朋友、家人和同伴分享。恭喜你成功地在问答任务上对模型进行了微调! -✏️ **轮到你了!** 尝试另一种模型架构, 看看它是否在此任务上表现更好! +✏️ **轮到你了!** 尝试使用另一个模型架构,看看它在这个任务上表现得是否更好! {#if fw === 'pt'} -如果你想更深入地了解训练循环, 我们现在将向你展示如何使用 🤗 Accelerate 来做同样的事情。 +如果你想更深入地了解训练循环,我们现在将向你展示如何使用 🤗 Accelerate 来做同样的事情。 ## 自定义训练循环 [[自定义训练循环]] -现在让我们来看一下完整的训练循环, 这样您就可以轻松地自定义所需的部分。它看起来很像 [第三章](/course/chapter3/4) 中的训练循环, 除了评估循环。我们将能够定期评估模型, 因为我们不再受 `Trainer` 类的限制。 +现在,让我们来看一下完整的训练循环,这样你就可以轻松地自定义所需的部分。它看起来很像 [第三章](https://www.hubchat.top/course/chapter3/4) 中的训练循环,只是评估的过程有所不同。由于我们不再受 `Trainer` 类的限制,因此我们可以在模型训练的过程中定期评估模型。 ### 为训练做准备 [[为训练做准备]] -首先, 我们需要从我们的数据集中构建 `DataLoader`。我们将这些数据集的格式设置为 `"torch"`, 并删除模型未使用的验证集中的列。然后, 我们可以使用 Transformers 提供的 `default_data_collator` 作为 `collate_fn`, 并打乱训练集, 但不打乱验证集58: +首先,我们需要使用数据集构建 `DataLoader` 。我们将这些数据集的格式设置为 `"torch"` ,并删除模型不使用的验证集的列。然后,我们可以使用 Transformers 提供的 `default_data_collator` 作为 `collate_fn` ,并打乱训练集,但不打乱验证集: ```py from torch.utils.data import DataLoader @@ -1033,13 +1034,13 @@ eval_dataloader = DataLoader( ) ``` -接下来我们重新实例化我们的模型, 以确保我们不会继续之前的微调, 而是再次从 BERT 预训练模型开始: +接下来,我们重新实例化我们的模型,以确保我们不是从上面的微调继续训练,而是从原始的 BERT 预训练模型重新开始训练: ```py model = AutoModelForQuestionAnswering.from_pretrained(model_checkpoint) ``` -然后我们需要一个优化器。像往常一样, 我们使用经典的 `AdamW`, 它与 Adam 类似, 但对权重衰减的应用方式进行了修复: +然后,我们需要一个优化器。通常我们使用经典的 `AdamW` 优化器,它与 Adam 类似,不过在权重衰减的方式上有些不同: ```py from torch.optim import AdamW @@ -1047,7 +1048,7 @@ from torch.optim import AdamW optimizer = AdamW(model.parameters(), lr=2e-5) ``` -一旦我们拥有所有这些对象, 我们可以将它们发送给 `accelerator.prepare()` 方法。请记住, 如果您想在 Colab 笔记本中的 TPU 上进行训练, 您需要将所有这些代码移动到一个训练函数中, 并且不应该执行任何实例化 `Accelerator` 的单元。我们可以通过传递 `fp16=True` 给 `Accelerator` (或者, 如果你将代码作为脚本执行, 只需确保适当地填写 🤗 Accelerate `config` )。 +当我们拥有了所有这些对象,我们可以将它们发送到 `accelerator.prepare()` 方法。请记住,如果你想在 Colab Notebook 上使用 TPU 进行训练,你需要将所有这些代码移到一个训练函数中,不要在 Colab Notebook 的单元格中直接实例化 `Accelerator` 对象。这是因为在 TPU 环境下,直接在单元格中实例化可能会导致资源分配和初始化的问题。此外我们还可以通过向 `Accelerator` 传递 `fp16=True` 来强制使用混合精度训练(或者,如果你想要将代码作为脚本执行,只需确保填写正确的🤗 Accelerate `config` )。 ```py from accelerate import Accelerator @@ -1058,7 +1059,7 @@ model, optimizer, train_dataloader, eval_dataloader = accelerator.prepare( ) ``` -从前面几节中你应该知道, 我们只能使用 `train_dataloader` 长度来计算经过 `accelerator.prepare()` 方法后的训练步骤的数量。我们使用与前几节相同的线性时间表: +从前面几节中你应该知道,我们只有在 `train_dataloader` 通过 `accelerator.prepare()` 方法后才能使用其长度来计算训练步骤的数量。我们使用与之前章节相同的线性学习率调度: ```py from transformers import get_scheduler @@ -1075,7 +1076,7 @@ lr_scheduler = get_scheduler( ) ``` -要将我们的模型推送到 Hub, 我们需要在工作文件夹中创建一个 `Repository` 对象。如果你尚未登录, 请先登录 Hugging Face Hub。我们将根据我们想要为模型提供的模型 ID 确定存储库名称 (随意用你自己的选择替换 `repo_name`; 它只需要包含你的用户名, 这就是函数 `get_full_repo_name()` 所做的): +要将模型推送到 Hub,我们需要在工作文件夹中创建一个 `Repository` 对象。如果你尚未登录 Hugging Face Hub,请先登录。我们将根据我们给模型指定的模型 ID 确定仓库名称(可以根据自己的选择替换 `repo_name` ;只需要包含你的用户名即可,用户名可以使用 `get_full_repo_name()` 函数可以获取): ```py from huggingface_hub import Repository, get_full_repo_name @@ -1089,24 +1090,25 @@ repo_name 'sgugger/bert-finetuned-squad-accelerate' ``` -然后我们可以将该存储库克隆到本地文件夹中。如果它已经存在, 这个本地文件夹应该是我们正在使用的存储库的克隆: +然后,我们可以将该存储库克隆到本地文件夹中。如果在设定的目录中已经存在一个同名的文件夹,那么这个本地文件夹应该是我们正在使用的仓库克隆在本地的版本,否则它会报错: ```py output_dir = "bert-finetuned-squad-accelerate" repo = Repository(output_dir, clone_from=repo_name) ``` -我们现在可以通过调用 `repo.push_to_hub()` 方法上传我们保存在 `output_dir` 中的任何内容。这将帮助我们在每个 epoch 结束时上传中间模型。 +现在,我们可以通过调用 `repo.push_to_hub()` 方法上传保存在 `output_dir` 中的所有内容。这将帮助我们在每个时期结束时上传中间模型。 ## 训练循环 [[训练循环]] -我们现在准备编写完整的训练循环。在定义了一个进度条来跟踪训练进行后, 循环分为三个部分: +现在,我们准备编写完整的训练循环。在定义一个进度条以跟踪训练进度之后,循环分为三个部分: + +- 训练本身,即对 `train_dataloader` 进行迭代,模型前向传播、反向传播和优化器更新。 +- 评估,我们将遍历整个评估数据集,同时收集 `start_logits` 和 `end_logits` 中的所有值。完成评估循环后,我们会将所有结果汇总到一起。需要注意的是,由于 `Accelerator` 可能会在最后添加一些额外的样本,以确保每个进程中的样本数量相同,因此我们需要对这些数据进行截断,以防止多余样本影响最终结果。 -- 训练本身是对 `train_dataloader` 的经典迭代, 前向传递模型, 然后反向传递和优化器步骤。 -- 在计算中, 我们在将 `start_logits` 和 `end_logits` 的所有值转换为 NumPy 数组之前, 收集它们的所有值。评估循环完成后,我们将连接所有结果。请注意, 我们需要截断, 因为 `Accelerator` 可能在最后添加了一些示例, 以确保我们在每个过程中拥有相同数量的示例。 -- 保存和上传, 这里我们先保存模型和分词器, 然后调用 `repo.push_to_hub()`。正如我们之前所做的那样, 我们使用参数 `blocking=False` 来告诉 🤗 Hub 库推入一个异步进程。这样, 训练正常继续, 并且这个 (长) 指令在后台执行。 +- 保存和上传,首先保存模型和 Tokenizer,然后调用 `repo.push_to_hub()` 。与之前一样,我们使用 `blocking=False` 参数告诉🤗 Hub 库在异步进程中推送。这样,训练将继续进行,而这个(需要很长时间的)上传指令将在后台异步执行。 -这是训练循环的完整代码: +以下训练循环的完整代码: ```py from tqdm.auto import tqdm @@ -1115,7 +1117,7 @@ import torch progress_bar = tqdm(range(num_training_steps)) for epoch in range(num_train_epochs): - # Training + # 训练 model.train() for step, batch in enumerate(train_dataloader): outputs = model(**batch) @@ -1127,7 +1129,7 @@ for epoch in range(num_train_epochs): optimizer.zero_grad() progress_bar.update(1) - # Evaluation + # 评估 model.eval() start_logits = [] end_logits = [] @@ -1149,7 +1151,7 @@ for epoch in range(num_train_epochs): ) print(f"epoch {epoch}:", metrics) - # Save and upload + # 保存和上传 accelerator.wait_for_everyone() unwrapped_model = accelerator.unwrap_model(model) unwrapped_model.save_pretrained(output_dir, save_function=accelerator.save) @@ -1160,7 +1162,7 @@ for epoch in range(num_train_epochs): ) ``` -如果这是您第一次看到使用 🤗 Accelerate 保存的模型, 让我们花点时间检查一下它附带的三行代码: +如果这是你第一次看到使用🤗 Accelerate 保存的模型,请花点时间了解一下与之相关的三行代码 ```py accelerator.wait_for_everyone() @@ -1168,20 +1170,20 @@ 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()`。 +第一行很好理解:它告诉所有进程在继续之前等待所有进程都到达该阶段。这是为了确保我们在保存之前,在每个进程中都有相同的模型。然后,我们获取 `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) 上查看我们使用此代码训练的模型。如果你想测试对训练循环的任何调整, 你可以通过编辑上面显示的代码直接实现它们! +完成后,你应该拥有一个产生与使用 `Trainer` 训练的模型非常相似的结果的模型。你可以在 [`huggingface-course/bert-finetuned-squad-accelerate`](https://huggingface.co/huggingface-course/bert-finetuned-squad-accelerate) 查看我们使用此代码训练的模型。如果你想测试对训练循环进行的任何调整,可以直接通过编辑上面显示的代码来实现! {/if} ## 使用微调模型 [[使用微调模型]] -我们已经向您展示了如何将我们在模型中心微调的模型与推理小部件一起使用。要在 `pipeline` 中本地使用它, 你只需要指定模型标识符: +我们已经向你展示了如何使用在模型中心上进行微调的模型,并使用推理小部件进行测试。要在本地使用 `pipeline` 来使用微调的模型,你只需指定模型标识符: ```py from transformers import pipeline -# Replace this with your own checkpoint +# 将其替换为你自己的 checkpoint model_checkpoint = "huggingface-course/bert-finetuned-squad" question_answerer = pipeline("question-answering", model=model_checkpoint) @@ -1200,4 +1202,4 @@ question_answerer(question=question, context=context) 'answer': 'Jax, PyTorch and TensorFlow'} ``` -很棒! 我们的模型与该管道的默认模型一样有效! +很棒!我们的模型与 pipeline 的默认模型一样有效! diff --git a/chapters/zh-CN/chapter7/8.mdx b/chapters/zh-CN/chapter7/8.mdx index 6d691a65a..51b36c22b 100644 --- a/chapters/zh-CN/chapter7/8.mdx +++ b/chapters/zh-CN/chapter7/8.mdx @@ -2,16 +2,16 @@ 如果你在课程中做到了这一步,恭喜你——你现在拥有了用 🤗 Transformers 和 Hugging Face 生态系统解决(几乎)任何 NLP 任务所需的所有知识和工具! -我们见过很多不同的数据整理器,所以我们制作了这个小视频来帮助您找到每个任务使用哪一个: +我们见过很多不同的数据整理器,所以我们制作了这个小视频来帮助你找到每个任务使用哪一个: -在完成核心 NLP 任务的快速入门后,您应该: +在完成核心 NLP 任务的快速入门后,你应该: -* 了解哪种架构(编码器、解码器或编码器-解码器)最适合每个任务 +* 了解哪种架构(编码器、解码器或编码器-解码器)最适合哪种任务 * 了解预训练和微调语言模型之间的区别 -* 了解如何使用 `Trainer` API 和 🤗 Accelerate 或 TensorFlow 和 Keras 的分布式训练功能来训练 Transformer 模型,具体选择那一种方法取决于您所需要完成的任务。 +* 了解如何使用 `Trainer` API 和 🤗 Accelerate 或 TensorFlow 和 Keras 的分布式训练功能来训练 Transformer 模型,具体选择那一种方法取决于你所需要完成的任务。 * 了解 ROUGE 和 BLEU 等指标在文本生成任务中的意义和局限性 -* 知道如何在 Hub 上和使用 🤗 Transformers 中的“管道”与您的微调模型进行交互 +* 知道如何在 Hub 上和使用 🤗 Transformers 中的“管道”与你的微调模型进行交互 -尽管掌握了所有这些知识,但总有一天你会遇到代码中的困难错误,或者对如何解决特定的 NLP 问题有疑问。幸运的是,Hugging Face 社区随时为您提供帮助!在这部分课程的最后一章中,我们将探讨如何调试 Transformer 模型并有效地寻求帮助。 \ No newline at end of file +尽管掌握了所有这些知识,但总有一天你会遇到代码中的困难错误,或者对如何解决特定的 NLP 问题有疑问。幸运的是,Hugging Face 社区随时为你提供帮助!在这部分课程的最后一章中,我们将探讨如何调试 Transformer 模型并有效地寻求帮助。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter7/9.mdx b/chapters/zh-CN/chapter7/9.mdx index 0ab1edce2..2ff82914c 100644 --- a/chapters/zh-CN/chapter7/9.mdx +++ b/chapters/zh-CN/chapter7/9.mdx @@ -4,14 +4,15 @@ # 章末小测验 [[章末小测验]] -Let's test what you learned in this chapter! +让我们测试一下你在这章学到了什么! + +### 1.以下哪些任务可以看作为 token 分类问题? -### 1.下列哪个任务可以被框定为令牌分类问题? -### 2.令牌分类预处理的哪一部分与其他预处理管道不同? +### 2. token 分类的预处理部分与其他预处理流程有什么不同? + -100 作为我们希望在丢失时忽略的令牌的标签。" + text: "我们使用 -100 来标记特殊 token 。", + explain: "这不是 token 分类特有的 —— 我们总是用 -100 作为我们想在损失中忽略的 token 的标签。" }, { - text: "因为互联网上有大量的文本", - explain: "的确如此! 但这并不是唯一的区别。", + text: "在进行截断/填充时,我们需要确保将待预测的标签截断或填充到与输入相同的大小", + explain: "的确如此!但这并不是唯一的区别。", correct: true } ]} /> -### 3.当我们对标记分类问题中的单词进行标记并希望标记时,会出现什么问题? +### 3.在 token 分类问题中,当我们分词并想要子词分词时,会出现什么问题? + -100 标记为 < code > ,以便在丢失时忽略它们。" + text: "Tokenizer 添加了特殊的 token ,我们没有这些 token 的标签。", + explain: "我们把这些 token ID设置为 -100,所以在计算损失时它们会被忽略。" }, { - text: "每个单词可以产生几个标记,所以我们最终得到的标记比标签多。", - explain: "这是主要的问题,我们需要将原始标签与标记对齐。", + text: "每个词可能产生多个 token ,因此我们最终会得到比我们拥有的标签更多的 token 。", + explain: "这是主要的问题,我们需要将原始的标签与 token 对齐。", correct: true }, { - text: "因为目标是按顺序排列的文本问题", - explain: "这是不正确的; 我们需要尽可能多的标签,否则我们的模型就会出错。" + text: "添加的 token 没有标签,所以没有问题。", + explain: "这是不正确的;我们需要和 token 相同数量的标签,否则我们的模型会报错。" } ]} /> @@ -77,92 +80,92 @@ Let's test what you learned in this chapter! -### 5.掩码语言建模问题中的标签是什么? +### 5.掩码语言建模问题中的标签是什么? -### 6.这些任务中的哪一个可以看作是一个顺序到顺序的问题? +### 6.哪些任务可以被看作是序列到序列的问题? -100 来标记特殊标记。", + text: "将一段中文文本翻译成英文。", explain: "这绝对是一个从序列到序列的问题。你能发现另一个吗?", correct: true }, { - text: "修正我侄子/朋友发来的信息,使它们用正确的英语", - explain: "这是一种翻译问题,所以肯定是一个顺序到顺序的任务。但这不是唯一正确的答案!", + text: "修正我侄子/朋友发来的信息,纠正他们的语法错误", + explain: "这是一种翻译问题,所以绝对是一个序列到序列的任务。这不是唯一的正确答案。", correct: true } ]} /> -### 7.对于序列到序列的问题,预处理数据的正确方法是什么? +### 7.对于序列到序列的问题,预处理数据的正确方法是什么? input = ... 和 < code > target = ... 。", - explain: "这可能是我们将来添加的一个 API,但现在不可能。" + text: "输入和目标必须一起发送到 tokenizer ,使用 `input = ...` 和 `target = ...` 。", + explain: "这可能是我们未来要添加的一个 API,但现在还不行。" }, { - text: "输入和目标都必须在对标记器的两个独立调用中进行预处理。", - explain: "不,这是在训练一个模型; 这里没有适应性。" + text: "输入和目标都必须在 tokenizer 的两次独立调用中进行预处理。", + explain: "这是正确的,但是不完整。你还需要做一些事情来确保 tokenizer 正确处理两者。" }, { - text: "因为我们在训练之后计算度量", - explain: "不是在序列分类问题; 目标也是文本,我们需要转换成数字!" + text: "像往常一样,我们只需要对输入进行 tokenize", + explain: "在一个序列到序列问题中,并不仅仅是输入文本需要进行 tokenize,目标文本也需要进行同样的转换!" }, { - text: "输入必须发送到标记器,目标也是如此,但需要使用特殊的上下文管理器。", - explain: "没错,标记器需要由上下文管理器放入目标模式。", + text: "输入序列和目标序列都需要通过特殊的上下文管理器分别发送给 tokenizer 进行预处理。", + explain: "这是正确的, tokenizer 需要通过该上下文管理器找到目标序列的范围并进行处理。", correct: true } ]} @@ -170,71 +173,73 @@ Let's test what you learned in this chapter! {#if fw === 'pt'} -### 8.为什么序列到序列问题有一个特定的“培训者”子类? +### 8.为什么需要有一个特定的 `Trainer `子类来解决序列到序列问题? + -100 的标签", - explain: "这根本不是习惯性的损失,而是损失总是通过计算得到的。" + text: "因为序列到序列问题使用自定义的损失计算方法,忽略 -100 的标签", + explain: "这根本不是自定义的损失计算方法,而是自然语言处理中一种常规的忽略特定 token 计算方式。" }, { - text: "当您拥有大量可用数据时,即使有一个经过预先训练的模型可以处理这些数据", - explain: "没错。 Sequence-to-sequence models' predictions are often run using the generate() method.", + text: "因为序列到序列问题需要特殊的评估循环", + explain: "没错。序列到序列模型的预测通常需要使用 generate() 方法", correct: true }, { - text: "文本是作为单词给出的,所以我们只需要应用子词的标记。", - explain: "< code > Trainer 并不关心这些,因为它们以前已经被预处理过。" + text: "因为该问题中的预测目标是序列到序列中问题部分的文本", + explain: "这不是Trainer需要考虑的部分,因为这些文本在进入`Tranier`之前已经被预处理过。" }, { text: "因为我们在序列到序列问题中使用了两个模型", - explain: "我们确实在某种程度上使用了两种模型,编码器和解码器,但是它们被组合在一个模型中。" + explain: "我们确实以某种方式使用两种模型,编码器和解码器,但它们被组合在一个模型中" } ]} /> {:else} -### 9.为什么在 Transformer 模型上调用“ compile ()”时通常不需要指定损失? +### 9.为什么在 Transformer 模型上调用 `compile()` 时通常不需要指定损失的计算方法? + input = ... 和 < code > target = ... 。", + text: "因为默认使用模型的内部损失计算方法", explain: "没错!", correct: true }, { - text: "因为我们在训练之后计算指标", - explain: "这可以被定义为一个从序列到序列的问题,尽管这不是唯一正确的答案。" + text: "因为我们在训练后计算评估指标", + explain: "我们确实经常这样做,但这并不能解释我们在训练中优化的损失值是从哪里得到的。" }, { - text: "因为损失是在“ model.fit ()”中指定的", - explain: "不,损失函数在运行‘ model.com pile ()’时是固定的,不能在‘ model.fit ()’中更改。" + text: "因为损失是在`model.fit()`中指定的", + explain: "不,一旦运行`model.compile()`,损失函数就总是固定的,并且不能在`model.fit()`中更改。" } ]} /> {/if} -### 10.你应该在什么时候预先训练一个新的模型? +### 10.你应该在什么时候预先训练一个新的模型? -### 11.为什么在大量的文本上预先训练一个语言模型是很容易的呢? +### 11.为什么在大量的文本上预先训练一个语言模型是很容易的呢? + -### 12.问答任务的预处理数据时,主要的挑战是什么? +### 12.问答任务预处理数据时,主要的挑战是什么? + -### 13.问题回答中的后处理通常是怎样进行的? +### 13.问答任务中的后处理通常是怎样进行的? diff --git a/chapters/zh-CN/chapter8/1.mdx b/chapters/zh-CN/chapter8/1.mdx index 7f2e8f531..fbb89a62b 100644 --- a/chapters/zh-CN/chapter8/1.mdx +++ b/chapters/zh-CN/chapter8/1.mdx @@ -1,12 +1,12 @@ # 介绍 [[介绍]] -既然您知道如何使用🤗 Transformers处理最常见的NLP任务,您应该能够开始自己的项目了!在本章中,我们将探讨遇到问题时该怎么做。您将学习如何成功调试代码或训练,以及如果自己无法解决问题,如何向社区寻求帮助。如果您认为您在其中一个拥抱人脸库中发现了错误,我们将向您展示报告它的最佳方式,以便尽快解决问题。 +现在,你已经知道如何使用🤗 Transformers 处理最常见的 NLP 任务,可以开始调试自己的项目了!在本章中我们将探讨可能遇到的问题以及解决方法。你将学习如何成功调试代码和训练,以及在无法自行解决问题时如何向社区寻求帮助。如果你发现了 Hugging Face 库中的一个 bug,我们会告诉你报告 bug 的最佳方法,以便尽快解决问题。 -更准确地说,在本章中,您将学习: +更准确地说,在本章中,你将学习: - 出现错误时要做的第一件事 -- 如何在网上寻求帮助[forums](https://discuss.huggingface.co/) -- 如何调试您的训练管道 +- 如何在 [论坛](https://discuss.huggingface.co) 寻求帮助 +- 如何调试你的训练管道 - 如何写一个好问题 -当然,所有这些都与 🤗 Transformers 或Hugging Face 生态无关;本章的经验教训适用于大多数开源项目! +当然,所有这些都与 🤗 Transformers 或 Hugging Face 生态无关;本章的经验教训适用于大多数开源项目! diff --git a/chapters/zh-CN/chapter8/2.mdx b/chapters/zh-CN/chapter8/2.mdx index bcab43e75..151113931 100644 --- a/chapters/zh-CN/chapter8/2.mdx +++ b/chapters/zh-CN/chapter8/2.mdx @@ -7,11 +7,11 @@ {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter8/section2.ipynb"}, ]} /> -在本节中, 我们将研究当你尝试从新调整的 Transformer 模型生成预测时可能发生的一些常见错误。这将为 [第四节](/course/chapter8/section4) 做准备, 我们将探索如何调试训练阶段本身。 +在本节中,我们将研究当你尝试从新调整的 Transformer 模型生成预测时可能发生的一些常见错误。本节为将 [第四节](/course/chapter8/section4) 做准备,在那一节中探索如何调试训练阶段本身。 -我们为这一节准备了一个 [模板模型库](https://huggingface.co/lewtun/distilbert-base-uncased-finetuned-squad-d5716d28), 如果你想运行本章中的代码, 你首先需要将模型复制到你的 [Hugging Face Hub](https://huggingface.co) 账号。为此, 首先通过在 Jupyter 笔记本中运行以下任一命令来登录: +我们为这一节准备了一个 [模板仓库](https://huggingface.co/lewtun/distilbert-base-uncased-finetuned-squad-d5716d28) ,如果你想运行本章中的代码,首先需要将模型复制到自己的 [Hugging Face Hub](https://huggingface.co) 账号。这需要你在 Jupyter Notebook 中运行以下命令来登录: ```python from huggingface_hub import notebook_login @@ -19,13 +19,13 @@ from huggingface_hub import notebook_login notebook_login() ``` -或在你最喜欢的终端中执行以下操作: +或在你最喜欢的终端中执行以下操作: ```bash huggingface-cli login ``` -这将提示你输入用户名和密码, 并将在下面保存一个令牌 *~/.cache/huggingface/*。登录后, 你可以使用以下功能复制模板存储库: +这里将会提示你输入用户名和密码,登陆后会自动在 `~/.cache/huggingface/` 保存一个令牌 完成登录后,可以使用以下功能克隆模板仓库: ```python from distutils.dir_util import copy_tree @@ -33,32 +33,32 @@ from huggingface_hub import Repository, snapshot_download, create_repo, get_full def copy_repository_template(): - # Clone the repo and extract the local path + # 克隆仓库并提取本地路径 template_repo_id = "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28" commit_hash = "be3eaffc28669d7932492681cd5f3e8905e358b4" template_repo_dir = snapshot_download(template_repo_id, revision=commit_hash) - # Create an empty repo on the Hub + # 在 Hub 上创建一个新仓库 model_name = template_repo_id.split("/")[1] create_repo(model_name, exist_ok=True) - # Clone the empty repo + # 克隆空仓库 new_repo_id = get_full_repo_name(model_name) new_repo_dir = model_name repo = Repository(local_dir=new_repo_dir, clone_from=new_repo_id) - # Copy files + # 复制文件 copy_tree(template_repo_dir, new_repo_dir) - # Push to Hub + # 上传到 Hub 上 repo.push_to_hub() ``` -现在, 当你调用 `copy_repository_template()` 时, 它将在你的帐户下创建模板存储库的副本。 +现在当你调用 `copy_repository_template()` 时,它将在你的帐户下创建模板仓库的副本。 -## 从 🤗 Transformers 调试管道 [[从 🤗 Transformers 调试管道]] +## 调试 🤗 Transformers 的 `pipeline` [[调试 🤗 Transformers 的 `pipeline`]] -要开始我们调试 Transformer 模型的奇妙世界之旅, 请考虑以下场景: 你正在与一位同事合作进行问答项目, 以帮助电子商务网站的客户找到有关消费品的答案。你的同事给你发了一条消息, 比如: +接下来要开始我们调试 Transformer 模型的奇妙世界之旅,请考虑以下情景:你正在与一位同事合作开发一个问答的项目,这个项目可以帮助电子商务网站的客户找到有关消费品一些问题的回答。假如你的同事给你发了这样一条消息: -> 嗨! 我刚刚使用了抱抱脸课程的 [第七章](/course/chapter7/7) 中的技术进行了一个实验, 并在 SQuAD 上获得了一些很棒的结果! 我认为我们可以用这个模型作为我们项目的起点。Hub上的模型ID是 "lewtun/distillbert-base-uncased-finetuned-squad-d5716d28"。请随意测试一下 :) +> 嗨!我刚刚使用了 Hugging Face 课程的 [第七章](/course/chapter7/7) 中的技术进行了一个实验,并在 SQuAD 上获得了一些很棒的结果!我觉得我们可以用这个模型作为项目的起点。Hub 上的模型 ID 是 `lewtun/distillbert-base-uncased-finetuned-squad-d5716d28`。你来测试一下 ) -你首先想到的是使用 🤗 Transformers 中的 `管道`: +你首先想到的是使用 🤗 Transformers 中的 `pipeline` : ```python from transformers import pipeline @@ -77,21 +77,21 @@ OSError: Can't load config for 'lewtun/distillbert-base-uncased-finetuned-squad- """ ``` -哦不对, 好像出了什么问题! 如果你是编程新手, 这些类型的错误一开始看起来有点神秘 (甚至是一个 `OSError`?!)。这里显示的错误只是一个更大的错误报告的最后一部分, 称为 _Python traceback_ (又名堆栈跟踪)。例如, 如果你在 Google Colab 上运行此代码, 你应该会看到类似于以下屏幕截图的内容: +啊哦,好像出了什么问题!如果你是编程新手,这类错误一开始看起来有点神秘 ( `OSError` 到底是什么?)。其实这里显示的错误只是全部错误报告中最后一部分,称为 `Python traceback` (又名堆栈跟踪)。例如,如果你在 Google Colab 上运行此代码,你应该会看到类似于以下屏幕截图的内容:
A Python traceback.
-这些报告中包含很多信息, 所以让我们一起来看看关键部分。首先要注意的是, 应该从 _从底部到顶部_ 读取回溯。如果你习惯于从上到下阅读英文文本, 这可能听起来很奇怪,但它反映了这样一个事实,即回溯显示了在下载模型和标记器时 `管道` 进行的函数调用序列。(查看 [第二章](/course/chapter2) 了解有关 `pipeline` 如何在后台工作的更多详细信息。) +这些报告中包含很多信息,让我们一起来看看关键部分。阅读这样的报告时的阅读顺序比较特殊,应该按照从底部到顶部的顺序阅读,如果你习惯于从上到下阅读文本,这可能听起来很奇怪,但它反映了一个事实:traceback 显示了在下载模型和 tokenizer 时 `pipeline` 函数调用的顺序。(查看 [第二章](/course/chapter2) 了解有关 `pipeline` 内部原理的更多详细信息。) -🚨 看到Google Colab 回溯中 "6 帧" 周围的蓝色框了吗? 这是 Colab 的一个特殊功能, 它将回溯压缩为"帧"。如果你似乎无法找到错误的来源, 请确保通过单击这两个小箭头来展开完整的回溯。 +🚨 看到 Google Colab 中 traceback 中间 “6 frames” 的椭圆形蓝色框了吗?这是 Colab 的一个特殊功能,它会自动将 traceback 的中间部分压缩为“frames”。如果你无法找到错误的来源,可以通过单击这两个小箭头来展开完整的 traceback。 -这意味着回溯的最后一行指示最后一条错误消息并给出引发的异常的名称。在这种情况下, 异常类型是`OSError`, 表示与系统相关的错误。如果我们阅读随附的错误消息, 我们可以看到模型的 *config.json* 文件似乎有问题, 我们给出了两个修复它的建议: +这意味着 traceback 的最后一行显示的是最后一条错误消息和引发的异常名称。在这里,异常类型是 `OSError` ,表示这个错误与系统相关。如果我们阅读随之附着的错误消息,我们就可以看到模型的 `config.json` 文件似乎有问题,这里给出了两个修复的建议: ```python out """ @@ -105,23 +105,23 @@ Make sure that: -💡 如果你遇到难以理解的错误消息, 只需将该消息复制并粘贴到 Google 或 [Stack Overflow](https://stackoverflow.com/) 搜索栏中 (是的, 真的!)。你很可能不是第一个遇到错误的人, 这是找到社区中其他人发布的解决方案的好方法。例如, 在 Stack Overflow 上搜索 `OSError: Can't load config for` 给出了几个[hits](https://stackoverflow.com/search?q=OSError%3A+Can%27t+load+config+for+), 可能是用作解决问题的起点。 +💡 如果你遇到难以理解的错误消息,只需将该消息复制并粘贴到 Google 或 [Stack Overflow](https://stackoverflow.com) 搜索栏中。你很有可能不是第一个遇到错误的人,因此很有可能在社区中找到其他人发布的解决方案。例如,在 Stack Overflow 上搜索 `OSError: Can't load config for` 给出了几个 [结果](https://stackoverflow.com/search?q=OSError%3A+Can%27t+load+config+for+) ,可以作为你解决问题的起点。 -第一个建议是要求我们检查模型ID是否真的正确, 所以首先要做的就是复制标识符并将其粘贴到Hub的搜索栏中: +第一个建议是检查模型 ID 是否真的正确,所以首先要做的就是复制标签并将其粘贴到 Hub 的搜索栏中:
The wrong model name.
-嗯, 看起来我们同事的模型确实不在 Hub 上... 啊哈, 但是模型名称中有一个错字! DistilBERT 的名称中只有一个 "l", 所以让我们解决这个问题并寻找 "lewtun/distilbert-base-uncased-finetuned-squad-d5716d28": +嗯,看起来你同事的模型确实不在 Hub 上。但是仔细看模型名称就会发现,里面有一个错别字!正确的 DistilBERT 的名称中只有一个 “l”,所以让我们修正后寻找 `lewtun/distilbert-base-uncased-finetuned-squad-d5716d28`:
The right model name.
-好的, 这很受欢迎。现在让我们尝试使用正确的模型 ID 再次下载模型: +好的,这次有结果了。现在让我们使用正确的模型 ID 再次尝试下载模型: ```python model_checkpoint = get_full_repo_name("distilbert-base-uncased-finetuned-squad-d5716d28") @@ -138,7 +138,7 @@ OSError: Can't load config for 'lewtun/distilbert-base-uncased-finetuned-squad-d """ ``` -啊, 再次挫败 -- 欢迎来到机器学习工程师的日常生活! 因为我们已经修复了模型 ID, 所以问题一定出在存储库本身。访问 🤗 Hub 上存储库内容的一种快速方法是通过 `huggingface_hub` 库的 `list_repo_files()` 方法: +啊,再次失败。不要气馁,欢迎来到机器学习工程师的日常生活!前面我们已经修正了模型 ID,所以问题一定出在仓库本身。这里提供一种快速访问 🤗 Hub 上仓库内容的方法——通过 `huggingface_hub` 库的 `list_repo_files()` 函数: ```python from huggingface_hub import list_repo_files @@ -150,7 +150,7 @@ list_repo_files(repo_id=model_checkpoint) ['.gitattributes', 'README.md', 'pytorch_model.bin', 'special_tokens_map.json', 'tokenizer_config.json', 'training_args.bin', 'vocab.txt'] ``` -有趣 -- 似乎没有配置文件存储库中的 *config.json* 文件! 难怪我们的 `pipeline` 无法加载模型; 我们的同事一定是在微调后忘记将这个文件推送到 Hub。在这种情况下, 问题似乎很容易解决: 我们可以要求他们添加文件, 或者, 因为我们可以从模型 ID 中看到使用的预训练模型是 [`distilbert-base-uncased`](https://huggingface.co/distilbert-base-uncased), 我们可以下载此模型的配置并将其推送到我们的存储库以查看是否可以解决问题。让我们试试看。使用我们在 [第二章](/course/chapter2) 中学习的技术, 我们使用 `AutoConfig` 类下载模型的配置: + 奇怪的是——仓库中似乎没有配置 `config.json` 文件!难怪我们的 `pipeline` 无法加载模型;你的同事一定是在微调后忘记将这个文件上传到 Hub。在这种情况下问题似乎很容易解决:要求他添加文件,或者我们从模型 ID 中可以看出使用的预训练模型是 [`distilbert-base-uncased`](https://huggingface.co/distilbert-base-uncased) ,因此我们可以直接下载此模型的配置文件并将其上传到你们的仓库后查看这样是否可以解决问题。在这里涉及到 [第二章](/course/chapter2) 中学习的技巧,使用 `AutoConfig` 类下载模型的配置文件: ```python from transformers import AutoConfig @@ -161,17 +161,17 @@ config = AutoConfig.from_pretrained(pretrained_checkpoint) -🚨 我们在这里采用的方法并不是万无一失的, 因为我们的同事可能在微调模型之前已经调整了 `distilbert-base-uncased` 配置。在现实生活中, 我们想首先检查它们, 但出于本节的目的, 我们假设它们使用默认配置。 +🚨 在这里采用的方法并不是百分之百可靠的,因为你的同事可能在微调模型之前已经调整了 `distilbert-base-uncased` 配置。在现实情况中,我们应该先与他们核实,但在本节中,我们先假设他们使用的是默认配置。 -然后我们可以使用配置的 `push_to_hub()` 方法将其推送到我们的模型存储库: +上一步成功后,可以使用 `config` 的 `push_to_hub()` 方法将其上传到模型仓库: ```python config.push_to_hub(model_checkpoint, commit_message="Add config.json") ``` -现在我们可以通过从最新提交的 `main` 分支中加载模型来测试这是否有效: +现在可以通过从最新提交的 `main` 分支中加载模型来测试是否有效: ```python reader = pipeline("question-answering", model=model_checkpoint, revision="main") @@ -198,33 +198,31 @@ reader(question=question, context=context) 'answer': 'the task of extracting an answer from a text given a question'} ``` -哇哦, 成功了!让我们回顾一下你刚刚学到的东西: +成功了!让我们回顾一下你刚刚学到的东西: -- Python 中的错误消息称为 _tracebacks_ , 并从下到上阅读。错误消息的最后一行通常包含定位问题根源所需的信息。 -- 如果最后一行没有包含足够的信息, 请按照您的方式进行回溯, 看看您是否可以确定源代码中发生错误的位置。 -- 如果没有任何错误消息可以帮助您调试问题, 请尝试在线搜索类似问题的解决方案。 -- `huggingface_hub` -// 🤗 Hub? -库提供了一套工具, 你可以使用这些工具与 Hub 上的存储库进行交互和调试。 +- Python 中的错误消息称为 `tracebacks` ,注意需要从下到上阅读。错误消息的最后一行通常包含定位问题根源所需的信息。 +- 如果最后一行没有包含足够的信息,需要进行 `tracebacks` (向上逐级查看),看看是否可以确定源代码中发生错误的位置。 +- 如果没有任何错误消息可以帮助你调试问题,请尝试在线搜索类似问题的解决方案。 +- `huggingface_hub` 库提供了一套工具,你可以使用这些工具与 Hub 上的仓库进行交互和调试。 -现在你知道如何调试管道, 让我们看一下模型本身前向传递中的一个更棘手的示例。 +现在你知道如何调试 `pipeline` ,让我们来看一个更棘手的例子,即调试模型本身的前向传播的过程。 -## 调试模型的前向传递 [[调试模型的前向传递]] +## 调试模型的前向传播 [[调试模型的前向传播]] -尽管 `pipeline` 对于大多数需要快速生成预测的应用程序来说非常有用, 有时您需要访问模型的 logits (例如, 如果您有一些想要应用的自定义后处理)。为了看看在这种情况下会出现什么问题, 让我们首先从 `pipeline` 中获取模型和标记器: +尽管 `pipeline` 对于大多数需要快速生成预测结果的来说非常应用场景有用,但是有时你需要访问模型的 logits (比如你想要实现一些的自定义后续处理)。为了看看在这种情况下可能会出现什么问题,让我们首先从 `pipeline` 中获取模型和 `tokenizers` ```python tokenizer = reader.tokenizer model = reader.model ``` -接下来我们需要一个问题, 那么让我们看看是否支持我们最喜欢的框架: +接下来我们需要提出一个问题,看看在这个上下文中的模型是否受支持我们最喜欢的框架: ```python question = "Which frameworks can I use?" ``` -正如我们在 [第七章](/course/chapter7) 中学习的, 我们需要采取的通常步骤是对输入进行标记化, 提取开始和结束标记的对数, 然后解码答案范围: +正如我们在 [第七章](/course/chapter7) 中学习的,我们接下来需要对输入进行 tokenize,提取起始和结束 token 的 logits,然后解码答案所在的文本范围: ```python import torch @@ -234,9 +232,9 @@ input_ids = inputs["input_ids"][0] outputs = model(**inputs) answer_start_scores = outputs.start_logits answer_end_scores = outputs.end_logits -# Get the most likely beginning of answer with the argmax of the score +# 计算分数的 argmax 获取最有可能的答案开头 answer_start = torch.argmax(answer_start_scores) -# Get the most likely end of answer with the argmax of the score +# 计算分数的 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]) @@ -288,15 +286,15 @@ AttributeError: 'list' object has no attribute 'size' """ ``` -噢, 看起来我们的代码中有一个错误!但我们不怕一点调试。您可以在笔记本中使用 Python 调试器: +看起来我们的代码中有一个错误!不用紧张,你可以在 Notebook 中使用 Python 调试器: -或在终端中: +或者跟随我们一起从终端中逐步试验找到错误: -在这里, 阅读错误消息告诉我们 `'list' object has no attribute 'size'`, 我们可以看到一个 `-->` 箭头指向 `model(**inputs)` 中出现问题的行。你可以使用 Python 调试器以交互方式调试它, 但现在我们只需打印出一部分 `inputs`, 看看我们有什么: +在这里,错误消息告诉我们 `'list' object has no attribute 'size'` ,我们可以看到一个 `-->` 箭头指向 `model(**inputs)` 中引发问题的行。你可以使用 Python 调试器用交互方式来调试它,但在这里我们只需打印出一部分 `inputs` ,检查一下它的值: ```python inputs["input_ids"][:5] @@ -306,7 +304,7 @@ inputs["input_ids"][:5] [101, 2029, 7705, 2015, 2064] ``` -这当然看起来像一个普通的 Python `list`, 但让我们仔细检查一下类型: +这当然看起来像一个普通的 Python `list` ,但让我们仔细检查一下类型: ```python type(inputs["input_ids"]) @@ -316,7 +314,7 @@ type(inputs["input_ids"]) list ``` -是的, 这肯定是一个 Python `list`。那么出了什么问题呢? 回忆 [第二章](/course/chapter2) 🤗 Transformers 中的 `AutoModelForXxx` 类在 _tensors_ 上运行(PyTorch或者or TensorFlow), 一个常见的操作是使用 `Tensor.size()` 方法提取张量的维度, 例如, 在 PyTorch 中。让我们再看看回溯, 看看哪一行触发了异常: +是的,的确是一个 Python `list` 。那么出了什么问题呢?回想一下我们在 [第二章](/course/chapter2) 🤗 Transformers 中的 `AutoModelForXxx` 类中的 `tensors` (PyTorch 或者 TensorFlow)进行的操作,例如在 PyTorch 中,一个常见的操作是使用 `Tensor.size()` 方法提取张量的维度。让我们再回到 traceback 中,看看哪一行触发了异常: ``` ~/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) @@ -329,13 +327,13 @@ list AttributeError: 'list' object has no attribute 'size' ``` -看起来我们的代码试图调用 `input_ids.size()`, 但这显然不适用于 Python `list`, 这只是一个容器。我们如何解决这个问题? 在 Stack Overflow 上搜索错误消息给出了很多相关的 [hits](https://stackoverflow.com/search?q=AttributeError%3A+%27list%27+object+has+no+attribute+%27size%27&s=c15ec54c-63cb-481d-a749-408920073e8f)。单击第一个会显示与我们类似的问题, 答案如下面的屏幕截图所示: +看起来我们的代码试图调用 `input_ids.size()` ,但 `.size()` 方法显然不适用于 Python 的 `list` , `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'` 到标记器, 所以让我们看看这是否适合我们: +这里建议我们添加 `return_tensors='pt'` 到 Tokenizer,让我们测试一下是否可以帮助我们解决问题: ```python out inputs = tokenizer(question, context, add_special_tokens=True, return_tensors="pt") @@ -343,9 +341,9 @@ input_ids = inputs["input_ids"][0] outputs = model(**inputs) answer_start_scores = outputs.start_logits answer_end_scores = outputs.end_logits -# Get the most likely beginning of answer with the argmax of the score +# 计算分数的 argmax 获取最有可能的答案开头 answer_start = torch.argmax(answer_start_scores) -# Get the most likely end of answer with the argmax of the score +# 计算分数的 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]) @@ -361,4 +359,4 @@ Answer: pytorch, tensorflow, and jax """ ``` -不错, 成功了! 这是 Stack Overflow 非常有用的一个很好的例子: 通过识别类似的问题, 我们能够从社区中其他人的经验中受益。然而, 像这样的搜索并不总是会产生相关的答案, 那么在这种情况下你能做什么呢? 幸运的是, 有一个受欢迎的开发者社区 [Hugging Face forums](https://discuss.huggingface.co/) 可以帮助你! 在下一节中, 我们将看看如何设计可能得到回答的优秀论坛问题。 \ No newline at end of file +成功了!这是一个很好的例子,展示了 Stack Overflow 社区的实用性,搜索类似的问题,我们能够从社区中其他人的经验中受益。然而,像我们这样的搜索不是总能找到相关的答案,那么在这种情况下你该怎么办呢?幸运的是,在 [Hugging Face 论坛](https://discuss.huggingface.co/) 上有一个友好的开发者社区可以帮助你!在下一节中,我们将看看在该平台中有效地得到问题的回答。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter8/3.mdx b/chapters/zh-CN/chapter8/3.mdx index 988b342cf..b84f47347 100644 --- a/chapters/zh-CN/chapter8/3.mdx +++ b/chapters/zh-CN/chapter8/3.mdx @@ -15,17 +15,17 @@ The Hugging Face forums.
-在左侧,您可以看到各种主题分组的所有类别,而右侧显示了最新的主题。主题是包含标题、类别和描述的帖子;它与我们在创建自己的数据集时看到的 GitHub 问题格式非常相似[Chapter 5](/course/chapter5).顾名思义,[Beginners](https://discuss.huggingface.co/c/beginners/5)类别主要面向刚开始使用 Hugging Face 库和生态系统的人。欢迎对任何库提出任何问题,无论是调试一些代码还是寻求有关如何做某事的帮助。 (也就是说,如果您的问题特别涉及某个图书馆,您可能应该前往论坛上的相应图书馆类别。) +在左侧,你可以看到各种主题分组的所有类别,而右侧显示了最新的主题。一个主题包含标题、类别和描述;它与我们在 [第五章](/course/chapter5) 中创建自己的数据集时看到的 GitHub issue 格式非常相似 顾名思义, [Beginners(初学者)](https://discuss.huggingface.co/c/beginners/5) 类别主要面向刚开始使用 Hugging Face 库和生态系统的人。在这里,欢迎你对任何库提出任何问题,无论是调试一些代码还是寻求有关如何做某事的帮助。(话虽如此,如果你的问题特属于某个库,你更应该前往论坛上对应库所在的类别。) -同样,the [Intermediate](https://discuss.huggingface.co/c/intermediate/6)和[Research](https://discuss.huggingface.co/c/research/7)类别用于更高级的问题,例如关于图书馆或您想讨论的一些很酷的新 NLP 研究。 +同样, [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 课程相关的任何问题! +当然,我们也应该提到 [Course](https://discuss.huggingface.co/c/course/20) 类别,你可以在里面提出与 Hugging Face Course 相关的任何问题! -选择类别后,您就可以编写第一个主题了。 你可以找一些[guidelines](https://discuss.huggingface.co/t/how-to-request-support/3128) 在有关如何执行此操作的论坛中,在本节中,我们将看看构成一个好的主题的一些功能。 +选择类别后,就可以编写第一个主题了。你可以阅读一下 [指南](https://discuss.huggingface.co/t/how-to-request-support/3128) ,里面会教你如何撰写主题。在本节中,我们将一起学习如何撰写一个好的主题。 -## 写一篇好的论坛帖子 [[写一篇好的论坛帖子]] +## 写一篇高质量的论坛帖子 [[写一篇高质量的论坛帖子]] -作为一个运行示例,假设我们试图从 Wikipedia 文章生成嵌入表示以创建自定义搜索引擎。像往常一样,我们按如下方式加载分词器和模型: +举个例子,假如我们要将 Wikipedia 文章转化为向量嵌入用来创建自定义搜索引擎。通常情况下,我们会用如下的方法加载 Tokenizer 和模型: ```python from transformers import AutoTokenizer, AutoModel @@ -35,7 +35,7 @@ tokenizer = AutoTokenizer.from_pretrained(model_checkpoint) model = AutoModel.from_pretrained(model_checkpoint) ``` -现在我们尝试将[变形金刚的维基百科](https://en.wikipedia.org/wiki/Transformers)的一整段进行嵌入表示(热知识:变形金刚的英文就是 Transformers ,而现在 Transformers 作为一个 🤗 Python 库也被越来越多人熟知): +现在假设我们尝试将维基百科文章中关于 [变形金刚](https://en.wikipedia.org/wiki/Transformers) (指的是变形金刚系列作品,而不是 Transformers 库!热知识:Transformers 作为一个 🤗 Python 库被越来越多人熟知,它和变形金刚的英文名字一模一样)的整个部分转化为嵌入的向量表示: ```python text = """ @@ -88,79 +88,79 @@ logits = model(**inputs).logits IndexError: index out of range in self ``` -呃,我们遇到了一个问题——错误信息比我们看到的要神秘得多[section 2](/course/chapter8/section2)!我们无法确定完整回溯的正面或反面,因此我们决定转向 Hugging Face 论坛寻求帮助。我们如何设计主题? +糟糕,我们遇到了一个问题——而且错误消息比我们在 [第2节](/course/chapter8/section2) 中看到的要难懂得多!我们无法理解完整的 traceback 信息,因此我们决定向 Hugging Face 论坛寻求帮助。我们该如何撰写主题呢? -首先,我们需要点击右上角的“新建主题”按钮(注意,要创建主题,我们需要登录): +首先,我们需要点击右上角的“New Topic”按钮(请注意,要创建主题,需要先登录账号):
Creating a new forum topic.
-这会出现一个写作界面,我们可以在其中输入我们的主题标题,选择一个类别,并起草内容: +这里会出现一个写作界面,我们可以在其中输入 Topic 的标题,选择一个类别,并起草内容:
The interface for creating a forum topic.
-由于错误似乎仅与 🤗 Transformers有关,因此我们将为该类别选择此错误。我们第一次尝试解释这个问题可能看起来像这样: +由于错误似乎仅与 🤗 Transformers 有关,因此我们将错误类别选择为该类别。第一次尝试解释这个问题可能看起来像这样:
Drafting the content for a new forum topic.
-尽管本主题包含我们需要帮助的错误消息,但其编写方式存在一些问题: +尽这个主题包含这个错误消息,但撰写方式存在一些问题: -1. 标题描述性不是很强,因此浏览论坛的任何人都无法在不阅读正文的情况下分辨出主题的内容。 +1. 标题描述性不是很强,这会导致浏览论坛的人在没有阅读正文的情况下无法知道主题是关于什么的。 -2. 正文没有提供足够的信息,说明错误来自何处以及如何重现错误。 +2. 正文没有提供足够的信息说明错误的来源以及如何重现错误。 -3. 这个话题直接用一种有点苛刻的语气标记了几个人。 +3. 正文中以某种要求的语气直接 @ 了几个人。 -像这样的主题不太可能很快得到答案(如果他们得到了答案),那么让我们看看如何改进它。我们将从选择一个好标题的第一个问题开始。 +像这样的主题不太可能很快得到答案(如果有答复的话),需要对其进行改进。我们将从解决第一个问题,选择一个好的标题开始。 ### 选择描述性标题 [[选择描述性标题]] -如果您想就代码中的错误寻求帮助,一个好的经验法则是在标题中包含足够的信息,以便其他人可以快速确定他们是否认为他们可以回答您的问题。在我们的运行示例中,我们知道正在引发的异常的名称,并有一些提示它是在模型的前向传递中触发的,我们调用 **model(**inputs)** .为了传达这一点,一个可能的标题可能是: +如果你想就代码中的错误寻求帮助,一个好的经验法则是在标题中包含足够的信息,以便其他人可以快速确定他们是否可以回答你的问题。在我们的运行示例中,我们知道正在引发的异常名称,并有一些错误提示表示它是在模型的前向传递中触发的,即我们调用 `model(**inputs)` 的位置。为了传达这一信息,一个可能的标题可能是: -> 自动建模正向传递中的索引错误的来源? +> AutoModel 前向传递中的 IndexError 来源? -这个标题告诉读者在哪里你认为错误来自,如果他们遇到了 **IndexError** 在此之前,他们很有可能知道如何调试它。当然,标题可以是您想要的任何内容,也可以是其他变体,例如: +这个标题告诉读者你认为的错误可能的来源,如果他们遇到了 `IndexError` 他们很可能知道如何回答你。当然,标题可以是其他的任何形式,其他可选的变体如下: -> 为什么我的模型会产生索引错误? +> 为什么我的模型会产生 IndexError? -也可以。现在我们有了一个描述性的标题,让我们来看看改善主体。 +这种标题也是可以的。现在我们有了一个描述性的标题,让我们来看看如何改善正文。 ### 设置代码段的格式 [[设置代码段的格式]] -如:也可以。现在我们有了一个描述性的标题,让我们来看看改善身体。在 IDE 中阅读源代码已经够难的了,但是当将代码复制粘贴为纯文本时就更难了!幸运的是,Hugging Face 论坛支持使用 Markdown,因此您应该始终用三个反引号 (```) 将代码块括起来,以便更容易阅读。让我们这样做来美化错误消息——在我们这样做的时候,让我们让正文比我们的原始版本更有礼貌: +现在我们有了一个描述性的标题,让我们来看看如何改善代码段的格式。在 IDE 中阅读源代码已经够难的了,但是当将代码复制粘贴为纯文本时就更难了!不过 Hugging Face 论坛支持使用 Markdown 将原始代码高亮,标准格式是用三个反引号 (```) 将代码块包裹起来。这样可以让正文比上面的原始版本看起来更加美观:
Our revised forum topic, with proper code formatting.
-正如您在屏幕截图中看到的,将代码块括在反引号中会将原始文本转换为格式化代码,并带有颜色样式!另请注意,单个反引号可用于格式化内联变量,就像我们所做的那样 **distilbert-base-uncased** .这个主题看起来好多了,如果幸运的话,我们可能会在社区中找到可以猜测错误是什么的人。然而,与其依靠运气,不如让我们在其完整的血腥细节中包含回溯,让生活更轻松! +正如你在屏幕截图中看到的,将代码块包裹在反引号中会将原始文本转换为带有颜色样式的格式化代码!另外,单个反引号可用于格式化内联变量,比如 `distilbert-base-uncased` 这样。这个主题看起来好多了,再加上一点运气,我们可能会在社区中遇到一些可以猜出错误的原因的人。然而,与其依靠运气,不如撰写完整而详细的回溯信息使定位问题更容易! -### 包括完整的回溯 [[包括完整的回溯]] +### 包括完整的回溯信息 [[包括完整的回溯信息]] -由于回溯的最后一行通常足以调试您自己的代码,因此很容易在您的主题中提供它以“节省空间”。虽然本意是好的,但这实际上使它更难供其他人调试问题,因为回溯中较高的信息也非常有用。因此,一个好的做法是复制并粘贴所有的回溯,同时确保它的格式很好。由于这些回溯可能会很长,有些人更喜欢在解释了源代码之后再展示它们。我们开工吧。现在,我们的论坛主题如下所示: +通常情况下 `traceback ` 的最后一行足以调试自己的代码,但是只提供这一行虽然“节省空间”单可能会使他人定位问题变得更加困难,因为 `traceback` 中更上面的信息也可能非常有用。因此,一个好的做法是复制并粘贴整个的 `traceback` ,同时确保它的格式不被破坏。但是这些 `traceback` 可能会很长,所以可以在对源代码进行解释之后再展示它们。按照这个思路现在来对我们的问题帖子进行修改,修改之后的帖子如下所示:
-Our example forum topic, with the complete traceback. +Our example forum topic, with the complete traceback.
-这提供了更多信息,细心的读者可能会指出问题似乎是由于回溯中的这一行而传递了一个长输入: +这提供了更多信息,细心的读者可能会发现:问题似乎是由于 `traceback` 中的这行代码导致输入过长: -> 令牌索引序列长度长于为此模型指定的最大序列长度 (583 > 512)。 +> Token 索引序列的长度大于此模型指定的最大序列长度 (583 > 512)。 -但是,通过提供触发错误的实际代码,我们可以让他们更轻松。我们现在就这样做。 +然而,我们可以通过提供触发错误的原始代码进一步改善问题描述。现在,我们就来做这件事。 -### 提供可重复的示例 [[提供可重复的示例]] +### 提供可复现的示例 [[提供可复现的示例]] -如果您曾经尝试过调试其他人的代码,那么您可能首先尝试重现他们报告的问题,以便您可以开始通过回溯来查明错误。在论坛上获得(或提供)帮助时没有什么不同,所以如果你能提供一个重现错误的小例子真的很有帮助。有一半的时间,简单地完成这个练习将帮助你找出问题所在。在任何情况下,我们的示例缺少的部分是显示输入我们提供给模型的。这样做会为我们提供类似于以下完整示例的内容: +如果你曾经尝试过调试其他人的代码,那么你可能首先尝试的复现他们报告的问题,这样你就可以逐步查找错误。在论坛上获取(或提供)帮助也不例外,如果你能提供一个复现错误的小例子真的很有帮助。这里有个示例帖子:
The final version of our forum topic.
-该主题现在包含相当多的信息,并且它的编写方式更可能吸引社区的注意力并获得有用的答案。有了这些基本指南,您现在可以创建很棒的主题来找到您的 🤗 Transformers问题的答案! +该帖子目前包含相当多的信息,并且它的撰写格式更可能吸引社区其他用户的注意,从而跟可能获得有用的答案。有了这些基本指南,你现在可以创建很棒的帖子来找到所遇到的 🤗 Transformers 问题的答案! diff --git a/chapters/zh-CN/chapter8/4.mdx b/chapters/zh-CN/chapter8/4.mdx index 46358c847..d7ce5d279 100644 --- a/chapters/zh-CN/chapter8/4.mdx +++ b/chapters/zh-CN/chapter8/4.mdx @@ -9,17 +9,17 @@ {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter8/section4.ipynb"}, ]} /> -你已经编写了一个漂亮的脚本来训练或微调给定任务的模型,尽职尽责地遵循 [Chapter 7](/course/chapter7) 中的建议。 但是当你启动命令 `trainer.train()` 时,可怕的事情发生了:你得到一个错误😱! 或者更糟糕的是,一切似乎都很好,训练运行没有错误,但生成的模型很糟糕。 在本节中,我们将向您展示如何调试此类问题。 +假如你已经尽可能地遵循 [第七章](/course/chapter7) 中的建议,编写了一段漂亮的代码来训练或微调给定任务的模型。但是当你启动命令 `trainer.train()` 时,可怕的事情发生了:你得到一个错误😱!或者更糟糕的是,虽然看起来一切似乎都正常,训练运行没有错误,但生成的模型很糟糕。在本节中,我们将向你展示如何调试此类问题。 ## 调试训练管道 [[调试训练管道]] -当您在 `trainer.train()` 中遇到错误时,它可能来自多个来源,因为 `Trainer` 通常会将很多东西放在一起组合运行。 它将datasets转换为dataloaders,因此问题可能出在datasets中,或者在尝试将datasets的元素一起批处理时出现问题。 然后它需要准备一批数据并将其提供给模型,因此问题可能出在模型代码中。 之后,它会计算梯度并执行优化器,因此问题也可能出在您的优化器中。 即使训练一切顺利,如果您的评估指标有问题,评估期间仍然可能出现问题。 +当你在 `trainer.train()` 中遇到错误时,它有可能来自多个不同的来源,因为 `Trainer` 会将很多模块放在一起组合运行。首先它会将 datasets 转换为 dataloaders 因此问题可能出在 datasets 中,或者在尝试将 datasets 的元素一起批处理时出现问题。接着它会准备一批数据并将其提供给模型,因此问题可能出在模型代码中。之后,它会计算梯度并执行优化器,因此问题也可能出在你的优化器中。即使训练一切顺利,如果你的评估指标有问题,评估期间仍然可能出现问题。 -调试 `trainer.train()` 中出现的错误的最佳方法是手动检查整个管道,看看哪里出了问题。 错误通常很容易解决。 +调试 `trainer.train()` 中出现的错误的最佳方法是手动检查整个管道,看看哪里出了问题。通常情况下,错误很容易解决。 -为了证明这一点,我们将使用以下脚本(尝试)在 [MNLI 数据集](https://huggingface.co/datasets/glue)上微调 DistilBERT 模型: +为了讲解这个过程,我们将尝试使用以下脚本在 [MNLI 数据集](https://huggingface.co/datasets/glue) 上微调 DistilBERT 模型: ```py from datasets import load_dataset @@ -70,7 +70,7 @@ trainer = Trainer( trainer.train() ``` -如果你尝试执行它,你会遇到一个相当神秘的错误: +如果你尝试运行它,你会遇到一个相当晦涩的错误: ```python out 'ValueError: You have to specify either input_ids or inputs_embeds' @@ -78,9 +78,9 @@ trainer.train() ### 检查数据 [[检查数据]] -这是不言而喻的,如果你的数据被破坏,“Trainer”将无法形成批次,更不用说训练你的模型了。 所以首先,你需要看看你的训练集中有什么。 +显而易见,如果你的数据损坏了, `Trainer` 将无法将数据整理成 batch,更不用说训练你的模型了。因此,首先需要检查一下你的训练集里面的内容。 -为了避免花费无数小时试图检查和修复不是错误来源的东西,我们建议您使用 `trainer.train_dataset` 进行检查。 所以让我们在这里这样做: +为了避免花费无数小时试图修复不是错误来源的问题,避免多次封装的干扰,我们建议你只使用 `trainer.train_dataset` 进行检查。所以让我们在这里这样尝试一下: ```py trainer.train_dataset[0] @@ -93,9 +93,9 @@ trainer.train_dataset[0] 'premise': 'Conceptually cream skimming has two basic dimensions - product and geography.'} ``` -你注意到有什么不对吗? 与缺少有关 `input_ids` 的错误消息相结合,应该让您意识到数据集里是文本,而不是模型可以理解的数字。 在这个例子,输出的原始错误信息非常具有误导性,因为 `Trainer` 会自动删除与模型签名不匹配的列(即模型预期的参数)。 这意味着在这里,除了标签之外的所有东西都被丢弃了。 因此,创建批次然后将它们发送到模型没有问题,而模型又抱怨它没有收到正确的输入。 +你注意到有什么不对吗?与缺少 `input_ids` 的错误消息相结合,你应该可以意识到数据集里是文本,而不是模型可以理解的数字。在这个例子中,输出的原始错误信息非常具有误导性,因为 `Trainer` 会自动删除与模型配置中不需要的列 (即模型预期的输入参数)。这意味着在这里,除了标签之外的所有东西都被丢弃了。因此,创建 batch 然后将它们发送到模型时没有问题,但是模型会提示没有收到正确的输入。 -为什么没有处理数据生成标记呢? 我们确实在数据集上使用了“Dataset.map()”方法来对每个样本应用标记器。 但是如果你仔细看代码,你会发现我们在将训练和评估集传递给`Trainer`时犯了一个错误。 我们在这里没有使用 `tokenized_datasets`,而是使用了 `raw_datasets` 🤦。 所以让我们解决这个问题! +为什么模型没有得到 input_ids 这一列呢?我们确实在数据集上调用了 `Dataset.map()` 方法,并使用 `tokenizer` 处理了每个样本。但是如果你仔细看代码,你会发现我们在将训练和评估集传递给 `Trainer` 时犯了一个错误。我们在这里没有使用 `tokenized_datasets` ,而是使用了 `raw_datasets` 🤦。所以让我们来解决这个问题! ```py from datasets import load_dataset @@ -146,13 +146,13 @@ trainer = Trainer( trainer.train() ``` -这个新代码现在会给出一个不同的错误: +运行这个新代码会输出一个新的错误😥: ```python out 'ValueError: expected sequence of length 43 at dim 1 (got 37)' ``` -查看traceback,我们可以看到错误发生在数据整理步骤中: +查看 traceback,我们可以看到错误发生在数据整理步骤中: ```python out ~/git/transformers/src/transformers/data/data_collator.py in torch_default_data_collator(features) @@ -163,9 +163,9 @@ trainer.train() 109 return batch ``` -所以,我们应该去研究一下那个。 然而,在我们这样做之前,让我们完成检查我们的数据, 先确定它100%是正确的。 +所以,我们应该去研究一下那个。然而,在检查数据整理步骤之前,请先完成所有的数据检查,确定数据是100% 正确的。 -在调试课程的内容时,您应该始终做的一件事是查看模型的解码输入。 我们无法理解直接提供给它的数字,所以我们应该看看这些数字代表什么。 例如,在计算机视觉中,这意味着查看您传递的图片像素的解码,在语音中意味着解码后的音频样本,对于我们的 NLP 示例,这意味着使用我们的标记器解码的输入: +在调试训练数据时,你应该始终要记得检查解码后的原始文本输入,而不是一大串编码后的数字。因为我们无法理解直接提供给它的数字,所以我们应该看看这些数字代表什么。例如,在计算机视觉中,这意味着查看你传递解码后的像素图片,在语音中意味着解码后的音频样本,对于我们的 NLP 示例,这意味着应该检查 `tokenizer` 解码后的原始输入文本: ```py tokenizer.decode(trainer.train_dataset[0]["input_ids"]) @@ -175,7 +175,7 @@ tokenizer.decode(trainer.train_dataset[0]["input_ids"]) '[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() @@ -185,7 +185,7 @@ trainer.train_dataset[0].keys() dict_keys(['attention_mask', 'hypothesis', 'idx', 'input_ids', 'label', 'premise']) ``` -请注意,与模型接受的输入不对应的键将被自动丢弃,因此这里我们将仅保留 `input_ids`、`attention_mask` 和 `label`(将重命名为 `labels`)。 要仔细检查模型输入的列,您可以打印模型的类,然后查看其文档: +请注意,模型不能接收的输入对应的键将被自动丢弃,因此这里我们将仅需要检查 `input_ids` 、 `attention_mask` 和 `label` (它将被重命名为 `labels` )。为了防止记错,可以先打印模型的类,然后在其文档中看看它需要输入哪些列。 ```py type(trainer.model) @@ -195,9 +195,9 @@ type(trainer.model) transformers.models.distilbert.modeling_distilbert.DistilBertForSequenceClassification ``` -所以在我们的例子中,我们在[在这个页面](https://huggingface.co/transformers/model_doc/distilbert.html#distilbertforsequenceclassification)可以检查上接受的参数。 `Trainer` 也会记录它丢弃的列。 +所以在我们的这个例子中,我们可以在 [模型文档](https://huggingface.co/transformers/model_doc/distilbert.html#distilbertforsequenceclassification) 中查看这个模型接受的参数。同样的 `Trainer` 也会记录它丢弃的列。 -我们通过解码检查了输入 ID 是否正确。 接下来是检查 `attention_mask`: +我们通过解码检查了 `inputs ID` 是否正确。接下来应该检查 `attention_mask` : ```py trainer.train_dataset[0]["attention_mask"] @@ -207,7 +207,7 @@ trainer.train_dataset[0]["attention_mask"] [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] ``` -由于我们没有在预处理中应用填充,这看起来非常自然。 为确保该注意掩码没有问题,让我们检查它与输入 ID 的长度是否相同: +由于我们没有在预处理中使用填充,这看起来没什么问题。为确保该注意掩码没有问题,让我们检查它与 `inputs ID` 的长度是否相同: ```py len(trainer.train_dataset[0]["attention_mask"]) == len( @@ -219,7 +219,7 @@ len(trainer.train_dataset[0]["attention_mask"]) == len( True ``` -那挺好的! 最后,让我们检查一下我们的标签: +那挺好的!最后,让我们检查一下我们的标签: ```py trainer.train_dataset[0]["label"] @@ -229,7 +229,7 @@ trainer.train_dataset[0]["label"] 1 ``` -与输入 ID 一样,这是一个本身并没有真正意义的数字。 正如我们之前看到的,整数和标签名称之间的映射存储在数据集相应 *feature* 的 `names` 属性中: +与inputs ID 一样,这是一个本身并没有真正意义的数字,我们需要检查的是它所对应的真实的标签名称是否是正确的。正如我们之前学到的,标签 ID 和标签名之间的映射存储在数据集 `features` 里的 `names` 属性中: ```py trainer.train_dataset.features["label"].names @@ -239,30 +239,30 @@ trainer.train_dataset.features["label"].names ['entailment', 'neutral', 'contradiction'] ``` -所以`1`表示`neutral`,表示我们上面看到的两句话并不矛盾,也没有包含关系。 这似乎是正确的! +所以 `1` 表示 `neutral` ,表示我们上面看到的两句话并不矛盾,也没有包含关系。这也看起来是正确的! -我们这里没有令牌类型 ID,因为 DistilBERT 不需要它们; 如果您的模型中有一些,您还应该确保它们正确匹配输入中第一句和第二句的位置。 +我们这里没有 token 类型 ID,因为 DistilBERT 不需要它们;如果你的模型中有,你还应该确保 token 类型 ID 可以正确匹配输入中第一句和第二句的位置。 -✏️ **轮到你了!** 检查训练数据集的第二个元素是否正确。 +✏️ **轮到你了!** 检查训练数据集的第二个条数据是否正确。 -我们在这里只对训练集进行检查,但您当然应该以同样的方式仔细检查验证集和测试集。 +我们在这里只对训练集进行检查,但你当然应该以同样的方式仔细检查验证集和测试集。 -现在我们知道我们的数据集看起来不错,是时候检查训练管道的下一步了。 +现在我们知道我们的数据集看起来不错,下一步是时候检查训练管道了。 ### 从 datasets 到 dataloaders [[从 datasets 到 dataloaders]] -训练管道中可能出错的下一件事是当“Trainer”尝试从训练或验证集形成批次时。 一旦你确定 `Trainer` 的数据集是正确的,你可以尝试通过执行以下操作手动形成一个批次(可以将 `train` 替换为 `eval` 用于验证数据加载器): +训练管道中可能出错的下一个操作发生在 `Trainer` 尝试从训练或验证集中生成 batch 时。当你确定 `Trainer` 的数据集是正确的后,你可以尝试通过执行以下代码手动形成一个 batch(当要测试验证集的 dataloaders 时,可以将 `train` 替换为 `eval` ): ```py for batch in trainer.get_train_dataloader(): break ``` -此代码创建训练数据加载器,然后对其进行迭代,在第一次迭代时停止。 如果代码执行没有错误,那么您就有了可以检查的第一个训练批次,如果代码出错,您可以确定问题出在数据加载器中,如下所示: +上述代码会创建训练集的数据加载器,然后对其进行迭代一次。如果代码执行没有错误,那么你就有了可以检查的第一个 batch,如果代码出错,你可以确定问题出在数据加载器中,如下所示: ```python out ~/git/transformers/src/transformers/data/data_collator.py in torch_default_data_collator(features) @@ -275,8 +275,7 @@ for batch in trainer.get_train_dataloader(): ValueError: expected sequence of length 45 at dim 1 (got 76) ``` -检查trackback的最后一个堆栈的输出应该足以给你一个线索,但让我们做更多的挖掘。 批处理创建过程中的大多数问题是由于将示例整理到单个批处理中而出现的,因此在有疑问时首先要检查的是您的 DataLoader 正在使用什么 collate_fn: - +Trackback 的最后一个堆栈的输出应该足够给你一些线索,但让我们再深入挖掘一下创建 batch 的过程。创建 batch 过程中的大多数问题是在将数据整理到单个 batch 中时出现的, 因此在出现问题时首先要检查的是 DataLoader 正在使用的 `collate_fn`: ```py data_collator = trainer.get_train_dataloader().collate_fn data_collator @@ -286,9 +285,9 @@ data_collator Dict[str, Any]> ``` -所以,目前使用的是 `default_data_collator`,但这不是我们在这种情况下想要的。 我们希望将示例填充到批处理中最长的句子,这是由 `DataCollatorWithPadding` 整理器完成的。 而这个数据收集器应该是默认被 `Trainer` 使用的,为什么这里没有使用呢? +所以,目前使用的是 `default_data_collator` ,但这不是我们在这个示例中想要使用的 `collate_fn` 。我们希望将 `batch` 中的每个句子填充到 `batch` 中最长的句子,这项功能是由 `DataCollatorWithPadding` 整理器实现的。而 `Trainer` 默认使用的数据整理器就是它,为什么这里没有使用呢? -答案是因为我们没有将 `tokenizer` 传递给 `Trainer`,所以它无法创建我们想要的 `DataCollatorWithPadding`。 在实践中,您应该明确地传递您想要使用的数据整理器,以确保避免这些类型的错误。 让我们调整我们的代码来做到这一点: +答案是因为我们在上面的代码中并没有把 `tokenizer` 传递给 `Trainer` ,所以它无法创建我们想要的 `DataCollatorWithPadding` 。在实践中,你应该明确地传递你想要使用的数据整理器,以确保避免这些类型的错误。让我们修改代码以实现这一点: ```py from datasets import load_dataset @@ -345,22 +344,22 @@ trainer = Trainer( trainer.train() ``` -好消息? 我们没有得到与以前相同的错误,这绝对是进步。 坏消息? 我们得到了一个臭名昭著的 CUDA 错误: +好消息是,我们没有得到与以前相同的错误,这绝对是进步。坏消息是,我们得到了一个臭名昭著的 CUDA 错误: ```python out RuntimeError: CUDA error: CUBLAS_STATUS_ALLOC_FAILED when calling `cublasCreate(handle)` ``` -这很糟糕,因为 CUDA 错误通常很难调试。 我们稍后会看到如何解决这个问题,但首先让我们完成对批处理创建的分析。 +这很糟糕,因为 CUDA 错误通常很难调试。我们将在稍后再解决这个问题,现在让我们先完成对创建的 `batch` 的分析。 -如果您确定您的数据整理器是正确的,则应尝试将其应用于数据集的几个样本: +如果你确定你的数据整理器是正确的,那么应该尝试用它来处理数据集的几个样本,检查一下在创建样本的时候是否会出现错误: ```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()` 方法来执行此操作: +上述代码运行失败,因为 `train_dataset` 包含字符串列, `Trainer` 通常会删除这些列,在这样的单步调试中,我们可以调用私有的 `trainer._remove_unused_ columns()` 方法手动删除这些列。: ```py data_collator = trainer.get_train_dataloader().collate_fn @@ -368,26 +367,26 @@ actual_train_set = trainer._remove_unused_columns(trainer.train_dataset) batch = data_collator([actual_train_set[i] for i in range(4)]) ``` -如果错误仍然存在,您应该能够手动调试数据整理器内部以确定具体的问题。 +如果又出现了新的错误,则应该手动调试数据整理器以确定具体的问题。 -现在我们已经调试了批处理创建过程,是时候将数据传递给模型了! +现在我们已经调试了创建 `batch` 过程,是时候将数据传递给模型了! ### 检查模型 [[检查模型]] -您应该能够通过执行以下命令来获得一个批次的数据: +你可以通过执行以下命令来获得一个 `batch` 的数据: ```py for batch in trainer.get_train_dataloader(): break ``` -如果您在notebook中运行此代码,您可能会收到与我们之前看到的类似的 CUDA 错误,在这种情况下,您需要重新启动notebook并重新执行最后一个片段,而不运行 `trainer.train()` 行.这是关于 CUDA 错误的第二个最烦人的事情:它们会破坏您的Cuda内核,而且无法恢复。它们最烦人的事情是它们很难调试。 +如果你在 notebook 中运行此代码,你可能会收到与我们之前看到的类似的 CUDA 错误,在这种情况下,你需要重新启动 notebook 并重新执行最后一段代码,但是暂时不要运行 `trainer.train()` 这一行命令。这是关于 CUDA 错误的第二个最烦人的事情:它们会破坏你的 Cuda 内核,而且无法恢复。它们最烦人的事情是它们很难调试。 -这是为什么?它与 GPU 的工作方式有关。它们在并行执行大量操作方面非常有效,但缺点是当其中一条指令导致错误时,您不会立即知道。只有当程序在 GPU 上调用多个进程的同步处理时,它才会意识到出现问题,因此错误实际上是在与创建它的原因无关的地方引发的。例如,如果我们查看之前的trackback,错误是在向后传递期间引发的,但我们会在一分钟内看到它实际上源于向前传递中的某些东西。 +这是为什么?它与 GPU 的工作方式有关。它们在并行执行大量操作方面非常有效,但缺点是当其中一条指令导致错误时,你不会立即知道。只有当程序在 GPU 上调用多个进程的同步处理时,才会输出一些错误信息,但事实上真实触发错误的地方往往与输出错误信息的地方并不一致。例如,如果我们查看之前的 Trackback,错误是在反向传播期间引发的,但我们会在一分钟后看到错误实际上源于前向传播的某些东西。 -那么我们如何调试这些错误呢?答案很简单:我们没有。除非您的 CUDA 错误是内存不足错误(这意味着您的 GPU 中没有足够的内存),否则您应该始终返回 CPU 进行调试。 +那么我们如何调试这些错误呢?答案很简单:不调试。除非你的 CUDA 错误是内存不足错误(这意味着你的 GPU 中没有足够的内存),除此之外你应该始终返回到 CPU 进行调试。 -为此,我们只需将模型放回 CPU 上并在我们的一批数据中调用它——“DataLoader”返回的那批数据尚未移动到 GPU: +为此,我们只需将模型放回 CPU 然后将一个 `batch` 的数据送入模型。现在 `DataLoader` 返回的那批数据还尚未移动到 GPU,因此可以直接送入这个 `batch`: ```python outputs = trainer.model.cpu()(**batch) @@ -404,7 +403,8 @@ outputs = trainer.model.cpu()(**batch) IndexError: Target 2 is out of bounds. ``` -所以,思路越来越清晰了。 我们现在在损失计算中没有出现 CUDA 错误,而是有一个“IndexError”(因此与我们之前所说的反向传播无关)。 更准确地说,我们可以看到是Target 2 造成了错误,所以这是检查模型标签数量的好时机: +现在,情况越来越明朗了。损失计算中没有出现CUDA 错误,而出现了一个 `IndexError` +(因此与反向传播无关)。我们可以看到是 Target 2 造成了错误,这个时候通常应该检查一下模型标签的数量。 ```python trainer.model.config.num_labels @@ -414,7 +414,7 @@ trainer.model.config.num_labels 2 ``` -有两个标签,只允许 0 和 1 作为目标,但是根据错误信息我们得到一个 2。得到一个 2 实际上是正常的:如果我们记得我们之前提取的标签名称,有三个,所以我们有索引 0 , 1 和 2 在我们的数据集中。 问题是我们没有告诉我们的模型,它应该创建三个标签。 所以让我们解决这个问题! +可以看到,模型有两个标签,只有 0 和 1 作为目标,但是根据错误信息我们得到一个 2。得到一个 2 实际上是正常的:如果你还有印象,我们之前提取了三个标签名称,所以在我们的数据集中我们有 0、1 和 2 三个标签索引,而问题是我们并没有告诉模型,它应该创建三个标签。让我们解决这个问题! ```py from datasets import load_dataset @@ -470,7 +470,7 @@ trainer = Trainer( ) ``` -我们还没有包含 `trainer.train()` 行,以便花时间检查一切是否正常。 如果我们请求一个批次的数据并将其传递给我们的模型,它现在可以正常工作了! +为了方便检查一切是否正常,现在先不要运行 `trainer.train()` 命令。先请求一个 batch 的数据并将其传递给我们的模型,如果它现在可以正常工作了! ```py for batch in trainer.get_train_dataloader(): @@ -479,7 +479,7 @@ for batch in trainer.get_train_dataloader(): outputs = trainer.model.cpu()(**batch) ``` -下一步是回到 GPU 并检查一切是否仍然有效: +那么下一步就可以回到 GPU 并检查我们的修改是否仍然有效: ```py import torch @@ -490,20 +490,20 @@ batch = {k: v.to(device) for k, v in batch.items()} outputs = trainer.model.to(device)(**batch) ``` -如果仍然出现错误,请确保重新启动notebook并仅执行脚本的最后一个版本。 +如果仍然出现错误,请确保重新启动 notebook 并仅执行最后一版的代码(如果之前出现了CUDA 错误,那么CUDA 内核就会被破坏,之后哪怕执行正确的代码也会出现错误)。 ### 执行一个优化器步骤 [[执行一个优化器步骤]] -现在我们知道我们可以构建实际通过模型检查的成批次的数据,我们已经为训练管道的下一步做好准备:计算梯度并执行优化步骤。 +现在我们已经可以构建通过模型检查的成批次的数据,我们已经为训练管道的下一步做好准备:接下来是计算梯度并执行优化器迭代。 -第一部分只是在 loss 上调用 `backward()` 方法: +第一部分是在 loss 上调用 `backward()` 方法: ```py loss = outputs.loss loss.backward() ``` -在这个阶段很少出现错误,但如果确实出现错误,请返回 CPU 以获取有用的错误消息。 +在这个阶段很少出现错误,但如果确实出现错误,那么需要返回 CPU 来获取更有用的错误消息。 要执行优化步骤,我们只需要创建 `optimizer` 并调用它的 `step()` 方法: @@ -512,26 +512,26 @@ trainer.create_optimizer() trainer.optimizer.step() ``` -同样,如果您在 `Trainer` 中使用默认优化器,则在此阶段您不应该收到错误,但如果您有自定义优化器,则可能会出现一些问题需要在这里调试。 如果您在此阶段遇到奇怪的 CUDA 错误,请不要忘记返回 CPU。 说到 CUDA 错误,前面我们提到了一个特殊情况。 现在让我们来看看。 +同样,如果你在 `Trainer` 中使用默认优化器,那么在此阶段你不应该收到错误,但如果你有自定义优化器,则可能会出现一些问题,需要在这里调试。如果你在此阶段遇到奇怪的 CUDA 错误,请不要忘记返回 CPU。说到 CUDA 错误,前面我们提到了一个特殊情况。现在让我们来看看这种情况。 -### 处理 CUDA out-of-memory错误 [[处理 CUDA out-of-memory错误]] +### 处理 CUDA out-of-memory 错误 [[处理 CUDA out-of-memory错误]] -每当您收到以`RuntimeError: CUDA out of memory`开头的错误消息时,这表明您的 GPU 内存不足。 这与您的代码没有直接关联,并且它可能发生在运行良好的代码中。 此错误意味着您试图在 GPU 的内部存储器中放入太多东西,这导致了错误。 与其他 CUDA 错误一样,您需要重新启动内核才能再次运行训练。 +每当你收到以 `RuntimeError: CUDA out of memory` 开头的错误消息时,这表明你的显存不足。这与你的代码没有直接关系,并且它也可能发生在运行良好的代码中。此错误意味着你试图在 GPU 的显存中放入太多东西,这导致了错误。与其他 CUDA 错误一样,你需要重新启动内核才能再次运行训练。 -要解决这个问题,您只需要使用更少的 GPU 空间——这往往说起来容易做起来难。 首先,确保您没有同时在 GPU 上运行两个模型(当然,除非您的问题需要这样做)。 然后,您可能应该减少batch的大小,因为它直接影响模型的所有中间输出的大小及其梯度。 如果问题仍然存在,请考虑使用较小版本的模型。 +要解决这个问题,你只需要使用更少的显存—这往往说起来容易做起来难。首先,确保你没有同时在 GPU 上运行两个模型(当然,除非在解决问题时必须要这样做)。然后,你可能应该减少 batch 的大小,因为它直接影响模型的所有中间输出的大小及其梯度。如果问题仍然存在,请考虑使用较小版本的模型,或者更换有更大显存的设备。 -在课程的下一部分中,我们将介绍更先进的技术,这些技术可以帮助您减少内存占用并让您微调最大的模型。 +在课程的下一部分中,我们将介绍更先进的技术,这些技术可以帮助你减少内存占用并让你微调超大的模型。 ### 评估模型 [[评估模型]] -现在我们已经解决了代码的所有问题,一切都很完美,训练应该可以顺利进行,对吧? 没那么快! 如果你运行 `trainer.train()` 命令,一开始一切看起来都不错,但过一会儿你会得到以下信息: +现在我们已经解决了所有的代码问题,一切都很完美,训练应该可以顺利进行,对吧?没那么快!如果你运行 `trainer.train()` 命令,一开始一切看起来都不错,但过一会儿你会得到以下信息: ```py -# This will take a long time and error out, so you shouldn't run this cell +# 这将花费很长时间并且会出错,所以不要直接运行这个单元 trainer.train() ``` @@ -539,9 +539,9 @@ trainer.train() TypeError: only size-1 arrays can be converted to Python scalars ``` -您将意识到此错误出现在评估阶段,因此这是我们需要调试的最后一件事。 +你将意识到此错误出现在评估阶段,因此这是我们需要调试的最后一件事。 -您可以像这样在训练中独立运行`Trainer`的评估循环: +你可以独立于训练,单独运行 Trainer 的评估循环,如下所示: ```py trainer.evaluate() @@ -553,11 +553,11 @@ TypeError: only size-1 arrays can be converted to Python scalars -💡 您应该始终确保在启动 `trainer.train()` 之前 `trainer.evaluate()`是可以运行的,以避免在遇到错误之前浪费大量计算资源。 +💡 你应该始终确保在启动 `trainer.train()` 之前 `trainer.evaluate()` 是可以运行的,以避免在遇到错误之前浪费大量计算资源。 -在尝试调试评估循环中的问题之前,您应该首先确保您已经查看了数据,能够正确地形成批处理,并且可以在其上运行您的模型。 我们已经完成了所有这些步骤,因此可以执行以下代码而不会出错: +在尝试调试评估循环中的问题之前,你应该首先确保你已经检查了数据,能够正确地形成了 batch 并且可以在其上运行你的模型。我们已经完成了所有这些步骤,因此可以执行以下代码而不会出错: ```py for batch in trainer.get_eval_dataloader(): @@ -569,18 +569,18 @@ with torch.no_grad(): outputs = trainer.model(**batch) ``` -稍等一会儿,错误出现,在评估阶段结束时,如果我们查看trackback,我们会看到: +稍等一会儿,错误就会出现,在评估阶段结束时输出了一个错误,如果我们查看 Trackback,我们会看到: ```python trace ~/git/datasets/src/datasets/metric.py in add_batch(self, predictions, references) - 431 """ + 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()` 函数的问题。 它需要一个带有 logits 和标签的元组作为 NumPy 数组,所以让我们尝试输入它: +这告诉我们错误源自 `datasets/metric.py` 模块所以很有可能是计算 `compute_metrics()` 函数时出现的问题。它需要输入 NumPy 数组格式的 logits 值和标签的元组,所以让我们尝试将其提供给它: ```py predictions = outputs.logits.cpu().numpy() @@ -593,7 +593,7 @@ compute_metrics((predictions, labels)) TypeError: only size-1 arrays can be converted to Python scalars ``` -我们得到同样的错误,所以问题肯定出在那个函数上。 如果我们回顾它的代码,我们会发现它只是将“预测”和“真实的标签”转发到“metric.compute()”。 那么这种方法有问题吗? 并不真地。 让我们快速浏览一下形状: +我们得到了同样的错误,所以问题肯定出在那个函数上。如果我们回顾它的代码,我们会发现它只是将 `predictions` 和 `labels` 转发到 `metric.compute()` 。那么这种方法有问题吗?不一定。让我们快速浏览一下输入的形状: ```py predictions.shape, labels.shape @@ -603,7 +603,7 @@ predictions.shape, labels.shape ((8, 3), (8,)) ``` -我们的预测仍然是 logits,而不是实际的预测,这就是metrics返回这个(有点模糊)错误的原因。 修复很简单; 我们只需要在 `compute_metrics()` 函数中添加一个 argmax: +我们的模型预测的输出是三个标签的 logits 值,而不是概率最高的标签id,这就是 metrics 返回这个(有点模糊)错误的原因。修复很简单;我们只需要在 `compute_metrics()` 函数中添加一个 argmax: ```py import numpy as np @@ -622,9 +622,9 @@ compute_metrics((predictions, labels)) {'accuracy': 0.625} ``` -现在我们的错误已修复! 这是最后一个,所以我们的脚本现在将正确训练模型。 +现在我们的错误已修复!这是最后一个错误,所以我们的脚本现在将可以正确地训练一个模型。 -作为参考,这里是完全修正好的脚本: +作为参考,这里是完全修复的代码: ```py import numpy as np @@ -683,44 +683,43 @@ trainer = Trainer( trainer.train() ``` -在这种情况下,如果没有更多错误,我们的脚本将微调一个应该给出合理结果的模型。 但是,如果训练没有任何错误,而训练出来的模型根本表现不佳,我们该怎么办? 这是机器学习中最难的部分,我们将向您展示一些可以提供帮助的技术。 - +在这种情况下,如果没有更多错误,我们的脚本将微调一个应该给出合理结果的模型。但是,如果训练没有任何错误,而训练出来的模型根本表现不佳,我们该怎么办?这是机器学习中最难的部分,我们将向你展示一些可以帮助解决这类问题的技巧。 -💡 如果您使用手动训练循环,则相同的步骤也适用于调试训练管道,而且更容易将它们分开。 但是,请确保您没有忘记正确位置的 `model.eval()` 或 `model.train()`,或者每个步骤中的 `zero_grad()`! +💡 如果你使用的是手动训练循环,调试训练流程时也需要遵循相同的步骤,而且更容易将训练中的各个步骤分开调试。但是,请确保你没有忘记在合适的位置调用 `model.eval()` 或 `model.train()` ,也不要忘记在每个步骤中使用 `zero_grad()` ! ## 在训练期间调试静默(没有任何错误提示)错误 [[在训练期间调试静默(没有任何错误提示)错误]] -我们可以做些什么来调试一个没有错误地完成但没有得到好的结果的训练? 我们会在这里给你一些提示,但请注意,这种调试是机器学习中最难的部分,并且没有神奇的答案。 +如何调试一个没有错误但也没有得到好的结果的训练?接下来会给出一些可以参考的做法,但请注意,这种调试是机器学习中最难的部分,并且没有万能的灵丹妙药。 -### 检查您的数据(再次!) [[检查您的数据(再次!)]] +### 再次检查你的数据 [[再次检查你的数据]] -只有在理论上可以从您的数据中学到任何东西时,您的模型才会学到一些东西。 如果存在损坏数据的错误或标签是随机属性的,那么您很可能不会在数据集上获得任何知识。 因此,始终首先仔细检查您的解码输入和标签,然后问自己以下问题: +理论上,只有数据中存在可以学习的知识,模型才会学到一些知识。如果数据已经被损坏了或标签是随机的,那么模型很可能无法从数据集中获得任何知识。因此,始终首先仔细检查你的解码后的输入和真实的标签,然后问自己以下问题: -- 解码后的数据是否可以理解? -- 你认同这些标签吗? +- 解码后的文本数据你是否可以正常阅读和理解? +- 你认同这些标签对于文本的描述吗? - 有没有一个标签比其他标签更常见? -- 如果模型预测随机的答案/总是相同的答案,那么loss/评估指标应该是多少? +- 如果模型预测的答案是随机的或总是相同的,那么 loss/ 评估指标应该是多少,是否模型根本没能学到任何知识? -⚠️ 如果您正在进行分布式训练,请在每个过程中打印数据集的样本,并三次检查您是否得到相同的结果。 一个常见的错误是在数据创建中有一些随机性来源,这使得每个进程都有不同版本的数据集。 +⚠️ 如果你正在进行分布式训练,请在每个进程中打印数据集的样本并仔细核对,确保你得到的是相同的内容。一个常见的错误是在数据创建过程中有一些随机性,导致每个进程具有不同版本的数据集。 -查看您的数据后,查看模型的一些预测并对其进行解码。 如果模型总是预测同样的事情,那可能是因为你的数据集偏向一个类别(针对分类问题); 过采样稀有类等技术可能会有所帮助。 +在检查数据后,可以检查模型的一些预测并对其进行解码。 如果模型总是预测同样的类别,那么可能是因为这个类别在数据集中的比例比较高(针对分类问题); 过采样稀有类等技术可能会对解决这种问题有帮助。或者,这也可能是由训练的设置(如错误的超参数设置)引起的。 -如果您在初始模型上获得的loss/评估指标与您期望的随机预测的loss/评估指标非常不同,请仔细检查您的loss或评估指标的计算方式,因为那里可能存在错误。 如果您使用最后添加的多个loss,请确保它们具有相同的规模。 +如果在初始模型上获得的 loss/ 评估指标与预估的随机时预测的 loss/ 评估指标非常不同,则应该仔细检查 loss/ 评估指标的计算方式,因为其中可能存在错误。如果使用多个 loss,并将其相加计算最后的loss,则应该确保它们具有相同的比例大小。 -当您确定您的数据是完美的时,您可以通过一个简单的测试来查看模型是否能够对其进行训练。 +当你确定你的数据是完美的之后,则可以通过一个简单的过拟合测试来查看模型是否能够用其进行训练。 -### 在一批上过度拟合你的模型 [[在一批上过度拟合你的模型]] +### 在一个 batch 上过拟合模型 [[在一个 batch 上过拟合模型]] -过度拟合通常是我们在训练时尽量避免的事情,因为这意味着模型没有学习识别我们想要的一般特征,而只是记住了训练样本。 在这种情况下,一遍又一遍地尝试在一个批次上训练您的模型是一个很好的测试,可以检查您的问题是否可以通过您尝试训练的模型来解决。 它还将帮助您查看您的初始学习率是否太高。 +过拟合通常是在训练时尽量避免的事情,因为这意味着模型没有识别并学习我们想要的一般特征,而只是记住了训练样本。 但一遍又一遍地尝试在一个 batch 上训练模型可以检查数据集所描述的问题是否可以通过训练的模型来解决, 它还将帮助查看你的初始学习率是否太高了。 -一旦你定义了你的 `Trainer` 之后,这样做真的很容易; 只需获取一批训练数据,然后仅使用该批次运行一个小型手动训练循环,大约 20 步: +在定义好 `Trainer` 之后,这样做真的很容易;只需获取一批训练数据,然后仅使用这个 `batch` 运行一个小型手动训练循环,大约 20 步: ```py for batch in trainer.get_train_dataloader(): @@ -739,11 +738,11 @@ for _ in range(20): -💡 如果您的训练数据不平衡,请确保构建一批包含所有标签的训练数据。 +💡 如果你的训练数据不平衡,请确保构建一批包含所有标签的训练数据。 -生成的模型在一个“批次”上应该有接近完美的结果。 让我们计算结果预测的指标: +生成的模型在一个 `batch` 上应该有接近完美的结果。让我们计算结果预测的评估指标: ```py with torch.no_grad(): @@ -758,33 +757,33 @@ compute_metrics((preds.cpu().numpy(), labels.cpu().numpy())) {'accuracy': 1.0} ``` -100% 准确率,现在这是一个很好的过拟合示例(这意味着如果你在任何其他句子上尝试你的模型,它很可能会给你一个错误的答案)! +100% 准确率,现在这是一个很好的过拟合示例(这意味着如果你在任何其他句子上尝试你的模型,它很可能会给你一个错误的答案)! -如果你没有设法让你的模型获得这样的完美结果,这意味着你构建问题或数据的方式有问题,所以你应该修复它。 只有当你可以通过过拟合测试时,你才能确定你的模型实际上可以学到一些东西。 +如果你没有设法让你的模型获得这样的完美结果,这意味着构建问题的方式或数据有问题。只有当你通过了过拟合测试,才能确定你的模型理论上确实可以学到一些东西。 -⚠️ 在此测试之后,您将不得不重新创建您的模型和“Trainer”,因为获得的模型可能无法在您的完整数据集上恢复和学习有用的东西。 +⚠️ 在此测试之后,你需要创建模型和 `Trainer` ,因为获得的模型可能无法在你的完整数据集上恢复和学习有用的东西。 -### 在你有第一个基线之前不要调整任何东西 [[在你有第一个基线之前不要调整任何东西]] +### 在你有第一个 baseline 模型之前不要调整任何东西 [[在你有第一个baseline 模型之前不要调整任何东西]] -超参数调优总是被强调为机器学习中最难的部分,但这只是帮助您在指标上有所收获的最后一步。 大多数情况下,`Trainer` 的默认超参数可以很好地为您提供良好的结果,因此在您获得超出数据集基线的东西之前,不要开始进行耗时且昂贵的超参数搜索 . +超参数调优总是被强调为机器学习中最难的部分,但这只是帮助你在指标上有所收获的最后一步。大多数情况下, `Trainer` 的默认超参数可以很好地为你提供良好的结果,因此在你拥有数据集上的 baseline 模型之前,不要急于进行耗时和昂贵的超参数搜索。 -一旦你有一个足够好的模型,你就可以开始稍微调整一下。 不要尝试使用不同的超参数启动一千次运行,而是比较一个超参数的不同值的几次运行,以了解哪个影响最大。 +在有一个足够好的模型后,就可以开始微调了。尽量避免使用不同的超参数进行一千次运行,而要比较一个超参数取不同数值的几次运行,从而了解哪个超参数的影响最大,从而理解超参数值的改变与于模型训练之间的关系。 -如果您正在调整模型本身,不要尝试任何您无法合理证明的事情。 始终确保您返回过拟合测试以验证您的更改没有产生任何意外后果。 +如果正在调整模型本身,请保持简单,不要直接对模型进行非常复杂的无法理解或者证明的修改,要一步步修改,同时尝试理解和证明这次修改对模型产生的影响,并且 确保通过过拟合测试来验证修改没有引发其他的问题。 ### 请求帮忙 [[请求帮忙]] -希望您会在本节中找到一些可以帮助您解决问题的建议,但如果不是这样,请记住您可以随时在 [论坛](https://discuss.huggingface.co/) 上向社区提问。 +希望你会在本课程中找到一些可以帮助你解决问题的建议,除此之外可以随时在 [论坛](https://discuss.huggingface.co/) 上向社区提问。 以下是一些可能有用的额外资源: -- [“作为工程最佳实践工具的再现性”](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p),作者:Joel Grus -- [“神经网络调试清单”](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) 作者:Cecelia Shao -- [“如何对机器学习代码进行单元测试”](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) by Chase Roberts -- [“训练神经网络的秘诀”](http://karpathy.github.io/2019/04/25/recipe/)作者:Andrej Karpathy +-Joel Grus 的 [“作为工程最佳实践工具的再现性”](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p) +- Cecelia Shao 的 [“神经网络调试清单”](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) +- Chase Roberts 的 [“如何对机器学习代码进行单元测试”](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) +- Andrej Karpathy 的 [“训练神经网络的秘诀”](http://karpathy.github.io/2019/04/25/recipe) -当然,并不是你在训练神经网络时遇到的每一个问题都是你自己的错! 如果您在 🤗 Transformers 或 🤗 Datasets 库中遇到看起来不正确的内容,您可能遇到了错误。 你应该告诉我们这一切,在下一节中,我们将准确解释如何做到这一点。 +当然,并不是你在训练神经网络时遇到的每个问题都是你自己的错!如果你在🤗 Transformers 或🤗 Datasets 库中遇到了似乎不正确的东西,你可能遇到了一个错误。你应该告诉我们所有这些问题,在下一节中,我们将详细解释如何做到这一点。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter8/4_tf.mdx b/chapters/zh-CN/chapter8/4_tf.mdx index b23b67fa9..f70e60508 100644 --- a/chapters/zh-CN/chapter8/4_tf.mdx +++ b/chapters/zh-CN/chapter8/4_tf.mdx @@ -1,6 +1,6 @@ -# Debugging the training pipeline [[Debugging the training pipeline]] +# 调试训练管道 [[调试训练管道]] -你已经编写了一个漂亮的脚本来训练或微调给定任务的模型,尽职尽责地遵循 [第七章](/course/chapter7) 中的建议。 但是当你启动命令 `model.fit()` 时,可怕的事情发生了:你得到一个错误😱! 或者更糟糕的是,一切似乎都很好,训练运行没有错误,但生成的模型很糟糕。 在本节中,我们将向您展示如何调试此类问题。 +假如你已经尽可能地遵循 [第七章](/course/chapter7) 中的建议,编写了一个漂亮的脚本来训练或微调给定任务的模型。 但是当你启动命令 `model.fit()` 时,你得到一个错误😱! 或者更糟的是虽然看起来一切似乎都正常,训练运行没有错误,但生成的模型却很糟糕。 在本节中,我们将向你展示如何调试此类问题。 -## Debugging the training pipeline +## 调试训练管道 [[调试训练管道]] -The problem when you encounter an error in `model.fit()` is that it could come from multiple sources, as training usually brings together a lot of things that you've been working on up until that point. The problem could be something wrong in your dataset, or some issue when trying to batch elements of the datasets together. Or it could be something wrong in the model code, or your loss function or optimizer. And even if everything goes well for training, something could still go wrong during the evaluation if there is a problem with your metric. +当你在运行 `model.fit()` 中遇到错误时,它有可能来自多个不同的来源,因为 `Trainer` 通常将之前的许多工作汇集到一起。比如有可能是你的数据集有问题,或者可能是在尝试将数据集的元素汇集在一起做批处理时出现问题,又或者模型代码、损失函数或优化器中存在问题,另外即使训练过程一切顺利,如果选取的评估指标有问题,评估过程中仍然可能出现错误。 -The best way to debug an error that arises in `model.fit()` is to manually go through this whole pipeline to see where things went awry. The error is then often very easy to solve. +所以调试 `model.fit()` 中出现的错误的最佳方法是手动检查整个管道,看看哪里出了问题。 -To demonstrate this, we will use the following script that (tries to) fine-tune a DistilBERT model on the [MNLI dataset](https://huggingface.co/datasets/glue): - -当您在 `model.fit()` 中遇到错误时,问题在于它可能来自多个来源,因为训练通常会汇集很多您在此之前一直在做的事情。 问题可能是您的数据集中有问题,或者是在尝试将数据集的元素批处理在一起时出现问题。 或者模型代码、损失函数或优化器中可能有问题。 即使训练一切顺利,如果您的指标有问题,评估期间仍然可能出现问题。 - -调试 `model.fit()` 中出现的错误的最佳方法是手动检查整个管道,看看哪里出了问题。 错误通常很容易解决。 - -为了证明这一点,我们将使用以下脚本(尝试)在 [MNLI 数据集](https://huggingface.co/datasets/glue)上微调 DistilBERT 模型: +这里我们将使用以下脚本在 [MNLI 数据集](https://huggingface.co/datasets/glue)上微调 DistilBERT 模型: ```py from datasets import load_dataset @@ -61,28 +55,28 @@ model.compile(loss="sparse_categorical_crossentropy", optimizer="adam") model.fit(train_dataset) ``` -如果您尝试执行它,在进行数据集转换时可能会收到一些“VisibleDeprecationWarning”——这是我们已知的 UX 问题,因此请忽略它。 如果你在 2021 年 11 月之后阅读这门课程并且它仍在继续,那么请在推特上 @carrigmat 上发送愤怒的推文,直到他修复它。 +如果执行这段代码,在进行数据集转换时可能会收到一些`VisibleDeprecationWarning`——这是已知的 UX 问题,可以忽略。 如果你在 2021 年 11 月之后学习本课程时还有这个问题,可以在推特上 @carrigmat 上发表推文敦促作者进行修复。 -然而,更严重的问题是我们得到了一个彻底的错误。 它真的非常长: +然而更严重的问题是你得到了一个段很长的报错: ```python out ValueError: No gradients provided for any variable: ['tf_distil_bert_for_sequence_classification/distilbert/embeddings/word_embeddings/weight:0', '...'] ``` -这意味着什么? 我们试图训练我们的数据,但我们没有梯度? 这很令人困惑。 我们甚至不知道该如何开始调试类似的东西? 当你得到的错误并不能立即表明问题出在哪里时,最好的解决方案通常是按顺序检查所有内容,确保在每个阶段一切看起来都是正确的。 当然,开始的地方总是... +这是什么意思?我们在数据上训练模型,但却没有梯度? 你甚至可能不知道该如何进行调试。当你不能从得到的报错消息直接找到问题出在哪里时,最好的解决方法通常是按顺序检查所有内容,确保在每个阶段一切看起来都正常。 -### 检查您的数据 [[检查您的数据]] +### 检查你的数据 [[检查你的数据]] -这是不言而喻的,但如果您的数据已损坏,Keras 将无法为您修复它。 所以首先,你需要看看你的训练集中有什么。 +显而易见,如果你的数据已损坏,Keras 不具备自动修复数据的功能。因此需要手动排查数据错误,首先要做的事情是查看训练集中的内容。 -尽管查看 `raw_datasets` 和 `tokenized_datasets` 很诱人,但我们强烈建议您在数据将要进入模型的地方直接查看数据。 这意味着应该从您使用 `to_tf_dataset()` 函数创建的 `tf.data.Dataset` 读取输出! 那么我们该怎么做呢? `tf.data.Dataset` 对象一次给我们整个批次并且不支持索引,所以我们不能只请求 `train_dataset[0]`。 但是,我们可以礼貌地向它要一批: +尽管查看 `raw_datasets` 和 `tokenized_datasets` 比较容易,他们都是没有经过处理的数据,比较接近人的阅读习惯。但强烈建议你在数据将要输入模型的地方直接查看数据。 这意味着你应该试着读取使用 `to_tf_dataset()` 函数创建的 `tf.data.Dataset` 的输出! 那应该怎么做呢? `tf.data.Dataset` 对象一次给我们整个 `batch` 的数据,并且不支持索引,所以不能使用 `train_dataset[0]` 来获取 `batch` 中的第一个数据。 但是我们可以先向它请求一个 `batch`: ```py for batch in train_dataset: break ``` -`break` 在一次迭代后结束循环,因此这会抓取来自`train_dataset` 的第一批并将其保存为`batch`。 现在,让我们看看里面有什么: +`break` 在第一次迭代后自动结束循环,我们可以使用它来抓取 `train_dataset` 的第一批数据并将其保存为 `batch`。 现在,让我们看看里面有什么: ```python out {'attention_mask': } ``` -这看起来不错,不是吗?我们将 `labels` 、`attention_mask` 和 `input_ids` 传递给模型,这应该是计算输出和计算损失所需的一切。那么为什么我们没有梯度呢?仔细看:我们将单个字典作为输入传递,但训练批次通常是输入张量或字典,加上标签张量。我们的标签只是我们输入字典中的一个键。 +看起来一切正常,对吧?我们将 `labels` 、`attention_mask` 和 `input_ids` 传递给模型,这的确是模型计算输出和计算损失所需的数据。那么为什么没有梯度呢?仔细看:仔细看,我们将输入模型的输入数据和真实的标签值放入一个字典中传递给了模型,但训练批次通常是模型的输入数据加上真实的标签值。而现在真实的标签值只是输入字典中的一个键,并没有独立出来。 -这是一个问题吗?实际上,并非总是如此!但这是您在使用 TensorFlow 训练 Transformer 模型时会遇到的最常见问题之一。我们的模型都可以在内部计算损失,但要做到这一点,需要在输入字典中传递标签。这是当我们没有为 `compile()` 指定损失值时使用的损失。另一方面,Keras 通常希望标签与输入字典分开传递,如果你不这样做,损失计算通常会失败。 +这是一个错误吗?实际上并不总是!不过这是在使用 TensorFlow 训练 Transformer 模型时会遇到的最常见问题之一。如果运行 `compile()` 时没有指定所使用的损失函数,那么模型可以使用内部默认的损失函数自动计算损失,这个时候就需要将输入模型的数据和真实的标签值放入一个字典中传递给模型。如果想要使用自定义的损失函数,在使用 Keras 时通常需要将真实的标签值与输入字典分开传递给模型,否则损失计算通常会失败。 -问题现在变得更清楚了:我们传递了一个“损失”参数,这意味着我们要求 Keras 为我们计算损失,但我们将标签作为模型的输入传递,而不是放在 Keras 期望的地方的!我们需要二选一:要么使用模型的内部损失并将标签保留在原处,要么继续使用 Keras 损失,但将标签移动到 Keras 期望的位置。为简单起见,让我们采用第一种方法。将对 `compile()` 的调用更改为: +问题现在变得清晰了,我们传递了一个 loss 参数,这意味着要求 Keras 使用自定义的损失函数计算损失。但是,我们将真实的标签值放入了输入的字典中传递给了模型,并没有放在 Keras 期望的位置!因此,我们需要从两种方法中二选一,要么删除自定义的损失函数使用模型的内部损失并将真实的标签值保留在原处,要么继续使用自定义的Keras 损失但将真实的标签值移动到Keras 期望的位置。为了简单起见,在这里可以采用第一种方法。将对 `compile()` 的调用更改如下。 ```py model.compile(optimizer="adam") ``` -现在我们将使用模型的内部损失,这个问题应该解决了! +现在我们可以使用模型的内部自动计算损失,这个问题解决了! -✏️ **轮到你了!** 作为我们解决其他问题后的可选挑战,你可以尝试回到这一步,让模型使用原始 Keras 计算的损失而不是内部损失。 您需要将 `"labels"` 添加到 `to_tf_dataset()` 的 `label_cols` 参数,以确保正确输出标签,这将为您提供梯度——但我们指定的损失还有一个问题 . 训练仍然会遇到这个问题,学习会非常缓慢,并且会在多次训练损失时达到稳定状态。 你能弄清楚它是什么吗? +✏️ **轮到你了!** 作为解决其他问题后的可选挑战,你可以尝试回到这一步,让模型使用原始 Keras 计算的损失而不是内部损失。 你需要将 `"labels"` 添加到 `to_tf_dataset()` 的 `label_cols` 参数,并且确保 `to_tf_dataset()` 输出真实的标签来提供梯度,但是我们指定的损失还有一个问题。即使在这个问题上可以进行训练,学习速度仍然会非常慢,并且 loss 会达到一个较高的值。你能找出问题在哪里吗? + +如果你卡住了,这是一个 ROT13 编码的提示(如果你不熟悉 ROT13,可以在[这里](https://rot13.com/)解码。):Vs lbh ybbx ng gur bhgchgf bs FrdhraprPynffvsvpngvba zbqryf va Genafsbezref, gurve svefg bhgchg vf `ybtvgf`. Jung ner ybtvgf?(查看 Transformers 中 `SequenceClassification` 模型的输出,第一个输出是“logits”。思考什么是 logits ?它所代表的实际含义是什么?) -一个 ROT13 编码的提示,如果你卡住了:Vs lbh ybbx ng gur bhgchgf bs FrdhraprPynffvsvpngvba zbqryf va Genafsbezref, gurve svefg bhgchg vf `ybtvgf`。 荣格纳 ybtvgf? +还有一个提示: -第二个提示:Jura lbh fcrpvsl bcgvzvmref, npgvingvbaf 是 ybffrf jvgu fgevatf, Xrenf frgf nyy gur nethzrag inyhrf gb gurve qrsnhygf。 Jung nethzragf qbrf FcnefrPngrtbevpnyPebffragebcl unir, naq jung ner gurve qrsnhygf? +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?(当训练模型时直接使用字符串告诉模型指定优化器、激活函数或损失函数时,Keras 会使用优化器、激活函数或损失函数参数值的默认值。 +`SparseCategoricalCrossentropy` 损失函数有哪些参数,它们的默认值是什么? +) -现在,让我们尝试训练。 我们现在应该得到梯度,所以希望(这里播放不祥的音乐)我们可以调用 `model.fit()` 一切都会正常工作! +现在让我们尝试继续进行训练。 如今已经得到梯度,所以希望(此处播放令人不安的音乐)只需调用`model.fit()`,一切都会正常工作! ```python out 246/24543 [..............................] - ETA: 15:52 - loss: nan -``` - -Oh no. +``` +哦不。 -`nan` 不是一个非常令人开心的损失值。 尽管如此,我们已经检查了我们的数据,它看起来还不错。 如果这不是问题,我们下一步该去哪里? 显而易见的下一步是... +`nan` 不是一个正常的损失值。我们已经检查了我们的数据,看起来一切正常。如果问题不在数据,那么我们接下来检查哪里呢?很明显的下一步是... -### 检查你的模型 [[检查你的模型]] +### 检查模型 [[检查模型]] -`model.fit()` 是 Keras 中一个非常方便的函数,但它为您做了很多事情,这使得准确找到问题发生的位置变得更加棘手。 如果您正在调试您的模型,一个真正有用的策略是只将一个批次传递给模型,并详细查看该批次的输出。 如果模型抛出错误,另一个非常有用的提示是使用 `run_eagerly=True` `compile()` 模型。 这会使它变慢很多,但它会使错误消息更容易理解,因为它们会准确地指出问题发生在模型代码的哪个位置。 +`model.fit()` 是 Keras 中一个很方便的函数,但这个函数一次性做了很多事情,这使准确定位问题发生的位置变得更加棘手。 如果你正在调试模型,那么一个明智的策略是考虑只将一个批次传递给模型,并查看该批次的详细输出。 如果模型抛出错误,另一个非常有用的提示是可以将`run_eagerly=True`参数传递给 `compile()`。 这会使它训练过程变慢很多,但可以使输出的错误消息变得更容易理解,因为它会准确地指出问题发生在模型代码的哪个位置。 -不过,目前我们还不需要 `run_eagerly`。 让我们通过模型运行我们之前得到的“批处理”,看看输出是什么样子的: +不过目前我们还不需要 `run_eagerly`。让我们将之前得到的 `batch` 输入模型,并查看输出的结果: ```py model(batch) @@ -168,16 +165,16 @@ array([[nan, nan], [nan, nan]], dtype=float32)>, hidden_states=None, attentions=None) ``` -嗯,这很棘手。一切都是`nan`!但这很奇怪,不是吗?我们所有的 logits 怎么会变成“nan”? `nan` 的意思是“不是数字”。 `nan` 值经常出现在您执行禁止操作时,例如除以零。但是,在机器学习中了解 `nan` 非常重要的一件事是,该值倾向于*传播*。如果将一个数字乘以 `nan`,则输出也是 `nan`。如果你在输出、损失或梯度的任何地方得到一个“nan”,那么它会迅速传播到你的整个模型中——因为当那个“nan”值通过你的网络传播回来时,你会得到nan 梯度,当使用这些梯度计算权重更新时,您将获得 nan 权重,这些权重将计算更多的 nan 输出!很快,整个网络将只是“nan”的一大块。一旦发生这种情况,就很难看出问题是从哪里开始的。我们如何隔离“nan”第一次出现的地方? +嗯,这看起来很棘手。所有的值都是`nan`!但是这很奇怪,对吧?为什么所有的 logits 都变成了`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=, hidden_states=None, attentions=None) ``` -*现在*我们到了某个地方! 我们的 logits 中没有 `nan` 值,这令人放心。 但是我们确实在损失中看到了一些“nan”值! 这些样本有什么特别导致这个问题的吗? 让我们看看它们是哪些(请注意,如果您自己运行此代码,您可能会得到不同的索引,因为数据集已被随机打乱): +现在我们到了 logits 中没有 `nan` 值的地方,这是一个好的开始。但是我们确实在损失中看到了一些 `nan` 值,这些出现 `nan` 的样本有什么特别之处可以导致这个问题吗?(请注意,你运行此代码时可能会得到不同的索引,因为数据集已被随机打乱): ```python import numpy as np @@ -317,7 +314,7 @@ array([[ 101, 2007, 2032, 2001, 1037, 16480, 3917, 2594, 4135, 0, 0, 0, 0]]) ``` -嗯,这里有很多东西,但没有什么不寻常的。 让我们看看标签: +目前没有什么不寻常之处。 让我们检查一下标签: ```python out labels = batch['labels'].numpy() @@ -328,7 +325,7 @@ labels[indices] array([2, 2, 2, 2, 2, 2, 2, 2, 2]) ``` -啊! `nan` 样本都具有相同的标签,即标签 2。这是一个非常明显的提示。 当我们的标签为 2 时,我们会得到loss为 `nan`,这表明这是检查模型中标签数量的好时机: +啊!所有的 `nan` 样本都具有相同的标签,即标签 `2` 。这是一个非常明显的提示, 当我们的标签为 `2` 时,我们会得到loss为 `nan`,现在是时候检查一下模型的标签了: ```python model.config.num_labels @@ -338,7 +335,7 @@ model.config.num_labels 2 ``` -现在我们看到了问题:模型认为只有两个类,但标签上升到 2,这意味着实际上有三个类(因为 0 也是一个类)。 这就是我们得到“nan”的方式——通过尝试计算不存在的类的损失! 让我们尝试改变它并再次拟合模型: +这表明模型认为只有两个类,但是标签的取值范围是从 0 到 2,这意味着实际上有三个类别(因为 0 也是一个类)。这就是我们得到`nan`的原因——尝试计算不存在的类别的损失。让我们改变标签的取值范围并再次拟合模型。 ``` model = TFAutoModelForSequenceClassification.from_pretrained(model_checkpoint, num_labels=3) @@ -349,16 +346,15 @@ model.fit(train_dataset) ```python out 869/24543 [>.............................] - ETA: 15:29 - loss: 1.1032 ``` +训练在正常进行!没有了`nan`,损失也在下降……有点。如果你观察一段时间,你可能会觉得有点不耐烦,虽然我们的损失正在一点点减少,但总体还是一直居高不下。先停止训练并尝试考虑可能导致此问题的原因。到目前为止,我们很确定数据和模型都没有问题,但是我们的模型的学习效果并不是特别好。还剩下什么?是时候... -我们在训练! 没有更多的'nan's,我们的损失正在减少......有点。 如果你观察一段时间,你可能会开始有点不耐烦,因为损失值一直居高不下。 让我们在这里停止训练并尝试考虑可能导致此问题的原因。 在这一点上,我们很确定数据和模型都没有问题,但是我们的模型并没有很好地学习。 还剩下什么? 是时候... - -### 检查你的超参数 [[检查你的超参数]] +### 检查超参数 [[检查超参数]] -如果你回头看上面的代码,你可能根本看不到任何超参数,除了 `batch_size`,这似乎不是罪魁祸首。不过,不要被迷惑;总是有超参数,如果你看不到它们,那只是意味着你不知道它们的设置是什么。特别要记住关于 Keras 的一个关键点:如果您使用字符串设置损失函数、优化器或激活函数,_它的所有参数都将设置为它们的默认值_。这意味着即使为此使用字符串非常方便,但在这样做时您应该非常小心,因为它很容易对您隐藏关键的事情。 (任何尝试上述方式的人都应该仔细注意这一事实。) +如果你回头看上面的代码,根本看不到别的超参数,除了 `batch_size`,而 `batch_size` 似乎不太可能是问题的原因。不过,不要被表象迷惑;超参数始终存在,如果你看不到它们,那只意味着你不知道它们默认被设置成了什么。这里强调一个关于 Keras 的关键点:如果使用字符串设置损失函数、优化器或激活函数,“它的所有参数都将设置为默认值”。这意味着即使使用字符串非常方便,但在这样做时应该非常小心,因为它很容易隐藏一些关键的问题。(尝试上面的可选挑战的人应该注意到了这一点。) -在这种情况下,我们在哪里设置了带有字符串的参数?我们最初使用字符串设置损失,但我们不再这样做了。但是,我们正在使用字符串设置优化器。难道这对我们隐瞒了什么?让我们看看[关于它的一些讨论](https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/Adam)。 +在这个例子中,我们在哪里使用了字符串参数呢 ?最初我们使用字符串设置了损失函数,但现在我们已经去掉了。除此之外,我们还使用字符串设置了优化器。其中是否隐藏了什么信息呢?让我们查看一下优化器的[参数](https://www.tensorflow.org/api_docs/python/tf/keras/optimizers/Adam): -这里有什么需要注意的吗?没错——学习率!当我们只使用字符串“adam”时,我们将获得默认的学习率,即 0.001,即 1e-3。这对于transormer模型来说太高了!一般来说,我们建议您的模型尝试 1e-5 和 1e-4 之间的学习率;这比我们在这里实际使用的值小 10X 到 100X 之间。听起来这可能是一个主要问题,所以让我们尝试减少它。为此,我们需要导入实际的“优化器”对象。当我们这样做的时候,让我们从检查点重新初始化模型,以防高学习率的训练损坏了它的权重: +这里需要注意学习率。当我们只使用字符串“adam”时,将使用默认的学习率 0.001(即 1e-3)。这对于transormer模型来说太高了,一般来说,我们建议尝试学习率在 1e-5 到 1e-4 之间的值;这比默认的值小 10倍 到 100倍。这听起来可能是一个导致loss下降很缓慢的原因,所以让我们尝试减小学习率。为此我们需要导入`optimizer` 对象,在`optimizer` 对象中设置学习率。让我们从`checkpoint` 重新初始化模型,以防刚刚过高的学习率的训练破坏了权重: ```python from tensorflow.keras.optimizers import Adam @@ -369,11 +365,11 @@ model.compile(optimizer=Adam(5e-5)) -💡您还可以从🤗 Transformers 中导入 `create_optimizer()` 函数,这将为您提供具有正确权重衰减以及学习率预热和学习率衰减的 AdamW 优化器。 此优化器通常会产生比使用默认 Adam 优化器获得的结果稍好一些的结果。 +💡除了从 Keras 中导入 `Adam` 优化器你还可以从🤗 Transformers 中导入 `create_optimizer()` 函数,这将提供具有正确的权重衰减和学习率预热和衰减的 AdamW 优化器。 此优化器通常会比使用默认 `Adam` 优化器的效果稍好一些。 -现在,我们可以尝试使用新的、改进后的学习率来拟合模型: +现在,我们可以使用改进后的学习率来拟合模型: ```python model.fit(train_dataset) @@ -383,107 +379,106 @@ model.fit(train_dataset) 319/24543 [..............................] - ETA: 16:07 - loss: 0.9718 ``` -现在我们的损失真的在某个地方! 训练终于看起来奏效了。 这里有一个教训:当你的模型正在运行但损失没有下降,并且你确定你的数据没问题时,检查学习率和权重衰减等超参数是个好主意。 将其中任何一个设置得太高很可能导致训练在高损失值下“停滞”。 +现在训练终于看起来奏效了。当你的模型在正常训练但损失没有下降,同时确定数据没问题时,可以检查学习率和权重衰减等超参数,其中任何一个设置得太高很可能导致训练的损失值居高不下。 ## 其他潜在问题 [[其他潜在问题]] -我们已经涵盖了上面脚本中的问题,但您可能会遇到其他几个常见错误。 让我们看一个(非常不完整的)列表。 +我们已经涵盖了上面脚本中存在的所有问题,但是还有其他一些常见错误可能会遇到。让我们来看一个(不太完整的)列表。 ### 处理内存不足错误 [[处理内存不足错误]] -内存不足的迹象是“分配张量时出现 OOM”之类的错误——OOM 是“内存不足”的缩写。 在处理大型语言模型时,这是一个非常常见的危险。 如果遇到这种情况,一个好的策略是将批量大小减半并重试。 但请记住,有些型号*非常*大。 例如,全尺寸 GPT-2 的参数为 1.5B,这意味着您将需要 6 GB 的内存来存储模型,另外需要 6 GB 的内存用于梯度下降! 无论您使用什么批量大小,训练完整的 GPT-2 模型通常需要超过 20 GB 的 VRAM,而只有少数 GPU 拥有。 像“distilbert-base-cased”这样更轻量级的模型更容易运行,训练也更快。 +内存不足指的是`OOM when allocating tensor`之类的错误——OOM 是`out of memory`的缩写。 在处理大型语言模型时,这是一个非常常见的错误。 如遇此种情况,可以将 batch size 减半并重试。 但有些尺寸非常大,比如全尺寸 GPT-2 的参数为 1.5B,这意味着你将需要 6 GB 的内存来存储模型,另外需要 6 GB 的内存用于梯度下降! 无论你使用什么 batch size ,训练完整的 GPT-2 模型通常都需要超过 20 GB 的 VRAM,然而这只有少数 GPU 才可以做到。 像`distilbert-base-cased`这样更轻量级的模型更容易训练,并且训练速度也更快。 -在课程的下一部分中,我们将介绍更先进的技术,这些技术可以帮助您减少内存占用并让您微调最大的模型。 +在课程的下一部分中,我们将介绍更先进的技术,这些技术可以帮助你减少内存占用并微调超大的模型。 ### TensorFlow 🦛饿饿 [[TensorFlow 🦛饿饿]] -您应该注意的 TensorFlow 的一个特殊怪癖是,它会在您加载模型或进行任何训练后立即为自己分配 *所有 * GPU 内存,然后根据需要分配该内存。这与其他框架的行为不同,例如 PyTorch,后者根据 CUDA 的需要分配内存,而不是在内部进行。 TensorFlow 方法的一个优点是,当您耗尽内存时,它通常会给出有用的错误,并且它可以从该状态恢复而不会导致整个 CUDA 内核崩溃。但也有一个重要的缺点:如果你同时运行两个 TensorFlow 进程,那么**你将度过一段糟糕的时光**。 +TensorFlow 的一个与众不同的特点是它会在加载模型或进行任何训练后会立即向系统请求所需的所有显存,然后在内部再将请求到的显存划分给不同的模块。这与其他框架的行为不同,例如 PyTorch根据 CUDA 的需要动态分配内存,而不是在内部进行。 TensorFlow 方法的一个优点是当内存耗尽时,它通常会给出可以帮助我们定位问题的错误信息,并且可以从错误状态恢复而不会导致整个 CUDA 内核崩溃。但是,如果同时运行两个 TensorFlow 进程,那么势必会因为预先分配内存导致内存爆炸。 -如果您在 Colab 上运行,则无需担心这一点,但如果您在本地运行,这绝对是您应该小心的事情。特别要注意,关闭笔记本选项卡并不一定会关闭该笔记本!您可能需要选择正在运行的笔记本(带有绿色图标的笔记本)并在目录列表中手动关闭它们。任何使用 TensorFlow 的正在运行的笔记本仍可能占用大量 GPU 内存,这意味着您启动的任何新笔记本都可能会遇到一些非常奇怪的问题。 +如果你在 Google Colab 上运行则无需担心这一点,但如果在本地运行,则应该小心是否成功释放了显存。特别要注意,关闭Notebook选项卡并不一定会彻底关闭该 Notebook !需要选择正在运行的 Notebook (带有绿色图标的 Notebook )并在目录列表中手动关闭它们。任何使用 TensorFlow 的正在运行的 Notebook 都可能占用大量显存,这意味着启动的任何新的使用TensorFlow 的 Notebook 可能会遇到一些非常奇怪的问题。 -如果您开始运行之前正确的代码却收到有关 CUDA、BLAS 或 cuBLAS 的错误,这通常是罪魁祸首。您可以使用类似 `nvidia-smi` 的命令来检查 - 当您关闭或重新启动当前笔记本时,您的大部分内存是否空闲,或者是否仍在使用中?如果它仍在使用中,则有其他东西在占用它! +如果在运行正确的代码后收到有关 CUDA、BLAS 或 CUBLAS 的错误,那么问题来源通常是类似的,可以使用类似 `nvidia-smi` 的命令来检查。当关闭或重新启动当前 Notebook 时,要查看大部分显存是否空闲或者仍在使用,如果仍在使用中,则代表仍然有其他东西在占用内存。 -### 检查您的数据(再次!) [[检查您的数据(再次!)]] +### 再次检查你的数据 [[再次检查你的数据]] -只有在理论上可以从您的数据中学到任何东西时,您的模型才会学到一些东西。 如果存在损坏数据的错误或标签是随机属性的,那么您很可能不会在数据集上获得任何知识。这里一个有用的工具是`tokenizer.decode()`。 这会将 `input_ids` 转换回字符串,因此您可以查看数据并查看您的训练数据是否正在教授您希望它教授的内容。 例如,像我们上面所做的那样从 `tf.data.Dataset` 中获取 `batch` 后,您可以像这样解码第一个元素: +理论上,只有数据中存在可以学习的知识,模型才会学到一些知识。如果数据已经被损坏了或标签是随机的,那么模型很可能无法从数据集中获得任何知识。这里一个有用的工具是解决该问题的一个有用的方法是使用 `tokenizer.decode()`将 `input_ids` 转换回字符串, 可以通过这个方法来查看数据和对应的标签是否正常。例如,像我们下面所做的那样, 从 `tf.data.Dataset` 中获取 `batch`,并解码第一个元素。 ```py input_ids = batch["input_ids"].numpy() tokenizer.decode(input_ids[0]) ``` -Then you can compare it with the first label, like so: +接着检查所对应的第一个数据的真实标签。 ```py labels = batch["labels"].numpy() label = labels[0] ``` -一旦您可以像这样查看您的数据,您可以问自己以下问题: +在查看数据时,可以对以下问题进行检查。 -- 解码后的数据是否可以理解? -- 你认同这些标签吗? +- 解码后的文本数据你是否可以正常阅读和理解? +- 你认同这些标签对于文本的描述吗? - 有没有一个标签比其他标签更常见? -- 如果模型预测随机的答案/总是相同的答案,那么loss/评估指标应该是多少? +- 如果模型预测的答案是随机的或总是相同的,那么 loss/ 评估指标应该是多少,是否模型根本没能学到任何知识? -查看您的数据后,查看模型的一些预测并对其进行解码。 如果模型总是预测同样的事情,那可能是因为你的数据集偏向一个类别(针对分类问题); 过采样稀有类等技术可能会有所帮助。 +在检查数据后,可以检查模型的一些预测并对其进行解码。 如果模型总是预测同样的类别,那么可能是因为这个类别在数据集中的比例比较高(针对分类问题); 过采样稀有类等技术可能会对解决这种问题有帮助。或者,这也可能是由训练的设置(如错误的超参数设置)引起的。 -如果您在初始模型上获得的loss/评估指标与您期望的随机预测的loss/评估指标非常不同,请仔细检查您的loss或评估指标的计算方式,因为那里可能存在错误。 如果您使用最后添加的多个loss,请确保它们具有相同的规模。 +如果在初始模型上获得的 loss/ 评估指标与预估的随机时预测的 loss/ 评估指标非常不同,则应该仔细检查 loss/ 评估指标的计算方式,因为其中可能存在错误。如果使用多个 loss,并将其相加计算最后的loss,则应该确保它们具有相同的比例大小。 -当您确定您的数据是完美的时,您可以通过一个简单的测试来查看模型是否能够对其进行训练。 +当你确定你的数据是完美的之后,则可以通过一个简单的过拟合测试来查看模型是否能够用其进行训练。 -### 在一批上过度拟合你的模型 [[在一批上过度拟合你的模型]] +### 在一个 batch 上过拟合模型 [[在一个 batch 上过拟合模型]] -过度拟合通常是我们在训练时尽量避免的事情,因为这意味着模型没有学习识别我们想要的一般特征,而只是记住了训练样本。 但是,一遍又一遍地尝试在一个批次上训练您的模型是一个很好的测试,可以检查您构建的问题是否可以通过您尝试训练的模型来解决。 它还将帮助您查看您的初始学习率是否太高。 +过拟合通常是在训练时尽量避免的事情,因为这意味着模型没有识别并学习我们想要的一般特征,而只是记住了训练样本。 但一遍又一遍地尝试在一个 batch 上训练模型可以检查数据集所描述的问题是否可以通过训练的模型来解决, 它还将帮助查看你的初始学习率是否太高了。 -一旦你定义了你的“模型”,这样做真的很容易; 只需获取一批训练数据,然后将该“批次”视为您的整个数据集,并在其上fit大量epoch: +在定义 Trainer 后,只需获取一个 batch 训练数据,然后将这个 batch 视为整个数据集,并在上面拟合大量 epoch 即可: ```py for batch in train_dataset: break -# Make sure you have run model.compile() and set your optimizer, [[Make sure you have run model.compile() and set your optimizer,]] -# and your loss/metrics if you're using them [[and your loss/metrics if you're using them]] +# 确保已经运行了 model.compile() 并设置了优化器和损失/指标 model.fit(batch, epochs=20) ``` -💡 如果您的训练数据不平衡,请确保构建一批包含所有标签的训练数据。 +💡 如果训练数据不平衡,请确保构建的这个 batch 包含训练数据中所有的标签。 -生成的模型在“批次”上应该有接近完美的结果,损失迅速下降到 0(或您正在使用的损失的最小值)。 +生成的模型在一个 batch 上应该有接近完美的结果,损失迅速下降到 0(或你正在使用的损失的最小值)。 -如果你没有设法让你的模型获得这样的完美结果,这意味着你构建问题或数据的方式有问题,所以你应该修复它。 只有当你设法通过过拟合测试时,你才能确定你的模型实际上可以学到一些东西。 +如果你没有设法让你的模型获得这样的完美结果,这意味着构建问题的方式或数据有问题。只有当你通过了过拟合测试,才能确定你的模型理论上确实可以学到一些东西。 -⚠️ 在此测试之后,您将不得不重新创建您的模型和“Trainer”,因为获得的模型可能无法在您的完整数据集上恢复和学习有用的东西。 +⚠️ 在此测试之后,你需要创建模型和 `Trainer` ,因为经过过拟合测试的模型可能无法恢复正常的参数范围,从而无法在完整数据集上学到有用的知识。 -### 在你有第一个基线之前不要调整任何东西 [[在你有第一个基线之前不要调整任何东西]] +### 在你有第一个 baseline 模型之前不要调整任何东西 [[在你有第一个 baseline 模型之前不要调整任何东西]] -超参数调整总是被强调为机器学习中最难的部分,但这只是帮助您在指标上获得一点点提升的最后一步。 例如将默认的 Adam 学习率 1e-3 与 Transformer 模型一起使用,当然会使学习进行得非常缓慢或完全停止,但大多数时候“合理”的超参数,例如从 1e-5 到 5e-5 的学习率,会很好地给你带来好的结果。因此,在您获得超出数据集基线的东西之前,不要开始进行耗时且昂贵的超参数搜索。 +超参数调整总是被是为机器学习中最难的部分,但这只是帮助你在指标上获得一点点提升的最后一步。 例如,使用学习率为 1e-3 的Adam 优化器训练 Transformer 模型时,当然会使学习进行得非常缓慢或完全停止,在大多数时候,合理的超参数会带来更好的结果, 如 1e-5 到 5e-5 的学习率。不过,在你有了 baseline 模型之前,请不要试图进行耗时且昂贵的超参数搜索。 -一旦你有一个足够好的模型,你就可以开始稍微调整一下。 不要尝试使用不同的超参数启动一千次运行,而是比较一个超参数的不同值的几次运行,以了解哪个影响最大。 +在有一个足够好的模型后,就可以开始微调了。尽量避免使用不同的超参数进行一千次运行,而要比较一个超参数取不同数值的几次运行,从而了解哪个超参数的影响最大,从而理解超参数值的改变与于模型训练之间的关系。 -如果您正在调整模型本身,不要尝试任何您无法合理证明的事情。 始终确保您返回过拟合测试以验证您的更改没有产生任何意外后果。 +如果正在调整模型本身,请保持简单,不要直接对模型进行非常复杂的无法理解或者证明的修改,要一步步修改,同时尝试理解和证明这次修改对模型产生的影响,并且 确保通过过拟合测试来验证修改没有引发其他的问题。 ### 请求帮忙 [[请求帮忙]] -希望您会在本节中找到一些可以帮助您解决问题的建议,但如果不是这样,请记住您可以随时在 [论坛](https://discuss.huggingface.co/) 上向社区提问。 +希望你会在本课程中找到一些可以帮助你解决问题的建议,除此之外可以随时在 [论坛](https://discuss.huggingface.co/) 上向社区提问。 以下是一些可能有用的额外资源: -- [“作为工程最佳实践工具的再现性”](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p),作者:Joel Grus -- [“神经网络调试清单”](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) 作者:Cecelia Shao -- [“如何对机器学习代码进行单元测试”](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) by Chase Roberts -- [“训练神经网络的秘诀”](http://karpathy.github.io/2019/04/25/recipe/)作者:Andrej Karpathy +-Joel Grus 的 [“作为工程最佳实践工具的再现性”](https://docs.google.com/presentation/d/1yHLPvPhUs2KGI5ZWo0sU-PKU3GimAk3iTsI38Z-B5Gw/edit#slide=id.p) +- Cecelia Shao 的 [“神经网络调试清单”](https://towardsdatascience.com/checklist-for-debugging-neural-networks-d8b2a9434f21) +- Chase Roberts 的 [“如何对机器学习代码进行单元测试”](https://medium.com/@keeper6928/how-to-unit-test-machine-learning-code-57cf6fd81765) +- Andrej Karpathy 的 [“训练神经网络的秘诀”](http://karpathy.github.io/2019/04/25/recipe) -当然,并不是你在训练神经网络时遇到的每一个问题都是你自己的错! 如果您在 🤗 Transformers 或 🤗 Datasets 库中遇到看起来不正确的内容,您可能遇到了错误。 你应该告诉我们这一切,在下一节中,我们将准确解释如何做到这一点。 +当然,并非你在训练神经网络时遇到的每个问题都是你自己的错!如果你在 🤗 Transformers 或 🤗 Datasets 库中遇到看起来不正确的内容而导致无法解决的问题,请及时告知我们。在下一节中,我们将准确解释如何进行这一步。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter8/5.mdx b/chapters/zh-CN/chapter8/5.mdx index e7164be66..d5cb0793e 100644 --- a/chapters/zh-CN/chapter8/5.mdx +++ b/chapters/zh-CN/chapter8/5.mdx @@ -7,40 +7,41 @@ {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter8/section5.ipynb"}, ]} /> -当您遇到 Hugging Face 库中的一个看起来不正确的东西时,您一定要告诉我们,以便我们可以修复它(就此而言,任何开源库也是如此)。如果您不能完全确定错误是在您自己的代码中还是在我们的某个库中,那么首先要检查的是[forums](https://discuss.huggingface.co/).社区会帮助你解决这个问题,Hugging Face 团队也会密切关注那里的讨论。 +当你在使用 Hugging Face 库时遇到了不正常的情况,你应该及时告诉我们,这样我们才能修复它(对于任何开源库都是一样)。如果你不确定 bug 是在你自己的代码中还是在我们的库中,首先可以在 [论坛](https://discuss.huggingface.co) 进行搜索。论坛中的帖子有可能会帮助你找出问题所在,同时Hugging Face 团队也会密切关注那里的讨论。 -当您确定手头有错误时,第一步是构建一个最小的可重现示例。 +当你确定手头上有一个 bug 时,第一步是构建一个最小可复现的示例。 + ## 创建一个最小的可重现示例 [[创建一个最小的可重现示例]] -隔离产生错误的代码段非常重要,因为 Hugging Face 团队中没有人是魔术师(目前),他们无法修复他们看不到的东西。顾名思义,最小的可重现示例应该是可重现的。这意味着它不应依赖于您可能拥有的任何外部文件或数据。尝试用一些看起来像真实值的虚拟值替换您正在使用的数据,但仍然会产生相同的错误。 +创建一个最小可复现的示例非常重要,因为 Hugging Face 团队中没有人是魔术师(至少目前还没有),他们不能修复他们看不到的问题。一个最小可复现的示例应该是可复现的,这意味着它不应依赖于你可能有的任何外部文件或数据。尝试用一些看起来像真实数据的虚拟值替换你正在使用的数据,并且仍然产生相同的错误。 -🚨🤗 Transformers 存储库中的许多问题都没有解决,因为用于复制它们的数据不可访问。 +🚨🤗 Transformers 仓库中有很多未解决的问题,因为无法访问复现这些问题的数据。 -一旦你有一些自包含的东西,你可以尝试将它减少到更少的代码行,构建我们所谓的最小的可重复示例.虽然这需要你做更多的工作,但如果你提供一个漂亮的、简短的错误重现器,你几乎可以保证得到帮助和修复。 +在创建了一个包含所遇见的问题示例后,你可以尝试将其进一步简化,构建我们所说的“最小可复现示例”。虽然这需要你多做一些工作,但如果你提供了一个简洁明了的 bug 复现,那么几乎可以肯定会得到帮助和修复。 -如果您觉得足够舒服,请检查发生错误的源代码。您可能会找到问题的解决方案(在这种情况下,您甚至可以提出拉取请求来修复它),但更一般地说,这可以帮助维护人员在阅读您的报告时更好地理解来源。 +如果你感觉足够自信,可以检查一下你的 bug 发生的源代码。你可能会找到解决问题的方法(在这种情况下,你甚至可以发起一个修复它的 pull request 请求),但更一般地说,这可以帮助维护人员在阅读你的报告时更好地理解源代码。 ## 填写问题模板 [[填写问题模板]] -当您提交问题时,您会注意到有一个模板需要填写。我们将按照[🤗 Transformers issues](https://github.com/huggingface/transformers/issues/new/choose)在这里,但是如果您在另一个存储库中报告问题,则需要相同类型的信息。不要将模板留空:花时间填写它可以最大限度地提高您获得答案和解决问题的机会。 +当你提交问题时,需要填写一个模板。我们将在这里遵循 [🤗 Transformers issues](https://github.com/huggingface/transformers/issues/new/choose) 的模板,但如果你在其他仓库中报告问题,也需要提供相同类型的信息。请不要将模板留空,花时间填写模板将最大程度地增加你得到答案和解决问题的机会。 -通常,在提交问题时,请始终保持礼貌。这是一个开源项目,因此您使用的是免费软件,没有人有任何义务帮助您。您可能会在您的问题中包含您认为合理的批评,但是维护人员很可能会认为它很糟糕并且不会急于帮助您。确保你阅读了[code of conduct](https://github.com/huggingface/transformers/blob/master/CODE_OF_CONDUCT.md)项目的。 +一般来说,当提交问题时,要保持礼貌。这是一个开源项目,所以你使用的是免费软件,没有人有义务帮助你。你可以在你的问题中包含你认为合理的批评,但维护者可能会对此感到不满,从而拒绝立即提供帮助。确保你阅读了该项目的 [行为准则](https://github.com/huggingface/transformers/blob/main/CODE_OF_CONDUCT.md) 。 -### 包括您的环境信息 [[包括您的环境信息]] +### 提供环境信息 [[提供环境信息]] -🤗 Transformers 提供了一个实用程序来获取我们需要的关于您的环境的所有信息。只需在终端中输入以下内容: +🤗 Transformers 提供了一个实用程序来获取有关于你环境的所有信息。只需在终端中输入以下内容: ``` transformers-cli env ``` -你应该得到这样的东西: +将得到以下输出: ```out Copy-and-paste the text below in your GitHub issue and FILL OUT the two last points. @@ -57,29 +58,34 @@ Copy-and-paste the text below in your GitHub issue and FILL OUT the two last poi - Using distributed or parallel set-up in script?: ``` -您还可以添加一个 **!** 在开始的时候 **transformers-cli env** 命令从笔记本单元执行它,然后在问题的开头复制并粘贴结果。 +如果你在 notebook 单元执行它,你还需要在 `transformers-cli env` 命令开始前添加一个 `!` ,然后把结果复制到你问题帖子的开头。 -### 标记人员 [[标记人员]] +### 标记相关人员 [[标记相关人员]] -通过输入标记人员 **@** 其次是他们的 GitHub 句柄将向他们发送通知,以便他们会看到您的问题并可能会更快地回复。适度使用它,因为如果您标记的人没有直接链接,他们可能不喜欢收到通知。如果您查看了与您的错误相关的源文件,您应该在您认为对您的问题负责的行中标记最后一个进行更改的人(您可以通过查看 GitHub 上的所述行找到此信息,选择它,然后单击“查看 git blame”)。 +使用 `@` 后跟上 GitHub 用户名来标记他人,可以向他们发送通知,这样他们就会看到你的问题来尽可能更快地回复你。如果你标记的人与你的问题没有直接联系请谨慎使用,因为他们可能不喜欢收到通知。如果你查看了与你的错误相关的源文件,就应该标记上一次对你认为造成问题的行进行修改的人(可以在 GitHub 上查看该行,选择它然后点击 “View git blame”来找到这些信息)。 -否则,模板会提供要标记的人的建议。一般来说,不要标记超过三个人! +如果没有进行标记,那么我们的模板会自动提供要标记的人的建议。一般不要标记超过三个人。 -### 包含一格可重复的示例 [[包含一格可重复的示例]] +### 包含一个可重复的示例 [[包含一个可重复的示例]] -如果您已经设法创建了一个产生错误的独立示例,那么现在是包含它的时候了!键入一行包含三个反引号,后跟 **python** , 像这样: +如果你已经创建了一个产生错误的独立示例,请键入一行包含三个反引号,后跟 `python` ,像这样: ``` -```python +```python ``` -然后粘贴您的最小可重现示例并键入一个带有三个反引号的新行。这将确保您的代码格式正确。如果您没有设法创建可重现的示例,请以清晰的步骤解释您是如何解决问题的。如果可以,请包含指向错误所在的 Google Colab 笔记本的链接。你分享的信息越多,维护者就越有能力回复你。在所有情况下,您都应该复制并粘贴您收到的整个错误消息。如果您在 Colab 中工作,请记住,堆栈跟踪中的某些帧可能会自动折叠,因此请确保在复制之前展开它们。与代码示例一样,将该错误消息放在两行之间,并带有三个反引号,因此格式正确。 +然后粘贴你的最小可复现示例,并在新的一行上输入三个反引号。这将就会将代码格式化并且高亮以提高代码的可读性。 + +如果你没有成功创建一个可复现的示例,清楚地描述你遇到问题的步骤。如果可以的话,包括一个你遇到错误的 Google Colab 笔记本的链接。你分享的信息越多,维护者就更有可能回复你。 + +解决问题的线索可能在错误信息的不同位置,因此在任何情况下都应该复制并粘贴收到的整个错误消息。如果在 Google Colab 中工作,则要记住堆栈跟踪可能会自动折叠,因此请确保在复制之前将其展开。与代码示例一样,将该错误消息放在带有三个反引号的两行之间。 ### 描述预期行为 [[描述预期行为]] -用几行解释你期望得到什么,以便维护人员完全掌握问题。这部分通常很明显,所以应该用一句话来形容,但在某些情况下,您可能有很多话要说。 +用几句话描述代码正确运行时预期的效果,有助于维护者完全理解问题。预期的效果在解决错误问题的帖子中通常会很明显,可以用一句话来概括,但在某些情况下可能需要描述很多内容。 -## 然后什么? [[然后什么?]] +## 提交?[[提交?]] -提交您的问题后,请确保快速检查一切是否正常。如果您犯了错误,您可以编辑问题,或者如果您发现问题与您最初的想法不同,甚至可以更改其标题。如果你没有得到答案,就没有必要对人进行 ping 操作。如果几天内没有人帮助您,很可能没有人能理解您的问题。不要犹豫,回到可重现的例子。你能不能让它更短更切题?如果您在一周内没有得到答复,您可以留言温和地寻求帮助,特别是如果您已编辑问题以包含有关该问题的更多信息。 +在提交问题后,请快速检查一遍提交的帖子是否是正确的。如果出现错误,你可以重新编辑问题,如果发现问题与最初的想法不同,甚至可以更改标题。 +即使没有很快得到答案,也不要很急切地去提醒别人。如果几天内没有人提供帮助,那么很可能没有人能理解我们的问题,不要犹豫,回到可复现的示例。修改一下,让它更加简洁明了。如果在一周内没有得到答复,那么可以温和地留言寻求帮助,特别是你已经修改过,添加了更多有效信息的问题。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter8/6.mdx b/chapters/zh-CN/chapter8/6.mdx index f33712406..4e85d8cb6 100644 --- a/chapters/zh-CN/chapter8/6.mdx +++ b/chapters/zh-CN/chapter8/6.mdx @@ -1,7 +1,5 @@ -# 第2部分完成! [[第2部分完成!]] +# 第 2 部分完成![[第2部分完成!]] -恭喜,您已经完成了课程的第二部分!我们正在积极研究第三个,所以订阅我们的[newsletter](https://huggingface.curated.co/)以确保您不会错过它的发布。 +恭喜,你已经完成了课程的第二部分!你现在应该能够处理一系列 NLP 任务,并对它们进行微调或预训练模型。不要忘记在 [Model Hub](https://huggingface.co/models) 和社区分享你的结果。 -。您现在应该能够处理一系列 NLP 任务,并对它们进行微调或预训练模型。不要忘记与社区分享您的结果[Model Hub](https://huggingface.co/models). - -我们迫不及待地想看看您将利用所获得的知识构建什么! +我们迫不及待地想看到你利用所学知识创造出什么样的作品! diff --git a/chapters/zh-CN/chapter8/7.mdx b/chapters/zh-CN/chapter8/7.mdx index 9bfb36bc0..ffa679375 100644 --- a/chapters/zh-CN/chapter8/7.mdx +++ b/chapters/zh-CN/chapter8/7.mdx @@ -2,47 +2,47 @@ # 章末小测验 [[章末小测验]] -让我们测试一下你在本章学到的东西! +让我们测试一下你在本章学到的东西! -### 1.应该按照什么顺序读取 Python 回溯? +### 1. 应该按照什么顺序读取 Python 回溯? -### 2.什么是最小可再生示例? +### 2. 什么是最小可复现示例? -### 3.假设你尝试运行以下代码,它抛出一个错误: +### 3. 假设你尝试运行以下代码,它抛出一个错误: ```py from transformers import GPT3ForSequenceClassification @@ -55,60 +55,60 @@ from transformers import GPT3ForSequenceClassification # ImportError: cannot import name 'GPT3ForSequenceClassification' from 'transformers' (/Users/lewtun/miniconda3/envs/huggingface/lib/python3.8/site-packages/transformers/__init__.py) ``` -以下哪项可能是论坛主题标题寻求帮助的好选择? +以下哪项可能是有助于寻求帮助的论坛帖子的标题呢? GPT3ForSequenceClassification ?", - explain: "不错的选择!这个标题是简洁的,并给读者一个线索,什么可能是错误的(即,gpt-3不支持在🤗 Transformers)。", + text: "为什么我不能导入 GPT3ForSequenceClassification ?", + explain: "不错的选择!这个标题是简洁的,并给读者一个线索,什么可能是错误的(即🤗 Transformers 不支持 GPT-3)。", correct: true }, { - text: "Gpt-3在🤗 Transformers中支持吗?", + text: "🤗 Transformers 支持 GPT-3吗?", explain: "好主意! 用问题作为主题标题是向社区传达问题的好方法。", correct: true } ]} /> -### 4.假设您试图运行 'trainer.train ()',但是遇到了一个神秘的错误,这个错误不能准确地告诉您错误来自哪里。下列哪一项是您应该首先在您的培训管道中寻找错误的地方? +### 4. 假设你试图运行 `trainer.train ()`,但是遇到了一个错误,这个错误不能准确地告诉你错误来自哪里。下列哪一项是你应该首先在你的训练管道中寻找错误的地方? -### 5.调试 CUDA 错误的最好方法是什么? +### 5. 调试 CUDA 错误的最好方法是什么? -### 6.修复 GitHub 上的问题最好的方法是什么? +### 6. 修复 GitHub 上的问题最好的方法是什么? -### 7.为什么对一个批处理进行过度调试通常是一种好的调试技术? +### 7. 为什么对一个 batch 进行过拟合通常是一种好的调试技术? -### 8.为什么在 🤗 Transformers 存储库中创建新问题时,使用 transformers-cli env 包含有关计算环境的详细信息是个好主意? +### 8. 为什么在 🤗 Transformers 存储库中创建新问题时,使用 transformers-cli env 包含有关计算环境的详细信息是个好主意? -* 一个抽取式**问题回答**模型,它接受上下文段落和一个任务并输出一个结果和一个概率分数(我们在[第7章](/course/chapter7/7)中讨论了这种模型): +* 一个**抽取式问答**模型,它可以输入一段材料和一个问题,输出从这段话中找到的问题的答案和一个概率分数(我们在 [第七章](/course/chapter7/7) 中讨论过这种模型): -* 一个**背景去除**模型,它接收图像并输出去除背景的图像: +* 一个**背景去除**模型,它可以输入一张图像,并返回去除背景的图像: -本章分为两个部分,包括_概念_和_应用程序_。在您了解每个部分的概念后,您将应用它来构建特定类型的演示,范围从图像分类到语音识别。当你读完本章时,你将能够用几行 Python 代码构建这些演示(以及更多!)。 +本章分为两个部分,包括 `基础概念` 和 `应用示例` 。在了解每个部分的概念后,你可以使用它来构建不同类型的演示,从图像分类到语言模型再到到语音识别。当你读完本章时,你就能够仅用几行 Python 代码构建这些演示并且可以根据需要定制和修改Web演示。 -👀 点击 Hugging Face Spaces 以查看机器学习社区构建的许多机器学习演示的最新示例! +👀 点击 [Hugging Face Spaces](https://huggingface.co/spaces) 就可以查看机器学习社区构建最新的许多机器学习演示示例! diff --git a/chapters/zh-CN/chapter9/2.mdx b/chapters/zh-CN/chapter9/2.mdx index 9910c483c..4b9a44bb1 100644 --- a/chapters/zh-CN/chapter9/2.mdx +++ b/chapters/zh-CN/chapter9/2.mdx @@ -7,14 +7,18 @@ {label: "Aws Studio", value: "https://studiolab.sagemaker.aws/import/github/huggingface/notebooks/blob/master/course/chapter9/section2.ipynb"}, ]} /> -让我们从安装 Gradio 开始吧! 由于它是一个 Python 包,只需运行: +让我们从安装 Gradio 开始吧!与安装其他 Python 包一样,只需运行: -`$ pip install gradio ` +```bash +pip install gradio + +``` + +Gradio 支持所有常见的平台,无论是从你最喜欢的 Python IDE、Jupyter notebook 还是 Google Colab 🤯! -您可以在任何地方运行 Gradio,无论是从您最喜欢的 Python IDE、Jupyter notebook 还是 Google Colab 🤯! 所以无论你在哪里运行 Python,都可以安装 Gradio! -让我们从一个简单的“Hello World”示例开始,熟悉 Gradio 语法: +让我们从一个简单的“Hello World”示例开始熟悉 Gradio 语法: ```py import gradio as gr @@ -29,19 +33,19 @@ demo = gr.Interface(fn=greet, inputs="text", outputs="text") demo.launch() ``` -让我们看一下上面的代码: +让我们逐步解释上面的代码: --首先,我们定义一个名为 `greet()` 的函数。 在这种情况下,它是一个在您的名字前添加“Hello”的简单函数,但它通常可以是 *any* Python 函数。 例如,在机器学习应用程序中,此函数将*调用模型以对输入进行预测*并返回输出。 -- 然后,我们创建一个带有三个参数的 Gradio `Interface`,`fn`、`inputs` 和 `outputs`。 这些参数定义了预测函数,以及我们想要的输入和输出组件的_type_。 在我们的例子中,两个组件都是简单的文本框。 -- 然后我们在我们创建的 `Interface` 上调用 `launch()` 方法。 +- 首先,我们定义一个名为 `greet()` 的函数。在这个例子中,它是一个在你的名字前添加“Hello”的简单函数,不过它可以被替换为任意的 Python 函数。例如,在机器学习的演示中,该函数可以调用机器学习模型对输入进行预测并返回输出。 +- 然后,我们创建一个带有三个参数 `fn` 、 `inputs` 和 `outputs` 的 Gradio `Interface` 。这些参数定义了预测函数,以及我们想要的输入和输出组件的类型。在我们的这个例子中,输入和输出组件都是简单的文本框。 +- 最后我们调用创建的 `Interface` 上的 `launch()` 方法。 -如果你运行这段代码,下面的界面会自动出现在 Jupyter/Colab notebook 中,或者在浏览器中弹出 **[http://localhost:7860](http://localhost:7860/)** 如果运行 从一个脚本。 +如果你运行这段代码,下面的界面会自动出现在 Jupyter/Colab notebook 中,或者如果在一个脚本中运行,会自动在浏览器中弹出 ** [http://localhost:7860](http://localhost:7860) ** 。 -立即尝试使用您自己的姓名或其他输入来使用此 GUI! +现在,试着使用你自己的名字或其他的输入来试一试这个 GUI! -您会注意到,在这个 GUI 中,Gradio 自动推断输入参数的名称 (`name`)并将其应用为文本框顶部的标签。 如果你想改变它怎么办?或者,如果您想以其他方式自定义文本框? 在这种情况下,您可以实例化一个表示输入组件的类对象。 +你会注意到,在这个 GUI 中,在这个例子中,Gradio 会根据输入参数的名称(`name`)自动推断文本框顶部的标签。如果你想改变这个标签怎么办?或者,如果你想自定义这个文本框的其他属性?在这种情况下,你可以实例化一个表示输入组件的类。 看看下面的例子: @@ -53,7 +57,7 @@ def greet(name): return "Hello " + name -# We instantiate the Textbox class +# 我们将文本框类实例化 textbox = gr.Textbox(label="Type your name here:", placeholder="John Doe", lines=2) gr.Interface(fn=greet, inputs=textbox, outputs="text").launch() @@ -61,19 +65,18 @@ gr.Interface(fn=greet, inputs=textbox, outputs="text").launch() -在这里,我们创建了一个带有标签、占位符和一组行数的输入文本框。您可以对输出文本框执行相同的操作,但我们现在将其保留。 - -我们已经看到,只需几行代码,Gradio 就可以让您围绕任何具有任何类型输入或输出的函数创建一个简单的界面。 在本节中,我们从一个简单的文本框开始,但在接下来的部分中,我们将介绍其他类型的输入和输出。 现在让我们看看在 Gradio 应用程序中包含一些 NLP。 +在上面的代码中,我们创建了一个带有自定义标签、预设的占位符和固定行数的输入文本框。同时,你也可以自定义输出文本框,但我们现在先不管它。 +我们已经看到,只需几行代码,Gradio 就能让你围绕任何函数创建一个简单的界面,同时兼容各种类型的输入或输出。在本节中,我们先从一个简单的文本框开始,但在接下来的节中,我们将介绍其他类型的输入和输出。现在让我们看一下如何在 Gradio 应用中包含一些自然语言处理(NLP)模型。 -## 🤖 包括模型预测 [[🤖 包括模型预测]] +## 🤖 使用模型预测 [[🤖 使用模型预测]] -现在让我们构建一个简单的界面,让您可以演示像 GPT-2 这样的**文本生成**模型。 +现在让我们构建一个简单的界面,让你可以在这个界面中演示像 GPT-2 这样的**文本生成**模型。 我们将使用 🤗 Transformers 中的 `pipeline()` 函数加载我们的模型。 -如果您需要快速复习,您可以返回到 [第 1 章中的那个部分](/course/chapter1/3#text-generation)。 +如果你需要快速复习,你可以返回到 [第一章](/course/chapter1/3#text-generation) 进行回顾。 -首先,我们定义一个接受文本提示并返回文本完成的预测函数: +首先,我们定义一个预测函数,它接收一个文本输入并返回文本生成的结果: ```py from transformers import pipeline @@ -86,7 +89,7 @@ def predict(prompt): return completion ``` -此函数完成您提供的提示,您可以使用自己的输入提示运行它以查看它是如何工作的。 这是一个示例(您可能会得到不同的完成): +此函数会不断接龙补全输入,你可以自己尝试各种输入来查看它是如何运行的。下面是一个示例(你可能会得到不同的输出结果): ``` predict("My favorite programming language is") @@ -96,7 +99,7 @@ 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 @@ -105,8 +108,8 @@ gr.Interface(fn=predict, inputs="text", outputs="text").launch() ``` -就是这样! 您现在可以使用此接口使用 GPT-2 模型生成文本,如下所示 🤯. +就是这样!你现在可以在 Interface 创建的 Web 网页上使用 GPT-2 模型生成文本,如下所示 🤯: -继续阅读以了解如何使用 Gradio 构建其他类型的演示! \ No newline at end of file +接下来,让我们一起了解一下如何使用 Gradio 构建其他输入输出类型的演示! \ No newline at end of file diff --git a/chapters/zh-CN/chapter9/3.mdx b/chapters/zh-CN/chapter9/3.mdx index 90ccb717b..9b4bda890 100644 --- a/chapters/zh-CN/chapter9/3.mdx +++ b/chapters/zh-CN/chapter9/3.mdx @@ -1,4 +1,4 @@ -# 了解接口类 [[了解接口类]] +# 了解 Interface 类 [[了解 Interface 类]] -在本节中,我们将仔细研究 `Interface` 类,并了解用于创建其的主要参数。 +在这一节中,我们将更详细地了解 `Interface` 类,并理解创建 Interface 时使用的主要参数的含义和设置方法。 -## 如何创建接口 [[如何创建接口]] +## 如何创建 Interface [[如何创建 Interface]] -您会注意到 `Interface` 类有 3 个必需参数: +你会注意到 `Interface` 类有 3 个必需参数: `Interface(fn, inputs, outputs, ...)` -`Interface(fn, inputs, outputs, ...)` +这些参数的含义是: -这些参数是: +- `fn` :由 Gradio 接口包装的预测函数。该函数可以接受一个或多个参数并返回一个或多个值 +- `inputs` :输入组件类型。Gradio 提供了许多预构建的组件,例如 `image` 或 `mic` 。 +- `outputs` :输出组件类型。同样,Gradio 提供了许多预构建的组件,例如 `image` 或 `label` 。 - - `fn`: 由 Gradio 接口包装的预测函数。 该函数可以接受一个或多个参数并返回一个或多个值 - - `inputs`: 输入组件类型。 Gradio 提供了许多预构建的组件,例如`"image"` 或`"mic"`。 - - `outputs`: 输出组件类型。 同样,Gradio 提供了许多预构建的组件,例如 `“图像”`或“标签”`。 +可以使用组件的完整列表请参阅 [Gradio 文档](https://gradio.app/docs) 。每个预构建的组件都可以通过实例化该组件对应的类来定制。 -有关组件的完整列表,[请参阅 Gradio 文档](https://gradio.app/docs)。 每个预构建的组件都可以通过实例化该组件对应的类来定制。 +例如,正如我们在 [前一小节](/course/chapter9/2) 中看到的,你可以将一个 `Textbox(lines=7, label="Prompt")` 组件传递给 `inputs` 参数,而不是将 `"textbox"` 以字符串形式传递进去,这样就可以创建一个 7 行并包含一个标签的文本框。 -例如,正如我们在 [上一节](/course/chapter9/2) 中看到的,您可以传入一个 `Textbox(lines=7, label="Prompt")` 组件来创建一个包含 7 行和一个标签的文本框,而不是将 `"textbox"` 传递给 `inputs` 参数。 -让我们看另一个例子,这次是一个 `Audio` 组件。 +让我们看另一个例子,这个例子使用了 `Audio` 组件。 -## 一个带音频的简单示例 [[一个带音频的简单示例]] +## 一个音频组件的简单示例 -如前所述,Gradio 提供了许多不同的输入和输出。 -因此,让我们构建一个适用于音频的“接口”。 +如前所述,Gradio 提供了许多不同的输入和输出组件。因此,让我们构建一个适用于音频的 `Interface` 。 -在这个例子中,我们将构建一个音频到音频的函数,它需要一个音频文件并简单地反转它。 +在这个例子中,我们将构建一个输入和输出都是音频的函数,它可以接收一个音频文件后将其反转并返回。 -我们将使用 `Audio` 组件作为输入。 使用 `Audio` 组件时,您可以指定希望音频的 `source` 是用户上传的文件还是用户录制声音的麦克风。 在这种情况下,让我们将其设置为“麦克风”。 只是为了好玩,我们会在我们的“音频”中添加一个标签,上面写着“在这里说话……”。 +我们将使用 `Audio` 组件作为输入。使用 `Audio` 组件时,你可以通过 `source` 指定输入音频的方式是上传的音频文件还是通过麦克风实时录制的声音。在这个例子中,让我们将其设置为“麦克风”。为了让交互更加友好,我们会在我们的 `Audio` 上添加一个标签,上面写着“Speak here.”。 -此外,我们希望将音频作为 numpy 数组接收,以便我们可以轻松地“反转”它。 所以我们将 `"type"` 设置为 `"numpy"`,它会传递输入data 作为 (`sample_rate`, `data`) 的元组进入我们的函数。 - -我们还将使用 `Audio` 输出组件,它可以自动将具有采样率和 numpy 数据数组的元组渲染为可播放的音频文件。 在这种情况下,我们不需要进行任何自定义,因此我们将使用字符串快捷方式“audio”。 +此外,我们希望函数接收的音频是 `numpy` 数组格式,这样我们可以轻松地“反转”它。因此我们将 `"type"` 设置为 `"numpy"` ,它会将传递输入 data 转换为 `(sample_rate,data)` 的元组输入到我们的函数。 +我们还将使用 `Audio` 作为输出组件,它可以自动将根据采样率和音频数据将 numpy 数组渲染为可播放的音频文件。因此,在这个例子中不需要对输出组件进行修改,只需要传递一个 `"audio"` 字符串。 ```py import numpy as np @@ -55,24 +52,24 @@ mic = gr.Audio(source="microphone", type="numpy", label="Speak here...") gr.Interface(reverse_audio, mic, "audio").launch() ``` -上面的代码会产生一个类似下面的界面(如果你的浏览器没有 -询问您的麦克风权限, open the demo in a separate tab.) +上面的代码将生成一个如下所示的界面(如果你的浏览器没有要求你授权麦克风权限,请在 新标签页中打开演示。) -您现在应该能够录制自己的声音并听到自己在反向说话 - 怪异 👻! +现在你可以录制你的声音并听到倒放的音频了 - 太神奇了 👻! + +## 处理多个输入和输出 -## 处理多个输入和输出 [[处理多个输入和输出]] +假设我们有一个更复杂的预测函数,有多个输入和输出。在下面的示例中,我我们有一个函数,它接收一个下拉框索引、一个滑块值和一个数字,并返回一个特定音调的音频样本。 -假设我们有一个更复杂的函数,有多个输入和输出。在下面的示例中,我们有一个接受下拉索引、滑块值和数字的函数,并返回一个音调的音频样本。 +让我们看看该如何传递输入和输出组件列表,看看你能不能理解他们。 -看看我们如何传递输入和输出组件列表,看看你能不能跟上正在发生的事情。 +关键要传递: -这里的关键是当你通过时: * 输入组件列表,每个组件依次对应一个参数。 * 输出组件列表,每个组件对应一个返回值。 -下面的代码片段显示了三个输入组件如何与 `generate_tone()` 函数的三个参数对齐: +下面的代码片段显示了三个输入组件如何与 `generate_tone()` 函数的三个参数一一对齐: ```py import numpy as np @@ -104,28 +101,25 @@ gr.Interface( +### `launch()` 方法 -### `launch()` 方法 [[`launch()` 方法]] +到目前为止,我们已经使用了 `launch()` 方法来启动界面,但是我们还没有真正讨论过它的作用。 -到目前为止,我们已经使用了`launch()`方法来启动界面,但是我们 -还没有真正讨论过它的作用。 +默认情况下, `launch()` 方法将在本地运行一个 Web 服务器来启动演示。如果你在 Jupyter 或 Colab Notebook 中运行代码,那么 Gradio 会将演示 GUI 嵌入到 Notebook 中,以便你可以轻松使用它。 -默认情况下,`launch()` 方法将在 Web 服务器中启动演示正在本地运行。 如果您在 Jupyter 或 Colab 笔记本中运行代码,那么Gradio 会将演示 GUI 嵌入到笔记本中,以便您轻松使用它。 +你可以通过不同的参数自定义 `launch()` 的行为: -您可以通过不同的参数自定义 `launch()` 的行为: - - - `inline` - whether to display the interface inline on Python notebooks. - - `inbrowser` - whether to automatically launch the interface in a new tab on the default browser. - - `share` - whether to create a publicly shareable link from your computer for the interface. Kind of like a Google Drive link! + - `inline` —— 是否在 Python Notebook 中内联显示接口。 + - `inbrowser` —— 是否在默认浏览器的新标签页中自动打开演示页面。 + - `share` —— 是否在你的计算机上创建一个公开可共享的链接。有点像 Google Drive 的链接! 我们将在下一节中更详细地介绍 `share` 参数! -## ✏️ 让我们应用它! [[✏️ 让我们应用它!]] +## ✏️ 让我们实践一下! -让我们构建一个界面,让您演示 **speech-recognition** 模型。 -为了让它变得有趣,我们将接受 *or* 麦克风输入或上传的文件。 +让我们构建一个演示语音识别模型的演示。为了让它变得有趣,我们将同时支持从麦克风实时录制或上传的文件。 -像往常一样,我们将使用 🤗 Transformers 中的 `pipeline()` 函数加载我们的语音识别模型。如果您需要快速复习,您可以返回 [第 1 章中的那个部分](/course/chapter1/3)。 接下来,我们将实现一个 `transcribe_audio()` 函数来处理音频并返回转录。 最后,我们将把这个函数包装在一个 `Interface` 中,其中 `Audio` 组件用于输入,只有文本用于输出。 总而言之,此应用程序的代码如下: +像往常一样,我们将使用 🤗 Transformers 中的 `pipeline()` 函数加载我们的语音识别模型。如果你需要快速复习回顾,你可以返回第一章。接下来,我们将实现一个 `transcribe_audio()` 函数来处理音频并返回转录后的文本。最后,我们将把这个函数包装在一个 `Interface` 中,将输入的类型设置为 `Audio` 组件,将输出的类型设置为文本。汇总起来,这个演示的代码如下: ```py from transformers import pipeline @@ -155,13 +149,10 @@ gr.Interface( ).launch() ``` -如果您的浏览器没有要求您提供麦克风权限,open the demo in a separate tab. +如果你的浏览器没有要求你授权麦克风权限,请在新标签页中打开演示 +就是这样!现在你可以使用这个界面来转录音频了。请注意,在这个例子中我们将 `optional` 参数设置为了 `True` ,这样用户可以提供麦克风的实时录音或音频文件中任意一种作为输入(或两者都不提供,但这将返回错误消息)。 -就是这样! 您现在可以使用此界面来转录音频。 注意这里 -通过将 `optional` 参数作为 `True` 传递,我们允许用户 -提供麦克风或音频文件(或两者都不提供,但这会返回错误消息)。 - -继续看看如何与他人分享您的界面! \ No newline at end of file +接下来,我们将学习如何与他人分享你的演示! diff --git a/chapters/zh-CN/chapter9/4.mdx b/chapters/zh-CN/chapter9/4.mdx index 676084ce1..e7e5a8315 100644 --- a/chapters/zh-CN/chapter9/4.mdx +++ b/chapters/zh-CN/chapter9/4.mdx @@ -1,5 +1,6 @@ # 与他人分享演示 [[与他人分享演示]] + -现在您已经构建了一个演示,您可能希望与其他人分享它。 梯度演示 -可以通过两种方式共享:使用 ***temporary share link*** 或 ***permanent hosting on Spaces***。 +现在你已经构建了一个演示,你可能希望将其分享给你的同事或者朋友一起体验一下。Gradio 演示可以通过两种方式进行分享:使用 **临时的共享链接** 或者 **在 Spaces 上永久托管**。 -我们将很快介绍这两种方法。 但在分享演示之前,您可能需要完善它 💅. +我们将稍后快速介绍这两种方法。但在分享演示之前,你可能需要完善它 💅。 -### 打磨你的 Gradio 演示: [[打磨你的 Gradio 演示:]] +### 打磨你的 Gradio 演示:[[打磨你的 Gradio 演示:]]
-Overview of a gradio interface - +Overview of a gradio interface +
-为了给你的演示添加额外的内容,`Interface` 类支持一些可选参数: - - `title`:你可以给你的演示一个标题,它出现在输入和输出组件的上方。 - - `description`:您可以为界面提供描述(文本、Markdown 或 HTML),显示在输入和输出组件的上方和标题下方。 - - `article`:您还可以编写扩展文章(文本、Markdown 或 HTML)来解释界面。如果提供,它会出现在输入和输出组件的_下方。 - - `theme`:不喜欢默认颜色?将主题设置为使用 `default`、`huggingface`、`grass`、`peach` 之一。您还可以添加 `dark-` 前缀,例如`dark-peach` 用于深色主题(或者只是 `dark` 用于默认的深色主题)。 - - `examples`:为了让您的演示*更易于使用*,您可以为函数提供一些示例输入。它们出现在 UI 组件下方,可用于填充界面。这些应该作为嵌套列表提供,其中外部列表​​由样本组成,每个内部列表对应于每个输入组件的输入组成。 - - `live`:如果你想让你的演示“活”,这意味着你的模型每次输入更改时都会重新运行,你可以设置 `live=True`。这对使用快速模型很有意义(我们将在本节末尾看到一个示例) -使用上面的选项,我们最终得到了一个更完整的界面。 运行下面的代码,以便与 Rick and Morty 聊天: +为了使演示使用起来更清晰, `Interface` 类支持一些可选参数: + - `title` :你可以给你的演示创建一个标题,它将显示在输入和输出组件的上方。 + - `description` :你可以为界面提供描述(以文本、Markdown 或 HTML 形式),显示在输入和输出组件的上方和标题下方。 + - `article` :你还可以编写扩展的文章(以文本、Markdown 或 HTML 形式)来解释界面。如果提供,它会出现在输入和输出组件的下方。 + - `theme` :如果不喜欢默认颜色?你可以将主题可以设置为 `default` 、 `huggingface` 、 `grass` 、 `peach` 之一。同时你还可以添加 `dark-` 前缀,例如 `dark-peach` 用于深色主题(或者仅使用 `dark` 表示默认的深色主题)。 + - `examples` :为了让你的演示更易于使用,你可以为函数提供一些示例输入。它们出现在 UI 组件下方,点击后可以自动填充到输入和输出。示例的格式应该是多层的列表,外层列表包含每个示例,每个内层的列表包含与每个输入组件对应的输入。 + - `live` :如果你想使你的演示“实时”反馈,即每次输入更改时自动重新运行模型,你可以设置 `live=True` 。这对运行比较快的模型很有意义(我们将在本节末尾看到一个示例) + +完善上述选项后,我们就得到了一个更完整的界面。运行以下代码,你可以与 Rick 和 Morty 进行对话: ```py title = "Ask Rick a Question" @@ -48,37 +49,36 @@ gr.Interface( ).launch() ``` -使用上面的选项,我们最终得到了一个更完整的界面。 试试下面的界面: +使用上面的选项,我们最终得到了一个更完整的界面。试试下面的界面: -### 使用临时链接分享您的演示 [[使用临时链接分享您的演示]] -现在我们已经有了机器学习模型的工作演示,让我们学习如何轻松共享指向我们界面的链接。 -通过在 `launch()` 方法中设置 `share=True` 可以轻松地公开共享接口: +### 使用临时链接分享你的演示 [[使用临时链接分享你的演示]] + +现在我们已经有了机器学习模型的工作演示,接下来将学习如何轻松地通过链接将演示共享给其他人。 +通过在 `launch()` 方法中设置 `share=True` 可以就轻松地公开共享接口: ```python gr.Interface(classify_image, "image", "label").launch(share=True) ``` -这会生成一个公开的、可共享的链接,您可以将其发送给任何人! 当您发送此链接时,另一方的用户可以在浏览器中试用该模型长达 72 小时。 因为处理发生在您的设备上(只要您的设备保持开启!),您不必担心打包任何依赖项。 如果您使用 Google Colab 笔记本工作,则始终会自动创建共享链接。 它通常看起来像这样:**XXXXX.gradio.app**。 虽然链接是通过 Gradio 链接提供的,但我们只是您本地服务器的代理,不会存储通过接口发送的任何数据。 +运行上面的代码会生成一个公开的、可共享的链接,你可以将其发送给任何人!得到此链接的用户可以在浏览器中试用该模型,时间长达 72 小时,时间长达 72 小时。因为处理发生在运行代码的设备上(设备需保持开启),所以无须打包任何依赖项。如果你使用 Google Colab 笔记本工作,则默认会自动创建共享链接。它通常看起来像这样:**XXXXX.gradio.app**。虽然链接是通过 Gradio 链接提供的,但 Gradio 只是提供了一个本地服务器的代理,不会存储通过接口发送的任何数据。 -但是请记住,这些链接是可公开访问的,这意味着任何人都可以使用您的模型进行预测! 因此,请确保不要通过您编写的函数公开任何敏感信息,或允许在您的设备上发生任何关键更改。 如果设置 `share=False`(默认值),则仅创建本地链接。 +但是请记住,这些链接是可公开访问的,这意味着任何人都可以使用你的模型进行预测!因此,请确保不要使用你编写的函数公开任何敏感信息,也不要允许其中的代码对你的设备进行任何重要更改。如果设置 `share=False` (默认值就是False),则仅创建本地链接。 -### 在 Hugging Face Spaces 上托管您的演示 [[在 Hugging Face Spaces 上托管您的演示]] +### 在 Hugging Face Spaces 上托管你的演示 [[在 Hugging Face Spaces 上托管你的演示]] -可以传递给同事的共享链接很酷,但是如何永久托管您的演示并让它存在于互联网上自己的“空间”中? +可以传递给同事的共享链接很酷,但是如何在互联网上永久托管你的演示并使其具有自己的“space”呢? -Hugging Face Spaces 提供了在互联网上永久托管 Gradio 模型的基础设施,**免费**! Spaces 允许您创建并推送到(公共或私人)存储库, -你的 Gradio 在哪里 -接口代码将存在于 `app.py` 文件中。 [阅读分步教程](https://huggingface.co/blog/gradio-spaces) 开始使用,或观看下面的示例视频。 +Hugging Face Spaces 提供了在互联网上永久托管你的 Gradio 模型的基础设施, 并且 **完全免费** !你可以将你创建 Space 推送到(公共或私有)仓库,在其中,你的 Gradio 界面代码需要存储在一个 `app.py` 文件中。 请 [阅读分步教程](https://huggingface.co/blog/gradio-spaces) 开始使用 Hugging Face Spaces ,或观看下面的示例视频。 -## ✏️ 让我们应用它! [[✏️ 让我们应用它!]] +## ✏️ 让我们实现它![[✏️ 让我们实现它!]] -使用到目前为止我们在各节中学到的知识,让我们创建我们在[本章第一节](/course/chapter9/1)中看到的草图识别演示。 让我们为我们的界面添加一些自定义并设置 `share=True` 以创建一个我们可以传递的公共链接。 +现在,让我们使用到目前为止我们在各节中学到的知识,尝试创建我们在 [本章第一节](/course/chapter9/1) 中看到的草图识别演示。我们会在界面中添加一些自定义配置并设置 `share=True` 以创建一个可以与其他人共享的链接。 -我们可以从 [class_names.txt](https://huggingface.co/spaces/dawood/Sketch-Recognition/blob/main/class_names.txt) 加载标签,并从 [pytorch_model.bin](https://huggingface.co/spaces/dawood/Sketch-Recognition/blob/main/pytorch_model.bin)加载预训练的 pytorch 模型 。 通过点击链接并单击文件预览左上角的下载来下载这些文件。 让我们看看下面的代码,看看我们如何使用这些文件来加载我们的模型并创建一个`predict()`函数: +首先,我们可以从 [class_names.txt](https://huggingface.co/spaces/dawood/Sketch-Recognition/blob/main/class_names.txt) 加载标签,并从 [pytorch_model.bin](https://huggingface.co/spaces/dawood/Sketch-Recognition/blob/main/pytorch_model.bin) 加载预训练的 pytorch 模型。通过点击链接并点击文件预览左上角的下载来下载这些文件。然后使用下面的代码加载模型并创建一个 `predict()` 函数: ```py from pathlib import Path import torch @@ -116,7 +116,7 @@ def predict(im): return {LABELS[i]: v.item() for i, v in zip(indices, values)} ``` -现在我们有了一个`predict()`函数。 下一步是定义并启动我们的渐变界面: +现在我们有了一个 `predict()` 函数。下一步是设置并启动我们的 gradio 界面: ```py interface = gr.Interface( @@ -135,10 +135,8 @@ interface.launch(share=True) -注意 `Interface` 中的 `live=True` 参数,这意味着草图演示使 -每次有人在画板上画画时的预测(没有提交按钮!)。 +注意 `Interface` 中设置了 `live=True` 参数,这意味着每当有人在画板上绘制时,草图演示都会实时进行预测(无需点击提交按钮!)。 -此外,我们还在 `launch()` 方法中设置了 `share=True` 参数。 -这将创建一个公共链接,您可以发送给任何人! 当您发送此链接时,对方的用户可以尝试草图识别模型。 重申一下,您还可以在 Hugging Face Spaces 上托管模型,这就是我们能够嵌入上面的演示的方式。 +此外,我们还在 `launch()` 方法中设置了 `share=True` 参数。这将创建一个公共链接,你可以发送给任何人!当你发送此链接时,对方可以尝试草图识别模型。重申一下,你还可以在 Hugging Face Spaces 上托管模型,这也是本教程中我们在网页上展示演示的方式。 -接下来,我们将介绍 Gradio 可用于 Hugging Face 生态系统的其他方式! \ No newline at end of file +接下来,我们将学习将 Gradio 与 Hugging Face 生态系统结合的其他用法! \ No newline at end of file diff --git a/chapters/zh-CN/chapter9/5.mdx b/chapters/zh-CN/chapter9/5.mdx index 77200b830..60f3ef4ce 100644 --- a/chapters/zh-CN/chapter9/5.mdx +++ b/chapters/zh-CN/chapter9/5.mdx @@ -1,5 +1,6 @@ # 与 Hugging Face Hub 整合 [[与 Hugging Face Hub 整合]] + -为了让你的生活更轻松, Gradio 直接与 Hugging Face Hub 和 Hugging Face Spaces 集成。你可以仅使用 *一行代码* 从中心和空间加载演示。 +如果觉得本地加载模型有些麻烦,为了让使用模型更轻松,Gradio 可以直接与 Hugging Face Hub 和 Hugging Face Spaces 集成。你可以仅使用一行代码从 Hub 和 Spaces 加载在线的数千个模型。 ### 从 Hugging Face Hub 加载模型 [[从 Hugging Face Hub 加载模型]] -首先, 从 Hugging Face 通过 Hub 提供的数千个模型中选择一个, 如 [第四章](/course/chapter4/2) 中所述。 -使用特殊的 `Interface.load()` 方法, 你可以传递 `"model/"` (或者, 等效的, `"huggingface/"`) 后面是模型名称。例如, 这里是为大型语言模型 [GPT-J](https://huggingface.co/EleutherAI/gpt-j-6B)构建演示的代码, 添加几个示例输入: +首先,在 [第四章](/course/chapter4/2) 中所述的数千个模型中选择一个。 + +使用特殊的 `Interface.load()` 方法,你可以传递 `"model/模型名称"` (或等效的 `"huggingface/模型名称"` )。例如,下面的代码是使用 [GPT-J](https://huggingface.co/EleutherAI/gpt-j-6B) 构建的一个演示,它是一个大型语言模型,示例代码如下: ```py import gradio as gr @@ -20,32 +22,26 @@ 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() ``` -上述代码将生成以下界面: +运行上述代码将生成以下界面: -以这种方式加载模型使用 Hugging Face 的 [Inference API](https://huggingface.co/inference-api),而不是将模型加载到内存中。这对于像 GPT-J 或 T0pp这样需要大量 RAM 的大型模型是理想的。 +以这种方式加载模型使用的是 Hugging Face 的 [Inference API](https://huggingface.co/inference-api) ,而不是将模型加载到内存中。这对于像 GPT-J 或 T0pp 这样需要大量 RAM 的大型模型是最理想的方式。 + +### 从 Hugging Face Spaces 中加载 [[从 Hugging Face Spaces 中加载]] -### 从 Hugging Face Spaces 空间加载 [[从 Hugging Face Spaces 空间加载]] -要从hugs Face Hub加载任何空间并在本地重新创建它, 你可以将 `spaces/` 传递给 `Interface`, 再加上空间的名称。 +除了模型,还可以将 `spaces/space名称` 传递给 Interface,这样就可以在本地修改和运行 Hugging Face Spaces 中的任何 Space。 -还记得第 1 节中删除图像背景的演示吗? 让我们从 Hugging Face Spaces 加载它: +还记得第 1 节中删除图像背景的演示吗?让我们从 Hugging Face Spaces 加载它: ```py gr.Interface.load("spaces/abidlabs/remove-bg").launch() @@ -53,7 +49,7 @@ gr.Interface.load("spaces/abidlabs/remove-bg").launch() -从Hub或Spaces加载演示的一个很酷的地方是, 你可以通过覆盖任何参数来自定义它们。在这里, 我们添加一个标题并让它与网络摄像头一起使用: +从Hub 或 Spaces 加载演示的一个很酷的地方是,你可以通过覆盖任何参数来进行一些自定义的调整。在这里,我们添加一个标题并将输入改为了网络摄像头: ```py gr.Interface.load( @@ -63,4 +59,4 @@ gr.Interface.load( -现在我们已经探索了几种将Gradio与hugs Face Hub集成的方法, 让我们来看看 `Interface` 类的一些高级功能。这就是下一节的主题! \ No newline at end of file +现在我们已经探索了一些将 Gradio 与 hugs Face Hub 集成的方法,在下一节让我们来看看 `Interface` 类的一些高级功能。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter9/6.mdx b/chapters/zh-CN/chapter9/6.mdx index 2f0a939a8..e6a030aa5 100644 --- a/chapters/zh-CN/chapter9/6.mdx +++ b/chapters/zh-CN/chapter9/6.mdx @@ -1,4 +1,5 @@ -# 高级接口功能 [[高级接口功能]] +# Interface 的高级功能 [[Interface 的高级功能]] + -现在我们可以构建和共享一个基本接口, 让我们来探索一些更高级的特性, 如状态和解释。 +现在我们已经能够构建和共享基本界面了,让我们来探索一些 Interface 更高级的功能,比如会话状态和解释。 -### 使用状态保存数据 [[使用状态保存数据]] +### 使用会话状态保存数据 [[使用会话状态保存数据]] -Gradio 支持 *会话状态*, 其中数据在页面加载中的多个提交中持续存在。会话状态对于构建演示很有用, 例如, 你希望在用户与模型交互时保留数据的聊天机器人。请注意, 会话状态不会在模型的不同用户之间共享数据。 +Gradio 支持存储会话状态,以及保留多次提交的数据。会话状态对于构建聊天机器人等需要在用户与模型交互时保持数据的演示非常有用。请注意,会话状态不会在不同用户之间共享数据。 -要将数据存储在会话状态中, 你需要做三件事: +要将数据存储在会话状态中,你需要做三件事: -1. 向函数中传递一个 *额外的参数* , 该参数表示接口的状态。 -1. 在函数结束时, 将状态的更新值作为 *额外的返回值* 返回。 -1. 在创建`接口`时添加 'state' 输入和 'state' 输出组件。 +1. 向预测函数中传递一个额外的参数,该参数表示 Interface 此时的状态。 +2. 在预测函数结束时,将状态的更新值添加到函数的返回值中。 +3. 在创建 `Interface` 时添加 `state` 输入和 `state` 输出组件。 -请参阅下面的聊天机器人示例: +请参考下面的聊天机器人示例: ```py import random @@ -53,11 +54,12 @@ iface.launch() -请注意输出组件的状态如何在提交之间保持不变。注意: 可以给 state 参数传入一个默认值, 作为 state 的初始值。 +请注意,输出组件的每次提交都被记录了下来。 +注:我们还可以向状态参数传递一个默认值,该值会作为会话状态的初始值。 ### 通过解释来理解预测 [[通过解释来理解预测]] -大多数机器学习模型都是黑盒子, 函数的内部逻辑对终端用户是隐藏的。为了提高透明度, 我们通过简单地将 Interface 类中的解释关键字设置为默认值, 使向模型添加解释变得非常容易。这允许你的用户理解输入的哪些部分负责输出。看看下面这个简单的接口, 它显示了一个还包括解释的图像分类器: +大多数机器学习模型都是黑盒子,函数的内部逻辑对使用的用户是隐藏的。为了提高透明度,我们设计了一个功能,使得为模型添加解释非常简单,只需将 Interface 类中的 interpretation 参数设置为 `default` 即可。这样,你的用户就可以理解输入的哪些部分对输出有影响。请看下面的简单界面示例,显示了一个带有解释功能的图像分类器: ```py import requests @@ -65,9 +67,9 @@ import tensorflow as tf import gradio as gr -inception_net = tf.keras.applications.MobileNetV2() # load the model +inception_net = tf.keras.applications.MobileNetV2() # 加载模型 -# Download human-readable labels for ImageNet. +# 下载 ImageNet 的可读标签 response = requests.get("https://git.io/JJkYN") labels = response.text.split("\n") @@ -88,10 +90,11 @@ gr.Interface( ).launch() ``` -通过提交一个输入, 然后单击输出组件下的Interpret来测试解释功能。 +提交一个输入,单击输出组件中的“Interpret”按钮,将显示图像中的哪一部分区域对 +分类的结果产生了影响。 -除了Gradio提供的默认解释方法之外, 你还可以为 `interpretation` 参数指定 `shap`, 并设置 `num_shap` 参数。这使用基于 Shapley 的解释, 你可以在 [here](https://christophm.github.io/interpretable-ml-book/shap.html) 阅读更多信息。最后, 还可以将自己的解释函数传入 `interpretation` 参数。在Gradio的入门页面 [here](https://gradio.app/getting_started/) 中可以看到一个例子。 +除了 Gradio 提供的默认解释方法之外,你还可以在 `interpretation` 参数中指定 `shap` ,并设置 `num_shap` 参数。这将使用基于 Shapley 的解释方法,你可以在 [这里](https://christophm.github.io/interpretable-ml-book/shap.html) 阅读关于 `shap` 更多的信息。最后,还可以将自己的解释函数传入 `interpretation` 参数。在 Gradio 的 [开始页面](https://gradio.app/getting_started/) 中可以看到一个例子。 -这结束了我们对Gradio的`Interface`类的深入研究。正如我们所看到的, 这个类使用几行Python代码创建机器学习演示变得简单。然而, 有时你会想通过改变布局或链接多个预测函数来定制你的demo。如果我们能以某种方式将 `接口` 分成可定制的 "块", 那不是很好吗? 幸运的是, 有! 这是最后一部分的主题。 \ No newline at end of file +这就是我们对 Gradio 的 `Interface` 类的深入研究。正如我们所看到的,这个类使得用几行 Python 代码创建机器学习演示变得简单。然而,有时你会想通过改变布局或将多个预测函数链接在一起来定制你的演示。如果我们能以某种方式将 `Interface` 拆分可定制的 “块”,那不是很好吗?幸运的是,我们可以做到!这是最后一节的主题。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter9/7.mdx b/chapters/zh-CN/chapter9/7.mdx index 208a2f163..df418c83d 100644 --- a/chapters/zh-CN/chapter9/7.mdx +++ b/chapters/zh-CN/chapter9/7.mdx @@ -1,4 +1,4 @@ -# Gradio 块简介 [[Gradio 块简介]] +# Gradio Blocks 简介 [[Gradio Blocks 简介]] -在前面的部分中, 我们已经使用 `Interface` 类探索并创建了演示。在本节中, 我们将介绍我们 **新开发**的称为`gradio.Blocks`低级API。 +在之前的章节中,我们已经探索并使用 `Interface` 类创建了一些演示。在本章中,我们将介绍我们新开发的低级 API,名为 `gradio.Blocks` 。 -现在, `接口`和`块`之间有什么区别? +那么,`Interface` 和 `Blocks` 之间有什么区别呢? -- ⚡ `接口`: 一个高级 API, 让你只需提供输入和输出列表即可创建完整的机器学习演示。 +- ⚡ `Interface` :一个高级 API,你只需提供输入和输出列表即可创建完整的机器学习演示。 -- 🧱 `块`: :一个低级的 API, 它允许你完全控制你的应用程序的数据流和布局。您可以使用`块`(如 "构建块")构建非常复杂的多步骤应用程序。 +- 🧱 `Blocks` :一个低级的 API,你可以使用它来完全控制你的应用程序的数据流和布局。你可以使用 `Blocks` (类似于“构建的砖块”)构建非常复杂的多步骤应用程序。 -### 为什么要块 🧱? [[为什么要块 🧱?]] +### 为什么要使用 Blocks 🧱?[[ 为什么要使用 Blocks 🧱]] -正如我们在前几节中看到的, `Interface` 类允许你使用几行代码轻松创建成熟的机器学习demo。`Interface` API 非常易于使用, 但缺乏 `Blocks` API 提供的灵活性。例如, 你可能想要: +正如我们在前几节中看到的,通过 `Interface` 类可以仅仅使用几行代码轻松创建成熟的机器学习 demo。 `Interface` API 非常易于使用,但灵活性不如 `Blocks` API。例如,你可能想要: -- 将相关演示组合为一个web应用程序中的多个选项卡 -- 更改demo的布局, 例如指定输入和输出的位置 -- 具有多步骤接口, 其中一个模型的输出成为下一个模型的输入, 或者通常具有更灵活的数据流 -- 根据用户输入更改组件的属性 (例如, 下拉列表中的选项) 或其可见性 +- 将相关演示组合为一个 web 应用程序中的多个选项卡 +- 更改 demo 的布局,例如指定输入和输出的位置 +- 创建多步骤界面,其中一个模型的输出成为下一个模型的输入,或者通常具有更灵活的数据流 +- 根据用户输入更改组件的属性 (例如,下拉列表中的选项) 或隐藏/显示部分组件 -我们将在下面探讨所有这些概念。 +我们将在下面探讨所有这些需求。 -### 使用块创建简单demo [[使用块创建简单demo]] +### 使用块创建简单 demo [[使用块创建简单demo]] -安装 Gradio 后, 将以下代码作为 Python 脚本、Jupyter 笔记本或 Colab 笔记本运行。 +安装 Gradio 后,然后在 Python 脚本、Jupyter 笔记本或 Colab 笔记本运行下面的代码,就可以使用 Block 翻转字符串。 ```py import gradio as gr @@ -58,33 +58,33 @@ demo.launch() -上述简单示例介绍了块的4个基本概念: +上面简单示例介绍了 Blocks 的 4 个基本概念: -1. 块允许你允许你构建结合markdown、HTML、按钮和交互组件的web应用程序, 只需在一个带有gradio的Python中实例化对象。 +1. 通过在 `with gradio.Blocks` 上下文中实例化 Python 对象,Blocks 支持构建组合了 markdown、HTML、按钮和交互式组件的 Web 网页。 -🙋如果你不熟悉 Python 中的 `with` 语句, 我们建议你查看来自 Real Python 的极好的[教程](https://realpython.com/python-with-statement/)。看完后回到这里 🤗 +🙋如果你不熟悉 Python 中的 `with` 语句,我们建议你先查看优秀的 [realpython 教程](https://realpython.com/python-with-statement) 后再回来查看🤗。 -实例化组件的顺序很重要, 因为每个元素都按照创建的顺序呈现到 Web 应用程序中。(更复杂的布局在下面讨论) +实例化组件的顺序很重要,因为每个元素都按照创建的顺序渲染到 Web 网页中。(更复杂的布局将在下面讨论) -2. 你可以在代码中的任何位置定义常规 Python 函数, 并使用`块`在用户输入的情况下运行它们。在我们的示例中, 们有一个"翻转"输入文本的简单函数, 但你可以编写任何 Python 函数, 从简单的计算到处理机器学习模型的预测。 +2. 你可以在代码中的任何位置定义常规 Python 函数,并指定 `Blocks` 在用户输入的情况下运行它们。在我们的示例中,我们使用了一个可以“翻转”输入的文本简单的函数,这个函数可以是任意的Python 函数,从简单的计算到处理来自机器学习模型的预测等。 -3. 你可以将事件指定给任何`块`组件。这将在组件被单击、更改等情况下运行函数。当你分配一个事件时, 你传入三个参数: `fn`: 应该被调用的函数, `inputs`: 输入组件的(列表), 以及 `outputs`: 应该被调用的输出组件的(列表)。 +3. 你可以将事件指定给任何 `Blocks` 组件。这些事件可以支持在组件被单击、更改等情况下运行函数。当你分配一个事件时,你需要传入三个参数: `fn` :应该被调用的函数, `inputs` :输入组件(列表),以及 `outputs` :应该被调用的输出组件(列表)。 - 在上面的示例中, 当名为 `input` 的 `Textbox` 中的值发生变化时, 我们运行 `flip_text()` 函数。该事件读取`输入`中的值, 将其作为名称参数传递给 `flip_text()`, 然后它返回一个值, 该值被分配给我们的第二个名为 `output` 的 `Textbox`。 + 在上面的示例中,当名为 `input` 的 `Textbox` 中的值发生变化时,会自动触发事件运行 `flip_text()` 函数。该事件读取第一个名为 `input` 的 `Textbox` 中的值,将其作为参数传递给 `flip_text()` ,然后 `flip_text()` 会返回一个值,该值会被分配给我们的第二个名为 `output` 的 `Textbox` 。 - 要查看每个组件支持的事件列表, 请参阅 Gradio [文档](https://www.gradio.app/docs/)。 + 要查看每个组件所支持的事件列表,请参阅 Gradio [文档](https://www.gradio.app/docs) 。 -4. 块会根据你定义的事件触发器自动确定组件是否应该是交互式的 (接受用户输入)。在我们的示例中, 第一个文本框是交互式的, 因为它的值由 `flip_text()` 函数使用。第二个文本框不是交互式的, 因为它的值从不用作输入。在某些情况下, 你可能想要覆盖它, 你可以通过传递一个布尔值给组件的`交互`参数(例如 `gr.Textbox(placeholder="Flip this text", interactive=True)`)。 +4. Blocks 会根据你定义的事件触发器自动确定组件是否是交互式的 (可以接受用户输入)。在我们的示例中,第一个文本框是交互式的,因为它的值能够被 `flip_text()` 函数使用。第二个文本框不是交互式的,因为它的值从未用作输入。在某些情况下,你可能想要覆盖自动的判断,你可以给组件的 `interactive` 参数传递一个布尔值来覆盖自动的判断。(例如 `gr.Textbox(placeholder="Flip this text", interactive=True)` )。 ### 自定义演示的布局 [[自定义演示的布局]] -我们如何使用`块`来定制我们的演示的布局? 默认情况下, `块`在一列中垂直呈现创建的组件。你可以通过使用 `with gradio.Column():` 创建其他列或使用 `with gradio.Row():` 创建其他行并在这些上下文中创建组件来改变这一点。 +我们如何使用 `Blocks` 来定制我们的演示的布局?默认情况下, `Blocks` 在一列中垂直排列创建的组件。你可以通过使用 `with gradio.Column():` 创建一列或使用 `with gradio.Row():` 创建一行,并且还可以在这些列或者行的上下文中创建其他的组件来更改布局。 -你应该记住: 在 `列` 下创建的任何组件(这也是默认设置) 都将垂直布局。在 `Row` 下创建的任何组件都将水平布局, 类似于 [Web 开发中的 flexbox 模型](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox)。 +你应该记住:在 `Column` 下创建的任何组件(这也是默认设置) 都将垂直排列。在 `Row` 下创建的任何组件都将水平排列,类似于 [Web 开发中的 flexbox 模型](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox) 。 -最后, 你还可以使用 `with gradio.Tabs()` 上下文管理器为您的demo创建选项卡。在此上下文中, 您可以通过使用 `gradio.TabItem(name_of_tab):` 指定来创建多个选项卡。在 `gradio.TabItem(name_of_tab):` 中创建的任何组件都会出现在该选项卡中。 +最后,你还可以使用 `with gradio.Tabs()` 上下文管理器为你的 demo 创建选项卡。在做个饭上下文中,你可以通过使用 `gradio.TabItem(name_of_tab):` 指定来创建多个选项卡。在 `gradio.TabItem(name_of_tab):` 中创建的任何组件都会排列在该选项卡中。 -现在让我们在demo中添加一个 `flip_image()`函数并添加一个翻转图像的新选项卡。下面是一个带有 2 个选项卡的示例, 也使用了一个行: +现在让我们在 demo 中添加一个 `flip_image()` 函数并添加一个翻转图像的新选项卡。下面是具有 2 个选项卡并使用了一个 Row 的示例: ```py import numpy as np @@ -123,22 +123,21 @@ demo.launch() - -你会注意到, 在这个示例中, 我们还在每个选项卡中创建了一个 `Button` 组件, 并且我们为每个按钮分配了一个点击事件,这是实际运行该函数的事件。 +你会注意到,在本示例中,我们在每个选项卡中都创建了一个 `Button` 组件,并且为每个按钮分配了一个点击事件,点击后运行事件对应的函数。 ### 探索事件和状态 [[探索事件和状态]] -正如你可以控制布局一样, `块` 可以让你对触发函数调用的事件进行细粒度控制。每个组件和许多布局都有它们支持的特定事件。 +使用 `Blocks` 不仅可以控制布局,还可以让你对触发函数调用的事件进行细粒度控制。每个组件和许多布局都有它们支持的特定事件。 -例如, `Textbox` 组件有两个事件: `change()` (当文本框内的值发生变化时), 和 `submit()` (当用户在关注文本框时按下enter键)。更复杂的组件可以有更多的事件: 例如,`Audio`组件也有单独的事件, 用于播放、清除、暂停音频文件等。请参阅文档了解每个组件支持的事件。 +例如, `Textbox` 组件有两个事件: `change()` (当文本框内的值发生变化时),和 `submit()` (当用户在文本框上输入并按下回车键时)。更复杂的组件可以有更多的事件:例如, `Audio` 组件还具有播放音频文件、清除音频文件、暂停等各种独立事件。更多的独立事件请参阅每个组件支持的事件的文档。 -你可以将事件触发器附加到这些事件中的一个、一个或多个。你可以通过在组件实例中调用事件名称作为函数来创建一个事件触发器 -- 例如 `textbox.change(...)` 或 `btn.click(...)`。如前所述, 该函数接受三个参数: +你可以使用事件触发器将要运行的函数附加到一个、多个或全部事件中。例如,你可以通过在组件实例中调用事件名称作为函数来创建一个事件触发器 —— 例如 `textbox.change(...)` 或 `btn.click(...)` 。如前所述,该函数接受三个参数: -- `fn`: 要运行的函数 -- `inputs`: 组件的(列表), 其值应作为函数的输入参数提供。每个组件的值按顺序映射到相应的函数参数。如果函数不带任何参数, 则此参数可以为 None。 -- `outputs`: 应根据函数返回的值更新其值的组件(列表)。每个返回值按顺序设置相应组件的值。如果函数不返回任何内容, 则此参数可以为None。 +- `fn` :要运行的函数 +- `inputs` :应作为函数的输入参数提供的组件(列表)。每个组件的值按顺序映射到相应的函数参数。如果函数不带任何参数,则此参数可以为 None。 +- `outputs` :应根据函数返回的值更新的组件(列表)。函数的每个返回值都按照顺序赋值给相应组件。如果函数不返回任何内容,则此参数可以为 None。 -你甚至可以使输入和输出组件成为同一个组件, 就像我们在这个使用 GPT 模型进行文本补全的示例中所做的那样: +你甚至可以使输入和输出组件设置为同一个组件,就像我们在这个使用 GPT 模型进行文本补全的示例中所做的那样: ```py import gradio as gr @@ -147,7 +146,7 @@ api = gr.Interface.load("huggingface/EleutherAI/gpt-j-6B") def complete_with_gpt(text): - # Use the last 50 characters of the text as context + # 使用文本的最后 50 个字符作为上下文 return text[:-50] + api(text[-50:]) @@ -162,9 +161,9 @@ demo.launch() -### 创建多步demo [[创建多步demo]] +### 创建多步骤 demo [[创建多步骤 demo]] -在某些情况下, 您可能需要一个 _多步骤的demo_, 其中重用一个函数的输出作为下一个函数的输入。使用 `块` 很容易做到这一点, 因为你可以使用组件作为一个事件触发器的输入, 但作为另一个事件触发器的输出。看看下面示例中的文本组件, 它的值是语音到文本模型的结果, 但也被传递到情感分析模型: +在某些情况下,你可能需要一个多步骤演示,将其中一个函数的输出作为下一个函数的输入。使用 `Blocks` 很容易做到这一点,因为你可以使用一个组件作为一个事件触发器的输入,同时又作为另一个事件触发器的输出。看一下下面的示例,文本组件的输入是语音转文本模型的结果,同时,这个文本也被传递到情感分析模型中: ```py from transformers import pipeline @@ -204,9 +203,9 @@ demo.launch() ### 更新组件属性 [[更新组件属性]] -到目前为止, 我们已经了解了如何创建事件来更新另一个组件的值。但是, 如果您想更改组件的其他属性, 例如文本框的可见性或单选按钮组中的选项, 会发生什么? 您可以通过返回组件类的 `update()` 方法而不是函数的常规返回值来做到这一点。 +到目前为止,我们已经了解了如何创建事件来更新另一个组件的值。但是,如果你想更改组件的其他属性,例如文本框的显示/隐藏或单选按钮中的选项,又该怎么办呢?你可以通过返回组件类的 `update()` 方法来代替函数的常规返回值来做到这一点。 -这很容易用一个例子来说明: +这很容易用一个例子来说明: ```py import gradio as gr @@ -231,6 +230,6 @@ with gr.Blocks() as block: block.launch() ``` - + -我们刚刚探索了`块`的所有核心概念! 就像 `参数一样`, 你可以创建很酷的demo, 可以通过在`launch()`方法中使用`share=True`来共享, 或者部署在[Hugging Face Spaces](https://huggingface.co/spaces)上。 \ No newline at end of file +我们刚刚探索了 `Blocks` 的所有核心概念!就像 `Interfaces` 一样 你可以创建很酷的 demo,可以通过在 `launch()` 方法中使用 `share=True` 选项创建共享的链接,或者部署在 [Hugging Face Spaces](https://huggingface.co/spaces) 上。 \ No newline at end of file diff --git a/chapters/zh-CN/chapter9/8.mdx b/chapters/zh-CN/chapter9/8.mdx index 0382eb43b..33703ed4e 100644 --- a/chapters/zh-CN/chapter9/8.mdx +++ b/chapters/zh-CN/chapter9/8.mdx @@ -1,19 +1,19 @@ -# Gradio,回顾! [[Gradio,回顾!]] +# Gradio,回顾![[Gradio,回顾!]] -关于使用 Gradio 构建酷炫的 ML 演示的章节到此结束 - 我们希望您喜欢它!回顾一下,在本章中,我们学习了: +关于使用 Gradio 构建酷炫的 ML 演示的章节到此结束 —— 希望你会喜欢!回顾一下,在本章中,我们学习了: - 如何使用高级 `Interface` API 创建 Gradio 演示,以及如何配置不同的输入和输出模式。 -- 通过临时链接和托管在 [Hugging Face Spaces](https://huggingface.co/spaces) 上共享 Gradio 演示的不同方式。 -- 如何将 Gradio 演示与 Hugging Face Hub 上的Model和Space集成在一起。 -- 高级功能,例如在演示中存储状态或提供身份验证。 +- 使用临时链接或者托管在 [Hugging Face Spaces](https://huggingface.co/spaces) 上共享 Gradio 演示。 +- 如何将 Gradio 演示与 Hugging Face Hub 上的 Model 和 Space 集成在一起。 +- 高级功能,例如在演示中存储状态或模型解释等高级功能。 - 如何使用 Gradio Blocks 完全控制演示的数据流和布局。 -如果您想测试您对本章所涵盖概念的理解,请查看下一节中的测验! +如果你想测试你对本章所涵盖概念的理解,请查看下一节中的测验! -## 下一步去哪里? [[下一步去哪里?]] +## 下一步去哪里?[[下一步去哪里?]] -如果您想了解有关 Gradio 的更多信息,您可以 +如果你想了解有关 Gradio 的更多信息,你可以 -- 看看 repo 中的 [Demos](https://github.com/gradio-app/gradio/tree/main/demo),那里有很多例子。 -- 请参阅 [指南](https://gradio.app/guides/) 页面,您可以在其中找到有关酷炫和高级功能的指南。 -- 查看 [文档](https://gradio.app/docs/) 页面了解详情。 +- 看看仓库中的 [Demos](https://github.com/gradio-app/gradio/tree/main/demo) ,那里有很多例子。 +- 请参阅 [指南](https://gradio.app/guides) 页面,你可以在其中找到有关酷炫和高级功能的指南。 +- 查看 [文档](https://gradio.app/docs) 页面了解详细信息。 diff --git a/chapters/zh-CN/chapter9/9.mdx b/chapters/zh-CN/chapter9/9.mdx index 9027af836..7e46efe6f 100644 --- a/chapters/zh-CN/chapter9/9.mdx +++ b/chapters/zh-CN/chapter9/9.mdx @@ -9,115 +9,115 @@ classNames="absolute z-10 right-0 top-0" /> -让我们测试一下您在本章中学到了什么! +让我们测试一下你在本章中学到了什么! -### 1.你能利用Grado做什么? +### 1. 你能使用Gradio做什么? share = True 参数,可以生成一个共享链接发送给任何人。", + explain: "在启动方法中添加 share = True 参数,可以生成一个共享链接发送给任何人。", correct: true }, { - text: "调试模型", - explain: "Grado演示的一个优点是能够用真实数据测试模型,您可以实时更改并观察模型的预测变化,从而帮助您调试模型。", + text: "测试模型的效果", + explain: "Gradio的一个优点是能够用真实数据测试模型,可以实时更改输入并观察模型的预测变化,从而帮助你测试模型。", correct: true }, { text: "训练你的模型", - explain: "在你的模型被训练之后,Grado 被设计用来进行模型推理。", + explain: "在你的模型被训练之后,Gradio 用来进行模型推理。", } ]} /> -### 2.Grado只在 PyTorch 模型上工作 +### 2. Gradio只能在 PyTorch 模型上工作 -### 3.你可以在哪里发布一个 GRadio 演示? +### 3. 可以在哪里运行一个 Gradio 演示? -### 4.Gdio 主要是为 NLP 模型设计的 +### 4. Gradio 主要是为 NLP 模型设计的 -### 5.下列哪些特性是由 Grado 支持的? +### 5. 下列哪些特性是由 Gradio 支持的? gr. Interface.load () 方法加载任何 Hugging Face 模型", + text: "从 HuggingFace 的模型中心或 HuggingFace Space 加载模型", + explain: "正确 —— 使用 gr.Interface.load () 方法可以加载任何 Hugging Face 模型", correct: true } ]} /> -### 6.下列哪一种是从 Hub 或 Spaces 加载 Huggging Face 模型的有效方法? +### 6. 下列哪一种是从 Hub 或 Spaces 加载 Huggging Face 模型的有效方法? -### 7.创建您的 Gradio 接口时,您必须添加以下步骤: +### 7. 创建你的 Gradio Interface时,必须添加以下步骤: -### 8.Gradio 库包括以下哪些组件? +### 8. Gradio 库包括以下哪些组件? -### 9.Gradio允许你做什么? +### 9. 你可以使用 Gradio 做些什么? -### 10.你可以共享一个`Blocks`演示的公共链接,并创建一个`Blocks`的演示在HuggingFace空间。 +### 10. 你可以共享一个`Blocks`演示的公共链接,并在HuggingFace space创建一个`Blocks`的演示。 + [🤗 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/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/6.mdx b/chapters/zh-TW/chapter5/6.mdx index 24bb8e9dd..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/about_map_batch#batch-mapping) 對這個任務很有用。 +✏️ **試試看!** 看看能不能不用pandas就可以完成列的擴充; 這有點棘手; 你可能會發現 🤗 Datasets 文檔的 ["Batch mapping"](https://huggingface.co/docs/datasets/about_map_batch#batch-mapping) 對這個任務很有用。 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/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)"